diff options
391 files changed, 9350 insertions, 4925 deletions
diff --git a/.appveyor.yml b/.appveyor.yml index 7250d4ad94..097874b17a 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -10,7 +10,7 @@ environment: QT_DOWNLOAD_URL: 'https://github.com/sipsorcery/qt_win_binary/releases/download/qt598x64_vs2019_v1681/qt598_x64_vs2019_1681.zip' QT_DOWNLOAD_HASH: '00cf7327818c07d74e0b1a4464ffe987c2728b00d49d4bf333065892af0515c3' QT_LOCAL_PATH: 'C:\Qt5.9.8_x64_static_vs2019' - VCPKG_TAG: '2020.11-1' + VCPKG_TAG: '75522bb1f2e7d863078bcd06322348f053a9e33f' install: # Disable zmq test for now since python zmq library on Windows would cause Access violation sometimes. # - cmd: pip install zmq diff --git a/.cirrus.yml b/.cirrus.yml index 691582239e..d7d5ab6c5c 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: @@ -168,15 +168,25 @@ task: FILE_ENV: "./ci/test/00_setup_env_mac.sh" task: - name: 'macOS 10.15 native [gui] [no depends]' + name: 'macOS 11 native [gui] [no depends]' macos_brew_addon_script: - brew install boost libevent berkeley-db4 qt miniupnpc libnatpmp ccache zeromq qrencode sqlite libtool automake pkg-config gnu-getopt << : *GLOBAL_TASK_TEMPLATE osx_instance: # Use latest image, but hardcode version to avoid silent upgrades (and breaks) - image: catalina-xcode-12.1 # https://cirrus-ci.org/guide/macOS + image: big-sur-xcode-12.4 # https://cirrus-ci.org/guide/macOS env: DANGER_RUN_CI_ON_HOST: "true" 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/.gitignore b/.gitignore index 010586ee5a..c0b5661464 100644 --- a/.gitignore +++ b/.gitignore @@ -10,8 +10,7 @@ src/bitcoin-node src/bitcoin-tx src/bitcoin-util src/bitcoin-wallet -src/test/fuzz/* -!src/test/fuzz/*.* +src/test/fuzz/fuzz src/test/test_bitcoin src/qt/test/test_bitcoin-qt 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-aux/m4/bitcoin_qt.m4 b/build-aux/m4/bitcoin_qt.m4 index 658dfc5476..cce06e2fff 100644 --- a/build-aux/m4/bitcoin_qt.m4 +++ b/build-aux/m4/bitcoin_qt.m4 @@ -64,6 +64,13 @@ AC_DEFUN([BITCOIN_QT_INIT],[ ], [bitcoin_qt_want_version=auto]) + AS_IF([test "x$with_gui" = xqt5_debug], + [AS_CASE([$host], + [*darwin*], [qt_lib_suffix=_debug], + [*mingw*], [qt_lib_suffix=d], + [qt_lib_suffix= ]); bitcoin_qt_want_version=qt5], + [qt_lib_suffix= ]) + AC_ARG_WITH([qt-incdir],[AS_HELP_STRING([--with-qt-incdir=INC_DIR],[specify qt include path (overridden by pkgconfig)])], [qt_include_path=$withval], []) AC_ARG_WITH([qt-libdir],[AS_HELP_STRING([--with-qt-libdir=LIB_DIR],[specify qt lib path (overridden by pkgconfig)])], [qt_lib_path=$withval], []) AC_ARG_WITH([qt-plugindir],[AS_HELP_STRING([--with-qt-plugindir=PLUGIN_DIR],[specify qt plugin path (overridden by pkgconfig)])], [qt_plugin_path=$withval], []) @@ -104,9 +111,7 @@ AC_DEFUN([BITCOIN_QT_CONFIGURE],[ dnl For Qt5, we can check a header to find out whether Qt is build dnl statically. When Qt is built statically, some plugins must be linked into dnl the final binary as well. - dnl With Qt5, languages moved into core and the WindowsIntegration plugin was - dnl added. - dnl _BITCOIN_QT_CHECK_STATIC_PLUGINS does a quick link-check and appends the + dnl _BITCOIN_QT_CHECK_STATIC_PLUGIN does a quick link-check and appends the dnl results to QT_LIBS. BITCOIN_QT_CHECK([ TEMP_CPPFLAGS=$CPPFLAGS @@ -115,20 +120,46 @@ AC_DEFUN([BITCOIN_QT_CONFIGURE],[ CXXFLAGS="$PIC_FLAGS $CXXFLAGS" _BITCOIN_QT_IS_STATIC if test "x$bitcoin_cv_static_qt" = xyes; then - _BITCOIN_QT_FIND_STATIC_PLUGINS + _BITCOIN_QT_CHECK_STATIC_LIBS + + if test "x$qt_plugin_path" != x; then + if test -d "$qt_plugin_path/platforms"; then + QT_LIBS="$QT_LIBS -L$qt_plugin_path/platforms" + fi + if test -d "$qt_plugin_path/styles"; then + QT_LIBS="$QT_LIBS -L$qt_plugin_path/styles" + fi + if test -d "$qt_plugin_path/accessible"; then + QT_LIBS="$QT_LIBS -L$qt_plugin_path/accessible" + fi + if test -d "$qt_plugin_path/platforms/android"; then + QT_LIBS="$QT_LIBS -L$qt_plugin_path/platforms/android -lqtfreetype -lEGL" + fi + fi + AC_DEFINE(QT_STATICPLUGIN, 1, [Define this symbol if qt plugins are static]) if test "x$TARGET_OS" != xandroid; then - _BITCOIN_QT_CHECK_STATIC_PLUGINS([Q_IMPORT_PLUGIN(QMinimalIntegrationPlugin)],[-lqminimal]) + _BITCOIN_QT_CHECK_STATIC_PLUGIN([QMinimalIntegrationPlugin], [-lqminimal]) AC_DEFINE(QT_QPA_PLATFORM_MINIMAL, 1, [Define this symbol if the minimal qt platform exists]) fi if test "x$TARGET_OS" = xwindows; then - _BITCOIN_QT_CHECK_STATIC_PLUGINS([Q_IMPORT_PLUGIN(QWindowsIntegrationPlugin)],[-lqwindows]) + dnl Linking against wtsapi32 is required. See #17749 and + dnl https://bugreports.qt.io/browse/QTBUG-27097. + AX_CHECK_LINK_FLAG([-lwtsapi32], [QT_LIBS="$QT_LIBS -lwtsapi32"], [AC_MSG_ERROR([could not link against -lwtsapi32])]) + _BITCOIN_QT_CHECK_STATIC_PLUGIN([QWindowsIntegrationPlugin], [-lqwindows]) AC_DEFINE(QT_QPA_PLATFORM_WINDOWS, 1, [Define this symbol if the qt platform is windows]) elif test "x$TARGET_OS" = xlinux; then - _BITCOIN_QT_CHECK_STATIC_PLUGINS([Q_IMPORT_PLUGIN(QXcbIntegrationPlugin)],[-lqxcb -lxcb-static]) + dnl workaround for https://bugreports.qt.io/browse/QTBUG-74874 + AX_CHECK_LINK_FLAG([-lxcb-shm], [QT_LIBS="-lxcb-shm $QT_LIBS"], [AC_MSG_ERROR([could not link against -lxcb-shm])]) + _BITCOIN_QT_CHECK_STATIC_PLUGIN([QXcbIntegrationPlugin], [-lqxcb]) AC_DEFINE(QT_QPA_PLATFORM_XCB, 1, [Define this symbol if the qt platform is xcb]) elif test "x$TARGET_OS" = xdarwin; then - _BITCOIN_QT_CHECK_STATIC_PLUGINS([Q_IMPORT_PLUGIN(QCocoaIntegrationPlugin)],[-lqcocoa]) + AX_CHECK_LINK_FLAG([[-framework Carbon]],[QT_LIBS="$QT_LIBS -framework Carbon"],[AC_MSG_ERROR(could not link against Carbon framework)]) + AX_CHECK_LINK_FLAG([[-framework IOSurface]],[QT_LIBS="$QT_LIBS -framework IOSurface"],[AC_MSG_ERROR(could not link against IOSurface framework)]) + AX_CHECK_LINK_FLAG([[-framework Metal]],[QT_LIBS="$QT_LIBS -framework Metal"],[AC_MSG_ERROR(could not link against Metal framework)]) + AX_CHECK_LINK_FLAG([[-framework QuartzCore]],[QT_LIBS="$QT_LIBS -framework QuartzCore"],[AC_MSG_ERROR(could not link against QuartzCore framework)]) + _BITCOIN_QT_CHECK_STATIC_PLUGIN([QCocoaIntegrationPlugin], [-lqcocoa]) + _BITCOIN_QT_CHECK_STATIC_PLUGIN([QMacStylePlugin], [-lqmacstyle]) AC_DEFINE(QT_QPA_PLATFORM_COCOA, 1, [Define this symbol if the qt platform is cocoa]) elif test "x$TARGET_OS" = xandroid; then QT_LIBS="-Wl,--export-dynamic,--undefined=JNI_OnLoad -lqtforandroid -ljnigraphics -landroid -lqtfreetype -lQt5EglSupport $QT_LIBS" @@ -277,51 +308,50 @@ AC_DEFUN([_BITCOIN_QT_IS_STATIC],[ ]) ]) -dnl Internal. Check if the link-requirements for static plugins are met. +dnl Internal. Check if the link-requirements for a static plugin are met. +dnl +dnl _BITCOIN_QT_CHECK_STATIC_PLUGIN(PLUGIN, LIBRARIES) +dnl -------------------------------------------------- +dnl dnl Requires: INCLUDES and LIBS must be populated as necessary. -dnl Inputs: $1: A series of Q_IMPORT_PLUGIN(). +dnl Inputs: $1: A static plugin name. dnl Inputs: $2: The libraries that resolve $1. dnl Output: QT_LIBS is prepended or configure exits. -AC_DEFUN([_BITCOIN_QT_CHECK_STATIC_PLUGINS],[ - AC_MSG_CHECKING(for static Qt plugins: $2) +AC_DEFUN([_BITCOIN_QT_CHECK_STATIC_PLUGIN], [ + AC_MSG_CHECKING([for $1 ($2)]) CHECK_STATIC_PLUGINS_TEMP_LIBS="$LIBS" - LIBS="$2 $QT_LIBS $LIBS" + LIBS="$2${qt_lib_suffix} $QT_LIBS $LIBS" AC_LINK_IFELSE([AC_LANG_PROGRAM([[ - #define QT_STATICPLUGIN - #include <QtPlugin> - $1]], - [[return 0;]])], - [AC_MSG_RESULT(yes); QT_LIBS="$2 $QT_LIBS"], - [AC_MSG_RESULT(no); BITCOIN_QT_FAIL(Could not resolve: $2)]) + #include <QtPlugin> + Q_IMPORT_PLUGIN($1) + ]])], + [AC_MSG_RESULT([yes]); QT_LIBS="$2${qt_lib_suffix} $QT_LIBS"], + [AC_MSG_RESULT([no]); BITCOIN_QT_FAIL([$1 not found.])]) LIBS="$CHECK_STATIC_PLUGINS_TEMP_LIBS" ]) -dnl Internal. Find paths necessary for linking qt static plugins -dnl Inputs: qt_plugin_path. optional. -dnl Outputs: QT_LIBS is appended -AC_DEFUN([_BITCOIN_QT_FIND_STATIC_PLUGINS],[ - if test "x$qt_plugin_path" != x; then - QT_LIBS="$QT_LIBS -L$qt_plugin_path/platforms" - if test -d "$qt_plugin_path/accessible"; then - QT_LIBS="$QT_LIBS -L$qt_plugin_path/accessible" - fi - if test -d "$qt_plugin_path/platforms/android"; then - QT_LIBS="$QT_LIBS -L$qt_plugin_path/platforms/android -lqtfreetype -lEGL" - fi - PKG_CHECK_MODULES([QTFONTDATABASE], [Qt5FontDatabaseSupport], [QT_LIBS="-lQt5FontDatabaseSupport $QT_LIBS"]) - PKG_CHECK_MODULES([QTEVENTDISPATCHER], [Qt5EventDispatcherSupport], [QT_LIBS="-lQt5EventDispatcherSupport $QT_LIBS"]) - PKG_CHECK_MODULES([QTTHEME], [Qt5ThemeSupport], [QT_LIBS="-lQt5ThemeSupport $QT_LIBS"]) - PKG_CHECK_MODULES([QTDEVICEDISCOVERY], [Qt5DeviceDiscoverySupport], [QT_LIBS="-lQt5DeviceDiscoverySupport $QT_LIBS"]) - PKG_CHECK_MODULES([QTACCESSIBILITY], [Qt5AccessibilitySupport], [QT_LIBS="-lQt5AccessibilitySupport $QT_LIBS"]) - PKG_CHECK_MODULES([QTFB], [Qt5FbSupport], [QT_LIBS="-lQt5FbSupport $QT_LIBS"]) - if test "x$TARGET_OS" = xlinux; then - PKG_CHECK_MODULES([QTXCBQPA], [Qt5XcbQpa], [QT_LIBS="$QTXCBQPA_LIBS $QT_LIBS"]) - elif test "x$TARGET_OS" = xdarwin; then - PKG_CHECK_MODULES([QTCLIPBOARD], [Qt5ClipboardSupport], [QT_LIBS="-lQt5ClipboardSupport $QT_LIBS"]) - PKG_CHECK_MODULES([QTGRAPHICS], [Qt5GraphicsSupport], [QT_LIBS="-lQt5GraphicsSupport $QT_LIBS"]) - PKG_CHECK_MODULES([QTCGL], [Qt5CglSupport], [QT_LIBS="-lQt5CglSupport $QT_LIBS"]) - fi - fi +dnl Internal. Check Qt static libs with PKG_CHECK_MODULES. +dnl +dnl _BITCOIN_QT_CHECK_STATIC_LIBS +dnl ----------------------------- +dnl +dnl Inputs: no inputs. +dnl Outputs: QT_LIBS is prepended. +AC_DEFUN([_BITCOIN_QT_CHECK_STATIC_LIBS], [ + PKG_CHECK_MODULES([QTFONTDATABASE], [Qt5FontDatabaseSupport${qt_lib_suffix}], [QT_LIBS="-lQt5FontDatabaseSupport${qt_lib_suffix} $QT_LIBS"]) + PKG_CHECK_MODULES([QTEVENTDISPATCHER], [Qt5EventDispatcherSupport${qt_lib_suffix}], [QT_LIBS="-lQt5EventDispatcherSupport${qt_lib_suffix} $QT_LIBS"]) + PKG_CHECK_MODULES([QTTHEME], [Qt5ThemeSupport${qt_lib_suffix}], [QT_LIBS="-lQt5ThemeSupport${qt_lib_suffix} $QT_LIBS"]) + PKG_CHECK_MODULES([QTDEVICEDISCOVERY], [Qt5DeviceDiscoverySupport${qt_lib_suffix}], [QT_LIBS="-lQt5DeviceDiscoverySupport${qt_lib_suffix} $QT_LIBS"]) + PKG_CHECK_MODULES([QTACCESSIBILITY], [Qt5AccessibilitySupport${qt_lib_suffix}], [QT_LIBS="-lQt5AccessibilitySupport${qt_lib_suffix} $QT_LIBS"]) + PKG_CHECK_MODULES([QTFB], [Qt5FbSupport${qt_lib_suffix}], [QT_LIBS="-lQt5FbSupport${qt_lib_suffix} $QT_LIBS"]) + if test "x$TARGET_OS" = xlinux; then + PKG_CHECK_MODULES([QTXCBQPA], [Qt5XcbQpa], [QT_LIBS="$QTXCBQPA_LIBS $QT_LIBS"]) + elif test "x$TARGET_OS" = xdarwin; then + PKG_CHECK_MODULES([QTCLIPBOARD], [Qt5ClipboardSupport${qt_lib_suffix}], [QT_LIBS="-lQt5ClipboardSupport${qt_lib_suffix} $QT_LIBS"]) + PKG_CHECK_MODULES([QTGRAPHICS], [Qt5GraphicsSupport${qt_lib_suffix}], [QT_LIBS="-lQt5GraphicsSupport${qt_lib_suffix} $QT_LIBS"]) + elif test "x$TARGET_OS" = xwindows; then + PKG_CHECK_MODULES([QTWINDOWSUIAUTOMATION], [Qt5WindowsUIAutomationSupport${qt_lib_suffix}], [QT_LIBS="-lQt5WindowsUIAutomationSupport${qt_lib_suffix} $QT_LIBS"]) + fi ]) dnl Internal. Find Qt libraries using pkg-config. @@ -329,26 +359,26 @@ dnl Outputs: All necessary QT_* variables are set. dnl Outputs: have_qt_test and have_qt_dbus are set (if applicable) to yes|no. AC_DEFUN([_BITCOIN_QT_FIND_LIBS],[ BITCOIN_QT_CHECK([ - PKG_CHECK_MODULES([QT_CORE], [${qt_lib_prefix}Core $qt_version], [], - [BITCOIN_QT_FAIL([${qt_lib_prefix}Core $qt_version not found])]) + PKG_CHECK_MODULES([QT_CORE], [${qt_lib_prefix}Core${qt_lib_suffix} $qt_version], [], + [BITCOIN_QT_FAIL([${qt_lib_prefix}Core${qt_lib_suffix} $qt_version not found])]) ]) BITCOIN_QT_CHECK([ - PKG_CHECK_MODULES([QT_GUI], [${qt_lib_prefix}Gui $qt_version], [], - [BITCOIN_QT_FAIL([${qt_lib_prefix}Gui $qt_version not found])]) + PKG_CHECK_MODULES([QT_GUI], [${qt_lib_prefix}Gui${qt_lib_suffix} $qt_version], [], + [BITCOIN_QT_FAIL([${qt_lib_prefix}Gui${qt_lib_suffix} $qt_version not found])]) ]) BITCOIN_QT_CHECK([ - PKG_CHECK_MODULES([QT_WIDGETS], [${qt_lib_prefix}Widgets $qt_version], [], - [BITCOIN_QT_FAIL([${qt_lib_prefix}Widgets $qt_version not found])]) + PKG_CHECK_MODULES([QT_WIDGETS], [${qt_lib_prefix}Widgets${qt_lib_suffix} $qt_version], [], + [BITCOIN_QT_FAIL([${qt_lib_prefix}Widgets${qt_lib_suffix} $qt_version not found])]) ]) BITCOIN_QT_CHECK([ - PKG_CHECK_MODULES([QT_NETWORK], [${qt_lib_prefix}Network $qt_version], [], - [BITCOIN_QT_FAIL([${qt_lib_prefix}Network $qt_version not found])]) + PKG_CHECK_MODULES([QT_NETWORK], [${qt_lib_prefix}Network${qt_lib_suffix} $qt_version], [], + [BITCOIN_QT_FAIL([${qt_lib_prefix}Network${qt_lib_suffix} $qt_version not found])]) ]) QT_INCLUDES="$QT_CORE_CFLAGS $QT_GUI_CFLAGS $QT_WIDGETS_CFLAGS $QT_NETWORK_CFLAGS" QT_LIBS="$QT_CORE_LIBS $QT_GUI_LIBS $QT_WIDGETS_LIBS $QT_NETWORK_LIBS" BITCOIN_QT_CHECK([ - PKG_CHECK_MODULES([QT_TEST], [${qt_lib_prefix}Test $qt_version], [QT_TEST_INCLUDES="$QT_TEST_CFLAGS"; have_qt_test=yes], [have_qt_test=no]) + PKG_CHECK_MODULES([QT_TEST], [${qt_lib_prefix}Test${qt_lib_suffix} $qt_version], [QT_TEST_INCLUDES="$QT_TEST_CFLAGS"; have_qt_test=yes], [have_qt_test=no]) if test "x$use_dbus" != xno; then PKG_CHECK_MODULES([QT_DBUS], [${qt_lib_prefix}DBus $qt_version], [QT_DBUS_INCLUDES="$QT_DBUS_CFLAGS"; have_qt_dbus=yes], [have_qt_dbus=no]) fi diff --git a/build-aux/m4/l_socket.m4 b/build-aux/m4/l_socket.m4 new file mode 100644 index 0000000000..38923a98fc --- /dev/null +++ b/build-aux/m4/l_socket.m4 @@ -0,0 +1,36 @@ +# Illumos/SmartOS requires linking with -lsocket if +# using getifaddrs & freeifaddrs + +m4_define([_CHECK_SOCKET_testbody], [[ + #include <sys/types.h> + #include <ifaddrs.h> + + int main() { + struct ifaddrs *ifaddr; + getifaddrs(&ifaddr); + freeifaddrs(ifaddr); + } +]]) + +AC_DEFUN([CHECK_SOCKET], [ + + AC_LANG_PUSH(C++) + + AC_MSG_CHECKING([whether ifaddrs funcs can be used without link library]) + + AC_LINK_IFELSE([AC_LANG_SOURCE([_CHECK_SOCKET_testbody])],[ + AC_MSG_RESULT([yes]) + ],[ + AC_MSG_RESULT([no]) + LIBS="$LIBS -lsocket" + AC_MSG_CHECKING([whether getifaddrs needs -lsocket]) + AC_LINK_IFELSE([AC_LANG_SOURCE([_CHECK_SOCKET_testbody])],[ + AC_MSG_RESULT([yes]) + ],[ + AC_MSG_RESULT([no]) + AC_MSG_FAILURE([cannot figure out how to use getifaddrs]) + ]) + ]) + + AC_LANG_POP +]) diff --git a/build_msvc/bitcoin_config.h b/build_msvc/bitcoin_config.h index 40a30b9749..aba5607eb6 100644 --- a/build_msvc/bitcoin_config.h +++ b/build_msvc/bitcoin_config.h @@ -92,9 +92,9 @@ don't. */ #define HAVE_DECL_BSWAP_64 0 -/* Define to 1 if you have the declaration of `daemon', and to 0 if you don't. +/* Define to 1 if you have the declaration of `fork', and to 0 if you don't. */ -#define HAVE_DECL_DAEMON 0 +#define HAVE_DECL_FORK 0 /* Define to 1 if you have the declaration of `htobe16', and to 0 if you don't. */ @@ -132,6 +132,10 @@ don't. */ #define HAVE_DECL_LE64TOH 0 +/* Define to 1 if you have the declaration of `setsid', and to 0 if you don't. + */ +#define HAVE_DECL_SETSID 0 + /* Define to 1 if you have the declaration of `strerror_r', and to 0 if you don't. */ #define HAVE_DECL_STRERROR_R 0 @@ -177,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/build_msvc/common.init.vcxproj b/build_msvc/common.init.vcxproj index a3487c7a0d..f195d3d451 100644 --- a/build_msvc/common.init.vcxproj +++ b/build_msvc/common.init.vcxproj @@ -97,7 +97,7 @@ <WarningLevel>Level3</WarningLevel> <PrecompiledHeader>NotUsing</PrecompiledHeader> <AdditionalOptions>/utf-8 /Zc:__cplusplus /std:c++17 %(AdditionalOptions)</AdditionalOptions> - <DisableSpecificWarnings>4018;4221;4244;4267;4334;4715;4805;4834</DisableSpecificWarnings> + <DisableSpecificWarnings>4018;4244;4267;4334;4715;4805;4834</DisableSpecificWarnings> <TreatWarningAsError>true</TreatWarningAsError> <PreprocessorDefinitions>_SILENCE_CXX17_CODECVT_HEADER_DEPRECATION_WARNING;_SILENCE_CXX17_OLD_ALLOCATOR_MEMBERS_DEPRECATION_WARNING;ZMQ_STATIC;NOMINMAX;WIN32;HAVE_CONFIG_H;_CRT_SECURE_NO_WARNINGS;_SCL_SECURE_NO_WARNINGS;_CONSOLE;_WIN32_WINNT=0x0601;_WIN32_IE=0x0501;WIN32_LEAN_AND_MEAN;%(PreprocessorDefinitions)</PreprocessorDefinitions> <AdditionalIncludeDirectories>..\..\src;..\..\src\univalue\include;..\..\src\secp256k1\include;..\..\src\leveldb\include;..\..\src\leveldb\helpers\memenv;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> diff --git a/build_msvc/libleveldb/libleveldb.vcxproj b/build_msvc/libleveldb/libleveldb.vcxproj index 1610ae7d86..009be30dec 100644 --- a/build_msvc/libleveldb/libleveldb.vcxproj +++ b/build_msvc/libleveldb/libleveldb.vcxproj @@ -51,7 +51,7 @@ <ItemDefinitionGroup> <ClCompile> <PreprocessorDefinitions>HAVE_CRC32C=0;HAVE_SNAPPY=0;__STDC_LIMIT_MACROS;LEVELDB_IS_BIG_ENDIAN=0;_UNICODE;UNICODE;_CRT_NONSTDC_NO_DEPRECATE;LEVELDB_PLATFORM_WINDOWS;LEVELDB_ATOMIC_PRESENT;%(PreprocessorDefinitions)</PreprocessorDefinitions> - <DisableSpecificWarnings>4244;4267;4312;4722;</DisableSpecificWarnings> + <DisableSpecificWarnings>4244;4267</DisableSpecificWarnings> <AdditionalIncludeDirectories>..\..\src\leveldb;..\..\src\leveldb\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> </ClCompile> </ItemDefinitionGroup> diff --git a/build_msvc/test_bitcoin-qt/test_bitcoin-qt.vcxproj b/build_msvc/test_bitcoin-qt/test_bitcoin-qt.vcxproj index 1ddd62edf2..1d2c86b7ac 100644 --- a/build_msvc/test_bitcoin-qt/test_bitcoin-qt.vcxproj +++ b/build_msvc/test_bitcoin-qt/test_bitcoin-qt.vcxproj @@ -11,7 +11,6 @@ <ClCompile Include="..\..\src\test\util\setup_common.cpp" /> <ClCompile Include="..\..\src\qt\test\addressbooktests.cpp" /> <ClCompile Include="..\..\src\qt\test\apptests.cpp" /> - <ClCompile Include="..\..\src\qt\test\compattests.cpp" /> <ClCompile Include="..\..\src\qt\test\rpcnestedtests.cpp" /> <ClCompile Include="..\..\src\qt\test\test_main.cpp" /> <ClCompile Include="..\..\src\qt\test\uritests.cpp" /> @@ -20,7 +19,6 @@ <ClCompile Include="..\..\src\wallet\test\wallet_test_fixture.cpp" /> <ClCompile Include="$(GeneratedFilesOutDir)\moc\moc_addressbooktests.cpp" /> <ClCompile Include="$(GeneratedFilesOutDir)\moc\moc_apptests.cpp" /> - <ClCompile Include="$(GeneratedFilesOutDir)\moc\moc_compattests.cpp" /> <ClCompile Include="$(GeneratedFilesOutDir)\moc\moc_rpcnestedtests.cpp" /> <ClCompile Include="$(GeneratedFilesOutDir)\moc\moc_uritests.cpp" /> <ClCompile Include="$(GeneratedFilesOutDir)\moc\moc_wallettests.cpp" /> @@ -89,7 +87,6 @@ <ItemGroup> <MocTestFiles Include="..\..\src\qt\test\addressbooktests.h" /> <MocTestFiles Include="..\..\src\qt\test\apptests.h" /> - <MocTestFiles Include="..\..\src\qt\test\compattests.h" /> <MocTestFiles Include="..\..\src\qt\test\rpcnestedtests.h" /> <MocTestFiles Include="..\..\src\qt\test\uritests.h" /> <MocTestFiles Include="..\..\src\qt\test\wallettests.h" /> diff --git a/ci/test/00_setup_env.sh b/ci/test/00_setup_env.sh index 72e29141a6..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,16 +59,13 @@ 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). export BASE_BUILD_DIR=${BASE_BUILD_DIR:-$BASE_SCRATCH_DIR/build} export PREVIOUS_RELEASES_DIR=${PREVIOUS_RELEASES_DIR:-$BASE_ROOT_DIR/releases/$HOST} export SDK_URL=${SDK_URL:-https://bitcoincore.org/depends-sources/sdks} -export DOCKER_PACKAGES=${DOCKER_PACKAGES:-build-essential libtool autotools-dev automake pkg-config bsdmainutils curl ca-certificates ccache python3 rsync git procps} +export DOCKER_PACKAGES=${DOCKER_PACKAGES:-build-essential libtool autotools-dev automake pkg-config bsdmainutils curl ca-certificates ccache python3 rsync git procps bison} export GOAL=${GOAL:-install} export DIR_QA_ASSETS=${DIR_QA_ASSETS:-${BASE_SCRATCH_DIR}/qa-assets} export PATH=${BASE_ROOT_DIR}/ci/retry:$PATH 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/00_setup_env_i686_centos.sh b/ci/test/00_setup_env_i686_centos.sh index 1ec44a7a1a..07e7c2dc27 100644 --- a/ci/test/00_setup_env_i686_centos.sh +++ b/ci/test/00_setup_env_i686_centos.sh @@ -9,7 +9,7 @@ export LC_ALL=C.UTF-8 export HOST=i686-pc-linux-gnu export CONTAINER_NAME=ci_i686_centos_8 export DOCKER_NAME_TAG=centos:8 -export DOCKER_PACKAGES="gcc-c++ glibc-devel.x86_64 libstdc++-devel.x86_64 glibc-devel.i686 libstdc++-devel.i686 ccache libtool make git python3 python3-zmq which patch lbzip2 dash rsync coreutils" +export DOCKER_PACKAGES="gcc-c++ glibc-devel.x86_64 libstdc++-devel.x86_64 glibc-devel.i686 libstdc++-devel.i686 ccache libtool make git python3 python3-zmq which patch lbzip2 dash rsync coreutils bison" export GOAL="install" export BITCOIN_CONFIG="--enable-zmq --with-gui=qt5 --enable-reduce-exports --with-boost-process" export CONFIG_SHELL="/bin/dash" diff --git a/ci/test/00_setup_env_mac.sh b/ci/test/00_setup_env_mac.sh index 6da011d19b..6ce3812447 100644 --- a/ci/test/00_setup_env_mac.sh +++ b/ci/test/00_setup_env_mac.sh @@ -9,7 +9,7 @@ export LC_ALL=C.UTF-8 export CONTAINER_NAME=ci_macos_cross export DOCKER_NAME_TAG=ubuntu:20.04 # Check that Focal can cross-compile to macos (Focal is used in the gitian build as well) export HOST=x86_64-apple-darwin18 -export PACKAGES="cmake imagemagick libcap-dev librsvg2-bin libz-dev libbz2-dev libtiff-tools libtinfo5 python3-dev python3-setuptools xorriso" +export PACKAGES="cmake imagemagick librsvg2-bin libz-dev libtiff-tools libtinfo5 python3-setuptools xorriso" export XCODE_VERSION=11.3.1 export XCODE_BUILD_ID=11C505 export RUN_UNIT_TESTS=false 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 bc35433052..c3eabad131 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. @@ -916,14 +936,15 @@ fi AC_CHECK_HEADERS([endian.h sys/endian.h byteswap.h stdio.h stdlib.h unistd.h strings.h sys/types.h sys/stat.h sys/select.h sys/prctl.h sys/sysctl.h vm/vm_param.h sys/vmmeter.h sys/resources.h]) -AC_CHECK_DECLS([getifaddrs, freeifaddrs],,, +AC_CHECK_DECLS([getifaddrs, freeifaddrs],[CHECK_SOCKET],, [#include <sys/types.h> #include <ifaddrs.h>] ) AC_CHECK_DECLS([strnlen]) -dnl Check for daemon(3), unrelated to --with-daemon (although used by it) -AC_CHECK_DECLS([daemon]) +dnl These are used for daemonization in bitcoind +AC_CHECK_DECLS([fork]) +AC_CHECK_DECLS([setsid]) AC_CHECK_DECLS([pipe2]) @@ -1283,7 +1304,7 @@ else BITCOIN_QT_INIT dnl sets $bitcoin_enable_qt, $bitcoin_enable_qt_test, $bitcoin_enable_qt_dbus - BITCOIN_QT_CONFIGURE([5.5.1]) + BITCOIN_QT_CONFIGURE([5.9.5]) dnl Keep a copy of the original $QT_INCLUDES and use it when invoking qt's moc QT_INCLUDES_UNSUPPRESSED=$QT_INCLUDES @@ -1345,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] ) @@ -1379,52 +1400,52 @@ if test "x$use_natpmp" != xno; then fi if test x$build_bitcoin_wallet$build_bitcoin_cli$build_bitcoin_tx$build_bitcoind$bitcoin_enable_qt$use_tests$use_bench = xnonononononono; then - use_boost=no + use_boost=no else - use_boost=yes + use_boost=yes fi if test x$use_boost = xyes; then -dnl Check for Boost headers -AX_BOOST_BASE([1.58.0],[],[AC_MSG_ERROR([Boost is not available!])]) -if test x$want_boost = xno; then + dnl Check for Boost headers + AX_BOOST_BASE([1.58.0],[],[AC_MSG_ERROR([Boost is not available!])]) + if test x$want_boost = xno; then AC_MSG_ERROR([[only libbitcoinconsensus can be built without boost]]) -fi -AX_BOOST_SYSTEM -AX_BOOST_FILESYSTEM - -dnl Opt-in to Boost Process if external signer support is requested -if test "x$use_external_signer" != xno; then -AC_MSG_CHECKING(for Boost Process) -AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[#include <boost/process.hpp>]], - [[ boost::process::child* child = new boost::process::child; delete child; ]])], - [ AC_MSG_RESULT(yes) - AC_DEFINE([ENABLE_EXTERNAL_SIGNER],,[define if external signer support is enabled]) - ], - [ AC_MSG_ERROR([Boost::Process is required for external signer support, but not available!])] -) -fi - -AM_CONDITIONAL([ENABLE_EXTERNAL_SIGNER], [test "x$use_external_signer" = "xyes"]) + fi + AX_BOOST_SYSTEM + AX_BOOST_FILESYSTEM + + dnl Opt-in to Boost Process if external signer support is requested + if test "x$use_external_signer" != xno; then + AC_MSG_CHECKING(for Boost Process) + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[#include <boost/process.hpp>]], + [[ boost::process::child* child = new boost::process::child; delete child; ]])], + [ AC_MSG_RESULT(yes) + AC_DEFINE([ENABLE_EXTERNAL_SIGNER],,[define if external signer support is enabled]) + ], + [ AC_MSG_ERROR([Boost::Process is required for external signer support, but not available!])] + ) + fi -if test x$suppress_external_warnings != xno; then + if test x$suppress_external_warnings != xno; then BOOST_CPPFLAGS=SUPPRESS_WARNINGS($BOOST_CPPFLAGS) -fi + fi -dnl Boost 1.56 through 1.62 allow using std::atomic instead of its own atomic -dnl counter implementations. In 1.63 and later the std::atomic approach is default. -m4_pattern_allow(DBOOST_AC_USE_STD_ATOMIC) dnl otherwise it's treated like a macro -BOOST_CPPFLAGS="-DBOOST_SP_USE_STD_ATOMIC -DBOOST_AC_USE_STD_ATOMIC $BOOST_CPPFLAGS" + dnl Boost 1.56 through 1.62 allow using std::atomic instead of its own atomic + dnl counter implementations. In 1.63 and later the std::atomic approach is default. + m4_pattern_allow(DBOOST_AC_USE_STD_ATOMIC) dnl otherwise it's treated like a macro + BOOST_CPPFLAGS="-DBOOST_SP_USE_STD_ATOMIC -DBOOST_AC_USE_STD_ATOMIC $BOOST_CPPFLAGS" -BOOST_LIBS="$BOOST_LDFLAGS $BOOST_SYSTEM_LIB $BOOST_FILESYSTEM_LIB" + BOOST_LIBS="$BOOST_LDFLAGS $BOOST_SYSTEM_LIB $BOOST_FILESYSTEM_LIB" fi +AM_CONDITIONAL([ENABLE_EXTERNAL_SIGNER], [test "x$use_external_signer" = "xyes"]) + dnl Check for reduced exports if test x$use_reduce_exports = xyes; then - AX_CHECK_COMPILE_FLAG([-fvisibility=hidden],[CXXFLAGS="$CXXFLAGS -fvisibility=hidden"], - [AC_MSG_ERROR([Cannot set hidden symbol visibility. Use --disable-reduce-exports.])],[[$CXXFLAG_WERROR]]) - AX_CHECK_LINK_FLAG([[-Wl,--exclude-libs,ALL]],[RELDFLAGS="-Wl,--exclude-libs,ALL"],,[[$LDFLAG_WERROR]]) + AX_CHECK_COMPILE_FLAG([-fvisibility=hidden],[CXXFLAGS="$CXXFLAGS -fvisibility=hidden"], + [AC_MSG_ERROR([Cannot set hidden symbol visibility. Use --disable-reduce-exports.])],[[$CXXFLAG_WERROR]]) + AX_CHECK_LINK_FLAG([[-Wl,--exclude-libs,ALL]],[RELDFLAGS="-Wl,--exclude-libs,ALL"],,[[$LDFLAG_WERROR]]) fi if test x$use_tests = xyes; then @@ -1435,25 +1456,25 @@ if test x$use_tests = xyes; then if test x$use_boost = xyes; then - AX_BOOST_UNIT_TEST_FRAMEWORK - - dnl Determine if -DBOOST_TEST_DYN_LINK is needed - AC_MSG_CHECKING([for dynamic linked boost test]) - TEMP_LIBS="$LIBS" - LIBS="$LIBS $BOOST_LDFLAGS $BOOST_UNIT_TEST_FRAMEWORK_LIB" - TEMP_CPPFLAGS="$CPPFLAGS" - CPPFLAGS="$CPPFLAGS $BOOST_CPPFLAGS" - AC_LINK_IFELSE([AC_LANG_SOURCE([ - #define BOOST_TEST_DYN_LINK - #define BOOST_TEST_MAIN - #include <boost/test/unit_test.hpp> - - ])], - [AC_MSG_RESULT(yes)] - [TESTDEFS="$TESTDEFS -DBOOST_TEST_DYN_LINK"], - [AC_MSG_RESULT(no)]) - LIBS="$TEMP_LIBS" - CPPFLAGS="$TEMP_CPPFLAGS" + AX_BOOST_UNIT_TEST_FRAMEWORK + + dnl Determine if -DBOOST_TEST_DYN_LINK is needed + AC_MSG_CHECKING([for dynamic linked boost test]) + TEMP_LIBS="$LIBS" + LIBS="$LIBS $BOOST_LDFLAGS $BOOST_UNIT_TEST_FRAMEWORK_LIB" + TEMP_CPPFLAGS="$CPPFLAGS" + CPPFLAGS="$CPPFLAGS $BOOST_CPPFLAGS" + AC_LINK_IFELSE([AC_LANG_SOURCE([ + #define BOOST_TEST_DYN_LINK + #define BOOST_TEST_MAIN + #include <boost/test/unit_test.hpp> + + ])], + [AC_MSG_RESULT(yes)] + [TESTDEFS="$TESTDEFS -DBOOST_TEST_DYN_LINK"], + [AC_MSG_RESULT(no)]) + LIBS="$TEMP_LIBS" + CPPFLAGS="$TEMP_CPPFLAGS" fi fi @@ -1669,6 +1690,9 @@ else fi AC_MSG_RESULT($use_natpmp_default) AC_DEFINE_UNQUOTED([USE_NATPMP], [$natpmp_setting], [NAT-PMP support not compiled if undefined, otherwise value (0 or 1) determines default state]) + if test x$TARGET_OS = xwindows; then + NATPMP_CPPFLAGS="-DSTATICLIB -DNATPMP_STATICLIB" + fi else AC_MSG_RESULT([no]) fi @@ -1826,6 +1850,7 @@ AC_SUBST(SQLITE_LIBS) AC_SUBST(TESTDEFS) AC_SUBST(MINIUPNPC_CPPFLAGS) AC_SUBST(MINIUPNPC_LIBS) +AC_SUBST(NATPMP_CPPFLAGS) AC_SUBST(NATPMP_LIBS) AC_SUBST(EVENT_LIBS) AC_SUBST(EVENT_PTHREADS_LIBS) @@ -1839,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/devtools/README.md b/contrib/devtools/README.md index bdff7a84b0..1fa850af1a 100644 --- a/contrib/devtools/README.md +++ b/contrib/devtools/README.md @@ -7,7 +7,8 @@ clang-format-diff.py A script to format unified git diffs according to [.clang-format](../../src/.clang-format). -Requires `clang-format`, installed e.g. via `brew install clang-format` on macOS. +Requires `clang-format`, installed e.g. via `brew install clang-format` on macOS, +or `sudo apt install clang-format` on Debian/Ubuntu. For instance, to format the last commit with 0 lines of context, the script should be called from the git root folder as follows. diff --git a/contrib/devtools/symbol-check.py b/contrib/devtools/symbol-check.py index b30ed62521..436f179d61 100755 --- a/contrib/devtools/symbol-check.py +++ b/contrib/devtools/symbol-check.py @@ -73,6 +73,8 @@ ELF_ALLOWED_LIBRARIES = { 'ld-linux-riscv64-lp64d.so.1', # 64-bit RISC-V dynamic linker # bitcoin-qt only 'libxcb.so.1', # part of X11 +'libxkbcommon.so.0', # keyboard keymapping +'libxkbcommon-x11.so.0', # keyboard keymapping 'libfontconfig.so.1', # font support 'libfreetype.so.6', # font parsing 'libdl.so.2' # programming interface to dynamic linker @@ -98,10 +100,15 @@ MACHO_ALLOWED_LIBRARIES = { 'CoreGraphics', # 2D rendering 'CoreServices', # operating system services 'CoreText', # interface for laying out text and handling fonts. +'CoreVideo', # video processing 'Foundation', # base layer functionality for apps/frameworks 'ImageIO', # read and write image file formats. 'IOKit', # user-space access to hardware devices and drivers. +'IOSurface', # cross process image/drawing buffers 'libobjc.A.dylib', # Objective-C runtime library +'Metal', # 3D graphics +'Security', # access control and authentication +'QuartzCore', # animation } PE_ALLOWED_LIBRARIES = { @@ -116,12 +123,15 @@ PE_ALLOWED_LIBRARIES = { 'dwmapi.dll', # desktop window manager 'GDI32.dll', # graphics device interface 'IMM32.dll', # input method editor +'NETAPI32.dll', 'ole32.dll', # component object model 'OLEAUT32.dll', # OLE Automation API 'SHLWAPI.dll', # light weight shell API +'USERENV.dll', 'UxTheme.dll', 'VERSION.dll', # version checking 'WINMM.dll', # WinMM audio API +'WTSAPI32.dll', } class CPPFilt(object): diff --git a/contrib/devtools/test-security-check.py b/contrib/devtools/test-security-check.py index ec2d886653..28b5f57489 100755 --- a/contrib/devtools/test-security-check.py +++ b/contrib/devtools/test-security-check.py @@ -5,6 +5,7 @@ ''' Test script for security-check.py ''' +import os import subprocess import unittest @@ -19,6 +20,10 @@ def write_testcode(filename): } ''') +def clean_files(source, executable): + os.remove(source) + os.remove(executable) + def call_security_check(cc, source, executable, options): subprocess.run([cc,source,'-o',executable] + options, check=True) p = subprocess.run(['./contrib/devtools/security-check.py',executable], stdout=subprocess.PIPE, universal_newlines=True) @@ -44,6 +49,8 @@ class TestSecurityChecks(unittest.TestCase): self.assertEqual(call_security_check(cc, source, executable, ['-Wl,-znoexecstack','-fstack-protector-all','-Wl,-zrelro','-Wl,-z,now','-pie','-fPIE', '-Wl,-z,separate-code']), (0, '')) + clean_files(source, executable) + def test_PE(self): source = 'test1.c' executable = 'test1.exe' @@ -61,6 +68,8 @@ class TestSecurityChecks(unittest.TestCase): self.assertEqual(call_security_check(cc, source, executable, ['-Wl,--nxcompat','-Wl,--dynamicbase','-Wl,--high-entropy-va','-pie','-fPIE']), (0, '')) + clean_files(source, executable) + def test_MACHO(self): source = 'test1.c' executable = 'test1' @@ -80,6 +89,8 @@ class TestSecurityChecks(unittest.TestCase): self.assertEqual(call_security_check(cc, source, executable, ['-Wl,-pie','-Wl,-bind_at_load','-fstack-protector-all']), (0, '')) + clean_files(source, executable) + if __name__ == '__main__': unittest.main() diff --git a/contrib/devtools/test-symbol-check.py b/contrib/devtools/test-symbol-check.py index ee7bfc9805..106dfd2c5a 100755 --- a/contrib/devtools/test-symbol-check.py +++ b/contrib/devtools/test-symbol-check.py @@ -5,18 +5,17 @@ ''' Test script for symbol-check.py ''' +import os import subprocess import unittest def call_symbol_check(cc, source, executable, options): subprocess.run([cc,source,'-o',executable] + options, check=True) p = subprocess.run(['./contrib/devtools/symbol-check.py',executable], stdout=subprocess.PIPE, universal_newlines=True) + os.remove(source) + os.remove(executable) return (p.returncode, p.stdout.rstrip()) -def get_machine(cc): - p = subprocess.run([cc,'-dumpmachine'], stdout=subprocess.PIPE, universal_newlines=True) - return p.stdout.rstrip() - class TestSymbolChecks(unittest.TestCase): def test_ELF(self): source = 'test1.c' diff --git a/contrib/gitian-descriptors/gitian-linux.yml b/contrib/gitian-descriptors/gitian-linux.yml index b06fc782a3..52e2a0514a 100644 --- a/contrib/gitian-descriptors/gitian-linux.yml +++ b/contrib/gitian-descriptors/gitian-linux.yml @@ -11,6 +11,7 @@ packages: - "autoconf" - "automake" - "binutils" +- "bison" - "bsdmainutils" - "ca-certificates" - "curl" diff --git a/contrib/gitian-descriptors/gitian-osx.yml b/contrib/gitian-descriptors/gitian-osx.yml index 0dc531df0e..c37ad5b842 100644 --- a/contrib/gitian-descriptors/gitian-osx.yml +++ b/contrib/gitian-descriptors/gitian-osx.yml @@ -21,11 +21,8 @@ packages: - "bsdmainutils" - "cmake" - "imagemagick" -- "libcap-dev" - "libz-dev" -- "libbz2-dev" - "python3" -- "python3-dev" - "python3-setuptools" - "fonts-tuffy" - "xorriso" diff --git a/contrib/guix/README.md b/contrib/guix/README.md index e218ba89a0..1122ec9ba5 100644 --- a/contrib/guix/README.md +++ b/contrib/guix/README.md @@ -103,8 +103,8 @@ find output/ -type f -print0 | sort -z | xargs -r0 sha256sum Override the space-separated list of platform triples for which to perform a bootstrappable build. _(defaults to "x86\_64-linux-gnu arm-linux-gnueabihf - aarch64-linux-gnu riscv64-linux-gnu x86_64-w64-mingw32 - x86_64-apple-darwin18")_ + aarch64-linux-gnu riscv64-linux-gnu powerpc64-linux-gnu powerpc64le-linux-gnu + x86\_64-w64-mingw32 x86\_64-apple-darwin18")_ * _**SOURCES_PATH**_ @@ -118,6 +118,13 @@ find output/ -type f -print0 | sort -z | xargs -r0 sha256sum depends tree. Setting this to the same directory across multiple builds of the depends tree can eliminate unnecessary building of packages. +* _**SDK_PATH**_ + + Set the path where _extracted_ SDKs can be found. This is passed through to + the depends tree. Note that this is should be set to the _parent_ directory of + the actual SDK (e.g. SDK_PATH=$HOME/Downloads/macOS-SDKs instead of + $HOME/Downloads/macOS-SDKs/Xcode-11.3.1-11C505-extracted-SDK-with-libcxx-headers). + * _**MAX_JOBS**_ Override the maximum number of jobs to run simultaneously, you might want to diff --git a/contrib/guix/guix-build.sh b/contrib/guix/guix-build.sh index 7af132b240..7c32fb5726 100755 --- a/contrib/guix/guix-build.sh +++ b/contrib/guix/guix-build.sh @@ -9,7 +9,7 @@ set -e -o pipefail ################ # Check 1: Make sure that we can invoke required tools ################ -for cmd in git make guix cat mkdir; do +for cmd in git make guix cat mkdir curl; do if ! command -v "$cmd" > /dev/null 2>&1; then echo "ERR: This script requires that '$cmd' is installed and available in your \$PATH" exit 1 @@ -163,7 +163,7 @@ SOURCE_DATE_EPOCH="${SOURCE_DATE_EPOCH:-$(git log --format=%at -1)}" time-machine() { # shellcheck disable=SC2086 guix time-machine --url=https://github.com/dongcarl/guix.git \ - --commit=6c9d16db962a6f7155571b36eced681fd2889e23 \ + --commit=490e39ff303f4f6873a04bfb8253755bdae1b29c \ --max-jobs="$MAX_JOBS" \ --keep-failed \ ${SUBSTITUTE_URLS:+--substitute-urls="$SUBSTITUTE_URLS"} \ @@ -196,10 +196,11 @@ and untracked files and directories will be wiped, allowing you to start anew. EOF } -# Create SOURCES_PATH and BASE_CACHE if they are non-empty so that we can map -# them into the container +# Create SOURCES_PATH, BASE_CACHE, and SDK_PATH if they are non-empty so that we +# can map them into the container [ -z "$SOURCES_PATH" ] || mkdir -p "$SOURCES_PATH" [ -z "$BASE_CACHE" ] || mkdir -p "$BASE_CACHE" +[ -z "$SDK_PATH" ] || mkdir -p "$SDK_PATH" # Deterministically build Bitcoin Core # shellcheck disable=SC2153 @@ -302,6 +303,7 @@ EOF --expose="$(git rev-parse --git-common-dir)" \ ${SOURCES_PATH:+--share="$SOURCES_PATH"} \ ${BASE_CACHE:+--share="$BASE_CACHE"} \ + ${SDK_PATH:+--share="$SDK_PATH"} \ --max-jobs="$MAX_JOBS" \ --keep-failed \ ${SUBSTITUTE_URLS:+--substitute-urls="$SUBSTITUTE_URLS"} \ @@ -312,6 +314,7 @@ EOF ${V:+V=1} \ ${SOURCES_PATH:+SOURCES_PATH="$SOURCES_PATH"} \ ${BASE_CACHE:+BASE_CACHE="$BASE_CACHE"} \ + ${SDK_PATH:+SDK_PATH="$SDK_PATH"} \ DISTSRC="$(DISTSRC_BASE=/distsrc-base && distsrc_for_host "$HOST")" \ OUTDIR=/outdir \ bash -c "cd /bitcoin && bash contrib/guix/libexec/build.sh" diff --git a/contrib/guix/libexec/build.sh b/contrib/guix/libexec/build.sh index 119fe04c75..051066a6a0 100644 --- a/contrib/guix/libexec/build.sh +++ b/contrib/guix/libexec/build.sh @@ -3,7 +3,7 @@ export LC_ALL=C set -e -o pipefail export TZ=UTC -# Althought Guix _does_ set umask when building its own packages (in our case, +# Although Guix _does_ set umask when building its own packages (in our case, # this is all packages in manifest.scm), it does not set it for `guix # environment`. It does make sense for at least `guix environment --container` # to set umask, so if that change gets merged upstream and we bump the @@ -58,17 +58,10 @@ unset C_INCLUDE_PATH unset CPLUS_INCLUDE_PATH case "$HOST" in *darwin*) - # When targeting darwin, some native tools built by depends require - # native packages not incorporated in depends - # - # libcap required by native_cdrkit/wodim - # zlib, bzip2 required by native_cdrkit/genisoimage - for native_pkg in libcap zlib bzip2; do - native_pkg_store_path=$(store_path "$native_pkg") - export LIBRARY_PATH="${native_pkg_store_path}/lib:${LIBRARY_PATH}" - export CPATH="${native_pkg_store_path}/include:${CPATH}" - done - ;; + # When targeting darwin, zlib is required by native_libdmg-hfsplus. + zlib_store_path=$(store_path "zlib") + export LIBRARY_PATH="${zlib_store_path}/lib:${LIBRARY_PATH}" + export CPATH="${zlib_store_path}/include:${CPATH}" esac # Set environment variables to point the CROSS toolchain to the right @@ -184,6 +177,7 @@ make -C depends --jobs="$MAX_JOBS" HOST="$HOST" \ ${V:+V=1} \ ${SOURCES_PATH+SOURCES_PATH="$SOURCES_PATH"} \ ${BASE_CACHE+BASE_CACHE="$BASE_CACHE"} \ + ${SDK_PATH+SDK_PATH="$SDK_PATH"} \ i686_linux_CC=i686-linux-gnu-gcc \ i686_linux_CXX=i686-linux-gnu-g++ \ i686_linux_AR=i686-linux-gnu-ar \ diff --git a/contrib/guix/manifest.scm b/contrib/guix/manifest.scm index fb585b7f25..4228532cb1 100644 --- a/contrib/guix/manifest.scm +++ b/contrib/guix/manifest.scm @@ -3,6 +3,7 @@ (gnu packages autotools) (gnu packages base) (gnu packages bash) + (gnu packages bison) (gnu packages cdrom) (gnu packages check) (gnu packages cmake) @@ -125,9 +126,9 @@ chain for " target " development.")) (define* (make-bitcoin-cross-toolchain target #:key - (base-gcc-for-libc gcc-5) - (base-kernel-headers linux-libre-headers-4.19) - (base-libc glibc-2.27) + (base-gcc-for-libc gcc-7) + (base-kernel-headers linux-libre-headers-5.4) + (base-libc glibc) ; glibc 2.31 (base-gcc (make-gcc-rpath-link gcc-9))) "Convenience wrapper around MAKE-CROSS-TOOLCHAIN with default values desirable for building Bitcoin Core release binaries." @@ -219,6 +220,7 @@ chain for " target " development.")) autoconf automake pkg-config + bison ;; Scripting perl python-3 @@ -232,14 +234,8 @@ chain for " target " development.")) (list zip (make-mingw-pthreads-cross-toolchain "x86_64-w64-mingw32") (make-nsis-with-sde-support nsis-x86_64))) - ((string-contains target "riscv64-linux-") - (list (make-bitcoin-cross-toolchain target - #:base-gcc-for-libc gcc-7))) - ((string-contains target "powerpc64le-linux-") - (list (make-bitcoin-cross-toolchain target - #:base-gcc-for-libc gcc-7))) ((string-contains target "-linux-") (list (make-bitcoin-cross-toolchain target))) ((string-contains target "darwin") - (list clang-8 libcap binutils imagemagick libtiff librsvg font-tuffy cmake xorriso)) + (list clang-8 binutils imagemagick libtiff librsvg font-tuffy cmake xorriso)) (else '()))))) diff --git a/contrib/init/bitcoind.openrc b/contrib/init/bitcoind.openrc index 86222295db..013a1a6070 100644 --- a/contrib/init/bitcoind.openrc +++ b/contrib/init/bitcoind.openrc @@ -60,16 +60,17 @@ start_pre() { "${BITCOIND_PIDDIR}" checkpath -f \ - -o ${BITCOIND_USER}:${BITCOIND_GROUP} \ + -o "${BITCOIND_USER}:${BITCOIND_GROUP}" \ -m 0660 \ - ${BITCOIND_CONFIGFILE} + "${BITCOIND_CONFIGFILE}" checkconfig || return 1 } checkconfig() { - if ! grep -qs '^rpcpassword=' "${BITCOIND_CONFIGFILE}" ; then + if grep -qs '^rpcuser=' "${BITCOIND_CONFIGFILE}" && \ + ! grep -qs '^rpcpassword=' "${BITCOIND_CONFIGFILE}" ; then eerror "" eerror "ERROR: You must set a secure rpcpassword to run bitcoind." eerror "The setting must appear in ${BITCOIND_CONFIGFILE}" 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/contrib/testgen/README.md b/contrib/testgen/README.md index eaca473b40..fcc5a378e2 100644 --- a/contrib/testgen/README.md +++ b/contrib/testgen/README.md @@ -4,5 +4,5 @@ Utilities to generate test vectors for the data-driven Bitcoin tests. Usage: - PYTHONPATH=../../test/functional/test_framework ./gen_key_io_test_vectors.py valid 50 > ../../src/test/data/key_io_valid.json - PYTHONPATH=../../test/functional/test_framework ./gen_key_io_test_vectors.py invalid 50 > ../../src/test/data/key_io_invalid.json + PYTHONPATH=../../test/functional/test_framework ./gen_key_io_test_vectors.py valid 70 > ../../src/test/data/key_io_valid.json + PYTHONPATH=../../test/functional/test_framework ./gen_key_io_test_vectors.py invalid 70 > ../../src/test/data/key_io_invalid.json diff --git a/contrib/testgen/gen_key_io_test_vectors.py b/contrib/testgen/gen_key_io_test_vectors.py index 8a3918da6b..74918cfb04 100755 --- a/contrib/testgen/gen_key_io_test_vectors.py +++ b/contrib/testgen/gen_key_io_test_vectors.py @@ -3,11 +3,11 @@ # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. ''' -Generate valid and invalid base58 address and private key test vectors. +Generate valid and invalid base58/bech32(m) address and private key test vectors. Usage: - PYTHONPATH=../../test/functional/test_framework ./gen_key_io_test_vectors.py valid 50 > ../../src/test/data/key_io_valid.json - PYTHONPATH=../../test/functional/test_framework ./gen_key_io_test_vectors.py invalid 50 > ../../src/test/data/key_io_invalid.json + PYTHONPATH=../../test/functional/test_framework ./gen_key_io_test_vectors.py valid 70 > ../../src/test/data/key_io_valid.json + PYTHONPATH=../../test/functional/test_framework ./gen_key_io_test_vectors.py invalid 70 > ../../src/test/data/key_io_invalid.json ''' # 2012 Wladimir J. van der Laan # Released under MIT License @@ -15,7 +15,7 @@ import os from itertools import islice from base58 import b58encode_chk, b58decode_chk, b58chars import random -from segwit_addr import bech32_encode, decode_segwit_address, convertbits, CHARSET +from segwit_addr import bech32_encode, decode_segwit_address, convertbits, CHARSET, Encoding # key types PUBKEY_ADDRESS = 0 @@ -32,6 +32,7 @@ PRIVKEY_REGTEST = 239 OP_0 = 0x00 OP_1 = 0x51 OP_2 = 0x52 +OP_3 = 0x53 OP_16 = 0x60 OP_DUP = 0x76 OP_EQUAL = 0x87 @@ -44,6 +45,7 @@ script_prefix = (OP_HASH160, 20) script_suffix = (OP_EQUAL,) p2wpkh_prefix = (OP_0, 20) p2wsh_prefix = (OP_0, 32) +p2tr_prefix = (OP_1, 32) metadata_keys = ['isPrivkey', 'chain', 'isCompressed', 'tryCaseFlip'] # templates for valid sequences @@ -54,40 +56,58 @@ templates = [ ((SCRIPT_ADDRESS,), 20, (), (False, 'main', None, None), script_prefix, script_suffix), ((PUBKEY_ADDRESS_TEST,), 20, (), (False, 'test', None, None), pubkey_prefix, pubkey_suffix), ((SCRIPT_ADDRESS_TEST,), 20, (), (False, 'test', None, None), script_prefix, script_suffix), + ((PUBKEY_ADDRESS_TEST,), 20, (), (False, 'signet', None, None), pubkey_prefix, pubkey_suffix), + ((SCRIPT_ADDRESS_TEST,), 20, (), (False, 'signet', None, None), script_prefix, script_suffix), ((PUBKEY_ADDRESS_REGTEST,), 20, (), (False, 'regtest', None, None), pubkey_prefix, pubkey_suffix), ((SCRIPT_ADDRESS_REGTEST,), 20, (), (False, 'regtest', None, None), script_prefix, script_suffix), ((PRIVKEY,), 32, (), (True, 'main', False, None), (), ()), ((PRIVKEY,), 32, (1,), (True, 'main', True, None), (), ()), ((PRIVKEY_TEST,), 32, (), (True, 'test', False, None), (), ()), ((PRIVKEY_TEST,), 32, (1,), (True, 'test', True, None), (), ()), + ((PRIVKEY_TEST,), 32, (), (True, 'signet', False, None), (), ()), + ((PRIVKEY_TEST,), 32, (1,), (True, 'signet', True, None), (), ()), ((PRIVKEY_REGTEST,), 32, (), (True, 'regtest', False, None), (), ()), ((PRIVKEY_REGTEST,), 32, (1,), (True, 'regtest', True, None), (), ()) ] # templates for valid bech32 sequences bech32_templates = [ - # hrp, version, witprog_size, metadata, output_prefix - ('bc', 0, 20, (False, 'main', None, True), p2wpkh_prefix), - ('bc', 0, 32, (False, 'main', None, True), p2wsh_prefix), - ('bc', 1, 2, (False, 'main', None, True), (OP_1, 2)), - ('tb', 0, 20, (False, 'test', None, True), p2wpkh_prefix), - ('tb', 0, 32, (False, 'test', None, True), p2wsh_prefix), - ('tb', 2, 16, (False, 'test', None, True), (OP_2, 16)), - ('bcrt', 0, 20, (False, 'regtest', None, True), p2wpkh_prefix), - ('bcrt', 0, 32, (False, 'regtest', None, True), p2wsh_prefix), - ('bcrt', 16, 40, (False, 'regtest', None, True), (OP_16, 40)) + # hrp, version, witprog_size, metadata, encoding, output_prefix + ('bc', 0, 20, (False, 'main', None, True), Encoding.BECH32, p2wpkh_prefix), + ('bc', 0, 32, (False, 'main', None, True), Encoding.BECH32, p2wsh_prefix), + ('bc', 1, 32, (False, 'main', None, True), Encoding.BECH32M, p2tr_prefix), + ('bc', 2, 2, (False, 'main', None, True), Encoding.BECH32M, (OP_2, 2)), + ('tb', 0, 20, (False, 'test', None, True), Encoding.BECH32, p2wpkh_prefix), + ('tb', 0, 32, (False, 'test', None, True), Encoding.BECH32, p2wsh_prefix), + ('tb', 1, 32, (False, 'test', None, True), Encoding.BECH32M, p2tr_prefix), + ('tb', 3, 16, (False, 'test', None, True), Encoding.BECH32M, (OP_3, 16)), + ('tb', 0, 20, (False, 'signet', None, True), Encoding.BECH32, p2wpkh_prefix), + ('tb', 0, 32, (False, 'signet', None, True), Encoding.BECH32, p2wsh_prefix), + ('tb', 1, 32, (False, 'signet', None, True), Encoding.BECH32M, p2tr_prefix), + ('tb', 3, 32, (False, 'signet', None, True), Encoding.BECH32M, (OP_3, 32)), + ('bcrt', 0, 20, (False, 'regtest', None, True), Encoding.BECH32, p2wpkh_prefix), + ('bcrt', 0, 32, (False, 'regtest', None, True), Encoding.BECH32, p2wsh_prefix), + ('bcrt', 1, 32, (False, 'regtest', None, True), Encoding.BECH32M, p2tr_prefix), + ('bcrt', 16, 40, (False, 'regtest', None, True), Encoding.BECH32M, (OP_16, 40)) ] # templates for invalid bech32 sequences bech32_ng_templates = [ - # hrp, version, witprog_size, invalid_bech32, invalid_checksum, invalid_char - ('tc', 0, 20, False, False, False), - ('tb', 17, 32, False, False, False), - ('bcrt', 3, 1, False, False, False), - ('bc', 15, 41, False, False, False), - ('tb', 0, 16, False, False, False), - ('bcrt', 0, 32, True, False, False), - ('bc', 0, 16, True, False, False), - ('tb', 0, 32, False, True, False), - ('bcrt', 0, 20, False, False, True) + # hrp, version, witprog_size, encoding, invalid_bech32, invalid_checksum, invalid_char + ('tc', 0, 20, Encoding.BECH32, False, False, False), + ('bt', 1, 32, Encoding.BECH32M, False, False, False), + ('tb', 17, 32, Encoding.BECH32M, False, False, False), + ('bcrt', 3, 1, Encoding.BECH32M, False, False, False), + ('bc', 15, 41, Encoding.BECH32M, False, False, False), + ('tb', 0, 16, Encoding.BECH32, False, False, False), + ('bcrt', 0, 32, Encoding.BECH32, True, False, False), + ('bc', 0, 16, Encoding.BECH32, True, False, False), + ('tb', 0, 32, Encoding.BECH32, False, True, False), + ('bcrt', 0, 20, Encoding.BECH32, False, False, True), + ('bc', 0, 20, Encoding.BECH32M, False, False, False), + ('tb', 0, 32, Encoding.BECH32M, False, False, False), + ('bcrt', 0, 20, Encoding.BECH32M, False, False, False), + ('bc', 1, 32, Encoding.BECH32, False, False, False), + ('tb', 2, 16, Encoding.BECH32, False, False, False), + ('bcrt', 16, 20, Encoding.BECH32, False, False, False), ] def is_valid(v): @@ -127,8 +147,9 @@ def gen_valid_bech32_vector(template): hrp = template[0] witver = template[1] witprog = bytearray(os.urandom(template[2])) - dst_prefix = bytearray(template[4]) - rv = bech32_encode(hrp, [witver] + convertbits(witprog, 8, 5)) + encoding = template[4] + dst_prefix = bytearray(template[5]) + rv = bech32_encode(encoding, hrp, [witver] + convertbits(witprog, 8, 5)) return rv, dst_prefix + witprog def gen_valid_vectors(): @@ -186,22 +207,23 @@ def gen_invalid_bech32_vector(template): hrp = template[0] witver = template[1] witprog = bytearray(os.urandom(template[2])) + encoding = template[3] if no_data: - rv = bech32_encode(hrp, []) + rv = bech32_encode(encoding, hrp, []) else: data = [witver] + convertbits(witprog, 8, 5) - if template[3] and not no_data: + if template[4] and not no_data: if template[2] % 5 in {2, 4}: data[-1] |= 1 else: data.append(0) - rv = bech32_encode(hrp, data) + rv = bech32_encode(encoding, hrp, data) - if template[4]: + if template[5]: i = len(rv) - random.randrange(1, 7) rv = rv[:i] + random.choice(CHARSET.replace(rv[i], '')) + rv[i + 1:] - if template[5]: + if template[6]: i = len(hrp) + 1 + random.randrange(0, len(rv) - len(hrp) - 4) rv = rv[:i] + rv[i:i + 4].upper() + rv[i + 4:] diff --git a/contrib/verifybinaries/verify.py b/contrib/verifybinaries/verify.py index b97ad11b8e..6cbaf2dc0c 100755 --- a/contrib/verifybinaries/verify.py +++ b/contrib/verifybinaries/verify.py @@ -51,7 +51,7 @@ def download_with_wget(remote_file, local_file=None): if local_file: wget_args = ['wget', '-O', local_file, remote_file] else: - # use timestamping mechanism if local filename is not explicitely set + # use timestamping mechanism if local filename is not explicitly set wget_args = ['wget', '-N', remote_file] result = subprocess.run(wget_args, @@ -85,7 +85,7 @@ def main(args): print("Error: need to specify a version on the command line") return 3 - # determine remote dir dependend on provided version string + # determine remote dir dependent on provided version string version_base, version_rc, os_filter = parse_version_string(args[0]) remote_dir = f"/bin/{VERSIONPREFIX}{version_base}/" if version_rc: diff --git a/depends/README.md b/depends/README.md index 9e9878c595..a1d4a99084 100644 --- a/depends/README.md +++ b/depends/README.md @@ -44,7 +44,7 @@ The paths are automatically configured and no other options are needed unless ta #### For macOS cross compilation - sudo apt-get install curl librsvg2-bin libtiff-tools bsdmainutils cmake imagemagick libcap-dev libz-dev libbz2-dev python3-setuptools libtinfo5 xorriso + sudo apt-get install curl librsvg2-bin libtiff-tools bsdmainutils cmake imagemagick libz-dev python3-setuptools libtinfo5 xorriso #### For Win64 cross compilation @@ -54,7 +54,7 @@ The paths are automatically configured and no other options are needed unless ta Common linux dependencies: - sudo apt-get install make automake cmake curl g++-multilib libtool binutils-gold bsdmainutils pkg-config python3 patch + sudo apt-get install make automake cmake curl g++-multilib libtool binutils-gold bsdmainutils pkg-config python3 patch bison For linux ARM cross compilation: diff --git a/depends/config.site.in b/depends/config.site.in index 391767357a..f1a59a5861 100644 --- a/depends/config.site.in +++ b/depends/config.site.in @@ -54,6 +54,10 @@ if test -z "$with_gui" && test -n "@no_qt@"; then with_gui=no fi +if test -n "@debug@" && test -z "@no_qt@" && test "x$with_gui" != xno; then + with_gui=qt5_debug +fi + if test -z "$enable_zmq" && test -n "@no_zmq@"; then enable_zmq=no fi diff --git a/depends/hosts/darwin.mk b/depends/hosts/darwin.mk index 646e97837a..dd71697f0f 100644 --- a/depends/hosts/darwin.mk +++ b/depends/hosts/darwin.mk @@ -12,14 +12,13 @@ ifeq ($(strip $(FORCE_USE_SYSTEM_CLANG)),) # FORCE_USE_SYSTEM_CLANG is empty, so we use our depends-managed, pinned clang # from llvm.org -# The native_cctools package is what provides clang when FORCE_USE_SYSTEM_CLANG -# is empty +# Clang is a dependency of native_cctools when FORCE_USE_SYSTEM_CLANG is empty darwin_native_toolchain=native_cctools clang_prog=$(build_prefix)/bin/clang clangxx_prog=$(clang_prog)++ -clang_resource_dir=$(build_prefix)/lib/clang/$(native_cctools_clang_version) +clang_resource_dir=$(build_prefix)/lib/clang/$(native_clang_version) else # FORCE_USE_SYSTEM_CLANG is non-empty, so we use the clang from the user's # system diff --git a/depends/packages/libnatpmp.mk b/depends/packages/libnatpmp.mk index a24f201859..cdcf8c0bf2 100644 --- a/depends/packages/libnatpmp.mk +++ b/depends/packages/libnatpmp.mk @@ -1,11 +1,14 @@ package=libnatpmp -$(package)_version=20150609 -$(package)_download_path=https://miniupnp.tuxfamily.org/files/ -$(package)_file_name=$(package)-$($(package)_version).tar.gz -$(package)_sha256_hash=e1aa9c4c4219bc06943d6b2130f664daee213fb262fcb94dd355815b8f4536b0 +$(package)_version=4536032ae32268a45c073a4d5e91bbab4534773a +$(package)_download_path=https://github.com/miniupnp/libnatpmp/archive +$(package)_file_name=$($(package)_version).tar.gz +$(package)_sha256_hash=543b460aab26acf91e11d15e17d8798f845304199eea2d76c2f444ec749c5383 define $(package)_set_vars $(package)_build_opts=CC="$($(package)_cc)" + $(package)_build_opts_mingw32=CPPFLAGS=-DNATPMP_STATICLIB + $(package)_build_opts_darwin=LIBTOOL="$($(package)_libtool)" + $(package)_build_env+=CFLAGS="$($(package)_cflags) $($(package)_cppflags)" AR="$($(package)_ar)" endef define $(package)_build_cmds diff --git a/depends/packages/libxcb.mk b/depends/packages/libxcb.mk index 2204b38195..710f43959c 100644 --- a/depends/packages/libxcb.mk +++ b/depends/packages/libxcb.mk @@ -15,25 +15,20 @@ $(package)_config_opts += --disable-composite --disable-damage --disable-dpms $(package)_config_opts += --disable-dri2 --disable-dri3 --disable-glx $(package)_config_opts += --disable-present --disable-randr --disable-record $(package)_config_opts += --disable-render --disable-resource --disable-screensaver -$(package)_config_opts += --disable-shape --disable-shm --disable-sync +$(package)_config_opts += --disable-shape --disable-sync $(package)_config_opts += --disable-xevie --disable-xfixes --disable-xfree86-dri -$(package)_config_opts += --disable-xinerama --disable-xinput --disable-xkb +$(package)_config_opts += --disable-xinerama --disable-xinput $(package)_config_opts += --disable-xprint --disable-selinux --disable-xtest $(package)_config_opts += --disable-xv --disable-xvmc endef define $(package)_preprocess_cmds - cp -f $(BASEDIR)/config.guess $(BASEDIR)/config.sub build-aux &&\ + cp -f $(BASEDIR)/config.guess $(BASEDIR)/config.sub build-aux && \ sed "s/pthread-stubs//" -i configure endef -# Don't install xcb headers to the default path in order to work around a qt -# build issue: https://bugreports.qt.io/browse/QTBUG-34748 -# When using qt's internal libxcb, it may end up finding the real headers in -# depends staging. Use a non-default path to avoid that. - define $(package)_config_cmds - $($(package)_autoconf) --includedir=$(host_prefix)/include/xcb-shared + $($(package)_autoconf) endef define $(package)_build_cmds @@ -45,5 +40,5 @@ define $(package)_stage_cmds endef define $(package)_postprocess_cmds - rm -rf share/man share/doc lib/*.la + rm -rf share lib/*.la endef diff --git a/depends/packages/libxkbcommon.mk b/depends/packages/libxkbcommon.mk new file mode 100644 index 0000000000..8c6c56545f --- /dev/null +++ b/depends/packages/libxkbcommon.mk @@ -0,0 +1,32 @@ +package=libxkbcommon +$(package)_version=0.8.4 +$(package)_download_path=https://xkbcommon.org/download/ +$(package)_file_name=$(package)-$($(package)_version).tar.xz +$(package)_sha256_hash=60ddcff932b7fd352752d51a5c4f04f3d0403230a584df9a2e0d5ed87c486c8b +$(package)_dependencies=libxcb + +define $(package)_set_vars +$(package)_config_opts = --enable-option-checking --disable-dependency-tracking +$(package)_config_opts += --disable-static --disable-docs +endef + +define $(package)_preprocess_cmds + cp -f $(BASEDIR)/config.guess $(BASEDIR)/config.sub build-aux +endef + +define $(package)_config_cmds + $($(package)_autoconf) +endef + +define $(package)_build_cmds + $(MAKE) +endef + +define $(package)_stage_cmds + $(MAKE) DESTDIR=$($(package)_staging_dir) install +endef + +define $(package)_postprocess_cmds + rm lib/*.la +endef + 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/native_cctools.mk b/depends/packages/native_cctools.mk index d56b636695..c789dab74c 100644 --- a/depends/packages/native_cctools.mk +++ b/depends/packages/native_cctools.mk @@ -5,81 +5,19 @@ $(package)_file_name=$($(package)_version).tar.gz $(package)_sha256_hash=e51995a843533a3dac155dd0c71362dd471597a2d23f13dff194c6285362f875 $(package)_build_subdir=cctools $(package)_patches=ld64_disable_threading.patch - -ifeq ($(strip $(FORCE_USE_SYSTEM_CLANG)),) -$(package)_clang_version=8.0.0 -$(package)_clang_download_path=https://releases.llvm.org/$($(package)_clang_version) -$(package)_clang_download_file=clang+llvm-$($(package)_clang_version)-x86_64-linux-gnu-ubuntu-14.04.tar.xz -$(package)_clang_file_name=clang-llvm-$($(package)_clang_version)-x86_64-linux-gnu-ubuntu-14.04.tar.xz -$(package)_clang_sha256_hash=9ef854b71949f825362a119bf2597f744836cb571131ae6b721cd102ffea8cd0 -endif - -$(package)_libtapi_version=3efb201881e7a76a21e0554906cf306432539cef -$(package)_libtapi_download_path=https://github.com/tpoechtrager/apple-libtapi/archive -$(package)_libtapi_download_file=$($(package)_libtapi_version).tar.gz -$(package)_libtapi_file_name=$($(package)_libtapi_version).tar.gz -$(package)_libtapi_sha256_hash=380c1ca37cfa04a8699d0887a8d3ee1ad27f3d08baba78887c73b09485c0fbd3 - -$(package)_extra_sources=$($(package)_libtapi_file_name) -ifeq ($(strip $(FORCE_USE_SYSTEM_CLANG)),) -$(package)_extra_sources += $($(package)_clang_file_name) -endif - -ifeq ($(strip $(FORCE_USE_SYSTEM_CLANG)),) -define $(package)_fetch_cmds -$(call fetch_file,$(package),$($(package)_download_path),$($(package)_download_file),$($(package)_file_name),$($(package)_sha256_hash)) && \ -$(call fetch_file,$(package),$($(package)_clang_download_path),$($(package)_clang_download_file),$($(package)_clang_file_name),$($(package)_clang_sha256_hash)) && \ -$(call fetch_file,$(package),$($(package)_libtapi_download_path),$($(package)_libtapi_download_file),$($(package)_libtapi_file_name),$($(package)_libtapi_sha256_hash)) -endef -else -define $(package)_fetch_cmds -$(call fetch_file,$(package),$($(package)_download_path),$($(package)_download_file),$($(package)_file_name),$($(package)_sha256_hash)) && \ -$(call fetch_file,$(package),$($(package)_libtapi_download_path),$($(package)_libtapi_download_file),$($(package)_libtapi_file_name),$($(package)_libtapi_sha256_hash)) -endef -endif - -ifeq ($(strip $(FORCE_USE_SYSTEM_CLANG)),) -define $(package)_extract_cmds - mkdir -p $($(package)_extract_dir) && \ - echo "$($(package)_sha256_hash) $($(package)_source)" > $($(package)_extract_dir)/.$($(package)_file_name).hash && \ - echo "$($(package)_clang_sha256_hash) $($(package)_source_dir)/$($(package)_clang_file_name)" >> $($(package)_extract_dir)/.$($(package)_file_name).hash && \ - echo "$($(package)_libtapi_sha256_hash) $($(package)_source_dir)/$($(package)_libtapi_file_name)" >> $($(package)_extract_dir)/.$($(package)_file_name).hash && \ - $(build_SHA256SUM) -c $($(package)_extract_dir)/.$($(package)_file_name).hash && \ - mkdir -p toolchain/bin toolchain/lib/clang/$($(package)_clang_version)/include && \ - mkdir -p libtapi && \ - tar --no-same-owner --strip-components=1 -C libtapi -xf $($(package)_source_dir)/$($(package)_libtapi_file_name) && \ - tar --no-same-owner --strip-components=1 -C toolchain -xf $($(package)_source_dir)/$($(package)_clang_file_name) && \ - rm -f toolchain/lib/libc++abi.so* && \ - tar --no-same-owner --strip-components=1 -xf $($(package)_source) -endef -else -define $(package)_extract_cmds - mkdir -p $($(package)_extract_dir) && \ - echo "$($(package)_sha256_hash) $($(package)_source)" > $($(package)_extract_dir)/.$($(package)_file_name).hash && \ - echo "$($(package)_libtapi_sha256_hash) $($(package)_source_dir)/$($(package)_libtapi_file_name)" >> $($(package)_extract_dir)/.$($(package)_file_name).hash && \ - $(build_SHA256SUM) -c $($(package)_extract_dir)/.$($(package)_file_name).hash && \ - mkdir -p libtapi && \ - tar --no-same-owner --strip-components=1 -C libtapi -xf $($(package)_source_dir)/$($(package)_libtapi_file_name) && \ - tar --no-same-owner --strip-components=1 -xf $($(package)_source) -endef -endif +$(package)_dependencies=native_libtapi define $(package)_set_vars - $(package)_config_opts=--target=$(host) --with-libtapi=$($(package)_extract_dir) + $(package)_config_opts=--target=$(host) $(package)_ldflags+=-Wl,-rpath=\\$$$$$$$$\$$$$$$$$ORIGIN/../lib ifeq ($(strip $(FORCE_USE_SYSTEM_CLANG)),) - $(package)_config_opts+=--enable-lto-support --with-llvm-config=$($(package)_extract_dir)/toolchain/bin/llvm-config - $(package)_cc=$($(package)_extract_dir)/toolchain/bin/clang - $(package)_cxx=$($(package)_extract_dir)/toolchain/bin/clang++ - else - $(package)_cc=clang - $(package)_cxx=clang++ + $(package)_config_opts+=--enable-lto-support --with-llvm-config=$(build_prefix)/bin/llvm-config endif + $(package)_cc=$(clang_prog) + $(package)_cxx=$(clangxx_prog) endef define $(package)_preprocess_cmds - CC=$($(package)_cc) CXX=$($(package)_cxx) INSTALLPREFIX=$($(package)_extract_dir) ./libtapi/build.sh && \ - CC=$($(package)_cc) CXX=$($(package)_cxx) INSTALLPREFIX=$($(package)_extract_dir) ./libtapi/install.sh && \ patch -p1 < $($(package)_patch_dir)/ld64_disable_threading.patch endef @@ -91,26 +29,10 @@ define $(package)_build_cmds $(MAKE) endef -ifeq ($(strip $(FORCE_USE_SYSTEM_CLANG)),) define $(package)_stage_cmds - $(MAKE) DESTDIR=$($(package)_staging_dir) install && \ - mkdir -p $($(package)_staging_prefix_dir)/lib/ && \ - cd $($(package)_extract_dir) && \ - cp lib/libtapi.so.6 $($(package)_staging_prefix_dir)/lib/ && \ - cd $($(package)_extract_dir)/toolchain && \ - mkdir -p $($(package)_staging_prefix_dir)/lib/clang/$($(package)_clang_version)/include && \ - mkdir -p $($(package)_staging_prefix_dir)/bin $($(package)_staging_prefix_dir)/include && \ - cp bin/clang $($(package)_staging_prefix_dir)/bin/ &&\ - cp -P bin/clang++ $($(package)_staging_prefix_dir)/bin/ &&\ - cp lib/libLTO.so $($(package)_staging_prefix_dir)/lib/ && \ - cp -rf lib/clang/$($(package)_clang_version)/include/* $($(package)_staging_prefix_dir)/lib/clang/$($(package)_clang_version)/include/ && \ - cp bin/dsymutil $($(package)_staging_prefix_dir)/bin/$(host)-dsymutil + $(MAKE) DESTDIR=$($(package)_staging_dir) install endef -else -define $(package)_stage_cmds - $(MAKE) DESTDIR=$($(package)_staging_dir) install && \ - mkdir -p $($(package)_staging_prefix_dir)/lib/ && \ - cd $($(package)_extract_dir) && \ - cp lib/libtapi.so.6 $($(package)_staging_prefix_dir)/lib/ + +define $(package)_postprocess_cmds + rm -rf share endef -endif diff --git a/depends/packages/native_clang.mk b/depends/packages/native_clang.mk new file mode 100644 index 0000000000..741ddacf09 --- /dev/null +++ b/depends/packages/native_clang.mk @@ -0,0 +1,26 @@ +package=native_clang +$(package)_version=8.0.0 +$(package)_download_path=https://releases.llvm.org/$($(package)_version) +$(package)_download_file=clang+llvm-$($(package)_version)-x86_64-linux-gnu-ubuntu-14.04.tar.xz +$(package)_file_name=clang-llvm-$($(package)_version)-x86_64-linux-gnu-ubuntu-14.04.tar.xz +$(package)_sha256_hash=9ef854b71949f825362a119bf2597f744836cb571131ae6b721cd102ffea8cd0 + +define $(package)_preprocess_cmds + rm -f $($(package)_extract_dir)/lib/libc++abi.so* +endef + +define $(package)_stage_cmds + mkdir -p $($(package)_staging_prefix_dir)/lib/clang/$($(package)_version)/include && \ + mkdir -p $($(package)_staging_prefix_dir)/bin && \ + mkdir -p $($(package)_staging_prefix_dir)/include && \ + cp bin/clang $($(package)_staging_prefix_dir)/bin/ && \ + cp -P bin/clang++ $($(package)_staging_prefix_dir)/bin/ && \ + cp bin/dsymutil $($(package)_staging_prefix_dir)/bin/$(host)-dsymutil && \ + cp bin/llvm-config $($(package)_staging_prefix_dir)/bin/ && \ + cp lib/libLTO.so $($(package)_staging_prefix_dir)/lib/ && \ + cp -rf lib/clang/$($(package)_version)/include/* $($(package)_staging_prefix_dir)/lib/clang/$($(package)_version)/include/ +endef + +define $(package)_postprocess_cmds + rmdir include +endef diff --git a/depends/packages/native_libtapi.mk b/depends/packages/native_libtapi.mk new file mode 100644 index 0000000000..d7ac4156a7 --- /dev/null +++ b/depends/packages/native_libtapi.mk @@ -0,0 +1,20 @@ +package=native_libtapi +$(package)_version=3efb201881e7a76a21e0554906cf306432539cef +$(package)_download_path=https://github.com/tpoechtrager/apple-libtapi/archive +$(package)_download_file=$($(package)_version).tar.gz +$(package)_file_name=$($(package)_version).tar.gz +$(package)_sha256_hash=380c1ca37cfa04a8699d0887a8d3ee1ad27f3d08baba78887c73b09485c0fbd3 + +ifeq ($(strip $(FORCE_USE_SYSTEM_CLANG)),) +$(package)_dependencies=native_clang +endif + +define $(package)_build_cmds + CC=$(clang_prog) CXX=$(clangxx_prog) INSTALLPREFIX=$($(package)_staging_prefix_dir) ./build.sh +endef + +define $(package)_stage_cmds + ./install.sh && \ + mkdir -p $($(package)_staging_prefix_dir)/include/llvm-c && \ + cp src/llvm/include/llvm-c/lto.h $($(package)_staging_prefix_dir)/include/llvm-c +endef diff --git a/depends/packages/packages.mk b/depends/packages/packages.mk index 0f35ca0d2d..9094e327ef 100644 --- a/depends/packages/packages.mk +++ b/depends/packages/packages.mk @@ -1,10 +1,8 @@ packages:=boost libevent -qt_packages = zlib - qrencode_packages = qrencode -qt_linux_packages:=qt expat libxcb xcb_proto libXau xproto freetype fontconfig +qt_linux_packages:=qt expat libxcb xcb_proto libXau xproto freetype fontconfig libxkbcommon qt_android_packages=qt qt_darwin_packages=qt @@ -26,5 +24,10 @@ darwin_native_packages = native_ds_store native_mac_alias $(host_arch)_$(host_os)_native_packages += native_b2 ifneq ($(build_os),darwin) -darwin_native_packages += native_cctools native_libdmg-hfsplus +darwin_native_packages += native_cctools native_libtapi native_libdmg-hfsplus + +ifeq ($(strip $(FORCE_USE_SYSTEM_CLANG)),) +darwin_native_packages+= native_clang +endif + endif diff --git a/depends/packages/qt.mk b/depends/packages/qt.mk index 7d122ea5f9..ee6796e2ad 100644 --- a/depends/packages/qt.mk +++ b/depends/packages/qt.mk @@ -1,23 +1,22 @@ PACKAGE=qt -$(package)_version=5.9.8 -$(package)_download_path=https://download.qt.io/official_releases/qt/5.9/$($(package)_version)/submodules -$(package)_suffix=opensource-src-$($(package)_version).tar.xz +$(package)_version=5.12.10 +$(package)_download_path=https://download.qt.io/official_releases/qt/5.12/$($(package)_version)/submodules +$(package)_suffix=everywhere-src-$($(package)_version).tar.xz $(package)_file_name=qtbase-$($(package)_suffix) -$(package)_sha256_hash=9b9dec1f67df1f94bce2955c5604de992d529dde72050239154c56352da0907d -$(package)_dependencies=zlib -$(package)_linux_dependencies=freetype fontconfig libxcb +$(package)_sha256_hash=8088f174e6d28e779516c083b6087b6a9e3c8322b4bc161fd1b54195e3c86940 +$(package)_linux_dependencies=freetype fontconfig libxcb libxkbcommon $(package)_qt_libs=corelib network widgets gui plugins testlib -$(package)_patches=fix_qt_pkgconfig.patch mac-qmake.conf fix_configure_mac.patch fix_no_printer.patch -$(package)_patches+= fix_rcc_determinism.patch fix_riscv64_arch.patch xkb-default.patch no-xlib.patch +$(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+= freetype_back_compat.patch drop_lrelease_dependency.patch fix_powerpc_libpng.patch -$(package)_patches+= fix_mingw_cross_compile.patch fix_qpainter_non_determinism.patch +$(package)_patches+= drop_lrelease_dependency.patch no_sdk_version_check.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=fb5a47799754af73d3bf501fe513342cfe2fc37f64e80df5533f6110e804220c +$(package)_qttranslations_sha256_hash=e1de58ed108b7e0a138815ea60fd46a2c4e1fc31396a707e5630e92de79c53de $(package)_qttools_file_name=qttools-$($(package)_suffix) -$(package)_qttools_sha256_hash=a97556eb7b2f30252cdd8a598c396cfce2b2f79d2bae883af6d3b26a2cdcc63c +$(package)_qttools_sha256_hash=b0cfa6e7aac41b7c61fc59acc04843d7a98f9e1840370611751bcfc1834a636c $(package)_extra_sources = $($(package)_qttranslations_file_name) $(package)_extra_sources += $($(package)_qttools_file_name) @@ -26,6 +25,7 @@ define $(package)_set_vars $(package)_config_opts_release = -release $(package)_config_opts_release += -silent $(package)_config_opts_debug = -debug +$(package)_config_opts_debug += -optimized-tools $(package)_config_opts += -bindir $(build_prefix)/bin $(package)_config_opts += -c++std c++1z $(package)_config_opts += -confirm-license @@ -49,7 +49,6 @@ $(package)_config_opts += -no-mtdev $(package)_config_opts += -no-openssl $(package)_config_opts += -no-openvg $(package)_config_opts += -no-reduce-relocations -$(package)_config_opts += -no-qml-debug $(package)_config_opts += -no-sctp $(package)_config_opts += -no-securetransport $(package)_config_opts += -no-sql-db2 @@ -63,17 +62,15 @@ $(package)_config_opts += -no-sql-sqlite $(package)_config_opts += -no-sql-sqlite2 $(package)_config_opts += -no-system-proxies $(package)_config_opts += -no-use-gold-linker -$(package)_config_opts += -no-xinput2 $(package)_config_opts += -nomake examples $(package)_config_opts += -nomake tests $(package)_config_opts += -opensource -$(package)_config_opts += -optimized-tools $(package)_config_opts += -pkg-config $(package)_config_opts += -prefix $(host_prefix) $(package)_config_opts += -qt-libpng $(package)_config_opts += -qt-pcre $(package)_config_opts += -qt-harfbuzz -$(package)_config_opts += -system-zlib +$(package)_config_opts += -qt-zlib $(package)_config_opts += -static $(package)_config_opts += -v $(package)_config_opts += -no-feature-bearermanagement @@ -94,10 +91,10 @@ $(package)_config_opts += -no-feature-printdialog $(package)_config_opts += -no-feature-printer $(package)_config_opts += -no-feature-printpreviewdialog $(package)_config_opts += -no-feature-printpreviewwidget -$(package)_config_opts += -no-feature-regularexpression $(package)_config_opts += -no-feature-sessionmanager $(package)_config_opts += -no-feature-socks5 $(package)_config_opts += -no-feature-sql +$(package)_config_opts += -no-feature-sqlmodel $(package)_config_opts += -no-feature-statemachine $(package)_config_opts += -no-feature-syntaxhighlighter $(package)_config_opts += -no-feature-textbrowser @@ -115,6 +112,8 @@ $(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) $(package)_config_opts_darwin += -xplatform macx-clang-linux @@ -129,13 +128,13 @@ endif # for macOS on Apple Silicon (ARM) see https://bugreports.qt.io/browse/QTBUG-85279 $(package)_config_opts_arm_darwin += -device-option QMAKE_APPLE_DEVICE_ARCHS=arm64 -$(package)_config_opts_linux = -qt-xkbcommon-x11 -$(package)_config_opts_linux += -qt-xcb +$(package)_config_opts_linux = -qt-xcb $(package)_config_opts_linux += -no-xcb-xlib $(package)_config_opts_linux += -no-feature-xlib $(package)_config_opts_linux += -system-freetype $(package)_config_opts_linux += -fontconfig $(package)_config_opts_linux += -no-opengl +$(package)_config_opts_linux += -no-feature-vulkan $(package)_config_opts_linux += -dbus-runtime $(package)_config_opts_arm_linux += -platform linux-g++ -xplatform bitcoin-linux-g++ $(package)_config_opts_i686_linux = -xplatform linux-g++-32 @@ -167,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 @@ -220,21 +220,18 @@ endef # 8. Adjust a regex in toolchain.prf, to accommodate Guix's usage of # CROSS_LIBRARY_PATH. See #15277. define $(package)_preprocess_cmds - patch -p1 -i $($(package)_patch_dir)/freetype_back_compat.patch && \ - patch -p1 -i $($(package)_patch_dir)/fix_powerpc_libpng.patch && \ patch -p1 -i $($(package)_patch_dir)/drop_lrelease_dependency.patch && \ patch -p1 -i $($(package)_patch_dir)/dont_hardcode_pwd.patch && \ patch -p1 -i $($(package)_patch_dir)/fix_qt_pkgconfig.patch && \ - patch -p1 -i $($(package)_patch_dir)/fix_configure_mac.patch && \ patch -p1 -i $($(package)_patch_dir)/fix_no_printer.patch && \ - patch -p1 -i $($(package)_patch_dir)/fix_rcc_determinism.patch && \ - patch -p1 -i $($(package)_patch_dir)/xkb-default.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_riscv64_arch.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_mingw_cross_compile.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/ &&\ @@ -247,8 +244,8 @@ define $(package)_preprocess_cmds sed -i.old "s|QMAKE_CFLAGS += |!host_build: QMAKE_CFLAGS = $($(package)_cflags) $($(package)_cppflags) |" qtbase/mkspecs/win32-g++/qmake.conf && \ sed -i.old "s|QMAKE_CXXFLAGS += |!host_build: QMAKE_CXXFLAGS = $($(package)_cxxflags) $($(package)_cppflags) |" qtbase/mkspecs/win32-g++/qmake.conf && \ sed -i.old "0,/^QMAKE_LFLAGS_/s|^QMAKE_LFLAGS_|!host_build: QMAKE_LFLAGS = $($(package)_ldflags)\n&|" qtbase/mkspecs/win32-g++/qmake.conf && \ - sed -i.old "s|QMAKE_CC = clang|QMAKE_CC = $($(package)_cc)|" qtbase/mkspecs/common/clang.conf && \ - sed -i.old "s|QMAKE_CXX = clang++|QMAKE_CXX = $($(package)_cxx)|" qtbase/mkspecs/common/clang.conf && \ + sed -i.old "s|QMAKE_CC = \$$$$\$$$${CROSS_COMPILE}clang|QMAKE_CC = $($(package)_cc)|" qtbase/mkspecs/common/clang.conf && \ + sed -i.old "s|QMAKE_CXX = \$$$$\$$$${CROSS_COMPILE}clang++|QMAKE_CXX = $($(package)_cxx)|" qtbase/mkspecs/common/clang.conf && \ sed -i.old "s/LIBRARY_PATH/(CROSS_)?\0/g" qtbase/mkspecs/features/toolchain.prf endef @@ -258,8 +255,6 @@ define $(package)_config_cmds export PKG_CONFIG_PATH=$(host_prefix)/share/pkgconfig && \ cd qtbase && \ ./configure $($(package)_config_opts) && \ - echo "host_build: QT_CONFIG ~= s/system-zlib/zlib" >> mkspecs/qconfig.pri && \ - echo "CONFIG += force_bootstrap" >> mkspecs/qconfig.pri && \ cd .. && \ $(MAKE) -C qtbase sub-src-clean && \ qtbase/bin/qmake -o qttranslations/Makefile qttranslations/qttranslations.pro && \ diff --git a/depends/packages/zlib.mk b/depends/packages/zlib.mk deleted file mode 100644 index acb02020a8..0000000000 --- a/depends/packages/zlib.mk +++ /dev/null @@ -1,31 +0,0 @@ -package=zlib -$(package)_version=1.2.11 -$(package)_download_path=https://www.zlib.net -$(package)_file_name=$(package)-$($(package)_version).tar.gz -$(package)_sha256_hash=c3e5e9fdd5004dcb542feda5ee4f0ff0744628baf8ed2dd5d66f8ca1197cb1a1 - -define $(package)_set_vars -$(package)_config_opts= CC="$($(package)_cc)" -$(package)_config_opts+=CFLAGS="$($(package)_cflags) $($(package)_cppflags) -fPIC" -$(package)_config_opts+=RANLIB="$($(package)_ranlib)" -$(package)_config_opts+=AR="$($(package)_ar)" -$(package)_config_opts_darwin+=AR="$($(package)_libtool)" -$(package)_config_opts_darwin+=ARFLAGS="-o" -$(package)_config_opts_android+=CHOST=$(host) -endef - -# zlib has its own custom configure script that takes in options like CC, -# CFLAGS, RANLIB, AR, and ARFLAGS from the environment rather than from -# command-line arguments. -define $(package)_config_cmds - env $($(package)_config_opts) ./configure --static --prefix=$(host_prefix) -endef - -define $(package)_build_cmds - $(MAKE) libz.a -endef - -define $(package)_stage_cmds - $(MAKE) DESTDIR=$($(package)_staging_dir) install -endef - 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/drop_lrelease_dependency.patch b/depends/patches/qt/drop_lrelease_dependency.patch index f6b2c9fc80..9b918af77c 100644 --- a/depends/patches/qt/drop_lrelease_dependency.patch +++ b/depends/patches/qt/drop_lrelease_dependency.patch @@ -14,7 +14,7 @@ diff --git a/qttranslations/translations/translations.pro b/qttranslations/trans index 694544c..eff339d 100644 --- a/qttranslations/translations/translations.pro +++ b/qttranslations/translations/translations.pro -@@ -109,3 +109,2 @@ updateqm.commands = $$LRELEASE ${QMAKE_FILE_IN} -qm ${QMAKE_FILE_OUT} +@@ -107,3 +107,2 @@ updateqm.commands = $$LRELEASE ${QMAKE_FILE_IN} -qm ${QMAKE_FILE_OUT} silent:updateqm.commands = @echo lrelease ${QMAKE_FILE_IN} && $$updateqm.commands -updateqm.depends = $$LRELEASE_EXE updateqm.name = LRELEASE ${QMAKE_FILE_IN} diff --git a/depends/patches/qt/fix_android_jni_static.patch b/depends/patches/qt/fix_android_jni_static.patch index 2f6ff00f40..f891da6ddf 100644 --- a/depends/patches/qt/fix_android_jni_static.patch +++ b/depends/patches/qt/fix_android_jni_static.patch @@ -1,6 +1,6 @@ --- old/qtbase/src/plugins/platforms/android/androidjnimain.cpp +++ new/qtbase/src/plugins/platforms/android/androidjnimain.cpp -@@ -890,6 +890,14 @@ +@@ -897,6 +897,14 @@ __android_log_print(ANDROID_LOG_FATAL, "Qt", "registerNatives failed"); return -1; } 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_android_qmake_conf.patch b/depends/patches/qt/fix_android_qmake_conf.patch index 13bfff9776..3a8753fd1d 100644 --- a/depends/patches/qt/fix_android_qmake_conf.patch +++ b/depends/patches/qt/fix_android_qmake_conf.patch @@ -1,20 +1,10 @@ --- old/qtbase/mkspecs/android-clang/qmake.conf +++ new/qtbase/mkspecs/android-clang/qmake.conf -@@ -30,7 +30,7 @@ - QMAKE_CFLAGS += -target mips64el-none-linux-android +@@ -47,7 +47,7 @@ ANDROID_STDCPP_PATH = $$ANDROID_SOURCES_CXX_STL_LIBDIR/libc++_shared.so + ANDROID_USE_LLVM = true - QMAKE_CFLAGS += -gcc-toolchain $$NDK_TOOLCHAIN_PATH --QMAKE_LINK = $$QMAKE_CXX $$QMAKE_CFLAGS -Wl,--exclude-libs,libgcc.a -+QMAKE_LINK = $$QMAKE_CXX $$QMAKE_CFLAGS -Wl,--exclude-libs,libgcc.a -nostdlib++ - QMAKE_CFLAGS += -DANDROID_HAS_WSTRING --sysroot=$$NDK_ROOT/sysroot \ - -isystem $$NDK_ROOT/sysroot/usr/include/$$NDK_TOOLS_PREFIX \ - -isystem $$NDK_ROOT/sources/cxx-stl/llvm-libc++/include \ -@@ -40,7 +40,7 @@ - ANDROID_SOURCES_CXX_STL_LIBDIR = $$NDK_ROOT/sources/cxx-stl/llvm-libc++/libs/$$ANDROID_TARGET_ARCH - - ANDROID_STDCPP_PATH = $$ANDROID_SOURCES_CXX_STL_LIBDIR/libc++_shared.so --ANDROID_CXX_STL_LIBS = -lc++ -+ANDROID_CXX_STL_LIBS = -lc++_shared - - QMAKE_ARM_CFLAGS_RELEASE = -Oz - QMAKE_ARM_CFLAGS_RELEASE_WITH_DEBUGINFO = -g -Oz + exists($$ANDROID_SOURCES_CXX_STL_LIBDIR/libc++.so): \ +- ANDROID_CXX_STL_LIBS = -lc++ ++ ANDROID_CXX_STL_LIBS = -lc++_shared + else: \ + ANDROID_CXX_STL_LIBS = $$ANDROID_SOURCES_CXX_STL_LIBDIR/libc++.so.$$replace(ANDROID_PLATFORM, "android-", "") 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/depends/patches/qt/fix_configure_mac.patch b/depends/patches/qt/fix_configure_mac.patch deleted file mode 100644 index 0d7dd647de..0000000000 --- a/depends/patches/qt/fix_configure_mac.patch +++ /dev/null @@ -1,50 +0,0 @@ ---- old/qtbase/mkspecs/features/mac/sdk.prf 2018-02-08 10:24:48.000000000 -0800 -+++ new/qtbase/mkspecs/features/mac/sdk.prf 2018-03-23 10:38:56.000000000 -0700 -@@ -8,21 +8,21 @@ - defineReplace(xcodeSDKInfo) { - info = $$1 - equals(info, "Path"): \ -- info = --show-sdk-path -+ infoarg = --show-sdk-path - equals(info, "PlatformPath"): \ -- info = --show-sdk-platform-path -+ infoarg = --show-sdk-platform-path - equals(info, "SDKVersion"): \ -- info = --show-sdk-version -+ infoarg = --show-sdk-version - sdk = $$2 - isEmpty(sdk): \ - sdk = $$QMAKE_MAC_SDK - - isEmpty(QMAKE_MAC_SDK.$${sdk}.$${info}) { -- QMAKE_MAC_SDK.$${sdk}.$${info} = $$system("/usr/bin/xcrun --sdk $$sdk $$info 2>/dev/null") -+ QMAKE_MAC_SDK.$${sdk}.$${info} = $$system("/usr/bin/xcrun --sdk $$sdk $$infoarg 2>/dev/null") - # --show-sdk-platform-path won't work for Command Line Tools; this is fine - # only used by the XCTest backend to testlib -- isEmpty(QMAKE_MAC_SDK.$${sdk}.$${info}):if(!isEmpty(QMAKE_XCODEBUILD_PATH)|!equals(info, "--show-sdk-platform-path")): \ -- error("Could not resolve SDK $$info for \'$$sdk\'") -+ isEmpty(QMAKE_MAC_SDK.$${sdk}.$${info}):if(!isEmpty(QMAKE_XCODEBUILD_PATH)|!equals(infoarg, "--show-sdk-platform-path")): \ -+ error("Could not resolve SDK $$info for \'$$sdk\' using $$infoarg") - cache(QMAKE_MAC_SDK.$${sdk}.$${info}, set stash, QMAKE_MAC_SDK.$${sdk}.$${info}) - } - ---- old/qtbase/configure 2018-02-08 10:24:48.000000000 -0800 -+++ new/qtbase/configure 2018-03-23 05:42:29.000000000 -0700 -@@ -232,8 +232,13 @@ - - sdk=$(getSingleQMakeVariable "QMAKE_MAC_SDK" "$1") - if [ -z "$sdk" ]; then echo "QMAKE_MAC_SDK must be set when building on Mac" >&2; exit 1; fi -- sysroot=$(/usr/bin/xcrun --sdk $sdk --show-sdk-path 2>/dev/null) -- if [ -z "$sysroot" ]; then echo "Failed to resolve SDK path for '$sdk'" >&2; exit 1; fi -+ sysroot=$(getSingleQMakeVariable "QMAKE_MAC_SDK_PATH" "$1") -+ -+ echo "sysroot pre-configured as $sysroot"; -+ if [ -z "$sysroot" ]; then -+ sysroot=$(/usr/bin/xcrun --sdk $sdk --show-sdk-path 2>/dev/null) -+ if [ -z "$sysroot" ]; then echo "Failed to resolve SDK path for '$sdk'" >&2; exit 1; fi -+ fi - - case "$sdk" in - macosx*) - - diff --git a/depends/patches/qt/fix_lib_paths.patch b/depends/patches/qt/fix_lib_paths.patch new file mode 100644 index 0000000000..d1a15373f4 --- /dev/null +++ b/depends/patches/qt/fix_lib_paths.patch @@ -0,0 +1,193 @@ +--- old/qtbase/mkspecs/common/mac.conf ++++ new/qtbase/mkspecs/common/mac.conf +@@ -14,7 +14,6 @@ + + QMAKE_RESOURCE = /Developer/Tools/Rez + QMAKE_EXTENSION_SHLIB = dylib +-QMAKE_EXTENSIONS_AUX_SHLIB = tbd + QMAKE_LIBDIR = + + # sdk.prf will prefix the proper SDK sysroot + +--- old/qtbase/mkspecs/features/qmake_use.prf ++++ new/qtbase/mkspecs/features/qmake_use.prf +@@ -22,6 +22,8 @@ + !defined(QMAKE_LIBS_$$nu, var): \ + error("Library '$$lower($$replace(nu, _, -))' is not defined.") + ++ QMAKE_LIBDIR += $$eval(QMAKE_LIBDIR_$$nu) ++ + debug: \ + LIBS$${suffix} += $$eval(QMAKE_LIBS_$${nu}_DEBUG) $$eval(QMAKE_LIBS_$$nu) + else: \ + +--- old/qtbase/mkspecs/features/qt_configure.prf ++++ new/qtbase/mkspecs/features/qt_configure.prf +@@ -526,98 +526,23 @@ + return($$sysrootified) + } + +-# libs-var, libs, in-paths, out-paths-var ++# libs-var, libs, in-paths + defineTest(qtConfResolveLibs) { +- ret = true +- paths = $$3 +- out = +- copy = false +- for (l, 2) { +- $$copy { +- copy = false +- out += $$l +- } else: equals(l, "-s") { +- # em++ flag to link libraries from emscripten-ports; passed on literally. +- copy = true +- out += $$l +- } else: contains(l, "^-L.*") { +- lp = $$replace(l, "^-L", ) +- gcc: lp = $$qtGccSysrootifiedPath($$lp) +- !exists($$lp/.) { +- qtLog("Library path $$val_escape(lp) is invalid.") +- ret = false +- } else { +- paths += $$lp +- } +- } else: contains(l, "^-l.*") { +- lib = $$replace(l, "^-l", ) +- lcan = +- integrity:contains(lib, "^.*\\.a") { +- # INTEGRITY compiler searches for exact filename +- # if -l argument has .a suffix +- lcan += $${lib} +- } else: contains(lib, "^:.*") { +- # Use exact filename when -l:filename syntax is used. +- lib ~= s/^:// +- lcan += $${lib} +- } else: unix { +- # Under UNIX, we look for actual shared libraries, in addition +- # to static ones. +- shexts = $$QMAKE_EXTENSION_SHLIB $$QMAKE_EXTENSIONS_AUX_SHLIB +- for (ext, shexts) { +- lcan += $${QMAKE_PREFIX_SHLIB}$${lib}.$${ext} +- } +- lcan += \ +- $${QMAKE_PREFIX_STATICLIB}$${lib}.$${QMAKE_EXTENSION_STATICLIB} +- } else { +- # Under Windows, we look only for static libraries, as even for DLLs +- # one actually links against a static import library. +- mingw { +- lcan += \ +- # MinGW supports UNIX-style library naming in addition to +- # the MSVC style. +- lib$${lib}.dll.a lib$${lib}.a \ +- # Fun fact: prefix-less libraries are also supported. +- $${lib}.dll.a $${lib}.a +- } +- lcan += $${lib}.lib +- } +- l = $$qtConfFindInPathList($$lcan, $$paths $$EXTRA_LIBDIR $$QMAKE_DEFAULT_LIBDIRS) +- isEmpty(l) { +- qtLog("None of [$$val_escape(lcan)] found in [$$val_escape(paths)] and global paths.") +- ret = false +- } else { +- out += $$l +- } +- } else { +- out += $$l +- } +- } +- $$1 = $$out ++ for (path, 3): \ ++ pre_lflags += -L$$path ++ $$1 = $$pre_lflags $$2 + export($$1) +- !isEmpty(4) { +- $$4 = $$paths +- export($$4) +- } +- return($$ret) +-} +- +-# source-var +-defineTest(qtConfResolveAllLibs) { +- ret = true +- !qtConfResolveLibs($${1}.libs, $$eval($${1}.libs), , $${1}.libdirs): \ +- ret = false +- for (b, $${1}.builds._KEYS_): \ +- !qtConfResolveLibs($${1}.builds.$${b}, $$eval($${1}.builds.$${b}), $$eval($${1}.libdirs), ): \ +- ret = false +- return($$ret) ++ return(true) + } + + # libs-var, in-paths, libs + defineTest(qtConfResolvePathLibs) { + ret = true +- gcc: 2 = $$qtGccSysrootifiedPaths($$2) +- for (libdir, 2) { ++ gcc: \ ++ local_paths = $$qtGccSysrootifiedPaths($$2) ++ else: \ ++ local_paths = $$2 ++ for (libdir, local_paths) { + !exists($$libdir/.) { + qtLog("Library path $$val_escape(libdir) is invalid.") + ret = false +@@ -667,8 +592,11 @@ + # includes-var, in-paths, test-object-var + defineTest(qtConfResolvePathIncs) { + ret = true +- gcc: 2 = $$qtGccSysrootifiedPaths($$2) +- for (incdir, 2) { ++ gcc: \ ++ local_paths = $$qtGccSysrootifiedPaths($$2) ++ else: \ ++ local_paths = $$2 ++ for (incdir, local_paths) { + !exists($$incdir/.) { + qtLog("Include path $$val_escape(incdir) is invalid.") + ret = false +@@ -727,6 +655,7 @@ + vars += $$eval(config.commandline.rev_assignments.$${iv}) + defined(config.input.$${iv}, var) { + eval($${1}.builds.$${b} = $$eval(config.input.$${iv})) ++ export($${1}.builds.$${b}) + $${1}.builds._KEYS_ *= $${b} + any = true + } else { +@@ -741,11 +670,14 @@ + export($${1}.builds._KEYS_) + # we also reset the generic libs, to avoid surprises. + $${1}.libs = ++ export($${1}.libs) + } + + # direct libs. overwrites inline libs. +- defined(config.input.$${input}.libs, var): \ ++ defined(config.input.$${input}.libs, var) { + eval($${1}.libs = $$eval(config.input.$${input}.libs)) ++ export($${1}.libs) ++ } + + includes = $$eval(config.input.$${input}.incdir) + +@@ -754,6 +686,7 @@ + !isEmpty(prefix) { + includes += $$prefix/include + $${1}.libs = -L$$prefix/lib $$eval($${1}.libs) ++ export($${1}.libs) + } + + libdir = $$eval(config.input.$${input}.libdir) +@@ -762,11 +695,9 @@ + for (ld, libdir): \ + libs += -L$$ld + $${1}.libs = $$libs $$eval($${1}.libs) ++ export($${1}.libs) + } + +- !qtConfResolveAllLibs($$1): \ +- return(false) +- + !qtConfResolvePathIncs($${1}.includedir, $$includes, $$2): \ + return(false) + diff --git a/depends/patches/qt/fix_mingw_cross_compile.patch b/depends/patches/qt/fix_mingw_cross_compile.patch deleted file mode 100644 index 67f76f1d85..0000000000 --- a/depends/patches/qt/fix_mingw_cross_compile.patch +++ /dev/null @@ -1,25 +0,0 @@ -commit 5a992a549adfe5a587bbcd6cd2b2cee47d236e27 -Author: fanquake <fanquake@gmail.com> -Date: Fri Sep 4 08:13:44 2020 +0800 - - Work around broken mingw cross-compilation - - See upstream issues: - https://bugreports.qt.io/browse/QTBUG-63637 - https://bugreports.qt.io/browse/QTBUG-63659 - https://codereview.qt-project.org/q/8bebded9 - - We should be able to drop this once we are building qt 5.10.1 or later. - - Added in #12971. - -diff --git a/qtbase/mkspecs/win32-g++/qmake.conf b/qtbase/mkspecs/win32-g++/qmake.conf -index e071a0d1..ad229b10 100644 ---- a/qtbase/mkspecs/win32-g++/qmake.conf -+++ b/qtbase/mkspecs/win32-g++/qmake.conf -@@ -87,3 +87,5 @@ QMAKE_NM = $${CROSS_COMPILE}nm -P - include(../common/angle.conf) - - load(qt_config) -+QMAKE_LINK_OBJECT_MAX = 10 -+QMAKE_LINK_OBJECT_SCRIPT = object_script diff --git a/depends/patches/qt/fix_no_printer.patch b/depends/patches/qt/fix_no_printer.patch index f868ca2577..1372356138 100644 --- a/depends/patches/qt/fix_no_printer.patch +++ b/depends/patches/qt/fix_no_printer.patch @@ -10,10 +10,10 @@ --- x/qtbase/src/plugins/plugins.pro +++ y/qtbase/src/plugins/plugins.pro -@@ -8,6 +8,3 @@ qtHaveModule(gui) { - qtConfig(imageformatplugin): SUBDIRS *= imageformats +@@ -9,6 +9,3 @@ qtHaveModule(gui) { !android:qtConfig(library): SUBDIRS *= generic } + qtHaveModule(widgets): SUBDIRS += styles - -!winrt:qtHaveModule(printsupport): \ - SUBDIRS += printsupport diff --git a/depends/patches/qt/fix_powerpc_libpng.patch b/depends/patches/qt/fix_powerpc_libpng.patch deleted file mode 100644 index d37b6c7776..0000000000 --- a/depends/patches/qt/fix_powerpc_libpng.patch +++ /dev/null @@ -1,23 +0,0 @@ -commit 6f9feb773a43c5abfa3455da2e324180e789285b -Author: fanquake <fanquake@gmail.com> -Date: Tue Sep 15 21:44:31 2020 +0800 - - Fix PowerPC build of libpng - - See https://bugreports.qt.io/browse/QTBUG-66388. - - Can be dropped when we are building qt 5.12.0 or later. - -diff --git a/qtbase/src/3rdparty/libpng/libpng.pro b/qtbase/src/3rdparty/libpng/libpng.pro -index 577b61d8..a2f56669 100644 ---- a/qtbase/src/3rdparty/libpng/libpng.pro -+++ b/qtbase/src/3rdparty/libpng/libpng.pro -@@ -10,7 +10,7 @@ MODULE_INCLUDEPATH = $$PWD - - load(qt_helper_lib) - --DEFINES += PNG_ARM_NEON_OPT=0 -+DEFINES += PNG_ARM_NEON_OPT=0 PNG_POWERPC_VSX_OPT=0 - SOURCES += \ - png.c \ - pngerror.c \ diff --git a/depends/patches/qt/fix_qpainter_non_determinism.patch b/depends/patches/qt/fix_qpainter_non_determinism.patch index 3cfcc22f03..44c45187c5 100644 --- a/depends/patches/qt/fix_qpainter_non_determinism.patch +++ b/depends/patches/qt/fix_qpainter_non_determinism.patch @@ -22,7 +22,7 @@ diff --git a/qtbase/src/gui/painting/qpaintengine_raster.cpp b/qtbase/src/gui/pa index 92ab6e8375..f018009e0b 100644 --- a/qtbase/src/gui/painting/qpaintengine_raster.cpp +++ b/qtbase/src/gui/painting/qpaintengine_raster.cpp -@@ -3971,22 +3971,23 @@ static const QSpan *qt_intersect_spans(const QClipData *clip, int *currentClip, +@@ -4128,22 +4128,23 @@ static const QSpan *qt_intersect_spans(const QClipData *clip, int *currentClip, const QSpan *clipEnd = clip->m_spans + clip->count; while (available && spans < end ) { @@ -51,7 +51,7 @@ index 92ab6e8375..f018009e0b 100644 int sx1 = spans->x; int sx2 = sx1 + spans->len; -@@ -4005,7 +4006,7 @@ static const QSpan *qt_intersect_spans(const QClipData *clip, int *currentClip, +@@ -4162,7 +4163,7 @@ static const QSpan *qt_intersect_spans(const QClipData *clip, int *currentClip, if (len) { out->x = qMax(sx1, cx1); out->len = qMin(sx2, cx2) - out->x; diff --git a/depends/patches/qt/fix_qt_pkgconfig.patch b/depends/patches/qt/fix_qt_pkgconfig.patch index 8c722ffb46..a5de2b4b9e 100644 --- a/depends/patches/qt/fix_qt_pkgconfig.patch +++ b/depends/patches/qt/fix_qt_pkgconfig.patch @@ -1,17 +1,17 @@ --- old/qtbase/mkspecs/features/qt_module.prf +++ new/qtbase/mkspecs/features/qt_module.prf -@@ -264,7 +264,7 @@ +@@ -269,7 +269,7 @@ load(qt_installs) load(qt_targets) # this builds on top of qt_common --!internal_module:!lib_bundle:if(unix|mingw) { +-!internal_module:if(unix|mingw) { +if(unix|mingw):!if(darwin:debug_and_release:CONFIG(debug, debug|release)) { CONFIG += create_pc QMAKE_PKGCONFIG_DESTDIR = pkgconfig host_build: \ -@@ -274,9 +274,9 @@ - QMAKE_PKGCONFIG_INCDIR = $$[QT_INSTALL_HEADERS/raw] - QMAKE_PKGCONFIG_CFLAGS = -I${includedir}/$$MODULE_INCNAME +@@ -284,9 +284,9 @@ load(qt_targets) + QMAKE_PKGCONFIG_CFLAGS = -D$$MODULE_DEFINE -I${includedir}/$$MODULE_INCNAME + } QMAKE_PKGCONFIG_NAME = $$replace(TARGET, ^Qt, "Qt$$QT_MAJOR_VERSION ") - QMAKE_PKGCONFIG_FILE = $$replace(TARGET, ^Qt, Qt$$QT_MAJOR_VERSION) + QMAKE_PKGCONFIG_FILE = $$replace(TARGET, ^Qt, Qt$$QT_MAJOR_VERSION)$$qtPlatformTargetSuffix() @@ -20,4 +20,4 @@ + QMAKE_PKGCONFIG_REQUIRES += $$replace(QT.$${i}.name, ^Qt, Qt$$section(QT.$${i}.VERSION, ., 0, 0))$$qtPlatformTargetSuffix() isEmpty(QMAKE_PKGCONFIG_DESCRIPTION): \ QMAKE_PKGCONFIG_DESCRIPTION = $$replace(TARGET, ^Qt, "Qt ") module - pclib_replace.match = $$lib_replace.match + !isEmpty(lib_replace0.match) { diff --git a/depends/patches/qt/fix_rcc_determinism.patch b/depends/patches/qt/fix_rcc_determinism.patch deleted file mode 100644 index c1b07fe23a..0000000000 --- a/depends/patches/qt/fix_rcc_determinism.patch +++ /dev/null @@ -1,15 +0,0 @@ ---- old/qtbase/src/tools/rcc/rcc.cpp -+++ new/qtbase/src/tools/rcc/rcc.cpp -@@ -207,7 +207,11 @@ void RCCFileInfo::writeDataInfo(RCCResourceLibrary &lib) - if (lib.formatVersion() >= 2) { - // last modified time stamp - const QDateTime lastModified = m_fileInfo.lastModified(); -- lib.writeNumber8(quint64(lastModified.isValid() ? lastModified.toMSecsSinceEpoch() : 0)); -+ quint64 lastmod = quint64(lastModified.isValid() ? lastModified.toMSecsSinceEpoch() : 0); -+ static const quint64 sourceDate = 1000 * qgetenv("QT_RCC_SOURCE_DATE_OVERRIDE").toULongLong(); -+ if (sourceDate != 0) -+ lastmod = sourceDate; -+ lib.writeNumber8(lastmod); - if (text || pass1) - lib.writeChar('\n'); - } diff --git a/depends/patches/qt/fix_riscv64_arch.patch b/depends/patches/qt/fix_riscv64_arch.patch deleted file mode 100644 index e7f29f01f9..0000000000 --- a/depends/patches/qt/fix_riscv64_arch.patch +++ /dev/null @@ -1,14 +0,0 @@ -diff --git a/qtbase/src/3rdparty/double-conversion/include/double-conversion/utils.h b/qtbase/src/3rdparty/double-conversion/include/double-conversion/utils.h -index 20bfd36..93729fa 100644 ---- a/qtbase/src/3rdparty/double-conversion/include/double-conversion/utils.h -+++ b/qtbase/src/3rdparty/double-conversion/include/double-conversion/utils.h -@@ -65,7 +65,8 @@ - defined(__sparc__) || defined(__sparc) || defined(__s390__) || \ - defined(__SH4__) || defined(__alpha__) || \ - defined(_MIPS_ARCH_MIPS32R2) || \ -- defined(__AARCH64EL__) -+ defined(__AARCH64EL__) || defined(__aarch64__) || \ -+ defined(__riscv) - #define DOUBLE_CONVERSION_CORRECT_DOUBLE_OPERATIONS 1 - #elif defined(_M_IX86) || defined(__i386__) || defined(__i386) - #if defined(_WIN32) diff --git a/depends/patches/qt/freetype_back_compat.patch b/depends/patches/qt/freetype_back_compat.patch deleted file mode 100644 index 1ca55f1ce3..0000000000 --- a/depends/patches/qt/freetype_back_compat.patch +++ /dev/null @@ -1,28 +0,0 @@ -commit 14bc77db61bf9d56f9b6c8b84aa02573605c19c6 -Author: fanquake <fanquake@gmail.com> -Date: Tue Aug 18 15:15:08 2020 +0800 - - Fix backwards compatibility with older Freetype versions at runtime - - A few years ago, libfreetype introduced FT_Get_Font_Format() as an alias - for FT_Get_X11_Font_Format(), but FT_Get_X11_Font_Format() was kept for abi - backwards-compatibility. - - Qt 5.9 introduced a call to FT_Get_Font_Format(). Replace it with FT_Get_X11_Font_Format() - in order to remain compatible with older freetype, which is still used by e.g. Ubuntu Trusty. - - See #14348. - -diff --git a/qtbase/src/platformsupport/fontdatabases/freetype/qfontengine_ft.cpp b/qtbase/src/platformsupport/fontdatabases/freetype/qfontengine_ft.cpp -index 3f543755..8ecc1c8c 100644 ---- a/qtbase/src/platformsupport/fontdatabases/freetype/qfontengine_ft.cpp -+++ b/qtbase/src/platformsupport/fontdatabases/freetype/qfontengine_ft.cpp -@@ -898,7 +898,7 @@ bool QFontEngineFT::init(FaceId faceId, bool antialias, GlyphFormat format, - } - } - #if defined(FT_FONT_FORMATS_H) -- const char *fmt = FT_Get_Font_Format(face); -+ const char *fmt = FT_Get_X11_Font_Format(face); - if (fmt && qstrncmp(fmt, "CFF", 4) == 0) { - FT_Bool no_stem_darkening = true; - FT_Error err = FT_Property_Get(qt_getFreetype(), "cff", "no-stem-darkening", &no_stem_darkening); diff --git a/depends/patches/qt/no-xlib.patch b/depends/patches/qt/no-xlib.patch index fe82c2c73c..f4a6f09ee4 100644 --- a/depends/patches/qt/no-xlib.patch +++ b/depends/patches/qt/no-xlib.patch @@ -22,15 +22,15 @@ index 7c62c2e2b3..c05c6c0a07 100644 #include <xcb/xfixes.h> #include <xcb/xcb_image.h> -@@ -384,6 +386,7 @@ void QXcbCursor::changeCursor(QCursor *cursor, QWindow *widget) - w->setCursor(c, isBitmapCursor); +@@ -391,6 +393,7 @@ void QXcbCursor::changeCursor(QCursor *cursor, QWindow *window) + xcb_flush(xcb_connection()); } +#if QT_CONFIG(xcb_xlib) && QT_CONFIG(library) static int cursorIdForShape(int cshape) { int cursorId = 0; -@@ -437,6 +440,7 @@ static int cursorIdForShape(int cshape) +@@ -444,6 +447,7 @@ static int cursorIdForShape(int cshape) } return cursorId; } @@ -38,7 +38,7 @@ index 7c62c2e2b3..c05c6c0a07 100644 xcb_cursor_t QXcbCursor::createNonStandardCursor(int cshape) { -@@ -558,7 +562,9 @@ static xcb_cursor_t loadCursor(void *dpy, int cshape) +@@ -556,7 +560,9 @@ static xcb_cursor_t loadCursor(void *dpy, int cshape) xcb_cursor_t QXcbCursor::createFontCursor(int cshape) { xcb_connection_t *conn = xcb_connection(); @@ -48,22 +48,23 @@ index 7c62c2e2b3..c05c6c0a07 100644 xcb_cursor_t cursor = XCB_NONE; // Try Xcursor first -@@ -589,6 +595,7 @@ xcb_cursor_t QXcbCursor::createFontCursor(int cshape) +@@ -585,7 +591,7 @@ xcb_cursor_t QXcbCursor::createFontCursor(int cshape) + // Non-standard X11 cursors are created from bitmaps cursor = createNonStandardCursor(cshape); - +- +#if QT_CONFIG(xcb_xlib) && QT_CONFIG(library) // Create a glpyh cursor if everything else failed if (!cursor && cursorId) { cursor = xcb_generate_id(conn); -@@ -596,6 +603,7 @@ xcb_cursor_t QXcbCursor::createFontCursor(int cshape) +@@ -593,6 +599,7 @@ xcb_cursor_t QXcbCursor::createFontCursor(int cshape) cursorId, cursorId + 1, 0xFFFF, 0xFFFF, 0xFFFF, 0, 0, 0); } +#endif if (cursor && cshape >= 0 && cshape < Qt::LastCursor && connection()->hasXFixes()) { - const char *name = cursorNames[cshape]; + const char *name = cursorNames[cshape].front(); -- 2.22.0 diff --git a/depends/patches/qt/no_sdk_version_check.patch b/depends/patches/qt/no_sdk_version_check.patch new file mode 100644 index 0000000000..b16635b572 --- /dev/null +++ b/depends/patches/qt/no_sdk_version_check.patch @@ -0,0 +1,20 @@ +commit f5eb142cd04be2bc4ca610ed3b5b7e8ce3520ee3 +Author: fanquake <fanquake@gmail.com> +Date: Tue Jan 5 16:08:49 2021 +0800 + + Don't invoke macOS SDK version checking + + This tries to use xcrun which is not available when cross-compiling. + +diff --git a/qtbase/mkspecs/features/mac/default_post.prf b/qtbase/mkspecs/features/mac/default_post.prf +index 92a9112bca6..447e186eb26 100644 +--- a/qtbase/mkspecs/features/mac/default_post.prf ++++ b/qtbase/mkspecs/features/mac/default_post.prf +@@ -8,7 +8,6 @@ contains(TEMPLATE, .*app) { + !macx-xcode:if(isEmpty(BUILDS)|build_pass) { + # Detect changes to the platform SDK + QMAKE_EXTRA_VARIABLES += QMAKE_MAC_SDK QMAKE_MAC_SDK_VERSION QMAKE_XCODE_DEVELOPER_PATH +- QMAKE_EXTRA_INCLUDES += $$shell_quote($$PWD/sdk.mk) + } + + # Detect incompatible SDK versions diff --git a/depends/patches/qt/xkb-default.patch b/depends/patches/qt/xkb-default.patch deleted file mode 100644 index 165abf3e2e..0000000000 --- a/depends/patches/qt/xkb-default.patch +++ /dev/null @@ -1,26 +0,0 @@ ---- old/qtbase/src/gui/configure.pri 2018-06-06 17:28:10.000000000 -0400 -+++ new/qtbase/src/gui/configure.pri 2018-08-17 18:43:01.589384567 -0400 -@@ -43,18 +43,11 @@ - } - - defineTest(qtConfTest_xkbConfigRoot) { -- qtConfTest_getPkgConfigVariable($${1}): return(true) -- -- for (dir, $$list("/usr/share/X11/xkb", "/usr/local/share/X11/xkb")) { -- exists($$dir) { -- $${1}.value = $$dir -- export($${1}.value) -- $${1}.cache += value -- export($${1}.cache) -- return(true) -- } -- } -- return(false) -+ $${1}.value = "/usr/share/X11/xkb" -+ export($${1}.value) -+ $${1}.cache += value -+ export($${1}.cache) -+ return(true) - } - - defineTest(qtConfTest_qpaDefaultPlatform) { 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/REST-interface.md b/doc/REST-interface.md index ea06952af4..8b281acca7 100644 --- a/doc/REST-interface.md +++ b/doc/REST-interface.md @@ -95,11 +95,8 @@ $ curl localhost:18332/rest/getutxos/checkmempool/b2cdfd7b89def827ff8af7cd9bff76 "scriptPubKey" : { "asm" : "OP_DUP OP_HASH160 1c7cebb529b86a04c683dfa87be49de35bcf589e OP_EQUALVERIFY OP_CHECKSIG", "hex" : "76a9141c7cebb529b86a04c683dfa87be49de35bcf589e88ac", - "reqSigs" : 1, "type" : "pubkeyhash", - "addresses" : [ - "mi7as51dvLJsizWnTMurtRmrP8hG2m1XvD" - ] + "address" : "mi7as51dvLJsizWnTMurtRmrP8hG2m1XvD" } } ] diff --git a/doc/bips.md b/doc/bips.md index a5e9a6c020..f72dbead9d 100644 --- a/doc/bips.md +++ b/doc/bips.md @@ -1,4 +1,4 @@ -BIPs that are implemented by Bitcoin Core (up-to-date up to **v0.21.0**): +BIPs that are implemented by Bitcoin Core (up-to-date up to **v22.0**): * [`BIP 9`](https://github.com/bitcoin/bips/blob/master/bip-0009.mediawiki): The changes allowing multiple soft-forks to be deployed in parallel have been implemented since **v0.12.1** ([PR #7575](https://github.com/bitcoin/bitcoin/pull/7575)) * [`BIP 11`](https://github.com/bitcoin/bips/blob/master/bip-0011.mediawiki): Multisig outputs are standard since **v0.6.0** ([PR #669](https://github.com/bitcoin/bitcoin/pull/669)). @@ -50,3 +50,4 @@ BIPs that are implemented by Bitcoin Core (up-to-date up to **v0.21.0**): * [`BIP 325`](https://github.com/bitcoin/bips/blob/master/bip-0325.mediawiki): Signet test network is supported as of **v0.21.0** ([PR 18267](https://github.com/bitcoin/bitcoin/pull/18267)). * [`BIP 339`](https://github.com/bitcoin/bips/blob/master/bip-0339.mediawiki): Relay of transactions by wtxid is supported as of **v0.21.0** ([PR 18044](https://github.com/bitcoin/bitcoin/pull/18044)). * [`BIP 340`](https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki) [`341`](https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki) [`342`](https://github.com/bitcoin/bips/blob/master/bip-0342.mediawiki): Validation rules for Taproot (including Schnorr signatures and Tapscript leaves) are implemented as of **v0.21.0** ([PR 19953](https://github.com/bitcoin/bitcoin/pull/19953)), without mainnet activation. +* [`BIP 350`](https://github.com/bitcoin/bips/blob/master/bip-0350.mediawiki): Addresses for native v1+ segregated Witness outputs use Bech32m instead of Bech32 as of **v22.0** ([PR 20861](https://github.com/bitcoin/bitcoin/pull/20861)). diff --git a/doc/bitcoin-conf.md b/doc/bitcoin-conf.md index f4a8edec75..9a312bc33c 100644 --- a/doc/bitcoin-conf.md +++ b/doc/bitcoin-conf.md @@ -27,7 +27,7 @@ Comments may appear in two ways: ### Network specific options Network specific options can be: -- placed into sections with headers `[main]` (not `[mainnet]`), `[test]` (not `[testnet]`) or `[regtest]`; +- placed into sections with headers `[main]` (not `[mainnet]`), `[test]` (not `[testnet]`), `[signet]` or `[regtest]`; - prefixed with a chain name; e.g., `regtest.maxmempool=100`. Network specific options take precedence over non-network specific options. 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/build-osx.md b/doc/build-osx.md index 04ee43f81d..16c6da66d5 100644 --- a/doc/build-osx.md +++ b/doc/build-osx.md @@ -1,116 +1,303 @@ -# macOS Build Instructions and Notes +# macOS Build Guide + +**Updated for MacOS [11.2](https://www.apple.com/macos/big-sur/)** + +This guide describes how to build bitcoind, command-line utilities, and GUI on macOS + +**Note:** The following is for Intel Macs only! + +## Dependencies + +The following dependencies are **required**: + +Library | Purpose | Description +-----------------------------------------------------------|------------|---------------------- +[automake](https://formulae.brew.sh/formula/automake) | Build | Generate makefile +[libtool](https://formulae.brew.sh/formula/libtool) | Build | Shared library support +[pkg-config](https://formulae.brew.sh/formula/pkg-config) | Build | Configure compiler and linker flags +[boost](https://formulae.brew.sh/formula/boost) | Utility | Library for threading, data structures, etc +[libevent](https://formulae.brew.sh/formula/libevent) | Networking | OS independent asynchronous networking + +The following dependencies are **optional**: + +Library | Purpose | Description +--------------------------------------------------------------- |------------------|---------------------- +[berkeley-db@4](https://formulae.brew.sh/formula/berkeley-db@4) | Berkeley DB | Wallet storage (only needed when wallet enabled) +[qt@5](https://formulae.brew.sh/formula/qt@5) | GUI | GUI toolkit (only needed when GUI enabled) +[qrencode](https://formulae.brew.sh/formula/qrencode) | QR codes in GUI | Generating QR codes (only needed when GUI enabled) +[zeromq](https://formulae.brew.sh/formula/zeromq) | ZMQ notification | Allows generating ZMQ notifications (requires ZMQ version >= 4.0.0) +[sqlite](https://formulae.brew.sh/formula/sqlite) | SQLite DB | Wallet storage (only needed when wallet enabled) +[miniupnpc](https://formulae.brew.sh/formula/miniupnpc) | UPnP Support | Firewall-jumping support (needed for port mapping support) +[libnatpmp](https://formulae.brew.sh/formula/libnatpmp) | NAT-PMP Support | Firewall-jumping support (needed for port mapping support) +[python3](https://formulae.brew.sh/formula/python@3.9) | Testing | Python Interpreter (only needed when running the test suite) + +The following dependencies are **optional** packages required for deploying: + +Library | Purpose | Description +----------------------------------------------------|------------------|---------------------- +[librsvg](https://formulae.brew.sh/formula/librsvg) | Deploy Dependency| Library to render SVG files +[ds_store](https://pypi.org/project/ds-store/) | Deploy Dependency| Examine and modify .DS_Store files +[mac_alias](https://pypi.org/project/mac-alias/) | Deploy Dependency| Generate/Read binary alias and bookmark records + +See [dependencies.md](dependencies.md) for a complete overview. + +## Preparation The commands in this guide should be executed in a Terminal application. -The built-in one is located in +macOS comes with a built-in Terminal located in: + ``` /Applications/Utilities/Terminal.app ``` -## Preparation -Install the macOS command line tools: +### 1. Xcode Command Line Tools -```shell +The Xcode Command Line Tools are a collection of build tools for macOS. +These tools must be installed in order to build Bitcoin Core from source. + +To install, run the following command from your terminal: + +``` bash xcode-select --install ``` -When the popup appears, click `Install`. +Upon running the command, you should see a popup appear. +Click on `Install` to continue the installation process. -Then install [Homebrew](https://brew.sh). +### 2. Homebrew Package Manager -## Dependencies -```shell -brew install automake libtool boost miniupnpc libnatpmp pkg-config python qt libevent qrencode +Homebrew is a package manager for macOS that allows one to install packages from the command line easily. +While several package managers are available for macOS, this guide will focus on Homebrew as it is the most popular. +Since the examples in this guide which walk through the installation of a package will use Homebrew, it is recommended that you install it to follow along. +Otherwise, you can adapt the commands to your package manager of choice. + +To install the Homebrew package manager, see: https://brew.sh + +Note: If you run into issues while installing Homebrew or pulling packages, refer to [Homebrew's troubleshooting page](https://docs.brew.sh/Troubleshooting). + +### 3. Install Required Dependencies + +The first step is to download the required dependencies. +These dependencies represent the packages required to get a barebones installation up and running. +To install, run the following from your terminal: + +``` bash +brew install automake libtool boost pkg-config libevent ``` -If you run into issues, check [Homebrew's troubleshooting page](https://docs.brew.sh/Troubleshooting). -See [dependencies.md](dependencies.md) for a complete overview. +### 4. Clone Bitcoin repository -If you want to build the disk image with `make deploy` (.dmg / optional), you need RSVG: -```shell -brew install librsvg +`git` should already be installed by default on your system. +Now that all the required dependencies are installed, let's clone the Bitcoin Core repository to a directory. +All build scripts and commands will run from this directory. + +``` bash +git clone https://github.com/bitcoin/bitcoin.git ``` -and [`macdeployqtplus`](../contrib/macdeploy/README.md) dependencies: -```shell -pip3 install ds_store mac_alias +### 5. Install Optional Dependencies + +#### Wallet Dependencies + +It is not necessary to build wallet functionality to run `bitcoind` or `bitcoin-qt`. +To enable legacy wallets, you must install `berkeley-db@4`. +To enable [descriptor wallets](https://github.com/bitcoin/bitcoin/blob/master/doc/descriptors.md), `sqlite` is required. +Skip `berkeley-db@4` if you intend to *exclusively* use descriptor wallets. + +###### Legacy Wallet Support + +`berkeley-db@4` is required to enable support for legacy wallets. +Skip if you don't intend to use legacy wallets. + +``` bash +brew install berkeley-db@4 ``` -The wallet support requires one or both of the dependencies ([*SQLite*](#sqlite) and [*Berkeley DB*](#berkeley-db)) in the sections below. -To build Bitcoin Core without wallet, see [*Disable-wallet mode*](#disable-wallet-mode). +###### Descriptor Wallet Support -#### SQLite +Note: Apple has included a useable `sqlite` package since macOS 10.14. +You may not need to install this package. -Usually, macOS installation already has a suitable SQLite installation. -Also, the Homebrew package could be installed: +`sqlite` is required to enable support for descriptor wallets. +Skip if you don't intend to use descriptor wallets. -```shell +``` bash brew install sqlite ``` +--- -In that case the Homebrew package will prevail. +#### GUI Dependencies -#### Berkeley DB +###### Qt -It is recommended to use Berkeley DB 4.8. If you have to build it yourself, -you can use [this](/contrib/install_db4.sh) script to install it -like so: +Bitcoin Core includes a GUI built with the cross-platform Qt Framework. +To compile the GUI, we need to install `qt@5`. +Skip if you don't intend to use the GUI. -```shell -./contrib/install_db4.sh . +``` bash +brew install qt@5 ``` -from the root of the repository. +Note: Building with Qt binaries downloaded from the Qt website is not officially supported. +See the notes in [#7714](https://github.com/bitcoin/bitcoin/issues/7714). -Also, the Homebrew package could be installed: +###### qrencode -```shell -brew install berkeley-db4 +The GUI can encode addresses in a QR Code. To build in QR support for the GUI, install `qrencode`. +Skip if not using the GUI or don't want QR code functionality. + +``` bash +brew install qrencode ``` +--- + +#### Port Mapping Dependencies + +###### miniupnpc -## Build Bitcoin Core +miniupnpc may be used for UPnP port mapping. +Skip if you do not need this functionality. -1. Clone the Bitcoin Core source code: - ```shell - git clone https://github.com/bitcoin/bitcoin - cd bitcoin - ``` +``` bash +brew install miniupnpc +``` -2. Build Bitcoin Core: +###### libnatpmp - Configure and build the headless Bitcoin Core binaries as well as the GUI (if Qt is found). +libnatpmp may be used for NAT-PMP port mapping. +Skip if you do not need this functionality. - You can disable the GUI build by passing `--without-gui` to configure. - ```shell - ./autogen.sh - ./configure - make - ``` +``` bash +brew install libnatpmp +``` -3. It is recommended to build and run the unit tests: - ```shell - make check - ``` +Note: UPnP and NAT-PMP support will be compiled in and disabled by default. +Check out the [further configuration](#further-configuration) section for more information. -4. You can also create a `.dmg` that contains the `.app` bundle (optional): - ```shell - make deploy - ``` +--- -## Disable-wallet mode -When the intention is to run only a P2P node without a wallet, Bitcoin Core may be -compiled in disable-wallet mode with: -```shell -./configure --disable-wallet +#### ZMQ Dependencies + +Support for ZMQ notifications requires the following dependency. +Skip if you do not need ZMQ functionality. + +``` bash +brew install zeromq ``` -In this case there is no dependency on [*Berkeley DB*](#berkeley-db) and [*SQLite*](#sqlite). +ZMQ is automatically compiled in and enabled if the dependency is detected. +Check out the [further configuration](#further-configuration) section for more information. + +For more information on ZMQ, see: [zmq.md](zmq.md) -Mining is also possible in disable-wallet mode using the `getblocktemplate` RPC call. +--- -## Running -Bitcoin Core is now available at `./src/bitcoind` +#### Test Suite Dependencies + +There is an included test suite that is useful for testing code changes when developing. +To run the test suite (recommended), you will need to have Python 3 installed: + +``` bash +brew install python +``` + +--- + +#### Deploy Dependencies + +You can deploy a `.dmg` containing the Bitcoin Core application using `make deploy`. +This command depends on a couple of python packages, so it is required that you have `python` installed. + +Ensuring that `python` is installed, you can install the deploy dependencies by running the following commands in your terminal: + +``` bash +brew install librsvg +``` + +``` bash +pip3 install ds_store mac_alias +``` + +## Building Bitcoin Core + +### 1. Configuration + +There are many ways to configure Bitcoin Core, here are a few common examples: + +##### Wallet (BDB + SQlite) Support, No GUI: + +If `berkeley-db@4` is installed, then legacy wallet support will be built. +If `berkeley-db@4` is not installed, then this will throw an error. +If `sqlite` is installed, then descriptor wallet support will also be built. +Additionally, this explicitly disables the GUI. + +``` bash +./autogen.sh +./configure --with-gui=no +``` + +##### Wallet (only SQlite) and GUI Support: + +This explicitly enables the GUI and disables legacy wallet support. +If `qt` is not installed, this will throw an error. +If `sqlite` is installed then descriptor wallet functionality will be built. +If `sqlite` is not installed, then wallet functionality will be disabled. + +``` bash +./autogen.sh +./configure --without-bdb --with-gui=yes +``` + +##### No Wallet or GUI + +``` bash +./autogen.sh +./configure --without-wallet --with-gui=no +``` + +##### Further Configuration + +You may want to dig deeper into the configuration options to achieve your desired behavior. +Examine the output of the following command for a full list of configuration options: + +``` bash +./configure -help +``` + +### 2. Compile + +After configuration, you are ready to compile. +Run the following in your terminal to compile Bitcoin Core: + +``` bash +make -jx # use -jX here for parallelism +make check # Run tests if Python 3 is available +``` + +### 3. Deploy (optional) + +You can also create a `.dmg` containing the `.app` bundle by running the following command: + +``` bash +make deploy +``` + +## Running Bitcoin Core + +Bitcoin Core should now be available at `./src/bitcoind`. +If you compiled support for the GUI, it should be available at `./src/qt/bitcoin-qt`. + +The first time you run `bitcoind` or `bitcoin-qt`, it will start downloading the blockchain. +This process could take many hours, or even days on slower than average systems. + +By default, blockchain and wallet data files will be stored in: + +``` bash +/Users/${USER}/Library/Application Support/Bitcoin/ +``` Before running, you may create an empty configuration file: + ```shell mkdir -p "/Users/${USER}/Library/Application Support/Bitcoin" @@ -119,22 +306,17 @@ touch "/Users/${USER}/Library/Application Support/Bitcoin/bitcoin.conf" chmod 600 "/Users/${USER}/Library/Application Support/Bitcoin/bitcoin.conf" ``` -The first time you run bitcoind, it will start downloading the blockchain. This process could -take many hours, or even days on slower than average systems. - You can monitor the download process by looking at the debug.log file: + ```shell tail -f $HOME/Library/Application\ Support/Bitcoin/debug.log ``` ## Other commands: + ```shell ./src/bitcoind -daemon # Starts the bitcoin daemon. ./src/bitcoin-cli --help # Outputs a list of command-line options. ./src/bitcoin-cli help # Outputs a list of RPC commands when the daemon is running. +./src/qt/bitcoin-qt -server # Starts the bitcoin-qt server mode, allows bitcoin-cli control ``` - -## Notes -* Tested on OS X 10.14 Mojave through macOS 11 Big Sur on 64-bit Intel -processors only. -* Building with downloaded Qt binaries is not officially supported. See the notes in [#7714](https://github.com/bitcoin/bitcoin/issues/7714). diff --git a/doc/build-unix.md b/doc/build-unix.md index 07fb9c453e..d7e0ff705d 100644 --- a/doc/build-unix.md +++ b/doc/build-unix.md @@ -84,23 +84,22 @@ Now, you can either build from self-compiled [depends](/depends/README.md) or in sudo apt-get install libevent-dev libboost-dev libboost-system-dev libboost-filesystem-dev libboost-test-dev -BerkeleyDB is required for the wallet. +Berkeley DB is required for the wallet. Ubuntu and Debian have their own `libdb-dev` and `libdb++-dev` packages, but these will install -BerkeleyDB 5.1 or later. This will break binary wallet compatibility with the distributed executables, which +Berkeley DB 5.1 or later. This will break binary wallet compatibility with the distributed executables, which are based on BerkeleyDB 4.8. If you do not care about wallet compatibility, pass `--with-incompatible-bdb` to configure. -Otherwise, you can build from self-compiled `depends` (see above). +Otherwise, you can build Berkeley DB [yourself](#berkeley-db). -SQLite is required for the wallet: +SQLite is required for the descriptor wallet: sudo apt install libsqlite3-dev -To build Bitcoin Core without wallet, see [*Disable-wallet mode*](/doc/build-unix.md#disable-wallet-mode) +To build Bitcoin Core without wallet, see [*Disable-wallet mode*](#disable-wallet-mode) - -Optional port mapping libraries (see: `--with-miniupnpc`, and `--enable-upnp-default`, `--with-natpmp`, `--enable-natpmp-default`): +Optional port mapping libraries (see: `--with-miniupnpc`, `--enable-upnp-default`, and `--with-natpmp`, `--enable-natpmp-default`): sudo apt install libminiupnpc-dev libnatpmp-dev @@ -132,9 +131,30 @@ built by default. Build requirements: - sudo dnf install gcc-c++ libtool make autoconf automake libevent-devel boost-devel libdb4-devel libdb4-cxx-devel python3 + sudo dnf install gcc-c++ libtool make autoconf automake python3 + +Now, you can either build from self-compiled [depends](/depends/README.md) or install the required dependencies: + + sudo dnf install libevent-devel boost-devel + +Berkeley DB is required for the wallet: + + sudo dnf install libdb4-devel libdb4-cxx-devel + +Newer Fedora releases, since Fedora 33, have only `libdb-devel` and `libdb-cxx-devel` packages, but these will install +Berkeley DB 5.3 or later. This will break binary wallet compatibility with the distributed executables, which +are based on Berkeley DB 4.8. If you do not care about wallet compatibility, +pass `--with-incompatible-bdb` to configure. + +Otherwise, you can build Berkeley DB [yourself](#berkeley-db). + +SQLite is required for the descriptor wallet: + + sudo dnf install sqlite-devel + +To build Bitcoin Core without wallet, see [*Disable-wallet mode*](#disable-wallet-mode) -Optional port mapping libraries (see: `--with-miniupnpc`, and `--enable-upnp-default`, `--with-natpmp`, `--enable-natpmp-default`): +Optional port mapping libraries (see: `--with-miniupnpc`, `--enable-upnp-default`, and `--with-natpmp`, `--enable-natpmp-default`): sudo dnf install miniupnpc-devel libnatpmp-devel @@ -142,6 +162,12 @@ ZMQ dependencies (provides ZMQ API): sudo dnf install zeromq-devel +GUI dependencies: + +If you want to build bitcoin-qt, make sure that the required packages for Qt development +are installed. Qt 5 is necessary to build the GUI. +To build without GUI pass `--without-gui`. + To build with Qt 5 you need the following: sudo dnf install qt5-qttools-devel qt5-qtbase-devel @@ -150,9 +176,8 @@ libqrencode (optional) can be installed with: sudo dnf install qrencode-devel -SQLite can be installed with: - - sudo dnf install sqlite-devel +Once these are installed, they will be found by configure and a bitcoin-qt executable will be +built by default. Notes ----- @@ -193,7 +218,9 @@ like so: from the root of the repository. -**Note**: You only need Berkeley DB if the wallet is enabled (see [*Disable-wallet mode*](/doc/build-unix.md#disable-wallet-mode)). +Otherwise, you can build Bitcoin Core from self-compiled [depends](/depends/README.md). + +**Note**: You only need Berkeley DB if the wallet is enabled (see [*Disable-wallet mode*](#disable-wallet-mode)). Boost ----- diff --git a/doc/build-windows.md b/doc/build-windows.md index 28b6aceb3c..d1b84eef42 100644 --- a/doc/build-windows.md +++ b/doc/build-windows.md @@ -103,7 +103,7 @@ Build using: cd depends make HOST=x86_64-w64-mingw32 cd .. - ./autogen.sh # not required when building from tarball + ./autogen.sh CONFIG_SITE=$PWD/depends/x86_64-w64-mingw32/share/config.site ./configure --prefix=/ make sudo bash -c "echo 1 > /proc/sys/fs/binfmt_misc/status" # Enable WSL support for Win32 applications. diff --git a/doc/dependencies.md b/doc/dependencies.md index 6b2b904632..22161856ce 100644 --- a/doc/dependencies.md +++ b/doc/dependencies.md @@ -14,19 +14,19 @@ These are the dependencies currently used by Bitcoin Core. You can find instruct | GCC | | [7+](https://gcc.gnu.org/) (C++17 support) | | | | | HarfBuzz-NG | | | | | [Yes](https://github.com/bitcoin/bitcoin/blob/master/depends/packages/qt.mk) | | libevent | [2.1.11-stable](https://github.com/libevent/libevent/releases) | [2.0.21](https://github.com/bitcoin/bitcoin/pull/18676) | No | | | -| libnatpmp | [20150609](https://miniupnp.tuxfamily.org/files) | | No | | | +| 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 | | | -| Qt | [5.9.8](https://download.qt.io/official_releases/qt/) | [5.5.1](https://github.com/bitcoin/bitcoin/issues/13478) | No | | | +| Qt | [5.12.10](https://download.qt.io/official_releases/qt/) | [5.9.5](https://github.com/bitcoin/bitcoin/issues/20104) | No | | | | SQLite | [3.32.1](https://sqlite.org/download.html) | [3.7.17](https://github.com/bitcoin/bitcoin/pull/19077) | | | | | XCB | | | | | [Yes](https://github.com/bitcoin/bitcoin/blob/master/depends/packages/qt.mk) (Linux only) | | xkbcommon | | | | | [Yes](https://github.com/bitcoin/bitcoin/blob/master/depends/packages/qt.mk) (Linux only) | | ZeroMQ | [4.3.1](https://github.com/zeromq/libzmq/releases) | 4.0.0 | No | | | -| zlib | [1.2.11](https://zlib.net/) | | | | No | +| zlib | | | | | [Yes](https://github.com/bitcoin/bitcoin/blob/master/depends/packages/qt.mk) | Controlling dependencies ------------------------ @@ -43,3 +43,4 @@ Some dependencies are not needed in all configurations. The following are some f #### Other * librsvg is only needed if you need to run `make deploy` on (cross-compilation to) macOS. +* Not-Qt-bundled zlib is required to build the [DMG tool](../contrib/macdeploy/README.md#deterministic-macos-dmg-notes) from the libdmg-hfsplus project. diff --git a/doc/developer-notes.md b/doc/developer-notes.md index 8f2d7af089..0a78cdff20 100644 --- a/doc/developer-notes.md +++ b/doc/developer-notes.md @@ -595,11 +595,6 @@ Common misconceptions are clarified in those sections: - *Rationale*: This avoids memory and resource leaks, and ensures exception safety. -- Use `MakeUnique()` to construct objects owned by `unique_ptr`s. - - - *Rationale*: `MakeUnique` is concise and ensures exception safety in complex expressions. - `MakeUnique` is a temporary project local implementation of `std::make_unique` (C++14). - C++ data structures -------------------- diff --git a/doc/external-signer.md b/doc/external-signer.md index c91ea9bab7..2b3b378bee 100644 --- a/doc/external-signer.md +++ b/doc/external-signer.md @@ -4,7 +4,7 @@ Bitcoin Core can be launched with `-signer=<cmd>` where `<cmd>` is an external t ## Example usage -The following example is based on the [HWI](https://github.com/bitcoin-core/HWI) tool. Although this tool is hosted under the Bitcoin Core GitHub organization and maintained by Bitcoin Core developers, it should be used with caution. It is considered experimental and has far less review than Bitcoin Core itself. Be particularly careful when running tools such as these on a computer with private keys on it. +The following example is based on the [HWI](https://github.com/bitcoin-core/HWI) tool. Version 2.0 or newer is required. Although this tool is hosted under the Bitcoin Core GitHub organization and maintained by Bitcoin Core developers, it should be used with caution. It is considered experimental and has far less review than Bitcoin Core itself. Be particularly careful when running tools such as these on a computer with private keys on it. When using a hardware wallet, consult the manufacturer website for (alternative) software they recommend. As long as their software conforms to the standard below, it should be able to work with Bitcoin Core. diff --git a/doc/files.md b/doc/files.md index 545e8fc92c..a80adf30ea 100644 --- a/doc/files.md +++ b/doc/files.md @@ -64,6 +64,7 @@ Subdirectory | File(s) | Description `./` | `ip_asn.map` | IP addresses to Autonomous System Numbers (ASNs) mapping used for bucketing of the peers; path can be specified with the `-asmap` option `./` | `mempool.dat` | Dump of the mempool's transactions `./` | `onion_v3_private_key` | Cached Tor onion service private key for `-listenonion` option +`./` | `i2p_private_key` | Private key that corresponds to our I2P address. When `-i2psam=` is specified the contents of this file is used to identify ourselves for making outgoing connections to I2P peers and possibly accepting incoming ones. Automatically generated if it does not exist. `./` | `peers.dat` | Peer IP address database (custom format) `./` | `settings.json` | Read-write settings set through GUI or RPC interfaces, augmenting manual settings from [bitcoin.conf](bitcoin-conf.md). File is created automatically if read-write settings storage is not disabled with `-nosettings` option. Path can be specified with `-settings` option `./` | `.cookie` | Session RPC authentication cookie; if used, created at start and deleted on shutdown; can be specified by `-rpccookiefile` option diff --git a/doc/fuzzing.md b/doc/fuzzing.md index 87df2bbbb9..4d8825f4c2 100644 --- a/doc/fuzzing.md +++ b/doc/fuzzing.md @@ -108,33 +108,32 @@ Full configure that was tested on macOS Catalina with `brew` installed `llvm`: Read the [libFuzzer documentation](https://llvm.org/docs/LibFuzzer.html) for more information. This [libFuzzer tutorial](https://github.com/google/fuzzing/blob/master/tutorial/libFuzzerTutorial.md) might also be of interest. -# Fuzzing Bitcoin Core using american fuzzy lop (`afl-fuzz`) +# Fuzzing Bitcoin Core using afl++ ## Quickstart guide -To quickly get started fuzzing Bitcoin Core using [`afl-fuzz`](https://github.com/google/afl): +To quickly get started fuzzing Bitcoin Core using [afl++](https://github.com/AFLplusplus/AFLplusplus): ```sh $ git clone https://github.com/bitcoin/bitcoin $ cd bitcoin/ -$ git clone https://github.com/google/afl -$ make -C afl/ -$ make -C afl/llvm_mode/ +$ git clone https://github.com/AFLplusplus/AFLplusplus +$ make -C AFLplusplus/ source-only $ ./autogen.sh -# It is possible to compile with afl-gcc and afl-g++ instead of afl-clang. However, running afl-fuzz -# may require more memory via the -m flag. -$ CC=$(pwd)/afl/afl-clang-fast CXX=$(pwd)/afl/afl-clang-fast++ ./configure --enable-fuzz +# If afl-clang-lto is not available, see +# https://github.com/AFLplusplus/AFLplusplus#a-selecting-the-best-afl-compiler-for-instrumenting-the-target +$ CC=$(pwd)/AFLplusplus/afl-clang-lto CXX=$(pwd)/AFLplusplus/afl-clang-lto++ ./configure --enable-fuzz $ make # For macOS you may need to ignore x86 compilation checks when running "make". If so, # try compiling using: AFL_NO_X86=1 make $ mkdir -p inputs/ outputs/ $ echo A > inputs/thin-air-input -$ FUZZ=bech32 afl/afl-fuzz -i inputs/ -o outputs/ -- src/test/fuzz/fuzz +$ FUZZ=bech32 AFLplusplus/afl-fuzz -i inputs/ -o outputs/ -- src/test/fuzz/fuzz # You may have to change a few kernel parameters to test optimally - afl-fuzz # will print an error and suggestion if so. ``` -Read the [`afl-fuzz` documentation](https://github.com/google/afl) for more information. +Read the [afl++ documentation](https://github.com/AFLplusplus/AFLplusplus) for more information. # Fuzzing Bitcoin Core using Honggfuzz diff --git a/doc/release-notes-18077.md b/doc/release-notes-18077.md deleted file mode 100644 index 4034a4c19c..0000000000 --- a/doc/release-notes-18077.md +++ /dev/null @@ -1,10 +0,0 @@ -P2P and network changes ------------------------ - -- Added NAT-PMP port mapping support via [`libnatpmp`](https://miniupnp.tuxfamily.org/libnatpmp.html) - -Command-line options --------------------- - -- The `-natpmp` option has been added to use NAT-PMP to map the listening port. If both UPnP -and NAT-PMP are enabled, a successful allocation from UPnP prevails over one from NAT-PMP. diff --git a/doc/release-notes-19776.md b/doc/release-notes-19776.md deleted file mode 100644 index 5553c5a7bd..0000000000 --- a/doc/release-notes-19776.md +++ /dev/null @@ -1,9 +0,0 @@ -Updated RPCs ------------- - -- The `getpeerinfo` RPC returns two new boolean fields, `bip152_hb_to` and - `bip152_hb_from`, that respectively indicate whether we selected a peer to be - in compact blocks high-bandwidth mode or whether a peer selected us as a - compact blocks high-bandwidth peer. High-bandwidth peers send new block - announcements via a `cmpctblock` message rather than the usual inv/headers - announcements. See BIP 152 for more details. (#19776) diff --git a/doc/release-notes.md b/doc/release-notes.md index 0f248494c7..334dfa80a4 100644 --- a/doc/release-notes.md +++ b/doc/release-notes.md @@ -51,9 +51,7 @@ Core should also work on most other Unix-like systems but is not as frequently tested on them. It is not recommended to use Bitcoin Core on unsupported systems. -From Bitcoin Core 0.22.0 onwards, macOS versions earlier than 10.14 are no -longer supported. Additionally, Bitcoin Core does not yet change appearance -when macOS "dark mode" is activated. +From Bitcoin Core 22.0 onwards, macOS versions earlier than 10.14 are no longer supported. Notable changes =============== @@ -61,14 +59,51 @@ Notable changes P2P and network changes ----------------------- +- Added NAT-PMP port mapping support via + [`libnatpmp`](https://miniupnp.tuxfamily.org/libnatpmp.html). (#18077) + Updated RPCs ------------ + +- Due to [BIP 350](https://github.com/bitcoin/bips/blob/master/bip-0350.mediawiki) + being implemented, behavior for all RPCs that accept addresses is changed when + a native witness version 1 (or higher) is passed. These now require a Bech32m + encoding instead of a Bech32 one, and Bech32m encoding will be used for such + addresses in RPC output as well. No version 1 addresses should be created + for mainnet until consensus rules are adopted that give them meaning + (e.g. through [BIP 341](https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki)). + Once that happens, Bech32m is expected to be used for them, so this shouldn't + affect any production systems, but may be observed on other networks where such + addresses already have meaning (like signet). (#20861) + +- The `getpeerinfo` RPC returns two new boolean fields, `bip152_hb_to` and + `bip152_hb_from`, that respectively indicate whether we selected a peer to be + in compact blocks high-bandwidth mode or whether a peer selected us as a + compact blocks high-bandwidth peer. High-bandwidth peers send new block + announcements via a `cmpctblock` message rather than the usual inv/headers + announcements. See BIP 152 for more details. (#19776) + - `getpeerinfo` no longer returns the following fields: `addnode`, `banscore`, and `whitelisted`, which were previously deprecated in 0.21. Instead of `addnode`, the `connection_type` field returns manual. Instead of `whitelisted`, the `permissions` field indicates if the peer has special privileges. The `banscore` field has simply been removed. (#20755) +- The following RPCs: `gettxout`, `getrawtransaction`, `decoderawtransaction`, + `decodescript`, `gettransaction`, and REST endpoints: `/rest/tx`, + `/rest/getutxos`, `/rest/block` deprecated the following fields (which are no + longer returned in the responses by default): `addresses`, `reqSigs`. + The `-deprecatedrpc=addresses` flag must be passed for these fields to be + included in the RPC response. This flag/option will be available only for this major release, after which + the deprecation will be removed entirely. Note that these fields are attributes of + the `scriptPubKey` object returned in the RPC response. However, in the response + of `decodescript` these fields are top-level attributes, and included again as attributes + of the `scriptPubKey` object. (#20286) + +- When creating a hex-encoded bitcoin transaction using the `bitcoin-tx` utility + with the `-json` option set, the following fields: `addresses`, `reqSigs` are no longer + returned in the tx output of the response. (#20286) + Changes to Wallet or GUI related RPCs can be found in the GUI or Wallet section below. New RPCs @@ -80,6 +115,10 @@ Build System New settings ------------ +- The `-natpmp` option has been added to use NAT-PMP to map the listening port. + If both UPnP and NAT-PMP are enabled, a successful allocation from UPnP + prevails over one from NAT-PMP. (#18077) + Updated settings ---------------- @@ -97,6 +136,9 @@ Wallet The RPC returns public versions of all imported descriptors, including their timestamp and flags. For ranged descriptors, it also returns the range boundaries and the next index to generate addresses from. (#20226) +- The `bumpfee` RPC is not available with wallets that have private keys + disabled. `psbtbumpfee` can be used instead. (#20891) + GUI changes ----------- @@ -106,6 +148,19 @@ Low-level changes RPC --- +- The RPC server can process a limited number of simultaneous RPC requests. + Previously, if this limit was exceeded, the RPC server would respond with + [status code 500 (`HTTP_INTERNAL_SERVER_ERROR`)](https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#5xx_server_errors). + Now it returns status code 503 (`HTTP_SERVICE_UNAVAILABLE`). (#18335) + +- Error codes have been updated to be more accurate for the following error cases (#18466): + - `signmessage` now returns RPC_INVALID_ADDRESS_OR_KEY (-5) if the + passed address is invalid. Previously returned RPC_TYPE_ERROR (-3). + - `verifymessage` now returns RPC_INVALID_ADDRESS_OR_KEY (-5) if the + passed address is invalid. Previously returned RPC_TYPE_ERROR (-3). + - `verifymessage` now returns RPC_TYPE_ERROR (-3) if the passed signature + is malformed. Previously returned RPC_INVALID_ADDRESS_OR_KEY (-5). + Tests ----- diff --git a/share/examples/bitcoin.conf b/share/examples/bitcoin.conf index 90a592cc63..5b7fc776a4 100644 --- a/share/examples/bitcoin.conf +++ b/share/examples/bitcoin.conf @@ -4,13 +4,16 @@ # Network-related settings: -# Note that if you use testnet or regtest, particularly with the options +# Note that if you use testnet, signet or regtest, particularly with the options # addnode, connect, port, bind, rpcport, rpcbind or wallet, you will also # want to read "[Sections]" further down. -# Run on the test network instead of the real bitcoin network. +# Run on the testnet network #testnet=0 +# Run on a signet network +#signet=0 + # Run a regression test network #regtest=0 @@ -57,7 +60,7 @@ # Listening mode, enabled by default except when 'connect' is being used #listen=1 -# Port on which to listen for connections (default: 8333, testnet: 18333, regtest: 18444) +# Port on which to listen for connections (default: 8333, testnet: 18333, signet: 38333, regtest: 18444) #port= # Maximum number of inbound+outbound connections. @@ -155,7 +158,7 @@ #minimizetotray=1 # [Sections] -# Most options apply to mainnet, testnet and regtest. +# Most options apply to mainnet, testnet, signet and regtest. # If you want to confine an option to just one network, you should add it in the # relevant section below. # EXCEPTIONS: The options addnode, connect, port, bind, rpcport, rpcbind and wallet @@ -167,5 +170,8 @@ # Options only for testnet [test] +# Options only for signet +[signet] + # Options only for regtest [regtest] diff --git a/share/qt/Info.plist.in b/share/qt/Info.plist.in index 207d60aca3..da10dbb3be 100644 --- a/share/qt/Info.plist.in +++ b/share/qt/Info.plist.in @@ -60,9 +60,6 @@ <key>NSHighResolutionCapable</key> <string>True</string> - <key>NSRequiresAquaSystemAppearance</key> - <string>True</string> - <key>LSApplicationCategoryType</key> <string>public.app-category.finance</string> </dict> diff --git a/share/setup.nsi.in b/share/setup.nsi.in index 681f243d04..85ae7c57af 100644 --- a/share/setup.nsi.in +++ b/share/setup.nsi.in @@ -3,6 +3,7 @@ Name "@PACKAGE_NAME@ (64-bit)" RequestExecutionLevel highest SetCompressor /SOLID lzma SetDateSave off +Unicode true # Uncomment these lines when investigating reproducibility errors #SetCompress off diff --git a/src/Makefile.am b/src/Makefile.am index eae226b1d4..4e09c86ebd 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -148,6 +148,7 @@ BITCOIN_CORE_H = \ fs.h \ httprpc.h \ httpserver.h \ + i2p.h \ index/base.h \ index/blockfilterindex.h \ index/disktxpos.h \ @@ -181,7 +182,6 @@ BITCOIN_CORE_H = \ node/ui_interface.h \ node/utxo_snapshot.h \ noui.h \ - optional.h \ outputtype.h \ policy/feerate.h \ policy/fees.h \ @@ -224,8 +224,9 @@ BITCOIN_CORE_H = \ timedata.h \ torcontrol.h \ txdb.h \ - txrequest.h \ txmempool.h \ + txorphanage.h \ + txrequest.h \ undo.h \ util/asmap.h \ util/bip32.h \ @@ -238,11 +239,10 @@ BITCOIN_CORE_H = \ util/golombrice.h \ util/hasher.h \ util/macros.h \ - util/memory.h \ util/message.h \ util/moneystr.h \ util/rbf.h \ - util/ref.h \ + util/readwritefile.h \ util/settings.h \ util/sock.h \ util/spanparsing.h \ @@ -250,6 +250,7 @@ BITCOIN_CORE_H = \ util/system.h \ util/threadnames.h \ util/time.h \ + util/tokenpipe.h \ util/trace.h \ util/translation.h \ util/ui_change_type.h \ @@ -300,7 +301,7 @@ libbitcoin_util_a-clientversion.$(OBJEXT): obj/build.h # Contains code accessing mempool and chain state that is meant to be separated # from wallet and gui code (see node/README.md). Shared code should go in # libbitcoin_common or libbitcoin_util libraries, instead. -libbitcoin_server_a_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) $(MINIUPNPC_CPPFLAGS) $(EVENT_CFLAGS) $(EVENT_PTHREADS_CFLAGS) +libbitcoin_server_a_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) $(MINIUPNPC_CPPFLAGS) $(NATPMP_CPPFLAGS) $(EVENT_CFLAGS) $(EVENT_PTHREADS_CFLAGS) libbitcoin_server_a_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) libbitcoin_server_a_SOURCES = \ addrdb.cpp \ @@ -314,6 +315,7 @@ libbitcoin_server_a_SOURCES = \ flatfile.cpp \ httprpc.cpp \ httpserver.cpp \ + i2p.cpp \ index/base.cpp \ index/blockfilterindex.cpp \ index/txindex.cpp \ @@ -347,8 +349,9 @@ libbitcoin_server_a_SOURCES = \ timedata.cpp \ torcontrol.cpp \ txdb.cpp \ - txrequest.cpp \ txmempool.cpp \ + txorphanage.cpp \ + txrequest.cpp \ validation.cpp \ validationinterface.cpp \ versionbits.cpp \ @@ -548,7 +551,6 @@ libbitcoin_util_a_SOURCES = \ support/lockedpool.cpp \ chainparamsbase.cpp \ clientversion.cpp \ - compat/glibc_sanity.cpp \ compat/glibcxx_sanity.cpp \ compat/strnlen.cpp \ fs.cpp \ @@ -572,12 +574,14 @@ libbitcoin_util_a_SOURCES = \ util/message.cpp \ util/moneystr.cpp \ util/rbf.cpp \ + util/readwritefile.cpp \ util/settings.cpp \ util/threadnames.cpp \ util/spanparsing.cpp \ util/strencodings.cpp \ util/string.cpp \ util/time.cpp \ + util/tokenpipe.cpp \ $(BITCOIN_CORE_H) if USE_LIBEVENT 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.qttest.include b/src/Makefile.qttest.include index a6a857d952..91a5e9fd9b 100644 --- a/src/Makefile.qttest.include +++ b/src/Makefile.qttest.include @@ -7,7 +7,6 @@ TESTS += qt/test/test_bitcoin-qt TEST_QT_MOC_CPP = \ qt/test/moc_apptests.cpp \ - qt/test/moc_compattests.cpp \ qt/test/moc_rpcnestedtests.cpp \ qt/test/moc_uritests.cpp @@ -20,7 +19,6 @@ endif # ENABLE_WALLET TEST_QT_H = \ qt/test/addressbooktests.h \ qt/test/apptests.h \ - qt/test/compattests.h \ qt/test/rpcnestedtests.h \ qt/test/uritests.h \ qt/test/util.h \ @@ -31,7 +29,6 @@ qt_test_test_bitcoin_qt_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) $(BITCOIN_ qt_test_test_bitcoin_qt_SOURCES = \ qt/test/apptests.cpp \ - qt/test/compattests.cpp \ qt/test/rpcnestedtests.cpp \ qt/test/test_main.cpp \ qt/test/uritests.cpp \ diff --git a/src/Makefile.test.include b/src/Makefile.test.include index e817bb2ee2..570f011f7a 100644 --- a/src/Makefile.test.include +++ b/src/Makefile.test.include @@ -90,6 +90,7 @@ BITCOIN_TESTS =\ test/fs_tests.cpp \ test/getarg_tests.cpp \ test/hash_tests.cpp \ + test/i2p_tests.cpp \ test/interfaces_tests.cpp \ test/key_io_tests.cpp \ test/key_tests.cpp \ @@ -101,6 +102,7 @@ BITCOIN_TESTS =\ test/merkleblock_tests.cpp \ test/miner_tests.cpp \ test/multisig_tests.cpp \ + test/net_peer_eviction_tests.cpp \ test/net_tests.cpp \ test/netbase_tests.cpp \ test/pmt_tests.cpp \ @@ -110,7 +112,6 @@ BITCOIN_TESTS =\ test/prevector_tests.cpp \ test/raii_event_tests.cpp \ test/random_tests.cpp \ - test/ref_tests.cpp \ test/reverselock_tests.cpp \ test/rpc_tests.cpp \ test/sanity_tests.cpp \ @@ -239,6 +240,7 @@ test_fuzz_fuzz_SOURCES = \ test/fuzz/golomb_rice.cpp \ test/fuzz/hex.cpp \ test/fuzz/http_request.cpp \ + test/fuzz/i2p.cpp \ test/fuzz/integer.cpp \ test/fuzz/key.cpp \ test/fuzz/key_io.cpp \ @@ -252,6 +254,7 @@ test_fuzz_fuzz_SOURCES = \ test/fuzz/net.cpp \ test/fuzz/net_permissions.cpp \ test/fuzz/netaddress.cpp \ + test/fuzz/netbase_dns_lookup.cpp \ test/fuzz/node_eviction.cpp \ test/fuzz/p2p_transport_deserializer.cpp \ test/fuzz/parse_hd_keypath.cpp \ @@ -285,16 +288,21 @@ test_fuzz_fuzz_SOURCES = \ test/fuzz/secp256k1_ecdsa_signature_parse_der_lax.cpp \ test/fuzz/signature_checker.cpp \ test/fuzz/signet.cpp \ + test/fuzz/socks5.cpp \ test/fuzz/span.cpp \ test/fuzz/spanparsing.cpp \ test/fuzz/string.cpp \ test/fuzz/strprintf.cpp \ test/fuzz/system.cpp \ test/fuzz/timedata.cpp \ + test/fuzz/torcontrol.cpp \ test/fuzz/transaction.cpp \ test/fuzz/tx_in.cpp \ test/fuzz/tx_out.cpp \ - test/fuzz/txrequest.cpp + test/fuzz/tx_pool.cpp \ + test/fuzz/txrequest.cpp \ + test/fuzz/validation_load_mempool.cpp \ + test/fuzz/versionbits.cpp endif # ENABLE_FUZZ_BINARY nodist_test_test_bitcoin_SOURCES = $(GENERATED_TEST_FILES) diff --git a/src/Makefile.test_fuzz.include b/src/Makefile.test_fuzz.include index 75fe68fcd1..2d772f2fca 100644 --- a/src/Makefile.test_fuzz.include +++ b/src/Makefile.test_fuzz.include @@ -12,7 +12,7 @@ TEST_FUZZ_H = \ test/fuzz/FuzzedDataProvider.h \ test/fuzz/util.h -libtest_fuzz_a_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) $(MINIUPNPC_CPPFLAGS) $(EVENT_CFLAGS) $(EVENT_PTHREADS_CFLAGS) +libtest_fuzz_a_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) $(MINIUPNPC_CPPFLAGS) $(NATPMP_CPPFLAGS) $(EVENT_CFLAGS) $(EVENT_PTHREADS_CFLAGS) libtest_fuzz_a_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) libtest_fuzz_a_SOURCES = \ test/fuzz/fuzz.cpp \ diff --git a/src/Makefile.test_util.include b/src/Makefile.test_util.include index 1abfb667a0..85e50ebf70 100644 --- a/src/Makefile.test_util.include +++ b/src/Makefile.test_util.include @@ -19,13 +19,14 @@ TEST_UTIL_H = \ test/util/validation.h \ test/util/wallet.h -libtest_util_a_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) $(MINIUPNPC_CPPFLAGS) $(EVENT_CFLAGS) $(EVENT_PTHREADS_CFLAGS) +libtest_util_a_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) $(MINIUPNPC_CPPFLAGS) $(NATPMP_CPPFLAGS) $(EVENT_CFLAGS) $(EVENT_PTHREADS_CFLAGS) libtest_util_a_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) libtest_util_a_SOURCES = \ test/util/blockfilter.cpp \ test/util/logging.cpp \ test/util/mining.cpp \ test/util/net.cpp \ + test/util/script.cpp \ test/util/setup_common.cpp \ test/util/str.cpp \ test/util/transaction_utils.cpp \ diff --git a/src/bech32.cpp b/src/bech32.cpp index 1e0471f110..288b14e023 100644 --- a/src/bech32.cpp +++ b/src/bech32.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017 Pieter Wuille +// Copyright (c) 2017, 2021 Pieter Wuille // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. @@ -7,15 +7,18 @@ #include <assert.h> +namespace bech32 +{ + namespace { typedef std::vector<uint8_t> data; -/** The Bech32 character set for encoding. */ +/** The Bech32 and Bech32m character set for encoding. */ const char* CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l"; -/** The Bech32 character set for decoding. */ +/** The Bech32 and Bech32m character set for decoding. */ const int8_t CHARSET_REV[128] = { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, @@ -27,6 +30,12 @@ const int8_t CHARSET_REV[128] = { 1, 0, 3, 16, 11, 28, 12, 14, 6, 4, 2, -1, -1, -1, -1, -1 }; +/* Determine the final constant to use for the specified encoding. */ +uint32_t EncodingConstant(Encoding encoding) { + assert(encoding == Encoding::BECH32 || encoding == Encoding::BECH32M); + return encoding == Encoding::BECH32 ? 1 : 0x2bc830a3; +} + /** This function will compute what 6 5-bit values to XOR into the last 6 input values, in order to * make the checksum 0. These 6 values are packed together in a single 30-bit integer. The higher * bits correspond to earlier values. */ @@ -111,21 +120,24 @@ data ExpandHRP(const std::string& hrp) } /** Verify a checksum. */ -bool VerifyChecksum(const std::string& hrp, const data& values) +Encoding VerifyChecksum(const std::string& hrp, const data& values) { // PolyMod computes what value to xor into the final values to make the checksum 0. However, // if we required that the checksum was 0, it would be the case that appending a 0 to a valid // list of values would result in a new valid list. For that reason, Bech32 requires the - // resulting checksum to be 1 instead. - return PolyMod(Cat(ExpandHRP(hrp), values)) == 1; + // resulting checksum to be 1 instead. In Bech32m, this constant was amended. + const uint32_t check = PolyMod(Cat(ExpandHRP(hrp), values)); + if (check == EncodingConstant(Encoding::BECH32)) return Encoding::BECH32; + if (check == EncodingConstant(Encoding::BECH32M)) return Encoding::BECH32M; + return Encoding::INVALID; } /** Create a checksum. */ -data CreateChecksum(const std::string& hrp, const data& values) +data CreateChecksum(Encoding encoding, const std::string& hrp, const data& values) { data enc = Cat(ExpandHRP(hrp), values); enc.resize(enc.size() + 6); // Append 6 zeroes - uint32_t mod = PolyMod(enc) ^ 1; // Determine what to XOR into those 6 zeroes. + uint32_t mod = PolyMod(enc) ^ EncodingConstant(encoding); // Determine what to XOR into those 6 zeroes. data ret(6); for (size_t i = 0; i < 6; ++i) { // Convert the 5-bit groups in mod to checksum values. @@ -136,16 +148,13 @@ data CreateChecksum(const std::string& hrp, const data& values) } // namespace -namespace bech32 -{ - -/** Encode a Bech32 string. */ -std::string Encode(const std::string& hrp, const data& values) { - // First ensure that the HRP is all lowercase. BIP-173 requires an encoder - // to return a lowercase Bech32 string, but if given an uppercase HRP, the +/** Encode a Bech32 or Bech32m string. */ +std::string Encode(Encoding encoding, const std::string& hrp, const data& values) { + // First ensure that the HRP is all lowercase. BIP-173 and BIP350 require an encoder + // to return a lowercase Bech32/Bech32m string, but if given an uppercase HRP, the // result will always be invalid. for (const char& c : hrp) assert(c < 'A' || c > 'Z'); - data checksum = CreateChecksum(hrp, values); + data checksum = CreateChecksum(encoding, hrp, values); data combined = Cat(values, checksum); std::string ret = hrp + '1'; ret.reserve(ret.size() + combined.size()); @@ -155,8 +164,8 @@ std::string Encode(const std::string& hrp, const data& values) { return ret; } -/** Decode a Bech32 string. */ -std::pair<std::string, data> Decode(const std::string& str) { +/** Decode a Bech32 or Bech32m string. */ +DecodeResult Decode(const std::string& str) { bool lower = false, upper = false; for (size_t i = 0; i < str.size(); ++i) { unsigned char c = str[i]; @@ -183,10 +192,9 @@ std::pair<std::string, data> Decode(const std::string& str) { for (size_t i = 0; i < pos; ++i) { hrp += LowerCase(str[i]); } - if (!VerifyChecksum(hrp, values)) { - return {}; - } - return {hrp, data(values.begin(), values.end() - 6)}; + Encoding result = VerifyChecksum(hrp, values); + if (result == Encoding::INVALID) return {}; + return {result, std::move(hrp), data(values.begin(), values.end() - 6)}; } } // namespace bech32 diff --git a/src/bech32.h b/src/bech32.h index fb39cd352b..e9450ccc2b 100644 --- a/src/bech32.h +++ b/src/bech32.h @@ -1,13 +1,14 @@ -// Copyright (c) 2017 Pieter Wuille +// Copyright (c) 2017, 2021 Pieter Wuille // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -// Bech32 is a string encoding format used in newer address types. -// The output consists of a human-readable part (alphanumeric), a -// separator character (1), and a base32 data section, the last -// 6 characters of which are a checksum. +// Bech32 and Bech32m are string encoding formats used in newer +// address types. The outputs consist of a human-readable part +// (alphanumeric), a separator character (1), and a base32 data +// section, the last 6 characters of which are a checksum. The +// module is namespaced under bech32 for historical reasons. // -// For more information, see BIP 173. +// For more information, see BIP 173 and BIP 350. #ifndef BITCOIN_BECH32_H #define BITCOIN_BECH32_H @@ -19,11 +20,29 @@ namespace bech32 { -/** Encode a Bech32 string. If hrp contains uppercase characters, this will cause an assertion error. */ -std::string Encode(const std::string& hrp, const std::vector<uint8_t>& values); +enum class Encoding { + INVALID, //!< Failed decoding -/** Decode a Bech32 string. Returns (hrp, data). Empty hrp means failure. */ -std::pair<std::string, std::vector<uint8_t>> Decode(const std::string& str); + BECH32, //!< Bech32 encoding as defined in BIP173 + BECH32M, //!< Bech32m encoding as defined in BIP350 +}; + +/** Encode a Bech32 or Bech32m string. If hrp contains uppercase characters, this will cause an + * assertion error. Encoding must be one of BECH32 or BECH32M. */ +std::string Encode(Encoding encoding, const std::string& hrp, const std::vector<uint8_t>& values); + +struct DecodeResult +{ + Encoding encoding; //!< What encoding was detected in the result; Encoding::INVALID if failed. + std::string hrp; //!< The human readable part + std::vector<uint8_t> data; //!< The payload (excluding checksum) + + DecodeResult() : encoding(Encoding::INVALID) {} + DecodeResult(Encoding enc, std::string&& h, std::vector<uint8_t>&& d) : encoding(enc), hrp(std::move(h)), data(std::move(d)) {} +}; + +/** Decode a Bech32 or Bech32m string. */ +DecodeResult Decode(const std::string& str); } // namespace bech32 diff --git a/src/bench/bech32.cpp b/src/bench/bech32.cpp index c74d8d51b3..8e10862a37 100644 --- a/src/bench/bech32.cpp +++ b/src/bench/bech32.cpp @@ -19,7 +19,7 @@ static void Bech32Encode(benchmark::Bench& bench) tmp.reserve(1 + 32 * 8 / 5); ConvertBits<8, 5, true>([&](unsigned char c) { tmp.push_back(c); }, v.begin(), v.end()); bench.batch(v.size()).unit("byte").run([&] { - bech32::Encode("bc", tmp); + bech32::Encode(bech32::Encoding::BECH32, "bc", tmp); }); } diff --git a/src/bench/block_assemble.cpp b/src/bench/block_assemble.cpp index 8f656c44d9..67ab02a5b3 100644 --- a/src/bench/block_assemble.cpp +++ b/src/bench/block_assemble.cpp @@ -16,13 +16,7 @@ static void AssembleBlock(benchmark::Bench& bench) { - TestingSetup test_setup{ - CBaseChainParams::REGTEST, - /* extra_args */ { - "-nodebuglogfile", - "-nodebug", - }, - }; + const auto test_setup = MakeNoLogFileContext<const TestingSetup>(); const std::vector<unsigned char> op_true{OP_TRUE}; CScriptWitness witness; @@ -38,7 +32,7 @@ static void AssembleBlock(benchmark::Bench& bench) std::array<CTransactionRef, NUM_BLOCKS - COINBASE_MATURITY + 1> txs; for (size_t b{0}; b < NUM_BLOCKS; ++b) { CMutableTransaction tx; - tx.vin.push_back(MineBlock(test_setup.m_node, SCRIPT_PUB)); + tx.vin.push_back(MineBlock(test_setup->m_node, SCRIPT_PUB)); tx.vin.back().scriptWitness = witness; tx.vout.emplace_back(1337, SCRIPT_PUB); if (NUM_BLOCKS - b >= COINBASE_MATURITY) @@ -48,13 +42,13 @@ static void AssembleBlock(benchmark::Bench& bench) LOCK(::cs_main); // Required for ::AcceptToMemoryPool. for (const auto& txr : txs) { - const MempoolAcceptResult res = ::AcceptToMemoryPool(::ChainstateActive(), *test_setup.m_node.mempool, txr, false /* bypass_limits */); + const MempoolAcceptResult res = ::AcceptToMemoryPool(::ChainstateActive(), *test_setup->m_node.mempool, txr, false /* bypass_limits */); assert(res.m_result_type == MempoolAcceptResult::ResultType::VALID); } } bench.run([&] { - PrepareBlock(test_setup.m_node, SCRIPT_PUB); + PrepareBlock(test_setup->m_node, SCRIPT_PUB); }); } diff --git a/src/bench/coin_selection.cpp b/src/bench/coin_selection.cpp index 3abfbfd784..80acdec044 100644 --- a/src/bench/coin_selection.cpp +++ b/src/bench/coin_selection.cpp @@ -17,7 +17,7 @@ static void addCoin(const CAmount& nValue, const CWallet& wallet, std::vector<st tx.nLockTime = nextLockTime++; // so all transactions get different hashes tx.vout.resize(1); tx.vout[0].nValue = nValue; - wtxs.push_back(MakeUnique<CWalletTx>(&wallet, MakeTransactionRef(std::move(tx)))); + wtxs.push_back(std::make_unique<CWalletTx>(&wallet, MakeTransactionRef(std::move(tx)))); } // Simple benchmark for wallet coin selection. Note that it maybe be necessary @@ -49,7 +49,10 @@ static void CoinSelection(benchmark::Bench& bench) } const CoinEligibilityFilter filter_standard(1, 6, 0); - const CoinSelectionParams coin_selection_params(true, 34, 148, CFeeRate(0), 0, false); + const CoinSelectionParams coin_selection_params(/* use_bnb= */ true, /* change_output_size= */ 34, + /* change_spend_size= */ 148, /* effective_feerate= */ CFeeRate(0), + /* long_term_feerate= */ CFeeRate(0), /* discard_feerate= */ CFeeRate(0), + /* tx_no_inputs_size= */ 0, /* avoid_partial= */ false); bench.run([&] { std::set<CInputCoin> setCoinsRet; CAmount nValueRet; @@ -73,7 +76,7 @@ static void add_coin(const CAmount& nValue, int nInput, std::vector<OutputGroup> CMutableTransaction tx; tx.vout.resize(nInput + 1); tx.vout[nInput].nValue = nValue; - std::unique_ptr<CWalletTx> wtx = MakeUnique<CWalletTx>(&testWallet, MakeTransactionRef(std::move(tx))); + std::unique_ptr<CWalletTx> wtx = std::make_unique<CWalletTx>(&testWallet, MakeTransactionRef(std::move(tx))); set.emplace_back(); set.back().Insert(COutput(wtx.get(), nInput, 0, true, true, true).GetInputCoin(), 0, true, 0, 0, false); wtxn.emplace_back(std::move(wtx)); diff --git a/src/bench/duplicate_inputs.cpp b/src/bench/duplicate_inputs.cpp index 5745e4276c..25d1a2b56c 100644 --- a/src/bench/duplicate_inputs.cpp +++ b/src/bench/duplicate_inputs.cpp @@ -14,13 +14,7 @@ static void DuplicateInputs(benchmark::Bench& bench) { - TestingSetup test_setup{ - CBaseChainParams::REGTEST, - /* extra_args */ { - "-nodebuglogfile", - "-nodebug", - }, - }; + const auto testing_setup = MakeNoLogFileContext<const TestingSetup>(); const CScript SCRIPT_PUB{CScript(OP_TRUE)}; diff --git a/src/bench/mempool_eviction.cpp b/src/bench/mempool_eviction.cpp index db9a5661fd..4f49fba7b7 100644 --- a/src/bench/mempool_eviction.cpp +++ b/src/bench/mempool_eviction.cpp @@ -25,13 +25,7 @@ static void AddTx(const CTransactionRef& tx, const CAmount& nFee, CTxMemPool& po // unique transactions for a more meaningful performance measurement. static void MempoolEviction(benchmark::Bench& bench) { - TestingSetup test_setup{ - CBaseChainParams::REGTEST, - /* extra_args */ { - "-nodebuglogfile", - "-nodebug", - }, - }; + const auto testing_setup = MakeNoLogFileContext<const TestingSetup>(); CMutableTransaction tx1 = CMutableTransaction(); tx1.vin.resize(1); diff --git a/src/bench/mempool_stress.cpp b/src/bench/mempool_stress.cpp index 9b862b735c..f28768efc8 100644 --- a/src/bench/mempool_stress.cpp +++ b/src/bench/mempool_stress.cpp @@ -79,7 +79,7 @@ static void ComplexMemPool(benchmark::Bench& bench) ordered_coins.emplace_back(MakeTransactionRef(tx)); available_coins.emplace_back(ordered_coins.back(), tx_counter++); } - TestingSetup test_setup; + const auto testing_setup = MakeNoLogFileContext<const TestingSetup>(CBaseChainParams::MAIN); CTxMemPool pool; LOCK2(cs_main, pool.cs); bench.run([&]() NO_THREAD_SAFETY_ANALYSIS { diff --git a/src/bench/rpc_blockchain.cpp b/src/bench/rpc_blockchain.cpp index 45ed9f60dc..c8886a4c23 100644 --- a/src/bench/rpc_blockchain.cpp +++ b/src/bench/rpc_blockchain.cpp @@ -12,25 +12,49 @@ #include <univalue.h> -static void BlockToJsonVerbose(benchmark::Bench& bench) -{ - TestingSetup test_setup{}; +namespace { + +struct TestBlockAndIndex { + const std::unique_ptr<const TestingSetup> testing_setup{MakeNoLogFileContext<const TestingSetup>(CBaseChainParams::MAIN)}; + CBlock block{}; + uint256 blockHash{}; + CBlockIndex blockindex{}; - CDataStream stream(benchmark::data::block413567, SER_NETWORK, PROTOCOL_VERSION); - char a = '\0'; - stream.write(&a, 1); // Prevent compaction + TestBlockAndIndex() + { + CDataStream stream(benchmark::data::block413567, SER_NETWORK, PROTOCOL_VERSION); + char a = '\0'; + stream.write(&a, 1); // Prevent compaction - CBlock block; - stream >> block; + stream >> block; - CBlockIndex blockindex; - const uint256 blockHash = block.GetHash(); - blockindex.phashBlock = &blockHash; - blockindex.nBits = 403014710; + blockHash = block.GetHash(); + blockindex.phashBlock = &blockHash; + blockindex.nBits = 403014710; + } +}; +} // namespace + +static void BlockToJsonVerbose(benchmark::Bench& bench) +{ + TestBlockAndIndex data; bench.run([&] { - (void)blockToJSON(block, &blockindex, &blockindex, /*verbose*/ true); + auto univalue = blockToJSON(data.block, &data.blockindex, &data.blockindex, /*verbose*/ true); + ankerl::nanobench::doNotOptimizeAway(univalue); }); } BENCHMARK(BlockToJsonVerbose); + +static void BlockToJsonVerboseWrite(benchmark::Bench& bench) +{ + TestBlockAndIndex data; + auto univalue = blockToJSON(data.block, &data.blockindex, &data.blockindex, /*verbose*/ true); + bench.run([&] { + auto str = univalue.write(); + ankerl::nanobench::doNotOptimizeAway(str); + }); +} + +BENCHMARK(BlockToJsonVerboseWrite); diff --git a/src/bench/wallet_balance.cpp b/src/bench/wallet_balance.cpp index b385cec085..d7cc167885 100644 --- a/src/bench/wallet_balance.cpp +++ b/src/bench/wallet_balance.cpp @@ -5,39 +5,34 @@ #include <bench/bench.h> #include <interfaces/chain.h> #include <node/context.h> -#include <optional.h> #include <test/util/mining.h> #include <test/util/setup_common.h> #include <test/util/wallet.h> #include <validationinterface.h> #include <wallet/wallet.h> +#include <optional> + static void WalletBalance(benchmark::Bench& bench, const bool set_dirty, const bool add_watchonly, const bool add_mine) { - TestingSetup test_setup{ - CBaseChainParams::REGTEST, - /* extra_args */ { - "-nodebuglogfile", - "-nodebug", - }, - }; + const auto test_setup = MakeNoLogFileContext<const TestingSetup>(); const auto& ADDRESS_WATCHONLY = ADDRESS_BCRT1_UNSPENDABLE; - CWallet wallet{test_setup.m_node.chain.get(), "", CreateMockWalletDatabase()}; + CWallet wallet{test_setup->m_node.chain.get(), "", CreateMockWalletDatabase()}; { wallet.SetupLegacyScriptPubKeyMan(); bool first_run; if (wallet.LoadWallet(first_run) != DBErrors::LOAD_OK) assert(false); } - auto handler = test_setup.m_node.chain->handleNotifications({&wallet, [](CWallet*) {}}); + auto handler = test_setup->m_node.chain->handleNotifications({&wallet, [](CWallet*) {}}); - const Optional<std::string> address_mine{add_mine ? Optional<std::string>{getnewaddress(wallet)} : nullopt}; + const std::optional<std::string> address_mine{add_mine ? std::optional<std::string>{getnewaddress(wallet)} : std::nullopt}; if (add_watchonly) importaddress(wallet, ADDRESS_WATCHONLY); for (int i = 0; i < 100; ++i) { - generatetoaddress(test_setup.m_node, address_mine.value_or(ADDRESS_WATCHONLY)); - generatetoaddress(test_setup.m_node, ADDRESS_WATCHONLY); + generatetoaddress(test_setup->m_node, address_mine.value_or(ADDRESS_WATCHONLY)); + generatetoaddress(test_setup->m_node, ADDRESS_WATCHONLY); } SyncWithValidationInterfaceQueue(); diff --git a/src/bitcoin-cli.cpp b/src/bitcoin-cli.cpp index 0830cb54cb..4f3d7a4ffe 100644 --- a/src/bitcoin-cli.cpp +++ b/src/bitcoin-cli.cpp @@ -9,7 +9,6 @@ #include <chainparamsbase.h> #include <clientversion.h> -#include <optional.h> #include <rpc/client.h> #include <rpc/mining.h> #include <rpc/protocol.h> @@ -24,6 +23,7 @@ #include <cmath> #include <functional> #include <memory> +#include <optional> #include <stdio.h> #include <string> #include <tuple> @@ -300,7 +300,6 @@ class NetinfoRequestHandler : public BaseRequestHandler { private: static constexpr int8_t UNKNOWN_NETWORK{-1}; - static constexpr int8_t NET_I2P{3}; // pos of "i2p" in m_networks static constexpr uint8_t m_networks_size{4}; static constexpr uint8_t MAX_DETAIL_LEVEL{4}; const std::array<std::string, m_networks_size> m_networks{{"ipv4", "ipv6", "onion", "i2p"}}; @@ -314,13 +313,11 @@ private: } return UNKNOWN_NETWORK; } - uint8_t m_details_level{0}; //!< Optional user-supplied arg to set dashboard details level - bool m_is_help_requested{false}; //!< Optional user-supplied arg to print help documentation + uint8_t m_details_level{0}; //!< Optional user-supplied arg to set dashboard details level bool DetailsRequested() const { return m_details_level > 0 && m_details_level < 5; } bool IsAddressSelected() const { return m_details_level == 2 || m_details_level == 4; } bool IsVersionSelected() const { return m_details_level == 3 || m_details_level == 4; } bool m_is_asmap_on{false}; - bool m_is_i2p_on{false}; size_t m_max_addr_length{0}; size_t m_max_age_length{3}; size_t m_max_id_length{2}; @@ -367,68 +364,6 @@ private: if (conn_type == "addr-fetch") return "addr"; return ""; } - const UniValue NetinfoHelp() - { - return std::string{ - "-netinfo level|\"help\" \n\n" - "Returns a network peer connections dashboard with information from the remote server.\n" - "Under the hood, -netinfo fetches the data by calling getpeerinfo and getnetworkinfo.\n" - "An optional integer argument from 0 to 4 can be passed for different peers listings.\n" - "Pass \"help\" to see this detailed help documentation.\n" - "If more than one argument is passed, only the first one is read and parsed.\n" - "Suggestion: use with the Linux watch(1) command for a live dashboard; see example below.\n\n" - "Arguments:\n" - "1. level (integer 0-4, optional) Specify the info level of the peers dashboard (default 0):\n" - " 0 - Connection counts and local addresses\n" - " 1 - Like 0 but with a peers listing (without address or version columns)\n" - " 2 - Like 1 but with an address column\n" - " 3 - Like 1 but with a version column\n" - " 4 - Like 1 but with both address and version columns\n" - "2. help (string \"help\", optional) Print this help documentation instead of the dashboard.\n\n" - "Result:\n\n" - "* The peers listing in levels 1-4 displays all of the peers sorted by direction and minimum ping time:\n\n" - " Column Description\n" - " ------ -----------\n" - " <-> Direction\n" - " \"in\" - inbound connections are those initiated by the peer\n" - " \"out\" - outbound connections are those initiated by us\n" - " type Type of peer connection\n" - " \"full\" - full relay, the default\n" - " \"block\" - block relay; like full relay but does not relay transactions or addresses\n" - " \"manual\" - peer we manually added using RPC addnode or the -addnode/-connect config options\n" - " \"feeler\" - short-lived connection for testing addresses\n" - " \"addr\" - address fetch; short-lived connection for requesting addresses\n" - " net Network the peer connected through (\"ipv4\", \"ipv6\", \"onion\", \"i2p\", or \"cjdns\")\n" - " mping Minimum observed ping time, in milliseconds (ms)\n" - " ping Last observed ping time, in milliseconds (ms)\n" - " send Time since last message sent to the peer, in seconds\n" - " recv Time since last message received from the peer, in seconds\n" - " txn Time since last novel transaction received from the peer and accepted into our mempool, in minutes\n" - " blk Time since last novel block passing initial validity checks received from the peer, in minutes\n" - " hb High-bandwidth BIP152 compact block relay\n" - " \".\" (to) - we selected the peer as a high-bandwidth peer\n" - " \"*\" (from) - the peer selected us as a high-bandwidth peer\n" - " age Duration of connection to the peer, in minutes\n" - " asmap Mapped AS (Autonomous System) number in the BGP route to the peer, used for diversifying\n" - " peer selection (only displayed if the -asmap config option is set)\n" - " id Peer index, in increasing order of peer connections since node startup\n" - " address IP address and port of the peer\n" - " version Peer version and subversion concatenated, e.g. \"70016/Satoshi:21.0.0/\"\n\n" - "* The connection counts table displays the number of peers by direction, network, and the totals\n" - " for each, as well as two special outbound columns for block relay peers and manual peers.\n\n" - "* The local addresses table lists each local address broadcast by the node, the port, and the score.\n\n" - "Examples:\n\n" - "Connection counts and local addresses only\n" - "> bitcoin-cli -netinfo\n\n" - "Compact peers listing\n" - "> bitcoin-cli -netinfo 1\n\n" - "Full dashboard\n" - "> bitcoin-cli -netinfo 4\n\n" - "Full live dashboard, adjust --interval or --no-title as needed (Linux)\n" - "> watch --interval 1 --no-title bitcoin-cli -netinfo 4\n\n" - "See this help\n" - "> bitcoin-cli -netinfo help\n"}; - } const int64_t m_time_now{GetSystemTimeInSeconds()}; public: @@ -441,10 +376,8 @@ public: uint8_t n{0}; if (ParseUInt8(args.at(0), &n)) { m_details_level = std::min(n, MAX_DETAIL_LEVEL); - } else if (args.at(0) == "help") { - m_is_help_requested = true; } else { - throw std::runtime_error(strprintf("invalid -netinfo argument: %s", args.at(0))); + throw std::runtime_error(strprintf("invalid -netinfo argument: %s\nFor more information, run: bitcoin-cli -netinfo help", args.at(0))); } } UniValue result(UniValue::VARR); @@ -455,9 +388,6 @@ public: UniValue ProcessReply(const UniValue& batch_in) override { - if (m_is_help_requested) { - return JSONRPCReplyObj(NetinfoHelp(), NullUniValue, 1); - } const std::vector<UniValue> batch{JSONRPCProcessBatchReply(batch_in)}; if (!batch[ID_PEERINFO]["error"].isNull()) return batch[ID_PEERINFO]; if (!batch[ID_NETWORKINFO]["error"].isNull()) return batch[ID_NETWORKINFO]; @@ -472,7 +402,6 @@ public: const std::string network{peer["network"].get_str()}; const int8_t network_id{NetworkStringToId(network)}; if (network_id == UNKNOWN_NETWORK) continue; - m_is_i2p_on |= (network_id == NET_I2P); const bool is_outbound{!peer["inbound"].get_bool()}; const bool is_block_relay{!peer["relaytxes"].get_bool()}; const std::string conn_type{peer["connection_type"].get_str()}; @@ -545,13 +474,14 @@ public: // Report peer connection totals by type. result += " ipv4 ipv6 onion"; - if (m_is_i2p_on) result += " i2p"; + const bool any_i2p_peers = m_counts.at(2).at(3); // false if total i2p peers count is 0, otherwise true + if (any_i2p_peers) result += " i2p"; result += " total block"; if (m_manual_peers_count) result += " manual"; const std::array<std::string, 3> rows{{"in", "out", "total"}}; for (uint8_t i = 0; i < 3; ++i) { result += strprintf("\n%-5s %5i %5i %5i", rows.at(i), m_counts.at(i).at(0), m_counts.at(i).at(1), m_counts.at(i).at(2)); // ipv4/ipv6/onion peers counts - if (m_is_i2p_on) result += strprintf(" %5i", m_counts.at(i).at(3)); // i2p peers count + if (any_i2p_peers) result += strprintf(" %5i", m_counts.at(i).at(3)); // i2p peers count result += strprintf(" %5i", m_counts.at(i).at(m_networks_size)); // total peers count if (i == 1) { // the outbound row has two extra columns for block relay and manual peer counts result += strprintf(" %5i", m_block_relay_peers_count); @@ -576,6 +506,67 @@ public: return JSONRPCReplyObj(UniValue{result}, NullUniValue, 1); } + + const std::string m_help_doc{ + "-netinfo level|\"help\" \n\n" + "Returns a network peer connections dashboard with information from the remote server.\n" + "This human-readable interface will change regularly and is not intended to be a stable API.\n" + "Under the hood, -netinfo fetches the data by calling getpeerinfo and getnetworkinfo.\n" + + strprintf("An optional integer argument from 0 to %d can be passed for different peers listings; %d to 255 are parsed as %d.\n", MAX_DETAIL_LEVEL, MAX_DETAIL_LEVEL, MAX_DETAIL_LEVEL) + + "Pass \"help\" to see this detailed help documentation.\n" + "If more than one argument is passed, only the first one is read and parsed.\n" + "Suggestion: use with the Linux watch(1) command for a live dashboard; see example below.\n\n" + "Arguments:\n" + + strprintf("1. level (integer 0-%d, optional) Specify the info level of the peers dashboard (default 0):\n", MAX_DETAIL_LEVEL) + + " 0 - Connection counts and local addresses\n" + " 1 - Like 0 but with a peers listing (without address or version columns)\n" + " 2 - Like 1 but with an address column\n" + " 3 - Like 1 but with a version column\n" + " 4 - Like 1 but with both address and version columns\n" + "2. help (string \"help\", optional) Print this help documentation instead of the dashboard.\n\n" + "Result:\n\n" + + strprintf("* The peers listing in levels 1-%d displays all of the peers sorted by direction and minimum ping time:\n\n", MAX_DETAIL_LEVEL) + + " Column Description\n" + " ------ -----------\n" + " <-> Direction\n" + " \"in\" - inbound connections are those initiated by the peer\n" + " \"out\" - outbound connections are those initiated by us\n" + " type Type of peer connection\n" + " \"full\" - full relay, the default\n" + " \"block\" - block relay; like full relay but does not relay transactions or addresses\n" + " \"manual\" - peer we manually added using RPC addnode or the -addnode/-connect config options\n" + " \"feeler\" - short-lived connection for testing addresses\n" + " \"addr\" - address fetch; short-lived connection for requesting addresses\n" + " net Network the peer connected through (\"ipv4\", \"ipv6\", \"onion\", \"i2p\", or \"cjdns\")\n" + " mping Minimum observed ping time, in milliseconds (ms)\n" + " ping Last observed ping time, in milliseconds (ms)\n" + " send Time since last message sent to the peer, in seconds\n" + " recv Time since last message received from the peer, in seconds\n" + " txn Time since last novel transaction received from the peer and accepted into our mempool, in minutes\n" + " blk Time since last novel block passing initial validity checks received from the peer, in minutes\n" + " hb High-bandwidth BIP152 compact block relay\n" + " \".\" (to) - we selected the peer as a high-bandwidth peer\n" + " \"*\" (from) - the peer selected us as a high-bandwidth peer\n" + " age Duration of connection to the peer, in minutes\n" + " asmap Mapped AS (Autonomous System) number in the BGP route to the peer, used for diversifying\n" + " peer selection (only displayed if the -asmap config option is set)\n" + " id Peer index, in increasing order of peer connections since node startup\n" + " address IP address and port of the peer\n" + " version Peer version and subversion concatenated, e.g. \"70016/Satoshi:21.0.0/\"\n\n" + "* The connection counts table displays the number of peers by direction, network, and the totals\n" + " for each, as well as two special outbound columns for block relay peers and manual peers.\n\n" + "* The local addresses table lists each local address broadcast by the node, the port, and the score.\n\n" + "Examples:\n\n" + "Connection counts and local addresses only\n" + "> bitcoin-cli -netinfo\n\n" + "Compact peers listing\n" + "> bitcoin-cli -netinfo 1\n\n" + "Full dashboard\n" + + strprintf("> bitcoin-cli -netinfo %d\n\n", MAX_DETAIL_LEVEL) + + "Full live dashboard, adjust --interval or --no-title as needed (Linux)\n" + + strprintf("> watch --interval 1 --no-title bitcoin-cli -netinfo %d\n\n", MAX_DETAIL_LEVEL) + + "See this help\n" + "> bitcoin-cli -netinfo help\n"}; }; /** Process RPC generatetoaddress request. */ @@ -620,16 +611,16 @@ public: } }; -static UniValue CallRPC(BaseRequestHandler* rh, const std::string& strMethod, const std::vector<std::string>& args, const Optional<std::string>& rpcwallet = {}) +static UniValue CallRPC(BaseRequestHandler* rh, const std::string& strMethod, const std::vector<std::string>& args, const std::optional<std::string>& rpcwallet = {}) { std::string host; // In preference order, we choose the following for the port: // 1. -rpcport // 2. port in -rpcconnect (ie following : in ipv4 or ]: in ipv6) // 3. default port for chain - int port = BaseParams().RPCPort(); + uint16_t port{BaseParams().RPCPort()}; SplitHostPort(gArgs.GetArg("-rpcconnect", DEFAULT_RPCCONNECT), port, host); - port = gArgs.GetArg("-rpcport", port); + port = static_cast<uint16_t>(gArgs.GetArg("-rpcport", port)); // Obtain event base raii_event_base base = obtain_event_base(); @@ -717,6 +708,8 @@ static UniValue CallRPC(BaseRequestHandler* rh, const std::string& strMethod, co } else { throw std::runtime_error("Authorization failed: Incorrect rpcuser or rpcpassword"); } + } else if (response.status == HTTP_SERVICE_UNAVAILABLE) { + throw std::runtime_error(strprintf("Server response: %s", response.body)); } else if (response.status >= 400 && response.status != HTTP_BAD_REQUEST && response.status != HTTP_NOT_FOUND && response.status != HTTP_INTERNAL_SERVER_ERROR) throw std::runtime_error(strprintf("server returned HTTP error %d", response.status)); else if (response.body.empty()) @@ -742,7 +735,7 @@ static UniValue CallRPC(BaseRequestHandler* rh, const std::string& strMethod, co * @returns the RPC response as a UniValue object. * @throws a CConnectionFailed std::runtime_error if connection failed or RPC server still in warmup. */ -static UniValue ConnectAndCallRPC(BaseRequestHandler* rh, const std::string& strMethod, const std::vector<std::string>& args, const Optional<std::string>& rpcwallet = {}) +static UniValue ConnectAndCallRPC(BaseRequestHandler* rh, const std::string& strMethod, const std::vector<std::string>& args, const std::optional<std::string>& rpcwallet = {}) { UniValue response(UniValue::VOBJ); // Execute and handle connection failures with -rpcwait. @@ -826,7 +819,7 @@ static void GetWalletBalances(UniValue& result) */ static UniValue GetNewAddress() { - Optional<std::string> wallet_name{}; + std::optional<std::string> wallet_name{}; if (gArgs.IsArgSet("-rpcwallet")) wallet_name = gArgs.GetArg("-rpcwallet", ""); DefaultRequestHandler rh; return ConnectAndCallRPC(&rh, "getnewaddress", /* args=*/{}, wallet_name); @@ -907,6 +900,10 @@ static int CommandLineRPC(int argc, char *argv[]) if (gArgs.IsArgSet("-getinfo")) { rh.reset(new GetinfoRequestHandler()); } else if (gArgs.GetBoolArg("-netinfo", false)) { + if (!args.empty() && args.at(0) == "help") { + tfm::format(std::cout, "%s\n", NetinfoRequestHandler().m_help_doc); + return 0; + } rh.reset(new NetinfoRequestHandler()); } else if (gArgs.GetBoolArg("-generate", false)) { const UniValue getnewaddress{GetNewAddress()}; @@ -927,7 +924,7 @@ static int CommandLineRPC(int argc, char *argv[]) } if (nRet == 0) { // Perform RPC call - Optional<std::string> wallet_name{}; + std::optional<std::string> wallet_name{}; if (gArgs.IsArgSet("-rpcwallet")) wallet_name = gArgs.GetArg("-rpcwallet", ""); const UniValue reply = ConnectAndCallRPC(rh.get(), method, args, wallet_name); diff --git a/src/bitcoin-tx.cpp b/src/bitcoin-tx.cpp index 321d62fe4d..93ac4b8f7e 100644 --- a/src/bitcoin-tx.cpp +++ b/src/bitcoin-tx.cpp @@ -725,7 +725,7 @@ static void MutateTx(CMutableTransaction& tx, const std::string& command, static void OutputTxJSON(const CTransaction& tx) { UniValue entry(UniValue::VOBJ); - TxToUniv(tx, uint256(), entry); + TxToUniv(tx, uint256(), /* include_addresses */ false, entry); std::string jsonOutput = entry.write(4); tfm::format(std::cout, "%s\n", jsonOutput); diff --git a/src/bitcoind.cpp b/src/bitcoind.cpp index b7bcb534ef..80ab69c131 100644 --- a/src/bitcoind.cpp +++ b/src/bitcoind.cpp @@ -16,18 +16,94 @@ #include <node/ui_interface.h> #include <noui.h> #include <shutdown.h> -#include <util/ref.h> +#include <util/check.h> #include <util/strencodings.h> #include <util/system.h> #include <util/threadnames.h> +#include <util/tokenpipe.h> #include <util/translation.h> #include <util/url.h> +#include <any> #include <functional> +#include <optional> const std::function<std::string(const char*)> G_TRANSLATION_FUN = nullptr; UrlDecodeFn* const URL_DECODE = urlDecode; +#if HAVE_DECL_FORK + +/** Custom implementation of daemon(). This implements the same order of operations as glibc. + * Opens a pipe to the child process to be able to wait for an event to occur. + * + * @returns 0 if successful, and in child process. + * >0 if successful, and in parent process. + * -1 in case of error (in parent process). + * + * In case of success, endpoint will be one end of a pipe from the child to parent process, + * which can be used with TokenWrite (in the child) or TokenRead (in the parent). + */ +int fork_daemon(bool nochdir, bool noclose, TokenPipeEnd& endpoint) +{ + // communication pipe with child process + std::optional<TokenPipe> umbilical = TokenPipe::Make(); + if (!umbilical) { + return -1; // pipe or pipe2 failed. + } + + int pid = fork(); + if (pid < 0) { + return -1; // fork failed. + } + if (pid != 0) { + // Parent process gets read end, closes write end. + endpoint = umbilical->TakeReadEnd(); + umbilical->TakeWriteEnd().Close(); + + int status = endpoint.TokenRead(); + if (status != 0) { // Something went wrong while setting up child process. + endpoint.Close(); + return -1; + } + + return pid; + } + // Child process gets write end, closes read end. + endpoint = umbilical->TakeWriteEnd(); + umbilical->TakeReadEnd().Close(); + +#if HAVE_DECL_SETSID + if (setsid() < 0) { + exit(1); // setsid failed. + } +#endif + + if (!nochdir) { + if (chdir("/") != 0) { + exit(1); // chdir failed. + } + } + if (!noclose) { + // Open /dev/null, and clone it into STDIN, STDOUT and STDERR to detach + // from terminal. + int fd = open("/dev/null", O_RDWR); + if (fd >= 0) { + bool err = dup2(fd, STDIN_FILENO) < 0 || dup2(fd, STDOUT_FILENO) < 0 || dup2(fd, STDERR_FILENO) < 0; + // Don't close if fd<=2 to try to handle the case where the program was invoked without any file descriptors open. + if (fd > 2) close(fd); + if (err) { + exit(1); // dup2 failed. + } + } else { + exit(1); // open /dev/null failed. + } + } + endpoint.TokenWrite(0); // Success + return 0; +} + +#endif + static bool AppInit(int argc, char* argv[]) { NodeContext node; @@ -59,7 +135,15 @@ static bool AppInit(int argc, char* argv[]) return true; } - util::Ref context{node}; +#if HAVE_DECL_FORK + // Communication with parent after daemonizing. This is used for signalling in the following ways: + // - a boolean token is sent when the initialization process (all the Init* functions) have finished to indicate + // that the parent process can quit, and whether it was successful/unsuccessful. + // - an unexpected shutdown of the child process creates an unexpected end of stream at the parent + // end, which is interpreted as failure to start. + TokenPipeEnd daemon_ep; +#endif + std::any context{&node}; try { if (!CheckDataDirOption()) { @@ -105,24 +189,34 @@ static bool AppInit(int argc, char* argv[]) // InitError will have been called with detailed error, which ends up on console return false; } - if (args.GetBoolArg("-daemon", false)) { -#if HAVE_DECL_DAEMON -#if defined(MAC_OSX) -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wdeprecated-declarations" -#endif + if (args.GetBoolArg("-daemon", DEFAULT_DAEMON) || args.GetBoolArg("-daemonwait", DEFAULT_DAEMONWAIT)) { +#if HAVE_DECL_FORK tfm::format(std::cout, PACKAGE_NAME " starting\n"); // Daemonize - if (daemon(1, 0)) { // don't chdir (1), do close FDs (0) - return InitError(Untranslated(strprintf("daemon() failed: %s\n", strerror(errno)))); + switch (fork_daemon(1, 0, daemon_ep)) { // don't chdir (1), do close FDs (0) + case 0: // Child: continue. + // If -daemonwait is not enabled, immediately send a success token the parent. + if (!args.GetBoolArg("-daemonwait", DEFAULT_DAEMONWAIT)) { + daemon_ep.TokenWrite(1); + daemon_ep.Close(); + } + break; + case -1: // Error happened. + return InitError(Untranslated(strprintf("fork_daemon() failed: %s\n", strerror(errno)))); + default: { // Parent: wait and exit. + int token = daemon_ep.TokenRead(); + if (token) { // Success + exit(EXIT_SUCCESS); + } else { // fRet = false or token read error (premature exit). + tfm::format(std::cerr, "Error during initializaton - check debug.log for details\n"); + exit(EXIT_FAILURE); + } + } } -#if defined(MAC_OSX) -#pragma GCC diagnostic pop -#endif #else return InitError(Untranslated("-daemon is not supported on this operating system\n")); -#endif // HAVE_DECL_DAEMON +#endif // HAVE_DECL_FORK } // Lock data directory after daemonization if (!AppInitLockDataDirectory()) @@ -138,6 +232,13 @@ static bool AppInit(int argc, char* argv[]) PrintExceptionContinue(nullptr, "AppInit()"); } +#if HAVE_DECL_FORK + if (daemon_ep.IsOpen()) { + // Signal initialization status to parent, then close pipe. + daemon_ep.TokenWrite(fRet); + daemon_ep.Close(); + } +#endif if (fRet) { WaitForShutdown(); } diff --git a/src/chainparams.h b/src/chainparams.h index 4d24dcdb7c..013f075be6 100644 --- a/src/chainparams.h +++ b/src/chainparams.h @@ -84,7 +84,7 @@ public: const Consensus::Params& GetConsensus() const { return consensus; } const CMessageHeader::MessageStartChars& MessageStart() const { return pchMessageStart; } - int GetDefaultPort() const { return nDefaultPort; } + uint16_t GetDefaultPort() const { return nDefaultPort; } const CBlock& GenesisBlock() const { return genesis; } /** Default value for -checkmempool and -checkblockindex argument */ @@ -121,7 +121,7 @@ protected: Consensus::Params consensus; CMessageHeader::MessageStartChars pchMessageStart; - int nDefaultPort; + uint16_t nDefaultPort; uint64_t nPruneAfterHeight; uint64_t m_assumed_blockchain_size; uint64_t m_assumed_chain_state_size; diff --git a/src/chainparamsbase.cpp b/src/chainparamsbase.cpp index 2c517b58f8..1631176477 100644 --- a/src/chainparamsbase.cpp +++ b/src/chainparamsbase.cpp @@ -7,7 +7,6 @@ #include <tinyformat.h> #include <util/system.h> -#include <util/memory.h> #include <assert.h> @@ -44,13 +43,13 @@ const CBaseChainParams& BaseParams() std::unique_ptr<CBaseChainParams> CreateBaseChainParams(const std::string& chain) { if (chain == CBaseChainParams::MAIN) { - return MakeUnique<CBaseChainParams>("", 8332, 8334); + return std::make_unique<CBaseChainParams>("", 8332, 8334); } else if (chain == CBaseChainParams::TESTNET) { - return MakeUnique<CBaseChainParams>("testnet3", 18332, 18334); + return std::make_unique<CBaseChainParams>("testnet3", 18332, 18334); } else if (chain == CBaseChainParams::SIGNET) { - return MakeUnique<CBaseChainParams>("signet", 38332, 38334); + return std::make_unique<CBaseChainParams>("signet", 38332, 38334); } else if (chain == CBaseChainParams::REGTEST) { - return MakeUnique<CBaseChainParams>("regtest", 18443, 18445); + return std::make_unique<CBaseChainParams>("regtest", 18443, 18445); } throw std::runtime_error(strprintf("%s: Unknown chain %s.", __func__, chain)); } 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/compat.h b/src/compat.h index dad14748a2..3449bc2661 100644 --- a/src/compat.h +++ b/src/compat.h @@ -44,6 +44,7 @@ typedef unsigned int SOCKET; #define WSAEINVAL EINVAL #define WSAEALREADY EALREADY #define WSAEWOULDBLOCK EWOULDBLOCK +#define WSAEAGAIN EAGAIN #define WSAEMSGSIZE EMSGSIZE #define WSAEINTR EINTR #define WSAEINPROGRESS EINPROGRESS @@ -51,6 +52,14 @@ typedef unsigned int SOCKET; #define WSAENOTSOCK EBADF #define INVALID_SOCKET (SOCKET)(~0) #define SOCKET_ERROR -1 +#else +#ifndef WSAEAGAIN +#ifdef EAGAIN +#define WSAEAGAIN EAGAIN +#else +#define WSAEAGAIN WSAEWOULDBLOCK +#endif +#endif #endif #ifdef WIN32 @@ -96,4 +105,14 @@ bool static inline IsSelectableSocket(const SOCKET& s) { #endif } +// MSG_NOSIGNAL is not available on some platforms, if it doesn't exist define it as 0 +#if !defined(MSG_NOSIGNAL) +#define MSG_NOSIGNAL 0 +#endif + +// MSG_DONTWAIT is not available on some platforms, if it doesn't exist define it as 0 +#if !defined(MSG_DONTWAIT) +#define MSG_DONTWAIT 0 +#endif + #endif // BITCOIN_COMPAT_H diff --git a/src/compat/glibc_compat.cpp b/src/compat/glibc_compat.cpp index 8a51f310f7..ff581d4a9e 100644 --- a/src/compat/glibc_compat.cpp +++ b/src/compat/glibc_compat.cpp @@ -9,13 +9,6 @@ #include <cstddef> #include <cstdint> -// Prior to GLIBC_2.14, memcpy was aliased to memmove. -extern "C" void* memmove(void* a, const void* b, size_t c); -extern "C" void* memcpy(void* a, const void* b, size_t c) -{ - return memmove(a, b, c); -} - #if defined(__i386__) || defined(__arm__) extern "C" int64_t __udivmoddi4(uint64_t u, uint64_t v, uint64_t* rp); diff --git a/src/compat/glibc_sanity.cpp b/src/compat/glibc_sanity.cpp deleted file mode 100644 index 06d0dd6fba..0000000000 --- a/src/compat/glibc_sanity.cpp +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright (c) 2009-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. - -#if defined(HAVE_CONFIG_H) -#include <config/bitcoin-config.h> -#endif - -#include <cstddef> - -extern "C" void* memcpy(void* a, const void* b, size_t c); -void* memcpy_int(void* a, const void* b, size_t c) -{ - return memcpy(a, b, c); -} - -namespace -{ -// trigger: Use the memcpy_int wrapper which calls our internal memcpy. -// A direct call to memcpy may be optimized away by the compiler. -// test: Fill an array with a sequence of integers. memcpy to a new empty array. -// Verify that the arrays are equal. Use an odd size to decrease the odds of -// the call being optimized away. -template <unsigned int T> -bool sanity_test_memcpy() -{ - unsigned int memcpy_test[T]; - unsigned int memcpy_verify[T] = {}; - for (unsigned int i = 0; i != T; ++i) - memcpy_test[i] = i; - - memcpy_int(memcpy_verify, memcpy_test, sizeof(memcpy_test)); - - for (unsigned int i = 0; i != T; ++i) { - if (memcpy_verify[i] != i) - return false; - } - return true; -} -} // namespace - -bool glibc_sanity_test() -{ - return sanity_test_memcpy<1025>(); -} diff --git a/src/compat/sanity.h b/src/compat/sanity.h index 909c4f6da8..8efa416102 100644 --- a/src/compat/sanity.h +++ b/src/compat/sanity.h @@ -5,7 +5,6 @@ #ifndef BITCOIN_COMPAT_SANITY_H #define BITCOIN_COMPAT_SANITY_H -bool glibc_sanity_test(); bool glibcxx_sanity_test(); #endif // BITCOIN_COMPAT_SANITY_H diff --git a/src/core_io.h b/src/core_io.h index 5469a760ee..3b9b66574c 100644 --- a/src/core_io.h +++ b/src/core_io.h @@ -40,12 +40,12 @@ std::vector<unsigned char> ParseHexUV(const UniValue& v, const std::string& strN int ParseSighashString(const UniValue& sighash); // core_write.cpp -UniValue ValueFromAmount(const CAmount& amount); +UniValue ValueFromAmount(const CAmount amount); std::string FormatScript(const CScript& script); std::string EncodeHexTx(const CTransaction& tx, const int serializeFlags = 0); std::string SighashToStr(unsigned char sighash_type); -void ScriptPubKeyToUniv(const CScript& scriptPubKey, UniValue& out, bool fIncludeHex); +void ScriptPubKeyToUniv(const CScript& scriptPubKey, UniValue& out, bool fIncludeHex, bool include_addresses); void ScriptToUniv(const CScript& script, UniValue& out, bool include_address); -void TxToUniv(const CTransaction& tx, const uint256& hashBlock, UniValue& entry, bool include_hex = true, int serialize_flags = 0, const CTxUndo* txundo = nullptr); +void TxToUniv(const CTransaction& tx, const uint256& hashBlock, bool include_addresses, UniValue& entry, bool include_hex = true, int serialize_flags = 0, const CTxUndo* txundo = nullptr); #endif // BITCOIN_CORE_IO_H diff --git a/src/core_write.cpp b/src/core_write.cpp index a3902863d6..b35f835f42 100644 --- a/src/core_write.cpp +++ b/src/core_write.cpp @@ -14,17 +14,20 @@ #include <undo.h> #include <univalue.h> #include <util/check.h> -#include <util/system.h> #include <util/strencodings.h> +#include <util/system.h> -UniValue ValueFromAmount(const CAmount& amount) +UniValue ValueFromAmount(const CAmount amount) { - bool sign = amount < 0; - int64_t n_abs = (sign ? -amount : amount); - int64_t quotient = n_abs / COIN; - int64_t remainder = n_abs % COIN; + static_assert(COIN > 1); + int64_t quotient = amount / COIN; + int64_t remainder = amount % COIN; + if (amount < 0) { + quotient = -quotient; + remainder = -remainder; + } return UniValue(UniValue::VNUM, - strprintf("%s%d.%08d", sign ? "-" : "", quotient, remainder)); + strprintf("%s%d.%08d", amount < 0 ? "-" : "", quotient, remainder)); } std::string FormatScript(const CScript& script) @@ -153,10 +156,13 @@ void ScriptToUniv(const CScript& script, UniValue& out, bool include_address) } } +// TODO: from v23 ("addresses" and "reqSigs" deprecated) this method should be refactored to remove the `include_addresses` option +// this method can also be combined with `ScriptToUniv` as they will overlap void ScriptPubKeyToUniv(const CScript& scriptPubKey, - UniValue& out, bool fIncludeHex) + UniValue& out, bool fIncludeHex, bool include_addresses) { TxoutType type; + CTxDestination address; std::vector<CTxDestination> addresses; int nRequired; @@ -169,17 +175,22 @@ void ScriptPubKeyToUniv(const CScript& scriptPubKey, return; } - out.pushKV("reqSigs", nRequired); + if (ExtractDestination(scriptPubKey, address)) { + out.pushKV("address", EncodeDestination(address)); + } out.pushKV("type", GetTxnOutputType(type)); - UniValue a(UniValue::VARR); - for (const CTxDestination& addr : addresses) { - a.push_back(EncodeDestination(addr)); + if (include_addresses) { + UniValue a(UniValue::VARR); + for (const CTxDestination& addr : addresses) { + a.push_back(EncodeDestination(addr)); + } + out.pushKV("addresses", a); + out.pushKV("reqSigs", nRequired); } - out.pushKV("addresses", a); } -void TxToUniv(const CTransaction& tx, const uint256& hashBlock, UniValue& entry, bool include_hex, int serialize_flags, const CTxUndo* txundo) +void TxToUniv(const CTransaction& tx, const uint256& hashBlock, bool include_addresses, UniValue& entry, bool include_hex, int serialize_flags, const CTxUndo* txundo) { entry.pushKV("txid", tx.GetHash().GetHex()); entry.pushKV("hash", tx.GetWitnessHash().GetHex()); @@ -238,7 +249,7 @@ void TxToUniv(const CTransaction& tx, const uint256& hashBlock, UniValue& entry, out.pushKV("n", (int64_t)i); UniValue o(UniValue::VOBJ); - ScriptPubKeyToUniv(txout.scriptPubKey, o, true); + ScriptPubKeyToUniv(txout.scriptPubKey, o, true, include_addresses); out.pushKV("scriptPubKey", o); vout.push_back(out); diff --git a/src/httprpc.cpp b/src/httprpc.cpp index cb8b220895..16ab38e0b2 100644 --- a/src/httprpc.cpp +++ b/src/httprpc.cpp @@ -144,7 +144,7 @@ static bool RPCAuthorized(const std::string& strAuth, std::string& strAuthUserna return multiUserAuthorized(strUserPass); } -static bool HTTPReq_JSONRPC(const util::Ref& context, HTTPRequest* req) +static bool HTTPReq_JSONRPC(const std::any& context, HTTPRequest* req) { // JSONRPC handles only POST if (req->GetRequestMethod() != HTTPRequest::POST) { @@ -288,7 +288,7 @@ static bool InitRPCAuthentication() return true; } -bool StartHTTPRPC(const util::Ref& context) +bool StartHTTPRPC(const std::any& context) { LogPrint(BCLog::RPC, "Starting HTTP RPC server\n"); if (!InitRPCAuthentication()) @@ -301,7 +301,7 @@ bool StartHTTPRPC(const util::Ref& context) } struct event_base* eventBase = EventBase(); assert(eventBase); - httpRPCTimerInterface = MakeUnique<HTTPRPCTimerInterface>(eventBase); + httpRPCTimerInterface = std::make_unique<HTTPRPCTimerInterface>(eventBase); RPCSetTimerInterface(httpRPCTimerInterface.get()); return true; } diff --git a/src/httprpc.h b/src/httprpc.h index 97af6f7bb1..5a3b990646 100644 --- a/src/httprpc.h +++ b/src/httprpc.h @@ -5,14 +5,12 @@ #ifndef BITCOIN_HTTPRPC_H #define BITCOIN_HTTPRPC_H -namespace util { -class Ref; -} // namespace util +#include <any> /** Start HTTP RPC subsystem. * Precondition; HTTP and RPC has been started. */ -bool StartHTTPRPC(const util::Ref& context); +bool StartHTTPRPC(const std::any& context); /** Interrupt HTTP RPC subsystem. */ void InterruptHTTPRPC(); @@ -24,7 +22,7 @@ void StopHTTPRPC(); /** Start HTTP REST subsystem. * Precondition; HTTP and RPC has been started. */ -void StartREST(const util::Ref& context); +void StartREST(const std::any& context); /** Interrupt RPC REST subsystem. */ void InterruptREST(); diff --git a/src/httpserver.cpp b/src/httpserver.cpp index 0a8e58ab67..12395f5b24 100644 --- a/src/httpserver.cpp +++ b/src/httpserver.cpp @@ -262,7 +262,7 @@ static void http_request_cb(struct evhttp_request* req, void* arg) item.release(); /* if true, queue took ownership */ else { LogPrintf("WARNING: request rejected because http work queue depth exceeded, it can be increased with the -rpcworkqueue= setting\n"); - item->req->WriteReply(HTTP_INTERNAL_SERVER_ERROR, "Work queue depth exceeded"); + item->req->WriteReply(HTTP_SERVICE_UNAVAILABLE, "Work queue depth exceeded"); } } else { hreq->WriteReply(HTTP_NOT_FOUND); @@ -290,8 +290,8 @@ static bool ThreadHTTP(struct event_base* base) /** Bind HTTP server to specified addresses */ static bool HTTPBindAddresses(struct evhttp* http) { - int http_port = gArgs.GetArg("-rpcport", BaseParams().RPCPort()); - std::vector<std::pair<std::string, uint16_t> > endpoints; + uint16_t http_port{static_cast<uint16_t>(gArgs.GetArg("-rpcport", BaseParams().RPCPort()))}; + std::vector<std::pair<std::string, uint16_t>> endpoints; // Determine what addresses to bind to if (!(gArgs.IsArgSet("-rpcallowip") && gArgs.IsArgSet("-rpcbind"))) { // Default to loopback if not allowing external IPs @@ -305,7 +305,7 @@ static bool HTTPBindAddresses(struct evhttp* http) } } else if (gArgs.IsArgSet("-rpcbind")) { // Specific bind address for (const std::string& strRPCBind : gArgs.GetArgs("-rpcbind")) { - int port = http_port; + uint16_t port{http_port}; std::string host; SplitHostPort(strRPCBind, port, host); endpoints.push_back(std::make_pair(host, port)); diff --git a/src/i2p.cpp b/src/i2p.cpp new file mode 100644 index 0000000000..a44f09f043 --- /dev/null +++ b/src/i2p.cpp @@ -0,0 +1,409 @@ +// Copyright (c) 2020-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. + +#include <chainparams.h> +#include <compat.h> +#include <compat/endian.h> +#include <crypto/sha256.h> +#include <fs.h> +#include <i2p.h> +#include <logging.h> +#include <netaddress.h> +#include <netbase.h> +#include <random.h> +#include <util/strencodings.h> +#include <tinyformat.h> +#include <util/readwritefile.h> +#include <util/sock.h> +#include <util/spanparsing.h> +#include <util/system.h> + +#include <chrono> +#include <memory> +#include <stdexcept> +#include <string> + +namespace i2p { + +/** + * Swap Standard Base64 <-> I2P Base64. + * Standard Base64 uses `+` and `/` as last two characters of its alphabet. + * I2P Base64 uses `-` and `~` respectively. + * So it is easy to detect in which one is the input and convert to the other. + * @param[in] from Input to convert. + * @return converted `from` + */ +static std::string SwapBase64(const std::string& from) +{ + std::string to; + to.resize(from.size()); + for (size_t i = 0; i < from.size(); ++i) { + switch (from[i]) { + case '-': + to[i] = '+'; + break; + case '~': + to[i] = '/'; + break; + case '+': + to[i] = '-'; + break; + case '/': + to[i] = '~'; + break; + default: + to[i] = from[i]; + break; + } + } + return to; +} + +/** + * Decode an I2P-style Base64 string. + * @param[in] i2p_b64 I2P-style Base64 string. + * @return decoded `i2p_b64` + * @throw std::runtime_error if decoding fails + */ +static Binary DecodeI2PBase64(const std::string& i2p_b64) +{ + const std::string& std_b64 = SwapBase64(i2p_b64); + bool invalid; + Binary decoded = DecodeBase64(std_b64.c_str(), &invalid); + if (invalid) { + throw std::runtime_error(strprintf("Cannot decode Base64: \"%s\"", i2p_b64)); + } + return decoded; +} + +/** + * Derive the .b32.i2p address of an I2P destination (binary). + * @param[in] dest I2P destination. + * @return the address that corresponds to `dest` + * @throw std::runtime_error if conversion fails + */ +static CNetAddr DestBinToAddr(const Binary& dest) +{ + CSHA256 hasher; + hasher.Write(dest.data(), dest.size()); + unsigned char hash[CSHA256::OUTPUT_SIZE]; + hasher.Finalize(hash); + + CNetAddr addr; + const std::string addr_str = EncodeBase32(hash, false) + ".b32.i2p"; + if (!addr.SetSpecial(addr_str)) { + throw std::runtime_error(strprintf("Cannot parse I2P address: \"%s\"", addr_str)); + } + + return addr; +} + +/** + * Derive the .b32.i2p address of an I2P destination (I2P-style Base64). + * @param[in] dest I2P destination. + * @return the address that corresponds to `dest` + * @throw std::runtime_error if conversion fails + */ +static CNetAddr DestB64ToAddr(const std::string& dest) +{ + const Binary& decoded = DecodeI2PBase64(dest); + return DestBinToAddr(decoded); +} + +namespace sam { + +Session::Session(const fs::path& private_key_file, + const CService& control_host, + CThreadInterrupt* interrupt) + : m_private_key_file(private_key_file), m_control_host(control_host), m_interrupt(interrupt), + m_control_sock(std::make_unique<Sock>(INVALID_SOCKET)) +{ +} + +Session::~Session() +{ + LOCK(m_mutex); + Disconnect(); +} + +bool Session::Listen(Connection& conn) +{ + try { + LOCK(m_mutex); + CreateIfNotCreatedAlready(); + conn.me = m_my_addr; + conn.sock = StreamAccept(); + return true; + } catch (const std::runtime_error& e) { + Log("Error listening: %s", e.what()); + CheckControlSock(); + } + return false; +} + +bool Session::Accept(Connection& conn) +{ + try { + while (!*m_interrupt) { + Sock::Event occurred; + conn.sock->Wait(MAX_WAIT_FOR_IO, Sock::RECV, &occurred); + + if ((occurred & Sock::RECV) == 0) { + // Timeout, no incoming connections within MAX_WAIT_FOR_IO. + continue; + } + + const std::string& peer_dest = + conn.sock->RecvUntilTerminator('\n', MAX_WAIT_FOR_IO, *m_interrupt, MAX_MSG_SIZE); + + conn.peer = CService(DestB64ToAddr(peer_dest), Params().GetDefaultPort()); + + return true; + } + } catch (const std::runtime_error& e) { + Log("Error accepting: %s", e.what()); + CheckControlSock(); + } + return false; +} + +bool Session::Connect(const CService& to, Connection& conn, bool& proxy_error) +{ + proxy_error = true; + + std::string session_id; + std::unique_ptr<Sock> sock; + conn.peer = to; + + try { + { + LOCK(m_mutex); + CreateIfNotCreatedAlready(); + session_id = m_session_id; + conn.me = m_my_addr; + sock = Hello(); + } + + const Reply& lookup_reply = + SendRequestAndGetReply(*sock, strprintf("NAMING LOOKUP NAME=%s", to.ToStringIP())); + + const std::string& dest = lookup_reply.Get("VALUE"); + + const Reply& connect_reply = SendRequestAndGetReply( + *sock, strprintf("STREAM CONNECT ID=%s DESTINATION=%s SILENT=false", session_id, dest), + false); + + const std::string& result = connect_reply.Get("RESULT"); + + if (result == "OK") { + conn.sock = std::move(sock); + return true; + } + + if (result == "INVALID_ID") { + LOCK(m_mutex); + Disconnect(); + throw std::runtime_error("Invalid session id"); + } + + if (result == "CANT_REACH_PEER" || result == "TIMEOUT") { + proxy_error = false; + } + + 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()); + CheckControlSock(); + return false; + } +} + +// Private methods + +std::string Session::Reply::Get(const std::string& key) const +{ + const auto& pos = keys.find(key); + if (pos == keys.end() || !pos->second.has_value()) { + throw std::runtime_error( + strprintf("Missing %s= in the reply to \"%s\": \"%s\"", key, request, full)); + } + return pos->second.value(); +} + +template <typename... Args> +void Session::Log(const std::string& fmt, const Args&... args) const +{ + LogPrint(BCLog::I2P, "I2P: %s\n", tfm::format(fmt, args...)); +} + +Session::Reply Session::SendRequestAndGetReply(const Sock& sock, + const std::string& request, + bool check_result_ok) const +{ + sock.SendComplete(request + "\n", MAX_WAIT_FOR_IO, *m_interrupt); + + Reply reply; + + // Don't log the full "SESSION CREATE ..." because it contains our private key. + reply.request = request.substr(0, 14) == "SESSION CREATE" ? "SESSION CREATE ..." : request; + + // It could take a few minutes for the I2P router to reply as it is querying the I2P network + // (when doing name lookup, for example). Notice: `RecvUntilTerminator()` is checking + // `m_interrupt` more often, so we would not be stuck here for long if `m_interrupt` is + // signaled. + static constexpr auto recv_timeout = 3min; + + reply.full = sock.RecvUntilTerminator('\n', recv_timeout, *m_interrupt, MAX_MSG_SIZE); + + for (const auto& kv : spanparsing::Split(reply.full, ' ')) { + const auto& pos = std::find(kv.begin(), kv.end(), '='); + if (pos != kv.end()) { + reply.keys.emplace(std::string{kv.begin(), pos}, std::string{pos + 1, kv.end()}); + } else { + reply.keys.emplace(std::string{kv.begin(), kv.end()}, std::nullopt); + } + } + + if (check_result_ok && reply.Get("RESULT") != "OK") { + throw std::runtime_error( + strprintf("Unexpected reply to \"%s\": \"%s\"", request, reply.full)); + } + + return reply; +} + +std::unique_ptr<Sock> Session::Hello() const +{ + auto sock = CreateSock(m_control_host); + + if (!sock) { + throw std::runtime_error("Cannot create socket"); + } + + if (!ConnectSocketDirectly(m_control_host, *sock, nConnectTimeout, true)) { + throw std::runtime_error(strprintf("Cannot connect to %s", m_control_host.ToString())); + } + + SendRequestAndGetReply(*sock, "HELLO VERSION MIN=3.1 MAX=3.1"); + + return sock; +} + +void Session::CheckControlSock() +{ + LOCK(m_mutex); + + std::string errmsg; + if (!m_control_sock->IsConnected(errmsg)) { + Log("Control socket error: %s", errmsg); + Disconnect(); + } +} + +void Session::DestGenerate(const Sock& sock) +{ + // https://geti2p.net/spec/common-structures#key-certificates + // "7" or "EdDSA_SHA512_Ed25519" - "Recent Router Identities and Destinations". + // Use "7" because i2pd <2.24.0 does not recognize the textual form. + const Reply& reply = SendRequestAndGetReply(sock, "DEST GENERATE SIGNATURE_TYPE=7", false); + + m_private_key = DecodeI2PBase64(reply.Get("PRIV")); +} + +void Session::GenerateAndSavePrivateKey(const Sock& sock) +{ + DestGenerate(sock); + + // umask is set to 077 in init.cpp, which is ok (unless -sysperms is given) + if (!WriteBinaryFile(m_private_key_file, + std::string(m_private_key.begin(), m_private_key.end()))) { + throw std::runtime_error( + strprintf("Cannot save I2P private key to %s", m_private_key_file)); + } +} + +Binary Session::MyDestination() const +{ + // From https://geti2p.net/spec/common-structures#destination: + // "They are 387 bytes plus the certificate length specified at bytes 385-386, which may be + // non-zero" + static constexpr size_t DEST_LEN_BASE = 387; + static constexpr size_t CERT_LEN_POS = 385; + + uint16_t cert_len; + memcpy(&cert_len, &m_private_key.at(CERT_LEN_POS), sizeof(cert_len)); + cert_len = be16toh(cert_len); + + const size_t dest_len = DEST_LEN_BASE + cert_len; + + return Binary{m_private_key.begin(), m_private_key.begin() + dest_len}; +} + +void Session::CreateIfNotCreatedAlready() +{ + std::string errmsg; + if (m_control_sock->IsConnected(errmsg)) { + return; + } + + Log("Creating SAM session with %s", m_control_host.ToString()); + + auto sock = Hello(); + + const auto& [read_ok, data] = ReadBinaryFile(m_private_key_file); + if (read_ok) { + m_private_key.assign(data.begin(), data.end()); + } else { + GenerateAndSavePrivateKey(*sock); + } + + const std::string& session_id = GetRandHash().GetHex().substr(0, 10); // full is an overkill, too verbose in the logs + const std::string& private_key_b64 = SwapBase64(EncodeBase64(m_private_key)); + + SendRequestAndGetReply(*sock, strprintf("SESSION CREATE STYLE=STREAM ID=%s DESTINATION=%s", + session_id, private_key_b64)); + + m_my_addr = CService(DestBinToAddr(MyDestination()), Params().GetDefaultPort()); + m_session_id = session_id; + m_control_sock = std::move(sock); + + LogPrintf("I2P: SAM session created: session id=%s, my address=%s\n", m_session_id, + m_my_addr.ToString()); +} + +std::unique_ptr<Sock> Session::StreamAccept() +{ + auto sock = Hello(); + + const Reply& reply = SendRequestAndGetReply( + *sock, strprintf("STREAM ACCEPT ID=%s SILENT=false", m_session_id), false); + + const std::string& result = reply.Get("RESULT"); + + if (result == "OK") { + return sock; + } + + if (result == "INVALID_ID") { + // If our session id is invalid, then force session re-creation on next usage. + Disconnect(); + } + + throw std::runtime_error(strprintf("\"%s\"", reply.full)); +} + +void Session::Disconnect() +{ + if (m_control_sock->Get() != INVALID_SOCKET) { + if (m_session_id.empty()) { + Log("Destroying incomplete session"); + } else { + Log("Destroying session %s", m_session_id); + } + } + m_control_sock->Reset(); + m_session_id.clear(); +} +} // namespace sam +} // namespace i2p diff --git a/src/i2p.h b/src/i2p.h new file mode 100644 index 0000000000..cb2efedba8 --- /dev/null +++ b/src/i2p.h @@ -0,0 +1,270 @@ +// Copyright (c) 2020-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. + +#ifndef BITCOIN_I2P_H +#define BITCOIN_I2P_H + +#include <compat.h> +#include <fs.h> +#include <netaddress.h> +#include <sync.h> +#include <threadinterrupt.h> +#include <util/sock.h> + +#include <memory> +#include <optional> +#include <string> +#include <unordered_map> +#include <vector> + +namespace i2p { + +/** + * Binary data. + */ +using Binary = std::vector<uint8_t>; + +/** + * An established connection with another peer. + */ +struct Connection { + /** Connected socket. */ + std::unique_ptr<Sock> sock; + + /** Our I2P address. */ + CService me; + + /** The peer's I2P address. */ + CService peer; +}; + +namespace sam { + +/** + * The maximum size of an incoming message from the I2P SAM proxy (in bytes). + * Used to avoid a runaway proxy from sending us an "unlimited" amount of data without a terminator. + * The longest known message is ~1400 bytes, so this is high enough not to be triggered during + * normal operation, yet low enough to avoid a malicious proxy from filling our memory. + */ +static constexpr size_t MAX_MSG_SIZE{65536}; + +/** + * I2P SAM session. + */ +class Session +{ +public: + /** + * Construct a session. This will not initiate any IO, the session will be lazily created + * later when first used. + * @param[in] private_key_file Path to a private key file. If the file does not exist then the + * private key will be generated and saved into the file. + * @param[in] control_host Location of the SAM proxy. + * @param[in,out] interrupt If this is signaled then all operations are canceled as soon as + * possible and executing methods throw an exception. Notice: only a pointer to the + * `CThreadInterrupt` object is saved, so it must not be destroyed earlier than this + * `Session` object. + */ + Session(const fs::path& private_key_file, + const CService& control_host, + CThreadInterrupt* interrupt); + + /** + * Destroy the session, closing the internally used sockets. The sockets that have been + * returned by `Accept()` or `Connect()` will not be closed, but they will be closed by + * the SAM proxy because the session is destroyed. So they will return an error next time + * we try to read or write to them. + */ + ~Session(); + + /** + * Start listening for an incoming connection. + * @param[out] conn Upon successful completion the `sock` and `me` members will be set + * to the listening socket and address. + * @return true on success + */ + bool Listen(Connection& conn); + + /** + * Wait for and accept a new incoming connection. + * @param[in,out] conn The `sock` member is used for waiting and accepting. Upon successful + * completion the `peer` member will be set to the address of the incoming peer. + * @return true on success + */ + bool Accept(Connection& conn); + + /** + * Connect to an I2P peer. + * @param[in] to Peer to connect to. + * @param[out] conn Established connection. Only set if `true` is returned. + * @param[out] proxy_error If an error occurs due to proxy or general network failure, then + * this is set to `true`. If an error occurs due to unreachable peer (likely peer is down), then + * it is set to `false`. Only set if `false` is returned. + * @return true on success + */ + bool Connect(const CService& to, Connection& conn, bool& proxy_error); + +private: + /** + * A reply from the SAM proxy. + */ + struct Reply { + /** + * Full, unparsed reply. + */ + std::string full; + + /** + * Request, used for detailed error reporting. + */ + std::string request; + + /** + * A map of keywords from the parsed reply. + * For example, if the reply is "A=X B C=YZ", then the map will be + * keys["A"] == "X" + * keys["B"] == (empty std::optional) + * keys["C"] == "YZ" + */ + std::unordered_map<std::string, std::optional<std::string>> keys; + + /** + * Get the value of a given key. + * For example if the reply is "A=X B" then: + * Value("A") -> "X" + * Value("B") -> throws + * Value("C") -> throws + * @param[in] key Key whose value to retrieve + * @returns the key's value + * @throws std::runtime_error if the key is not present or if it has no value + */ + std::string Get(const std::string& key) const; + }; + + /** + * Log a message in the `BCLog::I2P` category. + * @param[in] fmt printf(3)-like format string. + * @param[in] args printf(3)-like arguments that correspond to `fmt`. + */ + template <typename... Args> + void Log(const std::string& fmt, const Args&... args) const; + + /** + * Send request and get a reply from the SAM proxy. + * @param[in] sock A socket that is connected to the SAM proxy. + * @param[in] request Raw request to send, a newline terminator is appended to it. + * @param[in] check_result_ok If true then after receiving the reply a check is made + * whether it contains "RESULT=OK" and an exception is thrown if it does not. + * @throws std::runtime_error if an error occurs + */ + Reply SendRequestAndGetReply(const Sock& sock, + const std::string& request, + bool check_result_ok = true) const; + + /** + * Open a new connection to the SAM proxy. + * @return a connected socket + * @throws std::runtime_error if an error occurs + */ + std::unique_ptr<Sock> Hello() const EXCLUSIVE_LOCKS_REQUIRED(m_mutex); + + /** + * Check the control socket for errors and possibly disconnect. + */ + void CheckControlSock(); + + /** + * Generate a new destination with the SAM proxy and set `m_private_key` to it. + * @param[in] sock Socket to use for talking to the SAM proxy. + * @throws std::runtime_error if an error occurs + */ + void DestGenerate(const Sock& sock) EXCLUSIVE_LOCKS_REQUIRED(m_mutex); + + /** + * Generate a new destination with the SAM proxy, set `m_private_key` to it and save + * it on disk to `m_private_key_file`. + * @param[in] sock Socket to use for talking to the SAM proxy. + * @throws std::runtime_error if an error occurs + */ + void GenerateAndSavePrivateKey(const Sock& sock) EXCLUSIVE_LOCKS_REQUIRED(m_mutex); + + /** + * Derive own destination from `m_private_key`. + * @see https://geti2p.net/spec/common-structures#destination + * @return an I2P destination + */ + Binary MyDestination() const EXCLUSIVE_LOCKS_REQUIRED(m_mutex); + + /** + * Create the session if not already created. Reads the private key file and connects to the + * SAM proxy. + * @throws std::runtime_error if an error occurs + */ + void CreateIfNotCreatedAlready() EXCLUSIVE_LOCKS_REQUIRED(m_mutex); + + /** + * Open a new connection to the SAM proxy and issue "STREAM ACCEPT" request using the existing + * session id. + * @return the idle socket that is waiting for a peer to connect to us + * @throws std::runtime_error if an error occurs + */ + std::unique_ptr<Sock> StreamAccept() EXCLUSIVE_LOCKS_REQUIRED(m_mutex); + + /** + * Destroy the session, closing the internally used sockets. + */ + void Disconnect() EXCLUSIVE_LOCKS_REQUIRED(m_mutex); + + /** + * The name of the file where this peer's private key is stored (in binary). + */ + const fs::path m_private_key_file; + + /** + * The host and port of the SAM control service. + */ + const CService m_control_host; + + /** + * Cease network activity when this is signaled. + */ + CThreadInterrupt* const m_interrupt; + + /** + * Mutex protecting the members that can be concurrently accessed. + */ + mutable Mutex m_mutex; + + /** + * The private key of this peer. + * @see The reply to the "DEST GENERATE" command in https://geti2p.net/en/docs/api/samv3 + */ + Binary m_private_key GUARDED_BY(m_mutex); + + /** + * SAM control socket. + * Used to connect to the I2P SAM service and create a session + * ("SESSION CREATE"). With the established session id we later open + * other connections to the SAM service to accept incoming I2P + * connections and make outgoing ones. + * See https://geti2p.net/en/docs/api/samv3 + */ + std::unique_ptr<Sock> m_control_sock GUARDED_BY(m_mutex); + + /** + * Our .b32.i2p address. + * Derived from `m_private_key`. + */ + CService m_my_addr GUARDED_BY(m_mutex); + + /** + * SAM session id. + */ + std::string m_session_id GUARDED_BY(m_mutex); +}; + +} // namespace sam +} // namespace i2p + +#endif /* BITCOIN_I2P_H */ diff --git a/src/index/blockfilterindex.cpp b/src/index/blockfilterindex.cpp index 4f61bbeabd..32271fb7ab 100644 --- a/src/index/blockfilterindex.cpp +++ b/src/index/blockfilterindex.cpp @@ -102,8 +102,8 @@ BlockFilterIndex::BlockFilterIndex(BlockFilterType filter_type, fs::create_directories(path); m_name = filter_name + " block filter index"; - m_db = MakeUnique<BaseIndex::DB>(path / "db", n_cache_size, f_memory, f_wipe); - m_filter_fileseq = MakeUnique<FlatFileSeq>(std::move(path), "fltr", FLTR_FILE_CHUNK_SIZE); + m_db = std::make_unique<BaseIndex::DB>(path / "db", n_cache_size, f_memory, f_wipe); + m_filter_fileseq = std::make_unique<FlatFileSeq>(std::move(path), "fltr", FLTR_FILE_CHUNK_SIZE); } bool BlockFilterIndex::Init() diff --git a/src/index/txindex.cpp b/src/index/txindex.cpp index 6398b7edc8..f41985c344 100644 --- a/src/index/txindex.cpp +++ b/src/index/txindex.cpp @@ -192,7 +192,7 @@ bool TxIndex::DB::MigrateData(CBlockTreeDB& block_tree_db, const CBlockLocator& } TxIndex::TxIndex(size_t n_cache_size, bool f_memory, bool f_wipe) - : m_db(MakeUnique<TxIndex::DB>(n_cache_size, f_memory, f_wipe)) + : m_db(std::make_unique<TxIndex::DB>(n_cache_size, f_memory, f_wipe)) {} TxIndex::~TxIndex() {} diff --git a/src/init.cpp b/src/init.cpp index befba2eb2d..17b216573f 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -52,6 +52,7 @@ #include <torcontrol.h> #include <txdb.h> #include <txmempool.h> +#include <txorphanage.h> #include <util/asmap.h> #include <util/check.h> #include <util/moneystr.h> @@ -227,6 +228,7 @@ void Shutdown(NodeContext& node) node.peerman.reset(); node.connman.reset(); node.banman.reset(); + node.addrman.reset(); if (node.mempool && node.mempool->IsLoaded() && node.args->GetArg("-persistmempool", DEFAULT_PERSIST_MEMPOOL)) { DumpMempool(*node.mempool); @@ -447,7 +449,9 @@ void SetupServerArgs(NodeContext& node) argsman.AddArg("-maxtimeadjustment", strprintf("Maximum allowed median peer time offset adjustment. Local perspective of time may be influenced by peers forward or backward by this amount. (default: %u seconds)", DEFAULT_MAX_TIME_ADJUSTMENT), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); argsman.AddArg("-maxuploadtarget=<n>", strprintf("Tries to keep outbound traffic under the given target (in MiB per 24h). Limit does not apply to peers with 'download' permission. 0 = no limit (default: %d)", DEFAULT_MAX_UPLOAD_TARGET), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); argsman.AddArg("-onion=<ip:port>", "Use separate SOCKS5 proxy to reach peers via Tor onion services, set -noonion to disable (default: -proxy)", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); - argsman.AddArg("-onlynet=<net>", "Make outgoing connections only through network <net> (" + Join(GetNetworkNames(), ", ") + "). Incoming connections are not affected by this option. This option can be specified multiple times to allow multiple networks. Warning: if it is used with ipv4 or ipv6 but not onion and the -onion or -proxy option is set, then outbound onion connections will still be made; use -noonion or -onion=0 to disable outbound onion connections in this case.", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); + argsman.AddArg("-i2psam=<ip:port>", "I2P SAM proxy to reach I2P peers and accept I2P connections (default: none)", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); + argsman.AddArg("-i2pacceptincoming", "If set and -i2psam is also set then incoming I2P connections are accepted via the SAM proxy. If this is not set but -i2psam is set then only outgoing connections will be made to the I2P network. Ignored if -i2psam is not set. Listening for incoming I2P connections is done through the SAM proxy, not by binding to a local address and port (default: 1)", ArgsManager::ALLOW_BOOL, OptionsCategory::CONNECTION); + argsman.AddArg("-onlynet=<net>", "Make outgoing connections only through network <net> (" + Join(GetNetworkNames(), ", ") + "). Incoming connections are not affected by this option. This option can be specified multiple times to allow multiple networks. Warning: if it is used with non-onion networks and the -onion or -proxy option is set, then outbound onion connections will still be made; use -noonion or -onion=0 to disable outbound onion connections in this case.", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); argsman.AddArg("-peerbloomfilters", strprintf("Support filtering of blocks and transaction with bloom filters (default: %u)", DEFAULT_PEERBLOOMFILTERS), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); argsman.AddArg("-peerblockfilters", strprintf("Serve compact block filters to peers per BIP 157 (default: %u)", DEFAULT_PEERBLOCKFILTERS), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); argsman.AddArg("-permitbaremultisig", strprintf("Relay non-P2SH multisig (default: %u)", DEFAULT_PERMIT_BAREMULTISIG), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); @@ -577,10 +581,12 @@ void SetupServerArgs(NodeContext& node) argsman.AddArg("-rpcworkqueue=<n>", strprintf("Set the depth of the work queue to service RPC calls (default: %d)", DEFAULT_HTTP_WORKQUEUE), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::RPC); argsman.AddArg("-server", "Accept command line and JSON-RPC commands", ArgsManager::ALLOW_ANY, OptionsCategory::RPC); -#if HAVE_DECL_DAEMON - argsman.AddArg("-daemon", "Run in the background as a daemon and accept commands", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); +#if HAVE_DECL_FORK + argsman.AddArg("-daemon", strprintf("Run in the background as a daemon and accept commands (default: %d)", DEFAULT_DAEMON), ArgsManager::ALLOW_BOOL, OptionsCategory::OPTIONS); + argsman.AddArg("-daemonwait", strprintf("Wait for initialization to be finished before exiting. This implies -daemon (default: %d)", DEFAULT_DAEMONWAIT), ArgsManager::ALLOW_BOOL, OptionsCategory::OPTIONS); #else hidden_args.emplace_back("-daemon"); + hidden_args.emplace_back("-daemonwait"); #endif // Add the hidden options @@ -717,7 +723,7 @@ static void ThreadImport(ChainstateManager& chainman, std::vector<fs::path> vImp fReindex = false; LogPrintf("Reindexing finished\n"); // To avoid ending up in a situation without genesis block, re-try initializing (no-op if reindexing worked): - LoadGenesisBlock(chainparams); + ::ChainstateActive().LoadGenesisBlock(chainparams); } // -loadblock= @@ -768,7 +774,7 @@ static bool InitSanityCheck() return InitError(Untranslated("Elliptic curve cryptography sanity check failure. Aborting.")); } - if (!glibc_sanity_test() || !glibcxx_sanity_test()) + if (!glibcxx_sanity_test()) return false; if (!Random_SanityCheck()) { @@ -782,7 +788,7 @@ static bool InitSanityCheck() return true; } -static bool AppInitServers(const util::Ref& context, NodeContext& node) +static bool AppInitServers(const std::any& context, NodeContext& node) { const ArgsManager& args = *Assert(node.args); RPCServer::OnStarted(&OnRPCStarted); @@ -847,6 +853,9 @@ void InitParameterInteraction(ArgsManager& args) LogPrintf("%s: parameter interaction: -listen=0 -> setting -discover=0\n", __func__); if (args.SoftSetBoolArg("-listenonion", false)) LogPrintf("%s: parameter interaction: -listen=0 -> setting -listenonion=0\n", __func__); + if (args.SoftSetBoolArg("-i2pacceptincoming", false)) { + LogPrintf("%s: parameter interaction: -listen=0 -> setting -i2pacceptincoming=0\n", __func__); + } } if (args.IsArgSet("-externalip")) { @@ -1268,7 +1277,7 @@ bool AppInitInterfaces(NodeContext& node) return true; } -bool AppInitMain(const util::Ref& context, NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) +bool AppInitMain(const std::any& context, NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) { const ArgsManager& args = *Assert(node.args); const CChainParams& chainparams = Params(); @@ -1300,7 +1309,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()); @@ -1343,7 +1352,7 @@ bool AppInitMain(const util::Ref& context, NodeContext& node, interfaces::BlockA } assert(!node.scheduler); - node.scheduler = MakeUnique<CScheduler>(); + node.scheduler = std::make_unique<CScheduler>(); // Start the lightweight task scheduler thread node.scheduler->m_service_thread = std::thread([&] { TraceThread("scheduler", [&] { node.scheduler->serviceQueue(); }); }); @@ -1394,10 +1403,12 @@ bool AppInitMain(const util::Ref& context, NodeContext& node, interfaces::BlockA fDiscover = args.GetBoolArg("-discover", true); const bool ignores_incoming_txs{args.GetBoolArg("-blocksonly", DEFAULT_BLOCKSONLY)}; + assert(!node.addrman); + node.addrman = std::make_unique<CAddrMan>(); assert(!node.banman); - node.banman = MakeUnique<BanMan>(GetDataDir() / "banlist.dat", &uiInterface, args.GetArg("-bantime", DEFAULT_MISBEHAVING_BANTIME)); + node.banman = std::make_unique<BanMan>(GetDataDir() / "banlist.dat", &uiInterface, args.GetArg("-bantime", DEFAULT_MISBEHAVING_BANTIME)); assert(!node.connman); - node.connman = MakeUnique<CConnman>(GetRand(std::numeric_limits<uint64_t>::max()), GetRand(std::numeric_limits<uint64_t>::max()), args.GetBoolArg("-networkactive", true)); + node.connman = std::make_unique<CConnman>(GetRand(std::numeric_limits<uint64_t>::max()), GetRand(std::numeric_limits<uint64_t>::max()), *node.addrman, args.GetBoolArg("-networkactive", true)); assert(!node.fee_estimator); // Don't initialize fee estimation with old data if we don't relay transactions, @@ -1413,7 +1424,7 @@ bool AppInitMain(const util::Ref& context, NodeContext& node, interfaces::BlockA ChainstateManager& chainman = *Assert(node.chainman); assert(!node.peerman); - node.peerman = PeerManager::make(chainparams, *node.connman, node.banman.get(), + node.peerman = PeerManager::make(chainparams, *node.connman, *node.addrman, node.banman.get(), *node.scheduler, chainman, *node.mempool, ignores_incoming_txs); RegisterValidationInterface(node.peerman.get()); @@ -1630,7 +1641,7 @@ bool AppInitMain(const util::Ref& context, NodeContext& node, interfaces::BlockA // If we're not mid-reindex (based on disk + args), add a genesis block on disk // (otherwise we use the one already on disk). // This is called again in ThreadImport after the reindex completes. - if (!fReindex && !LoadGenesisBlock(chainparams)) { + if (!fReindex && !::ChainstateActive().LoadGenesisBlock(chainparams)) { strLoadError = _("Error initializing block database"); break; } @@ -1741,7 +1752,7 @@ bool AppInitMain(const util::Ref& context, NodeContext& node, interfaces::BlockA // work when we allow VerifyDB to be parameterized by chainstate. if (&::ChainstateActive() == chainstate && !CVerifyDB().VerifyDB( - chainparams, &chainstate->CoinsDB(), + chainparams, *chainstate, &chainstate->CoinsDB(), args.GetArg("-checklevel", DEFAULT_CHECKLEVEL), args.GetArg("-checkblocks", DEFAULT_CHECKBLOCKS))) { strLoadError = _("Corrupted block database detected"); @@ -1793,7 +1804,7 @@ bool AppInitMain(const util::Ref& context, NodeContext& node, interfaces::BlockA // ********************************************************* Step 8: start indexers if (args.GetBoolArg("-txindex", DEFAULT_TXINDEX)) { - g_txindex = MakeUnique<TxIndex>(nTxIndexCache, false, fReindex); + g_txindex = std::make_unique<TxIndex>(nTxIndexCache, false, fReindex); g_txindex->Start(); } @@ -1990,6 +2001,21 @@ bool AppInitMain(const util::Ref& context, NodeContext& node, interfaces::BlockA connOptions.m_specified_outgoing = connect; } } + + const std::string& i2psam_arg = args.GetArg("-i2psam", ""); + if (!i2psam_arg.empty()) { + CService addr; + if (!Lookup(i2psam_arg, addr, 7656, fNameLookup) || !addr.IsValid()) { + return InitError(strprintf(_("Invalid -i2psam address or hostname: '%s'"), i2psam_arg)); + } + SetReachable(NET_I2P, true); + SetProxy(NET_I2P, proxyType{addr}); + } else { + SetReachable(NET_I2P, false); + } + + connOptions.m_i2p_accept_incoming = args.GetBoolArg("-i2pacceptincoming", true); + if (!node.connman->Start(*node.scheduler, connOptions)) { return false; } diff --git a/src/init.h b/src/init.h index c04d966d06..5d01d4c1ac 100644 --- a/src/init.h +++ b/src/init.h @@ -6,9 +6,15 @@ #ifndef BITCOIN_INIT_H #define BITCOIN_INIT_H +#include <any> #include <memory> #include <string> +//! Default value for -daemon option +static constexpr bool DEFAULT_DAEMON = false; +//! Default value for -daemonwait option +static constexpr bool DEFAULT_DAEMONWAIT = false; + class ArgsManager; struct NodeContext; namespace interfaces { @@ -17,9 +23,6 @@ struct BlockAndHeaderTipInfo; namespace boost { class thread_group; } // namespace boost -namespace util { -class Ref; -} // namespace util /** Interrupt threads */ void Interrupt(NodeContext& node); @@ -61,7 +64,7 @@ bool AppInitInterfaces(NodeContext& node); * @note This should only be done after daemonization. Call Shutdown() if this function fails. * @pre Parameters should be parsed and config file should be read, AppInitLockDataDirectory should have been called. */ -bool AppInitMain(const util::Ref& context, NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info = nullptr); +bool AppInitMain(const std::any& context, NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info = nullptr); /** * Register all arguments with the ArgsManager diff --git a/src/interfaces/chain.h b/src/interfaces/chain.h index 1a49518d69..3395741b1b 100644 --- a/src/interfaces/chain.h +++ b/src/interfaces/chain.h @@ -5,12 +5,12 @@ #ifndef BITCOIN_INTERFACES_CHAIN_H #define BITCOIN_INTERFACES_CHAIN_H -#include <optional.h> // For Optional and nullopt #include <primitives/transaction.h> // For CTransactionRef #include <util/settings.h> // For util::SettingsValue #include <functional> #include <memory> +#include <optional> #include <stddef.h> #include <stdint.h> #include <string> @@ -94,7 +94,7 @@ public: //! Get current chain height, not including genesis block (returns 0 if //! chain only contains genesis block, nullopt if chain does not contain //! any blocks) - virtual Optional<int> getHeight() = 0; + virtual std::optional<int> getHeight() = 0; //! Get block hash. Height must be valid or this function will abort. virtual uint256 getBlockHash(int height) = 0; @@ -109,7 +109,7 @@ public: //! Return height of the highest block on chain in common with the locator, //! which will either be the original block used to create the locator, //! or one of its ancestors. - virtual Optional<int> findLocatorFork(const CBlockLocator& locator) = 0; + virtual std::optional<int> findLocatorFork(const CBlockLocator& locator) = 0; //! Check if transaction will be final given chain height current time. virtual bool checkFinalTx(const CTransaction& tx) = 0; @@ -154,11 +154,14 @@ public: //! Return true if data is available for all blocks in the specified range //! of blocks. This checks all blocks that are ancestors of block_hash in //! the height range from min_height to max_height, inclusive. - virtual bool hasBlocks(const uint256& block_hash, int min_height = 0, Optional<int> max_height = {}) = 0; + virtual bool hasBlocks(const uint256& block_hash, int min_height = 0, std::optional<int> max_height = {}) = 0; //! Check if transaction is RBF opt in. virtual RBFTransactionState isRBFOptIn(const CTransaction& tx) = 0; + //! Check if transaction is in mempool. + virtual bool isInMempool(const uint256& txid) = 0; + //! Check if transaction has descendants in mempool. virtual bool hasDescendantsInMempool(const uint256& txid) = 0; diff --git a/src/interfaces/handler.cpp b/src/interfaces/handler.cpp index 4134a4527f..c6bbbed445 100644 --- a/src/interfaces/handler.cpp +++ b/src/interfaces/handler.cpp @@ -4,7 +4,6 @@ #include <interfaces/handler.h> -#include <util/memory.h> #include <boost/signals2/connection.hpp> #include <utility> @@ -35,12 +34,12 @@ public: std::unique_ptr<Handler> MakeHandler(boost::signals2::connection connection) { - return MakeUnique<HandlerImpl>(std::move(connection)); + return std::make_unique<HandlerImpl>(std::move(connection)); } std::unique_ptr<Handler> MakeHandler(std::function<void()> cleanup) { - return MakeUnique<CleanupHandler>(std::move(cleanup)); + return std::make_unique<CleanupHandler>(std::move(cleanup)); } } // namespace interfaces diff --git a/src/interfaces/node.h b/src/interfaces/node.h index 15f7ef6256..1dd1e92e2f 100644 --- a/src/interfaces/node.h +++ b/src/interfaces/node.h @@ -6,9 +6,10 @@ #define BITCOIN_INTERFACES_NODE_H #include <amount.h> // For CAmount -#include <net.h> // For CConnman::NumConnections +#include <net.h> // For NodeId #include <net_types.h> // For banmap_t #include <netaddress.h> // For Network +#include <netbase.h> // For ConnectionDirection #include <support/allocators/secure.h> // For SecureString #include <util/translation.h> @@ -88,7 +89,7 @@ public: virtual bool getProxy(Network net, proxyType& proxy_info) = 0; //! Get number of connections. - virtual size_t getNodeCount(CConnman::NumConnections flags) = 0; + virtual size_t getNodeCount(ConnectionDirection flags) = 0; //! Get stats for connected nodes. using NodesStats = std::vector<std::tuple<CNodeStats, bool, CNodeStateStats>>; diff --git a/src/key_io.cpp b/src/key_io.cpp index e27673fd16..dbcbfa1f29 100644 --- a/src/key_io.cpp +++ b/src/key_io.cpp @@ -43,7 +43,7 @@ public: std::vector<unsigned char> data = {0}; data.reserve(33); ConvertBits<8, 5, true>([&](unsigned char c) { data.push_back(c); }, id.begin(), id.end()); - return bech32::Encode(m_params.Bech32HRP(), data); + return bech32::Encode(bech32::Encoding::BECH32, m_params.Bech32HRP(), data); } std::string operator()(const WitnessV0ScriptHash& id) const @@ -51,7 +51,7 @@ public: std::vector<unsigned char> data = {0}; data.reserve(53); ConvertBits<8, 5, true>([&](unsigned char c) { data.push_back(c); }, id.begin(), id.end()); - return bech32::Encode(m_params.Bech32HRP(), data); + return bech32::Encode(bech32::Encoding::BECH32, m_params.Bech32HRP(), data); } std::string operator()(const WitnessUnknown& id) const @@ -62,7 +62,7 @@ public: std::vector<unsigned char> data = {(unsigned char)id.version}; data.reserve(1 + (id.length * 8 + 4) / 5); ConvertBits<8, 5, true>([&](unsigned char c) { data.push_back(c); }, id.program, id.program + id.length); - return bech32::Encode(m_params.Bech32HRP(), data); + return bech32::Encode(bech32::Encoding::BECH32M, m_params.Bech32HRP(), data); } std::string operator()(const CNoDestination& no) const { return {}; } @@ -95,20 +95,26 @@ CTxDestination DecodeDestination(const std::string& str, const CChainParams& par error_str = "Invalid prefix for Base58-encoded address"; } data.clear(); - auto bech = bech32::Decode(str); - if (bech.second.size() > 0) { + const auto dec = bech32::Decode(str); + if ((dec.encoding == bech32::Encoding::BECH32 || dec.encoding == bech32::Encoding::BECH32M) && dec.data.size() > 0) { + // Bech32 decoding error_str = ""; - - if (bech.first != params.Bech32HRP()) { + if (dec.hrp != params.Bech32HRP()) { error_str = "Invalid prefix for Bech32 address"; return CNoDestination(); } - - // Bech32 decoding - int version = bech.second[0]; // The first 5 bit symbol is the witness version (0-16) + int version = dec.data[0]; // The first 5 bit symbol is the witness version (0-16) + if (version == 0 && dec.encoding != bech32::Encoding::BECH32) { + error_str = "Version 0 witness address must use Bech32 checksum"; + return CNoDestination(); + } + if (version != 0 && dec.encoding != bech32::Encoding::BECH32M) { + error_str = "Version 1+ witness address must use Bech32m checksum"; + return CNoDestination(); + } // The rest of the symbols are converted witness program bytes. - data.reserve(((bech.second.size() - 1) * 5) / 8); - if (ConvertBits<5, 8, false>([&](unsigned char c) { data.push_back(c); }, bech.second.begin() + 1, bech.second.end())) { + data.reserve(((dec.data.size() - 1) * 5) / 8); + if (ConvertBits<5, 8, false>([&](unsigned char c) { data.push_back(c); }, dec.data.begin() + 1, dec.data.end())) { if (version == 0) { { WitnessV0KeyHash keyid; diff --git a/src/logging.cpp b/src/logging.cpp index e82f2c2810..866213786e 100644 --- a/src/logging.cpp +++ b/src/logging.cpp @@ -156,6 +156,7 @@ const CLogCategoryDesc LogCategories[] = {BCLog::QT, "qt"}, {BCLog::LEVELDB, "leveldb"}, {BCLog::VALIDATION, "validation"}, + {BCLog::I2P, "i2p"}, {BCLog::ALL, "1"}, {BCLog::ALL, "all"}, }; diff --git a/src/logging.h b/src/logging.h index 4ece8f5e3a..436f0cd12e 100644 --- a/src/logging.h +++ b/src/logging.h @@ -57,6 +57,7 @@ namespace BCLog { QT = (1 << 19), LEVELDB = (1 << 20), VALIDATION = (1 << 21), + I2P = (1 << 22), ALL = ~(uint32_t)0, }; diff --git a/src/miner.cpp b/src/miner.cpp index 076d43c951..8a9406f810 100644 --- a/src/miner.cpp +++ b/src/miner.cpp @@ -39,13 +39,13 @@ int64_t UpdateTime(CBlockHeader* pblock, const Consensus::Params& consensusParam return nNewTime - nOldTime; } -void RegenerateCommitments(CBlock& block) +void RegenerateCommitments(CBlock& block, BlockManager& blockman) { CMutableTransaction tx{*block.vtx.at(0)}; tx.vout.erase(tx.vout.begin() + GetWitnessCommitmentIndex(block)); block.vtx.at(0) = MakeTransactionRef(tx); - GenerateCoinbaseCommitment(block, WITH_LOCK(cs_main, return g_chainman.m_blockman.LookupBlockIndex(block.hashPrevBlock)), Params().GetConsensus()); + GenerateCoinbaseCommitment(block, WITH_LOCK(::cs_main, assert(std::addressof(g_chainman.m_blockman) == std::addressof(blockman)); return blockman.LookupBlockIndex(block.hashPrevBlock)), Params().GetConsensus()); block.hashMerkleRoot = BlockMerkleRoot(block); } @@ -55,9 +55,10 @@ BlockAssembler::Options::Options() { nBlockMaxWeight = DEFAULT_BLOCK_MAX_WEIGHT; } -BlockAssembler::BlockAssembler(const CTxMemPool& mempool, const CChainParams& params, const Options& options) +BlockAssembler::BlockAssembler(CChainState& chainstate, const CTxMemPool& mempool, const CChainParams& params, const Options& options) : chainparams(params), - m_mempool(mempool) + m_mempool(mempool), + m_chainstate(chainstate) { blockMinFeeRate = options.blockMinFeeRate; // Limit weight to between 4K and MAX_BLOCK_WEIGHT-4K for sanity: @@ -79,8 +80,8 @@ static BlockAssembler::Options DefaultOptions() return options; } -BlockAssembler::BlockAssembler(const CTxMemPool& mempool, const CChainParams& params) - : BlockAssembler(mempool, params, DefaultOptions()) {} +BlockAssembler::BlockAssembler(CChainState& chainstate, const CTxMemPool& mempool, const CChainParams& params) + : BlockAssembler(chainstate, mempool, params, DefaultOptions()) {} void BlockAssembler::resetBlock() { @@ -96,9 +97,6 @@ void BlockAssembler::resetBlock() nFees = 0; } -Optional<int64_t> BlockAssembler::m_last_block_num_txs{nullopt}; -Optional<int64_t> BlockAssembler::m_last_block_weight{nullopt}; - std::unique_ptr<CBlockTemplate> BlockAssembler::CreateNewBlock(const CScript& scriptPubKeyIn) { int64_t nTimeStart = GetTimeMicros(); @@ -117,7 +115,8 @@ std::unique_ptr<CBlockTemplate> BlockAssembler::CreateNewBlock(const CScript& sc pblocktemplate->vTxSigOpsCost.push_back(-1); // updated at end LOCK2(cs_main, m_mempool.cs); - CBlockIndex* pindexPrev = ::ChainActive().Tip(); + assert(std::addressof(*::ChainActive().Tip()) == std::addressof(*m_chainstate.m_chain.Tip())); + CBlockIndex* pindexPrev = m_chainstate.m_chain.Tip(); assert(pindexPrev != nullptr); nHeight = pindexPrev->nHeight + 1; @@ -176,7 +175,8 @@ std::unique_ptr<CBlockTemplate> BlockAssembler::CreateNewBlock(const CScript& sc pblocktemplate->vTxSigOpsCost[0] = WITNESS_SCALE_FACTOR * GetLegacySigOpCount(*pblock->vtx[0]); BlockValidationState state; - if (!TestBlockValidity(state, chainparams, ::ChainstateActive(), *pblock, pindexPrev, false, false)) { + assert(std::addressof(::ChainstateActive()) == std::addressof(m_chainstate)); + if (!TestBlockValidity(state, chainparams, m_chainstate, *pblock, pindexPrev, false, false)) { throw std::runtime_error(strprintf("%s: TestBlockValidity failed: %s", __func__, state.ToString())); } int64_t nTime2 = GetTimeMicros(); diff --git a/src/miner.h b/src/miner.h index 9a2b7063f4..c400c90f6c 100644 --- a/src/miner.h +++ b/src/miner.h @@ -6,12 +6,12 @@ #ifndef BITCOIN_MINER_H #define BITCOIN_MINER_H -#include <optional.h> #include <primitives/block.h> #include <txmempool.h> #include <validation.h> #include <memory> +#include <optional> #include <stdint.h> #include <boost/multi_index_container.hpp> @@ -146,6 +146,7 @@ private: int64_t nLockTimeCutoff; const CChainParams& chainparams; const CTxMemPool& m_mempool; + CChainState& m_chainstate; public: struct Options { @@ -154,14 +155,14 @@ public: CFeeRate blockMinFeeRate; }; - explicit BlockAssembler(const CTxMemPool& mempool, const CChainParams& params); - explicit BlockAssembler(const CTxMemPool& mempool, const CChainParams& params, const Options& options); + explicit BlockAssembler(CChainState& chainstate, const CTxMemPool& mempool, const CChainParams& params); + explicit BlockAssembler(CChainState& chainstate, const CTxMemPool& mempool, const CChainParams& params, const Options& options); /** Construct a new block template with coinbase to scriptPubKeyIn */ std::unique_ptr<CBlockTemplate> CreateNewBlock(const CScript& scriptPubKeyIn); - static Optional<int64_t> m_last_block_num_txs; - static Optional<int64_t> m_last_block_weight; + inline static std::optional<int64_t> m_last_block_num_txs{}; + inline static std::optional<int64_t> m_last_block_weight{}; private: // utility functions @@ -201,7 +202,8 @@ private: void IncrementExtraNonce(CBlock* pblock, const CBlockIndex* pindexPrev, unsigned int& nExtraNonce); int64_t UpdateTime(CBlockHeader* pblock, const Consensus::Params& consensusParams, const CBlockIndex* pindexPrev); +// TODO just accept a CBlockIndex* /** Update an old GenerateCoinbaseCommitment from CreateNewBlock after the block txs have changed */ -void RegenerateCommitments(CBlock& block); +void RegenerateCommitments(CBlock& block, BlockManager& blockman); #endif // BITCOIN_MINER_H diff --git a/src/net.cpp b/src/net.cpp index d9571e7036..1dcb141421 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -11,12 +11,13 @@ #include <banman.h> #include <clientversion.h> +#include <compat.h> #include <consensus/consensus.h> #include <crypto/sha256.h> +#include <i2p.h> #include <net_permissions.h> #include <netbase.h> #include <node/ui_interface.h> -#include <optional.h> #include <protocol.h> #include <random.h> #include <scheduler.h> @@ -30,6 +31,10 @@ #include <fcntl.h> #endif +#if HAVE_DECL_GETIFADDRS && HAVE_DECL_FREEIFADDRS +#include <ifaddrs.h> +#endif + #ifdef USE_POLL #include <poll.h> #endif @@ -37,6 +42,7 @@ #include <algorithm> #include <cstdint> #include <functional> +#include <optional> #include <unordered_map> #include <math.h> @@ -72,16 +78,6 @@ static constexpr std::chrono::seconds MAX_UPLOAD_TIMEFRAME{60 * 60 * 24}; // We add a random period time (0 to 1 seconds) to feeler connections to prevent synchronization. #define FEELER_SLEEP_WINDOW 1 -// MSG_NOSIGNAL is not available on some platforms, if it doesn't exist define it as 0 -#if !defined(MSG_NOSIGNAL) -#define MSG_NOSIGNAL 0 -#endif - -// MSG_DONTWAIT is not available on some platforms, if it doesn't exist define it as 0 -#if !defined(MSG_DONTWAIT) -#define MSG_DONTWAIT 0 -#endif - /** Used to pass flags to the Bind() function */ enum BindFlags { BF_NONE = 0, @@ -121,7 +117,7 @@ void CConnman::AddAddrFetch(const std::string& strDest) uint16_t GetListenPort() { - return (uint16_t)(gArgs.GetArg("-port", Params().GetDefaultPort())); + return static_cast<uint16_t>(gArgs.GetArg("-port", Params().GetDefaultPort())); } // find 'best' local address for a particular peer @@ -201,7 +197,7 @@ bool IsPeerAddrLocalGood(CNode *pnode) IsReachable(addrLocal.GetNetwork()); } -Optional<CAddress> GetLocalAddrForPeer(CNode *pnode) +std::optional<CAddress> GetLocalAddrForPeer(CNode *pnode) { CAddress addrLocal = GetLocalAddress(&pnode->addr, pnode->GetLocalServices()); if (gArgs.GetBoolArg("-addrmantest", false)) { @@ -223,7 +219,7 @@ Optional<CAddress> GetLocalAddrForPeer(CNode *pnode) return addrLocal; } // Address is unroutable. Don't advertise. - return nullopt; + return std::nullopt; } // learn a new local address @@ -402,7 +398,7 @@ CNode* CConnman::ConnectNode(CAddress addrConnect, const char *pszDest, bool fCo pszDest ? 0.0 : (double)(GetAdjustedTime() - addrConnect.nTime)/3600.0); // Resolve - const int default_port = Params().GetDefaultPort(); + const uint16_t default_port{Params().GetDefaultPort()}; if (pszDest) { std::vector<CService> resolved; if (Lookup(pszDest, resolved, default_port, fNameLookup && !HaveNameProxy(), 256) && !resolved.empty()) { @@ -430,10 +426,20 @@ CNode* CConnman::ConnectNode(CAddress addrConnect, const char *pszDest, bool fCo bool connected = false; std::unique_ptr<Sock> sock; proxyType proxy; + CAddress addr_bind; + assert(!addr_bind.IsValid()); + if (addrConnect.IsValid()) { bool proxyConnectionFailed = false; - if (GetProxy(addrConnect.GetNetwork(), proxy)) { + if (addrConnect.GetNetwork() == NET_I2P && m_i2p_sam_session.get() != nullptr) { + i2p::Connection conn; + if (m_i2p_sam_session->Connect(addrConnect, conn, proxyConnectionFailed)) { + connected = true; + sock = std::move(conn.sock); + addr_bind = CAddress{conn.me, NODE_NONE}; + } + } else if (GetProxy(addrConnect.GetNetwork(), proxy)) { sock = CreateSock(proxy.proxy); if (!sock) { return nullptr; @@ -446,7 +452,7 @@ CNode* CConnman::ConnectNode(CAddress addrConnect, const char *pszDest, bool fCo if (!sock) { return nullptr; } - connected = ConnectSocketDirectly(addrConnect, sock->Get(), nConnectTimeout, + connected = ConnectSocketDirectly(addrConnect, *sock, nConnectTimeout, conn_type == ConnectionType::MANUAL); } if (!proxyConnectionFailed) { @@ -460,7 +466,7 @@ CNode* CConnman::ConnectNode(CAddress addrConnect, const char *pszDest, bool fCo return nullptr; } std::string host; - int port = default_port; + uint16_t port{default_port}; SplitHostPort(std::string(pszDest), port, host); bool proxyConnectionFailed; connected = ConnectThroughProxy(proxy, host, port, *sock, nConnectTimeout, @@ -473,7 +479,9 @@ CNode* CConnman::ConnectNode(CAddress addrConnect, const char *pszDest, bool fCo // Add node NodeId id = GetNewNodeId(); uint64_t nonce = GetDeterministicRandomizer(RANDOMIZER_ID_LOCALHOSTNONCE).Write(id).Finalize(); - CAddress addr_bind = GetBindAddress(sock->Get()); + if (!addr_bind.IsValid()) { + addr_bind = GetBindAddress(sock->Get()); + } CNode* pnode = new CNode(id, nLocalServices, sock->Release(), addrConnect, CalculateKeyedNetGroup(addrConnect), nonce, addr_bind, pszDest ? pszDest : "", conn_type, /* inbound_onion */ false); pnode->AddRef(); @@ -599,8 +607,8 @@ void CNode::copyStats(CNodeStats &stats, const std::vector<bool> &m_asmap) stats.minFeeFilter = 0; } - stats.m_ping_usec = m_last_ping_time; - stats.m_min_ping_usec = m_min_ping_time; + X(m_last_ping_time); + X(m_min_ping_time); // Leave string empty if addrLocal invalid (not filled in yet) CService addrLocalUnlocked = GetAddrLocal(); @@ -628,7 +636,7 @@ bool CNode::ReceiveMsgBytes(Span<const uint8_t> msg_bytes, bool& complete) if (m_deserializer->Complete()) { // decompose a transport agnostic CNetMessage from the deserializer uint32_t out_err_raw_size{0}; - Optional<CNetMessage> result{m_deserializer->GetMessage(time, out_err_raw_size)}; + std::optional<CNetMessage> result{m_deserializer->GetMessage(time, out_err_raw_size)}; if (!result) { // Message deserialization failed. Drop the message but don't disconnect the peer. // store the size of the corrupt message @@ -719,10 +727,10 @@ const uint256& V1TransportDeserializer::GetMessageHash() const return data_hash; } -Optional<CNetMessage> V1TransportDeserializer::GetMessage(const std::chrono::microseconds time, uint32_t& out_err_raw_size) +std::optional<CNetMessage> V1TransportDeserializer::GetMessage(const std::chrono::microseconds time, uint32_t& out_err_raw_size) { // decompose a single CNetMessage from the TransportDeserializer - Optional<CNetMessage> msg(std::move(vRecv)); + std::optional<CNetMessage> msg(std::move(vRecv)); // store command string, time, and sizes msg->m_command = hdr.GetCommand(); @@ -743,12 +751,12 @@ Optional<CNetMessage> V1TransportDeserializer::GetMessage(const std::chrono::mic HexStr(hdr.pchChecksum), m_node_id); out_err_raw_size = msg->m_raw_message_size; - msg = nullopt; + msg = std::nullopt; } else if (!hdr.IsCommandValid()) { LogPrint(BCLog::NET, "HEADER ERROR - COMMAND (%s, %u bytes), peer=%d\n", hdr.GetCommand(), msg->m_message_size, m_node_id); out_err_raw_size = msg->m_raw_message_size; - msg = nullopt; + msg.reset(); } // Always reset the network deserializer (prepare for the next message) @@ -836,6 +844,12 @@ static bool CompareLocalHostTimeConnected(const NodeEvictionCandidate &a, const return a.nTimeConnected > b.nTimeConnected; } +static bool CompareOnionTimeConnected(const NodeEvictionCandidate& a, const NodeEvictionCandidate& b) +{ + if (a.m_is_onion != b.m_is_onion) return b.m_is_onion; + return a.nTimeConnected > b.nTimeConnected; +} + static bool CompareNetGroupKeyed(const NodeEvictionCandidate &a, const NodeEvictionCandidate &b) { return a.nKeyedNetGroup < b.nKeyedNetGroup; } @@ -866,16 +880,54 @@ static bool CompareNodeBlockRelayOnlyTime(const NodeEvictionCandidate &a, const return a.nTimeConnected > b.nTimeConnected; } -//! Sort an array by the specified comparator, then erase the last K elements. -template<typename T, typename Comparator> -static void EraseLastKElements(std::vector<T> &elements, Comparator comparator, size_t k) +//! Sort an array by the specified comparator, then erase the last K elements where predicate is true. +template <typename T, typename Comparator> +static void EraseLastKElements( + std::vector<T>& elements, Comparator comparator, size_t k, + std::function<bool(const NodeEvictionCandidate&)> predicate = [](const NodeEvictionCandidate& n) { return true; }) { std::sort(elements.begin(), elements.end(), comparator); size_t eraseSize = std::min(k, elements.size()); - elements.erase(elements.end() - eraseSize, elements.end()); + elements.erase(std::remove_if(elements.end() - eraseSize, elements.end(), predicate), elements.end()); +} + +void ProtectEvictionCandidatesByRatio(std::vector<NodeEvictionCandidate>& vEvictionCandidates) +{ + // Protect the half of the remaining nodes which have been connected the longest. + // This replicates the non-eviction implicit behavior, and precludes attacks that start later. + // To favorise the diversity of our peer connections, reserve up to (half + 2) of + // these protected spots for onion and localhost peers, if any, even if they're not + // longest uptime overall. This helps protect tor peers, which tend to be otherwise + // disadvantaged under our eviction criteria. + const size_t initial_size = vEvictionCandidates.size(); + size_t total_protect_size = initial_size / 2; + const size_t onion_protect_size = total_protect_size / 2; + + if (onion_protect_size) { + // Pick out up to 1/4 peers connected via our onion service, sorted by longest uptime. + EraseLastKElements(vEvictionCandidates, CompareOnionTimeConnected, onion_protect_size, + [](const NodeEvictionCandidate& n) { return n.m_is_onion; }); + } + + const size_t localhost_min_protect_size{2}; + if (onion_protect_size >= localhost_min_protect_size) { + // Allocate any remaining slots of the 1/4, or minimum 2 additional slots, + // to localhost peers, sorted by longest uptime, as manually configured + // hidden services not using `-bind=addr[:port]=onion` will not be detected + // as inbound onion connections. + const size_t remaining_tor_slots{onion_protect_size - (initial_size - vEvictionCandidates.size())}; + const size_t localhost_protect_size{std::max(remaining_tor_slots, localhost_min_protect_size)}; + EraseLastKElements(vEvictionCandidates, CompareLocalHostTimeConnected, localhost_protect_size, + [](const NodeEvictionCandidate& n) { return n.m_is_local; }); + } + + // Calculate how many we removed, and update our total number of peers that + // we want to protect based on uptime accordingly. + total_protect_size -= initial_size - vEvictionCandidates.size(); + EraseLastKElements(vEvictionCandidates, ReverseCompareNodeTimeConnected, total_protect_size); } -[[nodiscard]] Optional<NodeId> SelectNodeToEvict(std::vector<NodeEvictionCandidate>&& vEvictionCandidates) +[[nodiscard]] std::optional<NodeId> SelectNodeToEvict(std::vector<NodeEvictionCandidate>&& vEvictionCandidates) { // Protect connections with certain characteristics @@ -889,32 +941,19 @@ static void EraseLastKElements(std::vector<T> &elements, Comparator comparator, // An attacker cannot manipulate this metric without performing useful work. EraseLastKElements(vEvictionCandidates, CompareNodeTXTime, 4); // Protect up to 8 non-tx-relay peers that have sent us novel blocks. - std::sort(vEvictionCandidates.begin(), vEvictionCandidates.end(), CompareNodeBlockRelayOnlyTime); - size_t erase_size = std::min(size_t(8), vEvictionCandidates.size()); - vEvictionCandidates.erase(std::remove_if(vEvictionCandidates.end() - erase_size, vEvictionCandidates.end(), [](NodeEvictionCandidate const &n) { return !n.fRelayTxes && n.fRelevantServices; }), vEvictionCandidates.end()); + const size_t erase_size = std::min(size_t(8), vEvictionCandidates.size()); + EraseLastKElements(vEvictionCandidates, CompareNodeBlockRelayOnlyTime, erase_size, + [](const NodeEvictionCandidate& n) { return !n.fRelayTxes && n.fRelevantServices; }); // Protect 4 nodes that most recently sent us novel blocks. // An attacker cannot manipulate this metric without performing useful work. EraseLastKElements(vEvictionCandidates, CompareNodeBlockTime, 4); - // Protect the half of the remaining nodes which have been connected the longest. - // This replicates the non-eviction implicit behavior, and precludes attacks that start later. - // Reserve half of these protected spots for localhost peers, even if - // they're not longest-uptime overall. This helps protect tor peers, which - // tend to be otherwise disadvantaged under our eviction criteria. - size_t initial_size = vEvictionCandidates.size(); - size_t total_protect_size = initial_size / 2; + // Protect some of the remaining eviction candidates by ratios of desirable + // or disadvantaged characteristics. + ProtectEvictionCandidatesByRatio(vEvictionCandidates); - // Pick out up to 1/4 peers that are localhost, sorted by longest uptime. - std::sort(vEvictionCandidates.begin(), vEvictionCandidates.end(), CompareLocalHostTimeConnected); - size_t local_erase_size = total_protect_size / 2; - vEvictionCandidates.erase(std::remove_if(vEvictionCandidates.end() - local_erase_size, vEvictionCandidates.end(), [](NodeEvictionCandidate const &n) { return n.m_is_local; }), vEvictionCandidates.end()); - // Calculate how many we removed, and update our total number of peers that - // we want to protect based on uptime accordingly. - total_protect_size -= initial_size - vEvictionCandidates.size(); - EraseLastKElements(vEvictionCandidates, ReverseCompareNodeTimeConnected, total_protect_size); - - if (vEvictionCandidates.empty()) return nullopt; + if (vEvictionCandidates.empty()) return std::nullopt; // If any remaining peers are preferred for eviction consider only them. // This happens after the other preferences since if a peer is really the best by other criteria (esp relaying blocks) @@ -933,7 +972,7 @@ static void EraseLastKElements(std::vector<T> &elements, Comparator comparator, for (const NodeEvictionCandidate &node : vEvictionCandidates) { std::vector<NodeEvictionCandidate> &group = mapNetGroupNodes[node.nKeyedNetGroup]; group.push_back(node); - int64_t grouptime = group[0].nTimeConnected; + const int64_t grouptime = group[0].nTimeConnected; if (group.size() > nMostConnections || (group.size() == nMostConnections && grouptime > nMostConnectionsTime)) { nMostConnections = group.size(); @@ -981,11 +1020,12 @@ bool CConnman::AttemptToEvictConnection() node->nLastBlockTime, node->nLastTXTime, HasAllDesirableServiceFlags(node->nServices), peer_relay_txes, peer_filter_not_null, node->nKeyedNetGroup, - node->m_prefer_evict, node->addr.IsLocal()}; + node->m_prefer_evict, node->addr.IsLocal(), + node->m_inbound_onion}; vEvictionCandidates.push_back(candidate); } } - const Optional<NodeId> node_id_to_evict = SelectNodeToEvict(std::move(vEvictionCandidates)); + const std::optional<NodeId> node_id_to_evict = SelectNodeToEvict(std::move(vEvictionCandidates)); if (!node_id_to_evict) { return false; } @@ -1005,17 +1045,35 @@ void CConnman::AcceptConnection(const ListenSocket& hListenSocket) { socklen_t len = sizeof(sockaddr); SOCKET hSocket = accept(hListenSocket.socket, (struct sockaddr*)&sockaddr, &len); CAddress addr; - int nInbound = 0; - int nMaxInbound = nMaxConnections - m_max_outbound; - if (hSocket != INVALID_SOCKET) { - if (!addr.SetSockAddr((const struct sockaddr*)&sockaddr)) { - LogPrintf("Warning: Unknown socket family\n"); + if (hSocket == INVALID_SOCKET) { + const int nErr = WSAGetLastError(); + if (nErr != WSAEWOULDBLOCK) { + LogPrintf("socket error accept failed: %s\n", NetworkErrorString(nErr)); } + return; } + if (!addr.SetSockAddr((const struct sockaddr*)&sockaddr)) { + LogPrintf("Warning: Unknown socket family\n"); + } + + const CAddress addr_bind = GetBindAddress(hSocket); + NetPermissionFlags permissionFlags = NetPermissionFlags::PF_NONE; hListenSocket.AddSocketPermissionFlags(permissionFlags); + + CreateNodeFromAcceptedSocket(hSocket, permissionFlags, addr_bind, addr); +} + +void CConnman::CreateNodeFromAcceptedSocket(SOCKET hSocket, + NetPermissionFlags permissionFlags, + const CAddress& addr_bind, + const CAddress& addr) +{ + int nInbound = 0; + int nMaxInbound = nMaxConnections - m_max_outbound; + AddWhitelistPermissionFlags(permissionFlags, addr); if (NetPermissions::HasFlag(permissionFlags, NetPermissionFlags::PF_ISIMPLICIT)) { NetPermissions::ClearFlag(permissionFlags, PF_ISIMPLICIT); @@ -1032,14 +1090,6 @@ void CConnman::AcceptConnection(const ListenSocket& hListenSocket) { } } - if (hSocket == INVALID_SOCKET) - { - int nErr = WSAGetLastError(); - if (nErr != WSAEWOULDBLOCK) - LogPrintf("socket error accept failed: %s\n", NetworkErrorString(nErr)); - return; - } - if (!fNetworkActive) { LogPrint(BCLog::NET, "connection from %s dropped: not accepting new connections\n", addr.ToString()); CloseSocket(hSocket); @@ -1087,7 +1137,6 @@ void CConnman::AcceptConnection(const ListenSocket& hListenSocket) { NodeId id = GetNewNodeId(); uint64_t nonce = GetDeterministicRandomizer(RANDOMIZER_ID_LOCALHOSTNONCE).Write(id).Finalize(); - CAddress addr_bind = GetBindAddress(hSocket); ServiceFlags nodeServices = nLocalServices; if (NetPermissions::HasFlag(permissionFlags, PF_BLOOMFILTER)) { @@ -1748,12 +1797,11 @@ void CConnman::ThreadOpenConnections(const std::vector<std::string> connect) } // Initiate network connections - auto start = GetTime<std::chrono::seconds>(); + auto start = GetTime<std::chrono::microseconds>(); // Minimum time before next feeler connection (in microseconds). - - int64_t nNextFeeler = PoissonNextSend(count_microseconds(start), FEELER_INTERVAL); - int64_t nNextExtraBlockRelay = PoissonNextSend(count_microseconds(start), EXTRA_BLOCK_RELAY_ONLY_PEER_INTERVAL); + auto next_feeler = PoissonNextSend(start, FEELER_INTERVAL); + auto next_extra_block_relay = PoissonNextSend(start, EXTRA_BLOCK_RELAY_ONLY_PEER_INTERVAL); const bool dnsseed = gArgs.GetBoolArg("-dnsseed", DEFAULT_DNSSEED); bool add_fixed_seeds = gArgs.GetBoolArg("-fixedseeds", DEFAULT_FIXEDSEEDS); @@ -1836,7 +1884,7 @@ void CConnman::ThreadOpenConnections(const std::vector<std::string> connect) } ConnectionType conn_type = ConnectionType::OUTBOUND_FULL_RELAY; - int64_t nTime = GetTimeMicros(); + auto now = GetTime<std::chrono::microseconds>(); bool anchor = false; bool fFeeler = false; @@ -1848,7 +1896,7 @@ void CConnman::ThreadOpenConnections(const std::vector<std::string> connect) // GetTryNewOutboundPeer() gets set when a stale tip is detected, so we // try opening an additional OUTBOUND_FULL_RELAY connection. If none of // these conditions are met, check to see if it's time to try an extra - // block-relay-only peer (to confirm our tip is current, see below) or the nNextFeeler + // block-relay-only peer (to confirm our tip is current, see below) or the next_feeler // timer to decide if we should open a FEELER. if (!m_anchors.empty() && (nOutboundBlockRelay < m_max_outbound_block_relay)) { @@ -1860,7 +1908,7 @@ void CConnman::ThreadOpenConnections(const std::vector<std::string> connect) conn_type = ConnectionType::BLOCK_RELAY; } else if (GetTryNewOutboundPeer()) { // OUTBOUND_FULL_RELAY - } else if (nTime > nNextExtraBlockRelay && m_start_extra_block_relay_peers) { + } else if (now > next_extra_block_relay && m_start_extra_block_relay_peers) { // Periodically connect to a peer (using regular outbound selection // methodology from addrman) and stay connected long enough to sync // headers, but not much else. @@ -1882,10 +1930,10 @@ void CConnman::ThreadOpenConnections(const std::vector<std::string> connect) // Because we can promote these connections to block-relay-only // connections, they do not get their own ConnectionType enum // (similar to how we deal with extra outbound peers). - nNextExtraBlockRelay = PoissonNextSend(nTime, EXTRA_BLOCK_RELAY_ONLY_PEER_INTERVAL); + next_extra_block_relay = PoissonNextSend(now, EXTRA_BLOCK_RELAY_ONLY_PEER_INTERVAL); conn_type = ConnectionType::BLOCK_RELAY; - } else if (nTime > nNextFeeler) { - nNextFeeler = PoissonNextSend(nTime, FEELER_INTERVAL); + } else if (now > next_feeler) { + next_feeler = PoissonNextSend(now, FEELER_INTERVAL); conn_type = ConnectionType::FEELER; fFeeler = true; } else { @@ -2175,6 +2223,45 @@ void CConnman::ThreadMessageHandler() } } +void CConnman::ThreadI2PAcceptIncoming() +{ + static constexpr auto err_wait_begin = 1s; + static constexpr auto err_wait_cap = 5min; + auto err_wait = err_wait_begin; + + bool advertising_listen_addr = false; + i2p::Connection conn; + + while (!interruptNet) { + + if (!m_i2p_sam_session->Listen(conn)) { + if (advertising_listen_addr && conn.me.IsValid()) { + RemoveLocal(conn.me); + advertising_listen_addr = false; + } + + interruptNet.sleep_for(err_wait); + if (err_wait < err_wait_cap) { + err_wait *= 2; + } + + continue; + } + + if (!advertising_listen_addr) { + AddLocal(conn.me, LOCAL_BIND); + advertising_listen_addr = true; + } + + if (!m_i2p_sam_session->Accept(conn)) { + continue; + } + + CreateNodeFromAcceptedSocket(conn.sock->Release(), NetPermissionFlags::PF_NONE, + CAddress{conn.me, NODE_NONE}, CAddress{conn.peer, NODE_NONE}); + } +} + bool CConnman::BindListenPort(const CService& addrBind, bilingual_str& strError, NetPermissionFlags permissions) { int nOne = 1; @@ -2300,8 +2387,8 @@ void CConnman::SetNetworkActive(bool active) uiInterface.NotifyNetworkActiveChanged(fNetworkActive); } -CConnman::CConnman(uint64_t nSeed0In, uint64_t nSeed1In, bool network_active) - : nSeed0(nSeed0In), nSeed1(nSeed1In) +CConnman::CConnman(uint64_t nSeed0In, uint64_t nSeed1In, CAddrMan& addrman_in, bool network_active) + : addrman(addrman_in), nSeed0(nSeed0In), nSeed1(nSeed1In) { SetTryNewOutboundPeer(false); @@ -2374,6 +2461,12 @@ bool CConnman::Start(CScheduler& scheduler, const Options& connOptions) return false; } + proxyType i2p_sam; + if (GetProxy(NET_I2P, i2p_sam)) { + m_i2p_sam_session = std::make_unique<i2p::sam::Session>(GetDataDir() / "i2p_private_key", + i2p_sam.proxy, &interruptNet); + } + for (const auto& strDest : connOptions.vSeedNodes) { AddAddrFetch(strDest); } @@ -2409,11 +2502,11 @@ bool CConnman::Start(CScheduler& scheduler, const Options& connOptions) if (semOutbound == nullptr) { // initialize semaphore - semOutbound = MakeUnique<CSemaphore>(std::min(m_max_outbound, nMaxConnections)); + semOutbound = std::make_unique<CSemaphore>(std::min(m_max_outbound, nMaxConnections)); } if (semAddnode == nullptr) { // initialize semaphore - semAddnode = MakeUnique<CSemaphore>(nMaxAddnode); + semAddnode = std::make_unique<CSemaphore>(nMaxAddnode); } // @@ -2454,6 +2547,12 @@ bool CConnman::Start(CScheduler& scheduler, const Options& connOptions) // Process messages threadMessageHandler = std::thread(&TraceThread<std::function<void()> >, "msghand", std::function<void()>(std::bind(&CConnman::ThreadMessageHandler, this))); + if (connOptions.m_i2p_accept_incoming && m_i2p_sam_session.get() != nullptr) { + threadI2PAcceptIncoming = + std::thread(&TraceThread<std::function<void()>>, "i2paccept", + std::function<void()>(std::bind(&CConnman::ThreadI2PAcceptIncoming, this))); + } + // Dump network addresses scheduler.scheduleEvery([this] { DumpAddresses(); }, DUMP_PEERS_INTERVAL); @@ -2501,6 +2600,9 @@ void CConnman::Interrupt() void CConnman::StopThreads() { + if (threadI2PAcceptIncoming.joinable()) { + threadI2PAcceptIncoming.join(); + } if (threadMessageHandler.joinable()) threadMessageHandler.join(); if (threadOpenConnections.joinable()) @@ -2555,11 +2657,7 @@ void CConnman::StopNodes() void CConnman::DeleteNode(CNode* pnode) { assert(pnode); - bool fUpdateConnectionTime = false; - m_msgproc->FinalizeNode(*pnode, fUpdateConnectionTime); - if (fUpdateConnectionTime) { - addrman.Connected(pnode->addr); - } + m_msgproc->FinalizeNode(*pnode); delete pnode; } @@ -2569,21 +2667,6 @@ CConnman::~CConnman() Stop(); } -void CConnman::SetServices(const CService &addr, ServiceFlags nServices) -{ - addrman.SetServices(addr, nServices); -} - -void CConnman::MarkAddressGood(const CAddress& addr) -{ - addrman.Good(addr); -} - -bool CConnman::AddNewAddresses(const std::vector<CAddress>& vAddr, const CAddress& addrFrom, int64_t nTimePenalty) -{ - return addrman.Add(vAddr, addrFrom, nTimePenalty); -} - std::vector<CAddress> CConnman::GetAddresses(size_t max_addresses, size_t max_pct) { std::vector<CAddress> addresses = addrman.GetAddr(max_addresses, max_pct); @@ -2597,9 +2680,7 @@ std::vector<CAddress> CConnman::GetAddresses(size_t max_addresses, size_t max_pc std::vector<CAddress> CConnman::GetAddresses(CNode& requestor, size_t max_addresses, size_t max_pct) { - SOCKET socket; - WITH_LOCK(requestor.cs_hSocket, socket = requestor.hSocket); - auto local_socket_bytes = GetBindAddress(socket).GetAddrBytes(); + auto local_socket_bytes = requestor.addrBind.GetAddrBytes(); uint64_t cache_id = GetDeterministicRandomizer(RANDOMIZER_ID_ADDRCACHE) .Write(requestor.addr.GetNetwork()) .Write(local_socket_bytes.data(), local_socket_bytes.size()) @@ -2661,15 +2742,15 @@ bool CConnman::RemoveAddedNode(const std::string& strNode) return false; } -size_t CConnman::GetNodeCount(NumConnections flags) +size_t CConnman::GetNodeCount(ConnectionDirection flags) { LOCK(cs_vNodes); - if (flags == CConnman::CONNECTIONS_ALL) // Shortcut if we want total + if (flags == ConnectionDirection::Both) // Shortcut if we want total return vNodes.size(); int nNum = 0; for (const auto& pnode : vNodes) { - if (flags & (pnode->IsInboundConn() ? CONNECTIONS_IN : CONNECTIONS_OUT)) { + if (flags & (pnode->IsInboundConn() ? ConnectionDirection::In : ConnectionDirection::Out)) { nNum++; } } @@ -2842,11 +2923,11 @@ CNode::CNode(NodeId idIn, ServiceFlags nLocalServicesIn, SOCKET hSocketIn, const hSocket = hSocketIn; addrName = addrNameIn == "" ? addr.ToStringIPPort() : addrNameIn; if (conn_type_in != ConnectionType::BLOCK_RELAY) { - m_tx_relay = MakeUnique<TxRelay>(); + m_tx_relay = std::make_unique<TxRelay>(); } if (RelayAddrsWithConn()) { - m_addr_known = MakeUnique<CRollingBloomFilter>(5000, 0.001); + m_addr_known = std::make_unique<CRollingBloomFilter>(5000, 0.001); } for (const std::string &msg : getAllNetMessageTypes()) @@ -2859,8 +2940,8 @@ CNode::CNode(NodeId idIn, ServiceFlags nLocalServicesIn, SOCKET hSocketIn, const LogPrint(BCLog::NET, "Added connection peer=%d\n", id); } - m_deserializer = MakeUnique<V1TransportDeserializer>(V1TransportDeserializer(Params(), GetId(), SER_NETWORK, INIT_PROTO_VERSION)); - m_serializer = MakeUnique<V1TransportSerializer>(V1TransportSerializer()); + m_deserializer = std::make_unique<V1TransportDeserializer>(V1TransportDeserializer(Params(), GetId(), SER_NETWORK, INIT_PROTO_VERSION)); + m_serializer = std::make_unique<V1TransportSerializer>(V1TransportSerializer()); } CNode::~CNode() @@ -2918,20 +2999,21 @@ bool CConnman::ForNode(NodeId id, std::function<bool(CNode* pnode)> func) return found != nullptr && NodeFullyConnected(found) && func(found); } -int64_t CConnman::PoissonNextSendInbound(int64_t now, int average_interval_seconds) +std::chrono::microseconds CConnman::PoissonNextSendInbound(std::chrono::microseconds now, std::chrono::seconds average_interval) { - if (m_next_send_inv_to_incoming < now) { + if (m_next_send_inv_to_incoming.load() < now) { // If this function were called from multiple threads simultaneously // it would possible that both update the next send variable, and return a different result to their caller. // This is not possible in practice as only the net processing thread invokes this function. - m_next_send_inv_to_incoming = PoissonNextSend(now, average_interval_seconds); + m_next_send_inv_to_incoming = PoissonNextSend(now, average_interval); } return m_next_send_inv_to_incoming; } -int64_t PoissonNextSend(int64_t now, int average_interval_seconds) +std::chrono::microseconds PoissonNextSend(std::chrono::microseconds now, std::chrono::seconds average_interval) { - return now + (int64_t)(log1p(GetRand(1ULL << 48) * -0.0000000000000035527136788 /* -1/2^48 */) * average_interval_seconds * -1000000.0 + 0.5); + double unscaled = -log1p(GetRand(1ULL << 48) * -0.0000000000000035527136788 /* -1/2^48 */); + return now + std::chrono::duration_cast<std::chrono::microseconds>(unscaled * average_interval + 0.5us); } CSipHasher CConnman::GetDeterministicRandomizer(uint64_t id) const @@ -14,9 +14,10 @@ #include <compat.h> #include <crypto/siphash.h> #include <hash.h> +#include <i2p.h> #include <net_permissions.h> #include <netaddress.h> -#include <optional.h> +#include <netbase.h> #include <policy/feerate.h> #include <protocol.h> #include <random.h> @@ -33,6 +34,7 @@ #include <deque> #include <map> #include <memory> +#include <optional> #include <thread> #include <vector> @@ -48,10 +50,10 @@ static const bool DEFAULT_WHITELISTFORCERELAY = false; /** Time after which to disconnect, after waiting for a ping response (or inactivity). */ static const int TIMEOUT_INTERVAL = 20 * 60; -/** Run the feeler connection loop once every 2 minutes or 120 seconds. **/ -static const int FEELER_INTERVAL = 120; +/** Run the feeler connection loop once every 2 minutes. **/ +static constexpr auto FEELER_INTERVAL = 2min; /** Run the extra block-relay-only connection loop once every 5 minutes. **/ -static const int EXTRA_BLOCK_RELAY_ONLY_PEER_INTERVAL = 300; +static constexpr auto EXTRA_BLOCK_RELAY_ONLY_PEER_INTERVAL = 5min; /** The maximum number of addresses from our addrman to return in response to a getaddr message. */ static constexpr size_t MAX_ADDR_TO_SEND = 1000; /** Maximum length of incoming protocol messages (no message over 4 MB is currently acceptable). */ @@ -198,7 +200,7 @@ enum bool IsPeerAddrLocalGood(CNode *pnode); /** Returns a local address that we should advertise to this peer */ -Optional<CAddress> GetLocalAddrForPeer(CNode *pnode); +std::optional<CAddress> GetLocalAddrForPeer(CNode *pnode); /** * Mark a network as reachable or unreachable (no automatic connects to it) @@ -227,7 +229,7 @@ extern std::string strSubVersion; struct LocalServiceInfo { int nScore; - int nPort; + uint16_t nPort; }; extern RecursiveMutex cs_mapLocalHost; @@ -260,8 +262,8 @@ public: uint64_t nRecvBytes; mapMsgCmdSize mapRecvBytesPerMsgCmd; NetPermissionFlags m_permissionFlags; - int64_t m_ping_usec; - int64_t m_min_ping_usec; + std::chrono::microseconds m_last_ping_time; + std::chrono::microseconds m_min_ping_time; CAmount minFeeFilter; // Our address, as reported by the peer std::string addrLocal; @@ -309,7 +311,7 @@ public: /** read and deserialize data, advances msg_bytes data pointer */ virtual int Read(Span<const uint8_t>& msg_bytes) = 0; // decomposes a message from the context - virtual Optional<CNetMessage> GetMessage(std::chrono::microseconds time, uint32_t& out_err) = 0; + virtual std::optional<CNetMessage> GetMessage(std::chrono::microseconds time, uint32_t& out_err) = 0; virtual ~TransportDeserializer() {} }; @@ -373,7 +375,7 @@ public: } return ret; } - Optional<CNetMessage> GetMessage(std::chrono::microseconds time, uint32_t& out_err_raw_size) override; + std::optional<CNetMessage> GetMessage(std::chrono::microseconds time, uint32_t& out_err_raw_size) override; }; /** The TransportSerializer prepares messages for the network transport @@ -423,6 +425,7 @@ public: std::atomic<int64_t> nLastSend{0}; std::atomic<int64_t> nLastRecv{0}; + //! Unix epoch time at peer connection, in seconds. const int64_t nTimeConnected; std::atomic<int64_t> nTimeOffset{0}; // Address of this peer @@ -546,8 +549,9 @@ public: std::vector<CAddress> vAddrToSend; std::unique_ptr<CRollingBloomFilter> m_addr_known{nullptr}; bool fGetAddr{false}; - std::chrono::microseconds m_next_addr_send GUARDED_BY(cs_sendProcessing){0}; - std::chrono::microseconds m_next_local_addr_send GUARDED_BY(cs_sendProcessing){0}; + Mutex m_addr_send_times_mutex; + std::chrono::microseconds m_next_addr_send GUARDED_BY(m_addr_send_times_mutex){0}; + std::chrono::microseconds m_next_local_addr_send GUARDED_BY(m_addr_send_times_mutex){0}; struct TxRelay { mutable RecursiveMutex cs_filter; @@ -572,7 +576,7 @@ public: /** Minimum fee rate with which to filter inv's to this node */ std::atomic<CAmount> minFeeFilter{0}; CAmount lastSentFeeFilter{0}; - int64_t nextSendTimeFeeFilter{0}; + std::chrono::microseconds m_next_send_feefilter{0}; }; // m_tx_relay == nullptr if we're not relaying transactions with this peer @@ -592,11 +596,11 @@ public: std::atomic<int64_t> nLastTXTime{0}; /** Last measured round-trip time. Used only for RPC/GUI stats/debugging.*/ - std::atomic<int64_t> m_last_ping_time{0}; + std::atomic<std::chrono::microseconds> m_last_ping_time{0us}; /** Lowest measured round-trip time. Used as an inbound peer eviction * criterium in CConnman::AttemptToEvictConnection. */ - std::atomic<int64_t> m_min_ping_time{std::numeric_limits<int64_t>::max()}; + std::atomic<std::chrono::microseconds> m_min_ping_time{std::chrono::microseconds::max()}; CNode(NodeId id, ServiceFlags nLocalServicesIn, SOCKET hSocketIn, const CAddress& addrIn, uint64_t nKeyedNetGroupIn, uint64_t nLocalHostNonceIn, const CAddress& addrBindIn, const std::string& addrNameIn, ConnectionType conn_type_in, bool inbound_onion); ~CNode(); @@ -718,8 +722,8 @@ public: /** A ping-pong round trip has completed successfully. Update latest and minimum ping times. */ void PongReceived(std::chrono::microseconds ping_time) { - m_last_ping_time = count_microseconds(ping_time); - m_min_ping_time = std::min(m_min_ping_time.load(), count_microseconds(ping_time)); + m_last_ping_time = ping_time; + m_min_ping_time = std::min(m_min_ping_time.load(), ping_time); } private: @@ -768,7 +772,7 @@ public: virtual void InitializeNode(CNode* pnode) = 0; /** Handle removal of a peer (clear state) */ - virtual void FinalizeNode(const CNode& node, bool& update_connection_time) = 0; + virtual void FinalizeNode(const CNode& node) = 0; /** * Process protocol messages received from a given node @@ -800,13 +804,6 @@ class CConnman { public: - enum NumConnections { - CONNECTIONS_NONE = 0, - CONNECTIONS_IN = (1U << 0), - CONNECTIONS_OUT = (1U << 1), - CONNECTIONS_ALL = (CONNECTIONS_IN | CONNECTIONS_OUT), - }; - struct Options { ServiceFlags nLocalServices = NODE_NONE; @@ -831,6 +828,7 @@ public: std::vector<std::string> m_specified_outgoing; std::vector<std::string> m_added_nodes; std::vector<bool> m_asmap; + bool m_i2p_accept_incoming; }; void Init(const Options& connOptions) { @@ -860,7 +858,7 @@ public: m_onion_binds = connOptions.onion_binds; } - CConnman(uint64_t seed0, uint64_t seed1, bool network_active = true); + CConnman(uint64_t seed0, uint64_t seed1, CAddrMan& addrman, bool network_active = true); ~CConnman(); bool Start(CScheduler& scheduler, const Options& options); @@ -925,9 +923,6 @@ public: }; // Addrman functions - void SetServices(const CService &addr, ServiceFlags nServices); - void MarkAddressGood(const CAddress& addr); - bool AddNewAddresses(const std::vector<CAddress>& vAddr, const CAddress& addrFrom, int64_t nTimePenalty = 0); std::vector<CAddress> GetAddresses(size_t max_addresses, size_t max_pct); /** * Cache is used to minimize topology leaks, so it should @@ -974,7 +969,7 @@ public: */ bool AddConnection(const std::string& address, ConnectionType conn_type); - size_t GetNodeCount(NumConnections num); + size_t GetNodeCount(ConnectionDirection); void GetNodeStats(std::vector<CNodeStats>& vstats); bool DisconnectNode(const std::string& node); bool DisconnectNode(const CSubNet& subnet); @@ -1019,7 +1014,7 @@ public: Works assuming that a single interval is used. Variable intervals will result in privacy decrease. */ - int64_t PoissonNextSendInbound(int64_t now, int average_interval_seconds); + std::chrono::microseconds PoissonNextSendInbound(std::chrono::microseconds now, std::chrono::seconds average_interval); void SetAsmap(std::vector<bool> asmap) { addrman.m_asmap = std::move(asmap); } @@ -1048,7 +1043,22 @@ private: void ProcessAddrFetch(); void ThreadOpenConnections(std::vector<std::string> connect); void ThreadMessageHandler(); + void ThreadI2PAcceptIncoming(); void AcceptConnection(const ListenSocket& hListenSocket); + + /** + * Create a `CNode` object from a socket that has just been accepted and add the node to + * the `vNodes` member. + * @param[in] hSocket Connected socket to communicate with the peer. + * @param[in] permissionFlags The peer's permissions. + * @param[in] addr_bind The address and port at our side of the connection. + * @param[in] addr The address and port at the peer's side of the connection. + */ + void CreateNodeFromAcceptedSocket(SOCKET hSocket, + NetPermissionFlags permissionFlags, + const CAddress& addr_bind, + const CAddress& addr); + void DisconnectNodes(); void NotifyNumConnectionsChanged(); /** Return true if the peer is inactive and should be disconnected. */ @@ -1119,7 +1129,7 @@ private: std::vector<ListenSocket> vhListenSocket; std::atomic<bool> fNetworkActive{true}; bool fAddressesInitialized{false}; - CAddrMan addrman; + CAddrMan& addrman; std::deque<std::string> m_addr_fetches GUARDED_BY(m_addr_fetches_mutex); RecursiveMutex m_addr_fetches_mutex; std::vector<std::string> vAddedNodes GUARDED_BY(cs_vAddedNodes); @@ -1207,13 +1217,26 @@ private: Mutex mutexMsgProc; std::atomic<bool> flagInterruptMsgProc{false}; + /** + * This is signaled when network activity should cease. + * A pointer to it is saved in `m_i2p_sam_session`, so make sure that + * the lifetime of `interruptNet` is not shorter than + * the lifetime of `m_i2p_sam_session`. + */ CThreadInterrupt interruptNet; + /** + * I2P SAM session. + * Used to accept incoming and make outgoing I2P connections. + */ + std::unique_ptr<i2p::sam::Session> m_i2p_sam_session; + std::thread threadDNSAddressSeed; std::thread threadSocketHandler; std::thread threadOpenAddedConnections; std::thread threadOpenConnections; std::thread threadMessageHandler; + std::thread threadI2PAcceptIncoming; /** flag for deciding to connect to an extra outbound peer, * in excess of m_max_outbound_full_relay @@ -1226,7 +1249,7 @@ private: */ std::atomic_bool m_start_extra_block_relay_peers{false}; - std::atomic<int64_t> m_next_send_inv_to_incoming{0}; + std::atomic<std::chrono::microseconds> m_next_send_inv_to_incoming{0us}; /** * A vector of -bind=<address>:<port>=onion arguments each of which is @@ -1239,13 +1262,7 @@ private: }; /** Return a timestamp in the future (in microseconds) for exponentially distributed events. */ -int64_t PoissonNextSend(int64_t now, int average_interval_seconds); - -/** Wrapper to return mockable type */ -inline std::chrono::microseconds PoissonNextSend(std::chrono::microseconds now, std::chrono::seconds average_interval) -{ - return std::chrono::microseconds{PoissonNextSend(now.count(), average_interval.count())}; -} +std::chrono::microseconds PoissonNextSend(std::chrono::microseconds now, std::chrono::seconds average_interval); /** Dump binary message to file, with timestamp */ void CaptureMessage(const CAddress& addr, const std::string& msg_type, const Span<const unsigned char>& data, bool is_incoming); @@ -1254,7 +1271,7 @@ struct NodeEvictionCandidate { NodeId id; int64_t nTimeConnected; - int64_t m_min_ping_time; + std::chrono::microseconds m_min_ping_time; int64_t nLastBlockTime; int64_t nLastTXTime; bool fRelevantServices; @@ -1263,8 +1280,39 @@ struct NodeEvictionCandidate uint64_t nKeyedNetGroup; bool prefer_evict; bool m_is_local; + bool m_is_onion; }; -[[nodiscard]] Optional<NodeId> SelectNodeToEvict(std::vector<NodeEvictionCandidate>&& vEvictionCandidates); +/** + * Select an inbound peer to evict after filtering out (protecting) peers having + * distinct, difficult-to-forge characteristics. The protection logic picks out + * fixed numbers of desirable peers per various criteria, followed by (mostly) + * ratios of desirable or disadvantaged peers. If any eviction candidates + * remain, the selection logic chooses a peer to evict. + */ +[[nodiscard]] std::optional<NodeId> SelectNodeToEvict(std::vector<NodeEvictionCandidate>&& vEvictionCandidates); + +/** Protect desirable or disadvantaged inbound peers from eviction by ratio. + * + * This function protects half of the peers which have been connected the + * longest, to replicate the non-eviction implicit behavior and preclude attacks + * that start later. + * + * Half of these protected spots (1/4 of the total) are reserved for onion peers + * connected via our tor control service, if any, sorted by longest uptime, even + * if they're not longest uptime overall. Any remaining slots of the 1/4 are + * then allocated to protect localhost peers, if any (or up to 2 localhost peers + * if no slots remain and 2 or more onion peers were protected), sorted by + * longest uptime, as manually configured hidden services not using + * `-bind=addr[:port]=onion` will not be detected as inbound onion connections. + * + * This helps protect onion peers, which tend to be otherwise disadvantaged + * under our eviction criteria for their higher min ping times relative to IPv4 + * and IPv6 peers, and favorise the diversity of peer connections. + * + * This function was extracted from SelectNodeToEvict() to be able to test the + * ratio-based protection logic deterministically. + */ +void ProtectEvictionCandidatesByRatio(std::vector<NodeEvictionCandidate>& vEvictionCandidates); #endif // BITCOIN_NET_H diff --git a/src/net_processing.cpp b/src/net_processing.cpp index c97f7ced46..17d7619ff5 100644 --- a/src/net_processing.cpp +++ b/src/net_processing.cpp @@ -26,27 +26,26 @@ #include <streams.h> #include <tinyformat.h> #include <txmempool.h> +#include <txorphanage.h> #include <txrequest.h> #include <util/check.h> // For NDEBUG compile time check #include <util/strencodings.h> #include <util/system.h> #include <validation.h> +#include <algorithm> #include <memory> +#include <optional> #include <typeinfo> -/** Expiration time for orphan transactions in seconds */ -static constexpr int64_t ORPHAN_TX_EXPIRE_TIME = 20 * 60; -/** Minimum time between orphan transactions expire time checks in seconds */ -static constexpr int64_t ORPHAN_TX_EXPIRE_INTERVAL = 5 * 60; /** How long to cache transactions in mapRelay for normal relay */ -static constexpr std::chrono::seconds RELAY_TX_CACHE_TIME = std::chrono::minutes{15}; +static constexpr auto RELAY_TX_CACHE_TIME = 15min; /** How long a transaction has to be in the mempool before it can unconditionally be relayed (even when not in mapRelay). */ -static constexpr std::chrono::seconds UNCONDITIONAL_RELAY_DELAY = std::chrono::minutes{2}; -/** Headers download timeout expressed in microseconds +static constexpr auto UNCONDITIONAL_RELAY_DELAY = 2min; +/** Headers download timeout. * Timeout = base + per_header * (expected number of headers) */ -static constexpr int64_t HEADERS_DOWNLOAD_TIMEOUT_BASE = 15 * 60 * 1000000; // 15 minutes -static constexpr int64_t HEADERS_DOWNLOAD_TIMEOUT_PER_HEADER = 1000; // 1ms/header +static constexpr auto HEADERS_DOWNLOAD_TIMEOUT_BASE = 15min; +static constexpr auto HEADERS_DOWNLOAD_TIMEOUT_PER_HEADER = 1ms; /** Protect at least this many outbound peers from disconnection due to slow/ * behind headers chain. */ @@ -93,8 +92,8 @@ static constexpr std::chrono::microseconds GETDATA_TX_INTERVAL{std::chrono::seco static const unsigned int MAX_GETDATA_SZ = 1000; /** Number of blocks that can be requested at any given time from a single peer. */ static const int MAX_BLOCKS_IN_TRANSIT_PER_PEER = 16; -/** Timeout in seconds during which a peer must stall block download progress before being disconnected. */ -static const unsigned int BLOCK_STALLING_TIMEOUT = 2; +/** Time during which a peer must stall block download progress before being disconnected. */ +static constexpr auto BLOCK_STALLING_TIMEOUT = 2s; /** Number of headers sent in one getheaders result. We rely on the assumption that if a peer sends * less than this number, we reached its tip. Changing this value is a protocol upgrade. */ static const unsigned int MAX_HEADERS_RESULTS = 2000; @@ -108,10 +107,10 @@ static const int MAX_BLOCKTXN_DEPTH = 10; * degree of disordering of blocks on disk (which make reindexing and pruning harder). We'll probably * want to make this a per-peer adaptive value at some point. */ static const unsigned int BLOCK_DOWNLOAD_WINDOW = 1024; -/** Block download timeout base, expressed in millionths of the block interval (i.e. 10 min) */ -static const int64_t BLOCK_DOWNLOAD_TIMEOUT_BASE = 1000000; +/** Block download timeout base, expressed in multiples of the block interval (i.e. 10 min) */ +static constexpr double BLOCK_DOWNLOAD_TIMEOUT_BASE = 1; /** Additional block download timeout per parallel downloading peer (i.e. 5 min) */ -static const int64_t BLOCK_DOWNLOAD_TIMEOUT_PER_PEER = 500000; +static constexpr double BLOCK_DOWNLOAD_TIMEOUT_PER_PEER = 0.5; /** Maximum number of headers to announce when relaying blocks with headers message.*/ static const unsigned int MAX_BLOCKS_TO_ANNOUNCE = 8; /** Maximum number of unconnecting headers announcements before DoS score */ @@ -119,17 +118,21 @@ static const int MAX_UNCONNECTING_HEADERS = 10; /** Minimum blocks required to signal NODE_NETWORK_LIMITED */ static const unsigned int NODE_NETWORK_LIMITED_MIN_BLOCKS = 288; /** Average delay between local address broadcasts */ -static constexpr std::chrono::hours AVG_LOCAL_ADDRESS_BROADCAST_INTERVAL{24}; +static constexpr auto AVG_LOCAL_ADDRESS_BROADCAST_INTERVAL = 24h; /** Average delay between peer address broadcasts */ -static constexpr std::chrono::seconds AVG_ADDRESS_BROADCAST_INTERVAL{30}; -/** Average delay between trickled inventory transmissions in seconds. - * Blocks and peers with noban permission bypass this, outbound peers get half this delay. */ -static const unsigned int INVENTORY_BROADCAST_INTERVAL = 5; +static constexpr auto AVG_ADDRESS_BROADCAST_INTERVAL = 30s; +/** Average delay between trickled inventory transmissions for inbound peers. + * Blocks and peers with noban permission bypass this. */ +static constexpr auto INBOUND_INVENTORY_BROADCAST_INTERVAL = 5s; +/** Average delay between trickled inventory transmissions for outbound peers. + * Use a smaller delay as there is less privacy concern for them. + * Blocks and peers with noban permission bypass this. */ +static constexpr auto OUTBOUND_INVENTORY_BROADCAST_INTERVAL = 2s; /** Maximum rate of inventory items to send per second. * Limits the impact of low-fee transaction floods. */ static constexpr unsigned int INVENTORY_BROADCAST_PER_SECOND = 7; /** Maximum number of inventory items to send per transmission. */ -static constexpr unsigned int INVENTORY_BROADCAST_MAX = INVENTORY_BROADCAST_PER_SECOND * INVENTORY_BROADCAST_INTERVAL; +static constexpr unsigned int INVENTORY_BROADCAST_MAX = INVENTORY_BROADCAST_PER_SECOND * count_seconds(INBOUND_INVENTORY_BROADCAST_INTERVAL); /** The number of most recently announced transactions a peer can request. */ static constexpr unsigned int INVENTORY_MAX_RECENT_RELAY = 3500; /** Verify that INVENTORY_MAX_RECENT_RELAY is enough to cache everything typically @@ -138,9 +141,9 @@ static constexpr unsigned int INVENTORY_MAX_RECENT_RELAY = 3500; * peers, and random variations in the broadcast mechanism. */ static_assert(INVENTORY_MAX_RECENT_RELAY >= INVENTORY_BROADCAST_PER_SECOND * UNCONDITIONAL_RELAY_DELAY / std::chrono::seconds{1}, "INVENTORY_RELAY_MAX too low"); /** Average delay between feefilter broadcasts in seconds. */ -static constexpr unsigned int AVG_FEEFILTER_BROADCAST_INTERVAL = 10 * 60; +static constexpr auto AVG_FEEFILTER_BROADCAST_INTERVAL = 10min; /** Maximum feefilter broadcast delay after significant change. */ -static constexpr unsigned int MAX_FEEFILTER_CHANGE_DELAY = 5 * 60; +static constexpr auto MAX_FEEFILTER_CHANGE_DELAY = 5min; /** Maximum number of compact filters that may be requested with one getcfilters. See BIP 157. */ static constexpr uint32_t MAX_GETCFILTERS_SIZE = 1000; /** Maximum number of cf hashes that may be requested with one getcfheaders. See BIP 157. */ @@ -148,25 +151,6 @@ static constexpr uint32_t MAX_GETCFHEADERS_SIZE = 2000; /** the maximum percentage of addresses from our addrman to return in response to a getaddr message. */ static constexpr size_t MAX_PCT_ADDR_TO_SEND = 23; -struct COrphanTx { - // When modifying, adapt the copy of this definition in tests/DoS_tests. - CTransactionRef tx; - NodeId fromPeer; - int64_t nTimeExpire; - size_t list_pos; -}; - -/** Guards orphan transactions and extra txs for compact blocks */ -RecursiveMutex g_cs_orphans; -/** Map from txid to orphan transaction record. Limited by - * -maxorphantx/DEFAULT_MAX_ORPHAN_TRANSACTIONS */ -std::map<uint256, COrphanTx> mapOrphanTransactions GUARDED_BY(g_cs_orphans); -/** Index from wtxid into the mapOrphanTransactions to lookup orphan - * transactions using their witness ids. */ -std::map<uint256, std::map<uint256, COrphanTx>::iterator> g_orphans_by_wtxid GUARDED_BY(g_cs_orphans); - -void EraseOrphansFor(NodeId peer); - // Internal stuff namespace { /** Blocks that are in flight, and that are in the queue to be downloaded. */ @@ -242,9 +226,9 @@ using PeerRef = std::shared_ptr<Peer>; class PeerManagerImpl final : public PeerManager { public: - PeerManagerImpl(const CChainParams& chainparams, CConnman& connman, BanMan* banman, - CScheduler& scheduler, ChainstateManager& chainman, CTxMemPool& pool, - bool ignore_incoming_txs); + PeerManagerImpl(const CChainParams& chainparams, CConnman& connman, CAddrMan& addrman, + BanMan* banman, CScheduler& scheduler, ChainstateManager& chainman, + CTxMemPool& pool, bool ignore_incoming_txs); /** Overridden from CValidationInterface. */ void BlockConnected(const std::shared_ptr<const CBlock>& pblock, const CBlockIndex* pindexConnected) override; @@ -255,7 +239,7 @@ public: /** Implement NetEventsInterface */ void InitializeNode(CNode* pnode) override; - void FinalizeNode(const CNode& node, bool& fUpdateConnectionTime) override; + void FinalizeNode(const CNode& node) override; bool ProcessMessages(CNode* pfrom, std::atomic<bool>& interrupt) override; bool SendMessages(CNode* pto) override EXCLUSIVE_LOCKS_REQUIRED(pto->cs_sendProcessing); @@ -264,6 +248,7 @@ public: bool GetNodeStateStats(NodeId nodeid, CNodeStateStats& stats) override; bool IgnoresIncomingTxs() override { return m_ignore_incoming_txs; } void SendPings() override; + void RelayTransaction(const uint256& txid, const uint256& wtxid) override; void SetBestHeight(int height) override { m_best_height = height; }; void Misbehaving(const NodeId pnode, const int howmuch, const std::string& message) override; void ProcessMessage(CNode& pfrom, const std::string& msg_type, CDataStream& vRecv, @@ -277,7 +262,7 @@ private: void EvictExtraOutboundPeers(int64_t time_in_seconds) EXCLUSIVE_LOCKS_REQUIRED(cs_main); /** Retrieve unbroadcast transactions from the mempool and reattempt sending to peers */ - void ReattemptInitialBroadcast(CScheduler& scheduler) const; + void ReattemptInitialBroadcast(CScheduler& scheduler); /** Get a shared pointer to the Peer object. * May return an empty shared_ptr if the Peer object can't be found. */ @@ -333,11 +318,17 @@ private: void PushNodeVersion(CNode& pnode, int64_t nTime); /** Send a ping message every PING_INTERVAL or if requested via RPC. May - * mark the peer to be disconnected if a ping has timed out. */ - void MaybeSendPing(CNode& node_to, Peer& peer); + * mark the peer to be disconnected if a ping has timed out. + * We use mockable time for ping timeouts, so setmocktime may cause pings + * to time out. */ + void MaybeSendPing(CNode& node_to, Peer& peer, std::chrono::microseconds now); + + /** Send `addr` messages on a regular schedule. */ + void MaybeSendAddr(CNode& node, std::chrono::microseconds current_time); const CChainParams& m_chainparams; CConnman& m_connman; + CAddrMan& m_addrman; /** Pointer to this node's banman. May be nullptr - check existence before dereferencing. */ BanMan* const m_banman; ChainstateManager& m_chainman; @@ -463,7 +454,7 @@ private: typedef std::map<uint256, CTransactionRef> MapRelay; MapRelay mapRelay GUARDED_BY(cs_main); /** Expiration-time ordered list of (expire time, relay map entry) pairs. */ - std::deque<std::pair<int64_t, MapRelay::iterator>> vRelayExpiration GUARDED_BY(cs_main); + std::deque<std::pair<std::chrono::microseconds, MapRelay::iterator>> g_relay_expiration GUARDED_BY(cs_main); /** * When a peer sends us a valid block, instruct it to announce blocks to us @@ -479,35 +470,89 @@ private: /** Number of peers from which we're downloading blocks. */ int nPeersWithValidatedDownloads GUARDED_BY(cs_main) = 0; -}; -} // namespace - -namespace { - - /** Number of preferable block download peers. */ - int nPreferredDownload GUARDED_BY(cs_main) = 0; + /** Storage for orphan information */ + TxOrphanage m_orphanage; - struct IteratorComparator - { - template<typename I> - bool operator()(const I& a, const I& b) const - { - return &(*a) < &(*b); - } - }; - - /** Index from the parents' COutPoint into the mapOrphanTransactions. Used - * to remove orphan transactions from the mapOrphanTransactions */ - std::map<COutPoint, std::set<std::map<uint256, COrphanTx>::iterator, IteratorComparator>> mapOrphanTransactionsByPrev GUARDED_BY(g_cs_orphans); - /** Orphan transactions in vector for quick random eviction */ - std::vector<std::map<uint256, COrphanTx>::iterator> g_orphan_list GUARDED_BY(g_cs_orphans); + void AddToCompactExtraTransactions(const CTransactionRef& tx) EXCLUSIVE_LOCKS_REQUIRED(g_cs_orphans); /** Orphan/conflicted/etc transactions that are kept for compact block reconstruction. * The last -blockreconstructionextratxn/DEFAULT_BLOCK_RECONSTRUCTION_EXTRA_TXN of * these are kept in a ring buffer */ - static std::vector<std::pair<uint256, CTransactionRef>> vExtraTxnForCompact GUARDED_BY(g_cs_orphans); + std::vector<std::pair<uint256, CTransactionRef>> vExtraTxnForCompact GUARDED_BY(g_cs_orphans); /** Offset into vExtraTxnForCompact to insert the next tx */ - static size_t vExtraTxnForCompactIt GUARDED_BY(g_cs_orphans) = 0; + size_t vExtraTxnForCompactIt GUARDED_BY(g_cs_orphans) = 0; + + /** Check whether the last unknown block a peer advertised is not yet known. */ + void ProcessBlockAvailability(NodeId nodeid) EXCLUSIVE_LOCKS_REQUIRED(cs_main); + /** Update tracking information about which blocks a peer is assumed to have. */ + void UpdateBlockAvailability(NodeId nodeid, const uint256& hash) EXCLUSIVE_LOCKS_REQUIRED(cs_main); + bool CanDirectFetch() EXCLUSIVE_LOCKS_REQUIRED(cs_main); + + /** + * To prevent fingerprinting attacks, only send blocks/headers outside of + * the active chain if they are no more than a month older (both in time, + * and in best equivalent proof of work) than the best header chain we know + * about and we fully-validated them at some point. + */ + bool BlockRequestAllowed(const CBlockIndex* pindex) EXCLUSIVE_LOCKS_REQUIRED(cs_main); + bool AlreadyHaveBlock(const uint256& block_hash) EXCLUSIVE_LOCKS_REQUIRED(cs_main); + void ProcessGetBlockData(CNode& pfrom, Peer& peer, const CInv& inv); + + /** + * Validation logic for compact filters request handling. + * + * May disconnect from the peer in the case of a bad request. + * + * @param[in] peer The peer that we received the request from + * @param[in] filter_type The filter type the request is for. Must be basic filters. + * @param[in] start_height The start height for the request + * @param[in] stop_hash The stop_hash for the request + * @param[in] max_height_diff The maximum number of items permitted to request, as specified in BIP 157 + * @param[out] stop_index The CBlockIndex for the stop_hash block, if the request can be serviced. + * @param[out] filter_index The filter index, if the request can be serviced. + * @return True if the request can be serviced. + */ + bool PrepareBlockFilterRequest(CNode& peer, + BlockFilterType filter_type, uint32_t start_height, + const uint256& stop_hash, uint32_t max_height_diff, + const CBlockIndex*& stop_index, + BlockFilterIndex*& filter_index); + + /** + * Handle a cfilters request. + * + * May disconnect from the peer in the case of a bad request. + * + * @param[in] peer The peer that we received the request from + * @param[in] vRecv The raw message received + */ + void ProcessGetCFilters(CNode& peer, CDataStream& vRecv); + + /** + * Handle a cfheaders request. + * + * May disconnect from the peer in the case of a bad request. + * + * @param[in] peer The peer that we received the request from + * @param[in] vRecv The raw message received + */ + void ProcessGetCFHeaders(CNode& peer, CDataStream& vRecv); + + /** + * Handle a getcfcheckpt request. + * + * May disconnect from the peer in the case of a bad request. + * + * @param[in] peer The peer that we received the request from + * @param[in] vRecv The raw message received + */ + void ProcessGetCFCheckPt(CNode& peer, CDataStream& vRecv); +}; +} // namespace + +namespace { + /** Number of preferable block download peers. */ + int nPreferredDownload GUARDED_BY(cs_main) = 0; } // namespace namespace { @@ -518,50 +563,48 @@ namespace { * and we're no longer holding the node's locks. */ struct CNodeState { - //! The peer's address - const CService address; //! The best known block we know this peer has announced. - const CBlockIndex *pindexBestKnownBlock; + const CBlockIndex* pindexBestKnownBlock{nullptr}; //! The hash of the last unknown block this peer has announced. - uint256 hashLastUnknownBlock; + uint256 hashLastUnknownBlock{}; //! The last full block we both have. - const CBlockIndex *pindexLastCommonBlock; + const CBlockIndex* pindexLastCommonBlock{nullptr}; //! The best header we have sent our peer. - const CBlockIndex *pindexBestHeaderSent; + const CBlockIndex* pindexBestHeaderSent{nullptr}; //! Length of current-streak of unconnecting headers announcements - int nUnconnectingHeaders; + int nUnconnectingHeaders{0}; //! Whether we've started headers synchronization with this peer. - bool fSyncStarted; + bool fSyncStarted{false}; //! When to potentially disconnect peer for stalling headers download - int64_t nHeadersSyncTimeout; + std::chrono::microseconds m_headers_sync_timeout{0us}; //! Since when we're stalling block download progress (in microseconds), or 0. - int64_t nStallingSince; + std::chrono::microseconds m_stalling_since{0us}; std::list<QueuedBlock> vBlocksInFlight; //! When the first entry in vBlocksInFlight started downloading. Don't care when vBlocksInFlight is empty. - int64_t nDownloadingSince; - int nBlocksInFlight; - int nBlocksInFlightValidHeaders; + std::chrono::microseconds m_downloading_since{0us}; + int nBlocksInFlight{0}; + int nBlocksInFlightValidHeaders{0}; //! Whether we consider this a preferred download peer. - bool fPreferredDownload; + bool fPreferredDownload{false}; //! Whether this peer wants invs or headers (when possible) for block announcements. - bool fPreferHeaders; + bool fPreferHeaders{false}; //! Whether this peer wants invs or cmpctblocks (when possible) for block announcements. - bool fPreferHeaderAndIDs; + bool fPreferHeaderAndIDs{false}; /** * Whether this peer will send us cmpctblocks if we request them. * This is not used to gate request logic, as we really only care about fSupportsDesiredCmpctVersion, * but is used as a flag to "lock in" the version of compact blocks (fWantsCmpctWitness) we send. */ - bool fProvidesHeaderAndIDs; + bool fProvidesHeaderAndIDs{false}; //! Whether this peer can give us witnesses - bool fHaveWitness; + bool fHaveWitness{false}; //! Whether this peer wants witnesses in cmpctblocks/blocktxns - bool fWantsCmpctWitness; + bool fWantsCmpctWitness{false}; /** * If we've announced NODE_WITNESS to this peer: whether the peer sends witnesses in cmpctblocks/blocktxns, * otherwise: whether this peer sends non-witnesses in cmpctblocks/blocktxns. */ - bool fSupportsDesiredCmpctVersion; + bool fSupportsDesiredCmpctVersion{false}; /** State used to enforce CHAIN_SYNC_TIMEOUT and EXTRA_PEER_CHECK_INTERVAL logic. * @@ -571,7 +614,7 @@ struct CNodeState { * - its connection type is IsBlockOnlyConn() == false * - it gave us a valid connecting header * - we haven't reached MAX_OUTBOUND_PEERS_TO_PROTECT_FROM_DISCONNECT yet - * - it has a better chain than we have + * - its chain tip has at least as much work as ours * * CHAIN_SYNC_TIMEOUT: if a peer's best known block has less work than our tip, * set a timeout CHAIN_SYNC_TIMEOUT seconds in the future: @@ -589,22 +632,22 @@ struct CNodeState { */ struct ChainSyncTimeoutState { //! A timeout used for checking whether our peer has sufficiently synced - int64_t m_timeout; + int64_t m_timeout{0}; //! A header with the work we require on our peer's chain - const CBlockIndex * m_work_header; + const CBlockIndex* m_work_header{nullptr}; //! After timeout is reached, set to true after sending getheaders - bool m_sent_getheaders; + bool m_sent_getheaders{false}; //! Whether this peer is protected from disconnection due to a bad/slow chain - bool m_protect; + bool m_protect{false}; }; ChainSyncTimeoutState m_chain_sync; //! Time of last new block announcement - int64_t m_last_block_announcement; + int64_t m_last_block_announcement{0}; //! Whether this peer is an inbound connection - bool m_is_inbound; + const bool m_is_inbound; //! A rolling bloom filter of all announced tx CInvs to this peer. CRollingBloomFilter m_recently_announced_invs = CRollingBloomFilter{INVENTORY_MAX_RECENT_RELAY, 0.000001}; @@ -612,31 +655,7 @@ struct CNodeState { //! Whether this peer relays txs via wtxid bool m_wtxid_relay{false}; - CNodeState(CAddress addrIn, bool is_inbound) - : address(addrIn), m_is_inbound(is_inbound) - { - pindexBestKnownBlock = nullptr; - hashLastUnknownBlock.SetNull(); - pindexLastCommonBlock = nullptr; - pindexBestHeaderSent = nullptr; - nUnconnectingHeaders = 0; - fSyncStarted = false; - nHeadersSyncTimeout = 0; - nStallingSince = 0; - nDownloadingSince = 0; - nBlocksInFlight = 0; - nBlocksInFlightValidHeaders = 0; - fPreferredDownload = false; - fPreferHeaders = false; - fPreferHeaderAndIDs = false; - fProvidesHeaderAndIDs = false; - fHaveWitness = false; - fWantsCmpctWitness = false; - fSupportsDesiredCmpctVersion = false; - m_chain_sync = { 0, nullptr, false, false }; - m_last_block_announcement = 0; - m_recently_announced_invs.reset(); - } + CNodeState(bool is_inbound) : m_is_inbound(is_inbound) {} }; /** Map maintaining per-node state. */ @@ -672,11 +691,11 @@ bool PeerManagerImpl::MarkBlockAsReceived(const uint256& hash) } if (state->vBlocksInFlight.begin() == itInFlight->second.second) { // First block on the queue was received, update the start download time for the next one - state->nDownloadingSince = std::max(state->nDownloadingSince, count_microseconds(GetTime<std::chrono::microseconds>())); + state->m_downloading_since = std::max(state->m_downloading_since, GetTime<std::chrono::microseconds>()); } state->vBlocksInFlight.erase(itInFlight->second.second); state->nBlocksInFlight--; - state->nStallingSince = 0; + state->m_stalling_since = 0us; mapBlocksInFlight.erase(itInFlight); return true; } @@ -706,7 +725,7 @@ bool PeerManagerImpl::MarkBlockAsInFlight(NodeId nodeid, const uint256& hash, co state->nBlocksInFlightValidHeaders += it->fValidatedHeaders; if (state->nBlocksInFlight == 1) { // We're starting a block download (batch) from this peer. - state->nDownloadingSince = GetTime<std::chrono::microseconds>().count(); + state->m_downloading_since = GetTime<std::chrono::microseconds>(); } if (state->nBlocksInFlightValidHeaders == 1 && pindex != nullptr) { nPeersWithValidatedDownloads++; @@ -717,41 +736,6 @@ bool PeerManagerImpl::MarkBlockAsInFlight(NodeId nodeid, const uint256& hash, co return true; } -/** Check whether the last unknown block a peer advertised is not yet known. */ -static void ProcessBlockAvailability(NodeId nodeid) EXCLUSIVE_LOCKS_REQUIRED(cs_main) { - CNodeState *state = State(nodeid); - assert(state != nullptr); - - if (!state->hashLastUnknownBlock.IsNull()) { - const CBlockIndex* pindex = g_chainman.m_blockman.LookupBlockIndex(state->hashLastUnknownBlock); - if (pindex && pindex->nChainWork > 0) { - if (state->pindexBestKnownBlock == nullptr || pindex->nChainWork >= state->pindexBestKnownBlock->nChainWork) { - state->pindexBestKnownBlock = pindex; - } - state->hashLastUnknownBlock.SetNull(); - } - } -} - -/** Update tracking information about which blocks a peer is assumed to have. */ -static void UpdateBlockAvailability(NodeId nodeid, const uint256 &hash) EXCLUSIVE_LOCKS_REQUIRED(cs_main) { - CNodeState *state = State(nodeid); - assert(state != nullptr); - - ProcessBlockAvailability(nodeid); - - const CBlockIndex* pindex = g_chainman.m_blockman.LookupBlockIndex(hash); - if (pindex && pindex->nChainWork > 0) { - // An actually better block was announced. - if (state->pindexBestKnownBlock == nullptr || pindex->nChainWork >= state->pindexBestKnownBlock->nChainWork) { - state->pindexBestKnownBlock = pindex; - } - } else { - // An unknown block was announced; just assume that the latest one is the best one. - state->hashLastUnknownBlock = hash; - } -} - void PeerManagerImpl::MaybeSetPeerAsAnnouncingHeaderAndIDs(NodeId nodeid) { AssertLockHeld(cs_main); @@ -801,9 +785,9 @@ bool PeerManagerImpl::TipMayBeStale() return m_last_tip_update < GetTime() - consensusParams.nPowTargetSpacing * 3 && mapBlocksInFlight.empty(); } -static bool CanDirectFetch(const Consensus::Params &consensusParams) EXCLUSIVE_LOCKS_REQUIRED(cs_main) +bool PeerManagerImpl::CanDirectFetch() { - return ::ChainActive().Tip()->GetBlockTime() > GetAdjustedTime() - consensusParams.nPowTargetSpacing * 20; + return m_chainman.ActiveChain().Tip()->GetBlockTime() > GetAdjustedTime() - m_chainparams.GetConsensus().nPowTargetSpacing * 20; } static bool PeerHasHeader(CNodeState *state, const CBlockIndex *pindex) EXCLUSIVE_LOCKS_REQUIRED(cs_main) @@ -815,6 +799,39 @@ static bool PeerHasHeader(CNodeState *state, const CBlockIndex *pindex) EXCLUSIV return false; } +void PeerManagerImpl::ProcessBlockAvailability(NodeId nodeid) { + CNodeState *state = State(nodeid); + assert(state != nullptr); + + if (!state->hashLastUnknownBlock.IsNull()) { + const CBlockIndex* pindex = m_chainman.m_blockman.LookupBlockIndex(state->hashLastUnknownBlock); + if (pindex && pindex->nChainWork > 0) { + if (state->pindexBestKnownBlock == nullptr || pindex->nChainWork >= state->pindexBestKnownBlock->nChainWork) { + state->pindexBestKnownBlock = pindex; + } + state->hashLastUnknownBlock.SetNull(); + } + } +} + +void PeerManagerImpl::UpdateBlockAvailability(NodeId nodeid, const uint256 &hash) { + CNodeState *state = State(nodeid); + assert(state != nullptr); + + ProcessBlockAvailability(nodeid); + + const CBlockIndex* pindex = m_chainman.m_blockman.LookupBlockIndex(hash); + if (pindex && pindex->nChainWork > 0) { + // An actually better block was announced. + if (state->pindexBestKnownBlock == nullptr || pindex->nChainWork >= state->pindexBestKnownBlock->nChainWork) { + state->pindexBestKnownBlock = pindex; + } + } else { + // An unknown block was announced; just assume that the latest one is the best one. + state->hashLastUnknownBlock = hash; + } +} + void PeerManagerImpl::FindNextBlocksToDownload(NodeId nodeid, unsigned int count, std::vector<const CBlockIndex*>& vBlocks, NodeId& nodeStaller) { if (count == 0) @@ -827,7 +844,7 @@ void PeerManagerImpl::FindNextBlocksToDownload(NodeId nodeid, unsigned int count // Make sure pindexBestKnownBlock is up to date, we'll need it. ProcessBlockAvailability(nodeid); - if (state->pindexBestKnownBlock == nullptr || state->pindexBestKnownBlock->nChainWork < ::ChainActive().Tip()->nChainWork || state->pindexBestKnownBlock->nChainWork < nMinimumChainWork) { + if (state->pindexBestKnownBlock == nullptr || state->pindexBestKnownBlock->nChainWork < m_chainman.ActiveChain().Tip()->nChainWork || state->pindexBestKnownBlock->nChainWork < nMinimumChainWork) { // This peer has nothing interesting. return; } @@ -835,7 +852,7 @@ void PeerManagerImpl::FindNextBlocksToDownload(NodeId nodeid, unsigned int count if (state->pindexLastCommonBlock == nullptr) { // Bootstrap quickly by guessing a parent of our best tip is the forking point. // Guessing wrong in either direction is not a problem. - state->pindexLastCommonBlock = ::ChainActive()[std::min(state->pindexBestKnownBlock->nHeight, ::ChainActive().Height())]; + state->pindexLastCommonBlock = m_chainman.ActiveChain()[std::min(state->pindexBestKnownBlock->nHeight, m_chainman.ActiveChain().Height())]; } // If the peer reorganized, our previous pindexLastCommonBlock may not be an ancestor @@ -878,7 +895,7 @@ void PeerManagerImpl::FindNextBlocksToDownload(NodeId nodeid, unsigned int count // We wouldn't download this block or its descendants from this peer. return; } - if (pindex->nStatus & BLOCK_HAVE_DATA || ::ChainActive().Contains(pindex)) { + if (pindex->nStatus & BLOCK_HAVE_DATA || m_chainman.ActiveChain().Contains(pindex)) { if (pindex->HaveTxsDownloaded()) state->pindexLastCommonBlock = pindex; } else if (mapBlocksInFlight.count(pindex->GetBlockHash()) == 0) { @@ -970,12 +987,10 @@ void UpdateLastBlockAnnounceTime(NodeId node, int64_t time_in_seconds) void PeerManagerImpl::InitializeNode(CNode *pnode) { - CAddress addr = pnode->addr; - std::string addrName = pnode->GetAddrName(); NodeId nodeid = pnode->GetId(); { LOCK(cs_main); - mapNodeState.emplace_hint(mapNodeState.end(), std::piecewise_construct, std::forward_as_tuple(nodeid), std::forward_as_tuple(addr, pnode->IsInboundConn())); + mapNodeState.emplace_hint(mapNodeState.end(), std::piecewise_construct, std::forward_as_tuple(nodeid), std::forward_as_tuple(pnode->IsInboundConn())); assert(m_txrequest.Count(nodeid) == 0); } { @@ -988,7 +1003,7 @@ void PeerManagerImpl::InitializeNode(CNode *pnode) } } -void PeerManagerImpl::ReattemptInitialBroadcast(CScheduler& scheduler) const +void PeerManagerImpl::ReattemptInitialBroadcast(CScheduler& scheduler) { std::set<uint256> unbroadcast_txids = m_mempool.GetUnbroadcastTxs(); @@ -997,7 +1012,7 @@ void PeerManagerImpl::ReattemptInitialBroadcast(CScheduler& scheduler) const if (tx != nullptr) { LOCK(cs_main); - RelayTransaction(txid, tx->GetWitnessHash(), m_connman); + RelayTransaction(txid, tx->GetWitnessHash()); } else { m_mempool.RemoveUnbroadcastTx(txid, true); } @@ -1009,13 +1024,13 @@ void PeerManagerImpl::ReattemptInitialBroadcast(CScheduler& scheduler) const scheduler.scheduleFromNow([&] { ReattemptInitialBroadcast(scheduler); }, delta); } -void PeerManagerImpl::FinalizeNode(const CNode& node, bool& fUpdateConnectionTime) +void PeerManagerImpl::FinalizeNode(const CNode& node) { NodeId nodeid = node.GetId(); - fUpdateConnectionTime = false; - LOCK(cs_main); int misbehavior{0}; { + LOCK(cs_main); + { // We remove the PeerRef from g_peer_map here, but we don't always // destruct the Peer. Sometimes another thread is still holding a // PeerRef, so the refcount is >= 1. Be careful not to do any @@ -1031,16 +1046,10 @@ void PeerManagerImpl::FinalizeNode(const CNode& node, bool& fUpdateConnectionTim if (state->fSyncStarted) nSyncStarted--; - if (node.fSuccessfullyConnected && misbehavior == 0 && - !node.IsBlockOnlyConn() && !node.IsInboundConn()) { - // Only change visible addrman state for outbound, full-relay peers - fUpdateConnectionTime = true; - } - for (const QueuedBlock& entry : state->vBlocksInFlight) { mapBlocksInFlight.erase(entry.hash); } - EraseOrphansFor(nodeid); + WITH_LOCK(g_cs_orphans, m_orphanage.EraseForPeer(nodeid)); m_txrequest.DisconnectedPeer(nodeid); nPreferredDownload -= state->fPreferredDownload; nPeersWithValidatedDownloads -= (state->nBlocksInFlightValidHeaders != 0); @@ -1061,6 +1070,14 @@ void PeerManagerImpl::FinalizeNode(const CNode& node, bool& fUpdateConnectionTim assert(m_wtxid_relay_peers == 0); assert(m_txrequest.Size() == 0); } + } // cs_main + if (node.fSuccessfullyConnected && misbehavior == 0 && + !node.IsBlockOnlyConn() && !node.IsInboundConn()) { + // Only change visible addrman state for full outbound peers. We don't + // call Connected() for feeler connections since they don't have + // fSuccessfullyConnected set. + m_addrman.Connected(node.addr); + } LogPrint(BCLog::NET, "Cleared nodestate for peer=%d\n", nodeid); } @@ -1112,17 +1129,12 @@ bool PeerManagerImpl::GetNodeStateStats(NodeId nodeid, CNodeStateStats &stats) ping_wait = GetTime<std::chrono::microseconds>() - peer->m_ping_start.load(); } - stats.m_ping_wait_usec = count_microseconds(ping_wait); + stats.m_ping_wait = ping_wait; return true; } -////////////////////////////////////////////////////////////////////////////// -// -// mapOrphanTransactions -// - -static void AddToCompactExtraTransactions(const CTransactionRef& tx) EXCLUSIVE_LOCKS_REQUIRED(g_cs_orphans) +void PeerManagerImpl::AddToCompactExtraTransactions(const CTransactionRef& tx) { size_t max_extra_txn = gArgs.GetArg("-blockreconstructionextratxn", DEFAULT_BLOCK_RECONSTRUCTION_EXTRA_TXN); if (max_extra_txn <= 0) @@ -1133,126 +1145,6 @@ static void AddToCompactExtraTransactions(const CTransactionRef& tx) EXCLUSIVE_L vExtraTxnForCompactIt = (vExtraTxnForCompactIt + 1) % max_extra_txn; } -bool AddOrphanTx(const CTransactionRef& tx, NodeId peer) EXCLUSIVE_LOCKS_REQUIRED(g_cs_orphans) -{ - const uint256& hash = tx->GetHash(); - if (mapOrphanTransactions.count(hash)) - return false; - - // Ignore big transactions, to avoid a - // send-big-orphans memory exhaustion attack. If a peer has a legitimate - // large transaction with a missing parent then we assume - // it will rebroadcast it later, after the parent transaction(s) - // have been mined or received. - // 100 orphans, each of which is at most 100,000 bytes big is - // at most 10 megabytes of orphans and somewhat more byprev index (in the worst case): - unsigned int sz = GetTransactionWeight(*tx); - if (sz > MAX_STANDARD_TX_WEIGHT) - { - LogPrint(BCLog::MEMPOOL, "ignoring large orphan tx (size: %u, hash: %s)\n", sz, hash.ToString()); - return false; - } - - auto ret = mapOrphanTransactions.emplace(hash, COrphanTx{tx, peer, GetTime() + ORPHAN_TX_EXPIRE_TIME, g_orphan_list.size()}); - assert(ret.second); - g_orphan_list.push_back(ret.first); - // Allow for lookups in the orphan pool by wtxid, as well as txid - g_orphans_by_wtxid.emplace(tx->GetWitnessHash(), ret.first); - for (const CTxIn& txin : tx->vin) { - mapOrphanTransactionsByPrev[txin.prevout].insert(ret.first); - } - - AddToCompactExtraTransactions(tx); - - LogPrint(BCLog::MEMPOOL, "stored orphan tx %s (mapsz %u outsz %u)\n", hash.ToString(), - mapOrphanTransactions.size(), mapOrphanTransactionsByPrev.size()); - return true; -} - -int static EraseOrphanTx(uint256 hash) EXCLUSIVE_LOCKS_REQUIRED(g_cs_orphans) -{ - std::map<uint256, COrphanTx>::iterator it = mapOrphanTransactions.find(hash); - if (it == mapOrphanTransactions.end()) - return 0; - for (const CTxIn& txin : it->second.tx->vin) - { - auto itPrev = mapOrphanTransactionsByPrev.find(txin.prevout); - if (itPrev == mapOrphanTransactionsByPrev.end()) - continue; - itPrev->second.erase(it); - if (itPrev->second.empty()) - mapOrphanTransactionsByPrev.erase(itPrev); - } - - size_t old_pos = it->second.list_pos; - assert(g_orphan_list[old_pos] == it); - if (old_pos + 1 != g_orphan_list.size()) { - // Unless we're deleting the last entry in g_orphan_list, move the last - // entry to the position we're deleting. - auto it_last = g_orphan_list.back(); - g_orphan_list[old_pos] = it_last; - it_last->second.list_pos = old_pos; - } - g_orphan_list.pop_back(); - g_orphans_by_wtxid.erase(it->second.tx->GetWitnessHash()); - - mapOrphanTransactions.erase(it); - return 1; -} - -void EraseOrphansFor(NodeId peer) -{ - LOCK(g_cs_orphans); - int nErased = 0; - std::map<uint256, COrphanTx>::iterator iter = mapOrphanTransactions.begin(); - while (iter != mapOrphanTransactions.end()) - { - std::map<uint256, COrphanTx>::iterator maybeErase = iter++; // increment to avoid iterator becoming invalid - if (maybeErase->second.fromPeer == peer) - { - nErased += EraseOrphanTx(maybeErase->second.tx->GetHash()); - } - } - if (nErased > 0) LogPrint(BCLog::MEMPOOL, "Erased %d orphan tx from peer=%d\n", nErased, peer); -} - - -unsigned int LimitOrphanTxSize(unsigned int nMaxOrphans) -{ - LOCK(g_cs_orphans); - - unsigned int nEvicted = 0; - static int64_t nNextSweep; - int64_t nNow = GetTime(); - if (nNextSweep <= nNow) { - // Sweep out expired orphan pool entries: - int nErased = 0; - int64_t nMinExpTime = nNow + ORPHAN_TX_EXPIRE_TIME - ORPHAN_TX_EXPIRE_INTERVAL; - std::map<uint256, COrphanTx>::iterator iter = mapOrphanTransactions.begin(); - while (iter != mapOrphanTransactions.end()) - { - std::map<uint256, COrphanTx>::iterator maybeErase = iter++; - if (maybeErase->second.nTimeExpire <= nNow) { - nErased += EraseOrphanTx(maybeErase->second.tx->GetHash()); - } else { - nMinExpTime = std::min(maybeErase->second.nTimeExpire, nMinExpTime); - } - } - // Sweep again 5 minutes after the next entry that expires in order to batch the linear scan. - nNextSweep = nMinExpTime + ORPHAN_TX_EXPIRE_INTERVAL; - if (nErased > 0) LogPrint(BCLog::MEMPOOL, "Erased %d orphan tx due to expiration\n", nErased); - } - FastRandomContext rng; - while (mapOrphanTransactions.size() > nMaxOrphans) - { - // Evict a random orphan: - size_t randompos = rng.randrange(g_orphan_list.size()); - EraseOrphanTx(g_orphan_list[randompos]->first); - ++nEvicted; - } - return nEvicted; -} - void PeerManagerImpl::Misbehaving(const NodeId pnode, const int howmuch, const std::string& message) { assert(howmuch > 0); @@ -1348,43 +1240,35 @@ bool PeerManagerImpl::MaybePunishNodeForTx(NodeId nodeid, const TxValidationStat return false; } - -////////////////////////////////////////////////////////////////////////////// -// -// blockchain -> download logic notification -// - -// To prevent fingerprinting attacks, only send blocks/headers outside of the -// active chain if they are no more than a month older (both in time, and in -// best equivalent proof of work) than the best header chain we know about and -// we fully-validated them at some point. -static bool BlockRequestAllowed(const CBlockIndex* pindex, const Consensus::Params& consensusParams) EXCLUSIVE_LOCKS_REQUIRED(cs_main) +bool PeerManagerImpl::BlockRequestAllowed(const CBlockIndex* pindex) { AssertLockHeld(cs_main); - if (::ChainActive().Contains(pindex)) return true; + if (m_chainman.ActiveChain().Contains(pindex)) return true; return pindex->IsValid(BLOCK_VALID_SCRIPTS) && (pindexBestHeader != nullptr) && - (pindexBestHeader->GetBlockTime() - pindex->GetBlockTime() < STALE_RELAY_AGE_LIMIT) && - (GetBlockProofEquivalentTime(*pindexBestHeader, *pindex, *pindexBestHeader, consensusParams) < STALE_RELAY_AGE_LIMIT); + (pindexBestHeader->GetBlockTime() - pindex->GetBlockTime() < STALE_RELAY_AGE_LIMIT) && + (GetBlockProofEquivalentTime(*pindexBestHeader, *pindex, *pindexBestHeader, m_chainparams.GetConsensus()) < STALE_RELAY_AGE_LIMIT); } -std::unique_ptr<PeerManager> PeerManager::make(const CChainParams& chainparams, CConnman& connman, BanMan* banman, - CScheduler& scheduler, ChainstateManager& chainman, CTxMemPool& pool, - bool ignore_incoming_txs) +std::unique_ptr<PeerManager> PeerManager::make(const CChainParams& chainparams, CConnman& connman, CAddrMan& addrman, + BanMan* banman, CScheduler& scheduler, ChainstateManager& chainman, + CTxMemPool& pool, bool ignore_incoming_txs) { - return std::make_unique<PeerManagerImpl>(chainparams, connman, banman, scheduler, chainman, pool, ignore_incoming_txs); + return std::make_unique<PeerManagerImpl>(chainparams, connman, addrman, banman, scheduler, chainman, pool, ignore_incoming_txs); } -PeerManagerImpl::PeerManagerImpl(const CChainParams& chainparams, CConnman& connman, BanMan* banman, - CScheduler& scheduler, ChainstateManager& chainman, CTxMemPool& pool, - bool ignore_incoming_txs) +PeerManagerImpl::PeerManagerImpl(const CChainParams& chainparams, CConnman& connman, CAddrMan& addrman, + BanMan* banman, CScheduler& scheduler, ChainstateManager& chainman, + CTxMemPool& pool, bool ignore_incoming_txs) : m_chainparams(chainparams), m_connman(connman), + m_addrman(addrman), m_banman(banman), m_chainman(chainman), m_mempool(pool), m_stale_tip_check_time(0), m_ignore_incoming_txs(ignore_incoming_txs) { + assert(std::addressof(g_chainman) == std::addressof(m_chainman)); // Initialize global variables that cannot be constructed at startup. recentRejects.reset(new CRollingBloomFilter(120000, 0.000001)); @@ -1412,43 +1296,15 @@ PeerManagerImpl::PeerManagerImpl(const CChainParams& chainparams, CConnman& conn } /** - * Evict orphan txn pool entries (EraseOrphanTx) based on a newly connected + * Evict orphan txn pool entries based on a newly connected * block, remember the recently confirmed transactions, and delete tracked * announcements for them. Also save the time of the last tip update. */ void PeerManagerImpl::BlockConnected(const std::shared_ptr<const CBlock>& pblock, const CBlockIndex* pindex) { - { - LOCK(g_cs_orphans); - - std::vector<uint256> vOrphanErase; - - for (const CTransactionRef& ptx : pblock->vtx) { - const CTransaction& tx = *ptx; - - // Which orphan pool entries must we evict? - for (const auto& txin : tx.vin) { - auto itByPrev = mapOrphanTransactionsByPrev.find(txin.prevout); - if (itByPrev == mapOrphanTransactionsByPrev.end()) continue; - for (auto mi = itByPrev->second.begin(); mi != itByPrev->second.end(); ++mi) { - const CTransaction& orphanTx = *(*mi)->second.tx; - const uint256& orphanHash = orphanTx.GetHash(); - vOrphanErase.push_back(orphanHash); - } - } - } + m_orphanage.EraseForBlock(*pblock); + m_last_tip_update = GetTime(); - // Erase orphan transactions included or precluded by this block - if (vOrphanErase.size()) { - int nErased = 0; - for (const uint256& orphanHash : vOrphanErase) { - nErased += EraseOrphanTx(orphanHash); - } - LogPrint(BCLog::MEMPOOL, "Erased %d orphan tx included or conflicted by block\n", nErased); - } - - m_last_tip_update = GetTime(); - } { LOCK(m_recent_confirmed_transactions_mutex); for (const auto& ptx : pblock->vtx) { @@ -1538,7 +1394,7 @@ void PeerManagerImpl::NewPoWValidBlock(const CBlockIndex *pindex, const std::sha /** * Update our best height and announce any block hashes which weren't previously - * in ::ChainActive() to our peers. + * in m_chainman.ActiveChain() to our peers. */ void PeerManagerImpl::UpdatedBlockTip(const CBlockIndex *pindexNew, const CBlockIndex *pindexFork, bool fInitialDownload) { @@ -1600,7 +1456,7 @@ void PeerManagerImpl::BlockChecked(const CBlock& block, const BlockValidationSta // the tip yet so we have no way to check this directly here. Instead we // just check that there are currently no other blocks in flight. else if (state.IsValid() && - !::ChainstateActive().IsInitialBlockDownload() && + !m_chainman.ActiveChainstate().IsInitialBlockDownload() && mapBlocksInFlight.count(hash) == mapBlocksInFlight.size()) { if (it != mapBlockSource.end()) { MaybeSetPeerAsAnnouncingHeaderAndIDs(it->second.first); @@ -1619,25 +1475,18 @@ void PeerManagerImpl::BlockChecked(const CBlock& block, const BlockValidationSta bool PeerManagerImpl::AlreadyHaveTx(const GenTxid& gtxid) { assert(recentRejects); - if (::ChainActive().Tip()->GetBlockHash() != hashRecentRejectsChainTip) { + if (m_chainman.ActiveChain().Tip()->GetBlockHash() != hashRecentRejectsChainTip) { // If the chain tip has changed previously rejected transactions // might be now valid, e.g. due to a nLockTime'd tx becoming valid, // or a double-spend. Reset the rejects filter and give those // txs a second chance. - hashRecentRejectsChainTip = ::ChainActive().Tip()->GetBlockHash(); + hashRecentRejectsChainTip = m_chainman.ActiveChain().Tip()->GetBlockHash(); recentRejects->reset(); } const uint256& hash = gtxid.GetHash(); - { - LOCK(g_cs_orphans); - if (!gtxid.IsWtxid() && mapOrphanTransactions.count(hash)) { - return true; - } else if (gtxid.IsWtxid() && g_orphans_by_wtxid.count(hash)) { - return true; - } - } + if (m_orphanage.HaveTx(gtxid)) return true; { LOCK(m_recent_confirmed_transactions_mutex); @@ -1647,9 +1496,9 @@ bool PeerManagerImpl::AlreadyHaveTx(const GenTxid& gtxid) return recentRejects->contains(hash) || m_mempool.exists(gtxid); } -bool static AlreadyHaveBlock(const uint256& block_hash) EXCLUSIVE_LOCKS_REQUIRED(cs_main) +bool PeerManagerImpl::AlreadyHaveBlock(const uint256& block_hash) { - return g_chainman.m_blockman.LookupBlockIndex(block_hash) != nullptr; + return m_chainman.m_blockman.LookupBlockIndex(block_hash) != nullptr; } void PeerManagerImpl::SendPings() @@ -1658,9 +1507,9 @@ void PeerManagerImpl::SendPings() for(auto& it : m_peer_map) it.second->m_ping_queued = true; } -void RelayTransaction(const uint256& txid, const uint256& wtxid, const CConnman& connman) +void PeerManagerImpl::RelayTransaction(const uint256& txid, const uint256& wtxid) { - connman.ForEachNode([&txid, &wtxid](CNode* pnode) EXCLUSIVE_LOCKS_REQUIRED(::cs_main) { + m_connman.ForEachNode([&txid, &wtxid](CNode* pnode) EXCLUSIVE_LOCKS_REQUIRED(::cs_main) { AssertLockHeld(::cs_main); CNodeState* state = State(pnode->GetId()); @@ -1728,13 +1577,11 @@ static void RelayAddress(const CNode& originator, connman.ForEachNodeThen(std::move(sortfunc), std::move(pushfunc)); } -void static ProcessGetBlockData(CNode& pfrom, Peer& peer, const CChainParams& chainparams, const CInv& inv, CConnman& connman) +void PeerManagerImpl::ProcessGetBlockData(CNode& pfrom, Peer& peer, const CInv& inv) { - bool send = false; std::shared_ptr<const CBlock> a_recent_block; std::shared_ptr<const CBlockHeaderAndShortTxIDs> a_recent_compact_block; bool fWitnessesPresentInARecentCompactBlock; - const Consensus::Params& consensusParams = chainparams.GetConsensus(); { LOCK(cs_most_recent_block); a_recent_block = most_recent_block; @@ -1745,7 +1592,7 @@ void static ProcessGetBlockData(CNode& pfrom, Peer& peer, const CChainParams& ch bool need_activate_chain = false; { LOCK(cs_main); - const CBlockIndex* pindex = g_chainman.m_blockman.LookupBlockIndex(inv.hash); + const CBlockIndex* pindex = m_chainman.m_blockman.LookupBlockIndex(inv.hash); if (pindex) { if (pindex->HaveTxsDownloaded() && !pindex->IsValid(BLOCK_VALID_SCRIPTS) && pindex->IsValid(BLOCK_VALID_TREE)) { @@ -1760,126 +1607,124 @@ void static ProcessGetBlockData(CNode& pfrom, Peer& peer, const CChainParams& ch } // release cs_main before calling ActivateBestChain if (need_activate_chain) { BlockValidationState state; - if (!::ChainstateActive().ActivateBestChain(state, chainparams, a_recent_block)) { + if (!m_chainman.ActiveChainstate().ActivateBestChain(state, m_chainparams, a_recent_block)) { LogPrint(BCLog::NET, "failed to activate chain (%s)\n", state.ToString()); } } LOCK(cs_main); - const CBlockIndex* pindex = g_chainman.m_blockman.LookupBlockIndex(inv.hash); - if (pindex) { - send = BlockRequestAllowed(pindex, consensusParams); - if (!send) { - LogPrint(BCLog::NET, "%s: ignoring request from peer=%i for old block that isn't in the main chain\n", __func__, pfrom.GetId()); - } + const CBlockIndex* pindex = m_chainman.m_blockman.LookupBlockIndex(inv.hash); + if (!pindex) { + return; + } + if (!BlockRequestAllowed(pindex)) { + LogPrint(BCLog::NET, "%s: ignoring request from peer=%i for old block that isn't in the main chain\n", __func__, pfrom.GetId()); + return; } const CNetMsgMaker msgMaker(pfrom.GetCommonVersion()); // disconnect node in case we have reached the outbound limit for serving historical blocks - if (send && - connman.OutboundTargetReached(true) && + if (m_connman.OutboundTargetReached(true) && (((pindexBestHeader != nullptr) && (pindexBestHeader->GetBlockTime() - pindex->GetBlockTime() > HISTORICAL_BLOCK_AGE)) || inv.IsMsgFilteredBlk()) && !pfrom.HasPermission(PF_DOWNLOAD) // nodes with the download permission may exceed target ) { LogPrint(BCLog::NET, "historical block serving limit reached, disconnect peer=%d\n", pfrom.GetId()); - - //disconnect node pfrom.fDisconnect = true; - send = false; + return; } // Avoid leaking prune-height by never sending blocks below the NODE_NETWORK_LIMITED threshold - if (send && !pfrom.HasPermission(PF_NOBAN) && ( - (((pfrom.GetLocalServices() & NODE_NETWORK_LIMITED) == NODE_NETWORK_LIMITED) && ((pfrom.GetLocalServices() & NODE_NETWORK) != NODE_NETWORK) && (::ChainActive().Tip()->nHeight - pindex->nHeight > (int)NODE_NETWORK_LIMITED_MIN_BLOCKS + 2 /* add two blocks buffer extension for possible races */) ) + if (!pfrom.HasPermission(PF_NOBAN) && ( + (((pfrom.GetLocalServices() & NODE_NETWORK_LIMITED) == NODE_NETWORK_LIMITED) && ((pfrom.GetLocalServices() & NODE_NETWORK) != NODE_NETWORK) && (m_chainman.ActiveChain().Tip()->nHeight - pindex->nHeight > (int)NODE_NETWORK_LIMITED_MIN_BLOCKS + 2 /* add two blocks buffer extension for possible races */) ) )) { - LogPrint(BCLog::NET, "Ignore block request below NODE_NETWORK_LIMITED threshold from peer=%d\n", pfrom.GetId()); - + LogPrint(BCLog::NET, "Ignore block request below NODE_NETWORK_LIMITED threshold, disconnect peer=%d\n", pfrom.GetId()); //disconnect node and prevent it from stalling (would otherwise wait for the missing block) pfrom.fDisconnect = true; - send = false; + return; } // Pruned nodes may have deleted the block, so check whether // it's available before trying to send. - if (send && (pindex->nStatus & BLOCK_HAVE_DATA)) - { - std::shared_ptr<const CBlock> pblock; - if (a_recent_block && a_recent_block->GetHash() == pindex->GetBlockHash()) { - pblock = a_recent_block; - } else if (inv.IsMsgWitnessBlk()) { - // Fast-path: in this case it is possible to serve the block directly from disk, - // as the network format matches the format on disk - std::vector<uint8_t> block_data; - if (!ReadRawBlockFromDisk(block_data, pindex, chainparams.MessageStart())) { - assert(!"cannot load block from disk"); - } - connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::BLOCK, MakeSpan(block_data))); - // Don't set pblock as we've sent the block - } else { - // Send block from disk - std::shared_ptr<CBlock> pblockRead = std::make_shared<CBlock>(); - if (!ReadBlockFromDisk(*pblockRead, pindex, consensusParams)) - assert(!"cannot load block from disk"); - pblock = pblockRead; + if (!(pindex->nStatus & BLOCK_HAVE_DATA)) { + return; + } + std::shared_ptr<const CBlock> pblock; + if (a_recent_block && a_recent_block->GetHash() == pindex->GetBlockHash()) { + pblock = a_recent_block; + } else if (inv.IsMsgWitnessBlk()) { + // Fast-path: in this case it is possible to serve the block directly from disk, + // as the network format matches the format on disk + std::vector<uint8_t> block_data; + if (!ReadRawBlockFromDisk(block_data, pindex, m_chainparams.MessageStart())) { + assert(!"cannot load block from disk"); + } + m_connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::BLOCK, MakeSpan(block_data))); + // Don't set pblock as we've sent the block + } else { + // Send block from disk + std::shared_ptr<CBlock> pblockRead = std::make_shared<CBlock>(); + if (!ReadBlockFromDisk(*pblockRead, pindex, m_chainparams.GetConsensus())) { + assert(!"cannot load block from disk"); } - if (pblock) { - if (inv.IsMsgBlk()) { - connman.PushMessage(&pfrom, msgMaker.Make(SERIALIZE_TRANSACTION_NO_WITNESS, NetMsgType::BLOCK, *pblock)); - } else if (inv.IsMsgWitnessBlk()) { - connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::BLOCK, *pblock)); - } else if (inv.IsMsgFilteredBlk()) { - bool sendMerkleBlock = false; - CMerkleBlock merkleBlock; - if (pfrom.m_tx_relay != nullptr) { - LOCK(pfrom.m_tx_relay->cs_filter); - if (pfrom.m_tx_relay->pfilter) { - sendMerkleBlock = true; - merkleBlock = CMerkleBlock(*pblock, *pfrom.m_tx_relay->pfilter); - } - } - if (sendMerkleBlock) { - connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::MERKLEBLOCK, merkleBlock)); - // CMerkleBlock just contains hashes, so also push any transactions in the block the client did not see - // This avoids hurting performance by pointlessly requiring a round-trip - // Note that there is currently no way for a node to request any single transactions we didn't send here - - // they must either disconnect and retry or request the full block. - // Thus, the protocol spec specified allows for us to provide duplicate txn here, - // however we MUST always provide at least what the remote peer needs - typedef std::pair<unsigned int, uint256> PairType; - for (PairType& pair : merkleBlock.vMatchedTxn) - connman.PushMessage(&pfrom, msgMaker.Make(SERIALIZE_TRANSACTION_NO_WITNESS, NetMsgType::TX, *pblock->vtx[pair.first])); + pblock = pblockRead; + } + if (pblock) { + if (inv.IsMsgBlk()) { + m_connman.PushMessage(&pfrom, msgMaker.Make(SERIALIZE_TRANSACTION_NO_WITNESS, NetMsgType::BLOCK, *pblock)); + } else if (inv.IsMsgWitnessBlk()) { + m_connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::BLOCK, *pblock)); + } else if (inv.IsMsgFilteredBlk()) { + bool sendMerkleBlock = false; + CMerkleBlock merkleBlock; + if (pfrom.m_tx_relay != nullptr) { + LOCK(pfrom.m_tx_relay->cs_filter); + if (pfrom.m_tx_relay->pfilter) { + sendMerkleBlock = true; + merkleBlock = CMerkleBlock(*pblock, *pfrom.m_tx_relay->pfilter); } - // else - // no response - } else if (inv.IsMsgCmpctBlk()) { - // If a peer is asking for old blocks, we're almost guaranteed - // they won't have a useful mempool to match against a compact block, - // and we don't feel like constructing the object for them, so - // instead we respond with the full, non-compact block. - bool fPeerWantsWitness = State(pfrom.GetId())->fWantsCmpctWitness; - int nSendFlags = fPeerWantsWitness ? 0 : SERIALIZE_TRANSACTION_NO_WITNESS; - if (CanDirectFetch(consensusParams) && pindex->nHeight >= ::ChainActive().Height() - MAX_CMPCTBLOCK_DEPTH) { - if ((fPeerWantsWitness || !fWitnessesPresentInARecentCompactBlock) && a_recent_compact_block && a_recent_compact_block->header.GetHash() == pindex->GetBlockHash()) { - connman.PushMessage(&pfrom, msgMaker.Make(nSendFlags, NetMsgType::CMPCTBLOCK, *a_recent_compact_block)); - } else { - CBlockHeaderAndShortTxIDs cmpctblock(*pblock, fPeerWantsWitness); - connman.PushMessage(&pfrom, msgMaker.Make(nSendFlags, NetMsgType::CMPCTBLOCK, cmpctblock)); - } + } + if (sendMerkleBlock) { + m_connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::MERKLEBLOCK, merkleBlock)); + // CMerkleBlock just contains hashes, so also push any transactions in the block the client did not see + // This avoids hurting performance by pointlessly requiring a round-trip + // Note that there is currently no way for a node to request any single transactions we didn't send here - + // they must either disconnect and retry or request the full block. + // Thus, the protocol spec specified allows for us to provide duplicate txn here, + // however we MUST always provide at least what the remote peer needs + typedef std::pair<unsigned int, uint256> PairType; + for (PairType& pair : merkleBlock.vMatchedTxn) + m_connman.PushMessage(&pfrom, msgMaker.Make(SERIALIZE_TRANSACTION_NO_WITNESS, NetMsgType::TX, *pblock->vtx[pair.first])); + } + // else + // no response + } else if (inv.IsMsgCmpctBlk()) { + // If a peer is asking for old blocks, we're almost guaranteed + // they won't have a useful mempool to match against a compact block, + // and we don't feel like constructing the object for them, so + // instead we respond with the full, non-compact block. + bool fPeerWantsWitness = State(pfrom.GetId())->fWantsCmpctWitness; + int nSendFlags = fPeerWantsWitness ? 0 : SERIALIZE_TRANSACTION_NO_WITNESS; + if (CanDirectFetch() && pindex->nHeight >= m_chainman.ActiveChain().Height() - MAX_CMPCTBLOCK_DEPTH) { + if ((fPeerWantsWitness || !fWitnessesPresentInARecentCompactBlock) && a_recent_compact_block && a_recent_compact_block->header.GetHash() == pindex->GetBlockHash()) { + m_connman.PushMessage(&pfrom, msgMaker.Make(nSendFlags, NetMsgType::CMPCTBLOCK, *a_recent_compact_block)); } else { - connman.PushMessage(&pfrom, msgMaker.Make(nSendFlags, NetMsgType::BLOCK, *pblock)); + CBlockHeaderAndShortTxIDs cmpctblock(*pblock, fPeerWantsWitness); + m_connman.PushMessage(&pfrom, msgMaker.Make(nSendFlags, NetMsgType::CMPCTBLOCK, cmpctblock)); } + } else { + m_connman.PushMessage(&pfrom, msgMaker.Make(nSendFlags, NetMsgType::BLOCK, *pblock)); } } + } - { - LOCK(peer.m_block_inv_mutex); - // Trigger the peer node to send a getblocks request for the next batch of inventory - if (inv.hash == peer.m_continuation_block) { - // Send immediately. This must send even if redundant, - // and we want it right after the last block so they don't - // wait for other stuff first. - std::vector<CInv> vInv; - vInv.push_back(CInv(MSG_BLOCK, ::ChainActive().Tip()->GetBlockHash())); - connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::INV, vInv)); - peer.m_continuation_block.SetNull(); - } + { + LOCK(peer.m_block_inv_mutex); + // Trigger the peer node to send a getblocks request for the next batch of inventory + if (inv.hash == peer.m_continuation_block) { + // Send immediately. This must send even if redundant, + // and we want it right after the last block so they don't + // wait for other stuff first. + std::vector<CInv> vInv; + vInv.push_back(CInv(MSG_BLOCK, m_chainman.ActiveChain().Tip()->GetBlockHash())); + m_connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::INV, vInv)); + peer.m_continuation_block.SetNull(); } } } @@ -1978,7 +1823,7 @@ void PeerManagerImpl::ProcessGetData(CNode& pfrom, Peer& peer, const std::atomic if (it != peer.m_getdata_requests.end() && !pfrom.fPauseSend) { const CInv &inv = *it++; if (inv.IsGenBlkMsg()) { - ProcessGetBlockData(pfrom, peer, m_chainparams, inv, m_connman); + ProcessGetBlockData(pfrom, peer, inv); } // else: If the first item on the queue is an unknown type, we erase it // and continue processing the queue on the next call. @@ -2055,9 +1900,9 @@ void PeerManagerImpl::ProcessHeadersMessage(CNode& pfrom, const Peer& peer, // don't connect before giving DoS points // - Once a headers message is received that is valid and does connect, // nUnconnectingHeaders gets reset back to 0. - if (!g_chainman.m_blockman.LookupBlockIndex(headers[0].hashPrevBlock) && nCount < MAX_BLOCKS_TO_ANNOUNCE) { + if (!m_chainman.m_blockman.LookupBlockIndex(headers[0].hashPrevBlock) && nCount < MAX_BLOCKS_TO_ANNOUNCE) { nodestate->nUnconnectingHeaders++; - m_connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::GETHEADERS, ::ChainActive().GetLocator(pindexBestHeader), uint256())); + m_connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::GETHEADERS, m_chainman.ActiveChain().GetLocator(pindexBestHeader), uint256())); LogPrint(BCLog::NET, "received header %s: missing prev block %s, sending getheaders (%d) to end (peer=%d, nUnconnectingHeaders=%d)\n", headers[0].GetHash().ToString(), headers[0].hashPrevBlock.ToString(), @@ -2085,7 +1930,7 @@ void PeerManagerImpl::ProcessHeadersMessage(CNode& pfrom, const Peer& peer, // If we don't have the last header, then they'll have given us // something new (if these headers are valid). - if (!g_chainman.m_blockman.LookupBlockIndex(hashLastBlock)) { + if (!m_chainman.m_blockman.LookupBlockIndex(hashLastBlock)) { received_new_header = true; } } @@ -2113,27 +1958,26 @@ void PeerManagerImpl::ProcessHeadersMessage(CNode& pfrom, const Peer& peer, // because it is set in UpdateBlockAvailability. Some nullptr checks // are still present, however, as belt-and-suspenders. - if (received_new_header && pindexLast->nChainWork > ::ChainActive().Tip()->nChainWork) { + if (received_new_header && pindexLast->nChainWork > m_chainman.ActiveChain().Tip()->nChainWork) { nodestate->m_last_block_announcement = GetTime(); } if (nCount == MAX_HEADERS_RESULTS) { // Headers message had its maximum size; the peer may have more headers. - // TODO: optimize: if pindexLast is an ancestor of ::ChainActive().Tip or pindexBestHeader, continue + // TODO: optimize: if pindexLast is an ancestor of m_chainman.ActiveChain().Tip or pindexBestHeader, continue // from there instead. LogPrint(BCLog::NET, "more getheaders (%d) to end to peer=%d (startheight:%d)\n", pindexLast->nHeight, pfrom.GetId(), peer.m_starting_height); - m_connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::GETHEADERS, ::ChainActive().GetLocator(pindexLast), uint256())); + m_connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::GETHEADERS, m_chainman.ActiveChain().GetLocator(pindexLast), uint256())); } - bool fCanDirectFetch = CanDirectFetch(m_chainparams.GetConsensus()); // If this set of headers is valid and ends in a block with at least as // much work as our tip, download as much as possible. - if (fCanDirectFetch && pindexLast->IsValid(BLOCK_VALID_TREE) && ::ChainActive().Tip()->nChainWork <= pindexLast->nChainWork) { + if (CanDirectFetch() && pindexLast->IsValid(BLOCK_VALID_TREE) && m_chainman.ActiveChain().Tip()->nChainWork <= pindexLast->nChainWork) { std::vector<const CBlockIndex*> vToFetch; const CBlockIndex *pindexWalk = pindexLast; // Calculate all the blocks we'd need to switch to pindexLast, up to a limit. - while (pindexWalk && !::ChainActive().Contains(pindexWalk) && vToFetch.size() <= MAX_BLOCKS_IN_TRANSIT_PER_PEER) { + while (pindexWalk && !m_chainman.ActiveChain().Contains(pindexWalk) && vToFetch.size() <= MAX_BLOCKS_IN_TRANSIT_PER_PEER) { if (!(pindexWalk->nStatus & BLOCK_HAVE_DATA) && !mapBlocksInFlight.count(pindexWalk->GetBlockHash()) && (!IsWitnessEnabled(pindexWalk->pprev, m_chainparams.GetConsensus()) || State(pfrom.GetId())->fHaveWitness)) { @@ -2146,7 +1990,7 @@ void PeerManagerImpl::ProcessHeadersMessage(CNode& pfrom, const Peer& peer, // very large reorg at a time we think we're close to caught up to // the main chain -- this shouldn't really happen. Bail out on the // direct fetch and rely on parallel download instead. - if (!::ChainActive().Contains(pindexWalk)) { + if (!m_chainman.ActiveChain().Contains(pindexWalk)) { LogPrint(BCLog::NET, "Large reorg, won't direct fetch to %s (%d)\n", pindexLast->GetBlockHash().ToString(), pindexLast->nHeight); @@ -2179,7 +2023,7 @@ void PeerManagerImpl::ProcessHeadersMessage(CNode& pfrom, const Peer& peer, } // If we're in IBD, we want outbound peers that will serve us a useful // chain. Disconnect peers that are on chains with insufficient work. - if (::ChainstateActive().IsInitialBlockDownload() && nCount != MAX_HEADERS_RESULTS) { + if (m_chainman.ActiveChainstate().IsInitialBlockDownload() && nCount != MAX_HEADERS_RESULTS) { // When nCount < MAX_HEADERS_RESULTS, we know we have no more // headers to fetch from this peer. if (nodestate->pindexBestKnownBlock && nodestate->pindexBestKnownBlock->nChainWork < nMinimumChainWork) { @@ -2187,7 +2031,7 @@ void PeerManagerImpl::ProcessHeadersMessage(CNode& pfrom, const Peer& peer, // us sync -- disconnect if it is an outbound disconnection // candidate. // Note: We compare their tip to nMinimumChainWork (rather than - // ::ChainActive().Tip()) because we won't start block download + // m_chainman.ActiveChain().Tip()) because we won't start block download // until we have a headers chain that has at least // nMinimumChainWork, even if a peer has a chain past our tip, // as an anti-DoS measure. @@ -2204,7 +2048,7 @@ void PeerManagerImpl::ProcessHeadersMessage(CNode& pfrom, const Peer& peer, // thus always subject to eviction under the bad/lagging chain logic. // See ChainSyncTimeoutState. if (!pfrom.fDisconnect && pfrom.IsFullOutboundConn() && nodestate->pindexBestKnownBlock != nullptr) { - if (m_outbound_peers_with_protect_from_disconnect < MAX_OUTBOUND_PEERS_TO_PROTECT_FROM_DISCONNECT && nodestate->pindexBestKnownBlock->nChainWork >= ::ChainActive().Tip()->nChainWork && !nodestate->m_chain_sync.m_protect) { + if (m_outbound_peers_with_protect_from_disconnect < MAX_OUTBOUND_PEERS_TO_PROTECT_FROM_DISCONNECT && nodestate->pindexBestKnownBlock->nChainWork >= m_chainman.ActiveChain().Tip()->nChainWork && !nodestate->m_chain_sync.m_protect) { LogPrint(BCLog::NET, "Protecting outbound peer=%d from eviction\n", pfrom.GetId()); nodestate->m_chain_sync.m_protect = true; ++m_outbound_peers_with_protect_from_disconnect; @@ -2232,25 +2076,17 @@ void PeerManagerImpl::ProcessOrphanTx(std::set<uint256>& orphan_work_set) const uint256 orphanHash = *orphan_work_set.begin(); orphan_work_set.erase(orphan_work_set.begin()); - auto orphan_it = mapOrphanTransactions.find(orphanHash); - if (orphan_it == mapOrphanTransactions.end()) continue; + const auto [porphanTx, from_peer] = m_orphanage.GetTx(orphanHash); + if (porphanTx == nullptr) continue; - const CTransactionRef porphanTx = orphan_it->second.tx; - const MempoolAcceptResult result = AcceptToMemoryPool(::ChainstateActive(), m_mempool, porphanTx, false /* bypass_limits */); + const MempoolAcceptResult result = AcceptToMemoryPool(m_chainman.ActiveChainstate(), m_mempool, porphanTx, false /* bypass_limits */); const TxValidationState& state = result.m_state; if (result.m_result_type == MempoolAcceptResult::ResultType::VALID) { LogPrint(BCLog::MEMPOOL, " accepted orphan tx %s\n", orphanHash.ToString()); - RelayTransaction(orphanHash, porphanTx->GetWitnessHash(), m_connman); - for (unsigned int i = 0; i < porphanTx->vout.size(); i++) { - auto it_by_prev = mapOrphanTransactionsByPrev.find(COutPoint(orphanHash, i)); - if (it_by_prev != mapOrphanTransactionsByPrev.end()) { - for (const auto& elem : it_by_prev->second) { - orphan_work_set.insert(elem->first); - } - } - } - EraseOrphanTx(orphanHash); + RelayTransaction(orphanHash, porphanTx->GetWitnessHash()); + m_orphanage.AddChildrenToWorkSet(*porphanTx, orphan_work_set); + m_orphanage.EraseTx(orphanHash); for (const CTransactionRef& removedTx : result.m_replaced_transactions.value()) { AddToCompactExtraTransactions(removedTx); } @@ -2259,10 +2095,10 @@ void PeerManagerImpl::ProcessOrphanTx(std::set<uint256>& orphan_work_set) if (state.IsInvalid()) { LogPrint(BCLog::MEMPOOL, " invalid orphan tx %s from peer=%d. %s\n", orphanHash.ToString(), - orphan_it->second.fromPeer, + from_peer, state.ToString()); // Maybe punish peer that gave us an invalid orphan tx - MaybePunishNodeForTx(orphan_it->second.fromPeer, state); + MaybePunishNodeForTx(from_peer, state); } // Has inputs but not accepted to mempool // Probably non-standard or insufficient fee @@ -2297,33 +2133,18 @@ void PeerManagerImpl::ProcessOrphanTx(std::set<uint256>& orphan_work_set) recentRejects->insert(porphanTx->GetHash()); } } - EraseOrphanTx(orphanHash); + m_orphanage.EraseTx(orphanHash); break; } } - m_mempool.check(&::ChainstateActive().CoinsTip()); + m_mempool.check(m_chainman.ActiveChainstate()); } -/** - * Validation logic for compact filters request handling. - * - * May disconnect from the peer in the case of a bad request. - * - * @param[in] peer The peer that we received the request from - * @param[in] chain_params Chain parameters - * @param[in] filter_type The filter type the request is for. Must be basic filters. - * @param[in] start_height The start height for the request - * @param[in] stop_hash The stop_hash for the request - * @param[in] max_height_diff The maximum number of items permitted to request, as specified in BIP 157 - * @param[out] stop_index The CBlockIndex for the stop_hash block, if the request can be serviced. - * @param[out] filter_index The filter index, if the request can be serviced. - * @return True if the request can be serviced. - */ -static bool PrepareBlockFilterRequest(CNode& peer, const CChainParams& chain_params, - BlockFilterType filter_type, uint32_t start_height, - const uint256& stop_hash, uint32_t max_height_diff, - const CBlockIndex*& stop_index, - BlockFilterIndex*& filter_index) +bool PeerManagerImpl::PrepareBlockFilterRequest(CNode& peer, + BlockFilterType filter_type, uint32_t start_height, + const uint256& stop_hash, uint32_t max_height_diff, + const CBlockIndex*& stop_index, + BlockFilterIndex*& filter_index) { const bool supported_filter_type = (filter_type == BlockFilterType::BASIC && @@ -2337,10 +2158,10 @@ static bool PrepareBlockFilterRequest(CNode& peer, const CChainParams& chain_par { LOCK(cs_main); - stop_index = g_chainman.m_blockman.LookupBlockIndex(stop_hash); + stop_index = m_chainman.m_blockman.LookupBlockIndex(stop_hash); // Check that the stop block exists and the peer would be allowed to fetch it. - if (!stop_index || !BlockRequestAllowed(stop_index, chain_params.GetConsensus())) { + if (!stop_index || !BlockRequestAllowed(stop_index)) { LogPrint(BCLog::NET, "peer %d requested invalid block hash: %s\n", peer.GetId(), stop_hash.ToString()); peer.fDisconnect = true; @@ -2372,18 +2193,7 @@ static bool PrepareBlockFilterRequest(CNode& peer, const CChainParams& chain_par return true; } -/** - * Handle a cfilters request. - * - * May disconnect from the peer in the case of a bad request. - * - * @param[in] peer The peer that we received the request from - * @param[in] vRecv The raw message received - * @param[in] chain_params Chain parameters - * @param[in] connman Pointer to the connection manager - */ -static void ProcessGetCFilters(CNode& peer, CDataStream& vRecv, const CChainParams& chain_params, - CConnman& connman) +void PeerManagerImpl::ProcessGetCFilters(CNode& peer, CDataStream& vRecv) { uint8_t filter_type_ser; uint32_t start_height; @@ -2395,7 +2205,7 @@ static void ProcessGetCFilters(CNode& peer, CDataStream& vRecv, const CChainPara const CBlockIndex* stop_index; BlockFilterIndex* filter_index; - if (!PrepareBlockFilterRequest(peer, chain_params, filter_type, start_height, stop_hash, + if (!PrepareBlockFilterRequest(peer, filter_type, start_height, stop_hash, MAX_GETCFILTERS_SIZE, stop_index, filter_index)) { return; } @@ -2410,22 +2220,11 @@ static void ProcessGetCFilters(CNode& peer, CDataStream& vRecv, const CChainPara for (const auto& filter : filters) { CSerializedNetMsg msg = CNetMsgMaker(peer.GetCommonVersion()) .Make(NetMsgType::CFILTER, filter); - connman.PushMessage(&peer, std::move(msg)); + m_connman.PushMessage(&peer, std::move(msg)); } } -/** - * Handle a cfheaders request. - * - * May disconnect from the peer in the case of a bad request. - * - * @param[in] peer The peer that we received the request from - * @param[in] vRecv The raw message received - * @param[in] chain_params Chain parameters - * @param[in] connman Pointer to the connection manager - */ -static void ProcessGetCFHeaders(CNode& peer, CDataStream& vRecv, const CChainParams& chain_params, - CConnman& connman) +void PeerManagerImpl::ProcessGetCFHeaders(CNode& peer, CDataStream& vRecv) { uint8_t filter_type_ser; uint32_t start_height; @@ -2437,7 +2236,7 @@ static void ProcessGetCFHeaders(CNode& peer, CDataStream& vRecv, const CChainPar const CBlockIndex* stop_index; BlockFilterIndex* filter_index; - if (!PrepareBlockFilterRequest(peer, chain_params, filter_type, start_height, stop_hash, + if (!PrepareBlockFilterRequest(peer, filter_type, start_height, stop_hash, MAX_GETCFHEADERS_SIZE, stop_index, filter_index)) { return; } @@ -2466,21 +2265,10 @@ static void ProcessGetCFHeaders(CNode& peer, CDataStream& vRecv, const CChainPar stop_index->GetBlockHash(), prev_header, filter_hashes); - connman.PushMessage(&peer, std::move(msg)); + m_connman.PushMessage(&peer, std::move(msg)); } -/** - * Handle a getcfcheckpt request. - * - * May disconnect from the peer in the case of a bad request. - * - * @param[in] peer The peer that we received the request from - * @param[in] vRecv The raw message received - * @param[in] chain_params Chain parameters - * @param[in] connman Pointer to the connection manager - */ -static void ProcessGetCFCheckPt(CNode& peer, CDataStream& vRecv, const CChainParams& chain_params, - CConnman& connman) +void PeerManagerImpl::ProcessGetCFCheckPt(CNode& peer, CDataStream& vRecv) { uint8_t filter_type_ser; uint256 stop_hash; @@ -2491,7 +2279,7 @@ static void ProcessGetCFCheckPt(CNode& peer, CDataStream& vRecv, const CChainPar const CBlockIndex* stop_index; BlockFilterIndex* filter_index; - if (!PrepareBlockFilterRequest(peer, chain_params, filter_type, /*start_height=*/0, stop_hash, + if (!PrepareBlockFilterRequest(peer, filter_type, /*start_height=*/0, stop_hash, /*max_height_diff=*/std::numeric_limits<uint32_t>::max(), stop_index, filter_index)) { return; @@ -2517,7 +2305,7 @@ static void ProcessGetCFCheckPt(CNode& peer, CDataStream& vRecv, const CChainPar filter_type_ser, stop_index->GetBlockHash(), headers); - connman.PushMessage(&peer, std::move(msg)); + m_connman.PushMessage(&peer, std::move(msg)); } void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, CDataStream& vRecv, @@ -2553,7 +2341,7 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, nServices = ServiceFlags(nServiceInt); if (!pfrom.IsInboundConn()) { - m_connman.SetServices(pfrom.addr, nServices); + m_addrman.SetServices(pfrom.addr, nServices); } if (pfrom.ExpectServicesFromConn() && !HasAllDesirableServiceFlags(nServices)) { @@ -2662,7 +2450,7 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, // // We skip this for block-relay-only peers to avoid potentially leaking // information about our block-relay-only connections via address relay. - if (fListen && !::ChainstateActive().IsInitialBlockDownload()) + if (fListen && !m_chainman.ActiveChainstate().IsInitialBlockDownload()) { CAddress addr = GetLocalAddress(&pfrom.addr, pfrom.GetLocalServices()); FastRandomContext insecure_rand; @@ -2697,7 +2485,7 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, // // This moves an address from New to Tried table in Addrman, // resolves tried-table collisions, etc. - m_connman.MarkAddressGood(pfrom.addr); + m_addrman.Good(pfrom.addr); } std::string remoteAddr; @@ -2902,7 +2690,7 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, if (fReachable) vAddrOk.push_back(addr); } - m_connman.AddNewAddresses(vAddrOk, pfrom.addr, 2 * 60 * 60); + m_addrman.Add(vAddrOk, pfrom.addr, 2 * 60 * 60); if (vAddr.size() < 1000) pfrom.fGetAddr = false; if (pfrom.IsAddrFetchConn()) { @@ -2979,7 +2767,7 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, } if (best_block != nullptr) { - m_connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::GETHEADERS, ::ChainActive().GetLocator(pindexBestHeader), *best_block)); + m_connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::GETHEADERS, m_chainman.ActiveChain().GetLocator(pindexBestHeader), *best_block)); LogPrint(BCLog::NET, "getheaders (%d) %s to peer=%d\n", pindexBestHeader->nHeight, best_block->ToString(), pfrom.GetId()); } @@ -3035,7 +2823,7 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, a_recent_block = most_recent_block; } BlockValidationState state; - if (!::ChainstateActive().ActivateBestChain(state, m_chainparams, a_recent_block)) { + if (!m_chainman.ActiveChainstate().ActivateBestChain(state, m_chainparams, a_recent_block)) { LogPrint(BCLog::NET, "failed to activate chain (%s)\n", state.ToString()); } } @@ -3043,14 +2831,14 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, LOCK(cs_main); // Find the last block the caller has in the main chain - const CBlockIndex* pindex = g_chainman.m_blockman.FindForkInGlobalIndex(::ChainActive(), locator); + const CBlockIndex* pindex = m_chainman.m_blockman.FindForkInGlobalIndex(m_chainman.ActiveChain(), locator); // Send the rest of the chain if (pindex) - pindex = ::ChainActive().Next(pindex); + pindex = m_chainman.ActiveChain().Next(pindex); int nLimit = 500; LogPrint(BCLog::NET, "getblocks %d to %s limit %d from peer=%d\n", (pindex ? pindex->nHeight : -1), hashStop.IsNull() ? "end" : hashStop.ToString(), nLimit, pfrom.GetId()); - for (; pindex; pindex = ::ChainActive().Next(pindex)) + for (; pindex; pindex = m_chainman.ActiveChain().Next(pindex)) { if (pindex->GetBlockHash() == hashStop) { @@ -3060,7 +2848,7 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, // If pruning, don't inv blocks unless we have on disk and are likely to still have // for some reasonable time window (1 hour) that block relay might require. const int nPrunedBlocksLikelyToHave = MIN_BLOCKS_TO_KEEP - 3600 / m_chainparams.GetConsensus().nPowTargetSpacing; - if (fPruneMode && (!(pindex->nStatus & BLOCK_HAVE_DATA) || pindex->nHeight <= ::ChainActive().Tip()->nHeight - nPrunedBlocksLikelyToHave)) + if (fPruneMode && (!(pindex->nStatus & BLOCK_HAVE_DATA) || pindex->nHeight <= m_chainman.ActiveChain().Tip()->nHeight - nPrunedBlocksLikelyToHave)) { LogPrint(BCLog::NET, " getblocks stopping, pruned or too old block at %d %s\n", pindex->nHeight, pindex->GetBlockHash().ToString()); break; @@ -3096,13 +2884,13 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, { LOCK(cs_main); - const CBlockIndex* pindex = g_chainman.m_blockman.LookupBlockIndex(req.blockhash); + const CBlockIndex* pindex = m_chainman.m_blockman.LookupBlockIndex(req.blockhash); if (!pindex || !(pindex->nStatus & BLOCK_HAVE_DATA)) { LogPrint(BCLog::NET, "Peer %d sent us a getblocktxn for a block we don't have\n", pfrom.GetId()); return; } - if (pindex->nHeight >= ::ChainActive().Height() - MAX_BLOCKTXN_DEPTH) { + if (pindex->nHeight >= m_chainman.ActiveChain().Height() - MAX_BLOCKTXN_DEPTH) { CBlock block; bool ret = ReadBlockFromDisk(block, pindex, m_chainparams.GetConsensus()); assert(ret); @@ -3140,7 +2928,7 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, } LOCK(cs_main); - if (::ChainstateActive().IsInitialBlockDownload() && !pfrom.HasPermission(PF_DOWNLOAD)) { + if (m_chainman.ActiveChainstate().IsInitialBlockDownload() && !pfrom.HasPermission(PF_DOWNLOAD)) { LogPrint(BCLog::NET, "Ignoring getheaders from peer=%d because node is in initial block download\n", pfrom.GetId()); return; } @@ -3150,12 +2938,12 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, if (locator.IsNull()) { // If locator is null, return the hashStop block - pindex = g_chainman.m_blockman.LookupBlockIndex(hashStop); + pindex = m_chainman.m_blockman.LookupBlockIndex(hashStop); if (!pindex) { return; } - if (!BlockRequestAllowed(pindex, m_chainparams.GetConsensus())) { + if (!BlockRequestAllowed(pindex)) { LogPrint(BCLog::NET, "%s: ignoring request from peer=%i for old block header that isn't in the main chain\n", __func__, pfrom.GetId()); return; } @@ -3163,23 +2951,23 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, else { // Find the last block the caller has in the main chain - pindex = g_chainman.m_blockman.FindForkInGlobalIndex(::ChainActive(), locator); + pindex = m_chainman.m_blockman.FindForkInGlobalIndex(m_chainman.ActiveChain(), locator); if (pindex) - pindex = ::ChainActive().Next(pindex); + pindex = m_chainman.ActiveChain().Next(pindex); } // we must use CBlocks, as CBlockHeaders won't include the 0x00 nTx count at the end std::vector<CBlock> vHeaders; int nLimit = MAX_HEADERS_RESULTS; LogPrint(BCLog::NET, "getheaders %d to %s from peer=%d\n", (pindex ? pindex->nHeight : -1), hashStop.IsNull() ? "end" : hashStop.ToString(), pfrom.GetId()); - for (; pindex; pindex = ::ChainActive().Next(pindex)) + for (; pindex; pindex = m_chainman.ActiveChain().Next(pindex)) { vHeaders.push_back(pindex->GetBlockHeader()); if (--nLimit <= 0 || pindex->GetBlockHash() == hashStop) break; } - // pindex can be nullptr either if we sent ::ChainActive().Tip() OR - // if our peer has ::ChainActive().Tip() (and thus we are sending an empty + // pindex can be nullptr either if we sent m_chainman.ActiveChain().Tip() OR + // if our peer has m_chainman.ActiveChain().Tip() (and thus we are sending an empty // headers message). In both cases it's safe to update // pindexBestHeaderSent to be our tip. // @@ -3190,7 +2978,7 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, // without the new block. By resetting the BestHeaderSent, we ensure we // will re-announce the new block via headers (or compact blocks again) // in the SendMessages logic. - nodestate->pindexBestHeaderSent = pindex ? pindex : ::ChainActive().Tip(); + nodestate->pindexBestHeaderSent = pindex ? pindex : m_chainman.ActiveChain().Tip(); m_connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::HEADERS, vHeaders)); return; } @@ -3252,30 +3040,23 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, LogPrintf("Not relaying non-mempool transaction %s from forcerelay peer=%d\n", tx.GetHash().ToString(), pfrom.GetId()); } else { LogPrintf("Force relaying tx %s from peer=%d\n", tx.GetHash().ToString(), pfrom.GetId()); - RelayTransaction(tx.GetHash(), tx.GetWitnessHash(), m_connman); + RelayTransaction(tx.GetHash(), tx.GetWitnessHash()); } } return; } - const MempoolAcceptResult result = AcceptToMemoryPool(::ChainstateActive(), m_mempool, ptx, false /* bypass_limits */); + const MempoolAcceptResult result = AcceptToMemoryPool(m_chainman.ActiveChainstate(), m_mempool, ptx, false /* bypass_limits */); const TxValidationState& state = result.m_state; if (result.m_result_type == MempoolAcceptResult::ResultType::VALID) { - m_mempool.check(&::ChainstateActive().CoinsTip()); + m_mempool.check(m_chainman.ActiveChainstate()); // As this version of the transaction was acceptable, we can forget about any // requests for it. m_txrequest.ForgetTxHash(tx.GetHash()); m_txrequest.ForgetTxHash(tx.GetWitnessHash()); - RelayTransaction(tx.GetHash(), tx.GetWitnessHash(), m_connman); - for (unsigned int i = 0; i < tx.vout.size(); i++) { - auto it_by_prev = mapOrphanTransactionsByPrev.find(COutPoint(txid, i)); - if (it_by_prev != mapOrphanTransactionsByPrev.end()) { - for (const auto& elem : it_by_prev->second) { - peer->m_orphan_work_set.insert(elem->first); - } - } - } + RelayTransaction(tx.GetHash(), tx.GetWitnessHash()); + m_orphanage.AddChildrenToWorkSet(tx, peer->m_orphan_work_set); pfrom.nLastTXTime = GetTime(); @@ -3324,17 +3105,20 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, pfrom.AddKnownTx(parent_txid); if (!AlreadyHaveTx(gtxid)) AddTxAnnouncement(pfrom, gtxid, current_time); } - AddOrphanTx(ptx, pfrom.GetId()); + + if (m_orphanage.AddTx(ptx, pfrom.GetId())) { + AddToCompactExtraTransactions(ptx); + } // Once added to the orphan pool, a tx is considered AlreadyHave, and we shouldn't request it anymore. m_txrequest.ForgetTxHash(tx.GetHash()); m_txrequest.ForgetTxHash(tx.GetWitnessHash()); - // DoS prevention: do not allow mapOrphanTransactions to grow unbounded (see CVE-2012-3789) + // DoS prevention: do not allow m_orphanage to grow unbounded (see CVE-2012-3789) unsigned int nMaxOrphanTx = (unsigned int)std::max((int64_t)0, gArgs.GetArg("-maxorphantx", DEFAULT_MAX_ORPHAN_TRANSACTIONS)); - unsigned int nEvicted = LimitOrphanTxSize(nMaxOrphanTx); + unsigned int nEvicted = m_orphanage.LimitOrphans(nMaxOrphanTx); if (nEvicted > 0) { - LogPrint(BCLog::MEMPOOL, "mapOrphan overflow, removed %u tx\n", nEvicted); + LogPrint(BCLog::MEMPOOL, "orphanage overflow, removed %u tx\n", nEvicted); } } else { LogPrint(BCLog::MEMPOOL, "not keeping orphan with rejected parents %s\n",tx.GetHash().ToString()); @@ -3427,14 +3211,14 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, { LOCK(cs_main); - if (!g_chainman.m_blockman.LookupBlockIndex(cmpctblock.header.hashPrevBlock)) { + if (!m_chainman.m_blockman.LookupBlockIndex(cmpctblock.header.hashPrevBlock)) { // Doesn't connect (or is genesis), instead of DoSing in AcceptBlockHeader, request deeper headers - if (!::ChainstateActive().IsInitialBlockDownload()) - m_connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::GETHEADERS, ::ChainActive().GetLocator(pindexBestHeader), uint256())); + if (!m_chainman.ActiveChainstate().IsInitialBlockDownload()) + m_connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::GETHEADERS, m_chainman.ActiveChain().GetLocator(pindexBestHeader), uint256())); return; } - if (!g_chainman.m_blockman.LookupBlockIndex(cmpctblock.header.GetHash())) { + if (!m_chainman.m_blockman.LookupBlockIndex(cmpctblock.header.GetHash())) { received_new_header = true; } } @@ -3474,7 +3258,7 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, // If this was a new header with more work than our tip, update the // peer's last block announcement time - if (received_new_header && pindex->nChainWork > ::ChainActive().Tip()->nChainWork) { + if (received_new_header && pindex->nChainWork > m_chainman.ActiveChain().Tip()->nChainWork) { nodestate->m_last_block_announcement = GetTime(); } @@ -3484,7 +3268,7 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, if (pindex->nStatus & BLOCK_HAVE_DATA) // Nothing to do here return; - if (pindex->nChainWork <= ::ChainActive().Tip()->nChainWork || // We know something better + if (pindex->nChainWork <= m_chainman.ActiveChain().Tip()->nChainWork || // We know something better pindex->nTx != 0) { // We had this block at some point, but pruned it if (fAlreadyInFlight) { // We requested this block for some reason, but our mempool will probably be useless @@ -3497,8 +3281,9 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, } // If we're not close to tip yet, give up and let parallel block fetch work its magic - if (!fAlreadyInFlight && !CanDirectFetch(m_chainparams.GetConsensus())) + if (!fAlreadyInFlight && !CanDirectFetch()) { return; + } if (IsWitnessEnabled(pindex->pprev, m_chainparams.GetConsensus()) && !nodestate->fSupportsDesiredCmpctVersion) { // Don't bother trying to process compact blocks from v1 peers @@ -3508,7 +3293,7 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, // We want to be a bit conservative just to be extra careful about DoS // possibilities in compact block processing... - if (pindex->nHeight <= ::ChainActive().Height() + 2) { + if (pindex->nHeight <= m_chainman.ActiveChain().Height() + 2) { if ((!fAlreadyInFlight && nodestate->nBlocksInFlight < MAX_BLOCKS_IN_TRANSIT_PER_PEER) || (fAlreadyInFlight && blockInFlightIt->second.first == pfrom.GetId())) { std::list<QueuedBlock>::iterator* queuedBlockIt = nullptr; @@ -3991,17 +3776,17 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, } if (msg_type == NetMsgType::GETCFILTERS) { - ProcessGetCFilters(pfrom, vRecv, m_chainparams, m_connman); + ProcessGetCFilters(pfrom, vRecv); return; } if (msg_type == NetMsgType::GETCFHEADERS) { - ProcessGetCFHeaders(pfrom, vRecv, m_chainparams, m_connman); + ProcessGetCFHeaders(pfrom, vRecv); return; } if (msg_type == NetMsgType::GETCFCHECKPT) { - ProcessGetCFCheckPt(pfrom, vRecv, m_chainparams, m_connman); + ProcessGetCFCheckPt(pfrom, vRecv); return; } @@ -4156,7 +3941,7 @@ void PeerManagerImpl::ConsiderEviction(CNode& pto, int64_t time_in_seconds) // their chain has more work than ours, we should sync to it, // unless it's invalid, in which case we should find that out and // disconnect from them elsewhere). - if (state.pindexBestKnownBlock != nullptr && state.pindexBestKnownBlock->nChainWork >= ::ChainActive().Tip()->nChainWork) { + if (state.pindexBestKnownBlock != nullptr && state.pindexBestKnownBlock->nChainWork >= m_chainman.ActiveChain().Tip()->nChainWork) { if (state.m_chain_sync.m_timeout != 0) { state.m_chain_sync.m_timeout = 0; state.m_chain_sync.m_work_header = nullptr; @@ -4168,7 +3953,7 @@ void PeerManagerImpl::ConsiderEviction(CNode& pto, int64_t time_in_seconds) // where we checked against our tip. // Either way, set a new timeout based on current tip. state.m_chain_sync.m_timeout = time_in_seconds + CHAIN_SYNC_TIMEOUT; - state.m_chain_sync.m_work_header = ::ChainActive().Tip(); + state.m_chain_sync.m_work_header = m_chainman.ActiveChain().Tip(); state.m_chain_sync.m_sent_getheaders = false; } else if (state.m_chain_sync.m_timeout > 0 && time_in_seconds > state.m_chain_sync.m_timeout) { // No evidence yet that our peer has synced to a chain with work equal to that @@ -4181,7 +3966,7 @@ void PeerManagerImpl::ConsiderEviction(CNode& pto, int64_t time_in_seconds) } else { assert(state.m_chain_sync.m_work_header); LogPrint(BCLog::NET, "sending getheaders to outbound peer=%d to verify chain work (current best known block:%s, benchmark blockhash: %s)\n", pto.GetId(), state.pindexBestKnownBlock != nullptr ? state.pindexBestKnownBlock->GetBlockHash().ToString() : "<none>", state.m_chain_sync.m_work_header->GetBlockHash().ToString()); - m_connman.PushMessage(&pto, msgMaker.Make(NetMsgType::GETHEADERS, ::ChainActive().GetLocator(state.m_chain_sync.m_work_header->pprev), uint256())); + m_connman.PushMessage(&pto, msgMaker.Make(NetMsgType::GETHEADERS, m_chainman.ActiveChain().GetLocator(state.m_chain_sync.m_work_header->pprev), uint256())); state.m_chain_sync.m_sent_getheaders = true; constexpr int64_t HEADERS_RESPONSE_TIME = 120; // 2 minutes // Bump the timeout to allow a response, which could clear the timeout @@ -4316,18 +4101,14 @@ void PeerManagerImpl::CheckForStaleTipAndEvictPeers() m_stale_tip_check_time = time_in_seconds + STALE_CHECK_INTERVAL; } - if (!m_initial_sync_finished && CanDirectFetch(m_chainparams.GetConsensus())) { + if (!m_initial_sync_finished && CanDirectFetch()) { m_connman.StartExtraBlockRelayPeers(); m_initial_sync_finished = true; } } -void PeerManagerImpl::MaybeSendPing(CNode& node_to, Peer& peer) +void PeerManagerImpl::MaybeSendPing(CNode& node_to, Peer& peer, std::chrono::microseconds now) { - // Use mockable time for ping timeouts. - // This means that setmocktime may cause pings to time out. - auto now = GetTime<std::chrono::microseconds>(); - if (m_connman.RunInactivityChecks(node_to) && peer.m_ping_nonce_sent && now > peer.m_ping_start.load() + std::chrono::seconds{TIMEOUT_INTERVAL}) { LogPrint(BCLog::NET, "ping timeout: %fs peer=%d\n", 0.000001 * count_microseconds(now - peer.m_ping_start.load()), peer.m_id); @@ -4366,6 +4147,75 @@ void PeerManagerImpl::MaybeSendPing(CNode& node_to, Peer& peer) } } +void PeerManagerImpl::MaybeSendAddr(CNode& node, std::chrono::microseconds current_time) +{ + // Nothing to do for non-address-relay peers + if (!node.RelayAddrsWithConn()) return; + + assert(node.m_addr_known); + + LOCK(node.m_addr_send_times_mutex); + // Periodically advertise our local address to the peer. + if (fListen && !m_chainman.ActiveChainstate().IsInitialBlockDownload() && + node.m_next_local_addr_send < current_time) { + // If we've sent before, clear the bloom filter for the peer, so that our + // self-announcement will actually go out. + // This might be unnecessary if the bloom filter has already rolled + // over since our last self-announcement, but there is only a small + // bandwidth cost that we can incur by doing this (which happens + // once a day on average). + if (node.m_next_local_addr_send != 0us) { + node.m_addr_known->reset(); + } + if (std::optional<CAddress> local_addr = GetLocalAddrForPeer(&node)) { + FastRandomContext insecure_rand; + node.PushAddress(*local_addr, insecure_rand); + } + node.m_next_local_addr_send = PoissonNextSend(current_time, AVG_LOCAL_ADDRESS_BROADCAST_INTERVAL); + } + + // We sent an `addr` message to this peer recently. Nothing more to do. + if (current_time <= node.m_next_addr_send) return; + + node.m_next_addr_send = PoissonNextSend(current_time, AVG_ADDRESS_BROADCAST_INTERVAL); + + if (!Assume(node.vAddrToSend.size() <= MAX_ADDR_TO_SEND)) { + // Should be impossible since we always check size before adding to + // vAddrToSend. Recover by trimming the vector. + node.vAddrToSend.resize(MAX_ADDR_TO_SEND); + } + + // Remove addr records that the peer already knows about, and add new + // addrs to the m_addr_known filter on the same pass. + auto addr_already_known = [&node](const CAddress& addr) { + bool ret = node.m_addr_known->contains(addr.GetKey()); + if (!ret) node.m_addr_known->insert(addr.GetKey()); + return ret; + }; + node.vAddrToSend.erase(std::remove_if(node.vAddrToSend.begin(), node.vAddrToSend.end(), addr_already_known), + node.vAddrToSend.end()); + + // No addr messages to send + if (node.vAddrToSend.empty()) return; + + const char* msg_type; + int make_flags; + if (node.m_wants_addrv2) { + msg_type = NetMsgType::ADDRV2; + make_flags = ADDRV2_FORMAT; + } else { + msg_type = NetMsgType::ADDR; + make_flags = 0; + } + m_connman.PushMessage(&node, CNetMsgMaker(node.GetCommonVersion()).Make(make_flags, msg_type, node.vAddrToSend)); + node.vAddrToSend.clear(); + + // we only send the big addr message once + if (node.vAddrToSend.capacity() > 40) { + node.vAddrToSend.shrink_to_fit(); + } +} + namespace { class CompareInvMempoolOrder { @@ -4404,88 +4254,35 @@ bool PeerManagerImpl::SendMessages(CNode* pto) // If we get here, the outgoing message serialization version is set and can't change. const CNetMsgMaker msgMaker(pto->GetCommonVersion()); - MaybeSendPing(*pto, *peer); + const auto current_time = GetTime<std::chrono::microseconds>(); + + MaybeSendPing(*pto, *peer, current_time); // MaybeSendPing may have marked peer for disconnection if (pto->fDisconnect) return true; + MaybeSendAddr(*pto, current_time); + { LOCK(cs_main); CNodeState &state = *State(pto->GetId()); - // Address refresh broadcast - auto current_time = GetTime<std::chrono::microseconds>(); - - if (fListen && pto->RelayAddrsWithConn() && - !::ChainstateActive().IsInitialBlockDownload() && - pto->m_next_local_addr_send < current_time) { - // If we've sent before, clear the bloom filter for the peer, so that our - // self-announcement will actually go out. - // This might be unnecessary if the bloom filter has already rolled - // over since our last self-announcement, but there is only a small - // bandwidth cost that we can incur by doing this (which happens - // once a day on average). - if (pto->m_next_local_addr_send != 0us) { - pto->m_addr_known->reset(); - } - if (Optional<CAddress> local_addr = GetLocalAddrForPeer(pto)) { - FastRandomContext insecure_rand; - pto->PushAddress(*local_addr, insecure_rand); - } - pto->m_next_local_addr_send = PoissonNextSend(current_time, AVG_LOCAL_ADDRESS_BROADCAST_INTERVAL); - } - - // - // Message: addr - // - if (pto->RelayAddrsWithConn() && pto->m_next_addr_send < current_time) { - pto->m_next_addr_send = PoissonNextSend(current_time, AVG_ADDRESS_BROADCAST_INTERVAL); - std::vector<CAddress> vAddr; - vAddr.reserve(pto->vAddrToSend.size()); - assert(pto->m_addr_known); - - const char* msg_type; - int make_flags; - if (pto->m_wants_addrv2) { - msg_type = NetMsgType::ADDRV2; - make_flags = ADDRV2_FORMAT; - } else { - msg_type = NetMsgType::ADDR; - make_flags = 0; - } - - for (const CAddress& addr : pto->vAddrToSend) - { - if (!pto->m_addr_known->contains(addr.GetKey())) - { - pto->m_addr_known->insert(addr.GetKey()); - vAddr.push_back(addr); - // receiver rejects addr messages larger than MAX_ADDR_TO_SEND - if (vAddr.size() >= MAX_ADDR_TO_SEND) - { - m_connman.PushMessage(pto, msgMaker.Make(make_flags, msg_type, vAddr)); - vAddr.clear(); - } - } - } - pto->vAddrToSend.clear(); - if (!vAddr.empty()) - m_connman.PushMessage(pto, msgMaker.Make(make_flags, msg_type, vAddr)); - // we only send the big addr message once - if (pto->vAddrToSend.capacity() > 40) - pto->vAddrToSend.shrink_to_fit(); - } - // Start block sync if (pindexBestHeader == nullptr) - pindexBestHeader = ::ChainActive().Tip(); + pindexBestHeader = m_chainman.ActiveChain().Tip(); bool fFetch = state.fPreferredDownload || (nPreferredDownload == 0 && !pto->fClient && !pto->IsAddrFetchConn()); // Download if this is a nice peer, or we have no nice peers and this one might do. if (!state.fSyncStarted && !pto->fClient && !fImporting && !fReindex) { // Only actively request headers from a single peer, unless we're close to today. if ((nSyncStarted == 0 && fFetch) || pindexBestHeader->GetBlockTime() > GetAdjustedTime() - 24 * 60 * 60) { state.fSyncStarted = true; - state.nHeadersSyncTimeout = count_microseconds(current_time) + HEADERS_DOWNLOAD_TIMEOUT_BASE + HEADERS_DOWNLOAD_TIMEOUT_PER_HEADER * (GetAdjustedTime() - pindexBestHeader->GetBlockTime())/(consensusParams.nPowTargetSpacing); + state.m_headers_sync_timeout = current_time + HEADERS_DOWNLOAD_TIMEOUT_BASE + + ( + // Convert HEADERS_DOWNLOAD_TIMEOUT_PER_HEADER to microseconds before scaling + // to maintain precision + std::chrono::microseconds{HEADERS_DOWNLOAD_TIMEOUT_PER_HEADER} * + (GetAdjustedTime() - pindexBestHeader->GetBlockTime()) / consensusParams.nPowTargetSpacing + ); nSyncStarted++; const CBlockIndex *pindexStart = pindexBestHeader; /* If possible, start at the block preceding the currently @@ -4498,7 +4295,7 @@ bool PeerManagerImpl::SendMessages(CNode* pto) if (pindexStart->pprev) pindexStart = pindexStart->pprev; LogPrint(BCLog::NET, "initial getheaders (%d) to peer=%d (startheight:%d)\n", pindexStart->nHeight, pto->GetId(), peer->m_starting_height); - m_connman.PushMessage(pto, msgMaker.Make(NetMsgType::GETHEADERS, ::ChainActive().GetLocator(pindexStart), uint256())); + m_connman.PushMessage(pto, msgMaker.Make(NetMsgType::GETHEADERS, m_chainman.ActiveChain().GetLocator(pindexStart), uint256())); } } @@ -4525,11 +4322,11 @@ bool PeerManagerImpl::SendMessages(CNode* pto) bool fFoundStartingHeader = false; // Try to find first header that our peer doesn't have, and // then send all headers past that one. If we come across any - // headers that aren't on ::ChainActive(), give up. + // headers that aren't on m_chainman.ActiveChain(), give up. for (const uint256& hash : peer->m_blocks_for_headers_relay) { - const CBlockIndex* pindex = g_chainman.m_blockman.LookupBlockIndex(hash); + const CBlockIndex* pindex = m_chainman.m_blockman.LookupBlockIndex(hash); assert(pindex); - if (::ChainActive()[pindex->nHeight] != pindex) { + if (m_chainman.ActiveChain()[pindex->nHeight] != pindex) { // Bail out if we reorged away from this block fRevertToInv = true; break; @@ -4619,15 +4416,15 @@ bool PeerManagerImpl::SendMessages(CNode* pto) // in the past. if (!peer->m_blocks_for_headers_relay.empty()) { const uint256& hashToAnnounce = peer->m_blocks_for_headers_relay.back(); - const CBlockIndex* pindex = g_chainman.m_blockman.LookupBlockIndex(hashToAnnounce); + const CBlockIndex* pindex = m_chainman.m_blockman.LookupBlockIndex(hashToAnnounce); assert(pindex); // Warn if we're announcing a block that is not on the main chain. // This should be very rare and could be optimized out. // Just log for now. - if (::ChainActive()[pindex->nHeight] != pindex) { + if (m_chainman.ActiveChain()[pindex->nHeight] != pindex) { LogPrint(BCLog::NET, "Announcing block %s not on main chain (tip=%s)\n", - hashToAnnounce.ToString(), ::ChainActive().Tip()->GetBlockHash().ToString()); + hashToAnnounce.ToString(), m_chainman.ActiveChain().Tip()->GetBlockHash().ToString()); } // If the peer's chain has this block, don't inv it back. @@ -4666,10 +4463,9 @@ bool PeerManagerImpl::SendMessages(CNode* pto) if (pto->m_tx_relay->nNextInvSend < current_time) { fSendTrickle = true; if (pto->IsInboundConn()) { - pto->m_tx_relay->nNextInvSend = std::chrono::microseconds{m_connman.PoissonNextSendInbound(count_microseconds(current_time), INVENTORY_BROADCAST_INTERVAL)}; + pto->m_tx_relay->nNextInvSend = m_connman.PoissonNextSendInbound(current_time, INBOUND_INVENTORY_BROADCAST_INTERVAL); } else { - // Use half the delay for outbound peers, as there is less privacy concern for them. - pto->m_tx_relay->nNextInvSend = PoissonNextSend(current_time, std::chrono::seconds{INVENTORY_BROADCAST_INTERVAL >> 1}); + pto->m_tx_relay->nNextInvSend = PoissonNextSend(current_time, OUTBOUND_INVENTORY_BROADCAST_INTERVAL); } } @@ -4706,7 +4502,7 @@ bool PeerManagerImpl::SendMessages(CNode* pto) vInv.clear(); } } - pto->m_tx_relay->m_last_mempool_req = GetTime<std::chrono::seconds>(); + pto->m_tx_relay->m_last_mempool_req = std::chrono::duration_cast<std::chrono::seconds>(current_time); } // Determine transactions to relay @@ -4757,20 +4553,20 @@ bool PeerManagerImpl::SendMessages(CNode* pto) nRelayedTransactions++; { // Expire old relay messages - while (!vRelayExpiration.empty() && vRelayExpiration.front().first < count_microseconds(current_time)) + while (!g_relay_expiration.empty() && g_relay_expiration.front().first < current_time) { - mapRelay.erase(vRelayExpiration.front().second); - vRelayExpiration.pop_front(); + mapRelay.erase(g_relay_expiration.front().second); + g_relay_expiration.pop_front(); } auto ret = mapRelay.emplace(txid, std::move(txinfo.tx)); if (ret.second) { - vRelayExpiration.emplace_back(count_microseconds(current_time + std::chrono::microseconds{RELAY_TX_CACHE_TIME}), ret.first); + g_relay_expiration.emplace_back(current_time + RELAY_TX_CACHE_TIME, ret.first); } // Add wtxid-based lookup into mapRelay as well, so that peers can request by wtxid auto ret2 = mapRelay.emplace(wtxid, ret.first->second); if (ret2.second) { - vRelayExpiration.emplace_back(count_microseconds(current_time + std::chrono::microseconds{RELAY_TX_CACHE_TIME}), ret2.first); + g_relay_expiration.emplace_back(current_time + RELAY_TX_CACHE_TIME, ret2.first); } } if (vInv.size() == MAX_INV_SZ) { @@ -4794,8 +4590,7 @@ bool PeerManagerImpl::SendMessages(CNode* pto) m_connman.PushMessage(pto, msgMaker.Make(NetMsgType::INV, vInv)); // Detect whether we're stalling - current_time = GetTime<std::chrono::microseconds>(); - if (state.nStallingSince && state.nStallingSince < count_microseconds(current_time) - 1000000 * BLOCK_STALLING_TIMEOUT) { + if (state.m_stalling_since.count() && state.m_stalling_since < current_time - BLOCK_STALLING_TIMEOUT) { // Stalling only triggers when the block download window cannot move. During normal steady state, // the download window should be much larger than the to-be-downloaded set of blocks, so disconnection // should only happen during initial block download. @@ -4803,7 +4598,7 @@ bool PeerManagerImpl::SendMessages(CNode* pto) pto->fDisconnect = true; return true; } - // In case there is a block that has been in flight from this peer for 2 + 0.5 * N times the block interval + // In case there is a block that has been in flight from this peer for block_interval * (1 + 0.5 * N) // (with N the number of peers from which we're downloading validated blocks), disconnect due to timeout. // We compensate for other peers to prevent killing off peers due to our own downstream link // being saturated. We only count validated in-flight blocks so peers can't advertise non-existing block hashes @@ -4811,17 +4606,17 @@ bool PeerManagerImpl::SendMessages(CNode* pto) if (state.vBlocksInFlight.size() > 0) { QueuedBlock &queuedBlock = state.vBlocksInFlight.front(); int nOtherPeersWithValidatedDownloads = nPeersWithValidatedDownloads - (state.nBlocksInFlightValidHeaders > 0); - if (count_microseconds(current_time) > state.nDownloadingSince + consensusParams.nPowTargetSpacing * (BLOCK_DOWNLOAD_TIMEOUT_BASE + BLOCK_DOWNLOAD_TIMEOUT_PER_PEER * nOtherPeersWithValidatedDownloads)) { + if (current_time > state.m_downloading_since + std::chrono::seconds{consensusParams.nPowTargetSpacing} * (BLOCK_DOWNLOAD_TIMEOUT_BASE + BLOCK_DOWNLOAD_TIMEOUT_PER_PEER * nOtherPeersWithValidatedDownloads)) { LogPrintf("Timeout downloading block %s from peer=%d, disconnecting\n", queuedBlock.hash.ToString(), pto->GetId()); pto->fDisconnect = true; return true; } } // Check for headers sync timeouts - if (state.fSyncStarted && state.nHeadersSyncTimeout < std::numeric_limits<int64_t>::max()) { + if (state.fSyncStarted && state.m_headers_sync_timeout < std::chrono::microseconds::max()) { // Detect whether this is a stalling initial-headers-sync peer if (pindexBestHeader->GetBlockTime() <= GetAdjustedTime() - 24 * 60 * 60) { - if (count_microseconds(current_time) > state.nHeadersSyncTimeout && nSyncStarted == 1 && (nPreferredDownload - state.fPreferredDownload >= 1)) { + if (current_time > state.m_headers_sync_timeout && nSyncStarted == 1 && (nPreferredDownload - state.fPreferredDownload >= 1)) { // Disconnect a peer (without the noban permission) if it is our only sync peer, // and we have others we could be using instead. // Note: If all our peers are inbound, then we won't @@ -4840,13 +4635,13 @@ bool PeerManagerImpl::SendMessages(CNode* pto) // this peer (eventually). state.fSyncStarted = false; nSyncStarted--; - state.nHeadersSyncTimeout = 0; + state.m_headers_sync_timeout = 0us; } } } else { // After we've caught up once, reset the timeout so we can't trigger // disconnect later. - state.nHeadersSyncTimeout = std::numeric_limits<int64_t>::max(); + state.m_headers_sync_timeout = std::chrono::microseconds::max(); } } @@ -4858,7 +4653,7 @@ bool PeerManagerImpl::SendMessages(CNode* pto) // Message: getdata (blocks) // std::vector<CInv> vGetData; - if (!pto->fClient && ((fFetch && !pto->m_limited_node) || !::ChainstateActive().IsInitialBlockDownload()) && state.nBlocksInFlight < MAX_BLOCKS_IN_TRANSIT_PER_PEER) { + if (!pto->fClient && ((fFetch && !pto->m_limited_node) || !m_chainman.ActiveChainstate().IsInitialBlockDownload()) && state.nBlocksInFlight < MAX_BLOCKS_IN_TRANSIT_PER_PEER) { std::vector<const CBlockIndex*> vToDownload; NodeId staller = -1; FindNextBlocksToDownload(pto->GetId(), MAX_BLOCKS_IN_TRANSIT_PER_PEER - state.nBlocksInFlight, vToDownload, staller); @@ -4870,8 +4665,8 @@ bool PeerManagerImpl::SendMessages(CNode* pto) pindex->nHeight, pto->GetId()); } if (state.nBlocksInFlight == 0 && staller != -1) { - if (State(staller)->nStallingSince == 0) { - State(staller)->nStallingSince = count_microseconds(current_time); + if (State(staller)->m_stalling_since == 0us) { + State(staller)->m_stalling_since = current_time; LogPrint(BCLog::NET, "Stall started peer=%d\n", staller); } } @@ -4910,7 +4705,10 @@ bool PeerManagerImpl::SendMessages(CNode* pto) // // Message: feefilter // - if (pto->m_tx_relay != nullptr && pto->GetCommonVersion() >= FEEFILTER_VERSION && gArgs.GetBoolArg("-feefilter", DEFAULT_FEEFILTER) && + if (pto->m_tx_relay != nullptr && + !m_ignore_incoming_txs && + pto->GetCommonVersion() >= FEEFILTER_VERSION && + gArgs.GetBoolArg("-feefilter", DEFAULT_FEEFILTER) && !pto->HasPermission(PF_FORCERELAY) // peers with the forcerelay permission should not filter txs to us ) { CAmount currentFilter = m_mempool.GetMinFee(gArgs.GetArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000).GetFeePerK(); @@ -4924,10 +4722,10 @@ bool PeerManagerImpl::SendMessages(CNode* pto) if (pto->m_tx_relay->lastSentFeeFilter == MAX_FILTER) { // Send the current filter if we sent MAX_FILTER previously // and made it out of IBD. - pto->m_tx_relay->nextSendTimeFeeFilter = count_microseconds(current_time) - 1; + pto->m_tx_relay->m_next_send_feefilter = 0us; } } - if (count_microseconds(current_time) > pto->m_tx_relay->nextSendTimeFeeFilter) { + if (current_time > pto->m_tx_relay->m_next_send_feefilter) { CAmount filterToSend = g_filter_rounder.round(currentFilter); // We always have a fee filter of at least minRelayTxFee filterToSend = std::max(filterToSend, ::minRelayTxFee.GetFeePerK()); @@ -4935,28 +4733,15 @@ bool PeerManagerImpl::SendMessages(CNode* pto) m_connman.PushMessage(pto, msgMaker.Make(NetMsgType::FEEFILTER, filterToSend)); pto->m_tx_relay->lastSentFeeFilter = filterToSend; } - pto->m_tx_relay->nextSendTimeFeeFilter = PoissonNextSend(count_microseconds(current_time), AVG_FEEFILTER_BROADCAST_INTERVAL); + pto->m_tx_relay->m_next_send_feefilter = PoissonNextSend(current_time, AVG_FEEFILTER_BROADCAST_INTERVAL); } // If the fee filter has changed substantially and it's still more than MAX_FEEFILTER_CHANGE_DELAY // until scheduled broadcast, then move the broadcast to within MAX_FEEFILTER_CHANGE_DELAY. - else if (count_microseconds(current_time) + MAX_FEEFILTER_CHANGE_DELAY * 1000000 < pto->m_tx_relay->nextSendTimeFeeFilter && + else if (current_time + MAX_FEEFILTER_CHANGE_DELAY < pto->m_tx_relay->m_next_send_feefilter && (currentFilter < 3 * pto->m_tx_relay->lastSentFeeFilter / 4 || currentFilter > 4 * pto->m_tx_relay->lastSentFeeFilter / 3)) { - pto->m_tx_relay->nextSendTimeFeeFilter = count_microseconds(current_time) + GetRandInt(MAX_FEEFILTER_CHANGE_DELAY) * 1000000; + pto->m_tx_relay->m_next_send_feefilter = current_time + GetRandomDuration<std::chrono::microseconds>(MAX_FEEFILTER_CHANGE_DELAY); } } } // release cs_main return true; } - -class CNetProcessingCleanup -{ -public: - CNetProcessingCleanup() {} - ~CNetProcessingCleanup() { - // orphan transactions - mapOrphanTransactions.clear(); - mapOrphanTransactionsByPrev.clear(); - g_orphans_by_wtxid.clear(); - } -}; -static CNetProcessingCleanup instance_of_cnetprocessingcleanup; diff --git a/src/net_processing.h b/src/net_processing.h index d7be453df5..4556d32377 100644 --- a/src/net_processing.h +++ b/src/net_processing.h @@ -10,12 +10,12 @@ #include <sync.h> #include <validationinterface.h> +class CAddrMan; class CChainParams; class CTxMemPool; class ChainstateManager; extern RecursiveMutex cs_main; -extern RecursiveMutex g_cs_orphans; /** Default for -maxorphantx, maximum number of orphan transactions kept in memory */ static const unsigned int DEFAULT_MAX_ORPHAN_TRANSACTIONS = 100; @@ -30,16 +30,16 @@ struct CNodeStateStats { int nSyncHeight = -1; int nCommonHeight = -1; int m_starting_height = -1; - int64_t m_ping_wait_usec; + std::chrono::microseconds m_ping_wait; std::vector<int> vHeightInFlight; }; class PeerManager : public CValidationInterface, public NetEventsInterface { public: - static std::unique_ptr<PeerManager> make(const CChainParams& chainparams, CConnman& connman, BanMan* banman, - CScheduler& scheduler, ChainstateManager& chainman, CTxMemPool& pool, - bool ignore_incoming_txs); + static std::unique_ptr<PeerManager> make(const CChainParams& chainparams, CConnman& connman, CAddrMan& addrman, + BanMan* banman, CScheduler& scheduler, ChainstateManager& chainman, + CTxMemPool& pool, bool ignore_incoming_txs); virtual ~PeerManager() { } /** Get statistics from node state */ @@ -48,6 +48,10 @@ public: /** Whether this node ignores txs received over p2p. */ virtual bool IgnoresIncomingTxs() = 0; + /** Relay transaction to all peers. */ + virtual void RelayTransaction(const uint256& txid, const uint256& wtxid) + EXCLUSIVE_LOCKS_REQUIRED(cs_main) = 0; + /** Send ping message to all peers */ virtual void SendPings() = 0; @@ -72,7 +76,4 @@ public: const std::chrono::microseconds time_received, const std::atomic<bool>& interruptMsgProc) = 0; }; -/** Relay transaction to every node */ -void RelayTransaction(const uint256& txid, const uint256& wtxid, const CConnman& connman) EXCLUSIVE_LOCKS_REQUIRED(cs_main); - #endif // BITCOIN_NET_PROCESSING_H diff --git a/src/netaddress.cpp b/src/netaddress.cpp index 85e46fd373..69edc15c66 100644 --- a/src/netaddress.cpp +++ b/src/netaddress.cpp @@ -221,25 +221,34 @@ static void Checksum(Span<const uint8_t> addr_pubkey, uint8_t (&checksum)[CHECKS }; // namespace torv3 -/** - * Parse a TOR address and set this object to it. - * - * @returns Whether or not the operation was successful. - * - * @see CNetAddr::IsTor() - */ -bool CNetAddr::SetSpecial(const std::string& str) +bool CNetAddr::SetSpecial(const std::string& addr) +{ + if (!ValidAsCString(addr)) { + return false; + } + + if (SetTor(addr)) { + return true; + } + + if (SetI2P(addr)) { + return true; + } + + return false; +} + +bool CNetAddr::SetTor(const std::string& addr) { static const char* suffix{".onion"}; static constexpr size_t suffix_len{6}; - if (!ValidAsCString(str) || str.size() <= suffix_len || - str.substr(str.size() - suffix_len) != suffix) { + if (addr.size() <= suffix_len || addr.substr(addr.size() - suffix_len) != suffix) { return false; } bool invalid; - const auto& input = DecodeBase32(str.substr(0, str.size() - suffix_len).c_str(), &invalid); + const auto& input = DecodeBase32(addr.substr(0, addr.size() - suffix_len).c_str(), &invalid); if (invalid) { return false; @@ -275,6 +284,34 @@ bool CNetAddr::SetSpecial(const std::string& str) return false; } +bool CNetAddr::SetI2P(const std::string& addr) +{ + // I2P addresses that we support consist of 52 base32 characters + ".b32.i2p". + static constexpr size_t b32_len{52}; + static const char* suffix{".b32.i2p"}; + static constexpr size_t suffix_len{8}; + + if (addr.size() != b32_len + suffix_len || ToLower(addr.substr(b32_len)) != suffix) { + return false; + } + + // Remove the ".b32.i2p" suffix and pad to a multiple of 8 chars, so DecodeBase32() + // can decode it. + const std::string b32_padded = addr.substr(0, b32_len) + "===="; + + bool invalid; + const auto& address_bytes = DecodeBase32(b32_padded.c_str(), &invalid); + + if (invalid || address_bytes.size() != ADDR_I2P_SIZE) { + return false; + } + + m_net = NET_I2P; + m_addr.assign(address_bytes.begin(), address_bytes.end()); + + return true; +} + CNetAddr::CNetAddr(const struct in_addr& ipv4Addr) { m_net = NET_IPV4; @@ -841,6 +878,11 @@ int CNetAddr::GetReachabilityFrom(const CNetAddr *paddrPartner) const case NET_IPV4: return REACH_IPV4; // Tor users can connect to IPv4 as well case NET_ONION: return REACH_PRIVATE; } + case NET_I2P: + switch (ourNet) { + case NET_I2P: return REACH_PRIVATE; + default: return REACH_DEFAULT; + } case NET_TEREDO: switch(ourNet) { default: return REACH_DEFAULT; diff --git a/src/netaddress.h b/src/netaddress.h index d0986557f7..897ce46cda 100644 --- a/src/netaddress.h +++ b/src/netaddress.h @@ -151,7 +151,16 @@ class CNetAddr bool SetInternal(const std::string& name); - bool SetSpecial(const std::string &strName); // for Tor addresses + /** + * Parse a Tor or I2P address and set this object to it. + * @param[in] addr Address to parse, for example + * pg6mmjiyjmcrsslvykfwnntlaru7p5svn6y2ymmju6nubxndf4pscryd.onion or + * ukeu3k5oycgaauneqgtnvselmt4yemvoilkln7jpvamvfx7dnkdq.b32.i2p. + * @returns Whether the operation was successful. + * @see CNetAddr::IsTor(), CNetAddr::IsI2P() + */ + bool SetSpecial(const std::string& addr); + bool IsBindAny() const; // INADDR_ANY equivalent bool IsIPv4() const; // IPv4 mapped address (::FFFF:0:0/96, 0.0.0.0/0) bool IsIPv6() const; // IPv6 address (not mapped IPv4, not Tor) @@ -249,6 +258,25 @@ class CNetAddr private: /** + * Parse a Tor address and set this object to it. + * @param[in] addr Address to parse, must be a valid C string, for example + * pg6mmjiyjmcrsslvykfwnntlaru7p5svn6y2ymmju6nubxndf4pscryd.onion or + * 6hzph5hv6337r6p2.onion. + * @returns Whether the operation was successful. + * @see CNetAddr::IsTor() + */ + bool SetTor(const std::string& addr); + + /** + * Parse an I2P address and set this object to it. + * @param[in] addr Address to parse, must be a valid C string, for example + * ukeu3k5oycgaauneqgtnvselmt4yemvoilkln7jpvamvfx7dnkdq.b32.i2p. + * @returns Whether the operation was successful. + * @see CNetAddr::IsI2P() + */ + bool SetI2P(const std::string& addr); + + /** * BIP155 network ids recognized by this software. */ enum BIP155Network : uint8_t { diff --git a/src/netbase.cpp b/src/netbase.cpp index 0c5b3a220e..2980bdf459 100644 --- a/src/netbase.cpp +++ b/src/netbase.cpp @@ -5,6 +5,7 @@ #include <netbase.h> +#include <compat.h> #include <sync.h> #include <tinyformat.h> #include <util/sock.h> @@ -14,6 +15,7 @@ #include <util/time.h> #include <atomic> +#include <chrono> #include <cstdint> #include <functional> #include <limits> @@ -29,10 +31,6 @@ #include <poll.h> #endif -#if !defined(MSG_NOSIGNAL) -#define MSG_NOSIGNAL 0 -#endif - // Settings static Mutex g_proxyinfo_mutex; static proxyType proxyInfo[NET_MAX] GUARDED_BY(g_proxyinfo_mutex); @@ -41,9 +39,53 @@ int nConnectTimeout = DEFAULT_CONNECT_TIMEOUT; bool fNameLookup = DEFAULT_NAME_LOOKUP; // Need ample time for negotiation for very slow proxies such as Tor (milliseconds) -static const int SOCKS5_RECV_TIMEOUT = 20 * 1000; +int g_socks5_recv_timeout = 20 * 1000; static std::atomic<bool> interruptSocks5Recv(false); +std::vector<CNetAddr> WrappedGetAddrInfo(const std::string& name, bool allow_lookup) +{ + addrinfo ai_hint{}; + // We want a TCP port, which is a streaming socket type + ai_hint.ai_socktype = SOCK_STREAM; + ai_hint.ai_protocol = IPPROTO_TCP; + // We don't care which address family (IPv4 or IPv6) is returned + ai_hint.ai_family = AF_UNSPEC; + // If we allow lookups of hostnames, use the AI_ADDRCONFIG flag to only + // return addresses whose family we have an address configured for. + // + // If we don't allow lookups, then use the AI_NUMERICHOST flag for + // getaddrinfo to only decode numerical network addresses and suppress + // hostname lookups. + ai_hint.ai_flags = allow_lookup ? AI_ADDRCONFIG : AI_NUMERICHOST; + + addrinfo* ai_res{nullptr}; + const int n_err{getaddrinfo(name.c_str(), nullptr, &ai_hint, &ai_res)}; + if (n_err != 0) { + return {}; + } + + // Traverse the linked list starting with ai_trav. + addrinfo* ai_trav{ai_res}; + std::vector<CNetAddr> resolved_addresses; + while (ai_trav != nullptr) { + if (ai_trav->ai_family == AF_INET) { + assert(ai_trav->ai_addrlen >= sizeof(sockaddr_in)); + resolved_addresses.emplace_back(reinterpret_cast<sockaddr_in*>(ai_trav->ai_addr)->sin_addr); + } + if (ai_trav->ai_family == AF_INET6) { + assert(ai_trav->ai_addrlen >= sizeof(sockaddr_in6)); + const sockaddr_in6* s6{reinterpret_cast<sockaddr_in6*>(ai_trav->ai_addr)}; + resolved_addresses.emplace_back(s6->sin6_addr, s6->sin6_scope_id); + } + ai_trav = ai_trav->ai_next; + } + freeaddrinfo(ai_res); + + return resolved_addresses; +} + +DNSLookupFn g_dns_lookup{WrappedGetAddrInfo}; + enum Network ParseNetwork(const std::string& net_in) { std::string net = ToLower(net_in); if (net == "ipv4") return NET_IPV4; @@ -53,6 +95,9 @@ enum Network ParseNetwork(const std::string& net_in) { LogPrintf("Warning: net name 'tor' is deprecated and will be removed in the future. You should use 'onion' instead.\n"); return NET_ONION; } + if (net == "i2p") { + return NET_I2P; + } return NET_UNROUTABLE; } @@ -77,7 +122,7 @@ std::vector<std::string> GetNetworkNames(bool append_unroutable) std::vector<std::string> names; for (int n = 0; n < NET_MAX; ++n) { const enum Network network{static_cast<Network>(n)}; - if (network == NET_UNROUTABLE || network == NET_I2P || network == NET_CJDNS || network == NET_INTERNAL) continue; + if (network == NET_UNROUTABLE || network == NET_CJDNS || network == NET_INTERNAL) continue; names.emplace_back(GetNetworkName(network)); } if (append_unroutable) { @@ -86,7 +131,7 @@ std::vector<std::string> GetNetworkNames(bool append_unroutable) return names; } -bool static LookupIntern(const std::string& name, std::vector<CNetAddr>& vIP, unsigned int nMaxSolutions, bool fAllowLookup) +static bool LookupIntern(const std::string& name, std::vector<CNetAddr>& vIP, unsigned int nMaxSolutions, bool fAllowLookup, DNSLookupFn dns_lookup_function) { vIP.clear(); @@ -108,73 +153,20 @@ bool static LookupIntern(const std::string& name, std::vector<CNetAddr>& vIP, un } } - struct addrinfo aiHint; - memset(&aiHint, 0, sizeof(struct addrinfo)); - - // We want a TCP port, which is a streaming socket type - aiHint.ai_socktype = SOCK_STREAM; - aiHint.ai_protocol = IPPROTO_TCP; - // We don't care which address family (IPv4 or IPv6) is returned - aiHint.ai_family = AF_UNSPEC; - // If we allow lookups of hostnames, use the AI_ADDRCONFIG flag to only - // return addresses whose family we have an address configured for. - // - // If we don't allow lookups, then use the AI_NUMERICHOST flag for - // getaddrinfo to only decode numerical network addresses and suppress - // hostname lookups. - aiHint.ai_flags = fAllowLookup ? AI_ADDRCONFIG : AI_NUMERICHOST; - struct addrinfo *aiRes = nullptr; - int nErr = getaddrinfo(name.c_str(), nullptr, &aiHint, &aiRes); - if (nErr) - return false; - - // Traverse the linked list starting with aiTrav, add all non-internal - // IPv4,v6 addresses to vIP while respecting nMaxSolutions. - struct addrinfo *aiTrav = aiRes; - while (aiTrav != nullptr && (nMaxSolutions == 0 || vIP.size() < nMaxSolutions)) - { - CNetAddr resolved; - if (aiTrav->ai_family == AF_INET) - { - assert(aiTrav->ai_addrlen >= sizeof(sockaddr_in)); - resolved = CNetAddr(((struct sockaddr_in*)(aiTrav->ai_addr))->sin_addr); - } - - if (aiTrav->ai_family == AF_INET6) - { - assert(aiTrav->ai_addrlen >= sizeof(sockaddr_in6)); - struct sockaddr_in6* s6 = (struct sockaddr_in6*) aiTrav->ai_addr; - resolved = CNetAddr(s6->sin6_addr, s6->sin6_scope_id); + for (const CNetAddr& resolved : dns_lookup_function(name, fAllowLookup)) { + if (nMaxSolutions > 0 && vIP.size() >= nMaxSolutions) { + break; } /* Never allow resolving to an internal address. Consider any such result invalid */ if (!resolved.IsInternal()) { vIP.push_back(resolved); } - - aiTrav = aiTrav->ai_next; } - freeaddrinfo(aiRes); - return (vIP.size() > 0); } -/** - * Resolve a host string to its corresponding network addresses. - * - * @param name The string representing a host. Could be a name or a numerical - * IP address (IPv6 addresses in their bracketed form are - * allowed). - * @param[out] vIP The resulting network addresses to which the specified host - * string resolved. - * - * @returns Whether or not the specified host string successfully resolved to - * any resulting network addresses. - * - * @see Lookup(const char *, std::vector<CService>&, int, bool, unsigned int) - * for additional parameter descriptions. - */ -bool LookupHost(const std::string& name, std::vector<CNetAddr>& vIP, unsigned int nMaxSolutions, bool fAllowLookup) +bool LookupHost(const std::string& name, std::vector<CNetAddr>& vIP, unsigned int nMaxSolutions, bool fAllowLookup, DNSLookupFn dns_lookup_function) { if (!ValidAsCString(name)) { return false; @@ -186,59 +178,33 @@ bool LookupHost(const std::string& name, std::vector<CNetAddr>& vIP, unsigned in strHost = strHost.substr(1, strHost.size() - 2); } - return LookupIntern(strHost, vIP, nMaxSolutions, fAllowLookup); + return LookupIntern(strHost, vIP, nMaxSolutions, fAllowLookup, dns_lookup_function); } - /** - * Resolve a host string to its first corresponding network address. - * - * @see LookupHost(const std::string&, std::vector<CNetAddr>&, unsigned int, bool) for - * additional parameter descriptions. - */ -bool LookupHost(const std::string& name, CNetAddr& addr, bool fAllowLookup) +bool LookupHost(const std::string& name, CNetAddr& addr, bool fAllowLookup, DNSLookupFn dns_lookup_function) { if (!ValidAsCString(name)) { return false; } std::vector<CNetAddr> vIP; - LookupHost(name, vIP, 1, fAllowLookup); + LookupHost(name, vIP, 1, fAllowLookup, dns_lookup_function); if(vIP.empty()) return false; addr = vIP.front(); return true; } -/** - * Resolve a service string to its corresponding service. - * - * @param name The string representing a service. Could be a name or a - * numerical IP address (IPv6 addresses should be in their - * disambiguated bracketed form), optionally followed by a port - * number. (e.g. example.com:8333 or - * [2001:db8:85a3:8d3:1319:8a2e:370:7348]:420) - * @param[out] vAddr The resulting services to which the specified service string - * resolved. - * @param portDefault The default port for resulting services if not specified - * by the service string. - * @param fAllowLookup Whether or not hostname lookups are permitted. If yes, - * external queries may be performed. - * @param nMaxSolutions The maximum number of results we want, specifying 0 - * means "as many solutions as we get." - * - * @returns Whether or not the service string successfully resolved to any - * resulting services. - */ -bool Lookup(const std::string& name, std::vector<CService>& vAddr, int portDefault, bool fAllowLookup, unsigned int nMaxSolutions) +bool Lookup(const std::string& name, std::vector<CService>& vAddr, uint16_t portDefault, bool fAllowLookup, unsigned int nMaxSolutions, DNSLookupFn dns_lookup_function) { if (name.empty() || !ValidAsCString(name)) { return false; } - int port = portDefault; + uint16_t port{portDefault}; std::string hostname; SplitHostPort(name, port, hostname); std::vector<CNetAddr> vIP; - bool fRet = LookupIntern(hostname, vIP, nMaxSolutions, fAllowLookup); + bool fRet = LookupIntern(hostname, vIP, nMaxSolutions, fAllowLookup, dns_lookup_function); if (!fRet) return false; vAddr.resize(vIP.size()); @@ -247,36 +213,20 @@ bool Lookup(const std::string& name, std::vector<CService>& vAddr, int portDefau return true; } -/** - * Resolve a service string to its first corresponding service. - * - * @see Lookup(const char *, std::vector<CService>&, int, bool, unsigned int) - * for additional parameter descriptions. - */ -bool Lookup(const std::string& name, CService& addr, int portDefault, bool fAllowLookup) +bool Lookup(const std::string& name, CService& addr, uint16_t portDefault, bool fAllowLookup, DNSLookupFn dns_lookup_function) { if (!ValidAsCString(name)) { return false; } std::vector<CService> vService; - bool fRet = Lookup(name, vService, portDefault, fAllowLookup, 1); + bool fRet = Lookup(name, vService, portDefault, fAllowLookup, 1, dns_lookup_function); if (!fRet) return false; addr = vService[0]; return true; } -/** - * Resolve a service string with a numeric IP to its first corresponding - * service. - * - * @returns The resulting CService if the resolution was successful, [::]:0 - * otherwise. - * - * @see Lookup(const char *, CService&, int, bool) for additional parameter - * descriptions. - */ -CService LookupNumeric(const std::string& name, int portDefault) +CService LookupNumeric(const std::string& name, uint16_t portDefault, DNSLookupFn dns_lookup_function) { if (!ValidAsCString(name)) { return {}; @@ -284,7 +234,7 @@ CService LookupNumeric(const std::string& name, int portDefault) CService addr; // "1.2:345" will fail to resolve the ip, but will still set the port. // If the ip fails to resolve, re-init the result. - if(!Lookup(name, addr, portDefault, false)) + if(!Lookup(name, addr, portDefault, false, dns_lookup_function)) addr = CService(); return addr; } @@ -360,9 +310,6 @@ static IntrRecvError InterruptibleRecv(uint8_t* data, size_t len, int timeout, c { int64_t curTime = GetTimeMillis(); int64_t endTime = curTime + timeout; - // Maximum time to wait for I/O readiness. It will take up until this time - // (in millis) to break off in case of an interruption. - const int64_t maxWait = 1000; while (len > 0 && curTime < endTime) { ssize_t ret = sock.Recv(data, len, 0); // Optimistically try the recv first if (ret > 0) { @@ -373,10 +320,11 @@ static IntrRecvError InterruptibleRecv(uint8_t* data, size_t len, int timeout, c } else { // Other error or blocking int nErr = WSAGetLastError(); if (nErr == WSAEINPROGRESS || nErr == WSAEWOULDBLOCK || nErr == WSAEINVAL) { - // Only wait at most maxWait milliseconds at a time, unless + // Only wait at most MAX_WAIT_FOR_IO at a time, unless // we're approaching the end of the specified total timeout - int timeout_ms = std::min(endTime - curTime, maxWait); - if (!sock.Wait(std::chrono::milliseconds{timeout_ms}, Sock::RECV)) { + const auto remaining = std::chrono::milliseconds{endTime - curTime}; + const auto timeout = std::min(remaining, std::chrono::milliseconds{MAX_WAIT_FOR_IO}); + if (!sock.Wait(timeout, Sock::RECV)) { return IntrRecvError::NetworkError; } } else { @@ -390,13 +338,6 @@ static IntrRecvError InterruptibleRecv(uint8_t* data, size_t len, int timeout, c return len == 0 ? IntrRecvError::OK : IntrRecvError::Timeout; } -/** Credentials for proxy authentication */ -struct ProxyCredentials -{ - std::string username; - std::string password; -}; - /** Convert SOCKS5 reply to an error message */ static std::string Socks5ErrorString(uint8_t err) { @@ -422,25 +363,7 @@ static std::string Socks5ErrorString(uint8_t err) } } -/** - * Connect to a specified destination service through an already connected - * SOCKS5 proxy. - * - * @param strDest The destination fully-qualified domain name. - * @param port The destination port. - * @param auth The credentials with which to authenticate with the specified - * SOCKS5 proxy. - * @param sock The SOCKS5 proxy socket. - * - * @returns Whether or not the operation succeeded. - * - * @note The specified SOCKS5 proxy socket must already be connected to the - * SOCKS5 proxy. - * - * @see <a href="https://www.ietf.org/rfc/rfc1928.txt">RFC1928: SOCKS Protocol - * Version 5</a> - */ -static bool Socks5(const std::string& strDest, int port, const ProxyCredentials* auth, const Sock& sock) +bool Socks5(const std::string& strDest, uint16_t port, const ProxyCredentials* auth, const Sock& sock) { IntrRecvError recvr; LogPrint(BCLog::NET, "SOCKS5 connecting %s\n", strDest); @@ -463,7 +386,7 @@ static bool Socks5(const std::string& strDest, int port, const ProxyCredentials* return error("Error sending to proxy"); } uint8_t pchRet1[2]; - if ((recvr = InterruptibleRecv(pchRet1, 2, SOCKS5_RECV_TIMEOUT, sock)) != IntrRecvError::OK) { + if ((recvr = InterruptibleRecv(pchRet1, 2, g_socks5_recv_timeout, sock)) != IntrRecvError::OK) { LogPrintf("Socks5() connect to %s:%d failed: InterruptibleRecv() timeout or other failure\n", strDest, port); return false; } @@ -486,7 +409,7 @@ static bool Socks5(const std::string& strDest, int port, const ProxyCredentials* } LogPrint(BCLog::PROXY, "SOCKS5 sending proxy authentication %s:%s\n", auth->username, auth->password); uint8_t pchRetA[2]; - if ((recvr = InterruptibleRecv(pchRetA, 2, SOCKS5_RECV_TIMEOUT, sock)) != IntrRecvError::OK) { + if ((recvr = InterruptibleRecv(pchRetA, 2, g_socks5_recv_timeout, sock)) != IntrRecvError::OK) { return error("Error reading proxy authentication response"); } if (pchRetA[0] != 0x01 || pchRetA[1] != 0x00) { @@ -511,7 +434,7 @@ static bool Socks5(const std::string& strDest, int port, const ProxyCredentials* return error("Error sending to proxy"); } uint8_t pchRet2[4]; - if ((recvr = InterruptibleRecv(pchRet2, 4, SOCKS5_RECV_TIMEOUT, sock)) != IntrRecvError::OK) { + if ((recvr = InterruptibleRecv(pchRet2, 4, g_socks5_recv_timeout, sock)) != IntrRecvError::OK) { if (recvr == IntrRecvError::Timeout) { /* If a timeout happens here, this effectively means we timed out while connecting * to the remote node. This is very common for Tor, so do not print an @@ -535,16 +458,16 @@ static bool Socks5(const std::string& strDest, int port, const ProxyCredentials* uint8_t pchRet3[256]; switch (pchRet2[3]) { - case SOCKS5Atyp::IPV4: recvr = InterruptibleRecv(pchRet3, 4, SOCKS5_RECV_TIMEOUT, sock); break; - case SOCKS5Atyp::IPV6: recvr = InterruptibleRecv(pchRet3, 16, SOCKS5_RECV_TIMEOUT, sock); break; + case SOCKS5Atyp::IPV4: recvr = InterruptibleRecv(pchRet3, 4, g_socks5_recv_timeout, sock); break; + case SOCKS5Atyp::IPV6: recvr = InterruptibleRecv(pchRet3, 16, g_socks5_recv_timeout, sock); break; case SOCKS5Atyp::DOMAINNAME: { - recvr = InterruptibleRecv(pchRet3, 1, SOCKS5_RECV_TIMEOUT, sock); + recvr = InterruptibleRecv(pchRet3, 1, g_socks5_recv_timeout, sock); if (recvr != IntrRecvError::OK) { return error("Error reading from proxy"); } int nRecv = pchRet3[0]; - recvr = InterruptibleRecv(pchRet3, nRecv, SOCKS5_RECV_TIMEOUT, sock); + recvr = InterruptibleRecv(pchRet3, nRecv, g_socks5_recv_timeout, sock); break; } default: return error("Error: malformed proxy response"); @@ -552,7 +475,7 @@ static bool Socks5(const std::string& strDest, int port, const ProxyCredentials* if (recvr != IntrRecvError::OK) { return error("Error reading from proxy"); } - if ((recvr = InterruptibleRecv(pchRet3, 2, SOCKS5_RECV_TIMEOUT, sock)) != IntrRecvError::OK) { + if ((recvr = InterruptibleRecv(pchRet3, 2, g_socks5_recv_timeout, sock)) != IntrRecvError::OK) { return error("Error reading from proxy"); } LogPrint(BCLog::NET, "SOCKS5 connected %s\n", strDest); @@ -614,24 +537,12 @@ static void LogConnectFailure(bool manual_connection, const char* fmt, const Arg } } -/** - * Try to connect to the specified service on the specified socket. - * - * @param addrConnect The service to which to connect. - * @param hSocket The socket on which to connect. - * @param nTimeout Wait this many milliseconds for the connection to be - * established. - * @param manual_connection Whether or not the connection was manually requested - * (e.g. through the addnode RPC) - * - * @returns Whether or not a connection was successfully made. - */ -bool ConnectSocketDirectly(const CService &addrConnect, const SOCKET& hSocket, int nTimeout, bool manual_connection) +bool ConnectSocketDirectly(const CService &addrConnect, const Sock& sock, int nTimeout, bool manual_connection) { // Create a sockaddr from the specified service. struct sockaddr_storage sockaddr; socklen_t len = sizeof(sockaddr); - if (hSocket == INVALID_SOCKET) { + if (sock.Get() == INVALID_SOCKET) { LogPrintf("Cannot connect to %s: invalid socket\n", addrConnect.ToString()); return false; } @@ -641,8 +552,7 @@ bool ConnectSocketDirectly(const CService &addrConnect, const SOCKET& hSocket, i } // Connect to the addrConnect service on the hSocket socket. - if (connect(hSocket, (struct sockaddr*)&sockaddr, len) == SOCKET_ERROR) - { + if (sock.Connect(reinterpret_cast<struct sockaddr*>(&sockaddr), len) == SOCKET_ERROR) { int nErr = WSAGetLastError(); // WSAEINVAL is here because some legacy version of winsock uses it if (nErr == WSAEINPROGRESS || nErr == WSAEWOULDBLOCK || nErr == WSAEINVAL) @@ -650,46 +560,34 @@ bool ConnectSocketDirectly(const CService &addrConnect, const SOCKET& hSocket, i // Connection didn't actually fail, but is being established // asynchronously. Thus, use async I/O api (select/poll) // synchronously to check for successful connection with a timeout. -#ifdef USE_POLL - struct pollfd pollfd = {}; - pollfd.fd = hSocket; - pollfd.events = POLLIN | POLLOUT; - int nRet = poll(&pollfd, 1, nTimeout); -#else - struct timeval timeout = MillisToTimeval(nTimeout); - fd_set fdset; - FD_ZERO(&fdset); - FD_SET(hSocket, &fdset); - int nRet = select(hSocket + 1, nullptr, &fdset, nullptr, &timeout); -#endif - // Upon successful completion, both select and poll return the total - // number of file descriptors that have been selected. A value of 0 - // indicates that the call timed out and no file descriptors have - // been selected. - if (nRet == 0) - { - LogPrint(BCLog::NET, "connection to %s timeout\n", addrConnect.ToString()); + const Sock::Event requested = Sock::RECV | Sock::SEND; + Sock::Event occurred; + if (!sock.Wait(std::chrono::milliseconds{nTimeout}, requested, &occurred)) { + LogPrintf("wait for connect to %s failed: %s\n", + addrConnect.ToString(), + NetworkErrorString(WSAGetLastError())); return false; - } - if (nRet == SOCKET_ERROR) - { - LogPrintf("select() for %s failed: %s\n", addrConnect.ToString(), NetworkErrorString(WSAGetLastError())); + } else if (occurred == 0) { + LogPrint(BCLog::NET, "connection attempt to %s timed out\n", addrConnect.ToString()); return false; } - // Even if the select/poll was successful, the connect might not + // Even if the wait was successful, the connect might not // have been successful. The reason for this failure is hidden away // in the SO_ERROR for the socket in modern systems. We read it into - // nRet here. - socklen_t nRetSize = sizeof(nRet); - if (getsockopt(hSocket, SOL_SOCKET, SO_ERROR, (sockopt_arg_type)&nRet, &nRetSize) == SOCKET_ERROR) - { + // sockerr here. + int sockerr; + 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())); return false; } - if (nRet != 0) - { - LogConnectFailure(manual_connection, "connect() to %s failed after select(): %s", addrConnect.ToString(), NetworkErrorString(nRet)); + if (sockerr != 0) { + LogConnectFailure(manual_connection, + "connect() to %s failed after wait: %s", + addrConnect.ToString(), + NetworkErrorString(sockerr)); return false; } } @@ -724,22 +622,6 @@ bool GetProxy(enum Network net, proxyType &proxyInfoOut) { return true; } -/** - * Set the name proxy to use for all connections to nodes specified by a - * hostname. After setting this proxy, connecting to a node specified by a - * hostname won't result in a local lookup of said hostname, rather, connect to - * the node by asking the name proxy for a proxy connection to the hostname, - * effectively delegating the hostname lookup to the specified proxy. - * - * This delegation increases privacy for those who set the name proxy as they no - * longer leak their external hostname queries to their DNS servers. - * - * @returns Whether or not the operation succeeded. - * - * @note SOCKS5's support for UDP-over-SOCKS5 has been considered, but no SOCK5 - * server in common use (most notably Tor) actually implements UDP - * support, and a DNS resolver is beyond the scope of this project. - */ bool SetNameProxy(const proxyType &addrProxy) { if (!addrProxy.IsValid()) return false; @@ -770,25 +652,10 @@ bool IsProxy(const CNetAddr &addr) { return false; } -/** - * Connect to a specified destination service through a SOCKS5 proxy by first - * connecting to the SOCKS5 proxy. - * - * @param proxy The SOCKS5 proxy. - * @param strDest The destination service to which to connect. - * @param port The destination port. - * @param sock The socket on which to connect to the SOCKS5 proxy. - * @param nTimeout Wait this many milliseconds for the connection to the SOCKS5 - * proxy to be established. - * @param[out] outProxyConnectionFailed Whether or not the connection to the - * SOCKS5 proxy failed. - * - * @returns Whether or not the operation succeeded. - */ -bool ConnectThroughProxy(const proxyType& proxy, const std::string& strDest, int port, const Sock& sock, int nTimeout, bool& outProxyConnectionFailed) +bool ConnectThroughProxy(const proxyType& proxy, const std::string& strDest, uint16_t port, const Sock& sock, int nTimeout, bool& outProxyConnectionFailed) { // first connect to proxy server - if (!ConnectSocketDirectly(proxy.proxy, sock.Get(), nTimeout, true)) { + if (!ConnectSocketDirectly(proxy.proxy, sock, nTimeout, true)) { outProxyConnectionFailed = true; return false; } @@ -797,29 +664,18 @@ bool ConnectThroughProxy(const proxyType& proxy, const std::string& strDest, int ProxyCredentials random_auth; static std::atomic_int counter(0); random_auth.username = random_auth.password = strprintf("%i", counter++); - if (!Socks5(strDest, (uint16_t)port, &random_auth, sock)) { + if (!Socks5(strDest, port, &random_auth, sock)) { return false; } } else { - if (!Socks5(strDest, (uint16_t)port, 0, sock)) { + if (!Socks5(strDest, port, 0, sock)) { return false; } } return true; } -/** - * Parse and resolve a specified subnet string into the appropriate internal - * representation. - * - * @param strSubnet A string representation of a subnet of the form `network - * address [ "/", ( CIDR-style suffix | netmask ) ]`(e.g. - * `2001:db8::/32`, `192.0.2.0/255.255.255.0`, or `8.8.8.8`). - * @param ret The resulting internal representation of a subnet. - * - * @returns Whether the operation succeeded or not. - */ -bool LookupSubNet(const std::string& strSubnet, CSubNet& ret) +bool LookupSubNet(const std::string& strSubnet, CSubNet& ret, DNSLookupFn dns_lookup_function) { if (!ValidAsCString(strSubnet)) { return false; @@ -830,7 +686,7 @@ bool LookupSubNet(const std::string& strSubnet, CSubNet& ret) std::string strAddress = strSubnet.substr(0, slash); // TODO: Use LookupHost(const std::string&, CNetAddr&, bool) instead to just get // one CNetAddr. - if (LookupHost(strAddress, vIP, 1, false)) + if (LookupHost(strAddress, vIP, 1, false, dns_lookup_function)) { CNetAddr network = vIP[0]; if (slash != strSubnet.npos) @@ -845,7 +701,7 @@ bool LookupSubNet(const std::string& strSubnet, CSubNet& ret) else // If not a valid number, try full netmask syntax { // Never allow lookup for netmask - if (LookupHost(strNetmask, vIP, 1, false)) { + if (LookupHost(strNetmask, vIP, 1, false, dns_lookup_function)) { ret = CSubNet(network, vIP[0]); return ret.IsValid(); } diff --git a/src/netbase.h b/src/netbase.h index 847a72ca8e..1f35c29dcb 100644 --- a/src/netbase.h +++ b/src/netbase.h @@ -18,6 +18,7 @@ #include <memory> #include <stdint.h> #include <string> +#include <type_traits> #include <vector> extern int nConnectTimeout; @@ -28,6 +29,22 @@ static const int DEFAULT_CONNECT_TIMEOUT = 5000; //! -dns default static const int DEFAULT_NAME_LOOKUP = true; +enum class ConnectionDirection { + None = 0, + In = (1U << 0), + Out = (1U << 1), + Both = (In | Out), +}; +static inline ConnectionDirection& operator|=(ConnectionDirection& a, ConnectionDirection b) { + using underlying = typename std::underlying_type<ConnectionDirection>::type; + a = ConnectionDirection(underlying(a) | underlying(b)); + return a; +} +static inline bool operator&(ConnectionDirection a, ConnectionDirection b) { + using underlying = typename std::underlying_type<ConnectionDirection>::type; + return (underlying(a) & underlying(b)); +} + class proxyType { public: @@ -40,6 +57,18 @@ public: bool randomize_credentials; }; +/** Credentials for proxy authentication */ +struct ProxyCredentials +{ + std::string username; + std::string password; +}; + +/** + * Wrapper for getaddrinfo(3). Do not use directly: call Lookup/LookupHost/LookupNumeric/LookupSubNet. + */ +std::vector<CNetAddr> WrappedGetAddrInfo(const std::string& name, bool allow_lookup); + enum Network ParseNetwork(const std::string& net); std::string GetNetworkName(enum Network net); /** Return a vector of publicly routable Network names; optionally append NET_UNROUTABLE. */ @@ -47,15 +76,107 @@ std::vector<std::string> GetNetworkNames(bool append_unroutable = false); bool SetProxy(enum Network net, const proxyType &addrProxy); bool GetProxy(enum Network net, proxyType &proxyInfoOut); bool IsProxy(const CNetAddr &addr); +/** + * Set the name proxy to use for all connections to nodes specified by a + * hostname. After setting this proxy, connecting to a node specified by a + * hostname won't result in a local lookup of said hostname, rather, connect to + * the node by asking the name proxy for a proxy connection to the hostname, + * effectively delegating the hostname lookup to the specified proxy. + * + * This delegation increases privacy for those who set the name proxy as they no + * longer leak their external hostname queries to their DNS servers. + * + * @returns Whether or not the operation succeeded. + * + * @note SOCKS5's support for UDP-over-SOCKS5 has been considered, but no SOCK5 + * server in common use (most notably Tor) actually implements UDP + * support, and a DNS resolver is beyond the scope of this project. + */ bool SetNameProxy(const proxyType &addrProxy); bool HaveNameProxy(); bool GetNameProxy(proxyType &nameProxyOut); -bool LookupHost(const std::string& name, std::vector<CNetAddr>& vIP, unsigned int nMaxSolutions, bool fAllowLookup); -bool LookupHost(const std::string& name, CNetAddr& addr, bool fAllowLookup); -bool Lookup(const std::string& name, CService& addr, int portDefault, bool fAllowLookup); -bool Lookup(const std::string& name, std::vector<CService>& vAddr, int portDefault, bool fAllowLookup, unsigned int nMaxSolutions); -CService LookupNumeric(const std::string& name, int portDefault = 0); -bool LookupSubNet(const std::string& strSubnet, CSubNet& subnet); + +using DNSLookupFn = std::function<std::vector<CNetAddr>(const std::string&, bool)>; +extern DNSLookupFn g_dns_lookup; + +/** + * Resolve a host string to its corresponding network addresses. + * + * @param name The string representing a host. Could be a name or a numerical + * IP address (IPv6 addresses in their bracketed form are + * allowed). + * @param[out] vIP The resulting network addresses to which the specified host + * string resolved. + * + * @returns Whether or not the specified host string successfully resolved to + * any resulting network addresses. + * + * @see Lookup(const std::string&, std::vector<CService>&, uint16_t, bool, unsigned int, DNSLookupFn) + * for additional parameter descriptions. + */ +bool LookupHost(const std::string& name, std::vector<CNetAddr>& vIP, unsigned int nMaxSolutions, bool fAllowLookup, DNSLookupFn dns_lookup_function = g_dns_lookup); + +/** + * Resolve a host string to its first corresponding network address. + * + * @see LookupHost(const std::string&, std::vector<CNetAddr>&, uint16_t, bool, DNSLookupFn) + * for additional parameter descriptions. + */ +bool LookupHost(const std::string& name, CNetAddr& addr, bool fAllowLookup, DNSLookupFn dns_lookup_function = g_dns_lookup); + +/** + * Resolve a service string to its corresponding service. + * + * @param name The string representing a service. Could be a name or a + * numerical IP address (IPv6 addresses should be in their + * disambiguated bracketed form), optionally followed by a uint16_t port + * number. (e.g. example.com:8333 or + * [2001:db8:85a3:8d3:1319:8a2e:370:7348]:420) + * @param[out] vAddr The resulting services to which the specified service string + * resolved. + * @param portDefault The default port for resulting services if not specified + * by the service string. + * @param fAllowLookup Whether or not hostname lookups are permitted. If yes, + * external queries may be performed. + * @param nMaxSolutions The maximum number of results we want, specifying 0 + * means "as many solutions as we get." + * + * @returns Whether or not the service string successfully resolved to any + * resulting services. + */ +bool Lookup(const std::string& name, std::vector<CService>& vAddr, uint16_t portDefault, bool fAllowLookup, unsigned int nMaxSolutions, DNSLookupFn dns_lookup_function = g_dns_lookup); + +/** + * Resolve a service string to its first corresponding service. + * + * @see Lookup(const std::string&, std::vector<CService>&, uint16_t, bool, unsigned int, DNSLookupFn) + * for additional parameter descriptions. + */ +bool Lookup(const std::string& name, CService& addr, uint16_t portDefault, bool fAllowLookup, DNSLookupFn dns_lookup_function = g_dns_lookup); + +/** + * Resolve a service string with a numeric IP to its first corresponding + * service. + * + * @returns The resulting CService if the resolution was successful, [::]:0 otherwise. + * + * @see Lookup(const std::string&, std::vector<CService>&, uint16_t, bool, unsigned int, DNSLookupFn) + * for additional parameter descriptions. + */ +CService LookupNumeric(const std::string& name, uint16_t portDefault = 0, DNSLookupFn dns_lookup_function = g_dns_lookup); + +/** + * Parse and resolve a specified subnet string into the appropriate internal + * representation. + * + * @param strSubnet A string representation of a subnet of the form `network + * address [ "/", ( CIDR-style suffix | netmask ) ]`(e.g. + * `2001:db8::/32`, `192.0.2.0/255.255.255.0`, or `8.8.8.8`). + * @param ret The resulting internal representation of a subnet. + * + * @returns Whether the operation succeeded or not. + */ +bool LookupSubNet(const std::string& strSubnet, CSubNet& subnet, DNSLookupFn dns_lookup_function = g_dns_lookup); /** * Create a TCP socket in the given address family. @@ -69,12 +190,61 @@ std::unique_ptr<Sock> CreateSockTCP(const CService& address_family); */ extern std::function<std::unique_ptr<Sock>(const CService&)> CreateSock; -bool ConnectSocketDirectly(const CService &addrConnect, const SOCKET& hSocketRet, int nTimeout, bool manual_connection); -bool ConnectThroughProxy(const proxyType& proxy, const std::string& strDest, int port, const Sock& sock, int nTimeout, bool& outProxyConnectionFailed); +/** + * Try to connect to the specified service on the specified socket. + * + * @param addrConnect The service to which to connect. + * @param sock The socket on which to connect. + * @param nTimeout Wait this many milliseconds for the connection to be + * established. + * @param manual_connection Whether or not the connection was manually requested + * (e.g. through the addnode RPC) + * + * @returns Whether or not a connection was successfully made. + */ +bool ConnectSocketDirectly(const CService &addrConnect, const Sock& sock, int nTimeout, bool manual_connection); + +/** + * Connect to a specified destination service through a SOCKS5 proxy by first + * connecting to the SOCKS5 proxy. + * + * @param proxy The SOCKS5 proxy. + * @param strDest The destination service to which to connect. + * @param port The destination port. + * @param sock The socket on which to connect to the SOCKS5 proxy. + * @param nTimeout Wait this many milliseconds for the connection to the SOCKS5 + * proxy to be established. + * @param[out] outProxyConnectionFailed Whether or not the connection to the + * SOCKS5 proxy failed. + * + * @returns Whether or not the operation succeeded. + */ +bool ConnectThroughProxy(const proxyType& proxy, const std::string& strDest, uint16_t port, const Sock& sock, int nTimeout, bool& outProxyConnectionFailed); + /** Disable or enable blocking-mode for a socket */ bool SetSocketNonBlocking(const SOCKET& hSocket, bool fNonBlocking); /** Set the TCP_NODELAY flag on a socket */ bool SetSocketNoDelay(const SOCKET& hSocket); void InterruptSocks5(bool interrupt); +/** + * Connect to a specified destination service through an already connected + * SOCKS5 proxy. + * + * @param strDest The destination fully-qualified domain name. + * @param port The destination port. + * @param auth The credentials with which to authenticate with the specified + * SOCKS5 proxy. + * @param sock The SOCKS5 proxy socket. + * + * @returns Whether or not the operation succeeded. + * + * @note The specified SOCKS5 proxy socket must already be connected to the + * SOCKS5 proxy. + * + * @see <a href="https://www.ietf.org/rfc/rfc1928.txt">RFC1928: SOCKS Protocol + * Version 5</a> + */ +bool Socks5(const std::string& strDest, uint16_t port, const ProxyCredentials* auth, const Sock& socket); + #endif // BITCOIN_NETBASE_H diff --git a/src/node/coin.cpp b/src/node/coin.cpp index f4f86cdbe9..23d4fa2aae 100644 --- a/src/node/coin.cpp +++ b/src/node/coin.cpp @@ -11,8 +11,10 @@ void FindCoins(const NodeContext& node, std::map<COutPoint, Coin>& coins) { assert(node.mempool); + assert(node.chainman); LOCK2(cs_main, node.mempool->cs); - CCoinsViewCache& chain_view = ::ChainstateActive().CoinsTip(); + assert(std::addressof(::ChainstateActive()) == std::addressof(node.chainman->ActiveChainstate())); + CCoinsViewCache& chain_view = node.chainman->ActiveChainstate().CoinsTip(); CCoinsViewMemPool mempool_view(&chain_view, *node.mempool); for (auto& coin : coins) { if (!mempool_view.GetCoin(coin.first, coin.second)) { diff --git a/src/node/coinstats.cpp b/src/node/coinstats.cpp index 06fcc33725..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 */ + @@ -83,7 +84,7 @@ static void ApplyStats(CCoinsStats& stats, T& hash_obj, const uint256& hash, con //! Calculate statistics about the unspent transaction output set template <typename T> -static bool GetUTXOStats(CCoinsView* view, CCoinsStats& stats, T hash_obj, const std::function<void()>& interruption_point) +static bool GetUTXOStats(CCoinsView* view, BlockManager& blockman, CCoinsStats& stats, T hash_obj, const std::function<void()>& interruption_point) { stats = CCoinsStats(); std::unique_ptr<CCoinsViewCursor> pcursor(view->Cursor()); @@ -92,7 +93,8 @@ static bool GetUTXOStats(CCoinsView* view, CCoinsStats& stats, T hash_obj, const stats.hashBlock = pcursor->GetBestBlock(); { LOCK(cs_main); - stats.nHeight = g_chainman.m_blockman.LookupBlockIndex(stats.hashBlock)->nHeight; + assert(std::addressof(g_chainman.m_blockman) == std::addressof(blockman)); + stats.nHeight = blockman.LookupBlockIndex(stats.hashBlock)->nHeight; } PrepareHash(hash_obj, stats); @@ -126,19 +128,19 @@ static bool GetUTXOStats(CCoinsView* view, CCoinsStats& stats, T hash_obj, const return true; } -bool GetUTXOStats(CCoinsView* view, CCoinsStats& stats, CoinStatsHashType hash_type, const std::function<void()>& interruption_point) +bool GetUTXOStats(CCoinsView* view, BlockManager& blockman, CCoinsStats& stats, CoinStatsHashType hash_type, const std::function<void()>& interruption_point) { switch (hash_type) { case(CoinStatsHashType::HASH_SERIALIZED): { CHashWriter ss(SER_GETHASH, PROTOCOL_VERSION); - return GetUTXOStats(view, stats, ss, interruption_point); + return GetUTXOStats(view, blockman, stats, ss, interruption_point); } case(CoinStatsHashType::MUHASH): { MuHash3072 muhash; - return GetUTXOStats(view, stats, muhash, interruption_point); + return GetUTXOStats(view, blockman, stats, muhash, interruption_point); } case(CoinStatsHashType::NONE): { - return GetUTXOStats(view, stats, nullptr, interruption_point); + return GetUTXOStats(view, blockman, stats, nullptr, interruption_point); } } // no default case, so the compiler can warn about missing cases assert(false); diff --git a/src/node/coinstats.h b/src/node/coinstats.h index f02b95235f..975651dcc4 100644 --- a/src/node/coinstats.h +++ b/src/node/coinstats.h @@ -12,6 +12,7 @@ #include <cstdint> #include <functional> +class BlockManager; class CCoinsView; enum class CoinStatsHashType { @@ -36,6 +37,6 @@ struct CCoinsStats }; //! Calculate statistics about the unspent transaction output set -bool GetUTXOStats(CCoinsView* view, CCoinsStats& stats, const CoinStatsHashType hash_type, const std::function<void()>& interruption_point = {}); +bool GetUTXOStats(CCoinsView* view, BlockManager& blockman, CCoinsStats& stats, const CoinStatsHashType hash_type, const std::function<void()>& interruption_point = {}); #endif // BITCOIN_NODE_COINSTATS_H diff --git a/src/node/context.cpp b/src/node/context.cpp index 958221a913..6d22a6b110 100644 --- a/src/node/context.cpp +++ b/src/node/context.cpp @@ -4,6 +4,7 @@ #include <node/context.h> +#include <addrman.h> #include <banman.h> #include <interfaces/chain.h> #include <net.h> diff --git a/src/node/context.h b/src/node/context.h index 9b611bf8f5..2be9a584e6 100644 --- a/src/node/context.h +++ b/src/node/context.h @@ -12,6 +12,7 @@ class ArgsManager; class BanMan; +class CAddrMan; class CBlockPolicyEstimator; class CConnman; class CScheduler; @@ -35,6 +36,7 @@ class WalletClient; //! any member functions. It should just be a collection of references that can //! be used without pulling in unwanted dependencies or functionality. struct NodeContext { + std::unique_ptr<CAddrMan> addrman; std::unique_ptr<CConnman> connman; std::unique_ptr<CTxMemPool> mempool; std::unique_ptr<CBlockPolicyEstimator> fee_estimator; diff --git a/src/node/interfaces.cpp b/src/node/interfaces.cpp index ec976fe9bf..7ad02f81dc 100644 --- a/src/node/interfaces.cpp +++ b/src/node/interfaces.cpp @@ -38,7 +38,6 @@ #include <uint256.h> #include <univalue.h> #include <util/check.h> -#include <util/ref.h> #include <util/system.h> #include <util/translation.h> #include <validation.h> @@ -49,7 +48,9 @@ #include <config/bitcoin-config.h> #endif +#include <any> #include <memory> +#include <optional> #include <utility> using interfaces::BlockTip; @@ -64,6 +65,8 @@ namespace node { namespace { class NodeImpl : public Node { +private: + ChainstateManager& chainman() { return *Assert(m_context->chainman); } public: explicit NodeImpl(NodeContext* context) { setContext(context); } void initLogging() override { InitLogging(*Assert(m_context->args)); } @@ -96,7 +99,7 @@ public: bool shutdownRequested() override { return ShutdownRequested(); } void mapPort(bool use_upnp, bool use_natpmp) override { StartMapPort(use_upnp, use_natpmp); } bool getProxy(Network net, proxyType& proxy_info) override { return GetProxy(net, proxy_info); } - size_t getNodeCount(CConnman::NumConnections flags) override + size_t getNodeCount(ConnectionDirection flags) override { return m_context->connman ? m_context->connman->GetNodeCount(flags) : 0; } @@ -182,18 +185,28 @@ public: int getNumBlocks() override { LOCK(::cs_main); - return ::ChainActive().Height(); + assert(std::addressof(::ChainActive()) == std::addressof(chainman().ActiveChain())); + return chainman().ActiveChain().Height(); } uint256 getBestBlockHash() override { - const CBlockIndex* tip = WITH_LOCK(::cs_main, return ::ChainActive().Tip()); + const CBlockIndex* tip; + { + // TODO: Temporary scope to check correctness of refactored code. + // Should be removed manually after merge of + // https://github.com/bitcoin/bitcoin/pull/20158 + LOCK(cs_main); + assert(std::addressof(::ChainActive()) == std::addressof(chainman().ActiveChain())); + tip = chainman().ActiveChain().Tip(); + } return tip ? tip->GetBlockHash() : Params().GenesisBlock().GetHash(); } int64_t getLastBlockTime() override { LOCK(::cs_main); - if (::ChainActive().Tip()) { - return ::ChainActive().Tip()->GetBlockTime(); + assert(std::addressof(::ChainActive()) == std::addressof(chainman().ActiveChain())); + if (chainman().ActiveChain().Tip()) { + return chainman().ActiveChain().Tip()->GetBlockTime(); } return Params().GenesisBlock().GetBlockTime(); // Genesis block's time of current network } @@ -202,11 +215,23 @@ public: const CBlockIndex* tip; { LOCK(::cs_main); - tip = ::ChainActive().Tip(); + assert(std::addressof(::ChainActive()) == std::addressof(chainman().ActiveChain())); + tip = chainman().ActiveChain().Tip(); } return GuessVerificationProgress(Params().TxData(), tip); } - bool isInitialBlockDownload() override { return ::ChainstateActive().IsInitialBlockDownload(); } + bool isInitialBlockDownload() override { + const CChainState* active_chainstate; + { + // TODO: Temporary scope to check correctness of refactored code. + // Should be removed manually after merge of + // https://github.com/bitcoin/bitcoin/pull/20158 + LOCK(::cs_main); + active_chainstate = &m_context->chainman->ActiveChainstate(); + assert(std::addressof(::ChainstateActive()) == std::addressof(*active_chainstate)); + } + return active_chainstate->IsInitialBlockDownload(); + } bool getReindex() override { return ::fReindex; } bool getImporting() override { return ::fImporting; } void setNetworkActive(bool active) override @@ -231,7 +256,8 @@ public: bool getUnspentOutput(const COutPoint& output, Coin& coin) override { LOCK(::cs_main); - return ::ChainstateActive().CoinsTip().GetCoin(output, coin); + assert(std::addressof(::ChainstateActive()) == std::addressof(chainman().ActiveChainstate())); + return chainman().ActiveChainstate().CoinsTip().GetCoin(output, coin); } WalletClient& walletClient() override { @@ -289,13 +315,13 @@ public: { m_context = context; if (context) { - m_context_ref.Set(*context); + m_context_ref = context; } else { - m_context_ref.Clear(); + m_context_ref.reset(); } } NodeContext* m_context{nullptr}; - util::Ref m_context_ref; + std::any m_context_ref; }; bool FillBlock(const CBlockIndex* index, const FoundBlock& block, UniqueLock<RecursiveMutex>& lock, const CChain& active) @@ -405,9 +431,11 @@ public: class ChainImpl : public Chain { +private: + ChainstateManager& chainman() { return *Assert(m_node.chainman); } public: explicit ChainImpl(NodeContext& node) : m_node(node) {} - Optional<int> getHeight() override + std::optional<int> getHeight() override { LOCK(::cs_main); const CChain& active = Assert(m_node.chainman)->ActiveChain(); @@ -415,7 +443,7 @@ public: if (height >= 0) { return height; } - return nullopt; + return std::nullopt; } uint256 getBlockHash(int height) override { @@ -441,22 +469,25 @@ public: bool checkFinalTx(const CTransaction& tx) override { LOCK(cs_main); - return CheckFinalTx(::ChainActive().Tip(), tx); + assert(std::addressof(::ChainActive()) == std::addressof(chainman().ActiveChain())); + return CheckFinalTx(chainman().ActiveChain().Tip(), tx); } - Optional<int> findLocatorFork(const CBlockLocator& locator) override + std::optional<int> findLocatorFork(const CBlockLocator& locator) override { LOCK(cs_main); const CChain& active = Assert(m_node.chainman)->ActiveChain(); - if (CBlockIndex* fork = g_chainman.m_blockman.FindForkInGlobalIndex(active, locator)) { + assert(std::addressof(g_chainman) == std::addressof(*m_node.chainman)); + if (CBlockIndex* fork = m_node.chainman->m_blockman.FindForkInGlobalIndex(active, locator)) { return fork->nHeight; } - return nullopt; + return std::nullopt; } bool findBlock(const uint256& hash, const FoundBlock& block) override { WAIT_LOCK(cs_main, lock); const CChain& active = Assert(m_node.chainman)->ActiveChain(); - return FillBlock(g_chainman.m_blockman.LookupBlockIndex(hash), block, lock, active); + assert(std::addressof(g_chainman) == std::addressof(*m_node.chainman)); + return FillBlock(m_node.chainman->m_blockman.LookupBlockIndex(hash), block, lock, active); } bool findFirstBlockWithTimeAndHeight(int64_t min_time, int min_height, const FoundBlock& block) override { @@ -468,7 +499,8 @@ public: { WAIT_LOCK(cs_main, lock); const CChain& active = Assert(m_node.chainman)->ActiveChain(); - if (const CBlockIndex* block = g_chainman.m_blockman.LookupBlockIndex(block_hash)) { + assert(std::addressof(g_chainman) == std::addressof(*m_node.chainman)); + if (const CBlockIndex* block = m_node.chainman->m_blockman.LookupBlockIndex(block_hash)) { if (const CBlockIndex* ancestor = block->GetAncestor(ancestor_height)) { return FillBlock(ancestor, ancestor_out, lock, active); } @@ -479,8 +511,10 @@ public: { WAIT_LOCK(cs_main, lock); const CChain& active = Assert(m_node.chainman)->ActiveChain(); - const CBlockIndex* block = g_chainman.m_blockman.LookupBlockIndex(block_hash); - const CBlockIndex* ancestor = g_chainman.m_blockman.LookupBlockIndex(ancestor_hash); + assert(std::addressof(g_chainman) == std::addressof(*m_node.chainman)); + const CBlockIndex* block = m_node.chainman->m_blockman.LookupBlockIndex(block_hash); + assert(std::addressof(g_chainman) == std::addressof(*m_node.chainman)); + const CBlockIndex* ancestor = m_node.chainman->m_blockman.LookupBlockIndex(ancestor_hash); if (block && ancestor && block->GetAncestor(ancestor->nHeight) != ancestor) ancestor = nullptr; return FillBlock(ancestor, ancestor_out, lock, active); } @@ -488,8 +522,10 @@ public: { WAIT_LOCK(cs_main, lock); const CChain& active = Assert(m_node.chainman)->ActiveChain(); - const CBlockIndex* block1 = g_chainman.m_blockman.LookupBlockIndex(block_hash1); - const CBlockIndex* block2 = g_chainman.m_blockman.LookupBlockIndex(block_hash2); + assert(std::addressof(g_chainman) == std::addressof(*m_node.chainman)); + const CBlockIndex* block1 = m_node.chainman->m_blockman.LookupBlockIndex(block_hash1); + assert(std::addressof(g_chainman) == std::addressof(*m_node.chainman)); + const CBlockIndex* block2 = m_node.chainman->m_blockman.LookupBlockIndex(block_hash2); const CBlockIndex* ancestor = block1 && block2 ? LastCommonAncestor(block1, block2) : nullptr; // Using & instead of && below to avoid short circuiting and leaving // output uninitialized. @@ -499,9 +535,10 @@ public: double guessVerificationProgress(const uint256& block_hash) override { LOCK(cs_main); - return GuessVerificationProgress(Params().TxData(), g_chainman.m_blockman.LookupBlockIndex(block_hash)); + assert(std::addressof(g_chainman.m_blockman) == std::addressof(chainman().m_blockman)); + return GuessVerificationProgress(Params().TxData(), chainman().m_blockman.LookupBlockIndex(block_hash)); } - bool hasBlocks(const uint256& block_hash, int min_height, Optional<int> max_height) override + bool hasBlocks(const uint256& block_hash, int min_height, std::optional<int> max_height) override { // hasBlocks returns true if all ancestors of block_hash in specified // range have block data (are not pruned), false if any ancestors in @@ -511,7 +548,8 @@ public: // used to limit the range, and passing min_height that's too low or // max_height that's too high will not crash or change the result. LOCK(::cs_main); - if (CBlockIndex* block = g_chainman.m_blockman.LookupBlockIndex(block_hash)) { + assert(std::addressof(g_chainman.m_blockman) == std::addressof(chainman().m_blockman)); + if (CBlockIndex* block = chainman().m_blockman.LookupBlockIndex(block_hash)) { if (max_height && block->nHeight >= *max_height) block = block->GetAncestor(*max_height); for (; block->nStatus & BLOCK_HAVE_DATA; block = block->pprev) { // Check pprev to not segfault if min_height is too low @@ -526,6 +564,12 @@ public: LOCK(m_node.mempool->cs); return IsRBFOptIn(tx, *m_node.mempool); } + bool isInMempool(const uint256& txid) override + { + if (!m_node.mempool) return false; + LOCK(m_node.mempool->cs); + return m_node.mempool->exists(txid); + } bool hasDescendantsInMempool(const uint256& txid) override { if (!m_node.mempool) return false; @@ -595,7 +639,18 @@ public: return ::fHavePruned; } bool isReadyToBroadcast() override { return !::fImporting && !::fReindex && !isInitialBlockDownload(); } - bool isInitialBlockDownload() override { return ::ChainstateActive().IsInitialBlockDownload(); } + bool isInitialBlockDownload() override { + const CChainState* active_chainstate; + { + // TODO: Temporary scope to check correctness of refactored code. + // Should be removed manually after merge of + // https://github.com/bitcoin/bitcoin/pull/20158 + LOCK(::cs_main); + active_chainstate = &chainman().ActiveChainstate(); + assert(std::addressof(::ChainstateActive()) == std::addressof(*active_chainstate)); + } + return active_chainstate->IsInitialBlockDownload(); + } bool shutdownRequested() override { return ShutdownRequested(); } int64_t getAdjustedTime() override { return GetAdjustedTime(); } void initMessage(const std::string& message) override { ::uiInterface.InitMessage(message); } @@ -607,7 +662,7 @@ public: } std::unique_ptr<Handler> handleNotifications(std::shared_ptr<Notifications> notifications) override { - return MakeUnique<NotificationsHandlerImpl>(std::move(notifications)); + return std::make_unique<NotificationsHandlerImpl>(std::move(notifications)); } void waitForNotificationsIfTipChanged(const uint256& old_tip) override { @@ -620,7 +675,7 @@ public: } std::unique_ptr<Handler> handleRpc(const CRPCCommand& command) override { - return MakeUnique<RpcHandlerImpl>(command); + return std::make_unique<RpcHandlerImpl>(command); } bool rpcEnableDeprecated(const std::string& method) override { return IsDeprecatedRPCEnabled(method); } void rpcRunLater(const std::string& name, std::function<void()> fn, int64_t seconds) override @@ -663,6 +718,6 @@ public: } // namespace node namespace interfaces { -std::unique_ptr<Node> MakeNode(NodeContext* context) { return MakeUnique<node::NodeImpl>(context); } -std::unique_ptr<Chain> MakeChain(NodeContext& context) { return MakeUnique<node::ChainImpl>(context); } +std::unique_ptr<Node> MakeNode(NodeContext* context) { return std::make_unique<node::NodeImpl>(context); } +std::unique_ptr<Chain> MakeChain(NodeContext& context) { return std::make_unique<node::ChainImpl>(context); } } // namespace interfaces diff --git a/src/node/psbt.h b/src/node/psbt.h index 7384dc415c..def4385c09 100644 --- a/src/node/psbt.h +++ b/src/node/psbt.h @@ -7,6 +7,8 @@ #include <psbt.h> +#include <optional> + /** * Holds an analysis of one input from a PSBT */ @@ -25,18 +27,18 @@ struct PSBTInputAnalysis { * Holds the results of AnalyzePSBT (miscellaneous information about a PSBT) */ struct PSBTAnalysis { - Optional<size_t> estimated_vsize; //!< Estimated weight of the transaction - Optional<CFeeRate> estimated_feerate; //!< Estimated feerate (fee / weight) of the transaction - Optional<CAmount> fee; //!< Amount of fee being paid by the transaction - std::vector<PSBTInputAnalysis> inputs; //!< More information about the individual inputs of the transaction - PSBTRole next; //!< Which of the BIP 174 roles needs to handle the transaction next - std::string error; //!< Error message + std::optional<size_t> estimated_vsize; //!< Estimated weight of the transaction + std::optional<CFeeRate> estimated_feerate; //!< Estimated feerate (fee / weight) of the transaction + std::optional<CAmount> fee; //!< Amount of fee being paid by the transaction + std::vector<PSBTInputAnalysis> inputs; //!< More information about the individual inputs of the transaction + PSBTRole next; //!< Which of the BIP 174 roles needs to handle the transaction next + std::string error; //!< Error message void SetInvalid(std::string err_msg) { - estimated_vsize = nullopt; - estimated_feerate = nullopt; - fee = nullopt; + estimated_vsize = std::nullopt; + estimated_feerate = std::nullopt; + fee = std::nullopt; inputs.clear(); next = PSBTRole::CREATOR; error = err_msg; diff --git a/src/node/transaction.cpp b/src/node/transaction.cpp index 3b3fab7b6b..691b2791d7 100644 --- a/src/node/transaction.cpp +++ b/src/node/transaction.cpp @@ -29,19 +29,21 @@ static TransactionError HandleATMPError(const TxValidationState& state, std::str TransactionError BroadcastTransaction(NodeContext& node, const CTransactionRef tx, std::string& err_string, const CAmount& max_tx_fee, bool relay, bool wait_callback) { // BroadcastTransaction can be called by either sendrawtransaction RPC or wallet RPCs. - // node.connman is assigned both before chain clients and before RPC server is accepting calls, - // and reset after chain clients and RPC sever are stopped. node.connman should never be null here. - assert(node.connman); + // node.peerman is assigned both before chain clients and before RPC server is accepting calls, + // and reset after chain clients and RPC sever are stopped. node.peerman should never be null here. + assert(node.peerman); assert(node.mempool); std::promise<void> promise; uint256 hashTx = tx->GetHash(); bool callback_set = false; { // cs_main scope + assert(node.chainman); LOCK(cs_main); + assert(std::addressof(::ChainstateActive()) == std::addressof(node.chainman->ActiveChainstate())); // If the transaction is already confirmed in the chain, don't do anything // and return early. - CCoinsViewCache &view = ::ChainstateActive().CoinsTip(); + CCoinsViewCache &view = node.chainman->ActiveChainstate().CoinsTip(); for (size_t o = 0; o < tx->vout.size(); o++) { const Coin& existingCoin = view.AccessCoin(COutPoint(hashTx, o)); // IsSpent doesn't mean the coin is spent, it means the output doesn't exist. @@ -53,7 +55,7 @@ TransactionError BroadcastTransaction(NodeContext& node, const CTransactionRef t if (max_tx_fee > 0) { // First, call ATMP with test_accept and check the fee. If ATMP // fails here, return error immediately. - const MempoolAcceptResult result = AcceptToMemoryPool(::ChainstateActive(), *node.mempool, tx, false /* bypass_limits */, + const MempoolAcceptResult result = AcceptToMemoryPool(node.chainman->ActiveChainstate(), *node.mempool, tx, false /* bypass_limits */, true /* test_accept */); if (result.m_result_type != MempoolAcceptResult::ResultType::VALID) { return HandleATMPError(result.m_state, err_string); @@ -62,7 +64,7 @@ TransactionError BroadcastTransaction(NodeContext& node, const CTransactionRef t } } // Try to submit the transaction to the mempool. - const MempoolAcceptResult result = AcceptToMemoryPool(::ChainstateActive(), *node.mempool, tx, false /* bypass_limits */, + const MempoolAcceptResult result = AcceptToMemoryPool(node.chainman->ActiveChainstate(), *node.mempool, tx, false /* bypass_limits */, false /* test_accept */); if (result.m_result_type != MempoolAcceptResult::ResultType::VALID) { return HandleATMPError(result.m_state, err_string); @@ -100,7 +102,7 @@ TransactionError BroadcastTransaction(NodeContext& node, const CTransactionRef t node.mempool->AddUnbroadcastTx(hashTx); LOCK(cs_main); - RelayTransaction(hashTx, tx->GetWitnessHash(), *node.connman); + node.peerman->RelayTransaction(hashTx, tx->GetWitnessHash()); } return TransactionError::OK; diff --git a/src/optional.h b/src/optional.h deleted file mode 100644 index 583c56eabd..0000000000 --- a/src/optional.h +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (c) 2017-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. - -#ifndef BITCOIN_OPTIONAL_H -#define BITCOIN_OPTIONAL_H - -#include <optional> -#include <utility> - -//! Substitute for C++17 std::optional -//! DEPRECATED use std::optional in new code. -template <typename T> -using Optional = std::optional<T>; - -//! Substitute for C++17 std::nullopt -//! DEPRECATED use std::nullopt in new code. -static auto& nullopt = std::nullopt; - -#endif // BITCOIN_OPTIONAL_H diff --git a/src/psbt.h b/src/psbt.h index b566726ee3..96ae39fdb8 100644 --- a/src/psbt.h +++ b/src/psbt.h @@ -7,13 +7,14 @@ #include <attributes.h> #include <node/transaction.h> -#include <optional.h> #include <policy/feerate.h> #include <primitives/transaction.h> #include <pubkey.h> #include <script/sign.h> #include <script/signingprovider.h> +#include <optional> + // Magic bytes static constexpr uint8_t PSBT_MAGIC_BYTES[5] = {'p', 's', 'b', 't', 0xff}; @@ -389,7 +390,7 @@ struct PSBTOutput /** A version of CTransaction with the PSBT format*/ struct PartiallySignedTransaction { - Optional<CMutableTransaction> tx; + std::optional<CMutableTransaction> tx; std::vector<PSBTInput> inputs; std::vector<PSBTOutput> outputs; std::map<std::vector<unsigned char>, std::vector<unsigned char>> unknown; 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/addressbookpage.cpp b/src/qt/addressbookpage.cpp index ab6168a541..9927e925ac 100644 --- a/src/qt/addressbookpage.cpp +++ b/src/qt/addressbookpage.cpp @@ -295,7 +295,7 @@ void AddressBookPage::on_exportButton_clicked() // CSV is currently the only supported format QString filename = GUIUtil::getSaveFileName(this, tr("Export Address List"), QString(), - tr("Comma separated file (*.csv)"), nullptr); + tr("Comma separated file", "Name of CSV file format") + QLatin1String(" (*.csv)"), nullptr); if (filename.isNull()) return; diff --git a/src/qt/addresstablemodel.cpp b/src/qt/addresstablemodel.cpp index bb444f22b3..ee2462ed74 100644 --- a/src/qt/addresstablemodel.cpp +++ b/src/qt/addresstablemodel.cpp @@ -198,42 +198,38 @@ QVariant AddressTableModel::data(const QModelIndex &index, int role) const AddressTableEntry *rec = static_cast<AddressTableEntry*>(index.internalPointer()); - if(role == Qt::DisplayRole || role == Qt::EditRole) - { - switch(index.column()) - { + const auto column = static_cast<ColumnIndex>(index.column()); + if (role == Qt::DisplayRole || role == Qt::EditRole) { + switch (column) { case Label: - if(rec->label.isEmpty() && role == Qt::DisplayRole) - { + if (rec->label.isEmpty() && role == Qt::DisplayRole) { return tr("(no label)"); - } - else - { + } else { return rec->label; } case Address: return rec->address; - } - } - else if (role == Qt::FontRole) - { - QFont font; - if(index.column() == Address) - { - font = GUIUtil::fixedPitchFont(); - } - return font; - } - else if (role == TypeRole) - { + } // no default case, so the compiler can warn about missing cases + assert(false); + } else if (role == Qt::FontRole) { + switch (column) { + case Label: + return QFont(); + case Address: + return GUIUtil::fixedPitchFont(); + } // no default case, so the compiler can warn about missing cases + assert(false); + } else if (role == TypeRole) { switch(rec->type) { case AddressTableEntry::Sending: return Send; case AddressTableEntry::Receiving: return Receive; - default: break; - } + case AddressTableEntry::Hidden: + return {}; + } // no default case, so the compiler can warn about missing cases + assert(false); } return QVariant(); } 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/bantablemodel.cpp b/src/qt/bantablemodel.cpp index a01a7bc386..6cb4a4c546 100644 --- a/src/qt/bantablemodel.cpp +++ b/src/qt/bantablemodel.cpp @@ -23,15 +23,13 @@ bool BannedNodeLessThan::operator()(const CCombinedBan& left, const CCombinedBan if (order == Qt::DescendingOrder) std::swap(pLeft, pRight); - switch(column) - { + switch (static_cast<BanTableModel::ColumnIndex>(column)) { case BanTableModel::Address: return pLeft->subnet.ToString().compare(pRight->subnet.ToString()) < 0; case BanTableModel::Bantime: return pLeft->banEntry.nBanUntil < pRight->banEntry.nBanUntil; - } - - return false; + } // no default case, so the compiler can warn about missing cases + assert(false); } // private implementation @@ -119,16 +117,17 @@ QVariant BanTableModel::data(const QModelIndex &index, int role) const CCombinedBan *rec = static_cast<CCombinedBan*>(index.internalPointer()); + const auto column = static_cast<ColumnIndex>(index.column()); if (role == Qt::DisplayRole) { - switch(index.column()) - { + switch (column) { case Address: return QString::fromStdString(rec->subnet.ToString()); case Bantime: QDateTime date = QDateTime::fromMSecsSinceEpoch(0); date = date.addSecs(rec->banEntry.nBanUntil); return QLocale::system().toString(date, QLocale::LongFormat); - } + } // no default case, so the compiler can warn about missing cases + assert(false); } return QVariant(); diff --git a/src/qt/bitcoin.cpp b/src/qt/bitcoin.cpp index bad74fcbcc..fc6d0febc2 100644 --- a/src/qt/bitcoin.cpp +++ b/src/qt/bitcoin.cpp @@ -52,7 +52,6 @@ #include <QThread> #include <QTimer> #include <QTranslator> -#include <QtGlobal> #if defined(QT_STATICPLUGIN) #include <QtPlugin> @@ -62,6 +61,7 @@ Q_IMPORT_PLUGIN(QXcbIntegrationPlugin); Q_IMPORT_PLUGIN(QWindowsIntegrationPlugin); #elif defined(QT_QPA_PLATFORM_COCOA) Q_IMPORT_PLUGIN(QCocoaIntegrationPlugin); +Q_IMPORT_PLUGIN(QMacStylePlugin); #endif #endif @@ -464,15 +464,12 @@ int GuiMain(int argc, char* argv[]) // Generate high-dpi pixmaps QApplication::setAttribute(Qt::AA_UseHighDpiPixmaps); -#if QT_VERSION >= 0x050600 QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); -#endif -#if (QT_VERSION <= QT_VERSION_CHECK(5, 9, 8)) && defined(Q_OS_MACOS) - const auto os_name = QSysInfo::prettyProductName(); - if (os_name.startsWith("macOS 11") || os_name.startsWith("macOS 10.16")) { - QApplication::setStyle("fusion"); - } +#if defined(QT_QPA_PLATFORM_ANDROID) + QApplication::setAttribute(Qt::AA_DontUseNativeMenuBar); + QApplication::setAttribute(Qt::AA_DontCreateNativeWidgetSiblings); + QApplication::setAttribute(Qt::AA_DontUseNativeDialogs); #endif BitcoinApplication app; diff --git a/src/qt/clientmodel.cpp b/src/qt/clientmodel.cpp index 56813b2d19..b244bc94f2 100644 --- a/src/qt/clientmodel.cpp +++ b/src/qt/clientmodel.cpp @@ -71,14 +71,14 @@ ClientModel::~ClientModel() int ClientModel::getNumConnections(unsigned int flags) const { - CConnman::NumConnections connections = CConnman::CONNECTIONS_NONE; + ConnectionDirection connections = ConnectionDirection::None; if(flags == CONNECTIONS_IN) - connections = CConnman::CONNECTIONS_IN; + connections = ConnectionDirection::In; else if (flags == CONNECTIONS_OUT) - connections = CConnman::CONNECTIONS_OUT; + connections = ConnectionDirection::Out; else if (flags == CONNECTIONS_ALL) - connections = CConnman::CONNECTIONS_ALL; + connections = ConnectionDirection::Both; return m_node.getNodeCount(connections); } 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/forms/overviewpage.ui b/src/qt/forms/overviewpage.ui index b82143e1ba..f85de0811a 100644 --- a/src/qt/forms/overviewpage.ui +++ b/src/qt/forms/overviewpage.ui @@ -68,7 +68,7 @@ </property> <property name="maximumSize"> <size> - <width>30</width> + <width>45</width> <height>16777215</height> </size> </property> @@ -89,9 +89,6 @@ <height>24</height> </size> </property> - <property name="flat"> - <bool>true</bool> - </property> </widget> </item> <item> @@ -406,7 +403,7 @@ </property> <property name="maximumSize"> <size> - <width>30</width> + <width>45</width> <height>16777215</height> </size> </property> @@ -427,9 +424,6 @@ <height>24</height> </size> </property> - <property name="flat"> - <bool>true</bool> - </property> </widget> </item> <item> diff --git a/src/qt/guiutil.cpp b/src/qt/guiutil.cpp index c70bd9f418..b4afdbcc22 100644 --- a/src/qt/guiutil.cpp +++ b/src/qt/guiutil.cpp @@ -59,6 +59,8 @@ #include <QUrlQuery> #include <QtGlobal> +#include <chrono> + #if defined(Q_OS_MAC) #include <QProcess> @@ -681,13 +683,13 @@ QString formatDurationStr(int secs) int seconds = secs % 60; if (days) - strList.append(QString(QObject::tr("%1 d")).arg(days)); + strList.append(QObject::tr("%1 d").arg(days)); if (hours) - strList.append(QString(QObject::tr("%1 h")).arg(hours)); + strList.append(QObject::tr("%1 h").arg(hours)); if (mins) - strList.append(QString(QObject::tr("%1 m")).arg(mins)); + strList.append(QObject::tr("%1 m").arg(mins)); if (seconds || (!days && !hours && !mins)) - strList.append(QString(QObject::tr("%1 s")).arg(seconds)); + strList.append(QObject::tr("%1 s").arg(seconds)); return strList.join(" "); } @@ -706,14 +708,16 @@ QString formatServicesStr(quint64 mask) return QObject::tr("None"); } -QString formatPingTime(int64_t ping_usec) +QString formatPingTime(std::chrono::microseconds ping_time) { - return (ping_usec == std::numeric_limits<int64_t>::max() || ping_usec == 0) ? QObject::tr("N/A") : QString(QObject::tr("%1 ms")).arg(QString::number((int)(ping_usec / 1000), 10)); + return (ping_time == std::chrono::microseconds::max() || ping_time == 0us) ? + QObject::tr("N/A") : + QObject::tr("%1 ms").arg(QString::number((int)(count_microseconds(ping_time) / 1000), 10)); } QString formatTimeOffset(int64_t nTimeOffset) { - return QString(QObject::tr("%1 s")).arg(QString::number((int)nTimeOffset, 10)); + return QObject::tr("%1 s").arg(QString::number((int)nTimeOffset, 10)); } QString formatNiceTimeOffset(qint64 secs) @@ -755,14 +759,14 @@ QString formatNiceTimeOffset(qint64 secs) QString formatBytes(uint64_t bytes) { - if(bytes < 1024) - return QString(QObject::tr("%1 B")).arg(bytes); - if(bytes < 1024 * 1024) - return QString(QObject::tr("%1 KB")).arg(bytes / 1024); - if(bytes < 1024 * 1024 * 1024) - return QString(QObject::tr("%1 MB")).arg(bytes / 1024 / 1024); + if (bytes < 1'000) + return QObject::tr("%1 B").arg(bytes); + 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 QString(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/guiutil.h b/src/qt/guiutil.h index 7984aa1141..6395ec6abd 100644 --- a/src/qt/guiutil.h +++ b/src/qt/guiutil.h @@ -20,6 +20,8 @@ #include <QString> #include <QTableView> +#include <chrono> + class QValidatedLineEdit; class SendCoinsRecipient; @@ -202,8 +204,8 @@ namespace GUIUtil /** Format CNodeStats.nServices bitmask into a user-readable string */ QString formatServicesStr(quint64 mask); - /** Format a CNodeStats.m_ping_usec into a user-readable string or display N/A, if 0 */ - QString formatPingTime(int64_t ping_usec); + /** Format a CNodeStats.m_last_ping_time into a user-readable string or display N/A, if 0 */ + QString formatPingTime(std::chrono::microseconds ping_time); /** Format a CNodeCombinedStats.nTimeOffset into a user-readable string */ QString formatTimeOffset(int64_t nTimeOffset); diff --git a/src/qt/optionsdialog.h b/src/qt/optionsdialog.h index 1cc96035c6..ba35ff3b67 100644 --- a/src/qt/optionsdialog.h +++ b/src/qt/optionsdialog.h @@ -67,7 +67,7 @@ private Q_SLOTS: void updateDefaultProxyNets(); Q_SIGNALS: - void proxyIpChecks(QValidatedLineEdit *pUiProxyIp, int nProxyPort); + void proxyIpChecks(QValidatedLineEdit *pUiProxyIp, uint16_t nProxyPort); private: Ui::OptionsDialog *ui; 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/peertablemodel.cpp b/src/qt/peertablemodel.cpp index 5f518a67cd..3459bf4cf8 100644 --- a/src/qt/peertablemodel.cpp +++ b/src/qt/peertablemodel.cpp @@ -23,8 +23,7 @@ bool NodeLessThan::operator()(const CNodeCombinedStats &left, const CNodeCombine if (order == Qt::DescendingOrder) std::swap(pLeft, pRight); - switch(column) - { + switch (static_cast<PeerTableModel::ColumnIndex>(column)) { case PeerTableModel::NetNodeId: return pLeft->nodeid < pRight->nodeid; case PeerTableModel::Address: @@ -34,16 +33,15 @@ bool NodeLessThan::operator()(const CNodeCombinedStats &left, const CNodeCombine case PeerTableModel::Network: return pLeft->m_network < pRight->m_network; case PeerTableModel::Ping: - return pLeft->m_min_ping_usec < pRight->m_min_ping_usec; + return pLeft->m_min_ping_time < pRight->m_min_ping_time; case PeerTableModel::Sent: return pLeft->nSendBytes < pRight->nSendBytes; case PeerTableModel::Received: return pLeft->nRecvBytes < pRight->nRecvBytes; case PeerTableModel::Subversion: return pLeft->cleanSubVer.compare(pRight->cleanSubVer) < 0; - } - - return false; + } // no default case, so the compiler can warn about missing cases + assert(false); } // private implementation @@ -157,9 +155,9 @@ QVariant PeerTableModel::data(const QModelIndex &index, int role) const CNodeCombinedStats *rec = static_cast<CNodeCombinedStats*>(index.internalPointer()); + const auto column = static_cast<ColumnIndex>(index.column()); if (role == Qt::DisplayRole) { - switch(index.column()) - { + switch (column) { case NetNodeId: return (qint64)rec->nodeStats.nodeid; case Address: @@ -170,26 +168,31 @@ QVariant PeerTableModel::data(const QModelIndex &index, int role) const case Network: return GUIUtil::NetworkToQString(rec->nodeStats.m_network); case Ping: - return GUIUtil::formatPingTime(rec->nodeStats.m_min_ping_usec); + return GUIUtil::formatPingTime(rec->nodeStats.m_min_ping_time); case Sent: return GUIUtil::formatBytes(rec->nodeStats.nSendBytes); case Received: return GUIUtil::formatBytes(rec->nodeStats.nRecvBytes); case Subversion: return QString::fromStdString(rec->nodeStats.cleanSubVer); - } + } // no default case, so the compiler can warn about missing cases + assert(false); } else if (role == Qt::TextAlignmentRole) { - switch (index.column()) { - case ConnectionType: - case Network: - return QVariant(Qt::AlignCenter); - case Ping: - case Sent: - case Received: - return QVariant(Qt::AlignRight | Qt::AlignVCenter); - default: - return QVariant(); - } + switch (column) { + case NetNodeId: + case Address: + return {}; + case ConnectionType: + case Network: + return QVariant(Qt::AlignCenter); + case Ping: + case Sent: + case Received: + return QVariant(Qt::AlignRight | Qt::AlignVCenter); + case Subversion: + return {}; + } // no default case, so the compiler can warn about missing cases + assert(false); } else if (role == StatsRole) { switch (index.column()) { case NetNodeId: return QVariant::fromValue(rec); diff --git a/src/qt/platformstyle.cpp b/src/qt/platformstyle.cpp index aab8d8e4af..1d0605c903 100644 --- a/src/qt/platformstyle.cpp +++ b/src/qt/platformstyle.cpp @@ -18,7 +18,7 @@ static const struct { /** Extra padding/spacing in transactionview */ const bool useExtraSpacing; } platform_styles[] = { - {"macosx", false, false, true}, + {"macosx", false, true, true}, {"windows", true, false, false}, /* Other: linux, unix, ... */ {"other", true, true, false} diff --git a/src/qt/psbtoperationsdialog.cpp b/src/qt/psbtoperationsdialog.cpp index 55ab6046cf..17746b395b 100644 --- a/src/qt/psbtoperationsdialog.cpp +++ b/src/qt/psbtoperationsdialog.cpp @@ -141,11 +141,11 @@ void PSBTOperationsDialog::saveTransaction() { filename_suggestion.append(".psbt"); QString filename = GUIUtil::getSaveFileName(this, tr("Save Transaction Data"), filename_suggestion, - tr("Partially Signed Transaction (Binary) (*.psbt)"), &selected_filter); + tr("Partially Signed Transaction (Binary)", "Name of binary PSBT file format") + QLatin1String(" (*.psbt)"), &selected_filter); if (filename.isEmpty()) { return; } - std::ofstream out(filename.toLocal8Bit().data()); + std::ofstream out(filename.toLocal8Bit().data(), std::ofstream::out | std::ofstream::binary); out << ssTx.str(); out.close(); showStatus(tr("PSBT saved to disk."), StatusLevel::INFO); diff --git a/src/qt/qrimagewidget.cpp b/src/qt/qrimagewidget.cpp index 490826cbbb..a71c8831e9 100644 --- a/src/qt/qrimagewidget.cpp +++ b/src/qt/qrimagewidget.cpp @@ -120,7 +120,9 @@ void QRImageWidget::saveImage() { if (!GUIUtil::HasPixmap(this)) return; - QString fn = GUIUtil::getSaveFileName(this, tr("Save QR Code"), QString(), tr("PNG Image (*.png)"), nullptr); + QString fn = GUIUtil::getSaveFileName( + this, tr("Save QR Code"), QString(), + tr("PNG Image", "Name of PNG file format") + QLatin1String(" (*.png)"), nullptr); if (!fn.isEmpty()) { exportImage().save(fn); diff --git a/src/qt/receivecoinsdialog.cpp b/src/qt/receivecoinsdialog.cpp index 0da12c84fd..61cb89d75a 100644 --- a/src/qt/receivecoinsdialog.cpp +++ b/src/qt/receivecoinsdialog.cpp @@ -81,7 +81,6 @@ ReceiveCoinsDialog::ReceiveCoinsDialog(const PlatformStyle *_platformStyle, QWid tableView->horizontalHeader()->setMinimumSectionSize(MINIMUM_COLUMN_WIDTH); tableView->horizontalHeader()->setStretchLastSection(true); } - tableView->horizontalHeader()->setSortIndicator(RecentRequestsTableModel::Date, Qt::DescendingOrder); } void ReceiveCoinsDialog::setModel(WalletModel *_model) @@ -96,6 +95,8 @@ void ReceiveCoinsDialog::setModel(WalletModel *_model) QTableView* tableView = ui->recentRequestsView; tableView->setModel(_model->getRecentRequestsTableModel()); + tableView->sortByColumn(RecentRequestsTableModel::Date, Qt::DescendingOrder); + connect(tableView->selectionModel(), &QItemSelectionModel::selectionChanged, this, &ReceiveCoinsDialog::recentRequestsView_selectionChanged); diff --git a/src/qt/rpcconsole.cpp b/src/qt/rpcconsole.cpp index 5acf8b1cf0..34d055e5a5 100644 --- a/src/qt/rpcconsole.cpp +++ b/src/qt/rpcconsole.cpp @@ -474,9 +474,9 @@ RPCConsole::RPCConsole(interfaces::Node& node, const PlatformStyle *_platformSty const QString list{"<ul><li>" + Join(CONNECTION_TYPE_DOC, QString("</li><li>")) + "</li></ul>"}; ui->peerConnectionTypeLabel->setToolTip(ui->peerConnectionTypeLabel->toolTip().arg(list)); const QString hb_list{"<ul><li>\"" - + tr("To") + "\" – " + tr("we selected the peer for high bandwidth relay") + "</li><li>\"" - + tr("From") + "\" – " + tr("the peer selected us for high bandwidth relay") + "</li><li>\"" - + tr("No") + "\" – " + tr("no high bandwidth relay selected") + "</li></ul>"}; + + ts.to + "\" – " + tr("we selected the peer for high bandwidth relay") + "</li><li>\"" + + ts.from + "\" – " + tr("the peer selected us for high bandwidth relay") + "</li><li>\"" + + ts.no + "\" – " + tr("no high bandwidth relay selected") + "</li></ul>"}; ui->peerHighBandwidthLabel->setToolTip(ui->peerHighBandwidthLabel->toolTip().arg(hb_list)); ui->dataDir->setToolTip(ui->dataDir->toolTip().arg(QString(nonbreaking_hyphen) + "datadir")); ui->blocksDir->setToolTip(ui->blocksDir->toolTip().arg(QString(nonbreaking_hyphen) + "blocksdir")); @@ -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); @@ -619,10 +618,10 @@ void RPCConsole::setClientModel(ClientModel *model, int bestblock_height, int64_ // create peer table context menu actions QAction* disconnectAction = new QAction(tr("&Disconnect"), this); - QAction* banAction1h = new QAction(tr("Ban for") + " " + tr("1 &hour"), this); - QAction* banAction24h = new QAction(tr("Ban for") + " " + tr("1 &day"), this); - QAction* banAction7d = new QAction(tr("Ban for") + " " + tr("1 &week"), this); - QAction* banAction365d = new QAction(tr("Ban for") + " " + tr("1 &year"), this); + QAction* banAction1h = new QAction(ts.ban_for + " " + tr("1 &hour"), this); + QAction* banAction24h = new QAction(ts.ban_for + " " + tr("1 &day"), this); + QAction* banAction7d = new QAction(ts.ban_for + " " + tr("1 &week"), this); + QAction* banAction365d = new QAction(ts.ban_for + " " + tr("1 &year"), this); // create peer table context menu peersTableContextMenu = new QMenu(this); @@ -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); @@ -1114,11 +1112,11 @@ void RPCConsole::updateDetailWidget() peerAddrDetails += "<br />" + tr("via %1").arg(QString::fromStdString(stats->nodeStats.addrLocal)); ui->peerHeading->setText(peerAddrDetails); ui->peerServices->setText(GUIUtil::formatServicesStr(stats->nodeStats.nServices)); - ui->peerRelayTxes->setText(stats->nodeStats.fRelayTxes ? "Yes" : "No"); + ui->peerRelayTxes->setText(stats->nodeStats.fRelayTxes ? ts.yes : ts.no); QString bip152_hb_settings; - if (stats->nodeStats.m_bip152_highbandwidth_to) bip152_hb_settings += "To"; - if (stats->nodeStats.m_bip152_highbandwidth_from) bip152_hb_settings += (bip152_hb_settings == "" ? "From" : "/From"); - if (bip152_hb_settings == "") bip152_hb_settings = "No"; + if (stats->nodeStats.m_bip152_highbandwidth_to) bip152_hb_settings = ts.to; + if (stats->nodeStats.m_bip152_highbandwidth_from) bip152_hb_settings += (bip152_hb_settings.isEmpty() ? ts.from : QLatin1Char('/') + ts.from); + if (bip152_hb_settings.isEmpty()) bip152_hb_settings = ts.no; ui->peerHighBandwidth->setText(bip152_hb_settings); const int64_t time_now{GetSystemTimeInSeconds()}; ui->peerConnTime->setText(GUIUtil::formatDurationStr(time_now - stats->nodeStats.nTimeConnected)); @@ -1128,15 +1126,15 @@ void RPCConsole::updateDetailWidget() ui->peerLastRecv->setText(TimeDurationField(time_now, stats->nodeStats.nLastRecv)); ui->peerBytesSent->setText(GUIUtil::formatBytes(stats->nodeStats.nSendBytes)); ui->peerBytesRecv->setText(GUIUtil::formatBytes(stats->nodeStats.nRecvBytes)); - ui->peerPingTime->setText(GUIUtil::formatPingTime(stats->nodeStats.m_ping_usec)); - ui->peerMinPing->setText(GUIUtil::formatPingTime(stats->nodeStats.m_min_ping_usec)); + ui->peerPingTime->setText(GUIUtil::formatPingTime(stats->nodeStats.m_last_ping_time)); + ui->peerMinPing->setText(GUIUtil::formatPingTime(stats->nodeStats.m_min_ping_time)); ui->timeoffset->setText(GUIUtil::formatTimeOffset(stats->nodeStats.nTimeOffset)); ui->peerVersion->setText(QString::number(stats->nodeStats.nVersion)); ui->peerSubversion->setText(QString::fromStdString(stats->nodeStats.cleanSubVer)); ui->peerConnectionType->setText(GUIUtil::ConnectionTypeToQString(stats->nodeStats.m_conn_type, /* prepend_direction */ true)); ui->peerNetwork->setText(GUIUtil::NetworkToQString(stats->nodeStats.m_network)); if (stats->nodeStats.m_permissionFlags == PF_NONE) { - ui->peerPermissions->setText(tr("N/A")); + ui->peerPermissions->setText(ts.na); } else { QStringList permissions; for (const auto& permission : NetPermissions::ToStrings(stats->nodeStats.m_permissionFlags)) { @@ -1144,25 +1142,25 @@ void RPCConsole::updateDetailWidget() } ui->peerPermissions->setText(permissions.join(" & ")); } - ui->peerMappedAS->setText(stats->nodeStats.m_mapped_as != 0 ? QString::number(stats->nodeStats.m_mapped_as) : tr("N/A")); + ui->peerMappedAS->setText(stats->nodeStats.m_mapped_as != 0 ? QString::number(stats->nodeStats.m_mapped_as) : ts.na); // This check fails for example if the lock was busy and // nodeStateStats couldn't be fetched. if (stats->fNodeStateStatsAvailable) { // Sync height is init to -1 - if (stats->nodeStateStats.nSyncHeight > -1) + if (stats->nodeStateStats.nSyncHeight > -1) { ui->peerSyncHeight->setText(QString("%1").arg(stats->nodeStateStats.nSyncHeight)); - else - ui->peerSyncHeight->setText(tr("Unknown")); - + } else { + ui->peerSyncHeight->setText(ts.unknown); + } // Common height is init to -1 - if (stats->nodeStateStats.nCommonHeight > -1) + if (stats->nodeStateStats.nCommonHeight > -1) { ui->peerCommonHeight->setText(QString("%1").arg(stats->nodeStateStats.nCommonHeight)); - else - ui->peerCommonHeight->setText(tr("Unknown")); - + } else { + ui->peerCommonHeight->setText(ts.unknown); + } ui->peerHeight->setText(QString::number(stats->nodeStateStats.m_starting_height)); - ui->peerPingWait->setText(GUIUtil::formatPingTime(stats->nodeStateStats.m_ping_wait_usec)); + ui->peerPingWait->setText(GUIUtil::formatPingTime(stats->nodeStateStats.m_ping_wait)); } ui->peersTabRightPanel->show(); diff --git a/src/qt/rpcconsole.h b/src/qt/rpcconsole.h index 27d4c42eb4..b9806e40c9 100644 --- a/src/qt/rpcconsole.h +++ b/src/qt/rpcconsole.h @@ -136,6 +136,11 @@ Q_SIGNALS: void cmdRequest(const QString &command, const WalletModel* wallet_model); private: + struct TranslatedStrings { + const QString yes{tr("Yes")}, no{tr("No")}, to{tr("To")}, from{tr("From")}, + ban_for{tr("Ban for")}, na{tr("N/A")}, unknown{tr("Unknown")}; + } const ts; + void startExecutor(); void setTrafficGraphRange(int mins); diff --git a/src/qt/sendcoinsdialog.cpp b/src/qt/sendcoinsdialog.cpp index 611f3584c3..95e1ce2210 100644 --- a/src/qt/sendcoinsdialog.cpp +++ b/src/qt/sendcoinsdialog.cpp @@ -266,7 +266,7 @@ bool SendCoinsDialog::PrepareSendText(QString& question_string, QString& informa } // prepare transaction for getting txFee earlier - m_current_transaction = MakeUnique<WalletModelTransaction>(recipients); + m_current_transaction = std::make_unique<WalletModelTransaction>(recipients); WalletModel::SendCoinsReturn prepareStatus; updateCoinControlState(*m_coin_control); @@ -430,11 +430,11 @@ void SendCoinsDialog::on_sendButton_clicked() fileNameSuggestion.append(".psbt"); QString filename = GUIUtil::getSaveFileName(this, tr("Save Transaction Data"), fileNameSuggestion, - tr("Partially Signed Transaction (Binary) (*.psbt)"), &selectedFilter); + tr("Partially Signed Transaction (Binary)", "Name of binary PSBT file format") + QLatin1String(" (*.psbt)"), &selectedFilter); if (filename.isEmpty()) { return; } - std::ofstream out(filename.toLocal8Bit().data()); + std::ofstream out(filename.toLocal8Bit().data(), std::ofstream::out | std::ofstream::binary); out << ssTx.str(); out.close(); Q_EMIT message(tr("PSBT saved"), "PSBT saved to disk", CClientUIInterface::MSG_INFORMATION); diff --git a/src/qt/test/addressbooktests.cpp b/src/qt/test/addressbooktests.cpp index 35fcb2b0ca..a026069232 100644 --- a/src/qt/test/addressbooktests.cpp +++ b/src/qt/test/addressbooktests.cpp @@ -112,7 +112,7 @@ void TestAddAddressesToSendBook(interfaces::Node& node) ClientModel clientModel(node, &optionsModel); AddWallet(wallet); WalletModel walletModel(interfaces::MakeWallet(wallet), clientModel, platformStyle.get()); - RemoveWallet(wallet, nullopt); + RemoveWallet(wallet, std::nullopt); EditAddressDialog editAddressDialog(EditAddressDialog::NewSendingAddress); editAddressDialog.setModel(walletModel.getAddressTableModel()); diff --git a/src/qt/test/compattests.cpp b/src/qt/test/compattests.cpp deleted file mode 100644 index c76dee5091..0000000000 --- a/src/qt/test/compattests.cpp +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright (c) 2016-2019 The Bitcoin Core developers -// Distributed under the MIT software license, see the accompanying -// file COPYING or http://www.opensource.org/licenses/mit-license.php. - -#if defined(HAVE_CONFIG_H) -#include <config/bitcoin-config.h> -#endif - -#include <qt/test/compattests.h> - -#include <compat/byteswap.h> - -void CompatTests::bswapTests() -{ - // Sibling in bitcoin/src/test/bswap_tests.cpp - uint16_t u1 = 0x1234; - uint32_t u2 = 0x56789abc; - uint64_t u3 = 0xdef0123456789abc; - uint16_t e1 = 0x3412; - uint32_t e2 = 0xbc9a7856; - uint64_t e3 = 0xbc9a78563412f0de; - QVERIFY(bswap_16(u1) == e1); - QVERIFY(bswap_32(u2) == e2); - QVERIFY(bswap_64(u3) == e3); -} diff --git a/src/qt/test/compattests.h b/src/qt/test/compattests.h deleted file mode 100644 index 1af97696b2..0000000000 --- a/src/qt/test/compattests.h +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) 2009-2016 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_QT_TEST_COMPATTESTS_H -#define BITCOIN_QT_TEST_COMPATTESTS_H - -#include <QObject> -#include <QTest> - -class CompatTests : public QObject -{ - Q_OBJECT - -private Q_SLOTS: - void bswapTests(); -}; - -#endif // BITCOIN_QT_TEST_COMPATTESTS_H diff --git a/src/qt/test/test_main.cpp b/src/qt/test/test_main.cpp index 9ef5fe8fc7..eb86f027ef 100644 --- a/src/qt/test/test_main.cpp +++ b/src/qt/test/test_main.cpp @@ -11,7 +11,6 @@ #include <qt/test/apptests.h> #include <qt/test/rpcnestedtests.h> #include <qt/test/uritests.h> -#include <qt/test/compattests.h> #include <test/util/setup_common.h> #ifdef ENABLE_WALLET @@ -54,6 +53,13 @@ int main(int argc, char* argv[]) NodeContext node_context; std::unique_ptr<interfaces::Node> node = interfaces::MakeNode(&node_context); + gArgs.ForceSetArg("-listen", "0"); + gArgs.ForceSetArg("-listenonion", "0"); + gArgs.ForceSetArg("-discover", "0"); + gArgs.ForceSetArg("-dnsseed", "0"); + gArgs.ForceSetArg("-fixedseeds", "0"); + gArgs.ForceSetArg("-upnp", "0"); + gArgs.ForceSetArg("-natpmp", "0"); bool fInvalid = false; @@ -85,10 +91,6 @@ int main(int argc, char* argv[]) if (QTest::qExec(&test3) != 0) { fInvalid = true; } - CompatTests test4; - if (QTest::qExec(&test4) != 0) { - fInvalid = true; - } #ifdef ENABLE_WALLET WalletTests test5(app.node()); if (QTest::qExec(&test5) != 0) { diff --git a/src/qt/test/wallettests.cpp b/src/qt/test/wallettests.cpp index d6d2d0e3df..1107c44dc9 100644 --- a/src/qt/test/wallettests.cpp +++ b/src/qt/test/wallettests.cpp @@ -167,7 +167,7 @@ void TestGUI(interfaces::Node& node) ClientModel clientModel(node, &optionsModel); AddWallet(wallet); WalletModel walletModel(interfaces::MakeWallet(wallet), clientModel, platformStyle.get()); - RemoveWallet(wallet, nullopt); + RemoveWallet(wallet, std::nullopt); sendCoinsDialog.setModel(&walletModel); transactionView.setModel(&walletModel); 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/qt/transactiontablemodel.cpp b/src/qt/transactiontablemodel.cpp index e6d483364c..a7556eed04 100644 --- a/src/qt/transactiontablemodel.cpp +++ b/src/qt/transactiontablemodel.cpp @@ -526,27 +526,30 @@ QVariant TransactionTableModel::data(const QModelIndex &index, int role) const return QVariant(); TransactionRecord *rec = static_cast<TransactionRecord*>(index.internalPointer()); - switch(role) - { + const auto column = static_cast<ColumnIndex>(index.column()); + switch (role) { case RawDecorationRole: - switch(index.column()) - { + switch (column) { case Status: return txStatusDecoration(rec); case Watchonly: return txWatchonlyDecoration(rec); + case Date: return {}; + case Type: return {}; case ToAddress: return txAddressDecoration(rec); - } - break; + case Amount: return {}; + } // no default case, so the compiler can warn about missing cases + assert(false); case Qt::DecorationRole: { QIcon icon = qvariant_cast<QIcon>(index.data(RawDecorationRole)); return platformStyle->TextColorIcon(icon); } case Qt::DisplayRole: - switch(index.column()) - { + switch (column) { + case Status: return {}; + case Watchonly: return {}; case Date: return formatTxDate(rec); case Type: @@ -555,12 +558,11 @@ QVariant TransactionTableModel::data(const QModelIndex &index, int role) const return formatTxToAddress(rec, false); case Amount: return formatTxAmount(rec, true, BitcoinUnits::SeparatorStyle::ALWAYS); - } - break; + } // no default case, so the compiler can warn about missing cases + assert(false); case Qt::EditRole: // Edit role is used for sorting, so return the unformatted values - switch(index.column()) - { + switch (column) { case Status: return QString::fromStdString(rec->status.sortKey); case Date: @@ -573,8 +575,8 @@ QVariant TransactionTableModel::data(const QModelIndex &index, int role) const return formatTxToAddress(rec, true); case Amount: return qint64(rec->credit + rec->debit); - } - break; + } // no default case, so the compiler can warn about missing cases + assert(false); case Qt::ToolTipRole: return formatTooltip(rec); case Qt::TextAlignmentRole: diff --git a/src/qt/transactionview.cpp b/src/qt/transactionview.cpp index e1ec7b6ed0..42e08c6af7 100644 --- a/src/qt/transactionview.cpp +++ b/src/qt/transactionview.cpp @@ -160,7 +160,6 @@ TransactionView::TransactionView(const PlatformStyle *platformStyle, QWidget *pa transactionView->horizontalHeader()->setMinimumSectionSize(MINIMUM_COLUMN_WIDTH); transactionView->horizontalHeader()->setStretchLastSection(true); } - transactionView->horizontalHeader()->setSortIndicator(TransactionTableModel::Date, Qt::DescendingOrder); // Actions abandonAction = new QAction(tr("Abandon transaction"), this); @@ -236,6 +235,7 @@ void TransactionView::setModel(WalletModel *_model) transactionProxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive); transactionProxyModel->setSortRole(Qt::EditRole); transactionView->setModel(transactionProxyModel); + transactionView->sortByColumn(TransactionTableModel::Date, Qt::DescendingOrder); if (_model->getOptionsModel()) { @@ -357,7 +357,7 @@ void TransactionView::exportClicked() // CSV is currently the only supported format QString filename = GUIUtil::getSaveFileName(this, tr("Export Transaction History"), QString(), - tr("Comma separated file (*.csv)"), nullptr); + tr("Comma separated file", "Name of CSV file format") + QLatin1String(" (*.csv)"), nullptr); if (filename.isNull()) return; diff --git a/src/qt/walletview.cpp b/src/qt/walletview.cpp index b1e6b43e60..8612893683 100644 --- a/src/qt/walletview.cpp +++ b/src/qt/walletview.cpp @@ -273,7 +273,7 @@ void WalletView::backupWallet() { QString filename = GUIUtil::getSaveFileName(this, tr("Backup Wallet"), QString(), - tr("Wallet Data (*.dat)"), nullptr); + tr("Wallet Data", "Name of wallet data file format") + QLatin1String(" (*.dat)"), nullptr); if (filename.isEmpty()) return; diff --git a/src/randomenv.cpp b/src/randomenv.cpp index 9248db1539..fa2a3a0607 100644 --- a/src/randomenv.cpp +++ b/src/randomenv.cpp @@ -38,7 +38,7 @@ #include <sys/utsname.h> #include <unistd.h> #endif -#if HAVE_DECL_GETIFADDRS +#if HAVE_DECL_GETIFADDRS && HAVE_DECL_FREEIFADDRS #include <ifaddrs.h> #endif #if HAVE_SYSCTL @@ -361,7 +361,7 @@ void RandAddStaticEnv(CSHA512& hasher) hasher.Write((const unsigned char*)hname, strnlen(hname, 256)); } -#if HAVE_DECL_GETIFADDRS +#if HAVE_DECL_GETIFADDRS && HAVE_DECL_FREEIFADDRS // Network interfaces struct ifaddrs *ifad = NULL; getifaddrs(&ifad); diff --git a/src/rest.cpp b/src/rest.cpp index 71426a4dc4..aa97470ca7 100644 --- a/src/rest.cpp +++ b/src/rest.cpp @@ -18,10 +18,12 @@ #include <sync.h> #include <txmempool.h> #include <util/check.h> -#include <util/ref.h> +#include <util/system.h> #include <validation.h> #include <version.h> +#include <any> + #include <boost/algorithm/string.hpp> #include <univalue.h> @@ -73,10 +75,10 @@ static bool RESTERR(HTTPRequest* req, enum HTTPStatusCode status, std::string me * context is not found. * @returns Pointer to the node context or nullptr if not found. */ -static NodeContext* GetNodeContext(const util::Ref& context, HTTPRequest* req) +static NodeContext* GetNodeContext(const std::any& context, HTTPRequest* req) { - NodeContext* node = context.Has<NodeContext>() ? &context.Get<NodeContext>() : nullptr; - if (!node) { + auto node_context = util::AnyPtr<NodeContext>(context); + if (!node_context) { RESTERR(req, HTTP_INTERNAL_SERVER_ERROR, strprintf("%s:%d (%s)\n" "Internal bug detected: Node context not found!\n" @@ -84,7 +86,7 @@ static NodeContext* GetNodeContext(const util::Ref& context, HTTPRequest* req) __FILE__, __LINE__, __func__, PACKAGE_BUGREPORT)); return nullptr; } - return node; + return node_context; } /** @@ -94,14 +96,14 @@ static NodeContext* GetNodeContext(const util::Ref& context, HTTPRequest* req) * context mempool is not found. * @returns Pointer to the mempool or nullptr if no mempool found. */ -static CTxMemPool* GetMemPool(const util::Ref& context, HTTPRequest* req) +static CTxMemPool* GetMemPool(const std::any& context, HTTPRequest* req) { - NodeContext* node = context.Has<NodeContext>() ? &context.Get<NodeContext>() : nullptr; - if (!node || !node->mempool) { + auto node_context = util::AnyPtr<NodeContext>(context); + if (!node_context || !node_context->mempool) { RESTERR(req, HTTP_NOT_FOUND, "Mempool disabled or instance not found"); return nullptr; } - return node->mempool.get(); + return node_context->mempool.get(); } static RetFormat ParseDataFormat(std::string& param, const std::string& strReq) @@ -151,7 +153,7 @@ static bool CheckWarmup(HTTPRequest* req) return true; } -static bool rest_headers(const util::Ref& context, +static bool rest_headers(const std::any& context, HTTPRequest* req, const std::string& strURIPart) { @@ -293,12 +295,12 @@ static bool rest_block(HTTPRequest* req, } } -static bool rest_block_extended(const util::Ref& context, HTTPRequest* req, const std::string& strURIPart) +static bool rest_block_extended(const std::any& context, HTTPRequest* req, const std::string& strURIPart) { return rest_block(req, strURIPart, true); } -static bool rest_block_notxdetails(const util::Ref& context, HTTPRequest* req, const std::string& strURIPart) +static bool rest_block_notxdetails(const std::any& context, HTTPRequest* req, const std::string& strURIPart) { return rest_block(req, strURIPart, false); } @@ -306,7 +308,7 @@ static bool rest_block_notxdetails(const util::Ref& context, HTTPRequest* req, c // A bit of a hack - dependency on a function defined in rpc/blockchain.cpp RPCHelpMan getblockchaininfo(); -static bool rest_chaininfo(const util::Ref& context, HTTPRequest* req, const std::string& strURIPart) +static bool rest_chaininfo(const std::any& context, HTTPRequest* req, const std::string& strURIPart) { if (!CheckWarmup(req)) return false; @@ -329,7 +331,7 @@ static bool rest_chaininfo(const util::Ref& context, HTTPRequest* req, const std } } -static bool rest_mempool_info(const util::Ref& context, HTTPRequest* req, const std::string& strURIPart) +static bool rest_mempool_info(const std::any& context, HTTPRequest* req, const std::string& strURIPart) { if (!CheckWarmup(req)) return false; @@ -353,7 +355,7 @@ static bool rest_mempool_info(const util::Ref& context, HTTPRequest* req, const } } -static bool rest_mempool_contents(const util::Ref& context, HTTPRequest* req, const std::string& strURIPart) +static bool rest_mempool_contents(const std::any& context, HTTPRequest* req, const std::string& strURIPart) { if (!CheckWarmup(req)) return false; const CTxMemPool* mempool = GetMemPool(context, req); @@ -376,7 +378,7 @@ static bool rest_mempool_contents(const util::Ref& context, HTTPRequest* req, co } } -static bool rest_tx(const util::Ref& context, HTTPRequest* req, const std::string& strURIPart) +static bool rest_tx(const std::any& context, HTTPRequest* req, const std::string& strURIPart) { if (!CheckWarmup(req)) return false; @@ -435,7 +437,7 @@ static bool rest_tx(const util::Ref& context, HTTPRequest* req, const std::strin } } -static bool rest_getutxos(const util::Ref& context, HTTPRequest* req, const std::string& strURIPart) +static bool rest_getutxos(const std::any& context, HTTPRequest* req, const std::string& strURIPart) { if (!CheckWarmup(req)) return false; @@ -621,7 +623,7 @@ static bool rest_getutxos(const util::Ref& context, HTTPRequest* req, const std: } } -static bool rest_blockhash_by_height(const util::Ref& context, HTTPRequest* req, +static bool rest_blockhash_by_height(const std::any& context, HTTPRequest* req, const std::string& str_uri_part) { if (!CheckWarmup(req)) return false; @@ -669,7 +671,7 @@ static bool rest_blockhash_by_height(const util::Ref& context, HTTPRequest* req, static const struct { const char* prefix; - bool (*handler)(const util::Ref& context, HTTPRequest* req, const std::string& strReq); + bool (*handler)(const std::any& context, HTTPRequest* req, const std::string& strReq); } uri_prefixes[] = { {"/rest/tx/", rest_tx}, {"/rest/block/notxdetails/", rest_block_notxdetails}, @@ -682,7 +684,7 @@ static const struct { {"/rest/blockhashbyheight/", rest_blockhash_by_height}, }; -void StartREST(const util::Ref& context) +void StartREST(const std::any& context) { for (const auto& up : uri_prefixes) { auto handler = [&context, up](HTTPRequest* req, const std::string& prefix) { return up.handler(context, req, prefix); }; diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp index c71d2cbe49..e1501d7254 100644 --- a/src/rpc/blockchain.cpp +++ b/src/rpc/blockchain.cpp @@ -30,7 +30,6 @@ #include <txdb.h> #include <txmempool.h> #include <undo.h> -#include <util/ref.h> #include <util/strencodings.h> #include <util/system.h> #include <util/translation.h> @@ -56,15 +55,16 @@ static Mutex cs_blockchange; static std::condition_variable cond_blockchange; static CUpdatedBlock latestblock GUARDED_BY(cs_blockchange); -NodeContext& EnsureNodeContext(const util::Ref& context) +NodeContext& EnsureNodeContext(const std::any& context) { - if (!context.Has<NodeContext>()) { + auto node_context = util::AnyPtr<NodeContext>(context); + if (!node_context) { throw JSONRPCError(RPC_INTERNAL_ERROR, "Node context not found"); } - return context.Get<NodeContext>(); + return *node_context; } -CTxMemPool& EnsureMemPool(const util::Ref& context) +CTxMemPool& EnsureMemPool(const std::any& context) { const NodeContext& node = EnsureNodeContext(context); if (!node.mempool) { @@ -73,7 +73,7 @@ CTxMemPool& EnsureMemPool(const util::Ref& context) return *node.mempool; } -ChainstateManager& EnsureChainman(const util::Ref& context) +ChainstateManager& EnsureChainman(const std::any& context) { const NodeContext& node = EnsureNodeContext(context); if (!node.chainman) { @@ -82,7 +82,7 @@ ChainstateManager& EnsureChainman(const util::Ref& context) return *node.chainman; } -CBlockPolicyEstimator& EnsureFeeEstimator(const util::Ref& context) +CBlockPolicyEstimator& EnsureFeeEstimator(const std::any& context) { NodeContext& node = EnsureNodeContext(context); if (!node.fee_estimator) { @@ -1015,7 +1015,7 @@ static RPCHelpMan pruneblockchain() height = chainHeight - MIN_BLOCKS_TO_KEEP; } - PruneBlockFilesManual(height); + PruneBlockFilesManual(::ChainstateActive(), height); const CBlockIndex* block = ::ChainActive().Tip(); CHECK_NONFATAL(block); while (block->pprev && (block->pprev->nStatus & BLOCK_HAVE_DATA)) { @@ -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", "") @@ -1073,9 +1073,9 @@ static RPCHelpMan gettxoutsetinfo() const CoinStatsHashType hash_type{request.params[0].isNull() ? CoinStatsHashType::HASH_SERIALIZED : ParseHashType(request.params[0].get_str())}; - CCoinsView* coins_view = WITH_LOCK(cs_main, return &ChainstateActive().CoinsDB()); + CCoinsView* coins_view = WITH_LOCK(::cs_main, return &::ChainstateActive().CoinsDB()); NodeContext& node = EnsureNodeContext(request.context); - if (GetUTXOStats(coins_view, stats, hash_type, node.rpc_interruption_point)) { + if (GetUTXOStats(coins_view, WITH_LOCK(::cs_main, return std::ref(g_chainman.m_blockman)), stats, hash_type, node.rpc_interruption_point)) { ret.pushKV("height", (int64_t)stats.nHeight); ret.pushKV("bestblock", stats.hashBlock.GetHex()); ret.pushKV("transactions", (int64_t)stats.nTransactions); @@ -1100,30 +1100,31 @@ static RPCHelpMan gettxoutsetinfo() static RPCHelpMan gettxout() { return RPCHelpMan{"gettxout", - "\nReturns details about an unspent transaction output.\n", - { - {"txid", RPCArg::Type::STR, RPCArg::Optional::NO, "The transaction id"}, - {"n", RPCArg::Type::NUM, RPCArg::Optional::NO, "vout number"}, - {"include_mempool", RPCArg::Type::BOOL, /* default */ "true", "Whether to include the mempool. Note that an unspent output that is spent in the mempool won't appear."}, - }, - RPCResult{ - RPCResult::Type::OBJ, "", "", - { - {RPCResult::Type::STR_HEX, "bestblock", "The hash of the block at the tip of the chain"}, - {RPCResult::Type::NUM, "confirmations", "The number of confirmations"}, - {RPCResult::Type::STR_AMOUNT, "value", "The transaction value in " + CURRENCY_UNIT}, - {RPCResult::Type::OBJ, "scriptPubKey", "", - { - {RPCResult::Type::STR_HEX, "asm", ""}, - {RPCResult::Type::STR_HEX, "hex", ""}, - {RPCResult::Type::NUM, "reqSigs", "Number of required signatures"}, - {RPCResult::Type::STR_HEX, "type", "The type, eg pubkeyhash"}, - {RPCResult::Type::ARR, "addresses", "array of bitcoin addresses", - {{RPCResult::Type::STR, "address", "bitcoin address"}}}, - }}, - {RPCResult::Type::BOOL, "coinbase", "Coinbase or not"}, - }}, - RPCExamples{ + "\nReturns details about an unspent transaction output.\n", + { + {"txid", RPCArg::Type::STR, RPCArg::Optional::NO, "The transaction id"}, + {"n", RPCArg::Type::NUM, RPCArg::Optional::NO, "vout number"}, + {"include_mempool", RPCArg::Type::BOOL, /* default */ "true", "Whether to include the mempool. Note that an unspent output that is spent in the mempool won't appear."}, + }, + { + RPCResult{"If the UTXO was not found", RPCResult::Type::NONE, "", ""}, + RPCResult{"Otherwise", RPCResult::Type::OBJ, "", "", { + {RPCResult::Type::STR_HEX, "bestblock", "The hash of the block at the tip of the chain"}, + {RPCResult::Type::NUM, "confirmations", "The number of confirmations"}, + {RPCResult::Type::STR_AMOUNT, "value", "The transaction value in " + CURRENCY_UNIT}, + {RPCResult::Type::OBJ, "scriptPubKey", "", { + {RPCResult::Type::STR, "asm", ""}, + {RPCResult::Type::STR_HEX, "hex", ""}, + {RPCResult::Type::NUM, "reqSigs", /* optional */ true, "(DEPRECATED, returned only if config option -deprecatedrpc=addresses is passed) Number of required signatures"}, + {RPCResult::Type::STR, "type", "The type, eg pubkeyhash"}, + {RPCResult::Type::STR, "address", /* optional */ true, "bitcoin address (only if a well-defined address exists)"}, + {RPCResult::Type::ARR, "addresses", /* optional */ true, "(DEPRECATED, returned only if config option -deprecatedrpc=addresses is passed) Array of bitcoin addresses", + {{RPCResult::Type::STR, "address", "bitcoin address"}}}, + }}, + {RPCResult::Type::BOOL, "coinbase", "Coinbase or not"}, + }}, + }, + RPCExamples{ "\nGet unspent transactions\n" + HelpExampleCli("listunspent", "") + "\nView the details\n" @@ -1200,7 +1201,7 @@ static RPCHelpMan verifychain() LOCK(cs_main); - return CVerifyDB().VerifyDB(Params(), &::ChainstateActive().CoinsTip(), check_level, check_depth); + return CVerifyDB().VerifyDB(Params(), ::ChainstateActive(), &::ChainstateActive().CoinsTip(), check_level, check_depth); }, }; } @@ -1232,7 +1233,7 @@ static void BIP9SoftForkDescPushBack(UniValue& softforks, const std::string &nam if (consensusParams.vDeployments[id].nTimeout <= 1230768000) return; UniValue bip9(UniValue::VOBJ); - const ThresholdState thresholdState = VersionBitsTipState(consensusParams, id); + const ThresholdState thresholdState = VersionBitsState(::ChainActive().Tip(), consensusParams, id, versionbitscache); switch (thresholdState) { case ThresholdState::DEFINED: bip9.pushKV("status", "defined"); break; case ThresholdState::STARTED: bip9.pushKV("status", "started"); break; @@ -1246,12 +1247,12 @@ static void BIP9SoftForkDescPushBack(UniValue& softforks, const std::string &nam } bip9.pushKV("start_time", consensusParams.vDeployments[id].nStartTime); bip9.pushKV("timeout", consensusParams.vDeployments[id].nTimeout); - int64_t since_height = VersionBitsTipStateSinceHeight(consensusParams, id); + int64_t since_height = VersionBitsStateSinceHeight(::ChainActive().Tip(), consensusParams, id, versionbitscache); bip9.pushKV("since", since_height); if (ThresholdState::STARTED == thresholdState) { UniValue statsUV(UniValue::VOBJ); - BIP9Stats statsStruct = VersionBitsTipStatistics(consensusParams, id); + BIP9Stats statsStruct = VersionBitsStatistics(::ChainActive().Tip(), consensusParams, id); statsUV.pushKV("period", statsStruct.period); statsUV.pushKV("threshold", statsStruct.threshold); statsUV.pushKV("elapsed", statsStruct.elapsed); @@ -1562,7 +1563,7 @@ static RPCHelpMan preciousblock() } BlockValidationState state; - PreciousBlock(state, Params(), pblockindex); + ::ChainstateActive().PreciousBlock(state, Params(), pblockindex); if (!state.IsValid()) { throw JSONRPCError(RPC_DATABASE_ERROR, state.ToString()); @@ -1598,7 +1599,7 @@ static RPCHelpMan invalidateblock() throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found"); } } - InvalidateBlock(state, Params(), pblockindex); + ::ChainstateActive().InvalidateBlock(state, Params(), pblockindex); if (state.IsValid()) { ::ChainstateActive().ActivateBestChain(state, Params()); @@ -1637,7 +1638,7 @@ static RPCHelpMan reconsiderblock() throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found"); } - ResetBlockFailureFlags(pblockindex); + ::ChainstateActive().ResetBlockFailureFlags(pblockindex); } BlockValidationState state; @@ -1668,9 +1669,9 @@ static RPCHelpMan getchaintxstats() {RPCResult::Type::STR_HEX, "window_final_block_hash", "The hash of the final block in the window"}, {RPCResult::Type::NUM, "window_final_block_height", "The height of the final block in the window."}, {RPCResult::Type::NUM, "window_block_count", "Size of the window in number of blocks"}, - {RPCResult::Type::NUM, "window_tx_count", "The number of transactions in the window. Only returned if \"window_block_count\" is > 0"}, - {RPCResult::Type::NUM, "window_interval", "The elapsed time in the window in seconds. Only returned if \"window_block_count\" is > 0"}, - {RPCResult::Type::NUM, "txrate", "The average rate of transactions per second in the window. Only returned if \"window_interval\" is > 0"}, + {RPCResult::Type::NUM, "window_tx_count", /* optional */ true, "The number of transactions in the window. Only returned if \"window_block_count\" is > 0"}, + {RPCResult::Type::NUM, "window_interval", /* optional */ true, "The elapsed time in the window in seconds. Only returned if \"window_block_count\" is > 0"}, + {RPCResult::Type::NUM, "txrate", /* optional */ true, "The average rate of transactions per second in the window. Only returned if \"window_interval\" is > 0"}, }}, RPCExamples{ HelpExampleCli("getchaintxstats", "") @@ -1776,6 +1777,16 @@ void CalculatePercentilesByWeight(CAmount result[NUM_GETBLOCKSTATS_PERCENTILES], } } +void ScriptPubKeyToUniv(const CScript& scriptPubKey, UniValue& out, bool fIncludeHex) +{ + ScriptPubKeyToUniv(scriptPubKey, out, fIncludeHex, IsDeprecatedRPCEnabled("addresses")); +} + +void TxToUniv(const CTransaction& tx, const uint256& hashBlock, UniValue& entry, bool include_hex, int serialize_flags, const CTxUndo* txundo) +{ + TxToUniv(tx, hashBlock, IsDeprecatedRPCEnabled("addresses"), entry, include_hex, serialize_flags, txundo); +} + template<typename T> static inline bool SetHasKeys(const std::set<T>& set) {return false;} template<typename T, typename Tk, typename... Args> @@ -2137,59 +2148,63 @@ public: static RPCHelpMan scantxoutset() { return RPCHelpMan{"scantxoutset", - "\nEXPERIMENTAL warning: this call may be removed or changed in future releases.\n" - "\nScans the unspent transaction output set for entries that match certain output descriptors.\n" - "Examples of output descriptors are:\n" - " addr(<address>) Outputs whose scriptPubKey corresponds to the specified address (does not include P2PK)\n" - " raw(<hex script>) Outputs whose scriptPubKey equals the specified hex scripts\n" - " combo(<pubkey>) P2PK, P2PKH, P2WPKH, and P2SH-P2WPKH outputs for the given pubkey\n" - " pkh(<pubkey>) P2PKH outputs for the given pubkey\n" - " sh(multi(<n>,<pubkey>,<pubkey>,...)) P2SH-multisig outputs for the given threshold and pubkeys\n" - "\nIn the above, <pubkey> either refers to a fixed public key in hexadecimal notation, or to an xpub/xprv optionally followed by one\n" - "or more path elements separated by \"/\", and optionally ending in \"/*\" (unhardened), or \"/*'\" or \"/*h\" (hardened) to specify all\n" - "unhardened or hardened child keys.\n" - "In the latter case, a range needs to be specified by below if different from 1000.\n" - "For more information on output descriptors, see the documentation in the doc/descriptors.md file.\n", + "\nScans the unspent transaction output set for entries that match certain output descriptors.\n" + "Examples of output descriptors are:\n" + " addr(<address>) Outputs whose scriptPubKey corresponds to the specified address (does not include P2PK)\n" + " raw(<hex script>) Outputs whose scriptPubKey equals the specified hex scripts\n" + " combo(<pubkey>) P2PK, P2PKH, P2WPKH, and P2SH-P2WPKH outputs for the given pubkey\n" + " pkh(<pubkey>) P2PKH outputs for the given pubkey\n" + " sh(multi(<n>,<pubkey>,<pubkey>,...)) P2SH-multisig outputs for the given threshold and pubkeys\n" + "\nIn the above, <pubkey> either refers to a fixed public key in hexadecimal notation, or to an xpub/xprv optionally followed by one\n" + "or more path elements separated by \"/\", and optionally ending in \"/*\" (unhardened), or \"/*'\" or \"/*h\" (hardened) to specify all\n" + "unhardened or hardened child keys.\n" + "In the latter case, a range needs to be specified by below if different from 1000.\n" + "For more information on output descriptors, see the documentation in the doc/descriptors.md file.\n", + { + {"action", RPCArg::Type::STR, RPCArg::Optional::NO, "The action to execute\n" + "\"start\" for starting a scan\n" + "\"abort\" for aborting the current scan (returns true when abort was successful)\n" + "\"status\" for progress report (in %) of the current scan"}, + {"scanobjects", RPCArg::Type::ARR, RPCArg::Optional::OMITTED, "Array of scan objects. Required for \"start\" action\n" + "Every scan object is either a string descriptor or an object:", + { + {"descriptor", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "An output descriptor"}, + {"", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED, "An object with output descriptor and metadata", { - {"action", RPCArg::Type::STR, RPCArg::Optional::NO, "The action to execute\n" - " \"start\" for starting a scan\n" - " \"abort\" for aborting the current scan (returns true when abort was successful)\n" - " \"status\" for progress report (in %) of the current scan"}, - {"scanobjects", RPCArg::Type::ARR, RPCArg::Optional::OMITTED, "Array of scan objects. Required for \"start\" action\n" - " Every scan object is either a string descriptor or an object:", - { - {"descriptor", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "An output descriptor"}, - {"", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED, "An object with output descriptor and metadata", - { - {"desc", RPCArg::Type::STR, RPCArg::Optional::NO, "An output descriptor"}, - {"range", RPCArg::Type::RANGE, /* default */ "1000", "The range of HD chain indexes to explore (either end or [begin,end])"}, - }, - }, - }, + {"desc", RPCArg::Type::STR, RPCArg::Optional::NO, "An output descriptor"}, + {"range", RPCArg::Type::RANGE, /* default */ "1000", "The range of HD chain indexes to explore (either end or [begin,end])"}, + }}, + }, "[scanobjects,...]"}, - }, - RPCResult{ - RPCResult::Type::OBJ, "", "", + }, + { + RPCResult{"When action=='abort'", RPCResult::Type::BOOL, "", ""}, + RPCResult{"When action=='status' and no scan is in progress", RPCResult::Type::NONE, "", ""}, + RPCResult{"When action=='status' and scan is in progress", RPCResult::Type::OBJ, "", "", + { + {RPCResult::Type::NUM, "progress", "The scan progress"}, + }}, + RPCResult{"When action=='start'", RPCResult::Type::OBJ, "", "", { + {RPCResult::Type::BOOL, "success", "Whether the scan was completed"}, + {RPCResult::Type::NUM, "txouts", "The number of unspent transaction outputs scanned"}, + {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::ARR, "unspents", "", + { + {RPCResult::Type::OBJ, "", "", { - {RPCResult::Type::BOOL, "success", "Whether the scan was completed"}, - {RPCResult::Type::NUM, "txouts", "The number of unspent transaction outputs scanned"}, - {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::ARR, "unspents", "", - { - {RPCResult::Type::OBJ, "", "", - { - {RPCResult::Type::STR_HEX, "txid", "The transaction id"}, - {RPCResult::Type::NUM, "vout", "The vout value"}, - {RPCResult::Type::STR_HEX, "scriptPubKey", "The script key"}, - {RPCResult::Type::STR, "desc", "A specialized descriptor for the matched scriptPubKey"}, - {RPCResult::Type::STR_AMOUNT, "amount", "The total amount in " + CURRENCY_UNIT + " of the unspent output"}, - {RPCResult::Type::NUM, "height", "Height of the unspent transaction output"}, - }}, - }}, - {RPCResult::Type::STR_AMOUNT, "total_amount", "The total amount of all found unspent outputs in " + CURRENCY_UNIT}, + {RPCResult::Type::STR_HEX, "txid", "The transaction id"}, + {RPCResult::Type::NUM, "vout", "The vout value"}, + {RPCResult::Type::STR_HEX, "scriptPubKey", "The script key"}, + {RPCResult::Type::STR, "desc", "A specialized descriptor for the matched scriptPubKey"}, + {RPCResult::Type::STR_AMOUNT, "amount", "The total amount in " + CURRENCY_UNIT + " of the unspent output"}, + {RPCResult::Type::NUM, "height", "Height of the unspent transaction output"}, }}, - RPCExamples{""}, + }}, + {RPCResult::Type::STR_AMOUNT, "total_amount", "The total amount of all found unspent outputs in " + CURRENCY_UNIT}, + }}, + }, + RPCExamples{""}, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VARR}); @@ -2444,7 +2459,7 @@ UniValue CreateUTXOSnapshot(NodeContext& node, CChainState& chainstate, CAutoFil chainstate.ForceFlushStateToDisk(); - if (!GetUTXOStats(&chainstate.CoinsDB(), stats, CoinStatsHashType::NONE, node.rpc_interruption_point)) { + if (!GetUTXOStats(&chainstate.CoinsDB(), chainstate.m_blockman, stats, CoinStatsHashType::NONE, node.rpc_interruption_point)) { throw JSONRPCError(RPC_INTERNAL_ERROR, "Unable to read UTXO set"); } diff --git a/src/rpc/blockchain.h b/src/rpc/blockchain.h index d8cae4dd24..cd04c9a10f 100644 --- a/src/rpc/blockchain.h +++ b/src/rpc/blockchain.h @@ -6,9 +6,11 @@ #define BITCOIN_RPC_BLOCKCHAIN_H #include <amount.h> +#include <core_io.h> #include <streams.h> #include <sync.h> +#include <any> #include <stdint.h> #include <vector> @@ -22,9 +24,6 @@ class CTxMemPool; class ChainstateManager; class UniValue; struct NodeContext; -namespace util { -class Ref; -} // namespace util static constexpr int NUM_GETBLOCKSTATS_PERCENTILES = 5; @@ -54,10 +53,13 @@ UniValue blockheaderToJSON(const CBlockIndex* tip, const CBlockIndex* blockindex /** Used by getblockstats to get feerates at different percentiles by weight */ void CalculatePercentilesByWeight(CAmount result[NUM_GETBLOCKSTATS_PERCENTILES], std::vector<std::pair<CAmount, int64_t>>& scores, int64_t total_weight); -NodeContext& EnsureNodeContext(const util::Ref& context); -CTxMemPool& EnsureMemPool(const util::Ref& context); -ChainstateManager& EnsureChainman(const util::Ref& context); -CBlockPolicyEstimator& EnsureFeeEstimator(const util::Ref& context); +void ScriptPubKeyToUniv(const CScript& scriptPubKey, UniValue& out, bool fIncludeHex); +void TxToUniv(const CTransaction& tx, const uint256& hashBlock, UniValue& entry, bool include_hex = true, int serialize_flags = 0, const CTxUndo* txundo = nullptr); + +NodeContext& EnsureNodeContext(const std::any& context); +CTxMemPool& EnsureMemPool(const std::any& context); +ChainstateManager& EnsureChainman(const std::any& context); +CBlockPolicyEstimator& EnsureFeeEstimator(const std::any& context); /** * Helper to create UTXO snapshots given a chainstate and a file handle. diff --git a/src/rpc/mining.cpp b/src/rpc/mining.cpp index 50987a735b..72ad0df199 100644 --- a/src/rpc/mining.cpp +++ b/src/rpc/mining.cpp @@ -150,7 +150,7 @@ static UniValue generateBlocks(ChainstateManager& chainman, const CTxMemPool& me UniValue blockHashes(UniValue::VARR); while (nHeight < nHeightEnd && !ShutdownRequested()) { - std::unique_ptr<CBlockTemplate> pblocktemplate(BlockAssembler(mempool, Params()).CreateNewBlock(coinbase_script)); + std::unique_ptr<CBlockTemplate> pblocktemplate(BlockAssembler(chainman.ActiveChainstate(), mempool, Params()).CreateNewBlock(coinbase_script)); if (!pblocktemplate.get()) throw JSONRPCError(RPC_INTERNAL_ERROR, "Couldn't create new block"); CBlock *pblock = &pblocktemplate->block; @@ -358,7 +358,7 @@ static RPCHelpMan generateblock() LOCK(cs_main); CTxMemPool empty_mempool; - std::unique_ptr<CBlockTemplate> blocktemplate(BlockAssembler(empty_mempool, chainparams).CreateNewBlock(coinbase_script)); + std::unique_ptr<CBlockTemplate> blocktemplate(BlockAssembler(::ChainstateActive(), empty_mempool, chainparams).CreateNewBlock(coinbase_script)); if (!blocktemplate) { throw JSONRPCError(RPC_INTERNAL_ERROR, "Couldn't create new block"); } @@ -369,7 +369,7 @@ static RPCHelpMan generateblock() // Add transactions block.vtx.insert(block.vtx.end(), txs.begin(), txs.end()); - RegenerateCommitments(block); + RegenerateCommitments(block, WITH_LOCK(::cs_main, return std::ref(g_chainman.m_blockman))); { LOCK(cs_main); @@ -505,83 +505,84 @@ static std::string gbt_vb_name(const Consensus::DeploymentPos pos) { static RPCHelpMan getblocktemplate() { return RPCHelpMan{"getblocktemplate", - "\nIf the request parameters include a 'mode' key, that is used to explicitly select between the default 'template' request or a 'proposal'.\n" - "It returns data needed to construct a block to work on.\n" - "For full specification, see BIPs 22, 23, 9, and 145:\n" - " https://github.com/bitcoin/bips/blob/master/bip-0022.mediawiki\n" - " https://github.com/bitcoin/bips/blob/master/bip-0023.mediawiki\n" - " https://github.com/bitcoin/bips/blob/master/bip-0009.mediawiki#getblocktemplate_changes\n" - " https://github.com/bitcoin/bips/blob/master/bip-0145.mediawiki\n", + "\nIf the request parameters include a 'mode' key, that is used to explicitly select between the default 'template' request or a 'proposal'.\n" + "It returns data needed to construct a block to work on.\n" + "For full specification, see BIPs 22, 23, 9, and 145:\n" + " https://github.com/bitcoin/bips/blob/master/bip-0022.mediawiki\n" + " https://github.com/bitcoin/bips/blob/master/bip-0023.mediawiki\n" + " https://github.com/bitcoin/bips/blob/master/bip-0009.mediawiki#getblocktemplate_changes\n" + " https://github.com/bitcoin/bips/blob/master/bip-0145.mediawiki\n", + { + {"template_request", RPCArg::Type::OBJ, "{}", "Format of the template", + { + {"mode", RPCArg::Type::STR, /* treat as named arg */ RPCArg::Optional::OMITTED_NAMED_ARG, "This must be set to \"template\", \"proposal\" (see BIP 23), or omitted"}, + {"capabilities", RPCArg::Type::ARR, /* treat as named arg */ RPCArg::Optional::OMITTED_NAMED_ARG, "A list of strings", { - {"template_request", RPCArg::Type::OBJ, "{}", "Format of the template", - { - {"mode", RPCArg::Type::STR, /* treat as named arg */ RPCArg::Optional::OMITTED_NAMED_ARG, "This must be set to \"template\", \"proposal\" (see BIP 23), or omitted"}, - {"capabilities", RPCArg::Type::ARR, /* treat as named arg */ RPCArg::Optional::OMITTED_NAMED_ARG, "A list of strings", - { - {"str", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "client side supported feature, 'longpoll', 'coinbasevalue', 'proposal', 'serverlist', 'workid'"}, - }, - }, - {"rules", RPCArg::Type::ARR, RPCArg::Optional::NO, "A list of strings", - { - {"segwit", RPCArg::Type::STR, RPCArg::Optional::NO, "(literal) indicates client side segwit support"}, - {"str", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "other client side supported softfork deployment"}, - }, - }, - }, + {"str", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "client side supported feature, 'longpoll', 'coinbasevalue', 'proposal', 'serverlist', 'workid'"}, + }}, + {"rules", RPCArg::Type::ARR, RPCArg::Optional::NO, "A list of strings", + { + {"segwit", RPCArg::Type::STR, RPCArg::Optional::NO, "(literal) indicates client side segwit support"}, + {"str", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "other client side supported softfork deployment"}, + }}, + }, "\"template_request\""}, - }, - RPCResult{ - RPCResult::Type::OBJ, "", "", + }, + { + RPCResult{"If the proposal was accepted with mode=='proposal'", RPCResult::Type::NONE, "", ""}, + RPCResult{"If the proposal was not accepted with mode=='proposal'", RPCResult::Type::STR, "", "According to BIP22"}, + RPCResult{"Otherwise", RPCResult::Type::OBJ, "", "", + { + {RPCResult::Type::NUM, "version", "The preferred block version"}, + {RPCResult::Type::ARR, "rules", "specific block rules that are to be enforced", + { + {RPCResult::Type::STR, "", "name of a rule the client must understand to some extent; see BIP 9 for format"}, + }}, + {RPCResult::Type::OBJ_DYN, "vbavailable", "set of pending, supported versionbit (BIP 9) softfork deployments", + { + {RPCResult::Type::NUM, "rulename", "identifies the bit number as indicating acceptance and readiness for the named softfork rule"}, + }}, + {RPCResult::Type::NUM, "vbrequired", "bit mask of versionbits the server requires set in submissions"}, + {RPCResult::Type::STR, "previousblockhash", "The hash of current highest block"}, + {RPCResult::Type::ARR, "transactions", "contents of non-coinbase transactions that should be included in the next block", + { + {RPCResult::Type::OBJ, "", "", { - {RPCResult::Type::NUM, "version", "The preferred block version"}, - {RPCResult::Type::ARR, "rules", "specific block rules that are to be enforced", - { - {RPCResult::Type::STR, "", "name of a rule the client must understand to some extent; see BIP 9 for format"}, - }}, - {RPCResult::Type::OBJ_DYN, "vbavailable", "set of pending, supported versionbit (BIP 9) softfork deployments", - { - {RPCResult::Type::NUM, "rulename", "identifies the bit number as indicating acceptance and readiness for the named softfork rule"}, - }}, - {RPCResult::Type::NUM, "vbrequired", "bit mask of versionbits the server requires set in submissions"}, - {RPCResult::Type::STR, "previousblockhash", "The hash of current highest block"}, - {RPCResult::Type::ARR, "transactions", "contents of non-coinbase transactions that should be included in the next block", - { - {RPCResult::Type::OBJ, "", "", - { - {RPCResult::Type::STR_HEX, "data", "transaction data encoded in hexadecimal (byte-for-byte)"}, - {RPCResult::Type::STR_HEX, "txid", "transaction id encoded in little-endian hexadecimal"}, - {RPCResult::Type::STR_HEX, "hash", "hash encoded in little-endian hexadecimal (including witness data)"}, - {RPCResult::Type::ARR, "depends", "array of numbers", - { - {RPCResult::Type::NUM, "", "transactions before this one (by 1-based index in 'transactions' list) that must be present in the final block if this one is"}, - }}, - {RPCResult::Type::NUM, "fee", "difference in value between transaction inputs and outputs (in satoshis); for coinbase transactions, this is a negative Number of the total collected block fees (ie, not including the block subsidy); if key is not present, fee is unknown and clients MUST NOT assume there isn't one"}, - {RPCResult::Type::NUM, "sigops", "total SigOps cost, as counted for purposes of block limits; if key is not present, sigop cost is unknown and clients MUST NOT assume it is zero"}, - {RPCResult::Type::NUM, "weight", "total transaction weight, as counted for purposes of block limits"}, - }}, - }}, - {RPCResult::Type::OBJ_DYN, "coinbaseaux", "data that should be included in the coinbase's scriptSig content", + {RPCResult::Type::STR_HEX, "data", "transaction data encoded in hexadecimal (byte-for-byte)"}, + {RPCResult::Type::STR_HEX, "txid", "transaction id encoded in little-endian hexadecimal"}, + {RPCResult::Type::STR_HEX, "hash", "hash encoded in little-endian hexadecimal (including witness data)"}, + {RPCResult::Type::ARR, "depends", "array of numbers", { - {RPCResult::Type::STR_HEX, "key", "values must be in the coinbase (keys may be ignored)"}, + {RPCResult::Type::NUM, "", "transactions before this one (by 1-based index in 'transactions' list) that must be present in the final block if this one is"}, }}, - {RPCResult::Type::NUM, "coinbasevalue", "maximum allowable input to coinbase transaction, including the generation award and transaction fees (in satoshis)"}, - {RPCResult::Type::STR, "longpollid", "an id to include with a request to longpoll on an update to this template"}, - {RPCResult::Type::STR, "target", "The hash target"}, - {RPCResult::Type::NUM_TIME, "mintime", "The minimum timestamp appropriate for the next block time, expressed in " + UNIX_EPOCH_TIME}, - {RPCResult::Type::ARR, "mutable", "list of ways the block template may be changed", - { - {RPCResult::Type::STR, "value", "A way the block template may be changed, e.g. 'time', 'transactions', 'prevblock'"}, - }}, - {RPCResult::Type::STR_HEX, "noncerange", "A range of valid nonces"}, - {RPCResult::Type::NUM, "sigoplimit", "limit of sigops in blocks"}, - {RPCResult::Type::NUM, "sizelimit", "limit of block size"}, - {RPCResult::Type::NUM, "weightlimit", "limit of block weight"}, - {RPCResult::Type::NUM_TIME, "curtime", "current timestamp in " + UNIX_EPOCH_TIME}, - {RPCResult::Type::STR, "bits", "compressed target of next block"}, - {RPCResult::Type::NUM, "height", "The height of the next block"}, - {RPCResult::Type::STR, "default_witness_commitment", /* optional */ true, "a valid witness commitment for the unmodified block template"} + {RPCResult::Type::NUM, "fee", "difference in value between transaction inputs and outputs (in satoshis); for coinbase transactions, this is a negative Number of the total collected block fees (ie, not including the block subsidy); if key is not present, fee is unknown and clients MUST NOT assume there isn't one"}, + {RPCResult::Type::NUM, "sigops", "total SigOps cost, as counted for purposes of block limits; if key is not present, sigop cost is unknown and clients MUST NOT assume it is zero"}, + {RPCResult::Type::NUM, "weight", "total transaction weight, as counted for purposes of block limits"}, }}, - RPCExamples{ + }}, + {RPCResult::Type::OBJ_DYN, "coinbaseaux", "data that should be included in the coinbase's scriptSig content", + { + {RPCResult::Type::STR_HEX, "key", "values must be in the coinbase (keys may be ignored)"}, + }}, + {RPCResult::Type::NUM, "coinbasevalue", "maximum allowable input to coinbase transaction, including the generation award and transaction fees (in satoshis)"}, + {RPCResult::Type::STR, "longpollid", "an id to include with a request to longpoll on an update to this template"}, + {RPCResult::Type::STR, "target", "The hash target"}, + {RPCResult::Type::NUM_TIME, "mintime", "The minimum timestamp appropriate for the next block time, expressed in " + UNIX_EPOCH_TIME}, + {RPCResult::Type::ARR, "mutable", "list of ways the block template may be changed", + { + {RPCResult::Type::STR, "value", "A way the block template may be changed, e.g. 'time', 'transactions', 'prevblock'"}, + }}, + {RPCResult::Type::STR_HEX, "noncerange", "A range of valid nonces"}, + {RPCResult::Type::NUM, "sigoplimit", "limit of sigops in blocks"}, + {RPCResult::Type::NUM, "sizelimit", "limit of block size"}, + {RPCResult::Type::NUM, "weightlimit", "limit of block weight"}, + {RPCResult::Type::NUM_TIME, "curtime", "current timestamp in " + UNIX_EPOCH_TIME}, + {RPCResult::Type::STR, "bits", "compressed target of next block"}, + {RPCResult::Type::NUM, "height", "The height of the next block"}, + {RPCResult::Type::STR, "default_witness_commitment", /* optional */ true, "a valid witness commitment for the unmodified block template"}, + }}, + }, + RPCExamples{ HelpExampleCli("getblocktemplate", "'{\"rules\": [\"segwit\"]}'") + HelpExampleRpc("getblocktemplate", "{\"rules\": [\"segwit\"]}") }, @@ -659,7 +660,7 @@ static RPCHelpMan getblocktemplate() throw JSONRPCError(RPC_CLIENT_P2P_DISABLED, "Error: Peer-to-peer functionality missing or disabled"); if (!Params().IsTestChain()) { - if (node.connman->GetNodeCount(CConnman::CONNECTIONS_ALL) == 0) { + if (node.connman->GetNodeCount(ConnectionDirection::Both) == 0) { throw JSONRPCError(RPC_CLIENT_NOT_CONNECTED, PACKAGE_NAME " is not connected!"); } @@ -747,7 +748,7 @@ static RPCHelpMan getblocktemplate() // Create new block CScript scriptDummy = CScript() << OP_TRUE; - pblocktemplate = BlockAssembler(mempool, Params()).CreateNewBlock(scriptDummy); + pblocktemplate = BlockAssembler(::ChainstateActive(), mempool, Params()).CreateNewBlock(scriptDummy); if (!pblocktemplate) throw JSONRPCError(RPC_OUT_OF_MEMORY, "Out of memory"); @@ -940,14 +941,17 @@ static RPCHelpMan submitblock() { // We allow 2 arguments for compliance with BIP22. Argument 2 is ignored. return RPCHelpMan{"submitblock", - "\nAttempts to submit new block to network.\n" - "See https://en.bitcoin.it/wiki/BIP_0022 for full specification.\n", - { - {"hexdata", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "the hex-encoded block data to submit"}, - {"dummy", RPCArg::Type::STR, /* default */ "ignored", "dummy value, for compatibility with BIP22. This value is ignored."}, - }, - RPCResult{RPCResult::Type::NONE, "", "Returns JSON Null when valid, a string according to BIP22 otherwise"}, - RPCExamples{ + "\nAttempts to submit new block to network.\n" + "See https://en.bitcoin.it/wiki/BIP_0022 for full specification.\n", + { + {"hexdata", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "the hex-encoded block data to submit"}, + {"dummy", RPCArg::Type::STR, /* default */ "ignored", "dummy value, for compatibility with BIP22. This value is ignored."}, + }, + { + RPCResult{"If the block was accepted", RPCResult::Type::NONE, "", ""}, + RPCResult{"Otherwise", RPCResult::Type::STR, "", "According to BIP22"}, + }, + RPCExamples{ HelpExampleCli("submitblock", "\"mydata\"") + HelpExampleRpc("submitblock", "\"mydata\"") }, diff --git a/src/rpc/misc.cpp b/src/rpc/misc.cpp index 38a0bddddb..1df5c51718 100644 --- a/src/rpc/misc.cpp +++ b/src/rpc/misc.cpp @@ -17,7 +17,6 @@ #include <script/descriptor.h> #include <util/check.h> #include <util/message.h> // For MessageSign(), MessageVerify() -#include <util/ref.h> #include <util/strencodings.h> #include <util/system.h> @@ -305,11 +304,11 @@ static RPCHelpMan verifymessage() switch (MessageVerify(strAddress, strSign, strMessage)) { case MessageVerificationResult::ERR_INVALID_ADDRESS: - throw JSONRPCError(RPC_TYPE_ERROR, "Invalid address"); + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid address"); case MessageVerificationResult::ERR_ADDRESS_NO_KEY: throw JSONRPCError(RPC_TYPE_ERROR, "Address does not refer to key"); case MessageVerificationResult::ERR_MALFORMED_SIGNATURE: - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Malformed base64 encoding"); + throw JSONRPCError(RPC_TYPE_ERROR, "Malformed base64 encoding"); case MessageVerificationResult::ERR_PUBKEY_NOT_RECOVERED: case MessageVerificationResult::ERR_NOT_SIGNED: return false; @@ -391,8 +390,9 @@ static RPCHelpMan setmocktime() throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Mocktime can not be negative: %s.", time)); } SetMockTime(time); - if (request.context.Has<NodeContext>()) { - for (const auto& chain_client : request.context.Get<NodeContext>().chain_clients) { + auto node_context = util::AnyPtr<NodeContext>(request.context); + if (node_context) { + for (const auto& chain_client : node_context->chain_clients) { chain_client->setMockTime(time); } } @@ -424,11 +424,11 @@ static RPCHelpMan mockscheduler() throw std::runtime_error("delta_time must be between 1 and 3600 seconds (1 hr)"); } + auto node_context = util::AnyPtr<NodeContext>(request.context); // protect against null pointer dereference - CHECK_NONFATAL(request.context.Has<NodeContext>()); - NodeContext& node = request.context.Get<NodeContext>(); - CHECK_NONFATAL(node.scheduler); - node.scheduler->MockForward(std::chrono::seconds(delta_seconds)); + CHECK_NONFATAL(node_context); + CHECK_NONFATAL(node_context->scheduler); + node_context->scheduler->MockForward(std::chrono::seconds(delta_seconds)); return NullUniValue; }, diff --git a/src/rpc/net.cpp b/src/rpc/net.cpp index 0224ee697a..96533a50c8 100644 --- a/src/rpc/net.cpp +++ b/src/rpc/net.cpp @@ -57,7 +57,7 @@ static RPCHelpMan getconnectioncount() if(!node.connman) throw JSONRPCError(RPC_CLIENT_P2P_DISABLED, "Error: Peer-to-peer functionality missing or disabled"); - return (int)node.connman->GetNodeCount(CConnman::CONNECTIONS_ALL); + return (int)node.connman->GetNodeCount(ConnectionDirection::Both); }, }; } @@ -202,14 +202,14 @@ static RPCHelpMan getpeerinfo() obj.pushKV("bytesrecv", stats.nRecvBytes); obj.pushKV("conntime", stats.nTimeConnected); obj.pushKV("timeoffset", stats.nTimeOffset); - if (stats.m_ping_usec > 0) { - obj.pushKV("pingtime", ((double)stats.m_ping_usec) / 1e6); + if (stats.m_last_ping_time > 0us) { + obj.pushKV("pingtime", CountSecondsDouble(stats.m_last_ping_time)); } - if (stats.m_min_ping_usec < std::numeric_limits<int64_t>::max()) { - obj.pushKV("minping", ((double)stats.m_min_ping_usec) / 1e6); + if (stats.m_min_ping_time < std::chrono::microseconds::max()) { + obj.pushKV("minping", CountSecondsDouble(stats.m_min_ping_time)); } - if (fStateStats && statestats.m_ping_wait_usec > 0) { - obj.pushKV("pingwait", ((double)statestats.m_ping_wait_usec) / 1e6); + if (fStateStats && statestats.m_ping_wait > 0s) { + obj.pushKV("pingwait", CountSecondsDouble(statestats.m_ping_wait)); } obj.pushKV("version", stats.nVersion); // Use the sanitized form of subver here, to avoid tricksy remote peers from @@ -546,7 +546,7 @@ static UniValue GetNetworksInfo() UniValue networks(UniValue::VARR); for (int n = 0; n < NET_MAX; ++n) { enum Network network = static_cast<enum Network>(n); - if (network == NET_UNROUTABLE || network == NET_I2P || network == NET_CJDNS || network == NET_INTERNAL) continue; + if (network == NET_UNROUTABLE || network == NET_CJDNS || network == NET_INTERNAL) continue; proxyType proxy; UniValue obj(UniValue::VOBJ); GetProxy(network, proxy); @@ -630,9 +630,9 @@ static RPCHelpMan getnetworkinfo() obj.pushKV("timeoffset", GetTimeOffset()); if (node.connman) { obj.pushKV("networkactive", node.connman->GetNetworkActive()); - obj.pushKV("connections", (int)node.connman->GetNodeCount(CConnman::CONNECTIONS_ALL)); - obj.pushKV("connections_in", (int)node.connman->GetNodeCount(CConnman::CONNECTIONS_IN)); - obj.pushKV("connections_out", (int)node.connman->GetNodeCount(CConnman::CONNECTIONS_OUT)); + obj.pushKV("connections", (int)node.connman->GetNodeCount(ConnectionDirection::Both)); + obj.pushKV("connections_in", (int)node.connman->GetNodeCount(ConnectionDirection::In)); + obj.pushKV("connections_out", (int)node.connman->GetNodeCount(ConnectionDirection::Out)); } obj.pushKV("networks", GetNetworksInfo()); obj.pushKV("relayfee", ValueFromAmount(::minRelayTxFee.GetFeePerK())); @@ -907,14 +907,14 @@ static RPCHelpMan addpeeraddress() [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { NodeContext& node = EnsureNodeContext(request.context); - if (!node.connman) { - throw JSONRPCError(RPC_CLIENT_P2P_DISABLED, "Error: Peer-to-peer functionality missing or disabled"); + if (!node.addrman) { + throw JSONRPCError(RPC_CLIENT_P2P_DISABLED, "Error: Address manager functionality missing or disabled"); } UniValue obj(UniValue::VOBJ); std::string addr_string = request.params[0].get_str(); - uint16_t port = request.params[1].get_int(); + uint16_t port{static_cast<uint16_t>(request.params[1].get_int())}; CNetAddr net_addr; if (!LookupHost(addr_string, net_addr, false)) { @@ -925,7 +925,7 @@ static RPCHelpMan addpeeraddress() address.nTime = GetAdjustedTime(); // The source address is set equal to the address. This is equivalent to the peer // announcing itself. - if (!node.connman->AddNewAddresses({address}, address)) { + if (!node.addrman->Add(address, address)) { obj.pushKV("success", false); return obj; } diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp index 47c776bbd1..7932bd2915 100644 --- a/src/rpc/rawtransaction.cpp +++ b/src/rpc/rawtransaction.cpp @@ -35,7 +35,6 @@ #include <validation.h> #include <validationinterface.h> - #include <numeric> #include <stdint.h> @@ -132,9 +131,10 @@ static RPCHelpMan getrawtransaction() { {RPCResult::Type::STR, "asm", "the asm"}, {RPCResult::Type::STR, "hex", "the hex"}, - {RPCResult::Type::NUM, "reqSigs", "The required sigs"}, + {RPCResult::Type::NUM, "reqSigs", /* optional */ true, "(DEPRECATED, returned only if config option -deprecatedrpc=addresses is passed) Number of required signatures"}, {RPCResult::Type::STR, "type", "The type, eg 'pubkeyhash'"}, - {RPCResult::Type::ARR, "addresses", "", + {RPCResult::Type::STR, "address", /* optional */ true, "bitcoin address (only if a well-defined address exists)"}, + {RPCResult::Type::ARR, "addresses", /* optional */ true, "(DEPRECATED, returned only if config option -deprecatedrpc=addresses is passed) Array of bitcoin addresses", { {RPCResult::Type::STR, "address", "bitcoin address"}, }}, @@ -490,9 +490,10 @@ static RPCHelpMan decoderawtransaction() { {RPCResult::Type::STR, "asm", "the asm"}, {RPCResult::Type::STR_HEX, "hex", "the hex"}, - {RPCResult::Type::NUM, "reqSigs", "The required sigs"}, + {RPCResult::Type::NUM, "reqSigs", /* optional */ true, "(DEPRECATED, returned only if config option -deprecatedrpc=addresses is passed) Number of required signatures"}, {RPCResult::Type::STR, "type", "The type, eg 'pubkeyhash'"}, - {RPCResult::Type::ARR, "addresses", "", + {RPCResult::Type::STR, "address", /* optional */ true, "bitcoin address (only if a well-defined address exists)"}, + {RPCResult::Type::ARR, "addresses", /* optional */ true, "(DEPRECATED, returned only if config option -deprecatedrpc=addresses is passed) Array of bitcoin addresses", { {RPCResult::Type::STR, "address", "bitcoin address"}, }}, @@ -548,8 +549,9 @@ static RPCHelpMan decodescript() { {RPCResult::Type::STR, "asm", "Script public key"}, {RPCResult::Type::STR, "type", "The output type (e.g. "+GetAllOutputTypes()+")"}, - {RPCResult::Type::NUM, "reqSigs", "The required signatures"}, - {RPCResult::Type::ARR, "addresses", "", + {RPCResult::Type::STR, "address", /* optional */ true, "bitcoin address (only if a well-defined address exists)"}, + {RPCResult::Type::NUM, "reqSigs", /* optional */ true, "(DEPRECATED, returned only if config option -deprecatedrpc=addresses is passed) Number of required signatures"}, + {RPCResult::Type::ARR, "addresses", /* optional */ true, "(DEPRECATED, returned only if config option -deprecatedrpc=addresses is passed) Array of bitcoin addresses", { {RPCResult::Type::STR, "address", "bitcoin address"}, }}, @@ -559,8 +561,9 @@ static RPCHelpMan decodescript() {RPCResult::Type::STR, "asm", "String representation of the script public key"}, {RPCResult::Type::STR_HEX, "hex", "Hex string of the script public key"}, {RPCResult::Type::STR, "type", "The type of the script public key (e.g. witness_v0_keyhash or witness_v0_scripthash)"}, - {RPCResult::Type::NUM, "reqSigs", "The required signatures (always 1)"}, - {RPCResult::Type::ARR, "addresses", "(always length 1)", + {RPCResult::Type::STR, "address", /* optional */ true, "bitcoin address (only if a well-defined address exists)"}, + {RPCResult::Type::NUM, "reqSigs", /* optional */ true, "(DEPRECATED, returned only if config option -deprecatedrpc=addresses is passed) Number of required signatures"}, + {RPCResult::Type::ARR, "addresses", /* optional */ true, "(DEPRECATED, returned only if config option -deprecatedrpc=addresses is passed) Array of bitcoin addresses", { {RPCResult::Type::STR, "address", "segwit address"}, }}, @@ -1828,13 +1831,13 @@ static RPCHelpMan analyzepsbt() } if (!inputs_result.empty()) result.pushKV("inputs", inputs_result); - if (psbta.estimated_vsize != nullopt) { + if (psbta.estimated_vsize != std::nullopt) { result.pushKV("estimated_vsize", (int)*psbta.estimated_vsize); } - if (psbta.estimated_feerate != nullopt) { + if (psbta.estimated_feerate != std::nullopt) { result.pushKV("estimated_feerate", ValueFromAmount(psbta.estimated_feerate->GetFeePerK())); } - if (psbta.fee != nullopt) { + if (psbta.fee != std::nullopt) { result.pushKV("fee", ValueFromAmount(*psbta.fee)); } result.pushKV("next", PSBTRoleName(psbta.next)); diff --git a/src/rpc/request.h b/src/rpc/request.h index de3a4ae840..e1569673f6 100644 --- a/src/rpc/request.h +++ b/src/rpc/request.h @@ -6,14 +6,11 @@ #ifndef BITCOIN_RPC_REQUEST_H #define BITCOIN_RPC_REQUEST_H +#include <any> #include <string> #include <univalue.h> -namespace util { -class Ref; -} // namespace util - UniValue JSONRPCRequestObj(const std::string& strMethod, const UniValue& params, const UniValue& id); UniValue JSONRPCReplyObj(const UniValue& result, const UniValue& error, const UniValue& id); std::string JSONRPCReply(const UniValue& result, const UniValue& error, const UniValue& id); @@ -34,19 +31,19 @@ public: UniValue id; std::string strMethod; UniValue params; - bool fHelp; + enum Mode { EXECUTE, GET_HELP, GET_ARGS } mode = EXECUTE; std::string URI; std::string authUser; std::string peerAddr; - const util::Ref& context; + const std::any& context; - explicit JSONRPCRequest(const util::Ref& context) : id(NullUniValue), params(NullUniValue), fHelp(false), context(context) {} + explicit JSONRPCRequest(const std::any& context) : id(NullUniValue), params(NullUniValue), context(context) {} //! Initializes request information from another request object and the //! given context. The implementation should be updated if any members are //! added or removed above. - JSONRPCRequest(const JSONRPCRequest& other, const util::Ref& context) - : id(other.id), strMethod(other.strMethod), params(other.params), fHelp(other.fHelp), URI(other.URI), + JSONRPCRequest(const JSONRPCRequest& other, const std::any& context) + : id(other.id), strMethod(other.strMethod), params(other.params), mode(other.mode), URI(other.URI), authUser(other.authUser), peerAddr(other.peerAddr), context(context) { } diff --git a/src/rpc/server.cpp b/src/rpc/server.cpp index 9a9b3713f3..2f05c8842f 100644 --- a/src/rpc/server.cpp +++ b/src/rpc/server.cpp @@ -87,8 +87,8 @@ std::string CRPCTable::help(const std::string& strCommand, const JSONRPCRequest& vCommands.push_back(make_pair(entry.second.front()->category + entry.first, entry.second.front())); sort(vCommands.begin(), vCommands.end()); - JSONRPCRequest jreq(helpreq); - jreq.fHelp = true; + JSONRPCRequest jreq = helpreq; + jreq.mode = JSONRPCRequest::GET_HELP; jreq.params = UniValue(); for (const std::pair<std::string, const CRPCCommand*>& command : vCommands) @@ -149,7 +149,7 @@ static RPCHelpMan help() } if (strCommand == "dump_all_command_conversions") { // Used for testing only, undocumented - return tableRPC.dumpArgMap(); + return tableRPC.dumpArgMap(jsonRequest); } return tableRPC.help(strCommand, jsonRequest); @@ -437,6 +437,16 @@ static inline JSONRPCRequest transformNamedArguments(const JSONRPCRequest& in, c return out; } +static bool ExecuteCommands(const std::vector<const CRPCCommand*>& commands, const JSONRPCRequest& request, UniValue& result) +{ + for (const auto& command : commands) { + if (ExecuteCommand(*command, request, result, &command == &commands.back())) { + return true; + } + } + return false; +} + UniValue CRPCTable::execute(const JSONRPCRequest &request) const { // Return immediately if in warmup @@ -450,10 +460,8 @@ UniValue CRPCTable::execute(const JSONRPCRequest &request) const auto it = mapCommands.find(request.strMethod); if (it != mapCommands.end()) { UniValue result; - for (const auto& command : it->second) { - if (ExecuteCommand(*command, request, result, &command == &it->second.back())) { - return result; - } + if (ExecuteCommands(it->second, request, result)) { + return result; } } throw JSONRPCError(RPC_METHOD_NOT_FOUND, "Method not found"); @@ -484,13 +492,18 @@ std::vector<std::string> CRPCTable::listCommands() const return commandList; } -UniValue CRPCTable::dumpArgMap() const +UniValue CRPCTable::dumpArgMap(const JSONRPCRequest& args_request) const { + JSONRPCRequest request = args_request; + request.mode = JSONRPCRequest::GET_ARGS; + UniValue ret{UniValue::VARR}; for (const auto& cmd : mapCommands) { - for (const auto& c : cmd.second) { - const auto help = RpcMethodFnType(c->unique_id)(); - help.AppendArgMap(ret); + UniValue result; + if (ExecuteCommands(cmd.second, request, result)) { + for (const auto& values : result.getValues()) { + ret.push_back(values); + } } } return ret; diff --git a/src/rpc/server.h b/src/rpc/server.h index fe5a791e1e..03967020c2 100644 --- a/src/rpc/server.h +++ b/src/rpc/server.h @@ -148,7 +148,7 @@ public: /** * Return all named arguments that need to be converted by the client from string to another JSON type */ - UniValue dumpArgMap() const; + UniValue dumpArgMap(const JSONRPCRequest& request) const; /** * Appends a CRPCCommand to the dispatch table. diff --git a/src/rpc/util.cpp b/src/rpc/util.cpp index e890c0108a..44e58cb75f 100644 --- a/src/rpc/util.cpp +++ b/src/rpc/util.cpp @@ -459,6 +459,21 @@ std::string RPCExamples::ToDescriptionString() const return m_examples.empty() ? m_examples : "\nExamples:\n" + m_examples; } +UniValue RPCHelpMan::HandleRequest(const JSONRPCRequest& request) +{ + if (request.mode == JSONRPCRequest::GET_ARGS) { + return GetArgMap(); + } + /* + * Check if the given request is valid according to this command or if + * the user is asking for help information, and throw help when appropriate. + */ + if (request.mode == JSONRPCRequest::GET_HELP || !IsValidNumArgs(request.params.size())) { + throw std::runtime_error(ToString()); + } + return m_fun(*this, request); +} + bool RPCHelpMan::IsValidNumArgs(size_t num_args) const { size_t num_required_args = 0; @@ -532,8 +547,9 @@ std::string RPCHelpMan::ToString() const return ret; } -void RPCHelpMan::AppendArgMap(UniValue& arr) const +UniValue RPCHelpMan::GetArgMap() const { + UniValue arr{UniValue::VARR}; for (int i{0}; i < int(m_args.size()); ++i) { const auto& arg = m_args.at(i); std::vector<std::string> arg_names; @@ -548,6 +564,7 @@ void RPCHelpMan::AppendArgMap(UniValue& arr) const arr.push_back(map); } } + return arr; } std::string RPCArg::GetFirstName() const diff --git a/src/rpc/util.h b/src/rpc/util.h index c54ce85f60..94c2d2d626 100644 --- a/src/rpc/util.h +++ b/src/rpc/util.h @@ -333,26 +333,12 @@ public: using RPCMethodImpl = std::function<UniValue(const RPCHelpMan&, const JSONRPCRequest&)>; RPCHelpMan(std::string name, std::string description, std::vector<RPCArg> args, RPCResults results, RPCExamples examples, RPCMethodImpl fun); + UniValue HandleRequest(const JSONRPCRequest& request); std::string ToString() const; - /** Append the named args that need to be converted from string to another JSON type */ - void AppendArgMap(UniValue& arr) const; - UniValue HandleRequest(const JSONRPCRequest& request) - { - Check(request); - return m_fun(*this, request); - } + /** Return the named args that need to be converted from string to another JSON type */ + UniValue GetArgMap() const; /** If the supplied number of args is neither too small nor too high */ bool IsValidNumArgs(size_t num_args) const; - /** - * Check if the given request is valid according to this command or if - * the user is asking for help information, and throw help when appropriate. - */ - inline void Check(const JSONRPCRequest& request) const { - if (request.fHelp || !IsValidNumArgs(request.params.size())) { - throw std::runtime_error(ToString()); - } - } - std::vector<std::string> GetArgNames() const; const std::string m_name; diff --git a/src/script/descriptor.cpp b/src/script/descriptor.cpp index 6ab01882ac..30399dca51 100644 --- a/src/script/descriptor.cpp +++ b/src/script/descriptor.cpp @@ -17,6 +17,7 @@ #include <util/vector.h> #include <memory> +#include <optional> #include <string> #include <vector> @@ -632,7 +633,7 @@ public: } } - Optional<OutputType> GetOutputType() const override { return nullopt; } + std::optional<OutputType> GetOutputType() const override { return std::nullopt; } }; /** A parsed addr(A) descriptor. */ @@ -646,7 +647,7 @@ public: AddressDescriptor(CTxDestination destination) : DescriptorImpl({}, {}, "addr"), m_destination(std::move(destination)) {} bool IsSolvable() const final { return false; } - Optional<OutputType> GetOutputType() const override + std::optional<OutputType> GetOutputType() const override { switch (m_destination.index()) { case 1 /* PKHash */: @@ -655,7 +656,7 @@ public: case 4 /* WitnessV0KeyHash */: case 5 /* WitnessUnknown */: return OutputType::BECH32; case 0 /* CNoDestination */: - default: return nullopt; + default: return std::nullopt; } } bool IsSingleType() const final { return true; } @@ -672,7 +673,7 @@ public: RawDescriptor(CScript script) : DescriptorImpl({}, {}, "raw"), m_script(std::move(script)) {} bool IsSolvable() const final { return false; } - Optional<OutputType> GetOutputType() const override + std::optional<OutputType> GetOutputType() const override { CTxDestination dest; ExtractDestination(m_script, dest); @@ -683,7 +684,7 @@ public: case 4 /* WitnessV0KeyHash */: case 5 /* WitnessUnknown */: return OutputType::BECH32; case 0 /* CNoDestination */: - default: return nullopt; + default: return std::nullopt; } } bool IsSingleType() const final { return true; } @@ -711,7 +712,7 @@ protected: } public: PKHDescriptor(std::unique_ptr<PubkeyProvider> prov) : DescriptorImpl(Vector(std::move(prov)), {}, "pkh") {} - Optional<OutputType> GetOutputType() const override { return OutputType::LEGACY; } + std::optional<OutputType> GetOutputType() const override { return OutputType::LEGACY; } bool IsSingleType() const final { return true; } }; @@ -727,7 +728,7 @@ protected: } public: WPKHDescriptor(std::unique_ptr<PubkeyProvider> prov) : DescriptorImpl(Vector(std::move(prov)), {}, "wpkh") {} - Optional<OutputType> GetOutputType() const override { return OutputType::BECH32; } + std::optional<OutputType> GetOutputType() const override { return OutputType::BECH32; } bool IsSingleType() const final { return true; } }; @@ -783,7 +784,7 @@ protected: public: SHDescriptor(std::unique_ptr<DescriptorImpl> desc) : DescriptorImpl({}, std::move(desc), "sh") {} - Optional<OutputType> GetOutputType() const override + std::optional<OutputType> GetOutputType() const override { assert(m_subdescriptor_arg); if (m_subdescriptor_arg->GetOutputType() == OutputType::BECH32) return OutputType::P2SH_SEGWIT; @@ -799,7 +800,7 @@ protected: std::vector<CScript> MakeScripts(const std::vector<CPubKey>&, const CScript* script, FlatSigningProvider&) const override { return Vector(GetScriptForDestination(WitnessV0ScriptHash(*script))); } public: WSHDescriptor(std::unique_ptr<DescriptorImpl> desc) : DescriptorImpl({}, std::move(desc), "wsh") {} - Optional<OutputType> GetOutputType() const override { return OutputType::BECH32; } + std::optional<OutputType> GetOutputType() const override { return OutputType::BECH32; } bool IsSingleType() const final { return true; } }; @@ -853,7 +854,7 @@ std::unique_ptr<PubkeyProvider> ParsePubkeyInner(uint32_t key_exp_index, const S CPubKey pubkey(data); if (pubkey.IsFullyValid()) { if (permit_uncompressed || pubkey.IsCompressed()) { - return MakeUnique<ConstPubkeyProvider>(key_exp_index, pubkey); + return std::make_unique<ConstPubkeyProvider>(key_exp_index, pubkey); } else { error = "Uncompressed keys are not allowed"; return nullptr; @@ -867,7 +868,7 @@ std::unique_ptr<PubkeyProvider> ParsePubkeyInner(uint32_t key_exp_index, const S if (permit_uncompressed || key.IsCompressed()) { CPubKey pubkey = key.GetPubKey(); out.keys.emplace(pubkey.GetID(), key); - return MakeUnique<ConstPubkeyProvider>(key_exp_index, pubkey); + return std::make_unique<ConstPubkeyProvider>(key_exp_index, pubkey); } else { error = "Uncompressed keys are not allowed"; return nullptr; @@ -894,7 +895,7 @@ std::unique_ptr<PubkeyProvider> ParsePubkeyInner(uint32_t key_exp_index, const S extpubkey = extkey.Neuter(); out.keys.emplace(extpubkey.pubkey.GetID(), extkey.key); } - return MakeUnique<BIP32PubkeyProvider>(key_exp_index, extpubkey, std::move(path), type); + return std::make_unique<BIP32PubkeyProvider>(key_exp_index, extpubkey, std::move(path), type); } /** Parse a public key including origin information (if enabled). */ @@ -931,7 +932,7 @@ std::unique_ptr<PubkeyProvider> ParsePubkey(uint32_t key_exp_index, const Span<c if (!ParseKeyPath(slash_split, info.path, error)) return nullptr; auto provider = ParsePubkeyInner(key_exp_index, origin_split[1], permit_uncompressed, out, error); if (!provider) return nullptr; - return MakeUnique<OriginPubkeyProvider>(key_exp_index, std::move(info), std::move(provider)); + return std::make_unique<OriginPubkeyProvider>(key_exp_index, std::move(info), std::move(provider)); } /** Parse a script in a particular context. */ @@ -944,17 +945,17 @@ std::unique_ptr<DescriptorImpl> ParseScript(uint32_t key_exp_index, Span<const c if (Func("pk", expr)) { auto pubkey = ParsePubkey(key_exp_index, expr, ctx != ParseScriptContext::P2WSH, out, error); if (!pubkey) return nullptr; - return MakeUnique<PKDescriptor>(std::move(pubkey)); + return std::make_unique<PKDescriptor>(std::move(pubkey)); } if (Func("pkh", expr)) { auto pubkey = ParsePubkey(key_exp_index, expr, ctx != ParseScriptContext::P2WSH, out, error); if (!pubkey) return nullptr; - return MakeUnique<PKHDescriptor>(std::move(pubkey)); + return std::make_unique<PKHDescriptor>(std::move(pubkey)); } if (ctx == ParseScriptContext::TOP && Func("combo", expr)) { auto pubkey = ParsePubkey(key_exp_index, expr, true, out, error); if (!pubkey) return nullptr; - return MakeUnique<ComboDescriptor>(std::move(pubkey)); + return std::make_unique<ComboDescriptor>(std::move(pubkey)); } else if (ctx != ParseScriptContext::TOP && Func("combo", expr)) { error = "Cannot have combo in non-top level"; return nullptr; @@ -1002,12 +1003,12 @@ std::unique_ptr<DescriptorImpl> ParseScript(uint32_t key_exp_index, Span<const c return nullptr; } } - return MakeUnique<MultisigDescriptor>(thres, std::move(providers), sorted_multi); + return std::make_unique<MultisigDescriptor>(thres, std::move(providers), sorted_multi); } if (ctx != ParseScriptContext::P2WSH && Func("wpkh", expr)) { auto pubkey = ParsePubkey(key_exp_index, expr, false, out, error); if (!pubkey) return nullptr; - return MakeUnique<WPKHDescriptor>(std::move(pubkey)); + return std::make_unique<WPKHDescriptor>(std::move(pubkey)); } else if (ctx == ParseScriptContext::P2WSH && Func("wpkh", expr)) { error = "Cannot have wpkh within wsh"; return nullptr; @@ -1015,7 +1016,7 @@ std::unique_ptr<DescriptorImpl> ParseScript(uint32_t key_exp_index, Span<const c if (ctx == ParseScriptContext::TOP && Func("sh", expr)) { auto desc = ParseScript(key_exp_index, expr, ParseScriptContext::P2SH, out, error); if (!desc || expr.size()) return nullptr; - return MakeUnique<SHDescriptor>(std::move(desc)); + return std::make_unique<SHDescriptor>(std::move(desc)); } else if (ctx != ParseScriptContext::TOP && Func("sh", expr)) { error = "Cannot have sh in non-top level"; return nullptr; @@ -1023,7 +1024,7 @@ std::unique_ptr<DescriptorImpl> ParseScript(uint32_t key_exp_index, Span<const c if (ctx != ParseScriptContext::P2WSH && Func("wsh", expr)) { auto desc = ParseScript(key_exp_index, expr, ParseScriptContext::P2WSH, out, error); if (!desc || expr.size()) return nullptr; - return MakeUnique<WSHDescriptor>(std::move(desc)); + return std::make_unique<WSHDescriptor>(std::move(desc)); } else if (ctx == ParseScriptContext::P2WSH && Func("wsh", expr)) { error = "Cannot have wsh within wsh"; return nullptr; @@ -1034,7 +1035,7 @@ std::unique_ptr<DescriptorImpl> ParseScript(uint32_t key_exp_index, Span<const c error = "Address is not valid"; return nullptr; } - return MakeUnique<AddressDescriptor>(std::move(dest)); + return std::make_unique<AddressDescriptor>(std::move(dest)); } if (ctx == ParseScriptContext::TOP && Func("raw", expr)) { std::string str(expr.begin(), expr.end()); @@ -1043,7 +1044,7 @@ std::unique_ptr<DescriptorImpl> ParseScript(uint32_t key_exp_index, Span<const c return nullptr; } auto bytes = ParseHex(str); - return MakeUnique<RawDescriptor>(CScript(bytes.begin(), bytes.end())); + return std::make_unique<RawDescriptor>(CScript(bytes.begin(), bytes.end())); } if (ctx == ParseScriptContext::P2SH) { error = "A function is needed within P2SH"; @@ -1058,10 +1059,10 @@ std::unique_ptr<DescriptorImpl> ParseScript(uint32_t key_exp_index, Span<const c std::unique_ptr<PubkeyProvider> InferPubkey(const CPubKey& pubkey, ParseScriptContext, const SigningProvider& provider) { - std::unique_ptr<PubkeyProvider> key_provider = MakeUnique<ConstPubkeyProvider>(0, pubkey); + std::unique_ptr<PubkeyProvider> key_provider = std::make_unique<ConstPubkeyProvider>(0, pubkey); KeyOriginInfo info; if (provider.GetKeyOrigin(pubkey.GetID(), info)) { - return MakeUnique<OriginPubkeyProvider>(0, std::move(info), std::move(key_provider)); + return std::make_unique<OriginPubkeyProvider>(0, std::move(info), std::move(key_provider)); } return key_provider; } @@ -1074,7 +1075,7 @@ std::unique_ptr<DescriptorImpl> InferScript(const CScript& script, ParseScriptCo if (txntype == TxoutType::PUBKEY) { CPubKey pubkey(data[0].begin(), data[0].end()); if (pubkey.IsValid()) { - return MakeUnique<PKDescriptor>(InferPubkey(pubkey, ctx, provider)); + return std::make_unique<PKDescriptor>(InferPubkey(pubkey, ctx, provider)); } } if (txntype == TxoutType::PUBKEYHASH) { @@ -1082,7 +1083,7 @@ std::unique_ptr<DescriptorImpl> InferScript(const CScript& script, ParseScriptCo CKeyID keyid(hash); CPubKey pubkey; if (provider.GetPubKey(keyid, pubkey)) { - return MakeUnique<PKHDescriptor>(InferPubkey(pubkey, ctx, provider)); + return std::make_unique<PKHDescriptor>(InferPubkey(pubkey, ctx, provider)); } } if (txntype == TxoutType::WITNESS_V0_KEYHASH && ctx != ParseScriptContext::P2WSH) { @@ -1090,7 +1091,7 @@ std::unique_ptr<DescriptorImpl> InferScript(const CScript& script, ParseScriptCo CKeyID keyid(hash); CPubKey pubkey; if (provider.GetPubKey(keyid, pubkey)) { - return MakeUnique<WPKHDescriptor>(InferPubkey(pubkey, ctx, provider)); + return std::make_unique<WPKHDescriptor>(InferPubkey(pubkey, ctx, provider)); } } if (txntype == TxoutType::MULTISIG) { @@ -1099,7 +1100,7 @@ std::unique_ptr<DescriptorImpl> InferScript(const CScript& script, ParseScriptCo CPubKey pubkey(data[i].begin(), data[i].end()); providers.push_back(InferPubkey(pubkey, ctx, provider)); } - return MakeUnique<MultisigDescriptor>((int)data[0][0], std::move(providers)); + return std::make_unique<MultisigDescriptor>((int)data[0][0], std::move(providers)); } if (txntype == TxoutType::SCRIPTHASH && ctx == ParseScriptContext::TOP) { uint160 hash(data[0]); @@ -1107,7 +1108,7 @@ std::unique_ptr<DescriptorImpl> InferScript(const CScript& script, ParseScriptCo CScript subscript; if (provider.GetCScript(scriptid, subscript)) { auto sub = InferScript(subscript, ParseScriptContext::P2SH, provider); - if (sub) return MakeUnique<SHDescriptor>(std::move(sub)); + if (sub) return std::make_unique<SHDescriptor>(std::move(sub)); } } if (txntype == TxoutType::WITNESS_V0_SCRIPTHASH && ctx != ParseScriptContext::P2WSH) { @@ -1116,18 +1117,18 @@ std::unique_ptr<DescriptorImpl> InferScript(const CScript& script, ParseScriptCo CScript subscript; if (provider.GetCScript(scriptid, subscript)) { auto sub = InferScript(subscript, ParseScriptContext::P2WSH, provider); - if (sub) return MakeUnique<WSHDescriptor>(std::move(sub)); + if (sub) return std::make_unique<WSHDescriptor>(std::move(sub)); } } CTxDestination dest; if (ExtractDestination(script, dest)) { if (GetScriptForDestination(dest) == script) { - return MakeUnique<AddressDescriptor>(std::move(dest)); + return std::make_unique<AddressDescriptor>(std::move(dest)); } } - return MakeUnique<RawDescriptor>(script); + return std::make_unique<RawDescriptor>(script); } diff --git a/src/script/descriptor.h b/src/script/descriptor.h index 46d51fa587..332ae2f230 100644 --- a/src/script/descriptor.h +++ b/src/script/descriptor.h @@ -5,12 +5,12 @@ #ifndef BITCOIN_SCRIPT_DESCRIPTOR_H #define BITCOIN_SCRIPT_DESCRIPTOR_H -#include <optional.h> #include <outputtype.h> #include <script/script.h> #include <script/sign.h> #include <script/signingprovider.h> +#include <optional> #include <vector> using ExtPubKeyMap = std::unordered_map<uint32_t, CExtPubKey>; @@ -124,7 +124,7 @@ struct Descriptor { virtual void ExpandPrivate(int pos, const SigningProvider& provider, FlatSigningProvider& out) const = 0; /** @return The OutputType of the scriptPubKey(s) produced by this descriptor. Or nullopt if indeterminate (multiple or none) */ - virtual Optional<OutputType> GetOutputType() const = 0; + virtual std::optional<OutputType> GetOutputType() const = 0; }; /** Parse a `descriptor` string. Included private keys are put in `out`. diff --git a/src/script/interpreter.cpp b/src/script/interpreter.cpp index ecac3b9e7e..20a4ce48b0 100644 --- a/src/script/interpreter.cpp +++ b/src/script/interpreter.cpp @@ -1834,7 +1834,7 @@ static bool ExecuteWitnessScript(const Span<const valtype>& stack_span, const CS static bool VerifyTaprootCommitment(const std::vector<unsigned char>& control, const std::vector<unsigned char>& program, const CScript& script, uint256& tapleaf_hash) { const int path_len = (control.size() - TAPROOT_CONTROL_BASE_SIZE) / TAPROOT_CONTROL_NODE_SIZE; - //! The inner pubkey (x-only, so no Y coordinate parity). + //! The internal pubkey (x-only, so no Y coordinate parity). const XOnlyPubKey p{uint256(std::vector<unsigned char>(control.begin() + 1, control.begin() + TAPROOT_CONTROL_BASE_SIZE))}; //! The output pubkey (taken from the scriptPubKey). const XOnlyPubKey q{uint256(program)}; @@ -1852,9 +1852,9 @@ static bool VerifyTaprootCommitment(const std::vector<unsigned char>& control, c } k = ss_branch.GetSHA256(); } - // Compute the tweak from the Merkle root and the inner pubkey. + // Compute the tweak from the Merkle root and the internal pubkey. k = (CHashWriter(HASHER_TAPTWEAK) << MakeSpan(p) << k).GetSHA256(); - // Verify that the output pubkey matches the tweaked inner pubkey, after correcting for parity. + // Verify that the output pubkey matches the tweaked internal pubkey, after correcting for parity. return q.CheckPayToContract(p, k, control[0] & 1); } diff --git a/src/script/standard.cpp b/src/script/standard.cpp index 4d882cd1f1..700155c8d4 100644 --- a/src/script/standard.cpp +++ b/src/script/standard.cpp @@ -220,7 +220,6 @@ bool ExtractDestination(const CScript& scriptPubKey, CTxDestination& addressRet) return true; } case TxoutType::MULTISIG: - // Multisig txns have more than one address... case TxoutType::NULL_DATA: case TxoutType::NONSTANDARD: return false; @@ -228,6 +227,7 @@ bool ExtractDestination(const CScript& scriptPubKey, CTxDestination& addressRet) assert(false); } +// TODO: from v23 ("addresses" and "reqSigs" deprecated) "ExtractDestinations" should be removed bool ExtractDestinations(const CScript& scriptPubKey, TxoutType& typeRet, std::vector<CTxDestination>& addressRet, int& nRequiredRet) { addressRet.clear(); diff --git a/src/script/standard.h b/src/script/standard.h index d5d87392ad..f2bf4a8af3 100644 --- a/src/script/standard.h +++ b/src/script/standard.h @@ -247,6 +247,8 @@ bool ExtractDestination(const CScript& scriptPubKey, CTxDestination& addressRet) * Note: this function confuses destinations (a subset of CScripts that are * encodable as an address) with key identifiers (of keys involved in a * CScript), and its use should be phased out. + * + * TODO: from v23 ("addresses" and "reqSigs" deprecated) "ExtractDestinations" should be removed */ bool ExtractDestinations(const CScript& scriptPubKey, TxoutType& typeRet, std::vector<CTxDestination>& addressRet, int& nRequiredRet); diff --git a/src/shutdown.cpp b/src/shutdown.cpp index 6a73e0b2d8..2fc195e2d1 100644 --- a/src/shutdown.cpp +++ b/src/shutdown.cpp @@ -5,16 +5,15 @@ #include <shutdown.h> +#include <logging.h> +#include <util/tokenpipe.h> + #include <config/bitcoin-config.h> #include <assert.h> #include <atomic> #ifdef WIN32 #include <condition_variable> -#else -#include <errno.h> -#include <fcntl.h> -#include <unistd.h> #endif static std::atomic<bool> fRequestShutdown(false); @@ -24,25 +23,18 @@ std::mutex g_shutdown_mutex; std::condition_variable g_shutdown_cv; #else /** On UNIX-like operating systems use the self-pipe trick. - * Index 0 will be the read end of the pipe, index 1 the write end. */ -static int g_shutdown_pipe[2] = {-1, -1}; +static TokenPipeEnd g_shutdown_r; +static TokenPipeEnd g_shutdown_w; #endif bool InitShutdownState() { #ifndef WIN32 -#if HAVE_O_CLOEXEC && HAVE_DECL_PIPE2 - // If we can, make sure that the file descriptors are closed on exec() - // to prevent interference. - if (pipe2(g_shutdown_pipe, O_CLOEXEC) != 0) { - return false; - } -#else - if (pipe(g_shutdown_pipe) != 0) { - return false; - } -#endif + std::optional<TokenPipe> pipe = TokenPipe::Make(); + if (!pipe) return false; + g_shutdown_r = pipe->TakeReadEnd(); + g_shutdown_w = pipe->TakeWriteEnd(); #endif return true; } @@ -59,17 +51,10 @@ void StartShutdown() // case of a reentrant signal. if (!fRequestShutdown.exchange(true)) { // Write an arbitrary byte to the write end of the shutdown pipe. - const char token = 'x'; - while (true) { - int result = write(g_shutdown_pipe[1], &token, 1); - if (result < 0) { - // Failure. It's possible that the write was interrupted by another signal. - // Other errors are unexpected here. - assert(errno == EINTR); - } else { - assert(result == 1); - break; - } + int res = g_shutdown_w.TokenWrite('x'); + if (res != 0) { + LogPrintf("Sending shutdown token failed\n"); + assert(0); } } #endif @@ -96,17 +81,10 @@ void WaitForShutdown() std::unique_lock<std::mutex> lk(g_shutdown_mutex); g_shutdown_cv.wait(lk, [] { return fRequestShutdown.load(); }); #else - char token; - while (true) { - int result = read(g_shutdown_pipe[0], &token, 1); - if (result < 0) { - // Failure. Check if the read was interrupted by a signal. - // Other errors are unexpected here. - assert(errno == EINTR); - } else { - assert(result == 1); - break; - } + int res = g_shutdown_r.TokenRead(); + if (res != 'x') { + LogPrintf("Reading shutdown token failed\n"); + assert(0); } #endif } diff --git a/src/signet.cpp b/src/signet.cpp index e68f031aa4..fb41e43752 100644 --- a/src/signet.cpp +++ b/src/signet.cpp @@ -65,7 +65,7 @@ static uint256 ComputeModifiedMerkleRoot(const CMutableTransaction& cb, const CB return ComputeMerkleRoot(std::move(leaves)); } -Optional<SignetTxs> SignetTxs::Create(const CBlock& block, const CScript& challenge) +std::optional<SignetTxs> SignetTxs::Create(const CBlock& block, const CScript& challenge) { CMutableTransaction tx_to_spend; tx_to_spend.nVersion = 0; @@ -83,12 +83,12 @@ Optional<SignetTxs> SignetTxs::Create(const CBlock& block, const CScript& challe // responses from block coinbase tx // find and delete signet signature - if (block.vtx.empty()) return nullopt; // no coinbase tx in block; invalid + if (block.vtx.empty()) return std::nullopt; // no coinbase tx in block; invalid CMutableTransaction modified_cb(*block.vtx.at(0)); const int cidx = GetWitnessCommitmentIndex(block); if (cidx == NO_WITNESS_COMMITMENT) { - return nullopt; // require a witness commitment + return std::nullopt; // require a witness commitment } CScript& witness_commitment = modified_cb.vout.at(cidx).scriptPubKey; @@ -101,9 +101,9 @@ Optional<SignetTxs> SignetTxs::Create(const CBlock& block, const CScript& challe VectorReader v(SER_NETWORK, INIT_PROTO_VERSION, signet_solution, 0); v >> tx_spending.vin[0].scriptSig; v >> tx_spending.vin[0].scriptWitness.stack; - if (!v.empty()) return nullopt; // extraneous data encountered + if (!v.empty()) return std::nullopt; // extraneous data encountered } catch (const std::exception&) { - return nullopt; // parsing error + return std::nullopt; // parsing error } } uint256 signet_merkle = ComputeModifiedMerkleRoot(modified_cb, block); @@ -129,7 +129,7 @@ bool CheckSignetBlockSolution(const CBlock& block, const Consensus::Params& cons } const CScript challenge(consensusParams.signet_challenge.begin(), consensusParams.signet_challenge.end()); - const Optional<SignetTxs> signet_txs = SignetTxs::Create(block, challenge); + const std::optional<SignetTxs> signet_txs = SignetTxs::Create(block, challenge); if (!signet_txs) { LogPrint(BCLog::VALIDATION, "CheckSignetBlockSolution: Errors in block (block solution parse failure)\n"); diff --git a/src/signet.h b/src/signet.h index 23563a83c4..f876488c0a 100644 --- a/src/signet.h +++ b/src/signet.h @@ -9,7 +9,7 @@ #include <primitives/block.h> #include <primitives/transaction.h> -#include <optional.h> +#include <optional> /** * Extract signature and check whether a block has a valid solution @@ -28,7 +28,7 @@ class SignetTxs { SignetTxs(const T1& to_spend, const T2& to_sign) : m_to_spend{to_spend}, m_to_sign{to_sign} { } public: - static Optional<SignetTxs> Create(const CBlock& block, const CScript& challenge); + static std::optional<SignetTxs> Create(const CBlock& block, const CScript& challenge); const CTransaction m_to_spend; const CTransaction m_to_sign; diff --git a/src/test/addrman_tests.cpp b/src/test/addrman_tests.cpp index 37ff8a9afe..d438537606 100644 --- a/src/test/addrman_tests.cpp +++ b/src/test/addrman_tests.cpp @@ -100,7 +100,7 @@ static CNetAddr ResolveIP(const std::string& ip) return addr; } -static CService ResolveService(const std::string& ip, const int port = 0) +static CService ResolveService(const std::string& ip, uint16_t port = 0) { CService serv; BOOST_CHECK_MESSAGE(Lookup(ip, serv, port, false), strprintf("failed to resolve: %s:%i", ip, port)); diff --git a/src/test/allocator_tests.cpp b/src/test/allocator_tests.cpp index d33d668a04..b523173a45 100644 --- a/src/test/allocator_tests.cpp +++ b/src/test/allocator_tests.cpp @@ -2,7 +2,6 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#include <util/memory.h> #include <util/system.h> #include <test/util/setup_common.h> @@ -163,7 +162,7 @@ private: BOOST_AUTO_TEST_CASE(lockedpool_tests_mock) { // Test over three virtual arenas, of which one will succeed being locked - std::unique_ptr<LockedPageAllocator> x = MakeUnique<TestLockedPageAllocator>(3, 1); + std::unique_ptr<LockedPageAllocator> x = std::make_unique<TestLockedPageAllocator>(3, 1); LockedPool pool(std::move(x)); BOOST_CHECK(pool.stats().total == 0); BOOST_CHECK(pool.stats().locked == 0); diff --git a/src/test/bech32_tests.cpp b/src/test/bech32_tests.cpp index a2098f4f56..2651e46430 100644 --- a/src/test/bech32_tests.cpp +++ b/src/test/bech32_tests.cpp @@ -10,7 +10,7 @@ BOOST_FIXTURE_TEST_SUITE(bech32_tests, BasicTestingSetup) -BOOST_AUTO_TEST_CASE(bip173_testvectors_valid) +BOOST_AUTO_TEST_CASE(bech32_testvectors_valid) { static const std::string CASES[] = { "A12UEL5L", @@ -22,15 +22,35 @@ BOOST_AUTO_TEST_CASE(bip173_testvectors_valid) "?1ezyfcl", }; for (const std::string& str : CASES) { - auto ret = bech32::Decode(str); - BOOST_CHECK(!ret.first.empty()); - std::string recode = bech32::Encode(ret.first, ret.second); + const auto dec = bech32::Decode(str); + BOOST_CHECK(dec.encoding == bech32::Encoding::BECH32); + std::string recode = bech32::Encode(bech32::Encoding::BECH32, dec.hrp, dec.data); BOOST_CHECK(!recode.empty()); BOOST_CHECK(CaseInsensitiveEqual(str, recode)); } } -BOOST_AUTO_TEST_CASE(bip173_testvectors_invalid) +BOOST_AUTO_TEST_CASE(bech32m_testvectors_valid) +{ + static const std::string CASES[] = { + "A1LQFN3A", + "a1lqfn3a", + "an83characterlonghumanreadablepartthatcontainsthetheexcludedcharactersbioandnumber11sg7hg6", + "abcdef1l7aum6echk45nj3s0wdvt2fg8x9yrzpqzd3ryx", + "11llllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllludsr8", + "split1checkupstagehandshakeupstreamerranterredcaperredlc445v", + "?1v759aa" + }; + for (const std::string& str : CASES) { + const auto dec = bech32::Decode(str); + BOOST_CHECK(dec.encoding == bech32::Encoding::BECH32M); + std::string recode = bech32::Encode(bech32::Encoding::BECH32M, dec.hrp, dec.data); + BOOST_CHECK(!recode.empty()); + BOOST_CHECK(CaseInsensitiveEqual(str, recode)); + } +} + +BOOST_AUTO_TEST_CASE(bech32_testvectors_invalid) { static const std::string CASES[] = { " 1nwldj5", @@ -49,8 +69,32 @@ BOOST_AUTO_TEST_CASE(bip173_testvectors_invalid) "A12uEL5L", }; for (const std::string& str : CASES) { - auto ret = bech32::Decode(str); - BOOST_CHECK(ret.first.empty()); + const auto dec = bech32::Decode(str); + BOOST_CHECK(dec.encoding == bech32::Encoding::INVALID); + } +} + +BOOST_AUTO_TEST_CASE(bech32m_testvectors_invalid) +{ + static const std::string CASES[] = { + " 1xj0phk", + "\x7f""1g6xzxy", + "\x80""1vctc34", + "an84characterslonghumanreadablepartthatcontainsthetheexcludedcharactersbioandnumber11d6pts4", + "qyrz8wqd2c9m", + "1qyrz8wqd2c9m", + "y1b0jsk6g", + "lt1igcx5c0", + "in1muywd", + "mm1crxm3i", + "au1s5cgom", + "M1VUXWEZ", + "16plkw9", + "1p2gdwpf" + }; + for (const std::string& str : CASES) { + const auto dec = bech32::Decode(str); + BOOST_CHECK(dec.encoding == bech32::Encoding::INVALID); } } diff --git a/src/test/blockfilter_index_tests.cpp b/src/test/blockfilter_index_tests.cpp index 633a95ce96..9903ba75cb 100644 --- a/src/test/blockfilter_index_tests.cpp +++ b/src/test/blockfilter_index_tests.cpp @@ -62,7 +62,7 @@ CBlock BuildChainTestingSetup::CreateBlock(const CBlockIndex* prev, const CScript& scriptPubKey) { const CChainParams& chainparams = Params(); - std::unique_ptr<CBlockTemplate> pblocktemplate = BlockAssembler(*m_node.mempool, chainparams).CreateNewBlock(scriptPubKey); + std::unique_ptr<CBlockTemplate> pblocktemplate = BlockAssembler(::ChainstateActive(), *m_node.mempool, chainparams).CreateNewBlock(scriptPubKey); CBlock& block = pblocktemplate->block; block.hashPrevBlock = prev->GetBlockHash(); block.nTime = prev->nTime + 1; diff --git a/src/test/bswap_tests.cpp b/src/test/bswap_tests.cpp index c89cb5488d..2dbca4e8b6 100644 --- a/src/test/bswap_tests.cpp +++ b/src/test/bswap_tests.cpp @@ -11,7 +11,6 @@ BOOST_FIXTURE_TEST_SUITE(bswap_tests, BasicTestingSetup) BOOST_AUTO_TEST_CASE(bswap_tests) { - // Sibling in bitcoin/src/qt/test/compattests.cpp uint16_t u1 = 0x1234; uint32_t u2 = 0x56789abc; uint64_t u3 = 0xdef0123456789abc; diff --git a/src/test/checkqueue_tests.cpp b/src/test/checkqueue_tests.cpp index 21921375b3..64c6d7f634 100644 --- a/src/test/checkqueue_tests.cpp +++ b/src/test/checkqueue_tests.cpp @@ -5,7 +5,6 @@ #include <checkqueue.h> #include <sync.h> #include <test/util/setup_common.h> -#include <util/memory.h> #include <util/system.h> #include <util/time.h> @@ -146,7 +145,7 @@ typedef CCheckQueue<FrozenCleanupCheck> FrozenCleanup_Queue; */ static void Correct_Queue_range(std::vector<size_t> range) { - auto small_queue = MakeUnique<Correct_Queue>(QUEUE_BATCH_SIZE); + auto small_queue = std::make_unique<Correct_Queue>(QUEUE_BATCH_SIZE); small_queue->StartWorkerThreads(SCRIPT_CHECK_THREADS); // Make vChecks here to save on malloc (this test can be slow...) std::vector<FakeCheckCheckCompletion> vChecks; @@ -206,7 +205,7 @@ BOOST_AUTO_TEST_CASE(test_CheckQueue_Correct_Random) /** Test that failing checks are caught */ BOOST_AUTO_TEST_CASE(test_CheckQueue_Catches_Failure) { - auto fail_queue = MakeUnique<Failing_Queue>(QUEUE_BATCH_SIZE); + auto fail_queue = std::make_unique<Failing_Queue>(QUEUE_BATCH_SIZE); fail_queue->StartWorkerThreads(SCRIPT_CHECK_THREADS); for (size_t i = 0; i < 1001; ++i) { @@ -234,7 +233,7 @@ BOOST_AUTO_TEST_CASE(test_CheckQueue_Catches_Failure) // future blocks, ie, the bad state is cleared. BOOST_AUTO_TEST_CASE(test_CheckQueue_Recovers_From_Failure) { - auto fail_queue = MakeUnique<Failing_Queue>(QUEUE_BATCH_SIZE); + auto fail_queue = std::make_unique<Failing_Queue>(QUEUE_BATCH_SIZE); fail_queue->StartWorkerThreads(SCRIPT_CHECK_THREADS); for (auto times = 0; times < 10; ++times) { @@ -258,7 +257,7 @@ BOOST_AUTO_TEST_CASE(test_CheckQueue_Recovers_From_Failure) // more than once as well BOOST_AUTO_TEST_CASE(test_CheckQueue_UniqueCheck) { - auto queue = MakeUnique<Unique_Queue>(QUEUE_BATCH_SIZE); + auto queue = std::make_unique<Unique_Queue>(QUEUE_BATCH_SIZE); queue->StartWorkerThreads(SCRIPT_CHECK_THREADS); size_t COUNT = 100000; @@ -293,7 +292,7 @@ BOOST_AUTO_TEST_CASE(test_CheckQueue_UniqueCheck) // time could leave the data hanging across a sequence of blocks. BOOST_AUTO_TEST_CASE(test_CheckQueue_Memory) { - auto queue = MakeUnique<Memory_Queue>(QUEUE_BATCH_SIZE); + auto queue = std::make_unique<Memory_Queue>(QUEUE_BATCH_SIZE); queue->StartWorkerThreads(SCRIPT_CHECK_THREADS); for (size_t i = 0; i < 1000; ++i) { size_t total = i; @@ -320,7 +319,7 @@ BOOST_AUTO_TEST_CASE(test_CheckQueue_Memory) // have been destructed BOOST_AUTO_TEST_CASE(test_CheckQueue_FrozenCleanup) { - auto queue = MakeUnique<FrozenCleanup_Queue>(QUEUE_BATCH_SIZE); + auto queue = std::make_unique<FrozenCleanup_Queue>(QUEUE_BATCH_SIZE); bool fails = false; queue->StartWorkerThreads(SCRIPT_CHECK_THREADS); std::thread t0([&]() { @@ -360,7 +359,7 @@ BOOST_AUTO_TEST_CASE(test_CheckQueue_FrozenCleanup) /** Test that CCheckQueueControl is threadsafe */ BOOST_AUTO_TEST_CASE(test_CheckQueueControl_Locks) { - auto queue = MakeUnique<Standard_Queue>(QUEUE_BATCH_SIZE); + auto queue = std::make_unique<Standard_Queue>(QUEUE_BATCH_SIZE); { std::vector<std::thread> tg; std::atomic<int> nThreads {0}; diff --git a/src/test/data/key_io_invalid.json b/src/test/data/key_io_invalid.json index 9b52943ac6..abe07dad24 100644 --- a/src/test/data/key_io_invalid.json +++ b/src/test/data/key_io_invalid.json @@ -6,177 +6,207 @@ "x" ], [ - "37qgekLpCCHrQuSjvX3fs496FWTGsHFHizjJAs6NPcR47aefnnCWECAhHV6E3g4YN7u7Yuwod5Y" + "2v7k5Bb8Lr1MMgTgW6HAf5YHXi6BzpPjHpQ4srD4RSwHYpzXKiXmLAgiLhkXvp3JF5v7nq45EWr" ], [ - "dzb7VV1Ui55BARxv7ATxAtCUeJsANKovDGWFVgpTbhq9gvPqP3yv" + "RAZzCGtMbiUgMiiyrZySrSpdfnQReFXA3r" ], [ - "MuNu7ZAEDFiHthiunm7dPjwKqrVNCM3mAz6rP9zFveQu14YA8CxExSJTHcVP9DErn6u84E6Ej7S" + "NYamy7tcPQTzoU5iyQojD3sqhiz7zxkvn8" ], [ - "rPpQpYknyNQ5AEHuY6H8ijJJrYc2nDKKk9jjmKEXsWzyAQcFGpDLU2Zvsmoi8JLR7hAwoy3RQWf" + "geaFG555Ex5nyRf7JjW6Pj2GwZA8KYxtJJLbr1eZhVW75STbYBZeRszy3wg4pkKdF4ez9J4wQiz" ], [ - "4Uc3FmN6NQ6zLBK5QQBXRBUREaaHwCZYsGCueHauuDmJpZKn6jkEskMB2Zi2CNgtb5r6epWEFfUJq" + "2Cxmid3c2XQ2zvQ8SA1ha2TKqvqbJS9XFmXRsCneBS3Po7Qqb65z5zNdsoF9AfieXFcpoVPmkmfa" ], [ - "7aQgR5DFQ25vyXmqZAWmnVCjL3PkBcdVkBUpjrjMTcghHx3E8wb" + "gaJ7UVge2njVg9tFTetJrtHgruMm7aQDiSAxfHrVEgzK8N2ooagDVmDkdph434xzc4K96Gjyxcs" ], [ - "17QpPprjeg69fW1DV8DcYYCKvWjYhXvWkov6MJ1iTTvMFj6weAqW7wybZeH57WTNxXVCRH4veVs" + "5JN5BEVQPZ3tAiatz1RGXkrJuE3EC6bervMaPb38wTNgEuZCeqp" ], [ - "KxuACDviz8Xvpn1xAh9MfopySZNuyajYMZWz16Dv2mHHryznWUp3" + "3TnFbyUtBRS5rE1KTW81qLVspjJNaB3uu6uuvLjxhZo2DB6PCGh" ], [ - "7nK3GSmqdXJQtdohvGfJ7KsSmn3TmGqExug49583bDAL91pVSGq5xS9SHoAYL3Wv3ijKTit65th" + "7UgSZGaMaTc4d2mdEgcGBFiMeS6eMsithGUqvBsKTQdGzD7XQDbMEYo3gojdbXEPbUdFF3CQoK72f" ], [ - "cTivdBmq7bay3RFGEBBuNfMh2P1pDCgRYN2Wbxmgwr4ki3jNUL2va" + "9261wfqQqruNDnBDhbbb4tN9oKA1KpRFHeoYeufyJApVGixyAG4V" ], [ - "gjMV4vjNjyMrna4fsAr8bWxAbwtmMUBXJS3zL4NJt5qjozpbQLmAfK1uA3CquSqsZQMpoD1g2nk" + "cS824CTUh18scFmYuqt6BgxuRhdR4dEEnCHs3fzBbcyQgbfasHbw" ], [ - "emXm1naBMoVzPjbk7xpeTVMFy4oDEe25UmoyGgKEB1gGWsK8kRGs" + "tc1q0ywf7wkz6t580n3yemd3ucfw8jxn93tpc6wskt" ], [ - "7VThQnNRj1o3Zyvc7XHPRrjDf8j2oivPTeDXnRPYWeYGE4pXeRJDZgf28ppti5hsHWXS2GSobdqyo" + "bt1pxeeuh96wpm5c6u3kavts2qgwlv6y8um7u7ga6ltlwrhrv7w9vers8lgt3k" ], [ - "1G9u6oCVCPh2o8m3t55ACiYvG1y5BHewUkDSdiQarDcYXXhFHYdzMdYfUAhfxn5vNZBwpgUNpso" + "tb130lvl2lyugsk2tf3zhwcjjv39dmwt2tt7ytqaexy8edwcuwks6p5scll5kz" ], [ - "31QQ7ZMLkScDiB4VyZjuptr7AEc9j1SjstF7pRoLhHTGkW4Q2y9XELobQmhhWxeRvqcukGd1XCq" + "bcrt1rhsveeudk" ], [ - "DHqKSnpxa8ZdQyH8keAhvLTrfkyBMQxqngcQA5N8LQ9KVt25kmGN" + "bc10rmfwl8nxdweeyc4sf89t0tn9fv9w6qpyzsnl2r4k48vjqh03qas9asdje0rlr0phru0wqw0p" ], [ - "2LUHcJPbwLCy9GLH1qXmfmAwvadWw4bp4PCpDfduLqV17s6iDcy1imUwhQJhAoNoN1XNmweiJP4i" + "tb1qjqnfsuatr54e957xzg9sqk7yqcry9lns" ], [ - "7USRzBXAnmck8fX9HmW7RAb4qt92VFX6soCnts9s74wxm4gguVhtG5of8fZGbNPJA83irHVY6bCos" + "bcrt1q8p08mv8echkf3es027u4cdswxlylm3th76ls8v6y4zy4vwsavngpr4e4td" ], [ - "1DGezo7BfVebZxAbNT3XGujdeHyNNBF3vnficYoTSp4PfK2QaML9bHzAMxke3wdKdHYWmsMTJVu" + "BC1QNC2H66VLWTWTW52DP0FYUSNU3QQG5VT4V" ], [ - "2D12DqDZKwCxxkzs1ZATJWvgJGhQ4cFi3WrizQ5zLAyhN5HxuAJ1yMYaJp8GuYsTLLxTAz6otCfb" + "tb1qgk665m2auw09rc7pqyf7aulcuhmatz9xqtr5mxew7zuysacaascqs9v0vn" ], [ - "8AFJzuTujXjw1Z6M3fWhQ1ujDW7zsV4ePeVjVo7D1egERqSW9nZ" + "bcrt17CAPP7" ], [ - "163Q17qLbTCue8YY3AvjpUhotuaodLm2uqMhpYirsKjVqnxJRWTEoywMVY3NbBAHuhAJ2cF9GAZ" + "bc1qxmf2d6aerjzam3rur0zufqxqnyqfts5u302s7x" ], [ - "2MnmgiRH4eGLyLc9eAqStzk7dFgBjFtUCtu" + "tb1qn8x5dnzpexq7nnvrvnhwr9c3wkakpcyu9wwsjzq9pstkwg0t6qhs4l3rv6" ], [ - "461QQ2sYWxU7H2PV4oBwJGNch8XVTYYbZxU" + "BCRT1Q397G2RNVYRL5LK07CE8NCKHVKP8Z4SC9U0MVH9" ], [ - "2UCtv53VttmQYkVU4VMtXB31REvQg4ABzs41AEKZ8UcB7DAfVzdkV9JDErwGwyj5AUHLkmgZeobs" + "bc1pgxwyajq0gdn389f69uwn2fw9q0z5c9s063j5dgkdd23ajaud4hpsercr9h" ], [ - "cSNjAsnhgtiFMi6MtfvgscMB2Cbhn2v1FUYfviJ1CdjfidvmeW6mn" + "tb1z6mnmp5k542l6yk4ul0mp4rq3yvz44lfm" ], [ - "gmsow2Y6EWAFDFE1CE4Hd3Tpu2BvfmBfG1SXsuRARbnt1WjkZnFh1qGTiptWWbjsq2Q6qvpgJVj" + "bcrt17capp7" ], [ - "nksUKSkzS76v8EsSgozXGMoQFiCoCHzCVajFKAXqzK5on9ZJYVHMD5CKwgmX3S3c7M1U3xabUny" + "2D2bqvKseKHdoKjCNvjVULUgmxHu9hjKGwDbPRjTRH59tsHNLeyKwq3vyVBbo9LByY9wiapqjwFY" ], [ - "L3favK1UzFGgdzYBF2oBT5tbayCo4vtVBLJhg2iYuMeePxWG8SQc" + "2SSjAim4wZpeQRe5zTj1qqS6Li9ttJDaZ3ze" ], [ - "7VxLxGGtYT6N99GdEfi6xz56xdQ8nP2dG1CavuXx7Rf2PrvNMTBNevjkfgs9JmkcGm6EXpj8ipyPZ" + "mi9H6MjLwXxy9kxe1x4ToxyLRBsmcZxgVi" ], [ - "2mbZwFXF6cxShaCo2czTRB62WTx9LxhTtpP" + "VciXoxEitcn88jy197J9n9cpJ1pZahzU3SyWUiHqLgcfjttLEEJz" ], [ - "dB7cwYdcPSgiyAwKWL3JwCVwSk6epU2txw" + "KppmwADGoExPT9Eq5hjRWpWFDbzJyfzHFgsfxBiDHNpVBgWPRNuy" ], [ - "HPhFUhUAh8ZQQisH8QQWafAxtQYju3SFTX" + "TN7EQXMxKffzvHo54yHHu9R4ks9f5gWBW3MMVf5k72zAqrgVK9ys" ], [ - "4ctAH6AkHzq5ioiM1m9T3E2hiYEev5mTsB" + "92dbrMEYzP5dD5UhQ6maNkCQ4GLG42BM4Gc6XKZzSSMSfosfkkcB" ], [ - "Hn1uFi4dNexWrqARpjMqgT6cX1UsNPuV3cHdGg9ExyXw8HTKadbktRDtdeVmY3M1BxJStiL4vjJ" + "J7VQxPxyzuWEkRstQWpCz2AgysEz1APgnWCEQrFvkN3umAnCrhQF" ], [ - "Sq3fDbvutABmnAHHExJDgPLQn44KnNC7UsXuT7KZecpaYDMU9Txs" + "tc1qymllj6c96v5qj2504y27ldtner6eh8ldx38t83" ], [ - "6TqWyrqdgUEYDQU1aChMuFMMEimHX44qHFzCUgGfqxGgZNMUVWJ" + "bt1flep4g" ], [ - "giqJo7oWqFxNKWyrgcBxAVHXnjJ1t6cGoEffce5Y1y7u649Noj5wJ4mmiUAKEVVrYAGg2KPB3Y4" + "tb13c553hwygcgj48qwmr9f8q0hgdcfklyaye5sxzcpcjnmxv4z506xs90tchn" ], [ - "cNzHY5e8vcmM3QVJUcjCyiKMYfeYvyueq5qCMV3kqcySoLyGLYUK" + "bcrt1tyddyu" ], [ - "37uTe568EYc9WLoHEd9jXEvUiWbq5LFLscNyqvAzLU5vBArUJA6eydkLmnMwJDjkL5kXc2VK7ig" + "bc10qssq2mknjqf0glwe2f3587wc4jysvs3f8s6chysae6hcl6fxzdm4wxyyscrl5k9f5qmnf05a" ], [ - "EsYbG4tWWWY45G31nox838qNdzksbPySWc" + "tb1q425lmgvxdgtyl2m6xuu2pc354y4fvgg8" ], [ - "nbuzhfwMoNzA3PaFnyLcRxE9bTJPDkjZ6Rf6Y6o2ckXZfzZzXBT" + "bcrt1q9wp8e5d2u3u4g0pll0cy7smeeuqezdun9xl439n3p2gg4fvgfvk3hu52hj" ], [ - "cQN9PoxZeCWK1x56xnz6QYAsvR11XAce3Ehp3gMUdfSQ53Y2mPzx" + "bc1qrz5acazpue8vl4zsaxn8fxtmeuqmyjkq3" ], [ - "1Gm3N3rkef6iMbx4voBzaxtXcmmiMTqZPhcuAepRzYUJQW4qRpEnHvMojzof42hjFRf8PE2jPde" + "tb1qkeuglpgmnex9tv3fr7htzfrh3rwrk23r52rx9halxzmv9fr85lwq0fwhmp" ], [ - "2TAq2tuN6x6m233bpT7yqdYQPELdTDJn1eU" + "bcrt1qd0t2wrhl7s57z99rsyaekpq0dyjcQRSSmz80r4" ], [ - "ntEtnnGhqPii4joABvBtSEJG6BxjT2tUZqE8PcVYgk3RHpgxgHDCQxNbLJf7ardf1dDk2oCQ7Cf" + "BC1QXLFDUCGX90T3E53PQCNKJ2PK25MSF3VLPMVY6T" ], [ - "Ky1YjoZNgQ196HJV3HpdkecfhRBmRZdMJk89Hi5KGfpfPwS2bUbfd" + "tb1qmycg4zszgnk34vaurx3cu8wpvteg9h40yq6cp52gt26gjel03t3su3x3xu" ], [ - "2A1q1YsMZowabbvta7kTy2Fd6qN4r5ZCeG3qLpvZBMzCixMUdkN2Y4dHB1wPsZAeVXUGD83MfRED" + "bcrt1q9hy58r4fnuxqzdqndpmq9pptc9nt2dw3rczf5e" ], [ - "tc1qw508d6qejxtdg4y5r3zarvary0c5xw7kg3g4ty" + "BC1PA7682NAY6JQSLUWAJYTC0ERWTMW7A4RPWLNTUS32LCXWLHVKKKTQ2UL8CG" ], [ - "bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t5" + "tb1z850dpxnwz2fzae5h2myatj4yvu6rq5xq" ], [ - "BC13W508D6QEJXTDG4Y5R3ZARVARY0C5XW7KN40WF2" + "bcrt1sp525pzjsmpqvcrawjreww36e9keg876skjvpwt" ], [ - "bc1rw5uspcuh" + "xcAvW5jurCpzSpLxBKEhCewCgwwuGhqJnC" ], [ - "bc10w508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7kw5rljs90" + "2Cvv8yp9kXbQt8EKh6Yma95yJ1uwYF9YKXuVhGJyu3dHGVsb2AVpTC62TFACZZ3KDNrALxR2CVNs" ], [ - "BC1QR508D6QEJXTDG4Y5R3ZARVARYV98GJ9P" + "niUuL46hCuEVvkAzZKHvD746qbmLmzip9Pv3F6UZV14JxzEXBnTkVxCT4URapChJG6qAEgsZs6G" ], [ - "tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sL5k7" + "2UHHgGfiipzvB8Eumnmvq6SowvrMJimjT3NwwG1839XEiUfwtpSdkUrseNsQuagXv21ce7aZu6yo" ], [ - "bc1zw508d6qejxtdg4y5r3zarvaryvqyzf3du" + "8u9djKu4u6o3bsgeR4BKNnLK3akpo64FYzDAmA9239wKeshgF97" ], [ - "tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3pjxtptv" + "TC1QPAARXSLVMXHVRR0474LZXQYZWLGFZYPSFVL9E4" ], [ - "bc1gmk9yu" + "bt1pakek0n2267t9yaksxaczgr2syhv9y3xkx0wnsdwchfa6xkmjtvuqg3kgyr" + ], + [ + "tb13h83rtwq62udrhwpn87uely7cyxcjrj0azz6a4r3n9s87x5uj98ys6ufp83" + ], + [ + "bcrt1rk5vw5qf2" + ], + [ + "bc10d3rmtg62h747en5j6fju5g5qyvsransrkty6ghh96pu647wumctejlsngh9pf26cysrys2x2" + ], + [ + "tb1qajuy2cdwqgmrzc7la85al5cwcq374tsp" + ], + [ + "bcrt1q3udxvj6x20chqh723mn064mzz65yr56ef00xk8czvu3jnx04ydapzk02s5" + ], + [ + "bc1qule2szwzyaq4qy0s3aa4mauucyqt6fewe" + ], + [ + "tb1ql0qny5vg9gh5tyzke6dw36px5ulkrp24x53x0pl2t5lpwrtejw3s2seej2" + ], + [ + "bcrt17CAPP7" + ], + [ + "bc1qtvm6davyf725wfedc2d5mrgfewqgcrce8gjrpl" + ], + [ + "tb1q5acjgtqrrw3an0dzavxxxzlex8k7aukjzjk9v2u4rmfdqxjphcyq7ge97e" ] ] diff --git a/src/test/data/key_io_valid.json b/src/test/data/key_io_valid.json index 8418a6002d..5dee44c04b 100644 --- a/src/test/data/key_io_valid.json +++ b/src/test/data/key_io_valid.json @@ -1,533 +1,610 @@ [ [ - "1AGNa15ZQXAZUgFiqJ2i7Z2DPU2J6hW62i", - "76a91465a16059864a2fdbc7c99a4723a8395bc6f188eb88ac", + "1BShJZ8A5q53oJJfMJoEF1gfZCWdZqZwwD", + "76a914728d4cc27d19707b0197cfcd7c412d43287864b588ac", { - "isPrivkey": false, - "chain": "main" + "chain": "main", + "isPrivkey": false } ], [ - "3CMNFxN1oHBc4R1EpboAL5yzHGgE611Xou", - "a91474f209f6ea907e2ea48f74fae05782ae8a66525787", + "3L1YkZjdeNSqaZcNKZFXQfyokx3zVYm7r6", + "a914c8f37c3cc21561296ad81f4bec6b5de10ebc185187", { - "isPrivkey": false, - "chain": "main" + "chain": "main", + "isPrivkey": false } ], [ - "mo9ncXisMeAoXwqcV5EWuyncbmCcQN4rVs", - "76a91453c0307d6851aa0ce7825ba883c6bd9ad242b48688ac", + "mhJuoGLgnJC8gdBgBzEigsoyG4omQXejPT", + "76a91413a92d1998e081354d36c13ce0c9dc04b865d40a88ac", { - "isPrivkey": false, - "chain": "test" + "chain": "test", + "isPrivkey": false } ], [ - "mo9ncXisMeAoXwqcV5EWuyncbmCcQN4rVs", - "76a91453c0307d6851aa0ce7825ba883c6bd9ad242b48688ac", + "2N5VpzKEuYvZJbmg6eUNGnfrrD1ir92FWGu", + "a91486648cc2faaf05660e72c04c7a837bcc3e986f1787", { - "isPrivkey": false, - "chain": "regtest" + "chain": "test", + "isPrivkey": false } ], [ - "2N2JD6wb56AfK4tfmM6PwdVmoYk2dCKf4Br", - "a9146349a418fc4578d10a372b54b45c280cc8c4382f87", + "mtQueCtmAnP3E4aBHXCiFNEQAuPaLMuQNy", + "76a9148d74ecd86c845baf9c6d4484d2d00e731b79e34788ac", { - "isPrivkey": false, - "chain": "test" + "chain": "signet", + "isPrivkey": false + } + ], + [ + "2NEvWRTHjh89gV52fkperFtwzoFWQiQmiCh", + "a914edc895152c67ccff0ba620bcc373b789ec68266f87", + { + "chain": "signet", + "isPrivkey": false } ], [ - "5Kd3NBUAdUnhyzenEwVLy9pBKxSwXvE9FMPyR4UKZvpe6E3AgLr", - "eddbdc1168f1daeadbd3e44c1e3f8f5a284c2029f78ad26af98583a499de5b19", + "mngdx94qJFhSf7A7SAEgQSC9fQJuapujJp", + "76a9144e9dba545455a80ce94c343d1cac9dec62cbf22288ac", { + "chain": "regtest", + "isPrivkey": false + } + ], + [ + "2NBzRN3pV56k3JUvSHifaHyzjGHv7ZS9FZZ", + "a914cd9da5642451273e5b6d088854cc1fad4a8d442187", + { + "chain": "regtest", + "isPrivkey": false + } + ], + [ + "5KcrFZvJ2p4dM6QVUPu53cKXcCfozA1PJLHm1mNAxkDYhgThLu4", + "ed6c796e2f62377410766214f55aa81ac9a6590ad7ed57c509c983bf648409ac", + { + "chain": "main", "isCompressed": false, - "isPrivkey": true, - "chain": "main" + "isPrivkey": true } ], [ - "Kz6UJmQACJmLtaQj5A3JAge4kVTNQ8gbvXuwbmCj7bsaabudb3RD", - "55c9bccb9ed68446d1b75273bbce89d7fe013a8acd1625514420fb2aca1a21c4", + "L195WBrf2G3nCnun4CLxrb8XKk9LbCqH43THh4n4QrL5SzRzpq9j", + "74f76c106e38d20514a99a86e4fe3bb28319e7dd2ad21dbc170cbb516a5358fa", { + "chain": "main", "isCompressed": true, - "isPrivkey": true, - "chain": "main" + "isPrivkey": true } ], [ - "9213qJab2HNEpMpYNBa7wHGFKKbkDn24jpANDs2huN3yi4J11ko", - "36cb93b9ab1bdabf7fb9f2c04f1b9cc879933530ae7842398eef5a63a56800c2", + "92z6HnMQR4tWqjfVA3UaUN5EuUMgoVMdCa5rZFYZfmgyD7wxYCw", + "b8511e1d74549e305517d48a1d394d1be2cfa5d0f3c0d83f9f450316ffa01276", { + "chain": "test", "isCompressed": false, - "isPrivkey": true, - "chain": "test" + "isPrivkey": true } ], [ - "9213qJab2HNEpMpYNBa7wHGFKKbkDn24jpANDs2huN3yi4J11ko", - "36cb93b9ab1bdabf7fb9f2c04f1b9cc879933530ae7842398eef5a63a56800c2", + "cTPnaF52x4w4Tq6afPxRHux3wbYb86thS7S45A7r3oZc1AHTQ6Qm", + "ad68c48d337181da125de9061933ececcdf7d917631af7d34f7e38082bff9a11", { + "chain": "test", + "isCompressed": true, + "isPrivkey": true + } + ], + [ + "924U35yFcYkxe2JXGmuhSRVaShGyhRDZx1ysPmw1sAHuszGMoxq", + "3e8dfaf78d4f02b11d0b645648a4f3080d71d0d068979c47f7255c9a29eee01d", + { + "chain": "signet", "isCompressed": false, - "isPrivkey": true, - "chain": "regtest" + "isPrivkey": true } ], [ - "cTpB4YiyKiBcPxnefsDpbnDxFDffjqJob8wGCEDXxgQ7zQoMXJdH", - "b9f4892c9e8282028fea1d2667c4dc5213564d41fc5783896a0d843fc15089f3", + "cRy1qCf2LUesGPQagTkYwk2V3PyN2KCPKgxeg6k6KoJPzH7nrVjw", + "82d4187690d6b59bcffda27dae52f2ecb87313cfc0904e0b674a27d906a65fde", { + "chain": "signet", "isCompressed": true, - "isPrivkey": true, - "chain": "test" + "isPrivkey": true } ], [ - "cTpB4YiyKiBcPxnefsDpbnDxFDffjqJob8wGCEDXxgQ7zQoMXJdH", - "b9f4892c9e8282028fea1d2667c4dc5213564d41fc5783896a0d843fc15089f3", + "932NTcHK35Apf2C3K9Zv1ZdeZEmB1x7ZT2Ju3SjoEY6pUgUpT7H", + "bd7dba24df9e003e145ae9b4862776413a0bb6fa5b4e42753397f2d9536e58a9", { - "isCompressed": true, - "isPrivkey": true, - "chain": "regtest" + "chain": "regtest", + "isCompressed": false, + "isPrivkey": true } ], [ - "1Ax4gZtb7gAit2TivwejZHYtNNLT18PUXJ", - "76a9146d23156cbbdcc82a5a47eee4c2c7c583c18b6bf488ac", + "cNa75orYQ2oos52zCnMaS5PG6XbNZKc5LpGxTHacrxwWeX4WAK3E", + "1d87e3c58b08766fea03598380ec8d59f8c88d5392bf683ab1088bd4caf073ee", { - "isPrivkey": false, - "chain": "main" + "chain": "regtest", + "isCompressed": true, + "isPrivkey": true } ], [ - "3QjYXhTkvuj8qPaXHTTWb5wjXhdsLAAWVy", - "a914fcc5460dd6e2487c7d75b1963625da0e8f4c597587", + "bc1q5cuatynjmk4szh40mmunszfzh7zrc5xm9w8ccy", + "0014a639d59272ddab015eafdef9380922bf843c50db", { + "chain": "main", "isPrivkey": false, - "chain": "main" + "tryCaseFlip": true } ], [ - "n3ZddxzLvAY9o7184TB4c6FJasAybsw4HZ", - "76a914f1d470f9b02370fdec2e6b708b08ac431bf7a5f788ac", + "bc1qkw7lz3ahms6e0ajv27mzh7g62tchjpmve4afc29u7w49tddydy2syv0087", + "0020b3bdf147b7dc3597f64c57b62bf91a52f179076ccd7a9c28bcf3aa55b5a46915", { + "chain": "main", "isPrivkey": false, - "chain": "test" + "tryCaseFlip": true } ], [ - "2NBFNJTktNa7GZusGbDbGKRZTxdK9VVez3n", - "a914c579342c2c4c9220205e2cdc285617040c924a0a87", + "bc1p5rgvqejqh9dh37t9g94dd9cm8vtqns7dndgj423egwggsggcdzmsspvr7j", + "5120a0d0c06640b95b78f965416ad6971b3b1609c3cd9b512aaa39439088211868b7", { + "chain": "main", "isPrivkey": false, - "chain": "test" + "tryCaseFlip": true } ], [ - "5K494XZwps2bGyeL71pWid4noiSNA2cfCibrvRWqcHSptoFn7rc", - "a326b95ebae30164217d7a7f57d72ab2b54e3be64928a19da0210b9568d4015e", + "bc1zr4pq63udck", + "52021d42", { - "isCompressed": false, - "isPrivkey": true, - "chain": "main" + "chain": "main", + "isPrivkey": false, + "tryCaseFlip": true } ], [ - "L1RrrnXkcKut5DEMwtDthjwRcTTwED36thyL1DebVrKuwvohjMNi", - "7d998b45c219a1e38e99e7cbd312ef67f77a455a9b50c730c27f02c6f730dfb4", + "tb1q74fxwnvhsue0l8wremgq66xzvn48jlc5zthsvz", + "0014f552674d978732ff9dc3ced00d68c264ea797f14", { - "isCompressed": true, - "isPrivkey": true, - "chain": "main" + "chain": "test", + "isPrivkey": false, + "tryCaseFlip": true } ], [ - "93DVKyFYwSN6wEo3E2fCrFPUp17FtrtNi2Lf7n4G3garFb16CRj", - "d6bca256b5abc5602ec2e1c121a08b0da2556587430bcf7e1898af2224885203", + "tb1qpt7cqgq8ukv92dcraun9c3n0s3aswrt62vtv8nqmkfpa2tjfghesv9ln74", + "00200afd802007e598553703ef265c466f847b070d7a5316c3cc1bb243d52e4945f3", { - "isCompressed": false, - "isPrivkey": true, - "chain": "test" + "chain": "test", + "isPrivkey": false, + "tryCaseFlip": true } ], [ - "cTDVKtMGVYWTHCb1AFjmVbEbWjvKpKqKgMaR3QJxToMSQAhmCeTN", - "a81ca4e8f90181ec4b61b6a7eb998af17b2cb04de8a03b504b9e34c4c61db7d9", + "tb1ph9v3e8nxct57hknlkhkz75p5pnxnkn05cw8ewpxu6tek56g29xgqydzfu7", + "5120b9591c9e66c2e9ebda7fb5ec2f50340ccd3b4df4c38f9704dcd2f36a690a2990", { - "isCompressed": true, - "isPrivkey": true, - "chain": "test" + "chain": "test", + "isPrivkey": false, + "tryCaseFlip": true } ], [ - "1C5bSj1iEGUgSTbziymG7Cn18ENQuT36vv", - "76a9147987ccaa53d02c8873487ef919677cd3db7a691288ac", + "tb1ray6e8gxfx49ers6c4c70l3c8lsxtcmlx", + "5310e93593a0c9354b91c358ae3cffc707fc", { + "chain": "test", "isPrivkey": false, - "chain": "main" + "tryCaseFlip": true } ], [ - "3AnNxabYGoTxYiTEZwFEnerUoeFXK2Zoks", - "a91463bcc565f9e68ee0189dd5cc67f1b0e5f02f45cb87", + "tb1q0sqzfp3zj42u0perxr6jahhu4y03uw4dypk6sc", + "00147c002486229555c7872330f52edefca91f1e3aad", { + "chain": "signet", "isPrivkey": false, - "chain": "main" + "tryCaseFlip": true } ], [ - "n3LnJXCqbPjghuVs8ph9CYsAe4Sh4j97wk", - "76a914ef66444b5b17f14e8fae6e7e19b045a78c54fd7988ac", + "tb1q9jv4qnawnuevqaeadn47gkq05ev78m4qg3zqejykdr9u0cm7yutq6gu5dj", + "00202c99504fae9f32c0773d6cebe4580fa659e3eea044440cc89668cbc7e37e2716", { + "chain": "signet", "isPrivkey": false, - "chain": "test" + "tryCaseFlip": true } ], [ - "2NB72XtkjpnATMggui83aEtPawyyKvnbX2o", - "a914c3e55fceceaa4391ed2a9677f4a4d34eacd021a087", + "tb1pxqf7d825wjtcftj7uep8w24jq3tz8vudfaqj20rns8ahqya56gcs92eqtu", + "51203013e69d54749784ae5ee642772ab2045623b38d4f41253c7381fb7013b4d231", { + "chain": "signet", "isPrivkey": false, - "chain": "test" + "tryCaseFlip": true } ], [ - "5KaBW9vNtWNhc3ZEDyNCiXLPdVPHCikRxSBWwV9NrpLLa4LsXi9", - "e75d936d56377f432f404aabb406601f892fd49da90eb6ac558a733c93b47252", + "tb1rsrzkyvu2rt0dcgexajtazlw5nft4j7494ay396q6auw9375wxsrsgag884", + "532080c562338a1adedc2326ec97d17dd49a57597aa5af4912e81aef1c58fa8e3407", { - "isCompressed": false, - "isPrivkey": true, - "chain": "main" + "chain": "signet", + "isPrivkey": false, + "tryCaseFlip": true } ], [ - "L1axzbSyynNYA8mCAhzxkipKkfHtAXYF4YQnhSKcLV8YXA874fgT", - "8248bd0375f2f75d7e274ae544fb920f51784480866b102384190b1addfbaa5c", + "bcrt1qwf52dt9y2sv0f7fwkcpmtfjf74d4np2saeljt6", + "00147268a6aca45418f4f92eb603b5a649f55b598550", { - "isCompressed": true, - "isPrivkey": true, - "chain": "main" + "chain": "regtest", + "isPrivkey": false, + "tryCaseFlip": true } ], [ - "927CnUkUbasYtDwYwVn2j8GdTuACNnKkjZ1rpZd2yBB1CLcnXpo", - "44c4f6a096eac5238291a94cc24c01e3b19b8d8cef72874a079e00a242237a52", + "bcrt1q0lma84unycxl4n96etffthqlf7y5axyp4fxf64kmhymvw8l6pwfs39futd", + "00207ff7d3d793260dfaccbacad295dc1f4f894e9881aa4c9d56dbb936c71ffa0b93", { - "isCompressed": false, - "isPrivkey": true, - "chain": "test" + "chain": "regtest", + "isPrivkey": false, + "tryCaseFlip": true } ], [ - "cUcfCMRjiQf85YMzzQEk9d1s5A4K7xL5SmBCLrezqXFuTVefyhY7", - "d1de707020a9059d6d3abaf85e17967c6555151143db13dbb06db78df0f15c69", + "bcrt1p3xat2ryucc2v0adrktqnavfzttvezrr27ngltsa2726p2ehvxz4se722v2", + "512089bab50c9cc614c7f5a3b2c13eb1225ad9910c6af4d1f5c3aaf2b41566ec30ab", { - "isCompressed": true, - "isPrivkey": true, - "chain": "test" + "chain": "regtest", + "isPrivkey": false, + "tryCaseFlip": true } ], [ - "1Gqk4Tv79P91Cc1STQtU3s1W6277M2CVWu", - "76a914adc1cc2081a27206fae25792f28bbc55b831549d88ac", + "bcrt1saflydw6e26xhp29euhy5jke5jjqyywk3wvtc9ulgw9dvxyuqy9hdnxthyw755c7ldavy7u", + "6028ea7e46bb59568d70a8b9e5c9495b349480423ad1731782f3e8715ac31380216ed9997723bd4a63df", { + "chain": "regtest", "isPrivkey": false, - "chain": "main" + "tryCaseFlip": true } ], [ - "33vt8ViH5jsr115AGkW6cEmEz9MpvJSwDk", - "a914188f91a931947eddd7432d6e614387e32b24470987", + "16y3Q1XVRZqMR9T1XL1FkvNtD2E1bXBuYa", + "76a9144171ec673aeb9fcf42af6094a3c82207e3b9a78188ac", { - "isPrivkey": false, - "chain": "main" + "chain": "main", + "isPrivkey": false } ], [ - "mhaMcBxNh5cqXm4aTQ6EcVbKtfL6LGyK2H", - "76a9141694f5bc1a7295b600f40018a618a6ea48eeb49888ac", + "3CmZZnAiHVQgiAKSakf864oJMxN2BP1eLC", + "a914798575fc1041b9440c4e63c28e57e597d00b7e4387", { - "isPrivkey": false, - "chain": "test" + "chain": "main", + "isPrivkey": false } ], [ - "2MxgPqX1iThW3oZVk9KoFcE5M4JpiETssVN", - "a9143b9b3fd7a50d4f08d1a5b0f62f644fa7115ae2f387", + "mtCB3SoBo7EYUv8j54kUubGY4x3aJPY8nk", + "76a9148b0c5f9ee714e0d1d24642ad63d9d5f398d9b56588ac", { - "isPrivkey": false, - "chain": "test" + "chain": "test", + "isPrivkey": false } ], [ - "5HtH6GdcwCJA4ggWEL1B3jzBBUB8HPiBi9SBc5h9i4Wk4PSeApR", - "091035445ef105fa1bb125eccfb1882f3fe69592265956ade751fd095033d8d0", + "2N5ymzzKpx6EdUR4UdMZ7t9hcuwqtpHwgw5", + "a9148badb3c3b5c0d39f906f7618e0018b7eae4baf7387", { - "isCompressed": false, - "isPrivkey": true, - "chain": "main" + "chain": "test", + "isPrivkey": false } ], [ - "L2xSYmMeVo3Zek3ZTsv9xUrXVAmrWxJ8Ua4cw8pkfbQhcEFhkXT8", - "ab2b4bcdfc91d34dee0ae2a8c6b6668dadaeb3a88b9859743156f462325187af", + "myXnpYbub28zgiJupDdZSWZtDbjcyfJVby", + "76a914c59ac57661b57daadd7c0caf7318c14f54c6c0fa88ac", { - "isCompressed": true, - "isPrivkey": true, - "chain": "main" + "chain": "signet", + "isPrivkey": false } ], [ - "92xFEve1Z9N8Z641KQQS7ByCSb8kGjsDzw6fAmjHN1LZGKQXyMq", - "b4204389cef18bbe2b353623cbf93e8678fbc92a475b664ae98ed594e6cf0856", + "2MtLg8jS5jSXm9evMzTtvpLjy26dBmjFEoT", + "a9140c0007e89cea625d3bf9543baa5a470bb7e5b67287", { - "isCompressed": false, - "isPrivkey": true, - "chain": "test" + "chain": "signet", + "isPrivkey": false } ], [ - "92xFEve1Z9N8Z641KQQS7ByCSb8kGjsDzw6fAmjHN1LZGKQXyMq", - "b4204389cef18bbe2b353623cbf93e8678fbc92a475b664ae98ed594e6cf0856", + "mzCyqdf2UNGdpgkD9NBgLcxdwXRg1i9buY", + "76a914cd04311bdd1ef9c5c24e41930e032aade82a863a88ac", { - "isCompressed": false, - "isPrivkey": true, - "chain": "regtest" + "chain": "regtest", + "isPrivkey": false } ], [ - "cVM65tdYu1YK37tNoAyGoJTR13VBYFva1vg9FLuPAsJijGvG6NEA", - "e7b230133f1b5489843260236b06edca25f66adb1be455fbd38d4010d48faeef", + "2N3zGiwFku2vQjYnAqXv5Qu2ztfYRhh7tbF", + "a91475d56d75c88e704d6c72fbe84ac1505abf736b4087", { - "isCompressed": true, - "isPrivkey": true, - "chain": "test" + "chain": "regtest", + "isPrivkey": false } ], [ - "1JwMWBVLtiqtscbaRHai4pqHokhFCbtoB4", - "76a914c4c1b72491ede1eedaca00618407ee0b772cad0d88ac", + "5JUHCgyxNSHg64wwju72eNsG6ajqo4Z2fHHw9iLDLfh69rSiL7w", + "5644d06d88855dacf3192a31df8f4acd8e4c155c52a86d2c1fa48303f5cff053", { - "isPrivkey": false, - "chain": "main" + "chain": "main", + "isCompressed": false, + "isPrivkey": true } ], [ - "3QCzvfL4ZRvmJFiWWBVwxfdaNBT8EtxB5y", - "a914f6fe69bcb548a829cce4c57bf6fff8af3a5981f987", + "L2kZaexG69VSriMe9T2m1jkS86iPe3xNbjcdfakRC1PHe7ay78Ji", + "a50ee94aefcabf5a5d7c85be5b3844dee03c5604861dbfc77fe388c91e5a30f8", { - "isPrivkey": false, - "chain": "main" + "chain": "main", + "isCompressed": true, + "isPrivkey": true } ], [ - "mizXiucXRCsEriQCHUkCqef9ph9qtPbZZ6", - "76a914261f83568a098a8638844bd7aeca039d5f2352c088ac", + "927JwT1ViCr5TD2ZX8CsMNhg17dXmou5xu4y2KiH54zD7i34UJq", + "4502a54c0026b0150281d41f40860d1e23870c63cdc32645bbed688f2ee41f64", { - "isPrivkey": false, - "chain": "test" + "chain": "test", + "isCompressed": false, + "isPrivkey": true } ], [ - "2NEWDzHWwY5ZZp8CQWbB7ouNMLqCia6YRda", - "a914e930e1834a4d234702773951d627cce82fbb5d2e87", + "cTpGGNPVy2Eagawohbr4aGtRJzpLnjxGsGYh9DUcBM45f3KdKGF6", + "ba005a0cb39587aab00bd54c848b59e8adaed47403228567ddc739c2a344ff59", { - "isPrivkey": false, - "chain": "test" + "chain": "test", + "isCompressed": true, + "isPrivkey": true } ], [ - "5KQmDryMNDcisTzRp3zEq9e4awRmJrEVU1j5vFRTKpRNYPqYrMg", - "d1fab7ab7385ad26872237f1eb9789aa25cc986bacc695e07ac571d6cdac8bc0", + "932PLCLA19yPNqV67qwHBSGjxi82LVzWBF7josL9ab4Q1kxgPGF", + "bd8677e076eb39770bf7e9f9e8d3f2cf257effab9b4c220fd3439ccfc208c984", { + "chain": "signet", "isCompressed": false, - "isPrivkey": true, - "chain": "main" + "isPrivkey": true } ], [ - "L39Fy7AC2Hhj95gh3Yb2AU5YHh1mQSAHgpNixvm27poizcJyLtUi", - "b0bbede33ef254e8376aceb1510253fc3550efd0fcf84dcd0c9998b288f166b3", + "cViUpEy8URSsLjUvxwL7cEuNgCVqM7oKBzd1ZPbA4khcQsQJuj1j", + "f2b36ade8393e29dc71e52cb75ba1109ba210203cd7d0a5ae881ad6846516203", { + "chain": "signet", "isCompressed": true, - "isPrivkey": true, - "chain": "main" + "isPrivkey": true } ], [ - "91cTVUcgydqyZLgaANpf1fvL55FH53QMm4BsnCADVNYuWuqdVys", - "037f4192c630f399d9271e26c575269b1d15be553ea1a7217f0cb8513cef41cb", + "92jddDjJCVDmJtgvBHQ9i58PMash8kwsYhRdNo22ea2MYPXdCBE", + "977bf8686f1bcad28f86c4c14afbd33215746bd19203647bf7ff9c6fddc9cc87", { + "chain": "regtest", "isCompressed": false, - "isPrivkey": true, - "chain": "test" + "isPrivkey": true } ], [ - "cQspfSzsgLeiJGB2u8vrAiWpCU4MxUT6JseWo2SjXy4Qbzn2fwDw", - "6251e205e8ad508bab5596bee086ef16cd4b239e0cc0c5d7c4e6035441e7d5de", + "cVwAuMoUqRo399X7vXzuzQyPEvZJMXM8c82zHzRkFCxPCSGx8A6y", + "f93acbbce02b8cb9ddca3fad495441e324cc01eb640b0a7b4c9f0e31644c822a", { + "chain": "regtest", "isCompressed": true, - "isPrivkey": true, - "chain": "test" + "isPrivkey": true } ], [ - "19dcawoKcZdQz365WpXWMhX6QCUpR9SY4r", - "76a9145eadaf9bb7121f0f192561a5a62f5e5f5421029288ac", + "bc1qz377zwe5awr68dnggengqx9vrjt05k98q3sw2n", + "0014147de13b34eb87a3b66846668018ac1c96fa58a7", { + "chain": "main", "isPrivkey": false, - "chain": "main" + "tryCaseFlip": true } ], [ - "37Sp6Rv3y4kVd1nQ1JV5pfqXccHNyZm1x3", - "a9143f210e7277c899c3a155cc1c90f4106cbddeec6e87", + "bc1qkmhskpdzg8kdkfywhu09kswwn9qan9vnkrf6mk40jvnr06s6sz5ssf82ya", + "0020b6ef0b05a241ecdb248ebf1e5b41ce9941d99593b0d3addaaf932637ea1a80a9", { + "chain": "main", "isPrivkey": false, - "chain": "main" + "tryCaseFlip": true } ], [ - "myoqcgYiehufrsnnkqdqbp69dddVDMopJu", - "76a914c8a3c2a09a298592c3e180f02487cd91ba3400b588ac", + "bc1ps8cndas60cntk8x79sg9f5e5jz7x050z8agyugln2ukkks23rryqpejzkc", + "512081f136f61a7e26bb1cde2c1054d33490bc67d1e23f504e23f3572d6b415118c8", { + "chain": "main", "isPrivkey": false, - "chain": "test" + "tryCaseFlip": true } ], [ - "2N7FuwuUuoTBrDFdrAZ9KxBmtqMLxce9i1C", - "a91499b31df7c9068d1481b596578ddbb4d3bd90baeb87", + "bc1zn4tsczge9l", + "52029d57", { + "chain": "main", "isPrivkey": false, - "chain": "test" + "tryCaseFlip": true } ], [ - "5KL6zEaMtPRXZKo1bbMq7JDjjo1bJuQcsgL33je3oY8uSJCR5b4", - "c7666842503db6dc6ea061f092cfb9c388448629a6fe868d068c42a488b478ae", + "tb1q6xw0wwd9n9d7ge87dryz4vm5vtahzhvz6yett3", + "0014d19cf739a5995be464fe68c82ab37462fb715d82", { - "isCompressed": false, - "isPrivkey": true, - "chain": "main" + "chain": "test", + "isPrivkey": false, + "tryCaseFlip": true } ], [ - "KwV9KAfwbwt51veZWNscRTeZs9CKpojyu1MsPnaKTF5kz69H1UN2", - "07f0803fc5399e773555ab1e8939907e9badacc17ca129e67a2f5f2ff84351dd", + "tb1qwn9zq9fu5uk35ykpgsc7rz4uawy4yh0r5m5er26768h5ur50su3qj6evun", + "002074ca20153ca72d1a12c14431e18abceb89525de3a6e991ab5ed1ef4e0e8f8722", { - "isCompressed": true, - "isPrivkey": true, - "chain": "main" + "chain": "test", + "isPrivkey": false, + "tryCaseFlip": true } ], [ - "93N87D6uxSBzwXvpokpzg8FFmfQPmvX4xHoWQe3pLdYpbiwT5YV", - "ea577acfb5d1d14d3b7b195c321566f12f87d2b77ea3a53f68df7ebf8604a801", + "tb1pmcdc5d8gr92rtemfsnhpeqanvs0nr82upn5dktxluz9n0qcv34lqxke0wq", + "5120de1b8a34e8195435e76984ee1c83b3641f319d5c0ce8db2cdfe08b37830c8d7e", { - "isCompressed": false, - "isPrivkey": true, - "chain": "test" + "chain": "test", + "isPrivkey": false, + "tryCaseFlip": true } ], [ - "cMxXusSihaX58wpJ3tNuuUcZEQGt6DKJ1wEpxys88FFaQCYjku9h", - "0b3b34f0958d8a268193a9814da92c3e8b58b4a4378a542863e34ac289cd830c", + "tb1rgxjvtfzp0xczz6dlzqv8d5cmuykk4qkk", + "531041a4c5a44179b02169bf101876d31be1", { - "isCompressed": true, - "isPrivkey": true, - "chain": "test" + "chain": "test", + "isPrivkey": false, + "tryCaseFlip": true } ], [ - "13p1ijLwsnrcuyqcTvJXkq2ASdXqcnEBLE", - "76a9141ed467017f043e91ed4c44b4e8dd674db211c4e688ac", + "tb1qa9dlyt6fydestul4y4wh72yshh044w32np8etk", + "0014e95bf22f49237305f3f5255d7f2890bddf5aba2a", { + "chain": "signet", "isPrivkey": false, - "chain": "main" + "tryCaseFlip": true } ], [ - "3ALJH9Y951VCGcVZYAdpA3KchoP9McEj1G", - "a9145ece0cadddc415b1980f001785947120acdb36fc87", + "tb1qu4p26n0033720xm0rjgkds5ehdwf039k2fgv75um5krrvfhrrj7qckl9r2", + "0020e542ad4def8c7ca79b6f1c9166c299bb5c97c4b65250cf539ba5863626e31cbc", { + "chain": "signet", "isPrivkey": false, - "chain": "main" + "tryCaseFlip": true } ], [ - "bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4", - "0014751e76e8199196d454941c45d1b3a323f1433bd6", + "tb1pjyukm4n4flwd0ey3lrl06c9kalr60ggmlkcxq2rhhxmy4lvkmkpqexdzqy", + "512091396dd6754fdcd7e491f8fefd60b6efc7a7a11bfdb0602877b9b64afd96dd82", { + "chain": "signet", "isPrivkey": false, - "chain": "main", "tryCaseFlip": true } ], [ - "bcrt1qw508d6qejxtdg4y5r3zarvary0c5xw7kygt080", - "0014751e76e8199196d454941c45d1b3a323f1433bd6", + "tb1r4k75s5syvewsvxufdc3xfhf4tw4u30alw39xny3dnxrl6hau7systymfdv", + "5320adbd485204665d061b896e2264dd355babc8bfbf744a69922d9987fd5fbcf409", { + "chain": "signet", "isPrivkey": false, - "chain": "regtest", "tryCaseFlip": true } ], [ - "tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sl5k7", - "00201863143c14c5166804bd19203356da136c985678cd4d27a1b8c6329604903262", + "bcrt1qnk3tdwwj47ppc4pqmxkjdusegedn9ru5gvccwa", + "00149da2b6b9d2af821c5420d9ad26f219465b328f94", { + "chain": "regtest", "isPrivkey": false, - "chain": "test", "tryCaseFlip": true } ], [ - "bc1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7k7grplx", - "5128751e76e8199196d454941c45d1b3a323f1433bd6751e76e8199196d454941c45d1b3a323f1433bd6", + "bcrt1qz7prfshfkwsxuk72pt6mzr6uumq4qllxe4vmwqt89tat48d362yqlykk6a", + "0020178234c2e9b3a06e5bca0af5b10f5ce6c1507fe6cd59b701672afaba9db1d288", { + "chain": "regtest", "isPrivkey": false, - "chain": "main", "tryCaseFlip": true } ], [ - "bc1sw50qa3jx3s", - "6002751e", + "bcrt1pumee3wj80xvyr7wjmj7zsk26x5pn095aegy862yhx6f2j9sgc9hq6cj4cm", + "5120e6f398ba47799841f9d2dcbc28595a350337969dca087d28973692a91608c16e", { + "chain": "regtest", "isPrivkey": false, - "chain": "main", "tryCaseFlip": true } ], [ - "bc1zw508d6qejxtdg4y5r3zarvaryvg6kdaj", - "5210751e76e8199196d454941c45d1b3a323", + "bcrt1szqz8hj64d2hhc6nt65v09jxal66pgff2xpcp9kj648qkk8kjzxelsts4dktd799g47uase", + "602810047bcb556aaf7c6a6bd518f2c8ddfeb414252a307012da5aa9c16b1ed211b3f82e156d96df14a8", { + "chain": "regtest", "isPrivkey": false, - "chain": "main", "tryCaseFlip": true } ], [ - "tb1qqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesrxh6hy", - "0020000000c4a5cad46221b2a187905e5266362b99d5e91c6ce24d165dab93e86433", + "12agZTajtRE3STSchwWNWnrm467zzTQ916", + "76a9141156e00f70061e5faba8b71593a8c7554b47090c88ac", + { + "chain": "main", + "isPrivkey": false + } + ], + [ + "3NXqB6iZiPYbKruNT3d9xNBTmtb73xMvvf", + "a914e49decc9e5d97e0547d3642f3a4795b13ae62bca87", + { + "chain": "main", + "isPrivkey": false + } + ], + [ + "mjgt4BoCYxjzWvJFoh68x7cj5GeaKDYhyx", + "76a9142dc11fc7b8072f733f690ffb0591c00f4062295c88ac", { - "isPrivkey": false, "chain": "test", - "tryCaseFlip": true + "isPrivkey": false } ], [ - "bcrt1qqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvseswlauz7", - "0020000000c4a5cad46221b2a187905e5266362b99d5e91c6ce24d165dab93e86433", + "2NCT6FdQ5MxorHgnFxLeHyGwTGRdkHcrJDH", + "a914d2a8ec992b0894a0d9391ca5d9c45c388c41be7e87", { - "isPrivkey": false, - "chain": "regtest", - "tryCaseFlip": true + "chain": "test", + "isPrivkey": false + } + ], + [ + "mpomiA7wqDnMcxaNLC23eBuXAb4U6H4ZqW", + "76a91465e75e340415ed297c58d6a14d3c17ceeaa17bbd88ac", + { + "chain": "signet", + "isPrivkey": false + } + ], + [ + "2N1pGAA5uatbU2PKvMA9BnJmHcK6yHfMiZa", + "a9145e008b6cc232164570befc23d216060bf4ea793b87", + { + "chain": "signet", + "isPrivkey": false } ] ] diff --git a/src/test/dbwrapper_tests.cpp b/src/test/dbwrapper_tests.cpp index 3d802cbeb3..a6080ad3dd 100644 --- a/src/test/dbwrapper_tests.cpp +++ b/src/test/dbwrapper_tests.cpp @@ -5,7 +5,6 @@ #include <dbwrapper.h> #include <test/util/setup_common.h> #include <uint256.h> -#include <util/memory.h> #include <memory> @@ -207,7 +206,7 @@ BOOST_AUTO_TEST_CASE(existing_data_no_obfuscate) create_directories(ph); // Set up a non-obfuscated wrapper to write some initial data. - std::unique_ptr<CDBWrapper> dbw = MakeUnique<CDBWrapper>(ph, (1 << 10), false, false, false); + std::unique_ptr<CDBWrapper> dbw = std::make_unique<CDBWrapper>(ph, (1 << 10), false, false, false); char key = 'k'; uint256 in = InsecureRand256(); uint256 res; @@ -248,7 +247,7 @@ BOOST_AUTO_TEST_CASE(existing_data_reindex) create_directories(ph); // Set up a non-obfuscated wrapper to write some initial data. - std::unique_ptr<CDBWrapper> dbw = MakeUnique<CDBWrapper>(ph, (1 << 10), false, false, false); + std::unique_ptr<CDBWrapper> dbw = std::make_unique<CDBWrapper>(ph, (1 << 10), false, false, false); char key = 'k'; uint256 in = InsecureRand256(); uint256 res; diff --git a/src/test/denialofservice_tests.cpp b/src/test/denialofservice_tests.cpp index 0d480e35ea..7557d4618a 100644 --- a/src/test/denialofservice_tests.cpp +++ b/src/test/denialofservice_tests.cpp @@ -14,7 +14,7 @@ #include <script/signingprovider.h> #include <script/standard.h> #include <serialize.h> -#include <util/memory.h> +#include <txorphanage.h> #include <util/string.h> #include <util/system.h> #include <util/time.h> @@ -43,18 +43,6 @@ struct CConnmanTest : public CConnman { } }; -// Tests these internal-to-net_processing.cpp methods: -extern bool AddOrphanTx(const CTransactionRef& tx, NodeId peer); -extern void EraseOrphansFor(NodeId peer); -extern unsigned int LimitOrphanTxSize(unsigned int nMaxOrphans); - -struct COrphanTx { - CTransactionRef tx; - NodeId fromPeer; - int64_t nTimeExpire; -}; -extern std::map<uint256, COrphanTx> mapOrphanTransactions GUARDED_BY(g_cs_orphans); - static CService ip(uint32_t i) { struct in_addr s; @@ -79,9 +67,9 @@ BOOST_FIXTURE_TEST_SUITE(denialofservice_tests, TestingSetup) BOOST_AUTO_TEST_CASE(outbound_slow_chain_eviction) { const CChainParams& chainparams = Params(); - auto connman = MakeUnique<CConnman>(0x1337, 0x1337); - auto peerLogic = PeerManager::make(chainparams, *connman, nullptr, *m_node.scheduler, - *m_node.chainman, *m_node.mempool, false); + auto connman = std::make_unique<CConnman>(0x1337, 0x1337, *m_node.addrman); + auto peerLogic = PeerManager::make(chainparams, *connman, *m_node.addrman, nullptr, + *m_node.scheduler, *m_node.chainman, *m_node.mempool, false); // Mock an outbound peer CAddress addr1(ip(0xa0b0c001), NODE_NONE); @@ -129,8 +117,7 @@ BOOST_AUTO_TEST_CASE(outbound_slow_chain_eviction) BOOST_CHECK(dummyNode1.fDisconnect == true); SetMockTime(0); - bool dummy; - peerLogic->FinalizeNode(dummyNode1, dummy); + peerLogic->FinalizeNode(dummyNode1); } static void AddRandomOutboundPeer(std::vector<CNode *> &vNodes, PeerManager &peerLogic, CConnmanTest* connman) @@ -149,9 +136,9 @@ static void AddRandomOutboundPeer(std::vector<CNode *> &vNodes, PeerManager &pee BOOST_AUTO_TEST_CASE(stale_tip_peer_management) { const CChainParams& chainparams = Params(); - auto connman = MakeUnique<CConnmanTest>(0x1337, 0x1337); - auto peerLogic = PeerManager::make(chainparams, *connman, nullptr, *m_node.scheduler, - *m_node.chainman, *m_node.mempool, false); + auto connman = std::make_unique<CConnmanTest>(0x1337, 0x1337, *m_node.addrman); + auto peerLogic = PeerManager::make(chainparams, *connman, *m_node.addrman, nullptr, + *m_node.scheduler, *m_node.chainman, *m_node.mempool, false); constexpr int max_outbound_full_relay = MAX_OUTBOUND_FULL_RELAY_CONNECTIONS; CConnman::Options options; @@ -211,9 +198,8 @@ BOOST_AUTO_TEST_CASE(stale_tip_peer_management) BOOST_CHECK(vNodes[max_outbound_full_relay-1]->fDisconnect == true); BOOST_CHECK(vNodes.back()->fDisconnect == false); - bool dummy; for (const CNode *node : vNodes) { - peerLogic->FinalizeNode(*node, dummy); + peerLogic->FinalizeNode(*node); } connman->ClearNodes(); @@ -222,10 +208,10 @@ BOOST_AUTO_TEST_CASE(stale_tip_peer_management) BOOST_AUTO_TEST_CASE(peer_discouragement) { const CChainParams& chainparams = Params(); - auto banman = MakeUnique<BanMan>(GetDataDir() / "banlist.dat", nullptr, DEFAULT_MISBEHAVING_BANTIME); - auto connman = MakeUnique<CConnman>(0x1337, 0x1337); - auto peerLogic = PeerManager::make(chainparams, *connman, banman.get(), *m_node.scheduler, - *m_node.chainman, *m_node.mempool, false); + auto banman = std::make_unique<BanMan>(GetDataDir() / "banlist.dat", nullptr, DEFAULT_MISBEHAVING_BANTIME); + auto connman = std::make_unique<CConnman>(0x1337, 0x1337, *m_node.addrman); + auto peerLogic = PeerManager::make(chainparams, *connman, *m_node.addrman, banman.get(), + *m_node.scheduler, *m_node.chainman, *m_node.mempool, false); banman->ClearBanned(); CAddress addr1(ip(0xa0b0c001), NODE_NONE); @@ -261,18 +247,17 @@ BOOST_AUTO_TEST_CASE(peer_discouragement) BOOST_CHECK(banman->IsDiscouraged(addr1)); // Expect both 1 and 2 BOOST_CHECK(banman->IsDiscouraged(addr2)); // to be discouraged now - bool dummy; - peerLogic->FinalizeNode(dummyNode1, dummy); - peerLogic->FinalizeNode(dummyNode2, dummy); + peerLogic->FinalizeNode(dummyNode1); + peerLogic->FinalizeNode(dummyNode2); } BOOST_AUTO_TEST_CASE(DoS_bantime) { const CChainParams& chainparams = Params(); - auto banman = MakeUnique<BanMan>(GetDataDir() / "banlist.dat", nullptr, DEFAULT_MISBEHAVING_BANTIME); - auto connman = MakeUnique<CConnman>(0x1337, 0x1337); - auto peerLogic = PeerManager::make(chainparams, *connman, banman.get(), *m_node.scheduler, - *m_node.chainman, *m_node.mempool, false); + auto banman = std::make_unique<BanMan>(GetDataDir() / "banlist.dat", nullptr, DEFAULT_MISBEHAVING_BANTIME); + auto connman = std::make_unique<CConnman>(0x1337, 0x1337, *m_node.addrman); + auto peerLogic = PeerManager::make(chainparams, *connman, *m_node.addrman, banman.get(), + *m_node.scheduler, *m_node.chainman, *m_node.mempool, false); banman->ClearBanned(); int64_t nStartTime = GetTime(); @@ -291,19 +276,26 @@ BOOST_AUTO_TEST_CASE(DoS_bantime) } BOOST_CHECK(banman->IsDiscouraged(addr)); - bool dummy; - peerLogic->FinalizeNode(dummyNode, dummy); + peerLogic->FinalizeNode(dummyNode); } -static CTransactionRef RandomOrphan() +class TxOrphanageTest : public TxOrphanage { - std::map<uint256, COrphanTx>::iterator it; - LOCK2(cs_main, g_cs_orphans); - it = mapOrphanTransactions.lower_bound(InsecureRand256()); - if (it == mapOrphanTransactions.end()) - it = mapOrphanTransactions.begin(); - return it->second.tx; -} +public: + inline size_t CountOrphans() const EXCLUSIVE_LOCKS_REQUIRED(g_cs_orphans) + { + return m_orphans.size(); + } + + CTransactionRef RandomOrphan() EXCLUSIVE_LOCKS_REQUIRED(g_cs_orphans) + { + std::map<uint256, OrphanTx>::iterator it; + it = m_orphans.lower_bound(InsecureRand256()); + if (it == m_orphans.end()) + it = m_orphans.begin(); + return it->second.tx; + } +}; static void MakeNewKeyWithFastRandomContext(CKey& key) { @@ -323,11 +315,14 @@ BOOST_AUTO_TEST_CASE(DoS_mapOrphans) // signature's R and S values have leading zeros. g_insecure_rand_ctx = FastRandomContext(ArithToUint256(arith_uint256(33))); + TxOrphanageTest orphanage; CKey key; MakeNewKeyWithFastRandomContext(key); FillableSigningProvider keystore; BOOST_CHECK(keystore.AddKey(key)); + LOCK(g_cs_orphans); + // 50 orphan transactions: for (int i = 0; i < 50; i++) { @@ -340,13 +335,13 @@ BOOST_AUTO_TEST_CASE(DoS_mapOrphans) tx.vout[0].nValue = 1*CENT; tx.vout[0].scriptPubKey = GetScriptForDestination(PKHash(key.GetPubKey())); - AddOrphanTx(MakeTransactionRef(tx), i); + orphanage.AddTx(MakeTransactionRef(tx), i); } // ... and 50 that depend on other orphans: for (int i = 0; i < 50; i++) { - CTransactionRef txPrev = RandomOrphan(); + CTransactionRef txPrev = orphanage.RandomOrphan(); CMutableTransaction tx; tx.vin.resize(1); @@ -357,13 +352,13 @@ BOOST_AUTO_TEST_CASE(DoS_mapOrphans) tx.vout[0].scriptPubKey = GetScriptForDestination(PKHash(key.GetPubKey())); BOOST_CHECK(SignSignature(keystore, *txPrev, tx, 0, SIGHASH_ALL)); - AddOrphanTx(MakeTransactionRef(tx), i); + orphanage.AddTx(MakeTransactionRef(tx), i); } // This really-big orphan should be ignored: for (int i = 0; i < 10; i++) { - CTransactionRef txPrev = RandomOrphan(); + CTransactionRef txPrev = orphanage.RandomOrphan(); CMutableTransaction tx; tx.vout.resize(1); @@ -381,25 +376,24 @@ BOOST_AUTO_TEST_CASE(DoS_mapOrphans) for (unsigned int j = 1; j < tx.vin.size(); j++) tx.vin[j].scriptSig = tx.vin[0].scriptSig; - BOOST_CHECK(!AddOrphanTx(MakeTransactionRef(tx), i)); + BOOST_CHECK(!orphanage.AddTx(MakeTransactionRef(tx), i)); } - LOCK2(cs_main, g_cs_orphans); // Test EraseOrphansFor: for (NodeId i = 0; i < 3; i++) { - size_t sizeBefore = mapOrphanTransactions.size(); - EraseOrphansFor(i); - BOOST_CHECK(mapOrphanTransactions.size() < sizeBefore); + size_t sizeBefore = orphanage.CountOrphans(); + orphanage.EraseForPeer(i); + BOOST_CHECK(orphanage.CountOrphans() < sizeBefore); } // Test LimitOrphanTxSize() function: - LimitOrphanTxSize(40); - BOOST_CHECK(mapOrphanTransactions.size() <= 40); - LimitOrphanTxSize(10); - BOOST_CHECK(mapOrphanTransactions.size() <= 10); - LimitOrphanTxSize(0); - BOOST_CHECK(mapOrphanTransactions.empty()); + orphanage.LimitOrphans(40); + BOOST_CHECK(orphanage.CountOrphans() <= 40); + orphanage.LimitOrphans(10); + BOOST_CHECK(orphanage.CountOrphans() <= 10); + orphanage.LimitOrphans(0); + BOOST_CHECK(orphanage.CountOrphans() == 0); } BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/descriptor_tests.cpp b/src/test/descriptor_tests.cpp index acbd6a01ee..aecf955fee 100644 --- a/src/test/descriptor_tests.cpp +++ b/src/test/descriptor_tests.cpp @@ -10,6 +10,7 @@ #include <boost/test/unit_test.hpp> +#include <optional> #include <string> #include <vector> @@ -65,7 +66,7 @@ std::string UseHInsteadOfApostrophe(const std::string& desc) const std::set<std::vector<uint32_t>> ONLY_EMPTY{{}}; -void DoCheck(const std::string& prv, const std::string& pub, const std::string& norm_prv, const std::string& norm_pub, int flags, const std::vector<std::vector<std::string>>& scripts, const Optional<OutputType>& type, const std::set<std::vector<uint32_t>>& paths = ONLY_EMPTY, +void DoCheck(const std::string& prv, const std::string& pub, const std::string& norm_prv, const std::string& norm_pub, int flags, const std::vector<std::vector<std::string>>& scripts, const std::optional<OutputType>& type, const std::set<std::vector<uint32_t>>& paths = ONLY_EMPTY, bool replace_apostrophe_with_h_in_prv=false, bool replace_apostrophe_with_h_in_pub=false) { FlatSigningProvider keys_priv, keys_pub; @@ -262,7 +263,7 @@ void DoCheck(const std::string& prv, const std::string& pub, const std::string& BOOST_CHECK_MESSAGE(left_paths.empty(), "Not all expected key paths found: " + prv); } -void Check(const std::string& prv, const std::string& pub, const std::string& norm_prv, const std::string& norm_pub, int flags, const std::vector<std::vector<std::string>>& scripts, const Optional<OutputType>& type, const std::set<std::vector<uint32_t>>& paths = ONLY_EMPTY) +void Check(const std::string& prv, const std::string& pub, const std::string& norm_prv, const std::string& norm_pub, int flags, const std::vector<std::vector<std::string>>& scripts, const std::optional<OutputType>& type, const std::set<std::vector<uint32_t>>& paths = ONLY_EMPTY) { bool found_apostrophes_in_prv = false; bool found_apostrophes_in_pub = false; @@ -295,8 +296,8 @@ BOOST_FIXTURE_TEST_SUITE(descriptor_tests, BasicTestingSetup) BOOST_AUTO_TEST_CASE(descriptor_test) { // Basic single-key compressed - Check("combo(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)", "combo(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", "combo(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)", "combo(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", SIGNABLE, {{"2103a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bdac","76a9149a1c78a507689f6f54b847ad1cef1e614ee23f1e88ac","00149a1c78a507689f6f54b847ad1cef1e614ee23f1e","a91484ab21b1b2fd065d4504ff693d832434b6108d7b87"}}, nullopt); - Check("pk(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)", "pk(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", "pk(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)", "pk(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", SIGNABLE, {{"2103a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bdac"}}, nullopt); + Check("combo(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)", "combo(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", "combo(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)", "combo(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", SIGNABLE, {{"2103a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bdac","76a9149a1c78a507689f6f54b847ad1cef1e614ee23f1e88ac","00149a1c78a507689f6f54b847ad1cef1e614ee23f1e","a91484ab21b1b2fd065d4504ff693d832434b6108d7b87"}}, std::nullopt); + Check("pk(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)", "pk(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", "pk(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)", "pk(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", SIGNABLE, {{"2103a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bdac"}}, std::nullopt); Check("pkh([deadbeef/1/2'/3/4']L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)", "pkh([deadbeef/1/2'/3/4']03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", "pkh([deadbeef/1/2'/3/4']L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)", "pkh([deadbeef/1/2'/3/4']03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", SIGNABLE, {{"76a9149a1c78a507689f6f54b847ad1cef1e614ee23f1e88ac"}}, OutputType::LEGACY, {{1,0x80000002UL,3,0x80000004UL}}); Check("wpkh(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)", "wpkh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", "wpkh(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)", "wpkh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", SIGNABLE, {{"00149a1c78a507689f6f54b847ad1cef1e614ee23f1e"}}, OutputType::BECH32); Check("sh(wpkh(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1))", "sh(wpkh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd))", "sh(wpkh(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1))", "sh(wpkh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd))", SIGNABLE, {{"a91484ab21b1b2fd065d4504ff693d832434b6108d7b87"}}, OutputType::P2SH_SEGWIT); @@ -305,8 +306,8 @@ BOOST_AUTO_TEST_CASE(descriptor_test) CheckUnparsable("pkh([deadbeef]/1/2'/3/4']L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)", "pkh([deadbeef]/1/2'/3/4']03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", "Multiple ']' characters found for a single pubkey"); // Multiple end brackets in key origin // Basic single-key uncompressed - Check("combo(5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss)", "combo(04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235)", "combo(5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss)", "combo(04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235)",SIGNABLE, {{"4104a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235ac","76a914b5bd079c4d57cc7fc28ecf8213a6b791625b818388ac"}}, nullopt); - Check("pk(5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss)", "pk(04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235)", "pk(5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss)", "pk(04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235)", SIGNABLE, {{"4104a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235ac"}}, nullopt); + Check("combo(5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss)", "combo(04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235)", "combo(5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss)", "combo(04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235)",SIGNABLE, {{"4104a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235ac","76a914b5bd079c4d57cc7fc28ecf8213a6b791625b818388ac"}}, std::nullopt); + Check("pk(5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss)", "pk(04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235)", "pk(5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss)", "pk(04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235)", SIGNABLE, {{"4104a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235ac"}}, std::nullopt); Check("pkh(5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss)", "pkh(04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235)", "pkh(5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss)", "pkh(04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235)", SIGNABLE, {{"76a914b5bd079c4d57cc7fc28ecf8213a6b791625b818388ac"}}, OutputType::LEGACY); CheckUnparsable("wpkh(5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss)", "wpkh(04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235)", "Uncompressed keys are not allowed"); // No uncompressed keys in witness CheckUnparsable("wsh(pk(5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss))", "wsh(pk(04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235))", "Uncompressed keys are not allowed"); // No uncompressed keys in witness @@ -321,23 +322,23 @@ BOOST_AUTO_TEST_CASE(descriptor_test) Check("sh(wsh(pkh(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)))", "sh(wsh(pkh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)))", "sh(wsh(pkh(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)))", "sh(wsh(pkh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)))", SIGNABLE, {{"a914b61b92e2ca21bac1e72a3ab859a742982bea960a87"}}, OutputType::P2SH_SEGWIT); // Versions with BIP32 derivations - Check("combo([01234567]xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc)", "combo([01234567]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL)", "combo([01234567]xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc)", "combo([01234567]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL)", SIGNABLE, {{"2102d2b36900396c9282fa14628566582f206a5dd0bcc8d5e892611806cafb0301f0ac","76a91431a507b815593dfc51ffc7245ae7e5aee304246e88ac","001431a507b815593dfc51ffc7245ae7e5aee304246e","a9142aafb926eb247cb18240a7f4c07983ad1f37922687"}}, nullopt); - Check("pk(xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0)", "pk(xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0)", "pk(xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0)", "pk(xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0)", DEFAULT, {{"210379e45b3cf75f9c5f9befd8e9506fb962f6a9d185ac87001ec44a8d3df8d4a9e3ac"}}, nullopt, {{0}}); + Check("combo([01234567]xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc)", "combo([01234567]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL)", "combo([01234567]xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc)", "combo([01234567]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL)", SIGNABLE, {{"2102d2b36900396c9282fa14628566582f206a5dd0bcc8d5e892611806cafb0301f0ac","76a91431a507b815593dfc51ffc7245ae7e5aee304246e88ac","001431a507b815593dfc51ffc7245ae7e5aee304246e","a9142aafb926eb247cb18240a7f4c07983ad1f37922687"}}, std::nullopt); + Check("pk(xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0)", "pk(xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0)", "pk(xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0)", "pk(xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0)", DEFAULT, {{"210379e45b3cf75f9c5f9befd8e9506fb962f6a9d185ac87001ec44a8d3df8d4a9e3ac"}}, std::nullopt, {{0}}); Check("pkh(xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U/2147483647'/0)", "pkh(xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/2147483647'/0)", "pkh([bd16bee5/2147483647']xprv9vHkqa6XAPwKqSKSEJMcAB3yoCZhaSVsGZbSkFY5L3Lfjjk8sjZucbsbvEw5o3QrSA69nPfZDCgFnNnLhQ2ohpZuwummndnPasDw2Qr6dC2/0)", "pkh([bd16bee5/2147483647']xpub69H7F5dQzmVd3vPuLKtcXJziMEQByuDidnX3YdwgtNsecY5HRGtAAQC5mXTt4dsv9RzyjgDjAQs9VGVV6ydYCHnprc9vvaA5YtqWyL6hyds/0)", HARDENED, {{"76a914ebdc90806a9c4356c1c88e42216611e1cb4c1c1788ac"}}, OutputType::LEGACY, {{0xFFFFFFFFUL,0}}); Check("wpkh([ffffffff/13']xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt/1/2/*)", "wpkh([ffffffff/13']xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/1/2/*)", "wpkh([ffffffff/13']xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt/1/2/*)", "wpkh([ffffffff/13']xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/1/2/*)", RANGE, {{"0014326b2249e3a25d5dc60935f044ee835d090ba859"},{"0014af0bd98abc2f2cae66e36896a39ffe2d32984fb7"},{"00141fa798efd1cbf95cebf912c031b8a4a6e9fb9f27"}}, OutputType::BECH32, {{0x8000000DUL, 1, 2, 0}, {0x8000000DUL, 1, 2, 1}, {0x8000000DUL, 1, 2, 2}}); Check("sh(wpkh(xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi/10/20/30/40/*'))", "sh(wpkh(xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8/10/20/30/40/*'))", "sh(wpkh(xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi/10/20/30/40/*'))", "sh(wpkh(xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8/10/20/30/40/*'))", RANGE | HARDENED | DERIVE_HARDENED, {{"a9149a4d9901d6af519b2a23d4a2f51650fcba87ce7b87"},{"a914bed59fc0024fae941d6e20a3b44a109ae740129287"},{"a9148483aa1116eb9c05c482a72bada4b1db24af654387"}}, OutputType::P2SH_SEGWIT, {{10, 20, 30, 40, 0x80000000UL}, {10, 20, 30, 40, 0x80000001UL}, {10, 20, 30, 40, 0x80000002UL}}); - Check("combo(xprvA2JDeKCSNNZky6uBCviVfJSKyQ1mDYahRjijr5idH2WwLsEd4Hsb2Tyh8RfQMuPh7f7RtyzTtdrbdqqsunu5Mm3wDvUAKRHSC34sJ7in334/*)", "combo(xpub6FHa3pjLCk84BayeJxFW2SP4XRrFd1JYnxeLeU8EqN3vDfZmbqBqaGJAyiLjTAwm6ZLRQUMv1ZACTj37sR62cfN7fe5JnJ7dh8zL4fiyLHV/*)", "combo(xprvA2JDeKCSNNZky6uBCviVfJSKyQ1mDYahRjijr5idH2WwLsEd4Hsb2Tyh8RfQMuPh7f7RtyzTtdrbdqqsunu5Mm3wDvUAKRHSC34sJ7in334/*)", "combo(xpub6FHa3pjLCk84BayeJxFW2SP4XRrFd1JYnxeLeU8EqN3vDfZmbqBqaGJAyiLjTAwm6ZLRQUMv1ZACTj37sR62cfN7fe5JnJ7dh8zL4fiyLHV/*)", RANGE, {{"2102df12b7035bdac8e3bab862a3a83d06ea6b17b6753d52edecba9be46f5d09e076ac","76a914f90e3178ca25f2c808dc76624032d352fdbdfaf288ac","0014f90e3178ca25f2c808dc76624032d352fdbdfaf2","a91408f3ea8c68d4a7585bf9e8bda226723f70e445f087"},{"21032869a233c9adff9a994e4966e5b821fd5bac066da6c3112488dc52383b4a98ecac","76a914a8409d1b6dfb1ed2a3e8aa5e0ef2ff26b15b75b788ac","0014a8409d1b6dfb1ed2a3e8aa5e0ef2ff26b15b75b7","a91473e39884cb71ae4e5ac9739e9225026c99763e6687"}}, nullopt, {{0}, {1}}); + Check("combo(xprvA2JDeKCSNNZky6uBCviVfJSKyQ1mDYahRjijr5idH2WwLsEd4Hsb2Tyh8RfQMuPh7f7RtyzTtdrbdqqsunu5Mm3wDvUAKRHSC34sJ7in334/*)", "combo(xpub6FHa3pjLCk84BayeJxFW2SP4XRrFd1JYnxeLeU8EqN3vDfZmbqBqaGJAyiLjTAwm6ZLRQUMv1ZACTj37sR62cfN7fe5JnJ7dh8zL4fiyLHV/*)", "combo(xprvA2JDeKCSNNZky6uBCviVfJSKyQ1mDYahRjijr5idH2WwLsEd4Hsb2Tyh8RfQMuPh7f7RtyzTtdrbdqqsunu5Mm3wDvUAKRHSC34sJ7in334/*)", "combo(xpub6FHa3pjLCk84BayeJxFW2SP4XRrFd1JYnxeLeU8EqN3vDfZmbqBqaGJAyiLjTAwm6ZLRQUMv1ZACTj37sR62cfN7fe5JnJ7dh8zL4fiyLHV/*)", RANGE, {{"2102df12b7035bdac8e3bab862a3a83d06ea6b17b6753d52edecba9be46f5d09e076ac","76a914f90e3178ca25f2c808dc76624032d352fdbdfaf288ac","0014f90e3178ca25f2c808dc76624032d352fdbdfaf2","a91408f3ea8c68d4a7585bf9e8bda226723f70e445f087"},{"21032869a233c9adff9a994e4966e5b821fd5bac066da6c3112488dc52383b4a98ecac","76a914a8409d1b6dfb1ed2a3e8aa5e0ef2ff26b15b75b788ac","0014a8409d1b6dfb1ed2a3e8aa5e0ef2ff26b15b75b7","a91473e39884cb71ae4e5ac9739e9225026c99763e6687"}}, std::nullopt, {{0}, {1}}); CheckUnparsable("combo([012345678]xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc)", "combo([012345678]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL)", "Fingerprint is not 4 bytes (9 characters instead of 8 characters)"); // Too long key fingerprint CheckUnparsable("pkh(xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U/2147483648)", "pkh(xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/2147483648)", "Key path value 2147483648 is out of range"); // BIP 32 path element overflow CheckUnparsable("pkh(xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U/1aa)", "pkh(xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/1aa)", "Key path value '1aa' is not a valid uint32"); // Path is not valid uint Check("pkh([01234567/10/20]xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U/2147483647'/0)", "pkh([01234567/10/20]xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/2147483647'/0)", "pkh([01234567/10/20/2147483647']xprv9vHkqa6XAPwKqSKSEJMcAB3yoCZhaSVsGZbSkFY5L3Lfjjk8sjZucbsbvEw5o3QrSA69nPfZDCgFnNnLhQ2ohpZuwummndnPasDw2Qr6dC2/0)", "pkh([01234567/10/20/2147483647']xpub69H7F5dQzmVd3vPuLKtcXJziMEQByuDidnX3YdwgtNsecY5HRGtAAQC5mXTt4dsv9RzyjgDjAQs9VGVV6ydYCHnprc9vvaA5YtqWyL6hyds/0)", HARDENED, {{"76a914ebdc90806a9c4356c1c88e42216611e1cb4c1c1788ac"}}, OutputType::LEGACY, {{10, 20, 0xFFFFFFFFUL, 0}}); // Multisig constructions - Check("multi(1,L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1,5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss)", "multi(1,03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd,04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235)", "multi(1,L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1,5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss)", "multi(1,03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd,04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235)", SIGNABLE, {{"512103a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd4104a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea23552ae"}}, nullopt); - Check("sortedmulti(1,L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1,5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss)", "sortedmulti(1,03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd,04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235)", "sortedmulti(1,L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1,5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss)", "sortedmulti(1,03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd,04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235)", SIGNABLE, {{"512103a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd4104a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea23552ae"}}, nullopt); - Check("sortedmulti(1,5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss,L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)", "sortedmulti(1,04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235,03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", "sortedmulti(1,5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss,L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)", "sortedmulti(1,04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235,03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", SIGNABLE, {{"512103a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd4104a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea23552ae"}}, nullopt); + Check("multi(1,L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1,5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss)", "multi(1,03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd,04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235)", "multi(1,L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1,5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss)", "multi(1,03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd,04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235)", SIGNABLE, {{"512103a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd4104a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea23552ae"}}, std::nullopt); + Check("sortedmulti(1,L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1,5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss)", "sortedmulti(1,03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd,04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235)", "sortedmulti(1,L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1,5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss)", "sortedmulti(1,03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd,04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235)", SIGNABLE, {{"512103a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd4104a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea23552ae"}}, std::nullopt); + Check("sortedmulti(1,5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss,L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)", "sortedmulti(1,04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235,03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", "sortedmulti(1,5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss,L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)", "sortedmulti(1,04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235,03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", SIGNABLE, {{"512103a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd4104a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea23552ae"}}, std::nullopt); Check("sh(multi(2,[00000000/111'/222]xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0))", "sh(multi(2,[00000000/111'/222]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0))", "sh(multi(2,[00000000/111'/222]xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0))", "sh(multi(2,[00000000/111'/222]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0))", DEFAULT, {{"a91445a9a622a8b0a1269944be477640eedc447bbd8487"}}, OutputType::LEGACY, {{0x8000006FUL,222},{0}}); - Check("sortedmulti(2,xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc/*,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0/0/*)", "sortedmulti(2,xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/*,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0/0/*)", "sortedmulti(2,xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc/*,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0/0/*)", "sortedmulti(2,xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/*,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0/0/*)", RANGE, {{"5221025d5fc65ebb8d44a5274b53bac21ff8307fec2334a32df05553459f8b1f7fe1b62102fbd47cc8034098f0e6a94c6aeee8528abf0a2153a5d8e46d325b7284c046784652ae"}, {"52210264fd4d1f5dea8ded94c61e9641309349b62f27fbffe807291f664e286bfbe6472103f4ece6dfccfa37b211eb3d0af4d0c61dba9ef698622dc17eecdf764beeb005a652ae"}, {"5221022ccabda84c30bad578b13c89eb3b9544ce149787e5b538175b1d1ba259cbb83321024d902e1a2fc7a8755ab5b694c575fce742c48d9ff192e63df5193e4c7afe1f9c52ae"}}, nullopt, {{0}, {1}, {2}, {0, 0, 0}, {0, 0, 1}, {0, 0, 2}}); + Check("sortedmulti(2,xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc/*,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0/0/*)", "sortedmulti(2,xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/*,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0/0/*)", "sortedmulti(2,xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc/*,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0/0/*)", "sortedmulti(2,xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/*,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0/0/*)", RANGE, {{"5221025d5fc65ebb8d44a5274b53bac21ff8307fec2334a32df05553459f8b1f7fe1b62102fbd47cc8034098f0e6a94c6aeee8528abf0a2153a5d8e46d325b7284c046784652ae"}, {"52210264fd4d1f5dea8ded94c61e9641309349b62f27fbffe807291f664e286bfbe6472103f4ece6dfccfa37b211eb3d0af4d0c61dba9ef698622dc17eecdf764beeb005a652ae"}, {"5221022ccabda84c30bad578b13c89eb3b9544ce149787e5b538175b1d1ba259cbb83321024d902e1a2fc7a8755ab5b694c575fce742c48d9ff192e63df5193e4c7afe1f9c52ae"}}, std::nullopt, {{0}, {1}, {2}, {0, 0, 0}, {0, 0, 1}, {0, 0, 2}}); Check("wsh(multi(2,xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U/2147483647'/0,xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt/1/2/*,xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi/10/20/30/40/*'))", "wsh(multi(2,xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/2147483647'/0,xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/1/2/*,xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8/10/20/30/40/*'))", "wsh(multi(2,[bd16bee5/2147483647']xprv9vHkqa6XAPwKqSKSEJMcAB3yoCZhaSVsGZbSkFY5L3Lfjjk8sjZucbsbvEw5o3QrSA69nPfZDCgFnNnLhQ2ohpZuwummndnPasDw2Qr6dC2/0,xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt/1/2/*,xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi/10/20/30/40/*'))", "wsh(multi(2,[bd16bee5/2147483647']xpub69H7F5dQzmVd3vPuLKtcXJziMEQByuDidnX3YdwgtNsecY5HRGtAAQC5mXTt4dsv9RzyjgDjAQs9VGVV6ydYCHnprc9vvaA5YtqWyL6hyds/0,xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/1/2/*,xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8/10/20/30/40/*'))", HARDENED | RANGE | DERIVE_HARDENED, {{"0020b92623201f3bb7c3771d45b2ad1d0351ea8fbf8cfe0a0e570264e1075fa1948f"},{"002036a08bbe4923af41cf4316817c93b8d37e2f635dd25cfff06bd50df6ae7ea203"},{"0020a96e7ab4607ca6b261bfe3245ffda9c746b28d3f59e83d34820ec0e2b36c139c"}}, OutputType::BECH32, {{0xFFFFFFFFUL,0}, {1,2,0}, {1,2,1}, {1,2,2}, {10, 20, 30, 40, 0x80000000UL}, {10, 20, 30, 40, 0x80000001UL}, {10, 20, 30, 40, 0x80000002UL}}); Check("sh(wsh(multi(16,KzoAz5CanayRKex3fSLQ2BwJpN7U52gZvxMyk78nDMHuqrUxuSJy,KwGNz6YCCQtYvFzMtrC6D3tKTKdBBboMrLTsjr2NYVBwapCkn7Mr,KxogYhiNfwxuswvXV66eFyKcCpm7dZ7TqHVqujHAVUjJxyivxQ9X,L2BUNduTSyZwZjwNHynQTF14mv2uz2NRq5n5sYWTb4FkkmqgEE9f,L1okJGHGn1kFjdXHKxXjwVVtmCMR2JA5QsbKCSpSb7ReQjezKeoD,KxDCNSST75HFPaW5QKpzHtAyaCQC7p9Vo3FYfi2u4dXD1vgMiboK,L5edQjFtnkcf5UWURn6UuuoFrabgDQUHdheKCziwN42aLwS3KizU,KzF8UWFcEC7BYTq8Go1xVimMkDmyNYVmXV5PV7RuDicvAocoPB8i,L3nHUboKG2w4VSJ5jYZ5CBM97oeK6YuKvfZxrefdShECcjEYKMWZ,KyjHo36dWkYhimKmVVmQTq3gERv3pnqA4xFCpvUgbGDJad7eS8WE,KwsfyHKRUTZPQtysN7M3tZ4GXTnuov5XRgjdF2XCG8faAPmFruRF,KzCUbGhN9LJhdeFfL9zQgTJMjqxdBKEekRGZX24hXdgCNCijkkap,KzgpMBwwsDLwkaC5UrmBgCYaBD2WgZ7PBoGYXR8KT7gCA9UTN5a3,KyBXTPy4T7YG4q9tcAM3LkvfRpD1ybHMvcJ2ehaWXaSqeGUxEdkP,KzJDe9iwJRPtKP2F2AoN6zBgzS7uiuAwhWCfGdNeYJ3PC1HNJ8M8,L1xbHrxynrqLKkoYc4qtoQPx6uy5qYXR5ZDYVYBSRmCV5piU3JG9)))","sh(wsh(multi(16,03669b8afcec803a0d323e9a17f3ea8e68e8abe5a278020a929adbec52421adbd0,0260b2003c386519fc9eadf2b5cf124dd8eea4c4e68d5e154050a9346ea98ce600,0362a74e399c39ed5593852a30147f2959b56bb827dfa3e60e464b02ccf87dc5e8,0261345b53de74a4d721ef877c255429961b7e43714171ac06168d7e08c542a8b8,02da72e8b46901a65d4374fe6315538d8f368557dda3a1dcf9ea903f3afe7314c8,0318c82dd0b53fd3a932d16e0ba9e278fcc937c582d5781be626ff16e201f72286,0297ccef1ef99f9d73dec9ad37476ddb232f1238aff877af19e72ba04493361009,02e502cfd5c3f972fe9a3e2a18827820638f96b6f347e54d63deb839011fd5765d,03e687710f0e3ebe81c1037074da939d409c0025f17eb86adb9427d28f0f7ae0e9,02c04d3a5274952acdbc76987f3184b346a483d43be40874624b29e3692c1df5af,02ed06e0f418b5b43a7ec01d1d7d27290fa15f75771cb69b642a51471c29c84acd,036d46073cbb9ffee90473f3da429abc8de7f8751199da44485682a989a4bebb24,02f5d1ff7c9029a80a4e36b9a5497027ef7f3e73384a4a94fbfe7c4e9164eec8bc,02e41deffd1b7cce11cde209a781adcffdabd1b91c0ba0375857a2bfd9302419f3,02d76625f7956a7fc505ab02556c23ee72d832f1bac391bcd2d3abce5710a13d06,0399eb0a5487515802dc14544cf10b3666623762fbed2ec38a3975716e2c29c232)))", "sh(wsh(multi(16,KzoAz5CanayRKex3fSLQ2BwJpN7U52gZvxMyk78nDMHuqrUxuSJy,KwGNz6YCCQtYvFzMtrC6D3tKTKdBBboMrLTsjr2NYVBwapCkn7Mr,KxogYhiNfwxuswvXV66eFyKcCpm7dZ7TqHVqujHAVUjJxyivxQ9X,L2BUNduTSyZwZjwNHynQTF14mv2uz2NRq5n5sYWTb4FkkmqgEE9f,L1okJGHGn1kFjdXHKxXjwVVtmCMR2JA5QsbKCSpSb7ReQjezKeoD,KxDCNSST75HFPaW5QKpzHtAyaCQC7p9Vo3FYfi2u4dXD1vgMiboK,L5edQjFtnkcf5UWURn6UuuoFrabgDQUHdheKCziwN42aLwS3KizU,KzF8UWFcEC7BYTq8Go1xVimMkDmyNYVmXV5PV7RuDicvAocoPB8i,L3nHUboKG2w4VSJ5jYZ5CBM97oeK6YuKvfZxrefdShECcjEYKMWZ,KyjHo36dWkYhimKmVVmQTq3gERv3pnqA4xFCpvUgbGDJad7eS8WE,KwsfyHKRUTZPQtysN7M3tZ4GXTnuov5XRgjdF2XCG8faAPmFruRF,KzCUbGhN9LJhdeFfL9zQgTJMjqxdBKEekRGZX24hXdgCNCijkkap,KzgpMBwwsDLwkaC5UrmBgCYaBD2WgZ7PBoGYXR8KT7gCA9UTN5a3,KyBXTPy4T7YG4q9tcAM3LkvfRpD1ybHMvcJ2ehaWXaSqeGUxEdkP,KzJDe9iwJRPtKP2F2AoN6zBgzS7uiuAwhWCfGdNeYJ3PC1HNJ8M8,L1xbHrxynrqLKkoYc4qtoQPx6uy5qYXR5ZDYVYBSRmCV5piU3JG9)))","sh(wsh(multi(16,03669b8afcec803a0d323e9a17f3ea8e68e8abe5a278020a929adbec52421adbd0,0260b2003c386519fc9eadf2b5cf124dd8eea4c4e68d5e154050a9346ea98ce600,0362a74e399c39ed5593852a30147f2959b56bb827dfa3e60e464b02ccf87dc5e8,0261345b53de74a4d721ef877c255429961b7e43714171ac06168d7e08c542a8b8,02da72e8b46901a65d4374fe6315538d8f368557dda3a1dcf9ea903f3afe7314c8,0318c82dd0b53fd3a932d16e0ba9e278fcc937c582d5781be626ff16e201f72286,0297ccef1ef99f9d73dec9ad37476ddb232f1238aff877af19e72ba04493361009,02e502cfd5c3f972fe9a3e2a18827820638f96b6f347e54d63deb839011fd5765d,03e687710f0e3ebe81c1037074da939d409c0025f17eb86adb9427d28f0f7ae0e9,02c04d3a5274952acdbc76987f3184b346a483d43be40874624b29e3692c1df5af,02ed06e0f418b5b43a7ec01d1d7d27290fa15f75771cb69b642a51471c29c84acd,036d46073cbb9ffee90473f3da429abc8de7f8751199da44485682a989a4bebb24,02f5d1ff7c9029a80a4e36b9a5497027ef7f3e73384a4a94fbfe7c4e9164eec8bc,02e41deffd1b7cce11cde209a781adcffdabd1b91c0ba0375857a2bfd9302419f3,02d76625f7956a7fc505ab02556c23ee72d832f1bac391bcd2d3abce5710a13d06,0399eb0a5487515802dc14544cf10b3666623762fbed2ec38a3975716e2c29c232)))", SIGNABLE, {{"a9147fc63e13dc25e8a95a3cee3d9a714ac3afd96f1e87"}}, OutputType::P2SH_SEGWIT); CheckUnparsable("sh(multi(16,KzoAz5CanayRKex3fSLQ2BwJpN7U52gZvxMyk78nDMHuqrUxuSJy,KwGNz6YCCQtYvFzMtrC6D3tKTKdBBboMrLTsjr2NYVBwapCkn7Mr,KxogYhiNfwxuswvXV66eFyKcCpm7dZ7TqHVqujHAVUjJxyivxQ9X,L2BUNduTSyZwZjwNHynQTF14mv2uz2NRq5n5sYWTb4FkkmqgEE9f,L1okJGHGn1kFjdXHKxXjwVVtmCMR2JA5QsbKCSpSb7ReQjezKeoD,KxDCNSST75HFPaW5QKpzHtAyaCQC7p9Vo3FYfi2u4dXD1vgMiboK,L5edQjFtnkcf5UWURn6UuuoFrabgDQUHdheKCziwN42aLwS3KizU,KzF8UWFcEC7BYTq8Go1xVimMkDmyNYVmXV5PV7RuDicvAocoPB8i,L3nHUboKG2w4VSJ5jYZ5CBM97oeK6YuKvfZxrefdShECcjEYKMWZ,KyjHo36dWkYhimKmVVmQTq3gERv3pnqA4xFCpvUgbGDJad7eS8WE,KwsfyHKRUTZPQtysN7M3tZ4GXTnuov5XRgjdF2XCG8faAPmFruRF,KzCUbGhN9LJhdeFfL9zQgTJMjqxdBKEekRGZX24hXdgCNCijkkap,KzgpMBwwsDLwkaC5UrmBgCYaBD2WgZ7PBoGYXR8KT7gCA9UTN5a3,KyBXTPy4T7YG4q9tcAM3LkvfRpD1ybHMvcJ2ehaWXaSqeGUxEdkP,KzJDe9iwJRPtKP2F2AoN6zBgzS7uiuAwhWCfGdNeYJ3PC1HNJ8M8,L1xbHrxynrqLKkoYc4qtoQPx6uy5qYXR5ZDYVYBSRmCV5piU3JG9))","sh(multi(16,03669b8afcec803a0d323e9a17f3ea8e68e8abe5a278020a929adbec52421adbd0,0260b2003c386519fc9eadf2b5cf124dd8eea4c4e68d5e154050a9346ea98ce600,0362a74e399c39ed5593852a30147f2959b56bb827dfa3e60e464b02ccf87dc5e8,0261345b53de74a4d721ef877c255429961b7e43714171ac06168d7e08c542a8b8,02da72e8b46901a65d4374fe6315538d8f368557dda3a1dcf9ea903f3afe7314c8,0318c82dd0b53fd3a932d16e0ba9e278fcc937c582d5781be626ff16e201f72286,0297ccef1ef99f9d73dec9ad37476ddb232f1238aff877af19e72ba04493361009,02e502cfd5c3f972fe9a3e2a18827820638f96b6f347e54d63deb839011fd5765d,03e687710f0e3ebe81c1037074da939d409c0025f17eb86adb9427d28f0f7ae0e9,02c04d3a5274952acdbc76987f3184b346a483d43be40874624b29e3692c1df5af,02ed06e0f418b5b43a7ec01d1d7d27290fa15f75771cb69b642a51471c29c84acd,036d46073cbb9ffee90473f3da429abc8de7f8751199da44485682a989a4bebb24,02f5d1ff7c9029a80a4e36b9a5497027ef7f3e73384a4a94fbfe7c4e9164eec8bc,02e41deffd1b7cce11cde209a781adcffdabd1b91c0ba0375857a2bfd9302419f3,02d76625f7956a7fc505ab02556c23ee72d832f1bac391bcd2d3abce5710a13d06,0399eb0a5487515802dc14544cf10b3666623762fbed2ec38a3975716e2c29c232))", "P2SH script is too large, 547 bytes is larger than 520 bytes"); // P2SH does not fit 16 compressed pubkeys in a redeemscript diff --git a/src/test/fuzz/FuzzedDataProvider.h b/src/test/fuzz/FuzzedDataProvider.h index 744a9d78ce..6cbfc39bc2 100644 --- a/src/test/fuzz/FuzzedDataProvider.h +++ b/src/test/fuzz/FuzzedDataProvider.h @@ -20,6 +20,7 @@ #include <cstdint> #include <cstring> #include <initializer_list> +#include <limits> #include <string> #include <type_traits> #include <utility> 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/asmap_direct.cpp b/src/test/fuzz/asmap_direct.cpp index 8b7822dc16..8ca4de3919 100644 --- a/src/test/fuzz/asmap_direct.cpp +++ b/src/test/fuzz/asmap_direct.cpp @@ -2,8 +2,9 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#include <test/fuzz/fuzz.h> +#include <netaddress.h> #include <util/asmap.h> +#include <test/fuzz/fuzz.h> #include <cstdint> #include <optional> diff --git a/src/test/fuzz/autofile.cpp b/src/test/fuzz/autofile.cpp index dbc0b5ab81..479342e4be 100644 --- a/src/test/fuzz/autofile.cpp +++ b/src/test/fuzz/autofile.cpp @@ -2,7 +2,6 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#include <optional.h> #include <streams.h> #include <test/fuzz/FuzzedDataProvider.h> #include <test/fuzz/fuzz.h> diff --git a/src/test/fuzz/banman.cpp b/src/test/fuzz/banman.cpp index 124439559e..8bf484722c 100644 --- a/src/test/fuzz/banman.cpp +++ b/src/test/fuzz/banman.cpp @@ -8,6 +8,7 @@ #include <test/fuzz/FuzzedDataProvider.h> #include <test/fuzz/fuzz.h> #include <test/fuzz/util.h> +#include <test/util/setup_common.h> #include <util/system.h> #include <cstdint> diff --git a/src/test/fuzz/bech32.cpp b/src/test/fuzz/bech32.cpp index 95cd4b413f..ad3bf73af4 100644 --- a/src/test/fuzz/bech32.cpp +++ b/src/test/fuzz/bech32.cpp @@ -16,28 +16,28 @@ FUZZ_TARGET(bech32) { const std::string random_string(buffer.begin(), buffer.end()); - const std::pair<std::string, std::vector<uint8_t>> r1 = bech32::Decode(random_string); - if (r1.first.empty()) { - assert(r1.second.empty()); + const auto r1 = bech32::Decode(random_string); + if (r1.hrp.empty()) { + assert(r1.encoding == bech32::Encoding::INVALID); + assert(r1.data.empty()); } else { - const std::string& hrp = r1.first; - const std::vector<uint8_t>& data = r1.second; - const std::string reencoded = bech32::Encode(hrp, data); + assert(r1.encoding != bech32::Encoding::INVALID); + const std::string reencoded = bech32::Encode(r1.encoding, r1.hrp, r1.data); assert(CaseInsensitiveEqual(random_string, reencoded)); } std::vector<unsigned char> input; ConvertBits<8, 5, true>([&](unsigned char c) { input.push_back(c); }, buffer.begin(), buffer.end()); - const std::string encoded = bech32::Encode("bc", input); - assert(!encoded.empty()); - const std::pair<std::string, std::vector<uint8_t>> r2 = bech32::Decode(encoded); - if (r2.first.empty()) { - assert(r2.second.empty()); - } else { - const std::string& hrp = r2.first; - const std::vector<uint8_t>& data = r2.second; - assert(hrp == "bc"); - assert(data == input); + if (input.size() + 3 + 6 <= 90) { + // If it's possible to encode input in Bech32(m) without exceeding the 90-character limit: + for (auto encoding : {bech32::Encoding::BECH32, bech32::Encoding::BECH32M}) { + const std::string encoded = bech32::Encode(encoding, "bc", input); + assert(!encoded.empty()); + const auto r2 = bech32::Decode(encoded); + assert(r2.encoding == encoding); + assert(r2.hrp == "bc"); + assert(r2.data == input); + } } } diff --git a/src/test/fuzz/buffered_file.cpp b/src/test/fuzz/buffered_file.cpp index ffe38f10fc..ed72260d10 100644 --- a/src/test/fuzz/buffered_file.cpp +++ b/src/test/fuzz/buffered_file.cpp @@ -2,7 +2,6 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#include <optional.h> #include <streams.h> #include <test/fuzz/FuzzedDataProvider.h> #include <test/fuzz/fuzz.h> diff --git a/src/test/fuzz/coins_view.cpp b/src/test/fuzz/coins_view.cpp index 19486365f6..b21d2eae79 100644 --- a/src/test/fuzz/coins_view.cpp +++ b/src/test/fuzz/coins_view.cpp @@ -16,6 +16,7 @@ #include <test/fuzz/FuzzedDataProvider.h> #include <test/fuzz/fuzz.h> #include <test/fuzz/util.h> +#include <test/util/setup_common.h> #include <validation.h> #include <cstdint> @@ -229,11 +230,8 @@ FUZZ_TARGET_INIT(coins_view, initialize_coins_view) // consensus/tx_verify.cpp:171: bool Consensus::CheckTxInputs(const CTransaction &, TxValidationState &, const CCoinsViewCache &, int, CAmount &): Assertion `!coin.IsSpent()' failed. return; } - try { - (void)Consensus::CheckTxInputs(transaction, state, coins_view_cache, fuzzed_data_provider.ConsumeIntegralInRange<int>(0, std::numeric_limits<int>::max()), tx_fee_out); - assert(MoneyRange(tx_fee_out)); - } catch (const std::runtime_error&) { - } + (void)Consensus::CheckTxInputs(transaction, state, coins_view_cache, fuzzed_data_provider.ConsumeIntegralInRange<int>(0, std::numeric_limits<int>::max()), tx_fee_out); + assert(MoneyRange(tx_fee_out)); }, [&] { const CTransaction transaction{random_mutable_transaction}; @@ -263,7 +261,7 @@ FUZZ_TARGET_INIT(coins_view, initialize_coins_view) CCoinsStats stats; bool expected_code_path = false; try { - (void)GetUTXOStats(&coins_view_cache, stats, CoinStatsHashType::HASH_SERIALIZED); + (void)GetUTXOStats(&coins_view_cache, WITH_LOCK(::cs_main, return std::ref(g_chainman.m_blockman)), stats, CoinStatsHashType::HASH_SERIALIZED); } catch (const std::logic_error&) { expected_code_path = true; } diff --git a/src/test/fuzz/connman.cpp b/src/test/fuzz/connman.cpp index 3fb8d8ca06..e07f25dedf 100644 --- a/src/test/fuzz/connman.cpp +++ b/src/test/fuzz/connman.cpp @@ -10,6 +10,7 @@ #include <test/fuzz/FuzzedDataProvider.h> #include <test/fuzz/fuzz.h> #include <test/fuzz/util.h> +#include <test/util/setup_common.h> #include <util/translation.h> #include <cstdint> @@ -24,40 +25,25 @@ FUZZ_TARGET_INIT(connman, initialize_connman) { FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()}; SetMockTime(ConsumeTime(fuzzed_data_provider)); - CConnman connman{fuzzed_data_provider.ConsumeIntegral<uint64_t>(), fuzzed_data_provider.ConsumeIntegral<uint64_t>(), fuzzed_data_provider.ConsumeBool()}; - CAddress random_address; + CAddrMan addrman; + CConnman connman{fuzzed_data_provider.ConsumeIntegral<uint64_t>(), fuzzed_data_provider.ConsumeIntegral<uint64_t>(), addrman, fuzzed_data_provider.ConsumeBool()}; CNetAddr random_netaddr; CNode random_node = ConsumeNode(fuzzed_data_provider); - CService random_service; CSubNet random_subnet; std::string random_string; while (fuzzed_data_provider.ConsumeBool()) { CallOneOf( fuzzed_data_provider, [&] { - random_address = ConsumeAddress(fuzzed_data_provider); - }, - [&] { random_netaddr = ConsumeNetAddr(fuzzed_data_provider); }, [&] { - random_service = ConsumeService(fuzzed_data_provider); - }, - [&] { random_subnet = ConsumeSubNet(fuzzed_data_provider); }, [&] { random_string = fuzzed_data_provider.ConsumeRandomLengthString(64); }, [&] { - std::vector<CAddress> addresses; - while (fuzzed_data_provider.ConsumeBool()) { - addresses.push_back(ConsumeAddress(fuzzed_data_provider)); - } - // Limit nTimePenalty to int32_t to avoid signed integer overflow - (void)connman.AddNewAddresses(addresses, ConsumeAddress(fuzzed_data_provider), fuzzed_data_provider.ConsumeIntegral<int32_t>()); - }, - [&] { connman.AddNode(random_string); }, [&] { @@ -94,17 +80,16 @@ FUZZ_TARGET_INIT(connman, initialize_connman) (void)connman.GetDeterministicRandomizer(fuzzed_data_provider.ConsumeIntegral<uint64_t>()); }, [&] { - (void)connman.GetNodeCount(fuzzed_data_provider.PickValueInArray({CConnman::CONNECTIONS_NONE, CConnman::CONNECTIONS_IN, CConnman::CONNECTIONS_OUT, CConnman::CONNECTIONS_ALL})); - }, - [&] { - connman.MarkAddressGood(random_address); + (void)connman.GetNodeCount(fuzzed_data_provider.PickValueInArray({ConnectionDirection::None, ConnectionDirection::In, ConnectionDirection::Out, ConnectionDirection::Both})); }, [&] { (void)connman.OutboundTargetReached(fuzzed_data_provider.ConsumeBool()); }, [&] { // Limit now to int32_t to avoid signed integer overflow - (void)connman.PoissonNextSendInbound(fuzzed_data_provider.ConsumeIntegral<int32_t>(), fuzzed_data_provider.ConsumeIntegral<int>()); + (void)connman.PoissonNextSendInbound( + std::chrono::microseconds{fuzzed_data_provider.ConsumeIntegral<int32_t>()}, + std::chrono::seconds{fuzzed_data_provider.ConsumeIntegral<int>()}); }, [&] { CSerializedNetMsg serialized_net_msg; @@ -125,9 +110,6 @@ FUZZ_TARGET_INIT(connman, initialize_connman) connman.SetNetworkActive(fuzzed_data_provider.ConsumeBool()); }, [&] { - connman.SetServices(random_service, ConsumeWeakEnum(fuzzed_data_provider, ALL_SERVICE_FLAGS)); - }, - [&] { connman.SetTryNewOutboundPeer(fuzzed_data_provider.ConsumeBool()); }); } 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/data_stream.cpp b/src/test/fuzz/data_stream.cpp index d6ef0c6691..473caec6ff 100644 --- a/src/test/fuzz/data_stream.cpp +++ b/src/test/fuzz/data_stream.cpp @@ -7,6 +7,7 @@ #include <test/fuzz/FuzzedDataProvider.h> #include <test/fuzz/fuzz.h> #include <test/fuzz/util.h> +#include <test/util/setup_common.h> #include <cstdint> #include <vector> diff --git a/src/test/fuzz/descriptor_parse.cpp b/src/test/fuzz/descriptor_parse.cpp index 0d1921f285..ffe4855662 100644 --- a/src/test/fuzz/descriptor_parse.cpp +++ b/src/test/fuzz/descriptor_parse.cpp @@ -6,7 +6,6 @@ #include <pubkey.h> #include <script/descriptor.h> #include <test/fuzz/fuzz.h> -#include <util/memory.h> void initialize_descriptor_parse() { diff --git a/src/test/fuzz/deserialize.cpp b/src/test/fuzz/deserialize.cpp index 64c6e49615..1290c78712 100644 --- a/src/test/fuzz/deserialize.cpp +++ b/src/test/fuzz/deserialize.cpp @@ -15,7 +15,6 @@ #include <net.h> #include <netbase.h> #include <node/utxo_snapshot.h> -#include <optional.h> #include <primitives/block.h> #include <protocol.h> #include <psbt.h> @@ -26,6 +25,7 @@ #include <version.h> #include <exception> +#include <optional> #include <stdexcept> #include <stdint.h> #include <unistd.h> @@ -69,7 +69,7 @@ T Deserialize(CDataStream ds) } template <typename T> -void DeserializeFromFuzzingInput(FuzzBufferType buffer, T& obj, const Optional<int> protocol_version = nullopt) +void DeserializeFromFuzzingInput(FuzzBufferType buffer, T& obj, const std::optional<int> protocol_version = std::nullopt) { CDataStream ds(buffer, SER_NETWORK, INIT_PROTO_VERSION); if (protocol_version) { diff --git a/src/test/fuzz/eval_script.cpp b/src/test/fuzz/eval_script.cpp index 635288fc36..77ed798923 100644 --- a/src/test/fuzz/eval_script.cpp +++ b/src/test/fuzz/eval_script.cpp @@ -6,7 +6,6 @@ #include <script/interpreter.h> #include <test/fuzz/FuzzedDataProvider.h> #include <test/fuzz/fuzz.h> -#include <util/memory.h> #include <limits> diff --git a/src/test/fuzz/i2p.cpp b/src/test/fuzz/i2p.cpp new file mode 100644 index 0000000000..345d68502a --- /dev/null +++ b/src/test/fuzz/i2p.cpp @@ -0,0 +1,57 @@ +// Copyright (c) 2020-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 <i2p.h> +#include <netaddress.h> +#include <netbase.h> +#include <test/fuzz/FuzzedDataProvider.h> +#include <test/fuzz/fuzz.h> +#include <test/fuzz/util.h> +#include <test/util/setup_common.h> +#include <threadinterrupt.h> +#include <util/system.h> + +void initialize_i2p() +{ + static const auto testing_setup = MakeNoLogFileContext<>(); +} + +FUZZ_TARGET_INIT(i2p, initialize_i2p) +{ + FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()}; + + // Mock CreateSock() to create FuzzedSock. + auto CreateSockOrig = CreateSock; + CreateSock = [&fuzzed_data_provider](const CService&) { + return std::make_unique<FuzzedSock>(fuzzed_data_provider); + }; + + const CService sam_proxy; + CThreadInterrupt interrupt; + + i2p::sam::Session sess{GetDataDir() / "fuzzed_i2p_private_key", sam_proxy, &interrupt}; + + i2p::Connection conn; + + if (sess.Listen(conn)) { + if (sess.Accept(conn)) { + try { + conn.sock->RecvUntilTerminator('\n', 10ms, interrupt, i2p::sam::MAX_MSG_SIZE); + } catch (const std::runtime_error&) { + } + } + } + + const CService to; + bool proxy_error; + + if (sess.Connect(to, conn, proxy_error)) { + try { + conn.sock->SendComplete("verack\n", 10ms, interrupt); + } catch (const std::runtime_error&) { + } + } + + CreateSock = CreateSockOrig; +} diff --git a/src/test/fuzz/integer.cpp b/src/test/fuzz/integer.cpp index ac83d91ea0..5bc99ddcb9 100644 --- a/src/test/fuzz/integer.cpp +++ b/src/test/fuzz/integer.cpp @@ -84,8 +84,7 @@ FUZZ_TARGET_INIT(integer, initialize_integer) (void)DecompressAmount(u64); (void)FormatISO8601Date(i64); (void)FormatISO8601DateTime(i64); - // FormatMoney(i) not defined when i == std::numeric_limits<int64_t>::min() - if (i64 != std::numeric_limits<int64_t>::min()) { + { int64_t parsed_money; if (ParseMoney(FormatMoney(i64), parsed_money)) { assert(parsed_money == i64); @@ -132,8 +131,7 @@ FUZZ_TARGET_INIT(integer, initialize_integer) (void)SipHashUint256Extra(u64, u64, u256, u32); (void)ToLower(ch); (void)ToUpper(ch); - // ValueFromAmount(i) not defined when i == std::numeric_limits<int64_t>::min() - if (i64 != std::numeric_limits<int64_t>::min()) { + { int64_t parsed_money; if (ParseMoney(ValueFromAmount(i64).getValStr(), parsed_money)) { assert(parsed_money == i64); diff --git a/src/test/fuzz/key.cpp b/src/test/fuzz/key.cpp index aa8f826e4a..32077b1fe2 100644 --- a/src/test/fuzz/key.cpp +++ b/src/test/fuzz/key.cpp @@ -17,7 +17,6 @@ #include <script/standard.h> #include <streams.h> #include <test/fuzz/fuzz.h> -#include <util/memory.h> #include <util/strencodings.h> #include <cassert> diff --git a/src/test/fuzz/net.cpp b/src/test/fuzz/net.cpp index b056f46f2e..272f6415a9 100644 --- a/src/test/fuzz/net.cpp +++ b/src/test/fuzz/net.cpp @@ -7,7 +7,6 @@ #include <net.h> #include <net_permissions.h> #include <netaddress.h> -#include <optional.h> #include <protocol.h> #include <random.h> #include <test/fuzz/FuzzedDataProvider.h> @@ -17,6 +16,7 @@ #include <test/util/setup_common.h> #include <cstdint> +#include <optional> #include <string> #include <vector> diff --git a/src/test/fuzz/netbase_dns_lookup.cpp b/src/test/fuzz/netbase_dns_lookup.cpp new file mode 100644 index 0000000000..cf2fa33744 --- /dev/null +++ b/src/test/fuzz/netbase_dns_lookup.cpp @@ -0,0 +1,71 @@ +// 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 <netaddress.h> +#include <netbase.h> +#include <test/fuzz/FuzzedDataProvider.h> +#include <test/fuzz/fuzz.h> +#include <test/fuzz/util.h> + +#include <cstdint> +#include <string> +#include <vector> + +FUZZ_TARGET(netbase_dns_lookup) +{ + FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()}; + const std::string name = fuzzed_data_provider.ConsumeRandomLengthString(512); + const unsigned int max_results = fuzzed_data_provider.ConsumeIntegral<unsigned int>(); + const bool allow_lookup = fuzzed_data_provider.ConsumeBool(); + const uint16_t default_port = fuzzed_data_provider.ConsumeIntegral<uint16_t>(); + + auto fuzzed_dns_lookup_function = [&](const std::string&, bool) { + std::vector<CNetAddr> resolved_addresses; + while (fuzzed_data_provider.ConsumeBool()) { + resolved_addresses.push_back(ConsumeNetAddr(fuzzed_data_provider)); + } + return resolved_addresses; + }; + + { + std::vector<CNetAddr> resolved_addresses; + if (LookupHost(name, resolved_addresses, max_results, allow_lookup, fuzzed_dns_lookup_function)) { + for (const CNetAddr& resolved_address : resolved_addresses) { + assert(!resolved_address.IsInternal()); + } + } + assert(resolved_addresses.size() <= max_results || max_results == 0); + } + { + CNetAddr resolved_address; + if (LookupHost(name, resolved_address, allow_lookup, fuzzed_dns_lookup_function)) { + assert(!resolved_address.IsInternal()); + } + } + { + std::vector<CService> resolved_services; + if (Lookup(name, resolved_services, default_port, allow_lookup, max_results, fuzzed_dns_lookup_function)) { + for (const CNetAddr& resolved_service : resolved_services) { + assert(!resolved_service.IsInternal()); + } + } + assert(resolved_services.size() <= max_results || max_results == 0); + } + { + CService resolved_service; + if (Lookup(name, resolved_service, default_port, allow_lookup, fuzzed_dns_lookup_function)) { + assert(!resolved_service.IsInternal()); + } + } + { + CService resolved_service = LookupNumeric(name, default_port, fuzzed_dns_lookup_function); + assert(!resolved_service.IsInternal()); + } + { + CSubNet resolved_subnet; + if (LookupSubNet(name, resolved_subnet, fuzzed_dns_lookup_function)) { + assert(resolved_subnet.IsValid()); + } + } +} diff --git a/src/test/fuzz/node_eviction.cpp b/src/test/fuzz/node_eviction.cpp index aaebe83c0a..70ffc6bf37 100644 --- a/src/test/fuzz/node_eviction.cpp +++ b/src/test/fuzz/node_eviction.cpp @@ -3,7 +3,6 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include <net.h> -#include <optional.h> #include <protocol.h> #include <test/fuzz/FuzzedDataProvider.h> #include <test/fuzz/fuzz.h> @@ -21,23 +20,24 @@ FUZZ_TARGET(node_eviction) std::vector<NodeEvictionCandidate> eviction_candidates; while (fuzzed_data_provider.ConsumeBool()) { eviction_candidates.push_back({ - fuzzed_data_provider.ConsumeIntegral<NodeId>(), - fuzzed_data_provider.ConsumeIntegral<int64_t>(), - fuzzed_data_provider.ConsumeIntegral<int64_t>(), - fuzzed_data_provider.ConsumeIntegral<int64_t>(), - fuzzed_data_provider.ConsumeIntegral<int64_t>(), - fuzzed_data_provider.ConsumeBool(), - fuzzed_data_provider.ConsumeBool(), - fuzzed_data_provider.ConsumeBool(), - fuzzed_data_provider.ConsumeIntegral<uint64_t>(), - fuzzed_data_provider.ConsumeBool(), - fuzzed_data_provider.ConsumeBool(), + /* id */ fuzzed_data_provider.ConsumeIntegral<NodeId>(), + /* nTimeConnected */ fuzzed_data_provider.ConsumeIntegral<int64_t>(), + /* m_min_ping_time */ std::chrono::microseconds{fuzzed_data_provider.ConsumeIntegral<int64_t>()}, + /* nLastBlockTime */ fuzzed_data_provider.ConsumeIntegral<int64_t>(), + /* nLastTXTime */ fuzzed_data_provider.ConsumeIntegral<int64_t>(), + /* fRelevantServices */ fuzzed_data_provider.ConsumeBool(), + /* fRelayTxes */ fuzzed_data_provider.ConsumeBool(), + /* fBloomFilter */ fuzzed_data_provider.ConsumeBool(), + /* nKeyedNetGroup */ fuzzed_data_provider.ConsumeIntegral<uint64_t>(), + /* prefer_evict */ fuzzed_data_provider.ConsumeBool(), + /* m_is_local */ fuzzed_data_provider.ConsumeBool(), + /* m_is_onion */ fuzzed_data_provider.ConsumeBool(), }); } // Make a copy since eviction_candidates may be in some valid but otherwise // indeterminate state after the SelectNodeToEvict(&&) call. const std::vector<NodeEvictionCandidate> eviction_candidates_copy = eviction_candidates; - const Optional<NodeId> node_to_evict = SelectNodeToEvict(std::move(eviction_candidates)); + const std::optional<NodeId> node_to_evict = SelectNodeToEvict(std::move(eviction_candidates)); if (node_to_evict) { assert(std::any_of(eviction_candidates_copy.begin(), eviction_candidates_copy.end(), [&node_to_evict](const NodeEvictionCandidate& eviction_candidate) { return *node_to_evict == eviction_candidate.id; })); } diff --git a/src/test/fuzz/p2p_transport_deserializer.cpp b/src/test/fuzz/p2p_transport_deserializer.cpp index 163f1b839e..3a1fdaad8f 100644 --- a/src/test/fuzz/p2p_transport_deserializer.cpp +++ b/src/test/fuzz/p2p_transport_deserializer.cpp @@ -10,6 +10,7 @@ #include <cassert> #include <cstdint> #include <limits> +#include <optional> #include <vector> void initialize_p2p_transport_deserializer() @@ -30,7 +31,7 @@ FUZZ_TARGET_INIT(p2p_transport_deserializer, initialize_p2p_transport_deserializ if (deserializer.Complete()) { const std::chrono::microseconds m_time{std::numeric_limits<int64_t>::max()}; uint32_t out_err_raw_size{0}; - Optional<CNetMessage> result{deserializer.GetMessage(m_time, out_err_raw_size)}; + std::optional<CNetMessage> result{deserializer.GetMessage(m_time, out_err_raw_size)}; if (result) { assert(result->m_command.size() <= CMessageHeader::COMMAND_SIZE); assert(result->m_raw_message_size <= buffer.size()); diff --git a/src/test/fuzz/parse_numbers.cpp b/src/test/fuzz/parse_numbers.cpp index ddd2bcfba3..2c546e9b4a 100644 --- a/src/test/fuzz/parse_numbers.cpp +++ b/src/test/fuzz/parse_numbers.cpp @@ -18,6 +18,12 @@ FUZZ_TARGET(parse_numbers) double d; (void)ParseDouble(random_string, &d); + 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/parse_univalue.cpp b/src/test/fuzz/parse_univalue.cpp index afe382ba21..3fffaac8d0 100644 --- a/src/test/fuzz/parse_univalue.cpp +++ b/src/test/fuzz/parse_univalue.cpp @@ -7,7 +7,6 @@ #include <rpc/client.h> #include <rpc/util.h> #include <test/fuzz/fuzz.h> -#include <util/memory.h> #include <limits> #include <string> diff --git a/src/test/fuzz/policy_estimator.cpp b/src/test/fuzz/policy_estimator.cpp index 311550f041..116b7a71d9 100644 --- a/src/test/fuzz/policy_estimator.cpp +++ b/src/test/fuzz/policy_estimator.cpp @@ -7,6 +7,7 @@ #include <test/fuzz/FuzzedDataProvider.h> #include <test/fuzz/fuzz.h> #include <test/fuzz/util.h> +#include <test/util/setup_common.h> #include <txmempool.h> #include <cstdint> diff --git a/src/test/fuzz/policy_estimator_io.cpp b/src/test/fuzz/policy_estimator_io.cpp index c24ad3d49a..9021d95954 100644 --- a/src/test/fuzz/policy_estimator_io.cpp +++ b/src/test/fuzz/policy_estimator_io.cpp @@ -6,6 +6,7 @@ #include <test/fuzz/FuzzedDataProvider.h> #include <test/fuzz/fuzz.h> #include <test/fuzz/util.h> +#include <test/util/setup_common.h> #include <cstdint> #include <vector> 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 e3571e15b7..96e1cfa08f 100644 --- a/src/test/fuzz/process_message.cpp +++ b/src/test/fuzz/process_message.cpp @@ -18,7 +18,7 @@ #include <test/util/net.h> #include <test/util/setup_common.h> #include <test/util/validation.h> -#include <util/memory.h> +#include <txorphanage.h> #include <validationinterface.h> #include <version.h> @@ -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 f62a0c64ed..203c0ef8e1 100644 --- a/src/test/fuzz/process_messages.cpp +++ b/src/test/fuzz/process_messages.cpp @@ -13,7 +13,7 @@ #include <test/util/net.h> #include <test/util/setup_common.h> #include <test/util/validation.h> -#include <util/memory.h> +#include <txorphanage.h> #include <validation.h> #include <validationinterface.h> @@ -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/psbt.cpp b/src/test/fuzz/psbt.cpp index 0b4588c4ce..d1cc6f9c7e 100644 --- a/src/test/fuzz/psbt.cpp +++ b/src/test/fuzz/psbt.cpp @@ -5,15 +5,14 @@ #include <test/fuzz/fuzz.h> #include <node/psbt.h> -#include <optional.h> #include <psbt.h> #include <pubkey.h> #include <script/script.h> #include <streams.h> -#include <util/memory.h> #include <version.h> #include <cstdint> +#include <optional> #include <string> #include <vector> @@ -40,7 +39,7 @@ FUZZ_TARGET_INIT(psbt, initialize_psbt) (void)psbt.IsNull(); - Optional<CMutableTransaction> tx = psbt.tx; + std::optional<CMutableTransaction> tx = psbt.tx; if (tx) { const CMutableTransaction& mtx = *tx; const PartiallySignedTransaction psbt_from_tx{mtx}; diff --git a/src/test/fuzz/script.cpp b/src/test/fuzz/script.cpp index 193862e847..e87ae5b04b 100644 --- a/src/test/fuzz/script.cpp +++ b/src/test/fuzz/script.cpp @@ -20,7 +20,6 @@ #include <test/fuzz/fuzz.h> #include <test/fuzz/util.h> #include <univalue.h> -#include <util/memory.h> #include <algorithm> #include <cassert> @@ -104,9 +103,11 @@ FUZZ_TARGET_INIT(script, initialize_script) (void)ScriptToAsmStr(script, true); UniValue o1(UniValue::VOBJ); - ScriptPubKeyToUniv(script, o1, true); + ScriptPubKeyToUniv(script, o1, true, true); + ScriptPubKeyToUniv(script, o1, true, false); UniValue o2(UniValue::VOBJ); - ScriptPubKeyToUniv(script, o2, false); + ScriptPubKeyToUniv(script, o2, false, true); + ScriptPubKeyToUniv(script, o2, false, false); UniValue o3(UniValue::VOBJ); ScriptToUniv(script, o3, true); UniValue o4(UniValue::VOBJ); diff --git a/src/test/fuzz/script_descriptor_cache.cpp b/src/test/fuzz/script_descriptor_cache.cpp index 1c62c018e7..6ce13d5679 100644 --- a/src/test/fuzz/script_descriptor_cache.cpp +++ b/src/test/fuzz/script_descriptor_cache.cpp @@ -2,7 +2,6 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#include <optional.h> #include <pubkey.h> #include <script/descriptor.h> #include <test/fuzz/FuzzedDataProvider.h> @@ -10,6 +9,7 @@ #include <test/fuzz/util.h> #include <cstdint> +#include <optional> #include <string> #include <vector> diff --git a/src/test/fuzz/script_flags.cpp b/src/test/fuzz/script_flags.cpp index ce8915ca2c..387f9c069c 100644 --- a/src/test/fuzz/script_flags.cpp +++ b/src/test/fuzz/script_flags.cpp @@ -5,14 +5,11 @@ #include <pubkey.h> #include <script/interpreter.h> #include <streams.h> -#include <util/memory.h> +#include <test/util/script.h> #include <version.h> #include <test/fuzz/fuzz.h> -/** Flags that are not forbidden by an assert */ -static bool IsValidFlagCombination(unsigned flags); - void initialize_script_flags() { static const ECCVerifyHandle verify_handle; @@ -75,10 +72,3 @@ FUZZ_TARGET_INIT(script_flags, initialize_script_flags) return; } } - -static bool IsValidFlagCombination(unsigned flags) -{ - if (flags & SCRIPT_VERIFY_CLEANSTACK && ~flags & (SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_WITNESS)) return false; - if (flags & SCRIPT_VERIFY_WITNESS && ~flags & SCRIPT_VERIFY_P2SH) return false; - return true; -} diff --git a/src/test/fuzz/signature_checker.cpp b/src/test/fuzz/signature_checker.cpp index 3e7b72805e..6b86c8889d 100644 --- a/src/test/fuzz/signature_checker.cpp +++ b/src/test/fuzz/signature_checker.cpp @@ -6,7 +6,8 @@ #include <script/interpreter.h> #include <test/fuzz/FuzzedDataProvider.h> #include <test/fuzz/fuzz.h> -#include <util/memory.h> +#include <test/fuzz/util.h> +#include <test/util/script.h> #include <cstdint> #include <limits> @@ -15,7 +16,7 @@ void initialize_signature_checker() { - static const auto verify_handle = MakeUnique<ECCVerifyHandle>(); + static const auto verify_handle = std::make_unique<ECCVerifyHandle>(); } namespace { @@ -57,17 +58,12 @@ FUZZ_TARGET_INIT(signature_checker, initialize_signature_checker) FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size()); const unsigned int flags = fuzzed_data_provider.ConsumeIntegral<unsigned int>(); const SigVersion sig_version = fuzzed_data_provider.PickValueInArray({SigVersion::BASE, SigVersion::WITNESS_V0}); - const std::string script_string_1 = fuzzed_data_provider.ConsumeRandomLengthString(65536); - const std::vector<uint8_t> script_bytes_1{script_string_1.begin(), script_string_1.end()}; - const std::string script_string_2 = fuzzed_data_provider.ConsumeRandomLengthString(65536); - const std::vector<uint8_t> script_bytes_2{script_string_2.begin(), script_string_2.end()}; + const auto script_1 = ConsumeScript(fuzzed_data_provider, 65536); + const auto script_2 = ConsumeScript(fuzzed_data_provider, 65536); std::vector<std::vector<unsigned char>> stack; - (void)EvalScript(stack, {script_bytes_1.begin(), script_bytes_1.end()}, flags, FuzzedSignatureChecker(fuzzed_data_provider), sig_version, nullptr); - if ((flags & SCRIPT_VERIFY_CLEANSTACK) != 0 && ((flags & SCRIPT_VERIFY_P2SH) == 0 || (flags & SCRIPT_VERIFY_WITNESS) == 0)) { + (void)EvalScript(stack, script_1, flags, FuzzedSignatureChecker(fuzzed_data_provider), sig_version, nullptr); + if (!IsValidFlagCombination(flags)) { return; } - if ((flags & SCRIPT_VERIFY_WITNESS) != 0 && (flags & SCRIPT_VERIFY_P2SH) == 0) { - return; - } - (void)VerifyScript({script_bytes_1.begin(), script_bytes_1.end()}, {script_bytes_2.begin(), script_bytes_2.end()}, nullptr, flags, FuzzedSignatureChecker(fuzzed_data_provider), nullptr); + (void)VerifyScript(script_1, script_2, nullptr, flags, FuzzedSignatureChecker(fuzzed_data_provider), nullptr); } diff --git a/src/test/fuzz/signet.cpp b/src/test/fuzz/signet.cpp index 0d1999731f..303dcf13e3 100644 --- a/src/test/fuzz/signet.cpp +++ b/src/test/fuzz/signet.cpp @@ -10,6 +10,7 @@ #include <test/fuzz/FuzzedDataProvider.h> #include <test/fuzz/fuzz.h> #include <test/fuzz/util.h> +#include <test/util/setup_common.h> #include <cstdint> #include <optional> diff --git a/src/test/fuzz/socks5.cpp b/src/test/fuzz/socks5.cpp new file mode 100644 index 0000000000..c3a6eed089 --- /dev/null +++ b/src/test/fuzz/socks5.cpp @@ -0,0 +1,45 @@ +// Copyright (c) 2020-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 <netaddress.h> +#include <netbase.h> +#include <test/fuzz/FuzzedDataProvider.h> +#include <test/fuzz/fuzz.h> +#include <test/fuzz/util.h> +#include <test/util/setup_common.h> + +#include <cstdint> +#include <string> +#include <vector> + +namespace { +int default_socks5_recv_timeout; +}; + +extern int g_socks5_recv_timeout; + +void initialize_socks5() +{ + static const auto testing_setup = MakeNoLogFileContext<const BasicTestingSetup>(); + default_socks5_recv_timeout = g_socks5_recv_timeout; +} + +FUZZ_TARGET_INIT(socks5, initialize_socks5) +{ + FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()}; + ProxyCredentials proxy_credentials; + proxy_credentials.username = fuzzed_data_provider.ConsumeRandomLengthString(512); + proxy_credentials.password = fuzzed_data_provider.ConsumeRandomLengthString(512); + InterruptSocks5(fuzzed_data_provider.ConsumeBool()); + // Set FUZZED_SOCKET_FAKE_LATENCY=1 to exercise recv timeout code paths. This + // will slow down fuzzing. + g_socks5_recv_timeout = (fuzzed_data_provider.ConsumeBool() && std::getenv("FUZZED_SOCKET_FAKE_LATENCY") != nullptr) ? 1 : default_socks5_recv_timeout; + FuzzedSock fuzzed_sock = ConsumeSock(fuzzed_data_provider); + // This Socks5(...) fuzzing harness would have caught CVE-2017-18350 within + // a few seconds of fuzzing. + (void)Socks5(fuzzed_data_provider.ConsumeRandomLengthString(512), + fuzzed_data_provider.ConsumeIntegral<uint16_t>(), + fuzzed_data_provider.ConsumeBool() ? &proxy_credentials : nullptr, + fuzzed_sock); +} diff --git a/src/test/fuzz/string.cpp b/src/test/fuzz/string.cpp index 93b4948a2f..286375f7ae 100644 --- a/src/test/fuzz/string.cpp +++ b/src/test/fuzz/string.cpp @@ -5,6 +5,7 @@ #include <blockfilter.h> #include <clientversion.h> #include <logging.h> +#include <netaddress.h> #include <netbase.h> #include <outputtype.h> #include <rpc/client.h> @@ -82,7 +83,7 @@ FUZZ_TARGET(string) #ifndef WIN32 (void)ShellEscape(random_string_1); #endif // WIN32 - int port_out; + uint16_t port_out; std::string host_out; SplitHostPort(random_string_1, port_out, host_out); (void)TimingResistantEqual(random_string_1, random_string_2); diff --git a/src/test/fuzz/system.cpp b/src/test/fuzz/system.cpp index d9571209fa..b25dcfcd3b 100644 --- a/src/test/fuzz/system.cpp +++ b/src/test/fuzz/system.cpp @@ -51,7 +51,7 @@ FUZZ_TARGET(system) // Avoid hitting: // util/system.cpp:425: void ArgsManager::AddArg(const std::string &, const std::string &, unsigned int, const OptionsCategory &): Assertion `ret.second' failed. const std::string argument_name = GetArgumentName(fuzzed_data_provider.ConsumeRandomLengthString(16)); - if (args_manager.GetArgFlags(argument_name) != nullopt) { + if (args_manager.GetArgFlags(argument_name) != std::nullopt) { return; } args_manager.AddArg(argument_name, fuzzed_data_provider.ConsumeRandomLengthString(16), fuzzed_data_provider.ConsumeIntegral<unsigned int>() & ~ArgsManager::COMMAND, options_category); @@ -63,7 +63,7 @@ FUZZ_TARGET(system) std::vector<std::string> hidden_arguments; for (const std::string& name : names) { const std::string hidden_argument = GetArgumentName(name); - if (args_manager.GetArgFlags(hidden_argument) != nullopt) { + if (args_manager.GetArgFlags(hidden_argument) != std::nullopt) { continue; } if (std::find(hidden_arguments.begin(), hidden_arguments.end(), hidden_argument) != hidden_arguments.end()) { diff --git a/src/test/fuzz/torcontrol.cpp b/src/test/fuzz/torcontrol.cpp new file mode 100644 index 0000000000..a97d3962bf --- /dev/null +++ b/src/test/fuzz/torcontrol.cpp @@ -0,0 +1,80 @@ +// Copyright (c) 2020-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 <test/fuzz/FuzzedDataProvider.h> +#include <test/fuzz/fuzz.h> +#include <test/fuzz/util.h> +#include <test/util/setup_common.h> +#include <torcontrol.h> + +#include <cstdint> +#include <string> +#include <vector> + +class DummyTorControlConnection : public TorControlConnection +{ +public: + DummyTorControlConnection() : TorControlConnection{nullptr} + { + } + + bool Connect(const std::string&, const ConnectionCB&, const ConnectionCB&) + { + return true; + } + + void Disconnect() + { + } + + bool Command(const std::string&, const ReplyHandlerCB&) + { + return true; + } +}; + +void initialize_torcontrol() +{ + static const auto testing_setup = MakeNoLogFileContext<>(); +} + +FUZZ_TARGET_INIT(torcontrol, initialize_torcontrol) +{ + FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()}; + + TorController tor_controller; + while (fuzzed_data_provider.ConsumeBool()) { + TorControlReply tor_control_reply; + CallOneOf( + fuzzed_data_provider, + [&] { + tor_control_reply.code = 250; + }, + [&] { + tor_control_reply.code = 510; + }, + [&] { + tor_control_reply.code = fuzzed_data_provider.ConsumeIntegral<int>(); + }); + tor_control_reply.lines = ConsumeRandomLengthStringVector(fuzzed_data_provider); + if (tor_control_reply.lines.empty()) { + break; + } + DummyTorControlConnection dummy_tor_control_connection; + CallOneOf( + fuzzed_data_provider, + [&] { + tor_controller.add_onion_cb(dummy_tor_control_connection, tor_control_reply); + }, + [&] { + tor_controller.auth_cb(dummy_tor_control_connection, tor_control_reply); + }, + [&] { + tor_controller.authchallenge_cb(dummy_tor_control_connection, tor_control_reply); + }, + [&] { + tor_controller.protocolinfo_cb(dummy_tor_control_connection, tor_control_reply); + }); + } +} diff --git a/src/test/fuzz/transaction.cpp b/src/test/fuzz/transaction.cpp index 13ae450756..17e4405a13 100644 --- a/src/test/fuzz/transaction.cpp +++ b/src/test/fuzz/transaction.cpp @@ -100,16 +100,9 @@ FUZZ_TARGET_INIT(transaction, initialize_transaction) (void)IsWitnessStandard(tx, coins_view_cache); UniValue u(UniValue::VOBJ); - // ValueFromAmount(i) not defined when i == std::numeric_limits<int64_t>::min() - bool skip_tx_to_univ = false; - for (const CTxOut& txout : tx.vout) { - if (txout.nValue == std::numeric_limits<int64_t>::min()) { - skip_tx_to_univ = true; - } - } - if (!skip_tx_to_univ) { - TxToUniv(tx, /* hashBlock */ {}, u); - static const uint256 u256_max(uint256S("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")); - TxToUniv(tx, u256_max, u); - } + TxToUniv(tx, /* hashBlock */ {}, /* include_addresses */ true, u); + TxToUniv(tx, /* hashBlock */ {}, /* include_addresses */ false, u); + static const uint256 u256_max(uint256S("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")); + TxToUniv(tx, u256_max, /* include_addresses */ true, u); + TxToUniv(tx, u256_max, /* include_addresses */ false, u); } 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..d786ac1db1 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, max_length); + 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 f3bc3c78ab..50d3ac66e5 100644 --- a/src/test/fuzz/util.h +++ b/src/test/fuzz/util.h @@ -10,6 +10,7 @@ #include <attributes.h> #include <chainparamsbase.h> #include <coins.h> +#include <compat.h> #include <consensus/consensus.h> #include <merkleblock.h> #include <net.h> @@ -23,14 +24,13 @@ #include <test/fuzz/FuzzedDataProvider.h> #include <test/fuzz/fuzz.h> #include <test/util/net.h> -#include <test/util/setup_common.h> #include <txmempool.h> #include <uint256.h> #include <util/time.h> -#include <util/vector.h> #include <version.h> #include <algorithm> +#include <array> #include <cstdint> #include <cstdio> #include <optional> @@ -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 { @@ -251,6 +263,25 @@ template <class T> } /** + * Sets errno to a value selected from the given std::array `errnos`. + */ +template <typename T, size_t size> +void SetFuzzedErrNo(FuzzedDataProvider& fuzzed_data_provider, const std::array<T, size>& errnos) +{ + errno = fuzzed_data_provider.PickValueInArray(errnos); +} + +/* + * Sets a fuzzed errno in the range [0, 133 (EHWPOISON)]. Can be used from functions emulating + * standard library functions that set errno, or in other contexts where the value of errno + * might be relevant for the execution path that will be taken. + */ +inline void SetFuzzedErrNo(FuzzedDataProvider& fuzzed_data_provider) noexcept +{ + errno = fuzzed_data_provider.ConsumeIntegralInRange<int>(0, 133); +} + +/** * Returns a byte vector of specified size regardless of the number of remaining bytes available * from the fuzzer. Pads with zero value bytes if needed to achieve the specified size. */ @@ -324,19 +355,6 @@ inline std::unique_ptr<CNode> ConsumeNodeAsUniquePtr(FuzzedDataProvider& fdp, co void FillNode(FuzzedDataProvider& fuzzed_data_provider, CNode& node, bool init_version) noexcept; -template <class T = const BasicTestingSetup> -std::unique_ptr<T> MakeNoLogFileContext(const std::string& chain_name = CBaseChainParams::REGTEST, const std::vector<const char*>& extra_args = {}) -{ - // Prepend default arguments for fuzzing - const std::vector<const char*> arguments = Cat( - { - "-nodebuglogfile", - }, - extra_args); - - return MakeUnique<T>(chain_name, arguments); -} - class FuzzedFileProvider { FuzzedDataProvider& m_fuzzed_data_provider; @@ -349,6 +367,7 @@ public: FILE* open() { + SetFuzzedErrNo(m_fuzzed_data_provider); if (m_fuzzed_data_provider.ConsumeBool()) { return nullptr; } @@ -390,6 +409,7 @@ public: static ssize_t read(void* cookie, char* buf, size_t size) { FuzzedFileProvider* fuzzed_file = (FuzzedFileProvider*)cookie; + SetFuzzedErrNo(fuzzed_file->m_fuzzed_data_provider); if (buf == nullptr || size == 0 || fuzzed_file->m_fuzzed_data_provider.ConsumeBool()) { return fuzzed_file->m_fuzzed_data_provider.ConsumeBool() ? 0 : -1; } @@ -408,6 +428,7 @@ public: static ssize_t write(void* cookie, const char* buf, size_t size) { FuzzedFileProvider* fuzzed_file = (FuzzedFileProvider*)cookie; + SetFuzzedErrNo(fuzzed_file->m_fuzzed_data_provider); const ssize_t n = fuzzed_file->m_fuzzed_data_provider.ConsumeIntegralInRange<ssize_t>(0, size); if (AdditionOverflow(fuzzed_file->m_offset, (int64_t)n)) { return fuzzed_file->m_fuzzed_data_provider.ConsumeBool() ? 0 : -1; @@ -418,8 +439,9 @@ public: static int seek(void* cookie, int64_t* offset, int whence) { - assert(whence == SEEK_SET || whence == SEEK_CUR); // SEEK_END not implemented yet. + assert(whence == SEEK_SET || whence == SEEK_CUR || whence == SEEK_END); FuzzedFileProvider* fuzzed_file = (FuzzedFileProvider*)cookie; + SetFuzzedErrNo(fuzzed_file->m_fuzzed_data_provider); int64_t new_offset = 0; if (whence == SEEK_SET) { new_offset = *offset; @@ -428,6 +450,12 @@ public: return -1; } new_offset = fuzzed_file->m_offset + *offset; + } else if (whence == SEEK_END) { + const int64_t n = fuzzed_file->m_fuzzed_data_provider.ConsumeIntegralInRange<int64_t>(0, 4096); + if (AdditionOverflow(n, *offset)) { + return -1; + } + new_offset = n + *offset; } if (new_offset < 0) { return -1; @@ -440,6 +468,7 @@ public: static int close(void* cookie) { FuzzedFileProvider* fuzzed_file = (FuzzedFileProvider*)cookie; + SetFuzzedErrNo(fuzzed_file->m_fuzzed_data_provider); return fuzzed_file->m_fuzzed_data_provider.ConsumeIntegralInRange<int>(-1, 0); } }; @@ -534,4 +563,198 @@ void ReadFromStream(FuzzedDataProvider& fuzzed_data_provider, Stream& stream) no } } +class FuzzedSock : public Sock +{ + FuzzedDataProvider& m_fuzzed_data_provider; + + /** + * Data to return when `MSG_PEEK` is used as a `Recv()` flag. + * If `MSG_PEEK` is used, then our `Recv()` returns some random data as usual, but on the next + * `Recv()` call we must return the same data, thus we remember it here. + */ + mutable std::optional<uint8_t> m_peek_data; + +public: + explicit FuzzedSock(FuzzedDataProvider& fuzzed_data_provider) : m_fuzzed_data_provider{fuzzed_data_provider} + { + m_socket = fuzzed_data_provider.ConsumeIntegral<SOCKET>(); + } + + ~FuzzedSock() override + { + // Sock::~Sock() will be called after FuzzedSock::~FuzzedSock() and it will call + // Sock::Reset() (not FuzzedSock::Reset()!) which will call CloseSocket(m_socket). + // Avoid closing an arbitrary file descriptor (m_socket is just a random number which + // may concide with a real opened file descriptor). + Reset(); + } + + FuzzedSock& operator=(Sock&& other) override + { + assert(false && "Move of Sock into FuzzedSock not allowed."); + return *this; + } + + void Reset() override + { + m_socket = INVALID_SOCKET; + } + + ssize_t Send(const void* data, size_t len, int flags) const override + { + constexpr std::array send_errnos{ + EACCES, + EAGAIN, + EALREADY, + EBADF, + ECONNRESET, + EDESTADDRREQ, + EFAULT, + EINTR, + EINVAL, + EISCONN, + EMSGSIZE, + ENOBUFS, + ENOMEM, + ENOTCONN, + ENOTSOCK, + EOPNOTSUPP, + EPIPE, + EWOULDBLOCK, + }; + if (m_fuzzed_data_provider.ConsumeBool()) { + return len; + } + const ssize_t r = m_fuzzed_data_provider.ConsumeIntegralInRange<ssize_t>(-1, len); + if (r == -1) { + SetFuzzedErrNo(m_fuzzed_data_provider, send_errnos); + } + return r; + } + + ssize_t Recv(void* buf, size_t len, int flags) const override + { + // Have a permanent error at recv_errnos[0] because when the fuzzed data is exhausted + // SetFuzzedErrNo() will always return the first element and we want to avoid Recv() + // returning -1 and setting errno to EAGAIN repeatedly. + constexpr std::array recv_errnos{ + ECONNREFUSED, + EAGAIN, + EBADF, + EFAULT, + EINTR, + EINVAL, + ENOMEM, + ENOTCONN, + ENOTSOCK, + EWOULDBLOCK, + }; + assert(buf != nullptr || len == 0); + if (len == 0 || m_fuzzed_data_provider.ConsumeBool()) { + const ssize_t r = m_fuzzed_data_provider.ConsumeBool() ? 0 : -1; + if (r == -1) { + SetFuzzedErrNo(m_fuzzed_data_provider, recv_errnos); + } + return r; + } + std::vector<uint8_t> random_bytes; + bool pad_to_len_bytes{m_fuzzed_data_provider.ConsumeBool()}; + if (m_peek_data.has_value()) { + // `MSG_PEEK` was used in the preceding `Recv()` call, return `m_peek_data`. + random_bytes.assign({m_peek_data.value()}); + if ((flags & MSG_PEEK) == 0) { + m_peek_data.reset(); + } + pad_to_len_bytes = false; + } else if ((flags & MSG_PEEK) != 0) { + // New call with `MSG_PEEK`. + random_bytes = m_fuzzed_data_provider.ConsumeBytes<uint8_t>(1); + if (!random_bytes.empty()) { + m_peek_data = random_bytes[0]; + pad_to_len_bytes = false; + } + } else { + random_bytes = m_fuzzed_data_provider.ConsumeBytes<uint8_t>( + m_fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, len)); + } + if (random_bytes.empty()) { + const ssize_t r = m_fuzzed_data_provider.ConsumeBool() ? 0 : -1; + if (r == -1) { + SetFuzzedErrNo(m_fuzzed_data_provider, recv_errnos); + } + return r; + } + std::memcpy(buf, random_bytes.data(), random_bytes.size()); + if (pad_to_len_bytes) { + if (len > random_bytes.size()) { + std::memset((char*)buf + random_bytes.size(), 0, len - random_bytes.size()); + } + return len; + } + if (m_fuzzed_data_provider.ConsumeBool() && std::getenv("FUZZED_SOCKET_FAKE_LATENCY") != nullptr) { + std::this_thread::sleep_for(std::chrono::milliseconds{2}); + } + return random_bytes.size(); + } + + int Connect(const sockaddr*, socklen_t) const override + { + // Have a permanent error at connect_errnos[0] because when the fuzzed data is exhausted + // SetFuzzedErrNo() will always return the first element and we want to avoid Connect() + // returning -1 and setting errno to EAGAIN repeatedly. + constexpr std::array connect_errnos{ + ECONNREFUSED, + EAGAIN, + ECONNRESET, + EHOSTUNREACH, + EINPROGRESS, + EINTR, + ENETUNREACH, + ETIMEDOUT, + }; + if (m_fuzzed_data_provider.ConsumeBool()) { + SetFuzzedErrNo(m_fuzzed_data_provider, connect_errnos); + return -1; + } + return 0; + } + + int GetSockOpt(int level, int opt_name, void* opt_val, socklen_t* opt_len) const override + { + constexpr std::array getsockopt_errnos{ + ENOMEM, + ENOBUFS, + }; + if (m_fuzzed_data_provider.ConsumeBool()) { + SetFuzzedErrNo(m_fuzzed_data_provider, getsockopt_errnos); + return -1; + } + if (opt_val == nullptr) { + return 0; + } + std::memcpy(opt_val, + ConsumeFixedLengthByteVector(m_fuzzed_data_provider, *opt_len).data(), + *opt_len); + return 0; + } + + bool Wait(std::chrono::milliseconds timeout, Event requested, Event* occurred = nullptr) const override + { + return m_fuzzed_data_provider.ConsumeBool(); + } + + bool IsConnected(std::string& errmsg) const override { + if (m_fuzzed_data_provider.ConsumeBool()) { + return true; + } + errmsg = "disconnected at random by the fuzzer"; + return false; + } +}; + +[[nodiscard]] inline FuzzedSock ConsumeSock(FuzzedDataProvider& fuzzed_data_provider) +{ + return FuzzedSock{fuzzed_data_provider}; +} + #endif // BITCOIN_TEST_FUZZ_UTIL_H diff --git a/src/test/fuzz/validation_load_mempool.cpp b/src/test/fuzz/validation_load_mempool.cpp new file mode 100644 index 0000000000..e1a21b6c53 --- /dev/null +++ b/src/test/fuzz/validation_load_mempool.cpp @@ -0,0 +1,34 @@ +// 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. + +#include <chainparamsbase.h> +#include <test/fuzz/FuzzedDataProvider.h> +#include <test/fuzz/fuzz.h> +#include <test/fuzz/util.h> +#include <test/util/setup_common.h> +#include <txmempool.h> +#include <util/time.h> +#include <validation.h> + +#include <cstdint> +#include <vector> + +void initialize_validation_load_mempool() +{ + static const auto testing_setup = MakeNoLogFileContext<const TestingSetup>(); +} + +FUZZ_TARGET_INIT(validation_load_mempool, initialize_validation_load_mempool) +{ + FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()}; + SetMockTime(ConsumeTime(fuzzed_data_provider)); + FuzzedFileProvider fuzzed_file_provider = ConsumeFile(fuzzed_data_provider); + + CTxMemPool pool{}; + auto fuzzed_fopen = [&](const fs::path&, const char*) { + return fuzzed_file_provider.open(); + }; + (void)LoadMempool(pool, ::ChainstateActive(), fuzzed_fopen); + (void)DumpMempool(pool, fuzzed_fopen, true); +} diff --git a/src/test/fuzz/versionbits.cpp b/src/test/fuzz/versionbits.cpp new file mode 100644 index 0000000000..88c1a1a9cb --- /dev/null +++ b/src/test/fuzz/versionbits.cpp @@ -0,0 +1,351 @@ +// Copyright (c) 2020-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 <chain.h> +#include <chainparams.h> +#include <consensus/params.h> +#include <primitives/block.h> +#include <versionbits.h> + +#include <test/fuzz/FuzzedDataProvider.h> +#include <test/fuzz/fuzz.h> +#include <test/fuzz/util.h> + +#include <cstdint> +#include <limits> +#include <memory> +#include <vector> + +namespace { +class TestConditionChecker : public AbstractThresholdConditionChecker +{ +private: + mutable ThresholdConditionCache m_cache; + const Consensus::Params dummy_params{}; + +public: + 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); + } + + bool Condition(const CBlockIndex* pindex, const Consensus::Params& params) const override { return Condition(pindex->nVersion); } + int64_t BeginTime(const Consensus::Params& params) const override { return m_begin; } + int64_t EndTime(const Consensus::Params& params) const override { return m_end; } + int Period(const Consensus::Params& params) const override { return m_period; } + int Threshold(const Consensus::Params& params) const override { return m_threshold; } + + ThresholdState GetStateFor(const CBlockIndex* pindexPrev) const { return AbstractThresholdConditionChecker::GetStateFor(pindexPrev, dummy_params, m_cache); } + 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(int32_t version) const + { + 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); } +}; + +/** Track blocks mined for test */ +class Blocks +{ +private: + std::vector<std::unique_ptr<CBlockIndex>> m_blocks; + const uint32_t m_start_time; + const uint32_t m_interval; + const int32_t m_signal; + const int32_t m_no_signal; + +public: + Blocks(uint32_t start_time, uint32_t interval, int32_t signal, int32_t no_signal) + : m_start_time{start_time}, m_interval{interval}, m_signal{signal}, m_no_signal{no_signal} {} + + size_t size() const { return m_blocks.size(); } + + CBlockIndex* tip() const + { + return m_blocks.empty() ? nullptr : m_blocks.back().get(); + } + + CBlockIndex* mine_block(bool signal) + { + CBlockHeader header; + header.nVersion = signal ? m_signal : m_no_signal; + header.nTime = m_start_time + m_blocks.size() * m_interval; + header.nBits = 0x1d00ffff; + + auto current_block = std::make_unique<CBlockIndex>(header); + current_block->pprev = tip(); + current_block->nHeight = m_blocks.size(); + current_block->BuildSkip(); + + return m_blocks.emplace_back(std::move(current_block)).get(); + } +}; + +std::unique_ptr<const CChainParams> g_params; + +void initialize() +{ + // this is actually comparatively slow, so only do it once + g_params = CreateChainParams(ArgsManager{}, CBaseChainParams::MAIN); + assert(g_params != nullptr); +} + +constexpr uint32_t MAX_START_TIME = 4102444800; // 2100-01-01 + +FUZZ_TARGET_INIT(versionbits, initialize) +{ + 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()); + + FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size()); + + // making period/max_periods larger slows these tests down significantly + const int period = 32; + const size_t max_periods = 16; + const size_t max_blocks = 2 * period * max_periods; + + const int threshold = fuzzed_data_provider.ConsumeIntegralInRange(1, period); + assert(0 < threshold && threshold <= period); // must be able to both pass and fail threshold! + + // 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_START_TIME > interval * max_blocks); + + 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>(); + const int32_t ver_nosignal = fuzzed_data_provider.ConsumeIntegral<int32_t>(); + + // select deployment parameters: bit, start time, timeout + const int bit = fuzzed_data_provider.ConsumeIntegralInRange<int>(0, VERSIONBITS_NUM_BITS - 1); + + bool always_active_test = false; + bool never_active_test = false; + int64_t start_time; + int64_t timeout; + if (fuzzed_data_provider.ConsumeBool()) { + // pick the timestamp to switch based on a block + // note states will change *after* these blocks because mediantime lags + int start_block = fuzzed_data_provider.ConsumeIntegralInRange<int>(0, period * (max_periods - 3)); + int end_block = fuzzed_data_provider.ConsumeIntegralInRange<int>(start_block, period * (max_periods - 3)); + + start_time = block_start_time + start_block * interval; + timeout = block_start_time + end_block * interval; + + assert(start_time <= timeout); + + // allow for times to not exactly match a block + if (fuzzed_data_provider.ConsumeBool()) start_time += interval / 2; + if (fuzzed_data_provider.ConsumeBool()) timeout += interval / 2; + + // this may make timeout too early; if so, don't run the test + if (start_time > timeout) return; + } else { + if (fuzzed_data_provider.ConsumeBool()) { + start_time = Consensus::BIP9Deployment::ALWAYS_ACTIVE; + timeout = Consensus::BIP9Deployment::NO_TIMEOUT; + always_active_test = true; + } else { + start_time = 1199145601; // January 1, 2008 + timeout = 1230767999; // December 31, 2008 + never_active_test = true; + } + } + + TestConditionChecker checker(start_time, timeout, period, threshold, bit); + + // Early exit if the versions don't signal sensibly for the deployment + if (!checker.Condition(ver_signal)) return; + if (checker.Condition(ver_nosignal)) return; + if (ver_nosignal < 0) return; + + // 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); + + /* Strategy: + * * we will mine a final period worth of blocks, with + * randomised signalling according to a mask + * * but before we mine those blocks, we will mine some + * randomised number of prior periods; with either all + * or no blocks in the period signalling + * + * We establish the mask first, then consume "bools" until + * we run out of fuzz data to work out how many prior periods + * there are and which ones will signal. + */ + + // establish the mask + const uint32_t signalling_mask = fuzzed_data_provider.ConsumeIntegral<uint32_t>(); + + // mine prior periods + while (fuzzed_data_provider.remaining_bytes() > 0) { + // all blocks in these periods either do or don't signal + bool signal = fuzzed_data_provider.ConsumeBool(); + for (int b = 0; b < period; ++b) { + blocks.mine_block(signal); + } + + // don't risk exceeding max_blocks or times may wrap around + 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 + + // now we mine the final period and check that everything looks sane + + // count the number of signalling blocks + int blocks_sig = 0; + + // get the info for the first block of the period + CBlockIndex* prev = blocks.tip(); + const int exp_since = checker.GetStateSinceHeightFor(prev); + const ThresholdState exp_state = checker.GetStateFor(prev); + BIP9Stats last_stats = checker.GetStateStatisticsFor(prev); + + int prev_next_height = (prev == nullptr ? 0 : prev->nHeight + 1); + assert(exp_since <= prev_next_height); + + // mine (period-1) blocks and check state + for (int b = 1; b < period; ++b) { + const bool signal = (signalling_mask >> (b % 32)) & 1; + if (signal) ++blocks_sig; + + CBlockIndex* current_block = blocks.mine_block(signal); + + // verify that signalling attempt was interpreted correctly + assert(checker.Condition(current_block) == signal); + + // state and since don't change within the period + const ThresholdState state = checker.GetStateFor(current_block); + const int since = checker.GetStateSinceHeightFor(current_block); + assert(state == exp_state); + assert(since == exp_since); + + // GetStateStatistics may crash when state is not STARTED + if (state != ThresholdState::STARTED) continue; + + // check that after mining this block stats change as expected + const BIP9Stats stats = checker.GetStateStatisticsFor(current_block); + assert(stats.period == period); + assert(stats.threshold == threshold); + assert(stats.elapsed == b); + assert(stats.count == last_stats.count + (signal ? 1 : 0)); + assert(stats.possible == (stats.count + period >= stats.elapsed + threshold)); + last_stats = stats; + } + + if (exp_state == ThresholdState::STARTED) { + // double check that stats.possible is sane + if (blocks_sig >= threshold - 1) assert(last_stats.possible); + } + + // mine the final block + bool signal = (signalling_mask >> (period % 32)) & 1; + if (signal) ++blocks_sig; + CBlockIndex* current_block = blocks.mine_block(signal); + assert(checker.Condition(current_block) == signal); + + // GetStateStatistics is safe on a period boundary + // and has progressed to a new period + const BIP9Stats stats = checker.GetStateStatisticsFor(current_block); + assert(stats.period == period); + assert(stats.threshold == threshold); + assert(stats.elapsed == 0); + assert(stats.count == 0); + assert(stats.possible == true); + + // More interesting is whether the state changed. + const ThresholdState state = checker.GetStateFor(current_block); + const int since = checker.GetStateSinceHeightFor(current_block); + + // since is straightforward: + assert(since % period == 0); + assert(0 <= since && since <= current_block->nHeight + 1); + if (state == exp_state) { + assert(since == exp_since); + } else { + assert(since == current_block->nHeight + 1); + } + + // state is where everything interesting is + switch (state) { + case ThresholdState::DEFINED: + assert(since == 0); + assert(exp_state == ThresholdState::DEFINED); + assert(current_block->GetMedianTimePast() < checker.m_begin); + assert(current_block->GetMedianTimePast() < checker.m_end); + break; + case ThresholdState::STARTED: + assert(current_block->GetMedianTimePast() >= checker.m_begin); + assert(current_block->GetMedianTimePast() < checker.m_end); + if (exp_state == ThresholdState::STARTED) { + assert(blocks_sig < threshold); + } else { + assert(exp_state == ThresholdState::DEFINED); + } + break; + case ThresholdState::LOCKED_IN: + assert(exp_state == ThresholdState::STARTED); + assert(current_block->GetMedianTimePast() < checker.m_end); + assert(blocks_sig >= threshold); + break; + case ThresholdState::ACTIVE: + assert(exp_state == ThresholdState::ACTIVE || exp_state == ThresholdState::LOCKED_IN); + break; + case ThresholdState::FAILED: + assert(current_block->GetMedianTimePast() >= checker.m_end); + assert(exp_state != ThresholdState::LOCKED_IN && exp_state != ThresholdState::ACTIVE); + break; + default: + assert(false); + } + + 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); + } + + // "always active" has additional restrictions + if (always_active_test) { + assert(state == ThresholdState::ACTIVE); + assert(exp_state == ThresholdState::ACTIVE); + assert(since == 0); + } else { + // except for always active, the initial state is always DEFINED + assert(since > 0 || state == ThresholdState::DEFINED); + assert(exp_since > 0 || exp_state == ThresholdState::DEFINED); + } + + // "never active" does too + if (never_active_test) { + assert(state == ThresholdState::FAILED); + assert(since == period); + if (exp_since == 0) { + assert(exp_state == ThresholdState::DEFINED); + } else { + assert(exp_state == ThresholdState::FAILED); + } + } +} +} // namespace diff --git a/src/test/i2p_tests.cpp b/src/test/i2p_tests.cpp new file mode 100644 index 0000000000..334f71106c --- /dev/null +++ b/src/test/i2p_tests.cpp @@ -0,0 +1,44 @@ +// Copyright (c) 2021-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 <i2p.h> +#include <netaddress.h> +#include <test/util/logging.h> +#include <test/util/net.h> +#include <test/util/setup_common.h> +#include <threadinterrupt.h> +#include <util/system.h> + +#include <boost/test/unit_test.hpp> + +#include <memory> +#include <string> + +BOOST_FIXTURE_TEST_SUITE(i2p_tests, BasicTestingSetup) + +BOOST_AUTO_TEST_CASE(unlimited_recv) +{ + auto CreateSockOrig = CreateSock; + + // Mock CreateSock() to create MockSock. + CreateSock = [](const CService&) { + return std::make_unique<StaticContentsSock>(std::string(i2p::sam::MAX_MSG_SIZE + 1, 'a')); + }; + + CThreadInterrupt interrupt; + i2p::sam::Session session(GetDataDir() / "test_i2p_private_key", CService{}, &interrupt); + + { + ASSERT_DEBUG_LOG("Creating SAM session"); + ASSERT_DEBUG_LOG("too many bytes without a terminator"); + + i2p::Connection conn; + bool proxy_error; + BOOST_REQUIRE(!session.Connect(CService{}, conn, proxy_error)); + } + + CreateSock = CreateSockOrig; +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/miner_tests.cpp b/src/test/miner_tests.cpp index aa628371e6..9ba004cc38 100644 --- a/src/test/miner_tests.cpp +++ b/src/test/miner_tests.cpp @@ -44,7 +44,7 @@ BlockAssembler MinerTestingSetup::AssemblerForTest(const CChainParams& params) options.nBlockMaxWeight = MAX_BLOCK_WEIGHT; options.blockMinFeeRate = blockMinFeeRate; - return BlockAssembler(*m_node.mempool, params, options); + return BlockAssembler(::ChainstateActive(), *m_node.mempool, params, options); } constexpr static struct { diff --git a/src/test/net_peer_eviction_tests.cpp b/src/test/net_peer_eviction_tests.cpp new file mode 100644 index 0000000000..31d391bf7d --- /dev/null +++ b/src/test/net_peer_eviction_tests.cpp @@ -0,0 +1,348 @@ +// 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 <net.h> +#include <test/util/setup_common.h> + +#include <boost/test/unit_test.hpp> + +#include <algorithm> +#include <functional> +#include <optional> +#include <unordered_set> +#include <vector> + +BOOST_FIXTURE_TEST_SUITE(net_peer_eviction_tests, BasicTestingSetup) + +namespace { +constexpr int NODE_EVICTION_TEST_ROUNDS{10}; +constexpr int NODE_EVICTION_TEST_UP_TO_N_NODES{200}; +} // namespace + +std::vector<NodeEvictionCandidate> GetRandomNodeEvictionCandidates(const int n_candidates, FastRandomContext& random_context) +{ + std::vector<NodeEvictionCandidate> candidates; + for (int id = 0; id < n_candidates; ++id) { + candidates.push_back({ + /* id */ id, + /* nTimeConnected */ static_cast<int64_t>(random_context.randrange(100)), + /* m_min_ping_time */ std::chrono::microseconds{random_context.randrange(100)}, + /* nLastBlockTime */ static_cast<int64_t>(random_context.randrange(100)), + /* nLastTXTime */ static_cast<int64_t>(random_context.randrange(100)), + /* fRelevantServices */ random_context.randbool(), + /* fRelayTxes */ random_context.randbool(), + /* fBloomFilter */ random_context.randbool(), + /* nKeyedNetGroup */ random_context.randrange(100), + /* prefer_evict */ random_context.randbool(), + /* m_is_local */ random_context.randbool(), + /* m_is_onion */ random_context.randbool(), + }); + } + return candidates; +} + +// Create `num_peers` random nodes, apply setup function `candidate_setup_fn`, +// call ProtectEvictionCandidatesByRatio() to apply protection logic, and then +// return true if all of `protected_peer_ids` and none of `unprotected_peer_ids` +// are protected from eviction, i.e. removed from the eviction candidates. +bool IsProtected(int num_peers, + std::function<void(NodeEvictionCandidate&)> candidate_setup_fn, + const std::unordered_set<NodeId>& protected_peer_ids, + const std::unordered_set<NodeId>& unprotected_peer_ids, + FastRandomContext& random_context) +{ + std::vector<NodeEvictionCandidate> candidates{GetRandomNodeEvictionCandidates(num_peers, random_context)}; + for (NodeEvictionCandidate& candidate : candidates) { + candidate_setup_fn(candidate); + } + Shuffle(candidates.begin(), candidates.end(), random_context); + + const size_t size{candidates.size()}; + const size_t expected{size - size / 2}; // Expect half the candidates will be protected. + ProtectEvictionCandidatesByRatio(candidates); + BOOST_CHECK_EQUAL(candidates.size(), expected); + + size_t unprotected_count{0}; + for (const NodeEvictionCandidate& candidate : candidates) { + if (protected_peer_ids.count(candidate.id)) { + // this peer should have been removed from the eviction candidates + BOOST_TEST_MESSAGE(strprintf("expected candidate to be protected: %d", candidate.id)); + return false; + } + if (unprotected_peer_ids.count(candidate.id)) { + // this peer remains in the eviction candidates, as expected + ++unprotected_count; + } + } + + const bool is_protected{unprotected_count == unprotected_peer_ids.size()}; + if (!is_protected) { + BOOST_TEST_MESSAGE(strprintf("unprotected: expected %d, actual %d", + unprotected_peer_ids.size(), unprotected_count)); + } + return is_protected; +} + +BOOST_AUTO_TEST_CASE(peer_protection_test) +{ + FastRandomContext random_context{true}; + int num_peers{12}; + + // Expect half of the peers with greatest uptime (the lowest nTimeConnected) + // to be protected from eviction. + BOOST_CHECK(IsProtected( + num_peers, [](NodeEvictionCandidate& c) { + c.nTimeConnected = c.id; + c.m_is_onion = c.m_is_local = false; + }, + /* protected_peer_ids */ {0, 1, 2, 3, 4, 5}, + /* unprotected_peer_ids */ {6, 7, 8, 9, 10, 11}, + random_context)); + + // Verify in the opposite direction. + BOOST_CHECK(IsProtected( + num_peers, [num_peers](NodeEvictionCandidate& c) { + c.nTimeConnected = num_peers - c.id; + c.m_is_onion = c.m_is_local = false; + }, + /* protected_peer_ids */ {6, 7, 8, 9, 10, 11}, + /* unprotected_peer_ids */ {0, 1, 2, 3, 4, 5}, + random_context)); + + // Test protection of onion and localhost peers... + + // Expect 1/4 onion peers to be protected from eviction, + // independently of other characteristics. + BOOST_CHECK(IsProtected( + num_peers, [](NodeEvictionCandidate& c) { + c.m_is_onion = (c.id == 3 || c.id == 8 || c.id == 9); + }, + /* protected_peer_ids */ {3, 8, 9}, + /* unprotected_peer_ids */ {}, + random_context)); + + // Expect 1/4 onion peers and 1/4 of the others to be protected + // from eviction, sorted by longest uptime (lowest nTimeConnected). + BOOST_CHECK(IsProtected( + num_peers, [](NodeEvictionCandidate& c) { + c.nTimeConnected = c.id; + c.m_is_local = false; + c.m_is_onion = (c.id == 3 || c.id > 7); + }, + /* protected_peer_ids */ {0, 1, 2, 3, 8, 9}, + /* unprotected_peer_ids */ {4, 5, 6, 7, 10, 11}, + random_context)); + + // Expect 1/4 localhost peers to be protected from eviction, + // if no onion peers. + BOOST_CHECK(IsProtected( + num_peers, [](NodeEvictionCandidate& c) { + c.m_is_onion = false; + c.m_is_local = (c.id == 1 || c.id == 9 || c.id == 11); + }, + /* protected_peer_ids */ {1, 9, 11}, + /* unprotected_peer_ids */ {}, + random_context)); + + // Expect 1/4 localhost peers and 1/4 of the other peers to be protected, + // sorted by longest uptime (lowest nTimeConnected), if no onion peers. + BOOST_CHECK(IsProtected( + num_peers, [](NodeEvictionCandidate& c) { + c.nTimeConnected = c.id; + c.m_is_onion = false; + c.m_is_local = (c.id > 6); + }, + /* protected_peer_ids */ {0, 1, 2, 7, 8, 9}, + /* unprotected_peer_ids */ {3, 4, 5, 6, 10, 11}, + random_context)); + + // Combined test: expect 1/4 onion and 2 localhost peers to be protected + // from eviction, sorted by longest uptime. + BOOST_CHECK(IsProtected( + num_peers, [](NodeEvictionCandidate& c) { + c.nTimeConnected = c.id; + c.m_is_onion = (c.id == 0 || c.id == 5 || c.id == 10); + c.m_is_local = (c.id == 1 || c.id == 9 || c.id == 11); + }, + /* protected_peer_ids */ {0, 1, 2, 5, 9, 10}, + /* unprotected_peer_ids */ {3, 4, 6, 7, 8, 11}, + random_context)); + + // Combined test: expect having only 1 onion to allow allocating the + // remaining 2 of the 1/4 to localhost peers, sorted by longest uptime. + BOOST_CHECK(IsProtected( + num_peers + 4, [](NodeEvictionCandidate& c) { + c.nTimeConnected = c.id; + c.m_is_onion = (c.id == 15); + c.m_is_local = (c.id > 6 && c.id < 11); + }, + /* protected_peer_ids */ {0, 1, 2, 3, 7, 8, 9, 15}, + /* unprotected_peer_ids */ {4, 5, 6, 10, 11, 12, 13, 14}, + random_context)); + + // Combined test: expect 2 onions (< 1/4) to allow allocating the minimum 2 + // localhost peers, sorted by longest uptime. + BOOST_CHECK(IsProtected( + num_peers, [](NodeEvictionCandidate& c) { + c.nTimeConnected = c.id; + c.m_is_onion = (c.id == 7 || c.id == 9); + c.m_is_local = (c.id == 6 || c.id == 11); + }, + /* protected_peer_ids */ {0, 1, 6, 7, 9, 11}, + /* unprotected_peer_ids */ {2, 3, 4, 5, 8, 10}, + random_context)); + + // Combined test: when > 1/4, expect max 1/4 onion and 2 localhost peers + // to be protected from eviction, sorted by longest uptime. + BOOST_CHECK(IsProtected( + num_peers, [](NodeEvictionCandidate& c) { + c.nTimeConnected = c.id; + c.m_is_onion = (c.id > 3 && c.id < 8); + c.m_is_local = (c.id > 7); + }, + /* protected_peer_ids */ {0, 4, 5, 6, 8, 9}, + /* unprotected_peer_ids */ {1, 2, 3, 7, 10, 11}, + random_context)); + + // Combined test: idem > 1/4 with only 8 peers: expect 2 onion and 2 + // localhost peers (1/4 + 2) to be protected, sorted by longest uptime. + BOOST_CHECK(IsProtected( + 8, [](NodeEvictionCandidate& c) { + c.nTimeConnected = c.id; + c.m_is_onion = (c.id > 1 && c.id < 5); + c.m_is_local = (c.id > 4); + }, + /* protected_peer_ids */ {2, 3, 5, 6}, + /* unprotected_peer_ids */ {0, 1, 4, 7}, + random_context)); + + // Combined test: idem > 1/4 with only 6 peers: expect 1 onion peer and no + // localhost peers (1/4 + 0) to be protected, sorted by longest uptime. + BOOST_CHECK(IsProtected( + 6, [](NodeEvictionCandidate& c) { + c.nTimeConnected = c.id; + c.m_is_onion = (c.id == 4 || c.id == 5); + c.m_is_local = (c.id == 3); + }, + /* protected_peer_ids */ {0, 1, 4}, + /* unprotected_peer_ids */ {2, 3, 5}, + random_context)); +} + +// Returns true if any of the node ids in node_ids are selected for eviction. +bool IsEvicted(std::vector<NodeEvictionCandidate> candidates, const std::unordered_set<NodeId>& node_ids, FastRandomContext& random_context) +{ + Shuffle(candidates.begin(), candidates.end(), random_context); + const std::optional<NodeId> evicted_node_id = SelectNodeToEvict(std::move(candidates)); + if (!evicted_node_id) { + return false; + } + return node_ids.count(*evicted_node_id); +} + +// Create number_of_nodes random nodes, apply setup function candidate_setup_fn, +// apply eviction logic and then return true if any of the node ids in node_ids +// are selected for eviction. +bool IsEvicted(const int number_of_nodes, std::function<void(NodeEvictionCandidate&)> candidate_setup_fn, const std::unordered_set<NodeId>& node_ids, FastRandomContext& random_context) +{ + std::vector<NodeEvictionCandidate> candidates = GetRandomNodeEvictionCandidates(number_of_nodes, random_context); + for (NodeEvictionCandidate& candidate : candidates) { + candidate_setup_fn(candidate); + } + return IsEvicted(candidates, node_ids, random_context); +} + +BOOST_AUTO_TEST_CASE(peer_eviction_test) +{ + FastRandomContext random_context{true}; + + for (int i = 0; i < NODE_EVICTION_TEST_ROUNDS; ++i) { + for (int number_of_nodes = 0; number_of_nodes < NODE_EVICTION_TEST_UP_TO_N_NODES; ++number_of_nodes) { + // Four nodes with the highest keyed netgroup values should be + // protected from eviction. + BOOST_CHECK(!IsEvicted( + number_of_nodes, [number_of_nodes](NodeEvictionCandidate& candidate) { + candidate.nKeyedNetGroup = number_of_nodes - candidate.id; + }, + {0, 1, 2, 3}, random_context)); + + // Eight nodes with the lowest minimum ping time should be protected + // from eviction. + BOOST_CHECK(!IsEvicted( + number_of_nodes, [](NodeEvictionCandidate& candidate) { + candidate.m_min_ping_time = std::chrono::microseconds{candidate.id}; + }, + {0, 1, 2, 3, 4, 5, 6, 7}, random_context)); + + // Four nodes that most recently sent us novel transactions accepted + // into our mempool should be protected from eviction. + BOOST_CHECK(!IsEvicted( + number_of_nodes, [number_of_nodes](NodeEvictionCandidate& candidate) { + candidate.nLastTXTime = number_of_nodes - candidate.id; + }, + {0, 1, 2, 3}, random_context)); + + // Up to eight non-tx-relay peers that most recently sent us novel + // blocks should be protected from eviction. + BOOST_CHECK(!IsEvicted( + number_of_nodes, [number_of_nodes](NodeEvictionCandidate& candidate) { + candidate.nLastBlockTime = number_of_nodes - candidate.id; + if (candidate.id <= 7) { + candidate.fRelayTxes = false; + candidate.fRelevantServices = true; + } + }, + {0, 1, 2, 3, 4, 5, 6, 7}, random_context)); + + // Four peers that most recently sent us novel blocks should be + // protected from eviction. + BOOST_CHECK(!IsEvicted( + number_of_nodes, [number_of_nodes](NodeEvictionCandidate& candidate) { + candidate.nLastBlockTime = number_of_nodes - candidate.id; + }, + {0, 1, 2, 3}, random_context)); + + // Combination of the previous two tests. + BOOST_CHECK(!IsEvicted( + number_of_nodes, [number_of_nodes](NodeEvictionCandidate& candidate) { + candidate.nLastBlockTime = number_of_nodes - candidate.id; + if (candidate.id <= 7) { + candidate.fRelayTxes = false; + candidate.fRelevantServices = true; + } + }, + {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}, random_context)); + + // Combination of all tests above. + BOOST_CHECK(!IsEvicted( + number_of_nodes, [number_of_nodes](NodeEvictionCandidate& candidate) { + candidate.nKeyedNetGroup = number_of_nodes - candidate.id; // 4 protected + candidate.m_min_ping_time = std::chrono::microseconds{candidate.id}; // 8 protected + candidate.nLastTXTime = number_of_nodes - candidate.id; // 4 protected + candidate.nLastBlockTime = number_of_nodes - candidate.id; // 4 protected + }, + {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19}, random_context)); + + // An eviction is expected given >= 29 random eviction candidates. The eviction logic protects at most + // four peers by net group, eight by lowest ping time, four by last time of novel tx, up to eight non-tx-relay + // peers by last novel block time, and four more peers by last novel block time. + if (number_of_nodes >= 29) { + BOOST_CHECK(SelectNodeToEvict(GetRandomNodeEvictionCandidates(number_of_nodes, random_context))); + } + + // No eviction is expected given <= 20 random eviction candidates. The eviction logic protects at least + // four peers by net group, eight by lowest ping time, four by last time of novel tx and four peers by last + // novel block time. + if (number_of_nodes <= 20) { + BOOST_CHECK(!SelectNodeToEvict(GetRandomNodeEvictionCandidates(number_of_nodes, random_context))); + } + + // Cases left to test: + // * "If any remaining peers are preferred for eviction consider only them. [...]" + // * "Identify the network group with the most connections and youngest member. [...]" + } + } +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/net_tests.cpp b/src/test/net_tests.cpp index 1c7c35528e..8eab26f3d5 100644 --- a/src/test/net_tests.cpp +++ b/src/test/net_tests.cpp @@ -8,13 +8,12 @@ #include <clientversion.h> #include <cstdint> #include <net.h> +#include <netaddress.h> #include <netbase.h> -#include <optional.h> #include <serialize.h> #include <span.h> #include <streams.h> #include <test/util/setup_common.h> -#include <util/memory.h> #include <util/strencodings.h> #include <util/string.h> #include <util/system.h> @@ -25,6 +24,7 @@ #include <algorithm> #include <ios> #include <memory> +#include <optional> #include <string> using namespace std::literals; @@ -92,7 +92,7 @@ BOOST_FIXTURE_TEST_SUITE(net_tests, BasicTestingSetup) BOOST_AUTO_TEST_CASE(cnode_listen_port) { // test default - uint16_t port = GetListenPort(); + uint16_t port{GetListenPort()}; BOOST_CHECK(port == Params().GetDefaultPort()); // test set port uint16_t altPort = 12345; @@ -188,7 +188,7 @@ BOOST_AUTO_TEST_CASE(cnode_simple_test) CAddress addr = CAddress(CService(ipv4Addr, 7777), NODE_NETWORK); std::string pszDest; - std::unique_ptr<CNode> pnode1 = MakeUnique<CNode>( + std::unique_ptr<CNode> pnode1 = std::make_unique<CNode>( id++, NODE_NETWORK, hSocket, addr, /* nKeyedNetGroupIn = */ 0, /* nLocalHostNonceIn = */ 0, @@ -203,7 +203,7 @@ BOOST_AUTO_TEST_CASE(cnode_simple_test) BOOST_CHECK(pnode1->m_inbound_onion == false); BOOST_CHECK_EQUAL(pnode1->ConnectedThroughNetwork(), Network::NET_IPV4); - std::unique_ptr<CNode> pnode2 = MakeUnique<CNode>( + std::unique_ptr<CNode> pnode2 = std::make_unique<CNode>( id++, NODE_NETWORK, hSocket, addr, /* nKeyedNetGroupIn = */ 1, /* nLocalHostNonceIn = */ 1, @@ -218,7 +218,7 @@ BOOST_AUTO_TEST_CASE(cnode_simple_test) BOOST_CHECK(pnode2->m_inbound_onion == false); BOOST_CHECK_EQUAL(pnode2->ConnectedThroughNetwork(), Network::NET_IPV4); - std::unique_ptr<CNode> pnode3 = MakeUnique<CNode>( + std::unique_ptr<CNode> pnode3 = std::make_unique<CNode>( id++, NODE_NETWORK, hSocket, addr, /* nKeyedNetGroupIn = */ 0, /* nLocalHostNonceIn = */ 0, @@ -233,7 +233,7 @@ BOOST_AUTO_TEST_CASE(cnode_simple_test) BOOST_CHECK(pnode3->m_inbound_onion == false); BOOST_CHECK_EQUAL(pnode3->ConnectedThroughNetwork(), Network::NET_IPV4); - std::unique_ptr<CNode> pnode4 = MakeUnique<CNode>( + std::unique_ptr<CNode> pnode4 = std::make_unique<CNode>( id++, NODE_NETWORK, hSocket, addr, /* nKeyedNetGroupIn = */ 1, /* nLocalHostNonceIn = */ 1, @@ -322,6 +322,7 @@ BOOST_AUTO_TEST_CASE(cnetaddr_basic) BOOST_REQUIRE(addr.IsValid()); BOOST_REQUIRE(addr.IsTor()); + BOOST_CHECK(!addr.IsI2P()); BOOST_CHECK(!addr.IsBindAny()); BOOST_CHECK(addr.IsAddrV1Compatible()); BOOST_CHECK_EQUAL(addr.ToString(), "6hzph5hv6337r6p2.onion"); @@ -332,6 +333,7 @@ BOOST_AUTO_TEST_CASE(cnetaddr_basic) BOOST_REQUIRE(addr.IsValid()); BOOST_REQUIRE(addr.IsTor()); + BOOST_CHECK(!addr.IsI2P()); BOOST_CHECK(!addr.IsBindAny()); BOOST_CHECK(!addr.IsAddrV1Compatible()); BOOST_CHECK_EQUAL(addr.ToString(), torv3_addr); @@ -352,6 +354,35 @@ BOOST_AUTO_TEST_CASE(cnetaddr_basic) // TOR, invalid base32 BOOST_CHECK(!addr.SetSpecial(std::string{"mf*g zak.onion"})); + // I2P + const char* i2p_addr = "UDHDrtrcetjm5sxzskjyr5ztpeszydbh4dpl3pl4utgqqw2v4jna.b32.I2P"; + BOOST_REQUIRE(addr.SetSpecial(i2p_addr)); + BOOST_REQUIRE(addr.IsValid()); + BOOST_REQUIRE(addr.IsI2P()); + + BOOST_CHECK(!addr.IsTor()); + BOOST_CHECK(!addr.IsBindAny()); + BOOST_CHECK(!addr.IsAddrV1Compatible()); + BOOST_CHECK_EQUAL(addr.ToString(), ToLower(i2p_addr)); + + // I2P, correct length, but decodes to less than the expected number of bytes. + BOOST_CHECK(!addr.SetSpecial("udhdrtrcetjm5sxzskjyr5ztpeszydbh4dpl3pl4utgqqw2v4jn=.b32.i2p")); + + // I2P, extra unnecessary padding + BOOST_CHECK(!addr.SetSpecial("udhdrtrcetjm5sxzskjyr5ztpeszydbh4dpl3pl4utgqqw2v4jna=.b32.i2p")); + + // I2P, malicious + BOOST_CHECK(!addr.SetSpecial("udhdrtrcetjm5sxzskjyr5ztpeszydbh4dpl3pl4utgqqw2v\0wtf.b32.i2p"s)); + + // I2P, valid but unsupported (56 Base32 characters) + // See "Encrypted LS with Base 32 Addresses" in + // https://geti2p.net/spec/encryptedleaseset.txt + BOOST_CHECK( + !addr.SetSpecial("pg6mmjiyjmcrsslvykfwnntlaru7p5svn6y2ymmju6nubxndf4pscsad.b32.i2p")); + + // I2P, invalid base32 + BOOST_CHECK(!addr.SetSpecial(std::string{"tp*szydbh4dp.b32.i2p"})); + // Internal addr.SetInternal("esffpp"); BOOST_REQUIRE(!addr.IsValid()); // "internal" is considered invalid @@ -365,6 +396,60 @@ BOOST_AUTO_TEST_CASE(cnetaddr_basic) BOOST_CHECK(!addr.SetSpecial("totally bogus")); } +BOOST_AUTO_TEST_CASE(cnetaddr_tostring_canonical_ipv6) +{ + // Test that CNetAddr::ToString formats IPv6 addresses with zero compression as described in + // RFC 5952 ("A Recommendation for IPv6 Address Text Representation"). + const std::map<std::string, std::string> canonical_representations_ipv6{ + {"0000:0000:0000:0000:0000:0000:0000:0000", "::"}, + {"000:0000:000:00:0:00:000:0000", "::"}, + {"000:000:000:000:000:000:000:000", "::"}, + {"00:00:00:00:00:00:00:00", "::"}, + {"0:0:0:0:0:0:0:0", "::"}, + {"0:0:0:0:0:0:0:1", "::1"}, + {"2001:0:0:1:0:0:0:1", "2001:0:0:1::1"}, + {"2001:0db8:0:0:1:0:0:1", "2001:db8::1:0:0:1"}, + {"2001:0db8:85a3:0000:0000:8a2e:0370:7334", "2001:db8:85a3::8a2e:370:7334"}, + {"2001:0db8::0001", "2001:db8::1"}, + {"2001:0db8::0001:0000", "2001:db8::1:0"}, + {"2001:0db8::1:0:0:1", "2001:db8::1:0:0:1"}, + {"2001:db8:0000:0:1::1", "2001:db8::1:0:0:1"}, + {"2001:db8:0000:1:1:1:1:1", "2001:db8:0:1:1:1:1:1"}, + {"2001:db8:0:0:0:0:2:1", "2001:db8::2:1"}, + {"2001:db8:0:0:0::1", "2001:db8::1"}, + {"2001:db8:0:0:1:0:0:1", "2001:db8::1:0:0:1"}, + {"2001:db8:0:0:1::1", "2001:db8::1:0:0:1"}, + {"2001:DB8:0:0:1::1", "2001:db8::1:0:0:1"}, + {"2001:db8:0:0::1", "2001:db8::1"}, + {"2001:db8:0:0:aaaa::1", "2001:db8::aaaa:0:0:1"}, + {"2001:db8:0:1:1:1:1:1", "2001:db8:0:1:1:1:1:1"}, + {"2001:db8:0::1", "2001:db8::1"}, + {"2001:db8:85a3:0:0:8a2e:370:7334", "2001:db8:85a3::8a2e:370:7334"}, + {"2001:db8::0:1", "2001:db8::1"}, + {"2001:db8::0:1:0:0:1", "2001:db8::1:0:0:1"}, + {"2001:DB8::1", "2001:db8::1"}, + {"2001:db8::1", "2001:db8::1"}, + {"2001:db8::1:0:0:1", "2001:db8::1:0:0:1"}, + {"2001:db8::1:1:1:1:1", "2001:db8:0:1:1:1:1:1"}, + {"2001:db8::aaaa:0:0:1", "2001:db8::aaaa:0:0:1"}, + {"2001:db8:aaaa:bbbb:cccc:dddd:0:1", "2001:db8:aaaa:bbbb:cccc:dddd:0:1"}, + {"2001:db8:aaaa:bbbb:cccc:dddd::1", "2001:db8:aaaa:bbbb:cccc:dddd:0:1"}, + {"2001:db8:aaaa:bbbb:cccc:dddd:eeee:0001", "2001:db8:aaaa:bbbb:cccc:dddd:eeee:1"}, + {"2001:db8:aaaa:bbbb:cccc:dddd:eeee:001", "2001:db8:aaaa:bbbb:cccc:dddd:eeee:1"}, + {"2001:db8:aaaa:bbbb:cccc:dddd:eeee:01", "2001:db8:aaaa:bbbb:cccc:dddd:eeee:1"}, + {"2001:db8:aaaa:bbbb:cccc:dddd:eeee:1", "2001:db8:aaaa:bbbb:cccc:dddd:eeee:1"}, + {"2001:db8:aaaa:bbbb:cccc:dddd:eeee:aaaa", "2001:db8:aaaa:bbbb:cccc:dddd:eeee:aaaa"}, + {"2001:db8:aaaa:bbbb:cccc:dddd:eeee:AAAA", "2001:db8:aaaa:bbbb:cccc:dddd:eeee:aaaa"}, + {"2001:db8:aaaa:bbbb:cccc:dddd:eeee:AaAa", "2001:db8:aaaa:bbbb:cccc:dddd:eeee:aaaa"}, + }; + for (const auto& [input_address, expected_canonical_representation_output] : canonical_representations_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_AUTO_TEST_CASE(cnetaddr_serialize_v1) { CNetAddr addr; @@ -680,7 +765,7 @@ BOOST_AUTO_TEST_CASE(ipv4_peer_with_ipv6_addrMe_test) in_addr ipv4AddrPeer; ipv4AddrPeer.s_addr = 0xa0b0c001; CAddress addr = CAddress(CService(ipv4AddrPeer, 7777), NODE_NETWORK); - std::unique_ptr<CNode> pnode = MakeUnique<CNode>(0, NODE_NETWORK, INVALID_SOCKET, addr, /* nKeyedNetGroupIn */ 0, /* nLocalHostNonceIn */ 0, CAddress{}, /* pszDest */ std::string{}, ConnectionType::OUTBOUND_FULL_RELAY, /* inbound_onion */ false); + std::unique_ptr<CNode> pnode = std::make_unique<CNode>(0, NODE_NETWORK, INVALID_SOCKET, addr, /* nKeyedNetGroupIn */ 0, /* nLocalHostNonceIn */ 0, CAddress{}, /* pszDest */ std::string{}, ConnectionType::OUTBOUND_FULL_RELAY, /* inbound_onion */ false); pnode->fSuccessfullyConnected.store(true); // the peer claims to be reaching us via IPv6 @@ -772,162 +857,4 @@ BOOST_AUTO_TEST_CASE(LocalAddress_BasicLifecycle) BOOST_CHECK_EQUAL(IsLocal(addr), false); } -BOOST_AUTO_TEST_CASE(PoissonNextSend) -{ - g_mock_deterministic_tests = true; - - int64_t now = 5000; - int average_interval_seconds = 600; - - auto poisson = ::PoissonNextSend(now, average_interval_seconds); - std::chrono::microseconds poisson_chrono = ::PoissonNextSend(std::chrono::microseconds{now}, std::chrono::seconds{average_interval_seconds}); - - BOOST_CHECK_EQUAL(poisson, poisson_chrono.count()); - - g_mock_deterministic_tests = false; -} - -std::vector<NodeEvictionCandidate> GetRandomNodeEvictionCandidates(const int n_candidates, FastRandomContext& random_context) -{ - std::vector<NodeEvictionCandidate> candidates; - for (int id = 0; id < n_candidates; ++id) { - candidates.push_back({ - /* id */ id, - /* nTimeConnected */ static_cast<int64_t>(random_context.randrange(100)), - /* m_min_ping_time */ static_cast<int64_t>(random_context.randrange(100)), - /* nLastBlockTime */ static_cast<int64_t>(random_context.randrange(100)), - /* nLastTXTime */ static_cast<int64_t>(random_context.randrange(100)), - /* fRelevantServices */ random_context.randbool(), - /* fRelayTxes */ random_context.randbool(), - /* fBloomFilter */ random_context.randbool(), - /* nKeyedNetGroup */ random_context.randrange(100), - /* prefer_evict */ random_context.randbool(), - /* m_is_local */ random_context.randbool(), - }); - } - return candidates; -} - -// Returns true if any of the node ids in node_ids are selected for eviction. -bool IsEvicted(std::vector<NodeEvictionCandidate> candidates, const std::vector<NodeId>& node_ids, FastRandomContext& random_context) -{ - Shuffle(candidates.begin(), candidates.end(), random_context); - const Optional<NodeId> evicted_node_id = SelectNodeToEvict(std::move(candidates)); - if (!evicted_node_id) { - return false; - } - return std::find(node_ids.begin(), node_ids.end(), *evicted_node_id) != node_ids.end(); -} - -// Create number_of_nodes random nodes, apply setup function candidate_setup_fn, -// apply eviction logic and then return true if any of the node ids in node_ids -// are selected for eviction. -bool IsEvicted(const int number_of_nodes, std::function<void(NodeEvictionCandidate&)> candidate_setup_fn, const std::vector<NodeId>& node_ids, FastRandomContext& random_context) -{ - std::vector<NodeEvictionCandidate> candidates = GetRandomNodeEvictionCandidates(number_of_nodes, random_context); - for (NodeEvictionCandidate& candidate : candidates) { - candidate_setup_fn(candidate); - } - return IsEvicted(candidates, node_ids, random_context); -} - -namespace { -constexpr int NODE_EVICTION_TEST_ROUNDS{10}; -constexpr int NODE_EVICTION_TEST_UP_TO_N_NODES{200}; -} // namespace - -BOOST_AUTO_TEST_CASE(node_eviction_test) -{ - FastRandomContext random_context{true}; - - for (int i = 0; i < NODE_EVICTION_TEST_ROUNDS; ++i) { - for (int number_of_nodes = 0; number_of_nodes < NODE_EVICTION_TEST_UP_TO_N_NODES; ++number_of_nodes) { - // Four nodes with the highest keyed netgroup values should be - // protected from eviction. - BOOST_CHECK(!IsEvicted( - number_of_nodes, [number_of_nodes](NodeEvictionCandidate& candidate) { - candidate.nKeyedNetGroup = number_of_nodes - candidate.id; - }, - {0, 1, 2, 3}, random_context)); - - // Eight nodes with the lowest minimum ping time should be protected - // from eviction. - BOOST_CHECK(!IsEvicted( - number_of_nodes, [](NodeEvictionCandidate& candidate) { - candidate.m_min_ping_time = candidate.id; - }, - {0, 1, 2, 3, 4, 5, 6, 7}, random_context)); - - // Four nodes that most recently sent us novel transactions accepted - // into our mempool should be protected from eviction. - BOOST_CHECK(!IsEvicted( - number_of_nodes, [number_of_nodes](NodeEvictionCandidate& candidate) { - candidate.nLastTXTime = number_of_nodes - candidate.id; - }, - {0, 1, 2, 3}, random_context)); - - // Up to eight non-tx-relay peers that most recently sent us novel - // blocks should be protected from eviction. - BOOST_CHECK(!IsEvicted( - number_of_nodes, [number_of_nodes](NodeEvictionCandidate& candidate) { - candidate.nLastBlockTime = number_of_nodes - candidate.id; - if (candidate.id <= 7) { - candidate.fRelayTxes = false; - candidate.fRelevantServices = true; - } - }, - {0, 1, 2, 3, 4, 5, 6, 7}, random_context)); - - // Four peers that most recently sent us novel blocks should be - // protected from eviction. - BOOST_CHECK(!IsEvicted( - number_of_nodes, [number_of_nodes](NodeEvictionCandidate& candidate) { - candidate.nLastBlockTime = number_of_nodes - candidate.id; - }, - {0, 1, 2, 3}, random_context)); - - // Combination of the previous two tests. - BOOST_CHECK(!IsEvicted( - number_of_nodes, [number_of_nodes](NodeEvictionCandidate& candidate) { - candidate.nLastBlockTime = number_of_nodes - candidate.id; - if (candidate.id <= 7) { - candidate.fRelayTxes = false; - candidate.fRelevantServices = true; - } - }, - {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}, random_context)); - - // Combination of all tests above. - BOOST_CHECK(!IsEvicted( - number_of_nodes, [number_of_nodes](NodeEvictionCandidate& candidate) { - candidate.nKeyedNetGroup = number_of_nodes - candidate.id; // 4 protected - candidate.m_min_ping_time = candidate.id; // 8 protected - candidate.nLastTXTime = number_of_nodes - candidate.id; // 4 protected - candidate.nLastBlockTime = number_of_nodes - candidate.id; // 4 protected - }, - {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19}, random_context)); - - // An eviction is expected given >= 29 random eviction candidates. The eviction logic protects at most - // four peers by net group, eight by lowest ping time, four by last time of novel tx, up to eight non-tx-relay - // peers by last novel block time, and four more peers by last novel block time. - if (number_of_nodes >= 29) { - BOOST_CHECK(SelectNodeToEvict(GetRandomNodeEvictionCandidates(number_of_nodes, random_context))); - } - - // No eviction is expected given <= 20 random eviction candidates. The eviction logic protects at least - // four peers by net group, eight by lowest ping time, four by last time of novel tx and four peers by last - // novel block time. - if (number_of_nodes <= 20) { - BOOST_CHECK(!SelectNodeToEvict(GetRandomNodeEvictionCandidates(number_of_nodes, random_context))); - } - - // Cases left to test: - // * "Protect the half of the remaining nodes which have been connected the longest. [...]" - // * "Pick out up to 1/4 peers that are localhost, sorted by longest uptime. [...]" - // * "If any remaining peers are preferred for eviction consider only them. [...]" - // * "Identify the network group with the most connections and youngest member. [...]" - } - } -} - BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/netbase_tests.cpp b/src/test/netbase_tests.cpp index 66ad7bb5ea..33b56624a8 100644 --- a/src/test/netbase_tests.cpp +++ b/src/test/netbase_tests.cpp @@ -3,6 +3,7 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include <net_permissions.h> +#include <netaddress.h> #include <netbase.h> #include <protocol.h> #include <serialize.h> @@ -83,31 +84,31 @@ BOOST_AUTO_TEST_CASE(netbase_properties) } -bool static TestSplitHost(std::string test, std::string host, int port) +bool static TestSplitHost(const std::string& test, const std::string& host, uint16_t port) { std::string hostOut; - int portOut = -1; + uint16_t portOut{0}; SplitHostPort(test, portOut, hostOut); return hostOut == host && port == portOut; } BOOST_AUTO_TEST_CASE(netbase_splithost) { - BOOST_CHECK(TestSplitHost("www.bitcoincore.org", "www.bitcoincore.org", -1)); - BOOST_CHECK(TestSplitHost("[www.bitcoincore.org]", "www.bitcoincore.org", -1)); + BOOST_CHECK(TestSplitHost("www.bitcoincore.org", "www.bitcoincore.org", 0)); + BOOST_CHECK(TestSplitHost("[www.bitcoincore.org]", "www.bitcoincore.org", 0)); BOOST_CHECK(TestSplitHost("www.bitcoincore.org:80", "www.bitcoincore.org", 80)); BOOST_CHECK(TestSplitHost("[www.bitcoincore.org]:80", "www.bitcoincore.org", 80)); - BOOST_CHECK(TestSplitHost("127.0.0.1", "127.0.0.1", -1)); + BOOST_CHECK(TestSplitHost("127.0.0.1", "127.0.0.1", 0)); BOOST_CHECK(TestSplitHost("127.0.0.1:8333", "127.0.0.1", 8333)); - BOOST_CHECK(TestSplitHost("[127.0.0.1]", "127.0.0.1", -1)); + BOOST_CHECK(TestSplitHost("[127.0.0.1]", "127.0.0.1", 0)); BOOST_CHECK(TestSplitHost("[127.0.0.1]:8333", "127.0.0.1", 8333)); - BOOST_CHECK(TestSplitHost("::ffff:127.0.0.1", "::ffff:127.0.0.1", -1)); + BOOST_CHECK(TestSplitHost("::ffff:127.0.0.1", "::ffff:127.0.0.1", 0)); BOOST_CHECK(TestSplitHost("[::ffff:127.0.0.1]:8333", "::ffff:127.0.0.1", 8333)); BOOST_CHECK(TestSplitHost("[::]:8333", "::", 8333)); - BOOST_CHECK(TestSplitHost("::8333", "::8333", -1)); + BOOST_CHECK(TestSplitHost("::8333", "::8333", 0)); BOOST_CHECK(TestSplitHost(":8333", "", 8333)); BOOST_CHECK(TestSplitHost("[]:8333", "", 8333)); - BOOST_CHECK(TestSplitHost("", "", -1)); + BOOST_CHECK(TestSplitHost("", "", 0)); } bool static TestParse(std::string src, std::string canon) diff --git a/src/test/ref_tests.cpp b/src/test/ref_tests.cpp deleted file mode 100644 index 0ec0799fbc..0000000000 --- a/src/test/ref_tests.cpp +++ /dev/null @@ -1,33 +0,0 @@ -// 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. - -#include <util/ref.h> - -#include <boost/test/unit_test.hpp> - -BOOST_AUTO_TEST_SUITE(ref_tests) - -BOOST_AUTO_TEST_CASE(ref_test) -{ - util::Ref ref; - BOOST_CHECK(!ref.Has<int>()); - BOOST_CHECK_THROW(ref.Get<int>(), NonFatalCheckError); - int value = 5; - ref.Set(value); - BOOST_CHECK(ref.Has<int>()); - BOOST_CHECK_EQUAL(ref.Get<int>(), 5); - ++ref.Get<int>(); - BOOST_CHECK_EQUAL(ref.Get<int>(), 6); - BOOST_CHECK_EQUAL(value, 6); - ++value; - BOOST_CHECK_EQUAL(value, 7); - BOOST_CHECK_EQUAL(ref.Get<int>(), 7); - BOOST_CHECK(!ref.Has<bool>()); - BOOST_CHECK_THROW(ref.Get<bool>(), NonFatalCheckError); - ref.Clear(); - BOOST_CHECK(!ref.Has<int>()); - BOOST_CHECK_THROW(ref.Get<int>(), NonFatalCheckError); -} - -BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/rpc_tests.cpp b/src/test/rpc_tests.cpp index b54cbb3f00..67e70b3bc3 100644 --- a/src/test/rpc_tests.cpp +++ b/src/test/rpc_tests.cpp @@ -10,9 +10,10 @@ #include <interfaces/chain.h> #include <node/context.h> #include <test/util/setup_common.h> -#include <util/ref.h> #include <util/time.h> +#include <any> + #include <boost/algorithm/string.hpp> #include <boost/test/unit_test.hpp> @@ -32,11 +33,10 @@ UniValue RPCTestingSetup::CallRPC(std::string args) boost::split(vArgs, args, boost::is_any_of(" \t")); std::string strMethod = vArgs[0]; vArgs.erase(vArgs.begin()); - util::Ref context{m_node}; + std::any context{&m_node}; JSONRPCRequest request(context); request.strMethod = strMethod; request.params = RPCConvertValues(strMethod, vArgs); - request.fHelp = false; if (RPCIsInWarmup(nullptr)) SetRPCWarmupFinished(); try { UniValue result = tableRPC.execute(request); @@ -174,6 +174,16 @@ BOOST_AUTO_TEST_CASE(rpc_format_monetary_values) BOOST_CHECK_EQUAL(ValueFromAmount(COIN/1000000).write(), "0.00000100"); BOOST_CHECK_EQUAL(ValueFromAmount(COIN/10000000).write(), "0.00000010"); BOOST_CHECK_EQUAL(ValueFromAmount(COIN/100000000).write(), "0.00000001"); + + BOOST_CHECK_EQUAL(ValueFromAmount(std::numeric_limits<CAmount>::max()).write(), "92233720368.54775807"); + BOOST_CHECK_EQUAL(ValueFromAmount(std::numeric_limits<CAmount>::max() - 1).write(), "92233720368.54775806"); + BOOST_CHECK_EQUAL(ValueFromAmount(std::numeric_limits<CAmount>::max() - 2).write(), "92233720368.54775805"); + BOOST_CHECK_EQUAL(ValueFromAmount(std::numeric_limits<CAmount>::max() - 3).write(), "92233720368.54775804"); + // ... + BOOST_CHECK_EQUAL(ValueFromAmount(std::numeric_limits<CAmount>::min() + 3).write(), "-92233720368.54775805"); + BOOST_CHECK_EQUAL(ValueFromAmount(std::numeric_limits<CAmount>::min() + 2).write(), "-92233720368.54775806"); + BOOST_CHECK_EQUAL(ValueFromAmount(std::numeric_limits<CAmount>::min() + 1).write(), "-92233720368.54775807"); + BOOST_CHECK_EQUAL(ValueFromAmount(std::numeric_limits<CAmount>::min()).write(), "-92233720368.54775808"); } static UniValue ValueFromString(const std::string &str) diff --git a/src/test/sanity_tests.cpp b/src/test/sanity_tests.cpp index 3e4b963fe3..496292875d 100644 --- a/src/test/sanity_tests.cpp +++ b/src/test/sanity_tests.cpp @@ -13,7 +13,6 @@ BOOST_FIXTURE_TEST_SUITE(sanity_tests, BasicTestingSetup) BOOST_AUTO_TEST_CASE(basic_sanity) { - BOOST_CHECK_MESSAGE(glibc_sanity_test() == true, "libc sanity test"); BOOST_CHECK_MESSAGE(glibcxx_sanity_test() == true, "stdlib sanity test"); BOOST_CHECK_MESSAGE(ECC_InitSanityCheck() == true, "secp256k1 sanity test"); BOOST_CHECK_MESSAGE(ChronoSanityCheck() == true, "chrono epoch test"); diff --git a/src/test/sock_tests.cpp b/src/test/sock_tests.cpp index ed9780dfb5..400de875b7 100644 --- a/src/test/sock_tests.cpp +++ b/src/test/sock_tests.cpp @@ -4,11 +4,13 @@ #include <compat.h> #include <test/util/setup_common.h> +#include <threadinterrupt.h> #include <util/sock.h> #include <util/system.h> #include <boost/test/unit_test.hpp> +#include <cassert> #include <thread> using namespace std::chrono_literals; @@ -144,6 +146,35 @@ BOOST_AUTO_TEST_CASE(wait) waiter.join(); } +BOOST_AUTO_TEST_CASE(recv_until_terminator_limit) +{ + constexpr auto timeout = 1min; // High enough so that it is never hit. + CThreadInterrupt interrupt; + int s[2]; + CreateSocketPair(s); + + Sock sock_send(s[0]); + Sock sock_recv(s[1]); + + std::thread receiver([&sock_recv, &timeout, &interrupt]() { + constexpr size_t max_data{10}; + bool threw_as_expected{false}; + // BOOST_CHECK_EXCEPTION() writes to some variables shared with the main thread which + // creates a data race. So mimic it manually. + try { + sock_recv.RecvUntilTerminator('\n', timeout, interrupt, max_data); + } catch (const std::runtime_error& e) { + threw_as_expected = HasReason("too many bytes without a terminator")(e); + } + assert(threw_as_expected); + }); + + BOOST_REQUIRE_NO_THROW(sock_send.SendComplete("1234567", timeout, interrupt)); + BOOST_REQUIRE_NO_THROW(sock_send.SendComplete("89a\n", timeout, interrupt)); + + receiver.join(); +} + #endif /* WIN32 */ BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/util/logging.cpp b/src/test/util/logging.cpp index c1dd47f06c..66f4efc139 100644 --- a/src/test/util/logging.cpp +++ b/src/test/util/logging.cpp @@ -7,7 +7,6 @@ #include <logging.h> #include <noui.h> #include <tinyformat.h> -#include <util/memory.h> #include <stdexcept> diff --git a/src/test/util/mining.cpp b/src/test/util/mining.cpp index 0c6487fbfa..3fc3329da2 100644 --- a/src/test/util/mining.cpp +++ b/src/test/util/mining.cpp @@ -41,7 +41,7 @@ CTxIn MineBlock(const NodeContext& node, const CScript& coinbase_scriptPubKey) std::shared_ptr<CBlock> PrepareBlock(const NodeContext& node, const CScript& coinbase_scriptPubKey) { auto block = std::make_shared<CBlock>( - BlockAssembler{*Assert(node.mempool), Params()} + BlockAssembler{::ChainstateActive(), *Assert(node.mempool), Params()} .CreateNewBlock(coinbase_scriptPubKey) ->block); diff --git a/src/test/util/net.h b/src/test/util/net.h index e25036be26..2b7988413f 100644 --- a/src/test/util/net.h +++ b/src/test/util/net.h @@ -5,7 +5,13 @@ #ifndef BITCOIN_TEST_UTIL_NET_H #define BITCOIN_TEST_UTIL_NET_H +#include <compat.h> #include <net.h> +#include <util/sock.h> + +#include <cassert> +#include <cstring> +#include <string> struct ConnmanTestMsg : public CConnman { using CConnman::CConnman; @@ -61,4 +67,67 @@ constexpr ConnectionType ALL_CONNECTION_TYPES[]{ ConnectionType::ADDR_FETCH, }; +/** + * A mocked Sock alternative that returns a statically contained data upon read and succeeds + * and ignores all writes. The data to be returned is given to the constructor and when it is + * exhausted an EOF is returned by further reads. + */ +class StaticContentsSock : public Sock +{ +public: + explicit StaticContentsSock(const std::string& contents) : m_contents{contents}, m_consumed{0} + { + // Just a dummy number that is not INVALID_SOCKET. + static_assert(INVALID_SOCKET != 1000); + m_socket = 1000; + } + + ~StaticContentsSock() override { Reset(); } + + StaticContentsSock& operator=(Sock&& other) override + { + assert(false && "Move of Sock into MockSock not allowed."); + return *this; + } + + void Reset() override + { + m_socket = INVALID_SOCKET; + } + + ssize_t Send(const void*, size_t len, int) const override { return len; } + + ssize_t Recv(void* buf, size_t len, int flags) const override + { + const size_t consume_bytes{std::min(len, m_contents.size() - m_consumed)}; + std::memcpy(buf, m_contents.data() + m_consumed, consume_bytes); + if ((flags & MSG_PEEK) == 0) { + m_consumed += consume_bytes; + } + return consume_bytes; + } + + int Connect(const sockaddr*, socklen_t) const override { return 0; } + + int GetSockOpt(int level, int opt_name, void* opt_val, socklen_t* opt_len) const override + { + std::memset(opt_val, 0x0, *opt_len); + return 0; + } + + bool Wait(std::chrono::milliseconds timeout, + Event requested, + Event* occurred = nullptr) const override + { + if (occurred != nullptr) { + *occurred = requested; + } + return true; + } + +private: + const std::string m_contents; + mutable size_t m_consumed; +}; + #endif // BITCOIN_TEST_UTIL_NET_H diff --git a/src/test/util/script.cpp b/src/test/util/script.cpp new file mode 100644 index 0000000000..a5852daa60 --- /dev/null +++ b/src/test/util/script.cpp @@ -0,0 +1,13 @@ +// 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 <script/interpreter.h> +#include <test/util/script.h> + +bool IsValidFlagCombination(unsigned flags) +{ + if (flags & SCRIPT_VERIFY_CLEANSTACK && ~flags & (SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_WITNESS)) return false; + if (flags & SCRIPT_VERIFY_WITNESS && ~flags & SCRIPT_VERIFY_P2SH) return false; + return true; +} diff --git a/src/test/util/script.h b/src/test/util/script.h index abd14c2067..428b3e10b3 100644 --- a/src/test/util/script.h +++ b/src/test/util/script.h @@ -18,4 +18,7 @@ static const CScript P2WSH_OP_TRUE{ return hash; }())}; +/** Flags that are not forbidden by an assert in script validation */ +bool IsValidFlagCombination(unsigned flags); + #endif // BITCOIN_TEST_UTIL_SCRIPT_H diff --git a/src/test/util/setup_common.cpp b/src/test/util/setup_common.cpp index 1ffe435531..f7800aefca 100644 --- a/src/test/util/setup_common.cpp +++ b/src/test/util/setup_common.cpp @@ -4,6 +4,7 @@ #include <test/util/setup_common.h> +#include <addrman.h> #include <banman.h> #include <chainparams.h> #include <consensus/consensus.h> @@ -25,7 +26,6 @@ #include <script/sigcache.h> #include <streams.h> #include <txdb.h> -#include <util/memory.h> #include <util/strencodings.h> #include <util/string.h> #include <util/time.h> @@ -131,7 +131,7 @@ ChainTestingSetup::ChainTestingSetup(const std::string& chainName, const std::ve { // We have to run a scheduler thread to prevent ActivateBestChain // from blocking due to queue overrun. - m_node.scheduler = MakeUnique<CScheduler>(); + m_node.scheduler = std::make_unique<CScheduler>(); m_node.scheduler->m_service_thread = std::thread([&] { TraceThread("scheduler", [&] { m_node.scheduler->serviceQueue(); }); }); GetMainSignals().RegisterBackgroundSignalScheduler(*m_node.scheduler); @@ -156,6 +156,7 @@ ChainTestingSetup::~ChainTestingSetup() GetMainSignals().UnregisterBackgroundSignalScheduler(); m_node.connman.reset(); m_node.banman.reset(); + m_node.addrman.reset(); m_node.args = nullptr; UnloadBlockIndex(m_node.mempool.get(), *m_node.chainman); m_node.mempool.reset(); @@ -179,7 +180,7 @@ TestingSetup::TestingSetup(const std::string& chainName, const std::vector<const assert(!::ChainstateActive().CanFlushToDisk()); ::ChainstateActive().InitCoinsCache(1 << 23); assert(::ChainstateActive().CanFlushToDisk()); - if (!LoadGenesisBlock(chainparams)) { + if (!::ChainstateActive().LoadGenesisBlock(chainparams)) { throw std::runtime_error("LoadGenesisBlock failed."); } @@ -188,11 +189,12 @@ TestingSetup::TestingSetup(const std::string& chainName, const std::vector<const throw std::runtime_error(strprintf("ActivateBestChain failed. (%s)", state.ToString())); } - m_node.banman = MakeUnique<BanMan>(GetDataDir() / "banlist.dat", nullptr, DEFAULT_MISBEHAVING_BANTIME); - m_node.connman = MakeUnique<CConnman>(0x1337, 0x1337); // Deterministic randomness for tests. - m_node.peerman = PeerManager::make(chainparams, *m_node.connman, m_node.banman.get(), - *m_node.scheduler, *m_node.chainman, *m_node.mempool, - false); + m_node.addrman = std::make_unique<CAddrMan>(); + m_node.banman = std::make_unique<BanMan>(GetDataDir() / "banlist.dat", nullptr, DEFAULT_MISBEHAVING_BANTIME); + m_node.connman = std::make_unique<CConnman>(0x1337, 0x1337, *m_node.addrman); // Deterministic randomness for tests. + m_node.peerman = PeerManager::make(chainparams, *m_node.connman, *m_node.addrman, + m_node.banman.get(), *m_node.scheduler, *m_node.chainman, + *m_node.mempool, false); { CConnman::Options options; options.m_msgproc = m_node.peerman.get(); @@ -245,13 +247,13 @@ CBlock TestChain100Setup::CreateAndProcessBlock(const std::vector<CMutableTransa { const CChainParams& chainparams = Params(); CTxMemPool empty_pool; - CBlock block = BlockAssembler(empty_pool, chainparams).CreateNewBlock(scriptPubKey)->block; + CBlock block = BlockAssembler(::ChainstateActive(), empty_pool, chainparams).CreateNewBlock(scriptPubKey)->block; Assert(block.vtx.size() == 1); for (const CMutableTransaction& tx : txns) { block.vtx.push_back(MakeTransactionRef(tx)); } - RegenerateCommitments(block); + RegenerateCommitments(block, WITH_LOCK(::cs_main, return std::ref(g_chainman.m_blockman))); while (!CheckProofOfWork(block.GetHash(), block.nBits, chainparams.GetConsensus())) ++block.nNonce; diff --git a/src/test/util/setup_common.h b/src/test/util/setup_common.h index 33f24e7c44..7323f1f0b6 100644 --- a/src/test/util/setup_common.h +++ b/src/test/util/setup_common.h @@ -15,6 +15,7 @@ #include <txmempool.h> #include <util/check.h> #include <util/string.h> +#include <util/vector.h> #include <type_traits> #include <vector> @@ -152,6 +153,23 @@ struct TestChain100DeterministicSetup : public TestChain100Setup { TestChain100DeterministicSetup() : TestChain100Setup(true) { } }; +/** + * Make a test setup that has disk access to the debug.log file disabled. Can + * be used in "hot loops", for example fuzzing or benchmarking. + */ +template <class T = const BasicTestingSetup> +std::unique_ptr<T> MakeNoLogFileContext(const std::string& chain_name = CBaseChainParams::REGTEST, const std::vector<const char*>& extra_args = {}) +{ + const std::vector<const char*> arguments = Cat( + { + "-nodebuglogfile", + "-nodebug", + }, + extra_args); + + return std::make_unique<T>(chain_name, arguments); +} + class CTxMemPoolEntry; struct TestMemPoolEntryHelper diff --git a/src/test/util_tests.cpp b/src/test/util_tests.cpp index 845854bd4b..5ac09b05db 100644 --- a/src/test/util_tests.cpp +++ b/src/test/util_tests.cpp @@ -7,7 +7,6 @@ #include <clientversion.h> #include <hash.h> // For Hash() #include <key.h> // For CKey -#include <optional.h> #include <sync.h> #include <test/util/logging.h> #include <test/util/setup_common.h> @@ -23,6 +22,7 @@ #include <util/vector.h> #include <array> +#include <optional> #include <stdint.h> #include <string.h> #include <thread> @@ -38,6 +38,7 @@ #include <boost/test/unit_test.hpp> using namespace std::literals; +static const std::string STRING_WITH_EMBEDDED_NULL_CHAR{"1"s "\0" "1"s}; /* defined in logging.cpp */ namespace BCLog { @@ -71,11 +72,14 @@ BOOST_AUTO_TEST_CASE(util_datadir) BOOST_AUTO_TEST_CASE(util_check) { // Check that Assert can forward - const std::unique_ptr<int> p_two = Assert(MakeUnique<int>(2)); + const std::unique_ptr<int> p_two = Assert(std::make_unique<int>(2)); // Check that Assert works on lvalues and rvalues 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) @@ -227,9 +231,9 @@ public: bool default_int = false; bool default_bool = false; const char* string_value = nullptr; - Optional<int64_t> int_value; - Optional<bool> bool_value; - Optional<std::vector<std::string>> list_value; + std::optional<int64_t> int_value; + std::optional<bool> bool_value; + std::optional<std::vector<std::string>> list_value; const char* error = nullptr; explicit Expect(util::SettingsValue s) : setting(std::move(s)) {} @@ -1180,6 +1184,16 @@ BOOST_AUTO_TEST_CASE(util_FormatMoney) BOOST_CHECK_EQUAL(FormatMoney(COIN/1000000), "0.000001"); BOOST_CHECK_EQUAL(FormatMoney(COIN/10000000), "0.0000001"); BOOST_CHECK_EQUAL(FormatMoney(COIN/100000000), "0.00000001"); + + BOOST_CHECK_EQUAL(FormatMoney(std::numeric_limits<CAmount>::max()), "92233720368.54775807"); + BOOST_CHECK_EQUAL(FormatMoney(std::numeric_limits<CAmount>::max() - 1), "92233720368.54775806"); + BOOST_CHECK_EQUAL(FormatMoney(std::numeric_limits<CAmount>::max() - 2), "92233720368.54775805"); + BOOST_CHECK_EQUAL(FormatMoney(std::numeric_limits<CAmount>::max() - 3), "92233720368.54775804"); + // ... + BOOST_CHECK_EQUAL(FormatMoney(std::numeric_limits<CAmount>::min() + 3), "-92233720368.54775805"); + BOOST_CHECK_EQUAL(FormatMoney(std::numeric_limits<CAmount>::min() + 2), "-92233720368.54775806"); + BOOST_CHECK_EQUAL(FormatMoney(std::numeric_limits<CAmount>::min() + 1), "-92233720368.54775807"); + BOOST_CHECK_EQUAL(FormatMoney(std::numeric_limits<CAmount>::min()), "-92233720368.54775808"); } BOOST_AUTO_TEST_CASE(util_ParseMoney) @@ -1262,7 +1276,7 @@ BOOST_AUTO_TEST_CASE(util_ParseMoney) // Parsing strings with embedded NUL characters should fail BOOST_CHECK(!ParseMoney("\0-1"s, ret)); - BOOST_CHECK(!ParseMoney("\0" "1"s, ret)); + BOOST_CHECK(!ParseMoney(STRING_WITH_EMBEDDED_NULL_CHAR, ret)); BOOST_CHECK(!ParseMoney("1\0"s, ret)); } @@ -1439,10 +1453,7 @@ 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 - const char test_bytes[] = {'1', 0, '1'}; - std::string teststr(test_bytes, sizeof(test_bytes)); - BOOST_CHECK(!ParseInt32(teststr, &n)); // no embedded NULs + BOOST_CHECK(!ParseInt32(STRING_WITH_EMBEDDED_NULL_CHAR, &n)); // Overflow and underflow BOOST_CHECK(!ParseInt32("-2147483649", nullptr)); BOOST_CHECK(!ParseInt32("2147483648", nullptr)); @@ -1470,9 +1481,7 @@ BOOST_AUTO_TEST_CASE(test_ParseInt64) BOOST_CHECK(!ParseInt64("1a", &n)); BOOST_CHECK(!ParseInt64("aap", &n)); BOOST_CHECK(!ParseInt64("0x1", &n)); // no hex - const char test_bytes[] = {'1', 0, '1'}; - std::string teststr(test_bytes, sizeof(test_bytes)); - BOOST_CHECK(!ParseInt64(teststr, &n)); // no embedded NULs + BOOST_CHECK(!ParseInt64(STRING_WITH_EMBEDDED_NULL_CHAR, &n)); // Overflow and underflow BOOST_CHECK(!ParseInt64("-9223372036854775809", nullptr)); BOOST_CHECK(!ParseInt64("9223372036854775808", nullptr)); @@ -1480,6 +1489,76 @@ BOOST_AUTO_TEST_CASE(test_ParseInt64) BOOST_CHECK(!ParseInt64("32482348723847471234", nullptr)); } +BOOST_AUTO_TEST_CASE(test_ParseUInt8) +{ + uint8_t n; + // Valid values + BOOST_CHECK(ParseUInt8("255", nullptr)); + BOOST_CHECK(ParseUInt8("0", &n) && n == 0); + BOOST_CHECK(ParseUInt8("255", &n) && n == 255); + BOOST_CHECK(ParseUInt8("0255", &n) && n == 255); // no octal + BOOST_CHECK(ParseUInt8("255", &n) && n == static_cast<uint8_t>(255)); + BOOST_CHECK(ParseUInt8("+255", &n) && n == 255); + BOOST_CHECK(ParseUInt8("00000000000000000012", &n) && n == 12); + BOOST_CHECK(ParseUInt8("00000000000000000000", &n) && n == 0); + // Invalid values + BOOST_CHECK(!ParseUInt8("-00000000000000000000", &n)); + BOOST_CHECK(!ParseUInt8("", &n)); + BOOST_CHECK(!ParseUInt8(" 1", &n)); // no padding inside + BOOST_CHECK(!ParseUInt8(" -1", &n)); + BOOST_CHECK(!ParseUInt8("++1", &n)); + BOOST_CHECK(!ParseUInt8("+-1", &n)); + BOOST_CHECK(!ParseUInt8("-+1", &n)); + BOOST_CHECK(!ParseUInt8("--1", &n)); + BOOST_CHECK(!ParseUInt8("-1", &n)); + BOOST_CHECK(!ParseUInt8("1 ", &n)); + BOOST_CHECK(!ParseUInt8("1a", &n)); + BOOST_CHECK(!ParseUInt8("aap", &n)); + BOOST_CHECK(!ParseUInt8("0x1", &n)); // no hex + BOOST_CHECK(!ParseUInt8(STRING_WITH_EMBEDDED_NULL_CHAR, &n)); + // Overflow and underflow + BOOST_CHECK(!ParseUInt8("-255", &n)); + BOOST_CHECK(!ParseUInt8("256", &n)); + BOOST_CHECK(!ParseUInt8("-123", &n)); + BOOST_CHECK(!ParseUInt8("-123", nullptr)); + 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; @@ -1508,10 +1587,7 @@ 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 - const char test_bytes[] = {'1', 0, '1'}; - std::string teststr(test_bytes, sizeof(test_bytes)); - BOOST_CHECK(!ParseUInt32(teststr, &n)); // no embedded NULs + BOOST_CHECK(!ParseUInt32(STRING_WITH_EMBEDDED_NULL_CHAR, &n)); // Overflow and underflow BOOST_CHECK(!ParseUInt32("-2147483648", &n)); BOOST_CHECK(!ParseUInt32("4294967296", &n)); @@ -1540,9 +1616,7 @@ BOOST_AUTO_TEST_CASE(test_ParseUInt64) BOOST_CHECK(!ParseUInt64("1a", &n)); BOOST_CHECK(!ParseUInt64("aap", &n)); BOOST_CHECK(!ParseUInt64("0x1", &n)); // no hex - const char test_bytes[] = {'1', 0, '1'}; - std::string teststr(test_bytes, sizeof(test_bytes)); - BOOST_CHECK(!ParseUInt64(teststr, &n)); // no embedded NULs + BOOST_CHECK(!ParseUInt64(STRING_WITH_EMBEDDED_NULL_CHAR, &n)); // Overflow and underflow BOOST_CHECK(!ParseUInt64("-9223372036854775809", nullptr)); BOOST_CHECK(!ParseUInt64("18446744073709551616", nullptr)); @@ -1572,9 +1646,7 @@ BOOST_AUTO_TEST_CASE(test_ParseDouble) BOOST_CHECK(!ParseDouble("1a", &n)); BOOST_CHECK(!ParseDouble("aap", &n)); BOOST_CHECK(!ParseDouble("0x1", &n)); // no hex - const char test_bytes[] = {'1', 0, '1'}; - std::string teststr(test_bytes, sizeof(test_bytes)); - BOOST_CHECK(!ParseDouble(teststr, &n)); // no embedded NULs + BOOST_CHECK(!ParseDouble(STRING_WITH_EMBEDDED_NULL_CHAR, &n)); // Overflow and underflow BOOST_CHECK(!ParseDouble("-1e10000", nullptr)); BOOST_CHECK(!ParseDouble("1e10000", nullptr)); diff --git a/src/test/validation_block_tests.cpp b/src/test/validation_block_tests.cpp index 0c87c4d360..552be0a2da 100644 --- a/src/test/validation_block_tests.cpp +++ b/src/test/validation_block_tests.cpp @@ -63,7 +63,7 @@ std::shared_ptr<CBlock> MinerTestingSetup::Block(const uint256& prev_hash) static int i = 0; static uint64_t time = Params().GenesisBlock().nTime; - auto ptemplate = BlockAssembler(*m_node.mempool, Params()).CreateNewBlock(CScript{} << i++ << OP_TRUE); + auto ptemplate = BlockAssembler(m_node.chainman->ActiveChainstate(), *m_node.mempool, Params()).CreateNewBlock(CScript{} << i++ << OP_TRUE); auto pblock = std::make_shared<CBlock>(ptemplate->block); pblock->hashPrevBlock = prev_hash; pblock->nTime = ++time; @@ -325,7 +325,7 @@ BOOST_AUTO_TEST_CASE(witness_commitment_index) { CScript pubKey; pubKey << 1 << OP_TRUE; - auto ptemplate = BlockAssembler(*m_node.mempool, Params()).CreateNewBlock(pubKey); + auto ptemplate = BlockAssembler(m_node.chainman->ActiveChainstate(), *m_node.mempool, Params()).CreateNewBlock(pubKey); CBlock pblock = ptemplate->block; CTxOut witness; diff --git a/src/test/versionbits_tests.cpp b/src/test/versionbits_tests.cpp index 50444f7bbe..8841a540f2 100644 --- a/src/test/versionbits_tests.cpp +++ b/src/test/versionbits_tests.cpp @@ -14,6 +14,18 @@ /* Define a virtual block time, one block per 10 minutes after Nov 14 2014, 0:55:36am */ static int32_t TestTime(int nHeight) { return 1415926536 + 600 * nHeight; } +static const std::string StateName(ThresholdState state) +{ + switch (state) { + case ThresholdState::DEFINED: return "DEFINED"; + case ThresholdState::STARTED: return "STARTED"; + case ThresholdState::LOCKED_IN: return "LOCKED_IN"; + case ThresholdState::ACTIVE: return "ACTIVE"; + case ThresholdState::FAILED: return "FAILED"; + } // no default case, so the compiler can warn about missing cases + return ""; +} + static const Consensus::Params paramsDummy = Consensus::Params(); class TestConditionChecker : public AbstractThresholdConditionChecker @@ -38,6 +50,13 @@ public: int64_t BeginTime(const Consensus::Params& params) const override { return Consensus::BIP9Deployment::ALWAYS_ACTIVE; } }; +class TestNeverActiveConditionChecker : public TestConditionChecker +{ +public: + int64_t BeginTime(const Consensus::Params& params) const override { return 0; } + int64_t EndTime(const Consensus::Params& params) const override { return 1230768000; } +}; + #define CHECKERS 6 class VersionBitsTester @@ -51,6 +70,8 @@ class VersionBitsTester TestConditionChecker checker[CHECKERS]; // Another 6 that assume always active activation TestAlwaysActiveConditionChecker checker_always[CHECKERS]; + // Another 6 that assume never active activation + TestNeverActiveConditionChecker checker_never[CHECKERS]; // Test counter (to identify failures) int num; @@ -65,6 +86,7 @@ public: for (unsigned int i = 0; i < CHECKERS; i++) { checker[i] = TestConditionChecker(); checker_always[i] = TestAlwaysActiveConditionChecker(); + checker_never[i] = TestNeverActiveConditionChecker(); } vpblock.clear(); return *this; @@ -92,66 +114,40 @@ public: if (InsecureRandBits(i) == 0) { BOOST_CHECK_MESSAGE(checker[i].GetStateSinceHeightFor(vpblock.empty() ? nullptr : vpblock.back()) == height, strprintf("Test %i for StateSinceHeight", num)); BOOST_CHECK_MESSAGE(checker_always[i].GetStateSinceHeightFor(vpblock.empty() ? nullptr : vpblock.back()) == 0, strprintf("Test %i for StateSinceHeight (always active)", num)); - } - } - num++; - return *this; - } - VersionBitsTester& TestDefined() { - for (int i = 0; i < CHECKERS; i++) { - if (InsecureRandBits(i) == 0) { - BOOST_CHECK_MESSAGE(checker[i].GetStateFor(vpblock.empty() ? nullptr : vpblock.back()) == ThresholdState::DEFINED, strprintf("Test %i for DEFINED", num)); - BOOST_CHECK_MESSAGE(checker_always[i].GetStateFor(vpblock.empty() ? nullptr : vpblock.back()) == ThresholdState::ACTIVE, strprintf("Test %i for ACTIVE (always active)", num)); + // never active may go from DEFINED -> FAILED at the first period + const auto never_height = checker_never[i].GetStateSinceHeightFor(vpblock.empty() ? nullptr : vpblock.back()); + BOOST_CHECK_MESSAGE(never_height == 0 || never_height == checker_never[i].Period(paramsDummy), strprintf("Test %i for StateSinceHeight (never active)", num)); } } num++; return *this; } - VersionBitsTester& TestStarted() { + VersionBitsTester& TestState(ThresholdState exp) { for (int i = 0; i < CHECKERS; i++) { if (InsecureRandBits(i) == 0) { - BOOST_CHECK_MESSAGE(checker[i].GetStateFor(vpblock.empty() ? nullptr : vpblock.back()) == ThresholdState::STARTED, strprintf("Test %i for STARTED", num)); - BOOST_CHECK_MESSAGE(checker_always[i].GetStateFor(vpblock.empty() ? nullptr : vpblock.back()) == ThresholdState::ACTIVE, strprintf("Test %i for ACTIVE (always active)", num)); + const CBlockIndex* pindex = vpblock.empty() ? nullptr : vpblock.back(); + ThresholdState got = checker[i].GetStateFor(pindex); + ThresholdState got_always = checker_always[i].GetStateFor(pindex); + ThresholdState got_never = checker_never[i].GetStateFor(pindex); + // nHeight of the next block. If vpblock is empty, the next (ie first) + // block should be the genesis block with nHeight == 0. + int height = pindex == nullptr ? 0 : pindex->nHeight + 1; + BOOST_CHECK_MESSAGE(got == exp, strprintf("Test %i for %s height %d (got %s)", num, StateName(exp), height, StateName(got))); + BOOST_CHECK_MESSAGE(got_always == ThresholdState::ACTIVE, strprintf("Test %i for ACTIVE height %d (got %s; always active case)", num, height, StateName(got_always))); + BOOST_CHECK_MESSAGE(got_never == ThresholdState::DEFINED|| got_never == ThresholdState::FAILED, strprintf("Test %i for DEFINED/FAILED height %d (got %s; never active case)", num, height, StateName(got_never))); } } num++; return *this; } - VersionBitsTester& TestLockedIn() { - for (int i = 0; i < CHECKERS; i++) { - if (InsecureRandBits(i) == 0) { - BOOST_CHECK_MESSAGE(checker[i].GetStateFor(vpblock.empty() ? nullptr : vpblock.back()) == ThresholdState::LOCKED_IN, strprintf("Test %i for LOCKED_IN", num)); - BOOST_CHECK_MESSAGE(checker_always[i].GetStateFor(vpblock.empty() ? nullptr : vpblock.back()) == ThresholdState::ACTIVE, strprintf("Test %i for ACTIVE (always active)", num)); - } - } - num++; - return *this; - } - - VersionBitsTester& TestActive() { - for (int i = 0; i < CHECKERS; i++) { - if (InsecureRandBits(i) == 0) { - BOOST_CHECK_MESSAGE(checker[i].GetStateFor(vpblock.empty() ? nullptr : vpblock.back()) == ThresholdState::ACTIVE, strprintf("Test %i for ACTIVE", num)); - BOOST_CHECK_MESSAGE(checker_always[i].GetStateFor(vpblock.empty() ? nullptr : vpblock.back()) == ThresholdState::ACTIVE, strprintf("Test %i for ACTIVE (always active)", num)); - } - } - num++; - return *this; - } - - VersionBitsTester& TestFailed() { - for (int i = 0; i < CHECKERS; i++) { - if (InsecureRandBits(i) == 0) { - BOOST_CHECK_MESSAGE(checker[i].GetStateFor(vpblock.empty() ? nullptr : vpblock.back()) == ThresholdState::FAILED, strprintf("Test %i for FAILED", num)); - BOOST_CHECK_MESSAGE(checker_always[i].GetStateFor(vpblock.empty() ? nullptr : vpblock.back()) == ThresholdState::ACTIVE, strprintf("Test %i for ACTIVE (always active)", num)); - } - } - num++; - return *this; - } + VersionBitsTester& TestDefined() { return TestState(ThresholdState::DEFINED); } + VersionBitsTester& TestStarted() { return TestState(ThresholdState::STARTED); } + VersionBitsTester& TestLockedIn() { return TestState(ThresholdState::LOCKED_IN); } + VersionBitsTester& TestActive() { return TestState(ThresholdState::ACTIVE); } + VersionBitsTester& TestFailed() { return TestState(ThresholdState::FAILED); } CBlockIndex * Tip() { return vpblock.size() ? vpblock.back() : nullptr; } }; diff --git a/src/torcontrol.cpp b/src/torcontrol.cpp index 605c77fc3a..6666e49a2b 100644 --- a/src/torcontrol.cpp +++ b/src/torcontrol.cpp @@ -12,6 +12,7 @@ #include <net.h> #include <netaddress.h> #include <netbase.h> +#include <util/readwritefile.h> #include <util/strencodings.h> #include <util/system.h> #include <util/time.h> @@ -55,77 +56,6 @@ static const int MAX_LINE_LENGTH = 100000; /****** Low-level TorControlConnection ********/ -/** Reply from Tor, can be single or multi-line */ -class TorControlReply -{ -public: - TorControlReply() { Clear(); } - - int code; - std::vector<std::string> lines; - - void Clear() - { - code = 0; - lines.clear(); - } -}; - -/** Low-level handling for Tor control connection. - * Speaks the SMTP-like protocol as defined in torspec/control-spec.txt - */ -class TorControlConnection -{ -public: - typedef std::function<void(TorControlConnection&)> ConnectionCB; - typedef std::function<void(TorControlConnection &,const TorControlReply &)> ReplyHandlerCB; - - /** Create a new TorControlConnection. - */ - explicit TorControlConnection(struct event_base *base); - ~TorControlConnection(); - - /** - * Connect to a Tor control port. - * tor_control_center is address of the form host:port. - * connected is the handler that is called when connection is successfully established. - * disconnected is a handler that is called when the connection is broken. - * Return true on success. - */ - bool Connect(const std::string& tor_control_center, const ConnectionCB& connected, const ConnectionCB& disconnected); - - /** - * Disconnect from Tor control port. - */ - void Disconnect(); - - /** Send a command, register a handler for the reply. - * A trailing CRLF is automatically added. - * Return true on success. - */ - bool Command(const std::string &cmd, const ReplyHandlerCB& reply_handler); - - /** Response handlers for async replies */ - boost::signals2::signal<void(TorControlConnection &,const TorControlReply &)> async_handler; -private: - /** Callback when ready for use */ - std::function<void(TorControlConnection&)> connected; - /** Callback when connection lost */ - std::function<void(TorControlConnection&)> disconnected; - /** Libevent event base */ - struct event_base *base; - /** Connection to control socket */ - struct bufferevent *b_conn; - /** Message being received */ - TorControlReply message; - /** Response handlers */ - std::deque<ReplyHandlerCB> reply_handlers; - - /** Libevent handlers: internal */ - static void readcb(struct bufferevent *bev, void *ctx); - static void eventcb(struct bufferevent *bev, short what, void *ctx); -}; - TorControlConnection::TorControlConnection(struct event_base *_base): base(_base), b_conn(nullptr) { @@ -362,101 +292,6 @@ std::map<std::string,std::string> ParseTorReplyMapping(const std::string &s) return mapping; } -/** Read full contents of a file and return them in a std::string. - * Returns a pair <status, string>. - * If an error occurred, status will be false, otherwise status will be true and the data will be returned in string. - * - * @param maxsize Puts a maximum size limit on the file that is read. If the file is larger than this, truncated data - * (with len > maxsize) will be returned. - */ -static std::pair<bool,std::string> ReadBinaryFile(const fs::path &filename, size_t maxsize=std::numeric_limits<size_t>::max()) -{ - FILE *f = fsbridge::fopen(filename, "rb"); - if (f == nullptr) - return std::make_pair(false,""); - std::string retval; - char buffer[128]; - size_t n; - while ((n=fread(buffer, 1, sizeof(buffer), f)) > 0) { - // Check for reading errors so we don't return any data if we couldn't - // read the entire file (or up to maxsize) - if (ferror(f)) { - fclose(f); - return std::make_pair(false,""); - } - retval.append(buffer, buffer+n); - if (retval.size() > maxsize) - break; - } - fclose(f); - return std::make_pair(true,retval); -} - -/** Write contents of std::string to a file. - * @return true on success. - */ -static bool WriteBinaryFile(const fs::path &filename, const std::string &data) -{ - FILE *f = fsbridge::fopen(filename, "wb"); - if (f == nullptr) - return false; - if (fwrite(data.data(), 1, data.size(), f) != data.size()) { - fclose(f); - return false; - } - fclose(f); - return true; -} - -/****** Bitcoin specific TorController implementation ********/ - -/** Controller that connects to Tor control socket, authenticate, then create - * and maintain an ephemeral onion service. - */ -class TorController -{ -public: - TorController(struct event_base* base, const std::string& tor_control_center, const CService& target); - ~TorController(); - - /** Get name of file to store private key in */ - fs::path GetPrivateKeyFile(); - - /** Reconnect, after getting disconnected */ - void Reconnect(); -private: - struct event_base* base; - const std::string m_tor_control_center; - TorControlConnection conn; - std::string private_key; - std::string service_id; - bool reconnect; - struct event *reconnect_ev; - float reconnect_timeout; - CService service; - const CService m_target; - /** Cookie for SAFECOOKIE auth */ - std::vector<uint8_t> cookie; - /** ClientNonce for SAFECOOKIE auth */ - std::vector<uint8_t> clientNonce; - - /** Callback for ADD_ONION result */ - void add_onion_cb(TorControlConnection& conn, const TorControlReply& reply); - /** Callback for AUTHENTICATE result */ - void auth_cb(TorControlConnection& conn, const TorControlReply& reply); - /** Callback for AUTHCHALLENGE result */ - void authchallenge_cb(TorControlConnection& conn, const TorControlReply& reply); - /** Callback for PROTOCOLINFO result */ - void protocolinfo_cb(TorControlConnection& conn, const TorControlReply& reply); - /** Callback after successful connection */ - void connected_cb(TorControlConnection& conn); - /** Callback after connection lost or failed connection attempt */ - void disconnected_cb(TorControlConnection& conn); - - /** Callback for reconnect timer */ - static void reconnect_cb(evutil_socket_t fd, short what, void *arg); -}; - TorController::TorController(struct event_base* _base, const std::string& tor_control_center, const CService& target): base(_base), m_tor_control_center(tor_control_center), conn(base), reconnect(true), reconnect_ev(0), diff --git a/src/torcontrol.h b/src/torcontrol.h index 00f19db6ae..7258f27cb6 100644 --- a/src/torcontrol.h +++ b/src/torcontrol.h @@ -8,7 +8,19 @@ #ifndef BITCOIN_TORCONTROL_H #define BITCOIN_TORCONTROL_H +#include <fs.h> +#include <netaddress.h> + +#include <boost/signals2/signal.hpp> + +#include <event2/bufferevent.h> +#include <event2/event.h> + +#include <cstdlib> +#include <deque> +#include <functional> #include <string> +#include <vector> class CService; @@ -21,4 +33,128 @@ void StopTorControl(); CService DefaultOnionServiceTarget(); +/** Reply from Tor, can be single or multi-line */ +class TorControlReply +{ +public: + TorControlReply() { Clear(); } + + int code; + std::vector<std::string> lines; + + void Clear() + { + code = 0; + lines.clear(); + } +}; + +/** Low-level handling for Tor control connection. + * Speaks the SMTP-like protocol as defined in torspec/control-spec.txt + */ +class TorControlConnection +{ +public: + typedef std::function<void(TorControlConnection&)> ConnectionCB; + typedef std::function<void(TorControlConnection &,const TorControlReply &)> ReplyHandlerCB; + + /** Create a new TorControlConnection. + */ + explicit TorControlConnection(struct event_base *base); + ~TorControlConnection(); + + /** + * Connect to a Tor control port. + * tor_control_center is address of the form host:port. + * connected is the handler that is called when connection is successfully established. + * disconnected is a handler that is called when the connection is broken. + * Return true on success. + */ + bool Connect(const std::string& tor_control_center, const ConnectionCB& connected, const ConnectionCB& disconnected); + + /** + * Disconnect from Tor control port. + */ + void Disconnect(); + + /** Send a command, register a handler for the reply. + * A trailing CRLF is automatically added. + * Return true on success. + */ + bool Command(const std::string &cmd, const ReplyHandlerCB& reply_handler); + + /** Response handlers for async replies */ + boost::signals2::signal<void(TorControlConnection &,const TorControlReply &)> async_handler; +private: + /** Callback when ready for use */ + std::function<void(TorControlConnection&)> connected; + /** Callback when connection lost */ + std::function<void(TorControlConnection&)> disconnected; + /** Libevent event base */ + struct event_base *base; + /** Connection to control socket */ + struct bufferevent *b_conn; + /** Message being received */ + TorControlReply message; + /** Response handlers */ + std::deque<ReplyHandlerCB> reply_handlers; + + /** Libevent handlers: internal */ + static void readcb(struct bufferevent *bev, void *ctx); + static void eventcb(struct bufferevent *bev, short what, void *ctx); +}; + +/****** Bitcoin specific TorController implementation ********/ + +/** Controller that connects to Tor control socket, authenticate, then create + * and maintain an ephemeral onion service. + */ +class TorController +{ +public: + TorController(struct event_base* base, const std::string& tor_control_center, const CService& target); + TorController() : conn{nullptr} { + // Used for testing only. + } + ~TorController(); + + /** Get name of file to store private key in */ + fs::path GetPrivateKeyFile(); + + /** Reconnect, after getting disconnected */ + void Reconnect(); +private: + struct event_base* base; + const std::string m_tor_control_center; + TorControlConnection conn; + std::string private_key; + std::string service_id; + bool reconnect; + struct event *reconnect_ev = nullptr; + float reconnect_timeout; + CService service; + const CService m_target; + /** Cookie for SAFECOOKIE auth */ + std::vector<uint8_t> cookie; + /** ClientNonce for SAFECOOKIE auth */ + std::vector<uint8_t> clientNonce; + +public: + /** Callback for ADD_ONION result */ + void add_onion_cb(TorControlConnection& conn, const TorControlReply& reply); + /** Callback for AUTHENTICATE result */ + void auth_cb(TorControlConnection& conn, const TorControlReply& reply); + /** Callback for AUTHCHALLENGE result */ + void authchallenge_cb(TorControlConnection& conn, const TorControlReply& reply); + /** Callback for PROTOCOLINFO result */ + void protocolinfo_cb(TorControlConnection& conn, const TorControlReply& reply); + /** Callback after successful connection */ + void connected_cb(TorControlConnection& conn); + /** Callback after connection lost or failed connection attempt */ + void disconnected_cb(TorControlConnection& conn); + + /** Callback for reconnect timer */ + static void reconnect_cb(evutil_socket_t fd, short what, void *arg); +}; + #endif /* BITCOIN_TORCONTROL_H */ diff --git a/src/txdb.cpp b/src/txdb.cpp index 4b4766e1ba..3a08e28c01 100644 --- a/src/txdb.cpp +++ b/src/txdb.cpp @@ -10,7 +10,6 @@ #include <random.h> #include <shutdown.h> #include <uint256.h> -#include <util/memory.h> #include <util/system.h> #include <util/translation.h> #include <util/vector.h> @@ -41,7 +40,7 @@ struct CoinEntry { } CCoinsViewDB::CCoinsViewDB(fs::path ldb_path, size_t nCacheSize, bool fMemory, bool fWipe) : - m_db(MakeUnique<CDBWrapper>(ldb_path, nCacheSize, fMemory, fWipe, true)), + m_db(std::make_unique<CDBWrapper>(ldb_path, nCacheSize, fMemory, fWipe, true)), m_ldb_path(ldb_path), m_is_memory(fMemory) { } @@ -53,7 +52,7 @@ void CCoinsViewDB::ResizeCache(size_t new_cache_size) // Have to do a reset first to get the original `m_db` state to release its // filesystem lock. m_db.reset(); - m_db = MakeUnique<CDBWrapper>( + m_db = std::make_unique<CDBWrapper>( m_ldb_path, new_cache_size, m_is_memory, /*fWipe*/ false, /*obfuscate*/ true); } } diff --git a/src/txmempool.cpp b/src/txmempool.cpp index 48424a75d0..67549fc13d 100644 --- a/src/txmempool.cpp +++ b/src/txmempool.cpp @@ -8,7 +8,6 @@ #include <consensus/consensus.h> #include <consensus/tx_verify.h> #include <consensus/validation.h> -#include <optional.h> #include <policy/fees.h> #include <policy/policy.h> #include <policy/settings.h> @@ -19,6 +18,8 @@ #include <validation.h> #include <validationinterface.h> +#include <optional> + CTxMemPoolEntry::CTxMemPoolEntry(const CTransactionRef& _tx, const CAmount& _nFee, int64_t _nTime, unsigned int _entryHeight, bool _spendsCoinbase, int64_t _sigOpsCost, LockPoints lp) @@ -159,7 +160,7 @@ bool CTxMemPool::CalculateMemPoolAncestors(const CTxMemPoolEntry &entry, setEntr // GetMemPoolParents() is only valid for entries in the mempool, so we // iterate mapTx to find parents. for (unsigned int i = 0; i < tx.vin.size(); i++) { - Optional<txiter> piter = GetIter(tx.vin[i].prevout.hash); + std::optional<txiter> piter = GetIter(tx.vin[i].prevout.hash); if (piter) { staged_ancestors.insert(**piter); if (staged_ancestors.size() + 1 > limitAncestorCount) { @@ -619,7 +620,7 @@ static void CheckInputsAndUpdateCoins(const CTransaction& tx, CCoinsViewCache& m UpdateCoins(tx, mempoolDuplicate, std::numeric_limits<int>::max()); } -void CTxMemPool::check(const CCoinsViewCache *pcoins) const +void CTxMemPool::check(CChainState& active_chainstate) const { if (m_check_ratio == 0) return; @@ -633,8 +634,11 @@ void CTxMemPool::check(const CCoinsViewCache *pcoins) const CAmount check_total_fee{0}; uint64_t innerUsage = 0; - CCoinsViewCache mempoolDuplicate(const_cast<CCoinsViewCache*>(pcoins)); - const int64_t spendheight = g_chainman.m_blockman.GetSpendHeight(mempoolDuplicate); + CCoinsViewCache& active_coins_tip = active_chainstate.CoinsTip(); + assert(std::addressof(::ChainstateActive().CoinsTip()) == std::addressof(active_coins_tip)); // TODO: REVIEW-ONLY, REMOVE IN FUTURE COMMIT + CCoinsViewCache mempoolDuplicate(const_cast<CCoinsViewCache*>(&active_coins_tip)); + const int64_t spendheight = active_chainstate.m_chain.Height() + 1; + assert(g_chainman.m_blockman.GetSpendHeight(mempoolDuplicate) == spendheight); // TODO: REVIEW-ONLY, REMOVE IN FUTURE COMMIT std::list<const CTxMemPoolEntry*> waitingOnDependants; for (indexed_transaction_set::const_iterator it = mapTx.begin(); it != mapTx.end(); it++) { @@ -655,7 +659,7 @@ void CTxMemPool::check(const CCoinsViewCache *pcoins) const fDependsWait = true; setParentCheck.insert(*it2); } else { - assert(pcoins->HaveCoin(txin.prevout)); + assert(active_coins_tip.HaveCoin(txin.prevout)); } // Check whether its inputs are marked in mapNextTx. auto it3 = mapNextTx.find(txin.prevout); @@ -887,11 +891,11 @@ const CTransaction* CTxMemPool::GetConflictTx(const COutPoint& prevout) const return it == mapNextTx.end() ? nullptr : it->second; } -Optional<CTxMemPool::txiter> CTxMemPool::GetIter(const uint256& txid) const +std::optional<CTxMemPool::txiter> CTxMemPool::GetIter(const uint256& txid) const { auto it = mapTx.find(txid); if (it != mapTx.end()) return it; - return Optional<txiter>{}; + return std::nullopt; } CTxMemPool::setEntries CTxMemPool::GetIterSet(const std::set<uint256>& hashes) const diff --git a/src/txmempool.h b/src/txmempool.h index 143048b205..c3a9bd851d 100644 --- a/src/txmempool.h +++ b/src/txmempool.h @@ -8,6 +8,7 @@ #include <atomic> #include <map> +#include <optional> #include <set> #include <string> #include <utility> @@ -16,7 +17,6 @@ #include <amount.h> #include <coins.h> #include <indirectmap.h> -#include <optional.h> #include <policy/feerate.h> #include <primitives/transaction.h> #include <random.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; @@ -605,7 +605,7 @@ public: * all inputs are in the mapNextTx array). If sanity-checking is turned off, * check does nothing. */ - void check(const CCoinsViewCache *pcoins) const EXCLUSIVE_LOCKS_REQUIRED(::cs_main); + void check(CChainState& active_chainstate) const EXCLUSIVE_LOCKS_REQUIRED(::cs_main); // addUnchecked must updated state for all ancestors of a given transaction, // to track size/count of descendant transactions. First version of @@ -644,7 +644,7 @@ public: const CTransaction* GetConflictTx(const COutPoint& prevout) const EXCLUSIVE_LOCKS_REQUIRED(cs); /** Returns an iterator to the given hash, if found */ - Optional<txiter> GetIter(const uint256& txid) const EXCLUSIVE_LOCKS_REQUIRED(cs); + std::optional<txiter> GetIter(const uint256& txid) const EXCLUSIVE_LOCKS_REQUIRED(cs); /** Translate a set of hashes into a set of pool iterators to avoid repeated lookups */ setEntries GetIterSet(const std::set<uint256>& hashes) const EXCLUSIVE_LOCKS_REQUIRED(cs); @@ -841,7 +841,7 @@ public: return m_epoch.visited(it->m_epoch_marker); } - bool visited(Optional<txiter> it) const EXCLUSIVE_LOCKS_REQUIRED(cs, m_epoch) + bool visited(std::optional<txiter> it) const EXCLUSIVE_LOCKS_REQUIRED(cs, m_epoch) { assert(m_epoch.guarded()); // verify guard even when it==nullopt return !it || visited(*it); diff --git a/src/txorphanage.cpp b/src/txorphanage.cpp new file mode 100644 index 0000000000..ed4783f1a5 --- /dev/null +++ b/src/txorphanage.cpp @@ -0,0 +1,202 @@ +// 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 <txorphanage.h> + +#include <consensus/validation.h> +#include <logging.h> +#include <policy/policy.h> + +#include <cassert> + +/** Expiration time for orphan transactions in seconds */ +static constexpr int64_t ORPHAN_TX_EXPIRE_TIME = 20 * 60; +/** Minimum time between orphan transactions expire time checks in seconds */ +static constexpr int64_t ORPHAN_TX_EXPIRE_INTERVAL = 5 * 60; + +RecursiveMutex g_cs_orphans; + +bool TxOrphanage::AddTx(const CTransactionRef& tx, NodeId peer) +{ + AssertLockHeld(g_cs_orphans); + + const uint256& hash = tx->GetHash(); + if (m_orphans.count(hash)) + return false; + + // Ignore big transactions, to avoid a + // send-big-orphans memory exhaustion attack. If a peer has a legitimate + // large transaction with a missing parent then we assume + // it will rebroadcast it later, after the parent transaction(s) + // have been mined or received. + // 100 orphans, each of which is at most 100,000 bytes big is + // at most 10 megabytes of orphans and somewhat more byprev index (in the worst case): + unsigned int sz = GetTransactionWeight(*tx); + if (sz > MAX_STANDARD_TX_WEIGHT) + { + LogPrint(BCLog::MEMPOOL, "ignoring large orphan tx (size: %u, hash: %s)\n", sz, hash.ToString()); + return false; + } + + auto ret = m_orphans.emplace(hash, OrphanTx{tx, peer, GetTime() + ORPHAN_TX_EXPIRE_TIME, m_orphan_list.size()}); + assert(ret.second); + m_orphan_list.push_back(ret.first); + // Allow for lookups in the orphan pool by wtxid, as well as txid + m_wtxid_to_orphan_it.emplace(tx->GetWitnessHash(), ret.first); + for (const CTxIn& txin : tx->vin) { + m_outpoint_to_orphan_it[txin.prevout].insert(ret.first); + } + + LogPrint(BCLog::MEMPOOL, "stored orphan tx %s (mapsz %u outsz %u)\n", hash.ToString(), + m_orphans.size(), m_outpoint_to_orphan_it.size()); + return true; +} + +int TxOrphanage::EraseTx(const uint256& txid) +{ + AssertLockHeld(g_cs_orphans); + std::map<uint256, OrphanTx>::iterator it = m_orphans.find(txid); + if (it == m_orphans.end()) + return 0; + for (const CTxIn& txin : it->second.tx->vin) + { + auto itPrev = m_outpoint_to_orphan_it.find(txin.prevout); + if (itPrev == m_outpoint_to_orphan_it.end()) + continue; + itPrev->second.erase(it); + if (itPrev->second.empty()) + m_outpoint_to_orphan_it.erase(itPrev); + } + + size_t old_pos = it->second.list_pos; + assert(m_orphan_list[old_pos] == it); + if (old_pos + 1 != m_orphan_list.size()) { + // Unless we're deleting the last entry in m_orphan_list, move the last + // entry to the position we're deleting. + auto it_last = m_orphan_list.back(); + m_orphan_list[old_pos] = it_last; + it_last->second.list_pos = old_pos; + } + m_orphan_list.pop_back(); + m_wtxid_to_orphan_it.erase(it->second.tx->GetWitnessHash()); + + m_orphans.erase(it); + return 1; +} + +void TxOrphanage::EraseForPeer(NodeId peer) +{ + AssertLockHeld(g_cs_orphans); + + int nErased = 0; + std::map<uint256, OrphanTx>::iterator iter = m_orphans.begin(); + while (iter != m_orphans.end()) + { + std::map<uint256, OrphanTx>::iterator maybeErase = iter++; // increment to avoid iterator becoming invalid + if (maybeErase->second.fromPeer == peer) + { + nErased += EraseTx(maybeErase->second.tx->GetHash()); + } + } + if (nErased > 0) LogPrint(BCLog::MEMPOOL, "Erased %d orphan tx from peer=%d\n", nErased, peer); +} + +unsigned int TxOrphanage::LimitOrphans(unsigned int max_orphans) +{ + AssertLockHeld(g_cs_orphans); + + unsigned int nEvicted = 0; + static int64_t nNextSweep; + int64_t nNow = GetTime(); + if (nNextSweep <= nNow) { + // Sweep out expired orphan pool entries: + int nErased = 0; + int64_t nMinExpTime = nNow + ORPHAN_TX_EXPIRE_TIME - ORPHAN_TX_EXPIRE_INTERVAL; + std::map<uint256, OrphanTx>::iterator iter = m_orphans.begin(); + while (iter != m_orphans.end()) + { + std::map<uint256, OrphanTx>::iterator maybeErase = iter++; + if (maybeErase->second.nTimeExpire <= nNow) { + nErased += EraseTx(maybeErase->second.tx->GetHash()); + } else { + nMinExpTime = std::min(maybeErase->second.nTimeExpire, nMinExpTime); + } + } + // Sweep again 5 minutes after the next entry that expires in order to batch the linear scan. + nNextSweep = nMinExpTime + ORPHAN_TX_EXPIRE_INTERVAL; + if (nErased > 0) LogPrint(BCLog::MEMPOOL, "Erased %d orphan tx due to expiration\n", nErased); + } + FastRandomContext rng; + while (m_orphans.size() > max_orphans) + { + // Evict a random orphan: + size_t randompos = rng.randrange(m_orphan_list.size()); + EraseTx(m_orphan_list[randompos]->first); + ++nEvicted; + } + return nEvicted; +} + +void TxOrphanage::AddChildrenToWorkSet(const CTransaction& tx, std::set<uint256>& orphan_work_set) const +{ + AssertLockHeld(g_cs_orphans); + for (unsigned int i = 0; i < tx.vout.size(); i++) { + const auto it_by_prev = m_outpoint_to_orphan_it.find(COutPoint(tx.GetHash(), i)); + if (it_by_prev != m_outpoint_to_orphan_it.end()) { + for (const auto& elem : it_by_prev->second) { + orphan_work_set.insert(elem->first); + } + } + } +} + +bool TxOrphanage::HaveTx(const GenTxid& gtxid) const +{ + LOCK(g_cs_orphans); + if (gtxid.IsWtxid()) { + return m_wtxid_to_orphan_it.count(gtxid.GetHash()); + } else { + return m_orphans.count(gtxid.GetHash()); + } +} + +std::pair<CTransactionRef, NodeId> TxOrphanage::GetTx(const uint256& txid) const +{ + AssertLockHeld(g_cs_orphans); + + const auto it = m_orphans.find(txid); + if (it == m_orphans.end()) return {nullptr, -1}; + return {it->second.tx, it->second.fromPeer}; +} + +void TxOrphanage::EraseForBlock(const CBlock& block) +{ + LOCK(g_cs_orphans); + + std::vector<uint256> vOrphanErase; + + for (const CTransactionRef& ptx : block.vtx) { + const CTransaction& tx = *ptx; + + // Which orphan pool entries must we evict? + for (const auto& txin : tx.vin) { + auto itByPrev = m_outpoint_to_orphan_it.find(txin.prevout); + if (itByPrev == m_outpoint_to_orphan_it.end()) continue; + for (auto mi = itByPrev->second.begin(); mi != itByPrev->second.end(); ++mi) { + const CTransaction& orphanTx = *(*mi)->second.tx; + const uint256& orphanHash = orphanTx.GetHash(); + vOrphanErase.push_back(orphanHash); + } + } + } + + // Erase orphan transactions included or precluded by this block + if (vOrphanErase.size()) { + int nErased = 0; + for (const uint256& orphanHash : vOrphanErase) { + nErased += EraseTx(orphanHash); + } + LogPrint(BCLog::MEMPOOL, "Erased %d orphan tx included or conflicted by block\n", nErased); + } +} diff --git a/src/txorphanage.h b/src/txorphanage.h new file mode 100644 index 0000000000..df55cdb3be --- /dev/null +++ b/src/txorphanage.h @@ -0,0 +1,85 @@ +// Copyright (c) 2021 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_TXORPHANAGE_H +#define BITCOIN_TXORPHANAGE_H + +#include <net.h> +#include <primitives/block.h> +#include <primitives/transaction.h> +#include <sync.h> + +/** Guards orphan transactions and extra txs for compact blocks */ +extern RecursiveMutex g_cs_orphans; + +/** A class to track orphan transactions (failed on TX_MISSING_INPUTS) + * Since we cannot distinguish orphans from bad transactions with + * non-existent inputs, we heavily limit the number of orphans + * we keep and the duration we keep them for. + */ +class TxOrphanage { +public: + /** Add a new orphan transaction */ + bool AddTx(const CTransactionRef& tx, NodeId peer) EXCLUSIVE_LOCKS_REQUIRED(g_cs_orphans); + + /** Check if we already have an orphan transaction (by txid or wtxid) */ + bool HaveTx(const GenTxid& gtxid) const EXCLUSIVE_LOCKS_REQUIRED(!g_cs_orphans); + + /** Get an orphan transaction and its orginating peer + * (Transaction ref will be nullptr if not found) + */ + std::pair<CTransactionRef, NodeId> GetTx(const uint256& txid) const EXCLUSIVE_LOCKS_REQUIRED(g_cs_orphans); + + /** Erase an orphan by txid */ + int EraseTx(const uint256& txid) EXCLUSIVE_LOCKS_REQUIRED(g_cs_orphans); + + /** Erase all orphans announced by a peer (eg, after that peer disconnects) */ + void EraseForPeer(NodeId peer) EXCLUSIVE_LOCKS_REQUIRED(g_cs_orphans); + + /** Erase all orphans included in or invalidated by a new block */ + void EraseForBlock(const CBlock& block) EXCLUSIVE_LOCKS_REQUIRED(!g_cs_orphans); + + /** Limit the orphanage to the given maximum */ + unsigned int LimitOrphans(unsigned int max_orphans) EXCLUSIVE_LOCKS_REQUIRED(g_cs_orphans); + + /** Add any orphans that list a particular tx as a parent into a peer's work set + * (ie orphans that may have found their final missing parent, and so should be reconsidered for the mempool) */ + void AddChildrenToWorkSet(const CTransaction& tx, std::set<uint256>& orphan_work_set) const EXCLUSIVE_LOCKS_REQUIRED(g_cs_orphans); + +protected: + struct OrphanTx { + CTransactionRef tx; + NodeId fromPeer; + int64_t nTimeExpire; + size_t list_pos; + }; + + /** Map from txid to orphan transaction record. Limited by + * -maxorphantx/DEFAULT_MAX_ORPHAN_TRANSACTIONS */ + std::map<uint256, OrphanTx> m_orphans GUARDED_BY(g_cs_orphans); + + using OrphanMap = decltype(m_orphans); + + struct IteratorComparator + { + template<typename I> + bool operator()(const I& a, const I& b) const + { + return &(*a) < &(*b); + } + }; + + /** Index from the parents' COutPoint into the m_orphans. Used + * to remove orphan transactions from the m_orphans */ + std::map<COutPoint, std::set<OrphanMap::iterator, IteratorComparator>> m_outpoint_to_orphan_it GUARDED_BY(g_cs_orphans); + + /** Orphan transactions in vector for quick random eviction */ + std::vector<OrphanMap::iterator> m_orphan_list GUARDED_BY(g_cs_orphans); + + /** Index from wtxid into the m_orphans to lookup orphan + * transactions using their witness ids. */ + std::map<uint256, OrphanMap::iterator> m_wtxid_to_orphan_it GUARDED_BY(g_cs_orphans); +}; + +#endif // BITCOIN_TXORPHANAGE_H diff --git a/src/txrequest.cpp b/src/txrequest.cpp index e54c073328..58424134b0 100644 --- a/src/txrequest.cpp +++ b/src/txrequest.cpp @@ -9,7 +9,6 @@ #include <primitives/transaction.h> #include <random.h> #include <uint256.h> -#include <util/memory.h> #include <boost/multi_index_container.hpp> #include <boost/multi_index/ordered_index.hpp> @@ -711,7 +710,7 @@ public: }; TxRequestTracker::TxRequestTracker(bool deterministic) : - m_impl{MakeUnique<TxRequestTracker::Impl>(deterministic)} {} + m_impl{std::make_unique<TxRequestTracker::Impl>(deterministic)} {} TxRequestTracker::~TxRequestTracker() = default; 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/util/memory.h b/src/util/memory.h deleted file mode 100644 index f21b81bade..0000000000 --- a/src/util/memory.h +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (c) 2009-2010 Satoshi Nakamoto -// Copyright (c) 2009-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. - -#ifndef BITCOIN_UTIL_MEMORY_H -#define BITCOIN_UTIL_MEMORY_H - -#include <memory> -#include <utility> - -//! Substitute for C++14 std::make_unique. -//! DEPRECATED use std::make_unique in new code. -template <typename T, typename... Args> -std::unique_ptr<T> MakeUnique(Args&&... args) -{ - return std::make_unique<T>(std::forward<Args>(args)...); -} - -#endif diff --git a/src/util/moneystr.cpp b/src/util/moneystr.cpp index 1bc8d02eab..3f9ce7dce4 100644 --- a/src/util/moneystr.cpp +++ b/src/util/moneystr.cpp @@ -9,13 +9,17 @@ #include <util/strencodings.h> #include <util/string.h> -std::string FormatMoney(const CAmount& n) +std::string FormatMoney(const CAmount n) { // Note: not using straight sprintf here because we do NOT want // localized number formatting. - int64_t n_abs = (n > 0 ? n : -n); - int64_t quotient = n_abs/COIN; - int64_t remainder = n_abs%COIN; + static_assert(COIN > 1); + int64_t quotient = n / COIN; + int64_t remainder = n % COIN; + if (n < 0) { + quotient = -quotient; + remainder = -remainder; + } std::string str = strprintf("%d.%08d", quotient, remainder); // Right-trim excess zeros before the decimal point: diff --git a/src/util/moneystr.h b/src/util/moneystr.h index da7f673cda..2aedbee358 100644 --- a/src/util/moneystr.h +++ b/src/util/moneystr.h @@ -17,7 +17,7 @@ /* Do not use these functions to represent or parse monetary amounts to or from * JSON but use AmountFromValue and ValueFromAmount for that. */ -std::string FormatMoney(const CAmount& n); +std::string FormatMoney(const CAmount n); /** Parse an amount denoted in full coins. E.g. "0.0034" supplied on the command line. **/ [[nodiscard]] bool ParseMoney(const std::string& str, CAmount& nRet); diff --git a/src/util/readwritefile.cpp b/src/util/readwritefile.cpp new file mode 100644 index 0000000000..a45c41d367 --- /dev/null +++ b/src/util/readwritefile.cpp @@ -0,0 +1,47 @@ +// Copyright (c) 2015-2020 The Bitcoin Core developers +// Copyright (c) 2017 The Zcash developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include <fs.h> + +#include <limits> +#include <stdio.h> +#include <string> +#include <utility> + +std::pair<bool,std::string> ReadBinaryFile(const fs::path &filename, size_t maxsize=std::numeric_limits<size_t>::max()) +{ + FILE *f = fsbridge::fopen(filename, "rb"); + if (f == nullptr) + return std::make_pair(false,""); + std::string retval; + char buffer[128]; + do { + const size_t n = fread(buffer, 1, sizeof(buffer), f); + // Check for reading errors so we don't return any data if we couldn't + // read the entire file (or up to maxsize) + if (ferror(f)) { + fclose(f); + return std::make_pair(false,""); + } + retval.append(buffer, buffer+n); + } while (!feof(f) && retval.size() <= maxsize); + fclose(f); + return std::make_pair(true,retval); +} + +bool WriteBinaryFile(const fs::path &filename, const std::string &data) +{ + FILE *f = fsbridge::fopen(filename, "wb"); + if (f == nullptr) + return false; + if (fwrite(data.data(), 1, data.size(), f) != data.size()) { + fclose(f); + return false; + } + if (fclose(f) != 0) { + return false; + } + return true; +} diff --git a/src/util/readwritefile.h b/src/util/readwritefile.h new file mode 100644 index 0000000000..1dab874b38 --- /dev/null +++ b/src/util/readwritefile.h @@ -0,0 +1,28 @@ +// Copyright (c) 2015-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. + +#ifndef BITCOIN_UTIL_READWRITEFILE_H +#define BITCOIN_UTIL_READWRITEFILE_H + +#include <fs.h> + +#include <limits> +#include <string> +#include <utility> + +/** Read full contents of a file and return them in a std::string. + * Returns a pair <status, string>. + * If an error occurred, status will be false, otherwise status will be true and the data will be returned in string. + * + * @param maxsize Puts a maximum size limit on the file that is read. If the file is larger than this, truncated data + * (with len > maxsize) will be returned. + */ +std::pair<bool,std::string> ReadBinaryFile(const fs::path &filename, size_t maxsize=std::numeric_limits<size_t>::max()); + +/** Write contents of std::string to a file. + * @return true on success. + */ +bool WriteBinaryFile(const fs::path &filename, const std::string &data); + +#endif /* BITCOIN_UTIL_READWRITEFILE_H */ diff --git a/src/util/ref.h b/src/util/ref.h deleted file mode 100644 index 9685ea9fec..0000000000 --- a/src/util/ref.h +++ /dev/null @@ -1,38 +0,0 @@ -// 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. - -#ifndef BITCOIN_UTIL_REF_H -#define BITCOIN_UTIL_REF_H - -#include <util/check.h> - -#include <typeindex> - -namespace util { - -/** - * Type-safe dynamic reference. - * - * This implements a small subset of the functionality in C++17's std::any - * class, and can be dropped when the project updates to C++17 - * (https://github.com/bitcoin/bitcoin/issues/16684) - */ -class Ref -{ -public: - Ref() = default; - template<typename T> Ref(T& value) { Set(value); } - template<typename T> T& Get() const { CHECK_NONFATAL(Has<T>()); return *static_cast<T*>(m_value); } - template<typename T> void Set(T& value) { m_value = &value; m_type = std::type_index(typeid(T)); } - template<typename T> bool Has() const { return m_value && m_type == std::type_index(typeid(T)); } - void Clear() { m_value = nullptr; m_type = std::type_index(typeid(void)); } - -private: - void* m_value = nullptr; - std::type_index m_type = std::type_index(typeid(void)); -}; - -} // namespace util - -#endif // BITCOIN_UTIL_REF_H diff --git a/src/util/sock.cpp b/src/util/sock.cpp index 4c65b5b680..0bc9795db3 100644 --- a/src/util/sock.cpp +++ b/src/util/sock.cpp @@ -4,6 +4,7 @@ #include <compat.h> #include <logging.h> +#include <threadinterrupt.h> #include <tinyformat.h> #include <util/sock.h> #include <util/system.h> @@ -12,12 +13,18 @@ #include <codecvt> #include <cwchar> #include <locale> +#include <stdexcept> #include <string> #ifdef USE_POLL #include <poll.h> #endif +static inline bool IOErrorIsPermanent(int err) +{ + return err != WSAEAGAIN && err != WSAEINTR && err != WSAEWOULDBLOCK && err != WSAEINPROGRESS; +} + Sock::Sock() : m_socket(INVALID_SOCKET) {} Sock::Sock(SOCKET s) : m_socket(s) {} @@ -59,7 +66,17 @@ ssize_t Sock::Recv(void* buf, size_t len, int flags) const return recv(m_socket, static_cast<char*>(buf), len, flags); } -bool Sock::Wait(std::chrono::milliseconds timeout, Event requested) const +int Sock::Connect(const sockaddr* addr, socklen_t addr_len) const +{ + return connect(m_socket, addr, addr_len); +} + +int Sock::GetSockOpt(int level, int opt_name, void* opt_val, socklen_t* opt_len) const +{ + return getsockopt(m_socket, level, opt_name, static_cast<char*>(opt_val), opt_len); +} + +bool Sock::Wait(std::chrono::milliseconds timeout, Event requested, Event* occurred) const { #ifdef USE_POLL pollfd fd; @@ -72,7 +89,21 @@ bool Sock::Wait(std::chrono::milliseconds timeout, Event requested) const fd.events |= POLLOUT; } - return poll(&fd, 1, count_milliseconds(timeout)) != SOCKET_ERROR; + if (poll(&fd, 1, count_milliseconds(timeout)) == SOCKET_ERROR) { + return false; + } + + if (occurred != nullptr) { + *occurred = 0; + if (fd.revents & POLLIN) { + *occurred |= RECV; + } + if (fd.revents & POLLOUT) { + *occurred |= SEND; + } + } + + return true; #else if (!IsSelectableSocket(m_socket)) { return false; @@ -93,10 +124,173 @@ bool Sock::Wait(std::chrono::milliseconds timeout, Event requested) const timeval timeout_struct = MillisToTimeval(timeout); - return select(m_socket + 1, &fdset_recv, &fdset_send, nullptr, &timeout_struct) != SOCKET_ERROR; + if (select(m_socket + 1, &fdset_recv, &fdset_send, nullptr, &timeout_struct) == SOCKET_ERROR) { + return false; + } + + if (occurred != nullptr) { + *occurred = 0; + if (FD_ISSET(m_socket, &fdset_recv)) { + *occurred |= RECV; + } + if (FD_ISSET(m_socket, &fdset_send)) { + *occurred |= SEND; + } + } + + return true; #endif /* USE_POLL */ } +void Sock::SendComplete(const std::string& data, + std::chrono::milliseconds timeout, + CThreadInterrupt& interrupt) const +{ + const auto deadline = GetTime<std::chrono::milliseconds>() + timeout; + size_t sent{0}; + + for (;;) { + const ssize_t ret{Send(data.data() + sent, data.size() - sent, MSG_NOSIGNAL)}; + + if (ret > 0) { + sent += static_cast<size_t>(ret); + if (sent == data.size()) { + break; + } + } else { + const int err{WSAGetLastError()}; + if (IOErrorIsPermanent(err)) { + throw std::runtime_error(strprintf("send(): %s", NetworkErrorString(err))); + } + } + + const auto now = GetTime<std::chrono::milliseconds>(); + + if (now >= deadline) { + throw std::runtime_error(strprintf( + "Send timeout (sent only %u of %u bytes before that)", sent, data.size())); + } + + if (interrupt) { + throw std::runtime_error(strprintf( + "Send interrupted (sent only %u of %u bytes before that)", sent, data.size())); + } + + // Wait for a short while (or the socket to become ready for sending) before retrying + // if nothing was sent. + const auto wait_time = std::min(deadline - now, std::chrono::milliseconds{MAX_WAIT_FOR_IO}); + Wait(wait_time, SEND); + } +} + +std::string Sock::RecvUntilTerminator(uint8_t terminator, + std::chrono::milliseconds timeout, + CThreadInterrupt& interrupt, + size_t max_data) const +{ + const auto deadline = GetTime<std::chrono::milliseconds>() + timeout; + std::string data; + bool terminator_found{false}; + + // We must not consume any bytes past the terminator from the socket. + // One option is to read one byte at a time and check if we have read a terminator. + // However that is very slow. Instead, we peek at what is in the socket and only read + // as many bytes as possible without crossing the terminator. + // Reading 64 MiB of random data with 262526 terminator chars takes 37 seconds to read + // one byte at a time VS 0.71 seconds with the "peek" solution below. Reading one byte + // at a time is about 50 times slower. + + for (;;) { + if (data.size() >= max_data) { + throw std::runtime_error( + strprintf("Received too many bytes without a terminator (%u)", data.size())); + } + + char buf[512]; + + const ssize_t peek_ret{Recv(buf, std::min(sizeof(buf), max_data - data.size()), MSG_PEEK)}; + + switch (peek_ret) { + case -1: { + const int err{WSAGetLastError()}; + if (IOErrorIsPermanent(err)) { + throw std::runtime_error(strprintf("recv(): %s", NetworkErrorString(err))); + } + break; + } + case 0: + throw std::runtime_error("Connection unexpectedly closed by peer"); + default: + auto end = buf + peek_ret; + auto terminator_pos = std::find(buf, end, terminator); + terminator_found = terminator_pos != end; + + const size_t try_len{terminator_found ? terminator_pos - buf + 1 : + static_cast<size_t>(peek_ret)}; + + const ssize_t read_ret{Recv(buf, try_len, 0)}; + + if (read_ret < 0 || static_cast<size_t>(read_ret) != try_len) { + throw std::runtime_error( + strprintf("recv() returned %u bytes on attempt to read %u bytes but previous " + "peek claimed %u bytes are available", + read_ret, try_len, peek_ret)); + } + + // Don't include the terminator in the output. + const size_t append_len{terminator_found ? try_len - 1 : try_len}; + + data.append(buf, buf + append_len); + + if (terminator_found) { + return data; + } + } + + const auto now = GetTime<std::chrono::milliseconds>(); + + if (now >= deadline) { + throw std::runtime_error(strprintf( + "Receive timeout (received %u bytes without terminator before that)", data.size())); + } + + if (interrupt) { + throw std::runtime_error(strprintf( + "Receive interrupted (received %u bytes without terminator before that)", + data.size())); + } + + // Wait for a short while (or the socket to become ready for reading) before retrying. + const auto wait_time = std::min(deadline - now, std::chrono::milliseconds{MAX_WAIT_FOR_IO}); + Wait(wait_time, RECV); + } +} + +bool Sock::IsConnected(std::string& errmsg) const +{ + if (m_socket == INVALID_SOCKET) { + errmsg = "not connected"; + return false; + } + + char c; + switch (Recv(&c, sizeof(c), MSG_PEEK)) { + case -1: { + const int err = WSAGetLastError(); + if (IOErrorIsPermanent(err)) { + errmsg = NetworkErrorString(err); + return false; + } + return true; + } + case 0: + errmsg = "closed"; + return false; + default: + return true; + } +} + #ifdef WIN32 std::string NetworkErrorString(int err) { diff --git a/src/util/sock.h b/src/util/sock.h index 26fe60f18f..c4ad0cbc43 100644 --- a/src/util/sock.h +++ b/src/util/sock.h @@ -6,11 +6,19 @@ #define BITCOIN_UTIL_SOCK_H #include <compat.h> +#include <threadinterrupt.h> +#include <util/time.h> #include <chrono> #include <string> /** + * Maximum time to wait for I/O readiness. + * It will take up until this time to break off in case of an interruption. + */ +static constexpr auto MAX_WAIT_FOR_IO = 1s; + +/** * RAII helper class that manages a socket. Mimics `std::unique_ptr`, but instead of a pointer it * contains a socket and closes it automatically when it goes out of scope. */ @@ -72,16 +80,29 @@ public: /** * send(2) wrapper. Equivalent to `send(this->Get(), data, len, flags);`. Code that uses this - * wrapper can be unit-tested if this method is overridden by a mock Sock implementation. + * wrapper can be unit tested if this method is overridden by a mock Sock implementation. */ virtual ssize_t Send(const void* data, size_t len, int flags) const; /** * recv(2) wrapper. Equivalent to `recv(this->Get(), buf, len, flags);`. Code that uses this - * wrapper can be unit-tested if this method is overridden by a mock Sock implementation. + * wrapper can be unit tested if this method is overridden by a mock Sock implementation. */ virtual ssize_t Recv(void* buf, size_t len, int flags) const; + /** + * connect(2) wrapper. Equivalent to `connect(this->Get(), addr, addrlen)`. Code that uses this + * wrapper can be unit tested if this method is overridden by a mock Sock implementation. + */ + virtual int Connect(const sockaddr* addr, socklen_t addr_len) const; + + /** + * getsockopt(2) wrapper. Equivalent to + * `getsockopt(this->Get(), level, opt_name, opt_val, opt_len)`. Code that uses this + * wrapper can be unit tested if this method is overridden by a mock Sock implementation. + */ + virtual int GetSockOpt(int level, int opt_name, void* opt_val, socklen_t* opt_len) const; + using Event = uint8_t; /** @@ -98,11 +119,54 @@ public: * Wait for readiness for input (recv) or output (send). * @param[in] timeout Wait this much for at least one of the requested events to occur. * @param[in] requested Wait for those events, bitwise-or of `RECV` and `SEND`. + * @param[out] occurred If not nullptr and `true` is returned, then upon return this + * indicates which of the requested events occurred. A timeout is indicated by return + * value of `true` and `occurred` being set to 0. * @return true on success and false otherwise */ - virtual bool Wait(std::chrono::milliseconds timeout, Event requested) const; + virtual bool Wait(std::chrono::milliseconds timeout, + Event requested, + Event* occurred = nullptr) const; + + /* Higher level, convenience, methods. These may throw. */ + + /** + * Send the given data, retrying on transient errors. + * @param[in] data Data to send. + * @param[in] timeout Timeout for the entire operation. + * @param[in] interrupt If this is signaled then the operation is canceled. + * @throws std::runtime_error if the operation cannot be completed. In this case only some of + * the data will be written to the socket. + */ + virtual void SendComplete(const std::string& data, + std::chrono::milliseconds timeout, + CThreadInterrupt& interrupt) const; + + /** + * Read from socket until a terminator character is encountered. Will never consume bytes past + * the terminator from the socket. + * @param[in] terminator Character up to which to read from the socket. + * @param[in] timeout Timeout for the entire operation. + * @param[in] interrupt If this is signaled then the operation is canceled. + * @param[in] max_data The maximum amount of data (in bytes) to receive. If this many bytes + * are received and there is still no terminator, then this method will throw an exception. + * @return The data that has been read, without the terminating character. + * @throws std::runtime_error if the operation cannot be completed. In this case some bytes may + * have been consumed from the socket. + */ + virtual std::string RecvUntilTerminator(uint8_t terminator, + std::chrono::milliseconds timeout, + CThreadInterrupt& interrupt, + size_t max_data) const; + + /** + * Check if still connected. + * @param[out] err The error string, if the socket has been disconnected. + * @return true if connected + */ + virtual bool IsConnected(std::string& errmsg) const; -private: +protected: /** * Contained socket. `INVALID_SOCKET` designates the object is empty. */ diff --git a/src/util/strencodings.cpp b/src/util/strencodings.cpp index f3d54a2ac9..4734de3e0b 100644 --- a/src/util/strencodings.cpp +++ b/src/util/strencodings.cpp @@ -107,23 +107,25 @@ std::vector<unsigned char> ParseHex(const std::string& str) return ParseHex(str.c_str()); } -void SplitHostPort(std::string in, int &portOut, std::string &hostOut) { +void SplitHostPort(std::string in, uint16_t& portOut, std::string& hostOut) +{ size_t colon = in.find_last_of(':'); // if a : is found, and it either follows a [...], or no other : is in the string, treat it as port separator bool fHaveColon = colon != in.npos; - bool fBracketed = fHaveColon && (in[0]=='[' && in[colon-1]==']'); // if there is a colon, and in[0]=='[', colon is not 0, so in[colon-1] is safe - bool fMultiColon = fHaveColon && (in.find_last_of(':',colon-1) != in.npos); - if (fHaveColon && (colon==0 || fBracketed || !fMultiColon)) { - int32_t n; - if (ParseInt32(in.substr(colon + 1), &n) && n > 0 && n < 0x10000) { + bool fBracketed = fHaveColon && (in[0] == '[' && in[colon - 1] == ']'); // if there is a colon, and in[0]=='[', colon is not 0, so in[colon-1] is safe + bool fMultiColon = fHaveColon && (in.find_last_of(':', colon - 1) != in.npos); + if (fHaveColon && (colon == 0 || fBracketed || !fMultiColon)) { + uint16_t n; + if (ParseUInt16(in.substr(colon + 1), &n)) { in = in.substr(0, colon); portOut = n; } } - if (in.size()>0 && in[0] == '[' && in[in.size()-1] == ']') - hostOut = in.substr(1, in.size()-2); - else + if (in.size() > 0 && in[0] == '[' && in[in.size() - 1] == ']') { + hostOut = in.substr(1, in.size() - 2); + } else { hostOut = in; + } } std::string EncodeBase64(Span<const unsigned char> input) @@ -334,6 +336,18 @@ bool ParseUInt8(const std::string& str, uint8_t *out) return true; } +bool ParseUInt16(const std::string& str, uint16_t* out) +{ + uint32_t u32; + if (!ParseUInt32(str, &u32) || u32 > std::numeric_limits<uint16_t>::max()) { + return false; + } + if (out != nullptr) { + *out = static_cast<uint16_t>(u32); + } + return true; +} + bool ParseUInt32(const std::string& str, uint32_t *out) { if (!ParsePrechecks(str)) diff --git a/src/util/strencodings.h b/src/util/strencodings.h index 98379e9138..26dc0a0ce3 100644 --- a/src/util/strencodings.h +++ b/src/util/strencodings.h @@ -65,7 +65,7 @@ std::string EncodeBase32(Span<const unsigned char> input, bool pad = true); */ std::string EncodeBase32(const std::string& str, bool pad = true); -void SplitHostPort(std::string in, int& portOut, std::string& hostOut); +void SplitHostPort(std::string in, uint16_t& portOut, std::string& hostOut); int64_t atoi64(const std::string& str); int atoi(const std::string& str); @@ -116,6 +116,13 @@ constexpr inline bool IsSpace(char c) noexcept { [[nodiscard]] bool ParseUInt8(const std::string& str, uint8_t *out); /** + * Convert decimal string to unsigned 16-bit integer with strict parse error feedback. + * @returns true if the entire string could be parsed as valid integer, + * false if the entire string could not be parsed or if overflow or underflow occurred. + */ +[[nodiscard]] bool ParseUInt16(const std::string& str, uint16_t* out); + +/** * Convert decimal string to unsigned 32-bit integer with strict parse error feedback. * @returns true if the entire string could be parsed as valid integer, * false if not the entire string could be parsed or when overflow or underflow occurred. diff --git a/src/util/system.cpp b/src/util/system.cpp index 71453eed81..0b83a76504 100644 --- a/src/util/system.cpp +++ b/src/util/system.cpp @@ -100,7 +100,7 @@ bool LockDirectory(const fs::path& directory, const std::string lockfile_name, b // Create empty lock file if it doesn't exist. FILE* file = fsbridge::fopen(pathLockFile, "a"); if (file) fclose(file); - auto lock = MakeUnique<fsbridge::FileLock>(pathLockFile); + auto lock = std::make_unique<fsbridge::FileLock>(pathLockFile); if (!lock->TryLock()) { return error("Error while attempting to lock directory %s: %s", directory.string(), lock->GetReason()); } @@ -315,7 +315,7 @@ bool ArgsManager::ParseParameters(int argc, const char* const argv[], std::strin if (key[0] != '-') { if (!m_accept_any_command && m_command.empty()) { // The first non-dash arg is a registered command - Optional<unsigned int> flags = GetArgFlags(key); + std::optional<unsigned int> flags = GetArgFlags(key); if (!flags || !(*flags & ArgsManager::COMMAND)) { error = strprintf("Invalid command '%s'", argv[i]); return false; @@ -337,7 +337,7 @@ bool ArgsManager::ParseParameters(int argc, const char* const argv[], std::strin key.erase(0, 1); std::string section; util::SettingsValue value = InterpretOption(section, key, val); - Optional<unsigned int> flags = GetArgFlags('-' + key); + std::optional<unsigned int> flags = GetArgFlags('-' + key); // Unknown command line options and command line options with dot // characters (which are returned from InterpretOption with nonempty @@ -363,7 +363,7 @@ bool ArgsManager::ParseParameters(int argc, const char* const argv[], std::strin return success; } -Optional<unsigned int> ArgsManager::GetArgFlags(const std::string& name) const +std::optional<unsigned int> ArgsManager::GetArgFlags(const std::string& name) const { LOCK(cs_args); for (const auto& arg_map : m_available_args) { @@ -372,7 +372,7 @@ Optional<unsigned int> ArgsManager::GetArgFlags(const std::string& name) const return search->second.m_flags; } } - return nullopt; + return std::nullopt; } std::optional<const ArgsManager::Command> ArgsManager::GetCommand() const @@ -874,7 +874,7 @@ bool ArgsManager::ReadConfigStream(std::istream& stream, const std::string& file std::string section; std::string key = option.first; util::SettingsValue value = InterpretOption(section, key, option.second); - Optional<unsigned int> flags = GetArgFlags('-' + key); + std::optional<unsigned int> flags = GetArgFlags('-' + key); if (flags) { if (!CheckValid(key, value, *flags, error)) { return false; @@ -1034,7 +1034,7 @@ void ArgsManager::logArgsPrefix( std::string section_str = section.empty() ? "" : "[" + section + "] "; for (const auto& arg : args) { for (const auto& value : arg.second) { - Optional<unsigned int> flags = GetArgFlags('-' + arg.first); + std::optional<unsigned int> flags = GetArgFlags('-' + arg.first); if (flags) { std::string value_str = (*flags & SENSITIVE) ? "****" : value.write(); LogPrintf("%s %s%s=%s\n", prefix, section_str, arg.first, value_str); diff --git a/src/util/system.h b/src/util/system.h index de47b93b6e..29657e56e2 100644 --- a/src/util/system.h +++ b/src/util/system.h @@ -19,16 +19,16 @@ #include <compat/assumptions.h> #include <fs.h> #include <logging.h> -#include <optional.h> #include <sync.h> #include <tinyformat.h> -#include <util/memory.h> #include <util/settings.h> #include <util/threadnames.h> #include <util/time.h> +#include <any> #include <exception> #include <map> +#include <optional> #include <set> #include <stdint.h> #include <string> @@ -376,7 +376,7 @@ public: * Return Flags for known arg. * Return nullopt for unknown arg. */ - Optional<unsigned int> GetArgFlags(const std::string& name) const; + std::optional<unsigned int> GetArgFlags(const std::string& name) const; /** * Read and update settings file with saved settings. This needs to be @@ -501,6 +501,18 @@ inline void insert(std::set<TsetT>& dst, const Tsrc& src) { dst.insert(src.begin(), src.end()); } +/** + * Helper function to access the contained object of a std::any instance. + * Returns a pointer to the object if passed instance has a value and the type + * matches, nullptr otherwise. + */ +template<typename T> +T* AnyPtr(const std::any& any) noexcept +{ + T* const* ptr = std::any_cast<T*>(&any); + return ptr ? *ptr : nullptr; +} + #ifdef WIN32 class WinCmdLineArgs { diff --git a/src/util/time.h b/src/util/time.h index 56131ce0fe..7ebcaaa339 100644 --- a/src/util/time.h +++ b/src/util/time.h @@ -26,9 +26,16 @@ void UninterruptibleSleep(const std::chrono::microseconds& n); * This helper is used to convert durations before passing them over an * interface that doesn't support std::chrono (e.g. RPC, debug log, or the GUI) */ -inline int64_t count_seconds(std::chrono::seconds t) { return t.count(); } -inline int64_t count_milliseconds(std::chrono::milliseconds t) { return t.count(); } -inline int64_t count_microseconds(std::chrono::microseconds t) { return t.count(); } +constexpr int64_t count_seconds(std::chrono::seconds t) { return t.count(); } +constexpr int64_t count_milliseconds(std::chrono::milliseconds t) { return t.count(); } +constexpr int64_t count_microseconds(std::chrono::microseconds t) { return t.count(); } + +using SecondsDouble = std::chrono::duration<double, std::chrono::seconds::period>; + +/** + * Helper to count the seconds in any std::chrono::duration type + */ +inline double CountSecondsDouble(SecondsDouble t) { return t.count(); } /** * DEPRECATED diff --git a/src/util/tokenpipe.cpp b/src/util/tokenpipe.cpp new file mode 100644 index 0000000000..4c091cd2e6 --- /dev/null +++ b/src/util/tokenpipe.cpp @@ -0,0 +1,109 @@ +// 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 <util/tokenpipe.h> + +#include <config/bitcoin-config.h> + +#ifndef WIN32 + +#include <errno.h> +#include <fcntl.h> +#include <optional> +#include <unistd.h> + +TokenPipeEnd TokenPipe::TakeReadEnd() +{ + TokenPipeEnd res(m_fds[0]); + m_fds[0] = -1; + return res; +} + +TokenPipeEnd TokenPipe::TakeWriteEnd() +{ + TokenPipeEnd res(m_fds[1]); + m_fds[1] = -1; + return res; +} + +TokenPipeEnd::TokenPipeEnd(int fd) : m_fd(fd) +{ +} + +TokenPipeEnd::~TokenPipeEnd() +{ + Close(); +} + +int TokenPipeEnd::TokenWrite(uint8_t token) +{ + while (true) { + ssize_t result = write(m_fd, &token, 1); + if (result < 0) { + // Failure. It's possible that the write was interrupted by a signal, + // in that case retry. + if (errno != EINTR) { + return TS_ERR; + } + } else if (result == 0) { + return TS_EOS; + } else { // ==1 + return 0; + } + } +} + +int TokenPipeEnd::TokenRead() +{ + uint8_t token; + while (true) { + ssize_t result = read(m_fd, &token, 1); + if (result < 0) { + // Failure. Check if the read was interrupted by a signal, + // in that case retry. + if (errno != EINTR) { + return TS_ERR; + } + } else if (result == 0) { + return TS_EOS; + } else { // ==1 + return token; + } + } + return token; +} + +void TokenPipeEnd::Close() +{ + if (m_fd != -1) close(m_fd); + m_fd = -1; +} + +std::optional<TokenPipe> TokenPipe::Make() +{ + int fds[2] = {-1, -1}; +#if HAVE_O_CLOEXEC && HAVE_DECL_PIPE2 + if (pipe2(fds, O_CLOEXEC) != 0) { + return std::nullopt; + } +#else + if (pipe(fds) != 0) { + return std::nullopt; + } +#endif + return TokenPipe(fds); +} + +TokenPipe::~TokenPipe() +{ + Close(); +} + +void TokenPipe::Close() +{ + if (m_fds[0] != -1) close(m_fds[0]); + if (m_fds[1] != -1) close(m_fds[1]); + m_fds[0] = m_fds[1] = -1; +} + +#endif // WIN32 diff --git a/src/util/tokenpipe.h b/src/util/tokenpipe.h new file mode 100644 index 0000000000..f56be93a38 --- /dev/null +++ b/src/util/tokenpipe.h @@ -0,0 +1,127 @@ +// Copyright (c) 2021 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_UTIL_TOKENPIPE_H +#define BITCOIN_UTIL_TOKENPIPE_H + +#ifndef WIN32 + +#include <cstdint> +#include <optional> + +/** One end of a token pipe. */ +class TokenPipeEnd +{ +private: + int m_fd = -1; + +public: + TokenPipeEnd(int fd = -1); + ~TokenPipeEnd(); + + /** Return value constants for TokenWrite and TokenRead. */ + enum Status { + TS_ERR = -1, //!< I/O error + TS_EOS = -2, //!< Unexpected end of stream + }; + + /** Write token to endpoint. + * + * @returns 0 If successful. + * <0 if error: + * TS_ERR If an error happened. + * TS_EOS If end of stream happened. + */ + int TokenWrite(uint8_t token); + + /** Read token from endpoint. + * + * @returns >=0 Token value, if successful. + * <0 if error: + * TS_ERR If an error happened. + * TS_EOS If end of stream happened. + */ + int TokenRead(); + + /** Explicit close function. + */ + void Close(); + + /** Return whether endpoint is open. + */ + bool IsOpen() { return m_fd != -1; } + + // Move-only class. + TokenPipeEnd(TokenPipeEnd&& other) + { + m_fd = other.m_fd; + other.m_fd = -1; + } + TokenPipeEnd& operator=(TokenPipeEnd&& other) + { + Close(); + m_fd = other.m_fd; + other.m_fd = -1; + return *this; + } + TokenPipeEnd(const TokenPipeEnd&) = delete; + TokenPipeEnd& operator=(const TokenPipeEnd&) = delete; +}; + +/** An interprocess or interthread pipe for sending tokens (one-byte values) + * over. + */ +class TokenPipe +{ +private: + int m_fds[2] = {-1, -1}; + + TokenPipe(int fds[2]) : m_fds{fds[0], fds[1]} {} + +public: + ~TokenPipe(); + + /** Create a new pipe. + * @returns The created TokenPipe, or an empty std::nullopt in case of error. + */ + static std::optional<TokenPipe> Make(); + + /** Take the read end of this pipe. This can only be called once, + * as the object will be moved out. + */ + TokenPipeEnd TakeReadEnd(); + + /** Take the write end of this pipe. This should only be called once, + * as the object will be moved out. + */ + TokenPipeEnd TakeWriteEnd(); + + /** Close and end of the pipe that hasn't been moved out. + */ + void Close(); + + // Move-only class. + TokenPipe(TokenPipe&& other) + { + for (int i = 0; i < 2; ++i) { + m_fds[i] = other.m_fds[i]; + other.m_fds[i] = -1; + } + } + TokenPipe& operator=(TokenPipe&& other) + { + Close(); + for (int i = 0; i < 2; ++i) { + m_fds[i] = other.m_fds[i]; + other.m_fds[i] = -1; + } + return *this; + } + TokenPipe(const TokenPipe&) = delete; + TokenPipe& operator=(const TokenPipe&) = delete; +}; + +#endif // WIN32 + +#endif // BITCOIN_UTIL_TOKENPIPE_H diff --git a/src/validation.cpp b/src/validation.cpp index 0b2ca4b422..b5c19971b4 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -23,7 +23,6 @@ #include <logging/timer.h> #include <node/coinstats.h> #include <node/ui_interface.h> -#include <optional.h> #include <policy/policy.h> #include <policy/settings.h> #include <pow.h> @@ -50,6 +49,7 @@ #include <validationinterface.h> #include <warnings.h> +#include <optional> #include <string> #include <boost/algorithm/string/replace.hpp> @@ -169,7 +169,7 @@ namespace { std::set<int> setDirtyFileInfo; } // anon namespace -CBlockIndex* BlockManager::LookupBlockIndex(const uint256& hash) +CBlockIndex* BlockManager::LookupBlockIndex(const uint256& hash) const { AssertLockHeld(cs_main); assert(std::addressof(g_chainman.BlockIndex()) == std::addressof(m_block_index)); @@ -211,6 +211,7 @@ static FlatFileSeq UndoFileSeq(); bool CheckFinalTx(const CBlockIndex* active_chain_tip, const CTransaction &tx, int flags) { AssertLockHeld(cs_main); + assert(active_chain_tip); // TODO: Make active_chain_tip a reference assert(std::addressof(*::ChainActive().Tip()) == std::addressof(*active_chain_tip)); // By convention a negative value for flags indicates that the @@ -221,12 +222,12 @@ bool CheckFinalTx(const CBlockIndex* active_chain_tip, const CTransaction &tx, i // scheduled, so no flags are set. flags = std::max(flags, 0); - // CheckFinalTx() uses ::ChainActive().Height()+1 to evaluate + // CheckFinalTx() uses active_chain_tip.Height()+1 to evaluate // nLockTime because when IsFinalTx() is called within // CBlock::AcceptBlock(), the height of the block *being* // evaluated is what is used. Thus if we want to know if a // transaction can be part of the *next* block, we need to call - // IsFinalTx() with one more than ::ChainActive().Height(). + // IsFinalTx() with one more than active_chain_tip.Height(). const int nBlockHeight = active_chain_tip->nHeight + 1; // BIP113 requires that time-locked transactions have nLockTime set to @@ -1286,7 +1287,7 @@ CoinsViews::CoinsViews( void CoinsViews::InitCache() { - m_cacheview = MakeUnique<CCoinsViewCache>(&m_catcherview); + m_cacheview = std::make_unique<CCoinsViewCache>(&m_catcherview); } CChainState::CChainState(CTxMemPool& mempool, BlockManager& blockman, uint256 from_snapshot_blockhash) @@ -1304,7 +1305,7 @@ void CChainState::InitCoinsDB( leveldb_name += "_" + m_from_snapshot_blockhash.ToString(); } - m_coins_views = MakeUnique<CoinsViews>( + m_coins_views = std::make_unique<CoinsViews>( leveldb_name, cache_size_bytes, in_memory, should_wipe); } @@ -1362,16 +1363,18 @@ static void AlertNotify(const std::string& strMessage) #endif } -static void CheckForkWarningConditions() EXCLUSIVE_LOCKS_REQUIRED(cs_main) +void CChainState::CheckForkWarningConditions() { AssertLockHeld(cs_main); + assert(std::addressof(::ChainstateActive()) == std::addressof(*this)); + // Before we get past initial download, we cannot reliably alert about forks // (we assume we don't get stuck on a fork before finishing our initial sync) - if (::ChainstateActive().IsInitialBlockDownload()) { + if (IsInitialBlockDownload()) { return; } - if (pindexBestInvalid && pindexBestInvalid->nChainWork > ::ChainActive().Tip()->nChainWork + (GetBlockProof(*::ChainActive().Tip()) * 6)) { + if (pindexBestInvalid && pindexBestInvalid->nChainWork > m_chain.Tip()->nChainWork + (GetBlockProof(*m_chain.Tip()) * 6)) { LogPrintf("%s: Warning: Found invalid chain at least ~6 blocks longer than our best chain.\nChain state database corruption likely.\n", __func__); SetfLargeWorkInvalidChainFound(true); } else { @@ -1380,21 +1383,22 @@ static void CheckForkWarningConditions() EXCLUSIVE_LOCKS_REQUIRED(cs_main) } // Called both upon regular invalid block discovery *and* InvalidateBlock -void static InvalidChainFound(CBlockIndex* pindexNew) EXCLUSIVE_LOCKS_REQUIRED(cs_main) +void CChainState::InvalidChainFound(CBlockIndex* pindexNew) { + assert(std::addressof(::ChainstateActive()) == std::addressof(*this)); if (!pindexBestInvalid || pindexNew->nChainWork > pindexBestInvalid->nChainWork) pindexBestInvalid = pindexNew; if (pindexBestHeader != nullptr && pindexBestHeader->GetAncestor(pindexNew->nHeight) == pindexNew) { - pindexBestHeader = ::ChainActive().Tip(); + pindexBestHeader = m_chain.Tip(); } LogPrintf("%s: invalid block=%s height=%d log2_work=%f date=%s\n", __func__, pindexNew->GetBlockHash().ToString(), pindexNew->nHeight, log(pindexNew->nChainWork.getdouble())/log(2.0), FormatISO8601DateTime(pindexNew->GetBlockTime())); - CBlockIndex *tip = ::ChainActive().Tip(); + CBlockIndex *tip = m_chain.Tip(); assert (tip); LogPrintf("%s: current best=%s height=%d log2_work=%f date=%s\n", __func__, - tip->GetBlockHash().ToString(), ::ChainActive().Height(), log(tip->nChainWork.getdouble())/log(2.0), + tip->GetBlockHash().ToString(), m_chain.Height(), log(tip->nChainWork.getdouble())/log(2.0), FormatISO8601DateTime(tip->GetBlockTime())); CheckForkWarningConditions(); } @@ -2432,7 +2436,7 @@ static void AppendWarning(bilingual_str& res, const bilingual_str& warn) } /** Check warning conditions and do some notifications on new chain tip set. */ -static void UpdateTip(CTxMemPool& mempool, const CBlockIndex* pindexNew, const CChainParams& chainParams) +static void UpdateTip(CTxMemPool& mempool, const CBlockIndex* pindexNew, const CChainParams& chainParams, CChainState& active_chainstate) EXCLUSIVE_LOCKS_REQUIRED(::cs_main) { // New best block @@ -2445,7 +2449,8 @@ static void UpdateTip(CTxMemPool& mempool, const CBlockIndex* pindexNew, const C } bilingual_str warning_messages; - if (!::ChainstateActive().IsInitialBlockDownload()) { + assert(std::addressof(::ChainstateActive()) == std::addressof(active_chainstate)); + if (!active_chainstate.IsInitialBlockDownload()) { const CBlockIndex* pindex = pindexNew; for (int bit = 0; bit < VERSIONBITS_NUM_BITS; bit++) { WarningBitsConditionChecker checker(bit); @@ -2460,11 +2465,12 @@ static void UpdateTip(CTxMemPool& mempool, const CBlockIndex* pindexNew, const C } } } + assert(std::addressof(::ChainstateActive()) == std::addressof(active_chainstate)); LogPrintf("%s: new best=%s height=%d version=0x%08x log2_work=%f tx=%lu date='%s' progress=%f cache=%.1fMiB(%utxo)%s\n", __func__, pindexNew->GetBlockHash().ToString(), pindexNew->nHeight, pindexNew->nVersion, log(pindexNew->nChainWork.getdouble())/log(2.0), (unsigned long)pindexNew->nChainTx, FormatISO8601DateTime(pindexNew->GetBlockTime()), - GuessVerificationProgress(chainParams.TxData(), pindexNew), ::ChainstateActive().CoinsTip().DynamicMemoryUsage() * (1.0 / (1<<20)), ::ChainstateActive().CoinsTip().GetCacheSize(), + GuessVerificationProgress(chainParams.TxData(), pindexNew), active_chainstate.CoinsTip().DynamicMemoryUsage() * (1.0 / (1<<20)), active_chainstate.CoinsTip().GetCacheSize(), !warning_messages.empty() ? strprintf(" warning='%s'", warning_messages.original) : ""); } @@ -2520,7 +2526,7 @@ bool CChainState::DisconnectTip(BlockValidationState& state, const CChainParams& m_chain.SetTip(pindexDelete->pprev); - UpdateTip(m_mempool, pindexDelete->pprev, chainparams); + UpdateTip(m_mempool, pindexDelete->pprev, chainparams, *this); // Let wallets know transactions went from 1-confirmed to // 0-confirmed or conflicted: GetMainSignals().BlockDisconnected(pblock, pindexDelete); @@ -2628,7 +2634,7 @@ bool CChainState::ConnectTip(BlockValidationState& state, const CChainParams& ch disconnectpool.removeForBlock(blockConnecting.vtx); // Update m_chain & related variables. m_chain.SetTip(pindexNew); - UpdateTip(m_mempool, pindexNew, chainparams); + UpdateTip(m_mempool, pindexNew, chainparams, *this); int64_t nTime6 = GetTimeMicros(); nTimePostConnect += nTime6 - nTime5; nTimeTotal += nTime6 - nTime1; LogPrint(BCLog::BENCH, " - Connect postprocess: %.2fms [%.2fs (%.2fms/blk)]\n", (nTime6 - nTime5) * MILLI, nTimePostConnect * MICRO, nTimePostConnect * MILLI / nBlocksTotal); @@ -2719,6 +2725,7 @@ bool CChainState::ActivateBestChainStep(BlockValidationState& state, const CChai { AssertLockHeld(cs_main); AssertLockHeld(m_mempool.cs); + assert(std::addressof(::ChainstateActive()) == std::addressof(*this)); const CBlockIndex* pindexOldTip = m_chain.Tip(); const CBlockIndex* pindexFork = m_chain.FindFork(pindexMostWork); @@ -2730,7 +2737,7 @@ bool CChainState::ActivateBestChainStep(BlockValidationState& state, const CChai if (!DisconnectTip(state, chainparams, &disconnectpool)) { // This is likely a fatal error, but keep the mempool consistent, // just in case. Only remove from the mempool in this case. - UpdateMempoolForReorg(::ChainstateActive(), m_mempool, disconnectpool, false); + UpdateMempoolForReorg(*this, m_mempool, disconnectpool, false); // If we're unable to disconnect a block during normal operation, // then that is a failure of our local system -- we should abort @@ -2774,7 +2781,7 @@ bool CChainState::ActivateBestChainStep(BlockValidationState& state, const CChai // A system error occurred (disk space, database error, ...). // Make the mempool consistent with the current tip, just in case // any observers try to use it before shutdown. - UpdateMempoolForReorg(::ChainstateActive(), m_mempool, disconnectpool, false); + UpdateMempoolForReorg(*this, m_mempool, disconnectpool, false); return false; } } else { @@ -2791,9 +2798,9 @@ bool CChainState::ActivateBestChainStep(BlockValidationState& state, const CChai if (fBlocksDisconnected) { // If any blocks were disconnected, disconnectpool may be non empty. Add // any disconnected transactions back to the mempool. - UpdateMempoolForReorg(::ChainstateActive(), m_mempool, disconnectpool, true); + UpdateMempoolForReorg(*this, m_mempool, disconnectpool, true); } - m_mempool.check(&CoinsTip()); + m_mempool.check(*this); CheckForkWarningConditions(); @@ -2964,12 +2971,13 @@ bool CChainState::PreciousBlock(BlockValidationState& state, const CChainParams& return ActivateBestChain(state, params, std::shared_ptr<const CBlock>()); } -bool PreciousBlock(BlockValidationState& state, const CChainParams& params, CBlockIndex *pindex) { - return ::ChainstateActive().PreciousBlock(state, params, pindex); -} 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; @@ -3028,7 +3036,8 @@ bool CChainState::InvalidateBlock(BlockValidationState& state, const CChainParam // transactions back to the mempool if disconnecting was successful, // and we're not doing a very deep invalidation (in which case // keeping the mempool up to date is probably futile anyway). - UpdateMempoolForReorg(::ChainstateActive(), m_mempool, disconnectpool, /* fAddToMempool = */ (++disconnected <= 10) && ret); + assert(std::addressof(::ChainstateActive()) == std::addressof(*this)); + UpdateMempoolForReorg(*this, m_mempool, disconnectpool, /* fAddToMempool = */ (++disconnected <= 10) && ret); if (!ret) return false; assert(invalid_walk_tip->pprev == m_chain.Tip()); @@ -3104,10 +3113,6 @@ bool CChainState::InvalidateBlock(BlockValidationState& state, const CChainParam return true; } -bool InvalidateBlock(BlockValidationState& state, const CChainParams& chainparams, CBlockIndex *pindex) { - return ::ChainstateActive().InvalidateBlock(state, chainparams, pindex); -} - void CChainState::ResetBlockFailureFlags(CBlockIndex *pindex) { AssertLockHeld(cs_main); @@ -3142,10 +3147,6 @@ void CChainState::ResetBlockFailureFlags(CBlockIndex *pindex) { } } -void ResetBlockFailureFlags(CBlockIndex *pindex) { - return ::ChainstateActive().ResetBlockFailureFlags(pindex); -} - CBlockIndex* BlockManager::AddToBlockIndex(const CBlockHeader& block) { AssertLockHeld(cs_main); @@ -3229,7 +3230,7 @@ void CChainState::ReceivedBlockTransactions(const CBlock& block, CBlockIndex* pi } } -static bool FindBlockPos(FlatFilePos &pos, unsigned int nAddSize, unsigned int nHeight, uint64_t nTime, bool fKnown = false) +static bool FindBlockPos(FlatFilePos &pos, unsigned int nAddSize, unsigned int nHeight, CChain& active_chain, uint64_t nTime, bool fKnown = false) { LOCK(cs_LastBlockFile); @@ -3244,7 +3245,8 @@ static bool FindBlockPos(FlatFilePos &pos, unsigned int nAddSize, unsigned int n // when the undo file is keeping up with the block file, we want to flush it explicitly // when it is lagging behind (more blocks arrive than are being connected), we let the // undo block write case handle it - finalize_undo = (vinfoBlockFile[nFile].nHeightLast == (unsigned int)ChainActive().Tip()->nHeight); + assert(std::addressof(::ChainActive()) == std::addressof(active_chain)); + finalize_undo = (vinfoBlockFile[nFile].nHeightLast == (unsigned int)active_chain.Tip()->nHeight); nFile++; if (vinfoBlockFile.size() <= nFile) { vinfoBlockFile.resize(nFile + 1); @@ -3706,12 +3708,12 @@ bool ChainstateManager::ProcessNewBlockHeaders(const std::vector<CBlockHeader>& } /** Store block on disk. If dbp is non-nullptr, the file is known to already reside on disk */ -static FlatFilePos SaveBlockToDisk(const CBlock& block, int nHeight, const CChainParams& chainparams, const FlatFilePos* dbp) { +static FlatFilePos SaveBlockToDisk(const CBlock& block, int nHeight, CChain& active_chain, const CChainParams& chainparams, const FlatFilePos* dbp) { unsigned int nBlockSize = ::GetSerializeSize(block, CLIENT_VERSION); FlatFilePos blockPos; if (dbp != nullptr) blockPos = *dbp; - if (!FindBlockPos(blockPos, nBlockSize+8, nHeight, block.GetBlockTime(), dbp != nullptr)) { + if (!FindBlockPos(blockPos, nBlockSize+8, nHeight, active_chain, block.GetBlockTime(), dbp != nullptr)) { error("%s: FindBlockPos failed", __func__); return FlatFilePos(); } @@ -3789,8 +3791,9 @@ bool CChainState::AcceptBlock(const std::shared_ptr<const CBlock>& pblock, Block // Write block to history file if (fNewBlock) *fNewBlock = true; + assert(std::addressof(::ChainActive()) == std::addressof(m_chain)); try { - FlatFilePos blockPos = SaveBlockToDisk(block, pindex->nHeight, chainparams, dbp); + FlatFilePos blockPos = SaveBlockToDisk(block, pindex->nHeight, m_chain, chainparams, dbp); if (blockPos.IsNull()) { state.Error(strprintf("%s: Failed to find position to write new block to disk", __func__)); return false; @@ -3961,11 +3964,12 @@ void BlockManager::FindFilesToPruneManual(std::set<int>& setFilesToPrune, int nM } /* This function is called from the RPC code for pruneblockchain */ -void PruneBlockFilesManual(int nManualPruneHeight) +void PruneBlockFilesManual(CChainState& active_chainstate, int nManualPruneHeight) { BlockValidationState state; const CChainParams& chainparams = Params(); - if (!::ChainstateActive().FlushStateToDisk( + assert(std::addressof(::ChainstateActive()) == std::addressof(active_chainstate)); + if (!active_chainstate.FlushStateToDisk( chainparams, state, FlushStateMode::NONE, nManualPruneHeight)) { LogPrintf("%s: failed to flush state (%s)\n", __func__, state.ToString()); } @@ -4140,11 +4144,12 @@ void BlockManager::Unload() { m_block_index.clear(); } -bool static LoadBlockIndexDB(ChainstateManager& chainman, const CChainParams& chainparams) EXCLUSIVE_LOCKS_REQUIRED(cs_main) +bool CChainState::LoadBlockIndexDB(const CChainParams& chainparams) { - if (!chainman.m_blockman.LoadBlockIndex( + assert(std::addressof(::ChainstateActive()) == std::addressof(*this)); + if (!m_blockman.LoadBlockIndex( chainparams.GetConsensus(), *pblocktree, - ::ChainstateActive().setBlockIndexCandidates)) { + setBlockIndexCandidates)) { return false; } @@ -4168,7 +4173,7 @@ bool static LoadBlockIndexDB(ChainstateManager& chainman, const CChainParams& ch // Check presence of blk files LogPrintf("Checking all blk files are present...\n"); std::set<int> setBlkDataFiles; - for (const std::pair<const uint256, CBlockIndex*>& item : chainman.BlockIndex()) { + for (const std::pair<const uint256, CBlockIndex*>& item : m_blockman.m_block_index) { CBlockIndex* pindex = item.second; if (pindex->nStatus & BLOCK_HAVE_DATA) { setBlkDataFiles.insert(pindex->nFile); @@ -4242,15 +4247,17 @@ CVerifyDB::~CVerifyDB() uiInterface.ShowProgress("", 100, false); } -bool CVerifyDB::VerifyDB(const CChainParams& chainparams, CCoinsView *coinsview, int nCheckLevel, int nCheckDepth) +bool CVerifyDB::VerifyDB(const CChainParams& chainparams, CChainState& active_chainstate, CCoinsView *coinsview, int nCheckLevel, int nCheckDepth) { - LOCK(cs_main); - if (::ChainActive().Tip() == nullptr || ::ChainActive().Tip()->pprev == nullptr) + AssertLockHeld(cs_main); + + assert(std::addressof(::ChainstateActive()) == std::addressof(active_chainstate)); + if (active_chainstate.m_chain.Tip() == nullptr || active_chainstate.m_chain.Tip()->pprev == nullptr) return true; // Verify blocks in the best chain - if (nCheckDepth <= 0 || nCheckDepth > ::ChainActive().Height()) - nCheckDepth = ::ChainActive().Height(); + if (nCheckDepth <= 0 || nCheckDepth > active_chainstate.m_chain.Height()) + nCheckDepth = active_chainstate.m_chain.Height(); nCheckLevel = std::max(0, std::min(4, nCheckLevel)); LogPrintf("Verifying last %i blocks at level %i\n", nCheckDepth, nCheckLevel); CCoinsViewCache coins(coinsview); @@ -4260,15 +4267,15 @@ bool CVerifyDB::VerifyDB(const CChainParams& chainparams, CCoinsView *coinsview, BlockValidationState state; int reportDone = 0; LogPrintf("[0%%]..."); /* Continued */ - for (pindex = ::ChainActive().Tip(); pindex && pindex->pprev; pindex = pindex->pprev) { - const int percentageDone = std::max(1, std::min(99, (int)(((double)(::ChainActive().Height() - pindex->nHeight)) / (double)nCheckDepth * (nCheckLevel >= 4 ? 50 : 100)))); + for (pindex = active_chainstate.m_chain.Tip(); pindex && pindex->pprev; pindex = pindex->pprev) { + const int percentageDone = std::max(1, std::min(99, (int)(((double)(active_chainstate.m_chain.Height() - pindex->nHeight)) / (double)nCheckDepth * (nCheckLevel >= 4 ? 50 : 100)))); if (reportDone < percentageDone/10) { // report every 10% step LogPrintf("[%d%%]...", percentageDone); /* Continued */ reportDone = percentageDone/10; } uiInterface.ShowProgress(_("Verifying blocks...").translated, percentageDone, false); - if (pindex->nHeight <= ::ChainActive().Height()-nCheckDepth) + if (pindex->nHeight <= active_chainstate.m_chain.Height()-nCheckDepth) break; if (fPruneMode && !(pindex->nStatus & BLOCK_HAVE_DATA)) { // If pruning, only go back as far as we have data. @@ -4293,9 +4300,9 @@ bool CVerifyDB::VerifyDB(const CChainParams& chainparams, CCoinsView *coinsview, } } // check level 3: check for inconsistencies during memory-only disconnect of tip blocks - if (nCheckLevel >= 3 && (coins.DynamicMemoryUsage() + ::ChainstateActive().CoinsTip().DynamicMemoryUsage()) <= ::ChainstateActive().m_coinstip_cache_size_bytes) { + if (nCheckLevel >= 3 && (coins.DynamicMemoryUsage() + active_chainstate.CoinsTip().DynamicMemoryUsage()) <= active_chainstate.m_coinstip_cache_size_bytes) { assert(coins.GetBestBlock() == pindex->GetBlockHash()); - DisconnectResult res = ::ChainstateActive().DisconnectBlock(block, pindex, coins); + DisconnectResult res = active_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()); } @@ -4309,26 +4316,26 @@ bool CVerifyDB::VerifyDB(const CChainParams& chainparams, CCoinsView *coinsview, if (ShutdownRequested()) return true; } if (pindexFailure) - return error("VerifyDB(): *** coin database inconsistencies found (last %i blocks, %i good transactions before that)\n", ::ChainActive().Height() - pindexFailure->nHeight + 1, nGoodTransactions); + return error("VerifyDB(): *** coin database inconsistencies found (last %i blocks, %i good transactions before that)\n", active_chainstate.m_chain.Height() - pindexFailure->nHeight + 1, nGoodTransactions); // store block count as we move pindex at check level >= 4 - int block_count = ::ChainActive().Height() - pindex->nHeight; + int block_count = active_chainstate.m_chain.Height() - pindex->nHeight; // check level 4: try reconnecting blocks if (nCheckLevel >= 4) { - while (pindex != ::ChainActive().Tip()) { - const int percentageDone = std::max(1, std::min(99, 100 - (int)(((double)(::ChainActive().Height() - pindex->nHeight)) / (double)nCheckDepth * 50))); + while (pindex != active_chainstate.m_chain.Tip()) { + const int percentageDone = std::max(1, std::min(99, 100 - (int)(((double)(active_chainstate.m_chain.Height() - pindex->nHeight)) / (double)nCheckDepth * 50))); if (reportDone < percentageDone/10) { // report every 10% step LogPrintf("[%d%%]...", percentageDone); /* Continued */ reportDone = percentageDone/10; } uiInterface.ShowProgress(_("Verifying blocks...").translated, percentageDone, false); - pindex = ::ChainActive().Next(pindex); + pindex = active_chainstate.m_chain.Next(pindex); CBlock block; if (!ReadBlockFromDisk(block, pindex, chainparams.GetConsensus())) return error("VerifyDB(): *** ReadBlockFromDisk failed at %d, hash=%s", pindex->nHeight, pindex->GetBlockHash().ToString()); - if (!::ChainstateActive().ConnectBlock(block, state, pindex, coins, chainparams)) + if (!active_chainstate.ConnectBlock(block, state, pindex, coins, chainparams)) return error("VerifyDB(): *** found unconnectable block at %d, hash=%s (%s)", pindex->nHeight, pindex->GetBlockHash().ToString(), state.ToString()); if (ShutdownRequested()) return true; } @@ -4599,7 +4606,7 @@ bool ChainstateManager::LoadBlockIndex(const CChainParams& chainparams) // Load block index from databases bool needs_init = fReindex; if (!fReindex) { - bool ret = LoadBlockIndexDB(*this, chainparams); + bool ret = ActiveChainstate().LoadBlockIndexDB(chainparams); if (!ret) return false; needs_init = m_blockman.m_block_index.empty(); } @@ -4627,9 +4634,10 @@ bool CChainState::LoadGenesisBlock(const CChainParams& chainparams) if (m_blockman.m_block_index.count(chainparams.GenesisBlock().GetHash())) return true; + assert(std::addressof(::ChainActive()) == std::addressof(m_chain)); try { const CBlock& block = chainparams.GenesisBlock(); - FlatFilePos blockPos = SaveBlockToDisk(block, 0, chainparams, nullptr); + FlatFilePos blockPos = SaveBlockToDisk(block, 0, m_chain, chainparams, nullptr); if (blockPos.IsNull()) return error("%s: writing genesis block to disk failed", __func__); CBlockIndex *pindex = m_blockman.AddToBlockIndex(block); @@ -4641,11 +4649,6 @@ bool CChainState::LoadGenesisBlock(const CChainParams& chainparams) return true; } -bool LoadGenesisBlock(const CChainParams& chainparams) -{ - return ::ChainstateActive().LoadGenesisBlock(chainparams); -} - void CChainState::LoadExternalBlockFile(const CChainParams& chainparams, FILE* fileIn, FlatFilePos* dbp) { // Map of disk positions for blocks with unknown parent (only used for reindex) @@ -5009,31 +5012,13 @@ CBlockFileInfo* GetBlockFileInfo(size_t n) return &vinfoBlockFile.at(n); } -ThresholdState VersionBitsTipState(const Consensus::Params& params, Consensus::DeploymentPos pos) -{ - LOCK(cs_main); - return VersionBitsState(::ChainActive().Tip(), params, pos, versionbitscache); -} - -BIP9Stats VersionBitsTipStatistics(const Consensus::Params& params, Consensus::DeploymentPos pos) -{ - LOCK(cs_main); - return VersionBitsStatistics(::ChainActive().Tip(), params, pos); -} - -int VersionBitsTipStateSinceHeight(const Consensus::Params& params, Consensus::DeploymentPos pos) -{ - LOCK(cs_main); - return VersionBitsStateSinceHeight(::ChainActive().Tip(), params, pos, versionbitscache); -} - static const uint64_t MEMPOOL_DUMP_VERSION = 1; -bool LoadMempool(CTxMemPool& pool, CChainState& active_chainstate) +bool LoadMempool(CTxMemPool& pool, CChainState& active_chainstate, FopenFn mockable_fopen_function) { const CChainParams& chainparams = Params(); int64_t nExpiryTimeout = gArgs.GetArg("-mempoolexpiry", DEFAULT_MEMPOOL_EXPIRY) * 60 * 60; - FILE* filestr = fsbridge::fopen(GetDataDir() / "mempool.dat", "rb"); + FILE* filestr{mockable_fopen_function(GetDataDir() / "mempool.dat", "rb")}; CAutoFile file(filestr, SER_DISK, CLIENT_VERSION); if (file.IsNull()) { LogPrintf("Failed to open mempool file from disk. Continuing anyway.\n"); @@ -5114,7 +5099,7 @@ bool LoadMempool(CTxMemPool& pool, CChainState& active_chainstate) return true; } -bool DumpMempool(const CTxMemPool& pool) +bool DumpMempool(const CTxMemPool& pool, FopenFn mockable_fopen_function, bool skip_file_commit) { int64_t start = GetTimeMicros(); @@ -5137,7 +5122,7 @@ bool DumpMempool(const CTxMemPool& pool) int64_t mid = GetTimeMicros(); try { - FILE* filestr = fsbridge::fopen(GetDataDir() / "mempool.dat.new", "wb"); + FILE* filestr{mockable_fopen_function(GetDataDir() / "mempool.dat.new", "wb")}; if (!filestr) { return false; } @@ -5160,7 +5145,7 @@ bool DumpMempool(const CTxMemPool& pool) LogPrintf("Writing %d unbroadcast transactions to disk.\n", unbroadcast_txids.size()); file << unbroadcast_txids; - if (!FileCommit(file.Get())) + if (!skip_file_commit && !FileCommit(file.Get())) throw std::runtime_error("FileCommit failed"); file.fclose(); if (!RenameOver(GetDataDir() / "mempool.dat.new", GetDataDir() / "mempool.dat")) { @@ -5194,14 +5179,14 @@ double GuessVerificationProgress(const ChainTxData& data, const CBlockIndex *pin return std::min<double>(pindex->nChainTx / fTxTotal, 1.0); } -Optional<uint256> ChainstateManager::SnapshotBlockhash() const { +std::optional<uint256> ChainstateManager::SnapshotBlockhash() const { LOCK(::cs_main); if (m_active_chainstate != nullptr && !m_active_chainstate->m_from_snapshot_blockhash.IsNull()) { // 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() @@ -5298,7 +5283,7 @@ bool ChainstateManager::ActivateSnapshot( static_cast<size_t>(current_coinsdb_cache_size * IBD_CACHE_PERC)); } - auto snapshot_chainstate = WITH_LOCK(::cs_main, return MakeUnique<CChainState>( + auto snapshot_chainstate = WITH_LOCK(::cs_main, return std::make_unique<CChainState>( this->ActiveChainstate().m_mempool, m_blockman, base_blockhash)); { @@ -5444,7 +5429,7 @@ bool ChainstateManager::PopulateAndValidateSnapshot( // about the snapshot_chainstate. CCoinsViewDB* snapshot_coinsdb = WITH_LOCK(::cs_main, return &snapshot_chainstate.CoinsDB()); - if (!GetUTXOStats(snapshot_coinsdb, stats, CoinStatsHashType::HASH_SERIALIZED, breakpoint_fnc)) { + if (!GetUTXOStats(snapshot_coinsdb, WITH_LOCK(::cs_main, return std::ref(m_blockman)), stats, CoinStatsHashType::HASH_SERIALIZED, breakpoint_fnc)) { LogPrintf("[snapshot] failed to generate coins stats\n"); return false; } diff --git a/src/validation.h b/src/validation.h index 4e4bdbea54..21e63947fa 100644 --- a/src/validation.h +++ b/src/validation.h @@ -17,7 +17,6 @@ #include <crypto/common.h> // for ReadLE64 #include <fs.h> #include <node/utxo_snapshot.h> -#include <optional.h> #include <policy/feerate.h> #include <protocol.h> // For CMessageHeader::MessageStartChars #include <script/script_error.h> @@ -32,6 +31,7 @@ #include <atomic> #include <map> #include <memory> +#include <optional> #include <set> #include <stdint.h> #include <string> @@ -148,8 +148,6 @@ extern const std::vector<std::string> CHECKLEVEL_DOC; FILE* OpenBlockFile(const FlatFilePos &pos, bool fReadOnly = false); /** Translation to a filesystem path */ fs::path GetBlockPosFilename(const FlatFilePos &pos); -/** Ensures we have a genesis block in the block tree, possibly writing one to disk. */ -bool LoadGenesisBlock(const CChainParams& chainparams); /** Unload database information */ void UnloadBlockIndex(CTxMemPool* mempool, ChainstateManager& chainman); /** Run instances of script checking worker threads */ @@ -183,7 +181,7 @@ uint64_t CalculateCurrentUsage(); void UnlinkPrunedFiles(const std::set<int>& setFilesToPrune); /** Prune block files up to a given height */ -void PruneBlockFilesManual(int nManualPruneHeight); +void PruneBlockFilesManual(CChainState& active_chainstate, int nManualPruneHeight); /** * Validation result for a single transaction mempool acceptance. @@ -207,8 +205,7 @@ struct MempoolAcceptResult { /** Constructor for failure case */ explicit MempoolAcceptResult(TxValidationState state) - : m_result_type(ResultType::INVALID), - m_state(state), m_replaced_transactions(nullopt), m_base_fees(nullopt) { + : m_result_type(ResultType::INVALID), m_state(state) { Assume(!state.IsValid()); // Can be invalid or error } @@ -226,15 +223,6 @@ struct MempoolAcceptResult { MempoolAcceptResult AcceptToMemoryPool(CChainState& active_chainstate, CTxMemPool& pool, const CTransactionRef& tx, bool bypass_limits, bool test_accept=false) EXCLUSIVE_LOCKS_REQUIRED(cs_main); -/** Get the BIP9 state for a given deployment at the current tip. */ -ThresholdState VersionBitsTipState(const Consensus::Params& params, Consensus::DeploymentPos pos); - -/** Get the numerical statistics for the BIP9 state for a given deployment at the current tip. */ -BIP9Stats VersionBitsTipStatistics(const Consensus::Params& params, Consensus::DeploymentPos pos); - -/** Get the block height at which the BIP9 deployment switched into the state for the block building on the current tip. */ -int VersionBitsTipStateSinceHeight(const Consensus::Params& params, Consensus::DeploymentPos pos); - /** Apply the effects of this transaction on the UTXO set represented by view */ void UpdateCoins(const CTransaction& tx, CCoinsViewCache& inputs, int nHeight); @@ -349,7 +337,7 @@ class CVerifyDB { public: CVerifyDB(); ~CVerifyDB(); - bool VerifyDB(const CChainParams& chainparams, CCoinsView *coinsview, int nCheckLevel, int nCheckDepth); + bool VerifyDB(const CChainParams& chainparams, CChainState& active_chainstate, CCoinsView *coinsview, int nCheckLevel, int nCheckDepth) EXCLUSIVE_LOCKS_REQUIRED(cs_main); }; enum DisconnectResult @@ -469,7 +457,7 @@ public: const CChainParams& chainparams, CBlockIndex** ppindex) EXCLUSIVE_LOCKS_REQUIRED(cs_main); - CBlockIndex* LookupBlockIndex(const uint256& hash) EXCLUSIVE_LOCKS_REQUIRED(cs_main); + CBlockIndex* LookupBlockIndex(const uint256& hash) const EXCLUSIVE_LOCKS_REQUIRED(cs_main); /** Find the last common block between the parameter chain and a locator. */ CBlockIndex* FindForkInGlobalIndex(const CChain& chain, const CBlockLocator& locator) EXCLUSIVE_LOCKS_REQUIRED(cs_main); @@ -721,13 +709,20 @@ public: bool DisconnectTip(BlockValidationState& state, const CChainParams& chainparams, DisconnectedBlockTransactions* disconnectpool) EXCLUSIVE_LOCKS_REQUIRED(cs_main, m_mempool.cs); // Manual block validity manipulation: + /** Mark a block as precious and reorganize. + * + * May not be called in a validationinterface callback. + */ bool PreciousBlock(BlockValidationState& state, const CChainParams& params, CBlockIndex* pindex) LOCKS_EXCLUDED(cs_main); + /** Mark a block as invalid. */ bool InvalidateBlock(BlockValidationState& state, const CChainParams& chainparams, CBlockIndex* pindex) LOCKS_EXCLUDED(cs_main); + /** Remove invalidity status from a block and its descendants. */ void ResetBlockFailureFlags(CBlockIndex* pindex) EXCLUSIVE_LOCKS_REQUIRED(cs_main); /** Replay blocks that aren't fully applied to the database. */ bool ReplayBlocks(const CChainParams& params); bool RewindBlockIndex(const CChainParams& params) LOCKS_EXCLUDED(cs_main); + /** Ensures we have a genesis block in the block tree, possibly writing one to disk. */ bool LoadGenesisBlock(const CChainParams& chainparams); void PruneBlockIndexCandidates(); @@ -776,21 +771,13 @@ private: //! Mark a block as not having block data void EraseBlockData(CBlockIndex* index) EXCLUSIVE_LOCKS_REQUIRED(cs_main); - friend ChainstateManager; -}; + void CheckForkWarningConditions() EXCLUSIVE_LOCKS_REQUIRED(cs_main); + void InvalidChainFound(CBlockIndex* pindexNew) EXCLUSIVE_LOCKS_REQUIRED(cs_main); -/** Mark a block as precious and reorganize. - * - * May not be called in a - * validationinterface callback. - */ -bool PreciousBlock(BlockValidationState& state, const CChainParams& params, CBlockIndex *pindex) LOCKS_EXCLUDED(cs_main); - -/** Mark a block as invalid. */ -bool InvalidateBlock(BlockValidationState& state, const CChainParams& chainparams, CBlockIndex* pindex) LOCKS_EXCLUDED(cs_main); + bool LoadBlockIndexDB(const CChainParams& chainparams) EXCLUSIVE_LOCKS_REQUIRED(cs_main); -/** Remove invalidity status from a block and its descendants. */ -void ResetBlockFailureFlags(CBlockIndex* pindex) EXCLUSIVE_LOCKS_REQUIRED(cs_main); + friend ChainstateManager; +}; /** * Provides an interface for creating and interacting with one or two @@ -936,7 +923,7 @@ public: bool IsSnapshotActive() const; - Optional<uint256> SnapshotBlockhash() const; + std::optional<uint256> SnapshotBlockhash() const; //! Is there a snapshot in use and has it been fully validated? bool IsSnapshotValidated() const { return m_snapshot_validated; } @@ -1025,11 +1012,13 @@ int32_t ComputeBlockVersion(const CBlockIndex* pindexPrev, const Consensus::Para /** Get block file info entry for one block file */ CBlockFileInfo* GetBlockFileInfo(size_t n); +using FopenFn = std::function<FILE*(const fs::path&, const char*)>; + /** Dump the mempool to disk. */ -bool DumpMempool(const CTxMemPool& pool); +bool DumpMempool(const CTxMemPool& pool, FopenFn mockable_fopen_function = fsbridge::fopen, bool skip_file_commit = false); /** Load the mempool from disk. */ -bool LoadMempool(CTxMemPool& pool, CChainState& active_chainstate); +bool LoadMempool(CTxMemPool& pool, CChainState& active_chainstate, FopenFn mockable_fopen_function = fsbridge::fopen); //! Check whether the block associated with this index entry is pruned or not. inline bool IsBlockPruned(const CBlockIndex* pblockindex) diff --git a/src/versionbits.h b/src/versionbits.h index b02f848b67..6df1db8814 100644 --- a/src/versionbits.h +++ b/src/versionbits.h @@ -79,8 +79,11 @@ struct VersionBitsCache void Clear(); }; +/** Get the BIP9 state for a given deployment at the current tip. */ ThresholdState VersionBitsState(const CBlockIndex* pindexPrev, const Consensus::Params& params, Consensus::DeploymentPos pos, VersionBitsCache& cache); +/** Get the numerical statistics for the BIP9 state for a given deployment at the current tip. */ BIP9Stats VersionBitsStatistics(const CBlockIndex* pindexPrev, const Consensus::Params& params, Consensus::DeploymentPos pos); +/** Get the block height at which the BIP9 deployment switched into the state for the block building on the current tip. */ int VersionBitsStateSinceHeight(const CBlockIndex* pindexPrev, const Consensus::Params& params, Consensus::DeploymentPos pos, VersionBitsCache& cache); uint32_t VersionBitsMask(const Consensus::Params& params, Consensus::DeploymentPos pos); diff --git a/src/wallet/bdb.cpp b/src/wallet/bdb.cpp index ad40e6da9a..1dc23374e3 100644 --- a/src/wallet/bdb.cpp +++ b/src/wallet/bdb.cpp @@ -331,7 +331,7 @@ void BerkeleyDatabase::Open() if (m_db == nullptr) { int ret; - std::unique_ptr<Db> pdb_temp = MakeUnique<Db>(env->dbenv.get(), 0); + std::unique_ptr<Db> pdb_temp = std::make_unique<Db>(env->dbenv.get(), 0); bool fMockDb = env->IsMock(); if (fMockDb) { @@ -462,7 +462,7 @@ bool BerkeleyDatabase::Rewrite(const char* pszSkip) std::string strFileRes = strFile + ".rewrite"; { // surround usage of db with extra {} BerkeleyBatch db(*this, true); - std::unique_ptr<Db> pdbCopy = MakeUnique<Db>(env->dbenv.get(), 0); + std::unique_ptr<Db> pdbCopy = std::make_unique<Db>(env->dbenv.get(), 0); int ret = pdbCopy->open(nullptr, // Txn pointer strFileRes.c_str(), // Filename @@ -819,7 +819,7 @@ void BerkeleyDatabase::RemoveRef() std::unique_ptr<DatabaseBatch> BerkeleyDatabase::MakeBatch(bool flush_on_close) { - return MakeUnique<BerkeleyBatch>(*this, false, flush_on_close); + return std::make_unique<BerkeleyBatch>(*this, false, flush_on_close); } std::unique_ptr<BerkeleyDatabase> MakeBerkeleyDatabase(const fs::path& path, const DatabaseOptions& options, DatabaseStatus& status, bilingual_str& error) @@ -835,7 +835,7 @@ std::unique_ptr<BerkeleyDatabase> MakeBerkeleyDatabase(const fs::path& path, con status = DatabaseStatus::FAILED_ALREADY_LOADED; return nullptr; } - db = MakeUnique<BerkeleyDatabase>(std::move(env), std::move(data_filename)); + db = std::make_unique<BerkeleyDatabase>(std::move(env), std::move(data_filename)); } if (options.verify && !db->Verify(error)) { diff --git a/src/wallet/coincontrol.h b/src/wallet/coincontrol.h index c499b0ff25..d25a3fb3fa 100644 --- a/src/wallet/coincontrol.h +++ b/src/wallet/coincontrol.h @@ -5,13 +5,14 @@ #ifndef BITCOIN_WALLET_COINCONTROL_H #define BITCOIN_WALLET_COINCONTROL_H -#include <optional.h> #include <outputtype.h> #include <policy/feerate.h> #include <policy/fees.h> #include <primitives/transaction.h> #include <script/standard.h> +#include <optional> + const int DEFAULT_MIN_DEPTH = 0; const int DEFAULT_MAX_DEPTH = 9999999; @@ -25,7 +26,7 @@ public: //! Custom change destination, if not set an address is generated CTxDestination destChange; //! Override the default change type if set, ignored if destChange is set - Optional<OutputType> m_change_type; + std::optional<OutputType> m_change_type; //! If false, only selected inputs are used bool m_add_inputs; //! If false, allows unselected inputs, but requires all selected inputs be used @@ -35,11 +36,11 @@ public: //! Override automatic min/max checks on fee, m_feerate must be set if true bool fOverrideFeeRate; //! Override the wallet's m_pay_tx_fee if set - Optional<CFeeRate> m_feerate; + std::optional<CFeeRate> m_feerate; //! Override the default confirmation target if set - Optional<unsigned int> m_confirm_target; + std::optional<unsigned int> m_confirm_target; //! Override the wallet's m_signal_rbf if set - Optional<bool> m_signal_bip125_rbf; + std::optional<bool> m_signal_bip125_rbf; //! Avoid partial use of funds sent to a given address bool m_avoid_partial_spends; //! Forbids inclusion of dirty (previously used) addresses diff --git a/src/wallet/coinselection.cpp b/src/wallet/coinselection.cpp index 10f89e3a6f..5a18308a73 100644 --- a/src/wallet/coinselection.cpp +++ b/src/wallet/coinselection.cpp @@ -4,11 +4,12 @@ #include <wallet/coinselection.h> -#include <optional.h> #include <policy/feerate.h> #include <util/system.h> #include <util/moneystr.h> +#include <optional> + // Descending order comparator struct { bool operator()(const OutputGroup& a, const OutputGroup& b) const @@ -222,7 +223,7 @@ bool KnapsackSolver(const CAmount& nTargetValue, std::vector<OutputGroup>& group nValueRet = 0; // List of values less than target - Optional<OutputGroup> lowest_larger; + std::optional<OutputGroup> lowest_larger; std::vector<OutputGroup> applicable_groups; CAmount nTotalLower = 0; diff --git a/src/wallet/db.h b/src/wallet/db.h index 2c75486a44..7a0d3d2e07 100644 --- a/src/wallet/db.h +++ b/src/wallet/db.h @@ -8,13 +8,12 @@ #include <clientversion.h> #include <fs.h> -#include <optional.h> #include <streams.h> #include <support/allocators/secure.h> -#include <util/memory.h> #include <atomic> #include <memory> +#include <optional> #include <string> struct bilingual_str; @@ -193,7 +192,7 @@ public: void ReloadDbEnv() override {} std::string Filename() override { return "dummy"; } std::string Format() override { return "dummy"; } - std::unique_ptr<DatabaseBatch> MakeBatch(bool flush_on_close = true) override { return MakeUnique<DummyBatch>(); } + std::unique_ptr<DatabaseBatch> MakeBatch(bool flush_on_close = true) override { return std::make_unique<DummyBatch>(); } }; enum class DatabaseFormat { @@ -204,7 +203,7 @@ enum class DatabaseFormat { struct DatabaseOptions { bool require_existing = false; bool require_create = false; - Optional<DatabaseFormat> require_format; + std::optional<DatabaseFormat> require_format; uint64_t create_flags = 0; SecureString create_passphrase; bool verify = true; diff --git a/src/wallet/feebumper.cpp b/src/wallet/feebumper.cpp index 5e319d4f95..08adf09df4 100644 --- a/src/wallet/feebumper.cpp +++ b/src/wallet/feebumper.cpp @@ -190,7 +190,7 @@ Result CreateRateBumpTransaction(CWallet& wallet, const uint256& txid, const CCo if (coin_control.m_feerate) { // The user provided a feeRate argument. // We calculate this here to avoid compiler warning on the cs_wallet lock - const int64_t maxTxSize = CalculateMaximumSignedTxSize(*wtx.tx, &wallet); + const int64_t maxTxSize = CalculateMaximumSignedTxSize(*wtx.tx, &wallet).first; Result res = CheckFeeRate(wallet, wtx, *new_coin_control.m_feerate, maxTxSize, errors); if (res != Result::OK) { return res; diff --git a/src/wallet/init.cpp b/src/wallet/init.cpp index f3e24384df..fdeead1fa5 100644 --- a/src/wallet/init.cpp +++ b/src/wallet/init.cpp @@ -70,7 +70,7 @@ void WalletInit::AddWalletOptions(ArgsManager& argsman) const argsman.AddArg("-walletbroadcast", strprintf("Make the wallet broadcast transactions (default: %u)", DEFAULT_WALLETBROADCAST), ArgsManager::ALLOW_ANY, OptionsCategory::WALLET); argsman.AddArg("-walletdir=<dir>", "Specify directory to hold wallets (default: <datadir>/wallets if it exists, otherwise <datadir>)", ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::WALLET); #if HAVE_SYSTEM - argsman.AddArg("-walletnotify=<cmd>", "Execute command when a wallet transaction changes. %s in cmd is replaced by TxID and %w is replaced by wallet name. %w is not currently implemented on windows. On systems where %w is supported, it should NOT be quoted because this would break shell escaping used to invoke the command.", ArgsManager::ALLOW_ANY, OptionsCategory::WALLET); + argsman.AddArg("-walletnotify=<cmd>", "Execute command when a wallet transaction changes. %s in cmd is replaced by TxID, %w is replaced by wallet name, %b is replaced by the hash of the block including the transaction (set to 'unconfirmed' if the transaction is not included) and %h is replaced by the block height (-1 if not included). %w is not currently implemented on windows. On systems where %w is supported, it should NOT be quoted because this would break shell escaping used to invoke the command.", ArgsManager::ALLOW_ANY, OptionsCategory::WALLET); #endif argsman.AddArg("-walletrbf", strprintf("Send transactions with full-RBF opt-in enabled (RPC only, default: %u)", DEFAULT_WALLET_RBF), ArgsManager::ALLOW_ANY, OptionsCategory::WALLET); diff --git a/src/wallet/interfaces.cpp b/src/wallet/interfaces.cpp index 1fb789b128..da5b84ce83 100644 --- a/src/wallet/interfaces.cpp +++ b/src/wallet/interfaces.cpp @@ -15,7 +15,6 @@ #include <sync.h> #include <uint256.h> #include <util/check.h> -#include <util/ref.h> #include <util/system.h> #include <util/ui_change_type.h> #include <wallet/context.h> @@ -515,7 +514,7 @@ public: { for (const CRPCCommand& command : GetWalletRPCCommands()) { m_rpc_commands.emplace_back(command.category, command.name, [this, &command](const JSONRPCRequest& request, UniValue& result, bool last_handler) { - return command.actor({request, m_context}, result, last_handler); + return command.actor({request, &m_context}, result, last_handler); }, command.argNames, command.unique_id); m_rpc_handlers.emplace_back(m_context.chain->handleRpc(m_rpc_commands.back())); } @@ -588,10 +587,10 @@ public: } // namespace wallet namespace interfaces { -std::unique_ptr<Wallet> MakeWallet(const std::shared_ptr<CWallet>& wallet) { return wallet ? MakeUnique<wallet::WalletImpl>(wallet) : nullptr; } +std::unique_ptr<Wallet> MakeWallet(const std::shared_ptr<CWallet>& wallet) { return wallet ? std::make_unique<wallet::WalletImpl>(wallet) : nullptr; } std::unique_ptr<WalletClient> MakeWalletClient(Chain& chain, ArgsManager& args) { - return MakeUnique<wallet::WalletClientImpl>(chain, args); + return std::make_unique<wallet::WalletClientImpl>(chain, args); } } // namespace interfaces diff --git a/src/wallet/load.cpp b/src/wallet/load.cpp index 30832f983b..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; @@ -154,7 +154,7 @@ void UnloadWallets() auto wallet = wallets.back(); wallets.pop_back(); std::vector<bilingual_str> warnings; - RemoveWallet(wallet, nullopt, warnings); + RemoveWallet(wallet, std::nullopt, warnings); UnloadWallet(std::move(wallet)); } } diff --git a/src/wallet/rpcdump.cpp b/src/wallet/rpcdump.cpp index 99803a91d2..0737e68cf3 100644 --- a/src/wallet/rpcdump.cpp +++ b/src/wallet/rpcdump.cpp @@ -56,13 +56,13 @@ static std::string DecodeDumpString(const std::string &str) { return ret.str(); } -static bool GetWalletAddressesForKey(LegacyScriptPubKeyMan* spk_man, const CWallet* const pwallet, const CKeyID& keyid, std::string& strAddr, std::string& strLabel) EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet) +static bool GetWalletAddressesForKey(LegacyScriptPubKeyMan* spk_man, const CWallet& wallet, const CKeyID& keyid, std::string& strAddr, std::string& strLabel) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet) { bool fLabelFound = false; CKey key; spk_man->GetKey(keyid, key); for (const auto& dest : GetAllDestinationsForKey(key.GetPubKey())) { - const auto* address_book_entry = pwallet->FindAddressBookEntry(dest); + const auto* address_book_entry = wallet.FindAddressBookEntry(dest); if (address_book_entry) { if (!strAddr.empty()) { strAddr += ","; @@ -73,7 +73,7 @@ static bool GetWalletAddressesForKey(LegacyScriptPubKeyMan* spk_man, const CWall } } if (!fLabelFound) { - strAddr = EncodeDestination(GetDestinationForKey(key.GetPubKey(), pwallet->m_default_address_type)); + strAddr = EncodeDestination(GetDestinationForKey(key.GetPubKey(), wallet.m_default_address_type)); } return fLabelFound; } @@ -118,22 +118,21 @@ RPCHelpMan importprivkey() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); - if (!wallet) return NullUniValue; - CWallet* const pwallet = wallet.get(); + std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); + if (!pwallet) return NullUniValue; if (pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { throw JSONRPCError(RPC_WALLET_ERROR, "Cannot import private keys to a wallet with private keys disabled"); } - EnsureLegacyScriptPubKeyMan(*wallet, true); + EnsureLegacyScriptPubKeyMan(*pwallet, true); WalletRescanReserver reserver(*pwallet); bool fRescan = true; { LOCK(pwallet->cs_wallet); - EnsureWalletIsUnlocked(pwallet); + EnsureWalletIsUnlocked(*pwallet); std::string strSecret = request.params[0].get_str(); std::string strLabel = ""; @@ -210,9 +209,8 @@ RPCHelpMan abortrescan() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); - if (!wallet) return NullUniValue; - CWallet* const pwallet = wallet.get(); + std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); + if (!pwallet) return NullUniValue; if (!pwallet->IsScanning() || pwallet->IsAbortingRescan()) return false; pwallet->AbortRescan(); @@ -249,9 +247,8 @@ RPCHelpMan importaddress() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); - if (!wallet) return NullUniValue; - CWallet* const pwallet = wallet.get(); + std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); + if (!pwallet) return NullUniValue; EnsureLegacyScriptPubKeyMan(*pwallet, true); @@ -335,9 +332,8 @@ RPCHelpMan importprunedfunds() RPCExamples{""}, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); - if (!wallet) return NullUniValue; - CWallet* const pwallet = wallet.get(); + std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); + if (!pwallet) return NullUniValue; CMutableTransaction tx; if (!DecodeHexTx(tx, request.params[0].get_str())) { @@ -397,9 +393,8 @@ RPCHelpMan removeprunedfunds() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); - if (!wallet) return NullUniValue; - CWallet* const pwallet = wallet.get(); + std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); + if (!pwallet) return NullUniValue; LOCK(pwallet->cs_wallet); @@ -445,11 +440,10 @@ RPCHelpMan importpubkey() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); - if (!wallet) return NullUniValue; - CWallet* const pwallet = wallet.get(); + std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); + if (!pwallet) return NullUniValue; - EnsureLegacyScriptPubKeyMan(*wallet, true); + EnsureLegacyScriptPubKeyMan(*pwallet, true); std::string strLabel; if (!request.params[1].isNull()) @@ -527,11 +521,10 @@ RPCHelpMan importwallet() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); - if (!wallet) return NullUniValue; - CWallet* const pwallet = wallet.get(); + std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); + if (!pwallet) return NullUniValue; - EnsureLegacyScriptPubKeyMan(*wallet, true); + EnsureLegacyScriptPubKeyMan(*pwallet, true); if (pwallet->chain().havePruned()) { // Exit early and print an error. @@ -550,7 +543,7 @@ RPCHelpMan importwallet() { LOCK(pwallet->cs_wallet); - EnsureWalletIsUnlocked(pwallet); + EnsureWalletIsUnlocked(*pwallet); fsbridge::ifstream file; file.open(request.params[0].get_str(), std::ios::in | std::ios::ate); @@ -684,15 +677,14 @@ RPCHelpMan dumpprivkey() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); - if (!wallet) return NullUniValue; - const CWallet* const pwallet = wallet.get(); + std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); + if (!pwallet) return NullUniValue; - LegacyScriptPubKeyMan& spk_man = EnsureLegacyScriptPubKeyMan(*wallet); + LegacyScriptPubKeyMan& spk_man = EnsureLegacyScriptPubKeyMan(*pwallet); LOCK2(pwallet->cs_wallet, spk_man.cs_KeyStore); - EnsureWalletIsUnlocked(pwallet); + EnsureWalletIsUnlocked(*pwallet); std::string strAddress = request.params[0].get_str(); CTxDestination dest = DecodeDestination(strAddress); @@ -747,7 +739,7 @@ RPCHelpMan dumpwallet() LOCK2(wallet.cs_wallet, spk_man.cs_KeyStore); - EnsureWalletIsUnlocked(&wallet); + EnsureWalletIsUnlocked(wallet); fs::path filepath = request.params[0].get_str(); filepath = fs::absolute(filepath); @@ -809,7 +801,7 @@ RPCHelpMan dumpwallet() CKey key; if (spk_man.GetKey(keyid, key)) { file << strprintf("%s %s ", EncodeSecret(key), strTime); - if (GetWalletAddressesForKey(&spk_man, &wallet, keyid, strAddr, strLabel)) { + if (GetWalletAddressesForKey(&spk_man, wallet, keyid, strAddr, strLabel)) { file << strprintf("label=%s", strLabel); } else if (keyid == seed_id) { file << "hdseed=1"; @@ -990,14 +982,14 @@ static UniValue ProcessImportLegacy(ImportData& import_data, std::map<CKeyID, CP throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid redeem script \"" + strRedeemScript + "\": must be hex string"); } auto parsed_redeemscript = ParseHex(strRedeemScript); - import_data.redeemscript = MakeUnique<CScript>(parsed_redeemscript.begin(), parsed_redeemscript.end()); + import_data.redeemscript = std::make_unique<CScript>(parsed_redeemscript.begin(), parsed_redeemscript.end()); } if (witness_script_hex.size()) { if (!IsHex(witness_script_hex)) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid witness script \"" + witness_script_hex + "\": must be hex string"); } auto parsed_witnessscript = ParseHex(witness_script_hex); - import_data.witnessscript = MakeUnique<CScript>(parsed_witnessscript.begin(), parsed_witnessscript.end()); + import_data.witnessscript = std::make_unique<CScript>(parsed_witnessscript.begin(), parsed_witnessscript.end()); } for (size_t i = 0; i < pubKeys.size(); ++i) { const auto& str = pubKeys[i].get_str(); @@ -1169,7 +1161,7 @@ static UniValue ProcessImportDescriptor(ImportData& import_data, std::map<CKeyID return warnings; } -static UniValue ProcessImport(CWallet * const pwallet, const UniValue& data, const int64_t timestamp) EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet) +static UniValue ProcessImport(CWallet& wallet, const UniValue& data, const int64_t timestamp) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet) { UniValue warnings(UniValue::VARR); UniValue result(UniValue::VOBJ); @@ -1184,7 +1176,7 @@ static UniValue ProcessImport(CWallet * const pwallet, const UniValue& data, con const bool add_keypool = data.exists("keypool") ? data["keypool"].get_bool() : false; // Add to keypool only works with privkeys disabled - if (add_keypool && !pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { + if (add_keypool && !wallet.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Keys can only be imported to the keypool when private keys are disabled"); } @@ -1206,29 +1198,29 @@ static UniValue ProcessImport(CWallet * const pwallet, const UniValue& data, con } // If private keys are disabled, abort if private keys are being imported - if (pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) && !privkey_map.empty()) { + if (wallet.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) && !privkey_map.empty()) { throw JSONRPCError(RPC_WALLET_ERROR, "Cannot import private keys to a wallet with private keys disabled"); } // Check whether we have any work to do for (const CScript& script : script_pub_keys) { - if (pwallet->IsMine(script) & ISMINE_SPENDABLE) { + if (wallet.IsMine(script) & ISMINE_SPENDABLE) { throw JSONRPCError(RPC_WALLET_ERROR, "The wallet already contains the private key for this address or script (\"" + HexStr(script) + "\")"); } } // All good, time to import - pwallet->MarkDirty(); - if (!pwallet->ImportScripts(import_data.import_scripts, timestamp)) { + wallet.MarkDirty(); + if (!wallet.ImportScripts(import_data.import_scripts, timestamp)) { throw JSONRPCError(RPC_WALLET_ERROR, "Error adding script to wallet"); } - if (!pwallet->ImportPrivKeys(privkey_map, timestamp)) { + if (!wallet.ImportPrivKeys(privkey_map, timestamp)) { throw JSONRPCError(RPC_WALLET_ERROR, "Error adding key to wallet"); } - if (!pwallet->ImportPubKeys(ordered_pubkeys, pubkey_map, import_data.key_origins, add_keypool, internal, timestamp)) { + if (!wallet.ImportPubKeys(ordered_pubkeys, pubkey_map, import_data.key_origins, add_keypool, internal, timestamp)) { throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet"); } - if (!pwallet->ImportScriptPubKeys(label, script_pub_keys, have_solving_data, !internal, timestamp)) { + if (!wallet.ImportScriptPubKeys(label, script_pub_keys, have_solving_data, !internal, timestamp)) { throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet"); } @@ -1336,13 +1328,12 @@ RPCHelpMan importmulti() }, [&](const RPCHelpMan& self, const JSONRPCRequest& mainRequest) -> UniValue { - std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(mainRequest); - if (!wallet) return NullUniValue; - CWallet* const pwallet = wallet.get(); + std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(mainRequest); + if (!pwallet) return NullUniValue; RPCTypeCheck(mainRequest.params, {UniValue::VARR, UniValue::VOBJ}); - EnsureLegacyScriptPubKeyMan(*wallet, true); + EnsureLegacyScriptPubKeyMan(*pwallet, true); const UniValue& requests = mainRequest.params[0]; @@ -1368,7 +1359,7 @@ RPCHelpMan importmulti() UniValue response(UniValue::VARR); { LOCK(pwallet->cs_wallet); - EnsureWalletIsUnlocked(pwallet); + EnsureWalletIsUnlocked(*pwallet); // Verify all timestamps are present before importing any keys. CHECK_NONFATAL(pwallet->chain().findBlock(pwallet->GetLastBlockHash(), FoundBlock().time(nLowestTimestamp).mtpTime(now))); @@ -1380,7 +1371,7 @@ RPCHelpMan importmulti() for (const UniValue& data : requests.getValues()) { const int64_t timestamp = std::max(GetImportTimestamp(data, now), minimumTimestamp); - const UniValue result = ProcessImport(pwallet, data, timestamp); + const UniValue result = ProcessImport(*pwallet, data, timestamp); response.push_back(result); if (!fRescan) { @@ -1447,7 +1438,7 @@ RPCHelpMan importmulti() }; } -static UniValue ProcessDescriptorImport(CWallet * const pwallet, const UniValue& data, const int64_t timestamp) EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet) +static UniValue ProcessDescriptorImport(CWallet& wallet, const UniValue& data, const int64_t timestamp) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet) { UniValue warnings(UniValue::VARR); UniValue result(UniValue::VOBJ); @@ -1516,7 +1507,7 @@ static UniValue ProcessDescriptorImport(CWallet * const pwallet, const UniValue& } // If the wallet disabled private keys, abort if private keys exist - if (pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) && !keys.keys.empty()) { + if (wallet.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) && !keys.keys.empty()) { throw JSONRPCError(RPC_WALLET_ERROR, "Cannot import private keys to a wallet with private keys disabled"); } @@ -1540,7 +1531,7 @@ static UniValue ProcessDescriptorImport(CWallet * const pwallet, const UniValue& } // If private keys are enabled, check some things. - if (!pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { + if (!wallet.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { if (keys.keys.empty()) { throw JSONRPCError(RPC_WALLET_ERROR, "Cannot import descriptor without private keys to a wallet with private keys enabled"); } @@ -1552,7 +1543,7 @@ static UniValue ProcessDescriptorImport(CWallet * const pwallet, const UniValue& WalletDescriptor w_desc(std::move(parsed_desc), timestamp, range_start, range_end, next_index); // Check if the wallet already contains the descriptor - auto existing_spk_manager = pwallet->GetDescriptorScriptPubKeyMan(w_desc); + auto existing_spk_manager = wallet.GetDescriptorScriptPubKeyMan(w_desc); if (existing_spk_manager) { LOCK(existing_spk_manager->cs_desc_man); if (range_start > existing_spk_manager->GetWalletDescriptor().range_start) { @@ -1561,7 +1552,7 @@ static UniValue ProcessDescriptorImport(CWallet * const pwallet, const UniValue& } // Add descriptor to the wallet - auto spk_manager = pwallet->AddWalletDescriptor(w_desc, keys, label, internal); + auto spk_manager = wallet.AddWalletDescriptor(w_desc, keys, label, internal); if (spk_manager == nullptr) { throw JSONRPCError(RPC_WALLET_ERROR, strprintf("Could not add descriptor '%s'", descriptor)); } @@ -1571,7 +1562,7 @@ static UniValue ProcessDescriptorImport(CWallet * const pwallet, const UniValue& if (!w_desc.descriptor->GetOutputType()) { warnings.push_back("Unknown output type, cannot set descriptor to active."); } else { - pwallet->AddActiveScriptPubKeyMan(spk_manager->GetID(), *w_desc.descriptor->GetOutputType(), internal); + wallet.AddActiveScriptPubKeyMan(spk_manager->GetID(), *w_desc.descriptor->GetOutputType(), internal); } } @@ -1641,9 +1632,8 @@ RPCHelpMan importdescriptors() }, [&](const RPCHelpMan& self, const JSONRPCRequest& main_request) -> UniValue { - std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(main_request); - if (!wallet) return NullUniValue; - CWallet* const pwallet = wallet.get(); + std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(main_request); + if (!pwallet) return NullUniValue; // Make sure wallet is a descriptor wallet if (!pwallet->IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS)) { @@ -1665,7 +1655,7 @@ RPCHelpMan importdescriptors() UniValue response(UniValue::VARR); { LOCK(pwallet->cs_wallet); - EnsureWalletIsUnlocked(pwallet); + EnsureWalletIsUnlocked(*pwallet); CHECK_NONFATAL(pwallet->chain().findBlock(pwallet->GetLastBlockHash(), FoundBlock().time(lowest_timestamp).mtpTime(now))); @@ -1673,7 +1663,7 @@ RPCHelpMan importdescriptors() for (const UniValue& request : requests.getValues()) { // This throws an error if "timestamp" doesn't exist const int64_t timestamp = std::max(GetImportTimestamp(request, now), minimum_timestamp); - const UniValue result = ProcessDescriptorImport(pwallet, request, timestamp); + const UniValue result = ProcessDescriptorImport(*pwallet, request, timestamp); response.push_back(result); if (lowest_timestamp > timestamp ) { @@ -1775,6 +1765,8 @@ RPCHelpMan listdescriptors() throw JSONRPCError(RPC_WALLET_ERROR, "listdescriptors is not available for non-descriptor wallets"); } + EnsureWalletIsUnlocked(*wallet); + LOCK(wallet->cs_wallet); UniValue response(UniValue::VARR); @@ -1787,12 +1779,16 @@ RPCHelpMan listdescriptors() UniValue spk(UniValue::VOBJ); LOCK(desc_spk_man->cs_desc_man); const auto& wallet_descriptor = desc_spk_man->GetWalletDescriptor(); - spk.pushKV("desc", wallet_descriptor.descriptor->ToString()); + std::string descriptor; + if (!desc_spk_man->GetDescriptorString(descriptor, false)) { + throw JSONRPCError(RPC_WALLET_ERROR, "Can't get normalized descriptor string."); + } + spk.pushKV("desc", descriptor); spk.pushKV("timestamp", wallet_descriptor.creation_time); const bool active = active_spk_mans.count(desc_spk_man) != 0; spk.pushKV("active", active); const auto& type = wallet_descriptor.descriptor->GetOutputType(); - if (active && type != nullopt) { + if (active && type) { spk.pushKV("internal", wallet->GetScriptPubKeyMan(*type, true) == desc_spk_man); } if (wallet_descriptor.descriptor->IsRange()) { diff --git a/src/wallet/rpcsigner.cpp b/src/wallet/rpcsigner.cpp index 607b778c68..696c74d665 100644 --- a/src/wallet/rpcsigner.cpp +++ b/src/wallet/rpcsigner.cpp @@ -32,9 +32,6 @@ static RPCHelpMan enumeratesigners() }, RPCExamples{""}, [](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); - if (!wallet) return NullUniValue; - const std::string command = gArgs.GetArg("-signer", ""); if (command == "") throw JSONRPCError(RPC_WALLET_ERROR, "Error: restart bitcoind with -signer=<cmd>"); std::string chain = gArgs.GetChainName(); diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index bfc42ac1b0..df26f039a0 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -8,7 +8,6 @@ #include <interfaces/chain.h> #include <key_io.h> #include <node/context.h> -#include <optional.h> #include <outputtype.h> #include <policy/feerate.h> #include <policy/fees.h> @@ -23,7 +22,6 @@ #include <util/fees.h> #include <util/message.h> // For MessageSign() #include <util/moneystr.h> -#include <util/ref.h> #include <util/string.h> #include <util/system.h> #include <util/translation.h> @@ -38,6 +36,7 @@ #include <wallet/walletdb.h> #include <wallet/walletutil.h> +#include <optional> #include <stdint.h> #include <univalue.h> @@ -48,8 +47,8 @@ using interfaces::FoundBlock; static const std::string WALLET_ENDPOINT_BASE = "/wallet/"; static const std::string HELP_REQUIRING_PASSPHRASE{"\nRequires wallet passphrase to be set with walletpassphrase call if wallet is encrypted.\n"}; -static inline bool GetAvoidReuseFlag(const CWallet* const pwallet, const UniValue& param) { - bool can_avoid_reuse = pwallet->IsWalletFlagSet(WALLET_FLAG_AVOID_REUSE); +static inline bool GetAvoidReuseFlag(const CWallet& wallet, const UniValue& param) { + bool can_avoid_reuse = wallet.IsWalletFlagSet(WALLET_FLAG_AVOID_REUSE); bool avoid_reuse = param.isNull() ? can_avoid_reuse : param.get_bool(); if (avoid_reuse && !can_avoid_reuse) { @@ -64,11 +63,11 @@ static inline bool GetAvoidReuseFlag(const CWallet* const pwallet, const UniValu * We default to true for watchonly wallets if include_watchonly isn't * explicitly set. */ -static bool ParseIncludeWatchonly(const UniValue& include_watchonly, const CWallet& pwallet) +static bool ParseIncludeWatchonly(const UniValue& include_watchonly, const CWallet& wallet) { if (include_watchonly.isNull()) { // if include_watchonly isn't explicitly set, then check if we have a watchonly wallet - return pwallet.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS); + return wallet.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS); } // otherwise return whatever include_watchonly was set to @@ -96,7 +95,7 @@ bool GetWalletNameFromJSONRPCRequest(const JSONRPCRequest& request, std::string& std::shared_ptr<CWallet> GetWalletForJSONRPCRequest(const JSONRPCRequest& request) { - CHECK_NONFATAL(!request.fHelp); + CHECK_NONFATAL(request.mode == JSONRPCRequest::EXECUTE); std::string wallet_name; if (GetWalletNameFromJSONRPCRequest(request, wallet_name)) { std::shared_ptr<CWallet> pwallet = GetWallet(wallet_name); @@ -117,19 +116,20 @@ std::shared_ptr<CWallet> GetWalletForJSONRPCRequest(const JSONRPCRequest& reques "Wallet file not specified (must request wallet RPC through /wallet/<filename> uri-path)."); } -void EnsureWalletIsUnlocked(const CWallet* pwallet) +void EnsureWalletIsUnlocked(const CWallet& wallet) { - if (pwallet->IsLocked()) { + if (wallet.IsLocked()) { throw JSONRPCError(RPC_WALLET_UNLOCK_NEEDED, "Error: Please enter the wallet passphrase with walletpassphrase first."); } } -WalletContext& EnsureWalletContext(const util::Ref& context) +WalletContext& EnsureWalletContext(const std::any& context) { - if (!context.Has<WalletContext>()) { + auto wallet_context = util::AnyPtr<WalletContext>(context); + if (!wallet_context) { throw JSONRPCError(RPC_INTERNAL_ERROR, "Wallet context not found"); } - return context.Get<WalletContext>(); + return *wallet_context; } // also_create should only be set to true only when the RPC is expected to add things to a blank wallet and make it no longer blank @@ -219,7 +219,7 @@ static void SetFeeEstimateMode(const CWallet& wallet, CCoinControl& cc, const Un cc.m_feerate = CFeeRate(AmountFromValue(fee_rate), COIN); if (override_min_fee) cc.fOverrideFeeRate = true; // Default RBF to true for explicit fee_rate, if unset. - if (cc.m_signal_bip125_rbf == nullopt) cc.m_signal_bip125_rbf = true; + if (!cc.m_signal_bip125_rbf) cc.m_signal_bip125_rbf = true; return; } if (!estimate_mode.isNull() && !FeeModeFromString(estimate_mode.get_str(), cc.m_fee_mode)) { @@ -249,9 +249,8 @@ static RPCHelpMan getnewaddress() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); - if (!wallet) return NullUniValue; - CWallet* const pwallet = wallet.get(); + std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); + if (!pwallet) return NullUniValue; LOCK(pwallet->cs_wallet); @@ -299,9 +298,8 @@ static RPCHelpMan getrawchangeaddress() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); - if (!wallet) return NullUniValue; - CWallet* const pwallet = wallet.get(); + std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); + if (!pwallet) return NullUniValue; LOCK(pwallet->cs_wallet); @@ -342,9 +340,8 @@ static RPCHelpMan setlabel() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); - if (!wallet) return NullUniValue; - CWallet* const pwallet = wallet.get(); + std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); + if (!pwallet) return NullUniValue; LOCK(pwallet->cs_wallet); @@ -396,13 +393,13 @@ void ParseRecipients(const UniValue& address_amounts, const UniValue& subtract_f } } -UniValue SendMoney(CWallet* const pwallet, const CCoinControl &coin_control, std::vector<CRecipient> &recipients, mapValue_t map_value, bool verbose) +UniValue SendMoney(CWallet& wallet, const CCoinControl &coin_control, std::vector<CRecipient> &recipients, mapValue_t map_value, bool verbose) { - EnsureWalletIsUnlocked(pwallet); + EnsureWalletIsUnlocked(wallet); // This function is only used by sendtoaddress and sendmany. // This should always try to sign, if we don't have private keys, don't try to do anything here. - if (pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { + if (wallet.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { throw JSONRPCError(RPC_WALLET_ERROR, "Error: Private keys are disabled for this wallet"); } @@ -415,11 +412,11 @@ UniValue SendMoney(CWallet* const pwallet, const CCoinControl &coin_control, std bilingual_str error; CTransactionRef tx; FeeCalculation fee_calc_out; - const bool fCreated = pwallet->CreateTransaction(recipients, tx, nFeeRequired, nChangePosRet, error, coin_control, fee_calc_out, true); + const bool fCreated = wallet.CreateTransaction(recipients, tx, nFeeRequired, nChangePosRet, error, coin_control, fee_calc_out, true); if (!fCreated) { throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, error.original); } - pwallet->CommitTransaction(tx, std::move(map_value), {} /* orderForm */); + wallet.CommitTransaction(tx, std::move(map_value), {} /* orderForm */); if (verbose) { UniValue entry(UniValue::VOBJ); entry.pushKV("txid", tx->GetHash().GetHex()); @@ -480,9 +477,8 @@ static RPCHelpMan sendtoaddress() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); - if (!wallet) return NullUniValue; - CWallet* const pwallet = wallet.get(); + std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); + if (!pwallet) return NullUniValue; // Make sure the results are valid at least up to the most recent block // the user could have gotten from another RPC command prior to now @@ -507,13 +503,13 @@ static RPCHelpMan sendtoaddress() coin_control.m_signal_bip125_rbf = request.params[5].get_bool(); } - coin_control.m_avoid_address_reuse = GetAvoidReuseFlag(pwallet, request.params[8]); + coin_control.m_avoid_address_reuse = GetAvoidReuseFlag(*pwallet, request.params[8]); // We also enable partial spend avoidance if reuse avoidance is set. coin_control.m_avoid_partial_spends |= coin_control.m_avoid_address_reuse; SetFeeEstimateMode(*pwallet, coin_control, /* conf_target */ request.params[6], /* estimate_mode */ request.params[7], /* fee_rate */ request.params[9], /* override_min_fee */ false); - EnsureWalletIsUnlocked(pwallet); + EnsureWalletIsUnlocked(*pwallet); UniValue address_amounts(UniValue::VOBJ); const std::string address = request.params[0].get_str(); @@ -527,7 +523,7 @@ static RPCHelpMan sendtoaddress() ParseRecipients(address_amounts, subtractFeeFromAmount, recipients); const bool verbose{request.params[10].isNull() ? false : request.params[10].get_bool()}; - return SendMoney(pwallet, coin_control, recipients, mapValue, verbose); + return SendMoney(*pwallet, coin_control, recipients, mapValue, verbose); }, }; } @@ -559,9 +555,8 @@ static RPCHelpMan listaddressgroupings() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); - if (!wallet) return NullUniValue; - const CWallet* const pwallet = wallet.get(); + std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); + if (!pwallet) return NullUniValue; // Make sure the results are valid at least up to the most recent block // the user could have gotten from another RPC command prior to now @@ -617,20 +612,19 @@ static RPCHelpMan signmessage() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); - if (!wallet) return NullUniValue; - const CWallet* const pwallet = wallet.get(); + std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); + if (!pwallet) return NullUniValue; LOCK(pwallet->cs_wallet); - EnsureWalletIsUnlocked(pwallet); + EnsureWalletIsUnlocked(*pwallet); std::string strAddress = request.params[0].get_str(); std::string strMessage = request.params[1].get_str(); CTxDestination dest = DecodeDestination(strAddress); if (!IsValidDestination(dest)) { - throw JSONRPCError(RPC_TYPE_ERROR, "Invalid address"); + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid address"); } const PKHash* pkhash = std::get_if<PKHash>(&dest); @@ -720,9 +714,8 @@ static RPCHelpMan getreceivedbyaddress() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); - if (!wallet) return NullUniValue; - const CWallet* const pwallet = wallet.get(); + std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); + if (!pwallet) return NullUniValue; // Make sure the results are valid at least up to the most recent block // the user could have gotten from another RPC command prior to now @@ -759,9 +752,8 @@ static RPCHelpMan getreceivedbylabel() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); - if (!wallet) return NullUniValue; - const CWallet* const pwallet = wallet.get(); + std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); + if (!pwallet) return NullUniValue; // Make sure the results are valid at least up to the most recent block // the user could have gotten from another RPC command prior to now @@ -800,9 +792,8 @@ static RPCHelpMan getbalance() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); - if (!wallet) return NullUniValue; - const CWallet* const pwallet = wallet.get(); + std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); + if (!pwallet) return NullUniValue; // Make sure the results are valid at least up to the most recent block // the user could have gotten from another RPC command prior to now @@ -822,7 +813,7 @@ static RPCHelpMan getbalance() bool include_watchonly = ParseIncludeWatchonly(request.params[2], *pwallet); - bool avoid_reuse = GetAvoidReuseFlag(pwallet, request.params[3]); + bool avoid_reuse = GetAvoidReuseFlag(*pwallet, request.params[3]); const auto bal = pwallet->GetBalance(min_depth, avoid_reuse); @@ -840,9 +831,8 @@ static RPCHelpMan getunconfirmedbalance() RPCExamples{""}, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); - if (!wallet) return NullUniValue; - const CWallet* const pwallet = wallet.get(); + std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); + if (!pwallet) return NullUniValue; // Make sure the results are valid at least up to the most recent block // the user could have gotten from another RPC command prior to now @@ -911,9 +901,8 @@ static RPCHelpMan sendmany() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); - if (!wallet) return NullUniValue; - CWallet* const pwallet = wallet.get(); + std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); + if (!pwallet) return NullUniValue; // Make sure the results are valid at least up to the most recent block // the user could have gotten from another RPC command prior to now @@ -945,7 +934,7 @@ static RPCHelpMan sendmany() ParseRecipients(sendTo, subtractFeeFromAmount, recipients); const bool verbose{request.params[9].isNull() ? false : request.params[9].get_bool()}; - return SendMoney(pwallet, coin_control, recipients, std::move(mapValue), verbose); + return SendMoney(*pwallet, coin_control, recipients, std::move(mapValue), verbose); }, }; } @@ -985,9 +974,8 @@ static RPCHelpMan addmultisigaddress() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); - if (!wallet) return NullUniValue; - CWallet* const pwallet = wallet.get(); + std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); + if (!pwallet) return NullUniValue; LegacyScriptPubKeyMan& spk_man = EnsureLegacyScriptPubKeyMan(*pwallet); @@ -1045,7 +1033,7 @@ struct tallyitem } }; -static UniValue ListReceived(const CWallet* const pwallet, const UniValue& params, bool by_label) EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet) +static UniValue ListReceived(const CWallet& wallet, const UniValue& params, bool by_label) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet) { // Minimum confirmations int nMinDepth = 1; @@ -1059,7 +1047,7 @@ static UniValue ListReceived(const CWallet* const pwallet, const UniValue& param isminefilter filter = ISMINE_SPENDABLE; - if (ParseIncludeWatchonly(params[2], *pwallet)) { + if (ParseIncludeWatchonly(params[2], wallet)) { filter |= ISMINE_WATCH_ONLY; } @@ -1075,10 +1063,10 @@ static UniValue ListReceived(const CWallet* const pwallet, const UniValue& param // Tally std::map<CTxDestination, tallyitem> mapTally; - for (const std::pair<const uint256, CWalletTx>& pairWtx : pwallet->mapWallet) { + for (const std::pair<const uint256, CWalletTx>& pairWtx : wallet.mapWallet) { const CWalletTx& wtx = pairWtx.second; - if (wtx.IsCoinBase() || !pwallet->chain().checkFinalTx(*wtx.tx)) { + if (wtx.IsCoinBase() || !wallet.chain().checkFinalTx(*wtx.tx)) { continue; } @@ -1096,7 +1084,7 @@ static UniValue ListReceived(const CWallet* const pwallet, const UniValue& param continue; } - isminefilter mine = pwallet->IsMine(address); + isminefilter mine = wallet.IsMine(address); if(!(mine & filter)) continue; @@ -1115,11 +1103,11 @@ static UniValue ListReceived(const CWallet* const pwallet, const UniValue& param // Create m_address_book iterator // If we aren't filtering, go from begin() to end() - auto start = pwallet->m_address_book.begin(); - auto end = pwallet->m_address_book.end(); + auto start = wallet.m_address_book.begin(); + auto end = wallet.m_address_book.end(); // If we are filtering, find() the applicable entry if (has_filtered_address) { - start = pwallet->m_address_book.find(filtered_address); + start = wallet.m_address_book.find(filtered_address); if (start != end) { end = std::next(start); } @@ -1227,9 +1215,8 @@ static RPCHelpMan listreceivedbyaddress() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); - if (!wallet) return NullUniValue; - const CWallet* const pwallet = wallet.get(); + std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); + if (!pwallet) return NullUniValue; // Make sure the results are valid at least up to the most recent block // the user could have gotten from another RPC command prior to now @@ -1237,7 +1224,7 @@ static RPCHelpMan listreceivedbyaddress() LOCK(pwallet->cs_wallet); - return ListReceived(pwallet, request.params, false); + return ListReceived(*pwallet, request.params, false); }, }; } @@ -1270,9 +1257,8 @@ static RPCHelpMan listreceivedbylabel() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); - if (!wallet) return NullUniValue; - const CWallet* const pwallet = wallet.get(); + std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); + if (!pwallet) return NullUniValue; // Make sure the results are valid at least up to the most recent block // the user could have gotten from another RPC command prior to now @@ -1280,7 +1266,7 @@ static RPCHelpMan listreceivedbylabel() LOCK(pwallet->cs_wallet); - return ListReceived(pwallet, request.params, true); + return ListReceived(*pwallet, request.params, true); }, }; } @@ -1303,7 +1289,7 @@ static void MaybePushAddress(UniValue & entry, const CTxDestination &dest) * @param filter_ismine The "is mine" filter flags. * @param filter_label Optional label string to filter incoming transactions. */ -static void ListTransactions(const CWallet* const pwallet, const CWalletTx& wtx, int nMinDepth, bool fLong, UniValue& ret, const isminefilter& filter_ismine, const std::string* filter_label) EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet) +static void ListTransactions(const CWallet& wallet, const CWalletTx& wtx, int nMinDepth, bool fLong, UniValue& ret, const isminefilter& filter_ismine, const std::string* filter_label) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet) { CAmount nFee; std::list<COutputEntry> listReceived; @@ -1319,20 +1305,20 @@ static void ListTransactions(const CWallet* const pwallet, const CWalletTx& wtx, for (const COutputEntry& s : listSent) { UniValue entry(UniValue::VOBJ); - if (involvesWatchonly || (pwallet->IsMine(s.destination) & ISMINE_WATCH_ONLY)) { + if (involvesWatchonly || (wallet.IsMine(s.destination) & ISMINE_WATCH_ONLY)) { entry.pushKV("involvesWatchonly", true); } MaybePushAddress(entry, s.destination); entry.pushKV("category", "send"); entry.pushKV("amount", ValueFromAmount(-s.amount)); - const auto* address_book_entry = pwallet->FindAddressBookEntry(s.destination); + const auto* address_book_entry = wallet.FindAddressBookEntry(s.destination); if (address_book_entry) { entry.pushKV("label", address_book_entry->GetLabel()); } entry.pushKV("vout", s.vout); entry.pushKV("fee", ValueFromAmount(-nFee)); if (fLong) - WalletTxToJSON(pwallet->chain(), wtx, entry); + WalletTxToJSON(wallet.chain(), wtx, entry); entry.pushKV("abandoned", wtx.isAbandoned()); ret.push_back(entry); } @@ -1343,7 +1329,7 @@ static void ListTransactions(const CWallet* const pwallet, const CWalletTx& wtx, for (const COutputEntry& r : listReceived) { std::string label; - const auto* address_book_entry = pwallet->FindAddressBookEntry(r.destination); + const auto* address_book_entry = wallet.FindAddressBookEntry(r.destination); if (address_book_entry) { label = address_book_entry->GetLabel(); } @@ -1351,7 +1337,7 @@ static void ListTransactions(const CWallet* const pwallet, const CWalletTx& wtx, continue; } UniValue entry(UniValue::VOBJ); - if (involvesWatchonly || (pwallet->IsMine(r.destination) & ISMINE_WATCH_ONLY)) { + if (involvesWatchonly || (wallet.IsMine(r.destination) & ISMINE_WATCH_ONLY)) { entry.pushKV("involvesWatchonly", true); } MaybePushAddress(entry, r.destination); @@ -1374,7 +1360,7 @@ static void ListTransactions(const CWallet* const pwallet, const CWalletTx& wtx, } entry.pushKV("vout", r.vout); if (fLong) - WalletTxToJSON(pwallet->chain(), wtx, entry); + WalletTxToJSON(wallet.chain(), wtx, entry); ret.push_back(entry); } } @@ -1451,9 +1437,8 @@ static RPCHelpMan listtransactions() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); - if (!wallet) return NullUniValue; - const CWallet* const pwallet = wallet.get(); + std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); + if (!pwallet) return NullUniValue; // Make sure the results are valid at least up to the most recent block // the user could have gotten from another RPC command prior to now @@ -1494,7 +1479,7 @@ static RPCHelpMan listtransactions() for (CWallet::TxItems::const_reverse_iterator it = txOrdered.rbegin(); it != txOrdered.rend(); ++it) { CWalletTx *const pwtx = (*it).second; - ListTransactions(pwallet, *pwtx, 0, true, ret, filter, filter_label); + ListTransactions(*pwallet, *pwtx, 0, true, ret, filter, filter_label); if ((int)ret.size() >= (nCount+nFrom)) break; } } @@ -1579,8 +1564,8 @@ static RPCHelpMan listsinceblock() LOCK(wallet.cs_wallet); - Optional<int> height; // Height of the specified block or the common ancestor, if the block provided was in a deactivated chain. - Optional<int> altheight; // Height of the specified block, even if it's in a deactivated chain. + std::optional<int> height; // Height of the specified block or the common ancestor, if the block provided was in a deactivated chain. + std::optional<int> altheight; // Height of the specified block, even if it's in a deactivated chain. int target_confirms = 1; isminefilter filter = ISMINE_SPENDABLE; @@ -1616,7 +1601,7 @@ static RPCHelpMan listsinceblock() const CWalletTx& tx = pairWtx.second; if (depth == -1 || abs(tx.GetDepthInMainChain()) < depth) { - ListTransactions(&wallet, tx, 0, true, transactions, filter, nullptr /* filter_label */); + ListTransactions(wallet, tx, 0, true, transactions, filter, nullptr /* filter_label */); } } @@ -1633,7 +1618,7 @@ static RPCHelpMan listsinceblock() if (it != wallet.mapWallet.end()) { // We want all transactions regardless of confirmation count to appear here, // even negative confirmation ones, hence the big negative. - ListTransactions(&wallet, it->second, -100000000, true, removed, filter, nullptr /* filter_label */); + ListTransactions(wallet, it->second, -100000000, true, removed, filter, nullptr /* filter_label */); } } blockId = block.hashPrevBlock; @@ -1710,9 +1695,8 @@ static RPCHelpMan gettransaction() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); - if (!wallet) return NullUniValue; - const CWallet* const pwallet = wallet.get(); + std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); + if (!pwallet) return NullUniValue; // Make sure the results are valid at least up to the most recent block // the user could have gotten from another RPC command prior to now @@ -1749,7 +1733,7 @@ static RPCHelpMan gettransaction() WalletTxToJSON(pwallet->chain(), wtx, entry); UniValue details(UniValue::VARR); - ListTransactions(pwallet, wtx, 0, false, details, filter, nullptr /* filter_label */); + ListTransactions(*pwallet, wtx, 0, false, details, filter, nullptr /* filter_label */); entry.pushKV("details", details); std::string strHex = EncodeHexTx(*wtx.tx, pwallet->chain().rpcSerializationFlags()); @@ -1757,7 +1741,7 @@ static RPCHelpMan gettransaction() if (verbose) { UniValue decoded(UniValue::VOBJ); - TxToUniv(*wtx.tx, uint256(), decoded, false); + TxToUniv(*wtx.tx, uint256(), pwallet->chain().rpcEnableDeprecated("addresses"), decoded, false); entry.pushKV("decoded", decoded); } @@ -1784,9 +1768,8 @@ static RPCHelpMan abandontransaction() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); - if (!wallet) return NullUniValue; - CWallet* const pwallet = wallet.get(); + std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); + if (!pwallet) return NullUniValue; // Make sure the results are valid at least up to the most recent block // the user could have gotten from another RPC command prior to now @@ -1823,9 +1806,8 @@ static RPCHelpMan backupwallet() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); - if (!wallet) return NullUniValue; - const CWallet* const pwallet = wallet.get(); + std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); + if (!pwallet) return NullUniValue; // Make sure the results are valid at least up to the most recent block // the user could have gotten from another RPC command prior to now @@ -1859,9 +1841,8 @@ static RPCHelpMan keypoolrefill() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); - if (!wallet) return NullUniValue; - CWallet* const pwallet = wallet.get(); + std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); + if (!pwallet) return NullUniValue; if (pwallet->IsLegacy() && pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { throw JSONRPCError(RPC_WALLET_ERROR, "Error: Private keys are disabled for this wallet"); @@ -1877,7 +1858,7 @@ static RPCHelpMan keypoolrefill() kpSize = (unsigned int)request.params[0].get_int(); } - EnsureWalletIsUnlocked(pwallet); + EnsureWalletIsUnlocked(*pwallet); pwallet->TopUpKeyPool(kpSize); if (pwallet->GetKeyPoolSize() < kpSize) { @@ -2001,9 +1982,8 @@ static RPCHelpMan walletpassphrasechange() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); - if (!wallet) return NullUniValue; - CWallet* const pwallet = wallet.get(); + std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); + if (!pwallet) return NullUniValue; LOCK(pwallet->cs_wallet); @@ -2055,9 +2035,8 @@ static RPCHelpMan walletlock() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); - if (!wallet) return NullUniValue; - CWallet* const pwallet = wallet.get(); + std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); + if (!pwallet) return NullUniValue; LOCK(pwallet->cs_wallet); @@ -2100,9 +2079,8 @@ static RPCHelpMan encryptwallet() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); - if (!wallet) return NullUniValue; - CWallet* const pwallet = wallet.get(); + std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); + if (!pwallet) return NullUniValue; LOCK(pwallet->cs_wallet); @@ -2174,9 +2152,8 @@ static RPCHelpMan lockunspent() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); - if (!wallet) return NullUniValue; - CWallet* const pwallet = wallet.get(); + std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); + if (!pwallet) return NullUniValue; // Make sure the results are valid at least up to the most recent block // the user could have gotten from another RPC command prior to now @@ -2289,9 +2266,8 @@ static RPCHelpMan listlockunspent() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); - if (!wallet) return NullUniValue; - const CWallet* const pwallet = wallet.get(); + std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); + if (!pwallet) return NullUniValue; LOCK(pwallet->cs_wallet); @@ -2330,9 +2306,8 @@ static RPCHelpMan settxfee() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); - if (!wallet) return NullUniValue; - CWallet* const pwallet = wallet.get(); + std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); + if (!pwallet) return NullUniValue; LOCK(pwallet->cs_wallet); @@ -2460,9 +2435,8 @@ static RPCHelpMan getwalletinfo() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); - if (!wallet) return NullUniValue; - const CWallet* const pwallet = wallet.get(); + std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); + if (!pwallet) return NullUniValue; // Make sure the results are valid at least up to the most recent block // the user could have gotten from another RPC command prior to now @@ -2616,7 +2590,7 @@ static RPCHelpMan loadwallet() options.require_existing = true; bilingual_str error; std::vector<bilingual_str> warnings; - Optional<bool> load_on_start = request.params[1].isNull() ? nullopt : Optional<bool>(request.params[1].get_bool()); + std::optional<bool> load_on_start = request.params[1].isNull() ? std::nullopt : std::optional<bool>(request.params[1].get_bool()); std::shared_ptr<CWallet> const wallet = LoadWallet(*context.chain, name, load_on_start, options, status, error, warnings); if (!wallet) { // Map bad format to not found, since bad format is returned when the @@ -2672,9 +2646,8 @@ static RPCHelpMan setwalletflag() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); - if (!wallet) return NullUniValue; - CWallet* const pwallet = wallet.get(); + std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); + if (!pwallet) return NullUniValue; std::string flag_str = request.params[0].get_str(); bool value = request.params[1].isNull() || request.params[1].get_bool(); @@ -2791,7 +2764,7 @@ static RPCHelpMan createwallet() options.create_flags = flags; options.create_passphrase = passphrase; bilingual_str error; - Optional<bool> load_on_start = request.params[6].isNull() ? nullopt : Optional<bool>(request.params[6].get_bool()); + std::optional<bool> load_on_start = request.params[6].isNull() ? std::nullopt : std::optional<bool>(request.params[6].get_bool()); std::shared_ptr<CWallet> wallet = CreateWallet(*context.chain, request.params[0].get_str(), load_on_start, options, status, error, warnings); if (!wallet) { RPCErrorCode code = status == DatabaseStatus::FAILED_ENCRYPT ? RPC_WALLET_ENCRYPTION_FAILED : RPC_WALLET_ERROR; @@ -2843,7 +2816,7 @@ static RPCHelpMan unloadwallet() // Note that any attempt to load the same wallet would fail until the wallet // is destroyed (see CheckUniqueFileid). std::vector<bilingual_str> warnings; - Optional<bool> load_on_start = request.params[1].isNull() ? nullopt : Optional<bool>(request.params[1].get_bool()); + std::optional<bool> load_on_start = request.params[1].isNull() ? std::nullopt : std::optional<bool>(request.params[1].get_bool()); if (!RemoveWallet(wallet, load_on_start, warnings)) { throw JSONRPCError(RPC_MISC_ERROR, "Requested wallet already unloaded"); } @@ -2916,9 +2889,8 @@ static RPCHelpMan listunspent() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); - if (!wallet) return NullUniValue; - const CWallet* const pwallet = wallet.get(); + std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); + if (!pwallet) return NullUniValue; int nMinDepth = 1; if (!request.params[0].isNull()) { @@ -3080,11 +3052,11 @@ static RPCHelpMan listunspent() }; } -void FundTransaction(CWallet* const pwallet, CMutableTransaction& tx, CAmount& fee_out, int& change_position, const UniValue& options, CCoinControl& coinControl, bool override_min_fee) +void FundTransaction(CWallet& wallet, CMutableTransaction& tx, CAmount& fee_out, int& change_position, const UniValue& options, CCoinControl& coinControl, bool override_min_fee) { // Make sure the results are valid at least up to the most recent block // the user could have gotten from another RPC command prior to now - pwallet->BlockUntilSyncedToCurrentChain(); + wallet.BlockUntilSyncedToCurrentChain(); change_position = -1; bool lockUnspents = false; @@ -3155,7 +3127,7 @@ void FundTransaction(CWallet* const pwallet, CMutableTransaction& tx, CAmount& f } const UniValue include_watching_option = options.exists("include_watching") ? options["include_watching"] : options["includeWatching"]; - coinControl.fAllowWatchOnly = ParseIncludeWatchonly(include_watching_option, *pwallet); + coinControl.fAllowWatchOnly = ParseIncludeWatchonly(include_watching_option, wallet); if (options.exists("lockUnspents") || options.exists("lock_unspents")) { lockUnspents = (options.exists("lock_unspents") ? options["lock_unspents"] : options["lockUnspents"]).get_bool(); @@ -3181,11 +3153,11 @@ void FundTransaction(CWallet* const pwallet, CMutableTransaction& tx, CAmount& f if (options.exists("replaceable")) { coinControl.m_signal_bip125_rbf = options["replaceable"].get_bool(); } - SetFeeEstimateMode(*pwallet, coinControl, options["conf_target"], options["estimate_mode"], options["fee_rate"], override_min_fee); + SetFeeEstimateMode(wallet, coinControl, options["conf_target"], options["estimate_mode"], options["fee_rate"], override_min_fee); } } else { // if options is null and not a bool - coinControl.fAllowWatchOnly = ParseIncludeWatchonly(NullUniValue, *pwallet); + coinControl.fAllowWatchOnly = ParseIncludeWatchonly(NullUniValue, wallet); } if (tx.vout.size() == 0) @@ -3207,7 +3179,7 @@ void FundTransaction(CWallet* const pwallet, CMutableTransaction& tx, CAmount& f bilingual_str error; - if (!pwallet->FundTransaction(tx, fee_out, change_position, error, lockUnspents, setSubtractFeeFromOutputs, coinControl)) { + if (!wallet.FundTransaction(tx, fee_out, change_position, error, lockUnspents, setSubtractFeeFromOutputs, coinControl)) { throw JSONRPCError(RPC_WALLET_ERROR, error.original); } } @@ -3283,9 +3255,8 @@ static RPCHelpMan fundrawtransaction() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); - if (!wallet) return NullUniValue; - CWallet* const pwallet = wallet.get(); + std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); + if (!pwallet) return NullUniValue; RPCTypeCheck(request.params, {UniValue::VSTR, UniValueType(), UniValue::VBOOL}); @@ -3302,7 +3273,7 @@ static RPCHelpMan fundrawtransaction() CCoinControl coin_control; // Automatically select (additional) coins. Can be overridden by options.add_inputs. coin_control.m_add_inputs = true; - FundTransaction(pwallet, tx, fee, change_position, request.params[1], coin_control, /* override_min_fee */ true); + FundTransaction(*pwallet, tx, fee, change_position, request.params[1], coin_control, /* override_min_fee */ true); UniValue result(UniValue::VOBJ); result.pushKV("hex", EncodeHexTx(CTransaction(tx))); @@ -3369,9 +3340,8 @@ RPCHelpMan signrawtransactionwithwallet() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); - if (!wallet) return NullUniValue; - const CWallet* const pwallet = wallet.get(); + std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); + if (!pwallet) return NullUniValue; RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VARR, UniValue::VSTR}, true); @@ -3382,7 +3352,7 @@ RPCHelpMan signrawtransactionwithwallet() // Sign the transaction LOCK(pwallet->cs_wallet); - EnsureWalletIsUnlocked(pwallet); + EnsureWalletIsUnlocked(*pwallet); // Fetch previous transactions (inputs): std::map<COutPoint, Coin> coins; @@ -3409,7 +3379,7 @@ RPCHelpMan signrawtransactionwithwallet() static RPCHelpMan bumpfee_helper(std::string method_name) { - bool want_psbt = method_name == "psbtbumpfee"; + const bool want_psbt = method_name == "psbtbumpfee"; const std::string incremental_fee{CFeeRate(DEFAULT_INCREMENTAL_RELAY_FEE).ToString(FeeEstimateMode::SAT_VB)}; return RPCHelpMan{method_name, @@ -3443,14 +3413,14 @@ 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, /* default */ "unset", std::string() + "The fee estimate mode, must be one of (case insensitive):\n" - " \"" + FeeModes("\"\n\"") + "\""}, + "\"" + FeeModes("\"\n\"") + "\""}, }, "options"}, }, RPCResult{ RPCResult::Type::OBJ, "", "", Cat(Cat<std::vector<RPCResult>>( { - {RPCResult::Type::STR, "psbt", "The base64-encoded unsigned PSBT of the new transaction." + std::string(want_psbt ? "" : " Only returned when wallet private keys are disabled. (DEPRECATED)")}, + {RPCResult::Type::STR, "psbt", "The base64-encoded unsigned PSBT of the new transaction."}, }, want_psbt ? std::vector<RPCResult>{} : std::vector<RPCResult>{{RPCResult::Type::STR_HEX, "txid", "The id of the new transaction. Only returned when wallet private keys are enabled."}} ), @@ -3467,11 +3437,10 @@ static RPCHelpMan bumpfee_helper(std::string method_name) "\nBump the fee, get the new transaction\'s" + std::string(want_psbt ? "psbt" : "txid") + "\n" + HelpExampleCli(method_name, "<txid>") }, - [want_psbt](const RPCHelpMan& self, const JSONRPCRequest& request) mutable -> UniValue + [want_psbt](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); - if (!wallet) return NullUniValue; - CWallet* const pwallet = wallet.get(); + std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); + if (!pwallet) return NullUniValue; if (pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) && !want_psbt) { throw JSONRPCError(RPC_WALLET_ERROR, "bumpfee is not available with wallets that have private keys disabled. Use psbtbumpfee instead."); @@ -3514,7 +3483,8 @@ static RPCHelpMan bumpfee_helper(std::string method_name) pwallet->BlockUntilSyncedToCurrentChain(); LOCK(pwallet->cs_wallet); - EnsureWalletIsUnlocked(pwallet); + + EnsureWalletIsUnlocked(*pwallet); std::vector<bilingual_str> errors; @@ -3608,9 +3578,8 @@ static RPCHelpMan rescanblockchain() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); - if (!wallet) return NullUniValue; - CWallet* const pwallet = wallet.get(); + std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); + if (!pwallet) return NullUniValue; WalletRescanReserver reserver(*pwallet); if (!reserver.reserve()) { @@ -3618,7 +3587,7 @@ static RPCHelpMan rescanblockchain() } int start_height = 0; - Optional<int> stop_height; + std::optional<int> stop_height; uint256 start_block; { LOCK(pwallet->cs_wallet); @@ -3759,15 +3728,13 @@ public: UniValue operator()(const WitnessUnknown& id) const { return UniValue(UniValue::VOBJ); } }; -static UniValue DescribeWalletAddress(const CWallet* const pwallet, const CTxDestination& dest) +static UniValue DescribeWalletAddress(const CWallet& wallet, const CTxDestination& dest) { UniValue ret(UniValue::VOBJ); UniValue detail = DescribeAddress(dest); CScript script = GetScriptForDestination(dest); std::unique_ptr<SigningProvider> provider = nullptr; - if (pwallet) { - provider = pwallet->GetSolvingProvider(script); - } + provider = wallet.GetSolvingProvider(script); ret.pushKVs(detail); ret.pushKVs(std::visit(DescribeWalletAddressVisitor(provider.get()), dest)); return ret; @@ -3840,9 +3807,8 @@ RPCHelpMan getaddressinfo() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); - if (!wallet) return NullUniValue; - const CWallet* const pwallet = wallet.get(); + std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); + if (!pwallet) return NullUniValue; LOCK(pwallet->cs_wallet); @@ -3887,7 +3853,7 @@ RPCHelpMan getaddressinfo() ret.pushKV("iswatchonly", bool(mine & ISMINE_WATCH_ONLY)); - UniValue detail = DescribeWalletAddress(pwallet, dest); + UniValue detail = DescribeWalletAddress(*pwallet, dest); ret.pushKVs(detail); ret.pushKV("ischange", pwallet->IsChange(scriptPubKey)); @@ -3943,9 +3909,8 @@ static RPCHelpMan getaddressesbylabel() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); - if (!wallet) return NullUniValue; - const CWallet* const pwallet = wallet.get(); + std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); + if (!pwallet) return NullUniValue; LOCK(pwallet->cs_wallet); @@ -4005,9 +3970,8 @@ static RPCHelpMan listlabels() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); - if (!wallet) return NullUniValue; - const CWallet* const pwallet = wallet.get(); + std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); + if (!pwallet) return NullUniValue; LOCK(pwallet->cs_wallet); @@ -4130,9 +4094,8 @@ static RPCHelpMan send() }, true ); - std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); - if (!wallet) return NullUniValue; - CWallet* const pwallet = wallet.get(); + std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); + if (!pwallet) return NullUniValue; UniValue options{request.params[4].isNull() ? UniValue::VOBJ : request.params[4]}; if (options.exists("conf_target") || options.exists("estimate_mode")) { @@ -4185,7 +4148,7 @@ static RPCHelpMan send() // Automatically select coins, unless at least one is manually selected. Can // be overridden by options.add_inputs. coin_control.m_add_inputs = rawTx.vin.size() == 0; - FundTransaction(pwallet, rawTx, fee, change_position, options, coin_control, /* override_min_fee */ false); + FundTransaction(*pwallet, rawTx, fee, change_position, options, coin_control, /* override_min_fee */ false); bool add_to_wallet = true; if (options.exists("add_to_wallet")) { @@ -4258,9 +4221,8 @@ static RPCHelpMan sethdseed() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); - if (!wallet) return NullUniValue; - CWallet* const pwallet = wallet.get(); + std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); + if (!pwallet) return NullUniValue; LegacyScriptPubKeyMan& spk_man = EnsureLegacyScriptPubKeyMan(*pwallet, true); @@ -4275,7 +4237,7 @@ static RPCHelpMan sethdseed() throw JSONRPCError(RPC_WALLET_ERROR, "Cannot set an HD seed on a non-HD wallet. Use the upgradewallet RPC in order to upgrade a non-HD wallet to HD"); } - EnsureWalletIsUnlocked(pwallet); + EnsureWalletIsUnlocked(*pwallet); bool flush_key_pool = true; if (!request.params[0].isNull()) { @@ -4336,9 +4298,8 @@ static RPCHelpMan walletprocesspsbt() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); - if (!wallet) return NullUniValue; - const CWallet* const pwallet = wallet.get(); + std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); + if (!pwallet) return NullUniValue; RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VBOOL, UniValue::VSTR}); @@ -4448,9 +4409,8 @@ static RPCHelpMan walletcreatefundedpsbt() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); - if (!wallet) return NullUniValue; - CWallet* const pwallet = wallet.get(); + std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); + if (!pwallet) return NullUniValue; RPCTypeCheck(request.params, { UniValue::VARR, @@ -4474,7 +4434,7 @@ static RPCHelpMan walletcreatefundedpsbt() // Automatically select coins, unless at least one is manually selected. Can // be overridden by options.add_inputs. coin_control.m_add_inputs = rawTx.vin.size() == 0; - FundTransaction(pwallet, rawTx, fee, change_position, request.params[3], coin_control, /* override_min_fee */ true); + FundTransaction(*pwallet, rawTx, fee, change_position, request.params[3], coin_control, /* override_min_fee */ true); // Make a blank psbt PartiallySignedTransaction psbtx(rawTx); @@ -4524,13 +4484,12 @@ static RPCHelpMan upgradewallet() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); - if (!wallet) return NullUniValue; - CWallet* const pwallet = wallet.get(); + std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); + if (!pwallet) return NullUniValue; RPCTypeCheck(request.params, {UniValue::VNUM}, true); - EnsureWalletIsUnlocked(pwallet); + EnsureWalletIsUnlocked(*pwallet); int version = 0; if (!request.params[0].isNull()) { diff --git a/src/wallet/rpcwallet.h b/src/wallet/rpcwallet.h index 184a16e91d..8b88ffe8ed 100644 --- a/src/wallet/rpcwallet.h +++ b/src/wallet/rpcwallet.h @@ -7,6 +7,7 @@ #include <span.h> +#include <any> #include <memory> #include <string> #include <vector> @@ -30,8 +31,8 @@ Span<const CRPCCommand> GetWalletRPCCommands(); */ std::shared_ptr<CWallet> GetWalletForJSONRPCRequest(const JSONRPCRequest& request); -void EnsureWalletIsUnlocked(const CWallet*); -WalletContext& EnsureWalletContext(const util::Ref& context); +void EnsureWalletIsUnlocked(const CWallet&); +WalletContext& EnsureWalletContext(const std::any& context); LegacyScriptPubKeyMan& EnsureLegacyScriptPubKeyMan(CWallet& wallet, bool also_create = false); RPCHelpMan getaddressinfo(); diff --git a/src/wallet/salvage.cpp b/src/wallet/salvage.cpp index 09a9ec68cd..6d912be019 100644 --- a/src/wallet/salvage.cpp +++ b/src/wallet/salvage.cpp @@ -119,7 +119,7 @@ bool RecoverDatabaseFile(const fs::path& file_path, bilingual_str& error, std::v return false; } - std::unique_ptr<Db> pdbCopy = MakeUnique<Db>(env->dbenv.get(), 0); + std::unique_ptr<Db> pdbCopy = std::make_unique<Db>(env->dbenv.get(), 0); int ret = pdbCopy->open(nullptr, // Txn pointer filename.c_str(), // Filename "main", // Logical db name diff --git a/src/wallet/scriptpubkeyman.cpp b/src/wallet/scriptpubkeyman.cpp index efb408c163..54319ca662 100644 --- a/src/wallet/scriptpubkeyman.cpp +++ b/src/wallet/scriptpubkeyman.cpp @@ -16,6 +16,8 @@ #include <wallet/external_signer.h> #include <wallet/scriptpubkeyman.h> +#include <optional> + //! Value for the first BIP 32 hardened derivation. Can be used as a bit mask and as a value. See BIP 32 for more details. const uint32_t BIP32_HARDENED_KEY_LIMIT = 0x80000000; @@ -378,7 +380,7 @@ void LegacyScriptPubKeyMan::UpgradeKeyMetadata() return; } - std::unique_ptr<WalletBatch> batch = MakeUnique<WalletBatch>(m_storage.GetDatabase()); + std::unique_ptr<WalletBatch> batch = std::make_unique<WalletBatch>(m_storage.GetDatabase()); for (auto& meta_pair : mapKeyMetadata) { CKeyMetadata& meta = meta_pair.second; if (!meta.hd_seed_id.IsNull() && !meta.has_key_origin && meta.hdKeypath != "s") { // If the hdKeypath is "s", that's the seed and it doesn't have a key origin @@ -551,7 +553,7 @@ int64_t LegacyScriptPubKeyMan::GetTimeFirstKey() const std::unique_ptr<SigningProvider> LegacyScriptPubKeyMan::GetSolvingProvider(const CScript& script) const { - return MakeUnique<LegacySigningProvider>(*this); + return std::make_unique<LegacySigningProvider>(*this); } bool LegacyScriptPubKeyMan::CanProvide(const CScript& script, SignatureData& sigdata) @@ -651,14 +653,14 @@ std::unique_ptr<CKeyMetadata> LegacyScriptPubKeyMan::GetMetadata(const CTxDestin if (!key_id.IsNull()) { auto it = mapKeyMetadata.find(key_id); if (it != mapKeyMetadata.end()) { - return MakeUnique<CKeyMetadata>(it->second); + return std::make_unique<CKeyMetadata>(it->second); } } CScript scriptPubKey = GetScriptForDestination(dest); auto it = m_script_metadata.find(CScriptID(scriptPubKey)); if (it != m_script_metadata.end()) { - return MakeUnique<CKeyMetadata>(it->second); + return std::make_unique<CKeyMetadata>(it->second); } return nullptr; @@ -1607,7 +1609,7 @@ bool DescriptorScriptPubKeyMan::GetNewDestination(const OutputType type, CTxDest { LOCK(cs_desc_man); assert(m_wallet_descriptor.descriptor->IsSingleType()); // This is a combo descriptor which should not be an active descriptor - Optional<OutputType> desc_addr_type = m_wallet_descriptor.descriptor->GetOutputType(); + std::optional<OutputType> desc_addr_type = m_wallet_descriptor.descriptor->GetOutputType(); assert(desc_addr_type); if (type != *desc_addr_type) { throw std::runtime_error(std::string(__func__) + ": Types are inconsistent"); @@ -1629,7 +1631,7 @@ bool DescriptorScriptPubKeyMan::GetNewDestination(const OutputType type, CTxDest return false; } - Optional<OutputType> out_script_type = m_wallet_descriptor.descriptor->GetOutputType(); + std::optional<OutputType> out_script_type = m_wallet_descriptor.descriptor->GetOutputType(); if (out_script_type && out_script_type == type) { ExtractDestination(scripts_temp[0], dest); } else { @@ -2026,7 +2028,7 @@ std::unique_ptr<FlatSigningProvider> DescriptorScriptPubKeyMan::GetSigningProvid { AssertLockHeld(cs_desc_man); // Get the scripts, keys, and key origins for this script - std::unique_ptr<FlatSigningProvider> out_keys = MakeUnique<FlatSigningProvider>(); + std::unique_ptr<FlatSigningProvider> out_keys = std::make_unique<FlatSigningProvider>(); std::vector<CScript> scripts_temp; if (!m_wallet_descriptor.descriptor->ExpandFromCache(index, m_wallet_descriptor.cache, scripts_temp, *out_keys)) return nullptr; @@ -2051,7 +2053,7 @@ bool DescriptorScriptPubKeyMan::CanProvide(const CScript& script, SignatureData& bool DescriptorScriptPubKeyMan::SignTransaction(CMutableTransaction& tx, const std::map<COutPoint, Coin>& coins, int sighash, std::map<int, std::string>& input_errors) const { - std::unique_ptr<FlatSigningProvider> keys = MakeUnique<FlatSigningProvider>(); + std::unique_ptr<FlatSigningProvider> keys = std::make_unique<FlatSigningProvider>(); for (const auto& coin_pair : coins) { std::unique_ptr<FlatSigningProvider> coin_keys = GetSigningProvider(coin_pair.second.out.scriptPubKey, true); if (!coin_keys) { @@ -2115,13 +2117,13 @@ TransactionError DescriptorScriptPubKeyMan::FillPSBT(PartiallySignedTransaction& SignatureData sigdata; input.FillSignatureData(sigdata); - std::unique_ptr<FlatSigningProvider> keys = MakeUnique<FlatSigningProvider>(); + std::unique_ptr<FlatSigningProvider> keys = std::make_unique<FlatSigningProvider>(); std::unique_ptr<FlatSigningProvider> script_keys = GetSigningProvider(script, sign); if (script_keys) { *keys = Merge(*keys, *script_keys); } else { // Maybe there are pubkeys listed that we can sign for - script_keys = MakeUnique<FlatSigningProvider>(); + script_keys = std::make_unique<FlatSigningProvider>(); for (const auto& pk_pair : input.hd_keypaths) { const CPubKey& pubkey = pk_pair.first; std::unique_ptr<FlatSigningProvider> pk_keys = GetSigningProvider(pubkey); @@ -2162,7 +2164,7 @@ std::unique_ptr<CKeyMetadata> DescriptorScriptPubKeyMan::GetMetadata(const CTxDe CKeyID key_id = GetKeyForDestination(*provider, dest); if (provider->GetKeyOrigin(key_id, orig)) { LOCK(cs_desc_man); - std::unique_ptr<CKeyMetadata> meta = MakeUnique<CKeyMetadata>(); + std::unique_ptr<CKeyMetadata> meta = std::make_unique<CKeyMetadata>(); meta->key_origin = orig; meta->has_key_origin = true; meta->nCreateTime = m_wallet_descriptor.creation_time; diff --git a/src/wallet/sqlite.cpp b/src/wallet/sqlite.cpp index 0fb3b1d3c4..975974cb6a 100644 --- a/src/wallet/sqlite.cpp +++ b/src/wallet/sqlite.cpp @@ -8,7 +8,6 @@ #include <crypto/common.h> #include <logging.h> #include <sync.h> -#include <util/memory.h> #include <util/strencodings.h> #include <util/system.h> #include <util/translation.h> @@ -330,7 +329,7 @@ void SQLiteDatabase::Close() std::unique_ptr<DatabaseBatch> SQLiteDatabase::MakeBatch(bool flush_on_close) { // We ignore flush_on_close because we don't do manual flushing for SQLite - return MakeUnique<SQLiteBatch>(*this); + return std::make_unique<SQLiteBatch>(*this); } SQLiteBatch::SQLiteBatch(SQLiteDatabase& database) @@ -571,7 +570,7 @@ std::unique_ptr<SQLiteDatabase> MakeSQLiteDatabase(const fs::path& path, const D { try { fs::path data_file = SQLiteDataFile(path); - auto db = MakeUnique<SQLiteDatabase>(data_file.parent_path(), data_file); + auto db = std::make_unique<SQLiteDatabase>(data_file.parent_path(), data_file); if (options.verify && !db->Verify(error)) { status = DatabaseStatus::FAILED_VERIFY; return nullptr; diff --git a/src/wallet/test/coinselector_tests.cpp b/src/wallet/test/coinselector_tests.cpp index ffac78d752..7eff6e592d 100644 --- a/src/wallet/test/coinselector_tests.cpp +++ b/src/wallet/test/coinselector_tests.cpp @@ -35,7 +35,10 @@ static CAmount balance = 0; CoinEligibilityFilter filter_standard(1, 6, 0); CoinEligibilityFilter filter_confirmed(1, 1, 0); CoinEligibilityFilter filter_standard_extra(6, 6, 0); -CoinSelectionParams coin_selection_params(false, 0, 0, CFeeRate(0), 0, false); +CoinSelectionParams coin_selection_params(/* use_bnb= */ false, /* change_output_size= */ 0, + /* change_spend_size= */ 0, /* effective_feerate= */ CFeeRate(0), + /* long_term_feerate= */ CFeeRate(0), /* discard_feerate= */ CFeeRate(0), + /* tx_no_inputs_size= */ 0, /* avoid_partial= */ false); static void add_coin(const CAmount& nValue, int nInput, std::vector<CInputCoin>& set) { @@ -269,7 +272,10 @@ BOOST_AUTO_TEST_CASE(bnb_search_test) } // Make sure that effective value is working in SelectCoinsMinConf when BnB is used - CoinSelectionParams coin_selection_params_bnb(true, 0, 0, CFeeRate(3000), 0, false); + CoinSelectionParams coin_selection_params_bnb(/* use_bnb= */ true, /* change_output_size= */ 0, + /* change_spend_size= */ 0, /* effective_feerate= */ CFeeRate(3000), + /* long_term_feerate= */ CFeeRate(1000), /* discard_feerate= */ CFeeRate(1000), + /* tx_no_inputs_size= */ 0, /* avoid_partial= */ false); CoinSet setCoinsRet; CAmount nValueRet; bool bnb_used; @@ -290,7 +296,7 @@ BOOST_AUTO_TEST_CASE(bnb_search_test) // Make sure that can use BnB when there are preset inputs empty_wallet(); { - std::unique_ptr<CWallet> wallet = MakeUnique<CWallet>(m_node.chain.get(), "", CreateMockWalletDatabase()); + std::unique_ptr<CWallet> wallet = std::make_unique<CWallet>(m_node.chain.get(), "", CreateMockWalletDatabase()); bool firstRun; wallet->LoadWallet(firstRun); wallet->SetupLegacyScriptPubKeyMan(); @@ -301,7 +307,7 @@ BOOST_AUTO_TEST_CASE(bnb_search_test) CCoinControl coin_control; coin_control.fAllowOtherInputs = true; coin_control.Select(COutPoint(vCoins.at(0).tx->GetHash(), vCoins.at(0).i)); - coin_selection_params_bnb.effective_fee = CFeeRate(0); + coin_selection_params_bnb.m_effective_feerate = CFeeRate(0); BOOST_CHECK(wallet->SelectCoins(vCoins, 10 * CENT, setCoinsRet, nValueRet, coin_control, coin_selection_params_bnb, bnb_used)); BOOST_CHECK(bnb_used); BOOST_CHECK(coin_selection_params_bnb.use_bnb); @@ -639,8 +645,14 @@ BOOST_AUTO_TEST_CASE(SelectCoins_test) CAmount target = rand.randrange(balance - 1000) + 1000; // Perform selection - CoinSelectionParams coin_selection_params_knapsack(false, 34, 148, CFeeRate(0), 0, false); - CoinSelectionParams coin_selection_params_bnb(true, 34, 148, CFeeRate(0), 0, false); + CoinSelectionParams coin_selection_params_knapsack(/* use_bnb= */ false, /* change_output_size= */ 34, + /* change_spend_size= */ 148, /* effective_feerate= */ CFeeRate(0), + /* long_term_feerate= */ CFeeRate(0), /* discard_feerate= */ CFeeRate(0), + /* tx_no_inputs_size= */ 0, /* avoid_partial= */ false); + CoinSelectionParams coin_selection_params_bnb(/* use_bnb= */ true, /* change_output_size= */ 34, + /* change_spend_size= */ 148, /* effective_feerate= */ CFeeRate(0), + /* long_term_feerate= */ CFeeRate(0), /* discard_feerate= */ CFeeRate(0), + /* tx_no_inputs_size= */ 0, /* avoid_partial= */ false); CoinSet out_set; CAmount out_value = 0; bool bnb_used = false; diff --git a/src/wallet/test/wallet_tests.cpp b/src/wallet/test/wallet_tests.cpp index 5480f3ab22..ba2e17d62a 100644 --- a/src/wallet/test/wallet_tests.cpp +++ b/src/wallet/test/wallet_tests.cpp @@ -4,6 +4,7 @@ #include <wallet/wallet.h> +#include <any> #include <future> #include <memory> #include <stdint.h> @@ -15,7 +16,6 @@ #include <rpc/server.h> #include <test/util/logging.h> #include <test/util/setup_common.h> -#include <util/ref.h> #include <util/translation.h> #include <validation.h> #include <wallet/coincontrol.h> @@ -213,7 +213,7 @@ BOOST_FIXTURE_TEST_CASE(importmulti_rescan, TestChain100Setup) key.pushKV("timestamp", newTip->GetBlockTimeMax() + TIMESTAMP_WINDOW + 1); key.pushKV("internal", UniValue(true)); keys.push_back(key); - util::Ref context; + std::any context; JSONRPCRequest request(context); request.params.setArray(); request.params.push_back(keys); @@ -228,7 +228,7 @@ BOOST_FIXTURE_TEST_CASE(importmulti_rescan, TestChain100Setup) "downloading and rescanning the relevant blocks (see -reindex and -rescan " "options).\"}},{\"success\":true}]", 0, oldTip->GetBlockTimeMax(), TIMESTAMP_WINDOW)); - RemoveWallet(wallet, nullopt); + RemoveWallet(wallet, std::nullopt); } } @@ -265,13 +265,13 @@ BOOST_FIXTURE_TEST_CASE(importwallet_rescan, TestChain100Setup) AddWallet(wallet); wallet->SetLastBlockProcessed(::ChainActive().Height(), ::ChainActive().Tip()->GetBlockHash()); } - util::Ref context; + std::any context; JSONRPCRequest request(context); request.params.setArray(); request.params.push_back(backup_file); ::dumpwallet().HandleRequest(request); - RemoveWallet(wallet, nullopt); + RemoveWallet(wallet, std::nullopt); } // Call importwallet RPC and verify all blocks with timestamps >= BLOCK_TIME @@ -281,14 +281,14 @@ BOOST_FIXTURE_TEST_CASE(importwallet_rescan, TestChain100Setup) LOCK(wallet->cs_wallet); wallet->SetupLegacyScriptPubKeyMan(); - util::Ref context; + std::any context; JSONRPCRequest request(context); request.params.setArray(); request.params.push_back(backup_file); AddWallet(wallet); wallet->SetLastBlockProcessed(::ChainActive().Height(), ::ChainActive().Tip()->GetBlockHash()); ::importwallet().HandleRequest(request); - RemoveWallet(wallet, nullopt); + RemoveWallet(wallet, std::nullopt); BOOST_CHECK_EQUAL(wallet->mapWallet.size(), 3U); BOOST_CHECK_EQUAL(m_coinbase_txns.size(), 103U); @@ -485,7 +485,7 @@ public: ListCoinsTestingSetup() { CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey())); - wallet = MakeUnique<CWallet>(m_node.chain.get(), "", CreateMockWalletDatabase()); + wallet = std::make_unique<CWallet>(m_node.chain.get(), "", CreateMockWalletDatabase()); { LOCK2(wallet->cs_wallet, ::cs_main); wallet->SetLastBlockProcessed(::ChainActive().Height(), ::ChainActive().Tip()->GetBlockHash()); diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 08e480225d..b00fa851fd 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -13,7 +13,6 @@ #include <interfaces/wallet.h> #include <key.h> #include <key_io.h> -#include <optional.h> #include <outputtype.h> #include <policy/fees.h> #include <policy/policy.h> @@ -40,6 +39,7 @@ #include <algorithm> #include <assert.h> +#include <optional> #include <boost/algorithm/string/replace.hpp> @@ -84,10 +84,10 @@ bool RemoveWalletSetting(interfaces::Chain& chain, const std::string& wallet_nam static void UpdateWalletSetting(interfaces::Chain& chain, const std::string& wallet_name, - Optional<bool> load_on_startup, + std::optional<bool> load_on_startup, std::vector<bilingual_str>& warnings) { - if (load_on_startup == nullopt) return; + if (!load_on_startup) return; if (load_on_startup.value() && !AddWalletSetting(chain, wallet_name)) { warnings.emplace_back(Untranslated("Wallet load on startup setting could not be updated, so wallet may not be loaded next node startup.")); } else if (!load_on_startup.value() && !RemoveWalletSetting(chain, wallet_name)) { @@ -107,7 +107,7 @@ bool AddWallet(const std::shared_ptr<CWallet>& wallet) return true; } -bool RemoveWallet(const std::shared_ptr<CWallet>& wallet, Optional<bool> load_on_start, std::vector<bilingual_str>& warnings) +bool RemoveWallet(const std::shared_ptr<CWallet>& wallet, std::optional<bool> load_on_start, std::vector<bilingual_str>& warnings) { assert(wallet); @@ -127,7 +127,7 @@ bool RemoveWallet(const std::shared_ptr<CWallet>& wallet, Optional<bool> load_on return true; } -bool RemoveWallet(const std::shared_ptr<CWallet>& wallet, Optional<bool> load_on_start) +bool RemoveWallet(const std::shared_ptr<CWallet>& wallet, std::optional<bool> load_on_start) { std::vector<bilingual_str> warnings; return RemoveWallet(wallet, load_on_start, warnings); @@ -204,7 +204,7 @@ void UnloadWallet(std::shared_ptr<CWallet>&& wallet) } namespace { -std::shared_ptr<CWallet> LoadWalletInternal(interfaces::Chain& chain, const std::string& name, Optional<bool> load_on_start, const DatabaseOptions& options, DatabaseStatus& status, bilingual_str& error, std::vector<bilingual_str>& warnings) +std::shared_ptr<CWallet> LoadWalletInternal(interfaces::Chain& chain, const std::string& name, std::optional<bool> load_on_start, const DatabaseOptions& options, DatabaseStatus& status, bilingual_str& error, std::vector<bilingual_str>& warnings) { try { std::unique_ptr<WalletDatabase> database = MakeWalletDatabase(name, options, status, error); @@ -234,11 +234,11 @@ std::shared_ptr<CWallet> LoadWalletInternal(interfaces::Chain& chain, const std: } } // namespace -std::shared_ptr<CWallet> LoadWallet(interfaces::Chain& chain, const std::string& name, Optional<bool> load_on_start, const DatabaseOptions& options, DatabaseStatus& status, bilingual_str& error, std::vector<bilingual_str>& warnings) +std::shared_ptr<CWallet> LoadWallet(interfaces::Chain& chain, const std::string& name, std::optional<bool> load_on_start, const DatabaseOptions& options, DatabaseStatus& status, bilingual_str& error, std::vector<bilingual_str>& warnings) { 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; } @@ -247,7 +247,7 @@ std::shared_ptr<CWallet> LoadWallet(interfaces::Chain& chain, const std::string& return wallet; } -std::shared_ptr<CWallet> CreateWallet(interfaces::Chain& chain, const std::string& name, Optional<bool> load_on_start, DatabaseOptions& options, DatabaseStatus& status, bilingual_str& error, std::vector<bilingual_str>& warnings) +std::shared_ptr<CWallet> CreateWallet(interfaces::Chain& chain, const std::string& name, std::optional<bool> load_on_start, DatabaseOptions& options, DatabaseStatus& status, bilingual_str& error, std::vector<bilingual_str>& warnings) { uint64_t wallet_creation_flags = options.create_flags; const SecureString& passphrase = options.create_passphrase; @@ -791,6 +791,12 @@ bool CWallet::MarkReplaced(const uint256& originalHash, const uint256& newHash) wtx.mapValue["replaced_by_txid"] = newHash.ToString(); + // Refresh mempool status without waiting for transactionRemovedFromMempool + // notification so the wallet is in an internally consistent state and + // immediately knows the old transaction should not be considered trusted + // and is eligible to be abandoned + wtx.fInMempool = chain().isInMempool(originalHash); + WalletBatch batch(GetDatabase()); bool success = true; @@ -938,6 +944,14 @@ CWalletTx* CWallet::AddToWallet(CTransactionRef tx, const CWalletTx::Confirmatio if (!strCmd.empty()) { boost::replace_all(strCmd, "%s", hash.GetHex()); + if (confirm.status == CWalletTx::Status::CONFIRMED) + { + boost::replace_all(strCmd, "%b", confirm.hashBlock.GetHex()); + boost::replace_all(strCmd, "%h", ToString(confirm.block_height)); + } else { + boost::replace_all(strCmd, "%b", "unconfirmed"); + boost::replace_all(strCmd, "%h", "-1"); + } #ifndef WIN32 // Substituting the wallet name isn't currently supported on windows // because windows shell escaping has not been implemented yet: @@ -1611,14 +1625,15 @@ bool CWallet::ImportScriptPubKeys(const std::string& label, const std::set<CScri return true; } -int64_t CalculateMaximumSignedTxSize(const CTransaction &tx, const CWallet *wallet, bool use_max_sig) +// Returns pair of vsize and weight +std::pair<int64_t, int64_t> CalculateMaximumSignedTxSize(const CTransaction &tx, const CWallet *wallet, bool use_max_sig) { std::vector<CTxOut> txouts; for (const CTxIn& input : tx.vin) { const auto mi = wallet->mapWallet.find(input.prevout.hash); // Can not estimate size without knowing the input details if (mi == wallet->mapWallet.end()) { - return -1; + return std::make_pair(-1, -1); } assert(input.prevout.n < mi->second.tx->vout.size()); txouts.emplace_back(mi->second.tx->vout[input.prevout.n]); @@ -1627,13 +1642,16 @@ int64_t CalculateMaximumSignedTxSize(const CTransaction &tx, const CWallet *wall } // txouts needs to be in the order of tx.vin -int64_t CalculateMaximumSignedTxSize(const CTransaction &tx, const CWallet *wallet, const std::vector<CTxOut>& txouts, bool use_max_sig) +std::pair<int64_t, int64_t> CalculateMaximumSignedTxSize(const CTransaction &tx, const CWallet *wallet, const std::vector<CTxOut>& txouts, bool use_max_sig) { CMutableTransaction txNew(tx); if (!wallet->DummySignTx(txNew, txouts, use_max_sig)) { - return -1; + return std::make_pair(-1, -1); } - return GetVirtualTransactionSize(CTransaction(txNew)); + CTransaction ctx(txNew); + int64_t vsize = GetVirtualTransactionSize(ctx); + int64_t weight = GetTransactionWeight(ctx); + return std::make_pair(vsize, weight); } int CalculateMaximumSignedInputSize(const CTxOut& txout, const CWallet* wallet, bool use_max_sig) @@ -1753,7 +1771,7 @@ int64_t CWallet::RescanFromTime(int64_t startTime, const WalletRescanReserver& r * the main chain after to the addition of any new keys you want to detect * transactions for. */ -CWallet::ScanResult CWallet::ScanForWalletTransactions(const uint256& start_block, int start_height, Optional<int> max_height, const WalletRescanReserver& reserver, bool fUpdate) +CWallet::ScanResult CWallet::ScanForWalletTransactions(const uint256& start_block, int start_height, std::optional<int> max_height, const WalletRescanReserver& reserver, bool fUpdate) { int64_t nNow = GetTime(); int64_t start_time = GetTimeMillis(); @@ -2381,26 +2399,20 @@ bool CWallet::SelectCoinsMinConf(const CAmount& nTargetValue, const CoinEligibil nValueRet = 0; if (coin_selection_params.use_bnb) { - // Get long term estimate - FeeCalculation feeCalc; - CCoinControl temp; - temp.m_confirm_target = 1008; - CFeeRate long_term_feerate = GetMinimumFeeRate(*this, temp, &feeCalc); - // Get the feerate for effective value. // When subtracting the fee from the outputs, we want the effective feerate to be 0 CFeeRate effective_feerate{0}; if (!coin_selection_params.m_subtract_fee_outputs) { - effective_feerate = coin_selection_params.effective_fee; + effective_feerate = coin_selection_params.m_effective_feerate; } - std::vector<OutputGroup> groups = GroupOutputs(coins, !coin_selection_params.m_avoid_partial_spends, effective_feerate, long_term_feerate, eligibility_filter, true /* positive_only */); + std::vector<OutputGroup> groups = GroupOutputs(coins, !coin_selection_params.m_avoid_partial_spends, effective_feerate, coin_selection_params.m_long_term_feerate, eligibility_filter, true /* positive_only */); // Calculate cost of change - CAmount cost_of_change = GetDiscardRate(*this).GetFee(coin_selection_params.change_spend_size) + coin_selection_params.effective_fee.GetFee(coin_selection_params.change_output_size); + CAmount cost_of_change = coin_selection_params.m_discard_feerate.GetFee(coin_selection_params.change_spend_size) + coin_selection_params.m_effective_feerate.GetFee(coin_selection_params.change_output_size); // Calculate the fees for things that aren't inputs - CAmount not_input_fees = coin_selection_params.effective_fee.GetFee(coin_selection_params.tx_noinputs_size); + CAmount not_input_fees = coin_selection_params.m_effective_feerate.GetFee(coin_selection_params.tx_noinputs_size); bnb_used = true; return SelectCoinsBnB(groups, nTargetValue, cost_of_change, setCoinsRet, nValueRet, not_input_fees); } else { @@ -2454,7 +2466,7 @@ bool CWallet::SelectCoins(const std::vector<COutput>& vAvailableCoins, const CAm if (coin.m_input_bytes <= 0) { return false; // Not solvable, can't estimate size for fee } - coin.effective_value = coin.txout.nValue - coin_selection_params.effective_fee.GetFee(coin.m_input_bytes); + coin.effective_value = coin.txout.nValue - coin_selection_params.m_effective_feerate.GetFee(coin.m_input_bytes); if (coin_selection_params.use_bnb) { value_to_select -= coin.effective_value; } else { @@ -2715,7 +2727,7 @@ static uint32_t GetLocktimeForNewTransaction(interfaces::Chain& chain, const uin return locktime; } -OutputType CWallet::TransactionChangeType(const Optional<OutputType>& change_type, const std::vector<CRecipient>& vecSend) const +OutputType CWallet::TransactionChangeType(const std::optional<OutputType>& change_type, const std::vector<CRecipient>& vecSend) const { // If -changetype is specified, always use that change type. if (change_type) { @@ -2779,6 +2791,7 @@ bool CWallet::CreateTransactionInternal( CMutableTransaction txNew; FeeCalculation feeCalc; CAmount nFeeNeeded; + std::pair<int64_t, int64_t> tx_sizes; int nBytes; { std::set<CInputCoin> setCoins; @@ -2821,16 +2834,27 @@ bool CWallet::CreateTransactionInternal( CTxOut change_prototype_txout(0, scriptChange); coin_selection_params.change_output_size = GetSerializeSize(change_prototype_txout); - CFeeRate discard_rate = GetDiscardRate(*this); + // Set discard feerate + coin_selection_params.m_discard_feerate = GetDiscardRate(*this); // Get the fee rate to use effective values in coin selection - CFeeRate nFeeRateNeeded = GetMinimumFeeRate(*this, coin_control, &feeCalc); + coin_selection_params.m_effective_feerate = GetMinimumFeeRate(*this, coin_control, &feeCalc); // Do not, ever, assume that it's fine to change the fee rate if the user has explicitly // provided one - if (coin_control.m_feerate && nFeeRateNeeded > *coin_control.m_feerate) { - error = strprintf(_("Fee rate (%s) is lower than the minimum fee rate setting (%s)"), coin_control.m_feerate->ToString(FeeEstimateMode::SAT_VB), nFeeRateNeeded.ToString(FeeEstimateMode::SAT_VB)); + if (coin_control.m_feerate && coin_selection_params.m_effective_feerate > *coin_control.m_feerate) { + error = strprintf(_("Fee rate (%s) is lower than the minimum fee rate setting (%s)"), coin_control.m_feerate->ToString(FeeEstimateMode::SAT_VB), coin_selection_params.m_effective_feerate.ToString(FeeEstimateMode::SAT_VB)); return false; } + if (feeCalc.reason == FeeReason::FALLBACK && !m_allow_fallback_fee) { + // eventually allow a fallback fee + error = _("Fee estimation failed. Fallbackfee is disabled. Wait a few blocks or enable -fallbackfee."); + return false; + } + + // Get long term estimate + CCoinControl cc_temp; + cc_temp.m_confirm_target = chain().estimateMaxBlocks(); + coin_selection_params.m_long_term_feerate = GetMinimumFeeRate(*this, cc_temp, nullptr); nFeeRet = 0; bool pick_new_inputs = true; @@ -2905,7 +2929,6 @@ bool CWallet::CreateTransactionInternal( } else { coin_selection_params.change_spend_size = (size_t)change_spend_size; } - coin_selection_params.effective_fee = nFeeRateNeeded; if (!SelectCoins(vAvailableCoins, nValueToSelect, setCoins, nValueIn, coin_control, coin_selection_params, bnb_used)) { // If BnB was used, it was the first pass. No longer the first pass and continue loop with knapsack. @@ -2931,7 +2954,7 @@ bool CWallet::CreateTransactionInternal( // Never create dust outputs; if we would, just // add the dust to the fee. // The nChange when BnB is used is always going to go to fees. - if (IsDust(newTxOut, discard_rate) || bnb_used) + if (IsDust(newTxOut, coin_selection_params.m_discard_feerate) || bnb_used) { nChangePosInOut = -1; nFeeRet += nChange; @@ -2962,19 +2985,14 @@ bool CWallet::CreateTransactionInternal( txNew.vin.push_back(CTxIn(coin.outpoint,CScript())); } - nBytes = CalculateMaximumSignedTxSize(CTransaction(txNew), this, coin_control.fAllowWatchOnly); + tx_sizes = CalculateMaximumSignedTxSize(CTransaction(txNew), this, coin_control.fAllowWatchOnly); + nBytes = tx_sizes.first; if (nBytes < 0) { error = _("Signing transaction failed"); return false; } - nFeeNeeded = GetMinimumFee(*this, nBytes, coin_control, &feeCalc); - if (feeCalc.reason == FeeReason::FALLBACK && !m_allow_fallback_fee) { - // eventually allow a fallback fee - error = _("Fee estimation failed. Fallbackfee is disabled. Wait a few blocks or enable -fallbackfee."); - return false; - } - + nFeeNeeded = coin_selection_params.m_effective_feerate.GetFee(nBytes); if (nFeeRet >= nFeeNeeded) { // Reduce fee to only the needed amount if possible. This // prevents potential overpayment in fees if the coins @@ -2988,8 +3006,8 @@ bool CWallet::CreateTransactionInternal( // change output. Only try this once. if (nChangePosInOut == -1 && nSubtractFeeFromAmount == 0 && pick_new_inputs) { unsigned int tx_size_with_change = nBytes + coin_selection_params.change_output_size + 2; // Add 2 as a buffer in case increasing # of outputs changes compact size - CAmount fee_needed_with_change = GetMinimumFee(*this, tx_size_with_change, coin_control, nullptr); - CAmount minimum_value_for_change = GetDustThreshold(change_prototype_txout, discard_rate); + CAmount fee_needed_with_change = coin_selection_params.m_effective_feerate.GetFee(tx_size_with_change); + CAmount minimum_value_for_change = GetDustThreshold(change_prototype_txout, coin_selection_params.m_discard_feerate); if (nFeeRet >= fee_needed_with_change + minimum_value_for_change) { pick_new_inputs = false; nFeeRet = fee_needed_with_change; @@ -3072,7 +3090,8 @@ bool CWallet::CreateTransactionInternal( tx = MakeTransactionRef(std::move(txNew)); // Limit size - if (GetTransactionWeight(*tx) > MAX_STANDARD_TX_WEIGHT) + if ((sign && GetTransactionWeight(*tx) > MAX_STANDARD_TX_WEIGHT) || + (!sign && tx_sizes.second > MAX_STANDARD_TX_WEIGHT)) { error = _("Transaction too large"); return false; @@ -4054,13 +4073,13 @@ std::shared_ptr<CWallet> CWallet::Create(interfaces::Chain& chain, const std::st WalletBatch batch(walletInstance->GetDatabase()); CBlockLocator locator; if (batch.ReadBestBlock(locator)) { - if (const Optional<int> fork_height = chain.findLocatorFork(locator)) { + if (const std::optional<int> fork_height = chain.findLocatorFork(locator)) { rescan_height = *fork_height; } } } - const Optional<int> tip_height = chain.getHeight(); + const std::optional<int> tip_height = chain.getHeight(); if (tip_height) { walletInstance->m_last_block_processed = chain.getBlockHash(*tip_height); walletInstance->m_last_block_processed_height = *tip_height; @@ -4094,7 +4113,7 @@ std::shared_ptr<CWallet> CWallet::Create(interfaces::Chain& chain, const std::st // No need to read and scan block if block was created before // our wallet birthday (as adjusted for block time variability) - Optional<int64_t> time_first_key; + std::optional<int64_t> time_first_key; for (auto spk_man : walletInstance->GetAllScriptPubKeyMans()) { int64_t time = spk_man->GetTimeFirstKey(); if (!time_first_key || time < *time_first_key) time_first_key = time; diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index eb797938cd..6031d36b97 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -30,6 +30,7 @@ #include <atomic> #include <map> #include <memory> +#include <optional> #include <set> #include <stdexcept> #include <stdint.h> @@ -51,12 +52,12 @@ struct bilingual_str; void UnloadWallet(std::shared_ptr<CWallet>&& wallet); bool AddWallet(const std::shared_ptr<CWallet>& wallet); -bool RemoveWallet(const std::shared_ptr<CWallet>& wallet, Optional<bool> load_on_start, std::vector<bilingual_str>& warnings); -bool RemoveWallet(const std::shared_ptr<CWallet>& wallet, Optional<bool> load_on_start); +bool RemoveWallet(const std::shared_ptr<CWallet>& wallet, std::optional<bool> load_on_start, std::vector<bilingual_str>& warnings); +bool RemoveWallet(const std::shared_ptr<CWallet>& wallet, std::optional<bool> load_on_start); std::vector<std::shared_ptr<CWallet>> GetWallets(); std::shared_ptr<CWallet> GetWallet(const std::string& name); -std::shared_ptr<CWallet> LoadWallet(interfaces::Chain& chain, const std::string& name, Optional<bool> load_on_start, const DatabaseOptions& options, DatabaseStatus& status, bilingual_str& error, std::vector<bilingual_str>& warnings); -std::shared_ptr<CWallet> CreateWallet(interfaces::Chain& chain, const std::string& name, Optional<bool> load_on_start, DatabaseOptions& options, DatabaseStatus& status, bilingual_str& error, std::vector<bilingual_str>& warnings); +std::shared_ptr<CWallet> LoadWallet(interfaces::Chain& chain, const std::string& name, std::optional<bool> load_on_start, const DatabaseOptions& options, DatabaseStatus& status, bilingual_str& error, std::vector<bilingual_str>& warnings); +std::shared_ptr<CWallet> CreateWallet(interfaces::Chain& chain, const std::string& name, std::optional<bool> load_on_start, DatabaseOptions& options, DatabaseStatus& status, bilingual_str& error, std::vector<bilingual_str>& warnings); std::unique_ptr<interfaces::Handler> HandleLoadWallet(LoadWalletFn load_wallet); std::unique_ptr<WalletDatabase> MakeWalletDatabase(const std::string& name, const DatabaseOptions& options, DatabaseStatus& status, bilingual_str& error); @@ -608,17 +609,22 @@ struct CoinSelectionParams bool use_bnb = true; size_t change_output_size = 0; size_t change_spend_size = 0; - CFeeRate effective_fee = CFeeRate(0); + CFeeRate m_effective_feerate; + CFeeRate m_long_term_feerate; + CFeeRate m_discard_feerate; size_t tx_noinputs_size = 0; //! Indicate that we are subtracting the fee from outputs bool m_subtract_fee_outputs = false; bool m_avoid_partial_spends = false; - CoinSelectionParams(bool use_bnb, size_t change_output_size, size_t change_spend_size, CFeeRate effective_fee, size_t tx_noinputs_size, bool avoid_partial) : + CoinSelectionParams(bool use_bnb, size_t change_output_size, size_t change_spend_size, CFeeRate effective_feerate, + CFeeRate long_term_feerate, CFeeRate discard_feerate, size_t tx_noinputs_size, bool avoid_partial) : use_bnb(use_bnb), change_output_size(change_output_size), change_spend_size(change_spend_size), - effective_fee(effective_fee), + m_effective_feerate(effective_feerate), + m_long_term_feerate(long_term_feerate), + m_discard_feerate(discard_feerate), tx_noinputs_size(tx_noinputs_size), m_avoid_partial_spends(avoid_partial) {} @@ -923,7 +929,7 @@ public: //! Unset if no blocks were scanned due to read errors or the chain //! being empty. uint256 last_scanned_block; - Optional<int> last_scanned_height; + std::optional<int> last_scanned_height; //! Height of the most recent block that could not be scanned due to //! read errors or pruning. Will be set if status is FAILURE, unset if @@ -931,7 +937,7 @@ public: //! USER_ABORT. uint256 last_failed_block; }; - ScanResult ScanForWalletTransactions(const uint256& start_block, int start_height, Optional<int> max_height, const WalletRescanReserver& reserver, bool fUpdate); + ScanResult ScanForWalletTransactions(const uint256& start_block, int start_height, std::optional<int> max_height, const WalletRescanReserver& reserver, bool fUpdate); void transactionRemovedFromMempool(const CTransactionRef& tx, MemPoolRemovalReason reason, uint64_t mempool_sequence) override; void ReacceptWalletTransactions() EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); void ResendWalletTransactions(); @@ -946,7 +952,7 @@ public: Balance GetBalance(int min_depth = 0, bool avoid_reuse = true) const; CAmount GetAvailableBalance(const CCoinControl* coinControl = nullptr) const; - OutputType TransactionChangeType(const Optional<OutputType>& change_type, const std::vector<CRecipient>& vecSend) const; + OutputType TransactionChangeType(const std::optional<OutputType>& change_type, const std::vector<CRecipient>& vecSend) const; /** * Insert additional inputs into the transaction by @@ -1031,7 +1037,7 @@ public: * (see -changetype option documentation and implementation in * CWallet::TransactionChangeType for details). */ - Optional<OutputType> m_default_change_type{}; + std::optional<OutputType> m_default_change_type{}; /** Absolute maximum transaction fee (in satoshis) used by default for the wallet */ CAmount m_default_max_tx_fee{DEFAULT_TRANSACTION_MAXFEE}; @@ -1334,8 +1340,8 @@ public: // Use DummySignatureCreator, which inserts 71 byte signatures everywhere. // NOTE: this requires that all inputs must be in mapWallet (eg the tx should // be IsAllFromMe). -int64_t CalculateMaximumSignedTxSize(const CTransaction &tx, const CWallet *wallet, bool use_max_sig = false) EXCLUSIVE_LOCKS_REQUIRED(wallet->cs_wallet); -int64_t CalculateMaximumSignedTxSize(const CTransaction &tx, const CWallet *wallet, const std::vector<CTxOut>& txouts, bool use_max_sig = false); +std::pair<int64_t, int64_t> CalculateMaximumSignedTxSize(const CTransaction &tx, const CWallet *wallet, bool use_max_sig = false) EXCLUSIVE_LOCKS_REQUIRED(wallet->cs_wallet); +std::pair<int64_t, int64_t> CalculateMaximumSignedTxSize(const CTransaction &tx, const CWallet *wallet, const std::vector<CTxOut>& txouts, bool use_max_sig = false); //! Add wallet name to persistent configuration so it will be loaded on startup. bool AddWalletSetting(interfaces::Chain& chain, const std::string& wallet_name); diff --git a/src/wallet/walletdb.cpp b/src/wallet/walletdb.cpp index 69854cae05..3d9248009f 100644 --- a/src/wallet/walletdb.cpp +++ b/src/wallet/walletdb.cpp @@ -23,6 +23,7 @@ #include <wallet/wallet.h> #include <atomic> +#include <optional> #include <string> namespace DBKeys { @@ -1015,7 +1016,7 @@ std::unique_ptr<WalletDatabase> MakeDatabase(const fs::path& path, const Databas return nullptr; } - Optional<DatabaseFormat> format; + std::optional<DatabaseFormat> format; if (exists) { if (IsBDBFile(BDBDataFile(path))) { format = DatabaseFormat::BERKELEY; @@ -1086,15 +1087,15 @@ std::unique_ptr<WalletDatabase> MakeDatabase(const fs::path& path, const Databas /** Return object for accessing dummy database with no read/write capabilities. */ std::unique_ptr<WalletDatabase> CreateDummyWalletDatabase() { - return MakeUnique<DummyDatabase>(); + return std::make_unique<DummyDatabase>(); } /** Return object for accessing temporary in-memory database. */ std::unique_ptr<WalletDatabase> CreateMockWalletDatabase() { #ifdef USE_BDB - return MakeUnique<BerkeleyDatabase>(std::make_shared<BerkeleyEnvironment>(), ""); + return std::make_unique<BerkeleyDatabase>(std::make_shared<BerkeleyEnvironment>(), ""); #elif USE_SQLITE - return MakeUnique<SQLiteDatabase>("", "", true); + return std::make_unique<SQLiteDatabase>("", "", true); #endif } diff --git a/src/zmq/zmqabstractnotifier.h b/src/zmq/zmqabstractnotifier.h index 6f0b202a18..49c1c2a07d 100644 --- a/src/zmq/zmqabstractnotifier.h +++ b/src/zmq/zmqabstractnotifier.h @@ -5,7 +5,6 @@ #ifndef BITCOIN_ZMQ_ZMQABSTRACTNOTIFIER_H #define BITCOIN_ZMQ_ZMQABSTRACTNOTIFIER_H -#include <util/memory.h> #include <memory> #include <string> @@ -27,7 +26,7 @@ public: template <typename T> static std::unique_ptr<CZMQAbstractNotifier> Create() { - return MakeUnique<T>(); + return std::make_unique<T>(); } std::string GetType() const { return type; } 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_blockfilterindex_prune.py b/test/functional/feature_blockfilterindex_prune.py index 6648e8ecd9..d13d191b20 100755 --- a/test/functional/feature_blockfilterindex_prune.py +++ b/test/functional/feature_blockfilterindex_prune.py @@ -13,43 +13,52 @@ from test_framework.util import ( class FeatureBlockfilterindexPruneTest(BitcoinTestFramework): def set_test_params(self): - self.num_nodes = 2 - self.extra_args = [["-fastprune", "-prune=1"], ["-fastprune", "-prune=1", "-blockfilterindex=1"]] + self.num_nodes = 1 + self.extra_args = [["-fastprune", "-prune=1", "-blockfilterindex=1"]] + + def sync_index(self, height): + expected = {'basic block filter index': {'synced': True, 'best_block_height': height}} + self.wait_until(lambda: self.nodes[0].getindexinfo() == expected) def run_test(self): self.log.info("check if we can access a blockfilter when pruning is enabled but no blocks are actually pruned") - self.wait_until(lambda: self.nodes[1].getindexinfo() == {'basic block filter index': {'synced': True, 'best_block_height': 200}}) - assert len(self.nodes[1].getblockfilter(self.nodes[1].getbestblockhash())['filter']) > 0 + self.sync_index(height=200) + assert_greater_than(len(self.nodes[0].getblockfilter(self.nodes[0].getbestblockhash())['filter']), 0) # Mine two batches of blocks to avoid hitting NODE_NETWORK_LIMITED_MIN_BLOCKS disconnection - self.nodes[1].generate(250) + self.nodes[0].generate(250) self.sync_all() - self.nodes[1].generate(250) + self.nodes[0].generate(250) self.sync_all() - self.wait_until(lambda: self.nodes[1].getindexinfo() == {'basic block filter index': {'synced': True, 'best_block_height': 700}}) + self.sync_index(height=700) + self.log.info("prune some blocks") - pruneheight = self.nodes[1].pruneblockchain(400) + pruneheight = self.nodes[0].pruneblockchain(400) assert_equal(pruneheight, 250) + self.log.info("check if we can access the tips blockfilter when we have pruned some blocks") - assert len(self.nodes[1].getblockfilter(self.nodes[1].getbestblockhash())['filter']) > 0 + assert_greater_than(len(self.nodes[0].getblockfilter(self.nodes[0].getbestblockhash())['filter']), 0) + self.log.info("check if we can access the blockfilter of a pruned block") - assert len(self.nodes[1].getblockfilter(self.nodes[1].getblockhash(2))['filter']) > 0 + assert_greater_than(len(self.nodes[0].getblockfilter(self.nodes[0].getblockhash(2))['filter']), 0) + self.log.info("start node without blockfilterindex") - self.stop_node(1) - self.start_node(1, extra_args=self.extra_args[0]) + self.restart_node(0, extra_args=["-fastprune", "-prune=1"]) + self.log.info("make sure accessing the blockfilters throws an error") - assert_raises_rpc_error(-1, "Index is not enabled for filtertype basic", self.nodes[1].getblockfilter, self.nodes[1].getblockhash(2)) - self.nodes[1].generate(1000) + assert_raises_rpc_error(-1, "Index is not enabled for filtertype basic", self.nodes[0].getblockfilter, self.nodes[0].getblockhash(2)) + self.nodes[0].generate(1000) + self.log.info("prune below the blockfilterindexes best block while blockfilters are disabled") - pruneheight_new = self.nodes[1].pruneblockchain(1000) + pruneheight_new = self.nodes[0].pruneblockchain(1000) assert_greater_than(pruneheight_new, pruneheight) - self.stop_node(1) + self.stop_node(0) + self.log.info("make sure we get an init error when starting the node again with block filters") - with self.nodes[1].assert_debug_log(["basic block filter index best block of the index goes beyond pruned data. Please disable the index or reindex (which will download the whole blockchain again)"]): - self.nodes[1].assert_start_raises_init_error(extra_args=self.extra_args[1]) + with self.nodes[0].assert_debug_log(["basic block filter index best block of the index goes beyond pruned data. Please disable the index or reindex (which will download the whole blockchain again)"]): + self.nodes[0].assert_start_raises_init_error(extra_args=["-fastprune", "-prune=1", "-blockfilterindex=1"]) + self.log.info("make sure the node starts again with the -reindex arg") - reindex_args = self.extra_args[1] - reindex_args.append("-reindex") - self.start_node(1, extra_args=reindex_args) + self.start_node(0, extra_args = ["-fastprune", "-prune=1", "-blockfilterindex", "-reindex"]) if __name__ == '__main__': diff --git a/test/functional/feature_notifications.py b/test/functional/feature_notifications.py index b068ce612c..4e2de1daf4 100755 --- a/test/functional/feature_notifications.py +++ b/test/functional/feature_notifications.py @@ -17,7 +17,7 @@ from test_framework.util import ( FILE_CHAR_START = 32 if os.name == 'nt' else 1 FILE_CHAR_END = 128 FILE_CHARS_DISALLOWED = '/\\?%*:|"<>' if os.name == 'nt' else '/' - +UNCONFIRMED_HASH_STRING = 'unconfirmed' def notify_outputname(walletname, txid): return txid if os.name == 'nt' else '{}_{}'.format(walletname, txid) @@ -43,7 +43,7 @@ class NotificationsTest(BitcoinTestFramework): "-blocknotify=echo > {}".format(os.path.join(self.blocknotify_dir, '%s')), ], [ "-rescan", - "-walletnotify=echo > {}".format(os.path.join(self.walletnotify_dir, notify_outputname('%w', '%s'))), + "-walletnotify=echo %h_%b > {}".format(os.path.join(self.walletnotify_dir, notify_outputname('%w', '%s'))), ]] self.wallet_names = [self.default_wallet_name, self.wallet] super().setup_network() @@ -90,11 +90,9 @@ class NotificationsTest(BitcoinTestFramework): self.wait_until(lambda: len(os.listdir(self.walletnotify_dir)) == block_count, timeout=10) # directory content should equal the generated transaction hashes - txids_rpc = list(map(lambda t: notify_outputname(self.wallet, t['txid']), self.nodes[1].listtransactions("*", block_count))) - assert_equal(sorted(txids_rpc), sorted(os.listdir(self.walletnotify_dir))) + tx_details = list(map(lambda t: (t['txid'], t['blockheight'], t['blockhash']), self.nodes[1].listtransactions("*", block_count))) self.stop_node(1) - for tx_file in os.listdir(self.walletnotify_dir): - os.remove(os.path.join(self.walletnotify_dir, tx_file)) + self.expect_wallet_notify(tx_details) self.log.info("test -walletnotify after rescan") # restart node to rescan to force wallet notifications @@ -104,10 +102,8 @@ class NotificationsTest(BitcoinTestFramework): self.wait_until(lambda: len(os.listdir(self.walletnotify_dir)) == block_count, timeout=10) # directory content should equal the generated transaction hashes - txids_rpc = list(map(lambda t: notify_outputname(self.wallet, t['txid']), self.nodes[1].listtransactions("*", block_count))) - assert_equal(sorted(txids_rpc), sorted(os.listdir(self.walletnotify_dir))) - for tx_file in os.listdir(self.walletnotify_dir): - os.remove(os.path.join(self.walletnotify_dir, tx_file)) + tx_details = list(map(lambda t: (t['txid'], t['blockheight'], t['blockhash']), self.nodes[1].listtransactions("*", block_count))) + self.expect_wallet_notify(tx_details) # Conflicting transactions tests. # Generate spends from node 0, and check notifications @@ -122,7 +118,7 @@ class NotificationsTest(BitcoinTestFramework): tx1 = self.nodes[0].sendtoaddress(address=ADDRESS_BCRT1_UNSPENDABLE, amount=1, replaceable=True) assert_equal(tx1 in self.nodes[0].getrawmempool(), True) self.sync_mempools() - self.expect_wallet_notify([tx1]) + self.expect_wallet_notify([(tx1, -1, UNCONFIRMED_HASH_STRING)]) # Generate bump transaction, sync mempools, and check for bump1 # notification. In the future, per @@ -131,39 +127,57 @@ class NotificationsTest(BitcoinTestFramework): bump1 = self.nodes[0].bumpfee(tx1)["txid"] assert_equal(bump1 in self.nodes[0].getrawmempool(), True) self.sync_mempools() - self.expect_wallet_notify([bump1]) + self.expect_wallet_notify([(bump1, -1, UNCONFIRMED_HASH_STRING)]) # Add bump1 transaction to new block, checking for a notification # and the correct number of confirmations. - self.nodes[0].generatetoaddress(1, ADDRESS_BCRT1_UNSPENDABLE) + blockhash1 = self.nodes[0].generatetoaddress(1, ADDRESS_BCRT1_UNSPENDABLE)[0] + blockheight1 = self.nodes[0].getblockcount() self.sync_blocks() - self.expect_wallet_notify([bump1]) + self.expect_wallet_notify([(bump1, blockheight1, blockhash1)]) assert_equal(self.nodes[1].gettransaction(bump1)["confirmations"], 1) # Generate a second transaction to be bumped. tx2 = self.nodes[0].sendtoaddress(address=ADDRESS_BCRT1_UNSPENDABLE, amount=1, replaceable=True) assert_equal(tx2 in self.nodes[0].getrawmempool(), True) self.sync_mempools() - self.expect_wallet_notify([tx2]) + self.expect_wallet_notify([(tx2, -1, UNCONFIRMED_HASH_STRING)]) # Bump tx2 as bump2 and generate a block on node 0 while # disconnected, then reconnect and check for notifications on node 1 # about newly confirmed bump2 and newly conflicted tx2. self.disconnect_nodes(0, 1) bump2 = self.nodes[0].bumpfee(tx2)["txid"] - self.nodes[0].generatetoaddress(1, ADDRESS_BCRT1_UNSPENDABLE) + blockhash2 = self.nodes[0].generatetoaddress(1, ADDRESS_BCRT1_UNSPENDABLE)[0] + blockheight2 = self.nodes[0].getblockcount() assert_equal(self.nodes[0].gettransaction(bump2)["confirmations"], 1) assert_equal(tx2 in self.nodes[1].getrawmempool(), True) self.connect_nodes(0, 1) self.sync_blocks() - self.expect_wallet_notify([bump2, tx2]) + self.expect_wallet_notify([(bump2, blockheight2, blockhash2), (tx2, -1, UNCONFIRMED_HASH_STRING)]) assert_equal(self.nodes[1].gettransaction(bump2)["confirmations"], 1) # TODO: add test for `-alertnotify` large fork notifications - def expect_wallet_notify(self, tx_ids): - self.wait_until(lambda: len(os.listdir(self.walletnotify_dir)) >= len(tx_ids), timeout=10) - assert_equal(sorted(notify_outputname(self.wallet, tx_id) for tx_id in tx_ids), sorted(os.listdir(self.walletnotify_dir))) + def expect_wallet_notify(self, tx_details): + self.wait_until(lambda: len(os.listdir(self.walletnotify_dir)) >= len(tx_details), timeout=10) + # Should have no more and no less files than expected + assert_equal(sorted(notify_outputname(self.wallet, tx_id) for tx_id, _, _ in tx_details), sorted(os.listdir(self.walletnotify_dir))) + # Should now verify contents of each file + for tx_id, blockheight, blockhash in tx_details: + fname = os.path.join(self.walletnotify_dir, notify_outputname(self.wallet, tx_id)) + with open(fname, 'rt', encoding='utf-8') as f: + text = f.read() + # Universal newline ensures '\n' on 'nt' + assert_equal(text[-1], '\n') + text = text[:-1] + if os.name == 'nt': + # On Windows, echo as above will append a whitespace + assert_equal(text[-1], ' ') + text = text[:-1] + expected = str(blockheight) + '_' + blockhash + assert_equal(text, expected) + for tx_file in os.listdir(self.walletnotify_dir): os.remove(os.path.join(self.walletnotify_dir, tx_file)) diff --git a/test/functional/feature_proxy.py b/test/functional/feature_proxy.py index 2983feaa0d..8bee43b8ad 100755 --- a/test/functional/feature_proxy.py +++ b/test/functional/feature_proxy.py @@ -49,9 +49,10 @@ NET_UNROUTABLE = "not_publicly_routable" NET_IPV4 = "ipv4" NET_IPV6 = "ipv6" NET_ONION = "onion" +NET_I2P = "i2p" # Networks returned by RPC getnetworkinfo, defined in src/rpc/net.cpp::GetNetworksInfo() -NETWORKS = frozenset({NET_IPV4, NET_IPV6, NET_ONION}) +NETWORKS = frozenset({NET_IPV4, NET_IPV6, NET_ONION, NET_I2P}) class ProxyTest(BitcoinTestFramework): @@ -90,11 +91,15 @@ class ProxyTest(BitcoinTestFramework): self.serv3 = Socks5Server(self.conf3) self.serv3.start() + # We will not try to connect to this. + self.i2p_sam = ('127.0.0.1', 7656) + # Note: proxies are not used to connect to local nodes. This is because the proxy to # use is based on CService.GetNetwork(), which returns NET_UNROUTABLE for localhost. args = [ ['-listen', '-proxy=%s:%i' % (self.conf1.addr),'-proxyrandomize=1'], - ['-listen', '-proxy=%s:%i' % (self.conf1.addr),'-onion=%s:%i' % (self.conf2.addr),'-proxyrandomize=0'], + ['-listen', '-proxy=%s:%i' % (self.conf1.addr),'-onion=%s:%i' % (self.conf2.addr), + '-i2psam=%s:%i' % (self.i2p_sam), '-i2pacceptincoming=0', '-proxyrandomize=0'], ['-listen', '-proxy=%s:%i' % (self.conf2.addr),'-proxyrandomize=1'], [] ] @@ -199,9 +204,16 @@ class ProxyTest(BitcoinTestFramework): n0 = networks_dict(self.nodes[0].getnetworkinfo()) assert_equal(NETWORKS, n0.keys()) for net in NETWORKS: - assert_equal(n0[net]['proxy'], '%s:%i' % (self.conf1.addr)) - assert_equal(n0[net]['proxy_randomize_credentials'], True) + if net == NET_I2P: + expected_proxy = '' + expected_randomize = False + else: + expected_proxy = '%s:%i' % (self.conf1.addr) + expected_randomize = True + assert_equal(n0[net]['proxy'], expected_proxy) + assert_equal(n0[net]['proxy_randomize_credentials'], expected_randomize) assert_equal(n0['onion']['reachable'], True) + assert_equal(n0['i2p']['reachable'], False) n1 = networks_dict(self.nodes[1].getnetworkinfo()) assert_equal(NETWORKS, n1.keys()) @@ -211,21 +223,36 @@ class ProxyTest(BitcoinTestFramework): assert_equal(n1['onion']['proxy'], '%s:%i' % (self.conf2.addr)) assert_equal(n1['onion']['proxy_randomize_credentials'], False) assert_equal(n1['onion']['reachable'], True) + assert_equal(n1['i2p']['proxy'], '%s:%i' % (self.i2p_sam)) + assert_equal(n1['i2p']['proxy_randomize_credentials'], False) + assert_equal(n1['i2p']['reachable'], True) n2 = networks_dict(self.nodes[2].getnetworkinfo()) assert_equal(NETWORKS, n2.keys()) for net in NETWORKS: - assert_equal(n2[net]['proxy'], '%s:%i' % (self.conf2.addr)) - assert_equal(n2[net]['proxy_randomize_credentials'], True) + if net == NET_I2P: + expected_proxy = '' + expected_randomize = False + else: + expected_proxy = '%s:%i' % (self.conf2.addr) + expected_randomize = True + assert_equal(n2[net]['proxy'], expected_proxy) + assert_equal(n2[net]['proxy_randomize_credentials'], expected_randomize) assert_equal(n2['onion']['reachable'], True) + assert_equal(n2['i2p']['reachable'], False) if self.have_ipv6: n3 = networks_dict(self.nodes[3].getnetworkinfo()) assert_equal(NETWORKS, n3.keys()) for net in NETWORKS: - assert_equal(n3[net]['proxy'], '[%s]:%i' % (self.conf3.addr)) + if net == NET_I2P: + expected_proxy = '' + else: + expected_proxy = '[%s]:%i' % (self.conf3.addr) + assert_equal(n3[net]['proxy'], expected_proxy) assert_equal(n3[net]['proxy_randomize_credentials'], False) assert_equal(n3['onion']['reachable'], False) + assert_equal(n3['i2p']['reachable'], False) if __name__ == '__main__': diff --git a/test/functional/feature_rbf.py b/test/functional/feature_rbf.py index c6f55c62b4..945880cc3b 100755 --- a/test/functional/feature_rbf.py +++ b/test/functional/feature_rbf.py @@ -33,12 +33,7 @@ def make_utxo(node, amount, confirmed=True, scriptPubKey=DUMMY_P2WPKH_SCRIPT): txid = node.sendtoaddress(new_addr, satoshi_round((amount+fee)/COIN)) tx1 = node.getrawtransaction(txid, 1) txid = int(txid, 16) - i = None - - for i, txout in enumerate(tx1['vout']): - if txout['scriptPubKey']['addresses'] == [new_addr]: - break - assert i is not None + i, _ = next(filter(lambda vout: new_addr == vout[1]['scriptPubKey']['address'], enumerate(tx1['vout']))) tx2 = CTransaction() tx2.vin = [CTxIn(COutPoint(txid, i))] diff --git a/test/functional/feature_segwit.py b/test/functional/feature_segwit.py index 7bd2fc7847..ad8767556b 100755 --- a/test/functional/feature_segwit.py +++ b/test/functional/feature_segwit.py @@ -523,7 +523,7 @@ class SegWitTest(BitcoinTestFramework): v1_addr = program_to_witness(1, [3, 5]) v1_tx = self.nodes[0].createrawtransaction([getutxo(spendable_txid[0])], {v1_addr: 1}) v1_decoded = self.nodes[1].decoderawtransaction(v1_tx) - assert_equal(v1_decoded['vout'][0]['scriptPubKey']['addresses'][0], v1_addr) + assert_equal(v1_decoded['vout'][0]['scriptPubKey']['address'], v1_addr) assert_equal(v1_decoded['vout'][0]['scriptPubKey']['hex'], "51020305") # Check that spendable outputs are really spendable diff --git a/test/functional/feature_taproot.py b/test/functional/feature_taproot.py index 5027a9828f..183a43abd4 100755 --- a/test/functional/feature_taproot.py +++ b/test/functional/feature_taproot.py @@ -177,17 +177,17 @@ def default_negflag(ctx): """Default expression for "negflag": tap.negflag.""" return get(ctx, "tap").negflag -def default_pubkey_inner(ctx): - """Default expression for "pubkey_inner": tap.inner_pubkey.""" - return get(ctx, "tap").inner_pubkey +def default_pubkey_internal(ctx): + """Default expression for "pubkey_internal": tap.internal_pubkey.""" + return get(ctx, "tap").internal_pubkey def default_merklebranch(ctx): """Default expression for "merklebranch": tapleaf.merklebranch.""" return get(ctx, "tapleaf").merklebranch def default_controlblock(ctx): - """Default expression for "controlblock": combine leafversion, negflag, pubkey_inner, merklebranch.""" - return bytes([get(ctx, "leafversion") + get(ctx, "negflag")]) + get(ctx, "pubkey_inner") + get(ctx, "merklebranch") + """Default expression for "controlblock": combine leafversion, negflag, pubkey_internal, merklebranch.""" + return bytes([get(ctx, "leafversion") + get(ctx, "negflag")]) + get(ctx, "pubkey_internal") + get(ctx, "merklebranch") def default_sighash(ctx): """Default expression for "sighash": depending on mode, compute BIP341, BIP143, or legacy sighash.""" @@ -341,9 +341,9 @@ DEFAULT_CONTEXT = { "tapleaf": default_tapleaf, # The script to push, and include in the sighash, for a taproot script path spend. "script_taproot": default_script_taproot, - # The inner pubkey for a taproot script path spend (32 bytes). - "pubkey_inner": default_pubkey_inner, - # The negation flag of the inner pubkey for a taproot script path spend. + # The internal pubkey for a taproot script path spend (32 bytes). + "pubkey_internal": default_pubkey_internal, + # The negation flag of the internal pubkey for a taproot script path spend. "negflag": default_negflag, # The leaf version to include in the sighash (this does not affect the one in the control block). "leafversion": default_leafversion, @@ -780,8 +780,8 @@ def spenders_taproot_active(): add_spender(spenders, "spendpath/negflag", tap=tap, leaf="128deep", **SINGLE_SIG, key=secs[0], failure={"negflag": lambda ctx: 1 - default_negflag(ctx)}, **ERR_WITNESS_PROGRAM_MISMATCH) # Test that bitflips in the Merkle branch invalidate it. add_spender(spenders, "spendpath/bitflipmerkle", tap=tap, leaf="128deep", **SINGLE_SIG, key=secs[0], failure={"merklebranch": bitflipper(default_merklebranch)}, **ERR_WITNESS_PROGRAM_MISMATCH) - # Test that bitflips in the inner pubkey invalidate it. - add_spender(spenders, "spendpath/bitflippubkey", tap=tap, leaf="128deep", **SINGLE_SIG, key=secs[0], failure={"pubkey_inner": bitflipper(default_pubkey_inner)}, **ERR_WITNESS_PROGRAM_MISMATCH) + # Test that bitflips in the internal pubkey invalidate it. + add_spender(spenders, "spendpath/bitflippubkey", tap=tap, leaf="128deep", **SINGLE_SIG, key=secs[0], failure={"pubkey_internal": bitflipper(default_pubkey_internal)}, **ERR_WITNESS_PROGRAM_MISMATCH) # Test that empty witnesses are invalid. add_spender(spenders, "spendpath/emptywit", tap=tap, leaf="128deep", **SINGLE_SIG, key=secs[0], failure={"witness": []}, **ERR_EMPTY_WITNESS) # Test that adding garbage to the control block invalidates it. 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/interface_rpc.py b/test/functional/interface_rpc.py index 9c877aaeae..4d5666f414 100755 --- a/test/functional/interface_rpc.py +++ b/test/functional/interface_rpc.py @@ -8,6 +8,9 @@ import os from test_framework.authproxy import JSONRPCException from test_framework.test_framework import BitcoinTestFramework from test_framework.util import assert_equal, assert_greater_than_or_equal +from threading import Thread +import subprocess + def expect_http_status(expected_http_status, expected_rpc_code, fcn, *args): @@ -18,6 +21,16 @@ def expect_http_status(expected_http_status, expected_rpc_code, assert_equal(exc.error["code"], expected_rpc_code) assert_equal(exc.http_status, expected_http_status) + +def test_work_queue_getblock(node, got_exceeded_error): + while not got_exceeded_error: + try: + node.cli('getrpcinfo').send_cli() + except subprocess.CalledProcessError as e: + assert_equal(e.output, 'error: Server response: Work queue depth exceeded\n') + got_exceeded_error.append(True) + + class RPCInterfaceTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 1 @@ -67,10 +80,23 @@ class RPCInterfaceTest(BitcoinTestFramework): expect_http_status(404, -32601, self.nodes[0].invalidmethod) expect_http_status(500, -8, self.nodes[0].getblockhash, 42) + def test_work_queue_exceeded(self): + self.log.info("Testing work queue exceeded...") + self.restart_node(0, ['-rpcworkqueue=1', '-rpcthreads=1']) + got_exceeded_error = [] + threads = [] + for _ in range(3): + t = Thread(target=test_work_queue_getblock, args=(self.nodes[0], got_exceeded_error)) + t.start() + threads.append(t) + for t in threads: + t.join() + def run_test(self): self.test_getrpcinfo() self.test_batch_request() self.test_http_status_codes() + self.test_work_queue_exceeded() if __name__ == '__main__': diff --git a/test/functional/interface_zmq.py b/test/functional/interface_zmq.py index d0967a9340..94e162b748 100755 --- a/test/functional/interface_zmq.py +++ b/test/functional/interface_zmq.py @@ -62,6 +62,31 @@ class ZMQSubscriber: return (hash, label, mempool_sequence) +class ZMQTestSetupBlock: + """Helper class for setting up a ZMQ test via the "sync up" procedure. + Generates a block on the specified node on instantiation and provides a + method to check whether a ZMQ notification matches, i.e. the event was + caused by this generated block. Assumes that a notification either contains + the generated block's hash, it's (coinbase) transaction id, the raw block or + raw transaction data. + """ + + def __init__(self, node): + self.block_hash = node.generate(1)[0] + coinbase = node.getblock(self.block_hash, 2)['tx'][0] + self.tx_hash = coinbase['txid'] + self.raw_tx = coinbase['hex'] + self.raw_block = node.getblock(self.block_hash, 0) + + def caused_notification(self, notification): + return ( + self.block_hash in notification + or self.tx_hash in notification + or self.raw_block in notification + or self.raw_tx in notification + ) + + class ZMQTest (BitcoinTestFramework): def set_test_params(self): self.num_nodes = 2 @@ -105,17 +130,18 @@ class ZMQTest (BitcoinTestFramework): # Ensure that all zmq publisher notification interfaces are ready by # running the following "sync up" procedure: # 1. Generate a block on the node - # 2. Try to receive a notification on all subscribers - # 3. If all subscribers get a message within the timeout (1 second), + # 2. Try to receive the corresponding notification on all subscribers + # 3. If all subscribers get the message within the timeout (1 second), # we are done, otherwise repeat starting from step 1 for sub in subscribers: sub.socket.set(zmq.RCVTIMEO, 1000) while True: - self.nodes[0].generate(1) + test_block = ZMQTestSetupBlock(self.nodes[0]) recv_failed = False for sub in subscribers: try: - sub.receive() + while not test_block.caused_notification(sub.receive().hex()): + self.log.debug("Ignoring sync-up notification for previously generated block.") except zmq.error.Again: self.log.debug("Didn't receive sync-up notification, trying again.") recv_failed = True @@ -340,7 +366,7 @@ class ZMQTest (BitcoinTestFramework): block_count = self.nodes[0].getblockcount() best_hash = self.nodes[0].getbestblockhash() self.nodes[0].invalidateblock(best_hash) - sleep(2) # Bit of room to make sure transaction things happened + sleep(2) # Bit of room to make sure transaction things happened # Make sure getrawmempool mempool_sequence results aren't "queued" but immediately reflective # of the time they were gathered. @@ -389,8 +415,8 @@ class ZMQTest (BitcoinTestFramework): assert_equal(label, "A") # More transactions to be simply mined for i in range(len(more_tx)): - assert_equal((more_tx[i], "A", mempool_seq), seq.receive_sequence()) - mempool_seq += 1 + assert_equal((more_tx[i], "A", mempool_seq), seq.receive_sequence()) + mempool_seq += 1 # Bumped by rbf assert_equal((orig_txid, "R", mempool_seq), seq.receive_sequence()) mempool_seq += 1 @@ -405,7 +431,7 @@ class ZMQTest (BitcoinTestFramework): assert_equal((orig_txid_2, "A", mempool_seq), seq.receive_sequence()) mempool_seq += 1 self.nodes[0].generatetoaddress(1, ADDRESS_BCRT1_UNSPENDABLE) - self.sync_all() # want to make sure we didn't break "consensus" for other tests + self.sync_all() # want to make sure we didn't break "consensus" for other tests def test_mempool_sync(self): """ diff --git a/test/functional/mempool_package_onemore.py b/test/functional/mempool_package_onemore.py index a9e2b000fb..884a2fef11 100755 --- a/test/functional/mempool_package_onemore.py +++ b/test/functional/mempool_package_onemore.py @@ -80,7 +80,7 @@ class MempoolPackagesTest(BitcoinTestFramework): self.chain_transaction(self.nodes[0], [second_chain], [0], second_chain_value, fee, 1) # Make sure we can RBF the chain which used our carve-out rule - second_tx_outputs = {self.nodes[0].getrawtransaction(replacable_txid, True)["vout"][0]['scriptPubKey']['addresses'][0]: replacable_orig_value - (Decimal(1) / Decimal(100))} + second_tx_outputs = {self.nodes[0].getrawtransaction(replacable_txid, True)["vout"][0]['scriptPubKey']['address']: replacable_orig_value - (Decimal(1) / Decimal(100))} second_tx = self.nodes[0].createrawtransaction([{'txid': chain[0][0], 'vout': 1}], second_tx_outputs) signed_second_tx = self.nodes[0].signrawtransactionwithwallet(second_tx) self.nodes[0].sendrawtransaction(signed_second_tx['hex']) diff --git a/test/functional/p2p_feefilter.py b/test/functional/p2p_feefilter.py index a2a122b352..52dc4de3bd 100755 --- a/test/functional/p2p_feefilter.py +++ b/test/functional/p2p_feefilter.py @@ -61,6 +61,7 @@ class FeeFilterTest(BitcoinTestFramework): def run_test(self): self.test_feefilter_forcerelay() self.test_feefilter() + self.test_feefilter_blocksonly() def test_feefilter_forcerelay(self): self.log.info('Check that peers without forcerelay permission (default) get a feefilter message') @@ -119,6 +120,19 @@ class FeeFilterTest(BitcoinTestFramework): conn.wait_for_invs_to_match(txids) conn.clear_invs() + def test_feefilter_blocksonly(self): + """Test that we don't send fee filters to block-relay-only peers and when we're in blocksonly mode.""" + self.log.info("Check that we don't send fee filters to block-relay-only peers.") + feefilter_peer = self.nodes[0].add_outbound_p2p_connection(FeefilterConn(), p2p_idx=0, connection_type="block-relay-only") + feefilter_peer.sync_with_ping() + feefilter_peer.assert_feefilter_received(False) + + self.log.info("Check that we don't send fee filters when in blocksonly mode.") + self.restart_node(0, ["-blocksonly"]) + feefilter_peer = self.nodes[0].add_p2p_connection(FeefilterConn()) + feefilter_peer.sync_with_ping() + feefilter_peer.assert_feefilter_received(False) + if __name__ == '__main__': FeeFilterTest().main() diff --git a/test/functional/p2p_filter.py b/test/functional/p2p_filter.py index 8f64419138..4bee33f825 100755 --- a/test/functional/p2p_filter.py +++ b/test/functional/p2p_filter.py @@ -130,7 +130,7 @@ class FilterTest(BitcoinTestFramework): filter_peer = P2PBloomFilter() self.log.debug("Create a tx relevant to the peer before connecting") - filter_address = self.nodes[0].decodescript(filter_peer.watch_script_pubkey)['addresses'][0] + filter_address = self.nodes[0].decodescript(filter_peer.watch_script_pubkey)['address'] txid = self.nodes[0].sendtoaddress(filter_address, 90) self.log.debug("Send a mempool msg after connecting and check that the tx is received") @@ -142,7 +142,7 @@ class FilterTest(BitcoinTestFramework): def test_frelay_false(self, filter_peer): self.log.info("Check that a node with fRelay set to false does not receive invs until the filter is set") filter_peer.tx_received = False - filter_address = self.nodes[0].decodescript(filter_peer.watch_script_pubkey)['addresses'][0] + filter_address = self.nodes[0].decodescript(filter_peer.watch_script_pubkey)['address'] self.nodes[0].sendtoaddress(filter_address, 90) # Sync to make sure the reason filter_peer doesn't receive the tx is not p2p delays filter_peer.sync_with_ping() @@ -156,7 +156,7 @@ class FilterTest(BitcoinTestFramework): filter_peer.send_and_ping(filter_peer.watch_filter_init) # If fRelay is not already True, sending filterload sets it to True assert self.nodes[0].getpeerinfo()[0]['relaytxes'] - filter_address = self.nodes[0].decodescript(filter_peer.watch_script_pubkey)['addresses'][0] + filter_address = self.nodes[0].decodescript(filter_peer.watch_script_pubkey)['address'] self.log.info('Check that we receive merkleblock and tx if the filter matches a tx in a block') block_hash = self.nodes[0].generatetoaddress(1, filter_address)[0] diff --git a/test/functional/p2p_invalid_tx.py b/test/functional/p2p_invalid_tx.py index cca7390ae3..8783c244c3 100755 --- a/test/functional/p2p_invalid_tx.py +++ b/test/functional/p2p_invalid_tx.py @@ -154,7 +154,7 @@ class InvalidTxRequestTest(BitcoinTestFramework): orphan_tx_pool[i].vin.append(CTxIn(outpoint=COutPoint(i, 333))) orphan_tx_pool[i].vout.append(CTxOut(nValue=11 * COIN, scriptPubKey=SCRIPT_PUB_KEY_OP_TRUE)) - with node.assert_debug_log(['mapOrphan overflow, removed 1 tx']): + with node.assert_debug_log(['orphanage overflow, removed 1 tx']): node.p2ps[0].send_txs_and_test(orphan_tx_pool, node, success=False) rejected_parent = CTransaction() diff --git a/test/functional/p2p_leak.py b/test/functional/p2p_leak.py index 12b8b7baff..71d5ca92b3 100755 --- a/test/functional/p2p_leak.py +++ b/test/functional/p2p_leak.py @@ -4,8 +4,8 @@ # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test message sending before handshake completion. -A node should never send anything other than VERSION/VERACK until it's -received a VERACK. +Before receiving a VERACK, a node should not send anything but VERSION/VERACK +and feature negotiation messages (WTXIDRELAY, SENDADDRV2). This test connects to a node and sends it a few messages, trying to entice it into sending us something it shouldn't.""" @@ -35,10 +35,12 @@ class LazyPeer(P2PInterface): super().__init__() self.unexpected_msg = False self.ever_connected = False + self.got_wtxidrelay = False + self.got_sendaddrv2 = False def bad_message(self, message): self.unexpected_msg = True - self.log.info("should not have received message: %s" % message.msgtype) + print("should not have received message: %s" % message.msgtype) def on_open(self): self.ever_connected = True @@ -64,6 +66,8 @@ class LazyPeer(P2PInterface): def on_cmpctblock(self, message): self.bad_message(message) def on_getblocktxn(self, message): self.bad_message(message) def on_blocktxn(self, message): self.bad_message(message) + def on_wtxidrelay(self, message): self.got_wtxidrelay = True + def on_sendaddrv2(self, message): self.got_sendaddrv2 = True # Peer that sends a version but not a verack. @@ -94,32 +98,61 @@ class P2PVersionStore(P2PInterface): class P2PLeakTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 1 + self.extra_args = [['-peertimeout=4']] + + def create_old_version(self, nversion): + old_version_msg = msg_version() + old_version_msg.nVersion = nversion + old_version_msg.strSubVer = P2P_SUBVERSION + old_version_msg.nServices = P2P_SERVICES + old_version_msg.relay = P2P_VERSION_RELAY + return old_version_msg def run_test(self): - # Another peer that never sends a version, nor any other messages. It shouldn't receive anything from the node. + self.log.info('Check that the node doesn\'t send unexpected messages before handshake completion') + # Peer that never sends a version, nor any other messages. It shouldn't receive anything from the node. no_version_idle_peer = self.nodes[0].add_p2p_connection(LazyPeer(), send_version=False, wait_for_verack=False) # Peer that sends a version but not a verack. no_verack_idle_peer = self.nodes[0].add_p2p_connection(NoVerackIdlePeer(), wait_for_verack=False) - # Wait until we got the verack in response to the version. Though, don't wait for the node to receive the - # verack, since we never sent one + # Pre-wtxidRelay peer that sends a version but not a verack and does not support feature negotiation + # messages which start at nVersion == 70016 + pre_wtxidrelay_peer = self.nodes[0].add_p2p_connection(NoVerackIdlePeer(), send_version=False, wait_for_verack=False) + pre_wtxidrelay_peer.send_message(self.create_old_version(70015)) + + # Wait until the peer gets the verack in response to the version. Though, don't wait for the node to receive the + # verack, since the peer never sent one no_verack_idle_peer.wait_for_verack() + pre_wtxidrelay_peer.wait_for_verack() no_version_idle_peer.wait_until(lambda: no_version_idle_peer.ever_connected) no_verack_idle_peer.wait_until(lambda: no_verack_idle_peer.version_received) + pre_wtxidrelay_peer.wait_until(lambda: pre_wtxidrelay_peer.version_received) # Mine a block and make sure that it's not sent to the connected peers self.nodes[0].generate(nblocks=1) - #Give the node enough time to possibly leak out a message + # Give the node enough time to possibly leak out a message time.sleep(5) - self.nodes[0].disconnect_p2ps() + # Make sure only expected messages came in + assert not no_version_idle_peer.unexpected_msg + assert not no_version_idle_peer.got_wtxidrelay + assert not no_version_idle_peer.got_sendaddrv2 - # Make sure no unexpected messages came in - assert no_version_idle_peer.unexpected_msg == False - assert no_verack_idle_peer.unexpected_msg == False + assert not no_verack_idle_peer.unexpected_msg + assert no_verack_idle_peer.got_wtxidrelay + assert no_verack_idle_peer.got_sendaddrv2 + + assert not pre_wtxidrelay_peer.unexpected_msg + assert not pre_wtxidrelay_peer.got_wtxidrelay + assert not pre_wtxidrelay_peer.got_sendaddrv2 + + # Expect peers to be disconnected due to timeout + assert not no_version_idle_peer.is_connected + assert not no_verack_idle_peer.is_connected + assert not pre_wtxidrelay_peer.is_connected self.log.info('Check that the version message does not leak the local address of the node') p2p_version_store = self.nodes[0].add_p2p_connection(P2PVersionStore()) @@ -134,13 +167,8 @@ class P2PLeakTest(BitcoinTestFramework): self.log.info('Check that old peers are disconnected') p2p_old_peer = self.nodes[0].add_p2p_connection(P2PInterface(), send_version=False, wait_for_verack=False) - old_version_msg = msg_version() - old_version_msg.nVersion = 31799 - old_version_msg.strSubVer = P2P_SUBVERSION - old_version_msg.nServices = P2P_SERVICES - old_version_msg.relay = P2P_VERSION_RELAY - with self.nodes[0].assert_debug_log(['peer=3 using obsolete version 31799; disconnecting']): - p2p_old_peer.send_message(old_version_msg) + with self.nodes[0].assert_debug_log(['peer=4 using obsolete version 31799; disconnecting']): + p2p_old_peer.send_message(self.create_old_version(31799)) p2p_old_peer.wait_for_disconnect() diff --git a/test/functional/rpc_addresses_deprecation.py b/test/functional/rpc_addresses_deprecation.py new file mode 100644 index 0000000000..bc0559f3b5 --- /dev/null +++ b/test/functional/rpc_addresses_deprecation.py @@ -0,0 +1,58 @@ +#!/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 deprecation of reqSigs and addresses RPC fields.""" + +from io import BytesIO + +from test_framework.messages import CTransaction +from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import ( + assert_equal, + hex_str_to_bytes +) + + +class AddressesDeprecationTest(BitcoinTestFramework): + def set_test_params(self): + self.num_nodes = 2 + self.extra_args = [[], ["-deprecatedrpc=addresses"]] + + def skip_test_if_missing_module(self): + self.skip_if_no_wallet() + + def run_test(self): + self.test_addresses_deprecation() + + def test_addresses_deprecation(self): + node = self.nodes[0] + coin = node.listunspent().pop() + + inputs = [{'txid': coin['txid'], 'vout': coin['vout']}] + outputs = {node.getnewaddress(): 0.99} + raw = node.createrawtransaction(inputs, outputs) + signed = node.signrawtransactionwithwallet(raw)['hex'] + + # This transaction is derived from test/util/data/txcreatemultisig1.json + tx = CTransaction() + tx.deserialize(BytesIO(hex_str_to_bytes(signed))) + tx.vout[0].scriptPubKey = hex_str_to_bytes("522102a5613bd857b7048924264d1e70e08fb2a7e6527d32b7ab1bb993ac59964ff39721021ac43c7ff740014c3b33737ede99c967e4764553d1b2b83db77c83b8715fa72d2102df2089105c77f266fa11a9d33f05c735234075f2e8780824c6b709415f9fb48553ae") + tx_signed = node.signrawtransactionwithwallet(tx.serialize().hex())['hex'] + txid = node.sendrawtransaction(hexstring=tx_signed, maxfeerate=0) + + self.log.info("Test RPCResult scriptPubKey no longer returns the fields addresses or reqSigs by default") + hash = node.generateblock(output=node.getnewaddress(), transactions=[txid])['hash'] + # Ensure both nodes have the newly generated block on disk. + self.sync_blocks() + script_pub_key = node.getblock(blockhash=hash, verbose=2)['tx'][-1]['vout'][0]['scriptPubKey'] + assert 'addresses' not in script_pub_key and 'reqSigs' not in script_pub_key + + self.log.info("Test RPCResult scriptPubKey returns the addresses field with -deprecatedrpc=addresses") + script_pub_key = self.nodes[1].getblock(blockhash=hash, verbose=2)['tx'][-1]['vout'][0]['scriptPubKey'] + assert_equal(script_pub_key['addresses'], ['mvKDK6D54HU8wQumJBLHY95eq5iHFqXSBz', 'mv3rHCQSwKp2BLSuMHD8uCS32LW5xiNAA5', 'mirrsyhAQYzo5CwVhcaYJKwUJu1WJRCRJe']) + assert_equal(script_pub_key['reqSigs'], 2) + + +if __name__ == "__main__": + AddressesDeprecationTest().main() diff --git a/test/functional/rpc_createmultisig.py b/test/functional/rpc_createmultisig.py index 31baeba582..19f0d5765a 100755 --- a/test/functional/rpc_createmultisig.py +++ b/test/functional/rpc_createmultisig.py @@ -165,7 +165,7 @@ class RpcCreateMultiSigTest(BitcoinTestFramework): txid = node0.sendtoaddress(madd, 40) tx = node0.getrawtransaction(txid, True) - vout = [v["n"] for v in tx["vout"] if madd in v["scriptPubKey"].get("addresses", [])] + vout = [v["n"] for v in tx["vout"] if madd == v["scriptPubKey"]["address"]] assert len(vout) == 1 vout = vout[0] scriptPubKey = tx["vout"][vout]["scriptPubKey"]["hex"] diff --git a/test/functional/rpc_fundrawtransaction.py b/test/functional/rpc_fundrawtransaction.py index 569471dc87..5129ecb895 100755 --- a/test/functional/rpc_fundrawtransaction.py +++ b/test/functional/rpc_fundrawtransaction.py @@ -32,6 +32,7 @@ class RawTransactionsTest(BitcoinTestFramework): # This test isn't testing tx relay. Set whitelist on the peers for # instant tx relay. self.extra_args = [['-whitelist=noban@127.0.0.1']] * self.num_nodes + self.rpc_timeout = 90 # to prevent timeouts in `test_transaction_too_large` def skip_test_if_missing_module(self): self.skip_if_no_wallet() @@ -94,6 +95,7 @@ class RawTransactionsTest(BitcoinTestFramework): self.test_address_reuse() self.test_option_subtract_fee_from_outputs() self.test_subtract_fee_with_presets() + self.test_transaction_too_large() def test_change_position(self): """Ensure setting changePosition in fundraw with an exact match is handled properly.""" @@ -246,7 +248,7 @@ class RawTransactionsTest(BitcoinTestFramework): rawtxfund = self.nodes[2].fundrawtransaction(rawtx, {'changeAddress': change, 'changePosition': 0}) dec_tx = self.nodes[2].decoderawtransaction(rawtxfund['hex']) out = dec_tx['vout'][0] - assert_equal(change, out['scriptPubKey']['addresses'][0]) + assert_equal(change, out['scriptPubKey']['address']) def test_change_type(self): self.log.info("Test fundrawtxn with a provided change type") @@ -286,7 +288,7 @@ class RawTransactionsTest(BitcoinTestFramework): matchingOuts = 0 for i, out in enumerate(dec_tx['vout']): totalOut += out['value'] - if out['scriptPubKey']['addresses'][0] in outputs: + if out['scriptPubKey']['address'] in outputs: matchingOuts+=1 else: assert_equal(i, rawtxfund['changepos']) @@ -317,7 +319,7 @@ class RawTransactionsTest(BitcoinTestFramework): matchingOuts = 0 for out in dec_tx['vout']: totalOut += out['value'] - if out['scriptPubKey']['addresses'][0] in outputs: + if out['scriptPubKey']['address'] in outputs: matchingOuts+=1 assert_equal(matchingOuts, 1) @@ -351,7 +353,7 @@ class RawTransactionsTest(BitcoinTestFramework): matchingOuts = 0 for out in dec_tx['vout']: totalOut += out['value'] - if out['scriptPubKey']['addresses'][0] in outputs: + if out['scriptPubKey']['address'] in outputs: matchingOuts+=1 assert_equal(matchingOuts, 2) @@ -800,7 +802,7 @@ class RawTransactionsTest(BitcoinTestFramework): changeaddress = "" for out in res_dec['vout']: if out['value'] > 1.0: - changeaddress += out['scriptPubKey']['addresses'][0] + changeaddress += out['scriptPubKey']['address'] assert changeaddress != "" nextaddr = self.nodes[3].getnewaddress() # Now the change address key should be removed from the keypool. @@ -907,5 +909,25 @@ class RawTransactionsTest(BitcoinTestFramework): signedtx = self.nodes[0].signrawtransactionwithwallet(fundedtx['hex']) self.nodes[0].sendrawtransaction(signedtx['hex']) + def test_transaction_too_large(self): + self.log.info("Test fundrawtx where BnB solution would result in a too large transaction, but Knapsack would not") + self.nodes[0].createwallet("large") + wallet = self.nodes[0].get_wallet_rpc(self.default_wallet_name) + recipient = self.nodes[0].get_wallet_rpc("large") + outputs = {} + rawtx = recipient.createrawtransaction([], {wallet.getnewaddress(): 147.99899260}) + + # Make 1500 0.1 BTC outputs. The amount that we target for funding is in + # the BnB range when these outputs are used. However if these outputs + # are selected, the transaction will end up being too large, so it + # shouldn't use BnB and instead fall back to Knapsack but that behavior + # is not implemented yet. For now we just check that we get an error. + for _ in range(1500): + outputs[recipient.getnewaddress()] = 0.1 + wallet.sendmany("", outputs) + self.nodes[0].generate(10) + assert_raises_rpc_error(-4, "Transaction too large", recipient.fundrawtransaction, rawtx) + + if __name__ == '__main__': RawTransactionsTest().main() diff --git a/test/functional/rpc_generateblock.py b/test/functional/rpc_generateblock.py index 08ff0fba50..7424416484 100755 --- a/test/functional/rpc_generateblock.py +++ b/test/functional/rpc_generateblock.py @@ -27,13 +27,13 @@ class GenerateBlockTest(BitcoinTestFramework): hash = node.generateblock(output=address, transactions=[])['hash'] block = node.getblock(blockhash=hash, verbose=2) assert_equal(len(block['tx']), 1) - assert_equal(block['tx'][0]['vout'][0]['scriptPubKey']['addresses'][0], address) + assert_equal(block['tx'][0]['vout'][0]['scriptPubKey']['address'], address) self.log.info('Generate an empty block to a descriptor') hash = node.generateblock('addr(' + address + ')', [])['hash'] block = node.getblock(blockhash=hash, verbosity=2) assert_equal(len(block['tx']), 1) - assert_equal(block['tx'][0]['vout'][0]['scriptPubKey']['addresses'][0], address) + assert_equal(block['tx'][0]['vout'][0]['scriptPubKey']['address'], address) self.log.info('Generate an empty block to a combo descriptor with compressed pubkey') combo_key = '0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798' @@ -41,7 +41,7 @@ class GenerateBlockTest(BitcoinTestFramework): hash = node.generateblock('combo(' + combo_key + ')', [])['hash'] block = node.getblock(hash, 2) assert_equal(len(block['tx']), 1) - assert_equal(block['tx'][0]['vout'][0]['scriptPubKey']['addresses'][0], combo_address) + assert_equal(block['tx'][0]['vout'][0]['scriptPubKey']['address'], combo_address) self.log.info('Generate an empty block to a combo descriptor with uncompressed pubkey') combo_key = '0408ef68c46d20596cc3f6ddf7c8794f71913add807f1dc55949fa805d764d191c0b7ce6894c126fce0babc6663042f3dde9b0cf76467ea315514e5a6731149c67' @@ -49,7 +49,7 @@ class GenerateBlockTest(BitcoinTestFramework): hash = node.generateblock('combo(' + combo_key + ')', [])['hash'] block = node.getblock(hash, 2) assert_equal(len(block['tx']), 1) - assert_equal(block['tx'][0]['vout'][0]['scriptPubKey']['addresses'][0], combo_address) + assert_equal(block['tx'][0]['vout'][0]['scriptPubKey']['address'], combo_address) # Generate 110 blocks to spend node.generatetoaddress(110, address) diff --git a/test/functional/rpc_getblockfilter.py b/test/functional/rpc_getblockfilter.py index 044dbd35bf..a99e50f29f 100755 --- a/test/functional/rpc_getblockfilter.py +++ b/test/functional/rpc_getblockfilter.py @@ -54,5 +54,11 @@ class GetBlockFilterTest(BitcoinTestFramework): genesis_hash = self.nodes[0].getblockhash(0) assert_raises_rpc_error(-5, "Unknown filtertype", self.nodes[0].getblockfilter, genesis_hash, "unknown") + # Test getblockfilter fails on node without compact block filter index + self.restart_node(0, extra_args=["-blockfilterindex=0"]) + for filter_type in FILTER_TYPES: + assert_raises_rpc_error(-1, "Index is not enabled for filtertype {}".format(filter_type), + self.nodes[0].getblockfilter, genesis_hash, filter_type) + if __name__ == '__main__': GetBlockFilterTest().main() diff --git a/test/functional/rpc_invalid_address_message.py b/test/functional/rpc_invalid_address_message.py index 469d6bdb05..e362642f0f 100755 --- a/test/functional/rpc_invalid_address_message.py +++ b/test/functional/rpc_invalid_address_message.py @@ -12,8 +12,12 @@ from test_framework.util import ( ) BECH32_VALID = 'bcrt1qtmp74ayg7p24uslctssvjm06q5phz4yrxucgnv' -BECH32_INVALID_SIZE = 'bcrt1sqqpl9r5c' -BECH32_INVALID_PREFIX = 'bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4' +BECH32_INVALID_BECH32 = 'bcrt1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vqdmchcc' +BECH32_INVALID_BECH32M = 'bcrt1qw508d6qejxtdg4y5r3zarvary0c5xw7k35mrzd' +BECH32_INVALID_VERSION = 'bcrt130xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vqynjegk' +BECH32_INVALID_SIZE = 'bcrt1s0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7v8n0nx0muaewav25430mtr' +BECH32_INVALID_V0_SIZE = 'bcrt1qw508d6qejxtdg4y5r3zarvary0c5xw7kqqq5k3my' +BECH32_INVALID_PREFIX = 'bc1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7k7grplx' BASE58_VALID = 'mipcBbFg9gMiCh81Kj8tqqdgoZub1ZJRfn' BASE58_INVALID_PREFIX = '17VZNX1SN5NtKa8UQFxwQbFeFc3iqRYhem' @@ -40,6 +44,18 @@ class InvalidAddressErrorMessageTest(BitcoinTestFramework): assert not info['isvalid'] assert_equal(info['error'], 'Invalid prefix for Bech32 address') + info = node.validateaddress(BECH32_INVALID_BECH32) + assert not info['isvalid'] + assert_equal(info['error'], 'Version 1+ witness address must use Bech32m checksum') + + info = node.validateaddress(BECH32_INVALID_BECH32M) + assert not info['isvalid'] + assert_equal(info['error'], 'Version 0 witness address must use Bech32 checksum') + + info = node.validateaddress(BECH32_INVALID_V0_SIZE) + assert not info['isvalid'] + assert_equal(info['error'], 'Invalid Bech32 v0 address data size') + info = node.validateaddress(BECH32_VALID) assert info['isvalid'] assert 'error' not in info diff --git a/test/functional/rpc_net.py b/test/functional/rpc_net.py index 2d41963beb..9adb32c3c5 100755 --- a/test/functional/rpc_net.py +++ b/test/functional/rpc_net.py @@ -105,7 +105,7 @@ class NetTest(BitcoinTestFramework): assert_equal(peer_info[1][1]['connection_type'], 'inbound') # Check dynamically generated networks list in getpeerinfo help output. - assert "(ipv4, ipv6, onion, not_publicly_routable)" in self.nodes[0].help("getpeerinfo") + assert "(ipv4, ipv6, onion, i2p, not_publicly_routable)" in self.nodes[0].help("getpeerinfo") def test_getnettotals(self): self.log.info("Test getnettotals") @@ -156,7 +156,7 @@ class NetTest(BitcoinTestFramework): assert_net_servicesnames(int(info["localservices"], 0x10), info["localservicesnames"]) # Check dynamically generated networks list in getnetworkinfo help output. - assert "(ipv4, ipv6, onion)" in self.nodes[0].help("getnetworkinfo") + assert "(ipv4, ipv6, onion, i2p)" in self.nodes[0].help("getnetworkinfo") def test_getaddednodeinfo(self): self.log.info("Test getaddednodeinfo") diff --git a/test/functional/rpc_psbt.py b/test/functional/rpc_psbt.py index ed6abaed78..079a3bd3ba 100755 --- a/test/functional/rpc_psbt.py +++ b/test/functional/rpc_psbt.py @@ -158,17 +158,17 @@ class PSBTTest(BitcoinTestFramework): p2sh_p2wpkh_pos = -1 decoded = self.nodes[0].decoderawtransaction(signed_tx) for out in decoded['vout']: - if out['scriptPubKey']['addresses'][0] == p2sh: + if out['scriptPubKey']['address'] == p2sh: p2sh_pos = out['n'] - elif out['scriptPubKey']['addresses'][0] == p2wsh: + elif out['scriptPubKey']['address'] == p2wsh: p2wsh_pos = out['n'] - elif out['scriptPubKey']['addresses'][0] == p2wpkh: + elif out['scriptPubKey']['address'] == p2wpkh: p2wpkh_pos = out['n'] - elif out['scriptPubKey']['addresses'][0] == p2sh_p2wsh: + elif out['scriptPubKey']['address'] == p2sh_p2wsh: p2sh_p2wsh_pos = out['n'] - elif out['scriptPubKey']['addresses'][0] == p2sh_p2wpkh: + elif out['scriptPubKey']['address'] == p2sh_p2wpkh: p2sh_p2wpkh_pos = out['n'] - elif out['scriptPubKey']['addresses'][0] == p2pkh: + elif out['scriptPubKey']['address'] == p2pkh: p2pkh_pos = out['n'] inputs = [{"txid": txid, "vout": p2wpkh_pos}, {"txid": txid, "vout": p2sh_p2wpkh_pos}, {"txid": txid, "vout": p2pkh_pos}] diff --git a/test/functional/rpc_signmessage.py b/test/functional/rpc_signmessage.py index 0cb3ce4215..1c71732a61 100755 --- a/test/functional/rpc_signmessage.py +++ b/test/functional/rpc_signmessage.py @@ -5,7 +5,10 @@ """Test RPC commands for signing and verifying messages.""" from test_framework.test_framework import BitcoinTestFramework -from test_framework.util import assert_equal +from test_framework.util import ( + assert_equal, + assert_raises_rpc_error, +) class SignMessagesTest(BitcoinTestFramework): def set_test_params(self): @@ -38,5 +41,22 @@ class SignMessagesTest(BitcoinTestFramework): assert not self.nodes[0].verifymessage(other_address, signature, message) assert not self.nodes[0].verifymessage(address, other_signature, message) + self.log.info('test parameter validity and error codes') + # signmessage(withprivkey) have two required parameters + for num_params in [0, 1, 3, 4, 5]: + param_list = ["dummy"]*num_params + assert_raises_rpc_error(-1, "signmessagewithprivkey", self.nodes[0].signmessagewithprivkey, *param_list) + assert_raises_rpc_error(-1, "signmessage", self.nodes[0].signmessage, *param_list) + # verifymessage has three required parameters + for num_params in [0, 1, 2, 4, 5]: + param_list = ["dummy"]*num_params + assert_raises_rpc_error(-1, "verifymessage", self.nodes[0].verifymessage, *param_list) + # invalid key or address provided + assert_raises_rpc_error(-5, "Invalid private key", self.nodes[0].signmessagewithprivkey, "invalid_key", message) + assert_raises_rpc_error(-5, "Invalid address", self.nodes[0].signmessage, "invalid_addr", message) + assert_raises_rpc_error(-5, "Invalid address", self.nodes[0].verifymessage, "invalid_addr", signature, message) + # malformed signature provided + assert_raises_rpc_error(-3, "Malformed base64 encoding", self.nodes[0].verifymessage, self.nodes[0].getnewaddress(), "invalid_sig", message) + if __name__ == '__main__': SignMessagesTest().main() 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/script.py b/test/functional/test_framework/script.py index c35533698c..3c9b8a6e69 100644 --- a/test/functional/test_framework/script.py +++ b/test/functional/test_framework/script.py @@ -826,11 +826,11 @@ def taproot_tree_helper(scripts): # A TaprootInfo object has the following fields: # - scriptPubKey: the scriptPubKey (witness v1 CScript) -# - inner_pubkey: the inner pubkey (32 bytes) -# - negflag: whether the pubkey in the scriptPubKey was negated from inner_pubkey+tweak*G (bool). +# - internal_pubkey: the internal pubkey (32 bytes) +# - negflag: whether the pubkey in the scriptPubKey was negated from internal_pubkey+tweak*G (bool). # - tweak: the tweak (32 bytes) # - leaves: a dict of name -> TaprootLeafInfo objects for all known leaves -TaprootInfo = namedtuple("TaprootInfo", "scriptPubKey,inner_pubkey,negflag,tweak,leaves") +TaprootInfo = namedtuple("TaprootInfo", "scriptPubKey,internal_pubkey,negflag,tweak,leaves") # A TaprootLeafInfo object has the following fields: # - script: the leaf script (CScript or bytes) diff --git a/test/functional/test_framework/segwit_addr.py b/test/functional/test_framework/segwit_addr.py index 00c0d8a919..861ca2b949 100644 --- a/test/functional/test_framework/segwit_addr.py +++ b/test/functional/test_framework/segwit_addr.py @@ -2,10 +2,18 @@ # Copyright (c) 2017 Pieter Wuille # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. -"""Reference implementation for Bech32 and segwit addresses.""" +"""Reference implementation for Bech32/Bech32m and segwit addresses.""" import unittest +from enum import Enum CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l" +BECH32_CONST = 1 +BECH32M_CONST = 0x2bc830a3 + +class Encoding(Enum): + """Enumeration type to list the various supported encodings.""" + BECH32 = 1 + BECH32M = 2 def bech32_polymod(values): @@ -27,38 +35,45 @@ def bech32_hrp_expand(hrp): def bech32_verify_checksum(hrp, data): """Verify a checksum given HRP and converted data characters.""" - return bech32_polymod(bech32_hrp_expand(hrp) + data) == 1 - + check = bech32_polymod(bech32_hrp_expand(hrp) + data) + if check == BECH32_CONST: + return Encoding.BECH32 + elif check == BECH32M_CONST: + return Encoding.BECH32M + else: + return None -def bech32_create_checksum(hrp, data): +def bech32_create_checksum(encoding, hrp, data): """Compute the checksum values given HRP and data.""" values = bech32_hrp_expand(hrp) + data - polymod = bech32_polymod(values + [0, 0, 0, 0, 0, 0]) ^ 1 + const = BECH32M_CONST if encoding == Encoding.BECH32M else BECH32_CONST + polymod = bech32_polymod(values + [0, 0, 0, 0, 0, 0]) ^ const return [(polymod >> 5 * (5 - i)) & 31 for i in range(6)] -def bech32_encode(hrp, data): - """Compute a Bech32 string given HRP and data values.""" - combined = data + bech32_create_checksum(hrp, data) +def bech32_encode(encoding, hrp, data): + """Compute a Bech32 or Bech32m string given HRP and data values.""" + combined = data + bech32_create_checksum(encoding, hrp, data) return hrp + '1' + ''.join([CHARSET[d] for d in combined]) def bech32_decode(bech): - """Validate a Bech32 string, and determine HRP and data.""" + """Validate a Bech32/Bech32m string, and determine HRP and data.""" if ((any(ord(x) < 33 or ord(x) > 126 for x in bech)) or (bech.lower() != bech and bech.upper() != bech)): - return (None, None) + return (None, None, None) bech = bech.lower() pos = bech.rfind('1') if pos < 1 or pos + 7 > len(bech) or len(bech) > 90: - return (None, None) + return (None, None, None) if not all(x in CHARSET for x in bech[pos+1:]): - return (None, None) + return (None, None, None) hrp = bech[:pos] data = [CHARSET.find(x) for x in bech[pos+1:]] - if not bech32_verify_checksum(hrp, data): - return (None, None) - return (hrp, data[:-6]) + encoding = bech32_verify_checksum(hrp, data) + if encoding is None: + return (None, None, None) + return (encoding, hrp, data[:-6]) def convertbits(data, frombits, tobits, pad=True): @@ -86,7 +101,7 @@ def convertbits(data, frombits, tobits, pad=True): def decode_segwit_address(hrp, addr): """Decode a segwit address.""" - hrpgot, data = bech32_decode(addr) + encoding, hrpgot, data = bech32_decode(addr) if hrpgot != hrp: return (None, None) decoded = convertbits(data[1:], 5, 8, False) @@ -96,12 +111,15 @@ def decode_segwit_address(hrp, addr): return (None, None) if data[0] == 0 and len(decoded) != 20 and len(decoded) != 32: return (None, None) + if (data[0] == 0 and encoding != Encoding.BECH32) or (data[0] != 0 and encoding != Encoding.BECH32M): + return (None, None) return (data[0], decoded) def encode_segwit_address(hrp, witver, witprog): """Encode a segwit address.""" - ret = bech32_encode(hrp, [witver] + convertbits(witprog, 8, 5)) + encoding = Encoding.BECH32 if witver == 0 else Encoding.BECH32M + ret = bech32_encode(encoding, hrp, [witver] + convertbits(witprog, 8, 5)) if decode_segwit_address(hrp, ret) == (None, None): return None return ret @@ -119,3 +137,5 @@ class TestFrameworkScript(unittest.TestCase): # P2WSH test_python_bech32('bcrt1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq3xueyj') test_python_bech32('bcrt1qft5p2uhsdcdc3l2ua4ap5qqfg4pjaqlp250x7us7a8qqhrxrxfsqseac85') + # P2TR + test_python_bech32('bcrt1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vqc8gma6') diff --git a/test/functional/test_framework/util.py b/test/functional/test_framework/util.py index d335d4ea79..5c774934be 100644 --- a/test/functional/test_framework/util.py +++ b/test/functional/test_framework/util.py @@ -543,7 +543,7 @@ def find_vout_for_address(node, txid, addr): """ tx = node.getrawtransaction(txid, True) for i in range(len(tx["vout"])): - if any([addr == a for a in tx["vout"][i]["scriptPubKey"]["addresses"]]): + if addr == tx["vout"][i]["scriptPubKey"]["address"]: return i raise RuntimeError("Vout not found for address: txid=%s, addr=%s" % (txid, addr)) 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..001d161612 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', @@ -286,6 +287,7 @@ BASE_SCRIPTS = [ 'feature_config_args.py', 'feature_settings.py', 'rpc_getdescriptorinfo.py', + 'rpc_addresses_deprecation.py', 'rpc_help.py', 'feature_help.py', 'feature_shutdown.py', diff --git a/test/functional/wallet_address_types.py b/test/functional/wallet_address_types.py index 2db5eae33b..b3bee1876d 100755 --- a/test/functional/wallet_address_types.py +++ b/test/functional/wallet_address_types.py @@ -210,7 +210,7 @@ class AddressTypeTest(BitcoinTestFramework): assert_equal(len(tx["vout"]), len(destinations) + 1) # Make sure the destinations are included, and remove them: - output_addresses = [vout['scriptPubKey']['addresses'][0] for vout in tx["vout"]] + output_addresses = [vout['scriptPubKey']['address'] for vout in tx["vout"]] change_addresses = [d for d in output_addresses if d not in destinations] assert_equal(len(change_addresses), 1) diff --git a/test/functional/wallet_avoidreuse.py b/test/functional/wallet_avoidreuse.py index bc4fa90e83..1d3736d9b1 100755 --- a/test/functional/wallet_avoidreuse.py +++ b/test/functional/wallet_avoidreuse.py @@ -253,7 +253,7 @@ class AvoidReuseTest(BitcoinTestFramework): if second_addr_type == "p2sh-segwit": new_fundaddr = fund_decoded["segwit"]["p2sh-segwit"] elif second_addr_type == "bech32": - new_fundaddr = fund_decoded["segwit"]["addresses"][0] + new_fundaddr = fund_decoded["segwit"]["address"] else: new_fundaddr = fundaddr assert_equal(second_addr_type, "legacy") diff --git a/test/functional/wallet_basic.py b/test/functional/wallet_basic.py index 4a589f0393..dc6f8ed9c4 100755 --- a/test/functional/wallet_basic.py +++ b/test/functional/wallet_basic.py @@ -600,7 +600,7 @@ class WalletTest(BitcoinTestFramework): destination = self.nodes[1].getnewaddress() txid = self.nodes[0].sendtoaddress(destination, 0.123) tx = self.nodes[0].decoderawtransaction(self.nodes[0].gettransaction(txid)['hex']) - output_addresses = [vout['scriptPubKey']['addresses'][0] for vout in tx["vout"]] + output_addresses = [vout['scriptPubKey']['address'] for vout in tx["vout"]] assert len(output_addresses) > 1 for address in output_addresses: ischange = self.nodes[0].getaddressinfo(address)['ischange'] diff --git a/test/functional/wallet_bumpfee.py b/test/functional/wallet_bumpfee.py index 5fc8438e8f..0d1b6c54ce 100755 --- a/test/functional/wallet_bumpfee.py +++ b/test/functional/wallet_bumpfee.py @@ -535,7 +535,7 @@ def test_change_script_match(self, rbf_node, dest_address): def get_change_address(tx): tx_details = rbf_node.getrawtransaction(tx, 1) - txout_addresses = [txout['scriptPubKey']['addresses'][0] for txout in tx_details["vout"]] + txout_addresses = [txout['scriptPubKey']['address'] for txout in tx_details["vout"]] return [address for address in txout_addresses if rbf_node.getaddressinfo(address)["ischange"]] # Check that there is only one change output diff --git a/test/functional/wallet_groups.py b/test/functional/wallet_groups.py index e5c4f12f20..c0b76d960f 100755 --- a/test/functional/wallet_groups.py +++ b/test/functional/wallet_groups.py @@ -29,8 +29,9 @@ class WalletGroupTest(BitcoinTestFramework): self.skip_if_no_wallet() def run_test(self): + self.log.info("Setting up") # Mine some coins - self.nodes[0].generate(110) + self.nodes[0].generate(101) # Get some addresses from the two nodes addr1 = [self.nodes[1].getnewaddress() for _ in range(3)] @@ -48,6 +49,7 @@ class WalletGroupTest(BitcoinTestFramework): # - node[1] should pick one 0.5 UTXO and leave the rest # - node[2] should pick one (1.0 + 0.5) UTXO group corresponding to a # given address, and leave the rest + self.log.info("Test sending transactions picks one UTXO group and leaves the rest") txid1 = self.nodes[1].sendtoaddress(self.nodes[0].getnewaddress(), 0.2) tx1 = self.nodes[1].getrawtransaction(txid1, True) # txid1 should have 1 input and 2 outputs @@ -70,7 +72,7 @@ class WalletGroupTest(BitcoinTestFramework): assert_approx(v[0], vexp=0.2, vspan=0.0001) assert_approx(v[1], vexp=1.3, vspan=0.0001) - # Test 'avoid partial if warranted, even if disabled' + self.log.info("Test avoiding partial spends if warranted, even if avoidpartialspends is disabled") self.sync_all() self.nodes[0].generate(1) # Nodes 1-2 now have confirmed UTXOs (letters denote destinations): @@ -104,7 +106,7 @@ class WalletGroupTest(BitcoinTestFramework): assert_equal(input_addrs[0], input_addrs[1]) # Node 2 enforces avoidpartialspends so needs no checking here - # Test wallet option maxapsfee with Node 3 + self.log.info("Test wallet option maxapsfee") addr_aps = self.nodes[3].getnewaddress() self.nodes[0].sendtoaddress(addr_aps, 1.0) self.nodes[0].sendtoaddress(addr_aps, 1.0) @@ -131,6 +133,7 @@ class WalletGroupTest(BitcoinTestFramework): # Test wallet option maxapsfee with node 4, which sets maxapsfee # 1 sat higher, crossing the threshold from non-grouped to grouped. + self.log.info("Test wallet option maxapsfee threshold from non-grouped to grouped") addr_aps3 = self.nodes[4].getnewaddress() [self.nodes[0].sendtoaddress(addr_aps3, 1.0) for _ in range(5)] self.nodes[0].generate(1) @@ -147,8 +150,7 @@ class WalletGroupTest(BitcoinTestFramework): self.sync_all() self.nodes[0].generate(1) - # Fill node2's wallet with 10000 outputs corresponding to the same - # scriptPubKey + self.log.info("Fill a wallet with 10,000 outputs corresponding to the same scriptPubKey") for _ in range(5): raw_tx = self.nodes[0].createrawtransaction([{"txid":"0"*64, "vout":0}], [{addr2[0]: 0.05}]) tx = FromHex(CTransaction(), raw_tx) @@ -158,12 +160,12 @@ class WalletGroupTest(BitcoinTestFramework): signed_tx = self.nodes[0].signrawtransactionwithwallet(funded_tx['hex']) self.nodes[0].sendrawtransaction(signed_tx['hex']) self.nodes[0].generate(1) - - self.sync_all() + self.sync_all() # Check that we can create a transaction that only requires ~100 of our # utxos, without pulling in all outputs and creating a transaction that # is way too big. + self.log.info("Test creating txn that only requires ~100 of our UTXOs without pulling in all outputs") assert self.nodes[2].sendtoaddress(address=addr2[0], amount=5) diff --git a/test/functional/wallet_hd.py b/test/functional/wallet_hd.py index d45cf05689..23d132df41 100755 --- a/test/functional/wallet_hd.py +++ b/test/functional/wallet_hd.py @@ -132,7 +132,7 @@ class WalletHDTest(BitcoinTestFramework): keypath = "" for out in outs: if out['value'] != 1: - keypath = self.nodes[1].getaddressinfo(out['scriptPubKey']['addresses'][0])['hdkeypath'] + keypath = self.nodes[1].getaddressinfo(out['scriptPubKey']['address'])['hdkeypath'] if self.options.descriptors: assert_equal(keypath[0:14], "m/84'/1'/0'/1/") diff --git a/test/functional/wallet_labels.py b/test/functional/wallet_labels.py index 883b97561e..551eb72720 100755 --- a/test/functional/wallet_labels.py +++ b/test/functional/wallet_labels.py @@ -138,13 +138,13 @@ class WalletLabelsTest(BitcoinTestFramework): node.createwallet(wallet_name='watch_only', disable_private_keys=True) wallet_watch_only = node.get_wallet_rpc('watch_only') BECH32_VALID = { - '✔️_VER15_PROG40': 'bcrt10qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqn2cjv3', - '✔️_VER16_PROG03': 'bcrt1sqqqqqjq8pdp', - '✔️_VER16_PROB02': 'bcrt1sqqqqqjq8pv', + '✔️_VER15_PROG40': 'bcrt10qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqxkg7fn', + '✔️_VER16_PROG03': 'bcrt1sqqqqq8uhdgr', + '✔️_VER16_PROB02': 'bcrt1sqqqq4wstyw', } BECH32_INVALID = { - '❌_VER15_PROG41': 'bcrt10qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqzc7xyq', - '❌_VER16_PROB01': 'bcrt1sqqpl9r5c', + '❌_VER15_PROG41': 'bcrt1sqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqajlxj8', + '❌_VER16_PROB01': 'bcrt1sqq5r4036', } for l in BECH32_VALID: ad = BECH32_VALID[l] diff --git a/test/functional/wallet_listdescriptors.py b/test/functional/wallet_listdescriptors.py index 9f8c341bc7..8d02949ff4 100755 --- a/test/functional/wallet_listdescriptors.py +++ b/test/functional/wallet_listdescriptors.py @@ -50,6 +50,22 @@ class ListDescriptorsTest(BitcoinTestFramework): assert item['range'] == [0, 0] assert item['timestamp'] is not None + self.log.info('Test descriptors with hardened derivations are listed in importable form.') + xprv = 'tprv8ZgxMBicQKsPeuVhWwi6wuMQGfPKi9Li5GtX35jVNknACgqe3CY4g5xgkfDDJcmtF7o1QnxWDRYw4H5P26PXq7sbcUkEqeR4fg3Kxp2tigg' + xpub_acc = 'tpubDCMVLhErorrAGfApiJSJzEKwqeaf2z3NrkVMxgYQjZLzMjXMBeRw2muGNYbvaekAE8rUFLftyEar4LdrG2wXyyTJQZ26zptmeTEjPTaATts' + hardened_path = '/84\'/1\'/0\'' + wallet = node.get_wallet_rpc('w2') + wallet.importdescriptors([{ + 'desc': descsum_create('wpkh(' + xprv + hardened_path + '/0/*)'), + 'timestamp': 1296688602, + }]) + expected = {'desc': descsum_create('wpkh([80002067' + hardened_path + ']' + xpub_acc + '/0/*)'), + 'timestamp': 1296688602, + 'active': False, + 'range': [0, 0], + 'next': 0} + assert_equal([expected], wallet.listdescriptors()) + self.log.info('Test non-active non-range combo descriptor') node.createwallet(wallet_name='w4', blank=True, descriptors=True) wallet = node.get_wallet_rpc('w4') 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 diff --git a/test/functional/wallet_send.py b/test/functional/wallet_send.py index 880341fdd9..53553dcd80 100755 --- a/test/functional/wallet_send.py +++ b/test/functional/wallet_send.py @@ -389,10 +389,10 @@ class WalletSendTest(BitcoinTestFramework): assert res["complete"] res = self.test_send(from_wallet=w0, to_wallet=w1, amount=1, add_to_wallet=False, change_address=change_address, change_position=0) assert res["complete"] - assert_equal(self.nodes[0].decodepsbt(res["psbt"])["tx"]["vout"][0]["scriptPubKey"]["addresses"], [change_address]) + assert_equal(self.nodes[0].decodepsbt(res["psbt"])["tx"]["vout"][0]["scriptPubKey"]["address"], change_address) res = self.test_send(from_wallet=w0, to_wallet=w1, amount=1, add_to_wallet=False, change_type="legacy", change_position=0) assert res["complete"] - change_address = self.nodes[0].decodepsbt(res["psbt"])["tx"]["vout"][0]["scriptPubKey"]["addresses"][0] + change_address = self.nodes[0].decodepsbt(res["psbt"])["tx"]["vout"][0]["scriptPubKey"]["address"] assert change_address[0] == "m" or change_address[0] == "n" self.log.info("Set lock time...") diff --git a/test/functional/wallet_txn_clone.py b/test/functional/wallet_txn_clone.py index 6fc1d13c53..84ff9ad772 100755 --- a/test/functional/wallet_txn_clone.py +++ b/test/functional/wallet_txn_clone.py @@ -65,8 +65,8 @@ class TxnMallTest(BitcoinTestFramework): # Construct a clone of tx1, to be malleated rawtx1 = self.nodes[0].getrawtransaction(txid1, 1) clone_inputs = [{"txid": rawtx1["vin"][0]["txid"], "vout": rawtx1["vin"][0]["vout"], "sequence": rawtx1["vin"][0]["sequence"]}] - clone_outputs = {rawtx1["vout"][0]["scriptPubKey"]["addresses"][0]: rawtx1["vout"][0]["value"], - rawtx1["vout"][1]["scriptPubKey"]["addresses"][0]: rawtx1["vout"][1]["value"]} + clone_outputs = {rawtx1["vout"][0]["scriptPubKey"]["address"]: rawtx1["vout"][0]["value"], + rawtx1["vout"][1]["scriptPubKey"]["address"]: rawtx1["vout"][1]["value"]} clone_locktime = rawtx1["locktime"] clone_raw = self.nodes[0].createrawtransaction(clone_inputs, clone_outputs, clone_locktime) diff --git a/test/fuzz/test_runner.py b/test/fuzz/test_runner.py index 611061072f..eeff7a4515 100755 --- a/test/fuzz/test_runner.py +++ b/test/fuzz/test_runner.py @@ -27,7 +27,7 @@ def get_fuzz_env(*, target, source_dir): def main(): parser = argparse.ArgumentParser( formatter_class=argparse.ArgumentDefaultsHelpFormatter, - description='''Run the fuzz targets with all inputs from the seed_dir once.''', + description='''Run the fuzz targets with all inputs from the corpus_dir once.''', ) parser.add_argument( "-l", @@ -54,8 +54,8 @@ def main(): help='How many targets to merge or execute in parallel.', ) parser.add_argument( - 'seed_dir', - help='The seed corpus to run on (must contain subfolders for each fuzz target).', + 'corpus_dir', + help='The corpus to run on (must contain subfolders for each fuzz target).', ) parser.add_argument( 'target', @@ -64,15 +64,15 @@ def main(): ) parser.add_argument( '--m_dir', - help='Merge inputs from this directory into the seed_dir.', + help='Merge inputs from this directory into the corpus_dir.', ) parser.add_argument( '-g', '--generate', action='store_true', - help='Create new corpus seeds (or extend the existing ones) by running' + help='Create new corpus (or extend the existing ones) by running' ' the given targets for a finite number of times. Outputs them to' - ' the passed seed_dir.' + ' the passed corpus_dir.' ) args = parser.parse_args() @@ -119,19 +119,19 @@ def main(): logging.info("{} of {} detected fuzz target(s) selected: {}".format(len(test_list_selection), len(test_list_all), " ".join(test_list_selection))) if not args.generate: - test_list_seedless = [] + test_list_missing_corpus = [] for t in test_list_selection: - corpus_path = os.path.join(args.seed_dir, t) + corpus_path = os.path.join(args.corpus_dir, t) if not os.path.exists(corpus_path) or len(os.listdir(corpus_path)) == 0: - test_list_seedless.append(t) - test_list_seedless.sort() - if test_list_seedless: + test_list_missing_corpus.append(t) + test_list_missing_corpus.sort() + if test_list_missing_corpus: logging.info( - "Fuzzing harnesses lacking a seed corpus: {}".format( - " ".join(test_list_seedless) + "Fuzzing harnesses lacking a corpus: {}".format( + " ".join(test_list_missing_corpus) ) ) - logging.info("Please consider adding a fuzz seed corpus at https://github.com/bitcoin-core/qa-assets") + logging.info("Please consider adding a fuzz corpus at https://github.com/bitcoin-core/qa-assets") try: help_output = subprocess.run( @@ -154,18 +154,18 @@ def main(): with ThreadPoolExecutor(max_workers=args.par) as fuzz_pool: if args.generate: - return generate_corpus_seeds( + return generate_corpus( fuzz_pool=fuzz_pool, src_dir=config['environment']['SRCDIR'], build_dir=config["environment"]["BUILDDIR"], - seed_dir=args.seed_dir, + corpus_dir=args.corpus_dir, targets=test_list_selection, ) if args.m_dir: merge_inputs( fuzz_pool=fuzz_pool, - corpus=args.seed_dir, + corpus=args.corpus_dir, test_list=test_list_selection, src_dir=config['environment']['SRCDIR'], build_dir=config["environment"]["BUILDDIR"], @@ -175,7 +175,7 @@ def main(): run_once( fuzz_pool=fuzz_pool, - corpus=args.seed_dir, + corpus=args.corpus_dir, test_list=test_list_selection, src_dir=config['environment']['SRCDIR'], build_dir=config["environment"]["BUILDDIR"], @@ -183,13 +183,13 @@ def main(): ) -def generate_corpus_seeds(*, fuzz_pool, src_dir, build_dir, seed_dir, targets): - """Generates new corpus seeds. +def generate_corpus(*, fuzz_pool, src_dir, build_dir, corpus_dir, targets): + """Generates new corpus. - Run {targets} without input, and outputs the generated corpus seeds to - {seed_dir}. + Run {targets} without input, and outputs the generated corpus to + {corpus_dir}. """ - logging.info("Generating corpus seeds to {}".format(seed_dir)) + logging.info("Generating corpus to {}".format(corpus_dir)) def job(command, t): logging.debug("Running '{}'\n".format(" ".join(command))) @@ -205,12 +205,12 @@ def generate_corpus_seeds(*, fuzz_pool, src_dir, build_dir, seed_dir, targets): futures = [] for target in targets: - target_seed_dir = os.path.join(seed_dir, target) - os.makedirs(target_seed_dir, exist_ok=True) + target_corpus_dir = os.path.join(corpus_dir, target) + os.makedirs(target_corpus_dir, exist_ok=True) command = [ os.path.join(build_dir, 'src', 'test', 'fuzz', 'fuzz'), "-runs=100000", - target_seed_dir, + target_corpus_dir, ] futures.append(fuzz_pool.submit(job, command, target)) @@ -219,7 +219,7 @@ def generate_corpus_seeds(*, fuzz_pool, src_dir, build_dir, seed_dir, targets): def merge_inputs(*, fuzz_pool, corpus, test_list, src_dir, build_dir, merge_dir): - logging.info("Merge the inputs from the passed dir into the seed_dir. Passed dir {}".format(merge_dir)) + logging.info("Merge the inputs from the passed dir into the corpus_dir. Passed dir {}".format(merge_dir)) jobs = [] for t in test_list: args = [ diff --git a/test/lint/extended-lint-cppcheck.sh b/test/lint/extended-lint-cppcheck.sh index b2ed811cda..0ab6aad50c 100755 --- a/test/lint/extended-lint-cppcheck.sh +++ b/test/lint/extended-lint-cppcheck.sh @@ -57,7 +57,6 @@ IGNORED_WARNINGS=( "src/test/checkqueue_tests.cpp:.* Struct 'UniqueCheck' has a constructor with 1 argument that is not explicit." "src/test/fuzz/util.h:.* Class 'FuzzedFileProvider' has a constructor with 1 argument that is not explicit." "src/test/fuzz/util.h:.* Class 'FuzzedAutoFileProvider' has a constructor with 1 argument that is not explicit." - "src/util/ref.h:.* Class 'Ref' has a constructor with 1 argument that is not explicit." "src/wallet/db.h:.* Class 'BerkeleyEnvironment' has a constructor with 1 argument that is not explicit." ) diff --git a/test/util/data/tt-delin1-out.json b/test/util/data/tt-delin1-out.json index 9fc2ddc376..c5b9f6df01 100644 --- a/test/util/data/tt-delin1-out.json +++ b/test/util/data/tt-delin1-out.json @@ -195,11 +195,8 @@ "scriptPubKey": { "asm": "OP_DUP OP_HASH160 8fd139bb39ced713f231c58a4d07bf6954d1c201 OP_EQUALVERIFY OP_CHECKSIG", "hex": "76a9148fd139bb39ced713f231c58a4d07bf6954d1c20188ac", - "reqSigs": 1, - "type": "pubkeyhash", - "addresses": [ - "1E7SGgAZFCHDnVZLuRViX3gUmxpMfdvd2o" - ] + "address": "1E7SGgAZFCHDnVZLuRViX3gUmxpMfdvd2o", + "type": "pubkeyhash" } }, { @@ -208,11 +205,8 @@ "scriptPubKey": { "asm": "OP_DUP OP_HASH160 6c772e9cf96371bba3da8cb733da70a2fcf20078 OP_EQUALVERIFY OP_CHECKSIG", "hex": "76a9146c772e9cf96371bba3da8cb733da70a2fcf2007888ac", - "reqSigs": 1, - "type": "pubkeyhash", - "addresses": [ - "1AtWkdmfmYkErU16d3KYykJUbEp9MAj9Sb" - ] + "address": "1AtWkdmfmYkErU16d3KYykJUbEp9MAj9Sb", + "type": "pubkeyhash" } } ], diff --git a/test/util/data/tt-delout1-out.json b/test/util/data/tt-delout1-out.json index 922d048900..3863416430 100644 --- a/test/util/data/tt-delout1-out.json +++ b/test/util/data/tt-delout1-out.json @@ -204,11 +204,8 @@ "scriptPubKey": { "asm": "OP_DUP OP_HASH160 8fd139bb39ced713f231c58a4d07bf6954d1c201 OP_EQUALVERIFY OP_CHECKSIG", "hex": "76a9148fd139bb39ced713f231c58a4d07bf6954d1c20188ac", - "reqSigs": 1, - "type": "pubkeyhash", - "addresses": [ - "1E7SGgAZFCHDnVZLuRViX3gUmxpMfdvd2o" - ] + "address": "1E7SGgAZFCHDnVZLuRViX3gUmxpMfdvd2o", + "type": "pubkeyhash" } } ], diff --git a/test/util/data/tt-locktime317000-out.json b/test/util/data/tt-locktime317000-out.json index c97206f1ea..62e785f7d0 100644 --- a/test/util/data/tt-locktime317000-out.json +++ b/test/util/data/tt-locktime317000-out.json @@ -204,11 +204,8 @@ "scriptPubKey": { "asm": "OP_DUP OP_HASH160 8fd139bb39ced713f231c58a4d07bf6954d1c201 OP_EQUALVERIFY OP_CHECKSIG", "hex": "76a9148fd139bb39ced713f231c58a4d07bf6954d1c20188ac", - "reqSigs": 1, - "type": "pubkeyhash", - "addresses": [ - "1E7SGgAZFCHDnVZLuRViX3gUmxpMfdvd2o" - ] + "address": "1E7SGgAZFCHDnVZLuRViX3gUmxpMfdvd2o", + "type": "pubkeyhash" } }, { @@ -217,11 +214,8 @@ "scriptPubKey": { "asm": "OP_DUP OP_HASH160 6c772e9cf96371bba3da8cb733da70a2fcf20078 OP_EQUALVERIFY OP_CHECKSIG", "hex": "76a9146c772e9cf96371bba3da8cb733da70a2fcf2007888ac", - "reqSigs": 1, - "type": "pubkeyhash", - "addresses": [ - "1AtWkdmfmYkErU16d3KYykJUbEp9MAj9Sb" - ] + "address": "1AtWkdmfmYkErU16d3KYykJUbEp9MAj9Sb", + "type": "pubkeyhash" } } ], diff --git a/test/util/data/txcreate1.json b/test/util/data/txcreate1.json index ca9eacd546..96d77ef273 100644 --- a/test/util/data/txcreate1.json +++ b/test/util/data/txcreate1.json @@ -42,11 +42,8 @@ "scriptPubKey": { "asm": "OP_DUP OP_HASH160 1fc11f39be1729bf973a7ab6a615ca4729d64574 OP_EQUALVERIFY OP_CHECKSIG", "hex": "76a9141fc11f39be1729bf973a7ab6a615ca4729d6457488ac", - "reqSigs": 1, - "type": "pubkeyhash", - "addresses": [ - "13tuJJDR2RgArmgfv6JScSdreahzgc4T6o" - ] + "address": "13tuJJDR2RgArmgfv6JScSdreahzgc4T6o", + "type": "pubkeyhash" } }, { @@ -55,11 +52,8 @@ "scriptPubKey": { "asm": "OP_DUP OP_HASH160 f2d4db28cad6502226ee484ae24505c2885cb12d OP_EQUALVERIFY OP_CHECKSIG", "hex": "76a914f2d4db28cad6502226ee484ae24505c2885cb12d88ac", - "reqSigs": 1, - "type": "pubkeyhash", - "addresses": [ - "1P8yWvZW8jVihP1bzHeqfE4aoXNX8AVa46" - ] + "address": "1P8yWvZW8jVihP1bzHeqfE4aoXNX8AVa46", + "type": "pubkeyhash" } } ], diff --git a/test/util/data/txcreatedata1.json b/test/util/data/txcreatedata1.json index 39909c2e3f..87fc7e9cf7 100644 --- a/test/util/data/txcreatedata1.json +++ b/test/util/data/txcreatedata1.json @@ -24,11 +24,8 @@ "scriptPubKey": { "asm": "OP_DUP OP_HASH160 1fc11f39be1729bf973a7ab6a615ca4729d64574 OP_EQUALVERIFY OP_CHECKSIG", "hex": "76a9141fc11f39be1729bf973a7ab6a615ca4729d6457488ac", - "reqSigs": 1, - "type": "pubkeyhash", - "addresses": [ - "13tuJJDR2RgArmgfv6JScSdreahzgc4T6o" - ] + "address": "13tuJJDR2RgArmgfv6JScSdreahzgc4T6o", + "type": "pubkeyhash" } }, { diff --git a/test/util/data/txcreatedata2.json b/test/util/data/txcreatedata2.json index 2958006e58..d03b1c8244 100644 --- a/test/util/data/txcreatedata2.json +++ b/test/util/data/txcreatedata2.json @@ -24,11 +24,8 @@ "scriptPubKey": { "asm": "OP_DUP OP_HASH160 1fc11f39be1729bf973a7ab6a615ca4729d64574 OP_EQUALVERIFY OP_CHECKSIG", "hex": "76a9141fc11f39be1729bf973a7ab6a615ca4729d6457488ac", - "reqSigs": 1, - "type": "pubkeyhash", - "addresses": [ - "13tuJJDR2RgArmgfv6JScSdreahzgc4T6o" - ] + "address": "13tuJJDR2RgArmgfv6JScSdreahzgc4T6o", + "type": "pubkeyhash" } }, { diff --git a/test/util/data/txcreatedata_seq0.json b/test/util/data/txcreatedata_seq0.json index a6656b5ad5..8a123f1ba8 100644 --- a/test/util/data/txcreatedata_seq0.json +++ b/test/util/data/txcreatedata_seq0.json @@ -24,11 +24,8 @@ "scriptPubKey": { "asm": "OP_DUP OP_HASH160 1fc11f39be1729bf973a7ab6a615ca4729d64574 OP_EQUALVERIFY OP_CHECKSIG", "hex": "76a9141fc11f39be1729bf973a7ab6a615ca4729d6457488ac", - "reqSigs": 1, - "type": "pubkeyhash", - "addresses": [ - "13tuJJDR2RgArmgfv6JScSdreahzgc4T6o" - ] + "address": "13tuJJDR2RgArmgfv6JScSdreahzgc4T6o", + "type": "pubkeyhash" } } ], diff --git a/test/util/data/txcreatedata_seq1.json b/test/util/data/txcreatedata_seq1.json index e5980427b1..006fd7259f 100644 --- a/test/util/data/txcreatedata_seq1.json +++ b/test/util/data/txcreatedata_seq1.json @@ -33,11 +33,8 @@ "scriptPubKey": { "asm": "OP_DUP OP_HASH160 1fc11f39be1729bf973a7ab6a615ca4729d64574 OP_EQUALVERIFY OP_CHECKSIG", "hex": "76a9141fc11f39be1729bf973a7ab6a615ca4729d6457488ac", - "reqSigs": 1, - "type": "pubkeyhash", - "addresses": [ - "13tuJJDR2RgArmgfv6JScSdreahzgc4T6o" - ] + "address": "13tuJJDR2RgArmgfv6JScSdreahzgc4T6o", + "type": "pubkeyhash" } } ], diff --git a/test/util/data/txcreatemultisig1.json b/test/util/data/txcreatemultisig1.json index c32e755db1..baa290c2b1 100644 --- a/test/util/data/txcreatemultisig1.json +++ b/test/util/data/txcreatemultisig1.json @@ -15,13 +15,7 @@ "scriptPubKey": { "asm": "2 02a5613bd857b7048924264d1e70e08fb2a7e6527d32b7ab1bb993ac59964ff397 021ac43c7ff740014c3b33737ede99c967e4764553d1b2b83db77c83b8715fa72d 02df2089105c77f266fa11a9d33f05c735234075f2e8780824c6b709415f9fb485 3 OP_CHECKMULTISIG", "hex": "522102a5613bd857b7048924264d1e70e08fb2a7e6527d32b7ab1bb993ac59964ff39721021ac43c7ff740014c3b33737ede99c967e4764553d1b2b83db77c83b8715fa72d2102df2089105c77f266fa11a9d33f05c735234075f2e8780824c6b709415f9fb48553ae", - "reqSigs": 2, - "type": "multisig", - "addresses": [ - "1FoG2386FG2tAJS9acMuiDsKy67aGg9MKz", - "1FXtz9KU8JNmQDyHdiEm5HDiALuP3zdHvV", - "14LuavcBbXZYJ6Tsz3cAUQj9SuQoL2xCQX" - ] + "type": "multisig" } } ], diff --git a/test/util/data/txcreatemultisig2.json b/test/util/data/txcreatemultisig2.json index f97d265894..6685512587 100644 --- a/test/util/data/txcreatemultisig2.json +++ b/test/util/data/txcreatemultisig2.json @@ -15,11 +15,8 @@ "scriptPubKey": { "asm": "OP_HASH160 1c6fbaf46d64221e80cbae182c33ddf81b9294ac OP_EQUAL", "hex": "a9141c6fbaf46d64221e80cbae182c33ddf81b9294ac87", - "reqSigs": 1, - "type": "scripthash", - "addresses": [ - "34HNh57oBCRKkxNyjTuWAJkTbuGh6jg2Ms" - ] + "address": "34HNh57oBCRKkxNyjTuWAJkTbuGh6jg2Ms", + "type": "scripthash" } } ], diff --git a/test/util/data/txcreatemultisig3.json b/test/util/data/txcreatemultisig3.json index b355d7b191..be96f4c704 100644 --- a/test/util/data/txcreatemultisig3.json +++ b/test/util/data/txcreatemultisig3.json @@ -15,11 +15,8 @@ "scriptPubKey": { "asm": "0 e15a86a23178f433d514dbbce042e87d72662b8b5edcacfd2e37ab7a2d135f05", "hex": "0020e15a86a23178f433d514dbbce042e87d72662b8b5edcacfd2e37ab7a2d135f05", - "reqSigs": 1, - "type": "witness_v0_scripthash", - "addresses": [ - "bc1qu9dgdg330r6r84g5mw7wqshg04exv2uttmw2elfwx74h5tgntuzs44gyfg" - ] + "address": "bc1qu9dgdg330r6r84g5mw7wqshg04exv2uttmw2elfwx74h5tgntuzs44gyfg", + "type": "witness_v0_scripthash" } } ], diff --git a/test/util/data/txcreatemultisig4.json b/test/util/data/txcreatemultisig4.json index a00dbe3f5d..08831ecdca 100644 --- a/test/util/data/txcreatemultisig4.json +++ b/test/util/data/txcreatemultisig4.json @@ -15,11 +15,8 @@ "scriptPubKey": { "asm": "OP_HASH160 6edf12858999f0dae74f9c692e6694ee3621b2ac OP_EQUAL", "hex": "a9146edf12858999f0dae74f9c692e6694ee3621b2ac87", - "reqSigs": 1, - "type": "scripthash", - "addresses": [ - "3BoFUz1StqcNcgUTZE5cC1eFhuYFzj3fGH" - ] + "address": "3BoFUz1StqcNcgUTZE5cC1eFhuYFzj3fGH", + "type": "scripthash" } } ], diff --git a/test/util/data/txcreatemultisig5.json b/test/util/data/txcreatemultisig5.json index ea07822ddd..93048cf261 100644 --- a/test/util/data/txcreatemultisig5.json +++ b/test/util/data/txcreatemultisig5.json @@ -15,11 +15,8 @@ "scriptPubKey": { "asm": "OP_HASH160 a4051c02398868af83f28f083208fae99a769263 OP_EQUAL", "hex": "a914a4051c02398868af83f28f083208fae99a76926387", - "reqSigs": 1, - "type": "scripthash", - "addresses": [ - "3GeGs1eHUxPz5YyuFe9WPpXid2UsUb5Jos" - ] + "address": "3GeGs1eHUxPz5YyuFe9WPpXid2UsUb5Jos", + "type": "scripthash" } } ], diff --git a/test/util/data/txcreateoutpubkey2.json b/test/util/data/txcreateoutpubkey2.json index c0ee181ede..52168a889b 100644 --- a/test/util/data/txcreateoutpubkey2.json +++ b/test/util/data/txcreateoutpubkey2.json @@ -15,11 +15,8 @@ "scriptPubKey": { "asm": "0 a2516e770582864a6a56ed21a102044e388c62e3", "hex": "0014a2516e770582864a6a56ed21a102044e388c62e3", - "reqSigs": 1, - "type": "witness_v0_keyhash", - "addresses": [ - "bc1q5fgkuac9s2ry56jka5s6zqsyfcugcchry5cwu0" - ] + "address": "bc1q5fgkuac9s2ry56jka5s6zqsyfcugcchry5cwu0", + "type": "witness_v0_keyhash" } } ], diff --git a/test/util/data/txcreateoutpubkey3.json b/test/util/data/txcreateoutpubkey3.json index 4d904df3c8..fce210f8a3 100644 --- a/test/util/data/txcreateoutpubkey3.json +++ b/test/util/data/txcreateoutpubkey3.json @@ -15,11 +15,8 @@ "scriptPubKey": { "asm": "OP_HASH160 a5ab14c9804d0d8bf02f1aea4e82780733ad0a83 OP_EQUAL", "hex": "a914a5ab14c9804d0d8bf02f1aea4e82780733ad0a8387", - "reqSigs": 1, - "type": "scripthash", - "addresses": [ - "3GnzN8FqgvYGYdhj8NW6UNxxVv3Uj1ApQn" - ] + "address": "3GnzN8FqgvYGYdhj8NW6UNxxVv3Uj1ApQn", + "type": "scripthash" } } ], diff --git a/test/util/data/txcreatescript2.json b/test/util/data/txcreatescript2.json index 32dd644579..2cde70fdf7 100644 --- a/test/util/data/txcreatescript2.json +++ b/test/util/data/txcreatescript2.json @@ -15,11 +15,8 @@ "scriptPubKey": { "asm": "OP_HASH160 71ed53322d470bb96657deb786b94f97dd46fb15 OP_EQUAL", "hex": "a91471ed53322d470bb96657deb786b94f97dd46fb1587", - "reqSigs": 1, - "type": "scripthash", - "addresses": [ - "3C5QarEGh9feKbDJ3QbMf2YNjnMoiPDhNp" - ] + "address": "3C5QarEGh9feKbDJ3QbMf2YNjnMoiPDhNp", + "type": "scripthash" } } ], diff --git a/test/util/data/txcreatescript3.json b/test/util/data/txcreatescript3.json index b9192d9a82..7a282faf4f 100644 --- a/test/util/data/txcreatescript3.json +++ b/test/util/data/txcreatescript3.json @@ -15,11 +15,8 @@ "scriptPubKey": { "asm": "0 0bfe935e70c321c7ca3afc75ce0d0ca2f98b5422e008bb31c00c6d7f1f1c0ad6", "hex": "00200bfe935e70c321c7ca3afc75ce0d0ca2f98b5422e008bb31c00c6d7f1f1c0ad6", - "reqSigs": 1, - "type": "witness_v0_scripthash", - "addresses": [ - "bc1qp0lfxhnscvsu0j36l36uurgv5tuck4pzuqytkvwqp3kh78cupttqyf705v" - ] + "address": "bc1qp0lfxhnscvsu0j36l36uurgv5tuck4pzuqytkvwqp3kh78cupttqyf705v", + "type": "witness_v0_scripthash" } } ], diff --git a/test/util/data/txcreatescript4.json b/test/util/data/txcreatescript4.json index 2271ecfa0a..298b37bb4a 100644 --- a/test/util/data/txcreatescript4.json +++ b/test/util/data/txcreatescript4.json @@ -15,11 +15,8 @@ "scriptPubKey": { "asm": "OP_HASH160 6a2c482f4985f57e702f325816c90e3723ca81ae OP_EQUAL", "hex": "a9146a2c482f4985f57e702f325816c90e3723ca81ae87", - "reqSigs": 1, - "type": "scripthash", - "addresses": [ - "3BNQbeFeJJGMAyDxPwWPuqxPMrjsFLjk3f" - ] + "address": "3BNQbeFeJJGMAyDxPwWPuqxPMrjsFLjk3f", + "type": "scripthash" } } ], diff --git a/test/util/data/txcreatesignv1.json b/test/util/data/txcreatesignv1.json index 7a06aa9ffe..ca5e003110 100644 --- a/test/util/data/txcreatesignv1.json +++ b/test/util/data/txcreatesignv1.json @@ -24,11 +24,8 @@ "scriptPubKey": { "asm": "OP_DUP OP_HASH160 5834479edbbe0539b31ffd3a8f8ebadc2165ed01 OP_EQUALVERIFY OP_CHECKSIG", "hex": "76a9145834479edbbe0539b31ffd3a8f8ebadc2165ed0188ac", - "reqSigs": 1, - "type": "pubkeyhash", - "addresses": [ - "193P6LtvS4nCnkDvM9uXn1gsSRqh4aDAz7" - ] + "address": "193P6LtvS4nCnkDvM9uXn1gsSRqh4aDAz7", + "type": "pubkeyhash" } } ], |