diff options
710 files changed, 25180 insertions, 14751 deletions
diff --git a/.cirrus.yml b/.cirrus.yml index ccf7077546..f9b47ff7d9 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -24,33 +24,38 @@ filter_template: &FILTER_TEMPLATE base_template: &BASE_TEMPLATE << : *FILTER_TEMPLATE merge_base_script: - # Unconditionally install git (used in fingerprint_script) and set the - # default git author name (used in verify-commits.py) + # Unconditionally install git (used in fingerprint_script). - bash -c "$PACKAGE_MANAGER_INSTALL git" - - git config --global user.email "ci@ci.ci" - - git config --global user.name "ci" - if [ "$CIRRUS_PR" = "" ]; then exit 0; fi - - git fetch $CIRRUS_REPO_CLONE_URL $CIRRUS_BASE_BRANCH - - git merge FETCH_HEAD # Merge base to detect silent merge conflicts + - git fetch $CIRRUS_REPO_CLONE_URL "pull/${CIRRUS_PR}/merge" + - git checkout FETCH_HEAD # Use merged changes to detect silent merge conflicts main_template: &MAIN_TEMPLATE timeout_in: 120m # https://cirrus-ci.org/faq/#instance-timed-out + ccache_cache: + folder: "/tmp/ccache_dir" + ci_script: + - ./ci/test_run_all.sh + +global_task_template: &GLOBAL_TASK_TEMPLATE + << : *BASE_TEMPLATE container: # https://cirrus-ci.org/faq/#are-there-any-limits # Each project has 16 CPU in total, assign 2 to each container, so that 8 tasks run in parallel cpu: 2 greedy: true memory: 8G # Set to 8GB to avoid OOM. https://cirrus-ci.org/guide/linux/#linux-containers - ccache_cache: - folder: "/tmp/ccache_dir" depends_built_cache: folder: "depends/built" fingerprint_script: echo $CIRRUS_TASK_NAME $(git rev-list -1 HEAD ./depends) - ci_script: - - ./ci/test_run_all.sh + << : *MAIN_TEMPLATE -global_task_template: &GLOBAL_TASK_TEMPLATE +macos_native_task_template: &MACOS_NATIVE_TASK_TEMPLATE << : *BASE_TEMPLATE + check_clang_script: + - clang --version + brew_install_script: + - brew install boost libevent qt@5 miniupnpc libnatpmp ccache zeromq qrencode libtool automake gnu-getopt << : *MAIN_TEMPLATE compute_credits_template: &CREDITS_TEMPLATE @@ -86,37 +91,30 @@ task: FILE_ENV: "./ci/test/00_setup_env_native_tidy.sh" task: - name: "Win64 native [msvc]" + name: "Win64 native [vs2022]" << : *FILTER_TEMPLATE windows_container: - cpu: 4 - memory: 8G - image: cirrusci/windowsservercore:visualstudio2019 + cpu: 6 + memory: 12G + image: cirrusci/windowsservercore:visualstudio2022 timeout_in: 120m env: - PATH: 'C:\jom;C:\Python39;C:\Python39\Scripts;C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools\MSBuild\Current\Bin;%PATH%' + PATH: 'C:\jom;C:\Python39;C:\Python39\Scripts;C:\Program Files (x86)\Microsoft Visual Studio\2022\BuildTools\MSBuild\Current\Bin;%PATH%' PYTHONUTF8: 1 - CI_VCPKG_TAG: '2022.04.12' + CI_VCPKG_TAG: '2022.09.27' VCPKG_DOWNLOADS: 'C:\Users\ContainerAdministrator\AppData\Local\vcpkg\downloads' VCPKG_DEFAULT_BINARY_CACHE: 'C:\Users\ContainerAdministrator\AppData\Local\vcpkg\archives' CCACHE_DIR: 'C:\Users\ContainerAdministrator\AppData\Local\ccache' WRAPPED_CL: 'C:\Users\ContainerAdministrator\AppData\Local\Temp\cirrus-ci-build\ci\test\wrapped-cl.bat' - QT_DOWNLOAD_URL: 'https://download.qt.io/official_releases/qt/5.15/5.15.3/single/qt-everywhere-opensource-src-5.15.3.zip' - QT_LOCAL_PATH: 'C:\qt-everywhere-opensource-src-5.15.3.zip' - QT_SOURCE_DIR: 'C:\qt-everywhere-src-5.15.3' + QT_DOWNLOAD_URL: 'https://download.qt.io/official_releases/qt/5.15/5.15.5/single/qt-everywhere-opensource-src-5.15.5.zip' + QT_LOCAL_PATH: 'C:\qt-everywhere-opensource-src-5.15.5.zip' + QT_SOURCE_DIR: 'C:\qt-everywhere-src-5.15.5' QTBASEDIR: 'C:\Qt_static' - x64_NATIVE_TOOLS: '"C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools\VC\Auxiliary\Build\vcvars64.bat"' + x64_NATIVE_TOOLS: '"C:\Program Files (x86)\Microsoft Visual Studio\2022\BuildTools\VC\Auxiliary\Build\vcvars64.bat"' QT_CONFIGURE_COMMAND: '..\configure -release -silent -opensource -confirm-license -opengl desktop -static -static-runtime -mp -qt-zlib -qt-pcre -qt-libpng -nomake examples -nomake tests -nomake tools -no-angle -no-dbus -no-gif -no-gtk -no-ico -no-icu -no-libjpeg -no-libudev -no-sql-sqlite -no-sql-odbc -no-sqlite -no-vulkan -skip qt3d -skip qtactiveqt -skip qtandroidextras -skip qtcharts -skip qtconnectivity -skip qtdatavis3d -skip qtdeclarative -skip doc -skip qtdoc -skip qtgamepad -skip qtgraphicaleffects -skip qtimageformats -skip qtlocation -skip qtlottie -skip qtmacextras -skip qtmultimedia -skip qtnetworkauth -skip qtpurchasing -skip qtquick3d -skip qtquickcontrols -skip qtquickcontrols2 -skip qtquicktimeline -skip qtremoteobjects -skip qtscript -skip qtscxml -skip qtsensors -skip qtserialbus -skip qtserialport -skip qtspeech -skip qtsvg -skip qtvirtualkeyboard -skip qtwayland -skip qtwebchannel -skip qtwebengine -skip qtwebglplugin -skip qtwebsockets -skip qtwebview -skip qtx11extras -skip qtxmlpatterns -no-openssl -no-feature-bearermanagement -no-feature-printdialog -no-feature-printer -no-feature-printpreviewdialog -no-feature-printpreviewwidget -no-feature-sql -no-feature-sqlmodel -no-feature-textbrowser -no-feature-textmarkdownwriter -no-feature-textodfwriter -no-feature-xml' IgnoreWarnIntDirInTempDetected: 'true' merge_script: - - git config --global user.email "ci@ci.ci" - - git config --global user.name "ci" - # Windows filesystem loses the executable bit, and all of the executable - # files are considered "modified" now. It will break the following `git merge` - # command. The next two commands make git ignore this issue. - - git config core.filemode false - - git reset --hard - - PowerShell -NoLogo -Command if ($env:CIRRUS_PR -ne $null) { git fetch $env:CIRRUS_REPO_CLONE_URL $env:CIRRUS_BASE_BRANCH; git merge FETCH_HEAD; } + - PowerShell -NoLogo -Command if ($env:CIRRUS_PR -ne $null) { git fetch $env:CIRRUS_REPO_CLONE_URL pull/$env:CIRRUS_PR/merge; git reset --hard FETCH_HEAD; } msvc_qt_built_cache: folder: "%QTBASEDIR%" reupload_on_changes: false @@ -154,7 +152,7 @@ task: ccache_cache: folder: '%CCACHE_DIR%' install_tools_script: - - choco install --yes --no-progress ccache + - choco install --yes --no-progress ccache --version=4.6.1 - choco install --yes --no-progress python3 --version=3.9.6 - pip install zmq - ccache --version @@ -171,13 +169,13 @@ task: build_script: - '%x64_NATIVE_TOOLS%' - cd %CIRRUS_WORKING_DIR% - - ccache --zero-stats + - ccache --zero-stats --max-size=%CCACHE_SIZE% - python build_msvc\msvc-autogen.py - msbuild build_msvc\bitcoin.sln -property:CLToolExe=%WRAPPED_CL% -property:Configuration=Release -maxCpuCount -verbosity:minimal -noLogo - ccache --show-stats - unit_tests_script: + check_script: - src\test_bitcoin.exe -l test_suite - - src\bench_bitcoin.exe > NUL + - src\bench_bitcoin.exe --sanity-check > NUL - python test\util\test_runner.py - python test\util\rpcauth-test.py functional_tests_script: @@ -251,12 +249,20 @@ task: MAKEJOBS: "-j4" # Avoid excessive memory use due to MSan task: - name: '[ASan + LSan + UBSan + integer, no depends] [jammy]' + name: '[ASan + LSan + UBSan + integer, no depends, USDT] [jammy]' << : *GLOBAL_TASK_TEMPLATE - container: - image: ubuntu:jammy + # We can't use a 'container' for the USDT interface tests as the CirrusCI + # containers don't have privileges to hook into bitcoind. CirrusCI uses + # Google Compute Engine instances: https://cirrus-ci.org/guide/custom-vms/ + # Images can be found here: https://cloud.google.com/compute/docs/images/os-details + compute_engine_instance: + image_project: ubuntu-os-cloud + image: family/ubuntu-2204-lts # when upgrading, check if we can drop "ADD_UNTRUSTED_BPFCC_PPA" + cpu: 4 + memory: 12G env: << : *CIRRUS_EPHEMERAL_WORKER_TEMPLATE_ENV + HOME: /root/ # Only needed for compute_engine_instance FILE_ENV: "./ci/test/00_setup_env_native_asan.sh" MAKEJOBS: "-j4" # Avoid excessive memory use @@ -306,18 +312,16 @@ task: FILE_ENV: "./ci/test/00_setup_env_mac.sh" task: - name: 'macOS 12 native [gui, system sqlite only] [no depends]' - brew_install_script: - - brew install boost libevent qt@5 miniupnpc libnatpmp ccache zeromq qrencode libtool automake gnu-getopt - << : *GLOBAL_TASK_TEMPLATE + name: 'macOS 12 native x86_64 [gui, system sqlite] [no depends]' macos_instance: # Use latest image, but hardcode version to avoid silent upgrades (and breaks) image: monterey-xcode-13.3 # https://cirrus-ci.org/guide/macOS + << : *MACOS_NATIVE_TASK_TEMPLATE env: << : *CIRRUS_EPHEMERAL_WORKER_TEMPLATE_ENV CI_USE_APT_INSTALL: "no" PACKAGE_MANAGER_INSTALL: "echo" # Nothing to do - FILE_ENV: "./ci/test/00_setup_env_mac_host.sh" + FILE_ENV: "./ci/test/00_setup_env_mac_native_x86_64.sh" task: name: 'ARM64 Android APK [focal]' diff --git a/.gitignore b/.gitignore index 6c888bfdc4..6ca9d39a16 100644 --- a/.gitignore +++ b/.gitignore @@ -44,8 +44,6 @@ src/obj share/setup.nsi share/qt/Info.plist -src/univalue/gen - src/qt/*.moc src/qt/moc_*.cpp src/qt/forms/ui_*.h diff --git a/.tx/config b/.tx/config index c4fe7cc324..37c0bca0ab 100644 --- a/.tx/config +++ b/.tx/config @@ -1,7 +1,7 @@ [main] host = https://www.transifex.com -[bitcoin.qt-translation-023x] +[bitcoin.qt-translation-024x] file_filter = src/qt/locale/bitcoin_<lang>.xlf source_file = src/qt/locale/bitcoin_en.xlf source_lang = en diff --git a/Makefile.am b/Makefile.am index 05e89f12b7..8b61763ae4 100644 --- a/Makefile.am +++ b/Makefile.am @@ -23,6 +23,7 @@ endif BITCOIND_BIN=$(top_builddir)/src/$(BITCOIN_DAEMON_NAME)$(EXEEXT) BITCOIN_QT_BIN=$(top_builddir)/src/qt/$(BITCOIN_GUI_NAME)$(EXEEXT) +BITCOIN_TEST_BIN=$(top_builddir)/src/test/$(BITCOIN_TEST_NAME)$(EXEEXT) BITCOIN_CLI_BIN=$(top_builddir)/src/$(BITCOIN_CLI_NAME)$(EXEEXT) BITCOIN_TX_BIN=$(top_builddir)/src/$(BITCOIN_TX_NAME)$(EXEEXT) BITCOIN_UTIL_BIN=$(top_builddir)/src/$(BITCOIN_UTIL_NAME)$(EXEEXT) @@ -78,6 +79,7 @@ $(BITCOIN_WIN_INSTALLER): all-recursive $(MKDIR_P) $(top_builddir)/release STRIPPROG="$(STRIP)" $(INSTALL_STRIP_PROGRAM) $(BITCOIND_BIN) $(top_builddir)/release STRIPPROG="$(STRIP)" $(INSTALL_STRIP_PROGRAM) $(BITCOIN_QT_BIN) $(top_builddir)/release + STRIPPROG="$(STRIP)" $(INSTALL_STRIP_PROGRAM) $(BITCOIN_TEST_BIN) $(top_builddir)/release STRIPPROG="$(STRIP)" $(INSTALL_STRIP_PROGRAM) $(BITCOIN_CLI_BIN) $(top_builddir)/release STRIPPROG="$(STRIP)" $(INSTALL_STRIP_PROGRAM) $(BITCOIN_TX_BIN) $(top_builddir)/release STRIPPROG="$(STRIP)" $(INSTALL_STRIP_PROGRAM) $(BITCOIN_WALLET_BIN) $(top_builddir)/release @@ -174,7 +176,6 @@ LCOV_FILTER_PATTERN = \ -p "src/leveldb/" \ -p "src/crc32c/" \ -p "src/bench/" \ - -p "src/univalue" \ -p "src/crypto/ctaes" \ -p "src/minisketch" \ -p "src/secp256k1" \ @@ -15,126 +15,3 @@ # review a pull request. Peer review is always welcome and is a critical # component of the progress of the codebase. Information on peer review # guidelines can be found in the CONTRIBUTING.md doc. - - -# Maintainers -# @achow101 -# @fanquake -# @hebasto -# @laanwj -# @marcofalke -# @sipa - -# Docs -/doc/*[a-zA-Z-].md @harding -/doc/Doxyfile.in @fanquake -/doc/REST-interface.md @jonasschnelli -/doc/benchmarking.md @ariard -/doc/bitcoin-conf.md @hebasto -/doc/build-freebsd.md @fanquake -/doc/build-netbsd.md @fanquake -/doc/build-openbsd.md @laanwj -/doc/build-osx.md @fanquake -/doc/build-unix.md @laanwj -/doc/build-windows.md @sipsorcery -/doc/dependencies.md @fanquake -/doc/developer-notes.md @laanwj -/doc/files.md @hebasto -/doc/reduce-memory.md @fanquake -/doc/reduce-traffic.md @jonasschnelli -/doc/release-process.md @laanwj -/doc/translation_strings_policy.md @laanwj - -# Build aux -/build-aux/m4/bitcoin_qt.m4 @hebasto - -# MSVC build system -/build_msvc/ @sipsorcery - -# Settings -/src/util/settings.* @ryanofsky - -# Fuzzing - -# Tests -/src/test/net_peer_eviction_tests.cpp @jonatack -/test/functional/mempool_updatefromblock.py @hebasto -/test/functional/feature_asmap.py @jonatack -/test/functional/interface_bitcoin_cli.py @jonatack - -# Backwards compatibility tests -*_compatibility.py @sjors -/test/functional/wallet_upgradewallet.py @sjors @achow101 -/test/get_previous_releases.py @sjors - -# Translations -/src/util/translation.h @hebasto - -# Dev Tools -/contrib/devtools/security-check.py @fanquake -/contrib/devtools/test-security-check.py @fanquake -/contrib/devtools/symbol-check.py @fanquake - -# Guix -/contrib/guix/ @dongcarl - -# Compatibility -/src/compat/glibc_* @fanquake - -# GUI -/src/qt/forms/ @hebasto - -# Wallet -/src/wallet/ @achow101 - -# CLI -/src/bitcoin-cli.cpp @jonatack - -# Coinstats -/src/node/coinstats.* @fjahr - -# Index -/src/index/ @fjahr - -# Descriptors -*descriptor* @achow101 @sipa - -# External signer -*external_signer* @sjors -/doc/external-signer.md @sjors -*signer.py @sjors - -# Interfaces -/src/interfaces/ @ryanofsky - -# DB -/src/txdb.* @jamesob -/src/dbwrapper.* @jamesob - -# Linter -/test/lint/lint-shell.py @hebasto - -# Bech32 -/src/bech32.* @sipa -/src/bench/bech32.* @sipa - -# PSBT -/src/psbt* @achow101 -/src/node/psbt* @achow101 -/doc/psbt.md @achow101 - -# P2P -/src/net_processing.* @sipa -/src/protocol.* @sipa - -# Consensus -/src/coins.* @sipa @jamesob -/src/script/script.* @sipa -/src/script/interpreter.* @sipa -/src/validation.* @sipa -/src/consensus/ @sipa - -# Tracing -/doc/tracing.md @jb55 @0xB10C -/src/util/trace.h @jb55 @0xB10C -/contrib/tracing/ @jb55 @0xB10C diff --git a/SECURITY.md b/SECURITY.md index 25b6175c95..c0660e7042 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -13,8 +13,8 @@ The following keys may be used to communicate sensitive information to developer | Name | Fingerprint | |------|-------------| -| Wladimir van der Laan | 71A3 B167 3540 5025 D447 E8F2 7481 0B01 2346 C9A6 | | Pieter Wuille | 133E AC17 9436 F14A 5CF1 B794 860F EB80 4E66 9320 | | Michael Ford | E777 299F C265 DD04 7930 70EB 944D 35F9 AC3D B76A | +| Andrew Chow | 1528 1230 0785 C964 44D3 334D 1756 5732 E08E 5E41 | You can import a key by running the following command with that individual’s fingerprint: `gpg --keyserver hkps://keys.openpgp.org --recv-keys "<fingerprint>"` Ensure that you put quotes around fingerprints containing spaces. diff --git a/build-aux/m4/ax_boost_base.m4 b/build-aux/m4/ax_boost_base.m4 index 7aac53c815..6c944b160f 100644 --- a/build-aux/m4/ax_boost_base.m4 +++ b/build-aux/m4/ax_boost_base.m4 @@ -11,9 +11,9 @@ # Test for the Boost C++ libraries of a particular version (or newer) # # If no path to the installed boost library is given the macro searchs -# under /usr, /usr/local, /opt, /opt/local and /opt/homebrew and evaluates the -# $BOOST_ROOT environment variable. Further documentation is available at -# <http://randspringer.de/boost/index.html>. +# under /usr, /usr/local, /opt, /opt/local and /opt/homebrew and evaluates +# the $BOOST_ROOT environment variable. Further documentation is available +# at <http://randspringer.de/boost/index.html>. # # This macro calls: # @@ -33,7 +33,7 @@ # and this notice are preserved. This file is offered as-is, without any # warranty. -#serial 48 +#serial 51 # example boost program (need to pass version) m4_define([_AX_BOOST_BASE_PROGRAM], @@ -114,7 +114,7 @@ AC_DEFUN([_AX_BOOST_BASE_RUNDETECT],[ AS_CASE([${host_cpu}], [x86_64],[libsubdirs="lib64 libx32 lib lib64"], [mips*64*],[libsubdirs="lib64 lib32 lib lib64"], - [ppc64|powerpc64|s390x|sparc64|aarch64|ppc64le|powerpc64le|riscv64],[libsubdirs="lib64 lib lib64"], + [ppc64|powerpc64|s390x|sparc64|aarch64|ppc64le|powerpc64le|riscv64|e2k],[libsubdirs="lib64 lib lib64"], [libsubdirs="lib"] ) @@ -128,7 +128,7 @@ AC_DEFUN([_AX_BOOST_BASE_RUNDETECT],[ ) dnl first we check the system location for boost libraries - dnl this location ist chosen if boost libraries are installed with the --layout=system option + dnl this location is chosen if boost libraries are installed with the --layout=system option dnl or if you install boost with RPM AS_IF([test "x$_AX_BOOST_BASE_boost_path" != "x"],[ AC_MSG_CHECKING([for boostlib >= $1 ($WANT_BOOST_VERSION) includes in "$_AX_BOOST_BASE_boost_path/include"]) @@ -151,7 +151,7 @@ AC_DEFUN([_AX_BOOST_BASE_RUNDETECT],[ else search_libsubdirs="$multiarch_libsubdir $libsubdirs" fi - for _AX_BOOST_BASE_boost_path_tmp in /usr /usr/local /opt /opt/local /opt/homebrew/; do + for _AX_BOOST_BASE_boost_path_tmp in /usr /usr/local /opt /opt/local /opt/homebrew ; do if test -d "$_AX_BOOST_BASE_boost_path_tmp/include/boost" && test -r "$_AX_BOOST_BASE_boost_path_tmp/include/boost" ; then for libsubdir in $search_libsubdirs ; do if ls "$_AX_BOOST_BASE_boost_path_tmp/$libsubdir/libboost_"* >/dev/null 2>&1 ; then break; fi diff --git a/build-aux/m4/l_atomic.m4 b/build-aux/m4/l_atomic.m4 index 40639dfe61..602b57fe43 100644 --- a/build-aux/m4/l_atomic.m4 +++ b/build-aux/m4/l_atomic.m4 @@ -18,7 +18,7 @@ m4_define([_CHECK_ATOMIC_testbody], [[ int main() { std::atomic<bool> lock{true}; - std::atomic_exchange(&lock, false); + lock.exchange(false); std::atomic<std::chrono::seconds> t{0s}; t.store(2s); @@ -34,6 +34,8 @@ m4_define([_CHECK_ATOMIC_testbody], [[ AC_DEFUN([CHECK_ATOMIC], [ AC_LANG_PUSH(C++) + TEMP_CXXFLAGS="$CXXFLAGS" + CXXFLAGS="$CXXFLAGS $PTHREAD_CFLAGS" AC_MSG_CHECKING([whether std::atomic can be used without link library]) @@ -51,5 +53,6 @@ AC_DEFUN([CHECK_ATOMIC], [ ]) ]) + CXXFLAGS="$TEMP_CXXFLAGS" AC_LANG_POP ]) diff --git a/build_msvc/README.md b/build_msvc/README.md index 7feee6b766..b9bebd369c 100644 --- a/build_msvc/README.md +++ b/build_msvc/README.md @@ -3,7 +3,9 @@ Building Bitcoin Core with Visual Studio Introduction --------------------- -Solution and project files to build Bitcoin Core with `msbuild` or Visual Studio can be found in the `build_msvc` directory. The build has been tested with Visual Studio 2019 (building with earlier versions of Visual Studio should not be expected to work). +Visual Studio 2022 is minimum required to build Bitcoin Core. + +Solution and project files to build with `msbuild` or Visual Studio can be found in the `build_msvc` directory. To build Bitcoin Core from the command-line, it is sufficient to only install the Visual Studio Build Tools component. @@ -28,9 +30,9 @@ Qt --------------------- To build Bitcoin Core with the GUI, a static build of Qt is required. -1. Download a single ZIP archive of Qt source code from https://download.qt.io/official_releases/qt/ (e.g., [`qt-everywhere-opensource-src-5.15.3.zip`](https://download.qt.io/official_releases/qt/5.15/5.15.3/single/qt-everywhere-opensource-src-5.15.3.zip)), and expand it into a dedicated folder. The following instructions assume that this folder is `C:\dev\qt-source`. +1. Download a single ZIP archive of Qt source code from https://download.qt.io/official_releases/qt/ (e.g., [`qt-everywhere-opensource-src-5.15.5.zip`](https://download.qt.io/official_releases/qt/5.15/5.15.5/single/qt-everywhere-opensource-src-5.15.5.zip)), and expand it into a dedicated folder. The following instructions assume that this folder is `C:\dev\qt-source`. -2. Open "x64 Native Tools Command Prompt for VS 2019", and input the following commands: +2. Open "x64 Native Tools Command Prompt for VS 2022", and input the following commands: ```cmd cd C:\dev\qt-source mkdir build @@ -47,21 +49,21 @@ To build Bitcoin Core without Qt, unload or disable the `bitcoin-qt`, `libbitcoi Building --------------------- -1. Use Python to generate `*.vcxproj` from Makefile: +1. Use Python to generate `*.vcxproj` for the Visual Studio 2022 toolchain from Makefile: -``` -PS >py -3 msvc-autogen.py +```cmd +python build_msvc\msvc-autogen.py ``` 2. An optional step is to adjust the settings in the `build_msvc` directory and the `common.init.vcxproj` file. This project file contains settings that are common to all projects such as the runtime library version and target Windows SDK version. The Qt directories can also be set. To specify a non-default path to a static Qt package directory, use the `QTBASEDIR` environment variable. -3. To build from the command-line with the Visual Studio 2019 toolchain use: +3. To build from the command-line with the Visual Studio toolchain use: ```cmd -msbuild -property:Configuration=Release -maxCpuCount -verbosity:minimal bitcoin.sln +msbuild build_msvc\bitcoin.sln -property:Configuration=Release -maxCpuCount -verbosity:minimal ``` -Alternatively, open the `build_msvc/bitcoin.sln` file in Visual Studio 2019. +Alternatively, open the `build_msvc/bitcoin.sln` file in Visual Studio. Security --------------------- diff --git a/build_msvc/bitcoin_config.h.in b/build_msvc/bitcoin_config.h.in index b37d536947..02d8fc41c2 100644 --- a/build_msvc/bitcoin_config.h.in +++ b/build_msvc/bitcoin_config.h.in @@ -41,18 +41,12 @@ /* Define to 1 to enable ZMQ functions */ #define ENABLE_ZMQ 1 -/* define if the Boost library is available */ -#define HAVE_BOOST /**/ - /* define if external signer support is enabled (requires Boost::Process) */ #define ENABLE_EXTERNAL_SIGNER /**/ /* Define this symbol if the consensus lib has been built */ #define HAVE_CONSENSUS_LIB 1 -/* define if the compiler supports basic C++17 syntax */ -#define HAVE_CXX17 1 - /* Define to 1 if you have the declaration of `be16toh', and to 0 if you don't. */ #define HAVE_DECL_BE16TOH 0 @@ -121,49 +115,9 @@ */ #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 - /* Define if the dllexport attribute is supported. */ #define HAVE_DLLEXPORT_ATTRIBUTE 1 -/* Define to 1 if you have the <inttypes.h> header file. */ -#define HAVE_INTTYPES_H 1 - -/* Define to 1 if you have the <memory.h> header file. */ -#define HAVE_MEMORY_H 1 - -/* 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/upnpcommands.h> header file. */ -#define HAVE_MINIUPNPC_UPNPCOMMANDS_H 1 - -/* Define to 1 if you have the <miniupnpc/upnperrors.h> header file. */ -#define HAVE_MINIUPNPC_UPNPERRORS_H 1 - -/* Define to 1 if you have the <stdint.h> header file. */ -#define HAVE_STDINT_H 1 - -/* Define to 1 if you have the <stdio.h> header file. */ -#define HAVE_STDIO_H 1 - -/* Define to 1 if you have the <stdlib.h> header file. */ -#define HAVE_STDLIB_H 1 - -/* Define to 1 if you have the <strings.h> header file. */ -#define HAVE_STRINGS_H 1 - -/* Define to 1 if you have the <string.h> header file. */ -#define HAVE_STRING_H 1 - -/* Define to 1 if you have the <sys/stat.h> header file. */ -#define HAVE_SYS_STAT_H 1 - -/* Define to 1 if you have the <sys/types.h> header file. */ -#define HAVE_SYS_TYPES_H 1 - /* Define to the address where bug reports for this package should be sent. */ #define PACKAGE_BUGREPORT "https://github.com/bitcoin/bitcoin/issues" diff --git a/build_msvc/common.init.vcxproj.in b/build_msvc/common.init.vcxproj.in index 4c435ea09d..24d5922182 100644 --- a/build_msvc/common.init.vcxproj.in +++ b/build_msvc/common.init.vcxproj.in @@ -87,10 +87,10 @@ <ClCompile> <WarningLevel>Level3</WarningLevel> <PrecompiledHeader>NotUsing</PrecompiledHeader> - <AdditionalOptions>/utf-8 /Zc:__cplusplus /std:c++17 %(AdditionalOptions)</AdditionalOptions> - <DisableSpecificWarnings>4018;4244;4267;4334;4715;4805;4834</DisableSpecificWarnings> + <AdditionalOptions>/utf-8 /Zc:__cplusplus /std:c++20 %(AdditionalOptions)</AdditionalOptions> + <DisableSpecificWarnings>4018;4244;4267;4715;4805</DisableSpecificWarnings> <TreatWarningAsError>true</TreatWarningAsError> - <PreprocessorDefinitions>DISABLE_DESIGNATED_INITIALIZER_ERRORS;_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> + <PreprocessorDefinitions>_SILENCE_CXX17_CODECVT_HEADER_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\minisketch\include;..\..\src\univalue\include;..\..\src\secp256k1\include;..\..\src\leveldb\include;..\..\src\leveldb\helpers\memenv;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> </ClCompile> <Link> diff --git a/build_msvc/libbitcoin_qt/libbitcoin_qt.vcxproj b/build_msvc/libbitcoin_qt/libbitcoin_qt.vcxproj index a64ae881f2..9f9dc9d5fa 100644 --- a/build_msvc/libbitcoin_qt/libbitcoin_qt.vcxproj +++ b/build_msvc/libbitcoin_qt/libbitcoin_qt.vcxproj @@ -53,6 +53,7 @@ <ClCompile Include="..\..\src\qt\transactiondesc.cpp" /> <ClCompile Include="..\..\src\qt\transactiondescdialog.cpp" /> <ClCompile Include="..\..\src\qt\transactionfilterproxy.cpp" /> + <ClCompile Include="..\..\src\qt\transactionoverviewwidget.cpp" /> <ClCompile Include="..\..\src\qt\transactionrecord.cpp" /> <ClCompile Include="..\..\src\qt\transactiontablemodel.cpp" /> <ClCompile Include="..\..\src\qt\transactionview.cpp" /> diff --git a/build_msvc/libsecp256k1_config.h b/build_msvc/libsecp256k1_config.h index 57f2f144ff..2b1a980e27 100644 --- a/build_msvc/libsecp256k1_config.h +++ b/build_msvc/libsecp256k1_config.h @@ -8,23 +8,6 @@ #define BITCOIN_LIBSECP256K1_CONFIG_H #undef USE_ASM_X86_64 -#undef USE_ENDOMORPHISM -#undef USE_FIELD_10X26 -#undef USE_FIELD_5X52 -#undef USE_FIELD_INV_BUILTIN -#undef USE_FIELD_INV_NUM -#undef USE_NUM_GMP -#undef USE_NUM_NONE -#undef USE_SCALAR_4X64 -#undef USE_SCALAR_8X32 -#undef USE_SCALAR_INV_BUILTIN -#undef USE_SCALAR_INV_NUM - -#define USE_NUM_NONE 1 -#define USE_FIELD_INV_BUILTIN 1 -#define USE_SCALAR_INV_BUILTIN 1 -#define USE_FIELD_10X26 1 -#define USE_SCALAR_8X32 1 #define ECMULT_GEN_PREC_BITS 4 #define ECMULT_WINDOW_SIZE 15 diff --git a/build_msvc/msvc-autogen.py b/build_msvc/msvc-autogen.py index 819fe1b7ae..ae48a52a2f 100755 --- a/build_msvc/msvc-autogen.py +++ b/build_msvc/msvc-autogen.py @@ -9,7 +9,7 @@ import argparse from shutil import copyfile SOURCE_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'src')) -DEFAULT_PLATFORM_TOOLSET = R'v142' +DEFAULT_PLATFORM_TOOLSET = R'v143' libs = [ 'libbitcoin_cli', @@ -93,7 +93,7 @@ def set_properties(vcxproj_filename, placeholder, content): def main(): parser = argparse.ArgumentParser(description='Bitcoin-core msbuild configuration initialiser.') parser.add_argument('-toolset', nargs='?', default=DEFAULT_PLATFORM_TOOLSET, - help='Optionally sets the msbuild platform toolset, e.g. v142 for Visual Studio 2019.' + help='Optionally sets the msbuild platform toolset, e.g. v143 for Visual Studio 2022.' ' default is %s.'%DEFAULT_PLATFORM_TOOLSET) args = parser.parse_args() set_properties(os.path.join(SOURCE_DIR, '../build_msvc/common.init.vcxproj'), '@TOOLSET@', args.toolset) diff --git a/ci/lint/04_install.sh b/ci/lint/04_install.sh index 8330df87eb..6f9a4709dd 100755 --- a/ci/lint/04_install.sh +++ b/ci/lint/04_install.sh @@ -7,11 +7,16 @@ export LC_ALL=C ${CI_RETRY_EXE} apt-get update -${CI_RETRY_EXE} apt-get install -y clang-format-9 python3-pip curl git gawk jq -update-alternatives --install /usr/bin/clang-format clang-format "$(which clang-format-9 )" 100 -update-alternatives --install /usr/bin/clang-format-diff clang-format-diff "$(which clang-format-diff-9)" 100 +${CI_RETRY_EXE} apt-get install -y python3-pip curl git gawk jq +( + # Temporary workaround for https://github.com/bitcoin/bitcoin/pull/26130#issuecomment-1260499544 + # Can be removed once the underlying image is bumped to something that includes git2.34 or later + sed -i -e 's/bionic/jammy/g' /etc/apt/sources.list + ${CI_RETRY_EXE} apt-get update + ${CI_RETRY_EXE} apt-get install -y --reinstall git +) -${CI_RETRY_EXE} pip3 install codespell==2.1.0 +${CI_RETRY_EXE} pip3 install codespell==2.2.1 ${CI_RETRY_EXE} pip3 install flake8==4.0.1 ${CI_RETRY_EXE} pip3 install mypy==0.942 ${CI_RETRY_EXE} pip3 install pyzmq==22.3.0 diff --git a/ci/lint/06_script.sh b/ci/lint/06_script.sh index 84b3404e78..4506848740 100755 --- a/ci/lint/06_script.sh +++ b/ci/lint/06_script.sh @@ -18,7 +18,6 @@ export COMMIT_RANGE test/lint/git-subtree-check.sh src/crypto/ctaes test/lint/git-subtree-check.sh src/secp256k1 test/lint/git-subtree-check.sh src/minisketch -test/lint/git-subtree-check.sh src/univalue test/lint/git-subtree-check.sh src/leveldb test/lint/git-subtree-check.sh src/crc32c test/lint/check-doc.py @@ -32,6 +31,8 @@ if [ "$CIRRUS_REPO_FULL_NAME" = "bitcoin/bitcoin" ] && [ "$CIRRUS_PR" = "" ] ; t git log HEAD~10 -1 --format='%H' > ./contrib/verify-commits/trusted-sha512-root-commit git log HEAD~10 -1 --format='%H' > ./contrib/verify-commits/trusted-git-root mapfile -t KEYS < contrib/verify-commits/trusted-keys + git config user.email "ci@ci.ci" + git config user.name "ci" ${CI_RETRY_EXE} gpg --keyserver hkps://keys.openpgp.org --recv-keys "${KEYS[@]}" && ./contrib/verify-commits/verify-commits.py; fi diff --git a/ci/test/00_setup_env_i686_centos.sh b/ci/test/00_setup_env_i686_centos.sh index 8f1cc8af29..1ce3261f44 100755 --- a/ci/test/00_setup_env_i686_centos.sh +++ b/ci/test/00_setup_env_i686_centos.sh @@ -9,7 +9,8 @@ export LC_ALL=C.UTF-8 export HOST=i686-pc-linux-gnu export CONTAINER_NAME=ci_i686_centos export DOCKER_NAME_TAG=quay.io/centos/centos:stream8 -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 xz procps-ng dash rsync coreutils bison" +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-pip which patch lbzip2 xz procps-ng dash rsync coreutils bison" +export PIP_PACKAGES="pyzmq" export GOAL="install" export BITCOIN_CONFIG="--enable-zmq --with-gui=qt5 --enable-reduce-exports" export CONFIG_SHELL="/bin/dash" diff --git a/ci/test/00_setup_env_i686_multiprocess.sh b/ci/test/00_setup_env_i686_multiprocess.sh index 766424769d..76de87d955 100755 --- a/ci/test/00_setup_env_i686_multiprocess.sh +++ b/ci/test/00_setup_env_i686_multiprocess.sh @@ -9,7 +9,7 @@ export LC_ALL=C.UTF-8 export HOST=i686-pc-linux-gnu export CONTAINER_NAME=ci_i686_multiprocess export DOCKER_NAME_TAG=ubuntu:20.04 -export PACKAGES="cmake python3 python3-pip llvm clang g++-multilib" +export PACKAGES="cmake python3 llvm clang g++-multilib" export DEP_OPTS="DEBUG=1 MULTIPROCESS=1" export GOAL="install" export BITCOIN_CONFIG="--enable-debug CC='clang -m32' CXX='clang++ -m32' LDFLAGS='--rtlib=compiler-rt -lgcc_s'" diff --git a/ci/test/00_setup_env_mac_host.sh b/ci/test/00_setup_env_mac_native_x86_64.sh index d176296e76..d176296e76 100755 --- a/ci/test/00_setup_env_mac_host.sh +++ b/ci/test/00_setup_env_mac_native_x86_64.sh diff --git a/ci/test/00_setup_env_native_asan.sh b/ci/test/00_setup_env_native_asan.sh index 69883e3609..4f1792a9f0 100755 --- a/ci/test/00_setup_env_native_asan.sh +++ b/ci/test/00_setup_env_native_asan.sh @@ -6,9 +6,14 @@ export LC_ALL=C.UTF-8 +# We install an up-to-date 'bpfcc-tools' package from an untrusted PPA. +# This can be dropped with the next Ubuntu or Debian release that includes up-to-date packages. +# See the if-then in ci/test/04_install.sh too. +export ADD_UNTRUSTED_BPFCC_PPA=true + export CONTAINER_NAME=ci_native_asan -export PACKAGES="clang llvm python3-zmq qtbase5-dev qttools5-dev-tools libevent-dev bsdmainutils libboost-dev libdb5.3++-dev libminiupnpc-dev libnatpmp-dev libzmq3-dev libqrencode-dev libsqlite3-dev" -export DOCKER_NAME_TAG=ubuntu:22.04 +export PACKAGES="systemtap-sdt-dev bpfcc-tools clang llvm python3-zmq qtbase5-dev qttools5-dev-tools libevent-dev bsdmainutils libboost-dev libdb5.3++-dev libminiupnpc-dev libnatpmp-dev libzmq3-dev libqrencode-dev libsqlite3-dev" +export DOCKER_NAME_TAG=ubuntu:22.04 # May not run in docker unless --enable-usdt is dropped export NO_DEPENDS=1 export GOAL="install" -export BITCOIN_CONFIG="--enable-c++20 --enable-zmq --with-incompatible-bdb --with-gui=qt5 CPPFLAGS='-DARENA_DEBUG -DDEBUG_LOCKORDER' --with-sanitizers=address,integer,undefined CC=clang CXX=clang++" +export BITCOIN_CONFIG="--enable-usdt --enable-zmq --with-incompatible-bdb --with-gui=qt5 CPPFLAGS='-DARENA_DEBUG -DDEBUG_LOCKORDER' --with-sanitizers=address,integer,undefined CC=clang CXX=clang++" diff --git a/ci/test/00_setup_env_native_fuzz_with_valgrind.sh b/ci/test/00_setup_env_native_fuzz_with_valgrind.sh index 9477fb2d9f..97c530e19e 100755 --- a/ci/test/00_setup_env_native_fuzz_with_valgrind.sh +++ b/ci/test/00_setup_env_native_fuzz_with_valgrind.sh @@ -16,5 +16,5 @@ export RUN_FUZZ_TESTS=true export FUZZ_TESTS_CONFIG="--valgrind" export GOAL="install" # Temporarily pin dwarf 4, until valgrind can understand clang's dwarf 5 -export BITCOIN_CONFIG="--enable-fuzz --with-sanitizers=fuzzer CC=clang CXX=clang++ CXXFLAGS='-fdebug-default-version=4'" +export BITCOIN_CONFIG="--enable-fuzz --with-sanitizers=fuzzer CC=clang CXX=clang++ CFLAGS='-gdwarf-4' CXXFLAGS='-gdwarf-4'" export CCACHE_SIZE=200M diff --git a/ci/test/00_setup_env_native_qt5.sh b/ci/test/00_setup_env_native_qt5.sh index f399e43612..8a6ea62d5c 100755 --- a/ci/test/00_setup_env_native_qt5.sh +++ b/ci/test/00_setup_env_native_qt5.sh @@ -14,6 +14,6 @@ export TEST_RUNNER_EXTRA="--previous-releases --coverage --extended --exclude fe export RUN_UNIT_TESTS_SEQUENTIAL="true" export RUN_UNIT_TESTS="false" export GOAL="install" -export PREVIOUS_RELEASES_TO_DOWNLOAD="v0.14.3 v0.15.2 v0.16.3 v0.17.2 v0.18.1 v0.19.1 v0.20.1 v0.21.0 v22.0 v23.0" +export DOWNLOAD_PREVIOUS_RELEASES="true" export BITCOIN_CONFIG="--enable-zmq --with-libs=no --with-gui=qt5 --enable-reduce-exports \ --enable-debug CFLAGS=\"-g0 -O2 -funsigned-char\" CXXFLAGS=\"-g0 -O2 -funsigned-char\" CC=gcc-8 CXX=g++-8" diff --git a/ci/test/00_setup_env_native_tidy.sh b/ci/test/00_setup_env_native_tidy.sh index e4d3468473..11a12e336a 100755 --- a/ci/test/00_setup_env_native_tidy.sh +++ b/ci/test/00_setup_env_native_tidy.sh @@ -15,5 +15,5 @@ export RUN_FUNCTIONAL_TESTS=false export RUN_FUZZ_TESTS=false export RUN_TIDY=true export GOAL="install" -export BITCOIN_CONFIG="CC=clang CXX=clang++ --with-incompatible-bdb --disable-hardening CFLAGS='-O0 -g0' CXXFLAGS='-O0 -g0'" +export BITCOIN_CONFIG="CC=clang CXX=clang++ --enable-c++20 --with-incompatible-bdb --disable-hardening CFLAGS='-O0 -g0' CXXFLAGS='-O0 -g0'" export CCACHE_SIZE=200M diff --git a/ci/test/00_setup_env_native_valgrind.sh b/ci/test/00_setup_env_native_valgrind.sh index 7b714dff5c..d8c08fca39 100755 --- a/ci/test/00_setup_env_native_valgrind.sh +++ b/ci/test/00_setup_env_native_valgrind.sh @@ -14,4 +14,4 @@ export NO_DEPENDS=1 export TEST_RUNNER_EXTRA="--nosandbox --exclude feature_init,rpc_bind,feature_bind_extra" # Excluded for now, see https://github.com/bitcoin/bitcoin/issues/17765#issuecomment-602068547 export GOAL="install" # Temporarily pin dwarf 4, until valgrind can understand clang's dwarf 5 -export BITCOIN_CONFIG="--enable-zmq --with-incompatible-bdb --with-gui=no CC=clang CXX=clang++ CXXFLAGS='-fdebug-default-version=4'" # TODO enable GUI +export BITCOIN_CONFIG="--enable-zmq --with-incompatible-bdb --with-gui=no CC=clang CXX=clang++ CFLAGS='-gdwarf-4' CXXFLAGS='-gdwarf-4'" # TODO enable GUI diff --git a/ci/test/04_install.sh b/ci/test/04_install.sh index 453a34ca78..b256ae21a5 100755 --- a/ci/test/04_install.sh +++ b/ci/test/04_install.sh @@ -10,12 +10,6 @@ if [[ $QEMU_USER_CMD == qemu-s390* ]]; then export LC_ALL=C fi -if [ "$CI_OS_NAME" == "macos" ]; then - sudo -H pip3 install --upgrade pip - # shellcheck disable=SC2086 - IN_GETOPT_BIN="/usr/local/opt/gnu-getopt/bin/getopt" ${CI_RETRY_EXE} pip3 install --user $PIP_PACKAGES -fi - # Create folders that are mounted into the docker mkdir -p "${CCACHE_DIR}" mkdir -p "${PREVIOUS_RELEASES_DIR}" @@ -68,11 +62,26 @@ if [[ $DOCKER_NAME_TAG == *centos* ]]; then ${CI_RETRY_EXE} CI_EXEC dnf -y install epel-release ${CI_RETRY_EXE} CI_EXEC dnf -y --allowerasing install "$DOCKER_PACKAGES" "$PACKAGES" elif [ "$CI_USE_APT_INSTALL" != "no" ]; then + if [[ "${ADD_UNTRUSTED_BPFCC_PPA}" == "true" ]]; then + # Ubuntu 22.04 LTS and Debian 11 both have an outdated bpfcc-tools packages. + # The iovisor PPA is outdated as well. The next Ubuntu and Debian releases will contain updated + # packages. Meanwhile, use an untrusted PPA to install an up-to-date version of the bpfcc-tools + # package. + # TODO: drop this once we can use newer images in GCE + CI_EXEC add-apt-repository ppa:hadret/bpfcc + fi ${CI_RETRY_EXE} CI_EXEC apt-get update ${CI_RETRY_EXE} CI_EXEC apt-get install --no-install-recommends --no-upgrade -y "$PACKAGES" "$DOCKER_PACKAGES" - if [ -n "$PIP_PACKAGES" ]; then +fi + +if [ -n "$PIP_PACKAGES" ]; then + if [ "$CI_OS_NAME" == "macos" ]; then + sudo -H pip3 install --upgrade pip + # shellcheck disable=SC2086 + IN_GETOPT_BIN="/usr/local/opt/gnu-getopt/bin/getopt" ${CI_RETRY_EXE} pip3 install --user $PIP_PACKAGES + else # shellcheck disable=SC2086 - ${CI_RETRY_EXE} pip3 install --user $PIP_PACKAGES + ${CI_RETRY_EXE} CI_EXEC pip3 install --user $PIP_PACKAGES fi fi diff --git a/ci/test/05_before_script.sh b/ci/test/05_before_script.sh index f3da6b4f31..ef3dff86ca 100755 --- a/ci/test/05_before_script.sh +++ b/ci/test/05_before_script.sh @@ -47,6 +47,6 @@ if [ -z "$NO_DEPENDS" ]; then fi CI_EXEC "$SHELL_OPTS" make "$MAKEJOBS" -C depends HOST="$HOST" "$DEP_OPTS" LOG=1 fi -if [ -n "$PREVIOUS_RELEASES_TO_DOWNLOAD" ]; then - CI_EXEC test/get_previous_releases.py -b -t "$PREVIOUS_RELEASES_DIR" "${PREVIOUS_RELEASES_TO_DOWNLOAD}" +if [ "$DOWNLOAD_PREVIOUS_RELEASES" = "true" ]; then + CI_EXEC test/get_previous_releases.py -b -t "$PREVIOUS_RELEASES_DIR" fi diff --git a/ci/test/06_script_a.sh b/ci/test/06_script_a.sh index 218f5eeb63..13693a2ecf 100755 --- a/ci/test/06_script_a.sh +++ b/ci/test/06_script_a.sh @@ -11,16 +11,19 @@ if [ -z "$NO_WERROR" ]; then BITCOIN_CONFIG_ALL="${BITCOIN_CONFIG_ALL} --enable-werror" fi +CI_EXEC "ccache --zero-stats --max-size=$CCACHE_SIZE" +PRINT_CCACHE_STATISTICS="ccache --version | head -n 1 && ccache --show-stats" + if [ -n "$ANDROID_TOOLS_URL" ]; then CI_EXEC make distclean || true CI_EXEC ./autogen.sh CI_EXEC ./configure "$BITCOIN_CONFIG_ALL" "$BITCOIN_CONFIG" || ( (CI_EXEC cat config.log) && false) CI_EXEC "make $MAKEJOBS && cd src/qt && ANDROID_HOME=${ANDROID_HOME} ANDROID_NDK_HOME=${ANDROID_NDK_HOME} make apk" + CI_EXEC "${PRINT_CCACHE_STATISTICS}" exit 0 fi BITCOIN_CONFIG_ALL="${BITCOIN_CONFIG_ALL} --enable-external-signer --bindir=$BASE_OUTDIR/bin --libdir=$BASE_OUTDIR/lib" -CI_EXEC "ccache --zero-stats --max-size=$CCACHE_SIZE" if [ -n "$CONFIG_SHELL" ]; then CI_EXEC "$CONFIG_SHELL" -c "./autogen.sh" @@ -57,6 +60,6 @@ fi CI_EXEC "${MAYBE_BEAR}" "${MAYBE_TOKEN}" make "$MAKEJOBS" "$GOAL" || ( echo "Build failure. Verbose build follows." && CI_EXEC make "$GOAL" V=1 ; false ) -CI_EXEC "ccache --version | head -n 1 && ccache --show-stats" +CI_EXEC "${PRINT_CCACHE_STATISTICS}" CI_EXEC du -sh "${DEPENDS_DIR}"/*/ CI_EXEC du -sh "${PREVIOUS_RELEASES_DIR}" diff --git a/ci/test/06_script_b.sh b/ci/test/06_script_b.sh index bdb68e0f6f..5bdb392ba3 100755 --- a/ci/test/06_script_b.sh +++ b/ci/test/06_script_b.sh @@ -35,17 +35,39 @@ if [ "$RUN_FUNCTIONAL_TESTS" = "true" ]; then fi if [ "${RUN_TIDY}" = "true" ]; then + set -eo pipefail export P_CI_DIR="${BASE_BUILD_DIR}/bitcoin-$HOST/src/" - CI_EXEC run-clang-tidy "${MAKEJOBS}" + ( CI_EXEC run-clang-tidy -quiet "${MAKEJOBS}" ) | grep -C5 "error" export P_CI_DIR="${BASE_BUILD_DIR}/bitcoin-$HOST/" CI_EXEC "python3 ${DIR_IWYU}/include-what-you-use/iwyu_tool.py"\ " src/compat"\ + " src/dbwrapper.cpp"\ " src/init"\ + " src/kernel"\ + " src/node/chainstate.cpp"\ + " src/node/mempool_args.cpp"\ + " src/node/validation_cache_args.cpp"\ " src/policy/feerate.cpp"\ " src/policy/packages.cpp"\ " src/policy/settings.cpp"\ + " src/primitives/transaction.cpp"\ " src/rpc/fees.cpp"\ " src/rpc/signmessage.cpp"\ + " src/test/fuzz/txorphan.cpp"\ + " src/threadinterrupt.cpp"\ + " src/util/bip32.cpp"\ + " src/util/bytevectorhash.cpp"\ + " src/util/error.cpp"\ + " src/util/getuniquepath.cpp"\ + " src/util/hasher.cpp"\ + " src/util/message.cpp"\ + " src/util/moneystr.cpp"\ + " src/util/serfloat.cpp"\ + " src/util/spanparsing.cpp"\ + " src/util/strencodings.cpp"\ + " src/util/string.cpp"\ + " src/util/syserror.cpp"\ + " src/util/url.cpp"\ " -p . ${MAKEJOBS} -- -Xiwyu --cxx17ns -Xiwyu --mapping_file=${BASE_BUILD_DIR}/bitcoin-$HOST/contrib/devtools/iwyu/bitcoin.core.imp" fi diff --git a/ci/test/wrap-qemu.sh b/ci/test/wrap-qemu.sh index fcd56f533e..eb31edbce8 100755 --- a/ci/test/wrap-qemu.sh +++ b/ci/test/wrap-qemu.sh @@ -6,7 +6,7 @@ export LC_ALL=C.UTF-8 -for b_name in {"${BASE_OUTDIR}/bin"/*,src/secp256k1/*tests,src/minisketch/test{,-verify},src/univalue/{no_nul,test_json,unitester,object}}; do +for b_name in {"${BASE_OUTDIR}/bin"/*,src/secp256k1/*tests,src/minisketch/test{,-verify},src/univalue/{test_json,unitester,object}}; do # shellcheck disable=SC2044 for b in $(find "${BASE_ROOT_DIR}" -executable -type f -name "$(basename "$b_name")"); do echo "Wrap $b ..." diff --git a/ci/test/wrap-wine.sh b/ci/test/wrap-wine.sh index 525db9eded..1662f8f6a3 100755 --- a/ci/test/wrap-wine.sh +++ b/ci/test/wrap-wine.sh @@ -6,7 +6,7 @@ export LC_ALL=C.UTF-8 -for b_name in {"${BASE_OUTDIR}/bin"/*,src/secp256k1/*tests,src/minisketch/test{,-verify},src/univalue/{no_nul,test_json,unitester,object}}.exe; do +for b_name in {"${BASE_OUTDIR}/bin"/*,src/secp256k1/*tests,src/minisketch/test{,-verify},src/univalue/{test_json,unitester,object}}.exe; do # shellcheck disable=SC2044 for b in $(find "${BASE_ROOT_DIR}" -executable -type f -name "$(basename "$b_name")"); do if (file "$b" | grep "Windows"); then diff --git a/configure.ac b/configure.ac index f6d00d0283..936ade6bc3 100644 --- a/configure.ac +++ b/configure.ac @@ -1,5 +1,5 @@ AC_PREREQ([2.69]) -define(_CLIENT_VERSION_MAJOR, 23) +define(_CLIENT_VERSION_MAJOR, 24) define(_CLIENT_VERSION_MINOR, 99) define(_CLIENT_VERSION_BUILD, 0) define(_CLIENT_VERSION_RC, 0) @@ -19,8 +19,19 @@ if test "$PKG_CONFIG" = ""; then AC_MSG_ERROR([pkg-config not found]) fi +# When compiling with depends, the `PKG_CONFIG_PATH` and `PKG_CONFIG_LIBDIR` variables, +# being set in a `config.site` file, are not exported to let the `--config-cache` option +# work properly. +if test -n "$PKG_CONFIG_PATH"; then + PKG_CONFIG="env PKG_CONFIG_PATH=$PKG_CONFIG_PATH $PKG_CONFIG" +fi +if test -n "$PKG_CONFIG_LIBDIR"; then + PKG_CONFIG="env PKG_CONFIG_LIBDIR=$PKG_CONFIG_LIBDIR $PKG_CONFIG" +fi + BITCOIN_DAEMON_NAME=bitcoind BITCOIN_GUI_NAME=bitcoin-qt +BITCOIN_TEST_NAME=test_bitcoin BITCOIN_CLI_NAME=bitcoin-cli BITCOIN_TX_NAME=bitcoin-tx BITCOIN_UTIL_NAME=bitcoin-util @@ -31,6 +42,7 @@ BITCOIN_MP_NODE_NAME=bitcoin-node BITCOIN_MP_GUI_NAME=bitcoin-gui dnl Unless the user specified ARFLAGS, force it to be cr +dnl This is also the default as-of libtool 2.4.7 AC_ARG_VAR([ARFLAGS], [Flags for the archiver, defaults to <cr> if not set]) if test "${ARFLAGS+set}" != "set"; then ARFLAGS="cr" @@ -86,9 +98,6 @@ else AX_CXX_COMPILE_STDCXX([20], [noext], [mandatory]) fi -dnl Check if -latomic is required for <std::atomic> -CHECK_ATOMIC - dnl check if additional link flags are required for std::filesystem CHECK_FILESYSTEM @@ -324,7 +333,7 @@ AC_ARG_ENABLE([werror], [enable_werror=no]) AC_ARG_ENABLE([external-signer], - [AS_HELP_STRING([--enable-external-signer],[compile external signer support (default is yes, requires Boost::Process)])], + [AS_HELP_STRING([--enable-external-signer],[compile external signer support (default is auto, requires Boost::Process)])], [use_external_signer=$enableval], [use_external_signer=auto]) @@ -510,7 +519,7 @@ if test "$enable_clmul" = "yes"; then fi TEMP_CXXFLAGS="$CXXFLAGS" -CXXFLAGS="$CXXFLAGS $SSE42_CXXFLAGS" +CXXFLAGS="$SSE42_CXXFLAGS $CXXFLAGS" AC_MSG_CHECKING([for SSE4.2 intrinsics]) AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ #include <stdint.h> @@ -532,7 +541,7 @@ AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ CXXFLAGS="$TEMP_CXXFLAGS" TEMP_CXXFLAGS="$CXXFLAGS" -CXXFLAGS="$CXXFLAGS $SSE41_CXXFLAGS" +CXXFLAGS="$SSE41_CXXFLAGS $CXXFLAGS" AC_MSG_CHECKING([for SSE4.1 intrinsics]) AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ #include <stdint.h> @@ -547,7 +556,7 @@ AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ CXXFLAGS="$TEMP_CXXFLAGS" TEMP_CXXFLAGS="$CXXFLAGS" -CXXFLAGS="$CXXFLAGS $AVX2_CXXFLAGS" +CXXFLAGS="$AVX2_CXXFLAGS $CXXFLAGS" AC_MSG_CHECKING([for AVX2 intrinsics]) AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ #include <stdint.h> @@ -562,7 +571,7 @@ AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ CXXFLAGS="$TEMP_CXXFLAGS" TEMP_CXXFLAGS="$CXXFLAGS" -CXXFLAGS="$CXXFLAGS $X86_SHANI_CXXFLAGS" +CXXFLAGS="$X86_SHANI_CXXFLAGS $CXXFLAGS" AC_MSG_CHECKING([for x86 SHA-NI intrinsics]) AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ #include <stdint.h> @@ -579,11 +588,11 @@ AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ CXXFLAGS="$TEMP_CXXFLAGS" # ARM -AX_CHECK_COMPILE_FLAG([-march=armv8-a+crc+crypto], [ARM_CRC_CXXFLAGS="-march=armv8-a+crc+crypto"], [], [$CXXFLAG_WERROR]) -AX_CHECK_COMPILE_FLAG([-march=armv8-a+crc+crypto], [ARM_SHANI_CXXFLAGS="-march=armv8-a+crc+crypto"], [], [$CXXFLAG_WERROR]) +AX_CHECK_COMPILE_FLAG([-march=armv8-a+crc], [ARM_CRC_CXXFLAGS="-march=armv8-a+crc"], [], [$CXXFLAG_WERROR]) +AX_CHECK_COMPILE_FLAG([-march=armv8-a+crypto], [ARM_SHANI_CXXFLAGS="-march=armv8-a+crypto"], [], [$CXXFLAG_WERROR]) TEMP_CXXFLAGS="$CXXFLAGS" -CXXFLAGS="$CXXFLAGS $ARM_CRC_CXXFLAGS" +CXXFLAGS="$ARM_CRC_CXXFLAGS $CXXFLAGS" AC_MSG_CHECKING([for ARMv8 CRC32 intrinsics]) AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ #include <arm_acle.h> @@ -602,7 +611,7 @@ AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ CXXFLAGS="$TEMP_CXXFLAGS" TEMP_CXXFLAGS="$CXXFLAGS" -CXXFLAGS="$CXXFLAGS $ARM_SHANI_CXXFLAGS" +CXXFLAGS="$ARM_SHANI_CXXFLAGS $CXXFLAGS" AC_MSG_CHECKING([for ARMv8 SHA-NI intrinsics]) AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ #include <arm_acle.h> @@ -710,6 +719,9 @@ case $host in fi CORE_CPPFLAGS="$CORE_CPPFLAGS -D_MT -DWIN32 -D_WINDOWS -D_WIN32_WINNT=0x0601 -D_WIN32_IE=0x0501 -DWIN32_LEAN_AND_MEAN" + dnl Prevent the definition of min/max macros. + dnl We always want to use the standard library. + CORE_CPPFLAGS="$CORE_CPPFLAGS -DNOMINMAX" dnl libtool insists upon adding -nostdlib and a list of objects/libs to link against. dnl That breaks our ability to build dll's with static libgcc/libstdc++/libssp. Override @@ -734,6 +746,16 @@ case $host in dnl It's safe to add these paths even if the functionality is disabled by dnl the user (--without-wallet or --without-gui for example). + dnl Homebrew may create symlinks in /usr/local/include for some packages. + dnl Because MacOS's clang internally adds "-I /usr/local/include" to its search + dnl paths, this will negate efforts to use -isystem for those packages, as they + dnl will be found first in /usr/local. Use the internal "-internal-isystem" + dnl option to system-ify all /usr/local/include paths without adding it to the list + dnl of search paths in case it's not already there. + if test "$suppress_external_warnings" != "no"; then + AX_CHECK_PREPROC_FLAG([-Xclang -internal-isystem/usr/local/include], [CORE_CPPFLAGS="$CORE_CPPFLAGS -Xclang -internal-isystem/usr/local/include"], [], [$CXXFLAG_WERROR]) + fi + if test "$use_bdb" != "no" && $BREW list --versions berkeley-db@4 >/dev/null && test "$BDB_CFLAGS" = "" && test "$BDB_LIBS" = ""; then bdb_prefix=$($BREW --prefix berkeley-db@4 2>/dev/null) dnl This must precede the call to BITCOIN_FIND_BDB48 below. @@ -883,6 +905,9 @@ AC_C_BIGENDIAN dnl Check for pthread compile/link requirements AX_PTHREAD +dnl Check if -latomic is required for <std::atomic> +CHECK_ATOMIC + dnl The following macro will add the necessary defines to bitcoin-config.h, but dnl they also need to be passed down to any subprojects. Pull the results out of dnl the cache and add them to CPPFLAGS. @@ -985,7 +1010,7 @@ if test "$TARGET_OS" = "darwin"; then AX_CHECK_LINK_FLAG([-Wl,-bind_at_load], [HARDENED_LDFLAGS="$HARDENED_LDFLAGS -Wl,-bind_at_load"], [], [$LDFLAG_WERROR]) 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_HEADERS([endian.h sys/endian.h byteswap.h unistd.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],[CHECK_SOCKET],, [#include <sys/types.h> @@ -998,6 +1023,8 @@ AC_CHECK_DECLS([setsid]) AC_CHECK_DECLS([pipe2]) +AC_CHECK_FUNCS([timingsafe_bcmp]) + AC_CHECK_DECLS([le16toh, le32toh, le64toh, htole16, htole32, htole64, be16toh, be32toh, be64toh, htobe16, htobe32, htobe64],,, [#if HAVE_ENDIAN_H #include <endian.h> @@ -1259,8 +1286,8 @@ AC_LINK_IFELSE( AC_MSG_CHECKING([for ::_wsystem]) AC_LINK_IFELSE( [ AC_LANG_PROGRAM( - [[ ]], - [[ int nErr = ::_wsystem(""); ]] + [[ #include <stdlib.h> ]], + [[ int nErr = ::_wsystem(NULL); ]] )], [ AC_MSG_RESULT([yes]); have_any_system=yes], [ AC_MSG_RESULT([no]) ] @@ -1297,6 +1324,7 @@ if test "$enable_fuzz" = "yes"; then bitcoin_enable_qt_test=no bitcoin_enable_qt_dbus=no use_bench=no + use_tests=no use_external_signer=no use_upnp=no use_natpmp=no @@ -1454,6 +1482,11 @@ if test "$use_boost" = "yes"; then dnl we don't use multi_index serialization BOOST_CPPFLAGS="$BOOST_CPPFLAGS -DBOOST_MULTI_INDEX_DISABLE_SERIALIZATION" + dnl Prevent use of std::unary_function, which was removed in C++17, + dnl and will generate warnings with newer compilers. + dnl See: https://github.com/boostorg/container_hash/issues/22. + BOOST_CPPFLAGS="$BOOST_CPPFLAGS -DBOOST_NO_CXX98_FUNCTION_BASE" + if test "$enable_debug" = "yes" || test "$enable_fuzz" = "yes"; then BOOST_CPPFLAGS="$BOOST_CPPFLAGS -DBOOST_MULTI_INDEX_ENABLE_SAFE_MODE" fi @@ -1552,7 +1585,8 @@ fi dnl libevent check -if test "$build_bitcoin_cli$build_bitcoind$bitcoin_enable_qt$use_tests$use_bench" != "nonononono"; then +use_libevent=no +if test "$build_bitcoin_cli$build_bitcoind$bitcoin_enable_qt$enable_fuzz_binary$use_tests$use_bench" != "nononononono"; then PKG_CHECK_MODULES([EVENT], [libevent >= 2.1.8], [use_libevent=yes], [AC_MSG_ERROR([libevent version 2.1.8 or greater not found.])]) if test "$TARGET_OS" != "windows"; then PKG_CHECK_MODULES([EVENT_PTHREADS], [libevent_pthreads >= 2.1.8], [], [AC_MSG_ERROR([libevent_pthreads version 2.1.8 or greater not found.])]) @@ -1671,11 +1705,12 @@ AM_CONDITIONAL([BUILD_BITCOIN_UTIL], [test $build_bitcoin_util = "yes"]) AC_MSG_RESULT($build_bitcoin_util) AC_MSG_CHECKING([whether to build experimental bitcoin-chainstate]) -if test "$build_experimental_kernel_lib" = "no"; then -AC_MSG_ERROR([experimental bitcoin-chainstate cannot be built without the experimental bitcoinkernel library. Use --with-experimental-kernel-lib]); -else - AM_CONDITIONAL([BUILD_BITCOIN_CHAINSTATE], [test $build_bitcoin_chainstate = "yes"]) +if test "$build_bitcoin_chainstate" = "yes"; then + if test "$build_experimental_kernel_lib" = "no"; then + AC_MSG_ERROR([experimental bitcoin-chainstate cannot be built without the experimental bitcoinkernel library. Use --with-experimental-kernel-lib]); + fi fi +AM_CONDITIONAL([BUILD_BITCOIN_CHAINSTATE], [test $build_bitcoin_chainstate = "yes"]) AC_MSG_RESULT($build_bitcoin_chainstate) AC_MSG_CHECKING([whether to build libraries]) @@ -1839,8 +1874,8 @@ else AC_MSG_RESULT([no]) fi -if test "$build_bitcoin_wallet$build_bitcoin_cli$build_bitcoin_tx$build_bitcoin_libs$build_bitcoind$bitcoin_enable_qt$use_bench$use_tests" = "nononononononono"; then - AC_MSG_ERROR([No targets! Please specify at least one of: --with-utils --with-libs --with-daemon --with-gui --enable-bench or --enable-tests]) +if test "$build_bitcoin_wallet$build_bitcoin_cli$build_bitcoin_tx$build_bitcoin_libs$build_bitcoind$bitcoin_enable_qt$enable_fuzz_binary$use_bench$use_tests" = "nonononononononono"; then + AC_MSG_ERROR([No targets! Please specify at least one of: --with-utils --with-libs --with-daemon --with-gui --enable-fuzz(-binary) --enable-bench or --enable-tests]) fi AM_CONDITIONAL([TARGET_DARWIN], [test "$TARGET_OS" = "darwin"]) @@ -1894,6 +1929,7 @@ AC_SUBST(COPYRIGHT_HOLDERS_SUBSTITUTION, "_COPYRIGHT_HOLDERS_SUBSTITUTION") AC_SUBST(COPYRIGHT_HOLDERS_FINAL, "_COPYRIGHT_HOLDERS_FINAL") AC_SUBST(BITCOIN_DAEMON_NAME) AC_SUBST(BITCOIN_GUI_NAME) +AC_SUBST(BITCOIN_TEST_NAME) AC_SUBST(BITCOIN_CLI_NAME) AC_SUBST(BITCOIN_TX_NAME) AC_SUBST(BITCOIN_UTIL_NAME) @@ -1965,6 +2001,9 @@ AC_CONFIG_LINKS([test/functional/test_runner.py:test/functional/test_runner.py]) AC_CONFIG_LINKS([test/fuzz/test_runner.py:test/fuzz/test_runner.py]) AC_CONFIG_LINKS([test/util/test_runner.py:test/util/test_runner.py]) AC_CONFIG_LINKS([test/util/rpcauth-test.py:test/util/rpcauth-test.py]) +AC_CONFIG_LINKS([src/qt/Makefile:src/qt/Makefile]) +AC_CONFIG_LINKS([src/qt/test/Makefile:src/qt/test/Makefile]) +AC_CONFIG_LINKS([src/test/Makefile:src/test/Makefile]) dnl boost's m4 checks do something really nasty: they export these vars. As a dnl result, they leak into secp256k1's configure and crazy things happen. @@ -2036,5 +2075,6 @@ echo " CPPFLAGS = $DEBUG_CPPFLAGS $HARDENED_CPPFLAGS $CORE_CPPFLAGS $CPP echo " CXX = $CXX" echo " CXXFLAGS = $LTO_CXXFLAGS $DEBUG_CXXFLAGS $HARDENED_CXXFLAGS $WARN_CXXFLAGS $NOWARN_CXXFLAGS $ERROR_CXXFLAGS $GPROF_CXXFLAGS $CORE_CXXFLAGS $CXXFLAGS" echo " LDFLAGS = $LTO_LDFLAGS $PTHREAD_LIBS $HARDENED_LDFLAGS $GPROF_LDFLAGS $CORE_LDFLAGS $LDFLAGS" +echo " AR = $AR" echo " ARFLAGS = $ARFLAGS" echo diff --git a/contrib/builder-keys/keys.txt b/contrib/builder-keys/keys.txt index c70069b440..f8377cce33 100644 --- a/contrib/builder-keys/keys.txt +++ b/contrib/builder-keys/keys.txt @@ -19,6 +19,7 @@ BF6273FAEF7CC0BA1F562E50989F6B3048A116B5 Dev Random (devrandom) D35176BE9264832E4ACA8986BF0792FBE95DC863 fivepiece (fivepiece) 6F993B250557E7B016ADE5713BDCDA2D87A881D9 Fuzzbawls (Fuzzbawls) 01CDF4627A3B88AAE4A571C87588242FBE38D3A8 Gavin Andresen (gavinandresen) +6B002C6EA3F91B1B0DF0C9BC8F617F1200A6D25C Gloria Zhao (glozow) D1DBF2C4B96F2DEBF4C16654410108112E7EA81F Hennadii Stepanov (hebasto) A2FD494D0021AA9B4FA58F759102B7AE654A4A5A Ilyas Ridhuan (IlyasRidhuan) 2688F5A9A4BE0F295E921E8A25F27A38A47AD566 James O'Beirne (jamesob) diff --git a/contrib/devtools/copyright_header.py b/contrib/devtools/copyright_header.py index e20eb4b0d2..680de1f1b3 100755 --- a/contrib/devtools/copyright_header.py +++ b/contrib/devtools/copyright_header.py @@ -35,7 +35,6 @@ EXCLUDE_DIRS = [ "src/leveldb/", "src/minisketch", "src/secp256k1/", - "src/univalue/", "src/crc32c/", ] diff --git a/contrib/devtools/gen-manpages.py b/contrib/devtools/gen-manpages.py index 26612cc444..38cdb3acb8 100755 --- a/contrib/devtools/gen-manpages.py +++ b/contrib/devtools/gen-manpages.py @@ -36,7 +36,7 @@ versions = [] for relpath in BINARIES: abspath = os.path.join(builddir, relpath) try: - r = subprocess.run([abspath, '--version'], stdout=subprocess.PIPE, universal_newlines=True) + r = subprocess.run([abspath, "--version"], stdout=subprocess.PIPE, check=True, universal_newlines=True) except IOError: print(f'{abspath} not found or not an executable', file=sys.stderr) sys.exit(1) diff --git a/contrib/devtools/iwyu/bitcoin.core.imp b/contrib/devtools/iwyu/bitcoin.core.imp index ce7786f58c..919ffab102 100644 --- a/contrib/devtools/iwyu/bitcoin.core.imp +++ b/contrib/devtools/iwyu/bitcoin.core.imp @@ -3,4 +3,5 @@ { include: [ "<bits/termios-c_lflag.h>", private, "<termios.h>", public ] }, { include: [ "<bits/termios-struct.h>", private, "<termios.h>", public ] }, { include: [ "<bits/termios-tcflow.h>", private, "<termios.h>", public ] }, + { include: [ "<bits/chrono.h>", private, "<chrono>", public ] }, ] diff --git a/contrib/devtools/symbol-check.py b/contrib/devtools/symbol-check.py index a419e392ee..4b1cceb57c 100755 --- a/contrib/devtools/symbol-check.py +++ b/contrib/devtools/symbol-check.py @@ -35,7 +35,6 @@ import lief #type:ignore MAX_VERSIONS = { 'GCC': (4,8,0), 'GLIBC': { - lief.ELF.ARCH.i386: (2,18), lief.ELF.ARCH.x86_64: (2,18), lief.ELF.ARCH.ARM: (2,18), lief.ELF.ARCH.AARCH64:(2,18), @@ -45,22 +44,16 @@ MAX_VERSIONS = { 'LIBATOMIC': (1,0), 'V': (0,5,0), # xkb (bitcoin-qt only) } -# See here for a description of _IO_stdin_used: -# https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=634261#109 # Ignore symbols that are exported as part of every executable IGNORE_EXPORTS = { -'_edata', '_end', '__end__', '_init', '__bss_start', '__bss_start__', '_bss_end__', -'__bss_end__', '_fini', '_IO_stdin_used', 'stdin', 'stdout', 'stderr', -'environ', '_environ', '__environ', +'environ', '_environ', '__environ', '_fini', '_init', 'stdin', +'stdout', 'stderr', } # Expected linker-loader names can be found here: # https://sourceware.org/glibc/wiki/ABIList?action=recall&rev=16 ELF_INTERPRETER_NAMES: Dict[lief.ELF.ARCH, Dict[lief.ENDIANNESS, str]] = { - lief.ELF.ARCH.i386: { - lief.ENDIANNESS.LITTLE: "/lib/ld-linux.so.2", - }, lief.ELF.ARCH.x86_64: { lief.ENDIANNESS.LITTLE: "/lib64/ld-linux-x86-64.so.2", }, diff --git a/contrib/devtools/test-symbol-check.py b/contrib/devtools/test-symbol-check.py index b4c112b266..2881e3efac 100755 --- a/contrib/devtools/test-symbol-check.py +++ b/contrib/devtools/test-symbol-check.py @@ -39,7 +39,7 @@ class TestSymbolChecks(unittest.TestCase): cc = determine_wellknown_cmd('CC', 'gcc') # there's no way to do this test for RISC-V at the moment; we build for - # RISC-V in a glibc 2.27 envinonment and we allow all symbols from 2.27. + # RISC-V in a glibc 2.27 environment and we allow all symbols from 2.27. if 'riscv' in get_machine(cc): self.skipTest("test not available for RISC-V") diff --git a/contrib/guix/INSTALL.md b/contrib/guix/INSTALL.md index 68aae18731..a9a41ddff6 100644 --- a/contrib/guix/INSTALL.md +++ b/contrib/guix/INSTALL.md @@ -72,11 +72,11 @@ writing (July 2021). Guix is expected to be more widely packaged over time. For an up-to-date view on Guix's package status/version across distros, please see: https://repology.org/project/guix/versions -### Debian 11 (Bullseye)/Ubuntu 21.04 (Hirsute Hippo) +### Debian / Ubuntu Guix v1.2.0 is available as a distribution package starting in [Debian 11](https://packages.debian.org/bullseye/guix) and [Ubuntu -21.04](https://packages.ubuntu.com/hirsute/guix). +21.04](https://packages.ubuntu.com/search?keywords=guix). Note that if you intend on using Guix without using any substitutes (more details [here][security-model]), v1.2.0 has a known problem when building GnuTLS diff --git a/contrib/guix/README.md b/contrib/guix/README.md index af5607c710..ed6ac8d589 100644 --- a/contrib/guix/README.md +++ b/contrib/guix/README.md @@ -382,7 +382,7 @@ https://ci.guix.gnu.org is automatically used unless the `--no-substitutes` flag is supplied. This default list of substitute servers is overridable both on a `guix-daemon` level and when you invoke `guix` commands. See examples below for the various ways of adding dongcarl's substitute server after having [authorized -his signing key](#authorize-the-signing-keys). +his signing key](#step-1-authorize-the-signing-keys). Change the **default list** of substitute servers by starting `guix-daemon` with the `--substitute-urls` option (you will likely need to edit your init script): diff --git a/contrib/guix/libexec/build.sh b/contrib/guix/libexec/build.sh index cdf0020d4d..f39f83d443 100755 --- a/contrib/guix/libexec/build.sh +++ b/contrib/guix/libexec/build.sh @@ -196,7 +196,6 @@ make -C depends --jobs="$JOBS" HOST="$HOST" \ x86_64_linux_RANLIB=x86_64-linux-gnu-ranlib \ x86_64_linux_NM=x86_64-linux-gnu-nm \ x86_64_linux_STRIP=x86_64-linux-gnu-strip \ - qt_config_opts_x86_64_linux='-platform linux-g++ -xplatform bitcoin-linux-g++' \ FORCE_USE_SYSTEM_CLANG=1 @@ -223,6 +222,7 @@ CONFIGFLAGS="--enable-reduce-exports --disable-bench --disable-gui-tests --disab # CFLAGS HOST_CFLAGS="-O2 -g" +HOST_CFLAGS+=$(find /gnu/store -maxdepth 1 -mindepth 1 -type d -exec echo -n " -ffile-prefix-map={}=/usr" \;) case "$HOST" in *linux*) HOST_CFLAGS+=" -ffile-prefix-map=${PWD}=." ;; *mingw*) HOST_CFLAGS+=" -fno-ident" ;; @@ -249,10 +249,6 @@ case "$HOST" in *powerpc64*) HOST_LDFLAGS="${HOST_LDFLAGS} -Wl,--no-tls-get-addr-optimize" ;; esac -case "$HOST" in - powerpc64-linux-*|riscv64-linux-*) HOST_LDFLAGS="${HOST_LDFLAGS} -Wl,-z,noexecstack" ;; -esac - # Make $HOST-specific native binaries from depends available in $PATH export PATH="${BASEPREFIX}/${HOST}/native/bin:${PATH}" mkdir -p "$DISTSRC" @@ -372,6 +368,8 @@ mkdir -p "$DISTSRC" # has not been run before buildling, this file will be a stub cp "${DISTSRC}/share/examples/bitcoin.conf" "${DISTNAME}/" + cp -r "${DISTSRC}/share/rpcauth" "${DISTNAME}/share/" + # Finally, deterministically produce {non-,}debug binary tarballs ready # for release case "$HOST" in diff --git a/contrib/guix/manifest.scm b/contrib/guix/manifest.scm index 34a9c608db..8e5c89cc5e 100644 --- a/contrib/guix/manifest.scm +++ b/contrib/guix/manifest.scm @@ -21,7 +21,6 @@ (gnu packages llvm) (gnu packages mingw) (gnu packages moreutils) - (gnu packages perl) (gnu packages pkg-config) (gnu packages python) (gnu packages python-crypto) @@ -78,6 +77,11 @@ http://www.linuxfromscratch.org/hlfs/view/development/chapter05/gcc-pass1.html" (("-rpath=") "-rpath-link=")) #t)))))))) +(define building-on (string-append (list-ref (string-split (%current-system) #\-) 0) "-guix-linux-gnu")) + +(define (explicit-cross-configure package) + (package-with-extra-configure-variable package "--build" building-on)) + (define (make-cross-toolchain target base-gcc-for-libc base-kernel-headers @@ -87,9 +91,9 @@ http://www.linuxfromscratch.org/hlfs/view/development/chapter05/gcc-pass1.html" (let* ((xbinutils (cross-binutils target)) ;; 1. Build a cross-compiling gcc without targeting any libc, derived ;; from BASE-GCC-FOR-LIBC - (xgcc-sans-libc (cross-gcc target - #:xgcc base-gcc-for-libc - #:xbinutils xbinutils)) + (xgcc-sans-libc (explicit-cross-configure (cross-gcc target + #:xgcc base-gcc-for-libc + #:xbinutils xbinutils))) ;; 2. Build cross-compiled kernel headers with XGCC-SANS-LIBC, derived ;; from BASE-KERNEL-HEADERS (xkernel (cross-kernel-headers target @@ -98,17 +102,17 @@ http://www.linuxfromscratch.org/hlfs/view/development/chapter05/gcc-pass1.html" xbinutils)) ;; 3. Build a cross-compiled libc with XGCC-SANS-LIBC and XKERNEL, ;; derived from BASE-LIBC - (xlibc (cross-libc target - base-libc - xgcc-sans-libc - xbinutils - xkernel)) + (xlibc (explicit-cross-configure (cross-libc target + base-libc + xgcc-sans-libc + xbinutils + xkernel))) ;; 4. Build a cross-compiling gcc targeting XLIBC, derived from ;; BASE-GCC - (xgcc (cross-gcc target - #:xgcc base-gcc - #:xbinutils xbinutils - #:libc xlibc))) + (xgcc (explicit-cross-configure (cross-gcc target + #:xgcc base-gcc + #:xbinutils xbinutils + #:libc xlibc)))) ;; Define a meta-package that propagates the resulting XBINUTILS, XLIBC, and ;; XGCC (package @@ -132,26 +136,19 @@ chain for " target " development.")) (define base-gcc gcc-10) (define base-linux-kernel-headers linux-libre-headers-5.15) -;; Building glibc with stack smashing protector first landed in glibc 2.25, use -;; this function to disable for older glibcs -;; -;; From glibc 2.25 changelog: -;; -;; * Most of glibc can now be built with the stack smashing protector enabled. -;; It is recommended to build glibc with --enable-stack-protector=strong. -;; Implemented by Nick Alcock (Oracle). -(define (make-glibc-without-ssp xglibc) - (package-with-extra-configure-variable - (package-with-extra-configure-variable - xglibc "libc_cv_ssp" "no") - "libc_cv_ssp_strong" "no")) +;; https://gcc.gnu.org/install/configure.html +(define (hardened-gcc gcc) + (package-with-extra-configure-variable ( + package-with-extra-configure-variable gcc + "--enable-default-ssp" "yes") + "--enable-default-pie" "yes")) (define* (make-bitcoin-cross-toolchain target #:key (base-gcc-for-libc base-gcc) (base-kernel-headers base-linux-kernel-headers) - (base-libc (make-glibc-without-ssp (make-glibc-without-werror glibc-2.24))) - (base-gcc (make-gcc-rpath-link base-gcc))) + (base-libc (make-glibc-with-bind-now (make-glibc-without-werror glibc-2.24))) + (base-gcc (make-gcc-rpath-link (hardened-gcc base-gcc)))) "Convenience wrapper around MAKE-CROSS-TOOLCHAIN with default values desirable for building Bitcoin Core release binaries." (make-cross-toolchain target @@ -161,7 +158,10 @@ desirable for building Bitcoin Core release binaries." base-gcc)) (define (make-gcc-with-pthreads gcc) - (package-with-extra-configure-variable gcc "--enable-threads" "posix")) + (package-with-extra-configure-variable + (package-with-extra-patches gcc + (search-our-patches "gcc-10-remap-guix-store.patch")) + "--enable-threads" "posix")) (define (make-mingw-w64-cross-gcc cross-gcc) (package-with-extra-patches cross-gcc @@ -198,12 +198,17 @@ chain for " target " development.")) (define (make-nsis-for-gcc-10 base-nsis) (package-with-extra-patches base-nsis - (search-our-patches "nsis-gcc-10-memmove.patch"))) + (search-our-patches "nsis-gcc-10-memmove.patch" + "nsis-disable-installer-reloc.patch"))) + +(define (fix-ppc64-nx-default lief) + (package-with-extra-patches lief + (search-our-patches "lief-fix-ppc64-nx-default.patch"))) (define-public lief (package (name "python-lief") - (version "0.12.0") + (version "0.12.1") (source (origin (method git-fetch) @@ -213,8 +218,15 @@ chain for " target " development.")) (file-name (git-file-name name version)) (sha256 (base32 - "026jchj56q25v6gc0754dj9cj5hz5zaza8ij93y5ga94w20kzm9q")))) + "1xzbh3bxy4rw1yamnx68da1v5s56ay4g081cyamv67256g0qy2i1")))) (build-system python-build-system) + (arguments + `(#:phases + (modify-phases %standard-phases + (add-after 'unpack 'parallel-jobs + ;; build with multiple cores + (lambda _ + (substitute* "setup.py" (("self.parallel if self.parallel else 1") (number->string (parallel-job-count))))))))) (native-inputs `(("cmake" ,cmake))) (home-page "https://github.com/lief-project/LIEF") @@ -256,7 +268,7 @@ thus should be able to compile on most platforms where these exist.") (license license:gpl3+))) ; license is with openssl exception (define-public python-elfesteem - (let ((commit "87bbd79ab7e361004c98cc8601d4e5f029fd8bd5")) + (let ((commit "2eb1e5384ff7a220fd1afacd4a0170acff54fe56")) (package (name "python-elfesteem") (version (git-version "0.1" "1" commit)) @@ -269,8 +281,7 @@ thus should be able to compile on most platforms where these exist.") (file-name (git-file-name name commit)) (sha256 (base32 - "1nyvjisvyxyxnd0023xjf5846xd03lwawp5pfzr8vrky7wwm5maz")) - (patches (search-our-patches "elfsteem-value-error-python-39.patch")))) + "07x6p8clh11z8s1n2kdxrqwqm2almgc5qpkcr9ckb6y5ivjdr5r6")))) (build-system python-build-system) ;; There are no tests, but attempting to run python setup.py test leads to ;; PYTHONPATH problems, just disable the test @@ -403,6 +414,11 @@ thus should be able to compile on most platforms where these exist.") (string-append indent "@unittest.skip(\"Disabled by Guix\")\n" line))) + (substitute* "tests/test_validate.py" + (("^(.*)def test_revocation_mode_soft" line indent) + (string-append indent + "@unittest.skip(\"Disabled by Guix\")\n" + line))) #t)) (replace 'check (lambda _ @@ -521,6 +537,12 @@ inspecting signatures in Mach-O binaries.") (define (make-glibc-without-werror glibc) (package-with-extra-configure-variable glibc "enable_werror" "no")) +(define (make-glibc-with-stack-protector glibc) + (package-with-extra-configure-variable glibc "--enable-stack-protector" "all")) + +(define (make-glibc-with-bind-now glibc) + (package-with-extra-configure-variable glibc "--enable-bind-now" "yes")) + (define-public glibc-2.24 (package (inherit glibc-2.31) @@ -538,7 +560,8 @@ inspecting signatures in Mach-O binaries.") "glibc-versioned-locpath.patch" "glibc-2.24-elfm-loadaddr-dynamic-rewrite.patch" "glibc-2.24-no-build-time-cxx-header-run.patch" - "glibc-2.24-fcommon.patch")))))) + "glibc-2.24-fcommon.patch" + "glibc-2.24-guix-prefix.patch")))))) (define-public glibc-2.27/bitcoin-patched (package @@ -555,7 +578,8 @@ inspecting signatures in Mach-O binaries.") "1b2n1gxv9f4fd5yy68qjbnarhf8mf4vmlxk10i3328c1w5pmp0ca")) (patches (search-our-patches "glibc-ldd-x86_64.patch" "glibc-2.27-riscv64-Use-__has_include-to-include-asm-syscalls.h.patch" - "glibc-2.27-dont-redefine-nss-database.patch")))))) + "glibc-2.27-dont-redefine-nss-database.patch" + "glibc-2.27-guix-prefix.patch")))))) (packages->manifest (append @@ -581,7 +605,7 @@ inspecting signatures in Mach-O binaries.") xz ;; Build tools gnu-make - libtool + libtool-2.4.7 autoconf-2.71 automake pkg-config @@ -590,12 +614,11 @@ inspecting signatures in Mach-O binaries.") gcc-toolchain-10 (list gcc-toolchain-10 "static") ;; Scripting - perl python-3 ;; Git - git + git-minimal ;; Tests - lief) + (fix-ppc64-nx-default lief)) (let ((target (getenv "HOST"))) (cond ((string-suffix? "-mingw32" target) ;; Windows @@ -606,8 +629,8 @@ inspecting signatures in Mach-O binaries.") ((string-contains target "-linux-") (list (cond ((string-contains target "riscv64-") (make-bitcoin-cross-toolchain target - #:base-libc (make-glibc-without-werror glibc-2.27/bitcoin-patched) - #:base-kernel-headers base-linux-kernel-headers)) + #:base-libc (make-glibc-with-stack-protector + (make-glibc-with-bind-now (make-glibc-without-werror glibc-2.27/bitcoin-patched))))) (else (make-bitcoin-cross-toolchain target))))) ((string-contains target "darwin") diff --git a/contrib/guix/patches/elfsteem-value-error-python-39.patch b/contrib/guix/patches/elfsteem-value-error-python-39.patch deleted file mode 100644 index 21e1228afd..0000000000 --- a/contrib/guix/patches/elfsteem-value-error-python-39.patch +++ /dev/null @@ -1,13 +0,0 @@ -diff --git a/examples/otool.py b/examples/otool.py -index 2b8efc0..d797b2e 100755 ---- a/examples/otool.py -+++ b/examples/otool.py -@@ -342,7 +342,7 @@ if __name__ == '__main__': - try: - e = macho_init.MACHO(raw, - parseSymbols = False) -- except ValueError, err: -+ except ValueError as err: - print("%s:" %file) - print(" %s" % err) - continue diff --git a/contrib/guix/patches/gcc-10-remap-guix-store.patch b/contrib/guix/patches/gcc-10-remap-guix-store.patch new file mode 100644 index 0000000000..a47ef7a2df --- /dev/null +++ b/contrib/guix/patches/gcc-10-remap-guix-store.patch @@ -0,0 +1,25 @@ +From aad25427e74f387412e8bc9a9d7bbc6c496c792f Mon Sep 17 00:00:00 2001 +From: Andrew Chow <achow101-github@achow101.com> +Date: Wed, 6 Jul 2022 16:49:41 -0400 +Subject: [PATCH] guix: remap guix store paths to /usr + +--- + libgcc/Makefile.in | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/libgcc/Makefile.in b/libgcc/Makefile.in +index 851e7657d07..476c2becd1c 100644 +--- a/libgcc/Makefile.in ++++ b/libgcc/Makefile.in +@@ -854,7 +854,7 @@ endif + # libgcc_eh.a, only LIB2ADDEH matters. If we do, only LIB2ADDEHSTATIC and + # LIB2ADDEHSHARED matter. (Usually all three are identical.) + +-c_flags := -fexceptions ++c_flags := -fexceptions $(shell find /gnu/store -maxdepth 1 -mindepth 1 -type d -exec echo -n " -ffile-prefix-map={}=/usr" \;) + + ifeq ($(enable_shared),yes) + +-- +2.37.0 + diff --git a/contrib/guix/patches/glibc-2.24-guix-prefix.patch b/contrib/guix/patches/glibc-2.24-guix-prefix.patch new file mode 100644 index 0000000000..875e8cd611 --- /dev/null +++ b/contrib/guix/patches/glibc-2.24-guix-prefix.patch @@ -0,0 +1,25 @@ +Without ffile-prefix-map, the debug symbols will contain paths for the +guix store which will include the hashes of each package. However, the +hash for the same package will differ when on different architectures. +In order to be reproducible regardless of the architecture used to build +the package, map all guix store prefixes to something fixed, e.g. /usr. + +We might be able to drop this in favour of using --with-nonshared-cflags +when we being using newer versions of glibc. + +--- a/Makeconfig ++++ b/Makeconfig +@@ -950,6 +950,10 @@ object-suffixes-for-libc += .oS + # shared objects. We don't want to use CFLAGS-os because users may, for + # example, make that processor-specific. + CFLAGS-.oS = $(CFLAGS-.o) $(PIC-ccflag) ++ ++# Map Guix store paths to /usr ++CFLAGS-.oS += `find /gnu/store -maxdepth 1 -mindepth 1 -type d -exec echo -n " -ffile-prefix-map={}=/usr" \;` ++ + CPPFLAGS-.oS = $(CPPFLAGS-.o) -DPIC -DLIBC_NONSHARED=1 + libtype.oS = lib%_nonshared.a + endif +-- +2.35.1 + diff --git a/contrib/guix/patches/glibc-2.27-guix-prefix.patch b/contrib/guix/patches/glibc-2.27-guix-prefix.patch new file mode 100644 index 0000000000..d777af74f0 --- /dev/null +++ b/contrib/guix/patches/glibc-2.27-guix-prefix.patch @@ -0,0 +1,25 @@ +Without ffile-prefix-map, the debug symbols will contain paths for the +guix store which will include the hashes of each package. However, the +hash for the same package will differ when on different architectures. +In order to be reproducible regardless of the architecture used to build +the package, map all guix store prefixes to something fixed, e.g. /usr. + +We might be able to drop this in favour of using --with-nonshared-cflags +when we being using newer versions of glibc. + +--- a/Makeconfig ++++ b/Makeconfig +@@ -992,6 +992,10 @@ object-suffixes := + CPPFLAGS-.o = $(pic-default) + # libc.a must be compiled with -fPIE/-fpie for static PIE. + CFLAGS-.o = $(filter %frame-pointer,$(+cflags)) $(pie-default) ++ ++# Map Guix store paths to /usr ++CFLAGS-.o += `find /gnu/store -maxdepth 1 -mindepth 1 -type d -exec echo -n " -ffile-prefix-map={}=/usr" \;` ++ + libtype.o := lib%.a + object-suffixes += .o + ifeq (yes,$(build-shared)) +-- +2.35.1 + diff --git a/contrib/guix/patches/lief-fix-ppc64-nx-default.patch b/contrib/guix/patches/lief-fix-ppc64-nx-default.patch new file mode 100644 index 0000000000..101bc1ddc0 --- /dev/null +++ b/contrib/guix/patches/lief-fix-ppc64-nx-default.patch @@ -0,0 +1,29 @@ +Correct default for Binary::has_nx on ppc64 + +From the Linux kernel source: + + * This is the default if a program doesn't have a PT_GNU_STACK + * program header entry. The PPC64 ELF ABI has a non executable stack + * stack by default, so in the absence of a PT_GNU_STACK program header + * we turn execute permission off. + +This patch can be dropped the next time we update LIEF. + +diff --git a/src/ELF/Binary.cpp b/src/ELF/Binary.cpp +index a90be1ab..fd2d9764 100644 +--- a/src/ELF/Binary.cpp ++++ b/src/ELF/Binary.cpp +@@ -1084,7 +1084,12 @@ bool Binary::has_nx() const { + return segment->type() == SEGMENT_TYPES::PT_GNU_STACK; + }); + if (it_stack == std::end(segments_)) { +- return false; ++ if (header().machine_type() == ARCH::EM_PPC64) { ++ // The PPC64 ELF ABI has a non-executable stack by default. ++ return true; ++ } else { ++ return false; ++ } + } + + return !(*it_stack)->has(ELF_SEGMENT_FLAGS::PF_X); diff --git a/contrib/guix/patches/nsis-disable-installer-reloc.patch b/contrib/guix/patches/nsis-disable-installer-reloc.patch new file mode 100644 index 0000000000..4914527e56 --- /dev/null +++ b/contrib/guix/patches/nsis-disable-installer-reloc.patch @@ -0,0 +1,30 @@ +Patch NSIS so that it's installer stubs, produced at NSIS build time, +do not contain .reloc sections, which will exist by default when using +binutils/ld 2.36+. + +This ultimately fixes an issue when running the installer with the +"Force randomization for images (Mandatory ASLR)" setting active. + +This patch has not yet been sent upstream, because it's not clear if this +is the best fix, for the underlying issue, which seems to be that makensis +doesn't account for .reloc sections when it builds installers. + +The existence of a reloc section shouldn't be a problem, and, if anything, +is actually a requirement for working ASLR. All other Windows binaries we +produce contain them, and function correctly when under the same +"Force randomization for images (Mandatory ASLR)" setting. + +See: +https://github.com/bitcoin/bitcoin/issues/25726 +https://sourceforge.net/p/nsis/bugs/1131/ + +--- a/SCons/Config/gnu ++++ b/SCons/Config/gnu +@@ -102,6 +102,7 @@ stub_env.Append(LINKFLAGS = ['-mwindows']) # build windows executables + stub_env.Append(LINKFLAGS = ['$NODEFLIBS_FLAG']) # no standard libraries + stub_env.Append(LINKFLAGS = ['$ALIGN_FLAG']) # 512 bytes align + stub_env.Append(LINKFLAGS = ['$MAP_FLAG']) # generate map file ++stub_env.Append(LINKFLAGS = ['-Wl,--disable-reloc-section']) + + conf = FlagsConfigure(stub_env) + conf.CheckCompileFlag('-fno-tree-loop-distribute-patterns') # GCC 10: Don't generate msvcrt!memmove calls (bug #1248) diff --git a/contrib/macdeploy/README.md b/contrib/macdeploy/README.md index 64e36f22b9..599a0bfa6c 100644 --- a/contrib/macdeploy/README.md +++ b/contrib/macdeploy/README.md @@ -20,7 +20,7 @@ can be extracted from [Xcode_12.2.xip](https://download.developer.apple.com/Developer_Tools/Xcode_12.2/Xcode_12.2.xip). Alternatively, after logging in to your account go to 'Downloads', then 'More' -and search for [`Xcode_12.2`](https://developer.apple.com/download/all/?q=Xcode%2012.2). +and search for [`Xcode 12.2`](https://developer.apple.com/download/all/?q=Xcode%2012.2). An Apple ID and cookies enabled for the hostname are needed to download this. diff --git a/contrib/message-capture/message-capture-parser.py b/contrib/message-capture/message-capture-parser.py index 9988478f1b..33759ee713 100755 --- a/contrib/message-capture/message-capture-parser.py +++ b/contrib/message-capture/message-capture-parser.py @@ -79,7 +79,7 @@ def to_jsonable(obj: Any) -> Any: val = getattr(obj, slot, None) if slot in HASH_INTS and isinstance(val, int): ret[slot] = ser_uint256(val).hex() - elif slot in HASH_INT_VECTORS and isinstance(val[0], int): + elif slot in HASH_INT_VECTORS and all(isinstance(a, int) for a in val): ret[slot] = [ser_uint256(a).hex() for a in val] else: ret[slot] = to_jsonable(val) diff --git a/contrib/seeds/.gitignore b/contrib/seeds/.gitignore index e4a39d6093..d9a2451f70 100644 --- a/contrib/seeds/.gitignore +++ b/contrib/seeds/.gitignore @@ -1 +1,2 @@ seeds_main.txt +asmap-filled.dat diff --git a/contrib/seeds/README.md b/contrib/seeds/README.md index c53446bfb0..b2ea7522ac 100644 --- a/contrib/seeds/README.md +++ b/contrib/seeds/README.md @@ -8,21 +8,11 @@ and remove old versions as necessary (at a minimum when GetDesirableServiceFlags changes its default return value, as those are the services which seeds are added to addrman with). -The seeds compiled into the release are created from sipa's DNS seed data, like this: +The seeds compiled into the release are created from sipa's DNS seed and AS map +data. Run the following commands from the `/contrib/seeds` directory: curl https://bitcoin.sipa.be/seeds.txt.gz | gzip -dc > seeds_main.txt - python3 makeseeds.py < seeds_main.txt > nodes_main.txt + curl https://bitcoin.sipa.be/asmap-filled.dat > asmap-filled.dat + python3 makeseeds.py -a asmap-filled.dat < seeds_main.txt > nodes_main.txt cat nodes_main_manual.txt >> nodes_main.txt python3 generate-seeds.py . > ../../src/chainparamsseeds.h - -## Dependencies - -Ubuntu, Debian: - - sudo apt-get install python3-dnspython - -and/or for other operating systems: - - pip install dnspython - -See https://dnspython.readthedocs.io/en/latest/installation.html for more information. diff --git a/contrib/seeds/asmap.py b/contrib/seeds/asmap.py new file mode 100644 index 0000000000..e28e5cf532 --- /dev/null +++ b/contrib/seeds/asmap.py @@ -0,0 +1,815 @@ +# Copyright (c) 2022 Pieter Wuille +# Distributed under the MIT software license, see the accompanying +# file LICENSE or http://www.opensource.org/licenses/mit-license.php. + +""" +This module provides the ASNEntry and ASMap classes. +""" + +import copy +import ipaddress +import random +import unittest +from enum import Enum +from functools import total_ordering +from typing import Callable, Dict, Iterable, List, Optional, Tuple, Union, overload + +def net_to_prefix(net: Union[ipaddress.IPv4Network,ipaddress.IPv6Network]) -> List[bool]: + """ + Convert an IPv4 or IPv6 network to a prefix represented as a list of bits. + + IPv4 ranges are remapped to their IPv4-mapped IPv6 range (::ffff:0:0/96). + """ + num_bits = net.prefixlen + netrange = int.from_bytes(net.network_address.packed, 'big') + + # Map an IPv4 prefix into IPv6 space. + if isinstance(net, ipaddress.IPv4Network): + num_bits += 96 + netrange += 0xffff00000000 + + # Strip unused bottom bits. + assert (netrange & ((1 << (128 - num_bits)) - 1)) == 0 + return [((netrange >> (127 - i)) & 1) != 0 for i in range(num_bits)] + +def prefix_to_net(prefix: List[bool]) -> Union[ipaddress.IPv4Network,ipaddress.IPv6Network]: + """The reverse operation of net_to_prefix.""" + # Convert to number + netrange = sum(b << (127 - i) for i, b in enumerate(prefix)) + num_bits = len(prefix) + assert num_bits <= 128 + + # Return IPv4 range if in ::ffff:0:0/96 + if num_bits >= 96 and (netrange >> 32) == 0xffff: + return ipaddress.IPv4Network((netrange & 0xffffffff, num_bits - 96), True) + + # Return IPv6 range otherwise. + return ipaddress.IPv6Network((netrange, num_bits), True) + +# Shortcut for (prefix, ASN) entries. +ASNEntry = Tuple[List[bool], int] + +# Shortcut for (prefix, old ASN, new ASN) entries. +ASNDiff = Tuple[List[bool], int, int] + +class _VarLenCoder: + """ + A class representing a custom variable-length binary encoder/decoder for + integers. Each object represents a different coder, with different parameters + minval and clsbits. + + The encoding is easiest to describe using an example. Let's say minval=100 and + clsbits=[4,2,2,3]. In that case: + - x in [100..115]: encoded as [0] + [4-bit BE encoding of (x-100)]. + - x in [116..119]: encoded as [1,0] + [2-bit BE encoding of (x-116)]. + - x in [120..123]: encoded as [1,1,0] + [2-bit BE encoding of (x-120)]. + - x in [124..131]: encoded as [1,1,1] + [3-bit BE encoding of (x-124)]. + + In general, every number is encoded as: + - First, k "1"-bits, where k is the class the number falls in (there is one class + per element of clsbits). + - Then, a "0"-bit, unless k is the highest class, in which case there is nothing. + - Lastly, clsbits[k] bits encoding in big endian the position in its class that + number falls into. + - Every class k consists of 2^clsbits[k] consecutive integers. k=0 starts at minval, + other classes start one past the last element of the class before it. + """ + + def __init__(self, minval: int, clsbits: List[int]): + """Construct a new _VarLenCoder.""" + self._minval = minval + self._clsbits = clsbits + self._maxval = minval + sum(1 << b for b in clsbits) - 1 + + def can_encode(self, val: int) -> bool: + """Check whether value val is in the range this coder supports.""" + return self._minval <= val <= self._maxval + + def encode(self, val: int, ret: List[int]) -> None: + """Append encoding of val onto integer list ret.""" + + assert self._minval <= val <= self._maxval + val -= self._minval + bits = 0 + for k, bits in enumerate(self._clsbits): + if val >> bits: + # If the value will not fit in class k, subtract its range from v, + # emit a "1" bit and continue with the next class. + val -= 1 << bits + ret.append(1) + else: + if k + 1 < len(self._clsbits): + # Unless we're in the last class, emit a "0" bit. + ret.append(0) + break + # And then encode v (now the position within the class) in big endian. + ret.extend((val >> (bits - 1 - b)) & 1 for b in range(bits)) + + def encode_size(self, val: int) -> int: + """Compute how many bits are needed to encode val.""" + assert self._minval <= val <= self._maxval + val -= self._minval + ret = 0 + bits = 0 + for k, bits in enumerate(self._clsbits): + if val >> bits: + val -= 1 << bits + ret += 1 + else: + ret += k + 1 < len(self._clsbits) + break + return ret + bits + + def decode(self, stream, bitpos) -> Tuple[int,int]: + """Decode a number starting at bitpos in stream, returning value and new bitpos.""" + val = self._minval + bits = 0 + for k, bits in enumerate(self._clsbits): + bit = 0 + if k + 1 < len(self._clsbits): + bit = stream[bitpos] + bitpos += 1 + if not bit: + break + val += 1 << bits + for i in range(bits): + bit = stream[bitpos] + bitpos += 1 + val += bit << (bits - 1 - i) + return val, bitpos + +# Variable-length encoders used in the binary asmap format. +_CODER_INS = _VarLenCoder(0, [0, 0, 1]) +_CODER_ASN = _VarLenCoder(1, list(range(15, 25))) +_CODER_MATCH = _VarLenCoder(2, list(range(1, 9))) +_CODER_JUMP = _VarLenCoder(17, list(range(5, 31))) + +class _Instruction(Enum): + """One instruction in the binary asmap format.""" + # A return instruction, encoded as [0], returns a constant ASN. It is followed by + # an integer using the ASN encoding. + RETURN = 0 + # A jump instruction, encoded as [1,0] inspects the next unused bit in the input + # and either continues execution (if 0), or skips a specified number of bits (if 1). + # It is followed by an integer, and then two subprograms. The integer uses jump encoding + # and corresponds to the length of the first subprogram (so it can be skipped). + JUMP = 1 + # A match instruction, encoded as [1,1,0] inspects 1 or more of the next unused bits + # in the input with its argument. If they all match, execution continues. If they do + # not, failure is returned. If a default instruction has been executed before, instead + # of failure the default instruction's argument is returned. It is followed by an + # integer in match encoding, and a subprogram. That value is at least 2 bits and at + # most 9 bits. An n-bit value signifies matching (n-1) bits in the input with the lower + # (n-1) bits in the match value. + MATCH = 2 + # A default instruction, encoded as [1,1,1] sets the default variable to its argument, + # and continues execution. It is followed by an integer in ASN encoding, and a subprogram. + DEFAULT = 3 + # Not an actual instruction, but a way to encode the empty program that fails. In the + # encoder, it is used more generally to represent the failure case inside MATCH instructions, + # which may (if used inside the context of a DEFAULT instruction) actually correspond to + # a successful return. In this usage, they're always converted to an actual MATCH or RETURN + # before the top level is reached (see make_default below). + END = 4 + +class _BinNode: + """A class representing a (node of) the parsed binary asmap format.""" + + @overload + def __init__(self, ins: _Instruction): ... + @overload + def __init__(self, ins: _Instruction, arg1: int): ... + @overload + def __init__(self, ins: _Instruction, arg1: "_BinNode", arg2: "_BinNode"): ... + @overload + def __init__(self, ins: _Instruction, arg1: int, arg2: "_BinNode"): ... + + def __init__(self, ins: _Instruction, arg1=None, arg2=None): + """ + Construct a new asmap node. Possibilities are: + - _BinNode(_Instruction.RETURN, asn) + - _BinNode(_Instruction.JUMP, node_0, node_1) + - _BinNode(_Instruction.MATCH, val, node) + - _BinNode(_Instruction.DEFAULT, asn, node) + - _BinNode(_Instruction.END) + """ + self.ins = ins + self.arg1 = arg1 + self.arg2 = arg2 + if ins == _Instruction.RETURN: + assert isinstance(arg1, int) + assert arg2 is None + self.size = _CODER_INS.encode_size(ins.value) + _CODER_ASN.encode_size(arg1) + elif ins == _Instruction.JUMP: + assert isinstance(arg1, _BinNode) + assert isinstance(arg2, _BinNode) + self.size = (_CODER_INS.encode_size(ins.value) + _CODER_JUMP.encode_size(arg1.size) + + arg1.size + arg2.size) + elif ins == _Instruction.DEFAULT: + assert isinstance(arg1, int) + assert isinstance(arg2, _BinNode) + self.size = _CODER_INS.encode_size(ins.value) + _CODER_ASN.encode_size(arg1) + arg2.size + elif ins == _Instruction.MATCH: + assert isinstance(arg1, int) + assert isinstance(arg2, _BinNode) + self.size = (_CODER_INS.encode_size(ins.value) + _CODER_MATCH.encode_size(arg1) + + arg2.size) + elif ins == _Instruction.END: + assert arg1 is None + assert arg2 is None + self.size = 0 + else: + assert False + + @staticmethod + def make_end() -> "_BinNode": + """Constructor for a _BinNode with just an END instruction.""" + return _BinNode(_Instruction.END) + + @staticmethod + def make_leaf(val: int) -> "_BinNode": + """Constructor for a _BinNode of just a RETURN instruction.""" + assert val is not None and val > 0 + return _BinNode(_Instruction.RETURN, val) + + @staticmethod + def make_branch(node0: "_BinNode", node1: "_BinNode") -> "_BinNode": + """ + Construct a _BinNode corresponding to running either the node0 or node1 subprogram, + based on the next input bit. It exploits shortcuts that are possible in the encoding, + and uses either a JUMP, MATCH, or END instruction. + """ + if node0.ins == _Instruction.END and node1.ins == _Instruction.END: + return node0 + if node0.ins == _Instruction.END: + if node1.ins == _Instruction.MATCH and node1.arg1 <= 0xFF: + return _BinNode(node1.ins, node1.arg1 + (1 << node1.arg1.bit_length()), node1.arg2) + return _BinNode(_Instruction.MATCH, 3, node1) + if node1.ins == _Instruction.END: + if node0.ins == _Instruction.MATCH and node0.arg1 <= 0xFF: + return _BinNode(node0.ins, node0.arg1 + (1 << (node0.arg1.bit_length() - 1)), + node0.arg2) + return _BinNode(_Instruction.MATCH, 2, node0) + return _BinNode(_Instruction.JUMP, node0, node1) + + @staticmethod + def make_default(val: int, sub: "_BinNode") -> "_BinNode": + """ + Construct a _BinNode that corresponds to the specified subprogram, with the specified + default value. It exploits shortcuts that are possible in the encoding, and will use + either a DEFAULT or a RETURN instruction.""" + assert val is not None and val > 0 + if sub.ins == _Instruction.END: + return _BinNode(_Instruction.RETURN, val) + if sub.ins in (_Instruction.RETURN, _Instruction.DEFAULT): + return sub + return _BinNode(_Instruction.DEFAULT, val, sub) + +@total_ordering +class ASMap: + """ + A class whose objects represent a mapping from subnets to ASNs. + + Internally the mapping is stored as a binary trie, but can be converted + from/to a list of ASNEntry objects, and from/to the binary asmap file format. + + In the trie representation, nodes are represented as bare lists for efficiency + and ease of manipulation: + - [0] means an unassigned subnet (no ASN mapping for it is present) + - [int] means a subnet mapped entirely to the specified ASN. + - [node,node] means a subnet whose lower half and upper half have different + - mappings, represented by new trie nodes. + """ + + def update(self, prefix: List[bool], asn: int) -> None: + """Update this ASMap object to map prefix to the specified asn.""" + assert asn == 0 or _CODER_ASN.can_encode(asn) + + def recurse(node: List, offset: int) -> None: + if offset == len(prefix): + # Reached the end of prefix; overwrite this node. + node.clear() + node.append(asn) + return + if len(node) == 1: + # Need to descend into a leaf node; split it up. + oldasn = node[0] + node.clear() + node.append([oldasn]) + node.append([oldasn]) + # Descend into the node. + recurse(node[prefix[offset]], offset + 1) + # If the result is two identical leaf children, merge them. + if len(node[0]) == 1 and len(node[1]) == 1 and node[0] == node[1]: + oldasn = node[0][0] + node.clear() + node.append(oldasn) + recurse(self._trie, 0) + + def update_multi(self, entries: List[Tuple[List[bool], int]]) -> None: + """Apply multiple update operations, where longer prefixes take precedence.""" + entries.sort(key=lambda entry: len(entry[0])) + for prefix, asn in entries: + self.update(prefix, asn) + + def _set_trie(self, trie) -> None: + """Set trie directly. Internal use only.""" + def recurse(node: List) -> None: + if len(node) < 2: + return + recurse(node[0]) + recurse(node[1]) + if len(node[0]) == 2: + return + if node[0] == node[1]: + if len(node[0]) == 0: + node.clear() + else: + asn = node[0][0] + node.clear() + node.append(asn) + recurse(trie) + self._trie = trie + + def __init__(self, entries: Optional[Iterable[ASNEntry]] = None) -> None: + """Construct an ASMap object from an optional list of entries.""" + self._trie = [0] + if entries is not None: + def entry_key(entry): + """Sort function that places shorter prefixes first.""" + prefix, asn = entry + return len(prefix), prefix, asn + for prefix, asn in sorted(entries, key=entry_key): + self.update(prefix, asn) + + def lookup(self, prefix: List[bool]) -> Optional[int]: + """Look up a prefix. Returns ASN, or 0 if unassigned, or None if indeterminate.""" + node = self._trie + for bit in prefix: + if len(node) == 1: + break + node = node[bit] + if len(node) == 1: + return node[0] + return None + + def _to_entries_flat(self, fill: bool = False) -> List[ASNEntry]: + """Convert an ASMap object to a list of non-overlapping (prefix, asn) objects.""" + prefix : List[bool] = [] + + def recurse(node: List) -> List[ASNEntry]: + ret = [] + if len(node) == 1: + if node[0] > 0: + ret = [(list(prefix), node[0])] + elif len(node) == 2: + prefix.append(False) + ret = recurse(node[0]) + prefix[-1] = True + ret += recurse(node[1]) + prefix.pop() + if fill and len(ret) > 1: + asns = set(x[1] for x in ret) + if len(asns) == 1: + ret = [(list(prefix), list(asns)[0])] + return ret + return recurse(self._trie) + + def _to_entries_minimal(self, fill: bool = False) -> List[ASNEntry]: + """Convert a trie to a minimal list of ASNEntry objects, exploiting overlap.""" + prefix : List[bool] = [] + + def recurse(node: List) -> (Tuple[Dict[Optional[int], List[ASNEntry]], bool]): + if len(node) == 1 and node[0] == 0: + return {None if fill else 0: []}, True + if len(node) == 1: + return {node[0]: [], None: [(list(prefix), node[0])]}, False + ret: Dict[Optional[int], List[ASNEntry]] = {} + prefix.append(False) + left, lhole = recurse(node[0]) + prefix[-1] = True + right, rhole = recurse(node[1]) + prefix.pop() + hole = not fill and (lhole or rhole) + def candidate(ctx: Optional[int], res0: Optional[List[ASNEntry]], + res1: Optional[List[ASNEntry]]): + if res0 is not None and res1 is not None: + if ctx not in ret or len(res0) + len(res1) < len(ret[ctx]): + ret[ctx] = res0 + res1 + for ctx in set(left) | set(right): + candidate(ctx, left.get(ctx), right.get(ctx)) + candidate(ctx, left.get(None), right.get(ctx)) + candidate(ctx, left.get(ctx), right.get(None)) + if not hole: + for ctx in list(ret): + if ctx is not None: + candidate(None, [(list(prefix), ctx)], ret[ctx]) + if None in ret: + ret = {ctx:entries for ctx, entries in ret.items() + if ctx is None or len(entries) < len(ret[None])} + if hole: + ret = {ctx:entries for ctx, entries in ret.items() if ctx is None or ctx == 0} + return ret, hole + res, _ = recurse(self._trie) + return res[0] if 0 in res else res[None] + + def __str__(self) -> str: + """Convert this ASMap object to a string containing Python code constructing it.""" + return f"ASMap({self._trie})" + + def to_entries(self, overlapping: bool = True, fill: bool = False) -> List[ASNEntry]: + """ + Convert the mappings in this ASMap object to a list of ASNEntry objects. + + Arguments: + overlapping: Permit the subnets in the resulting ASNEntry to overlap. + Setting this can result in a shorter list. + fill: Permit the resulting ASNEntry objects to cover subnets that + are unassigned in this ASMap object. Setting this can + result in a shorter list. + """ + if overlapping: + return self._to_entries_minimal(fill) + return self._to_entries_flat(fill) + + @staticmethod + def from_random(num_leaves: int = 10, max_asn: int = 6, + unassigned_prob: float = 0.5) -> "ASMap": + """ + Construct a random ASMap object, with specified: + - Number of leaves in its trie (at least 1) + - Maximum ASN value (at least 1) + - Probability for leaf nodes to be unassigned + + The number of leaves in the resulting object may be less than what is + requested. This method is mostly intended for testing. + """ + assert num_leaves >= 1 + assert max_asn >= 1 or unassigned_prob == 1 + assert _CODER_ASN.can_encode(max_asn) + assert 0.0 <= unassigned_prob <= 1.0 + trie: List = [] + leaves = [trie] + ret = ASMap() + for i in range(1, num_leaves): + idx = random.randrange(i) + leaf = leaves[idx] + lastleaf = leaves.pop() + if idx + 1 < i: + leaves[idx] = lastleaf + leaf.append([]) + leaf.append([]) + leaves.append(leaf[0]) + leaves.append(leaf[1]) + for leaf in leaves: + if random.random() >= unassigned_prob: + leaf.append(random.randrange(1, max_asn + 1)) + else: + leaf.append(0) + #pylint: disable=protected-access + ret._set_trie(trie) + return ret + + def _to_binnode(self, fill: bool = False) -> _BinNode: + """Convert a trie to a _BinNode object.""" + def recurse(node: List) -> Tuple[Dict[Optional[int], _BinNode], bool]: + if len(node) == 1 and node[0] == 0: + return {(None if fill else 0): _BinNode.make_end()}, True + if len(node) == 1: + return {None: _BinNode.make_leaf(node[0]), node[0]: _BinNode.make_end()}, False + ret: Dict[Optional[int], _BinNode] = {} + left, lhole = recurse(node[0]) + right, rhole = recurse(node[1]) + hole = (lhole or rhole) and not fill + + def candidate(ctx: Optional[int], arg1, arg2, func: Callable): + if arg1 is not None and arg2 is not None: + cand = func(arg1, arg2) + if ctx not in ret or cand.size < ret[ctx].size: + ret[ctx] = cand + + for ctx in set(left) | set(right): + candidate(ctx, left.get(ctx), right.get(ctx), _BinNode.make_branch) + candidate(ctx, left.get(None), right.get(ctx), _BinNode.make_branch) + candidate(ctx, left.get(ctx), right.get(None), _BinNode.make_branch) + if not hole: + for ctx in set(ret) - set([None]): + candidate(None, ctx, ret[ctx], _BinNode.make_default) + if None in ret: + ret = {ctx:enc for ctx, enc in ret.items() + if ctx is None or enc.size < ret[None].size} + if hole: + ret = {ctx:enc for ctx, enc in ret.items() if ctx is None or ctx == 0} + return ret, hole + res, _ = recurse(self._trie) + return res[0] if 0 in res else res[None] + + @staticmethod + def _from_binnode(binnode: _BinNode) -> "ASMap": + """Construct an ASMap object from a _BinNode. Internal use only.""" + def recurse(node: _BinNode, default: int) -> List: + if node.ins == _Instruction.RETURN: + return [node.arg1] + if node.ins == _Instruction.JUMP: + return [recurse(node.arg1, default), recurse(node.arg2, default)] + if node.ins == _Instruction.MATCH: + val = node.arg1 + sub = recurse(node.arg2, default) + while val >= 2: + bit = val & 1 + val >>= 1 + if bit: + sub = [[default], sub] + else: + sub = [sub, [default]] + return sub + assert node.ins == _Instruction.DEFAULT + return recurse(node.arg2, node.arg1) + ret = ASMap() + if binnode.ins != _Instruction.END: + #pylint: disable=protected-access + ret._set_trie(recurse(binnode, 0)) + return ret + + def to_binary(self, fill: bool = False) -> bytes: + """ + Convert this ASMap object to binary. + + Argument: + fill: permit the resulting binary encoder to contain mappers for + unassigned subnets in this ASMap object. Doing so may + reduce the size of the encoding. + Returns: + A bytes object with the encoding of this ASMap object. + """ + bits: List[int] = [] + + def recurse(node: _BinNode) -> None: + _CODER_INS.encode(node.ins.value, bits) + if node.ins == _Instruction.RETURN: + _CODER_ASN.encode(node.arg1, bits) + elif node.ins == _Instruction.JUMP: + _CODER_JUMP.encode(node.arg1.size, bits) + recurse(node.arg1) + recurse(node.arg2) + elif node.ins == _Instruction.DEFAULT: + _CODER_ASN.encode(node.arg1, bits) + recurse(node.arg2) + else: + assert node.ins == _Instruction.MATCH + _CODER_MATCH.encode(node.arg1, bits) + recurse(node.arg2) + + binnode = self._to_binnode(fill) + if binnode.ins != _Instruction.END: + recurse(binnode) + + val = 0 + nbits = 0 + ret = [] + for bit in bits: + val += (bit << nbits) + nbits += 1 + if nbits == 8: + ret.append(val) + val = 0 + nbits = 0 + if nbits: + ret.append(val) + return bytes(ret) + + @staticmethod + def from_binary(bindata: bytes) -> Optional["ASMap"]: + """Decode an ASMap object from the provided binary encoding.""" + + bits: List[int] = [] + for byte in bindata: + bits.extend((byte >> i) & 1 for i in range(8)) + + def recurse(bitpos: int) -> Tuple[_BinNode, int]: + insval, bitpos = _CODER_INS.decode(bits, bitpos) + ins = _Instruction(insval) + if ins == _Instruction.RETURN: + asn, bitpos = _CODER_ASN.decode(bits, bitpos) + return _BinNode(ins, asn), bitpos + if ins == _Instruction.JUMP: + jump, bitpos = _CODER_JUMP.decode(bits, bitpos) + left, bitpos1 = recurse(bitpos) + if bitpos1 != bitpos + jump: + raise ValueError("Inconsistent jump") + right, bitpos = recurse(bitpos1) + return _BinNode(ins, left, right), bitpos + if ins == _Instruction.MATCH: + match, bitpos = _CODER_MATCH.decode(bits, bitpos) + sub, bitpos = recurse(bitpos) + return _BinNode(ins, match, sub), bitpos + assert ins == _Instruction.DEFAULT + asn, bitpos = _CODER_ASN.decode(bits, bitpos) + sub, bitpos = recurse(bitpos) + return _BinNode(ins, asn, sub), bitpos + + if len(bits) == 0: + binnode = _BinNode(_Instruction.END) + else: + try: + binnode, bitpos = recurse(0) + except (ValueError, IndexError): + return None + if bitpos < len(bits) - 7: + return None + if not all(bit == 0 for bit in bits[bitpos:]): + return None + + return ASMap._from_binnode(binnode) + + def __lt__(self, other: "ASMap") -> bool: + return self._trie < other._trie + + def __eq__(self, other: object) -> bool: + if isinstance(other, ASMap): + return self._trie == other._trie + return False + + def extends(self, req: "ASMap") -> bool: + """Determine whether this matches req for all subranges where req is assigned.""" + def recurse(actual: List, require: List) -> bool: + if len(require) == 1 and require[0] == 0: + return True + if len(require) == 1: + if len(actual) == 1: + return bool(require[0] == actual[0]) + return recurse(actual[0], require) and recurse(actual[1], require) + if len(actual) == 2: + return recurse(actual[0], require[0]) and recurse(actual[1], require[1]) + return recurse(actual, require[0]) and recurse(actual, require[1]) + assert isinstance(req, ASMap) + #pylint: disable=protected-access + return recurse(self._trie, req._trie) + + def diff(self, other: "ASMap") -> List[ASNDiff]: + """Compute the diff from self to other.""" + prefix: List[bool] = [] + ret: List[ASNDiff] = [] + + def recurse(old_node: List, new_node: List): + if len(old_node) == 1 and len(new_node) == 1: + if old_node[0] != new_node[0]: + ret.append((list(prefix), old_node[0], new_node[0])) + else: + old_left: List = old_node if len(old_node) == 1 else old_node[0] + old_right: List = old_node if len(old_node) == 1 else old_node[1] + new_left: List = new_node if len(new_node) == 1 else new_node[0] + new_right: List = new_node if len(new_node) == 1 else new_node[1] + prefix.append(False) + recurse(old_left, new_left) + prefix[-1] = True + recurse(old_right, new_right) + prefix.pop() + assert isinstance(other, ASMap) + #pylint: disable=protected-access + recurse(self._trie, other._trie) + return ret + + def __copy__(self) -> "ASMap": + """Construct a copy of this ASMap object. Its state will not be shared.""" + ret = ASMap() + #pylint: disable=protected-access + ret._set_trie(copy.deepcopy(self._trie)) + return ret + + def __deepcopy__(self, _) -> "ASMap": + # ASMap objects do not allow sharing of the _trie member, so we don't need the memoization. + return self.__copy__() + + +class TestASMap(unittest.TestCase): + """Unit tests for this module.""" + + def test_ipv6_prefix_roundtrips(self) -> None: + """Test that random IPv6 network ranges roundtrip through prefix encoding.""" + for _ in range(20): + net_bits = random.getrandbits(128) + for prefix_len in range(0, 129): + masked_bits = (net_bits >> (128 - prefix_len)) << (128 - prefix_len) + net = ipaddress.IPv6Network((masked_bits.to_bytes(16, 'big'), prefix_len)) + prefix = net_to_prefix(net) + self.assertTrue(len(prefix) <= 128) + net2 = prefix_to_net(prefix) + self.assertEqual(net, net2) + + def test_ipv4_prefix_roundtrips(self) -> None: + """Test that random IPv4 network ranges roundtrip through prefix encoding.""" + for _ in range(100): + net_bits = random.getrandbits(32) + for prefix_len in range(0, 33): + masked_bits = (net_bits >> (32 - prefix_len)) << (32 - prefix_len) + net = ipaddress.IPv4Network((masked_bits.to_bytes(4, 'big'), prefix_len)) + prefix = net_to_prefix(net) + self.assertTrue(32 <= len(prefix) <= 128) + net2 = prefix_to_net(prefix) + self.assertEqual(net, net2) + + def test_asmap_roundtrips(self) -> None: + """Test case that verifies random ASMap objects roundtrip to/from entries/binary.""" + # Iterate over the number of leaves the random test ASMap objects have. + for leaves in range(1, 20): + # Iterate over the number of bits in the AS numbers used. + for asnbits in range(0, 24): + # Iterate over the probability that leaves are unassigned. + for pct in range(101): + # Construct a random ASMap object according to the above parameters. + asmap = ASMap.from_random(num_leaves=leaves, max_asn=1 + (1 << asnbits), + unassigned_prob=0.01 * pct) + # Run tests for to_entries and construction from those entries, both + # for overlapping and non-overlapping ones. + for overlapping in [False, True]: + entries = asmap.to_entries(overlapping=overlapping, fill=False) + random.shuffle(entries) + asmap2 = ASMap(entries) + assert asmap2 is not None + self.assertEqual(asmap2, asmap) + entries = asmap.to_entries(overlapping=overlapping, fill=True) + random.shuffle(entries) + asmap2 = ASMap(entries) + assert asmap2 is not None + self.assertTrue(asmap2.extends(asmap)) + + # Run tests for to_binary and construction from binary. + enc = asmap.to_binary(fill=False) + asmap3 = ASMap.from_binary(enc) + assert asmap3 is not None + self.assertEqual(asmap3, asmap) + enc = asmap.to_binary(fill=True) + asmap3 = ASMap.from_binary(enc) + assert asmap3 is not None + self.assertTrue(asmap3.extends(asmap)) + + def test_patching(self) -> None: + """Test behavior of update, lookup, extends, and diff.""" + #pylint: disable=too-many-locals,too-many-nested-blocks + # Iterate over the number of leaves the random test ASMap objects have. + for leaves in range(1, 20): + # Iterate over the number of bits in the AS numbers used. + for asnbits in range(0, 10): + # Iterate over the probability that leaves are unassigned. + for pct in range(0, 101): + # Construct a random ASMap object according to the above parameters. + asmap = ASMap.from_random(num_leaves=leaves, max_asn=1 + (1 << asnbits), + unassigned_prob=0.01 * pct) + # Make a copy of that asmap object to which patches will be applied. + # It starts off being equal to asmap. + patched = copy.copy(asmap) + # Keep a list of patches performed. + patches: List[ASNEntry] = [] + # Initially there cannot be any difference. + self.assertEqual(asmap.diff(patched), []) + # Make 5 patches, each building on top of the previous ones. + for _ in range(0, 5): + # Construct a random path and new ASN to assign it to, apply it to patched, + # and remember it in patches. + pathlen = random.randrange(5) + path = [random.getrandbits(1) != 0 for _ in range(pathlen)] + newasn = random.randrange(1 + (1 << asnbits)) + patched.update(path, newasn) + patches = [(path, newasn)] + patches + + # Compute the diff, and whether asmap extends patched, and the other way + # around. + diff = asmap.diff(patched) + self.assertEqual(asmap == patched, len(diff) == 0) + extends = asmap.extends(patched) + back_extends = patched.extends(asmap) + # Determine whether those extends results are consistent with the diff + # result. + self.assertEqual(extends, all(d[2] == 0 for d in diff)) + self.assertEqual(back_extends, all(d[1] == 0 for d in diff)) + # For every diff found: + for path, old_asn, new_asn in diff: + # Verify asmap and patched actually differ there. + self.assertTrue(old_asn != new_asn) + self.assertEqual(asmap.lookup(path), old_asn) + self.assertEqual(patched.lookup(path), new_asn) + for _ in range(2): + # Extend the path far enough that it's smaller than any mapped + # range, and check the lookup holds there too. + spec_path = list(path) + while len(spec_path) < 32: + spec_path.append(random.getrandbits(1) != 0) + self.assertEqual(asmap.lookup(spec_path), old_asn) + self.assertEqual(patched.lookup(spec_path), new_asn) + # Search through the list of performed patches to find the last one + # applying to the extended path (note that patches is in reverse + # order, so the first match should work). + found = False + for patch_path, patch_asn in patches: + if spec_path[:len(patch_path)] == patch_path: + # When found, it must match whatever the result was patched + # to. + self.assertEqual(new_asn, patch_asn) + found = True + break + # And such a patch must exist. + self.assertTrue(found) + +if __name__ == '__main__': + unittest.main() diff --git a/contrib/seeds/makeseeds.py b/contrib/seeds/makeseeds.py index 78eb04a836..eda58c370f 100755 --- a/contrib/seeds/makeseeds.py +++ b/contrib/seeds/makeseeds.py @@ -1,17 +1,20 @@ #!/usr/bin/env python3 -# Copyright (c) 2013-2020 The Bitcoin Core developers +# Copyright (c) 2013-2022 The Bitcoin Core developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. # # Generate seeds.txt from Pieter's DNS seeder # +import argparse +import collections +import ipaddress import re import sys -import dns.resolver -import collections from typing import List, Dict, Union +from asmap import ASMap, net_to_prefix + NSEEDS=512 MAX_SEEDS_PER_ASN = { @@ -35,7 +38,8 @@ PATTERN_AGENT = re.compile( r"0.20.(0|1|2|99)|" r"0.21.(0|1|2|99)|" r"22.(0|99)|" - r"23.99" + r"23.(0|99)|" + r"24.99" r")") def parseline(line: str) -> Union[dict, None]: @@ -45,7 +49,7 @@ def parseline(line: str) -> Union[dict, None]: sline = line.split() if len(sline) < 11: # line too short to be valid, skip it. - return None + return None m = PATTERN_IPV4.match(sline[0]) sortkey = None ip = None @@ -123,34 +127,8 @@ def filtermultiport(ips: List[Dict]) -> List[Dict]: hist[ip['sortkey']].append(ip) return [value[0] for (key,value) in list(hist.items()) if len(value)==1] -def lookup_asn(net: str, ip: str) -> Union[int, None]: - """ Look up the asn for an `ip` address by querying cymru.com - on network `net` (e.g. ipv4 or ipv6). - - Returns in integer ASN or None if it could not be found. - """ - try: - if net == 'ipv4': - ipaddr = ip - prefix = '.origin' - else: # http://www.team-cymru.com/IP-ASN-mapping.html - res = str() # 2001:4860:b002:23::68 - for nb in ip.split(':')[:4]: # pick the first 4 nibbles - for c in nb.zfill(4): # right padded with '0' - res += c + '.' # 2001 4860 b002 0023 - ipaddr = res.rstrip('.') # 2.0.0.1.4.8.6.0.b.0.0.2.0.0.2.3 - prefix = '.origin6' - - asn = int([x.to_text() for x in dns.resolver.resolve('.'.join( - reversed(ipaddr.split('.'))) + prefix + '.asn.cymru.com', - 'TXT').response.answer][0].split('\"')[1].split(' ')[0]) - return asn - except Exception as e: - sys.stderr.write(f'ERR: Could not resolve ASN for "{ip}": {e}\n') - return None - # Based on Greg Maxwell's seed_filter.py -def filterbyasn(ips: List[Dict], max_per_asn: Dict, max_per_net: int) -> List[Dict]: +def filterbyasn(asmap: ASMap, ips: List[Dict], max_per_asn: Dict, max_per_net: int) -> List[Dict]: """ Prunes `ips` by (a) trimming ips to have at most `max_per_net` ips from each net (e.g. ipv4, ipv6); and (b) trimming ips to have at most `max_per_asn` ips from each asn in each net. @@ -165,21 +143,18 @@ def filterbyasn(ips: List[Dict], max_per_asn: Dict, max_per_net: int) -> List[Di asn_count: Dict[int, int] = collections.defaultdict(int) for i, ip in enumerate(ips_ipv46): - if i % 10 == 0: - # give progress update - print(f"{i:6d}/{len(ips_ipv46)} [{100*i/len(ips_ipv46):04.1f}%]\r", file=sys.stderr, end='', flush=True) - if net_count[ip['net']] == max_per_net: # do not add this ip as we already too many # ips from this network continue - asn = lookup_asn(ip['net'], ip['ip']) - if asn is None or asn_count[asn] == max_per_asn[ip['net']]: + asn = asmap.lookup(net_to_prefix(ipaddress.ip_network(ip['ip']))) + if not asn or asn_count[ip['net'], asn] == max_per_asn[ip['net']]: # do not add this ip as we already have too many # ips from this ASN on this network continue - asn_count[asn] += 1 + asn_count[ip['net'], asn] += 1 net_count[ip['net']] += 1 + ip['asn'] = asn result.append(ip) # Add back Onions (up to max_per_net) @@ -195,9 +170,23 @@ def ip_stats(ips: List[Dict]) -> str: return f"{hist['ipv4']:6d} {hist['ipv6']:6d} {hist['onion']:6d}" +def parse_args(): + argparser = argparse.ArgumentParser(description='Generate a list of bitcoin node seed ip addresses.') + argparser.add_argument("-a","--asmap", help='the location of the asmap asn database file (required)', required=True) + return argparser.parse_args() + def main(): + args = parse_args() + + print(f'Loading asmap database "{args.asmap}"…', end='', file=sys.stderr, flush=True) + with open(args.asmap, 'rb') as f: + asmap = ASMap.from_binary(f.read()) + print('Done.', file=sys.stderr) + + print('Loading and parsing DNS seeds…', end='', file=sys.stderr, flush=True) lines = sys.stdin.readlines() ips = [parseline(line) for line in lines] + print('Done.', file=sys.stderr) print('\x1b[7m IPv4 IPv6 Onion Pass \x1b[0m', file=sys.stderr) print(f'{ip_stats(ips):s} Initial', file=sys.stderr) @@ -230,15 +219,18 @@ def main(): ips = filtermultiport(ips) print(f'{ip_stats(ips):s} Filter out hosts with multiple bitcoin ports', file=sys.stderr) # Look up ASNs and limit results, both per ASN and globally. - ips = filterbyasn(ips, MAX_SEEDS_PER_ASN, NSEEDS) + ips = filterbyasn(asmap, ips, MAX_SEEDS_PER_ASN, NSEEDS) print(f'{ip_stats(ips):s} Look up ASNs and limit results per ASN and per net', file=sys.stderr) # Sort the results by IP address (for deterministic output). ips.sort(key=lambda x: (x['net'], x['sortkey'])) for ip in ips: if ip['net'] == 'ipv6': - print('[%s]:%i' % (ip['ip'], ip['port'])) + print(f"[{ip['ip']}]:{ip['port']}", end="") else: - print('%s:%i' % (ip['ip'], ip['port'])) + print(f"{ip['ip']}:{ip['port']}", end="") + if 'asn' in ip: + print(f" # AS{ip['asn']}", end="") + print() if __name__ == '__main__': main() diff --git a/contrib/seeds/nodes_main.txt b/contrib/seeds/nodes_main.txt index d8e34bdb60..f8572b26c7 100644 --- a/contrib/seeds/nodes_main.txt +++ b/contrib/seeds/nodes_main.txt @@ -1,688 +1,856 @@ -2.37.30.144:8777 -2.138.174.158:8333 -2.152.78.124:8333 -5.8.18.154:8333 -5.45.74.50:8333 -5.79.123.3:8333 -5.102.168.217:22220 -5.103.137.146:9333 -5.128.87.126:8333 -5.172.132.200:8333 -5.188.62.18:8333 -5.254.101.226:8334 -8.210.18.56:8333 -8.210.92.32:8333 -14.13.34.225:16181 -14.39.151.167:8333 -18.196.79.108:8333 -18.218.139.58:48333 -20.184.15.116:8433 -23.175.0.220:8333 -23.233.107.21:8333 -24.35.68.229:8333 -24.37.3.26:8333 -24.102.91.203:8333 -24.116.153.115:8333 -24.134.6.165:8333 -24.155.218.13:8333 -24.160.137.173:8333 -24.177.106.85:8333 -24.184.0.146:8333 -24.194.222.116:8333 -24.205.215.192:8333 -27.124.108.19:8333 -31.14.40.64:8333 -31.47.202.112:8333 -31.165.115.7:8333 -34.65.45.157:8333 -34.78.48.104:8333 -34.80.134.68:8333 -34.101.132.198:8333 -34.227.68.216:8333 -35.137.212.22:8333 -35.231.190.134:8333 -37.1.217.35:8333 -37.15.62.32:8333 -37.143.118.174:8333 -37.200.59.67:8333 -37.205.9.165:8333 -38.23.180.228:8333 -38.65.119.26:8333 -38.141.134.140:8333 -39.109.122.127:8444 -41.79.70.146:8333 -41.193.122.191:8333 -43.225.62.107:8333 -45.35.73.152:8333 -45.43.97.103:8333 -45.63.10.52:20008 -45.84.153.40:8333 -45.95.64.225:8333 -45.129.180.214:8333 -45.154.255.162:8333 -45.226.80.102:8333 -46.6.10.230:8333 -46.23.87.218:8333 -46.32.50.98:8333 -46.47.84.85:8333 -46.48.126.58:8333 -46.146.248.89:8333 -46.165.221.209:9333 -46.166.142.2:8333 -46.166.162.45:20001 -46.173.50.58:8333 -46.175.178.3:8333 -46.188.30.118:8333 -46.219.120.59:3673 -46.229.238.187:8333 -47.93.230.171:8333 -47.100.162.210:18332 -47.144.106.249:8333 -47.188.70.205:8333 -47.227.226.242:8333 -50.2.13.164:8333 -50.5.46.195:8333 -50.45.128.28:8333 -51.148.153.60:8333 -51.154.62.103:8333 -51.154.131.18:8333 -51.158.150.155:8333 -51.159.2.218:8333 -54.198.19.34:8333 -58.105.168.41:8333 -58.158.0.86:8333 -60.251.129.61:8336 -61.239.91.250:8333 -62.28.190.194:8333 -62.152.58.16:9421 -62.171.129.32:8333 -62.251.54.163:8333 -63.247.147.166:8333 -64.33.68.176:8333 -64.156.192.61:8333 -64.187.175.226:8333 -64.233.245.39:8333 -64.237.82.149:8333 -65.101.247.26:8333 -66.29.129.218:8333 -66.49.204.11:8333 -66.58.243.215:8333 -66.85.234.129:8333 -66.130.120.52:8333 -67.10.121.145:8333 -67.210.228.203:8333 -67.213.87.21:8333 -68.181.4.12:8333 -69.7.124.146:8333 -69.8.175.201:8333 -69.59.18.22:8333 -69.119.193.9:8333 -69.130.201.27:8333 -69.131.101.176:8333 -70.15.194.32:8333 -70.64.27.12:8333 -72.29.170.151:8333 -72.74.34.99:8333 -72.133.177.119:8333 -73.166.84.222:8333 -74.67.240.204:8333 -74.91.115.229:8333 -74.118.137.119:8333 -74.213.251.203:8333 -74.220.255.190:8333 -76.11.60.155:8333 -76.66.144.127:8333 -77.70.16.245:8333 -77.85.204.149:8333 -77.105.87.97:8333 -77.120.113.69:8433 -77.120.113.71:8433 -77.120.122.116:8433 -77.120.122.118:8433 -77.162.190.90:8333 -77.167.245.239:55544 -77.232.41.189:8333 -78.20.227.249:8333 -78.21.167.8:8333 -78.27.139.13:8333 -78.43.208.25:8333 -78.63.28.146:8333 -78.72.228.239:8333 -78.108.102.8:8333 -78.129.0.39:8333 -78.129.169.69:8333 -79.77.182.180:8333 -79.77.182.183:8333 -79.107.178.59:8333 -80.55.225.158:8333 -80.64.211.102:8333 -80.64.211.103:8333 -80.71.57.50:8333 -80.81.3.27:8333 -80.82.55.43:8333 -80.88.172.227:64264 -80.89.203.172:8001 -80.93.213.246:8333 -80.147.82.165:8333 -80.229.28.60:8333 -80.247.233.40:8333 -80.255.8.93:8333 -81.7.17.202:8333 -81.10.241.165:8333 -81.21.86.157:8333 -81.171.22.143:8333 -81.237.206.224:8343 -82.69.23.195:8333 -82.96.96.40:8333 -82.116.50.101:8333 -82.136.99.122:8333 -82.149.97.25:17567 -82.154.24.209:8333 -82.165.241.50:8333 -82.197.218.253:8333 -82.202.68.231:8333 -83.137.41.10:8333 -83.208.6.211:8333 -83.217.8.31:44420 -83.220.110.48:8333 -83.222.138.85:8333 -83.243.191.199:8333 -84.22.139.57:8333 -84.27.155.17:8333 -84.75.28.247:8333 -84.112.60.16:8333 -84.211.7.56:8333 -84.237.7.249:8333 -85.23.51.177:8333 -85.24.145.198:8333 -85.184.138.108:8333 -85.194.238.134:8333 -85.195.54.110:8333 -85.208.71.36:8333 -85.208.71.39:8333 -85.214.136.45:8333 -85.214.161.252:8333 -85.227.245.128:8333 -86.18.34.243:8333 -86.20.50.170:8333 -86.49.105.90:8333 -86.76.7.132:8333 -86.100.26.188:8333 -86.106.143.143:55373 -86.120.58.66:8333 -86.133.251.239:8901 -86.149.8.23:8901 -87.78.197.234:8333 -87.120.8.5:20008 -87.121.37.156:8333 -88.82.181.44:8333 -88.87.93.52:1691 -88.98.235.134:8333 -88.136.187.214:8333 -88.147.244.250:8333 -88.148.153.148:8333 -88.212.45.166:8333 -88.212.55.138:8333 -89.38.96.153:9273 -89.47.161.135:8333 -89.88.62.190:8333 -89.158.32.44:8333 -89.163.145.240:8333 -89.163.249.234:3673 -89.176.196.80:8333 -89.216.21.96:8333 -90.84.227.255:8333 -90.146.130.214:8333 -90.250.9.1:8333 -91.93.194.154:8333 -91.106.188.229:8333 -91.126.40.109:8333 -91.137.127.123:8333 -91.147.232.98:8333 -91.152.123.18:8333 -91.178.17.120:8333 -91.204.99.178:8333 -91.223.175.14:8333 -92.42.110.242:8333 -92.53.90.84:8333 -92.221.155.228:8333 -93.57.81.162:8333 -93.95.88.13:8333 -93.103.13.1:8333 -93.123.180.164:8333 -93.190.117.26:8333 -94.105.125.240:8333 -94.110.23.215:8333 -94.154.159.99:8333 -94.189.161.119:8333 -94.203.255.70:8333 -94.232.173.93:8333 -95.79.122.99:8333 -95.80.1.110:8333 -95.83.73.31:8333 -95.110.133.223:8333 -95.110.234.93:8333 -95.164.65.194:8333 -95.165.8.182:8333 -95.174.219.101:8333 -95.191.130.100:8333 -95.214.53.154:8333 -95.215.205.180:8333 -96.43.130.234:8333 -98.25.201.31:8333 -98.128.247.182:8333 -98.171.21.129:8333 -99.147.135.161:8333 -101.100.163.118:8327 -102.132.245.16:8333 -102.182.204.96:8333 -102.182.235.245:8333 -103.14.245.250:8333 -103.47.192.15:8333 -103.84.84.250:8335 -103.99.168.130:8333 -103.99.168.140:8333 -103.198.192.14:20008 -103.232.104.227:8333 -104.143.2.195:8333 -104.172.235.227:8333 -104.238.220.199:8333 -107.11.115.68:8333 -107.173.166.43:8333 -108.4.212.83:8333 -109.136.73.97:8333 -109.173.98.23:8333 -109.190.68.116:8333 -109.235.246.60:8333 -109.248.206.13:8333 -110.12.64.96:8333 -111.90.140.46:8333 -111.90.159.184:50001 -113.107.201.131:8333 -115.47.141.250:8885 -116.58.171.67:8333 -116.87.57.218:8333 -116.202.161.56:8333 -117.51.159.130:8333 -118.103.126.140:28333 -121.45.190.210:8333 -121.99.193.25:8333 -122.112.148.153:8339 -122.148.135.234:8333 -128.0.190.26:8333 -128.65.194.136:8333 -129.126.172.115:8333 -129.226.125.10:8333 -131.188.40.191:8333 -134.195.185.52:8333 -135.180.44.61:8333 -136.52.114.123:8333 -136.56.170.96:8333 -137.116.213.143:8333 -137.226.34.46:8333 -138.43.233.57:8333 -139.130.41.82:8333 -140.190.12.129:8333 -142.4.105.77:8333 -142.54.181.218:8333 -143.177.231.247:8333 -143.178.64.10:8333 -144.34.161.65:18333 -146.4.124.134:8333 -146.83.56.69:8333 -146.90.193.68:8333 -146.196.55.156:28833 -148.66.50.50:8335 -148.251.1.20:8343 -151.48.95.212:8333 -151.252.193.245:8333 -152.44.137.83:8333 -152.115.191.196:8333 -154.221.31.86:8333 -156.17.103.2:8088 -157.138.20.22:8333 -158.58.188.37:8333 -158.140.209.79:8333 -159.89.230.128:8333 -159.246.25.52:8333 -160.20.59.250:8433 -162.0.234.190:8333 -162.62.26.218:8333 -162.250.188.194:8333 -162.251.70.82:8333 -163.158.206.255:8333 -164.68.105.105:8333 -165.228.174.117:8333 -166.62.82.103:32771 -166.70.49.26:8333 -166.78.241.9:8333 -166.78.241.25:8333 -167.71.73.244:8333 -167.179.147.155:8333 -168.91.238.8:8333 -172.105.21.216:8333 -172.117.105.95:8333 -173.23.103.30:8000 -173.205.92.151:54805 -173.205.92.154:54805 -173.205.92.157:54805 -173.208.152.218:8333 -173.241.227.243:8333 -174.3.4.232:8333 -174.17.11.22:8333 -174.88.241.167:8333 -174.114.102.41:8333 -174.114.250.86:8333 -174.142.191.136:8333 -175.39.72.87:8333 -176.12.16.135:8333 -176.37.23.30:8333 -176.62.179.221:8333 -176.74.136.237:8333 -176.99.6.226:8333 -176.212.185.153:8333 -177.81.236.117:8333 -178.19.106.26:8333 -178.21.118.178:8333 -178.33.232.69:8333 -178.79.84.139:8333 -178.124.162.209:8333 -178.132.2.246:8333 -178.150.96.46:8333 -178.162.212.44:8333 -178.193.226.120:8333 -178.236.137.63:8333 -180.150.46.187:8333 -181.164.210.228:8530 -183.110.220.210:30301 -184.95.58.166:8336 -184.164.147.82:41333 -184.171.208.109:8333 -185.17.143.220:8333 -185.21.217.49:8333 -185.25.48.184:8333 -185.28.96.16:8333 -185.31.136.246:8333 -185.64.116.15:8333 -185.68.249.91:8333 -185.108.247.190:8333 -185.141.60.36:8333 -185.148.3.227:8333 -185.148.145.74:8333 -185.159.20.143:8333 -185.167.113.59:8333 -185.185.26.141:8111 -185.189.132.178:57780 -185.204.197.112:8333 -185.209.70.17:8333 -185.220.156.193:8333 -185.238.129.113:8333 -185.239.221.5:8333 -185.244.217.39:8333 -185.254.97.164:8333 -186.33.167.11:8333 -188.32.14.31:8334 -188.42.40.234:18333 -188.134.8.36:8333 -188.138.88.14:8333 -188.156.110.239:8333 -188.165.244.143:8333 -188.213.68.38:8333 -188.214.129.65:20012 -188.242.15.74:8333 -188.244.4.78:8333 -189.39.6.82:8333 -189.207.46.32:8333 -189.212.121.74:8333 -192.3.11.20:8333 -192.65.170.15:8333 -192.146.137.44:8333 -192.182.157.119:8333 -192.187.109.141:8333 -192.227.80.83:8333 -193.10.203.23:8334 -193.32.127.160:58477 -193.32.127.162:58477 -193.58.196.212:8333 -193.106.29.106:8333 -193.138.154.43:8333 -193.178.170.232:8333 -193.196.37.62:8333 -193.222.130.14:8333 -193.234.50.227:8333 -194.14.246.205:8333 -194.135.135.69:8333 -194.147.113.201:8333 -194.165.30.20:8333 -194.219.62.23:8333 -195.56.63.4:8333 -195.134.183.188:8333 -195.208.103.30:8444 -195.208.103.31:8444 -198.1.231.6:8333 -198.12.14.136:8333 -198.84.237.70:8333 -198.178.120.5:8112 -199.48.92.184:8333 -199.68.199.19:8333 -199.182.184.204:8333 -199.189.242.141:8333 -199.247.7.208:8333 -200.122.181.37:8333 -201.191.6.103:8333 -202.107.219.130:8333 -202.108.211.135:8333 -203.94.33.112:8333 -203.130.48.117:8885 -203.132.94.196:8333 -203.162.13.181:8332 -204.191.201.43:8333 -204.229.10.90:8333 -205.178.41.124:8333 -206.55.178.157:8333 -206.126.203.8:8333 -206.174.115.96:8333 -206.223.153.52:8333 -207.188.159.25:8333 -207.229.46.80:8333 -209.58.145.157:8333 -209.126.81.147:8333 -209.145.63.150:8333 -209.209.10.30:8333 -209.237.127.227:8333 -212.99.226.36:9020 -212.185.86.84:8333 -212.227.211.87:8333 -213.5.36.58:8333 -213.89.236.219:8333 -213.93.145.183:8333 -213.214.66.182:8333 -216.41.249.178:8333 -216.146.251.8:8333 -216.249.70.22:8333 -217.11.240.4:8333 -217.15.178.7:8333 -217.24.233.116:8333 -217.64.148.98:51401 -217.113.121.169:8333 -217.170.124.170:8333 -220.132.135.54:8333 -220.221.58.25:8333 -220.233.178.199:8333 -221.219.97.105:2001 -[2001:1608:1b:f9::1]:26491 -[2001:1620:510::2]:8333 -[2001:1bc0:c1::2000]:8333 -[2001:470:1f0a:89a::2]:8333 -[2001:470:de5a::ec]:9333 -[2001:4b98:dc0:45:216:3eff:fea2:95cd]:8333 -[2001:4dd0:3564:0:fd76:c1d3:1854:5bd9]:8333 -[2001:4de8:b1b2:1:0:dead:beef:7]:8333 -[2001:638:a000:4140::ffff:191]:8333 -[2001:648:2800:131:4b1f:f6fc:20f7:f99f]:8333 -[2001:678:cc8::1:10:88]:20008 -[2001:67c:26b4:ff00::44]:8333 -[2001:67c:2db8:13::92]:8333 -[2001:7c0:2310:0:f816:3eff:fe6c:4f58]:8333 -[2001:818:ea1b:7600:f053:aade:f47b:b701]:8333 -[2001:8f1:1404:3700:8e49:715a:2e09:b634]:9444 -[2001:985:55a0:1::2]:8333 -[2001:999:270:2c2c:c8b:3a20:3f2f:318f]:8333 -[2001:b07:ac9:442b:79d6:bbbe:b37c:a783]:8333 -[2002:2f5b:a5f9::2f5b:a5f9]:8885 -[2002:b6ff:3dca::b6ff:3dca]:28364 -[2400:2410:cea2:d00:41bc:c9ea:861b:51ee]:8333 -[2400:3b00:20:c:bacb:29ff:feab:8886]:8333 -[2401:d002:3902:700:d72c:5e22:4e95:389d]:8333 -[2403:6200:88a0:fb17:f5f2:d8b5:b7ba:f4d3]:8333 -[2405:9800:b910:5f8e:1830:f630:2cc6:88fb]:8333 -[2405:9800:b970:c64c:109f:74e7:ae5f:87c7]:8333 -[2405:aa00:2::40]:8333 -[2407:8800:bc61:2202:d63d:7eff:fe6c:dc36]:8333 -[2408:8248:7004:f831::83c]:8333 -[2409:10:ca20:1df0:224:e8ff:fe1f:60d9]:8333 -[240b:11:43a1:bd00:e589:f8a7:49b:3b86]:8333 -[240d:1a:791:3400:d65d:64ff:fe28:927e]:8333 -[240d:1a:791:3400:d681:d7ff:fef6:a21e]:10050 -[2600:1700:5b2b:5f::8040]:8333 -[2600:2104:1003:c5ab:dc5e:90ff:fe18:1d08]:8333 -[2600:3c00:e002:2e32::1:14]:8333 -[2600:8805:2400:14e:12dd:b1ff:fef2:3013]:8333 -[2602:ffb8::208:72:57:200]:8333 -[2603:301f:1ebf:e000:e23f:49ff:fee7:7431]:8333 -[2603:6081:1800:6600:16dd:a9ff:feee:b2f3]:8333 -[2604:1380:1000:7400::1]:8333 -[2604:4500::2e06]:8112 -[2604:5500:c134:4000:7285:c2ff:fe4a:e143]:32797 -[2604:5500:c134:4000::3fc]:32797 -[2604:7c00:120:4b::eb24]:8333 -[2605:6400:30:f220::]:8333 -[2605:6f80:0:7:fc1b:ccff:fe8a:d822]:8333 -[2605:ae00:203::203]:8333 -[2605:c000:2a0a:1::102]:8333 -[2605:f700:c0:827:225:90ff:fee3:34a6]:8333 -[2607:9280:b:73b:250:56ff:fe14:25b5]:8333 -[2607:f2f8:ad40:bc1::1]:8333 -[2607:fa18:3a01::20]:8333 -[2620:11c:5001:1118:d267:e5ff:fee9:e673]:8333 -[2620:11c:5001:2199:d267:e5ff:fee9:e673]:8333 -[2620:6:2003:105:2d8:61ff:fe0f:853]:8333 -[2620:6e:a000:1:42:42:42:42]:8333 -[2803:cf00:af8:f200:b89e:cf34:92c7:2d26]:8333 -[2804:14c:65d1:402c:bc53:bf5d:68a:2136]:8333 -[2804:7f1:e783:d401:661c:67ff:feba:5547]:8333 -[2804:d57:5537:4800:21e:67ff:fea8:d798]:8333 -[2804:d57:5537:4800:3615:9eff:fe23:d610]:8333 -[2806:2f0:2080:62a:86f:1a01:c44f:1794]:8333 -[2a00:1028:8382:bf22:5f7f:b78f:2737:7739]:8333 -[2a00:12e0:101:99:20c:29ff:fe29:d03f]:8333 -[2a00:1328:e101:c00::163]:8333 -[2a00:1630:10:1003:0:b19:b00b:babe]:8333 -[2a00:1768:2001:27::ef6a]:8333 -[2a00:1828:a004:2::666]:8333 -[2a00:1838:2a:1400:92e2:baff:fe4a:c416]:8333 -[2a00:1c10:2:709::217]:22220 -[2a00:1f40:5001:108:5d17:7703:b0f5:4133]:8333 -[2a00:6020:15dd:ee00:c8c2:2c77:1749:35db]:8333 -[2a00:6020:b482:9200:491a:358c:d8f7:1da]:8333 -[2a00:7145:c1:1:ae29:727:2b87:f64]:5141 -[2a00:8a60:e012:a00::21]:8333 -[2a00:a040:100:f3:45a5:ac0:fea3:71e1]:8333 -[2a01:488:2000:9801::d]:8333 -[2a01:490:16:301::2]:8333 -[2a01:5200:6c:6162:7a61:746b:6f2e:736b]:8333 -[2a01:6380:fffe:73:4e3:b3cc:a871:36d1]:8333 -[2a01:7a0:2:137c::3]:8333 -[2a01:7c8:aac9:c9:5054:ff:fedf:ff95]:8333 -[2a01:7c8:d001:1c1:5054:ff:feee:3e1a]:8333 -[2a01:8740:1:ffc5::8c6a]:8333 -[2a01:cb00:d3d:7700:227:eff:fe28:c565]:8333 -[2a01:d0:0:1c::253]:8333 -[2a01:d0:bef2::12]:8333 -[2a01:e0a:9fb:b0e0:54f8:1901:6e83:62c1]:8333 -[2a01:e0a:aa7:c8c0:9679:affa:b6e5:efc7]:8333 -[2a02:13b8:f000:101::a]:8333 -[2a02:168:6328:0:2a8:2cff:fe68:e32c]:8333 -[2a02:2780:9000:70::7]:8333 -[2a02:2e02:3900:5400:a099:e1ff:feb6:d0e]:8333 -[2a02:390:9000:0:aaa1:59ff:fe43:b57b]:8333 -[2a02:58:97:7d20::60]:8333 -[2a02:6d40:305e:601:dea6:32ff:fe44:4b25]:8333 -[2a02:7a01::91:228:45:130]:8333 -[2a02:7aa0:1619::adc:8de0]:8333 -[2a02:7b40:3e4d:998d::1]:8333 -[2a02:7b40:592f:a187::1]:8333 -[2a02:8388:e5c6:d380:201:2eff:fe82:b3cc]:8333 -[2a02:9a0:102::110]:8333 -[2a02:a311:8143:8c00::4]:8353 -[2a02:af8:fab0:808:85:234:145:132]:8333 -[2a02:e00:fff0:506::1]:8444 -[2a02:e00:fff0:506::a]:8444 -[2a02:e98:20:1504::1]:8333 -[2a03:4000:47:f1::1]:8333 -[2a03:6000:870:0:46:23:87:218]:8333 -[2a03:7380:3015:524:afc5:d3bc:7c66:8f94]:8333 -[2a03:ec0:0:928:8c00:93ff:fe84:a007]:8333 -[2a03:ec0:0:928::701]:8333 -[2a04:2180:0:2::aa]:8333 -[2a04:52c0:101:29e::]:8333 -[2a04:52c0:103:c455::1]:8333 -[2a04:bc40:1dc3:8d::2:1001]:8333 -[2a05:1500:702:0:1c00:40ff:fe00:c]:8333 -[2a06:dd00:10:3:225:90ff:fe32:64cc]:8333 -[2a06:dd00:1:22:225:90ff:fe0e:bd48]:8333 -[2a07:6b47:100:464::9357:ffda]:8333 -[2a07:a880:4601:1062:b4b4:bd2a:39d4:7acf]:51401 -[2a07:abc4::1:946]:8333 -[2a07:abc4::89:234:180:194]:8333 -[2a09:2681:102::210]:8333 -[2a0a:c801:1:7::183]:8333 -[2a0b:f300:2:6::2]:8333 -[2a0c:59c0:18::a20e]:57658 -[2a0d:5600:24:a8e::a91e]:55373 -[2a0d:eb00:8005:1::13]:8333 -[2a10:4740:45:1:a013:d1ff:fe85:36e3]:8333 -[2a10:8b40:1::103]:8335 -[2c0f:f8f0:da51:0:70c3:eea9:9717:9579]:8333 +2.3.25.181:8333 # AS3215 +2.152.78.124:8333 # AS12430 +5.39.74.166:8333 # AS16276 +5.45.79.81:18332 # AS50673 +5.53.16.128:8333 # AS50923 +5.95.186.78:8333 # AS30722 +5.128.87.126:8333 # AS31200 +5.133.65.82:8333 # AS15440 +5.146.20.229:8333 # AS3209 +5.180.41.119:8333 # AS18978 +5.188.62.18:8333 # AS34665 +5.199.173.66:8333 # AS16125 +5.255.97.25:8333 # AS60404 +5.255.103.180:8333 # AS60404 +8.209.70.77:8333 # AS45102 +8.209.105.138:8333 # AS45102 +18.162.208.153:48332 # AS16509 +23.175.0.200:8333 # AS395502 +23.175.0.222:8333 # AS395502 +23.233.107.21:8333 # AS5645 +23.236.25.169:8333 # AS30029 +24.35.68.229:8333 # AS11404 +24.84.164.50:8333 # AS6327 +24.116.153.115:8333 # AS11492 +24.184.0.146:8333 # AS6128 +27.33.160.196:8333 # AS7545 +27.124.108.19:8333 # AS58511 +27.148.206.140:8333 # AS4134 +31.17.64.192:8333 # AS204028 +31.18.114.135:8333 # AS204028 +31.41.23.249:8333 # AS31287 +31.42.176.138:8333 # AS43641 +31.47.202.112:8333 # AS34385 +34.65.45.157:8333 # AS15169 +34.80.134.68:8333 # AS15169 +34.126.115.35:8333 # AS396982 +37.1.204.231:8333 # AS50673 +37.120.155.34:8333 # AS9009 +37.143.118.174:8333 # AS48926 +37.193.227.16:8333 # AS31200 +37.220.135.151:8333 # AS41206 +37.235.146.236:8333 # AS41268 +38.124.126.42:8333 # AS11550 +38.141.134.140:8333 # AS174 +38.145.151.150:8333 # AS40545 +40.115.137.28:8333 # AS8075 +41.72.154.66:8333 # AS37153 +41.79.70.146:8333 # AS37349 +42.193.55.135:8333 # AS45090 +43.225.62.107:8333 # AS63953 +45.43.97.103:8333 # AS26827 +45.85.48.58:8333 # AS208016 +45.126.26.229:8333 # AS45763 +45.134.142.40:8333 # AS60068 +45.154.252.162:8333 # AS13335 +46.13.216.169:8333 # AS6855 +46.23.87.218:8333 # AS51088 +46.40.127.164:8333 # AS43205 +46.48.126.58:8333 # AS12668 +46.59.13.35:8333 # AS8473 +46.72.238.17:8333 # AS12714 +46.128.141.184:8333 # AS16097 +46.146.248.89:8333 # AS9049 +46.165.221.209:9333 # AS28753 +46.166.142.2:8333 # AS43350 +46.175.178.3:8333 # AS28725 +47.36.144.51:8333 # AS20115 +47.180.49.158:8333 # AS5650 +49.228.131.133:2210 # AS133481 +50.2.13.164:8333 # AS62904 +50.35.71.51:8333 # AS20055 +50.53.250.162:8333 # AS20055 +51.68.36.57:8333 # AS16276 +51.138.4.135:30001 # AS8075 +51.154.62.103:8333 # AS15796 +51.158.150.155:8333 # AS12876 +54.176.63.16:8333 # AS16509 +58.158.0.86:8333 # AS2519 +59.138.115.137:8333 # AS2516 +59.167.191.60:8333 # AS4739 +60.205.205.119:8333 # AS37963 +60.234.122.245:8333 # AS9790 +60.240.210.155:8333 # AS7545 +61.239.91.250:8333 # AS9269 +62.74.143.11:8333 # AS3329 +62.138.162.12:8333 # AS20773 +62.169.74.233:8333 # AS2860 +62.171.129.32:8333 # AS51167 +62.209.198.65:8333 # AS6855 +63.247.147.166:8333 # AS30221 +64.98.76.62:8333 # AS32133 +66.29.129.218:8333 # AS22612 +66.96.235.28:8333 # AS63859 +66.130.120.52:8333 # AS5769 +66.198.209.243:8333 # AS33152 +66.208.64.128:8333 # AS10352 +66.225.231.148:8333 # AS23352 +67.55.3.200:8333 # AS33139 +67.58.232.107:8333 # AS14051 +67.211.92.2:8333 # AS11711 +67.223.119.122:8333 # AS22612 +68.48.131.251:8333 # AS7922 +68.181.4.12:8333 # AS47 +69.14.185.9:8333 # AS12083 +69.54.29.193:8333 # AS12282 +69.59.18.22:8333 # AS397444 +69.131.101.176:8333 # AS4181 +69.165.205.142:8833 # AS5645 +69.228.219.124:8333 # AS7018 +70.59.123.25:8333 # AS209 +70.62.13.150:8333 # AS7843 +70.66.248.170:8333 # AS6327 +70.112.153.229:8333 # AS7843 +70.160.240.132:8333 # AS22773 +70.190.177.204:8333 # AS22773 +71.28.189.239:8333 # AS398465 +71.234.125.198:8333 # AS1351 +72.74.123.179:8333 # AS701 +72.253.236.217:8333 # AS36149 +73.219.254.120:8333 # AS1351 +74.91.115.229:8333 # AS14586 +74.118.137.119:8333 # AS20326 +74.195.166.100:8333 # AS19108 +74.220.255.190:8333 # AS23175 +76.67.211.110:8333 # AS577 +76.169.163.14:8333 # AS20001 +77.32.121.162:8333 # AS35612 +77.53.135.74:8333 # AS45011 +77.70.16.245:8333 # AS8717 +77.85.204.149:8333 # AS8866 +77.107.38.239:8333 # AS62183 +77.120.26.102:8333 # AS25229 +77.162.190.90:8333 # AS1136 +78.20.227.249:8333 # AS6848 +78.21.167.8:8333 # AS6848 +78.27.139.13:8333 # AS6723 +78.90.91.220:8333 # AS8717 +78.108.108.25:8333 # AS8251 +78.108.108.38:8333 # AS8251 +79.77.182.183:8333 # AS13285 +79.98.159.7:11333 # AS44065 +79.189.211.201:8333 # AS5617 +80.55.225.158:8333 # AS5617 +80.83.186.35:8333 # AS33891 +80.88.172.227:64264 # AS31263 +80.209.87.103:9333 # AS31027 +80.229.28.60:8333 # AS2856 +81.7.16.182:8333 # AS35366 +81.7.17.202:8333 # AS35366 +81.19.10.2:8333 # AS24641 +81.88.221.190:8333 # AS39709 +81.171.22.143:8333 # AS60781 +81.224.44.164:8333 # AS3301 +81.224.160.81:8333 # AS3301 +82.1.68.54:8333 # AS5089 +82.21.164.47:8333 # AS5089 +82.64.116.5:8333 # AS12322 +82.66.10.11:8333 # AS12322 +82.96.96.40:8333 # AS29686 +82.116.50.101:8333 # AS30936 +82.129.68.62:8333 # AS48945 +82.136.99.122:8333 # AS8821 +82.154.24.209:8333 # AS8657 +82.197.215.125:8333 # AS25596 +83.128.132.91:8333 # AS15435 +83.137.41.10:8333 # AS31394 +83.208.6.211:8333 # AS5610 +83.208.193.242:8333 # AS5610 +83.222.138.85:8333 # AS31736 +83.240.124.68:8333 # AS31246 +83.243.191.199:8333 # AS41164 +84.9.5.211:8333 # AS5378 +84.28.57.90:8333 # AS6830 +84.38.3.249:8333 # AS196691 +84.112.60.16:8333 # AS8412 +84.215.56.119:8333 # AS41164 +84.226.243.175:8333 # AS6730 +84.245.14.73:8333 # AS25596 +84.252.157.90:18333 # AS200590 +84.255.244.61:8333 # AS34779 +85.23.24.123:8333 # AS16086 +85.52.185.29:8666 # AS12479 +85.58.120.201:8333 # AS12479 +85.93.96.18:8333 # AS29208 +85.165.8.197:8333 # AS2119 +85.173.165.66:8333 # AS12389 +85.184.143.105:8333 # AS39642 +85.191.74.103:8333 # AS39642 +85.194.238.134:8333 # AS47605 +85.195.54.110:8333 # AS35706 +85.195.196.142:8333 # AS13030 +85.208.69.11:8333 # AS25091 +85.208.69.21:8333 # AS25091 +85.208.71.36:8333 # AS42275 +85.208.71.39:8333 # AS42275 +85.214.118.71:8333 # AS6724 +85.214.161.252:8333 # AS6724 +85.216.32.73:8333 # AS51185 +85.254.98.221:8333 # AS13194 +86.58.11.152:8333 # AS3212 +86.95.8.249:8333 # AS1136 +86.100.26.188:8333 # AS39007 +86.106.143.143:55373 # AS9009 +86.124.145.184:8333 # AS8708 +86.133.251.239:8901 # AS2856 +87.79.94.221:8333 # AS8422 +87.120.8.5:20008 # AS34224 +87.125.157.220:8333 # AS12430 +88.9.76.133:8333 # AS3352 +88.90.184.68:8333 # AS2119 +88.151.101.14:5000 # AS41075 +88.151.101.253:5000 # AS41075 +88.198.92.47:8333 # AS24940 +88.208.115.70:8333 # AS29208 +88.210.15.24:8333 # AS212702 +88.212.45.166:8333 # AS42841 +89.102.206.238:8333 # AS16019 +89.103.111.34:8333 # AS16019 +89.114.143.113:8333 # AS12353 +89.134.62.74:8333 # AS21334 +89.152.8.231:8333 # AS2860 +89.161.26.78:8333 # AS39375 +89.207.131.19:8333 # AS49544 +89.248.193.229:8333 # AS49505 +90.3.48.62:8333 # AS3215 +90.146.121.97:8333 # AS12605 +90.146.130.214:8333 # AS12605 +90.196.169.58:8333 # AS5607 +90.250.9.1:8333 # AS5378 +91.93.194.154:8333 # AS34984 +91.126.40.109:8333 # AS35699 +91.204.99.178:8333 # AS20485 +91.204.149.5:8333 # AS42765 +91.206.17.195:8333 # AS13259 +91.209.51.131:8333 # AS48239 +91.215.91.254:8333 # AS48078 +92.91.27.60:8333 # AS15557 +92.221.20.232:8333 # AS29695 +92.255.85.31:8333 # AS9002 +93.4.101.37:8333 # AS15557 +93.46.81.5:8333 # AS12874 +93.57.81.162:8333 # AS12874 +93.73.39.196:8333 # AS25229 +93.90.82.226:8333 # AS47626 +93.95.88.13:8333 # AS35434 +93.123.180.164:8333 # AS35539 +93.189.145.169:8333 # AS12555 +94.17.185.107:8333 # AS12709 +94.75.198.120:8333 # AS60781 +94.114.196.169:8333 # AS3209 +94.142.213.250:55544 # AS5524 +94.154.159.99:8333 # AS62240 +94.158.246.183:8333 # AS39798 +94.239.145.32:8333 # AS5410 +95.31.12.22:8333 # AS8402 +95.31.196.15:8333 # AS3216 +95.110.133.223:8333 # AS31034 +95.110.234.93:8333 # AS31034 +95.161.12.45:8333 # AS39598 +95.191.130.100:8333 # AS12389 +95.208.158.161:8333 # AS51185 +95.213.145.218:8333 # AS49505 +95.214.53.154:8333 # AS201814 +95.214.53.160:8333 # AS201814 +96.44.156.199:8333 # AS8100 +97.75.145.12:8333 # AS22709 +102.132.192.141:8333 # AS37680 +103.14.245.250:8333 # AS24482 +103.85.38.205:8333 # AS134090 +103.88.92.78:8332 # AS17547 +103.99.168.100:8333 # AS6939 +103.99.168.140:8333 # AS6939 +103.99.170.210:8333 # AS54415 +103.99.170.220:8333 # AS54415 +103.100.44.70:8333 # AS10143 +103.178.236.27:8333 # AS49981 +103.209.12.144:8333 # AS58511 +104.59.147.15:8333 # AS7018 +104.129.171.121:8333 # AS174 +104.200.65.234:8333 # AS23033 +104.238.220.199:8333 # AS23470 +104.244.73.6:8333 # AS53667 +106.71.119.230:8333 # AS4804 +107.173.166.43:8333 # AS23352 +108.161.22.78:8333 # AS54154 +108.174.63.234:8333 # AS36352 +109.99.63.159:8333 # AS9050 +109.105.40.247:8333 # AS12570 +109.107.185.130:8333 # AS48282 +109.110.239.4:8333 # AS35432 +109.173.41.43:8333 # AS42610 +109.236.90.117:8333 # AS49981 +109.248.206.13:8333 # AS203493 +109.255.106.206:8333 # AS6830 +111.90.140.23:8333 # AS45839 +111.90.140.46:8333 # AS45839 +111.90.159.246:8333 # AS34309 +112.118.188.50:8333 # AS4760 +115.47.141.250:8885 # AS4134 +116.58.171.67:8333 # AS2514 +118.92.107.108:8333 # AS9500 +119.42.55.203:8333 # AS133159 +120.79.71.72:8333 # AS37963 +121.99.240.87:8333 # AS9790 +123.60.213.192:8333 # AS55990 +124.156.158.100:8333 # AS132203 +124.222.123.238:8333 # AS45090 +125.178.6.116:8333 # AS3786 +128.0.190.26:8333 # AS30764 +128.65.194.136:8333 # AS29222 +129.13.189.212:8333 # AS34878 +129.126.172.115:8333 # AS17547 +129.146.52.174:8333 # AS31898 +130.44.168.202:8333 # AS6079 +131.161.80.166:8333 # AS263694 +131.188.40.191:8333 # AS680 +134.195.185.52:8333 # AS13536 +135.134.238.47:8333 # AS4181 +135.180.218.58:8333 # AS46375 +135.181.215.237:8333 # AS24940 +136.29.109.180:8333 # AS19165 +136.32.238.6:8333 # AS16591 +136.56.170.96:8333 # AS16591 +137.25.38.108:8333 # AS20115 +137.226.34.46:8333 # AS680 +138.207.211.106:8333 # AS11776 +139.130.41.82:8333 # AS1221 +139.153.255.107:8333 # AS786 +140.190.12.129:8333 # AS14828 +142.54.181.218:8333 # AS32097 +143.177.229.149:8333 # AS50266 +143.178.64.10:8333 # AS50266 +144.24.245.183:8333 # AS31898 +144.126.130.178:8333 # AS40021 +146.4.124.129:8333 # AS3303 +146.71.69.103:8333 # AS7782 +146.83.56.69:8333 # AS23140 +147.194.177.165:8333 # AS15128 +149.90.214.78:8333 # AS12353 +149.102.157.156:8333 # AS13768 +151.248.156.55:8333 # AS8821 +151.252.193.245:8333 # AS29582 +153.92.93.114:8333 # AS41998 +154.211.6.2:8333 # AS140224 +156.17.103.2:8088 # AS8970 +156.146.177.221:8333 # AS1448 +157.131.143.173:8333 # AS46375 +158.58.188.37:8333 # AS57497 +158.248.39.239:8333 # AS29695 +159.89.230.128:8333 # AS14061 +159.196.3.239:8333 # AS4764 +159.224.189.250:8333 # AS13188 +160.72.51.154:8333 # AS46887 +161.29.236.55:8333 # AS4826 +161.97.119.166:8333 # AS51167 +161.246.11.230:8333 # AS9486 +162.62.18.226:8333 # AS132203 +162.250.123.179:8333 # AS19318 +162.250.191.222:8333 # AS26832 +162.254.118.20:8333 # AS6130 +163.172.81.70:8333 # AS12876 +164.90.47.8:8333 # AS53449 +165.228.174.117:8333 # AS1221 +166.70.145.151:8333 # AS6315 +168.91.238.8:8333 # AS11039 +170.253.11.25:8333 # AS15704 +171.103.170.115:8333 # AS7470 +172.93.166.135:8333 # AS22653 +172.103.217.236:8333 # AS25668 +172.105.21.216:8333 # AS63949 +172.112.153.95:8333 # AS20001 +173.3.218.91:8333 # AS6128 +173.12.119.133:8333 # AS7922 +173.34.127.181:8333 # AS812 +173.76.123.173:8333 # AS701 +173.176.198.68:8333 # AS5769 +173.208.152.218:8333 # AS32097 +173.241.227.243:8333 # AS19009 +173.246.27.7:8333 # AS1403 +173.255.240.205:8333 # AS63949 +174.30.47.15:8333 # AS209 +174.114.250.86:8333 # AS812 +174.138.35.229:8333 # AS14061 +174.142.191.136:8333 # AS32613 +176.10.143.190:8333 # AS8473 +176.74.136.237:8333 # AS35613 +176.118.220.29:8333 # AS60042 +176.126.116.7:8333 # AS20473 +176.126.167.10:8333 # AS8449 +176.212.185.153:8333 # AS9049 +176.235.209.186:8333 # AS34984 +177.81.236.117:8333 # AS28573 +177.89.205.70:8333 # AS28220 +178.48.168.12:8333 # AS21334 +178.124.162.209:8333 # AS6697 +178.159.98.133:8333 # AS202390 +178.196.89.209:8333 # AS3303 +178.236.137.63:8333 # AS44843 +178.252.123.24:8333 # AS42893 +179.43.170.186:8333 # AS51852 +180.150.46.187:8333 # AS4764 +181.117.128.140:8333 # AS19037 +184.19.19.16:8333 # AS5650 +185.21.217.48:8333 # AS200052 +185.25.48.184:8333 # AS61272 +185.31.136.246:8333 # AS47605 +185.52.93.45:8333 # AS39449 +185.64.116.15:8333 # AS31736 +185.68.249.91:8333 # AS51184 +185.98.54.20:8333 # AS39572 +185.107.83.55:8333 # AS43350 +185.140.253.169:8333 # AS200735 +185.148.145.74:8333 # AS44901 +185.165.170.19:8333 # AS3223 +185.167.113.59:8333 # AS207054 +185.185.26.141:8111 # AS201206 +185.197.163.136:8333 # AS60144 +185.209.12.76:8333 # AS212323 +185.209.70.17:8333 # AS204568 +185.227.156.226:8333 # AS209846 +185.233.189.210:8333 # AS61303 +185.239.221.5:8333 # AS61282 +185.244.100.106:8333 # AS2586 +185.254.97.164:8333 # AS44486 +186.33.167.11:8333 # AS1299 +186.176.98.37:8333 # AS262197 +186.249.217.25:8333 # AS7195 +186.250.95.132:8333 # AS262967 +188.32.14.31:8334 # AS42610 +188.35.167.14:8333 # AS34123 +188.68.45.143:8333 # AS47147 +188.117.200.212:8333 # AS25447 +188.138.88.14:8333 # AS20773 +188.151.237.158:8333 # AS1257 +188.154.236.49:8333 # AS6730 +189.123.177.128:8333 # AS4230 +190.123.27.11:8333 # AS52468 +190.145.127.254:8333 # AS14080 +192.69.53.77:8333 # AS11142 +192.146.137.44:8333 # AS25376 +192.222.24.54:8333 # AS22646 +192.222.147.141:8333 # AS1403 +193.32.127.162:60969 # AS39351 +193.111.198.187:8111 # AS24961 +193.196.37.62:8333 # AS34878 +194.13.80.185:15430 # AS47147 +194.147.113.201:8333 # AS21232 +194.165.30.20:8333 # AS35162 +194.191.239.98:8333 # AS1836 +195.56.63.4:8333 # AS5483 +195.56.63.10:8333 # AS5483 +195.123.239.185:8333 # AS64010 +195.140.226.154:8333 # AS35614 +198.1.231.6:8333 # AS30236 +198.148.112.27:8333 # AS35916 +199.126.234.237:8333 # AS395570 +199.193.174.173:8333 # AS7992 +199.247.7.208:8333 # AS20473 +200.122.181.46:8333 # AS3790 +201.191.6.103:8333 # AS11830 +201.212.36.209:8333 # AS7303 +201.221.234.200:8333 # AS27928 +202.108.211.135:8333 # AS4837 +202.169.17.178:8333 # AS137549 +202.177.24.140:8333 # AS7479 +203.130.48.117:8885 # AS54994 +203.132.94.196:8333 # AS38195 +205.178.41.124:8333 # AS11039 +206.72.201.228:8333 # AS19318 +206.192.203.0:8333 # AS7029 +206.223.153.52:8333 # AS19214 +207.134.216.145:8334 # AS395570 +207.188.154.50:8333 # AS15704 +207.229.46.80:8333 # AS852 +207.255.193.47:8333 # AS11776 +208.104.92.74:8333 # AS14615 +209.58.145.157:8333 # AS394380 +209.58.158.232:8335 # AS394380 +209.141.43.243:8333 # AS53667 +209.226.142.62:8333 # AS577 +209.237.127.227:8333 # AS1299 +209.237.133.54:8333 # AS53859 +211.248.90.50:8333 # AS4766 +212.21.18.78:8333 # AS20485 +212.34.225.118:8333 # AS44395 +212.51.146.137:8333 # AS13030 +212.227.211.87:8333 # AS8560 +213.0.69.76:8333 # AS3352 +213.5.36.58:8333 # AS49974 +213.47.64.105:8333 # AS8412 +213.89.135.151:8333 # AS1257 +213.141.154.201:8333 # AS12714 +213.159.198.45:8333 # AS8359 +213.184.244.24:8333 # AS60280 +213.214.66.182:8333 # AS43205 +213.226.123.76:8333 # AS49943 +216.146.251.8:8333 # AS54579 +216.186.238.14:8333 # AS12083 +217.5.150.114:8333 # AS3320 +217.15.178.11:8333 # AS25534 +217.24.239.109:8333 # AS9063 +217.64.47.138:8333 # AS39324 +217.73.80.104:8333 # AS44291 +217.79.181.38:8333 # AS24961 +217.92.55.246:8333 # AS3320 +217.113.121.169:8333 # AS8416 +217.115.116.250:8333 # AS30900 +217.155.244.170:8333 # AS13037 +217.170.124.170:8333 # AS35401 +220.132.135.54:8333 # AS3462 +220.233.178.199:8333 # AS38195 +222.154.111.46:8333 # AS4648 +[2001:1620:510::2]:8333 # AS13030 +[2001:19f0:6001:39aa:5400:3ff:fef0:916]:8333 # AS20473 +[2001:19f0:8001:f71:5400:4ff:fe10:6a63]:8333 # AS20473 +[2001:1bc0:c1::2000]:8333 # AS29686 +[2001:1c02:11e:3500:df25:6321:8260:d9be]:8333 # AS6830 +[2001:41d0:1004:1b79::]:8339 # AS16276 +[2001:41d0:203:3739::]:8333 # AS16276 +[2001:41d0:203:aacc::]:8333 # AS16276 +[2001:41d0:203:bb0a::]:8333 # AS16276 +[2001:41d0:2:bf8f::]:8333 # AS16276 +[2001:41d0:303:6586::]:8333 # AS16276 +[2001:41d0:602:4493::]:8333 # AS16276 +[2001:41d0:8:b9d8::1]:8333 # AS16276 +[2001:41d0:a:69a2::1]:8333 # AS16276 +[2001:41f0::62:6974:636f:696e]:8333 # AS6830 +[2001:44b8:256:5d11:216:3eff:fe39:d5d4]:8333 # AS4739 +[2001:470:1b62::]:8333 # AS6939 +[2001:470:1f07:803:20c:29ff:fe2d:5879]:8333 # AS6939 +[2001:470:1f15:106:e2d5:5eff:fe42:7ae5]:8333 # AS6939 +[2001:470:1f15:c43::11]:8333 # AS6939 +[2001:470:26:472::b7c]:8333 # AS6939 +[2001:470:75e9:1::10]:8333 # AS6939 +[2001:470:de5a::ec]:9333 # AS6939 +[2001:4ba0:babe:584::1]:8333 # AS24961 +[2001:4ba0:ffff:24::1]:8333 # AS24961 +[2001:4dd0:3564:0:30b7:1d7b:6fec:4c5c]:8333 # AS8422 +[2001:4dd0:3564:0:88e:b4ff:2ad0:699b]:8333 # AS8422 +[2001:4dd0:3564:0:9c1c:cc31:9fe8:5505]:8333 # AS8422 +[2001:4dd0:3564:0:a0c4:d41f:4c4:1bb0]:8333 # AS8422 +[2001:4dd0:3564:0:fd76:c1d3:1854:5bd9]:8333 # AS8422 +[2001:4dd0:3564:1::7676:8090]:8333 # AS8422 +[2001:4dd0:3564:1:b977:bd71:4612:8e40]:8333 # AS8422 +[2001:4dd0:af0e:3564::69:1]:8333 # AS8422 +[2001:4dd0:af0e:3564::69:90]:8333 # AS8422 +[2001:4de8:b1b2:1:0:dead:beef:7]:8333 # AS29208 +[2001:638:a000:4140::ffff:191]:8333 # AS680 +[2001:678:acc:42::]:8333 # AS60404 +[2001:67c:26b4:ff00::44]:8333 # AS25376 +[2001:67c:2db8:6::36]:8333 # AS39798 +[2001:7c0:2310:0:f816:3eff:fe0d:4ab6]:8333 # AS34878 +[2001:7c0:2310:0:f816:3eff:fe6c:4f58]:8333 # AS34878 +[2001:861:3246:a10::40]:8333 # AS5410 +[2001:b07:2e6:38d7:ba27:ebff:fe60:3dc1]:8333 # AS12874 +[2001:b07:6461:7811:489:d2da:e07:1af7]:8333 # AS12874 +[2001:b07:ac9:442b:79d6:bbbe:b37c:a783]:8333 # AS12874 +[2001:bc8:1600:0:208:a2ff:fe0c:8a2e]:8333 # AS12876 +[2001:bc8:323c:ff:a634:384f:1849:f4bc]:8333 # AS12876 +[2001:bc8:323c:ff:d217:c2ff:fe07:2cd9]:8333 # AS12876 +[2001:bc8:3bec:100::1]:8333 # AS12876 +[2002:2f5b:a5f9::2f5b:a5f9]:8885 # AS6939 +[2003:cb:8713:6102:aaa1:59ff:fe57:7779]:8333 # AS3320 +[2003:e0:370e:1400::5]:8333 # AS3320 +[2003:f6:3f10:6700:4c9f:7620:8324:d4a7]:8333 # AS3320 +[2400:2410:cea2:d00:41bc:c9ea:861b:51ee]:8333 # AS17676 +[2400:2411:a3e1:4900:2568:684b:e99:7120]:8333 # AS17676 +[2400:2411:a3e1:4900:2987:b88f:61e0:84fa]:8333 # AS17676 +[2400:3b00:20:c:bacb:29ff:feab:8886]:8333 # AS18229 +[2401:b140:1::100:210]:8333 # AS54415 +[2401:b140:1::100:220]:8333 # AS54415 +[2401:b140::42:100]:8333 # AS6939 +[2401:b140::44:130]:8333 # AS6939 +[2401:d002:3902:700:d72c:5e22:4e95:389d]:8333 # AS38195 +[2404:4408:6752:c000::1999]:8333 # AS9790 +[2404:7a85:4161:2b00:49a1:427a:fac:3409]:8333 # AS2518 +[2405:9800:b972:ab58:c05:e938:267e:271]:8333 # AS45430 +[2406:da11:169:b03:32b5:f901:9f7c:3e4b]:8333 # AS16509 +[2406:da14:335:b601:ceb7:b4fc:a855:f3a5]:8333 # AS16509 +[2406:da1e:a4e:8a03:2aad:496b:768d:e497]:8333 # AS16509 +[2407:8800:bc61:2202:a0c6:107:502b:4e3b]:8333 # AS7545 +[2409:10:ca20:1df0:224:e8ff:fe1f:60d9]:8333 # AS55391 +[2600:1700:22f1:641f:e8:39c8:eb1d:a1eb]:8333 # AS7018 +[2600:1700:9c5d:ed0::38]:8333 # AS7018 +[2600:1700:9c5d:ed0:d0d6:1d9:5cc2:ab47]:8333 # AS7018 +[2600:1702:1ce0:4010::40]:8333 # AS7018 +[2600:1f14:40e:e301:d155:aa3a:77be:960e]:8333 # AS16509 +[2600:1f16:a08:b901:1afa:ef4e:4ce7:2ba4]:8333 # AS16509 +[2600:1f1c:2d3:2403:5bac:3fc6:6513:7a63]:8333 # AS16509 +[2600:2104:1003:c5ab:dc5e:90ff:fe18:1d08]:8333 # AS11404 +[2600:3c00::f03c:92ff:fe92:2745]:8333 # AS63949 +[2600:3c00::f03c:92ff:fecf:61b6]:8333 # AS63949 +[2600:3c00::f03c:93ff:feb3:1b6]:8333 # AS63949 +[2600:3c00:e002:2e32::1:14]:8333 # AS63949 +[2600:3c02::f03c:92ff:fe5d:9fb]:8333 # AS63949 +[2600:4040:2854:5e00:c6e9:84ff:fe46:ee8]:8666 # AS13786 +[2600:6c54:7100:1ad1:bddf:550e:91be:f9e1]:8333 # AS20115 +[2600:8805:2400:14e:12dd:b1ff:fef2:3013]:8333 # AS22773 +[2601:184:300:bde:3c29:8e94:1ba8:fde3]:8333 # AS7922 +[2601:18c:8080:300f:219:d1ff:fe75:dc2f]:8333 # AS7922 +[2601:18d:4600:43f1:20e7:b3ff:fecf:a99]:8333 # AS7922 +[2601:18d:8701:c290::3330]:8333 # AS7922 +[2601:246:4d7f:9e28:f321:36ca:7a71:c687]:8333 # AS7922 +[2601:640:c201:960d:86eb:f27d:66a2:f2c1]:8333 # AS7922 +[2602:241:75d1:2b90::7840]:8333 # AS46375 +[2602:ffb8::208:72:57:200]:8333 # AS2914 +[2603:3004:6a1:3800:851f:584d:7aba:affb]:8333 # AS7922 +[2603:3004:6a1:3800::4402]:8333 # AS7922 +[2603:3004:70d:1400:8532:2900:ce6f:acdf]:8333 # AS7922 +[2603:3004:745:900:f0d7:556a:a8c:ced5]:8333 # AS7922 +[2603:6080:c000:5d8a::104f]:8333 # AS7843 +[2603:8000:d100:8991:cc29:ccff:fe42:300c]:8333 # AS7843 +[2603:8080:1f07:6fdd:7de2:d969:78c9:b7ea]:8333 # AS7843 +[2603:8080:7300:531::13ea]:8333 # AS7843 +[2603:80a0:703:40f8::38]:8333 # AS7843 +[2604:180:f3::218]:8333 # AS3842 +[2604:3d08:0:5:d941:4b03:a093:131b]:8333 # AS6327 +[2604:7c00:120:4b::eb24]:8333 # AS174 +[2604:a00:21:3043:bf6a:535e:dfeb:5b7b]:8333 # AS19318 +[2604:a880:400:d0::1ce7:4001]:8333 # AS14061 +[2604:a880:400:d0::1d44:e001]:8333 # AS14061 +[2604:a880:400:d0::261f:6001]:8333 # AS14061 +[2604:a880:400:d1::7e2:e001]:8333 # AS14061 +[2604:a880:4:1d0::14:3000]:8333 # AS14061 +[2604:a880:4:1d0::e5:b000]:8333 # AS14061 +[2605:6400:30:f220::]:8333 # AS53667 +[2605:6f80:0:7:fc1b:ccff:fe8a:d822]:8333 # AS53340 +[2605:a140:2076:8253::1]:8333 # AS40021 +[2605:a140:3007:1287::1]:8333 # AS40021 +[2605:ae00:203::203]:8333 # AS7819 +[2605:c000:2a0a:1::102]:8333 # AS7393 +[2607:1a00:1:d::11:7c4d]:8333 # AS22653 +[2607:5300:203:1214::]:8333 # AS16276 +[2607:9280:b:73b:250:56ff:fe14:25b5]:8333 # AS395502 +[2607:9280:b:73b:250:56ff:fe21:9c2f]:8333 # AS395502 +[2607:9280:b:73b:250:56ff:fe21:bf32]:8333 # AS395502 +[2607:9280:b:73b:250:56ff:fe33:4d1b]:8333 # AS395502 +[2607:9280:b:73b:250:56ff:fe3d:401]:8333 # AS395502 +[2607:f2c0:e1c2:69:12c3:7bff:fe4d:9431]:8333 # AS5645 +[2607:f2c0:e1c2:69:ecb2:6e88:9f33:5057]:8333 # AS5645 +[2620:6:2003:105:2d8:61ff:fe0f:853]:8333 # AS25682 +[2620:6e:a000:1:42:42:42:42]:8333 # AS397444 +[2620:a6:2000:1::3:d570]:8333 # AS27566 +[2620:a6:2000:1::5:162a]:8333 # AS27566 +[2620:a6:2000:1::5:1631]:8333 # AS27566 +[2620:a6:2000:1::c:e634]:8333 # AS27566 +[2800:40:33:8ab:a0e7:b215:fc83:5c31]:8333 # AS16814 +[2800:bf0:149:f4b:f8df:8d7d:801b:e25e]:8333 # AS27947 +[2804:14c:198:80d5:7603:41d1:d3fc:e797]:8333 # AS28573 +[2804:14d:ae81:827b:99a8:1e3f:6db2:29db]:8333 # AS4230 +[2804:d57:5537:4800:3e7c:3fff:fe7b:80aa]:8333 # AS8167 +[2a00:12e0:101:99:20c:29ff:fe29:d03f]:8333 # AS6798 +[2a00:1328:e101:c00::163]:8333 # AS31078 +[2a00:1398:4:2a03:215:5dff:fed6:1033]:8333 # AS34878 +[2a00:1398:4:2a03::bc03]:8333 # AS34878 +[2a00:1630:10:1003:0:b19:b00b:babe]:8333 # AS49544 +[2a00:1768:2001:27::ef6a]:8333 # AS43350 +[2a00:1828:a004:2::666]:8333 # AS34240 +[2a00:1c10:2:709::217]:22220 # AS50300 +[2a00:1f40:5001:108:5d17:7703:b0f5:4133]:8333 # AS42864 +[2a00:23c5:fe80:7301:d6ae:52ff:fed5:56a5]:8333 # AS2856 +[2a00:23c6:5c91:5808:c05a:4dff:fe65:9d69]:8333 # AS2856 +[2a00:6020:1bfa:d400:20c:29ff:fe61:4a4c]:8333 # AS60294 +[2a00:6020:b482:9200:491a:358c:d8f7:1da]:8333 # AS60294 +[2a00:6020:b489:2000:5054:ff:fefc:5ed8]:8333 # AS60294 +[2a00:7c80:0:25::e37a]:8333 # AS49981 +[2a00:7c80:0:71::8]:8333 # AS49981 +[2a00:8a60:e012:a00::21]:8333 # AS680 +[2a00:ae40:240e:3200::3]:8333 # AS50923 +[2a00:bbe0:cc:0:62a4:4cff:fe23:7510]:8333 # AS47605 +[2a00:ca8:a1f:3025:f949:e442:c940:13e8]:8333 # AS30764 +[2a00:d4e0:2:d002:4467:31e0:6fa5:b3ef]:8333 # AS15600 +[2a00:ee2:1200:1900:8d3:d2ff:feb1:bc58]:8333 # AS5603 +[2a01:238:420f:9200:fa5a:1a4b:1e6a:fadf]:8333 # AS6724 +[2a01:238:4389:c400:3b26:d94e:38d5:44ef]:8333 # AS6724 +[2a01:490:16:301::2]:8333 # AS8251 +[2a01:4b00:807c:3100:cda1:c6a:2bad:2418]:8333 # AS56478 +[2a01:4f8:141:2254::2]:8333 # AS24940 +[2a01:4f8:173:230a::2]:8333 # AS24940 +[2a01:4f8:190:91c4::2]:8333 # AS24940 +[2a01:4f8:200:7222::2]:8333 # AS24940 +[2a01:4f8:202:3e6::2]:8333 # AS24940 +[2a01:4f8:221:44d7::2]:8333 # AS24940 +[2a01:4f8:231:915::2]:8333 # AS24940 +[2a01:4f9:2a:1ce0::2]:8333 # AS24940 +[2a01:4f9:2b:29a::2]:8333 # AS24940 +[2a01:4f9:4a:31de::2]:8333 # AS24940 +[2a01:5200:6c:6162:7a61:746b:6f2e:736b]:8333 # AS6855 +[2a01:6380:fffe:73:10fb:d012:8581:b4d7]:8333 # AS25540 +[2a01:7a7:2:2804:ae1f:6bff:fe9d:6c94]:8333 # AS20773 +[2a01:7c8:aaac:89:5054:ff:feb7:f5cb]:8333 # AS20857 +[2a01:7c8:aac9:c9:5054:ff:fedf:ff95]:8333 # AS20857 +[2a01:7c8:d001:1c1:5054:ff:feee:3e1a]:8333 # AS20857 +[2a01:7c8:d009:2aa:5054:ff:fe1b:a196]:11520 # AS20857 +[2a01:7c8:fffa:50e:ddfe:c924:ca0a:cbab]:8333 # AS20857 +[2a01:7e00::f03c:93ff:fe59:66dc]:8333 # AS63949 +[2a01:7e01::f03c:93ff:fe3b:bb5b]:8333 # AS63949 +[2a01:8740:1:ffc5::8c6a]:8333 # AS57344 +[2a01:9f40:a000::100]:8333 # AS42908 +[2a01:cb00:d3d:7700:227:eff:fe28:c565]:8333 # AS3215 +[2a01:e0a:20:7350:919c:b1c3:8b83:adf9]:8333 # AS12322 +[2a01:e0a:301:7010:b87d:e14b:cea9:b998]:8333 # AS12322 +[2a01:e0a:48b:2d10:94f2:4d5c:ca5f:bf49]:8333 # AS12322 +[2a01:e0a:530:a0a0:f465:af5:be1b:9075]:8333 # AS12322 +[2a01:e0a:aa7:c8c0:9679:affa:b6e5:efc7]:8333 # AS12322 +[2a01:e11:100c:70:cbc8:9e31:4b77:1626]:8333 # AS12322 +[2a01:e34:ee78:3060:230:48ff:fe81:f1c6]:8333 # AS12322 +[2a02:1210:14a9:6700:a00:27ff:fe4e:82b6]:8333 # AS3303 +[2a02:1210:4639:f00:10a7:e965:509a:7a4a]:8333 # AS3303 +[2a02:1210:7c92:5100:211:32ff:feae:152d]:8333 # AS3303 +[2a02:1210:86bf:f100:3178:d700:d44d:6bb1]:8333 # AS3303 +[2a02:1210:9487:a200:edc1:93a4:945:9a92]:8333 # AS3303 +[2a02:168:420b:a::20]:8333 # AS13030 +[2a02:168:6328:0:4a21:bff:fe26:38c3]:8333 # AS13030 +[2a02:168:676e:0:e65f:1ff:fe09:3591]:8333 # AS13030 +[2a02:1748:f39f:5872:dead:beef:b1ac:c0fe]:8333 # AS51184 +[2a02:180:1:1::517:10b6]:8333 # AS35366 +[2a02:2168:a379:d100:96de:80ff:fea3:fd00]:8333 # AS42610 +[2a02:2780:9000:70::7]:8333 # AS35434 +[2a02:2780:9000:70::f]:8333 # AS35434 +[2a02:2780::e01a]:8333 # AS35434 +[2a02:2e02:3900:5400:a099:e1ff:feb6:d0e]:8333 # AS12479 +[2a02:2f05:660e:8b00::1]:8333 # AS48571 +[2a02:58:97:7d20::60]:8333 # AS25596 +[2a02:6d40:3073:c01:dea6:32ff:fe44:4b25]:8333 # AS42652 +[2a02:7a01::91:228:45:130]:8333 # AS16019 +[2a02:7b40:5928:89::1]:8333 # AS62282 +[2a02:7b40:c3b5:f583::1]:8333 # AS62282 +[2a02:8308:8087:aa00:9ea8:1b2:ef98:56bf]:8333 # AS16019 +[2a02:842a:1df:8a01:1e1b:dff:fe0b:236d]:8333 # AS15557 +[2a02:a44d:14d6:1:2c0:8ff:fe8f:b3b2]:8333 # AS1136 +[2a02:a45a:94cd:f00d::1]:8333 # AS1136 +[2a02:a45f:3b9d:30::3]:8333 # AS1136 +[2a02:a467:7833:1:7285:c2ff:fe2c:21e9]:8333 # AS1136 +[2a02:aa14:2380:b300:4040:be88:8b01:d38]:8333 # AS6830 +[2a02:c206:2044:9826::1]:8333 # AS51167 +[2a02:c206:2082:1246::1]:8333 # AS51167 +[2a02:c206:3008:2368::1]:8333 # AS51167 +[2a02:c207:0:4971::1]:5332 # AS51167 +[2a02:c207:2014:4199::1]:8333 # AS51167 +[2a02:c207:2024:6115::1]:8333 # AS51167 +[2a02:c207:2026:6682::1]:8333 # AS51167 +[2a02:c207:3002:7468::1]:8333 # AS51167 +[2a02:e98:20:1504::1]:8333 # AS24641 +[2a03:4000:6:416c::43]:8333 # AS47147 +[2a03:4000:6:f814:548b:17ff:fe31:b64a]:8333 # AS47147 +[2a03:6000:870:0:46:23:87:218]:8333 # AS51088 +[2a03:94e0:ffff:185:243:218:0:19]:8333 # AS56655 +[2a03:b0c0:1:e0::397:6001]:8333 # AS14061 +[2a03:b0c0:2:f0::163:3001]:8333 # AS14061 +[2a03:b0c0:2:f0::18a:d001]:8333 # AS14061 +[2a03:b0c0:3:d0::f3e:2001]:8333 # AS14061 +[2a03:e2c0:1347::2]:8333 # AS50113 +[2a03:ec0:0:928::701:701]:8333 # AS199669 +[2a04:52c0:103:c455::1]:8334 # AS60404 +[2a04:52c0:3007:200::2000]:8333 # AS60404 +[2a04:bc40:1dc3:8d::2:1001]:8333 # AS35277 +[2a05:1500:702:0:1c00:40ff:fe00:c]:8333 # AS48635 +[2a05:3580:d101:3700::]:8333 # AS20764 +[2a05:3580:db0b:1600:c489:76ed:313d:b33]:8333 # AS20764 +[2a05:d014:a55:4001:8127:afa7:daf9:d91b]:8333 # AS16509 +[2a05:d014:a55:4001:f6ab:dd5e:4039:b46c]:8333 # AS16509 +[2a05:d014:a55:4003:6523:50a1:152:e88c]:8333 # AS16509 +[2a05:d01a:b7b:3c01:8bf7:ae14:afb3:33ae]:8333 # AS16509 +[2a05:f480:1800:697:5400:2ff:feb6:c36d]:8333 # AS20473 +[2a06:e040:7603:2918:c6ef:464e:9fe5:73ec]:8333 # AS198507 +[2a07:abc4::1:946]:8333 # AS62000 +[2a09:2681:102::210]:8333 # AS61282 +[2a0a:c801:1:7::183]:8333 # AS39798 +[2a0c:5a80:1210:a800:6af7:28ff:fee5:6b3a]:8333 # AS57269 +[2a0d:5600:24:a8e::a91e]:55373 # AS9009 +[2a0d:7c40:3000:b04::2]:8333 # AS54290 +[2a0d:8340:24::2]:8333 # AS50113 +[2a0f:df00:0:2010::162]:8333 # AS41281 +[2a10:3781:16b9:1:fe3f:dbff:fe04:2d4c]:8333 # AS206238 +[2a10:3781:84b:1:b123:6306:943a:f09b]:8333 # AS206238 +[2a10:d200:1:33:a6bf:1ff:fe6a:46a9]:8333 # AS212323 +[2c0f:f4c0:2202:20b0:261c:4ff:fe14:daa0]:8333 # AS327693 +[2c0f:f8f0:da51:0:70c3:eea9:9717:9579]:8333 # AS30844 -# manually added 2021-03 for minimal torv3 bootstrap support -2g5qfdkn2vvcbqhzcyvyiitg4ceukybxklraxjnu7atlhd22gdwywaid.onion:8333 -2jmtxvyup3ijr7u6uvu7ijtnojx4g5wodvaedivbv74w4vzntxbrhvad.onion:8333 -37m62wn7dz3uqpathpc4qfmgrbupachj52nt3jbtbjugpbu54kbud7yd.onion:8333 +# manually updated 2022-08 for minimal torv3 bootstrap support 5g72ppm3krkorsfopcm2bi7wlv4ohhs4u4mlseymasn7g7zhdcyjpfid.onion:8333 -7cgwjuwi5ehvcay4tazy7ya6463bndjk6xzrttw5t3xbpq4p22q6fyid.onion:8333 -7pyrpvqdhmayxggpcyqn5l3m5vqkw3qubnmgwlpya2mdo6x7pih7r7id.onion:8333 b64xcbleqmwgq2u46bh4hegnlrzzvxntyzbmucn3zt7cssm7y4ubv3id.onion:8333 -ejxefzf5fpst4mg2rib7grksvscl7p6fvjp6agzgfc2yglxnjtxc3aid.onion:8333 fjdyxicpm4o42xmedlwl3uvk5gmqdfs5j37wir52327vncjzvtpfv7yd.onion:8333 fpz6r5ppsakkwypjcglz6gcnwt7ytfhxskkfhzu62tnylcknh3eq6pad.onion:8333 -fzhn4uoxfbfss7h7d6ffbn266ca432ekbbzvqtsdd55ylgxn4jucm5qd.onion:8333 gxo5anvfnffnftfy5frkgvplq3rpga2ie3tcblo2vl754fvnhgorn5yd.onion:8333 ifdu5qvbofrt4ekui2iyb3kbcyzcsglazhx2hn4wfskkrx2v24qxriid.onion:8333 itz3oxsihs62muvknc237xabl5f6w6rfznfhbpayrslv2j2ubels47yd.onion:8333 -lrjh6fywjqttmlifuemq3puhvmshxzzyhoqx7uoufali57eypuenzzid.onion:8333 +kpgvmscirrdqpekbqjsvw5teanhatztpp2gl6eee4zkowvwfxwenqaid.onion:8333 m7cbpjolo662uel7rpaid46as2otcj44vvwg3gccodnvaeuwbm3anbyd.onion:8333 -opnyfyeiibe5qo5a3wbxzbb4xdiagc32bbce46owmertdknta5mi7uyd.onion:8333 -owjsdxmzla6d7lrwkbmetywqym5cyswpihciesfl5qdv2vrmwsgy4uqd.onion:8333 -q7kgmd7n7h27ds4fg7wocgniuqb3oe2zxp4nfe4skd5da6wyipibqzqd.onion:8333 +mwmfluek4au6mxxpw6fy7sjhkm65bdfc7izc7lpz3trewfdghyrzsbid.onion:8333 rp7k2go3s5lyj3fnj6zn62ktarlrsft2ohlsxkyd7v3e3idqyptvread.onion:8333 -sys54sv4xv3hn3sdiv3oadmzqpgyhd4u4xphv4xqk64ckvaxzm57a7yd.onion:8333 -tddeij4qigtjr6jfnrmq6btnirmq5msgwcsdpcdjr7atftm7cxlqztid.onion:8333 -vi5bnbxkleeqi6hfccjochnn65lcxlfqs4uwgmhudph554zibiusqnad.onion:8333 -xqt25cobm5zqucac3634zfght72he6u3eagfyej5ellbhcdgos7t2had.onion:8333 -# manually added 2021-08 for minimal i2p bootstrap support +# manually updated 2022-08 for minimal i2p bootstrap support +255fhcp6ajvftnyo7bwz3an3t4a4brhopm3bamyh2iu5r3gnr2rq.b32.i2p:0 +27yrtht5b5bzom2w5ajb27najuqvuydtzb7bavlak25wkufec5mq.b32.i2p:0 +2el6enckmfyiwbfcwsygkwksovtynzsigmyv3bzyk7j7qqahooua.b32.i2p:0 +3gocb7wc4zvbmmebktet7gujccuux4ifk3kqilnxnj5wpdpqx2hq.b32.i2p:0 +3tns2oov4tnllntotazy6umzkq4fhkco3iu5rnkxtu3pbfzxda7q.b32.i2p:0 +4fcc23wt3hyjk3csfzcdyjz5pcwg5dzhdqgma6bch2qyiakcbboa.b32.i2p:0 +4osyqeknhx5qf3a73jeimexwclmt42cju6xdp7icja4ixxguu2hq.b32.i2p:0 +4umsi4nlmgyp4rckosg4vegd2ysljvid47zu7pqsollkaszcbpqq.b32.i2p:0 +52v6uo6crlrlhzphslyiqblirux6olgsaa45ixih7sq5np4jujaa.b32.i2p:0 +6j2ezegd3e2e2x3o3pox335f5vxfthrrigkdrbgfbdjchm5h4awa.b32.i2p:0 +6n36ljyr55szci5ygidmxqer64qr24f4qmnymnbvgehz7qinxnla.b32.i2p:0 +72yjs6mvlby3ky6mgpvvlemmwq5pfcznrzd34jkhclgrishqdxva.b32.i2p:0 +7r4ri53lby2i3xqbgpw3idvhzeku7ubhftlf72ldqkg5kde6dauq.b32.i2p:0 a5qsnv3maw77mlmmzlcglu6twje6ttctd3fhpbfwcbpmewx6fczq.b32.i2p:0 -bitcornrd36coazsbzsz4pdebyzvaplmsalq4kpoljmn6cg6x5zq.b32.i2p:0 +aovep2pco7v2k4rheofrgytbgk23eg22dczpsjqgqtxcqqvmxk6a.b32.i2p:0 +bddbsmkas3z6fakorbkfjhv77i4hv6rysyjsvrdjukxolfghc23q.b32.i2p:0 +bitcoi656nll5hu6u7ddzrmzysdtwtnzcnrjd4rfdqbeey7dmn5a.b32.i2p:0 +brifkruhlkgrj65hffybrjrjqcgdgqs2r7siizb5b2232nruik3a.b32.i2p:0 c4gfnttsuwqomiygupdqqqyy5y5emnk5c73hrfvatri67prd7vyq.b32.i2p:0 -dhtq2p76tyhi442aidb3vd2bv7yxxjuddpb2jydnnrl2ons5bhha.b32.i2p:0 +day3hgxyrtwjslt54sikevbhxxs4qzo7d6vi72ipmscqtq3qmijq.b32.i2p:0 +di2zq6fr3fegf2jdcd7hdwyql4umr462gonsns2nxz5qg5vz4bka.b32.i2p:0 +e55k6wu46rzp4pg5pk5npgbr3zz45bc3ihtzu2xcye5vwnzdy7pq.b32.i2p:0 +eciohu5nq7vsvwjjc52epskuk75d24iccgzmhbzrwonw6lx4gdva.b32.i2p:0 +ejlnngarmhqvune74ko7kk55xtgbz5i5ncs4vmnvjpy3l7y63xaa.b32.i2p:0 +g47cqoppu26pr4n2cfaioqx7lbdi7mea7yqhlrkdz3wjwxjxdh2a.b32.i2p:0 h3r6bkn46qxftwja53pxiykntegfyfjqtnzbm6iv6r5mungmqgmq.b32.i2p:0 -hnbbyjpxx54623l555sta7pocy3se4sdgmuebi5k6reesz5rjp6q.b32.i2p:0 +hhfi4yqkg2twqiwezrfksftjjofbyx3ojkmlnfmcwntgnrjjhkya.b32.i2p:0 +hpiibrflqkbrcshfhmrtwfyeb7mds7a3obzwrgarejevddzamvsq.b32.i2p:0 +i4pyhsfdq4247dunel7paatdaq5gusi2hnybp2yf5wxwdnrgxaqq.b32.i2p:0 +iw6tgpmbdykffceku5da6nzf2bmz66fvp5fpcvemfu3df6aq6pga.b32.i2p:0 +jkfuajo4ayvo2rbv5qdj443q6adqmnormbhsf2f7rlp5t24xomda.b32.i2p:0 jz3s4eurm5vzjresf4mwo7oni4bk36daolwxh4iqtewakylgkxmq.b32.i2p:0 -kokkmpquqlkptu5hkmzqlttsmtwxicldr4so7wqsufk6bwf32nma.b32.i2p:0 +liu75cvktv4icbctg72w7nxbk4eibt7wamizfdii4omz7gcke5vq.b32.i2p:0 +ljsquuu3y4xje6l32p32inn6r2y6ull6oocgup6jtjrohrqxbz6a.b32.i2p:0 +lrah7acdsgopybg43shadwwiv6igezaw64i6jb5muqdg7dmhj3la.b32.i2p:0 +lzuu6mjtu7vd55d2biphicihufipoa7vyym6xfnkmmlra3tiziia.b32.i2p:0 +m6bpynxkv2ktwxkg6p2gyudjfhdupb6kuzabeqdnckkdkf4kxjla.b32.i2p:0 +m6v454xd6p3bt5swujgmveklsp7lzbkqlqqfc2p36cjlwv5dbucq.b32.i2p:0 +mlgeizrroynuhpxbzeosajt5u4ddcvynxfmcbm6kwjpaufilxigq.b32.i2p:0 +ofubxr2ir7u2guzjwyrvujicivzmvinwa36nuzlrg7tnsmebal7a.b32.i2p:0 +okfxeoh6itu4f5f43dhbzvkqwfrvm5c66lj6lvjj4q2b35i4pk4q.b32.i2p:0 +oz2ia3flpm3du2tyusulrn7h7e2eo3juzkrmn34bvnrlcrugv7ia.b32.i2p:0 +qd6jlsevsexww3wefpqs7iglxb3f63y4e6ydulfzrvwflpicmdqa.b32.i2p:0 +qddg7myylinn4tw6kdjmmp6fsyetkosnrbp2gsjx77tmkqyqv6ua.b32.i2p:0 +rizfinyses2r3or4iubs5wx66gdy6mpf73w7uobfacm2l5cral3q.b32.i2p:0 +s5hhjtmlg53bko3nwwskas7xgsmeqzy6thtsj5aa64djyrljgqaq.b32.i2p:0 sedndhv5vpcgdmykyi5st4yqhdxl3hpdtglta4do435wupahhx6q.b32.i2p:0 +tsl4dlpu2id252b6crbdnblruct664se6f2iw35fuqwa3te7wcoq.b32.i2p:0 +tugq6wa2ls2bv27pr2iy3da3k5ow3fzefbcvjcr22uc7w5vmevja.b32.i2p:0 +usztavbib756k5vqggzgkyswoj6mttihjvp3c2pa642t2mb4pvsa.b32.i2p:0 +vgu6llqbyjphml25umd5ztvyxrxuplz2g74fzbx75g3kkaetoyiq.b32.i2p:0 +wjrul5jwwb4vqdmkkrjbmly7osj6amecdpsac5xvaoqrti4nb3ha.b32.i2p:0 +wvktcp7hy4l6immhi5cxyz2dlsbhhvtcmskjemrnqehacnoap23q.b32.i2p:0 wwbw7nqr3ahkqv62cuqfwgtneekvvpnuc4i4f6yo7tpoqjswvcwa.b32.i2p:0 +xlqndzjoe5nr2nsxo6xwibh44ghyz4jfqevu62xykvemextpmjbq.b32.i2p:0 +yc4xwin5ujenvcr6ynwkz7lnmmq3nmzxvfguele6ovqqpxgjvonq.b32.i2p:0 +zdoabsg7ugzothyawodjhq54nvlofa746rxfkxpnjzj6nukmha6a.b32.i2p:0 zsxwyo6qcn3chqzwxnseusqgsnuw3maqnztkiypyfxtya4snkoka.b32.i2p:0 +zysrlpii5ftrzivfcyhdrwpeyyqddbrdefnfu5q6otk5gtugmh2a.b32.i2p:0 # manually added 2022-01 for minimal cjdns bootstrap support [fc32:17ea:e415:c3bf:9808:149d:b5a2:c9aa]:8333 diff --git a/contrib/seeds/nodes_main_manual.txt b/contrib/seeds/nodes_main_manual.txt index a6e0b8763a..286448d95d 100644 --- a/contrib/seeds/nodes_main_manual.txt +++ b/contrib/seeds/nodes_main_manual.txt @@ -1,42 +1,77 @@ -# manually added 2021-03 for minimal torv3 bootstrap support -2g5qfdkn2vvcbqhzcyvyiitg4ceukybxklraxjnu7atlhd22gdwywaid.onion:8333 -2jmtxvyup3ijr7u6uvu7ijtnojx4g5wodvaedivbv74w4vzntxbrhvad.onion:8333 -37m62wn7dz3uqpathpc4qfmgrbupachj52nt3jbtbjugpbu54kbud7yd.onion:8333 +# manually updated 2022-08 for minimal torv3 bootstrap support 5g72ppm3krkorsfopcm2bi7wlv4ohhs4u4mlseymasn7g7zhdcyjpfid.onion:8333 -7cgwjuwi5ehvcay4tazy7ya6463bndjk6xzrttw5t3xbpq4p22q6fyid.onion:8333 -7pyrpvqdhmayxggpcyqn5l3m5vqkw3qubnmgwlpya2mdo6x7pih7r7id.onion:8333 b64xcbleqmwgq2u46bh4hegnlrzzvxntyzbmucn3zt7cssm7y4ubv3id.onion:8333 -ejxefzf5fpst4mg2rib7grksvscl7p6fvjp6agzgfc2yglxnjtxc3aid.onion:8333 fjdyxicpm4o42xmedlwl3uvk5gmqdfs5j37wir52327vncjzvtpfv7yd.onion:8333 fpz6r5ppsakkwypjcglz6gcnwt7ytfhxskkfhzu62tnylcknh3eq6pad.onion:8333 -fzhn4uoxfbfss7h7d6ffbn266ca432ekbbzvqtsdd55ylgxn4jucm5qd.onion:8333 gxo5anvfnffnftfy5frkgvplq3rpga2ie3tcblo2vl754fvnhgorn5yd.onion:8333 ifdu5qvbofrt4ekui2iyb3kbcyzcsglazhx2hn4wfskkrx2v24qxriid.onion:8333 itz3oxsihs62muvknc237xabl5f6w6rfznfhbpayrslv2j2ubels47yd.onion:8333 -lrjh6fywjqttmlifuemq3puhvmshxzzyhoqx7uoufali57eypuenzzid.onion:8333 +kpgvmscirrdqpekbqjsvw5teanhatztpp2gl6eee4zkowvwfxwenqaid.onion:8333 m7cbpjolo662uel7rpaid46as2otcj44vvwg3gccodnvaeuwbm3anbyd.onion:8333 -opnyfyeiibe5qo5a3wbxzbb4xdiagc32bbce46owmertdknta5mi7uyd.onion:8333 -owjsdxmzla6d7lrwkbmetywqym5cyswpihciesfl5qdv2vrmwsgy4uqd.onion:8333 -q7kgmd7n7h27ds4fg7wocgniuqb3oe2zxp4nfe4skd5da6wyipibqzqd.onion:8333 +mwmfluek4au6mxxpw6fy7sjhkm65bdfc7izc7lpz3trewfdghyrzsbid.onion:8333 rp7k2go3s5lyj3fnj6zn62ktarlrsft2ohlsxkyd7v3e3idqyptvread.onion:8333 -sys54sv4xv3hn3sdiv3oadmzqpgyhd4u4xphv4xqk64ckvaxzm57a7yd.onion:8333 -tddeij4qigtjr6jfnrmq6btnirmq5msgwcsdpcdjr7atftm7cxlqztid.onion:8333 -vi5bnbxkleeqi6hfccjochnn65lcxlfqs4uwgmhudph554zibiusqnad.onion:8333 -xqt25cobm5zqucac3634zfght72he6u3eagfyej5ellbhcdgos7t2had.onion:8333 -# manually added 2021-08 for minimal i2p bootstrap support +# manually updated 2022-08 for minimal i2p bootstrap support +255fhcp6ajvftnyo7bwz3an3t4a4brhopm3bamyh2iu5r3gnr2rq.b32.i2p:0 +27yrtht5b5bzom2w5ajb27najuqvuydtzb7bavlak25wkufec5mq.b32.i2p:0 +2el6enckmfyiwbfcwsygkwksovtynzsigmyv3bzyk7j7qqahooua.b32.i2p:0 +3gocb7wc4zvbmmebktet7gujccuux4ifk3kqilnxnj5wpdpqx2hq.b32.i2p:0 +3tns2oov4tnllntotazy6umzkq4fhkco3iu5rnkxtu3pbfzxda7q.b32.i2p:0 +4fcc23wt3hyjk3csfzcdyjz5pcwg5dzhdqgma6bch2qyiakcbboa.b32.i2p:0 +4osyqeknhx5qf3a73jeimexwclmt42cju6xdp7icja4ixxguu2hq.b32.i2p:0 +4umsi4nlmgyp4rckosg4vegd2ysljvid47zu7pqsollkaszcbpqq.b32.i2p:0 +52v6uo6crlrlhzphslyiqblirux6olgsaa45ixih7sq5np4jujaa.b32.i2p:0 +6j2ezegd3e2e2x3o3pox335f5vxfthrrigkdrbgfbdjchm5h4awa.b32.i2p:0 +6n36ljyr55szci5ygidmxqer64qr24f4qmnymnbvgehz7qinxnla.b32.i2p:0 +72yjs6mvlby3ky6mgpvvlemmwq5pfcznrzd34jkhclgrishqdxva.b32.i2p:0 +7r4ri53lby2i3xqbgpw3idvhzeku7ubhftlf72ldqkg5kde6dauq.b32.i2p:0 a5qsnv3maw77mlmmzlcglu6twje6ttctd3fhpbfwcbpmewx6fczq.b32.i2p:0 -bitcornrd36coazsbzsz4pdebyzvaplmsalq4kpoljmn6cg6x5zq.b32.i2p:0 +aovep2pco7v2k4rheofrgytbgk23eg22dczpsjqgqtxcqqvmxk6a.b32.i2p:0 +bddbsmkas3z6fakorbkfjhv77i4hv6rysyjsvrdjukxolfghc23q.b32.i2p:0 +bitcoi656nll5hu6u7ddzrmzysdtwtnzcnrjd4rfdqbeey7dmn5a.b32.i2p:0 +brifkruhlkgrj65hffybrjrjqcgdgqs2r7siizb5b2232nruik3a.b32.i2p:0 c4gfnttsuwqomiygupdqqqyy5y5emnk5c73hrfvatri67prd7vyq.b32.i2p:0 -dhtq2p76tyhi442aidb3vd2bv7yxxjuddpb2jydnnrl2ons5bhha.b32.i2p:0 +day3hgxyrtwjslt54sikevbhxxs4qzo7d6vi72ipmscqtq3qmijq.b32.i2p:0 +di2zq6fr3fegf2jdcd7hdwyql4umr462gonsns2nxz5qg5vz4bka.b32.i2p:0 +e55k6wu46rzp4pg5pk5npgbr3zz45bc3ihtzu2xcye5vwnzdy7pq.b32.i2p:0 +eciohu5nq7vsvwjjc52epskuk75d24iccgzmhbzrwonw6lx4gdva.b32.i2p:0 +ejlnngarmhqvune74ko7kk55xtgbz5i5ncs4vmnvjpy3l7y63xaa.b32.i2p:0 +g47cqoppu26pr4n2cfaioqx7lbdi7mea7yqhlrkdz3wjwxjxdh2a.b32.i2p:0 h3r6bkn46qxftwja53pxiykntegfyfjqtnzbm6iv6r5mungmqgmq.b32.i2p:0 -hnbbyjpxx54623l555sta7pocy3se4sdgmuebi5k6reesz5rjp6q.b32.i2p:0 +hhfi4yqkg2twqiwezrfksftjjofbyx3ojkmlnfmcwntgnrjjhkya.b32.i2p:0 +hpiibrflqkbrcshfhmrtwfyeb7mds7a3obzwrgarejevddzamvsq.b32.i2p:0 +i4pyhsfdq4247dunel7paatdaq5gusi2hnybp2yf5wxwdnrgxaqq.b32.i2p:0 +iw6tgpmbdykffceku5da6nzf2bmz66fvp5fpcvemfu3df6aq6pga.b32.i2p:0 +jkfuajo4ayvo2rbv5qdj443q6adqmnormbhsf2f7rlp5t24xomda.b32.i2p:0 jz3s4eurm5vzjresf4mwo7oni4bk36daolwxh4iqtewakylgkxmq.b32.i2p:0 -kokkmpquqlkptu5hkmzqlttsmtwxicldr4so7wqsufk6bwf32nma.b32.i2p:0 +liu75cvktv4icbctg72w7nxbk4eibt7wamizfdii4omz7gcke5vq.b32.i2p:0 +ljsquuu3y4xje6l32p32inn6r2y6ull6oocgup6jtjrohrqxbz6a.b32.i2p:0 +lrah7acdsgopybg43shadwwiv6igezaw64i6jb5muqdg7dmhj3la.b32.i2p:0 +lzuu6mjtu7vd55d2biphicihufipoa7vyym6xfnkmmlra3tiziia.b32.i2p:0 +m6bpynxkv2ktwxkg6p2gyudjfhdupb6kuzabeqdnckkdkf4kxjla.b32.i2p:0 +m6v454xd6p3bt5swujgmveklsp7lzbkqlqqfc2p36cjlwv5dbucq.b32.i2p:0 +mlgeizrroynuhpxbzeosajt5u4ddcvynxfmcbm6kwjpaufilxigq.b32.i2p:0 +ofubxr2ir7u2guzjwyrvujicivzmvinwa36nuzlrg7tnsmebal7a.b32.i2p:0 +okfxeoh6itu4f5f43dhbzvkqwfrvm5c66lj6lvjj4q2b35i4pk4q.b32.i2p:0 +oz2ia3flpm3du2tyusulrn7h7e2eo3juzkrmn34bvnrlcrugv7ia.b32.i2p:0 +qd6jlsevsexww3wefpqs7iglxb3f63y4e6ydulfzrvwflpicmdqa.b32.i2p:0 +qddg7myylinn4tw6kdjmmp6fsyetkosnrbp2gsjx77tmkqyqv6ua.b32.i2p:0 +rizfinyses2r3or4iubs5wx66gdy6mpf73w7uobfacm2l5cral3q.b32.i2p:0 +s5hhjtmlg53bko3nwwskas7xgsmeqzy6thtsj5aa64djyrljgqaq.b32.i2p:0 sedndhv5vpcgdmykyi5st4yqhdxl3hpdtglta4do435wupahhx6q.b32.i2p:0 +tsl4dlpu2id252b6crbdnblruct664se6f2iw35fuqwa3te7wcoq.b32.i2p:0 +tugq6wa2ls2bv27pr2iy3da3k5ow3fzefbcvjcr22uc7w5vmevja.b32.i2p:0 +usztavbib756k5vqggzgkyswoj6mttihjvp3c2pa642t2mb4pvsa.b32.i2p:0 +vgu6llqbyjphml25umd5ztvyxrxuplz2g74fzbx75g3kkaetoyiq.b32.i2p:0 +wjrul5jwwb4vqdmkkrjbmly7osj6amecdpsac5xvaoqrti4nb3ha.b32.i2p:0 +wvktcp7hy4l6immhi5cxyz2dlsbhhvtcmskjemrnqehacnoap23q.b32.i2p:0 wwbw7nqr3ahkqv62cuqfwgtneekvvpnuc4i4f6yo7tpoqjswvcwa.b32.i2p:0 +xlqndzjoe5nr2nsxo6xwibh44ghyz4jfqevu62xykvemextpmjbq.b32.i2p:0 +yc4xwin5ujenvcr6ynwkz7lnmmq3nmzxvfguele6ovqqpxgjvonq.b32.i2p:0 +zdoabsg7ugzothyawodjhq54nvlofa746rxfkxpnjzj6nukmha6a.b32.i2p:0 zsxwyo6qcn3chqzwxnseusqgsnuw3maqnztkiypyfxtya4snkoka.b32.i2p:0 +zysrlpii5ftrzivfcyhdrwpeyyqddbrdefnfu5q6otk5gtugmh2a.b32.i2p:0 # manually added 2022-01 for minimal cjdns bootstrap support [fc32:17ea:e415:c3bf:9808:149d:b5a2:c9aa]:8333 diff --git a/contrib/seeds/nodes_test.txt b/contrib/seeds/nodes_test.txt index 118bec280e..5b04791d60 100644 --- a/contrib/seeds/nodes_test.txt +++ b/contrib/seeds/nodes_test.txt @@ -1,16 +1,89 @@ # List of fixed seed nodes for testnet -# Onion nodes -35k2va6vyw4oo5ly2quvcszgdqr56kcnfgcqpnpcffut4jn3mhhwgbid.onion:18333 -blo2esfvk2rr7sr4jspmu3vt2vpgr5rigflsj645fnku7v4qmljurtid.onion:18333 -fuckcswupr5rmlvx2kqqrrosxvjyong4hatmuvxsvtcwe4dsh5rus7qd.onion:18333 -gblylyacjlitd2ywdmo2qqylwtdky7kgeqfvlhiw4zdag4x62tx54hyd.onion:18333 -gzwpduv33l7yze3bcdzj3inebiyjwddjnwvnjhh5wvnv4me76mjt2kad.onion:18333 -h3rphzofxzq52tb63mg5f6kc4my3fkcrgh3m5qryeatts43iljbawiid.onion:18333 -kf4qlhek34b3kgyxyodlmvgm4bxfrjsbjtgayyaiuyhr2eoyfgtm3bad.onion:18333 +# Onion nodes, last verified 2022-08 for minimal torv3 bootstrap support +24j74ahq6ed4wmfrghdwroyfzimlkhnrb7zh4zw3vl2allzxbjrhaqid.onion:18333 +2fy74te65gm3c3gv3u5mhwdudvbdfh6k5fdz4gduimrltjjrxftbxrqd.onion:18333 +2lsncqdflwk272dhydrxf7ikfy23ppnmm54dnynyxiym6lqf3wowrmqd.onion:18333 +33o6qaidta7s2pmltet6vynd337vamgcifhh44rehwwxqpflcjt2njid.onion:18333 +3oo6bsc5mvf6a6ypmoaikilta6ka7mbdhdwhrnqhuhjlbaxyedvfvaqd.onion:18333 +3pe3fyklipy4sppkkgnhc22kcxtt57uler5kv72t676bbrwmcseo5qad.onion:18333 +4u4mcz2sfvxs7pwcwncswgmmcdzqtzjx7ztfo332jv4pqucb22ikdhad.onion:18333 +5v3i2kfqiqwp75gznjoptss7qgrcgseceqxpzpqkd34qeqzrg726i7id.onion:18333 +5zlrxk6q24t4vz5k4ie7gtuasdjavhoelhinzimxbfhc77u7vafipsid.onion:18333 +67s3af64ehw7xnxv422axm7tns4d6kutrftc6bjq375n74q3kj4pp7ad.onion:18333 +6a4ony53julvnufo632ktgmwvhupz63wbdwx7n7qudjy32qyq6gm3bqd.onion:18333 +6ftyg3nhc6tn2hyzls6zfdsfbroczhkxtdqumqb5q4yafhy5rdpapbid.onion:18333 +7554uw5djruh34j5ddx3iprzgqgzypcjtptwoldymfbgoywqcw2wiwyd.onion:18333 +766lozlabxaqjpbqsvt6sn3c65n6gkwwhoxyvggj7nfwnmw4cpaoccad.onion:18333 +7blv5abnytdf47yvbhxmykprmvjryqob65i2jmdwq3rrajcn2iiysbqd.onion:18333 +7v2ja4igx4v5y2jr6jrr6gaxohjhlzhvgwe4avlraxchozf7ea3kruqd.onion:18333 +7zgbmtzxow2oevd5aaqtsormw7ujv4zprl3oi2355immhq4gk7cyw5ad.onion:18333 +adstabjz7ec2y3jt4w2dvummowzv7g6m2f3kajeejffuaz7ojwj6epqd.onion:18333 +aesy6tfufadkut6flu2bsqgnw2422ur2ynjalguxlzuzuktg3zehttqd.onion:18333 +alxo32b5edi3bn2e224qrgytgxxpic4knyipvpdvctfsrvcaiq5lgeyd.onion:18333 +aoeart34umoonvd2kbqr3bc4sweu6a4msh2gp4skyqvei3shzcxbgmyd.onion:18333 +aprzvj7hgctsde4mkj3ewq35gvykspjvkqiygg7bpnw5tkvse2n7rhid.onion:18333 +awpk6z3xghx6ozouhodcydaqtr6uzzbnw4creuix7mkupxoxlmhhspad.onion:18333 +ayynqazucyh2jd5rehcfggmhunqpdwzlbhzbqgy6lj4ctz2ocj7chpid.onion:18333 +b2ika53aqckv4gs7wmog3byrea2vfzm5p7ye33digcsmvvnpbyqmzoyd.onion:18333 +be7zx3hh6dlahorlvsrrgqm4oahfrgqm2tbwnbd4u53ntu5f765n6hyd.onion:18333 +bluk62wj24bsvdwh47muo54hhwsatkftiqxevt5kba7hstjoex6ueeyd.onion:18333 +bubm6fiopfzkxqrfx6vqpioe5ahlhyubz57ogsqqy4ha5pnngiqlh6id.onion:18333 +d3czabzjj57lgrsr5gawkjd7v3gznrqa7zyizqmk4lryascavmipnyad.onion:18333 +ddj4cuvb32ve5chtp6jattcdnnmxmpoofjthzi7thgxxht7yqoetj3yd.onion:18333 +dqhhlssfwmh3g6zhwxpcfbw64xz5rfikcglinbhoxv5ajv4qzicjyeid.onion:18333 +drthcyb4x4rdfekw5g7xjogxi7aqoluilgulbgwvsme3nw3oibvchbad.onion:18333 +dwb47cmqa2tjpmvjaear7gdcars2lez6niefhi4qf22qehtyta6577qd.onion:18333 +e7tkrf54ng3q5vcn5gn77zwjwm74lkfav4mwdux3pvon6yvqg3tf46qd.onion:18333 +etuymy47s3quepvdaoo72i5e5mc7uovrzu5m4jf5q6mwlwizoxy4xgid.onion:18333 +fbimesnyhzubbzqc3uaufzkbyfmnkxvypoxaveaub7rzpzh2foxrn2yd.onion:18333 +fzbrwmgwmko7quelrhfuskt3ijabac76zx7g52dfrevmhdkj6ivh7qyd.onion:18333 +gy6nih4pmp5esyvvnhlj6qvk7zkbjuoswkxffyiip3dbkvsfxwz5zcqd.onion:18333 +ha62ziqzqdogd75zg7lfh4fqrg3bim3cpqzyupo43w5pw4fen6nr2pyd.onion:18333 +hacjjgj2mbqqrthzimmi6anvin7dljjhfl3ik6ebg3w3nmgsvr3ymmqd.onion:18333 +hbkp5xwpqo4qm75kpglfrclyiuuvdgv7mtiqfys7oqks4dmpqgpeoeid.onion:18333 +hqgoy62hoqjmz37brdfvoeov3cix5fixbqjoert4ydr6herg5oc3iwyd.onion:18333 +hvbmmzvqrpgps2x5u4ip4ksf3e5m2fneac754gtnhjn2rsevni6cz3ad.onion:18333 +hw3vzp32w4h6giplue6ix445oi6wt7gmeksrznb7tdfwhkgit7gnbbad.onion:18333 +iddr66ewkhenivapgianudjkwqcp6dxtssg7ixrdot5az6uh7m5tmjqd.onion:18333 +imya36iexiiiqrkwuxxcehnv4kg5shtirwd2vg4cnjy6lfjlph3fusqd.onion:18333 +iuhhuocns7entrzlxsxktyz2ibs7hqgiggv6sauzqkzka6laslwz7oqd.onion:18333 +ji5wmshokuc63eiulzlwj2zdvnligvrwfvvc76bice3tu43wfzvpmkyd.onion:18333 +jjfuyj7krgzkmpxvn3b2j2hwlzkmze3ezy3ifwk7dnswwawgmzqhjrqd.onion:18333 +jn2p4sgfphkxpow7kjrubrbqat77kkibzqkvuwhxyalcrazwmcqeaqyd.onion:18333 +jrveyz4us6sog6e6czsvr5mvvhgzjgv4idbe4idrolmqeudvt5a2dgid.onion:18333 +jsc4frvvnl2d3bhzyofsc72xpztgm23nl4fnb4dwkzsxr6fhij2q5iyd.onion:18333 +klymxdvje7kccv3tznabo3udopsftkmjemkbi2urqxjm4hefaudejjyd.onion:18333 +kwjxlauwjtecjfsiwopbl5pvn5n6z5rz76uk6osmlurd3uyuymcw7aid.onion:18333 +lc7upz2srw2yhpcvwg4afy64ylcoo6mfwlttqj5ovuglqnhnohpi5iqd.onion:18333 +lf3mpxfyjuovcqdvinl52pvdmmda6xqyfeiarlfamdjpgy3ouzmmlbyd.onion:18333 mc7k47ndjvvhcgs54wmjzxvate4rtuybbjoryikdssjhcxlx27psbyqd.onion:18333 -mrhiniicugfo7mgrwv3wtolk3tptlcw2uq7ih6sq43fa4k4zbilut3yd.onion:18333 -uiudyws3qizgmepfoh7wwjmsoxoxut4qrmotjjhrn247xnjopr7sfcid.onion:18333 -zc2wvoqcezcrf64trji6jmhtss34a5ds5ntzdhqegzvex3ynrd7nxcad.onion:18333 -zd5m3dgdn46naj36pxvvcalfw2paecle6sdxq64ptwxtxjomkywpklqd.onion:18333 - +mjbg3ggeuelmc7ixty3zjccyo2urg2uyherfqe7ytkm2ejkwlec7h6ad.onion:18333 +nkyqozv6kdwi423s7s2mezzguf5bafot2a3hv4ed2dbvtblisdmad4qd.onion:18333 +nvvqo4xxiwgb3y246jmcbuuveurfdq2zs3a5y7veqkeqv5jfhang7gyd.onion:18333 +o6vfovqxz3oxszfppczpjejwouobztjrgvfojc3emvhan3bkyskzhuad.onion:18333 +oaiw2lnhzgp5ry7ivzneuufmh7lfploquu2rjv5rozmlbefedsnxe5qd.onion:18333 +oln7ybci53wk4g5n42nipyixvyjxbludsbrfsmhnirb6tk7ovlikd5id.onion:18333 +otmfnhc6wrrbf2tpdy6zkisqc3r3urnsuowsnmatoto6yixaocnkseid.onion:18333 +ovc6sajbqfcbwv3wrq7ylklu6q6prvisz4jr4lyycn4kgukzjfe4mjad.onion:18333 +pm57didyzg5ljuvn5ufr5uun2iencuk3af2gzqc5zvgfh452c3rxtjyd.onion:18333 +pmismhpwug34gnqzbutranvx2wjwbshyqj4un2dyzyuvak2eh55psfyd.onion:18333 +polarisultijjhaku6z6u7jyboho5epdsg44ttebfaxmgau2z5sqolad.onion:18333 +qe2jbe447he6panfvpyqhyntf7346gmuf55bxrmdzggmgwyjsyknhxyd.onion:18333 +qz6yd5lsgdajcteoareeptwnipxsezyx5kks6ukpk5tvqinffzunqmyd.onion:18333 +rp6pn3b3oesyr2giolbysbjhqeugxntsu7crnkth4y33ok4zvcl7yrqd.onion:18333 +ujdchuw3hz5gkbouiv4p6pwbfdn7v4k6gluwvd4wiukqc7y7ow754uad.onion:18333 +vctlwaqgmu53eutz2hewuakcipfgtyljsd7czut4dd62xr3rp6fqezad.onion:18333 +vf5ur53tzmdtotvkndcgochklnuav7quqjvkc6mctqfvef6wnmn26mid.onion:18333 +wnxgjgjgplv5iu4mssyuunycvku4qnqr5t4q6cfdt47k7uwrfifuirad.onion:18333 +wpkbkdr7clw7zk3jkwiult6bf422j54u77ml4rgig2xq7icogyrcspid.onion:18333 +wzpdt24tdark26eugredddorik3tqwcj5ialtt2yim4ceiuiq7phkyqd.onion:18333 +xgapnikkbldoggjh5ewxkyauhuwnvf3xkspxroe3ojvfrk4lswkyx5yd.onion:18333 +xkvzdhcirontixbq6pjhru57bf4sgtqylvphk25csfrsy5p5ay3oc3yd.onion:18333 +xnipauenw5wnjb2zbx6v6umgvbb3g6xhf5kjo7pnyn5tdzvzaxtzicid.onion:18333 +yda7kwpii33j2qpq32ftf6lp22znknswipjwaccvsqj7l337jvfesnid.onion:18333 +z3j5foswuhpmtrg3kb56stkzmuoaesvd5jz3eztq46c4cidapglcyuad.onion:18333 +zcep44k7unwjm2wxty4ijh2e4fv5zgbrvwlctzyaqnrqhltjfzrtodad.onion:18333 +zmvizz7fd5hdue6wt3lwqumd6qwt4ijymmmotfzh75curq3mzjm53hyd.onion:18333 +zoaa3x7quyuijggii5zl4uyeioodudsgtr2uyv2qtdsslac5ukiwlxid.onion:18333 +zovauxlorl5eswumbsoxv2m5y3sm3qlk7657dcpr2uld7xf35en46sqd.onion:18333 diff --git a/contrib/signet/getcoins.py b/contrib/signet/getcoins.py index 147d12600d..a069f5fad3 100755 --- a/contrib/signet/getcoins.py +++ b/contrib/signet/getcoins.py @@ -129,7 +129,7 @@ if args.captcha != '': # Retrieve a captcha # Convert SVG image to PPM, and load it try: - rv = subprocess.run([args.imagemagick, 'svg:-', '-depth', '8', 'ppm:-'], input=res.content, check=True, capture_output=True) + rv = subprocess.run([args.imagemagick, 'svg:-', '-depth', '8', 'ppm:-'], input=res.content, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) except FileNotFoundError: raise SystemExit(f"The binary {args.imagemagick} could not be found. Please make sure ImageMagick (or a compatible fork) is installed and that the correct path is specified.") diff --git a/contrib/signet/miner b/contrib/signet/miner index b366b98e2d..61d9f62be7 100755 --- a/contrib/signet/miner +++ b/contrib/signet/miner @@ -4,7 +4,6 @@ # file COPYING or http://www.opensource.org/licenses/mit-license.php. import argparse -import base64 import json import logging import math @@ -15,14 +14,13 @@ import sys import time import subprocess -from io import BytesIO - PATH_BASE_CONTRIB_SIGNET = os.path.abspath(os.path.dirname(os.path.realpath(__file__))) PATH_BASE_TEST_FUNCTIONAL = os.path.abspath(os.path.join(PATH_BASE_CONTRIB_SIGNET, "..", "..", "test", "functional")) sys.path.insert(0, PATH_BASE_TEST_FUNCTIONAL) -from test_framework.blocktools import WITNESS_COMMITMENT_HEADER, script_BIP34_coinbase_height # noqa: E402 -from test_framework.messages import CBlock, CBlockHeader, COutPoint, CTransaction, CTxIn, CTxInWitness, CTxOut, from_hex, deser_string, hash256, ser_compact_size, ser_string, ser_uint256, tx_from_hex, uint256_from_str # noqa: E402 +from test_framework.blocktools import get_witness_script, script_BIP34_coinbase_height # noqa: E402 +from test_framework.messages import CBlock, CBlockHeader, COutPoint, CTransaction, CTxIn, CTxInWitness, CTxOut, from_binary, from_hex, ser_string, ser_uint256, tx_from_hex # noqa: E402 +from test_framework.psbt import PSBT, PSBTMap, PSBT_GLOBAL_UNSIGNED_TX, PSBT_IN_FINAL_SCRIPTSIG, PSBT_IN_FINAL_SCRIPTWITNESS, PSBT_IN_NON_WITNESS_UTXO, PSBT_IN_SIGHASH_TYPE # noqa: E402 from test_framework.script import CScriptOp # noqa: E402 logging.basicConfig( @@ -34,99 +32,12 @@ SIGNET_HEADER = b"\xec\xc7\xda\xa2" PSBT_SIGNET_BLOCK = b"\xfc\x06signetb" # proprietary PSBT global field holding the block being signed RE_MULTIMINER = re.compile("^(\d+)(-(\d+))?/(\d+)$") -# #### some helpers that could go into test_framework - -# like from_hex, but without the hex part -def FromBinary(cls, stream): - """deserialize a binary stream (or bytes object) into an object""" - # handle bytes object by turning it into a stream - was_bytes = isinstance(stream, bytes) - if was_bytes: - stream = BytesIO(stream) - obj = cls() - obj.deserialize(stream) - if was_bytes: - assert len(stream.read()) == 0 - return obj - -class PSBTMap: - """Class for serializing and deserializing PSBT maps""" - - def __init__(self, map=None): - self.map = map if map is not None else {} - - def deserialize(self, f): - m = {} - while True: - k = deser_string(f) - if len(k) == 0: - break - v = deser_string(f) - if len(k) == 1: - k = k[0] - assert k not in m - m[k] = v - self.map = m - - def serialize(self): - m = b"" - for k,v in self.map.items(): - if isinstance(k, int) and 0 <= k and k <= 255: - k = bytes([k]) - m += ser_compact_size(len(k)) + k - m += ser_compact_size(len(v)) + v - m += b"\x00" - return m - -class PSBT: - """Class for serializing and deserializing PSBTs""" - - def __init__(self): - self.g = PSBTMap() - self.i = [] - self.o = [] - self.tx = None - - def deserialize(self, f): - assert f.read(5) == b"psbt\xff" - self.g = FromBinary(PSBTMap, f) - assert 0 in self.g.map - self.tx = FromBinary(CTransaction, self.g.map[0]) - self.i = [FromBinary(PSBTMap, f) for _ in self.tx.vin] - self.o = [FromBinary(PSBTMap, f) for _ in self.tx.vout] - return self - - def serialize(self): - assert isinstance(self.g, PSBTMap) - assert isinstance(self.i, list) and all(isinstance(x, PSBTMap) for x in self.i) - assert isinstance(self.o, list) and all(isinstance(x, PSBTMap) for x in self.o) - assert 0 in self.g.map - tx = FromBinary(CTransaction, self.g.map[0]) - assert len(tx.vin) == len(self.i) - assert len(tx.vout) == len(self.o) - - psbt = [x.serialize() for x in [self.g] + self.i + self.o] - return b"psbt\xff" + b"".join(psbt) - - def to_base64(self): - return base64.b64encode(self.serialize()).decode("utf8") - - @classmethod - def from_base64(cls, b64psbt): - return FromBinary(cls, base64.b64decode(b64psbt)) - -# ##### - def create_coinbase(height, value, spk): cb = CTransaction() cb.vin = [CTxIn(COutPoint(0, 0xffffffff), script_BIP34_coinbase_height(height), 0xffffffff)] cb.vout = [CTxOut(value, spk)] return cb -def get_witness_script(witness_root, witness_nonce): - commitment = uint256_from_str(hash256(ser_uint256(witness_root) + ser_uint256(witness_nonce))) - return b"\x6a" + CScriptOp.encode_op_pushdata(WITNESS_COMMITMENT_HEADER + ser_uint256(commitment)) - def signet_txs(block, challenge): # assumes signet solution has not been added yet so does not need # to be removed @@ -163,11 +74,11 @@ def signet_txs(block, challenge): def do_createpsbt(block, signme, spendme): psbt = PSBT() - psbt.g = PSBTMap( {0: signme.serialize(), + psbt.g = PSBTMap( {PSBT_GLOBAL_UNSIGNED_TX: signme.serialize(), PSBT_SIGNET_BLOCK: block.serialize() } ) - psbt.i = [ PSBTMap( {0: spendme.serialize(), - 3: bytes([1,0,0,0])}) + psbt.i = [ PSBTMap( {PSBT_IN_NON_WITNESS_UTXO: spendme.serialize(), + PSBT_IN_SIGHASH_TYPE: bytes([1,0,0,0])}) ] psbt.o = [ PSBTMap() ] return psbt.to_base64() @@ -179,10 +90,10 @@ def do_decode_psbt(b64psbt): assert len(psbt.tx.vout) == 1 assert PSBT_SIGNET_BLOCK in psbt.g.map - scriptSig = psbt.i[0].map.get(7, b"") - scriptWitness = psbt.i[0].map.get(8, b"\x00") + scriptSig = psbt.i[0].map.get(PSBT_IN_FINAL_SCRIPTSIG, b"") + scriptWitness = psbt.i[0].map.get(PSBT_IN_FINAL_SCRIPTWITNESS, b"\x00") - return FromBinary(CBlock, psbt.g.map[PSBT_SIGNET_BLOCK]), ser_string(scriptSig) + scriptWitness + return from_binary(CBlock, psbt.g.map[PSBT_SIGNET_BLOCK]), ser_string(scriptSig) + scriptWitness def finish_block(block, signet_solution, grind_cmd): block.vtx[0].vout[-1].scriptPubKey += CScriptOp.encode_op_pushdata(SIGNET_HEADER + signet_solution) @@ -222,7 +133,7 @@ def generate_psbt(tmpl, reward_spk, *, blocktime=None): cbwit = CTxInWitness() cbwit.scriptWitness.stack = [ser_uint256(witnonce)] block.vtx[0].wit.vtxinwit = [cbwit] - block.vtx[0].vout.append(CTxOut(0, get_witness_script(witroot, witnonce))) + block.vtx[0].vout.append(CTxOut(0, bytes(get_witness_script(witroot, witnonce)))) signme, spendme = signet_txs(block, signet_spk_bin) @@ -314,7 +225,7 @@ def seconds_to_hms(s): out = "-" + out return out -def next_block_delta(last_nbits, last_hash, ultimate_target, do_poisson): +def next_block_delta(last_nbits, last_hash, ultimate_target, do_poisson, max_interval): # strategy: # 1) work out how far off our desired target we are # 2) cap it to a factor of 4 since that's the best we can do in a single retarget period @@ -337,7 +248,7 @@ def next_block_delta(last_nbits, last_hash, ultimate_target, do_poisson): this_interval_variance = 1 this_interval = avg_interval * this_interval_variance - this_interval = max(1, min(this_interval, 3600)) + this_interval = max(1, min(this_interval, max_interval)) return this_interval @@ -397,6 +308,10 @@ def do_generate(args): return 1 my_blocks = (start-1, stop, total) + if args.max_interval < 960: + logging.error("--max-interval must be at least 960 (16 minutes)") + return 1 + ultimate_target = nbits_to_target(int(args.nbits,16)) mined_blocks = 0 @@ -413,7 +328,7 @@ def do_generate(args): if lastheader is None: lastheader = bestheader["hash"] elif bestheader["hash"] != lastheader: - next_delta = next_block_delta(int(bestheader["bits"], 16), bestheader["hash"], ultimate_target, args.poisson) + next_delta = next_block_delta(int(bestheader["bits"], 16), bestheader["hash"], ultimate_target, args.poisson, args.max_interval) next_delta += bestheader["time"] - time.time() next_is_mine = next_block_is_mine(bestheader["hash"], my_blocks) logging.info("Received new block at height %d; next in %s (%s)", bestheader["height"], seconds_to_hms(next_delta), ("mine" if next_is_mine else "backup")) @@ -427,14 +342,14 @@ def do_generate(args): action_time = now is_mine = True elif bestheader["height"] == 0: - time_delta = next_block_delta(int(bestheader["bits"], 16), bci["bestblockhash"], ultimate_target, args.poisson) + time_delta = next_block_delta(int(bestheader["bits"], 16), bci["bestblockhash"], ultimate_target, args.poisson, args.max_interval) time_delta *= 100 # 100 blocks logging.info("Backdating time for first block to %d minutes ago" % (time_delta/60)) mine_time = now - time_delta action_time = now is_mine = True else: - time_delta = next_block_delta(int(bestheader["bits"], 16), bci["bestblockhash"], ultimate_target, args.poisson) + time_delta = next_block_delta(int(bestheader["bits"], 16), bci["bestblockhash"], ultimate_target, args.poisson, args.max_interval) mine_time = bestheader["time"] + time_delta is_mine = next_block_is_mine(bci["bestblockhash"], my_blocks) @@ -508,7 +423,7 @@ def do_generate(args): # report bstr = "block" if is_mine else "backup block" - next_delta = next_block_delta(block.nBits, block.hash, ultimate_target, args.poisson) + next_delta = next_block_delta(block.nBits, block.hash, ultimate_target, args.poisson, args.max_interval) next_delta += block.nTime - time.time() next_is_mine = next_block_is_mine(block.hash, my_blocks) @@ -586,6 +501,7 @@ def main(): generate.add_argument("--multiminer", default=None, type=str, help="Specify which set of blocks to mine (eg: 1-40/100 for the first 40%%, 2/3 for the second 3rd)") generate.add_argument("--backup-delay", default=300, type=int, help="Seconds to delay before mining blocks reserved for other miners (default=300)") generate.add_argument("--standby-delay", default=0, type=int, help="Seconds to delay before mining blocks (default=0)") + generate.add_argument("--max-interval", default=1800, type=int, help="Maximum interblock interval (seconds)") calibrate = cmds.add_parser("calibrate", help="Calibrate difficulty") calibrate.set_defaults(fn=do_calibrate) @@ -627,5 +543,3 @@ def main(): if __name__ == "__main__": main() - - diff --git a/contrib/valgrind.supp b/contrib/valgrind.supp index 6efe49254b..d6856b4274 100644 --- a/contrib/valgrind.supp +++ b/contrib/valgrind.supp @@ -65,12 +65,6 @@ obj:*/libdb_cxx-*.so } { - Suppress leaks on init - Memcheck:Leak - ... - fun:_Z11AppInitMainR11NodeContext -} -{ Suppress leaks on shutdown Memcheck:Leak ... @@ -83,21 +77,6 @@ obj:/usr/lib64/libgdk-3.so.0.2404.7 } { - Suppress leveldb warning (leveldb::InitModule()) - https://github.com/google/leveldb/issues/113 - Memcheck:Leak - match-leak-kinds: reachable - fun:_Znwm - fun:_ZN7leveldbL10InitModuleEv -} -{ - Suppress leveldb warning (leveldb::Env::Default()) - https://github.com/google/leveldb/issues/113 - Memcheck:Leak - match-leak-kinds: reachable - fun:_Znwm - ... - fun:_ZN7leveldbL14InitDefaultEnvEv -} -{ Suppress leveldb leak Memcheck:Leak match-leak-kinds: reachable diff --git a/contrib/verify-commits/trusted-keys b/contrib/verify-commits/trusted-keys index 2f8a21009a..5ca65e7b0d 100644 --- a/contrib/verify-commits/trusted-keys +++ b/contrib/verify-commits/trusted-keys @@ -1,6 +1,6 @@ 71A3B16735405025D447E8F274810B012346C9A6 -133EAC179436F14A5CF1B794860FEB804E669320 B8B3F1C0E58C15DB6A81D30C3648A882F4316B9B E777299FC265DD04793070EB944D35F9AC3DB76A D1DBF2C4B96F2DEBF4C16654410108112E7EA81F 152812300785C96444D3334D17565732E08E5E41 +6B002C6EA3F91B1B0DF0C9BC8F617F1200A6D25C diff --git a/depends/Makefile b/depends/Makefile index 20f5f6b2c6..11fdd6dd53 100644 --- a/depends/Makefile +++ b/depends/Makefile @@ -42,8 +42,12 @@ NO_UPNP ?= NO_USDT ?= NO_NATPMP ?= MULTIPROCESS ?= +LTO ?= FALLBACK_DOWNLOAD_PATH ?= https://bitcoincore.org/depends-sources +C_STANDARD ?= c11 +CXX_STANDARD ?= c++17 + BUILD = $(shell ./config.guess) HOST ?= $(BUILD) PATCHES_PATH = $(BASEDIR)/patches @@ -140,8 +144,8 @@ include packages/packages.mk # 2. Before including packages/*.mk (excluding packages/packages.mk), since # they rely on the build_id variables # -build_id:=$(shell env CC='$(build_CC)' CXX='$(build_CXX)' AR='$(build_AR)' RANLIB='$(build_RANLIB)' STRIP='$(build_STRIP)' SHA256SUM='$(build_SHA256SUM)' DEBUG='$(DEBUG)' ./gen_id '$(BUILD_ID_SALT)' 'GUIX_ENVIRONMENT=$(realpath $(GUIX_ENVIRONMENT))') -$(host_arch)_$(host_os)_id:=$(shell env CC='$(host_CC)' CXX='$(host_CXX)' AR='$(host_AR)' RANLIB='$(host_RANLIB)' STRIP='$(host_STRIP)' SHA256SUM='$(build_SHA256SUM)' DEBUG='$(DEBUG)' ./gen_id '$(HOST_ID_SALT)' 'GUIX_ENVIRONMENT=$(realpath $(GUIX_ENVIRONMENT))') +build_id:=$(shell env CC='$(build_CC)' C_STANDARD='$(C_STANDARD)' CXX='$(build_CXX)' CXX_STANDARD='$(CXX_STANDARD)' AR='$(build_AR)' RANLIB='$(build_RANLIB)' STRIP='$(build_STRIP)' SHA256SUM='$(build_SHA256SUM)' DEBUG='$(DEBUG)' LTO='$(LTO)' ./gen_id '$(BUILD_ID_SALT)' 'GUIX_ENVIRONMENT=$(realpath $(GUIX_ENVIRONMENT))') +$(host_arch)_$(host_os)_id:=$(shell env CC='$(host_CC)' C_STANDARD='$(C_STANDARD)' CXX='$(host_CXX)' CXX_STANDARD='$(CXX_STANDARD)' AR='$(host_AR)' RANLIB='$(host_RANLIB)' STRIP='$(host_STRIP)' SHA256SUM='$(build_SHA256SUM)' DEBUG='$(DEBUG)' LTO='$(LTO)' ./gen_id '$(HOST_ID_SALT)' 'GUIX_ENVIRONMENT=$(realpath $(GUIX_ENVIRONMENT))') qrencode_packages_$(NO_QR) = $(qrencode_$(host_os)_packages) @@ -242,6 +246,7 @@ $(host_prefix)/share/config.site : config.site.in $(host_prefix)/.stamp_$(final_ -e 's|@no_usdt@|$(NO_USDT)|' \ -e 's|@no_natpmp@|$(NO_NATPMP)|' \ -e 's|@multiprocess@|$(MULTIPROCESS)|' \ + -e 's|@lto@|$(LTO)|' \ -e 's|@debug@|$(DEBUG)|' \ $< > $@ touch $@ diff --git a/depends/README.md b/depends/README.md index da2a74e0e7..66e1ddc4eb 100644 --- a/depends/README.md +++ b/depends/README.md @@ -96,6 +96,8 @@ The following can be set when running make: `make FOO=bar` - `BASE_CACHE`: Built packages will be placed here - `SDK_PATH`: Path where SDKs can be found (used by macOS) - `FALLBACK_DOWNLOAD_PATH`: If a source file can't be fetched, try here before giving up +- `C_STANDARD`: Set the C standard version used. Defaults to `c11`. +- `CXX_STANDARD`: Set the C++ standard version used. Defaults to `c++17`. - `NO_QT`: Don't download/build/cache Qt and its dependencies - `NO_QR`: Don't download/build/cache packages needed for enabling qrencode - `NO_ZMQ`: Don't download/build/cache packages needed for enabling ZeroMQ @@ -117,6 +119,7 @@ The following can be set when running make: `make FOO=bar` - `LOG`: Use file-based logging for individual packages. During a package build its log file resides in the `depends` directory, and the log file is printed out automatically in case of build error. After successful build log files are moved along with package archives +- `LTO`: Use LTO when building packages. If some packages are not built, for example `make NO_WALLET=1`, the appropriate options will be passed to bitcoin's configure. In this case, `--disable-wallet`. diff --git a/depends/config.site.in b/depends/config.site.in index 03dabeea0a..8f6849214d 100644 --- a/depends/config.site.in +++ b/depends/config.site.in @@ -78,14 +78,15 @@ if test "@host_os@" = darwin; then BREW=no fi +if test -z "$enable_lto" && test -n "@lto@"; then + enable_lto=yes +fi + PKG_CONFIG="$(which pkg-config) --static" -# These two need to remain exported because pkg-config does not see them -# otherwise. That means they must be unexported at the end of configure.ac to -# avoid ruining the cache. Sigh. -export PKG_CONFIG_PATH="${depends_prefix}/share/pkgconfig:${depends_prefix}/lib/pkgconfig" +PKG_CONFIG_PATH="${depends_prefix}/share/pkgconfig:${depends_prefix}/lib/pkgconfig" if test -z "@allow_host_packages@"; then - export PKG_CONFIG_LIBDIR="${depends_prefix}/lib/pkgconfig" + PKG_CONFIG_LIBDIR="${depends_prefix}/lib/pkgconfig" fi CPPFLAGS="-I${depends_prefix}/include/ ${CPPFLAGS}" @@ -101,7 +102,7 @@ PYTHONPATH="${depends_prefix}/native/lib/python3/dist-packages${PYTHONPATH:+${PA if test -n "@AR@"; then AR="@AR@" - ac_cv_path_ac_pt_AR="${AR}" + ac_cv_path_AR="${AR}" fi if test -n "@RANLIB@"; then @@ -122,17 +123,17 @@ fi if test "@host_os@" = darwin; then if test -n "@OTOOL@"; then OTOOL="@OTOOL@" - ac_cv_path_ac_pt_OTOOL="${OTOOL}" + ac_cv_path_OTOOL="${OTOOL}" fi if test -n "@INSTALL_NAME_TOOL@"; then INSTALL_NAME_TOOL="@INSTALL_NAME_TOOL@" - ac_cv_path_ac_pt_INSTALL_NAME_TOOL="${INSTALL_NAME_TOOL}" + ac_cv_path_INSTALL_NAME_TOOL="${INSTALL_NAME_TOOL}" fi if test -n "@DSYMUTIL@"; then DSYMUTIL="@DSYMUTIL@" - ac_cv_path_ac_pt_DSYMUTIL="${DSYMUTIL}" + ac_cv_path_DSYMUTIL="${DSYMUTIL}" fi fi diff --git a/depends/gen_id b/depends/gen_id index ac69ca7ee1..7caf8d764d 100755 --- a/depends/gen_id +++ b/depends/gen_id @@ -1,7 +1,8 @@ #!/usr/bin/env bash -# Usage: env [ CC=... ] [ CXX=... ] [ AR=... ] [ RANLIB=... ] [ STRIP=... ] \ -# [ DEBUG=... ] ./build-id [ID_SALT]... +# Usage: env [ CC=... ] [ C_STANDARD=...] [ CXX=... ] [CXX_STANDARD=...] \ +# [ AR=... ] [ RANLIB=... ] [ STRIP=... ] [ DEBUG=... ] \ +# [ LTO=... ] ./build-id [ID_SALT]... # # Prints to stdout a SHA256 hash representing the current toolset, used by # depends/Makefile as a build id for caching purposes (detecting when the @@ -39,12 +40,14 @@ bash -c "${CC} -v" bash -c "${CC} -v -E -xc -o /dev/null - < /dev/null" bash -c "${CC} -v -E -xobjective-c -o /dev/null - < /dev/null" + echo "C_STANDARD=${C_STANDARD}" echo "END CC" echo "BEGIN CXX" bash -c "${CXX} -v" bash -c "${CXX} -v -E -xc++ -o /dev/null - < /dev/null" bash -c "${CXX} -v -E -xobjective-c++ -o /dev/null - < /dev/null" + echo "CXX_STANDARD=${CXX_STANDARD}" echo "END CXX" echo "BEGIN AR" @@ -63,6 +66,10 @@ env | grep '^STRIP_' echo "END STRIP" + echo "BEGIN LTO" + echo "LTO=${LTO}" + echo "END LTO" + echo "END ALL" ) | if [ -n "$DEBUG" ] && command -v tee > /dev/null 2>&1; then # When debugging and `tee` is available, output the preimage to stderr diff --git a/depends/hosts/android.mk b/depends/hosts/android.mk index fcc1c4f5c3..b53966dcf8 100644 --- a/depends/hosts/android.mk +++ b/depends/hosts/android.mk @@ -5,6 +5,15 @@ else android_CXX=$(ANDROID_TOOLCHAIN_BIN)/$(HOST)$(ANDROID_API_LEVEL)-clang++ android_CC=$(ANDROID_TOOLCHAIN_BIN)/$(HOST)$(ANDROID_API_LEVEL)-clang endif + +android_CFLAGS=-std=$(C_STANDARD) +android_CXXFLAGS=-std=$(CXX_STANDARD) + +ifneq ($(LTO),) +android_CFLAGS += -flto +android_LDFLAGS += -flto +endif + android_AR=$(ANDROID_TOOLCHAIN_BIN)/llvm-ar android_RANLIB=$(ANDROID_TOOLCHAIN_BIN)/llvm-ranlib diff --git a/depends/hosts/darwin.mk b/depends/hosts/darwin.mk index bf9b7625f2..8fcea35d98 100644 --- a/depends/hosts/darwin.mk +++ b/depends/hosts/darwin.mk @@ -17,6 +17,7 @@ darwin_native_toolchain=native_cctools clang_prog=$(build_prefix)/bin/clang clangxx_prog=$(clang_prog)++ +llvm_config_prog=$(build_prefix)/bin/llvm-config clang_resource_dir=$(build_prefix)/lib/clang/$(native_clang_version) else @@ -34,8 +35,10 @@ darwin_native_toolchain= # Source: https://lists.gnu.org/archive/html/bug-make/2017-11/msg00017.html clang_prog=$(shell $(SHELL) $(.SHELLFLAGS) "command -v clang") clangxx_prog=$(shell $(SHELL) $(.SHELLFLAGS) "command -v clang++") +llvm_config_prog=$(shell $(SHELL) $(.SHELLFLAGS) "command -v llvm-config") clang_resource_dir=$(shell clang -print-resource-dir) +llvm_lib_dir=$(shell $(llvm_config_prog) --libdir) endif cctools_TOOLS=AR RANLIB STRIP NM LIBTOOL OTOOL INSTALL_NAME_TOOL DSYMUTIL @@ -109,8 +112,14 @@ darwin_CXX=env -u C_INCLUDE_PATH -u CPLUS_INCLUDE_PATH \ -Xclang -internal-externc-isystem$(clang_resource_dir)/include \ -Xclang -internal-externc-isystem$(OSX_SDK)/usr/include -darwin_CFLAGS=-pipe -darwin_CXXFLAGS=$(darwin_CFLAGS) +darwin_CFLAGS=-pipe -std=$(C_STANDARD) +darwin_CXXFLAGS=-pipe -std=$(CXX_STANDARD) + +ifneq ($(LTO),) +darwin_CFLAGS += -flto +darwin_CXXFLAGS += -flto +darwin_LDFLAGS += -flto +endif darwin_release_CFLAGS=-O2 darwin_release_CXXFLAGS=$(darwin_release_CFLAGS) diff --git a/depends/hosts/default.mk b/depends/hosts/default.mk index ea60f025de..7c76331ab4 100644 --- a/depends/hosts/default.mk +++ b/depends/hosts/default.mk @@ -9,6 +9,7 @@ default_host_RANLIB = $(host_toolchain)ranlib default_host_STRIP = $(host_toolchain)strip default_host_LIBTOOL = $(host_toolchain)libtool default_host_NM = $(host_toolchain)nm +default_host_OBJCOPY = $(host_toolchain)objcopy define add_host_tool_func ifneq ($(filter $(origin $1),undefined default),) @@ -33,5 +34,5 @@ host_$1 = $$($(host_arch)_$(host_os)_$1) host_$(release_type)_$1 = $$($(host_arch)_$(host_os)_$(release_type)_$1) endef -$(foreach tool,CC CXX AR RANLIB STRIP NM LIBTOOL OTOOL INSTALL_NAME_TOOL DSYMUTIL,$(eval $(call add_host_tool_func,$(tool)))) +$(foreach tool,CC CXX AR RANLIB STRIP LIBTOOL NM OBJCOPY OTOOL INSTALL_NAME_TOOL DSYMUTIL,$(eval $(call add_host_tool_func,$(tool)))) $(foreach flags,CFLAGS CXXFLAGS CPPFLAGS LDFLAGS, $(eval $(call add_host_flags_func,$(flags)))) diff --git a/depends/hosts/freebsd.mk b/depends/hosts/freebsd.mk index 0a62347b57..5351d0b900 100644 --- a/depends/hosts/freebsd.mk +++ b/depends/hosts/freebsd.mk @@ -1,5 +1,11 @@ -freebsd_CFLAGS=-pipe -freebsd_CXXFLAGS=$(freebsd_CFLAGS) +freebsd_CFLAGS=-pipe -std=$(C_STANDARD) +freebsd_CXXFLAGS=-pipe -std=$(CXX_STANDARD) + +ifneq ($(LTO),) +freebsd_CFLAGS += -flto +freebsd_CXXFLAGS += -flto +freebsd_LDFLAGS += -flto +endif freebsd_release_CFLAGS=-O2 freebsd_release_CXXFLAGS=$(freebsd_release_CFLAGS) diff --git a/depends/hosts/linux.mk b/depends/hosts/linux.mk index 07da752492..635d3d16da 100644 --- a/depends/hosts/linux.mk +++ b/depends/hosts/linux.mk @@ -1,5 +1,15 @@ -linux_CFLAGS=-pipe -linux_CXXFLAGS=$(linux_CFLAGS) +linux_CFLAGS=-pipe -std=$(C_STANDARD) +linux_CXXFLAGS=-pipe -std=$(CXX_STANDARD) + +ifneq ($(LTO),) +linux_CFLAGS += -flto +linux_CXXFLAGS += -flto +linux_LDFLAGS += -flto + +linux_AR = $(host_toolchain)gcc-ar +linux_NM = $(host_toolchain)gcc-nm +linux_RANLIB = $(host_toolchain)gcc-ranlib +endif linux_release_CFLAGS=-O2 linux_release_CXXFLAGS=$(linux_release_CFLAGS) diff --git a/depends/hosts/mingw32.mk b/depends/hosts/mingw32.mk index 48020d71af..fc1cc1afbe 100644 --- a/depends/hosts/mingw32.mk +++ b/depends/hosts/mingw32.mk @@ -2,8 +2,18 @@ ifneq ($(shell $(SHELL) $(.SHELLFLAGS) "command -v $(host)-g++-posix"),) mingw32_CXX := $(host)-g++-posix endif -mingw32_CFLAGS=-pipe -mingw32_CXXFLAGS=$(mingw32_CFLAGS) +mingw32_CFLAGS=-pipe -std=$(C_STANDARD) +mingw32_CXXFLAGS=-pipe -std=$(CXX_STANDARD) + +ifneq ($(LTO),) +mingw32_CFLAGS += -flto +mingw32_CXXFLAGS += -flto +mingw32_LDFLAGS += -flto + +mingw32_AR = $(host_toolchain)gcc-ar +mingw32_NM = $(host_toolchain)gcc-nm +mingw32_RANLIB = $(host_toolchain)gcc-ranlib +endif mingw32_release_CFLAGS=-O2 mingw32_release_CXXFLAGS=$(mingw32_release_CFLAGS) diff --git a/depends/hosts/netbsd.mk b/depends/hosts/netbsd.mk index b3e4545a64..14121dca20 100644 --- a/depends/hosts/netbsd.mk +++ b/depends/hosts/netbsd.mk @@ -1,4 +1,16 @@ -netbsd_CFLAGS=-pipe +netbsd_CFLAGS=-pipe -std=$(C_STANDARD) +netbsd_CXXFLAGS=-pipe -std=$(CXX_STANDARD) + +ifneq ($(LTO),) +netbsd_CFLAGS += -flto +netbsd_CXXFLAGS += -flto +netbsd_LDFLAGS += -flto + +netbsd_AR = $(host_toolchain)gcc-ar +netbsd_NM = $(host_toolchain)gcc-nm +netbsd_RANLIB = $(host_toolchain)gcc-ranlib +endif + netbsd_CXXFLAGS=$(netbsd_CFLAGS) netbsd_release_CFLAGS=-O2 diff --git a/depends/hosts/openbsd.mk b/depends/hosts/openbsd.mk index 5988f24bff..d330e94d2e 100644 --- a/depends/hosts/openbsd.mk +++ b/depends/hosts/openbsd.mk @@ -1,5 +1,11 @@ -openbsd_CFLAGS=-pipe -openbsd_CXXFLAGS=$(openbsd_CFLAGS) +openbsd_CFLAGS=-pipe -std=$(C_STANDARD) +openbsd_CXXFLAGS=-pipe -std=$(CXX_STANDARD) + +ifneq ($(LTO),) +openbsd_CFLAGS += -flto +openbsd_CXXFLAGS += -flto +openbsd_LDFLAGS += -flto +endif openbsd_release_CFLAGS=-O2 openbsd_release_CXXFLAGS=$(openbsd_release_CFLAGS) diff --git a/depends/packages/bdb.mk b/depends/packages/bdb.mk index dc536fd399..2370c5b759 100644 --- a/depends/packages/bdb.mk +++ b/depends/packages/bdb.mk @@ -14,8 +14,7 @@ $(package)_config_opts_freebsd=--with-pic $(package)_config_opts_netbsd=--with-pic $(package)_config_opts_openbsd=--with-pic $(package)_config_opts_android=--with-pic -$(package)_cflags+=-Wno-error=implicit-function-declaration -$(package)_cxxflags+=-std=c++17 +$(package)_cflags+=-Wno-error=implicit-function-declaration -Wno-error=format-security $(package)_cppflags_mingw32=-DUNICODE -D_UNICODE endef diff --git a/depends/packages/boost.mk b/depends/packages/boost.mk index 563848c398..e3625d97f5 100644 --- a/depends/packages/boost.mk +++ b/depends/packages/boost.mk @@ -1,8 +1,8 @@ package=boost -$(package)_version=1.77.0 +$(package)_version=1.80.0 $(package)_download_path=https://boostorg.jfrog.io/artifactory/main/release/$($(package)_version)/source/ $(package)_file_name=boost_$(subst .,_,$($(package)_version)).tar.bz2 -$(package)_sha256_hash=fc9f85fc030e233142908241af7a846e60630aa7388de9a5fafb1f3a26840854 +$(package)_sha256_hash=1e19565d82e43bc59209a168f5ac899d3ba471d55c7610c677d4ccf2c9c500c0 define $(package)_stage_cmds mkdir -p $($(package)_staging_prefix_dir)/include && \ diff --git a/depends/packages/expat.mk b/depends/packages/expat.mk index 50791ebc6e..bb203d06f8 100644 --- a/depends/packages/expat.mk +++ b/depends/packages/expat.mk @@ -1,14 +1,18 @@ package=expat -$(package)_version=2.4.1 +$(package)_version=2.4.8 $(package)_download_path=https://github.com/libexpat/libexpat/releases/download/R_$(subst .,_,$($(package)_version))/ $(package)_file_name=$(package)-$($(package)_version).tar.xz -$(package)_sha256_hash=cf032d0dba9b928636548e32b327a2d66b1aab63c4f4a13dd132c2d1d2f2fb6a +$(package)_sha256_hash=f79b8f904b749e3e0d20afeadecf8249c55b2e32d4ebb089ae378df479dcaf25 +# -D_DEFAULT_SOURCE defines __USE_MISC, which exposes additional +# definitions in endian.h, which are required for a working +# endianess check in configure when building with -flto. define $(package)_set_vars $(package)_config_opts=--disable-shared --without-docbook --without-tests --without-examples $(package)_config_opts += --disable-dependency-tracking --enable-option-checking $(package)_config_opts += --without-xmlwf $(package)_config_opts_linux=--with-pic + $(package)_cppflags += -D_DEFAULT_SOURCE endef define $(package)_config_cmds diff --git a/depends/packages/libevent.mk b/depends/packages/libevent.mk index 1efe6220d3..5bd12522a7 100644 --- a/depends/packages/libevent.mk +++ b/depends/packages/libevent.mk @@ -37,5 +37,6 @@ endef define $(package)_postprocess_cmds rm lib/*.la && \ - rm include/ev*.h + rm include/ev*.h && \ + rm include/event2/*_compat.h endef diff --git a/depends/packages/libnatpmp.mk b/depends/packages/libnatpmp.mk index cdcf8c0bf2..2eddc76d9c 100644 --- a/depends/packages/libnatpmp.mk +++ b/depends/packages/libnatpmp.mk @@ -1,8 +1,8 @@ package=libnatpmp -$(package)_version=4536032ae32268a45c073a4d5e91bbab4534773a +$(package)_version=07004b97cf691774efebe70404cf22201e4d330d $(package)_download_path=https://github.com/miniupnp/libnatpmp/archive $(package)_file_name=$($(package)_version).tar.gz -$(package)_sha256_hash=543b460aab26acf91e11d15e17d8798f845304199eea2d76c2f444ec749c5383 +$(package)_sha256_hash=9321953ceb39d07c25463e266e50d0ae7b64676bb3a986d932b18881ed94f1fb define $(package)_set_vars $(package)_build_opts=CC="$($(package)_cc)" diff --git a/depends/packages/libxcb.mk b/depends/packages/libxcb.mk index fa30e80f5c..036eaf6560 100644 --- a/depends/packages/libxcb.mk +++ b/depends/packages/libxcb.mk @@ -4,6 +4,7 @@ $(package)_download_path=https://xcb.freedesktop.org/dist $(package)_file_name=$(package)-$($(package)_version).tar.xz $(package)_sha256_hash=a55ed6db98d43469801262d81dc2572ed124edc3db31059d4e9916eb9f844c34 $(package)_dependencies=xcb_proto libXau +$(package)_patches = remove_pthread_stubs.patch define $(package)_set_vars $(package)_config_opts=--disable-static --disable-devel-docs --without-doxygen --without-launchd @@ -20,7 +21,7 @@ endef define $(package)_preprocess_cmds cp -f $(BASEDIR)/config.guess $(BASEDIR)/config.sub build-aux && \ - sed "s/pthread-stubs//" -i configure + patch -p1 -i $($(package)_patch_dir)/remove_pthread_stubs.patch endef define $(package)_config_cmds diff --git a/depends/packages/libxkbcommon.mk b/depends/packages/libxkbcommon.mk index 8c6c56545f..bcdcf671f7 100644 --- a/depends/packages/libxkbcommon.mk +++ b/depends/packages/libxkbcommon.mk @@ -5,9 +5,14 @@ $(package)_file_name=$(package)-$($(package)_version).tar.xz $(package)_sha256_hash=60ddcff932b7fd352752d51a5c4f04f3d0403230a584df9a2e0d5ed87c486c8b $(package)_dependencies=libxcb +# This package explicitly enables -Werror=array-bounds, which causes build failures +# with GCC 12.1+. Work around that by turning errors back into warnings. +# This workaround would be dropped if the package was updated, as that would require +# a different build system (Meson) define $(package)_set_vars $(package)_config_opts = --enable-option-checking --disable-dependency-tracking $(package)_config_opts += --disable-static --disable-docs +$(package)_cflags += -Wno-error=array-bounds endef define $(package)_preprocess_cmds diff --git a/depends/packages/native_cctools.mk b/depends/packages/native_cctools.mk index d169eb6723..03e9002ecd 100644 --- a/depends/packages/native_cctools.mk +++ b/depends/packages/native_cctools.mk @@ -7,18 +7,24 @@ $(package)_build_subdir=cctools $(package)_dependencies=native_libtapi define $(package)_set_vars - $(package)_config_opts=--target=$(host) + $(package)_config_opts=--target=$(host) --enable-lto-support + $(package)_config_opts+=--with-llvm-config=$(llvm_config_prog) $(package)_ldflags+=-Wl,-rpath=\\$$$$$$$$\$$$$$$$$ORIGIN/../lib - ifeq ($(strip $(FORCE_USE_SYSTEM_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 +ifneq ($(strip $(FORCE_USE_SYSTEM_CLANG)),) define $(package)_preprocess_cmds + mkdir -p $($(package)_staging_prefix_dir)/lib && \ + cp $(llvm_lib_dir)/libLTO.so $($(package)_staging_prefix_dir)/lib/ && \ cp -f $(BASEDIR)/config.guess $(BASEDIR)/config.sub cctools endef +else +define $(package)_preprocess_cmds + cp -f $(BASEDIR)/config.guess $(BASEDIR)/config.sub cctools +endef +endif define $(package)_config_cmds $($(package)_autoconf) diff --git a/depends/packages/native_clang.mk b/depends/packages/native_clang.mk index 245269a9d3..f2712294ab 100644 --- a/depends/packages/native_clang.mk +++ b/depends/packages/native_clang.mk @@ -16,10 +16,13 @@ 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/llvm-c && \ 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 include/llvm-c/ExternC.h $($(package)_staging_prefix_dir)/include/llvm-c && \ + cp include/llvm-c/lto.h $($(package)_staging_prefix_dir)/include/llvm-c && \ cp lib/libLTO.so $($(package)_staging_prefix_dir)/lib/ && \ cp -r lib/clang/$($(package)_version)/include/* $($(package)_staging_prefix_dir)/lib/clang/$($(package)_version)/include/ endef diff --git a/depends/packages/native_ds_store.mk b/depends/packages/native_ds_store.mk index 44108925a4..51a95f48ef 100644 --- a/depends/packages/native_ds_store.mk +++ b/depends/packages/native_ds_store.mk @@ -1,6 +1,6 @@ package=native_ds_store $(package)_version=1.3.0 -$(package)_download_path=https://github.com/al45tair/ds_store/archive/ +$(package)_download_path=https://github.com/dmgbuild/ds_store/archive/ $(package)_file_name=v$($(package)_version).tar.gz $(package)_sha256_hash=76b3280cd4e19e5179defa23fb594a9dd32643b0c80d774bd3108361d94fb46d $(package)_install_libdir=$(build_prefix)/lib/python3/dist-packages diff --git a/depends/packages/native_libtapi.mk b/depends/packages/native_libtapi.mk index 1633213a42..052bb23689 100644 --- a/depends/packages/native_libtapi.mk +++ b/depends/packages/native_libtapi.mk @@ -13,7 +13,5 @@ define $(package)_build_cmds 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 + ./install.sh endef diff --git a/depends/packages/native_mac_alias.mk b/depends/packages/native_mac_alias.mk index 783f87ca7c..ddd631186e 100644 --- a/depends/packages/native_mac_alias.mk +++ b/depends/packages/native_mac_alias.mk @@ -1,6 +1,6 @@ package=native_mac_alias $(package)_version=2.2.0 -$(package)_download_path=https://github.com/al45tair/mac_alias/archive/ +$(package)_download_path=https://github.com/dmgbuild/mac_alias/archive/ $(package)_file_name=v$($(package)_version).tar.gz $(package)_sha256_hash=421e6d7586d1f155c7db3e7da01ca0dacc9649a509a253ad7077b70174426499 $(package)_install_libdir=$(build_prefix)/lib/python3/dist-packages diff --git a/depends/packages/qt.mk b/depends/packages/qt.mk index 2bc3a81430..d9ae918d71 100644 --- a/depends/packages/qt.mk +++ b/depends/packages/qt.mk @@ -1,9 +1,9 @@ package=qt -$(package)_version=5.15.3 +$(package)_version=5.15.5 $(package)_download_path=https://download.qt.io/official_releases/qt/5.15/$($(package)_version)/submodules $(package)_suffix=everywhere-opensource-src-$($(package)_version).tar.xz $(package)_file_name=qtbase-$($(package)_suffix) -$(package)_sha256_hash=26394ec9375d52c1592bd7b689b1619c6b8dbe9b6f91fdd5c355589787f3a0b6 +$(package)_sha256_hash=0c42c799aa7c89e479a07c451bf5a301e291266ba789e81afc18f95049524edc $(package)_linux_dependencies=freetype fontconfig libxcb libxkbcommon libxcb_util libxcb_util_render libxcb_util_keysyms libxcb_util_image libxcb_util_wm $(package)_qt_libs=corelib network widgets gui plugins testlib $(package)_linguist_tools = lrelease lupdate lconvert @@ -17,16 +17,17 @@ $(package)_patches += fix_montery_include.patch $(package)_patches += fix_android_jni_static.patch $(package)_patches += dont_hardcode_pwd.patch $(package)_patches += qtbase-moc-ignore-gcc-macro.patch -$(package)_patches += fix_limits_header.patch $(package)_patches += use_android_ndk23.patch $(package)_patches += rcc_hardcode_timestamp.patch $(package)_patches += duplicate_lcqpafonts.patch +$(package)_patches += fast_fixed_dtoa_no_optimize.patch +$(package)_patches += guix_cross_lib_path.patch $(package)_qttranslations_file_name=qttranslations-$($(package)_suffix) -$(package)_qttranslations_sha256_hash=5d7869f670a135ad0986e266813b9dd5bbae2b09577338f9cdf8904d4af52db0 +$(package)_qttranslations_sha256_hash=c92af4171397a0ed272330b4fa0669790fcac8d050b07c8b8cc565ebeba6735e $(package)_qttools_file_name=qttools-$($(package)_suffix) -$(package)_qttools_sha256_hash=463b2fe71a085e7ab4e39333ae360ab0ec857b966d7a08f752c427e5df55f90d +$(package)_qttools_sha256_hash=6d0778b71b2742cb527561791d1d3d255366163d54a10f78c683a398f09ffc6c $(package)_extra_sources = $($(package)_qttranslations_file_name) $(package)_extra_sources += $($(package)_qttools_file_name) @@ -55,6 +56,7 @@ $(package)_config_opts += -no-linuxfb $(package)_config_opts += -no-libjpeg $(package)_config_opts += -no-libproxy $(package)_config_opts += -no-libudev +$(package)_config_opts += -no-mimetype-database $(package)_config_opts += -no-mtdev $(package)_config_opts += -no-openssl $(package)_config_opts += -no-openvg @@ -130,6 +132,9 @@ $(package)_config_opts_darwin += -no-feature-corewlan $(package)_config_opts_darwin += -no-freetype $(package)_config_opts_darwin += QMAKE_MACOSX_DEPLOYMENT_TARGET=$(OSX_MIN_VERSION) +# Optimizing using > -O1 causes non-determinism when building across arches. +$(package)_config_opts_aarch64_darwin += "QMAKE_CFLAGS_OPTIMIZE_FULL = -O1" + ifneq ($(build_os),darwin) $(package)_config_opts_darwin += -xplatform macx-clang-linux $(package)_config_opts_darwin += -device-option MAC_SDK_PATH=$(OSX_SDK) @@ -152,18 +157,13 @@ $(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 +ifneq ($(LTO),) +$(package)_config_opts_linux += -ltcg +endif +$(package)_config_opts_linux += -platform linux-g++ -xplatform bitcoin-linux-g++ ifneq (,$(findstring -stdlib=libc++,$($(1)_cxx))) $(package)_config_opts_x86_64_linux = -xplatform linux-clang-libc++ -else -$(package)_config_opts_x86_64_linux = -xplatform linux-g++-64 endif -$(package)_config_opts_aarch64_linux = -xplatform linux-aarch64-gnu-g++ -$(package)_config_opts_powerpc64_linux = -platform linux-g++ -xplatform bitcoin-linux-g++ -$(package)_config_opts_powerpc64le_linux = -platform linux-g++ -xplatform bitcoin-linux-g++ -$(package)_config_opts_riscv64_linux = -platform linux-g++ -xplatform bitcoin-linux-g++ -$(package)_config_opts_s390x_linux = -platform linux-g++ -xplatform bitcoin-linux-g++ $(package)_config_opts_mingw32 = -no-opengl $(package)_config_opts_mingw32 += -no-dbus @@ -171,8 +171,9 @@ $(package)_config_opts_mingw32 += -no-freetype $(package)_config_opts_mingw32 += -xplatform win32-g++ $(package)_config_opts_mingw32 += "QMAKE_CFLAGS = '$($(package)_cflags) $($(package)_cppflags)'" $(package)_config_opts_mingw32 += "QMAKE_CXX = '$($(package)_cxx)'" -$(package)_config_opts_mingw32 += "QMAKE_CXXFLAGS = '$($(package)_cflags) $($(package)_cppflags)'" +$(package)_config_opts_mingw32 += "QMAKE_CXXFLAGS = '$($(package)_cxxflags) $($(package)_cppflags)'" $(package)_config_opts_mingw32 += "QMAKE_LFLAGS = '$($(package)_ldflags)'" +$(package)_config_opts_mingw32 += "QMAKE_LIB = '$($(package)_ar) rc'" $(package)_config_opts_mingw32 += -device-option CROSS_COMPILE="$(host)-" $(package)_config_opts_mingw32 += -pch @@ -222,18 +223,11 @@ endef # 2. Create a macOS-Clang-Linux mkspec using our mac-qmake.conf. # # 3. After making a copy of the mkspec for the linux-arm-gnueabi host, named -# bitcoin-linux-g++, replace instances of linux-arm-gnueabi with $(host). This -# way we can generically support hosts like riscv64-linux-gnu, which Qt doesn't -# ship a mkspec for. See it's usage in config_opts_* above. +# bitcoin-linux-g++, replace tool names with $($($(package)_type)_TOOL). # # 4. Put our C, CXX and LD FLAGS into gcc-base.conf. Only used for non-host builds. # -# 5. Do similar for the win32-g++ mkspec. -# -# 6. In clang.conf, swap out clang & clang++, for our compiler + flags. See #17466. -# -# 7. Adjust a regex in toolchain.prf, to accommodate Guix's usage of -# CROSS_LIBRARY_PATH. See #15277. +# 5. In clang.conf, swap out clang & clang++, for our compiler + flags. See #17466. define $(package)_preprocess_cmds cp $($(package)_patch_dir)/qt.pro qt.pro && \ cp $($(package)_patch_dir)/qttools_src.pro qttools/src/src.pro && \ @@ -243,22 +237,27 @@ define $(package)_preprocess_cmds patch -p1 -i $($(package)_patch_dir)/no-xlib.patch && \ patch -p1 -i $($(package)_patch_dir)/dont_hardcode_x86_64.patch && \ patch -p1 -i $($(package)_patch_dir)/qtbase-moc-ignore-gcc-macro.patch && \ - patch -p1 -i $($(package)_patch_dir)/fix_limits_header.patch && \ patch -p1 -i $($(package)_patch_dir)/fix_montery_include.patch && \ patch -p1 -i $($(package)_patch_dir)/use_android_ndk23.patch && \ patch -p1 -i $($(package)_patch_dir)/rcc_hardcode_timestamp.patch && \ patch -p1 -i $($(package)_patch_dir)/duplicate_lcqpafonts.patch && \ + patch -p1 -i $($(package)_patch_dir)/fast_fixed_dtoa_no_optimize.patch && \ + patch -p1 -i $($(package)_patch_dir)/guix_cross_lib_path.patch && \ mkdir -p qtbase/mkspecs/macx-clang-linux &&\ cp -f qtbase/mkspecs/macx-clang/qplatformdefs.h qtbase/mkspecs/macx-clang-linux/ &&\ cp -f $($(package)_patch_dir)/mac-qmake.conf qtbase/mkspecs/macx-clang-linux/qmake.conf && \ cp -r qtbase/mkspecs/linux-arm-gnueabi-g++ qtbase/mkspecs/bitcoin-linux-g++ && \ - sed -i.old "s/arm-linux-gnueabi-/$(host)-/g" qtbase/mkspecs/bitcoin-linux-g++/qmake.conf && \ + sed -i.old "s|arm-linux-gnueabi-gcc|$($($(package)_type)_CC)|" qtbase/mkspecs/bitcoin-linux-g++/qmake.conf && \ + sed -i.old "s|arm-linux-gnueabi-g++|$($($(package)_type)_CXX)|" qtbase/mkspecs/bitcoin-linux-g++/qmake.conf && \ + sed -i.old "s|arm-linux-gnueabi-ar|$($($(package)_type)_AR)|" qtbase/mkspecs/bitcoin-linux-g++/qmake.conf && \ + sed -i.old "s|arm-linux-gnueabi-objcopy|$($($(package)_type)_OBJCOPY)|" qtbase/mkspecs/bitcoin-linux-g++/qmake.conf && \ + sed -i.old "s|arm-linux-gnueabi-nm|$($($(package)_type)_NM)|" qtbase/mkspecs/bitcoin-linux-g++/qmake.conf && \ + sed -i.old "s|arm-linux-gnueabi-strip|$($($(package)_type)_STRIP)|" qtbase/mkspecs/bitcoin-linux-g++/qmake.conf && \ echo "!host_build: QMAKE_CFLAGS += $($(package)_cflags) $($(package)_cppflags)" >> qtbase/mkspecs/common/gcc-base.conf && \ echo "!host_build: QMAKE_CXXFLAGS += $($(package)_cxxflags) $($(package)_cppflags)" >> qtbase/mkspecs/common/gcc-base.conf && \ echo "!host_build: QMAKE_LFLAGS += $($(package)_ldflags)" >> qtbase/mkspecs/common/gcc-base.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 + sed -i.old "s|QMAKE_CXX = \$$$$\$$$${CROSS_COMPILE}clang++|QMAKE_CXX = $($(package)_cxx)|" qtbase/mkspecs/common/clang.conf endef define $(package)_config_cmds diff --git a/depends/packages/sqlite.mk b/depends/packages/sqlite.mk index 126781ceeb..820d724214 100644 --- a/depends/packages/sqlite.mk +++ b/depends/packages/sqlite.mk @@ -1,8 +1,8 @@ package=sqlite -$(package)_version=3320100 -$(package)_download_path=https://sqlite.org/2020/ +$(package)_version=3380500 +$(package)_download_path=https://sqlite.org/2022/ $(package)_file_name=sqlite-autoconf-$($(package)_version).tar.gz -$(package)_sha256_hash=486748abfb16abd8af664e3a5f03b228e5f124682b0c942e157644bf6fff7d10 +$(package)_sha256_hash=5af07de982ba658fd91a03170c945f99c971f6955bc79df3266544373e39869c define $(package)_set_vars $(package)_config_opts=--disable-shared --disable-readline --disable-dynamic-extensions --enable-option-checking diff --git a/depends/packages/systemtap.mk b/depends/packages/systemtap.mk index 833e75b978..a57f1b6d36 100644 --- a/depends/packages/systemtap.mk +++ b/depends/packages/systemtap.mk @@ -1,8 +1,8 @@ package=systemtap -$(package)_version=4.5 +$(package)_version=4.7 $(package)_download_path=https://sourceware.org/systemtap/ftp/releases/ $(package)_file_name=$(package)-$($(package)_version).tar.gz -$(package)_sha256_hash=75078ed37e0dd2a769c9d1f9394170b2d9f4d7daa425f43ca80c13bad6cfc925 +$(package)_sha256_hash=43a0a3db91aa4d41e28015b39a65e62059551f3cc7377ebf3a3a5ca7339e7b1f $(package)_patches=remove_SDT_ASM_SECTION_AUTOGROUP_SUPPORT_check.patch define $(package)_preprocess_cmds diff --git a/depends/packages/zeromq.mk b/depends/packages/zeromq.mk index c74ae15b31..267ed11253 100644 --- a/depends/packages/zeromq.mk +++ b/depends/packages/zeromq.mk @@ -16,7 +16,6 @@ define $(package)_set_vars $(package)_config_opts_netbsd=--with-pic $(package)_config_opts_openbsd=--with-pic $(package)_config_opts_android=--with-pic - $(package)_cxxflags+=-std=c++17 endef define $(package)_preprocess_cmds diff --git a/depends/patches/libxcb/remove_pthread_stubs.patch b/depends/patches/libxcb/remove_pthread_stubs.patch new file mode 100644 index 0000000000..1f32dea527 --- /dev/null +++ b/depends/patches/libxcb/remove_pthread_stubs.patch @@ -0,0 +1,12 @@ +Remove uneeded pthread-stubs dependency +--- a/configure ++++ b/configure +@@ -19695,7 +19695,7 @@ fi + NEEDED="xau >= 0.99.2" + case $host_os in + linux*) ;; +- *) NEEDED="$NEEDED pthread-stubs" ;; ++ *) NEEDED="$NEEDED" ;; + esac + + pkg_failed=no diff --git a/depends/patches/qt/dont_hardcode_x86_64.patch b/depends/patches/qt/dont_hardcode_x86_64.patch index 5c1e030fa4..a66426877a 100644 --- a/depends/patches/qt/dont_hardcode_x86_64.patch +++ b/depends/patches/qt/dont_hardcode_x86_64.patch @@ -73,7 +73,7 @@ diff --git a/mkspecs/features/mac/default_post.prf b/mkspecs/features/mac/defaul index 92a9112bca6..d888731ec8d 100644 --- old/qtbase/mkspecs/features/mac/default_post.prf +++ new/qtbase/mkspecs/features/mac/default_post.prf -@@ -90,6 +90,11 @@ app_extension_api_only { +@@ -95,6 +95,11 @@ app_extension_api_only { QMAKE_LFLAGS += $$QMAKE_CFLAGS_APPLICATION_EXTENSION } @@ -85,7 +85,7 @@ index 92a9112bca6..d888731ec8d 100644 macx-xcode { qmake_pkginfo_typeinfo.name = QMAKE_PKGINFO_TYPEINFO !isEmpty(QMAKE_PKGINFO_TYPEINFO): \ -@@ -145,9 +150,6 @@ macx-xcode { +@@ -150,9 +155,6 @@ macx-xcode { simulator: VALID_SIMULATOR_ARCHS = $$QMAKE_APPLE_SIMULATOR_ARCHS VALID_ARCHS = $$VALID_DEVICE_ARCHS $$VALID_SIMULATOR_ARCHS diff --git a/depends/patches/qt/fast_fixed_dtoa_no_optimize.patch b/depends/patches/qt/fast_fixed_dtoa_no_optimize.patch new file mode 100644 index 0000000000..d4d6539f56 --- /dev/null +++ b/depends/patches/qt/fast_fixed_dtoa_no_optimize.patch @@ -0,0 +1,20 @@ +Modify the optimisation flags for FastFixedDtoa. +This fixes a non-determinism issue in the asm produced for +this function when cross-compiling on x86_64 and aarch64 for +the arm-linux-gnueabihf HOST. + +--- a/qtbase/src/3rdparty/double-conversion/fixed-dtoa.h ++++ b/qtbase/src/3rdparty/double-conversion/fixed-dtoa.h +@@ -48,9 +48,12 @@ namespace double_conversion { + // + // This method only works for some parameters. If it can't handle the input it + // returns false. The output is null-terminated when the function succeeds. ++#pragma GCC push_options ++#pragma GCC optimize ("-O1") + bool FastFixedDtoa(double v, int fractional_count, + Vector<char> buffer, int* length, int* decimal_point); + ++#pragma GCC pop_options + } // namespace double_conversion + + #endif // DOUBLE_CONVERSION_FIXED_DTOA_H_ diff --git a/depends/patches/qt/fix_android_jni_static.patch b/depends/patches/qt/fix_android_jni_static.patch index 22a4d5ab0e..936b82e152 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 -@@ -934,6 +934,14 @@ Q_DECL_EXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void */*reserved*/) +@@ -943,6 +943,14 @@ Q_DECL_EXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void */*reserved*/) __android_log_print(ANDROID_LOG_FATAL, "Qt", "registerNatives failed"); return -1; } diff --git a/depends/patches/qt/fix_limits_header.patch b/depends/patches/qt/fix_limits_header.patch deleted file mode 100644 index 258128c0ca..0000000000 --- a/depends/patches/qt/fix_limits_header.patch +++ /dev/null @@ -1,33 +0,0 @@ -Fix compiling with GCC 11 - -Upstream: - - bug report: https://bugreports.qt.io/browse/QTBUG-89977 - - fix in Qt 6.1: 813a928c7c3cf98670b6043149880ed5c955efb9 - ---- old/qtbase/src/corelib/text/qbytearraymatcher.h -+++ new/qtbase/src/corelib/text/qbytearraymatcher.h -@@ -42,6 +42,8 @@ - - #include <QtCore/qbytearray.h> - -+#include <limits> -+ - QT_BEGIN_NAMESPACE - - - -Upstream fix and backports: - - Qt 6.1: 3eab20ad382569cb2c9e6ccec2322c3d08c0f716 - - Qt 6.2: 380294a5971da85010a708dc23b0edec192cbf27 - - Qt 6.3: 2b2b3155d9f6ba1e4f859741468fbc47db09292b - ---- old/qtbase/src/corelib/tools/qoffsetstringarray_p.h -+++ new/qtbase/src/corelib/tools/qoffsetstringarray_p.h -@@ -55,6 +55,7 @@ - - #include <tuple> - #include <array> -+#include <limits> - - QT_BEGIN_NAMESPACE - diff --git a/depends/patches/qt/guix_cross_lib_path.patch b/depends/patches/qt/guix_cross_lib_path.patch new file mode 100644 index 0000000000..7911dc21d7 --- /dev/null +++ b/depends/patches/qt/guix_cross_lib_path.patch @@ -0,0 +1,17 @@ +Facilitate guix building with CROSS_LIBRARY_PATH + +See discussion in https://github.com/bitcoin/bitcoin/pull/15277. + +--- a/qtbase/mkspecs/features/toolchain.prf ++++ b/qtbase/mkspecs/features/toolchain.prf +@@ -236,8 +236,8 @@ isEmpty($${target_prefix}.INCDIRS) { + add_libraries = false + for (line, output) { + line ~= s/^[ \\t]*// # remove leading spaces +- contains(line, "LIBRARY_PATH=.*") { +- line ~= s/^LIBRARY_PATH=// # remove leading LIBRARY_PATH= ++ contains(line, "(CROSS_)?LIBRARY_PATH=.*") { ++ line ~= s/^(CROSS_)?LIBRARY_PATH=// # remove leading (CROSS_)?LIBRARY_PATH= + equals(QMAKE_HOST.os, Windows): \ + paths = $$split(line, ;) + else: \ diff --git a/depends/patches/qt/mac-qmake.conf b/depends/patches/qt/mac-qmake.conf index 543407f853..cb94bf07b4 100644 --- a/depends/patches/qt/mac-qmake.conf +++ b/depends/patches/qt/mac-qmake.conf @@ -15,7 +15,7 @@ QMAKE_MAC_SDK.macosx.SDKVersion = $${MAC_SDK_VERSION} QMAKE_MAC_SDK.macosx.PlatformPath = /phony !host_build: QMAKE_CFLAGS += -target $${MAC_TARGET} !host_build: QMAKE_OBJECTIVE_CFLAGS += $$QMAKE_CFLAGS -!host_build: QMAKE_CXXFLAGS += $$QMAKE_CFLAGS +!host_build: QMAKE_CXXFLAGS += -target $${MAC_TARGET} !host_build: QMAKE_LFLAGS += -target $${MAC_TARGET} QMAKE_AR = $${CROSS_COMPILE}ar cq QMAKE_RANLIB=$${CROSS_COMPILE}ranlib diff --git a/depends/patches/systemtap/remove_SDT_ASM_SECTION_AUTOGROUP_SUPPORT_check.patch b/depends/patches/systemtap/remove_SDT_ASM_SECTION_AUTOGROUP_SUPPORT_check.patch index eae0cf72d6..c115bc43e8 100644 --- a/depends/patches/systemtap/remove_SDT_ASM_SECTION_AUTOGROUP_SUPPORT_check.patch +++ b/depends/patches/systemtap/remove_SDT_ASM_SECTION_AUTOGROUP_SUPPORT_check.patch @@ -1,19 +1,15 @@ -commit b92d4c121486f3c6e8a2cea537c53eb09894479a -Author: 0xb10c <0xb10c@gmail.com> -Date: Tue Dec 7 11:02:07 2021 +0100 +Remove _SDT_ASM_SECTION_AUTOGROUP_SUPPORT check - Remove _SDT_ASM_SECTION_AUTOGROUP_SUPPORT check - - We assume that the assembler supports "?" in .pushsection directives. - This enables us to skip configure and make. - - See https://github.com/bitcoin/bitcoin/issues/23297. +We assume that the assembler supports "?" in .pushsection directives. +This enables us to skip configure and make. + +See https://github.com/bitcoin/bitcoin/issues/23297. diff --git a/includes/sys/sdt.h b/includes/sys/sdt.h -index 97766e710..352b4ee25 100644 +index ca0162b..f96e0ee 100644 --- a/includes/sys/sdt.h +++ b/includes/sys/sdt.h -@@ -230,12 +230,10 @@ __extension__ extern unsigned long long __sdt_unsp; +@@ -241,12 +241,10 @@ __extension__ extern unsigned long long __sdt_unsp; nice with code in COMDAT sections, which comes up in C++ code. Without that assembler support, some combinations of probe placements in certain kinds of C++ code may produce link-time errors. */ @@ -27,5 +23,5 @@ index 97766e710..352b4ee25 100644 -# define _SDT_ASM_AUTOGROUP "" -#endif - #define _SDT_ASM_BODY(provider, name, pack_args, args) \ - _SDT_ASM_1(990: _SDT_NOP) \ + #define _SDT_DEF_MACROS \ + _SDT_ASM_1(.altmacro) \ diff --git a/doc/README.md b/doc/README.md index 33f71f4807..c570432aa4 100644 --- a/doc/README.md +++ b/doc/README.md @@ -53,7 +53,6 @@ The Bitcoin repo's [root README](/README.md) contains relevant information on th - [Developer Notes](developer-notes.md) - [Productivity Notes](productivity.md) -- [Release Notes](release-notes.md) - [Release Process](release-process.md) - [Source Code Documentation (External Link)](https://doxygen.bitcoincore.org/) - [Translation Process](translation_process.md) @@ -64,6 +63,7 @@ The Bitcoin repo's [root README](/README.md) contains relevant information on th - [BIPS](bips.md) - [Dnsseed Policy](dnsseed-policy.md) - [Benchmarking](benchmarking.md) +- [Internal Design Docs](design/) ### Resources * Discuss on the [BitcoinTalk](https://bitcointalk.org/) forums, in the [Development & Technical Discussion board](https://bitcointalk.org/index.php?board=6.0). @@ -71,7 +71,6 @@ The Bitcoin repo's [root README](/README.md) contains relevant information on th ### Miscellaneous - [Assets Attribution](assets-attribution.md) -- [Assumeutxo design](assumeutxo.md) - [bitcoin.conf Configuration File](bitcoin-conf.md) - [CJDNS Support](cjdns.md) - [Files](files.md) diff --git a/doc/REST-interface.md b/doc/REST-interface.md index 4b46f29153..265b74ee9a 100644 --- a/doc/REST-interface.md +++ b/doc/REST-interface.md @@ -31,6 +31,7 @@ Supported API `GET /rest/tx/<TX-HASH>.<bin|hex|json>` Given a transaction hash: returns a transaction in binary, hex-encoded binary, or JSON formats. +Responds with 404 if the transaction doesn't exist. By default, this endpoint will only search the mempool. To query for a confirmed transaction, enable the transaction index via "txindex=1" command line / configuration option. @@ -76,6 +77,7 @@ Responds with 404 if the block doesn't exist. `GET /rest/blockhashbyheight/<HEIGHT>.<bin|hex|json>` Given a height: returns hash of block in best-block-chain at height provided. +Responds with 404 if block not found. #### Chaininfos `GET /rest/chaininfo.json` diff --git a/doc/bips.md b/doc/bips.md index 0f3f61daf1..25381818e4 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 **v23.0**): +BIPs that are implemented by Bitcoin Core (up-to-date up to **v24.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)). @@ -58,6 +58,7 @@ BIPs that are implemented by Bitcoin Core (up-to-date up to **v23.0**): with mainnet activation as of **v0.21.1** ([PR 21377](https://github.com/bitcoin/bitcoin/pull/21377), [PR 21686](https://github.com/bitcoin/bitcoin/pull/21686)). * [`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)). +* [`BIP 371`](https://github.com/bitcoin/bips/blob/master/bip-0371.mediawiki): Taproot fields for PSBT as of **v24.0** ([PR 22558](https://github.com/bitcoin/bitcoin/pull/22558)). * [`BIP 380`](https://github.com/bitcoin/bips/blob/master/bip-0380.mediawiki) [`381`](https://github.com/bitcoin/bips/blob/master/bip-0381.mediawiki) [`382`](https://github.com/bitcoin/bips/blob/master/bip-0382.mediawiki) diff --git a/doc/bitcoin-conf.md b/doc/bitcoin-conf.md index acd767f234..1ebfb4c1de 100644 --- a/doc/bitcoin-conf.md +++ b/doc/bitcoin-conf.md @@ -6,6 +6,8 @@ All command-line options (except for `-?`, `-help`, `-version` and `-conf`) may Changes to the configuration file while `bitcoind` or `bitcoin-qt` is running only take effect after restarting. +Users should never make any configuration changes which they do not understand. Furthermore, users should always be wary of accepting any configuration changes provided to them by another source (even if they believe that they do understand them). + ## Configuration File Format The configuration file is a plain text file and consists of `option=value` entries, one per line. Leading and trailing whitespaces are removed. diff --git a/doc/build-netbsd.md b/doc/build-netbsd.md index 9cec201faf..0f05cdcba7 100644 --- a/doc/build-netbsd.md +++ b/doc/build-netbsd.md @@ -1,92 +1,116 @@ -NetBSD Build Guide -====================== -**Updated for NetBSD [8.0](https://www.netbsd.org/releases/formal-8/NetBSD-8.0.html)** +# NetBSD Build Guide -This guide describes how to build bitcoind and command-line utilities on NetBSD. +Updated for NetBSD [9.2](https://netbsd.org/releases/formal-9/NetBSD-9.2.html). -This guide does not contain instructions for building the GUI. +This guide describes how to build bitcoind, command-line utilities, and GUI on NetBSD. -Preparation -------------- +## Preparation -You will need the following modules, which can be installed via pkgsrc or pkgin: +### 1. Install Required Dependencies + +Install the required dependencies the usual way you [install software on NetBSD](https://www.netbsd.org/docs/guide/en/chap-boot.html#chap-boot-pkgsrc). +The example commands below use `pkgin`. + +```bash +pkgin install autoconf automake libtool pkg-config git gmake boost libevent ``` -autoconf -automake -boost -git -gmake -libevent -libtool -pkg-config -python37 -git clone https://github.com/bitcoin/bitcoin.git +NetBSD currently ships with an older version of `gcc` than is needed to build. You should upgrade your `gcc` and then pass this new version to the configure script. + +For example, grab `gcc9`: +``` +pkgin install gcc9 +``` + +Then, when configuring, pass the following: +```bash +./configure + ... + CC="/usr/pkg/gcc9/bin/gcc" \ + CXX="/usr/pkg/gcc9/bin/g++" \ + ... ``` See [dependencies.md](dependencies.md) for a complete overview. -### Building Bitcoin Core +### 2. Clone Bitcoin Repo -**Important**: Use `gmake` (the non-GNU `make` will exit with an error). +Clone the Bitcoin Core repository to a directory. All build scripts and commands will run from this directory. -#### With descriptor wallet: +```bash +git clone https://github.com/bitcoin/bitcoin.git +``` + +### 3. Install Optional Dependencies + +#### Wallet Dependencies + +It is not necessary to build wallet functionality to run bitcoind or the GUI. + +###### Descriptor Wallet Support + +`sqlite3` is required to enable support for [descriptor wallets](https://github.com/bitcoin/bitcoin/blob/master/doc/descriptors.md). -The descriptor wallet uses `sqlite3`. You can install it using: ```bash pkgin install sqlite3 ``` +###### Legacy Wallet Support + +`db4` is required to enable support for legacy wallets. + ```bash -./autogen.sh -./configure --with-gui=no --without-bdb \ - CPPFLAGS="-I/usr/pkg/include" \ - LDFLAGS="-L/usr/pkg/lib" \ - BOOST_CPPFLAGS="-I/usr/pkg/include" \ - MAKE=gmake +pkgin install db4 ``` -#### With legacy wallet: - -BerkeleyDB is use for legacy wallet functionality. +#### GUI Dependencies -It is recommended to use Berkeley DB 4.8. You cannot use the BerkeleyDB library -from ports. -You can use [the installation script included in contrib/](/contrib/install_db4.sh) like so: +Bitcoin Core includes a GUI built with the cross-platform Qt Framework. To compile the GUI, we need to install `qt5`. ```bash -./contrib/install_db4.sh `pwd` +pkgin install qt5 ``` -from the root of the repository. Then set `BDB_PREFIX` for the next section: +The GUI can encode addresses in a QR Code. To build in QR support for the GUI, install `qrencode`. ```bash -export BDB_PREFIX="$PWD/db4" +pkgin install qrencode ``` +#### 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 -./autogen.sh -./configure --with-gui=no CPPFLAGS="-I/usr/pkg/include" \ - LDFLAGS="-L/usr/pkg/lib" \ - BOOST_CPPFLAGS="-I/usr/pkg/include" \ - BDB_LIBS="-L${BDB_PREFIX}/lib -ldb_cxx-4.8" \ - BDB_CFLAGS="-I${BDB_PREFIX}/include" \ - MAKE=gmake +pkgin install python37 ``` -#### Without wallet: +### Building Bitcoin Core + +**Note**: Use `gmake` (the non-GNU `make` will exit with an error). + + +### 1. Configuration + +There are many ways to configure Bitcoin Core. Here is an example that +explicitly disables the wallet and GUI: + ```bash ./autogen.sh -./configure --with-gui=no --disable-wallet \ +./configure --without-wallet --with-gui=no \ CPPFLAGS="-I/usr/pkg/include" \ - LDFLAGS="-L/usr/pkg/lib" \ - BOOST_CPPFLAGS="-I/usr/pkg/include" \ MAKE=gmake ``` +For a full list of configuration options, see the output of `./configure --help` + +### 2. Compile + Build and run the tests: + ```bash gmake # use "-j N" here for N parallel jobs -gmake check +gmake check # Run tests if Python 3 is available ``` diff --git a/doc/build-osx.md b/doc/build-osx.md index fdf0a9d414..f11ed97e09 100644 --- a/doc/build-osx.md +++ b/doc/build-osx.md @@ -96,14 +96,6 @@ Skip if you don't intend to use the GUI. brew install qt@5 ``` -Ensure that the `qt@5` package is installed, not the `qt` package. -If 'qt' is installed, the build process will fail. -if installed, remove the `qt` package with the following command: - -``` bash -brew uninstall qt -``` - 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). diff --git a/doc/build-unix.md b/doc/build-unix.md index f02729ee32..874015707a 100644 --- a/doc/build-unix.md +++ b/doc/build-unix.md @@ -277,42 +277,14 @@ A list of additional configure flags can be displayed with: Setup and Build Example: Arch Linux ----------------------------------- -This example lists the steps necessary to setup and build a command line only, non-wallet distribution of the latest changes on Arch Linux: +This example lists the steps necessary to setup and build a command line only distribution of the latest changes on Arch Linux: - pacman -S git base-devel boost libevent python + pacman --sync --needed autoconf automake boost gcc git libevent libtool make pkgconf python sqlite git clone https://github.com/bitcoin/bitcoin.git cd bitcoin/ ./autogen.sh - ./configure --disable-wallet --without-gui --without-miniupnpc + ./configure make check + ./src/bitcoind -Note: -Enabling wallet support requires either compiling against a Berkeley DB newer than 4.8 (package `db`) using `--with-incompatible-bdb`, -or building and depending on a local version of Berkeley DB 4.8. The readily available Arch Linux packages are currently built using -`--with-incompatible-bdb` according to the [PKGBUILD](https://github.com/archlinux/svntogit-community/blob/packages/bitcoin/trunk/PKGBUILD). -As mentioned above, when maintaining portability of the wallet between the standard Bitcoin Core distributions and independently built -node software is desired, Berkeley DB 4.8 must be used. - - -ARM Cross-compilation -------------------- -These steps can be performed on, for example, an Ubuntu VM. The depends system -will also work on other Linux distributions, however the commands for -installing the toolchain will be different. - -Make sure you install the build requirements mentioned above. -Then, install the toolchain and curl: - - sudo apt-get install g++-arm-linux-gnueabihf curl - -To build executables for ARM: - - cd depends - make HOST=arm-linux-gnueabihf NO_QT=1 - cd .. - ./autogen.sh - CONFIG_SITE=$PWD/depends/arm-linux-gnueabihf/share/config.site ./configure --enable-reduce-exports LDFLAGS=-static-libstdc++ - make - - -For further documentation on the depends system see [README.md](../depends/README.md) in the depends directory. +If you intend to work with legacy Berkeley DB wallets, see [Berkeley DB](#berkeley-db) section. diff --git a/doc/dependencies.md b/doc/dependencies.md index 697d432520..ef8faff06c 100644 --- a/doc/dependencies.md +++ b/doc/dependencies.md @@ -17,7 +17,7 @@ You can find installation instructions in the `build-*.md` file for your platfor | Dependency | Releases | Version used | Minimum required | Runtime | | --- | --- | --- | --- | --- | -| [Boost](../depends/packages/boost.mk) | [link](https://www.boost.org/users/download/) | [1.77.0](https://github.com/bitcoin/bitcoin/pull/24383) | [1.64.0](https://github.com/bitcoin/bitcoin/pull/22320) | No | +| [Boost](../depends/packages/boost.mk) | [link](https://www.boost.org/users/download/) | [1.80.0](https://github.com/bitcoin/bitcoin/pull/25873) | [1.64.0](https://github.com/bitcoin/bitcoin/pull/22320) | No | | [libevent](../depends/packages/libevent.mk) | [link](https://github.com/libevent/libevent/releases) | [2.1.12-stable](https://github.com/bitcoin/bitcoin/pull/21991) | [2.1.8](https://github.com/bitcoin/bitcoin/pull/24681) | No | | glibc | [link](https://www.gnu.org/software/libc/) | N/A | [2.18](https://github.com/bitcoin/bitcoin/pull/23511) | Yes | | Linux Kernel | [link](https://www.kernel.org/) | N/A | 3.2.0 | Yes | @@ -30,12 +30,12 @@ You can find installation instructions in the `build-*.md` file for your platfor | [Fontconfig](../depends/packages/fontconfig.mk) | [link](https://www.freedesktop.org/wiki/Software/fontconfig/) | [2.12.6](https://github.com/bitcoin/bitcoin/pull/23495) | 2.6 | Yes | | [FreeType](../depends/packages/freetype.mk) | [link](https://freetype.org) | [2.11.0](https://github.com/bitcoin/bitcoin/commit/01544dd78ccc0b0474571da854e27adef97137fb) | 2.3.0 | Yes | | [qrencode](../depends/packages/qrencode.mk) | [link](https://fukuchi.org/works/qrencode/) | [3.4.4](https://github.com/bitcoin/bitcoin/pull/6373) | | No | -| [Qt](../depends/packages/qt.mk) | [link](https://download.qt.io/official_releases/qt/) | [5.15.3](https://github.com/bitcoin/bitcoin/pull/24668) | [5.11.3](https://github.com/bitcoin/bitcoin/pull/24132) | No | +| [Qt](../depends/packages/qt.mk) | [link](https://download.qt.io/official_releases/qt/) | [5.15.5](https://github.com/bitcoin/bitcoin/pull/25719) | [5.11.3](https://github.com/bitcoin/bitcoin/pull/24132) | No | ### Networking | Dependency | Releases | Version used | Minimum required | Runtime | | --- | --- | --- | --- | --- | -| [libnatpmp](../depends/packages/libnatpmp.mk) | [link](https://github.com/miniupnp/libnatpmp/) | commit [4536032...](https://github.com/bitcoin/bitcoin/pull/21209) | | No | +| [libnatpmp](../depends/packages/libnatpmp.mk) | [link](https://github.com/miniupnp/libnatpmp/) | commit [07004b9...](https://github.com/bitcoin/bitcoin/pull/25917) | | No | | [MiniUPnPc](../depends/packages/miniupnpc.mk) | [link](https://miniupnp.tuxfamily.org/) | [2.2.2](https://github.com/bitcoin/bitcoin/pull/20421) | 1.9 | No | ### Notifications diff --git a/doc/descriptors.md b/doc/descriptors.md index ab2face4f0..69a5ee2715 100644 --- a/doc/descriptors.md +++ b/doc/descriptors.md @@ -11,13 +11,16 @@ Supporting RPCs are: addresses. - `listunspent` outputs a specialized descriptor for the reported unspent outputs. - `getaddressinfo` outputs a descriptor for solvable addresses (since v0.18). -- `importmulti` takes as input descriptors to import into the wallet +- `importmulti` takes as input descriptors to import into a legacy wallet (since v0.18). - `generatetodescriptor` takes as input a descriptor and generates coins to it (`regtest` only, since v0.19). - `utxoupdatepsbt` takes as input descriptors to add information to the psbt (since v0.19). -- `createmultisig` and `addmultisigaddress` return descriptors as well (since v0.20) +- `createmultisig` and `addmultisigaddress` return descriptors as well (since v0.20). +- `importdescriptors` takes as input descriptors to import into a descriptor wallet + (since v0.21). +- `listdescriptors` outputs descriptors imported into a descriptor wallet (since v22). This document describes the language. For the specifics on usage, see the RPC documentation for the functions mentioned above. @@ -77,6 +80,7 @@ Descriptors consist of several types of expressions. The top level expression is - `tr(KEY)` or `tr(KEY,TREE)` (top level only): P2TR output with the specified key as internal key, and optionally a tree of script paths. - `addr(ADDR)` (top level only): the script which ADDR expands to. - `raw(HEX)` (top level only): the script whose hex encoding is HEX. +- `rawtr(KEY)` (top level only): P2TR output with the specified key as output key. NOTE: while it's possible to use this to construct wallets, it has several downsides, like being unable to prove no hidden script path exists. Use at your own risk. `KEY` expressions: - Optionally, key origin information, consisting of: @@ -87,7 +91,7 @@ Descriptors consist of several types of expressions. The top level expression is - Followed by the actual key, which is either: - Hex encoded public keys (either 66 characters starting with `02` or `03` for a compressed pubkey, or 130 characters starting with `04` for an uncompressed pubkey). - Inside `wpkh` and `wsh`, only compressed public keys are permitted. - - Inside `tr`, x-only pubkeys are also permitted (64 hex characters). + - Inside `tr` and `rawtr`, x-only pubkeys are also permitted (64 hex characters). - [WIF](https://en.bitcoin.it/wiki/Wallet_import_format) encoded private keys may be specified instead of the corresponding public key, with the same meaning. - `xpub` encoded extended public key or `xprv` encoded extended private key (as defined in [BIP 32](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki)). - Followed by zero or more `/NUM` unhardened and `/NUM'` hardened BIP32 derivation steps. diff --git a/doc/assumeutxo.md b/doc/design/assumeutxo.md index 2726cf779b..c353c78ff8 100644 --- a/doc/assumeutxo.md +++ b/doc/design/assumeutxo.md @@ -41,7 +41,7 @@ be of use. Chainstate within the system goes through a number of phases when UTXO snapshots are used, as managed by `ChainstateManager`. At various points there can be multiple -`CChainState` objects in existence to facilitate both maintaining the network tip and +`Chainstate` objects in existence to facilitate both maintaining the network tip and performing historical validation of the assumed-valid chain. It is worth noting that though there are multiple separate chainstates, those @@ -53,7 +53,7 @@ data. ### "Normal" operation via initial block download -`ChainstateManager` manages a single CChainState object, for which +`ChainstateManager` manages a single Chainstate object, for which `m_snapshot_blockhash` is null. This chainstate is (maybe obviously) considered active. This is the "traditional" mode of operation for bitcoind. diff --git a/doc/design/libraries.md b/doc/design/libraries.md new file mode 100644 index 0000000000..75f8d60ba0 --- /dev/null +++ b/doc/design/libraries.md @@ -0,0 +1,104 @@ +# Libraries + +| Name | Description | +|--------------------------|-------------| +| *libbitcoin_cli* | RPC client functionality used by *bitcoin-cli* executable | +| *libbitcoin_common* | Home for common functionality shared by different executables and libraries. Similar to *libbitcoin_util*, but higher-level (see [Dependencies](#dependencies)). | +| *libbitcoin_consensus* | Stable, backwards-compatible consensus functionality used by *libbitcoin_node* and *libbitcoin_wallet* and also exposed as a [shared library](../shared-libraries.md). | +| *libbitcoinconsensus* | Shared library build of static *libbitcoin_consensus* library | +| *libbitcoin_kernel* | Consensus engine and support library used for validation by *libbitcoin_node* and also exposed as a [shared library](../shared-libraries.md). | +| *libbitcoinqt* | GUI functionality used by *bitcoin-qt* and *bitcoin-gui* executables | +| *libbitcoin_ipc* | IPC functionality used by *bitcoin-node*, *bitcoin-wallet*, *bitcoin-gui* executables to communicate when [`--enable-multiprocess`](multiprocess.md) is used. | +| *libbitcoin_node* | P2P and RPC server functionality used by *bitcoind* and *bitcoin-qt* executables. | +| *libbitcoin_util* | Home for common functionality shared by different executables and libraries. Similar to *libbitcoin_common*, but lower-level (see [Dependencies](#dependencies)). | +| *libbitcoin_wallet* | Wallet functionality used by *bitcoind* and *bitcoin-wallet* executables. | +| *libbitcoin_wallet_tool* | Lower-level wallet functionality used by *bitcoin-wallet* executable. | +| *libbitcoin_zmq* | [ZeroMQ](../zmq.md) functionality used by *bitcoind* and *bitcoin-qt* executables. | + +## Conventions + +- Most libraries are internal libraries and have APIs which are completely unstable! There are few or no restrictions on backwards compatibility or rules about external dependencies. Exceptions are *libbitcoin_consensus* and *libbitcoin_kernel* which have external interfaces documented at [../shared-libraries.md](../shared-libraries.md). + +- Generally each library should have a corresponding source directory and namespace. Source code organization is a work in progress, so it is true that some namespaces are applied inconsistently, and if you look at [`libbitcoin_*_SOURCES`](../../src/Makefile.am) lists you can see that many libraries pull in files from outside their source directory. But when working with libraries, it is good to follow a consistent pattern like: + + - *libbitcoin_node* code lives in `src/node/` in the `node::` namespace + - *libbitcoin_wallet* code lives in `src/wallet/` in the `wallet::` namespace + - *libbitcoin_ipc* code lives in `src/ipc/` in the `ipc::` namespace + - *libbitcoin_util* code lives in `src/util/` in the `util::` namespace + - *libbitcoin_consensus* code lives in `src/consensus/` in the `Consensus::` namespace + +## Dependencies + +- Libraries should minimize what other libraries they depend on, and only reference symbols following the arrows shown in the dependency graph below: + +<table><tr><td> + +```mermaid + +%%{ init : { "flowchart" : { "curve" : "linear" }}}%% + +graph TD; + +bitcoin-cli[bitcoin-cli]-->libbitcoin_cli; + +bitcoind[bitcoind]-->libbitcoin_node; +bitcoind[bitcoind]-->libbitcoin_wallet; + +bitcoin-qt[bitcoin-qt]-->libbitcoin_node; +bitcoin-qt[bitcoin-qt]-->libbitcoinqt; +bitcoin-qt[bitcoin-qt]-->libbitcoin_wallet; + +bitcoin-wallet[bitcoin-wallet]-->libbitcoin_wallet; +bitcoin-wallet[bitcoin-wallet]-->libbitcoin_wallet_tool; + +libbitcoin_cli-->libbitcoin_common; +libbitcoin_cli-->libbitcoin_util; + +libbitcoin_common-->libbitcoin_util; +libbitcoin_common-->libbitcoin_consensus; + +libbitcoin_kernel-->libbitcoin_consensus; +libbitcoin_kernel-->libbitcoin_util; + +libbitcoin_node-->libbitcoin_common; +libbitcoin_node-->libbitcoin_consensus; +libbitcoin_node-->libbitcoin_kernel; +libbitcoin_node-->libbitcoin_util; + +libbitcoinqt-->libbitcoin_common; +libbitcoinqt-->libbitcoin_util; + +libbitcoin_wallet-->libbitcoin_common; +libbitcoin_wallet-->libbitcoin_util; + +libbitcoin_wallet_tool-->libbitcoin_util; +libbitcoin_wallet_tool-->libbitcoin_wallet; + +classDef bold stroke-width:2px, font-weight:bold, font-size: smaller; +class bitcoin-qt,bitcoind,bitcoin-cli,bitcoin-wallet bold +``` +</td></tr><tr><td> + +**Dependency graph**. Arrows show linker symbol dependencies. *Consensus* lib depends on nothing. *Util* lib is depended on by everything. *Kernel* lib depends only on consensus and util. + +</td></tr></table> + +- The graph shows what _linker symbols_ (functions and variables) from each library other libraries can call and reference directly, but it is not a call graph. For example, there is no arrow connecting *libbitcoin_wallet* and *libbitcoin_node* libraries, because these libraries are intended to be modular and not depend on each other's internal implementation details. But wallet code still is still able to call node code indirectly through the `interfaces::Chain` abstract class in [`interfaces/chain.h`](../../src/interfaces/chain.h) and node code calls wallet code through the `interfaces::ChainClient` and `interfaces::Chain::Notifications` abstract classes in the same file. In general, defining abstract classes in [`src/interfaces/`](../../src/interfaces/) can be a convenient way of avoiding unwanted direct dependencies or circular dependencies between libraries. + +- *libbitcoin_consensus* should be a standalone dependency that any library can depend on, and it should not depend on any other libraries itself. + +- *libbitcoin_util* should also be a standalone dependency that any library can depend on, and it should not depend on other internal libraries. + +- *libbitcoin_common* should serve a similar function as *libbitcoin_util* and be a place for miscellaneous code used by various daemon, GUI, and CLI applications and libraries to live. It should not depend on anything other than *libbitcoin_util* and *libbitcoin_consensus*. The boundary between _util_ and _common_ is a little fuzzy but historically _util_ has been used for more generic, lower-level things like parsing hex, and _common_ has been used for bitcoin-specific, higher-level things like parsing base58. The difference between util and common is mostly important because *libbitcoin_kernel* is not supposed to depend on *libbitcoin_common*, only *libbitcoin_util*. In general, if it is ever unclear whether it is better to add code to *util* or *common*, it is probably better to add it to *common* unless it is very generically useful or useful particularly to include in the kernel. + + +- *libbitcoin_kernel* should only depend on *libbitcoin_util* and *libbitcoin_consensus*. + +- The only thing that should depend on *libbitcoin_kernel* internally should be *libbitcoin_node*. GUI and wallet libraries *libbitcoinqt* and *libbitcoin_wallet* in particular should not depend on *libbitcoin_kernel* and the unneeded functionality it would pull in, like block validation. To the extent that GUI and wallet code need scripting and signing functionality, they should be get able it from *libbitcoin_consensus*, *libbitcoin_common*, and *libbitcoin_util*, instead of *libbitcoin_kernel*. + +- GUI, node, and wallet code internal implementations should all be independent of each other, and the *libbitcoinqt*, *libbitcoin_node*, *libbitcoin_wallet* libraries should never reference each other's symbols. They should only call each other through [`src/interfaces/`](`../../src/interfaces/`) abstract interfaces. + +## Work in progress + +- Validation code is moving from *libbitcoin_node* to *libbitcoin_kernel* as part of [The libbitcoinkernel Project #24303](https://github.com/bitcoin/bitcoin/issues/24303) +- Source code organization is discussed in general in [Library source code organization #15732](https://github.com/bitcoin/bitcoin/issues/15732) diff --git a/doc/multiprocess.md b/doc/design/multiprocess.md index e3f389a6d3..e3f389a6d3 100644 --- a/doc/multiprocess.md +++ b/doc/design/multiprocess.md diff --git a/doc/developer-notes.md b/doc/developer-notes.md index bd385efdfd..00c68911ef 100644 --- a/doc/developer-notes.md +++ b/doc/developer-notes.md @@ -386,8 +386,10 @@ Run configure with the `--enable-gprof` option, then make. If the code is behaving strangely, take a look in the `debug.log` file in the data directory; error and debugging messages are written there. -The `-debug=...` command-line option controls debugging; running with just `-debug` or `-debug=1` will turn -on all categories (and give you a very large `debug.log` file). +Debug logging can be enabled on startup with the `-debug` and `-loglevel` +configuration options and toggled while bitcoind is running with the `logging` +RPC. For instance, launching bitcoind with `-debug` or `-debug=1` will turn on +all log categories and `-loglevel=trace` will turn on all log severity levels. The Qt code routes `qDebug()` output to `debug.log` under category "qt": run with `-debug=qt` to see it. @@ -603,10 +605,10 @@ Threads : Started from `main()` in `bitcoind.cpp`. Responsible for starting up and shutting down the application. -- [ThreadImport (`b-loadblk`)](https://doxygen.bitcoincore.org/init_8cpp.html#ae9e290a0e829ec0198518de2eda579d1) +- [ThreadImport (`b-loadblk`)](https://doxygen.bitcoincore.org/namespacenode.html#ab4305679079866f0f420f7dbf278381d) : Loads blocks from `blk*.dat` files or `-loadblock=<file>` on startup. -- [ThreadScriptCheck (`b-scriptch.x`)](https://doxygen.bitcoincore.org/validation_8cpp.html#a925a33e7952a157922b0bbb8dab29a20) +- [CCheckQueue::Loop (`b-scriptch.x`)](https://doxygen.bitcoincore.org/class_c_check_queue.html#a6e7fa51d3a25e7cb65446d4b50e6a987) : Parallel script validation threads for transactions in blocks. - [ThreadHTTP (`b-http`)](https://doxygen.bitcoincore.org/httpserver_8cpp.html#abb9f6ea8819672bd9a62d3695070709c) @@ -622,7 +624,7 @@ Threads : Does asynchronous background tasks like dumping wallet contents, dumping addrman and running asynchronous validationinterface callbacks. -- [TorControlThread (`b-torcontrol`)](https://doxygen.bitcoincore.org/torcontrol_8cpp.html#a4faed3692d57a0d7bdbecf3b37f72de0) +- [TorControlThread (`b-torcontrol`)](https://doxygen.bitcoincore.org/torcontrol_8cpp.html#a52a3efff23634500bb42c6474f306091) : Libevent thread for tor connections. - Net threads: @@ -634,7 +636,7 @@ Threads - [ThreadDNSAddressSeed (`b-dnsseed`)](https://doxygen.bitcoincore.org/class_c_connman.html#aa7c6970ed98a4a7bafbc071d24897d13) : Loads addresses of peers from the DNS. - - [ThreadMapPort (`b-upnp`)](https://doxygen.bitcoincore.org/net_8cpp.html#a63f82a71c4169290c2db1651a9bbe249) + - ThreadMapPort (`b-mapport`) : Universal plug-and-play startup/shutdown. - [ThreadSocketHandler (`b-net`)](https://doxygen.bitcoincore.org/class_c_connman.html#a765597cbfe99c083d8fa3d61bb464e34) @@ -646,6 +648,9 @@ Threads - [ThreadOpenConnections (`b-opencon`)](https://doxygen.bitcoincore.org/class_c_connman.html#a55e9feafc3bab78e5c9d408c207faa45) : Initiates new connections to peers. + - [ThreadI2PAcceptIncoming (`b-i2paccept`)](https://doxygen.bitcoincore.org/class_c_connman.html#a57787b4f9ac847d24065fbb0dd6e70f8) + : Listens for and accepts incoming I2P connections through the I2P SAM proxy. + Ignoring IDE/editor files -------------------------- @@ -921,14 +926,19 @@ Threads and synchronization - Prefer `Mutex` type to `RecursiveMutex` one. - Consistently use [Clang Thread Safety Analysis](https://clang.llvm.org/docs/ThreadSafetyAnalysis.html) annotations to - get compile-time warnings about potential race conditions in code. Combine annotations in function declarations with - run-time asserts in function definitions: + get compile-time warnings about potential race conditions or deadlocks in code. - In functions that are declared separately from where they are defined, the thread safety annotations should be added exclusively to the function declaration. Annotations on the definition could lead to false positives (lack of compile failure) at call sites between the two. + - Prefer locks that are in a class rather than global, and that are + internal to a class (private or protected) rather than public. + + - Combine annotations in function declarations with run-time asserts in + function definitions: + ```C++ // txmempool.h class CTxMemPool @@ -952,21 +962,37 @@ void CTxMemPool::UpdateTransactionsFromBlock(...) ```C++ // validation.h -class ChainstateManager +class Chainstate { +protected: + ... + Mutex m_chainstate_mutex; + ... public: ... - bool ProcessNewBlock(...) LOCKS_EXCLUDED(::cs_main); + bool ActivateBestChain( + BlockValidationState& state, + std::shared_ptr<const CBlock> pblock = nullptr) + EXCLUSIVE_LOCKS_REQUIRED(!m_chainstate_mutex) + LOCKS_EXCLUDED(::cs_main); + ... + bool PreciousBlock(BlockValidationState& state, CBlockIndex* pindex) + EXCLUSIVE_LOCKS_REQUIRED(!m_chainstate_mutex) + LOCKS_EXCLUDED(::cs_main); ... } // validation.cpp -bool ChainstateManager::ProcessNewBlock(...) +bool Chainstate::PreciousBlock(BlockValidationState& state, CBlockIndex* pindex) { + AssertLockNotHeld(m_chainstate_mutex); AssertLockNotHeld(::cs_main); - ... - LOCK(::cs_main); - ... + { + LOCK(cs_main); + ... + } + + return ActivateBestChain(state, std::shared_ptr<const CBlock>()); } ``` @@ -1137,10 +1163,6 @@ Current subtrees include: - src/crypto/ctaes - Upstream at https://github.com/bitcoin-core/ctaes ; maintained by Core contributors. -- src/univalue - - Subtree at https://github.com/bitcoin-core/univalue-subtree ; maintained by Core contributors. - - Deviates from upstream https://github.com/jgarzik/univalue. - - src/minisketch - Upstream at https://github.com/sipa/minisketch ; maintained by Core contributors. diff --git a/doc/i2p.md b/doc/i2p.md index 39f65c4e5f..1599c2fe0f 100644 --- a/doc/i2p.md +++ b/doc/i2p.md @@ -47,9 +47,26 @@ In a typical situation, this suffices: bitcoind -i2psam=127.0.0.1:7656 ``` -The first time Bitcoin Core connects to the I2P router, its I2P address (and -corresponding private key) will be automatically generated and saved in a file -named `i2p_private_key` in the Bitcoin Core data directory. +The first time Bitcoin Core connects to the I2P router, if +`-i2pacceptincoming=1`, then it will automatically generate a persistent I2P +address and its corresponding private key. The private key will be saved in a +file named `i2p_private_key` in the Bitcoin Core data directory. The persistent +I2P address is used for accepting incoming connections and for making outgoing +connections if `-i2pacceptincoming=1`. If `-i2pacceptincoming=0` then only +outbound I2P connections are made and a different transient I2P address is used +for each connection to improve privacy. + +## Persistent vs transient I2P addresses + +In I2P connections, the connection receiver sees the I2P address of the +connection initiator. This is unlike the Tor network where the recipient does +not know who is connecting to them and can't tell if two connections are from +the same peer or not. + +If an I2P node is not accepting incoming connections, then Bitcoin Core uses +random, one-time, transient I2P addresses for itself for outbound connections +to make it harder to discriminate, fingerprint or analyze it based on its I2P +address. ## Additional configuration options related to I2P @@ -85,7 +102,8 @@ one of the networks has issues. ## I2P-related information in Bitcoin Core -There are several ways to see your I2P address in Bitcoin Core: +There are several ways to see your I2P address in Bitcoin Core if accepting +incoming I2P connections (`-i2pacceptincoming`): - in the "Local addresses" output of CLI `-netinfo` - in the "localaddresses" output of RPC `getnetworkinfo` - in the debug log (grep for `AddLocal`; the I2P address ends in `.b32.i2p`) diff --git a/doc/managing-wallets.md b/doc/managing-wallets.md index 6c1e13c503..366d7ec54b 100644 --- a/doc/managing-wallets.md +++ b/doc/managing-wallets.md @@ -120,4 +120,29 @@ After that, `getwalletinfo` can be used to check if the wallet has been fully re $ bitcoin-cli -rpcwallet="restored-wallet" getwalletinfo ``` -The restored wallet can also be loaded in the GUI via `File` ->`Open wallet`.
\ No newline at end of file +The restored wallet can also be loaded in the GUI via `File` ->`Open wallet`. + +## Migrating Legacy Wallets to Descriptor Wallets + +Legacy wallets (traditional non-descriptor wallets) can be migrated to become Descriptor wallets +through the use of the `migratewallet` RPC. Migrated wallets will have all of their addresses and private keys added to +a newly created Descriptor wallet that has the same name as the original wallet. Because Descriptor +wallets do not support having private keys and watch-only scripts, there may be up to two +additional wallets created after migration. In addition to a descriptor wallet of the same name, +there may also be a wallet named `<name>_watchonly` and `<name>_solvables`. `<name>_watchonly` +contains all of the watchonly scripts. `<name>_solvables` contains any scripts which the wallet +knows but is not watching the corresponding P2(W)SH scripts. + +Migrated wallets will also generate new addresses differently. While the same BIP 32 seed will be +used, the BIP 44, 49, 84, and 86 standard derivation paths will be used. After migrating, a new +backup of the wallet(s) will need to be created. + +Given that there is an extremely large number of possible configurations for the scripts that +Legacy wallets can know about, be watching for, and be able to sign for, `migratewallet` only +makes a best effort attempt to capture all of these things into Descriptor wallets. There may be +unforeseen configurations which result in some scripts being excluded. If a migration fails +unexpectedly or otherwise misses any scripts, please create an issue on GitHub. A backup of the +original wallet can be found in the wallet directory with the name `<name>-<timestamp>.legacy.bak`. + +The backup can be restored using the `restorewallet` command as discussed in the +[Restoring the Wallet From a Backup](#16-restoring-the-wallet-from-a-backup) section diff --git a/doc/policy/README.md b/doc/policy/README.md index 6e8686365d..27536407e7 100644 --- a/doc/policy/README.md +++ b/doc/policy/README.md @@ -3,7 +3,7 @@ **Policy** (Mempool or Transaction Relay Policy) is the node's set of validation rules, in addition to consensus, enforced for unconfirmed transactions before submitting them to the mempool. These rules are local to the node and configurable (e.g. `-minrelaytxfee`, `-limitancestorsize`, -`-incrementalRelayFee`). Policy may include restrictions on the transaction itself, the transaction +`-incrementalrelayfee`). Policy may include restrictions on the transaction itself, the transaction in relation to the current chain tip, and the transaction in relation to the node's mempool contents. Policy is *not* applied to transactions in blocks. diff --git a/doc/policy/mempool-replacements.md b/doc/policy/mempool-replacements.md index 3e844f8d7b..b3c4239b73 100644 --- a/doc/policy/mempool-replacements.md +++ b/doc/policy/mempool-replacements.md @@ -15,6 +15,8 @@ other consensus and policy rules, each of the following conditions are met: *Rationale*: See [BIP125 explanation](https://github.com/bitcoin/bips/blob/master/bip-0125.mediawiki#motivation). + Use the (`-mempoolfullrbf`) configuration option to allow transaction replacement without enforcement of the + opt-in signaling rule. 2. The replacement transaction only include an unconfirmed input if that input was included in one of the directly conflicting transactions. An unconfirmed input spends an output from a @@ -51,6 +53,13 @@ other consensus and policy rules, each of the following conditions are met: significant portions of the node's mempool using replacements with multiple directly conflicting transactions, each with large descendant sets. +6. The replacement transaction's feerate is greater than the feerates of all directly conflicting + transactions. + + *Rationale*: This rule was originally intended to ensure that the replacement transaction is + preferable for block-inclusion, compared to what would be removed from the mempool. This rule + predates ancestor feerate-based transaction selection. + This set of rules is similar but distinct from BIP125. ## History @@ -62,8 +71,11 @@ This set of rules is similar but distinct from BIP125. Bitcoin Core implementation. * The incremental relay feerate used to calculate the required additional fees is distinct from - `minRelayTxFee` and configurable using `-incrementalrelayfee` + `-minrelaytxfee` and configurable using `-incrementalrelayfee` ([PR #9380](https://github.com/bitcoin/bitcoin/pull/9380)). * RBF enabled by default in the wallet GUI as of **v0.18.1** ([PR #11605](https://github.com/bitcoin/bitcoin/pull/11605)). + +* Full replace-by-fee enabled as a configurable mempool policy as of **v24.0** ([PR + #25353](https://github.com/bitcoin/bitcoin/pull/25353)). diff --git a/doc/policy/packages.md b/doc/policy/packages.md index f2a3d6517e..274854ddf9 100644 --- a/doc/policy/packages.md +++ b/doc/policy/packages.md @@ -33,7 +33,7 @@ The following rules are enforced for all packages: * Packages cannot have conflicting transactions, i.e. no two transactions in a package can spend the same inputs. Packages cannot have duplicate transactions. (#20833) -* No transaction in a package can conflict with a mempool transaction. BIP125 Replace By Fee is +* No transaction in a package can conflict with a mempool transaction. Replace By Fee is currently disabled for packages. (#20833) - Package RBF may be enabled in the future. @@ -81,7 +81,7 @@ If any transactions in the package are already in the mempool, they are not subm ("deduplicated") and are thus excluded from this calculation. To meet the two feerate requirements of a mempool, i.e., the pre-configured minimum relay feerate -(`minRelayTxFee`) and the dynamic mempool minimum feerate, the total package feerate is used instead +(`-minrelaytxfee`) and the dynamic mempool minimum feerate, the total package feerate is used instead of the individual feerate. The individual transactions are allowed to be below the feerate requirements if the package meets the feerate requirements. For example, the parent(s) in the package can pay no fees but be paid for by the child. diff --git a/doc/productivity.md b/doc/productivity.md index a01c6f545d..e9b7bc270c 100644 --- a/doc/productivity.md +++ b/doc/productivity.md @@ -9,6 +9,7 @@ Table of Contents * [Disable features with `./configure`](#disable-features-with-configure) * [Make use of your threads with `make -j`](#make-use-of-your-threads-with-make--j) * [Only build what you need](#only-build-what-you-need) + * [Compile on multiple machines](#compile-on-multiple-machines) * [Multiple working directories with `git worktrees`](#multiple-working-directories-with-git-worktrees) * [Interactive "dummy rebases" for fixups and execs with `git merge-base`](#interactive-dummy-rebases-for-fixups-and-execs-with-git-merge-base) * [Writing code](#writing-code) @@ -81,6 +82,10 @@ make -C src bitcoin_bench (You can and should combine this with `-j`, as above, for a parallel build.) +### Compile on multiple machines + +If you have more than one computer at your disposal, you can use [distcc](https://www.distcc.org) to speed up compilation. This is easiest when all computers run the same operating system and compiler version. + ### Multiple working directories with `git worktrees` If you work with multiple branches or multiple copies of the repository, you should try `git worktrees`. diff --git a/doc/release-notes-24098.md b/doc/release-notes-24098.md deleted file mode 100644 index 79e047e9a5..0000000000 --- a/doc/release-notes-24098.md +++ /dev/null @@ -1,22 +0,0 @@ -Notable changes -=============== - -Updated REST APIs ------------------ - -- The `/headers/` and `/blockfilterheaders/` endpoints have been updated to use - a query parameter instead of path parameter to specify the result count. The - count parameter is now optional, and defaults to 5 for both endpoints. The old - endpoints are still functional, and have no documented behaviour change. - - For `/headers`, use - `GET /rest/headers/<BLOCK-HASH>.<bin|hex|json>?count=<COUNT=5>` - instead of - `GET /rest/headers/<COUNT>/<BLOCK-HASH>.<bin|hex|json>` (deprecated) - - For `/blockfilterheaders/`, use - `GET /rest/blockfilterheaders/<FILTERTYPE>/<BLOCK-HASH>.<bin|hex|json>?count=<COUNT=5>` - instead of - `GET /rest/blockfilterheaders/<FILTERTYPE>/<COUNT>/<BLOCK-HASH>.<bin|hex|json>` (deprecated) - - (#24098) diff --git a/doc/release-notes-24118.md b/doc/release-notes-24118.md deleted file mode 100644 index 16f23c7d00..0000000000 --- a/doc/release-notes-24118.md +++ /dev/null @@ -1,10 +0,0 @@ -New RPCs --------- - -- The `sendall` RPC spends specific UTXOs to one or more recipients - without creating change. By default, the `sendall` RPC will spend - every UTXO in the wallet. `sendall` is useful to empty wallets or to - create a changeless payment from select UTXOs. When creating a payment - from a specific amount for which the recipient incurs the transaction - fee, continue to use the `subtractfeefromamount` option via the - `send`, `sendtoaddress`, or `sendmany` RPCs. (#24118) diff --git a/doc/release-notes-24198.md b/doc/release-notes-24198.md deleted file mode 100644 index e41b2a8e26..0000000000 --- a/doc/release-notes-24198.md +++ /dev/null @@ -1,6 +0,0 @@ -Updated RPCs ------------- - -- The `listtransactions`, `gettransaction`, and `listsinceblock` - RPC methods now include a wtxid field (hash of serialized transaction, - including witness data) for each transaction.
\ No newline at end of file diff --git a/doc/release-notes-24494.md b/doc/release-notes-24494.md deleted file mode 100644 index afbb926433..0000000000 --- a/doc/release-notes-24494.md +++ /dev/null @@ -1,2 +0,0 @@ -To help prevent fingerprinting transactions created by the Bitcoin Core wallet, change output -amounts are now randomized. (#24494) diff --git a/doc/release-notes.md b/doc/release-notes.md deleted file mode 100644 index 35f0713879..0000000000 --- a/doc/release-notes.md +++ /dev/null @@ -1,120 +0,0 @@ -*The release notes draft is a temporary file that can be added to by anyone. See -[/doc/developer-notes.md#release-notes](/doc/developer-notes.md#release-notes) -for the process.* - -*version* Release Notes Draft -=============================== - -Bitcoin Core version *version* is now available from: - - <https://bitcoincore.org/bin/bitcoin-core-*version*/> - -This release includes new features, various bug fixes and performance -improvements, as well as updated translations. - -Please report bugs using the issue tracker at GitHub: - - <https://github.com/bitcoin/bitcoin/issues> - -To receive security and update notifications, please subscribe to: - - <https://bitcoincore.org/en/list/announcements/join/> - -How to Upgrade -============== - -If you are running an older version, shut it down. Wait until it has completely -shut down (which might take a few minutes in some cases), then run the -installer (on Windows) or just copy over `/Applications/Bitcoin-Qt` (on Mac) -or `bitcoind`/`bitcoin-qt` (on Linux). - -Upgrading directly from a version of Bitcoin Core that has reached its EOL is -possible, but it might take some time if the data directory needs to be migrated. Old -wallet versions of Bitcoin Core are generally supported. - -Compatibility -============== - -Bitcoin Core is supported and extensively tested on operating systems -using the Linux kernel, macOS 10.15+, and Windows 7 and newer. Bitcoin -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. - -Notable changes -=============== - -P2P and network changes ------------------------ - -Updated RPCs ------------- - -- The `-deprecatedrpc=softforks` configuration option has been removed. The - RPC `getblockchaininfo` no longer returns the `softforks` field, which was - previously deprecated in 23.0. (#23508) Information on soft fork status is - now only available via the `getdeploymentinfo` RPC. - -- The `deprecatedrpc=exclude_coinbase` configuration option has been removed. - The `receivedby` RPCs (`listreceivedbyaddress`, `listreceivedbylabel`, - `getreceivedbyaddress` and `getreceivedbylabel`) now always return results - accounting for received coins from coinbase outputs, without an option to - change that behaviour. Excluding coinbases was previously deprecated in 23.0. - (#25171) - -- The `deprecatedrpc=fees` configuration option has been removed. The top-level - fee fields `fee`, `modifiedfee`, `ancestorfees` and `descendantfees` are no - longer returned by RPCs `getmempoolentry`, `getrawmempool(verbose=true)`, - `getmempoolancestors(verbose=true)` and `getmempooldescendants(verbose=true)`. - The same fee fields can be accessed through the `fees` object in the result. - The top-level fee fields were previously deprecated in 23.0. (#25204) - -Changes to wallet related RPCs can be found in the Wallet section below. - -New RPCs --------- - -Build System ------------- - -Updated settings ----------------- - - -Changes to GUI or wallet related settings can be found in the GUI or Wallet section below. - -New settings ------------- - -Tools and Utilities -------------------- - -Wallet ------- - -- RPC `getreceivedbylabel` now returns an error, "Label not found - in wallet" (-4), if the label is not in the address book. (#25122) - -GUI changes ------------ - -Low-level changes -================= - -RPC ---- - -Tests ------ - -*version* change log -==================== - -Credits -======= - -Thanks to everyone who directly contributed to this release: - - -As well as to everyone that helped with translations on -[Transifex](https://www.transifex.com/bitcoin/bitcoin/). diff --git a/doc/release-notes/release-notes-0.20.2.md b/doc/release-notes/release-notes-0.20.2.md new file mode 100644 index 0000000000..ad001bc9c1 --- /dev/null +++ b/doc/release-notes/release-notes-0.20.2.md @@ -0,0 +1,165 @@ +0.20.2 Release Notes +==================== + +Bitcoin Core version 0.20.2 is now available from: + + <https://bitcoincore.org/bin/bitcoin-core-0.20.2/> + +This minor release includes various bug fixes and performance +improvements, as well as updated translations. + +Please report bugs using the issue tracker at GitHub: + + <https://github.com/bitcoin/bitcoin/issues> + +To receive security and update notifications, please subscribe to: + + <https://bitcoincore.org/en/list/announcements/join/> + +How to Upgrade +============== + +If you are running an older version, shut it down. Wait until it has completely +shut down (which might take a few minutes in some cases), then run the +installer (on Windows) or just copy over `/Applications/Bitcoin-Qt` (on Mac) +or `bitcoind`/`bitcoin-qt` (on Linux). + +Upgrading directly from a version of Bitcoin Core that has reached its EOL is +possible, but it might take some time if the data directory needs to be migrated. Old +wallet versions of Bitcoin Core are generally supported. + +Compatibility +============== + +Bitcoin Core is supported and extensively tested on operating systems +using the Linux kernel, macOS 10.12+, and Windows 7 and newer. Bitcoin +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.20.0 onwards, macOS versions earlier than 10.12 are no +longer supported. Additionally, Bitcoin Core does not yet change appearance +when macOS "dark mode" is activated. + +Known Bugs +========== + +The process for generating the source code release ("tarball") has changed in an +effort to make it more complete, however, there are a few regressions in +this release: + +- The generated `configure` script is currently missing, and you will need to + install autotools and run `./autogen.sh` before you can run + `./configure`. This is the same as when checking out from git. + +- Instead of running `make` simply, you should instead run + `BITCOIN_GENBUILD_NO_GIT=1 make`. + +Notable changes +=============== + +Changes regarding misbehaving peers +----------------------------------- + +Peers that misbehave (e.g. send us invalid blocks) are now referred to as +discouraged nodes in log output, as they're not (and weren't) strictly banned: +incoming connections are still allowed from them, but they're preferred for +eviction. + +Furthermore, a few additional changes are introduced to how discouraged +addresses are treated: + +- Discouraging an address does not time out automatically after 24 hours + (or the `-bantime` setting). Depending on traffic from other peers, + discouragement may time out at an indeterminate time. + +- Discouragement is not persisted over restarts. + +- There is no method to list discouraged addresses. They are not returned by + the `listbanned` RPC. That RPC also no longer reports the `ban_reason` + field, as `"manually added"` is the only remaining option. + +- Discouragement cannot be removed with the `setban remove` RPC command. + If you need to remove a discouragement, you can remove all discouragements by + stop-starting your node. + +Notification changes +-------------------- + +`-walletnotify` notifications are now sent for wallet transactions that are +removed from the mempool because they conflict with a new block. These +notifications were sent previously before the v0.19 release, but had been +broken since that release (bug +[#18325](https://github.com/bitcoin/bitcoin/issues/18325)). + +PSBT changes +------------ + +PSBTs will contain both the non-witness utxo and the witness utxo for segwit +inputs in order to restore compatibility with wallet software that are now +requiring the full previous transaction for segwit inputs. The witness utxo +is still provided to maintain compatibility with software which relied on its +existence to determine whether an input was segwit. + +0.20.2 change log +================= + +### P2P protocol and network code + +- #19620 Add txids with non-standard inputs to reject filter (sdaftuar) +- #20146 Send post-verack handshake messages at most once (MarcoFalke) + +### Wallet + +- #19740 Simplify and fix CWallet::SignTransaction (achow101) + +### RPC and other APIs + +- #19836 Properly deserialize txs with witness before signing (MarcoFalke) +- #20731 Add missing description of vout in getrawtransaction help text (benthecarman) + +### Build system + +- #20142 build: set minimum required Boost to 1.48.0 (fanquake) +- #20298 use the new plistlib API (jonasschnelli) +- #20880 gitian: Use custom MacOS code signing tool (achow101) +- #22190 Use latest signapple commit (achow101) + +### Tests and QA + +- #19839 Set appveyor vm version to previous Visual Studio 2019 release. (sipsorcery) +- #19842 Update the vcpkg checkout commit ID in appveyor config. (sipsorcery) +- #20562 Test that a fully signed tx given to signrawtx is unchanged (achow101) + +### Miscellaneous + +- #19192 Extract net permissions doc (MarcoFalke) +- #19777 Correct description for getblockstats's txs field (shesek) +- #20080 Strip any trailing / in -datadir and -blocksdir paths (hebasto) +- #20082 fixes read buffer to use min rather than max (EthanHeilman) +- #20141 Avoid the use of abs64 in timedata (sipa) +- #20756 Add missing field (permissions) to the getpeerinfo help (amitiuttarwar) +- #20861 BIP 350: Implement Bech32m and use it for v1+ segwit addresses (sipa) +- #22124 Update translations after closing 0.20.x on Transifex (hebasto) +- #21471 fix bech32_encode calls in gen_key_io_test_vectors.py (sipa) +- #22837 mention bech32m/BIP350 in doc/descriptors.md (sipa) + +Credits +======= + +Thanks to everyone who directly contributed to this release: + +- Aaron Clauson +- Amiti Uttarwar +- Andrew Chow +- Ethan Heilman +- fanquake +- Hennadii Stepanov +- Jonas Schnelli +- MarcoFalke +- Nadav Ivgi +- Pieter Wuille +- Suhas Daftuar + +As well as to everyone that helped with translations on +[Transifex](https://www.transifex.com/bitcoin/bitcoin/). diff --git a/doc/release-notes/release-notes-0.21.2.md b/doc/release-notes/release-notes-0.21.2.md new file mode 100644 index 0000000000..3b33c48a26 --- /dev/null +++ b/doc/release-notes/release-notes-0.21.2.md @@ -0,0 +1,109 @@ +0.21.2 Release Notes +==================== + +Bitcoin Core version 0.21.2 is now available from: + + <https://bitcoincore.org/bin/bitcoin-core-0.21.2/> + +This minor release includes various bug fixes and performance +improvements, as well as updated translations. + +Please report bugs using the issue tracker at GitHub: + + <https://github.com/bitcoin/bitcoin/issues> + +To receive security and update notifications, please subscribe to: + + <https://bitcoincore.org/en/list/announcements/join/> + +How to Upgrade +============== + +If you are running an older version, shut it down. Wait until it has completely +shut down (which might take a few minutes in some cases), then run the +installer (on Windows) or just copy over `/Applications/Bitcoin-Qt` (on Mac) +or `bitcoind`/`bitcoin-qt` (on Linux). + +Upgrading directly from a version of Bitcoin Core that has reached its EOL is +possible, but it might take some time if the data directory needs to be migrated. Old +wallet versions of Bitcoin Core are generally supported. + +Compatibility +============== + +Bitcoin Core is supported and extensively tested on operating systems +using the Linux kernel, macOS 10.12+, and Windows 7 and newer. Bitcoin +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.20.0 onwards, macOS versions earlier than 10.12 are no +longer supported. Additionally, Bitcoin Core does not yet change appearance +when macOS "dark mode" is activated. + + +0.21.2 change log +================= + +### P2P protocol and network code + +- #21644 use NetPermissions::HasFlag() in CConnman::Bind() (jonatack) +- #22569 Rate limit the processing of rumoured addresses (sipa) + +### Wallet + +- #21907 Do not iterate a directory if having an error while accessing it (hebasto) + +### RPC + +- #19361 Reset scantxoutset progress before inferring descriptors (prusnak) + +### Build System + +- #21932 depends: update Qt 5.9 source url (kittywhiskers) +- #22017 Update Windows code signing certificate (achow101) +- #22191 Use custom MacOS code signing tool (achow101) +- #22713 Fix build with Boost 1.77.0 (sizeofvoid) + +### Tests and QA + +- #20182 Build with --enable-werror by default, and document exceptions (hebasto) +- #20535 Fix intermittent feature_taproot issue (MarcoFalke) +- #21663 Fix macOS brew install command (hebasto) +- #22279 add missing ECCVerifyHandle to base_encode_decode (apoelstra) +- #22730 Run fuzzer task for the master branch only (hebasto) + +### GUI + +- #277 Do not use QClipboard::Selection on Windows and macOS. (hebasto) +- #280 Remove user input from URI error message (prayank23) +- #365 Draw "eye" sign at the beginning of watch-only addresses (hebasto) + +### Miscellaneous + +- #22002 Fix crash when parsing command line with -noincludeconf=0 (MarcoFalke) +- #22137 util: Properly handle -noincludeconf on command line (take 2) (MarcoFalke) + + +Credits +======= + +Thanks to everyone who directly contributed to this release: + +- Andrew Chow +- Andrew Poelstra +- fanquake +- Hennadii Stepanov +- Jon Atack +- Kittywhiskers Van Gogh +- Luke Dashjr +- MarcoFalke +- Pavol Rusnak +- Pieter Wuille +- prayank23 +- Rafael Sadowski +- W. J. van der Laan + + +As well as to everyone that helped with translations on +[Transifex](https://www.transifex.com/bitcoin/bitcoin/). diff --git a/doc/release-notes/release-notes-24408.md b/doc/release-notes/release-notes-24408.md deleted file mode 100644 index 1072ec786a..0000000000 --- a/doc/release-notes/release-notes-24408.md +++ /dev/null @@ -1,5 +0,0 @@ -New RPCs --------- - -- A new `gettxspendingprevout` RPC has been added, which scans the mempool to find - transactions spending any of the given outpoints. (#24408)
\ No newline at end of file diff --git a/share/setup.nsi.in b/share/setup.nsi.in index b31ba7a5b4..2ce798bd2d 100644 --- a/share/setup.nsi.in +++ b/share/setup.nsi.in @@ -75,13 +75,15 @@ Section -Main SEC0000 File @abs_top_builddir@/release/@BITCOIN_GUI_NAME@@EXEEXT@ File /oname=COPYING.txt @abs_top_srcdir@/COPYING File /oname=readme.txt @abs_top_srcdir@/doc/README_windows.txt + File @abs_top_srcdir@/share/examples/bitcoin.conf + SetOutPath $INSTDIR\share\rpcauth + File @abs_top_srcdir@/share/rpcauth/*.* SetOutPath $INSTDIR\daemon File @abs_top_builddir@/release/@BITCOIN_DAEMON_NAME@@EXEEXT@ File @abs_top_builddir@/release/@BITCOIN_CLI_NAME@@EXEEXT@ File @abs_top_builddir@/release/@BITCOIN_TX_NAME@@EXEEXT@ File @abs_top_builddir@/release/@BITCOIN_WALLET_TOOL_NAME@@EXEEXT@ - SetOutPath $INSTDIR\doc - File /r /x Makefile* @abs_top_srcdir@/doc\*.* + File @abs_top_builddir@/release/@BITCOIN_TEST_NAME@@EXEEXT@ SetOutPath $INSTDIR WriteRegStr HKCU "${REGKEY}\Components" Main 1 SectionEnd @@ -128,8 +130,9 @@ Section /o -un.Main UNSEC0000 Delete /REBOOTOK $INSTDIR\@BITCOIN_GUI_NAME@@EXEEXT@ Delete /REBOOTOK $INSTDIR\COPYING.txt Delete /REBOOTOK $INSTDIR\readme.txt + Delete /REBOOTOK $INSTDIR\bitcoin.conf + RMDir /r /REBOOTOK $INSTDIR\share RMDir /r /REBOOTOK $INSTDIR\daemon - RMDir /r /REBOOTOK $INSTDIR\doc DeleteRegValue HKCU "${REGKEY}\Components" Main SectionEnd diff --git a/src/.clang-tidy b/src/.clang-tidy index e9807d4cb7..9d78ccc959 100644 --- a/src/.clang-tidy +++ b/src/.clang-tidy @@ -1,13 +1,28 @@ Checks: ' -*, bugprone-argument-comment, +bugprone-use-after-move, +misc-unused-using-decls, modernize-use-default-member-init, modernize-use-nullptr, +performance-for-range-copy, +performance-move-const-arg, +performance-unnecessary-copy-initialization, readability-redundant-declaration, +readability-redundant-string-init, ' WarningsAsErrors: ' bugprone-argument-comment, +bugprone-use-after-move, +misc-unused-using-decls, modernize-use-default-member-init, modernize-use-nullptr, +performance-for-range-copy, +performance-move-const-arg, +performance-unnecessary-copy-initialization, readability-redundant-declaration, +readability-redundant-string-init, ' +CheckOptions: + - key: performance-move-const-arg.CheckTriviallyCopyableMove + value: false diff --git a/src/Makefile.am b/src/Makefile.am index 89329f5f69..caeaee511f 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -23,13 +23,12 @@ noinst_PROGRAMS = TESTS = BENCHMARKS = -BITCOIN_INCLUDES=-I$(builddir) -I$(srcdir)/$(MINISKETCH_INCLUDE_DIR_INT) -I$(srcdir)/secp256k1/include -I$(srcdir)/$(UNIVALUE_INCLUDE_DIR_INT) $(BOOST_CPPFLAGS) $(LEVELDB_CPPFLAGS) +BITCOIN_INCLUDES=-I$(builddir) -I$(srcdir)/$(MINISKETCH_INCLUDE_DIR_INT) -I$(srcdir)/secp256k1/include -I$(srcdir)/$(UNIVALUE_INCLUDE_DIR_INT) $(LEVELDB_CPPFLAGS) LIBBITCOIN_NODE=libbitcoin_node.a LIBBITCOIN_COMMON=libbitcoin_common.a LIBBITCOIN_CONSENSUS=libbitcoin_consensus.a LIBBITCOIN_CLI=libbitcoin_cli.a -LIBBITCOIN_KERNEL=libbitcoin_kernel.a LIBBITCOIN_UTIL=libbitcoin_util.a LIBBITCOIN_CRYPTO_BASE=crypto/libbitcoin_crypto_base.la LIBBITCOINQT=qt/libbitcoinqt.a @@ -134,9 +133,10 @@ BITCOIN_CORE_H = \ clientversion.h \ coins.h \ common/bloom.h \ - compat.h \ + common/run_command.h \ compat/assumptions.h \ compat/byteswap.h \ + compat/compat.h \ compat/cpuid.h \ compat/endian.h \ compressor.h \ @@ -152,6 +152,7 @@ BITCOIN_CORE_H = \ external_signer.h \ flatfile.h \ fs.h \ + headerssync.h \ httprpc.h \ httpserver.h \ i2p.h \ @@ -170,10 +171,15 @@ BITCOIN_CORE_H = \ interfaces/ipc.h \ interfaces/node.h \ interfaces/wallet.h \ + kernel/chain.h \ kernel/chainstatemanager_opts.h \ kernel/checks.h \ kernel/coinstats.h \ kernel/context.h \ + kernel/mempool_limits.h \ + kernel/mempool_options.h \ + kernel/mempool_persist.h \ + kernel/validation_cache_sizes.h \ key.h \ key_io.h \ logging.h \ @@ -193,17 +199,23 @@ BITCOIN_CORE_H = \ node/caches.h \ node/chainstate.h \ node/coin.h \ + node/connection_types.h \ node/context.h \ + node/eviction.h \ + node/interface_ui.h \ + node/mempool_args.h \ + node/mempool_persist_args.h \ node/miner.h \ node/minisketchwrapper.h \ node/psbt.h \ node/transaction.h \ - node/ui_interface.h \ node/utxo_snapshot.h \ + node/validation_cache_args.h \ noui.h \ outputtype.h \ policy/feerate.h \ policy/fees.h \ + policy/fees_args.h \ policy/packages.h \ policy/policy.h \ policy/rbf.h \ @@ -254,9 +266,9 @@ BITCOIN_CORE_H = \ undo.h \ util/asmap.h \ util/bip32.h \ + util/bitdeque.h \ util/bytevectorhash.h \ util/check.h \ - util/designator.h \ util/epochguard.h \ util/error.h \ util/fastrange.h \ @@ -272,6 +284,7 @@ BITCOIN_CORE_H = \ util/overloaded.h \ util/rbf.h \ util/readwritefile.h \ + util/result.h \ util/serfloat.h \ util/settings.h \ util/sock.h \ @@ -337,7 +350,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_node_a_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) $(MINIUPNPC_CPPFLAGS) $(NATPMP_CPPFLAGS) $(EVENT_CFLAGS) $(EVENT_PTHREADS_CFLAGS) +libbitcoin_node_a_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) $(BOOST_CPPFLAGS) $(MINIUPNPC_CPPFLAGS) $(NATPMP_CPPFLAGS) $(EVENT_CFLAGS) $(EVENT_PTHREADS_CFLAGS) libbitcoin_node_a_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) libbitcoin_node_a_SOURCES = \ addrdb.cpp \ @@ -350,6 +363,7 @@ libbitcoin_node_a_SOURCES = \ dbwrapper.cpp \ deploymentstatus.cpp \ flatfile.cpp \ + headerssync.cpp \ httprpc.cpp \ httpserver.cpp \ i2p.cpp \ @@ -358,26 +372,34 @@ libbitcoin_node_a_SOURCES = \ index/coinstatsindex.cpp \ index/txindex.cpp \ init.cpp \ + kernel/chain.cpp \ kernel/checks.cpp \ kernel/coinstats.cpp \ kernel/context.cpp \ + kernel/mempool_persist.cpp \ mapport.cpp \ net.cpp \ - netgroup.cpp \ net_processing.cpp \ + netgroup.cpp \ node/blockstorage.cpp \ node/caches.cpp \ node/chainstate.cpp \ node/coin.cpp \ + node/connection_types.cpp \ node/context.cpp \ + node/eviction.cpp \ + node/interface_ui.cpp \ node/interfaces.cpp \ + node/mempool_args.cpp \ + node/mempool_persist_args.cpp \ node/miner.cpp \ node/minisketchwrapper.cpp \ node/psbt.cpp \ node/transaction.cpp \ - node/ui_interface.cpp \ + node/validation_cache_args.cpp \ noui.cpp \ policy/fees.cpp \ + policy/fees_args.cpp \ policy/packages.cpp \ policy/rbf.cpp \ policy/settings.cpp \ @@ -387,8 +409,8 @@ libbitcoin_node_a_SOURCES = \ rpc/fees.cpp \ rpc/mempool.cpp \ rpc/mining.cpp \ - rpc/node.cpp \ rpc/net.cpp \ + rpc/node.cpp \ rpc/output_script.cpp \ rpc/rawtransaction.cpp \ rpc/server.cpp \ @@ -431,7 +453,7 @@ endif # wallet: shared between bitcoind and bitcoin-qt, but only linked # when wallet enabled -libbitcoin_wallet_a_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) $(BDB_CPPFLAGS) $(SQLITE_CFLAGS) +libbitcoin_wallet_a_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) $(BOOST_CPPFLAGS) $(BDB_CPPFLAGS) $(SQLITE_CFLAGS) libbitcoin_wallet_a_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) libbitcoin_wallet_a_SOURCES = \ wallet/coincontrol.cpp \ @@ -470,7 +492,7 @@ if USE_BDB libbitcoin_wallet_a_SOURCES += wallet/bdb.cpp wallet/salvage.cpp endif -libbitcoin_wallet_tool_a_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) +libbitcoin_wallet_tool_a_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) $(BOOST_CPPFLAGS) libbitcoin_wallet_tool_a_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) libbitcoin_wallet_tool_a_SOURCES = \ wallet/wallettool.cpp \ @@ -595,7 +617,7 @@ libbitcoin_consensus_a_SOURCES = \ version.h # common: shared between bitcoind, and bitcoin-qt and non-server tools -libbitcoin_common_a_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) +libbitcoin_common_a_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) $(BOOST_CPPFLAGS) libbitcoin_common_a_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) libbitcoin_common_a_SOURCES = \ base58.cpp \ @@ -603,6 +625,7 @@ libbitcoin_common_a_SOURCES = \ chainparams.cpp \ coins.cpp \ common/bloom.cpp \ + common/run_command.cpp \ compressor.cpp \ core_read.cpp \ core_write.cpp \ @@ -634,7 +657,7 @@ libbitcoin_common_a_SOURCES = \ $(BITCOIN_CORE_H) # util: shared between all executables. -libbitcoin_util_a_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) +libbitcoin_util_a_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) $(BOOST_CPPFLAGS) libbitcoin_util_a_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) libbitcoin_util_a_SOURCES = \ support/lockedpool.cpp \ @@ -812,7 +835,7 @@ bitcoin_util_LDADD = \ # bitcoin-chainstate binary # bitcoin_chainstate_SOURCES = bitcoin-chainstate.cpp -bitcoin_chainstate_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) +bitcoin_chainstate_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) $(BOOST_CPPFLAGS) bitcoin_chainstate_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) # $(LIBTOOL_APP_LDFLAGS) deliberately omitted here so that we can test linking @@ -849,8 +872,7 @@ endif # TODO: libbitcoinkernel is a work in progress consensus engine library, as more # and more modules are decoupled from the consensus engine, this list will -# shrink to only those which are absolutely necessary. For example, things -# like index/*.cpp will be removed. +# shrink to only those which are absolutely necessary. libbitcoinkernel_la_SOURCES = \ kernel/bitcoinkernel.cpp \ arith_uint256.cpp \ @@ -870,14 +892,16 @@ libbitcoinkernel_la_SOURCES = \ flatfile.cpp \ fs.cpp \ hash.cpp \ + kernel/chain.cpp \ kernel/checks.cpp \ kernel/coinstats.cpp \ kernel/context.cpp \ + kernel/mempool_persist.cpp \ key.cpp \ logging.cpp \ node/blockstorage.cpp \ node/chainstate.cpp \ - node/ui_interface.cpp \ + node/interface_ui.cpp \ policy/feerate.cpp \ policy/fees.cpp \ policy/packages.cpp \ @@ -905,7 +929,6 @@ libbitcoinkernel_la_SOURCES = \ txdb.cpp \ txmempool.cpp \ uint256.cpp \ - util/bytevectorhash.cpp \ util/check.cpp \ util/getuniquepath.cpp \ util/hasher.cpp \ @@ -1046,6 +1069,15 @@ nodist_libbitcoin_ipc_a_SOURCES = $(libbitcoin_ipc_mpgen_output) CLEANFILES += $(libbitcoin_ipc_mpgen_output) endif +%.raw.h: %.raw + @$(MKDIR_P) $(@D) + @{ \ + echo "static unsigned const char $(*F)_raw[] = {" && \ + $(HEXDUMP) -v -e '8/1 "0x%02x, "' -e '"\n"' $< | $(SED) -e 's/0x ,//g' && \ + echo "};"; \ + } > "$@.new" && mv -f "$@.new" "$@" + @echo "Generated $@" + include Makefile.minisketch.include include Makefile.crc32c.include @@ -1054,9 +1086,7 @@ include Makefile.leveldb.include include Makefile.test_util.include include Makefile.test_fuzz.include -if ENABLE_TESTS include Makefile.test.include -endif if ENABLE_BENCH include Makefile.bench.include diff --git a/src/Makefile.bench.include b/src/Makefile.bench.include index 532f668668..e1e2066877 100644 --- a/src/Makefile.bench.include +++ b/src/Makefile.bench.include @@ -27,6 +27,7 @@ bench_bench_bitcoin_SOURCES = \ bench/crypto_hash.cpp \ bench/data.cpp \ bench/data.h \ + bench/descriptors.cpp \ bench/duplicate_inputs.cpp \ bench/examples.cpp \ bench/gcs_filter.cpp \ @@ -50,8 +51,9 @@ bench_bench_bitcoin_SOURCES = \ nodist_bench_bench_bitcoin_SOURCES = $(GENERATED_BENCH_FILES) -bench_bench_bitcoin_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) $(EVENT_CFLAGS) $(EVENT_PTHREADS_CFLAGS) -I$(builddir)/bench/ +bench_bench_bitcoin_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) $(BOOST_CPPFLAGS) $(EVENT_CFLAGS) $(EVENT_PTHREADS_CFLAGS) -I$(builddir)/bench/ bench_bench_bitcoin_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) +bench_bench_bitcoin_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS) $(PTHREAD_FLAGS) bench_bench_bitcoin_LDADD = \ $(LIBTEST_UTIL) \ $(LIBBITCOIN_NODE) \ @@ -65,7 +67,9 @@ bench_bench_bitcoin_LDADD = \ $(LIBSECP256K1) \ $(LIBUNIVALUE) \ $(EVENT_PTHREADS_LIBS) \ - $(EVENT_LIBS) + $(EVENT_LIBS) \ + $(MINIUPNPC_LIBS) \ + $(NATPMP_LIBS) if ENABLE_ZMQ bench_bench_bitcoin_LDADD += $(LIBBITCOIN_ZMQ) $(ZMQ_LIBS) @@ -75,11 +79,9 @@ if ENABLE_WALLET bench_bench_bitcoin_SOURCES += bench/coin_selection.cpp bench_bench_bitcoin_SOURCES += bench/wallet_balance.cpp bench_bench_bitcoin_SOURCES += bench/wallet_loading.cpp +bench_bench_bitcoin_LDADD += $(BDB_LIBS) $(SQLITE_LIBS) endif -bench_bench_bitcoin_LDADD += $(BDB_LIBS) $(EVENT_PTHREADS_LIBS) $(EVENT_LIBS) $(MINIUPNPC_LIBS) $(NATPMP_LIBS) $(SQLITE_LIBS) -bench_bench_bitcoin_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS) $(PTHREAD_FLAGS) - CLEAN_BITCOIN_BENCH = bench/*.gcda bench/*.gcno $(GENERATED_BENCH_FILES) CLEANFILES += $(CLEAN_BITCOIN_BENCH) @@ -93,12 +95,3 @@ bench: $(BENCH_BINARY) FORCE bitcoin_bench_clean : FORCE rm -f $(CLEAN_BITCOIN_BENCH) $(bench_bench_bitcoin_OBJECTS) $(BENCH_BINARY) - -%.raw.h: %.raw - @$(MKDIR_P) $(@D) - @{ \ - echo "static unsigned const char $(*F)_raw[] = {" && \ - $(HEXDUMP) -v -e '8/1 "0x%02x, "' -e '"\n"' $< | $(SED) -e 's/0x ,//g' && \ - echo "};"; \ - } > "$@.new" && mv -f "$@.new" "$@" - @echo "Generated $@" diff --git a/src/Makefile.leveldb.include b/src/Makefile.leveldb.include index 066f8940c5..bf14fe206b 100644 --- a/src/Makefile.leveldb.include +++ b/src/Makefile.leveldb.include @@ -13,7 +13,6 @@ LIBMEMENV = $(LIBMEMENV_INT) LEVELDB_CPPFLAGS = LEVELDB_CPPFLAGS += -I$(srcdir)/leveldb/include -LEVELDB_CPPFLAGS += -I$(srcdir)/leveldb/helpers/memenv LEVELDB_CPPFLAGS_INT = LEVELDB_CPPFLAGS_INT += -I$(srcdir)/leveldb diff --git a/src/Makefile.qt.include b/src/Makefile.qt.include index 72037b3db2..602a118259 100644 --- a/src/Makefile.qt.include +++ b/src/Makefile.qt.include @@ -270,6 +270,7 @@ BITCOIN_QT_WALLET_CPP = \ qt/transactiondesc.cpp \ qt/transactiondescdialog.cpp \ qt/transactionfilterproxy.cpp \ + qt/transactionoverviewwidget.cpp \ qt/transactionrecord.cpp \ qt/transactiontablemodel.cpp \ qt/transactionview.cpp \ @@ -294,7 +295,7 @@ BITCOIN_QT_RC = qt/res/bitcoin-qt-res.rc BITCOIN_QT_INCLUDES = -DQT_NO_KEYWORDS -DQT_USE_QSTRINGBUILDER qt_libbitcoinqt_a_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) $(BITCOIN_QT_INCLUDES) \ - $(QT_INCLUDES) $(QT_DBUS_INCLUDES) $(QR_CFLAGS) + $(QT_INCLUDES) $(QT_DBUS_INCLUDES) $(QR_CFLAGS) $(BOOST_CPPFLAGS) qt_libbitcoinqt_a_CXXFLAGS = $(AM_CXXFLAGS) $(QT_PIE_FLAGS) qt_libbitcoinqt_a_OBJCXXFLAGS = $(AM_OBJCXXFLAGS) $(QT_PIE_FLAGS) diff --git a/src/Makefile.qttest.include b/src/Makefile.qttest.include index fa822f2954..89c659d4b9 100644 --- a/src/Makefile.qttest.include +++ b/src/Makefile.qttest.include @@ -27,7 +27,7 @@ TEST_QT_H = \ qt/test/wallettests.h qt_test_test_bitcoin_qt_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) $(BITCOIN_QT_INCLUDES) \ - $(QT_INCLUDES) $(QT_TEST_INCLUDES) + $(QT_INCLUDES) $(QT_TEST_INCLUDES) $(BOOST_CPPFLAGS) qt_test_test_bitcoin_qt_SOURCES = \ init/bitcoin-qt.cpp \ diff --git a/src/Makefile.test.include b/src/Makefile.test.include index ebd9e860cf..253f64d2c3 100644 --- a/src/Makefile.test.include +++ b/src/Makefile.test.include @@ -6,7 +6,7 @@ if ENABLE_FUZZ_BINARY noinst_PROGRAMS += test/fuzz/fuzz endif -if !ENABLE_FUZZ +if ENABLE_TESTS bin_PROGRAMS += test/test_bitcoin endif @@ -93,6 +93,7 @@ BITCOIN_TESTS =\ test/fs_tests.cpp \ test/getarg_tests.cpp \ test/hash_tests.cpp \ + test/headers_sync_chainwork_tests.cpp \ test/httpserver_tests.cpp \ test/i2p_tests.cpp \ test/interfaces_tests.cpp \ @@ -117,7 +118,9 @@ BITCOIN_TESTS =\ test/prevector_tests.cpp \ test/raii_event_tests.cpp \ test/random_tests.cpp \ + test/rbf_tests.cpp \ test/rest_tests.cpp \ + test/result_tests.cpp \ test/reverselock_tests.cpp \ test/rpc_tests.cpp \ test/sanity_tests.cpp \ @@ -160,6 +163,7 @@ BITCOIN_TESTS =\ if ENABLE_WALLET BITCOIN_TESTS += \ + wallet/test/feebumper_tests.cpp \ wallet/test/psbt_wallet_tests.cpp \ wallet/test/spend_tests.cpp \ wallet/test/wallet_tests.cpp \ @@ -167,9 +171,12 @@ BITCOIN_TESTS += \ wallet/test/wallet_crypto_tests.cpp \ wallet/test/wallet_transaction_tests.cpp \ wallet/test/coinselector_tests.cpp \ + wallet/test/availablecoins_tests.cpp \ wallet/test/init_tests.cpp \ wallet/test/ismine_tests.cpp \ - wallet/test/scriptpubkeyman_tests.cpp + wallet/test/rpc_util_tests.cpp \ + wallet/test/scriptpubkeyman_tests.cpp \ + wallet/test/walletload_tests.cpp FUZZ_SUITE_LD_COMMON +=\ $(SQLITE_LIBS) \ @@ -180,7 +187,8 @@ BITCOIN_TESTS += wallet/test/db_tests.cpp endif FUZZ_WALLET_SRC = \ - wallet/test/fuzz/coinselection.cpp + wallet/test/fuzz/coinselection.cpp \ + wallet/test/fuzz/parse_iso8601.cpp if USE_SQLITE FUZZ_WALLET_SRC += \ @@ -197,7 +205,7 @@ BITCOIN_TEST_SUITE += \ endif # ENABLE_WALLET test_test_bitcoin_SOURCES = $(BITCOIN_TEST_SUITE) $(BITCOIN_TESTS) $(JSON_TEST_FILES) $(RAW_TEST_FILES) -test_test_bitcoin_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) $(TESTDEFS) $(EVENT_CFLAGS) +test_test_bitcoin_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) $(TESTDEFS) $(BOOST_CPPFLAGS) $(EVENT_CFLAGS) test_test_bitcoin_LDADD = $(LIBTEST_UTIL) if ENABLE_WALLET test_test_bitcoin_LDADD += $(LIBBITCOIN_WALLET) @@ -217,7 +225,7 @@ FUZZ_SUITE_LD_COMMON += $(LIBBITCOIN_ZMQ) $(ZMQ_LIBS) endif if ENABLE_FUZZ_BINARY -test_fuzz_fuzz_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) +test_fuzz_fuzz_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) $(BOOST_CPPFLAGS) test_fuzz_fuzz_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) test_fuzz_fuzz_LDADD = $(FUZZ_SUITE_LD_COMMON) test_fuzz_fuzz_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS) $(PTHREAD_FLAGS) $(RUNTIME_LDFLAGS) @@ -231,6 +239,7 @@ test_fuzz_fuzz_SOURCES = \ test/fuzz/banman.cpp \ test/fuzz/base_encode_decode.cpp \ test/fuzz/bech32.cpp \ + test/fuzz/bitdeque.cpp \ test/fuzz/block.cpp \ test/fuzz/block_header.cpp \ test/fuzz/blockfilter.cpp \ @@ -281,7 +290,6 @@ test_fuzz_fuzz_SOURCES = \ test/fuzz/node_eviction.cpp \ test/fuzz/p2p_transport_serialization.cpp \ test/fuzz/parse_hd_keypath.cpp \ - test/fuzz/parse_iso8601.cpp \ test/fuzz/parse_numbers.cpp \ test/fuzz/parse_script.cpp \ test/fuzz/parse_univalue.cpp \ @@ -325,6 +333,7 @@ test_fuzz_fuzz_SOURCES = \ test/fuzz/tx_in.cpp \ test/fuzz/tx_out.cpp \ test/fuzz/tx_pool.cpp \ + test/fuzz/txorphan.cpp \ test/fuzz/txrequest.cpp \ test/fuzz/utxo_snapshot.cpp \ test/fuzz/validation_load_mempool.cpp \ @@ -335,7 +344,7 @@ nodist_test_test_bitcoin_SOURCES = $(GENERATED_TEST_FILES) $(BITCOIN_TESTS): $(GENERATED_TEST_FILES) -CLEAN_BITCOIN_TEST = test/*.gcda test/*.gcno test/fuzz/*.gcda test/fuzz/*.gcno test/util/*.gcda test/util/*.gcno $(GENERATED_TEST_FILES) $(BITCOIN_TESTS:=.log) +CLEAN_BITCOIN_TEST = test/*.gcda test/*.gcno test/fuzz/*.gcda test/fuzz/*.gcno test/util/*.gcda test/util/*.gcno $(GENERATED_TEST_FILES) $(addsuffix .log,$(basename $(BITCOIN_TESTS))) CLEANFILES += $(CLEAN_BITCOIN_TEST) @@ -371,8 +380,8 @@ endif endif $(AM_V_at)$(MAKE) $(AM_MAKEFLAGS) -C secp256k1 check -if !ENABLE_FUZZ -UNIVALUE_TESTS = univalue/test/object univalue/test/unitester univalue/test/no_nul +if ENABLE_TESTS +UNIVALUE_TESTS = univalue/test/object univalue/test/unitester noinst_PROGRAMS += $(UNIVALUE_TESTS) TESTS += $(UNIVALUE_TESTS) @@ -381,11 +390,6 @@ univalue_test_unitester_LDADD = $(LIBUNIVALUE) univalue_test_unitester_CPPFLAGS = -I$(srcdir)/$(UNIVALUE_INCLUDE_DIR_INT) -DJSON_TEST_SRC=\"$(srcdir)/$(UNIVALUE_TEST_DATA_DIR_INT)\" univalue_test_unitester_LDFLAGS = -static $(LIBTOOL_APP_LDFLAGS) -univalue_test_no_nul_SOURCES = $(UNIVALUE_TEST_NO_NUL_INT) -univalue_test_no_nul_LDADD = $(LIBUNIVALUE) -univalue_test_no_nul_CPPFLAGS = -I$(srcdir)/$(UNIVALUE_INCLUDE_DIR_INT) -univalue_test_no_nul_LDFLAGS = -static $(LIBTOOL_APP_LDFLAGS) - univalue_test_object_SOURCES = $(UNIVALUE_TEST_OBJECT_INT) univalue_test_object_LDADD = $(LIBUNIVALUE) univalue_test_object_CPPFLAGS = -I$(srcdir)/$(UNIVALUE_INCLUDE_DIR_INT) @@ -416,12 +420,3 @@ endif echo "};};"; \ } > "$@.new" && mv -f "$@.new" "$@" @echo "Generated $@" - -%.raw.h: %.raw - @$(MKDIR_P) $(@D) - @{ \ - echo "static unsigned const char $(*F)_raw[] = {" && \ - $(HEXDUMP) -v -e '8/1 "0x%02x, "' -e '"\n"' $< | $(SED) -e 's/0x ,//g' && \ - echo "};"; \ - } > "$@.new" && mv -f "$@.new" "$@" - @echo "Generated $@" diff --git a/src/Makefile.test_fuzz.include b/src/Makefile.test_fuzz.include index 8922dda3ad..b35d713d57 100644 --- a/src/Makefile.test_fuzz.include +++ b/src/Makefile.test_fuzz.include @@ -10,11 +10,13 @@ EXTRA_LIBRARIES += \ TEST_FUZZ_H = \ test/fuzz/fuzz.h \ test/fuzz/FuzzedDataProvider.h \ - test/fuzz/util.h + test/fuzz/util.h \ + test/fuzz/util/mempool.h -libtest_fuzz_a_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) $(MINIUPNPC_CPPFLAGS) $(NATPMP_CPPFLAGS) $(EVENT_CFLAGS) $(EVENT_PTHREADS_CFLAGS) +libtest_fuzz_a_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) $(BOOST_CPPFLAGS) libtest_fuzz_a_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) libtest_fuzz_a_SOURCES = \ test/fuzz/fuzz.cpp \ test/fuzz/util.cpp \ + test/fuzz/util/mempool.cpp \ $(TEST_FUZZ_H) diff --git a/src/Makefile.test_util.include b/src/Makefile.test_util.include index 9306bb6fcc..ada789f1b0 100644 --- a/src/Makefile.test_util.include +++ b/src/Makefile.test_util.include @@ -20,7 +20,7 @@ TEST_UTIL_H = \ test/util/validation.h \ test/util/wallet.h -libtest_util_a_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) $(MINIUPNPC_CPPFLAGS) $(NATPMP_CPPFLAGS) $(EVENT_CFLAGS) $(EVENT_PTHREADS_CFLAGS) +libtest_util_a_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) $(BOOST_CPPFLAGS) libtest_util_a_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) libtest_util_a_SOURCES = \ test/util/blockfilter.cpp \ diff --git a/src/addrdb.cpp b/src/addrdb.cpp index 31f8eadf98..7106d819b0 100644 --- a/src/addrdb.cpp +++ b/src/addrdb.cpp @@ -187,11 +187,11 @@ std::optional<bilingual_str> LoadAddrman(const NetGroupManager& netgroupman, con auto check_addrman = std::clamp<int32_t>(args.GetIntArg("-checkaddrman", DEFAULT_ADDRMAN_CONSISTENCY_CHECKS), 0, 1000000); addrman = std::make_unique<AddrMan>(netgroupman, /*deterministic=*/false, /*consistency_check_ratio=*/check_addrman); - int64_t nStart = GetTimeMillis(); + const auto start{SteadyClock::now()}; const auto path_addr{args.GetDataDirNet() / "peers.dat"}; try { DeserializeFileDB(path_addr, *addrman, CLIENT_VERSION); - LogPrintf("Loaded %i addresses from peers.dat %dms\n", addrman->size(), GetTimeMillis() - nStart); + LogPrintf("Loaded %i addresses from peers.dat %dms\n", addrman->size(), Ticks<std::chrono::milliseconds>(SteadyClock::now() - start)); } catch (const DbNotFoundError&) { // Addrman can be in an inconsistent state after failure, reset it addrman = std::make_unique<AddrMan>(netgroupman, /*deterministic=*/false, /*consistency_check_ratio=*/check_addrman); diff --git a/src/addrman.cpp b/src/addrman.cpp index 204bb544c5..f16ff2230b 100644 --- a/src/addrman.cpp +++ b/src/addrman.cpp @@ -14,10 +14,10 @@ #include <random.h> #include <serialize.h> #include <streams.h> -#include <timedata.h> #include <tinyformat.h> #include <uint256.h> #include <util/check.h> +#include <util/time.h> #include <cmath> #include <optional> @@ -29,19 +29,19 @@ static constexpr uint32_t ADDRMAN_NEW_BUCKETS_PER_SOURCE_GROUP{64}; /** Maximum number of times an address can occur in the new table */ static constexpr int32_t ADDRMAN_NEW_BUCKETS_PER_ADDRESS{8}; /** How old addresses can maximally be */ -static constexpr int64_t ADDRMAN_HORIZON_DAYS{30}; +static constexpr auto ADDRMAN_HORIZON{30 * 24h}; /** After how many failed attempts we give up on a new node */ static constexpr int32_t ADDRMAN_RETRIES{3}; /** How many successive failures are allowed ... */ static constexpr int32_t ADDRMAN_MAX_FAILURES{10}; -/** ... in at least this many days */ -static constexpr int64_t ADDRMAN_MIN_FAIL_DAYS{7}; +/** ... in at least this duration */ +static constexpr auto ADDRMAN_MIN_FAIL{7 * 24h}; /** How recent a successful connection should be before we allow an address to be evicted from tried */ -static constexpr int64_t ADDRMAN_REPLACEMENT_HOURS{4}; +static constexpr auto ADDRMAN_REPLACEMENT{4h}; /** The maximum number of tried addr collisions to store */ static constexpr size_t ADDRMAN_SET_TRIED_COLLISION_SIZE{10}; -/** The maximum time we'll spend trying to resolve a tried table collision, in seconds */ -static constexpr int64_t ADDRMAN_TEST_WINDOW{40*60}; // 40 minutes +/** The maximum time we'll spend trying to resolve a tried table collision */ +static constexpr auto ADDRMAN_TEST_WINDOW{40min}; int AddrInfo::GetTriedBucket(const uint256& nKey, const NetGroupManager& netgroupman) const { @@ -64,36 +64,39 @@ int AddrInfo::GetBucketPosition(const uint256& nKey, bool fNew, int nBucket) con return hash1 % ADDRMAN_BUCKET_SIZE; } -bool AddrInfo::IsTerrible(int64_t nNow) const +bool AddrInfo::IsTerrible(NodeSeconds now) const { - if (nNow - nLastTry <= 60) { // never remove things tried in the last minute + if (now - m_last_try <= 1min) { // never remove things tried in the last minute return false; } - if (nTime > nNow + 10 * 60) // came in a flying DeLorean + if (nTime > now + 10min) { // came in a flying DeLorean return true; + } - if (nNow - nTime > ADDRMAN_HORIZON_DAYS * 24 * 60 * 60) { // not seen in recent history + if (now - nTime > ADDRMAN_HORIZON) { // not seen in recent history return true; } - if (nLastSuccess == 0 && nAttempts >= ADDRMAN_RETRIES) // tried N times and never a success + if (TicksSinceEpoch<std::chrono::seconds>(m_last_success) == 0 && nAttempts >= ADDRMAN_RETRIES) { // tried N times and never a success return true; + } - if (nNow - nLastSuccess > ADDRMAN_MIN_FAIL_DAYS * 24 * 60 * 60 && nAttempts >= ADDRMAN_MAX_FAILURES) // N successive failures in the last week + if (now - m_last_success > ADDRMAN_MIN_FAIL && nAttempts >= ADDRMAN_MAX_FAILURES) { // N successive failures in the last week return true; + } return false; } -double AddrInfo::GetChance(int64_t nNow) const +double AddrInfo::GetChance(NodeSeconds now) const { double fChance = 1.0; - int64_t nSinceLastTry = std::max<int64_t>(nNow - nLastTry, 0); // deprioritize very recent attempts away - if (nSinceLastTry < 60 * 10) + if (now - m_last_try < 10min) { fChance *= 0.01; + } // deprioritize 66% after each failed attempt, but at most 1/28th to avoid the search taking forever or overly penalizing outages. fChance *= pow(0.66, std::min(nAttempts, 8)); @@ -540,7 +543,7 @@ void AddrManImpl::MakeTried(AddrInfo& info, int nId) info.fInTried = true; } -bool AddrManImpl::AddSingle(const CAddress& addr, const CNetAddr& source, int64_t nTimePenalty) +bool AddrManImpl::AddSingle(const CAddress& addr, const CNetAddr& source, std::chrono::seconds time_penalty) { AssertLockHeld(cs); @@ -552,15 +555,15 @@ bool AddrManImpl::AddSingle(const CAddress& addr, const CNetAddr& source, int64_ // Do not set a penalty for a source's self-announcement if (addr == source) { - nTimePenalty = 0; + time_penalty = 0s; } if (pinfo) { // periodically update nTime - bool fCurrentlyOnline = (GetAdjustedTime() - addr.nTime < 24 * 60 * 60); - int64_t nUpdateInterval = (fCurrentlyOnline ? 60 * 60 : 24 * 60 * 60); - if (pinfo->nTime < addr.nTime - nUpdateInterval - nTimePenalty) { - pinfo->nTime = std::max((int64_t)0, addr.nTime - nTimePenalty); + const bool currently_online{NodeClock::now() - addr.nTime < 24h}; + const auto update_interval{currently_online ? 1h : 24h}; + if (pinfo->nTime < addr.nTime - update_interval - time_penalty) { + pinfo->nTime = std::max(NodeSeconds{0s}, addr.nTime - time_penalty); } // add services @@ -587,7 +590,7 @@ bool AddrManImpl::AddSingle(const CAddress& addr, const CNetAddr& source, int64_ return false; } else { pinfo = Create(addr, source, &nId); - pinfo->nTime = std::max((int64_t)0, (int64_t)pinfo->nTime - nTimePenalty); + pinfo->nTime = std::max(NodeSeconds{0s}, pinfo->nTime - time_penalty); nNew++; } @@ -617,13 +620,13 @@ bool AddrManImpl::AddSingle(const CAddress& addr, const CNetAddr& source, int64_ return fInsert; } -bool AddrManImpl::Good_(const CService& addr, bool test_before_evict, int64_t nTime) +bool AddrManImpl::Good_(const CService& addr, bool test_before_evict, NodeSeconds time) { AssertLockHeld(cs); int nId; - nLastGood = nTime; + m_last_good = time; AddrInfo* pinfo = Find(addr, &nId); @@ -633,8 +636,8 @@ bool AddrManImpl::Good_(const CService& addr, bool test_before_evict, int64_t nT AddrInfo& info = *pinfo; // update info - info.nLastSuccess = nTime; - info.nLastTry = nTime; + info.m_last_success = time; + info.m_last_try = time; info.nAttempts = 0; // nTime is not updated here, to avoid leaking information about // currently-connected peers. @@ -671,11 +674,11 @@ bool AddrManImpl::Good_(const CService& addr, bool test_before_evict, int64_t nT } } -bool AddrManImpl::Add_(const std::vector<CAddress> &vAddr, const CNetAddr& source, int64_t nTimePenalty) +bool AddrManImpl::Add_(const std::vector<CAddress>& vAddr, const CNetAddr& source, std::chrono::seconds time_penalty) { int added{0}; for (std::vector<CAddress>::const_iterator it = vAddr.begin(); it != vAddr.end(); it++) { - added += AddSingle(*it, source, nTimePenalty) ? 1 : 0; + added += AddSingle(*it, source, time_penalty) ? 1 : 0; } if (added > 0) { LogPrint(BCLog::ADDRMAN, "Added %i addresses (of %i) from %s: %i tried, %i new\n", added, vAddr.size(), source.ToString(), nTried, nNew); @@ -683,7 +686,7 @@ bool AddrManImpl::Add_(const std::vector<CAddress> &vAddr, const CNetAddr& sourc return added > 0; } -void AddrManImpl::Attempt_(const CService& addr, bool fCountFailure, int64_t nTime) +void AddrManImpl::Attempt_(const CService& addr, bool fCountFailure, NodeSeconds time) { AssertLockHeld(cs); @@ -696,14 +699,14 @@ void AddrManImpl::Attempt_(const CService& addr, bool fCountFailure, int64_t nTi AddrInfo& info = *pinfo; // update info - info.nLastTry = nTime; - if (fCountFailure && info.nLastCountAttempt < nLastGood) { - info.nLastCountAttempt = nTime; + info.m_last_try = time; + if (fCountFailure && info.m_last_count_attempt < m_last_good) { + info.m_last_count_attempt = time; info.nAttempts++; } } -std::pair<CAddress, int64_t> AddrManImpl::Select_(bool newOnly) const +std::pair<CAddress, NodeSeconds> AddrManImpl::Select_(bool newOnly) const { AssertLockHeld(cs); @@ -736,7 +739,7 @@ std::pair<CAddress, int64_t> AddrManImpl::Select_(bool newOnly) const // With probability GetChance() * fChanceFactor, return the entry. if (insecure_rand.randbits(30) < fChanceFactor * info.GetChance() * (1 << 30)) { LogPrint(BCLog::ADDRMAN, "Selected %s from tried\n", info.ToString()); - return {info, info.nLastTry}; + return {info, info.m_last_try}; } // Otherwise start over with a (likely) different bucket, and increased chance factor. fChanceFactor *= 1.2; @@ -764,7 +767,7 @@ std::pair<CAddress, int64_t> AddrManImpl::Select_(bool newOnly) const // With probability GetChance() * fChanceFactor, return the entry. if (insecure_rand.randbits(30) < fChanceFactor * info.GetChance() * (1 << 30)) { LogPrint(BCLog::ADDRMAN, "Selected %s from new\n", info.ToString()); - return {info, info.nLastTry}; + return {info, info.m_last_try}; } // Otherwise start over with a (likely) different bucket, and increased chance factor. fChanceFactor *= 1.2; @@ -785,7 +788,7 @@ std::vector<CAddress> AddrManImpl::GetAddr_(size_t max_addresses, size_t max_pct } // gather a list of random nodes, skipping those of low quality - const int64_t now{GetAdjustedTime()}; + const auto now{Now<NodeSeconds>()}; std::vector<CAddress> addresses; for (unsigned int n = 0; n < vRandom.size(); n++) { if (addresses.size() >= nNodes) @@ -810,7 +813,7 @@ std::vector<CAddress> AddrManImpl::GetAddr_(size_t max_addresses, size_t max_pct return addresses; } -void AddrManImpl::Connected_(const CService& addr, int64_t nTime) +void AddrManImpl::Connected_(const CService& addr, NodeSeconds time) { AssertLockHeld(cs); @@ -823,9 +826,10 @@ void AddrManImpl::Connected_(const CService& addr, int64_t nTime) AddrInfo& info = *pinfo; // update info - int64_t nUpdateInterval = 20 * 60; - if (nTime - info.nTime > nUpdateInterval) - info.nTime = nTime; + const auto update_interval{20min}; + if (time - info.nTime > update_interval) { + info.nTime = time; + } } void AddrManImpl::SetServices_(const CService& addr, ServiceFlags nServices) @@ -870,22 +874,22 @@ void AddrManImpl::ResolveCollisions_() int id_old = vvTried[tried_bucket][tried_bucket_pos]; AddrInfo& info_old = mapInfo[id_old]; - const auto current_time{GetAdjustedTime()}; + const auto current_time{Now<NodeSeconds>()}; // Has successfully connected in last X hours - if (current_time - info_old.nLastSuccess < ADDRMAN_REPLACEMENT_HOURS*(60*60)) { + if (current_time - info_old.m_last_success < ADDRMAN_REPLACEMENT) { erase_collision = true; - } else if (current_time - info_old.nLastTry < ADDRMAN_REPLACEMENT_HOURS*(60*60)) { // attempted to connect and failed in last X hours + } else if (current_time - info_old.m_last_try < ADDRMAN_REPLACEMENT) { // attempted to connect and failed in last X hours // Give address at least 60 seconds to successfully connect - if (current_time - info_old.nLastTry > 60) { + if (current_time - info_old.m_last_try > 60s) { LogPrint(BCLog::ADDRMAN, "Replacing %s with %s in tried table\n", info_old.ToString(), info_new.ToString()); // Replaces an existing address already in the tried table with the new address Good_(info_new, false, current_time); erase_collision = true; } - } else if (current_time - info_new.nLastSuccess > ADDRMAN_TEST_WINDOW) { + } else if (current_time - info_new.m_last_success > ADDRMAN_TEST_WINDOW) { // If the collision hasn't resolved in some reasonable amount of time, // just evict the old entry -- we must not be able to // connect to it for some reason. @@ -894,7 +898,7 @@ void AddrManImpl::ResolveCollisions_() erase_collision = true; } } else { // Collision is not actually a collision anymore - Good_(info_new, false, GetAdjustedTime()); + Good_(info_new, false, Now<NodeSeconds>()); erase_collision = true; } } @@ -907,7 +911,7 @@ void AddrManImpl::ResolveCollisions_() } } -std::pair<CAddress, int64_t> AddrManImpl::SelectTriedCollision_() +std::pair<CAddress, NodeSeconds> AddrManImpl::SelectTriedCollision_() { AssertLockHeld(cs); @@ -932,7 +936,7 @@ std::pair<CAddress, int64_t> AddrManImpl::SelectTriedCollision_() int tried_bucket_pos = newInfo.GetBucketPosition(nKey, false, tried_bucket); const AddrInfo& info_old = mapInfo[vvTried[tried_bucket][tried_bucket_pos]]; - return {info_old, info_old.nLastTry}; + return {info_old, info_old.m_last_try}; } std::optional<AddressPosition> AddrManImpl::FindAddressEntry_(const CAddress& addr) @@ -990,8 +994,9 @@ int AddrManImpl::CheckAddrman() const int n = entry.first; const AddrInfo& info = entry.second; if (info.fInTried) { - if (!info.nLastSuccess) + if (!TicksSinceEpoch<std::chrono::seconds>(info.m_last_success)) { return -1; + } if (info.nRefCount) return -2; setTried.insert(n); @@ -1008,10 +1013,12 @@ int AddrManImpl::CheckAddrman() const } if (info.nRandomPos < 0 || (size_t)info.nRandomPos >= vRandom.size() || vRandom[info.nRandomPos] != n) return -14; - if (info.nLastTry < 0) + if (info.m_last_try < NodeSeconds{0s}) { return -6; - if (info.nLastSuccess < 0) + } + if (info.m_last_success < NodeSeconds{0s}) { return -8; + } } if (setTried.size() != (size_t)nTried) @@ -1067,29 +1074,29 @@ size_t AddrManImpl::size() const return vRandom.size(); } -bool AddrManImpl::Add(const std::vector<CAddress>& vAddr, const CNetAddr& source, int64_t nTimePenalty) +bool AddrManImpl::Add(const std::vector<CAddress>& vAddr, const CNetAddr& source, std::chrono::seconds time_penalty) { LOCK(cs); Check(); - auto ret = Add_(vAddr, source, nTimePenalty); + auto ret = Add_(vAddr, source, time_penalty); Check(); return ret; } -bool AddrManImpl::Good(const CService& addr, int64_t nTime) +bool AddrManImpl::Good(const CService& addr, NodeSeconds time) { LOCK(cs); Check(); - auto ret = Good_(addr, /*test_before_evict=*/true, nTime); + auto ret = Good_(addr, /*test_before_evict=*/true, time); Check(); return ret; } -void AddrManImpl::Attempt(const CService& addr, bool fCountFailure, int64_t nTime) +void AddrManImpl::Attempt(const CService& addr, bool fCountFailure, NodeSeconds time) { LOCK(cs); Check(); - Attempt_(addr, fCountFailure, nTime); + Attempt_(addr, fCountFailure, time); Check(); } @@ -1101,7 +1108,7 @@ void AddrManImpl::ResolveCollisions() Check(); } -std::pair<CAddress, int64_t> AddrManImpl::SelectTriedCollision() +std::pair<CAddress, NodeSeconds> AddrManImpl::SelectTriedCollision() { LOCK(cs); Check(); @@ -1110,7 +1117,7 @@ std::pair<CAddress, int64_t> AddrManImpl::SelectTriedCollision() return ret; } -std::pair<CAddress, int64_t> AddrManImpl::Select(bool newOnly) const +std::pair<CAddress, NodeSeconds> AddrManImpl::Select(bool newOnly) const { LOCK(cs); Check(); @@ -1128,11 +1135,11 @@ std::vector<CAddress> AddrManImpl::GetAddr(size_t max_addresses, size_t max_pct, return addresses; } -void AddrManImpl::Connected(const CService& addr, int64_t nTime) +void AddrManImpl::Connected(const CService& addr, NodeSeconds time) { LOCK(cs); Check(); - Connected_(addr, nTime); + Connected_(addr, time); Check(); } @@ -1184,19 +1191,19 @@ size_t AddrMan::size() const return m_impl->size(); } -bool AddrMan::Add(const std::vector<CAddress>& vAddr, const CNetAddr& source, int64_t nTimePenalty) +bool AddrMan::Add(const std::vector<CAddress>& vAddr, const CNetAddr& source, std::chrono::seconds time_penalty) { - return m_impl->Add(vAddr, source, nTimePenalty); + return m_impl->Add(vAddr, source, time_penalty); } -bool AddrMan::Good(const CService& addr, int64_t nTime) +bool AddrMan::Good(const CService& addr, NodeSeconds time) { - return m_impl->Good(addr, nTime); + return m_impl->Good(addr, time); } -void AddrMan::Attempt(const CService& addr, bool fCountFailure, int64_t nTime) +void AddrMan::Attempt(const CService& addr, bool fCountFailure, NodeSeconds time) { - m_impl->Attempt(addr, fCountFailure, nTime); + m_impl->Attempt(addr, fCountFailure, time); } void AddrMan::ResolveCollisions() @@ -1204,12 +1211,12 @@ void AddrMan::ResolveCollisions() m_impl->ResolveCollisions(); } -std::pair<CAddress, int64_t> AddrMan::SelectTriedCollision() +std::pair<CAddress, NodeSeconds> AddrMan::SelectTriedCollision() { return m_impl->SelectTriedCollision(); } -std::pair<CAddress, int64_t> AddrMan::Select(bool newOnly) const +std::pair<CAddress, NodeSeconds> AddrMan::Select(bool newOnly) const { return m_impl->Select(newOnly); } @@ -1219,9 +1226,9 @@ std::vector<CAddress> AddrMan::GetAddr(size_t max_addresses, size_t max_pct, std return m_impl->GetAddr(max_addresses, max_pct, network); } -void AddrMan::Connected(const CService& addr, int64_t nTime) +void AddrMan::Connected(const CService& addr, NodeSeconds time) { - m_impl->Connected(addr, nTime); + m_impl->Connected(addr, time); } void AddrMan::SetServices(const CService& addr, ServiceFlags nServices) diff --git a/src/addrman.h b/src/addrman.h index a0063e8a9c..5099c8c7a3 100644 --- a/src/addrman.h +++ b/src/addrman.h @@ -10,7 +10,7 @@ #include <netgroup.h> #include <protocol.h> #include <streams.h> -#include <timedata.h> +#include <util/time.h> #include <cstdint> #include <memory> @@ -107,23 +107,23 @@ public: * * @param[in] vAddr Address records to attempt to add. * @param[in] source The address of the node that sent us these addr records. - * @param[in] nTimePenalty A "time penalty" to apply to the address record's nTime. If a peer + * @param[in] time_penalty A "time penalty" to apply to the address record's nTime. If a peer * sends us an address record with nTime=n, then we'll add it to our - * addrman with nTime=(n - nTimePenalty). + * addrman with nTime=(n - time_penalty). * @return true if at least one address is successfully added. */ - bool Add(const std::vector<CAddress>& vAddr, const CNetAddr& source, int64_t nTimePenalty = 0); + bool Add(const std::vector<CAddress>& vAddr, const CNetAddr& source, std::chrono::seconds time_penalty = 0s); /** * Mark an address record as accessible and attempt to move it to addrman's tried table. * * @param[in] addr Address record to attempt to move to tried table. - * @param[in] nTime The time that we were last connected to this peer. + * @param[in] time The time that we were last connected to this peer. * @return true if the address is successfully moved from the new table to the tried table. */ - bool Good(const CService& addr, int64_t nTime = GetAdjustedTime()); + bool Good(const CService& addr, NodeSeconds time = Now<NodeSeconds>()); //! Mark an entry as connection attempted to. - void Attempt(const CService& addr, bool fCountFailure, int64_t nTime = GetAdjustedTime()); + void Attempt(const CService& addr, bool fCountFailure, NodeSeconds time = Now<NodeSeconds>()); //! See if any to-be-evicted tried table entries have been tested and if so resolve the collisions. void ResolveCollisions(); @@ -133,18 +133,18 @@ public: * attempting to evict. * * @return CAddress The record for the selected tried peer. - * int64_t The last time we attempted to connect to that peer. + * seconds The last time we attempted to connect to that peer. */ - std::pair<CAddress, int64_t> SelectTriedCollision(); + std::pair<CAddress, NodeSeconds> SelectTriedCollision(); /** * Choose an address to connect to. * * @param[in] newOnly Whether to only select addresses from the new table. * @return CAddress The record for the selected peer. - * int64_t The last time we attempted to connect to that peer. + * seconds The last time we attempted to connect to that peer. */ - std::pair<CAddress, int64_t> Select(bool newOnly = false) const; + std::pair<CAddress, NodeSeconds> Select(bool newOnly = false) const; /** * Return all or many randomly selected addresses, optionally by network. @@ -166,9 +166,9 @@ public: * not leak information about currently connected peers. * * @param[in] addr The address of the peer we were connected to - * @param[in] nTime The time that we were last connected to this peer + * @param[in] time The time that we were last connected to this peer */ - void Connected(const CService& addr, int64_t nTime = GetAdjustedTime()); + void Connected(const CService& addr, NodeSeconds time = Now<NodeSeconds>()); //! Update an entry's service bits. void SetServices(const CService& addr, ServiceFlags nServices); diff --git a/src/addrman_impl.h b/src/addrman_impl.h index 9d98cdde54..376e79f49f 100644 --- a/src/addrman_impl.h +++ b/src/addrman_impl.h @@ -11,7 +11,9 @@ #include <protocol.h> #include <serialize.h> #include <sync.h> +#include <timedata.h> #include <uint256.h> +#include <util/time.h> #include <cstdint> #include <optional> @@ -38,16 +40,16 @@ class AddrInfo : public CAddress { public: //! last try whatsoever by us (memory only) - int64_t nLastTry{0}; + NodeSeconds m_last_try{0s}; //! last counted attempt (memory only) - int64_t nLastCountAttempt{0}; + NodeSeconds m_last_count_attempt{0s}; //! where knowledge about this address first came from CNetAddr source; //! last successful connection by us - int64_t nLastSuccess{0}; + NodeSeconds m_last_success{0s}; //! connection attempts since last successful attempt int nAttempts{0}; @@ -64,7 +66,7 @@ public: SERIALIZE_METHODS(AddrInfo, obj) { READWRITEAS(CAddress, obj); - READWRITE(obj.source, obj.nLastSuccess, obj.nAttempts); + READWRITE(obj.source, Using<ChronoFormatter<int64_t>>(obj.m_last_success), obj.nAttempts); } AddrInfo(const CAddress &addrIn, const CNetAddr &addrSource) : CAddress(addrIn), source(addrSource) @@ -91,10 +93,10 @@ public: int GetBucketPosition(const uint256 &nKey, bool fNew, int nBucket) const; //! Determine whether the statistics about this entry are bad enough so that it can just be deleted - bool IsTerrible(int64_t nNow = GetAdjustedTime()) const; + bool IsTerrible(NodeSeconds now = Now<NodeSeconds>()) const; //! Calculate the relative chance this entry should be given when selecting nodes to connect to - double GetChance(int64_t nNow = GetAdjustedTime()) const; + double GetChance(NodeSeconds now = Now<NodeSeconds>()) const; }; class AddrManImpl @@ -112,26 +114,26 @@ public: size_t size() const EXCLUSIVE_LOCKS_REQUIRED(!cs); - bool Add(const std::vector<CAddress>& vAddr, const CNetAddr& source, int64_t nTimePenalty) + bool Add(const std::vector<CAddress>& vAddr, const CNetAddr& source, std::chrono::seconds time_penalty) EXCLUSIVE_LOCKS_REQUIRED(!cs); - bool Good(const CService& addr, int64_t nTime) + bool Good(const CService& addr, NodeSeconds time) EXCLUSIVE_LOCKS_REQUIRED(!cs); - void Attempt(const CService& addr, bool fCountFailure, int64_t nTime) + void Attempt(const CService& addr, bool fCountFailure, NodeSeconds time) EXCLUSIVE_LOCKS_REQUIRED(!cs); void ResolveCollisions() EXCLUSIVE_LOCKS_REQUIRED(!cs); - std::pair<CAddress, int64_t> SelectTriedCollision() EXCLUSIVE_LOCKS_REQUIRED(!cs); + std::pair<CAddress, NodeSeconds> SelectTriedCollision() EXCLUSIVE_LOCKS_REQUIRED(!cs); - std::pair<CAddress, int64_t> Select(bool newOnly) const + std::pair<CAddress, NodeSeconds> Select(bool newOnly) const EXCLUSIVE_LOCKS_REQUIRED(!cs); std::vector<CAddress> GetAddr(size_t max_addresses, size_t max_pct, std::optional<Network> network) const EXCLUSIVE_LOCKS_REQUIRED(!cs); - void Connected(const CService& addr, int64_t nTime) + void Connected(const CService& addr, NodeSeconds time) EXCLUSIVE_LOCKS_REQUIRED(!cs); void SetServices(const CService& addr, ServiceFlags nServices) @@ -202,7 +204,7 @@ private: int vvNew[ADDRMAN_NEW_BUCKET_COUNT][ADDRMAN_BUCKET_SIZE] GUARDED_BY(cs); //! last time Good was called (memory only). Initially set to 1 so that "never" is strictly worse. - int64_t nLastGood GUARDED_BY(cs){1}; + NodeSeconds m_last_good GUARDED_BY(cs){1s}; //! Holds addrs inserted into tried table that collide with existing entries. Test-before-evict discipline used to resolve these collisions. std::set<int> m_tried_collisions; @@ -233,25 +235,25 @@ private: /** Attempt to add a single address to addrman's new table. * @see AddrMan::Add() for parameters. */ - bool AddSingle(const CAddress& addr, const CNetAddr& source, int64_t nTimePenalty) EXCLUSIVE_LOCKS_REQUIRED(cs); + bool AddSingle(const CAddress& addr, const CNetAddr& source, std::chrono::seconds time_penalty) EXCLUSIVE_LOCKS_REQUIRED(cs); - bool Good_(const CService& addr, bool test_before_evict, int64_t time) EXCLUSIVE_LOCKS_REQUIRED(cs); + bool Good_(const CService& addr, bool test_before_evict, NodeSeconds time) EXCLUSIVE_LOCKS_REQUIRED(cs); - bool Add_(const std::vector<CAddress> &vAddr, const CNetAddr& source, int64_t nTimePenalty) EXCLUSIVE_LOCKS_REQUIRED(cs); + bool Add_(const std::vector<CAddress>& vAddr, const CNetAddr& source, std::chrono::seconds time_penalty) EXCLUSIVE_LOCKS_REQUIRED(cs); - void Attempt_(const CService& addr, bool fCountFailure, int64_t nTime) EXCLUSIVE_LOCKS_REQUIRED(cs); + void Attempt_(const CService& addr, bool fCountFailure, NodeSeconds time) EXCLUSIVE_LOCKS_REQUIRED(cs); - std::pair<CAddress, int64_t> Select_(bool newOnly) const EXCLUSIVE_LOCKS_REQUIRED(cs); + std::pair<CAddress, NodeSeconds> Select_(bool newOnly) const EXCLUSIVE_LOCKS_REQUIRED(cs); std::vector<CAddress> GetAddr_(size_t max_addresses, size_t max_pct, std::optional<Network> network) const EXCLUSIVE_LOCKS_REQUIRED(cs); - void Connected_(const CService& addr, int64_t nTime) EXCLUSIVE_LOCKS_REQUIRED(cs); + void Connected_(const CService& addr, NodeSeconds time) EXCLUSIVE_LOCKS_REQUIRED(cs); void SetServices_(const CService& addr, ServiceFlags nServices) EXCLUSIVE_LOCKS_REQUIRED(cs); void ResolveCollisions_() EXCLUSIVE_LOCKS_REQUIRED(cs); - std::pair<CAddress, int64_t> SelectTriedCollision_() EXCLUSIVE_LOCKS_REQUIRED(cs); + std::pair<CAddress, NodeSeconds> SelectTriedCollision_() EXCLUSIVE_LOCKS_REQUIRED(cs); std::optional<AddressPosition> FindAddressEntry_(const CAddress& addr) EXCLUSIVE_LOCKS_REQUIRED(cs); diff --git a/src/banman.cpp b/src/banman.cpp index 2a6e0e010f..3cd646c148 100644 --- a/src/banman.cpp +++ b/src/banman.cpp @@ -6,7 +6,7 @@ #include <banman.h> #include <netaddress.h> -#include <node/ui_interface.h> +#include <node/interface_ui.h> #include <sync.h> #include <util/system.h> #include <util/time.h> @@ -31,12 +31,12 @@ void BanMan::LoadBanlist() if (m_client_interface) m_client_interface->InitMessage(_("Loading banlist…").translated); - int64_t n_start = GetTimeMillis(); + const auto start{SteadyClock::now()}; if (m_ban_db.Read(m_banned)) { SweepBanned(); // sweep out unused entries LogPrint(BCLog::NET, "Loaded %d banned node addresses/subnets %dms\n", m_banned.size(), - GetTimeMillis() - n_start); + Ticks<std::chrono::milliseconds>(SteadyClock::now() - start)); } else { LogPrintf("Recreating the banlist database\n"); m_banned = {}; @@ -58,13 +58,13 @@ void BanMan::DumpBanlist() SetBannedSetDirty(false); } - int64_t n_start = GetTimeMillis(); + const auto start{SteadyClock::now()}; if (!m_ban_db.Write(banmap)) { SetBannedSetDirty(true); } LogPrint(BCLog::NET, "Flushed %d banned node addresses/subnets to disk %dms\n", banmap.size(), - GetTimeMillis() - n_start); + Ticks<std::chrono::milliseconds>(SteadyClock::now() - start)); } void BanMan::ClearBanned() diff --git a/src/bech32.cpp b/src/bech32.cpp index dce9b2e4cc..8e0025b8f4 100644 --- a/src/bech32.cpp +++ b/src/bech32.cpp @@ -241,7 +241,7 @@ constexpr std::array<uint32_t, 25> GenerateSyndromeConstants() { std::array<uint32_t, 25> SYNDROME_CONSTS{}; for (int k = 1; k < 6; ++k) { for (int shift = 0; shift < 5; ++shift) { - int16_t b = GF1024_LOG.at(1 << shift); + int16_t b = GF1024_LOG.at(size_t{1} << shift); int16_t c0 = GF1024_EXP.at((997*k + b) % 1023); int16_t c1 = GF1024_EXP.at((998*k + b) % 1023); int16_t c2 = GF1024_EXP.at((999*k + b) % 1023); diff --git a/src/bench/addrman.cpp b/src/bench/addrman.cpp index 76300f4db8..f14d1f89b6 100644 --- a/src/bench/addrman.cpp +++ b/src/bench/addrman.cpp @@ -43,7 +43,7 @@ static void CreateAddresses() CAddress ret(CService(addr, port), NODE_NETWORK); - ret.nTime = GetAdjustedTime(); + ret.nTime = Now<NodeSeconds>(); return ret; }; diff --git a/src/bench/checkblock.cpp b/src/bench/checkblock.cpp index 52e5cb743f..53aa470042 100644 --- a/src/bench/checkblock.cpp +++ b/src/bench/checkblock.cpp @@ -8,6 +8,7 @@ #include <chainparams.h> #include <consensus/validation.h> #include <streams.h> +#include <util/system.h> #include <validation.h> // These are the two major time-sinks which happen after we have fully received diff --git a/src/bench/coin_selection.cpp b/src/bench/coin_selection.cpp index b2958bcc9f..6ada28115e 100644 --- a/src/bench/coin_selection.cpp +++ b/src/bench/coin_selection.cpp @@ -56,9 +56,10 @@ static void CoinSelection(benchmark::Bench& bench) addCoin(3 * COIN, wallet, wtxs); // Create coins - std::vector<COutput> coins; + wallet::CoinsResult available_coins; for (const auto& wtx : wtxs) { - coins.emplace_back(COutPoint(wtx->GetHash(), 0), wtx->tx->vout.at(0), /*depth=*/6 * 24, GetTxSpendSize(wallet, *wtx, 0), /*spendable=*/true, /*solvable=*/true, /*safe=*/true, wtx->GetTxTime(), /*from_me=*/true, /*fees=*/ 0); + const auto txout = wtx->tx->vout.at(0); + available_coins.coins[OutputType::BECH32].emplace_back(COutPoint(wtx->GetHash(), 0), txout, /*depth=*/6 * 24, CalculateMaximumSignedInputSize(txout, &wallet, /*coin_control=*/nullptr), /*spendable=*/true, /*solvable=*/true, /*safe=*/true, wtx->GetTxTime(), /*from_me=*/true, /*fees=*/ 0); } const CoinEligibilityFilter filter_standard(1, 6, 0); @@ -75,7 +76,7 @@ static void CoinSelection(benchmark::Bench& bench) /*avoid_partial=*/ false, }; bench.run([&] { - auto result = AttemptSelection(wallet, 1003 * COIN, filter_standard, coins, coin_selection_params); + auto result = AttemptSelection(wallet, 1003 * COIN, filter_standard, available_coins, coin_selection_params, /*allow_mixed_output_types=*/true); assert(result); assert(result->GetSelectedValue() == 1003 * COIN); assert(result->GetInputSet().size() == 2); diff --git a/src/bench/descriptors.cpp b/src/bench/descriptors.cpp new file mode 100644 index 0000000000..5c868a8573 --- /dev/null +++ b/src/bench/descriptors.cpp @@ -0,0 +1,30 @@ +// Copyright (c) 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. + +#include <bench/bench.h> +#include <key.h> +#include <script/descriptor.h> +#include <script/standard.h> + +#include <string> +#include <utility> + +static void ExpandDescriptor(benchmark::Bench& bench) +{ + const auto desc_str = "sh(wsh(multi(16,03669b8afcec803a0d323e9a17f3ea8e68e8abe5a278020a929adbec52421adbd0,0260b2003c386519fc9eadf2b5cf124dd8eea4c4e68d5e154050a9346ea98ce600,0362a74e399c39ed5593852a30147f2959b56bb827dfa3e60e464b02ccf87dc5e8,0261345b53de74a4d721ef877c255429961b7e43714171ac06168d7e08c542a8b8,02da72e8b46901a65d4374fe6315538d8f368557dda3a1dcf9ea903f3afe7314c8,0318c82dd0b53fd3a932d16e0ba9e278fcc937c582d5781be626ff16e201f72286,0297ccef1ef99f9d73dec9ad37476ddb232f1238aff877af19e72ba04493361009,02e502cfd5c3f972fe9a3e2a18827820638f96b6f347e54d63deb839011fd5765d,03e687710f0e3ebe81c1037074da939d409c0025f17eb86adb9427d28f0f7ae0e9,02c04d3a5274952acdbc76987f3184b346a483d43be40874624b29e3692c1df5af,02ed06e0f418b5b43a7ec01d1d7d27290fa15f75771cb69b642a51471c29c84acd,036d46073cbb9ffee90473f3da429abc8de7f8751199da44485682a989a4bebb24,02f5d1ff7c9029a80a4e36b9a5497027ef7f3e73384a4a94fbfe7c4e9164eec8bc,02e41deffd1b7cce11cde209a781adcffdabd1b91c0ba0375857a2bfd9302419f3,02d76625f7956a7fc505ab02556c23ee72d832f1bac391bcd2d3abce5710a13d06,0399eb0a5487515802dc14544cf10b3666623762fbed2ec38a3975716e2c29c232)))"; + const std::pair<int64_t, int64_t> range = {0, 1000}; + FlatSigningProvider provider; + std::string error; + auto desc = Parse(desc_str, provider, error); + + bench.run([&] { + for (int i = range.first; i <= range.second; ++i) { + std::vector<CScript> scripts; + bool success = desc->Expand(i, provider, scripts, provider); + assert(success); + } + }); +} + +BENCHMARK(ExpandDescriptor); diff --git a/src/bench/gcs_filter.cpp b/src/bench/gcs_filter.cpp index 607e4392b7..80babb213b 100644 --- a/src/bench/gcs_filter.cpp +++ b/src/bench/gcs_filter.cpp @@ -5,39 +5,84 @@ #include <bench/bench.h> #include <blockfilter.h> -static void ConstructGCSFilter(benchmark::Bench& bench) +static const GCSFilter::ElementSet GenerateGCSTestElements() { GCSFilter::ElementSet elements; - for (int i = 0; i < 10000; ++i) { + + // Testing the benchmarks with different number of elements show that a filter + // with at least 100,000 elements results in benchmarks that have the same + // ns/op. This makes it easy to reason about how long (in nanoseconds) a single + // filter element takes to process. + for (int i = 0; i < 100000; ++i) { GCSFilter::Element element(32); element[0] = static_cast<unsigned char>(i); element[1] = static_cast<unsigned char>(i >> 8); elements.insert(std::move(element)); } + return elements; +} + +static void GCSBlockFilterGetHash(benchmark::Bench& bench) +{ + auto elements = GenerateGCSTestElements(); + + GCSFilter filter({0, 0, BASIC_FILTER_P, BASIC_FILTER_M}, elements); + BlockFilter block_filter(BlockFilterType::BASIC, {}, filter.GetEncoded(), /*skip_decode_check=*/false); + + bench.run([&] { + block_filter.GetHash(); + }); +} + +static void GCSFilterConstruct(benchmark::Bench& bench) +{ + auto elements = GenerateGCSTestElements(); + uint64_t siphash_k0 = 0; - bench.batch(elements.size()).unit("elem").run([&] { - GCSFilter filter({siphash_k0, 0, 20, 1 << 20}, elements); + bench.run([&]{ + GCSFilter filter({siphash_k0, 0, BASIC_FILTER_P, BASIC_FILTER_M}, elements); siphash_k0++; }); } -static void MatchGCSFilter(benchmark::Bench& bench) +static void GCSFilterDecode(benchmark::Bench& bench) { - GCSFilter::ElementSet elements; - for (int i = 0; i < 10000; ++i) { - GCSFilter::Element element(32); - element[0] = static_cast<unsigned char>(i); - element[1] = static_cast<unsigned char>(i >> 8); - elements.insert(std::move(element)); - } - GCSFilter filter({0, 0, 20, 1 << 20}, elements); + auto elements = GenerateGCSTestElements(); - bench.unit("elem").run([&] { - filter.Match(GCSFilter::Element()); + GCSFilter filter({0, 0, BASIC_FILTER_P, BASIC_FILTER_M}, elements); + auto encoded = filter.GetEncoded(); + + bench.run([&] { + GCSFilter filter({0, 0, BASIC_FILTER_P, BASIC_FILTER_M}, encoded, /*skip_decode_check=*/false); }); } -BENCHMARK(ConstructGCSFilter); -BENCHMARK(MatchGCSFilter); +static void GCSFilterDecodeSkipCheck(benchmark::Bench& bench) +{ + auto elements = GenerateGCSTestElements(); + + GCSFilter filter({0, 0, BASIC_FILTER_P, BASIC_FILTER_M}, elements); + auto encoded = filter.GetEncoded(); + + bench.run([&] { + GCSFilter filter({0, 0, BASIC_FILTER_P, BASIC_FILTER_M}, encoded, /*skip_decode_check=*/true); + }); +} + +static void GCSFilterMatch(benchmark::Bench& bench) +{ + auto elements = GenerateGCSTestElements(); + + GCSFilter filter({0, 0, BASIC_FILTER_P, BASIC_FILTER_M}, elements); + + bench.run([&] { + filter.Match(GCSFilter::Element()); + }); +} +BENCHMARK(GCSBlockFilterGetHash); +BENCHMARK(GCSFilterConstruct); +BENCHMARK(GCSFilterDecode); +BENCHMARK(GCSFilterDecodeSkipCheck); +BENCHMARK(GCSFilterMatch); diff --git a/src/bench/mempool_eviction.cpp b/src/bench/mempool_eviction.cpp index e80b9e1ac2..60d991fab9 100644 --- a/src/bench/mempool_eviction.cpp +++ b/src/bench/mempool_eviction.cpp @@ -108,7 +108,7 @@ static void MempoolEviction(benchmark::Bench& bench) tx7.vout[1].scriptPubKey = CScript() << OP_7 << OP_EQUAL; tx7.vout[1].nValue = 10 * COIN; - CTxMemPool pool; + CTxMemPool& pool = *Assert(testing_setup->m_node.mempool); LOCK2(cs_main, pool.cs); // Create transaction references outside the "hot loop" const CTransactionRef tx1_r{MakeTransactionRef(tx1)}; diff --git a/src/bench/nanobench.h b/src/bench/nanobench.h index 27df08fb69..916a7d61ef 100644 --- a/src/bench/nanobench.h +++ b/src/bench/nanobench.h @@ -858,7 +858,7 @@ public: * @brief Retrieves all benchmark results collected by the bench object so far. * * Each call to run() generates a Result that is stored within the Bench instance. This is mostly for advanced users who want to - * see all the nitty gritty detials. + * see all the nitty gritty details. * * @return All results collected so far. */ diff --git a/src/bench/rpc_mempool.cpp b/src/bench/rpc_mempool.cpp index 6e322ba6aa..0e6fdae3d7 100644 --- a/src/bench/rpc_mempool.cpp +++ b/src/bench/rpc_mempool.cpp @@ -3,7 +3,9 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include <bench/bench.h> +#include <chainparamsbase.h> #include <rpc/mempool.h> +#include <test/util/setup_common.h> #include <txmempool.h> #include <univalue.h> @@ -17,7 +19,8 @@ static void AddTx(const CTransactionRef& tx, const CAmount& fee, CTxMemPool& poo static void RpcMempool(benchmark::Bench& bench) { - CTxMemPool pool; + const auto testing_setup = MakeNoLogFileContext<const ChainTestingSetup>(CBaseChainParams::MAIN); + CTxMemPool& pool = *Assert(testing_setup->m_node.mempool); LOCK2(cs_main, pool.cs); for (int i = 0; i < 1000; ++i) { diff --git a/src/bench/wallet_loading.cpp b/src/bench/wallet_loading.cpp index 38d3460001..27e4dd015d 100644 --- a/src/bench/wallet_loading.cpp +++ b/src/bench/wallet_loading.cpp @@ -17,20 +17,17 @@ #include <optional> using wallet::CWallet; +using wallet::DatabaseFormat; using wallet::DatabaseOptions; -using wallet::DatabaseStatus; -using wallet::ISMINE_SPENDABLE; -using wallet::MakeWalletDatabase; +using wallet::TxStateInactive; using wallet::WALLET_FLAG_DESCRIPTORS; using wallet::WalletContext; +using wallet::WalletDatabase; -static const std::shared_ptr<CWallet> BenchLoadWallet(WalletContext& context, DatabaseOptions& options) +static const std::shared_ptr<CWallet> BenchLoadWallet(std::unique_ptr<WalletDatabase> database, WalletContext& context, DatabaseOptions& options) { - DatabaseStatus status; bilingual_str error; std::vector<bilingual_str> warnings; - auto database = MakeWalletDatabase("", options, status, error); - assert(database); auto wallet = CWallet::Create(context, "", std::move(database), options.create_flags, error, warnings); NotifyWalletLoaded(context, wallet); if (context.chain) { @@ -46,9 +43,43 @@ static void BenchUnloadWallet(std::shared_ptr<CWallet>&& wallet) UnloadWallet(std::move(wallet)); } +static void AddTx(CWallet& wallet) +{ + CMutableTransaction mtx; + mtx.vout.push_back({COIN, GetScriptForDestination(*Assert(wallet.GetNewDestination(OutputType::BECH32, "")))}); + mtx.vin.push_back(CTxIn()); + + wallet.AddToWallet(MakeTransactionRef(mtx), TxStateInactive{}); +} + +static std::unique_ptr<WalletDatabase> DuplicateMockDatabase(WalletDatabase& database, DatabaseOptions& options) +{ + auto new_database = CreateMockWalletDatabase(options); + + // Get a cursor to the original database + auto batch = database.MakeBatch(); + batch->StartCursor(); + + // Get a batch for the new database + auto new_batch = new_database->MakeBatch(); + + // Read all records from the original database and write them to the new one + while (true) { + CDataStream key(SER_DISK, CLIENT_VERSION); + CDataStream value(SER_DISK, CLIENT_VERSION); + bool complete; + batch->ReadAtCursor(key, value, complete); + if (complete) break; + new_batch->Write(key, value); + } + + return new_database; +} + static void WalletLoading(benchmark::Bench& bench, bool legacy_wallet) { const auto test_setup = MakeNoLogFileContext<TestingSetup>(); + test_setup->m_args.ForceSetArg("-unsafesqlitesync", "1"); WalletContext context; context.args = &test_setup->m_args; @@ -57,27 +88,40 @@ static void WalletLoading(benchmark::Bench& bench, bool legacy_wallet) // Setup the wallet // Loading the wallet will also create it DatabaseOptions options; - if (!legacy_wallet) options.create_flags = WALLET_FLAG_DESCRIPTORS; - auto wallet = BenchLoadWallet(context, options); + if (legacy_wallet) { + options.require_format = DatabaseFormat::BERKELEY; + } else { + options.create_flags = WALLET_FLAG_DESCRIPTORS; + options.require_format = DatabaseFormat::SQLITE; + } + auto database = CreateMockWalletDatabase(options); + auto wallet = BenchLoadWallet(std::move(database), context, options); // Generate a bunch of transactions and addresses to put into the wallet - for (int i = 0; i < 5000; ++i) { - generatetoaddress(test_setup->m_node, getnewaddress(*wallet)); + for (int i = 0; i < 1000; ++i) { + AddTx(*wallet); } + database = DuplicateMockDatabase(wallet->GetDatabase(), options); + // reload the wallet for the actual benchmark BenchUnloadWallet(std::move(wallet)); - bench.minEpochIterations(10).run([&] { - wallet = BenchLoadWallet(context, options); + bench.epochs(5).run([&] { + wallet = BenchLoadWallet(std::move(database), context, options); // Cleanup + database = DuplicateMockDatabase(wallet->GetDatabase(), options); BenchUnloadWallet(std::move(wallet)); }); } +#ifdef USE_BDB static void WalletLoadingLegacy(benchmark::Bench& bench) { WalletLoading(bench, /*legacy_wallet=*/true); } -static void WalletLoadingDescriptors(benchmark::Bench& bench) { WalletLoading(bench, /*legacy_wallet=*/false); } - BENCHMARK(WalletLoadingLegacy); +#endif + +#ifdef USE_SQLITE +static void WalletLoadingDescriptors(benchmark::Bench& bench) { WalletLoading(bench, /*legacy_wallet=*/false); } BENCHMARK(WalletLoadingDescriptors); +#endif diff --git a/src/bitcoin-chainstate.cpp b/src/bitcoin-chainstate.cpp index 1817aa1a53..d972b71a65 100644 --- a/src/bitcoin-chainstate.cpp +++ b/src/bitcoin-chainstate.cpp @@ -13,11 +13,13 @@ #include <kernel/checks.h> #include <kernel/context.h> +#include <kernel/validation_cache_sizes.h> #include <chainparams.h> #include <consensus/validation.h> #include <core_io.h> #include <node/blockstorage.h> +#include <node/caches.h> #include <node/chainstate.h> #include <scheduler.h> #include <script/sigcache.h> @@ -61,8 +63,9 @@ int main(int argc, char* argv[]) // Necessary for CheckInputScripts (eventually called by ProcessNewBlock), // which will try the script cache first and fall back to actually // performing the check with the signature cache. - InitSignatureCache(); - InitScriptExecutionCache(); + kernel::ValidationCacheSizes validation_cache_sizes{}; + Assert(InitSignatureCache(validation_cache_sizes.signature_cache_bytes)); + Assert(InitScriptExecutionCache(validation_cache_sizes.script_execution_cache_bytes)); // SETUP: Scheduling and Background Signals @@ -78,38 +81,30 @@ int main(int argc, char* argv[]) // SETUP: Chainstate const ChainstateManager::Options chainman_opts{ - chainparams, - static_cast<int64_t(*)()>(GetTime), + .chainparams = chainparams, + .adjusted_time_callback = NodeClock::now, }; ChainstateManager chainman{chainman_opts}; - auto rv = node::LoadChainstate(false, - std::ref(chainman), - nullptr, - false, - false, - 2 << 20, - 2 << 22, - (450 << 20) - (2 << 20) - (2 << 22), - false, - false, - []() { return false; }); - if (rv.has_value()) { + node::CacheSizes cache_sizes; + cache_sizes.block_tree_db = 2 << 20; + cache_sizes.coins_db = 2 << 22; + cache_sizes.coins = (450 << 20) - (2 << 20) - (2 << 22); + node::ChainstateLoadOptions options; + options.check_interrupt = [] { return false; }; + auto [status, error] = node::LoadChainstate(chainman, cache_sizes, options); + if (status != node::ChainstateLoadStatus::SUCCESS) { std::cerr << "Failed to load Chain state from your datadir." << std::endl; goto epilogue; } else { - auto maybe_verify_error = node::VerifyLoadedChainstate(std::ref(chainman), - false, - false, - DEFAULT_CHECKBLOCKS, - DEFAULT_CHECKLEVEL); - if (maybe_verify_error.has_value()) { + std::tie(status, error) = node::VerifyLoadedChainstate(chainman, options); + if (status != node::ChainstateLoadStatus::SUCCESS) { std::cerr << "Failed to verify loaded Chain state from your datadir." << std::endl; goto epilogue; } } - for (CChainState* chainstate : WITH_LOCK(::cs_main, return chainman.GetAll())) { + for (Chainstate* chainstate : WITH_LOCK(::cs_main, return chainman.GetAll())) { BlockValidationState state; if (!chainstate->ActivateBestChain(state, nullptr)) { std::cerr << "Failed to connect best block (" << state.ToString() << ")" << std::endl; @@ -120,12 +115,14 @@ int main(int argc, char* argv[]) // Main program logic starts here std::cout << "Hello! I'm going to print out some information about your datadir." << std::endl - << "\t" << "Path: " << gArgs.GetDataDirNet() << std::endl + << "\t" << "Path: " << gArgs.GetDataDirNet() << std::endl; + { + LOCK(chainman.GetMutex()); + std::cout << "\t" << "Reindexing: " << std::boolalpha << node::fReindex.load() << std::noboolalpha << std::endl << "\t" << "Snapshot Active: " << std::boolalpha << chainman.IsSnapshotActive() << std::noboolalpha << std::endl << "\t" << "Active Height: " << chainman.ActiveHeight() << std::endl << "\t" << "Active IBD: " << std::boolalpha << chainman.ActiveChainstate().IsInitialBlockDownload() << std::noboolalpha << std::endl; - { CBlockIndex* tip = chainman.ActiveTip(); if (tip) { std::cout << "\t" << tip->ToString() << std::endl; @@ -198,7 +195,7 @@ int main(int argc, char* argv[]) bool new_block; auto sc = std::make_shared<submitblock_StateCatcher>(block.GetHash()); RegisterSharedValidationInterface(sc); - bool accepted = chainman.ProcessNewBlock(blockptr, /*force_processing=*/true, /*new_block=*/&new_block); + bool accepted = chainman.ProcessNewBlock(blockptr, /*force_processing=*/true, /*min_pow_checked=*/true, /*new_block=*/&new_block); UnregisterSharedValidationInterface(sc); if (!new_block && accepted) { std::cerr << "duplicate" << std::endl; @@ -213,6 +210,9 @@ int main(int argc, char* argv[]) case BlockValidationResult::BLOCK_RESULT_UNSET: std::cerr << "initial value. Block has not yet been rejected" << std::endl; break; + case BlockValidationResult::BLOCK_HEADER_LOW_WORK: + std::cerr << "the block header may be on a too-little-work chain" << std::endl; + break; case BlockValidationResult::BLOCK_CONSENSUS: std::cerr << "invalid by consensus rules (excluding any below reasons)" << std::endl; break; @@ -253,7 +253,7 @@ epilogue: GetMainSignals().FlushBackgroundCallbacks(); { LOCK(cs_main); - for (CChainState* chainstate : chainman.GetAll()) { + for (Chainstate* chainstate : chainman.GetAll()) { if (chainstate->CanFlushToDisk()) { chainstate->ForceFlushStateToDisk(); chainstate->ResetCoinsViews(); diff --git a/src/bitcoin-cli.cpp b/src/bitcoin-cli.cpp index b9e5a81f8d..e64e2202ba 100644 --- a/src/bitcoin-cli.cpp +++ b/src/bitcoin-cli.cpp @@ -9,6 +9,7 @@ #include <chainparamsbase.h> #include <clientversion.h> +#include <compat/compat.h> #include <compat/stdin.h> #include <policy/feerate.h> #include <rpc/client.h> @@ -806,7 +807,7 @@ static UniValue CallRPC(BaseRequestHandler* rh, const std::string& strMethod, co if (failedToGetAuthCookie) { throw std::runtime_error(strprintf( "Could not locate RPC credentials. No authentication cookie could be found, and RPC password is not set. See -rpcpassword and -stdinrpcpass. Configuration file: (%s)", - fs::PathToString(GetConfigFile(gArgs.GetArg("-conf", BITCOIN_CONF_FILENAME))))); + fs::PathToString(GetConfigFile(gArgs.GetPathArg("-conf", BITCOIN_CONF_FILENAME))))); } else { throw std::runtime_error("Authorization failed: Incorrect rpcuser or rpcpassword"); } @@ -910,7 +911,7 @@ static void GetWalletBalances(UniValue& result) UniValue balances(UniValue::VOBJ); for (const UniValue& wallet : wallets.getValues()) { - const std::string wallet_name = wallet.get_str(); + const std::string& wallet_name = wallet.get_str(); const UniValue getbalances = ConnectAndCallRPC(&rh, "getbalances", /* args=*/{}, wallet_name); const UniValue& balance = find_value(getbalances, "result")["mine"]["trusted"]; balances.pushKV(wallet_name, balance); @@ -1212,19 +1213,11 @@ static int CommandLineRPC(int argc, char *argv[]) return nRet; } -#ifdef WIN32 -// Export main() and ensure working ASLR on Windows. -// Exporting a symbol will prevent the linker from stripping -// the .reloc section from the binary, which is a requirement -// for ASLR. This is a temporary workaround until a fixed -// version of binutils is used for releases. -__declspec(dllexport) int main(int argc, char* argv[]) +MAIN_FUNCTION { +#ifdef WIN32 util::WinCmdLineArgs winArgs; std::tie(argc, argv) = winArgs.get(); -#else -int main(int argc, char* argv[]) -{ #endif SetupEnvironment(); if (!SetupNetworking()) { diff --git a/src/bitcoin-tx.cpp b/src/bitcoin-tx.cpp index bcefb3f18e..010cac5920 100644 --- a/src/bitcoin-tx.cpp +++ b/src/bitcoin-tx.cpp @@ -8,13 +8,13 @@ #include <clientversion.h> #include <coins.h> +#include <compat/compat.h> #include <consensus/amount.h> #include <consensus/consensus.h> #include <core_io.h> #include <key_io.h> #include <fs.h> #include <policy/policy.h> -#include <policy/rbf.h> #include <primitives/transaction.h> #include <script/script.h> #include <script/sign.h> @@ -27,9 +27,9 @@ #include <util/system.h> #include <util/translation.h> +#include <cstdio> #include <functional> #include <memory> -#include <stdio.h> static bool fCreateBlank; static std::map<std::string,UniValue> registers; @@ -595,7 +595,7 @@ static void MutateTxSign(CMutableTransaction& tx, const std::string& flagStr) UniValue prevtxsObj = registers["prevtxs"]; { for (unsigned int previdx = 0; previdx < prevtxsObj.size(); previdx++) { - UniValue prevOut = prevtxsObj[previdx]; + const UniValue& prevOut = prevtxsObj[previdx]; if (!prevOut.isObject()) throw std::runtime_error("expected prevtxs internal object"); @@ -854,7 +854,7 @@ static int CommandLineRawTx(int argc, char* argv[]) return nRet; } -int main(int argc, char* argv[]) +MAIN_FUNCTION { SetupEnvironment(); diff --git a/src/bitcoin-util.cpp b/src/bitcoin-util.cpp index 1aeac3cef0..fb184c0486 100644 --- a/src/bitcoin-util.cpp +++ b/src/bitcoin-util.cpp @@ -11,6 +11,7 @@ #include <chainparams.h> #include <chainparamsbase.h> #include <clientversion.h> +#include <compat/compat.h> #include <core_io.h> #include <streams.h> #include <util/system.h> @@ -142,16 +143,7 @@ static int Grind(const std::vector<std::string>& args, std::string& strPrint) return EXIT_SUCCESS; } -#ifdef WIN32 -// Export main() and ensure working ASLR on Windows. -// Exporting a symbol will prevent the linker from stripping -// the .reloc section from the binary, which is a requirement -// for ASLR. This is a temporary workaround until a fixed -// version of binutils is used for releases. -__declspec(dllexport) int main(int argc, char* argv[]) -#else -int main(int argc, char* argv[]) -#endif +MAIN_FUNCTION { ArgsManager& args = gArgs; SetupEnvironment(); diff --git a/src/bitcoin-wallet.cpp b/src/bitcoin-wallet.cpp index 2f3dd45267..d556300ee2 100644 --- a/src/bitcoin-wallet.cpp +++ b/src/bitcoin-wallet.cpp @@ -9,6 +9,7 @@ #include <chainparams.h> #include <chainparamsbase.h> #include <clientversion.h> +#include <compat/compat.h> #include <interfaces/init.h> #include <key.h> #include <logging.h> @@ -49,15 +50,16 @@ static void SetupWalletToolArgs(ArgsManager& argsman) argsman.AddCommand("createfromdump", "Create new wallet file from dumped records"); } -static bool WalletAppInit(ArgsManager& args, int argc, char* argv[]) +static std::optional<int> WalletAppInit(ArgsManager& args, int argc, char* argv[]) { SetupWalletToolArgs(args); std::string error_message; if (!args.ParseParameters(argc, argv, error_message)) { tfm::format(std::cerr, "Error parsing command line arguments: %s\n", error_message); - return false; + return EXIT_FAILURE; } - if (argc < 2 || HelpRequested(args) || args.IsArgSet("-version")) { + const bool missing_args{argc < 2}; + if (missing_args || HelpRequested(args) || args.IsArgSet("-version")) { std::string strUsage = strprintf("%s bitcoin-wallet version", PACKAGE_NAME) + " " + FormatFullVersion() + "\n"; if (args.IsArgSet("-version")) { @@ -72,7 +74,11 @@ static bool WalletAppInit(ArgsManager& args, int argc, char* argv[]) strUsage += "\n" + args.GetHelpMessage(); } tfm::format(std::cout, "%s", strUsage); - return false; + if (missing_args) { + tfm::format(std::cerr, "Error: too few parameters\n"); + return EXIT_FAILURE; + } + return EXIT_SUCCESS; } // check for printtoconsole, allow -debug @@ -80,15 +86,15 @@ static bool WalletAppInit(ArgsManager& args, int argc, char* argv[]) if (!CheckDataDirOption()) { tfm::format(std::cerr, "Error: Specified data directory \"%s\" does not exist.\n", args.GetArg("-datadir", "")); - return false; + return EXIT_FAILURE; } // Check for chain settings (Params() calls are only valid after this clause) SelectParams(args.GetChainName()); - return true; + return std::nullopt; } -int main(int argc, char* argv[]) +MAIN_FUNCTION { ArgsManager& args = gArgs; #ifdef WIN32 @@ -105,7 +111,7 @@ int main(int argc, char* argv[]) SetupEnvironment(); RandomInit(); try { - if (!WalletAppInit(args, argc, argv)) return EXIT_FAILURE; + if (const auto maybe_exit{WalletAppInit(args, argc, argv)}) return *maybe_exit; } catch (const std::exception& e) { PrintExceptionContinue(&e, "WalletAppInit()"); return EXIT_FAILURE; diff --git a/src/bitcoind.cpp b/src/bitcoind.cpp index 92e73d7c2a..9f81640ddb 100644 --- a/src/bitcoind.cpp +++ b/src/bitcoind.cpp @@ -9,12 +9,12 @@ #include <chainparams.h> #include <clientversion.h> -#include <compat.h> +#include <compat/compat.h> #include <init.h> #include <interfaces/chain.h> #include <interfaces/init.h> #include <node/context.h> -#include <node/ui_interface.h> +#include <node/interface_ui.h> #include <noui.h> #include <shutdown.h> #include <util/check.h> @@ -216,7 +216,7 @@ static bool AppInit(NodeContext& node, int argc, char* argv[]) 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"); + tfm::format(std::cerr, "Error during initialization - check debug.log for details\n"); exit(EXIT_FAILURE); } } @@ -256,7 +256,7 @@ static bool AppInit(NodeContext& node, int argc, char* argv[]) return fRet; } -int main(int argc, char* argv[]) +MAIN_FUNCTION { #ifdef WIN32 util::WinCmdLineArgs winArgs; diff --git a/src/blockfilter.cpp b/src/blockfilter.cpp index 63a9ba498f..85929747be 100644 --- a/src/blockfilter.cpp +++ b/src/blockfilter.cpp @@ -3,7 +3,6 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include <mutex> -#include <sstream> #include <set> #include <blockfilter.h> @@ -13,6 +12,7 @@ #include <script/script.h> #include <streams.h> #include <util/golombrice.h> +#include <util/string.h> /// SerType used to serialize parameters in GCS filter encoding. static constexpr int GCS_SER_TYPE = SER_NETWORK; @@ -47,7 +47,7 @@ GCSFilter::GCSFilter(const Params& params) : m_params(params), m_N(0), m_F(0), m_encoded{0} {} -GCSFilter::GCSFilter(const Params& params, std::vector<unsigned char> encoded_filter) +GCSFilter::GCSFilter(const Params& params, std::vector<unsigned char> encoded_filter, bool skip_decode_check) : m_params(params), m_encoded(std::move(encoded_filter)) { SpanReader stream{GCS_SER_TYPE, GCS_SER_VERSION, m_encoded}; @@ -59,6 +59,8 @@ GCSFilter::GCSFilter(const Params& params, std::vector<unsigned char> encoded_fi } m_F = static_cast<uint64_t>(m_N) * static_cast<uint64_t>(m_params.m_M); + if (skip_decode_check) return; + // Verify that the encoded filter contains exactly N elements. If it has too much or too little // data, a std::ios_base::failure exception will be raised. BitStreamReader<SpanReader> bitreader{stream}; @@ -146,7 +148,7 @@ bool GCSFilter::MatchAny(const ElementSet& elements) const const std::string& BlockFilterTypeName(BlockFilterType filter_type) { - static std::string unknown_retval = ""; + static std::string unknown_retval; auto it = g_filter_types.find(filter_type); return it != g_filter_types.end() ? it->second : unknown_retval; } @@ -167,7 +169,7 @@ const std::set<BlockFilterType>& AllBlockFilterTypes() static std::once_flag flag; std::call_once(flag, []() { - for (auto entry : g_filter_types) { + for (const auto& entry : g_filter_types) { types.insert(entry.first); } }); @@ -177,19 +179,7 @@ const std::set<BlockFilterType>& AllBlockFilterTypes() const std::string& ListBlockFilterTypes() { - static std::string type_list; - - static std::once_flag flag; - std::call_once(flag, []() { - std::stringstream ret; - bool first = true; - for (auto entry : g_filter_types) { - if (!first) ret << ", "; - ret << entry.second; - first = false; - } - type_list = ret.str(); - }); + static std::string type_list{Join(g_filter_types, ", ", [](const auto& entry) { return entry.second; })}; return type_list; } @@ -219,14 +209,14 @@ static GCSFilter::ElementSet BasicFilterElements(const CBlock& block, } BlockFilter::BlockFilter(BlockFilterType filter_type, const uint256& block_hash, - std::vector<unsigned char> filter) + std::vector<unsigned char> filter, bool skip_decode_check) : m_filter_type(filter_type), m_block_hash(block_hash) { GCSFilter::Params params; if (!BuildParams(params)) { throw std::invalid_argument("unknown filter_type"); } - m_filter = GCSFilter(params, std::move(filter)); + m_filter = GCSFilter(params, std::move(filter), skip_decode_check); } BlockFilter::BlockFilter(BlockFilterType filter_type, const CBlock& block, const CBlockUndo& block_undo) diff --git a/src/blockfilter.h b/src/blockfilter.h index 96cefbf3b2..0cb627d9df 100644 --- a/src/blockfilter.h +++ b/src/blockfilter.h @@ -11,6 +11,7 @@ #include <unordered_set> #include <vector> +#include <attributes.h> #include <primitives/block.h> #include <serialize.h> #include <uint256.h> @@ -59,14 +60,14 @@ public: explicit GCSFilter(const Params& params = Params()); /** Reconstructs an already-created filter from an encoding. */ - GCSFilter(const Params& params, std::vector<unsigned char> encoded_filter); + GCSFilter(const Params& params, std::vector<unsigned char> encoded_filter, bool skip_decode_check); /** Builds a new filter from the params and set of elements. */ GCSFilter(const Params& params, const ElementSet& elements); uint32_t GetN() const { return m_N; } - const Params& GetParams() const { return m_params; } - const std::vector<unsigned char>& GetEncoded() const { return m_encoded; } + const Params& GetParams() const LIFETIMEBOUND { return m_params; } + const std::vector<unsigned char>& GetEncoded() const LIFETIMEBOUND { return m_encoded; } /** * Checks if the element may be in the set. False positives are possible @@ -122,16 +123,16 @@ public: //! Reconstruct a BlockFilter from parts. BlockFilter(BlockFilterType filter_type, const uint256& block_hash, - std::vector<unsigned char> filter); + std::vector<unsigned char> filter, bool skip_decode_check); //! Construct a new BlockFilter of the specified type from a block. BlockFilter(BlockFilterType filter_type, const CBlock& block, const CBlockUndo& block_undo); BlockFilterType GetFilterType() const { return m_filter_type; } - const uint256& GetBlockHash() const { return m_block_hash; } - const GCSFilter& GetFilter() const { return m_filter; } + const uint256& GetBlockHash() const LIFETIMEBOUND { return m_block_hash; } + const GCSFilter& GetFilter() const LIFETIMEBOUND { return m_filter; } - const std::vector<unsigned char>& GetEncodedFilter() const + const std::vector<unsigned char>& GetEncodedFilter() const LIFETIMEBOUND { return m_filter.GetEncoded(); } @@ -164,7 +165,7 @@ public: if (!BuildParams(params)) { throw std::ios_base::failure("unknown filter_type"); } - m_filter = GCSFilter(params, std::move(encoded_filter)); + m_filter = GCSFilter(params, std::move(encoded_filter), /*skip_decode_check=*/false); } }; diff --git a/src/chain.cpp b/src/chain.cpp index b8158f7b0b..66a0830394 100644 --- a/src/chain.cpp +++ b/src/chain.cpp @@ -4,6 +4,7 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include <chain.h> +#include <tinyformat.h> #include <util/time.h> std::string CBlockFileInfo::ToString() const @@ -11,11 +12,15 @@ std::string CBlockFileInfo::ToString() const return strprintf("CBlockFileInfo(blocks=%u, size=%u, heights=%u...%u, time=%s...%s)", nBlocks, nSize, nHeightFirst, nHeightLast, FormatISO8601Date(nTimeFirst), FormatISO8601Date(nTimeLast)); } -void CChain::SetTip(CBlockIndex *pindex) { - if (pindex == nullptr) { - vChain.clear(); - return; - } +std::string CBlockIndex::ToString() const +{ + return strprintf("CBlockIndex(pprev=%p, nHeight=%d, merkle=%s, hashBlock=%s)", + pprev, nHeight, hashMerkleRoot.ToString(), GetBlockHash().ToString()); +} + +void CChain::SetTip(CBlockIndex& block) +{ + CBlockIndex* pindex = █ vChain.resize(pindex->nHeight + 1); while (pindex && vChain[pindex->nHeight] != pindex) { vChain[pindex->nHeight] = pindex; @@ -23,32 +28,33 @@ void CChain::SetTip(CBlockIndex *pindex) { } } -CBlockLocator CChain::GetLocator(const CBlockIndex *pindex) const { - int nStep = 1; - std::vector<uint256> vHave; - vHave.reserve(32); - - if (!pindex) - pindex = Tip(); - while (pindex) { - vHave.push_back(pindex->GetBlockHash()); - // Stop when we have added the genesis block. - if (pindex->nHeight == 0) - break; +std::vector<uint256> LocatorEntries(const CBlockIndex* index) +{ + int step = 1; + std::vector<uint256> have; + if (index == nullptr) return have; + + have.reserve(32); + while (index) { + have.emplace_back(index->GetBlockHash()); + if (index->nHeight == 0) break; // Exponentially larger steps back, plus the genesis block. - int nHeight = std::max(pindex->nHeight - nStep, 0); - if (Contains(pindex)) { - // Use O(1) CChain index if possible. - pindex = (*this)[nHeight]; - } else { - // Otherwise, use O(log n) skiplist. - pindex = pindex->GetAncestor(nHeight); - } - if (vHave.size() > 10) - nStep *= 2; + int height = std::max(index->nHeight - step, 0); + // Use skiplist. + index = index->GetAncestor(height); + if (have.size() > 10) step *= 2; } + return have; +} - return CBlockLocator(vHave); +CBlockLocator GetLocator(const CBlockIndex* index) +{ + return CBlockLocator{LocatorEntries(index)}; +} + +CBlockLocator CChain::GetLocator() const +{ + return ::GetLocator(Tip()); } const CBlockIndex *CChain::FindFork(const CBlockIndex *pindex) const { diff --git a/src/chain.h b/src/chain.h index 24b5026aba..2d3b084b9b 100644 --- a/src/chain.h +++ b/src/chain.h @@ -11,8 +11,8 @@ #include <flatfile.h> #include <primitives/block.h> #include <sync.h> -#include <tinyformat.h> #include <uint256.h> +#include <util/time.h> #include <vector> @@ -138,7 +138,7 @@ enum BlockStatus : uint32_t { * If set, this indicates that the block index entry is assumed-valid. * Certain diagnostics will be skipped in e.g. CheckBlockIndex(). * It almost certainly means that the block's full validation is pending - * on a background chainstate. See `doc/assumeutxo.md`. + * on a background chainstate. See `doc/design/assumeutxo.md`. */ BLOCK_ASSUMED_VALID = 256, }; @@ -263,6 +263,7 @@ public: uint256 GetBlockHash() const { + assert(phashBlock != nullptr); return *phashBlock; } @@ -275,6 +276,11 @@ public: */ bool HaveTxsDownloaded() const { return nChainTx != 0; } + NodeSeconds Time() const + { + return NodeSeconds{std::chrono::seconds{nTime}}; + } + int64_t GetBlockTime() const { return (int64_t)nTime; @@ -301,13 +307,7 @@ public: return pbegin[(pend - pbegin) / 2]; } - std::string ToString() const - { - return strprintf("CBlockIndex(pprev=%p, nHeight=%d, merkle=%s, hashBlock=%s)", - pprev, nHeight, - hashMerkleRoot.ToString(), - GetBlockHash().ToString()); - } + std::string ToString() const; //! Check whether this block index entry is valid up to the passed validity level. bool IsValid(enum BlockStatus nUpTo = BLOCK_VALID_TRANSACTIONS) const @@ -402,7 +402,7 @@ public: READWRITE(obj.nNonce); } - uint256 GetBlockHash() const + uint256 ConstructBlockHash() const { CBlockHeader block; block.nVersion = nVersion; @@ -414,16 +414,8 @@ public: return block.GetHash(); } - - std::string ToString() const - { - std::string str = "CDiskBlockIndex("; - str += CBlockIndex::ToString(); - str += strprintf("\n hashBlock=%s, hashPrev=%s)", - GetBlockHash().ToString(), - hashPrev.ToString()); - return str; - } + uint256 GetBlockHash() = delete; + std::string ToString() = delete; }; /** An in-memory indexed chain of blocks. */ @@ -479,10 +471,10 @@ public: } /** Set/initialize a chain with a given tip. */ - void SetTip(CBlockIndex* pindex); + void SetTip(CBlockIndex& block); - /** Return a CBlockLocator that refers to a block in this chain (by default the tip). */ - CBlockLocator GetLocator(const CBlockIndex* pindex = nullptr) const; + /** Return a CBlockLocator that refers to the tip in of this chain. */ + CBlockLocator GetLocator() const; /** Find the last common block between this chain and a block index entry. */ const CBlockIndex* FindFork(const CBlockIndex* pindex) const; @@ -491,4 +483,10 @@ public: CBlockIndex* FindEarliestAtLeast(int64_t nTime, int height) const; }; +/** Get a locator for a block index entry. */ +CBlockLocator GetLocator(const CBlockIndex* index); + +/** Construct a list of hash entries to put in a locator. */ +std::vector<uint256> LocatorEntries(const CBlockIndex* index); + #endif // BITCOIN_CHAIN_H diff --git a/src/chainparams.cpp b/src/chainparams.cpp index 7a7c72ea25..c6d4eee7b9 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -93,8 +93,8 @@ public: consensus.vDeployments[Consensus::DEPLOYMENT_TAPROOT].nTimeout = 1628640000; // August 11th, 2021 consensus.vDeployments[Consensus::DEPLOYMENT_TAPROOT].min_activation_height = 709632; // Approximately November 12th, 2021 - consensus.nMinimumChainWork = uint256S("0x00000000000000000000000000000000000000002927cdceccbd5209e81e80db"); - consensus.defaultAssumeValid = uint256S("0x000000000000000000052d314a259755ca65944e68df6b12a067ea8f1f5a7091"); // 724466 + consensus.nMinimumChainWork = uint256S("0x00000000000000000000000000000000000000003404ba0801921119f903495e"); + consensus.defaultAssumeValid = uint256S("0x00000000000000000009c97098b5295f7e5f183ac811fb5d1534040adb93cabd"); // 751565 /** * The message start string is designed to be unlikely to occur in normal data. @@ -107,7 +107,7 @@ public: pchMessageStart[3] = 0xd9; nDefaultPort = 8333; nPruneAfterHeight = 100000; - m_assumed_blockchain_size = 460; + m_assumed_blockchain_size = 496; m_assumed_chain_state_size = 6; genesis = CreateGenesisBlock(1231006505, 2083236893, 0x1d00ffff, 1, 50 * COIN); @@ -168,10 +168,10 @@ public: }; chainTxData = ChainTxData{ - // Data from RPC: getchaintxstats 4096 000000000000000000052d314a259755ca65944e68df6b12a067ea8f1f5a7091 - /* nTime */ 1645542140, - /* nTxCount */ 712531200, - /* dTxRate */ 2.891036496010309, + // Data from RPC: getchaintxstats 4096 00000000000000000009c97098b5295f7e5f183ac811fb5d1534040adb93cabd + .nTime = 1661697692, + .nTxCount = 760120522, + .dTxRate = 2.925802860942233, }; } }; @@ -213,8 +213,8 @@ public: consensus.vDeployments[Consensus::DEPLOYMENT_TAPROOT].nTimeout = 1628640000; // August 11th, 2021 consensus.vDeployments[Consensus::DEPLOYMENT_TAPROOT].min_activation_height = 0; // No activation delay - consensus.nMinimumChainWork = uint256S("0x00000000000000000000000000000000000000000000064728c7be6fe4b2f961"); - consensus.defaultAssumeValid = uint256S("0x00000000000163cfb1f97c4e4098a3692c8053ad9cab5ad9c86b338b5c00b8b7"); // 2143398 + consensus.nMinimumChainWork = uint256S("0x00000000000000000000000000000000000000000000076f6e7cbd0beade5d20"); + consensus.defaultAssumeValid = uint256S("0x0000000000000004877fa2d36316398528de4f347df2f8a96f76613a298ce060"); // 2344474 pchMessageStart[0] = 0x0b; pchMessageStart[1] = 0x11; @@ -222,7 +222,7 @@ public: pchMessageStart[3] = 0x07; nDefaultPort = 18333; nPruneAfterHeight = 1000; - m_assumed_blockchain_size = 40; + m_assumed_blockchain_size = 42; m_assumed_chain_state_size = 2; genesis = CreateGenesisBlock(1296688602, 414098458, 0x1d00ffff, 1, 50 * COIN); @@ -264,10 +264,10 @@ public: }; chainTxData = ChainTxData{ - // Data from RPC: getchaintxstats 4096 00000000d18cfe81cbeea665076807789bd8f831d557632e635bc6e3c003069e - /* nTime */ 1645635119, - /* nTxCount */ 62226341, - /* dTxRate */ 0.07717997442177152, + // Data from RPC: getchaintxstats 4096 0000000000000004877fa2d36316398528de4f347df2f8a96f76613a298ce060 + .nTime = 1661705221, + .nTxCount = 63531852, + .dTxRate = 0.1079119341520164, }; } }; @@ -289,15 +289,15 @@ public: vSeeds.emplace_back("178.128.221.177"); vSeeds.emplace_back("v7ajjeirttkbnt32wpy3c6w3emwnfr3fkla7hpxcfokr3ysd3kqtzmqd.onion:38333"); - consensus.nMinimumChainWork = uint256S("0x000000000000000000000000000000000000000000000000000000de26b0e471"); - consensus.defaultAssumeValid = uint256S("0x00000112852484b5fe3451572368f93cfd2723279af3464e478aee35115256ef"); // 78788 + consensus.nMinimumChainWork = uint256S("0x000000000000000000000000000000000000000000000000000001291fc22898"); + consensus.defaultAssumeValid = uint256S("0x000000d1a0e224fa4679d2fb2187ba55431c284fa1b74cbc8cfda866fd4d2c09"); // 105495 m_assumed_blockchain_size = 1; m_assumed_chain_state_size = 0; chainTxData = ChainTxData{ - // Data from RPC: getchaintxstats 4096 0000003d9144c56ac110ae04a0c271a0acce2f14f426b39fdf0d938c96d2eb09 - /* nTime */ 1645631279, - /* nTxCount */ 1257429, - /* dTxRate */ 0.1389638742514995, + // Data from RPC: getchaintxstats 4096 000000d1a0e224fa4679d2fb2187ba55431c284fa1b74cbc8cfda866fd4d2c09 + .nTime = 1661702566, + .nTxCount = 1903567, + .dTxRate = 0.02336701143027275, }; } else { const auto signet_challenge = args.GetArgs("-signetchallenge"); @@ -352,7 +352,7 @@ public: consensus.vDeployments[Consensus::DEPLOYMENT_TAPROOT].min_activation_height = 0; // No activation delay // message start is defined as the first 4 bytes of the sha256d of the block script - CHashWriter h(SER_DISK, 0); + HashWriter h{}; h << consensus.signet_challenge; uint256 hash = h.GetHash(); memcpy(pchMessageStart, hash.begin(), 4); diff --git a/src/chainparamsseeds.h b/src/chainparamsseeds.h index b9df1c61f0..f065647d49 100644 --- a/src/chainparamsseeds.h +++ b/src/chainparamsseeds.h @@ -7,703 +7,945 @@ * Each line contains a BIP155 serialized (networkID, addr, port) tuple. */ static const uint8_t chainparams_seed_main[] = { - 0x01,0x04,0x02,0x25,0x1e,0x90,0x22,0x49, - 0x01,0x04,0x02,0x8a,0xae,0x9e,0x20,0x8d, + 0x01,0x04,0x02,0x03,0x19,0xb5,0x20,0x8d, 0x01,0x04,0x02,0x98,0x4e,0x7c,0x20,0x8d, - 0x01,0x04,0x05,0x08,0x12,0x9a,0x20,0x8d, - 0x01,0x04,0x05,0x2d,0x4a,0x32,0x20,0x8d, - 0x01,0x04,0x05,0x4f,0x7b,0x03,0x20,0x8d, - 0x01,0x04,0x05,0x66,0xa8,0xd9,0x56,0xcc, - 0x01,0x04,0x05,0x67,0x89,0x92,0x24,0x75, + 0x01,0x04,0x05,0x27,0x4a,0xa6,0x20,0x8d, + 0x01,0x04,0x05,0x2d,0x4f,0x51,0x47,0x9c, + 0x01,0x04,0x05,0x35,0x10,0x80,0x20,0x8d, + 0x01,0x04,0x05,0x5f,0xba,0x4e,0x20,0x8d, 0x01,0x04,0x05,0x80,0x57,0x7e,0x20,0x8d, - 0x01,0x04,0x05,0xac,0x84,0xc8,0x20,0x8d, + 0x01,0x04,0x05,0x85,0x41,0x52,0x20,0x8d, + 0x01,0x04,0x05,0x92,0x14,0xe5,0x20,0x8d, + 0x01,0x04,0x05,0xb4,0x29,0x77,0x20,0x8d, 0x01,0x04,0x05,0xbc,0x3e,0x12,0x20,0x8d, - 0x01,0x04,0x05,0xfe,0x65,0xe2,0x20,0x8e, - 0x01,0x04,0x08,0xd2,0x12,0x38,0x20,0x8d, - 0x01,0x04,0x08,0xd2,0x5c,0x20,0x20,0x8d, - 0x01,0x04,0x0e,0x0d,0x22,0xe1,0x3f,0x35, - 0x01,0x04,0x0e,0x27,0x97,0xa7,0x20,0x8d, - 0x01,0x04,0x12,0xc4,0x4f,0x6c,0x20,0x8d, - 0x01,0x04,0x12,0xda,0x8b,0x3a,0xbc,0xcd, - 0x01,0x04,0x14,0xb8,0x0f,0x74,0x20,0xf1, - 0x01,0x04,0x17,0xaf,0x00,0xdc,0x20,0x8d, + 0x01,0x04,0x05,0xc7,0xad,0x42,0x20,0x8d, + 0x01,0x04,0x05,0xff,0x61,0x19,0x20,0x8d, + 0x01,0x04,0x05,0xff,0x67,0xb4,0x20,0x8d, + 0x01,0x04,0x08,0xd1,0x46,0x4d,0x20,0x8d, + 0x01,0x04,0x08,0xd1,0x69,0x8a,0x20,0x8d, + 0x01,0x04,0x12,0xa2,0xd0,0x99,0xbc,0xcc, + 0x01,0x04,0x17,0xaf,0x00,0xc8,0x20,0x8d, + 0x01,0x04,0x17,0xaf,0x00,0xde,0x20,0x8d, 0x01,0x04,0x17,0xe9,0x6b,0x15,0x20,0x8d, + 0x01,0x04,0x17,0xec,0x19,0xa9,0x20,0x8d, 0x01,0x04,0x18,0x23,0x44,0xe5,0x20,0x8d, - 0x01,0x04,0x18,0x25,0x03,0x1a,0x20,0x8d, - 0x01,0x04,0x18,0x66,0x5b,0xcb,0x20,0x8d, + 0x01,0x04,0x18,0x54,0xa4,0x32,0x20,0x8d, 0x01,0x04,0x18,0x74,0x99,0x73,0x20,0x8d, - 0x01,0x04,0x18,0x86,0x06,0xa5,0x20,0x8d, - 0x01,0x04,0x18,0x9b,0xda,0x0d,0x20,0x8d, - 0x01,0x04,0x18,0xa0,0x89,0xad,0x20,0x8d, - 0x01,0x04,0x18,0xb1,0x6a,0x55,0x20,0x8d, 0x01,0x04,0x18,0xb8,0x00,0x92,0x20,0x8d, - 0x01,0x04,0x18,0xc2,0xde,0x74,0x20,0x8d, - 0x01,0x04,0x18,0xcd,0xd7,0xc0,0x20,0x8d, + 0x01,0x04,0x1b,0x21,0xa0,0xc4,0x20,0x8d, 0x01,0x04,0x1b,0x7c,0x6c,0x13,0x20,0x8d, - 0x01,0x04,0x1f,0x0e,0x28,0x40,0x20,0x8d, + 0x01,0x04,0x1b,0x94,0xce,0x8c,0x20,0x8d, + 0x01,0x04,0x1f,0x11,0x40,0xc0,0x20,0x8d, + 0x01,0x04,0x1f,0x12,0x72,0x87,0x20,0x8d, + 0x01,0x04,0x1f,0x29,0x17,0xf9,0x20,0x8d, + 0x01,0x04,0x1f,0x2a,0xb0,0x8a,0x20,0x8d, 0x01,0x04,0x1f,0x2f,0xca,0x70,0x20,0x8d, - 0x01,0x04,0x1f,0xa5,0x73,0x07,0x20,0x8d, 0x01,0x04,0x22,0x41,0x2d,0x9d,0x20,0x8d, - 0x01,0x04,0x22,0x4e,0x30,0x68,0x20,0x8d, 0x01,0x04,0x22,0x50,0x86,0x44,0x20,0x8d, - 0x01,0x04,0x22,0x65,0x84,0xc6,0x20,0x8d, - 0x01,0x04,0x22,0xe3,0x44,0xd8,0x20,0x8d, - 0x01,0x04,0x23,0x89,0xd4,0x16,0x20,0x8d, - 0x01,0x04,0x23,0xe7,0xbe,0x86,0x20,0x8d, - 0x01,0x04,0x25,0x01,0xd9,0x23,0x20,0x8d, - 0x01,0x04,0x25,0x0f,0x3e,0x20,0x20,0x8d, + 0x01,0x04,0x22,0x7e,0x73,0x23,0x20,0x8d, + 0x01,0x04,0x25,0x01,0xcc,0xe7,0x20,0x8d, + 0x01,0x04,0x25,0x78,0x9b,0x22,0x20,0x8d, 0x01,0x04,0x25,0x8f,0x76,0xae,0x20,0x8d, - 0x01,0x04,0x25,0xc8,0x3b,0x43,0x20,0x8d, - 0x01,0x04,0x25,0xcd,0x09,0xa5,0x20,0x8d, - 0x01,0x04,0x26,0x17,0xb4,0xe4,0x20,0x8d, - 0x01,0x04,0x26,0x41,0x77,0x1a,0x20,0x8d, + 0x01,0x04,0x25,0xc1,0xe3,0x10,0x20,0x8d, + 0x01,0x04,0x25,0xdc,0x87,0x97,0x20,0x8d, + 0x01,0x04,0x25,0xeb,0x92,0xec,0x20,0x8d, + 0x01,0x04,0x26,0x7c,0x7e,0x2a,0x20,0x8d, 0x01,0x04,0x26,0x8d,0x86,0x8c,0x20,0x8d, - 0x01,0x04,0x27,0x6d,0x7a,0x7f,0x20,0xfc, + 0x01,0x04,0x26,0x91,0x97,0x96,0x20,0x8d, + 0x01,0x04,0x28,0x73,0x89,0x1c,0x20,0x8d, + 0x01,0x04,0x29,0x48,0x9a,0x42,0x20,0x8d, 0x01,0x04,0x29,0x4f,0x46,0x92,0x20,0x8d, - 0x01,0x04,0x29,0xc1,0x7a,0xbf,0x20,0x8d, + 0x01,0x04,0x2a,0xc1,0x37,0x87,0x20,0x8d, 0x01,0x04,0x2b,0xe1,0x3e,0x6b,0x20,0x8d, - 0x01,0x04,0x2d,0x23,0x49,0x98,0x20,0x8d, 0x01,0x04,0x2d,0x2b,0x61,0x67,0x20,0x8d, - 0x01,0x04,0x2d,0x3f,0x0a,0x34,0x4e,0x28, - 0x01,0x04,0x2d,0x54,0x99,0x28,0x20,0x8d, - 0x01,0x04,0x2d,0x5f,0x40,0xe1,0x20,0x8d, - 0x01,0x04,0x2d,0x81,0xb4,0xd6,0x20,0x8d, - 0x01,0x04,0x2d,0x9a,0xff,0xa2,0x20,0x8d, - 0x01,0x04,0x2d,0xe2,0x50,0x66,0x20,0x8d, - 0x01,0x04,0x2e,0x06,0x0a,0xe6,0x20,0x8d, + 0x01,0x04,0x2d,0x55,0x30,0x3a,0x20,0x8d, + 0x01,0x04,0x2d,0x7e,0x1a,0xe5,0x20,0x8d, + 0x01,0x04,0x2d,0x86,0x8e,0x28,0x20,0x8d, + 0x01,0x04,0x2d,0x9a,0xfc,0xa2,0x20,0x8d, + 0x01,0x04,0x2e,0x0d,0xd8,0xa9,0x20,0x8d, 0x01,0x04,0x2e,0x17,0x57,0xda,0x20,0x8d, - 0x01,0x04,0x2e,0x20,0x32,0x62,0x20,0x8d, - 0x01,0x04,0x2e,0x2f,0x54,0x55,0x20,0x8d, + 0x01,0x04,0x2e,0x28,0x7f,0xa4,0x20,0x8d, 0x01,0x04,0x2e,0x30,0x7e,0x3a,0x20,0x8d, + 0x01,0x04,0x2e,0x3b,0x0d,0x23,0x20,0x8d, + 0x01,0x04,0x2e,0x48,0xee,0x11,0x20,0x8d, + 0x01,0x04,0x2e,0x80,0x8d,0xb8,0x20,0x8d, 0x01,0x04,0x2e,0x92,0xf8,0x59,0x20,0x8d, 0x01,0x04,0x2e,0xa5,0xdd,0xd1,0x24,0x75, 0x01,0x04,0x2e,0xa6,0x8e,0x02,0x20,0x8d, - 0x01,0x04,0x2e,0xa6,0xa2,0x2d,0x4e,0x21, - 0x01,0x04,0x2e,0xad,0x32,0x3a,0x20,0x8d, 0x01,0x04,0x2e,0xaf,0xb2,0x03,0x20,0x8d, - 0x01,0x04,0x2e,0xbc,0x1e,0x76,0x20,0x8d, - 0x01,0x04,0x2e,0xdb,0x78,0x3b,0x0e,0x59, - 0x01,0x04,0x2e,0xe5,0xee,0xbb,0x20,0x8d, - 0x01,0x04,0x2f,0x5d,0xe6,0xab,0x20,0x8d, - 0x01,0x04,0x2f,0x64,0xa2,0xd2,0x47,0x9c, - 0x01,0x04,0x2f,0x90,0x6a,0xf9,0x20,0x8d, - 0x01,0x04,0x2f,0xbc,0x46,0xcd,0x20,0x8d, - 0x01,0x04,0x2f,0xe3,0xe2,0xf2,0x20,0x8d, + 0x01,0x04,0x2f,0x24,0x90,0x33,0x20,0x8d, + 0x01,0x04,0x2f,0xb4,0x31,0x9e,0x20,0x8d, + 0x01,0x04,0x31,0xe4,0x83,0x85,0x08,0xa2, 0x01,0x04,0x32,0x02,0x0d,0xa4,0x20,0x8d, - 0x01,0x04,0x32,0x05,0x2e,0xc3,0x20,0x8d, - 0x01,0x04,0x32,0x2d,0x80,0x1c,0x20,0x8d, - 0x01,0x04,0x33,0x94,0x99,0x3c,0x20,0x8d, + 0x01,0x04,0x32,0x23,0x47,0x33,0x20,0x8d, + 0x01,0x04,0x32,0x35,0xfa,0xa2,0x20,0x8d, + 0x01,0x04,0x33,0x44,0x24,0x39,0x20,0x8d, + 0x01,0x04,0x33,0x8a,0x04,0x87,0x75,0x31, 0x01,0x04,0x33,0x9a,0x3e,0x67,0x20,0x8d, - 0x01,0x04,0x33,0x9a,0x83,0x12,0x20,0x8d, 0x01,0x04,0x33,0x9e,0x96,0x9b,0x20,0x8d, - 0x01,0x04,0x33,0x9f,0x02,0xda,0x20,0x8d, - 0x01,0x04,0x36,0xc6,0x13,0x22,0x20,0x8d, - 0x01,0x04,0x3a,0x69,0xa8,0x29,0x20,0x8d, + 0x01,0x04,0x36,0xb0,0x3f,0x10,0x20,0x8d, 0x01,0x04,0x3a,0x9e,0x00,0x56,0x20,0x8d, - 0x01,0x04,0x3c,0xfb,0x81,0x3d,0x20,0x90, + 0x01,0x04,0x3b,0x8a,0x73,0x89,0x20,0x8d, + 0x01,0x04,0x3b,0xa7,0xbf,0x3c,0x20,0x8d, + 0x01,0x04,0x3c,0xcd,0xcd,0x77,0x20,0x8d, + 0x01,0x04,0x3c,0xea,0x7a,0xf5,0x20,0x8d, + 0x01,0x04,0x3c,0xf0,0xd2,0x9b,0x20,0x8d, 0x01,0x04,0x3d,0xef,0x5b,0xfa,0x20,0x8d, - 0x01,0x04,0x3e,0x1c,0xbe,0xc2,0x20,0x8d, - 0x01,0x04,0x3e,0x98,0x3a,0x10,0x24,0xcd, + 0x01,0x04,0x3e,0x4a,0x8f,0x0b,0x20,0x8d, + 0x01,0x04,0x3e,0x8a,0xa2,0x0c,0x20,0x8d, + 0x01,0x04,0x3e,0xa9,0x4a,0xe9,0x20,0x8d, 0x01,0x04,0x3e,0xab,0x81,0x20,0x20,0x8d, - 0x01,0x04,0x3e,0xfb,0x36,0xa3,0x20,0x8d, + 0x01,0x04,0x3e,0xd1,0xc6,0x41,0x20,0x8d, 0x01,0x04,0x3f,0xf7,0x93,0xa6,0x20,0x8d, - 0x01,0x04,0x40,0x21,0x44,0xb0,0x20,0x8d, - 0x01,0x04,0x40,0x9c,0xc0,0x3d,0x20,0x8d, - 0x01,0x04,0x40,0xbb,0xaf,0xe2,0x20,0x8d, - 0x01,0x04,0x40,0xe9,0xf5,0x27,0x20,0x8d, - 0x01,0x04,0x40,0xed,0x52,0x95,0x20,0x8d, - 0x01,0x04,0x41,0x65,0xf7,0x1a,0x20,0x8d, + 0x01,0x04,0x40,0x62,0x4c,0x3e,0x20,0x8d, 0x01,0x04,0x42,0x1d,0x81,0xda,0x20,0x8d, - 0x01,0x04,0x42,0x31,0xcc,0x0b,0x20,0x8d, - 0x01,0x04,0x42,0x3a,0xf3,0xd7,0x20,0x8d, - 0x01,0x04,0x42,0x55,0xea,0x81,0x20,0x8d, + 0x01,0x04,0x42,0x60,0xeb,0x1c,0x20,0x8d, 0x01,0x04,0x42,0x82,0x78,0x34,0x20,0x8d, - 0x01,0x04,0x43,0x0a,0x79,0x91,0x20,0x8d, - 0x01,0x04,0x43,0xd2,0xe4,0xcb,0x20,0x8d, - 0x01,0x04,0x43,0xd5,0x57,0x15,0x20,0x8d, + 0x01,0x04,0x42,0xc6,0xd1,0xf3,0x20,0x8d, + 0x01,0x04,0x42,0xd0,0x40,0x80,0x20,0x8d, + 0x01,0x04,0x42,0xe1,0xe7,0x94,0x20,0x8d, + 0x01,0x04,0x43,0x37,0x03,0xc8,0x20,0x8d, + 0x01,0x04,0x43,0x3a,0xe8,0x6b,0x20,0x8d, + 0x01,0x04,0x43,0xd3,0x5c,0x02,0x20,0x8d, + 0x01,0x04,0x43,0xdf,0x77,0x7a,0x20,0x8d, + 0x01,0x04,0x44,0x30,0x83,0xfb,0x20,0x8d, 0x01,0x04,0x44,0xb5,0x04,0x0c,0x20,0x8d, - 0x01,0x04,0x45,0x07,0x7c,0x92,0x20,0x8d, - 0x01,0x04,0x45,0x08,0xaf,0xc9,0x20,0x8d, + 0x01,0x04,0x45,0x0e,0xb9,0x09,0x20,0x8d, + 0x01,0x04,0x45,0x36,0x1d,0xc1,0x20,0x8d, 0x01,0x04,0x45,0x3b,0x12,0x16,0x20,0x8d, - 0x01,0x04,0x45,0x77,0xc1,0x09,0x20,0x8d, - 0x01,0x04,0x45,0x82,0xc9,0x1b,0x20,0x8d, 0x01,0x04,0x45,0x83,0x65,0xb0,0x20,0x8d, - 0x01,0x04,0x46,0x0f,0xc2,0x20,0x20,0x8d, - 0x01,0x04,0x46,0x40,0x1b,0x0c,0x20,0x8d, - 0x01,0x04,0x48,0x1d,0xaa,0x97,0x20,0x8d, - 0x01,0x04,0x48,0x4a,0x22,0x63,0x20,0x8d, - 0x01,0x04,0x48,0x85,0xb1,0x77,0x20,0x8d, - 0x01,0x04,0x49,0xa6,0x54,0xde,0x20,0x8d, - 0x01,0x04,0x4a,0x43,0xf0,0xcc,0x20,0x8d, + 0x01,0x04,0x45,0xa5,0xcd,0x8e,0x22,0x81, + 0x01,0x04,0x45,0xe4,0xdb,0x7c,0x20,0x8d, + 0x01,0x04,0x46,0x3b,0x7b,0x19,0x20,0x8d, + 0x01,0x04,0x46,0x3e,0x0d,0x96,0x20,0x8d, + 0x01,0x04,0x46,0x42,0xf8,0xaa,0x20,0x8d, + 0x01,0x04,0x46,0x70,0x99,0xe5,0x20,0x8d, + 0x01,0x04,0x46,0xa0,0xf0,0x84,0x20,0x8d, + 0x01,0x04,0x46,0xbe,0xb1,0xcc,0x20,0x8d, + 0x01,0x04,0x47,0x1c,0xbd,0xef,0x20,0x8d, + 0x01,0x04,0x47,0xea,0x7d,0xc6,0x20,0x8d, + 0x01,0x04,0x48,0x4a,0x7b,0xb3,0x20,0x8d, + 0x01,0x04,0x48,0xfd,0xec,0xd9,0x20,0x8d, + 0x01,0x04,0x49,0xdb,0xfe,0x78,0x20,0x8d, 0x01,0x04,0x4a,0x5b,0x73,0xe5,0x20,0x8d, 0x01,0x04,0x4a,0x76,0x89,0x77,0x20,0x8d, - 0x01,0x04,0x4a,0xd5,0xfb,0xcb,0x20,0x8d, + 0x01,0x04,0x4a,0xc3,0xa6,0x64,0x20,0x8d, 0x01,0x04,0x4a,0xdc,0xff,0xbe,0x20,0x8d, - 0x01,0x04,0x4c,0x0b,0x3c,0x9b,0x20,0x8d, - 0x01,0x04,0x4c,0x42,0x90,0x7f,0x20,0x8d, + 0x01,0x04,0x4c,0x43,0xd3,0x6e,0x20,0x8d, + 0x01,0x04,0x4c,0xa9,0xa3,0x0e,0x20,0x8d, + 0x01,0x04,0x4d,0x20,0x79,0xa2,0x20,0x8d, + 0x01,0x04,0x4d,0x35,0x87,0x4a,0x20,0x8d, 0x01,0x04,0x4d,0x46,0x10,0xf5,0x20,0x8d, 0x01,0x04,0x4d,0x55,0xcc,0x95,0x20,0x8d, - 0x01,0x04,0x4d,0x69,0x57,0x61,0x20,0x8d, - 0x01,0x04,0x4d,0x78,0x71,0x45,0x20,0xf1, - 0x01,0x04,0x4d,0x78,0x71,0x47,0x20,0xf1, - 0x01,0x04,0x4d,0x78,0x7a,0x74,0x20,0xf1, - 0x01,0x04,0x4d,0x78,0x7a,0x76,0x20,0xf1, + 0x01,0x04,0x4d,0x6b,0x26,0xef,0x20,0x8d, + 0x01,0x04,0x4d,0x78,0x1a,0x66,0x20,0x8d, 0x01,0x04,0x4d,0xa2,0xbe,0x5a,0x20,0x8d, - 0x01,0x04,0x4d,0xa7,0xf5,0xef,0xd8,0xf8, - 0x01,0x04,0x4d,0xe8,0x29,0xbd,0x20,0x8d, 0x01,0x04,0x4e,0x14,0xe3,0xf9,0x20,0x8d, 0x01,0x04,0x4e,0x15,0xa7,0x08,0x20,0x8d, 0x01,0x04,0x4e,0x1b,0x8b,0x0d,0x20,0x8d, - 0x01,0x04,0x4e,0x2b,0xd0,0x19,0x20,0x8d, - 0x01,0x04,0x4e,0x3f,0x1c,0x92,0x20,0x8d, - 0x01,0x04,0x4e,0x48,0xe4,0xef,0x20,0x8d, - 0x01,0x04,0x4e,0x6c,0x66,0x08,0x20,0x8d, - 0x01,0x04,0x4e,0x81,0x00,0x27,0x20,0x8d, - 0x01,0x04,0x4e,0x81,0xa9,0x45,0x20,0x8d, - 0x01,0x04,0x4f,0x4d,0xb6,0xb4,0x20,0x8d, + 0x01,0x04,0x4e,0x5a,0x5b,0xdc,0x20,0x8d, + 0x01,0x04,0x4e,0x6c,0x6c,0x19,0x20,0x8d, + 0x01,0x04,0x4e,0x6c,0x6c,0x26,0x20,0x8d, 0x01,0x04,0x4f,0x4d,0xb6,0xb7,0x20,0x8d, - 0x01,0x04,0x4f,0x6b,0xb2,0x3b,0x20,0x8d, + 0x01,0x04,0x4f,0x62,0x9f,0x07,0x2c,0x45, + 0x01,0x04,0x4f,0xbd,0xd3,0xc9,0x20,0x8d, 0x01,0x04,0x50,0x37,0xe1,0x9e,0x20,0x8d, - 0x01,0x04,0x50,0x40,0xd3,0x66,0x20,0x8d, - 0x01,0x04,0x50,0x40,0xd3,0x67,0x20,0x8d, - 0x01,0x04,0x50,0x47,0x39,0x32,0x20,0x8d, - 0x01,0x04,0x50,0x51,0x03,0x1b,0x20,0x8d, - 0x01,0x04,0x50,0x52,0x37,0x2b,0x20,0x8d, + 0x01,0x04,0x50,0x53,0xba,0x23,0x20,0x8d, 0x01,0x04,0x50,0x58,0xac,0xe3,0xfb,0x08, - 0x01,0x04,0x50,0x59,0xcb,0xac,0x1f,0x41, - 0x01,0x04,0x50,0x5d,0xd5,0xf6,0x20,0x8d, - 0x01,0x04,0x50,0x93,0x52,0xa5,0x20,0x8d, + 0x01,0x04,0x50,0xd1,0x57,0x67,0x24,0x75, 0x01,0x04,0x50,0xe5,0x1c,0x3c,0x20,0x8d, - 0x01,0x04,0x50,0xf7,0xe9,0x28,0x20,0x8d, - 0x01,0x04,0x50,0xff,0x08,0x5d,0x20,0x8d, + 0x01,0x04,0x51,0x07,0x10,0xb6,0x20,0x8d, 0x01,0x04,0x51,0x07,0x11,0xca,0x20,0x8d, - 0x01,0x04,0x51,0x0a,0xf1,0xa5,0x20,0x8d, - 0x01,0x04,0x51,0x15,0x56,0x9d,0x20,0x8d, + 0x01,0x04,0x51,0x13,0x0a,0x02,0x20,0x8d, + 0x01,0x04,0x51,0x58,0xdd,0xbe,0x20,0x8d, 0x01,0x04,0x51,0xab,0x16,0x8f,0x20,0x8d, - 0x01,0x04,0x51,0xed,0xce,0xe0,0x20,0x97, - 0x01,0x04,0x52,0x45,0x17,0xc3,0x20,0x8d, + 0x01,0x04,0x51,0xe0,0x2c,0xa4,0x20,0x8d, + 0x01,0x04,0x51,0xe0,0xa0,0x51,0x20,0x8d, + 0x01,0x04,0x52,0x01,0x44,0x36,0x20,0x8d, + 0x01,0x04,0x52,0x15,0xa4,0x2f,0x20,0x8d, + 0x01,0x04,0x52,0x40,0x74,0x05,0x20,0x8d, + 0x01,0x04,0x52,0x42,0x0a,0x0b,0x20,0x8d, 0x01,0x04,0x52,0x60,0x60,0x28,0x20,0x8d, 0x01,0x04,0x52,0x74,0x32,0x65,0x20,0x8d, + 0x01,0x04,0x52,0x81,0x44,0x3e,0x20,0x8d, 0x01,0x04,0x52,0x88,0x63,0x7a,0x20,0x8d, - 0x01,0x04,0x52,0x95,0x61,0x19,0x44,0x9f, 0x01,0x04,0x52,0x9a,0x18,0xd1,0x20,0x8d, - 0x01,0x04,0x52,0xa5,0xf1,0x32,0x20,0x8d, - 0x01,0x04,0x52,0xc5,0xda,0xfd,0x20,0x8d, - 0x01,0x04,0x52,0xca,0x44,0xe7,0x20,0x8d, + 0x01,0x04,0x52,0xc5,0xd7,0x7d,0x20,0x8d, + 0x01,0x04,0x53,0x80,0x84,0x5b,0x20,0x8d, 0x01,0x04,0x53,0x89,0x29,0x0a,0x20,0x8d, 0x01,0x04,0x53,0xd0,0x06,0xd3,0x20,0x8d, - 0x01,0x04,0x53,0xd9,0x08,0x1f,0xad,0x84, - 0x01,0x04,0x53,0xdc,0x6e,0x30,0x20,0x8d, + 0x01,0x04,0x53,0xd0,0xc1,0xf2,0x20,0x8d, 0x01,0x04,0x53,0xde,0x8a,0x55,0x20,0x8d, + 0x01,0x04,0x53,0xf0,0x7c,0x44,0x20,0x8d, 0x01,0x04,0x53,0xf3,0xbf,0xc7,0x20,0x8d, - 0x01,0x04,0x54,0x16,0x8b,0x39,0x20,0x8d, - 0x01,0x04,0x54,0x1b,0x9b,0x11,0x20,0x8d, - 0x01,0x04,0x54,0x4b,0x1c,0xf7,0x20,0x8d, + 0x01,0x04,0x54,0x09,0x05,0xd3,0x20,0x8d, + 0x01,0x04,0x54,0x1c,0x39,0x5a,0x20,0x8d, + 0x01,0x04,0x54,0x26,0x03,0xf9,0x20,0x8d, 0x01,0x04,0x54,0x70,0x3c,0x10,0x20,0x8d, - 0x01,0x04,0x54,0xd3,0x07,0x38,0x20,0x8d, - 0x01,0x04,0x54,0xed,0x07,0xf9,0x20,0x8d, - 0x01,0x04,0x55,0x17,0x33,0xb1,0x20,0x8d, - 0x01,0x04,0x55,0x18,0x91,0xc6,0x20,0x8d, - 0x01,0x04,0x55,0xb8,0x8a,0x6c,0x20,0x8d, + 0x01,0x04,0x54,0xd7,0x38,0x77,0x20,0x8d, + 0x01,0x04,0x54,0xe2,0xf3,0xaf,0x20,0x8d, + 0x01,0x04,0x54,0xf5,0x0e,0x49,0x20,0x8d, + 0x01,0x04,0x54,0xfc,0x9d,0x5a,0x47,0x9d, + 0x01,0x04,0x54,0xff,0xf4,0x3d,0x20,0x8d, + 0x01,0x04,0x55,0x17,0x18,0x7b,0x20,0x8d, + 0x01,0x04,0x55,0x34,0xb9,0x1d,0x21,0xda, + 0x01,0x04,0x55,0x3a,0x78,0xc9,0x20,0x8d, + 0x01,0x04,0x55,0x5d,0x60,0x12,0x20,0x8d, + 0x01,0x04,0x55,0xa5,0x08,0xc5,0x20,0x8d, + 0x01,0x04,0x55,0xad,0xa5,0x42,0x20,0x8d, + 0x01,0x04,0x55,0xb8,0x8f,0x69,0x20,0x8d, + 0x01,0x04,0x55,0xbf,0x4a,0x67,0x20,0x8d, 0x01,0x04,0x55,0xc2,0xee,0x86,0x20,0x8d, 0x01,0x04,0x55,0xc3,0x36,0x6e,0x20,0x8d, + 0x01,0x04,0x55,0xc3,0xc4,0x8e,0x20,0x8d, + 0x01,0x04,0x55,0xd0,0x45,0x0b,0x20,0x8d, + 0x01,0x04,0x55,0xd0,0x45,0x15,0x20,0x8d, 0x01,0x04,0x55,0xd0,0x47,0x24,0x20,0x8d, 0x01,0x04,0x55,0xd0,0x47,0x27,0x20,0x8d, - 0x01,0x04,0x55,0xd6,0x88,0x2d,0x20,0x8d, + 0x01,0x04,0x55,0xd6,0x76,0x47,0x20,0x8d, 0x01,0x04,0x55,0xd6,0xa1,0xfc,0x20,0x8d, - 0x01,0x04,0x55,0xe3,0xf5,0x80,0x20,0x8d, - 0x01,0x04,0x56,0x12,0x22,0xf3,0x20,0x8d, - 0x01,0x04,0x56,0x14,0x32,0xaa,0x20,0x8d, - 0x01,0x04,0x56,0x31,0x69,0x5a,0x20,0x8d, - 0x01,0x04,0x56,0x4c,0x07,0x84,0x20,0x8d, + 0x01,0x04,0x55,0xd8,0x20,0x49,0x20,0x8d, + 0x01,0x04,0x55,0xfe,0x62,0xdd,0x20,0x8d, + 0x01,0x04,0x56,0x3a,0x0b,0x98,0x20,0x8d, + 0x01,0x04,0x56,0x5f,0x08,0xf9,0x20,0x8d, 0x01,0x04,0x56,0x64,0x1a,0xbc,0x20,0x8d, 0x01,0x04,0x56,0x6a,0x8f,0x8f,0xd8,0x4d, - 0x01,0x04,0x56,0x78,0x3a,0x42,0x20,0x8d, + 0x01,0x04,0x56,0x7c,0x91,0xb8,0x20,0x8d, 0x01,0x04,0x56,0x85,0xfb,0xef,0x22,0xc5, - 0x01,0x04,0x56,0x95,0x08,0x17,0x22,0xc5, - 0x01,0x04,0x57,0x4e,0xc5,0xea,0x20,0x8d, + 0x01,0x04,0x57,0x4f,0x5e,0xdd,0x20,0x8d, 0x01,0x04,0x57,0x78,0x08,0x05,0x4e,0x28, - 0x01,0x04,0x57,0x79,0x25,0x9c,0x20,0x8d, - 0x01,0x04,0x58,0x52,0xb5,0x2c,0x20,0x8d, - 0x01,0x04,0x58,0x57,0x5d,0x34,0x06,0x9b, - 0x01,0x04,0x58,0x62,0xeb,0x86,0x20,0x8d, - 0x01,0x04,0x58,0x88,0xbb,0xd6,0x20,0x8d, - 0x01,0x04,0x58,0x93,0xf4,0xfa,0x20,0x8d, - 0x01,0x04,0x58,0x94,0x99,0x94,0x20,0x8d, + 0x01,0x04,0x57,0x7d,0x9d,0xdc,0x20,0x8d, + 0x01,0x04,0x58,0x09,0x4c,0x85,0x20,0x8d, + 0x01,0x04,0x58,0x5a,0xb8,0x44,0x20,0x8d, + 0x01,0x04,0x58,0x97,0x65,0x0e,0x13,0x88, + 0x01,0x04,0x58,0x97,0x65,0xfd,0x13,0x88, + 0x01,0x04,0x58,0xc6,0x5c,0x2f,0x20,0x8d, + 0x01,0x04,0x58,0xd0,0x73,0x46,0x20,0x8d, + 0x01,0x04,0x58,0xd2,0x0f,0x18,0x20,0x8d, 0x01,0x04,0x58,0xd4,0x2d,0xa6,0x20,0x8d, - 0x01,0x04,0x58,0xd4,0x37,0x8a,0x20,0x8d, - 0x01,0x04,0x59,0x26,0x60,0x99,0x24,0x39, - 0x01,0x04,0x59,0x2f,0xa1,0x87,0x20,0x8d, - 0x01,0x04,0x59,0x58,0x3e,0xbe,0x20,0x8d, - 0x01,0x04,0x59,0x9e,0x20,0x2c,0x20,0x8d, - 0x01,0x04,0x59,0xa3,0x91,0xf0,0x20,0x8d, - 0x01,0x04,0x59,0xa3,0xf9,0xea,0x0e,0x59, - 0x01,0x04,0x59,0xb0,0xc4,0x50,0x20,0x8d, - 0x01,0x04,0x59,0xd8,0x15,0x60,0x20,0x8d, - 0x01,0x04,0x5a,0x54,0xe3,0xff,0x20,0x8d, + 0x01,0x04,0x59,0x66,0xce,0xee,0x20,0x8d, + 0x01,0x04,0x59,0x67,0x6f,0x22,0x20,0x8d, + 0x01,0x04,0x59,0x72,0x8f,0x71,0x20,0x8d, + 0x01,0x04,0x59,0x86,0x3e,0x4a,0x20,0x8d, + 0x01,0x04,0x59,0x98,0x08,0xe7,0x20,0x8d, + 0x01,0x04,0x59,0xa1,0x1a,0x4e,0x20,0x8d, + 0x01,0x04,0x59,0xcf,0x83,0x13,0x20,0x8d, + 0x01,0x04,0x59,0xf8,0xc1,0xe5,0x20,0x8d, + 0x01,0x04,0x5a,0x03,0x30,0x3e,0x20,0x8d, + 0x01,0x04,0x5a,0x92,0x79,0x61,0x20,0x8d, 0x01,0x04,0x5a,0x92,0x82,0xd6,0x20,0x8d, + 0x01,0x04,0x5a,0xc4,0xa9,0x3a,0x20,0x8d, 0x01,0x04,0x5a,0xfa,0x09,0x01,0x20,0x8d, 0x01,0x04,0x5b,0x5d,0xc2,0x9a,0x20,0x8d, - 0x01,0x04,0x5b,0x6a,0xbc,0xe5,0x20,0x8d, 0x01,0x04,0x5b,0x7e,0x28,0x6d,0x20,0x8d, - 0x01,0x04,0x5b,0x89,0x7f,0x7b,0x20,0x8d, - 0x01,0x04,0x5b,0x93,0xe8,0x62,0x20,0x8d, - 0x01,0x04,0x5b,0x98,0x7b,0x12,0x20,0x8d, - 0x01,0x04,0x5b,0xb2,0x11,0x78,0x20,0x8d, 0x01,0x04,0x5b,0xcc,0x63,0xb2,0x20,0x8d, - 0x01,0x04,0x5b,0xdf,0xaf,0x0e,0x20,0x8d, - 0x01,0x04,0x5c,0x2a,0x6e,0xf2,0x20,0x8d, - 0x01,0x04,0x5c,0x35,0x5a,0x54,0x20,0x8d, - 0x01,0x04,0x5c,0xdd,0x9b,0xe4,0x20,0x8d, + 0x01,0x04,0x5b,0xcc,0x95,0x05,0x20,0x8d, + 0x01,0x04,0x5b,0xce,0x11,0xc3,0x20,0x8d, + 0x01,0x04,0x5b,0xd1,0x33,0x83,0x20,0x8d, + 0x01,0x04,0x5b,0xd7,0x5b,0xfe,0x20,0x8d, + 0x01,0x04,0x5c,0x5b,0x1b,0x3c,0x20,0x8d, + 0x01,0x04,0x5c,0xdd,0x14,0xe8,0x20,0x8d, + 0x01,0x04,0x5c,0xff,0x55,0x1f,0x20,0x8d, + 0x01,0x04,0x5d,0x04,0x65,0x25,0x20,0x8d, + 0x01,0x04,0x5d,0x2e,0x51,0x05,0x20,0x8d, 0x01,0x04,0x5d,0x39,0x51,0xa2,0x20,0x8d, + 0x01,0x04,0x5d,0x49,0x27,0xc4,0x20,0x8d, + 0x01,0x04,0x5d,0x5a,0x52,0xe2,0x20,0x8d, 0x01,0x04,0x5d,0x5f,0x58,0x0d,0x20,0x8d, - 0x01,0x04,0x5d,0x67,0x0d,0x01,0x20,0x8d, 0x01,0x04,0x5d,0x7b,0xb4,0xa4,0x20,0x8d, - 0x01,0x04,0x5d,0xbe,0x75,0x1a,0x20,0x8d, - 0x01,0x04,0x5e,0x69,0x7d,0xf0,0x20,0x8d, - 0x01,0x04,0x5e,0x6e,0x17,0xd7,0x20,0x8d, + 0x01,0x04,0x5d,0xbd,0x91,0xa9,0x20,0x8d, + 0x01,0x04,0x5e,0x11,0xb9,0x6b,0x20,0x8d, + 0x01,0x04,0x5e,0x4b,0xc6,0x78,0x20,0x8d, + 0x01,0x04,0x5e,0x72,0xc4,0xa9,0x20,0x8d, + 0x01,0x04,0x5e,0x8e,0xd5,0xfa,0xd8,0xf8, 0x01,0x04,0x5e,0x9a,0x9f,0x63,0x20,0x8d, - 0x01,0x04,0x5e,0xbd,0xa1,0x77,0x20,0x8d, - 0x01,0x04,0x5e,0xcb,0xff,0x46,0x20,0x8d, - 0x01,0x04,0x5e,0xe8,0xad,0x5d,0x20,0x8d, - 0x01,0x04,0x5f,0x4f,0x7a,0x63,0x20,0x8d, - 0x01,0x04,0x5f,0x50,0x01,0x6e,0x20,0x8d, - 0x01,0x04,0x5f,0x53,0x49,0x1f,0x20,0x8d, + 0x01,0x04,0x5e,0x9e,0xf6,0xb7,0x20,0x8d, + 0x01,0x04,0x5e,0xef,0x91,0x20,0x20,0x8d, + 0x01,0x04,0x5f,0x1f,0x0c,0x16,0x20,0x8d, + 0x01,0x04,0x5f,0x1f,0xc4,0x0f,0x20,0x8d, 0x01,0x04,0x5f,0x6e,0x85,0xdf,0x20,0x8d, 0x01,0x04,0x5f,0x6e,0xea,0x5d,0x20,0x8d, - 0x01,0x04,0x5f,0xa4,0x41,0xc2,0x20,0x8d, - 0x01,0x04,0x5f,0xa5,0x08,0xb6,0x20,0x8d, - 0x01,0x04,0x5f,0xae,0xdb,0x65,0x20,0x8d, + 0x01,0x04,0x5f,0xa1,0x0c,0x2d,0x20,0x8d, 0x01,0x04,0x5f,0xbf,0x82,0x64,0x20,0x8d, + 0x01,0x04,0x5f,0xd0,0x9e,0xa1,0x20,0x8d, + 0x01,0x04,0x5f,0xd5,0x91,0xda,0x20,0x8d, 0x01,0x04,0x5f,0xd6,0x35,0x9a,0x20,0x8d, - 0x01,0x04,0x5f,0xd7,0xcd,0xb4,0x20,0x8d, - 0x01,0x04,0x60,0x2b,0x82,0xea,0x20,0x8d, - 0x01,0x04,0x62,0x19,0xc9,0x1f,0x20,0x8d, - 0x01,0x04,0x62,0x80,0xf7,0xb6,0x20,0x8d, - 0x01,0x04,0x62,0xab,0x15,0x81,0x20,0x8d, - 0x01,0x04,0x63,0x93,0x87,0xa1,0x20,0x8d, - 0x01,0x04,0x65,0x64,0xa3,0x76,0x20,0x87, - 0x01,0x04,0x66,0x84,0xf5,0x10,0x20,0x8d, - 0x01,0x04,0x66,0xb6,0xcc,0x60,0x20,0x8d, - 0x01,0x04,0x66,0xb6,0xeb,0xf5,0x20,0x8d, + 0x01,0x04,0x5f,0xd6,0x35,0xa0,0x20,0x8d, + 0x01,0x04,0x60,0x2c,0x9c,0xc7,0x20,0x8d, + 0x01,0x04,0x61,0x4b,0x91,0x0c,0x20,0x8d, + 0x01,0x04,0x66,0x84,0xc0,0x8d,0x20,0x8d, 0x01,0x04,0x67,0x0e,0xf5,0xfa,0x20,0x8d, - 0x01,0x04,0x67,0x2f,0xc0,0x0f,0x20,0x8d, - 0x01,0x04,0x67,0x54,0x54,0xfa,0x20,0x8f, - 0x01,0x04,0x67,0x63,0xa8,0x82,0x20,0x8d, + 0x01,0x04,0x67,0x55,0x26,0xcd,0x20,0x8d, + 0x01,0x04,0x67,0x58,0x5c,0x4e,0x20,0x8c, + 0x01,0x04,0x67,0x63,0xa8,0x64,0x20,0x8d, 0x01,0x04,0x67,0x63,0xa8,0x8c,0x20,0x8d, - 0x01,0x04,0x67,0xc6,0xc0,0x0e,0x4e,0x28, - 0x01,0x04,0x67,0xe8,0x68,0xe3,0x20,0x8d, - 0x01,0x04,0x68,0x8f,0x02,0xc3,0x20,0x8d, - 0x01,0x04,0x68,0xac,0xeb,0xe3,0x20,0x8d, + 0x01,0x04,0x67,0x63,0xaa,0xd2,0x20,0x8d, + 0x01,0x04,0x67,0x63,0xaa,0xdc,0x20,0x8d, + 0x01,0x04,0x67,0x64,0x2c,0x46,0x20,0x8d, + 0x01,0x04,0x67,0xb2,0xec,0x1b,0x20,0x8d, + 0x01,0x04,0x67,0xd1,0x0c,0x90,0x20,0x8d, + 0x01,0x04,0x68,0x3b,0x93,0x0f,0x20,0x8d, + 0x01,0x04,0x68,0x81,0xab,0x79,0x20,0x8d, + 0x01,0x04,0x68,0xc8,0x41,0xea,0x20,0x8d, 0x01,0x04,0x68,0xee,0xdc,0xc7,0x20,0x8d, - 0x01,0x04,0x6b,0x0b,0x73,0x44,0x20,0x8d, + 0x01,0x04,0x68,0xf4,0x49,0x06,0x20,0x8d, + 0x01,0x04,0x6a,0x47,0x77,0xe6,0x20,0x8d, 0x01,0x04,0x6b,0xad,0xa6,0x2b,0x20,0x8d, - 0x01,0x04,0x6c,0x04,0xd4,0x53,0x20,0x8d, - 0x01,0x04,0x6d,0x88,0x49,0x61,0x20,0x8d, - 0x01,0x04,0x6d,0xad,0x62,0x17,0x20,0x8d, - 0x01,0x04,0x6d,0xbe,0x44,0x74,0x20,0x8d, - 0x01,0x04,0x6d,0xeb,0xf6,0x3c,0x20,0x8d, + 0x01,0x04,0x6c,0xa1,0x16,0x4e,0x20,0x8d, + 0x01,0x04,0x6c,0xae,0x3f,0xea,0x20,0x8d, + 0x01,0x04,0x6d,0x63,0x3f,0x9f,0x20,0x8d, + 0x01,0x04,0x6d,0x69,0x28,0xf7,0x20,0x8d, + 0x01,0x04,0x6d,0x6b,0xb9,0x82,0x20,0x8d, + 0x01,0x04,0x6d,0x6e,0xef,0x04,0x20,0x8d, + 0x01,0x04,0x6d,0xad,0x29,0x2b,0x20,0x8d, + 0x01,0x04,0x6d,0xec,0x5a,0x75,0x20,0x8d, 0x01,0x04,0x6d,0xf8,0xce,0x0d,0x20,0x8d, - 0x01,0x04,0x6e,0x0c,0x40,0x60,0x20,0x8d, + 0x01,0x04,0x6d,0xff,0x6a,0xce,0x20,0x8d, + 0x01,0x04,0x6f,0x5a,0x8c,0x17,0x20,0x8d, 0x01,0x04,0x6f,0x5a,0x8c,0x2e,0x20,0x8d, - 0x01,0x04,0x6f,0x5a,0x9f,0xb8,0xc3,0x51, - 0x01,0x04,0x71,0x6b,0xc9,0x83,0x20,0x8d, + 0x01,0x04,0x6f,0x5a,0x9f,0xf6,0x20,0x8d, + 0x01,0x04,0x70,0x76,0xbc,0x32,0x20,0x8d, 0x01,0x04,0x73,0x2f,0x8d,0xfa,0x22,0xb5, 0x01,0x04,0x74,0x3a,0xab,0x43,0x20,0x8d, - 0x01,0x04,0x74,0x57,0x39,0xda,0x20,0x8d, - 0x01,0x04,0x74,0xca,0xa1,0x38,0x20,0x8d, - 0x01,0x04,0x75,0x33,0x9f,0x82,0x20,0x8d, - 0x01,0x04,0x76,0x67,0x7e,0x8c,0x6e,0xad, - 0x01,0x04,0x79,0x2d,0xbe,0xd2,0x20,0x8d, - 0x01,0x04,0x79,0x63,0xc1,0x19,0x20,0x8d, - 0x01,0x04,0x7a,0x70,0x94,0x99,0x20,0x93, - 0x01,0x04,0x7a,0x94,0x87,0xea,0x20,0x8d, + 0x01,0x04,0x76,0x5c,0x6b,0x6c,0x20,0x8d, + 0x01,0x04,0x77,0x2a,0x37,0xcb,0x20,0x8d, + 0x01,0x04,0x78,0x4f,0x47,0x48,0x20,0x8d, + 0x01,0x04,0x79,0x63,0xf0,0x57,0x20,0x8d, + 0x01,0x04,0x7b,0x3c,0xd5,0xc0,0x20,0x8d, + 0x01,0x04,0x7c,0x9c,0x9e,0x64,0x20,0x8d, + 0x01,0x04,0x7c,0xde,0x7b,0xee,0x20,0x8d, + 0x01,0x04,0x7d,0xb2,0x06,0x74,0x20,0x8d, 0x01,0x04,0x80,0x00,0xbe,0x1a,0x20,0x8d, 0x01,0x04,0x80,0x41,0xc2,0x88,0x20,0x8d, + 0x01,0x04,0x81,0x0d,0xbd,0xd4,0x20,0x8d, 0x01,0x04,0x81,0x7e,0xac,0x73,0x20,0x8d, - 0x01,0x04,0x81,0xe2,0x7d,0x0a,0x20,0x8d, + 0x01,0x04,0x81,0x92,0x34,0xae,0x20,0x8d, + 0x01,0x04,0x82,0x2c,0xa8,0xca,0x20,0x8d, + 0x01,0x04,0x83,0xa1,0x50,0xa6,0x20,0x8d, 0x01,0x04,0x83,0xbc,0x28,0xbf,0x20,0x8d, 0x01,0x04,0x86,0xc3,0xb9,0x34,0x20,0x8d, - 0x01,0x04,0x87,0xb4,0x2c,0x3d,0x20,0x8d, - 0x01,0x04,0x88,0x34,0x72,0x7b,0x20,0x8d, + 0x01,0x04,0x87,0x86,0xee,0x2f,0x20,0x8d, + 0x01,0x04,0x87,0xb4,0xda,0x3a,0x20,0x8d, + 0x01,0x04,0x87,0xb5,0xd7,0xed,0x20,0x8d, + 0x01,0x04,0x88,0x1d,0x6d,0xb4,0x20,0x8d, + 0x01,0x04,0x88,0x20,0xee,0x06,0x20,0x8d, 0x01,0x04,0x88,0x38,0xaa,0x60,0x20,0x8d, - 0x01,0x04,0x89,0x74,0xd5,0x8f,0x20,0x8d, + 0x01,0x04,0x89,0x19,0x26,0x6c,0x20,0x8d, 0x01,0x04,0x89,0xe2,0x22,0x2e,0x20,0x8d, - 0x01,0x04,0x8a,0x2b,0xe9,0x39,0x20,0x8d, + 0x01,0x04,0x8a,0xcf,0xd3,0x6a,0x20,0x8d, 0x01,0x04,0x8b,0x82,0x29,0x52,0x20,0x8d, + 0x01,0x04,0x8b,0x99,0xff,0x6b,0x20,0x8d, 0x01,0x04,0x8c,0xbe,0x0c,0x81,0x20,0x8d, - 0x01,0x04,0x8e,0x04,0x69,0x4d,0x20,0x8d, 0x01,0x04,0x8e,0x36,0xb5,0xda,0x20,0x8d, - 0x01,0x04,0x8f,0xb1,0xe7,0xf7,0x20,0x8d, + 0x01,0x04,0x8f,0xb1,0xe5,0x95,0x20,0x8d, 0x01,0x04,0x8f,0xb2,0x40,0x0a,0x20,0x8d, - 0x01,0x04,0x90,0x22,0xa1,0x41,0x47,0x9d, - 0x01,0x04,0x92,0x04,0x7c,0x86,0x20,0x8d, + 0x01,0x04,0x90,0x18,0xf5,0xb7,0x20,0x8d, + 0x01,0x04,0x90,0x7e,0x82,0xb2,0x20,0x8d, + 0x01,0x04,0x92,0x04,0x7c,0x81,0x20,0x8d, + 0x01,0x04,0x92,0x47,0x45,0x67,0x20,0x8d, 0x01,0x04,0x92,0x53,0x38,0x45,0x20,0x8d, - 0x01,0x04,0x92,0x5a,0xc1,0x44,0x20,0x8d, - 0x01,0x04,0x92,0xc4,0x37,0x9c,0x70,0xa1, - 0x01,0x04,0x94,0x42,0x32,0x32,0x20,0x8f, - 0x01,0x04,0x94,0xfb,0x01,0x14,0x20,0x97, - 0x01,0x04,0x97,0x30,0x5f,0xd4,0x20,0x8d, + 0x01,0x04,0x93,0xc2,0xb1,0xa5,0x20,0x8d, + 0x01,0x04,0x95,0x5a,0xd6,0x4e,0x20,0x8d, + 0x01,0x04,0x95,0x66,0x9d,0x9c,0x20,0x8d, + 0x01,0x04,0x97,0xf8,0x9c,0x37,0x20,0x8d, 0x01,0x04,0x97,0xfc,0xc1,0xf5,0x20,0x8d, - 0x01,0x04,0x98,0x2c,0x89,0x53,0x20,0x8d, - 0x01,0x04,0x98,0x73,0xbf,0xc4,0x20,0x8d, - 0x01,0x04,0x9a,0xdd,0x1f,0x56,0x20,0x8d, + 0x01,0x04,0x99,0x5c,0x5d,0x72,0x20,0x8d, + 0x01,0x04,0x9a,0xd3,0x06,0x02,0x20,0x8d, 0x01,0x04,0x9c,0x11,0x67,0x02,0x1f,0x98, - 0x01,0x04,0x9d,0x8a,0x14,0x16,0x20,0x8d, + 0x01,0x04,0x9c,0x92,0xb1,0xdd,0x20,0x8d, + 0x01,0x04,0x9d,0x83,0x8f,0xad,0x20,0x8d, 0x01,0x04,0x9e,0x3a,0xbc,0x25,0x20,0x8d, - 0x01,0x04,0x9e,0x8c,0xd1,0x4f,0x20,0x8d, + 0x01,0x04,0x9e,0xf8,0x27,0xef,0x20,0x8d, 0x01,0x04,0x9f,0x59,0xe6,0x80,0x20,0x8d, - 0x01,0x04,0x9f,0xf6,0x19,0x34,0x20,0x8d, - 0x01,0x04,0xa0,0x14,0x3b,0xfa,0x20,0xf1, - 0x01,0x04,0xa2,0x00,0xea,0xbe,0x20,0x8d, - 0x01,0x04,0xa2,0x3e,0x1a,0xda,0x20,0x8d, - 0x01,0x04,0xa2,0xfa,0xbc,0xc2,0x20,0x8d, - 0x01,0x04,0xa2,0xfb,0x46,0x52,0x20,0x8d, - 0x01,0x04,0xa3,0x9e,0xce,0xff,0x20,0x8d, - 0x01,0x04,0xa4,0x44,0x69,0x69,0x20,0x8d, + 0x01,0x04,0x9f,0xc4,0x03,0xef,0x20,0x8d, + 0x01,0x04,0x9f,0xe0,0xbd,0xfa,0x20,0x8d, + 0x01,0x04,0xa0,0x48,0x33,0x9a,0x20,0x8d, + 0x01,0x04,0xa1,0x1d,0xec,0x37,0x20,0x8d, + 0x01,0x04,0xa1,0x61,0x77,0xa6,0x20,0x8d, + 0x01,0x04,0xa1,0xf6,0x0b,0xe6,0x20,0x8d, + 0x01,0x04,0xa2,0x3e,0x12,0xe2,0x20,0x8d, + 0x01,0x04,0xa2,0xfa,0x7b,0xb3,0x20,0x8d, + 0x01,0x04,0xa2,0xfa,0xbf,0xde,0x20,0x8d, + 0x01,0x04,0xa2,0xfe,0x76,0x14,0x20,0x8d, + 0x01,0x04,0xa3,0xac,0x51,0x46,0x20,0x8d, + 0x01,0x04,0xa4,0x5a,0x2f,0x08,0x20,0x8d, 0x01,0x04,0xa5,0xe4,0xae,0x75,0x20,0x8d, - 0x01,0x04,0xa6,0x3e,0x52,0x67,0x80,0x03, - 0x01,0x04,0xa6,0x46,0x31,0x1a,0x20,0x8d, - 0x01,0x04,0xa6,0x4e,0xf1,0x09,0x20,0x8d, - 0x01,0x04,0xa6,0x4e,0xf1,0x19,0x20,0x8d, - 0x01,0x04,0xa7,0x47,0x49,0xf4,0x20,0x8d, - 0x01,0x04,0xa7,0xb3,0x93,0x9b,0x20,0x8d, + 0x01,0x04,0xa6,0x46,0x91,0x97,0x20,0x8d, 0x01,0x04,0xa8,0x5b,0xee,0x08,0x20,0x8d, + 0x01,0x04,0xaa,0xfd,0x0b,0x19,0x20,0x8d, + 0x01,0x04,0xab,0x67,0xaa,0x73,0x20,0x8d, + 0x01,0x04,0xac,0x5d,0xa6,0x87,0x20,0x8d, + 0x01,0x04,0xac,0x67,0xd9,0xec,0x20,0x8d, 0x01,0x04,0xac,0x69,0x15,0xd8,0x20,0x8d, - 0x01,0x04,0xac,0x75,0x69,0x5f,0x20,0x8d, - 0x01,0x04,0xad,0x17,0x67,0x1e,0x1f,0x40, - 0x01,0x04,0xad,0xcd,0x5c,0x97,0xd6,0x15, - 0x01,0x04,0xad,0xcd,0x5c,0x9a,0xd6,0x15, - 0x01,0x04,0xad,0xcd,0x5c,0x9d,0xd6,0x15, + 0x01,0x04,0xac,0x70,0x99,0x5f,0x20,0x8d, + 0x01,0x04,0xad,0x03,0xda,0x5b,0x20,0x8d, + 0x01,0x04,0xad,0x0c,0x77,0x85,0x20,0x8d, + 0x01,0x04,0xad,0x22,0x7f,0xb5,0x20,0x8d, + 0x01,0x04,0xad,0x4c,0x7b,0xad,0x20,0x8d, + 0x01,0x04,0xad,0xb0,0xc6,0x44,0x20,0x8d, 0x01,0x04,0xad,0xd0,0x98,0xda,0x20,0x8d, 0x01,0x04,0xad,0xf1,0xe3,0xf3,0x20,0x8d, - 0x01,0x04,0xae,0x03,0x04,0xe8,0x20,0x8d, - 0x01,0x04,0xae,0x11,0x0b,0x16,0x20,0x8d, - 0x01,0x04,0xae,0x58,0xf1,0xa7,0x20,0x8d, - 0x01,0x04,0xae,0x72,0x66,0x29,0x20,0x8d, + 0x01,0x04,0xad,0xf6,0x1b,0x07,0x20,0x8d, + 0x01,0x04,0xad,0xff,0xf0,0xcd,0x20,0x8d, + 0x01,0x04,0xae,0x1e,0x2f,0x0f,0x20,0x8d, 0x01,0x04,0xae,0x72,0xfa,0x56,0x20,0x8d, + 0x01,0x04,0xae,0x8a,0x23,0xe5,0x20,0x8d, 0x01,0x04,0xae,0x8e,0xbf,0x88,0x20,0x8d, - 0x01,0x04,0xaf,0x27,0x48,0x57,0x20,0x8d, - 0x01,0x04,0xb0,0x0c,0x10,0x87,0x20,0x8d, - 0x01,0x04,0xb0,0x25,0x17,0x1e,0x20,0x8d, - 0x01,0x04,0xb0,0x3e,0xb3,0xdd,0x20,0x8d, + 0x01,0x04,0xb0,0x0a,0x8f,0xbe,0x20,0x8d, 0x01,0x04,0xb0,0x4a,0x88,0xed,0x20,0x8d, - 0x01,0x04,0xb0,0x63,0x06,0xe2,0x20,0x8d, + 0x01,0x04,0xb0,0x76,0xdc,0x1d,0x20,0x8d, + 0x01,0x04,0xb0,0x7e,0x74,0x07,0x20,0x8d, + 0x01,0x04,0xb0,0x7e,0xa7,0x0a,0x20,0x8d, 0x01,0x04,0xb0,0xd4,0xb9,0x99,0x20,0x8d, + 0x01,0x04,0xb0,0xeb,0xd1,0xba,0x20,0x8d, 0x01,0x04,0xb1,0x51,0xec,0x75,0x20,0x8d, - 0x01,0x04,0xb2,0x13,0x6a,0x1a,0x20,0x8d, - 0x01,0x04,0xb2,0x15,0x76,0xb2,0x20,0x8d, - 0x01,0x04,0xb2,0x21,0xe8,0x45,0x20,0x8d, - 0x01,0x04,0xb2,0x4f,0x54,0x8b,0x20,0x8d, + 0x01,0x04,0xb1,0x59,0xcd,0x46,0x20,0x8d, + 0x01,0x04,0xb2,0x30,0xa8,0x0c,0x20,0x8d, 0x01,0x04,0xb2,0x7c,0xa2,0xd1,0x20,0x8d, - 0x01,0x04,0xb2,0x84,0x02,0xf6,0x20,0x8d, - 0x01,0x04,0xb2,0x96,0x60,0x2e,0x20,0x8d, - 0x01,0x04,0xb2,0xa2,0xd4,0x2c,0x20,0x8d, - 0x01,0x04,0xb2,0xc1,0xe2,0x78,0x20,0x8d, + 0x01,0x04,0xb2,0x9f,0x62,0x85,0x20,0x8d, + 0x01,0x04,0xb2,0xc4,0x59,0xd1,0x20,0x8d, 0x01,0x04,0xb2,0xec,0x89,0x3f,0x20,0x8d, + 0x01,0x04,0xb2,0xfc,0x7b,0x18,0x20,0x8d, + 0x01,0x04,0xb3,0x2b,0xaa,0xba,0x20,0x8d, 0x01,0x04,0xb4,0x96,0x2e,0xbb,0x20,0x8d, - 0x01,0x04,0xb5,0xa4,0xd2,0xe4,0x21,0x52, - 0x01,0x04,0xb7,0x6e,0xdc,0xd2,0x76,0x5d, - 0x01,0x04,0xb8,0x5f,0x3a,0xa6,0x20,0x90, - 0x01,0x04,0xb8,0xa4,0x93,0x52,0xa1,0x75, - 0x01,0x04,0xb8,0xab,0xd0,0x6d,0x20,0x8d, - 0x01,0x04,0xb9,0x11,0x8f,0xdc,0x20,0x8d, - 0x01,0x04,0xb9,0x15,0xd9,0x31,0x20,0x8d, + 0x01,0x04,0xb5,0x75,0x80,0x8c,0x20,0x8d, + 0x01,0x04,0xb8,0x13,0x13,0x10,0x20,0x8d, + 0x01,0x04,0xb9,0x15,0xd9,0x30,0x20,0x8d, 0x01,0x04,0xb9,0x19,0x30,0xb8,0x20,0x8d, - 0x01,0x04,0xb9,0x1c,0x60,0x10,0x20,0x8d, 0x01,0x04,0xb9,0x1f,0x88,0xf6,0x20,0x8d, + 0x01,0x04,0xb9,0x34,0x5d,0x2d,0x20,0x8d, 0x01,0x04,0xb9,0x40,0x74,0x0f,0x20,0x8d, 0x01,0x04,0xb9,0x44,0xf9,0x5b,0x20,0x8d, - 0x01,0x04,0xb9,0x6c,0xf7,0xbe,0x20,0x8d, - 0x01,0x04,0xb9,0x8d,0x3c,0x24,0x20,0x8d, - 0x01,0x04,0xb9,0x94,0x03,0xe3,0x20,0x8d, + 0x01,0x04,0xb9,0x62,0x36,0x14,0x20,0x8d, + 0x01,0x04,0xb9,0x6b,0x53,0x37,0x20,0x8d, + 0x01,0x04,0xb9,0x8c,0xfd,0xa9,0x20,0x8d, 0x01,0x04,0xb9,0x94,0x91,0x4a,0x20,0x8d, - 0x01,0x04,0xb9,0x9f,0x14,0x8f,0x20,0x8d, + 0x01,0x04,0xb9,0xa5,0xaa,0x13,0x20,0x8d, 0x01,0x04,0xb9,0xa7,0x71,0x3b,0x20,0x8d, 0x01,0x04,0xb9,0xb9,0x1a,0x8d,0x1f,0xaf, - 0x01,0x04,0xb9,0xbd,0x84,0xb2,0xe1,0xb4, - 0x01,0x04,0xb9,0xcc,0xc5,0x70,0x20,0x8d, + 0x01,0x04,0xb9,0xc5,0xa3,0x88,0x20,0x8d, + 0x01,0x04,0xb9,0xd1,0x0c,0x4c,0x20,0x8d, 0x01,0x04,0xb9,0xd1,0x46,0x11,0x20,0x8d, - 0x01,0x04,0xb9,0xdc,0x9c,0xc1,0x20,0x8d, - 0x01,0x04,0xb9,0xee,0x81,0x71,0x20,0x8d, + 0x01,0x04,0xb9,0xe3,0x9c,0xe2,0x20,0x8d, + 0x01,0x04,0xb9,0xe9,0xbd,0xd2,0x20,0x8d, 0x01,0x04,0xb9,0xef,0xdd,0x05,0x20,0x8d, - 0x01,0x04,0xb9,0xf4,0xd9,0x27,0x20,0x8d, + 0x01,0x04,0xb9,0xf4,0x64,0x6a,0x20,0x8d, 0x01,0x04,0xb9,0xfe,0x61,0xa4,0x20,0x8d, 0x01,0x04,0xba,0x21,0xa7,0x0b,0x20,0x8d, + 0x01,0x04,0xba,0xb0,0x62,0x25,0x20,0x8d, + 0x01,0x04,0xba,0xf9,0xd9,0x19,0x20,0x8d, + 0x01,0x04,0xba,0xfa,0x5f,0x84,0x20,0x8d, 0x01,0x04,0xbc,0x20,0x0e,0x1f,0x20,0x8e, - 0x01,0x04,0xbc,0x2a,0x28,0xea,0x47,0x9d, - 0x01,0x04,0xbc,0x86,0x08,0x24,0x20,0x8d, + 0x01,0x04,0xbc,0x23,0xa7,0x0e,0x20,0x8d, + 0x01,0x04,0xbc,0x44,0x2d,0x8f,0x20,0x8d, + 0x01,0x04,0xbc,0x75,0xc8,0xd4,0x20,0x8d, 0x01,0x04,0xbc,0x8a,0x58,0x0e,0x20,0x8d, - 0x01,0x04,0xbc,0x9c,0x6e,0xef,0x20,0x8d, - 0x01,0x04,0xbc,0xa5,0xf4,0x8f,0x20,0x8d, - 0x01,0x04,0xbc,0xd5,0x44,0x26,0x20,0x8d, - 0x01,0x04,0xbc,0xd6,0x81,0x41,0x4e,0x2c, - 0x01,0x04,0xbc,0xf2,0x0f,0x4a,0x20,0x8d, - 0x01,0x04,0xbc,0xf4,0x04,0x4e,0x20,0x8d, - 0x01,0x04,0xbd,0x27,0x06,0x52,0x20,0x8d, - 0x01,0x04,0xbd,0xcf,0x2e,0x20,0x20,0x8d, - 0x01,0x04,0xbd,0xd4,0x79,0x4a,0x20,0x8d, - 0x01,0x04,0xc0,0x03,0x0b,0x14,0x20,0x8d, - 0x01,0x04,0xc0,0x41,0xaa,0x0f,0x20,0x8d, + 0x01,0x04,0xbc,0x97,0xed,0x9e,0x20,0x8d, + 0x01,0x04,0xbc,0x9a,0xec,0x31,0x20,0x8d, + 0x01,0x04,0xbd,0x7b,0xb1,0x80,0x20,0x8d, + 0x01,0x04,0xbe,0x7b,0x1b,0x0b,0x20,0x8d, + 0x01,0x04,0xbe,0x91,0x7f,0xfe,0x20,0x8d, + 0x01,0x04,0xc0,0x45,0x35,0x4d,0x20,0x8d, 0x01,0x04,0xc0,0x92,0x89,0x2c,0x20,0x8d, - 0x01,0x04,0xc0,0xb6,0x9d,0x77,0x20,0x8d, - 0x01,0x04,0xc0,0xbb,0x6d,0x8d,0x20,0x8d, - 0x01,0x04,0xc0,0xe3,0x50,0x53,0x20,0x8d, - 0x01,0x04,0xc1,0x0a,0xcb,0x17,0x20,0x8e, - 0x01,0x04,0xc1,0x20,0x7f,0xa0,0xe4,0x6d, - 0x01,0x04,0xc1,0x20,0x7f,0xa2,0xe4,0x6d, - 0x01,0x04,0xc1,0x3a,0xc4,0xd4,0x20,0x8d, - 0x01,0x04,0xc1,0x6a,0x1d,0x6a,0x20,0x8d, - 0x01,0x04,0xc1,0x8a,0x9a,0x2b,0x20,0x8d, - 0x01,0x04,0xc1,0xb2,0xaa,0xe8,0x20,0x8d, + 0x01,0x04,0xc0,0xde,0x18,0x36,0x20,0x8d, + 0x01,0x04,0xc0,0xde,0x93,0x8d,0x20,0x8d, + 0x01,0x04,0xc1,0x20,0x7f,0xa2,0xee,0x29, + 0x01,0x04,0xc1,0x6f,0xc6,0xbb,0x1f,0xaf, 0x01,0x04,0xc1,0xc4,0x25,0x3e,0x20,0x8d, - 0x01,0x04,0xc1,0xde,0x82,0x0e,0x20,0x8d, - 0x01,0x04,0xc1,0xea,0x32,0xe3,0x20,0x8d, - 0x01,0x04,0xc2,0x0e,0xf6,0xcd,0x20,0x8d, - 0x01,0x04,0xc2,0x87,0x87,0x45,0x20,0x8d, + 0x01,0x04,0xc2,0x0d,0x50,0xb9,0x3c,0x46, 0x01,0x04,0xc2,0x93,0x71,0xc9,0x20,0x8d, 0x01,0x04,0xc2,0xa5,0x1e,0x14,0x20,0x8d, - 0x01,0x04,0xc2,0xdb,0x3e,0x17,0x20,0x8d, + 0x01,0x04,0xc2,0xbf,0xef,0x62,0x20,0x8d, 0x01,0x04,0xc3,0x38,0x3f,0x04,0x20,0x8d, - 0x01,0x04,0xc3,0x86,0xb7,0xbc,0x20,0x8d, - 0x01,0x04,0xc3,0xd0,0x67,0x1e,0x20,0xfc, - 0x01,0x04,0xc3,0xd0,0x67,0x1f,0x20,0xfc, + 0x01,0x04,0xc3,0x38,0x3f,0x0a,0x20,0x8d, + 0x01,0x04,0xc3,0x7b,0xef,0xb9,0x20,0x8d, + 0x01,0x04,0xc3,0x8c,0xe2,0x9a,0x20,0x8d, 0x01,0x04,0xc6,0x01,0xe7,0x06,0x20,0x8d, - 0x01,0x04,0xc6,0x0c,0x0e,0x88,0x20,0x8d, - 0x01,0x04,0xc6,0x54,0xed,0x46,0x20,0x8d, - 0x01,0x04,0xc6,0xb2,0x78,0x05,0x1f,0xb0, - 0x01,0x04,0xc7,0x30,0x5c,0xb8,0x20,0x8d, - 0x01,0x04,0xc7,0x44,0xc7,0x13,0x20,0x8d, - 0x01,0x04,0xc7,0xb6,0xb8,0xcc,0x20,0x8d, - 0x01,0x04,0xc7,0xbd,0xf2,0x8d,0x20,0x8d, + 0x01,0x04,0xc6,0x94,0x70,0x1b,0x20,0x8d, + 0x01,0x04,0xc7,0x7e,0xea,0xed,0x20,0x8d, + 0x01,0x04,0xc7,0xc1,0xae,0xad,0x20,0x8d, 0x01,0x04,0xc7,0xf7,0x07,0xd0,0x20,0x8d, - 0x01,0x04,0xc8,0x7a,0xb5,0x25,0x20,0x8d, + 0x01,0x04,0xc8,0x7a,0xb5,0x2e,0x20,0x8d, 0x01,0x04,0xc9,0xbf,0x06,0x67,0x20,0x8d, - 0x01,0x04,0xca,0x6b,0xdb,0x82,0x20,0x8d, + 0x01,0x04,0xc9,0xd4,0x24,0xd1,0x20,0x8d, + 0x01,0x04,0xc9,0xdd,0xea,0xc8,0x20,0x8d, 0x01,0x04,0xca,0x6c,0xd3,0x87,0x20,0x8d, - 0x01,0x04,0xcb,0x5e,0x21,0x70,0x20,0x8d, + 0x01,0x04,0xca,0xa9,0x11,0xb2,0x20,0x8d, + 0x01,0x04,0xca,0xb1,0x18,0x8c,0x20,0x8d, 0x01,0x04,0xcb,0x82,0x30,0x75,0x22,0xb5, 0x01,0x04,0xcb,0x84,0x5e,0xc4,0x20,0x8d, - 0x01,0x04,0xcb,0xa2,0x0d,0xb5,0x20,0x8c, - 0x01,0x04,0xcc,0xbf,0xc9,0x2b,0x20,0x8d, - 0x01,0x04,0xcc,0xe5,0x0a,0x5a,0x20,0x8d, 0x01,0x04,0xcd,0xb2,0x29,0x7c,0x20,0x8d, - 0x01,0x04,0xce,0x37,0xb2,0x9d,0x20,0x8d, - 0x01,0x04,0xce,0x7e,0xcb,0x08,0x20,0x8d, - 0x01,0x04,0xce,0xae,0x73,0x60,0x20,0x8d, + 0x01,0x04,0xce,0x48,0xc9,0xe4,0x20,0x8d, + 0x01,0x04,0xce,0xc0,0xcb,0x00,0x20,0x8d, 0x01,0x04,0xce,0xdf,0x99,0x34,0x20,0x8d, - 0x01,0x04,0xcf,0xbc,0x9f,0x19,0x20,0x8d, + 0x01,0x04,0xcf,0x86,0xd8,0x91,0x20,0x8e, + 0x01,0x04,0xcf,0xbc,0x9a,0x32,0x20,0x8d, 0x01,0x04,0xcf,0xe5,0x2e,0x50,0x20,0x8d, + 0x01,0x04,0xcf,0xff,0xc1,0x2f,0x20,0x8d, + 0x01,0x04,0xd0,0x68,0x5c,0x4a,0x20,0x8d, 0x01,0x04,0xd1,0x3a,0x91,0x9d,0x20,0x8d, - 0x01,0x04,0xd1,0x7e,0x51,0x93,0x20,0x8d, - 0x01,0x04,0xd1,0x91,0x3f,0x96,0x20,0x8d, - 0x01,0x04,0xd1,0xd1,0x0a,0x1e,0x20,0x8d, + 0x01,0x04,0xd1,0x3a,0x9e,0xe8,0x20,0x8f, + 0x01,0x04,0xd1,0x8d,0x2b,0xf3,0x20,0x8d, + 0x01,0x04,0xd1,0xe2,0x8e,0x3e,0x20,0x8d, 0x01,0x04,0xd1,0xed,0x7f,0xe3,0x20,0x8d, - 0x01,0x04,0xd4,0x63,0xe2,0x24,0x23,0x3c, - 0x01,0x04,0xd4,0xb9,0x56,0x54,0x20,0x8d, + 0x01,0x04,0xd1,0xed,0x85,0x36,0x20,0x8d, + 0x01,0x04,0xd3,0xf8,0x5a,0x32,0x20,0x8d, + 0x01,0x04,0xd4,0x15,0x12,0x4e,0x20,0x8d, + 0x01,0x04,0xd4,0x22,0xe1,0x76,0x20,0x8d, + 0x01,0x04,0xd4,0x33,0x92,0x89,0x20,0x8d, 0x01,0x04,0xd4,0xe3,0xd3,0x57,0x20,0x8d, + 0x01,0x04,0xd5,0x00,0x45,0x4c,0x20,0x8d, 0x01,0x04,0xd5,0x05,0x24,0x3a,0x20,0x8d, - 0x01,0x04,0xd5,0x59,0xec,0xdb,0x20,0x8d, - 0x01,0x04,0xd5,0x5d,0x91,0xb7,0x20,0x8d, + 0x01,0x04,0xd5,0x2f,0x40,0x69,0x20,0x8d, + 0x01,0x04,0xd5,0x59,0x87,0x97,0x20,0x8d, + 0x01,0x04,0xd5,0x8d,0x9a,0xc9,0x20,0x8d, + 0x01,0x04,0xd5,0x9f,0xc6,0x2d,0x20,0x8d, + 0x01,0x04,0xd5,0xb8,0xf4,0x18,0x20,0x8d, 0x01,0x04,0xd5,0xd6,0x42,0xb6,0x20,0x8d, - 0x01,0x04,0xd8,0x29,0xf9,0xb2,0x20,0x8d, + 0x01,0x04,0xd5,0xe2,0x7b,0x4c,0x20,0x8d, 0x01,0x04,0xd8,0x92,0xfb,0x08,0x20,0x8d, - 0x01,0x04,0xd8,0xf9,0x46,0x16,0x20,0x8d, - 0x01,0x04,0xd9,0x0b,0xf0,0x04,0x20,0x8d, - 0x01,0x04,0xd9,0x0f,0xb2,0x07,0x20,0x8d, - 0x01,0x04,0xd9,0x18,0xe9,0x74,0x20,0x8d, - 0x01,0x04,0xd9,0x40,0x94,0x62,0xc8,0xc9, + 0x01,0x04,0xd8,0xba,0xee,0x0e,0x20,0x8d, + 0x01,0x04,0xd9,0x05,0x96,0x72,0x20,0x8d, + 0x01,0x04,0xd9,0x0f,0xb2,0x0b,0x20,0x8d, + 0x01,0x04,0xd9,0x18,0xef,0x6d,0x20,0x8d, + 0x01,0x04,0xd9,0x40,0x2f,0x8a,0x20,0x8d, + 0x01,0x04,0xd9,0x49,0x50,0x68,0x20,0x8d, + 0x01,0x04,0xd9,0x4f,0xb5,0x26,0x20,0x8d, + 0x01,0x04,0xd9,0x5c,0x37,0xf6,0x20,0x8d, 0x01,0x04,0xd9,0x71,0x79,0xa9,0x20,0x8d, + 0x01,0x04,0xd9,0x73,0x74,0xfa,0x20,0x8d, + 0x01,0x04,0xd9,0x9b,0xf4,0xaa,0x20,0x8d, 0x01,0x04,0xd9,0xaa,0x7c,0xaa,0x20,0x8d, 0x01,0x04,0xdc,0x84,0x87,0x36,0x20,0x8d, - 0x01,0x04,0xdc,0xdd,0x3a,0x19,0x20,0x8d, 0x01,0x04,0xdc,0xe9,0xb2,0xc7,0x20,0x8d, - 0x01,0x04,0xdd,0xdb,0x61,0x69,0x07,0xd1, - 0x02,0x10,0x20,0x01,0x16,0x08,0x00,0x1b,0x00,0xf9,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x67,0x7b, + 0x01,0x04,0xde,0x9a,0x6f,0x2e,0x20,0x8d, 0x02,0x10,0x20,0x01,0x16,0x20,0x05,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x20,0x8d, + 0x02,0x10,0x20,0x01,0x19,0xf0,0x60,0x01,0x39,0xaa,0x54,0x00,0x03,0xff,0xfe,0xf0,0x09,0x16,0x20,0x8d, + 0x02,0x10,0x20,0x01,0x19,0xf0,0x80,0x01,0x0f,0x71,0x54,0x00,0x04,0xff,0xfe,0x10,0x6a,0x63,0x20,0x8d, 0x02,0x10,0x20,0x01,0x1b,0xc0,0x00,0xc1,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x20,0x00,0x20,0x8d, - 0x02,0x10,0x20,0x01,0x04,0x70,0x1f,0x0a,0x08,0x9a,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x20,0x8d, + 0x02,0x10,0x20,0x01,0x1c,0x02,0x01,0x1e,0x35,0x00,0xdf,0x25,0x63,0x21,0x82,0x60,0xd9,0xbe,0x20,0x8d, + 0x02,0x10,0x20,0x01,0x41,0xd0,0x10,0x04,0x1b,0x79,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x20,0x93, + 0x02,0x10,0x20,0x01,0x41,0xd0,0x02,0x03,0x37,0x39,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x20,0x8d, + 0x02,0x10,0x20,0x01,0x41,0xd0,0x02,0x03,0xaa,0xcc,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x20,0x8d, + 0x02,0x10,0x20,0x01,0x41,0xd0,0x02,0x03,0xbb,0x0a,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x20,0x8d, + 0x02,0x10,0x20,0x01,0x41,0xd0,0x00,0x02,0xbf,0x8f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x20,0x8d, + 0x02,0x10,0x20,0x01,0x41,0xd0,0x03,0x03,0x65,0x86,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x20,0x8d, + 0x02,0x10,0x20,0x01,0x41,0xd0,0x06,0x02,0x44,0x93,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x20,0x8d, + 0x02,0x10,0x20,0x01,0x41,0xd0,0x00,0x08,0xb9,0xd8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x20,0x8d, + 0x02,0x10,0x20,0x01,0x41,0xd0,0x00,0x0a,0x69,0xa2,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x20,0x8d, + 0x02,0x10,0x20,0x01,0x41,0xf0,0x00,0x00,0x00,0x00,0x00,0x62,0x69,0x74,0x63,0x6f,0x69,0x6e,0x20,0x8d, + 0x02,0x10,0x20,0x01,0x44,0xb8,0x02,0x56,0x5d,0x11,0x02,0x16,0x3e,0xff,0xfe,0x39,0xd5,0xd4,0x20,0x8d, + 0x02,0x10,0x20,0x01,0x04,0x70,0x1b,0x62,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x20,0x8d, + 0x02,0x10,0x20,0x01,0x04,0x70,0x1f,0x07,0x08,0x03,0x02,0x0c,0x29,0xff,0xfe,0x2d,0x58,0x79,0x20,0x8d, + 0x02,0x10,0x20,0x01,0x04,0x70,0x1f,0x15,0x01,0x06,0xe2,0xd5,0x5e,0xff,0xfe,0x42,0x7a,0xe5,0x20,0x8d, + 0x02,0x10,0x20,0x01,0x04,0x70,0x1f,0x15,0x0c,0x43,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x11,0x20,0x8d, + 0x02,0x10,0x20,0x01,0x04,0x70,0x00,0x26,0x04,0x72,0x00,0x00,0x00,0x00,0x00,0x00,0x0b,0x7c,0x20,0x8d, + 0x02,0x10,0x20,0x01,0x04,0x70,0x75,0xe9,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x10,0x20,0x8d, 0x02,0x10,0x20,0x01,0x04,0x70,0xde,0x5a,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xec,0x24,0x75, - 0x02,0x10,0x20,0x01,0x4b,0x98,0x0d,0xc0,0x00,0x45,0x02,0x16,0x3e,0xff,0xfe,0xa2,0x95,0xcd,0x20,0x8d, + 0x02,0x10,0x20,0x01,0x4b,0xa0,0xba,0xbe,0x05,0x84,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x20,0x8d, + 0x02,0x10,0x20,0x01,0x4b,0xa0,0xff,0xff,0x00,0x24,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x20,0x8d, + 0x02,0x10,0x20,0x01,0x4d,0xd0,0x35,0x64,0x00,0x00,0x30,0xb7,0x1d,0x7b,0x6f,0xec,0x4c,0x5c,0x20,0x8d, + 0x02,0x10,0x20,0x01,0x4d,0xd0,0x35,0x64,0x00,0x00,0x08,0x8e,0xb4,0xff,0x2a,0xd0,0x69,0x9b,0x20,0x8d, + 0x02,0x10,0x20,0x01,0x4d,0xd0,0x35,0x64,0x00,0x00,0x9c,0x1c,0xcc,0x31,0x9f,0xe8,0x55,0x05,0x20,0x8d, + 0x02,0x10,0x20,0x01,0x4d,0xd0,0x35,0x64,0x00,0x00,0xa0,0xc4,0xd4,0x1f,0x04,0xc4,0x1b,0xb0,0x20,0x8d, 0x02,0x10,0x20,0x01,0x4d,0xd0,0x35,0x64,0x00,0x00,0xfd,0x76,0xc1,0xd3,0x18,0x54,0x5b,0xd9,0x20,0x8d, + 0x02,0x10,0x20,0x01,0x4d,0xd0,0x35,0x64,0x00,0x01,0x00,0x00,0x00,0x00,0x76,0x76,0x80,0x90,0x20,0x8d, + 0x02,0x10,0x20,0x01,0x4d,0xd0,0x35,0x64,0x00,0x01,0xb9,0x77,0xbd,0x71,0x46,0x12,0x8e,0x40,0x20,0x8d, + 0x02,0x10,0x20,0x01,0x4d,0xd0,0xaf,0x0e,0x35,0x64,0x00,0x00,0x00,0x00,0x00,0x69,0x00,0x01,0x20,0x8d, + 0x02,0x10,0x20,0x01,0x4d,0xd0,0xaf,0x0e,0x35,0x64,0x00,0x00,0x00,0x00,0x00,0x69,0x00,0x90,0x20,0x8d, 0x02,0x10,0x20,0x01,0x4d,0xe8,0xb1,0xb2,0x00,0x01,0x00,0x00,0xde,0xad,0xbe,0xef,0x00,0x07,0x20,0x8d, 0x02,0x10,0x20,0x01,0x06,0x38,0xa0,0x00,0x41,0x40,0x00,0x00,0x00,0x00,0xff,0xff,0x01,0x91,0x20,0x8d, - 0x02,0x10,0x20,0x01,0x06,0x48,0x28,0x00,0x01,0x31,0x4b,0x1f,0xf6,0xfc,0x20,0xf7,0xf9,0x9f,0x20,0x8d, - 0x02,0x10,0x20,0x01,0x06,0x78,0x0c,0xc8,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x10,0x00,0x88,0x4e,0x28, + 0x02,0x10,0x20,0x01,0x06,0x78,0x0a,0xcc,0x00,0x42,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x20,0x8d, 0x02,0x10,0x20,0x01,0x06,0x7c,0x26,0xb4,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x44,0x20,0x8d, - 0x02,0x10,0x20,0x01,0x06,0x7c,0x2d,0xb8,0x00,0x13,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x92,0x20,0x8d, + 0x02,0x10,0x20,0x01,0x06,0x7c,0x2d,0xb8,0x00,0x06,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x36,0x20,0x8d, + 0x02,0x10,0x20,0x01,0x07,0xc0,0x23,0x10,0x00,0x00,0xf8,0x16,0x3e,0xff,0xfe,0x0d,0x4a,0xb6,0x20,0x8d, 0x02,0x10,0x20,0x01,0x07,0xc0,0x23,0x10,0x00,0x00,0xf8,0x16,0x3e,0xff,0xfe,0x6c,0x4f,0x58,0x20,0x8d, - 0x02,0x10,0x20,0x01,0x08,0x18,0xea,0x1b,0x76,0x00,0xf0,0x53,0xaa,0xde,0xf4,0x7b,0xb7,0x01,0x20,0x8d, - 0x02,0x10,0x20,0x01,0x08,0xf1,0x14,0x04,0x37,0x00,0x8e,0x49,0x71,0x5a,0x2e,0x09,0xb6,0x34,0x24,0xe4, - 0x02,0x10,0x20,0x01,0x09,0x85,0x55,0xa0,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x20,0x8d, - 0x02,0x10,0x20,0x01,0x09,0x99,0x02,0x70,0x2c,0x2c,0x0c,0x8b,0x3a,0x20,0x3f,0x2f,0x31,0x8f,0x20,0x8d, + 0x02,0x10,0x20,0x01,0x08,0x61,0x32,0x46,0x0a,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x40,0x20,0x8d, + 0x02,0x10,0x20,0x01,0x0b,0x07,0x02,0xe6,0x38,0xd7,0xba,0x27,0xeb,0xff,0xfe,0x60,0x3d,0xc1,0x20,0x8d, + 0x02,0x10,0x20,0x01,0x0b,0x07,0x64,0x61,0x78,0x11,0x04,0x89,0xd2,0xda,0x0e,0x07,0x1a,0xf7,0x20,0x8d, 0x02,0x10,0x20,0x01,0x0b,0x07,0x0a,0xc9,0x44,0x2b,0x79,0xd6,0xbb,0xbe,0xb3,0x7c,0xa7,0x83,0x20,0x8d, + 0x02,0x10,0x20,0x01,0x0b,0xc8,0x16,0x00,0x00,0x00,0x02,0x08,0xa2,0xff,0xfe,0x0c,0x8a,0x2e,0x20,0x8d, + 0x02,0x10,0x20,0x01,0x0b,0xc8,0x32,0x3c,0x00,0xff,0xa6,0x34,0x38,0x4f,0x18,0x49,0xf4,0xbc,0x20,0x8d, + 0x02,0x10,0x20,0x01,0x0b,0xc8,0x32,0x3c,0x00,0xff,0xd2,0x17,0xc2,0xff,0xfe,0x07,0x2c,0xd9,0x20,0x8d, + 0x02,0x10,0x20,0x01,0x0b,0xc8,0x3b,0xec,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x20,0x8d, 0x02,0x10,0x20,0x02,0x2f,0x5b,0xa5,0xf9,0x00,0x00,0x00,0x00,0x00,0x00,0x2f,0x5b,0xa5,0xf9,0x22,0xb5, - 0x02,0x10,0x20,0x02,0xb6,0xff,0x3d,0xca,0x00,0x00,0x00,0x00,0x00,0x00,0xb6,0xff,0x3d,0xca,0x6e,0xcc, + 0x02,0x10,0x20,0x03,0x00,0xcb,0x87,0x13,0x61,0x02,0xaa,0xa1,0x59,0xff,0xfe,0x57,0x77,0x79,0x20,0x8d, + 0x02,0x10,0x20,0x03,0x00,0xe0,0x37,0x0e,0x14,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x05,0x20,0x8d, + 0x02,0x10,0x20,0x03,0x00,0xf6,0x3f,0x10,0x67,0x00,0x4c,0x9f,0x76,0x20,0x83,0x24,0xd4,0xa7,0x20,0x8d, 0x02,0x10,0x24,0x00,0x24,0x10,0xce,0xa2,0x0d,0x00,0x41,0xbc,0xc9,0xea,0x86,0x1b,0x51,0xee,0x20,0x8d, + 0x02,0x10,0x24,0x00,0x24,0x11,0xa3,0xe1,0x49,0x00,0x25,0x68,0x68,0x4b,0x0e,0x99,0x71,0x20,0x20,0x8d, + 0x02,0x10,0x24,0x00,0x24,0x11,0xa3,0xe1,0x49,0x00,0x29,0x87,0xb8,0x8f,0x61,0xe0,0x84,0xfa,0x20,0x8d, 0x02,0x10,0x24,0x00,0x3b,0x00,0x00,0x20,0x00,0x0c,0xba,0xcb,0x29,0xff,0xfe,0xab,0x88,0x86,0x20,0x8d, + 0x02,0x10,0x24,0x01,0xb1,0x40,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x02,0x10,0x20,0x8d, + 0x02,0x10,0x24,0x01,0xb1,0x40,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x02,0x20,0x20,0x8d, + 0x02,0x10,0x24,0x01,0xb1,0x40,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x42,0x01,0x00,0x20,0x8d, + 0x02,0x10,0x24,0x01,0xb1,0x40,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x44,0x01,0x30,0x20,0x8d, 0x02,0x10,0x24,0x01,0xd0,0x02,0x39,0x02,0x07,0x00,0xd7,0x2c,0x5e,0x22,0x4e,0x95,0x38,0x9d,0x20,0x8d, - 0x02,0x10,0x24,0x03,0x62,0x00,0x88,0xa0,0xfb,0x17,0xf5,0xf2,0xd8,0xb5,0xb7,0xba,0xf4,0xd3,0x20,0x8d, - 0x02,0x10,0x24,0x05,0x98,0x00,0xb9,0x10,0x5f,0x8e,0x18,0x30,0xf6,0x30,0x2c,0xc6,0x88,0xfb,0x20,0x8d, - 0x02,0x10,0x24,0x05,0x98,0x00,0xb9,0x70,0xc6,0x4c,0x10,0x9f,0x74,0xe7,0xae,0x5f,0x87,0xc7,0x20,0x8d, - 0x02,0x10,0x24,0x05,0xaa,0x00,0x00,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x40,0x20,0x8d, - 0x02,0x10,0x24,0x07,0x88,0x00,0xbc,0x61,0x22,0x02,0xd6,0x3d,0x7e,0xff,0xfe,0x6c,0xdc,0x36,0x20,0x8d, - 0x02,0x10,0x24,0x08,0x82,0x48,0x70,0x04,0xf8,0x31,0x00,0x00,0x00,0x00,0x00,0x00,0x08,0x3c,0x20,0x8d, + 0x02,0x10,0x24,0x04,0x44,0x08,0x67,0x52,0xc0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x19,0x99,0x20,0x8d, + 0x02,0x10,0x24,0x04,0x7a,0x85,0x41,0x61,0x2b,0x00,0x49,0xa1,0x42,0x7a,0x0f,0xac,0x34,0x09,0x20,0x8d, + 0x02,0x10,0x24,0x05,0x98,0x00,0xb9,0x72,0xab,0x58,0x0c,0x05,0xe9,0x38,0x26,0x7e,0x02,0x71,0x20,0x8d, + 0x02,0x10,0x24,0x06,0xda,0x11,0x01,0x69,0x0b,0x03,0x32,0xb5,0xf9,0x01,0x9f,0x7c,0x3e,0x4b,0x20,0x8d, + 0x02,0x10,0x24,0x06,0xda,0x14,0x03,0x35,0xb6,0x01,0xce,0xb7,0xb4,0xfc,0xa8,0x55,0xf3,0xa5,0x20,0x8d, + 0x02,0x10,0x24,0x06,0xda,0x1e,0x0a,0x4e,0x8a,0x03,0x2a,0xad,0x49,0x6b,0x76,0x8d,0xe4,0x97,0x20,0x8d, + 0x02,0x10,0x24,0x07,0x88,0x00,0xbc,0x61,0x22,0x02,0xa0,0xc6,0x01,0x07,0x50,0x2b,0x4e,0x3b,0x20,0x8d, 0x02,0x10,0x24,0x09,0x00,0x10,0xca,0x20,0x1d,0xf0,0x02,0x24,0xe8,0xff,0xfe,0x1f,0x60,0xd9,0x20,0x8d, - 0x02,0x10,0x24,0x0b,0x00,0x11,0x43,0xa1,0xbd,0x00,0xe5,0x89,0xf8,0xa7,0x04,0x9b,0x3b,0x86,0x20,0x8d, - 0x02,0x10,0x24,0x0d,0x00,0x1a,0x07,0x91,0x34,0x00,0xd6,0x5d,0x64,0xff,0xfe,0x28,0x92,0x7e,0x20,0x8d, - 0x02,0x10,0x24,0x0d,0x00,0x1a,0x07,0x91,0x34,0x00,0xd6,0x81,0xd7,0xff,0xfe,0xf6,0xa2,0x1e,0x27,0x42, - 0x02,0x10,0x26,0x00,0x17,0x00,0x5b,0x2b,0x00,0x5f,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0x40,0x20,0x8d, + 0x02,0x10,0x26,0x00,0x17,0x00,0x22,0xf1,0x64,0x1f,0x00,0xe8,0x39,0xc8,0xeb,0x1d,0xa1,0xeb,0x20,0x8d, + 0x02,0x10,0x26,0x00,0x17,0x00,0x9c,0x5d,0x0e,0xd0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x38,0x20,0x8d, + 0x02,0x10,0x26,0x00,0x17,0x00,0x9c,0x5d,0x0e,0xd0,0xd0,0xd6,0x01,0xd9,0x5c,0xc2,0xab,0x47,0x20,0x8d, + 0x02,0x10,0x26,0x00,0x17,0x02,0x1c,0xe0,0x40,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x40,0x20,0x8d, + 0x02,0x10,0x26,0x00,0x1f,0x14,0x04,0x0e,0xe3,0x01,0xd1,0x55,0xaa,0x3a,0x77,0xbe,0x96,0x0e,0x20,0x8d, + 0x02,0x10,0x26,0x00,0x1f,0x16,0x0a,0x08,0xb9,0x01,0x1a,0xfa,0xef,0x4e,0x4c,0xe7,0x2b,0xa4,0x20,0x8d, + 0x02,0x10,0x26,0x00,0x1f,0x1c,0x02,0xd3,0x24,0x03,0x5b,0xac,0x3f,0xc6,0x65,0x13,0x7a,0x63,0x20,0x8d, 0x02,0x10,0x26,0x00,0x21,0x04,0x10,0x03,0xc5,0xab,0xdc,0x5e,0x90,0xff,0xfe,0x18,0x1d,0x08,0x20,0x8d, + 0x02,0x10,0x26,0x00,0x3c,0x00,0x00,0x00,0x00,0x00,0xf0,0x3c,0x92,0xff,0xfe,0x92,0x27,0x45,0x20,0x8d, + 0x02,0x10,0x26,0x00,0x3c,0x00,0x00,0x00,0x00,0x00,0xf0,0x3c,0x92,0xff,0xfe,0xcf,0x61,0xb6,0x20,0x8d, + 0x02,0x10,0x26,0x00,0x3c,0x00,0x00,0x00,0x00,0x00,0xf0,0x3c,0x93,0xff,0xfe,0xb3,0x01,0xb6,0x20,0x8d, 0x02,0x10,0x26,0x00,0x3c,0x00,0xe0,0x02,0x2e,0x32,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x14,0x20,0x8d, + 0x02,0x10,0x26,0x00,0x3c,0x02,0x00,0x00,0x00,0x00,0xf0,0x3c,0x92,0xff,0xfe,0x5d,0x09,0xfb,0x20,0x8d, + 0x02,0x10,0x26,0x00,0x40,0x40,0x28,0x54,0x5e,0x00,0xc6,0xe9,0x84,0xff,0xfe,0x46,0x0e,0xe8,0x21,0xda, + 0x02,0x10,0x26,0x00,0x6c,0x54,0x71,0x00,0x1a,0xd1,0xbd,0xdf,0x55,0x0e,0x91,0xbe,0xf9,0xe1,0x20,0x8d, 0x02,0x10,0x26,0x00,0x88,0x05,0x24,0x00,0x01,0x4e,0x12,0xdd,0xb1,0xff,0xfe,0xf2,0x30,0x13,0x20,0x8d, + 0x02,0x10,0x26,0x01,0x01,0x84,0x03,0x00,0x0b,0xde,0x3c,0x29,0x8e,0x94,0x1b,0xa8,0xfd,0xe3,0x20,0x8d, + 0x02,0x10,0x26,0x01,0x01,0x8c,0x80,0x80,0x30,0x0f,0x02,0x19,0xd1,0xff,0xfe,0x75,0xdc,0x2f,0x20,0x8d, + 0x02,0x10,0x26,0x01,0x01,0x8d,0x46,0x00,0x43,0xf1,0x20,0xe7,0xb3,0xff,0xfe,0xcf,0x0a,0x99,0x20,0x8d, + 0x02,0x10,0x26,0x01,0x01,0x8d,0x87,0x01,0xc2,0x90,0x00,0x00,0x00,0x00,0x00,0x00,0x33,0x30,0x20,0x8d, + 0x02,0x10,0x26,0x01,0x02,0x46,0x4d,0x7f,0x9e,0x28,0xf3,0x21,0x36,0xca,0x7a,0x71,0xc6,0x87,0x20,0x8d, + 0x02,0x10,0x26,0x01,0x06,0x40,0xc2,0x01,0x96,0x0d,0x86,0xeb,0xf2,0x7d,0x66,0xa2,0xf2,0xc1,0x20,0x8d, + 0x02,0x10,0x26,0x02,0x02,0x41,0x75,0xd1,0x2b,0x90,0x00,0x00,0x00,0x00,0x00,0x00,0x78,0x40,0x20,0x8d, 0x02,0x10,0x26,0x02,0xff,0xb8,0x00,0x00,0x00,0x00,0x02,0x08,0x00,0x72,0x00,0x57,0x02,0x00,0x20,0x8d, - 0x02,0x10,0x26,0x03,0x30,0x1f,0x1e,0xbf,0xe0,0x00,0xe2,0x3f,0x49,0xff,0xfe,0xe7,0x74,0x31,0x20,0x8d, - 0x02,0x10,0x26,0x03,0x60,0x81,0x18,0x00,0x66,0x00,0x16,0xdd,0xa9,0xff,0xfe,0xee,0xb2,0xf3,0x20,0x8d, - 0x02,0x10,0x26,0x04,0x13,0x80,0x10,0x00,0x74,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x20,0x8d, - 0x02,0x10,0x26,0x04,0x45,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x2e,0x06,0x1f,0xb0, - 0x02,0x10,0x26,0x04,0x55,0x00,0xc1,0x34,0x40,0x00,0x72,0x85,0xc2,0xff,0xfe,0x4a,0xe1,0x43,0x80,0x1d, - 0x02,0x10,0x26,0x04,0x55,0x00,0xc1,0x34,0x40,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0xfc,0x80,0x1d, + 0x02,0x10,0x26,0x03,0x30,0x04,0x06,0xa1,0x38,0x00,0x85,0x1f,0x58,0x4d,0x7a,0xba,0xaf,0xfb,0x20,0x8d, + 0x02,0x10,0x26,0x03,0x30,0x04,0x06,0xa1,0x38,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x44,0x02,0x20,0x8d, + 0x02,0x10,0x26,0x03,0x30,0x04,0x07,0x0d,0x14,0x00,0x85,0x32,0x29,0x00,0xce,0x6f,0xac,0xdf,0x20,0x8d, + 0x02,0x10,0x26,0x03,0x30,0x04,0x07,0x45,0x09,0x00,0xf0,0xd7,0x55,0x6a,0x0a,0x8c,0xce,0xd5,0x20,0x8d, + 0x02,0x10,0x26,0x03,0x60,0x80,0xc0,0x00,0x5d,0x8a,0x00,0x00,0x00,0x00,0x00,0x00,0x10,0x4f,0x20,0x8d, + 0x02,0x10,0x26,0x03,0x80,0x00,0xd1,0x00,0x89,0x91,0xcc,0x29,0xcc,0xff,0xfe,0x42,0x30,0x0c,0x20,0x8d, + 0x02,0x10,0x26,0x03,0x80,0x80,0x1f,0x07,0x6f,0xdd,0x7d,0xe2,0xd9,0x69,0x78,0xc9,0xb7,0xea,0x20,0x8d, + 0x02,0x10,0x26,0x03,0x80,0x80,0x73,0x00,0x05,0x31,0x00,0x00,0x00,0x00,0x00,0x00,0x13,0xea,0x20,0x8d, + 0x02,0x10,0x26,0x03,0x80,0xa0,0x07,0x03,0x40,0xf8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x38,0x20,0x8d, + 0x02,0x10,0x26,0x04,0x01,0x80,0x00,0xf3,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x18,0x20,0x8d, + 0x02,0x10,0x26,0x04,0x3d,0x08,0x00,0x00,0x00,0x05,0xd9,0x41,0x4b,0x03,0xa0,0x93,0x13,0x1b,0x20,0x8d, 0x02,0x10,0x26,0x04,0x7c,0x00,0x01,0x20,0x00,0x4b,0x00,0x00,0x00,0x00,0x00,0x00,0xeb,0x24,0x20,0x8d, + 0x02,0x10,0x26,0x04,0x0a,0x00,0x00,0x21,0x30,0x43,0xbf,0x6a,0x53,0x5e,0xdf,0xeb,0x5b,0x7b,0x20,0x8d, + 0x02,0x10,0x26,0x04,0xa8,0x80,0x04,0x00,0x00,0xd0,0x00,0x00,0x00,0x00,0x1c,0xe7,0x40,0x01,0x20,0x8d, + 0x02,0x10,0x26,0x04,0xa8,0x80,0x04,0x00,0x00,0xd0,0x00,0x00,0x00,0x00,0x1d,0x44,0xe0,0x01,0x20,0x8d, + 0x02,0x10,0x26,0x04,0xa8,0x80,0x04,0x00,0x00,0xd0,0x00,0x00,0x00,0x00,0x26,0x1f,0x60,0x01,0x20,0x8d, + 0x02,0x10,0x26,0x04,0xa8,0x80,0x04,0x00,0x00,0xd1,0x00,0x00,0x00,0x00,0x07,0xe2,0xe0,0x01,0x20,0x8d, + 0x02,0x10,0x26,0x04,0xa8,0x80,0x00,0x04,0x01,0xd0,0x00,0x00,0x00,0x00,0x00,0x14,0x30,0x00,0x20,0x8d, + 0x02,0x10,0x26,0x04,0xa8,0x80,0x00,0x04,0x01,0xd0,0x00,0x00,0x00,0x00,0x00,0xe5,0xb0,0x00,0x20,0x8d, 0x02,0x10,0x26,0x05,0x64,0x00,0x00,0x30,0xf2,0x20,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x20,0x8d, 0x02,0x10,0x26,0x05,0x6f,0x80,0x00,0x00,0x00,0x07,0xfc,0x1b,0xcc,0xff,0xfe,0x8a,0xd8,0x22,0x20,0x8d, + 0x02,0x10,0x26,0x05,0xa1,0x40,0x20,0x76,0x82,0x53,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x20,0x8d, + 0x02,0x10,0x26,0x05,0xa1,0x40,0x30,0x07,0x12,0x87,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x20,0x8d, 0x02,0x10,0x26,0x05,0xae,0x00,0x02,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x03,0x20,0x8d, 0x02,0x10,0x26,0x05,0xc0,0x00,0x2a,0x0a,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x02,0x20,0x8d, - 0x02,0x10,0x26,0x05,0xf7,0x00,0x00,0xc0,0x08,0x27,0x02,0x25,0x90,0xff,0xfe,0xe3,0x34,0xa6,0x20,0x8d, + 0x02,0x10,0x26,0x07,0x1a,0x00,0x00,0x01,0x00,0x0d,0x00,0x00,0x00,0x00,0x00,0x11,0x7c,0x4d,0x20,0x8d, + 0x02,0x10,0x26,0x07,0x53,0x00,0x02,0x03,0x12,0x14,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x20,0x8d, 0x02,0x10,0x26,0x07,0x92,0x80,0x00,0x0b,0x07,0x3b,0x02,0x50,0x56,0xff,0xfe,0x14,0x25,0xb5,0x20,0x8d, - 0x02,0x10,0x26,0x07,0xf2,0xf8,0xad,0x40,0x0b,0xc1,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x20,0x8d, - 0x02,0x10,0x26,0x07,0xfa,0x18,0x3a,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x20,0x20,0x8d, - 0x02,0x10,0x26,0x20,0x01,0x1c,0x50,0x01,0x11,0x18,0xd2,0x67,0xe5,0xff,0xfe,0xe9,0xe6,0x73,0x20,0x8d, - 0x02,0x10,0x26,0x20,0x01,0x1c,0x50,0x01,0x21,0x99,0xd2,0x67,0xe5,0xff,0xfe,0xe9,0xe6,0x73,0x20,0x8d, + 0x02,0x10,0x26,0x07,0x92,0x80,0x00,0x0b,0x07,0x3b,0x02,0x50,0x56,0xff,0xfe,0x21,0x9c,0x2f,0x20,0x8d, + 0x02,0x10,0x26,0x07,0x92,0x80,0x00,0x0b,0x07,0x3b,0x02,0x50,0x56,0xff,0xfe,0x21,0xbf,0x32,0x20,0x8d, + 0x02,0x10,0x26,0x07,0x92,0x80,0x00,0x0b,0x07,0x3b,0x02,0x50,0x56,0xff,0xfe,0x33,0x4d,0x1b,0x20,0x8d, + 0x02,0x10,0x26,0x07,0x92,0x80,0x00,0x0b,0x07,0x3b,0x02,0x50,0x56,0xff,0xfe,0x3d,0x04,0x01,0x20,0x8d, + 0x02,0x10,0x26,0x07,0xf2,0xc0,0xe1,0xc2,0x00,0x69,0x12,0xc3,0x7b,0xff,0xfe,0x4d,0x94,0x31,0x20,0x8d, + 0x02,0x10,0x26,0x07,0xf2,0xc0,0xe1,0xc2,0x00,0x69,0xec,0xb2,0x6e,0x88,0x9f,0x33,0x50,0x57,0x20,0x8d, 0x02,0x10,0x26,0x20,0x00,0x06,0x20,0x03,0x01,0x05,0x02,0xd8,0x61,0xff,0xfe,0x0f,0x08,0x53,0x20,0x8d, 0x02,0x10,0x26,0x20,0x00,0x6e,0xa0,0x00,0x00,0x01,0x00,0x42,0x00,0x42,0x00,0x42,0x00,0x42,0x20,0x8d, - 0x02,0x10,0x28,0x03,0xcf,0x00,0x0a,0xf8,0xf2,0x00,0xb8,0x9e,0xcf,0x34,0x92,0xc7,0x2d,0x26,0x20,0x8d, - 0x02,0x10,0x28,0x04,0x01,0x4c,0x65,0xd1,0x40,0x2c,0xbc,0x53,0xbf,0x5d,0x06,0x8a,0x21,0x36,0x20,0x8d, - 0x02,0x10,0x28,0x04,0x07,0xf1,0xe7,0x83,0xd4,0x01,0x66,0x1c,0x67,0xff,0xfe,0xba,0x55,0x47,0x20,0x8d, - 0x02,0x10,0x28,0x04,0x0d,0x57,0x55,0x37,0x48,0x00,0x02,0x1e,0x67,0xff,0xfe,0xa8,0xd7,0x98,0x20,0x8d, - 0x02,0x10,0x28,0x04,0x0d,0x57,0x55,0x37,0x48,0x00,0x36,0x15,0x9e,0xff,0xfe,0x23,0xd6,0x10,0x20,0x8d, - 0x02,0x10,0x28,0x06,0x02,0xf0,0x20,0x80,0x06,0x2a,0x08,0x6f,0x1a,0x01,0xc4,0x4f,0x17,0x94,0x20,0x8d, - 0x02,0x10,0x2a,0x00,0x10,0x28,0x83,0x82,0xbf,0x22,0x5f,0x7f,0xb7,0x8f,0x27,0x37,0x77,0x39,0x20,0x8d, + 0x02,0x10,0x26,0x20,0x00,0xa6,0x20,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x03,0xd5,0x70,0x20,0x8d, + 0x02,0x10,0x26,0x20,0x00,0xa6,0x20,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x05,0x16,0x2a,0x20,0x8d, + 0x02,0x10,0x26,0x20,0x00,0xa6,0x20,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x05,0x16,0x31,0x20,0x8d, + 0x02,0x10,0x26,0x20,0x00,0xa6,0x20,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x0c,0xe6,0x34,0x20,0x8d, + 0x02,0x10,0x28,0x00,0x00,0x40,0x00,0x33,0x08,0xab,0xa0,0xe7,0xb2,0x15,0xfc,0x83,0x5c,0x31,0x20,0x8d, + 0x02,0x10,0x28,0x00,0x0b,0xf0,0x01,0x49,0x0f,0x4b,0xf8,0xdf,0x8d,0x7d,0x80,0x1b,0xe2,0x5e,0x20,0x8d, + 0x02,0x10,0x28,0x04,0x01,0x4c,0x01,0x98,0x80,0xd5,0x76,0x03,0x41,0xd1,0xd3,0xfc,0xe7,0x97,0x20,0x8d, + 0x02,0x10,0x28,0x04,0x01,0x4d,0xae,0x81,0x82,0x7b,0x99,0xa8,0x1e,0x3f,0x6d,0xb2,0x29,0xdb,0x20,0x8d, + 0x02,0x10,0x28,0x04,0x0d,0x57,0x55,0x37,0x48,0x00,0x3e,0x7c,0x3f,0xff,0xfe,0x7b,0x80,0xaa,0x20,0x8d, 0x02,0x10,0x2a,0x00,0x12,0xe0,0x01,0x01,0x00,0x99,0x02,0x0c,0x29,0xff,0xfe,0x29,0xd0,0x3f,0x20,0x8d, 0x02,0x10,0x2a,0x00,0x13,0x28,0xe1,0x01,0x0c,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x63,0x20,0x8d, + 0x02,0x10,0x2a,0x00,0x13,0x98,0x00,0x04,0x2a,0x03,0x02,0x15,0x5d,0xff,0xfe,0xd6,0x10,0x33,0x20,0x8d, + 0x02,0x10,0x2a,0x00,0x13,0x98,0x00,0x04,0x2a,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0xbc,0x03,0x20,0x8d, 0x02,0x10,0x2a,0x00,0x16,0x30,0x00,0x10,0x10,0x03,0x00,0x00,0x0b,0x19,0xb0,0x0b,0xba,0xbe,0x20,0x8d, 0x02,0x10,0x2a,0x00,0x17,0x68,0x20,0x01,0x00,0x27,0x00,0x00,0x00,0x00,0x00,0x00,0xef,0x6a,0x20,0x8d, 0x02,0x10,0x2a,0x00,0x18,0x28,0xa0,0x04,0x00,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x06,0x66,0x20,0x8d, - 0x02,0x10,0x2a,0x00,0x18,0x38,0x00,0x2a,0x14,0x00,0x92,0xe2,0xba,0xff,0xfe,0x4a,0xc4,0x16,0x20,0x8d, 0x02,0x10,0x2a,0x00,0x1c,0x10,0x00,0x02,0x07,0x09,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x17,0x56,0xcc, 0x02,0x10,0x2a,0x00,0x1f,0x40,0x50,0x01,0x01,0x08,0x5d,0x17,0x77,0x03,0xb0,0xf5,0x41,0x33,0x20,0x8d, - 0x02,0x10,0x2a,0x00,0x60,0x20,0x15,0xdd,0xee,0x00,0xc8,0xc2,0x2c,0x77,0x17,0x49,0x35,0xdb,0x20,0x8d, + 0x02,0x10,0x2a,0x00,0x23,0xc5,0xfe,0x80,0x73,0x01,0xd6,0xae,0x52,0xff,0xfe,0xd5,0x56,0xa5,0x20,0x8d, + 0x02,0x10,0x2a,0x00,0x23,0xc6,0x5c,0x91,0x58,0x08,0xc0,0x5a,0x4d,0xff,0xfe,0x65,0x9d,0x69,0x20,0x8d, + 0x02,0x10,0x2a,0x00,0x60,0x20,0x1b,0xfa,0xd4,0x00,0x02,0x0c,0x29,0xff,0xfe,0x61,0x4a,0x4c,0x20,0x8d, 0x02,0x10,0x2a,0x00,0x60,0x20,0xb4,0x82,0x92,0x00,0x49,0x1a,0x35,0x8c,0xd8,0xf7,0x01,0xda,0x20,0x8d, - 0x02,0x10,0x2a,0x00,0x71,0x45,0x00,0xc1,0x00,0x01,0xae,0x29,0x07,0x27,0x2b,0x87,0x0f,0x64,0x14,0x15, + 0x02,0x10,0x2a,0x00,0x60,0x20,0xb4,0x89,0x20,0x00,0x50,0x54,0x00,0xff,0xfe,0xfc,0x5e,0xd8,0x20,0x8d, + 0x02,0x10,0x2a,0x00,0x7c,0x80,0x00,0x00,0x00,0x25,0x00,0x00,0x00,0x00,0x00,0x00,0xe3,0x7a,0x20,0x8d, + 0x02,0x10,0x2a,0x00,0x7c,0x80,0x00,0x00,0x00,0x71,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x08,0x20,0x8d, 0x02,0x10,0x2a,0x00,0x8a,0x60,0xe0,0x12,0x0a,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x21,0x20,0x8d, - 0x02,0x10,0x2a,0x00,0xa0,0x40,0x01,0x00,0x00,0xf3,0x45,0xa5,0x0a,0xc0,0xfe,0xa3,0x71,0xe1,0x20,0x8d, - 0x02,0x10,0x2a,0x01,0x04,0x88,0x20,0x00,0x98,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0d,0x20,0x8d, + 0x02,0x10,0x2a,0x00,0xae,0x40,0x24,0x0e,0x32,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x20,0x8d, + 0x02,0x10,0x2a,0x00,0xbb,0xe0,0x00,0xcc,0x00,0x00,0x62,0xa4,0x4c,0xff,0xfe,0x23,0x75,0x10,0x20,0x8d, + 0x02,0x10,0x2a,0x00,0x0c,0xa8,0x0a,0x1f,0x30,0x25,0xf9,0x49,0xe4,0x42,0xc9,0x40,0x13,0xe8,0x20,0x8d, + 0x02,0x10,0x2a,0x00,0xd4,0xe0,0x00,0x02,0xd0,0x02,0x44,0x67,0x31,0xe0,0x6f,0xa5,0xb3,0xef,0x20,0x8d, + 0x02,0x10,0x2a,0x00,0x0e,0xe2,0x12,0x00,0x19,0x00,0x08,0xd3,0xd2,0xff,0xfe,0xb1,0xbc,0x58,0x20,0x8d, + 0x02,0x10,0x2a,0x01,0x02,0x38,0x42,0x0f,0x92,0x00,0xfa,0x5a,0x1a,0x4b,0x1e,0x6a,0xfa,0xdf,0x20,0x8d, + 0x02,0x10,0x2a,0x01,0x02,0x38,0x43,0x89,0xc4,0x00,0x3b,0x26,0xd9,0x4e,0x38,0xd5,0x44,0xef,0x20,0x8d, 0x02,0x10,0x2a,0x01,0x04,0x90,0x00,0x16,0x03,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x20,0x8d, + 0x02,0x10,0x2a,0x01,0x4b,0x00,0x80,0x7c,0x31,0x00,0xcd,0xa1,0x0c,0x6a,0x2b,0xad,0x24,0x18,0x20,0x8d, + 0x02,0x10,0x2a,0x01,0x04,0xf8,0x01,0x41,0x22,0x54,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x20,0x8d, + 0x02,0x10,0x2a,0x01,0x04,0xf8,0x01,0x73,0x23,0x0a,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x20,0x8d, + 0x02,0x10,0x2a,0x01,0x04,0xf8,0x01,0x90,0x91,0xc4,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x20,0x8d, + 0x02,0x10,0x2a,0x01,0x04,0xf8,0x02,0x00,0x72,0x22,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x20,0x8d, + 0x02,0x10,0x2a,0x01,0x04,0xf8,0x02,0x02,0x03,0xe6,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x20,0x8d, + 0x02,0x10,0x2a,0x01,0x04,0xf8,0x02,0x21,0x44,0xd7,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x20,0x8d, + 0x02,0x10,0x2a,0x01,0x04,0xf8,0x02,0x31,0x09,0x15,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x20,0x8d, + 0x02,0x10,0x2a,0x01,0x04,0xf9,0x00,0x2a,0x1c,0xe0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x20,0x8d, + 0x02,0x10,0x2a,0x01,0x04,0xf9,0x00,0x2b,0x02,0x9a,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x20,0x8d, + 0x02,0x10,0x2a,0x01,0x04,0xf9,0x00,0x4a,0x31,0xde,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x20,0x8d, 0x02,0x10,0x2a,0x01,0x52,0x00,0x00,0x6c,0x61,0x62,0x7a,0x61,0x74,0x6b,0x6f,0x2e,0x73,0x6b,0x20,0x8d, - 0x02,0x10,0x2a,0x01,0x63,0x80,0xff,0xfe,0x00,0x73,0x04,0xe3,0xb3,0xcc,0xa8,0x71,0x36,0xd1,0x20,0x8d, - 0x02,0x10,0x2a,0x01,0x07,0xa0,0x00,0x02,0x13,0x7c,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x20,0x8d, + 0x02,0x10,0x2a,0x01,0x63,0x80,0xff,0xfe,0x00,0x73,0x10,0xfb,0xd0,0x12,0x85,0x81,0xb4,0xd7,0x20,0x8d, + 0x02,0x10,0x2a,0x01,0x07,0xa7,0x00,0x02,0x28,0x04,0xae,0x1f,0x6b,0xff,0xfe,0x9d,0x6c,0x94,0x20,0x8d, + 0x02,0x10,0x2a,0x01,0x07,0xc8,0xaa,0xac,0x00,0x89,0x50,0x54,0x00,0xff,0xfe,0xb7,0xf5,0xcb,0x20,0x8d, 0x02,0x10,0x2a,0x01,0x07,0xc8,0xaa,0xc9,0x00,0xc9,0x50,0x54,0x00,0xff,0xfe,0xdf,0xff,0x95,0x20,0x8d, 0x02,0x10,0x2a,0x01,0x07,0xc8,0xd0,0x01,0x01,0xc1,0x50,0x54,0x00,0xff,0xfe,0xee,0x3e,0x1a,0x20,0x8d, + 0x02,0x10,0x2a,0x01,0x07,0xc8,0xd0,0x09,0x02,0xaa,0x50,0x54,0x00,0xff,0xfe,0x1b,0xa1,0x96,0x2d,0x00, + 0x02,0x10,0x2a,0x01,0x07,0xc8,0xff,0xfa,0x05,0x0e,0xdd,0xfe,0xc9,0x24,0xca,0x0a,0xcb,0xab,0x20,0x8d, + 0x02,0x10,0x2a,0x01,0x7e,0x00,0x00,0x00,0x00,0x00,0xf0,0x3c,0x93,0xff,0xfe,0x59,0x66,0xdc,0x20,0x8d, + 0x02,0x10,0x2a,0x01,0x7e,0x01,0x00,0x00,0x00,0x00,0xf0,0x3c,0x93,0xff,0xfe,0x3b,0xbb,0x5b,0x20,0x8d, 0x02,0x10,0x2a,0x01,0x87,0x40,0x00,0x01,0xff,0xc5,0x00,0x00,0x00,0x00,0x00,0x00,0x8c,0x6a,0x20,0x8d, + 0x02,0x10,0x2a,0x01,0x9f,0x40,0xa0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x20,0x8d, 0x02,0x10,0x2a,0x01,0xcb,0x00,0x0d,0x3d,0x77,0x00,0x02,0x27,0x0e,0xff,0xfe,0x28,0xc5,0x65,0x20,0x8d, - 0x02,0x10,0x2a,0x01,0x00,0xd0,0x00,0x00,0x00,0x1c,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x53,0x20,0x8d, - 0x02,0x10,0x2a,0x01,0x00,0xd0,0xbe,0xf2,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x12,0x20,0x8d, - 0x02,0x10,0x2a,0x01,0x0e,0x0a,0x09,0xfb,0xb0,0xe0,0x54,0xf8,0x19,0x01,0x6e,0x83,0x62,0xc1,0x20,0x8d, + 0x02,0x10,0x2a,0x01,0x0e,0x0a,0x00,0x20,0x73,0x50,0x91,0x9c,0xb1,0xc3,0x8b,0x83,0xad,0xf9,0x20,0x8d, + 0x02,0x10,0x2a,0x01,0x0e,0x0a,0x03,0x01,0x70,0x10,0xb8,0x7d,0xe1,0x4b,0xce,0xa9,0xb9,0x98,0x20,0x8d, + 0x02,0x10,0x2a,0x01,0x0e,0x0a,0x04,0x8b,0x2d,0x10,0x94,0xf2,0x4d,0x5c,0xca,0x5f,0xbf,0x49,0x20,0x8d, + 0x02,0x10,0x2a,0x01,0x0e,0x0a,0x05,0x30,0xa0,0xa0,0xf4,0x65,0x0a,0xf5,0xbe,0x1b,0x90,0x75,0x20,0x8d, 0x02,0x10,0x2a,0x01,0x0e,0x0a,0x0a,0xa7,0xc8,0xc0,0x96,0x79,0xaf,0xfa,0xb6,0xe5,0xef,0xc7,0x20,0x8d, - 0x02,0x10,0x2a,0x02,0x13,0xb8,0xf0,0x00,0x01,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0a,0x20,0x8d, - 0x02,0x10,0x2a,0x02,0x01,0x68,0x63,0x28,0x00,0x00,0x02,0xa8,0x2c,0xff,0xfe,0x68,0xe3,0x2c,0x20,0x8d, + 0x02,0x10,0x2a,0x01,0x0e,0x11,0x10,0x0c,0x00,0x70,0xcb,0xc8,0x9e,0x31,0x4b,0x77,0x16,0x26,0x20,0x8d, + 0x02,0x10,0x2a,0x01,0x0e,0x34,0xee,0x78,0x30,0x60,0x02,0x30,0x48,0xff,0xfe,0x81,0xf1,0xc6,0x20,0x8d, + 0x02,0x10,0x2a,0x02,0x12,0x10,0x14,0xa9,0x67,0x00,0x0a,0x00,0x27,0xff,0xfe,0x4e,0x82,0xb6,0x20,0x8d, + 0x02,0x10,0x2a,0x02,0x12,0x10,0x46,0x39,0x0f,0x00,0x10,0xa7,0xe9,0x65,0x50,0x9a,0x7a,0x4a,0x20,0x8d, + 0x02,0x10,0x2a,0x02,0x12,0x10,0x7c,0x92,0x51,0x00,0x02,0x11,0x32,0xff,0xfe,0xae,0x15,0x2d,0x20,0x8d, + 0x02,0x10,0x2a,0x02,0x12,0x10,0x86,0xbf,0xf1,0x00,0x31,0x78,0xd7,0x00,0xd4,0x4d,0x6b,0xb1,0x20,0x8d, + 0x02,0x10,0x2a,0x02,0x12,0x10,0x94,0x87,0xa2,0x00,0xed,0xc1,0x93,0xa4,0x09,0x45,0x9a,0x92,0x20,0x8d, + 0x02,0x10,0x2a,0x02,0x01,0x68,0x42,0x0b,0x00,0x0a,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x20,0x20,0x8d, + 0x02,0x10,0x2a,0x02,0x01,0x68,0x63,0x28,0x00,0x00,0x4a,0x21,0x0b,0xff,0xfe,0x26,0x38,0xc3,0x20,0x8d, + 0x02,0x10,0x2a,0x02,0x01,0x68,0x67,0x6e,0x00,0x00,0xe6,0x5f,0x01,0xff,0xfe,0x09,0x35,0x91,0x20,0x8d, + 0x02,0x10,0x2a,0x02,0x17,0x48,0xf3,0x9f,0x58,0x72,0xde,0xad,0xbe,0xef,0xb1,0xac,0xc0,0xfe,0x20,0x8d, + 0x02,0x10,0x2a,0x02,0x01,0x80,0x00,0x01,0x00,0x01,0x00,0x00,0x00,0x00,0x05,0x17,0x10,0xb6,0x20,0x8d, + 0x02,0x10,0x2a,0x02,0x21,0x68,0xa3,0x79,0xd1,0x00,0x96,0xde,0x80,0xff,0xfe,0xa3,0xfd,0x00,0x20,0x8d, 0x02,0x10,0x2a,0x02,0x27,0x80,0x90,0x00,0x00,0x70,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x07,0x20,0x8d, + 0x02,0x10,0x2a,0x02,0x27,0x80,0x90,0x00,0x00,0x70,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0f,0x20,0x8d, + 0x02,0x10,0x2a,0x02,0x27,0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xe0,0x1a,0x20,0x8d, 0x02,0x10,0x2a,0x02,0x2e,0x02,0x39,0x00,0x54,0x00,0xa0,0x99,0xe1,0xff,0xfe,0xb6,0x0d,0x0e,0x20,0x8d, - 0x02,0x10,0x2a,0x02,0x03,0x90,0x90,0x00,0x00,0x00,0xaa,0xa1,0x59,0xff,0xfe,0x43,0xb5,0x7b,0x20,0x8d, + 0x02,0x10,0x2a,0x02,0x2f,0x05,0x66,0x0e,0x8b,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x20,0x8d, 0x02,0x10,0x2a,0x02,0x00,0x58,0x00,0x97,0x7d,0x20,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x60,0x20,0x8d, - 0x02,0x10,0x2a,0x02,0x6d,0x40,0x30,0x5e,0x06,0x01,0xde,0xa6,0x32,0xff,0xfe,0x44,0x4b,0x25,0x20,0x8d, + 0x02,0x10,0x2a,0x02,0x6d,0x40,0x30,0x73,0x0c,0x01,0xde,0xa6,0x32,0xff,0xfe,0x44,0x4b,0x25,0x20,0x8d, 0x02,0x10,0x2a,0x02,0x7a,0x01,0x00,0x00,0x00,0x00,0x00,0x91,0x02,0x28,0x00,0x45,0x01,0x30,0x20,0x8d, - 0x02,0x10,0x2a,0x02,0x7a,0xa0,0x16,0x19,0x00,0x00,0x00,0x00,0x00,0x00,0x0a,0xdc,0x8d,0xe0,0x20,0x8d, - 0x02,0x10,0x2a,0x02,0x7b,0x40,0x3e,0x4d,0x99,0x8d,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x20,0x8d, - 0x02,0x10,0x2a,0x02,0x7b,0x40,0x59,0x2f,0xa1,0x87,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x20,0x8d, - 0x02,0x10,0x2a,0x02,0x83,0x88,0xe5,0xc6,0xd3,0x80,0x02,0x01,0x2e,0xff,0xfe,0x82,0xb3,0xcc,0x20,0x8d, - 0x02,0x10,0x2a,0x02,0x09,0xa0,0x01,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x10,0x20,0x8d, - 0x02,0x10,0x2a,0x02,0xa3,0x11,0x81,0x43,0x8c,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x04,0x20,0xa1, - 0x02,0x10,0x2a,0x02,0x0a,0xf8,0xfa,0xb0,0x08,0x08,0x00,0x85,0x02,0x34,0x01,0x45,0x01,0x32,0x20,0x8d, - 0x02,0x10,0x2a,0x02,0x0e,0x00,0xff,0xf0,0x05,0x06,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x20,0xfc, - 0x02,0x10,0x2a,0x02,0x0e,0x00,0xff,0xf0,0x05,0x06,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0a,0x20,0xfc, + 0x02,0x10,0x2a,0x02,0x7b,0x40,0x59,0x28,0x00,0x89,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x20,0x8d, + 0x02,0x10,0x2a,0x02,0x7b,0x40,0xc3,0xb5,0xf5,0x83,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x20,0x8d, + 0x02,0x10,0x2a,0x02,0x83,0x08,0x80,0x87,0xaa,0x00,0x9e,0xa8,0x01,0xb2,0xef,0x98,0x56,0xbf,0x20,0x8d, + 0x02,0x10,0x2a,0x02,0x84,0x2a,0x01,0xdf,0x8a,0x01,0x1e,0x1b,0x0d,0xff,0xfe,0x0b,0x23,0x6d,0x20,0x8d, + 0x02,0x10,0x2a,0x02,0xa4,0x4d,0x14,0xd6,0x00,0x01,0x02,0xc0,0x08,0xff,0xfe,0x8f,0xb3,0xb2,0x20,0x8d, + 0x02,0x10,0x2a,0x02,0xa4,0x5a,0x94,0xcd,0xf0,0x0d,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x20,0x8d, + 0x02,0x10,0x2a,0x02,0xa4,0x5f,0x3b,0x9d,0x00,0x30,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x20,0x8d, + 0x02,0x10,0x2a,0x02,0xa4,0x67,0x78,0x33,0x00,0x01,0x72,0x85,0xc2,0xff,0xfe,0x2c,0x21,0xe9,0x20,0x8d, + 0x02,0x10,0x2a,0x02,0xaa,0x14,0x23,0x80,0xb3,0x00,0x40,0x40,0xbe,0x88,0x8b,0x01,0x0d,0x38,0x20,0x8d, + 0x02,0x10,0x2a,0x02,0xc2,0x06,0x20,0x44,0x98,0x26,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x20,0x8d, + 0x02,0x10,0x2a,0x02,0xc2,0x06,0x20,0x82,0x12,0x46,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x20,0x8d, + 0x02,0x10,0x2a,0x02,0xc2,0x06,0x30,0x08,0x23,0x68,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x20,0x8d, + 0x02,0x10,0x2a,0x02,0xc2,0x07,0x00,0x00,0x49,0x71,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x14,0xd4, + 0x02,0x10,0x2a,0x02,0xc2,0x07,0x20,0x14,0x41,0x99,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x20,0x8d, + 0x02,0x10,0x2a,0x02,0xc2,0x07,0x20,0x24,0x61,0x15,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x20,0x8d, + 0x02,0x10,0x2a,0x02,0xc2,0x07,0x20,0x26,0x66,0x82,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x20,0x8d, + 0x02,0x10,0x2a,0x02,0xc2,0x07,0x30,0x02,0x74,0x68,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x20,0x8d, 0x02,0x10,0x2a,0x02,0x0e,0x98,0x00,0x20,0x15,0x04,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x20,0x8d, - 0x02,0x10,0x2a,0x03,0x40,0x00,0x00,0x47,0x00,0xf1,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x20,0x8d, + 0x02,0x10,0x2a,0x03,0x40,0x00,0x00,0x06,0x41,0x6c,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x43,0x20,0x8d, + 0x02,0x10,0x2a,0x03,0x40,0x00,0x00,0x06,0xf8,0x14,0x54,0x8b,0x17,0xff,0xfe,0x31,0xb6,0x4a,0x20,0x8d, 0x02,0x10,0x2a,0x03,0x60,0x00,0x08,0x70,0x00,0x00,0x00,0x46,0x00,0x23,0x00,0x87,0x02,0x18,0x20,0x8d, - 0x02,0x10,0x2a,0x03,0x73,0x80,0x30,0x15,0x05,0x24,0xaf,0xc5,0xd3,0xbc,0x7c,0x66,0x8f,0x94,0x20,0x8d, - 0x02,0x10,0x2a,0x03,0x0e,0xc0,0x00,0x00,0x09,0x28,0x8c,0x00,0x93,0xff,0xfe,0x84,0xa0,0x07,0x20,0x8d, - 0x02,0x10,0x2a,0x03,0x0e,0xc0,0x00,0x00,0x09,0x28,0x00,0x00,0x00,0x00,0x00,0x00,0x07,0x01,0x20,0x8d, - 0x02,0x10,0x2a,0x04,0x21,0x80,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xaa,0x20,0x8d, - 0x02,0x10,0x2a,0x04,0x52,0xc0,0x01,0x01,0x02,0x9e,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x20,0x8d, - 0x02,0x10,0x2a,0x04,0x52,0xc0,0x01,0x03,0xc4,0x55,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x20,0x8d, + 0x02,0x10,0x2a,0x03,0x94,0xe0,0xff,0xff,0x01,0x85,0x02,0x43,0x02,0x18,0x00,0x00,0x00,0x19,0x20,0x8d, + 0x02,0x10,0x2a,0x03,0xb0,0xc0,0x00,0x01,0x00,0xe0,0x00,0x00,0x00,0x00,0x03,0x97,0x60,0x01,0x20,0x8d, + 0x02,0x10,0x2a,0x03,0xb0,0xc0,0x00,0x02,0x00,0xf0,0x00,0x00,0x00,0x00,0x01,0x63,0x30,0x01,0x20,0x8d, + 0x02,0x10,0x2a,0x03,0xb0,0xc0,0x00,0x02,0x00,0xf0,0x00,0x00,0x00,0x00,0x01,0x8a,0xd0,0x01,0x20,0x8d, + 0x02,0x10,0x2a,0x03,0xb0,0xc0,0x00,0x03,0x00,0xd0,0x00,0x00,0x00,0x00,0x0f,0x3e,0x20,0x01,0x20,0x8d, + 0x02,0x10,0x2a,0x03,0xe2,0xc0,0x13,0x47,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x20,0x8d, + 0x02,0x10,0x2a,0x03,0x0e,0xc0,0x00,0x00,0x09,0x28,0x00,0x00,0x00,0x00,0x07,0x01,0x07,0x01,0x20,0x8d, + 0x02,0x10,0x2a,0x04,0x52,0xc0,0x01,0x03,0xc4,0x55,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x20,0x8e, + 0x02,0x10,0x2a,0x04,0x52,0xc0,0x30,0x07,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x20,0x00,0x20,0x8d, 0x02,0x10,0x2a,0x04,0xbc,0x40,0x1d,0xc3,0x00,0x8d,0x00,0x00,0x00,0x00,0x00,0x02,0x10,0x01,0x20,0x8d, 0x02,0x10,0x2a,0x05,0x15,0x00,0x07,0x02,0x00,0x00,0x1c,0x00,0x40,0xff,0xfe,0x00,0x00,0x0c,0x20,0x8d, - 0x02,0x10,0x2a,0x06,0xdd,0x00,0x00,0x10,0x00,0x03,0x02,0x25,0x90,0xff,0xfe,0x32,0x64,0xcc,0x20,0x8d, - 0x02,0x10,0x2a,0x06,0xdd,0x00,0x00,0x01,0x00,0x22,0x02,0x25,0x90,0xff,0xfe,0x0e,0xbd,0x48,0x20,0x8d, - 0x02,0x10,0x2a,0x07,0x6b,0x47,0x01,0x00,0x04,0x64,0x00,0x00,0x00,0x00,0x93,0x57,0xff,0xda,0x20,0x8d, - 0x02,0x10,0x2a,0x07,0xa8,0x80,0x46,0x01,0x10,0x62,0xb4,0xb4,0xbd,0x2a,0x39,0xd4,0x7a,0xcf,0xc8,0xc9, + 0x02,0x10,0x2a,0x05,0x35,0x80,0xd1,0x01,0x37,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x20,0x8d, + 0x02,0x10,0x2a,0x05,0x35,0x80,0xdb,0x0b,0x16,0x00,0xc4,0x89,0x76,0xed,0x31,0x3d,0x0b,0x33,0x20,0x8d, + 0x02,0x10,0x2a,0x05,0xd0,0x14,0x0a,0x55,0x40,0x01,0x81,0x27,0xaf,0xa7,0xda,0xf9,0xd9,0x1b,0x20,0x8d, + 0x02,0x10,0x2a,0x05,0xd0,0x14,0x0a,0x55,0x40,0x01,0xf6,0xab,0xdd,0x5e,0x40,0x39,0xb4,0x6c,0x20,0x8d, + 0x02,0x10,0x2a,0x05,0xd0,0x14,0x0a,0x55,0x40,0x03,0x65,0x23,0x50,0xa1,0x01,0x52,0xe8,0x8c,0x20,0x8d, + 0x02,0x10,0x2a,0x05,0xd0,0x1a,0x0b,0x7b,0x3c,0x01,0x8b,0xf7,0xae,0x14,0xaf,0xb3,0x33,0xae,0x20,0x8d, + 0x02,0x10,0x2a,0x05,0xf4,0x80,0x18,0x00,0x06,0x97,0x54,0x00,0x02,0xff,0xfe,0xb6,0xc3,0x6d,0x20,0x8d, + 0x02,0x10,0x2a,0x06,0xe0,0x40,0x76,0x03,0x29,0x18,0xc6,0xef,0x46,0x4e,0x9f,0xe5,0x73,0xec,0x20,0x8d, 0x02,0x10,0x2a,0x07,0xab,0xc4,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x09,0x46,0x20,0x8d, - 0x02,0x10,0x2a,0x07,0xab,0xc4,0x00,0x00,0x00,0x00,0x00,0x89,0x02,0x34,0x01,0x80,0x01,0x94,0x20,0x8d, 0x02,0x10,0x2a,0x09,0x26,0x81,0x01,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x10,0x20,0x8d, 0x02,0x10,0x2a,0x0a,0xc8,0x01,0x00,0x01,0x00,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x83,0x20,0x8d, - 0x02,0x10,0x2a,0x0b,0xf3,0x00,0x00,0x02,0x00,0x06,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x20,0x8d, - 0x02,0x10,0x2a,0x0c,0x59,0xc0,0x00,0x18,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xa2,0x0e,0xe1,0x3a, + 0x02,0x10,0x2a,0x0c,0x5a,0x80,0x12,0x10,0xa8,0x00,0x6a,0xf7,0x28,0xff,0xfe,0xe5,0x6b,0x3a,0x20,0x8d, 0x02,0x10,0x2a,0x0d,0x56,0x00,0x00,0x24,0x0a,0x8e,0x00,0x00,0x00,0x00,0x00,0x00,0xa9,0x1e,0xd8,0x4d, - 0x02,0x10,0x2a,0x0d,0xeb,0x00,0x80,0x05,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x13,0x20,0x8d, - 0x02,0x10,0x2a,0x10,0x47,0x40,0x00,0x45,0x00,0x01,0xa0,0x13,0xd1,0xff,0xfe,0x85,0x36,0xe3,0x20,0x8d, - 0x02,0x10,0x2a,0x10,0x8b,0x40,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x03,0x20,0x8f, + 0x02,0x10,0x2a,0x0d,0x7c,0x40,0x30,0x00,0x0b,0x04,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x20,0x8d, + 0x02,0x10,0x2a,0x0d,0x83,0x40,0x00,0x24,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x20,0x8d, + 0x02,0x10,0x2a,0x0f,0xdf,0x00,0x00,0x00,0x20,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x62,0x20,0x8d, + 0x02,0x10,0x2a,0x10,0x37,0x81,0x16,0xb9,0x00,0x01,0xfe,0x3f,0xdb,0xff,0xfe,0x04,0x2d,0x4c,0x20,0x8d, + 0x02,0x10,0x2a,0x10,0x37,0x81,0x08,0x4b,0x00,0x01,0xb1,0x23,0x63,0x06,0x94,0x3a,0xf0,0x9b,0x20,0x8d, + 0x02,0x10,0x2a,0x10,0xd2,0x00,0x00,0x01,0x00,0x33,0xa6,0xbf,0x01,0xff,0xfe,0x6a,0x46,0xa9,0x20,0x8d, + 0x02,0x10,0x2c,0x0f,0xf4,0xc0,0x22,0x02,0x20,0xb0,0x26,0x1c,0x04,0xff,0xfe,0x14,0xda,0xa0,0x20,0x8d, 0x02,0x10,0x2c,0x0f,0xf8,0xf0,0xda,0x51,0x00,0x00,0x70,0xc3,0xee,0xa9,0x97,0x17,0x95,0x79,0x20,0x8d, - 0x04,0x20,0xd1,0xbb,0x02,0x8d,0x4d,0xd5,0x6a,0x20,0xc0,0xf9,0x16,0x2b,0x84,0x22,0x66,0xe0,0x89,0x45,0x60,0x37,0x52,0xe2,0x0b,0xa5,0xb4,0xf8,0x26,0xb3,0x8f,0x5a,0x30,0xed,0x20,0x8d, - 0x04,0x20,0xd2,0x59,0x3b,0xd7,0x14,0x7e,0xd0,0x98,0xfe,0x9e,0xa5,0x69,0xf4,0x26,0x6d,0x72,0x6f,0xc3,0x76,0xce,0x1d,0x40,0x41,0xa2,0xa1,0xaf,0xf9,0x6e,0x57,0x2d,0x9d,0xc3,0x20,0x8d, - 0x04,0x20,0xdf,0xd9,0xed,0x59,0xbf,0x1e,0x77,0x48,0x3c,0x13,0x3b,0xc5,0xc8,0x15,0x86,0x88,0x68,0xf0,0x08,0xe9,0xee,0x9b,0x3d,0xa4,0x33,0x0a,0x68,0x67,0x86,0x9d,0xe2,0x83,0x20,0x8d, 0x04,0x20,0xe9,0xbf,0xa7,0xbd,0x9b,0x54,0x54,0xe8,0xc8,0xae,0x78,0x99,0xa0,0xa3,0xf6,0x5d,0x78,0xe3,0x9e,0x5c,0xa7,0x18,0xb9,0x13,0x0c,0x04,0x9b,0xf3,0x7f,0x27,0x18,0xb0,0x20,0x8d, - 0x04,0x20,0xf8,0x8d,0x64,0xd2,0xc8,0xe9,0x0f,0x51,0x03,0x1c,0x98,0x33,0x8f,0xe0,0x1e,0xe7,0xb6,0x16,0x8d,0x2a,0xf5,0xf3,0x19,0xce,0xdd,0x9e,0xee,0x17,0xc3,0x8f,0xd6,0xa1,0x20,0x8d, - 0x04,0x20,0xfb,0xf1,0x17,0xd6,0x03,0x3b,0x01,0x8b,0x98,0xcf,0x16,0x20,0xde,0xaf,0x6c,0xed,0x60,0xab,0x6e,0x14,0x0b,0x58,0x6b,0x2d,0xf8,0x06,0x98,0x37,0x7a,0xff,0x7a,0x0f,0x20,0x8d, 0x04,0x20,0x0f,0xb9,0x71,0x05,0x64,0x83,0x2c,0x68,0x6a,0x9c,0xf0,0x4f,0xc3,0x90,0xcd,0x5c,0x73,0x9a,0xdd,0xb3,0xc6,0x42,0xca,0x09,0xbb,0xcc,0xfe,0x29,0x49,0x9f,0xc7,0x28,0x20,0x8d, - 0x04,0x20,0x22,0x6e,0x42,0xe4,0xbd,0x2b,0xe5,0x3e,0x30,0xda,0x8a,0x03,0xf3,0x45,0x52,0xac,0x84,0xbf,0xbf,0xc5,0xaa,0x5f,0xe0,0x1b,0x26,0x28,0xb5,0x83,0x2e,0xed,0x4c,0xee,0x20,0x8d, 0x04,0x20,0x2a,0x47,0x8b,0xa0,0x4f,0x67,0x1d,0xcd,0x5d,0x84,0x1a,0xec,0xbd,0xd2,0xaa,0xe9,0x99,0x01,0x96,0x5d,0x4e,0xff,0x64,0x47,0xba,0xde,0xbf,0x56,0x89,0x39,0xac,0xde,0x20,0x8d, 0x04,0x20,0x2b,0xf3,0xe8,0xf5,0xef,0x90,0x14,0xab,0x61,0xe9,0x11,0x97,0x9f,0x18,0x4d,0xb4,0xff,0x89,0x94,0xf7,0x92,0x94,0x53,0xe6,0x9e,0xd4,0xdb,0x85,0x89,0x4d,0x3e,0xc9,0x20,0x8d, - 0x04,0x20,0x2e,0x4e,0xde,0x51,0xd7,0x28,0x4b,0x29,0x7c,0xff,0x1f,0x8a,0x50,0xb7,0x5e,0xf0,0x81,0xcd,0xe8,0x8a,0x08,0x73,0x58,0x4e,0x43,0x1f,0x7b,0x85,0x9a,0xed,0xe2,0x68,0x20,0x8d, 0x04,0x20,0x35,0xdd,0xd0,0x36,0xa5,0x69,0x4a,0xd2,0xcc,0xb8,0xe9,0x62,0xa3,0x55,0xeb,0x86,0xe2,0xf3,0x03,0x48,0x26,0xe6,0x20,0xad,0xda,0xaa,0xff,0xde,0x16,0xad,0x39,0x9d,0x20,0x8d, 0x04,0x20,0x41,0x47,0x4e,0xc2,0xa1,0x71,0x63,0x3e,0x11,0x54,0x46,0x91,0x80,0xed,0x41,0x16,0x32,0x29,0x19,0x60,0xc9,0xef,0xa3,0xb7,0x96,0x2c,0x94,0xa8,0xdf,0x55,0xd7,0x21,0x20,0x8d, 0x04,0x20,0x44,0xf3,0xb7,0x5e,0x48,0x3c,0xbd,0xa6,0x52,0xaa,0x68,0xb5,0xbf,0xdc,0x01,0x5f,0x4b,0xeb,0x7a,0x25,0xcb,0x4a,0x70,0xbc,0x18,0x8c,0x97,0x5d,0x27,0x54,0x09,0x17,0x20,0x8d, - 0x04,0x20,0x5c,0x52,0x7f,0x17,0x16,0x4c,0x27,0x36,0x2d,0x05,0xa1,0x19,0x0d,0xbe,0x87,0xab,0x24,0x7b,0xe7,0x38,0x3b,0xa1,0x7f,0xd1,0xd4,0x28,0x16,0x8e,0xfc,0x98,0x7d,0x08,0x20,0x8d, + 0x04,0x20,0x53,0xcd,0x56,0x48,0x48,0x8c,0x47,0x07,0x91,0x41,0x82,0x65,0x5b,0x76,0x64,0x03,0x4e,0x09,0xe6,0x6f,0x7e,0x8c,0xbf,0x10,0x84,0xe6,0x54,0xeb,0x56,0xc5,0xbd,0x88,0x20,0x8d, 0x04,0x20,0x67,0xc4,0x17,0xa5,0xcb,0x77,0xbd,0xaa,0x11,0x7f,0x8b,0xc0,0x81,0xf3,0xc0,0x96,0x9d,0x31,0x27,0x9c,0xad,0x6c,0x6d,0x98,0x42,0x70,0xdb,0x50,0x12,0x96,0x0b,0x36,0x20,0x8d, - 0x04,0x20,0x73,0xdb,0x82,0xe0,0x88,0x40,0x49,0xd8,0x3b,0xa0,0xdd,0x83,0x7c,0x84,0x3c,0xb8,0xd0,0x03,0x0b,0x7a,0x08,0x44,0x4e,0x79,0xd6,0x61,0x23,0x31,0xa9,0xb3,0x07,0x58,0x20,0x8d, - 0x04,0x20,0x75,0x93,0x21,0xdd,0x99,0x58,0x3c,0x3f,0xae,0x36,0x50,0x58,0x49,0xe2,0xd0,0xc3,0x3a,0x2c,0x4a,0xcf,0x41,0xc4,0x82,0x48,0xab,0xec,0x07,0x5d,0x56,0x2c,0xb4,0x8d,0x20,0x8d, - 0x04,0x20,0x87,0xd4,0x66,0x0f,0xed,0xf9,0xf5,0xf1,0xcb,0x85,0x37,0xec,0xe1,0x19,0xa8,0xa4,0x03,0xb7,0x13,0x59,0xbb,0xf8,0xd2,0x93,0x92,0x50,0xfa,0x30,0x7a,0xd8,0x43,0xd0,0x20,0x8d, + 0x04,0x20,0x65,0x98,0x55,0xd0,0x8a,0xe0,0x29,0xe6,0x5e,0xef,0xb7,0x8b,0x8f,0xc9,0x27,0x53,0x3d,0xd0,0x8c,0xa2,0xfa,0x32,0x2f,0xad,0xf9,0xdc,0xe2,0x4b,0x14,0x66,0x3e,0x23,0x20,0x8d, 0x04,0x20,0x8b,0xfe,0xad,0x19,0xdb,0x97,0x57,0x84,0xec,0xad,0x4f,0xb2,0xdf,0x69,0x53,0x04,0x57,0x19,0x16,0x7a,0x71,0xd7,0x2b,0xab,0x03,0xfd,0x76,0x4d,0xa0,0x70,0xc3,0xe7,0x20,0x8d, - 0x04,0x20,0x96,0x25,0xde,0x4a,0xbc,0xbd,0x76,0x76,0xee,0x43,0x45,0x76,0xe0,0x0d,0x99,0x83,0xcd,0x83,0x8f,0x94,0xe5,0xde,0x7a,0xf2,0xf0,0x57,0xb8,0x25,0x54,0x17,0xcb,0x3b,0x20,0x8d, - 0x04,0x20,0x98,0xc6,0x44,0x27,0x90,0x41,0xa6,0x98,0xf9,0x25,0x6c,0x59,0x0f,0x06,0x6d,0x44,0x59,0x0e,0xb2,0x46,0xb0,0xa4,0x37,0x88,0x69,0x8f,0xc1,0x32,0xcd,0x9f,0x15,0xd7,0x20,0x8d, - 0x04,0x20,0xaa,0x3a,0x16,0x86,0xea,0x59,0x09,0x04,0x78,0xe5,0x10,0x92,0xe1,0x1d,0xad,0xf7,0x56,0x2b,0xac,0xb0,0x97,0x29,0x63,0x30,0xf4,0x1b,0xcf,0xde,0xf3,0x28,0x0a,0x29,0x20,0x8d, - 0x04,0x20,0xbc,0x27,0xae,0x89,0xc1,0x67,0x73,0x0a,0x08,0x02,0xdf,0xb7,0xcc,0x94,0xc7,0x9f,0xf4,0x72,0x7a,0x9b,0x20,0x0c,0x5c,0x11,0x3d,0x22,0xd6,0x13,0x88,0x66,0x74,0xbf,0x20,0x8d, + 0x05,0x20,0xd7,0x7a,0x53,0x89,0xfe,0x02,0x6a,0x59,0xb7,0x0e,0xf8,0x6d,0x9d,0x81,0xbb,0x9f,0x01,0xc0,0xc4,0xee,0x7b,0x36,0x10,0x33,0x07,0xd2,0x29,0xd8,0xec,0xcd,0x8e,0xa3,0x00,0x00, + 0x05,0x20,0xd7,0xf1,0x19,0x9e,0x7d,0x0f,0x43,0x97,0x33,0x56,0xe8,0x12,0x1d,0x7d,0xa0,0x4d,0x21,0x5a,0x60,0x73,0xc8,0x7e,0x10,0x55,0x60,0x56,0xbb,0x65,0x50,0xa4,0x17,0x59,0x00,0x00, + 0x05,0x20,0xd1,0x17,0xe2,0x34,0x4a,0x61,0x70,0x8b,0x04,0xa2,0xb4,0xb0,0x65,0x59,0x52,0x75,0x67,0x86,0xe6,0x48,0x33,0x31,0x5d,0x87,0x38,0x57,0xd3,0xf8,0x40,0x07,0x73,0xa8,0x00,0x00, + 0x05,0x20,0xd9,0x9c,0x20,0xfe,0xc2,0xe6,0x6a,0x16,0x30,0x81,0x54,0xc9,0x3f,0x9a,0x89,0x10,0xa9,0x4b,0xf1,0x05,0x56,0xd5,0x04,0x2d,0xb7,0x6a,0x7b,0x67,0x8d,0xf0,0xbe,0x8f,0x00,0x00, + 0x05,0x20,0xdc,0xdb,0x2d,0x39,0xd5,0xe4,0xda,0xb5,0xb6,0x6e,0x98,0x33,0x8f,0x51,0x99,0x54,0x38,0x53,0xa8,0x4e,0xda,0x29,0xd8,0xb5,0x57,0x9d,0x36,0xf0,0x97,0x37,0x18,0x3f,0x00,0x00, + 0x05,0x20,0xe1,0x44,0x2d,0x6e,0xd3,0xd9,0xf0,0x95,0x6c,0x52,0x2e,0x44,0x3c,0x27,0x3d,0x78,0xac,0x6e,0x8f,0x27,0x1c,0x0c,0xc0,0x78,0x22,0x3e,0xa1,0x84,0x01,0x42,0x08,0x5c,0x00,0x00, + 0x05,0x20,0xe3,0xa5,0x88,0x11,0x4d,0x3d,0xfb,0x02,0xec,0x1f,0xda,0x48,0x86,0x12,0xf6,0x12,0xd9,0x3e,0x68,0x49,0xa7,0xae,0x37,0xfd,0x02,0x48,0x38,0x8b,0xdc,0xd4,0xa6,0x8f,0x00,0x00, + 0x05,0x20,0xe5,0x19,0x24,0x71,0xab,0x61,0xb0,0xfe,0x44,0x4a,0x74,0x8d,0xca,0x90,0xc3,0xd6,0x24,0xb4,0xd5,0x03,0xe7,0xf3,0x4f,0xbe,0x12,0x72,0xd6,0xa0,0x4b,0x22,0x0b,0xe1,0x00,0x00, + 0x05,0x20,0xee,0xab,0xea,0x3b,0xc2,0x8a,0xe2,0xb3,0xe5,0xe7,0x92,0xf0,0x88,0x05,0x68,0x8d,0x2f,0xe7,0x2c,0xd2,0x00,0x39,0xd4,0x5d,0x07,0xfc,0xa1,0xd6,0xbf,0x89,0xa2,0x40,0x00,0x00, + 0x05,0x20,0xf2,0x74,0x4c,0x90,0xc3,0xd9,0x34,0x4d,0x5f,0x6e,0xdb,0xdd,0x7d,0xef,0xa5,0xed,0x6e,0x59,0x9e,0x31,0x41,0x94,0x38,0x84,0xc5,0x08,0xd2,0x23,0xb3,0xa7,0xe0,0x2c,0x00,0x00, + 0x05,0x20,0xf3,0x77,0xe5,0xa7,0x11,0xef,0x65,0x91,0x23,0xb8,0x32,0x06,0xcb,0xc0,0x91,0xf7,0x21,0x1d,0x70,0xbc,0x83,0x1b,0x86,0x34,0x35,0x31,0x0f,0x9f,0xc1,0x0d,0xbb,0x56,0x00,0x00, + 0x05,0x20,0xfe,0xb0,0x99,0x79,0x95,0x58,0x71,0xb5,0x63,0xcc,0x33,0xeb,0x55,0x91,0x8c,0xb4,0x3a,0xf2,0x8b,0x2d,0x8e,0x47,0xbe,0x25,0x47,0x12,0xcd,0x14,0x48,0xf0,0x1d,0xea,0x00,0x00, + 0x05,0x20,0xfc,0x79,0x14,0x77,0x6b,0x0e,0x34,0x8d,0xde,0x01,0x33,0xed,0xb4,0x0e,0xa7,0xc9,0x15,0x4f,0xd0,0x27,0x2c,0xd6,0x5f,0xe9,0x63,0x82,0x8d,0xd5,0x0c,0x9e,0x18,0x29,0x00,0x00, 0x05,0x20,0x07,0x61,0x26,0xd7,0x6c,0x05,0xbf,0xf6,0x2d,0x8c,0xca,0xc4,0x65,0xd3,0xd3,0xb2,0x49,0xe9,0xcc,0x53,0x1e,0xca,0x77,0x84,0xb6,0x10,0x5e,0xc2,0x5a,0xfe,0x28,0xb3,0x00,0x00, - 0x05,0x20,0x0a,0x26,0x27,0x45,0xb1,0x1e,0xfc,0x27,0x03,0x32,0x0e,0x65,0x9e,0x3c,0x64,0x0e,0x33,0x50,0x3d,0x6c,0x90,0x17,0x0e,0x29,0xee,0x5a,0x58,0xdf,0x08,0xde,0xbf,0x73,0x00,0x00, + 0x05,0x20,0x03,0xaa,0x47,0xe9,0xe2,0x77,0xeb,0xa5,0x72,0x27,0x23,0x8b,0x13,0x62,0x61,0x32,0xb5,0xb2,0x1b,0x5a,0x18,0xb2,0xf9,0x26,0x06,0x84,0xee,0x28,0x42,0xac,0xba,0xbc,0x00,0x00, + 0x05,0x20,0x08,0xc6,0x19,0x31,0x40,0x96,0xf3,0xe2,0x81,0x4e,0x88,0x54,0x54,0x9e,0xbf,0xfa,0x38,0x7a,0xfa,0x38,0x96,0x13,0x2a,0xc4,0x69,0xa2,0xae,0xe5,0x94,0xc7,0x16,0xb7,0x00,0x00, + 0x05,0x20,0x0a,0x26,0x27,0x23,0xdd,0xf3,0x56,0xbe,0x9e,0x9e,0xa7,0xc6,0x3c,0xc5,0x99,0xc4,0x87,0x3b,0x4d,0xb9,0x13,0x62,0x91,0xf2,0x25,0x1c,0x02,0x42,0x63,0xe3,0x63,0x7a,0x00,0x00, + 0x05,0x20,0x0c,0x50,0x55,0x46,0x87,0x5a,0x8d,0x14,0xfb,0xa7,0x29,0x70,0x18,0xa6,0x29,0x80,0x8c,0x33,0x42,0x5a,0x8f,0xe4,0x84,0x64,0x3d,0x0e,0xb5,0xbd,0x36,0x34,0x42,0xb6,0x00,0x00, 0x05,0x20,0x17,0x0c,0x56,0xce,0x72,0xa5,0xa0,0xe6,0x23,0x06,0xa3,0xc7,0x08,0x43,0x18,0xee,0x3a,0x46,0x35,0x5d,0x17,0xf6,0x78,0x96,0xa0,0x9c,0x51,0xef,0xbe,0x23,0xfd,0x71,0x00,0x00, - 0x05,0x20,0x19,0xe7,0x0d,0x3f,0xfe,0x9e,0x0e,0x8e,0x73,0x40,0x40,0xc3,0xba,0x8f,0x41,0xaf,0xf1,0x7b,0xa6,0x83,0x1b,0xc3,0xa4,0xe0,0x6d,0x6c,0x57,0xa7,0x36,0x5d,0x09,0xce,0x00,0x00, + 0x05,0x20,0x18,0x31,0xb3,0x9a,0xf8,0x8c,0xec,0x99,0x2e,0x7d,0xe4,0x90,0xa2,0x54,0x27,0xbd,0xe5,0xc8,0x65,0xdf,0x1f,0xaa,0x8f,0xe9,0x0f,0x64,0x85,0x09,0xc3,0x70,0x62,0x13,0x00,0x00, + 0x05,0x20,0x1a,0x35,0x98,0x78,0xb1,0xd9,0x48,0x62,0xe9,0x23,0x10,0xfe,0x71,0xdb,0x10,0x5f,0x28,0xc8,0xf3,0xda,0x33,0x9b,0x26,0xcb,0x4d,0xbe,0x7b,0x03,0x76,0xb9,0xe0,0x54,0x00,0x00, + 0x05,0x20,0x27,0x7a,0xaf,0x5a,0x9c,0xf4,0x72,0xfe,0x3c,0xdd,0x7a,0xba,0xd7,0x98,0x31,0xde,0x73,0xce,0x84,0x5b,0x41,0xe7,0x9a,0x6a,0xe2,0xc1,0x3b,0x5b,0x37,0x23,0xc7,0xdf,0x00,0x00, + 0x05,0x20,0x20,0x90,0xe3,0xd3,0xad,0x87,0xeb,0x2a,0xd9,0x29,0x17,0x74,0x47,0xc9,0x54,0x57,0xfa,0x3d,0x71,0x02,0x11,0xb2,0xc3,0x87,0x31,0xb3,0x9b,0x6f,0x2e,0xfc,0x30,0xea,0x00,0x00, + 0x05,0x20,0x22,0x56,0xd6,0x98,0x11,0x61,0xe1,0x5a,0x34,0x9f,0xe2,0x9d,0xf5,0x2b,0xbd,0xbc,0xcc,0x1c,0xf5,0x1d,0x68,0xa5,0xca,0xb1,0xb5,0x4b,0xf1,0xb5,0xff,0x1e,0xdd,0xc0,0x00,0x00, + 0x05,0x20,0x37,0x3e,0x28,0x39,0xef,0xa6,0xbc,0xf8,0xf1,0xba,0x11,0x40,0x87,0x42,0xff,0x58,0x46,0x8f,0xb0,0x80,0xfe,0x20,0x75,0xc5,0x43,0xce,0xec,0x9b,0x5d,0x37,0x19,0xf4,0x00,0x00, 0x05,0x20,0x3e,0xe3,0xe0,0xa9,0xbc,0xf4,0x2e,0x59,0xd9,0x20,0xee,0xdf,0x74,0x61,0x4d,0x99,0x0c,0x5c,0x15,0x30,0x9b,0x72,0x16,0x79,0x15,0xf4,0x7a,0xca,0x34,0xcc,0x81,0x99,0x00,0x00, - 0x05,0x20,0x3b,0x42,0x1c,0x25,0xf7,0xbf,0x79,0xed,0x6d,0x7d,0xef,0x65,0x30,0x7d,0xee,0x16,0x37,0x22,0x72,0x43,0x33,0x28,0x40,0xa3,0xaa,0xf4,0x48,0x49,0x67,0xb1,0x4b,0xfd,0x00,0x00, + 0x05,0x20,0x39,0xca,0x8e,0x62,0x0a,0x36,0xa7,0x68,0x22,0xc4,0xcc,0x4a,0xa9,0x16,0x69,0x4b,0x8a,0x1c,0x5f,0x6e,0x4a,0x98,0xb6,0x95,0x82,0xb3,0x66,0x66,0xc5,0x29,0x3a,0xb0,0x00,0x00, + 0x05,0x20,0x3b,0xd0,0x80,0xc4,0xab,0x82,0x83,0x11,0x48,0xe5,0x3b,0x23,0x3b,0x17,0x04,0x0f,0xd8,0x39,0x7c,0x1b,0x70,0x73,0x68,0x98,0x11,0x22,0x49,0x51,0x8f,0x20,0x65,0x65,0x00,0x00, + 0x05,0x20,0x47,0x1f,0x83,0xc8,0xa3,0x87,0x35,0xcf,0x8e,0x8d,0x22,0xfe,0xf0,0x02,0x63,0x04,0x3a,0x6a,0x49,0x1a,0x3b,0x70,0x17,0xeb,0x05,0xed,0xaf,0x61,0xb6,0x26,0xb8,0x21,0x00,0x00, + 0x05,0x20,0x45,0xbd,0x33,0x3d,0x81,0x1e,0x14,0x52,0x88,0x8a,0xa7,0x46,0x0f,0x37,0x25,0xd0,0x59,0x9f,0x78,0xb5,0x7f,0x4a,0xf1,0x54,0x8c,0x2d,0x36,0x32,0xf8,0x10,0xf3,0xcc,0x00,0x00, + 0x05,0x20,0x4a,0x8b,0x40,0x25,0xdc,0x06,0x2a,0xed,0x44,0x35,0xec,0x06,0x9e,0x73,0x70,0xf0,0x07,0x06,0x35,0xd1,0x60,0x4f,0x22,0xe8,0xbf,0x8a,0xdf,0xd9,0xeb,0x97,0x73,0x06,0x00,0x00, 0x05,0x20,0x4e,0x77,0x2e,0x12,0x91,0x67,0x6b,0x94,0xc4,0x92,0x2f,0x19,0x67,0x7d,0xcd,0x47,0x02,0xad,0xf8,0x60,0x72,0xed,0x73,0xf1,0x10,0x99,0x2c,0x05,0x61,0x66,0x55,0xd9,0x00,0x00, - 0x05,0x20,0x53,0x94,0xa6,0x3e,0x14,0x82,0xd4,0xf9,0xd3,0xa7,0x53,0x33,0x05,0xce,0x72,0x64,0xed,0x74,0x09,0x63,0x8f,0x24,0xef,0xda,0x12,0xa1,0x55,0xe0,0xd8,0xbb,0xd3,0x58,0x00,0x00, + 0x05,0x20,0x5a,0x29,0xfe,0x8a,0xaa,0x9d,0x78,0x81,0x04,0x53,0x37,0xf5,0x6f,0xb6,0xe1,0x57,0x08,0x80,0xcf,0xf6,0x03,0x11,0x92,0x8d,0x08,0xe3,0x99,0x9f,0x98,0x4a,0x27,0x6b,0x00,0x00, + 0x05,0x20,0x5a,0x65,0x0a,0x52,0x9b,0xc7,0x2e,0x92,0x79,0x7b,0xd3,0xf7,0xa4,0x35,0xbe,0x8e,0xb1,0xea,0x2d,0x7e,0x73,0x84,0x6a,0x3f,0xc9,0x9a,0x62,0xe3,0xc6,0x17,0x0e,0x7c,0x00,0x00, + 0x05,0x20,0x5c,0x40,0x7f,0x80,0x43,0x91,0x9c,0xfc,0x04,0xdc,0xdc,0x8e,0x01,0xda,0xc8,0xaf,0x90,0x62,0x64,0x16,0xf7,0x11,0xe4,0x87,0xac,0xa4,0x06,0x6f,0x8d,0x87,0x4e,0xd6,0x00,0x00, + 0x05,0x20,0x5e,0x69,0x4f,0x31,0x33,0xa7,0xea,0x3e,0xf4,0x7a,0x0a,0x1e,0x74,0x09,0x07,0xa1,0x50,0xf7,0x03,0xf5,0xc6,0x19,0xeb,0x95,0xaa,0x63,0x17,0x10,0x6e,0x68,0xca,0x10,0x00,0x00, + 0x05,0x20,0x67,0x82,0xfc,0x36,0xea,0xae,0x95,0x3b,0x5d,0x46,0xf3,0xf4,0x6c,0x50,0x69,0x29,0xc7,0x47,0x87,0xca,0xa6,0x40,0x12,0x40,0x6d,0x12,0x94,0x35,0x17,0x8a,0xba,0x56,0x00,0x00, + 0x05,0x20,0x67,0xab,0xce,0xf2,0xe3,0xf3,0xf6,0x19,0xf6,0x56,0xa2,0x4c,0xca,0x91,0x4b,0x93,0xfe,0xbc,0x85,0x50,0x5c,0x20,0x51,0x69,0xfb,0xf0,0x92,0xbb,0x57,0xa3,0x0d,0x05,0x00,0x00, + 0x05,0x20,0x62,0xcc,0x44,0x66,0x31,0x76,0x1b,0x43,0xbe,0xe1,0xc9,0x1d,0x20,0x26,0x7d,0xa7,0x06,0x31,0x57,0x0d,0xb9,0x58,0x20,0xb3,0xca,0xb2,0x5e,0x0a,0x15,0x0b,0xba,0x0d,0x00,0x00, + 0x05,0x20,0x71,0x68,0x1b,0xc7,0x48,0x8f,0xe9,0xa3,0x53,0x29,0xb6,0x23,0x5a,0x25,0x02,0x45,0x72,0xca,0xa1,0xb6,0x06,0xfc,0xda,0x65,0x71,0x37,0xe6,0xd9,0x30,0x81,0x02,0xfe,0x00,0x00, + 0x05,0x20,0x72,0x8b,0x72,0x38,0xfe,0x44,0xe9,0xc2,0xf4,0xbc,0xd8,0xce,0x1c,0xd5,0x50,0xb1,0x63,0x56,0x74,0x5e,0xf2,0xd3,0xe5,0xd5,0x29,0xe4,0x34,0x1d,0xf5,0x1c,0x7a,0xb9,0x00,0x00, + 0x05,0x20,0x76,0x74,0x80,0x6c,0xab,0x7b,0x36,0x3a,0x6a,0x78,0xa4,0xa8,0xb8,0xb7,0xe7,0xf9,0x34,0x47,0x6d,0x34,0xca,0xa2,0xc6,0xef,0x81,0xab,0x62,0xb1,0x46,0x86,0xaf,0xd0,0x00,0x00, + 0x05,0x20,0x80,0xfc,0x95,0xc8,0x95,0x91,0x2f,0x6b,0x6e,0xc4,0x2b,0xe1,0x2f,0xa0,0xcb,0xb8,0x76,0x5f,0x6f,0x1c,0x27,0xb0,0x3a,0x2c,0xb9,0x8d,0x6c,0x55,0xbd,0x02,0x60,0xe0,0x00,0x00, + 0x05,0x20,0x80,0xc6,0x6f,0xb3,0x18,0x5a,0x1a,0xde,0x4e,0xde,0x50,0xd2,0xc6,0x3f,0xc5,0x96,0x09,0x35,0x3a,0x4d,0x88,0x5f,0xa3,0x49,0x37,0xff,0xe6,0xc5,0x43,0x10,0xaf,0xa8,0x00,0x00, + 0x05,0x20,0x8a,0x32,0x54,0x37,0x12,0x24,0xb5,0x1d,0xba,0x3c,0x45,0x03,0x2e,0xda,0xfe,0xf1,0x87,0x8f,0x31,0xe5,0xfe,0xed,0xfa,0x38,0x25,0x00,0x99,0xa5,0xf4,0x51,0x02,0xf7,0x00,0x00, + 0x05,0x20,0x97,0x4e,0x74,0xcd,0x8b,0x37,0x76,0x15,0x3b,0x6d,0xb5,0xa4,0xa0,0x4b,0xf7,0x34,0x98,0x48,0x67,0x1e,0x99,0xe7,0x24,0xf4,0x00,0xf7,0x06,0x9c,0x45,0x69,0x34,0x01,0x00,0x00, 0x05,0x20,0x91,0x06,0xd1,0x9e,0xbd,0xab,0xc4,0x61,0xb3,0x0a,0xc2,0x3b,0x29,0xf3,0x10,0x38,0xee,0xbd,0x9d,0xe3,0x99,0x97,0x30,0x70,0x6e,0xe6,0xfb,0x6a,0x3c,0x07,0x3d,0xfd,0x00,0x00, + 0x05,0x20,0x9c,0x97,0xc1,0xad,0xf4,0xd2,0x07,0xae,0xe8,0x3e,0x14,0x42,0x36,0x85,0x71,0xa0,0xa7,0xef,0x72,0x44,0xf1,0x74,0x8b,0x6f,0xa5,0xa4,0x2c,0x0d,0xcc,0x9f,0xb0,0x9d,0x00,0x00, + 0x05,0x20,0x9d,0x0d,0x0f,0x58,0x1a,0x5c,0xb4,0x1a,0xeb,0xef,0x8e,0x91,0x8d,0x8c,0x1b,0x57,0x5d,0x6d,0x97,0x24,0x28,0x45,0x54,0x8a,0x3a,0xd5,0x05,0xfb,0x76,0xac,0x25,0x52,0x00,0x00, + 0x05,0x20,0xa4,0xb3,0x30,0x54,0x28,0x0f,0xfb,0xe5,0x76,0xb0,0x31,0xb2,0x65,0x62,0x56,0x72,0x7c,0xc9,0xcd,0x07,0x4d,0x5f,0xb1,0x69,0xe0,0xf7,0x35,0x3d,0x30,0x3c,0x7d,0x64,0x00,0x00, + 0x05,0x20,0xa9,0xa9,0xe5,0xae,0x01,0xc2,0x5e,0x76,0x2f,0x5d,0xa3,0x07,0xdc,0xce,0xb8,0xbc,0x6f,0x47,0xaf,0x3a,0x37,0xf8,0x5c,0x86,0xff,0xe9,0xb6,0xa5,0x00,0x93,0x76,0x11,0x00,0x00, + 0x05,0x20,0xb2,0x63,0x45,0xf5,0x36,0xb0,0x79,0x58,0x0d,0x8a,0x54,0x52,0x16,0x2f,0x1f,0x74,0x93,0xe0,0x30,0x82,0x1b,0xe4,0x01,0x76,0xf5,0x03,0xa1,0x19,0xa3,0x8d,0x0e,0xce,0x00,0x00, + 0x05,0x20,0xb5,0x55,0x31,0x3f,0xe7,0xc7,0x17,0xe4,0x31,0x87,0x47,0x45,0x7c,0x67,0x43,0x5c,0x82,0x73,0xd6,0x62,0x64,0x94,0x92,0x32,0x2d,0x81,0x0e,0x01,0x35,0xc0,0x7e,0xb7,0x00,0x00, 0x05,0x20,0xb5,0x83,0x6f,0xb6,0x11,0xd8,0x0e,0xa8,0x57,0xda,0x15,0x20,0x5b,0x1a,0x6d,0x21,0x15,0x5a,0xbd,0xb4,0x17,0x11,0xc2,0xfb,0x0e,0xfc,0xde,0xe8,0x26,0x56,0xa8,0xac,0x00,0x00, + 0x05,0x20,0xba,0xe0,0xd1,0xe5,0x2e,0x27,0x5b,0x1d,0x36,0x57,0x77,0xaf,0x64,0x04,0xfc,0xe1,0x8f,0x8c,0xf1,0x25,0x81,0x2b,0x4f,0x6a,0xf8,0x55,0x48,0xc2,0x5e,0x6f,0x62,0x43,0x00,0x00, + 0x05,0x20,0xc0,0xb9,0x7b,0x21,0xbd,0xa2,0x48,0xda,0x8a,0x3e,0xc3,0x6c,0xac,0xfd,0x6d,0x63,0x21,0xb6,0xb3,0x37,0xa9,0x4d,0x42,0x2c,0x9e,0x75,0x61,0x07,0xdc,0xc9,0xab,0x9b,0x00,0x00, + 0x05,0x20,0xc8,0xdc,0x00,0xc8,0xdf,0xa1,0xb2,0xe9,0x9f,0x00,0xb3,0x86,0x93,0xc3,0xbc,0x6d,0x56,0xe2,0x83,0xfc,0xf4,0x6e,0x55,0x5d,0xed,0x4e,0x53,0xe6,0xd1,0x4c,0x38,0x3c,0x00,0x00, 0x05,0x20,0xcc,0xaf,0x6c,0x3b,0xd0,0x13,0x76,0x23,0xc3,0x36,0xbb,0x64,0x4a,0x4a,0x06,0x93,0x69,0x6d,0xb0,0x10,0x6e,0x66,0xa4,0x61,0xf8,0x2d,0xe7,0x80,0x72,0x4d,0x53,0x94,0x00,0x00, + 0x05,0x20,0xce,0x25,0x15,0xbd,0x08,0xe9,0x67,0x1c,0xa2,0xa5,0x16,0x0e,0x38,0xd9,0xe4,0xc6,0x20,0x31,0x86,0x23,0x21,0x5a,0x5a,0x76,0x1e,0x74,0xd5,0xd3,0x4e,0x86,0x61,0xf4,0x00,0x00, 0x06,0x10,0xfc,0x32,0x17,0xea,0xe4,0x15,0xc3,0xbf,0x98,0x08,0x14,0x9d,0xb5,0xa2,0xc9,0xaa,0x20,0x8d, 0x06,0x10,0xfc,0xc7,0xbe,0x49,0xcc,0xd1,0xdc,0x91,0x31,0x25,0xf0,0xda,0x45,0x7d,0x08,0xce,0x20,0x8d, }; static const uint8_t chainparams_seed_test[] = { - 0x04,0x20,0xdf,0x55,0xaa,0x83,0xd5,0xc5,0xb8,0xe7,0x75,0x78,0xd4,0x29,0x51,0x4b,0x26,0x1c,0x23,0xdf,0x28,0x4d,0x29,0x85,0x07,0xb5,0xe2,0x29,0x69,0x3e,0x25,0xbb,0x61,0xcf,0x47,0x9d, - 0x04,0x20,0x0a,0xdd,0xa2,0x48,0xb5,0x56,0xa3,0x1f,0xca,0x3c,0x4c,0x9e,0xca,0x6e,0xb3,0xd5,0x5e,0x68,0xf6,0x28,0x31,0x57,0x24,0xfb,0x9d,0x2b,0x55,0x4f,0xd7,0x90,0x62,0xd3,0x47,0x9d, - 0x04,0x20,0x2d,0x04,0xa1,0x4a,0xd4,0x7c,0x7b,0x16,0x2e,0xb7,0xd2,0xa1,0x08,0xc5,0xd2,0xbd,0x53,0x87,0x34,0xdc,0x38,0x26,0xca,0x56,0xf2,0xac,0xc5,0x62,0x70,0x72,0x3f,0x63,0x47,0x9d, - 0x04,0x20,0x30,0x57,0x85,0xe0,0x02,0x4a,0xd1,0x31,0xeb,0x16,0x1b,0x1d,0xa8,0x43,0x0b,0xb4,0xc6,0xac,0x7d,0x46,0x24,0x0b,0x55,0x9d,0x16,0xe6,0x46,0x03,0x72,0xfe,0xd4,0xef,0x47,0x9d, - 0x04,0x20,0x36,0x6c,0xf1,0xd2,0xbb,0xda,0xff,0x8c,0x93,0x61,0x10,0xf2,0x9d,0xa1,0xa4,0x0a,0x30,0x9b,0x0c,0x69,0x6d,0xaa,0xd4,0x9c,0xfd,0xb5,0x5b,0x5e,0x30,0x9f,0xf3,0x13,0x47,0x9d, - 0x04,0x20,0x3e,0xe2,0xf3,0xe5,0xc5,0xbe,0x61,0xdd,0x4c,0x3e,0xdb,0x0d,0xd2,0xf9,0x42,0xe3,0x31,0xb2,0xa8,0x51,0x31,0xf6,0xce,0xc2,0x38,0x20,0x27,0x39,0x73,0x68,0x5a,0x42,0x47,0x9d, - 0x04,0x20,0x51,0x79,0x05,0x9c,0x8a,0xdf,0x03,0xb5,0x1b,0x17,0xc3,0x86,0xb6,0x54,0xcc,0xe0,0x6e,0x58,0xa6,0x41,0x4c,0xcc,0x0c,0x60,0x08,0xa6,0x0f,0x1d,0x11,0xd8,0x29,0xa6,0x47,0x9d, + 0x04,0x20,0xd7,0x13,0xfe,0x00,0xf0,0xf1,0x07,0xcb,0x30,0xb1,0x31,0xc7,0x68,0xbb,0x05,0xca,0x18,0xb5,0x1d,0xb1,0x0f,0xf2,0x7e,0x66,0xdb,0xaa,0xf4,0x05,0xaf,0x37,0x0a,0x62,0x47,0x9d, + 0x04,0x20,0xd1,0x71,0xfe,0x4c,0x9e,0xe9,0x99,0xb1,0x6c,0xd5,0xdd,0x3a,0xc3,0xd8,0x74,0x1d,0x42,0x32,0x9f,0xca,0xe9,0x47,0x9e,0x18,0x74,0x43,0x22,0xb9,0xa5,0x31,0xb9,0x66,0x47,0x9d, + 0x04,0x20,0xd2,0xe4,0xd1,0x40,0x65,0x5d,0x95,0xaf,0xe8,0x67,0xc0,0xe3,0x72,0xfd,0x0a,0x2e,0x35,0xb7,0xbd,0xac,0x67,0x78,0x36,0xe1,0xb8,0xba,0x30,0xcf,0x2e,0x05,0xdd,0x9d,0x47,0x9d, + 0x04,0x20,0xde,0xdd,0xe8,0x01,0x03,0x98,0x3f,0x2d,0x3d,0x8b,0x99,0x27,0xea,0xe1,0xa3,0xde,0xff,0x50,0x30,0xc2,0x41,0x4e,0x7e,0x72,0x24,0x3d,0xad,0x78,0x3c,0xab,0x12,0x67,0x47,0x9d, + 0x04,0x20,0xdb,0x9d,0xe0,0xc8,0x5d,0x65,0x4b,0xe0,0x7b,0x0f,0x63,0x80,0x85,0x21,0x73,0x07,0x94,0x0f,0xb0,0x23,0x38,0xec,0x78,0xb6,0x07,0xa1,0xd2,0xb0,0x82,0xf8,0x20,0xea,0x47,0x9d, + 0x04,0x20,0xdb,0xc9,0xb2,0xe1,0x4b,0x43,0xf1,0xc9,0x3d,0xea,0x51,0x9a,0x71,0x6b,0x4a,0x15,0xe7,0x3e,0xfe,0x8b,0x24,0x7a,0xaa,0xff,0x53,0xf7,0xfc,0x10,0xc6,0xcc,0x14,0x88,0x47,0x9d, + 0x04,0x20,0xe5,0x38,0xc1,0x67,0x52,0x2d,0x6f,0x2f,0xbe,0xc2,0xb3,0x45,0x2b,0x19,0x8c,0x10,0xf3,0x09,0xe5,0x37,0xfe,0x66,0x57,0x6f,0x7a,0x4d,0x78,0xf8,0x50,0x41,0xd6,0x90,0x47,0x9d, + 0x04,0x20,0xed,0x76,0x8d,0x28,0xb0,0x44,0x2c,0xff,0xf4,0xd9,0x6a,0x5c,0xf9,0xca,0x5f,0x81,0xa2,0x23,0x48,0x82,0x24,0x2e,0xfc,0xbe,0x0a,0x1e,0xf9,0x02,0x43,0x31,0x37,0xf5,0x47,0x9d, + 0x04,0x20,0xee,0x57,0x1b,0xab,0xd0,0xd7,0x27,0xca,0xe7,0xaa,0xe2,0x09,0xf3,0x4e,0x80,0x90,0xd2,0x0a,0x9d,0xc4,0x59,0xd0,0xdc,0xa1,0x97,0x09,0x4e,0x2f,0xfe,0x9f,0xa8,0x0a,0x47,0x9d, + 0x04,0x20,0xf7,0xe5,0xb0,0x17,0xdc,0x21,0xed,0xfb,0xb6,0xf5,0xe6,0xb4,0x0b,0xb3,0xf3,0x6c,0xb8,0x3f,0x2a,0x93,0x89,0x66,0x2f,0x05,0x30,0xdf,0xfa,0xdf,0xf2,0x1b,0x52,0x78,0x47,0x9d, + 0x04,0x20,0xf0,0x38,0xe6,0xe3,0xbb,0x4d,0x17,0x56,0xd0,0xae,0xf6,0xf4,0xa9,0x99,0x96,0xa9,0xe8,0xfc,0xfb,0x76,0x08,0xed,0x7f,0xb7,0xf0,0xa0,0xd3,0x8d,0xea,0x18,0x87,0x8c,0x47,0x9d, + 0x04,0x20,0xf1,0x67,0x83,0x6d,0xa7,0x17,0xa6,0xdd,0x1f,0x19,0x5c,0xbd,0x92,0x8e,0x45,0x0c,0x5c,0x2c,0x9d,0x57,0x98,0xe1,0x46,0x40,0x3d,0x87,0x30,0x02,0x9f,0x1d,0x88,0xde,0x47,0x9d, + 0x04,0x20,0xff,0x7b,0xca,0x5b,0xa3,0x4c,0x68,0x7d,0xf1,0x3d,0x18,0xef,0xb4,0x3e,0x39,0x34,0x0d,0x9c,0x3c,0x49,0x9b,0xe7,0x67,0x2c,0x78,0x61,0x42,0x67,0x62,0xd0,0x15,0xb5,0x47,0x9d, + 0x04,0x20,0xff,0xbc,0xb7,0x65,0x60,0x0d,0xc1,0x04,0xbc,0x30,0x95,0x67,0xe9,0x37,0x62,0xf7,0x5b,0xe3,0x2a,0xd6,0x3b,0xaf,0x8a,0x98,0xc9,0xfb,0x4b,0x66,0xb2,0xdc,0x13,0xc0,0x47,0x9d, + 0x04,0x20,0xf8,0x57,0x5e,0x80,0x2d,0xc4,0xc6,0x5e,0x7f,0x15,0x09,0xee,0xcc,0x29,0xf1,0x65,0x53,0x1c,0x41,0xc1,0xf7,0x51,0xa4,0xb0,0x76,0x86,0xe3,0x10,0x24,0x4d,0xd2,0x11,0x47,0x9d, + 0x04,0x20,0xfd,0x74,0x90,0x71,0x06,0xbf,0x2b,0xdc,0x69,0x31,0xf2,0x63,0x1f,0x18,0x17,0x71,0xd2,0x75,0xe4,0xf5,0x35,0x89,0xc0,0x55,0x71,0x05,0xc4,0x77,0x64,0xbf,0x20,0x36,0x47,0x9d, + 0x04,0x20,0xfe,0x4c,0x16,0x4f,0x37,0x75,0xb4,0xe2,0x54,0x7d,0x00,0x21,0x39,0x3a,0x2c,0xb7,0xe8,0x9a,0xf3,0x2f,0x8a,0xf6,0xe4,0x6b,0x7d,0xea,0x18,0xc3,0xc3,0x86,0x57,0xc5,0x47,0x9d, + 0x04,0x20,0x00,0xe5,0x30,0x05,0x39,0xf9,0x05,0xac,0x6d,0x33,0xe5,0xb4,0x3a,0xd1,0x8c,0x75,0xb3,0x5f,0x9b,0xcc,0xd1,0x76,0xa0,0x24,0x84,0x49,0x4b,0x40,0x67,0xee,0x4d,0x93,0x47,0x9d, + 0x04,0x20,0x01,0x25,0x8f,0x4c,0xb4,0x28,0x06,0xaa,0x4f,0xc5,0x5d,0x34,0x19,0x40,0xcd,0xb6,0xb9,0xad,0x52,0x3a,0xc3,0x52,0x05,0x9a,0x97,0x5e,0x69,0x9a,0x2a,0x66,0xde,0x48,0x47,0x9d, + 0x04,0x20,0x02,0xee,0xed,0xe8,0x3d,0x20,0xd1,0xb0,0xb7,0x44,0xd6,0xb9,0x08,0x9b,0x13,0x35,0xee,0xf4,0x0b,0x8a,0x6e,0x10,0xfa,0xbc,0x75,0x14,0xcb,0x28,0xd4,0x40,0x44,0x3a,0x47,0x9d, + 0x04,0x20,0x03,0x88,0x08,0xcf,0x7c,0xa3,0x1c,0xe6,0xd4,0x7a,0x50,0x61,0x1d,0x84,0x5c,0x95,0x89,0x4f,0x03,0x8c,0x91,0xf4,0x67,0xf2,0x4a,0xc4,0x2a,0x44,0x6e,0x47,0xc8,0xae,0x47,0x9d, + 0x04,0x20,0x03,0xe3,0x9a,0xa7,0xe7,0x30,0xa7,0x21,0x93,0x8c,0x52,0x76,0x4b,0x43,0x7d,0x35,0x70,0xa9,0x3d,0x35,0x54,0x11,0x83,0x1b,0xe1,0x7b,0x6d,0xd9,0xaa,0xb2,0x26,0x9b,0x47,0x9d, + 0x04,0x20,0x05,0x9e,0xaf,0x67,0x77,0x31,0xef,0xe7,0x65,0xd4,0x3b,0x86,0x2c,0x0c,0x10,0x9c,0x7d,0x4c,0xe4,0x2d,0xb7,0x05,0x12,0x51,0x17,0xfb,0x15,0x47,0xdd,0xd7,0x5b,0x0e,0x47,0x9d, + 0x04,0x20,0x06,0x30,0xd8,0x03,0x34,0x16,0x0f,0xa4,0x8f,0xb1,0x21,0xc4,0x53,0x19,0x87,0xa3,0x60,0xf1,0xdb,0x2b,0x09,0xf2,0x18,0x1b,0x1e,0x5a,0x78,0x29,0xe7,0x4e,0x12,0x7e,0x47,0x9d, + 0x04,0x20,0x0e,0x90,0xa0,0x77,0x60,0x80,0x95,0x5e,0x1a,0x5f,0xb3,0x1c,0x6d,0x87,0x11,0x20,0x35,0x52,0xe5,0x9d,0x7f,0xf0,0x4d,0xec,0x68,0x30,0xa4,0xca,0xd5,0xaf,0x0e,0x20,0x47,0x9d, + 0x04,0x20,0x09,0x3f,0x9b,0xec,0xe7,0xf0,0xd6,0x03,0xba,0x2b,0xac,0xa3,0x13,0x41,0x9c,0x70,0x0e,0x58,0x9a,0x0c,0xd4,0xc3,0x66,0x84,0x7c,0xa7,0x76,0xd9,0xd3,0xa5,0xff,0xba,0x47,0x9d, + 0x04,0x20,0x0a,0xe8,0xaf,0x6a,0xc9,0xd7,0x03,0x2a,0x8e,0xc7,0xe7,0xd9,0x47,0x77,0x87,0x3d,0xa4,0x09,0xa8,0xb3,0x44,0x2e,0x4a,0xcf,0xaa,0x08,0x3e,0x79,0x4d,0x2e,0x25,0xfd,0x47,0x9d, + 0x04,0x20,0x0d,0x02,0xcf,0x15,0x0e,0x79,0x72,0xab,0xc2,0x25,0xbf,0xab,0x07,0xa1,0xc4,0xe8,0x0e,0xb3,0xe2,0x81,0xcf,0x7e,0xe3,0x4a,0x10,0xc7,0x0e,0x0e,0xbd,0xad,0x32,0x20,0x47,0x9d, + 0x04,0x20,0x1e,0xc5,0x90,0x07,0x29,0x4f,0x7e,0xb3,0x46,0x51,0xe9,0x81,0x65,0x24,0x7f,0xae,0xcd,0x96,0xc6,0x00,0xfe,0x70,0x8c,0xc1,0x8a,0xe2,0xe3,0x80,0x48,0x40,0xab,0x10,0x47,0x9d, + 0x04,0x20,0x18,0xd3,0xc1,0x52,0xa1,0xde,0xaa,0x4e,0x88,0xf3,0x7f,0x92,0x09,0xcc,0x43,0x6b,0x59,0x76,0x3d,0xce,0x2a,0x66,0x7c,0xa3,0xf3,0x39,0xaf,0x73,0xcf,0xf8,0x83,0x89,0x47,0x9d, + 0x04,0x20,0x1c,0x0e,0x75,0xca,0x45,0xb3,0x0f,0xb3,0x7b,0x27,0xb5,0xde,0x22,0x86,0xde,0xe5,0xf3,0xd8,0x95,0x0a,0x11,0x96,0x86,0x84,0xee,0xbd,0x7a,0x04,0xd7,0x90,0xca,0x04,0x47,0x9d, + 0x04,0x20,0x1c,0x66,0x71,0x60,0x3c,0xbf,0x22,0x32,0x91,0x56,0xe9,0xbf,0x74,0xb8,0xd7,0x47,0xc1,0x07,0x2e,0x88,0x59,0xa8,0xb0,0x9a,0xd5,0x93,0x09,0xb6,0xdb,0x6e,0x40,0x6a,0x47,0x9d, + 0x04,0x20,0x1d,0x83,0xcf,0x89,0x90,0x06,0xa6,0x97,0xb2,0xa9,0x01,0x01,0x1f,0x98,0x62,0x04,0x65,0xa5,0x93,0x3e,0x6a,0x08,0x53,0xa3,0x90,0x2e,0xb5,0x02,0x1e,0x78,0x98,0x3d,0x47,0x9d, + 0x04,0x20,0x27,0xe6,0xa8,0x97,0xbc,0x69,0xb7,0x0e,0xd4,0x4d,0xe9,0x9b,0xff,0xe6,0xc9,0xb3,0x3f,0xc5,0xa8,0xa0,0xaf,0x19,0x61,0xd2,0xfb,0x7d,0x5c,0xdf,0x62,0xb0,0x36,0xe6,0x47,0x9d, + 0x04,0x20,0x24,0xe9,0x86,0x63,0x9f,0x96,0xe1,0x42,0x3e,0xa3,0x03,0x9d,0xfd,0x23,0xa4,0xeb,0x05,0xfa,0x3a,0xb1,0xcd,0x3a,0xce,0x24,0xbd,0x87,0x99,0x65,0xd9,0x19,0x75,0xf1,0x47,0x9d, + 0x04,0x20,0x28,0x50,0xc2,0x49,0xb8,0x3e,0x68,0x10,0xe6,0x02,0xdd,0x01,0x42,0xe5,0x41,0xc1,0x58,0xd5,0x5e,0xb8,0x7b,0xae,0x0a,0x90,0x14,0x0f,0xe3,0x97,0xe4,0xfa,0x2b,0xaf,0x47,0x9d, + 0x04,0x20,0x2e,0x43,0x1b,0x30,0xd6,0x62,0x9d,0xf8,0x50,0x8b,0x89,0xcb,0x49,0x2a,0x7b,0x42,0x40,0x10,0x0b,0xfe,0xcd,0xfe,0x6e,0xe8,0x65,0x89,0x2a,0xc3,0x8d,0x49,0xf2,0x2a,0x47,0x9d, + 0x04,0x20,0x36,0x3c,0xd4,0x1f,0x8f,0x63,0xfa,0x49,0x62,0xb5,0x69,0xd6,0x9f,0x42,0xaa,0xfe,0x54,0x14,0xd1,0xd2,0xb2,0xae,0x52,0xe1,0x08,0x7e,0xc6,0x15,0x56,0x45,0xbd,0xb3,0x47,0x9d, + 0x04,0x20,0x38,0x3d,0xac,0xa2,0x19,0x80,0xdc,0x61,0xff,0xb9,0x37,0xd6,0x53,0xf0,0xb0,0x89,0xb6,0x14,0x33,0x62,0x7c,0x33,0x8a,0x3d,0xdc,0xdd,0xba,0xfb,0x70,0xa4,0x6f,0x9b,0x47,0x9d, + 0x04,0x20,0x38,0x04,0x94,0x99,0x3a,0x60,0x61,0x08,0xcc,0xf9,0x43,0x18,0x8f,0x01,0xb5,0x43,0x7e,0x35,0xa5,0x27,0x2a,0xf6,0x85,0x78,0x81,0x36,0xed,0xb6,0xb0,0xd2,0xac,0x77,0x47,0x9d, + 0x04,0x20,0x38,0x54,0xfe,0xde,0xcf,0x83,0xb9,0x06,0x7f,0xaa,0x79,0x96,0x58,0x89,0x78,0x45,0x29,0x51,0x9a,0xbf,0x64,0xd1,0x02,0xe2,0x5f,0x74,0x15,0x2e,0x0d,0x8f,0x81,0x9e,0x47,0x9d, + 0x04,0x20,0x3c,0x0c,0xec,0x7b,0x47,0x74,0x12,0xcc,0xef,0xe1,0x88,0xcb,0x57,0x11,0xd5,0xd8,0x91,0x7e,0x95,0x17,0x0c,0x12,0xe2,0x46,0x7c,0xc0,0xe3,0xe3,0x92,0x26,0xeb,0x85,0x47,0x9d, + 0x04,0x20,0x3d,0x42,0xc6,0x66,0xb0,0x8b,0xcc,0xf9,0x6a,0xfd,0xa7,0x10,0xfe,0x2a,0x45,0xd9,0x3a,0xcd,0x15,0xa4,0x00,0xbf,0xde,0x1a,0x6d,0x3a,0x5b,0xa8,0xc8,0x95,0x6a,0x3c,0x47,0x9d, + 0x04,0x20,0x3d,0xb7,0x5c,0xbf,0x7a,0xb7,0x0f,0xe3,0x21,0xeb,0xa1,0x3c,0x8b,0xf3,0x9d,0x72,0x3d,0x69,0xfc,0xcc,0x22,0xa5,0x1c,0xb4,0x3f,0x98,0xcb,0x63,0xa8,0xc8,0x9f,0xcc,0x47,0x9d, + 0x04,0x20,0x40,0xc7,0x1f,0x78,0x96,0x51,0xc8,0xd4,0x54,0x0f,0x32,0x00,0xda,0x0d,0x2a,0xb4,0x04,0xff,0x0e,0xf3,0x94,0x8d,0xf4,0x5e,0x23,0x74,0xfa,0x0c,0xfa,0x87,0xfb,0x3b,0x47,0x9d, + 0x04,0x20,0x43,0x30,0x0d,0xf9,0x04,0xba,0x10,0x88,0x45,0x56,0xa5,0xee,0x22,0x1d,0xb5,0xe2,0x8d,0xd9,0x1e,0x68,0x8d,0x87,0xaa,0x9b,0x82,0x6a,0x71,0xe5,0x95,0x2b,0x79,0xf6,0x47,0x9d, + 0x04,0x20,0x45,0x0e,0x7a,0x38,0x4d,0x97,0xc8,0xd9,0xc7,0x2b,0xbc,0xae,0xa9,0xe3,0x3a,0x40,0x65,0xf3,0xc0,0xc8,0x31,0xab,0xe9,0x02,0x99,0x82,0xb2,0xa0,0x79,0x60,0x92,0xed,0x47,0x9d, + 0x04,0x20,0x4a,0x3b,0x66,0x48,0xee,0x55,0x05,0xed,0x91,0x14,0x5e,0x57,0x64,0xeb,0x23,0xab,0x56,0x83,0x56,0x36,0x2d,0x6a,0x2f,0xf8,0x28,0x11,0x37,0x3a,0x73,0x76,0x2e,0x6a,0x47,0x9d, + 0x04,0x20,0x4a,0x4b,0x4c,0x27,0xea,0x89,0xb2,0xa6,0x3e,0xf5,0x6e,0xc3,0xa4,0xe8,0xf6,0x5e,0x54,0xcc,0x93,0x64,0xce,0x36,0x82,0xd9,0x5f,0x1b,0x65,0x6b,0x02,0xc6,0x66,0x60,0x47,0x9d, + 0x04,0x20,0x4b,0x74,0xfe,0x48,0xc5,0x79,0xd5,0x77,0xba,0xdf,0x52,0x63,0x40,0xc4,0x30,0x04,0xff,0xf5,0x29,0x01,0xcc,0x15,0x5a,0x58,0xf7,0xc0,0x16,0x28,0x83,0x36,0x60,0xa0,0x47,0x9d, + 0x04,0x20,0x4c,0x6a,0x4c,0x67,0x94,0x97,0xa4,0xe3,0x78,0x9e,0x16,0x65,0x58,0xf5,0x95,0xa9,0xcd,0x94,0x9a,0xbc,0x40,0xc2,0x4e,0x20,0x71,0x72,0xd9,0x02,0x50,0x75,0x9f,0x41,0x47,0x9d, + 0x04,0x20,0x4c,0x85,0xc2,0xc6,0xb5,0x6a,0xf4,0x3d,0x84,0xf9,0xc3,0x8b,0x21,0x7f,0x57,0x7e,0x66,0x66,0x6b,0x6d,0x5f,0x0a,0xd0,0xf0,0x76,0x56,0x65,0x78,0xf8,0xa7,0x42,0x75,0x47,0x9d, + 0x04,0x20,0x52,0xf0,0xcb,0x8e,0xa9,0x27,0xd4,0x21,0x57,0x73,0xcb,0x40,0x17,0x6e,0x83,0x73,0xe4,0x59,0xa9,0x89,0x23,0x14,0x14,0x6a,0x91,0x85,0xd2,0xce,0x1c,0x85,0x05,0x06,0x47,0x9d, + 0x04,0x20,0x55,0x93,0x75,0x82,0x96,0x4c,0xc8,0x24,0x96,0x48,0xb3,0x9e,0x15,0xf5,0xf5,0x6f,0x5b,0xec,0xf6,0x39,0xff,0xa8,0xaf,0x3a,0x4c,0x5d,0x22,0x3d,0xd3,0x14,0xc3,0x05,0x47,0x9d, + 0x04,0x20,0x58,0xbf,0x47,0xe7,0x52,0x8d,0xb5,0x83,0xbc,0x55,0xb1,0xb8,0x02,0xe3,0xdc,0xc2,0xc4,0xe7,0x79,0x85,0xb2,0xe7,0x38,0x27,0xae,0xad,0x0c,0xb8,0x34,0xed,0x71,0xde,0x47,0x9d, + 0x04,0x20,0x59,0x76,0xc7,0xdc,0xb8,0x4d,0x1d,0x51,0x40,0x75,0x43,0x57,0xdd,0x3e,0xa3,0x63,0x06,0x0f,0x5e,0x18,0x29,0x10,0x08,0xac,0xa0,0x60,0xd2,0xf3,0x63,0x6e,0xa6,0x58,0x47,0x9d, 0x04,0x20,0x60,0xbe,0xae,0x7d,0xa3,0x4d,0x6a,0x71,0x1a,0x5d,0xe5,0x98,0x9c,0xde,0xa0,0x99,0x39,0x19,0xd3,0x01,0x0a,0x5d,0x1c,0x21,0x43,0x94,0x92,0x71,0x5d,0x77,0xd7,0xdf,0x47,0x9d, - 0x04,0x20,0x64,0x4e,0x86,0xa1,0x02,0xa1,0x8a,0xef,0xb0,0xd1,0xb5,0x77,0x69,0xb9,0x6a,0xdc,0xdf,0x35,0x8a,0xda,0xa4,0x3e,0x83,0xfa,0x50,0xe6,0xca,0x0e,0x2b,0x99,0x0a,0x17,0x47,0x9d, - 0x04,0x20,0xa2,0x28,0x3c,0x5a,0x5b,0x82,0x32,0x66,0x11,0xe5,0x71,0xff,0x6b,0x25,0x92,0x75,0xdd,0x7a,0x4f,0x90,0x8b,0x1d,0x34,0xa4,0xf1,0x6e,0xb9,0xfb,0xb5,0x2e,0x7c,0x7f,0x47,0x9d, - 0x04,0x20,0xc8,0xb5,0x6a,0xba,0x02,0x26,0x45,0x12,0xfb,0x93,0x8a,0x51,0xe4,0xb0,0xf3,0x94,0xb7,0xc0,0x74,0x72,0xeb,0x67,0x91,0x9e,0x04,0x36,0x6a,0x4b,0xef,0x0d,0x88,0xfe,0x47,0x9d, - 0x04,0x20,0xc8,0xfa,0xcd,0x8c,0xc3,0x6f,0x3c,0xd0,0x27,0x7e,0x7d,0xeb,0x51,0x01,0x65,0xb6,0x9e,0x02,0x09,0x64,0xf4,0x87,0x78,0x7b,0x8f,0x9d,0xaf,0x3b,0xa5,0xcc,0x56,0x2c,0x47,0x9d, + 0x04,0x20,0x62,0x42,0x6d,0x98,0xc4,0xa1,0x16,0xc1,0x7d,0x17,0x9e,0x37,0x94,0x88,0x58,0x76,0xa9,0x13,0x6a,0x98,0x39,0x22,0x58,0x13,0xf8,0x9a,0x99,0xa2,0x25,0x56,0x59,0x05,0x47,0x9d, + 0x04,0x20,0x6a,0xb1,0x07,0x66,0xbe,0x50,0xec,0x8e,0x6b,0x72,0xfc,0xb4,0xc2,0x67,0x26,0xa1,0x7a,0x10,0x15,0xd3,0xd0,0x36,0x7a,0xf0,0x83,0xd0,0xc3,0x59,0x85,0x68,0x90,0xd8,0x47,0x9d, + 0x04,0x20,0x6d,0x6b,0x07,0x72,0xf7,0x45,0x8c,0x1d,0xe3,0x5c,0xf2,0x58,0x20,0xd2,0x95,0x25,0x22,0x51,0xc3,0x59,0x96,0xc1,0xdc,0x7e,0xa4,0x82,0x89,0x0a,0xf5,0x25,0x38,0x1a,0x47,0x9d, + 0x04,0x20,0x77,0xaa,0x57,0x56,0x17,0xce,0xdd,0x79,0x64,0xaf,0x78,0xb2,0xf4,0x91,0x36,0x75,0x1c,0x1c,0xcd,0x31,0x35,0x4a,0xe4,0x8b,0x64,0x65,0x4e,0x06,0xec,0x2a,0xc4,0x95,0x47,0x9d, + 0x04,0x20,0x70,0x11,0x6d,0x2d,0xa7,0xc9,0x9f,0xd8,0xe3,0xe8,0xae,0x5a,0x4a,0x50,0xac,0x3f,0xd6,0x57,0xad,0xd0,0xa5,0x35,0x14,0xd7,0xb1,0x76,0x58,0xb0,0x90,0xa4,0x1c,0x9b,0x47,0x9d, + 0x04,0x20,0x72,0xdb,0xfc,0x04,0x48,0xee,0xec,0xae,0x1b,0xad,0xe6,0x9a,0x87,0xe1,0x17,0xae,0x13,0x70,0xae,0x83,0x90,0x62,0x59,0x30,0xed,0x44,0x43,0xe9,0xab,0xee,0xaa,0xd0,0x47,0x9d, + 0x04,0x20,0x74,0xd8,0x56,0x9c,0x5e,0xb4,0x62,0x12,0xea,0x6f,0x1e,0x3d,0x95,0x22,0x50,0x16,0xe3,0xba,0x45,0xb2,0xa3,0xad,0x26,0xb0,0x13,0x74,0xdd,0xec,0x22,0xe0,0x70,0x9a,0x47,0x9d, + 0x04,0x20,0x75,0x45,0xe9,0x01,0x21,0x81,0x44,0x1b,0x57,0x76,0x8c,0x3f,0x85,0xa9,0x74,0xf4,0x3c,0xf8,0xd5,0x12,0xcf,0x13,0x1e,0x2f,0x18,0x13,0x78,0xa3,0x51,0x59,0x49,0x49,0x47,0x9d, + 0x04,0x20,0x7b,0x3b,0xf1,0xa0,0x78,0xc9,0xba,0xb4,0xd2,0xad,0xed,0x0b,0x1e,0xd2,0x8d,0xd2,0x08,0xd1,0x51,0x5b,0x01,0x74,0x6c,0xc0,0x5d,0xcd,0x4c,0x53,0xf3,0xba,0x16,0xe3,0x47,0x9d, + 0x04,0x20,0x7b,0x11,0x26,0x1d,0xf6,0xa1,0xb7,0xc3,0x36,0x19,0x0d,0x27,0x10,0x36,0xb7,0xd5,0x93,0x60,0xc8,0xf8,0x82,0x79,0x46,0xe8,0x78,0xce,0x29,0x50,0x2b,0x44,0x3f,0x7a,0x47,0x9d, + 0x04,0x20,0x7b,0x96,0x08,0xa2,0x54,0x5c,0xd0,0x94,0x9c,0x0a,0xa7,0xb3,0xea,0x7d,0x38,0x0b,0x8e,0xee,0x91,0xe3,0x91,0xb9,0xc9,0xcc,0x81,0x28,0x2e,0xc3,0x02,0x9a,0xcf,0x65,0x47,0x9d, + 0x04,0x20,0x81,0x34,0x90,0x93,0x9c,0xf9,0xc9,0xe7,0x81,0xa5,0xab,0xf1,0x03,0xe1,0xb3,0x2f,0xf7,0xcf,0x19,0x94,0x2f,0x7a,0x1b,0xc5,0x83,0xc9,0x8c,0xc3,0x5b,0x09,0x96,0x14,0x47,0x9d, + 0x04,0x20,0x86,0x7d,0x81,0xf5,0x72,0x30,0xc0,0x91,0x4c,0x8e,0x04,0x48,0x47,0xce,0xcd,0x43,0xef,0x22,0x67,0x17,0xea,0x95,0x2f,0x51,0x4f,0x57,0x67,0x58,0x21,0xa5,0x2e,0x68,0x47,0x9d, + 0x04,0x20,0x8b,0xfc,0xf6,0xec,0x3b,0x71,0x25,0x88,0xe8,0xc8,0x72,0xc3,0x89,0x05,0x27,0x81,0x28,0x6b,0xb6,0x72,0xa7,0xc5,0x16,0xaa,0x67,0xe6,0x37,0xb7,0x2b,0x99,0xa8,0x97,0x47,0x9d, + 0x04,0x20,0xa2,0x46,0x23,0xd2,0xdb,0x3e,0x7a,0x65,0x05,0xd4,0x45,0x78,0xff,0x3e,0xc1,0x28,0xdb,0xfa,0xf1,0x5e,0x32,0xe9,0x6a,0x8f,0x96,0x45,0x15,0x01,0x7f,0x1f,0x75,0xbf,0x47,0x9d, + 0x04,0x20,0xa8,0xa6,0xbb,0x02,0x06,0x65,0x3b,0xb2,0x52,0x79,0xd1,0xc9,0x6a,0x01,0x42,0x43,0xca,0x69,0xe1,0x69,0x90,0xfe,0x2c,0xd2,0x7c,0x18,0xfd,0xab,0xc7,0x71,0x7f,0x8b,0x47,0x9d, + 0x04,0x20,0xa9,0x7b,0x48,0xf7,0x73,0xcb,0x07,0x37,0x4e,0xaa,0x68,0xc4,0x67,0x08,0xea,0x5b,0x68,0x0a,0xfe,0x14,0x82,0x6a,0xa1,0x79,0x82,0x9c,0x0b,0x52,0x17,0xd6,0x6b,0x1b,0x47,0x9d, + 0x04,0x20,0xb3,0x6e,0x64,0x99,0x26,0x7a,0xeb,0xd4,0x53,0x8c,0x94,0xb1,0x4a,0x37,0x02,0xaa,0xa9,0xc8,0x36,0x11,0xec,0xf9,0x0f,0x08,0xa3,0x9f,0x3e,0xaf,0xd2,0xd1,0x2a,0x0b,0x47,0x9d, + 0x04,0x20,0xb3,0xd4,0x15,0x0e,0x3f,0x12,0xed,0xfc,0xab,0x69,0x55,0x91,0x45,0xcf,0xc1,0x2f,0x35,0xa4,0xf7,0x94,0xff,0xd8,0xbe,0x44,0xc8,0x36,0xaf,0x0f,0xa0,0x4e,0x36,0x22,0x47,0x9d, + 0x04,0x20,0xb6,0x5e,0x39,0xeb,0x93,0x18,0x22,0xad,0x78,0x94,0x34,0x48,0x31,0x8d,0xd1,0x42,0xb7,0x38,0x58,0x49,0xea,0x00,0xb9,0xcf,0x58,0x43,0x38,0x22,0x22,0x88,0x87,0xde,0x47,0x9d, + 0x04,0x20,0xb9,0x80,0xf6,0xa1,0x4a,0x0a,0xc6,0xe3,0x19,0x27,0xe9,0x2d,0x75,0x60,0x14,0x3d,0x2c,0xda,0x97,0x77,0x54,0x9f,0x78,0xb8,0x9b,0x72,0x6a,0x58,0xab,0x8b,0x95,0x95,0x47,0x9d, + 0x04,0x20,0xba,0xab,0x91,0x9c,0x48,0x8b,0x9b,0x34,0x5c,0x30,0xf3,0xd2,0x78,0xd3,0xbf,0x09,0x79,0x23,0x4e,0x18,0x5d,0x5e,0x75,0x6b,0xa2,0x91,0x63,0x2c,0x75,0xfd,0x06,0x36,0x47,0x9d, + 0x04,0x20,0xbb,0x50,0xf0,0x50,0x8d,0xb7,0x6c,0xd4,0x87,0x59,0x0d,0xfd,0x5f,0x51,0x86,0xa8,0x43,0xb3,0x7a,0xe7,0x2f,0x54,0x97,0x7d,0xed,0xc3,0x7b,0x31,0xe6,0xb9,0x05,0xe7,0x47,0x9d, + 0x04,0x20,0xc0,0xc1,0xf5,0x59,0xe8,0x46,0xf6,0x9d,0x41,0xf0,0xde,0x8b,0x32,0xf9,0x6f,0xd6,0xb2,0xd5,0x36,0x56,0x43,0xd3,0x60,0x08,0x55,0x94,0x13,0xf5,0xef,0x7f,0x4d,0x4a,0x47,0x9d, + 0x04,0x20,0xce,0xd3,0xd2,0xba,0x56,0xa1,0xde,0xc9,0xc4,0xdb,0x50,0x7b,0xe9,0x4d,0x59,0x65,0x1c,0x02,0x4a,0xa3,0xea,0x73,0xb2,0x66,0x70,0xe7,0x85,0xc1,0x20,0x60,0x79,0x96,0x47,0x9d, + 0x04,0x20,0xc8,0x88,0xfe,0x71,0x5f,0xa3,0x6c,0x96,0x6a,0xd7,0x9e,0x38,0x84,0x9f,0x44,0xe1,0x6b,0xdc,0x98,0x31,0xad,0x96,0x29,0xe7,0x00,0x83,0x63,0x03,0xae,0x69,0x2e,0x63,0x47,0x9d, + 0x04,0x20,0xcb,0x2a,0x8c,0xe7,0xe5,0x1f,0x4e,0x3a,0x13,0xd6,0x9e,0xd7,0x68,0x51,0x83,0xf4,0x2d,0x3e,0x21,0x38,0x63,0x18,0xe9,0x97,0x27,0xff,0x45,0x48,0xc3,0x6c,0xca,0x59,0x47,0x9d, + 0x04,0x20,0xcb,0x80,0x0d,0xdf,0xf0,0xa6,0x28,0x84,0x98,0xc8,0x47,0x72,0xbe,0x53,0x04,0x43,0x9c,0x3a,0x0e,0x46,0x9c,0x75,0x4c,0x57,0x50,0x98,0xe5,0x25,0x80,0x5d,0xa2,0x91,0x47,0x9d, + 0x04,0x20,0xcb,0xaa,0x0a,0x5d,0x6e,0x8a,0xfa,0x49,0x5a,0x8c,0x0c,0x9d,0x7a,0xe9,0x9d,0xc6,0xe4,0xcd,0xc1,0x6a,0xff,0xbb,0xf1,0x89,0xf1,0xd5,0x16,0x3f,0xdc,0xbb,0xe9,0x1b,0x47,0x9d, }; #endif // BITCOIN_CHAINPARAMSSEEDS_H diff --git a/src/coins.cpp b/src/coins.cpp index 1abdcb54d2..5983a8a39f 100644 --- a/src/coins.cpp +++ b/src/coins.cpp @@ -99,9 +99,9 @@ void CCoinsViewCache::AddCoin(const COutPoint &outpoint, Coin&& coin, bool possi TRACE5(utxocache, add, outpoint.hash.data(), (uint32_t)outpoint.n, - (uint32_t)coin.nHeight, - (int64_t)coin.out.nValue, - (bool)coin.IsCoinBase()); + (uint32_t)it->second.coin.nHeight, + (int64_t)it->second.coin.out.nValue, + (bool)it->second.coin.IsCoinBase()); } void CCoinsViewCache::EmplaceCoinInternalDANGER(COutPoint&& outpoint, Coin&& coin) { @@ -296,7 +296,7 @@ bool CCoinsViewErrorCatcher::GetCoin(const COutPoint &outpoint, Coin &coin) cons try { return CCoinsViewBacked::GetCoin(outpoint, coin); } catch(const std::runtime_error& e) { - for (auto f : m_err_callbacks) { + for (const auto& f : m_err_callbacks) { f(); } LogPrintf("Error reading from database: %s\n", e.what()); diff --git a/src/coins.h b/src/coins.h index de297dd427..67fecc9785 100644 --- a/src/coins.h +++ b/src/coins.h @@ -142,7 +142,6 @@ public: virtual bool GetKey(COutPoint &key) const = 0; virtual bool GetValue(Coin &coin) const = 0; - virtual unsigned int GetValueSize() const = 0; virtual bool Valid() const = 0; virtual void Next() = 0; diff --git a/src/common/run_command.cpp b/src/common/run_command.cpp new file mode 100644 index 0000000000..6ad9f75b5d --- /dev/null +++ b/src/common/run_command.cpp @@ -0,0 +1,64 @@ +// Copyright (c) 2022 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#if defined(HAVE_CONFIG_H) +#include <config/bitcoin-config.h> +#endif + +#include <common/run_command.h> + +#include <tinyformat.h> +#include <univalue.h> + +#ifdef ENABLE_EXTERNAL_SIGNER +#if defined(__GNUC__) +// Boost 1.78 requires the following workaround. +// See: https://github.com/boostorg/process/issues/235 +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wnarrowing" +#endif +#include <boost/process.hpp> +#if defined(__GNUC__) +#pragma GCC diagnostic pop +#endif +#endif // ENABLE_EXTERNAL_SIGNER + +UniValue RunCommandParseJSON(const std::string& str_command, const std::string& str_std_in) +{ +#ifdef ENABLE_EXTERNAL_SIGNER + namespace bp = boost::process; + + UniValue result_json; + bp::opstream stdin_stream; + bp::ipstream stdout_stream; + bp::ipstream stderr_stream; + + if (str_command.empty()) return UniValue::VNULL; + + bp::child c( + str_command, + bp::std_out > stdout_stream, + bp::std_err > stderr_stream, + bp::std_in < stdin_stream + ); + if (!str_std_in.empty()) { + stdin_stream << str_std_in << std::endl; + } + stdin_stream.pipe().close(); + + std::string result; + std::string error; + std::getline(stdout_stream, result); + std::getline(stderr_stream, error); + + c.wait(); + const int n_error = c.exit_code(); + if (n_error) throw std::runtime_error(strprintf("RunCommandParseJSON error: process(%s) returned %d: %s\n", str_command, n_error, error)); + if (!result_json.read(result)) throw std::runtime_error("Unable to parse JSON: " + result); + + return result_json; +#else + throw std::runtime_error("Compiled without external signing support (required for external signing)."); +#endif // ENABLE_EXTERNAL_SIGNER +} diff --git a/src/common/run_command.h b/src/common/run_command.h new file mode 100644 index 0000000000..2fbdc071ee --- /dev/null +++ b/src/common/run_command.h @@ -0,0 +1,21 @@ +// Copyright (c) 2022 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_COMMON_RUN_COMMAND_H +#define BITCOIN_COMMON_RUN_COMMAND_H + +#include <string> + +class UniValue; + +/** + * Execute a command which returns JSON, and parse the result. + * + * @param str_command The command to execute, including any arguments + * @param str_std_in string to pass to stdin + * @return parsed JSON + */ +UniValue RunCommandParseJSON(const std::string& str_command, const std::string& str_std_in=""); + +#endif // BITCOIN_COMMON_RUN_COMMAND_H diff --git a/src/compat.h b/src/compat/compat.h index 3ec4ab53fd..a8e5552c0a 100644 --- a/src/compat.h +++ b/src/compat/compat.h @@ -3,24 +3,24 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#ifndef BITCOIN_COMPAT_H -#define BITCOIN_COMPAT_H +#ifndef BITCOIN_COMPAT_COMPAT_H +#define BITCOIN_COMPAT_COMPAT_H #if defined(HAVE_CONFIG_H) #include <config/bitcoin-config.h> #endif +// Windows defines FD_SETSIZE to 64 (see _fd_types.h in mingw-w64), +// which is too small for our usage, but allows us to redefine it safely. +// We redefine it to be 1024, to match glibc, see typesizes.h. #ifdef WIN32 -#ifndef NOMINMAX -#define NOMINMAX -#endif #ifdef FD_SETSIZE -#undef FD_SETSIZE // prevent redefinition compiler warning +#undef FD_SETSIZE #endif -#define FD_SETSIZE 1024 // max number of fds in fd_set +#define FD_SETSIZE 1024 #include <winsock2.h> #include <ws2tcpip.h> -#include <stdint.h> +#include <cstdint> #else #include <fcntl.h> #include <sys/mman.h> @@ -37,55 +37,71 @@ #include <unistd.h> #endif +// We map Linux / BSD error functions and codes, to the equivalent +// Windows definitions, and use the WSA* names throughout our code. +// Note that glibc defines EWOULDBLOCK as EAGAIN (see errno.h). #ifndef WIN32 typedef unsigned int SOCKET; -#include <errno.h> +#include <cerrno> #define WSAGetLastError() errno #define WSAEINVAL EINVAL -#define WSAEALREADY EALREADY #define WSAEWOULDBLOCK EWOULDBLOCK #define WSAEAGAIN EAGAIN #define WSAEMSGSIZE EMSGSIZE #define WSAEINTR EINTR #define WSAEINPROGRESS EINPROGRESS #define WSAEADDRINUSE EADDRINUSE -#define WSAENOTSOCK EBADF #define INVALID_SOCKET (SOCKET)(~0) #define SOCKET_ERROR -1 #else -#ifndef WSAEAGAIN +// WSAEAGAIN doesn't exist on Windows #ifdef EAGAIN #define WSAEAGAIN EAGAIN #else #define WSAEAGAIN WSAEWOULDBLOCK #endif #endif -#endif +// Windows doesn't define S_IRUSR or S_IWUSR. We define both +// here, with the same values as glibc (see stat.h). #ifdef WIN32 #ifndef S_IRUSR #define S_IRUSR 0400 #define S_IWUSR 0200 #endif -#else +#endif + +// Windows defines MAX_PATH as it's maximum path length. +// We define MAX_PATH for use on non-Windows systems. +#ifndef WIN32 #define MAX_PATH 1024 #endif + +// ssize_t is POSIX, and not present when using MSVC. #ifdef _MSC_VER -#if !defined(ssize_t) -#ifdef _WIN64 -typedef int64_t ssize_t; -#else -typedef int32_t ssize_t; -#endif -#endif +#include <BaseTsd.h> +typedef SSIZE_T ssize_t; #endif +// The type of the option value passed to getsockopt & setsockopt +// differs between Windows and non-Windows. #ifndef WIN32 typedef void* sockopt_arg_type; #else typedef char* sockopt_arg_type; #endif +#ifdef WIN32 +// Export main() and ensure working ASLR when using mingw-w64. +// Exporting a symbol will prevent the linker from stripping +// the .reloc section from the binary, which is a requirement +// for ASLR. While release builds are not affected, anyone +// building with a binutils < 2.36 is subject to this ld bug. +#define MAIN_FUNCTION __declspec(dllexport) int main(int argc, char* argv[]) +#else +#define MAIN_FUNCTION int main(int argc, char* argv[]) +#endif + // Note these both should work with the current usage of poll, but best to be safe // WIN32 poll is broken https://daniel.haxx.se/blog/2012/10/10/wsapoll-is-broken/ // __APPLE__ poll is broke https://github.com/bitcoin/bitcoin/pull/14336#issuecomment-437384408 @@ -111,4 +127,4 @@ bool static inline IsSelectableSocket(const SOCKET& s) { #define MSG_DONTWAIT 0 #endif -#endif // BITCOIN_COMPAT_H +#endif // BITCOIN_COMPAT_COMPAT_H diff --git a/src/consensus/consensus.h b/src/consensus/consensus.h index 788fa4e55b..10bdbf31a8 100644 --- a/src/consensus/consensus.h +++ b/src/consensus/consensus.h @@ -6,7 +6,7 @@ #ifndef BITCOIN_CONSENSUS_CONSENSUS_H #define BITCOIN_CONSENSUS_CONSENSUS_H -#include <stdlib.h> +#include <cstdlib> #include <stdint.h> /** The maximum allowed size for a serialized block, in bytes (only for buffer size limits) */ @@ -26,7 +26,5 @@ static const size_t MIN_SERIALIZABLE_TRANSACTION_WEIGHT = WITNESS_SCALE_FACTOR * /** Flags for nSequence and nLockTime locks */ /** Interpret sequence numbers as relative lock-time constraints. */ static constexpr unsigned int LOCKTIME_VERIFY_SEQUENCE = (1 << 0); -/** Use GetMedianTimePast() instead of nTime for end point timestamp. */ -static constexpr unsigned int LOCKTIME_MEDIAN_TIME_PAST = (1 << 1); #endif // BITCOIN_CONSENSUS_CONSENSUS_H diff --git a/src/consensus/params.h b/src/consensus/params.h index 794e0f5383..7c35222713 100644 --- a/src/consensus/params.h +++ b/src/consensus/params.h @@ -8,6 +8,7 @@ #include <uint256.h> +#include <chrono> #include <limits> #include <map> @@ -109,6 +110,10 @@ struct Params { bool fPowNoRetargeting; int64_t nPowTargetSpacing; int64_t nPowTargetTimespan; + std::chrono::seconds PowTargetSpacing() const + { + return std::chrono::seconds{nPowTargetSpacing}; + } int64_t DifficultyAdjustmentInterval() const { return nPowTargetTimespan / nPowTargetSpacing; } /** The best chain should have at least this much work */ uint256 nMinimumChainWork; diff --git a/src/consensus/validation.h b/src/consensus/validation.h index 6027bb9aeb..9c0aa09356 100644 --- a/src/consensus/validation.h +++ b/src/consensus/validation.h @@ -79,6 +79,7 @@ enum class BlockValidationResult { BLOCK_INVALID_PREV, //!< A block this one builds on is invalid BLOCK_TIME_FUTURE, //!< block timestamp was > 2 hours in the future (or our clock is bad) BLOCK_CHECKPOINT, //!< the block failed to meet one of our checkpoints + BLOCK_HEADER_LOW_WORK //!< the block header may be on a too-little-work chain }; diff --git a/src/core_read.cpp b/src/core_read.cpp index 77c516427a..ec21a2f7f4 100644 --- a/src/core_read.cpp +++ b/src/core_read.cpp @@ -265,7 +265,7 @@ int ParseSighashString(const UniValue& sighash) {std::string("SINGLE"), int(SIGHASH_SINGLE)}, {std::string("SINGLE|ANYONECANPAY"), int(SIGHASH_SINGLE|SIGHASH_ANYONECANPAY)}, }; - std::string strHashType = sighash.get_str(); + const std::string& strHashType = sighash.get_str(); const auto& it = map_sighash_values.find(strHashType); if (it != map_sighash_values.end()) { hash_type = it->second; diff --git a/src/crc32c/CMakeLists.txt b/src/crc32c/CMakeLists.txt index 71692d5796..2f52db104c 100644 --- a/src/crc32c/CMakeLists.txt +++ b/src/crc32c/CMakeLists.txt @@ -296,9 +296,10 @@ target_include_directories(crc32c $<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}> ) -target_compile_definitions(crc32c -PRIVATE - CRC32C_HAVE_CONFIG_H=1 +set_property( + TARGET crc32c_arm64 crc32c_sse42 crc32c + APPEND + PROPERTY COMPILE_DEFINITIONS CRC32C_HAVE_CONFIG_H ) set_target_properties(crc32c diff --git a/src/crypto/chacha20.h b/src/crypto/chacha20.h index 69fbbe9fa5..de16a77878 100644 --- a/src/crypto/chacha20.h +++ b/src/crypto/chacha20.h @@ -5,8 +5,8 @@ #ifndef BITCOIN_CRYPTO_CHACHA20_H #define BITCOIN_CRYPTO_CHACHA20_H +#include <cstdlib> #include <stdint.h> -#include <stdlib.h> /** A class for ChaCha20 256-bit stream cipher developed by Daniel J. Bernstein https://cr.yp.to/chacha/chacha-20080128.pdf */ diff --git a/src/crypto/chacha_poly_aead.cpp b/src/crypto/chacha_poly_aead.cpp index 4f3e6f7fa3..f736b2d867 100644 --- a/src/crypto/chacha_poly_aead.cpp +++ b/src/crypto/chacha_poly_aead.cpp @@ -2,6 +2,10 @@ // 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 <crypto/chacha_poly_aead.h> #include <crypto/poly1305.h> diff --git a/src/crypto/hkdf_sha256_32.h b/src/crypto/hkdf_sha256_32.h index fa1e42aec1..878b03a37f 100644 --- a/src/crypto/hkdf_sha256_32.h +++ b/src/crypto/hkdf_sha256_32.h @@ -7,8 +7,8 @@ #include <crypto/hmac_sha256.h> +#include <cstdlib> #include <stdint.h> -#include <stdlib.h> /** A rfc5869 HKDF implementation with HMAC_SHA256 and fixed key output length of 32 bytes (L=32) */ class CHKDF_HMAC_SHA256_L32 diff --git a/src/crypto/hmac_sha256.h b/src/crypto/hmac_sha256.h index d31fda1dd1..9c25edd7c1 100644 --- a/src/crypto/hmac_sha256.h +++ b/src/crypto/hmac_sha256.h @@ -7,8 +7,8 @@ #include <crypto/sha256.h> +#include <cstdlib> #include <stdint.h> -#include <stdlib.h> /** A hasher class for HMAC-SHA-256. */ class CHMAC_SHA256 diff --git a/src/crypto/hmac_sha512.h b/src/crypto/hmac_sha512.h index 1ea9a3671e..6acce8992e 100644 --- a/src/crypto/hmac_sha512.h +++ b/src/crypto/hmac_sha512.h @@ -7,8 +7,8 @@ #include <crypto/sha512.h> +#include <cstdlib> #include <stdint.h> -#include <stdlib.h> /** A hasher class for HMAC-SHA-512. */ class CHMAC_SHA512 diff --git a/src/crypto/muhash.cpp b/src/crypto/muhash.cpp index 57ed357645..7d14b7938e 100644 --- a/src/crypto/muhash.cpp +++ b/src/crypto/muhash.cpp @@ -298,7 +298,7 @@ void Num3072::ToBytes(unsigned char (&out)[BYTE_SIZE]) { Num3072 MuHash3072::ToNum3072(Span<const unsigned char> in) { unsigned char tmp[Num3072::BYTE_SIZE]; - uint256 hashed_in = (CHashWriter(SER_DISK, 0) << in).GetSHA256(); + uint256 hashed_in{(HashWriter{} << in).GetSHA256()}; ChaCha20(hashed_in.data(), hashed_in.size()).Keystream(tmp, Num3072::BYTE_SIZE); Num3072 out{tmp}; @@ -318,7 +318,7 @@ void MuHash3072::Finalize(uint256& out) noexcept unsigned char data[Num3072::BYTE_SIZE]; m_numerator.ToBytes(data); - out = (CHashWriter(SER_DISK, 0) << data).GetSHA256(); + out = (HashWriter{} << data).GetSHA256(); } MuHash3072& MuHash3072::operator*=(const MuHash3072& mul) noexcept diff --git a/src/crypto/poly1305.h b/src/crypto/poly1305.h index 1598b013b9..c80faada7e 100644 --- a/src/crypto/poly1305.h +++ b/src/crypto/poly1305.h @@ -5,8 +5,8 @@ #ifndef BITCOIN_CRYPTO_POLY1305_H #define BITCOIN_CRYPTO_POLY1305_H +#include <cstdlib> #include <stdint.h> -#include <stdlib.h> #define POLY1305_KEYLEN 32 #define POLY1305_TAGLEN 16 diff --git a/src/crypto/ripemd160.h b/src/crypto/ripemd160.h index 38ea375c1f..f1d89b8407 100644 --- a/src/crypto/ripemd160.h +++ b/src/crypto/ripemd160.h @@ -5,8 +5,8 @@ #ifndef BITCOIN_CRYPTO_RIPEMD160_H #define BITCOIN_CRYPTO_RIPEMD160_H +#include <cstdlib> #include <stdint.h> -#include <stdlib.h> /** A hasher class for RIPEMD-160. */ class CRIPEMD160 diff --git a/src/crypto/sha1.h b/src/crypto/sha1.h index 8b4568ee12..6ef0187efd 100644 --- a/src/crypto/sha1.h +++ b/src/crypto/sha1.h @@ -5,8 +5,8 @@ #ifndef BITCOIN_CRYPTO_SHA1_H #define BITCOIN_CRYPTO_SHA1_H +#include <cstdlib> #include <stdint.h> -#include <stdlib.h> /** A hasher class for SHA1. */ class CSHA1 diff --git a/src/crypto/sha256.h b/src/crypto/sha256.h index 028ee14345..24bd1f2e7e 100644 --- a/src/crypto/sha256.h +++ b/src/crypto/sha256.h @@ -5,8 +5,8 @@ #ifndef BITCOIN_CRYPTO_SHA256_H #define BITCOIN_CRYPTO_SHA256_H +#include <cstdlib> #include <stdint.h> -#include <stdlib.h> #include <string> /** A hasher class for SHA-256. */ diff --git a/src/crypto/sha256_sse4.cpp b/src/crypto/sha256_sse4.cpp index f1a7fefea3..bc69703607 100644 --- a/src/crypto/sha256_sse4.cpp +++ b/src/crypto/sha256_sse4.cpp @@ -5,8 +5,8 @@ // This is a translation to GCC extended asm syntax from YASM code by Intel // (available at the bottom of this file). +#include <cstdlib> #include <stdint.h> -#include <stdlib.h> #if defined(__x86_64__) || defined(__amd64__) diff --git a/src/crypto/sha3.h b/src/crypto/sha3.h index 88d8c1204d..78608eae76 100644 --- a/src/crypto/sha3.h +++ b/src/crypto/sha3.h @@ -7,8 +7,8 @@ #include <span.h> +#include <cstdlib> #include <stdint.h> -#include <stdlib.h> //! The Keccak-f[1600] transform. void KeccakF(uint64_t (&st)[25]); diff --git a/src/crypto/sha512.h b/src/crypto/sha512.h index 21ca930c75..7356dff6d9 100644 --- a/src/crypto/sha512.h +++ b/src/crypto/sha512.h @@ -5,8 +5,8 @@ #ifndef BITCOIN_CRYPTO_SHA512_H #define BITCOIN_CRYPTO_SHA512_H +#include <cstdlib> #include <stdint.h> -#include <stdlib.h> /** A hasher class for SHA-512. */ class CSHA512 diff --git a/src/cuckoocache.h b/src/cuckoocache.h index d0dc61c7e6..61f553806e 100644 --- a/src/cuckoocache.h +++ b/src/cuckoocache.h @@ -12,7 +12,9 @@ #include <atomic> #include <cmath> #include <cstring> +#include <limits> #include <memory> +#include <optional> #include <utility> #include <vector> @@ -326,7 +328,7 @@ public: } /** setup initializes the container to store no more than new_size - * elements. + * elements and no less than 2 elements. * * setup should only be called once. * @@ -336,8 +338,8 @@ public: uint32_t setup(uint32_t new_size) { // depth_limit must be at least one otherwise errors can occur. - depth_limit = static_cast<uint8_t>(std::log2(static_cast<float>(std::max((uint32_t)2, new_size)))); size = std::max<uint32_t>(2, new_size); + depth_limit = static_cast<uint8_t>(std::log2(static_cast<float>(size))); table.resize(size); collection_flags.setup(size); epoch_flags.resize(size); @@ -357,12 +359,21 @@ public: * * @param bytes the approximate number of bytes to use for this data * structure - * @returns the maximum number of elements storable (see setup() - * documentation for more detail) + * @returns A pair of the maximum number of elements storable (see setup() + * documentation for more detail) and the approxmiate total size of these + * elements in bytes or std::nullopt if the size requested is too large. */ - uint32_t setup_bytes(size_t bytes) + std::optional<std::pair<uint32_t, size_t>> setup_bytes(size_t bytes) { - return setup(bytes/sizeof(Element)); + size_t requested_num_elems = bytes / sizeof(Element); + if (std::numeric_limits<uint32_t>::max() < requested_num_elems) { + return std::nullopt; + } + + auto num_elems = setup(bytes/sizeof(Element)); + + size_t approx_size_bytes = num_elems * sizeof(Element); + return std::make_pair(num_elems, approx_size_bytes); } /** insert loops at most depth_limit times trying to insert a hash diff --git a/src/dbwrapper.cpp b/src/dbwrapper.cpp index a2f1f32780..4dbc839941 100644 --- a/src/dbwrapper.cpp +++ b/src/dbwrapper.cpp @@ -4,15 +4,28 @@ #include <dbwrapper.h> -#include <memory> +#include <fs.h> +#include <logging.h> #include <random.h> +#include <tinyformat.h> +#include <util/strencodings.h> +#include <util/system.h> +#include <algorithm> +#include <cassert> +#include <cstdarg> +#include <cstdint> +#include <cstdio> #include <leveldb/cache.h> +#include <leveldb/db.h> #include <leveldb/env.h> #include <leveldb/filter_policy.h> -#include <memenv.h> -#include <stdint.h> -#include <algorithm> +#include <leveldb/helpers/memenv/memenv.h> +#include <leveldb/iterator.h> +#include <leveldb/options.h> +#include <leveldb/status.h> +#include <memory> +#include <optional> class CBitcoinLevelDBLogger : public leveldb::Logger { public: diff --git a/src/dbwrapper.h b/src/dbwrapper.h index 1109cb5888..665eaa0e98 100644 --- a/src/dbwrapper.h +++ b/src/dbwrapper.h @@ -7,14 +7,26 @@ #include <clientversion.h> #include <fs.h> +#include <logging.h> #include <serialize.h> #include <span.h> #include <streams.h> -#include <util/strencodings.h> -#include <util/system.h> +#include <cstddef> +#include <cstdint> +#include <exception> #include <leveldb/db.h> +#include <leveldb/iterator.h> +#include <leveldb/options.h> +#include <leveldb/slice.h> +#include <leveldb/status.h> #include <leveldb/write_batch.h> +#include <stdexcept> +#include <string> +#include <vector> +namespace leveldb { +class Env; +} static const size_t DBWRAPPER_PREALLOC_KEY_SIZE = 64; static const size_t DBWRAPPER_PREALLOC_VALUE_SIZE = 1024; @@ -166,11 +178,6 @@ public: } return true; } - - unsigned int GetValueSize() { - return piter->value().size(); - } - }; class CDBWrapper @@ -318,22 +325,6 @@ public: pdb->GetApproximateSizes(&range, 1, &size); return size; } - - /** - * Compact a certain range of keys in the database. - */ - template<typename K> - void CompactRange(const K& key_begin, const K& key_end) const - { - CDataStream ssKey1(SER_DISK, CLIENT_VERSION), ssKey2(SER_DISK, CLIENT_VERSION); - ssKey1.reserve(DBWRAPPER_PREALLOC_KEY_SIZE); - ssKey2.reserve(DBWRAPPER_PREALLOC_KEY_SIZE); - ssKey1 << key_begin; - ssKey2 << key_end; - leveldb::Slice slKey1((const char*)ssKey1.data(), ssKey1.size()); - leveldb::Slice slKey2((const char*)ssKey2.data(), ssKey2.size()); - pdb->CompactRange(&slKey1, &slKey2); - } }; #endif // BITCOIN_DBWRAPPER_H diff --git a/src/external_signer.cpp b/src/external_signer.cpp index d125fe479b..0e582629f7 100644 --- a/src/external_signer.cpp +++ b/src/external_signer.cpp @@ -3,10 +3,10 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include <chainparams.h> +#include <common/run_command.h> #include <core_io.h> #include <psbt.h> #include <util/strencodings.h> -#include <util/system.h> #include <external_signer.h> #include <algorithm> @@ -28,7 +28,7 @@ bool ExternalSigner::Enumerate(const std::string& command, std::vector<ExternalS if (!result.isArray()) { throw std::runtime_error(strprintf("'%s' received invalid response, expected array of signers", command)); } - for (UniValue signer : result.getValues()) { + for (const UniValue& signer : result.getValues()) { // Check for error const UniValue& error = find_value(signer, "error"); if (!error.isNull()) { @@ -49,7 +49,7 @@ bool ExternalSigner::Enumerate(const std::string& command, std::vector<ExternalS if (signer.m_fingerprint.compare(fingerprintStr) == 0) duplicate = true; } if (duplicate) break; - std::string name = ""; + std::string name; const UniValue& model_field = find_value(signer, "model"); if (model_field.isStr() && model_field.getValStr() != "") { name += model_field.getValStr(); diff --git a/src/fs.cpp b/src/fs.cpp index b61115bf01..07cce269ed 100644 --- a/src/fs.cpp +++ b/src/fs.cpp @@ -12,9 +12,6 @@ #include <sys/utsname.h> #include <unistd.h> #else -#ifndef NOMINMAX -#define NOMINMAX -#endif #include <codecvt> #include <limits> #include <windows.h> @@ -129,7 +126,7 @@ bool FileLock::TryLock() if (hFile == INVALID_HANDLE_VALUE) { return false; } - _OVERLAPPED overlapped = {0}; + _OVERLAPPED overlapped = {}; if (!LockFileEx(hFile, LOCKFILE_EXCLUSIVE_LOCK | LOCKFILE_FAIL_IMMEDIATELY, 0, std::numeric_limits<DWORD>::max(), std::numeric_limits<DWORD>::max(), &overlapped)) { reason = GetErrorReason(); return false; @@ -9,6 +9,7 @@ #include <cstdio> #include <filesystem> +#include <functional> #include <iomanip> #include <ios> #include <ostream> @@ -68,7 +69,11 @@ public: static inline path u8path(const std::string& utf8_str) { +#if __cplusplus < 202002L return std::filesystem::u8path(utf8_str); +#else + return std::filesystem::path(std::u8string{utf8_str.begin(), utf8_str.end()}); +#endif } // Disallow implicit std::string conversion for absolute to avoid @@ -199,6 +204,7 @@ bool create_directories(const std::filesystem::path& p, std::error_code& ec) = d /** Bridge operations to C stdio */ namespace fsbridge { + using FopenFn = std::function<FILE*(const fs::path&, const char*)>; FILE *fopen(const fs::path& p, const char *mode); /** diff --git a/src/hash.cpp b/src/hash.cpp index f58b29e3ba..06caaac6ee 100644 --- a/src/hash.cpp +++ b/src/hash.cpp @@ -16,7 +16,7 @@ inline uint32_t ROTL32(uint32_t x, int8_t r) unsigned int MurmurHash3(unsigned int nHashSeed, Span<const unsigned char> vDataToHash) { - // The following is MurmurHash3 (x86_32), see https://code.google.com/p/smhasher/source/browse/trunk/MurmurHash3.cpp + // The following is MurmurHash3 (x86_32), see https://github.com/aappleby/smhasher/blob/master/src/MurmurHash3.cpp uint32_t h1 = nHashSeed; const uint32_t c1 = 0xcc9e2d51; const uint32_t c2 = 0x1b873593; @@ -86,9 +86,9 @@ uint256 SHA256Uint256(const uint256& input) return result; } -CHashWriter TaggedHash(const std::string& tag) +HashWriter TaggedHash(const std::string& tag) { - CHashWriter writer(SER_GETHASH, 0); + HashWriter writer{}; uint256 taghash; CSHA256().Write((const unsigned char*)tag.data(), tag.size()).Finalize(taghash.begin()); writer << taghash << taghash; diff --git a/src/hash.h b/src/hash.h index 0ccef2105f..b1ff3acc7d 100644 --- a/src/hash.h +++ b/src/hash.h @@ -96,20 +96,12 @@ inline uint160 Hash160(const T1& in1) } /** A writer stream (for serialization) that computes a 256-bit hash. */ -class CHashWriter +class HashWriter { private: CSHA256 ctx; - const int nType; - const int nVersion; public: - - CHashWriter(int nTypeIn, int nVersionIn) : nType(nTypeIn), nVersion(nVersionIn) {} - - int GetType() const { return nType; } - int GetVersion() const { return nVersion; } - void write(Span<const std::byte> src) { ctx.Write(UCharCast(src.data()), src.size()); @@ -144,6 +136,26 @@ public: return ReadLE64(result.begin()); } + template <typename T> + HashWriter& operator<<(const T& obj) + { + ::Serialize(*this, obj); + return *this; + } +}; + +class CHashWriter : public HashWriter +{ +private: + const int nType; + const int nVersion; + +public: + CHashWriter(int nTypeIn, int nVersionIn) : nType(nTypeIn), nVersion(nVersionIn) {} + + int GetType() const { return nType; } + int GetVersion() const { return nVersion; } + template<typename T> CHashWriter& operator<<(const T& obj) { // Serialize to this stream @@ -203,12 +215,12 @@ unsigned int MurmurHash3(unsigned int nHashSeed, Span<const unsigned char> vData void BIP32Hash(const ChainCode &chainCode, unsigned int nChild, unsigned char header, const unsigned char data[32], unsigned char output[64]); -/** Return a CHashWriter primed for tagged hashes (as specified in BIP 340). +/** Return a HashWriter primed for tagged hashes (as specified in BIP 340). * * The returned object will have SHA256(tag) written to it twice (= 64 bytes). * A tagged hash can be computed by feeding the message into this object, and - * then calling CHashWriter::GetSHA256(). + * then calling HashWriter::GetSHA256(). */ -CHashWriter TaggedHash(const std::string& tag); +HashWriter TaggedHash(const std::string& tag); #endif // BITCOIN_HASH_H diff --git a/src/headerssync.cpp b/src/headerssync.cpp new file mode 100644 index 0000000000..757b942cd9 --- /dev/null +++ b/src/headerssync.cpp @@ -0,0 +1,317 @@ +// Copyright (c) 2022 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include <headerssync.h> +#include <logging.h> +#include <pow.h> +#include <timedata.h> +#include <util/check.h> + +// The two constants below are computed using the simulation script on +// https://gist.github.com/sipa/016ae445c132cdf65a2791534dfb7ae1 + +//! Store a commitment to a header every HEADER_COMMITMENT_PERIOD blocks. +constexpr size_t HEADER_COMMITMENT_PERIOD{584}; + +//! Only feed headers to validation once this many headers on top have been +//! received and validated against commitments. +constexpr size_t REDOWNLOAD_BUFFER_SIZE{13959}; // 13959/584 = ~23.9 commitments + +// Our memory analysis assumes 48 bytes for a CompressedHeader (so we should +// re-calculate parameters if we compress further) +static_assert(sizeof(CompressedHeader) == 48); + +HeadersSyncState::HeadersSyncState(NodeId id, const Consensus::Params& consensus_params, + const CBlockIndex* chain_start, const arith_uint256& minimum_required_work) : + m_id(id), m_consensus_params(consensus_params), + m_chain_start(chain_start), + m_minimum_required_work(minimum_required_work), + m_current_chain_work(chain_start->nChainWork), + m_commit_offset(GetRand<unsigned>(HEADER_COMMITMENT_PERIOD)), + m_last_header_received(m_chain_start->GetBlockHeader()), + m_current_height(chain_start->nHeight) +{ + // Estimate the number of blocks that could possibly exist on the peer's + // chain *right now* using 6 blocks/second (fastest blockrate given the MTP + // rule) times the number of seconds from the last allowed block until + // today. This serves as a memory bound on how many commitments we might + // store from this peer, and we can safely give up syncing if the peer + // exceeds this bound, because it's not possible for a consensus-valid + // chain to be longer than this (at the current time -- in the future we + // could try again, if necessary, to sync a longer chain). + m_max_commitments = 6*(Ticks<std::chrono::seconds>(GetAdjustedTime() - NodeSeconds{std::chrono::seconds{chain_start->GetMedianTimePast()}}) + MAX_FUTURE_BLOCK_TIME) / HEADER_COMMITMENT_PERIOD; + + LogPrint(BCLog::NET, "Initial headers sync started with peer=%d: height=%i, max_commitments=%i, min_work=%s\n", m_id, m_current_height, m_max_commitments, m_minimum_required_work.ToString()); +} + +/** Free any memory in use, and mark this object as no longer usable. This is + * required to guarantee that we won't reuse this object with the same + * SaltedTxidHasher for another sync. */ +void HeadersSyncState::Finalize() +{ + Assume(m_download_state != State::FINAL); + m_header_commitments = {}; + m_last_header_received.SetNull(); + m_redownloaded_headers = {}; + m_redownload_buffer_last_hash.SetNull(); + m_redownload_buffer_first_prev_hash.SetNull(); + m_process_all_remaining_headers = false; + m_current_height = 0; + + m_download_state = State::FINAL; +} + +/** Process the next batch of headers received from our peer. + * Validate and store commitments, and compare total chainwork to our target to + * see if we can switch to REDOWNLOAD mode. */ +HeadersSyncState::ProcessingResult HeadersSyncState::ProcessNextHeaders(const + std::vector<CBlockHeader>& received_headers, const bool full_headers_message) +{ + ProcessingResult ret; + + Assume(!received_headers.empty()); + if (received_headers.empty()) return ret; + + Assume(m_download_state != State::FINAL); + if (m_download_state == State::FINAL) return ret; + + if (m_download_state == State::PRESYNC) { + // During PRESYNC, we minimally validate block headers and + // occasionally add commitments to them, until we reach our work + // threshold (at which point m_download_state is updated to REDOWNLOAD). + ret.success = ValidateAndStoreHeadersCommitments(received_headers); + if (ret.success) { + if (full_headers_message || m_download_state == State::REDOWNLOAD) { + // A full headers message means the peer may have more to give us; + // also if we just switched to REDOWNLOAD then we need to re-request + // headers from the beginning. + ret.request_more = true; + } else { + Assume(m_download_state == State::PRESYNC); + // If we're in PRESYNC and we get a non-full headers + // message, then the peer's chain has ended and definitely doesn't + // have enough work, so we can stop our sync. + LogPrint(BCLog::NET, "Initial headers sync aborted with peer=%d: incomplete headers message at height=%i (presync phase)\n", m_id, m_current_height); + } + } + } else if (m_download_state == State::REDOWNLOAD) { + // During REDOWNLOAD, we compare our stored commitments to what we + // receive, and add headers to our redownload buffer. When the buffer + // gets big enough (meaning that we've checked enough commitments), + // we'll return a batch of headers to the caller for processing. + ret.success = true; + for (const auto& hdr : received_headers) { + if (!ValidateAndStoreRedownloadedHeader(hdr)) { + // Something went wrong -- the peer gave us an unexpected chain. + // We could consider looking at the reason for failure and + // punishing the peer, but for now just give up on sync. + ret.success = false; + break; + } + } + + if (ret.success) { + // Return any headers that are ready for acceptance. + ret.pow_validated_headers = PopHeadersReadyForAcceptance(); + + // If we hit our target blockhash, then all remaining headers will be + // returned and we can clear any leftover internal state. + if (m_redownloaded_headers.empty() && m_process_all_remaining_headers) { + LogPrint(BCLog::NET, "Initial headers sync complete with peer=%d: releasing all at height=%i (redownload phase)\n", m_id, m_redownload_buffer_last_height); + } else if (full_headers_message) { + // If the headers message is full, we need to request more. + ret.request_more = true; + } else { + // For some reason our peer gave us a high-work chain, but is now + // declining to serve us that full chain again. Give up. + // Note that there's no more processing to be done with these + // headers, so we can still return success. + LogPrint(BCLog::NET, "Initial headers sync aborted with peer=%d: incomplete headers message at height=%i (redownload phase)\n", m_id, m_redownload_buffer_last_height); + } + } + } + + if (!(ret.success && ret.request_more)) Finalize(); + return ret; +} + +bool HeadersSyncState::ValidateAndStoreHeadersCommitments(const std::vector<CBlockHeader>& headers) +{ + // The caller should not give us an empty set of headers. + Assume(headers.size() > 0); + if (headers.size() == 0) return true; + + Assume(m_download_state == State::PRESYNC); + if (m_download_state != State::PRESYNC) return false; + + if (headers[0].hashPrevBlock != m_last_header_received.GetHash()) { + // Somehow our peer gave us a header that doesn't connect. + // This might be benign -- perhaps our peer reorged away from the chain + // they were on. Give up on this sync for now (likely we will start a + // new sync with a new starting point). + LogPrint(BCLog::NET, "Initial headers sync aborted with peer=%d: non-continuous headers at height=%i (presync phase)\n", m_id, m_current_height); + return false; + } + + // If it does connect, (minimally) validate and occasionally store + // commitments. + for (const auto& hdr : headers) { + if (!ValidateAndProcessSingleHeader(hdr)) { + return false; + } + } + + if (m_current_chain_work >= m_minimum_required_work) { + m_redownloaded_headers.clear(); + m_redownload_buffer_last_height = m_chain_start->nHeight; + m_redownload_buffer_first_prev_hash = m_chain_start->GetBlockHash(); + m_redownload_buffer_last_hash = m_chain_start->GetBlockHash(); + m_redownload_chain_work = m_chain_start->nChainWork; + m_download_state = State::REDOWNLOAD; + LogPrint(BCLog::NET, "Initial headers sync transition with peer=%d: reached sufficient work at height=%i, redownloading from height=%i\n", m_id, m_current_height, m_redownload_buffer_last_height); + } + return true; +} + +bool HeadersSyncState::ValidateAndProcessSingleHeader(const CBlockHeader& current) +{ + Assume(m_download_state == State::PRESYNC); + if (m_download_state != State::PRESYNC) return false; + + int next_height = m_current_height + 1; + + // Verify that the difficulty isn't growing too fast; an adversary with + // limited hashing capability has a greater chance of producing a high + // work chain if they compress the work into as few blocks as possible, + // so don't let anyone give a chain that would violate the difficulty + // adjustment maximum. + if (!PermittedDifficultyTransition(m_consensus_params, next_height, + m_last_header_received.nBits, current.nBits)) { + LogPrint(BCLog::NET, "Initial headers sync aborted with peer=%d: invalid difficulty transition at height=%i (presync phase)\n", m_id, next_height); + return false; + } + + if (next_height % HEADER_COMMITMENT_PERIOD == m_commit_offset) { + // Add a commitment. + m_header_commitments.push_back(m_hasher(current.GetHash()) & 1); + if (m_header_commitments.size() > m_max_commitments) { + // The peer's chain is too long; give up. + // It's possible the chain grew since we started the sync; so + // potentially we could succeed in syncing the peer's chain if we + // try again later. + LogPrint(BCLog::NET, "Initial headers sync aborted with peer=%d: exceeded max commitments at height=%i (presync phase)\n", m_id, next_height); + return false; + } + } + + m_current_chain_work += GetBlockProof(CBlockIndex(current)); + m_last_header_received = current; + m_current_height = next_height; + + return true; +} + +bool HeadersSyncState::ValidateAndStoreRedownloadedHeader(const CBlockHeader& header) +{ + Assume(m_download_state == State::REDOWNLOAD); + if (m_download_state != State::REDOWNLOAD) return false; + + int64_t next_height = m_redownload_buffer_last_height + 1; + + // Ensure that we're working on a header that connects to the chain we're + // downloading. + if (header.hashPrevBlock != m_redownload_buffer_last_hash) { + LogPrint(BCLog::NET, "Initial headers sync aborted with peer=%d: non-continuous headers at height=%i (redownload phase)\n", m_id, next_height); + return false; + } + + // Check that the difficulty adjustments are within our tolerance: + uint32_t previous_nBits{0}; + if (!m_redownloaded_headers.empty()) { + previous_nBits = m_redownloaded_headers.back().nBits; + } else { + previous_nBits = m_chain_start->nBits; + } + + if (!PermittedDifficultyTransition(m_consensus_params, next_height, + previous_nBits, header.nBits)) { + LogPrint(BCLog::NET, "Initial headers sync aborted with peer=%d: invalid difficulty transition at height=%i (redownload phase)\n", m_id, next_height); + return false; + } + + // Track work on the redownloaded chain + m_redownload_chain_work += GetBlockProof(CBlockIndex(header)); + + if (m_redownload_chain_work >= m_minimum_required_work) { + m_process_all_remaining_headers = true; + } + + // If we're at a header for which we previously stored a commitment, verify + // it is correct. Failure will result in aborting download. + // Also, don't check commitments once we've gotten to our target blockhash; + // it's possible our peer has extended its chain between our first sync and + // our second, and we don't want to return failure after we've seen our + // target blockhash just because we ran out of commitments. + if (!m_process_all_remaining_headers && next_height % HEADER_COMMITMENT_PERIOD == m_commit_offset) { + if (m_header_commitments.size() == 0) { + LogPrint(BCLog::NET, "Initial headers sync aborted with peer=%d: commitment overrun at height=%i (redownload phase)\n", m_id, next_height); + // Somehow our peer managed to feed us a different chain and + // we've run out of commitments. + return false; + } + bool commitment = m_hasher(header.GetHash()) & 1; + bool expected_commitment = m_header_commitments.front(); + m_header_commitments.pop_front(); + if (commitment != expected_commitment) { + LogPrint(BCLog::NET, "Initial headers sync aborted with peer=%d: commitment mismatch at height=%i (redownload phase)\n", m_id, next_height); + return false; + } + } + + // Store this header for later processing. + m_redownloaded_headers.push_back(header); + m_redownload_buffer_last_height = next_height; + m_redownload_buffer_last_hash = header.GetHash(); + + return true; +} + +std::vector<CBlockHeader> HeadersSyncState::PopHeadersReadyForAcceptance() +{ + std::vector<CBlockHeader> ret; + + Assume(m_download_state == State::REDOWNLOAD); + if (m_download_state != State::REDOWNLOAD) return ret; + + while (m_redownloaded_headers.size() > REDOWNLOAD_BUFFER_SIZE || + (m_redownloaded_headers.size() > 0 && m_process_all_remaining_headers)) { + ret.emplace_back(m_redownloaded_headers.front().GetFullHeader(m_redownload_buffer_first_prev_hash)); + m_redownloaded_headers.pop_front(); + m_redownload_buffer_first_prev_hash = ret.back().GetHash(); + } + return ret; +} + +CBlockLocator HeadersSyncState::NextHeadersRequestLocator() const +{ + Assume(m_download_state != State::FINAL); + if (m_download_state == State::FINAL) return {}; + + auto chain_start_locator = LocatorEntries(m_chain_start); + std::vector<uint256> locator; + + if (m_download_state == State::PRESYNC) { + // During pre-synchronization, we continue from the last header received. + locator.push_back(m_last_header_received.GetHash()); + } + + if (m_download_state == State::REDOWNLOAD) { + // During redownload, we will download from the last received header that we stored. + locator.push_back(m_redownload_buffer_last_hash); + } + + locator.insert(locator.end(), chain_start_locator.begin(), chain_start_locator.end()); + + return CBlockLocator{std::move(locator)}; +} diff --git a/src/headerssync.h b/src/headerssync.h new file mode 100644 index 0000000000..16da964246 --- /dev/null +++ b/src/headerssync.h @@ -0,0 +1,277 @@ +// Copyright (c) 2022 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_HEADERSSYNC_H +#define BITCOIN_HEADERSSYNC_H + +#include <arith_uint256.h> +#include <chain.h> +#include <consensus/params.h> +#include <net.h> // For NodeId +#include <primitives/block.h> +#include <uint256.h> +#include <util/bitdeque.h> +#include <util/hasher.h> + +#include <deque> +#include <vector> + +// A compressed CBlockHeader, which leaves out the prevhash +struct CompressedHeader { + // header + int32_t nVersion{0}; + uint256 hashMerkleRoot; + uint32_t nTime{0}; + uint32_t nBits{0}; + uint32_t nNonce{0}; + + CompressedHeader() + { + hashMerkleRoot.SetNull(); + } + + CompressedHeader(const CBlockHeader& header) + { + nVersion = header.nVersion; + hashMerkleRoot = header.hashMerkleRoot; + nTime = header.nTime; + nBits = header.nBits; + nNonce = header.nNonce; + } + + CBlockHeader GetFullHeader(const uint256& hash_prev_block) { + CBlockHeader ret; + ret.nVersion = nVersion; + ret.hashPrevBlock = hash_prev_block; + ret.hashMerkleRoot = hashMerkleRoot; + ret.nTime = nTime; + ret.nBits = nBits; + ret.nNonce = nNonce; + return ret; + }; +}; + +/** HeadersSyncState: + * + * We wish to download a peer's headers chain in a DoS-resistant way. + * + * The Bitcoin protocol does not offer an easy way to determine the work on a + * peer's chain. Currently, we can query a peer's headers by using a GETHEADERS + * message, and our peer can return a set of up to 2000 headers that connect to + * something we know. If a peer's chain has more than 2000 blocks, then we need + * a way to verify that the chain actually has enough work on it to be useful to + * us -- by being above our anti-DoS minimum-chain-work threshold -- before we + * commit to storing those headers in memory. Otherwise, it would be cheap for + * an attacker to waste all our memory by serving us low-work headers + * (particularly for a new node coming online for the first time). + * + * To prevent memory-DoS with low-work headers, while still always being + * able to reorg to whatever the most-work chain is, we require that a chain + * meet a work threshold before committing it to memory. We can do this by + * downloading a peer's headers twice, whenever we are not sure that the chain + * has sufficient work: + * + * - In the first download phase, called pre-synchronization, we can calculate + * the work on the chain as we go (just by checking the nBits value on each + * header, and validating the proof-of-work). + * + * - Once we have reached a header where the cumulative chain work is + * sufficient, we switch to downloading the headers a second time, this time + * processing them fully, and possibly storing them in memory. + * + * To prevent an attacker from using (eg) the honest chain to convince us that + * they have a high-work chain, but then feeding us an alternate set of + * low-difficulty headers in the second phase, we store commitments to the + * chain we see in the first download phase that we check in the second phase, + * as follows: + * + * - In phase 1 (presync), store 1 bit (using a salted hash function) for every + * N headers that we see. With a reasonable choice of N, this uses relatively + * little memory even for a very long chain. + * + * - In phase 2 (redownload), keep a lookahead buffer and only accept headers + * from that buffer into the block index (permanent memory usage) once they + * have some target number of verified commitments on top of them. With this + * parametrization, we can achieve a given security target for potential + * permanent memory usage, while choosing N to minimize memory use during the + * sync (temporary, per-peer storage). + */ + +class HeadersSyncState { +public: + ~HeadersSyncState() {} + + enum class State { + /** PRESYNC means the peer has not yet demonstrated their chain has + * sufficient work and we're only building commitments to the chain they + * serve us. */ + PRESYNC, + /** REDOWNLOAD means the peer has given us a high-enough-work chain, + * and now we're redownloading the headers we saw before and trying to + * accept them */ + REDOWNLOAD, + /** We're done syncing with this peer and can discard any remaining state */ + FINAL + }; + + /** Return the current state of our download */ + State GetState() const { return m_download_state; } + + /** Return the height reached during the PRESYNC phase */ + int64_t GetPresyncHeight() const { return m_current_height; } + + /** Return the block timestamp of the last header received during the PRESYNC phase. */ + uint32_t GetPresyncTime() const { return m_last_header_received.nTime; } + + /** Return the amount of work in the chain received during the PRESYNC phase. */ + arith_uint256 GetPresyncWork() const { return m_current_chain_work; } + + /** Construct a HeadersSyncState object representing a headers sync via this + * download-twice mechanism). + * + * id: node id (for logging) + * consensus_params: parameters needed for difficulty adjustment validation + * chain_start: best known fork point that the peer's headers branch from + * minimum_required_work: amount of chain work required to accept the chain + */ + HeadersSyncState(NodeId id, const Consensus::Params& consensus_params, + const CBlockIndex* chain_start, const arith_uint256& minimum_required_work); + + /** Result data structure for ProcessNextHeaders. */ + struct ProcessingResult { + std::vector<CBlockHeader> pow_validated_headers; + bool success{false}; + bool request_more{false}; + }; + + /** Process a batch of headers, once a sync via this mechanism has started + * + * received_headers: headers that were received over the network for processing. + * Assumes the caller has already verified the headers + * are continuous, and has checked that each header + * satisfies the proof-of-work target included in the + * header (but not necessarily verified that the + * proof-of-work target is correct and passes consensus + * rules). + * full_headers_message: true if the message was at max capacity, + * indicating more headers may be available + * ProcessingResult.pow_validated_headers: will be filled in with any + * headers that the caller can fully process and + * validate now (because these returned headers are + * on a chain with sufficient work) + * ProcessingResult.success: set to false if an error is detected and the sync is + * aborted; true otherwise. + * ProcessingResult.request_more: if true, the caller is suggested to call + * NextHeadersRequestLocator and send a getheaders message using it. + */ + ProcessingResult ProcessNextHeaders(const std::vector<CBlockHeader>& + received_headers, bool full_headers_message); + + /** Issue the next GETHEADERS message to our peer. + * + * This will return a locator appropriate for the current sync object, to continue the + * synchronization phase it is in. + */ + CBlockLocator NextHeadersRequestLocator() const; + +private: + /** Clear out all download state that might be in progress (freeing any used + * memory), and mark this object as no longer usable. + */ + void Finalize(); + + /** + * Only called in PRESYNC. + * Validate the work on the headers we received from the network, and + * store commitments for later. Update overall state with successfully + * processed headers. + * On failure, this invokes Finalize() and returns false. + */ + bool ValidateAndStoreHeadersCommitments(const std::vector<CBlockHeader>& headers); + + /** In PRESYNC, process and update state for a single header */ + bool ValidateAndProcessSingleHeader(const CBlockHeader& current); + + /** In REDOWNLOAD, check a header's commitment (if applicable) and add to + * buffer for later processing */ + bool ValidateAndStoreRedownloadedHeader(const CBlockHeader& header); + + /** Return a set of headers that satisfy our proof-of-work threshold */ + std::vector<CBlockHeader> PopHeadersReadyForAcceptance(); + +private: + /** NodeId of the peer (used for log messages) **/ + const NodeId m_id; + + /** We use the consensus params in our anti-DoS calculations */ + const Consensus::Params& m_consensus_params; + + /** Store the last block in our block index that the peer's chain builds from */ + const CBlockIndex* m_chain_start{nullptr}; + + /** Minimum work that we're looking for on this chain. */ + const arith_uint256 m_minimum_required_work; + + /** Work that we've seen so far on the peer's chain */ + arith_uint256 m_current_chain_work; + + /** m_hasher is a salted hasher for making our 1-bit commitments to headers we've seen. */ + const SaltedTxidHasher m_hasher; + + /** A queue of commitment bits, created during the 1st phase, and verified during the 2nd. */ + bitdeque<> m_header_commitments; + + /** The (secret) offset on the heights for which to create commitments. + * + * m_header_commitments entries are created at any height h for which + * (h % HEADER_COMMITMENT_PERIOD) == m_commit_offset. */ + const unsigned m_commit_offset; + + /** m_max_commitments is a bound we calculate on how long an honest peer's chain could be, + * given the MTP rule. + * + * Any peer giving us more headers than this will have its sync aborted. This serves as a + * memory bound on m_header_commitments. */ + uint64_t m_max_commitments{0}; + + /** Store the latest header received while in PRESYNC (initialized to m_chain_start) */ + CBlockHeader m_last_header_received; + + /** Height of m_last_header_received */ + int64_t m_current_height{0}; + + /** During phase 2 (REDOWNLOAD), we buffer redownloaded headers in memory + * until enough commitments have been verified; those are stored in + * m_redownloaded_headers */ + std::deque<CompressedHeader> m_redownloaded_headers; + + /** Height of last header in m_redownloaded_headers */ + int64_t m_redownload_buffer_last_height{0}; + + /** Hash of last header in m_redownloaded_headers (initialized to + * m_chain_start). We have to cache it because we don't have hashPrevBlock + * available in a CompressedHeader. + */ + uint256 m_redownload_buffer_last_hash; + + /** The hashPrevBlock entry for the first header in m_redownloaded_headers + * We need this to reconstruct the full header when it's time for + * processing. + */ + uint256 m_redownload_buffer_first_prev_hash; + + /** The accumulated work on the redownloaded chain. */ + arith_uint256 m_redownload_chain_work; + + /** Set this to true once we encounter the target blockheader during phase + * 2 (REDOWNLOAD). At this point, we can process and store all remaining + * headers still in m_redownloaded_headers. + */ + bool m_process_all_remaining_headers{false}; + + /** Current state of our headers sync. */ + State m_download_state{State::PRESYNC}; +}; + +#endif // BITCOIN_HEADERSSYNC_H diff --git a/src/httpserver.cpp b/src/httpserver.cpp index 44937dc523..1a19555f76 100644 --- a/src/httpserver.cpp +++ b/src/httpserver.cpp @@ -9,9 +9,9 @@ #include <httpserver.h> #include <chainparamsbase.h> -#include <compat.h> +#include <compat/compat.h> #include <netbase.h> -#include <node/ui_interface.h> +#include <node/interface_ui.h> #include <rpc/protocol.h> // For HTTP status codes #include <shutdown.h> #include <sync.h> @@ -21,11 +21,11 @@ #include <util/threadnames.h> #include <util/translation.h> +#include <cstdio> +#include <cstdlib> #include <deque> #include <memory> #include <optional> -#include <stdio.h> -#include <stdlib.h> #include <string> #include <sys/types.h> @@ -142,7 +142,8 @@ static std::vector<CSubNet> rpc_allow_subnets; //! Work queue for handling longer requests off the event loop thread static std::unique_ptr<WorkQueue<HTTPClosure>> g_work_queue{nullptr}; //! Handlers for (sub)paths -static std::vector<HTTPPathHandler> pathHandlers; +static GlobalMutex g_httppathhandlers_mutex; +static std::vector<HTTPPathHandler> pathHandlers GUARDED_BY(g_httppathhandlers_mutex); //! Bound listening sockets static std::vector<evhttp_bound_socket *> boundSockets; @@ -243,6 +244,7 @@ static void http_request_cb(struct evhttp_request* req, void* arg) // Find registered handler for prefix std::string strURI = hreq->GetURI(); std::string path; + LOCK(g_httppathhandlers_mutex); std::vector<HTTPPathHandler>::const_iterator i = pathHandlers.begin(); std::vector<HTTPPathHandler>::const_iterator iend = pathHandlers.end(); for (; i != iend; ++i) { @@ -318,7 +320,7 @@ static bool HTTPBindAddresses(struct evhttp* http) // Bind addresses for (std::vector<std::pair<std::string, uint16_t> >::iterator i = endpoints.begin(); i != endpoints.end(); ++i) { - LogPrint(BCLog::HTTP, "Binding RPC on address %s port %i\n", i->first, i->second); + LogPrintf("Binding RPC on address %s port %i\n", i->first, i->second); evhttp_bound_socket *bind_handle = evhttp_bind_socket_with_handle(http, i->first.empty() ? nullptr : i->first.c_str(), i->second); if (bind_handle) { CNetAddr addr; @@ -400,7 +402,7 @@ bool InitHTTPServer() LogPrint(BCLog::HTTP, "Initialized HTTP server\n"); int workQueueDepth = std::max((long)gArgs.GetIntArg("-rpcworkqueue", DEFAULT_HTTP_WORKQUEUE), 1L); - LogPrintf("HTTP: creating work queue of depth %d\n", workQueueDepth); + LogPrintfCategory(BCLog::HTTP, "creating work queue of depth %d\n", workQueueDepth); g_work_queue = std::make_unique<WorkQueue<HTTPClosure>>(workQueueDepth); // transfer ownership to eventBase/HTTP via .release() @@ -424,7 +426,7 @@ void StartHTTPServer() { LogPrint(BCLog::HTTP, "Starting HTTP server\n"); int rpcThreads = std::max((long)gArgs.GetIntArg("-rpcthreads", DEFAULT_HTTP_THREADS), 1L); - LogPrintf("HTTP: starting %d worker threads\n", rpcThreads); + LogPrintfCategory(BCLog::HTTP, "starting %d worker threads\n", rpcThreads); g_thread_http = std::thread(ThreadHTTP, eventBase); for (int i = 0; i < rpcThreads; i++) { @@ -674,11 +676,13 @@ std::optional<std::string> GetQueryParameterFromUri(const char* uri, const std:: void RegisterHTTPHandler(const std::string &prefix, bool exactMatch, const HTTPRequestHandler &handler) { LogPrint(BCLog::HTTP, "Registering HTTP handler for %s (exactmatch %d)\n", prefix, exactMatch); + LOCK(g_httppathhandlers_mutex); pathHandlers.push_back(HTTPPathHandler(prefix, exactMatch, handler)); } void UnregisterHTTPHandler(const std::string &prefix, bool exactMatch) { + LOCK(g_httppathhandlers_mutex); std::vector<HTTPPathHandler>::iterator i = pathHandlers.begin(); std::vector<HTTPPathHandler>::iterator iend = pathHandlers.end(); for (; i != iend; ++i) diff --git a/src/i2p.cpp b/src/i2p.cpp index 9b8f83caef..28be8009dc 100644 --- a/src/i2p.cpp +++ b/src/i2p.cpp @@ -3,7 +3,7 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include <chainparams.h> -#include <compat.h> +#include <compat/compat.h> #include <compat/endian.h> #include <crypto/sha256.h> #include <fs.h> @@ -12,11 +12,11 @@ #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/strencodings.h> #include <util/system.h> #include <chrono> @@ -115,8 +115,19 @@ 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)) + : m_private_key_file{private_key_file}, + m_control_host{control_host}, + m_interrupt{interrupt}, + m_control_sock{std::make_unique<Sock>(INVALID_SOCKET)}, + m_transient{false} +{ +} + +Session::Session(const CService& control_host, CThreadInterrupt* interrupt) + : m_control_host{control_host}, + m_interrupt{interrupt}, + m_control_sock{std::make_unique<Sock>(INVALID_SOCKET)}, + m_transient{true} { } @@ -150,8 +161,8 @@ bool Session::Accept(Connection& conn) throw std::runtime_error("wait on socket failed"); } - if ((occurred & Sock::RECV) == 0) { - // Timeout, no incoming connections within MAX_WAIT_FOR_IO. + if (occurred == 0) { + // Timeout, no incoming connections or errors within MAX_WAIT_FOR_IO. continue; } @@ -314,6 +325,7 @@ 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. + // If SIGNATURE_TYPE is not specified, then the default one is DSA_SHA1. const Reply& reply = SendRequestAndGetReply(sock, "DEST GENERATE SIGNATURE_TYPE=7", false); m_private_key = DecodeI2PBase64(reply.Get("PRIV")); @@ -355,29 +367,47 @@ void Session::CreateIfNotCreatedAlready() return; } - Log("Creating SAM session with %s", m_control_host.ToString()); + const auto session_type = m_transient ? "transient" : "persistent"; + const auto session_id = GetRandHash().GetHex().substr(0, 10); // full is overkill, too verbose in the logs + + Log("Creating %s SAM session %s with %s", session_type, session_id, m_control_host.ToString()); auto sock = Hello(); - const auto& [read_ok, data] = ReadBinaryFile(m_private_key_file); - if (read_ok) { - m_private_key.assign(data.begin(), data.end()); + if (m_transient) { + // The destination (private key) is generated upon session creation and returned + // in the reply in DESTINATION=. + const Reply& reply = SendRequestAndGetReply( + *sock, + strprintf("SESSION CREATE STYLE=STREAM ID=%s DESTINATION=TRANSIENT SIGNATURE_TYPE=7", session_id)); + + m_private_key = DecodeI2PBase64(reply.Get("DESTINATION")); } else { - GenerateAndSavePrivateKey(*sock); - } + // Read our persistent destination (private key) from disk or generate + // one and save it to disk. Then use it when creating the session. + 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)); + 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)); + SendRequestAndGetReply(*sock, + strprintf("SESSION CREATE STYLE=STREAM ID=%s DESTINATION=%s", + session_id, + private_key_b64)); + } m_my_addr = CService(DestBinToAddr(MyDestination()), I2P_SAM31_PORT); 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()); + Log("%s SAM session %s created, my address=%s", + Capitalize(session_type), + m_session_id, + m_my_addr.ToString()); } std::unique_ptr<Sock> Session::StreamAccept() @@ -405,12 +435,12 @@ void Session::Disconnect() { if (m_control_sock->Get() != INVALID_SOCKET) { if (m_session_id.empty()) { - Log("Destroying incomplete session"); + Log("Destroying incomplete SAM session"); } else { - Log("Destroying session %s", m_session_id); + Log("Destroying SAM session %s", m_session_id); } } - m_control_sock->Reset(); + m_control_sock = std::make_unique<Sock>(INVALID_SOCKET); m_session_id.clear(); } } // namespace sam @@ -5,7 +5,7 @@ #ifndef BITCOIN_I2P_H #define BITCOIN_I2P_H -#include <compat.h> +#include <compat/compat.h> #include <fs.h> #include <netaddress.h> #include <sync.h> @@ -71,6 +71,19 @@ public: CThreadInterrupt* interrupt); /** + * Construct a transient session which will generate its own I2P private key + * rather than read the one from disk (it will not be saved on disk either and + * will be lost once this object is destroyed). This will not initiate any IO, + * the session will be lazily created later when first used. + * @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 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 @@ -262,6 +275,12 @@ private: * SAM session id. */ std::string m_session_id GUARDED_BY(m_mutex); + + /** + * Whether this is a transient session (the I2P private key will not be + * read or written to disk). + */ + const bool m_transient; }; } // namespace sam diff --git a/src/index/base.cpp b/src/index/base.cpp index 9f0c1dea24..3eea09b17d 100644 --- a/src/index/base.cpp +++ b/src/index/base.cpp @@ -4,16 +4,23 @@ #include <chainparams.h> #include <index/base.h> +#include <interfaces/chain.h> +#include <kernel/chain.h> #include <node/blockstorage.h> -#include <node/ui_interface.h> +#include <node/context.h> +#include <node/interface_ui.h> #include <shutdown.h> #include <tinyformat.h> #include <util/syscall_sandbox.h> +#include <util/system.h> #include <util/thread.h> #include <util/translation.h> #include <validation.h> // For g_chainman #include <warnings.h> +#include <string> +#include <utility> + using node::ReadBlockFromDisk; constexpr uint8_t DB_BEST_BLOCK{'B'}; @@ -31,6 +38,15 @@ static void FatalError(const char* fmt, const Args&... args) StartShutdown(); } +CBlockLocator GetLocator(interfaces::Chain& chain, const uint256& block_hash) +{ + CBlockLocator locator; + bool found = chain.findBlock(block_hash, interfaces::FoundBlock().locator(locator)); + assert(found); + assert(!locator.IsNull()); + return locator; +} + BaseIndex::DB::DB(const fs::path& path, size_t n_cache_size, bool f_memory, bool f_wipe, bool f_obfuscate) : CDBWrapper(path, n_cache_size, f_memory, f_wipe, f_obfuscate) {} @@ -49,6 +65,9 @@ void BaseIndex::DB::WriteBestBlock(CDBBatch& batch, const CBlockLocator& locator batch.Write(DB_BEST_BLOCK, locator); } +BaseIndex::BaseIndex(std::unique_ptr<interfaces::Chain> chain, std::string name) + : m_chain{std::move(chain)}, m_name{std::move(name)} {} + BaseIndex::~BaseIndex() { Interrupt(); @@ -69,6 +88,10 @@ bool BaseIndex::Init() } else { SetBestBlockIndex(m_chainstate->FindForkInGlobalIndex(locator)); } + + // Note: this will latch to true immediately if the user starts up with an empty + // datadir and an index enabled. If this is the case, indexation will happen solely + // via `BlockConnected` signals until, possibly, the next restart. m_synced = m_best_block_index.load() == active_chain.Tip(); if (!m_synced) { bool prune_violation = false; @@ -175,12 +198,15 @@ void BaseIndex::ThreadSync() } CBlock block; + interfaces::BlockInfo block_info = kernel::MakeBlockInfo(pindex); if (!ReadBlockFromDisk(block, pindex, consensus_params)) { FatalError("%s: Failed to read block %s from disk", __func__, pindex->GetBlockHash().ToString()); return; + } else { + block_info.data = █ } - if (!WriteBlock(block, pindex)) { + if (!CustomAppend(block_info)) { FatalError("%s: Failed to write block %s to index database", __func__, pindex->GetBlockHash().ToString()); return; @@ -197,22 +223,20 @@ void BaseIndex::ThreadSync() bool BaseIndex::Commit() { - CDBBatch batch(GetDB()); - if (!CommitInternal(batch) || !GetDB().WriteBatch(batch)) { - return error("%s: Failed to commit latest %s state", __func__, GetName()); - } - return true; -} - -bool BaseIndex::CommitInternal(CDBBatch& batch) -{ - LOCK(cs_main); // Don't commit anything if we haven't indexed any block yet // (this could happen if init is interrupted). - if (m_best_block_index == nullptr) { - return false; + bool ok = m_best_block_index != nullptr; + if (ok) { + CDBBatch batch(GetDB()); + ok = CustomCommit(batch); + if (ok) { + GetDB().WriteBestBlock(batch, GetLocator(*m_chain, m_best_block_index.load()->GetBlockHash())); + ok = GetDB().WriteBatch(batch); + } + } + if (!ok) { + return error("%s: Failed to commit latest %s state", __func__, GetName()); } - GetDB().WriteBestBlock(batch, m_chainstate->m_chain.GetLocator(m_best_block_index)); return true; } @@ -221,6 +245,10 @@ bool BaseIndex::Rewind(const CBlockIndex* current_tip, const CBlockIndex* new_ti assert(current_tip == m_best_block_index); assert(current_tip->GetAncestor(new_tip->nHeight) == new_tip); + if (!CustomRewind({current_tip->GetBlockHash(), current_tip->nHeight}, {new_tip->GetBlockHash(), new_tip->nHeight})) { + return false; + } + // In the case of a reorg, ensure persisted block locator is not stale. // Pruning has a minimum of 288 blocks-to-keep and getting the index // out of sync may be possible but a users fault. @@ -268,8 +296,12 @@ void BaseIndex::BlockConnected(const std::shared_ptr<const CBlock>& block, const return; } } - - if (WriteBlock(*block, pindex)) { + interfaces::BlockInfo block_info = kernel::MakeBlockInfo(pindex, block.get()); + if (CustomAppend(block_info)) { + // Setting the best block index is intentionally the last step of this + // function, so BlockUntilSyncedToCurrentChain callers waiting for the + // best block index to be updated can rely on the block being fully + // processed, and the index object being safe to delete. SetBestBlockIndex(pindex); } else { FatalError("%s: Failed to write block %s to index", @@ -346,13 +378,18 @@ void BaseIndex::Interrupt() m_interrupt(); } -bool BaseIndex::Start(CChainState& active_chainstate) +bool BaseIndex::Start() { - m_chainstate = &active_chainstate; + // m_chainstate member gives indexing code access to node internals. It is + // removed in followup https://github.com/bitcoin/bitcoin/pull/24230 + m_chainstate = &m_chain->context()->chainman->ActiveChainstate(); // Need to register this ValidationInterface before running Init(), so that // callbacks are not missed if Init sets m_synced to true. RegisterValidationInterface(this); - if (!Init()) { + if (!Init()) return false; + + const CBlockIndex* index = m_best_block_index.load(); + if (!CustomInit(index ? std::make_optional(interfaces::BlockKey{index->GetBlockHash(), index->nHeight}) : std::nullopt)) { return false; } @@ -381,10 +418,17 @@ IndexSummary BaseIndex::GetSummary() const void BaseIndex::SetBestBlockIndex(const CBlockIndex* block) { assert(!node::fPruneMode || AllowPrune()); - m_best_block_index = block; if (AllowPrune() && block) { node::PruneLockInfo prune_lock; prune_lock.height_first = block->nHeight; WITH_LOCK(::cs_main, m_chainstate->m_blockman.UpdatePruneLock(GetName(), prune_lock)); } + + // Intentionally set m_best_block_index as the last step in this function, + // after updating prune locks above, and after making any other references + // to *this, so the BlockUntilSyncedToCurrentChain function (which checks + // m_best_block_index as an optimization) can be used to wait for the last + // BlockConnected notification and safely assume that prune locks are + // updated and that the index object is safe to delete. + m_best_block_index = block; } diff --git a/src/index/base.h b/src/index/base.h index a8f6a18c8d..349178a535 100644 --- a/src/index/base.h +++ b/src/index/base.h @@ -6,12 +6,18 @@ #define BITCOIN_INDEX_BASE_H #include <dbwrapper.h> +#include <interfaces/chain.h> #include <threadinterrupt.h> #include <validationinterface.h> +#include <string> + class CBlock; class CBlockIndex; -class CChainState; +class Chainstate; +namespace interfaces { +class Chain; +} // namespace interfaces struct IndexSummary { std::string name; @@ -51,6 +57,10 @@ private: /// Whether the index is in sync with the main chain. The flag is flipped /// from false to true once, after which point this starts processing /// ValidationInterface notifications to stay in sync. + /// + /// Note that this will latch to true *immediately* upon startup if + /// `m_chainstate->m_chain` is empty, which will be the case upon startup + /// with an empty datadir if, e.g., `-txindex=1` is specified. std::atomic<bool> m_synced{false}; /// The last block in the chain that the index is in sync with. @@ -59,6 +69,9 @@ private: std::thread m_thread_sync; CThreadInterrupt m_interrupt; + /// Read best block locator and check that data needed to sync has not been pruned. + bool Init(); + /// Sync the index with the block index starting from the current best block. /// Intended to be run in its own thread, m_thread_sync, and can be /// interrupted with m_interrupt. Once the index gets in sync, the m_synced @@ -76,40 +89,44 @@ private: /// getting corrupted. bool Commit(); + /// Loop over disconnected blocks and call CustomRewind. + bool Rewind(const CBlockIndex* current_tip, const CBlockIndex* new_tip); + virtual bool AllowPrune() const = 0; protected: - CChainState* m_chainstate{nullptr}; + std::unique_ptr<interfaces::Chain> m_chain; + Chainstate* m_chainstate{nullptr}; + const std::string m_name; void BlockConnected(const std::shared_ptr<const CBlock>& block, const CBlockIndex* pindex) override; void ChainStateFlushed(const CBlockLocator& locator) override; - const CBlockIndex* CurrentIndex() { return m_best_block_index.load(); }; - /// Initialize internal state from the database and block index. - [[nodiscard]] virtual bool Init(); + [[nodiscard]] virtual bool CustomInit(const std::optional<interfaces::BlockKey>& block) { return true; } /// Write update index entries for a newly connected block. - virtual bool WriteBlock(const CBlock& block, const CBlockIndex* pindex) { return true; } + [[nodiscard]] virtual bool CustomAppend(const interfaces::BlockInfo& block) { return true; } /// Virtual method called internally by Commit that can be overridden to atomically /// commit more index state. - virtual bool CommitInternal(CDBBatch& batch); + virtual bool CustomCommit(CDBBatch& batch) { return true; } /// Rewind index to an earlier chain tip during a chain reorg. The tip must /// be an ancestor of the current best block. - virtual bool Rewind(const CBlockIndex* current_tip, const CBlockIndex* new_tip); + [[nodiscard]] virtual bool CustomRewind(const interfaces::BlockKey& current_tip, const interfaces::BlockKey& new_tip) { return true; } virtual DB& GetDB() const = 0; /// Get the name of the index for display in logs. - virtual const char* GetName() const = 0; + const std::string& GetName() const LIFETIMEBOUND { return m_name; } /// Update the internal best block index as well as the prune lock. void SetBestBlockIndex(const CBlockIndex* block); public: + BaseIndex(std::unique_ptr<interfaces::Chain> chain, std::string name); /// Destructor interrupts sync thread if running and blocks until it exits. virtual ~BaseIndex(); @@ -124,7 +141,7 @@ public: /// Start initializes the sync state and registers the instance as a /// ValidationInterface so that it stays in sync with blockchain updates. - [[nodiscard]] bool Start(CChainState& active_chainstate); + [[nodiscard]] bool Start(); /// Stops the instance from staying in sync with blockchain updates. void Stop(); diff --git a/src/index/blockfilterindex.cpp b/src/index/blockfilterindex.cpp index c92b8c7e19..292e11c874 100644 --- a/src/index/blockfilterindex.cpp +++ b/src/index/blockfilterindex.cpp @@ -5,9 +5,11 @@ #include <map> #include <dbwrapper.h> +#include <hash.h> #include <index/blockfilterindex.h> #include <node/blockstorage.h> #include <util/system.h> +#include <validation.h> using node::UndoReadFromDisk; @@ -93,9 +95,10 @@ struct DBHashKey { static std::map<BlockFilterType, BlockFilterIndex> g_filter_indexes; -BlockFilterIndex::BlockFilterIndex(BlockFilterType filter_type, +BlockFilterIndex::BlockFilterIndex(std::unique_ptr<interfaces::Chain> chain, BlockFilterType filter_type, size_t n_cache_size, bool f_memory, bool f_wipe) - : m_filter_type(filter_type) + : BaseIndex(std::move(chain), BlockFilterTypeName(filter_type) + " block filter index") + , m_filter_type(filter_type) { const std::string& filter_name = BlockFilterTypeName(filter_type); if (filter_name.empty()) throw std::invalid_argument("unknown filter_type"); @@ -103,12 +106,11 @@ BlockFilterIndex::BlockFilterIndex(BlockFilterType filter_type, fs::path path = gArgs.GetDataDirNet() / "indexes" / "blockfilter" / fs::u8path(filter_name); fs::create_directories(path); - m_name = filter_name + " block filter index"; 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() +bool BlockFilterIndex::CustomInit(const std::optional<interfaces::BlockKey>& block) { if (!m_db->Read(DB_FILTER_POS, m_next_filter_pos)) { // Check that the cause of the read failure is that the key does not exist. Any other errors @@ -123,15 +125,15 @@ bool BlockFilterIndex::Init() m_next_filter_pos.nFile = 0; m_next_filter_pos.nPos = 0; } - return BaseIndex::Init(); + return true; } -bool BlockFilterIndex::CommitInternal(CDBBatch& batch) +bool BlockFilterIndex::CustomCommit(CDBBatch& batch) { const FlatFilePos& pos = m_next_filter_pos; // Flush current filter file to disk. - CAutoFile file(m_filter_fileseq->Open(pos), SER_DISK, CLIENT_VERSION); + AutoFile file{m_filter_fileseq->Open(pos)}; if (file.IsNull()) { return error("%s: Failed to open filter file %d", __func__, pos.nFile); } @@ -140,21 +142,25 @@ bool BlockFilterIndex::CommitInternal(CDBBatch& batch) } batch.Write(DB_FILTER_POS, pos); - return BaseIndex::CommitInternal(batch); + return true; } -bool BlockFilterIndex::ReadFilterFromDisk(const FlatFilePos& pos, BlockFilter& filter) const +bool BlockFilterIndex::ReadFilterFromDisk(const FlatFilePos& pos, const uint256& hash, BlockFilter& filter) const { - CAutoFile filein(m_filter_fileseq->Open(pos, true), SER_DISK, CLIENT_VERSION); + AutoFile filein{m_filter_fileseq->Open(pos, true)}; if (filein.IsNull()) { return false; } + // Check that the hash of the encoded_filter matches the one stored in the db. uint256 block_hash; std::vector<uint8_t> encoded_filter; try { filein >> block_hash >> encoded_filter; - filter = BlockFilter(GetFilterType(), block_hash, std::move(encoded_filter)); + uint256 result; + CHash256().Write(encoded_filter).Finalize(result); + if (result != hash) return error("Checksum mismatch in filter decode."); + filter = BlockFilter(GetFilterType(), block_hash, std::move(encoded_filter), /*skip_decode_check=*/true); } catch (const std::exception& e) { return error("%s: Failed to deserialize block filter from disk: %s", __func__, e.what()); @@ -173,7 +179,7 @@ size_t BlockFilterIndex::WriteFilterToDisk(FlatFilePos& pos, const BlockFilter& // If writing the filter would overflow the file, flush and move to the next one. if (pos.nPos + data_size > MAX_FLTR_FILE_SIZE) { - CAutoFile last_file(m_filter_fileseq->Open(pos), SER_DISK, CLIENT_VERSION); + AutoFile last_file{m_filter_fileseq->Open(pos)}; if (last_file.IsNull()) { LogPrintf("%s: Failed to open filter file %d\n", __func__, pos.nFile); return 0; @@ -199,7 +205,7 @@ size_t BlockFilterIndex::WriteFilterToDisk(FlatFilePos& pos, const BlockFilter& return 0; } - CAutoFile fileout(m_filter_fileseq->Open(pos), SER_DISK, CLIENT_VERSION); + AutoFile fileout{m_filter_fileseq->Open(pos)}; if (fileout.IsNull()) { LogPrintf("%s: Failed to open filter file %d\n", __func__, pos.nFile); return 0; @@ -209,22 +215,25 @@ size_t BlockFilterIndex::WriteFilterToDisk(FlatFilePos& pos, const BlockFilter& return data_size; } -bool BlockFilterIndex::WriteBlock(const CBlock& block, const CBlockIndex* pindex) +bool BlockFilterIndex::CustomAppend(const interfaces::BlockInfo& block) { CBlockUndo block_undo; uint256 prev_header; - if (pindex->nHeight > 0) { + if (block.height > 0) { + // pindex variable gives indexing code access to node internals. It + // will be removed in upcoming commit + const CBlockIndex* pindex = WITH_LOCK(cs_main, return m_chainstate->m_blockman.LookupBlockIndex(block.hash)); if (!UndoReadFromDisk(block_undo, pindex)) { return false; } std::pair<uint256, DBVal> read_out; - if (!m_db->Read(DBHeightKey(pindex->nHeight - 1), read_out)) { + if (!m_db->Read(DBHeightKey(block.height - 1), read_out)) { return false; } - uint256 expected_block_hash = pindex->pprev->GetBlockHash(); + uint256 expected_block_hash = *Assert(block.prev_hash); if (read_out.first != expected_block_hash) { return error("%s: previous block header belongs to unexpected block %s; expected %s", __func__, read_out.first.ToString(), expected_block_hash.ToString()); @@ -233,18 +242,18 @@ bool BlockFilterIndex::WriteBlock(const CBlock& block, const CBlockIndex* pindex prev_header = read_out.second.header; } - BlockFilter filter(m_filter_type, block, block_undo); + BlockFilter filter(m_filter_type, *Assert(block.data), block_undo); size_t bytes_written = WriteFilterToDisk(m_next_filter_pos, filter); if (bytes_written == 0) return false; std::pair<uint256, DBVal> value; - value.first = pindex->GetBlockHash(); + value.first = block.hash; value.second.hash = filter.GetHash(); value.second.header = filter.ComputeHeader(prev_header); value.second.pos = m_next_filter_pos; - if (!m_db->Write(DBHeightKey(pindex->nHeight), value)) { + if (!m_db->Write(DBHeightKey(block.height), value)) { return false; } @@ -278,17 +287,15 @@ static bool CopyHeightIndexToHashIndex(CDBIterator& db_it, CDBBatch& batch, return true; } -bool BlockFilterIndex::Rewind(const CBlockIndex* current_tip, const CBlockIndex* new_tip) +bool BlockFilterIndex::CustomRewind(const interfaces::BlockKey& current_tip, const interfaces::BlockKey& new_tip) { - assert(current_tip->GetAncestor(new_tip->nHeight) == new_tip); - CDBBatch batch(*m_db); std::unique_ptr<CDBIterator> db_it(m_db->NewIterator()); // During a reorg, we need to copy all filters for blocks that are getting disconnected from the // height index to the hash index so we can still find them when the height index entries are // overwritten. - if (!CopyHeightIndexToHashIndex(*db_it, batch, m_name, new_tip->nHeight, current_tip->nHeight)) { + if (!CopyHeightIndexToHashIndex(*db_it, batch, m_name, new_tip.height, current_tip.height)) { return false; } @@ -298,7 +305,7 @@ bool BlockFilterIndex::Rewind(const CBlockIndex* current_tip, const CBlockIndex* batch.Write(DB_FILTER_POS, m_next_filter_pos); if (!m_db->WriteBatch(batch)) return false; - return BaseIndex::Rewind(current_tip, new_tip); + return true; } static bool LookupOne(const CDBWrapper& db, const CBlockIndex* block_index, DBVal& result) @@ -381,7 +388,7 @@ bool BlockFilterIndex::LookupFilter(const CBlockIndex* block_index, BlockFilter& return false; } - return ReadFilterFromDisk(entry.pos, filter_out); + return ReadFilterFromDisk(entry.pos, entry.hash, filter_out); } bool BlockFilterIndex::LookupFilterHeader(const CBlockIndex* block_index, uint256& header_out) @@ -425,7 +432,7 @@ bool BlockFilterIndex::LookupFilterRange(int start_height, const CBlockIndex* st filters_out.resize(entries.size()); auto filter_pos_it = filters_out.begin(); for (const auto& entry : entries) { - if (!ReadFilterFromDisk(entry.pos, *filter_pos_it)) { + if (!ReadFilterFromDisk(entry.pos, entry.hash, *filter_pos_it)) { return false; } ++filter_pos_it; @@ -462,12 +469,12 @@ void ForEachBlockFilterIndex(std::function<void (BlockFilterIndex&)> fn) for (auto& entry : g_filter_indexes) fn(entry.second); } -bool InitBlockFilterIndex(BlockFilterType filter_type, +bool InitBlockFilterIndex(std::function<std::unique_ptr<interfaces::Chain>()> make_chain, BlockFilterType filter_type, size_t n_cache_size, bool f_memory, bool f_wipe) { auto result = g_filter_indexes.emplace(std::piecewise_construct, std::forward_as_tuple(filter_type), - std::forward_as_tuple(filter_type, + std::forward_as_tuple(make_chain(), filter_type, n_cache_size, f_memory, f_wipe)); return result.second; } diff --git a/src/index/blockfilterindex.h b/src/index/blockfilterindex.h index 6deff59000..9e69388dc8 100644 --- a/src/index/blockfilterindex.h +++ b/src/index/blockfilterindex.h @@ -5,12 +5,15 @@ #ifndef BITCOIN_INDEX_BLOCKFILTERINDEX_H #define BITCOIN_INDEX_BLOCKFILTERINDEX_H +#include <attributes.h> #include <blockfilter.h> #include <chain.h> #include <flatfile.h> #include <index/base.h> #include <util/hasher.h> +static const char* const DEFAULT_BLOCKFILTERINDEX = "0"; + /** Interval between compact filter checkpoints. See BIP 157. */ static constexpr int CFCHECKPT_INTERVAL = 1000; @@ -25,13 +28,12 @@ class BlockFilterIndex final : public BaseIndex { private: BlockFilterType m_filter_type; - std::string m_name; std::unique_ptr<BaseIndex::DB> m_db; FlatFilePos m_next_filter_pos; std::unique_ptr<FlatFileSeq> m_filter_fileseq; - bool ReadFilterFromDisk(const FlatFilePos& pos, BlockFilter& filter) const; + bool ReadFilterFromDisk(const FlatFilePos& pos, const uint256& hash, BlockFilter& filter) const; size_t WriteFilterToDisk(FlatFilePos& pos, const BlockFilter& filter); Mutex m_cs_headers_cache; @@ -41,21 +43,19 @@ private: bool AllowPrune() const override { return true; } protected: - bool Init() override; - - bool CommitInternal(CDBBatch& batch) override; + bool CustomInit(const std::optional<interfaces::BlockKey>& block) override; - bool WriteBlock(const CBlock& block, const CBlockIndex* pindex) override; + bool CustomCommit(CDBBatch& batch) override; - bool Rewind(const CBlockIndex* current_tip, const CBlockIndex* new_tip) override; + bool CustomAppend(const interfaces::BlockInfo& block) override; - BaseIndex::DB& GetDB() const override { return *m_db; } + bool CustomRewind(const interfaces::BlockKey& current_tip, const interfaces::BlockKey& new_tip) override; - const char* GetName() const override { return m_name.c_str(); } + BaseIndex::DB& GetDB() const LIFETIMEBOUND override { return *m_db; } public: /** Constructs the index, which becomes available to be queried. */ - explicit BlockFilterIndex(BlockFilterType filter_type, + explicit BlockFilterIndex(std::unique_ptr<interfaces::Chain> chain, BlockFilterType filter_type, size_t n_cache_size, bool f_memory = false, bool f_wipe = false); BlockFilterType GetFilterType() const { return m_filter_type; } @@ -88,7 +88,7 @@ void ForEachBlockFilterIndex(std::function<void (BlockFilterIndex&)> fn); * Initialize a block filter index for the given type if one does not already exist. Returns true if * a new index is created and false if one has already been initialized. */ -bool InitBlockFilterIndex(BlockFilterType filter_type, +bool InitBlockFilterIndex(std::function<std::unique_ptr<interfaces::Chain>()> make_chain, BlockFilterType filter_type, size_t n_cache_size, bool f_memory = false, bool f_wipe = false); /** diff --git a/src/index/coinstatsindex.cpp b/src/index/coinstatsindex.cpp index 687e330fe0..d3559b1b75 100644 --- a/src/index/coinstatsindex.cpp +++ b/src/index/coinstatsindex.cpp @@ -6,10 +6,12 @@ #include <coins.h> #include <crypto/muhash.h> #include <index/coinstatsindex.h> +#include <kernel/coinstats.h> #include <node/blockstorage.h> #include <serialize.h> #include <txdb.h> #include <undo.h> +#include <util/system.h> #include <validation.h> using kernel::CCoinsStats; @@ -102,7 +104,8 @@ struct DBHashKey { std::unique_ptr<CoinStatsIndex> g_coin_stats_index; -CoinStatsIndex::CoinStatsIndex(size_t n_cache_size, bool f_memory, bool f_wipe) +CoinStatsIndex::CoinStatsIndex(std::unique_ptr<interfaces::Chain> chain, size_t n_cache_size, bool f_memory, bool f_wipe) + : BaseIndex(std::move(chain), "coinstatsindex") { fs::path path{gArgs.GetDataDirNet() / "indexes" / "coinstats"}; fs::create_directories(path); @@ -110,24 +113,27 @@ CoinStatsIndex::CoinStatsIndex(size_t n_cache_size, bool f_memory, bool f_wipe) m_db = std::make_unique<CoinStatsIndex::DB>(path / "db", n_cache_size, f_memory, f_wipe); } -bool CoinStatsIndex::WriteBlock(const CBlock& block, const CBlockIndex* pindex) +bool CoinStatsIndex::CustomAppend(const interfaces::BlockInfo& block) { CBlockUndo block_undo; - const CAmount block_subsidy{GetBlockSubsidy(pindex->nHeight, Params().GetConsensus())}; + const CAmount block_subsidy{GetBlockSubsidy(block.height, Params().GetConsensus())}; m_total_subsidy += block_subsidy; // Ignore genesis block - if (pindex->nHeight > 0) { + if (block.height > 0) { + // pindex variable gives indexing code access to node internals. It + // will be removed in upcoming commit + const CBlockIndex* pindex = WITH_LOCK(cs_main, return m_chainstate->m_blockman.LookupBlockIndex(block.hash)); if (!UndoReadFromDisk(block_undo, pindex)) { return false; } std::pair<uint256, DBVal> read_out; - if (!m_db->Read(DBHeightKey(pindex->nHeight - 1), read_out)) { + if (!m_db->Read(DBHeightKey(block.height - 1), read_out)) { return false; } - uint256 expected_block_hash{pindex->pprev->GetBlockHash()}; + uint256 expected_block_hash{*Assert(block.prev_hash)}; if (read_out.first != expected_block_hash) { LogPrintf("WARNING: previous block header belongs to unexpected block %s; expected %s\n", read_out.first.ToString(), expected_block_hash.ToString()); @@ -139,12 +145,13 @@ bool CoinStatsIndex::WriteBlock(const CBlock& block, const CBlockIndex* pindex) } // TODO: Deduplicate BIP30 related code - bool is_bip30_block{(pindex->nHeight == 91722 && pindex->GetBlockHash() == uint256S("0x00000000000271a2dc26e7667f8419f2e15416dc6955e5a6c6cdf3f2574dd08e")) || - (pindex->nHeight == 91812 && pindex->GetBlockHash() == uint256S("0x00000000000af0aed4792b1acee3d966af36cf5def14935db8de83d6f9306f2f"))}; + bool is_bip30_block{(block.height == 91722 && block.hash == uint256S("0x00000000000271a2dc26e7667f8419f2e15416dc6955e5a6c6cdf3f2574dd08e")) || + (block.height == 91812 && block.hash == uint256S("0x00000000000af0aed4792b1acee3d966af36cf5def14935db8de83d6f9306f2f"))}; // Add the new utxos created from the block - for (size_t i = 0; i < block.vtx.size(); ++i) { - const auto& tx{block.vtx.at(i)}; + assert(block.data); + for (size_t i = 0; i < block.data->vtx.size(); ++i) { + const auto& tx{block.data->vtx.at(i)}; // Skip duplicate txid coinbase transactions (BIP30). if (is_bip30_block && tx->IsCoinBase()) { @@ -155,7 +162,7 @@ bool CoinStatsIndex::WriteBlock(const CBlock& block, const CBlockIndex* pindex) for (uint32_t j = 0; j < tx->vout.size(); ++j) { const CTxOut& out{tx->vout[j]}; - Coin coin{out, pindex->nHeight, tx->IsCoinBase()}; + Coin coin{out, block.height, tx->IsCoinBase()}; COutPoint outpoint{tx->GetHash(), j}; // Skip unspendable coins @@ -211,7 +218,7 @@ bool CoinStatsIndex::WriteBlock(const CBlock& block, const CBlockIndex* pindex) m_total_unspendables_unclaimed_rewards += unclaimed_rewards; std::pair<uint256, DBVal> value; - value.first = pindex->GetBlockHash(); + value.first = block.hash; value.second.transaction_output_count = m_transaction_output_count; value.second.bogo_size = m_bogo_size; value.second.total_amount = m_total_amount; @@ -231,7 +238,7 @@ bool CoinStatsIndex::WriteBlock(const CBlock& block, const CBlockIndex* pindex) // Intentionally do not update DB_MUHASH here so it stays in sync with // DB_BEST_BLOCK, and the index is not corrupted if there is an unclean shutdown. - return m_db->Write(DBHeightKey(pindex->nHeight), value); + return m_db->Write(DBHeightKey(block.height), value); } static bool CopyHeightIndexToHashIndex(CDBIterator& db_it, CDBBatch& batch, @@ -260,17 +267,15 @@ static bool CopyHeightIndexToHashIndex(CDBIterator& db_it, CDBBatch& batch, return true; } -bool CoinStatsIndex::Rewind(const CBlockIndex* current_tip, const CBlockIndex* new_tip) +bool CoinStatsIndex::CustomRewind(const interfaces::BlockKey& current_tip, const interfaces::BlockKey& new_tip) { - assert(current_tip->GetAncestor(new_tip->nHeight) == new_tip); - CDBBatch batch(*m_db); std::unique_ptr<CDBIterator> db_it(m_db->NewIterator()); // During a reorg, we need to copy all hash digests for blocks that are // getting disconnected from the height index to the hash index so we can // still find them when the height index entries are overwritten. - if (!CopyHeightIndexToHashIndex(*db_it, batch, m_name, new_tip->nHeight, current_tip->nHeight)) { + if (!CopyHeightIndexToHashIndex(*db_it, batch, m_name, new_tip.height, current_tip.height)) { return false; } @@ -278,7 +283,8 @@ bool CoinStatsIndex::Rewind(const CBlockIndex* current_tip, const CBlockIndex* n { LOCK(cs_main); - const CBlockIndex* iter_tip{m_chainstate->m_blockman.LookupBlockIndex(current_tip->GetBlockHash())}; + const CBlockIndex* iter_tip{m_chainstate->m_blockman.LookupBlockIndex(current_tip.hash)}; + const CBlockIndex* new_tip_index{m_chainstate->m_blockman.LookupBlockIndex(new_tip.hash)}; const auto& consensus_params{Params().GetConsensus()}; do { @@ -292,38 +298,38 @@ bool CoinStatsIndex::Rewind(const CBlockIndex* current_tip, const CBlockIndex* n ReverseBlock(block, iter_tip); iter_tip = iter_tip->GetAncestor(iter_tip->nHeight - 1); - } while (new_tip != iter_tip); + } while (new_tip_index != iter_tip); } - return BaseIndex::Rewind(current_tip, new_tip); + return true; } -static bool LookUpOne(const CDBWrapper& db, const CBlockIndex* block_index, DBVal& result) +static bool LookUpOne(const CDBWrapper& db, const interfaces::BlockKey& block, DBVal& result) { // First check if the result is stored under the height index and the value // there matches the block hash. This should be the case if the block is on // the active chain. std::pair<uint256, DBVal> read_out; - if (!db.Read(DBHeightKey(block_index->nHeight), read_out)) { + if (!db.Read(DBHeightKey(block.height), read_out)) { return false; } - if (read_out.first == block_index->GetBlockHash()) { + if (read_out.first == block.hash) { result = std::move(read_out.second); return true; } // If value at the height index corresponds to an different block, the // result will be stored in the hash index. - return db.Read(DBHashKey(block_index->GetBlockHash()), result); + return db.Read(DBHashKey(block.hash), result); } -std::optional<CCoinsStats> CoinStatsIndex::LookUpStats(const CBlockIndex* block_index) const +std::optional<CCoinsStats> CoinStatsIndex::LookUpStats(const CBlockIndex& block_index) const { - CCoinsStats stats{Assert(block_index)->nHeight, block_index->GetBlockHash()}; + CCoinsStats stats{block_index.nHeight, block_index.GetBlockHash()}; stats.index_used = true; DBVal entry; - if (!LookUpOne(*m_db, block_index, entry)) { + if (!LookUpOne(*m_db, {block_index.GetBlockHash(), block_index.nHeight}, entry)) { return std::nullopt; } @@ -344,7 +350,7 @@ std::optional<CCoinsStats> CoinStatsIndex::LookUpStats(const CBlockIndex* block_ return stats; } -bool CoinStatsIndex::Init() +bool CoinStatsIndex::CustomInit(const std::optional<interfaces::BlockKey>& block) { if (!m_db->Read(DB_MUHASH, m_muhash)) { // Check that the cause of the read failure is that the key does not @@ -356,13 +362,9 @@ bool CoinStatsIndex::Init() } } - if (!BaseIndex::Init()) return false; - - const CBlockIndex* pindex{CurrentIndex()}; - - if (pindex) { + if (block) { DBVal entry; - if (!LookUpOne(*m_db, pindex, entry)) { + if (!LookUpOne(*m_db, *block, entry)) { return error("%s: Cannot read current %s state; index may be corrupted", __func__, GetName()); } @@ -391,12 +393,12 @@ bool CoinStatsIndex::Init() return true; } -bool CoinStatsIndex::CommitInternal(CDBBatch& batch) +bool CoinStatsIndex::CustomCommit(CDBBatch& batch) { // DB_MUHASH should always be committed in a batch together with DB_BEST_BLOCK // to prevent an inconsistent state of the DB. batch.Write(DB_MUHASH, m_muhash); - return BaseIndex::CommitInternal(batch); + return true; } // Reverse a single block as part of a reorg diff --git a/src/index/coinstatsindex.h b/src/index/coinstatsindex.h index cae052d913..aa0d7f9fd5 100644 --- a/src/index/coinstatsindex.h +++ b/src/index/coinstatsindex.h @@ -5,11 +5,16 @@ #ifndef BITCOIN_INDEX_COINSTATSINDEX_H #define BITCOIN_INDEX_COINSTATSINDEX_H -#include <chain.h> #include <crypto/muhash.h> -#include <flatfile.h> #include <index/base.h> -#include <kernel/coinstats.h> + +class CBlockIndex; +class CDBBatch; +namespace kernel { +struct CCoinsStats; +} + +static constexpr bool DEFAULT_COINSTATSINDEX{false}; /** * CoinStatsIndex maintains statistics on the UTXO set. @@ -17,7 +22,6 @@ class CoinStatsIndex final : public BaseIndex { private: - std::string m_name; std::unique_ptr<BaseIndex::DB> m_db; MuHash3072 m_muhash; @@ -39,24 +43,22 @@ private: bool AllowPrune() const override { return true; } protected: - bool Init() override; + bool CustomInit(const std::optional<interfaces::BlockKey>& block) override; - bool CommitInternal(CDBBatch& batch) override; + bool CustomCommit(CDBBatch& batch) override; - bool WriteBlock(const CBlock& block, const CBlockIndex* pindex) override; + bool CustomAppend(const interfaces::BlockInfo& block) override; - bool Rewind(const CBlockIndex* current_tip, const CBlockIndex* new_tip) override; + bool CustomRewind(const interfaces::BlockKey& current_tip, const interfaces::BlockKey& new_tip) override; BaseIndex::DB& GetDB() const override { return *m_db; } - const char* GetName() const override { return "coinstatsindex"; } - public: // Constructs the index, which becomes available to be queried. - explicit CoinStatsIndex(size_t n_cache_size, bool f_memory = false, bool f_wipe = false); + explicit CoinStatsIndex(std::unique_ptr<interfaces::Chain> chain, size_t n_cache_size, bool f_memory = false, bool f_wipe = false); // Look up stats for a specific block using CBlockIndex - std::optional<kernel::CCoinsStats> LookUpStats(const CBlockIndex* block_index) const; + std::optional<kernel::CCoinsStats> LookUpStats(const CBlockIndex& block_index) const; }; /// The global UTXO set hash object. diff --git a/src/index/txindex.cpp b/src/index/txindex.cpp index 97c11c4383..a4fe1b611e 100644 --- a/src/index/txindex.cpp +++ b/src/index/txindex.cpp @@ -48,23 +48,22 @@ bool TxIndex::DB::WriteTxs(const std::vector<std::pair<uint256, CDiskTxPos>>& v_ return WriteBatch(batch); } -TxIndex::TxIndex(size_t n_cache_size, bool f_memory, bool f_wipe) - : m_db(std::make_unique<TxIndex::DB>(n_cache_size, f_memory, f_wipe)) +TxIndex::TxIndex(std::unique_ptr<interfaces::Chain> chain, size_t n_cache_size, bool f_memory, bool f_wipe) + : BaseIndex(std::move(chain), "txindex"), m_db(std::make_unique<TxIndex::DB>(n_cache_size, f_memory, f_wipe)) {} TxIndex::~TxIndex() = default; -bool TxIndex::WriteBlock(const CBlock& block, const CBlockIndex* pindex) +bool TxIndex::CustomAppend(const interfaces::BlockInfo& block) { // Exclude genesis block transaction because outputs are not spendable. - if (pindex->nHeight == 0) return true; + if (block.height == 0) return true; - CDiskTxPos pos{ - WITH_LOCK(::cs_main, return pindex->GetBlockPos()), - GetSizeOfCompactSize(block.vtx.size())}; + assert(block.data); + CDiskTxPos pos({block.file_number, block.data_pos}, GetSizeOfCompactSize(block.data->vtx.size())); std::vector<std::pair<uint256, CDiskTxPos>> vPos; - vPos.reserve(block.vtx.size()); - for (const auto& tx : block.vtx) { + vPos.reserve(block.data->vtx.size()); + for (const auto& tx : block.data->vtx) { vPos.emplace_back(tx->GetHash(), pos); pos.nTxOffset += ::GetSerializeSize(*tx, CLIENT_VERSION); } diff --git a/src/index/txindex.h b/src/index/txindex.h index ec339abaa1..4cea35045d 100644 --- a/src/index/txindex.h +++ b/src/index/txindex.h @@ -7,6 +7,8 @@ #include <index/base.h> +static constexpr bool DEFAULT_TXINDEX{false}; + /** * TxIndex is used to look up transactions included in the blockchain by hash. * The index is written to a LevelDB database and records the filesystem @@ -23,15 +25,13 @@ private: bool AllowPrune() const override { return false; } protected: - bool WriteBlock(const CBlock& block, const CBlockIndex* pindex) override; + bool CustomAppend(const interfaces::BlockInfo& block) override; BaseIndex::DB& GetDB() const override; - const char* GetName() const override { return "txindex"; } - public: /// Constructs the index, which becomes available to be queried. - explicit TxIndex(size_t n_cache_size, bool f_memory = false, bool f_wipe = false); + explicit TxIndex(std::unique_ptr<interfaces::Chain> chain, size_t n_cache_size, bool f_memory = false, bool f_wipe = false); // Destructor is declared because this class contains a unique_ptr to an incomplete type. virtual ~TxIndex() override; diff --git a/src/init.cpp b/src/init.cpp index d0fd6074b1..25b40c6c6e 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -10,6 +10,8 @@ #include <init.h> #include <kernel/checks.h> +#include <kernel/mempool_persist.h> +#include <kernel/validation_cache_sizes.h> #include <addrman.h> #include <banman.h> @@ -39,10 +41,14 @@ #include <node/caches.h> #include <node/chainstate.h> #include <node/context.h> +#include <node/interface_ui.h> +#include <node/mempool_args.h> +#include <node/mempool_persist_args.h> #include <node/miner.h> -#include <node/ui_interface.h> +#include <node/validation_cache_args.h> #include <policy/feerate.h> #include <policy/fees.h> +#include <policy/fees_args.h> #include <policy/policy.h> #include <policy/settings.h> #include <protocol.h> @@ -100,14 +106,18 @@ #include <zmq/zmqrpc.h> #endif +using kernel::DumpMempool; +using kernel::ValidationCacheSizes; + +using node::ApplyArgsManOptions; using node::CacheSizes; using node::CalculateCacheSizes; -using node::ChainstateLoadVerifyError; -using node::ChainstateLoadingError; -using node::CleanupBlockRevFiles; +using node::DEFAULT_PERSIST_MEMPOOL; using node::DEFAULT_PRINTPRIORITY; using node::DEFAULT_STOPAFTERBLOCKIMPORT; using node::LoadChainstate; +using node::MempoolPath; +using node::ShouldPersistMempool; using node::NodeContext; using node::ThreadImport; using node::VerifyLoadedChainstate; @@ -243,8 +253,8 @@ void Shutdown(NodeContext& node) node.addrman.reset(); node.netgroupman.reset(); - if (node.mempool && node.mempool->IsLoaded() && node.args->GetBoolArg("-persistmempool", DEFAULT_PERSIST_MEMPOOL)) { - DumpMempool(*node.mempool); + if (node.mempool && node.mempool->GetLoadTried() && ShouldPersistMempool(*node.args)) { + DumpMempool(*node.mempool, MempoolPath(*node.args)); } // Drop transactions we were still watching, and record fee estimations. @@ -253,7 +263,7 @@ void Shutdown(NodeContext& node) // FlushStateToDisk generates a ChainStateFlushed callback, which we should avoid missing if (node.chainman) { LOCK(cs_main); - for (CChainState* chainstate : node.chainman->GetAll()) { + for (Chainstate* chainstate : node.chainman->GetAll()) { if (chainstate->CanFlushToDisk()) { chainstate->ForceFlushStateToDisk(); } @@ -284,7 +294,7 @@ void Shutdown(NodeContext& node) if (node.chainman) { LOCK(cs_main); - for (CChainState* chainstate : node.chainman->GetAll()) { + for (Chainstate* chainstate : node.chainman->GetAll()) { if (chainstate->CanFlushToDisk()) { chainstate->ForceFlushStateToDisk(); chainstate->ResetCoinsViews(); @@ -413,9 +423,9 @@ void SetupServerArgs(ArgsManager& argsman) argsman.AddArg("-dbcache=<n>", strprintf("Maximum database cache size <n> MiB (%d to %d, default: %d). In addition, unused mempool memory is shared for this cache (see -maxmempool).", nMinDbCache, nMaxDbCache, nDefaultDbCache), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); argsman.AddArg("-includeconf=<file>", "Specify additional configuration file, relative to the -datadir path (only useable from configuration file, not command line)", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); argsman.AddArg("-loadblock=<file>", "Imports blocks from external file on startup", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); - argsman.AddArg("-maxmempool=<n>", strprintf("Keep the transaction memory pool below <n> megabytes (default: %u)", DEFAULT_MAX_MEMPOOL_SIZE), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); + argsman.AddArg("-maxmempool=<n>", strprintf("Keep the transaction memory pool below <n> megabytes (default: %u)", DEFAULT_MAX_MEMPOOL_SIZE_MB), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); argsman.AddArg("-maxorphantx=<n>", strprintf("Keep at most <n> unconnectable transactions in memory (default: %u)", DEFAULT_MAX_ORPHAN_TRANSACTIONS), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); - argsman.AddArg("-mempoolexpiry=<n>", strprintf("Do not keep transactions in the mempool longer than <n> hours (default: %u)", DEFAULT_MEMPOOL_EXPIRY), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); + argsman.AddArg("-mempoolexpiry=<n>", strprintf("Do not keep transactions in the mempool longer than <n> hours (default: %u)", DEFAULT_MEMPOOL_EXPIRY_HOURS), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); argsman.AddArg("-minimumchainwork=<hex>", strprintf("Minimum work assumed to exist on a valid chain in hex (default: %s, testnet: %s, signet: %s)", defaultChainParams->GetConsensus().nMinimumChainWork.GetHex(), testnetChainParams->GetConsensus().nMinimumChainWork.GetHex(), signetChainParams->GetConsensus().nMinimumChainWork.GetHex()), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::OPTIONS); argsman.AddArg("-par=<n>", strprintf("Set the number of script verification threads (%u to %d, 0 = auto, <0 = leave that many cores free, default: %d)", -GetNumCores(), MAX_SCRIPTCHECK_THREADS, DEFAULT_SCRIPTCHECK_THREADS), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); @@ -466,7 +476,6 @@ void SetupServerArgs(ArgsManager& argsman) argsman.AddArg("-onlynet=<net>", "Make automatic outbound connections only to network <net> (" + Join(GetNetworkNames(), ", ") + "). Inbound and manual connections are not affected by this option. It can be specified multiple times to allow multiple networks.", 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); // TODO: remove the sentence "Nodes not using ... incoming connections." once the changes from // https://github.com/bitcoin/bitcoin/pull/23542 have become widespread. argsman.AddArg("-port=<port>", strprintf("Listen for connections on <port>. Nodes not using the default ports (default: %u, testnet: %u, signet: %u, regtest: %u) are unlikely to get incoming connections. Not relevant for I2P (see doc/i2p.md).", defaultChainParams->GetDefaultPort(), testnetChainParams->GetDefaultPort(), signetChainParams->GetDefaultPort(), regtestChainParams->GetDefaultPort()), ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::CONNECTION); @@ -536,13 +545,13 @@ void SetupServerArgs(ArgsManager& argsman) argsman.AddArg("-stopafterblockimport", strprintf("Stop running after importing blocks from disk (default: %u)", DEFAULT_STOPAFTERBLOCKIMPORT), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST); argsman.AddArg("-stopatheight", strprintf("Stop running after reaching the given height in the main chain (default: %u)", DEFAULT_STOPATHEIGHT), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST); argsman.AddArg("-limitancestorcount=<n>", strprintf("Do not accept transactions if number of in-mempool ancestors is <n> or more (default: %u)", DEFAULT_ANCESTOR_LIMIT), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST); - argsman.AddArg("-limitancestorsize=<n>", strprintf("Do not accept transactions whose size with all in-mempool ancestors exceeds <n> kilobytes (default: %u)", DEFAULT_ANCESTOR_SIZE_LIMIT), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST); + argsman.AddArg("-limitancestorsize=<n>", strprintf("Do not accept transactions whose size with all in-mempool ancestors exceeds <n> kilobytes (default: %u)", DEFAULT_ANCESTOR_SIZE_LIMIT_KVB), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST); argsman.AddArg("-limitdescendantcount=<n>", strprintf("Do not accept transactions if any ancestor would have <n> or more in-mempool descendants (default: %u)", DEFAULT_DESCENDANT_LIMIT), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST); - argsman.AddArg("-limitdescendantsize=<n>", strprintf("Do not accept transactions if any ancestor would have more than <n> kilobytes of in-mempool descendants (default: %u).", DEFAULT_DESCENDANT_SIZE_LIMIT), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST); + argsman.AddArg("-limitdescendantsize=<n>", strprintf("Do not accept transactions if any ancestor would have more than <n> kilobytes of in-mempool descendants (default: %u).", DEFAULT_DESCENDANT_SIZE_LIMIT_KVB), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST); argsman.AddArg("-addrmantest", "Allows to test address relay on localhost", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST); argsman.AddArg("-capturemessages", "Capture all P2P messages to disk", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST); argsman.AddArg("-mocktime=<n>", "Replace actual time with " + UNIX_EPOCH_TIME + " (default: 0)", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST); - argsman.AddArg("-maxsigcachesize=<n>", strprintf("Limit sum of signature cache and script execution cache sizes to <n> MiB (default: %u)", DEFAULT_MAX_SIG_CACHE_SIZE), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST); + argsman.AddArg("-maxsigcachesize=<n>", strprintf("Limit sum of signature cache and script execution cache sizes to <n> MiB (default: %u)", DEFAULT_MAX_SIG_CACHE_BYTES >> 20), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST); argsman.AddArg("-maxtipage=<n>", strprintf("Maximum tip age in seconds to consider node in initial block download (default: %u)", DEFAULT_MAX_TIP_AGE), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST); argsman.AddArg("-printpriority", strprintf("Log transaction fee rate in " + CURRENCY_UNIT + "/kvB when mining blocks (default: %u)", DEFAULT_PRINTPRIORITY), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST); argsman.AddArg("-uacomment=<cmt>", "Append comment to the user agent string", ArgsManager::ALLOW_ANY, OptionsCategory::DEBUG_TEST); @@ -550,11 +559,14 @@ void SetupServerArgs(ArgsManager& argsman) SetupChainParamsBaseOptions(argsman); argsman.AddArg("-acceptnonstdtxn", strprintf("Relay and mine \"non-standard\" transactions (%sdefault: %u)", "testnet/regtest only; ", !testnetChainParams->RequireStandard()), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::NODE_RELAY); - argsman.AddArg("-incrementalrelayfee=<amt>", strprintf("Fee rate (in %s/kvB) used to define cost of relay, used for mempool limiting and BIP 125 replacement. (default: %s)", CURRENCY_UNIT, FormatMoney(DEFAULT_INCREMENTAL_RELAY_FEE)), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::NODE_RELAY); + argsman.AddArg("-incrementalrelayfee=<amt>", strprintf("Fee rate (in %s/kvB) used to define cost of relay, used for mempool limiting and replacement policy. (default: %s)", CURRENCY_UNIT, FormatMoney(DEFAULT_INCREMENTAL_RELAY_FEE)), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::NODE_RELAY); argsman.AddArg("-dustrelayfee=<amt>", strprintf("Fee rate (in %s/kvB) used to define dust, the value of an output such that it will cost more than its value in fees at this fee rate to spend it. (default: %s)", CURRENCY_UNIT, FormatMoney(DUST_RELAY_TX_FEE)), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::NODE_RELAY); argsman.AddArg("-bytespersigop", strprintf("Equivalent bytes per sigop in transactions for relay and mining (default: %u)", DEFAULT_BYTES_PER_SIGOP), ArgsManager::ALLOW_ANY, OptionsCategory::NODE_RELAY); argsman.AddArg("-datacarrier", strprintf("Relay and mine data carrier transactions (default: %u)", DEFAULT_ACCEPT_DATACARRIER), ArgsManager::ALLOW_ANY, OptionsCategory::NODE_RELAY); argsman.AddArg("-datacarriersize", strprintf("Maximum size of data in data carrier transactions we relay and mine (default: %u)", MAX_OP_RETURN_RELAY), ArgsManager::ALLOW_ANY, OptionsCategory::NODE_RELAY); + argsman.AddArg("-mempoolfullrbf", strprintf("Accept transaction replace-by-fee without requiring replaceability signaling (default: %u)", DEFAULT_MEMPOOL_FULL_RBF), ArgsManager::ALLOW_ANY, OptionsCategory::NODE_RELAY); + argsman.AddArg("-permitbaremultisig", strprintf("Relay non-P2SH multisig (default: %u)", DEFAULT_PERMIT_BAREMULTISIG), ArgsManager::ALLOW_ANY, + OptionsCategory::NODE_RELAY); argsman.AddArg("-minrelaytxfee=<amt>", strprintf("Fees (in %s/kvB) smaller than this are considered zero fee for relaying, mining and transaction creation (default: %s)", CURRENCY_UNIT, FormatMoney(DEFAULT_MIN_RELAY_TX_FEE)), ArgsManager::ALLOW_ANY, OptionsCategory::NODE_RELAY); argsman.AddArg("-whitelistforcerelay", strprintf("Add 'forcerelay' permission to whitelisted inbound peers with default permissions. This will relay transactions even if the transactions were already in the mempool. (default: %d)", DEFAULT_WHITELISTFORCERELAY), ArgsManager::ALLOW_ANY, OptionsCategory::NODE_RELAY); @@ -599,7 +611,7 @@ void SetupServerArgs(ArgsManager& argsman) } static bool fHaveGenesis = false; -static Mutex g_genesis_wait_mutex; +static GlobalMutex g_genesis_wait_mutex; static std::condition_variable g_genesis_wait_cv; static void BlockNotifyGenesisWait(const CBlockIndex* pBlockIndex) @@ -712,6 +724,16 @@ void InitParameterInteraction(ArgsManager& args) if (args.SoftSetBoolArg("-whitelistrelay", true)) LogPrintf("%s: parameter interaction: -whitelistforcerelay=1 -> setting -whitelistrelay=1\n", __func__); } + if (args.IsArgSet("-onlynet")) { + const auto onlynets = args.GetArgs("-onlynet"); + bool clearnet_reachable = std::any_of(onlynets.begin(), onlynets.end(), [](const auto& net) { + const auto n = ParseNetwork(net); + return n == NET_IPV4 || n == NET_IPV6; + }); + if (!clearnet_reachable && args.SoftSetBoolArg("-dnsseed", false)) { + LogPrintf("%s: parameter interaction: -onlynet excludes IPv4 and IPv6 -> setting -dnsseed=0\n", __func__); + } + } } /** @@ -731,7 +753,7 @@ namespace { // Variables internal to initialization process only int nMaxConnections; int nUserMaxConnections; int nFD; -ServiceFlags nLocalServices = ServiceFlags(NODE_NETWORK | NODE_NETWORK_LIMITED | NODE_WITNESS); +ServiceFlags nLocalServices = ServiceFlags(NODE_NETWORK_LIMITED | NODE_WITNESS); int64_t peer_connect_timeout; std::set<BlockFilterType> g_enabled_filter_types; @@ -904,15 +926,12 @@ bool AppInitParameterInteraction(const ArgsManager& args, bool use_syscall_sandb // ********************************************************* Step 3: parameter-to-internal-flags init::SetLoggingCategories(args); + init::SetLoggingLevel(args); fCheckBlockIndex = args.GetBoolArg("-checkblockindex", chainparams.DefaultConsistencyChecks()); fCheckpointsEnabled = args.GetBoolArg("-checkpoints", DEFAULT_CHECKPOINTS_ENABLED); hashAssumeValid = uint256S(args.GetArg("-assumevalid", chainparams.GetConsensus().defaultAssumeValid.GetHex())); - if (!hashAssumeValid.IsNull()) - LogPrintf("Assuming ancestors of block %s have valid signatures.\n", hashAssumeValid.GetHex()); - else - LogPrintf("Validating signatures for all blocks.\n"); if (args.IsArgSet("-minimumchainwork")) { const std::string minChainWorkStr = args.GetArg("-minimumchainwork", ""); @@ -923,25 +942,6 @@ bool AppInitParameterInteraction(const ArgsManager& args, bool use_syscall_sandb } else { nMinimumChainWork = UintToArith256(chainparams.GetConsensus().nMinimumChainWork); } - LogPrintf("Setting nMinimumChainWork=%s\n", nMinimumChainWork.GetHex()); - if (nMinimumChainWork < UintToArith256(chainparams.GetConsensus().nMinimumChainWork)) { - LogPrintf("Warning: nMinimumChainWork set below default value of %s\n", chainparams.GetConsensus().nMinimumChainWork.GetHex()); - } - - // mempool limits - int64_t nMempoolSizeMax = args.GetIntArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000; - int64_t nMempoolSizeMin = args.GetIntArg("-limitdescendantsize", DEFAULT_DESCENDANT_SIZE_LIMIT) * 1000 * 40; - if (nMempoolSizeMax < 0 || nMempoolSizeMax < nMempoolSizeMin) - return InitError(strprintf(_("-maxmempool must be at least %d MB"), std::ceil(nMempoolSizeMin / 1000000.0))); - // incremental relay fee sets the minimum feerate increase necessary for BIP 125 replacement in the mempool - // and the amount the mempool min fee increases above the feerate of txs evicted due to mempool limiting. - if (args.IsArgSet("-incrementalrelayfee")) { - if (std::optional<CAmount> inc_relay_fee = ParseMoney(args.GetArg("-incrementalrelayfee", ""))) { - ::incrementalRelayFee = CFeeRate{inc_relay_fee.value()}; - } else { - return InitError(AmountErrMsg("incrementalrelayfee", args.GetArg("-incrementalrelayfee", ""))); - } - } // block pruning; get the amount of disk space (in MiB) to allot for block & undo files int64_t nPruneArg = args.GetIntArg("-prune", 0); @@ -950,14 +950,12 @@ bool AppInitParameterInteraction(const ArgsManager& args, bool use_syscall_sandb } nPruneTarget = (uint64_t) nPruneArg * 1024 * 1024; if (nPruneArg == 1) { // manual pruning: -prune=1 - LogPrintf("Block pruning enabled. Use RPC call pruneblockchain(height) to manually prune block and undo files.\n"); nPruneTarget = std::numeric_limits<uint64_t>::max(); fPruneMode = true; } else if (nPruneTarget) { if (nPruneTarget < MIN_DISK_SPACE_FOR_BLOCK_FILES) { return InitError(strprintf(_("Prune configured below the minimum of %d MiB. Please use a higher number."), MIN_DISK_SPACE_FOR_BLOCK_FILES / 1024 / 1024)); } - LogPrintf("Prune configured to target %u MiB on disk for block and undo files.\n", nPruneTarget / 1024 / 1024); fPruneMode = true; } @@ -968,20 +966,7 @@ bool AppInitParameterInteraction(const ArgsManager& args, bool use_syscall_sandb peer_connect_timeout = args.GetIntArg("-peertimeout", DEFAULT_PEER_CONNECT_TIMEOUT); if (peer_connect_timeout <= 0) { - return InitError(Untranslated("peertimeout cannot be configured with a negative value.")); - } - - if (args.IsArgSet("-minrelaytxfee")) { - if (std::optional<CAmount> min_relay_fee = ParseMoney(args.GetArg("-minrelaytxfee", ""))) { - // High fee check is done afterward in CWallet::Create() - ::minRelayTxFee = CFeeRate{min_relay_fee.value()}; - } else { - return InitError(AmountErrMsg("minrelaytxfee", args.GetArg("-minrelaytxfee", ""))); - } - } else if (incrementalRelayFee > ::minRelayTxFee) { - // Allow only setting incrementalRelayFee to control both - ::minRelayTxFee = incrementalRelayFee; - LogPrintf("Increasing minrelaytxfee to %s to match incrementalrelayfee\n",::minRelayTxFee.ToString()); + return InitError(Untranslated("peertimeout must be a positive integer.")); } // Sanity check argument for min fee for including tx in block @@ -992,28 +977,10 @@ bool AppInitParameterInteraction(const ArgsManager& args, bool use_syscall_sandb } } - // Feerate used to define dust. Shouldn't be changed lightly as old - // implementations may inadvertently create non-standard transactions - if (args.IsArgSet("-dustrelayfee")) { - if (std::optional<CAmount> parsed = ParseMoney(args.GetArg("-dustrelayfee", ""))) { - dustRelayFee = CFeeRate{parsed.value()}; - } else { - return InitError(AmountErrMsg("dustrelayfee", args.GetArg("-dustrelayfee", ""))); - } - } - - fRequireStandard = !args.GetBoolArg("-acceptnonstdtxn", !chainparams.RequireStandard()); - if (!chainparams.IsTestChain() && !fRequireStandard) { - return InitError(strprintf(Untranslated("acceptnonstdtxn is not currently supported for %s chain"), chainparams.NetworkIDString())); - } nBytesPerSigOp = args.GetIntArg("-bytespersigop", nBytesPerSigOp); if (!g_wallet_init_interface.ParameterInteraction()) return false; - fIsBareMultisigStd = args.GetBoolArg("-permitbaremultisig", DEFAULT_PERMIT_BAREMULTISIG); - fAcceptDatacarrier = args.GetBoolArg("-datacarrier", DEFAULT_ACCEPT_DATACARRIER); - nMaxDatacarrierBytes = args.GetIntArg("-datacarriersize", nMaxDatacarrierBytes); - // Option to startup with mocktime set (used for regression testing): SetMockTime(args.GetIntArg("-mocktime", 0)); // SetMockTime(0) is a no-op @@ -1081,7 +1048,7 @@ bool AppInitParameterInteraction(const ArgsManager& args, bool use_syscall_sandb static bool LockDataDirectory(bool probeOnly) { // Make sure only a single Bitcoin process is using the data directory. - fs::path datadir = gArgs.GetDataDirNet(); + const fs::path& datadir = gArgs.GetDataDirNet(); if (!DirIsWritable(datadir)) { return InitError(strprintf(_("Cannot write to data directory '%s'; check permissions."), fs::PathToString(datadir))); } @@ -1094,21 +1061,8 @@ static bool LockDataDirectory(bool probeOnly) bool AppInitSanityChecks(const kernel::Context& kernel) { // ********************************************************* Step 4: sanity checks - auto maybe_error = kernel::SanityChecks(kernel); - - if (maybe_error.has_value()) { - switch (maybe_error.value()) { - case kernel::SanityCheckError::ERROR_ECC: - InitError(Untranslated("Elliptic curve cryptography sanity check failure. Aborting.")); - break; - case kernel::SanityCheckError::ERROR_RANDOM: - InitError(Untranslated("OS cryptographic RNG sanity check failure. Aborting.")); - break; - case kernel::SanityCheckError::ERROR_CHRONO: - InitError(Untranslated("Clock epoch mismatch. Aborting.")); - break; - } // no default case, so the compiler can warn about missing cases - + if (auto error = kernel::SanityChecks(kernel)) { + InitError(*error); return InitError(strprintf(_("Initialization sanity check failed. %s is shutting down."), PACKAGE_NAME)); } @@ -1167,8 +1121,13 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) args.GetArg("-datadir", ""), fs::PathToString(fs::current_path())); } - InitSignatureCache(); - InitScriptExecutionCache(); + ValidationCacheSizes validation_cache_sizes{}; + ApplyArgsManOptions(args, validation_cache_sizes); + if (!InitSignatureCache(validation_cache_sizes.signature_cache_bytes) + || !InitScriptExecutionCache(validation_cache_sizes.script_execution_cache_bytes)) + { + return InitError(strprintf(_("Unable to allocate memory for -maxsigcachesize: '%s' MiB"), args.GetIntArg("-maxsigcachesize", DEFAULT_MAX_SIG_CACHE_BYTES >> 20))); + } int script_threads = args.GetIntArg("-par", DEFAULT_SCRIPTCHECK_THREADS); if (script_threads <= 0) { @@ -1294,7 +1253,7 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) assert(!node.fee_estimator); // Don't initialize fee estimation with old data if we don't relay transactions, // as they would never get updated. - if (!ignores_incoming_txs) node.fee_estimator = std::make_unique<CBlockPolicyEstimator>(); + if (!ignores_incoming_txs) node.fee_estimator = std::make_unique<CBlockPolicyEstimator>(FeeestPath(args)); // sanitize comments per BIP-0014, format user agent and check total size std::vector<std::string> uacomments; @@ -1326,6 +1285,11 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) } if (!args.IsArgSet("-cjdnsreachable")) { + if (args.IsArgSet("-onlynet") && IsReachable(NET_CJDNS)) { + return InitError( + _("Outbound connections restricted to CJDNS (-onlynet=cjdns) but " + "-cjdnsreachable is not provided")); + } SetReachable(NET_CJDNS, false); } // Now IsReachable(NET_CJDNS) is true if: @@ -1333,6 +1297,13 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) // 2.1. -onlynet is not given or // 2.2. -onlynet=cjdns is given + // Requesting DNS seeds entails connecting to IPv4/IPv6, which -onlynet options may prohibit: + // If -dnsseed=1 is explicitly specified, abort. If it's left unspecified by the user, we skip + // the DNS seeds by adjusting -dnsseed in InitParameterInteraction. + if (args.GetBoolArg("-dnsseed") == true && !IsReachable(NET_IPV4) && !IsReachable(NET_IPV6)) { + return InitError(strprintf(_("Incompatible options: -dnsseed=1 was explicitly specified, but -onlynet forbids connections to IPv4/IPv6"))); + }; + // Check for host lookup allowed before parsing any network related parameters fNameLookup = args.GetBoolArg("-dns", DEFAULT_NAME_LOOKUP); @@ -1359,6 +1330,8 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) onion_proxy = addrProxy; } + const bool onlynet_used_with_onion{args.IsArgSet("-onlynet") && IsReachable(NET_ONION)}; + // -onion can be used to set only a proxy for .onion, or override normal proxy for .onion addresses // -noonion (or -onion=0) disables connecting to .onion entirely // An empty string is used to not override the onion proxy (in which case it defaults to -proxy set above, or none) @@ -1366,6 +1339,11 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) if (onionArg != "") { if (onionArg == "0") { // Handle -noonion/-onion=0 onion_proxy = Proxy{}; + if (onlynet_used_with_onion) { + return InitError( + _("Outbound connections restricted to Tor (-onlynet=onion) but the proxy for " + "reaching the Tor network is explicitly forbidden: -onion=0")); + } } else { CService addr; if (!Lookup(onionArg, addr, 9050, fNameLookup) || !addr.IsValid()) { @@ -1378,11 +1356,14 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) if (onion_proxy.IsValid()) { SetProxy(NET_ONION, onion_proxy); } else { - if (args.IsArgSet("-onlynet") && IsReachable(NET_ONION)) { + // If -listenonion is set, then we will (try to) connect to the Tor control port + // later from the torcontrol thread and may retrieve the onion proxy from there. + const bool listenonion_disabled{!args.GetBoolArg("-listenonion", DEFAULT_LISTEN_ONION)}; + if (onlynet_used_with_onion && listenonion_disabled) { return InitError( _("Outbound connections restricted to Tor (-onlynet=onion) but the proxy for " - "reaching the Tor network is not provided (no -proxy= and no -onion= given) or " - "it is explicitly forbidden (-onion=0)")); + "reaching the Tor network is not provided: none of -proxy, -onion or " + "-listenonion is given")); } SetReachable(NET_ONION, false); } @@ -1411,7 +1392,6 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) // cache size calculations CacheSizes cache_sizes = CalculateCacheSizes(args, g_enabled_filter_types.size()); - int64_t nMempoolSizeMax = args.GetIntArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000; LogPrintf("Cache configuration:\n"); LogPrintf("* Using %.1f MiB for block index database\n", cache_sizes.block_tree_db * (1.0 / 1024 / 1024)); if (args.GetBoolArg("-txindex", DEFAULT_TXINDEX)) { @@ -1422,128 +1402,83 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) cache_sizes.filter_index * (1.0 / 1024 / 1024), BlockFilterTypeName(filter_type)); } LogPrintf("* Using %.1f MiB for chain state database\n", cache_sizes.coins_db * (1.0 / 1024 / 1024)); - LogPrintf("* Using %.1f MiB for in-memory UTXO set (plus up to %.1f MiB of unused mempool space)\n", cache_sizes.coins * (1.0 / 1024 / 1024), nMempoolSizeMax * (1.0 / 1024 / 1024)); assert(!node.mempool); assert(!node.chainman); - const int mempool_check_ratio = std::clamp<int>(args.GetIntArg("-checkmempool", chainparams.DefaultConsistencyChecks() ? 1 : 0), 0, 1000000); + + CTxMemPool::Options mempool_opts{ + .estimator = node.fee_estimator.get(), + .check_ratio = chainparams.DefaultConsistencyChecks() ? 1 : 0, + }; + if (const auto err{ApplyArgsManOptions(args, chainparams, mempool_opts)}) { + return InitError(*err); + } + mempool_opts.check_ratio = std::clamp<int>(mempool_opts.check_ratio, 0, 1'000'000); + + int64_t descendant_limit_bytes = mempool_opts.limits.descendant_size_vbytes * 40; + if (mempool_opts.max_size_bytes < 0 || mempool_opts.max_size_bytes < descendant_limit_bytes) { + return InitError(strprintf(_("-maxmempool must be at least %d MB"), std::ceil(descendant_limit_bytes / 1'000'000.0))); + } + LogPrintf("* Using %.1f MiB for in-memory UTXO set (plus up to %.1f MiB of unused mempool space)\n", cache_sizes.coins * (1.0 / 1024 / 1024), mempool_opts.max_size_bytes * (1.0 / 1024 / 1024)); for (bool fLoaded = false; !fLoaded && !ShutdownRequested();) { - node.mempool = std::make_unique<CTxMemPool>(node.fee_estimator.get(), mempool_check_ratio); + node.mempool = std::make_unique<CTxMemPool>(mempool_opts); const ChainstateManager::Options chainman_opts{ - chainparams, - GetAdjustedTime, + .chainparams = chainparams, + .adjusted_time_callback = GetAdjustedTime, }; node.chainman = std::make_unique<ChainstateManager>(chainman_opts); ChainstateManager& chainman = *node.chainman; - const bool fReset = fReindex; - bilingual_str strLoadError; + node::ChainstateLoadOptions options; + options.mempool = Assert(node.mempool.get()); + options.reindex = node::fReindex; + options.reindex_chainstate = fReindexChainState; + options.prune = node::fPruneMode; + options.check_blocks = args.GetIntArg("-checkblocks", DEFAULT_CHECKBLOCKS); + options.check_level = args.GetIntArg("-checklevel", DEFAULT_CHECKLEVEL); + options.check_interrupt = ShutdownRequested; + options.coins_error_cb = [] { + uiInterface.ThreadSafeMessageBox( + _("Error reading from database, shutting down."), + "", CClientUIInterface::MSG_ERROR); + }; uiInterface.InitMessage(_("Loading block index…").translated); - const int64_t load_block_index_start_time = GetTimeMillis(); - std::optional<ChainstateLoadingError> maybe_load_error; - try { - maybe_load_error = LoadChainstate(fReset, - chainman, - Assert(node.mempool.get()), - fPruneMode, - fReindexChainState, - cache_sizes.block_tree_db, - cache_sizes.coins_db, - cache_sizes.coins, - /*block_tree_db_in_memory=*/false, - /*coins_db_in_memory=*/false, - /*shutdown_requested=*/ShutdownRequested, - /*coins_error_cb=*/[]() { - uiInterface.ThreadSafeMessageBox( - _("Error reading from database, shutting down."), - "", CClientUIInterface::MSG_ERROR); - }); - } catch (const std::exception& e) { - LogPrintf("%s\n", e.what()); - maybe_load_error = ChainstateLoadingError::ERROR_GENERIC_BLOCKDB_OPEN_FAILED; - } - if (maybe_load_error.has_value()) { - switch (maybe_load_error.value()) { - case ChainstateLoadingError::ERROR_LOADING_BLOCK_DB: - strLoadError = _("Error loading block database"); - break; - case ChainstateLoadingError::ERROR_BAD_GENESIS_BLOCK: - // If the loaded chain has a wrong genesis, bail out immediately - // (we're likely using a testnet datadir, or the other way around). - return InitError(_("Incorrect or no genesis block found. Wrong datadir for network?")); - case ChainstateLoadingError::ERROR_PRUNED_NEEDS_REINDEX: - strLoadError = _("You need to rebuild the database using -reindex to go back to unpruned mode. This will redownload the entire blockchain"); - break; - case ChainstateLoadingError::ERROR_LOAD_GENESIS_BLOCK_FAILED: - strLoadError = _("Error initializing block database"); - break; - case ChainstateLoadingError::ERROR_CHAINSTATE_UPGRADE_FAILED: - return InitError(_("Unsupported chainstate database format found. " - "Please restart with -reindex-chainstate. This will " - "rebuild the chainstate database.")); - case ChainstateLoadingError::ERROR_REPLAYBLOCKS_FAILED: - strLoadError = _("Unable to replay blocks. You will need to rebuild the database using -reindex-chainstate."); - break; - case ChainstateLoadingError::ERROR_LOADCHAINTIP_FAILED: - strLoadError = _("Error initializing block database"); - break; - case ChainstateLoadingError::ERROR_GENERIC_BLOCKDB_OPEN_FAILED: - strLoadError = _("Error opening block database"); - break; - case ChainstateLoadingError::ERROR_BLOCKS_WITNESS_INSUFFICIENTLY_VALIDATED: - strLoadError = strprintf(_("Witness data for blocks after height %d requires validation. Please restart with -reindex."), - chainman.GetConsensus().SegwitHeight); - break; - case ChainstateLoadingError::SHUTDOWN_PROBED: - break; - } - } else { - std::optional<ChainstateLoadVerifyError> maybe_verify_error; + const auto load_block_index_start_time{SteadyClock::now()}; + auto catch_exceptions = [](auto&& f) { try { - uiInterface.InitMessage(_("Verifying blocks…").translated); - auto check_blocks = args.GetIntArg("-checkblocks", DEFAULT_CHECKBLOCKS); - if (chainman.m_blockman.m_have_pruned && check_blocks > MIN_BLOCKS_TO_KEEP) { - LogPrintf("Prune: pruned datadir may not have more than %d blocks; only checking available blocks\n", - MIN_BLOCKS_TO_KEEP); - } - maybe_verify_error = VerifyLoadedChainstate(chainman, - fReset, - fReindexChainState, - check_blocks, - args.GetIntArg("-checklevel", DEFAULT_CHECKLEVEL)); + return f(); } catch (const std::exception& e) { LogPrintf("%s\n", e.what()); - maybe_verify_error = ChainstateLoadVerifyError::ERROR_GENERIC_FAILURE; + return std::make_tuple(node::ChainstateLoadStatus::FAILURE, _("Error opening block database")); } - if (maybe_verify_error.has_value()) { - switch (maybe_verify_error.value()) { - case ChainstateLoadVerifyError::ERROR_BLOCK_FROM_FUTURE: - strLoadError = _("The block database contains a block which appears to be from the future. " - "This may be due to your computer's date and time being set incorrectly. " - "Only rebuild the block database if you are sure that your computer's date and time are correct"); - break; - case ChainstateLoadVerifyError::ERROR_CORRUPTED_BLOCK_DB: - strLoadError = _("Corrupted block database detected"); - break; - case ChainstateLoadVerifyError::ERROR_GENERIC_FAILURE: - strLoadError = _("Error opening block database"); - break; - } - } else { + }; + auto [status, error] = catch_exceptions([&]{ return LoadChainstate(chainman, cache_sizes, options); }); + if (status == node::ChainstateLoadStatus::SUCCESS) { + uiInterface.InitMessage(_("Verifying blocks…").translated); + if (chainman.m_blockman.m_have_pruned && options.check_blocks > MIN_BLOCKS_TO_KEEP) { + LogPrintfCategory(BCLog::PRUNE, "pruned datadir may not have more than %d blocks; only checking available blocks\n", + MIN_BLOCKS_TO_KEEP); + } + std::tie(status, error) = catch_exceptions([&]{ return VerifyLoadedChainstate(chainman, options);}); + if (status == node::ChainstateLoadStatus::SUCCESS) { fLoaded = true; - LogPrintf(" block index %15dms\n", GetTimeMillis() - load_block_index_start_time); + LogPrintf(" block index %15dms\n", Ticks<std::chrono::milliseconds>(SteadyClock::now() - load_block_index_start_time)); } } + if (status == node::ChainstateLoadStatus::FAILURE_INCOMPATIBLE_DB) { + return InitError(error); + } + if (!fLoaded && !ShutdownRequested()) { // first suggest a reindex - if (!fReset) { + if (!options.reindex) { bool fRet = uiInterface.ThreadSafeQuestion( - strLoadError + Untranslated(".\n\n") + _("Do you want to rebuild the block database now?"), - strLoadError.original + ".\nPlease restart with -reindex or -reindex-chainstate to recover.", + error + Untranslated(".\n\n") + _("Do you want to rebuild the block database now?"), + error.original + ".\nPlease restart with -reindex or -reindex-chainstate to recover.", "", CClientUIInterface::MSG_ERROR | CClientUIInterface::BTN_ABORT); if (fRet) { fReindex = true; @@ -1553,7 +1488,7 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) return false; } } else { - return InitError(strLoadError); + return InitError(error); } } } @@ -1579,22 +1514,22 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) return InitError(*error); } - g_txindex = std::make_unique<TxIndex>(cache_sizes.tx_index, false, fReindex); - if (!g_txindex->Start(chainman.ActiveChainstate())) { + g_txindex = std::make_unique<TxIndex>(interfaces::MakeChain(node), cache_sizes.tx_index, false, fReindex); + if (!g_txindex->Start()) { return false; } } for (const auto& filter_type : g_enabled_filter_types) { - InitBlockFilterIndex(filter_type, cache_sizes.filter_index, false, fReindex); - if (!GetBlockFilterIndex(filter_type)->Start(chainman.ActiveChainstate())) { + InitBlockFilterIndex([&]{ return interfaces::MakeChain(node); }, filter_type, cache_sizes.filter_index, false, fReindex); + if (!GetBlockFilterIndex(filter_type)->Start()) { return false; } } if (args.GetBoolArg("-coinstatsindex", DEFAULT_COINSTATSINDEX)) { - g_coin_stats_index = std::make_unique<CoinStatsIndex>(/* cache size */ 0, false, fReindex); - if (!g_coin_stats_index->Start(chainman.ActiveChainstate())) { + g_coin_stats_index = std::make_unique<CoinStatsIndex>(interfaces::MakeChain(node), /* cache size */ 0, false, fReindex); + if (!g_coin_stats_index->Start()) { return false; } } @@ -1608,18 +1543,19 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) // ********************************************************* Step 10: data directory maintenance - // if pruning, unset the service bit and perform the initial blockstore prune + // if pruning, perform the initial blockstore prune // after any wallet rescanning has taken place. if (fPruneMode) { - LogPrintf("Unsetting NODE_NETWORK on prune mode\n"); - nLocalServices = ServiceFlags(nLocalServices & ~NODE_NETWORK); if (!fReindex) { LOCK(cs_main); - for (CChainState* chainstate : chainman.GetAll()) { + for (Chainstate* chainstate : chainman.GetAll()) { uiInterface.InitMessage(_("Pruning blockstore…").translated); chainstate->PruneAndFlush(); } } + } else { + LogPrintf("Setting NODE_NETWORK on non-prune mode\n"); + nLocalServices = ServiceFlags(nLocalServices | NODE_NETWORK); } // ********************************************************* Step 11: import blocks @@ -1636,7 +1572,7 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) // Either install a handler to notify us when genesis activates, or set fHaveGenesis directly. // No locking, as this happens before any background thread is started. boost::signals2::connection block_notify_genesis_wait_connection; - if (chainman.ActiveChain().Tip() == nullptr) { + if (WITH_LOCK(chainman.GetMutex(), return chainman.ActiveChain().Tip() == nullptr)) { block_notify_genesis_wait_connection = uiInterface.NotifyBlockTip_connect(std::bind(BlockNotifyGenesisWait, std::placeholders::_2)); } else { fHaveGenesis = true; @@ -1661,7 +1597,7 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) } chainman.m_load_block = std::thread(&util::TraceThread, "loadblk", [=, &chainman, &args] { - ThreadImport(chainman, vImportFiles, args); + ThreadImport(chainman, vImportFiles, args, ShouldPersistMempool(args) ? MempoolPath(args) : fs::path{}); }); // Wait for genesis block to be processed @@ -1826,6 +1762,11 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) } SetProxy(NET_I2P, Proxy{addr}); } else { + if (args.IsArgSet("-onlynet") && IsReachable(NET_I2P)) { + return InitError( + _("Outbound connections restricted to i2p (-onlynet=i2p) but " + "-i2psam is not provided")); + } SetReachable(NET_I2P, false); } @@ -1840,12 +1781,12 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) // At this point, the RPC is "started", but still in warmup, which means it // cannot yet be called. Before we make it callable, we need to make sure // that the RPC's view of the best block is valid and consistent with - // ChainstateManager's ActiveTip. + // ChainstateManager's active tip. // // If we do not do this, RPC's view of the best block will be height=0 and // hash=0x0. This will lead to erroroneous responses for things like // waitforblockheight. - RPCNotifyBlockChange(chainman.ActiveTip()); + RPCNotifyBlockChange(WITH_LOCK(chainman.GetMutex(), return chainman.ActiveTip())); SetRPCWarmupFinished(); uiInterface.InitMessage(_("Done loading").translated); diff --git a/src/init/common.cpp b/src/init/common.cpp index d4e45454d2..f2d2c5640a 100644 --- a/src/init/common.cpp +++ b/src/init/common.cpp @@ -9,8 +9,9 @@ #include <clientversion.h> #include <fs.h> #include <logging.h> -#include <node/ui_interface.h> +#include <node/interface_ui.h> #include <tinyformat.h> +#include <util/string.h> #include <util/system.h> #include <util/time.h> #include <util/translation.h> @@ -23,11 +24,12 @@ namespace init { void AddLoggingArgs(ArgsManager& argsman) { argsman.AddArg("-debuglogfile=<file>", strprintf("Specify location of debug log file. Relative paths will be prefixed by a net-specific datadir location. (-nodebuglogfile to disable; default: %s)", DEFAULT_DEBUGLOGFILE), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); - argsman.AddArg("-debug=<category>", "Output debugging information (default: -nodebug, supplying <category> is optional). " - "If <category> is not supplied or if <category> = 1, output all debugging information. <category> can be: " + LogInstance().LogCategoriesString() + ". This option can be specified multiple times to output multiple categories.", + argsman.AddArg("-debug=<category>", "Output debug and trace logging (default: -nodebug, supplying <category> is optional). " + "If <category> is not supplied or if <category> = 1, output all debug and trace logging. <category> can be: " + LogInstance().LogCategoriesString() + ". This option can be specified multiple times to output multiple categories.", ArgsManager::ALLOW_ANY, OptionsCategory::DEBUG_TEST); - argsman.AddArg("-debugexclude=<category>", "Exclude debugging information for a category. Can be used in conjunction with -debug=1 to output debug logs for all categories except the specified category. This option can be specified multiple times to exclude multiple categories.", ArgsManager::ALLOW_ANY, OptionsCategory::DEBUG_TEST); + argsman.AddArg("-debugexclude=<category>", "Exclude debug and trace logging for a category. Can be used in conjunction with -debug=1 to output debug and trace logging for all categories except the specified category. This option can be specified multiple times to exclude multiple categories.", ArgsManager::ALLOW_ANY, OptionsCategory::DEBUG_TEST); argsman.AddArg("-logips", strprintf("Include IP addresses in debug output (default: %u)", DEFAULT_LOGIPS), ArgsManager::ALLOW_ANY, OptionsCategory::DEBUG_TEST); + argsman.AddArg("-loglevel=<level>|<category>:<level>", strprintf("Set the global or per-category severity level for logging categories enabled with the -debug configuration option or the logging RPC: %s (default=%s); warning and error levels are always logged. If <category>:<level> is supplied, the setting will override the global one and may be specified multiple times to set multiple category-specific levels. <category> can be: %s.", LogInstance().LogLevelsString(), LogInstance().LogLevelToStr(BCLog::DEFAULT_LOG_LEVEL), LogInstance().LogCategoriesString()), ArgsManager::DISALLOW_NEGATION | ArgsManager::DISALLOW_ELISION | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST); argsman.AddArg("-logtimestamps", strprintf("Prepend debug output with timestamp (default: %u)", DEFAULT_LOGTIMESTAMPS), ArgsManager::ALLOW_ANY, OptionsCategory::DEBUG_TEST); #ifdef HAVE_THREAD_LOCAL argsman.AddArg("-logthreadnames", strprintf("Prepend debug output with name of the originating thread (only available on platforms supporting thread_local) (default: %u)", DEFAULT_LOGTHREADNAMES), ArgsManager::ALLOW_ANY, OptionsCategory::DEBUG_TEST); @@ -55,6 +57,26 @@ void SetLoggingOptions(const ArgsManager& args) fLogIPs = args.GetBoolArg("-logips", DEFAULT_LOGIPS); } +void SetLoggingLevel(const ArgsManager& args) +{ + if (args.IsArgSet("-loglevel")) { + for (const std::string& level_str : args.GetArgs("-loglevel")) { + if (level_str.find_first_of(':', 3) == std::string::npos) { + // user passed a global log level, i.e. -loglevel=<level> + if (!LogInstance().SetLogLevel(level_str)) { + InitWarning(strprintf(_("Unsupported global logging level -loglevel=%s. Valid values: %s."), level_str, LogInstance().LogLevelsString())); + } + } else { + // user passed a category-specific log level, i.e. -loglevel=<category>:<level> + const auto& toks = SplitString(level_str, ':'); + if (!(toks.size() == 2 && LogInstance().SetCategoryLogLevel(toks[0], toks[1]))) { + InitWarning(strprintf(_("Unsupported category-specific logging level -loglevel=%s. Expected -loglevel=<category>:<loglevel>. Valid categories: %s. Valid loglevels: %s."), level_str, LogInstance().LogCategoriesString(), LogInstance().LogLevelsString())); + } + } + } + } +} + void SetLoggingCategories(const ArgsManager& args) { if (args.IsArgSet("-debug")) { @@ -99,7 +121,7 @@ bool StartLogging(const ArgsManager& args) LogPrintf("Using data directory %s\n", fs::PathToString(gArgs.GetDataDirNet())); // Only log conf file usage message if conf file actually exists. - fs::path config_file_path = GetConfigFile(args.GetArg("-conf", BITCOIN_CONF_FILENAME)); + fs::path config_file_path = GetConfigFile(args.GetPathArg("-conf", BITCOIN_CONF_FILENAME)); if (fs::exists(config_file_path)) { LogPrintf("Config file: %s\n", fs::PathToString(config_file_path)); } else if (args.IsArgSet("-conf")) { diff --git a/src/init/common.h b/src/init/common.h index 2c7f485908..53c860c297 100644 --- a/src/init/common.h +++ b/src/init/common.h @@ -14,6 +14,7 @@ namespace init { void AddLoggingArgs(ArgsManager& args); void SetLoggingOptions(const ArgsManager& args); void SetLoggingCategories(const ArgsManager& args); +void SetLoggingLevel(const ArgsManager& args); bool StartLogging(const ArgsManager& args); void LogPackageVersion(); } // namespace init diff --git a/src/interfaces/chain.h b/src/interfaces/chain.h index ddfb4bda95..5fc0e540a9 100644 --- a/src/interfaces/chain.h +++ b/src/interfaces/chain.h @@ -18,6 +18,7 @@ class ArgsManager; class CBlock; +class CBlockUndo; class CFeeRate; class CRPCCommand; class CScheduler; @@ -37,6 +38,12 @@ namespace interfaces { class Handler; class Wallet; +//! Hash/height pair to help track and identify blocks. +struct BlockKey { + uint256 hash; + int height = -1; +}; + //! Helper for findBlock to selectively return pieces of block data. If block is //! found, data will be returned by setting specified output variables. If block //! is not found, output variables will keep their previous values. @@ -50,6 +57,8 @@ public: FoundBlock& mtpTime(int64_t& mtp_time) { m_mtp_time = &mtp_time; return *this; } //! Return whether block is in the active (most-work) chain. FoundBlock& inActiveChain(bool& in_active_chain) { m_in_active_chain = &in_active_chain; return *this; } + //! Return locator if block is in the active chain. + FoundBlock& locator(CBlockLocator& locator) { m_locator = &locator; return *this; } //! Return next block in the active chain if current block is in the active chain. FoundBlock& nextBlock(const FoundBlock& next_block) { m_next_block = &next_block; return *this; } //! Read block data from disk. If the block exists but doesn't have data @@ -62,11 +71,25 @@ public: int64_t* m_max_time = nullptr; int64_t* m_mtp_time = nullptr; bool* m_in_active_chain = nullptr; + CBlockLocator* m_locator = nullptr; const FoundBlock* m_next_block = nullptr; CBlock* m_data = nullptr; mutable bool found = false; }; +//! Block data sent with blockConnected, blockDisconnected notifications. +struct BlockInfo { + const uint256& hash; + const uint256* prev_hash = nullptr; + int height = -1; + int file_number = -1; + unsigned data_pos = 0; + const CBlock* data = nullptr; + const CBlockUndo* undo_data = nullptr; + + BlockInfo(const uint256& hash LIFETIMEBOUND) : hash(hash) {} +}; + //! Interface giving clients (wallet processes, maybe other analysis tools in //! the future) ability to access to the chain state, receive notifications, //! estimate fees, and submit transactions. @@ -111,6 +134,10 @@ public: //! Get locator for the current chain tip. virtual CBlockLocator getTipLocator() = 0; + //! Return a locator that refers to a block in the active chain. + //! If specified block is not in the active chain, return locator for the latest ancestor that is in the chain. + virtual CBlockLocator getActiveChainLocator(const uint256& block_hash) = 0; + //! 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. @@ -235,8 +262,8 @@ public: virtual ~Notifications() {} virtual void transactionAddedToMempool(const CTransactionRef& tx, uint64_t mempool_sequence) {} virtual void transactionRemovedFromMempool(const CTransactionRef& tx, MemPoolRemovalReason reason, uint64_t mempool_sequence) {} - virtual void blockConnected(const CBlock& block, int height) {} - virtual void blockDisconnected(const CBlock& block, int height) {} + virtual void blockConnected(const BlockInfo& block) {} + virtual void blockDisconnected(const BlockInfo& block) {} virtual void updatedBlockTip() {} virtual void chainStateFlushed(const CBlockLocator& locator) {} }; @@ -283,6 +310,13 @@ public: //! to be prepared to handle this by ignoring notifications about unknown //! removed transactions and already added new transactions. virtual void requestMempoolTransactions(Notifications& notifications) = 0; + + //! Return true if an assumed-valid chain is in use. + virtual bool hasAssumedValidChain() = 0; + + //! Get internal node context. Useful for testing, but not + //! accessible across processes. + virtual node::NodeContext* context() { return nullptr; } }; //! Interface to let node manage chain clients (wallets, or maybe tools for diff --git a/src/interfaces/node.h b/src/interfaces/node.h index 2c31e12ada..dbdb21eb91 100644 --- a/src/interfaces/node.h +++ b/src/interfaces/node.h @@ -260,7 +260,7 @@ public: //! Register handler for header tip messages. using NotifyHeaderTipFn = - std::function<void(SynchronizationState, interfaces::BlockTip tip, double verification_progress)>; + std::function<void(SynchronizationState, interfaces::BlockTip tip, bool presync)>; virtual std::unique_ptr<Handler> handleNotifyHeaderTip(NotifyHeaderTipFn fn) = 0; //! Get and set internal node context. Useful for testing, but not diff --git a/src/interfaces/wallet.h b/src/interfaces/wallet.h index f26ac866dc..1148dc7e4c 100644 --- a/src/interfaces/wallet.h +++ b/src/interfaces/wallet.h @@ -12,6 +12,7 @@ #include <script/standard.h> // For CTxDestination #include <support/allocators/secure.h> // For SecureString #include <util/message.h> +#include <util/result.h> #include <util/ui_change_type.h> #include <cstdint> @@ -87,7 +88,7 @@ public: virtual std::string getWalletName() = 0; // Get a new address. - virtual bool getNewDestination(const OutputType type, const std::string label, CTxDestination& dest) = 0; + virtual util::Result<CTxDestination> getNewDestination(const OutputType type, const std::string& label) = 0; //! Get public key. virtual bool getPubKey(const CScript& script, const CKeyID& address, CPubKey& pub_key) = 0; @@ -114,7 +115,7 @@ public: std::string* purpose) = 0; //! Get wallet address list. - virtual std::vector<WalletAddress> getAddresses() = 0; + virtual std::vector<WalletAddress> getAddresses() const = 0; //! Get receive requests. virtual std::vector<std::string> getAddressReceiveRequests() = 0; @@ -138,12 +139,11 @@ public: virtual void listLockedCoins(std::vector<COutPoint>& outputs) = 0; //! Create transaction. - virtual CTransactionRef createTransaction(const std::vector<wallet::CRecipient>& recipients, + virtual util::Result<CTransactionRef> createTransaction(const std::vector<wallet::CRecipient>& recipients, const wallet::CCoinControl& coin_control, bool sign, int& change_pos, - CAmount& fee, - bilingual_str& fail_reason) = 0; + CAmount& fee) = 0; //! Commit transaction. virtual void commitTransaction(CTransactionRef tx, @@ -183,7 +183,7 @@ public: virtual WalletTx getWalletTx(const uint256& txid) = 0; //! Get list of all wallet transactions. - virtual std::vector<WalletTx> getWalletTxs() = 0; + virtual std::set<WalletTx> getWalletTxs() = 0; //! Try to get updated status for a particular transaction, if possible without blocking. virtual bool tryGetTxStatus(const uint256& txid, @@ -320,31 +320,31 @@ class WalletLoader : public ChainClient { public: //! Create new wallet. - virtual std::unique_ptr<Wallet> createWallet(const std::string& name, const SecureString& passphrase, uint64_t wallet_creation_flags, bilingual_str& error, std::vector<bilingual_str>& warnings) = 0; + virtual util::Result<std::unique_ptr<Wallet>> createWallet(const std::string& name, const SecureString& passphrase, uint64_t wallet_creation_flags, std::vector<bilingual_str>& warnings) = 0; - //! Load existing wallet. - virtual std::unique_ptr<Wallet> loadWallet(const std::string& name, bilingual_str& error, std::vector<bilingual_str>& warnings) = 0; + //! Load existing wallet. + virtual util::Result<std::unique_ptr<Wallet>> loadWallet(const std::string& name, std::vector<bilingual_str>& warnings) = 0; - //! Return default wallet directory. - virtual std::string getWalletDir() = 0; + //! Return default wallet directory. + virtual std::string getWalletDir() = 0; - //! Restore backup wallet - virtual std::unique_ptr<Wallet> restoreWallet(const fs::path& backup_file, const std::string& wallet_name, bilingual_str& error, std::vector<bilingual_str>& warnings) = 0; + //! Restore backup wallet + virtual util::Result<std::unique_ptr<Wallet>> restoreWallet(const fs::path& backup_file, const std::string& wallet_name, std::vector<bilingual_str>& warnings) = 0; - //! Return available wallets in wallet directory. - virtual std::vector<std::string> listWalletDir() = 0; + //! Return available wallets in wallet directory. + virtual std::vector<std::string> listWalletDir() = 0; - //! Return interfaces for accessing wallets (if any). - virtual std::vector<std::unique_ptr<Wallet>> getWallets() = 0; + //! Return interfaces for accessing wallets (if any). + virtual std::vector<std::unique_ptr<Wallet>> getWallets() = 0; - //! Register handler for load wallet messages. This callback is triggered by - //! createWallet and loadWallet above, and also triggered when wallets are - //! loaded at startup or by RPC. - using LoadWalletFn = std::function<void(std::unique_ptr<Wallet> wallet)>; - virtual std::unique_ptr<Handler> handleLoadWallet(LoadWalletFn fn) = 0; + //! Register handler for load wallet messages. This callback is triggered by + //! createWallet and loadWallet above, and also triggered when wallets are + //! loaded at startup or by RPC. + using LoadWalletFn = std::function<void(std::unique_ptr<Wallet> wallet)>; + virtual std::unique_ptr<Handler> handleLoadWallet(LoadWalletFn fn) = 0; - //! Return pointer to internal context, useful for testing. - virtual wallet::WalletContext* context() { return nullptr; } + //! Return pointer to internal context, useful for testing. + virtual wallet::WalletContext* context() { return nullptr; } }; //! Information about one wallet address. @@ -395,6 +395,8 @@ struct WalletTx int64_t time; std::map<std::string, std::string> value_map; bool is_coinbase; + + bool operator<(const WalletTx& a) const { return tx->GetHash() < a.tx->GetHash(); } }; //! Updated transaction status. diff --git a/src/ipc/interfaces.cpp b/src/ipc/interfaces.cpp index 580590fde9..ee0d4123ce 100644 --- a/src/ipc/interfaces.cpp +++ b/src/ipc/interfaces.cpp @@ -12,11 +12,11 @@ #include <tinyformat.h> #include <util/system.h> +#include <cstdio> +#include <cstdlib> #include <functional> #include <memory> #include <stdexcept> -#include <stdio.h> -#include <stdlib.h> #include <string.h> #include <string> #include <unistd.h> diff --git a/src/ipc/process.cpp b/src/ipc/process.cpp index 9036b80c45..9474ad2c4a 100644 --- a/src/ipc/process.cpp +++ b/src/ipc/process.cpp @@ -10,10 +10,10 @@ #include <util/strencodings.h> #include <cstdint> +#include <cstdlib> #include <exception> #include <iostream> #include <stdexcept> -#include <stdlib.h> #include <string.h> #include <system_error> #include <unistd.h> diff --git a/src/kernel/chain.cpp b/src/kernel/chain.cpp new file mode 100644 index 0000000000..82e77125d7 --- /dev/null +++ b/src/kernel/chain.cpp @@ -0,0 +1,26 @@ +// Copyright (c) 2022 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include <chain.h> +#include <interfaces/chain.h> +#include <sync.h> +#include <uint256.h> + +class CBlock; + +namespace kernel { +interfaces::BlockInfo MakeBlockInfo(const CBlockIndex* index, const CBlock* data) +{ + interfaces::BlockInfo info{index ? *index->phashBlock : uint256::ZERO}; + if (index) { + info.prev_hash = index->pprev ? index->pprev->phashBlock : nullptr; + info.height = index->nHeight; + LOCK(::cs_main); + info.file_number = index->nFile; + info.data_pos = index->nDataPos; + } + info.data = data; + return info; +} +} // namespace kernel diff --git a/src/kernel/chain.h b/src/kernel/chain.h new file mode 100644 index 0000000000..f0750f8266 --- /dev/null +++ b/src/kernel/chain.h @@ -0,0 +1,19 @@ +// Copyright (c) 2022 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_KERNEL_CHAIN_H +#define BITCOIN_KERNEL_CHAIN_H + +class CBlock; +class CBlockIndex; +namespace interfaces { +struct BlockInfo; +} // namespace interfaces + +namespace kernel { +//! Return data from block index. +interfaces::BlockInfo MakeBlockInfo(const CBlockIndex* block_index, const CBlock* data = nullptr); +} // namespace kernel + +#endif // BITCOIN_KERNEL_CHAIN_H diff --git a/src/kernel/chainstatemanager_opts.h b/src/kernel/chainstatemanager_opts.h index 575d94e2e9..520d0e8e75 100644 --- a/src/kernel/chainstatemanager_opts.h +++ b/src/kernel/chainstatemanager_opts.h @@ -5,11 +5,15 @@ #ifndef BITCOIN_KERNEL_CHAINSTATEMANAGER_OPTS_H #define BITCOIN_KERNEL_CHAINSTATEMANAGER_OPTS_H +#include <util/time.h> + #include <cstdint> #include <functional> class CChainParams; +namespace kernel { + /** * An options struct for `ChainstateManager`, more ergonomically referred to as * `ChainstateManager::Options` due to the using-declaration in @@ -17,7 +21,9 @@ class CChainParams; */ struct ChainstateManagerOpts { const CChainParams& chainparams; - const std::function<int64_t()> adjusted_time_callback{nullptr}; + const std::function<NodeClock::time_point()> adjusted_time_callback{nullptr}; }; +} // namespace kernel + #endif // BITCOIN_KERNEL_CHAINSTATEMANAGER_OPTS_H diff --git a/src/kernel/checks.cpp b/src/kernel/checks.cpp index 2a1dd3bfa2..4c303c172c 100644 --- a/src/kernel/checks.cpp +++ b/src/kernel/checks.cpp @@ -7,21 +7,24 @@ #include <key.h> #include <random.h> #include <util/time.h> +#include <util/translation.h> + +#include <memory> namespace kernel { -std::optional<SanityCheckError> SanityChecks(const Context&) +std::optional<bilingual_str> SanityChecks(const Context&) { if (!ECC_InitSanityCheck()) { - return SanityCheckError::ERROR_ECC; + return Untranslated("Elliptic curve cryptography sanity check failure. Aborting."); } if (!Random_SanityCheck()) { - return SanityCheckError::ERROR_RANDOM; + return Untranslated("OS cryptographic RNG sanity check failure. Aborting."); } if (!ChronoSanityCheck()) { - return SanityCheckError::ERROR_CHRONO; + return Untranslated("Clock epoch mismatch. Aborting."); } return std::nullopt; diff --git a/src/kernel/checks.h b/src/kernel/checks.h index 80b207f607..3eb14824fb 100644 --- a/src/kernel/checks.h +++ b/src/kernel/checks.h @@ -7,20 +7,16 @@ #include <optional> +struct bilingual_str; + namespace kernel { struct Context; -enum class SanityCheckError { - ERROR_ECC, - ERROR_RANDOM, - ERROR_CHRONO, -}; - /** * Ensure a usable environment with all necessary library support. */ -std::optional<SanityCheckError> SanityChecks(const Context&); +std::optional<bilingual_str> SanityChecks(const Context&); } diff --git a/src/kernel/coinstats.cpp b/src/kernel/coinstats.cpp index f380871627..06a4b8c974 100644 --- a/src/kernel/coinstats.cpp +++ b/src/kernel/coinstats.cpp @@ -4,16 +4,32 @@ #include <kernel/coinstats.h> +#include <chain.h> #include <coins.h> #include <crypto/muhash.h> #include <hash.h> +#include <node/blockstorage.h> +#include <primitives/transaction.h> +#include <script/script.h> #include <serialize.h> +#include <span.h> +#include <streams.h> +#include <sync.h> +#include <tinyformat.h> #include <uint256.h> +#include <util/check.h> #include <util/overflow.h> #include <util/system.h> #include <validation.h> +#include <version.h> +#include <cassert> +#include <iosfwd> +#include <iterator> #include <map> +#include <memory> +#include <string> +#include <utility> namespace kernel { @@ -52,7 +68,7 @@ CDataStream TxOutSer(const COutPoint& outpoint, const Coin& coin) { //! It is also possible, though very unlikely, that a change in this //! construction could cause a previously invalid (and potentially malicious) //! UTXO snapshot to be considered valid. -static void ApplyHash(CHashWriter& ss, const uint256& hash, const std::map<uint32_t, Coin>& outputs) +static void ApplyHash(HashWriter& ss, const uint256& hash, const std::map<uint32_t, Coin>& outputs) { for (auto it = outputs.begin(); it != outputs.end(); ++it) { if (it == outputs.begin()) { @@ -143,7 +159,7 @@ std::optional<CCoinsStats> ComputeUTXOStats(CoinStatsHashType hash_type, CCoinsV bool success = [&]() -> bool { switch (hash_type) { case(CoinStatsHashType::HASH_SERIALIZED): { - CHashWriter ss(SER_GETHASH, PROTOCOL_VERSION); + HashWriter ss{}; return ComputeUTXOStats(view, stats, ss, interruption_point); } case(CoinStatsHashType::MUHASH): { @@ -164,7 +180,7 @@ std::optional<CCoinsStats> ComputeUTXOStats(CoinStatsHashType hash_type, CCoinsV } // The legacy hash serializes the hashBlock -static void PrepareHash(CHashWriter& ss, const CCoinsStats& stats) +static void PrepareHash(HashWriter& ss, const CCoinsStats& stats) { ss << stats.hashBlock; } @@ -172,7 +188,7 @@ static void PrepareHash(CHashWriter& ss, const CCoinsStats& stats) static void PrepareHash(MuHash3072& muhash, CCoinsStats& stats) {} static void PrepareHash(std::nullptr_t, CCoinsStats& stats) {} -static void FinalizeHash(CHashWriter& ss, CCoinsStats& stats) +static void FinalizeHash(HashWriter& ss, CCoinsStats& stats) { stats.hashSerialized = ss.GetHash(); } diff --git a/src/kernel/coinstats.h b/src/kernel/coinstats.h index a15957233f..b7c1328e93 100644 --- a/src/kernel/coinstats.h +++ b/src/kernel/coinstats.h @@ -5,16 +5,18 @@ #ifndef BITCOIN_KERNEL_COINSTATS_H #define BITCOIN_KERNEL_COINSTATS_H -#include <chain.h> -#include <coins.h> #include <consensus/amount.h> #include <streams.h> #include <uint256.h> #include <cstdint> #include <functional> +#include <optional> class CCoinsView; +class Coin; +class COutPoint; +class CScript; namespace node { class BlockManager; } // namespace node diff --git a/src/kernel/mempool_limits.h b/src/kernel/mempool_limits.h new file mode 100644 index 0000000000..8d4495c3cb --- /dev/null +++ b/src/kernel/mempool_limits.h @@ -0,0 +1,39 @@ +// Copyright (c) 2022 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. +#ifndef BITCOIN_KERNEL_MEMPOOL_LIMITS_H +#define BITCOIN_KERNEL_MEMPOOL_LIMITS_H + +#include <policy/policy.h> + +#include <cstdint> + +namespace kernel { +/** + * Options struct containing limit options for a CTxMemPool. Default constructor + * populates the struct with sane default values which can be modified. + * + * Most of the time, this struct should be referenced as CTxMemPool::Limits. + */ +struct MemPoolLimits { + //! The maximum allowed number of transactions in a package including the entry and its ancestors. + int64_t ancestor_count{DEFAULT_ANCESTOR_LIMIT}; + //! The maximum allowed size in virtual bytes of an entry and its ancestors within a package. + int64_t ancestor_size_vbytes{DEFAULT_ANCESTOR_SIZE_LIMIT_KVB * 1'000}; + //! The maximum allowed number of transactions in a package including the entry and its descendants. + int64_t descendant_count{DEFAULT_DESCENDANT_LIMIT}; + //! The maximum allowed size in virtual bytes of an entry and its descendants within a package. + int64_t descendant_size_vbytes{DEFAULT_DESCENDANT_SIZE_LIMIT_KVB * 1'000}; + + /** + * @return MemPoolLimits with all the limits set to the maximum + */ + static constexpr MemPoolLimits NoLimits() + { + int64_t no_limit{std::numeric_limits<int64_t>::max()}; + return {no_limit, no_limit, no_limit, no_limit}; + } +}; +} // namespace kernel + +#endif // BITCOIN_KERNEL_MEMPOOL_LIMITS_H diff --git a/src/kernel/mempool_options.h b/src/kernel/mempool_options.h new file mode 100644 index 0000000000..dad6f14c39 --- /dev/null +++ b/src/kernel/mempool_options.h @@ -0,0 +1,60 @@ +// Copyright (c) 2022 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. +#ifndef BITCOIN_KERNEL_MEMPOOL_OPTIONS_H +#define BITCOIN_KERNEL_MEMPOOL_OPTIONS_H + +#include <kernel/mempool_limits.h> + +#include <policy/feerate.h> +#include <policy/policy.h> +#include <script/standard.h> + +#include <chrono> +#include <cstdint> +#include <optional> + +class CBlockPolicyEstimator; + +/** Default for -maxmempool, maximum megabytes of mempool memory usage */ +static constexpr unsigned int DEFAULT_MAX_MEMPOOL_SIZE_MB{300}; +/** Default for -mempoolexpiry, expiration time for mempool transactions in hours */ +static constexpr unsigned int DEFAULT_MEMPOOL_EXPIRY_HOURS{336}; +/** Default for -mempoolfullrbf, if the transaction replaceability signaling is ignored */ +static constexpr bool DEFAULT_MEMPOOL_FULL_RBF{false}; + +namespace kernel { +/** + * Options struct containing options for constructing a CTxMemPool. Default + * constructor populates the struct with sane default values which can be + * modified. + * + * Most of the time, this struct should be referenced as CTxMemPool::Options. + */ +struct MemPoolOptions { + /* Used to estimate appropriate transaction fees. */ + CBlockPolicyEstimator* estimator{nullptr}; + /* The ratio used to determine how often sanity checks will run. */ + int check_ratio{0}; + int64_t max_size_bytes{DEFAULT_MAX_MEMPOOL_SIZE_MB * 1'000'000}; + std::chrono::seconds expiry{std::chrono::hours{DEFAULT_MEMPOOL_EXPIRY_HOURS}}; + CFeeRate incremental_relay_feerate{DEFAULT_INCREMENTAL_RELAY_FEE}; + /** A fee rate smaller than this is considered zero fee (for relaying, mining and transaction creation) */ + CFeeRate min_relay_feerate{DEFAULT_MIN_RELAY_TX_FEE}; + CFeeRate dust_relay_feerate{DUST_RELAY_TX_FEE}; + /** + * A data carrying output is an unspendable output containing data. The script + * type is designated as TxoutType::NULL_DATA. + * + * Maximum size of TxoutType::NULL_DATA scripts that this node considers standard. + * If nullopt, any size is nonstandard. + */ + std::optional<unsigned> max_datacarrier_bytes{DEFAULT_ACCEPT_DATACARRIER ? std::optional{MAX_OP_RETURN_RELAY} : std::nullopt}; + bool permit_bare_multisig{DEFAULT_PERMIT_BAREMULTISIG}; + bool require_standard{true}; + bool full_rbf{DEFAULT_MEMPOOL_FULL_RBF}; + MemPoolLimits limits{}; +}; +} // namespace kernel + +#endif // BITCOIN_KERNEL_MEMPOOL_OPTIONS_H diff --git a/src/kernel/mempool_persist.cpp b/src/kernel/mempool_persist.cpp new file mode 100644 index 0000000000..a14b2e6163 --- /dev/null +++ b/src/kernel/mempool_persist.cpp @@ -0,0 +1,189 @@ +// Copyright (c) 2022 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include <kernel/mempool_persist.h> + +#include <clientversion.h> +#include <consensus/amount.h> +#include <fs.h> +#include <logging.h> +#include <primitives/transaction.h> +#include <serialize.h> +#include <shutdown.h> +#include <streams.h> +#include <sync.h> +#include <txmempool.h> +#include <uint256.h> +#include <util/system.h> +#include <util/time.h> +#include <validation.h> + +#include <chrono> +#include <cstdint> +#include <cstdio> +#include <exception> +#include <functional> +#include <map> +#include <memory> +#include <set> +#include <stdexcept> +#include <utility> +#include <vector> + +using fsbridge::FopenFn; + +namespace kernel { + +static const uint64_t MEMPOOL_DUMP_VERSION = 1; + +bool LoadMempool(CTxMemPool& pool, const fs::path& load_path, Chainstate& active_chainstate, FopenFn mockable_fopen_function) +{ + if (load_path.empty()) return false; + + FILE* filestr{mockable_fopen_function(load_path, "rb")}; + CAutoFile file(filestr, SER_DISK, CLIENT_VERSION); + if (file.IsNull()) { + LogPrintf("Failed to open mempool file from disk. Continuing anyway.\n"); + return false; + } + + int64_t count = 0; + int64_t expired = 0; + int64_t failed = 0; + int64_t already_there = 0; + int64_t unbroadcast = 0; + auto now = NodeClock::now(); + + try { + uint64_t version; + file >> version; + if (version != MEMPOOL_DUMP_VERSION) { + return false; + } + uint64_t num; + file >> num; + while (num) { + --num; + CTransactionRef tx; + int64_t nTime; + int64_t nFeeDelta; + file >> tx; + file >> nTime; + file >> nFeeDelta; + + CAmount amountdelta = nFeeDelta; + if (amountdelta) { + pool.PrioritiseTransaction(tx->GetHash(), amountdelta); + } + if (nTime > TicksSinceEpoch<std::chrono::seconds>(now - pool.m_expiry)) { + LOCK(cs_main); + const auto& accepted = AcceptToMemoryPool(active_chainstate, tx, nTime, /*bypass_limits=*/false, /*test_accept=*/false); + if (accepted.m_result_type == MempoolAcceptResult::ResultType::VALID) { + ++count; + } else { + // mempool may contain the transaction already, e.g. from + // wallet(s) having loaded it while we were processing + // mempool transactions; consider these as valid, instead of + // failed, but mark them as 'already there' + if (pool.exists(GenTxid::Txid(tx->GetHash()))) { + ++already_there; + } else { + ++failed; + } + } + } else { + ++expired; + } + if (ShutdownRequested()) + return false; + } + std::map<uint256, CAmount> mapDeltas; + file >> mapDeltas; + + for (const auto& i : mapDeltas) { + pool.PrioritiseTransaction(i.first, i.second); + } + + std::set<uint256> unbroadcast_txids; + file >> unbroadcast_txids; + unbroadcast = unbroadcast_txids.size(); + for (const auto& txid : unbroadcast_txids) { + // Ensure transactions were accepted to mempool then add to + // unbroadcast set. + if (pool.get(txid) != nullptr) pool.AddUnbroadcastTx(txid); + } + } catch (const std::exception& e) { + LogPrintf("Failed to deserialize mempool data on disk: %s. Continuing anyway.\n", e.what()); + return false; + } + + LogPrintf("Imported mempool transactions from disk: %i succeeded, %i failed, %i expired, %i already there, %i waiting for initial broadcast\n", count, failed, expired, already_there, unbroadcast); + return true; +} + +bool DumpMempool(const CTxMemPool& pool, const fs::path& dump_path, FopenFn mockable_fopen_function, bool skip_file_commit) +{ + auto start = SteadyClock::now(); + + std::map<uint256, CAmount> mapDeltas; + std::vector<TxMempoolInfo> vinfo; + std::set<uint256> unbroadcast_txids; + + static Mutex dump_mutex; + LOCK(dump_mutex); + + { + LOCK(pool.cs); + for (const auto &i : pool.mapDeltas) { + mapDeltas[i.first] = i.second; + } + vinfo = pool.infoAll(); + unbroadcast_txids = pool.GetUnbroadcastTxs(); + } + + auto mid = SteadyClock::now(); + + try { + FILE* filestr{mockable_fopen_function(dump_path + ".new", "wb")}; + if (!filestr) { + return false; + } + + CAutoFile file(filestr, SER_DISK, CLIENT_VERSION); + + uint64_t version = MEMPOOL_DUMP_VERSION; + file << version; + + file << (uint64_t)vinfo.size(); + for (const auto& i : vinfo) { + file << *(i.tx); + file << int64_t{count_seconds(i.m_time)}; + file << int64_t{i.nFeeDelta}; + mapDeltas.erase(i.tx->GetHash()); + } + + file << mapDeltas; + + LogPrintf("Writing %d unbroadcast transactions to disk.\n", unbroadcast_txids.size()); + file << unbroadcast_txids; + + if (!skip_file_commit && !FileCommit(file.Get())) + throw std::runtime_error("FileCommit failed"); + file.fclose(); + if (!RenameOver(dump_path + ".new", dump_path)) { + throw std::runtime_error("Rename failed"); + } + auto last = SteadyClock::now(); + + LogPrintf("Dumped mempool: %gs to copy, %gs to dump\n", + Ticks<SecondsDouble>(mid - start), + Ticks<SecondsDouble>(last - mid)); + } catch (const std::exception& e) { + LogPrintf("Failed to dump mempool: %s. Continuing anyway.\n", e.what()); + return false; + } + return true; +} + +} // namespace kernel diff --git a/src/kernel/mempool_persist.h b/src/kernel/mempool_persist.h new file mode 100644 index 0000000000..ca4917e38b --- /dev/null +++ b/src/kernel/mempool_persist.h @@ -0,0 +1,28 @@ +// Copyright (c) 2022 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_KERNEL_MEMPOOL_PERSIST_H +#define BITCOIN_KERNEL_MEMPOOL_PERSIST_H + +#include <fs.h> + +class Chainstate; +class CTxMemPool; + +namespace kernel { + +/** Dump the mempool to disk. */ +bool DumpMempool(const CTxMemPool& pool, const fs::path& dump_path, + fsbridge::FopenFn mockable_fopen_function = fsbridge::fopen, + bool skip_file_commit = false); + +/** Load the mempool from disk. */ +bool LoadMempool(CTxMemPool& pool, const fs::path& load_path, + Chainstate& active_chainstate, + fsbridge::FopenFn mockable_fopen_function = fsbridge::fopen); + +} // namespace kernel + + +#endif // BITCOIN_KERNEL_MEMPOOL_PERSIST_H diff --git a/src/kernel/validation_cache_sizes.h b/src/kernel/validation_cache_sizes.h new file mode 100644 index 0000000000..72e4d1a52c --- /dev/null +++ b/src/kernel/validation_cache_sizes.h @@ -0,0 +1,20 @@ +// Copyright (c) 2022 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_KERNEL_VALIDATION_CACHE_SIZES_H +#define BITCOIN_KERNEL_VALIDATION_CACHE_SIZES_H + +#include <script/sigcache.h> + +#include <cstddef> +#include <limits> + +namespace kernel { +struct ValidationCacheSizes { + size_t signature_cache_bytes{DEFAULT_MAX_SIG_CACHE_BYTES / 2}; + size_t script_execution_cache_bytes{DEFAULT_MAX_SIG_CACHE_BYTES / 2}; +}; +} + +#endif // BITCOIN_KERNEL_VALIDATION_CACHE_SIZES_H diff --git a/src/key.cpp b/src/key.cpp index 9b0971a2dd..199808505d 100644 --- a/src/key.cpp +++ b/src/key.cpp @@ -333,6 +333,7 @@ bool CKey::Derive(CKey& keyChild, ChainCode &ccChild, unsigned int nChild, const } bool CExtKey::Derive(CExtKey &out, unsigned int _nChild) const { + if (nDepth == std::numeric_limits<unsigned char>::max()) return false; out.nDepth = nDepth + 1; CKeyID id = key.GetPubKey().GetID(); memcpy(out.vchFingerprint, &id, 4); @@ -146,7 +146,7 @@ public: bool SignSchnorr(const uint256& hash, Span<unsigned char> sig, const uint256* merkle_root, const uint256& aux) const; //! Derive BIP32 child key. - bool Derive(CKey& keyChild, ChainCode &ccChild, unsigned int nChild, const ChainCode& cc) const; + [[nodiscard]] bool Derive(CKey& keyChild, ChainCode &ccChild, unsigned int nChild, const ChainCode& cc) const; /** * Verify thoroughly whether a private key and a public key match. @@ -176,7 +176,7 @@ struct CExtKey { void Encode(unsigned char code[BIP32_EXTKEY_SIZE]) const; void Decode(const unsigned char code[BIP32_EXTKEY_SIZE]); - bool Derive(CExtKey& out, unsigned int nChild) const; + [[nodiscard]] bool Derive(CExtKey& out, unsigned int nChild) const; CExtPubKey Neuter() const; void SetSeed(Span<const std::byte> seed); }; diff --git a/src/leveldb/util/env_posix.cc b/src/leveldb/util/env_posix.cc index 18626b327c..fac41be6ce 100644 --- a/src/leveldb/util/env_posix.cc +++ b/src/leveldb/util/env_posix.cc @@ -49,7 +49,7 @@ constexpr const int kDefaultMmapLimit = (sizeof(void*) >= 8) ? 4096 : 0; int g_mmap_limit = kDefaultMmapLimit; // Common flags defined for all posix open operations -#if defined(HAVE_O_CLOEXEC) +#if HAVE_O_CLOEXEC constexpr const int kOpenBaseFlags = O_CLOEXEC; #else constexpr const int kOpenBaseFlags = 0; diff --git a/src/leveldb/util/env_windows.cc b/src/leveldb/util/env_windows.cc index 4dcba222a1..aafcdcc3be 100644 --- a/src/leveldb/util/env_windows.cc +++ b/src/leveldb/util/env_windows.cc @@ -194,7 +194,7 @@ class WindowsRandomAccessFile : public RandomAccessFile { Status Read(uint64_t offset, size_t n, Slice* result, char* scratch) const override { DWORD bytes_read = 0; - OVERLAPPED overlapped = {0}; + OVERLAPPED overlapped = {}; overlapped.OffsetHigh = static_cast<DWORD>(offset >> 32); overlapped.Offset = static_cast<DWORD>(offset); diff --git a/src/logging.cpp b/src/logging.cpp index 1e2c1d5a77..a3f1d39be5 100644 --- a/src/logging.cpp +++ b/src/logging.cpp @@ -5,15 +5,17 @@ #include <fs.h> #include <logging.h> -#include <util/threadnames.h> #include <util/string.h> +#include <util/threadnames.h> #include <util/time.h> #include <algorithm> #include <array> #include <mutex> +#include <optional> const char * const DEFAULT_DEBUGLOGFILE = "debug.log"; +constexpr auto MAX_USER_SETABLE_SEVERITY_LEVEL{BCLog::Level::Info}; BCLog::Logger& LogInstance() { @@ -122,6 +124,19 @@ bool BCLog::Logger::WillLogCategory(BCLog::LogFlags category) const return (m_categories.load(std::memory_order_relaxed) & category) != 0; } +bool BCLog::Logger::WillLogCategoryLevel(BCLog::LogFlags category, BCLog::Level level) const +{ + // Log messages at Warning and Error level unconditionally, so that + // important troubleshooting information doesn't get lost. + if (level >= BCLog::Level::Warning) return true; + + if (!WillLogCategory(category)) return false; + + StdLockGuard scoped_lock(m_cs); + const auto it{m_category_log_levels.find(category)}; + return level >= (it == m_category_log_levels.end() ? LogLevel() : it->second); +} + bool BCLog::Logger::DefaultShrinkDebugFile() const { return m_categories == BCLog::NONE; @@ -135,7 +150,7 @@ struct CLogCategoryDesc { const CLogCategoryDesc LogCategories[] = { {BCLog::NONE, "0"}, - {BCLog::NONE, "none"}, + {BCLog::NONE, ""}, {BCLog::NET, "net"}, {BCLog::TOR, "tor"}, {BCLog::MEMPOOL, "mempool"}, @@ -184,11 +199,11 @@ bool GetLogCategory(BCLog::LogFlags& flag, const std::string& str) return false; } -std::string LogLevelToStr(BCLog::Level level) +std::string BCLog::Logger::LogLevelToStr(BCLog::Level level) const { switch (level) { - case BCLog::Level::None: - return "none"; + case BCLog::Level::Trace: + return "trace"; case BCLog::Level::Debug: return "debug"; case BCLog::Level::Info: @@ -197,6 +212,8 @@ std::string LogLevelToStr(BCLog::Level level) return "warning"; case BCLog::Level::Error: return "error"; + case BCLog::Level::None: + return ""; } assert(false); } @@ -206,7 +223,7 @@ std::string LogCategoryToStr(BCLog::LogFlags category) // Each log category string representation should sync with LogCategories switch (category) { case BCLog::LogFlags::NONE: - return "none"; + return ""; case BCLog::LogFlags::NET: return "net"; case BCLog::LogFlags::TOR: @@ -269,6 +286,25 @@ std::string LogCategoryToStr(BCLog::LogFlags category) assert(false); } +static std::optional<BCLog::Level> GetLogLevel(const std::string& level_str) +{ + if (level_str == "trace") { + return BCLog::Level::Trace; + } else if (level_str == "debug") { + return BCLog::Level::Debug; + } else if (level_str == "info") { + return BCLog::Level::Info; + } else if (level_str == "warning") { + return BCLog::Level::Warning; + } else if (level_str == "error") { + return BCLog::Level::Error; + } else if (level_str == "none") { + return BCLog::Level::None; + } else { + return std::nullopt; + } +} + std::vector<LogCategory> BCLog::Logger::LogCategoriesList() const { // Sort log categories by alphabetical order. @@ -287,6 +323,18 @@ std::vector<LogCategory> BCLog::Logger::LogCategoriesList() const return ret; } +/** Log severity levels that can be selected by the user. */ +static constexpr std::array<BCLog::Level, 3> LogLevelsList() +{ + return {BCLog::Level::Info, BCLog::Level::Debug, BCLog::Level::Trace}; +} + +std::string BCLog::Logger::LogLevelsString() const +{ + const auto& levels = LogLevelsList(); + return Join(std::vector<BCLog::Level>{levels.begin(), levels.end()}, ", ", [this](BCLog::Level level) { return LogLevelToStr(level); }); +} + std::string BCLog::Logger::LogTimestampStr(const std::string& str) { std::string strStamped; @@ -334,7 +382,7 @@ namespace BCLog { } } // namespace BCLog -void BCLog::Logger::LogPrintStr(const std::string& str, const std::string& logging_function, const std::string& source_file, const int source_line, const BCLog::LogFlags category, const BCLog::Level level) +void BCLog::Logger::LogPrintStr(const std::string& str, const std::string& logging_function, const std::string& source_file, int source_line, BCLog::LogFlags category, BCLog::Level level) { StdLockGuard scoped_lock(m_cs); std::string str_prefixed = LogEscapeMessage(str); @@ -364,7 +412,7 @@ void BCLog::Logger::LogPrintStr(const std::string& str, const std::string& loggi } if (m_log_threadnames && m_started_new_line) { - const auto threadname = util::ThreadGetInternalName(); + const auto& threadname = util::ThreadGetInternalName(); str_prefixed.insert(0, "[" + (threadname.empty() ? "unknown" : threadname) + "] "); } @@ -443,3 +491,24 @@ void BCLog::Logger::ShrinkDebugFile() else if (file != nullptr) fclose(file); } + +bool BCLog::Logger::SetLogLevel(const std::string& level_str) +{ + const auto level = GetLogLevel(level_str); + if (!level.has_value() || level.value() > MAX_USER_SETABLE_SEVERITY_LEVEL) return false; + m_log_level = level.value(); + return true; +} + +bool BCLog::Logger::SetCategoryLogLevel(const std::string& category_str, const std::string& level_str) +{ + BCLog::LogFlags flag; + if (!GetLogCategory(flag, category_str)) return false; + + const auto level = GetLogLevel(level_str); + if (!level.has_value() || level.value() > MAX_USER_SETABLE_SEVERITY_LEVEL) return false; + + StdLockGuard scoped_lock(m_cs); + m_category_log_levels[flag] = level.value(); + return true; +} diff --git a/src/logging.h b/src/logging.h index 8a896b6b33..fe91ee43a5 100644 --- a/src/logging.h +++ b/src/logging.h @@ -7,8 +7,8 @@ #define BITCOIN_LOGGING_H #include <fs.h> -#include <tinyformat.h> #include <threadsafety.h> +#include <tinyformat.h> #include <util/string.h> #include <atomic> @@ -17,6 +17,7 @@ #include <list> #include <mutex> #include <string> +#include <unordered_map> #include <vector> static const bool DEFAULT_LOGTIMEMICROS = false; @@ -68,12 +69,14 @@ namespace BCLog { ALL = ~(uint32_t)0, }; enum class Level { - Debug = 0, - None = 1, - Info = 2, - Warning = 3, - Error = 4, + Trace = 0, // High-volume or detailed logging for development/debugging + Debug, // Reasonably noisy logging, but still usable in production + Info, // Default + Warning, + Error, + None, // Internal use only }; + constexpr auto DEFAULT_LOG_LEVEL{Level::Debug}; class Logger { @@ -91,6 +94,13 @@ namespace BCLog { */ std::atomic_bool m_started_new_line{true}; + //! Category-specific log level. Overrides `m_log_level`. + std::unordered_map<LogFlags, Level> m_category_log_levels GUARDED_BY(m_cs); + + //! If there is no category-specific log level, all logs with a severity + //! level lower than `m_log_level` will be ignored. + std::atomic<Level> m_log_level{DEFAULT_LOG_LEVEL}; + /** Log categories bitfield. */ std::atomic<uint32_t> m_categories{0}; @@ -112,7 +122,7 @@ namespace BCLog { std::atomic<bool> m_reopen_file{false}; /** Send a string to the log output */ - void LogPrintStr(const std::string& str, const std::string& logging_function, const std::string& source_file, const int source_line, const BCLog::LogFlags category, const BCLog::Level level); + void LogPrintStr(const std::string& str, const std::string& logging_function, const std::string& source_file, int source_line, BCLog::LogFlags category, BCLog::Level level); /** Returns whether logs will be written to any output */ bool Enabled() const @@ -143,6 +153,22 @@ namespace BCLog { void ShrinkDebugFile(); + std::unordered_map<LogFlags, Level> CategoryLevels() const + { + StdLockGuard scoped_lock(m_cs); + return m_category_log_levels; + } + void SetCategoryLogLevel(const std::unordered_map<LogFlags, Level>& levels) + { + StdLockGuard scoped_lock(m_cs); + m_category_log_levels = levels; + } + bool SetCategoryLogLevel(const std::string& category_str, const std::string& level_str); + + Level LogLevel() const { return m_log_level.load(); } + void SetLogLevel(Level level) { m_log_level = level; } + bool SetLogLevel(const std::string& level); + uint32_t GetCategoryMask() const { return m_categories.load(); } void EnableCategory(LogFlags flag); @@ -151,6 +177,8 @@ namespace BCLog { bool DisableCategory(const std::string& str); bool WillLogCategory(LogFlags category) const; + bool WillLogCategoryLevel(LogFlags category, Level level) const; + /** Returns a vector of the log categories in alphabetical order. */ std::vector<LogCategory> LogCategoriesList() const; /** Returns a string with the log categories in alphabetical order. */ @@ -159,6 +187,12 @@ namespace BCLog { return Join(LogCategoriesList(), ", ", [&](const LogCategory& i) { return i.category; }); }; + //! Returns a string with all user-selectable log levels. + std::string LogLevelsString() const; + + //! Returns the string representation of a log level. + std::string LogLevelToStr(BCLog::Level level) const; + bool DefaultShrinkDebugFile() const; }; @@ -169,12 +203,7 @@ BCLog::Logger& LogInstance(); /** Return true if log accepts specified category, at the specified level. */ static inline bool LogAcceptCategory(BCLog::LogFlags category, BCLog::Level level) { - // Log messages at Warning and Error level unconditionally, so that - // important troubleshooting information doesn't get lost. - if (level >= BCLog::Level::Warning) { - return true; - } - return LogInstance().WillLogCategory(category); + return LogInstance().WillLogCategoryLevel(category, level); } /** Return true if str parses as a log category and set the flag */ @@ -199,13 +228,18 @@ static inline void LogPrintf_(const std::string& logging_function, const std::st } } - #define LogPrintLevel_(category, level, ...) LogPrintf_(__func__, __FILE__, __LINE__, category, level, __VA_ARGS__) +// Log unconditionally. #define LogPrintf(...) LogPrintLevel_(BCLog::LogFlags::NONE, BCLog::Level::None, __VA_ARGS__) +// Log unconditionally, prefixing the output with the passed category name. +#define LogPrintfCategory(category, ...) LogPrintLevel_(category, BCLog::Level::None, __VA_ARGS__) + // Use a macro instead of a function for conditional logging to prevent // evaluating arguments when logging for the category is not enabled. + +// Log conditionally, prefixing the output with the passed category name. #define LogPrint(category, ...) \ do { \ if (LogAcceptCategory((category), BCLog::Level::Debug)) { \ @@ -213,6 +247,7 @@ static inline void LogPrintf_(const std::string& logging_function, const std::st } \ } while (0) +// Log conditionally, prefixing the output with the passed category name and severity level. #define LogPrintLevel(category, level, ...) \ do { \ if (LogAcceptCategory((category), (level))) { \ diff --git a/src/mapport.cpp b/src/mapport.cpp index 235e6f904c..6262e51879 100644 --- a/src/mapport.cpp +++ b/src/mapport.cpp @@ -19,7 +19,7 @@ #include <util/thread.h> #ifdef USE_NATPMP -#include <compat.h> +#include <compat/compat.h> #include <natpmp.h> #endif // USE_NATPMP diff --git a/src/memusage.h b/src/memusage.h index a6e894129a..fd85a7956c 100644 --- a/src/memusage.h +++ b/src/memusage.h @@ -8,9 +8,8 @@ #include <indirectmap.h> #include <prevector.h> -#include <stdlib.h> - #include <cassert> +#include <cstdlib> #include <map> #include <memory> #include <set> diff --git a/src/minisketch/README.md b/src/minisketch/README.md index c0cfdc1623..f8b89ff33e 100644 --- a/src/minisketch/README.md +++ b/src/minisketch/README.md @@ -203,8 +203,8 @@ Some improvements that are still TODO: * <a name="myfootnote4">[4]</a> Bhaskar Biswas, Vincent Herbert. *Efficient Root Finding of Polynomials over Fields of Characteristic 2.* 2009. hal-00626997. [[URL]](https://hal.archives-ouvertes.fr/hal-00626997) [[PDF]](https://hal.archives-ouvertes.fr/hal-00626997/document) * <a name="myfootnote6">[6]</a> Eppstein, David, Michael T. Goodrich, Frank Uyeda, and George Varghese. *What's the difference?: efficient set reconciliation without prior context.* ACM SIGCOMM Computer Communication Review, vol. 41, no. 4, pp. 218-229. ACM, 2011. [[PDF]](https://www.ics.uci.edu/~eppstein/pubs/EppGooUye-SIGCOMM-11.pdf) * <a name="myfootnote7">[7]</a> Goodrich, Michael T. and Michael Mitzenmacher. *Invertible bloom lookup tables.* 2011 49th Annual Allerton Conference on Communication, Control, and Computing (Allerton) (2011): 792-799. [[PDF]](https://arxiv.org/pdf/1101.2245.pdf) -* <a name="myfootnote8">[8]</a> Maxwell, Gregory F. *[Blocksonly mode BW savings, the limits of efficient block xfer, and better relay](https://bitcointalk.org/index.php?topic=1377345.0)* Bitcointalk 2016, *[Technical notes on mempool synchronizing relay](https://people.xiph.org/~greg/mempool_sync_relay.txt)* #bitcoin-wizards 2016. -* <a name="myfootnote9">[9]</a> Maxwell, Gregory F. *[Block network coding](https://en.bitcoin.it/wiki/User:Gmaxwell/block_network_coding)* Bitcoin Wiki 2014, *[Technical notes on efficient block xfer](https://people.xiph.org/~greg/efficient.block.xfer.txt)* #bitcoin-wizards 2015. +* <a name="myfootnote8">[8]</a> Maxwell, Gregory F. *[Blocksonly mode BW savings, the limits of efficient block xfer, and better relay](https://bitcointalk.org/index.php?topic=1377345.0)* Bitcointalk 2016, *[Technical notes on mempool synchronizing relay](https://nt4tn.net/tech-notes/2016.mempool_sync_relay.txt)* #bitcoin-wizards 2016. +* <a name="myfootnote9">[9]</a> Maxwell, Gregory F. *[Block network coding](https://en.bitcoin.it/wiki/User:Gmaxwell/block_network_coding)* Bitcoin Wiki 2014, *[Technical notes on efficient block xfer](https://nt4tn.net/tech-notes/201512.efficient.block.xfer.txt)* #bitcoin-wizards 2015. * <a name="myfootnote10">[10]</a> Ruffing, Tim, Moreno-Sanchez, Pedro, Aniket, Kate, *P2P Mixing and Unlinkable Bitcoin Transactions* NDSS Symposium 2017 [[URL]](https://eprint.iacr.org/2016/824) [[PDF]](https://eprint.iacr.org/2016/824.pdf) * <a name="myfootnote11">[11]</a> Y. Misky, A. Trachtenberg, R. Zippel. *Set Reconciliation with Nearly Optimal Communication Complexity.* Cornell University, 2000. [[URL]](https://ecommons.cornell.edu/handle/1813/5803) [[PDF]](https://ecommons.cornell.edu/bitstream/handle/1813/5803/2000-1813.pdf) * <a name="myfootnote12">[12]</a> Itoh, Toshiya, and Shigeo Tsujii. "A fast algorithm for computing multiplicative inverses in GF (2m) using normal bases." Information and computation 78, no. 3 (1988): 171-177. [[URL]](https://www.sciencedirect.com/science/article/pii/0890540188900247) diff --git a/src/minisketch/include/minisketch.h b/src/minisketch/include/minisketch.h index 0b5d8372e8..24d6b4e1c0 100644 --- a/src/minisketch/include/minisketch.h +++ b/src/minisketch/include/minisketch.h @@ -5,7 +5,8 @@ #include <stdlib.h> #ifdef _MSC_VER -# include <compat.h> +# include <BaseTsd.h> + typedef SSIZE_T ssize_t; #else # include <unistd.h> #endif diff --git a/src/net.cpp b/src/net.cpp index 82b5a69eb5..0736f3ef1b 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -13,19 +13,19 @@ #include <addrman.h> #include <banman.h> #include <clientversion.h> -#include <compat.h> +#include <compat/compat.h> #include <consensus/consensus.h> #include <crypto/sha256.h> +#include <node/eviction.h> #include <fs.h> #include <i2p.h> #include <net_permissions.h> #include <netaddress.h> #include <netbase.h> -#include <node/ui_interface.h> +#include <node/interface_ui.h> #include <protocol.h> #include <random.h> #include <scheduler.h> -#include <util/designator.h> #include <util/sock.h> #include <util/strencodings.h> #include <util/syscall_sandbox.h> @@ -85,19 +85,18 @@ static constexpr int DNSSEEDS_DELAY_PEER_THRESHOLD = 1000; // "many" vs "few" pe /** The default timeframe for -maxuploadtarget. 1 day. */ 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 +// A random time period (0 to 1 seconds) is added to feeler connections to prevent synchronization. +static constexpr auto FEELER_SLEEP_WINDOW{1s}; /** Used to pass flags to the Bind() function */ enum BindFlags { BF_NONE = 0, - BF_EXPLICIT = (1U << 0), - BF_REPORT_ERROR = (1U << 1), + BF_REPORT_ERROR = (1U << 0), /** * Do not call AddLocal() for our special addresses, e.g., for incoming * Tor connections, to prevent gossiping them over the network. */ - BF_DONT_ADVERTISE = (1U << 2), + BF_DONT_ADVERTISE = (1U << 1), }; // The set of sockets cannot be modified while waiting @@ -114,7 +113,7 @@ static const uint64_t RANDOMIZER_ID_ADDRCACHE = 0x1cf2e4ddd306dda9ULL; // SHA256 // bool fDiscover = true; bool fListen = true; -Mutex g_maplocalhost_mutex; +GlobalMutex g_maplocalhost_mutex; std::map<CNetAddr, LocalServiceInfo> mapLocalHost GUARDED_BY(g_maplocalhost_mutex); static bool vfLimited[NET_MAX] GUARDED_BY(g_maplocalhost_mutex) = {}; std::string strSubVersion; @@ -187,7 +186,7 @@ static std::vector<CAddress> ConvertSeeds(const std::vector<uint8_t> &vSeedsIn) // it'll get a pile of addresses with newer timestamps. // Seed nodes are given a random 'last seen time' of between one and two // weeks ago. - const int64_t nOneWeek = 7*24*60*60; + const auto one_week{7 * 24h}; std::vector<CAddress> vSeedsOut; FastRandomContext rng; CDataStream s(vSeedsIn, SER_NETWORK, PROTOCOL_VERSION | ADDRV2_FORMAT); @@ -195,7 +194,7 @@ static std::vector<CAddress> ConvertSeeds(const std::vector<uint8_t> &vSeedsIn) CService endpoint; s >> endpoint; CAddress addr{endpoint, GetDesirableServiceFlags(NODE_NONE)}; - addr.nTime = GetTime() - rng.randrange(nOneWeek) - nOneWeek; + addr.nTime = rng.rand_uniform_delay(Now<NodeSeconds>() - one_week, -one_week); LogPrint(BCLog::NET, "Added hardcoded seed: %s\n", addr.ToString()); vSeedsOut.push_back(addr); } @@ -206,16 +205,13 @@ static std::vector<CAddress> ConvertSeeds(const std::vector<uint8_t> &vSeedsIn) // Otherwise, return the unroutable 0.0.0.0 but filled in with // the normal parameters, since the IP may be changed to a useful // one by discovery. -CAddress GetLocalAddress(const CNetAddr *paddrPeer, ServiceFlags nLocalServices) +CService GetLocalAddress(const CNetAddr& addrPeer) { - CAddress ret(CService(CNetAddr(),GetListenPort()), nLocalServices); CService addr; - if (GetLocal(addr, paddrPeer)) - { - ret = CAddress(addr, nLocalServices); + if (GetLocal(addr, &addrPeer)) { + return addr; } - ret.nTime = GetAdjustedTime(); - return ret; + return CService{CNetAddr(), GetListenPort()}; } static int GetnScore(const CService& addr) @@ -233,35 +229,35 @@ bool IsPeerAddrLocalGood(CNode *pnode) IsReachable(addrLocal.GetNetwork()); } -std::optional<CAddress> GetLocalAddrForPeer(CNode *pnode) +std::optional<CService> GetLocalAddrForPeer(CNode& node) { - CAddress addrLocal = GetLocalAddress(&pnode->addr, pnode->GetLocalServices()); + CService addrLocal{GetLocalAddress(node.addr)}; if (gArgs.GetBoolArg("-addrmantest", false)) { // use IPv4 loopback during addrmantest - addrLocal = CAddress(CService(LookupNumeric("127.0.0.1", GetListenPort())), pnode->GetLocalServices()); + addrLocal = CService(LookupNumeric("127.0.0.1", GetListenPort())); } // If discovery is enabled, sometimes give our peer the address it // tells us that it sees us as in case it has a better idea of our // address than we do. FastRandomContext rng; - if (IsPeerAddrLocalGood(pnode) && (!addrLocal.IsRoutable() || + if (IsPeerAddrLocalGood(&node) && (!addrLocal.IsRoutable() || rng.randbits((GetnScore(addrLocal) > LOCAL_MANUAL) ? 3 : 1) == 0)) { - if (pnode->IsInboundConn()) { + if (node.IsInboundConn()) { // For inbound connections, assume both the address and the port // as seen from the peer. - addrLocal = CAddress{pnode->GetAddrLocal(), addrLocal.nServices}; + addrLocal = CService{node.GetAddrLocal()}; } else { // For outbound connections, assume just the address as seen from // the peer and leave the port in `addrLocal` as returned by // `GetLocalAddress()` above. The peer has no way to observe our // listening port when we have initiated the connection. - addrLocal.SetIP(pnode->GetAddrLocal()); + addrLocal.SetIP(node.GetAddrLocal()); } } if (addrLocal.IsRoutable() || gArgs.GetBoolArg("-addrmantest", false)) { - LogPrint(BCLog::NET, "Advertising address %s to peer=%d\n", addrLocal.ToString(), pnode->GetId()); + LogPrint(BCLog::NET, "Advertising address %s to peer=%d\n", addrLocal.ToString(), node.GetId()); return addrLocal; } // Address is unroutable. Don't advertise. @@ -422,13 +418,13 @@ bool CConnman::CheckIncomingNonce(uint64_t nonce) } /** Get the bind address for a socket as CAddress */ -static CAddress GetBindAddress(SOCKET sock) +static CAddress GetBindAddress(const Sock& sock) { CAddress addr_bind; struct sockaddr_storage sockaddr_bind; socklen_t sockaddr_bind_len = sizeof(sockaddr_bind); - if (sock != INVALID_SOCKET) { - if (!getsockname(sock, (struct sockaddr*)&sockaddr_bind, &sockaddr_bind_len)) { + if (sock.Get() != INVALID_SOCKET) { + if (!sock.GetSockName((struct sockaddr*)&sockaddr_bind, &sockaddr_bind_len)) { addr_bind.SetSockAddr((const struct sockaddr*)&sockaddr_bind); } else { LogPrintLevel(BCLog::NET, BCLog::Level::Warning, "getsockname failed\n"); @@ -454,10 +450,9 @@ CNode* CConnman::ConnectNode(CAddress addrConnect, const char *pszDest, bool fCo } } - /// debug print LogPrintLevel(BCLog::NET, BCLog::Level::Debug, "trying connection %s lastseen=%.1fhrs\n", - pszDest ? pszDest : addrConnect.ToString(), - pszDest ? 0.0 : (double)(GetAdjustedTime() - addrConnect.nTime) / 3600.0); + pszDest ? pszDest : addrConnect.ToString(), + Ticks<HoursDouble>(pszDest ? 0h : Now<NodeSeconds>() - addrConnect.nTime)); // Resolve const uint16_t default_port{pszDest != nullptr ? Params().GetDefaultPort(pszDest) : @@ -488,18 +483,27 @@ CNode* CConnman::ConnectNode(CAddress addrConnect, const char *pszDest, bool fCo Proxy proxy; CAddress addr_bind; assert(!addr_bind.IsValid()); + std::unique_ptr<i2p::sam::Session> i2p_transient_session; if (addrConnect.IsValid()) { + const bool use_proxy{GetProxy(addrConnect.GetNetwork(), proxy)}; bool proxyConnectionFailed = false; - if (addrConnect.GetNetwork() == NET_I2P && m_i2p_sam_session.get() != nullptr) { + if (addrConnect.GetNetwork() == NET_I2P && use_proxy) { i2p::Connection conn; - if (m_i2p_sam_session->Connect(addrConnect, conn, proxyConnectionFailed)) { - connected = true; + + if (m_i2p_sam_session) { + connected = m_i2p_sam_session->Connect(addrConnect, conn, proxyConnectionFailed); + } else { + i2p_transient_session = std::make_unique<i2p::sam::Session>(proxy.proxy, &interruptNet); + connected = i2p_transient_session->Connect(addrConnect, conn, proxyConnectionFailed); + } + + if (connected) { sock = std::move(conn.sock); addr_bind = CAddress{conn.me, NODE_NONE}; } - } else if (GetProxy(addrConnect.GetNetwork(), proxy)) { + } else if (use_proxy) { sock = CreateSock(proxy.proxy); if (!sock) { return nullptr; @@ -540,10 +544,9 @@ CNode* CConnman::ConnectNode(CAddress addrConnect, const char *pszDest, bool fCo NodeId id = GetNewNodeId(); uint64_t nonce = GetDeterministicRandomizer(RANDOMIZER_ID_LOCALHOSTNONCE).Write(id).Finalize(); if (!addr_bind.IsValid()) { - addr_bind = GetBindAddress(sock->Get()); + addr_bind = GetBindAddress(*sock); } CNode* pnode = new CNode(id, - nLocalServices, std::move(sock), addrConnect, CalculateKeyedNetGroup(addrConnect), @@ -551,7 +554,8 @@ CNode* CConnman::ConnectNode(CAddress addrConnect, const char *pszDest, bool fCo addr_bind, pszDest ? pszDest : "", conn_type, - /*inbound_onion=*/false); + /*inbound_onion=*/false, + CNodeOptions{ .i2p_sam_session = std::move(i2p_transient_session) }); pnode->AddRef(); // We're making a new connection, harvest entropy from the time (and our peer count) @@ -568,6 +572,7 @@ void CNode::CloseSocketDisconnect() LogPrint(BCLog::NET, "disconnecting peer=%d\n", id); m_sock.reset(); } + m_i2p_sam_session.reset(); } void CConnman::AddWhitelistPermissionFlags(NetPermissionFlags& flags, const CNetAddr &addr) const { @@ -576,26 +581,6 @@ void CConnman::AddWhitelistPermissionFlags(NetPermissionFlags& flags, const CNet } } -std::string ConnectionTypeAsString(ConnectionType conn_type) -{ - switch (conn_type) { - case ConnectionType::INBOUND: - return "inbound"; - case ConnectionType::MANUAL: - return "manual"; - case ConnectionType::FEELER: - return "feeler"; - case ConnectionType::OUTBOUND_FULL_RELAY: - return "outbound-full-relay"; - case ConnectionType::BLOCK_RELAY: - return "block-relay-only"; - case ConnectionType::ADDR_FETCH: - return "addr-fetch"; - } // no default case, so the compiler can warn about missing cases - - assert(false); -} - CService CNode::GetAddrLocal() const { AssertLockNotHeld(m_addr_local_mutex); @@ -623,7 +608,6 @@ Network CNode::ConnectedThroughNetwork() const void CNode::CopyStats(CNodeStats& stats) { stats.nodeid = this->GetId(); - X(nServices); X(addr); X(addrBind); stats.m_network = ConnectedThroughNetwork(); @@ -652,7 +636,7 @@ void CNode::CopyStats(CNodeStats& stats) X(mapRecvBytesPerMsgType); X(nRecvBytes); } - X(m_permissionFlags); + X(m_permission_flags); X(m_last_ping_time); X(m_min_ping_time); @@ -812,7 +796,8 @@ CNetMessage V1TransportDeserializer::GetMessage(const std::chrono::microseconds return msg; } -void V1TransportSerializer::prepareForTransport(CSerializedNetMsg& msg, std::vector<unsigned char>& header) { +void V1TransportSerializer::prepareForTransport(CSerializedNetMsg& msg, std::vector<unsigned char>& header) const +{ // create dbl-sha256 checksum uint256 hash = Hash(msg.data); @@ -877,210 +862,6 @@ size_t CConnman::SocketSendData(CNode& node) const return nSentSize; } -static bool ReverseCompareNodeMinPingTime(const NodeEvictionCandidate &a, const NodeEvictionCandidate &b) -{ - return a.m_min_ping_time > b.m_min_ping_time; -} - -static bool ReverseCompareNodeTimeConnected(const NodeEvictionCandidate &a, const NodeEvictionCandidate &b) -{ - return a.m_connected > b.m_connected; -} - -static bool CompareNetGroupKeyed(const NodeEvictionCandidate &a, const NodeEvictionCandidate &b) { - return a.nKeyedNetGroup < b.nKeyedNetGroup; -} - -static bool CompareNodeBlockTime(const NodeEvictionCandidate &a, const NodeEvictionCandidate &b) -{ - // There is a fall-through here because it is common for a node to have many peers which have not yet relayed a block. - if (a.m_last_block_time != b.m_last_block_time) return a.m_last_block_time < b.m_last_block_time; - if (a.fRelevantServices != b.fRelevantServices) return b.fRelevantServices; - return a.m_connected > b.m_connected; -} - -static bool CompareNodeTXTime(const NodeEvictionCandidate &a, const NodeEvictionCandidate &b) -{ - // There is a fall-through here because it is common for a node to have more than a few peers that have not yet relayed txn. - if (a.m_last_tx_time != b.m_last_tx_time) return a.m_last_tx_time < b.m_last_tx_time; - if (a.m_relay_txs != b.m_relay_txs) return b.m_relay_txs; - if (a.fBloomFilter != b.fBloomFilter) return a.fBloomFilter; - return a.m_connected > b.m_connected; -} - -// Pick out the potential block-relay only peers, and sort them by last block time. -static bool CompareNodeBlockRelayOnlyTime(const NodeEvictionCandidate &a, const NodeEvictionCandidate &b) -{ - if (a.m_relay_txs != b.m_relay_txs) return a.m_relay_txs; - if (a.m_last_block_time != b.m_last_block_time) return a.m_last_block_time < b.m_last_block_time; - if (a.fRelevantServices != b.fRelevantServices) return b.fRelevantServices; - return a.m_connected > b.m_connected; -} - -/** - * Sort eviction candidates by network/localhost and connection uptime. - * Candidates near the beginning are more likely to be evicted, and those - * near the end are more likely to be protected, e.g. less likely to be evicted. - * - First, nodes that are not `is_local` and that do not belong to `network`, - * sorted by increasing uptime (from most recently connected to connected longer). - * - Then, nodes that are `is_local` or belong to `network`, sorted by increasing uptime. - */ -struct CompareNodeNetworkTime { - const bool m_is_local; - const Network m_network; - CompareNodeNetworkTime(bool is_local, Network network) : m_is_local(is_local), m_network(network) {} - bool operator()(const NodeEvictionCandidate& a, const NodeEvictionCandidate& b) const - { - if (m_is_local && a.m_is_local != b.m_is_local) return b.m_is_local; - if ((a.m_network == m_network) != (b.m_network == m_network)) return b.m_network == m_network; - return a.m_connected > b.m_connected; - }; -}; - -//! 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(std::remove_if(elements.end() - eraseSize, elements.end(), predicate), elements.end()); -} - -void ProtectEvictionCandidatesByRatio(std::vector<NodeEvictionCandidate>& eviction_candidates) -{ - // 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 of these protected - // spots for Tor/onion, localhost, I2P, and CJDNS peers, even if they're not longest uptime - // overall. This helps protect these higher-latency peers that tend to be otherwise - // disadvantaged under our eviction criteria. - const size_t initial_size = eviction_candidates.size(); - const size_t total_protect_size{initial_size / 2}; - - // Disadvantaged networks to protect. In the case of equal counts, earlier array members - // have the first opportunity to recover unused slots from the previous iteration. - struct Net { bool is_local; Network id; size_t count; }; - std::array<Net, 4> networks{ - {{false, NET_CJDNS, 0}, {false, NET_I2P, 0}, {/*localhost=*/true, NET_MAX, 0}, {false, NET_ONION, 0}}}; - - // Count and store the number of eviction candidates per network. - for (Net& n : networks) { - n.count = std::count_if(eviction_candidates.cbegin(), eviction_candidates.cend(), - [&n](const NodeEvictionCandidate& c) { - return n.is_local ? c.m_is_local : c.m_network == n.id; - }); - } - // Sort `networks` by ascending candidate count, to give networks having fewer candidates - // the first opportunity to recover unused protected slots from the previous iteration. - std::stable_sort(networks.begin(), networks.end(), [](Net a, Net b) { return a.count < b.count; }); - - // Protect up to 25% of the eviction candidates by disadvantaged network. - const size_t max_protect_by_network{total_protect_size / 2}; - size_t num_protected{0}; - - while (num_protected < max_protect_by_network) { - // Count the number of disadvantaged networks from which we have peers to protect. - auto num_networks = std::count_if(networks.begin(), networks.end(), [](const Net& n) { return n.count; }); - if (num_networks == 0) { - break; - } - const size_t disadvantaged_to_protect{max_protect_by_network - num_protected}; - const size_t protect_per_network{std::max(disadvantaged_to_protect / num_networks, static_cast<size_t>(1))}; - // Early exit flag if there are no remaining candidates by disadvantaged network. - bool protected_at_least_one{false}; - - for (Net& n : networks) { - if (n.count == 0) continue; - const size_t before = eviction_candidates.size(); - EraseLastKElements(eviction_candidates, CompareNodeNetworkTime(n.is_local, n.id), - protect_per_network, [&n](const NodeEvictionCandidate& c) { - return n.is_local ? c.m_is_local : c.m_network == n.id; - }); - const size_t after = eviction_candidates.size(); - if (before > after) { - protected_at_least_one = true; - const size_t delta{before - after}; - num_protected += delta; - if (num_protected >= max_protect_by_network) { - break; - } - n.count -= delta; - } - } - if (!protected_at_least_one) { - break; - } - } - - // Calculate how many we removed, and update our total number of peers that - // we want to protect based on uptime accordingly. - assert(num_protected == initial_size - eviction_candidates.size()); - const size_t remaining_to_protect{total_protect_size - num_protected}; - EraseLastKElements(eviction_candidates, ReverseCompareNodeTimeConnected, remaining_to_protect); -} - -[[nodiscard]] std::optional<NodeId> SelectNodeToEvict(std::vector<NodeEvictionCandidate>&& vEvictionCandidates) -{ - // Protect connections with certain characteristics - - // Deterministically select 4 peers to protect by netgroup. - // An attacker cannot predict which netgroups will be protected - EraseLastKElements(vEvictionCandidates, CompareNetGroupKeyed, 4); - // Protect the 8 nodes with the lowest minimum ping time. - // An attacker cannot manipulate this metric without physically moving nodes closer to the target. - EraseLastKElements(vEvictionCandidates, ReverseCompareNodeMinPingTime, 8); - // Protect 4 nodes that most recently sent us novel transactions accepted into our mempool. - // 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. - EraseLastKElements(vEvictionCandidates, CompareNodeBlockRelayOnlyTime, 8, - [](const NodeEvictionCandidate& n) { return !n.m_relay_txs && 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 some of the remaining eviction candidates by ratios of desirable - // or disadvantaged characteristics. - ProtectEvictionCandidatesByRatio(vEvictionCandidates); - - 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) - // then we probably don't want to evict it no matter what. - if (std::any_of(vEvictionCandidates.begin(),vEvictionCandidates.end(),[](NodeEvictionCandidate const &n){return n.prefer_evict;})) { - vEvictionCandidates.erase(std::remove_if(vEvictionCandidates.begin(),vEvictionCandidates.end(), - [](NodeEvictionCandidate const &n){return !n.prefer_evict;}),vEvictionCandidates.end()); - } - - // Identify the network group with the most connections and youngest member. - // (vEvictionCandidates is already sorted by reverse connect time) - uint64_t naMostConnections; - unsigned int nMostConnections = 0; - std::chrono::seconds nMostConnectionsTime{0}; - std::map<uint64_t, std::vector<NodeEvictionCandidate> > mapNetGroupNodes; - for (const NodeEvictionCandidate &node : vEvictionCandidates) { - std::vector<NodeEvictionCandidate> &group = mapNetGroupNodes[node.nKeyedNetGroup]; - group.push_back(node); - const auto grouptime{group[0].m_connected}; - - if (group.size() > nMostConnections || (group.size() == nMostConnections && grouptime > nMostConnectionsTime)) { - nMostConnections = group.size(); - nMostConnectionsTime = grouptime; - naMostConnections = node.nKeyedNetGroup; - } - } - - // Reduce to the network group with the most connections - vEvictionCandidates = std::move(mapNetGroupNodes[naMostConnections]); - - // Disconnect from the network group with the most connections - return vEvictionCandidates.front().id; -} - /** Try to find a connection to evict when the node is full. * Extreme care must be taken to avoid opening the node to attacker * triggered network partitioning. @@ -1096,25 +877,23 @@ bool CConnman::AttemptToEvictConnection() LOCK(m_nodes_mutex); for (const CNode* node : m_nodes) { - if (node->HasPermission(NetPermissionFlags::NoBan)) - continue; - if (!node->IsInboundConn()) - continue; if (node->fDisconnect) continue; NodeEvictionCandidate candidate{ - Desig(id) node->GetId(), - Desig(m_connected) node->m_connected, - Desig(m_min_ping_time) node->m_min_ping_time, - Desig(m_last_block_time) node->m_last_block_time, - Desig(m_last_tx_time) node->m_last_tx_time, - Desig(fRelevantServices) HasAllDesirableServiceFlags(node->nServices), - Desig(m_relay_txs) node->m_relays_txs.load(), - Desig(fBloomFilter) node->m_bloom_filter_loaded.load(), - Desig(nKeyedNetGroup) node->nKeyedNetGroup, - Desig(prefer_evict) node->m_prefer_evict, - Desig(m_is_local) node->addr.IsLocal(), - Desig(m_network) node->ConnectedThroughNetwork(), + .id = node->GetId(), + .m_connected = node->m_connected, + .m_min_ping_time = node->m_min_ping_time, + .m_last_block_time = node->m_last_block_time, + .m_last_tx_time = node->m_last_tx_time, + .fRelevantServices = node->m_has_all_wanted_services, + .m_relay_txs = node->m_relays_txs.load(), + .fBloomFilter = node->m_bloom_filter_loaded.load(), + .nKeyedNetGroup = node->nKeyedNetGroup, + .prefer_evict = node->m_prefer_evict, + .m_is_local = node->addr.IsLocal(), + .m_network = node->ConnectedThroughNetwork(), + .m_noban = node->HasPermission(NetPermissionFlags::NoBan), + .m_conn_type = node->m_conn_type, }; vEvictionCandidates.push_back(candidate); } @@ -1154,29 +933,29 @@ void CConnman::AcceptConnection(const ListenSocket& hListenSocket) { addr = CAddress{MaybeFlipIPv6toCJDNS(addr), NODE_NONE}; } - const CAddress addr_bind{MaybeFlipIPv6toCJDNS(GetBindAddress(sock->Get())), NODE_NONE}; + const CAddress addr_bind{MaybeFlipIPv6toCJDNS(GetBindAddress(*sock)), NODE_NONE}; - NetPermissionFlags permissionFlags = NetPermissionFlags::None; - hListenSocket.AddSocketPermissionFlags(permissionFlags); + NetPermissionFlags permission_flags = NetPermissionFlags::None; + hListenSocket.AddSocketPermissionFlags(permission_flags); - CreateNodeFromAcceptedSocket(std::move(sock), permissionFlags, addr_bind, addr); + CreateNodeFromAcceptedSocket(std::move(sock), permission_flags, addr_bind, addr); } void CConnman::CreateNodeFromAcceptedSocket(std::unique_ptr<Sock>&& sock, - NetPermissionFlags permissionFlags, + NetPermissionFlags permission_flags, const CAddress& addr_bind, const CAddress& addr) { int nInbound = 0; int nMaxInbound = nMaxConnections - m_max_outbound; - AddWhitelistPermissionFlags(permissionFlags, addr); - if (NetPermissions::HasFlag(permissionFlags, NetPermissionFlags::Implicit)) { - NetPermissions::ClearFlag(permissionFlags, NetPermissionFlags::Implicit); - if (gArgs.GetBoolArg("-whitelistforcerelay", DEFAULT_WHITELISTFORCERELAY)) NetPermissions::AddFlag(permissionFlags, NetPermissionFlags::ForceRelay); - if (gArgs.GetBoolArg("-whitelistrelay", DEFAULT_WHITELISTRELAY)) NetPermissions::AddFlag(permissionFlags, NetPermissionFlags::Relay); - NetPermissions::AddFlag(permissionFlags, NetPermissionFlags::Mempool); - NetPermissions::AddFlag(permissionFlags, NetPermissionFlags::NoBan); + AddWhitelistPermissionFlags(permission_flags, addr); + if (NetPermissions::HasFlag(permission_flags, NetPermissionFlags::Implicit)) { + NetPermissions::ClearFlag(permission_flags, NetPermissionFlags::Implicit); + if (gArgs.GetBoolArg("-whitelistforcerelay", DEFAULT_WHITELISTFORCERELAY)) NetPermissions::AddFlag(permission_flags, NetPermissionFlags::ForceRelay); + if (gArgs.GetBoolArg("-whitelistrelay", DEFAULT_WHITELISTRELAY)) NetPermissions::AddFlag(permission_flags, NetPermissionFlags::Relay); + NetPermissions::AddFlag(permission_flags, NetPermissionFlags::Mempool); + NetPermissions::AddFlag(permission_flags, NetPermissionFlags::NoBan); } { @@ -1207,7 +986,7 @@ void CConnman::CreateNodeFromAcceptedSocket(std::unique_ptr<Sock>&& sock, // Don't accept connections from banned peers. bool banned = m_banman && m_banman->IsBanned(addr); - if (!NetPermissions::HasFlag(permissionFlags, NetPermissionFlags::NoBan) && banned) + if (!NetPermissions::HasFlag(permission_flags, NetPermissionFlags::NoBan) && banned) { LogPrint(BCLog::NET, "connection from %s dropped (banned)\n", addr.ToString()); return; @@ -1215,7 +994,7 @@ void CConnman::CreateNodeFromAcceptedSocket(std::unique_ptr<Sock>&& sock, // Only accept connections from discouraged peers if our inbound slots aren't (almost) full. bool discouraged = m_banman && m_banman->IsDiscouraged(addr); - if (!NetPermissions::HasFlag(permissionFlags, NetPermissionFlags::NoBan) && nInbound + 1 >= nMaxInbound && discouraged) + if (!NetPermissions::HasFlag(permission_flags, NetPermissionFlags::NoBan) && nInbound + 1 >= nMaxInbound && discouraged) { LogPrint(BCLog::NET, "connection from %s dropped (discouraged)\n", addr.ToString()); return; @@ -1234,13 +1013,12 @@ void CConnman::CreateNodeFromAcceptedSocket(std::unique_ptr<Sock>&& sock, uint64_t nonce = GetDeterministicRandomizer(RANDOMIZER_ID_LOCALHOSTNONCE).Write(id).Finalize(); ServiceFlags nodeServices = nLocalServices; - if (NetPermissions::HasFlag(permissionFlags, NetPermissionFlags::BloomFilter)) { + if (NetPermissions::HasFlag(permission_flags, NetPermissionFlags::BloomFilter)) { nodeServices = static_cast<ServiceFlags>(nodeServices | NODE_BLOOM); } const bool inbound_onion = std::find(m_onion_binds.begin(), m_onion_binds.end(), addr_bind) != m_onion_binds.end(); CNode* pnode = new CNode(id, - nodeServices, std::move(sock), addr, CalculateKeyedNetGroup(addr), @@ -1248,11 +1026,13 @@ void CConnman::CreateNodeFromAcceptedSocket(std::unique_ptr<Sock>&& sock, addr_bind, /*addrNameIn=*/"", ConnectionType::INBOUND, - inbound_onion); + inbound_onion, + CNodeOptions{ + .permission_flags = permission_flags, + .prefer_evict = discouraged, + }); pnode->AddRef(); - pnode->m_permissionFlags = permissionFlags; - pnode->m_prefer_evict = discouraged; - m_msgproc->InitializeNode(pnode); + m_msgproc->InitializeNode(*pnode, nodeServices); LogPrint(BCLog::NET, "connection from %s accepted\n", addr.ToString()); @@ -1404,13 +1184,12 @@ bool CConnman::InactivityCheck(const CNode& node) const return false; } -bool CConnman::GenerateSelectSet(const std::vector<CNode*>& nodes, - std::set<SOCKET>& recv_set, - std::set<SOCKET>& send_set, - std::set<SOCKET>& error_set) +Sock::EventsPerSock CConnman::GenerateWaitSockets(Span<CNode* const> nodes) { + Sock::EventsPerSock events_per_sock; + for (const ListenSocket& hListenSocket : vhListenSocket) { - recv_set.insert(hListenSocket.sock->Get()); + events_per_sock.emplace(hListenSocket.sock, Sock::Events{Sock::RECV}); } for (CNode* pnode : nodes) { @@ -1437,172 +1216,49 @@ bool CConnman::GenerateSelectSet(const std::vector<CNode*>& nodes, continue; } - error_set.insert(pnode->m_sock->Get()); + Sock::Event requested{0}; if (select_send) { - send_set.insert(pnode->m_sock->Get()); - continue; - } - if (select_recv) { - recv_set.insert(pnode->m_sock->Get()); - } - } - - return !recv_set.empty() || !send_set.empty() || !error_set.empty(); -} - -#ifdef USE_POLL -void CConnman::SocketEvents(const std::vector<CNode*>& nodes, - std::set<SOCKET>& recv_set, - std::set<SOCKET>& send_set, - std::set<SOCKET>& error_set) -{ - std::set<SOCKET> recv_select_set, send_select_set, error_select_set; - if (!GenerateSelectSet(nodes, recv_select_set, send_select_set, error_select_set)) { - interruptNet.sleep_for(std::chrono::milliseconds(SELECT_TIMEOUT_MILLISECONDS)); - return; - } - - std::unordered_map<SOCKET, struct pollfd> pollfds; - for (SOCKET socket_id : recv_select_set) { - pollfds[socket_id].fd = socket_id; - pollfds[socket_id].events |= POLLIN; - } - - for (SOCKET socket_id : send_select_set) { - pollfds[socket_id].fd = socket_id; - pollfds[socket_id].events |= POLLOUT; - } - - for (SOCKET socket_id : error_select_set) { - pollfds[socket_id].fd = socket_id; - // These flags are ignored, but we set them for clarity - pollfds[socket_id].events |= POLLERR|POLLHUP; - } - - std::vector<struct pollfd> vpollfds; - vpollfds.reserve(pollfds.size()); - for (auto it : pollfds) { - vpollfds.push_back(std::move(it.second)); - } - - if (poll(vpollfds.data(), vpollfds.size(), SELECT_TIMEOUT_MILLISECONDS) < 0) return; - - if (interruptNet) return; - - for (struct pollfd pollfd_entry : vpollfds) { - if (pollfd_entry.revents & POLLIN) recv_set.insert(pollfd_entry.fd); - if (pollfd_entry.revents & POLLOUT) send_set.insert(pollfd_entry.fd); - if (pollfd_entry.revents & (POLLERR|POLLHUP)) error_set.insert(pollfd_entry.fd); - } -} -#else -void CConnman::SocketEvents(const std::vector<CNode*>& nodes, - std::set<SOCKET>& recv_set, - std::set<SOCKET>& send_set, - std::set<SOCKET>& error_set) -{ - std::set<SOCKET> recv_select_set, send_select_set, error_select_set; - if (!GenerateSelectSet(nodes, recv_select_set, send_select_set, error_select_set)) { - interruptNet.sleep_for(std::chrono::milliseconds(SELECT_TIMEOUT_MILLISECONDS)); - return; - } - - // - // Find which sockets have data to receive - // - struct timeval timeout; - timeout.tv_sec = 0; - timeout.tv_usec = SELECT_TIMEOUT_MILLISECONDS * 1000; // frequency to poll pnode->vSend - - fd_set fdsetRecv; - fd_set fdsetSend; - fd_set fdsetError; - FD_ZERO(&fdsetRecv); - FD_ZERO(&fdsetSend); - FD_ZERO(&fdsetError); - SOCKET hSocketMax = 0; - - for (SOCKET hSocket : recv_select_set) { - FD_SET(hSocket, &fdsetRecv); - hSocketMax = std::max(hSocketMax, hSocket); - } - - for (SOCKET hSocket : send_select_set) { - FD_SET(hSocket, &fdsetSend); - hSocketMax = std::max(hSocketMax, hSocket); - } - - for (SOCKET hSocket : error_select_set) { - FD_SET(hSocket, &fdsetError); - hSocketMax = std::max(hSocketMax, hSocket); - } - - int nSelect = select(hSocketMax + 1, &fdsetRecv, &fdsetSend, &fdsetError, &timeout); - - if (interruptNet) - return; - - if (nSelect == SOCKET_ERROR) - { - int nErr = WSAGetLastError(); - LogPrintf("socket select error %s\n", NetworkErrorString(nErr)); - for (unsigned int i = 0; i <= hSocketMax; i++) - FD_SET(i, &fdsetRecv); - FD_ZERO(&fdsetSend); - FD_ZERO(&fdsetError); - if (!interruptNet.sleep_for(std::chrono::milliseconds(SELECT_TIMEOUT_MILLISECONDS))) - return; - } - - for (SOCKET hSocket : recv_select_set) { - if (FD_ISSET(hSocket, &fdsetRecv)) { - recv_set.insert(hSocket); + requested = Sock::SEND; + } else if (select_recv) { + requested = Sock::RECV; } - } - for (SOCKET hSocket : send_select_set) { - if (FD_ISSET(hSocket, &fdsetSend)) { - send_set.insert(hSocket); - } + events_per_sock.emplace(pnode->m_sock, Sock::Events{requested}); } - for (SOCKET hSocket : error_select_set) { - if (FD_ISSET(hSocket, &fdsetError)) { - error_set.insert(hSocket); - } - } + return events_per_sock; } -#endif void CConnman::SocketHandler() { AssertLockNotHeld(m_total_bytes_sent_mutex); - std::set<SOCKET> recv_set; - std::set<SOCKET> send_set; - std::set<SOCKET> error_set; + Sock::EventsPerSock events_per_sock; { const NodesSnapshot snap{*this, /*shuffle=*/false}; + const auto timeout = std::chrono::milliseconds(SELECT_TIMEOUT_MILLISECONDS); + // Check for the readiness of the already connected sockets and the // listening sockets in one call ("readiness" as in poll(2) or // select(2)). If none are ready, wait for a short while and return // empty sets. - SocketEvents(snap.Nodes(), recv_set, send_set, error_set); + events_per_sock = GenerateWaitSockets(snap.Nodes()); + if (events_per_sock.empty() || !events_per_sock.begin()->first->WaitMany(timeout, events_per_sock)) { + interruptNet.sleep_for(timeout); + } // Service (send/receive) each of the already connected nodes. - SocketHandlerConnected(snap.Nodes(), recv_set, send_set, error_set); + SocketHandlerConnected(snap.Nodes(), events_per_sock); } // Accept new connections from listening sockets. - SocketHandlerListening(recv_set); + SocketHandlerListening(events_per_sock); } void CConnman::SocketHandlerConnected(const std::vector<CNode*>& nodes, - const std::set<SOCKET>& recv_set, - const std::set<SOCKET>& send_set, - const std::set<SOCKET>& error_set) + const Sock::EventsPerSock& events_per_sock) { AssertLockNotHeld(m_total_bytes_sent_mutex); @@ -1621,9 +1277,12 @@ void CConnman::SocketHandlerConnected(const std::vector<CNode*>& nodes, if (!pnode->m_sock) { continue; } - recvSet = recv_set.count(pnode->m_sock->Get()) > 0; - sendSet = send_set.count(pnode->m_sock->Get()) > 0; - errorSet = error_set.count(pnode->m_sock->Get()) > 0; + const auto it = events_per_sock.find(pnode->m_sock); + if (it != events_per_sock.end()) { + recvSet = it->second.occurred & Sock::RECV; + sendSet = it->second.occurred & Sock::SEND; + errorSet = it->second.occurred & Sock::ERR; + } } if (recvSet || errorSet) { @@ -1693,13 +1352,14 @@ void CConnman::SocketHandlerConnected(const std::vector<CNode*>& nodes, } } -void CConnman::SocketHandlerListening(const std::set<SOCKET>& recv_set) +void CConnman::SocketHandlerListening(const Sock::EventsPerSock& events_per_sock) { for (const ListenSocket& listen_socket : vhListenSocket) { if (interruptNet) { return; } - if (recv_set.count(listen_socket.sock->Get()) > 0) { + const auto it = events_per_sock.find(listen_socket.sock); + if (it != events_per_sock.end() && it->second.occurred & Sock::RECV) { AcceptConnection(listen_socket); } } @@ -1820,9 +1480,8 @@ void CConnman::ThreadDNSAddressSeed() unsigned int nMaxIPs = 256; // Limits number of IPs learned from a DNS seed if (LookupHost(host, vIPs, nMaxIPs, true)) { for (const CNetAddr& ip : vIPs) { - int nOneDay = 24*3600; CAddress addr = CAddress(CService(ip, Params().GetDefaultPort()), requiredServiceBits); - addr.nTime = GetTime() - 3*nOneDay - rng.randrange(4*nOneDay); // use a random age between 3 and 7 days old + addr.nTime = rng.rand_uniform_delay(Now<NodeSeconds>() - 3 * 24h, -4 * 24h); // use a random age between 3 and 7 days old vAdd.push_back(addr); found++; } @@ -1840,12 +1499,12 @@ void CConnman::ThreadDNSAddressSeed() void CConnman::DumpAddresses() { - int64_t nStart = GetTimeMillis(); + const auto start{SteadyClock::now()}; DumpPeerAddresses(::gArgs, addrman); LogPrint(BCLog::NET, "Flushed %d addresses to peers.dat %dms\n", - addrman.size(), GetTimeMillis() - nStart); + addrman.size(), Ticks<std::chrono::milliseconds>(SteadyClock::now() - start)); } void CConnman::ProcessAddrFetch() @@ -1919,6 +1578,7 @@ int CConnman::GetExtraBlockRelayCount() const void CConnman::ThreadOpenConnections(const std::vector<std::string> connect) { SetSyscallSandboxPolicy(SyscallSandboxPolicy::NET_OPEN_CONNECTION); + FastRandomContext rng; // Connect to specific addresses if (!connect.empty()) { @@ -1981,15 +1641,28 @@ void CConnman::ThreadOpenConnections(const std::vector<std::string> connect) LOCK2(m_addr_fetches_mutex, m_added_nodes_mutex); if (m_addr_fetches.empty() && m_added_nodes.empty()) { add_fixed_seeds_now = true; - LogPrintf("Adding fixed seeds as -dnsseed=0, -addnode is not provided and all -seednode(s) attempted\n"); + LogPrintf("Adding fixed seeds as -dnsseed=0 (or IPv4/IPv6 connections are disabled via -onlynet), -addnode is not provided and all -seednode(s) attempted\n"); } } if (add_fixed_seeds_now) { + std::vector<CAddress> seed_addrs{ConvertSeeds(Params().FixedSeeds())}; + // We will not make outgoing connections to peers that are unreachable + // (e.g. because of -onlynet configuration). + // Therefore, we do not add them to addrman in the first place. + // Note that if you change -onlynet setting from one network to another, + // peers.dat will contain only peers of unreachable networks and + // manual intervention will be needed (either delete peers.dat after + // configuration change or manually add some reachable peer using addnode), + // see <https://github.com/bitcoin/bitcoin/issues/26035> for details. + seed_addrs.erase(std::remove_if(seed_addrs.begin(), seed_addrs.end(), + [](const CAddress& addr) { return !IsReachable(addr); }), + seed_addrs.end()); CNetAddr local; local.SetInternal("fixedseeds"); - addrman.Add(ConvertSeeds(Params().FixedSeeds()), local); + addrman.Add(seed_addrs, local); add_fixed_seeds = false; + LogPrintf("Added %d fixed seeds from reachable networks.\n", seed_addrs.size()); } } @@ -2087,7 +1760,7 @@ void CConnman::ThreadOpenConnections(const std::vector<std::string> connect) addrman.ResolveCollisions(); - int64_t nANow = GetAdjustedTime(); + const auto current_time{NodeClock::now()}; int nTries = 0; while (!interruptNet) { @@ -2110,7 +1783,7 @@ void CConnman::ThreadOpenConnections(const std::vector<std::string> connect) break; CAddress addr; - int64_t addr_last_try{0}; + NodeSeconds addr_last_try{0s}; if (fFeeler) { // First, try to get a tried table collision address. This returns @@ -2150,8 +1823,9 @@ void CConnman::ThreadOpenConnections(const std::vector<std::string> connect) continue; // only consider very recently tried nodes after 30 failed attempts - if (nANow - addr_last_try < 600 && nTries < 30) + if (current_time - addr_last_try < 10min && nTries < 30) { continue; + } // for non-feelers, require all the services we'll want, // for feelers, only require they be a full node (only because most @@ -2172,12 +1846,11 @@ void CConnman::ThreadOpenConnections(const std::vector<std::string> connect) } if (addrConnect.IsValid()) { - if (fFeeler) { // Add small amount of random noise before connection to avoid synchronization. - int randsleep = GetRand<int>(FEELER_SLEEP_WINDOW * 1000); - if (!interruptNet.sleep_for(std::chrono::milliseconds(randsleep))) + if (!interruptNet.sleep_for(rng.rand_uniform_duration<CThreadInterrupt::Clock>(FEELER_SLEEP_WINDOW))) { return; + } LogPrint(BCLog::NET, "Making feeler connection to %s\n", addrConnect.ToString()); } @@ -2310,15 +1983,19 @@ void CConnman::OpenNetworkConnection(const CAddress& addrConnect, bool fCountFai if (grantOutbound) grantOutbound->MoveTo(pnode->grantOutbound); - m_msgproc->InitializeNode(pnode); + m_msgproc->InitializeNode(*pnode, nLocalServices); { LOCK(m_nodes_mutex); m_nodes.push_back(pnode); } } +Mutex NetEventsInterface::g_msgproc_mutex; + void CConnman::ThreadMessageHandler() { + LOCK(NetEventsInterface::g_msgproc_mutex); + SetSyscallSandboxPolicy(SyscallSandboxPolicy::MESSAGE_HANDLER); while (!flagInterruptMsgProc) { @@ -2340,10 +2017,7 @@ void CConnman::ThreadMessageHandler() if (flagInterruptMsgProc) return; // Send messages - { - LOCK(pnode->cs_sendProcessing); - m_msgproc->SendMessages(pnode); - } + m_msgproc->SendMessages(pnode); if (flagInterruptMsgProc) return; @@ -2443,8 +2117,7 @@ bool CConnman::BindListenPort(const CService& addrBind, bilingual_str& strError, #endif } - if (::bind(sock->Get(), (struct sockaddr*)&sockaddr, len) == SOCKET_ERROR) - { + if (sock->Bind(reinterpret_cast<struct sockaddr*>(&sockaddr), len) == SOCKET_ERROR) { int nErr = WSAGetLastError(); if (nErr == WSAEADDRINUSE) strError = strprintf(_("Unable to bind to %s on this computer. %s is probably already running."), addrBind.ToString(), PACKAGE_NAME); @@ -2456,7 +2129,7 @@ bool CConnman::BindListenPort(const CService& addrBind, bilingual_str& strError, LogPrintf("Bound to %s\n", addrBind.ToString()); // Listen for incoming connections - if (listen(sock->Get(), SOMAXCONN) == SOCKET_ERROR) + if (sock->Listen(SOMAXCONN) == SOCKET_ERROR) { strError = strprintf(_("Listening for incoming connections failed (listen returned error %s)"), NetworkErrorString(WSAGetLastError())); LogPrintLevel(BCLog::NET, BCLog::Level::Error, "%s\n", strError.original); @@ -2557,9 +2230,6 @@ bool CConnman::Bind(const CService& addr_, unsigned int flags, NetPermissionFlag { const CService addr{MaybeFlipIPv6toCJDNS(addr_)}; - if (!(flags & BF_EXPLICIT) && !IsReachable(addr)) { - return false; - } bilingual_str strError; if (!BindListenPort(addr, strError, permissions)) { if ((flags & BF_REPORT_ERROR) && m_client_interface) { @@ -2579,13 +2249,13 @@ bool CConnman::InitBinds(const Options& options) { bool fBound = false; for (const auto& addrBind : options.vBinds) { - fBound |= Bind(addrBind, (BF_EXPLICIT | BF_REPORT_ERROR), NetPermissionFlags::None); + fBound |= Bind(addrBind, BF_REPORT_ERROR, NetPermissionFlags::None); } for (const auto& addrBind : options.vWhiteBinds) { - fBound |= Bind(addrBind.m_service, (BF_EXPLICIT | BF_REPORT_ERROR), addrBind.m_flags); + fBound |= Bind(addrBind.m_service, BF_REPORT_ERROR, addrBind.m_flags); } for (const auto& addr_bind : options.onion_binds) { - fBound |= Bind(addr_bind, BF_EXPLICIT | BF_DONT_ADVERTISE, NetPermissionFlags::None); + fBound |= Bind(addr_bind, BF_DONT_ADVERTISE, NetPermissionFlags::None); } if (options.bind_on_any) { struct in_addr inaddr_any; @@ -2612,7 +2282,7 @@ bool CConnman::Start(CScheduler& scheduler, const Options& connOptions) } Proxy i2p_sam; - if (GetProxy(NET_I2P, i2p_sam)) { + if (GetProxy(NET_I2P, i2p_sam) && connOptions.m_i2p_accept_incoming) { m_i2p_sam_session = std::make_unique<i2p::sam::Session>(gArgs.GetDataDirNet() / "i2p_private_key", i2p_sam.proxy, &interruptNet); } @@ -2686,7 +2356,7 @@ bool CConnman::Start(CScheduler& scheduler, const Options& connOptions) // Process messages threadMessageHandler = std::thread(&util::TraceThread, "msghand", [this] { ThreadMessageHandler(); }); - if (connOptions.m_i2p_accept_incoming && m_i2p_sam_session.get() != nullptr) { + if (m_i2p_sam_session) { threadI2PAcceptIncoming = std::thread(&util::TraceThread, "i2paccept", [this] { ThreadI2PAcceptIncoming(); }); } @@ -3055,18 +2725,31 @@ ServiceFlags CConnman::GetLocalServices() const unsigned int CConnman::GetReceiveFloodSize() const { return nReceiveFloodSize; } -CNode::CNode(NodeId idIn, ServiceFlags nLocalServicesIn, std::shared_ptr<Sock> sock, const CAddress& addrIn, uint64_t nKeyedNetGroupIn, uint64_t nLocalHostNonceIn, const CAddress& addrBindIn, const std::string& addrNameIn, ConnectionType conn_type_in, bool inbound_onion) - : m_sock{sock}, +CNode::CNode(NodeId idIn, + std::shared_ptr<Sock> sock, + const CAddress& addrIn, + uint64_t nKeyedNetGroupIn, + uint64_t nLocalHostNonceIn, + const CAddress& addrBindIn, + const std::string& addrNameIn, + ConnectionType conn_type_in, + bool inbound_onion, + CNodeOptions&& node_opts) + : m_deserializer{std::make_unique<V1TransportDeserializer>(V1TransportDeserializer(Params(), idIn, SER_NETWORK, INIT_PROTO_VERSION))}, + m_serializer{std::make_unique<V1TransportSerializer>(V1TransportSerializer())}, + m_permission_flags{node_opts.permission_flags}, + m_sock{sock}, m_connected{GetTime<std::chrono::seconds>()}, - addr(addrIn), - addrBind(addrBindIn), + addr{addrIn}, + addrBind{addrBindIn}, m_addr_name{addrNameIn.empty() ? addr.ToStringIPPort() : addrNameIn}, - m_inbound_onion(inbound_onion), - nKeyedNetGroup(nKeyedNetGroupIn), - id(idIn), - nLocalHostNonce(nLocalHostNonceIn), - m_conn_type(conn_type_in), - nLocalServices(nLocalServicesIn) + m_inbound_onion{inbound_onion}, + m_prefer_evict{node_opts.prefer_evict}, + nKeyedNetGroup{nKeyedNetGroupIn}, + id{idIn}, + nLocalHostNonce{nLocalHostNonceIn}, + m_conn_type{conn_type_in}, + m_i2p_sam_session{std::move(node_opts.i2p_sam_session)} { if (inbound_onion) assert(conn_type_in == ConnectionType::INBOUND); @@ -3079,9 +2762,6 @@ CNode::CNode(NodeId idIn, ServiceFlags nLocalServicesIn, std::shared_ptr<Sock> s } else { LogPrint(BCLog::NET, "Added connection peer=%d\n", id); } - - m_deserializer = std::make_unique<V1TransportDeserializer>(V1TransportDeserializer(Params(), id, SER_NETWORK, INIT_PROTO_VERSION)); - m_serializer = std::make_unique<V1TransportSerializer>(V1TransportSerializer()); } bool CConnman::NodeFullyConnected(const CNode* pnode) @@ -3175,7 +2855,7 @@ void CaptureMessageToFile(const CAddress& addr, fs::create_directories(base_path); fs::path path = base_path / (is_incoming ? "msgs_recv.dat" : "msgs_sent.dat"); - CAutoFile f(fsbridge::fopen(path, "ab"), SER_DISK, CLIENT_VERSION); + AutoFile f{fsbridge::fopen(path, "ab")}; ser_writedata64(f, now.count()); f.write(MakeByteSpan(msg_type)); @@ -8,7 +8,8 @@ #include <chainparams.h> #include <common/bloom.h> -#include <compat.h> +#include <compat/compat.h> +#include <node/connection_types.h> #include <consensus/amount.h> #include <crypto/siphash.h> #include <hash.h> @@ -121,78 +122,6 @@ struct CSerializedNetMsg { std::string m_type; }; -/** Different types of connections to a peer. This enum encapsulates the - * information we have available at the time of opening or accepting the - * connection. Aside from INBOUND, all types are initiated by us. - * - * If adding or removing types, please update CONNECTION_TYPE_DOC in - * src/rpc/net.cpp and src/qt/rpcconsole.cpp, as well as the descriptions in - * src/qt/guiutil.cpp and src/bitcoin-cli.cpp::NetinfoRequestHandler. */ -enum class ConnectionType { - /** - * Inbound connections are those initiated by a peer. This is the only - * property we know at the time of connection, until P2P messages are - * exchanged. - */ - INBOUND, - - /** - * These are the default connections that we use to connect with the - * network. There is no restriction on what is relayed; by default we relay - * blocks, addresses & transactions. We automatically attempt to open - * MAX_OUTBOUND_FULL_RELAY_CONNECTIONS using addresses from our AddrMan. - */ - OUTBOUND_FULL_RELAY, - - - /** - * We open manual connections to addresses that users explicitly requested - * via the addnode RPC or the -addnode/-connect configuration options. Even if a - * manual connection is misbehaving, we do not automatically disconnect or - * add it to our discouragement filter. - */ - MANUAL, - - /** - * Feeler connections are short-lived connections made to check that a node - * is alive. They can be useful for: - * - test-before-evict: if one of the peers is considered for eviction from - * our AddrMan because another peer is mapped to the same slot in the tried table, - * evict only if this longer-known peer is offline. - * - move node addresses from New to Tried table, so that we have more - * connectable addresses in our AddrMan. - * Note that in the literature ("Eclipse Attacks on Bitcoin’s Peer-to-Peer Network") - * only the latter feature is referred to as "feeler connections", - * although in our codebase feeler connections encompass test-before-evict as well. - * We make these connections approximately every FEELER_INTERVAL: - * first we resolve previously found collisions if they exist (test-before-evict), - * otherwise we connect to a node from the new table. - */ - FEELER, - - /** - * We use block-relay-only connections to help prevent against partition - * attacks. By not relaying transactions or addresses, these connections - * are harder to detect by a third party, thus helping obfuscate the - * network topology. We automatically attempt to open - * MAX_BLOCK_RELAY_ONLY_ANCHORS using addresses from our anchors.dat. Then - * addresses from our AddrMan if MAX_BLOCK_RELAY_ONLY_CONNECTIONS - * isn't reached yet. - */ - BLOCK_RELAY, - - /** - * AddrFetch connections are short lived connections used to solicit - * addresses from peers. These are initiated to addresses submitted via the - * -seednode command line argument, or under certain conditions when the - * AddrMan is empty. - */ - ADDR_FETCH, -}; - -/** Convert ConnectionType enum to a string value */ -std::string ConnectionTypeAsString(ConnectionType conn_type); - /** * Look up IP addresses from all interfaces on the machine and add them to the * list of local addresses to self-advertise. @@ -215,8 +144,8 @@ enum }; bool IsPeerAddrLocalGood(CNode *pnode); -/** Returns a local address that we should advertise to this peer */ -std::optional<CAddress> GetLocalAddrForPeer(CNode *pnode); +/** Returns a local address that we should advertise to this peer. */ +std::optional<CService> GetLocalAddrForPeer(CNode& node); /** * Mark a network as reachable or unreachable (no automatic connects to it) @@ -234,7 +163,8 @@ void RemoveLocal(const CService& addr); bool SeenLocal(const CService& addr); bool IsLocal(const CService& addr); bool GetLocal(CService &addr, const CNetAddr *paddrPeer = nullptr); -CAddress GetLocalAddress(const CNetAddr *paddrPeer, ServiceFlags nLocalServices); +CService GetLocalAddress(const CNetAddr& addrPeer); +CService MaybeFlipIPv6toCJDNS(const CService& service); extern bool fDiscover; @@ -248,7 +178,7 @@ struct LocalServiceInfo { uint16_t nPort; }; -extern Mutex g_maplocalhost_mutex; +extern GlobalMutex g_maplocalhost_mutex; extern std::map<CNetAddr, LocalServiceInfo> mapLocalHost GUARDED_BY(g_maplocalhost_mutex); extern const std::string NET_MESSAGE_TYPE_OTHER; @@ -258,7 +188,6 @@ class CNodeStats { public: NodeId nodeid; - ServiceFlags nServices; std::chrono::seconds m_last_send; std::chrono::seconds m_last_recv; std::chrono::seconds m_last_tx_time; @@ -276,7 +205,7 @@ public: mapMsgTypeSize mapSendBytesPerMsgType; uint64_t nRecvBytes; mapMsgTypeSize mapRecvBytesPerMsgType; - NetPermissionFlags m_permissionFlags; + NetPermissionFlags m_permission_flags; std::chrono::microseconds m_last_ping_time; std::chrono::microseconds m_min_ping_time; // Our address, as reported by the peer @@ -397,13 +326,20 @@ public: class TransportSerializer { public: // prepare message for transport (header construction, error-correction computation, payload encryption, etc.) - virtual void prepareForTransport(CSerializedNetMsg& msg, std::vector<unsigned char>& header) = 0; + virtual void prepareForTransport(CSerializedNetMsg& msg, std::vector<unsigned char>& header) const = 0; virtual ~TransportSerializer() {} }; -class V1TransportSerializer : public TransportSerializer { +class V1TransportSerializer : public TransportSerializer { public: - void prepareForTransport(CSerializedNetMsg& msg, std::vector<unsigned char>& header) override; + void prepareForTransport(CSerializedNetMsg& msg, std::vector<unsigned char>& header) const override; +}; + +struct CNodeOptions +{ + NetPermissionFlags permission_flags = NetPermissionFlags::None; + std::unique_ptr<i2p::sam::Session> i2p_sam_session = nullptr; + bool prefer_evict = false; }; /** Information about a peer */ @@ -413,11 +349,10 @@ class CNode friend struct ConnmanTestMsg; public: - std::unique_ptr<TransportDeserializer> m_deserializer; - std::unique_ptr<TransportSerializer> m_serializer; + const std::unique_ptr<TransportDeserializer> m_deserializer; // Used only by SocketHandler thread + const std::unique_ptr<const TransportSerializer> m_serializer; - NetPermissionFlags m_permissionFlags{NetPermissionFlags::None}; - std::atomic<ServiceFlags> nServices{NODE_NONE}; + const NetPermissionFlags m_permission_flags; /** * Socket used for communication with the node. @@ -441,9 +376,7 @@ public: RecursiveMutex cs_vProcessMsg; std::list<CNetMessage> vProcessMsg GUARDED_BY(cs_vProcessMsg); - size_t nProcessQueueSize{0}; - - RecursiveMutex cs_sendProcessing; + size_t nProcessQueueSize GUARDED_BY(cs_vProcessMsg){0}; uint64_t nRecvBytes GUARDED_BY(cs_vRecv){0}; @@ -466,12 +399,10 @@ public: * from the wire. This cleaned string can safely be logged or displayed. */ std::string cleanSubVer GUARDED_BY(m_subver_mutex){}; - bool m_prefer_evict{false}; // This peer is preferred for eviction. + const bool m_prefer_evict{false}; // This peer is preferred for eviction. bool HasPermission(NetPermissionFlags permission) const { - return NetPermissions::HasFlag(m_permissionFlags, permission); + return NetPermissions::HasFlag(m_permission_flags, permission); } - bool fClient{false}; // set by version message - bool m_limited_node{false}; //after BIP159, set by version message /** fSuccessfullyConnected is set to true on receiving VERACK from the peer. */ std::atomic_bool fSuccessfullyConnected{false}; // Setting fDisconnect to true will cause the node to be disconnected the @@ -555,6 +486,9 @@ public: // Peer selected us as (compact blocks) high-bandwidth peer (BIP152) std::atomic<bool> m_bip152_highbandwidth_from{false}; + /** Whether this peer provides all services that we want. Used for eviction decisions */ + std::atomic_bool m_has_all_wanted_services{false}; + /** Whether we should relay transactions to this peer (their version * message did not include fRelay=false and this is not a block-relay-only * connection). This only changes from false to true. It will never change @@ -585,7 +519,16 @@ public: * criterium in CConnman::AttemptToEvictConnection. */ std::atomic<std::chrono::microseconds> m_min_ping_time{std::chrono::microseconds::max()}; - CNode(NodeId id, ServiceFlags nLocalServicesIn, std::shared_ptr<Sock> sock, const CAddress& addrIn, uint64_t nKeyedNetGroupIn, uint64_t nLocalHostNonceIn, const CAddress& addrBindIn, const std::string& addrNameIn, ConnectionType conn_type_in, bool inbound_onion); + CNode(NodeId id, + std::shared_ptr<Sock> sock, + const CAddress& addrIn, + uint64_t nKeyedNetGroupIn, + uint64_t nLocalHostNonceIn, + const CAddress& addrBindIn, + const std::string& addrNameIn, + ConnectionType conn_type_in, + bool inbound_onion, + CNodeOptions&& node_opts = {}); CNode(const CNode&) = delete; CNode& operator=(const CNode&) = delete; @@ -643,11 +586,6 @@ public: void CopyStats(CNodeStats& stats) EXCLUSIVE_LOCKS_REQUIRED(!m_subver_mutex, !m_addr_local_mutex, !cs_vSend, !cs_vRecv); - ServiceFlags GetLocalServices() const - { - return nLocalServices; - } - std::string ConnectionTypeAsString() const { return ::ConnectionTypeAsString(m_conn_type); } /** A ping-pong round trip has completed successfully. Update latest and minimum ping times. */ @@ -662,23 +600,6 @@ private: const ConnectionType m_conn_type; std::atomic<int> m_greatest_common_version{INIT_PROTO_VERSION}; - //! Services offered to this peer. - //! - //! This is supplied by the parent CConnman during peer connection - //! (CConnman::ConnectNode()) from its attribute of the same name. - //! - //! This is const because there is no protocol defined for renegotiating - //! services initially offered to a peer. The set of local services we - //! offer should not change after initialization. - //! - //! An interesting example of this is NODE_NETWORK and initial block - //! download: a node which starts up from scratch doesn't have any blocks - //! to serve, but still advertises NODE_NETWORK because it will eventually - //! fulfill this role after IBD completes. P2P code is written in such a - //! way that it can gracefully handle peers who don't make good on their - //! service advertisements. - const ServiceFlags nLocalServices; - std::list<CNetMessage> vRecvMsg; // Used only by SocketHandler thread // Our address, as reported by the peer @@ -687,6 +608,18 @@ private: mapMsgTypeSize mapSendBytesPerMsgType GUARDED_BY(cs_vSend); mapMsgTypeSize mapRecvBytesPerMsgType GUARDED_BY(cs_vRecv); + + /** + * If an I2P session is created per connection (for outbound transient I2P + * connections) then it is stored here so that it can be destroyed when the + * socket is closed. I2P sessions involve a data/transport socket (in `m_sock`) + * and a control socket (in `m_i2p_sam_session`). For transient sessions, once + * the data socket is closed, the control socket is not going to be used anymore + * and is just taking up resources. So better close it as soon as `m_sock` is + * closed. + * Otherwise this unique_ptr is empty. + */ + std::unique_ptr<i2p::sam::Session> m_i2p_sam_session GUARDED_BY(m_sock_mutex); }; /** @@ -695,8 +628,11 @@ private: class NetEventsInterface { public: + /** Mutex for anything that is only accessed via the msg processing thread */ + static Mutex g_msgproc_mutex; + /** Initialize a peer (setup state, queue any initial messages) */ - virtual void InitializeNode(CNode* pnode) = 0; + virtual void InitializeNode(CNode& node, ServiceFlags our_services) = 0; /** Handle removal of a peer (clear state) */ virtual void FinalizeNode(const CNode& node) = 0; @@ -708,7 +644,7 @@ public: * @param[in] interrupt Interrupt condition for processing threads * @return True if there is more work to be done */ - virtual bool ProcessMessages(CNode* pnode, std::atomic<bool>& interrupt) = 0; + virtual bool ProcessMessages(CNode* pnode, std::atomic<bool>& interrupt) EXCLUSIVE_LOCKS_REQUIRED(g_msgproc_mutex) = 0; /** * Send queued protocol messages to a given node. @@ -716,7 +652,7 @@ public: * @param[in] pnode The node which we are sending messages to. * @return True if there is more work to be done */ - virtual bool SendMessages(CNode* pnode) EXCLUSIVE_LOCKS_REQUIRED(pnode->cs_sendProcessing) = 0; + virtual bool SendMessages(CNode* pnode) EXCLUSIVE_LOCKS_REQUIRED(g_msgproc_mutex) = 0; protected: @@ -963,12 +899,12 @@ private: * Create a `CNode` object from a socket that has just been accepted and add the node to * the `m_nodes` member. * @param[in] sock Connected socket to communicate with the peer. - * @param[in] permissionFlags The peer's permissions. + * @param[in] permission_flags 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(std::unique_ptr<Sock>&& sock, - NetPermissionFlags permissionFlags, + NetPermissionFlags permission_flags, const CAddress& addr_bind, const CAddress& addr); @@ -980,28 +916,9 @@ private: /** * Generate a collection of sockets to check for IO readiness. * @param[in] nodes Select from these nodes' sockets. - * @param[out] recv_set Sockets to check for read readiness. - * @param[out] send_set Sockets to check for write readiness. - * @param[out] error_set Sockets to check for errors. - * @return true if at least one socket is to be checked (the returned set is not empty) - */ - bool GenerateSelectSet(const std::vector<CNode*>& nodes, - std::set<SOCKET>& recv_set, - std::set<SOCKET>& send_set, - std::set<SOCKET>& error_set); - - /** - * Check which sockets are ready for IO. - * @param[in] nodes Select from these nodes' sockets. - * @param[out] recv_set Sockets which are ready for read. - * @param[out] send_set Sockets which are ready for write. - * @param[out] error_set Sockets which have errors. - * This calls `GenerateSelectSet()` to gather a list of sockets to check. + * @return sockets to check for readiness */ - void SocketEvents(const std::vector<CNode*>& nodes, - std::set<SOCKET>& recv_set, - std::set<SOCKET>& send_set, - std::set<SOCKET>& error_set); + Sock::EventsPerSock GenerateWaitSockets(Span<CNode* const> nodes); /** * Check connected and listening sockets for IO readiness and process them accordingly. @@ -1010,23 +927,18 @@ private: /** * Do the read/write for connected sockets that are ready for IO. - * @param[in] nodes Nodes to process. The socket of each node is checked against - * `recv_set`, `send_set` and `error_set`. - * @param[in] recv_set Sockets that are ready for read. - * @param[in] send_set Sockets that are ready for send. - * @param[in] error_set Sockets that have an exceptional condition (error). + * @param[in] nodes Nodes to process. The socket of each node is checked against `what`. + * @param[in] events_per_sock Sockets that are ready for IO. */ void SocketHandlerConnected(const std::vector<CNode*>& nodes, - const std::set<SOCKET>& recv_set, - const std::set<SOCKET>& send_set, - const std::set<SOCKET>& error_set) + const Sock::EventsPerSock& events_per_sock) EXCLUSIVE_LOCKS_REQUIRED(!m_total_bytes_sent_mutex, !mutexMsgProc); /** * Accept incoming connections, one from each read-ready listening socket. - * @param[in] recv_set Sockets that are ready for read. + * @param[in] events_per_sock Sockets that are ready for IO. */ - void SocketHandlerListening(const std::set<SOCKET>& recv_set); + void SocketHandlerListening(const Sock::EventsPerSock& events_per_sock); void ThreadSocketHandler() EXCLUSIVE_LOCKS_REQUIRED(!m_total_bytes_sent_mutex, !mutexMsgProc); void ThreadDNSAddressSeed() EXCLUSIVE_LOCKS_REQUIRED(!m_addr_fetches_mutex, !m_nodes_mutex); @@ -1130,16 +1042,14 @@ private: std::map<uint64_t, CachedAddrResponse> m_addr_response_caches; /** - * Services this instance offers. + * Services this node offers. * - * This data is replicated in each CNode instance we create during peer - * connection (in ConnectNode()) under a member also called - * nLocalServices. + * This data is replicated in each Peer instance we create. * * This data is not marked const, but after being set it should not - * change. See the note in CNode::nLocalServices documentation. + * change. * - * \sa CNode::nLocalServices + * \sa Peer::our_services */ ServiceFlags nLocalServices; @@ -1189,7 +1099,8 @@ private: /** * I2P SAM session. - * Used to accept incoming and make outgoing I2P connections. + * Used to accept incoming and make outgoing I2P connections from a persistent + * address. */ std::unique_ptr<i2p::sam::Session> m_i2p_sam_session; @@ -1271,54 +1182,4 @@ extern std::function<void(const CAddress& addr, bool is_incoming)> CaptureMessage; -struct NodeEvictionCandidate -{ - NodeId id; - std::chrono::seconds m_connected; - std::chrono::microseconds m_min_ping_time; - std::chrono::seconds m_last_block_time; - std::chrono::seconds m_last_tx_time; - bool fRelevantServices; - bool m_relay_txs; - bool fBloomFilter; - uint64_t nKeyedNetGroup; - bool prefer_evict; - bool m_is_local; - Network m_network; -}; - -/** - * 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 the - * following categories of peers, sorted by longest uptime, even if they're not - * longest uptime overall: - * - * - onion peers connected via our tor control service - * - * - localhost peers, as manually configured hidden services not using - * `-bind=addr[:port]=onion` will not be detected as inbound onion connections - * - * - I2P peers - * - * - CJDNS peers - * - * This helps protect these privacy network peers, which tend to be otherwise - * disadvantaged under our eviction criteria for their higher min ping times - * relative to IPv4/IPv6 peers, and favorise the diversity of peer connections. - */ -void ProtectEvictionCandidatesByRatio(std::vector<NodeEvictionCandidate>& vEvictionCandidates); - #endif // BITCOIN_NET_H diff --git a/src/net_processing.cpp b/src/net_processing.cpp index 30d5548385..eca6263392 100644 --- a/src/net_processing.cpp +++ b/src/net_processing.cpp @@ -14,6 +14,7 @@ #include <consensus/validation.h> #include <deploymentstatus.h> #include <hash.h> +#include <headerssync.h> #include <index/blockfilterindex.h> #include <merkleblock.h> #include <netbase.h> @@ -29,6 +30,7 @@ #include <scheduler.h> #include <streams.h> #include <sync.h> +#include <timedata.h> #include <tinyformat.h> #include <txmempool.h> #include <txorphanage.h> @@ -61,6 +63,8 @@ static constexpr auto UNCONDITIONAL_RELAY_DELAY = 2min; * Timeout = base + per_header * (expected number of headers) */ static constexpr auto HEADERS_DOWNLOAD_TIMEOUT_BASE = 15min; static constexpr auto HEADERS_DOWNLOAD_TIMEOUT_PER_HEADER = 1ms; +/** How long to wait for a peer to respond to a getheaders request */ +static constexpr auto HEADERS_RESPONSE_TIME{2min}; /** Protect at least this many outbound peers from disconnection due to slow/ * behind headers chain. */ @@ -205,6 +209,23 @@ struct Peer { /** Same id as the CNode object for this peer */ const NodeId m_id{0}; + /** Services we offered to this peer. + * + * This is supplied by CConnman during peer initialization. It's const + * because there is no protocol defined for renegotiating services + * initially offered to a peer. The set of local services we offer should + * not change after initialization. + * + * An interesting example of this is NODE_NETWORK and initial block + * download: a node which starts up from scratch doesn't have any blocks + * to serve, but still advertises NODE_NETWORK because it will eventually + * fulfill this role after IBD completes. P2P code is written in such a + * way that it can gracefully handle peers who don't make good on their + * service advertisements. */ + const ServiceFlags m_our_services; + /** Services this peer offered to us. */ + std::atomic<ServiceFlags> m_their_services{NODE_NONE}; + /** Protects misbehavior data members */ Mutex m_misbehavior_mutex; /** Accumulated misbehavior score for this peer */ @@ -243,10 +264,10 @@ struct Peer { /** The feerate in the most recent BIP133 `feefilter` message sent to the peer. * It is *not* a p2p protocol violation for the peer to send us * transactions with a lower fee rate than this. See BIP133. */ - CAmount m_fee_filter_sent{0}; + CAmount m_fee_filter_sent GUARDED_BY(NetEventsInterface::g_msgproc_mutex){0}; /** Timestamp after which we will send the next BIP133 `feefilter` message * to the peer. */ - std::chrono::microseconds m_next_send_feefilter{0}; + std::chrono::microseconds m_next_send_feefilter GUARDED_BY(NetEventsInterface::g_msgproc_mutex){0}; struct TxRelay { mutable RecursiveMutex m_bloom_filter_mutex; @@ -269,7 +290,7 @@ struct Peer { * non-wtxid-relay peers, wtxid for wtxid-relay peers). We use the * mempool to sort transactions in dependency order before relay, so * this does not have to be sorted. */ - std::set<uint256> m_tx_inventory_to_send; + std::set<uint256> m_tx_inventory_to_send GUARDED_BY(m_tx_inventory_mutex); /** Whether the peer has requested us to send our complete mempool. Only * permitted if the peer has NetPermissionFlags::Mempool. See BIP35. */ bool m_send_mempool GUARDED_BY(m_tx_inventory_mutex){false}; @@ -277,7 +298,7 @@ struct Peer { std::atomic<std::chrono::seconds> m_last_mempool_req{0s}; /** The next time after which we will send an `inv` message containing * transaction announcements to this peer. */ - std::chrono::microseconds m_next_inv_send_time{0}; + std::chrono::microseconds m_next_inv_send_time GUARDED_BY(NetEventsInterface::g_msgproc_mutex){0}; /** Minimum fee rate with which to filter transaction announcements to this node. See BIP133. */ std::atomic<CAmount> m_fee_filter_received{0}; @@ -292,13 +313,13 @@ struct Peer { return m_tx_relay.get(); }; - TxRelay* GetTxRelay() + TxRelay* GetTxRelay() EXCLUSIVE_LOCKS_REQUIRED(!m_tx_relay_mutex) { return WITH_LOCK(m_tx_relay_mutex, return m_tx_relay.get()); }; /** A vector of addresses to send to the peer, limited to MAX_ADDR_TO_SEND. */ - std::vector<CAddress> m_addrs_to_send; + std::vector<CAddress> m_addrs_to_send GUARDED_BY(NetEventsInterface::g_msgproc_mutex); /** Probabilistic filter to track recent addr messages relayed with this * peer. Used to avoid relaying redundant addresses to this peer. * @@ -308,7 +329,7 @@ struct Peer { * * Presence of this filter must correlate with m_addr_relay_enabled. **/ - std::unique_ptr<CRollingBloomFilter> m_addr_known; + std::unique_ptr<CRollingBloomFilter> m_addr_known GUARDED_BY(NetEventsInterface::g_msgproc_mutex); /** Whether we are participating in address relay with this connection. * * We set this bool to true for outbound peers (other than @@ -325,7 +346,7 @@ struct Peer { * initialized.*/ std::atomic_bool m_addr_relay_enabled{false}; /** Whether a getaddr request to this peer is outstanding. */ - bool m_getaddr_sent{false}; + bool m_getaddr_sent GUARDED_BY(NetEventsInterface::g_msgproc_mutex){false}; /** Guards address sending timers. */ mutable Mutex m_addr_send_times_mutex; /** Time point to send the next ADDR message to this peer. */ @@ -336,12 +357,12 @@ struct Peer { * messages, indicating a preference to receive ADDRv2 instead of ADDR ones. */ std::atomic_bool m_wants_addrv2{false}; /** Whether this peer has already sent us a getaddr message. */ - bool m_getaddr_recvd{false}; + bool m_getaddr_recvd GUARDED_BY(NetEventsInterface::g_msgproc_mutex){false}; /** Number of addresses that can be processed from this peer. Start at 1 to * permit self-announcement. */ - double m_addr_token_bucket{1.0}; + double m_addr_token_bucket GUARDED_BY(NetEventsInterface::g_msgproc_mutex){1.0}; /** When m_addr_token_bucket was last updated */ - std::chrono::microseconds m_addr_token_timestamp{GetTime<std::chrono::microseconds>()}; + std::chrono::microseconds m_addr_token_timestamp GUARDED_BY(NetEventsInterface::g_msgproc_mutex){GetTime<std::chrono::microseconds>()}; /** Total number of addresses that were dropped due to rate limiting. */ std::atomic<uint64_t> m_addr_rate_limited{0}; /** Total number of addresses that were processed (excludes rate-limited ones). */ @@ -350,13 +371,29 @@ struct Peer { /** Set of txids to reconsider once their parent transactions have been accepted **/ std::set<uint256> m_orphan_work_set GUARDED_BY(g_cs_orphans); + /** Whether we've sent this peer a getheaders in response to an inv prior to initial-headers-sync completing */ + bool m_inv_triggered_getheaders_before_sync GUARDED_BY(NetEventsInterface::g_msgproc_mutex){false}; + /** Protects m_getdata_requests **/ Mutex m_getdata_requests_mutex; /** Work queue of items requested by this peer **/ std::deque<CInv> m_getdata_requests GUARDED_BY(m_getdata_requests_mutex); - Peer(NodeId id) + /** Time of the last getheaders message to this peer */ + NodeClock::time_point m_last_getheaders_timestamp GUARDED_BY(NetEventsInterface::g_msgproc_mutex){}; + + /** Protects m_headers_sync **/ + Mutex m_headers_sync_mutex; + /** Headers-sync state for this peer (eg for initial sync, or syncing large + * reorgs) **/ + std::unique_ptr<HeadersSyncState> m_headers_sync PT_GUARDED_BY(m_headers_sync_mutex) GUARDED_BY(m_headers_sync_mutex) {}; + + /** Whether we've sent our peer a sendheaders message. **/ + std::atomic<bool> m_sent_sendheaders{false}; + + explicit Peer(NodeId id, ServiceFlags our_services) : m_id{id} + , m_our_services{our_services} {} private: @@ -405,8 +442,6 @@ struct CNodeState { bool m_requested_hb_cmpctblocks{false}; /** Whether this peer will send us cmpctblocks if we request them. */ bool m_provides_cmpctblocks{false}; - //! Whether this peer can give us witnesses - bool fHaveWitness{false}; /** State used to enforce CHAIN_SYNC_TIMEOUT and EXTRA_PEER_CHECK_INTERVAL logic. * @@ -477,31 +512,32 @@ public: EXCLUSIVE_LOCKS_REQUIRED(!m_most_recent_block_mutex); /** Implement NetEventsInterface */ - void InitializeNode(CNode* pnode) override EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex); - void FinalizeNode(const CNode& node) override EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex); + void InitializeNode(CNode& node, ServiceFlags our_services) override EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex); + void FinalizeNode(const CNode& node) override EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex, !m_headers_presync_mutex); bool ProcessMessages(CNode* pfrom, std::atomic<bool>& interrupt) override - EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex, !m_recent_confirmed_transactions_mutex, !m_most_recent_block_mutex); - bool SendMessages(CNode* pto) override EXCLUSIVE_LOCKS_REQUIRED(pto->cs_sendProcessing) - EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex, !m_recent_confirmed_transactions_mutex, !m_most_recent_block_mutex); + EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex, !m_recent_confirmed_transactions_mutex, !m_most_recent_block_mutex, !m_headers_presync_mutex, g_msgproc_mutex); + bool SendMessages(CNode* pto) override + EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex, !m_recent_confirmed_transactions_mutex, !m_most_recent_block_mutex, g_msgproc_mutex); /** Implement PeerManager */ void StartScheduledTasks(CScheduler& scheduler) override; void CheckForStaleTipAndEvictPeers() override; - std::optional<std::string> FetchBlock(NodeId peer_id, const CBlockIndex& block_index) override; + std::optional<std::string> FetchBlock(NodeId peer_id, const CBlockIndex& block_index) override + EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex); bool GetNodeStateStats(NodeId nodeid, CNodeStateStats& stats) const override EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex); bool IgnoresIncomingTxs() override { return m_ignore_incoming_txs; } void SendPings() override EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex); void RelayTransaction(const uint256& txid, const uint256& wtxid) override EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex); void SetBestHeight(int height) override { m_best_height = height; }; - void Misbehaving(const NodeId pnode, const int howmuch, const std::string& message) override EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex); + void UnitTestMisbehaving(NodeId peer_id, int howmuch) override EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex) { Misbehaving(*Assert(GetPeerRef(peer_id)), howmuch, ""); }; void ProcessMessage(CNode& pfrom, const std::string& msg_type, CDataStream& vRecv, const std::chrono::microseconds time_received, const std::atomic<bool>& interruptMsgProc) override - EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex, !m_recent_confirmed_transactions_mutex, !m_most_recent_block_mutex); + EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex, !m_recent_confirmed_transactions_mutex, !m_most_recent_block_mutex, !m_headers_presync_mutex, g_msgproc_mutex); void UpdateLastBlockAnnounceTime(NodeId node, int64_t time_in_seconds) override; private: /** Consider evicting an outbound peer based on the amount of time they've been behind our tip */ - void ConsiderEviction(CNode& pto, std::chrono::seconds time_in_seconds) EXCLUSIVE_LOCKS_REQUIRED(cs_main); + void ConsiderEviction(CNode& pto, Peer& peer, std::chrono::seconds time_in_seconds) EXCLUSIVE_LOCKS_REQUIRED(cs_main, g_msgproc_mutex); /** If we have extra outbound peers, try to disconnect the one with the oldest block announcement */ void EvictExtraOutboundPeers(std::chrono::seconds now) EXCLUSIVE_LOCKS_REQUIRED(cs_main); @@ -518,6 +554,12 @@ private: PeerRef RemovePeer(NodeId id) EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex); /** + * Increment peer's misbehavior score. If the new value >= DISCOURAGEMENT_THRESHOLD, mark the node + * to be discouraged, meaning the peer might be disconnected and added to the discouragement filter. + */ + void Misbehaving(Peer& peer, int howmuch, const std::string& message); + + /** * Potentially mark a node discouraged based on the contents of a BlockValidationState object * * @param[in] via_compact_block this bool is passed in because net_processing should @@ -549,14 +591,81 @@ private: void ProcessOrphanTx(std::set<uint256>& orphan_work_set) EXCLUSIVE_LOCKS_REQUIRED(cs_main, g_cs_orphans) EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex); - /** Process a single headers message from a peer. */ - void ProcessHeadersMessage(CNode& pfrom, const Peer& peer, - const std::vector<CBlockHeader>& headers, + /** Process a single headers message from a peer. + * + * @param[in] pfrom CNode of the peer + * @param[in] peer The peer sending us the headers + * @param[in] headers The headers received. Note that this may be modified within ProcessHeadersMessage. + * @param[in] via_compact_block Whether this header came in via compact block handling. + */ + void ProcessHeadersMessage(CNode& pfrom, Peer& peer, + std::vector<CBlockHeader>&& headers, bool via_compact_block) - EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex); + EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex, !m_headers_presync_mutex, g_msgproc_mutex); + /** Various helpers for headers processing, invoked by ProcessHeadersMessage() */ + /** Return true if headers are continuous and have valid proof-of-work (DoS points assigned on failure) */ + bool CheckHeadersPoW(const std::vector<CBlockHeader>& headers, const Consensus::Params& consensusParams, Peer& peer); + /** Calculate an anti-DoS work threshold for headers chains */ + arith_uint256 GetAntiDoSWorkThreshold(); + /** Deal with state tracking and headers sync for peers that send the + * occasional non-connecting header (this can happen due to BIP 130 headers + * announcements for blocks interacting with the 2hr (MAX_FUTURE_BLOCK_TIME) rule). */ + void HandleFewUnconnectingHeaders(CNode& pfrom, Peer& peer, const std::vector<CBlockHeader>& headers) EXCLUSIVE_LOCKS_REQUIRED(g_msgproc_mutex); + /** Return true if the headers connect to each other, false otherwise */ + bool CheckHeadersAreContinuous(const std::vector<CBlockHeader>& headers) const; + /** Try to continue a low-work headers sync that has already begun. + * Assumes the caller has already verified the headers connect, and has + * checked that each header satisfies the proof-of-work target included in + * the header. + * @param[in] peer The peer we're syncing with. + * @param[in] pfrom CNode of the peer + * @param[in,out] headers The headers to be processed. + * @return True if the passed in headers were successfully processed + * as the continuation of a low-work headers sync in progress; + * false otherwise. + * If false, the passed in headers will be returned back to + * the caller. + * If true, the returned headers may be empty, indicating + * there is no more work for the caller to do; or the headers + * may be populated with entries that have passed anti-DoS + * checks (and therefore may be validated for block index + * acceptance by the caller). + */ + bool IsContinuationOfLowWorkHeadersSync(Peer& peer, CNode& pfrom, + std::vector<CBlockHeader>& headers) + EXCLUSIVE_LOCKS_REQUIRED(peer.m_headers_sync_mutex, !m_headers_presync_mutex, g_msgproc_mutex); + /** Check work on a headers chain to be processed, and if insufficient, + * initiate our anti-DoS headers sync mechanism. + * + * @param[in] peer The peer whose headers we're processing. + * @param[in] pfrom CNode of the peer + * @param[in] chain_start_header Where these headers connect in our index. + * @param[in,out] headers The headers to be processed. + * + * @return True if chain was low work and a headers sync was + * initiated (and headers will be empty after calling); false + * otherwise. + */ + bool TryLowWorkHeadersSync(Peer& peer, CNode& pfrom, + const CBlockIndex* chain_start_header, + std::vector<CBlockHeader>& headers) + EXCLUSIVE_LOCKS_REQUIRED(!peer.m_headers_sync_mutex, !m_peer_mutex, !m_headers_presync_mutex, g_msgproc_mutex); + + /** Return true if the given header is an ancestor of + * m_chainman.m_best_header or our current tip */ + bool IsAncestorOfBestHeaderOrTip(const CBlockIndex* header) EXCLUSIVE_LOCKS_REQUIRED(cs_main); + + /** Request further headers from this peer with a given locator. + * We don't issue a getheaders message if we have a recent one outstanding. + * This returns true if a getheaders is actually sent, and false otherwise. + */ + bool MaybeSendGetHeaders(CNode& pfrom, const CBlockLocator& locator, Peer& peer) EXCLUSIVE_LOCKS_REQUIRED(g_msgproc_mutex); + /** Potentially fetch blocks from this peer upon receipt of a new headers tip */ + void HeadersDirectFetchBlocks(CNode& pfrom, const Peer& peer, const CBlockIndex* pindexLast); + /** Update peer state based on received headers message */ + void UpdatePeerStateForReceivedHeaders(CNode& pfrom, const CBlockIndex *pindexLast, bool received_new_header, bool may_have_more_headers); - void SendBlockTransactions(CNode& pfrom, const CBlock& block, const BlockTransactionsRequest& req) - EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex); + void SendBlockTransactions(CNode& pfrom, Peer& peer, const CBlock& block, const BlockTransactionsRequest& req); /** Register with TxRequestTracker that an INV has been received from a * peer. The announcement parameters are decided in PeerManager and then @@ -574,7 +683,10 @@ private: void MaybeSendPing(CNode& node_to, Peer& peer, std::chrono::microseconds now); /** Send `addr` messages on a regular schedule. */ - void MaybeSendAddr(CNode& node, Peer& peer, std::chrono::microseconds current_time); + void MaybeSendAddr(CNode& node, Peer& peer, std::chrono::microseconds current_time) EXCLUSIVE_LOCKS_REQUIRED(g_msgproc_mutex); + + /** Send a single `sendheaders` message, after we have completed headers sync with a peer. */ + void MaybeSendSendHeaders(CNode& node, Peer& peer) EXCLUSIVE_LOCKS_REQUIRED(g_msgproc_mutex); /** Relay (gossip) an address to a few randomly chosen nodes. * @@ -583,10 +695,10 @@ private: * @param[in] fReachable Whether the address' network is reachable. We relay unreachable * addresses less. */ - void RelayAddress(NodeId originator, const CAddress& addr, bool fReachable) EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex); + void RelayAddress(NodeId originator, const CAddress& addr, bool fReachable) EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex, g_msgproc_mutex); /** Send `feefilter` message. */ - void MaybeSendFeefilter(CNode& node, Peer& peer, std::chrono::microseconds current_time); + void MaybeSendFeefilter(CNode& node, Peer& peer, std::chrono::microseconds current_time) EXCLUSIVE_LOCKS_REQUIRED(g_msgproc_mutex); const CChainParams& m_chainparams; CConnman& m_connman; @@ -601,14 +713,16 @@ private: std::atomic<int> m_best_height{-1}; /** Next time to check for stale tip */ - std::chrono::seconds m_stale_tip_check_time{0s}; + std::chrono::seconds m_stale_tip_check_time GUARDED_BY(cs_main){0s}; - /** Whether this node is running in blocks only mode */ + /** Whether this node is running in -blocksonly mode */ const bool m_ignore_incoming_txs; + bool RejectIncomingTxs(const CNode& peer) const; + /** Whether we've completed initial sync yet, for determining when to turn * on extra block-relay-only peers. */ - bool m_initial_sync_finished{false}; + bool m_initial_sync_finished GUARDED_BY(cs_main){false}; /** Protects m_peer_map. This mutex must not be locked while holding a lock * on any of the mutexes inside a Peer object. */ @@ -629,13 +743,16 @@ private: /** Get a pointer to a mutable CNodeState. */ CNodeState* State(NodeId pnode) EXCLUSIVE_LOCKS_REQUIRED(cs_main); - uint32_t GetFetchFlags(const CNode& pfrom) const EXCLUSIVE_LOCKS_REQUIRED(cs_main); + uint32_t GetFetchFlags(const Peer& peer) const; std::atomic<std::chrono::microseconds> m_next_inv_to_inbounds{0us}; /** Number of nodes with fSyncStarted. */ int nSyncStarted GUARDED_BY(cs_main) = 0; + /** Hash of the last block we received via INV */ + uint256 m_last_block_inv_triggering_headers_sync GUARDED_BY(g_msgproc_mutex){}; + /** * Sources of received blocks, saved to be able punish them when processing * happens afterwards. @@ -727,8 +844,26 @@ private: std::shared_ptr<const CBlockHeaderAndShortTxIDs> m_most_recent_compact_block GUARDED_BY(m_most_recent_block_mutex); uint256 m_most_recent_block_hash GUARDED_BY(m_most_recent_block_mutex); + // Data about the low-work headers synchronization, aggregated from all peers' HeadersSyncStates. + /** Mutex guarding the other m_headers_presync_* variables. */ + Mutex m_headers_presync_mutex; + /** A type to represent statistics about a peer's low-work headers sync. + * + * - The first field is the total verified amount of work in that synchronization. + * - The second is: + * - nullopt: the sync is in REDOWNLOAD phase (phase 2). + * - {height, timestamp}: the sync has the specified tip height and block timestamp (phase 1). + */ + using HeadersPresyncStats = std::pair<arith_uint256, std::optional<std::pair<int64_t, uint32_t>>>; + /** Statistics for all peers in low-work headers sync. */ + std::map<NodeId, HeadersPresyncStats> m_headers_presync_stats GUARDED_BY(m_headers_presync_mutex) {}; + /** The peer with the most-work entry in m_headers_presync_stats. */ + NodeId m_headers_presync_bestpeer GUARDED_BY(m_headers_presync_mutex) {-1}; + /** The m_headers_presync_stats improved, and needs signalling. */ + std::atomic_bool m_headers_presync_should_signal{false}; + /** Height of the highest block announced using BIP 152 high-bandwidth mode. */ - int m_highest_fast_announce{0}; + int m_highest_fast_announce GUARDED_BY(::cs_main){0}; /** Have we requested this block from a peer */ bool IsBlockRequested(const uint256& hash) EXCLUSIVE_LOCKS_REQUIRED(cs_main); @@ -750,7 +885,7 @@ private: /** Update pindexLastCommonBlock and add not-in-flight missing successors to vBlocks, until it has * at most count entries. */ - void FindNextBlocksToDownload(NodeId nodeid, unsigned int count, std::vector<const CBlockIndex*>& vBlocks, NodeId& nodeStaller) EXCLUSIVE_LOCKS_REQUIRED(cs_main); + void FindNextBlocksToDownload(const Peer& peer, unsigned int count, std::vector<const CBlockIndex*>& vBlocks, NodeId& nodeStaller) EXCLUSIVE_LOCKS_REQUIRED(cs_main); std::map<uint256, std::pair<NodeId, std::list<QueuedBlock>::iterator> > mapBlocksInFlight GUARDED_BY(cs_main); @@ -764,7 +899,7 @@ private: EXCLUSIVE_LOCKS_REQUIRED(!m_most_recent_block_mutex, peer.m_getdata_requests_mutex) LOCKS_EXCLUDED(::cs_main); /** Process a new block. Perform any post-processing housekeeping */ - void ProcessBlock(CNode& node, const std::shared_ptr<const CBlock>& block, bool force_processing); + void ProcessBlock(CNode& node, const std::shared_ptr<const CBlock>& block, bool force_processing, bool min_pow_checked); /** Relay map (txid or wtxid -> CTransactionRef) */ typedef std::map<uint256, CTransactionRef> MapRelay; @@ -820,6 +955,7 @@ private: * * May disconnect from the peer in the case of a bad request. * + * @param[in] node The node that we received the request from * @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 @@ -829,7 +965,7 @@ private: * @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, + bool PrepareBlockFilterRequest(CNode& node, Peer& peer, BlockFilterType filter_type, uint32_t start_height, const uint256& stop_hash, uint32_t max_height_diff, const CBlockIndex*& stop_index, @@ -840,30 +976,33 @@ private: * * May disconnect from the peer in the case of a bad request. * + * @param[in] node The node that we received the request from * @param[in] peer The peer that we received the request from * @param[in] vRecv The raw message received */ - void ProcessGetCFilters(CNode& peer, CDataStream& vRecv); + void ProcessGetCFilters(CNode& node, Peer& peer, CDataStream& vRecv); /** * Handle a cfheaders request. * * May disconnect from the peer in the case of a bad request. * + * @param[in] node The node that we received the request from * @param[in] peer The peer that we received the request from * @param[in] vRecv The raw message received */ - void ProcessGetCFHeaders(CNode& peer, CDataStream& vRecv); + void ProcessGetCFHeaders(CNode& node, Peer& peer, CDataStream& vRecv); /** * Handle a getcfcheckpt request. * * May disconnect from the peer in the case of a bad request. * + * @param[in] node The node that we received the request from * @param[in] peer The peer that we received the request from * @param[in] vRecv The raw message received */ - void ProcessGetCFCheckPt(CNode& peer, CDataStream& vRecv); + void ProcessGetCFCheckPt(CNode& node, Peer& peer, CDataStream& vRecv); /** Checks if address relay is permitted with peer. If needed, initializes * the m_addr_known bloom filter and sets m_addr_relay_enabled to true. @@ -871,7 +1010,10 @@ private: * @return True if address relay is enabled with peer * False if address relay is disallowed */ - bool SetupAddressRelay(const CNode& node, Peer& peer); + bool SetupAddressRelay(const CNode& node, Peer& peer) EXCLUSIVE_LOCKS_REQUIRED(g_msgproc_mutex); + + void AddAddressKnown(Peer& peer, const CAddress& addr) EXCLUSIVE_LOCKS_REQUIRED(g_msgproc_mutex); + void PushAddress(Peer& peer, const CAddress& addr, FastRandomContext& insecure_rand) EXCLUSIVE_LOCKS_REQUIRED(g_msgproc_mutex); }; const CNodeState* PeerManagerImpl::State(NodeId pnode) const EXCLUSIVE_LOCKS_REQUIRED(cs_main) @@ -897,13 +1039,13 @@ static bool IsAddrCompatible(const Peer& peer, const CAddress& addr) return peer.m_wants_addrv2 || addr.IsAddrV1Compatible(); } -static void AddAddressKnown(Peer& peer, const CAddress& addr) +void PeerManagerImpl::AddAddressKnown(Peer& peer, const CAddress& addr) { assert(peer.m_addr_known); peer.m_addr_known->insert(addr.GetKey()); } -static void PushAddress(Peer& peer, const CAddress& addr, FastRandomContext& insecure_rand) +void PeerManagerImpl::PushAddress(Peer& peer, const CAddress& addr, FastRandomContext& insecure_rand) { // Known checking here is only to save space from duplicates. // Before sending, we'll filter it again for known addresses that were @@ -927,6 +1069,26 @@ static void AddKnownTx(Peer& peer, const uint256& hash) tx_relay->m_tx_inventory_known_filter.insert(hash); } +/** Whether this peer can serve us blocks. */ +static bool CanServeBlocks(const Peer& peer) +{ + return peer.m_their_services & (NODE_NETWORK|NODE_NETWORK_LIMITED); +} + +/** Whether this peer can only serve limited recent blocks (e.g. because + * it prunes old blocks) */ +static bool IsLimitedPeer(const Peer& peer) +{ + return (!(peer.m_their_services & NODE_NETWORK) && + (peer.m_their_services & NODE_NETWORK_LIMITED)); +} + +/** Whether this peer can serve us witness data */ +static bool CanServeWitnesses(const Peer& peer) +{ + return peer.m_their_services & NODE_WITNESS; +} + std::chrono::microseconds PeerManagerImpl::NextInvToInbounds(std::chrono::microseconds now, std::chrono::seconds average_interval) { @@ -1009,7 +1171,7 @@ void PeerManagerImpl::MaybeSetPeerAsAnnouncingHeaderAndIDs(NodeId nodeid) { AssertLockHeld(cs_main); - // Never request high-bandwidth mode from peers if we're blocks-only. Our + // When in -blocksonly mode, never request high-bandwidth mode from peers. Our // mempool will not contain the transactions necessary to reconstruct the // compact block. if (m_ignore_incoming_txs) return; @@ -1075,7 +1237,7 @@ bool PeerManagerImpl::TipMayBeStale() bool PeerManagerImpl::CanDirectFetch() { - return m_chainman.ActiveChain().Tip()->GetBlockTime() > GetAdjustedTime() - m_chainparams.GetConsensus().nPowTargetSpacing * 20; + return m_chainman.ActiveChain().Tip()->Time() > GetAdjustedTime() - m_chainparams.GetConsensus().PowTargetSpacing() * 20; } static bool PeerHasHeader(CNodeState *state, const CBlockIndex *pindex) EXCLUSIVE_LOCKS_REQUIRED(cs_main) @@ -1120,17 +1282,17 @@ void PeerManagerImpl::UpdateBlockAvailability(NodeId nodeid, const uint256 &hash } } -void PeerManagerImpl::FindNextBlocksToDownload(NodeId nodeid, unsigned int count, std::vector<const CBlockIndex*>& vBlocks, NodeId& nodeStaller) +void PeerManagerImpl::FindNextBlocksToDownload(const Peer& peer, unsigned int count, std::vector<const CBlockIndex*>& vBlocks, NodeId& nodeStaller) { if (count == 0) return; vBlocks.reserve(vBlocks.size() + count); - CNodeState *state = State(nodeid); + CNodeState *state = State(peer.m_id); assert(state != nullptr); // Make sure pindexBestKnownBlock is up to date, we'll need it. - ProcessBlockAvailability(nodeid); + ProcessBlockAvailability(peer.m_id); if (state->pindexBestKnownBlock == nullptr || state->pindexBestKnownBlock->nChainWork < m_chainman.ActiveChain().Tip()->nChainWork || state->pindexBestKnownBlock->nChainWork < nMinimumChainWork) { // This peer has nothing interesting. @@ -1178,7 +1340,7 @@ void PeerManagerImpl::FindNextBlocksToDownload(NodeId nodeid, unsigned int count // We consider the chain that this peer is on invalid. return; } - if (!State(nodeid)->fHaveWitness && DeploymentActiveAt(*pindex, m_chainman, Consensus::DEPLOYMENT_SEGWIT)) { + if (!CanServeWitnesses(peer) && DeploymentActiveAt(*pindex, m_chainman, Consensus::DEPLOYMENT_SEGWIT)) { // We wouldn't download this block or its descendants from this peer. return; } @@ -1189,7 +1351,7 @@ void PeerManagerImpl::FindNextBlocksToDownload(NodeId nodeid, unsigned int count // The block is not already downloaded, and not yet in flight. if (pindex->nHeight > nWindowEnd) { // We reached the end of the window. - if (vBlocks.size() == 0 && waitingfor != nodeid) { + if (vBlocks.size() == 0 && waitingfor != peer.m_id) { // We aren't able to fetch anything, but we would be if the download window was one larger. nodeStaller = waitingfor; } @@ -1211,10 +1373,7 @@ void PeerManagerImpl::FindNextBlocksToDownload(NodeId nodeid, unsigned int count void PeerManagerImpl::PushNodeVersion(CNode& pnode, const Peer& peer) { - // Note that pnode->GetLocalServices() is a reflection of the local - // services we were offering when the CNode object was created for this - // peer. - uint64_t my_services{pnode.GetLocalServices()}; + uint64_t my_services{peer.m_our_services}; const int64_t nTime{count_seconds(GetTime<std::chrono::seconds>())}; uint64_t nonce = pnode.GetLocalNonce(); const int nNodeStartingHeight{m_best_height}; @@ -1271,21 +1430,21 @@ void PeerManagerImpl::UpdateLastBlockAnnounceTime(NodeId node, int64_t time_in_s if (state) state->m_last_block_announcement = time_in_seconds; } -void PeerManagerImpl::InitializeNode(CNode *pnode) +void PeerManagerImpl::InitializeNode(CNode& node, ServiceFlags our_services) { - NodeId nodeid = pnode->GetId(); + NodeId nodeid = node.GetId(); { LOCK(cs_main); - m_node_states.emplace_hint(m_node_states.end(), std::piecewise_construct, std::forward_as_tuple(nodeid), std::forward_as_tuple(pnode->IsInboundConn())); + m_node_states.emplace_hint(m_node_states.end(), std::piecewise_construct, std::forward_as_tuple(nodeid), std::forward_as_tuple(node.IsInboundConn())); assert(m_txrequest.Count(nodeid) == 0); } - PeerRef peer = std::make_shared<Peer>(nodeid); + PeerRef peer = std::make_shared<Peer>(nodeid, our_services); { LOCK(m_peer_mutex); m_peer_map.emplace_hint(m_peer_map.end(), nodeid, peer); } - if (!pnode->IsInboundConn()) { - PushNodeVersion(*pnode, *peer); + if (!node.IsInboundConn()) { + PushNodeVersion(node, *peer); } } @@ -1364,6 +1523,10 @@ void PeerManagerImpl::FinalizeNode(const CNode& node) // fSuccessfullyConnected set. m_addrman.Connected(node.addr); } + { + LOCK(m_headers_presync_mutex); + m_headers_presync_stats.erase(nodeid); + } LogPrint(BCLog::NET, "Cleared nodestate for peer=%d\n", nodeid); } @@ -1403,6 +1566,7 @@ bool PeerManagerImpl::GetNodeStateStats(NodeId nodeid, CNodeStateStats& stats) c PeerRef peer = GetPeerRef(nodeid); if (peer == nullptr) return false; + stats.their_services = peer->m_their_services; stats.m_starting_height = peer->m_starting_height; // It is common for nodes with good ping times to suddenly become lagged, // due to a new block arriving or other large transfer. @@ -1427,6 +1591,12 @@ bool PeerManagerImpl::GetNodeStateStats(NodeId nodeid, CNodeStateStats& stats) c stats.m_addr_processed = peer->m_addr_processed.load(); stats.m_addr_rate_limited = peer->m_addr_rate_limited.load(); stats.m_addr_relay_enabled = peer->m_addr_relay_enabled.load(); + { + LOCK(peer->m_headers_sync_mutex); + if (peer->m_headers_sync) { + stats.presync_height = peer->m_headers_sync->GetPresyncHeight(); + } + } return true; } @@ -1442,41 +1612,43 @@ void PeerManagerImpl::AddToCompactExtraTransactions(const CTransactionRef& tx) vExtraTxnForCompactIt = (vExtraTxnForCompactIt + 1) % max_extra_txn; } -void PeerManagerImpl::Misbehaving(const NodeId pnode, const int howmuch, const std::string& message) +void PeerManagerImpl::Misbehaving(Peer& peer, int howmuch, const std::string& message) { assert(howmuch > 0); - PeerRef peer = GetPeerRef(pnode); - if (peer == nullptr) return; - - LOCK(peer->m_misbehavior_mutex); - const int score_before{peer->m_misbehavior_score}; - peer->m_misbehavior_score += howmuch; - const int score_now{peer->m_misbehavior_score}; + LOCK(peer.m_misbehavior_mutex); + const int score_before{peer.m_misbehavior_score}; + peer.m_misbehavior_score += howmuch; + const int score_now{peer.m_misbehavior_score}; const std::string message_prefixed = message.empty() ? "" : (": " + message); std::string warning; if (score_now >= DISCOURAGEMENT_THRESHOLD && score_before < DISCOURAGEMENT_THRESHOLD) { warning = " DISCOURAGE THRESHOLD EXCEEDED"; - peer->m_should_discourage = true; + peer.m_should_discourage = true; } LogPrint(BCLog::NET, "Misbehaving: peer=%d (%d -> %d)%s%s\n", - pnode, score_before, score_now, warning, message_prefixed); + peer.m_id, score_before, score_now, warning, message_prefixed); } bool PeerManagerImpl::MaybePunishNodeForBlock(NodeId nodeid, const BlockValidationState& state, bool via_compact_block, const std::string& message) { + PeerRef peer{GetPeerRef(nodeid)}; switch (state.GetResult()) { case BlockValidationResult::BLOCK_RESULT_UNSET: break; + case BlockValidationResult::BLOCK_HEADER_LOW_WORK: + // We didn't try to process the block because the header chain may have + // too little work. + break; // The node is providing invalid data: case BlockValidationResult::BLOCK_CONSENSUS: case BlockValidationResult::BLOCK_MUTATED: if (!via_compact_block) { - Misbehaving(nodeid, 100, message); + if (peer) Misbehaving(*peer, 100, message); return true; } break; @@ -1491,7 +1663,7 @@ bool PeerManagerImpl::MaybePunishNodeForBlock(NodeId nodeid, const BlockValidati // Discourage outbound (but not inbound) peers if on an invalid chain. // Exempt HB compact block peers. Manual connections are always protected from discouragement. if (!via_compact_block && !node_state->m_is_inbound) { - Misbehaving(nodeid, 100, message); + if (peer) Misbehaving(*peer, 100, message); return true; } break; @@ -1499,12 +1671,12 @@ bool PeerManagerImpl::MaybePunishNodeForBlock(NodeId nodeid, const BlockValidati case BlockValidationResult::BLOCK_INVALID_HEADER: case BlockValidationResult::BLOCK_CHECKPOINT: case BlockValidationResult::BLOCK_INVALID_PREV: - Misbehaving(nodeid, 100, message); + if (peer) Misbehaving(*peer, 100, message); return true; // Conflicting (but not necessarily invalid) data or different policy: case BlockValidationResult::BLOCK_MISSING_PREV: // TODO: Handle this much more gracefully (10 DoS points is super arbitrary) - Misbehaving(nodeid, 10, message); + if (peer) Misbehaving(*peer, 10, message); return true; case BlockValidationResult::BLOCK_RECENT_CONSENSUS_CHANGE: case BlockValidationResult::BLOCK_TIME_FUTURE: @@ -1518,12 +1690,13 @@ bool PeerManagerImpl::MaybePunishNodeForBlock(NodeId nodeid, const BlockValidati bool PeerManagerImpl::MaybePunishNodeForTx(NodeId nodeid, const TxValidationState& state, const std::string& message) { + PeerRef peer{GetPeerRef(nodeid)}; switch (state.GetResult()) { case TxValidationResult::TX_RESULT_UNSET: break; // The node is providing invalid data: case TxValidationResult::TX_CONSENSUS: - Misbehaving(nodeid, 100, message); + if (peer) Misbehaving(*peer, 100, message); return true; // Conflicting (but not necessarily invalid) data or different policy: case TxValidationResult::TX_RECENT_CONSENSUS_CHANGE: @@ -1558,12 +1731,14 @@ std::optional<std::string> PeerManagerImpl::FetchBlock(NodeId peer_id, const CBl if (fImporting) return "Importing..."; if (fReindex) return "Reindexing..."; - LOCK(cs_main); // Ensure this peer exists and hasn't been disconnected - CNodeState* state = State(peer_id); - if (state == nullptr) return "Peer does not exist"; + PeerRef peer = GetPeerRef(peer_id); + if (peer == nullptr) return "Peer does not exist"; + // Ignore pre-segwit peers - if (!state->fHaveWitness) return "Pre-SegWit peer"; + if (!CanServeWitnesses(*peer)) return "Pre-SegWit peer"; + + LOCK(cs_main); // Mark block as in-flight unless it already is (for this peer). // If a block was already in-flight for a different peer, its BLOCKTXN @@ -1947,7 +2122,7 @@ void PeerManagerImpl::ProcessGetBlockData(CNode& pfrom, Peer& peer, const CInv& } // Avoid leaking prune-height by never sending blocks below the NODE_NETWORK_LIMITED threshold if (!pfrom.HasPermission(NetPermissionFlags::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 */) ) + (((peer.m_our_services & NODE_NETWORK_LIMITED) == NODE_NETWORK_LIMITED) && ((peer.m_our_services & 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, disconnect peer=%d\n", pfrom.GetId()); //disconnect node and prevent it from stalling (would otherwise wait for the missing block) @@ -2164,21 +2339,21 @@ void PeerManagerImpl::ProcessGetData(CNode& pfrom, Peer& peer, const std::atomic } } -uint32_t PeerManagerImpl::GetFetchFlags(const CNode& pfrom) const EXCLUSIVE_LOCKS_REQUIRED(cs_main) +uint32_t PeerManagerImpl::GetFetchFlags(const Peer& peer) const { uint32_t nFetchFlags = 0; - if (State(pfrom.GetId())->fHaveWitness) { + if (CanServeWitnesses(peer)) { nFetchFlags |= MSG_WITNESS_FLAG; } return nFetchFlags; } -void PeerManagerImpl::SendBlockTransactions(CNode& pfrom, const CBlock& block, const BlockTransactionsRequest& req) +void PeerManagerImpl::SendBlockTransactions(CNode& pfrom, Peer& peer, const CBlock& block, const BlockTransactionsRequest& req) { BlockTransactions resp(req); for (size_t i = 0; i < req.indexes.size(); i++) { if (req.indexes[i] >= block.vtx.size()) { - Misbehaving(pfrom.GetId(), 100, "getblocktxn with out-of-bounds tx indices"); + Misbehaving(peer, 100, "getblocktxn with out-of-bounds tx indices"); return; } resp.txn[i] = block.vtx[req.indexes[i]]; @@ -2188,192 +2363,515 @@ void PeerManagerImpl::SendBlockTransactions(CNode& pfrom, const CBlock& block, c m_connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::BLOCKTXN, resp)); } -void PeerManagerImpl::ProcessHeadersMessage(CNode& pfrom, const Peer& peer, - const std::vector<CBlockHeader>& headers, - bool via_compact_block) +bool PeerManagerImpl::CheckHeadersPoW(const std::vector<CBlockHeader>& headers, const Consensus::Params& consensusParams, Peer& peer) +{ + // Do these headers have proof-of-work matching what's claimed? + if (!HasValidProofOfWork(headers, consensusParams)) { + Misbehaving(peer, 100, "header with invalid proof of work"); + return false; + } + + // Are these headers connected to each other? + if (!CheckHeadersAreContinuous(headers)) { + Misbehaving(peer, 20, "non-continuous headers sequence"); + return false; + } + return true; +} + +arith_uint256 PeerManagerImpl::GetAntiDoSWorkThreshold() +{ + arith_uint256 near_chaintip_work = 0; + LOCK(cs_main); + if (m_chainman.ActiveChain().Tip() != nullptr) { + const CBlockIndex *tip = m_chainman.ActiveChain().Tip(); + // Use a 144 block buffer, so that we'll accept headers that fork from + // near our tip. + near_chaintip_work = tip->nChainWork - std::min<arith_uint256>(144*GetBlockProof(*tip), tip->nChainWork); + } + return std::max(near_chaintip_work, arith_uint256(nMinimumChainWork)); +} + +/** + * Special handling for unconnecting headers that might be part of a block + * announcement. + * + * We'll send a getheaders message in response to try to connect the chain. + * + * The peer can send up to MAX_UNCONNECTING_HEADERS in a row that + * don't connect before given DoS points. + * + * Once a headers message is received that is valid and does connect, + * nUnconnectingHeaders gets reset back to 0. + */ +void PeerManagerImpl::HandleFewUnconnectingHeaders(CNode& pfrom, Peer& peer, + const std::vector<CBlockHeader>& headers) { const CNetMsgMaker msgMaker(pfrom.GetCommonVersion()); - size_t nCount = headers.size(); - if (nCount == 0) { - // Nothing interesting. Stop asking this peers for more headers. - return; + LOCK(cs_main); + CNodeState *nodestate = State(pfrom.GetId()); + + nodestate->nUnconnectingHeaders++; + // Try to fill in the missing headers. + if (MaybeSendGetHeaders(pfrom, GetLocator(m_chainman.m_best_header), peer)) { + 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(), + m_chainman.m_best_header->nHeight, + pfrom.GetId(), nodestate->nUnconnectingHeaders); + } + // Set hashLastUnknownBlock for this peer, so that if we + // eventually get the headers - even from a different peer - + // we can use this peer to download. + UpdateBlockAvailability(pfrom.GetId(), headers.back().GetHash()); + + // The peer may just be broken, so periodically assign DoS points if this + // condition persists. + if (nodestate->nUnconnectingHeaders % MAX_UNCONNECTING_HEADERS == 0) { + Misbehaving(peer, 20, strprintf("%d non-connecting headers", nodestate->nUnconnectingHeaders)); } +} - bool received_new_header = false; - const CBlockIndex *pindexLast = nullptr; - { - LOCK(cs_main); - CNodeState *nodestate = State(pfrom.GetId()); +bool PeerManagerImpl::CheckHeadersAreContinuous(const std::vector<CBlockHeader>& headers) const +{ + uint256 hashLastBlock; + for (const CBlockHeader& header : headers) { + if (!hashLastBlock.IsNull() && header.hashPrevBlock != hashLastBlock) { + return false; + } + hashLastBlock = header.GetHash(); + } + return true; +} - // If this looks like it could be a block announcement (nCount < - // MAX_BLOCKS_TO_ANNOUNCE), use special logic for handling headers that - // don't connect: - // - Send a getheaders message in response to try to connect the chain. - // - The peer can send up to MAX_UNCONNECTING_HEADERS in a row that - // don't connect before giving DoS points - // - Once a headers message is received that is valid and does connect, - // nUnconnectingHeaders gets reset back to 0. - if (!m_chainman.m_blockman.LookupBlockIndex(headers[0].hashPrevBlock) && nCount < MAX_BLOCKS_TO_ANNOUNCE) { - nodestate->nUnconnectingHeaders++; - m_connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::GETHEADERS, m_chainman.ActiveChain().GetLocator(m_chainman.m_best_header), uint256())); - LogPrint(BCLog::NET, "received header %s: missing prev block %s, sending getheaders (%d) to end (peer=%d, nUnconnectingHeaders=%d)\n", - headers[0].GetHash().ToString(), - headers[0].hashPrevBlock.ToString(), - m_chainman.m_best_header->nHeight, - pfrom.GetId(), nodestate->nUnconnectingHeaders); - // Set hashLastUnknownBlock for this peer, so that if we - // eventually get the headers - even from a different peer - - // we can use this peer to download. - UpdateBlockAvailability(pfrom.GetId(), headers.back().GetHash()); - - if (nodestate->nUnconnectingHeaders % MAX_UNCONNECTING_HEADERS == 0) { - Misbehaving(pfrom.GetId(), 20, strprintf("%d non-connecting headers", nodestate->nUnconnectingHeaders)); +bool PeerManagerImpl::IsContinuationOfLowWorkHeadersSync(Peer& peer, CNode& pfrom, std::vector<CBlockHeader>& headers) +{ + if (peer.m_headers_sync) { + auto result = peer.m_headers_sync->ProcessNextHeaders(headers, headers.size() == MAX_HEADERS_RESULTS); + if (result.request_more) { + auto locator = peer.m_headers_sync->NextHeadersRequestLocator(); + // If we were instructed to ask for a locator, it should not be empty. + Assume(!locator.vHave.empty()); + if (!locator.vHave.empty()) { + // It should be impossible for the getheaders request to fail, + // because we should have cleared the last getheaders timestamp + // when processing the headers that triggered this call. But + // it may be possible to bypass this via compactblock + // processing, so check the result before logging just to be + // safe. + bool sent_getheaders = MaybeSendGetHeaders(pfrom, locator, peer); + if (sent_getheaders) { + LogPrint(BCLog::NET, "more getheaders (from %s) to peer=%d\n", + locator.vHave.front().ToString(), pfrom.GetId()); + } else { + LogPrint(BCLog::NET, "error sending next getheaders (from %s) to continue sync with peer=%d\n", + locator.vHave.front().ToString(), pfrom.GetId()); + } } - return; } - uint256 hashLastBlock; - for (const CBlockHeader& header : headers) { - if (!hashLastBlock.IsNull() && header.hashPrevBlock != hashLastBlock) { - Misbehaving(pfrom.GetId(), 20, "non-continuous headers sequence"); - return; + if (peer.m_headers_sync->GetState() == HeadersSyncState::State::FINAL) { + peer.m_headers_sync.reset(nullptr); + + // Delete this peer's entry in m_headers_presync_stats. + // If this is m_headers_presync_bestpeer, it will be replaced later + // by the next peer that triggers the else{} branch below. + LOCK(m_headers_presync_mutex); + m_headers_presync_stats.erase(pfrom.GetId()); + } else { + // Build statistics for this peer's sync. + HeadersPresyncStats stats; + stats.first = peer.m_headers_sync->GetPresyncWork(); + if (peer.m_headers_sync->GetState() == HeadersSyncState::State::PRESYNC) { + stats.second = {peer.m_headers_sync->GetPresyncHeight(), + peer.m_headers_sync->GetPresyncTime()}; + } + + // Update statistics in stats. + LOCK(m_headers_presync_mutex); + m_headers_presync_stats[pfrom.GetId()] = stats; + auto best_it = m_headers_presync_stats.find(m_headers_presync_bestpeer); + bool best_updated = false; + if (best_it == m_headers_presync_stats.end()) { + // If the cached best peer is outdated, iterate over all remaining ones (including + // newly updated one) to find the best one. + NodeId peer_best{-1}; + const HeadersPresyncStats* stat_best{nullptr}; + for (const auto& [peer, stat] : m_headers_presync_stats) { + if (!stat_best || stat > *stat_best) { + peer_best = peer; + stat_best = &stat; + } + } + m_headers_presync_bestpeer = peer_best; + best_updated = (peer_best == pfrom.GetId()); + } else if (best_it->first == pfrom.GetId() || stats > best_it->second) { + // pfrom was and remains the best peer, or pfrom just became best. + m_headers_presync_bestpeer = pfrom.GetId(); + best_updated = true; + } + if (best_updated && stats.second.has_value()) { + // If the best peer updated, and it is in its first phase, signal. + m_headers_presync_should_signal = true; } - hashLastBlock = header.GetHash(); } - // If we don't have the last header, then they'll have given us - // something new (if these headers are valid). - if (!m_chainman.m_blockman.LookupBlockIndex(hashLastBlock)) { - received_new_header = true; + if (result.success) { + // We only overwrite the headers passed in if processing was + // successful. + headers.swap(result.pow_validated_headers); } + + return result.success; } + // Either we didn't have a sync in progress, or something went wrong + // processing these headers, or we are returning headers to the caller to + // process. + return false; +} - BlockValidationState state; - if (!m_chainman.ProcessNewBlockHeaders(headers, state, &pindexLast)) { - if (state.IsInvalid()) { - MaybePunishNodeForBlock(pfrom.GetId(), state, via_compact_block, "invalid header received"); - return; +bool PeerManagerImpl::TryLowWorkHeadersSync(Peer& peer, CNode& pfrom, const CBlockIndex* chain_start_header, std::vector<CBlockHeader>& headers) +{ + // Calculate the total work on this chain. + arith_uint256 total_work = chain_start_header->nChainWork + CalculateHeadersWork(headers); + + // Our dynamic anti-DoS threshold (minimum work required on a headers chain + // before we'll store it) + arith_uint256 minimum_chain_work = GetAntiDoSWorkThreshold(); + + // Avoid DoS via low-difficulty-headers by only processing if the headers + // are part of a chain with sufficient work. + if (total_work < minimum_chain_work) { + // Only try to sync with this peer if their headers message was full; + // otherwise they don't have more headers after this so no point in + // trying to sync their too-little-work chain. + if (headers.size() == MAX_HEADERS_RESULTS) { + // Note: we could advance to the last header in this set that is + // known to us, rather than starting at the first header (which we + // may already have); however this is unlikely to matter much since + // ProcessHeadersMessage() already handles the case where all + // headers in a received message are already known and are + // ancestors of m_best_header or chainActive.Tip(), by skipping + // this logic in that case. So even if the first header in this set + // of headers is known, some header in this set must be new, so + // advancing to the first unknown header would be a small effect. + LOCK(peer.m_headers_sync_mutex); + peer.m_headers_sync.reset(new HeadersSyncState(peer.m_id, m_chainparams.GetConsensus(), + chain_start_header, minimum_chain_work)); + + // Now a HeadersSyncState object for tracking this synchronization is created, + // process the headers using it as normal. + return IsContinuationOfLowWorkHeadersSync(peer, pfrom, headers); + } else { + LogPrint(BCLog::NET, "Ignoring low-work chain (height=%u) from peer=%d\n", chain_start_header->nHeight + headers.size(), pfrom.GetId()); + // Since this is a low-work headers chain, no further processing is required. + headers = {}; + return true; } } + return false; +} - { - LOCK(cs_main); - CNodeState *nodestate = State(pfrom.GetId()); - if (nodestate->nUnconnectingHeaders > 0) { - LogPrint(BCLog::NET, "peer=%d: resetting nUnconnectingHeaders (%d -> 0)\n", pfrom.GetId(), nodestate->nUnconnectingHeaders); - } - nodestate->nUnconnectingHeaders = 0; +bool PeerManagerImpl::IsAncestorOfBestHeaderOrTip(const CBlockIndex* header) +{ + if (header == nullptr) { + return false; + } else if (m_chainman.m_best_header != nullptr && header == m_chainman.m_best_header->GetAncestor(header->nHeight)) { + return true; + } else if (m_chainman.ActiveChain().Contains(header)) { + return true; + } + return false; +} - assert(pindexLast); - UpdateBlockAvailability(pfrom.GetId(), pindexLast->GetBlockHash()); +bool PeerManagerImpl::MaybeSendGetHeaders(CNode& pfrom, const CBlockLocator& locator, Peer& peer) +{ + const CNetMsgMaker msgMaker(pfrom.GetCommonVersion()); - // From here, pindexBestKnownBlock should be guaranteed to be non-null, - // because it is set in UpdateBlockAvailability. Some nullptr checks - // are still present, however, as belt-and-suspenders. + const auto current_time = NodeClock::now(); - if (received_new_header && pindexLast->nChainWork > m_chainman.ActiveChain().Tip()->nChainWork) { - nodestate->m_last_block_announcement = GetTime(); - } + // Only allow a new getheaders message to go out if we don't have a recent + // one already in-flight + if (current_time - peer.m_last_getheaders_timestamp > HEADERS_RESPONSE_TIME) { + m_connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::GETHEADERS, locator, uint256())); + peer.m_last_getheaders_timestamp = current_time; + return true; + } + return false; +} - 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 m_chainman.ActiveChain().Tip or m_chainman.m_best_header, 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, m_chainman.ActiveChain().GetLocator(pindexLast), uint256())); - } - - // 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 (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 && !m_chainman.ActiveChain().Contains(pindexWalk) && vToFetch.size() <= MAX_BLOCKS_IN_TRANSIT_PER_PEER) { - if (!(pindexWalk->nStatus & BLOCK_HAVE_DATA) && - !IsBlockRequested(pindexWalk->GetBlockHash()) && - (!DeploymentActiveAt(*pindexWalk, m_chainman, Consensus::DEPLOYMENT_SEGWIT) || State(pfrom.GetId())->fHaveWitness)) { - // We don't have this block, and it's not yet in flight. - vToFetch.push_back(pindexWalk); - } - pindexWalk = pindexWalk->pprev; +/* + * Given a new headers tip ending in pindexLast, potentially request blocks towards that tip. + * We require that the given tip have at least as much work as our tip, and for + * our current tip to be "close to synced" (see CanDirectFetch()). + */ +void PeerManagerImpl::HeadersDirectFetchBlocks(CNode& pfrom, const Peer& peer, const CBlockIndex* pindexLast) +{ + const CNetMsgMaker msgMaker(pfrom.GetCommonVersion()); + + LOCK(cs_main); + CNodeState *nodestate = State(pfrom.GetId()); + + 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 && !m_chainman.ActiveChain().Contains(pindexWalk) && vToFetch.size() <= MAX_BLOCKS_IN_TRANSIT_PER_PEER) { + if (!(pindexWalk->nStatus & BLOCK_HAVE_DATA) && + !IsBlockRequested(pindexWalk->GetBlockHash()) && + (!DeploymentActiveAt(*pindexWalk, m_chainman, Consensus::DEPLOYMENT_SEGWIT) || CanServeWitnesses(peer))) { + // We don't have this block, and it's not yet in flight. + vToFetch.push_back(pindexWalk); } - // If pindexWalk still isn't on our main chain, we're looking at a - // 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 (!m_chainman.ActiveChain().Contains(pindexWalk)) { - LogPrint(BCLog::NET, "Large reorg, won't direct fetch to %s (%d)\n", - pindexLast->GetBlockHash().ToString(), - pindexLast->nHeight); - } else { - std::vector<CInv> vGetData; - // Download as much as possible, from earliest to latest. - for (const CBlockIndex *pindex : reverse_iterate(vToFetch)) { - if (nodestate->nBlocksInFlight >= MAX_BLOCKS_IN_TRANSIT_PER_PEER) { - // Can't download any more from this peer - break; - } - uint32_t nFetchFlags = GetFetchFlags(pfrom); - vGetData.push_back(CInv(MSG_BLOCK | nFetchFlags, pindex->GetBlockHash())); - BlockRequested(pfrom.GetId(), *pindex); - LogPrint(BCLog::NET, "Requesting block %s from peer=%d\n", - pindex->GetBlockHash().ToString(), pfrom.GetId()); - } - if (vGetData.size() > 1) { - LogPrint(BCLog::NET, "Downloading blocks toward %s (%d) via headers direct fetch\n", - pindexLast->GetBlockHash().ToString(), pindexLast->nHeight); + pindexWalk = pindexWalk->pprev; + } + // If pindexWalk still isn't on our main chain, we're looking at a + // 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 (!m_chainman.ActiveChain().Contains(pindexWalk)) { + LogPrint(BCLog::NET, "Large reorg, won't direct fetch to %s (%d)\n", + pindexLast->GetBlockHash().ToString(), + pindexLast->nHeight); + } else { + std::vector<CInv> vGetData; + // Download as much as possible, from earliest to latest. + for (const CBlockIndex *pindex : reverse_iterate(vToFetch)) { + if (nodestate->nBlocksInFlight >= MAX_BLOCKS_IN_TRANSIT_PER_PEER) { + // Can't download any more from this peer + break; } - if (vGetData.size() > 0) { - if (!m_ignore_incoming_txs && + uint32_t nFetchFlags = GetFetchFlags(peer); + vGetData.push_back(CInv(MSG_BLOCK | nFetchFlags, pindex->GetBlockHash())); + BlockRequested(pfrom.GetId(), *pindex); + LogPrint(BCLog::NET, "Requesting block %s from peer=%d\n", + pindex->GetBlockHash().ToString(), pfrom.GetId()); + } + if (vGetData.size() > 1) { + LogPrint(BCLog::NET, "Downloading blocks toward %s (%d) via headers direct fetch\n", + pindexLast->GetBlockHash().ToString(), pindexLast->nHeight); + } + if (vGetData.size() > 0) { + if (!m_ignore_incoming_txs && nodestate->m_provides_cmpctblocks && vGetData.size() == 1 && mapBlocksInFlight.size() == 1 && pindexLast->pprev->IsValid(BLOCK_VALID_CHAIN)) { - // In any case, we want to download using a compact block, not a regular one - vGetData[0] = CInv(MSG_CMPCT_BLOCK, vGetData[0].hash); - } - m_connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::GETDATA, vGetData)); + // In any case, we want to download using a compact block, not a regular one + vGetData[0] = CInv(MSG_CMPCT_BLOCK, vGetData[0].hash); } + m_connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::GETDATA, vGetData)); } } - // 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 (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) { - // This peer has too little work on their headers chain to help - // us sync -- disconnect if it is an outbound disconnection - // candidate. - // Note: We compare their tip to nMinimumChainWork (rather than - // 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. - if (pfrom.IsOutboundOrBlockRelayConn()) { - LogPrintf("Disconnecting outbound peer %d -- headers chain has insufficient work\n", pfrom.GetId()); - pfrom.fDisconnect = true; - } + } +} + +/** + * Given receipt of headers from a peer ending in pindexLast, along with + * whether that header was new and whether the headers message was full, + * update the state we keep for the peer. + */ +void PeerManagerImpl::UpdatePeerStateForReceivedHeaders(CNode& pfrom, + const CBlockIndex *pindexLast, bool received_new_header, bool may_have_more_headers) +{ + LOCK(cs_main); + CNodeState *nodestate = State(pfrom.GetId()); + if (nodestate->nUnconnectingHeaders > 0) { + LogPrint(BCLog::NET, "peer=%d: resetting nUnconnectingHeaders (%d -> 0)\n", pfrom.GetId(), nodestate->nUnconnectingHeaders); + } + nodestate->nUnconnectingHeaders = 0; + + assert(pindexLast); + UpdateBlockAvailability(pfrom.GetId(), pindexLast->GetBlockHash()); + + // From here, pindexBestKnownBlock should be guaranteed to be non-null, + // because it is set in UpdateBlockAvailability. Some nullptr checks + // are still present, however, as belt-and-suspenders. + + if (received_new_header && pindexLast->nChainWork > m_chainman.ActiveChain().Tip()->nChainWork) { + nodestate->m_last_block_announcement = GetTime(); + } + + // 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 (m_chainman.ActiveChainstate().IsInitialBlockDownload() && !may_have_more_headers) { + // If the peer has no more headers to give us, then we know we have + // their tip. + if (nodestate->pindexBestKnownBlock && nodestate->pindexBestKnownBlock->nChainWork < nMinimumChainWork) { + // This peer has too little work on their headers chain to help + // us sync -- disconnect if it is an outbound disconnection + // candidate. + // Note: We compare their tip to nMinimumChainWork (rather than + // 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. + if (pfrom.IsOutboundOrBlockRelayConn()) { + LogPrintf("Disconnecting outbound peer %d -- headers chain has insufficient work\n", pfrom.GetId()); + pfrom.fDisconnect = true; } } + } - // If this is an outbound full-relay peer, check to see if we should protect - // it from the bad/lagging chain logic. - // Note that outbound block-relay peers are excluded from this protection, and - // 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 >= 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; - } + // If this is an outbound full-relay peer, check to see if we should protect + // it from the bad/lagging chain logic. + // Note that outbound block-relay peers are excluded from this protection, and + // 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 >= 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; + } + } +} + +void PeerManagerImpl::ProcessHeadersMessage(CNode& pfrom, Peer& peer, + std::vector<CBlockHeader>&& headers, + bool via_compact_block) +{ + size_t nCount = headers.size(); + + if (nCount == 0) { + // Nothing interesting. Stop asking this peers for more headers. + // If we were in the middle of headers sync, receiving an empty headers + // message suggests that the peer suddenly has nothing to give us + // (perhaps it reorged to our chain). Clear download state for this peer. + LOCK(peer.m_headers_sync_mutex); + if (peer.m_headers_sync) { + peer.m_headers_sync.reset(nullptr); + LOCK(m_headers_presync_mutex); + m_headers_presync_stats.erase(pfrom.GetId()); } + return; + } + + // Before we do any processing, make sure these pass basic sanity checks. + // We'll rely on headers having valid proof-of-work further down, as an + // anti-DoS criteria (note: this check is required before passing any + // headers into HeadersSyncState). + if (!CheckHeadersPoW(headers, m_chainparams.GetConsensus(), peer)) { + // Misbehaving() calls are handled within CheckHeadersPoW(), so we can + // just return. (Note that even if a header is announced via compact + // block, the header itself should be valid, so this type of error can + // always be punished.) + return; } + const CBlockIndex *pindexLast = nullptr; + + // We'll set already_validated_work to true if these headers are + // successfully processed as part of a low-work headers sync in progress + // (either in PRESYNC or REDOWNLOAD phase). + // If true, this will mean that any headers returned to us (ie during + // REDOWNLOAD) can be validated without further anti-DoS checks. + bool already_validated_work = false; + + // If we're in the middle of headers sync, let it do its magic. + bool have_headers_sync = false; + { + LOCK(peer.m_headers_sync_mutex); + + already_validated_work = IsContinuationOfLowWorkHeadersSync(peer, pfrom, headers); + + // The headers we passed in may have been: + // - untouched, perhaps if no headers-sync was in progress, or some + // failure occurred + // - erased, such as if the headers were successfully processed and no + // additional headers processing needs to take place (such as if we + // are still in PRESYNC) + // - replaced with headers that are now ready for validation, such as + // during the REDOWNLOAD phase of a low-work headers sync. + // So just check whether we still have headers that we need to process, + // or not. + if (headers.empty()) { + return; + } + + have_headers_sync = !!peer.m_headers_sync; + } + + // Do these headers connect to something in our block index? + const CBlockIndex *chain_start_header{WITH_LOCK(::cs_main, return m_chainman.m_blockman.LookupBlockIndex(headers[0].hashPrevBlock))}; + bool headers_connect_blockindex{chain_start_header != nullptr}; + + if (!headers_connect_blockindex) { + if (nCount <= MAX_BLOCKS_TO_ANNOUNCE) { + // If this looks like it could be a BIP 130 block announcement, use + // special logic for handling headers that don't connect, as this + // could be benign. + HandleFewUnconnectingHeaders(pfrom, peer, headers); + } else { + Misbehaving(peer, 10, "invalid header received"); + } + return; + } + + // If the headers we received are already in memory and an ancestor of + // m_best_header or our tip, skip anti-DoS checks. These headers will not + // use any more memory (and we are not leaking information that could be + // used to fingerprint us). + const CBlockIndex *last_received_header{nullptr}; + { + LOCK(cs_main); + last_received_header = m_chainman.m_blockman.LookupBlockIndex(headers.back().GetHash()); + if (IsAncestorOfBestHeaderOrTip(last_received_header)) { + already_validated_work = true; + } + } + + // If our peer has NetPermissionFlags::NoBan privileges, then bypass our + // anti-DoS logic (this saves bandwidth when we connect to a trusted peer + // on startup). + if (pfrom.HasPermission(NetPermissionFlags::NoBan)) { + already_validated_work = true; + } + + // At this point, the headers connect to something in our block index. + // Do anti-DoS checks to determine if we should process or store for later + // processing. + if (!already_validated_work && TryLowWorkHeadersSync(peer, pfrom, + chain_start_header, headers)) { + // If we successfully started a low-work headers sync, then there + // should be no headers to process any further. + Assume(headers.empty()); + return; + } + + // At this point, we have a set of headers with sufficient work on them + // which can be processed. + + // If we don't have the last header, then this peer will have given us + // something new (if these headers are valid). + bool received_new_header{last_received_header == nullptr}; + + // Now process all the headers. + BlockValidationState state; + if (!m_chainman.ProcessNewBlockHeaders(headers, /*min_pow_checked=*/true, state, &pindexLast)) { + if (state.IsInvalid()) { + MaybePunishNodeForBlock(pfrom.GetId(), state, via_compact_block, "invalid header received"); + return; + } + } + Assume(pindexLast); + + // Consider fetching more headers if we are not using our headers-sync mechanism. + if (nCount == MAX_HEADERS_RESULTS && !have_headers_sync) { + // Headers message had its maximum size; the peer may have more headers. + if (MaybeSendGetHeaders(pfrom, GetLocator(pindexLast), peer)) { + LogPrint(BCLog::NET, "more getheaders (%d) to end to peer=%d (startheight:%d)\n", + pindexLast->nHeight, pfrom.GetId(), peer.m_starting_height); + } + } + + UpdatePeerStateForReceivedHeaders(pfrom, pindexLast, received_new_header, nCount == MAX_HEADERS_RESULTS); + + // Consider immediately downloading blocks. + HeadersDirectFetchBlocks(pfrom, peer, pindexLast); + return; } @@ -2456,7 +2954,7 @@ void PeerManagerImpl::ProcessOrphanTx(std::set<uint256>& orphan_work_set) } } -bool PeerManagerImpl::PrepareBlockFilterRequest(CNode& peer, +bool PeerManagerImpl::PrepareBlockFilterRequest(CNode& node, Peer& peer, BlockFilterType filter_type, uint32_t start_height, const uint256& stop_hash, uint32_t max_height_diff, const CBlockIndex*& stop_index, @@ -2464,11 +2962,11 @@ bool PeerManagerImpl::PrepareBlockFilterRequest(CNode& peer, { const bool supported_filter_type = (filter_type == BlockFilterType::BASIC && - (peer.GetLocalServices() & NODE_COMPACT_FILTERS)); + (peer.m_our_services & NODE_COMPACT_FILTERS)); if (!supported_filter_type) { LogPrint(BCLog::NET, "peer %d requested unsupported block filter type: %d\n", - peer.GetId(), static_cast<uint8_t>(filter_type)); - peer.fDisconnect = true; + node.GetId(), static_cast<uint8_t>(filter_type)); + node.fDisconnect = true; return false; } @@ -2479,8 +2977,8 @@ bool PeerManagerImpl::PrepareBlockFilterRequest(CNode& peer, // Check that the stop block exists and the peer would be allowed to fetch it. 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; + node.GetId(), stop_hash.ToString()); + node.fDisconnect = true; return false; } } @@ -2489,14 +2987,14 @@ bool PeerManagerImpl::PrepareBlockFilterRequest(CNode& peer, if (start_height > stop_height) { LogPrint(BCLog::NET, "peer %d sent invalid getcfilters/getcfheaders with " /* Continued */ "start height %d and stop height %d\n", - peer.GetId(), start_height, stop_height); - peer.fDisconnect = true; + node.GetId(), start_height, stop_height); + node.fDisconnect = true; return false; } if (stop_height - start_height >= max_height_diff) { LogPrint(BCLog::NET, "peer %d requested too many cfilters/cfheaders: %d / %d\n", - peer.GetId(), stop_height - start_height + 1, max_height_diff); - peer.fDisconnect = true; + node.GetId(), stop_height - start_height + 1, max_height_diff); + node.fDisconnect = true; return false; } @@ -2509,7 +3007,7 @@ bool PeerManagerImpl::PrepareBlockFilterRequest(CNode& peer, return true; } -void PeerManagerImpl::ProcessGetCFilters(CNode& peer, CDataStream& vRecv) +void PeerManagerImpl::ProcessGetCFilters(CNode& node,Peer& peer, CDataStream& vRecv) { uint8_t filter_type_ser; uint32_t start_height; @@ -2521,7 +3019,7 @@ void PeerManagerImpl::ProcessGetCFilters(CNode& peer, CDataStream& vRecv) const CBlockIndex* stop_index; BlockFilterIndex* filter_index; - if (!PrepareBlockFilterRequest(peer, filter_type, start_height, stop_hash, + if (!PrepareBlockFilterRequest(node, peer, filter_type, start_height, stop_hash, MAX_GETCFILTERS_SIZE, stop_index, filter_index)) { return; } @@ -2534,13 +3032,13 @@ void PeerManagerImpl::ProcessGetCFilters(CNode& peer, CDataStream& vRecv) } for (const auto& filter : filters) { - CSerializedNetMsg msg = CNetMsgMaker(peer.GetCommonVersion()) + CSerializedNetMsg msg = CNetMsgMaker(node.GetCommonVersion()) .Make(NetMsgType::CFILTER, filter); - m_connman.PushMessage(&peer, std::move(msg)); + m_connman.PushMessage(&node, std::move(msg)); } } -void PeerManagerImpl::ProcessGetCFHeaders(CNode& peer, CDataStream& vRecv) +void PeerManagerImpl::ProcessGetCFHeaders(CNode& node, Peer& peer, CDataStream& vRecv) { uint8_t filter_type_ser; uint32_t start_height; @@ -2552,7 +3050,7 @@ void PeerManagerImpl::ProcessGetCFHeaders(CNode& peer, CDataStream& vRecv) const CBlockIndex* stop_index; BlockFilterIndex* filter_index; - if (!PrepareBlockFilterRequest(peer, filter_type, start_height, stop_hash, + if (!PrepareBlockFilterRequest(node, peer, filter_type, start_height, stop_hash, MAX_GETCFHEADERS_SIZE, stop_index, filter_index)) { return; } @@ -2575,16 +3073,16 @@ void PeerManagerImpl::ProcessGetCFHeaders(CNode& peer, CDataStream& vRecv) return; } - CSerializedNetMsg msg = CNetMsgMaker(peer.GetCommonVersion()) + CSerializedNetMsg msg = CNetMsgMaker(node.GetCommonVersion()) .Make(NetMsgType::CFHEADERS, filter_type_ser, stop_index->GetBlockHash(), prev_header, filter_hashes); - m_connman.PushMessage(&peer, std::move(msg)); + m_connman.PushMessage(&node, std::move(msg)); } -void PeerManagerImpl::ProcessGetCFCheckPt(CNode& peer, CDataStream& vRecv) +void PeerManagerImpl::ProcessGetCFCheckPt(CNode& node, Peer& peer, CDataStream& vRecv) { uint8_t filter_type_ser; uint256 stop_hash; @@ -2595,7 +3093,7 @@ void PeerManagerImpl::ProcessGetCFCheckPt(CNode& peer, CDataStream& vRecv) const CBlockIndex* stop_index; BlockFilterIndex* filter_index; - if (!PrepareBlockFilterRequest(peer, filter_type, /*start_height=*/0, stop_hash, + if (!PrepareBlockFilterRequest(node, peer, filter_type, /*start_height=*/0, stop_hash, /*max_height_diff=*/std::numeric_limits<uint32_t>::max(), stop_index, filter_index)) { return; @@ -2616,18 +3114,18 @@ void PeerManagerImpl::ProcessGetCFCheckPt(CNode& peer, CDataStream& vRecv) } } - CSerializedNetMsg msg = CNetMsgMaker(peer.GetCommonVersion()) + CSerializedNetMsg msg = CNetMsgMaker(node.GetCommonVersion()) .Make(NetMsgType::CFCHECKPT, filter_type_ser, stop_index->GetBlockHash(), headers); - m_connman.PushMessage(&peer, std::move(msg)); + m_connman.PushMessage(&node, std::move(msg)); } -void PeerManagerImpl::ProcessBlock(CNode& node, const std::shared_ptr<const CBlock>& block, bool force_processing) +void PeerManagerImpl::ProcessBlock(CNode& node, const std::shared_ptr<const CBlock>& block, bool force_processing, bool min_pow_checked) { bool new_block{false}; - m_chainman.ProcessNewBlock(block, force_processing, &new_block); + m_chainman.ProcessNewBlock(block, force_processing, min_pow_checked, &new_block); if (new_block) { node.m_last_block_time = GetTime<std::chrono::seconds>(); } else { @@ -2640,6 +3138,8 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, const std::chrono::microseconds time_received, const std::atomic<bool>& interruptMsgProc) { + AssertLockHeld(g_msgproc_mutex); + LogPrint(BCLog::NET, "received: %s (%u bytes) peer=%d\n", SanitizeString(msg_type), vRecv.size(), pfrom.GetId()); PeerRef peer = GetPeerRef(pfrom.GetId()); @@ -2743,7 +3243,8 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, m_connman.PushMessage(&pfrom, msg_maker.Make(NetMsgType::VERACK)); - pfrom.nServices = nServices; + pfrom.m_has_all_wanted_services = HasAllDesirableServiceFlags(nServices); + peer->m_their_services = nServices; pfrom.SetAddrLocal(addrMe); { LOCK(pfrom.m_subver_mutex); @@ -2751,18 +3252,12 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, } peer->m_starting_height = starting_height; - // set nodes not relaying blocks and tx and not serving (parts) of the historical blockchain as "clients" - pfrom.fClient = (!(nServices & NODE_NETWORK) && !(nServices & NODE_NETWORK_LIMITED)); - - // set nodes not capable of serving the complete blockchain history as "limited nodes" - pfrom.m_limited_node = (!(nServices & NODE_NETWORK) && (nServices & NODE_NETWORK_LIMITED)); - // We only initialize the m_tx_relay data structure if: // - this isn't an outbound block-relay-only connection; and // - fRelay=true or we're offering NODE_BLOOM to this peer // (NODE_BLOOM means that the peer may turn on tx relay later) if (!pfrom.IsBlockOnlyConn() && - (fRelay || (pfrom.GetLocalServices() & NODE_BLOOM))) { + (fRelay || (peer->m_our_services & NODE_BLOOM))) { auto* const tx_relay = peer->SetTxRelay(); { LOCK(tx_relay->m_bloom_filter_mutex); @@ -2771,17 +3266,11 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, if (fRelay) pfrom.m_relays_txs = true; } - if((nServices & NODE_WITNESS)) - { - LOCK(cs_main); - State(pfrom.GetId())->fHaveWitness = true; - } - // Potentially mark this peer as a preferred download peer. { LOCK(cs_main); CNodeState* state = State(pfrom.GetId()); - state->fPreferredDownload = (!pfrom.IsInboundConn() || pfrom.HasPermission(NetPermissionFlags::NoBan)) && !pfrom.IsAddrFetchConn() && !pfrom.fClient; + state->fPreferredDownload = (!pfrom.IsInboundConn() || pfrom.HasPermission(NetPermissionFlags::NoBan)) && !pfrom.IsAddrFetchConn() && CanServeBlocks(*peer); m_num_preferred_download_peers += state->fPreferredDownload; } @@ -2800,7 +3289,7 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, // indicate to the peer that we will participate in addr relay. if (fListen && !m_chainman.ActiveChainstate().IsInitialBlockDownload()) { - CAddress addr = GetLocalAddress(&pfrom.addr, pfrom.GetLocalServices()); + CAddress addr{GetLocalAddress(pfrom.addr), peer->m_our_services, Now<NodeSeconds>()}; FastRandomContext insecure_rand; if (addr.IsRoutable()) { @@ -2896,13 +3385,6 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, pfrom.ConnectionTypeAsString()); } - if (pfrom.GetCommonVersion() >= SENDHEADERS_VERSION) { - // Tell our peer we prefer to receive headers rather than inv's - // We send this to non-NODE NETWORK peers as well, because even - // non-NODE NETWORK peers can announce blocks (such as pruning - // nodes) - m_connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::SENDHEADERS)); - } if (pfrom.GetCommonVersion() >= SHORT_IDS_BLOCKS_VERSION) { // Tell our peer we are willing to provide version 2 cmpctblocks. // However, we do not request new block announcements using @@ -2999,21 +3481,20 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, if (vAddr.size() > MAX_ADDR_TO_SEND) { - Misbehaving(pfrom.GetId(), 20, strprintf("%s message size = %u", msg_type, vAddr.size())); + Misbehaving(*peer, 20, strprintf("%s message size = %u", msg_type, vAddr.size())); return; } // Store the new addresses std::vector<CAddress> vAddrOk; - int64_t nNow = GetAdjustedTime(); - int64_t nSince = nNow - 10 * 60; + const auto current_a_time{Now<NodeSeconds>()}; // Update/increment addr rate limiting bucket. const auto current_time{GetTime<std::chrono::microseconds>()}; if (peer->m_addr_token_bucket < MAX_ADDR_PROCESSING_TOKEN_BUCKET) { // Don't increment bucket if it's already full const auto time_diff = std::max(current_time - peer->m_addr_token_timestamp, 0us); - const double increment = CountSecondsDouble(time_diff) * MAX_ADDR_RATE_PER_SECOND; + const double increment = Ticks<SecondsDouble>(time_diff) * MAX_ADDR_RATE_PER_SECOND; peer->m_addr_token_bucket = std::min<double>(peer->m_addr_token_bucket + increment, MAX_ADDR_PROCESSING_TOKEN_BUCKET); } peer->m_addr_token_timestamp = current_time; @@ -3042,8 +3523,9 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, if (!MayHaveUsefulAddressDB(addr.nServices) && !HasAllDesirableServiceFlags(addr.nServices)) continue; - if (addr.nTime <= 100000000 || addr.nTime > nNow + 10 * 60) - addr.nTime = nNow - 5 * 24 * 60 * 60; + if (addr.nTime <= NodeSeconds{100000000s} || addr.nTime > current_a_time + 10min) { + addr.nTime = current_a_time - 5 * 24h; + } AddAddressKnown(*peer, addr); if (m_banman && (m_banman->IsDiscouraged(addr) || m_banman->IsBanned(addr))) { // Do not process banned/discouraged addresses beyond remembering we received them @@ -3051,7 +3533,7 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, } ++num_proc; bool fReachable = IsReachable(addr); - if (addr.nTime > nSince && !peer->m_getaddr_sent && vAddr.size() <= 10 && addr.IsRoutable()) { + if (addr.nTime > current_a_time - 10min && !peer->m_getaddr_sent && vAddr.size() <= 10 && addr.IsRoutable()) { // Relay to a limited number of other nodes RelayAddress(pfrom.GetId(), addr, fReachable); } @@ -3064,7 +3546,7 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, LogPrint(BCLog::NET, "Received addr: %u addresses (%u processed, %u rate-limited) from peer=%d\n", vAddr.size(), num_proc, num_rate_limit, pfrom.GetId()); - m_addrman.Add(vAddrOk, pfrom.addr, 2 * 60 * 60); + m_addrman.Add(vAddrOk, pfrom.addr, 2h); if (vAddr.size() < 1000) peer->m_getaddr_sent = false; // AddrFetch: Require multiple addresses to avoid disconnecting on self-announcements @@ -3080,18 +3562,11 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, vRecv >> vInv; if (vInv.size() > MAX_INV_SZ) { - Misbehaving(pfrom.GetId(), 20, strprintf("inv message size = %u", vInv.size())); + Misbehaving(*peer, 20, strprintf("inv message size = %u", vInv.size())); return; } - // Reject tx INVs when the -blocksonly setting is enabled, or this is a - // block-relay-only peer - bool reject_tx_invs{m_ignore_incoming_txs || pfrom.IsBlockOnlyConn()}; - - // Allow peers with relay permission to send data other than blocks in blocks only mode - if (pfrom.HasPermission(NetPermissionFlags::Relay)) { - reject_tx_invs = false; - } + const bool reject_tx_invs{RejectIncomingTxs(pfrom)}; LOCK(cs_main); @@ -3117,8 +3592,9 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, UpdateBlockAvailability(pfrom.GetId(), inv.hash); if (!fAlreadyHave && !fImporting && !fReindex && !IsBlockRequested(inv.hash)) { // Headers-first is the primary method of announcement on - // the network. If a node fell back to sending blocks by inv, - // it's probably for a re-org. The final block hash + // the network. If a node fell back to sending blocks by + // inv, it may be for a re-org, or because we haven't + // completed initial headers sync. The final block hash // provided should be the highest, so send a getheaders and // then fetch the blocks we need to catch up. best_block = &inv.hash; @@ -3143,8 +3619,31 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, } if (best_block != nullptr) { - m_connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::GETHEADERS, m_chainman.ActiveChain().GetLocator(m_chainman.m_best_header), *best_block)); - LogPrint(BCLog::NET, "getheaders (%d) %s to peer=%d\n", m_chainman.m_best_header->nHeight, best_block->ToString(), pfrom.GetId()); + // If we haven't started initial headers-sync with this peer, then + // consider sending a getheaders now. On initial startup, there's a + // reliability vs bandwidth tradeoff, where we are only trying to do + // initial headers sync with one peer at a time, with a long + // timeout (at which point, if the sync hasn't completed, we will + // disconnect the peer and then choose another). In the meantime, + // as new blocks are found, we are willing to add one new peer per + // block to sync with as well, to sync quicker in the case where + // our initial peer is unresponsive (but less bandwidth than we'd + // use if we turned on sync with all peers). + CNodeState& state{*Assert(State(pfrom.GetId()))}; + if (state.fSyncStarted || (!peer->m_inv_triggered_getheaders_before_sync && *best_block != m_last_block_inv_triggering_headers_sync)) { + if (MaybeSendGetHeaders(pfrom, GetLocator(m_chainman.m_best_header), *peer)) { + LogPrint(BCLog::NET, "getheaders (%d) %s to peer=%d\n", + m_chainman.m_best_header->nHeight, best_block->ToString(), + pfrom.GetId()); + } + if (!state.fSyncStarted) { + peer->m_inv_triggered_getheaders_before_sync = true; + // Update the last block hash that triggered a new headers + // sync, so that we don't turn on headers sync with more + // than 1 new peer every new block. + m_last_block_inv_triggering_headers_sync = *best_block; + } + } } return; @@ -3155,7 +3654,7 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, vRecv >> vInv; if (vInv.size() > MAX_INV_SZ) { - Misbehaving(pfrom.GetId(), 20, strprintf("getdata message size = %u", vInv.size())); + Misbehaving(*peer, 20, strprintf("getdata message size = %u", vInv.size())); return; } @@ -3253,7 +3752,7 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, // Unlock m_most_recent_block_mutex to avoid cs_main lock inversion } if (recent_block) { - SendBlockTransactions(pfrom, *recent_block, req); + SendBlockTransactions(pfrom, *peer, *recent_block, req); return; } @@ -3271,7 +3770,7 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, bool ret = ReadBlockFromDisk(block, pindex, m_chainparams.GetConsensus()); assert(ret); - SendBlockTransactions(pfrom, block, req); + SendBlockTransactions(pfrom, *peer, block, req); return; } } @@ -3317,7 +3816,10 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, // others. if (m_chainman.ActiveTip() == nullptr || (m_chainman.ActiveTip()->nChainWork < nMinimumChainWork && !pfrom.HasPermission(NetPermissionFlags::Download))) { - LogPrint(BCLog::NET, "Ignoring getheaders from peer=%d because active chain has too little work\n", pfrom.GetId()); + LogPrint(BCLog::NET, "Ignoring getheaders from peer=%d because active chain has too little work; sending empty response\n", pfrom.GetId()); + // Just respond with an empty headers message, to tell the peer to + // go away but not treat us as unresponsive. + m_connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::HEADERS, std::vector<CBlock>())); return; } @@ -3372,10 +3874,7 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, } if (msg_type == NetMsgType::TX) { - // Stop processing the transaction early if - // 1) We are in blocks only mode and peer has no relay permission; OR - // 2) This peer is a block-relay-only peer - if ((m_ignore_incoming_txs && !pfrom.HasPermission(NetPermissionFlags::Relay)) || pfrom.IsBlockOnlyConn()) { + if (RejectIncomingTxs(pfrom)) { LogPrint(BCLog::NET, "transaction sent in violation of protocol peer=%d\n", pfrom.GetId()); pfrom.fDisconnect = true; return; @@ -3505,10 +4004,7 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, // 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.GetIntArg("-maxorphantx", DEFAULT_MAX_ORPHAN_TRANSACTIONS)); - unsigned int nEvicted = m_orphanage.LimitOrphans(nMaxOrphanTx); - if (nEvicted > 0) { - LogPrint(BCLog::MEMPOOL, "orphanage overflow, removed %u tx\n", nEvicted); - } + m_orphanage.LimitOrphans(nMaxOrphanTx); } else { LogPrint(BCLog::MEMPOOL, "not keeping orphan with rejected parents %s\n",tx.GetHash().ToString()); // We will continue to reject this tx since it has rejected @@ -3599,10 +4095,16 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, { LOCK(cs_main); - if (!m_chainman.m_blockman.LookupBlockIndex(cmpctblock.header.hashPrevBlock)) { + const CBlockIndex* prev_block = m_chainman.m_blockman.LookupBlockIndex(cmpctblock.header.hashPrevBlock); + if (!prev_block) { // Doesn't connect (or is genesis), instead of DoSing in AcceptBlockHeader, request deeper headers - if (!m_chainman.ActiveChainstate().IsInitialBlockDownload()) - m_connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::GETHEADERS, m_chainman.ActiveChain().GetLocator(m_chainman.m_best_header), uint256())); + if (!m_chainman.ActiveChainstate().IsInitialBlockDownload()) { + MaybeSendGetHeaders(pfrom, GetLocator(m_chainman.m_best_header), *peer); + } + return; + } else if (prev_block->nChainWork + CalculateHeadersWork({cmpctblock.header}) < GetAntiDoSWorkThreshold()) { + // If we get a low-work header in a compact block, we can ignore it. + LogPrint(BCLog::NET, "Ignoring low-work compact block from peer %d\n", pfrom.GetId()); return; } @@ -3613,7 +4115,7 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, const CBlockIndex *pindex = nullptr; BlockValidationState state; - if (!m_chainman.ProcessNewBlockHeaders({cmpctblock.header}, state, &pindex)) { + if (!m_chainman.ProcessNewBlockHeaders({cmpctblock.header}, /*min_pow_checked=*/true, state, &pindex)) { if (state.IsInvalid()) { MaybePunishNodeForBlock(pfrom.GetId(), state, /*via_compact_block=*/true, "invalid header via cmpctblock"); return; @@ -3662,7 +4164,7 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, // We requested this block for some reason, but our mempool will probably be useless // so we just grab the block via normal getdata std::vector<CInv> vInv(1); - vInv[0] = CInv(MSG_BLOCK | GetFetchFlags(pfrom), cmpctblock.header.GetHash()); + vInv[0] = CInv(MSG_BLOCK | GetFetchFlags(*peer), cmpctblock.header.GetHash()); m_connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::GETDATA, vInv)); } return; @@ -3693,12 +4195,12 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, ReadStatus status = partialBlock.InitData(cmpctblock, vExtraTxnForCompact); if (status == READ_STATUS_INVALID) { RemoveBlockRequest(pindex->GetBlockHash()); // Reset in-flight state in case Misbehaving does not result in a disconnect - Misbehaving(pfrom.GetId(), 100, "invalid compact block"); + Misbehaving(*peer, 100, "invalid compact block"); return; } else if (status == READ_STATUS_FAILED) { // Duplicate txindexes, the block is now in-flight, so just request it std::vector<CInv> vInv(1); - vInv[0] = CInv(MSG_BLOCK | GetFetchFlags(pfrom), cmpctblock.header.GetHash()); + vInv[0] = CInv(MSG_BLOCK | GetFetchFlags(*peer), cmpctblock.header.GetHash()); m_connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::GETDATA, vInv)); return; } @@ -3741,7 +4243,7 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, // We requested this block, but its far into the future, so our // mempool will probably be useless - request the block normally std::vector<CInv> vInv(1); - vInv[0] = CInv(MSG_BLOCK | GetFetchFlags(pfrom), cmpctblock.header.GetHash()); + vInv[0] = CInv(MSG_BLOCK | GetFetchFlags(*peer), cmpctblock.header.GetHash()); m_connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::GETDATA, vInv)); return; } else { @@ -3780,7 +4282,7 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, // we have a chain with at least nMinimumChainWork), and we ignore // compact blocks with less work than our tip, it is safe to treat // reconstructed compact blocks as having been requested. - ProcessBlock(pfrom, pblock, /*force_processing=*/true); + ProcessBlock(pfrom, pblock, /*force_processing=*/true, /*min_pow_checked=*/true); LOCK(cs_main); // hold cs_main for CBlockIndex::IsValid() if (pindex->IsValid(BLOCK_VALID_TRANSACTIONS)) { // Clear download state for this block, which is in @@ -3820,12 +4322,12 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, ReadStatus status = partialBlock.FillBlock(*pblock, resp.txn); if (status == READ_STATUS_INVALID) { RemoveBlockRequest(resp.blockhash); // Reset in-flight state in case Misbehaving does not result in a disconnect - Misbehaving(pfrom.GetId(), 100, "invalid compact block/non-matching block transactions"); + Misbehaving(*peer, 100, "invalid compact block/non-matching block transactions"); return; } else if (status == READ_STATUS_FAILED) { // Might have collided, fall back to getdata now :( std::vector<CInv> invs; - invs.push_back(CInv(MSG_BLOCK | GetFetchFlags(pfrom), resp.blockhash)); + invs.push_back(CInv(MSG_BLOCK | GetFetchFlags(*peer), resp.blockhash)); m_connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::GETDATA, invs)); } else { // Block is either okay, or possibly we received @@ -3863,7 +4365,7 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, // disk-space attacks), but this should be safe due to the // protections in the compact block handler -- see related comment // in compact block optimistic reconstruction handling. - ProcessBlock(pfrom, pblock, /*force_processing=*/true); + ProcessBlock(pfrom, pblock, /*force_processing=*/true, /*min_pow_checked=*/true); } return; } @@ -3876,12 +4378,16 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, return; } + // Assume that this is in response to any outstanding getheaders + // request we may have sent, and clear out the time of our last request + peer->m_last_getheaders_timestamp = {}; + std::vector<CBlockHeader> headers; // Bypass the normal CBlock deserialization, as we don't want to risk deserializing 2000 full blocks. unsigned int nCount = ReadCompactSize(vRecv); if (nCount > MAX_HEADERS_RESULTS) { - Misbehaving(pfrom.GetId(), 20, strprintf("headers message size = %u", nCount)); + Misbehaving(*peer, 20, strprintf("headers message size = %u", nCount)); return; } headers.resize(nCount); @@ -3890,7 +4396,23 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, ReadCompactSize(vRecv); // ignore tx count; assume it is 0. } - return ProcessHeadersMessage(pfrom, *peer, headers, /*via_compact_block=*/false); + ProcessHeadersMessage(pfrom, *peer, std::move(headers), /*via_compact_block=*/false); + + // Check if the headers presync progress needs to be reported to validation. + // This needs to be done without holding the m_headers_presync_mutex lock. + if (m_headers_presync_should_signal.exchange(false)) { + HeadersPresyncStats stats; + { + LOCK(m_headers_presync_mutex); + auto it = m_headers_presync_stats.find(m_headers_presync_bestpeer); + if (it != m_headers_presync_stats.end()) stats = it->second; + } + if (stats.second) { + m_chainman.ReportHeadersPresync(stats.first, stats.second->first, stats.second->second); + } + } + + return; } if (msg_type == NetMsgType::BLOCK) @@ -3908,6 +4430,7 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, bool forceProcessing = false; const uint256 hash(pblock->GetHash()); + bool min_pow_checked = false; { LOCK(cs_main); // Always process the block if we requested it, since we may @@ -3918,8 +4441,14 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, // which peers send us compact blocks, so the race between here and // cs_main in ProcessNewBlock is fine. mapBlockSource.emplace(hash, std::make_pair(pfrom.GetId(), true)); + + // Check work on this block against our anti-dos thresholds. + const CBlockIndex* prev_block = m_chainman.m_blockman.LookupBlockIndex(pblock->hashPrevBlock); + if (prev_block && prev_block->nChainWork + CalculateHeadersWork({pblock->GetBlockHeader()}) >= GetAntiDoSWorkThreshold()) { + min_pow_checked = true; + } } - ProcessBlock(pfrom, pblock, forceProcessing); + ProcessBlock(pfrom, pblock, forceProcessing, min_pow_checked); return; } @@ -3961,7 +4490,7 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, } if (msg_type == NetMsgType::MEMPOOL) { - if (!(pfrom.GetLocalServices() & NODE_BLOOM) && !pfrom.HasPermission(NetPermissionFlags::Mempool)) + if (!(peer->m_our_services & NODE_BLOOM) && !pfrom.HasPermission(NetPermissionFlags::Mempool)) { if (!pfrom.HasPermission(NetPermissionFlags::NoBan)) { @@ -4064,7 +4593,7 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, } if (msg_type == NetMsgType::FILTERLOAD) { - if (!(pfrom.GetLocalServices() & NODE_BLOOM)) { + if (!(peer->m_our_services & NODE_BLOOM)) { LogPrint(BCLog::NET, "filterload received despite not offering bloom services from peer=%d; disconnecting\n", pfrom.GetId()); pfrom.fDisconnect = true; return; @@ -4075,7 +4604,7 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, if (!filter.IsWithinSizeConstraints()) { // There is no excuse for sending a too-large filter - Misbehaving(pfrom.GetId(), 100, "too-large bloom filter"); + Misbehaving(*peer, 100, "too-large bloom filter"); } else if (auto tx_relay = peer->GetTxRelay(); tx_relay != nullptr) { { LOCK(tx_relay->m_bloom_filter_mutex); @@ -4083,12 +4612,13 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, tx_relay->m_relay_txs = true; } pfrom.m_bloom_filter_loaded = true; + pfrom.m_relays_txs = true; } return; } if (msg_type == NetMsgType::FILTERADD) { - if (!(pfrom.GetLocalServices() & NODE_BLOOM)) { + if (!(peer->m_our_services & NODE_BLOOM)) { LogPrint(BCLog::NET, "filteradd received despite not offering bloom services from peer=%d; disconnecting\n", pfrom.GetId()); pfrom.fDisconnect = true; return; @@ -4110,13 +4640,13 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, } } if (bad) { - Misbehaving(pfrom.GetId(), 100, "bad filteradd message"); + Misbehaving(*peer, 100, "bad filteradd message"); } return; } if (msg_type == NetMsgType::FILTERCLEAR) { - if (!(pfrom.GetLocalServices() & NODE_BLOOM)) { + if (!(peer->m_our_services & NODE_BLOOM)) { LogPrint(BCLog::NET, "filterclear received despite not offering bloom services from peer=%d; disconnecting\n", pfrom.GetId()); pfrom.fDisconnect = true; return; @@ -4147,17 +4677,17 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, } if (msg_type == NetMsgType::GETCFILTERS) { - ProcessGetCFilters(pfrom, vRecv); + ProcessGetCFilters(pfrom, *peer, vRecv); return; } if (msg_type == NetMsgType::GETCFHEADERS) { - ProcessGetCFHeaders(pfrom, vRecv); + ProcessGetCFHeaders(pfrom, *peer, vRecv); return; } if (msg_type == NetMsgType::GETCFCHECKPT) { - ProcessGetCFCheckPt(pfrom, vRecv); + ProcessGetCFCheckPt(pfrom, *peer, vRecv); return; } @@ -4223,6 +4753,8 @@ bool PeerManagerImpl::MaybeDiscourageAndDisconnect(CNode& pnode, Peer& peer) bool PeerManagerImpl::ProcessMessages(CNode* pfrom, std::atomic<bool>& interruptMsgProc) { + AssertLockHeld(g_msgproc_mutex); + bool fMoreWork = false; PeerRef peer = GetPeerRef(pfrom->GetId()); @@ -4303,7 +4835,7 @@ bool PeerManagerImpl::ProcessMessages(CNode* pfrom, std::atomic<bool>& interrupt return fMoreWork; } -void PeerManagerImpl::ConsiderEviction(CNode& pto, std::chrono::seconds time_in_seconds) +void PeerManagerImpl::ConsiderEviction(CNode& pto, Peer& peer, std::chrono::seconds time_in_seconds) { AssertLockHeld(cs_main); @@ -4341,10 +4873,15 @@ void PeerManagerImpl::ConsiderEviction(CNode& pto, std::chrono::seconds time_in_ pto.fDisconnect = true; } else { assert(state.m_chain_sync.m_work_header); + // Here, we assume that the getheaders message goes out, + // because it'll either go out or be skipped because of a + // getheaders in-flight already, in which case the peer should + // still respond to us with a sufficiently high work chain tip. + MaybeSendGetHeaders(pto, + GetLocator(state.m_chain_sync.m_work_header->pprev), + peer); 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, m_chainman.ActiveChain().GetLocator(state.m_chain_sync.m_work_header->pprev), uint256())); state.m_chain_sync.m_sent_getheaders = true; - constexpr auto HEADERS_RESPONSE_TIME{2min}; // Bump the timeout to allow a response, which could clear the timeout // (if the response shows the peer has synced), reset the timeout (if // the peer syncs to the required work but not to our tip), or result @@ -4548,9 +5085,10 @@ void PeerManagerImpl::MaybeSendAddr(CNode& node, Peer& peer, std::chrono::micros if (peer.m_next_local_addr_send != 0us) { peer.m_addr_known->reset(); } - if (std::optional<CAddress> local_addr = GetLocalAddrForPeer(&node)) { + if (std::optional<CService> local_service = GetLocalAddrForPeer(node)) { + CAddress local_addr{*local_service, peer.m_our_services, Now<NodeSeconds>()}; FastRandomContext insecure_rand; - PushAddress(peer, *local_addr, insecure_rand); + PushAddress(peer, local_addr, insecure_rand); } peer.m_next_local_addr_send = GetExponentialRand(current_time, AVG_LOCAL_ADDRESS_BROADCAST_INTERVAL); } @@ -4568,7 +5106,7 @@ void PeerManagerImpl::MaybeSendAddr(CNode& node, Peer& peer, std::chrono::micros // 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 = [&peer](const CAddress& addr) { + auto addr_already_known = [&peer](const CAddress& addr) EXCLUSIVE_LOCKS_REQUIRED(g_msgproc_mutex) { bool ret = peer.m_addr_known->contains(addr.GetKey()); if (!ret) peer.m_addr_known->insert(addr.GetKey()); return ret; @@ -4597,6 +5135,27 @@ void PeerManagerImpl::MaybeSendAddr(CNode& node, Peer& peer, std::chrono::micros } } +void PeerManagerImpl::MaybeSendSendHeaders(CNode& node, Peer& peer) +{ + // Delay sending SENDHEADERS (BIP 130) until we're done with an + // initial-headers-sync with this peer. Receiving headers announcements for + // new blocks while trying to sync their headers chain is problematic, + // because of the state tracking done. + if (!peer.m_sent_sendheaders && node.GetCommonVersion() >= SENDHEADERS_VERSION) { + LOCK(cs_main); + CNodeState &state = *State(node.GetId()); + if (state.pindexBestKnownBlock != nullptr && + state.pindexBestKnownBlock->nChainWork > nMinimumChainWork) { + // Tell our peer we prefer to receive headers rather than inv's + // We send this to non-NODE NETWORK peers as well, because even + // non-NODE NETWORK peers can announce blocks (such as pruning + // nodes) + m_connman.PushMessage(&node, CNetMsgMaker(node.GetCommonVersion()).Make(NetMsgType::SENDHEADERS)); + peer.m_sent_sendheaders = true; + } + } +} + void PeerManagerImpl::MaybeSendFeefilter(CNode& pto, Peer& peer, std::chrono::microseconds current_time) { if (m_ignore_incoming_txs) return; @@ -4607,7 +5166,7 @@ void PeerManagerImpl::MaybeSendFeefilter(CNode& pto, Peer& peer, std::chrono::mi // transactions to us, regardless of feefilter state. if (pto.IsBlockOnlyConn()) return; - CAmount currentFilter = m_mempool.GetMinFee(gArgs.GetIntArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000).GetFeePerK(); + CAmount currentFilter = m_mempool.GetMinFee().GetFeePerK(); static FeeFilterRounder g_filter_rounder{CFeeRate{DEFAULT_MIN_RELAY_TX_FEE}}; if (m_chainman.ActiveChainstate().IsInitialBlockDownload()) { @@ -4624,8 +5183,8 @@ void PeerManagerImpl::MaybeSendFeefilter(CNode& pto, Peer& peer, std::chrono::mi } if (current_time > peer.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()); + // We always have a fee filter of at least the min relay fee + filterToSend = std::max(filterToSend, m_mempool.m_min_relay_feerate.GetFeePerK()); if (filterToSend != peer.m_fee_filter_sent) { m_connman.PushMessage(&pto, CNetMsgMaker(pto.GetCommonVersion()).Make(NetMsgType::FEEFILTER, filterToSend)); peer.m_fee_filter_sent = filterToSend; @@ -4643,7 +5202,7 @@ void PeerManagerImpl::MaybeSendFeefilter(CNode& pto, Peer& peer, std::chrono::mi namespace { class CompareInvMempoolOrder { - CTxMemPool *mp; + CTxMemPool* mp; bool m_wtxid_relay; public: explicit CompareInvMempoolOrder(CTxMemPool *_mempool, bool use_wtxid) @@ -4659,6 +5218,15 @@ public: return mp->CompareDepthAndScore(*b, *a, m_wtxid_relay); } }; +} // namespace + +bool PeerManagerImpl::RejectIncomingTxs(const CNode& peer) const +{ + // block-relay-only peers may never send txs to us + if (peer.IsBlockOnlyConn()) return true; + // In -blocksonly mode, peers need the 'relay' permission to send txs to us + if (m_ignore_incoming_txs && !peer.HasPermission(NetPermissionFlags::Relay)) return true; + return false; } bool PeerManagerImpl::SetupAddressRelay(const CNode& node, Peer& peer) @@ -4679,6 +5247,8 @@ bool PeerManagerImpl::SetupAddressRelay(const CNode& node, Peer& peer) bool PeerManagerImpl::SendMessages(CNode* pto) { + AssertLockHeld(g_msgproc_mutex); + PeerRef peer = GetPeerRef(pto->GetId()); if (!peer) return false; const Consensus::Params& consensusParams = m_chainparams.GetConsensus(); @@ -4709,6 +5279,8 @@ bool PeerManagerImpl::SendMessages(CNode* pto) MaybeSendAddr(*pto, *peer, current_time); + MaybeSendSendHeaders(*pto, *peer); + { LOCK(cs_main); @@ -4725,7 +5297,7 @@ bool PeerManagerImpl::SendMessages(CNode* pto) bool sync_blocks_and_headers_from_peer = false; if (state.fPreferredDownload) { sync_blocks_and_headers_from_peer = true; - } else if (!pto->fClient && !pto->IsAddrFetchConn()) { + } else if (CanServeBlocks(*peer) && !pto->IsAddrFetchConn()) { // Typically this is an inbound peer. If we don't have any outbound // peers, or if we aren't downloading any blocks from such peers, // then allow block downloads from this peer, too. @@ -4740,18 +5312,9 @@ bool PeerManagerImpl::SendMessages(CNode* pto) } } - if (!state.fSyncStarted && !pto->fClient && !fImporting && !fReindex) { + if (!state.fSyncStarted && CanServeBlocks(*peer) && !fImporting && !fReindex) { // Only actively request headers from a single peer, unless we're close to today. - if ((nSyncStarted == 0 && sync_blocks_and_headers_from_peer) || m_chainman.m_best_header->GetBlockTime() > GetAdjustedTime() - 24 * 60 * 60) { - state.fSyncStarted = true; - 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() - m_chainman.m_best_header->GetBlockTime()) / consensusParams.nPowTargetSpacing - ); - nSyncStarted++; + if ((nSyncStarted == 0 && sync_blocks_and_headers_from_peer) || m_chainman.m_best_header->Time() > GetAdjustedTime() - 24h) { const CBlockIndex* pindexStart = m_chainman.m_best_header; /* If possible, start at the block preceding the currently best known header. This ensures that we always get a @@ -4762,8 +5325,19 @@ bool PeerManagerImpl::SendMessages(CNode* pto) got back an empty response. */ 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, m_chainman.ActiveChain().GetLocator(pindexStart), uint256())); + if (MaybeSendGetHeaders(*pto, GetLocator(pindexStart), *peer)) { + LogPrint(BCLog::NET, "initial getheaders (%d) to peer=%d (startheight:%d)\n", pindexStart->nHeight, pto->GetId(), peer->m_starting_height); + + state.fSyncStarted = true; + 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} * + Ticks<std::chrono::seconds>(GetAdjustedTime() - m_chainman.m_best_header->Time()) / consensusParams.nPowTargetSpacing + ); + nSyncStarted++; + } } } @@ -4771,7 +5345,7 @@ bool PeerManagerImpl::SendMessages(CNode* pto) // Try sending block announcements via headers // { - // If we have less than MAX_BLOCKS_TO_ANNOUNCE in our + // If we have no more than MAX_BLOCKS_TO_ANNOUNCE in our // list of block hashes we're relaying, and our peer wants // headers announcements, then find the first header // not yet known to our peer but would connect, and send. @@ -5077,7 +5651,7 @@ bool PeerManagerImpl::SendMessages(CNode* pto) // Check for headers sync timeouts if (state.fSyncStarted && state.m_headers_sync_timeout < std::chrono::microseconds::max()) { // Detect whether this is a stalling initial-headers-sync peer - if (m_chainman.m_best_header->GetBlockTime() <= GetAdjustedTime() - 24 * 60 * 60) { + if (m_chainman.m_best_header->Time() <= GetAdjustedTime() - 24h) { if (current_time > state.m_headers_sync_timeout && nSyncStarted == 1 && (m_num_preferred_download_peers - state.fPreferredDownload >= 1)) { // Disconnect a peer (without NetPermissionFlags::NoBan permission) if it is our only sync peer, // and we have others we could be using instead. @@ -5109,18 +5683,18 @@ bool PeerManagerImpl::SendMessages(CNode* pto) // Check that outbound peers have reasonable chains // GetTime() is used by this anti-DoS logic so we can test this using mocktime - ConsiderEviction(*pto, GetTime<std::chrono::seconds>()); + ConsiderEviction(*pto, *peer, GetTime<std::chrono::seconds>()); // // Message: getdata (blocks) // std::vector<CInv> vGetData; - if (!pto->fClient && ((sync_blocks_and_headers_from_peer && !pto->m_limited_node) || !m_chainman.ActiveChainstate().IsInitialBlockDownload()) && state.nBlocksInFlight < MAX_BLOCKS_IN_TRANSIT_PER_PEER) { + if (CanServeBlocks(*peer) && ((sync_blocks_and_headers_from_peer && !IsLimitedPeer(*peer)) || !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); + FindNextBlocksToDownload(*peer, MAX_BLOCKS_IN_TRANSIT_PER_PEER - state.nBlocksInFlight, vToDownload, staller); for (const CBlockIndex *pindex : vToDownload) { - uint32_t nFetchFlags = GetFetchFlags(*pto); + uint32_t nFetchFlags = GetFetchFlags(*peer); vGetData.push_back(CInv(MSG_BLOCK | nFetchFlags, pindex->GetBlockHash())); BlockRequested(pto->GetId(), *pindex); LogPrint(BCLog::NET, "Requesting block %s (%d) peer=%d\n", pindex->GetBlockHash().ToString(), @@ -5147,7 +5721,7 @@ bool PeerManagerImpl::SendMessages(CNode* pto) if (!AlreadyHaveTx(gtxid)) { LogPrint(BCLog::NET, "Requesting %s %s peer=%d\n", gtxid.IsWtxid() ? "wtx" : "tx", gtxid.GetHash().ToString(), pto->GetId()); - vGetData.emplace_back(gtxid.IsWtxid() ? MSG_WTX : (MSG_TX | GetFetchFlags(*pto)), gtxid.GetHash()); + vGetData.emplace_back(gtxid.IsWtxid() ? MSG_WTX : (MSG_TX | GetFetchFlags(*peer)), gtxid.GetHash()); if (vGetData.size() >= MAX_GETDATA_SZ) { m_connman.PushMessage(pto, msgMaker.Make(NetMsgType::GETDATA, vGetData)); vGetData.clear(); diff --git a/src/net_processing.h b/src/net_processing.h index d5c73e6c79..0d0842fa8e 100644 --- a/src/net_processing.h +++ b/src/net_processing.h @@ -34,6 +34,8 @@ struct CNodeStateStats { uint64_t m_addr_processed = 0; uint64_t m_addr_rate_limited = 0; bool m_addr_relay_enabled{false}; + ServiceFlags their_services; + int64_t presync_height{-1}; }; class PeerManager : public CValidationInterface, public NetEventsInterface @@ -71,12 +73,8 @@ public: /** Set the best height */ virtual void SetBestHeight(int height) = 0; - /** - * Increment peer's misbehavior score. If the new value >= DISCOURAGEMENT_THRESHOLD, mark the node - * to be discouraged, meaning the peer might be disconnected and added to the discouragement filter. - * Public for unit testing. - */ - virtual void Misbehaving(const NodeId pnode, const int howmuch, const std::string& message) = 0; + /* Public for unit testing. */ + virtual void UnitTestMisbehaving(NodeId peer_id, int howmuch) = 0; /** * Evict extra outbound peers. If we think our tip may be stale, connect to an extra outbound. @@ -86,7 +84,7 @@ public: /** Process a single message from a peer. Public for fuzz testing */ virtual void ProcessMessage(CNode& pfrom, const std::string& msg_type, CDataStream& vRecv, - const std::chrono::microseconds time_received, const std::atomic<bool>& interruptMsgProc) = 0; + const std::chrono::microseconds time_received, const std::atomic<bool>& interruptMsgProc) EXCLUSIVE_LOCKS_REQUIRED(g_msgproc_mutex) = 0; /** This function is used for testing the stale tip eviction logic, see denialofservice_tests.cpp */ virtual void UpdateLastBlockAnnounceTime(NodeId node, int64_t time_in_seconds) = 0; diff --git a/src/netaddress.h b/src/netaddress.h index 47ba045334..e52beb783d 100644 --- a/src/netaddress.h +++ b/src/netaddress.h @@ -9,7 +9,7 @@ #include <config/bitcoin-config.h> #endif -#include <compat.h> +#include <compat/compat.h> #include <crypto/siphash.h> #include <prevector.h> #include <random.h> diff --git a/src/netbase.cpp b/src/netbase.cpp index 8ff3b7a68c..a5f7bda875 100644 --- a/src/netbase.cpp +++ b/src/netbase.cpp @@ -5,7 +5,7 @@ #include <netbase.h> -#include <compat.h> +#include <compat/compat.h> #include <sync.h> #include <tinyformat.h> #include <util/sock.h> @@ -30,7 +30,7 @@ #endif // Settings -static Mutex g_proxyinfo_mutex; +static GlobalMutex g_proxyinfo_mutex; static Proxy proxyInfo[NET_MAX] GUARDED_BY(g_proxyinfo_mutex); static Proxy nameProxy GUARDED_BY(g_proxyinfo_mutex); int nConnectTimeout = DEFAULT_CONNECT_TIMEOUT; @@ -387,7 +387,7 @@ bool Socks5(const std::string& strDest, uint16_t port, const ProxyCredentials* a return error("Error sending to proxy"); } uint8_t pchRet1[2]; - if ((recvr = InterruptibleRecv(pchRet1, 2, g_socks5_recv_timeout, sock)) != IntrRecvError::OK) { + if (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; } @@ -410,7 +410,7 @@ bool Socks5(const std::string& strDest, uint16_t port, const ProxyCredentials* a } LogPrint(BCLog::PROXY, "SOCKS5 sending proxy authentication %s:%s\n", auth->username, auth->password); uint8_t pchRetA[2]; - if ((recvr = InterruptibleRecv(pchRetA, 2, g_socks5_recv_timeout, sock)) != IntrRecvError::OK) { + if (InterruptibleRecv(pchRetA, 2, g_socks5_recv_timeout, sock) != IntrRecvError::OK) { return error("Error reading proxy authentication response"); } if (pchRetA[0] != 0x01 || pchRetA[1] != 0x00) { @@ -476,7 +476,7 @@ bool Socks5(const std::string& strDest, uint16_t port, const ProxyCredentials* a if (recvr != IntrRecvError::OK) { return error("Error reading from proxy"); } - if ((recvr = InterruptibleRecv(pchRet3, 2, g_socks5_recv_timeout, sock)) != IntrRecvError::OK) { + if (InterruptibleRecv(pchRet3, 2, g_socks5_recv_timeout, sock) != IntrRecvError::OK) { return error("Error reading from proxy"); } LogPrint(BCLog::NET, "SOCKS5 connected %s\n", strDest); diff --git a/src/netbase.h b/src/netbase.h index bf7522210d..fadc8b418e 100644 --- a/src/netbase.h +++ b/src/netbase.h @@ -9,7 +9,7 @@ #include <config/bitcoin-config.h> #endif -#include <compat.h> +#include <compat/compat.h> #include <netaddress.h> #include <serialize.h> #include <util/sock.h> diff --git a/src/node/blockstorage.cpp b/src/node/blockstorage.cpp index cadafcaa8d..57f81e6bb6 100644 --- a/src/node/blockstorage.cpp +++ b/src/node/blockstorage.cpp @@ -21,6 +21,7 @@ #include <util/system.h> #include <validation.h> +#include <map> #include <unordered_map> namespace node { @@ -414,7 +415,7 @@ void CleanupBlockRevFiles() // Remove the rev files immediately and insert the blk file paths into an // ordered map keyed by block file index. LogPrintf("Removing unusable blk?????.dat and rev?????.dat files for -reindex with -prune\n"); - fs::path blocksdir = gArgs.GetBlocksDirPath(); + const fs::path& blocksdir = gArgs.GetBlocksDirPath(); for (fs::directory_iterator it(blocksdir); it != fs::directory_iterator(); it++) { const std::string path = fs::PathToString(it->path().filename()); if (fs::is_regular_file(*it) && @@ -471,7 +472,7 @@ static bool UndoWriteToDisk(const CBlockUndo& blockundo, FlatFilePos& pos, const fileout << blockundo; // calculate & write checksum - CHashWriter hasher(SER_GETHASH, PROTOCOL_VERSION); + HashWriter hasher{}; hasher << hashBlock; hasher << blockundo; fileout << hasher.GetHash(); @@ -823,7 +824,7 @@ struct CImportingNow { } }; -void ThreadImport(ChainstateManager& chainman, std::vector<fs::path> vImportFiles, const ArgsManager& args) +void ThreadImport(ChainstateManager& chainman, std::vector<fs::path> vImportFiles, const ArgsManager& args, const fs::path& mempool_path) { SetSyscallSandboxPolicy(SyscallSandboxPolicy::INITIALIZATION_LOAD_BLOCKS); ScheduleBatchPriority(); @@ -834,6 +835,9 @@ void ThreadImport(ChainstateManager& chainman, std::vector<fs::path> vImportFile // -reindex if (fReindex) { int nFile = 0; + // Map of disk positions for blocks with unknown parent (only used for reindex); + // parent hash -> child disk position, multiple children can have the same parent. + std::multimap<uint256, FlatFilePos> blocks_with_unknown_parent; while (true) { FlatFilePos pos(nFile, 0); if (!fs::exists(GetBlockPosFilename(pos))) { @@ -844,7 +848,7 @@ void ThreadImport(ChainstateManager& chainman, std::vector<fs::path> vImportFile break; // This error is logged in OpenBlockFile } LogPrintf("Reindexing block file blk%05u.dat...\n", (unsigned int)nFile); - chainman.ActiveChainstate().LoadExternalBlockFile(file, &pos); + chainman.ActiveChainstate().LoadExternalBlockFile(file, &pos, &blocks_with_unknown_parent); if (ShutdownRequested()) { LogPrintf("Shutdown requested. Exit %s\n", __func__); return; @@ -878,7 +882,7 @@ void ThreadImport(ChainstateManager& chainman, std::vector<fs::path> vImportFile // We can't hold cs_main during ActivateBestChain even though we're accessing // the chainman unique_ptrs since ABC requires us not to be holding cs_main, so retrieve // the relevant pointers before the ABC call. - for (CChainState* chainstate : WITH_LOCK(::cs_main, return chainman.GetAll())) { + for (Chainstate* chainstate : WITH_LOCK(::cs_main, return chainman.GetAll())) { BlockValidationState state; if (!chainstate->ActivateBestChain(state, nullptr)) { LogPrintf("Failed to connect best block (%s)\n", state.ToString()); @@ -893,6 +897,6 @@ void ThreadImport(ChainstateManager& chainman, std::vector<fs::path> vImportFile return; } } // End scope of CImportingNow - chainman.ActiveChainstate().LoadMempool(args); + chainman.ActiveChainstate().LoadMempool(mempool_path); } } // namespace node diff --git a/src/node/blockstorage.h b/src/node/blockstorage.h index 2e52716649..37d74ed102 100644 --- a/src/node/blockstorage.h +++ b/src/node/blockstorage.h @@ -26,7 +26,7 @@ class CBlockFileInfo; class CBlockUndo; class CChain; class CChainParams; -class CChainState; +class Chainstate; class ChainstateManager; struct CCheckpointData; struct FlatFilePos; @@ -49,7 +49,7 @@ extern std::atomic_bool fReindex; /** Pruning-related variables and constants */ /** True if we're running in -prune mode. */ extern bool fPruneMode; -/** Number of MiB of block files that we're trying to stay below. */ +/** Number of bytes of block files that we're trying to stay below. */ extern uint64_t nPruneTarget; // Because validation code takes pointers to the map's CBlockIndex objects, if @@ -75,12 +75,12 @@ struct PruneLockInfo { * Maintains a tree of blocks (stored in `m_block_index`) which is consulted * to determine where the most-work tip is. * - * This data is used mostly in `CChainState` - information about, e.g., + * This data is used mostly in `Chainstate` - information about, e.g., * candidate tips is not maintained here. */ class BlockManager { - friend CChainState; + friend Chainstate; friend ChainstateManager; private: @@ -211,7 +211,7 @@ bool ReadRawBlockFromDisk(std::vector<uint8_t>& block, const FlatFilePos& pos, c bool UndoReadFromDisk(CBlockUndo& blockundo, const CBlockIndex* pindex); -void ThreadImport(ChainstateManager& chainman, std::vector<fs::path> vImportFiles, const ArgsManager& args); +void ThreadImport(ChainstateManager& chainman, std::vector<fs::path> vImportFiles, const ArgsManager& args, const fs::path& mempool_path); } // namespace node #endif // BITCOIN_NODE_BLOCKSTORAGE_H diff --git a/src/node/caches.cpp b/src/node/caches.cpp index f168332ee6..a39ad7aeb6 100644 --- a/src/node/caches.cpp +++ b/src/node/caches.cpp @@ -4,9 +4,9 @@ #include <node/caches.h> +#include <index/txindex.h> #include <txdb.h> #include <util/system.h> -#include <validation.h> namespace node { CacheSizes CalculateCacheSizes(const ArgsManager& args, size_t n_indexes) diff --git a/src/node/chainstate.cpp b/src/node/chainstate.cpp index 54ba5b7966..3f1d6dd743 100644 --- a/src/node/chainstate.cpp +++ b/src/node/chainstate.cpp @@ -4,66 +4,90 @@ #include <node/chainstate.h> +#include <chain.h> +#include <coins.h> #include <consensus/params.h> #include <node/blockstorage.h> +#include <node/caches.h> +#include <sync.h> +#include <threadsafety.h> +#include <tinyformat.h> +#include <txdb.h> +#include <uint256.h> +#include <util/time.h> +#include <util/translation.h> #include <validation.h> +#include <algorithm> +#include <atomic> +#include <cassert> +#include <memory> +#include <vector> + namespace node { -std::optional<ChainstateLoadingError> LoadChainstate(bool fReset, - ChainstateManager& chainman, - CTxMemPool* mempool, - bool fPruneMode, - bool fReindexChainState, - int64_t nBlockTreeDBCache, - int64_t nCoinDBCache, - int64_t nCoinCacheUsage, - bool block_tree_db_in_memory, - bool coins_db_in_memory, - std::function<bool()> shutdown_requested, - std::function<void()> coins_error_cb) +ChainstateLoadResult LoadChainstate(ChainstateManager& chainman, const CacheSizes& cache_sizes, + const ChainstateLoadOptions& options) { - auto is_coinsview_empty = [&](CChainState* chainstate) EXCLUSIVE_LOCKS_REQUIRED(::cs_main) { - return fReset || fReindexChainState || chainstate->CoinsTip().GetBestBlock().IsNull(); + auto is_coinsview_empty = [&](Chainstate* chainstate) EXCLUSIVE_LOCKS_REQUIRED(::cs_main) { + return options.reindex || options.reindex_chainstate || chainstate->CoinsTip().GetBestBlock().IsNull(); }; + if (!hashAssumeValid.IsNull()) { + LogPrintf("Assuming ancestors of block %s have valid signatures.\n", hashAssumeValid.GetHex()); + } else { + LogPrintf("Validating signatures for all blocks.\n"); + } + LogPrintf("Setting nMinimumChainWork=%s\n", nMinimumChainWork.GetHex()); + if (nMinimumChainWork < UintToArith256(chainman.GetConsensus().nMinimumChainWork)) { + LogPrintf("Warning: nMinimumChainWork set below default value of %s\n", chainman.GetConsensus().nMinimumChainWork.GetHex()); + } + if (nPruneTarget == std::numeric_limits<uint64_t>::max()) { + LogPrintf("Block pruning enabled. Use RPC call pruneblockchain(height) to manually prune block and undo files.\n"); + } else if (nPruneTarget) { + LogPrintf("Prune configured to target %u MiB on disk for block and undo files.\n", nPruneTarget / 1024 / 1024); + } + LOCK(cs_main); - chainman.InitializeChainstate(mempool); - chainman.m_total_coinstip_cache = nCoinCacheUsage; - chainman.m_total_coinsdb_cache = nCoinDBCache; + chainman.InitializeChainstate(options.mempool); + chainman.m_total_coinstip_cache = cache_sizes.coins; + chainman.m_total_coinsdb_cache = cache_sizes.coins_db; auto& pblocktree{chainman.m_blockman.m_block_tree_db}; // new CBlockTreeDB tries to delete the existing file, which // fails if it's still open from the previous loop. Close it first: pblocktree.reset(); - pblocktree.reset(new CBlockTreeDB(nBlockTreeDBCache, block_tree_db_in_memory, fReset)); + pblocktree.reset(new CBlockTreeDB(cache_sizes.block_tree_db, options.block_tree_db_in_memory, options.reindex)); - if (fReset) { + if (options.reindex) { pblocktree->WriteReindexing(true); //If we're reindexing in prune mode, wipe away unusable block files and all undo data files - if (fPruneMode) + if (options.prune) { CleanupBlockRevFiles(); + } } - if (shutdown_requested && shutdown_requested()) return ChainstateLoadingError::SHUTDOWN_PROBED; + if (options.check_interrupt && options.check_interrupt()) return {ChainstateLoadStatus::INTERRUPTED, {}}; // LoadBlockIndex will load m_have_pruned if we've ever removed a // block file from disk. - // Note that it also sets fReindex based on the disk flag! - // From here on out fReindex and fReset mean something different! + // Note that it also sets fReindex global based on the disk flag! + // From here on, fReindex and options.reindex values may be different! if (!chainman.LoadBlockIndex()) { - if (shutdown_requested && shutdown_requested()) return ChainstateLoadingError::SHUTDOWN_PROBED; - return ChainstateLoadingError::ERROR_LOADING_BLOCK_DB; + if (options.check_interrupt && options.check_interrupt()) return {ChainstateLoadStatus::INTERRUPTED, {}}; + return {ChainstateLoadStatus::FAILURE, _("Error loading block database")}; } if (!chainman.BlockIndex().empty() && !chainman.m_blockman.LookupBlockIndex(chainman.GetConsensus().hashGenesisBlock)) { - return ChainstateLoadingError::ERROR_BAD_GENESIS_BLOCK; + // If the loaded chain has a wrong genesis, bail out immediately + // (we're likely using a testnet datadir, or the other way around). + return {ChainstateLoadStatus::FAILURE_INCOMPATIBLE_DB, _("Incorrect or no genesis block found. Wrong datadir for network?")}; } // Check for changed -prune state. What we are concerned about is a user who has pruned blocks // in the past, but is now trying to run unpruned. - if (chainman.m_blockman.m_have_pruned && !fPruneMode) { - return ChainstateLoadingError::ERROR_PRUNED_NEEDS_REINDEX; + if (chainman.m_blockman.m_have_pruned && !options.prune) { + return {ChainstateLoadStatus::FAILURE, _("You need to rebuild the database using -reindex to go back to unpruned mode. This will redownload the entire blockchain")}; } // At this point blocktree args are consistent with what's on disk. @@ -71,85 +95,86 @@ std::optional<ChainstateLoadingError> LoadChainstate(bool fReset, // (otherwise we use the one already on disk). // This is called again in ThreadImport after the reindex completes. if (!fReindex && !chainman.ActiveChainstate().LoadGenesisBlock()) { - return ChainstateLoadingError::ERROR_LOAD_GENESIS_BLOCK_FAILED; + return {ChainstateLoadStatus::FAILURE, _("Error initializing block database")}; } // At this point we're either in reindex or we've loaded a useful // block tree into BlockIndex()! - for (CChainState* chainstate : chainman.GetAll()) { + for (Chainstate* chainstate : chainman.GetAll()) { chainstate->InitCoinsDB( - /*cache_size_bytes=*/nCoinDBCache, - /*in_memory=*/coins_db_in_memory, - /*should_wipe=*/fReset || fReindexChainState); + /*cache_size_bytes=*/cache_sizes.coins_db, + /*in_memory=*/options.coins_db_in_memory, + /*should_wipe=*/options.reindex || options.reindex_chainstate); - if (coins_error_cb) { - chainstate->CoinsErrorCatcher().AddReadErrCallback(coins_error_cb); + if (options.coins_error_cb) { + chainstate->CoinsErrorCatcher().AddReadErrCallback(options.coins_error_cb); } // Refuse to load unsupported database format. // This is a no-op if we cleared the coinsviewdb with -reindex or -reindex-chainstate if (chainstate->CoinsDB().NeedsUpgrade()) { - return ChainstateLoadingError::ERROR_CHAINSTATE_UPGRADE_FAILED; + return {ChainstateLoadStatus::FAILURE_INCOMPATIBLE_DB, _("Unsupported chainstate database format found. " + "Please restart with -reindex-chainstate. This will " + "rebuild the chainstate database.")}; } // ReplayBlocks is a no-op if we cleared the coinsviewdb with -reindex or -reindex-chainstate if (!chainstate->ReplayBlocks()) { - return ChainstateLoadingError::ERROR_REPLAYBLOCKS_FAILED; + return {ChainstateLoadStatus::FAILURE, _("Unable to replay blocks. You will need to rebuild the database using -reindex-chainstate.")}; } // The on-disk coinsdb is now in a good state, create the cache - chainstate->InitCoinsCache(nCoinCacheUsage); + chainstate->InitCoinsCache(cache_sizes.coins); assert(chainstate->CanFlushToDisk()); if (!is_coinsview_empty(chainstate)) { // LoadChainTip initializes the chain based on CoinsTip()'s best block if (!chainstate->LoadChainTip()) { - return ChainstateLoadingError::ERROR_LOADCHAINTIP_FAILED; + return {ChainstateLoadStatus::FAILURE, _("Error initializing block database")}; } assert(chainstate->m_chain.Tip() != nullptr); } } - if (!fReset) { + if (!options.reindex) { auto chainstates{chainman.GetAll()}; if (std::any_of(chainstates.begin(), chainstates.end(), - [](const CChainState* cs) EXCLUSIVE_LOCKS_REQUIRED(cs_main) { return cs->NeedsRedownload(); })) { - return ChainstateLoadingError::ERROR_BLOCKS_WITNESS_INSUFFICIENTLY_VALIDATED; - } + [](const Chainstate* cs) EXCLUSIVE_LOCKS_REQUIRED(cs_main) { return cs->NeedsRedownload(); })) { + return {ChainstateLoadStatus::FAILURE, strprintf(_("Witness data for blocks after height %d requires validation. Please restart with -reindex."), + chainman.GetConsensus().SegwitHeight)}; + }; } - return std::nullopt; + return {ChainstateLoadStatus::SUCCESS, {}}; } -std::optional<ChainstateLoadVerifyError> VerifyLoadedChainstate(ChainstateManager& chainman, - bool fReset, - bool fReindexChainState, - int check_blocks, - int check_level) +ChainstateLoadResult VerifyLoadedChainstate(ChainstateManager& chainman, const ChainstateLoadOptions& options) { - auto is_coinsview_empty = [&](CChainState* chainstate) EXCLUSIVE_LOCKS_REQUIRED(::cs_main) { - return fReset || fReindexChainState || chainstate->CoinsTip().GetBestBlock().IsNull(); + auto is_coinsview_empty = [&](Chainstate* chainstate) EXCLUSIVE_LOCKS_REQUIRED(::cs_main) { + return options.reindex || options.reindex_chainstate || chainstate->CoinsTip().GetBestBlock().IsNull(); }; LOCK(cs_main); - for (CChainState* chainstate : chainman.GetAll()) { + for (Chainstate* chainstate : chainman.GetAll()) { if (!is_coinsview_empty(chainstate)) { const CBlockIndex* tip = chainstate->m_chain.Tip(); if (tip && tip->nTime > GetTime() + MAX_FUTURE_BLOCK_TIME) { - return ChainstateLoadVerifyError::ERROR_BLOCK_FROM_FUTURE; + return {ChainstateLoadStatus::FAILURE, _("The block database contains a block which appears to be from the future. " + "This may be due to your computer's date and time being set incorrectly. " + "Only rebuild the block database if you are sure that your computer's date and time are correct")}; } if (!CVerifyDB().VerifyDB( *chainstate, chainman.GetConsensus(), chainstate->CoinsDB(), - check_level, - check_blocks)) { - return ChainstateLoadVerifyError::ERROR_CORRUPTED_BLOCK_DB; + options.check_level, + options.check_blocks)) { + return {ChainstateLoadStatus::FAILURE, _("Corrupted block database detected")}; } } } - return std::nullopt; + return {ChainstateLoadStatus::SUCCESS, {}}; } } // namespace node diff --git a/src/node/chainstate.h b/src/node/chainstate.h index ff7935e8e0..2289310ece 100644 --- a/src/node/chainstate.h +++ b/src/node/chainstate.h @@ -5,30 +5,41 @@ #ifndef BITCOIN_NODE_CHAINSTATE_H #define BITCOIN_NODE_CHAINSTATE_H +#include <util/translation.h> +#include <validation.h> + #include <cstdint> #include <functional> -#include <optional> +#include <tuple> -class ChainstateManager; class CTxMemPool; -namespace Consensus { -struct Params; -} // namespace Consensus namespace node { -enum class ChainstateLoadingError { - ERROR_LOADING_BLOCK_DB, - ERROR_BAD_GENESIS_BLOCK, - ERROR_PRUNED_NEEDS_REINDEX, - ERROR_LOAD_GENESIS_BLOCK_FAILED, - ERROR_CHAINSTATE_UPGRADE_FAILED, - ERROR_REPLAYBLOCKS_FAILED, - ERROR_LOADCHAINTIP_FAILED, - ERROR_GENERIC_BLOCKDB_OPEN_FAILED, - ERROR_BLOCKS_WITNESS_INSUFFICIENTLY_VALIDATED, - SHUTDOWN_PROBED, + +struct CacheSizes; + +struct ChainstateLoadOptions { + CTxMemPool* mempool{nullptr}; + bool block_tree_db_in_memory{false}; + bool coins_db_in_memory{false}; + bool reindex{false}; + bool reindex_chainstate{false}; + bool prune{false}; + int64_t check_blocks{DEFAULT_CHECKBLOCKS}; + int64_t check_level{DEFAULT_CHECKLEVEL}; + std::function<bool()> check_interrupt; + std::function<void()> coins_error_cb; }; +//! Chainstate load status. Simple applications can just check for the success +//! case, and treat other cases as errors. More complex applications may want to +//! try reindexing in the generic failure case, and pass an interrupt callback +//! and exit cleanly in the interrupted case. +enum class ChainstateLoadStatus { SUCCESS, FAILURE, FAILURE_INCOMPATIBLE_DB, INTERRUPTED }; + +//! Chainstate load status code and optional error string. +using ChainstateLoadResult = std::tuple<ChainstateLoadStatus, bilingual_str>; + /** This sequence can have 4 types of outcomes: * * 1. Success @@ -40,45 +51,11 @@ enum class ChainstateLoadingError { * 4. Hard failure * - a failure that definitively cannot be recovered from with a reindex * - * Currently, LoadChainstate returns a std::optional<ChainstateLoadingError> - * which: - * - * - if has_value() - * - Either "Soft failure", "Hard failure", or "Shutdown requested", - * differentiable by the specific enumerator. - * - * Note that a return value of SHUTDOWN_PROBED means ONLY that "during - * this sequence, when we explicitly checked shutdown_requested() at - * arbitrary points, one of those calls returned true". Therefore, a - * return value other than SHUTDOWN_PROBED does not guarantee that - * shutdown hasn't been called indirectly. - * - else - * - Success! + * LoadChainstate returns a (status code, error string) tuple. */ -std::optional<ChainstateLoadingError> LoadChainstate(bool fReset, - ChainstateManager& chainman, - CTxMemPool* mempool, - bool fPruneMode, - bool fReindexChainState, - int64_t nBlockTreeDBCache, - int64_t nCoinDBCache, - int64_t nCoinCacheUsage, - bool block_tree_db_in_memory, - bool coins_db_in_memory, - std::function<bool()> shutdown_requested = nullptr, - std::function<void()> coins_error_cb = nullptr); - -enum class ChainstateLoadVerifyError { - ERROR_BLOCK_FROM_FUTURE, - ERROR_CORRUPTED_BLOCK_DB, - ERROR_GENERIC_FAILURE, -}; - -std::optional<ChainstateLoadVerifyError> VerifyLoadedChainstate(ChainstateManager& chainman, - bool fReset, - bool fReindexChainState, - int check_blocks, - int check_level); +ChainstateLoadResult LoadChainstate(ChainstateManager& chainman, const CacheSizes& cache_sizes, + const ChainstateLoadOptions& options); +ChainstateLoadResult VerifyLoadedChainstate(ChainstateManager& chainman, const ChainstateLoadOptions& options); } // namespace node #endif // BITCOIN_NODE_CHAINSTATE_H diff --git a/src/node/connection_types.cpp b/src/node/connection_types.cpp new file mode 100644 index 0000000000..904f4371aa --- /dev/null +++ b/src/node/connection_types.cpp @@ -0,0 +1,26 @@ +// Copyright (c) 2022 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include <node/connection_types.h> +#include <cassert> + +std::string ConnectionTypeAsString(ConnectionType conn_type) +{ + switch (conn_type) { + case ConnectionType::INBOUND: + return "inbound"; + case ConnectionType::MANUAL: + return "manual"; + case ConnectionType::FEELER: + return "feeler"; + case ConnectionType::OUTBOUND_FULL_RELAY: + return "outbound-full-relay"; + case ConnectionType::BLOCK_RELAY: + return "block-relay-only"; + case ConnectionType::ADDR_FETCH: + return "addr-fetch"; + } // no default case, so the compiler can warn about missing cases + + assert(false); +} diff --git a/src/node/connection_types.h b/src/node/connection_types.h new file mode 100644 index 0000000000..5e1abcace6 --- /dev/null +++ b/src/node/connection_types.h @@ -0,0 +1,82 @@ +// Copyright (c) 2022 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_NODE_CONNECTION_TYPES_H +#define BITCOIN_NODE_CONNECTION_TYPES_H + +#include <string> + +/** Different types of connections to a peer. This enum encapsulates the + * information we have available at the time of opening or accepting the + * connection. Aside from INBOUND, all types are initiated by us. + * + * If adding or removing types, please update CONNECTION_TYPE_DOC in + * src/rpc/net.cpp and src/qt/rpcconsole.cpp, as well as the descriptions in + * src/qt/guiutil.cpp and src/bitcoin-cli.cpp::NetinfoRequestHandler. */ +enum class ConnectionType { + /** + * Inbound connections are those initiated by a peer. This is the only + * property we know at the time of connection, until P2P messages are + * exchanged. + */ + INBOUND, + + /** + * These are the default connections that we use to connect with the + * network. There is no restriction on what is relayed; by default we relay + * blocks, addresses & transactions. We automatically attempt to open + * MAX_OUTBOUND_FULL_RELAY_CONNECTIONS using addresses from our AddrMan. + */ + OUTBOUND_FULL_RELAY, + + + /** + * We open manual connections to addresses that users explicitly requested + * via the addnode RPC or the -addnode/-connect configuration options. Even if a + * manual connection is misbehaving, we do not automatically disconnect or + * add it to our discouragement filter. + */ + MANUAL, + + /** + * Feeler connections are short-lived connections made to check that a node + * is alive. They can be useful for: + * - test-before-evict: if one of the peers is considered for eviction from + * our AddrMan because another peer is mapped to the same slot in the tried table, + * evict only if this longer-known peer is offline. + * - move node addresses from New to Tried table, so that we have more + * connectable addresses in our AddrMan. + * Note that in the literature ("Eclipse Attacks on Bitcoin’s Peer-to-Peer Network") + * only the latter feature is referred to as "feeler connections", + * although in our codebase feeler connections encompass test-before-evict as well. + * We make these connections approximately every FEELER_INTERVAL: + * first we resolve previously found collisions if they exist (test-before-evict), + * otherwise we connect to a node from the new table. + */ + FEELER, + + /** + * We use block-relay-only connections to help prevent against partition + * attacks. By not relaying transactions or addresses, these connections + * are harder to detect by a third party, thus helping obfuscate the + * network topology. We automatically attempt to open + * MAX_BLOCK_RELAY_ONLY_ANCHORS using addresses from our anchors.dat. Then + * addresses from our AddrMan if MAX_BLOCK_RELAY_ONLY_CONNECTIONS + * isn't reached yet. + */ + BLOCK_RELAY, + + /** + * AddrFetch connections are short lived connections used to solicit + * addresses from peers. These are initiated to addresses submitted via the + * -seednode command line argument, or under certain conditions when the + * AddrMan is empty. + */ + ADDR_FETCH, +}; + +/** Convert ConnectionType enum to a string value */ +std::string ConnectionTypeAsString(ConnectionType conn_type); + +#endif // BITCOIN_NODE_CONNECTION_TYPES_H diff --git a/src/node/eviction.cpp b/src/node/eviction.cpp new file mode 100644 index 0000000000..33406931d4 --- /dev/null +++ b/src/node/eviction.cpp @@ -0,0 +1,240 @@ +// Copyright (c) 2022 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include <node/eviction.h> + +#include <algorithm> +#include <array> +#include <chrono> +#include <cstdint> +#include <functional> +#include <map> +#include <vector> + + +static bool ReverseCompareNodeMinPingTime(const NodeEvictionCandidate &a, const NodeEvictionCandidate &b) +{ + return a.m_min_ping_time > b.m_min_ping_time; +} + +static bool ReverseCompareNodeTimeConnected(const NodeEvictionCandidate &a, const NodeEvictionCandidate &b) +{ + return a.m_connected > b.m_connected; +} + +static bool CompareNetGroupKeyed(const NodeEvictionCandidate &a, const NodeEvictionCandidate &b) { + return a.nKeyedNetGroup < b.nKeyedNetGroup; +} + +static bool CompareNodeBlockTime(const NodeEvictionCandidate &a, const NodeEvictionCandidate &b) +{ + // There is a fall-through here because it is common for a node to have many peers which have not yet relayed a block. + if (a.m_last_block_time != b.m_last_block_time) return a.m_last_block_time < b.m_last_block_time; + if (a.fRelevantServices != b.fRelevantServices) return b.fRelevantServices; + return a.m_connected > b.m_connected; +} + +static bool CompareNodeTXTime(const NodeEvictionCandidate &a, const NodeEvictionCandidate &b) +{ + // There is a fall-through here because it is common for a node to have more than a few peers that have not yet relayed txn. + if (a.m_last_tx_time != b.m_last_tx_time) return a.m_last_tx_time < b.m_last_tx_time; + if (a.m_relay_txs != b.m_relay_txs) return b.m_relay_txs; + if (a.fBloomFilter != b.fBloomFilter) return a.fBloomFilter; + return a.m_connected > b.m_connected; +} + +// Pick out the potential block-relay only peers, and sort them by last block time. +static bool CompareNodeBlockRelayOnlyTime(const NodeEvictionCandidate &a, const NodeEvictionCandidate &b) +{ + if (a.m_relay_txs != b.m_relay_txs) return a.m_relay_txs; + if (a.m_last_block_time != b.m_last_block_time) return a.m_last_block_time < b.m_last_block_time; + if (a.fRelevantServices != b.fRelevantServices) return b.fRelevantServices; + return a.m_connected > b.m_connected; +} + +/** + * Sort eviction candidates by network/localhost and connection uptime. + * Candidates near the beginning are more likely to be evicted, and those + * near the end are more likely to be protected, e.g. less likely to be evicted. + * - First, nodes that are not `is_local` and that do not belong to `network`, + * sorted by increasing uptime (from most recently connected to connected longer). + * - Then, nodes that are `is_local` or belong to `network`, sorted by increasing uptime. + */ +struct CompareNodeNetworkTime { + const bool m_is_local; + const Network m_network; + CompareNodeNetworkTime(bool is_local, Network network) : m_is_local(is_local), m_network(network) {} + bool operator()(const NodeEvictionCandidate& a, const NodeEvictionCandidate& b) const + { + if (m_is_local && a.m_is_local != b.m_is_local) return b.m_is_local; + if ((a.m_network == m_network) != (b.m_network == m_network)) return b.m_network == m_network; + return a.m_connected > b.m_connected; + }; +}; + +//! 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(std::remove_if(elements.end() - eraseSize, elements.end(), predicate), elements.end()); +} + +void ProtectNoBanConnections(std::vector<NodeEvictionCandidate>& eviction_candidates) +{ + eviction_candidates.erase(std::remove_if(eviction_candidates.begin(), eviction_candidates.end(), + [](NodeEvictionCandidate const& n) { + return n.m_noban; + }), + eviction_candidates.end()); +} + +void ProtectOutboundConnections(std::vector<NodeEvictionCandidate>& eviction_candidates) +{ + eviction_candidates.erase(std::remove_if(eviction_candidates.begin(), eviction_candidates.end(), + [](NodeEvictionCandidate const& n) { + return n.m_conn_type != ConnectionType::INBOUND; + }), + eviction_candidates.end()); +} + +void ProtectEvictionCandidatesByRatio(std::vector<NodeEvictionCandidate>& eviction_candidates) +{ + // 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 of these protected + // spots for Tor/onion, localhost, I2P, and CJDNS peers, even if they're not longest uptime + // overall. This helps protect these higher-latency peers that tend to be otherwise + // disadvantaged under our eviction criteria. + const size_t initial_size = eviction_candidates.size(); + const size_t total_protect_size{initial_size / 2}; + + // Disadvantaged networks to protect. In the case of equal counts, earlier array members + // have the first opportunity to recover unused slots from the previous iteration. + struct Net { bool is_local; Network id; size_t count; }; + std::array<Net, 4> networks{ + {{false, NET_CJDNS, 0}, {false, NET_I2P, 0}, {/*localhost=*/true, NET_MAX, 0}, {false, NET_ONION, 0}}}; + + // Count and store the number of eviction candidates per network. + for (Net& n : networks) { + n.count = std::count_if(eviction_candidates.cbegin(), eviction_candidates.cend(), + [&n](const NodeEvictionCandidate& c) { + return n.is_local ? c.m_is_local : c.m_network == n.id; + }); + } + // Sort `networks` by ascending candidate count, to give networks having fewer candidates + // the first opportunity to recover unused protected slots from the previous iteration. + std::stable_sort(networks.begin(), networks.end(), [](Net a, Net b) { return a.count < b.count; }); + + // Protect up to 25% of the eviction candidates by disadvantaged network. + const size_t max_protect_by_network{total_protect_size / 2}; + size_t num_protected{0}; + + while (num_protected < max_protect_by_network) { + // Count the number of disadvantaged networks from which we have peers to protect. + auto num_networks = std::count_if(networks.begin(), networks.end(), [](const Net& n) { return n.count; }); + if (num_networks == 0) { + break; + } + const size_t disadvantaged_to_protect{max_protect_by_network - num_protected}; + const size_t protect_per_network{std::max(disadvantaged_to_protect / num_networks, static_cast<size_t>(1))}; + // Early exit flag if there are no remaining candidates by disadvantaged network. + bool protected_at_least_one{false}; + + for (Net& n : networks) { + if (n.count == 0) continue; + const size_t before = eviction_candidates.size(); + EraseLastKElements(eviction_candidates, CompareNodeNetworkTime(n.is_local, n.id), + protect_per_network, [&n](const NodeEvictionCandidate& c) { + return n.is_local ? c.m_is_local : c.m_network == n.id; + }); + const size_t after = eviction_candidates.size(); + if (before > after) { + protected_at_least_one = true; + const size_t delta{before - after}; + num_protected += delta; + if (num_protected >= max_protect_by_network) { + break; + } + n.count -= delta; + } + } + if (!protected_at_least_one) { + break; + } + } + + // Calculate how many we removed, and update our total number of peers that + // we want to protect based on uptime accordingly. + assert(num_protected == initial_size - eviction_candidates.size()); + const size_t remaining_to_protect{total_protect_size - num_protected}; + EraseLastKElements(eviction_candidates, ReverseCompareNodeTimeConnected, remaining_to_protect); +} + +[[nodiscard]] std::optional<NodeId> SelectNodeToEvict(std::vector<NodeEvictionCandidate>&& vEvictionCandidates) +{ + // Protect connections with certain characteristics + + ProtectNoBanConnections(vEvictionCandidates); + + ProtectOutboundConnections(vEvictionCandidates); + + // Deterministically select 4 peers to protect by netgroup. + // An attacker cannot predict which netgroups will be protected + EraseLastKElements(vEvictionCandidates, CompareNetGroupKeyed, 4); + // Protect the 8 nodes with the lowest minimum ping time. + // An attacker cannot manipulate this metric without physically moving nodes closer to the target. + EraseLastKElements(vEvictionCandidates, ReverseCompareNodeMinPingTime, 8); + // Protect 4 nodes that most recently sent us novel transactions accepted into our mempool. + // 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. + EraseLastKElements(vEvictionCandidates, CompareNodeBlockRelayOnlyTime, 8, + [](const NodeEvictionCandidate& n) { return !n.m_relay_txs && 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 some of the remaining eviction candidates by ratios of desirable + // or disadvantaged characteristics. + ProtectEvictionCandidatesByRatio(vEvictionCandidates); + + 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) + // then we probably don't want to evict it no matter what. + if (std::any_of(vEvictionCandidates.begin(),vEvictionCandidates.end(),[](NodeEvictionCandidate const &n){return n.prefer_evict;})) { + vEvictionCandidates.erase(std::remove_if(vEvictionCandidates.begin(),vEvictionCandidates.end(), + [](NodeEvictionCandidate const &n){return !n.prefer_evict;}),vEvictionCandidates.end()); + } + + // Identify the network group with the most connections and youngest member. + // (vEvictionCandidates is already sorted by reverse connect time) + uint64_t naMostConnections; + unsigned int nMostConnections = 0; + std::chrono::seconds nMostConnectionsTime{0}; + std::map<uint64_t, std::vector<NodeEvictionCandidate> > mapNetGroupNodes; + for (const NodeEvictionCandidate &node : vEvictionCandidates) { + std::vector<NodeEvictionCandidate> &group = mapNetGroupNodes[node.nKeyedNetGroup]; + group.push_back(node); + const auto grouptime{group[0].m_connected}; + + if (group.size() > nMostConnections || (group.size() == nMostConnections && grouptime > nMostConnectionsTime)) { + nMostConnections = group.size(); + nMostConnectionsTime = grouptime; + naMostConnections = node.nKeyedNetGroup; + } + } + + // Reduce to the network group with the most connections + vEvictionCandidates = std::move(mapNetGroupNodes[naMostConnections]); + + // Disconnect from the network group with the most connections + return vEvictionCandidates.front().id; +} diff --git a/src/node/eviction.h b/src/node/eviction.h new file mode 100644 index 0000000000..1bb32e5327 --- /dev/null +++ b/src/node/eviction.h @@ -0,0 +1,69 @@ +// Copyright (c) 2022 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_NODE_EVICTION_H +#define BITCOIN_NODE_EVICTION_H + +#include <node/connection_types.h> +#include <net_permissions.h> + +#include <chrono> +#include <cstdint> +#include <optional> +#include <vector> + +typedef int64_t NodeId; + +struct NodeEvictionCandidate { + NodeId id; + std::chrono::seconds m_connected; + std::chrono::microseconds m_min_ping_time; + std::chrono::seconds m_last_block_time; + std::chrono::seconds m_last_tx_time; + bool fRelevantServices; + bool m_relay_txs; + bool fBloomFilter; + uint64_t nKeyedNetGroup; + bool prefer_evict; + bool m_is_local; + Network m_network; + bool m_noban; + ConnectionType m_conn_type; +}; + +/** + * 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 the + * following categories of peers, sorted by longest uptime, even if they're not + * longest uptime overall: + * + * - onion peers connected via our tor control service + * + * - localhost peers, as manually configured hidden services not using + * `-bind=addr[:port]=onion` will not be detected as inbound onion connections + * + * - I2P peers + * + * - CJDNS peers + * + * This helps protect these privacy network peers, which tend to be otherwise + * disadvantaged under our eviction criteria for their higher min ping times + * relative to IPv4/IPv6 peers, and favorise the diversity of peer connections. + */ +void ProtectEvictionCandidatesByRatio(std::vector<NodeEvictionCandidate>& vEvictionCandidates); + +#endif // BITCOIN_NODE_EVICTION_H diff --git a/src/node/ui_interface.cpp b/src/node/interface_ui.cpp index a3a6ede39a..fa90d6fda7 100644 --- a/src/node/ui_interface.cpp +++ b/src/node/interface_ui.cpp @@ -2,7 +2,7 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#include <node/ui_interface.h> +#include <node/interface_ui.h> #include <util/translation.h> @@ -53,7 +53,7 @@ void CClientUIInterface::NotifyNetworkActiveChanged(bool networkActive) { return void CClientUIInterface::NotifyAlertChanged() { return g_ui_signals.NotifyAlertChanged(); } void CClientUIInterface::ShowProgress(const std::string& title, int nProgress, bool resume_possible) { return g_ui_signals.ShowProgress(title, nProgress, resume_possible); } void CClientUIInterface::NotifyBlockTip(SynchronizationState s, const CBlockIndex* i) { return g_ui_signals.NotifyBlockTip(s, i); } -void CClientUIInterface::NotifyHeaderTip(SynchronizationState s, const CBlockIndex* i) { return g_ui_signals.NotifyHeaderTip(s, i); } +void CClientUIInterface::NotifyHeaderTip(SynchronizationState s, int64_t height, int64_t timestamp, bool presync) { return g_ui_signals.NotifyHeaderTip(s, height, timestamp, presync); } void CClientUIInterface::BannedListChanged() { return g_ui_signals.BannedListChanged(); } bool InitError(const bilingual_str& str) diff --git a/src/node/ui_interface.h b/src/node/interface_ui.h index d02238b549..316d75167e 100644 --- a/src/node/ui_interface.h +++ b/src/node/interface_ui.h @@ -3,8 +3,8 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#ifndef BITCOIN_NODE_UI_INTERFACE_H -#define BITCOIN_NODE_UI_INTERFACE_H +#ifndef BITCOIN_NODE_INTERFACE_UI_H +#define BITCOIN_NODE_INTERFACE_UI_H #include <functional> #include <memory> @@ -105,7 +105,7 @@ public: ADD_SIGNALS_DECL_WRAPPER(NotifyBlockTip, void, SynchronizationState, const CBlockIndex*); /** Best header has changed */ - ADD_SIGNALS_DECL_WRAPPER(NotifyHeaderTip, void, SynchronizationState, const CBlockIndex*); + ADD_SIGNALS_DECL_WRAPPER(NotifyHeaderTip, void, SynchronizationState, int64_t height, int64_t timestamp, bool presync); /** Banlist did change. */ ADD_SIGNALS_DECL_WRAPPER(BannedListChanged, void, void); @@ -120,4 +120,4 @@ constexpr auto AbortError = InitError; extern CClientUIInterface uiInterface; -#endif // BITCOIN_NODE_UI_INTERFACE_H +#endif // BITCOIN_NODE_INTERFACE_UI_H diff --git a/src/node/interfaces.cpp b/src/node/interfaces.cpp index 7752fb0f65..8a0011a629 100644 --- a/src/node/interfaces.cpp +++ b/src/node/interfaces.cpp @@ -19,10 +19,11 @@ #include <netaddress.h> #include <netbase.h> #include <node/blockstorage.h> +#include <kernel/chain.h> #include <node/coin.h> #include <node/context.h> #include <node/transaction.h> -#include <node/ui_interface.h> +#include <node/interface_ui.h> #include <policy/feerate.h> #include <policy/fees.h> #include <policy/policy.h> @@ -35,7 +36,6 @@ #include <shutdown.h> #include <support/allocators/secure.h> #include <sync.h> -#include <timedata.h> #include <txmempool.h> #include <uint256.h> #include <univalue.h> @@ -66,6 +66,8 @@ using interfaces::Node; using interfaces::WalletLoader; namespace node { +// All members of the classes in this namespace are intentionally public, as the +// classes themselves are private. namespace { #ifdef ENABLE_EXTERNAL_SIGNER class ExternalSignerImpl : public interfaces::ExternalSigner @@ -73,15 +75,12 @@ class ExternalSignerImpl : public interfaces::ExternalSigner public: ExternalSignerImpl(::ExternalSigner signer) : m_signer(std::move(signer)) {} std::string getName() override { return m_signer.m_name; } -private: ::ExternalSigner m_signer; }; #endif 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)); } @@ -288,12 +287,7 @@ public: } double getVerificationProgress() override { - const CBlockIndex* tip; - { - LOCK(::cs_main); - tip = chainman().ActiveChain().Tip(); - } - return GuessVerificationProgress(chainman().GetParams().TxData(), tip); + return GuessVerificationProgress(chainman().GetParams().TxData(), WITH_LOCK(::cs_main, return chainman().ActiveChain().Tip())); } bool isInitialBlockDownload() override { return chainman().ActiveChainstate().IsInitialBlockDownload(); @@ -307,7 +301,11 @@ public: } } bool getNetworkActive() override { return m_context->connman && m_context->connman->GetNetworkActive(); } - CFeeRate getDustRelayFee() override { return ::dustRelayFee; } + CFeeRate getDustRelayFee() override + { + if (!m_context->mempool) return CFeeRate{DUST_RELAY_TX_FEE}; + return m_context->mempool->m_dust_relay_feerate; + } UniValue executeRpc(const std::string& command, const UniValue& params, const std::string& uri) override { JSONRPCRequest req; @@ -379,9 +377,8 @@ public: std::unique_ptr<Handler> handleNotifyHeaderTip(NotifyHeaderTipFn fn) override { return MakeHandler( - ::uiInterface.NotifyHeaderTip_connect([fn](SynchronizationState sync_state, const CBlockIndex* block) { - fn(sync_state, BlockTip{block->nHeight, block->GetBlockTime(), block->GetBlockHash()}, - /* verification progress is unused when a header was received */ 0); + ::uiInterface.NotifyHeaderTip_connect([fn](SynchronizationState sync_state, int64_t height, int64_t timestamp, bool presync) { + fn(sync_state, BlockTip{(int)height, timestamp, uint256{}}, presync); })); } NodeContext* context() override { return m_context; } @@ -389,6 +386,7 @@ public: { m_context = context; } + ChainstateManager& chainman() { return *Assert(m_context->chainman); } NodeContext* m_context{nullptr}; }; @@ -401,6 +399,7 @@ bool FillBlock(const CBlockIndex* index, const FoundBlock& block, UniqueLock<Rec if (block.m_max_time) *block.m_max_time = index->GetBlockTimeMax(); if (block.m_mtp_time) *block.m_mtp_time = index->GetMedianTimePast(); if (block.m_in_active_chain) *block.m_in_active_chain = active[index->nHeight] == index; + if (block.m_locator) { *block.m_locator = GetLocator(index); } if (block.m_next_block) FillBlock(active[index->nHeight] == index ? active[index->nHeight + 1] : nullptr, *block.m_next_block, lock, active); if (block.m_data) { REVERSE_LOCK(lock); @@ -426,11 +425,11 @@ public: } void BlockConnected(const std::shared_ptr<const CBlock>& block, const CBlockIndex* index) override { - m_notifications->blockConnected(*block, index->nHeight); + m_notifications->blockConnected(kernel::MakeBlockInfo(index, block.get())); } void BlockDisconnected(const std::shared_ptr<const CBlock>& block, const CBlockIndex* index) override { - m_notifications->blockDisconnected(*block, index->nHeight); + m_notifications->blockDisconnected(kernel::MakeBlockInfo(index, block.get())); } void UpdatedBlockTip(const CBlockIndex* index, const CBlockIndex* fork_index, bool is_ibd) override { @@ -500,46 +499,39 @@ public: class ChainImpl : public Chain { -private: - ChainstateManager& chainman() { return *Assert(m_node.chainman); } public: explicit ChainImpl(NodeContext& node) : m_node(node) {} std::optional<int> getHeight() override { - LOCK(::cs_main); - const CChain& active = Assert(m_node.chainman)->ActiveChain(); - int height = active.Height(); - if (height >= 0) { - return height; - } - return std::nullopt; + const int height{WITH_LOCK(::cs_main, return chainman().ActiveChain().Height())}; + return height >= 0 ? std::optional{height} : std::nullopt; } uint256 getBlockHash(int height) override { LOCK(::cs_main); - const CChain& active = Assert(m_node.chainman)->ActiveChain(); - CBlockIndex* block = active[height]; - assert(block); - return block->GetBlockHash(); + return Assert(chainman().ActiveChain()[height])->GetBlockHash(); } bool haveBlockOnDisk(int height) override { - LOCK(cs_main); - const CChain& active = Assert(m_node.chainman)->ActiveChain(); - CBlockIndex* block = active[height]; + LOCK(::cs_main); + const CBlockIndex* block{chainman().ActiveChain()[height]}; return block && ((block->nStatus & BLOCK_HAVE_DATA) != 0) && block->nTx > 0; } CBlockLocator getTipLocator() override { - LOCK(cs_main); - const CChain& active = Assert(m_node.chainman)->ActiveChain(); - return active.GetLocator(); + LOCK(::cs_main); + return chainman().ActiveChain().GetLocator(); + } + CBlockLocator getActiveChainLocator(const uint256& block_hash) override + { + LOCK(::cs_main); + const CBlockIndex* index = chainman().m_blockman.LookupBlockIndex(block_hash); + return GetLocator(index); } std::optional<int> findLocatorFork(const CBlockLocator& locator) override { - LOCK(cs_main); - const CChainState& active = Assert(m_node.chainman)->ActiveChainstate(); - if (const CBlockIndex* fork = active.FindForkInGlobalIndex(locator)) { + LOCK(::cs_main); + if (const CBlockIndex* fork = chainman().ActiveChainstate().FindForkInGlobalIndex(locator)) { return fork->nHeight; } return std::nullopt; @@ -547,20 +539,19 @@ public: bool findBlock(const uint256& hash, const FoundBlock& block) override { WAIT_LOCK(cs_main, lock); - const CChain& active = Assert(m_node.chainman)->ActiveChain(); - return FillBlock(m_node.chainman->m_blockman.LookupBlockIndex(hash), block, lock, active); + return FillBlock(chainman().m_blockman.LookupBlockIndex(hash), block, lock, chainman().ActiveChain()); } bool findFirstBlockWithTimeAndHeight(int64_t min_time, int min_height, const FoundBlock& block) override { WAIT_LOCK(cs_main, lock); - const CChain& active = Assert(m_node.chainman)->ActiveChain(); + const CChain& active = chainman().ActiveChain(); return FillBlock(active.FindEarliestAtLeast(min_time, min_height), block, lock, active); } bool findAncestorByHeight(const uint256& block_hash, int ancestor_height, const FoundBlock& ancestor_out) override { WAIT_LOCK(cs_main, lock); - const CChain& active = Assert(m_node.chainman)->ActiveChain(); - if (const CBlockIndex* block = m_node.chainman->m_blockman.LookupBlockIndex(block_hash)) { + const CChain& active = chainman().ActiveChain(); + if (const CBlockIndex* block = chainman().m_blockman.LookupBlockIndex(block_hash)) { if (const CBlockIndex* ancestor = block->GetAncestor(ancestor_height)) { return FillBlock(ancestor, ancestor_out, lock, active); } @@ -570,18 +561,17 @@ public: bool findAncestorByHash(const uint256& block_hash, const uint256& ancestor_hash, const FoundBlock& ancestor_out) override { WAIT_LOCK(cs_main, lock); - const CChain& active = Assert(m_node.chainman)->ActiveChain(); - const CBlockIndex* block = m_node.chainman->m_blockman.LookupBlockIndex(block_hash); - const CBlockIndex* ancestor = m_node.chainman->m_blockman.LookupBlockIndex(ancestor_hash); + const CBlockIndex* block = chainman().m_blockman.LookupBlockIndex(block_hash); + const CBlockIndex* ancestor = chainman().m_blockman.LookupBlockIndex(ancestor_hash); if (block && ancestor && block->GetAncestor(ancestor->nHeight) != ancestor) ancestor = nullptr; - return FillBlock(ancestor, ancestor_out, lock, active); + return FillBlock(ancestor, ancestor_out, lock, chainman().ActiveChain()); } bool findCommonAncestor(const uint256& block_hash1, const uint256& block_hash2, const FoundBlock& ancestor_out, const FoundBlock& block1_out, const FoundBlock& block2_out) override { WAIT_LOCK(cs_main, lock); - const CChain& active = Assert(m_node.chainman)->ActiveChain(); - const CBlockIndex* block1 = m_node.chainman->m_blockman.LookupBlockIndex(block_hash1); - const CBlockIndex* block2 = m_node.chainman->m_blockman.LookupBlockIndex(block_hash2); + const CChain& active = chainman().ActiveChain(); + const CBlockIndex* block1 = chainman().m_blockman.LookupBlockIndex(block_hash1); + const CBlockIndex* block2 = 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. Cast bool to int to avoid -Wbitwise-instead-of-logical @@ -593,7 +583,7 @@ public: void findCoins(std::map<COutPoint, Coin>& coins) override { return FindCoins(m_node, coins); } double guessVerificationProgress(const uint256& block_hash) override { - LOCK(cs_main); + LOCK(::cs_main); return GuessVerificationProgress(chainman().GetParams().TxData(), chainman().m_blockman.LookupBlockIndex(block_hash)); } bool hasBlocks(const uint256& block_hash, int min_height, std::optional<int> max_height) override @@ -653,8 +643,12 @@ public: } void getPackageLimits(unsigned int& limit_ancestor_count, unsigned int& limit_descendant_count) override { - limit_ancestor_count = gArgs.GetIntArg("-limitancestorcount", DEFAULT_ANCESTOR_LIMIT); - limit_descendant_count = gArgs.GetIntArg("-limitdescendantcount", DEFAULT_DESCENDANT_LIMIT); + const CTxMemPool::Limits default_limits{}; + + const CTxMemPool::Limits& limits{m_node.mempool ? m_node.mempool->m_limits : default_limits}; + + limit_ancestor_count = limits.ancestor_count; + limit_descendant_count = limits.descendant_count; } bool checkChainLimits(const CTransactionRef& tx) override { @@ -662,15 +656,11 @@ public: LockPoints lp; CTxMemPoolEntry entry(tx, 0, 0, 0, false, 0, lp); CTxMemPool::setEntries ancestors; - auto limit_ancestor_count = gArgs.GetIntArg("-limitancestorcount", DEFAULT_ANCESTOR_LIMIT); - auto limit_ancestor_size = gArgs.GetIntArg("-limitancestorsize", DEFAULT_ANCESTOR_SIZE_LIMIT) * 1000; - auto limit_descendant_count = gArgs.GetIntArg("-limitdescendantcount", DEFAULT_DESCENDANT_LIMIT); - auto limit_descendant_size = gArgs.GetIntArg("-limitdescendantsize", DEFAULT_DESCENDANT_SIZE_LIMIT) * 1000; + const CTxMemPool::Limits& limits{m_node.mempool->m_limits}; std::string unused_error_string; LOCK(m_node.mempool->cs); return m_node.mempool->CalculateMemPoolAncestors( - entry, ancestors, limit_ancestor_count, limit_ancestor_size, - limit_descendant_count, limit_descendant_size, unused_error_string); + entry, ancestors, limits, unused_error_string); } CFeeRate estimateSmartFee(int num_blocks, bool conservative, FeeCalculation* calc) override { @@ -685,15 +675,27 @@ public: CFeeRate mempoolMinFee() override { if (!m_node.mempool) return {}; - return m_node.mempool->GetMinFee(gArgs.GetIntArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000); + return m_node.mempool->GetMinFee(); + } + CFeeRate relayMinFee() override + { + if (!m_node.mempool) return CFeeRate{DEFAULT_MIN_RELAY_TX_FEE}; + return m_node.mempool->m_min_relay_feerate; + } + CFeeRate relayIncrementalFee() override + { + if (!m_node.mempool) return CFeeRate{DEFAULT_INCREMENTAL_RELAY_FEE}; + return m_node.mempool->m_incremental_relay_feerate; + } + CFeeRate relayDustFee() override + { + if (!m_node.mempool) return CFeeRate{DUST_RELAY_TX_FEE}; + return m_node.mempool->m_dust_relay_feerate; } - CFeeRate relayMinFee() override { return ::minRelayTxFee; } - CFeeRate relayIncrementalFee() override { return ::incrementalRelayFee; } - CFeeRate relayDustFee() override { return ::dustRelayFee; } bool havePruned() override { - LOCK(cs_main); - return m_node.chainman->m_blockman.m_have_pruned; + LOCK(::cs_main); + return chainman().m_blockman.m_have_pruned; } bool isReadyToBroadcast() override { return !node::fImporting && !node::fReindex && !isInitialBlockDownload(); } bool isInitialBlockDownload() override { @@ -713,11 +715,7 @@ public: } void waitForNotificationsIfTipChanged(const uint256& old_tip) override { - if (!old_tip.IsNull()) { - LOCK(::cs_main); - const CChain& active = Assert(m_node.chainman)->ActiveChain(); - if (old_tip == active.Tip()->GetBlockHash()) return; - } + if (!old_tip.IsNull() && old_tip == WITH_LOCK(::cs_main, return chainman().ActiveChain().Tip()->GetBlockHash())) return; SyncWithValidationInterfaceQueue(); } std::unique_ptr<Handler> handleRpc(const CRPCCommand& command) override @@ -767,6 +765,13 @@ public: notifications.transactionAddedToMempool(entry.GetSharedTx(), 0 /* mempool_sequence */); } } + bool hasAssumedValidChain() override + { + return chainman().IsSnapshotActive(); + } + + NodeContext* context() override { return &m_node; } + ChainstateManager& chainman() { return *Assert(m_node.chainman); } NodeContext& m_node; }; } // namespace diff --git a/src/node/mempool_args.cpp b/src/node/mempool_args.cpp new file mode 100644 index 0000000000..8c929e5e0d --- /dev/null +++ b/src/node/mempool_args.cpp @@ -0,0 +1,100 @@ +// Copyright (c) 2022 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include <node/mempool_args.h> + +#include <kernel/mempool_limits.h> +#include <kernel/mempool_options.h> + +#include <chainparams.h> +#include <consensus/amount.h> +#include <logging.h> +#include <policy/feerate.h> +#include <policy/policy.h> +#include <script/standard.h> +#include <tinyformat.h> +#include <util/error.h> +#include <util/moneystr.h> +#include <util/system.h> +#include <util/translation.h> + +#include <chrono> +#include <memory> + +using kernel::MemPoolLimits; +using kernel::MemPoolOptions; + +namespace { +void ApplyArgsManOptions(const ArgsManager& argsman, MemPoolLimits& mempool_limits) +{ + mempool_limits.ancestor_count = argsman.GetIntArg("-limitancestorcount", mempool_limits.ancestor_count); + + if (auto vkb = argsman.GetIntArg("-limitancestorsize")) mempool_limits.ancestor_size_vbytes = *vkb * 1'000; + + mempool_limits.descendant_count = argsman.GetIntArg("-limitdescendantcount", mempool_limits.descendant_count); + + if (auto vkb = argsman.GetIntArg("-limitdescendantsize")) mempool_limits.descendant_size_vbytes = *vkb * 1'000; +} +} + +std::optional<bilingual_str> ApplyArgsManOptions(const ArgsManager& argsman, const CChainParams& chainparams, MemPoolOptions& mempool_opts) +{ + mempool_opts.check_ratio = argsman.GetIntArg("-checkmempool", mempool_opts.check_ratio); + + if (auto mb = argsman.GetIntArg("-maxmempool")) mempool_opts.max_size_bytes = *mb * 1'000'000; + + if (auto hours = argsman.GetIntArg("-mempoolexpiry")) mempool_opts.expiry = std::chrono::hours{*hours}; + + // incremental relay fee sets the minimum feerate increase necessary for replacement in the mempool + // and the amount the mempool min fee increases above the feerate of txs evicted due to mempool limiting. + if (argsman.IsArgSet("-incrementalrelayfee")) { + if (std::optional<CAmount> inc_relay_fee = ParseMoney(argsman.GetArg("-incrementalrelayfee", ""))) { + mempool_opts.incremental_relay_feerate = CFeeRate{inc_relay_fee.value()}; + } else { + return AmountErrMsg("incrementalrelayfee", argsman.GetArg("-incrementalrelayfee", "")); + } + } + + if (argsman.IsArgSet("-minrelaytxfee")) { + if (std::optional<CAmount> min_relay_feerate = ParseMoney(argsman.GetArg("-minrelaytxfee", ""))) { + // High fee check is done afterward in CWallet::Create() + mempool_opts.min_relay_feerate = CFeeRate{min_relay_feerate.value()}; + } else { + return AmountErrMsg("minrelaytxfee", argsman.GetArg("-minrelaytxfee", "")); + } + } else if (mempool_opts.incremental_relay_feerate > mempool_opts.min_relay_feerate) { + // Allow only setting incremental fee to control both + mempool_opts.min_relay_feerate = mempool_opts.incremental_relay_feerate; + LogPrintf("Increasing minrelaytxfee to %s to match incrementalrelayfee\n", mempool_opts.min_relay_feerate.ToString()); + } + + // Feerate used to define dust. Shouldn't be changed lightly as old + // implementations may inadvertently create non-standard transactions + if (argsman.IsArgSet("-dustrelayfee")) { + if (std::optional<CAmount> parsed = ParseMoney(argsman.GetArg("-dustrelayfee", ""))) { + mempool_opts.dust_relay_feerate = CFeeRate{parsed.value()}; + } else { + return AmountErrMsg("dustrelayfee", argsman.GetArg("-dustrelayfee", "")); + } + } + + mempool_opts.permit_bare_multisig = argsman.GetBoolArg("-permitbaremultisig", DEFAULT_PERMIT_BAREMULTISIG); + + if (argsman.GetBoolArg("-datacarrier", DEFAULT_ACCEPT_DATACARRIER)) { + mempool_opts.max_datacarrier_bytes = argsman.GetIntArg("-datacarriersize", MAX_OP_RETURN_RELAY); + } else { + mempool_opts.max_datacarrier_bytes = std::nullopt; + } + + mempool_opts.require_standard = !argsman.GetBoolArg("-acceptnonstdtxn", !chainparams.RequireStandard()); + if (!chainparams.IsTestChain() && !mempool_opts.require_standard) { + return strprintf(Untranslated("acceptnonstdtxn is not currently supported for %s chain"), chainparams.NetworkIDString()); + } + + mempool_opts.full_rbf = argsman.GetBoolArg("-mempoolfullrbf", mempool_opts.full_rbf); + + ApplyArgsManOptions(argsman, mempool_opts.limits); + + return std::nullopt; +} diff --git a/src/node/mempool_args.h b/src/node/mempool_args.h new file mode 100644 index 0000000000..52d8b4f265 --- /dev/null +++ b/src/node/mempool_args.h @@ -0,0 +1,27 @@ +// Copyright (c) 2022 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_NODE_MEMPOOL_ARGS_H +#define BITCOIN_NODE_MEMPOOL_ARGS_H + +#include <optional> + +class ArgsManager; +class CChainParams; +struct bilingual_str; +namespace kernel { +struct MemPoolOptions; +}; + +/** + * Overlay the options set in \p argsman on top of corresponding members in \p mempool_opts. + * Returns an error if one was encountered. + * + * @param[in] argsman The ArgsManager in which to check set options. + * @param[in,out] mempool_opts The MemPoolOptions to modify according to \p argsman. + */ +[[nodiscard]] std::optional<bilingual_str> ApplyArgsManOptions(const ArgsManager& argsman, const CChainParams& chainparams, kernel::MemPoolOptions& mempool_opts); + + +#endif // BITCOIN_NODE_MEMPOOL_ARGS_H diff --git a/src/node/mempool_persist_args.cpp b/src/node/mempool_persist_args.cpp new file mode 100644 index 0000000000..4e775869c6 --- /dev/null +++ b/src/node/mempool_persist_args.cpp @@ -0,0 +1,23 @@ +// Copyright (c) 2022 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include <node/mempool_persist_args.h> + +#include <fs.h> +#include <util/system.h> +#include <validation.h> + +namespace node { + +bool ShouldPersistMempool(const ArgsManager& argsman) +{ + return argsman.GetBoolArg("-persistmempool", DEFAULT_PERSIST_MEMPOOL); +} + +fs::path MempoolPath(const ArgsManager& argsman) +{ + return argsman.GetDataDirNet() / "mempool.dat"; +} + +} // namespace node diff --git a/src/node/mempool_persist_args.h b/src/node/mempool_persist_args.h new file mode 100644 index 0000000000..f719ec62ab --- /dev/null +++ b/src/node/mempool_persist_args.h @@ -0,0 +1,25 @@ +// Copyright (c) 2022 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_NODE_MEMPOOL_PERSIST_ARGS_H +#define BITCOIN_NODE_MEMPOOL_PERSIST_ARGS_H + +#include <fs.h> + +class ArgsManager; + +namespace node { + +/** + * Default for -persistmempool, indicating whether the node should attempt to + * automatically load the mempool on start and save to disk on shutdown + */ +static constexpr bool DEFAULT_PERSIST_MEMPOOL{true}; + +bool ShouldPersistMempool(const ArgsManager& argsman); +fs::path MempoolPath(const ArgsManager& argsman); + +} // namespace node + +#endif // BITCOIN_NODE_MEMPOOL_PERSIST_ARGS_H diff --git a/src/node/miner.cpp b/src/node/miner.cpp index 01db49d5cf..e11ec5b0f1 100644 --- a/src/node/miner.cpp +++ b/src/node/miner.cpp @@ -30,7 +30,7 @@ namespace node { int64_t UpdateTime(CBlockHeader* pblock, const Consensus::Params& consensusParams, const CBlockIndex* pindexPrev) { int64_t nOldTime = pblock->nTime; - int64_t nNewTime = std::max(pindexPrev->GetMedianTimePast() + 1, GetAdjustedTime()); + int64_t nNewTime{std::max<int64_t>(pindexPrev->GetMedianTimePast() + 1, TicksSinceEpoch<std::chrono::seconds>(GetAdjustedTime()))}; if (nOldTime < nNewTime) { pblock->nTime = nNewTime; @@ -62,7 +62,7 @@ BlockAssembler::Options::Options() nBlockMaxWeight = DEFAULT_BLOCK_MAX_WEIGHT; } -BlockAssembler::BlockAssembler(CChainState& chainstate, const CTxMemPool& mempool, const Options& options) +BlockAssembler::BlockAssembler(Chainstate& chainstate, const CTxMemPool* mempool, const Options& options) : chainparams{chainstate.m_chainman.GetParams()}, m_mempool(mempool), m_chainstate(chainstate) @@ -87,7 +87,7 @@ static BlockAssembler::Options DefaultOptions() return options; } -BlockAssembler::BlockAssembler(CChainState& chainstate, const CTxMemPool& mempool) +BlockAssembler::BlockAssembler(Chainstate& chainstate, const CTxMemPool* mempool) : BlockAssembler(chainstate, mempool, DefaultOptions()) {} void BlockAssembler::resetBlock() @@ -105,7 +105,7 @@ void BlockAssembler::resetBlock() std::unique_ptr<CBlockTemplate> BlockAssembler::CreateNewBlock(const CScript& scriptPubKeyIn) { - int64_t nTimeStart = GetTimeMicros(); + const auto time_start{SteadyClock::now()}; resetBlock(); @@ -121,7 +121,7 @@ std::unique_ptr<CBlockTemplate> BlockAssembler::CreateNewBlock(const CScript& sc pblocktemplate->vTxFees.push_back(-1); // updated at end pblocktemplate->vTxSigOpsCost.push_back(-1); // updated at end - LOCK2(cs_main, m_mempool.cs); + LOCK(::cs_main); CBlockIndex* pindexPrev = m_chainstate.m_chain.Tip(); assert(pindexPrev != nullptr); nHeight = pindexPrev->nHeight + 1; @@ -133,14 +133,17 @@ std::unique_ptr<CBlockTemplate> BlockAssembler::CreateNewBlock(const CScript& sc pblock->nVersion = gArgs.GetIntArg("-blockversion", pblock->nVersion); } - pblock->nTime = GetAdjustedTime(); + pblock->nTime = TicksSinceEpoch<std::chrono::seconds>(GetAdjustedTime()); m_lock_time_cutoff = pindexPrev->GetMedianTimePast(); int nPackagesSelected = 0; int nDescendantsUpdated = 0; - addPackageTxs(nPackagesSelected, nDescendantsUpdated); + if (m_mempool) { + LOCK(m_mempool->cs); + addPackageTxs(*m_mempool, nPackagesSelected, nDescendantsUpdated); + } - int64_t nTime1 = GetTimeMicros(); + const auto time_1{SteadyClock::now()}; m_last_block_num_txs = nBlockTx; m_last_block_weight = nBlockWeight; @@ -170,9 +173,12 @@ std::unique_ptr<CBlockTemplate> BlockAssembler::CreateNewBlock(const CScript& sc if (!TestBlockValidity(state, chainparams, m_chainstate, *pblock, pindexPrev, GetAdjustedTime, false, false)) { throw std::runtime_error(strprintf("%s: TestBlockValidity failed: %s", __func__, state.ToString())); } - int64_t nTime2 = GetTimeMicros(); + const auto time_2{SteadyClock::now()}; - LogPrint(BCLog::BENCH, "CreateNewBlock() packages: %.2fms (%d packages, %d updated descendants), validity: %.2fms (total %.2fms)\n", 0.001 * (nTime1 - nTimeStart), nPackagesSelected, nDescendantsUpdated, 0.001 * (nTime2 - nTime1), 0.001 * (nTime2 - nTimeStart)); + LogPrint(BCLog::BENCH, "CreateNewBlock() packages: %.2fms (%d packages, %d updated descendants), validity: %.2fms (total %.2fms)\n", + Ticks<MillisecondsDouble>(time_1 - time_start), nPackagesSelected, nDescendantsUpdated, + Ticks<MillisecondsDouble>(time_2 - time_1), + Ticks<MillisecondsDouble>(time_2 - time_start)); return std::move(pblocktemplate); } @@ -232,15 +238,19 @@ void BlockAssembler::AddToBlock(CTxMemPool::txiter iter) } } -int BlockAssembler::UpdatePackagesForAdded(const CTxMemPool::setEntries& alreadyAdded, - indexed_modified_transaction_set &mapModifiedTx) +/** Add descendants of given transactions to mapModifiedTx with ancestor + * state updated assuming given transactions are inBlock. Returns number + * of updated descendants. */ +static int UpdatePackagesForAdded(const CTxMemPool& mempool, + const CTxMemPool::setEntries& alreadyAdded, + indexed_modified_transaction_set& mapModifiedTx) EXCLUSIVE_LOCKS_REQUIRED(mempool.cs) { - AssertLockHeld(m_mempool.cs); + AssertLockHeld(mempool.cs); int nDescendantsUpdated = 0; for (CTxMemPool::txiter it : alreadyAdded) { CTxMemPool::setEntries descendants; - m_mempool.CalculateDescendants(it, descendants); + mempool.CalculateDescendants(it, descendants); // Insert all descendants (not yet in block) into the modified set for (CTxMemPool::txiter desc : descendants) { if (alreadyAdded.count(desc)) { @@ -250,35 +260,14 @@ int BlockAssembler::UpdatePackagesForAdded(const CTxMemPool::setEntries& already modtxiter mit = mapModifiedTx.find(desc); if (mit == mapModifiedTx.end()) { CTxMemPoolModifiedEntry modEntry(desc); - modEntry.nSizeWithAncestors -= it->GetTxSize(); - modEntry.nModFeesWithAncestors -= it->GetModifiedFee(); - modEntry.nSigOpCostWithAncestors -= it->GetSigOpCost(); - mapModifiedTx.insert(modEntry); - } else { - mapModifiedTx.modify(mit, update_for_parent_inclusion(it)); + mit = mapModifiedTx.insert(modEntry).first; } + mapModifiedTx.modify(mit, update_for_parent_inclusion(it)); } } return nDescendantsUpdated; } -// Skip entries in mapTx that are already in a block or are present -// in mapModifiedTx (which implies that the mapTx ancestor state is -// stale due to ancestor inclusion in the block) -// Also skip transactions that we've already failed to add. This can happen if -// we consider a transaction in mapModifiedTx and it fails: we can then -// potentially consider it again while walking mapTx. It's currently -// guaranteed to fail again, but as a belt-and-suspenders check we put it in -// failedTx and avoid re-evaluation, since the re-evaluation would be using -// cached size/sigops/fee values that are not actually correct. -bool BlockAssembler::SkipMapTxEntry(CTxMemPool::txiter it, indexed_modified_transaction_set& mapModifiedTx, CTxMemPool::setEntries& failedTx) -{ - AssertLockHeld(m_mempool.cs); - - assert(it != m_mempool.mapTx.end()); - return mapModifiedTx.count(it) || inBlock.count(it) || failedTx.count(it); -} - void BlockAssembler::SortForBlock(const CTxMemPool::setEntries& package, std::vector<CTxMemPool::txiter>& sortedEntries) { // Sort package by ancestor count @@ -300,9 +289,9 @@ void BlockAssembler::SortForBlock(const CTxMemPool::setEntries& package, std::ve // Each time through the loop, we compare the best transaction in // mapModifiedTxs with the next transaction in the mempool to decide what // transaction package to work on next. -void BlockAssembler::addPackageTxs(int& nPackagesSelected, int& nDescendantsUpdated) +void BlockAssembler::addPackageTxs(const CTxMemPool& mempool, int& nPackagesSelected, int& nDescendantsUpdated) { - AssertLockHeld(m_mempool.cs); + AssertLockHeld(mempool.cs); // mapModifiedTx will store sorted packages after they are modified // because some of their txs are already in the block @@ -310,7 +299,7 @@ void BlockAssembler::addPackageTxs(int& nPackagesSelected, int& nDescendantsUpda // Keep track of entries that failed inclusion, to avoid duplicate work CTxMemPool::setEntries failedTx; - CTxMemPool::indexed_transaction_set::index<ancestor_score>::type::iterator mi = m_mempool.mapTx.get<ancestor_score>().begin(); + CTxMemPool::indexed_transaction_set::index<ancestor_score>::type::iterator mi = mempool.mapTx.get<ancestor_score>().begin(); CTxMemPool::txiter iter; // Limit the number of attempts to add transactions to the block when it is @@ -319,12 +308,27 @@ void BlockAssembler::addPackageTxs(int& nPackagesSelected, int& nDescendantsUpda const int64_t MAX_CONSECUTIVE_FAILURES = 1000; int64_t nConsecutiveFailed = 0; - while (mi != m_mempool.mapTx.get<ancestor_score>().end() || !mapModifiedTx.empty()) { + while (mi != mempool.mapTx.get<ancestor_score>().end() || !mapModifiedTx.empty()) { // First try to find a new transaction in mapTx to evaluate. - if (mi != m_mempool.mapTx.get<ancestor_score>().end() && - SkipMapTxEntry(m_mempool.mapTx.project<0>(mi), mapModifiedTx, failedTx)) { - ++mi; - continue; + // + // Skip entries in mapTx that are already in a block or are present + // in mapModifiedTx (which implies that the mapTx ancestor state is + // stale due to ancestor inclusion in the block) + // Also skip transactions that we've already failed to add. This can happen if + // we consider a transaction in mapModifiedTx and it fails: we can then + // potentially consider it again while walking mapTx. It's currently + // guaranteed to fail again, but as a belt-and-suspenders check we put it in + // failedTx and avoid re-evaluation, since the re-evaluation would be using + // cached size/sigops/fee values that are not actually correct. + /** Return true if given transaction from mapTx has already been evaluated, + * or if the transaction's cached data in mapTx is incorrect. */ + if (mi != mempool.mapTx.get<ancestor_score>().end()) { + auto it = mempool.mapTx.project<0>(mi); + assert(it != mempool.mapTx.end()); + if (mapModifiedTx.count(it) || inBlock.count(it) || failedTx.count(it)) { + ++mi; + continue; + } } // Now that mi is not stale, determine which transaction to evaluate: @@ -332,13 +336,13 @@ void BlockAssembler::addPackageTxs(int& nPackagesSelected, int& nDescendantsUpda bool fUsingModified = false; modtxscoreiter modit = mapModifiedTx.get<ancestor_score>().begin(); - if (mi == m_mempool.mapTx.get<ancestor_score>().end()) { + if (mi == mempool.mapTx.get<ancestor_score>().end()) { // We're out of entries in mapTx; use the entry from mapModifiedTx iter = modit->iter; fUsingModified = true; } else { // Try to compare the mapTx entry to the mapModifiedTx entry - iter = m_mempool.mapTx.project<0>(mi); + iter = mempool.mapTx.project<0>(mi); if (modit != mapModifiedTx.get<ancestor_score>().end() && CompareTxMemPoolEntryByAncestorFee()(*modit, CTxMemPoolModifiedEntry(iter))) { // The best entry in mapModifiedTx has higher score @@ -391,9 +395,8 @@ void BlockAssembler::addPackageTxs(int& nPackagesSelected, int& nDescendantsUpda } CTxMemPool::setEntries ancestors; - uint64_t nNoLimit = std::numeric_limits<uint64_t>::max(); std::string dummy; - m_mempool.CalculateMemPoolAncestors(*iter, ancestors, nNoLimit, nNoLimit, nNoLimit, nNoLimit, dummy, false); + mempool.CalculateMemPoolAncestors(*iter, ancestors, CTxMemPool::Limits::NoLimits(), dummy, false); onlyUnconfirmed(ancestors); ancestors.insert(iter); @@ -423,7 +426,7 @@ void BlockAssembler::addPackageTxs(int& nPackagesSelected, int& nDescendantsUpda ++nPackagesSelected; // Update transactions that depend on each of these - nDescendantsUpdated += UpdatePackagesForAdded(ancestors, mapModifiedTx); + nDescendantsUpdated += UpdatePackagesForAdded(mempool, ancestors, mapModifiedTx); } } } // namespace node diff --git a/src/node/miner.h b/src/node/miner.h index 7cf8e3fb9e..7269ce1186 100644 --- a/src/node/miner.h +++ b/src/node/miner.h @@ -147,8 +147,8 @@ private: int64_t m_lock_time_cutoff; const CChainParams& chainparams; - const CTxMemPool& m_mempool; - CChainState& m_chainstate; + const CTxMemPool* const m_mempool; + Chainstate& m_chainstate; public: struct Options { @@ -157,8 +157,8 @@ public: CFeeRate blockMinFeeRate; }; - explicit BlockAssembler(CChainState& chainstate, const CTxMemPool& mempool); - explicit BlockAssembler(CChainState& chainstate, const CTxMemPool& mempool, const Options& options); + explicit BlockAssembler(Chainstate& chainstate, const CTxMemPool* mempool); + explicit BlockAssembler(Chainstate& chainstate, const CTxMemPool* mempool, const Options& options); /** Construct a new block template with coinbase to scriptPubKeyIn */ std::unique_ptr<CBlockTemplate> CreateNewBlock(const CScript& scriptPubKeyIn); @@ -177,7 +177,7 @@ private: /** Add transactions based on feerate including unconfirmed ancestors * Increments nPackagesSelected / nDescendantsUpdated with corresponding * statistics from the package selection (for logging statistics). */ - void addPackageTxs(int& nPackagesSelected, int& nDescendantsUpdated) EXCLUSIVE_LOCKS_REQUIRED(m_mempool.cs); + void addPackageTxs(const CTxMemPool& mempool, int& nPackagesSelected, int& nDescendantsUpdated) EXCLUSIVE_LOCKS_REQUIRED(mempool.cs); // helper functions for addPackageTxs() /** Remove confirmed (inBlock) entries from given set */ @@ -189,15 +189,8 @@ private: * These checks should always succeed, and they're here * only as an extra check in case of suboptimal node configuration */ bool TestPackageTransactions(const CTxMemPool::setEntries& package) const; - /** Return true if given transaction from mapTx has already been evaluated, - * or if the transaction's cached data in mapTx is incorrect. */ - bool SkipMapTxEntry(CTxMemPool::txiter it, indexed_modified_transaction_set& mapModifiedTx, CTxMemPool::setEntries& failedTx) EXCLUSIVE_LOCKS_REQUIRED(m_mempool.cs); /** Sort the package in an order that is valid to appear in a block */ void SortForBlock(const CTxMemPool::setEntries& package, std::vector<CTxMemPool::txiter>& sortedEntries); - /** Add descendants of given transactions to mapModifiedTx with ancestor - * state updated assuming given transactions are inBlock. Returns number - * of updated descendants. */ - int UpdatePackagesForAdded(const CTxMemPool::setEntries& alreadyAdded, indexed_modified_transaction_set& mapModifiedTx) EXCLUSIVE_LOCKS_REQUIRED(m_mempool.cs); }; int64_t UpdateTime(CBlockHeader* pblock, const Consensus::Params& consensusParams, const CBlockIndex* pindexPrev); diff --git a/src/node/psbt.cpp b/src/node/psbt.cpp index 5a932f435d..57162cd679 100644 --- a/src/node/psbt.cpp +++ b/src/node/psbt.cpp @@ -137,7 +137,7 @@ PSBTAnalysis AnalyzePSBT(PartiallySignedTransaction psbtx) if (success) { CTransaction ctx = CTransaction(mtx); - size_t size = GetVirtualTransactionSize(ctx, GetTransactionSigOpCost(ctx, view, STANDARD_SCRIPT_VERIFY_FLAGS)); + size_t size(GetVirtualTransactionSize(ctx, GetTransactionSigOpCost(ctx, view, STANDARD_SCRIPT_VERIFY_FLAGS), ::nBytesPerSigOp)); result.estimated_vsize = size; // Estimate fee rate CFeeRate feerate(fee, size); diff --git a/src/node/utxo_snapshot.h b/src/node/utxo_snapshot.h index 401d4baaeb..9dd6f06997 100644 --- a/src/node/utxo_snapshot.h +++ b/src/node/utxo_snapshot.h @@ -11,7 +11,7 @@ namespace node { //! Metadata describing a serialized version of a UTXO set from which an -//! assumeutxo CChainState can be constructed. +//! assumeutxo Chainstate can be constructed. class SnapshotMetadata { public: diff --git a/src/node/validation_cache_args.cpp b/src/node/validation_cache_args.cpp new file mode 100644 index 0000000000..5ea0a8ca0a --- /dev/null +++ b/src/node/validation_cache_args.cpp @@ -0,0 +1,34 @@ +// Copyright (c) 2022 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include <node/validation_cache_args.h> + +#include <kernel/validation_cache_sizes.h> + +#include <util/system.h> + +#include <algorithm> +#include <cstddef> +#include <cstdint> +#include <memory> +#include <optional> + +using kernel::ValidationCacheSizes; + +namespace node { +void ApplyArgsManOptions(const ArgsManager& argsman, ValidationCacheSizes& cache_sizes) +{ + if (auto max_size = argsman.GetIntArg("-maxsigcachesize")) { + // 1. When supplied with a max_size of 0, both InitSignatureCache and + // InitScriptExecutionCache create the minimum possible cache (2 + // elements). Therefore, we can use 0 as a floor here. + // 2. Multiply first, divide after to avoid integer truncation. + size_t clamped_size_each = std::max<int64_t>(*max_size, 0) * (1 << 20) / 2; + cache_sizes = { + .signature_cache_bytes = clamped_size_each, + .script_execution_cache_bytes = clamped_size_each, + }; + } +} +} // namespace node diff --git a/src/node/validation_cache_args.h b/src/node/validation_cache_args.h new file mode 100644 index 0000000000..f447c13b49 --- /dev/null +++ b/src/node/validation_cache_args.h @@ -0,0 +1,17 @@ +// Copyright (c) 2022 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_NODE_VALIDATION_CACHE_ARGS_H +#define BITCOIN_NODE_VALIDATION_CACHE_ARGS_H + +class ArgsManager; +namespace kernel { +struct ValidationCacheSizes; +}; + +namespace node { +void ApplyArgsManOptions(const ArgsManager& argsman, kernel::ValidationCacheSizes& cache_sizes); +} // namespace node + +#endif // BITCOIN_NODE_VALIDATION_CACHE_ARGS_H diff --git a/src/noui.cpp b/src/noui.cpp index 6fe5c5638c..54cb5f3cbf 100644 --- a/src/noui.cpp +++ b/src/noui.cpp @@ -6,7 +6,7 @@ #include <noui.h> #include <logging.h> -#include <node/ui_interface.h> +#include <node/interface_ui.h> #include <util/translation.h> #include <string> diff --git a/src/outputtype.cpp b/src/outputtype.cpp index 19366295e6..9ab2902256 100644 --- a/src/outputtype.cpp +++ b/src/outputtype.cpp @@ -20,6 +20,7 @@ static const std::string OUTPUT_TYPE_STRING_LEGACY = "legacy"; static const std::string OUTPUT_TYPE_STRING_P2SH_SEGWIT = "p2sh-segwit"; static const std::string OUTPUT_TYPE_STRING_BECH32 = "bech32"; static const std::string OUTPUT_TYPE_STRING_BECH32M = "bech32m"; +static const std::string OUTPUT_TYPE_STRING_UNKNOWN = "unknown"; std::optional<OutputType> ParseOutputType(const std::string& type) { @@ -31,6 +32,8 @@ std::optional<OutputType> ParseOutputType(const std::string& type) return OutputType::BECH32; } else if (type == OUTPUT_TYPE_STRING_BECH32M) { return OutputType::BECH32M; + } else if (type == OUTPUT_TYPE_STRING_UNKNOWN) { + return OutputType::UNKNOWN; } return std::nullopt; } @@ -42,6 +45,7 @@ const std::string& FormatOutputType(OutputType type) case OutputType::P2SH_SEGWIT: return OUTPUT_TYPE_STRING_P2SH_SEGWIT; case OutputType::BECH32: return OUTPUT_TYPE_STRING_BECH32; case OutputType::BECH32M: return OUTPUT_TYPE_STRING_BECH32M; + case OutputType::UNKNOWN: return OUTPUT_TYPE_STRING_UNKNOWN; } // no default case, so the compiler can warn about missing cases assert(false); } @@ -61,7 +65,8 @@ CTxDestination GetDestinationForKey(const CPubKey& key, OutputType type) return witdest; } } - case OutputType::BECH32M: {} // This function should never be used with BECH32M, so let it assert + case OutputType::BECH32M: + case OutputType::UNKNOWN: {} // This function should never be used with BECH32M or UNKNOWN, so let it assert } // no default case, so the compiler can warn about missing cases assert(false); } @@ -91,8 +96,6 @@ CTxDestination AddAndGetDestinationForScript(FillableSigningProvider& keystore, case OutputType::BECH32: { CTxDestination witdest = WitnessV0ScriptHash(script); CScript witprog = GetScriptForDestination(witdest); - // Check if the resulting program is solvable (i.e. doesn't use an uncompressed key) - if (!IsSolvable(keystore, witprog)) return ScriptHash(script); // Add the redeemscript, so that P2WSH and P2SH-P2WSH outputs are recognized as ours. keystore.AddCScript(witprog); if (type == OutputType::BECH32) { @@ -101,7 +104,8 @@ CTxDestination AddAndGetDestinationForScript(FillableSigningProvider& keystore, return ScriptHash(witprog); } } - case OutputType::BECH32M: {} // This function should not be used for BECH32M, so let it assert + case OutputType::BECH32M: + case OutputType::UNKNOWN: {} // This function should not be used for BECH32M or UNKNOWN, so let it assert } // no default case, so the compiler can warn about missing cases assert(false); } diff --git a/src/outputtype.h b/src/outputtype.h index 6b4e695760..c59262591b 100644 --- a/src/outputtype.h +++ b/src/outputtype.h @@ -19,6 +19,7 @@ enum class OutputType { P2SH_SEGWIT, BECH32, BECH32M, + UNKNOWN, }; static constexpr auto OUTPUT_TYPES = std::array{ diff --git a/src/policy/fees.cpp b/src/policy/fees.cpp index b39632364f..22defb91a9 100644 --- a/src/policy/fees.cpp +++ b/src/policy/fees.cpp @@ -31,8 +31,6 @@ #include <stdexcept> #include <utility> -static const char* FEE_ESTIMATES_FILENAME = "fee_estimates.dat"; - static constexpr double INF_FEERATE = 1e99; std::string StringForFeeEstimateHorizon(FeeEstimateHorizon horizon) @@ -163,13 +161,13 @@ public: unsigned int GetMaxConfirms() const { return scale * confAvg.size(); } /** Write state of estimation data to a file*/ - void Write(CAutoFile& fileout) const; + void Write(AutoFile& fileout) const; /** * Read saved state of estimation data from a file and replace all internal data structures and * variables with this state. */ - void Read(CAutoFile& filein, int nFileVersion, size_t numBuckets); + void Read(AutoFile& filein, int nFileVersion, size_t numBuckets); }; @@ -392,7 +390,7 @@ double TxConfirmStats::EstimateMedianVal(int confTarget, double sufficientTxVal, return median; } -void TxConfirmStats::Write(CAutoFile& fileout) const +void TxConfirmStats::Write(AutoFile& fileout) const { fileout << Using<EncodedDoubleFormatter>(decay); fileout << scale; @@ -402,7 +400,7 @@ void TxConfirmStats::Write(CAutoFile& fileout) const fileout << Using<VectorFormatter<VectorFormatter<EncodedDoubleFormatter>>>(failAvg); } -void TxConfirmStats::Read(CAutoFile& filein, int nFileVersion, size_t numBuckets) +void TxConfirmStats::Read(AutoFile& filein, int nFileVersion, size_t numBuckets) { // Read data file and do some very basic sanity checking // buckets and bucketMap are not updated yet, so don't access them @@ -529,8 +527,8 @@ bool CBlockPolicyEstimator::_removeTx(const uint256& hash, bool inBlock) } } -CBlockPolicyEstimator::CBlockPolicyEstimator() - : nBestSeenHeight(0), firstRecordedHeight(0), historicalFirst(0), historicalBest(0), trackedTxs(0), untrackedTxs(0) +CBlockPolicyEstimator::CBlockPolicyEstimator(const fs::path& estimation_filepath) + : m_estimation_filepath{estimation_filepath}, nBestSeenHeight{0}, firstRecordedHeight{0}, historicalFirst{0}, historicalBest{0}, trackedTxs{0}, untrackedTxs{0} { static_assert(MIN_BUCKET_FEERATE > 0, "Min feerate must be nonzero"); size_t bucketIndex = 0; @@ -548,10 +546,9 @@ CBlockPolicyEstimator::CBlockPolicyEstimator() longStats = std::unique_ptr<TxConfirmStats>(new TxConfirmStats(buckets, bucketMap, LONG_BLOCK_PERIODS, LONG_DECAY, LONG_SCALE)); // If the fee estimation file is present, read recorded estimations - fs::path est_filepath = gArgs.GetDataDirNet() / FEE_ESTIMATES_FILENAME; - CAutoFile est_file(fsbridge::fopen(est_filepath, "rb"), SER_DISK, CLIENT_VERSION); + AutoFile est_file{fsbridge::fopen(m_estimation_filepath, "rb")}; if (est_file.IsNull() || !Read(est_file)) { - LogPrintf("Failed to read fee estimates from %s. Continue anyway.\n", fs::PathToString(est_filepath)); + LogPrintf("Failed to read fee estimates from %s. Continue anyway.\n", fs::PathToString(m_estimation_filepath)); } } @@ -907,14 +904,13 @@ CFeeRate CBlockPolicyEstimator::estimateSmartFee(int confTarget, FeeCalculation void CBlockPolicyEstimator::Flush() { FlushUnconfirmed(); - fs::path est_filepath = gArgs.GetDataDirNet() / FEE_ESTIMATES_FILENAME; - CAutoFile est_file(fsbridge::fopen(est_filepath, "wb"), SER_DISK, CLIENT_VERSION); + AutoFile est_file{fsbridge::fopen(m_estimation_filepath, "wb")}; if (est_file.IsNull() || !Write(est_file)) { - LogPrintf("Failed to write fee estimates to %s. Continue anyway.\n", fs::PathToString(est_filepath)); + LogPrintf("Failed to write fee estimates to %s. Continue anyway.\n", fs::PathToString(m_estimation_filepath)); } } -bool CBlockPolicyEstimator::Write(CAutoFile& fileout) const +bool CBlockPolicyEstimator::Write(AutoFile& fileout) const { try { LOCK(m_cs_fee_estimator); @@ -939,7 +935,7 @@ bool CBlockPolicyEstimator::Write(CAutoFile& fileout) const return true; } -bool CBlockPolicyEstimator::Read(CAutoFile& filein) +bool CBlockPolicyEstimator::Read(AutoFile& filein) { try { LOCK(m_cs_fee_estimator); @@ -1001,8 +997,9 @@ bool CBlockPolicyEstimator::Read(CAutoFile& filein) return true; } -void CBlockPolicyEstimator::FlushUnconfirmed() { - int64_t startclear = GetTimeMicros(); +void CBlockPolicyEstimator::FlushUnconfirmed() +{ + const auto startclear{SteadyClock::now()}; LOCK(m_cs_fee_estimator); size_t num_entries = mapMemPoolTxs.size(); // Remove every entry in mapMemPoolTxs @@ -1010,8 +1007,8 @@ void CBlockPolicyEstimator::FlushUnconfirmed() { auto mi = mapMemPoolTxs.begin(); _removeTx(mi->first, false); // this calls erase() on mapMemPoolTxs } - int64_t endclear = GetTimeMicros(); - LogPrint(BCLog::ESTIMATEFEE, "Recorded %u unconfirmed txs from mempool in %gs\n", num_entries, (endclear - startclear)*0.000001); + const auto endclear{SteadyClock::now()}; + LogPrint(BCLog::ESTIMATEFEE, "Recorded %u unconfirmed txs from mempool in %gs\n", num_entries, Ticks<SecondsDouble>(endclear - startclear)); } FeeFilterRounder::FeeFilterRounder(const CFeeRate& minIncrementalFee) diff --git a/src/policy/fees.h b/src/policy/fees.h index dea1e1d31b..e4628bf853 100644 --- a/src/policy/fees.h +++ b/src/policy/fees.h @@ -6,6 +6,7 @@ #define BITCOIN_POLICY_FEES_H #include <consensus/amount.h> +#include <fs.h> #include <policy/feerate.h> #include <random.h> #include <sync.h> @@ -19,7 +20,7 @@ #include <string> #include <vector> -class CAutoFile; +class AutoFile; class CTxMemPoolEntry; class TxConfirmStats; @@ -179,9 +180,10 @@ private: */ static constexpr double FEE_SPACING = 1.05; + const fs::path m_estimation_filepath; public: /** Create new BlockPolicyEstimator and initialize stats tracking classes with default values */ - CBlockPolicyEstimator(); + CBlockPolicyEstimator(const fs::path& estimation_filepath); ~CBlockPolicyEstimator(); /** Process all the transactions that have been included in a block */ @@ -218,11 +220,11 @@ public: EXCLUSIVE_LOCKS_REQUIRED(!m_cs_fee_estimator); /** Write estimation data to a file */ - bool Write(CAutoFile& fileout) const + bool Write(AutoFile& fileout) const EXCLUSIVE_LOCKS_REQUIRED(!m_cs_fee_estimator); /** Read estimation data from a file */ - bool Read(CAutoFile& filein) + bool Read(AutoFile& filein) EXCLUSIVE_LOCKS_REQUIRED(!m_cs_fee_estimator); /** Empty mempool transactions on shutdown to record failure to confirm for txs still in mempool */ diff --git a/src/policy/fees_args.cpp b/src/policy/fees_args.cpp new file mode 100644 index 0000000000..a3531153b5 --- /dev/null +++ b/src/policy/fees_args.cpp @@ -0,0 +1,12 @@ +#include <policy/fees_args.h> + +#include <util/system.h> + +namespace { +const char* FEE_ESTIMATES_FILENAME = "fee_estimates.dat"; +} // namespace + +fs::path FeeestPath(const ArgsManager& argsman) +{ + return argsman.GetDataDirNet() / FEE_ESTIMATES_FILENAME; +} diff --git a/src/policy/fees_args.h b/src/policy/fees_args.h new file mode 100644 index 0000000000..6b65ce0aa9 --- /dev/null +++ b/src/policy/fees_args.h @@ -0,0 +1,15 @@ +// Copyright (c) 2022 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_POLICY_FEES_ARGS_H +#define BITCOIN_POLICY_FEES_ARGS_H + +#include <fs.h> + +class ArgsManager; + +/** @return The fee estimates data file path. */ +fs::path FeeestPath(const ArgsManager& argsman); + +#endif // BITCOIN_POLICY_FEES_ARGS_H diff --git a/src/policy/packages.h b/src/policy/packages.h index ba6a3a9a06..36c70e9e66 100644 --- a/src/policy/packages.h +++ b/src/policy/packages.h @@ -19,6 +19,15 @@ static constexpr uint32_t MAX_PACKAGE_COUNT{25}; static constexpr uint32_t MAX_PACKAGE_SIZE{101}; static_assert(MAX_PACKAGE_SIZE * WITNESS_SCALE_FACTOR * 1000 >= MAX_STANDARD_TX_WEIGHT); +// If a package is submitted, it must be within the mempool's ancestor/descendant limits. Since a +// submitted package must be child-with-unconfirmed-parents (all of the transactions are an ancestor +// of the child), package limits are ultimately bounded by mempool package limits. Ensure that the +// defaults reflect this constraint. +static_assert(DEFAULT_DESCENDANT_LIMIT >= MAX_PACKAGE_COUNT); +static_assert(DEFAULT_ANCESTOR_LIMIT >= MAX_PACKAGE_COUNT); +static_assert(DEFAULT_ANCESTOR_SIZE_LIMIT_KVB >= MAX_PACKAGE_SIZE); +static_assert(DEFAULT_DESCENDANT_SIZE_LIMIT_KVB >= MAX_PACKAGE_SIZE); + /** A "reason" why a package was invalid. It may be that one or more of the included * transactions is invalid or the package itself violates our rules. * We don't distinguish between consensus and policy violations right now. diff --git a/src/policy/policy.cpp b/src/policy/policy.cpp index f6452266b7..5086542865 100644 --- a/src/policy/policy.cpp +++ b/src/policy/policy.cpp @@ -67,7 +67,7 @@ bool IsDust(const CTxOut& txout, const CFeeRate& dustRelayFeeIn) return (txout.nValue < GetDustThreshold(txout, dustRelayFeeIn)); } -bool IsStandard(const CScript& scriptPubKey, TxoutType& whichType) +bool IsStandard(const CScript& scriptPubKey, const std::optional<unsigned>& max_datacarrier_bytes, TxoutType& whichType) { std::vector<std::vector<unsigned char> > vSolutions; whichType = Solver(scriptPubKey, vSolutions); @@ -82,15 +82,16 @@ bool IsStandard(const CScript& scriptPubKey, TxoutType& whichType) return false; if (m < 1 || m > n) return false; - } else if (whichType == TxoutType::NULL_DATA && - (!fAcceptDatacarrier || scriptPubKey.size() > nMaxDatacarrierBytes)) { - return false; + } else if (whichType == TxoutType::NULL_DATA) { + if (!max_datacarrier_bytes || scriptPubKey.size() > *max_datacarrier_bytes) { + return false; + } } return true; } -bool IsStandardTx(const CTransaction& tx, bool permit_bare_multisig, const CFeeRate& dust_relay_fee, std::string& reason) +bool IsStandardTx(const CTransaction& tx, const std::optional<unsigned>& max_datacarrier_bytes, bool permit_bare_multisig, const CFeeRate& dust_relay_fee, std::string& reason) { if (tx.nVersion > TX_MAX_STANDARD_VERSION || tx.nVersion < 1) { reason = "version"; @@ -130,7 +131,7 @@ bool IsStandardTx(const CTransaction& tx, bool permit_bare_multisig, const CFeeR unsigned int nDataOut = 0; TxoutType whichType; for (const CTxOut& txout : tx.vout) { - if (!::IsStandard(txout.scriptPubKey, whichType)) { + if (!::IsStandard(txout.scriptPubKey, max_datacarrier_bytes, whichType)) { reason = "scriptpubkey"; return false; } diff --git a/src/policy/policy.h b/src/policy/policy.h index 94f9623b8a..29764ea2d9 100644 --- a/src/policy/policy.h +++ b/src/policy/policy.h @@ -20,49 +20,61 @@ class CFeeRate; class CScript; /** Default for -blockmaxweight, which controls the range of block weights the mining code will create **/ -static const unsigned int DEFAULT_BLOCK_MAX_WEIGHT = MAX_BLOCK_WEIGHT - 4000; +static constexpr unsigned int DEFAULT_BLOCK_MAX_WEIGHT{MAX_BLOCK_WEIGHT - 4000}; /** Default for -blockmintxfee, which sets the minimum feerate for a transaction in blocks created by mining code **/ -static const unsigned int DEFAULT_BLOCK_MIN_TX_FEE = 1000; +static constexpr unsigned int DEFAULT_BLOCK_MIN_TX_FEE{1000}; /** The maximum weight for transactions we're willing to relay/mine */ -static const unsigned int MAX_STANDARD_TX_WEIGHT = 400000; +static constexpr unsigned int MAX_STANDARD_TX_WEIGHT{400000}; /** The minimum non-witness size for transactions we're willing to relay/mine (1 segwit input + 1 P2WPKH output = 82 bytes) */ -static const unsigned int MIN_STANDARD_TX_NONWITNESS_SIZE = 82; +static constexpr unsigned int MIN_STANDARD_TX_NONWITNESS_SIZE{82}; /** Maximum number of signature check operations in an IsStandard() P2SH script */ -static const unsigned int MAX_P2SH_SIGOPS = 15; +static constexpr unsigned int MAX_P2SH_SIGOPS{15}; /** The maximum number of sigops we're willing to relay/mine in a single tx */ -static const unsigned int MAX_STANDARD_TX_SIGOPS_COST = MAX_BLOCK_SIGOPS_COST/5; -/** Default for -maxmempool, maximum megabytes of mempool memory usage */ -static const unsigned int DEFAULT_MAX_MEMPOOL_SIZE = 300; -/** Default for -incrementalrelayfee, which sets the minimum feerate increase for mempool limiting or BIP 125 replacement **/ -static const unsigned int DEFAULT_INCREMENTAL_RELAY_FEE = 1000; +static constexpr unsigned int MAX_STANDARD_TX_SIGOPS_COST{MAX_BLOCK_SIGOPS_COST/5}; +/** Default for -incrementalrelayfee, which sets the minimum feerate increase for mempool limiting or replacement **/ +static constexpr unsigned int DEFAULT_INCREMENTAL_RELAY_FEE{1000}; /** Default for -bytespersigop */ -static const unsigned int DEFAULT_BYTES_PER_SIGOP = 20; +static constexpr unsigned int DEFAULT_BYTES_PER_SIGOP{20}; /** Default for -permitbaremultisig */ -static const bool DEFAULT_PERMIT_BAREMULTISIG = true; +static constexpr bool DEFAULT_PERMIT_BAREMULTISIG{true}; /** The maximum number of witness stack items in a standard P2WSH script */ -static const unsigned int MAX_STANDARD_P2WSH_STACK_ITEMS = 100; +static constexpr unsigned int MAX_STANDARD_P2WSH_STACK_ITEMS{100}; /** The maximum size in bytes of each witness stack item in a standard P2WSH script */ -static const unsigned int MAX_STANDARD_P2WSH_STACK_ITEM_SIZE = 80; +static constexpr unsigned int MAX_STANDARD_P2WSH_STACK_ITEM_SIZE{80}; /** The maximum size in bytes of each witness stack item in a standard BIP 342 script (Taproot, leaf version 0xc0) */ -static const unsigned int MAX_STANDARD_TAPSCRIPT_STACK_ITEM_SIZE = 80; +static constexpr unsigned int MAX_STANDARD_TAPSCRIPT_STACK_ITEM_SIZE{80}; /** The maximum size in bytes of a standard witnessScript */ -static const unsigned int MAX_STANDARD_P2WSH_SCRIPT_SIZE = 3600; +static constexpr unsigned int MAX_STANDARD_P2WSH_SCRIPT_SIZE{3600}; /** The maximum size of a standard ScriptSig */ -static const unsigned int MAX_STANDARD_SCRIPTSIG_SIZE = 1650; -/** Min feerate for defining dust. Historically this has been based on the - * minRelayTxFee, however changing the dust limit changes which transactions are +static constexpr unsigned int MAX_STANDARD_SCRIPTSIG_SIZE{1650}; +/** Min feerate for defining dust. + * Changing the dust limit changes which transactions are * standard and should be done with care and ideally rarely. It makes sense to * only increase the dust limit after prior releases were already not creating * outputs below the new threshold */ -static const unsigned int DUST_RELAY_TX_FEE = 3000; +static constexpr unsigned int DUST_RELAY_TX_FEE{3000}; /** Default for -minrelaytxfee, minimum relay fee for transactions */ -static const unsigned int DEFAULT_MIN_RELAY_TX_FEE = 1000; +static constexpr unsigned int DEFAULT_MIN_RELAY_TX_FEE{1000}; +/** Default for -limitancestorcount, max number of in-mempool ancestors */ +static constexpr unsigned int DEFAULT_ANCESTOR_LIMIT{25}; +/** Default for -limitancestorsize, maximum kilobytes of tx + all in-mempool ancestors */ +static constexpr unsigned int DEFAULT_ANCESTOR_SIZE_LIMIT_KVB{101}; +/** Default for -limitdescendantcount, max number of in-mempool descendants */ +static constexpr unsigned int DEFAULT_DESCENDANT_LIMIT{25}; +/** Default for -limitdescendantsize, maximum kilobytes of in-mempool descendants */ +static constexpr unsigned int DEFAULT_DESCENDANT_SIZE_LIMIT_KVB{101}; +/** + * An extra transaction can be added to a package, as long as it only has one + * ancestor and is no larger than this. Not really any reason to make this + * configurable as it doesn't materially change DoS parameters. + */ +static constexpr unsigned int EXTRA_DESCENDANT_TX_SIZE_LIMIT{10000}; /** * Standard script verification flags that standard transactions will comply * with. However scripts violating these flags may still be present in valid * blocks and we must accept those blocks. */ -static constexpr unsigned int STANDARD_SCRIPT_VERIFY_FLAGS = MANDATORY_SCRIPT_VERIFY_FLAGS | +static constexpr unsigned int STANDARD_SCRIPT_VERIFY_FLAGS{MANDATORY_SCRIPT_VERIFY_FLAGS | SCRIPT_VERIFY_DERSIG | SCRIPT_VERIFY_STRICTENC | SCRIPT_VERIFY_MINIMALDATA | @@ -81,20 +93,19 @@ static constexpr unsigned int STANDARD_SCRIPT_VERIFY_FLAGS = MANDATORY_SCRIPT_VE SCRIPT_VERIFY_TAPROOT | SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_TAPROOT_VERSION | SCRIPT_VERIFY_DISCOURAGE_OP_SUCCESS | - SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_PUBKEYTYPE; + SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_PUBKEYTYPE}; /** For convenience, standard but not mandatory verify flags. */ -static constexpr unsigned int STANDARD_NOT_MANDATORY_VERIFY_FLAGS = STANDARD_SCRIPT_VERIFY_FLAGS & ~MANDATORY_SCRIPT_VERIFY_FLAGS; +static constexpr unsigned int STANDARD_NOT_MANDATORY_VERIFY_FLAGS{STANDARD_SCRIPT_VERIFY_FLAGS & ~MANDATORY_SCRIPT_VERIFY_FLAGS}; /** Used as the flags parameter to sequence and nLocktime checks in non-consensus code. */ -static constexpr unsigned int STANDARD_LOCKTIME_VERIFY_FLAGS = LOCKTIME_VERIFY_SEQUENCE | - LOCKTIME_MEDIAN_TIME_PAST; +static constexpr unsigned int STANDARD_LOCKTIME_VERIFY_FLAGS{LOCKTIME_VERIFY_SEQUENCE}; CAmount GetDustThreshold(const CTxOut& txout, const CFeeRate& dustRelayFee); bool IsDust(const CTxOut& txout, const CFeeRate& dustRelayFee); -bool IsStandard(const CScript& scriptPubKey, TxoutType& whichType); +bool IsStandard(const CScript& scriptPubKey, const std::optional<unsigned>& max_datacarrier_bytes, TxoutType& whichType); // Changing the default transaction version requires a two step process: first @@ -106,7 +117,7 @@ static constexpr decltype(CTransaction::nVersion) TX_MAX_STANDARD_VERSION{2}; * Check for standard transaction types * @return True if all outputs (scriptPubKeys) use only standard transaction forms */ -bool IsStandardTx(const CTransaction& tx, bool permit_bare_multisig, const CFeeRate& dust_relay_fee, std::string& reason); +bool IsStandardTx(const CTransaction& tx, const std::optional<unsigned>& max_datacarrier_bytes, bool permit_bare_multisig, const CFeeRate& dust_relay_fee, std::string& reason); /** * Check for standard transaction types * @param[in] mapInputs Map of previous transactions that have outputs we're spending diff --git a/src/policy/rbf.cpp b/src/policy/rbf.cpp index e25f5c7c5b..55f47f485b 100644 --- a/src/policy/rbf.cpp +++ b/src/policy/rbf.cpp @@ -36,10 +36,9 @@ RBFTransactionState IsRBFOptIn(const CTransaction& tx, const CTxMemPool& pool) // If all the inputs have nSequence >= maxint-1, it still might be // signaled for RBF if any unconfirmed parents have signaled. - uint64_t noLimit = std::numeric_limits<uint64_t>::max(); std::string dummy; CTxMemPoolEntry entry = *pool.mapTx.find(tx.GetHash()); - pool.CalculateMemPoolAncestors(entry, ancestors, noLimit, noLimit, noLimit, noLimit, dummy, false); + pool.CalculateMemPoolAncestors(entry, ancestors, CTxMemPool::Limits::NoLimits(), dummy, false); for (CTxMemPool::txiter it : ancestors) { if (SignalsOptInRBF(it->GetTx())) { @@ -65,15 +64,15 @@ std::optional<std::string> GetEntriesForConflicts(const CTransaction& tx, uint64_t nConflictingCount = 0; for (const auto& mi : iters_conflicting) { nConflictingCount += mi->GetCountWithDescendants(); - // BIP125 Rule #5: don't consider replacing more than MAX_BIP125_REPLACEMENT_CANDIDATES + // Rule #5: don't consider replacing more than MAX_REPLACEMENT_CANDIDATES // entries from the mempool. This potentially overestimates the number of actual // descendants (i.e. if multiple conflicts share a descendant, it will be counted multiple // times), but we just want to be conservative to avoid doing too much work. - if (nConflictingCount > MAX_BIP125_REPLACEMENT_CANDIDATES) { + if (nConflictingCount > MAX_REPLACEMENT_CANDIDATES) { return strprintf("rejecting replacement %s; too many potential replacements (%d > %d)\n", txid.ToString(), nConflictingCount, - MAX_BIP125_REPLACEMENT_CANDIDATES); + MAX_REPLACEMENT_CANDIDATES); } } // Calculate the set of all transactions that would have to be evicted. @@ -96,7 +95,7 @@ std::optional<std::string> HasNoNewUnconfirmed(const CTransaction& tx, } for (unsigned int j = 0; j < tx.vin.size(); j++) { - // BIP125 Rule #2: We don't want to accept replacements that require low feerate junk to be + // Rule #2: We don't want to accept replacements that require low feerate junk to be // mined first. Ideally we'd keep track of the ancestor feerates and make the decision // based on that, but for now requiring all new inputs to be confirmed works. // @@ -162,7 +161,7 @@ std::optional<std::string> PaysForRBF(CAmount original_fees, CFeeRate relay_fee, const uint256& txid) { - // BIP125 Rule #3: The replacement fees must be greater than or equal to fees of the + // Rule #3: The replacement fees must be greater than or equal to fees of the // transactions it replaces, otherwise the bandwidth used by those conflicting transactions // would not be paid for. if (replacement_fees < original_fees) { @@ -170,7 +169,7 @@ std::optional<std::string> PaysForRBF(CAmount original_fees, txid.ToString(), FormatMoney(replacement_fees), FormatMoney(original_fees)); } - // BIP125 Rule #4: The new transaction must pay for its own bandwidth. Otherwise, we have a DoS + // Rule #4: The new transaction must pay for its own bandwidth. Otherwise, we have a DoS // vector where attackers can cause a transaction to be replaced (and relayed) repeatedly by // increasing the fee by tiny amounts. CAmount additional_fees = replacement_fees - original_fees; diff --git a/src/policy/rbf.h b/src/policy/rbf.h index 07f68c8fd4..28c4e4bf9b 100644 --- a/src/policy/rbf.h +++ b/src/policy/rbf.h @@ -19,9 +19,9 @@ class CFeeRate; class uint256; -/** Maximum number of transactions that can be replaced by BIP125 RBF (Rule #5). This includes all +/** Maximum number of transactions that can be replaced by RBF (Rule #5). This includes all * mempool conflicts and their descendants. */ -static constexpr uint32_t MAX_BIP125_REPLACEMENT_CANDIDATES{100}; +static constexpr uint32_t MAX_REPLACEMENT_CANDIDATES{100}; /** The rbf state of unconfirmed transactions */ enum class RBFTransactionState { @@ -47,24 +47,25 @@ enum class RBFTransactionState { RBFTransactionState IsRBFOptIn(const CTransaction& tx, const CTxMemPool& pool) EXCLUSIVE_LOCKS_REQUIRED(pool.cs); RBFTransactionState IsRBFOptInEmptyMempool(const CTransaction& tx); -/** Get all descendants of iters_conflicting. Also enforce BIP125 Rule #5, "The number of original - * transactions to be replaced and their descendant transactions which will be evicted from the - * mempool must not exceed a total of 100 transactions." Quit as early as possible. There cannot be - * more than MAX_BIP125_REPLACEMENT_CANDIDATES potential entries. +/** Get all descendants of iters_conflicting. Checks that there are no more than + * MAX_REPLACEMENT_CANDIDATES potential entries. May overestimate if the entries in + * iters_conflicting have overlapping descendants. * @param[in] iters_conflicting The set of iterators to mempool entries. * @param[out] all_conflicts Populated with all the mempool entries that would be replaced, - * which includes descendants of iters_conflicting. Not cleared at - * the start; any existing mempool entries will remain in the set. - * @returns an error message if Rule #5 is broken, otherwise a std::nullopt. + * which includes iters_conflicting and all entries' descendants. + * Not cleared at the start; any existing mempool entries will + * remain in the set. + * @returns an error message if MAX_REPLACEMENT_CANDIDATES may be exceeded, otherwise a std::nullopt. */ std::optional<std::string> GetEntriesForConflicts(const CTransaction& tx, CTxMemPool& pool, const CTxMemPool::setEntries& iters_conflicting, CTxMemPool::setEntries& all_conflicts) EXCLUSIVE_LOCKS_REQUIRED(pool.cs); -/** BIP125 Rule #2: "The replacement transaction may only include an unconfirmed input if that input - * was included in one of the original transactions." - * @returns error message if Rule #2 is broken, otherwise std::nullopt. */ +/** The replacement transaction may only include an unconfirmed input if that input was included in + * one of the original transactions. + * @returns error message if tx spends unconfirmed inputs not also spent by iters_conflicting, + * otherwise std::nullopt. */ std::optional<std::string> HasNoNewUnconfirmed(const CTransaction& tx, const CTxMemPool& pool, const CTxMemPool::setEntries& iters_conflicting) EXCLUSIVE_LOCKS_REQUIRED(pool.cs); @@ -90,9 +91,8 @@ std::optional<std::string> EntriesAndTxidsDisjoint(const CTxMemPool::setEntries& std::optional<std::string> PaysMoreThanConflicts(const CTxMemPool::setEntries& iters_conflicting, CFeeRate replacement_feerate, const uint256& txid); -/** Enforce BIP125 Rule #3 "The replacement transaction pays an absolute fee of at least the sum - * paid by the original transactions." Enforce BIP125 Rule #4 "The replacement transaction must also - * pay for its own bandwidth at or above the rate set by the node's minimum relay fee setting." +/** The replacement transaction must pay more fees than the original transactions. The additional + * fees must pay for the replacement's bandwidth at or above the incremental relay feerate. * @param[in] original_fees Total modified fees of original transaction(s). * @param[in] replacement_fees Total modified fees of replacement transaction(s). * @param[in] replacement_vsize Total virtual size of replacement transaction(s). diff --git a/src/policy/settings.cpp b/src/policy/settings.cpp index 0b67d274ce..39e00f1111 100644 --- a/src/policy/settings.cpp +++ b/src/policy/settings.cpp @@ -5,11 +5,6 @@ #include <policy/settings.h> -#include <policy/feerate.h> #include <policy/policy.h> -bool fIsBareMultisigStd = DEFAULT_PERMIT_BAREMULTISIG; -CFeeRate incrementalRelayFee = CFeeRate(DEFAULT_INCREMENTAL_RELAY_FEE); -CFeeRate dustRelayFee = CFeeRate(DUST_RELAY_TX_FEE); -CFeeRate minRelayTxFee = CFeeRate(DEFAULT_MIN_RELAY_TX_FEE); unsigned int nBytesPerSigOp = DEFAULT_BYTES_PER_SIGOP; diff --git a/src/policy/settings.h b/src/policy/settings.h index 2311d01fe8..f0d6f779ae 100644 --- a/src/policy/settings.h +++ b/src/policy/settings.h @@ -6,35 +6,6 @@ #ifndef BITCOIN_POLICY_SETTINGS_H #define BITCOIN_POLICY_SETTINGS_H -#include <policy/feerate.h> -#include <policy/policy.h> - -#include <cstdint> -#include <string> - -class CTransaction; - -// Policy settings which are configurable at runtime. -extern CFeeRate incrementalRelayFee; -extern CFeeRate dustRelayFee; -/** A fee rate smaller than this is considered zero fee (for relaying, mining and transaction creation) */ -extern CFeeRate minRelayTxFee; extern unsigned int nBytesPerSigOp; -extern bool fIsBareMultisigStd; - -static inline bool IsStandardTx(const CTransaction& tx, std::string& reason) -{ - return IsStandardTx(tx, ::fIsBareMultisigStd, ::dustRelayFee, reason); -} - -static inline int64_t GetVirtualTransactionSize(int64_t weight, int64_t sigop_cost) -{ - return GetVirtualTransactionSize(weight, sigop_cost, ::nBytesPerSigOp); -} - -static inline int64_t GetVirtualTransactionSize(const CTransaction& tx, int64_t sigop_cost) -{ - return GetVirtualTransactionSize(tx, sigop_cost, ::nBytesPerSigOp); -} #endif // BITCOIN_POLICY_SETTINGS_H diff --git a/src/pow.cpp b/src/pow.cpp index 1414d37564..c0449cac74 100644 --- a/src/pow.cpp +++ b/src/pow.cpp @@ -71,6 +71,57 @@ unsigned int CalculateNextWorkRequired(const CBlockIndex* pindexLast, int64_t nF return bnNew.GetCompact(); } +// Check that on difficulty adjustments, the new difficulty does not increase +// or decrease beyond the permitted limits. +bool PermittedDifficultyTransition(const Consensus::Params& params, int64_t height, uint32_t old_nbits, uint32_t new_nbits) +{ + if (params.fPowAllowMinDifficultyBlocks) return true; + + if (height % params.DifficultyAdjustmentInterval() == 0) { + int64_t smallest_timespan = params.nPowTargetTimespan/4; + int64_t largest_timespan = params.nPowTargetTimespan*4; + + const arith_uint256 pow_limit = UintToArith256(params.powLimit); + arith_uint256 observed_new_target; + observed_new_target.SetCompact(new_nbits); + + // Calculate the largest difficulty value possible: + arith_uint256 largest_difficulty_target; + largest_difficulty_target.SetCompact(old_nbits); + largest_difficulty_target *= largest_timespan; + largest_difficulty_target /= params.nPowTargetTimespan; + + if (largest_difficulty_target > pow_limit) { + largest_difficulty_target = pow_limit; + } + + // Round and then compare this new calculated value to what is + // observed. + arith_uint256 maximum_new_target; + maximum_new_target.SetCompact(largest_difficulty_target.GetCompact()); + if (maximum_new_target < observed_new_target) return false; + + // Calculate the smallest difficulty value possible: + arith_uint256 smallest_difficulty_target; + smallest_difficulty_target.SetCompact(old_nbits); + smallest_difficulty_target *= smallest_timespan; + smallest_difficulty_target /= params.nPowTargetTimespan; + + if (smallest_difficulty_target > pow_limit) { + smallest_difficulty_target = pow_limit; + } + + // Round and then compare this new calculated value to what is + // observed. + arith_uint256 minimum_new_target; + minimum_new_target.SetCompact(smallest_difficulty_target.GetCompact()); + if (minimum_new_target > observed_new_target) return false; + } else if (old_nbits != new_nbits) { + return false; + } + return true; +} + bool CheckProofOfWork(uint256 hash, unsigned int nBits, const Consensus::Params& params) { bool fNegative; @@ -20,4 +20,18 @@ unsigned int CalculateNextWorkRequired(const CBlockIndex* pindexLast, int64_t nF /** Check whether a block hash satisfies the proof-of-work requirement specified by nBits */ bool CheckProofOfWork(uint256 hash, unsigned int nBits, const Consensus::Params&); +/** + * Return false if the proof-of-work requirement specified by new_nbits at a + * given height is not possible, given the proof-of-work on the prior block as + * specified by old_nbits. + * + * This function only checks that the new value is within a factor of 4 of the + * old value for blocks at the difficulty adjustment interval, and otherwise + * requires the values to be the same. + * + * Always returns true on networks where min difficulty blocks are allowed, + * such as regtest/testnet. + */ +bool PermittedDifficultyTransition(const Consensus::Params& params, int64_t height, uint32_t old_nbits, uint32_t new_nbits); + #endif // BITCOIN_POW_H diff --git a/src/prevector.h b/src/prevector.h index a52510930a..7df5a067a2 100644 --- a/src/prevector.h +++ b/src/prevector.h @@ -6,7 +6,7 @@ #define BITCOIN_PREVECTOR_H #include <assert.h> -#include <stdlib.h> +#include <cstdlib> #include <stdint.h> #include <string.h> diff --git a/src/primitives/block.h b/src/primitives/block.h index 2d10853607..2e26e6c426 100644 --- a/src/primitives/block.h +++ b/src/primitives/block.h @@ -9,6 +9,7 @@ #include <primitives/transaction.h> #include <serialize.h> #include <uint256.h> +#include <util/time.h> /** Nodes collect new transactions into a block, hash them into a hash tree, * and scan through nonce values to make the block's hash satisfy proof-of-work @@ -52,6 +53,11 @@ public: uint256 GetHash() const; + NodeSeconds Time() const + { + return NodeSeconds{std::chrono::seconds{nTime}}; + } + int64_t GetBlockTime() const { return (int64_t)nTime; @@ -117,7 +123,7 @@ struct CBlockLocator CBlockLocator() {} - explicit CBlockLocator(const std::vector<uint256>& vHaveIn) : vHave(vHaveIn) {} + explicit CBlockLocator(std::vector<uint256>&& have) : vHave(std::move(have)) {} SERIALIZE_METHODS(CBlockLocator, obj) { diff --git a/src/primitives/transaction.cpp b/src/primitives/transaction.cpp index f7f6ae4480..ec48194ee9 100644 --- a/src/primitives/transaction.cpp +++ b/src/primitives/transaction.cpp @@ -7,10 +7,15 @@ #include <consensus/amount.h> #include <hash.h> +#include <script/script.h> +#include <serialize.h> #include <tinyformat.h> +#include <uint256.h> #include <util/strencodings.h> +#include <version.h> -#include <assert.h> +#include <cassert> +#include <stdexcept> std::string COutPoint::ToString() const { diff --git a/src/primitives/transaction.h b/src/primitives/transaction.h index fb98fb6868..f496ea022e 100644 --- a/src/primitives/transaction.h +++ b/src/primitives/transaction.h @@ -6,13 +6,21 @@ #ifndef BITCOIN_PRIMITIVES_TRANSACTION_H #define BITCOIN_PRIMITIVES_TRANSACTION_H -#include <stdint.h> #include <consensus/amount.h> +#include <prevector.h> #include <script/script.h> #include <serialize.h> #include <uint256.h> +#include <cstddef> +#include <cstdint> +#include <ios> +#include <limits> +#include <memory> +#include <string> #include <tuple> +#include <utility> +#include <vector> /** * A flag that is ORed into the protocol version to designate that a transaction @@ -303,7 +311,7 @@ private: public: /** Convert a CMutableTransaction into a CTransaction. */ explicit CTransaction(const CMutableTransaction& tx); - CTransaction(CMutableTransaction&& tx); + explicit CTransaction(CMutableTransaction&& tx); template <typename Stream> inline void Serialize(Stream& s) const { @@ -368,7 +376,7 @@ struct CMutableTransaction int32_t nVersion; uint32_t nLockTime; - CMutableTransaction(); + explicit CMutableTransaction(); explicit CMutableTransaction(const CTransaction& tx); template <typename Stream> diff --git a/src/protocol.cpp b/src/protocol.cpp index 139405170b..bdd1cc2aff 100644 --- a/src/protocol.cpp +++ b/src/protocol.cpp @@ -199,12 +199,7 @@ static std::string serviceFlagToStr(size_t bit) // Not using default, so we get warned when a case is missing } - std::ostringstream stream; - stream.imbue(std::locale::classic()); - stream << "UNKNOWN["; - stream << "2^" << bit; - stream << "]"; - return stream.str(); + return strprintf("UNKNOWN[2^%u]", bit); } std::vector<std::string> serviceFlagsToStr(uint64_t flags) diff --git a/src/protocol.h b/src/protocol.h index da2d24aff3..b85dc0d820 100644 --- a/src/protocol.h +++ b/src/protocol.h @@ -11,6 +11,7 @@ #include <serialize.h> #include <streams.h> #include <uint256.h> +#include <util/time.h> #include <cstdint> #include <limits> @@ -352,7 +353,7 @@ static inline bool MayHaveUsefulAddressDB(ServiceFlags services) /** A CService with information about it as peer */ class CAddress : public CService { - static constexpr uint32_t TIME_INIT{100000000}; + static constexpr std::chrono::seconds TIME_INIT{100000000}; /** Historically, CAddress disk serialization stored the CLIENT_VERSION, optionally OR'ed with * the ADDRV2_FORMAT flag to indicate V2 serialization. The first field has since been @@ -382,7 +383,7 @@ class CAddress : public CService public: CAddress() : CService{} {}; CAddress(CService ipIn, ServiceFlags nServicesIn) : CService{ipIn}, nServices{nServicesIn} {}; - CAddress(CService ipIn, ServiceFlags nServicesIn, uint32_t nTimeIn) : CService{ipIn}, nTime{nTimeIn}, nServices{nServicesIn} {}; + CAddress(CService ipIn, ServiceFlags nServicesIn, NodeSeconds time) : CService{ipIn}, nTime{time}, nServices{nServicesIn} {}; SERIALIZE_METHODS(CAddress, obj) { @@ -415,7 +416,7 @@ public: use_v2 = s.GetVersion() & ADDRV2_FORMAT; } - READWRITE(obj.nTime); + READWRITE(Using<LossyChronoFormatter<uint32_t>>(obj.nTime)); // nServices is serialized as CompactSize in V2; as uint64_t in V1. if (use_v2) { uint64_t services_tmp; @@ -430,8 +431,8 @@ public: SerReadWriteMany(os, ser_action, ReadWriteAsHelper<CService>(obj)); } - //! Always included in serialization. - uint32_t nTime{TIME_INIT}; + //! Always included in serialization. The behavior is unspecified if the value is not representable as uint32_t. + NodeSeconds nTime{TIME_INIT}; //! Serialized as uint64_t in V1, and as CompactSize in V2. ServiceFlags nServices{NODE_NONE}; diff --git a/src/psbt.cpp b/src/psbt.cpp index c1c8a385cc..36fec74bc9 100644 --- a/src/psbt.cpp +++ b/src/psbt.cpp @@ -113,6 +113,24 @@ void PSBTInput::FillSignatureData(SignatureData& sigdata) const for (const auto& key_pair : hd_keypaths) { sigdata.misc_pubkeys.emplace(key_pair.first.GetID(), key_pair); } + if (!m_tap_key_sig.empty()) { + sigdata.taproot_key_path_sig = m_tap_key_sig; + } + for (const auto& [pubkey_leaf, sig] : m_tap_script_sigs) { + sigdata.taproot_script_sigs.emplace(pubkey_leaf, sig); + } + if (!m_tap_internal_key.IsNull()) { + sigdata.tr_spenddata.internal_key = m_tap_internal_key; + } + if (!m_tap_merkle_root.IsNull()) { + sigdata.tr_spenddata.merkle_root = m_tap_merkle_root; + } + for (const auto& [leaf_script, control_block] : m_tap_scripts) { + sigdata.tr_spenddata.scripts.emplace(leaf_script, control_block); + } + for (const auto& [pubkey, leaf_origin] : m_tap_bip32_paths) { + sigdata.taproot_misc_pubkeys.emplace(pubkey, leaf_origin); + } } void PSBTInput::FromSignatureData(const SignatureData& sigdata) @@ -142,13 +160,30 @@ void PSBTInput::FromSignatureData(const SignatureData& sigdata) for (const auto& entry : sigdata.misc_pubkeys) { hd_keypaths.emplace(entry.second); } + if (!sigdata.taproot_key_path_sig.empty()) { + m_tap_key_sig = sigdata.taproot_key_path_sig; + } + for (const auto& [pubkey_leaf, sig] : sigdata.taproot_script_sigs) { + m_tap_script_sigs.emplace(pubkey_leaf, sig); + } + if (!sigdata.tr_spenddata.internal_key.IsNull()) { + m_tap_internal_key = sigdata.tr_spenddata.internal_key; + } + if (!sigdata.tr_spenddata.merkle_root.IsNull()) { + m_tap_merkle_root = sigdata.tr_spenddata.merkle_root; + } + for (const auto& [leaf_script, control_block] : sigdata.tr_spenddata.scripts) { + m_tap_scripts.emplace(leaf_script, control_block); + } + for (const auto& [pubkey, leaf_origin] : sigdata.taproot_misc_pubkeys) { + m_tap_bip32_paths.emplace(pubkey, leaf_origin); + } } void PSBTInput::Merge(const PSBTInput& input) { if (!non_witness_utxo && input.non_witness_utxo) non_witness_utxo = input.non_witness_utxo; if (witness_utxo.IsNull() && !input.witness_utxo.IsNull()) { - // TODO: For segwit v1, we will want to clear out the non-witness utxo when setting a witness one. For v0 and non-segwit, this is not safe witness_utxo = input.witness_utxo; } @@ -159,11 +194,17 @@ void PSBTInput::Merge(const PSBTInput& input) hash256_preimages.insert(input.hash256_preimages.begin(), input.hash256_preimages.end()); hd_keypaths.insert(input.hd_keypaths.begin(), input.hd_keypaths.end()); unknown.insert(input.unknown.begin(), input.unknown.end()); + m_tap_script_sigs.insert(input.m_tap_script_sigs.begin(), input.m_tap_script_sigs.end()); + m_tap_scripts.insert(input.m_tap_scripts.begin(), input.m_tap_scripts.end()); + m_tap_bip32_paths.insert(input.m_tap_bip32_paths.begin(), input.m_tap_bip32_paths.end()); if (redeem_script.empty() && !input.redeem_script.empty()) redeem_script = input.redeem_script; if (witness_script.empty() && !input.witness_script.empty()) witness_script = input.witness_script; if (final_script_sig.empty() && !input.final_script_sig.empty()) final_script_sig = input.final_script_sig; if (final_script_witness.IsNull() && !input.final_script_witness.IsNull()) final_script_witness = input.final_script_witness; + if (m_tap_key_sig.empty() && !input.m_tap_key_sig.empty()) m_tap_key_sig = input.m_tap_key_sig; + if (m_tap_internal_key.IsNull() && !input.m_tap_internal_key.IsNull()) m_tap_internal_key = input.m_tap_internal_key; + if (m_tap_merkle_root.IsNull() && !input.m_tap_merkle_root.IsNull()) m_tap_merkle_root = input.m_tap_merkle_root; } void PSBTOutput::FillSignatureData(SignatureData& sigdata) const @@ -177,6 +218,15 @@ void PSBTOutput::FillSignatureData(SignatureData& sigdata) const for (const auto& key_pair : hd_keypaths) { sigdata.misc_pubkeys.emplace(key_pair.first.GetID(), key_pair); } + if (m_tap_tree.has_value() && m_tap_internal_key.IsFullyValid()) { + TaprootSpendData spenddata = m_tap_tree->GetSpendData(); + + sigdata.tr_spenddata.internal_key = m_tap_internal_key; + sigdata.tr_spenddata.Merge(spenddata); + } + for (const auto& [pubkey, leaf_origin] : m_tap_bip32_paths) { + sigdata.taproot_misc_pubkeys.emplace(pubkey, leaf_origin); + } } void PSBTOutput::FromSignatureData(const SignatureData& sigdata) @@ -190,6 +240,15 @@ void PSBTOutput::FromSignatureData(const SignatureData& sigdata) for (const auto& entry : sigdata.misc_pubkeys) { hd_keypaths.emplace(entry.second); } + if (!sigdata.tr_spenddata.internal_key.IsNull()) { + m_tap_internal_key = sigdata.tr_spenddata.internal_key; + } + if (sigdata.tr_builder.has_value()) { + m_tap_tree = sigdata.tr_builder; + } + for (const auto& [pubkey, leaf_origin] : sigdata.taproot_misc_pubkeys) { + m_tap_bip32_paths.emplace(pubkey, leaf_origin); + } } bool PSBTOutput::IsNull() const @@ -201,9 +260,12 @@ void PSBTOutput::Merge(const PSBTOutput& output) { hd_keypaths.insert(output.hd_keypaths.begin(), output.hd_keypaths.end()); unknown.insert(output.unknown.begin(), output.unknown.end()); + m_tap_bip32_paths.insert(output.m_tap_bip32_paths.begin(), output.m_tap_bip32_paths.end()); if (redeem_script.empty() && !output.redeem_script.empty()) redeem_script = output.redeem_script; if (witness_script.empty() && !output.witness_script.empty()) witness_script = output.witness_script; + if (m_tap_internal_key.IsNull() && !output.m_tap_internal_key.IsNull()) m_tap_internal_key = output.m_tap_internal_key; + if (m_tap_tree.has_value() && !output.m_tap_tree.has_value()) m_tap_tree = output.m_tap_tree; } bool PSBTInputSigned(const PSBTInput& input) { @@ -313,10 +375,11 @@ bool SignPSBTInput(const SigningProvider& provider, PartiallySignedTransaction& input.FromSignatureData(sigdata); // If we have a witness signature, put a witness UTXO. - // TODO: For segwit v1, we should remove the non_witness_utxo if (sigdata.witness) { input.witness_utxo = utxo; - // input.non_witness_utxo = nullptr; + // We can remove the non_witness_utxo if and only if there are no non-segwit or segwit v0 + // inputs in this transaction. Since this requires inspecting the entire transaction, this + // is something for the caller to deal with (i.e. FillPSBT). } // Fill in the missing info diff --git a/src/psbt.h b/src/psbt.h index 8fda889bb4..d5c67802c7 100644 --- a/src/psbt.h +++ b/src/psbt.h @@ -40,12 +40,21 @@ static constexpr uint8_t PSBT_IN_RIPEMD160 = 0x0A; static constexpr uint8_t PSBT_IN_SHA256 = 0x0B; static constexpr uint8_t PSBT_IN_HASH160 = 0x0C; static constexpr uint8_t PSBT_IN_HASH256 = 0x0D; +static constexpr uint8_t PSBT_IN_TAP_KEY_SIG = 0x13; +static constexpr uint8_t PSBT_IN_TAP_SCRIPT_SIG = 0x14; +static constexpr uint8_t PSBT_IN_TAP_LEAF_SCRIPT = 0x15; +static constexpr uint8_t PSBT_IN_TAP_BIP32_DERIVATION = 0x16; +static constexpr uint8_t PSBT_IN_TAP_INTERNAL_KEY = 0x17; +static constexpr uint8_t PSBT_IN_TAP_MERKLE_ROOT = 0x18; static constexpr uint8_t PSBT_IN_PROPRIETARY = 0xFC; // Output types static constexpr uint8_t PSBT_OUT_REDEEMSCRIPT = 0x00; static constexpr uint8_t PSBT_OUT_WITNESSSCRIPT = 0x01; static constexpr uint8_t PSBT_OUT_BIP32_DERIVATION = 0x02; +static constexpr uint8_t PSBT_OUT_TAP_INTERNAL_KEY = 0x05; +static constexpr uint8_t PSBT_OUT_TAP_TREE = 0x06; +static constexpr uint8_t PSBT_OUT_TAP_BIP32_DERIVATION = 0x07; static constexpr uint8_t PSBT_OUT_PROPRIETARY = 0xFC; // The separator is 0x00. Reading this in means that the unserializer can interpret it @@ -54,7 +63,7 @@ static constexpr uint8_t PSBT_SEPARATOR = 0x00; // BIP 174 does not specify a maximum file size, but we set a limit anyway // to prevent reading a stream indefinitely and running out of memory. -const std::streamsize MAX_FILE_SIZE_PSBT = 100000000; // 100 MiB +const std::streamsize MAX_FILE_SIZE_PSBT = 100000000; // 100 MB // PSBT version number static constexpr uint32_t PSBT_HIGHEST_VERSION = 0; @@ -97,22 +106,30 @@ void UnserializeFromVector(Stream& s, X&... args) } } -// Deserialize an individual HD keypath to a stream +// Deserialize bytes of given length from the stream as a KeyOriginInfo template<typename Stream> -void DeserializeHDKeypath(Stream& s, KeyOriginInfo& hd_keypath) +KeyOriginInfo DeserializeKeyOrigin(Stream& s, uint64_t length) { // Read in key path - uint64_t value_len = ReadCompactSize(s); - if (value_len % 4 || value_len == 0) { + if (length % 4 || length == 0) { throw std::ios_base::failure("Invalid length for HD key path"); } + KeyOriginInfo hd_keypath; s >> hd_keypath.fingerprint; - for (unsigned int i = 4; i < value_len; i += sizeof(uint32_t)) { + for (unsigned int i = 4; i < length; i += sizeof(uint32_t)) { uint32_t index; s >> index; hd_keypath.path.push_back(index); } + return hd_keypath; +} + +// Deserialize a length prefixed KeyOriginInfo from a stream +template<typename Stream> +void DeserializeHDKeypath(Stream& s, KeyOriginInfo& hd_keypath) +{ + hd_keypath = DeserializeKeyOrigin(s, ReadCompactSize(s)); } // Deserialize HD keypaths into a map @@ -139,17 +156,24 @@ void DeserializeHDKeypaths(Stream& s, const std::vector<unsigned char>& key, std hd_keypaths.emplace(pubkey, std::move(keypath)); } -// Serialize an individual HD keypath to a stream +// Serialize a KeyOriginInfo to a stream template<typename Stream> -void SerializeHDKeypath(Stream& s, KeyOriginInfo hd_keypath) +void SerializeKeyOrigin(Stream& s, KeyOriginInfo hd_keypath) { - WriteCompactSize(s, (hd_keypath.path.size() + 1) * sizeof(uint32_t)); s << hd_keypath.fingerprint; for (const auto& path : hd_keypath.path) { s << path; } } +// Serialize a length prefixed KeyOriginInfo to a stream +template<typename Stream> +void SerializeHDKeypath(Stream& s, KeyOriginInfo hd_keypath) +{ + WriteCompactSize(s, (hd_keypath.path.size() + 1) * sizeof(uint32_t)); + SerializeKeyOrigin(s, hd_keypath); +} + // Serialize HD keypaths to a stream from a map template<typename Stream> void SerializeHDKeypaths(Stream& s, const std::map<CPubKey, KeyOriginInfo>& hd_keypaths, CompactSizeWriter type) @@ -178,6 +202,15 @@ struct PSBTInput std::map<uint256, std::vector<unsigned char>> sha256_preimages; std::map<uint160, std::vector<unsigned char>> hash160_preimages; std::map<uint256, std::vector<unsigned char>> hash256_preimages; + + // Taproot fields + std::vector<unsigned char> m_tap_key_sig; + std::map<std::pair<XOnlyPubKey, uint256>, std::vector<unsigned char>> m_tap_script_sigs; + std::map<std::pair<CScript, int>, std::set<std::vector<unsigned char>, ShortestVectorFirstComparator>> m_tap_scripts; + std::map<XOnlyPubKey, std::pair<std::set<uint256>, KeyOriginInfo>> m_tap_bip32_paths; + XOnlyPubKey m_tap_internal_key; + uint256 m_tap_merkle_root; + std::map<std::vector<unsigned char>, std::vector<unsigned char>> unknown; std::set<PSBTProprietary> m_proprietary; std::optional<int> sighash_type; @@ -252,6 +285,53 @@ struct PSBTInput SerializeToVector(s, CompactSizeWriter(PSBT_IN_HASH256), Span{hash}); s << preimage; } + + // Write taproot key sig + if (!m_tap_key_sig.empty()) { + SerializeToVector(s, PSBT_IN_TAP_KEY_SIG); + s << m_tap_key_sig; + } + + // Write taproot script sigs + for (const auto& [pubkey_leaf, sig] : m_tap_script_sigs) { + const auto& [xonly, leaf_hash] = pubkey_leaf; + SerializeToVector(s, PSBT_IN_TAP_SCRIPT_SIG, xonly, leaf_hash); + s << sig; + } + + // Write taproot leaf scripts + for (const auto& [leaf, control_blocks] : m_tap_scripts) { + const auto& [script, leaf_ver] = leaf; + for (const auto& control_block : control_blocks) { + SerializeToVector(s, PSBT_IN_TAP_LEAF_SCRIPT, Span{control_block}); + std::vector<unsigned char> value_v(script.begin(), script.end()); + value_v.push_back((uint8_t)leaf_ver); + s << value_v; + } + } + + // Write taproot bip32 keypaths + for (const auto& [xonly, leaf_origin] : m_tap_bip32_paths) { + const auto& [leaf_hashes, origin] = leaf_origin; + SerializeToVector(s, PSBT_IN_TAP_BIP32_DERIVATION, xonly); + std::vector<unsigned char> value; + CVectorWriter s_value(s.GetType(), s.GetVersion(), value, 0); + s_value << leaf_hashes; + SerializeKeyOrigin(s_value, origin); + s << value; + } + + // Write taproot internal key + if (!m_tap_internal_key.IsNull()) { + SerializeToVector(s, PSBT_IN_TAP_INTERNAL_KEY); + s << ToByteVector(m_tap_internal_key); + } + + // Write taproot merkle root + if (!m_tap_merkle_root.IsNull()) { + SerializeToVector(s, PSBT_IN_TAP_MERKLE_ROOT); + SerializeToVector(s, m_tap_merkle_root); + } } // Write script sig @@ -488,6 +568,106 @@ struct PSBTInput hash256_preimages.emplace(hash, std::move(preimage)); break; } + case PSBT_IN_TAP_KEY_SIG: + { + if (!key_lookup.emplace(key).second) { + throw std::ios_base::failure("Duplicate Key, input Taproot key signature already provided"); + } else if (key.size() != 1) { + throw std::ios_base::failure("Input Taproot key signature key is more than one byte type"); + } + s >> m_tap_key_sig; + if (m_tap_key_sig.size() < 64) { + throw std::ios_base::failure("Input Taproot key path signature is shorter than 64 bytes"); + } else if (m_tap_key_sig.size() > 65) { + throw std::ios_base::failure("Input Taproot key path signature is longer than 65 bytes"); + } + break; + } + case PSBT_IN_TAP_SCRIPT_SIG: + { + if (!key_lookup.emplace(key).second) { + throw std::ios_base::failure("Duplicate Key, input Taproot script signature already provided"); + } else if (key.size() != 65) { + throw std::ios_base::failure("Input Taproot script signature key is not 65 bytes"); + } + SpanReader s_key(s.GetType(), s.GetVersion(), Span{key}.subspan(1)); + XOnlyPubKey xonly; + uint256 hash; + s_key >> xonly; + s_key >> hash; + std::vector<unsigned char> sig; + s >> sig; + if (sig.size() < 64) { + throw std::ios_base::failure("Input Taproot script path signature is shorter than 64 bytes"); + } else if (sig.size() > 65) { + throw std::ios_base::failure("Input Taproot script path signature is longer than 65 bytes"); + } + m_tap_script_sigs.emplace(std::make_pair(xonly, hash), sig); + break; + } + case PSBT_IN_TAP_LEAF_SCRIPT: + { + if (!key_lookup.emplace(key).second) { + throw std::ios_base::failure("Duplicate Key, input Taproot leaf script already provided"); + } else if (key.size() < 34) { + throw std::ios_base::failure("Taproot leaf script key is not at least 34 bytes"); + } else if ((key.size() - 2) % 32 != 0) { + throw std::ios_base::failure("Input Taproot leaf script key's control block size is not valid"); + } + std::vector<unsigned char> script_v; + s >> script_v; + if (script_v.empty()) { + throw std::ios_base::failure("Input Taproot leaf script must be at least 1 byte"); + } + uint8_t leaf_ver = script_v.back(); + script_v.pop_back(); + const auto leaf_script = std::make_pair(CScript(script_v.begin(), script_v.end()), (int)leaf_ver); + m_tap_scripts[leaf_script].insert(std::vector<unsigned char>(key.begin() + 1, key.end())); + break; + } + case PSBT_IN_TAP_BIP32_DERIVATION: + { + if (!key_lookup.emplace(key).second) { + throw std::ios_base::failure("Duplicate Key, input Taproot BIP32 keypath already provided"); + } else if (key.size() != 33) { + throw std::ios_base::failure("Input Taproot BIP32 keypath key is not at 33 bytes"); + } + SpanReader s_key(s.GetType(), s.GetVersion(), Span{key}.subspan(1)); + XOnlyPubKey xonly; + s_key >> xonly; + std::set<uint256> leaf_hashes; + uint64_t value_len = ReadCompactSize(s); + size_t before_hashes = s.size(); + s >> leaf_hashes; + size_t after_hashes = s.size(); + size_t hashes_len = before_hashes - after_hashes; + if (hashes_len > value_len) { + throw std::ios_base::failure("Input Taproot BIP32 keypath has an invalid length"); + } + size_t origin_len = value_len - hashes_len; + m_tap_bip32_paths.emplace(xonly, std::make_pair(leaf_hashes, DeserializeKeyOrigin(s, origin_len))); + break; + } + case PSBT_IN_TAP_INTERNAL_KEY: + { + if (!key_lookup.emplace(key).second) { + throw std::ios_base::failure("Duplicate Key, input Taproot internal key already provided"); + } else if (key.size() != 1) { + throw std::ios_base::failure("Input Taproot internal key key is more than one byte type"); + } + UnserializeFromVector(s, m_tap_internal_key); + break; + } + case PSBT_IN_TAP_MERKLE_ROOT: + { + if (!key_lookup.emplace(key).second) { + throw std::ios_base::failure("Duplicate Key, input Taproot merkle root already provided"); + } else if (key.size() != 1) { + throw std::ios_base::failure("Input Taproot merkle root key is more than one byte type"); + } + UnserializeFromVector(s, m_tap_merkle_root); + break; + } case PSBT_IN_PROPRIETARY: { PSBTProprietary this_prop; @@ -532,6 +712,9 @@ struct PSBTOutput CScript redeem_script; CScript witness_script; std::map<CPubKey, KeyOriginInfo> hd_keypaths; + XOnlyPubKey m_tap_internal_key; + std::optional<TaprootBuilder> m_tap_tree; + std::map<XOnlyPubKey, std::pair<std::set<uint256>, KeyOriginInfo>> m_tap_bip32_paths; std::map<std::vector<unsigned char>, std::vector<unsigned char>> unknown; std::set<PSBTProprietary> m_proprietary; @@ -564,6 +747,40 @@ struct PSBTOutput s << entry.value; } + // Write taproot internal key + if (!m_tap_internal_key.IsNull()) { + SerializeToVector(s, PSBT_OUT_TAP_INTERNAL_KEY); + s << ToByteVector(m_tap_internal_key); + } + + // Write taproot tree + if (m_tap_tree.has_value()) { + SerializeToVector(s, PSBT_OUT_TAP_TREE); + std::vector<unsigned char> value; + CVectorWriter s_value(s.GetType(), s.GetVersion(), value, 0); + const auto& tuples = m_tap_tree->GetTreeTuples(); + for (const auto& tuple : tuples) { + uint8_t depth = std::get<0>(tuple); + uint8_t leaf_ver = std::get<1>(tuple); + CScript script = std::get<2>(tuple); + s_value << depth; + s_value << leaf_ver; + s_value << script; + } + s << value; + } + + // Write taproot bip32 keypaths + for (const auto& [xonly, leaf] : m_tap_bip32_paths) { + const auto& [leaf_hashes, origin] = leaf; + SerializeToVector(s, PSBT_OUT_TAP_BIP32_DERIVATION, xonly); + std::vector<unsigned char> value; + CVectorWriter s_value(s.GetType(), s.GetVersion(), value, 0); + s_value << leaf_hashes; + SerializeKeyOrigin(s_value, origin); + s << value; + } + // Write unknown things for (auto& entry : unknown) { s << entry.first; @@ -624,6 +841,68 @@ struct PSBTOutput DeserializeHDKeypaths(s, key, hd_keypaths); break; } + case PSBT_OUT_TAP_INTERNAL_KEY: + { + if (!key_lookup.emplace(key).second) { + throw std::ios_base::failure("Duplicate Key, output Taproot internal key already provided"); + } else if (key.size() != 1) { + throw std::ios_base::failure("Output Taproot internal key key is more than one byte type"); + } + UnserializeFromVector(s, m_tap_internal_key); + break; + } + case PSBT_OUT_TAP_TREE: + { + if (!key_lookup.emplace(key).second) { + throw std::ios_base::failure("Duplicate Key, output Taproot tree already provided"); + } else if (key.size() != 1) { + throw std::ios_base::failure("Output Taproot tree key is more than one byte type"); + } + m_tap_tree.emplace(); + std::vector<unsigned char> tree_v; + s >> tree_v; + SpanReader s_tree(s.GetType(), s.GetVersion(), tree_v); + while (!s_tree.empty()) { + uint8_t depth; + uint8_t leaf_ver; + CScript script; + s_tree >> depth; + s_tree >> leaf_ver; + s_tree >> script; + if (depth > TAPROOT_CONTROL_MAX_NODE_COUNT) { + throw std::ios_base::failure("Output Taproot tree has as leaf greater than Taproot maximum depth"); + } + if ((leaf_ver & ~TAPROOT_LEAF_MASK) != 0) { + throw std::ios_base::failure("Output Taproot tree has a leaf with an invalid leaf version"); + } + m_tap_tree->Add((int)depth, script, (int)leaf_ver, true /* track */); + } + if (!m_tap_tree->IsComplete()) { + throw std::ios_base::failure("Output Taproot tree is malformed"); + } + break; + } + case PSBT_OUT_TAP_BIP32_DERIVATION: + { + if (!key_lookup.emplace(key).second) { + throw std::ios_base::failure("Duplicate Key, output Taproot BIP32 keypath already provided"); + } else if (key.size() != 33) { + throw std::ios_base::failure("Output Taproot BIP32 keypath key is not at 33 bytes"); + } + XOnlyPubKey xonly(uint256({key.begin() + 1, key.begin() + 33})); + std::set<uint256> leaf_hashes; + uint64_t value_len = ReadCompactSize(s); + size_t before_hashes = s.size(); + s >> leaf_hashes; + size_t after_hashes = s.size(); + size_t hashes_len = before_hashes - after_hashes; + if (hashes_len > value_len) { + throw std::ios_base::failure("Output Taproot BIP32 keypath has an invalid length"); + } + size_t origin_len = value_len - hashes_len; + m_tap_bip32_paths.emplace(xonly, std::make_pair(leaf_hashes, DeserializeKeyOrigin(s, origin_len))); + break; + } case PSBT_OUT_PROPRIETARY: { PSBTProprietary this_prop; @@ -652,6 +931,11 @@ struct PSBTOutput } } + // Finalize m_tap_tree so that all of the computed things are computed + if (m_tap_tree.has_value() && m_tap_tree->IsComplete() && m_tap_internal_key.IsFullyValid()) { + m_tap_tree->Finalize(m_tap_internal_key); + } + if (!found_sep) { throw std::ios_base::failure("Separator is missing at the end of an output map"); } diff --git a/src/pubkey.cpp b/src/pubkey.cpp index 324f681a0a..2e37e16690 100644 --- a/src/pubkey.cpp +++ b/src/pubkey.cpp @@ -211,16 +211,16 @@ bool XOnlyPubKey::VerifySchnorr(const uint256& msg, Span<const unsigned char> si return secp256k1_schnorrsig_verify(secp256k1_context_verify, sigbytes.data(), msg.begin(), 32, &pubkey); } -static const CHashWriter HASHER_TAPTWEAK = TaggedHash("TapTweak"); +static const HashWriter HASHER_TAPTWEAK{TaggedHash("TapTweak")}; uint256 XOnlyPubKey::ComputeTapTweakHash(const uint256* merkle_root) const { if (merkle_root == nullptr) { // We have no scripts. The actual tweak does not matter, but follow BIP341 here to // allow for reproducible tweaking. - return (CHashWriter(HASHER_TAPTWEAK) << m_keydata).GetSHA256(); + return (HashWriter{HASHER_TAPTWEAK} << m_keydata).GetSHA256(); } else { - return (CHashWriter(HASHER_TAPTWEAK) << m_keydata << *merkle_root).GetSHA256(); + return (HashWriter{HASHER_TAPTWEAK} << m_keydata << *merkle_root).GetSHA256(); } } @@ -365,6 +365,7 @@ void CExtPubKey::DecodeWithVersion(const unsigned char code[BIP32_EXTKEY_WITH_VE } bool CExtPubKey::Derive(CExtPubKey &out, unsigned int _nChild) const { + if (nDepth == std::numeric_limits<unsigned char>::max()) return false; out.nDepth = nDepth + 1; CKeyID id = pubkey.GetID(); memcpy(out.vchFingerprint, &id, 4); diff --git a/src/pubkey.h b/src/pubkey.h index dfe06f834c..0485a38f72 100644 --- a/src/pubkey.h +++ b/src/pubkey.h @@ -218,7 +218,7 @@ public: bool Decompress(); //! Derive BIP32 child pubkey. - bool Derive(CPubKey& pubkeyChild, ChainCode &ccChild, unsigned int nChild, const ChainCode& cc) const; + [[nodiscard]] bool Derive(CPubKey& pubkeyChild, ChainCode &ccChild, unsigned int nChild, const ChainCode& cc) const; }; class XOnlyPubKey @@ -286,6 +286,9 @@ public: bool operator==(const XOnlyPubKey& other) const { return m_keydata == other.m_keydata; } bool operator!=(const XOnlyPubKey& other) const { return m_keydata != other.m_keydata; } bool operator<(const XOnlyPubKey& other) const { return m_keydata < other.m_keydata; } + + //! Implement serialization without length prefixes since it is a fixed length + SERIALIZE_METHODS(XOnlyPubKey, obj) { READWRITE(obj.m_keydata); } }; struct CExtPubKey { @@ -324,7 +327,7 @@ struct CExtPubKey { void Decode(const unsigned char code[BIP32_EXTKEY_SIZE]); void EncodeWithVersion(unsigned char code[BIP32_EXTKEY_WITH_VERSION_SIZE]) const; void DecodeWithVersion(const unsigned char code[BIP32_EXTKEY_WITH_VERSION_SIZE]); - bool Derive(CExtPubKey& out, unsigned int nChild) const; + [[nodiscard]] bool Derive(CExtPubKey& out, unsigned int nChild) const; }; /** Users of this module must hold an ECCVerifyHandle. The constructor and diff --git a/src/qt/addresstablemodel.cpp b/src/qt/addresstablemodel.cpp index 27ee9509e6..8b5da7f9f0 100644 --- a/src/qt/addresstablemodel.cpp +++ b/src/qt/addresstablemodel.cpp @@ -370,23 +370,21 @@ QString AddressTableModel::addRow(const QString &type, const QString &label, con else if(type == Receive) { // Generate a new address to associate with given label - CTxDestination dest; - if(!walletModel->wallet().getNewDestination(address_type, strLabel, dest)) - { + auto op_dest = walletModel->wallet().getNewDestination(address_type, strLabel); + if (!op_dest) { WalletModel::UnlockContext ctx(walletModel->requestUnlock()); - if(!ctx.isValid()) - { + if (!ctx.isValid()) { // Unlock wallet failed or was cancelled editStatus = WALLET_UNLOCK_FAILURE; return QString(); } - if(!walletModel->wallet().getNewDestination(address_type, strLabel, dest)) - { + op_dest = walletModel->wallet().getNewDestination(address_type, strLabel); + if (!op_dest) { editStatus = KEY_GENERATION_FAILURE; return QString(); } } - strAddress = EncodeDestination(dest); + strAddress = EncodeDestination(*op_dest); } else { diff --git a/src/qt/bitcoin.cpp b/src/qt/bitcoin.cpp index 53fbac0106..cc01e4d54a 100644 --- a/src/qt/bitcoin.cpp +++ b/src/qt/bitcoin.cpp @@ -13,7 +13,7 @@ #include <interfaces/handler.h> #include <interfaces/init.h> #include <interfaces/node.h> -#include <node/ui_interface.h> +#include <node/interface_ui.h> #include <noui.h> #include <qt/bitcoingui.h> #include <qt/clientmodel.h> @@ -75,15 +75,15 @@ Q_IMPORT_PLUGIN(QAndroidPlatformIntegrationPlugin) Q_DECLARE_METATYPE(bool*) Q_DECLARE_METATYPE(CAmount) Q_DECLARE_METATYPE(SynchronizationState) +Q_DECLARE_METATYPE(SyncType) Q_DECLARE_METATYPE(uint256) -using node::NodeContext; - static void RegisterMetaTypes() { // Register meta types used for QMetaObject::invokeMethod and Qt::QueuedConnection qRegisterMetaType<bool*>(); qRegisterMetaType<SynchronizationState>(); + qRegisterMetaType<SyncType>(); #ifdef ENABLE_WALLET qRegisterMetaType<WalletModel*>(); #endif @@ -96,7 +96,11 @@ static void RegisterMetaTypes() qRegisterMetaType<QMessageBox::Icon>("QMessageBox::Icon"); qRegisterMetaType<interfaces::BlockAndHeaderTipInfo>("interfaces::BlockAndHeaderTipInfo"); +#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) qRegisterMetaTypeStreamOperators<BitcoinUnit>("BitcoinUnit"); +#else + qRegisterMetaType<BitcoinUnit>("BitcoinUnit"); +#endif } static QString GetLangTerritory() @@ -135,21 +139,30 @@ static void initTranslations(QTranslator &qtTranslatorBase, QTranslator &qtTrans // - First load the translator for the base language, without territory // - Then load the more specific locale translator +#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) + const QString translation_path{QLibraryInfo::location(QLibraryInfo::TranslationsPath)}; +#else + const QString translation_path{QLibraryInfo::path(QLibraryInfo::TranslationsPath)}; +#endif // Load e.g. qt_de.qm - if (qtTranslatorBase.load("qt_" + lang, QLibraryInfo::location(QLibraryInfo::TranslationsPath))) + if (qtTranslatorBase.load("qt_" + lang, translation_path)) { QApplication::installTranslator(&qtTranslatorBase); + } // Load e.g. qt_de_DE.qm - if (qtTranslator.load("qt_" + lang_territory, QLibraryInfo::location(QLibraryInfo::TranslationsPath))) + if (qtTranslator.load("qt_" + lang_territory, translation_path)) { QApplication::installTranslator(&qtTranslator); + } // Load e.g. bitcoin_de.qm (shortcut "de" needs to be defined in bitcoin.qrc) - if (translatorBase.load(lang, ":/translations/")) + if (translatorBase.load(lang, ":/translations/")) { QApplication::installTranslator(&translatorBase); + } // Load e.g. bitcoin_de_DE.qm (shortcut "de_DE" needs to be defined in bitcoin.qrc) - if (translator.load(lang_territory, ":/translations/")) + if (translator.load(lang_territory, ":/translations/")) { QApplication::installTranslator(&translator); + } } static bool InitSettings() @@ -259,9 +272,26 @@ void BitcoinApplication::createPaymentServer() } #endif -void BitcoinApplication::createOptionsModel(bool resetSettings) +bool BitcoinApplication::createOptionsModel(bool resetSettings) { - optionsModel = new OptionsModel(node(), this, resetSettings); + optionsModel = new OptionsModel(node(), this); + if (resetSettings) { + optionsModel->Reset(); + } + bilingual_str error; + if (!optionsModel->Init(error)) { + fs::path settings_path; + if (gArgs.GetSettingsPath(&settings_path)) { + error += Untranslated("\n"); + std::string quoted_path = strprintf("%s", fs::quoted(fs::PathToString(settings_path))); + error.original += strprintf("Settings file %s might be corrupt or invalid.", quoted_path); + error.translated += tr("Settings file %1 might be corrupt or invalid.").arg(QString::fromStdString(quoted_path)).toStdString(); + } + InitError(error); + QMessageBox::critical(nullptr, PACKAGE_NAME, QString::fromStdString(error.translated)); + return false; + } + return true; } void BitcoinApplication::createWindow(const NetworkStyle *networkStyle) @@ -327,7 +357,7 @@ void BitcoinApplication::parameterSetup() void BitcoinApplication::InitPruneSetting(int64_t prune_MiB) { - optionsModel->SetPruneTargetGB(PruneMiBtoGB(prune_MiB), true); + optionsModel->SetPruneTargetGB(PruneMiBtoGB(prune_MiB)); } void BitcoinApplication::requestInitialize() @@ -500,9 +530,11 @@ int GuiMain(int argc, char* argv[]) Q_INIT_RESOURCE(bitcoin); Q_INIT_RESOURCE(bitcoin_locale); +#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) // Generate high-dpi pixmaps QApplication::setAttribute(Qt::AA_UseHighDpiPixmaps); QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); +#endif #if defined(QT_QPA_PLATFORM_ANDROID) QApplication::setAttribute(Qt::AA_DontUseNativeMenuBar); @@ -641,7 +673,9 @@ int GuiMain(int argc, char* argv[]) app.createNode(*init); // Load GUI settings from QSettings - app.createOptionsModel(gArgs.GetBoolArg("-resetguisettings", false)); + if (!app.createOptionsModel(gArgs.GetBoolArg("-resetguisettings", false))) { + return EXIT_FAILURE; + } if (did_show_intro) { // Store intro dialog settings other than datadir (network specific) diff --git a/src/qt/bitcoin.h b/src/qt/bitcoin.h index 7a6aa5cdc9..9ad37ca6c9 100644 --- a/src/qt/bitcoin.h +++ b/src/qt/bitcoin.h @@ -47,7 +47,7 @@ public: /// parameter interaction/setup based on rules void parameterSetup(); /// Create options model - void createOptionsModel(bool resetSettings); + [[nodiscard]] bool createOptionsModel(bool resetSettings); /// Initialize prune setting void InitPruneSetting(int64_t prune_MiB); /// Create main window diff --git a/src/qt/bitcoingui.cpp b/src/qt/bitcoingui.cpp index bfcdf6f316..894a401e56 100644 --- a/src/qt/bitcoingui.cpp +++ b/src/qt/bitcoingui.cpp @@ -35,7 +35,7 @@ #include <chainparams.h> #include <interfaces/handler.h> #include <interfaces/node.h> -#include <node/ui_interface.h> +#include <node/interface_ui.h> #include <util/system.h> #include <util/translation.h> #include <validation.h> @@ -47,6 +47,7 @@ #include <QCursor> #include <QDateTime> #include <QDragEnterEvent> +#include <QInputDialog> #include <QKeySequence> #include <QListWidget> #include <QMenu> @@ -348,6 +349,12 @@ void BitcoinGUI::createActions() m_create_wallet_action->setEnabled(false); m_create_wallet_action->setStatusTip(tr("Create a new wallet")); + //: Name of the menu item that restores wallet from a backup file. + m_restore_wallet_action = new QAction(tr("Restore Wallet…"), this); + m_restore_wallet_action->setEnabled(false); + //: Status tip for Restore Wallet menu item + m_restore_wallet_action->setStatusTip(tr("Restore a wallet from a backup file")); + m_close_all_wallets_action = new QAction(tr("Close All Wallets…"), this); m_close_all_wallets_action->setStatusTip(tr("Close all wallets")); @@ -412,6 +419,31 @@ void BitcoinGUI::createActions() action->setEnabled(false); } }); + connect(m_restore_wallet_action, &QAction::triggered, [this] { + //: Name of the wallet data file format. + QString name_data_file = tr("Wallet Data"); + + //: The title for Restore Wallet File Windows + QString title_windows = tr("Load Wallet Backup"); + + QString backup_file = GUIUtil::getOpenFileName(this, title_windows, QString(), name_data_file + QLatin1String(" (*.dat)"), nullptr); + if (backup_file.isEmpty()) return; + + bool wallet_name_ok; + /*: Title of pop-up window shown when the user is attempting to + restore a wallet. */ + QString title = tr("Restore Wallet"); + //: Label of the input field where the name of the wallet is entered. + QString label = tr("Wallet Name"); + QString wallet_name = QInputDialog::getText(this, title, label, QLineEdit::Normal, "", &wallet_name_ok); + if (!wallet_name_ok || wallet_name.isEmpty()) return; + + auto activity = new RestoreWalletActivity(m_wallet_controller, this); + connect(activity, &RestoreWalletActivity::restored, this, &BitcoinGUI::setCurrentWallet, Qt::QueuedConnection); + + auto backup_file_path = fs::PathFromString(backup_file.toStdString()); + activity->restore(backup_file_path, wallet_name.toStdString()); + }); connect(m_close_wallet_action, &QAction::triggered, [this] { m_wallet_controller->closeWallet(walletFrame->currentWalletModel(), this); }); @@ -450,8 +482,10 @@ void BitcoinGUI::createMenuBar() file->addAction(m_close_wallet_action); file->addAction(m_close_all_wallets_action); file->addSeparator(); - file->addAction(openAction); file->addAction(backupWalletAction); + file->addAction(m_restore_wallet_action); + file->addSeparator(); + file->addAction(openAction); file->addAction(signMessageAction); file->addAction(verifyMessageAction); file->addAction(m_load_psbt_action); @@ -581,8 +615,8 @@ void BitcoinGUI::setClientModel(ClientModel *_clientModel, interfaces::BlockAndH connect(_clientModel, &ClientModel::numConnectionsChanged, this, &BitcoinGUI::setNumConnections); connect(_clientModel, &ClientModel::networkActiveChanged, this, &BitcoinGUI::setNetworkActive); - modalOverlay->setKnownBestHeight(tip_info->header_height, QDateTime::fromSecsSinceEpoch(tip_info->header_time)); - setNumBlocks(tip_info->block_height, QDateTime::fromSecsSinceEpoch(tip_info->block_time), tip_info->verification_progress, false, SynchronizationState::INIT_DOWNLOAD); + modalOverlay->setKnownBestHeight(tip_info->header_height, QDateTime::fromSecsSinceEpoch(tip_info->header_time), /*presync=*/false); + setNumBlocks(tip_info->block_height, QDateTime::fromSecsSinceEpoch(tip_info->block_time), tip_info->verification_progress, SyncType::BLOCK_SYNC, SynchronizationState::INIT_DOWNLOAD); connect(_clientModel, &ClientModel::numBlocksChanged, this, &BitcoinGUI::setNumBlocks); // Receive and report messages from client model @@ -642,6 +676,7 @@ void BitcoinGUI::setWalletController(WalletController* wallet_controller) m_create_wallet_action->setEnabled(true); m_open_wallet_action->setEnabled(true); m_open_wallet_action->setMenu(m_open_wallet_menu); + m_restore_wallet_action->setEnabled(true); GUIUtil::ExceptionSafeConnect(wallet_controller, &WalletController::walletAdded, this, &BitcoinGUI::addWallet); connect(wallet_controller, &WalletController::walletRemoved, this, &BitcoinGUI::removeWallet); @@ -991,6 +1026,13 @@ void BitcoinGUI::updateHeadersSyncProgressLabel() progressBarLabel->setText(tr("Syncing Headers (%1%)…").arg(QString::number(100.0 / (headersTipHeight+estHeadersLeft)*headersTipHeight, 'f', 1))); } +void BitcoinGUI::updateHeadersPresyncProgressLabel(int64_t height, const QDateTime& blockDate) +{ + int estHeadersLeft = blockDate.secsTo(QDateTime::currentDateTime()) / Params().GetConsensus().nPowTargetSpacing; + if (estHeadersLeft > HEADER_HEIGHT_DELTA_SYNC) + progressBarLabel->setText(tr("Pre-syncing Headers (%1%)…").arg(QString::number(100.0 / (height+estHeadersLeft)*height, 'f', 1))); +} + void BitcoinGUI::openOptionsDialogWithTab(OptionsDialog::Tab tab) { if (!clientModel || !clientModel->getOptionsModel()) @@ -999,11 +1041,12 @@ void BitcoinGUI::openOptionsDialogWithTab(OptionsDialog::Tab tab) auto dlg = new OptionsDialog(this, enableWallet); connect(dlg, &OptionsDialog::quitOnReset, this, &BitcoinGUI::quitRequested); dlg->setCurrentTab(tab); + dlg->setClientModel(clientModel); dlg->setModel(clientModel->getOptionsModel()); GUIUtil::ShowModalDialogAsynchronously(dlg); } -void BitcoinGUI::setNumBlocks(int count, const QDateTime& blockDate, double nVerificationProgress, bool header, SynchronizationState sync_state) +void BitcoinGUI::setNumBlocks(int count, const QDateTime& blockDate, double nVerificationProgress, SyncType synctype, SynchronizationState sync_state) { // Disabling macOS App Nap on initial sync, disk and reindex operations. #ifdef Q_OS_MACOS @@ -1016,8 +1059,8 @@ void BitcoinGUI::setNumBlocks(int count, const QDateTime& blockDate, double nVer if (modalOverlay) { - if (header) - modalOverlay->setKnownBestHeight(count, blockDate); + if (synctype != SyncType::BLOCK_SYNC) + modalOverlay->setKnownBestHeight(count, blockDate, synctype == SyncType::HEADER_PRESYNC); else modalOverlay->tipUpdate(count, blockDate, nVerificationProgress); } @@ -1031,7 +1074,10 @@ void BitcoinGUI::setNumBlocks(int count, const QDateTime& blockDate, double nVer enum BlockSource blockSource = clientModel->getBlockSource(); switch (blockSource) { case BlockSource::NETWORK: - if (header) { + if (synctype == SyncType::HEADER_PRESYNC) { + updateHeadersPresyncProgressLabel(count, blockDate); + return; + } else if (synctype == SyncType::HEADER_SYNC) { updateHeadersSyncProgressLabel(); return; } @@ -1039,7 +1085,7 @@ void BitcoinGUI::setNumBlocks(int count, const QDateTime& blockDate, double nVer updateHeadersSyncProgressLabel(); break; case BlockSource::DISK: - if (header) { + if (synctype != SyncType::BLOCK_SYNC) { progressBarLabel->setText(tr("Indexing blocks on disk…")); } else { progressBarLabel->setText(tr("Processing blocks on disk…")); @@ -1049,7 +1095,7 @@ void BitcoinGUI::setNumBlocks(int count, const QDateTime& blockDate, double nVer progressBarLabel->setText(tr("Reindexing blocks on disk…")); break; case BlockSource::NONE: - if (header) { + if (synctype != SyncType::BLOCK_SYNC) { return; } progressBarLabel->setText(tr("Connecting to peers…")); @@ -1318,6 +1364,12 @@ void BitcoinGUI::setEncryptionStatus(int status) { switch(status) { + case WalletModel::NoKeys: + labelWalletEncryptionIcon->hide(); + encryptWalletAction->setChecked(false); + changePassphraseAction->setEnabled(false); + encryptWalletAction->setEnabled(false); + break; case WalletModel::Unencrypted: labelWalletEncryptionIcon->hide(); encryptWalletAction->setChecked(false); diff --git a/src/qt/bitcoingui.h b/src/qt/bitcoingui.h index 6272d5d947..912e9b95aa 100644 --- a/src/qt/bitcoingui.h +++ b/src/qt/bitcoingui.h @@ -10,6 +10,7 @@ #endif #include <qt/bitcoinunits.h> +#include <qt/clientmodel.h> #include <qt/guiutil.h> #include <qt/optionsdialog.h> @@ -28,7 +29,6 @@ #include <memory> -class ClientModel; class NetworkStyle; class Notificator; class OptionsModel; @@ -157,6 +157,7 @@ private: QAction* m_create_wallet_action{nullptr}; QAction* m_open_wallet_action{nullptr}; QMenu* m_open_wallet_menu{nullptr}; + QAction* m_restore_wallet_action{nullptr}; QAction* m_close_wallet_action{nullptr}; QAction* m_close_all_wallets_action{nullptr}; QAction* m_wallet_selector_label_action = nullptr; @@ -207,6 +208,7 @@ private: void updateNetworkState(); void updateHeadersSyncProgressLabel(); + void updateHeadersPresyncProgressLabel(int64_t height, const QDateTime& blockDate); /** Open the OptionsDialog on the specified tab index */ void openOptionsDialogWithTab(OptionsDialog::Tab tab); @@ -225,7 +227,7 @@ public Q_SLOTS: /** Set network state shown in the UI */ void setNetworkActive(bool network_active); /** Set number of blocks and last block date shown in the UI */ - void setNumBlocks(int count, const QDateTime& blockDate, double nVerificationProgress, bool headers, SynchronizationState sync_state); + void setNumBlocks(int count, const QDateTime& blockDate, double nVerificationProgress, SyncType synctype, SynchronizationState sync_state); /** Notify the user of an event from the core network or transaction handling code. @param[in] title the message box / notification title diff --git a/src/qt/bitcoinstrings.cpp b/src/qt/bitcoinstrings.cpp index ba804d6955..3df4d4d921 100644 --- a/src/qt/bitcoinstrings.cpp +++ b/src/qt/bitcoinstrings.cpp @@ -14,9 +14,28 @@ QT_TRANSLATE_NOOP("bitcoin-core", "" "%s corrupt. Try using the wallet tool bitcoin-wallet to salvage or restoring " "a backup."), QT_TRANSLATE_NOOP("bitcoin-core", "" +"%s request to listen on port %u. This port is considered \"bad\" and thus it " +"is unlikely that any Bitcoin Core peers connect to it. See doc/p2p-bad-ports." +"md for details and a full list."), +QT_TRANSLATE_NOOP("bitcoin-core", "" "-maxtxfee is set very high! Fees this large could be paid on a single " "transaction."), QT_TRANSLATE_NOOP("bitcoin-core", "" +"-reindex-chainstate option is not compatible with -blockfilterindex. Please " +"temporarily disable blockfilterindex while using -reindex-chainstate, or " +"replace -reindex-chainstate with -reindex to fully rebuild all indexes."), +QT_TRANSLATE_NOOP("bitcoin-core", "" +"-reindex-chainstate option is not compatible with -coinstatsindex. Please " +"temporarily disable coinstatsindex while using -reindex-chainstate, or " +"replace -reindex-chainstate with -reindex to fully rebuild all indexes."), +QT_TRANSLATE_NOOP("bitcoin-core", "" +"-reindex-chainstate option is not compatible with -txindex. Please " +"temporarily disable txindex while using -reindex-chainstate, or replace -" +"reindex-chainstate with -reindex to fully rebuild all indexes."), +QT_TRANSLATE_NOOP("bitcoin-core", "" +"Assumed-valid: last wallet synchronisation goes beyond available block data. " +"You need to wait for the background validation chain to download more blocks."), +QT_TRANSLATE_NOOP("bitcoin-core", "" "Cannot downgrade wallet from version %i to version %i. Wallet version " "unchanged."), QT_TRANSLATE_NOOP("bitcoin-core", "" @@ -41,6 +60,9 @@ QT_TRANSLATE_NOOP("bitcoin-core", "" "Error reading %s! Transaction data may be missing or incorrect. Rescanning " "wallet."), QT_TRANSLATE_NOOP("bitcoin-core", "" +"Error: Address book data in wallet cannot be identified to belong to " +"migrated wallets"), +QT_TRANSLATE_NOOP("bitcoin-core", "" "Error: Dumpfile format record is incorrect. Got \"%s\", expected \"format\"."), QT_TRANSLATE_NOOP("bitcoin-core", "" "Error: Dumpfile identifier record is incorrect. Got \"%s\", expected \"%s\"."), @@ -48,10 +70,20 @@ QT_TRANSLATE_NOOP("bitcoin-core", "" "Error: Dumpfile version is not supported. This version of bitcoin-wallet " "only supports version 1 dumpfiles. Got dumpfile with version %s"), QT_TRANSLATE_NOOP("bitcoin-core", "" +"Error: Duplicate descriptors created during migration. Your wallet may be " +"corrupted."), +QT_TRANSLATE_NOOP("bitcoin-core", "" "Error: Legacy wallets only support the \"legacy\", \"p2sh-segwit\", and " "\"bech32\" address types"), QT_TRANSLATE_NOOP("bitcoin-core", "" -"Error: Listening for incoming connections failed (listen returned error %s)"), +"Error: Transaction %s in wallet cannot be identified to belong to migrated " +"wallets"), +QT_TRANSLATE_NOOP("bitcoin-core", "" +"Error: Unable to produce descriptors for this legacy wallet. Make sure the " +"wallet is unlocked first"), +QT_TRANSLATE_NOOP("bitcoin-core", "" +"Failed to rename invalid peers.dat file. Please move or delete it and try " +"again."), QT_TRANSLATE_NOOP("bitcoin-core", "" "Fee estimation failed. Fallbackfee is disabled. Wait a few blocks or enable -" "fallbackfee."), @@ -59,6 +91,9 @@ QT_TRANSLATE_NOOP("bitcoin-core", "" "File %s already exists. If you are sure this is what you want, move it out " "of the way first."), QT_TRANSLATE_NOOP("bitcoin-core", "" +"Incompatible options: -dnsseed=1 was explicitly specified, but -onlynet " +"forbids connections to IPv4/IPv6"), +QT_TRANSLATE_NOOP("bitcoin-core", "" "Invalid amount for -maxtxfee=<amount>: '%s' (must be at least the minrelay " "fee of %s to prevent stuck transactions)"), QT_TRANSLATE_NOOP("bitcoin-core", "" @@ -77,6 +112,13 @@ QT_TRANSLATE_NOOP("bitcoin-core", "" "No wallet file format provided. To use createfromdump, -format=<format> must " "be provided."), QT_TRANSLATE_NOOP("bitcoin-core", "" +"Outbound connections restricted to Tor (-onlynet=onion) but the proxy for " +"reaching the Tor network is explicitly forbidden: -onion=0"), +QT_TRANSLATE_NOOP("bitcoin-core", "" +"Outbound connections restricted to Tor (-onlynet=onion) but the proxy for " +"reaching the Tor network is not provided: none of -proxy, -onion or -" +"listenonion is given"), +QT_TRANSLATE_NOOP("bitcoin-core", "" "Please check that your computer's date and time are correct! If your clock " "is wrong, %s will not work properly."), QT_TRANSLATE_NOOP("bitcoin-core", "" @@ -85,6 +127,9 @@ QT_TRANSLATE_NOOP("bitcoin-core", "" QT_TRANSLATE_NOOP("bitcoin-core", "" "Prune configured below the minimum of %d MiB. Please use a higher number."), QT_TRANSLATE_NOOP("bitcoin-core", "" +"Prune mode is incompatible with -reindex-chainstate. Use full -reindex " +"instead."), +QT_TRANSLATE_NOOP("bitcoin-core", "" "Prune: last wallet synchronisation goes beyond pruned data. You need to -" "reindex (download the whole blockchain again in case of pruned node)"), QT_TRANSLATE_NOOP("bitcoin-core", "" @@ -129,6 +174,21 @@ QT_TRANSLATE_NOOP("bitcoin-core", "" "Unknown wallet file format \"%s\" provided. Please provide one of \"bdb\" or " "\"sqlite\"."), QT_TRANSLATE_NOOP("bitcoin-core", "" +"Unrecognized descriptor found. Loading wallet %s\n" +"\n" +"The wallet might had been created on a newer version.\n" +"Please try running the latest software version.\n"), +QT_TRANSLATE_NOOP("bitcoin-core", "" +"Unsupported category-specific logging level -loglevel=%s. Expected -" +"loglevel=<category>:<loglevel>. Valid categories: %s. Valid loglevels: %s."), +QT_TRANSLATE_NOOP("bitcoin-core", "" +"Unsupported chainstate database format found. Please restart with -reindex-" +"chainstate. This will rebuild the chainstate database."), +QT_TRANSLATE_NOOP("bitcoin-core", "" +"Wallet created successfully. The legacy wallet type is being deprecated and " +"support for creating and opening legacy wallets will be removed in the " +"future."), +QT_TRANSLATE_NOOP("bitcoin-core", "" "Warning: Dumpfile wallet format \"%s\" does not match command line specified " "format \"%s\"."), QT_TRANSLATE_NOOP("bitcoin-core", "" @@ -142,6 +202,12 @@ QT_TRANSLATE_NOOP("bitcoin-core", "" QT_TRANSLATE_NOOP("bitcoin-core", "" "You need to rebuild the database using -reindex to go back to unpruned " "mode. This will redownload the entire blockchain"), +QT_TRANSLATE_NOOP("bitcoin-core", "" +"\n" +"Unable to cleanup failed migration"), +QT_TRANSLATE_NOOP("bitcoin-core", "" +"\n" +"Unable to restore backup of wallet."), QT_TRANSLATE_NOOP("bitcoin-core", "%s is set very high!"), QT_TRANSLATE_NOOP("bitcoin-core", "-maxmempool must be at least %d MB"), QT_TRANSLATE_NOOP("bitcoin-core", "A fatal internal error occurred, see debug.log for details"), @@ -169,16 +235,25 @@ QT_TRANSLATE_NOOP("bitcoin-core", "Error loading block database"), QT_TRANSLATE_NOOP("bitcoin-core", "Error opening block database"), QT_TRANSLATE_NOOP("bitcoin-core", "Error reading from database, shutting down."), QT_TRANSLATE_NOOP("bitcoin-core", "Error reading next record from wallet database"), -QT_TRANSLATE_NOOP("bitcoin-core", "Error upgrading chainstate database"), +QT_TRANSLATE_NOOP("bitcoin-core", "Error: Could not add watchonly tx to watchonly wallet"), +QT_TRANSLATE_NOOP("bitcoin-core", "Error: Could not delete watchonly transactions"), QT_TRANSLATE_NOOP("bitcoin-core", "Error: Couldn't create cursor into database"), QT_TRANSLATE_NOOP("bitcoin-core", "Error: Disk space is low for %s"), QT_TRANSLATE_NOOP("bitcoin-core", "Error: Dumpfile checksum does not match. Computed %s, expected %s"), +QT_TRANSLATE_NOOP("bitcoin-core", "Error: Failed to create new watchonly wallet"), QT_TRANSLATE_NOOP("bitcoin-core", "Error: Got key that was not hex: %s"), QT_TRANSLATE_NOOP("bitcoin-core", "Error: Got value that was not hex: %s"), QT_TRANSLATE_NOOP("bitcoin-core", "Error: Keypool ran out, please call keypoolrefill first"), QT_TRANSLATE_NOOP("bitcoin-core", "Error: Missing checksum"), QT_TRANSLATE_NOOP("bitcoin-core", "Error: No %s addresses available."), +QT_TRANSLATE_NOOP("bitcoin-core", "Error: Not all watchonly txs could be deleted"), +QT_TRANSLATE_NOOP("bitcoin-core", "Error: This wallet already uses SQLite"), +QT_TRANSLATE_NOOP("bitcoin-core", "Error: This wallet is already a descriptor wallet"), +QT_TRANSLATE_NOOP("bitcoin-core", "Error: Unable to begin reading all records in the database"), +QT_TRANSLATE_NOOP("bitcoin-core", "Error: Unable to make a backup of your wallet"), QT_TRANSLATE_NOOP("bitcoin-core", "Error: Unable to parse version %u as a uint32_t"), +QT_TRANSLATE_NOOP("bitcoin-core", "Error: Unable to read all records in the database"), +QT_TRANSLATE_NOOP("bitcoin-core", "Error: Unable to remove watchonly address book data"), QT_TRANSLATE_NOOP("bitcoin-core", "Error: Unable to write record to new wallet"), QT_TRANSLATE_NOOP("bitcoin-core", "Failed to listen on any port. Use -listen=0 if you want this."), QT_TRANSLATE_NOOP("bitcoin-core", "Failed to rescan the wallet during initialization"), @@ -199,6 +274,7 @@ QT_TRANSLATE_NOOP("bitcoin-core", "Invalid amount for -discardfee=<amount>: '%s' QT_TRANSLATE_NOOP("bitcoin-core", "Invalid amount for -fallbackfee=<amount>: '%s'"), QT_TRANSLATE_NOOP("bitcoin-core", "Invalid amount for -paytxfee=<amount>: '%s' (must be at least %s)"), QT_TRANSLATE_NOOP("bitcoin-core", "Invalid netmask specified in -whitelist: '%s'"), +QT_TRANSLATE_NOOP("bitcoin-core", "Listening for incoming connections failed (listen returned error %s)"), QT_TRANSLATE_NOOP("bitcoin-core", "Loading P2P addresses…"), QT_TRANSLATE_NOOP("bitcoin-core", "Loading banlist…"), QT_TRANSLATE_NOOP("bitcoin-core", "Loading block index…"), @@ -207,10 +283,8 @@ QT_TRANSLATE_NOOP("bitcoin-core", "Missing amount"), QT_TRANSLATE_NOOP("bitcoin-core", "Missing solving data for estimating transaction size"), QT_TRANSLATE_NOOP("bitcoin-core", "Need to specify a port with -whitebind: '%s'"), QT_TRANSLATE_NOOP("bitcoin-core", "No addresses available"), -QT_TRANSLATE_NOOP("bitcoin-core", "No proxy server specified. Use -proxy=<ip> or -proxy=<ip:port>."), QT_TRANSLATE_NOOP("bitcoin-core", "Not enough file descriptors available."), QT_TRANSLATE_NOOP("bitcoin-core", "Prune cannot be configured with a negative value."), -QT_TRANSLATE_NOOP("bitcoin-core", "Prune mode is incompatible with -coinstatsindex."), QT_TRANSLATE_NOOP("bitcoin-core", "Prune mode is incompatible with -txindex."), QT_TRANSLATE_NOOP("bitcoin-core", "Pruning blockstore…"), QT_TRANSLATE_NOOP("bitcoin-core", "Reducing -maxconnections from %d to %d, because of system limitations."), @@ -241,21 +315,24 @@ QT_TRANSLATE_NOOP("bitcoin-core", "Transaction has too long of a mempool chain") QT_TRANSLATE_NOOP("bitcoin-core", "Transaction must have at least one recipient"), QT_TRANSLATE_NOOP("bitcoin-core", "Transaction needs a change address, but we can't generate it."), QT_TRANSLATE_NOOP("bitcoin-core", "Transaction too large"), +QT_TRANSLATE_NOOP("bitcoin-core", "Unable to allocate memory for -maxsigcachesize: '%s' MiB"), QT_TRANSLATE_NOOP("bitcoin-core", "Unable to bind to %s on this computer (bind returned error %s)"), QT_TRANSLATE_NOOP("bitcoin-core", "Unable to bind to %s on this computer. %s is probably already running."), QT_TRANSLATE_NOOP("bitcoin-core", "Unable to create the PID file '%s': %s"), +QT_TRANSLATE_NOOP("bitcoin-core", "Unable to find UTXO for external input"), QT_TRANSLATE_NOOP("bitcoin-core", "Unable to generate initial keys"), QT_TRANSLATE_NOOP("bitcoin-core", "Unable to generate keys"), QT_TRANSLATE_NOOP("bitcoin-core", "Unable to open %s for writing"), QT_TRANSLATE_NOOP("bitcoin-core", "Unable to parse -maxuploadtarget: '%s'"), QT_TRANSLATE_NOOP("bitcoin-core", "Unable to start HTTP server. See debug log for details."), +QT_TRANSLATE_NOOP("bitcoin-core", "Unable to unload the wallet before migrating"), QT_TRANSLATE_NOOP("bitcoin-core", "Unknown -blockfilterindex value %s."), QT_TRANSLATE_NOOP("bitcoin-core", "Unknown address type '%s'"), QT_TRANSLATE_NOOP("bitcoin-core", "Unknown change type '%s'"), QT_TRANSLATE_NOOP("bitcoin-core", "Unknown network specified in -onlynet: '%s'"), QT_TRANSLATE_NOOP("bitcoin-core", "Unknown new rules activated (versionbit %i)"), +QT_TRANSLATE_NOOP("bitcoin-core", "Unsupported global logging level -loglevel=%s. Valid values: %s."), QT_TRANSLATE_NOOP("bitcoin-core", "Unsupported logging category %s=%s."), -QT_TRANSLATE_NOOP("bitcoin-core", "Upgrading UTXO database"), QT_TRANSLATE_NOOP("bitcoin-core", "User Agent comment (%s) contains unsafe characters."), QT_TRANSLATE_NOOP("bitcoin-core", "Verifying blocks…"), QT_TRANSLATE_NOOP("bitcoin-core", "Verifying wallet(s)…"), diff --git a/src/qt/clientmodel.cpp b/src/qt/clientmodel.cpp index f41da519df..092ffe7e5b 100644 --- a/src/qt/clientmodel.cpp +++ b/src/qt/clientmodel.cpp @@ -215,26 +215,26 @@ QString ClientModel::blocksDir() const return GUIUtil::PathToQString(gArgs.GetBlocksDirPath()); } -void ClientModel::TipChanged(SynchronizationState sync_state, interfaces::BlockTip tip, double verification_progress, bool header) +void ClientModel::TipChanged(SynchronizationState sync_state, interfaces::BlockTip tip, double verification_progress, SyncType synctype) { - if (header) { + if (synctype == SyncType::HEADER_SYNC) { // cache best headers time and height to reduce future cs_main locks cachedBestHeaderHeight = tip.block_height; cachedBestHeaderTime = tip.block_time; - } else { + } else if (synctype == SyncType::BLOCK_SYNC) { m_cached_num_blocks = tip.block_height; WITH_LOCK(m_cached_tip_mutex, m_cached_tip_blocks = tip.block_hash;); } // Throttle GUI notifications about (a) blocks during initial sync, and (b) both blocks and headers during reindex. - const bool throttle = (sync_state != SynchronizationState::POST_INIT && !header) || sync_state == SynchronizationState::INIT_REINDEX; + const bool throttle = (sync_state != SynchronizationState::POST_INIT && synctype == SyncType::BLOCK_SYNC) || sync_state == SynchronizationState::INIT_REINDEX; const int64_t now = throttle ? GetTimeMillis() : 0; - int64_t& nLastUpdateNotification = header ? nLastHeaderTipUpdateNotification : nLastBlockTipUpdateNotification; + int64_t& nLastUpdateNotification = synctype != SyncType::BLOCK_SYNC ? nLastHeaderTipUpdateNotification : nLastBlockTipUpdateNotification; if (throttle && now < nLastUpdateNotification + count_milliseconds(MODEL_UPDATE_DELAY)) { return; } - Q_EMIT numBlocksChanged(tip.block_height, QDateTime::fromSecsSinceEpoch(tip.block_time), verification_progress, header, sync_state); + Q_EMIT numBlocksChanged(tip.block_height, QDateTime::fromSecsSinceEpoch(tip.block_time), verification_progress, synctype, sync_state); nLastUpdateNotification = now; } @@ -264,11 +264,11 @@ void ClientModel::subscribeToCoreSignals() }); m_handler_notify_block_tip = m_node.handleNotifyBlockTip( [this](SynchronizationState sync_state, interfaces::BlockTip tip, double verification_progress) { - TipChanged(sync_state, tip, verification_progress, /*header=*/false); + TipChanged(sync_state, tip, verification_progress, SyncType::BLOCK_SYNC); }); m_handler_notify_header_tip = m_node.handleNotifyHeaderTip( - [this](SynchronizationState sync_state, interfaces::BlockTip tip, double verification_progress) { - TipChanged(sync_state, tip, verification_progress, /*header=*/true); + [this](SynchronizationState sync_state, interfaces::BlockTip tip, bool presync) { + TipChanged(sync_state, tip, /*verification_progress=*/0.0, presync ? SyncType::HEADER_PRESYNC : SyncType::HEADER_SYNC); }); } diff --git a/src/qt/clientmodel.h b/src/qt/clientmodel.h index b10ffb4523..4a6abd6a76 100644 --- a/src/qt/clientmodel.h +++ b/src/qt/clientmodel.h @@ -37,6 +37,12 @@ enum class BlockSource { NETWORK }; +enum class SyncType { + HEADER_PRESYNC, + HEADER_SYNC, + BLOCK_SYNC +}; + enum NumConnections { CONNECTIONS_NONE = 0, CONNECTIONS_IN = (1U << 0), @@ -105,13 +111,13 @@ private: //! A thread to interact with m_node asynchronously QThread* const m_thread; - void TipChanged(SynchronizationState sync_state, interfaces::BlockTip tip, double verification_progress, bool header); + void TipChanged(SynchronizationState sync_state, interfaces::BlockTip tip, double verification_progress, SyncType synctype) EXCLUSIVE_LOCKS_REQUIRED(!m_cached_tip_mutex); void subscribeToCoreSignals(); void unsubscribeFromCoreSignals(); Q_SIGNALS: void numConnectionsChanged(int count); - void numBlocksChanged(int count, const QDateTime& blockDate, double nVerificationProgress, bool header, SynchronizationState sync_state); + void numBlocksChanged(int count, const QDateTime& blockDate, double nVerificationProgress, SyncType header, SynchronizationState sync_state); void mempoolSizeChanged(long count, size_t mempoolSizeInBytes); void networkActiveChanged(bool networkActive); void alertsChanged(const QString &warnings); diff --git a/src/qt/forms/intro.ui b/src/qt/forms/intro.ui index a1e94f99e6..9ab91f6aa9 100644 --- a/src/qt/forms/intro.ui +++ b/src/qt/forms/intro.ui @@ -203,7 +203,7 @@ <item> <widget class="QLabel" name="lblExplanation1"> <property name="text"> - <string>When you click OK, %1 will begin to download and process the full %4 block chain (%2GB) starting with the earliest transactions in %3 when %4 initially launched.</string> + <string>When you click OK, %1 will begin to download and process the full %4 block chain (%2 GB) starting with the earliest transactions in %3 when %4 initially launched.</string> </property> <property name="wordWrap"> <bool>true</bool> diff --git a/src/qt/forms/optionsdialog.ui b/src/qt/forms/optionsdialog.ui index 5438811aff..582f02132a 100644 --- a/src/qt/forms/optionsdialog.ui +++ b/src/qt/forms/optionsdialog.ui @@ -896,7 +896,7 @@ <item> <widget class="QLabel" name="overriddenByCommandLineInfoLabel"> <property name="text"> - <string>Options set in this dialog are overridden by the command line or in the configuration file:</string> + <string>Options set in this dialog are overridden by the command line:</string> </property> <property name="textFormat"> <enum>Qt::PlainText</enum> diff --git a/src/qt/forms/sendcoinsentry.ui b/src/qt/forms/sendcoinsentry.ui index 934363af1f..ffebc316b1 100644 --- a/src/qt/forms/sendcoinsentry.ui +++ b/src/qt/forms/sendcoinsentry.ui @@ -1,7 +1,7 @@ <?xml version="1.0" encoding="UTF-8"?> <ui version="4.0"> <class>SendCoinsEntry</class> - <widget class="QStackedWidget" name="SendCoinsEntry"> + <widget class="QWidget" name="SendCoinsEntry"> <property name="geometry"> <rect> <x>0</x> @@ -16,1244 +16,204 @@ <property name="autoFillBackground"> <bool>false</bool> </property> - <widget class="QFrame" name="SendCoins"> - <property name="frameShape"> - <enum>QFrame::NoFrame</enum> + <layout class="QGridLayout" name="gridLayout"> + <property name="topMargin"> + <number>8</number> </property> - <layout class="QGridLayout" name="gridLayout"> - <property name="topMargin"> - <number>8</number> - </property> - <property name="bottomMargin"> - <number>4</number> - </property> - <property name="horizontalSpacing"> - <number>12</number> - </property> - <property name="verticalSpacing"> - <number>8</number> - </property> - <item row="0" column="0"> - <widget class="QLabel" name="payToLabel"> - <property name="text"> - <string>Pay &To:</string> - </property> - <property name="alignment"> - <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> - </property> - <property name="buddy"> - <cstring>payTo</cstring> - </property> - </widget> - </item> - <item row="0" column="1"> - <layout class="QHBoxLayout" name="payToLayout"> - <property name="spacing"> - <number>0</number> - </property> - <item> - <widget class="QValidatedLineEdit" name="payTo"> - <property name="toolTip"> - <string>The Bitcoin address to send the payment to</string> - </property> - </widget> - </item> - <item> - <widget class="QToolButton" name="addressBookButton"> - <property name="toolTip"> - <string>Choose previously used address</string> - </property> - <property name="text"> - <string/> - </property> - <property name="icon"> - <iconset resource="../bitcoin.qrc"> - <normaloff>:/icons/address-book</normaloff>:/icons/address-book</iconset> - </property> - <property name="iconSize"> - <size> - <width>22</width> - <height>22</height> - </size> - </property> - <property name="shortcut"> - <string>Alt+A</string> - </property> - </widget> - </item> - <item> - <widget class="QToolButton" name="pasteButton"> - <property name="toolTip"> - <string>Paste address from clipboard</string> - </property> - <property name="text"> - <string/> - </property> - <property name="icon"> - <iconset resource="../bitcoin.qrc"> - <normaloff>:/icons/editpaste</normaloff>:/icons/editpaste</iconset> - </property> - <property name="iconSize"> - <size> - <width>22</width> - <height>22</height> - </size> - </property> - <property name="shortcut"> - <string>Alt+P</string> - </property> - </widget> - </item> - <item> - <widget class="QToolButton" name="deleteButton"> - <property name="toolTip"> - <string>Remove this entry</string> - </property> - <property name="text"> - <string/> - </property> - <property name="icon"> - <iconset resource="../bitcoin.qrc"> - <normaloff>:/icons/remove</normaloff>:/icons/remove</iconset> - </property> - <property name="iconSize"> - <size> - <width>22</width> - <height>22</height> - </size> - </property> - </widget> - </item> - </layout> - </item> - <item row="1" column="0"> - <widget class="QLabel" name="labellLabel"> - <property name="text"> - <string>&Label:</string> - </property> - <property name="alignment"> - <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> - </property> - <property name="buddy"> - <cstring>addAsLabel</cstring> - </property> - </widget> - </item> - <item row="1" column="1"> - <widget class="QLineEdit" name="addAsLabel"> - <property name="toolTip"> - <string>Enter a label for this address to add it to the list of used addresses</string> - </property> - <property name="placeholderText"> - <string>Enter a label for this address to add it to the list of used addresses</string> - </property> - </widget> - </item> - <item row="2" column="0"> - <widget class="QLabel" name="amountLabel"> - <property name="text"> - <string>A&mount:</string> - </property> - <property name="alignment"> - <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> - </property> - <property name="buddy"> - <cstring>payAmount</cstring> - </property> - </widget> - </item> - <item row="2" column="1"> - <layout class="QHBoxLayout" name="horizontalLayoutAmount" stretch="0,1,0"> - <item> - <widget class="BitcoinAmountField" name="payAmount"> - <property name="toolTip"> - <string>The amount to send in the selected unit</string> - </property> - </widget> - </item> - <item> - <widget class="QCheckBox" name="checkboxSubtractFeeFromAmount"> - <property name="toolTip"> - <string>The fee will be deducted from the amount being sent. The recipient will receive less bitcoins than you enter in the amount field. If multiple recipients are selected, the fee is split equally.</string> - </property> - <property name="text"> - <string>S&ubtract fee from amount</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="useAvailableBalanceButton"> - <property name="text"> - <string>Use available balance</string> - </property> - </widget> - </item> - </layout> - </item> - <item row="3" column="0"> - <widget class="QLabel" name="messageLabel"> - <property name="text"> - <string>Message:</string> - </property> - <property name="alignment"> - <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> - </property> - </widget> - </item> - <item row="3" column="1"> - <widget class="QLabel" name="messageTextLabel"> - <property name="toolTip"> - <string>A message that was attached to the bitcoin: URI which will be stored with the transaction for your reference. Note: This message will not be sent over the Bitcoin network.</string> - </property> - <property name="textFormat"> - <enum>Qt::PlainText</enum> - </property> - </widget> - </item> - <item row="4" column="0" colspan="2"> - <widget class="Line" name="line"> - <property name="orientation"> - <enum>Qt::Horizontal</enum> - </property> - </widget> - </item> - </layout> - </widget> - <widget class="QFrame" name="SendCoins_UnauthenticatedPaymentRequest"> - <property name="palette"> - <palette> - <active> - <colorrole role="WindowText"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>0</red> - <green>0</green> - <blue>0</blue> - </color> - </brush> - </colorrole> - <colorrole role="Button"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>255</red> - <green>255</green> - <blue>127</blue> - </color> - </brush> - </colorrole> - <colorrole role="Light"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>255</red> - <green>255</green> - <blue>255</blue> - </color> - </brush> - </colorrole> - <colorrole role="Midlight"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>255</red> - <green>255</green> - <blue>191</blue> - </color> - </brush> - </colorrole> - <colorrole role="Dark"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>127</red> - <green>127</green> - <blue>63</blue> - </color> - </brush> - </colorrole> - <colorrole role="Mid"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>170</red> - <green>170</green> - <blue>84</blue> - </color> - </brush> - </colorrole> - <colorrole role="Text"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>0</red> - <green>0</green> - <blue>0</blue> - </color> - </brush> - </colorrole> - <colorrole role="BrightText"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>255</red> - <green>255</green> - <blue>255</blue> - </color> - </brush> - </colorrole> - <colorrole role="ButtonText"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>0</red> - <green>0</green> - <blue>0</blue> - </color> - </brush> - </colorrole> - <colorrole role="Base"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>255</red> - <green>255</green> - <blue>255</blue> - </color> - </brush> - </colorrole> - <colorrole role="Window"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>255</red> - <green>255</green> - <blue>127</blue> - </color> - </brush> - </colorrole> - <colorrole role="Shadow"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>0</red> - <green>0</green> - <blue>0</blue> - </color> - </brush> - </colorrole> - <colorrole role="AlternateBase"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>255</red> - <green>255</green> - <blue>191</blue> - </color> - </brush> - </colorrole> - <colorrole role="ToolTipBase"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>255</red> - <green>255</green> - <blue>220</blue> - </color> - </brush> - </colorrole> - <colorrole role="ToolTipText"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>0</red> - <green>0</green> - <blue>0</blue> - </color> - </brush> - </colorrole> - </active> - <inactive> - <colorrole role="WindowText"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>0</red> - <green>0</green> - <blue>0</blue> - </color> - </brush> - </colorrole> - <colorrole role="Button"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>255</red> - <green>255</green> - <blue>127</blue> - </color> - </brush> - </colorrole> - <colorrole role="Light"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>255</red> - <green>255</green> - <blue>255</blue> - </color> - </brush> - </colorrole> - <colorrole role="Midlight"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>255</red> - <green>255</green> - <blue>191</blue> - </color> - </brush> - </colorrole> - <colorrole role="Dark"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>127</red> - <green>127</green> - <blue>63</blue> - </color> - </brush> - </colorrole> - <colorrole role="Mid"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>170</red> - <green>170</green> - <blue>84</blue> - </color> - </brush> - </colorrole> - <colorrole role="Text"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>0</red> - <green>0</green> - <blue>0</blue> - </color> - </brush> - </colorrole> - <colorrole role="BrightText"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>255</red> - <green>255</green> - <blue>255</blue> - </color> - </brush> - </colorrole> - <colorrole role="ButtonText"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>0</red> - <green>0</green> - <blue>0</blue> - </color> - </brush> - </colorrole> - <colorrole role="Base"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>255</red> - <green>255</green> - <blue>255</blue> - </color> - </brush> - </colorrole> - <colorrole role="Window"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>255</red> - <green>255</green> - <blue>127</blue> - </color> - </brush> - </colorrole> - <colorrole role="Shadow"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>0</red> - <green>0</green> - <blue>0</blue> - </color> - </brush> - </colorrole> - <colorrole role="AlternateBase"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>255</red> - <green>255</green> - <blue>191</blue> - </color> - </brush> - </colorrole> - <colorrole role="ToolTipBase"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>255</red> - <green>255</green> - <blue>220</blue> - </color> - </brush> - </colorrole> - <colorrole role="ToolTipText"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>0</red> - <green>0</green> - <blue>0</blue> - </color> - </brush> - </colorrole> - </inactive> - <disabled> - <colorrole role="WindowText"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>127</red> - <green>127</green> - <blue>63</blue> - </color> - </brush> - </colorrole> - <colorrole role="Button"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>255</red> - <green>255</green> - <blue>127</blue> - </color> - </brush> - </colorrole> - <colorrole role="Light"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>255</red> - <green>255</green> - <blue>255</blue> - </color> - </brush> - </colorrole> - <colorrole role="Midlight"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>255</red> - <green>255</green> - <blue>191</blue> - </color> - </brush> - </colorrole> - <colorrole role="Dark"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>127</red> - <green>127</green> - <blue>63</blue> - </color> - </brush> - </colorrole> - <colorrole role="Mid"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>170</red> - <green>170</green> - <blue>84</blue> - </color> - </brush> - </colorrole> - <colorrole role="Text"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>127</red> - <green>127</green> - <blue>63</blue> - </color> - </brush> - </colorrole> - <colorrole role="BrightText"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>255</red> - <green>255</green> - <blue>255</blue> - </color> - </brush> - </colorrole> - <colorrole role="ButtonText"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>127</red> - <green>127</green> - <blue>63</blue> - </color> - </brush> - </colorrole> - <colorrole role="Base"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>255</red> - <green>255</green> - <blue>127</blue> - </color> - </brush> - </colorrole> - <colorrole role="Window"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>255</red> - <green>255</green> - <blue>127</blue> - </color> - </brush> - </colorrole> - <colorrole role="Shadow"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>0</red> - <green>0</green> - <blue>0</blue> - </color> - </brush> - </colorrole> - <colorrole role="AlternateBase"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>255</red> - <green>255</green> - <blue>127</blue> - </color> - </brush> - </colorrole> - <colorrole role="ToolTipBase"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>255</red> - <green>255</green> - <blue>220</blue> - </color> - </brush> - </colorrole> - <colorrole role="ToolTipText"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>0</red> - <green>0</green> - <blue>0</blue> - </color> - </brush> - </colorrole> - </disabled> - </palette> + <property name="bottomMargin"> + <number>4</number> </property> - <property name="toolTip"> - <string>This is an unauthenticated payment request.</string> + <property name="horizontalSpacing"> + <number>12</number> </property> - <property name="autoFillBackground"> - <bool>true</bool> + <property name="verticalSpacing"> + <number>8</number> </property> - <property name="frameShape"> - <enum>QFrame::NoFrame</enum> - </property> - <layout class="QGridLayout" name="gridLayout_is"> - <property name="spacing"> - <number>12</number> - </property> - <item row="0" column="0"> - <widget class="QLabel" name="payToLabel_is"> - <property name="text"> - <string>Pay To:</string> - </property> - <property name="alignment"> - <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> - </property> - </widget> - </item> - <item row="0" column="1"> - <layout class="QHBoxLayout" name="payToLayout_is"> - <property name="spacing"> - <number>0</number> - </property> - <item> - <widget class="QLabel" name="payTo_is"/> - </item> - <item> - <widget class="QToolButton" name="deleteButton_is"> - <property name="toolTip"> - <string>Remove this entry</string> - </property> - <property name="text"> - <string/> - </property> - <property name="icon"> - <iconset resource="../bitcoin.qrc"> - <normaloff>:/icons/remove</normaloff>:/icons/remove</iconset> - </property> - </widget> - </item> - </layout> - </item> - <item row="1" column="0"> - <widget class="QLabel" name="memoLabel_is"> - <property name="text"> - <string>Memo:</string> - </property> - <property name="alignment"> - <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> - </property> - </widget> - </item> - <item row="1" column="1"> - <widget class="QLabel" name="memoTextLabel_is"> - <property name="textFormat"> - <enum>Qt::PlainText</enum> - </property> - </widget> - </item> - <item row="2" column="0"> - <widget class="QLabel" name="amountLabel_is"> - <property name="text"> - <string>A&mount:</string> - </property> - <property name="alignment"> - <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> - </property> - <property name="buddy"> - <cstring>payAmount_is</cstring> - </property> - </widget> - </item> - <item row="2" column="1"> - <widget class="BitcoinAmountField" name="payAmount_is"> - <property name="acceptDrops"> - <bool>false</bool> - </property> - </widget> - </item> - </layout> - </widget> - <widget class="QFrame" name="SendCoins_AuthenticatedPaymentRequest"> - <property name="palette"> - <palette> - <active> - <colorrole role="WindowText"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>0</red> - <green>0</green> - <blue>0</blue> - </color> - </brush> - </colorrole> - <colorrole role="Button"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>140</red> - <green>232</green> - <blue>119</blue> - </color> - </brush> - </colorrole> - <colorrole role="Light"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>230</red> - <green>255</green> - <blue>224</blue> - </color> - </brush> - </colorrole> - <colorrole role="Midlight"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>185</red> - <green>243</green> - <blue>171</blue> - </color> - </brush> - </colorrole> - <colorrole role="Dark"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>70</red> - <green>116</green> - <blue>59</blue> - </color> - </brush> - </colorrole> - <colorrole role="Mid"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>93</red> - <green>155</green> - <blue>79</blue> - </color> - </brush> - </colorrole> - <colorrole role="Text"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>0</red> - <green>0</green> - <blue>0</blue> - </color> - </brush> - </colorrole> - <colorrole role="BrightText"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>155</red> - <green>255</green> - <blue>147</blue> - </color> - </brush> - </colorrole> - <colorrole role="ButtonText"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>0</red> - <green>0</green> - <blue>0</blue> - </color> - </brush> - </colorrole> - <colorrole role="Base"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>119</red> - <green>255</green> - <blue>233</blue> - </color> - </brush> - </colorrole> - <colorrole role="Window"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>140</red> - <green>232</green> - <blue>119</blue> - </color> - </brush> - </colorrole> - <colorrole role="Shadow"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>0</red> - <green>0</green> - <blue>0</blue> - </color> - </brush> - </colorrole> - <colorrole role="AlternateBase"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>197</red> - <green>243</green> - <blue>187</blue> - </color> - </brush> - </colorrole> - <colorrole role="NoRole"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>125</red> - <green>194</green> - <blue>122</blue> - </color> - </brush> - </colorrole> - <colorrole role="ToolTipBase"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>255</red> - <green>255</green> - <blue>220</blue> - </color> - </brush> - </colorrole> - <colorrole role="ToolTipText"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>0</red> - <green>0</green> - <blue>0</blue> - </color> - </brush> - </colorrole> - </active> - <inactive> - <colorrole role="WindowText"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>0</red> - <green>0</green> - <blue>0</blue> - </color> - </brush> - </colorrole> - <colorrole role="Button"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>140</red> - <green>232</green> - <blue>119</blue> - </color> - </brush> - </colorrole> - <colorrole role="Light"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>230</red> - <green>255</green> - <blue>224</blue> - </color> - </brush> - </colorrole> - <colorrole role="Midlight"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>185</red> - <green>243</green> - <blue>171</blue> - </color> - </brush> - </colorrole> - <colorrole role="Dark"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>70</red> - <green>116</green> - <blue>59</blue> - </color> - </brush> - </colorrole> - <colorrole role="Mid"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>93</red> - <green>155</green> - <blue>79</blue> - </color> - </brush> - </colorrole> - <colorrole role="Text"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>0</red> - <green>0</green> - <blue>0</blue> - </color> - </brush> - </colorrole> - <colorrole role="BrightText"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>155</red> - <green>255</green> - <blue>147</blue> - </color> - </brush> - </colorrole> - <colorrole role="ButtonText"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>0</red> - <green>0</green> - <blue>0</blue> - </color> - </brush> - </colorrole> - <colorrole role="Base"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>119</red> - <green>255</green> - <blue>233</blue> - </color> - </brush> - </colorrole> - <colorrole role="Window"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>140</red> - <green>232</green> - <blue>119</blue> - </color> - </brush> - </colorrole> - <colorrole role="Shadow"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>0</red> - <green>0</green> - <blue>0</blue> - </color> - </brush> - </colorrole> - <colorrole role="AlternateBase"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>197</red> - <green>243</green> - <blue>187</blue> - </color> - </brush> - </colorrole> - <colorrole role="NoRole"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>125</red> - <green>194</green> - <blue>122</blue> - </color> - </brush> - </colorrole> - <colorrole role="ToolTipBase"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>255</red> - <green>255</green> - <blue>220</blue> - </color> - </brush> - </colorrole> - <colorrole role="ToolTipText"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>0</red> - <green>0</green> - <blue>0</blue> - </color> - </brush> - </colorrole> - </inactive> - <disabled> - <colorrole role="WindowText"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>70</red> - <green>116</green> - <blue>59</blue> - </color> - </brush> - </colorrole> - <colorrole role="Button"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>140</red> - <green>232</green> - <blue>119</blue> - </color> - </brush> - </colorrole> - <colorrole role="Light"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>230</red> - <green>255</green> - <blue>224</blue> - </color> - </brush> - </colorrole> - <colorrole role="Midlight"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>185</red> - <green>243</green> - <blue>171</blue> - </color> - </brush> - </colorrole> - <colorrole role="Dark"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>70</red> - <green>116</green> - <blue>59</blue> - </color> - </brush> - </colorrole> - <colorrole role="Mid"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>93</red> - <green>155</green> - <blue>79</blue> - </color> - </brush> - </colorrole> - <colorrole role="Text"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>70</red> - <green>116</green> - <blue>59</blue> - </color> - </brush> - </colorrole> - <colorrole role="BrightText"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>155</red> - <green>255</green> - <blue>147</blue> - </color> - </brush> - </colorrole> - <colorrole role="ButtonText"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>70</red> - <green>116</green> - <blue>59</blue> - </color> - </brush> - </colorrole> - <colorrole role="Base"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>140</red> - <green>232</green> - <blue>119</blue> - </color> - </brush> - </colorrole> - <colorrole role="Window"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>140</red> - <green>232</green> - <blue>119</blue> - </color> - </brush> - </colorrole> - <colorrole role="Shadow"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>0</red> - <green>0</green> - <blue>0</blue> - </color> - </brush> - </colorrole> - <colorrole role="AlternateBase"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>140</red> - <green>232</green> - <blue>119</blue> - </color> - </brush> - </colorrole> - <colorrole role="NoRole"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>125</red> - <green>194</green> - <blue>122</blue> - </color> - </brush> - </colorrole> - <colorrole role="ToolTipBase"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>255</red> - <green>255</green> - <blue>220</blue> - </color> - </brush> - </colorrole> - <colorrole role="ToolTipText"> - <brush brushstyle="SolidPattern"> - <color alpha="255"> - <red>0</red> - <green>0</green> - <blue>0</blue> - </color> - </brush> - </colorrole> - </disabled> - </palette> - </property> - <property name="toolTip"> - <string>This is an authenticated payment request.</string> - </property> - <property name="autoFillBackground"> - <bool>true</bool> - </property> - <property name="frameShape"> - <enum>QFrame::NoFrame</enum> - </property> - <layout class="QGridLayout" name="gridLayout_s"> - <property name="spacing"> - <number>12</number> - </property> - <item row="0" column="0"> - <widget class="QLabel" name="payToLabel_s"> - <property name="text"> - <string>Pay To:</string> - </property> - <property name="alignment"> - <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> - </property> - </widget> - </item> - <item row="0" column="1"> - <layout class="QHBoxLayout" name="payToLayout_s"> - <property name="spacing"> - <number>0</number> - </property> - <item> - <widget class="QLabel" name="payTo_s"> - <property name="textFormat"> - <enum>Qt::PlainText</enum> - </property> - </widget> - </item> - <item> - <widget class="QToolButton" name="deleteButton_s"> - <property name="toolTip"> - <string>Remove this entry</string> - </property> - <property name="text"> - <string/> - </property> - <property name="icon"> - <iconset resource="../bitcoin.qrc"> - <normaloff>:/icons/remove</normaloff>:/icons/remove</iconset> - </property> - </widget> - </item> - </layout> - </item> - <item row="1" column="0"> - <widget class="QLabel" name="memoLabel_s"> - <property name="text"> - <string>Memo:</string> - </property> - <property name="alignment"> - <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> - </property> - </widget> - </item> - <item row="1" column="1"> - <widget class="QLabel" name="memoTextLabel_s"> - <property name="textFormat"> - <enum>Qt::PlainText</enum> - </property> - </widget> - </item> - <item row="2" column="0"> - <widget class="QLabel" name="amountLabel_s"> - <property name="text"> - <string>A&mount:</string> - </property> - <property name="alignment"> - <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> - </property> - <property name="buddy"> - <cstring>payAmount_s</cstring> - </property> - </widget> - </item> - <item row="2" column="1"> - <widget class="BitcoinAmountField" name="payAmount_s"> - <property name="acceptDrops"> - <bool>false</bool> - </property> - </widget> - </item> - </layout> - </widget> + <item row="0" column="0"> + <widget class="QLabel" name="payToLabel"> + <property name="text"> + <string>Pay &To:</string> + </property> + <property name="alignment"> + <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> + </property> + <property name="buddy"> + <cstring>payTo</cstring> + </property> + </widget> + </item> + <item row="0" column="1"> + <layout class="QHBoxLayout" name="payToLayout"> + <property name="spacing"> + <number>0</number> + </property> + <item> + <widget class="QValidatedLineEdit" name="payTo"> + <property name="toolTip"> + <string>The Bitcoin address to send the payment to</string> + </property> + </widget> + </item> + <item> + <widget class="QToolButton" name="addressBookButton"> + <property name="toolTip"> + <string>Choose previously used address</string> + </property> + <property name="text"> + <string/> + </property> + <property name="icon"> + <iconset resource="../bitcoin.qrc"> + <normaloff>:/icons/address-book</normaloff>:/icons/address-book</iconset> + </property> + <property name="iconSize"> + <size> + <width>22</width> + <height>22</height> + </size> + </property> + <property name="shortcut"> + <string>Alt+A</string> + </property> + </widget> + </item> + <item> + <widget class="QToolButton" name="pasteButton"> + <property name="toolTip"> + <string>Paste address from clipboard</string> + </property> + <property name="text"> + <string/> + </property> + <property name="icon"> + <iconset resource="../bitcoin.qrc"> + <normaloff>:/icons/editpaste</normaloff>:/icons/editpaste</iconset> + </property> + <property name="iconSize"> + <size> + <width>22</width> + <height>22</height> + </size> + </property> + <property name="shortcut"> + <string>Alt+P</string> + </property> + </widget> + </item> + <item> + <widget class="QToolButton" name="deleteButton"> + <property name="toolTip"> + <string>Remove this entry</string> + </property> + <property name="text"> + <string/> + </property> + <property name="icon"> + <iconset resource="../bitcoin.qrc"> + <normaloff>:/icons/remove</normaloff>:/icons/remove</iconset> + </property> + <property name="iconSize"> + <size> + <width>22</width> + <height>22</height> + </size> + </property> + </widget> + </item> + </layout> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="labellLabel"> + <property name="text"> + <string>&Label:</string> + </property> + <property name="alignment"> + <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> + </property> + <property name="buddy"> + <cstring>addAsLabel</cstring> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QLineEdit" name="addAsLabel"> + <property name="toolTip"> + <string>Enter a label for this address to add it to the list of used addresses</string> + </property> + <property name="placeholderText"> + <string>Enter a label for this address to add it to the list of used addresses</string> + </property> + </widget> + </item> + <item row="2" column="0"> + <widget class="QLabel" name="amountLabel"> + <property name="text"> + <string>A&mount:</string> + </property> + <property name="alignment"> + <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> + </property> + <property name="buddy"> + <cstring>payAmount</cstring> + </property> + </widget> + </item> + <item row="2" column="1"> + <layout class="QHBoxLayout" name="horizontalLayoutAmount" stretch="0,1,0"> + <item> + <widget class="BitcoinAmountField" name="payAmount" native="true"> + <property name="toolTip"> + <string>The amount to send in the selected unit</string> + </property> + </widget> + </item> + <item> + <widget class="QCheckBox" name="checkboxSubtractFeeFromAmount"> + <property name="toolTip"> + <string>The fee will be deducted from the amount being sent. The recipient will receive less bitcoins than you enter in the amount field. If multiple recipients are selected, the fee is split equally.</string> + </property> + <property name="text"> + <string>S&ubtract fee from amount</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="useAvailableBalanceButton"> + <property name="text"> + <string>Use available balance</string> + </property> + </widget> + </item> + </layout> + </item> + <item row="3" column="0"> + <widget class="QLabel" name="messageLabel"> + <property name="text"> + <string>Message:</string> + </property> + <property name="alignment"> + <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> + </property> + </widget> + </item> + <item row="3" column="1"> + <widget class="QLabel" name="messageTextLabel"> + <property name="toolTip"> + <string>A message that was attached to the bitcoin: URI which will be stored with the transaction for your reference. Note: This message will not be sent over the Bitcoin network.</string> + </property> + <property name="textFormat"> + <enum>Qt::PlainText</enum> + </property> + </widget> + </item> + <item row="4" column="0" colspan="2"> + <widget class="Line" name="line"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + </widget> + </item> + </layout> </widget> <customwidgets> <customwidget> @@ -1263,8 +223,9 @@ </customwidget> <customwidget> <class>BitcoinAmountField</class> - <extends>QLineEdit</extends> + <extends>QWidget</extends> <header>qt/bitcoinamountfield.h</header> + <container>1</container> </customwidget> </customwidgets> <tabstops> @@ -1274,10 +235,6 @@ <tabstop>deleteButton</tabstop> <tabstop>addAsLabel</tabstop> <tabstop>payAmount</tabstop> - <tabstop>payAmount_is</tabstop> - <tabstop>deleteButton_is</tabstop> - <tabstop>payAmount_s</tabstop> - <tabstop>deleteButton_s</tabstop> </tabstops> <resources> <include location="../bitcoin.qrc"/> diff --git a/src/qt/guiutil.cpp b/src/qt/guiutil.cpp index e3c6d8a624..b9f0be41e3 100644 --- a/src/qt/guiutil.cpp +++ b/src/qt/guiutil.cpp @@ -24,9 +24,6 @@ #include <util/time.h> #ifdef WIN32 -#ifndef NOMINMAX -#define NOMINMAX -#endif #include <shellapi.h> #include <shlobj.h> #include <shlwapi.h> @@ -56,6 +53,7 @@ #include <QMouseEvent> #include <QPluginLoader> #include <QProgressDialog> +#include <QRegularExpression> #include <QScreen> #include <QSettings> #include <QShortcut> @@ -292,6 +290,17 @@ QString getDefaultDataDirectory() return PathToQString(GetDefaultDataDir()); } +QString ExtractFirstSuffixFromFilter(const QString& filter) +{ + QRegularExpression filter_re(QStringLiteral(".* \\(\\*\\.(.*)[ \\)]"), QRegularExpression::InvertedGreedinessOption); + QString suffix; + QRegularExpressionMatch m = filter_re.match(filter); + if (m.hasMatch()) { + suffix = m.captured(1); + } + return suffix; +} + QString getSaveFileName(QWidget *parent, const QString &caption, const QString &dir, const QString &filter, QString *selectedSuffixOut) @@ -309,13 +318,7 @@ QString getSaveFileName(QWidget *parent, const QString &caption, const QString & /* Directly convert path to native OS path separators */ QString result = QDir::toNativeSeparators(QFileDialog::getSaveFileName(parent, caption, myDir, filter, &selectedFilter)); - /* Extract first suffix from filter pattern "Description (*.foo)" or "Description (*.foo *.bar ...) */ - QRegExp filter_re(".* \\(\\*\\.(.*)[ \\)]"); - QString selectedSuffix; - if(filter_re.exactMatch(selectedFilter)) - { - selectedSuffix = filter_re.cap(1); - } + QString selectedSuffix = ExtractFirstSuffixFromFilter(selectedFilter); /* Add suffix if needed */ QFileInfo info(result); @@ -357,14 +360,8 @@ QString getOpenFileName(QWidget *parent, const QString &caption, const QString & if(selectedSuffixOut) { - /* Extract first suffix from filter pattern "Description (*.foo)" or "Description (*.foo *.bar ...) */ - QRegExp filter_re(".* \\(\\*\\.(.*)[ \\)]"); - QString selectedSuffix; - if(filter_re.exactMatch(selectedFilter)) - { - selectedSuffix = filter_re.cap(1); - } - *selectedSuffixOut = selectedSuffix; + *selectedSuffixOut = ExtractFirstSuffixFromFilter(selectedFilter); + ; } return result; } @@ -431,7 +428,7 @@ void openDebugLogfile() bool openBitcoinConf() { - fs::path pathConfig = GetConfigFile(gArgs.GetArg("-conf", BITCOIN_CONF_FILENAME)); + fs::path pathConfig = GetConfigFile(gArgs.GetPathArg("-conf", BITCOIN_CONF_FILENAME)); /* Create the file */ std::ofstream configFile{pathConfig, std::ios_base::app}; @@ -985,7 +982,7 @@ void PrintSlotException( std::string description = sender->metaObject()->className(); description += "->"; description += receiver->metaObject()->className(); - PrintExceptionContinue(exception, description.c_str()); + PrintExceptionContinue(exception, description); } void ShowModalDialogAsynchronously(QDialog* dialog) diff --git a/src/qt/guiutil.h b/src/qt/guiutil.h index e38ac6026a..acbe415a91 100644 --- a/src/qt/guiutil.h +++ b/src/qt/guiutil.h @@ -123,6 +123,14 @@ namespace GUIUtil */ QString getDefaultDataDirectory(); + /** + * Extract first suffix from filter pattern "Description (*.foo)" or "Description (*.foo *.bar ...). + * + * @param[in] filter Filter specification such as "Comma Separated Files (*.csv)" + * @return QString + */ + QString ExtractFirstSuffixFromFilter(const QString& filter); + /** Get save filename, mimics QFileDialog::getSaveFileName, except that it appends a default suffix when no suffix is provided by the user. diff --git a/src/qt/intro.cpp b/src/qt/intro.cpp index f4928951fe..4c8b33bf28 100644 --- a/src/qt/intro.cpp +++ b/src/qt/intro.cpp @@ -287,7 +287,7 @@ void Intro::setStatus(int status, const QString &message, quint64 bytesAvailable ui->freeSpace->setText(""); } else { m_bytes_available = bytesAvailable; - if (ui->prune->isEnabled()) { + if (ui->prune->isEnabled() && !(gArgs.IsArgSet("-prune") && gArgs.GetIntArg("-prune", 0) == 0)) { ui->prune->setChecked(m_bytes_available < (m_blockchain_size_gb + m_chain_state_size_gb + 10) * GB_BYTES); } UpdateFreeSpaceLabel(); diff --git a/src/qt/locale/bitcoin_en.ts b/src/qt/locale/bitcoin_en.ts index 35d8201877..586240445e 100644 --- a/src/qt/locale/bitcoin_en.ts +++ b/src/qt/locale/bitcoin_en.ts @@ -55,7 +55,7 @@ </message> <message> <location line="-30"/> - <location filename="../addressbookpage.cpp" line="+122"/> + <location filename="../addressbookpage.cpp" line="+128"/> <source>&Delete</source> <translation>&Delete</translation> </message> @@ -313,7 +313,12 @@ Signing is only possible with addresses of the type 'legacy'.</source> <context> <name>BitcoinApplication</name> <message> - <location filename="../bitcoin.cpp" line="+433"/> + <location filename="../bitcoin.cpp" line="+288"/> + <source>Settings file %1 might be corrupt or invalid.</source> + <translation type="unfinished"></translation> + </message> + <message> + <location line="+178"/> <source>Runaway exception</source> <translation type="unfinished"></translation> </message> @@ -336,7 +341,7 @@ Signing is only possible with addresses of the type 'legacy'.</source> <context> <name>BitcoinGUI</name> <message> - <location filename="../bitcoingui.cpp" line="+250"/> + <location filename="../bitcoingui.cpp" line="+253"/> <source>&Overview</source> <translation>&Overview</translation> </message> @@ -396,7 +401,7 @@ Signing is only possible with addresses of the type 'legacy'.</source> <translation type="unfinished"></translation> </message> <message> - <location line="+127"/> + <location line="+160"/> <source>&Minimize</source> <translation type="unfinished"></translation> </message> @@ -406,18 +411,18 @@ Signing is only possible with addresses of the type 'legacy'.</source> <translation type="unfinished"></translation> </message> <message> - <location line="+392"/> + <location line="+393"/> <source>Network activity disabled.</source> <extracomment>A substring of the tooltip.</extracomment> <translation type="unfinished"></translation> </message> <message> - <location line="+422"/> + <location line="+439"/> <source>Proxy is <b>enabled</b>: %1</source> <translation type="unfinished"></translation> </message> <message> - <location line="-1109"/> + <location line="-1160"/> <source>Send coins to a Bitcoin address</source> <translation>Send coins to a Bitcoin address</translation> </message> @@ -507,17 +512,17 @@ Signing is only possible with addresses of the type 'legacy'.</source> <translation type="unfinished"></translation> </message> <message> - <location line="+4"/> + <location line="+10"/> <source>Close All Wallets…</source> <translation type="unfinished"></translation> </message> <message> - <location line="+94"/> + <location line="+119"/> <source>&File</source> <translation>&File</translation> </message> <message> - <location line="+18"/> + <location line="+20"/> <source>&Settings</source> <translation>&Settings</translation> </message> @@ -532,12 +537,12 @@ Signing is only possible with addresses of the type 'legacy'.</source> <translation>Tabs toolbar</translation> </message> <message> - <location line="+456"/> + <location line="+457"/> <source>Syncing Headers (%1%)…</source> <translation type="unfinished"></translation> </message> <message> - <location line="+47"/> + <location line="+58"/> <source>Synchronizing with network…</source> <translation type="unfinished"></translation> </message> @@ -562,7 +567,7 @@ Signing is only possible with addresses of the type 'legacy'.</source> <translation type="unfinished"></translation> </message> <message> - <location line="-788"/> + <location line="-833"/> <source>Request payments (generates QR codes and bitcoin: URIs)</source> <translation type="unfinished"></translation> </message> @@ -577,12 +582,12 @@ Signing is only possible with addresses of the type 'legacy'.</source> <translation type="unfinished"></translation> </message> <message> - <location line="+20"/> + <location line="+26"/> <source>&Command-line options</source> <translation type="unfinished"></translation> </message> <message numerus="yes"> - <location line="+710"/> + <location line="+749"/> <source>Processed %n block(s) of transaction history.</source> <translation> <numerusform>Processed %n block of transaction history.</numerusform> @@ -630,7 +635,12 @@ Signing is only possible with addresses of the type 'legacy'.</source> <translation>Up to date</translation> </message> <message> - <location line="-747"/> + <location line="-818"/> + <source>Ctrl+Q</source> + <translation type="unfinished"></translation> + </message> + <message> + <location line="+26"/> <source>Load Partially Signed Bitcoin Transaction</source> <translation type="unfinished"></translation> </message> @@ -686,6 +696,18 @@ Signing is only possible with addresses of the type 'legacy'.</source> </message> <message> <location line="+7"/> + <source>Restore Wallet…</source> + <extracomment>Name of the menu item that restores wallet from a backup file.</extracomment> + <translation type="unfinished"></translation> + </message> + <message> + <location line="+3"/> + <source>Restore a wallet from a backup file</source> + <extracomment>Status tip for Restore Wallet menu item</extracomment> + <translation type="unfinished"></translation> + </message> + <message> + <location line="+3"/> <source>Close all wallets</source> <translation type="unfinished"></translation> </message> @@ -715,12 +737,41 @@ Signing is only possible with addresses of the type 'legacy'.</source> <translation type="unfinished"></translation> </message> <message> - <location line="+63"/> + <location line="+6"/> + <source>Wallet Data</source> + <extracomment>Name of the wallet data file format.</extracomment> + <translation type="unfinished"></translation> + </message> + <message> + <location line="+3"/> + <source>Load Wallet Backup</source> + <extracomment>The title for Restore Wallet File Windows</extracomment> + <translation type="unfinished"></translation> + </message> + <message> + <location line="+8"/> + <source>Restore Wallet</source> + <extracomment>Title of pop-up window shown when the user is attempting to restore a wallet.</extracomment> + <translation type="unfinished"></translation> + </message> + <message> + <location line="+2"/> + <source>Wallet Name</source> + <extracomment>Label of the input field where the name of the wallet is entered.</extracomment> + <translation type="unfinished"></translation> + </message> + <message> + <location line="+71"/> <source>&Window</source> <translation type="unfinished">&Window</translation> </message> <message> - <location line="+12"/> + <location line="+3"/> + <source>Ctrl+M</source> + <translation type="unfinished"></translation> + </message> + <message> + <location line="+9"/> <source>Zoom</source> <translation type="unfinished"></translation> </message> @@ -730,7 +781,7 @@ Signing is only possible with addresses of the type 'legacy'.</source> <translation type="unfinished"></translation> </message> <message> - <location line="+257"/> + <location line="+258"/> <source>%1 client</source> <translation type="unfinished"></translation> </message> @@ -778,7 +829,12 @@ Signing is only possible with addresses of the type 'legacy'.</source> <translation type="unfinished"></translation> </message> <message> - <location line="+158"/> + <location line="+17"/> + <source>Pre-syncing Headers (%1%)…</source> + <translation type="unfinished"></translation> + </message> + <message> + <location line="+152"/> <source>Error: %1</source> <translation type="unfinished"></translation> </message> @@ -849,7 +905,7 @@ Signing is only possible with addresses of the type 'legacy'.</source> <translation type="unfinished"></translation> </message> <message> - <location line="+17"/> + <location line="+23"/> <source>Wallet is <b>encrypted</b> and currently <b>unlocked</b></source> <translation>Wallet is <b>encrypted</b> and currently <b>unlocked</b></translation> </message> @@ -1022,7 +1078,7 @@ Signing is only possible with addresses of the type 'legacy'.</source> <translation type="unfinished"></translation> </message> <message> - <location line="+155"/> + <location line="+154"/> <source>yes</source> <translation type="unfinished"></translation> </message> @@ -1061,7 +1117,7 @@ Signing is only possible with addresses of the type 'legacy'.</source> <context> <name>CreateWalletActivity</name> <message> - <location filename="../walletcontroller.cpp" line="+243"/> + <location filename="../walletcontroller.cpp" line="+244"/> <source>Create Wallet</source> <extracomment>Title of window indicating the progress of creation of a new wallet.</extracomment> <translation type="unfinished"></translation> @@ -1073,7 +1129,7 @@ Signing is only possible with addresses of the type 'legacy'.</source> <translation type="unfinished"></translation> </message> <message> - <location line="+29"/> + <location line="+33"/> <source>Create wallet failed</source> <translation type="unfinished"></translation> </message> @@ -1087,6 +1143,11 @@ Signing is only possible with addresses of the type 'legacy'.</source> <source>Can't list signers</source> <translation type="unfinished"></translation> </message> + <message> + <location line="+3"/> + <source>Too many external signers found</source> + <translation type="unfinished"></translation> + </message> </context> <context> <name>CreateWalletDialog</name> @@ -1276,7 +1337,7 @@ Signing is only possible with addresses of the type 'legacy'.</source> <context> <name>HelpMessageDialog</name> <message> - <location filename="../utilitydialog.cpp" line="+37"/> + <location filename="../utilitydialog.cpp" line="+38"/> <source>version</source> <translation type="unfinished">version</translation> </message> @@ -1286,7 +1347,7 @@ Signing is only possible with addresses of the type 'legacy'.</source> <translation type="unfinished"></translation> </message> <message> - <location line="+19"/> + <location line="+18"/> <source>Command-line options</source> <translation type="unfinished"></translation> </message> @@ -1309,12 +1370,7 @@ Signing is only possible with addresses of the type 'legacy'.</source> <translation type="unfinished"></translation> </message> <message> - <location line="+157"/> - <source>When you click OK, %1 will begin to download and process the full %4 block chain (%2GB) starting with the earliest transactions in %3 when %4 initially launched.</source> - <translation type="unfinished"></translation> - </message> - <message> - <location line="+32"/> + <location line="+189"/> <source>Limit block chain storage to</source> <translation type="unfinished"></translation> </message> @@ -1334,7 +1390,12 @@ Signing is only possible with addresses of the type 'legacy'.</source> <translation type="unfinished"></translation> </message> <message> - <location line="+10"/> + <location line="-10"/> + <source>When you click OK, %1 will begin to download and process the full %4 block chain (%2 GB) starting with the earliest transactions in %3 when %4 initially launched.</source> + <translation type="unfinished"></translation> + </message> + <message> + <location line="+20"/> <source>If you have chosen to limit block chain storage (pruning), the historical data must still be downloaded and processed, but will be deleted afterward to keep your disk usage low.</source> <translation type="unfinished"></translation> </message> @@ -1353,20 +1414,29 @@ Signing is only possible with addresses of the type 'legacy'.</source> <source>Bitcoin</source> <translation type="unfinished">Bitcoin</translation> </message> - <message> + <message numerus="yes"> <location line="+162"/> - <source>%1 GB of space available</source> - <translation type="unfinished"></translation> + <source>%n GB of space available</source> + <translation type="unfinished"> + <numerusform></numerusform> + <numerusform></numerusform> + </translation> </message> - <message> + <message numerus="yes"> <location line="+2"/> - <source>(of %1 GB needed)</source> - <translation type="unfinished"></translation> + <source>(of %n GB needed)</source> + <translation type="unfinished"> + <numerusform></numerusform> + <numerusform></numerusform> + </translation> </message> - <message> + <message numerus="yes"> <location line="+3"/> - <source>(%1 GB needed for full chain)</source> - <translation type="unfinished"></translation> + <source>(%n GB needed for full chain)</source> + <translation type="unfinished"> + <numerusform></numerusform> + <numerusform></numerusform> + </translation> </message> <message> <location line="+72"/> @@ -1411,7 +1481,7 @@ Signing is only possible with addresses of the type 'legacy'.</source> <context> <name>LoadWalletsActivity</name> <message> - <location filename="../walletcontroller.cpp" line="+69"/> + <location filename="../walletcontroller.cpp" line="+74"/> <source>Load Wallets</source> <extracomment>Title of progress window which is displayed when wallets are being loaded.</extracomment> <translation type="unfinished"></translation> @@ -1448,7 +1518,7 @@ Signing is only possible with addresses of the type 'legacy'.</source> <message> <location line="+7"/> <location line="+26"/> - <location filename="../modaloverlay.cpp" line="+152"/> + <location filename="../modaloverlay.cpp" line="+155"/> <source>Unknown…</source> <translation type="unfinished"></translation> </message> @@ -1489,15 +1559,20 @@ Signing is only possible with addresses of the type 'legacy'.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../modaloverlay.cpp" line="-118"/> + <location filename="../modaloverlay.cpp" line="-121"/> <source>%1 is currently syncing. It will download headers and blocks from peers and validate them until reaching the tip of the block chain.</source> <translation type="unfinished"></translation> </message> <message> - <location line="+124"/> + <location line="+127"/> <source>Unknown. Syncing Headers (%1, %2%)…</source> <translation type="unfinished"></translation> </message> + <message> + <location line="+5"/> + <source>Unknown. Pre-syncing Headers (%1, %2%)…</source> + <translation type="unfinished"></translation> + </message> </context> <context> <name>OpenURIDialog</name> @@ -1521,7 +1596,7 @@ Signing is only possible with addresses of the type 'legacy'.</source> <context> <name>OpenWalletActivity</name> <message> - <location filename="../walletcontroller.cpp" line="-42"/> + <location filename="../walletcontroller.cpp" line="-46"/> <source>Open wallet failed</source> <translation type="unfinished"></translation> </message> @@ -1604,7 +1679,12 @@ Signing is only possible with addresses of the type 'legacy'.</source> <translation type="unfinished"></translation> </message> <message> - <location line="+272"/> + <location line="+227"/> + <source>Options set in this dialog are overridden by the command line:</source> + <translation type="unfinished"></translation> + </message> + <message> + <location line="+45"/> <source>Open the %1 configuration file from the working directory.</source> <translation type="unfinished"></translation> </message> @@ -1916,12 +1996,7 @@ Signing is only possible with addresses of the type 'legacy'.</source> <translation type="unfinished"></translation> </message> <message> - <location line="+65"/> - <source>Options set in this dialog are overridden by the command line or in the configuration file:</source> - <translation type="unfinished"></translation> - </message> - <message> - <location line="+141"/> + <location line="+206"/> <source>&OK</source> <translation>&OK</translation> </message> @@ -1931,7 +2006,7 @@ Signing is only possible with addresses of the type 'legacy'.</source> <translation>&Cancel</translation> </message> <message> - <location filename="../optionsdialog.cpp" line="+99"/> + <location filename="../optionsdialog.cpp" line="+96"/> <source>Compiled without external signing support (required for external signing)</source> <extracomment>"External signing" means using devices such as hardware wallets.</extracomment> <translation type="unfinished"></translation> @@ -1942,28 +2017,37 @@ Signing is only possible with addresses of the type 'legacy'.</source> <translation>default</translation> </message> <message> - <location line="+81"/> + <location line="+86"/> <source>none</source> <translation type="unfinished"></translation> </message> <message> - <location line="+97"/> + <location line="+107"/> <source>Confirm options reset</source> + <extracomment>Window title text of pop-up window shown when the user has chosen to reset options.</extracomment> <translation>Confirm options reset</translation> </message> <message> - <location line="+1"/> - <location line="+70"/> + <location line="-9"/> + <location line="+79"/> <source>Client restart required to activate changes.</source> + <extracomment>Text explaining that the settings changed will not come into effect until the client is restarted.</extracomment> + <translation type="unfinished"></translation> + </message> + <message> + <location line="-75"/> + <source>Current settings will be backed up at "%1".</source> + <extracomment>Text explaining to the user that the client's current settings will be backed up at a specific location. %1 is a stand-in argument for the backup location's path.</extracomment> <translation type="unfinished"></translation> </message> <message> - <location line="-70"/> + <location line="+3"/> <source>Client will be shut down. Do you want to proceed?</source> + <extracomment>Text asking the user to confirm if they would like to proceed with a client shutdown.</extracomment> <translation type="unfinished"></translation> </message> <message> - <location line="+18"/> + <location line="+20"/> <source>Configuration options</source> <extracomment>Window title text of pop-up box that allows opening up of configuration file.</extracomment> <translation type="unfinished"></translation> @@ -2006,6 +2090,14 @@ Signing is only possible with addresses of the type 'legacy'.</source> </message> </context> <context> + <name>OptionsModel</name> + <message> + <location filename="../optionsmodel.cpp" line="+204"/> + <source>Could not read setting "%1", %2.</source> + <translation type="unfinished"></translation> + </message> +</context> +<context> <name>OverviewPage</name> <message> <location filename="../forms/overviewpage.ui" line="+14"/> @@ -2099,7 +2191,7 @@ Signing is only possible with addresses of the type 'legacy'.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../overviewpage.cpp" line="+187"/> + <location filename="../overviewpage.cpp" line="+185"/> <source>Privacy mode activated for the Overview tab. To unmask the values, uncheck Settings->Mask values.</source> <translation type="unfinished"></translation> </message> @@ -2271,7 +2363,7 @@ Signing is only possible with addresses of the type 'legacy'.</source> <context> <name>PaymentServer</name> <message> - <location filename="../paymentserver.cpp" line="+173"/> + <location filename="../paymentserver.cpp" line="+152"/> <source>Payment request error</source> <translation type="unfinished"></translation> </message> @@ -2281,7 +2373,7 @@ Signing is only possible with addresses of the type 'legacy'.</source> <translation type="unfinished"></translation> </message> <message> - <location line="+50"/> + <location line="+48"/> <location line="+16"/> <location line="+6"/> <location line="+7"/> @@ -2315,7 +2407,7 @@ If you are receiving this error you should request the merchant provide a BIP21 <context> <name>PeerTableModel</name> <message> - <location filename="../peertablemodel.h" line="+108"/> + <location filename="../peertablemodel.h" line="+112"/> <source>User Agent</source> <extracomment>Title of Peers Table column which contains the peer's User Agent string.</extracomment> <translation type="unfinished"></translation> @@ -2327,12 +2419,18 @@ If you are receiving this error you should request the merchant provide a BIP21 <translation type="unfinished"></translation> </message> <message> - <location line="-15"/> + <location line="-18"/> <source>Peer</source> <extracomment>Title of Peers Table column which contains a unique number used to identify a connection.</extracomment> <translation type="unfinished"></translation> </message> <message> + <location line="+3"/> + <source>Age</source> + <extracomment>Title of Peers Table column which indicates the duration (length of time) since the peer connection started.</extracomment> + <translation type="unfinished"></translation> + </message> + <message> <location line="+6"/> <source>Direction</source> <extracomment>Title of Peers Table column which indicates the direction the peer connection was initiated from.</extracomment> @@ -2369,7 +2467,7 @@ If you are receiving this error you should request the merchant provide a BIP21 <translation type="unfinished">Network</translation> </message> <message> - <location filename="../peertablemodel.cpp" line="+79"/> + <location filename="../peertablemodel.cpp" line="+78"/> <source>Inbound</source> <extracomment>An Inbound Connection from a Peer.</extracomment> <translation type="unfinished"></translation> @@ -2384,17 +2482,22 @@ If you are receiving this error you should request the merchant provide a BIP21 <context> <name>QObject</name> <message> - <location filename="../bitcoinunits.cpp" line="+215"/> + <location filename="../bitcoinunits.cpp" line="+197"/> <source>Amount</source> <translation type="unfinished">Amount</translation> </message> <message> - <location filename="../guiutil.cpp" line="+127"/> + <location filename="../guiutil.cpp" line="+129"/> <source>Enter a Bitcoin address (e.g. %1)</source> <translation type="unfinished"></translation> </message> <message> - <location line="+546"/> + <location line="+288"/> + <source>Ctrl+W</source> + <translation type="unfinished"></translation> + </message> + <message> + <location line="+256"/> <source>Unroutable</source> <translation type="unfinished"></translation> </message> @@ -2446,23 +2549,27 @@ If you are receiving this error you should request the merchant provide a BIP21 <translation type="unfinished"></translation> </message> <message> - <location line="+15"/> + <location line="+13"/> + <location line="+12"/> <source>%1 d</source> <translation type="unfinished"></translation> </message> <message> - <location line="+2"/> + <location line="-11"/> + <location line="+12"/> <source>%1 h</source> <translation type="unfinished"></translation> </message> <message> - <location line="+2"/> + <location line="-11"/> + <location line="+12"/> <source>%1 m</source> <translation type="unfinished"></translation> </message> <message> - <location line="+2"/> - <location line="+28"/> + <location line="-10"/> + <location line="+11"/> + <location line="+26"/> <source>%1 s</source> <translation type="unfinished"></translation> </message> @@ -2556,7 +2663,7 @@ If you are receiving this error you should request the merchant provide a BIP21 <translation type="unfinished"></translation> </message> <message> - <location filename="../bitcoin.cpp" line="-276"/> + <location filename="../bitcoin.cpp" line="-294"/> <source>Do you want to reset settings to default values, or to abort without making changes?</source> <extracomment>Explanatory text shown on startup when the settings file cannot be read. Prompts user to make a choice between resetting or aborting.</extracomment> <translation type="unfinished"></translation> @@ -2568,7 +2675,7 @@ If you are receiving this error you should request the merchant provide a BIP21 <translation type="unfinished"></translation> </message> <message> - <location line="+373"/> + <location line="+393"/> <source>Error: Specified data directory "%1" does not exist.</source> <translation type="unfinished"></translation> </message> @@ -2583,12 +2690,12 @@ If you are receiving this error you should request the merchant provide a BIP21 <translation type="unfinished"></translation> </message> <message> - <location line="+71"/> + <location line="+74"/> <source>%1 didn't yet exit safely…</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../modaloverlay.cpp" line="-35"/> + <location filename="../modaloverlay.cpp" line="-40"/> <source>unknown</source> <translation type="unfinished"></translation> </message> @@ -2672,7 +2779,7 @@ If you are receiving this error you should request the merchant provide a BIP21 <location line="+26"/> <location line="+26"/> <location line="+26"/> - <location filename="../rpcconsole.h" line="+139"/> + <location filename="../rpcconsole.h" line="+143"/> <source>N/A</source> <translation>N/A</translation> </message> @@ -2791,7 +2898,7 @@ If you are receiving this error you should request the merchant provide a BIP21 </message> <message> <location line="+68"/> - <location filename="../rpcconsole.cpp" line="+1160"/> + <location filename="../rpcconsole.cpp" line="+1155"/> <source>Select a peer to view detailed information.</source> <translation type="unfinished"></translation> </message> @@ -2833,34 +2940,37 @@ If you are receiving this error you should request the merchant provide a BIP21 <message> <location line="+23"/> <source>Whether we relay addresses to this peer.</source> - <extracomment>Tooltip text for the Address Relay field in the peer details area.</extracomment> + <extracomment>Tooltip text for the Address Relay field in the peer details area, which displays whether we relay addresses to this peer (Yes/No).</extracomment> <translation type="unfinished"></translation> </message> <message> <location line="+3"/> <source>Address Relay</source> + <extracomment>Text title for the Address Relay field in the peer details area, which displays whether we relay addresses to this peer (Yes/No).</extracomment> <translation type="unfinished"></translation> </message> <message> <location line="+23"/> - <source>Total number of addresses processed, excluding those dropped due to rate-limiting.</source> - <extracomment>Tooltip text for the Addresses Processed field in the peer details area.</extracomment> + <source>The total number of addresses received from this peer that were processed (excludes addresses that were dropped due to rate-limiting).</source> + <extracomment>Tooltip text for the Addresses Processed field in the peer details area, which displays the total number of addresses received from this peer that were processed (excludes addresses that were dropped due to rate-limiting).</extracomment> <translation type="unfinished"></translation> </message> <message> - <location line="+3"/> - <source>Addresses Processed</source> + <location line="+26"/> + <source>The total number of addresses received from this peer that were dropped (not processed) due to rate-limiting.</source> + <extracomment>Tooltip text for the Addresses Rate-Limited field in the peer details area, which displays the total number of addresses received from this peer that were dropped (not processed) due to rate-limiting.</extracomment> <translation type="unfinished"></translation> </message> <message> - <location line="+23"/> - <source>Total number of addresses dropped due to rate-limiting.</source> - <extracomment>Tooltip text for the Addresses Rate-Limited field in the peer details area.</extracomment> + <location line="-23"/> + <source>Addresses Processed</source> + <extracomment>Text title for the Addresses Processed field in the peer details area, which displays the total number of addresses received from this peer that were processed (excludes addresses that were dropped due to rate-limiting).</extracomment> <translation type="unfinished"></translation> </message> <message> - <location line="+3"/> + <location line="+26"/> <source>Addresses Rate-Limited</source> + <extracomment>Text title for the Addresses Rate-Limited field in the peer details area, which displays the total number of addresses received from this peer that were dropped (not processed) due to rate-limiting.</extracomment> <translation type="unfinished"></translation> </message> <message> @@ -3021,7 +3131,7 @@ If you are receiving this error you should request the merchant provide a BIP21 <translation type="unfinished"></translation> </message> <message> - <location filename="../rpcconsole.cpp" line="-201"/> + <location filename="../rpcconsole.cpp" line="-202"/> <source>In:</source> <translation type="unfinished"></translation> </message> @@ -3066,12 +3176,12 @@ If you are receiving this error you should request the merchant provide a BIP21 <translation type="unfinished"></translation> </message> <message> - <location line="+41"/> + <location line="+42"/> <source>Never</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../rpcconsole.cpp" line="-457"/> + <location filename="../rpcconsole.cpp" line="-458"/> <source>Inbound: initiated by peer</source> <extracomment>Explanatory text for an inbound peer connection.</extracomment> <translation type="unfinished"></translation> @@ -3177,7 +3287,7 @@ If you are receiving this error you should request the merchant provide a BIP21 <translation type="unfinished"></translation> </message> <message> - <location line="+25"/> + <location line="+26"/> <source>&Copy IP/Netmask</source> <extracomment>Context menu action to copy the IP/Netmask of a banned peer. IP/Netmask is the combination of a peer's IP address and its Netmask. For IP address, see: https://en.wikipedia.org/wiki/IP_address.</extracomment> <translation type="unfinished"></translation> @@ -3193,17 +3303,37 @@ If you are receiving this error you should request the merchant provide a BIP21 <translation type="unfinished"></translation> </message> <message> - <location line="+77"/> + <location line="+78"/> <source>Executing command without any wallet</source> <translation type="unfinished"></translation> </message> <message> - <location line="-2"/> + <location line="+316"/> + <source>Ctrl+I</source> + <translation type="unfinished"></translation> + </message> + <message> + <location line="+1"/> + <source>Ctrl+T</source> + <translation type="unfinished"></translation> + </message> + <message> + <location line="+1"/> + <source>Ctrl+N</source> + <translation type="unfinished"></translation> + </message> + <message> + <location line="+1"/> + <source>Ctrl+P</source> + <translation type="unfinished"></translation> + </message> + <message> + <location line="-321"/> <source>Executing command using "%1" wallet</source> <translation type="unfinished"></translation> </message> <message> - <location line="-145"/> + <location line="-146"/> <source>Welcome to the %1 RPC console. Use up and down arrows to navigate history, and %2 to clear screen. Use %3 and %4 to increase or decrease the font size. @@ -3215,7 +3345,7 @@ For more information on using this console, type %6. <translation type="unfinished"></translation> </message> <message> - <location line="+155"/> + <location line="+156"/> <source>Executing…</source> <extracomment>A console message indicating an entered command is currently being executed.</extracomment> <translation type="unfinished"></translation> @@ -3231,7 +3361,7 @@ For more information on using this console, type %6. <translation type="unfinished"></translation> </message> <message> - <location filename="../rpcconsole.h" line="-41"/> + <location filename="../rpcconsole.h" line="-42"/> <source>Unknown</source> <translation type="unfinished"></translation> </message> @@ -3446,7 +3576,7 @@ For more information on using this console, type %6. <translation type="unfinished"></translation> </message> <message> - <location line="+41"/> + <location line="+38"/> <source>(no label)</source> <translation type="unfinished"></translation> </message> @@ -3467,10 +3597,43 @@ For more information on using this console, type %6. </message> </context> <context> + <name>RestoreWalletActivity</name> + <message> + <location filename="../walletcontroller.cpp" line="+49"/> + <source>Restore Wallet</source> + <extracomment>Title of progress window which is displayed when wallets are being restored.</extracomment> + <translation type="unfinished"></translation> + </message> + <message> + <location line="+3"/> + <source>Restoring Wallet <b>%1</b>…</source> + <extracomment>Descriptive text of the restore wallets progress window which indicates to the user that wallets are currently being restored.</extracomment> + <translation type="unfinished"></translation> + </message> + <message> + <location line="+19"/> + <source>Restore wallet failed</source> + <extracomment>Title of message box which is displayed when the wallet could not be restored.</extracomment> + <translation type="unfinished"></translation> + </message> + <message> + <location line="+3"/> + <source>Restore wallet warning</source> + <extracomment>Title of message box which is displayed when the wallet is restored with some warning.</extracomment> + <translation type="unfinished"></translation> + </message> + <message> + <location line="+3"/> + <source>Restore wallet message</source> + <extracomment>Title of message box which is displayed when the wallet is successfully restored.</extracomment> + <translation type="unfinished"></translation> + </message> +</context> +<context> <name>SendCoinsDialog</name> <message> <location filename="../forms/sendcoinsdialog.ui" line="+14"/> - <location filename="../sendcoinsdialog.cpp" line="+752"/> + <location filename="../sendcoinsdialog.cpp" line="+757"/> <source>Send Coins</source> <translation>Send Coins</translation> </message> @@ -3657,7 +3820,7 @@ Note: Since the fee is calculated on a per-byte basis, a fee rate of "100 <translation>S&end</translation> </message> <message> - <location filename="../sendcoinsdialog.cpp" line="-653"/> + <location filename="../sendcoinsdialog.cpp" line="-658"/> <source>Copy quantity</source> <translation type="unfinished"></translation> </message> @@ -3692,7 +3855,7 @@ Note: Since the fee is calculated on a per-byte basis, a fee rate of "100 <translation type="unfinished"></translation> </message> <message> - <location line="+76"/> + <location line="+74"/> <source>%1 (%2 blocks)</source> <translation type="unfinished"></translation> </message> @@ -3739,29 +3902,29 @@ Note: Since the fee is calculated on a per-byte basis, a fee rate of "100 <translation type="unfinished"></translation> </message> <message> - <location line="+67"/> + <location line="+66"/> <source>To review recipient list click "Show Details…"</source> <translation type="unfinished"></translation> </message> <message> - <location line="+44"/> + <location line="+60"/> <source>Sign failed</source> <translation type="unfinished"></translation> </message> <message> - <location line="+6"/> + <location line="+5"/> <source>External signer not found</source> <extracomment>"External signer" means using devices such as hardware wallets.</extracomment> <translation type="unfinished"></translation> </message> <message> - <location line="+6"/> + <location line="+5"/> <source>External signer failure</source> <extracomment>"External signer" means using devices such as hardware wallets.</extracomment> <translation type="unfinished"></translation> </message> <message> - <location line="+58"/> + <location line="-34"/> <source>Save Transaction Data</source> <translation type="unfinished"></translation> </message> @@ -3777,17 +3940,17 @@ Note: Since the fee is calculated on a per-byte basis, a fee rate of "100 <translation type="unfinished"></translation> </message> <message> - <location line="+176"/> + <location line="+266"/> <source>External balance:</source> <translation type="unfinished"></translation> </message> <message> - <location line="-303"/> + <location line="-315"/> <source>or</source> <translation type="unfinished"></translation> </message> <message> - <location line="-19"/> + <location line="-18"/> <source>You can increase the fee later (signals Replace-By-Fee, BIP-125).</source> <translation type="unfinished"></translation> </message> @@ -3826,17 +3989,17 @@ Note: Since the fee is calculated on a per-byte basis, a fee rate of "100 <translation type="unfinished"></translation> </message> <message> - <location line="+14"/> + <location line="+13"/> <source>Total Amount</source> <translation type="unfinished"></translation> </message> <message> - <location line="+25"/> + <location line="+99"/> <source>Confirm send coins</source> <translation type="unfinished"></translation> </message> <message> - <location line="+284"/> + <location line="+222"/> <source>Watch-only balance:</source> <translation type="unfinished"></translation> </message> @@ -3875,13 +4038,8 @@ Note: Since the fee is calculated on a per-byte basis, a fee rate of "100 <source>A fee higher than %1 is considered an absurdly high fee.</source> <translation type="unfinished"></translation> </message> - <message> - <location line="+3"/> - <source>Payment request expired.</source> - <translation type="unfinished"></translation> - </message> <message numerus="yes"> - <location line="+124"/> + <location line="+123"/> <source>Estimated to begin confirmation within %n block(s).</source> <translation> <numerusform>Estimated to begin confirmation within %n block.</numerusform> @@ -3917,14 +4075,12 @@ Note: Since the fee is calculated on a per-byte basis, a fee rate of "100 <context> <name>SendCoinsEntry</name> <message> - <location filename="../forms/sendcoinsentry.ui" line="+155"/> - <location line="+550"/> - <location line="+533"/> + <location filename="../forms/sendcoinsentry.ui" line="+151"/> <source>A&mount:</source> <translation>A&mount:</translation> </message> <message> - <location line="-1199"/> + <location line="-116"/> <source>Pay &To:</source> <translation>Pay &To:</translation> </message> @@ -3960,13 +4116,11 @@ Note: Since the fee is calculated on a per-byte basis, a fee rate of "100 </message> <message> <location line="+7"/> - <location line="+562"/> - <location line="+533"/> <source>Remove this entry</source> <translation type="unfinished"></translation> </message> <message> - <location line="-1035"/> + <location line="+60"/> <source>The amount to send in the selected unit</source> <translation type="unfinished"></translation> </message> @@ -3991,17 +4145,7 @@ Note: Since the fee is calculated on a per-byte basis, a fee rate of "100 <translation type="unfinished"></translation> </message> <message> - <location line="+443"/> - <source>This is an unauthenticated payment request.</source> - <translation type="unfinished"></translation> - </message> - <message> - <location line="+529"/> - <source>This is an authenticated payment request.</source> - <translation type="unfinished"></translation> - </message> - <message> - <location line="-1023"/> + <location line="-51"/> <location line="+3"/> <source>Enter a label for this address to add it to the list of used addresses</source> <translation type="unfinished"></translation> @@ -4011,23 +4155,11 @@ Note: Since the fee is calculated on a per-byte basis, a fee rate of "100 <source>A message that was attached to the bitcoin: URI which will be stored with the transaction for your reference. Note: This message will not be sent over the Bitcoin network.</source> <translation type="unfinished"></translation> </message> - <message> - <location line="+448"/> - <location line="+529"/> - <source>Pay To:</source> - <translation type="unfinished"></translation> - </message> - <message> - <location line="-495"/> - <location line="+533"/> - <source>Memo:</source> - <translation type="unfinished"></translation> - </message> </context> <context> <name>SendConfirmationDialog</name> <message> - <location filename="../sendcoinsdialog.h" line="+131"/> + <location filename="../sendcoinsdialog.h" line="+144"/> <source>Send</source> <translation type="unfinished"></translation> </message> @@ -4274,42 +4406,43 @@ Note: Since the fee is calculated on a per-byte basis, a fee rate of "100 <context> <name>TransactionDesc</name> <message> - <location filename="../transactiondesc.cpp" line="+40"/> + <location filename="../transactiondesc.cpp" line="+43"/> <source>conflicted with a transaction with %1 confirmations</source> + <extracomment>Text explaining the current status of a transaction, shown in the status field of the details window for this transaction. This status represents an unconfirmed transaction that conflicts with a confirmed transaction.</extracomment> <translation type="unfinished"></translation> </message> <message> - <location line="+3"/> - <source>0/unconfirmed, %1</source> - <translation type="unfinished"></translation> - </message> - <message> - <location line="+0"/> - <source>in memory pool</source> + <location line="+7"/> + <source>0/unconfirmed, in memory pool</source> + <extracomment>Text explaining the current status of a transaction, shown in the status field of the details window for this transaction. This status represents an unconfirmed transaction that is in the memory pool.</extracomment> <translation type="unfinished"></translation> </message> <message> - <location line="+0"/> - <source>not in memory pool</source> + <location line="+5"/> + <source>0/unconfirmed, not in memory pool</source> + <extracomment>Text explaining the current status of a transaction, shown in the status field of the details window for this transaction. This status represents an unconfirmed transaction that is not in the memory pool.</extracomment> <translation type="unfinished"></translation> </message> <message> - <location line="-1"/> + <location line="+6"/> <source>abandoned</source> + <extracomment>Text explaining the current status of a transaction, shown in the status field of the details window for this transaction. This status represents an abandoned transaction.</extracomment> <translation type="unfinished"></translation> </message> <message> - <location line="+3"/> + <location line="+8"/> <source>%1/unconfirmed</source> + <extracomment>Text explaining the current status of a transaction, shown in the status field of the details window for this transaction. This status represents a transaction confirmed in at least one block, but less than 6 blocks.</extracomment> <translation type="unfinished"></translation> </message> <message> - <location line="+2"/> + <location line="+5"/> <source>%1 confirmations</source> + <extracomment>Text explaining the current status of a transaction, shown in the status field of the details window for this transaction. This status represents a transaction confirmed in 6 or more blocks.</extracomment> <translation type="unfinished"></translation> </message> <message> - <location line="+51"/> + <location line="+50"/> <source>Status</source> <translation type="unfinished"></translation> </message> @@ -4507,7 +4640,7 @@ Note: Since the fee is calculated on a per-byte basis, a fee rate of "100 <context> <name>TransactionTableModel</name> <message> - <location filename="../transactiontablemodel.cpp" line="+260"/> + <location filename="../transactiontablemodel.cpp" line="+261"/> <source>Date</source> <translation type="unfinished">Date</translation> </message> @@ -4844,7 +4977,7 @@ Note: Since the fee is calculated on a per-byte basis, a fee rate of "100 <context> <name>WalletController</name> <message> - <location filename="../walletcontroller.cpp" line="-259"/> + <location filename="../walletcontroller.cpp" line="-344"/> <source>Close wallet</source> <translation type="unfinished"></translation> </message> @@ -4884,19 +5017,19 @@ Go to File > Open Wallet to load a wallet. <translation type="unfinished"></translation> </message> <message> - <location line="+154"/> - <location line="+9"/> + <location line="+151"/> <location line="+10"/> + <location line="+18"/> <source>Error</source> <translation type="unfinished">Error</translation> </message> <message> - <location line="-19"/> + <location line="-28"/> <source>Unable to decode PSBT from clipboard (invalid base64)</source> <translation type="unfinished"></translation> </message> <message> - <location line="+5"/> + <location line="+6"/> <source>Load Transaction Data</source> <translation type="unfinished"></translation> </message> @@ -4911,7 +5044,7 @@ Go to File > Open Wallet to load a wallet. <translation type="unfinished"></translation> </message> <message> - <location line="+10"/> + <location line="+18"/> <source>Unable to decode PSBT</source> <translation type="unfinished"></translation> </message> @@ -4919,12 +5052,12 @@ Go to File > Open Wallet to load a wallet. <context> <name>WalletModel</name> <message> - <location filename="../walletmodel.cpp" line="+221"/> + <location filename="../walletmodel.cpp" line="+232"/> <source>Send Coins</source> <translation type="unfinished">Send Coins</translation> </message> <message> - <location line="+260"/> + <location line="+263"/> <location line="+52"/> <location line="+15"/> <location line="+5"/> @@ -5001,7 +5134,7 @@ Go to File > Open Wallet to load a wallet. <context> <name>WalletView</name> <message> - <location filename="../walletview.cpp" line="+52"/> + <location filename="../walletview.cpp" line="+51"/> <source>&Export</source> <translation type="unfinished">&Export</translation> </message> @@ -5011,7 +5144,7 @@ Go to File > Open Wallet to load a wallet. <translation type="unfinished">Export the data in the current tab to a file</translation> </message> <message> - <location line="+164"/> + <location line="+162"/> <source>Backup Wallet</source> <translation type="unfinished"></translation> </message> @@ -5060,12 +5193,12 @@ Go to File > Open Wallet to load a wallet. <translation type="unfinished"></translation> </message> <message> - <location line="+3"/> + <location line="+7"/> <source>-maxtxfee is set very high! Fees this large could be paid on a single transaction.</source> <translation type="unfinished"></translation> </message> <message> - <location line="+3"/> + <location line="+18"/> <source>Cannot downgrade wallet from version %i to version %i. Wallet version unchanged.</source> <translation type="unfinished"></translation> </message> @@ -5095,7 +5228,7 @@ Go to File > Open Wallet to load a wallet. <translation type="unfinished"></translation> </message> <message> - <location line="+3"/> + <location line="+6"/> <source>Error: Dumpfile format record is incorrect. Got "%s", expected "format".</source> <translation type="unfinished"></translation> </message> @@ -5110,17 +5243,12 @@ Go to File > Open Wallet to load a wallet. <translation type="unfinished"></translation> </message> <message> - <location line="+3"/> + <location line="+6"/> <source>Error: Legacy wallets only support the "legacy", "p2sh-segwit", and "bech32" address types</source> <translation type="unfinished"></translation> </message> <message> - <location line="+3"/> - <source>Error: Listening for incoming connections failed (listen returned error %s)</source> - <translation type="unfinished"></translation> - </message> - <message> - <location line="+2"/> + <location line="+12"/> <source>Fee estimation failed. Fallbackfee is disabled. Wait a few blocks or enable -fallbackfee.</source> <translation type="unfinished"></translation> </message> @@ -5130,7 +5258,7 @@ Go to File > Open Wallet to load a wallet. <translation type="unfinished"></translation> </message> <message> - <location line="+3"/> + <location line="+6"/> <source>Invalid amount for -maxtxfee=<amount>: '%s' (must be at least the minrelay fee of %s to prevent stuck transactions)</source> <translation type="unfinished"></translation> </message> @@ -5160,7 +5288,7 @@ Go to File > Open Wallet to load a wallet. <translation type="unfinished"></translation> </message> <message> - <location line="+3"/> + <location line="+10"/> <source>Please check that your computer's date and time are correct! If your clock is wrong, %s will not work properly.</source> <translation type="unfinished"></translation> </message> @@ -5176,6 +5304,11 @@ Go to File > Open Wallet to load a wallet. </message> <message> <location line="+2"/> + <source>Prune mode is incompatible with -reindex-chainstate. Use full -reindex instead.</source> + <translation type="unfinished"></translation> + </message> + <message> + <location line="+3"/> <source>Prune: last wallet synchronisation goes beyond pruned data. You need to -reindex (download the whole blockchain again in case of pruned node)</source> <translation type="unfinished"></translation> </message> @@ -5240,7 +5373,17 @@ Go to File > Open Wallet to load a wallet. <translation type="unfinished"></translation> </message> <message> + <location line="+11"/> + <source>Unsupported chainstate database format found. Please restart with -reindex-chainstate. This will rebuild the chainstate database.</source> + <translation type="unfinished"></translation> + </message> + <message> <location line="+3"/> + <source>Wallet created successfully. The legacy wallet type is being deprecated and support for creating and opening legacy wallets will be removed in the future.</source> + <translation type="unfinished"></translation> + </message> + <message> + <location line="+4"/> <source>Warning: Dumpfile wallet format "%s" does not match command line specified format "%s".</source> <translation type="unfinished"></translation> </message> @@ -5265,7 +5408,7 @@ Go to File > Open Wallet to load a wallet. <translation type="unfinished"></translation> </message> <message> - <location line="+3"/> + <location line="+9"/> <source>%s is set very high!</source> <translation type="unfinished"></translation> </message> @@ -5300,12 +5443,37 @@ Go to File > Open Wallet to load a wallet. <translation type="unfinished"></translation> </message> <message> - <location line="-58"/> + <location line="-79"/> <source>The -txindex upgrade started by a previous version cannot be completed. Restart with the previous version or run a full -reindex.</source> <translation type="unfinished"></translation> </message> <message> - <location line="-69"/> + <location line="-122"/> + <source>%s request to listen on port %u. This port is considered "bad" and thus it is unlikely that any Bitcoin Core peers connect to it. See doc/p2p-bad-ports.md for details and a full list.</source> + <translation type="unfinished"></translation> + </message> + <message> + <location line="+7"/> + <source>-reindex-chainstate option is not compatible with -blockfilterindex. Please temporarily disable blockfilterindex while using -reindex-chainstate, or replace -reindex-chainstate with -reindex to fully rebuild all indexes.</source> + <translation type="unfinished"></translation> + </message> + <message> + <location line="+4"/> + <source>-reindex-chainstate option is not compatible with -coinstatsindex. Please temporarily disable coinstatsindex while using -reindex-chainstate, or replace -reindex-chainstate with -reindex to fully rebuild all indexes.</source> + <translation type="unfinished"></translation> + </message> + <message> + <location line="+4"/> + <source>-reindex-chainstate option is not compatible with -txindex. Please temporarily disable txindex while using -reindex-chainstate, or replace -reindex-chainstate with -reindex to fully rebuild all indexes.</source> + <translation type="unfinished"></translation> + </message> + <message> + <location line="+4"/> + <source>Assumed-valid: last wallet synchronisation goes beyond available block data. You need to wait for the background validation chain to download more blocks.</source> + <translation type="unfinished"></translation> + </message> + <message> + <location line="+8"/> <source>Cannot provide specific connections and have addrman find outgoing connections at the same time.</source> <translation type="unfinished"></translation> </message> @@ -5315,7 +5483,73 @@ Go to File > Open Wallet to load a wallet. <translation type="unfinished"></translation> </message> <message> - <location line="+118"/> + <location line="+9"/> + <source>Error: Address book data in wallet cannot be identified to belong to migrated wallets</source> + <translation type="unfinished"></translation> + </message> + <message> + <location line="+10"/> + <source>Error: Duplicate descriptors created during migration. Your wallet may be corrupted.</source> + <translation type="unfinished"></translation> + </message> + <message> + <location line="+6"/> + <source>Error: Transaction %s in wallet cannot be identified to belong to migrated wallets</source> + <translation type="unfinished"></translation> + </message> + <message> + <location line="+3"/> + <source>Error: Unable to produce descriptors for this legacy wallet. Make sure the wallet is unlocked first</source> + <translation type="unfinished"></translation> + </message> + <message> + <location line="+3"/> + <source>Failed to rename invalid peers.dat file. Please move or delete it and try again.</source> + <translation type="unfinished"></translation> + </message> + <message> + <location line="+9"/> + <source>Incompatible options: -dnsseed=1 was explicitly specified, but -onlynet forbids connections to IPv4/IPv6</source> + <translation type="unfinished"></translation> + </message> + <message> + <location line="+21"/> + <source>Outbound connections restricted to Tor (-onlynet=onion) but the proxy for reaching the Tor network is explicitly forbidden: -onion=0</source> + <translation type="unfinished"></translation> + </message> + <message> + <location line="+3"/> + <source>Outbound connections restricted to Tor (-onlynet=onion) but the proxy for reaching the Tor network is not provided: none of -proxy, -onion or -listenonion is given</source> + <translation type="unfinished"></translation> + </message> + <message> + <location line="+59"/> + <source>Unrecognized descriptor found. Loading wallet %s + +The wallet might had been created on a newer version. +Please try running the latest software version. +</source> + <translation type="unfinished"></translation> + </message> + <message> + <location line="+5"/> + <source>Unsupported category-specific logging level -loglevel=%s. Expected -loglevel=<category>:<loglevel>. Valid categories: %s. Valid loglevels: %s.</source> + <translation type="unfinished"></translation> + </message> + <message> + <location line="+24"/> + <source> +Unable to cleanup failed migration</source> + <translation type="unfinished"></translation> + </message> + <message> + <location line="+3"/> + <source> +Unable to restore backup of wallet.</source> + <translation type="unfinished"></translation> + </message> + <message> + <location line="+10"/> <source>Config setting for %s only applied on %s network when in [%s] section.</source> <translation type="unfinished"></translation> </message> @@ -5416,7 +5650,12 @@ Go to File > Open Wallet to load a wallet. </message> <message> <location line="+1"/> - <source>Error upgrading chainstate database</source> + <source>Error: Could not add watchonly tx to watchonly wallet</source> + <translation type="unfinished"></translation> + </message> + <message> + <location line="+1"/> + <source>Error: Could not delete watchonly transactions</source> <translation type="unfinished"></translation> </message> <message> @@ -5436,6 +5675,11 @@ Go to File > Open Wallet to load a wallet. </message> <message> <location line="+1"/> + <source>Error: Failed to create new watchonly wallet</source> + <translation type="unfinished"></translation> + </message> + <message> + <location line="+1"/> <source>Error: Got key that was not hex: %s</source> <translation type="unfinished"></translation> </message> @@ -5461,11 +5705,46 @@ Go to File > Open Wallet to load a wallet. </message> <message> <location line="+1"/> + <source>Error: Not all watchonly txs could be deleted</source> + <translation type="unfinished"></translation> + </message> + <message> + <location line="+1"/> + <source>Error: This wallet already uses SQLite</source> + <translation type="unfinished"></translation> + </message> + <message> + <location line="+1"/> + <source>Error: This wallet is already a descriptor wallet</source> + <translation type="unfinished"></translation> + </message> + <message> + <location line="+1"/> + <source>Error: Unable to begin reading all records in the database</source> + <translation type="unfinished"></translation> + </message> + <message> + <location line="+1"/> + <source>Error: Unable to make a backup of your wallet</source> + <translation type="unfinished"></translation> + </message> + <message> + <location line="+1"/> <source>Error: Unable to parse version %u as a uint32_t</source> <translation type="unfinished"></translation> </message> <message> <location line="+1"/> + <source>Error: Unable to read all records in the database</source> + <translation type="unfinished"></translation> + </message> + <message> + <location line="+1"/> + <source>Error: Unable to remove watchonly address book data</source> + <translation type="unfinished"></translation> + </message> + <message> + <location line="+1"/> <source>Error: Unable to write record to new wallet</source> <translation type="unfinished"></translation> </message> @@ -5566,6 +5845,11 @@ Go to File > Open Wallet to load a wallet. </message> <message> <location line="+1"/> + <source>Listening for incoming connections failed (listen returned error %s)</source> + <translation type="unfinished"></translation> + </message> + <message> + <location line="+1"/> <source>Loading P2P addresses…</source> <translation type="unfinished"></translation> </message> @@ -5606,11 +5890,6 @@ Go to File > Open Wallet to load a wallet. </message> <message> <location line="+1"/> - <source>No proxy server specified. Use -proxy=<ip> or -proxy=<ip:port>.</source> - <translation type="unfinished"></translation> - </message> - <message> - <location line="+1"/> <source>Not enough file descriptors available.</source> <translation type="unfinished"></translation> </message> @@ -5621,11 +5900,6 @@ Go to File > Open Wallet to load a wallet. </message> <message> <location line="+1"/> - <source>Prune mode is incompatible with -coinstatsindex.</source> - <translation type="unfinished"></translation> - </message> - <message> - <location line="+1"/> <source>Prune mode is incompatible with -txindex.</source> <translation type="unfinished"></translation> </message> @@ -5776,6 +6050,11 @@ Go to File > Open Wallet to load a wallet. </message> <message> <location line="+1"/> + <source>Unable to allocate memory for -maxsigcachesize: '%s' MiB</source> + <translation type="unfinished"></translation> + </message> + <message> + <location line="+1"/> <source>Unable to bind to %s on this computer (bind returned error %s)</source> <translation type="unfinished"></translation> </message> @@ -5791,6 +6070,11 @@ Go to File > Open Wallet to load a wallet. </message> <message> <location line="+1"/> + <source>Unable to find UTXO for external input</source> + <translation type="unfinished"></translation> + </message> + <message> + <location line="+1"/> <source>Unable to generate initial keys</source> <translation type="unfinished"></translation> </message> @@ -5816,6 +6100,11 @@ Go to File > Open Wallet to load a wallet. </message> <message> <location line="+1"/> + <source>Unable to unload the wallet before migrating</source> + <translation type="unfinished"></translation> + </message> + <message> + <location line="+1"/> <source>Unknown -blockfilterindex value %s.</source> <translation type="unfinished"></translation> </message> @@ -5841,12 +6130,12 @@ Go to File > Open Wallet to load a wallet. </message> <message> <location line="+1"/> - <source>Unsupported logging category %s=%s.</source> + <source>Unsupported global logging level -loglevel=%s. Valid values: %s.</source> <translation type="unfinished"></translation> </message> <message> <location line="+1"/> - <source>Upgrading UTXO database</source> + <source>Unsupported logging category %s=%s.</source> <translation type="unfinished"></translation> </message> <message> @@ -5870,7 +6159,7 @@ Go to File > Open Wallet to load a wallet. <translation type="unfinished"></translation> </message> <message> - <location filename="../bitcoin.cpp" line="-496"/> + <location filename="../bitcoin.cpp" line="-519"/> <source>Settings file could not be read</source> <translation type="unfinished"></translation> </message> diff --git a/src/qt/locale/bitcoin_en.xlf b/src/qt/locale/bitcoin_en.xlf index 30ee5e4de6..a25ccfca72 100644 --- a/src/qt/locale/bitcoin_en.xlf +++ b/src/qt/locale/bitcoin_en.xlf @@ -45,7 +45,7 @@ <trans-unit id="_msg11"> <source xml:space="preserve">&Delete</source> <context-group purpose="location"><context context-type="linenumber">101</context></context-group> - <context-group purpose="location"><context context-type="sourcefile">../addressbookpage.cpp</context><context context-type="linenumber">122</context></context-group> + <context-group purpose="location"><context context-type="sourcefile">../addressbookpage.cpp</context><context context-type="linenumber">128</context></context-group> </trans-unit> </group> </body></file> @@ -53,62 +53,62 @@ <group restype="x-trolltech-linguist-context" resname="AddressBookPage"> <trans-unit id="_msg12"> <source xml:space="preserve">Choose the address to send coins to</source> - <context-group purpose="location"><context context-type="linenumber">84</context></context-group> + <context-group purpose="location"><context context-type="linenumber">90</context></context-group> </trans-unit> <trans-unit id="_msg13"> <source xml:space="preserve">Choose the address to receive coins with</source> - <context-group purpose="location"><context context-type="linenumber">85</context></context-group> + <context-group purpose="location"><context context-type="linenumber">91</context></context-group> </trans-unit> <trans-unit id="_msg14"> <source xml:space="preserve">C&hoose</source> - <context-group purpose="location"><context context-type="linenumber">90</context></context-group> + <context-group purpose="location"><context context-type="linenumber">96</context></context-group> </trans-unit> <trans-unit id="_msg15"> <source xml:space="preserve">Sending addresses</source> - <context-group purpose="location"><context context-type="linenumber">96</context></context-group> + <context-group purpose="location"><context context-type="linenumber">102</context></context-group> </trans-unit> <trans-unit id="_msg16"> <source xml:space="preserve">Receiving addresses</source> - <context-group purpose="location"><context context-type="linenumber">97</context></context-group> + <context-group purpose="location"><context context-type="linenumber">103</context></context-group> </trans-unit> <trans-unit id="_msg17"> <source xml:space="preserve">These are your Bitcoin addresses for sending payments. Always check the amount and the receiving address before sending coins.</source> - <context-group purpose="location"><context context-type="linenumber">104</context></context-group> + <context-group purpose="location"><context context-type="linenumber">110</context></context-group> </trans-unit> <trans-unit id="_msg18"> <source xml:space="preserve">These are your Bitcoin addresses for receiving payments. Use the 'Create new receiving address' button in the receive tab to create new addresses. Signing is only possible with addresses of the type 'legacy'.</source> - <context-group purpose="location"><context context-type="linenumber">109</context></context-group> + <context-group purpose="location"><context context-type="linenumber">115</context></context-group> </trans-unit> <trans-unit id="_msg19"> <source xml:space="preserve">&Copy Address</source> - <context-group purpose="location"><context context-type="linenumber">117</context></context-group> + <context-group purpose="location"><context context-type="linenumber">123</context></context-group> </trans-unit> <trans-unit id="_msg20"> <source xml:space="preserve">Copy &Label</source> - <context-group purpose="location"><context context-type="linenumber">118</context></context-group> + <context-group purpose="location"><context context-type="linenumber">124</context></context-group> </trans-unit> <trans-unit id="_msg21"> <source xml:space="preserve">&Edit</source> - <context-group purpose="location"><context context-type="linenumber">119</context></context-group> + <context-group purpose="location"><context context-type="linenumber">125</context></context-group> </trans-unit> <trans-unit id="_msg22"> <source xml:space="preserve">Export Address List</source> - <context-group purpose="location"><context context-type="linenumber">283</context></context-group> + <context-group purpose="location"><context context-type="linenumber">289</context></context-group> </trans-unit> <trans-unit id="_msg23"> <source xml:space="preserve">Comma separated file</source> - <context-group purpose="location"><context context-type="linenumber">286</context></context-group> + <context-group purpose="location"><context context-type="linenumber">292</context></context-group> <note annotates="source" from="developer">Expanded name of the CSV file format. See: https://en.wikipedia.org/wiki/Comma-separated_values.</note> </trans-unit> <trans-unit id="_msg24"> <source xml:space="preserve">There was an error trying to save the address list to %1. Please try again.</source> - <context-group purpose="location"><context context-type="linenumber">302</context></context-group> + <context-group purpose="location"><context context-type="linenumber">308</context></context-group> <note annotates="source" from="developer">An error message. %1 is a stand-in argument for the name of the file we attempted to save to.</note> </trans-unit> <trans-unit id="_msg25"> <source xml:space="preserve">Exporting Failed</source> - <context-group purpose="location"><context context-type="linenumber">299</context></context-group> + <context-group purpose="location"><context context-type="linenumber">305</context></context-group> </trans-unit> </group> </body></file> @@ -267,568 +267,614 @@ Signing is only possible with addresses of the type 'legacy'.</source> <file original="../bitcoin.cpp" datatype="cpp" source-language="en"><body> <group restype="x-trolltech-linguist-context" resname="BitcoinApplication"> <trans-unit id="_msg58"> - <source xml:space="preserve">Runaway exception</source> - <context-group purpose="location"><context context-type="linenumber">433</context></context-group> + <source xml:space="preserve">Settings file %1 might be corrupt or invalid.</source> + <context-group purpose="location"><context context-type="linenumber">288</context></context-group> </trans-unit> <trans-unit id="_msg59"> - <source xml:space="preserve">A fatal error occurred. %1 can no longer continue safely and will quit.</source> - <context-group purpose="location"><context context-type="linenumber">434</context></context-group> + <source xml:space="preserve">Runaway exception</source> + <context-group purpose="location"><context context-type="linenumber">466</context></context-group> </trans-unit> <trans-unit id="_msg60"> - <source xml:space="preserve">Internal error</source> - <context-group purpose="location"><context context-type="linenumber">443</context></context-group> + <source xml:space="preserve">A fatal error occurred. %1 can no longer continue safely and will quit.</source> + <context-group purpose="location"><context context-type="linenumber">467</context></context-group> </trans-unit> <trans-unit id="_msg61"> + <source xml:space="preserve">Internal error</source> + <context-group purpose="location"><context context-type="linenumber">476</context></context-group> + </trans-unit> + <trans-unit id="_msg62"> <source xml:space="preserve">An internal error occurred. %1 will attempt to continue safely. This is an unexpected bug which can be reported as described below.</source> - <context-group purpose="location"><context context-type="linenumber">444</context></context-group> + <context-group purpose="location"><context context-type="linenumber">477</context></context-group> </trans-unit> </group> <group restype="x-trolltech-linguist-context" resname="QObject"> - <trans-unit id="_msg62"> + <trans-unit id="_msg63"> <source xml:space="preserve">Do you want to reset settings to default values, or to abort without making changes?</source> - <context-group purpose="location"><context context-type="linenumber">168</context></context-group> + <context-group purpose="location"><context context-type="linenumber">183</context></context-group> <note annotates="source" from="developer">Explanatory text shown on startup when the settings file cannot be read. Prompts user to make a choice between resetting or aborting.</note> </trans-unit> - <trans-unit id="_msg63"> + <trans-unit id="_msg64"> <source xml:space="preserve">A fatal error occurred. Check that settings file is writable, or try running with -nosettings.</source> - <context-group purpose="location"><context context-type="linenumber">192</context></context-group> + <context-group purpose="location"><context context-type="linenumber">207</context></context-group> <note annotates="source" from="developer">Explanatory text shown on startup when the settings file could not be written. Prompts user to check that we have the ability to write to the file. Explains that the user has the option of running without a settings file.</note> </trans-unit> - <trans-unit id="_msg64"> + <trans-unit id="_msg65"> <source xml:space="preserve">Error: Specified data directory "%1" does not exist.</source> - <context-group purpose="location"><context context-type="linenumber">565</context></context-group> + <context-group purpose="location"><context context-type="linenumber">600</context></context-group> </trans-unit> - <trans-unit id="_msg65"> + <trans-unit id="_msg66"> <source xml:space="preserve">Error: Cannot parse configuration file: %1.</source> - <context-group purpose="location"><context context-type="linenumber">571</context></context-group> + <context-group purpose="location"><context context-type="linenumber">606</context></context-group> </trans-unit> - <trans-unit id="_msg66"> + <trans-unit id="_msg67"> <source xml:space="preserve">Error: %1</source> - <context-group purpose="location"><context context-type="linenumber">586</context></context-group> + <context-group purpose="location"><context context-type="linenumber">621</context></context-group> </trans-unit> - <trans-unit id="_msg67"> + <trans-unit id="_msg68"> <source xml:space="preserve">%1 didn't yet exit safely…</source> - <context-group purpose="location"><context context-type="linenumber">657</context></context-group> + <context-group purpose="location"><context context-type="linenumber">695</context></context-group> </trans-unit> </group> <group restype="x-trolltech-linguist-context" resname="bitcoin-core"> - <trans-unit id="_msg68"> + <trans-unit id="_msg69"> <source xml:space="preserve">Settings file could not be read</source> - <context-group purpose="location"><context context-type="linenumber">161</context></context-group> + <context-group purpose="location"><context context-type="linenumber">176</context></context-group> </trans-unit> - <trans-unit id="_msg69"> + <trans-unit id="_msg70"> <source xml:space="preserve">Settings file could not be written</source> - <context-group purpose="location"><context context-type="linenumber">184</context></context-group> + <context-group purpose="location"><context context-type="linenumber">199</context></context-group> </trans-unit> </group> </body></file> <file original="../bitcoingui.cpp" datatype="cpp" source-language="en"><body> <group restype="x-trolltech-linguist-context" resname="BitcoinGUI"> - <trans-unit id="_msg70"> - <source xml:space="preserve">&Overview</source> - <context-group purpose="location"><context context-type="linenumber">250</context></context-group> - </trans-unit> <trans-unit id="_msg71"> - <source xml:space="preserve">Show general overview of wallet</source> - <context-group purpose="location"><context context-type="linenumber">251</context></context-group> + <source xml:space="preserve">&Overview</source> + <context-group purpose="location"><context context-type="linenumber">253</context></context-group> </trans-unit> <trans-unit id="_msg72"> - <source xml:space="preserve">&Transactions</source> - <context-group purpose="location"><context context-type="linenumber">271</context></context-group> + <source xml:space="preserve">Show general overview of wallet</source> + <context-group purpose="location"><context context-type="linenumber">254</context></context-group> </trans-unit> <trans-unit id="_msg73"> - <source xml:space="preserve">Browse transaction history</source> - <context-group purpose="location"><context context-type="linenumber">272</context></context-group> + <source xml:space="preserve">&Transactions</source> + <context-group purpose="location"><context context-type="linenumber">274</context></context-group> </trans-unit> <trans-unit id="_msg74"> - <source xml:space="preserve">E&xit</source> - <context-group purpose="location"><context context-type="linenumber">291</context></context-group> + <source xml:space="preserve">Browse transaction history</source> + <context-group purpose="location"><context context-type="linenumber">275</context></context-group> </trans-unit> <trans-unit id="_msg75"> - <source xml:space="preserve">Quit application</source> - <context-group purpose="location"><context context-type="linenumber">292</context></context-group> + <source xml:space="preserve">E&xit</source> + <context-group purpose="location"><context context-type="linenumber">294</context></context-group> </trans-unit> <trans-unit id="_msg76"> - <source xml:space="preserve">&About %1</source> + <source xml:space="preserve">Quit application</source> <context-group purpose="location"><context context-type="linenumber">295</context></context-group> </trans-unit> <trans-unit id="_msg77"> - <source xml:space="preserve">Show information about %1</source> - <context-group purpose="location"><context context-type="linenumber">296</context></context-group> + <source xml:space="preserve">&About %1</source> + <context-group purpose="location"><context context-type="linenumber">298</context></context-group> </trans-unit> <trans-unit id="_msg78"> - <source xml:space="preserve">About &Qt</source> + <source xml:space="preserve">Show information about %1</source> <context-group purpose="location"><context context-type="linenumber">299</context></context-group> </trans-unit> <trans-unit id="_msg79"> - <source xml:space="preserve">Show information about Qt</source> - <context-group purpose="location"><context context-type="linenumber">300</context></context-group> + <source xml:space="preserve">About &Qt</source> + <context-group purpose="location"><context context-type="linenumber">302</context></context-group> </trans-unit> <trans-unit id="_msg80"> - <source xml:space="preserve">Modify configuration options for %1</source> + <source xml:space="preserve">Show information about Qt</source> <context-group purpose="location"><context context-type="linenumber">303</context></context-group> </trans-unit> <trans-unit id="_msg81"> - <source xml:space="preserve">Create a new wallet</source> - <context-group purpose="location"><context context-type="linenumber">347</context></context-group> + <source xml:space="preserve">Modify configuration options for %1</source> + <context-group purpose="location"><context context-type="linenumber">306</context></context-group> </trans-unit> <trans-unit id="_msg82"> - <source xml:space="preserve">&Minimize</source> - <context-group purpose="location"><context context-type="linenumber">474</context></context-group> + <source xml:space="preserve">Create a new wallet</source> + <context-group purpose="location"><context context-type="linenumber">350</context></context-group> </trans-unit> <trans-unit id="_msg83"> - <source xml:space="preserve">Wallet:</source> - <context-group purpose="location"><context context-type="linenumber">553</context></context-group> + <source xml:space="preserve">&Minimize</source> + <context-group purpose="location"><context context-type="linenumber">510</context></context-group> </trans-unit> <trans-unit id="_msg84"> - <source xml:space="preserve">Network activity disabled.</source> - <context-group purpose="location"><context context-type="linenumber">945</context></context-group> - <note annotates="source" from="developer">A substring of the tooltip.</note> + <source xml:space="preserve">Wallet:</source> + <context-group purpose="location"><context context-type="linenumber">589</context></context-group> </trans-unit> <trans-unit id="_msg85"> - <source xml:space="preserve">Proxy is <b>enabled</b>: %1</source> - <context-group purpose="location"><context context-type="linenumber">1367</context></context-group> + <source xml:space="preserve">Network activity disabled.</source> + <context-group purpose="location"><context context-type="linenumber">982</context></context-group> + <note annotates="source" from="developer">A substring of the tooltip.</note> </trans-unit> <trans-unit id="_msg86"> - <source xml:space="preserve">Send coins to a Bitcoin address</source> - <context-group purpose="location"><context context-type="linenumber">258</context></context-group> + <source xml:space="preserve">Proxy is <b>enabled</b>: %1</source> + <context-group purpose="location"><context context-type="linenumber">1421</context></context-group> </trans-unit> <trans-unit id="_msg87"> - <source xml:space="preserve">Backup wallet to another location</source> - <context-group purpose="location"><context context-type="linenumber">311</context></context-group> + <source xml:space="preserve">Send coins to a Bitcoin address</source> + <context-group purpose="location"><context context-type="linenumber">261</context></context-group> </trans-unit> <trans-unit id="_msg88"> - <source xml:space="preserve">Change the passphrase used for wallet encryption</source> - <context-group purpose="location"><context context-type="linenumber">313</context></context-group> + <source xml:space="preserve">Backup wallet to another location</source> + <context-group purpose="location"><context context-type="linenumber">314</context></context-group> </trans-unit> <trans-unit id="_msg89"> - <source xml:space="preserve">&Send</source> - <context-group purpose="location"><context context-type="linenumber">257</context></context-group> + <source xml:space="preserve">Change the passphrase used for wallet encryption</source> + <context-group purpose="location"><context context-type="linenumber">316</context></context-group> </trans-unit> <trans-unit id="_msg90"> - <source xml:space="preserve">&Receive</source> - <context-group purpose="location"><context context-type="linenumber">264</context></context-group> + <source xml:space="preserve">&Send</source> + <context-group purpose="location"><context context-type="linenumber">260</context></context-group> </trans-unit> <trans-unit id="_msg91"> - <source xml:space="preserve">&Options…</source> - <context-group purpose="location"><context context-type="linenumber">302</context></context-group> + <source xml:space="preserve">&Receive</source> + <context-group purpose="location"><context context-type="linenumber">267</context></context-group> </trans-unit> <trans-unit id="_msg92"> - <source xml:space="preserve">&Encrypt Wallet…</source> - <context-group purpose="location"><context context-type="linenumber">307</context></context-group> + <source xml:space="preserve">&Options…</source> + <context-group purpose="location"><context context-type="linenumber">305</context></context-group> </trans-unit> <trans-unit id="_msg93"> - <source xml:space="preserve">Encrypt the private keys that belong to your wallet</source> - <context-group purpose="location"><context context-type="linenumber">308</context></context-group> + <source xml:space="preserve">&Encrypt Wallet…</source> + <context-group purpose="location"><context context-type="linenumber">310</context></context-group> </trans-unit> <trans-unit id="_msg94"> - <source xml:space="preserve">&Backup Wallet…</source> - <context-group purpose="location"><context context-type="linenumber">310</context></context-group> + <source xml:space="preserve">Encrypt the private keys that belong to your wallet</source> + <context-group purpose="location"><context context-type="linenumber">311</context></context-group> </trans-unit> <trans-unit id="_msg95"> - <source xml:space="preserve">&Change Passphrase…</source> - <context-group purpose="location"><context context-type="linenumber">312</context></context-group> + <source xml:space="preserve">&Backup Wallet…</source> + <context-group purpose="location"><context context-type="linenumber">313</context></context-group> </trans-unit> <trans-unit id="_msg96"> - <source xml:space="preserve">Sign &message…</source> - <context-group purpose="location"><context context-type="linenumber">314</context></context-group> + <source xml:space="preserve">&Change Passphrase…</source> + <context-group purpose="location"><context context-type="linenumber">315</context></context-group> </trans-unit> <trans-unit id="_msg97"> - <source xml:space="preserve">Sign messages with your Bitcoin addresses to prove you own them</source> - <context-group purpose="location"><context context-type="linenumber">315</context></context-group> + <source xml:space="preserve">Sign &message…</source> + <context-group purpose="location"><context context-type="linenumber">317</context></context-group> </trans-unit> <trans-unit id="_msg98"> - <source xml:space="preserve">&Verify message…</source> - <context-group purpose="location"><context context-type="linenumber">316</context></context-group> + <source xml:space="preserve">Sign messages with your Bitcoin addresses to prove you own them</source> + <context-group purpose="location"><context context-type="linenumber">318</context></context-group> </trans-unit> <trans-unit id="_msg99"> - <source xml:space="preserve">Verify messages to ensure they were signed with specified Bitcoin addresses</source> - <context-group purpose="location"><context context-type="linenumber">317</context></context-group> + <source xml:space="preserve">&Verify message…</source> + <context-group purpose="location"><context context-type="linenumber">319</context></context-group> </trans-unit> <trans-unit id="_msg100"> - <source xml:space="preserve">&Load PSBT from file…</source> - <context-group purpose="location"><context context-type="linenumber">318</context></context-group> + <source xml:space="preserve">Verify messages to ensure they were signed with specified Bitcoin addresses</source> + <context-group purpose="location"><context context-type="linenumber">320</context></context-group> </trans-unit> <trans-unit id="_msg101"> - <source xml:space="preserve">Open &URI…</source> - <context-group purpose="location"><context context-type="linenumber">334</context></context-group> + <source xml:space="preserve">&Load PSBT from file…</source> + <context-group purpose="location"><context context-type="linenumber">321</context></context-group> </trans-unit> <trans-unit id="_msg102"> - <source xml:space="preserve">Close Wallet…</source> - <context-group purpose="location"><context context-type="linenumber">342</context></context-group> + <source xml:space="preserve">Open &URI…</source> + <context-group purpose="location"><context context-type="linenumber">337</context></context-group> </trans-unit> <trans-unit id="_msg103"> - <source xml:space="preserve">Create Wallet…</source> + <source xml:space="preserve">Close Wallet…</source> <context-group purpose="location"><context context-type="linenumber">345</context></context-group> </trans-unit> <trans-unit id="_msg104"> - <source xml:space="preserve">Close All Wallets…</source> - <context-group purpose="location"><context context-type="linenumber">349</context></context-group> + <source xml:space="preserve">Create Wallet…</source> + <context-group purpose="location"><context context-type="linenumber">348</context></context-group> </trans-unit> <trans-unit id="_msg105"> - <source xml:space="preserve">&File</source> - <context-group purpose="location"><context context-type="linenumber">443</context></context-group> + <source xml:space="preserve">Close All Wallets…</source> + <context-group purpose="location"><context context-type="linenumber">358</context></context-group> </trans-unit> <trans-unit id="_msg106"> - <source xml:space="preserve">&Settings</source> - <context-group purpose="location"><context context-type="linenumber">461</context></context-group> + <source xml:space="preserve">&File</source> + <context-group purpose="location"><context context-type="linenumber">477</context></context-group> </trans-unit> <trans-unit id="_msg107"> - <source xml:space="preserve">&Help</source> - <context-group purpose="location"><context context-type="linenumber">522</context></context-group> + <source xml:space="preserve">&Settings</source> + <context-group purpose="location"><context context-type="linenumber">497</context></context-group> </trans-unit> <trans-unit id="_msg108"> - <source xml:space="preserve">Tabs toolbar</source> - <context-group purpose="location"><context context-type="linenumber">533</context></context-group> + <source xml:space="preserve">&Help</source> + <context-group purpose="location"><context context-type="linenumber">558</context></context-group> </trans-unit> <trans-unit id="_msg109"> - <source xml:space="preserve">Syncing Headers (%1%)…</source> - <context-group purpose="location"><context context-type="linenumber">989</context></context-group> + <source xml:space="preserve">Tabs toolbar</source> + <context-group purpose="location"><context context-type="linenumber">569</context></context-group> </trans-unit> <trans-unit id="_msg110"> - <source xml:space="preserve">Synchronizing with network…</source> - <context-group purpose="location"><context context-type="linenumber">1036</context></context-group> + <source xml:space="preserve">Syncing Headers (%1%)…</source> + <context-group purpose="location"><context context-type="linenumber">1026</context></context-group> </trans-unit> <trans-unit id="_msg111"> - <source xml:space="preserve">Indexing blocks on disk…</source> - <context-group purpose="location"><context context-type="linenumber">1041</context></context-group> + <source xml:space="preserve">Synchronizing with network…</source> + <context-group purpose="location"><context context-type="linenumber">1084</context></context-group> </trans-unit> <trans-unit id="_msg112"> - <source xml:space="preserve">Processing blocks on disk…</source> - <context-group purpose="location"><context context-type="linenumber">1043</context></context-group> + <source xml:space="preserve">Indexing blocks on disk…</source> + <context-group purpose="location"><context context-type="linenumber">1089</context></context-group> </trans-unit> <trans-unit id="_msg113"> - <source xml:space="preserve">Reindexing blocks on disk…</source> - <context-group purpose="location"><context context-type="linenumber">1047</context></context-group> + <source xml:space="preserve">Processing blocks on disk…</source> + <context-group purpose="location"><context context-type="linenumber">1091</context></context-group> </trans-unit> <trans-unit id="_msg114"> - <source xml:space="preserve">Connecting to peers…</source> - <context-group purpose="location"><context context-type="linenumber">1053</context></context-group> + <source xml:space="preserve">Reindexing blocks on disk…</source> + <context-group purpose="location"><context context-type="linenumber">1095</context></context-group> </trans-unit> <trans-unit id="_msg115"> - <source xml:space="preserve">Request payments (generates QR codes and bitcoin: URIs)</source> - <context-group purpose="location"><context context-type="linenumber">265</context></context-group> + <source xml:space="preserve">Connecting to peers…</source> + <context-group purpose="location"><context context-type="linenumber">1101</context></context-group> </trans-unit> <trans-unit id="_msg116"> - <source xml:space="preserve">Show the list of used sending addresses and labels</source> - <context-group purpose="location"><context context-type="linenumber">330</context></context-group> + <source xml:space="preserve">Request payments (generates QR codes and bitcoin: URIs)</source> + <context-group purpose="location"><context context-type="linenumber">268</context></context-group> </trans-unit> <trans-unit id="_msg117"> - <source xml:space="preserve">Show the list of used receiving addresses and labels</source> - <context-group purpose="location"><context context-type="linenumber">332</context></context-group> + <source xml:space="preserve">Show the list of used sending addresses and labels</source> + <context-group purpose="location"><context context-type="linenumber">333</context></context-group> </trans-unit> <trans-unit id="_msg118"> + <source xml:space="preserve">Show the list of used receiving addresses and labels</source> + <context-group purpose="location"><context context-type="linenumber">335</context></context-group> + </trans-unit> + <trans-unit id="_msg119"> <source xml:space="preserve">&Command-line options</source> - <context-group purpose="location"><context context-type="linenumber">352</context></context-group> + <context-group purpose="location"><context context-type="linenumber">361</context></context-group> </trans-unit> <group restype="x-gettext-plurals"> - <context-group purpose="location"><context context-type="linenumber">1062</context></context-group> - <trans-unit id="_msg119[0]"> + <context-group purpose="location"><context context-type="linenumber">1110</context></context-group> + <trans-unit id="_msg120[0]"> <source xml:space="preserve">Processed %n block(s) of transaction history.</source> </trans-unit> - <trans-unit id="_msg119[1]"> + <trans-unit id="_msg120[1]"> <source xml:space="preserve">Processed %n block(s) of transaction history.</source> </trans-unit> </group> - <trans-unit id="_msg120"> - <source xml:space="preserve">%1 behind</source> - <context-group purpose="location"><context context-type="linenumber">1085</context></context-group> - </trans-unit> <trans-unit id="_msg121"> - <source xml:space="preserve">Catching up…</source> - <context-group purpose="location"><context context-type="linenumber">1090</context></context-group> + <source xml:space="preserve">%1 behind</source> + <context-group purpose="location"><context context-type="linenumber">1133</context></context-group> </trans-unit> <trans-unit id="_msg122"> - <source xml:space="preserve">Last received block was generated %1 ago.</source> - <context-group purpose="location"><context context-type="linenumber">1109</context></context-group> + <source xml:space="preserve">Catching up…</source> + <context-group purpose="location"><context context-type="linenumber">1138</context></context-group> </trans-unit> <trans-unit id="_msg123"> - <source xml:space="preserve">Transactions after this will not yet be visible.</source> - <context-group purpose="location"><context context-type="linenumber">1111</context></context-group> + <source xml:space="preserve">Last received block was generated %1 ago.</source> + <context-group purpose="location"><context context-type="linenumber">1157</context></context-group> </trans-unit> <trans-unit id="_msg124"> - <source xml:space="preserve">Error</source> - <context-group purpose="location"><context context-type="linenumber">1136</context></context-group> + <source xml:space="preserve">Transactions after this will not yet be visible.</source> + <context-group purpose="location"><context context-type="linenumber">1159</context></context-group> </trans-unit> <trans-unit id="_msg125"> - <source xml:space="preserve">Warning</source> - <context-group purpose="location"><context context-type="linenumber">1140</context></context-group> + <source xml:space="preserve">Error</source> + <context-group purpose="location"><context context-type="linenumber">1184</context></context-group> </trans-unit> <trans-unit id="_msg126"> - <source xml:space="preserve">Information</source> - <context-group purpose="location"><context context-type="linenumber">1144</context></context-group> + <source xml:space="preserve">Warning</source> + <context-group purpose="location"><context context-type="linenumber">1188</context></context-group> </trans-unit> <trans-unit id="_msg127"> - <source xml:space="preserve">Up to date</source> - <context-group purpose="location"><context context-type="linenumber">1066</context></context-group> + <source xml:space="preserve">Information</source> + <context-group purpose="location"><context context-type="linenumber">1192</context></context-group> </trans-unit> <trans-unit id="_msg128"> - <source xml:space="preserve">Load Partially Signed Bitcoin Transaction</source> - <context-group purpose="location"><context context-type="linenumber">319</context></context-group> + <source xml:space="preserve">Up to date</source> + <context-group purpose="location"><context context-type="linenumber">1114</context></context-group> </trans-unit> <trans-unit id="_msg129"> - <source xml:space="preserve">Load PSBT from &clipboard…</source> - <context-group purpose="location"><context context-type="linenumber">320</context></context-group> + <source xml:space="preserve">Ctrl+Q</source> + <context-group purpose="location"><context context-type="linenumber">296</context></context-group> </trans-unit> <trans-unit id="_msg130"> - <source xml:space="preserve">Load Partially Signed Bitcoin Transaction from clipboard</source> - <context-group purpose="location"><context context-type="linenumber">321</context></context-group> + <source xml:space="preserve">Load Partially Signed Bitcoin Transaction</source> + <context-group purpose="location"><context context-type="linenumber">322</context></context-group> </trans-unit> <trans-unit id="_msg131"> - <source xml:space="preserve">Node window</source> + <source xml:space="preserve">Load PSBT from &clipboard…</source> <context-group purpose="location"><context context-type="linenumber">323</context></context-group> </trans-unit> <trans-unit id="_msg132"> - <source xml:space="preserve">Open node debugging and diagnostic console</source> + <source xml:space="preserve">Load Partially Signed Bitcoin Transaction from clipboard</source> <context-group purpose="location"><context context-type="linenumber">324</context></context-group> </trans-unit> <trans-unit id="_msg133"> - <source xml:space="preserve">&Sending addresses</source> - <context-group purpose="location"><context context-type="linenumber">329</context></context-group> + <source xml:space="preserve">Node window</source> + <context-group purpose="location"><context context-type="linenumber">326</context></context-group> </trans-unit> <trans-unit id="_msg134"> - <source xml:space="preserve">&Receiving addresses</source> - <context-group purpose="location"><context context-type="linenumber">331</context></context-group> + <source xml:space="preserve">Open node debugging and diagnostic console</source> + <context-group purpose="location"><context context-type="linenumber">327</context></context-group> </trans-unit> <trans-unit id="_msg135"> - <source xml:space="preserve">Open a bitcoin: URI</source> - <context-group purpose="location"><context context-type="linenumber">335</context></context-group> + <source xml:space="preserve">&Sending addresses</source> + <context-group purpose="location"><context context-type="linenumber">332</context></context-group> </trans-unit> <trans-unit id="_msg136"> - <source xml:space="preserve">Open Wallet</source> - <context-group purpose="location"><context context-type="linenumber">337</context></context-group> + <source xml:space="preserve">&Receiving addresses</source> + <context-group purpose="location"><context context-type="linenumber">334</context></context-group> </trans-unit> <trans-unit id="_msg137"> - <source xml:space="preserve">Open a wallet</source> - <context-group purpose="location"><context context-type="linenumber">339</context></context-group> + <source xml:space="preserve">Open a bitcoin: URI</source> + <context-group purpose="location"><context context-type="linenumber">338</context></context-group> </trans-unit> <trans-unit id="_msg138"> - <source xml:space="preserve">Close wallet</source> - <context-group purpose="location"><context context-type="linenumber">343</context></context-group> + <source xml:space="preserve">Open Wallet</source> + <context-group purpose="location"><context context-type="linenumber">340</context></context-group> </trans-unit> <trans-unit id="_msg139"> - <source xml:space="preserve">Close all wallets</source> - <context-group purpose="location"><context context-type="linenumber">350</context></context-group> + <source xml:space="preserve">Open a wallet</source> + <context-group purpose="location"><context context-type="linenumber">342</context></context-group> </trans-unit> <trans-unit id="_msg140"> - <source xml:space="preserve">Show the %1 help message to get a list with possible Bitcoin command-line options</source> - <context-group purpose="location"><context context-type="linenumber">354</context></context-group> + <source xml:space="preserve">Close wallet</source> + <context-group purpose="location"><context context-type="linenumber">346</context></context-group> </trans-unit> <trans-unit id="_msg141"> - <source xml:space="preserve">&Mask values</source> - <context-group purpose="location"><context context-type="linenumber">356</context></context-group> + <source xml:space="preserve">Restore Wallet…</source> + <context-group purpose="location"><context context-type="linenumber">353</context></context-group> + <note annotates="source" from="developer">Name of the menu item that restores wallet from a backup file.</note> </trans-unit> <trans-unit id="_msg142"> - <source xml:space="preserve">Mask the values in the Overview tab</source> - <context-group purpose="location"><context context-type="linenumber">358</context></context-group> + <source xml:space="preserve">Restore a wallet from a backup file</source> + <context-group purpose="location"><context context-type="linenumber">356</context></context-group> + <note annotates="source" from="developer">Status tip for Restore Wallet menu item</note> </trans-unit> <trans-unit id="_msg143"> - <source xml:space="preserve">default wallet</source> - <context-group purpose="location"><context context-type="linenumber">389</context></context-group> + <source xml:space="preserve">Close all wallets</source> + <context-group purpose="location"><context context-type="linenumber">359</context></context-group> </trans-unit> <trans-unit id="_msg144"> - <source xml:space="preserve">No wallets available</source> - <context-group purpose="location"><context context-type="linenumber">409</context></context-group> + <source xml:space="preserve">Show the %1 help message to get a list with possible Bitcoin command-line options</source> + <context-group purpose="location"><context context-type="linenumber">363</context></context-group> </trans-unit> <trans-unit id="_msg145"> - <source xml:space="preserve">&Window</source> - <context-group purpose="location"><context context-type="linenumber">472</context></context-group> + <source xml:space="preserve">&Mask values</source> + <context-group purpose="location"><context context-type="linenumber">365</context></context-group> </trans-unit> <trans-unit id="_msg146"> - <source xml:space="preserve">Zoom</source> - <context-group purpose="location"><context context-type="linenumber">484</context></context-group> + <source xml:space="preserve">Mask the values in the Overview tab</source> + <context-group purpose="location"><context context-type="linenumber">367</context></context-group> </trans-unit> <trans-unit id="_msg147"> - <source xml:space="preserve">Main Window</source> - <context-group purpose="location"><context context-type="linenumber">502</context></context-group> + <source xml:space="preserve">default wallet</source> + <context-group purpose="location"><context context-type="linenumber">398</context></context-group> </trans-unit> <trans-unit id="_msg148"> - <source xml:space="preserve">%1 client</source> - <context-group purpose="location"><context context-type="linenumber">759</context></context-group> + <source xml:space="preserve">No wallets available</source> + <context-group purpose="location"><context context-type="linenumber">418</context></context-group> </trans-unit> <trans-unit id="_msg149"> - <source xml:space="preserve">&Hide</source> - <context-group purpose="location"><context context-type="linenumber">824</context></context-group> + <source xml:space="preserve">Wallet Data</source> + <context-group purpose="location"><context context-type="linenumber">424</context></context-group> + <note annotates="source" from="developer">Name of the wallet data file format.</note> </trans-unit> <trans-unit id="_msg150"> + <source xml:space="preserve">Load Wallet Backup</source> + <context-group purpose="location"><context context-type="linenumber">427</context></context-group> + <note annotates="source" from="developer">The title for Restore Wallet File Windows</note> + </trans-unit> + <trans-unit id="_msg151"> + <source xml:space="preserve">Restore Wallet</source> + <context-group purpose="location"><context context-type="linenumber">435</context></context-group> + <note annotates="source" from="developer">Title of pop-up window shown when the user is attempting to restore a wallet.</note> + </trans-unit> + <trans-unit id="_msg152"> + <source xml:space="preserve">Wallet Name</source> + <context-group purpose="location"><context context-type="linenumber">437</context></context-group> + <note annotates="source" from="developer">Label of the input field where the name of the wallet is entered.</note> + </trans-unit> + <trans-unit id="_msg153"> + <source xml:space="preserve">&Window</source> + <context-group purpose="location"><context context-type="linenumber">508</context></context-group> + </trans-unit> + <trans-unit id="_msg154"> + <source xml:space="preserve">Ctrl+M</source> + <context-group purpose="location"><context context-type="linenumber">511</context></context-group> + </trans-unit> + <trans-unit id="_msg155"> + <source xml:space="preserve">Zoom</source> + <context-group purpose="location"><context context-type="linenumber">520</context></context-group> + </trans-unit> + <trans-unit id="_msg156"> + <source xml:space="preserve">Main Window</source> + <context-group purpose="location"><context context-type="linenumber">538</context></context-group> + </trans-unit> + <trans-unit id="_msg157"> + <source xml:space="preserve">%1 client</source> + <context-group purpose="location"><context context-type="linenumber">796</context></context-group> + </trans-unit> + <trans-unit id="_msg158"> + <source xml:space="preserve">&Hide</source> + <context-group purpose="location"><context context-type="linenumber">861</context></context-group> + </trans-unit> + <trans-unit id="_msg159"> <source xml:space="preserve">S&how</source> - <context-group purpose="location"><context context-type="linenumber">825</context></context-group> + <context-group purpose="location"><context context-type="linenumber">862</context></context-group> </trans-unit> <group restype="x-gettext-plurals"> - <context-group purpose="location"><context context-type="linenumber">942</context></context-group> + <context-group purpose="location"><context context-type="linenumber">979</context></context-group> <note annotates="source" from="developer">A substring of the tooltip.</note> - <trans-unit id="_msg151[0]"> + <trans-unit id="_msg160[0]"> <source xml:space="preserve">%n active connection(s) to Bitcoin network.</source> </trans-unit> - <trans-unit id="_msg151[1]"> + <trans-unit id="_msg160[1]"> <source xml:space="preserve">%n active connection(s) to Bitcoin network.</source> </trans-unit> </group> - <trans-unit id="_msg152"> + <trans-unit id="_msg161"> <source xml:space="preserve">Click for more actions.</source> - <context-group purpose="location"><context context-type="linenumber">952</context></context-group> + <context-group purpose="location"><context context-type="linenumber">989</context></context-group> <note annotates="source" from="developer">A substring of the tooltip. "More actions" are available via the context menu.</note> </trans-unit> - <trans-unit id="_msg153"> + <trans-unit id="_msg162"> <source xml:space="preserve">Show Peers tab</source> - <context-group purpose="location"><context context-type="linenumber">969</context></context-group> + <context-group purpose="location"><context context-type="linenumber">1006</context></context-group> <note annotates="source" from="developer">A context menu item. The "Peers tab" is an element of the "Node window".</note> </trans-unit> - <trans-unit id="_msg154"> + <trans-unit id="_msg163"> <source xml:space="preserve">Disable network activity</source> - <context-group purpose="location"><context context-type="linenumber">977</context></context-group> + <context-group purpose="location"><context context-type="linenumber">1014</context></context-group> <note annotates="source" from="developer">A context menu item.</note> </trans-unit> - <trans-unit id="_msg155"> + <trans-unit id="_msg164"> <source xml:space="preserve">Enable network activity</source> - <context-group purpose="location"><context context-type="linenumber">979</context></context-group> + <context-group purpose="location"><context context-type="linenumber">1016</context></context-group> <note annotates="source" from="developer">A context menu item. The network activity was disabled previously.</note> </trans-unit> - <trans-unit id="_msg156"> + <trans-unit id="_msg165"> + <source xml:space="preserve">Pre-syncing Headers (%1%)…</source> + <context-group purpose="location"><context context-type="linenumber">1033</context></context-group> + </trans-unit> + <trans-unit id="_msg166"> <source xml:space="preserve">Error: %1</source> - <context-group purpose="location"><context context-type="linenumber">1137</context></context-group> + <context-group purpose="location"><context context-type="linenumber">1185</context></context-group> </trans-unit> - <trans-unit id="_msg157"> + <trans-unit id="_msg167"> <source xml:space="preserve">Warning: %1</source> - <context-group purpose="location"><context context-type="linenumber">1141</context></context-group> + <context-group purpose="location"><context context-type="linenumber">1189</context></context-group> </trans-unit> - <trans-unit id="_msg158"> + <trans-unit id="_msg168"> <source xml:space="preserve">Date: %1 </source> - <context-group purpose="location"><context context-type="linenumber">1249</context></context-group> + <context-group purpose="location"><context context-type="linenumber">1297</context></context-group> </trans-unit> - <trans-unit id="_msg159"> + <trans-unit id="_msg169"> <source xml:space="preserve">Amount: %1 </source> - <context-group purpose="location"><context context-type="linenumber">1250</context></context-group> + <context-group purpose="location"><context context-type="linenumber">1298</context></context-group> </trans-unit> - <trans-unit id="_msg160"> + <trans-unit id="_msg170"> <source xml:space="preserve">Wallet: %1 </source> - <context-group purpose="location"><context context-type="linenumber">1252</context></context-group> + <context-group purpose="location"><context context-type="linenumber">1300</context></context-group> </trans-unit> - <trans-unit id="_msg161"> + <trans-unit id="_msg171"> <source xml:space="preserve">Type: %1 </source> - <context-group purpose="location"><context context-type="linenumber">1254</context></context-group> + <context-group purpose="location"><context context-type="linenumber">1302</context></context-group> </trans-unit> - <trans-unit id="_msg162"> + <trans-unit id="_msg172"> <source xml:space="preserve">Label: %1 </source> - <context-group purpose="location"><context context-type="linenumber">1256</context></context-group> + <context-group purpose="location"><context context-type="linenumber">1304</context></context-group> </trans-unit> - <trans-unit id="_msg163"> + <trans-unit id="_msg173"> <source xml:space="preserve">Address: %1 </source> - <context-group purpose="location"><context context-type="linenumber">1258</context></context-group> + <context-group purpose="location"><context context-type="linenumber">1306</context></context-group> </trans-unit> - <trans-unit id="_msg164"> + <trans-unit id="_msg174"> <source xml:space="preserve">Sent transaction</source> - <context-group purpose="location"><context context-type="linenumber">1259</context></context-group> + <context-group purpose="location"><context context-type="linenumber">1307</context></context-group> </trans-unit> - <trans-unit id="_msg165"> + <trans-unit id="_msg175"> <source xml:space="preserve">Incoming transaction</source> - <context-group purpose="location"><context context-type="linenumber">1259</context></context-group> + <context-group purpose="location"><context context-type="linenumber">1307</context></context-group> </trans-unit> - <trans-unit id="_msg166"> + <trans-unit id="_msg176"> <source xml:space="preserve">HD key generation is <b>enabled</b></source> - <context-group purpose="location"><context context-type="linenumber">1311</context></context-group> + <context-group purpose="location"><context context-type="linenumber">1359</context></context-group> </trans-unit> - <trans-unit id="_msg167"> + <trans-unit id="_msg177"> <source xml:space="preserve">HD key generation is <b>disabled</b></source> - <context-group purpose="location"><context context-type="linenumber">1311</context></context-group> + <context-group purpose="location"><context context-type="linenumber">1359</context></context-group> </trans-unit> - <trans-unit id="_msg168"> + <trans-unit id="_msg178"> <source xml:space="preserve">Private key <b>disabled</b></source> - <context-group purpose="location"><context context-type="linenumber">1311</context></context-group> + <context-group purpose="location"><context context-type="linenumber">1359</context></context-group> </trans-unit> - <trans-unit id="_msg169"> + <trans-unit id="_msg179"> <source xml:space="preserve">Wallet is <b>encrypted</b> and currently <b>unlocked</b></source> - <context-group purpose="location"><context context-type="linenumber">1328</context></context-group> + <context-group purpose="location"><context context-type="linenumber">1382</context></context-group> </trans-unit> - <trans-unit id="_msg170"> + <trans-unit id="_msg180"> <source xml:space="preserve">Wallet is <b>encrypted</b> and currently <b>locked</b></source> - <context-group purpose="location"><context context-type="linenumber">1336</context></context-group> + <context-group purpose="location"><context context-type="linenumber">1390</context></context-group> </trans-unit> - <trans-unit id="_msg171"> + <trans-unit id="_msg181"> <source xml:space="preserve">Original message:</source> - <context-group purpose="location"><context context-type="linenumber">1455</context></context-group> + <context-group purpose="location"><context context-type="linenumber">1509</context></context-group> </trans-unit> </group> <group restype="x-trolltech-linguist-context" resname="UnitDisplayStatusBarControl"> - <trans-unit id="_msg172"> + <trans-unit id="_msg182"> <source xml:space="preserve">Unit to show amounts in. Click to select another unit.</source> - <context-group purpose="location"><context context-type="linenumber">1496</context></context-group> + <context-group purpose="location"><context context-type="linenumber">1550</context></context-group> </trans-unit> </group> </body></file> <file original="../forms/coincontroldialog.ui" datatype="x-trolltech-designer-ui" source-language="en"><body> <group restype="x-trolltech-linguist-context" resname="CoinControlDialog"> - <trans-unit id="_msg173"> + <trans-unit id="_msg183"> <source xml:space="preserve">Coin Selection</source> <context-group purpose="location"><context context-type="linenumber">14</context></context-group> </trans-unit> - <trans-unit id="_msg174"> + <trans-unit id="_msg184"> <source xml:space="preserve">Quantity:</source> <context-group purpose="location"><context context-type="linenumber">48</context></context-group> </trans-unit> - <trans-unit id="_msg175"> + <trans-unit id="_msg185"> <source xml:space="preserve">Bytes:</source> <context-group purpose="location"><context context-type="linenumber">77</context></context-group> </trans-unit> - <trans-unit id="_msg176"> + <trans-unit id="_msg186"> <source xml:space="preserve">Amount:</source> <context-group purpose="location"><context context-type="linenumber">122</context></context-group> </trans-unit> - <trans-unit id="_msg177"> + <trans-unit id="_msg187"> <source xml:space="preserve">Fee:</source> <context-group purpose="location"><context context-type="linenumber">202</context></context-group> </trans-unit> - <trans-unit id="_msg178"> + <trans-unit id="_msg188"> <source xml:space="preserve">Dust:</source> <context-group purpose="location"><context context-type="linenumber">154</context></context-group> </trans-unit> - <trans-unit id="_msg179"> + <trans-unit id="_msg189"> <source xml:space="preserve">After Fee:</source> <context-group purpose="location"><context context-type="linenumber">247</context></context-group> </trans-unit> - <trans-unit id="_msg180"> + <trans-unit id="_msg190"> <source xml:space="preserve">Change:</source> <context-group purpose="location"><context context-type="linenumber">279</context></context-group> </trans-unit> - <trans-unit id="_msg181"> + <trans-unit id="_msg191"> <source xml:space="preserve">(un)select all</source> <context-group purpose="location"><context context-type="linenumber">335</context></context-group> </trans-unit> - <trans-unit id="_msg182"> + <trans-unit id="_msg192"> <source xml:space="preserve">Tree mode</source> <context-group purpose="location"><context context-type="linenumber">351</context></context-group> </trans-unit> - <trans-unit id="_msg183"> + <trans-unit id="_msg193"> <source xml:space="preserve">List mode</source> <context-group purpose="location"><context context-type="linenumber">364</context></context-group> </trans-unit> - <trans-unit id="_msg184"> + <trans-unit id="_msg194"> <source xml:space="preserve">Amount</source> <context-group purpose="location"><context context-type="linenumber">420</context></context-group> </trans-unit> - <trans-unit id="_msg185"> + <trans-unit id="_msg195"> <source xml:space="preserve">Received with label</source> <context-group purpose="location"><context context-type="linenumber">425</context></context-group> </trans-unit> - <trans-unit id="_msg186"> + <trans-unit id="_msg196"> <source xml:space="preserve">Received with address</source> <context-group purpose="location"><context context-type="linenumber">430</context></context-group> </trans-unit> - <trans-unit id="_msg187"> + <trans-unit id="_msg197"> <source xml:space="preserve">Date</source> <context-group purpose="location"><context context-type="linenumber">435</context></context-group> </trans-unit> - <trans-unit id="_msg188"> + <trans-unit id="_msg198"> <source xml:space="preserve">Confirmations</source> <context-group purpose="location"><context context-type="linenumber">440</context></context-group> </trans-unit> - <trans-unit id="_msg189"> + <trans-unit id="_msg199"> <source xml:space="preserve">Confirmed</source> <context-group purpose="location"><context context-type="linenumber">443</context></context-group> </trans-unit> @@ -836,232 +882,263 @@ Signing is only possible with addresses of the type 'legacy'.</source> </body></file> <file original="../coincontroldialog.cpp" datatype="cpp" source-language="en"><body> <group restype="x-trolltech-linguist-context" resname="CoinControlDialog"> - <trans-unit id="_msg190"> + <trans-unit id="_msg200"> <source xml:space="preserve">Copy amount</source> <context-group purpose="location"><context context-type="linenumber">69</context></context-group> </trans-unit> - <trans-unit id="_msg191"> + <trans-unit id="_msg201"> <source xml:space="preserve">&Copy address</source> <context-group purpose="location"><context context-type="linenumber">58</context></context-group> </trans-unit> - <trans-unit id="_msg192"> + <trans-unit id="_msg202"> <source xml:space="preserve">Copy &label</source> <context-group purpose="location"><context context-type="linenumber">59</context></context-group> </trans-unit> - <trans-unit id="_msg193"> + <trans-unit id="_msg203"> <source xml:space="preserve">Copy &amount</source> <context-group purpose="location"><context context-type="linenumber">60</context></context-group> </trans-unit> - <trans-unit id="_msg194"> + <trans-unit id="_msg204"> <source xml:space="preserve">Copy transaction &ID and output index</source> <context-group purpose="location"><context context-type="linenumber">61</context></context-group> </trans-unit> - <trans-unit id="_msg195"> + <trans-unit id="_msg205"> <source xml:space="preserve">L&ock unspent</source> <context-group purpose="location"><context context-type="linenumber">63</context></context-group> </trans-unit> - <trans-unit id="_msg196"> + <trans-unit id="_msg206"> <source xml:space="preserve">&Unlock unspent</source> <context-group purpose="location"><context context-type="linenumber">64</context></context-group> </trans-unit> - <trans-unit id="_msg197"> + <trans-unit id="_msg207"> <source xml:space="preserve">Copy quantity</source> <context-group purpose="location"><context context-type="linenumber">68</context></context-group> </trans-unit> - <trans-unit id="_msg198"> + <trans-unit id="_msg208"> <source xml:space="preserve">Copy fee</source> <context-group purpose="location"><context context-type="linenumber">70</context></context-group> </trans-unit> - <trans-unit id="_msg199"> + <trans-unit id="_msg209"> <source xml:space="preserve">Copy after fee</source> <context-group purpose="location"><context context-type="linenumber">71</context></context-group> </trans-unit> - <trans-unit id="_msg200"> + <trans-unit id="_msg210"> <source xml:space="preserve">Copy bytes</source> <context-group purpose="location"><context context-type="linenumber">72</context></context-group> </trans-unit> - <trans-unit id="_msg201"> + <trans-unit id="_msg211"> <source xml:space="preserve">Copy dust</source> <context-group purpose="location"><context context-type="linenumber">73</context></context-group> </trans-unit> - <trans-unit id="_msg202"> + <trans-unit id="_msg212"> <source xml:space="preserve">Copy change</source> <context-group purpose="location"><context context-type="linenumber">74</context></context-group> </trans-unit> - <trans-unit id="_msg203"> + <trans-unit id="_msg213"> <source xml:space="preserve">(%1 locked)</source> <context-group purpose="location"><context context-type="linenumber">380</context></context-group> </trans-unit> - <trans-unit id="_msg204"> + <trans-unit id="_msg214"> <source xml:space="preserve">yes</source> - <context-group purpose="location"><context context-type="linenumber">535</context></context-group> + <context-group purpose="location"><context context-type="linenumber">534</context></context-group> </trans-unit> - <trans-unit id="_msg205"> + <trans-unit id="_msg215"> <source xml:space="preserve">no</source> - <context-group purpose="location"><context context-type="linenumber">535</context></context-group> + <context-group purpose="location"><context context-type="linenumber">534</context></context-group> </trans-unit> - <trans-unit id="_msg206"> + <trans-unit id="_msg216"> <source xml:space="preserve">This label turns red if any recipient receives an amount smaller than the current dust threshold.</source> - <context-group purpose="location"><context context-type="linenumber">549</context></context-group> + <context-group purpose="location"><context context-type="linenumber">548</context></context-group> </trans-unit> - <trans-unit id="_msg207"> + <trans-unit id="_msg217"> <source xml:space="preserve">Can vary +/- %1 satoshi(s) per input.</source> - <context-group purpose="location"><context context-type="linenumber">554</context></context-group> + <context-group purpose="location"><context context-type="linenumber">553</context></context-group> </trans-unit> - <trans-unit id="_msg208"> + <trans-unit id="_msg218"> <source xml:space="preserve">(no label)</source> - <context-group purpose="location"><context context-type="linenumber">601</context></context-group> - <context-group purpose="location"><context context-type="linenumber">655</context></context-group> + <context-group purpose="location"><context context-type="linenumber">600</context></context-group> + <context-group purpose="location"><context context-type="linenumber">654</context></context-group> </trans-unit> - <trans-unit id="_msg209"> + <trans-unit id="_msg219"> <source xml:space="preserve">change from %1 (%2)</source> - <context-group purpose="location"><context context-type="linenumber">648</context></context-group> + <context-group purpose="location"><context context-type="linenumber">647</context></context-group> </trans-unit> - <trans-unit id="_msg210"> + <trans-unit id="_msg220"> <source xml:space="preserve">(change)</source> - <context-group purpose="location"><context context-type="linenumber">649</context></context-group> + <context-group purpose="location"><context context-type="linenumber">648</context></context-group> </trans-unit> </group> </body></file> <file original="../walletcontroller.cpp" datatype="cpp" source-language="en"><body> <group restype="x-trolltech-linguist-context" resname="CreateWalletActivity"> - <trans-unit id="_msg211"> + <trans-unit id="_msg221"> <source xml:space="preserve">Create Wallet</source> - <context-group purpose="location"><context context-type="linenumber">243</context></context-group> + <context-group purpose="location"><context context-type="linenumber">244</context></context-group> <note annotates="source" from="developer">Title of window indicating the progress of creation of a new wallet.</note> </trans-unit> - <trans-unit id="_msg212"> + <trans-unit id="_msg222"> <source xml:space="preserve">Creating Wallet <b>%1</b>…</source> - <context-group purpose="location"><context context-type="linenumber">246</context></context-group> + <context-group purpose="location"><context context-type="linenumber">247</context></context-group> <note annotates="source" from="developer">Descriptive text of the create wallet progress window which indicates to the user which wallet is currently being created.</note> </trans-unit> - <trans-unit id="_msg213"> + <trans-unit id="_msg223"> <source xml:space="preserve">Create wallet failed</source> - <context-group purpose="location"><context context-type="linenumber">275</context></context-group> + <context-group purpose="location"><context context-type="linenumber">280</context></context-group> </trans-unit> - <trans-unit id="_msg214"> + <trans-unit id="_msg224"> <source xml:space="preserve">Create wallet warning</source> - <context-group purpose="location"><context context-type="linenumber">277</context></context-group> + <context-group purpose="location"><context context-type="linenumber">282</context></context-group> </trans-unit> - <trans-unit id="_msg215"> + <trans-unit id="_msg225"> <source xml:space="preserve">Can't list signers</source> - <context-group purpose="location"><context context-type="linenumber">293</context></context-group> + <context-group purpose="location"><context context-type="linenumber">298</context></context-group> + </trans-unit> + <trans-unit id="_msg226"> + <source xml:space="preserve">Too many external signers found</source> + <context-group purpose="location"><context context-type="linenumber">301</context></context-group> </trans-unit> </group> <group restype="x-trolltech-linguist-context" resname="LoadWalletsActivity"> - <trans-unit id="_msg216"> + <trans-unit id="_msg227"> <source xml:space="preserve">Load Wallets</source> - <context-group purpose="location"><context context-type="linenumber">362</context></context-group> + <context-group purpose="location"><context context-type="linenumber">375</context></context-group> <note annotates="source" from="developer">Title of progress window which is displayed when wallets are being loaded.</note> </trans-unit> - <trans-unit id="_msg217"> + <trans-unit id="_msg228"> <source xml:space="preserve">Loading wallets…</source> - <context-group purpose="location"><context context-type="linenumber">365</context></context-group> + <context-group purpose="location"><context context-type="linenumber">378</context></context-group> <note annotates="source" from="developer">Descriptive text of the load wallets progress window which indicates to the user that wallets are currently being loaded.</note> </trans-unit> </group> <group restype="x-trolltech-linguist-context" resname="OpenWalletActivity"> - <trans-unit id="_msg218"> + <trans-unit id="_msg229"> <source xml:space="preserve">Open wallet failed</source> - <context-group purpose="location"><context context-type="linenumber">323</context></context-group> + <context-group purpose="location"><context context-type="linenumber">332</context></context-group> </trans-unit> - <trans-unit id="_msg219"> + <trans-unit id="_msg230"> <source xml:space="preserve">Open wallet warning</source> - <context-group purpose="location"><context context-type="linenumber">325</context></context-group> + <context-group purpose="location"><context context-type="linenumber">334</context></context-group> </trans-unit> - <trans-unit id="_msg220"> + <trans-unit id="_msg231"> <source xml:space="preserve">default wallet</source> - <context-group purpose="location"><context context-type="linenumber">335</context></context-group> + <context-group purpose="location"><context context-type="linenumber">344</context></context-group> </trans-unit> - <trans-unit id="_msg221"> + <trans-unit id="_msg232"> <source xml:space="preserve">Open Wallet</source> - <context-group purpose="location"><context context-type="linenumber">339</context></context-group> + <context-group purpose="location"><context context-type="linenumber">348</context></context-group> <note annotates="source" from="developer">Title of window indicating the progress of opening of a wallet.</note> </trans-unit> - <trans-unit id="_msg222"> + <trans-unit id="_msg233"> <source xml:space="preserve">Opening Wallet <b>%1</b>…</source> - <context-group purpose="location"><context context-type="linenumber">342</context></context-group> + <context-group purpose="location"><context context-type="linenumber">351</context></context-group> <note annotates="source" from="developer">Descriptive text of the open wallet progress window which indicates to the user which wallet is currently being opened.</note> </trans-unit> </group> + <group restype="x-trolltech-linguist-context" resname="RestoreWalletActivity"> + <trans-unit id="_msg234"> + <source xml:space="preserve">Restore Wallet</source> + <context-group purpose="location"><context context-type="linenumber">400</context></context-group> + <note annotates="source" from="developer">Title of progress window which is displayed when wallets are being restored.</note> + </trans-unit> + <trans-unit id="_msg235"> + <source xml:space="preserve">Restoring Wallet <b>%1</b>…</source> + <context-group purpose="location"><context context-type="linenumber">403</context></context-group> + <note annotates="source" from="developer">Descriptive text of the restore wallets progress window which indicates to the user that wallets are currently being restored.</note> + </trans-unit> + <trans-unit id="_msg236"> + <source xml:space="preserve">Restore wallet failed</source> + <context-group purpose="location"><context context-type="linenumber">422</context></context-group> + <note annotates="source" from="developer">Title of message box which is displayed when the wallet could not be restored.</note> + </trans-unit> + <trans-unit id="_msg237"> + <source xml:space="preserve">Restore wallet warning</source> + <context-group purpose="location"><context context-type="linenumber">425</context></context-group> + <note annotates="source" from="developer">Title of message box which is displayed when the wallet is restored with some warning.</note> + </trans-unit> + <trans-unit id="_msg238"> + <source xml:space="preserve">Restore wallet message</source> + <context-group purpose="location"><context context-type="linenumber">428</context></context-group> + <note annotates="source" from="developer">Title of message box which is displayed when the wallet is successfully restored.</note> + </trans-unit> + </group> <group restype="x-trolltech-linguist-context" resname="WalletController"> - <trans-unit id="_msg223"> + <trans-unit id="_msg239"> <source xml:space="preserve">Close wallet</source> - <context-group purpose="location"><context context-type="linenumber">83</context></context-group> + <context-group purpose="location"><context context-type="linenumber">84</context></context-group> </trans-unit> - <trans-unit id="_msg224"> + <trans-unit id="_msg240"> <source xml:space="preserve">Are you sure you wish to close the wallet <i>%1</i>?</source> - <context-group purpose="location"><context context-type="linenumber">84</context></context-group> + <context-group purpose="location"><context context-type="linenumber">85</context></context-group> </trans-unit> - <trans-unit id="_msg225"> + <trans-unit id="_msg241"> <source xml:space="preserve">Closing the wallet for too long can result in having to resync the entire chain if pruning is enabled.</source> - <context-group purpose="location"><context context-type="linenumber">85</context></context-group> + <context-group purpose="location"><context context-type="linenumber">86</context></context-group> </trans-unit> - <trans-unit id="_msg226"> + <trans-unit id="_msg242"> <source xml:space="preserve">Close all wallets</source> - <context-group purpose="location"><context context-type="linenumber">98</context></context-group> + <context-group purpose="location"><context context-type="linenumber">99</context></context-group> </trans-unit> - <trans-unit id="_msg227"> + <trans-unit id="_msg243"> <source xml:space="preserve">Are you sure you wish to close all wallets?</source> - <context-group purpose="location"><context context-type="linenumber">99</context></context-group> + <context-group purpose="location"><context context-type="linenumber">100</context></context-group> </trans-unit> </group> </body></file> <file original="../forms/createwalletdialog.ui" datatype="x-trolltech-designer-ui" source-language="en"><body> <group restype="x-trolltech-linguist-context" resname="CreateWalletDialog"> - <trans-unit id="_msg228"> + <trans-unit id="_msg244"> <source xml:space="preserve">Create Wallet</source> <context-group purpose="location"><context context-type="linenumber">14</context></context-group> </trans-unit> - <trans-unit id="_msg229"> + <trans-unit id="_msg245"> <source xml:space="preserve">Wallet Name</source> <context-group purpose="location"><context context-type="linenumber">25</context></context-group> </trans-unit> - <trans-unit id="_msg230"> + <trans-unit id="_msg246"> <source xml:space="preserve">Wallet</source> <context-group purpose="location"><context context-type="linenumber">38</context></context-group> </trans-unit> - <trans-unit id="_msg231"> + <trans-unit id="_msg247"> <source xml:space="preserve">Encrypt the wallet. The wallet will be encrypted with a passphrase of your choice.</source> <context-group purpose="location"><context context-type="linenumber">47</context></context-group> </trans-unit> - <trans-unit id="_msg232"> + <trans-unit id="_msg248"> <source xml:space="preserve">Encrypt Wallet</source> <context-group purpose="location"><context context-type="linenumber">50</context></context-group> </trans-unit> - <trans-unit id="_msg233"> + <trans-unit id="_msg249"> <source xml:space="preserve">Advanced Options</source> <context-group purpose="location"><context context-type="linenumber">76</context></context-group> </trans-unit> - <trans-unit id="_msg234"> + <trans-unit id="_msg250"> <source xml:space="preserve">Disable private keys for this wallet. Wallets with private keys disabled will have no private keys and cannot have an HD seed or imported private keys. This is ideal for watch-only wallets.</source> <context-group purpose="location"><context context-type="linenumber">85</context></context-group> </trans-unit> - <trans-unit id="_msg235"> + <trans-unit id="_msg251"> <source xml:space="preserve">Disable Private Keys</source> <context-group purpose="location"><context context-type="linenumber">88</context></context-group> </trans-unit> - <trans-unit id="_msg236"> + <trans-unit id="_msg252"> <source xml:space="preserve">Make a blank wallet. Blank wallets do not initially have private keys or scripts. Private keys and addresses can be imported, or an HD seed can be set, at a later time.</source> <context-group purpose="location"><context context-type="linenumber">95</context></context-group> </trans-unit> - <trans-unit id="_msg237"> + <trans-unit id="_msg253"> <source xml:space="preserve">Make Blank Wallet</source> <context-group purpose="location"><context context-type="linenumber">98</context></context-group> </trans-unit> - <trans-unit id="_msg238"> + <trans-unit id="_msg254"> <source xml:space="preserve">Use descriptors for scriptPubKey management</source> <context-group purpose="location"><context context-type="linenumber">105</context></context-group> </trans-unit> - <trans-unit id="_msg239"> + <trans-unit id="_msg255"> <source xml:space="preserve">Descriptor Wallet</source> <context-group purpose="location"><context context-type="linenumber">108</context></context-group> </trans-unit> - <trans-unit id="_msg240"> + <trans-unit id="_msg256"> <source xml:space="preserve">Use an external signing device such as a hardware wallet. Configure the external signer script in wallet preferences first.</source> <context-group purpose="location"><context context-type="linenumber">118</context></context-group> </trans-unit> - <trans-unit id="_msg241"> + <trans-unit id="_msg257"> <source xml:space="preserve">External signer</source> <context-group purpose="location"><context context-type="linenumber">121</context></context-group> </trans-unit> @@ -1069,15 +1146,15 @@ Signing is only possible with addresses of the type 'legacy'.</source> </body></file> <file original="../createwalletdialog.cpp" datatype="cpp" source-language="en"><body> <group restype="x-trolltech-linguist-context" resname="CreateWalletDialog"> - <trans-unit id="_msg242"> + <trans-unit id="_msg258"> <source xml:space="preserve">Create</source> <context-group purpose="location"><context context-type="linenumber">22</context></context-group> </trans-unit> - <trans-unit id="_msg243"> + <trans-unit id="_msg259"> <source xml:space="preserve">Compiled without sqlite support (required for descriptor wallets)</source> <context-group purpose="location"><context context-type="linenumber">90</context></context-group> </trans-unit> - <trans-unit id="_msg244"> + <trans-unit id="_msg260"> <source xml:space="preserve">Compiled without external signing support (required for external signing)</source> <context-group purpose="location"><context context-type="linenumber">104</context></context-group> <note annotates="source" from="developer">"External signing" means using devices such as hardware wallets.</note> @@ -1086,23 +1163,23 @@ Signing is only possible with addresses of the type 'legacy'.</source> </body></file> <file original="../forms/editaddressdialog.ui" datatype="x-trolltech-designer-ui" source-language="en"><body> <group restype="x-trolltech-linguist-context" resname="EditAddressDialog"> - <trans-unit id="_msg245"> + <trans-unit id="_msg261"> <source xml:space="preserve">Edit Address</source> <context-group purpose="location"><context context-type="linenumber">14</context></context-group> </trans-unit> - <trans-unit id="_msg246"> + <trans-unit id="_msg262"> <source xml:space="preserve">&Label</source> <context-group purpose="location"><context context-type="linenumber">25</context></context-group> </trans-unit> - <trans-unit id="_msg247"> + <trans-unit id="_msg263"> <source xml:space="preserve">The label associated with this address list entry</source> <context-group purpose="location"><context context-type="linenumber">35</context></context-group> </trans-unit> - <trans-unit id="_msg248"> + <trans-unit id="_msg264"> <source xml:space="preserve">The address associated with this address list entry. This can only be modified for sending addresses.</source> <context-group purpose="location"><context context-type="linenumber">52</context></context-group> </trans-unit> - <trans-unit id="_msg249"> + <trans-unit id="_msg265"> <source xml:space="preserve">&Address</source> <context-group purpose="location"><context context-type="linenumber">42</context></context-group> </trans-unit> @@ -1110,35 +1187,35 @@ Signing is only possible with addresses of the type 'legacy'.</source> </body></file> <file original="../editaddressdialog.cpp" datatype="cpp" source-language="en"><body> <group restype="x-trolltech-linguist-context" resname="EditAddressDialog"> - <trans-unit id="_msg250"> + <trans-unit id="_msg266"> <source xml:space="preserve">New sending address</source> <context-group purpose="location"><context context-type="linenumber">29</context></context-group> </trans-unit> - <trans-unit id="_msg251"> + <trans-unit id="_msg267"> <source xml:space="preserve">Edit receiving address</source> <context-group purpose="location"><context context-type="linenumber">32</context></context-group> </trans-unit> - <trans-unit id="_msg252"> + <trans-unit id="_msg268"> <source xml:space="preserve">Edit sending address</source> <context-group purpose="location"><context context-type="linenumber">36</context></context-group> </trans-unit> - <trans-unit id="_msg253"> + <trans-unit id="_msg269"> <source xml:space="preserve">The entered address "%1" is not a valid Bitcoin address.</source> <context-group purpose="location"><context context-type="linenumber">113</context></context-group> </trans-unit> - <trans-unit id="_msg254"> + <trans-unit id="_msg270"> <source xml:space="preserve">Address "%1" already exists as a receiving address with label "%2" and so cannot be added as a sending address.</source> <context-group purpose="location"><context context-type="linenumber">146</context></context-group> </trans-unit> - <trans-unit id="_msg255"> + <trans-unit id="_msg271"> <source xml:space="preserve">The entered address "%1" is already in the address book with label "%2".</source> <context-group purpose="location"><context context-type="linenumber">151</context></context-group> </trans-unit> - <trans-unit id="_msg256"> + <trans-unit id="_msg272"> <source xml:space="preserve">Could not unlock wallet.</source> <context-group purpose="location"><context context-type="linenumber">123</context></context-group> </trans-unit> - <trans-unit id="_msg257"> + <trans-unit id="_msg273"> <source xml:space="preserve">New key generation failed.</source> <context-group purpose="location"><context context-type="linenumber">128</context></context-group> </trans-unit> @@ -1146,75 +1223,90 @@ Signing is only possible with addresses of the type 'legacy'.</source> </body></file> <file original="../intro.cpp" datatype="cpp" source-language="en"><body> <group restype="x-trolltech-linguist-context" resname="FreespaceChecker"> - <trans-unit id="_msg258"> + <trans-unit id="_msg274"> <source xml:space="preserve">A new data directory will be created.</source> <context-group purpose="location"><context context-type="linenumber">73</context></context-group> </trans-unit> - <trans-unit id="_msg259"> + <trans-unit id="_msg275"> <source xml:space="preserve">name</source> <context-group purpose="location"><context context-type="linenumber">95</context></context-group> </trans-unit> - <trans-unit id="_msg260"> + <trans-unit id="_msg276"> <source xml:space="preserve">Directory already exists. Add %1 if you intend to create a new directory here.</source> <context-group purpose="location"><context context-type="linenumber">97</context></context-group> </trans-unit> - <trans-unit id="_msg261"> + <trans-unit id="_msg277"> <source xml:space="preserve">Path already exists, and is not a directory.</source> <context-group purpose="location"><context context-type="linenumber">100</context></context-group> </trans-unit> - <trans-unit id="_msg262"> + <trans-unit id="_msg278"> <source xml:space="preserve">Cannot create data directory here.</source> <context-group purpose="location"><context context-type="linenumber">107</context></context-group> </trans-unit> </group> <group restype="x-trolltech-linguist-context" resname="Intro"> - <trans-unit id="_msg263"> + <trans-unit id="_msg279"> <source xml:space="preserve">Bitcoin</source> <context-group purpose="location"><context context-type="linenumber">139</context></context-group> </trans-unit> - <trans-unit id="_msg264"> - <source xml:space="preserve">%1 GB of space available</source> + <group restype="x-gettext-plurals"> <context-group purpose="location"><context context-type="linenumber">301</context></context-group> - </trans-unit> - <trans-unit id="_msg265"> - <source xml:space="preserve">(of %1 GB needed)</source> + <trans-unit id="_msg280[0]"> + <source xml:space="preserve">%n GB of space available</source> + </trans-unit> + <trans-unit id="_msg280[1]"> + <source xml:space="preserve">%n GB of space available</source> + </trans-unit> + </group> + <group restype="x-gettext-plurals"> <context-group purpose="location"><context context-type="linenumber">303</context></context-group> - </trans-unit> - <trans-unit id="_msg266"> - <source xml:space="preserve">(%1 GB needed for full chain)</source> + <trans-unit id="_msg281[0]"> + <source xml:space="preserve">(of %n GB needed)</source> + </trans-unit> + <trans-unit id="_msg281[1]"> + <source xml:space="preserve">(of %n GB needed)</source> + </trans-unit> + </group> + <group restype="x-gettext-plurals"> <context-group purpose="location"><context context-type="linenumber">306</context></context-group> - </trans-unit> - <trans-unit id="_msg267"> + <trans-unit id="_msg282[0]"> + <source xml:space="preserve">(%n GB needed for full chain)</source> + </trans-unit> + <trans-unit id="_msg282[1]"> + <source xml:space="preserve">(%n GB needed for full chain)</source> + </trans-unit> + </group> + <trans-unit id="_msg283"> <source xml:space="preserve">At least %1 GB of data will be stored in this directory, and it will grow over time.</source> <context-group purpose="location"><context context-type="linenumber">378</context></context-group> </trans-unit> - <trans-unit id="_msg268"> + <trans-unit id="_msg284"> <source xml:space="preserve">Approximately %1 GB of data will be stored in this directory.</source> <context-group purpose="location"><context context-type="linenumber">381</context></context-group> </trans-unit> <group restype="x-gettext-plurals"> <context-group purpose="location"><context context-type="linenumber">390</context></context-group> <note annotates="source" from="developer">Explanatory text on the capability of the current prune target.</note> - <trans-unit id="_msg269[0]"> + <trans-unit id="_msg285[0]"> <source xml:space="preserve">(sufficient to restore backups %n day(s) old)</source> </trans-unit> - <trans-unit id="_msg269[1]"> + <trans-unit id="_msg285[1]"> <source xml:space="preserve">(sufficient to restore backups %n day(s) old)</source> </trans-unit> </group> - <trans-unit id="_msg270"> + <trans-unit id="_msg286"> <source xml:space="preserve">%1 will download and store a copy of the Bitcoin block chain.</source> <context-group purpose="location"><context context-type="linenumber">392</context></context-group> </trans-unit> - <trans-unit id="_msg271"> + <trans-unit id="_msg287"> <source xml:space="preserve">The wallet will also be stored in this directory.</source> <context-group purpose="location"><context context-type="linenumber">394</context></context-group> </trans-unit> - <trans-unit id="_msg272"> + <trans-unit id="_msg288"> <source xml:space="preserve">Error: Specified data directory "%1" cannot be created.</source> <context-group purpose="location"><context context-type="linenumber">250</context></context-group> </trans-unit> - <trans-unit id="_msg273"> + <trans-unit id="_msg289"> <source xml:space="preserve">Error</source> <context-group purpose="location"><context context-type="linenumber">280</context></context-group> </trans-unit> @@ -1222,25 +1314,25 @@ Signing is only possible with addresses of the type 'legacy'.</source> </body></file> <file original="../utilitydialog.cpp" datatype="cpp" source-language="en"><body> <group restype="x-trolltech-linguist-context" resname="HelpMessageDialog"> - <trans-unit id="_msg274"> + <trans-unit id="_msg290"> <source xml:space="preserve">version</source> - <context-group purpose="location"><context context-type="linenumber">37</context></context-group> + <context-group purpose="location"><context context-type="linenumber">38</context></context-group> </trans-unit> - <trans-unit id="_msg275"> + <trans-unit id="_msg291"> <source xml:space="preserve">About %1</source> - <context-group purpose="location"><context context-type="linenumber">41</context></context-group> + <context-group purpose="location"><context context-type="linenumber">42</context></context-group> </trans-unit> - <trans-unit id="_msg276"> + <trans-unit id="_msg292"> <source xml:space="preserve">Command-line options</source> <context-group purpose="location"><context context-type="linenumber">60</context></context-group> </trans-unit> </group> <group restype="x-trolltech-linguist-context" resname="ShutdownWindow"> - <trans-unit id="_msg277"> + <trans-unit id="_msg293"> <source xml:space="preserve">%1 is shutting down…</source> <context-group purpose="location"><context context-type="linenumber">145</context></context-group> </trans-unit> - <trans-unit id="_msg278"> + <trans-unit id="_msg294"> <source xml:space="preserve">Do not shut down the computer until this window disappears.</source> <context-group purpose="location"><context context-type="linenumber">146</context></context-group> </trans-unit> @@ -1248,47 +1340,47 @@ Signing is only possible with addresses of the type 'legacy'.</source> </body></file> <file original="../forms/intro.ui" datatype="x-trolltech-designer-ui" source-language="en"><body> <group restype="x-trolltech-linguist-context" resname="Intro"> - <trans-unit id="_msg279"> + <trans-unit id="_msg295"> <source xml:space="preserve">Welcome</source> <context-group purpose="location"><context context-type="linenumber">14</context></context-group> </trans-unit> - <trans-unit id="_msg280"> + <trans-unit id="_msg296"> <source xml:space="preserve">Welcome to %1.</source> <context-group purpose="location"><context context-type="linenumber">23</context></context-group> </trans-unit> - <trans-unit id="_msg281"> + <trans-unit id="_msg297"> <source xml:space="preserve">As this is the first time the program is launched, you can choose where %1 will store its data.</source> <context-group purpose="location"><context context-type="linenumber">49</context></context-group> </trans-unit> - <trans-unit id="_msg282"> - <source xml:space="preserve">When you click OK, %1 will begin to download and process the full %4 block chain (%2GB) starting with the earliest transactions in %3 when %4 initially launched.</source> - <context-group purpose="location"><context context-type="linenumber">206</context></context-group> - </trans-unit> - <trans-unit id="_msg283"> + <trans-unit id="_msg298"> <source xml:space="preserve">Limit block chain storage to</source> <context-group purpose="location"><context context-type="linenumber">238</context></context-group> </trans-unit> - <trans-unit id="_msg284"> + <trans-unit id="_msg299"> <source xml:space="preserve">Reverting this setting requires re-downloading the entire blockchain. It is faster to download the full chain first and prune it later. Disables some advanced features.</source> <context-group purpose="location"><context context-type="linenumber">241</context></context-group> </trans-unit> - <trans-unit id="_msg285"> + <trans-unit id="_msg300"> <source xml:space="preserve"> GB</source> <context-group purpose="location"><context context-type="linenumber">248</context></context-group> </trans-unit> - <trans-unit id="_msg286"> + <trans-unit id="_msg301"> <source xml:space="preserve">This initial synchronisation is very demanding, and may expose hardware problems with your computer that had previously gone unnoticed. Each time you run %1, it will continue downloading where it left off.</source> <context-group purpose="location"><context context-type="linenumber">216</context></context-group> </trans-unit> - <trans-unit id="_msg287"> + <trans-unit id="_msg302"> + <source xml:space="preserve">When you click OK, %1 will begin to download and process the full %4 block chain (%2 GB) starting with the earliest transactions in %3 when %4 initially launched.</source> + <context-group purpose="location"><context context-type="linenumber">206</context></context-group> + </trans-unit> + <trans-unit id="_msg303"> <source xml:space="preserve">If you have chosen to limit block chain storage (pruning), the historical data must still be downloaded and processed, but will be deleted afterward to keep your disk usage low.</source> <context-group purpose="location"><context context-type="linenumber">226</context></context-group> </trans-unit> - <trans-unit id="_msg288"> + <trans-unit id="_msg304"> <source xml:space="preserve">Use the default data directory</source> <context-group purpose="location"><context context-type="linenumber">66</context></context-group> </trans-unit> - <trans-unit id="_msg289"> + <trans-unit id="_msg305"> <source xml:space="preserve">Use a custom data directory:</source> <context-group purpose="location"><context context-type="linenumber">73</context></context-group> </trans-unit> @@ -1296,54 +1388,54 @@ Signing is only possible with addresses of the type 'legacy'.</source> </body></file> <file original="../forms/modaloverlay.ui" datatype="x-trolltech-designer-ui" source-language="en"><body> <group restype="x-trolltech-linguist-context" resname="ModalOverlay"> - <trans-unit id="_msg290"> + <trans-unit id="_msg306"> <source xml:space="preserve">Form</source> <context-group purpose="location"><context context-type="linenumber">14</context></context-group> </trans-unit> - <trans-unit id="_msg291"> + <trans-unit id="_msg307"> <source xml:space="preserve">Recent transactions may not yet be visible, and therefore your wallet's balance might be incorrect. This information will be correct once your wallet has finished synchronizing with the bitcoin network, as detailed below.</source> <context-group purpose="location"><context context-type="linenumber">133</context></context-group> </trans-unit> - <trans-unit id="_msg292"> + <trans-unit id="_msg308"> <source xml:space="preserve">Attempting to spend bitcoins that are affected by not-yet-displayed transactions will not be accepted by the network.</source> <context-group purpose="location"><context context-type="linenumber">152</context></context-group> </trans-unit> - <trans-unit id="_msg293"> + <trans-unit id="_msg309"> <source xml:space="preserve">Number of blocks left</source> <context-group purpose="location"><context context-type="linenumber">215</context></context-group> </trans-unit> - <trans-unit id="_msg294"> + <trans-unit id="_msg310"> <source xml:space="preserve">Unknown…</source> <context-group purpose="location"><context context-type="linenumber">222</context></context-group> <context-group purpose="location"><context context-type="linenumber">248</context></context-group> - <context-group purpose="location"><context context-type="sourcefile">../modaloverlay.cpp</context><context context-type="linenumber">152</context></context-group> + <context-group purpose="location"><context context-type="sourcefile">../modaloverlay.cpp</context><context context-type="linenumber">155</context></context-group> </trans-unit> - <trans-unit id="_msg295"> + <trans-unit id="_msg311"> <source xml:space="preserve">calculating…</source> <context-group purpose="location"><context context-type="linenumber">292</context></context-group> <context-group purpose="location"><context context-type="linenumber">312</context></context-group> </trans-unit> - <trans-unit id="_msg296"> + <trans-unit id="_msg312"> <source xml:space="preserve">Last block time</source> <context-group purpose="location"><context context-type="linenumber">235</context></context-group> </trans-unit> - <trans-unit id="_msg297"> + <trans-unit id="_msg313"> <source xml:space="preserve">Progress</source> <context-group purpose="location"><context context-type="linenumber">261</context></context-group> </trans-unit> - <trans-unit id="_msg298"> + <trans-unit id="_msg314"> <source xml:space="preserve">Progress increase per hour</source> <context-group purpose="location"><context context-type="linenumber">285</context></context-group> </trans-unit> - <trans-unit id="_msg299"> + <trans-unit id="_msg315"> <source xml:space="preserve">Estimated time left until synced</source> <context-group purpose="location"><context context-type="linenumber">305</context></context-group> </trans-unit> - <trans-unit id="_msg300"> + <trans-unit id="_msg316"> <source xml:space="preserve">Hide</source> <context-group purpose="location"><context context-type="linenumber">342</context></context-group> </trans-unit> - <trans-unit id="_msg301"> + <trans-unit id="_msg317"> <source xml:space="preserve">Esc</source> <context-group purpose="location"><context context-type="linenumber">345</context></context-group> </trans-unit> @@ -1351,33 +1443,37 @@ Signing is only possible with addresses of the type 'legacy'.</source> </body></file> <file original="../modaloverlay.cpp" datatype="cpp" source-language="en"><body> <group restype="x-trolltech-linguist-context" resname="ModalOverlay"> - <trans-unit id="_msg302"> + <trans-unit id="_msg318"> <source xml:space="preserve">%1 is currently syncing. It will download headers and blocks from peers and validate them until reaching the tip of the block chain.</source> <context-group purpose="location"><context context-type="linenumber">34</context></context-group> </trans-unit> - <trans-unit id="_msg303"> + <trans-unit id="_msg319"> <source xml:space="preserve">Unknown. Syncing Headers (%1, %2%)…</source> - <context-group purpose="location"><context context-type="linenumber">158</context></context-group> + <context-group purpose="location"><context context-type="linenumber">161</context></context-group> + </trans-unit> + <trans-unit id="_msg320"> + <source xml:space="preserve">Unknown. Pre-syncing Headers (%1, %2%)…</source> + <context-group purpose="location"><context context-type="linenumber">166</context></context-group> </trans-unit> </group> <group restype="x-trolltech-linguist-context" resname="QObject"> - <trans-unit id="_msg304"> + <trans-unit id="_msg321"> <source xml:space="preserve">unknown</source> - <context-group purpose="location"><context context-type="linenumber">123</context></context-group> + <context-group purpose="location"><context context-type="linenumber">126</context></context-group> </trans-unit> </group> </body></file> <file original="../forms/openuridialog.ui" datatype="x-trolltech-designer-ui" source-language="en"><body> <group restype="x-trolltech-linguist-context" resname="OpenURIDialog"> - <trans-unit id="_msg305"> + <trans-unit id="_msg322"> <source xml:space="preserve">Open bitcoin URI</source> <context-group purpose="location"><context context-type="linenumber">14</context></context-group> </trans-unit> - <trans-unit id="_msg306"> + <trans-unit id="_msg323"> <source xml:space="preserve">URI:</source> <context-group purpose="location"><context context-type="linenumber">22</context></context-group> </trans-unit> - <trans-unit id="_msg307"> + <trans-unit id="_msg324"> <source xml:space="preserve">Paste address from clipboard</source> <context-group purpose="location"><context context-type="linenumber">36</context></context-group> <note annotates="source" from="developer">Tooltip text for button that allows you to paste an address that is in your clipboard.</note> @@ -1386,310 +1482,310 @@ Signing is only possible with addresses of the type 'legacy'.</source> </body></file> <file original="../forms/optionsdialog.ui" datatype="x-trolltech-designer-ui" source-language="en"><body> <group restype="x-trolltech-linguist-context" resname="OptionsDialog"> - <trans-unit id="_msg308"> + <trans-unit id="_msg325"> <source xml:space="preserve">Options</source> <context-group purpose="location"><context context-type="linenumber">14</context></context-group> </trans-unit> - <trans-unit id="_msg309"> + <trans-unit id="_msg326"> <source xml:space="preserve">&Main</source> <context-group purpose="location"><context context-type="linenumber">27</context></context-group> </trans-unit> - <trans-unit id="_msg310"> + <trans-unit id="_msg327"> <source xml:space="preserve">Automatically start %1 after logging in to the system.</source> <context-group purpose="location"><context context-type="linenumber">33</context></context-group> </trans-unit> - <trans-unit id="_msg311"> + <trans-unit id="_msg328"> <source xml:space="preserve">&Start %1 on system login</source> <context-group purpose="location"><context context-type="linenumber">36</context></context-group> </trans-unit> - <trans-unit id="_msg312"> + <trans-unit id="_msg329"> <source xml:space="preserve">Enabling pruning significantly reduces the disk space required to store transactions. All blocks are still fully validated. Reverting this setting requires re-downloading the entire blockchain.</source> <context-group purpose="location"><context context-type="linenumber">58</context></context-group> </trans-unit> - <trans-unit id="_msg313"> + <trans-unit id="_msg330"> <source xml:space="preserve">Size of &database cache</source> <context-group purpose="location"><context context-type="linenumber">111</context></context-group> </trans-unit> - <trans-unit id="_msg314"> + <trans-unit id="_msg331"> <source xml:space="preserve">Number of script &verification threads</source> <context-group purpose="location"><context context-type="linenumber">157</context></context-group> </trans-unit> - <trans-unit id="_msg315"> + <trans-unit id="_msg332"> <source xml:space="preserve">IP address of the proxy (e.g. IPv4: 127.0.0.1 / IPv6: ::1)</source> <context-group purpose="location"><context context-type="linenumber">388</context></context-group> <context-group purpose="location"><context context-type="linenumber">575</context></context-group> </trans-unit> - <trans-unit id="_msg316"> + <trans-unit id="_msg333"> <source xml:space="preserve">Shows if the supplied default SOCKS5 proxy is used to reach peers via this network type.</source> <context-group purpose="location"><context context-type="linenumber">457</context></context-group> <context-group purpose="location"><context context-type="linenumber">480</context></context-group> <context-group purpose="location"><context context-type="linenumber">503</context></context-group> </trans-unit> - <trans-unit id="_msg317"> + <trans-unit id="_msg334"> <source xml:space="preserve">Minimize instead of exit the application when the window is closed. When this option is enabled, the application will be closed only after selecting Exit in the menu.</source> <context-group purpose="location"><context context-type="linenumber">672</context></context-group> </trans-unit> - <trans-unit id="_msg318"> + <trans-unit id="_msg335"> + <source xml:space="preserve">Options set in this dialog are overridden by the command line:</source> + <context-group purpose="location"><context context-type="linenumber">899</context></context-group> + </trans-unit> + <trans-unit id="_msg336"> <source xml:space="preserve">Open the %1 configuration file from the working directory.</source> <context-group purpose="location"><context context-type="linenumber">944</context></context-group> </trans-unit> - <trans-unit id="_msg319"> + <trans-unit id="_msg337"> <source xml:space="preserve">Open Configuration File</source> <context-group purpose="location"><context context-type="linenumber">947</context></context-group> </trans-unit> - <trans-unit id="_msg320"> + <trans-unit id="_msg338"> <source xml:space="preserve">Reset all client options to default.</source> <context-group purpose="location"><context context-type="linenumber">957</context></context-group> </trans-unit> - <trans-unit id="_msg321"> + <trans-unit id="_msg339"> <source xml:space="preserve">&Reset Options</source> <context-group purpose="location"><context context-type="linenumber">960</context></context-group> </trans-unit> - <trans-unit id="_msg322"> + <trans-unit id="_msg340"> <source xml:space="preserve">&Network</source> <context-group purpose="location"><context context-type="linenumber">315</context></context-group> </trans-unit> - <trans-unit id="_msg323"> + <trans-unit id="_msg341"> <source xml:space="preserve">Prune &block storage to</source> <context-group purpose="location"><context context-type="linenumber">61</context></context-group> </trans-unit> - <trans-unit id="_msg324"> + <trans-unit id="_msg342"> <source xml:space="preserve">GB</source> <context-group purpose="location"><context context-type="linenumber">71</context></context-group> </trans-unit> - <trans-unit id="_msg325"> + <trans-unit id="_msg343"> <source xml:space="preserve">Reverting this setting requires re-downloading the entire blockchain.</source> <context-group purpose="location"><context context-type="linenumber">96</context></context-group> </trans-unit> - <trans-unit id="_msg326"> + <trans-unit id="_msg344"> <source xml:space="preserve">Maximum database cache size. A larger cache can contribute to faster sync, after which the benefit is less pronounced for most use cases. Lowering the cache size will reduce memory usage. Unused mempool memory is shared for this cache.</source> <context-group purpose="location"><context context-type="linenumber">108</context></context-group> <note annotates="source" from="developer">Tooltip text for Options window setting that sets the size of the database cache. Explains the corresponding effects of increasing/decreasing this value.</note> </trans-unit> - <trans-unit id="_msg327"> + <trans-unit id="_msg345"> <source xml:space="preserve">MiB</source> <context-group purpose="location"><context context-type="linenumber">127</context></context-group> </trans-unit> - <trans-unit id="_msg328"> + <trans-unit id="_msg346"> <source xml:space="preserve">Set the number of script verification threads. Negative values correspond to the number of cores you want to leave free to the system.</source> <context-group purpose="location"><context context-type="linenumber">154</context></context-group> <note annotates="source" from="developer">Tooltip text for Options window setting that sets the number of script verification threads. Explains that negative values mean to leave these many cores free to the system.</note> </trans-unit> - <trans-unit id="_msg329"> + <trans-unit id="_msg347"> <source xml:space="preserve">(0 = auto, <0 = leave that many cores free)</source> <context-group purpose="location"><context context-type="linenumber">170</context></context-group> </trans-unit> - <trans-unit id="_msg330"> + <trans-unit id="_msg348"> <source xml:space="preserve">This allows you or a third party tool to communicate with the node through command-line and JSON-RPC commands.</source> <context-group purpose="location"><context context-type="linenumber">192</context></context-group> <note annotates="source" from="developer">Tooltip text for Options window setting that enables the RPC server.</note> </trans-unit> - <trans-unit id="_msg331"> + <trans-unit id="_msg349"> <source xml:space="preserve">Enable R&PC server</source> <context-group purpose="location"><context context-type="linenumber">195</context></context-group> <note annotates="source" from="developer">An Options window setting to enable the RPC server.</note> </trans-unit> - <trans-unit id="_msg332"> + <trans-unit id="_msg350"> <source xml:space="preserve">W&allet</source> <context-group purpose="location"><context context-type="linenumber">216</context></context-group> </trans-unit> - <trans-unit id="_msg333"> + <trans-unit id="_msg351"> <source xml:space="preserve">Whether to set subtract fee from amount as default or not.</source> <context-group purpose="location"><context context-type="linenumber">222</context></context-group> <note annotates="source" from="developer">Tooltip text for Options window setting that sets subtracting the fee from a sending amount as default.</note> </trans-unit> - <trans-unit id="_msg334"> + <trans-unit id="_msg352"> <source xml:space="preserve">Subtract &fee from amount by default</source> <context-group purpose="location"><context context-type="linenumber">225</context></context-group> <note annotates="source" from="developer">An Options window setting to set subtracting the fee from a sending amount as default.</note> </trans-unit> - <trans-unit id="_msg335"> + <trans-unit id="_msg353"> <source xml:space="preserve">Expert</source> <context-group purpose="location"><context context-type="linenumber">232</context></context-group> </trans-unit> - <trans-unit id="_msg336"> + <trans-unit id="_msg354"> <source xml:space="preserve">Enable coin &control features</source> <context-group purpose="location"><context context-type="linenumber">241</context></context-group> </trans-unit> - <trans-unit id="_msg337"> + <trans-unit id="_msg355"> <source xml:space="preserve">If you disable the spending of unconfirmed change, the change from a transaction cannot be used until that transaction has at least one confirmation. This also affects how your balance is computed.</source> <context-group purpose="location"><context context-type="linenumber">248</context></context-group> </trans-unit> - <trans-unit id="_msg338"> + <trans-unit id="_msg356"> <source xml:space="preserve">&Spend unconfirmed change</source> <context-group purpose="location"><context context-type="linenumber">251</context></context-group> </trans-unit> - <trans-unit id="_msg339"> + <trans-unit id="_msg357"> <source xml:space="preserve">Enable &PSBT controls</source> <context-group purpose="location"><context context-type="linenumber">258</context></context-group> <note annotates="source" from="developer">An options window setting to enable PSBT controls.</note> </trans-unit> - <trans-unit id="_msg340"> + <trans-unit id="_msg358"> <source xml:space="preserve">Whether to show PSBT controls.</source> <context-group purpose="location"><context context-type="linenumber">261</context></context-group> <note annotates="source" from="developer">Tooltip text for options window setting that enables PSBT controls.</note> </trans-unit> - <trans-unit id="_msg341"> + <trans-unit id="_msg359"> <source xml:space="preserve">External Signer (e.g. hardware wallet)</source> <context-group purpose="location"><context context-type="linenumber">271</context></context-group> </trans-unit> - <trans-unit id="_msg342"> + <trans-unit id="_msg360"> <source xml:space="preserve">&External signer script path</source> <context-group purpose="location"><context context-type="linenumber">279</context></context-group> </trans-unit> - <trans-unit id="_msg343"> + <trans-unit id="_msg361"> <source xml:space="preserve">Full path to a Bitcoin Core compatible script (e.g. C:\Downloads\hwi.exe or /Users/you/Downloads/hwi.py). Beware: malware can steal your coins!</source> <context-group purpose="location"><context context-type="linenumber">289</context></context-group> </trans-unit> - <trans-unit id="_msg344"> + <trans-unit id="_msg362"> <source xml:space="preserve">Automatically open the Bitcoin client port on the router. This only works when your router supports UPnP and it is enabled.</source> <context-group purpose="location"><context context-type="linenumber">321</context></context-group> </trans-unit> - <trans-unit id="_msg345"> + <trans-unit id="_msg363"> <source xml:space="preserve">Map port using &UPnP</source> <context-group purpose="location"><context context-type="linenumber">324</context></context-group> </trans-unit> - <trans-unit id="_msg346"> + <trans-unit id="_msg364"> <source xml:space="preserve">Automatically open the Bitcoin client port on the router. This only works when your router supports NAT-PMP and it is enabled. The external port could be random.</source> <context-group purpose="location"><context context-type="linenumber">331</context></context-group> </trans-unit> - <trans-unit id="_msg347"> + <trans-unit id="_msg365"> <source xml:space="preserve">Map port using NA&T-PMP</source> <context-group purpose="location"><context context-type="linenumber">334</context></context-group> </trans-unit> - <trans-unit id="_msg348"> + <trans-unit id="_msg366"> <source xml:space="preserve">Accept connections from outside.</source> <context-group purpose="location"><context context-type="linenumber">341</context></context-group> </trans-unit> - <trans-unit id="_msg349"> + <trans-unit id="_msg367"> <source xml:space="preserve">Allow incomin&g connections</source> <context-group purpose="location"><context context-type="linenumber">344</context></context-group> </trans-unit> - <trans-unit id="_msg350"> + <trans-unit id="_msg368"> <source xml:space="preserve">Connect to the Bitcoin network through a SOCKS5 proxy.</source> <context-group purpose="location"><context context-type="linenumber">351</context></context-group> </trans-unit> - <trans-unit id="_msg351"> + <trans-unit id="_msg369"> <source xml:space="preserve">&Connect through SOCKS5 proxy (default proxy):</source> <context-group purpose="location"><context context-type="linenumber">354</context></context-group> </trans-unit> - <trans-unit id="_msg352"> + <trans-unit id="_msg370"> <source xml:space="preserve">Proxy &IP:</source> <context-group purpose="location"><context context-type="linenumber">363</context></context-group> <context-group purpose="location"><context context-type="linenumber">550</context></context-group> </trans-unit> - <trans-unit id="_msg353"> + <trans-unit id="_msg371"> <source xml:space="preserve">&Port:</source> <context-group purpose="location"><context context-type="linenumber">395</context></context-group> <context-group purpose="location"><context context-type="linenumber">582</context></context-group> </trans-unit> - <trans-unit id="_msg354"> + <trans-unit id="_msg372"> <source xml:space="preserve">Port of the proxy (e.g. 9050)</source> <context-group purpose="location"><context context-type="linenumber">420</context></context-group> <context-group purpose="location"><context context-type="linenumber">607</context></context-group> </trans-unit> - <trans-unit id="_msg355"> + <trans-unit id="_msg373"> <source xml:space="preserve">Used for reaching peers via:</source> <context-group purpose="location"><context context-type="linenumber">444</context></context-group> </trans-unit> - <trans-unit id="_msg356"> + <trans-unit id="_msg374"> <source xml:space="preserve">IPv4</source> <context-group purpose="location"><context context-type="linenumber">467</context></context-group> </trans-unit> - <trans-unit id="_msg357"> + <trans-unit id="_msg375"> <source xml:space="preserve">IPv6</source> <context-group purpose="location"><context context-type="linenumber">490</context></context-group> </trans-unit> - <trans-unit id="_msg358"> + <trans-unit id="_msg376"> <source xml:space="preserve">Tor</source> <context-group purpose="location"><context context-type="linenumber">513</context></context-group> </trans-unit> - <trans-unit id="_msg359"> + <trans-unit id="_msg377"> <source xml:space="preserve">&Window</source> <context-group purpose="location"><context context-type="linenumber">643</context></context-group> </trans-unit> - <trans-unit id="_msg360"> + <trans-unit id="_msg378"> <source xml:space="preserve">Show the icon in the system tray.</source> <context-group purpose="location"><context context-type="linenumber">649</context></context-group> </trans-unit> - <trans-unit id="_msg361"> + <trans-unit id="_msg379"> <source xml:space="preserve">&Show tray icon</source> <context-group purpose="location"><context context-type="linenumber">652</context></context-group> </trans-unit> - <trans-unit id="_msg362"> + <trans-unit id="_msg380"> <source xml:space="preserve">Show only a tray icon after minimizing the window.</source> <context-group purpose="location"><context context-type="linenumber">662</context></context-group> </trans-unit> - <trans-unit id="_msg363"> + <trans-unit id="_msg381"> <source xml:space="preserve">&Minimize to the tray instead of the taskbar</source> <context-group purpose="location"><context context-type="linenumber">665</context></context-group> </trans-unit> - <trans-unit id="_msg364"> + <trans-unit id="_msg382"> <source xml:space="preserve">M&inimize on close</source> <context-group purpose="location"><context context-type="linenumber">675</context></context-group> </trans-unit> - <trans-unit id="_msg365"> + <trans-unit id="_msg383"> <source xml:space="preserve">&Display</source> <context-group purpose="location"><context context-type="linenumber">696</context></context-group> </trans-unit> - <trans-unit id="_msg366"> + <trans-unit id="_msg384"> <source xml:space="preserve">User Interface &language:</source> <context-group purpose="location"><context context-type="linenumber">704</context></context-group> </trans-unit> - <trans-unit id="_msg367"> + <trans-unit id="_msg385"> <source xml:space="preserve">The user interface language can be set here. This setting will take effect after restarting %1.</source> <context-group purpose="location"><context context-type="linenumber">717</context></context-group> </trans-unit> - <trans-unit id="_msg368"> + <trans-unit id="_msg386"> <source xml:space="preserve">&Unit to show amounts in:</source> <context-group purpose="location"><context context-type="linenumber">728</context></context-group> </trans-unit> - <trans-unit id="_msg369"> + <trans-unit id="_msg387"> <source xml:space="preserve">Choose the default subdivision unit to show in the interface and when sending coins.</source> <context-group purpose="location"><context context-type="linenumber">741</context></context-group> </trans-unit> - <trans-unit id="_msg370"> + <trans-unit id="_msg388"> <source xml:space="preserve">Third-party URLs (e.g. a block explorer) that appear in the transactions tab as context menu items. %s in the URL is replaced by transaction hash. Multiple URLs are separated by vertical bar |.</source> <context-group purpose="location"><context context-type="linenumber">752</context></context-group> <context-group purpose="location"><context context-type="linenumber">765</context></context-group> </trans-unit> - <trans-unit id="_msg371"> + <trans-unit id="_msg389"> <source xml:space="preserve">&Third-party transaction URLs</source> <context-group purpose="location"><context context-type="linenumber">755</context></context-group> </trans-unit> - <trans-unit id="_msg372"> + <trans-unit id="_msg390"> <source xml:space="preserve">Whether to show coin control features or not.</source> <context-group purpose="location"><context context-type="linenumber">238</context></context-group> </trans-unit> - <trans-unit id="_msg373"> + <trans-unit id="_msg391"> <source xml:space="preserve">Connect to the Bitcoin network through a separate SOCKS5 proxy for Tor onion services.</source> <context-group purpose="location"><context context-type="linenumber">538</context></context-group> </trans-unit> - <trans-unit id="_msg374"> + <trans-unit id="_msg392"> <source xml:space="preserve">Use separate SOCKS&5 proxy to reach peers via Tor onion services:</source> <context-group purpose="location"><context context-type="linenumber">541</context></context-group> </trans-unit> - <trans-unit id="_msg375"> + <trans-unit id="_msg393"> <source xml:space="preserve">Monospaced font in the Overview tab:</source> <context-group purpose="location"><context context-type="linenumber">777</context></context-group> </trans-unit> - <trans-unit id="_msg376"> + <trans-unit id="_msg394"> <source xml:space="preserve">embedded "%1"</source> <context-group purpose="location"><context context-type="linenumber">785</context></context-group> </trans-unit> - <trans-unit id="_msg377"> + <trans-unit id="_msg395"> <source xml:space="preserve">closest matching "%1"</source> <context-group purpose="location"><context context-type="linenumber">834</context></context-group> </trans-unit> - <trans-unit id="_msg378"> - <source xml:space="preserve">Options set in this dialog are overridden by the command line or in the configuration file:</source> - <context-group purpose="location"><context context-type="linenumber">899</context></context-group> - </trans-unit> - <trans-unit id="_msg379"> + <trans-unit id="_msg396"> <source xml:space="preserve">&OK</source> <context-group purpose="location"><context context-type="linenumber">1040</context></context-group> </trans-unit> - <trans-unit id="_msg380"> + <trans-unit id="_msg397"> <source xml:space="preserve">&Cancel</source> <context-group purpose="location"><context context-type="linenumber">1053</context></context-group> </trans-unit> @@ -1697,140 +1793,156 @@ Signing is only possible with addresses of the type 'legacy'.</source> </body></file> <file original="../optionsdialog.cpp" datatype="cpp" source-language="en"><body> <group restype="x-trolltech-linguist-context" resname="OptionsDialog"> - <trans-unit id="_msg381"> + <trans-unit id="_msg398"> <source xml:space="preserve">Compiled without external signing support (required for external signing)</source> - <context-group purpose="location"><context context-type="linenumber">99</context></context-group> + <context-group purpose="location"><context context-type="linenumber">96</context></context-group> <note annotates="source" from="developer">"External signing" means using devices such as hardware wallets.</note> </trans-unit> - <trans-unit id="_msg382"> + <trans-unit id="_msg399"> <source xml:space="preserve">default</source> - <context-group purpose="location"><context context-type="linenumber">111</context></context-group> + <context-group purpose="location"><context context-type="linenumber">108</context></context-group> </trans-unit> - <trans-unit id="_msg383"> + <trans-unit id="_msg400"> <source xml:space="preserve">none</source> - <context-group purpose="location"><context context-type="linenumber">192</context></context-group> + <context-group purpose="location"><context context-type="linenumber">194</context></context-group> </trans-unit> - <trans-unit id="_msg384"> + <trans-unit id="_msg401"> <source xml:space="preserve">Confirm options reset</source> - <context-group purpose="location"><context context-type="linenumber">289</context></context-group> + <context-group purpose="location"><context context-type="linenumber">301</context></context-group> + <note annotates="source" from="developer">Window title text of pop-up window shown when the user has chosen to reset options.</note> </trans-unit> - <trans-unit id="_msg385"> + <trans-unit id="_msg402"> <source xml:space="preserve">Client restart required to activate changes.</source> - <context-group purpose="location"><context context-type="linenumber">290</context></context-group> - <context-group purpose="location"><context context-type="linenumber">360</context></context-group> + <context-group purpose="location"><context context-type="linenumber">292</context></context-group> + <context-group purpose="location"><context context-type="linenumber">371</context></context-group> + <note annotates="source" from="developer">Text explaining that the settings changed will not come into effect until the client is restarted.</note> </trans-unit> - <trans-unit id="_msg386"> + <trans-unit id="_msg403"> + <source xml:space="preserve">Current settings will be backed up at "%1".</source> + <context-group purpose="location"><context context-type="linenumber">296</context></context-group> + <note annotates="source" from="developer">Text explaining to the user that the client's current settings will be backed up at a specific location. %1 is a stand-in argument for the backup location's path.</note> + </trans-unit> + <trans-unit id="_msg404"> <source xml:space="preserve">Client will be shut down. Do you want to proceed?</source> - <context-group purpose="location"><context context-type="linenumber">290</context></context-group> + <context-group purpose="location"><context context-type="linenumber">299</context></context-group> + <note annotates="source" from="developer">Text asking the user to confirm if they would like to proceed with a client shutdown.</note> </trans-unit> - <trans-unit id="_msg387"> + <trans-unit id="_msg405"> <source xml:space="preserve">Configuration options</source> - <context-group purpose="location"><context context-type="linenumber">308</context></context-group> + <context-group purpose="location"><context context-type="linenumber">319</context></context-group> <note annotates="source" from="developer">Window title text of pop-up box that allows opening up of configuration file.</note> </trans-unit> - <trans-unit id="_msg388"> + <trans-unit id="_msg406"> <source xml:space="preserve">The configuration file is used to specify advanced user options which override GUI settings. Additionally, any command-line options will override this configuration file.</source> - <context-group purpose="location"><context context-type="linenumber">311</context></context-group> + <context-group purpose="location"><context context-type="linenumber">322</context></context-group> <note annotates="source" from="developer">Explanatory text about the priority order of instructions considered by client. The order from high to low being: command-line, configuration file, GUI settings.</note> </trans-unit> - <trans-unit id="_msg389"> + <trans-unit id="_msg407"> <source xml:space="preserve">Continue</source> - <context-group purpose="location"><context context-type="linenumber">314</context></context-group> + <context-group purpose="location"><context context-type="linenumber">325</context></context-group> </trans-unit> - <trans-unit id="_msg390"> + <trans-unit id="_msg408"> <source xml:space="preserve">Cancel</source> - <context-group purpose="location"><context context-type="linenumber">315</context></context-group> + <context-group purpose="location"><context context-type="linenumber">326</context></context-group> </trans-unit> - <trans-unit id="_msg391"> + <trans-unit id="_msg409"> <source xml:space="preserve">Error</source> - <context-group purpose="location"><context context-type="linenumber">324</context></context-group> + <context-group purpose="location"><context context-type="linenumber">335</context></context-group> </trans-unit> - <trans-unit id="_msg392"> + <trans-unit id="_msg410"> <source xml:space="preserve">The configuration file could not be opened.</source> - <context-group purpose="location"><context context-type="linenumber">324</context></context-group> + <context-group purpose="location"><context context-type="linenumber">335</context></context-group> </trans-unit> - <trans-unit id="_msg393"> + <trans-unit id="_msg411"> <source xml:space="preserve">This change would require a client restart.</source> - <context-group purpose="location"><context context-type="linenumber">364</context></context-group> + <context-group purpose="location"><context context-type="linenumber">375</context></context-group> </trans-unit> - <trans-unit id="_msg394"> + <trans-unit id="_msg412"> <source xml:space="preserve">The supplied proxy address is invalid.</source> - <context-group purpose="location"><context context-type="linenumber">392</context></context-group> + <context-group purpose="location"><context context-type="linenumber">403</context></context-group> + </trans-unit> + </group> + </body></file> + <file original="../optionsmodel.cpp" datatype="cpp" source-language="en"><body> + <group restype="x-trolltech-linguist-context" resname="OptionsModel"> + <trans-unit id="_msg413"> + <source xml:space="preserve">Could not read setting "%1", %2.</source> + <context-group purpose="location"><context context-type="linenumber">204</context></context-group> </trans-unit> </group> </body></file> <file original="../forms/overviewpage.ui" datatype="x-trolltech-designer-ui" source-language="en"><body> <group restype="x-trolltech-linguist-context" resname="OverviewPage"> - <trans-unit id="_msg395"> + <trans-unit id="_msg414"> <source xml:space="preserve">Form</source> <context-group purpose="location"><context context-type="linenumber">14</context></context-group> </trans-unit> - <trans-unit id="_msg396"> + <trans-unit id="_msg415"> <source xml:space="preserve">The displayed information may be out of date. Your wallet automatically synchronizes with the Bitcoin network after a connection is established, but this process has not completed yet.</source> <context-group purpose="location"><context context-type="linenumber">76</context></context-group> <context-group purpose="location"><context context-type="linenumber">411</context></context-group> </trans-unit> - <trans-unit id="_msg397"> + <trans-unit id="_msg416"> <source xml:space="preserve">Watch-only:</source> <context-group purpose="location"><context context-type="linenumber">284</context></context-group> </trans-unit> - <trans-unit id="_msg398"> + <trans-unit id="_msg417"> <source xml:space="preserve">Available:</source> <context-group purpose="location"><context context-type="linenumber">294</context></context-group> </trans-unit> - <trans-unit id="_msg399"> + <trans-unit id="_msg418"> <source xml:space="preserve">Your current spendable balance</source> <context-group purpose="location"><context context-type="linenumber">304</context></context-group> </trans-unit> - <trans-unit id="_msg400"> + <trans-unit id="_msg419"> <source xml:space="preserve">Pending:</source> <context-group purpose="location"><context context-type="linenumber">339</context></context-group> </trans-unit> - <trans-unit id="_msg401"> + <trans-unit id="_msg420"> <source xml:space="preserve">Total of transactions that have yet to be confirmed, and do not yet count toward the spendable balance</source> <context-group purpose="location"><context context-type="linenumber">139</context></context-group> </trans-unit> - <trans-unit id="_msg402"> + <trans-unit id="_msg421"> <source xml:space="preserve">Immature:</source> <context-group purpose="location"><context context-type="linenumber">239</context></context-group> </trans-unit> - <trans-unit id="_msg403"> + <trans-unit id="_msg422"> <source xml:space="preserve">Mined balance that has not yet matured</source> <context-group purpose="location"><context context-type="linenumber">210</context></context-group> </trans-unit> - <trans-unit id="_msg404"> + <trans-unit id="_msg423"> <source xml:space="preserve">Balances</source> <context-group purpose="location"><context context-type="linenumber">60</context></context-group> </trans-unit> - <trans-unit id="_msg405"> + <trans-unit id="_msg424"> <source xml:space="preserve">Total:</source> <context-group purpose="location"><context context-type="linenumber">200</context></context-group> </trans-unit> - <trans-unit id="_msg406"> + <trans-unit id="_msg425"> <source xml:space="preserve">Your current total balance</source> <context-group purpose="location"><context context-type="linenumber">249</context></context-group> </trans-unit> - <trans-unit id="_msg407"> + <trans-unit id="_msg426"> <source xml:space="preserve">Your current balance in watch-only addresses</source> <context-group purpose="location"><context context-type="linenumber">323</context></context-group> </trans-unit> - <trans-unit id="_msg408"> + <trans-unit id="_msg427"> <source xml:space="preserve">Spendable:</source> <context-group purpose="location"><context context-type="linenumber">346</context></context-group> </trans-unit> - <trans-unit id="_msg409"> + <trans-unit id="_msg428"> <source xml:space="preserve">Recent transactions</source> <context-group purpose="location"><context context-type="linenumber">395</context></context-group> </trans-unit> - <trans-unit id="_msg410"> + <trans-unit id="_msg429"> <source xml:space="preserve">Unconfirmed transactions to watch-only addresses</source> <context-group purpose="location"><context context-type="linenumber">120</context></context-group> </trans-unit> - <trans-unit id="_msg411"> + <trans-unit id="_msg430"> <source xml:space="preserve">Mined balance in watch-only addresses that has not yet matured</source> <context-group purpose="location"><context context-type="linenumber">158</context></context-group> </trans-unit> - <trans-unit id="_msg412"> + <trans-unit id="_msg431"> <source xml:space="preserve">Current total balance in watch-only addresses</source> <context-group purpose="location"><context context-type="linenumber">268</context></context-group> </trans-unit> @@ -1838,35 +1950,35 @@ Signing is only possible with addresses of the type 'legacy'.</source> </body></file> <file original="../overviewpage.cpp" datatype="cpp" source-language="en"><body> <group restype="x-trolltech-linguist-context" resname="OverviewPage"> - <trans-unit id="_msg413"> + <trans-unit id="_msg432"> <source xml:space="preserve">Privacy mode activated for the Overview tab. To unmask the values, uncheck Settings->Mask values.</source> - <context-group purpose="location"><context context-type="linenumber">187</context></context-group> + <context-group purpose="location"><context context-type="linenumber">185</context></context-group> </trans-unit> </group> </body></file> <file original="../forms/psbtoperationsdialog.ui" datatype="x-trolltech-designer-ui" source-language="en"><body> <group restype="x-trolltech-linguist-context" resname="PSBTOperationsDialog"> - <trans-unit id="_msg414"> + <trans-unit id="_msg433"> <source xml:space="preserve">Dialog</source> <context-group purpose="location"><context context-type="linenumber">14</context></context-group> </trans-unit> - <trans-unit id="_msg415"> + <trans-unit id="_msg434"> <source xml:space="preserve">Sign Tx</source> <context-group purpose="location"><context context-type="linenumber">86</context></context-group> </trans-unit> - <trans-unit id="_msg416"> + <trans-unit id="_msg435"> <source xml:space="preserve">Broadcast Tx</source> <context-group purpose="location"><context context-type="linenumber">102</context></context-group> </trans-unit> - <trans-unit id="_msg417"> + <trans-unit id="_msg436"> <source xml:space="preserve">Copy to Clipboard</source> <context-group purpose="location"><context context-type="linenumber">122</context></context-group> </trans-unit> - <trans-unit id="_msg418"> + <trans-unit id="_msg437"> <source xml:space="preserve">Save…</source> <context-group purpose="location"><context context-type="linenumber">129</context></context-group> </trans-unit> - <trans-unit id="_msg419"> + <trans-unit id="_msg438"> <source xml:space="preserve">Close</source> <context-group purpose="location"><context context-type="linenumber">136</context></context-group> </trans-unit> @@ -1874,108 +1986,108 @@ Signing is only possible with addresses of the type 'legacy'.</source> </body></file> <file original="../psbtoperationsdialog.cpp" datatype="cpp" source-language="en"><body> <group restype="x-trolltech-linguist-context" resname="PSBTOperationsDialog"> - <trans-unit id="_msg420"> + <trans-unit id="_msg439"> <source xml:space="preserve">Failed to load transaction: %1</source> <context-group purpose="location"><context context-type="linenumber">61</context></context-group> </trans-unit> - <trans-unit id="_msg421"> + <trans-unit id="_msg440"> <source xml:space="preserve">Failed to sign transaction: %1</source> <context-group purpose="location"><context context-type="linenumber">86</context></context-group> </trans-unit> - <trans-unit id="_msg422"> + <trans-unit id="_msg441"> <source xml:space="preserve">Cannot sign inputs while wallet is locked.</source> <context-group purpose="location"><context context-type="linenumber">94</context></context-group> </trans-unit> - <trans-unit id="_msg423"> + <trans-unit id="_msg442"> <source xml:space="preserve">Could not sign any more inputs.</source> <context-group purpose="location"><context context-type="linenumber">96</context></context-group> </trans-unit> - <trans-unit id="_msg424"> + <trans-unit id="_msg443"> <source xml:space="preserve">Signed %1 inputs, but more signatures are still required.</source> <context-group purpose="location"><context context-type="linenumber">98</context></context-group> </trans-unit> - <trans-unit id="_msg425"> + <trans-unit id="_msg444"> <source xml:space="preserve">Signed transaction successfully. Transaction is ready to broadcast.</source> <context-group purpose="location"><context context-type="linenumber">101</context></context-group> </trans-unit> - <trans-unit id="_msg426"> + <trans-unit id="_msg445"> <source xml:space="preserve">Unknown error processing transaction.</source> <context-group purpose="location"><context context-type="linenumber">113</context></context-group> </trans-unit> - <trans-unit id="_msg427"> + <trans-unit id="_msg446"> <source xml:space="preserve">Transaction broadcast successfully! Transaction ID: %1</source> <context-group purpose="location"><context context-type="linenumber">123</context></context-group> </trans-unit> - <trans-unit id="_msg428"> + <trans-unit id="_msg447"> <source xml:space="preserve">Transaction broadcast failed: %1</source> <context-group purpose="location"><context context-type="linenumber">126</context></context-group> </trans-unit> - <trans-unit id="_msg429"> + <trans-unit id="_msg448"> <source xml:space="preserve">PSBT copied to clipboard.</source> <context-group purpose="location"><context context-type="linenumber">135</context></context-group> </trans-unit> - <trans-unit id="_msg430"> + <trans-unit id="_msg449"> <source xml:space="preserve">Save Transaction Data</source> <context-group purpose="location"><context context-type="linenumber">158</context></context-group> </trans-unit> - <trans-unit id="_msg431"> + <trans-unit id="_msg450"> <source xml:space="preserve">Partially Signed Transaction (Binary)</source> <context-group purpose="location"><context context-type="linenumber">160</context></context-group> <note annotates="source" from="developer">Expanded name of the binary PSBT file format. See: BIP 174.</note> </trans-unit> - <trans-unit id="_msg432"> + <trans-unit id="_msg451"> <source xml:space="preserve">PSBT saved to disk.</source> <context-group purpose="location"><context context-type="linenumber">167</context></context-group> </trans-unit> - <trans-unit id="_msg433"> + <trans-unit id="_msg452"> <source xml:space="preserve"> * Sends %1 to %2</source> <context-group purpose="location"><context context-type="linenumber">183</context></context-group> </trans-unit> - <trans-unit id="_msg434"> + <trans-unit id="_msg453"> <source xml:space="preserve">Unable to calculate transaction fee or total transaction amount.</source> <context-group purpose="location"><context context-type="linenumber">193</context></context-group> </trans-unit> - <trans-unit id="_msg435"> + <trans-unit id="_msg454"> <source xml:space="preserve">Pays transaction fee: </source> <context-group purpose="location"><context context-type="linenumber">195</context></context-group> </trans-unit> - <trans-unit id="_msg436"> + <trans-unit id="_msg455"> <source xml:space="preserve">Total Amount</source> <context-group purpose="location"><context context-type="linenumber">207</context></context-group> </trans-unit> - <trans-unit id="_msg437"> + <trans-unit id="_msg456"> <source xml:space="preserve">or</source> <context-group purpose="location"><context context-type="linenumber">210</context></context-group> </trans-unit> - <trans-unit id="_msg438"> + <trans-unit id="_msg457"> <source xml:space="preserve">Transaction has %1 unsigned inputs.</source> <context-group purpose="location"><context context-type="linenumber">216</context></context-group> </trans-unit> - <trans-unit id="_msg439"> + <trans-unit id="_msg458"> <source xml:space="preserve">Transaction is missing some information about inputs.</source> <context-group purpose="location"><context context-type="linenumber">262</context></context-group> </trans-unit> - <trans-unit id="_msg440"> + <trans-unit id="_msg459"> <source xml:space="preserve">Transaction still needs signature(s).</source> <context-group purpose="location"><context context-type="linenumber">266</context></context-group> </trans-unit> - <trans-unit id="_msg441"> + <trans-unit id="_msg460"> <source xml:space="preserve">(But no wallet is loaded.)</source> <context-group purpose="location"><context context-type="linenumber">269</context></context-group> </trans-unit> - <trans-unit id="_msg442"> + <trans-unit id="_msg461"> <source xml:space="preserve">(But this wallet cannot sign transactions.)</source> <context-group purpose="location"><context context-type="linenumber">272</context></context-group> </trans-unit> - <trans-unit id="_msg443"> + <trans-unit id="_msg462"> <source xml:space="preserve">(But this wallet does not have the right keys.)</source> <context-group purpose="location"><context context-type="linenumber">275</context></context-group> </trans-unit> - <trans-unit id="_msg444"> + <trans-unit id="_msg463"> <source xml:space="preserve">Transaction is fully signed and ready for broadcast.</source> <context-group purpose="location"><context context-type="linenumber">283</context></context-group> </trans-unit> - <trans-unit id="_msg445"> + <trans-unit id="_msg464"> <source xml:space="preserve">Transaction status is unknown.</source> <context-group purpose="location"><context context-type="linenumber">287</context></context-group> </trans-unit> @@ -1983,295 +2095,308 @@ Signing is only possible with addresses of the type 'legacy'.</source> </body></file> <file original="../paymentserver.cpp" datatype="cpp" source-language="en"><body> <group restype="x-trolltech-linguist-context" resname="PaymentServer"> - <trans-unit id="_msg446"> + <trans-unit id="_msg465"> <source xml:space="preserve">Payment request error</source> - <context-group purpose="location"><context context-type="linenumber">173</context></context-group> + <context-group purpose="location"><context context-type="linenumber">152</context></context-group> </trans-unit> - <trans-unit id="_msg447"> + <trans-unit id="_msg466"> <source xml:space="preserve">Cannot start bitcoin: click-to-pay handler</source> - <context-group purpose="location"><context context-type="linenumber">174</context></context-group> + <context-group purpose="location"><context context-type="linenumber">153</context></context-group> </trans-unit> - <trans-unit id="_msg448"> + <trans-unit id="_msg467"> <source xml:space="preserve">URI handling</source> - <context-group purpose="location"><context context-type="linenumber">224</context></context-group> - <context-group purpose="location"><context context-type="linenumber">240</context></context-group> - <context-group purpose="location"><context context-type="linenumber">246</context></context-group> - <context-group purpose="location"><context context-type="linenumber">253</context></context-group> + <context-group purpose="location"><context context-type="linenumber">201</context></context-group> + <context-group purpose="location"><context context-type="linenumber">217</context></context-group> + <context-group purpose="location"><context context-type="linenumber">223</context></context-group> + <context-group purpose="location"><context context-type="linenumber">230</context></context-group> </trans-unit> - <trans-unit id="_msg449"> + <trans-unit id="_msg468"> <source xml:space="preserve">'bitcoin://' is not a valid URI. Use 'bitcoin:' instead.</source> - <context-group purpose="location"><context context-type="linenumber">224</context></context-group> + <context-group purpose="location"><context context-type="linenumber">201</context></context-group> </trans-unit> - <trans-unit id="_msg450"> + <trans-unit id="_msg469"> <source xml:space="preserve">Cannot process payment request because BIP70 is not supported. Due to widespread security flaws in BIP70 it's strongly recommended that any merchant instructions to switch wallets be ignored. If you are receiving this error you should request the merchant provide a BIP21 compatible URI.</source> + <context-group purpose="location"><context context-type="linenumber">218</context></context-group> <context-group purpose="location"><context context-type="linenumber">241</context></context-group> - <context-group purpose="location"><context context-type="linenumber">264</context></context-group> </trans-unit> - <trans-unit id="_msg451"> + <trans-unit id="_msg470"> <source xml:space="preserve">URI cannot be parsed! This can be caused by an invalid Bitcoin address or malformed URI parameters.</source> - <context-group purpose="location"><context context-type="linenumber">254</context></context-group> + <context-group purpose="location"><context context-type="linenumber">231</context></context-group> </trans-unit> - <trans-unit id="_msg452"> + <trans-unit id="_msg471"> <source xml:space="preserve">Payment request file handling</source> - <context-group purpose="location"><context context-type="linenumber">263</context></context-group> + <context-group purpose="location"><context context-type="linenumber">240</context></context-group> </trans-unit> </group> </body></file> <file original="../peertablemodel.h" datatype="c" source-language="en"><body> <group restype="x-trolltech-linguist-context" resname="PeerTableModel"> - <trans-unit id="_msg453"> + <trans-unit id="_msg472"> <source xml:space="preserve">User Agent</source> - <context-group purpose="location"><context context-type="linenumber">108</context></context-group> + <context-group purpose="location"><context context-type="linenumber">112</context></context-group> <note annotates="source" from="developer">Title of Peers Table column which contains the peer's User Agent string.</note> </trans-unit> - <trans-unit id="_msg454"> + <trans-unit id="_msg473"> <source xml:space="preserve">Ping</source> - <context-group purpose="location"><context context-type="linenumber">99</context></context-group> + <context-group purpose="location"><context context-type="linenumber">103</context></context-group> <note annotates="source" from="developer">Title of Peers Table column which indicates the current latency of the connection with the peer.</note> </trans-unit> - <trans-unit id="_msg455"> + <trans-unit id="_msg474"> <source xml:space="preserve">Peer</source> - <context-group purpose="location"><context context-type="linenumber">84</context></context-group> + <context-group purpose="location"><context context-type="linenumber">85</context></context-group> <note annotates="source" from="developer">Title of Peers Table column which contains a unique number used to identify a connection.</note> </trans-unit> - <trans-unit id="_msg456"> + <trans-unit id="_msg475"> + <source xml:space="preserve">Age</source> + <context-group purpose="location"><context context-type="linenumber">88</context></context-group> + <note annotates="source" from="developer">Title of Peers Table column which indicates the duration (length of time) since the peer connection started.</note> + </trans-unit> + <trans-unit id="_msg476"> <source xml:space="preserve">Direction</source> - <context-group purpose="location"><context context-type="linenumber">90</context></context-group> + <context-group purpose="location"><context context-type="linenumber">94</context></context-group> <note annotates="source" from="developer">Title of Peers Table column which indicates the direction the peer connection was initiated from.</note> </trans-unit> - <trans-unit id="_msg457"> + <trans-unit id="_msg477"> <source xml:space="preserve">Sent</source> - <context-group purpose="location"><context context-type="linenumber">102</context></context-group> + <context-group purpose="location"><context context-type="linenumber">106</context></context-group> <note annotates="source" from="developer">Title of Peers Table column which indicates the total amount of network information we have sent to the peer.</note> </trans-unit> - <trans-unit id="_msg458"> + <trans-unit id="_msg478"> <source xml:space="preserve">Received</source> - <context-group purpose="location"><context context-type="linenumber">105</context></context-group> + <context-group purpose="location"><context context-type="linenumber">109</context></context-group> <note annotates="source" from="developer">Title of Peers Table column which indicates the total amount of network information we have received from the peer.</note> </trans-unit> - <trans-unit id="_msg459"> + <trans-unit id="_msg479"> <source xml:space="preserve">Address</source> - <context-group purpose="location"><context context-type="linenumber">87</context></context-group> + <context-group purpose="location"><context context-type="linenumber">91</context></context-group> <note annotates="source" from="developer">Title of Peers Table column which contains the IP/Onion/I2P address of the connected peer.</note> </trans-unit> - <trans-unit id="_msg460"> + <trans-unit id="_msg480"> <source xml:space="preserve">Type</source> - <context-group purpose="location"><context context-type="linenumber">93</context></context-group> + <context-group purpose="location"><context context-type="linenumber">97</context></context-group> <note annotates="source" from="developer">Title of Peers Table column which describes the type of peer connection. The "type" describes why the connection exists.</note> </trans-unit> - <trans-unit id="_msg461"> + <trans-unit id="_msg481"> <source xml:space="preserve">Network</source> - <context-group purpose="location"><context context-type="linenumber">96</context></context-group> + <context-group purpose="location"><context context-type="linenumber">100</context></context-group> <note annotates="source" from="developer">Title of Peers Table column which states the network the peer connected through.</note> </trans-unit> </group> </body></file> <file original="../peertablemodel.cpp" datatype="cpp" source-language="en"><body> <group restype="x-trolltech-linguist-context" resname="PeerTableModel"> - <trans-unit id="_msg462"> + <trans-unit id="_msg482"> <source xml:space="preserve">Inbound</source> - <context-group purpose="location"><context context-type="linenumber">79</context></context-group> + <context-group purpose="location"><context context-type="linenumber">78</context></context-group> <note annotates="source" from="developer">An Inbound Connection from a Peer.</note> </trans-unit> - <trans-unit id="_msg463"> + <trans-unit id="_msg483"> <source xml:space="preserve">Outbound</source> - <context-group purpose="location"><context context-type="linenumber">81</context></context-group> + <context-group purpose="location"><context context-type="linenumber">80</context></context-group> <note annotates="source" from="developer">An Outbound Connection to a Peer.</note> </trans-unit> </group> </body></file> <file original="../bitcoinunits.cpp" datatype="cpp" source-language="en"><body> <group restype="x-trolltech-linguist-context" resname="QObject"> - <trans-unit id="_msg464"> + <trans-unit id="_msg484"> <source xml:space="preserve">Amount</source> - <context-group purpose="location"><context context-type="linenumber">215</context></context-group> + <context-group purpose="location"><context context-type="linenumber">197</context></context-group> </trans-unit> </group> </body></file> <file original="../guiutil.cpp" datatype="cpp" source-language="en"><body> <group restype="x-trolltech-linguist-context" resname="QObject"> - <trans-unit id="_msg465"> + <trans-unit id="_msg485"> <source xml:space="preserve">Enter a Bitcoin address (e.g. %1)</source> - <context-group purpose="location"><context context-type="linenumber">127</context></context-group> + <context-group purpose="location"><context context-type="linenumber">129</context></context-group> </trans-unit> - <trans-unit id="_msg466"> + <trans-unit id="_msg486"> + <source xml:space="preserve">Ctrl+W</source> + <context-group purpose="location"><context context-type="linenumber">417</context></context-group> + </trans-unit> + <trans-unit id="_msg487"> <source xml:space="preserve">Unroutable</source> <context-group purpose="location"><context context-type="linenumber">673</context></context-group> </trans-unit> - <trans-unit id="_msg467"> + <trans-unit id="_msg488"> <source xml:space="preserve">Internal</source> <context-group purpose="location"><context context-type="linenumber">679</context></context-group> </trans-unit> - <trans-unit id="_msg468"> + <trans-unit id="_msg489"> <source xml:space="preserve">Inbound</source> <context-group purpose="location"><context context-type="linenumber">692</context></context-group> <note annotates="source" from="developer">An inbound connection from a peer. An inbound connection is a connection initiated by a peer.</note> </trans-unit> - <trans-unit id="_msg469"> + <trans-unit id="_msg490"> <source xml:space="preserve">Outbound</source> <context-group purpose="location"><context context-type="linenumber">695</context></context-group> <note annotates="source" from="developer">An outbound connection to a peer. An outbound connection is a connection initiated by us.</note> </trans-unit> - <trans-unit id="_msg470"> + <trans-unit id="_msg491"> <source xml:space="preserve">Full Relay</source> <context-group purpose="location"><context context-type="linenumber">700</context></context-group> <note annotates="source" from="developer">Peer connection type that relays all network information.</note> </trans-unit> - <trans-unit id="_msg471"> + <trans-unit id="_msg492"> <source xml:space="preserve">Block Relay</source> <context-group purpose="location"><context context-type="linenumber">703</context></context-group> <note annotates="source" from="developer">Peer connection type that relays network information about blocks and not transactions or addresses.</note> </trans-unit> - <trans-unit id="_msg472"> + <trans-unit id="_msg493"> <source xml:space="preserve">Manual</source> <context-group purpose="location"><context context-type="linenumber">705</context></context-group> <note annotates="source" from="developer">Peer connection type established manually through one of several methods.</note> </trans-unit> - <trans-unit id="_msg473"> + <trans-unit id="_msg494"> <source xml:space="preserve">Feeler</source> <context-group purpose="location"><context context-type="linenumber">707</context></context-group> <note annotates="source" from="developer">Short-lived peer connection type that tests the aliveness of known addresses.</note> </trans-unit> - <trans-unit id="_msg474"> + <trans-unit id="_msg495"> <source xml:space="preserve">Address Fetch</source> <context-group purpose="location"><context context-type="linenumber">709</context></context-group> <note annotates="source" from="developer">Short-lived peer connection type that solicits known addresses from a peer.</note> </trans-unit> - <trans-unit id="_msg475"> + <trans-unit id="_msg496"> <source xml:space="preserve">%1 d</source> - <context-group purpose="location"><context context-type="linenumber">724</context></context-group> + <context-group purpose="location"><context context-type="linenumber">722</context></context-group> + <context-group purpose="location"><context context-type="linenumber">734</context></context-group> </trans-unit> - <trans-unit id="_msg476"> + <trans-unit id="_msg497"> <source xml:space="preserve">%1 h</source> - <context-group purpose="location"><context context-type="linenumber">726</context></context-group> + <context-group purpose="location"><context context-type="linenumber">723</context></context-group> + <context-group purpose="location"><context context-type="linenumber">735</context></context-group> </trans-unit> - <trans-unit id="_msg477"> + <trans-unit id="_msg498"> <source xml:space="preserve">%1 m</source> - <context-group purpose="location"><context context-type="linenumber">728</context></context-group> + <context-group purpose="location"><context context-type="linenumber">724</context></context-group> + <context-group purpose="location"><context context-type="linenumber">736</context></context-group> </trans-unit> - <trans-unit id="_msg478"> + <trans-unit id="_msg499"> <source xml:space="preserve">%1 s</source> - <context-group purpose="location"><context context-type="linenumber">730</context></context-group> - <context-group purpose="location"><context context-type="linenumber">758</context></context-group> + <context-group purpose="location"><context context-type="linenumber">726</context></context-group> + <context-group purpose="location"><context context-type="linenumber">737</context></context-group> + <context-group purpose="location"><context context-type="linenumber">763</context></context-group> </trans-unit> - <trans-unit id="_msg479"> + <trans-unit id="_msg500"> <source xml:space="preserve">None</source> - <context-group purpose="location"><context context-type="linenumber">746</context></context-group> + <context-group purpose="location"><context context-type="linenumber">751</context></context-group> </trans-unit> - <trans-unit id="_msg480"> + <trans-unit id="_msg501"> <source xml:space="preserve">N/A</source> - <context-group purpose="location"><context context-type="linenumber">752</context></context-group> + <context-group purpose="location"><context context-type="linenumber">757</context></context-group> </trans-unit> - <trans-unit id="_msg481"> + <trans-unit id="_msg502"> <source xml:space="preserve">%1 ms</source> - <context-group purpose="location"><context context-type="linenumber">753</context></context-group> + <context-group purpose="location"><context context-type="linenumber">758</context></context-group> </trans-unit> <group restype="x-gettext-plurals"> - <context-group purpose="location"><context context-type="linenumber">771</context></context-group> - <trans-unit id="_msg482[0]"> + <context-group purpose="location"><context context-type="linenumber">776</context></context-group> + <trans-unit id="_msg503[0]"> <source xml:space="preserve">%n second(s)</source> </trans-unit> - <trans-unit id="_msg482[1]"> + <trans-unit id="_msg503[1]"> <source xml:space="preserve">%n second(s)</source> </trans-unit> </group> <group restype="x-gettext-plurals"> - <context-group purpose="location"><context context-type="linenumber">775</context></context-group> - <trans-unit id="_msg483[0]"> + <context-group purpose="location"><context context-type="linenumber">780</context></context-group> + <trans-unit id="_msg504[0]"> <source xml:space="preserve">%n minute(s)</source> </trans-unit> - <trans-unit id="_msg483[1]"> + <trans-unit id="_msg504[1]"> <source xml:space="preserve">%n minute(s)</source> </trans-unit> </group> <group restype="x-gettext-plurals"> - <context-group purpose="location"><context context-type="linenumber">779</context></context-group> - <trans-unit id="_msg484[0]"> + <context-group purpose="location"><context context-type="linenumber">784</context></context-group> + <trans-unit id="_msg505[0]"> <source xml:space="preserve">%n hour(s)</source> </trans-unit> - <trans-unit id="_msg484[1]"> + <trans-unit id="_msg505[1]"> <source xml:space="preserve">%n hour(s)</source> </trans-unit> </group> <group restype="x-gettext-plurals"> - <context-group purpose="location"><context context-type="linenumber">783</context></context-group> - <trans-unit id="_msg485[0]"> + <context-group purpose="location"><context context-type="linenumber">788</context></context-group> + <trans-unit id="_msg506[0]"> <source xml:space="preserve">%n day(s)</source> </trans-unit> - <trans-unit id="_msg485[1]"> + <trans-unit id="_msg506[1]"> <source xml:space="preserve">%n day(s)</source> </trans-unit> </group> <group restype="x-gettext-plurals"> - <context-group purpose="location"><context context-type="linenumber">787</context></context-group> - <context-group purpose="location"><context context-type="linenumber">793</context></context-group> - <trans-unit id="_msg486[0]"> + <context-group purpose="location"><context context-type="linenumber">792</context></context-group> + <context-group purpose="location"><context context-type="linenumber">798</context></context-group> + <trans-unit id="_msg507[0]"> <source xml:space="preserve">%n week(s)</source> </trans-unit> - <trans-unit id="_msg486[1]"> + <trans-unit id="_msg507[1]"> <source xml:space="preserve">%n week(s)</source> </trans-unit> </group> - <trans-unit id="_msg487"> + <trans-unit id="_msg508"> <source xml:space="preserve">%1 and %2</source> - <context-group purpose="location"><context context-type="linenumber">793</context></context-group> + <context-group purpose="location"><context context-type="linenumber">798</context></context-group> </trans-unit> <group restype="x-gettext-plurals"> - <context-group purpose="location"><context context-type="linenumber">793</context></context-group> - <trans-unit id="_msg488[0]"> + <context-group purpose="location"><context context-type="linenumber">798</context></context-group> + <trans-unit id="_msg509[0]"> <source xml:space="preserve">%n year(s)</source> </trans-unit> - <trans-unit id="_msg488[1]"> + <trans-unit id="_msg509[1]"> <source xml:space="preserve">%n year(s)</source> </trans-unit> </group> - <trans-unit id="_msg489"> + <trans-unit id="_msg510"> <source xml:space="preserve">%1 B</source> - <context-group purpose="location"><context context-type="linenumber">801</context></context-group> + <context-group purpose="location"><context context-type="linenumber">806</context></context-group> </trans-unit> - <trans-unit id="_msg490"> + <trans-unit id="_msg511"> <source xml:space="preserve">%1 kB</source> - <context-group purpose="location"><context context-type="linenumber">803</context></context-group> + <context-group purpose="location"><context context-type="linenumber">808</context></context-group> </trans-unit> - <trans-unit id="_msg491"> + <trans-unit id="_msg512"> <source xml:space="preserve">%1 MB</source> - <context-group purpose="location"><context context-type="linenumber">805</context></context-group> + <context-group purpose="location"><context context-type="linenumber">810</context></context-group> </trans-unit> - <trans-unit id="_msg492"> + <trans-unit id="_msg513"> <source xml:space="preserve">%1 GB</source> - <context-group purpose="location"><context context-type="linenumber">807</context></context-group> + <context-group purpose="location"><context context-type="linenumber">812</context></context-group> </trans-unit> </group> </body></file> <file original="../qrimagewidget.cpp" datatype="cpp" source-language="en"><body> <group restype="x-trolltech-linguist-context" resname="QRImageWidget"> - <trans-unit id="_msg493"> + <trans-unit id="_msg514"> <source xml:space="preserve">&Save Image…</source> <context-group purpose="location"><context context-type="linenumber">30</context></context-group> </trans-unit> - <trans-unit id="_msg494"> + <trans-unit id="_msg515"> <source xml:space="preserve">&Copy Image</source> <context-group purpose="location"><context context-type="linenumber">31</context></context-group> </trans-unit> - <trans-unit id="_msg495"> + <trans-unit id="_msg516"> <source xml:space="preserve">Resulting URI too long, try to reduce the text for label / message.</source> <context-group purpose="location"><context context-type="linenumber">42</context></context-group> </trans-unit> - <trans-unit id="_msg496"> + <trans-unit id="_msg517"> <source xml:space="preserve">Error encoding URI into QR Code.</source> <context-group purpose="location"><context context-type="linenumber">49</context></context-group> </trans-unit> - <trans-unit id="_msg497"> + <trans-unit id="_msg518"> <source xml:space="preserve">QR code support not available.</source> <context-group purpose="location"><context context-type="linenumber">90</context></context-group> </trans-unit> - <trans-unit id="_msg498"> + <trans-unit id="_msg519"> <source xml:space="preserve">Save QR Code</source> <context-group purpose="location"><context context-type="linenumber">120</context></context-group> </trans-unit> - <trans-unit id="_msg499"> + <trans-unit id="_msg520"> <source xml:space="preserve">PNG Image</source> <context-group purpose="location"><context context-type="linenumber">123</context></context-group> <note annotates="source" from="developer">Expanded name of the PNG file format. See: https://en.wikipedia.org/wiki/Portable_Network_Graphics.</note> @@ -2280,7 +2405,7 @@ If you are receiving this error you should request the merchant provide a BIP21 </body></file> <file original="../forms/debugwindow.ui" datatype="x-trolltech-designer-ui" source-language="en"><body> <group restype="x-trolltech-linguist-context" resname="RPCConsole"> - <trans-unit id="_msg500"> + <trans-unit id="_msg521"> <source xml:space="preserve">N/A</source> <context-group purpose="location"><context context-type="linenumber">75</context></context-group> <context-group purpose="location"><context context-type="linenumber">101</context></context-group> @@ -2319,290 +2444,293 @@ If you are receiving this error you should request the merchant provide a BIP21 <context-group purpose="location"><context context-type="linenumber">1610</context></context-group> <context-group purpose="location"><context context-type="linenumber">1636</context></context-group> <context-group purpose="location"><context context-type="linenumber">1662</context></context-group> - <context-group purpose="location"><context context-type="sourcefile">../rpcconsole.h</context><context context-type="linenumber">139</context></context-group> + <context-group purpose="location"><context context-type="sourcefile">../rpcconsole.h</context><context context-type="linenumber">143</context></context-group> </trans-unit> - <trans-unit id="_msg501"> + <trans-unit id="_msg522"> <source xml:space="preserve">Client version</source> <context-group purpose="location"><context context-type="linenumber">65</context></context-group> </trans-unit> - <trans-unit id="_msg502"> + <trans-unit id="_msg523"> <source xml:space="preserve">&Information</source> <context-group purpose="location"><context context-type="linenumber">43</context></context-group> </trans-unit> - <trans-unit id="_msg503"> + <trans-unit id="_msg524"> <source xml:space="preserve">General</source> <context-group purpose="location"><context context-type="linenumber">58</context></context-group> </trans-unit> - <trans-unit id="_msg504"> + <trans-unit id="_msg525"> <source xml:space="preserve">Datadir</source> <context-group purpose="location"><context context-type="linenumber">114</context></context-group> </trans-unit> - <trans-unit id="_msg505"> + <trans-unit id="_msg526"> <source xml:space="preserve">To specify a non-default location of the data directory use the '%1' option.</source> <context-group purpose="location"><context context-type="linenumber">124</context></context-group> </trans-unit> - <trans-unit id="_msg506"> + <trans-unit id="_msg527"> <source xml:space="preserve">Blocksdir</source> <context-group purpose="location"><context context-type="linenumber">143</context></context-group> </trans-unit> - <trans-unit id="_msg507"> + <trans-unit id="_msg528"> <source xml:space="preserve">To specify a non-default location of the blocks directory use the '%1' option.</source> <context-group purpose="location"><context context-type="linenumber">153</context></context-group> </trans-unit> - <trans-unit id="_msg508"> + <trans-unit id="_msg529"> <source xml:space="preserve">Startup time</source> <context-group purpose="location"><context context-type="linenumber">172</context></context-group> </trans-unit> - <trans-unit id="_msg509"> + <trans-unit id="_msg530"> <source xml:space="preserve">Network</source> <context-group purpose="location"><context context-type="linenumber">201</context></context-group> <context-group purpose="location"><context context-type="linenumber">1093</context></context-group> </trans-unit> - <trans-unit id="_msg510"> + <trans-unit id="_msg531"> <source xml:space="preserve">Name</source> <context-group purpose="location"><context context-type="linenumber">208</context></context-group> </trans-unit> - <trans-unit id="_msg511"> + <trans-unit id="_msg532"> <source xml:space="preserve">Number of connections</source> <context-group purpose="location"><context context-type="linenumber">231</context></context-group> </trans-unit> - <trans-unit id="_msg512"> + <trans-unit id="_msg533"> <source xml:space="preserve">Block chain</source> <context-group purpose="location"><context context-type="linenumber">260</context></context-group> </trans-unit> - <trans-unit id="_msg513"> + <trans-unit id="_msg534"> <source xml:space="preserve">Memory Pool</source> <context-group purpose="location"><context context-type="linenumber">319</context></context-group> </trans-unit> - <trans-unit id="_msg514"> + <trans-unit id="_msg535"> <source xml:space="preserve">Current number of transactions</source> <context-group purpose="location"><context context-type="linenumber">326</context></context-group> </trans-unit> - <trans-unit id="_msg515"> + <trans-unit id="_msg536"> <source xml:space="preserve">Memory usage</source> <context-group purpose="location"><context context-type="linenumber">349</context></context-group> </trans-unit> - <trans-unit id="_msg516"> + <trans-unit id="_msg537"> <source xml:space="preserve">Wallet: </source> <context-group purpose="location"><context context-type="linenumber">443</context></context-group> </trans-unit> - <trans-unit id="_msg517"> + <trans-unit id="_msg538"> <source xml:space="preserve">(none)</source> <context-group purpose="location"><context context-type="linenumber">454</context></context-group> </trans-unit> - <trans-unit id="_msg518"> + <trans-unit id="_msg539"> <source xml:space="preserve">&Reset</source> <context-group purpose="location"><context context-type="linenumber">665</context></context-group> </trans-unit> - <trans-unit id="_msg519"> + <trans-unit id="_msg540"> <source xml:space="preserve">Received</source> <context-group purpose="location"><context context-type="linenumber">745</context></context-group> <context-group purpose="location"><context context-type="linenumber">1453</context></context-group> </trans-unit> - <trans-unit id="_msg520"> + <trans-unit id="_msg541"> <source xml:space="preserve">Sent</source> <context-group purpose="location"><context context-type="linenumber">825</context></context-group> <context-group purpose="location"><context context-type="linenumber">1430</context></context-group> </trans-unit> - <trans-unit id="_msg521"> + <trans-unit id="_msg542"> <source xml:space="preserve">&Peers</source> <context-group purpose="location"><context context-type="linenumber">866</context></context-group> </trans-unit> - <trans-unit id="_msg522"> + <trans-unit id="_msg543"> <source xml:space="preserve">Banned peers</source> <context-group purpose="location"><context context-type="linenumber">942</context></context-group> </trans-unit> - <trans-unit id="_msg523"> + <trans-unit id="_msg544"> <source xml:space="preserve">Select a peer to view detailed information.</source> <context-group purpose="location"><context context-type="linenumber">1010</context></context-group> - <context-group purpose="location"><context context-type="sourcefile">../rpcconsole.cpp</context><context context-type="linenumber">1160</context></context-group> + <context-group purpose="location"><context context-type="sourcefile">../rpcconsole.cpp</context><context context-type="linenumber">1155</context></context-group> </trans-unit> - <trans-unit id="_msg524"> + <trans-unit id="_msg545"> <source xml:space="preserve">Version</source> <context-group purpose="location"><context context-type="linenumber">1116</context></context-group> </trans-unit> - <trans-unit id="_msg525"> + <trans-unit id="_msg546"> <source xml:space="preserve">Starting Block</source> <context-group purpose="location"><context context-type="linenumber">1240</context></context-group> </trans-unit> - <trans-unit id="_msg526"> + <trans-unit id="_msg547"> <source xml:space="preserve">Synced Headers</source> <context-group purpose="location"><context context-type="linenumber">1263</context></context-group> </trans-unit> - <trans-unit id="_msg527"> + <trans-unit id="_msg548"> <source xml:space="preserve">Synced Blocks</source> <context-group purpose="location"><context context-type="linenumber">1286</context></context-group> </trans-unit> - <trans-unit id="_msg528"> + <trans-unit id="_msg549"> <source xml:space="preserve">Last Transaction</source> <context-group purpose="location"><context context-type="linenumber">1361</context></context-group> </trans-unit> - <trans-unit id="_msg529"> + <trans-unit id="_msg550"> <source xml:space="preserve">The mapped Autonomous System used for diversifying peer selection.</source> <context-group purpose="location"><context context-type="linenumber">1571</context></context-group> </trans-unit> - <trans-unit id="_msg530"> + <trans-unit id="_msg551"> <source xml:space="preserve">Mapped AS</source> <context-group purpose="location"><context context-type="linenumber">1574</context></context-group> </trans-unit> - <trans-unit id="_msg531"> + <trans-unit id="_msg552"> <source xml:space="preserve">Whether we relay addresses to this peer.</source> <context-group purpose="location"><context context-type="linenumber">1597</context></context-group> - <note annotates="source" from="developer">Tooltip text for the Address Relay field in the peer details area.</note> + <note annotates="source" from="developer">Tooltip text for the Address Relay field in the peer details area, which displays whether we relay addresses to this peer (Yes/No).</note> </trans-unit> - <trans-unit id="_msg532"> + <trans-unit id="_msg553"> <source xml:space="preserve">Address Relay</source> <context-group purpose="location"><context context-type="linenumber">1600</context></context-group> + <note annotates="source" from="developer">Text title for the Address Relay field in the peer details area, which displays whether we relay addresses to this peer (Yes/No).</note> </trans-unit> - <trans-unit id="_msg533"> - <source xml:space="preserve">Total number of addresses processed, excluding those dropped due to rate-limiting.</source> + <trans-unit id="_msg554"> + <source xml:space="preserve">The total number of addresses received from this peer that were processed (excludes addresses that were dropped due to rate-limiting).</source> <context-group purpose="location"><context context-type="linenumber">1623</context></context-group> - <note annotates="source" from="developer">Tooltip text for the Addresses Processed field in the peer details area.</note> + <note annotates="source" from="developer">Tooltip text for the Addresses Processed field in the peer details area, which displays the total number of addresses received from this peer that were processed (excludes addresses that were dropped due to rate-limiting).</note> </trans-unit> - <trans-unit id="_msg534"> + <trans-unit id="_msg555"> + <source xml:space="preserve">The total number of addresses received from this peer that were dropped (not processed) due to rate-limiting.</source> + <context-group purpose="location"><context context-type="linenumber">1649</context></context-group> + <note annotates="source" from="developer">Tooltip text for the Addresses Rate-Limited field in the peer details area, which displays the total number of addresses received from this peer that were dropped (not processed) due to rate-limiting.</note> + </trans-unit> + <trans-unit id="_msg556"> <source xml:space="preserve">Addresses Processed</source> <context-group purpose="location"><context context-type="linenumber">1626</context></context-group> + <note annotates="source" from="developer">Text title for the Addresses Processed field in the peer details area, which displays the total number of addresses received from this peer that were processed (excludes addresses that were dropped due to rate-limiting).</note> </trans-unit> - <trans-unit id="_msg535"> - <source xml:space="preserve">Total number of addresses dropped due to rate-limiting.</source> - <context-group purpose="location"><context context-type="linenumber">1649</context></context-group> - <note annotates="source" from="developer">Tooltip text for the Addresses Rate-Limited field in the peer details area.</note> - </trans-unit> - <trans-unit id="_msg536"> + <trans-unit id="_msg557"> <source xml:space="preserve">Addresses Rate-Limited</source> <context-group purpose="location"><context context-type="linenumber">1652</context></context-group> + <note annotates="source" from="developer">Text title for the Addresses Rate-Limited field in the peer details area, which displays the total number of addresses received from this peer that were dropped (not processed) due to rate-limiting.</note> </trans-unit> - <trans-unit id="_msg537"> + <trans-unit id="_msg558"> <source xml:space="preserve">User Agent</source> <context-group purpose="location"><context context-type="linenumber">88</context></context-group> <context-group purpose="location"><context context-type="linenumber">1139</context></context-group> </trans-unit> - <trans-unit id="_msg538"> + <trans-unit id="_msg559"> <source xml:space="preserve">Node window</source> <context-group purpose="location"><context context-type="linenumber">14</context></context-group> </trans-unit> - <trans-unit id="_msg539"> + <trans-unit id="_msg560"> <source xml:space="preserve">Current block height</source> <context-group purpose="location"><context context-type="linenumber">267</context></context-group> </trans-unit> - <trans-unit id="_msg540"> + <trans-unit id="_msg561"> <source xml:space="preserve">Open the %1 debug log file from the current data directory. This can take a few seconds for large log files.</source> <context-group purpose="location"><context context-type="linenumber">397</context></context-group> </trans-unit> - <trans-unit id="_msg541"> + <trans-unit id="_msg562"> <source xml:space="preserve">Decrease font size</source> <context-group purpose="location"><context context-type="linenumber">475</context></context-group> </trans-unit> - <trans-unit id="_msg542"> + <trans-unit id="_msg563"> <source xml:space="preserve">Increase font size</source> <context-group purpose="location"><context context-type="linenumber">495</context></context-group> </trans-unit> - <trans-unit id="_msg543"> + <trans-unit id="_msg564"> <source xml:space="preserve">Permissions</source> <context-group purpose="location"><context context-type="linenumber">1041</context></context-group> </trans-unit> - <trans-unit id="_msg544"> + <trans-unit id="_msg565"> <source xml:space="preserve">The direction and type of peer connection: %1</source> <context-group purpose="location"><context context-type="linenumber">1064</context></context-group> </trans-unit> - <trans-unit id="_msg545"> + <trans-unit id="_msg566"> <source xml:space="preserve">Direction/Type</source> <context-group purpose="location"><context context-type="linenumber">1067</context></context-group> </trans-unit> - <trans-unit id="_msg546"> + <trans-unit id="_msg567"> <source xml:space="preserve">The network protocol this peer is connected through: IPv4, IPv6, Onion, I2P, or CJDNS.</source> <context-group purpose="location"><context context-type="linenumber">1090</context></context-group> </trans-unit> - <trans-unit id="_msg547"> + <trans-unit id="_msg568"> <source xml:space="preserve">Services</source> <context-group purpose="location"><context context-type="linenumber">1162</context></context-group> </trans-unit> - <trans-unit id="_msg548"> + <trans-unit id="_msg569"> <source xml:space="preserve">Whether the peer requested us to relay transactions.</source> <context-group purpose="location"><context context-type="linenumber">1188</context></context-group> </trans-unit> - <trans-unit id="_msg549"> + <trans-unit id="_msg570"> <source xml:space="preserve">Wants Tx Relay</source> <context-group purpose="location"><context context-type="linenumber">1191</context></context-group> </trans-unit> - <trans-unit id="_msg550"> + <trans-unit id="_msg571"> <source xml:space="preserve">High bandwidth BIP152 compact block relay: %1</source> <context-group purpose="location"><context context-type="linenumber">1214</context></context-group> </trans-unit> - <trans-unit id="_msg551"> + <trans-unit id="_msg572"> <source xml:space="preserve">High Bandwidth</source> <context-group purpose="location"><context context-type="linenumber">1217</context></context-group> </trans-unit> - <trans-unit id="_msg552"> + <trans-unit id="_msg573"> <source xml:space="preserve">Connection Time</source> <context-group purpose="location"><context context-type="linenumber">1309</context></context-group> </trans-unit> - <trans-unit id="_msg553"> + <trans-unit id="_msg574"> <source xml:space="preserve">Elapsed time since a novel block passing initial validity checks was received from this peer.</source> <context-group purpose="location"><context context-type="linenumber">1332</context></context-group> </trans-unit> - <trans-unit id="_msg554"> + <trans-unit id="_msg575"> <source xml:space="preserve">Last Block</source> <context-group purpose="location"><context context-type="linenumber">1335</context></context-group> </trans-unit> - <trans-unit id="_msg555"> + <trans-unit id="_msg576"> <source xml:space="preserve">Elapsed time since a novel transaction accepted into our mempool was received from this peer.</source> <context-group purpose="location"><context context-type="linenumber">1358</context></context-group> <note annotates="source" from="developer">Tooltip text for the Last Transaction field in the peer details area.</note> </trans-unit> - <trans-unit id="_msg556"> + <trans-unit id="_msg577"> <source xml:space="preserve">Last Send</source> <context-group purpose="location"><context context-type="linenumber">1384</context></context-group> </trans-unit> - <trans-unit id="_msg557"> + <trans-unit id="_msg578"> <source xml:space="preserve">Last Receive</source> <context-group purpose="location"><context context-type="linenumber">1407</context></context-group> </trans-unit> - <trans-unit id="_msg558"> + <trans-unit id="_msg579"> <source xml:space="preserve">Ping Time</source> <context-group purpose="location"><context context-type="linenumber">1476</context></context-group> </trans-unit> - <trans-unit id="_msg559"> + <trans-unit id="_msg580"> <source xml:space="preserve">The duration of a currently outstanding ping.</source> <context-group purpose="location"><context context-type="linenumber">1499</context></context-group> </trans-unit> - <trans-unit id="_msg560"> + <trans-unit id="_msg581"> <source xml:space="preserve">Ping Wait</source> <context-group purpose="location"><context context-type="linenumber">1502</context></context-group> </trans-unit> - <trans-unit id="_msg561"> + <trans-unit id="_msg582"> <source xml:space="preserve">Min Ping</source> <context-group purpose="location"><context context-type="linenumber">1525</context></context-group> </trans-unit> - <trans-unit id="_msg562"> + <trans-unit id="_msg583"> <source xml:space="preserve">Time Offset</source> <context-group purpose="location"><context context-type="linenumber">1548</context></context-group> </trans-unit> - <trans-unit id="_msg563"> + <trans-unit id="_msg584"> <source xml:space="preserve">Last block time</source> <context-group purpose="location"><context context-type="linenumber">290</context></context-group> </trans-unit> - <trans-unit id="_msg564"> + <trans-unit id="_msg585"> <source xml:space="preserve">&Open</source> <context-group purpose="location"><context context-type="linenumber">400</context></context-group> </trans-unit> - <trans-unit id="_msg565"> + <trans-unit id="_msg586"> <source xml:space="preserve">&Console</source> <context-group purpose="location"><context context-type="linenumber">426</context></context-group> </trans-unit> - <trans-unit id="_msg566"> + <trans-unit id="_msg587"> <source xml:space="preserve">&Network Traffic</source> <context-group purpose="location"><context context-type="linenumber">613</context></context-group> </trans-unit> - <trans-unit id="_msg567"> + <trans-unit id="_msg588"> <source xml:space="preserve">Totals</source> <context-group purpose="location"><context context-type="linenumber">681</context></context-group> </trans-unit> - <trans-unit id="_msg568"> + <trans-unit id="_msg589"> <source xml:space="preserve">Debug log file</source> <context-group purpose="location"><context context-type="linenumber">390</context></context-group> </trans-unit> - <trans-unit id="_msg569"> + <trans-unit id="_msg590"> <source xml:space="preserve">Clear console</source> <context-group purpose="location"><context context-type="linenumber">515</context></context-group> </trans-unit> @@ -2610,123 +2738,139 @@ If you are receiving this error you should request the merchant provide a BIP21 </body></file> <file original="../rpcconsole.cpp" datatype="cpp" source-language="en"><body> <group restype="x-trolltech-linguist-context" resname="RPCConsole"> - <trans-unit id="_msg570"> + <trans-unit id="_msg591"> <source xml:space="preserve">In:</source> - <context-group purpose="location"><context context-type="linenumber">959</context></context-group> + <context-group purpose="location"><context context-type="linenumber">953</context></context-group> </trans-unit> - <trans-unit id="_msg571"> + <trans-unit id="_msg592"> <source xml:space="preserve">Out:</source> - <context-group purpose="location"><context context-type="linenumber">960</context></context-group> + <context-group purpose="location"><context context-type="linenumber">954</context></context-group> </trans-unit> - <trans-unit id="_msg572"> + <trans-unit id="_msg593"> <source xml:space="preserve">Inbound: initiated by peer</source> - <context-group purpose="location"><context context-type="linenumber">503</context></context-group> + <context-group purpose="location"><context context-type="linenumber">496</context></context-group> <note annotates="source" from="developer">Explanatory text for an inbound peer connection.</note> </trans-unit> - <trans-unit id="_msg573"> + <trans-unit id="_msg594"> <source xml:space="preserve">Outbound Full Relay: default</source> - <context-group purpose="location"><context context-type="linenumber">507</context></context-group> + <context-group purpose="location"><context context-type="linenumber">500</context></context-group> <note annotates="source" from="developer">Explanatory text for an outbound peer connection that relays all network information. This is the default behavior for outbound connections.</note> </trans-unit> - <trans-unit id="_msg574"> + <trans-unit id="_msg595"> <source xml:space="preserve">Outbound Block Relay: does not relay transactions or addresses</source> - <context-group purpose="location"><context context-type="linenumber">510</context></context-group> + <context-group purpose="location"><context context-type="linenumber">503</context></context-group> <note annotates="source" from="developer">Explanatory text for an outbound peer connection that relays network information about blocks and not transactions or addresses.</note> </trans-unit> - <trans-unit id="_msg575"> + <trans-unit id="_msg596"> <source xml:space="preserve">Outbound Manual: added using RPC %1 or %2/%3 configuration options</source> - <context-group purpose="location"><context context-type="linenumber">515</context></context-group> + <context-group purpose="location"><context context-type="linenumber">508</context></context-group> <note annotates="source" from="developer">Explanatory text for an outbound peer connection that was established manually through one of several methods. The numbered arguments are stand-ins for the methods available to establish manual connections.</note> </trans-unit> - <trans-unit id="_msg576"> + <trans-unit id="_msg597"> <source xml:space="preserve">Outbound Feeler: short-lived, for testing addresses</source> - <context-group purpose="location"><context context-type="linenumber">521</context></context-group> + <context-group purpose="location"><context context-type="linenumber">514</context></context-group> <note annotates="source" from="developer">Explanatory text for a short-lived outbound peer connection that is used to test the aliveness of known addresses.</note> </trans-unit> - <trans-unit id="_msg577"> + <trans-unit id="_msg598"> <source xml:space="preserve">Outbound Address Fetch: short-lived, for soliciting addresses</source> - <context-group purpose="location"><context context-type="linenumber">524</context></context-group> + <context-group purpose="location"><context context-type="linenumber">517</context></context-group> <note annotates="source" from="developer">Explanatory text for a short-lived outbound peer connection that is used to request addresses from a peer.</note> </trans-unit> - <trans-unit id="_msg578"> + <trans-unit id="_msg599"> <source xml:space="preserve">we selected the peer for high bandwidth relay</source> - <context-group purpose="location"><context context-type="linenumber">528</context></context-group> + <context-group purpose="location"><context context-type="linenumber">521</context></context-group> </trans-unit> - <trans-unit id="_msg579"> + <trans-unit id="_msg600"> <source xml:space="preserve">the peer selected us for high bandwidth relay</source> - <context-group purpose="location"><context context-type="linenumber">529</context></context-group> + <context-group purpose="location"><context context-type="linenumber">522</context></context-group> </trans-unit> - <trans-unit id="_msg580"> + <trans-unit id="_msg601"> <source xml:space="preserve">no high bandwidth relay selected</source> - <context-group purpose="location"><context context-type="linenumber">530</context></context-group> + <context-group purpose="location"><context context-type="linenumber">523</context></context-group> </trans-unit> - <trans-unit id="_msg581"> + <trans-unit id="_msg602"> <source xml:space="preserve">Ctrl++</source> - <context-group purpose="location"><context context-type="linenumber">543</context></context-group> + <context-group purpose="location"><context context-type="linenumber">536</context></context-group> <note annotates="source" from="developer">Main shortcut to increase the RPC console font size.</note> </trans-unit> - <trans-unit id="_msg582"> + <trans-unit id="_msg603"> <source xml:space="preserve">Ctrl+=</source> - <context-group purpose="location"><context context-type="linenumber">545</context></context-group> + <context-group purpose="location"><context context-type="linenumber">538</context></context-group> <note annotates="source" from="developer">Secondary shortcut to increase the RPC console font size.</note> </trans-unit> - <trans-unit id="_msg583"> + <trans-unit id="_msg604"> <source xml:space="preserve">Ctrl+-</source> - <context-group purpose="location"><context context-type="linenumber">549</context></context-group> + <context-group purpose="location"><context context-type="linenumber">542</context></context-group> <note annotates="source" from="developer">Main shortcut to decrease the RPC console font size.</note> </trans-unit> - <trans-unit id="_msg584"> + <trans-unit id="_msg605"> <source xml:space="preserve">Ctrl+_</source> - <context-group purpose="location"><context context-type="linenumber">551</context></context-group> + <context-group purpose="location"><context context-type="linenumber">544</context></context-group> <note annotates="source" from="developer">Secondary shortcut to decrease the RPC console font size.</note> </trans-unit> - <trans-unit id="_msg585"> + <trans-unit id="_msg606"> <source xml:space="preserve">&Copy address</source> - <context-group purpose="location"><context context-type="linenumber">702</context></context-group> + <context-group purpose="location"><context context-type="linenumber">695</context></context-group> <note annotates="source" from="developer">Context menu action to copy the address of a peer.</note> </trans-unit> - <trans-unit id="_msg586"> + <trans-unit id="_msg607"> <source xml:space="preserve">&Disconnect</source> - <context-group purpose="location"><context context-type="linenumber">706</context></context-group> + <context-group purpose="location"><context context-type="linenumber">699</context></context-group> </trans-unit> - <trans-unit id="_msg587"> + <trans-unit id="_msg608"> <source xml:space="preserve">1 &hour</source> - <context-group purpose="location"><context context-type="linenumber">707</context></context-group> + <context-group purpose="location"><context context-type="linenumber">700</context></context-group> </trans-unit> - <trans-unit id="_msg588"> + <trans-unit id="_msg609"> <source xml:space="preserve">1 d&ay</source> - <context-group purpose="location"><context context-type="linenumber">708</context></context-group> + <context-group purpose="location"><context context-type="linenumber">701</context></context-group> </trans-unit> - <trans-unit id="_msg589"> + <trans-unit id="_msg610"> <source xml:space="preserve">1 &week</source> - <context-group purpose="location"><context context-type="linenumber">709</context></context-group> + <context-group purpose="location"><context context-type="linenumber">702</context></context-group> </trans-unit> - <trans-unit id="_msg590"> + <trans-unit id="_msg611"> <source xml:space="preserve">1 &year</source> - <context-group purpose="location"><context context-type="linenumber">710</context></context-group> + <context-group purpose="location"><context context-type="linenumber">703</context></context-group> </trans-unit> - <trans-unit id="_msg591"> + <trans-unit id="_msg612"> <source xml:space="preserve">&Copy IP/Netmask</source> - <context-group purpose="location"><context context-type="linenumber">735</context></context-group> + <context-group purpose="location"><context context-type="linenumber">729</context></context-group> <note annotates="source" from="developer">Context menu action to copy the IP/Netmask of a banned peer. IP/Netmask is the combination of a peer's IP address and its Netmask. For IP address, see: https://en.wikipedia.org/wiki/IP_address.</note> </trans-unit> - <trans-unit id="_msg592"> + <trans-unit id="_msg613"> <source xml:space="preserve">&Unban</source> - <context-group purpose="location"><context context-type="linenumber">739</context></context-group> + <context-group purpose="location"><context context-type="linenumber">733</context></context-group> </trans-unit> - <trans-unit id="_msg593"> + <trans-unit id="_msg614"> <source xml:space="preserve">Network activity disabled</source> - <context-group purpose="location"><context context-type="linenumber">963</context></context-group> + <context-group purpose="location"><context context-type="linenumber">957</context></context-group> </trans-unit> - <trans-unit id="_msg594"> + <trans-unit id="_msg615"> <source xml:space="preserve">Executing command without any wallet</source> - <context-group purpose="location"><context context-type="linenumber">1040</context></context-group> + <context-group purpose="location"><context context-type="linenumber">1035</context></context-group> </trans-unit> - <trans-unit id="_msg595"> + <trans-unit id="_msg616"> + <source xml:space="preserve">Ctrl+I</source> + <context-group purpose="location"><context context-type="linenumber">1351</context></context-group> + </trans-unit> + <trans-unit id="_msg617"> + <source xml:space="preserve">Ctrl+T</source> + <context-group purpose="location"><context context-type="linenumber">1352</context></context-group> + </trans-unit> + <trans-unit id="_msg618"> + <source xml:space="preserve">Ctrl+N</source> + <context-group purpose="location"><context context-type="linenumber">1353</context></context-group> + </trans-unit> + <trans-unit id="_msg619"> + <source xml:space="preserve">Ctrl+P</source> + <context-group purpose="location"><context context-type="linenumber">1354</context></context-group> + </trans-unit> + <trans-unit id="_msg620"> <source xml:space="preserve">Executing command using "%1" wallet</source> - <context-group purpose="location"><context context-type="linenumber">1038</context></context-group> + <context-group purpose="location"><context context-type="linenumber">1033</context></context-group> </trans-unit> - <trans-unit id="_msg596"> + <trans-unit id="_msg621"> <source xml:space="preserve">Welcome to the %1 RPC console. Use up and down arrows to navigate history, and %2 to clear screen. Use %3 and %4 to increase or decrease the font size. @@ -2734,124 +2878,124 @@ Type %5 for an overview of available commands. For more information on using this console, type %6. %7WARNING: Scammers have been active, telling users to type commands here, stealing their wallet contents. Do not use this console without fully understanding the ramifications of a command.%8</source> - <context-group purpose="location"><context context-type="linenumber">893</context></context-group> + <context-group purpose="location"><context context-type="linenumber">887</context></context-group> <note annotates="source" from="developer">RPC console welcome message. Placeholders %7 and %8 are style tags for the warning content, and they are not space separated from the rest of the text intentionally.</note> </trans-unit> - <trans-unit id="_msg597"> + <trans-unit id="_msg622"> <source xml:space="preserve">Executing…</source> - <context-group purpose="location"><context context-type="linenumber">1048</context></context-group> + <context-group purpose="location"><context context-type="linenumber">1043</context></context-group> <note annotates="source" from="developer">A console message indicating an entered command is currently being executed.</note> </trans-unit> - <trans-unit id="_msg598"> + <trans-unit id="_msg623"> <source xml:space="preserve">(peer: %1)</source> - <context-group purpose="location"><context context-type="linenumber">1166</context></context-group> + <context-group purpose="location"><context context-type="linenumber">1161</context></context-group> </trans-unit> - <trans-unit id="_msg599"> + <trans-unit id="_msg624"> <source xml:space="preserve">via %1</source> - <context-group purpose="location"><context context-type="linenumber">1168</context></context-group> + <context-group purpose="location"><context context-type="linenumber">1163</context></context-group> </trans-unit> </group> </body></file> <file original="../rpcconsole.h" datatype="c" source-language="en"><body> <group restype="x-trolltech-linguist-context" resname="RPCConsole"> - <trans-unit id="_msg600"> + <trans-unit id="_msg625"> <source xml:space="preserve">Yes</source> - <context-group purpose="location"><context context-type="linenumber">138</context></context-group> + <context-group purpose="location"><context context-type="linenumber">142</context></context-group> </trans-unit> - <trans-unit id="_msg601"> + <trans-unit id="_msg626"> <source xml:space="preserve">No</source> - <context-group purpose="location"><context context-type="linenumber">138</context></context-group> + <context-group purpose="location"><context context-type="linenumber">142</context></context-group> </trans-unit> - <trans-unit id="_msg602"> + <trans-unit id="_msg627"> <source xml:space="preserve">To</source> - <context-group purpose="location"><context context-type="linenumber">138</context></context-group> + <context-group purpose="location"><context context-type="linenumber">142</context></context-group> </trans-unit> - <trans-unit id="_msg603"> + <trans-unit id="_msg628"> <source xml:space="preserve">From</source> - <context-group purpose="location"><context context-type="linenumber">138</context></context-group> + <context-group purpose="location"><context context-type="linenumber">142</context></context-group> </trans-unit> - <trans-unit id="_msg604"> + <trans-unit id="_msg629"> <source xml:space="preserve">Ban for</source> - <context-group purpose="location"><context context-type="linenumber">139</context></context-group> + <context-group purpose="location"><context context-type="linenumber">143</context></context-group> </trans-unit> - <trans-unit id="_msg605"> + <trans-unit id="_msg630"> <source xml:space="preserve">Never</source> - <context-group purpose="location"><context context-type="linenumber">180</context></context-group> + <context-group purpose="location"><context context-type="linenumber">185</context></context-group> </trans-unit> - <trans-unit id="_msg606"> + <trans-unit id="_msg631"> <source xml:space="preserve">Unknown</source> - <context-group purpose="location"><context context-type="linenumber">139</context></context-group> + <context-group purpose="location"><context context-type="linenumber">143</context></context-group> </trans-unit> </group> </body></file> <file original="../forms/receivecoinsdialog.ui" datatype="x-trolltech-designer-ui" source-language="en"><body> <group restype="x-trolltech-linguist-context" resname="ReceiveCoinsDialog"> - <trans-unit id="_msg607"> + <trans-unit id="_msg632"> <source xml:space="preserve">&Amount:</source> <context-group purpose="location"><context context-type="linenumber">37</context></context-group> </trans-unit> - <trans-unit id="_msg608"> + <trans-unit id="_msg633"> <source xml:space="preserve">&Label:</source> <context-group purpose="location"><context context-type="linenumber">83</context></context-group> </trans-unit> - <trans-unit id="_msg609"> + <trans-unit id="_msg634"> <source xml:space="preserve">&Message:</source> <context-group purpose="location"><context context-type="linenumber">53</context></context-group> </trans-unit> - <trans-unit id="_msg610"> + <trans-unit id="_msg635"> <source xml:space="preserve">An optional message to attach to the payment request, which will be displayed when the request is opened. Note: The message will not be sent with the payment over the Bitcoin network.</source> <context-group purpose="location"><context context-type="linenumber">50</context></context-group> </trans-unit> - <trans-unit id="_msg611"> + <trans-unit id="_msg636"> <source xml:space="preserve">An optional label to associate with the new receiving address.</source> <context-group purpose="location"><context context-type="linenumber">80</context></context-group> </trans-unit> - <trans-unit id="_msg612"> + <trans-unit id="_msg637"> <source xml:space="preserve">Use this form to request payments. All fields are <b>optional</b>.</source> <context-group purpose="location"><context context-type="linenumber">73</context></context-group> </trans-unit> - <trans-unit id="_msg613"> + <trans-unit id="_msg638"> <source xml:space="preserve">An optional amount to request. Leave this empty or zero to not request a specific amount.</source> <context-group purpose="location"><context context-type="linenumber">34</context></context-group> <context-group purpose="location"><context context-type="linenumber">193</context></context-group> </trans-unit> - <trans-unit id="_msg614"> + <trans-unit id="_msg639"> <source xml:space="preserve">An optional label to associate with the new receiving address (used by you to identify an invoice). It is also attached to the payment request.</source> <context-group purpose="location"><context context-type="linenumber">66</context></context-group> </trans-unit> - <trans-unit id="_msg615"> + <trans-unit id="_msg640"> <source xml:space="preserve">An optional message that is attached to the payment request and may be displayed to the sender.</source> <context-group purpose="location"><context context-type="linenumber">96</context></context-group> </trans-unit> - <trans-unit id="_msg616"> + <trans-unit id="_msg641"> <source xml:space="preserve">&Create new receiving address</source> <context-group purpose="location"><context context-type="linenumber">111</context></context-group> </trans-unit> - <trans-unit id="_msg617"> + <trans-unit id="_msg642"> <source xml:space="preserve">Clear all fields of the form.</source> <context-group purpose="location"><context context-type="linenumber">134</context></context-group> </trans-unit> - <trans-unit id="_msg618"> + <trans-unit id="_msg643"> <source xml:space="preserve">Clear</source> <context-group purpose="location"><context context-type="linenumber">137</context></context-group> </trans-unit> - <trans-unit id="_msg619"> + <trans-unit id="_msg644"> <source xml:space="preserve">Requested payments history</source> <context-group purpose="location"><context context-type="linenumber">273</context></context-group> </trans-unit> - <trans-unit id="_msg620"> + <trans-unit id="_msg645"> <source xml:space="preserve">Show the selected request (does the same as double clicking an entry)</source> <context-group purpose="location"><context context-type="linenumber">298</context></context-group> </trans-unit> - <trans-unit id="_msg621"> + <trans-unit id="_msg646"> <source xml:space="preserve">Show</source> <context-group purpose="location"><context context-type="linenumber">301</context></context-group> </trans-unit> - <trans-unit id="_msg622"> + <trans-unit id="_msg647"> <source xml:space="preserve">Remove the selected entries from the list</source> <context-group purpose="location"><context context-type="linenumber">318</context></context-group> </trans-unit> - <trans-unit id="_msg623"> + <trans-unit id="_msg648"> <source xml:space="preserve">Remove</source> <context-group purpose="location"><context context-type="linenumber">321</context></context-group> </trans-unit> @@ -2859,31 +3003,31 @@ For more information on using this console, type %6. </body></file> <file original="../receivecoinsdialog.cpp" datatype="cpp" source-language="en"><body> <group restype="x-trolltech-linguist-context" resname="ReceiveCoinsDialog"> - <trans-unit id="_msg624"> + <trans-unit id="_msg649"> <source xml:space="preserve">Copy &URI</source> <context-group purpose="location"><context context-type="linenumber">47</context></context-group> </trans-unit> - <trans-unit id="_msg625"> + <trans-unit id="_msg650"> <source xml:space="preserve">&Copy address</source> <context-group purpose="location"><context context-type="linenumber">48</context></context-group> </trans-unit> - <trans-unit id="_msg626"> + <trans-unit id="_msg651"> <source xml:space="preserve">Copy &label</source> <context-group purpose="location"><context context-type="linenumber">49</context></context-group> </trans-unit> - <trans-unit id="_msg627"> + <trans-unit id="_msg652"> <source xml:space="preserve">Copy &message</source> <context-group purpose="location"><context context-type="linenumber">50</context></context-group> </trans-unit> - <trans-unit id="_msg628"> + <trans-unit id="_msg653"> <source xml:space="preserve">Copy &amount</source> <context-group purpose="location"><context context-type="linenumber">51</context></context-group> </trans-unit> - <trans-unit id="_msg629"> + <trans-unit id="_msg654"> <source xml:space="preserve">Could not unlock wallet.</source> <context-group purpose="location"><context context-type="linenumber">176</context></context-group> </trans-unit> - <trans-unit id="_msg630"> + <trans-unit id="_msg655"> <source xml:space="preserve">Could not generate new %1 address</source> <context-group purpose="location"><context context-type="linenumber">181</context></context-group> </trans-unit> @@ -2891,51 +3035,51 @@ For more information on using this console, type %6. </body></file> <file original="../forms/receiverequestdialog.ui" datatype="x-trolltech-designer-ui" source-language="en"><body> <group restype="x-trolltech-linguist-context" resname="ReceiveRequestDialog"> - <trans-unit id="_msg631"> + <trans-unit id="_msg656"> <source xml:space="preserve">Request payment to …</source> <context-group purpose="location"><context context-type="linenumber">14</context></context-group> </trans-unit> - <trans-unit id="_msg632"> + <trans-unit id="_msg657"> <source xml:space="preserve">Address:</source> <context-group purpose="location"><context context-type="linenumber">90</context></context-group> </trans-unit> - <trans-unit id="_msg633"> + <trans-unit id="_msg658"> <source xml:space="preserve">Amount:</source> <context-group purpose="location"><context context-type="linenumber">119</context></context-group> </trans-unit> - <trans-unit id="_msg634"> + <trans-unit id="_msg659"> <source xml:space="preserve">Label:</source> <context-group purpose="location"><context context-type="linenumber">148</context></context-group> </trans-unit> - <trans-unit id="_msg635"> + <trans-unit id="_msg660"> <source xml:space="preserve">Message:</source> <context-group purpose="location"><context context-type="linenumber">180</context></context-group> </trans-unit> - <trans-unit id="_msg636"> + <trans-unit id="_msg661"> <source xml:space="preserve">Wallet:</source> <context-group purpose="location"><context context-type="linenumber">212</context></context-group> </trans-unit> - <trans-unit id="_msg637"> + <trans-unit id="_msg662"> <source xml:space="preserve">Copy &URI</source> <context-group purpose="location"><context context-type="linenumber">240</context></context-group> </trans-unit> - <trans-unit id="_msg638"> + <trans-unit id="_msg663"> <source xml:space="preserve">Copy &Address</source> <context-group purpose="location"><context context-type="linenumber">250</context></context-group> </trans-unit> - <trans-unit id="_msg639"> + <trans-unit id="_msg664"> <source xml:space="preserve">&Verify</source> <context-group purpose="location"><context context-type="linenumber">260</context></context-group> </trans-unit> - <trans-unit id="_msg640"> + <trans-unit id="_msg665"> <source xml:space="preserve">Verify this address on e.g. a hardware wallet screen</source> <context-group purpose="location"><context context-type="linenumber">263</context></context-group> </trans-unit> - <trans-unit id="_msg641"> + <trans-unit id="_msg666"> <source xml:space="preserve">&Save Image…</source> <context-group purpose="location"><context context-type="linenumber">273</context></context-group> </trans-unit> - <trans-unit id="_msg642"> + <trans-unit id="_msg667"> <source xml:space="preserve">Payment information</source> <context-group purpose="location"><context context-type="linenumber">39</context></context-group> </trans-unit> @@ -2943,7 +3087,7 @@ For more information on using this console, type %6. </body></file> <file original="../receiverequestdialog.cpp" datatype="cpp" source-language="en"><body> <group restype="x-trolltech-linguist-context" resname="ReceiveRequestDialog"> - <trans-unit id="_msg643"> + <trans-unit id="_msg668"> <source xml:space="preserve">Request payment to %1</source> <context-group purpose="location"><context context-type="linenumber">49</context></context-group> </trans-unit> @@ -2951,186 +3095,186 @@ For more information on using this console, type %6. </body></file> <file original="../recentrequeststablemodel.cpp" datatype="cpp" source-language="en"><body> <group restype="x-trolltech-linguist-context" resname="RecentRequestsTableModel"> - <trans-unit id="_msg644"> + <trans-unit id="_msg669"> <source xml:space="preserve">Date</source> <context-group purpose="location"><context context-type="linenumber">32</context></context-group> </trans-unit> - <trans-unit id="_msg645"> + <trans-unit id="_msg670"> <source xml:space="preserve">Label</source> <context-group purpose="location"><context context-type="linenumber">32</context></context-group> </trans-unit> - <trans-unit id="_msg646"> + <trans-unit id="_msg671"> <source xml:space="preserve">Message</source> <context-group purpose="location"><context context-type="linenumber">32</context></context-group> </trans-unit> - <trans-unit id="_msg647"> + <trans-unit id="_msg672"> <source xml:space="preserve">(no label)</source> - <context-group purpose="location"><context context-type="linenumber">73</context></context-group> + <context-group purpose="location"><context context-type="linenumber">70</context></context-group> </trans-unit> - <trans-unit id="_msg648"> + <trans-unit id="_msg673"> <source xml:space="preserve">(no message)</source> - <context-group purpose="location"><context context-type="linenumber">82</context></context-group> + <context-group purpose="location"><context context-type="linenumber">79</context></context-group> </trans-unit> - <trans-unit id="_msg649"> + <trans-unit id="_msg674"> <source xml:space="preserve">(no amount requested)</source> - <context-group purpose="location"><context context-type="linenumber">90</context></context-group> + <context-group purpose="location"><context context-type="linenumber">87</context></context-group> </trans-unit> - <trans-unit id="_msg650"> + <trans-unit id="_msg675"> <source xml:space="preserve">Requested</source> - <context-group purpose="location"><context context-type="linenumber">133</context></context-group> + <context-group purpose="location"><context context-type="linenumber">130</context></context-group> </trans-unit> </group> </body></file> <file original="../forms/sendcoinsdialog.ui" datatype="x-trolltech-designer-ui" source-language="en"><body> <group restype="x-trolltech-linguist-context" resname="SendCoinsDialog"> - <trans-unit id="_msg651"> + <trans-unit id="_msg676"> <source xml:space="preserve">Send Coins</source> <context-group purpose="location"><context context-type="linenumber">14</context></context-group> - <context-group purpose="location"><context context-type="sourcefile">../sendcoinsdialog.cpp</context><context context-type="linenumber">752</context></context-group> + <context-group purpose="location"><context context-type="sourcefile">../sendcoinsdialog.cpp</context><context context-type="linenumber">757</context></context-group> </trans-unit> - <trans-unit id="_msg652"> + <trans-unit id="_msg677"> <source xml:space="preserve">Coin Control Features</source> <context-group purpose="location"><context context-type="linenumber">90</context></context-group> </trans-unit> - <trans-unit id="_msg653"> + <trans-unit id="_msg678"> <source xml:space="preserve">automatically selected</source> <context-group purpose="location"><context context-type="linenumber">120</context></context-group> </trans-unit> - <trans-unit id="_msg654"> + <trans-unit id="_msg679"> <source xml:space="preserve">Insufficient funds!</source> <context-group purpose="location"><context context-type="linenumber">139</context></context-group> </trans-unit> - <trans-unit id="_msg655"> + <trans-unit id="_msg680"> <source xml:space="preserve">Quantity:</source> <context-group purpose="location"><context context-type="linenumber">228</context></context-group> </trans-unit> - <trans-unit id="_msg656"> + <trans-unit id="_msg681"> <source xml:space="preserve">Bytes:</source> <context-group purpose="location"><context context-type="linenumber">263</context></context-group> </trans-unit> - <trans-unit id="_msg657"> + <trans-unit id="_msg682"> <source xml:space="preserve">Amount:</source> <context-group purpose="location"><context context-type="linenumber">311</context></context-group> </trans-unit> - <trans-unit id="_msg658"> + <trans-unit id="_msg683"> <source xml:space="preserve">Fee:</source> <context-group purpose="location"><context context-type="linenumber">391</context></context-group> </trans-unit> - <trans-unit id="_msg659"> + <trans-unit id="_msg684"> <source xml:space="preserve">After Fee:</source> <context-group purpose="location"><context context-type="linenumber">442</context></context-group> </trans-unit> - <trans-unit id="_msg660"> + <trans-unit id="_msg685"> <source xml:space="preserve">Change:</source> <context-group purpose="location"><context context-type="linenumber">474</context></context-group> </trans-unit> - <trans-unit id="_msg661"> + <trans-unit id="_msg686"> <source xml:space="preserve">If this is activated, but the change address is empty or invalid, change will be sent to a newly generated address.</source> <context-group purpose="location"><context context-type="linenumber">518</context></context-group> </trans-unit> - <trans-unit id="_msg662"> + <trans-unit id="_msg687"> <source xml:space="preserve">Custom change address</source> <context-group purpose="location"><context context-type="linenumber">521</context></context-group> </trans-unit> - <trans-unit id="_msg663"> + <trans-unit id="_msg688"> <source xml:space="preserve">Transaction Fee:</source> <context-group purpose="location"><context context-type="linenumber">727</context></context-group> </trans-unit> - <trans-unit id="_msg664"> + <trans-unit id="_msg689"> <source xml:space="preserve">Using the fallbackfee can result in sending a transaction that will take several hours or days (or never) to confirm. Consider choosing your fee manually or wait until you have validated the complete chain.</source> <context-group purpose="location"><context context-type="linenumber">765</context></context-group> </trans-unit> - <trans-unit id="_msg665"> + <trans-unit id="_msg690"> <source xml:space="preserve">Warning: Fee estimation is currently not possible.</source> <context-group purpose="location"><context context-type="linenumber">774</context></context-group> </trans-unit> - <trans-unit id="_msg666"> + <trans-unit id="_msg691"> <source xml:space="preserve">per kilobyte</source> <context-group purpose="location"><context context-type="linenumber">856</context></context-group> </trans-unit> - <trans-unit id="_msg667"> + <trans-unit id="_msg692"> <source xml:space="preserve">Hide</source> <context-group purpose="location"><context context-type="linenumber">803</context></context-group> </trans-unit> - <trans-unit id="_msg668"> + <trans-unit id="_msg693"> <source xml:space="preserve">Recommended:</source> <context-group purpose="location"><context context-type="linenumber">915</context></context-group> </trans-unit> - <trans-unit id="_msg669"> + <trans-unit id="_msg694"> <source xml:space="preserve">Custom:</source> <context-group purpose="location"><context context-type="linenumber">945</context></context-group> </trans-unit> - <trans-unit id="_msg670"> + <trans-unit id="_msg695"> <source xml:space="preserve">Send to multiple recipients at once</source> <context-group purpose="location"><context context-type="linenumber">1160</context></context-group> </trans-unit> - <trans-unit id="_msg671"> + <trans-unit id="_msg696"> <source xml:space="preserve">Add &Recipient</source> <context-group purpose="location"><context context-type="linenumber">1163</context></context-group> </trans-unit> - <trans-unit id="_msg672"> + <trans-unit id="_msg697"> <source xml:space="preserve">Clear all fields of the form.</source> <context-group purpose="location"><context context-type="linenumber">1143</context></context-group> </trans-unit> - <trans-unit id="_msg673"> + <trans-unit id="_msg698"> <source xml:space="preserve">Inputs…</source> <context-group purpose="location"><context context-type="linenumber">110</context></context-group> </trans-unit> - <trans-unit id="_msg674"> + <trans-unit id="_msg699"> <source xml:space="preserve">Dust:</source> <context-group purpose="location"><context context-type="linenumber">343</context></context-group> </trans-unit> - <trans-unit id="_msg675"> + <trans-unit id="_msg700"> <source xml:space="preserve">Choose…</source> <context-group purpose="location"><context context-type="linenumber">741</context></context-group> </trans-unit> - <trans-unit id="_msg676"> + <trans-unit id="_msg701"> <source xml:space="preserve">Hide transaction fee settings</source> <context-group purpose="location"><context context-type="linenumber">800</context></context-group> </trans-unit> - <trans-unit id="_msg677"> + <trans-unit id="_msg702"> <source xml:space="preserve">Specify a custom fee per kB (1,000 bytes) of the transaction's virtual size. Note: Since the fee is calculated on a per-byte basis, a fee rate of "100 satoshis per kvB" for a transaction size of 500 virtual bytes (half of 1 kvB) would ultimately yield a fee of only 50 satoshis.</source> <context-group purpose="location"><context context-type="linenumber">851</context></context-group> </trans-unit> - <trans-unit id="_msg678"> + <trans-unit id="_msg703"> <source xml:space="preserve">When there is less transaction volume than space in the blocks, miners as well as relaying nodes may enforce a minimum fee. Paying only this minimum fee is just fine, but be aware that this can result in a never confirming transaction once there is more demand for bitcoin transactions than the network can process.</source> <context-group purpose="location"><context context-type="linenumber">886</context></context-group> </trans-unit> - <trans-unit id="_msg679"> + <trans-unit id="_msg704"> <source xml:space="preserve">A too low fee might result in a never confirming transaction (read the tooltip)</source> <context-group purpose="location"><context context-type="linenumber">889</context></context-group> </trans-unit> - <trans-unit id="_msg680"> + <trans-unit id="_msg705"> <source xml:space="preserve">(Smart fee not initialized yet. This usually takes a few blocks…)</source> <context-group purpose="location"><context context-type="linenumber">994</context></context-group> </trans-unit> - <trans-unit id="_msg681"> + <trans-unit id="_msg706"> <source xml:space="preserve">Confirmation time target:</source> <context-group purpose="location"><context context-type="linenumber">1020</context></context-group> </trans-unit> - <trans-unit id="_msg682"> + <trans-unit id="_msg707"> <source xml:space="preserve">Enable Replace-By-Fee</source> <context-group purpose="location"><context context-type="linenumber">1078</context></context-group> </trans-unit> - <trans-unit id="_msg683"> + <trans-unit id="_msg708"> <source xml:space="preserve">With Replace-By-Fee (BIP-125) you can increase a transaction's fee after it is sent. Without this, a higher fee may be recommended to compensate for increased transaction delay risk.</source> <context-group purpose="location"><context context-type="linenumber">1081</context></context-group> </trans-unit> - <trans-unit id="_msg684"> + <trans-unit id="_msg709"> <source xml:space="preserve">Clear &All</source> <context-group purpose="location"><context context-type="linenumber">1146</context></context-group> </trans-unit> - <trans-unit id="_msg685"> + <trans-unit id="_msg710"> <source xml:space="preserve">Balance:</source> <context-group purpose="location"><context context-type="linenumber">1201</context></context-group> </trans-unit> - <trans-unit id="_msg686"> + <trans-unit id="_msg711"> <source xml:space="preserve">Confirm the send action</source> <context-group purpose="location"><context context-type="linenumber">1117</context></context-group> </trans-unit> - <trans-unit id="_msg687"> + <trans-unit id="_msg712"> <source xml:space="preserve">S&end</source> <context-group purpose="location"><context context-type="linenumber">1120</context></context-group> </trans-unit> @@ -3138,422 +3282,396 @@ Note: Since the fee is calculated on a per-byte basis, a fee rate of "100 </body></file> <file original="../sendcoinsdialog.cpp" datatype="cpp" source-language="en"><body> <group restype="x-trolltech-linguist-context" resname="SendCoinsDialog"> - <trans-unit id="_msg688"> + <trans-unit id="_msg713"> <source xml:space="preserve">Copy quantity</source> <context-group purpose="location"><context context-type="linenumber">99</context></context-group> </trans-unit> - <trans-unit id="_msg689"> + <trans-unit id="_msg714"> <source xml:space="preserve">Copy amount</source> <context-group purpose="location"><context context-type="linenumber">100</context></context-group> </trans-unit> - <trans-unit id="_msg690"> + <trans-unit id="_msg715"> <source xml:space="preserve">Copy fee</source> <context-group purpose="location"><context context-type="linenumber">101</context></context-group> </trans-unit> - <trans-unit id="_msg691"> + <trans-unit id="_msg716"> <source xml:space="preserve">Copy after fee</source> <context-group purpose="location"><context context-type="linenumber">102</context></context-group> </trans-unit> - <trans-unit id="_msg692"> + <trans-unit id="_msg717"> <source xml:space="preserve">Copy bytes</source> <context-group purpose="location"><context context-type="linenumber">103</context></context-group> </trans-unit> - <trans-unit id="_msg693"> + <trans-unit id="_msg718"> <source xml:space="preserve">Copy dust</source> <context-group purpose="location"><context context-type="linenumber">104</context></context-group> </trans-unit> - <trans-unit id="_msg694"> + <trans-unit id="_msg719"> <source xml:space="preserve">Copy change</source> <context-group purpose="location"><context context-type="linenumber">105</context></context-group> </trans-unit> - <trans-unit id="_msg695"> + <trans-unit id="_msg720"> <source xml:space="preserve">%1 (%2 blocks)</source> - <context-group purpose="location"><context context-type="linenumber">181</context></context-group> + <context-group purpose="location"><context context-type="linenumber">179</context></context-group> </trans-unit> - <trans-unit id="_msg696"> + <trans-unit id="_msg721"> <source xml:space="preserve">Sign on device</source> - <context-group purpose="location"><context context-type="linenumber">211</context></context-group> + <context-group purpose="location"><context context-type="linenumber">209</context></context-group> <note annotates="source" from="developer">"device" usually means a hardware wallet.</note> </trans-unit> - <trans-unit id="_msg697"> + <trans-unit id="_msg722"> <source xml:space="preserve">Connect your hardware wallet first.</source> - <context-group purpose="location"><context context-type="linenumber">214</context></context-group> + <context-group purpose="location"><context context-type="linenumber">212</context></context-group> </trans-unit> - <trans-unit id="_msg698"> + <trans-unit id="_msg723"> <source xml:space="preserve">Set external signer script path in Options -> Wallet</source> - <context-group purpose="location"><context context-type="linenumber">218</context></context-group> + <context-group purpose="location"><context context-type="linenumber">216</context></context-group> <note annotates="source" from="developer">"External signer" means using devices such as hardware wallets.</note> </trans-unit> - <trans-unit id="_msg699"> + <trans-unit id="_msg724"> <source xml:space="preserve">Cr&eate Unsigned</source> - <context-group purpose="location"><context context-type="linenumber">221</context></context-group> + <context-group purpose="location"><context context-type="linenumber">219</context></context-group> </trans-unit> - <trans-unit id="_msg700"> + <trans-unit id="_msg725"> <source xml:space="preserve">Creates a Partially Signed Bitcoin Transaction (PSBT) for use with e.g. an offline %1 wallet, or a PSBT-compatible hardware wallet.</source> - <context-group purpose="location"><context context-type="linenumber">222</context></context-group> + <context-group purpose="location"><context context-type="linenumber">220</context></context-group> </trans-unit> - <trans-unit id="_msg701"> + <trans-unit id="_msg726"> <source xml:space="preserve"> from wallet '%1'</source> - <context-group purpose="location"><context context-type="linenumber">312</context></context-group> + <context-group purpose="location"><context context-type="linenumber">310</context></context-group> </trans-unit> - <trans-unit id="_msg702"> + <trans-unit id="_msg727"> <source xml:space="preserve">%1 to '%2'</source> - <context-group purpose="location"><context context-type="linenumber">323</context></context-group> + <context-group purpose="location"><context context-type="linenumber">321</context></context-group> </trans-unit> - <trans-unit id="_msg703"> + <trans-unit id="_msg728"> <source xml:space="preserve">%1 to %2</source> - <context-group purpose="location"><context context-type="linenumber">328</context></context-group> + <context-group purpose="location"><context context-type="linenumber">326</context></context-group> </trans-unit> - <trans-unit id="_msg704"> + <trans-unit id="_msg729"> <source xml:space="preserve">To review recipient list click "Show Details…"</source> - <context-group purpose="location"><context context-type="linenumber">395</context></context-group> + <context-group purpose="location"><context context-type="linenumber">392</context></context-group> </trans-unit> - <trans-unit id="_msg705"> + <trans-unit id="_msg730"> <source xml:space="preserve">Sign failed</source> - <context-group purpose="location"><context context-type="linenumber">439</context></context-group> + <context-group purpose="location"><context context-type="linenumber">452</context></context-group> </trans-unit> - <trans-unit id="_msg706"> + <trans-unit id="_msg731"> <source xml:space="preserve">External signer not found</source> - <context-group purpose="location"><context context-type="linenumber">445</context></context-group> + <context-group purpose="location"><context context-type="linenumber">457</context></context-group> <note annotates="source" from="developer">"External signer" means using devices such as hardware wallets.</note> </trans-unit> - <trans-unit id="_msg707"> + <trans-unit id="_msg732"> <source xml:space="preserve">External signer failure</source> - <context-group purpose="location"><context context-type="linenumber">451</context></context-group> + <context-group purpose="location"><context context-type="linenumber">462</context></context-group> <note annotates="source" from="developer">"External signer" means using devices such as hardware wallets.</note> </trans-unit> - <trans-unit id="_msg708"> + <trans-unit id="_msg733"> <source xml:space="preserve">Save Transaction Data</source> - <context-group purpose="location"><context context-type="linenumber">509</context></context-group> + <context-group purpose="location"><context context-type="linenumber">428</context></context-group> </trans-unit> - <trans-unit id="_msg709"> + <trans-unit id="_msg734"> <source xml:space="preserve">Partially Signed Transaction (Binary)</source> - <context-group purpose="location"><context context-type="linenumber">511</context></context-group> + <context-group purpose="location"><context context-type="linenumber">430</context></context-group> <note annotates="source" from="developer">Expanded name of the binary PSBT file format. See: BIP 174.</note> </trans-unit> - <trans-unit id="_msg710"> + <trans-unit id="_msg735"> <source xml:space="preserve">PSBT saved</source> - <context-group purpose="location"><context context-type="linenumber">518</context></context-group> + <context-group purpose="location"><context context-type="linenumber">437</context></context-group> </trans-unit> - <trans-unit id="_msg711"> + <trans-unit id="_msg736"> <source xml:space="preserve">External balance:</source> - <context-group purpose="location"><context context-type="linenumber">694</context></context-group> + <context-group purpose="location"><context context-type="linenumber">703</context></context-group> </trans-unit> - <trans-unit id="_msg712"> + <trans-unit id="_msg737"> <source xml:space="preserve">or</source> - <context-group purpose="location"><context context-type="linenumber">391</context></context-group> + <context-group purpose="location"><context context-type="linenumber">388</context></context-group> </trans-unit> - <trans-unit id="_msg713"> + <trans-unit id="_msg738"> <source xml:space="preserve">You can increase the fee later (signals Replace-By-Fee, BIP-125).</source> - <context-group purpose="location"><context context-type="linenumber">372</context></context-group> + <context-group purpose="location"><context context-type="linenumber">370</context></context-group> </trans-unit> - <trans-unit id="_msg714"> + <trans-unit id="_msg739"> <source xml:space="preserve">Please, review your transaction proposal. This will produce a Partially Signed Bitcoin Transaction (PSBT) which you can save or copy and then sign with e.g. an offline %1 wallet, or a PSBT-compatible hardware wallet.</source> - <context-group purpose="location"><context context-type="linenumber">342</context></context-group> + <context-group purpose="location"><context context-type="linenumber">340</context></context-group> <note annotates="source" from="developer">Text to inform a user attempting to create a transaction of their current options. At this stage, a user can only create a PSBT. This string is displayed when private keys are disabled and an external signer is not available.</note> </trans-unit> - <trans-unit id="_msg715"> + <trans-unit id="_msg740"> <source xml:space="preserve">Do you want to create this transaction?</source> - <context-group purpose="location"><context context-type="linenumber">336</context></context-group> + <context-group purpose="location"><context context-type="linenumber">334</context></context-group> <note annotates="source" from="developer">Message displayed when attempting to create a transaction. Cautionary text to prompt the user to verify that the displayed transaction details represent the transaction the user intends to create.</note> </trans-unit> - <trans-unit id="_msg716"> + <trans-unit id="_msg741"> <source xml:space="preserve">Please, review your transaction. You can create and send this transaction or create a Partially Signed Bitcoin Transaction (PSBT), which you can save or copy and then sign with, e.g., an offline %1 wallet, or a PSBT-compatible hardware wallet.</source> - <context-group purpose="location"><context context-type="linenumber">347</context></context-group> + <context-group purpose="location"><context context-type="linenumber">345</context></context-group> <note annotates="source" from="developer">Text to inform a user attempting to create a transaction of their current options. At this stage, a user can send their transaction or create a PSBT. This string is displayed when both private keys and PSBT controls are enabled.</note> </trans-unit> - <trans-unit id="_msg717"> + <trans-unit id="_msg742"> <source xml:space="preserve">Please, review your transaction.</source> - <context-group purpose="location"><context context-type="linenumber">350</context></context-group> + <context-group purpose="location"><context context-type="linenumber">348</context></context-group> <note annotates="source" from="developer">Text to prompt a user to review the details of the transaction they are attempting to send.</note> </trans-unit> - <trans-unit id="_msg718"> + <trans-unit id="_msg743"> <source xml:space="preserve">Transaction fee</source> - <context-group purpose="location"><context context-type="linenumber">358</context></context-group> + <context-group purpose="location"><context context-type="linenumber">356</context></context-group> </trans-unit> - <trans-unit id="_msg719"> + <trans-unit id="_msg744"> <source xml:space="preserve">Not signalling Replace-By-Fee, BIP-125.</source> - <context-group purpose="location"><context context-type="linenumber">374</context></context-group> + <context-group purpose="location"><context context-type="linenumber">372</context></context-group> </trans-unit> - <trans-unit id="_msg720"> + <trans-unit id="_msg745"> <source xml:space="preserve">Total Amount</source> - <context-group purpose="location"><context context-type="linenumber">388</context></context-group> + <context-group purpose="location"><context context-type="linenumber">385</context></context-group> </trans-unit> - <trans-unit id="_msg721"> + <trans-unit id="_msg746"> <source xml:space="preserve">Confirm send coins</source> - <context-group purpose="location"><context context-type="linenumber">413</context></context-group> + <context-group purpose="location"><context context-type="linenumber">484</context></context-group> </trans-unit> - <trans-unit id="_msg722"> + <trans-unit id="_msg747"> <source xml:space="preserve">Watch-only balance:</source> - <context-group purpose="location"><context context-type="linenumber">697</context></context-group> + <context-group purpose="location"><context context-type="linenumber">706</context></context-group> </trans-unit> - <trans-unit id="_msg723"> + <trans-unit id="_msg748"> <source xml:space="preserve">The recipient address is not valid. Please recheck.</source> - <context-group purpose="location"><context context-type="linenumber">721</context></context-group> + <context-group purpose="location"><context context-type="linenumber">730</context></context-group> </trans-unit> - <trans-unit id="_msg724"> + <trans-unit id="_msg749"> <source xml:space="preserve">The amount to pay must be larger than 0.</source> - <context-group purpose="location"><context context-type="linenumber">724</context></context-group> + <context-group purpose="location"><context context-type="linenumber">733</context></context-group> </trans-unit> - <trans-unit id="_msg725"> + <trans-unit id="_msg750"> <source xml:space="preserve">The amount exceeds your balance.</source> - <context-group purpose="location"><context context-type="linenumber">727</context></context-group> + <context-group purpose="location"><context context-type="linenumber">736</context></context-group> </trans-unit> - <trans-unit id="_msg726"> + <trans-unit id="_msg751"> <source xml:space="preserve">The total exceeds your balance when the %1 transaction fee is included.</source> - <context-group purpose="location"><context context-type="linenumber">730</context></context-group> + <context-group purpose="location"><context context-type="linenumber">739</context></context-group> </trans-unit> - <trans-unit id="_msg727"> + <trans-unit id="_msg752"> <source xml:space="preserve">Duplicate address found: addresses should only be used once each.</source> - <context-group purpose="location"><context context-type="linenumber">733</context></context-group> + <context-group purpose="location"><context context-type="linenumber">742</context></context-group> </trans-unit> - <trans-unit id="_msg728"> + <trans-unit id="_msg753"> <source xml:space="preserve">Transaction creation failed!</source> - <context-group purpose="location"><context context-type="linenumber">736</context></context-group> + <context-group purpose="location"><context context-type="linenumber">745</context></context-group> </trans-unit> - <trans-unit id="_msg729"> + <trans-unit id="_msg754"> <source xml:space="preserve">A fee higher than %1 is considered an absurdly high fee.</source> - <context-group purpose="location"><context context-type="linenumber">740</context></context-group> - </trans-unit> - <trans-unit id="_msg730"> - <source xml:space="preserve">Payment request expired.</source> - <context-group purpose="location"><context context-type="linenumber">743</context></context-group> + <context-group purpose="location"><context context-type="linenumber">749</context></context-group> </trans-unit> <group restype="x-gettext-plurals"> - <context-group purpose="location"><context context-type="linenumber">867</context></context-group> - <trans-unit id="_msg731[0]"> + <context-group purpose="location"><context context-type="linenumber">872</context></context-group> + <trans-unit id="_msg755[0]"> <source xml:space="preserve">Estimated to begin confirmation within %n block(s).</source> </trans-unit> - <trans-unit id="_msg731[1]"> + <trans-unit id="_msg755[1]"> <source xml:space="preserve">Estimated to begin confirmation within %n block(s).</source> </trans-unit> </group> - <trans-unit id="_msg732"> + <trans-unit id="_msg756"> <source xml:space="preserve">Warning: Invalid Bitcoin address</source> - <context-group purpose="location"><context context-type="linenumber">968</context></context-group> + <context-group purpose="location"><context context-type="linenumber">973</context></context-group> </trans-unit> - <trans-unit id="_msg733"> + <trans-unit id="_msg757"> <source xml:space="preserve">Warning: Unknown change address</source> - <context-group purpose="location"><context context-type="linenumber">973</context></context-group> + <context-group purpose="location"><context context-type="linenumber">978</context></context-group> </trans-unit> - <trans-unit id="_msg734"> + <trans-unit id="_msg758"> <source xml:space="preserve">Confirm custom change address</source> - <context-group purpose="location"><context context-type="linenumber">976</context></context-group> + <context-group purpose="location"><context context-type="linenumber">981</context></context-group> </trans-unit> - <trans-unit id="_msg735"> + <trans-unit id="_msg759"> <source xml:space="preserve">The address you selected for change is not part of this wallet. Any or all funds in your wallet may be sent to this address. Are you sure?</source> - <context-group purpose="location"><context context-type="linenumber">976</context></context-group> + <context-group purpose="location"><context context-type="linenumber">981</context></context-group> </trans-unit> - <trans-unit id="_msg736"> + <trans-unit id="_msg760"> <source xml:space="preserve">(no label)</source> - <context-group purpose="location"><context context-type="linenumber">997</context></context-group> + <context-group purpose="location"><context context-type="linenumber">1002</context></context-group> </trans-unit> </group> </body></file> <file original="../forms/sendcoinsentry.ui" datatype="x-trolltech-designer-ui" source-language="en"><body> <group restype="x-trolltech-linguist-context" resname="SendCoinsEntry"> - <trans-unit id="_msg737"> + <trans-unit id="_msg761"> <source xml:space="preserve">A&mount:</source> - <context-group purpose="location"><context context-type="linenumber">155</context></context-group> - <context-group purpose="location"><context context-type="linenumber">705</context></context-group> - <context-group purpose="location"><context context-type="linenumber">1238</context></context-group> + <context-group purpose="location"><context context-type="linenumber">151</context></context-group> </trans-unit> - <trans-unit id="_msg738"> + <trans-unit id="_msg762"> <source xml:space="preserve">Pay &To:</source> - <context-group purpose="location"><context context-type="linenumber">39</context></context-group> + <context-group purpose="location"><context context-type="linenumber">35</context></context-group> </trans-unit> - <trans-unit id="_msg739"> + <trans-unit id="_msg763"> <source xml:space="preserve">&Label:</source> - <context-group purpose="location"><context context-type="linenumber">132</context></context-group> + <context-group purpose="location"><context context-type="linenumber">128</context></context-group> </trans-unit> - <trans-unit id="_msg740"> + <trans-unit id="_msg764"> <source xml:space="preserve">Choose previously used address</source> - <context-group purpose="location"><context context-type="linenumber">64</context></context-group> + <context-group purpose="location"><context context-type="linenumber">60</context></context-group> </trans-unit> - <trans-unit id="_msg741"> + <trans-unit id="_msg765"> <source xml:space="preserve">The Bitcoin address to send the payment to</source> - <context-group purpose="location"><context context-type="linenumber">57</context></context-group> + <context-group purpose="location"><context context-type="linenumber">53</context></context-group> </trans-unit> - <trans-unit id="_msg742"> + <trans-unit id="_msg766"> <source xml:space="preserve">Alt+A</source> - <context-group purpose="location"><context context-type="linenumber">80</context></context-group> + <context-group purpose="location"><context context-type="linenumber">76</context></context-group> </trans-unit> - <trans-unit id="_msg743"> + <trans-unit id="_msg767"> <source xml:space="preserve">Paste address from clipboard</source> - <context-group purpose="location"><context context-type="linenumber">87</context></context-group> + <context-group purpose="location"><context context-type="linenumber">83</context></context-group> </trans-unit> - <trans-unit id="_msg744"> + <trans-unit id="_msg768"> <source xml:space="preserve">Alt+P</source> - <context-group purpose="location"><context context-type="linenumber">103</context></context-group> + <context-group purpose="location"><context context-type="linenumber">99</context></context-group> </trans-unit> - <trans-unit id="_msg745"> + <trans-unit id="_msg769"> <source xml:space="preserve">Remove this entry</source> - <context-group purpose="location"><context context-type="linenumber">110</context></context-group> - <context-group purpose="location"><context context-type="linenumber">672</context></context-group> - <context-group purpose="location"><context context-type="linenumber">1205</context></context-group> + <context-group purpose="location"><context context-type="linenumber">106</context></context-group> </trans-unit> - <trans-unit id="_msg746"> + <trans-unit id="_msg770"> <source xml:space="preserve">The amount to send in the selected unit</source> - <context-group purpose="location"><context context-type="linenumber">170</context></context-group> + <context-group purpose="location"><context context-type="linenumber">166</context></context-group> </trans-unit> - <trans-unit id="_msg747"> + <trans-unit id="_msg771"> <source xml:space="preserve">The fee will be deducted from the amount being sent. The recipient will receive less bitcoins than you enter in the amount field. If multiple recipients are selected, the fee is split equally.</source> - <context-group purpose="location"><context context-type="linenumber">177</context></context-group> + <context-group purpose="location"><context context-type="linenumber">173</context></context-group> </trans-unit> - <trans-unit id="_msg748"> + <trans-unit id="_msg772"> <source xml:space="preserve">S&ubtract fee from amount</source> - <context-group purpose="location"><context context-type="linenumber">180</context></context-group> + <context-group purpose="location"><context context-type="linenumber">176</context></context-group> </trans-unit> - <trans-unit id="_msg749"> + <trans-unit id="_msg773"> <source xml:space="preserve">Use available balance</source> - <context-group purpose="location"><context context-type="linenumber">187</context></context-group> + <context-group purpose="location"><context context-type="linenumber">183</context></context-group> </trans-unit> - <trans-unit id="_msg750"> + <trans-unit id="_msg774"> <source xml:space="preserve">Message:</source> - <context-group purpose="location"><context context-type="linenumber">196</context></context-group> - </trans-unit> - <trans-unit id="_msg751"> - <source xml:space="preserve">This is an unauthenticated payment request.</source> - <context-group purpose="location"><context context-type="linenumber">639</context></context-group> - </trans-unit> - <trans-unit id="_msg752"> - <source xml:space="preserve">This is an authenticated payment request.</source> - <context-group purpose="location"><context context-type="linenumber">1168</context></context-group> + <context-group purpose="location"><context context-type="linenumber">192</context></context-group> </trans-unit> - <trans-unit id="_msg753"> + <trans-unit id="_msg775"> <source xml:space="preserve">Enter a label for this address to add it to the list of used addresses</source> - <context-group purpose="location"><context context-type="linenumber">145</context></context-group> - <context-group purpose="location"><context context-type="linenumber">148</context></context-group> + <context-group purpose="location"><context context-type="linenumber">141</context></context-group> + <context-group purpose="location"><context context-type="linenumber">144</context></context-group> </trans-unit> - <trans-unit id="_msg754"> + <trans-unit id="_msg776"> <source xml:space="preserve">A message that was attached to the bitcoin: URI which will be stored with the transaction for your reference. Note: This message will not be sent over the Bitcoin network.</source> - <context-group purpose="location"><context context-type="linenumber">206</context></context-group> - </trans-unit> - <trans-unit id="_msg755"> - <source xml:space="preserve">Pay To:</source> - <context-group purpose="location"><context context-type="linenumber">654</context></context-group> - <context-group purpose="location"><context context-type="linenumber">1183</context></context-group> - </trans-unit> - <trans-unit id="_msg756"> - <source xml:space="preserve">Memo:</source> - <context-group purpose="location"><context context-type="linenumber">688</context></context-group> - <context-group purpose="location"><context context-type="linenumber">1221</context></context-group> + <context-group purpose="location"><context context-type="linenumber">202</context></context-group> </trans-unit> </group> </body></file> <file original="../sendcoinsdialog.h" datatype="c" source-language="en"><body> <group restype="x-trolltech-linguist-context" resname="SendConfirmationDialog"> - <trans-unit id="_msg757"> + <trans-unit id="_msg777"> <source xml:space="preserve">Send</source> - <context-group purpose="location"><context context-type="linenumber">131</context></context-group> + <context-group purpose="location"><context context-type="linenumber">144</context></context-group> </trans-unit> - <trans-unit id="_msg758"> + <trans-unit id="_msg778"> <source xml:space="preserve">Create Unsigned</source> - <context-group purpose="location"><context context-type="linenumber">133</context></context-group> + <context-group purpose="location"><context context-type="linenumber">146</context></context-group> </trans-unit> </group> </body></file> <file original="../forms/signverifymessagedialog.ui" datatype="x-trolltech-designer-ui" source-language="en"><body> <group restype="x-trolltech-linguist-context" resname="SignVerifyMessageDialog"> - <trans-unit id="_msg759"> + <trans-unit id="_msg779"> <source xml:space="preserve">Signatures - Sign / Verify a Message</source> <context-group purpose="location"><context context-type="linenumber">14</context></context-group> </trans-unit> - <trans-unit id="_msg760"> + <trans-unit id="_msg780"> <source xml:space="preserve">&Sign Message</source> <context-group purpose="location"><context context-type="linenumber">27</context></context-group> </trans-unit> - <trans-unit id="_msg761"> + <trans-unit id="_msg781"> <source xml:space="preserve">You can sign messages/agreements with your addresses to prove you can receive bitcoins sent to them. Be careful not to sign anything vague or random, as phishing attacks may try to trick you into signing your identity over to them. Only sign fully-detailed statements you agree to.</source> <context-group purpose="location"><context context-type="linenumber">33</context></context-group> </trans-unit> - <trans-unit id="_msg762"> + <trans-unit id="_msg782"> <source xml:space="preserve">The Bitcoin address to sign the message with</source> <context-group purpose="location"><context context-type="linenumber">51</context></context-group> </trans-unit> - <trans-unit id="_msg763"> + <trans-unit id="_msg783"> <source xml:space="preserve">Choose previously used address</source> <context-group purpose="location"><context context-type="linenumber">58</context></context-group> <context-group purpose="location"><context context-type="linenumber">274</context></context-group> </trans-unit> - <trans-unit id="_msg764"> + <trans-unit id="_msg784"> <source xml:space="preserve">Alt+A</source> <context-group purpose="location"><context context-type="linenumber">68</context></context-group> <context-group purpose="location"><context context-type="linenumber">284</context></context-group> </trans-unit> - <trans-unit id="_msg765"> + <trans-unit id="_msg785"> <source xml:space="preserve">Paste address from clipboard</source> <context-group purpose="location"><context context-type="linenumber">78</context></context-group> </trans-unit> - <trans-unit id="_msg766"> + <trans-unit id="_msg786"> <source xml:space="preserve">Alt+P</source> <context-group purpose="location"><context context-type="linenumber">88</context></context-group> </trans-unit> - <trans-unit id="_msg767"> + <trans-unit id="_msg787"> <source xml:space="preserve">Enter the message you want to sign here</source> <context-group purpose="location"><context context-type="linenumber">100</context></context-group> <context-group purpose="location"><context context-type="linenumber">103</context></context-group> </trans-unit> - <trans-unit id="_msg768"> + <trans-unit id="_msg788"> <source xml:space="preserve">Signature</source> <context-group purpose="location"><context context-type="linenumber">110</context></context-group> </trans-unit> - <trans-unit id="_msg769"> + <trans-unit id="_msg789"> <source xml:space="preserve">Copy the current signature to the system clipboard</source> <context-group purpose="location"><context context-type="linenumber">140</context></context-group> </trans-unit> - <trans-unit id="_msg770"> + <trans-unit id="_msg790"> <source xml:space="preserve">Sign the message to prove you own this Bitcoin address</source> <context-group purpose="location"><context context-type="linenumber">161</context></context-group> </trans-unit> - <trans-unit id="_msg771"> + <trans-unit id="_msg791"> <source xml:space="preserve">Sign &Message</source> <context-group purpose="location"><context context-type="linenumber">164</context></context-group> </trans-unit> - <trans-unit id="_msg772"> + <trans-unit id="_msg792"> <source xml:space="preserve">Reset all sign message fields</source> <context-group purpose="location"><context context-type="linenumber">178</context></context-group> </trans-unit> - <trans-unit id="_msg773"> + <trans-unit id="_msg793"> <source xml:space="preserve">Clear &All</source> <context-group purpose="location"><context context-type="linenumber">181</context></context-group> <context-group purpose="location"><context context-type="linenumber">338</context></context-group> </trans-unit> - <trans-unit id="_msg774"> + <trans-unit id="_msg794"> <source xml:space="preserve">&Verify Message</source> <context-group purpose="location"><context context-type="linenumber">240</context></context-group> </trans-unit> - <trans-unit id="_msg775"> + <trans-unit id="_msg795"> <source xml:space="preserve">Enter the receiver's address, message (ensure you copy line breaks, spaces, tabs, etc. exactly) and signature below to verify the message. Be careful not to read more into the signature than what is in the signed message itself, to avoid being tricked by a man-in-the-middle attack. Note that this only proves the signing party receives with the address, it cannot prove sendership of any transaction!</source> <context-group purpose="location"><context context-type="linenumber">246</context></context-group> </trans-unit> - <trans-unit id="_msg776"> + <trans-unit id="_msg796"> <source xml:space="preserve">The Bitcoin address the message was signed with</source> <context-group purpose="location"><context context-type="linenumber">267</context></context-group> </trans-unit> - <trans-unit id="_msg777"> + <trans-unit id="_msg797"> <source xml:space="preserve">The signed message to verify</source> <context-group purpose="location"><context context-type="linenumber">296</context></context-group> <context-group purpose="location"><context context-type="linenumber">299</context></context-group> </trans-unit> - <trans-unit id="_msg778"> + <trans-unit id="_msg798"> <source xml:space="preserve">The signature given when the message was signed</source> <context-group purpose="location"><context context-type="linenumber">306</context></context-group> <context-group purpose="location"><context context-type="linenumber">309</context></context-group> </trans-unit> - <trans-unit id="_msg779"> + <trans-unit id="_msg799"> <source xml:space="preserve">Verify the message to ensure it was signed with the specified Bitcoin address</source> <context-group purpose="location"><context context-type="linenumber">318</context></context-group> </trans-unit> - <trans-unit id="_msg780"> + <trans-unit id="_msg800"> <source xml:space="preserve">Verify &Message</source> <context-group purpose="location"><context context-type="linenumber">321</context></context-group> </trans-unit> - <trans-unit id="_msg781"> + <trans-unit id="_msg801"> <source xml:space="preserve">Reset all verify message fields</source> <context-group purpose="location"><context context-type="linenumber">335</context></context-group> </trans-unit> - <trans-unit id="_msg782"> + <trans-unit id="_msg802"> <source xml:space="preserve">Click "Sign Message" to generate signature</source> <context-group purpose="location"><context context-type="linenumber">125</context></context-group> </trans-unit> @@ -3561,61 +3679,61 @@ Note: Since the fee is calculated on a per-byte basis, a fee rate of "100 </body></file> <file original="../signverifymessagedialog.cpp" datatype="cpp" source-language="en"><body> <group restype="x-trolltech-linguist-context" resname="SignVerifyMessageDialog"> - <trans-unit id="_msg783"> + <trans-unit id="_msg803"> <source xml:space="preserve">The entered address is invalid.</source> <context-group purpose="location"><context context-type="linenumber">120</context></context-group> <context-group purpose="location"><context context-type="linenumber">219</context></context-group> </trans-unit> - <trans-unit id="_msg784"> + <trans-unit id="_msg804"> <source xml:space="preserve">Please check the address and try again.</source> <context-group purpose="location"><context context-type="linenumber">120</context></context-group> <context-group purpose="location"><context context-type="linenumber">127</context></context-group> <context-group purpose="location"><context context-type="linenumber">220</context></context-group> <context-group purpose="location"><context context-type="linenumber">227</context></context-group> </trans-unit> - <trans-unit id="_msg785"> + <trans-unit id="_msg805"> <source xml:space="preserve">The entered address does not refer to a key.</source> <context-group purpose="location"><context context-type="linenumber">127</context></context-group> <context-group purpose="location"><context context-type="linenumber">226</context></context-group> </trans-unit> - <trans-unit id="_msg786"> + <trans-unit id="_msg806"> <source xml:space="preserve">Wallet unlock was cancelled.</source> <context-group purpose="location"><context context-type="linenumber">135</context></context-group> </trans-unit> - <trans-unit id="_msg787"> + <trans-unit id="_msg807"> <source xml:space="preserve">No error</source> <context-group purpose="location"><context context-type="linenumber">146</context></context-group> </trans-unit> - <trans-unit id="_msg788"> + <trans-unit id="_msg808"> <source xml:space="preserve">Private key for the entered address is not available.</source> <context-group purpose="location"><context context-type="linenumber">149</context></context-group> </trans-unit> - <trans-unit id="_msg789"> + <trans-unit id="_msg809"> <source xml:space="preserve">Message signing failed.</source> <context-group purpose="location"><context context-type="linenumber">152</context></context-group> </trans-unit> - <trans-unit id="_msg790"> + <trans-unit id="_msg810"> <source xml:space="preserve">Message signed.</source> <context-group purpose="location"><context context-type="linenumber">164</context></context-group> </trans-unit> - <trans-unit id="_msg791"> + <trans-unit id="_msg811"> <source xml:space="preserve">The signature could not be decoded.</source> <context-group purpose="location"><context context-type="linenumber">233</context></context-group> </trans-unit> - <trans-unit id="_msg792"> + <trans-unit id="_msg812"> <source xml:space="preserve">Please check the signature and try again.</source> <context-group purpose="location"><context context-type="linenumber">234</context></context-group> <context-group purpose="location"><context context-type="linenumber">241</context></context-group> </trans-unit> - <trans-unit id="_msg793"> + <trans-unit id="_msg813"> <source xml:space="preserve">The signature did not match the message digest.</source> <context-group purpose="location"><context context-type="linenumber">240</context></context-group> </trans-unit> - <trans-unit id="_msg794"> + <trans-unit id="_msg814"> <source xml:space="preserve">Message verification failed.</source> <context-group purpose="location"><context context-type="linenumber">246</context></context-group> </trans-unit> - <trans-unit id="_msg795"> + <trans-unit id="_msg815"> <source xml:space="preserve">Message verified.</source> <context-group purpose="location"><context context-type="linenumber">214</context></context-group> </trans-unit> @@ -3623,11 +3741,11 @@ Note: Since the fee is calculated on a per-byte basis, a fee rate of "100 </body></file> <file original="../splashscreen.cpp" datatype="cpp" source-language="en"><body> <group restype="x-trolltech-linguist-context" resname="SplashScreen"> - <trans-unit id="_msg796"> + <trans-unit id="_msg816"> <source xml:space="preserve">(press q to shutdown and continue later)</source> <context-group purpose="location"><context context-type="linenumber">187</context></context-group> </trans-unit> - <trans-unit id="_msg797"> + <trans-unit id="_msg817"> <source xml:space="preserve">press q to shutdown</source> <context-group purpose="location"><context context-type="linenumber">188</context></context-group> </trans-unit> @@ -3635,7 +3753,7 @@ Note: Since the fee is calculated on a per-byte basis, a fee rate of "100 </body></file> <file original="../trafficgraphwidget.cpp" datatype="cpp" source-language="en"><body> <group restype="x-trolltech-linguist-context" resname="TrafficGraphWidget"> - <trans-unit id="_msg798"> + <trans-unit id="_msg818"> <source xml:space="preserve">kB/s</source> <context-group purpose="location"><context context-type="linenumber">79</context></context-group> </trans-unit> @@ -3643,190 +3761,192 @@ Note: Since the fee is calculated on a per-byte basis, a fee rate of "100 </body></file> <file original="../transactiondesc.cpp" datatype="cpp" source-language="en"><body> <group restype="x-trolltech-linguist-context" resname="TransactionDesc"> - <trans-unit id="_msg799"> + <trans-unit id="_msg819"> <source xml:space="preserve">conflicted with a transaction with %1 confirmations</source> - <context-group purpose="location"><context context-type="linenumber">40</context></context-group> - </trans-unit> - <trans-unit id="_msg800"> - <source xml:space="preserve">0/unconfirmed, %1</source> <context-group purpose="location"><context context-type="linenumber">43</context></context-group> + <note annotates="source" from="developer">Text explaining the current status of a transaction, shown in the status field of the details window for this transaction. This status represents an unconfirmed transaction that conflicts with a confirmed transaction.</note> </trans-unit> - <trans-unit id="_msg801"> - <source xml:space="preserve">in memory pool</source> - <context-group purpose="location"><context context-type="linenumber">43</context></context-group> + <trans-unit id="_msg820"> + <source xml:space="preserve">0/unconfirmed, in memory pool</source> + <context-group purpose="location"><context context-type="linenumber">50</context></context-group> + <note annotates="source" from="developer">Text explaining the current status of a transaction, shown in the status field of the details window for this transaction. This status represents an unconfirmed transaction that is in the memory pool.</note> </trans-unit> - <trans-unit id="_msg802"> - <source xml:space="preserve">not in memory pool</source> - <context-group purpose="location"><context context-type="linenumber">43</context></context-group> + <trans-unit id="_msg821"> + <source xml:space="preserve">0/unconfirmed, not in memory pool</source> + <context-group purpose="location"><context context-type="linenumber">55</context></context-group> + <note annotates="source" from="developer">Text explaining the current status of a transaction, shown in the status field of the details window for this transaction. This status represents an unconfirmed transaction that is not in the memory pool.</note> </trans-unit> - <trans-unit id="_msg803"> + <trans-unit id="_msg822"> <source xml:space="preserve">abandoned</source> - <context-group purpose="location"><context context-type="linenumber">42</context></context-group> + <context-group purpose="location"><context context-type="linenumber">61</context></context-group> + <note annotates="source" from="developer">Text explaining the current status of a transaction, shown in the status field of the details window for this transaction. This status represents an abandoned transaction.</note> </trans-unit> - <trans-unit id="_msg804"> + <trans-unit id="_msg823"> <source xml:space="preserve">%1/unconfirmed</source> - <context-group purpose="location"><context context-type="linenumber">45</context></context-group> + <context-group purpose="location"><context context-type="linenumber">69</context></context-group> + <note annotates="source" from="developer">Text explaining the current status of a transaction, shown in the status field of the details window for this transaction. This status represents a transaction confirmed in at least one block, but less than 6 blocks.</note> </trans-unit> - <trans-unit id="_msg805"> + <trans-unit id="_msg824"> <source xml:space="preserve">%1 confirmations</source> - <context-group purpose="location"><context context-type="linenumber">47</context></context-group> + <context-group purpose="location"><context context-type="linenumber">74</context></context-group> + <note annotates="source" from="developer">Text explaining the current status of a transaction, shown in the status field of the details window for this transaction. This status represents a transaction confirmed in 6 or more blocks.</note> </trans-unit> - <trans-unit id="_msg806"> + <trans-unit id="_msg825"> <source xml:space="preserve">Status</source> - <context-group purpose="location"><context context-type="linenumber">98</context></context-group> + <context-group purpose="location"><context context-type="linenumber">124</context></context-group> </trans-unit> - <trans-unit id="_msg807"> + <trans-unit id="_msg826"> <source xml:space="preserve">Date</source> - <context-group purpose="location"><context context-type="linenumber">101</context></context-group> + <context-group purpose="location"><context context-type="linenumber">127</context></context-group> </trans-unit> - <trans-unit id="_msg808"> + <trans-unit id="_msg827"> <source xml:space="preserve">Source</source> - <context-group purpose="location"><context context-type="linenumber">108</context></context-group> + <context-group purpose="location"><context context-type="linenumber">134</context></context-group> </trans-unit> - <trans-unit id="_msg809"> + <trans-unit id="_msg828"> <source xml:space="preserve">Generated</source> - <context-group purpose="location"><context context-type="linenumber">108</context></context-group> + <context-group purpose="location"><context context-type="linenumber">134</context></context-group> </trans-unit> - <trans-unit id="_msg810"> + <trans-unit id="_msg829"> <source xml:space="preserve">From</source> - <context-group purpose="location"><context context-type="linenumber">113</context></context-group> - <context-group purpose="location"><context context-type="linenumber">127</context></context-group> - <context-group purpose="location"><context context-type="linenumber">199</context></context-group> + <context-group purpose="location"><context context-type="linenumber">139</context></context-group> + <context-group purpose="location"><context context-type="linenumber">153</context></context-group> + <context-group purpose="location"><context context-type="linenumber">225</context></context-group> </trans-unit> - <trans-unit id="_msg811"> + <trans-unit id="_msg830"> <source xml:space="preserve">unknown</source> - <context-group purpose="location"><context context-type="linenumber">127</context></context-group> + <context-group purpose="location"><context context-type="linenumber">153</context></context-group> </trans-unit> - <trans-unit id="_msg812"> + <trans-unit id="_msg831"> <source xml:space="preserve">To</source> - <context-group purpose="location"><context context-type="linenumber">128</context></context-group> - <context-group purpose="location"><context context-type="linenumber">148</context></context-group> - <context-group purpose="location"><context context-type="linenumber">218</context></context-group> + <context-group purpose="location"><context context-type="linenumber">154</context></context-group> + <context-group purpose="location"><context context-type="linenumber">174</context></context-group> + <context-group purpose="location"><context context-type="linenumber">244</context></context-group> </trans-unit> - <trans-unit id="_msg813"> + <trans-unit id="_msg832"> <source xml:space="preserve">own address</source> - <context-group purpose="location"><context context-type="linenumber">130</context></context-group> + <context-group purpose="location"><context context-type="linenumber">156</context></context-group> </trans-unit> - <trans-unit id="_msg814"> + <trans-unit id="_msg833"> <source xml:space="preserve">watch-only</source> - <context-group purpose="location"><context context-type="linenumber">130</context></context-group> - <context-group purpose="location"><context context-type="linenumber">199</context></context-group> + <context-group purpose="location"><context context-type="linenumber">156</context></context-group> + <context-group purpose="location"><context context-type="linenumber">225</context></context-group> </trans-unit> - <trans-unit id="_msg815"> + <trans-unit id="_msg834"> <source xml:space="preserve">label</source> - <context-group purpose="location"><context context-type="linenumber">132</context></context-group> + <context-group purpose="location"><context context-type="linenumber">158</context></context-group> </trans-unit> - <trans-unit id="_msg816"> + <trans-unit id="_msg835"> <source xml:space="preserve">Credit</source> - <context-group purpose="location"><context context-type="linenumber">168</context></context-group> - <context-group purpose="location"><context context-type="linenumber">180</context></context-group> - <context-group purpose="location"><context context-type="linenumber">234</context></context-group> - <context-group purpose="location"><context context-type="linenumber">264</context></context-group> - <context-group purpose="location"><context context-type="linenumber">324</context></context-group> + <context-group purpose="location"><context context-type="linenumber">194</context></context-group> + <context-group purpose="location"><context context-type="linenumber">206</context></context-group> + <context-group purpose="location"><context context-type="linenumber">260</context></context-group> + <context-group purpose="location"><context context-type="linenumber">290</context></context-group> + <context-group purpose="location"><context context-type="linenumber">350</context></context-group> </trans-unit> <group restype="x-gettext-plurals"> - <context-group purpose="location"><context context-type="linenumber">170</context></context-group> - <trans-unit id="_msg817[0]"> + <context-group purpose="location"><context context-type="linenumber">196</context></context-group> + <trans-unit id="_msg836[0]"> <source xml:space="preserve">matures in %n more block(s)</source> </trans-unit> - <trans-unit id="_msg817[1]"> + <trans-unit id="_msg836[1]"> <source xml:space="preserve">matures in %n more block(s)</source> </trans-unit> </group> - <trans-unit id="_msg818"> + <trans-unit id="_msg837"> <source xml:space="preserve">not accepted</source> - <context-group purpose="location"><context context-type="linenumber">172</context></context-group> + <context-group purpose="location"><context context-type="linenumber">198</context></context-group> </trans-unit> - <trans-unit id="_msg819"> + <trans-unit id="_msg838"> <source xml:space="preserve">Debit</source> - <context-group purpose="location"><context context-type="linenumber">232</context></context-group> <context-group purpose="location"><context context-type="linenumber">258</context></context-group> - <context-group purpose="location"><context context-type="linenumber">321</context></context-group> + <context-group purpose="location"><context context-type="linenumber">284</context></context-group> + <context-group purpose="location"><context context-type="linenumber">347</context></context-group> </trans-unit> - <trans-unit id="_msg820"> + <trans-unit id="_msg839"> <source xml:space="preserve">Total debit</source> - <context-group purpose="location"><context context-type="linenumber">242</context></context-group> + <context-group purpose="location"><context context-type="linenumber">268</context></context-group> </trans-unit> - <trans-unit id="_msg821"> + <trans-unit id="_msg840"> <source xml:space="preserve">Total credit</source> - <context-group purpose="location"><context context-type="linenumber">243</context></context-group> + <context-group purpose="location"><context context-type="linenumber">269</context></context-group> </trans-unit> - <trans-unit id="_msg822"> + <trans-unit id="_msg841"> <source xml:space="preserve">Transaction fee</source> - <context-group purpose="location"><context context-type="linenumber">248</context></context-group> + <context-group purpose="location"><context context-type="linenumber">274</context></context-group> </trans-unit> - <trans-unit id="_msg823"> + <trans-unit id="_msg842"> <source xml:space="preserve">Net amount</source> - <context-group purpose="location"><context context-type="linenumber">270</context></context-group> + <context-group purpose="location"><context context-type="linenumber">296</context></context-group> </trans-unit> - <trans-unit id="_msg824"> + <trans-unit id="_msg843"> <source xml:space="preserve">Message</source> - <context-group purpose="location"><context context-type="linenumber">276</context></context-group> - <context-group purpose="location"><context context-type="linenumber">288</context></context-group> + <context-group purpose="location"><context context-type="linenumber">302</context></context-group> + <context-group purpose="location"><context context-type="linenumber">314</context></context-group> </trans-unit> - <trans-unit id="_msg825"> + <trans-unit id="_msg844"> <source xml:space="preserve">Comment</source> - <context-group purpose="location"><context context-type="linenumber">278</context></context-group> + <context-group purpose="location"><context context-type="linenumber">304</context></context-group> </trans-unit> - <trans-unit id="_msg826"> + <trans-unit id="_msg845"> <source xml:space="preserve">Transaction ID</source> - <context-group purpose="location"><context context-type="linenumber">280</context></context-group> + <context-group purpose="location"><context context-type="linenumber">306</context></context-group> </trans-unit> - <trans-unit id="_msg827"> + <trans-unit id="_msg846"> <source xml:space="preserve">Transaction total size</source> - <context-group purpose="location"><context context-type="linenumber">281</context></context-group> + <context-group purpose="location"><context context-type="linenumber">307</context></context-group> </trans-unit> - <trans-unit id="_msg828"> + <trans-unit id="_msg847"> <source xml:space="preserve">Transaction virtual size</source> - <context-group purpose="location"><context context-type="linenumber">282</context></context-group> + <context-group purpose="location"><context context-type="linenumber">308</context></context-group> </trans-unit> - <trans-unit id="_msg829"> + <trans-unit id="_msg848"> <source xml:space="preserve">Output index</source> - <context-group purpose="location"><context context-type="linenumber">283</context></context-group> + <context-group purpose="location"><context context-type="linenumber">309</context></context-group> </trans-unit> - <trans-unit id="_msg830"> + <trans-unit id="_msg849"> <source xml:space="preserve"> (Certificate was not verified)</source> - <context-group purpose="location"><context context-type="linenumber">299</context></context-group> + <context-group purpose="location"><context context-type="linenumber">325</context></context-group> </trans-unit> - <trans-unit id="_msg831"> + <trans-unit id="_msg850"> <source xml:space="preserve">Merchant</source> - <context-group purpose="location"><context context-type="linenumber">302</context></context-group> + <context-group purpose="location"><context context-type="linenumber">328</context></context-group> </trans-unit> - <trans-unit id="_msg832"> + <trans-unit id="_msg851"> <source xml:space="preserve">Generated coins must mature %1 blocks before they can be spent. When you generated this block, it was broadcast to the network to be added to the block chain. If it fails to get into the chain, its state will change to "not accepted" and it won't be spendable. This may occasionally happen if another node generates a block within a few seconds of yours.</source> - <context-group purpose="location"><context context-type="linenumber">310</context></context-group> + <context-group purpose="location"><context context-type="linenumber">336</context></context-group> </trans-unit> - <trans-unit id="_msg833"> + <trans-unit id="_msg852"> <source xml:space="preserve">Debug information</source> - <context-group purpose="location"><context context-type="linenumber">318</context></context-group> + <context-group purpose="location"><context context-type="linenumber">344</context></context-group> </trans-unit> - <trans-unit id="_msg834"> + <trans-unit id="_msg853"> <source xml:space="preserve">Transaction</source> - <context-group purpose="location"><context context-type="linenumber">326</context></context-group> + <context-group purpose="location"><context context-type="linenumber">352</context></context-group> </trans-unit> - <trans-unit id="_msg835"> + <trans-unit id="_msg854"> <source xml:space="preserve">Inputs</source> - <context-group purpose="location"><context context-type="linenumber">329</context></context-group> + <context-group purpose="location"><context context-type="linenumber">355</context></context-group> </trans-unit> - <trans-unit id="_msg836"> + <trans-unit id="_msg855"> <source xml:space="preserve">Amount</source> - <context-group purpose="location"><context context-type="linenumber">350</context></context-group> + <context-group purpose="location"><context context-type="linenumber">376</context></context-group> </trans-unit> - <trans-unit id="_msg837"> + <trans-unit id="_msg856"> <source xml:space="preserve">true</source> - <context-group purpose="location"><context context-type="linenumber">351</context></context-group> - <context-group purpose="location"><context context-type="linenumber">352</context></context-group> + <context-group purpose="location"><context context-type="linenumber">377</context></context-group> + <context-group purpose="location"><context context-type="linenumber">378</context></context-group> </trans-unit> - <trans-unit id="_msg838"> + <trans-unit id="_msg857"> <source xml:space="preserve">false</source> - <context-group purpose="location"><context context-type="linenumber">351</context></context-group> - <context-group purpose="location"><context context-type="linenumber">352</context></context-group> + <context-group purpose="location"><context context-type="linenumber">377</context></context-group> + <context-group purpose="location"><context context-type="linenumber">378</context></context-group> </trans-unit> </group> </body></file> <file original="../forms/transactiondescdialog.ui" datatype="x-trolltech-designer-ui" source-language="en"><body> <group restype="x-trolltech-linguist-context" resname="TransactionDescDialog"> - <trans-unit id="_msg839"> + <trans-unit id="_msg858"> <source xml:space="preserve">This pane shows a detailed description of the transaction</source> <context-group purpose="location"><context context-type="linenumber">20</context></context-group> </trans-unit> @@ -3834,7 +3954,7 @@ Note: Since the fee is calculated on a per-byte basis, a fee rate of "100 </body></file> <file original="../transactiondescdialog.cpp" datatype="cpp" source-language="en"><body> <group restype="x-trolltech-linguist-context" resname="TransactionDescDialog"> - <trans-unit id="_msg840"> + <trans-unit id="_msg859"> <source xml:space="preserve">Details for %1</source> <context-group purpose="location"><context context-type="linenumber">18</context></context-group> </trans-unit> @@ -3842,266 +3962,266 @@ Note: Since the fee is calculated on a per-byte basis, a fee rate of "100 </body></file> <file original="../transactiontablemodel.cpp" datatype="cpp" source-language="en"><body> <group restype="x-trolltech-linguist-context" resname="TransactionTableModel"> - <trans-unit id="_msg841"> + <trans-unit id="_msg860"> <source xml:space="preserve">Date</source> - <context-group purpose="location"><context context-type="linenumber">260</context></context-group> + <context-group purpose="location"><context context-type="linenumber">261</context></context-group> </trans-unit> - <trans-unit id="_msg842"> + <trans-unit id="_msg861"> <source xml:space="preserve">Type</source> - <context-group purpose="location"><context context-type="linenumber">260</context></context-group> + <context-group purpose="location"><context context-type="linenumber">261</context></context-group> </trans-unit> - <trans-unit id="_msg843"> + <trans-unit id="_msg862"> <source xml:space="preserve">Label</source> - <context-group purpose="location"><context context-type="linenumber">260</context></context-group> + <context-group purpose="location"><context context-type="linenumber">261</context></context-group> </trans-unit> - <trans-unit id="_msg844"> + <trans-unit id="_msg863"> <source xml:space="preserve">Unconfirmed</source> - <context-group purpose="location"><context context-type="linenumber">320</context></context-group> + <context-group purpose="location"><context context-type="linenumber">321</context></context-group> </trans-unit> - <trans-unit id="_msg845"> + <trans-unit id="_msg864"> <source xml:space="preserve">Abandoned</source> - <context-group purpose="location"><context context-type="linenumber">323</context></context-group> + <context-group purpose="location"><context context-type="linenumber">324</context></context-group> </trans-unit> - <trans-unit id="_msg846"> + <trans-unit id="_msg865"> <source xml:space="preserve">Confirming (%1 of %2 recommended confirmations)</source> - <context-group purpose="location"><context context-type="linenumber">326</context></context-group> + <context-group purpose="location"><context context-type="linenumber">327</context></context-group> </trans-unit> - <trans-unit id="_msg847"> + <trans-unit id="_msg866"> <source xml:space="preserve">Confirmed (%1 confirmations)</source> - <context-group purpose="location"><context context-type="linenumber">329</context></context-group> + <context-group purpose="location"><context context-type="linenumber">330</context></context-group> </trans-unit> - <trans-unit id="_msg848"> + <trans-unit id="_msg867"> <source xml:space="preserve">Conflicted</source> - <context-group purpose="location"><context context-type="linenumber">332</context></context-group> + <context-group purpose="location"><context context-type="linenumber">333</context></context-group> </trans-unit> - <trans-unit id="_msg849"> + <trans-unit id="_msg868"> <source xml:space="preserve">Immature (%1 confirmations, will be available after %2)</source> - <context-group purpose="location"><context context-type="linenumber">335</context></context-group> + <context-group purpose="location"><context context-type="linenumber">336</context></context-group> </trans-unit> - <trans-unit id="_msg850"> + <trans-unit id="_msg869"> <source xml:space="preserve">Generated but not accepted</source> - <context-group purpose="location"><context context-type="linenumber">338</context></context-group> + <context-group purpose="location"><context context-type="linenumber">339</context></context-group> </trans-unit> - <trans-unit id="_msg851"> + <trans-unit id="_msg870"> <source xml:space="preserve">Received with</source> - <context-group purpose="location"><context context-type="linenumber">377</context></context-group> + <context-group purpose="location"><context context-type="linenumber">378</context></context-group> </trans-unit> - <trans-unit id="_msg852"> + <trans-unit id="_msg871"> <source xml:space="preserve">Received from</source> - <context-group purpose="location"><context context-type="linenumber">379</context></context-group> + <context-group purpose="location"><context context-type="linenumber">380</context></context-group> </trans-unit> - <trans-unit id="_msg853"> + <trans-unit id="_msg872"> <source xml:space="preserve">Sent to</source> - <context-group purpose="location"><context context-type="linenumber">382</context></context-group> + <context-group purpose="location"><context context-type="linenumber">383</context></context-group> </trans-unit> - <trans-unit id="_msg854"> + <trans-unit id="_msg873"> <source xml:space="preserve">Payment to yourself</source> - <context-group purpose="location"><context context-type="linenumber">384</context></context-group> + <context-group purpose="location"><context context-type="linenumber">385</context></context-group> </trans-unit> - <trans-unit id="_msg855"> + <trans-unit id="_msg874"> <source xml:space="preserve">Mined</source> - <context-group purpose="location"><context context-type="linenumber">386</context></context-group> + <context-group purpose="location"><context context-type="linenumber">387</context></context-group> </trans-unit> - <trans-unit id="_msg856"> + <trans-unit id="_msg875"> <source xml:space="preserve">watch-only</source> - <context-group purpose="location"><context context-type="linenumber">414</context></context-group> + <context-group purpose="location"><context context-type="linenumber">415</context></context-group> </trans-unit> - <trans-unit id="_msg857"> + <trans-unit id="_msg876"> <source xml:space="preserve">(n/a)</source> - <context-group purpose="location"><context context-type="linenumber">430</context></context-group> + <context-group purpose="location"><context context-type="linenumber">431</context></context-group> </trans-unit> - <trans-unit id="_msg858"> + <trans-unit id="_msg877"> <source xml:space="preserve">(no label)</source> - <context-group purpose="location"><context context-type="linenumber">637</context></context-group> + <context-group purpose="location"><context context-type="linenumber">638</context></context-group> </trans-unit> - <trans-unit id="_msg859"> + <trans-unit id="_msg878"> <source xml:space="preserve">Transaction status. Hover over this field to show number of confirmations.</source> - <context-group purpose="location"><context context-type="linenumber">676</context></context-group> + <context-group purpose="location"><context context-type="linenumber">677</context></context-group> </trans-unit> - <trans-unit id="_msg860"> + <trans-unit id="_msg879"> <source xml:space="preserve">Date and time that the transaction was received.</source> - <context-group purpose="location"><context context-type="linenumber">678</context></context-group> + <context-group purpose="location"><context context-type="linenumber">679</context></context-group> </trans-unit> - <trans-unit id="_msg861"> + <trans-unit id="_msg880"> <source xml:space="preserve">Type of transaction.</source> - <context-group purpose="location"><context context-type="linenumber">680</context></context-group> + <context-group purpose="location"><context context-type="linenumber">681</context></context-group> </trans-unit> - <trans-unit id="_msg862"> + <trans-unit id="_msg881"> <source xml:space="preserve">Whether or not a watch-only address is involved in this transaction.</source> - <context-group purpose="location"><context context-type="linenumber">682</context></context-group> + <context-group purpose="location"><context context-type="linenumber">683</context></context-group> </trans-unit> - <trans-unit id="_msg863"> + <trans-unit id="_msg882"> <source xml:space="preserve">User-defined intent/purpose of the transaction.</source> - <context-group purpose="location"><context context-type="linenumber">684</context></context-group> + <context-group purpose="location"><context context-type="linenumber">685</context></context-group> </trans-unit> - <trans-unit id="_msg864"> + <trans-unit id="_msg883"> <source xml:space="preserve">Amount removed from or added to balance.</source> - <context-group purpose="location"><context context-type="linenumber">686</context></context-group> + <context-group purpose="location"><context context-type="linenumber">687</context></context-group> </trans-unit> </group> </body></file> <file original="../transactionview.cpp" datatype="cpp" source-language="en"><body> <group restype="x-trolltech-linguist-context" resname="TransactionView"> - <trans-unit id="_msg865"> + <trans-unit id="_msg884"> <source xml:space="preserve">All</source> <context-group purpose="location"><context context-type="linenumber">73</context></context-group> <context-group purpose="location"><context context-type="linenumber">89</context></context-group> </trans-unit> - <trans-unit id="_msg866"> + <trans-unit id="_msg885"> <source xml:space="preserve">Today</source> <context-group purpose="location"><context context-type="linenumber">74</context></context-group> </trans-unit> - <trans-unit id="_msg867"> + <trans-unit id="_msg886"> <source xml:space="preserve">This week</source> <context-group purpose="location"><context context-type="linenumber">75</context></context-group> </trans-unit> - <trans-unit id="_msg868"> + <trans-unit id="_msg887"> <source xml:space="preserve">This month</source> <context-group purpose="location"><context context-type="linenumber">76</context></context-group> </trans-unit> - <trans-unit id="_msg869"> + <trans-unit id="_msg888"> <source xml:space="preserve">Last month</source> <context-group purpose="location"><context context-type="linenumber">77</context></context-group> </trans-unit> - <trans-unit id="_msg870"> + <trans-unit id="_msg889"> <source xml:space="preserve">This year</source> <context-group purpose="location"><context context-type="linenumber">78</context></context-group> </trans-unit> - <trans-unit id="_msg871"> + <trans-unit id="_msg890"> <source xml:space="preserve">Received with</source> <context-group purpose="location"><context context-type="linenumber">90</context></context-group> </trans-unit> - <trans-unit id="_msg872"> + <trans-unit id="_msg891"> <source xml:space="preserve">Sent to</source> <context-group purpose="location"><context context-type="linenumber">92</context></context-group> </trans-unit> - <trans-unit id="_msg873"> + <trans-unit id="_msg892"> <source xml:space="preserve">To yourself</source> <context-group purpose="location"><context context-type="linenumber">94</context></context-group> </trans-unit> - <trans-unit id="_msg874"> + <trans-unit id="_msg893"> <source xml:space="preserve">Mined</source> <context-group purpose="location"><context context-type="linenumber">95</context></context-group> </trans-unit> - <trans-unit id="_msg875"> + <trans-unit id="_msg894"> <source xml:space="preserve">Other</source> <context-group purpose="location"><context context-type="linenumber">96</context></context-group> </trans-unit> - <trans-unit id="_msg876"> + <trans-unit id="_msg895"> <source xml:space="preserve">Enter address, transaction id, or label to search</source> <context-group purpose="location"><context context-type="linenumber">101</context></context-group> </trans-unit> - <trans-unit id="_msg877"> + <trans-unit id="_msg896"> <source xml:space="preserve">Min amount</source> <context-group purpose="location"><context context-type="linenumber">105</context></context-group> </trans-unit> - <trans-unit id="_msg878"> + <trans-unit id="_msg897"> <source xml:space="preserve">Range…</source> <context-group purpose="location"><context context-type="linenumber">79</context></context-group> </trans-unit> - <trans-unit id="_msg879"> + <trans-unit id="_msg898"> <source xml:space="preserve">&Copy address</source> <context-group purpose="location"><context context-type="linenumber">169</context></context-group> </trans-unit> - <trans-unit id="_msg880"> + <trans-unit id="_msg899"> <source xml:space="preserve">Copy &label</source> <context-group purpose="location"><context context-type="linenumber">170</context></context-group> </trans-unit> - <trans-unit id="_msg881"> + <trans-unit id="_msg900"> <source xml:space="preserve">Copy &amount</source> <context-group purpose="location"><context context-type="linenumber">171</context></context-group> </trans-unit> - <trans-unit id="_msg882"> + <trans-unit id="_msg901"> <source xml:space="preserve">Copy transaction &ID</source> <context-group purpose="location"><context context-type="linenumber">172</context></context-group> </trans-unit> - <trans-unit id="_msg883"> + <trans-unit id="_msg902"> <source xml:space="preserve">Copy &raw transaction</source> <context-group purpose="location"><context context-type="linenumber">173</context></context-group> </trans-unit> - <trans-unit id="_msg884"> + <trans-unit id="_msg903"> <source xml:space="preserve">Copy full transaction &details</source> <context-group purpose="location"><context context-type="linenumber">174</context></context-group> </trans-unit> - <trans-unit id="_msg885"> + <trans-unit id="_msg904"> <source xml:space="preserve">&Show transaction details</source> <context-group purpose="location"><context context-type="linenumber">175</context></context-group> </trans-unit> - <trans-unit id="_msg886"> + <trans-unit id="_msg905"> <source xml:space="preserve">Increase transaction &fee</source> <context-group purpose="location"><context context-type="linenumber">177</context></context-group> </trans-unit> - <trans-unit id="_msg887"> + <trans-unit id="_msg906"> <source xml:space="preserve">A&bandon transaction</source> <context-group purpose="location"><context context-type="linenumber">180</context></context-group> </trans-unit> - <trans-unit id="_msg888"> + <trans-unit id="_msg907"> <source xml:space="preserve">&Edit address label</source> <context-group purpose="location"><context context-type="linenumber">181</context></context-group> </trans-unit> - <trans-unit id="_msg889"> + <trans-unit id="_msg908"> <source xml:space="preserve">Show in %1</source> <context-group purpose="location"><context context-type="linenumber">240</context></context-group> <note annotates="source" from="developer">Transactions table context menu action to show the selected transaction in a third-party block explorer. %1 is a stand-in argument for the URL of the explorer.</note> </trans-unit> - <trans-unit id="_msg890"> + <trans-unit id="_msg909"> <source xml:space="preserve">Export Transaction History</source> <context-group purpose="location"><context context-type="linenumber">359</context></context-group> </trans-unit> - <trans-unit id="_msg891"> + <trans-unit id="_msg910"> <source xml:space="preserve">Comma separated file</source> <context-group purpose="location"><context context-type="linenumber">362</context></context-group> <note annotates="source" from="developer">Expanded name of the CSV file format. See: https://en.wikipedia.org/wiki/Comma-separated_values.</note> </trans-unit> - <trans-unit id="_msg892"> + <trans-unit id="_msg911"> <source xml:space="preserve">Confirmed</source> <context-group purpose="location"><context context-type="linenumber">371</context></context-group> </trans-unit> - <trans-unit id="_msg893"> + <trans-unit id="_msg912"> <source xml:space="preserve">Watch-only</source> <context-group purpose="location"><context context-type="linenumber">373</context></context-group> </trans-unit> - <trans-unit id="_msg894"> + <trans-unit id="_msg913"> <source xml:space="preserve">Date</source> <context-group purpose="location"><context context-type="linenumber">374</context></context-group> </trans-unit> - <trans-unit id="_msg895"> + <trans-unit id="_msg914"> <source xml:space="preserve">Type</source> <context-group purpose="location"><context context-type="linenumber">375</context></context-group> </trans-unit> - <trans-unit id="_msg896"> + <trans-unit id="_msg915"> <source xml:space="preserve">Label</source> <context-group purpose="location"><context context-type="linenumber">376</context></context-group> </trans-unit> - <trans-unit id="_msg897"> + <trans-unit id="_msg916"> <source xml:space="preserve">Address</source> <context-group purpose="location"><context context-type="linenumber">377</context></context-group> </trans-unit> - <trans-unit id="_msg898"> + <trans-unit id="_msg917"> <source xml:space="preserve">ID</source> <context-group purpose="location"><context context-type="linenumber">379</context></context-group> </trans-unit> - <trans-unit id="_msg899"> + <trans-unit id="_msg918"> <source xml:space="preserve">Exporting Failed</source> <context-group purpose="location"><context context-type="linenumber">382</context></context-group> </trans-unit> - <trans-unit id="_msg900"> + <trans-unit id="_msg919"> <source xml:space="preserve">There was an error trying to save the transaction history to %1.</source> <context-group purpose="location"><context context-type="linenumber">382</context></context-group> </trans-unit> - <trans-unit id="_msg901"> + <trans-unit id="_msg920"> <source xml:space="preserve">Exporting Successful</source> <context-group purpose="location"><context context-type="linenumber">386</context></context-group> </trans-unit> - <trans-unit id="_msg902"> + <trans-unit id="_msg921"> <source xml:space="preserve">The transaction history was successfully saved to %1.</source> <context-group purpose="location"><context context-type="linenumber">386</context></context-group> </trans-unit> - <trans-unit id="_msg903"> + <trans-unit id="_msg922"> <source xml:space="preserve">Range:</source> <context-group purpose="location"><context context-type="linenumber">558</context></context-group> </trans-unit> - <trans-unit id="_msg904"> + <trans-unit id="_msg923"> <source xml:space="preserve">to</source> <context-group purpose="location"><context context-type="linenumber">566</context></context-group> </trans-unit> @@ -4109,810 +4229,936 @@ Note: Since the fee is calculated on a per-byte basis, a fee rate of "100 </body></file> <file original="../walletframe.cpp" datatype="cpp" source-language="en"><body> <group restype="x-trolltech-linguist-context" resname="WalletFrame"> - <trans-unit id="_msg905"> + <trans-unit id="_msg924"> <source xml:space="preserve">No wallet has been loaded. Go to File > Open Wallet to load a wallet. - OR -</source> <context-group purpose="location"><context context-type="linenumber">45</context></context-group> </trans-unit> - <trans-unit id="_msg906"> + <trans-unit id="_msg925"> <source xml:space="preserve">Create a new wallet</source> <context-group purpose="location"><context context-type="linenumber">50</context></context-group> </trans-unit> - <trans-unit id="_msg907"> + <trans-unit id="_msg926"> <source xml:space="preserve">Error</source> - <context-group purpose="location"><context context-type="linenumber">204</context></context-group> - <context-group purpose="location"><context context-type="linenumber">213</context></context-group> - <context-group purpose="location"><context context-type="linenumber">223</context></context-group> + <context-group purpose="location"><context context-type="linenumber">201</context></context-group> + <context-group purpose="location"><context context-type="linenumber">211</context></context-group> + <context-group purpose="location"><context context-type="linenumber">229</context></context-group> </trans-unit> - <trans-unit id="_msg908"> + <trans-unit id="_msg927"> <source xml:space="preserve">Unable to decode PSBT from clipboard (invalid base64)</source> - <context-group purpose="location"><context context-type="linenumber">204</context></context-group> + <context-group purpose="location"><context context-type="linenumber">201</context></context-group> </trans-unit> - <trans-unit id="_msg909"> + <trans-unit id="_msg928"> <source xml:space="preserve">Load Transaction Data</source> - <context-group purpose="location"><context context-type="linenumber">209</context></context-group> + <context-group purpose="location"><context context-type="linenumber">207</context></context-group> </trans-unit> - <trans-unit id="_msg910"> + <trans-unit id="_msg929"> <source xml:space="preserve">Partially Signed Transaction (*.psbt)</source> - <context-group purpose="location"><context context-type="linenumber">210</context></context-group> + <context-group purpose="location"><context context-type="linenumber">208</context></context-group> </trans-unit> - <trans-unit id="_msg911"> + <trans-unit id="_msg930"> <source xml:space="preserve">PSBT file must be smaller than 100 MiB</source> - <context-group purpose="location"><context context-type="linenumber">213</context></context-group> + <context-group purpose="location"><context context-type="linenumber">211</context></context-group> </trans-unit> - <trans-unit id="_msg912"> + <trans-unit id="_msg931"> <source xml:space="preserve">Unable to decode PSBT</source> - <context-group purpose="location"><context context-type="linenumber">223</context></context-group> + <context-group purpose="location"><context context-type="linenumber">229</context></context-group> </trans-unit> </group> </body></file> <file original="../walletmodel.cpp" datatype="cpp" source-language="en"><body> <group restype="x-trolltech-linguist-context" resname="WalletModel"> - <trans-unit id="_msg913"> + <trans-unit id="_msg932"> <source xml:space="preserve">Send Coins</source> - <context-group purpose="location"><context context-type="linenumber">221</context></context-group> + <context-group purpose="location"><context context-type="linenumber">232</context></context-group> </trans-unit> - <trans-unit id="_msg914"> + <trans-unit id="_msg933"> <source xml:space="preserve">Fee bump error</source> - <context-group purpose="location"><context context-type="linenumber">481</context></context-group> - <context-group purpose="location"><context context-type="linenumber">533</context></context-group> - <context-group purpose="location"><context context-type="linenumber">548</context></context-group> - <context-group purpose="location"><context context-type="linenumber">553</context></context-group> + <context-group purpose="location"><context context-type="linenumber">495</context></context-group> + <context-group purpose="location"><context context-type="linenumber">547</context></context-group> + <context-group purpose="location"><context context-type="linenumber">562</context></context-group> + <context-group purpose="location"><context context-type="linenumber">567</context></context-group> </trans-unit> - <trans-unit id="_msg915"> + <trans-unit id="_msg934"> <source xml:space="preserve">Increasing transaction fee failed</source> - <context-group purpose="location"><context context-type="linenumber">481</context></context-group> + <context-group purpose="location"><context context-type="linenumber">495</context></context-group> </trans-unit> - <trans-unit id="_msg916"> + <trans-unit id="_msg935"> <source xml:space="preserve">Do you want to increase the fee?</source> - <context-group purpose="location"><context context-type="linenumber">488</context></context-group> + <context-group purpose="location"><context context-type="linenumber">502</context></context-group> <note annotates="source" from="developer">Asks a user if they would like to manually increase the fee of a transaction that has already been created.</note> </trans-unit> - <trans-unit id="_msg917"> + <trans-unit id="_msg936"> <source xml:space="preserve">Current fee:</source> - <context-group purpose="location"><context context-type="linenumber">492</context></context-group> + <context-group purpose="location"><context context-type="linenumber">506</context></context-group> </trans-unit> - <trans-unit id="_msg918"> + <trans-unit id="_msg937"> <source xml:space="preserve">Increase:</source> - <context-group purpose="location"><context context-type="linenumber">496</context></context-group> + <context-group purpose="location"><context context-type="linenumber">510</context></context-group> </trans-unit> - <trans-unit id="_msg919"> + <trans-unit id="_msg938"> <source xml:space="preserve">New fee:</source> - <context-group purpose="location"><context context-type="linenumber">500</context></context-group> + <context-group purpose="location"><context context-type="linenumber">514</context></context-group> </trans-unit> - <trans-unit id="_msg920"> + <trans-unit id="_msg939"> <source xml:space="preserve">Warning: This may pay the additional fee by reducing change outputs or adding inputs, when necessary. It may add a new change output if one does not already exist. These changes may potentially leak privacy.</source> - <context-group purpose="location"><context context-type="linenumber">508</context></context-group> + <context-group purpose="location"><context context-type="linenumber">522</context></context-group> </trans-unit> - <trans-unit id="_msg921"> + <trans-unit id="_msg940"> <source xml:space="preserve">Confirm fee bump</source> - <context-group purpose="location"><context context-type="linenumber">511</context></context-group> + <context-group purpose="location"><context context-type="linenumber">525</context></context-group> </trans-unit> - <trans-unit id="_msg922"> + <trans-unit id="_msg941"> <source xml:space="preserve">Can't draft transaction.</source> - <context-group purpose="location"><context context-type="linenumber">533</context></context-group> + <context-group purpose="location"><context context-type="linenumber">547</context></context-group> </trans-unit> - <trans-unit id="_msg923"> + <trans-unit id="_msg942"> <source xml:space="preserve">PSBT copied</source> - <context-group purpose="location"><context context-type="linenumber">540</context></context-group> + <context-group purpose="location"><context context-type="linenumber">554</context></context-group> </trans-unit> - <trans-unit id="_msg924"> + <trans-unit id="_msg943"> <source xml:space="preserve">Can't sign transaction.</source> - <context-group purpose="location"><context context-type="linenumber">548</context></context-group> + <context-group purpose="location"><context context-type="linenumber">562</context></context-group> </trans-unit> - <trans-unit id="_msg925"> + <trans-unit id="_msg944"> <source xml:space="preserve">Could not commit transaction</source> - <context-group purpose="location"><context context-type="linenumber">553</context></context-group> + <context-group purpose="location"><context context-type="linenumber">567</context></context-group> </trans-unit> - <trans-unit id="_msg926"> + <trans-unit id="_msg945"> <source xml:space="preserve">Can't display address</source> - <context-group purpose="location"><context context-type="linenumber">567</context></context-group> + <context-group purpose="location"><context context-type="linenumber">581</context></context-group> </trans-unit> - <trans-unit id="_msg927"> + <trans-unit id="_msg946"> <source xml:space="preserve">default wallet</source> - <context-group purpose="location"><context context-type="linenumber">585</context></context-group> + <context-group purpose="location"><context context-type="linenumber">599</context></context-group> </trans-unit> </group> </body></file> <file original="../walletview.cpp" datatype="cpp" source-language="en"><body> <group restype="x-trolltech-linguist-context" resname="WalletView"> - <trans-unit id="_msg928"> + <trans-unit id="_msg947"> <source xml:space="preserve">&Export</source> - <context-group purpose="location"><context context-type="linenumber">52</context></context-group> + <context-group purpose="location"><context context-type="linenumber">51</context></context-group> </trans-unit> - <trans-unit id="_msg929"> + <trans-unit id="_msg948"> <source xml:space="preserve">Export the data in the current tab to a file</source> - <context-group purpose="location"><context context-type="linenumber">53</context></context-group> + <context-group purpose="location"><context context-type="linenumber">52</context></context-group> </trans-unit> - <trans-unit id="_msg930"> + <trans-unit id="_msg949"> <source xml:space="preserve">Backup Wallet</source> - <context-group purpose="location"><context context-type="linenumber">217</context></context-group> + <context-group purpose="location"><context context-type="linenumber">214</context></context-group> </trans-unit> - <trans-unit id="_msg931"> + <trans-unit id="_msg950"> <source xml:space="preserve">Wallet Data</source> - <context-group purpose="location"><context context-type="linenumber">219</context></context-group> + <context-group purpose="location"><context context-type="linenumber">216</context></context-group> <note annotates="source" from="developer">Name of the wallet data file format.</note> </trans-unit> - <trans-unit id="_msg932"> + <trans-unit id="_msg951"> <source xml:space="preserve">Backup Failed</source> - <context-group purpose="location"><context context-type="linenumber">225</context></context-group> + <context-group purpose="location"><context context-type="linenumber">222</context></context-group> </trans-unit> - <trans-unit id="_msg933"> + <trans-unit id="_msg952"> <source xml:space="preserve">There was an error trying to save the wallet data to %1.</source> - <context-group purpose="location"><context context-type="linenumber">225</context></context-group> + <context-group purpose="location"><context context-type="linenumber">222</context></context-group> </trans-unit> - <trans-unit id="_msg934"> + <trans-unit id="_msg953"> <source xml:space="preserve">Backup Successful</source> - <context-group purpose="location"><context context-type="linenumber">229</context></context-group> + <context-group purpose="location"><context context-type="linenumber">226</context></context-group> </trans-unit> - <trans-unit id="_msg935"> + <trans-unit id="_msg954"> <source xml:space="preserve">The wallet data was successfully saved to %1.</source> - <context-group purpose="location"><context context-type="linenumber">229</context></context-group> + <context-group purpose="location"><context context-type="linenumber">226</context></context-group> </trans-unit> - <trans-unit id="_msg936"> + <trans-unit id="_msg955"> <source xml:space="preserve">Cancel</source> - <context-group purpose="location"><context context-type="linenumber">266</context></context-group> + <context-group purpose="location"><context context-type="linenumber">263</context></context-group> </trans-unit> </group> </body></file> <file original="../bitcoinstrings.cpp" datatype="cpp" source-language="en"><body> <group restype="x-trolltech-linguist-context" resname="bitcoin-core"> - <trans-unit id="_msg937"> + <trans-unit id="_msg956"> <source xml:space="preserve">The %s developers</source> <context-group purpose="location"><context context-type="linenumber">12</context></context-group> </trans-unit> - <trans-unit id="_msg938"> + <trans-unit id="_msg957"> <source xml:space="preserve">%s corrupt. Try using the wallet tool bitcoin-wallet to salvage or restoring a backup.</source> <context-group purpose="location"><context context-type="linenumber">13</context></context-group> </trans-unit> - <trans-unit id="_msg939"> + <trans-unit id="_msg958"> <source xml:space="preserve">-maxtxfee is set very high! Fees this large could be paid on a single transaction.</source> - <context-group purpose="location"><context context-type="linenumber">16</context></context-group> + <context-group purpose="location"><context context-type="linenumber">20</context></context-group> </trans-unit> - <trans-unit id="_msg940"> + <trans-unit id="_msg959"> <source xml:space="preserve">Cannot downgrade wallet from version %i to version %i. Wallet version unchanged.</source> - <context-group purpose="location"><context context-type="linenumber">19</context></context-group> + <context-group purpose="location"><context context-type="linenumber">38</context></context-group> </trans-unit> - <trans-unit id="_msg941"> + <trans-unit id="_msg960"> <source xml:space="preserve">Cannot obtain a lock on data directory %s. %s is probably already running.</source> - <context-group purpose="location"><context context-type="linenumber">22</context></context-group> + <context-group purpose="location"><context context-type="linenumber">41</context></context-group> </trans-unit> - <trans-unit id="_msg942"> + <trans-unit id="_msg961"> <source xml:space="preserve">Cannot upgrade a non HD split wallet from version %i to version %i without upgrading to support pre-split keypool. Please use version %i or no version specified.</source> - <context-group purpose="location"><context context-type="linenumber">27</context></context-group> + <context-group purpose="location"><context context-type="linenumber">46</context></context-group> </trans-unit> - <trans-unit id="_msg943"> + <trans-unit id="_msg962"> <source xml:space="preserve">Distributed under the MIT software license, see the accompanying file %s or %s</source> - <context-group purpose="location"><context context-type="linenumber">31</context></context-group> + <context-group purpose="location"><context context-type="linenumber">50</context></context-group> </trans-unit> - <trans-unit id="_msg944"> + <trans-unit id="_msg963"> <source xml:space="preserve">Error reading %s! All keys read correctly, but transaction data or address book entries might be missing or incorrect.</source> - <context-group purpose="location"><context context-type="linenumber">37</context></context-group> + <context-group purpose="location"><context context-type="linenumber">56</context></context-group> </trans-unit> - <trans-unit id="_msg945"> + <trans-unit id="_msg964"> <source xml:space="preserve">Error reading %s! Transaction data may be missing or incorrect. Rescanning wallet.</source> - <context-group purpose="location"><context context-type="linenumber">40</context></context-group> + <context-group purpose="location"><context context-type="linenumber">59</context></context-group> </trans-unit> - <trans-unit id="_msg946"> + <trans-unit id="_msg965"> <source xml:space="preserve">Error: Dumpfile format record is incorrect. Got "%s", expected "format".</source> - <context-group purpose="location"><context context-type="linenumber">43</context></context-group> + <context-group purpose="location"><context context-type="linenumber">65</context></context-group> </trans-unit> - <trans-unit id="_msg947"> + <trans-unit id="_msg966"> <source xml:space="preserve">Error: Dumpfile identifier record is incorrect. Got "%s", expected "%s".</source> - <context-group purpose="location"><context context-type="linenumber">45</context></context-group> + <context-group purpose="location"><context context-type="linenumber">67</context></context-group> </trans-unit> - <trans-unit id="_msg948"> + <trans-unit id="_msg967"> <source xml:space="preserve">Error: Dumpfile version is not supported. This version of bitcoin-wallet only supports version 1 dumpfiles. Got dumpfile with version %s</source> - <context-group purpose="location"><context context-type="linenumber">47</context></context-group> + <context-group purpose="location"><context context-type="linenumber">69</context></context-group> </trans-unit> - <trans-unit id="_msg949"> + <trans-unit id="_msg968"> <source xml:space="preserve">Error: Legacy wallets only support the "legacy", "p2sh-segwit", and "bech32" address types</source> - <context-group purpose="location"><context context-type="linenumber">50</context></context-group> - </trans-unit> - <trans-unit id="_msg950"> - <source xml:space="preserve">Error: Listening for incoming connections failed (listen returned error %s)</source> - <context-group purpose="location"><context context-type="linenumber">53</context></context-group> + <context-group purpose="location"><context context-type="linenumber">75</context></context-group> </trans-unit> - <trans-unit id="_msg951"> + <trans-unit id="_msg969"> <source xml:space="preserve">Fee estimation failed. Fallbackfee is disabled. Wait a few blocks or enable -fallbackfee.</source> - <context-group purpose="location"><context context-type="linenumber">55</context></context-group> + <context-group purpose="location"><context context-type="linenumber">87</context></context-group> </trans-unit> - <trans-unit id="_msg952"> + <trans-unit id="_msg970"> <source xml:space="preserve">File %s already exists. If you are sure this is what you want, move it out of the way first.</source> - <context-group purpose="location"><context context-type="linenumber">58</context></context-group> + <context-group purpose="location"><context context-type="linenumber">90</context></context-group> </trans-unit> - <trans-unit id="_msg953"> + <trans-unit id="_msg971"> <source xml:space="preserve">Invalid amount for -maxtxfee=<amount>: '%s' (must be at least the minrelay fee of %s to prevent stuck transactions)</source> - <context-group purpose="location"><context context-type="linenumber">61</context></context-group> + <context-group purpose="location"><context context-type="linenumber">96</context></context-group> </trans-unit> - <trans-unit id="_msg954"> + <trans-unit id="_msg972"> <source xml:space="preserve">Invalid or corrupt peers.dat (%s). If you believe this is a bug, please report it to %s. As a workaround, you can move the file (%s) out of the way (rename, move, or delete) to have a new one created on the next start.</source> - <context-group purpose="location"><context context-type="linenumber">64</context></context-group> + <context-group purpose="location"><context context-type="linenumber">99</context></context-group> </trans-unit> - <trans-unit id="_msg955"> + <trans-unit id="_msg973"> <source xml:space="preserve">More than one onion bind address is provided. Using %s for the automatically created Tor onion service.</source> - <context-group purpose="location"><context context-type="linenumber">68</context></context-group> + <context-group purpose="location"><context context-type="linenumber">103</context></context-group> </trans-unit> - <trans-unit id="_msg956"> + <trans-unit id="_msg974"> <source xml:space="preserve">No dump file provided. To use createfromdump, -dumpfile=<filename> must be provided.</source> - <context-group purpose="location"><context context-type="linenumber">71</context></context-group> + <context-group purpose="location"><context context-type="linenumber">106</context></context-group> </trans-unit> - <trans-unit id="_msg957"> + <trans-unit id="_msg975"> <source xml:space="preserve">No dump file provided. To use dump, -dumpfile=<filename> must be provided.</source> - <context-group purpose="location"><context context-type="linenumber">74</context></context-group> + <context-group purpose="location"><context context-type="linenumber">109</context></context-group> </trans-unit> - <trans-unit id="_msg958"> + <trans-unit id="_msg976"> <source xml:space="preserve">No wallet file format provided. To use createfromdump, -format=<format> must be provided.</source> - <context-group purpose="location"><context context-type="linenumber">76</context></context-group> + <context-group purpose="location"><context context-type="linenumber">111</context></context-group> </trans-unit> - <trans-unit id="_msg959"> + <trans-unit id="_msg977"> <source xml:space="preserve">Please check that your computer's date and time are correct! If your clock is wrong, %s will not work properly.</source> - <context-group purpose="location"><context context-type="linenumber">79</context></context-group> + <context-group purpose="location"><context context-type="linenumber">121</context></context-group> </trans-unit> - <trans-unit id="_msg960"> + <trans-unit id="_msg978"> <source xml:space="preserve">Please contribute if you find %s useful. Visit %s for further information about the software.</source> - <context-group purpose="location"><context context-type="linenumber">82</context></context-group> + <context-group purpose="location"><context context-type="linenumber">124</context></context-group> </trans-unit> - <trans-unit id="_msg961"> + <trans-unit id="_msg979"> <source xml:space="preserve">Prune configured below the minimum of %d MiB. Please use a higher number.</source> - <context-group purpose="location"><context context-type="linenumber">85</context></context-group> + <context-group purpose="location"><context context-type="linenumber">127</context></context-group> </trans-unit> - <trans-unit id="_msg962"> + <trans-unit id="_msg980"> + <source xml:space="preserve">Prune mode is incompatible with -reindex-chainstate. Use full -reindex instead.</source> + <context-group purpose="location"><context context-type="linenumber">129</context></context-group> + </trans-unit> + <trans-unit id="_msg981"> <source xml:space="preserve">Prune: last wallet synchronisation goes beyond pruned data. You need to -reindex (download the whole blockchain again in case of pruned node)</source> - <context-group purpose="location"><context context-type="linenumber">87</context></context-group> + <context-group purpose="location"><context context-type="linenumber">132</context></context-group> </trans-unit> - <trans-unit id="_msg963"> + <trans-unit id="_msg982"> <source xml:space="preserve">SQLiteDatabase: Unknown sqlite wallet schema version %d. Only version %d is supported</source> - <context-group purpose="location"><context context-type="linenumber">90</context></context-group> + <context-group purpose="location"><context context-type="linenumber">135</context></context-group> </trans-unit> - <trans-unit id="_msg964"> + <trans-unit id="_msg983"> <source xml:space="preserve">The block database contains a block which appears to be from the future. This may be due to your computer's date and time being set incorrectly. Only rebuild the block database if you are sure that your computer's date and time are correct</source> - <context-group purpose="location"><context context-type="linenumber">96</context></context-group> + <context-group purpose="location"><context context-type="linenumber">141</context></context-group> </trans-unit> - <trans-unit id="_msg965"> + <trans-unit id="_msg984"> <source xml:space="preserve">The block index db contains a legacy 'txindex'. To clear the occupied disk space, run a full -reindex, otherwise ignore this error. This error message will not be displayed again.</source> - <context-group purpose="location"><context context-type="linenumber">101</context></context-group> + <context-group purpose="location"><context context-type="linenumber">146</context></context-group> </trans-unit> - <trans-unit id="_msg966"> + <trans-unit id="_msg985"> <source xml:space="preserve">The transaction amount is too small to send after the fee has been deducted</source> - <context-group purpose="location"><context context-type="linenumber">105</context></context-group> + <context-group purpose="location"><context context-type="linenumber">150</context></context-group> </trans-unit> - <trans-unit id="_msg967"> + <trans-unit id="_msg986"> <source xml:space="preserve">This error could occur if this wallet was not shutdown cleanly and was last loaded using a build with a newer version of Berkeley DB. If so, please use the software that last loaded this wallet</source> - <context-group purpose="location"><context context-type="linenumber">107</context></context-group> + <context-group purpose="location"><context context-type="linenumber">152</context></context-group> </trans-unit> - <trans-unit id="_msg968"> + <trans-unit id="_msg987"> <source xml:space="preserve">This is a pre-release test build - use at your own risk - do not use for mining or merchant applications</source> - <context-group purpose="location"><context context-type="linenumber">111</context></context-group> + <context-group purpose="location"><context context-type="linenumber">156</context></context-group> </trans-unit> - <trans-unit id="_msg969"> + <trans-unit id="_msg988"> <source xml:space="preserve">This is the maximum transaction fee you pay (in addition to the normal fee) to prioritize partial spend avoidance over regular coin selection.</source> - <context-group purpose="location"><context context-type="linenumber">114</context></context-group> + <context-group purpose="location"><context context-type="linenumber">159</context></context-group> </trans-unit> - <trans-unit id="_msg970"> + <trans-unit id="_msg989"> <source xml:space="preserve">This is the transaction fee you may discard if change is smaller than dust at this level</source> - <context-group purpose="location"><context context-type="linenumber">117</context></context-group> + <context-group purpose="location"><context context-type="linenumber">162</context></context-group> </trans-unit> - <trans-unit id="_msg971"> + <trans-unit id="_msg990"> <source xml:space="preserve">This is the transaction fee you may pay when fee estimates are not available.</source> - <context-group purpose="location"><context context-type="linenumber">120</context></context-group> + <context-group purpose="location"><context context-type="linenumber">165</context></context-group> </trans-unit> - <trans-unit id="_msg972"> + <trans-unit id="_msg991"> <source xml:space="preserve">Total length of network version string (%i) exceeds maximum length (%i). Reduce the number or size of uacomments.</source> - <context-group purpose="location"><context context-type="linenumber">122</context></context-group> + <context-group purpose="location"><context context-type="linenumber">167</context></context-group> </trans-unit> - <trans-unit id="_msg973"> + <trans-unit id="_msg992"> <source xml:space="preserve">Unable to replay blocks. You will need to rebuild the database using -reindex-chainstate.</source> - <context-group purpose="location"><context context-type="linenumber">125</context></context-group> + <context-group purpose="location"><context context-type="linenumber">170</context></context-group> </trans-unit> - <trans-unit id="_msg974"> + <trans-unit id="_msg993"> <source xml:space="preserve">Unknown wallet file format "%s" provided. Please provide one of "bdb" or "sqlite".</source> - <context-group purpose="location"><context context-type="linenumber">128</context></context-group> + <context-group purpose="location"><context context-type="linenumber">173</context></context-group> </trans-unit> - <trans-unit id="_msg975"> + <trans-unit id="_msg994"> + <source xml:space="preserve">Unsupported chainstate database format found. Please restart with -reindex-chainstate. This will rebuild the chainstate database.</source> + <context-group purpose="location"><context context-type="linenumber">184</context></context-group> + </trans-unit> + <trans-unit id="_msg995"> + <source xml:space="preserve">Wallet created successfully. The legacy wallet type is being deprecated and support for creating and opening legacy wallets will be removed in the future.</source> + <context-group purpose="location"><context context-type="linenumber">187</context></context-group> + </trans-unit> + <trans-unit id="_msg996"> <source xml:space="preserve">Warning: Dumpfile wallet format "%s" does not match command line specified format "%s".</source> - <context-group purpose="location"><context context-type="linenumber">131</context></context-group> + <context-group purpose="location"><context context-type="linenumber">191</context></context-group> </trans-unit> - <trans-unit id="_msg976"> + <trans-unit id="_msg997"> <source xml:space="preserve">Warning: Private keys detected in wallet {%s} with disabled private keys</source> - <context-group purpose="location"><context context-type="linenumber">134</context></context-group> + <context-group purpose="location"><context context-type="linenumber">194</context></context-group> </trans-unit> - <trans-unit id="_msg977"> + <trans-unit id="_msg998"> <source xml:space="preserve">Warning: We do not appear to fully agree with our peers! You may need to upgrade, or other nodes may need to upgrade.</source> - <context-group purpose="location"><context context-type="linenumber">136</context></context-group> + <context-group purpose="location"><context context-type="linenumber">196</context></context-group> </trans-unit> - <trans-unit id="_msg978"> + <trans-unit id="_msg999"> <source xml:space="preserve">Witness data for blocks after height %d requires validation. Please restart with -reindex.</source> - <context-group purpose="location"><context context-type="linenumber">139</context></context-group> + <context-group purpose="location"><context context-type="linenumber">199</context></context-group> </trans-unit> - <trans-unit id="_msg979"> + <trans-unit id="_msg1000"> <source xml:space="preserve">You need to rebuild the database using -reindex to go back to unpruned mode. This will redownload the entire blockchain</source> - <context-group purpose="location"><context context-type="linenumber">142</context></context-group> + <context-group purpose="location"><context context-type="linenumber">202</context></context-group> </trans-unit> - <trans-unit id="_msg980"> + <trans-unit id="_msg1001"> <source xml:space="preserve">%s is set very high!</source> - <context-group purpose="location"><context context-type="linenumber">145</context></context-group> + <context-group purpose="location"><context context-type="linenumber">211</context></context-group> </trans-unit> - <trans-unit id="_msg981"> + <trans-unit id="_msg1002"> <source xml:space="preserve">-maxmempool must be at least %d MB</source> - <context-group purpose="location"><context context-type="linenumber">146</context></context-group> + <context-group purpose="location"><context context-type="linenumber">212</context></context-group> </trans-unit> - <trans-unit id="_msg982"> + <trans-unit id="_msg1003"> <source xml:space="preserve">A fatal internal error occurred, see debug.log for details</source> - <context-group purpose="location"><context context-type="linenumber">147</context></context-group> + <context-group purpose="location"><context context-type="linenumber">213</context></context-group> </trans-unit> - <trans-unit id="_msg983"> + <trans-unit id="_msg1004"> <source xml:space="preserve">Cannot resolve -%s address: '%s'</source> - <context-group purpose="location"><context context-type="linenumber">148</context></context-group> + <context-group purpose="location"><context context-type="linenumber">214</context></context-group> </trans-unit> - <trans-unit id="_msg984"> + <trans-unit id="_msg1005"> <source xml:space="preserve">Cannot set -forcednsseed to true when setting -dnsseed to false.</source> - <context-group purpose="location"><context context-type="linenumber">149</context></context-group> + <context-group purpose="location"><context context-type="linenumber">215</context></context-group> </trans-unit> - <trans-unit id="_msg985"> + <trans-unit id="_msg1006"> <source xml:space="preserve">Cannot set -peerblockfilters without -blockfilterindex.</source> - <context-group purpose="location"><context context-type="linenumber">150</context></context-group> + <context-group purpose="location"><context context-type="linenumber">216</context></context-group> </trans-unit> - <trans-unit id="_msg986"> + <trans-unit id="_msg1007"> <source xml:space="preserve">Cannot write to data directory '%s'; check permissions.</source> - <context-group purpose="location"><context context-type="linenumber">151</context></context-group> + <context-group purpose="location"><context context-type="linenumber">217</context></context-group> </trans-unit> - <trans-unit id="_msg987"> + <trans-unit id="_msg1008"> <source xml:space="preserve">The -txindex upgrade started by a previous version cannot be completed. Restart with the previous version or run a full -reindex.</source> - <context-group purpose="location"><context context-type="linenumber">93</context></context-group> + <context-group purpose="location"><context context-type="linenumber">138</context></context-group> </trans-unit> - <trans-unit id="_msg988"> + <trans-unit id="_msg1009"> + <source xml:space="preserve">%s request to listen on port %u. This port is considered "bad" and thus it is unlikely that any Bitcoin Core peers connect to it. See doc/p2p-bad-ports.md for details and a full list.</source> + <context-group purpose="location"><context context-type="linenumber">16</context></context-group> + </trans-unit> + <trans-unit id="_msg1010"> + <source xml:space="preserve">-reindex-chainstate option is not compatible with -blockfilterindex. Please temporarily disable blockfilterindex while using -reindex-chainstate, or replace -reindex-chainstate with -reindex to fully rebuild all indexes.</source> + <context-group purpose="location"><context context-type="linenumber">23</context></context-group> + </trans-unit> + <trans-unit id="_msg1011"> + <source xml:space="preserve">-reindex-chainstate option is not compatible with -coinstatsindex. Please temporarily disable coinstatsindex while using -reindex-chainstate, or replace -reindex-chainstate with -reindex to fully rebuild all indexes.</source> + <context-group purpose="location"><context context-type="linenumber">27</context></context-group> + </trans-unit> + <trans-unit id="_msg1012"> + <source xml:space="preserve">-reindex-chainstate option is not compatible with -txindex. Please temporarily disable txindex while using -reindex-chainstate, or replace -reindex-chainstate with -reindex to fully rebuild all indexes.</source> + <context-group purpose="location"><context context-type="linenumber">31</context></context-group> + </trans-unit> + <trans-unit id="_msg1013"> + <source xml:space="preserve">Assumed-valid: last wallet synchronisation goes beyond available block data. You need to wait for the background validation chain to download more blocks.</source> + <context-group purpose="location"><context context-type="linenumber">35</context></context-group> + </trans-unit> + <trans-unit id="_msg1014"> <source xml:space="preserve">Cannot provide specific connections and have addrman find outgoing connections at the same time.</source> - <context-group purpose="location"><context context-type="linenumber">24</context></context-group> + <context-group purpose="location"><context context-type="linenumber">43</context></context-group> </trans-unit> - <trans-unit id="_msg989"> + <trans-unit id="_msg1015"> <source xml:space="preserve">Error loading %s: External signer wallet being loaded without external signer support compiled</source> - <context-group purpose="location"><context context-type="linenumber">34</context></context-group> + <context-group purpose="location"><context context-type="linenumber">53</context></context-group> </trans-unit> - <trans-unit id="_msg990"> + <trans-unit id="_msg1016"> + <source xml:space="preserve">Error: Address book data in wallet cannot be identified to belong to migrated wallets</source> + <context-group purpose="location"><context context-type="linenumber">62</context></context-group> + </trans-unit> + <trans-unit id="_msg1017"> + <source xml:space="preserve">Error: Duplicate descriptors created during migration. Your wallet may be corrupted.</source> + <context-group purpose="location"><context context-type="linenumber">72</context></context-group> + </trans-unit> + <trans-unit id="_msg1018"> + <source xml:space="preserve">Error: Transaction %s in wallet cannot be identified to belong to migrated wallets</source> + <context-group purpose="location"><context context-type="linenumber">78</context></context-group> + </trans-unit> + <trans-unit id="_msg1019"> + <source xml:space="preserve">Error: Unable to produce descriptors for this legacy wallet. Make sure the wallet is unlocked first</source> + <context-group purpose="location"><context context-type="linenumber">81</context></context-group> + </trans-unit> + <trans-unit id="_msg1020"> + <source xml:space="preserve">Failed to rename invalid peers.dat file. Please move or delete it and try again.</source> + <context-group purpose="location"><context context-type="linenumber">84</context></context-group> + </trans-unit> + <trans-unit id="_msg1021"> + <source xml:space="preserve">Incompatible options: -dnsseed=1 was explicitly specified, but -onlynet forbids connections to IPv4/IPv6</source> + <context-group purpose="location"><context context-type="linenumber">93</context></context-group> + </trans-unit> + <trans-unit id="_msg1022"> + <source xml:space="preserve">Outbound connections restricted to Tor (-onlynet=onion) but the proxy for reaching the Tor network is explicitly forbidden: -onion=0</source> + <context-group purpose="location"><context context-type="linenumber">114</context></context-group> + </trans-unit> + <trans-unit id="_msg1023"> + <source xml:space="preserve">Outbound connections restricted to Tor (-onlynet=onion) but the proxy for reaching the Tor network is not provided: none of -proxy, -onion or -listenonion is given</source> + <context-group purpose="location"><context context-type="linenumber">117</context></context-group> + </trans-unit> + <trans-unit id="_msg1024"> + <source xml:space="preserve">Unrecognized descriptor found. Loading wallet %s + +The wallet might had been created on a newer version. +Please try running the latest software version. +</source> + <context-group purpose="location"><context context-type="linenumber">176</context></context-group> + </trans-unit> + <trans-unit id="_msg1025"> + <source xml:space="preserve">Unsupported category-specific logging level -loglevel=%s. Expected -loglevel=<category>:<loglevel>. Valid categories: %s. Valid loglevels: %s.</source> + <context-group purpose="location"><context context-type="linenumber">181</context></context-group> + </trans-unit> + <trans-unit id="_msg1026"> + <source xml:space="preserve"> +Unable to cleanup failed migration</source> + <context-group purpose="location"><context context-type="linenumber">205</context></context-group> + </trans-unit> + <trans-unit id="_msg1027"> + <source xml:space="preserve"> +Unable to restore backup of wallet.</source> + <context-group purpose="location"><context context-type="linenumber">208</context></context-group> + </trans-unit> + <trans-unit id="_msg1028"> <source xml:space="preserve">Config setting for %s only applied on %s network when in [%s] section.</source> - <context-group purpose="location"><context context-type="linenumber">152</context></context-group> + <context-group purpose="location"><context context-type="linenumber">218</context></context-group> </trans-unit> - <trans-unit id="_msg991"> + <trans-unit id="_msg1029"> <source xml:space="preserve">Copyright (C) %i-%i</source> - <context-group purpose="location"><context context-type="linenumber">153</context></context-group> + <context-group purpose="location"><context context-type="linenumber">219</context></context-group> </trans-unit> - <trans-unit id="_msg992"> + <trans-unit id="_msg1030"> <source xml:space="preserve">Corrupted block database detected</source> - <context-group purpose="location"><context context-type="linenumber">154</context></context-group> + <context-group purpose="location"><context context-type="linenumber">220</context></context-group> </trans-unit> - <trans-unit id="_msg993"> + <trans-unit id="_msg1031"> <source xml:space="preserve">Could not find asmap file %s</source> - <context-group purpose="location"><context context-type="linenumber">155</context></context-group> + <context-group purpose="location"><context context-type="linenumber">221</context></context-group> </trans-unit> - <trans-unit id="_msg994"> + <trans-unit id="_msg1032"> <source xml:space="preserve">Could not parse asmap file %s</source> - <context-group purpose="location"><context context-type="linenumber">156</context></context-group> + <context-group purpose="location"><context context-type="linenumber">222</context></context-group> </trans-unit> - <trans-unit id="_msg995"> + <trans-unit id="_msg1033"> <source xml:space="preserve">Disk space is too low!</source> - <context-group purpose="location"><context context-type="linenumber">157</context></context-group> + <context-group purpose="location"><context context-type="linenumber">223</context></context-group> </trans-unit> - <trans-unit id="_msg996"> + <trans-unit id="_msg1034"> <source xml:space="preserve">Do you want to rebuild the block database now?</source> - <context-group purpose="location"><context context-type="linenumber">158</context></context-group> + <context-group purpose="location"><context context-type="linenumber">224</context></context-group> </trans-unit> - <trans-unit id="_msg997"> + <trans-unit id="_msg1035"> <source xml:space="preserve">Done loading</source> - <context-group purpose="location"><context context-type="linenumber">159</context></context-group> + <context-group purpose="location"><context context-type="linenumber">225</context></context-group> </trans-unit> - <trans-unit id="_msg998"> + <trans-unit id="_msg1036"> <source xml:space="preserve">Dump file %s does not exist.</source> - <context-group purpose="location"><context context-type="linenumber">160</context></context-group> + <context-group purpose="location"><context context-type="linenumber">226</context></context-group> </trans-unit> - <trans-unit id="_msg999"> + <trans-unit id="_msg1037"> <source xml:space="preserve">Error creating %s</source> - <context-group purpose="location"><context context-type="linenumber">161</context></context-group> + <context-group purpose="location"><context context-type="linenumber">227</context></context-group> </trans-unit> - <trans-unit id="_msg1000"> + <trans-unit id="_msg1038"> <source xml:space="preserve">Error initializing block database</source> - <context-group purpose="location"><context context-type="linenumber">162</context></context-group> + <context-group purpose="location"><context context-type="linenumber">228</context></context-group> </trans-unit> - <trans-unit id="_msg1001"> + <trans-unit id="_msg1039"> <source xml:space="preserve">Error initializing wallet database environment %s!</source> - <context-group purpose="location"><context context-type="linenumber">163</context></context-group> + <context-group purpose="location"><context context-type="linenumber">229</context></context-group> </trans-unit> - <trans-unit id="_msg1002"> + <trans-unit id="_msg1040"> <source xml:space="preserve">Error loading %s</source> - <context-group purpose="location"><context context-type="linenumber">164</context></context-group> + <context-group purpose="location"><context context-type="linenumber">230</context></context-group> </trans-unit> - <trans-unit id="_msg1003"> + <trans-unit id="_msg1041"> <source xml:space="preserve">Error loading %s: Private keys can only be disabled during creation</source> - <context-group purpose="location"><context context-type="linenumber">165</context></context-group> + <context-group purpose="location"><context context-type="linenumber">231</context></context-group> </trans-unit> - <trans-unit id="_msg1004"> + <trans-unit id="_msg1042"> <source xml:space="preserve">Error loading %s: Wallet corrupted</source> - <context-group purpose="location"><context context-type="linenumber">166</context></context-group> + <context-group purpose="location"><context context-type="linenumber">232</context></context-group> </trans-unit> - <trans-unit id="_msg1005"> + <trans-unit id="_msg1043"> <source xml:space="preserve">Error loading %s: Wallet requires newer version of %s</source> - <context-group purpose="location"><context context-type="linenumber">167</context></context-group> + <context-group purpose="location"><context context-type="linenumber">233</context></context-group> </trans-unit> - <trans-unit id="_msg1006"> + <trans-unit id="_msg1044"> <source xml:space="preserve">Error loading block database</source> - <context-group purpose="location"><context context-type="linenumber">168</context></context-group> + <context-group purpose="location"><context context-type="linenumber">234</context></context-group> </trans-unit> - <trans-unit id="_msg1007"> + <trans-unit id="_msg1045"> <source xml:space="preserve">Error opening block database</source> - <context-group purpose="location"><context context-type="linenumber">169</context></context-group> + <context-group purpose="location"><context context-type="linenumber">235</context></context-group> </trans-unit> - <trans-unit id="_msg1008"> + <trans-unit id="_msg1046"> <source xml:space="preserve">Error reading from database, shutting down.</source> - <context-group purpose="location"><context context-type="linenumber">170</context></context-group> + <context-group purpose="location"><context context-type="linenumber">236</context></context-group> </trans-unit> - <trans-unit id="_msg1009"> + <trans-unit id="_msg1047"> <source xml:space="preserve">Error reading next record from wallet database</source> - <context-group purpose="location"><context context-type="linenumber">171</context></context-group> + <context-group purpose="location"><context context-type="linenumber">237</context></context-group> </trans-unit> - <trans-unit id="_msg1010"> - <source xml:space="preserve">Error upgrading chainstate database</source> - <context-group purpose="location"><context context-type="linenumber">172</context></context-group> + <trans-unit id="_msg1048"> + <source xml:space="preserve">Error: Could not add watchonly tx to watchonly wallet</source> + <context-group purpose="location"><context context-type="linenumber">238</context></context-group> </trans-unit> - <trans-unit id="_msg1011"> + <trans-unit id="_msg1049"> + <source xml:space="preserve">Error: Could not delete watchonly transactions</source> + <context-group purpose="location"><context context-type="linenumber">239</context></context-group> + </trans-unit> + <trans-unit id="_msg1050"> <source xml:space="preserve">Error: Couldn't create cursor into database</source> - <context-group purpose="location"><context context-type="linenumber">173</context></context-group> + <context-group purpose="location"><context context-type="linenumber">240</context></context-group> </trans-unit> - <trans-unit id="_msg1012"> + <trans-unit id="_msg1051"> <source xml:space="preserve">Error: Disk space is low for %s</source> - <context-group purpose="location"><context context-type="linenumber">174</context></context-group> + <context-group purpose="location"><context context-type="linenumber">241</context></context-group> </trans-unit> - <trans-unit id="_msg1013"> + <trans-unit id="_msg1052"> <source xml:space="preserve">Error: Dumpfile checksum does not match. Computed %s, expected %s</source> - <context-group purpose="location"><context context-type="linenumber">175</context></context-group> + <context-group purpose="location"><context context-type="linenumber">242</context></context-group> </trans-unit> - <trans-unit id="_msg1014"> + <trans-unit id="_msg1053"> + <source xml:space="preserve">Error: Failed to create new watchonly wallet</source> + <context-group purpose="location"><context context-type="linenumber">243</context></context-group> + </trans-unit> + <trans-unit id="_msg1054"> <source xml:space="preserve">Error: Got key that was not hex: %s</source> - <context-group purpose="location"><context context-type="linenumber">176</context></context-group> + <context-group purpose="location"><context context-type="linenumber">244</context></context-group> </trans-unit> - <trans-unit id="_msg1015"> + <trans-unit id="_msg1055"> <source xml:space="preserve">Error: Got value that was not hex: %s</source> - <context-group purpose="location"><context context-type="linenumber">177</context></context-group> + <context-group purpose="location"><context context-type="linenumber">245</context></context-group> </trans-unit> - <trans-unit id="_msg1016"> + <trans-unit id="_msg1056"> <source xml:space="preserve">Error: Keypool ran out, please call keypoolrefill first</source> - <context-group purpose="location"><context context-type="linenumber">178</context></context-group> + <context-group purpose="location"><context context-type="linenumber">246</context></context-group> </trans-unit> - <trans-unit id="_msg1017"> + <trans-unit id="_msg1057"> <source xml:space="preserve">Error: Missing checksum</source> - <context-group purpose="location"><context context-type="linenumber">179</context></context-group> + <context-group purpose="location"><context context-type="linenumber">247</context></context-group> </trans-unit> - <trans-unit id="_msg1018"> + <trans-unit id="_msg1058"> <source xml:space="preserve">Error: No %s addresses available.</source> - <context-group purpose="location"><context context-type="linenumber">180</context></context-group> + <context-group purpose="location"><context context-type="linenumber">248</context></context-group> </trans-unit> - <trans-unit id="_msg1019"> + <trans-unit id="_msg1059"> + <source xml:space="preserve">Error: Not all watchonly txs could be deleted</source> + <context-group purpose="location"><context context-type="linenumber">249</context></context-group> + </trans-unit> + <trans-unit id="_msg1060"> + <source xml:space="preserve">Error: This wallet already uses SQLite</source> + <context-group purpose="location"><context context-type="linenumber">250</context></context-group> + </trans-unit> + <trans-unit id="_msg1061"> + <source xml:space="preserve">Error: This wallet is already a descriptor wallet</source> + <context-group purpose="location"><context context-type="linenumber">251</context></context-group> + </trans-unit> + <trans-unit id="_msg1062"> + <source xml:space="preserve">Error: Unable to begin reading all records in the database</source> + <context-group purpose="location"><context context-type="linenumber">252</context></context-group> + </trans-unit> + <trans-unit id="_msg1063"> + <source xml:space="preserve">Error: Unable to make a backup of your wallet</source> + <context-group purpose="location"><context context-type="linenumber">253</context></context-group> + </trans-unit> + <trans-unit id="_msg1064"> <source xml:space="preserve">Error: Unable to parse version %u as a uint32_t</source> - <context-group purpose="location"><context context-type="linenumber">181</context></context-group> + <context-group purpose="location"><context context-type="linenumber">254</context></context-group> </trans-unit> - <trans-unit id="_msg1020"> + <trans-unit id="_msg1065"> + <source xml:space="preserve">Error: Unable to read all records in the database</source> + <context-group purpose="location"><context context-type="linenumber">255</context></context-group> + </trans-unit> + <trans-unit id="_msg1066"> + <source xml:space="preserve">Error: Unable to remove watchonly address book data</source> + <context-group purpose="location"><context context-type="linenumber">256</context></context-group> + </trans-unit> + <trans-unit id="_msg1067"> <source xml:space="preserve">Error: Unable to write record to new wallet</source> - <context-group purpose="location"><context context-type="linenumber">182</context></context-group> + <context-group purpose="location"><context context-type="linenumber">257</context></context-group> </trans-unit> - <trans-unit id="_msg1021"> + <trans-unit id="_msg1068"> <source xml:space="preserve">Failed to listen on any port. Use -listen=0 if you want this.</source> - <context-group purpose="location"><context context-type="linenumber">183</context></context-group> + <context-group purpose="location"><context context-type="linenumber">258</context></context-group> </trans-unit> - <trans-unit id="_msg1022"> + <trans-unit id="_msg1069"> <source xml:space="preserve">Failed to rescan the wallet during initialization</source> - <context-group purpose="location"><context context-type="linenumber">184</context></context-group> + <context-group purpose="location"><context context-type="linenumber">259</context></context-group> </trans-unit> - <trans-unit id="_msg1023"> + <trans-unit id="_msg1070"> <source xml:space="preserve">Failed to verify database</source> - <context-group purpose="location"><context context-type="linenumber">185</context></context-group> + <context-group purpose="location"><context context-type="linenumber">260</context></context-group> </trans-unit> - <trans-unit id="_msg1024"> + <trans-unit id="_msg1071"> <source xml:space="preserve">Fee rate (%s) is lower than the minimum fee rate setting (%s)</source> - <context-group purpose="location"><context context-type="linenumber">186</context></context-group> + <context-group purpose="location"><context context-type="linenumber">261</context></context-group> </trans-unit> - <trans-unit id="_msg1025"> + <trans-unit id="_msg1072"> <source xml:space="preserve">Ignoring duplicate -wallet %s.</source> - <context-group purpose="location"><context context-type="linenumber">187</context></context-group> + <context-group purpose="location"><context context-type="linenumber">262</context></context-group> </trans-unit> - <trans-unit id="_msg1026"> + <trans-unit id="_msg1073"> <source xml:space="preserve">Importing…</source> - <context-group purpose="location"><context context-type="linenumber">188</context></context-group> + <context-group purpose="location"><context context-type="linenumber">263</context></context-group> </trans-unit> - <trans-unit id="_msg1027"> + <trans-unit id="_msg1074"> <source xml:space="preserve">Incorrect or no genesis block found. Wrong datadir for network?</source> - <context-group purpose="location"><context context-type="linenumber">189</context></context-group> + <context-group purpose="location"><context context-type="linenumber">264</context></context-group> </trans-unit> - <trans-unit id="_msg1028"> + <trans-unit id="_msg1075"> <source xml:space="preserve">Initialization sanity check failed. %s is shutting down.</source> - <context-group purpose="location"><context context-type="linenumber">190</context></context-group> + <context-group purpose="location"><context context-type="linenumber">265</context></context-group> </trans-unit> - <trans-unit id="_msg1029"> + <trans-unit id="_msg1076"> <source xml:space="preserve">Input not found or already spent</source> - <context-group purpose="location"><context context-type="linenumber">191</context></context-group> + <context-group purpose="location"><context context-type="linenumber">266</context></context-group> </trans-unit> - <trans-unit id="_msg1030"> + <trans-unit id="_msg1077"> <source xml:space="preserve">Insufficient funds</source> - <context-group purpose="location"><context context-type="linenumber">192</context></context-group> + <context-group purpose="location"><context context-type="linenumber">267</context></context-group> </trans-unit> - <trans-unit id="_msg1031"> + <trans-unit id="_msg1078"> <source xml:space="preserve">Invalid -i2psam address or hostname: '%s'</source> - <context-group purpose="location"><context context-type="linenumber">193</context></context-group> + <context-group purpose="location"><context context-type="linenumber">268</context></context-group> </trans-unit> - <trans-unit id="_msg1032"> + <trans-unit id="_msg1079"> <source xml:space="preserve">Invalid -onion address or hostname: '%s'</source> - <context-group purpose="location"><context context-type="linenumber">194</context></context-group> + <context-group purpose="location"><context context-type="linenumber">269</context></context-group> </trans-unit> - <trans-unit id="_msg1033"> + <trans-unit id="_msg1080"> <source xml:space="preserve">Invalid -proxy address or hostname: '%s'</source> - <context-group purpose="location"><context context-type="linenumber">195</context></context-group> + <context-group purpose="location"><context context-type="linenumber">270</context></context-group> </trans-unit> - <trans-unit id="_msg1034"> + <trans-unit id="_msg1081"> <source xml:space="preserve">Invalid P2P permission: '%s'</source> - <context-group purpose="location"><context context-type="linenumber">196</context></context-group> + <context-group purpose="location"><context context-type="linenumber">271</context></context-group> </trans-unit> - <trans-unit id="_msg1035"> + <trans-unit id="_msg1082"> <source xml:space="preserve">Invalid amount for -%s=<amount>: '%s'</source> - <context-group purpose="location"><context context-type="linenumber">197</context></context-group> + <context-group purpose="location"><context context-type="linenumber">272</context></context-group> </trans-unit> - <trans-unit id="_msg1036"> + <trans-unit id="_msg1083"> <source xml:space="preserve">Invalid amount for -discardfee=<amount>: '%s'</source> - <context-group purpose="location"><context context-type="linenumber">198</context></context-group> + <context-group purpose="location"><context context-type="linenumber">273</context></context-group> </trans-unit> - <trans-unit id="_msg1037"> + <trans-unit id="_msg1084"> <source xml:space="preserve">Invalid amount for -fallbackfee=<amount>: '%s'</source> - <context-group purpose="location"><context context-type="linenumber">199</context></context-group> + <context-group purpose="location"><context context-type="linenumber">274</context></context-group> </trans-unit> - <trans-unit id="_msg1038"> + <trans-unit id="_msg1085"> <source xml:space="preserve">Invalid amount for -paytxfee=<amount>: '%s' (must be at least %s)</source> - <context-group purpose="location"><context context-type="linenumber">200</context></context-group> + <context-group purpose="location"><context context-type="linenumber">275</context></context-group> </trans-unit> - <trans-unit id="_msg1039"> + <trans-unit id="_msg1086"> <source xml:space="preserve">Invalid netmask specified in -whitelist: '%s'</source> - <context-group purpose="location"><context context-type="linenumber">201</context></context-group> + <context-group purpose="location"><context context-type="linenumber">276</context></context-group> </trans-unit> - <trans-unit id="_msg1040"> + <trans-unit id="_msg1087"> + <source xml:space="preserve">Listening for incoming connections failed (listen returned error %s)</source> + <context-group purpose="location"><context context-type="linenumber">277</context></context-group> + </trans-unit> + <trans-unit id="_msg1088"> <source xml:space="preserve">Loading P2P addresses…</source> - <context-group purpose="location"><context context-type="linenumber">202</context></context-group> + <context-group purpose="location"><context context-type="linenumber">278</context></context-group> </trans-unit> - <trans-unit id="_msg1041"> + <trans-unit id="_msg1089"> <source xml:space="preserve">Loading banlist…</source> - <context-group purpose="location"><context context-type="linenumber">203</context></context-group> + <context-group purpose="location"><context context-type="linenumber">279</context></context-group> </trans-unit> - <trans-unit id="_msg1042"> + <trans-unit id="_msg1090"> <source xml:space="preserve">Loading block index…</source> - <context-group purpose="location"><context context-type="linenumber">204</context></context-group> + <context-group purpose="location"><context context-type="linenumber">280</context></context-group> </trans-unit> - <trans-unit id="_msg1043"> + <trans-unit id="_msg1091"> <source xml:space="preserve">Loading wallet…</source> - <context-group purpose="location"><context context-type="linenumber">205</context></context-group> + <context-group purpose="location"><context context-type="linenumber">281</context></context-group> </trans-unit> - <trans-unit id="_msg1044"> + <trans-unit id="_msg1092"> <source xml:space="preserve">Missing amount</source> - <context-group purpose="location"><context context-type="linenumber">206</context></context-group> + <context-group purpose="location"><context context-type="linenumber">282</context></context-group> </trans-unit> - <trans-unit id="_msg1045"> + <trans-unit id="_msg1093"> <source xml:space="preserve">Missing solving data for estimating transaction size</source> - <context-group purpose="location"><context context-type="linenumber">207</context></context-group> + <context-group purpose="location"><context context-type="linenumber">283</context></context-group> </trans-unit> - <trans-unit id="_msg1046"> + <trans-unit id="_msg1094"> <source xml:space="preserve">Need to specify a port with -whitebind: '%s'</source> - <context-group purpose="location"><context context-type="linenumber">208</context></context-group> + <context-group purpose="location"><context context-type="linenumber">284</context></context-group> </trans-unit> - <trans-unit id="_msg1047"> + <trans-unit id="_msg1095"> <source xml:space="preserve">No addresses available</source> - <context-group purpose="location"><context context-type="linenumber">209</context></context-group> - </trans-unit> - <trans-unit id="_msg1048"> - <source xml:space="preserve">No proxy server specified. Use -proxy=<ip> or -proxy=<ip:port>.</source> - <context-group purpose="location"><context context-type="linenumber">210</context></context-group> + <context-group purpose="location"><context context-type="linenumber">285</context></context-group> </trans-unit> - <trans-unit id="_msg1049"> + <trans-unit id="_msg1096"> <source xml:space="preserve">Not enough file descriptors available.</source> - <context-group purpose="location"><context context-type="linenumber">211</context></context-group> + <context-group purpose="location"><context context-type="linenumber">286</context></context-group> </trans-unit> - <trans-unit id="_msg1050"> + <trans-unit id="_msg1097"> <source xml:space="preserve">Prune cannot be configured with a negative value.</source> - <context-group purpose="location"><context context-type="linenumber">212</context></context-group> - </trans-unit> - <trans-unit id="_msg1051"> - <source xml:space="preserve">Prune mode is incompatible with -coinstatsindex.</source> - <context-group purpose="location"><context context-type="linenumber">213</context></context-group> + <context-group purpose="location"><context context-type="linenumber">287</context></context-group> </trans-unit> - <trans-unit id="_msg1052"> + <trans-unit id="_msg1098"> <source xml:space="preserve">Prune mode is incompatible with -txindex.</source> - <context-group purpose="location"><context context-type="linenumber">214</context></context-group> + <context-group purpose="location"><context context-type="linenumber">288</context></context-group> </trans-unit> - <trans-unit id="_msg1053"> + <trans-unit id="_msg1099"> <source xml:space="preserve">Pruning blockstore…</source> - <context-group purpose="location"><context context-type="linenumber">215</context></context-group> + <context-group purpose="location"><context context-type="linenumber">289</context></context-group> </trans-unit> - <trans-unit id="_msg1054"> + <trans-unit id="_msg1100"> <source xml:space="preserve">Reducing -maxconnections from %d to %d, because of system limitations.</source> - <context-group purpose="location"><context context-type="linenumber">216</context></context-group> + <context-group purpose="location"><context context-type="linenumber">290</context></context-group> </trans-unit> - <trans-unit id="_msg1055"> + <trans-unit id="_msg1101"> <source xml:space="preserve">Replaying blocks…</source> - <context-group purpose="location"><context context-type="linenumber">217</context></context-group> + <context-group purpose="location"><context context-type="linenumber">291</context></context-group> </trans-unit> - <trans-unit id="_msg1056"> + <trans-unit id="_msg1102"> <source xml:space="preserve">Rescanning…</source> - <context-group purpose="location"><context context-type="linenumber">218</context></context-group> + <context-group purpose="location"><context context-type="linenumber">292</context></context-group> </trans-unit> - <trans-unit id="_msg1057"> + <trans-unit id="_msg1103"> <source xml:space="preserve">SQLiteDatabase: Failed to execute statement to verify database: %s</source> - <context-group purpose="location"><context context-type="linenumber">219</context></context-group> + <context-group purpose="location"><context context-type="linenumber">293</context></context-group> </trans-unit> - <trans-unit id="_msg1058"> + <trans-unit id="_msg1104"> <source xml:space="preserve">SQLiteDatabase: Failed to prepare statement to verify database: %s</source> - <context-group purpose="location"><context context-type="linenumber">220</context></context-group> + <context-group purpose="location"><context context-type="linenumber">294</context></context-group> </trans-unit> - <trans-unit id="_msg1059"> + <trans-unit id="_msg1105"> <source xml:space="preserve">SQLiteDatabase: Failed to read database verification error: %s</source> - <context-group purpose="location"><context context-type="linenumber">221</context></context-group> + <context-group purpose="location"><context context-type="linenumber">295</context></context-group> </trans-unit> - <trans-unit id="_msg1060"> + <trans-unit id="_msg1106"> <source xml:space="preserve">SQLiteDatabase: Unexpected application id. Expected %u, got %u</source> - <context-group purpose="location"><context context-type="linenumber">222</context></context-group> + <context-group purpose="location"><context context-type="linenumber">296</context></context-group> </trans-unit> - <trans-unit id="_msg1061"> + <trans-unit id="_msg1107"> <source xml:space="preserve">Section [%s] is not recognized.</source> - <context-group purpose="location"><context context-type="linenumber">223</context></context-group> + <context-group purpose="location"><context context-type="linenumber">297</context></context-group> </trans-unit> - <trans-unit id="_msg1062"> + <trans-unit id="_msg1108"> <source xml:space="preserve">Signing transaction failed</source> - <context-group purpose="location"><context context-type="linenumber">224</context></context-group> + <context-group purpose="location"><context context-type="linenumber">298</context></context-group> </trans-unit> - <trans-unit id="_msg1063"> + <trans-unit id="_msg1109"> <source xml:space="preserve">Specified -walletdir "%s" does not exist</source> - <context-group purpose="location"><context context-type="linenumber">225</context></context-group> + <context-group purpose="location"><context context-type="linenumber">299</context></context-group> </trans-unit> - <trans-unit id="_msg1064"> + <trans-unit id="_msg1110"> <source xml:space="preserve">Specified -walletdir "%s" is a relative path</source> - <context-group purpose="location"><context context-type="linenumber">226</context></context-group> + <context-group purpose="location"><context context-type="linenumber">300</context></context-group> </trans-unit> - <trans-unit id="_msg1065"> + <trans-unit id="_msg1111"> <source xml:space="preserve">Specified -walletdir "%s" is not a directory</source> - <context-group purpose="location"><context context-type="linenumber">227</context></context-group> + <context-group purpose="location"><context context-type="linenumber">301</context></context-group> </trans-unit> - <trans-unit id="_msg1066"> + <trans-unit id="_msg1112"> <source xml:space="preserve">Specified blocks directory "%s" does not exist.</source> - <context-group purpose="location"><context context-type="linenumber">228</context></context-group> + <context-group purpose="location"><context context-type="linenumber">302</context></context-group> </trans-unit> - <trans-unit id="_msg1067"> + <trans-unit id="_msg1113"> <source xml:space="preserve">Starting network threads…</source> - <context-group purpose="location"><context context-type="linenumber">229</context></context-group> + <context-group purpose="location"><context context-type="linenumber">303</context></context-group> </trans-unit> - <trans-unit id="_msg1068"> + <trans-unit id="_msg1114"> <source xml:space="preserve">The source code is available from %s.</source> - <context-group purpose="location"><context context-type="linenumber">230</context></context-group> + <context-group purpose="location"><context context-type="linenumber">304</context></context-group> </trans-unit> - <trans-unit id="_msg1069"> + <trans-unit id="_msg1115"> <source xml:space="preserve">The specified config file %s does not exist</source> - <context-group purpose="location"><context context-type="linenumber">231</context></context-group> + <context-group purpose="location"><context context-type="linenumber">305</context></context-group> </trans-unit> - <trans-unit id="_msg1070"> + <trans-unit id="_msg1116"> <source xml:space="preserve">The transaction amount is too small to pay the fee</source> - <context-group purpose="location"><context context-type="linenumber">232</context></context-group> + <context-group purpose="location"><context context-type="linenumber">306</context></context-group> </trans-unit> - <trans-unit id="_msg1071"> + <trans-unit id="_msg1117"> <source xml:space="preserve">The wallet will avoid paying less than the minimum relay fee.</source> - <context-group purpose="location"><context context-type="linenumber">233</context></context-group> + <context-group purpose="location"><context context-type="linenumber">307</context></context-group> </trans-unit> - <trans-unit id="_msg1072"> + <trans-unit id="_msg1118"> <source xml:space="preserve">This is experimental software.</source> - <context-group purpose="location"><context context-type="linenumber">234</context></context-group> + <context-group purpose="location"><context context-type="linenumber">308</context></context-group> </trans-unit> - <trans-unit id="_msg1073"> + <trans-unit id="_msg1119"> <source xml:space="preserve">This is the minimum transaction fee you pay on every transaction.</source> - <context-group purpose="location"><context context-type="linenumber">235</context></context-group> + <context-group purpose="location"><context context-type="linenumber">309</context></context-group> </trans-unit> - <trans-unit id="_msg1074"> + <trans-unit id="_msg1120"> <source xml:space="preserve">This is the transaction fee you will pay if you send a transaction.</source> - <context-group purpose="location"><context context-type="linenumber">236</context></context-group> + <context-group purpose="location"><context context-type="linenumber">310</context></context-group> </trans-unit> - <trans-unit id="_msg1075"> + <trans-unit id="_msg1121"> <source xml:space="preserve">Transaction amount too small</source> - <context-group purpose="location"><context context-type="linenumber">237</context></context-group> + <context-group purpose="location"><context context-type="linenumber">311</context></context-group> </trans-unit> - <trans-unit id="_msg1076"> + <trans-unit id="_msg1122"> <source xml:space="preserve">Transaction amounts must not be negative</source> - <context-group purpose="location"><context context-type="linenumber">238</context></context-group> + <context-group purpose="location"><context context-type="linenumber">312</context></context-group> </trans-unit> - <trans-unit id="_msg1077"> + <trans-unit id="_msg1123"> <source xml:space="preserve">Transaction change output index out of range</source> - <context-group purpose="location"><context context-type="linenumber">239</context></context-group> + <context-group purpose="location"><context context-type="linenumber">313</context></context-group> </trans-unit> - <trans-unit id="_msg1078"> + <trans-unit id="_msg1124"> <source xml:space="preserve">Transaction has too long of a mempool chain</source> - <context-group purpose="location"><context context-type="linenumber">240</context></context-group> + <context-group purpose="location"><context context-type="linenumber">314</context></context-group> </trans-unit> - <trans-unit id="_msg1079"> + <trans-unit id="_msg1125"> <source xml:space="preserve">Transaction must have at least one recipient</source> - <context-group purpose="location"><context context-type="linenumber">241</context></context-group> + <context-group purpose="location"><context context-type="linenumber">315</context></context-group> </trans-unit> - <trans-unit id="_msg1080"> + <trans-unit id="_msg1126"> <source xml:space="preserve">Transaction needs a change address, but we can't generate it.</source> - <context-group purpose="location"><context context-type="linenumber">242</context></context-group> + <context-group purpose="location"><context context-type="linenumber">316</context></context-group> </trans-unit> - <trans-unit id="_msg1081"> + <trans-unit id="_msg1127"> <source xml:space="preserve">Transaction too large</source> - <context-group purpose="location"><context context-type="linenumber">243</context></context-group> + <context-group purpose="location"><context context-type="linenumber">317</context></context-group> </trans-unit> - <trans-unit id="_msg1082"> + <trans-unit id="_msg1128"> + <source xml:space="preserve">Unable to allocate memory for -maxsigcachesize: '%s' MiB</source> + <context-group purpose="location"><context context-type="linenumber">318</context></context-group> + </trans-unit> + <trans-unit id="_msg1129"> <source xml:space="preserve">Unable to bind to %s on this computer (bind returned error %s)</source> - <context-group purpose="location"><context context-type="linenumber">244</context></context-group> + <context-group purpose="location"><context context-type="linenumber">319</context></context-group> </trans-unit> - <trans-unit id="_msg1083"> + <trans-unit id="_msg1130"> <source xml:space="preserve">Unable to bind to %s on this computer. %s is probably already running.</source> - <context-group purpose="location"><context context-type="linenumber">245</context></context-group> + <context-group purpose="location"><context context-type="linenumber">320</context></context-group> </trans-unit> - <trans-unit id="_msg1084"> + <trans-unit id="_msg1131"> <source xml:space="preserve">Unable to create the PID file '%s': %s</source> - <context-group purpose="location"><context context-type="linenumber">246</context></context-group> + <context-group purpose="location"><context context-type="linenumber">321</context></context-group> </trans-unit> - <trans-unit id="_msg1085"> + <trans-unit id="_msg1132"> + <source xml:space="preserve">Unable to find UTXO for external input</source> + <context-group purpose="location"><context context-type="linenumber">322</context></context-group> + </trans-unit> + <trans-unit id="_msg1133"> <source xml:space="preserve">Unable to generate initial keys</source> - <context-group purpose="location"><context context-type="linenumber">247</context></context-group> + <context-group purpose="location"><context context-type="linenumber">323</context></context-group> </trans-unit> - <trans-unit id="_msg1086"> + <trans-unit id="_msg1134"> <source xml:space="preserve">Unable to generate keys</source> - <context-group purpose="location"><context context-type="linenumber">248</context></context-group> + <context-group purpose="location"><context context-type="linenumber">324</context></context-group> </trans-unit> - <trans-unit id="_msg1087"> + <trans-unit id="_msg1135"> <source xml:space="preserve">Unable to open %s for writing</source> - <context-group purpose="location"><context context-type="linenumber">249</context></context-group> + <context-group purpose="location"><context context-type="linenumber">325</context></context-group> </trans-unit> - <trans-unit id="_msg1088"> + <trans-unit id="_msg1136"> <source xml:space="preserve">Unable to parse -maxuploadtarget: '%s'</source> - <context-group purpose="location"><context context-type="linenumber">250</context></context-group> + <context-group purpose="location"><context context-type="linenumber">326</context></context-group> </trans-unit> - <trans-unit id="_msg1089"> + <trans-unit id="_msg1137"> <source xml:space="preserve">Unable to start HTTP server. See debug log for details.</source> - <context-group purpose="location"><context context-type="linenumber">251</context></context-group> + <context-group purpose="location"><context context-type="linenumber">327</context></context-group> </trans-unit> - <trans-unit id="_msg1090"> + <trans-unit id="_msg1138"> + <source xml:space="preserve">Unable to unload the wallet before migrating</source> + <context-group purpose="location"><context context-type="linenumber">328</context></context-group> + </trans-unit> + <trans-unit id="_msg1139"> <source xml:space="preserve">Unknown -blockfilterindex value %s.</source> - <context-group purpose="location"><context context-type="linenumber">252</context></context-group> + <context-group purpose="location"><context context-type="linenumber">329</context></context-group> </trans-unit> - <trans-unit id="_msg1091"> + <trans-unit id="_msg1140"> <source xml:space="preserve">Unknown address type '%s'</source> - <context-group purpose="location"><context context-type="linenumber">253</context></context-group> + <context-group purpose="location"><context context-type="linenumber">330</context></context-group> </trans-unit> - <trans-unit id="_msg1092"> + <trans-unit id="_msg1141"> <source xml:space="preserve">Unknown change type '%s'</source> - <context-group purpose="location"><context context-type="linenumber">254</context></context-group> + <context-group purpose="location"><context context-type="linenumber">331</context></context-group> </trans-unit> - <trans-unit id="_msg1093"> + <trans-unit id="_msg1142"> <source xml:space="preserve">Unknown network specified in -onlynet: '%s'</source> - <context-group purpose="location"><context context-type="linenumber">255</context></context-group> + <context-group purpose="location"><context context-type="linenumber">332</context></context-group> </trans-unit> - <trans-unit id="_msg1094"> + <trans-unit id="_msg1143"> <source xml:space="preserve">Unknown new rules activated (versionbit %i)</source> - <context-group purpose="location"><context context-type="linenumber">256</context></context-group> + <context-group purpose="location"><context context-type="linenumber">333</context></context-group> </trans-unit> - <trans-unit id="_msg1095"> - <source xml:space="preserve">Unsupported logging category %s=%s.</source> - <context-group purpose="location"><context context-type="linenumber">257</context></context-group> + <trans-unit id="_msg1144"> + <source xml:space="preserve">Unsupported global logging level -loglevel=%s. Valid values: %s.</source> + <context-group purpose="location"><context context-type="linenumber">334</context></context-group> </trans-unit> - <trans-unit id="_msg1096"> - <source xml:space="preserve">Upgrading UTXO database</source> - <context-group purpose="location"><context context-type="linenumber">258</context></context-group> + <trans-unit id="_msg1145"> + <source xml:space="preserve">Unsupported logging category %s=%s.</source> + <context-group purpose="location"><context context-type="linenumber">335</context></context-group> </trans-unit> - <trans-unit id="_msg1097"> + <trans-unit id="_msg1146"> <source xml:space="preserve">User Agent comment (%s) contains unsafe characters.</source> - <context-group purpose="location"><context context-type="linenumber">259</context></context-group> + <context-group purpose="location"><context context-type="linenumber">336</context></context-group> </trans-unit> - <trans-unit id="_msg1098"> + <trans-unit id="_msg1147"> <source xml:space="preserve">Verifying blocks…</source> - <context-group purpose="location"><context context-type="linenumber">260</context></context-group> + <context-group purpose="location"><context context-type="linenumber">337</context></context-group> </trans-unit> - <trans-unit id="_msg1099"> + <trans-unit id="_msg1148"> <source xml:space="preserve">Verifying wallet(s)…</source> - <context-group purpose="location"><context context-type="linenumber">261</context></context-group> + <context-group purpose="location"><context context-type="linenumber">338</context></context-group> </trans-unit> - <trans-unit id="_msg1100"> + <trans-unit id="_msg1149"> <source xml:space="preserve">Wallet needed to be rewritten: restart %s to complete</source> - <context-group purpose="location"><context context-type="linenumber">262</context></context-group> + <context-group purpose="location"><context context-type="linenumber">339</context></context-group> </trans-unit> </group> </body></file> diff --git a/src/qt/main.cpp b/src/qt/main.cpp index 6e772d58c5..e8f39584ad 100644 --- a/src/qt/main.cpp +++ b/src/qt/main.cpp @@ -4,6 +4,7 @@ #include <qt/bitcoin.h> +#include <compat/compat.h> #include <util/translation.h> #include <util/url.h> @@ -18,4 +19,7 @@ extern const std::function<std::string(const char*)> G_TRANSLATION_FUN = [](cons }; UrlDecodeFn* const URL_DECODE = urlDecode; -int main(int argc, char* argv[]) { return GuiMain(argc, argv); } +MAIN_FUNCTION +{ + return GuiMain(argc, argv); +} diff --git a/src/qt/modaloverlay.cpp b/src/qt/modaloverlay.cpp index 97ee75a31f..dfa33764f6 100644 --- a/src/qt/modaloverlay.cpp +++ b/src/qt/modaloverlay.cpp @@ -78,13 +78,16 @@ bool ModalOverlay::event(QEvent* ev) { return QWidget::event(ev); } -void ModalOverlay::setKnownBestHeight(int count, const QDateTime& blockDate) +void ModalOverlay::setKnownBestHeight(int count, const QDateTime& blockDate, bool presync) { - if (count > bestHeaderHeight) { + if (!presync && count > bestHeaderHeight) { bestHeaderHeight = count; bestHeaderDate = blockDate; UpdateHeaderSyncLabel(); } + if (presync) { + UpdateHeaderPresyncLabel(count, blockDate); + } } void ModalOverlay::tipUpdate(int count, const QDateTime& blockDate, double nVerificationProgress) @@ -158,6 +161,11 @@ void ModalOverlay::UpdateHeaderSyncLabel() { ui->numberOfBlocksLeft->setText(tr("Unknown. Syncing Headers (%1, %2%)…").arg(bestHeaderHeight).arg(QString::number(100.0 / (bestHeaderHeight + est_headers_left) * bestHeaderHeight, 'f', 1))); } +void ModalOverlay::UpdateHeaderPresyncLabel(int height, const QDateTime& blockDate) { + int est_headers_left = blockDate.secsTo(QDateTime::currentDateTime()) / Params().GetConsensus().nPowTargetSpacing; + ui->numberOfBlocksLeft->setText(tr("Unknown. Pre-syncing Headers (%1, %2%)…").arg(height).arg(QString::number(100.0 / (height + est_headers_left) * height, 'f', 1))); +} + void ModalOverlay::toggleVisibility() { showHide(layerIsVisible, true); diff --git a/src/qt/modaloverlay.h b/src/qt/modaloverlay.h index 1d8af5cbf6..682c94cd01 100644 --- a/src/qt/modaloverlay.h +++ b/src/qt/modaloverlay.h @@ -26,7 +26,7 @@ public: ~ModalOverlay(); void tipUpdate(int count, const QDateTime& blockDate, double nVerificationProgress); - void setKnownBestHeight(int count, const QDateTime& blockDate); + void setKnownBestHeight(int count, const QDateTime& blockDate, bool presync); // will show or hide the modal layer void showHide(bool hide = false, bool userRequested = false); @@ -52,6 +52,7 @@ private: bool userClosed; QPropertyAnimation m_animation; void UpdateHeaderSyncLabel(); + void UpdateHeaderPresyncLabel(int height, const QDateTime& blockDate); }; #endif // BITCOIN_QT_MODALOVERLAY_H diff --git a/src/qt/optionsdialog.cpp b/src/qt/optionsdialog.cpp index e6ff43a379..2b6711ca40 100644 --- a/src/qt/optionsdialog.cpp +++ b/src/qt/optionsdialog.cpp @@ -10,6 +10,7 @@ #include <qt/forms/ui_optionsdialog.h> #include <qt/bitcoinunits.h> +#include <qt/clientmodel.h> #include <qt/guiconstants.h> #include <qt/guiutil.h> #include <qt/optionsmodel.h> @@ -18,6 +19,7 @@ #include <validation.h> // for DEFAULT_SCRIPTCHECK_THREADS and MAX_SCRIPTCHECK_THREADS #include <netbase.h> #include <txdb.h> // for -dbcache defaults +#include <util/system.h> #include <chrono> @@ -26,7 +28,6 @@ #include <QIntValidator> #include <QLocale> #include <QMessageBox> -#include <QSettings> #include <QSystemTrayIcon> #include <QTimer> @@ -56,10 +57,6 @@ OptionsDialog::OptionsDialog(QWidget *parent, bool enableWallet) : #ifndef USE_NATPMP ui->mapPortNatpmp->setEnabled(false); #endif - connect(this, &QDialog::accepted, [this](){ - QSettings settings; - model->node().mapPort(settings.value("fUseUPnP").toBool(), settings.value("fUseNatpmp").toBool()); - }); ui->proxyIp->setEnabled(false); ui->proxyPort->setEnabled(false); @@ -173,6 +170,11 @@ OptionsDialog::~OptionsDialog() delete ui; } +void OptionsDialog::setClientModel(ClientModel* client_model) +{ + m_client_model = client_model; +} + void OptionsDialog::setModel(OptionsModel *_model) { this->model = _model; @@ -283,14 +285,23 @@ void OptionsDialog::setOkButtonState(bool fState) void OptionsDialog::on_resetButton_clicked() { - if(model) - { + if (model) { // confirmation dialog + /*: Text explaining that the settings changed will not come into effect + until the client is restarted. */ + QString reset_dialog_text = tr("Client restart required to activate changes.") + "<br><br>"; + /*: Text explaining to the user that the client's current settings + will be backed up at a specific location. %1 is a stand-in + argument for the backup location's path. */ + reset_dialog_text.append(tr("Current settings will be backed up at \"%1\".").arg(m_client_model->dataDir()) + "<br><br>"); + /*: Text asking the user to confirm if they would like to proceed + with a client shutdown. */ + reset_dialog_text.append(tr("Client will be shut down. Do you want to proceed?")); + //: Window title text of pop-up window shown when the user has chosen to reset options. QMessageBox::StandardButton btnRetVal = QMessageBox::question(this, tr("Confirm options reset"), - tr("Client restart required to activate changes.") + "<br><br>" + tr("Client will be shut down. Do you want to proceed?"), - QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Cancel); + reset_dialog_text, QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Cancel); - if(btnRetVal == QMessageBox::Cancel) + if (btnRetVal == QMessageBox::Cancel) return; /* reset all options and close GUI */ diff --git a/src/qt/optionsdialog.h b/src/qt/optionsdialog.h index 0b7802536c..e5a19d5025 100644 --- a/src/qt/optionsdialog.h +++ b/src/qt/optionsdialog.h @@ -8,6 +8,7 @@ #include <QDialog> #include <QValidator> +class ClientModel; class OptionsModel; class QValidatedLineEdit; @@ -45,6 +46,7 @@ public: TAB_NETWORK, }; + void setClientModel(ClientModel* client_model); void setModel(OptionsModel *model); void setMapper(); void setCurrentTab(OptionsDialog::Tab tab); @@ -72,6 +74,7 @@ Q_SIGNALS: private: Ui::OptionsDialog *ui; + ClientModel* m_client_model{nullptr}; OptionsModel *model; QDataWidgetMapper *mapper; }; diff --git a/src/qt/optionsmodel.cpp b/src/qt/optionsmodel.cpp index 612d3009c1..0b4359a917 100644 --- a/src/qt/optionsmodel.cpp +++ b/src/qt/optionsmodel.cpp @@ -19,6 +19,7 @@ #include <txdb.h> // for -dbcache defaults #include <util/string.h> #include <validation.h> // For DEFAULT_SCRIPTCHECK_THREADS +#include <wallet/wallet.h> // For DEFAULT_SPEND_ZEROCONF_CHANGE #include <QDebug> #include <QLatin1Char> @@ -26,14 +27,99 @@ #include <QStringList> #include <QVariant> +#include <univalue.h> + const char *DEFAULT_GUI_PROXY_HOST = "127.0.0.1"; static const QString GetDefaultProxyAddress(); -OptionsModel::OptionsModel(interfaces::Node& node, QObject *parent, bool resetSettings) : +/** Map GUI option ID to node setting name. */ +static const char* SettingName(OptionsModel::OptionID option) +{ + switch (option) { + case OptionsModel::DatabaseCache: return "dbcache"; + case OptionsModel::ThreadsScriptVerif: return "par"; + case OptionsModel::SpendZeroConfChange: return "spendzeroconfchange"; + case OptionsModel::ExternalSignerPath: return "signer"; + case OptionsModel::MapPortUPnP: return "upnp"; + case OptionsModel::MapPortNatpmp: return "natpmp"; + case OptionsModel::Listen: return "listen"; + case OptionsModel::Server: return "server"; + case OptionsModel::PruneSize: return "prune"; + case OptionsModel::Prune: return "prune"; + case OptionsModel::ProxyIP: return "proxy"; + case OptionsModel::ProxyPort: return "proxy"; + case OptionsModel::ProxyUse: return "proxy"; + case OptionsModel::ProxyIPTor: return "onion"; + case OptionsModel::ProxyPortTor: return "onion"; + case OptionsModel::ProxyUseTor: return "onion"; + case OptionsModel::Language: return "lang"; + default: throw std::logic_error(strprintf("GUI option %i has no corresponding node setting.", option)); + } +} + +/** Call node.updateRwSetting() with Bitcoin 22.x workaround. */ +static void UpdateRwSetting(interfaces::Node& node, OptionsModel::OptionID option, const util::SettingsValue& value) +{ + if (value.isNum() && + (option == OptionsModel::DatabaseCache || + option == OptionsModel::ThreadsScriptVerif || + option == OptionsModel::Prune || + option == OptionsModel::PruneSize)) { + // Write certain old settings as strings, even though they are numbers, + // because Bitcoin 22.x releases try to read these specific settings as + // strings in addOverriddenOption() calls at startup, triggering + // uncaught exceptions in UniValue::get_str(). These errors were fixed + // in later releases by https://github.com/bitcoin/bitcoin/pull/24498. + // If new numeric settings are added, they can be written as numbers + // instead of strings, because bitcoin 22.x will not try to read these. + node.updateRwSetting(SettingName(option), value.getValStr()); + } else { + node.updateRwSetting(SettingName(option), value); + } +} + +//! Convert enabled/size values to bitcoin -prune setting. +static util::SettingsValue PruneSetting(bool prune_enabled, int prune_size_gb) +{ + assert(!prune_enabled || prune_size_gb >= 1); // PruneSizeGB and ParsePruneSizeGB never return less + return prune_enabled ? PruneGBtoMiB(prune_size_gb) : 0; +} + +//! Get pruning enabled value to show in GUI from bitcoin -prune setting. +static bool PruneEnabled(const util::SettingsValue& prune_setting) +{ + // -prune=1 setting is manual pruning mode, so disabled for purposes of the gui + return SettingToInt(prune_setting, 0) > 1; +} + +//! Get pruning size value to show in GUI from bitcoin -prune setting. If +//! pruning is not enabled, just show default recommended pruning size (2GB). +static int PruneSizeGB(const util::SettingsValue& prune_setting) +{ + int value = SettingToInt(prune_setting, 0); + return value > 1 ? PruneMiBtoGB(value) : DEFAULT_PRUNE_TARGET_GB; +} + +//! Parse pruning size value provided by user in GUI or loaded from QSettings +//! (windows registry key or qt .conf file). Smallest value that the GUI can +//! display is 1 GB, so round up if anything less is parsed. +static int ParsePruneSizeGB(const QVariant& prune_size) +{ + return std::max(1, prune_size.toInt()); +} + +struct ProxySetting { + bool is_set; + QString ip; + QString port; +}; +static ProxySetting ParseProxyString(const std::string& proxy); +static std::string ProxyString(bool is_set, QString ip, QString port); + +OptionsModel::OptionsModel(interfaces::Node& node, QObject *parent) : QAbstractListModel(parent), m_node{node} { - Init(resetSettings); } void OptionsModel::addOverriddenOption(const std::string &option) @@ -42,10 +128,17 @@ void OptionsModel::addOverriddenOption(const std::string &option) } // Writes all missing QSettings with their default values -void OptionsModel::Init(bool resetSettings) +bool OptionsModel::Init(bilingual_str& error) { - if (resetSettings) - Reset(); + // Initialize display settings from stored settings. + m_prune_size_gb = PruneSizeGB(node().getPersistentSetting("prune")); + ProxySetting proxy = ParseProxyString(SettingToString(node().getPersistentSetting("proxy"), GetDefaultProxyAddress().toStdString())); + m_proxy_ip = proxy.ip; + m_proxy_port = proxy.port; + ProxySetting onion = ParseProxyString(SettingToString(node().getPersistentSetting("onion"), GetDefaultProxyAddress().toStdString())); + m_onion_ip = onion.ip; + m_onion_port = onion.port; + language = QString::fromStdString(SettingToString(node().getPersistentSetting("lang"), "")); checkAndMigrate(); @@ -98,130 +191,43 @@ void OptionsModel::Init(bool resetSettings) // These are shared with the core or have a command-line parameter // and we want command-line parameters to overwrite the GUI settings. - // + for (OptionID option : {DatabaseCache, ThreadsScriptVerif, SpendZeroConfChange, ExternalSignerPath, MapPortUPnP, + MapPortNatpmp, Listen, Server, Prune, ProxyUse, ProxyUseTor, Language}) { + std::string setting = SettingName(option); + if (node().isSettingIgnored(setting)) addOverriddenOption("-" + setting); + try { + getOption(option); + } catch (const std::exception& e) { + // This handles exceptions thrown by univalue that can happen if + // settings in settings.json don't have the expected types. + error.original = strprintf("Could not read setting \"%s\", %s.", setting, e.what()); + error.translated = tr("Could not read setting \"%1\", %2.").arg(QString::fromStdString(setting), e.what()).toStdString(); + return false; + } + } + // If setting doesn't exist create it with defaults. - // - // If gArgs.SoftSetArg() or gArgs.SoftSetBoolArg() return false we were overridden - // by command-line and show this in the UI. // Main - if (!settings.contains("bPrune")) - settings.setValue("bPrune", false); - if (!settings.contains("nPruneSize")) - settings.setValue("nPruneSize", DEFAULT_PRUNE_TARGET_GB); - SetPruneEnabled(settings.value("bPrune").toBool()); - - if (!settings.contains("nDatabaseCache")) - settings.setValue("nDatabaseCache", (qint64)nDefaultDbCache); - if (!gArgs.SoftSetArg("-dbcache", settings.value("nDatabaseCache").toString().toStdString())) - addOverriddenOption("-dbcache"); - - if (!settings.contains("nThreadsScriptVerif")) - settings.setValue("nThreadsScriptVerif", DEFAULT_SCRIPTCHECK_THREADS); - if (!gArgs.SoftSetArg("-par", settings.value("nThreadsScriptVerif").toString().toStdString())) - addOverriddenOption("-par"); - if (!settings.contains("strDataDir")) settings.setValue("strDataDir", GUIUtil::getDefaultDataDirectory()); // Wallet #ifdef ENABLE_WALLET - if (!settings.contains("bSpendZeroConfChange")) - settings.setValue("bSpendZeroConfChange", true); - if (!gArgs.SoftSetBoolArg("-spendzeroconfchange", settings.value("bSpendZeroConfChange").toBool())) - addOverriddenOption("-spendzeroconfchange"); - - if (!settings.contains("external_signer_path")) - settings.setValue("external_signer_path", ""); - - if (!gArgs.SoftSetArg("-signer", settings.value("external_signer_path").toString().toStdString())) { - addOverriddenOption("-signer"); - } - if (!settings.contains("SubFeeFromAmount")) { settings.setValue("SubFeeFromAmount", false); } m_sub_fee_from_amount = settings.value("SubFeeFromAmount", false).toBool(); #endif - // Network - if (!settings.contains("fUseUPnP")) - settings.setValue("fUseUPnP", DEFAULT_UPNP); - if (!gArgs.SoftSetBoolArg("-upnp", settings.value("fUseUPnP").toBool())) - addOverriddenOption("-upnp"); - - if (!settings.contains("fUseNatpmp")) { - settings.setValue("fUseNatpmp", DEFAULT_NATPMP); - } - if (!gArgs.SoftSetBoolArg("-natpmp", settings.value("fUseNatpmp").toBool())) { - addOverriddenOption("-natpmp"); - } - - if (!settings.contains("fListen")) - settings.setValue("fListen", DEFAULT_LISTEN); - const bool listen{settings.value("fListen").toBool()}; - if (!gArgs.SoftSetBoolArg("-listen", listen)) { - addOverriddenOption("-listen"); - } else if (!listen) { - // We successfully set -listen=0, thus mimic the logic from InitParameterInteraction(): - // "parameter interaction: -listen=0 -> setting -listenonion=0". - // - // Both -listen and -listenonion default to true. - // - // The call order is: - // - // InitParameterInteraction() - // would set -listenonion=0 if it sees -listen=0, but for bitcoin-qt with - // fListen=false -listen is 1 at this point - // - // OptionsModel::Init() - // (this method) can flip -listen from 1 to 0 if fListen=false - // - // AppInitParameterInteraction() - // raises an error if -listen=0 and -listenonion=1 - gArgs.SoftSetBoolArg("-listenonion", false); - } - - if (!settings.contains("server")) { - settings.setValue("server", false); - } - if (!gArgs.SoftSetBoolArg("-server", settings.value("server").toBool())) { - addOverriddenOption("-server"); - } - - if (!settings.contains("fUseProxy")) - settings.setValue("fUseProxy", false); - if (!settings.contains("addrProxy")) - settings.setValue("addrProxy", GetDefaultProxyAddress()); - // Only try to set -proxy, if user has enabled fUseProxy - if ((settings.value("fUseProxy").toBool() && !gArgs.SoftSetArg("-proxy", settings.value("addrProxy").toString().toStdString()))) - addOverriddenOption("-proxy"); - else if(!settings.value("fUseProxy").toBool() && !gArgs.GetArg("-proxy", "").empty()) - addOverriddenOption("-proxy"); - - if (!settings.contains("fUseSeparateProxyTor")) - settings.setValue("fUseSeparateProxyTor", false); - if (!settings.contains("addrSeparateProxyTor")) - settings.setValue("addrSeparateProxyTor", GetDefaultProxyAddress()); - // Only try to set -onion, if user has enabled fUseSeparateProxyTor - if ((settings.value("fUseSeparateProxyTor").toBool() && !gArgs.SoftSetArg("-onion", settings.value("addrSeparateProxyTor").toString().toStdString()))) - addOverriddenOption("-onion"); - else if(!settings.value("fUseSeparateProxyTor").toBool() && !gArgs.GetArg("-onion", "").empty()) - addOverriddenOption("-onion"); - // Display - if (!settings.contains("language")) - settings.setValue("language", ""); - if (!gArgs.SoftSetArg("-lang", settings.value("language").toString().toStdString())) - addOverriddenOption("-lang"); - - language = settings.value("language").toString(); - if (!settings.contains("UseEmbeddedMonospacedFont")) { settings.setValue("UseEmbeddedMonospacedFont", "true"); } m_use_embedded_monospaced_font = settings.value("UseEmbeddedMonospacedFont").toBool(); Q_EMIT useEmbeddedMonospacedFontChanged(m_use_embedded_monospaced_font); + + return true; } /** Helper function to copy contents from one QSettings to another. @@ -245,6 +251,9 @@ static void BackupSettings(const fs::path& filename, const QSettings& src) void OptionsModel::Reset() { + // Backup and reset settings.json + node().resetSettings(); + QSettings settings; // Backup old settings to chain-specific datadir for troubleshooting @@ -273,21 +282,15 @@ int OptionsModel::rowCount(const QModelIndex & parent) const return OptionIDRowCount; } -struct ProxySetting { - bool is_set; - QString ip; - QString port; -}; - -static ProxySetting GetProxySetting(QSettings &settings, const QString &name) +static ProxySetting ParseProxyString(const QString& proxy) { static const ProxySetting default_val = {false, DEFAULT_GUI_PROXY_HOST, QString("%1").arg(DEFAULT_GUI_PROXY_PORT)}; // Handle the case that the setting is not set at all - if (!settings.contains(name)) { + if (proxy.isEmpty()) { return default_val; } // contains IP at index 0 and port at index 1 - QStringList ip_port = GUIUtil::SplitSkipEmptyParts(settings.value(name).toString(), ":"); + QStringList ip_port = GUIUtil::SplitSkipEmptyParts(proxy, ":"); if (ip_port.size() == 2) { return {true, ip_port.at(0), ip_port.at(1)}; } else { // Invalid: return default @@ -295,39 +298,42 @@ static ProxySetting GetProxySetting(QSettings &settings, const QString &name) } } -static void SetProxySetting(QSettings &settings, const QString &name, const ProxySetting &ip_port) +static ProxySetting ParseProxyString(const std::string& proxy) { - settings.setValue(name, QString{ip_port.ip + QLatin1Char(':') + ip_port.port}); + return ParseProxyString(QString::fromStdString(proxy)); } -static const QString GetDefaultProxyAddress() +static std::string ProxyString(bool is_set, QString ip, QString port) { - return QString("%1:%2").arg(DEFAULT_GUI_PROXY_HOST).arg(DEFAULT_GUI_PROXY_PORT); + return is_set ? QString(ip + ":" + port).toStdString() : ""; } -void OptionsModel::SetPruneEnabled(bool prune, bool force) +static const QString GetDefaultProxyAddress() { - QSettings settings; - settings.setValue("bPrune", prune); - const int64_t prune_target_mib = PruneGBtoMiB(settings.value("nPruneSize").toInt()); - std::string prune_val = prune ? ToString(prune_target_mib) : "0"; - if (force) { - gArgs.ForceSetArg("-prune", prune_val); - return; - } - if (!gArgs.SoftSetArg("-prune", prune_val)) { - addOverriddenOption("-prune"); - } + return QString("%1:%2").arg(DEFAULT_GUI_PROXY_HOST).arg(DEFAULT_GUI_PROXY_PORT); } -void OptionsModel::SetPruneTargetGB(int prune_target_gb, bool force) +void OptionsModel::SetPruneTargetGB(int prune_target_gb) { - const bool prune = prune_target_gb > 0; - if (prune) { - QSettings settings; - settings.setValue("nPruneSize", prune_target_gb); + const util::SettingsValue cur_value = node().getPersistentSetting("prune"); + const util::SettingsValue new_value = PruneSetting(prune_target_gb > 0, prune_target_gb); + + m_prune_size_gb = prune_target_gb; + + // Force setting to take effect. It is still safe to change the value at + // this point because this function is only called after the intro screen is + // shown, before the node starts. + node().forceSetting("prune", new_value); + + // Update settings.json if value configured in intro screen is different + // from saved value. Avoid writing settings.json if bitcoin.conf value + // doesn't need to be overridden. + if (PruneEnabled(cur_value) != PruneEnabled(new_value) || + PruneSizeGB(cur_value) != PruneSizeGB(new_value)) { + // Call UpdateRwSetting() instead of setOption() to avoid setting + // RestartRequired flag + UpdateRwSetting(node(), Prune, new_value); } - SetPruneEnabled(prune, force); } // read QSettings values and return them @@ -356,6 +362,8 @@ bool OptionsModel::setData(const QModelIndex & index, const QVariant & value, in QVariant OptionsModel::getOption(OptionID option) const { + auto setting = [&]{ return node().getPersistentSetting(SettingName(option)); }; + QSettings settings; switch (option) { case StartAtStartup: @@ -366,13 +374,13 @@ QVariant OptionsModel::getOption(OptionID option) const return fMinimizeToTray; case MapPortUPnP: #ifdef USE_UPNP - return settings.value("fUseUPnP"); + return SettingToBool(setting(), DEFAULT_UPNP); #else return false; #endif // USE_UPNP case MapPortNatpmp: #ifdef USE_NATPMP - return settings.value("fUseNatpmp"); + return SettingToBool(setting(), DEFAULT_NATPMP); #else return false; #endif // USE_NATPMP @@ -381,25 +389,25 @@ QVariant OptionsModel::getOption(OptionID option) const // default proxy case ProxyUse: - return settings.value("fUseProxy", false); + return ParseProxyString(SettingToString(setting(), "")).is_set; case ProxyIP: - return GetProxySetting(settings, "addrProxy").ip; + return m_proxy_ip; case ProxyPort: - return GetProxySetting(settings, "addrProxy").port; + return m_proxy_port; // separate Tor proxy case ProxyUseTor: - return settings.value("fUseSeparateProxyTor", false); + return ParseProxyString(SettingToString(setting(), "")).is_set; case ProxyIPTor: - return GetProxySetting(settings, "addrSeparateProxyTor").ip; + return m_onion_ip; case ProxyPortTor: - return GetProxySetting(settings, "addrSeparateProxyTor").port; + return m_onion_port; #ifdef ENABLE_WALLET case SpendZeroConfChange: - return settings.value("bSpendZeroConfChange"); + return SettingToBool(setting(), wallet::DEFAULT_SPEND_ZEROCONF_CHANGE); case ExternalSignerPath: - return settings.value("external_signer_path"); + return QString::fromStdString(SettingToString(setting(), "")); case SubFeeFromAmount: return m_sub_fee_from_amount; #endif @@ -408,7 +416,7 @@ QVariant OptionsModel::getOption(OptionID option) const case ThirdPartyTxUrls: return strThirdPartyTxUrls; case Language: - return settings.value("language"); + return QString::fromStdString(SettingToString(setting(), "")); case UseEmbeddedMonospacedFont: return m_use_embedded_monospaced_font; case CoinControlFeatures: @@ -416,17 +424,17 @@ QVariant OptionsModel::getOption(OptionID option) const case EnablePSBTControls: return settings.value("enable_psbt_controls"); case Prune: - return settings.value("bPrune"); + return PruneEnabled(setting()); case PruneSize: - return settings.value("nPruneSize"); + return m_prune_size_gb; case DatabaseCache: - return settings.value("nDatabaseCache"); + return qlonglong(SettingToInt(setting(), nDefaultDbCache)); case ThreadsScriptVerif: - return settings.value("nThreadsScriptVerif"); + return qlonglong(SettingToInt(setting(), DEFAULT_SCRIPTCHECK_THREADS)); case Listen: - return settings.value("fListen"); + return SettingToBool(setting(), DEFAULT_LISTEN); case Server: - return settings.value("server"); + return SettingToBool(setting(), false); default: return QVariant(); } @@ -434,6 +442,9 @@ QVariant OptionsModel::getOption(OptionID option) const bool OptionsModel::setOption(OptionID option, const QVariant& value) { + auto changed = [&] { return value.isValid() && value != getOption(option); }; + auto update = [&](const util::SettingsValue& value) { return UpdateRwSetting(node(), option, value); }; + bool successful = true; /* set to false on parse error */ QSettings settings; @@ -451,10 +462,16 @@ bool OptionsModel::setOption(OptionID option, const QVariant& value) settings.setValue("fMinimizeToTray", fMinimizeToTray); break; case MapPortUPnP: // core option - can be changed on-the-fly - settings.setValue("fUseUPnP", value.toBool()); + if (changed()) { + update(value.toBool()); + node().mapPort(value.toBool(), getOption(MapPortNatpmp).toBool()); + } break; case MapPortNatpmp: // core option - can be changed on-the-fly - settings.setValue("fUseNatpmp", value.toBool()); + if (changed()) { + update(value.toBool()); + node().mapPort(getOption(MapPortUPnP).toBool(), value.toBool()); + } break; case MinimizeOnClose: fMinimizeOnClose = value.toBool(); @@ -463,66 +480,66 @@ bool OptionsModel::setOption(OptionID option, const QVariant& value) // default proxy case ProxyUse: - if (settings.value("fUseProxy") != value) { - settings.setValue("fUseProxy", value.toBool()); + if (changed()) { + update(ProxyString(value.toBool(), m_proxy_ip, m_proxy_port)); setRestartRequired(true); } break; - case ProxyIP: { - auto ip_port = GetProxySetting(settings, "addrProxy"); - if (!ip_port.is_set || ip_port.ip != value.toString()) { - ip_port.ip = value.toString(); - SetProxySetting(settings, "addrProxy", ip_port); - setRestartRequired(true); + case ProxyIP: + if (changed()) { + m_proxy_ip = value.toString(); + if (getOption(ProxyUse).toBool()) { + update(ProxyString(true, m_proxy_ip, m_proxy_port)); + setRestartRequired(true); + } } - } - break; - case ProxyPort: { - auto ip_port = GetProxySetting(settings, "addrProxy"); - if (!ip_port.is_set || ip_port.port != value.toString()) { - ip_port.port = value.toString(); - SetProxySetting(settings, "addrProxy", ip_port); - setRestartRequired(true); + break; + case ProxyPort: + if (changed()) { + m_proxy_port = value.toString(); + if (getOption(ProxyUse).toBool()) { + update(ProxyString(true, m_proxy_ip, m_proxy_port)); + setRestartRequired(true); + } } - } - break; + break; // separate Tor proxy case ProxyUseTor: - if (settings.value("fUseSeparateProxyTor") != value) { - settings.setValue("fUseSeparateProxyTor", value.toBool()); + if (changed()) { + update(ProxyString(value.toBool(), m_onion_ip, m_onion_port)); setRestartRequired(true); } break; - case ProxyIPTor: { - auto ip_port = GetProxySetting(settings, "addrSeparateProxyTor"); - if (!ip_port.is_set || ip_port.ip != value.toString()) { - ip_port.ip = value.toString(); - SetProxySetting(settings, "addrSeparateProxyTor", ip_port); - setRestartRequired(true); + case ProxyIPTor: + if (changed()) { + m_onion_ip = value.toString(); + if (getOption(ProxyUseTor).toBool()) { + update(ProxyString(true, m_onion_ip, m_onion_port)); + setRestartRequired(true); + } } - } - break; - case ProxyPortTor: { - auto ip_port = GetProxySetting(settings, "addrSeparateProxyTor"); - if (!ip_port.is_set || ip_port.port != value.toString()) { - ip_port.port = value.toString(); - SetProxySetting(settings, "addrSeparateProxyTor", ip_port); - setRestartRequired(true); + break; + case ProxyPortTor: + if (changed()) { + m_onion_port = value.toString(); + if (getOption(ProxyUseTor).toBool()) { + update(ProxyString(true, m_onion_ip, m_onion_port)); + setRestartRequired(true); + } } - } - break; + break; #ifdef ENABLE_WALLET case SpendZeroConfChange: - if (settings.value("bSpendZeroConfChange") != value) { - settings.setValue("bSpendZeroConfChange", value); + if (changed()) { + update(value.toBool()); setRestartRequired(true); } break; case ExternalSignerPath: - if (settings.value("external_signer_path") != value.toString()) { - settings.setValue("external_signer_path", value.toString()); + if (changed()) { + update(value.toString().toStdString()); setRestartRequired(true); } break; @@ -542,8 +559,8 @@ bool OptionsModel::setOption(OptionID option, const QVariant& value) } break; case Language: - if (settings.value("language") != value) { - settings.setValue("language", value); + if (changed()) { + update(value.toString().toStdString()); setRestartRequired(true); } break; @@ -562,38 +579,36 @@ bool OptionsModel::setOption(OptionID option, const QVariant& value) settings.setValue("enable_psbt_controls", m_enable_psbt_controls); break; case Prune: - if (settings.value("bPrune") != value) { - settings.setValue("bPrune", value); + if (changed()) { + update(PruneSetting(value.toBool(), m_prune_size_gb)); setRestartRequired(true); } break; case PruneSize: - if (settings.value("nPruneSize") != value) { - settings.setValue("nPruneSize", value); - setRestartRequired(true); + if (changed()) { + m_prune_size_gb = ParsePruneSizeGB(value); + if (getOption(Prune).toBool()) { + update(PruneSetting(true, m_prune_size_gb)); + setRestartRequired(true); + } } break; case DatabaseCache: - if (settings.value("nDatabaseCache") != value) { - settings.setValue("nDatabaseCache", value); + if (changed()) { + update(static_cast<int64_t>(value.toLongLong())); setRestartRequired(true); } break; case ThreadsScriptVerif: - if (settings.value("nThreadsScriptVerif") != value) { - settings.setValue("nThreadsScriptVerif", value); + if (changed()) { + update(static_cast<int64_t>(value.toLongLong())); setRestartRequired(true); } break; case Listen: - if (settings.value("fListen") != value) { - settings.setValue("fListen", value); - setRestartRequired(true); - } - break; case Server: - if (settings.value("server") != value) { - settings.setValue("server", value); + if (changed()) { + update(value.toBool()); setRestartRequired(true); } break; @@ -654,4 +669,49 @@ void OptionsModel::checkAndMigrate() if (settings.contains("addrSeparateProxyTor") && settings.value("addrSeparateProxyTor").toString().endsWith("%2")) { settings.setValue("addrSeparateProxyTor", GetDefaultProxyAddress()); } + + // Migrate and delete legacy GUI settings that have now moved to <datadir>/settings.json. + auto migrate_setting = [&](OptionID option, const QString& qt_name) { + if (!settings.contains(qt_name)) return; + QVariant value = settings.value(qt_name); + if (node().getPersistentSetting(SettingName(option)).isNull()) { + if (option == ProxyIP) { + ProxySetting parsed = ParseProxyString(value.toString()); + setOption(ProxyIP, parsed.ip); + setOption(ProxyPort, parsed.port); + } else if (option == ProxyIPTor) { + ProxySetting parsed = ParseProxyString(value.toString()); + setOption(ProxyIPTor, parsed.ip); + setOption(ProxyPortTor, parsed.port); + } else { + setOption(option, value); + } + } + settings.remove(qt_name); + }; + + migrate_setting(DatabaseCache, "nDatabaseCache"); + migrate_setting(ThreadsScriptVerif, "nThreadsScriptVerif"); +#ifdef ENABLE_WALLET + migrate_setting(SpendZeroConfChange, "bSpendZeroConfChange"); + migrate_setting(ExternalSignerPath, "external_signer_path"); +#endif + migrate_setting(MapPortUPnP, "fUseUPnP"); + migrate_setting(MapPortNatpmp, "fUseNatpmp"); + migrate_setting(Listen, "fListen"); + migrate_setting(Server, "server"); + migrate_setting(PruneSize, "nPruneSize"); + migrate_setting(Prune, "bPrune"); + migrate_setting(ProxyIP, "addrProxy"); + migrate_setting(ProxyUse, "fUseProxy"); + migrate_setting(ProxyIPTor, "addrSeparateProxyTor"); + migrate_setting(ProxyUseTor, "fUseSeparateProxyTor"); + migrate_setting(Language, "language"); + + // In case migrating QSettings caused any settings value to change, rerun + // parameter interaction code to update other settings. This is particularly + // important for the -listen setting, which should cause -listenonion, -upnp, + // and other settings to default to false if it was set to false. + // (https://github.com/bitcoin-core/gui/issues/567). + node().initParameterInteraction(); } diff --git a/src/qt/optionsmodel.h b/src/qt/optionsmodel.h index 92f80ecf21..42b89c5029 100644 --- a/src/qt/optionsmodel.h +++ b/src/qt/optionsmodel.h @@ -13,6 +13,7 @@ #include <assert.h> +struct bilingual_str; namespace interfaces { class Node; } @@ -41,7 +42,7 @@ class OptionsModel : public QAbstractListModel Q_OBJECT public: - explicit OptionsModel(interfaces::Node& node, QObject *parent = nullptr, bool resetSettings = false); + explicit OptionsModel(interfaces::Node& node, QObject *parent = nullptr); enum OptionID { StartAtStartup, // bool @@ -74,7 +75,7 @@ public: OptionIDRowCount, }; - void Init(bool resetSettings = false); + bool Init(bilingual_str& error); void Reset(); int rowCount(const QModelIndex & parent = QModelIndex()) const override; @@ -98,8 +99,7 @@ public: const QString& getOverriddenByCommandLine() { return strOverriddenByCommandLine; } /* Explicit setters */ - void SetPruneEnabled(bool prune, bool force = false); - void SetPruneTargetGB(int prune_target_gb, bool force = false); + void SetPruneTargetGB(int prune_target_gb); /* Restart flag helper */ void setRestartRequired(bool fRequired); @@ -120,6 +120,16 @@ private: bool fCoinControlFeatures; bool m_sub_fee_from_amount; bool m_enable_psbt_controls; + + //! In-memory settings for display. These are stored persistently by the + //! bitcoin node but it's also nice to store them in memory to prevent them + //! getting cleared when enable/disable toggles are used in the GUI. + int m_prune_size_gb; + QString m_proxy_ip; + QString m_proxy_port; + QString m_onion_ip; + QString m_onion_port; + /* settings that were overridden by command-line */ QString strOverriddenByCommandLine; @@ -128,6 +138,7 @@ private: // Check settings version and upgrade default values if required void checkAndMigrate(); + Q_SIGNALS: void displayUnitChanged(BitcoinUnit unit); void coinControlFeaturesChanged(bool); diff --git a/src/qt/overviewpage.cpp b/src/qt/overviewpage.cpp index a8133f481e..85a3c36f39 100644 --- a/src/qt/overviewpage.cpp +++ b/src/qt/overviewpage.cpp @@ -147,8 +147,6 @@ OverviewPage::OverviewPage(const PlatformStyle *platformStyle, QWidget *parent) { ui->setupUi(this); - m_balances.balance = -1; - // use a SingleColorIcon for the "out of sync warning" icon QIcon icon = m_platform_style->SingleColorIcon(QStringLiteral(":/icons/warning")); ui->labelTransactionsStatus->setIcon(icon); @@ -177,8 +175,9 @@ void OverviewPage::handleTransactionClicked(const QModelIndex &index) void OverviewPage::setPrivacy(bool privacy) { m_privacy = privacy; - if (m_balances.balance != -1) { - setBalance(m_balances); + const auto& balances = walletModel->getCachedBalance(); + if (balances.balance != -1) { + setBalance(balances); } ui->listTransactions->setVisible(!m_privacy); @@ -197,7 +196,6 @@ OverviewPage::~OverviewPage() void OverviewPage::setBalance(const interfaces::WalletBalances& balances) { BitcoinUnit unit = walletModel->getOptionsModel()->getDisplayUnit(); - m_balances = balances; if (walletModel->wallet().isLegacy()) { if (walletModel->wallet().privateKeysDisabled()) { ui->labelBalance->setText(BitcoinUnits::formatWithPrivacy(unit, balances.watch_only_balance, BitcoinUnits::SeparatorStyle::ALWAYS, m_privacy)); @@ -276,14 +274,13 @@ void OverviewPage::setWalletModel(WalletModel *model) ui->listTransactions->setModelColumn(TransactionTableModel::ToAddress); // Keep up to date with wallet - interfaces::Wallet& wallet = model->wallet(); - interfaces::WalletBalances balances = wallet.getBalances(); - setBalance(balances); + setBalance(model->getCachedBalance()); connect(model, &WalletModel::balanceChanged, this, &OverviewPage::setBalance); connect(model->getOptionsModel(), &OptionsModel::displayUnitChanged, this, &OverviewPage::updateDisplayUnit); - updateWatchOnlyLabels(wallet.haveWatchOnly() && !model->wallet().privateKeysDisabled()); + interfaces::Wallet& wallet = model->wallet(); + updateWatchOnlyLabels(wallet.haveWatchOnly() && !wallet.privateKeysDisabled()); connect(model, &WalletModel::notifyWatchonlyChanged, [this](bool showWatchOnly) { updateWatchOnlyLabels(showWatchOnly && !walletModel->wallet().privateKeysDisabled()); }); @@ -306,10 +303,10 @@ void OverviewPage::changeEvent(QEvent* e) void OverviewPage::updateDisplayUnit() { - if(walletModel && walletModel->getOptionsModel()) - { - if (m_balances.balance != -1) { - setBalance(m_balances); + if (walletModel && walletModel->getOptionsModel()) { + const auto& balances = walletModel->getCachedBalance(); + if (balances.balance != -1) { + setBalance(balances); } // Update txdelegate->unit with the current unit diff --git a/src/qt/overviewpage.h b/src/qt/overviewpage.h index 058df1a8c5..56f45907db 100644 --- a/src/qt/overviewpage.h +++ b/src/qt/overviewpage.h @@ -52,7 +52,6 @@ private: Ui::OverviewPage *ui; ClientModel *clientModel; WalletModel *walletModel; - interfaces::WalletBalances m_balances; bool m_privacy{false}; const PlatformStyle* m_platform_style; diff --git a/src/qt/paymentserver.cpp b/src/qt/paymentserver.cpp index be6f604932..9f87c15c94 100644 --- a/src/qt/paymentserver.cpp +++ b/src/qt/paymentserver.cpp @@ -15,7 +15,7 @@ #include <chainparams.h> #include <interfaces/node.h> #include <key_io.h> -#include <node/ui_interface.h> +#include <node/interface_ui.h> #include <policy/policy.h> #include <util/system.h> #include <wallet/wallet.h> diff --git a/src/qt/rpcconsole.cpp b/src/qt/rpcconsole.cpp index b791fd30c4..295450d6b7 100644 --- a/src/qt/rpcconsole.cpp +++ b/src/qt/rpcconsole.cpp @@ -661,7 +661,7 @@ void RPCConsole::setClientModel(ClientModel *model, int bestblock_height, int64_ setNumConnections(model->getNumConnections()); connect(model, &ClientModel::numConnectionsChanged, this, &RPCConsole::setNumConnections); - setNumBlocks(bestblock_height, QDateTime::fromSecsSinceEpoch(bestblock_date), verification_progress, false); + setNumBlocks(bestblock_height, QDateTime::fromSecsSinceEpoch(bestblock_date), verification_progress, SyncType::BLOCK_SYNC); connect(model, &ClientModel::numBlocksChanged, this, &RPCConsole::setNumBlocks); updateNetworkState(); @@ -973,9 +973,9 @@ void RPCConsole::setNetworkActive(bool networkActive) updateNetworkState(); } -void RPCConsole::setNumBlocks(int count, const QDateTime& blockDate, double nVerificationProgress, bool headers) +void RPCConsole::setNumBlocks(int count, const QDateTime& blockDate, double nVerificationProgress, SyncType synctype) { - if (!headers) { + if (synctype == SyncType::BLOCK_SYNC) { ui->numberOfBlocks->setText(QString::number(count)); ui->lastBlockTime->setText(blockDate.toString()); } @@ -1162,7 +1162,6 @@ void RPCConsole::updateDetailWidget() if (!stats->nodeStats.addrLocal.empty()) peerAddrDetails += "<br />" + tr("via %1").arg(QString::fromStdString(stats->nodeStats.addrLocal)); ui->peerHeading->setText(peerAddrDetails); - ui->peerServices->setText(GUIUtil::formatServicesStr(stats->nodeStats.nServices)); QString bip152_hb_settings; 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); @@ -1183,11 +1182,11 @@ void RPCConsole::updateDetailWidget() 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 == NetPermissionFlags::None) { + if (stats->nodeStats.m_permission_flags == NetPermissionFlags::None) { ui->peerPermissions->setText(ts.na); } else { QStringList permissions; - for (const auto& permission : NetPermissions::ToStrings(stats->nodeStats.m_permissionFlags)) { + for (const auto& permission : NetPermissions::ToStrings(stats->nodeStats.m_permission_flags)) { permissions.append(QString::fromStdString(permission)); } ui->peerPermissions->setText(permissions.join(" & ")); @@ -1197,6 +1196,7 @@ void RPCConsole::updateDetailWidget() // This check fails for example if the lock was busy and // nodeStateStats couldn't be fetched. if (stats->fNodeStateStatsAvailable) { + ui->peerServices->setText(GUIUtil::formatServicesStr(stats->nodeStateStats.their_services)); // Sync height is init to -1 if (stats->nodeStateStats.nSyncHeight > -1) { ui->peerSyncHeight->setText(QString("%1").arg(stats->nodeStateStats.nSyncHeight)); diff --git a/src/qt/rpcconsole.h b/src/qt/rpcconsole.h index 1a54fe0cad..a3c713e966 100644 --- a/src/qt/rpcconsole.h +++ b/src/qt/rpcconsole.h @@ -9,6 +9,7 @@ #include <config/bitcoin-config.h> #endif +#include <qt/clientmodel.h> #include <qt/guiutil.h> #include <qt/peertablemodel.h> @@ -19,7 +20,6 @@ #include <QThread> #include <QWidget> -class ClientModel; class PlatformStyle; class RPCExecutor; class RPCTimerInterface; @@ -121,7 +121,7 @@ public Q_SLOTS: /** Set network state shown in the UI */ void setNetworkActive(bool networkActive); /** Set number of blocks and last block date shown in the UI */ - void setNumBlocks(int count, const QDateTime& blockDate, double nVerificationProgress, bool headers); + void setNumBlocks(int count, const QDateTime& blockDate, double nVerificationProgress, SyncType synctype); /** Set size (number of transactions and memory usage) of the mempool in the UI */ void setMempoolSize(long numberOfTxs, size_t dynUsage); /** Go forward or back in history */ diff --git a/src/qt/sendcoinsdialog.cpp b/src/qt/sendcoinsdialog.cpp index d8daccecbd..53c352b393 100644 --- a/src/qt/sendcoinsdialog.cpp +++ b/src/qt/sendcoinsdialog.cpp @@ -21,7 +21,7 @@ #include <chainparams.h> #include <interfaces/node.h> #include <key_io.h> -#include <node/ui_interface.h> +#include <node/interface_ui.h> #include <policy/fees.h> #include <txmempool.h> #include <validation.h> @@ -164,11 +164,9 @@ void SendCoinsDialog::setModel(WalletModel *_model) } } - interfaces::WalletBalances balances = _model->wallet().getBalances(); - setBalance(balances); connect(_model, &WalletModel::balanceChanged, this, &SendCoinsDialog::setBalance); - connect(_model->getOptionsModel(), &OptionsModel::displayUnitChanged, this, &SendCoinsDialog::updateDisplayUnit); - updateDisplayUnit(); + connect(_model->getOptionsModel(), &OptionsModel::displayUnitChanged, this, &SendCoinsDialog::refreshBalance); + refreshBalance(); // Coin Control connect(_model->getOptionsModel(), &OptionsModel::displayUnitChanged, this, &SendCoinsDialog::coinControlUpdateLabels); @@ -543,15 +541,8 @@ void SendCoinsDialog::sendButtonClicked([[maybe_unused]] bool checked) // failed, or more signatures are needed. if (broadcast) { // now send the prepared transaction - WalletModel::SendCoinsReturn sendStatus = model->sendCoins(*m_current_transaction); - // process sendStatus and on error generate message shown to user - processSendCoinsReturn(sendStatus); - - if (sendStatus.status == WalletModel::OK) { - Q_EMIT coinsSent(m_current_transaction->getWtx()->GetHash()); - } else { - send_failure = true; - } + model->sendCoins(*m_current_transaction); + Q_EMIT coinsSent(m_current_transaction->getWtx()->GetHash()); } } if (!send_failure) { @@ -718,9 +709,9 @@ void SendCoinsDialog::setBalance(const interfaces::WalletBalances& balances) } } -void SendCoinsDialog::updateDisplayUnit() +void SendCoinsDialog::refreshBalance() { - setBalance(model->wallet().getBalances()); + setBalance(model->getCachedBalance()); ui->customFee->setDisplayUnit(model->getOptionsModel()->getDisplayUnit()); updateSmartFeeLabel(); } @@ -793,7 +784,7 @@ void SendCoinsDialog::useAvailableBalance(SendCoinsEntry* entry) m_coin_control->fAllowWatchOnly = model->wallet().privateKeysDisabled() && !model->wallet().hasExternalSigner(); // Calculate available amount to send. - CAmount amount = model->wallet().getAvailableBalance(*m_coin_control); + CAmount amount = model->getAvailableBalance(m_coin_control.get()); for (int i = 0; i < ui->entries->count(); ++i) { SendCoinsEntry* e = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(i)->widget()); if (e && !e->isHidden() && e != entry) { @@ -848,7 +839,7 @@ void SendCoinsDialog::updateCoinControlState() m_coin_control->fAllowWatchOnly = model->wallet().privateKeysDisabled() && !model->wallet().hasExternalSigner(); } -void SendCoinsDialog::updateNumberOfBlocks(int count, const QDateTime& blockDate, double nVerificationProgress, bool headers, SynchronizationState sync_state) { +void SendCoinsDialog::updateNumberOfBlocks(int count, const QDateTime& blockDate, double nVerificationProgress, SyncType synctype, SynchronizationState sync_state) { if (sync_state == SynchronizationState::POST_INIT) { updateSmartFeeLabel(); } diff --git a/src/qt/sendcoinsdialog.h b/src/qt/sendcoinsdialog.h index 400503d0c0..dcdf189532 100644 --- a/src/qt/sendcoinsdialog.h +++ b/src/qt/sendcoinsdialog.h @@ -5,6 +5,7 @@ #ifndef BITCOIN_QT_SENDCOINSDIALOG_H #define BITCOIN_QT_SENDCOINSDIALOG_H +#include <qt/clientmodel.h> #include <qt/walletmodel.h> #include <QDialog> @@ -12,7 +13,6 @@ #include <QString> #include <QTimer> -class ClientModel; class PlatformStyle; class SendCoinsEntry; class SendCoinsRecipient; @@ -97,7 +97,7 @@ private Q_SLOTS: void on_buttonMinimizeFee_clicked(); void removeEntry(SendCoinsEntry* entry); void useAvailableBalance(SendCoinsEntry* entry); - void updateDisplayUnit(); + void refreshBalance(); void coinControlFeatureChanged(bool); void coinControlButtonClicked(); void coinControlChangeChecked(int); @@ -111,7 +111,7 @@ private Q_SLOTS: void coinControlClipboardLowOutput(); void coinControlClipboardChange(); void updateFeeSectionControls(); - void updateNumberOfBlocks(int count, const QDateTime& blockDate, double nVerificationProgress, bool headers, SynchronizationState sync_state); + void updateNumberOfBlocks(int count, const QDateTime& blockDate, double nVerificationProgress, SyncType synctype, SynchronizationState sync_state); void updateSmartFeeLabel(); Q_SIGNALS: diff --git a/src/qt/sendcoinsentry.cpp b/src/qt/sendcoinsentry.cpp index 339ac580d8..af514d5a43 100644 --- a/src/qt/sendcoinsentry.cpp +++ b/src/qt/sendcoinsentry.cpp @@ -20,7 +20,7 @@ #include <QClipboard> SendCoinsEntry::SendCoinsEntry(const PlatformStyle *_platformStyle, QWidget *parent) : - QStackedWidget(parent), + QWidget(parent), ui(new Ui::SendCoinsEntry), model(nullptr), platformStyle(_platformStyle) @@ -30,25 +30,16 @@ SendCoinsEntry::SendCoinsEntry(const PlatformStyle *_platformStyle, QWidget *par ui->addressBookButton->setIcon(platformStyle->SingleColorIcon(":/icons/address-book")); ui->pasteButton->setIcon(platformStyle->SingleColorIcon(":/icons/editpaste")); ui->deleteButton->setIcon(platformStyle->SingleColorIcon(":/icons/remove")); - ui->deleteButton_is->setIcon(platformStyle->SingleColorIcon(":/icons/remove")); - ui->deleteButton_s->setIcon(platformStyle->SingleColorIcon(":/icons/remove")); - - setCurrentWidget(ui->SendCoins); if (platformStyle->getUseExtraSpacing()) ui->payToLayout->setSpacing(4); - // normal bitcoin address field GUIUtil::setupAddressWidget(ui->payTo, this); - // just a label for displaying bitcoin address(es) - ui->payTo_is->setFont(GUIUtil::fixedPitchFont()); // Connect signals connect(ui->payAmount, &BitcoinAmountField::valueChanged, this, &SendCoinsEntry::payAmountChanged); connect(ui->checkboxSubtractFeeFromAmount, &QCheckBox::toggled, this, &SendCoinsEntry::subtractFeeFromAmountChanged); connect(ui->deleteButton, &QPushButton::clicked, this, &SendCoinsEntry::deleteClicked); - connect(ui->deleteButton_is, &QPushButton::clicked, this, &SendCoinsEntry::deleteClicked); - connect(ui->deleteButton_s, &QPushButton::clicked, this, &SendCoinsEntry::deleteClicked); connect(ui->useAvailableBalanceButton, &QPushButton::clicked, this, &SendCoinsEntry::useAvailableBalanceClicked); } @@ -103,14 +94,6 @@ void SendCoinsEntry::clear() ui->messageTextLabel->clear(); ui->messageTextLabel->hide(); ui->messageLabel->hide(); - // clear UI elements for unauthenticated payment request - ui->payTo_is->clear(); - ui->memoTextLabel_is->clear(); - ui->payAmount_is->clear(); - // clear UI elements for authenticated payment request - ui->payTo_s->clear(); - ui->memoTextLabel_s->clear(); - ui->payAmount_s->clear(); // update the display unit, to not use the default ("BTC") updateDisplayUnit(); @@ -219,7 +202,7 @@ void SendCoinsEntry::setAmount(const CAmount &amount) bool SendCoinsEntry::isClear() { - return ui->payTo->text().isEmpty() && ui->payTo_is->text().isEmpty() && ui->payTo_s->text().isEmpty(); + return ui->payTo->text().isEmpty(); } void SendCoinsEntry::setFocus() @@ -229,12 +212,8 @@ void SendCoinsEntry::setFocus() void SendCoinsEntry::updateDisplayUnit() { - if(model && model->getOptionsModel()) - { - // Update payAmount with the current unit + if (model && model->getOptionsModel()) { ui->payAmount->setDisplayUnit(model->getOptionsModel()->getDisplayUnit()); - ui->payAmount_is->setDisplayUnit(model->getOptionsModel()->getDisplayUnit()); - ui->payAmount_s->setDisplayUnit(model->getOptionsModel()->getDisplayUnit()); } } @@ -244,11 +223,9 @@ void SendCoinsEntry::changeEvent(QEvent* e) ui->addressBookButton->setIcon(platformStyle->SingleColorIcon(QStringLiteral(":/icons/address-book"))); ui->pasteButton->setIcon(platformStyle->SingleColorIcon(QStringLiteral(":/icons/editpaste"))); ui->deleteButton->setIcon(platformStyle->SingleColorIcon(QStringLiteral(":/icons/remove"))); - ui->deleteButton_is->setIcon(platformStyle->SingleColorIcon(QStringLiteral(":/icons/remove"))); - ui->deleteButton_s->setIcon(platformStyle->SingleColorIcon(QStringLiteral(":/icons/remove"))); } - QStackedWidget::changeEvent(e); + QWidget::changeEvent(e); } bool SendCoinsEntry::updateLabel(const QString &address) diff --git a/src/qt/sendcoinsentry.h b/src/qt/sendcoinsentry.h index e8db1e3a5c..ea9d58fbf8 100644 --- a/src/qt/sendcoinsentry.h +++ b/src/qt/sendcoinsentry.h @@ -7,7 +7,7 @@ #include <qt/sendcoinsrecipient.h> -#include <QStackedWidget> +#include <QWidget> class WalletModel; class PlatformStyle; @@ -22,10 +22,8 @@ namespace Ui { /** * A single entry in the dialog for sending bitcoins. - * Stacked widget, with different UIs for payment requests - * with a strong payee identity. */ -class SendCoinsEntry : public QStackedWidget +class SendCoinsEntry : public QWidget { Q_OBJECT diff --git a/src/qt/splashscreen.cpp b/src/qt/splashscreen.cpp index a4dfffa387..bf04a6dd5f 100644 --- a/src/qt/splashscreen.cpp +++ b/src/qt/splashscreen.cpp @@ -44,7 +44,7 @@ SplashScreen::SplashScreen(const NetworkStyle* networkStyle) QString titleText = PACKAGE_NAME; QString versionText = QString("Version %1").arg(QString::fromStdString(FormatFullVersion())); QString copyrightText = QString::fromUtf8(CopyrightHolders(strprintf("\xc2\xA9 %u-%u ", 2009, COPYRIGHT_YEAR)).c_str()); - QString titleAddText = networkStyle->getTitleAddText(); + const QString& titleAddText = networkStyle->getTitleAddText(); QString font = QApplication::font().toString(); diff --git a/src/qt/test/addressbooktests.cpp b/src/qt/test/addressbooktests.cpp index 3b7a40438b..581735263d 100644 --- a/src/qt/test/addressbooktests.cpp +++ b/src/qt/test/addressbooktests.cpp @@ -128,6 +128,8 @@ void TestAddAddressesToSendBook(interfaces::Node& node) // Initialize relevant QT models. std::unique_ptr<const PlatformStyle> platformStyle(PlatformStyle::instantiate("other")); OptionsModel optionsModel(node); + bilingual_str error; + QVERIFY(optionsModel.Init(error)); ClientModel clientModel(node, &optionsModel); WalletContext& context = *node.walletLoader().context(); AddWallet(context, wallet); diff --git a/src/qt/test/apptests.cpp b/src/qt/test/apptests.cpp index 9648ef6188..6fc7a52435 100644 --- a/src/qt/test/apptests.cpp +++ b/src/qt/test/apptests.cpp @@ -70,14 +70,9 @@ void AppTests::appTests() } #endif - fs::create_directories([] { - BasicTestingSetup test{CBaseChainParams::REGTEST}; // Create a temp data directory to backup the gui settings to - return gArgs.GetDataDirNet() / "blocks"; - }()); - qRegisterMetaType<interfaces::BlockAndHeaderTipInfo>("interfaces::BlockAndHeaderTipInfo"); m_app.parameterSetup(); - m_app.createOptionsModel(true /* reset settings */); + QVERIFY(m_app.createOptionsModel(true /* reset settings */)); QScopedPointer<const NetworkStyle> style(NetworkStyle::instantiate(Params().NetworkIDString())); m_app.setupPlatformStyle(); m_app.createWindow(style.data()); diff --git a/src/qt/test/optiontests.cpp b/src/qt/test/optiontests.cpp index 2ef9230c59..17ffeb220b 100644 --- a/src/qt/test/optiontests.cpp +++ b/src/qt/test/optiontests.cpp @@ -4,6 +4,7 @@ #include <init.h> #include <qt/bitcoin.h> +#include <qt/guiutil.h> #include <qt/test/optiontests.h> #include <test/util/setup_common.h> #include <util/system.h> @@ -13,6 +14,63 @@ #include <univalue.h> +#include <fstream> + +OptionTests::OptionTests(interfaces::Node& node) : m_node(node) +{ + gArgs.LockSettings([&](util::Settings& s) { m_previous_settings = s; }); +} + +void OptionTests::init() +{ + // reset args + gArgs.LockSettings([&](util::Settings& s) { s = m_previous_settings; }); + gArgs.ClearPathCache(); +} + +void OptionTests::migrateSettings() +{ + // Set legacy QSettings and verify that they get cleared and migrated to + // settings.json + QSettings settings; + settings.setValue("nDatabaseCache", 600); + settings.setValue("nThreadsScriptVerif", 12); + settings.setValue("fUseUPnP", false); + settings.setValue("fListen", false); + settings.setValue("bPrune", true); + settings.setValue("nPruneSize", 3); + settings.setValue("fUseProxy", true); + settings.setValue("addrProxy", "proxy:123"); + settings.setValue("fUseSeparateProxyTor", true); + settings.setValue("addrSeparateProxyTor", "onion:234"); + + settings.sync(); + + OptionsModel options{m_node}; + bilingual_str error; + QVERIFY(options.Init(error)); + QVERIFY(!settings.contains("nDatabaseCache")); + QVERIFY(!settings.contains("nThreadsScriptVerif")); + QVERIFY(!settings.contains("fUseUPnP")); + QVERIFY(!settings.contains("fListen")); + QVERIFY(!settings.contains("bPrune")); + QVERIFY(!settings.contains("nPruneSize")); + QVERIFY(!settings.contains("fUseProxy")); + QVERIFY(!settings.contains("addrProxy")); + QVERIFY(!settings.contains("fUseSeparateProxyTor")); + QVERIFY(!settings.contains("addrSeparateProxyTor")); + + std::ifstream file(gArgs.GetDataDirNet() / "settings.json"); + QCOMPARE(std::string(std::istreambuf_iterator<char>(file), std::istreambuf_iterator<char>()).c_str(), "{\n" + " \"dbcache\": \"600\",\n" + " \"listen\": false,\n" + " \"onion\": \"onion:234\",\n" + " \"par\": \"12\",\n" + " \"proxy\": \"proxy:123\",\n" + " \"prune\": \"2861\"\n" + "}\n"); +} + void OptionTests::integerGetArgBug() { // Test regression https://github.com/bitcoin/bitcoin/issues/24457. Ensure @@ -23,7 +81,8 @@ void OptionTests::integerGetArgBug() settings.rw_settings["prune"] = 3814; }); gArgs.WriteSettingsFile(); - OptionsModel{m_node}; + bilingual_str error; + QVERIFY(OptionsModel{m_node}.Init(error)); gArgs.LockSettings([&](util::Settings& settings) { settings.rw_settings.erase("prune"); }); @@ -36,8 +95,6 @@ void OptionTests::parametersInteraction() // It was fixed via https://github.com/bitcoin-core/gui/pull/568. // With fListen=false in ~/.config/Bitcoin/Bitcoin-Qt.conf and all else left as default, // bitcoin-qt should set both -listen and -listenonion to false and start successfully. - gArgs.ClearPathCache(); - gArgs.LockSettings([&](util::Settings& s) { s.forced_settings.erase("listen"); s.forced_settings.erase("listenonion"); @@ -48,7 +105,8 @@ void OptionTests::parametersInteraction() QSettings settings; settings.setValue("fListen", false); - OptionsModel{m_node}; + bilingual_str error; + QVERIFY(OptionsModel{m_node}.Init(error)); const bool expected{false}; @@ -65,3 +123,12 @@ void OptionTests::parametersInteraction() QVERIFY(!settings.contains("fListen")); gArgs.ClearPathCache(); } + +void OptionTests::extractFilter() +{ + QString filter = QString("Partially Signed Transaction (Binary) (*.psbt)"); + QCOMPARE(GUIUtil::ExtractFirstSuffixFromFilter(filter), "psbt"); + + filter = QString("Image (*.png *.jpg)"); + QCOMPARE(GUIUtil::ExtractFirstSuffixFromFilter(filter), "png"); +} diff --git a/src/qt/test/optiontests.h b/src/qt/test/optiontests.h index 257a0b65be..57ec8bd0f2 100644 --- a/src/qt/test/optiontests.h +++ b/src/qt/test/optiontests.h @@ -6,6 +6,8 @@ #define BITCOIN_QT_TEST_OPTIONTESTS_H #include <qt/optionsmodel.h> +#include <univalue.h> +#include <util/settings.h> #include <QObject> @@ -13,14 +15,18 @@ class OptionTests : public QObject { Q_OBJECT public: - explicit OptionTests(interfaces::Node& node) : m_node(node) {} + explicit OptionTests(interfaces::Node& node); private Q_SLOTS: + void init(); // called before each test function execution. + void migrateSettings(); void integerGetArgBug(); void parametersInteraction(); + void extractFilter(); private: interfaces::Node& m_node; + util::Settings m_previous_settings; }; #endif // BITCOIN_QT_TEST_OPTIONTESTS_H diff --git a/src/qt/test/test_main.cpp b/src/qt/test/test_main.cpp index aeedd92834..846fa519ee 100644 --- a/src/qt/test/test_main.cpp +++ b/src/qt/test/test_main.cpp @@ -43,8 +43,6 @@ Q_IMPORT_PLUGIN(QAndroidPlatformIntegrationPlugin) #endif #endif -using node::NodeContext; - const std::function<void(const std::string&)> G_TEST_LOG_FUN{}; const std::function<std::vector<const char*>()> G_TEST_COMMAND_LINE_ARGUMENTS{}; @@ -58,9 +56,10 @@ int main(int argc, char* argv[]) // regtest params. // // All tests must use their own testing setup (if needed). - { + fs::create_directories([] { BasicTestingSetup dummy{CBaseChainParams::REGTEST}; - } + return gArgs.GetDataDirNet() / "blocks"; + }()); std::unique_ptr<interfaces::Init> init = interfaces::MakeGuiInit(argc, argv); gArgs.ForceSetArg("-listen", "0"); diff --git a/src/qt/test/wallettests.cpp b/src/qt/test/wallettests.cpp index bc06f0f23b..b71dfb0e9f 100644 --- a/src/qt/test/wallettests.cpp +++ b/src/qt/test/wallettests.cpp @@ -129,6 +129,13 @@ void BumpFee(TransactionView& view, const uint256& txid, bool expectDisabled, st QVERIFY(text.indexOf(QString::fromStdString(expectError)) != -1); } +void CompareBalance(WalletModel& walletModel, CAmount expected_balance, QLabel* balance_label_to_check) +{ + BitcoinUnit unit = walletModel.getOptionsModel()->getDisplayUnit(); + QString balanceComparison = BitcoinUnits::formatWithUnit(unit, expected_balance, false, BitcoinUnits::SeparatorStyle::ALWAYS); + QCOMPARE(balance_label_to_check->text().trimmed(), balanceComparison); +} + //! Simple qt wallet tests. // // Test widgets can be debugged interactively calling show() on them and @@ -168,14 +175,14 @@ void TestGUI(interfaces::Node& node) if (!wallet->AddWalletDescriptor(w_desc, provider, "", false)) assert(false); CTxDestination dest = GetDestinationForKey(test.coinbaseKey.GetPubKey(), wallet->m_default_address_type); wallet->SetAddressBook(dest, "", "receive"); - wallet->SetLastBlockProcessed(105, node.context()->chainman->ActiveChain().Tip()->GetBlockHash()); + wallet->SetLastBlockProcessed(105, WITH_LOCK(node.context()->chainman->GetMutex(), return node.context()->chainman->ActiveChain().Tip()->GetBlockHash())); } { WalletRescanReserver reserver(*wallet); reserver.reserve(); - CWallet::ScanResult result = wallet->ScanForWalletTransactions(Params().GetConsensus().hashGenesisBlock, 0 /* block height */, {} /* max height */, reserver, true /* fUpdate */); + CWallet::ScanResult result = wallet->ScanForWalletTransactions(Params().GetConsensus().hashGenesisBlock, /*start_height=*/0, /*max_height=*/{}, reserver, /*fUpdate=*/true, /*save_progress=*/false); QCOMPARE(result.status, CWallet::ScanResult::SUCCESS); - QCOMPARE(result.last_scanned_block, node.context()->chainman->ActiveChain().Tip()->GetBlockHash()); + QCOMPARE(result.last_scanned_block, WITH_LOCK(node.context()->chainman->GetMutex(), return node.context()->chainman->ActiveChain().Tip()->GetBlockHash())); QVERIFY(result.last_failed_block.IsNull()); } wallet->SetBroadcastTransactions(true); @@ -185,6 +192,8 @@ void TestGUI(interfaces::Node& node) SendCoinsDialog sendCoinsDialog(platformStyle.get()); TransactionView transactionView(platformStyle.get()); OptionsModel optionsModel(node); + bilingual_str error; + QVERIFY(optionsModel.Init(error)); ClientModel clientModel(node, &optionsModel); WalletContext& context = *node.walletLoader().context(); AddWallet(context, wallet); @@ -193,15 +202,10 @@ void TestGUI(interfaces::Node& node) sendCoinsDialog.setModel(&walletModel); transactionView.setModel(&walletModel); - { - // Check balance in send dialog - QLabel* balanceLabel = sendCoinsDialog.findChild<QLabel*>("labelBalance"); - QString balanceText = balanceLabel->text(); - BitcoinUnit unit = walletModel.getOptionsModel()->getDisplayUnit(); - CAmount balance = walletModel.wallet().getBalance(); - QString balanceComparison = BitcoinUnits::formatWithUnit(unit, balance, false, BitcoinUnits::SeparatorStyle::ALWAYS); - QCOMPARE(balanceText, balanceComparison); - } + // Update walletModel cached balance which will trigger an update for the 'labelBalance' QLabel. + walletModel.pollBalanceChanged(); + // Check balance in send dialog + CompareBalance(walletModel, walletModel.wallet().getBalance(), sendCoinsDialog.findChild<QLabel*>("labelBalance")); // Send two transactions, and verify they are added to transaction list. TransactionTableModel* transactionTableModel = walletModel.getTransactionTableModel(); @@ -221,12 +225,8 @@ void TestGUI(interfaces::Node& node) // Check current balance on OverviewPage OverviewPage overviewPage(platformStyle.get()); overviewPage.setWalletModel(&walletModel); - QLabel* balanceLabel = overviewPage.findChild<QLabel*>("labelBalance"); - QString balanceText = balanceLabel->text().trimmed(); - BitcoinUnit unit = walletModel.getOptionsModel()->getDisplayUnit(); - CAmount balance = walletModel.wallet().getBalance(); - QString balanceComparison = BitcoinUnits::formatWithUnit(unit, balance, false, BitcoinUnits::SeparatorStyle::ALWAYS); - QCOMPARE(balanceText, balanceComparison); + walletModel.pollBalanceChanged(); // Manual balance polling update + CompareBalance(walletModel, walletModel.wallet().getBalance(), overviewPage.findChild<QLabel*>("labelBalance")); // Check Request Payment button ReceiveCoinsDialog receiveCoinsDialog(platformStyle.get()); diff --git a/src/qt/transactionoverviewwidget.cpp b/src/qt/transactionoverviewwidget.cpp new file mode 100644 index 0000000000..360a1364fb --- /dev/null +++ b/src/qt/transactionoverviewwidget.cpp @@ -0,0 +1,27 @@ +// 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 <qt/transactionoverviewwidget.h> + +#include <qt/transactiontablemodel.h> + +#include <QListView> +#include <QSize> +#include <QSizePolicy> + +TransactionOverviewWidget::TransactionOverviewWidget(QWidget* parent) + : QListView(parent) {} + +QSize TransactionOverviewWidget::sizeHint() const +{ + return {sizeHintForColumn(TransactionTableModel::ToAddress), QListView::sizeHint().height()}; +} + +void TransactionOverviewWidget::showEvent(QShowEvent* event) +{ + Q_UNUSED(event); + QSizePolicy sp = sizePolicy(); + sp.setHorizontalPolicy(QSizePolicy::Minimum); + setSizePolicy(sp); +} diff --git a/src/qt/transactionoverviewwidget.h b/src/qt/transactionoverviewwidget.h index 2bdead7bc4..0572e84090 100644 --- a/src/qt/transactionoverviewwidget.h +++ b/src/qt/transactionoverviewwidget.h @@ -5,11 +5,8 @@ #ifndef BITCOIN_QT_TRANSACTIONOVERVIEWWIDGET_H #define BITCOIN_QT_TRANSACTIONOVERVIEWWIDGET_H -#include <qt/transactiontablemodel.h> - #include <QListView> #include <QSize> -#include <QSizePolicy> QT_BEGIN_NAMESPACE class QShowEvent; @@ -21,21 +18,11 @@ class TransactionOverviewWidget : public QListView Q_OBJECT public: - explicit TransactionOverviewWidget(QWidget* parent = nullptr) : QListView(parent) {} - - QSize sizeHint() const override - { - return {sizeHintForColumn(TransactionTableModel::ToAddress), QListView::sizeHint().height()}; - } + explicit TransactionOverviewWidget(QWidget* parent = nullptr); + QSize sizeHint() const override; protected: - void showEvent(QShowEvent* event) override - { - Q_UNUSED(event); - QSizePolicy sp = sizePolicy(); - sp.setHorizontalPolicy(QSizePolicy::Minimum); - setSizePolicy(sp); - } + void showEvent(QShowEvent* event) override; }; #endif // BITCOIN_QT_TRANSACTIONOVERVIEWWIDGET_H diff --git a/src/qt/transactionview.cpp b/src/qt/transactionview.cpp index 47f3ba7e7f..b7432a0d77 100644 --- a/src/qt/transactionview.cpp +++ b/src/qt/transactionview.cpp @@ -17,7 +17,7 @@ #include <qt/transactiontablemodel.h> #include <qt/walletmodel.h> -#include <node/ui_interface.h> +#include <node/interface_ui.h> #include <chrono> #include <optional> diff --git a/src/qt/utilitydialog.cpp b/src/qt/utilitydialog.cpp index e68095f8e5..331487b51d 100644 --- a/src/qt/utilitydialog.cpp +++ b/src/qt/utilitydialog.cpp @@ -17,12 +17,13 @@ #include <util/system.h> #include <util/strencodings.h> -#include <stdio.h> +#include <cstdio> #include <QCloseEvent> #include <QLabel> #include <QMainWindow> -#include <QRegExp> +#include <QRegularExpression> +#include <QString> #include <QTextCursor> #include <QTextTable> #include <QVBoxLayout> @@ -44,9 +45,8 @@ HelpMessageDialog::HelpMessageDialog(QWidget *parent, bool about) : /// HTML-format the license message from the core QString licenseInfoHTML = QString::fromStdString(LicenseInfo()); // Make URLs clickable - QRegExp uri("<(.*)>", Qt::CaseSensitive, QRegExp::RegExp2); - uri.setMinimal(true); // use non-greedy matching - licenseInfoHTML.replace(uri, "<a href=\"\\1\">\\1</a>"); + QRegularExpression uri(QStringLiteral("<(.*)>"), QRegularExpression::InvertedGreedinessOption); + licenseInfoHTML.replace(uri, QStringLiteral("<a href=\"\\1\">\\1</a>")); // Replace newlines with HTML breaks licenseInfoHTML.replace("\n", "<br>"); diff --git a/src/qt/walletcontroller.cpp b/src/qt/walletcontroller.cpp index d27ddf1aba..8762ba9ab3 100644 --- a/src/qt/walletcontroller.cpp +++ b/src/qt/walletcontroller.cpp @@ -262,9 +262,13 @@ void CreateWalletActivity::createWallet() } QTimer::singleShot(500ms, worker(), [this, name, flags] { - std::unique_ptr<interfaces::Wallet> wallet = node().walletLoader().createWallet(name, m_passphrase, flags, m_error_message, m_warning_message); + auto wallet{node().walletLoader().createWallet(name, m_passphrase, flags, m_warning_message)}; - if (wallet) m_wallet_model = m_wallet_controller->getOrCreateWallet(std::move(wallet)); + if (wallet) { + m_wallet_model = m_wallet_controller->getOrCreateWallet(std::move(*wallet)); + } else { + m_error_message = util::ErrorString(wallet); + } QTimer::singleShot(500ms, this, &CreateWalletActivity::finish); }); @@ -293,6 +297,10 @@ void CreateWalletActivity::create() } catch (const std::runtime_error& e) { QMessageBox::critical(nullptr, tr("Can't list signers"), e.what()); } + if (signers.size() > 1) { + QMessageBox::critical(nullptr, tr("Too many external signers found"), QString::fromStdString("More than one external signer found. Please connect only one at a time.")); + signers.clear(); + } m_create_wallet_dialog->setSigners(signers); m_create_wallet_dialog->setWindowModality(Qt::ApplicationModal); @@ -343,9 +351,13 @@ void OpenWalletActivity::open(const std::string& path) tr("Opening Wallet <b>%1</b>…").arg(name.toHtmlEscaped())); QTimer::singleShot(0, worker(), [this, path] { - std::unique_ptr<interfaces::Wallet> wallet = node().walletLoader().loadWallet(path, m_error_message, m_warning_message); + auto wallet{node().walletLoader().loadWallet(path, m_warning_message)}; - if (wallet) m_wallet_model = m_wallet_controller->getOrCreateWallet(std::move(wallet)); + if (wallet) { + m_wallet_model = m_wallet_controller->getOrCreateWallet(std::move(*wallet)); + } else { + m_error_message = util::ErrorString(wallet); + } QTimer::singleShot(0, this, &OpenWalletActivity::finish); }); @@ -373,3 +385,50 @@ void LoadWalletsActivity::load() QTimer::singleShot(0, this, [this] { Q_EMIT finished(); }); }); } + +RestoreWalletActivity::RestoreWalletActivity(WalletController* wallet_controller, QWidget* parent_widget) + : WalletControllerActivity(wallet_controller, parent_widget) +{ +} + +void RestoreWalletActivity::restore(const fs::path& backup_file, const std::string& wallet_name) +{ + QString name = QString::fromStdString(wallet_name); + + showProgressDialog( + //: Title of progress window which is displayed when wallets are being restored. + tr("Restore Wallet"), + /*: Descriptive text of the restore wallets progress window which indicates to + the user that wallets are currently being restored.*/ + tr("Restoring Wallet <b>%1</b>…").arg(name.toHtmlEscaped())); + + QTimer::singleShot(0, worker(), [this, backup_file, wallet_name] { + auto wallet{node().walletLoader().restoreWallet(backup_file, wallet_name, m_warning_message)}; + + if (wallet) { + m_wallet_model = m_wallet_controller->getOrCreateWallet(std::move(*wallet)); + } else { + m_error_message = util::ErrorString(wallet); + } + + QTimer::singleShot(0, this, &RestoreWalletActivity::finish); + }); +} + +void RestoreWalletActivity::finish() +{ + if (!m_error_message.empty()) { + //: Title of message box which is displayed when the wallet could not be restored. + QMessageBox::critical(m_parent_widget, tr("Restore wallet failed"), QString::fromStdString(m_error_message.translated)); + } else if (!m_warning_message.empty()) { + //: Title of message box which is displayed when the wallet is restored with some warning. + QMessageBox::warning(m_parent_widget, tr("Restore wallet warning"), QString::fromStdString(Join(m_warning_message, Untranslated("\n")).translated)); + } else { + //: Title of message box which is displayed when the wallet is successfully restored. + QMessageBox::information(m_parent_widget, tr("Restore wallet message"), QString::fromStdString(Untranslated("Wallet restored successfully \n").translated)); + } + + if (m_wallet_model) Q_EMIT restored(m_wallet_model); + + Q_EMIT finished(); +} diff --git a/src/qt/walletcontroller.h b/src/qt/walletcontroller.h index 24f85c258c..fcd65756c6 100644 --- a/src/qt/walletcontroller.h +++ b/src/qt/walletcontroller.h @@ -33,6 +33,10 @@ class Node; class Wallet; } // namespace interfaces +namespace fs { +class path; +} + class AskPassphraseDialog; class CreateWalletActivity; class CreateWalletDialog; @@ -155,4 +159,20 @@ public: void load(); }; +class RestoreWalletActivity : public WalletControllerActivity +{ + Q_OBJECT + +public: + RestoreWalletActivity(WalletController* wallet_controller, QWidget* parent_widget); + + void restore(const fs::path& backup_file, const std::string& wallet_name); + +Q_SIGNALS: + void restored(WalletModel* wallet_model); + +private: + void finish(); +}; + #endif // BITCOIN_QT_WALLETCONTROLLER_H diff --git a/src/qt/walletframe.cpp b/src/qt/walletframe.cpp index 11bea85b21..8dc97e66a2 100644 --- a/src/qt/walletframe.cpp +++ b/src/qt/walletframe.cpp @@ -5,7 +5,7 @@ #include <qt/walletframe.h> #include <fs.h> -#include <node/ui_interface.h> +#include <node/interface_ui.h> #include <psbt.h> #include <qt/guiutil.h> #include <qt/overviewpage.h> @@ -213,6 +213,14 @@ void WalletFrame::gotoLoadPSBT(bool from_clipboard) } std::ifstream in{filename.toLocal8Bit().data(), std::ios::binary}; data.assign(std::istream_iterator<unsigned char>{in}, {}); + + // Some psbt files may be base64 strings in the file rather than binary data + std::string b64_str{data.begin(), data.end()}; + b64_str.erase(b64_str.find_last_not_of(" \t\n\r\f\v") + 1); // Trim trailing whitespace + auto b64_dec = DecodeBase64(b64_str); + if (b64_dec.has_value()) { + data = b64_dec.value(); + } } std::string error; diff --git a/src/qt/walletmodel.cpp b/src/qt/walletmodel.cpp index 1f6c90af4a..c6f3f5b00c 100644 --- a/src/qt/walletmodel.cpp +++ b/src/qt/walletmodel.cpp @@ -21,7 +21,7 @@ #include <interfaces/handler.h> #include <interfaces/node.h> #include <key_io.h> -#include <node/ui_interface.h> +#include <node/interface_ui.h> #include <psbt.h> #include <util/system.h> // for GetBoolArg #include <util/translation.h> @@ -67,6 +67,10 @@ WalletModel::~WalletModel() void WalletModel::startPollBalance() { + // Update the cached balance right away, so every view can make use of it, + // so them don't need to waste resources recalculating it. + pollBalanceChanged(); + // This timer will be fired repeatedly to update the balance // Since the QTimer::timeout is a private signal, it cannot be used // in the GUIUtil::ExceptionSafeConnect directly. @@ -120,12 +124,17 @@ void WalletModel::pollBalanceChanged() void WalletModel::checkBalanceChanged(const interfaces::WalletBalances& new_balances) { - if(new_balances.balanceChanged(m_cached_balances)) { + if (new_balances.balanceChanged(m_cached_balances)) { m_cached_balances = new_balances; Q_EMIT balanceChanged(new_balances); } } +interfaces::WalletBalances WalletModel::getCachedBalance() const +{ + return m_cached_balances; +} + void WalletModel::updateTransaction() { // Balance and number of transactions might have changed @@ -194,7 +203,9 @@ WalletModel::SendCoinsReturn WalletModel::prepareTransaction(WalletModelTransact return DuplicateAddress; } - CAmount nBalance = m_wallet->getAvailableBalance(coinControl); + // If no coin was manually selected, use the cached balance + // Future: can merge this call with 'createTransaction'. + CAmount nBalance = getAvailableBalance(&coinControl); if(total > nBalance) { @@ -204,10 +215,10 @@ WalletModel::SendCoinsReturn WalletModel::prepareTransaction(WalletModelTransact { CAmount nFeeRequired = 0; int nChangePosRet = -1; - bilingual_str error; auto& newTx = transaction.getWtx(); - newTx = m_wallet->createTransaction(vecSend, coinControl, !wallet().privateKeysDisabled() /* sign */, nChangePosRet, nFeeRequired, error); + const auto& res = m_wallet->createTransaction(vecSend, coinControl, !wallet().privateKeysDisabled() /* sign */, nChangePosRet, nFeeRequired); + newTx = res ? *res : nullptr; transaction.setTransactionFee(nFeeRequired); if (fSubtractFeeFromAmount && newTx) transaction.reassignAmounts(nChangePosRet); @@ -218,7 +229,7 @@ WalletModel::SendCoinsReturn WalletModel::prepareTransaction(WalletModelTransact { return SendCoinsReturn(AmountWithFeeExceedsBalance); } - Q_EMIT message(tr("Send Coins"), QString::fromStdString(error.translated), + Q_EMIT message(tr("Send Coins"), QString::fromStdString(util::ErrorString(res).translated), CClientUIInterface::MSG_ERROR); return TransactionCreationFailed; } @@ -234,7 +245,7 @@ WalletModel::SendCoinsReturn WalletModel::prepareTransaction(WalletModelTransact return SendCoinsReturn(OK); } -WalletModel::SendCoinsReturn WalletModel::sendCoins(WalletModelTransaction &transaction) +void WalletModel::sendCoins(WalletModelTransaction& transaction) { QByteArray transaction_array; /* store serialized transaction */ @@ -280,8 +291,6 @@ WalletModel::SendCoinsReturn WalletModel::sendCoins(WalletModelTransaction &tran } checkBalanceChanged(m_wallet->getBalances()); // update balance immediately, otherwise there could be a short noticeable delay until pollBalanceChanged hits - - return SendCoinsReturn(OK); } OptionsModel* WalletModel::getOptionsModel() const @@ -308,6 +317,11 @@ WalletModel::EncryptionStatus WalletModel::getEncryptionStatus() const { if(!m_wallet->isCrypted()) { + // A previous bug allowed for watchonly wallets to be encrypted (encryption keys set, but nothing is actually encrypted). + // To avoid misrepresenting the encryption status of such wallets, we only return NoKeys for watchonly wallets that are unencrypted. + if (m_wallet->privateKeysDisabled()) { + return NoKeys; + } return Unencrypted; } else if(m_wallet->isLocked()) @@ -599,3 +613,8 @@ uint256 WalletModel::getLastBlockProcessed() const { return m_client_model ? m_client_model->getBestBlockHash() : uint256{}; } + +CAmount WalletModel::getAvailableBalance(const CCoinControl* control) +{ + return control && control->HasSelected() ? wallet().getAvailableBalance(*control) : getCachedBalance().balance; +} diff --git a/src/qt/walletmodel.h b/src/qt/walletmodel.h index d524d48111..73dfe0386a 100644 --- a/src/qt/walletmodel.h +++ b/src/qt/walletmodel.h @@ -71,6 +71,7 @@ public: enum EncryptionStatus { + NoKeys, // wallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) Unencrypted, // !wallet->IsCrypted() Locked, // wallet->IsCrypted() && wallet->IsLocked() Unlocked // wallet->IsCrypted() && !wallet->IsLocked() @@ -102,7 +103,7 @@ public: SendCoinsReturn prepareTransaction(WalletModelTransaction &transaction, const wallet::CCoinControl& coinControl); // Send coins to a list of recipients - SendCoinsReturn sendCoins(WalletModelTransaction &transaction); + void sendCoins(WalletModelTransaction& transaction); // Wallet encryption bool setWalletEncrypted(const SecureString& passphrase); @@ -154,6 +155,13 @@ public: uint256 getLastBlockProcessed() const; + // Retrieve the cached wallet balance + interfaces::WalletBalances getCachedBalance() const; + + // If coin control has selected outputs, searches the total amount inside the wallet. + // Otherwise, uses the wallet's cached available balance. + CAmount getAvailableBalance(const wallet::CCoinControl* control); + private: std::unique_ptr<interfaces::Wallet> m_wallet; std::unique_ptr<interfaces::Handler> m_handler_unload; diff --git a/src/qt/walletview.cpp b/src/qt/walletview.cpp index 2f92c57607..10fc0fb6d0 100644 --- a/src/qt/walletview.cpp +++ b/src/qt/walletview.cpp @@ -19,7 +19,7 @@ #include <qt/walletmodel.h> #include <interfaces/node.h> -#include <node/ui_interface.h> +#include <node/interface_ui.h> #include <util/strencodings.h> #include <QAction> diff --git a/src/random.cpp b/src/random.cpp index 74ceb3d2a3..eab54630b1 100644 --- a/src/random.cpp +++ b/src/random.cpp @@ -10,7 +10,7 @@ #include <crypto/sha512.h> #include <support/cleanse.h> #ifdef WIN32 -#include <compat.h> // for Windows API +#include <compat/compat.h> #include <wincrypt.h> #endif #include <logging.h> @@ -21,7 +21,7 @@ #include <util/time.h> // for GetTimeMicros() #include <cmath> -#include <stdlib.h> +#include <cstdlib> #include <thread> #ifndef WIN32 @@ -97,7 +97,7 @@ static void ReportHardwareRand() // This must be done in a separate function, as InitHardwareRand() may be indirectly called // from global constructors, before logging is initialized. if (g_rdseed_supported) { - LogPrintf("Using RdSeed as additional entropy source\n"); + LogPrintf("Using RdSeed as an additional entropy source\n"); } if (g_rdrand_supported) { LogPrintf("Using RdRand as an additional entropy source\n"); diff --git a/src/random.h b/src/random.h index b92c29f0be..5fe20c5f76 100644 --- a/src/random.h +++ b/src/random.h @@ -11,6 +11,7 @@ #include <span.h> #include <uint256.h> +#include <cassert> #include <chrono> #include <cstdint> #include <limits> @@ -236,13 +237,19 @@ public: template <typename Tp> Tp rand_uniform_delay(const Tp& time, typename Tp::duration range) { - using Dur = typename Tp::duration; - Dur dur{range.count() > 0 ? /* interval [0..range) */ Dur{randrange(range.count())} : - range.count() < 0 ? /* interval (range..0] */ -Dur{randrange(-range.count())} : - /* interval [0..0] */ Dur{0}}; - return time + dur; + return time + rand_uniform_duration<Tp>(range); } + /** Generate a uniform random duration in the range from 0 (inclusive) to range (exclusive). */ + template <typename Chrono> + typename Chrono::duration rand_uniform_duration(typename Chrono::duration range) noexcept + { + using Dur = typename Chrono::duration; + return range.count() > 0 ? /* interval [0..range) */ Dur{randrange(range.count())} : + range.count() < 0 ? /* interval (range..0] */ -Dur{randrange(-range.count())} : + /* interval [0..0] */ Dur{0}; + }; + // Compatibility with the C++11 UniformRandomBitGenerator concept typedef uint64_t result_type; static constexpr uint64_t min() { return 0; } diff --git a/src/randomenv.cpp b/src/randomenv.cpp index c5dca346d6..9e58180b7a 100644 --- a/src/randomenv.cpp +++ b/src/randomenv.cpp @@ -15,7 +15,7 @@ #include <support/cleanse.h> #include <util/time.h> // for GetTime() #ifdef WIN32 -#include <compat.h> // for Windows API +#include <compat/compat.h> #endif #include <algorithm> diff --git a/src/rest.cpp b/src/rest.cpp index 1b90baaf95..7f00db2222 100644 --- a/src/rest.cpp +++ b/src/rest.cpp @@ -590,45 +590,31 @@ static bool rest_chaininfo(const std::any& context, HTTPRequest* req, const std: } } -static bool rest_mempool_info(const std::any& context, HTTPRequest* req, const std::string& strURIPart) +static bool rest_mempool(const std::any& context, HTTPRequest* req, const std::string& str_uri_part) { if (!CheckWarmup(req)) return false; - const CTxMemPool* mempool = GetMemPool(context, req); - if (!mempool) return false; - std::string param; - const RESTResponseFormat rf = ParseDataFormat(param, strURIPart); - - switch (rf) { - case RESTResponseFormat::JSON: { - UniValue mempoolInfoObject = MempoolInfoToJSON(*mempool); - std::string strJSON = mempoolInfoObject.write() + "\n"; - req->WriteHeader("Content-Type", "application/json"); - req->WriteReply(HTTP_OK, strJSON); - return true; - } - default: { - return RESTERR(req, HTTP_NOT_FOUND, "output format not found (available: json)"); - } + std::string param; + const RESTResponseFormat rf = ParseDataFormat(param, str_uri_part); + if (param != "contents" && param != "info") { + return RESTERR(req, HTTP_BAD_REQUEST, "Invalid URI format. Expected /rest/mempool/<info|contents>.json"); } -} -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); if (!mempool) return false; - std::string param; - const RESTResponseFormat rf = ParseDataFormat(param, strURIPart); switch (rf) { case RESTResponseFormat::JSON: { - UniValue mempoolObject = MempoolToJSON(*mempool, true); + std::string str_json; + if (param == "contents") { + str_json = MempoolToJSON(*mempool, true).write() + "\n"; + } else { + str_json = MempoolInfoToJSON(*mempool).write() + "\n"; + } - std::string strJSON = mempoolObject.write() + "\n"; req->WriteHeader("Content-Type", "application/json"); - req->WriteReply(HTTP_OK, strJSON); + req->WriteReply(HTTP_OK, str_json); return true; } default: { @@ -798,14 +784,18 @@ static bool rest_getutxos(const std::any& context, HTTPRequest* req, const std:: ChainstateManager* maybe_chainman = GetChainman(context, req); if (!maybe_chainman) return false; ChainstateManager& chainman = *maybe_chainman; + decltype(chainman.ActiveHeight()) active_height; + uint256 active_hash; { - auto process_utxos = [&vOutPoints, &outs, &hits](const CCoinsView& view, const CTxMemPool& mempool) { + auto process_utxos = [&vOutPoints, &outs, &hits, &active_height, &active_hash, &chainman](const CCoinsView& view, const CTxMemPool* mempool) EXCLUSIVE_LOCKS_REQUIRED(chainman.GetMutex()) { for (const COutPoint& vOutPoint : vOutPoints) { Coin coin; - bool hit = !mempool.isSpent(vOutPoint) && view.GetCoin(vOutPoint, coin); + bool hit = (!mempool || !mempool->isSpent(vOutPoint)) && view.GetCoin(vOutPoint, coin); hits.push_back(hit); if (hit) outs.emplace_back(std::move(coin)); } + active_height = chainman.ActiveHeight(); + active_hash = chainman.ActiveTip()->GetBlockHash(); }; if (fCheckMemPool) { @@ -815,10 +805,10 @@ static bool rest_getutxos(const std::any& context, HTTPRequest* req, const std:: LOCK2(cs_main, mempool->cs); CCoinsViewCache& viewChain = chainman.ActiveChainstate().CoinsTip(); CCoinsViewMemPool viewMempool(&viewChain, *mempool); - process_utxos(viewMempool, *mempool); + process_utxos(viewMempool, mempool); } else { - LOCK(cs_main); // no need to lock mempool! - process_utxos(chainman.ActiveChainstate().CoinsTip(), CTxMemPool()); + LOCK(cs_main); + process_utxos(chainman.ActiveChainstate().CoinsTip(), nullptr); } for (size_t i = 0; i < hits.size(); ++i) { @@ -833,7 +823,7 @@ static bool rest_getutxos(const std::any& context, HTTPRequest* req, const std:: // serialize data // use exact same output as mentioned in Bip64 CDataStream ssGetUTXOResponse(SER_NETWORK, PROTOCOL_VERSION); - ssGetUTXOResponse << chainman.ActiveChain().Height() << chainman.ActiveChain().Tip()->GetBlockHash() << bitmap << outs; + ssGetUTXOResponse << active_height << active_hash << bitmap << outs; std::string ssGetUTXOResponseString = ssGetUTXOResponse.str(); req->WriteHeader("Content-Type", "application/octet-stream"); @@ -843,7 +833,7 @@ static bool rest_getutxos(const std::any& context, HTTPRequest* req, const std:: case RESTResponseFormat::HEX: { CDataStream ssGetUTXOResponse(SER_NETWORK, PROTOCOL_VERSION); - ssGetUTXOResponse << chainman.ActiveChain().Height() << chainman.ActiveChain().Tip()->GetBlockHash() << bitmap << outs; + ssGetUTXOResponse << active_height << active_hash << bitmap << outs; std::string strHex = HexStr(ssGetUTXOResponse) + "\n"; req->WriteHeader("Content-Type", "text/plain"); @@ -856,8 +846,8 @@ static bool rest_getutxos(const std::any& context, HTTPRequest* req, const std:: // pack in some essentials // use more or less the same output as mentioned in Bip64 - objGetUTXOResponse.pushKV("chainHeight", chainman.ActiveChain().Height()); - objGetUTXOResponse.pushKV("chaintipHash", chainman.ActiveChain().Tip()->GetBlockHash().GetHex()); + objGetUTXOResponse.pushKV("chainHeight", active_height); + objGetUTXOResponse.pushKV("chaintipHash", active_hash.GetHex()); objGetUTXOResponse.pushKV("bitmap", bitmapStringRepresentation); UniValue utxos(UniValue::VARR); @@ -946,8 +936,7 @@ static const struct { {"/rest/blockfilter/", rest_block_filter}, {"/rest/blockfilterheaders/", rest_filter_header}, {"/rest/chaininfo", rest_chaininfo}, - {"/rest/mempool/info", rest_mempool_info}, - {"/rest/mempool/contents", rest_mempool_contents}, + {"/rest/mempool/", rest_mempool}, {"/rest/headers/", rest_headers}, {"/rest/getutxos", rest_getutxos}, {"/rest/blockhashbyheight/", rest_blockhash_by_height}, diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp index d682a7d3e8..93d11c8276 100644 --- a/src/rpc/blockchain.cpp +++ b/src/rpc/blockchain.cpp @@ -39,6 +39,7 @@ #include <univalue.h> #include <util/check.h> #include <util/strencodings.h> +#include <util/system.h> #include <util/translation.h> #include <validation.h> #include <validationinterface.h> @@ -66,7 +67,7 @@ struct CUpdatedBlock int height; }; -static Mutex cs_blockchange; +static GlobalMutex cs_blockchange; static std::condition_variable cond_blockchange; static CUpdatedBlock latestblock GUARDED_BY(cs_blockchange); @@ -396,7 +397,7 @@ static RPCHelpMan syncwithvalidationinterfacequeue() [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { SyncWithValidationInterfaceQueue(); - return NullUniValue; + return UniValue::VNULL; }, }; } @@ -440,6 +441,11 @@ static RPCHelpMan getblockfrompeer() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { + RPCTypeCheck(request.params, { + UniValue::VSTR, // blockhash + UniValue::VNUM, // peer_id + }); + const NodeContext& node = EnsureAnyNodeContext(request.context); ChainstateManager& chainman = EnsureChainman(node); PeerManager& peerman = EnsurePeerman(node); @@ -598,6 +604,30 @@ static CBlockUndo GetUndoChecked(BlockManager& blockman, const CBlockIndex* pblo return blockUndo; } +const RPCResult getblock_vin{ + RPCResult::Type::ARR, "vin", "", + { + {RPCResult::Type::OBJ, "", "", + { + {RPCResult::Type::ELISION, "", "The same output as verbosity = 2"}, + {RPCResult::Type::OBJ, "prevout", "(Only if undo information is available)", + { + {RPCResult::Type::BOOL, "generated", "Coinbase or not"}, + {RPCResult::Type::NUM, "height", "The height of the prevout"}, + {RPCResult::Type::STR_AMOUNT, "value", "The value in " + CURRENCY_UNIT}, + {RPCResult::Type::OBJ, "scriptPubKey", "", + { + {RPCResult::Type::STR, "asm", "Disassembly of the public key script"}, + {RPCResult::Type::STR, "desc", "Inferred descriptor for the output"}, + {RPCResult::Type::STR_HEX, "hex", "The raw public key script bytes, hex-encoded"}, + {RPCResult::Type::STR, "address", /*optional=*/true, "The Bitcoin address (only if a well-defined address exists)"}, + {RPCResult::Type::STR, "type", "The type (one of: " + GetAllOutputTypes() + ")"}, + }}, + }}, + }}, + } +}; + static RPCHelpMan getblock() { return RPCHelpMan{"getblock", @@ -657,26 +687,7 @@ static RPCHelpMan getblock() { {RPCResult::Type::OBJ, "", "", { - {RPCResult::Type::ARR, "vin", "", - { - {RPCResult::Type::OBJ, "", "", - { - {RPCResult::Type::ELISION, "", "The same output as verbosity = 2"}, - {RPCResult::Type::OBJ, "prevout", "(Only if undo information is available)", - { - {RPCResult::Type::BOOL, "generated", "Coinbase or not"}, - {RPCResult::Type::NUM, "height", "The height of the prevout"}, - {RPCResult::Type::NUM, "value", "The value in " + CURRENCY_UNIT}, - {RPCResult::Type::OBJ, "scriptPubKey", "", - { - {RPCResult::Type::STR, "asm", "The asm"}, - {RPCResult::Type::STR, "hex", "The hex"}, - {RPCResult::Type::STR, "address", /*optional=*/true, "The Bitcoin address (only if a well-defined address exists)"}, - {RPCResult::Type::STR, "type", "The type (one of: " + GetAllOutputTypes() + ")"}, - }}, - }}, - }}, - }}, + getblock_vin, }}, }}, }}, @@ -756,7 +767,7 @@ static RPCHelpMan pruneblockchain() ChainstateManager& chainman = EnsureAnyChainman(request.context); LOCK(cs_main); - CChainState& active_chainstate = chainman.ActiveChainstate(); + Chainstate& active_chainstate = chainman.ActiveChainstate(); CChain& active_chain = active_chainstate.m_chain; int heightParam = request.params[0].getInt<int>(); @@ -822,9 +833,9 @@ static std::optional<kernel::CCoinsStats> GetUTXOStats(CCoinsView* view, node::B // Use CoinStatsIndex if it is requested and available and a hash_type of Muhash or None was requested if ((hash_type == kernel::CoinStatsHashType::MUHASH || hash_type == kernel::CoinStatsHashType::NONE) && g_coin_stats_index && index_requested) { if (pindex) { - return g_coin_stats_index->LookUpStats(pindex); + return g_coin_stats_index->LookUpStats(*pindex); } else { - CBlockIndex* block_index = WITH_LOCK(::cs_main, return blockman.LookupBlockIndex(view->GetBestBlock())); + CBlockIndex& block_index = *CHECK_NONFATAL(WITH_LOCK(::cs_main, return blockman.LookupBlockIndex(view->GetBestBlock()))); return g_coin_stats_index->LookUpStats(block_index); } } @@ -845,7 +856,7 @@ static RPCHelpMan gettxoutsetinfo() "Note this call may take some time if you are not using coinstatsindex.\n", { {"hash_type", RPCArg::Type::STR, RPCArg::Default{"hash_serialized_2"}, "Which UTXO set hash should be calculated. Options: 'hash_serialized_2' (the legacy algorithm), 'muhash', 'none'."}, - {"hash_or_height", RPCArg::Type::NUM, RPCArg::Optional::OMITTED_NAMED_ARG, "The block hash or height of the target height (only available with coinstatsindex).", "", {"", "string or numeric"}}, + {"hash_or_height", RPCArg::Type::NUM, RPCArg::DefaultHint{"the current best block"}, "The block hash or height of the target height (only available with coinstatsindex).", RPCArgOptions{.type_str={"", "string or numeric"}}}, {"use_index", RPCArg::Type::BOOL, RPCArg::Default{true}, "Use coinstatsindex, if available."}, }, RPCResult{ @@ -881,6 +892,7 @@ static RPCHelpMan gettxoutsetinfo() HelpExampleCli("gettxoutsetinfo", R"("none")") + HelpExampleCli("gettxoutsetinfo", R"("none" 1000)") + HelpExampleCli("gettxoutsetinfo", R"("none" '"00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09"')") + + HelpExampleCli("-named gettxoutsetinfo", R"(hash_type='muhash' use_index='false')") + HelpExampleRpc("gettxoutsetinfo", "") + HelpExampleRpc("gettxoutsetinfo", R"("none")") + HelpExampleRpc("gettxoutsetinfo", R"("none", 1000)") + @@ -896,7 +908,7 @@ static RPCHelpMan gettxoutsetinfo() NodeContext& node = EnsureAnyNodeContext(request.context); ChainstateManager& chainman = EnsureChainman(node); - CChainState& active_chainstate = chainman.ActiveChainstate(); + Chainstate& active_chainstate = chainman.ActiveChainstate(); active_chainstate.ForceFlushStateToDisk(); CCoinsView* coins_view; @@ -917,6 +929,9 @@ static RPCHelpMan gettxoutsetinfo() throw JSONRPCError(RPC_INVALID_PARAMETER, "hash_serialized_2 hash type cannot be queried for a specific block"); } + if (!index_requested) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot set use_index to false when querying for a specific block"); + } pindex = ParseHashOrHeight(request.params[1], chainman); } @@ -1001,9 +1016,9 @@ static RPCHelpMan gettxout() {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, "asm", "Disassembly of the public key script"}, {RPCResult::Type::STR, "desc", "Inferred descriptor for the output"}, - {RPCResult::Type::STR_HEX, "hex", ""}, + {RPCResult::Type::STR_HEX, "hex", "The raw public key script bytes, hex-encoded"}, {RPCResult::Type::STR, "type", "The type, eg pubkeyhash"}, {RPCResult::Type::STR, "address", /*optional=*/true, "The Bitcoin address (only if a well-defined address exists)"}, }}, @@ -1033,7 +1048,7 @@ static RPCHelpMan gettxout() fMempool = request.params[2].get_bool(); Coin coin; - CChainState& active_chainstate = chainman.ActiveChainstate(); + Chainstate& active_chainstate = chainman.ActiveChainstate(); CCoinsViewCache* coins_view = &active_chainstate.CoinsTip(); if (fMempool) { @@ -1041,11 +1056,11 @@ static RPCHelpMan gettxout() LOCK(mempool.cs); CCoinsViewMemPool view(coins_view, mempool); if (!view.GetCoin(out, coin) || mempool.isSpent(out)) { - return NullUniValue; + return UniValue::VNULL; } } else { if (!coins_view->GetCoin(out, coin)) { - return NullUniValue; + return UniValue::VNULL; } } @@ -1090,7 +1105,7 @@ static RPCHelpMan verifychain() ChainstateManager& chainman = EnsureAnyChainman(request.context); LOCK(cs_main); - CChainState& active_chainstate = chainman.ActiveChainstate(); + Chainstate& active_chainstate = chainman.ActiveChainstate(); return CVerifyDB().VerifyDB( active_chainstate, chainman.GetParams().GetConsensus(), active_chainstate.CoinsTip(), check_level, check_depth); }, @@ -1218,7 +1233,7 @@ RPCHelpMan getblockchaininfo() const ArgsManager& args{EnsureAnyArgsman(request.context)}; ChainstateManager& chainman = EnsureAnyChainman(request.context); LOCK(cs_main); - CChainState& active_chainstate = chainman.ActiveChainstate(); + Chainstate& active_chainstate = chainman.ActiveChainstate(); const CBlockIndex& tip{*CHECK_NONFATAL(active_chainstate.m_chain.Tip())}; const int height{tip.nHeight}; @@ -1313,7 +1328,7 @@ static RPCHelpMan getdeploymentinfo() { const ChainstateManager& chainman = EnsureAnyChainman(request.context); LOCK(cs_main); - const CChainState& active_chainstate = chainman.ActiveChainstate(); + const Chainstate& active_chainstate = chainman.ActiveChainstate(); const CBlockIndex* blockindex; if (request.params[0].isNull()) { @@ -1483,7 +1498,7 @@ static RPCHelpMan preciousblock() throw JSONRPCError(RPC_DATABASE_ERROR, state.ToString()); } - return NullUniValue; + return UniValue::VNULL; }, }; } @@ -1524,7 +1539,7 @@ static RPCHelpMan invalidateblock() throw JSONRPCError(RPC_DATABASE_ERROR, state.ToString()); } - return NullUniValue; + return UniValue::VNULL; }, }; } @@ -1564,7 +1579,7 @@ static RPCHelpMan reconsiderblock() throw JSONRPCError(RPC_DATABASE_ERROR, state.ToString()); } - return NullUniValue; + return UniValue::VNULL; }, }; } @@ -1711,13 +1726,13 @@ static RPCHelpMan getblockstats() "\nCompute per block statistics for a given window. All amounts are in satoshis.\n" "It won't work for some heights with pruning.\n", { - {"hash_or_height", RPCArg::Type::NUM, RPCArg::Optional::NO, "The block hash or height of the target block", "", {"", "string or numeric"}}, + {"hash_or_height", RPCArg::Type::NUM, RPCArg::Optional::NO, "The block hash or height of the target block", RPCArgOptions{.type_str={"", "string or numeric"}}}, {"stats", RPCArg::Type::ARR, RPCArg::DefaultHint{"all values"}, "Values to plot (see result below)", { {"height", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "Selected statistic"}, {"time", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "Selected statistic"}, }, - "stats"}, + RPCArgOptions{.oneline_description="stats"}}, }, RPCResult{ RPCResult::Type::OBJ, "", "", @@ -2037,16 +2052,10 @@ static RPCHelpMan scantxoutset() {"range", RPCArg::Type::RANGE, RPCArg::Default{1000}, "The range of HD chain indexes to explore (either end or [begin,end])"}, }}, }, - "[scanobjects,...]"}, + RPCArgOptions{.oneline_description="[scanobjects,...]"}}, }, { - 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{"when action=='start'; only returns after scan completes", 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)"}, @@ -2065,6 +2074,12 @@ static RPCHelpMan scantxoutset() }}, {RPCResult::Type::STR_AMOUNT, "total_amount", "The total amount of all found unspent outputs in " + CURRENCY_UNIT}, }}, + RPCResult{"when action=='abort'", RPCResult::Type::BOOL, "success", "True if scan will be aborted (not necessarily before this RPC returns), or false if there is no scan to abort"}, + RPCResult{"when action=='status' and a scan is currently in progress", RPCResult::Type::OBJ, "", "", + { + {RPCResult::Type::NUM, "progress", "Approximate percent complete"}, + }}, + RPCResult{"when action=='status' and no scan is in progress - possibly already completed", RPCResult::Type::NONE, "", ""}, }, RPCExamples{ HelpExampleCli("scantxoutset", "start \'[\"" + EXAMPLE_DESCRIPTOR_RAW + "\"]\'") + @@ -2083,7 +2098,7 @@ static RPCHelpMan scantxoutset() CoinsViewScanReserver reserver; if (reserver.reserve()) { // no scan in progress - return NullUniValue; + return UniValue::VNULL; } result.pushKV("progress", g_scan_progress.load()); return result; @@ -2114,7 +2129,7 @@ static RPCHelpMan scantxoutset() for (const UniValue& scanobject : request.params[1].get_array().getValues()) { FlatSigningProvider provider; auto scripts = EvalDescriptorStringOrObject(scanobject, provider); - for (const auto& script : scripts) { + for (CScript& script : scripts) { std::string inferred = InferDescriptor(script, provider)->ToString(); needles.emplace(script); descriptors.emplace(std::move(script), std::move(inferred)); @@ -2133,7 +2148,7 @@ static RPCHelpMan scantxoutset() { ChainstateManager& chainman = EnsureChainman(node); LOCK(cs_main); - CChainState& active_chainstate = chainman.ActiveChainstate(); + Chainstate& active_chainstate = chainman.ActiveChainstate(); active_chainstate.ForceFlushStateToDisk(); pcursor = CHECK_NONFATAL(active_chainstate.CoinsDB().Cursor()); tip = CHECK_NONFATAL(active_chainstate.m_chain.Tip()); @@ -2177,7 +2192,7 @@ static RPCHelpMan getblockfilter() "\nRetrieve a BIP 157 content filter for a particular block.\n", { {"blockhash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The hash of the block"}, - {"filtertype", RPCArg::Type::STR, RPCArg::Default{"basic"}, "The type name of the filter"}, + {"filtertype", RPCArg::Type::STR, RPCArg::Default{BlockFilterTypeName(BlockFilterType::BASIC)}, "The type name of the filter"}, }, RPCResult{ RPCResult::Type::OBJ, "", "", @@ -2192,7 +2207,7 @@ static RPCHelpMan getblockfilter() [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { uint256 block_hash = ParseHashV(request.params[0], "blockhash"); - std::string filtertype_name = "basic"; + std::string filtertype_name = BlockFilterTypeName(BlockFilterType::BASIC); if (!request.params[1].isNull()) { filtertype_name = request.params[1].get_str(); } @@ -2293,7 +2308,7 @@ static RPCHelpMan dumptxoutset() } FILE* file{fsbridge::fopen(temppath, "wb")}; - CAutoFile afile{file, SER_DISK, CLIENT_VERSION}; + AutoFile afile{file}; if (afile.IsNull()) { throw JSONRPCError( RPC_INVALID_PARAMETER, @@ -2313,8 +2328,8 @@ static RPCHelpMan dumptxoutset() UniValue CreateUTXOSnapshot( NodeContext& node, - CChainState& chainstate, - CAutoFile& afile, + Chainstate& chainstate, + AutoFile& afile, const fs::path& path, const fs::path& temppath) { diff --git a/src/rpc/blockchain.h b/src/rpc/blockchain.h index 5fbd9d5fd3..6cdb5fa48b 100644 --- a/src/rpc/blockchain.h +++ b/src/rpc/blockchain.h @@ -20,7 +20,7 @@ extern RecursiveMutex cs_main; class CBlock; class CBlockIndex; -class CChainState; +class Chainstate; class UniValue; namespace node { struct NodeContext; @@ -54,8 +54,8 @@ void CalculatePercentilesByWeight(CAmount result[NUM_GETBLOCKSTATS_PERCENTILES], */ UniValue CreateUTXOSnapshot( node::NodeContext& node, - CChainState& chainstate, - CAutoFile& afile, + Chainstate& chainstate, + AutoFile& afile, const fs::path& path, const fs::path& tmppath); diff --git a/src/rpc/client.cpp b/src/rpc/client.cpp index ae0d0112ba..612dbbdacf 100644 --- a/src/rpc/client.cpp +++ b/src/rpc/client.cpp @@ -74,6 +74,7 @@ static const CRPCConvertParam vRPCConvertParams[] = { "listsinceblock", 1, "target_confirmations" }, { "listsinceblock", 2, "include_watchonly" }, { "listsinceblock", 3, "include_removed" }, + { "listsinceblock", 4, "include_change" }, { "sendmany", 1, "amounts" }, { "sendmany", 2, "minconf" }, { "sendmany", 4, "subtractfeefrom" }, @@ -110,6 +111,7 @@ static const CRPCConvertParam vRPCConvertParams[] = { "sendrawtransaction", 1, "maxfeerate" }, { "testmempoolaccept", 0, "rawtxs" }, { "testmempoolaccept", 1, "maxfeerate" }, + { "submitpackage", 0, "package" }, { "combinerawtransaction", 0, "txs" }, { "fundrawtransaction", 1, "options" }, { "fundrawtransaction", 2, "iswitness" }, @@ -146,6 +148,8 @@ static const CRPCConvertParam vRPCConvertParams[] = { "sendall", 1, "conf_target" }, { "sendall", 3, "fee_rate"}, { "sendall", 4, "options" }, + { "simulaterawtransaction", 0, "rawtxs" }, + { "simulaterawtransaction", 1, "options" }, { "importprivkey", 2, "rescan" }, { "importaddress", 2, "rescan" }, { "importaddress", 3, "p2sh" }, diff --git a/src/rpc/fees.cpp b/src/rpc/fees.cpp index 1873bc1587..e50bf00473 100644 --- a/src/rpc/fees.cpp +++ b/src/rpc/fees.cpp @@ -6,8 +6,6 @@ #include <core_io.h> #include <policy/feerate.h> #include <policy/fees.h> -#include <policy/policy.h> -#include <policy/settings.h> #include <rpc/protocol.h> #include <rpc/request.h> #include <rpc/server.h> @@ -16,7 +14,6 @@ #include <txmempool.h> #include <univalue.h> #include <util/fees.h> -#include <util/system.h> #include <algorithm> #include <array> @@ -67,7 +64,6 @@ static RPCHelpMan estimatesmartfee() [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { RPCTypeCheck(request.params, {UniValue::VNUM, UniValue::VSTR}); - RPCTypeCheckArgument(request.params[0], UniValue::VNUM); CBlockPolicyEstimator& fee_estimator = EnsureAnyFeeEstimator(request.context); const NodeContext& node = EnsureAnyNodeContext(request.context); @@ -89,8 +85,8 @@ static RPCHelpMan estimatesmartfee() FeeCalculation feeCalc; CFeeRate feeRate{fee_estimator.estimateSmartFee(conf_target, &feeCalc, conservative)}; if (feeRate != CFeeRate(0)) { - CFeeRate min_mempool_feerate{mempool.GetMinFee(gArgs.GetIntArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000)}; - CFeeRate min_relay_feerate{::minRelayTxFee}; + CFeeRate min_mempool_feerate{mempool.GetMinFee()}; + CFeeRate min_relay_feerate{mempool.m_min_relay_feerate}; feeRate = std::max({feeRate, min_mempool_feerate, min_relay_feerate}); result.pushKV("feerate", ValueFromAmount(feeRate.GetFeePerK())); } else { @@ -160,7 +156,6 @@ static RPCHelpMan estimaterawfee() [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { RPCTypeCheck(request.params, {UniValue::VNUM, UniValue::VNUM}, true); - RPCTypeCheckArgument(request.params[0], UniValue::VNUM); CBlockPolicyEstimator& fee_estimator = EnsureAnyFeeEstimator(request.context); diff --git a/src/rpc/mempool.cpp b/src/rpc/mempool.cpp index 97ec95a166..706d783942 100644 --- a/src/rpc/mempool.cpp +++ b/src/rpc/mempool.cpp @@ -5,8 +5,12 @@ #include <rpc/blockchain.h> +#include <kernel/mempool_persist.h> + +#include <chainparams.h> #include <core_io.h> #include <fs.h> +#include <node/mempool_persist_args.h> #include <policy/rbf.h> #include <policy/settings.h> #include <primitives/transaction.h> @@ -16,8 +20,12 @@ #include <txmempool.h> #include <univalue.h> #include <util/moneystr.h> +#include <util/time.h> + +using kernel::DumpMempool; using node::DEFAULT_MAX_RAW_TX_FEE_RATE; +using node::MempoolPath; using node::NodeContext; static RPCHelpMan sendrawtransaction() @@ -160,7 +168,7 @@ static RPCHelpMan testmempoolaccept() NodeContext& node = EnsureAnyNodeContext(request.context); CTxMemPool& mempool = EnsureMemPool(node); ChainstateManager& chainman = EnsureChainman(node); - CChainState& chainstate = chainman.ActiveChainstate(); + Chainstate& chainstate = chainman.ActiveChainstate(); const PackageMempoolAcceptResult package_result = [&] { LOCK(::cs_main); if (txns.size() > 1) return ProcessNewPackage(chainstate, mempool, txns, /*test_accept=*/true); @@ -247,7 +255,7 @@ static std::vector<RPCResult> MempoolEntryDescription() {RPCResult{RPCResult::Type::STR_HEX, "transactionid", "parent transaction id"}}}, RPCResult{RPCResult::Type::ARR, "spentby", "unconfirmed transactions spending outputs from this transaction", {RPCResult{RPCResult::Type::STR_HEX, "transactionid", "child transaction id"}}}, - RPCResult{RPCResult::Type::BOOL, "bip125-replaceable", "Whether this transaction could be replaced due to BIP125 (replace-by-fee)"}, + RPCResult{RPCResult::Type::BOOL, "bip125-replaceable", "Whether this transaction signals BIP125 replaceability or has an unconfirmed ancestor signaling BIP125 replaceability.\n"}, RPCResult{RPCResult::Type::BOOL, "unbroadcast", "Whether this transaction is currently unbroadcast (initial broadcast not yet acknowledged by any peers)"}, }; } @@ -441,9 +449,8 @@ static RPCHelpMan getmempoolancestors() } CTxMemPool::setEntries setAncestors; - uint64_t noLimit = std::numeric_limits<uint64_t>::max(); std::string dummy; - mempool.CalculateMemPoolAncestors(*it, setAncestors, noLimit, noLimit, noLimit, noLimit, dummy, false); + mempool.CalculateMemPoolAncestors(*it, setAncestors, CTxMemPool::Limits::NoLimits(), dummy, false); if (!fVerbose) { UniValue o(UniValue::VARR); @@ -597,8 +604,7 @@ static RPCHelpMan gettxspendingprevout() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - RPCTypeCheckArgument(request.params[0], UniValue::VARR); - const UniValue& output_params = request.params[0]; + const UniValue& output_params = request.params[0].get_array(); if (output_params.empty()) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, outputs are missing"); } @@ -652,23 +658,24 @@ UniValue MempoolInfoToJSON(const CTxMemPool& pool) // Make sure this call is atomic in the pool. LOCK(pool.cs); UniValue ret(UniValue::VOBJ); - ret.pushKV("loaded", pool.IsLoaded()); + ret.pushKV("loaded", pool.GetLoadTried()); ret.pushKV("size", (int64_t)pool.size()); ret.pushKV("bytes", (int64_t)pool.GetTotalTxSize()); ret.pushKV("usage", (int64_t)pool.DynamicMemoryUsage()); ret.pushKV("total_fee", ValueFromAmount(pool.GetTotalFee())); - int64_t maxmempool{gArgs.GetIntArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000}; - ret.pushKV("maxmempool", maxmempool); - ret.pushKV("mempoolminfee", ValueFromAmount(std::max(pool.GetMinFee(maxmempool), ::minRelayTxFee).GetFeePerK())); - ret.pushKV("minrelaytxfee", ValueFromAmount(::minRelayTxFee.GetFeePerK())); + ret.pushKV("maxmempool", pool.m_max_size_bytes); + ret.pushKV("mempoolminfee", ValueFromAmount(std::max(pool.GetMinFee(), pool.m_min_relay_feerate).GetFeePerK())); + ret.pushKV("minrelaytxfee", ValueFromAmount(pool.m_min_relay_feerate.GetFeePerK())); + ret.pushKV("incrementalrelayfee", ValueFromAmount(pool.m_incremental_relay_feerate.GetFeePerK())); ret.pushKV("unbroadcastcount", uint64_t{pool.GetUnbroadcastTxs().size()}); + ret.pushKV("fullrbf", pool.m_full_rbf); return ret; } static RPCHelpMan getmempoolinfo() { return RPCHelpMan{"getmempoolinfo", - "\nReturns details on the active state of the TX memory pool.\n", + "Returns details on the active state of the TX memory pool.", {}, RPCResult{ RPCResult::Type::OBJ, "", "", @@ -681,7 +688,9 @@ static RPCHelpMan getmempoolinfo() {RPCResult::Type::NUM, "maxmempool", "Maximum memory usage for the mempool"}, {RPCResult::Type::STR_AMOUNT, "mempoolminfee", "Minimum fee rate in " + CURRENCY_UNIT + "/kvB for tx to be accepted. Is the maximum of minrelaytxfee and minimum mempool fee"}, {RPCResult::Type::STR_AMOUNT, "minrelaytxfee", "Current minimum relay fee for transactions"}, - {RPCResult::Type::NUM, "unbroadcastcount", "Current number of transactions that haven't passed initial broadcast yet"} + {RPCResult::Type::NUM, "incrementalrelayfee", "minimum fee rate increment for mempool limiting or replacement in " + CURRENCY_UNIT + "/kvB"}, + {RPCResult::Type::NUM, "unbroadcastcount", "Current number of transactions that haven't passed initial broadcast yet"}, + {RPCResult::Type::BOOL, "fullrbf", "True if the mempool accepts RBF without replaceability signaling inspection"}, }}, RPCExamples{ HelpExampleCli("getmempoolinfo", "") @@ -713,22 +722,168 @@ static RPCHelpMan savemempool() const ArgsManager& args{EnsureAnyArgsman(request.context)}; const CTxMemPool& mempool = EnsureAnyMemPool(request.context); - if (!mempool.IsLoaded()) { + if (!mempool.GetLoadTried()) { throw JSONRPCError(RPC_MISC_ERROR, "The mempool was not loaded yet"); } - if (!DumpMempool(mempool)) { + const fs::path& dump_path = MempoolPath(args); + + if (!DumpMempool(mempool, dump_path)) { throw JSONRPCError(RPC_MISC_ERROR, "Unable to dump mempool to disk"); } UniValue ret(UniValue::VOBJ); - ret.pushKV("filename", fs::path((args.GetDataDirNet() / "mempool.dat")).u8string()); + ret.pushKV("filename", dump_path.u8string()); return ret; }, }; } +static RPCHelpMan submitpackage() +{ + return RPCHelpMan{"submitpackage", + "Submit a package of raw transactions (serialized, hex-encoded) to local node (-regtest only).\n" + "The package will be validated according to consensus and mempool policy rules. If all transactions pass, they will be accepted to mempool.\n" + "This RPC is experimental and the interface may be unstable. Refer to doc/policy/packages.md for documentation on package policies.\n" + "Warning: until package relay is in use, successful submission does not mean the transaction will propagate to other nodes on the network.\n" + "Currently, each transaction is broadcasted individually after submission, which means they must meet other nodes' feerate requirements alone.\n" + , + { + {"package", RPCArg::Type::ARR, RPCArg::Optional::NO, "An array of raw transactions.", + { + {"rawtx", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, ""}, + }, + }, + }, + RPCResult{ + RPCResult::Type::OBJ, "", "", + { + {RPCResult::Type::OBJ_DYN, "tx-results", "transaction results keyed by wtxid", + { + {RPCResult::Type::OBJ, "wtxid", "transaction wtxid", { + {RPCResult::Type::STR_HEX, "txid", "The transaction hash in hex"}, + {RPCResult::Type::STR_HEX, "other-wtxid", /*optional=*/true, "The wtxid of a different transaction with the same txid but different witness found in the mempool. This means the submitted transaction was ignored."}, + {RPCResult::Type::NUM, "vsize", "Virtual transaction size as defined in BIP 141."}, + {RPCResult::Type::OBJ, "fees", "Transaction fees", { + {RPCResult::Type::STR_AMOUNT, "base", "transaction fee in " + CURRENCY_UNIT}, + }}, + }} + }}, + {RPCResult::Type::STR_AMOUNT, "package-feerate", /*optional=*/true, "package feerate used for feerate checks in " + CURRENCY_UNIT + " per KvB. Excludes transactions which were deduplicated or accepted individually."}, + {RPCResult::Type::ARR, "replaced-transactions", /*optional=*/true, "List of txids of replaced transactions", + { + {RPCResult::Type::STR_HEX, "", "The transaction id"}, + }}, + }, + }, + RPCExamples{ + HelpExampleCli("testmempoolaccept", "[rawtx1, rawtx2]") + + HelpExampleCli("submitpackage", "[rawtx1, rawtx2]") + }, + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue + { + if (!Params().IsMockableChain()) { + throw std::runtime_error("submitpackage is for regression testing (-regtest mode) only"); + } + RPCTypeCheck(request.params, { + UniValue::VARR, + }); + const UniValue raw_transactions = request.params[0].get_array(); + if (raw_transactions.size() < 1 || raw_transactions.size() > MAX_PACKAGE_COUNT) { + throw JSONRPCError(RPC_INVALID_PARAMETER, + "Array must contain between 1 and " + ToString(MAX_PACKAGE_COUNT) + " transactions."); + } + + std::vector<CTransactionRef> txns; + txns.reserve(raw_transactions.size()); + for (const auto& rawtx : raw_transactions.getValues()) { + CMutableTransaction mtx; + if (!DecodeHexTx(mtx, rawtx.get_str())) { + throw JSONRPCError(RPC_DESERIALIZATION_ERROR, + "TX decode failed: " + rawtx.get_str() + " Make sure the tx has at least one input."); + } + txns.emplace_back(MakeTransactionRef(std::move(mtx))); + } + + NodeContext& node = EnsureAnyNodeContext(request.context); + CTxMemPool& mempool = EnsureMemPool(node); + Chainstate& chainstate = EnsureChainman(node).ActiveChainstate(); + const auto package_result = WITH_LOCK(::cs_main, return ProcessNewPackage(chainstate, mempool, txns, /*test_accept=*/ false)); + + // First catch any errors. + switch(package_result.m_state.GetResult()) { + case PackageValidationResult::PCKG_RESULT_UNSET: break; + case PackageValidationResult::PCKG_POLICY: + { + throw JSONRPCTransactionError(TransactionError::INVALID_PACKAGE, + package_result.m_state.GetRejectReason()); + } + case PackageValidationResult::PCKG_MEMPOOL_ERROR: + { + throw JSONRPCTransactionError(TransactionError::MEMPOOL_ERROR, + package_result.m_state.GetRejectReason()); + } + case PackageValidationResult::PCKG_TX: + { + for (const auto& tx : txns) { + auto it = package_result.m_tx_results.find(tx->GetWitnessHash()); + if (it != package_result.m_tx_results.end() && it->second.m_state.IsInvalid()) { + throw JSONRPCTransactionError(TransactionError::MEMPOOL_REJECTED, + strprintf("%s failed: %s", tx->GetHash().ToString(), it->second.m_state.GetRejectReason())); + } + } + // If a PCKG_TX error was returned, there must have been an invalid transaction. + NONFATAL_UNREACHABLE(); + } + } + for (const auto& tx : txns) { + size_t num_submitted{0}; + std::string err_string; + const auto err = BroadcastTransaction(node, tx, err_string, 0, true, true); + if (err != TransactionError::OK) { + throw JSONRPCTransactionError(err, + strprintf("transaction broadcast failed: %s (all transactions were submitted, %d transactions were broadcast successfully)", + err_string, num_submitted)); + } + } + UniValue rpc_result{UniValue::VOBJ}; + UniValue tx_result_map{UniValue::VOBJ}; + std::set<uint256> replaced_txids; + for (const auto& tx : txns) { + auto it = package_result.m_tx_results.find(tx->GetWitnessHash()); + CHECK_NONFATAL(it != package_result.m_tx_results.end()); + UniValue result_inner{UniValue::VOBJ}; + result_inner.pushKV("txid", tx->GetHash().GetHex()); + if (it->second.m_result_type == MempoolAcceptResult::ResultType::DIFFERENT_WITNESS) { + result_inner.pushKV("other-wtxid", it->second.m_other_wtxid.value().GetHex()); + } + if (it->second.m_result_type == MempoolAcceptResult::ResultType::VALID || + it->second.m_result_type == MempoolAcceptResult::ResultType::MEMPOOL_ENTRY) { + result_inner.pushKV("vsize", int64_t{it->second.m_vsize.value()}); + UniValue fees(UniValue::VOBJ); + fees.pushKV("base", ValueFromAmount(it->second.m_base_fees.value())); + result_inner.pushKV("fees", fees); + if (it->second.m_replaced_transactions.has_value()) { + for (const auto& ptx : it->second.m_replaced_transactions.value()) { + replaced_txids.insert(ptx->GetHash()); + } + } + } + tx_result_map.pushKV(tx->GetWitnessHash().GetHex(), result_inner); + } + rpc_result.pushKV("tx-results", tx_result_map); + if (package_result.m_package_feerate.has_value()) { + rpc_result.pushKV("package-feerate", ValueFromAmount(package_result.m_package_feerate.value().GetFeePerK())); + } + UniValue replaced_list(UniValue::VARR); + for (const uint256& hash : replaced_txids) replaced_list.push_back(hash.ToString()); + rpc_result.pushKV("replaced-transactions", replaced_list); + return rpc_result; + }, + }; +} + void RegisterMempoolRPCCommands(CRPCTable& t) { static const CRPCCommand commands[]{ @@ -741,6 +896,7 @@ void RegisterMempoolRPCCommands(CRPCTable& t) {"blockchain", &getmempoolinfo}, {"blockchain", &getrawmempool}, {"blockchain", &savemempool}, + {"hidden", &submitpackage}, }; for (const auto& c : commands) { t.appendCommand(c.name, &c); diff --git a/src/rpc/mining.cpp b/src/rpc/mining.cpp index 8fb6daf0cb..98383fdaca 100644 --- a/src/rpc/mining.cpp +++ b/src/rpc/mining.cpp @@ -132,7 +132,7 @@ static bool GenerateBlock(ChainstateManager& chainman, CBlock& block, uint64_t& } std::shared_ptr<const CBlock> shared_pblock = std::make_shared<const CBlock>(block); - if (!chainman.ProcessNewBlock(shared_pblock, true, nullptr)) { + if (!chainman.ProcessNewBlock(shared_pblock, /*force_processing=*/true, /*min_pow_checked=*/true, nullptr)) { throw JSONRPCError(RPC_INTERNAL_ERROR, "ProcessNewBlock, block not accepted"); } @@ -144,7 +144,7 @@ static UniValue generateBlocks(ChainstateManager& chainman, const CTxMemPool& me { UniValue blockHashes(UniValue::VARR); while (nGenerate > 0 && !ShutdownRequested()) { - std::unique_ptr<CBlockTemplate> pblocktemplate(BlockAssembler{chainman.ActiveChainstate(), mempool}.CreateNewBlock(coinbase_script)); + std::unique_ptr<CBlockTemplate> pblocktemplate(BlockAssembler{chainman.ActiveChainstate(), &mempool}.CreateNewBlock(coinbase_script)); if (!pblocktemplate.get()) throw JSONRPCError(RPC_INTERNAL_ERROR, "Couldn't create new block"); CBlock *pblock = &pblocktemplate->block; @@ -354,8 +354,7 @@ static RPCHelpMan generateblock() { LOCK(cs_main); - CTxMemPool empty_mempool; - std::unique_ptr<CBlockTemplate> blocktemplate(BlockAssembler{chainman.ActiveChainstate(), empty_mempool}.CreateNewBlock(coinbase_script)); + std::unique_ptr<CBlockTemplate> blocktemplate(BlockAssembler{chainman.ActiveChainstate(), nullptr}.CreateNewBlock(coinbase_script)); if (!blocktemplate) { throw JSONRPCError(RPC_INTERNAL_ERROR, "Couldn't create new block"); } @@ -477,7 +476,7 @@ static RPCHelpMan prioritisetransaction() static UniValue BIP22ValidationResult(const BlockValidationState& state) { if (state.IsValid()) - return NullUniValue; + return UniValue::VNULL; if (state.IsError()) throw JSONRPCError(RPC_VERIFY_ERROR, state.ToString()); @@ -525,7 +524,7 @@ static RPCHelpMan getblocktemplate() {"str", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "other client side supported softfork deployment"}, }}, }, - "\"template_request\""}, + RPCArgOptions{.oneline_description="\"template_request\""}}, }, { RPCResult{"If the proposal was accepted with mode=='proposal'", RPCResult::Type::NONE, "", ""}, @@ -599,8 +598,7 @@ static RPCHelpMan getblocktemplate() std::string strMode = "template"; UniValue lpval = NullUniValue; std::set<std::string> setClientRules; - int64_t nMaxVersionPreVB = -1; - CChainState& active_chainstate = chainman.ActiveChainstate(); + Chainstate& active_chainstate = chainman.ActiveChainstate(); CChain& active_chain = active_chainstate.m_chain; if (!request.params[0].isNull()) { @@ -651,12 +649,6 @@ static RPCHelpMan getblocktemplate() const UniValue& v = aClientRules[i]; setClientRules.insert(v.get_str()); } - } else { - // NOTE: It is important that this NOT be read if versionbits is supported - const UniValue& uvMaxVersion = find_value(oparam, "maxversion"); - if (uvMaxVersion.isNum()) { - nMaxVersionPreVB = uvMaxVersion.getInt<int64_t>(); - } } } @@ -687,7 +679,7 @@ static RPCHelpMan getblocktemplate() if (lpval.isStr()) { // Format: <hashBestChain><nTransactionsUpdatedLast> - std::string lpstr = lpval.get_str(); + const std::string& lpstr = lpval.get_str(); hashWatchedChain = ParseHashV(lpstr.substr(0, 64), "longpollid"); nTransactionsUpdatedLastLP = LocaleIndependentAtoi<int64_t>(lpstr.substr(64)); @@ -738,10 +730,10 @@ static RPCHelpMan getblocktemplate() // Update block static CBlockIndex* pindexPrev; - static int64_t nStart; + static int64_t time_start; static std::unique_ptr<CBlockTemplate> pblocktemplate; if (pindexPrev != active_chain.Tip() || - (mempool.GetTransactionsUpdated() != nTransactionsUpdatedLast && GetTime() - nStart > 5)) + (mempool.GetTransactionsUpdated() != nTransactionsUpdatedLast && GetTime() - time_start > 5)) { // Clear pindexPrev so future calls make a new block, despite any failures from here on pindexPrev = nullptr; @@ -749,11 +741,11 @@ static RPCHelpMan getblocktemplate() // Store the pindexBest used before CreateNewBlock, to avoid races nTransactionsUpdatedLast = mempool.GetTransactionsUpdated(); CBlockIndex* pindexPrevNew = active_chain.Tip(); - nStart = GetTime(); + time_start = GetTime(); // Create new block CScript scriptDummy = CScript() << OP_TRUE; - pblocktemplate = BlockAssembler{active_chainstate, mempool}.CreateNewBlock(scriptDummy); + pblocktemplate = BlockAssembler{active_chainstate, &mempool}.CreateNewBlock(scriptDummy); if (!pblocktemplate) throw JSONRPCError(RPC_OUT_OF_MEMORY, "Out of memory"); @@ -864,7 +856,6 @@ static RPCHelpMan getblocktemplate() if (setClientRules.find(vbinfo.name) == setClientRules.end()) { // Not supported by the client; make sure it's safe to proceed if (!vbinfo.gbt_force) { - // If we do anything other than throw an exception here, be sure version/force isn't sent to old clients throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Support for '%s' rule requires explicit client support", vbinfo.name)); } } @@ -877,14 +868,6 @@ static RPCHelpMan getblocktemplate() result.pushKV("vbavailable", vbavailable); result.pushKV("vbrequired", int(0)); - if (nMaxVersionPreVB >= 2) { - // If VB is supported by the client, nMaxVersionPreVB is -1, so we won't get here - // Because BIP 34 changed how the generation transaction is serialized, we can only use version/force back to v2 blocks - // This is safe to do [otherwise-]unconditionally only because we are throwing an exception above if a non-force deployment gets activated - // Note that this can probably also be removed entirely after the first BIP9 non-force deployment (ie, probably segwit) gets activated - aMutable.push_back("version/force"); - } - result.pushKV("previousblockhash", pblock->hashPrevBlock.GetHex()); result.pushKV("transactions", transactions); result.pushKV("coinbaseaux", aux); @@ -998,7 +981,7 @@ static RPCHelpMan submitblock() bool new_block; auto sc = std::make_shared<submitblock_StateCatcher>(block.GetHash()); RegisterSharedValidationInterface(sc); - bool accepted = chainman.ProcessNewBlock(blockptr, /*force_processing=*/true, /*new_block=*/&new_block); + bool accepted = chainman.ProcessNewBlock(blockptr, /*force_processing=*/true, /*min_pow_checked=*/true, /*new_block=*/&new_block); UnregisterSharedValidationInterface(sc); if (!new_block && accepted) { return "duplicate"; @@ -1040,8 +1023,8 @@ static RPCHelpMan submitheader() } BlockValidationState state; - chainman.ProcessNewBlockHeaders({h}, state); - if (state.IsValid()) return NullUniValue; + chainman.ProcessNewBlockHeaders({h}, /*min_pow_checked=*/true, state); + if (state.IsValid()) return UniValue::VNULL; if (state.IsError()) { throw JSONRPCError(RPC_VERIFY_ERROR, state.ToString()); } diff --git a/src/rpc/net.cpp b/src/rpc/net.cpp index 0a061f2451..d701a180ab 100644 --- a/src/rpc/net.cpp +++ b/src/rpc/net.cpp @@ -23,6 +23,7 @@ #include <timedata.h> #include <util/strencodings.h> #include <util/string.h> +#include <util/time.h> #include <util/translation.h> #include <validation.h> #include <version.h> @@ -60,7 +61,7 @@ static RPCHelpMan getconnectioncount() NodeContext& node = EnsureAnyNodeContext(request.context); const CConnman& connman = EnsureConnman(node); - return (int)connman.GetNodeCount(ConnectionDirection::Both); + return connman.GetNodeCount(ConnectionDirection::Both); }, }; } @@ -84,7 +85,7 @@ static RPCHelpMan ping() // Request that each node send a ping during next message processing pass peerman.SendPings(); - return NullUniValue; + return UniValue::VNULL; }, }; } @@ -131,6 +132,7 @@ static RPCHelpMan getpeerinfo() {RPCResult::Type::BOOL, "bip152_hb_to", "Whether we selected peer as (compact blocks) high-bandwidth peer"}, {RPCResult::Type::BOOL, "bip152_hb_from", "Whether peer selected us as (compact blocks) high-bandwidth peer"}, {RPCResult::Type::NUM, "startingheight", /*optional=*/true, "The starting height (block) of the peer"}, + {RPCResult::Type::NUM, "presynced_headers", /*optional=*/true, "The current height of header pre-synchronization with this peer, or -1 if no low-work sync is in progress"}, {RPCResult::Type::NUM, "synced_headers", /*optional=*/true, "The last header we have in common with this peer"}, {RPCResult::Type::NUM, "synced_blocks", /*optional=*/true, "The last block we have in common with this peer"}, {RPCResult::Type::ARR, "inflight", /*optional=*/true, "", @@ -195,8 +197,9 @@ static RPCHelpMan getpeerinfo() if (stats.m_mapped_as != 0) { obj.pushKV("mapped_as", uint64_t(stats.m_mapped_as)); } - obj.pushKV("services", strprintf("%016x", stats.nServices)); - obj.pushKV("servicesnames", GetServicesNames(stats.nServices)); + ServiceFlags services{fStateStats ? statestats.their_services : ServiceFlags::NODE_NONE}; + obj.pushKV("services", strprintf("%016x", services)); + obj.pushKV("servicesnames", GetServicesNames(services)); obj.pushKV("lastsend", count_seconds(stats.m_last_send)); obj.pushKV("lastrecv", count_seconds(stats.m_last_recv)); obj.pushKV("last_transaction", count_seconds(stats.m_last_tx_time)); @@ -206,13 +209,13 @@ static RPCHelpMan getpeerinfo() obj.pushKV("conntime", count_seconds(stats.m_connected)); obj.pushKV("timeoffset", stats.nTimeOffset); if (stats.m_last_ping_time > 0us) { - obj.pushKV("pingtime", CountSecondsDouble(stats.m_last_ping_time)); + obj.pushKV("pingtime", Ticks<SecondsDouble>(stats.m_last_ping_time)); } if (stats.m_min_ping_time < std::chrono::microseconds::max()) { - obj.pushKV("minping", CountSecondsDouble(stats.m_min_ping_time)); + obj.pushKV("minping", Ticks<SecondsDouble>(stats.m_min_ping_time)); } if (fStateStats && statestats.m_ping_wait > 0s) { - obj.pushKV("pingwait", CountSecondsDouble(statestats.m_ping_wait)); + obj.pushKV("pingwait", Ticks<SecondsDouble>(statestats.m_ping_wait)); } obj.pushKV("version", stats.nVersion); // Use the sanitized form of subver here, to avoid tricksy remote peers from @@ -224,6 +227,7 @@ static RPCHelpMan getpeerinfo() obj.pushKV("bip152_hb_from", stats.m_bip152_highbandwidth_from); if (fStateStats) { obj.pushKV("startingheight", statestats.m_starting_height); + obj.pushKV("presynced_headers", statestats.presync_height); obj.pushKV("synced_headers", statestats.nSyncHeight); obj.pushKV("synced_blocks", statestats.nCommonHeight); UniValue heights(UniValue::VARR); @@ -238,7 +242,7 @@ static RPCHelpMan getpeerinfo() obj.pushKV("addr_rate_limited", statestats.m_addr_rate_limited); } UniValue permissions(UniValue::VARR); - for (const auto& permission : NetPermissions::ToStrings(stats.m_permissionFlags)) { + for (const auto& permission : NetPermissions::ToStrings(stats.m_permission_flags)) { permissions.push_back(permission); } obj.pushKV("permissions", permissions); @@ -303,7 +307,7 @@ static RPCHelpMan addnode() { CAddress addr; connman.OpenNetworkConnection(addr, false, nullptr, strNode.c_str(), ConnectionType::MANUAL); - return NullUniValue; + return UniValue::VNULL; } if (strCommand == "add") @@ -319,7 +323,7 @@ static RPCHelpMan addnode() } } - return NullUniValue; + return UniValue::VNULL; }, }; } @@ -422,7 +426,7 @@ static RPCHelpMan disconnectnode() throw JSONRPCError(RPC_CLIENT_NODE_NOT_CONNECTED, "Node not found in connected nodes"); } - return NullUniValue; + return UniValue::VNULL; }, }; } @@ -603,7 +607,7 @@ static RPCHelpMan getnetworkinfo() }}, }}, {RPCResult::Type::NUM, "relayfee", "minimum relay fee rate for transactions in " + CURRENCY_UNIT + "/kvB"}, - {RPCResult::Type::NUM, "incrementalfee", "minimum fee rate increment for mempool limiting or BIP 125 replacement in " + CURRENCY_UNIT + "/kvB"}, + {RPCResult::Type::NUM, "incrementalfee", "minimum fee rate increment for mempool limiting or replacement in " + CURRENCY_UNIT + "/kvB"}, {RPCResult::Type::ARR, "localaddresses", "list of local addresses", { {RPCResult::Type::OBJ, "", "", @@ -639,13 +643,16 @@ static RPCHelpMan getnetworkinfo() obj.pushKV("timeoffset", GetTimeOffset()); if (node.connman) { obj.pushKV("networkactive", node.connman->GetNetworkActive()); - 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("connections", node.connman->GetNodeCount(ConnectionDirection::Both)); + obj.pushKV("connections_in", node.connman->GetNodeCount(ConnectionDirection::In)); + obj.pushKV("connections_out", node.connman->GetNodeCount(ConnectionDirection::Out)); } obj.pushKV("networks", GetNetworksInfo()); - obj.pushKV("relayfee", ValueFromAmount(::minRelayTxFee.GetFeePerK())); - obj.pushKV("incrementalfee", ValueFromAmount(::incrementalRelayFee.GetFeePerK())); + if (node.mempool) { + // Those fields can be deprecated, to be replaced by the getmempoolinfo fields + obj.pushKV("relayfee", ValueFromAmount(node.mempool->m_min_relay_feerate.GetFeePerK())); + obj.pushKV("incrementalfee", ValueFromAmount(node.mempool->m_incremental_relay_feerate.GetFeePerK())); + } UniValue localAddresses(UniValue::VARR); { LOCK(g_maplocalhost_mutex); @@ -744,7 +751,7 @@ static RPCHelpMan setban() throw JSONRPCError(RPC_CLIENT_INVALID_IP_OR_SUBNET, "Error: Unban failed. Requested address/subnet was not previously manually banned."); } } - return NullUniValue; + return UniValue::VNULL; }, }; } @@ -818,7 +825,7 @@ static RPCHelpMan clearbanned() node.banman->ClearBanned(); - return NullUniValue; + return UniValue::VNULL; }, }; } @@ -893,7 +900,7 @@ static RPCHelpMan getnodeaddresses() for (const CAddress& addr : vAddr) { UniValue obj(UniValue::VOBJ); - obj.pushKV("time", (int)addr.nTime); + obj.pushKV("time", int64_t{TicksSinceEpoch<std::chrono::seconds>(addr.nTime)}); obj.pushKV("services", (uint64_t)addr.nServices); obj.pushKV("address", addr.ToStringIP()); obj.pushKV("port", addr.GetPort()); @@ -940,8 +947,9 @@ static RPCHelpMan addpeeraddress() bool success{false}; if (LookupHost(addr_string, net_addr, false)) { - CAddress address{{net_addr, port}, ServiceFlags{NODE_NETWORK | NODE_WITNESS}}; - address.nTime = GetAdjustedTime(); + CService service{net_addr, port}; + CAddress address{MaybeFlipIPv6toCJDNS(service), ServiceFlags{NODE_NETWORK | NODE_WITNESS}}; + address.nTime = Now<NodeSeconds>(); // The source address is set equal to the address. This is equivalent to the peer // announcing itself. if (node.addrman->Add({address}, address)) { diff --git a/src/rpc/node.cpp b/src/rpc/node.cpp index 5475662b82..605ebc15a7 100644 --- a/src/rpc/node.cpp +++ b/src/rpc/node.cpp @@ -65,7 +65,7 @@ static RPCHelpMan setmocktime() } } - return NullUniValue; + return UniValue::VNULL; }, }; } @@ -85,7 +85,7 @@ static RPCHelpMan invokedisallowedsyscall() throw std::runtime_error("invokedisallowedsyscall is used for testing only."); } TestDisallowedSandboxCall(); - return NullUniValue; + return UniValue::VNULL; }, }; } @@ -118,7 +118,7 @@ static RPCHelpMan mockscheduler() CHECK_NONFATAL(node_context->scheduler); node_context->scheduler->MockForward(std::chrono::seconds(delta_seconds)); - return NullUniValue; + return UniValue::VNULL; }, }; } diff --git a/src/rpc/output_script.cpp b/src/rpc/output_script.cpp index f4bb76f50f..744f809814 100644 --- a/src/rpc/output_script.cpp +++ b/src/rpc/output_script.cpp @@ -26,11 +26,6 @@ #include <tuple> #include <vector> -namespace node { -struct NodeContext; -} -using node::NodeContext; - static RPCHelpMan validateaddress() { return RPCHelpMan{ diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp index b9b8c36bb3..f365de7d0c 100644 --- a/src/rpc/rawtransaction.cpp +++ b/src/rpc/rawtransaction.cpp @@ -46,14 +46,12 @@ #include <univalue.h> using node::AnalyzePSBT; -using node::BroadcastTransaction; using node::FindCoins; using node::GetTransaction; using node::NodeContext; using node::PSBTAnalysis; -using node::ReadBlockFromDisk; -static void TxToJSON(const CTransaction& tx, const uint256 hashBlock, UniValue& entry, CChainState& active_chainstate) +static void TxToJSON(const CTransaction& tx, const uint256 hashBlock, UniValue& entry, Chainstate& active_chainstate) { // Call into TxToUniv() in bitcoin-common to decode the transaction hex. // @@ -98,8 +96,8 @@ static std::vector<RPCResult> DecodeTxDoc(const std::string& txid_field_doc) {RPCResult::Type::NUM, "vout", /*optional=*/true, "The output number (if not coinbase transaction)"}, {RPCResult::Type::OBJ, "scriptSig", /*optional=*/true, "The script (if not coinbase transaction)", { - {RPCResult::Type::STR, "asm", "asm"}, - {RPCResult::Type::STR_HEX, "hex", "hex"}, + {RPCResult::Type::STR, "asm", "Disassembly of the signature script"}, + {RPCResult::Type::STR_HEX, "hex", "The raw signature script bytes, hex-encoded"}, }}, {RPCResult::Type::ARR, "txinwitness", /*optional=*/true, "", { @@ -116,9 +114,9 @@ static std::vector<RPCResult> DecodeTxDoc(const std::string& txid_field_doc) {RPCResult::Type::NUM, "n", "index"}, {RPCResult::Type::OBJ, "scriptPubKey", "", { - {RPCResult::Type::STR, "asm", "the asm"}, + {RPCResult::Type::STR, "asm", "Disassembly of the public key script"}, {RPCResult::Type::STR, "desc", "Inferred descriptor for the output"}, - {RPCResult::Type::STR_HEX, "hex", "the hex"}, + {RPCResult::Type::STR_HEX, "hex", "The raw public key script bytes, hex-encoded"}, {RPCResult::Type::STR, "type", "The type, eg 'pubkeyhash'"}, {RPCResult::Type::STR, "address", /*optional=*/true, "The Bitcoin address (only if a well-defined address exists)"}, }}, @@ -159,7 +157,7 @@ static std::vector<RPCArg> CreateTxDoc() }, }, {"locktime", RPCArg::Type::NUM, RPCArg::Default{0}, "Raw locktime. Non-0 value also locktime-activates inputs"}, - {"replaceable", RPCArg::Type::BOOL, RPCArg::Default{false}, "Marks this transaction as BIP125-replaceable.\n" + {"replaceable", RPCArg::Type::BOOL, RPCArg::Default{true}, "Marks this transaction as BIP125-replaceable.\n" "Allows this transaction to be replaced by a transaction with higher fees. If provided, it is an error if explicit sequence numbers are incompatible."}, }; } @@ -304,7 +302,7 @@ static RPCHelpMan createrawtransaction() }, true ); - bool rbf = false; + std::optional<bool> rbf; if (!request.params[3].isNull()) { rbf = request.params[3].isTrue(); } @@ -680,6 +678,200 @@ static RPCHelpMan signrawtransactionwithkey() }; } +const RPCResult decodepsbt_inputs{ + RPCResult::Type::ARR, "inputs", "", + { + {RPCResult::Type::OBJ, "", "", + { + {RPCResult::Type::OBJ, "non_witness_utxo", /*optional=*/true, "Decoded network transaction for non-witness UTXOs", + { + {RPCResult::Type::ELISION, "",""}, + }}, + {RPCResult::Type::OBJ, "witness_utxo", /*optional=*/true, "Transaction output for witness UTXOs", + { + {RPCResult::Type::NUM, "amount", "The value in " + CURRENCY_UNIT}, + {RPCResult::Type::OBJ, "scriptPubKey", "", + { + {RPCResult::Type::STR, "asm", "Disassembly of the public key script"}, + {RPCResult::Type::STR, "desc", "Inferred descriptor for the output"}, + {RPCResult::Type::STR_HEX, "hex", "The raw public key script bytes, hex-encoded"}, + {RPCResult::Type::STR, "type", "The type, eg 'pubkeyhash'"}, + {RPCResult::Type::STR, "address", /*optional=*/true, "The Bitcoin address (only if a well-defined address exists)"}, + }}, + }}, + {RPCResult::Type::OBJ_DYN, "partial_signatures", /*optional=*/true, "", + { + {RPCResult::Type::STR, "pubkey", "The public key and signature that corresponds to it."}, + }}, + {RPCResult::Type::STR, "sighash", /*optional=*/true, "The sighash type to be used"}, + {RPCResult::Type::OBJ, "redeem_script", /*optional=*/true, "", + { + {RPCResult::Type::STR, "asm", "Disassembly of the redeem script"}, + {RPCResult::Type::STR_HEX, "hex", "The raw redeem script bytes, hex-encoded"}, + {RPCResult::Type::STR, "type", "The type, eg 'pubkeyhash'"}, + }}, + {RPCResult::Type::OBJ, "witness_script", /*optional=*/true, "", + { + {RPCResult::Type::STR, "asm", "Disassembly of the witness script"}, + {RPCResult::Type::STR_HEX, "hex", "The raw witness script bytes, hex-encoded"}, + {RPCResult::Type::STR, "type", "The type, eg 'pubkeyhash'"}, + }}, + {RPCResult::Type::ARR, "bip32_derivs", /*optional=*/true, "", + { + {RPCResult::Type::OBJ, "", "", + { + {RPCResult::Type::STR, "pubkey", "The public key with the derivation path as the value."}, + {RPCResult::Type::STR, "master_fingerprint", "The fingerprint of the master key"}, + {RPCResult::Type::STR, "path", "The path"}, + }}, + }}, + {RPCResult::Type::OBJ, "final_scriptSig", /*optional=*/true, "", + { + {RPCResult::Type::STR, "asm", "Disassembly of the final signature script"}, + {RPCResult::Type::STR_HEX, "hex", "The raw final signature script bytes, hex-encoded"}, + }}, + {RPCResult::Type::ARR, "final_scriptwitness", /*optional=*/true, "", + { + {RPCResult::Type::STR_HEX, "", "hex-encoded witness data (if any)"}, + }}, + {RPCResult::Type::OBJ_DYN, "ripemd160_preimages", /*optional=*/ true, "", + { + {RPCResult::Type::STR, "hash", "The hash and preimage that corresponds to it."}, + }}, + {RPCResult::Type::OBJ_DYN, "sha256_preimages", /*optional=*/ true, "", + { + {RPCResult::Type::STR, "hash", "The hash and preimage that corresponds to it."}, + }}, + {RPCResult::Type::OBJ_DYN, "hash160_preimages", /*optional=*/ true, "", + { + {RPCResult::Type::STR, "hash", "The hash and preimage that corresponds to it."}, + }}, + {RPCResult::Type::OBJ_DYN, "hash256_preimages", /*optional=*/ true, "", + { + {RPCResult::Type::STR, "hash", "The hash and preimage that corresponds to it."}, + }}, + {RPCResult::Type::STR_HEX, "taproot_key_path_sig", /*optional=*/ true, "hex-encoded signature for the Taproot key path spend"}, + {RPCResult::Type::ARR, "taproot_script_path_sigs", /*optional=*/ true, "", + { + {RPCResult::Type::OBJ, "signature", /*optional=*/ true, "The signature for the pubkey and leaf hash combination", + { + {RPCResult::Type::STR, "pubkey", "The x-only pubkey for this signature"}, + {RPCResult::Type::STR, "leaf_hash", "The leaf hash for this signature"}, + {RPCResult::Type::STR, "sig", "The signature itself"}, + }}, + }}, + {RPCResult::Type::ARR, "taproot_scripts", /*optional=*/ true, "", + { + {RPCResult::Type::OBJ, "", "", + { + {RPCResult::Type::STR_HEX, "script", "A leaf script"}, + {RPCResult::Type::NUM, "leaf_ver", "The version number for the leaf script"}, + {RPCResult::Type::ARR, "control_blocks", "The control blocks for this script", + { + {RPCResult::Type::STR_HEX, "control_block", "A hex-encoded control block for this script"}, + }}, + }}, + }}, + {RPCResult::Type::ARR, "taproot_bip32_derivs", /*optional=*/ true, "", + { + {RPCResult::Type::OBJ, "", "", + { + {RPCResult::Type::STR, "pubkey", "The x-only public key this path corresponds to"}, + {RPCResult::Type::STR, "master_fingerprint", "The fingerprint of the master key"}, + {RPCResult::Type::STR, "path", "The path"}, + {RPCResult::Type::ARR, "leaf_hashes", "The hashes of the leaves this pubkey appears in", + { + {RPCResult::Type::STR_HEX, "hash", "The hash of a leaf this pubkey appears in"}, + }}, + }}, + }}, + {RPCResult::Type::STR_HEX, "taproot_internal_key", /*optional=*/ true, "The hex-encoded Taproot x-only internal key"}, + {RPCResult::Type::STR_HEX, "taproot_merkle_root", /*optional=*/ true, "The hex-encoded Taproot merkle root"}, + {RPCResult::Type::OBJ_DYN, "unknown", /*optional=*/ true, "The unknown input fields", + { + {RPCResult::Type::STR_HEX, "key", "(key-value pair) An unknown key-value pair"}, + }}, + {RPCResult::Type::ARR, "proprietary", /*optional=*/true, "The input proprietary map", + { + {RPCResult::Type::OBJ, "", "", + { + {RPCResult::Type::STR_HEX, "identifier", "The hex string for the proprietary identifier"}, + {RPCResult::Type::NUM, "subtype", "The number for the subtype"}, + {RPCResult::Type::STR_HEX, "key", "The hex for the key"}, + {RPCResult::Type::STR_HEX, "value", "The hex for the value"}, + }}, + }}, + }}, + } +}; + +const RPCResult decodepsbt_outputs{ + RPCResult::Type::ARR, "outputs", "", + { + {RPCResult::Type::OBJ, "", "", + { + {RPCResult::Type::OBJ, "redeem_script", /*optional=*/true, "", + { + {RPCResult::Type::STR, "asm", "Disassembly of the redeem script"}, + {RPCResult::Type::STR_HEX, "hex", "The raw redeem script bytes, hex-encoded"}, + {RPCResult::Type::STR, "type", "The type, eg 'pubkeyhash'"}, + }}, + {RPCResult::Type::OBJ, "witness_script", /*optional=*/true, "", + { + {RPCResult::Type::STR, "asm", "Disassembly of the witness script"}, + {RPCResult::Type::STR_HEX, "hex", "The raw witness script bytes, hex-encoded"}, + {RPCResult::Type::STR, "type", "The type, eg 'pubkeyhash'"}, + }}, + {RPCResult::Type::ARR, "bip32_derivs", /*optional=*/true, "", + { + {RPCResult::Type::OBJ, "", "", + { + {RPCResult::Type::STR, "pubkey", "The public key this path corresponds to"}, + {RPCResult::Type::STR, "master_fingerprint", "The fingerprint of the master key"}, + {RPCResult::Type::STR, "path", "The path"}, + }}, + }}, + {RPCResult::Type::STR_HEX, "taproot_internal_key", /*optional=*/ true, "The hex-encoded Taproot x-only internal key"}, + {RPCResult::Type::ARR, "taproot_tree", /*optional=*/ true, "The tuples that make up the Taproot tree, in depth first search order", + { + {RPCResult::Type::OBJ, "tuple", /*optional=*/ true, "A single leaf script in the taproot tree", + { + {RPCResult::Type::NUM, "depth", "The depth of this element in the tree"}, + {RPCResult::Type::NUM, "leaf_ver", "The version of this leaf"}, + {RPCResult::Type::STR, "script", "The hex-encoded script itself"}, + }}, + }}, + {RPCResult::Type::ARR, "taproot_bip32_derivs", /*optional=*/ true, "", + { + {RPCResult::Type::OBJ, "", "", + { + {RPCResult::Type::STR, "pubkey", "The x-only public key this path corresponds to"}, + {RPCResult::Type::STR, "master_fingerprint", "The fingerprint of the master key"}, + {RPCResult::Type::STR, "path", "The path"}, + {RPCResult::Type::ARR, "leaf_hashes", "The hashes of the leaves this pubkey appears in", + { + {RPCResult::Type::STR_HEX, "hash", "The hash of a leaf this pubkey appears in"}, + }}, + }}, + }}, + {RPCResult::Type::OBJ_DYN, "unknown", /*optional=*/true, "The unknown output fields", + { + {RPCResult::Type::STR_HEX, "key", "(key-value pair) An unknown key-value pair"}, + }}, + {RPCResult::Type::ARR, "proprietary", /*optional=*/true, "The output proprietary map", + { + {RPCResult::Type::OBJ, "", "", + { + {RPCResult::Type::STR_HEX, "identifier", "The hex string for the proprietary identifier"}, + {RPCResult::Type::NUM, "subtype", "The number for the subtype"}, + {RPCResult::Type::STR_HEX, "key", "The hex for the key"}, + {RPCResult::Type::STR_HEX, "value", "The hex for the value"}, + }}, + }}, + }}, + } +}; + static RPCHelpMan decodepsbt() { return RPCHelpMan{ @@ -719,134 +911,8 @@ static RPCHelpMan decodepsbt() { {RPCResult::Type::STR_HEX, "key", "(key-value pair) An unknown key-value pair"}, }}, - {RPCResult::Type::ARR, "inputs", "", - { - {RPCResult::Type::OBJ, "", "", - { - {RPCResult::Type::OBJ, "non_witness_utxo", /*optional=*/true, "Decoded network transaction for non-witness UTXOs", - { - {RPCResult::Type::ELISION, "",""}, - }}, - {RPCResult::Type::OBJ, "witness_utxo", /*optional=*/true, "Transaction output for witness UTXOs", - { - {RPCResult::Type::NUM, "amount", "The value in " + CURRENCY_UNIT}, - {RPCResult::Type::OBJ, "scriptPubKey", "", - { - {RPCResult::Type::STR, "asm", "The asm"}, - {RPCResult::Type::STR, "desc", "Inferred descriptor for the output"}, - {RPCResult::Type::STR_HEX, "hex", "The hex"}, - {RPCResult::Type::STR, "type", "The type, eg 'pubkeyhash'"}, - {RPCResult::Type::STR, "address", /*optional=*/true, "The Bitcoin address (only if a well-defined address exists)"}, - }}, - }}, - {RPCResult::Type::OBJ_DYN, "partial_signatures", /*optional=*/true, "", - { - {RPCResult::Type::STR, "pubkey", "The public key and signature that corresponds to it."}, - }}, - {RPCResult::Type::STR, "sighash", /*optional=*/true, "The sighash type to be used"}, - {RPCResult::Type::OBJ, "redeem_script", /*optional=*/true, "", - { - {RPCResult::Type::STR, "asm", "The asm"}, - {RPCResult::Type::STR_HEX, "hex", "The hex"}, - {RPCResult::Type::STR, "type", "The type, eg 'pubkeyhash'"}, - }}, - {RPCResult::Type::OBJ, "witness_script", /*optional=*/true, "", - { - {RPCResult::Type::STR, "asm", "The asm"}, - {RPCResult::Type::STR_HEX, "hex", "The hex"}, - {RPCResult::Type::STR, "type", "The type, eg 'pubkeyhash'"}, - }}, - {RPCResult::Type::ARR, "bip32_derivs", /*optional=*/true, "", - { - {RPCResult::Type::OBJ, "", "", - { - {RPCResult::Type::STR, "pubkey", "The public key with the derivation path as the value."}, - {RPCResult::Type::STR, "master_fingerprint", "The fingerprint of the master key"}, - {RPCResult::Type::STR, "path", "The path"}, - }}, - }}, - {RPCResult::Type::OBJ, "final_scriptSig", /*optional=*/true, "", - { - {RPCResult::Type::STR, "asm", "The asm"}, - {RPCResult::Type::STR, "hex", "The hex"}, - }}, - {RPCResult::Type::ARR, "final_scriptwitness", /*optional=*/true, "", - { - {RPCResult::Type::STR_HEX, "", "hex-encoded witness data (if any)"}, - }}, - {RPCResult::Type::OBJ_DYN, "ripemd160_preimages", /*optional=*/ true, "", - { - {RPCResult::Type::STR, "hash", "The hash and preimage that corresponds to it."}, - }}, - {RPCResult::Type::OBJ_DYN, "sha256_preimages", /*optional=*/ true, "", - { - {RPCResult::Type::STR, "hash", "The hash and preimage that corresponds to it."}, - }}, - {RPCResult::Type::OBJ_DYN, "hash160_preimages", /*optional=*/ true, "", - { - {RPCResult::Type::STR, "hash", "The hash and preimage that corresponds to it."}, - }}, - {RPCResult::Type::OBJ_DYN, "hash256_preimages", /*optional=*/ true, "", - { - {RPCResult::Type::STR, "hash", "The hash and preimage that corresponds to it."}, - }}, - {RPCResult::Type::OBJ_DYN, "unknown", /*optional=*/ true, "The unknown input fields", - { - {RPCResult::Type::STR_HEX, "key", "(key-value pair) An unknown key-value pair"}, - }}, - {RPCResult::Type::ARR, "proprietary", /*optional=*/true, "The input proprietary map", - { - {RPCResult::Type::OBJ, "", "", - { - {RPCResult::Type::STR_HEX, "identifier", "The hex string for the proprietary identifier"}, - {RPCResult::Type::NUM, "subtype", "The number for the subtype"}, - {RPCResult::Type::STR_HEX, "key", "The hex for the key"}, - {RPCResult::Type::STR_HEX, "value", "The hex for the value"}, - }}, - }}, - }}, - }}, - {RPCResult::Type::ARR, "outputs", "", - { - {RPCResult::Type::OBJ, "", "", - { - {RPCResult::Type::OBJ, "redeem_script", /*optional=*/true, "", - { - {RPCResult::Type::STR, "asm", "The asm"}, - {RPCResult::Type::STR_HEX, "hex", "The hex"}, - {RPCResult::Type::STR, "type", "The type, eg 'pubkeyhash'"}, - }}, - {RPCResult::Type::OBJ, "witness_script", /*optional=*/true, "", - { - {RPCResult::Type::STR, "asm", "The asm"}, - {RPCResult::Type::STR_HEX, "hex", "The hex"}, - {RPCResult::Type::STR, "type", "The type, eg 'pubkeyhash'"}, - }}, - {RPCResult::Type::ARR, "bip32_derivs", /*optional=*/true, "", - { - {RPCResult::Type::OBJ, "", "", - { - {RPCResult::Type::STR, "pubkey", "The public key this path corresponds to"}, - {RPCResult::Type::STR, "master_fingerprint", "The fingerprint of the master key"}, - {RPCResult::Type::STR, "path", "The path"}, - }}, - }}, - {RPCResult::Type::OBJ_DYN, "unknown", /*optional=*/true, "The unknown global fields", - { - {RPCResult::Type::STR_HEX, "key", "(key-value pair) An unknown key-value pair"}, - }}, - {RPCResult::Type::ARR, "proprietary", /*optional=*/true, "The output proprietary map", - { - {RPCResult::Type::OBJ, "", "", - { - {RPCResult::Type::STR_HEX, "identifier", "The hex string for the proprietary identifier"}, - {RPCResult::Type::NUM, "subtype", "The number for the subtype"}, - {RPCResult::Type::STR_HEX, "key", "The hex for the key"}, - {RPCResult::Type::STR_HEX, "value", "The hex for the value"}, - }}, - }}, - }}, - }}, + decodepsbt_inputs, + decodepsbt_outputs, {RPCResult::Type::STR_AMOUNT, "fee", /*optional=*/true, "The transaction fee paid if all UTXOs slots in the PSBT have been filled."}, } }, @@ -1045,6 +1111,72 @@ static RPCHelpMan decodepsbt() in.pushKV("hash256_preimages", hash256_preimages); } + // Taproot key path signature + if (!input.m_tap_key_sig.empty()) { + in.pushKV("taproot_key_path_sig", HexStr(input.m_tap_key_sig)); + } + + // Taproot script path signatures + if (!input.m_tap_script_sigs.empty()) { + UniValue script_sigs(UniValue::VARR); + for (const auto& [pubkey_leaf, sig] : input.m_tap_script_sigs) { + const auto& [xonly, leaf_hash] = pubkey_leaf; + UniValue sigobj(UniValue::VOBJ); + sigobj.pushKV("pubkey", HexStr(xonly)); + sigobj.pushKV("leaf_hash", HexStr(leaf_hash)); + sigobj.pushKV("sig", HexStr(sig)); + script_sigs.push_back(sigobj); + } + in.pushKV("taproot_script_path_sigs", script_sigs); + } + + // Taproot leaf scripts + if (!input.m_tap_scripts.empty()) { + UniValue tap_scripts(UniValue::VARR); + for (const auto& [leaf, control_blocks] : input.m_tap_scripts) { + const auto& [script, leaf_ver] = leaf; + UniValue script_info(UniValue::VOBJ); + script_info.pushKV("script", HexStr(script)); + script_info.pushKV("leaf_ver", leaf_ver); + UniValue control_blocks_univ(UniValue::VARR); + for (const auto& control_block : control_blocks) { + control_blocks_univ.push_back(HexStr(control_block)); + } + script_info.pushKV("control_blocks", control_blocks_univ); + tap_scripts.push_back(script_info); + } + in.pushKV("taproot_scripts", tap_scripts); + } + + // Taproot bip32 keypaths + if (!input.m_tap_bip32_paths.empty()) { + UniValue keypaths(UniValue::VARR); + for (const auto& [xonly, leaf_origin] : input.m_tap_bip32_paths) { + const auto& [leaf_hashes, origin] = leaf_origin; + UniValue path_obj(UniValue::VOBJ); + path_obj.pushKV("pubkey", HexStr(xonly)); + path_obj.pushKV("master_fingerprint", strprintf("%08x", ReadBE32(origin.fingerprint))); + path_obj.pushKV("path", WriteHDKeypath(origin.path)); + UniValue leaf_hashes_arr(UniValue::VARR); + for (const auto& leaf_hash : leaf_hashes) { + leaf_hashes_arr.push_back(HexStr(leaf_hash)); + } + path_obj.pushKV("leaf_hashes", leaf_hashes_arr); + keypaths.push_back(path_obj); + } + in.pushKV("taproot_bip32_derivs", keypaths); + } + + // Taproot internal key + if (!input.m_tap_internal_key.IsNull()) { + in.pushKV("taproot_internal_key", HexStr(input.m_tap_internal_key)); + } + + // Write taproot merkle root + if (!input.m_tap_merkle_root.IsNull()) { + in.pushKV("taproot_merkle_root", HexStr(input.m_tap_merkle_root)); + } + // Proprietary if (!input.m_proprietary.empty()) { UniValue proprietary(UniValue::VARR); @@ -1103,6 +1235,47 @@ static RPCHelpMan decodepsbt() out.pushKV("bip32_derivs", keypaths); } + // Taproot internal key + if (!output.m_tap_internal_key.IsNull()) { + out.pushKV("taproot_internal_key", HexStr(output.m_tap_internal_key)); + } + + // Taproot tree + if (output.m_tap_tree.has_value()) { + UniValue tree(UniValue::VARR); + const auto& tuples = output.m_tap_tree->GetTreeTuples(); + for (const auto& tuple : tuples) { + uint8_t depth = std::get<0>(tuple); + uint8_t leaf_ver = std::get<1>(tuple); + CScript script = std::get<2>(tuple); + UniValue elem(UniValue::VOBJ); + elem.pushKV("depth", (int)depth); + elem.pushKV("leaf_ver", (int)leaf_ver); + elem.pushKV("script", HexStr(script)); + tree.push_back(elem); + } + out.pushKV("taproot_tree", tree); + } + + // Taproot bip32 keypaths + if (!output.m_tap_bip32_paths.empty()) { + UniValue keypaths(UniValue::VARR); + for (const auto& [xonly, leaf_origin] : output.m_tap_bip32_paths) { + const auto& [leaf_hashes, origin] = leaf_origin; + UniValue path_obj(UniValue::VOBJ); + path_obj.pushKV("pubkey", HexStr(xonly)); + path_obj.pushKV("master_fingerprint", strprintf("%08x", ReadBE32(origin.fingerprint))); + path_obj.pushKV("path", WriteHDKeypath(origin.path)); + UniValue leaf_hashes_arr(UniValue::VARR); + for (const auto& leaf_hash : leaf_hashes) { + leaf_hashes_arr.push_back(HexStr(leaf_hash)); + } + path_obj.pushKV("leaf_hashes", leaf_hashes_arr); + keypaths.push_back(path_obj); + } + out.pushKV("taproot_bip32_derivs", keypaths); + } + // Proprietary if (!output.m_proprietary.empty()) { UniValue proprietary(UniValue::VARR); @@ -1278,7 +1451,7 @@ static RPCHelpMan createpsbt() }, true ); - bool rbf = false; + std::optional<bool> rbf; if (!request.params[3].isNull()) { rbf = request.params[3].isTrue(); } diff --git a/src/rpc/rawtransaction_util.cpp b/src/rpc/rawtransaction_util.cpp index 86b5b7e960..b078ee8b29 100644 --- a/src/rpc/rawtransaction_util.cpp +++ b/src/rpc/rawtransaction_util.cpp @@ -21,7 +21,7 @@ #include <util/strencodings.h> #include <util/translation.h> -CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniValue& outputs_in, const UniValue& locktime, bool rbf) +CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniValue& outputs_in, const UniValue& locktime, std::optional<bool> rbf) { if (outputs_in.isNull()) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, output argument must be non-null"); @@ -60,7 +60,8 @@ CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniVal throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, vout cannot be negative"); uint32_t nSequence; - if (rbf) { + + if (rbf.value_or(true)) { nSequence = MAX_BIP125_RBF_SEQUENCE; /* CTxIn::SEQUENCE_FINAL - 2 */ } else if (rawTx.nLockTime) { nSequence = CTxIn::MAX_SEQUENCE_NONFINAL; /* CTxIn::SEQUENCE_FINAL - 1 */ @@ -132,7 +133,7 @@ CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniVal } } - if (rbf && rawTx.vin.size() > 0 && !SignalsOptInRBF(CTransaction(rawTx))) { + if (rbf.has_value() && rbf.value() && rawTx.vin.size() > 0 && !SignalsOptInRBF(CTransaction(rawTx))) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter combination: Sequence number(s) contradict replaceable option"); } @@ -159,14 +160,14 @@ static void TxInErrorToJSON(const CTxIn& txin, UniValue& vErrorsRet, const std:: void ParsePrevouts(const UniValue& prevTxsUnival, FillableSigningProvider* keystore, std::map<COutPoint, Coin>& coins) { if (!prevTxsUnival.isNull()) { - UniValue prevTxs = prevTxsUnival.get_array(); + const UniValue& prevTxs = prevTxsUnival.get_array(); for (unsigned int idx = 0; idx < prevTxs.size(); ++idx) { const UniValue& p = prevTxs[idx]; if (!p.isObject()) { throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "expected object with {\"txid'\",\"vout\",\"scriptPubKey\"}"); } - UniValue prevOut = p.get_obj(); + const UniValue& prevOut = p.get_obj(); RPCTypeCheckObj(prevOut, { diff --git a/src/rpc/rawtransaction_util.h b/src/rpc/rawtransaction_util.h index c3eb1417f8..9b5c9f08d4 100644 --- a/src/rpc/rawtransaction_util.h +++ b/src/rpc/rawtransaction_util.h @@ -7,6 +7,7 @@ #include <map> #include <string> +#include <optional> struct bilingual_str; class FillableSigningProvider; @@ -38,6 +39,6 @@ void SignTransactionResultToJSON(CMutableTransaction& mtx, bool complete, const void ParsePrevouts(const UniValue& prevTxsUnival, FillableSigningProvider* keystore, std::map<COutPoint, Coin>& coins); /** Create a transaction from univalue parameters */ -CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniValue& outputs_in, const UniValue& locktime, bool rbf); +CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniValue& outputs_in, const UniValue& locktime, std::optional<bool> rbf); #endif // BITCOIN_RPC_RAWTRANSACTION_UTIL_H diff --git a/src/rpc/request.cpp b/src/rpc/request.cpp index 304c265b31..8595fa78bb 100644 --- a/src/rpc/request.cpp +++ b/src/rpc/request.cpp @@ -66,16 +66,16 @@ UniValue JSONRPCError(int code, const std::string& message) */ static const std::string COOKIEAUTH_USER = "__cookie__"; /** Default name for auth cookie file */ -static const std::string COOKIEAUTH_FILE = ".cookie"; +static const char* const COOKIEAUTH_FILE = ".cookie"; /** Get name of RPC authentication cookie file */ static fs::path GetAuthCookieFile(bool temp=false) { - std::string arg = gArgs.GetArg("-rpccookiefile", COOKIEAUTH_FILE); + fs::path arg = gArgs.GetPathArg("-rpccookiefile", COOKIEAUTH_FILE); if (temp) { arg += ".tmp"; } - return AbsPathForConfigVal(fs::PathFromString(arg)); + return AbsPathForConfigVal(arg); } bool GenerateAuthCookie(std::string *cookie_out) diff --git a/src/rpc/server.cpp b/src/rpc/server.cpp index af00acdc9f..1d7bd2eb94 100644 --- a/src/rpc/server.cpp +++ b/src/rpc/server.cpp @@ -11,29 +11,31 @@ #include <util/strencodings.h> #include <util/string.h> #include <util/system.h> +#include <util/time.h> #include <boost/signals2/signal.hpp> #include <cassert> -#include <memory> // for unique_ptr +#include <chrono> +#include <memory> #include <mutex> #include <unordered_map> -static Mutex g_rpc_warmup_mutex; +static GlobalMutex g_rpc_warmup_mutex; static std::atomic<bool> g_rpc_running{false}; static bool fRPCInWarmup GUARDED_BY(g_rpc_warmup_mutex) = true; static std::string rpcWarmupStatus GUARDED_BY(g_rpc_warmup_mutex) = "RPC server started"; /* Timer-creating functions */ static RPCTimerInterface* timerInterface = nullptr; /* Map of name to timer. */ -static Mutex g_deadline_timers_mutex; +static GlobalMutex g_deadline_timers_mutex; static std::map<std::string, std::unique_ptr<RPCTimerBase> > deadlineTimers GUARDED_BY(g_deadline_timers_mutex); static bool ExecuteCommand(const CRPCCommand& command, const JSONRPCRequest& request, UniValue& result, bool last_handler); struct RPCCommandExecutionInfo { std::string method; - int64_t start; + SteadyClock::time_point start; }; struct RPCServerInfo @@ -50,7 +52,7 @@ struct RPCCommandExecution explicit RPCCommandExecution(const std::string& method) { LOCK(g_rpc_server_info.mutex); - it = g_rpc_server_info.active_commands.insert(g_rpc_server_info.active_commands.end(), {method, GetTimeMicros()}); + it = g_rpc_server_info.active_commands.insert(g_rpc_server_info.active_commands.end(), {method, SteadyClock::now()}); } ~RPCCommandExecution() { @@ -166,7 +168,7 @@ static RPCHelpMan stop() // to the client (intended for testing) "\nRequest a graceful shutdown of " PACKAGE_NAME ".", { - {"wait", RPCArg::Type::NUM, RPCArg::Optional::OMITTED_NAMED_ARG, "how long to wait in ms", "", {}, /*hidden=*/true}, + {"wait", RPCArg::Type::NUM, RPCArg::Optional::OMITTED_NAMED_ARG, "how long to wait in ms", RPCArgOptions{.hidden=true}}, }, RPCResult{RPCResult::Type::STR, "", "A string with the content '" + RESULT + "'"}, RPCExamples{""}, @@ -231,7 +233,7 @@ static RPCHelpMan getrpcinfo() for (const RPCCommandExecutionInfo& info : g_rpc_server_info.active_commands) { UniValue entry(UniValue::VOBJ); entry.pushKV("method", info.method); - entry.pushKV("duration", GetTimeMicros() - info.start); + entry.pushKV("duration", int64_t{Ticks<std::chrono::microseconds>(SteadyClock::now() - info.start)}); active_commands.push_back(entry); } @@ -464,8 +466,7 @@ UniValue CRPCTable::execute(const JSONRPCRequest &request) const static bool ExecuteCommand(const CRPCCommand& command, const JSONRPCRequest& request, UniValue& result, bool last_handler) { - try - { + try { RPCCommandExecution execution(request.strMethod); // Execute, convert arguments to array if necessary if (request.params.isObject()) { @@ -473,9 +474,9 @@ static bool ExecuteCommand(const CRPCCommand& command, const JSONRPCRequest& req } else { return command.actor(request, result, last_handler); } - } - catch (const std::exception& e) - { + } catch (const UniValue::type_error& e) { + throw JSONRPCError(RPC_TYPE_ERROR, e.what()); + } catch (const std::exception& e) { throw JSONRPCError(RPC_MISC_ERROR, e.what()); } } diff --git a/src/rpc/txoutproof.cpp b/src/rpc/txoutproof.cpp index dcf6c6bee1..cd8b49bfe1 100644 --- a/src/rpc/txoutproof.cpp +++ b/src/rpc/txoutproof.cpp @@ -66,7 +66,7 @@ static RPCHelpMan gettxoutproof() } } else { LOCK(cs_main); - CChainState& active_chainstate = chainman.ActiveChainstate(); + Chainstate& active_chainstate = chainman.ActiveChainstate(); // Loop through txids and try to find which block they're in. Exit loop once a block is found. for (const auto& tx : setTxids) { diff --git a/src/rpc/util.cpp b/src/rpc/util.cpp index 7517f64ea1..3e98e89791 100644 --- a/src/rpc/util.cpp +++ b/src/rpc/util.cpp @@ -50,7 +50,8 @@ void RPCTypeCheck(const UniValue& params, void RPCTypeCheckArgument(const UniValue& value, const UniValueType& typeExpected) { if (!typeExpected.typeAny && value.type() != typeExpected.type) { - throw JSONRPCError(RPC_TYPE_ERROR, strprintf("Expected type %s, got %s", uvTypeName(typeExpected.type), uvTypeName(value.type()))); + throw JSONRPCError(RPC_TYPE_ERROR, + strprintf("JSON value of type %s is not of expected type %s", uvTypeName(value.type()), uvTypeName(typeExpected.type))); } } @@ -98,7 +99,7 @@ CAmount AmountFromValue(const UniValue& value, int decimals) uint256 ParseHashV(const UniValue& v, std::string strName) { - std::string strHex(v.get_str()); + const std::string& strHex(v.get_str()); if (64 != strHex.length()) throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("%s must be of length %d (not %d, for '%s')", strName, 64, strHex.length(), strHex)); if (!IsHex(strHex)) // Note: IsHex("") is false @@ -417,8 +418,8 @@ struct Sections { case RPCArg::Type::BOOL: { if (outer_type == OuterType::NONE) return; // Nothing more to do for non-recursive types on first recursion auto left = indent; - if (arg.m_type_str.size() != 0 && push_name) { - left += "\"" + arg.GetName() + "\": " + arg.m_type_str.at(0); + if (arg.m_opts.type_str.size() != 0 && push_name) { + left += "\"" + arg.GetName() + "\": " + arg.m_opts.type_str.at(0); } else { left += push_name ? arg.ToStringObj(/*oneline=*/false) : arg.ToString(/*oneline=*/false); } @@ -617,7 +618,7 @@ std::string RPCHelpMan::ToString() const ret += m_name; bool was_optional{false}; for (const auto& arg : m_args) { - if (arg.m_hidden) break; // Any arg that follows is also hidden + if (arg.m_opts.hidden) break; // Any arg that follows is also hidden const bool optional = arg.IsOptional(); ret += " "; if (optional) { @@ -638,7 +639,7 @@ std::string RPCHelpMan::ToString() const Sections sections; for (size_t i{0}; i < m_args.size(); ++i) { const auto& arg = m_args.at(i); - if (arg.m_hidden) break; // Any arg that follows is also hidden + if (arg.m_opts.hidden) break; // Any arg that follows is also hidden if (i == 0) ret += "\nArguments:\n"; @@ -703,8 +704,8 @@ std::string RPCArg::ToDescriptionString() const { std::string ret; ret += "("; - if (m_type_str.size() != 0) { - ret += m_type_str.at(1); + if (m_opts.type_str.size() != 0) { + ret += m_opts.type_str.at(1); } else { switch (m_type) { case Type::STR_HEX: @@ -990,7 +991,7 @@ std::string RPCArg::ToStringObj(const bool oneline) const std::string RPCArg::ToString(const bool oneline) const { - if (oneline && !m_oneline_description.empty()) return m_oneline_description; + if (oneline && !m_opts.oneline_description.empty()) return m_opts.oneline_description; switch (m_type) { case Type::STR_HEX: diff --git a/src/rpc/util.h b/src/rpc/util.h index e883dc008e..9aa5df00b1 100644 --- a/src/rpc/util.h +++ b/src/rpc/util.h @@ -137,6 +137,12 @@ enum class OuterType { NONE, // Only set on first recursion }; +struct RPCArgOptions { + std::string oneline_description{}; //!< Should be empty unless it is supposed to override the auto-generated summary line + std::vector<std::string> type_str{}; //!< Should be empty unless it is supposed to override the auto-generated type strings. Vector length is either 0 or 2, m_opts.type_str.at(0) will override the type of the value in a key-value pair, m_opts.type_str.at(1) will override the type in the argument description. + bool hidden{false}; //!< For testing only +}; + struct RPCArg { enum class Type { OBJ, @@ -169,30 +175,25 @@ struct RPCArg { using DefaultHint = std::string; using Default = UniValue; using Fallback = std::variant<Optional, /* hint for default value */ DefaultHint, /* default constant value */ Default>; + const std::string m_names; //!< The name of the arg (can be empty for inner args, can contain multiple aliases separated by | for named request arguments) const Type m_type; - const bool m_hidden; const std::vector<RPCArg> m_inner; //!< Only used for arrays or dicts const Fallback m_fallback; const std::string m_description; - const std::string m_oneline_description; //!< Should be empty unless it is supposed to override the auto-generated summary line - const std::vector<std::string> m_type_str; //!< Should be empty unless it is supposed to override the auto-generated type strings. Vector length is either 0 or 2, m_type_str.at(0) will override the type of the value in a key-value pair, m_type_str.at(1) will override the type in the argument description. + const RPCArgOptions m_opts; RPCArg( const std::string name, const Type type, const Fallback fallback, const std::string description, - const std::string oneline_description = "", - const std::vector<std::string> type_str = {}, - const bool hidden = false) + RPCArgOptions opts = {}) : m_names{std::move(name)}, m_type{std::move(type)}, - m_hidden{hidden}, m_fallback{std::move(fallback)}, m_description{std::move(description)}, - m_oneline_description{std::move(oneline_description)}, - m_type_str{std::move(type_str)} + m_opts{std::move(opts)} { CHECK_NONFATAL(type != Type::ARR && type != Type::OBJ && type != Type::OBJ_USER_KEYS); } @@ -203,16 +204,13 @@ struct RPCArg { const Fallback fallback, const std::string description, const std::vector<RPCArg> inner, - const std::string oneline_description = "", - const std::vector<std::string> type_str = {}) + RPCArgOptions opts = {}) : m_names{std::move(name)}, m_type{std::move(type)}, - m_hidden{false}, m_inner{std::move(inner)}, m_fallback{std::move(fallback)}, m_description{std::move(description)}, - m_oneline_description{std::move(oneline_description)}, - m_type_str{std::move(type_str)} + m_opts{std::move(opts)} { CHECK_NONFATAL(type == Type::ARR || type == Type::OBJ || type == Type::OBJ_USER_KEYS); } @@ -227,7 +225,7 @@ struct RPCArg { /** * Return the type string of the argument. - * Set oneline to allow it to be overridden by a custom oneline type string (m_oneline_description). + * Set oneline to allow it to be overridden by a custom oneline type string (m_opts.oneline_description). */ std::string ToString(bool oneline) const; /** diff --git a/src/script/descriptor.cpp b/src/script/descriptor.cpp index cece0b60ce..864eb8864f 100644 --- a/src/script/descriptor.cpp +++ b/src/script/descriptor.cpp @@ -6,6 +6,7 @@ #include <key_io.h> #include <pubkey.h> +#include <script/miniscript.h> #include <script/script.h> #include <script/standard.h> @@ -161,6 +162,20 @@ public: virtual ~PubkeyProvider() = default; + /** Compare two public keys represented by this provider. + * Used by the Miniscript descriptors to check for duplicate keys in the script. + */ + bool operator<(PubkeyProvider& other) const { + CPubKey a, b; + SigningProvider dummy; + KeyOriginInfo dummy_info; + + GetPubKey(0, dummy, a, dummy_info); + other.GetPubKey(0, dummy, b, dummy_info); + + return a < b; + } + /** Derive a public key. * read_cache is the cache to read keys from (if not nullptr) * write_cache is the cache to write keys to (if not nullptr) @@ -313,7 +328,7 @@ class BIP32PubkeyProvider final : public PubkeyProvider { if (!GetExtKey(arg, xprv)) return false; for (auto entry : m_path) { - xprv.Derive(xprv, entry); + if (!xprv.Derive(xprv, entry)) return false; if (entry >> 31) { last_hardened = xprv; } @@ -373,14 +388,13 @@ public: } } else { for (auto entry : m_path) { - der = parent_extkey.Derive(parent_extkey, entry); - assert(der); + if (!parent_extkey.Derive(parent_extkey, entry)) return false; } final_extkey = parent_extkey; if (m_derive == DeriveType::UNHARDENED) der = parent_extkey.Derive(final_extkey, pos); assert(m_derive != DeriveType::HARDENED); } - assert(der); + if (!der) return false; final_info_out = final_info_out_tmp; key_out = final_extkey.pubkey; @@ -483,8 +497,8 @@ public: CExtKey extkey; CExtKey dummy; if (!GetDerivedExtKey(arg, extkey, dummy)) return false; - if (m_derive == DeriveType::UNHARDENED) extkey.Derive(extkey, pos); - if (m_derive == DeriveType::HARDENED) extkey.Derive(extkey, pos | 0x80000000UL); + if (m_derive == DeriveType::UNHARDENED && !extkey.Derive(extkey, pos)) return false; + if (m_derive == DeriveType::HARDENED && !extkey.Derive(extkey, pos | 0x80000000UL)) return false; key = extkey.key; return true; } @@ -493,12 +507,12 @@ public: /** Base class for all Descriptor implementations. */ class DescriptorImpl : public Descriptor { - //! Public key arguments for this descriptor (size 1 for PK, PKH, WPKH; any size for Multisig). +protected: + //! Public key arguments for this descriptor (size 1 for PK, PKH, WPKH; any size for WSH and Multisig). const std::vector<std::unique_ptr<PubkeyProvider>> m_pubkey_args; //! The string name of the descriptor function. const std::string m_name; -protected: //! The sub-descriptor arguments (empty for everything but SH and WSH). //! In doc/descriptors.m this is referred to as SCRIPT expressions sh(SCRIPT) //! and wsh(SCRIPT), and distinct from KEY expressions and ADDR expressions. @@ -558,12 +572,12 @@ public: if (pos++) ret += ","; std::string tmp; if (!scriptarg->ToStringHelper(arg, tmp, type, cache)) return false; - ret += std::move(tmp); + ret += tmp; } return true; } - bool ToStringHelper(const SigningProvider* arg, std::string& out, const StringType type, const DescriptorCache* cache = nullptr) const + virtual bool ToStringHelper(const SigningProvider* arg, std::string& out, const StringType type, const DescriptorCache* cache = nullptr) const { std::string extra = ToStringExtra(); size_t pos = extra.size() > 0 ? 1 : 0; @@ -582,7 +596,7 @@ public: tmp = pubkey->ToString(); break; } - ret += std::move(tmp); + ret += tmp; } std::string subscript; if (!ToStringSubScriptHelper(arg, subscript, type, cache)) return false; @@ -598,7 +612,7 @@ public: return AddChecksum(ret); } - bool ToPrivateString(const SigningProvider& arg, std::string& out) const final + bool ToPrivateString(const SigningProvider& arg, std::string& out) const override { bool ret = ToStringHelper(&arg, out, StringType::PRIVATE); out = AddChecksum(out); @@ -630,7 +644,7 @@ public: assert(outscripts.size() == 1); subscripts.emplace_back(std::move(outscripts[0])); } - out = Merge(std::move(out), std::move(subprovider)); + out.Merge(std::move(subprovider)); std::vector<CPubKey> pubkeys; pubkeys.reserve(entries.size()); @@ -684,6 +698,7 @@ public: return OutputTypeFromDestination(m_destination); } bool IsSingleType() const final { return true; } + bool ToPrivateString(const SigningProvider& arg, std::string& out) const final { return false; } }; /** A parsed raw(H) descriptor. */ @@ -704,6 +719,7 @@ public: return OutputTypeFromDestination(dest); } bool IsSingleType() const final { return true; } + bool ToPrivateString(const SigningProvider& arg, std::string& out) const final { return false; } }; /** A parsed pk(P) descriptor. */ @@ -882,7 +898,7 @@ protected: if (!xpk.IsFullyValid()) return {}; builder.Finalize(xpk); WitnessV1Taproot output = builder.GetOutput(); - out.tr_spenddata[output].Merge(builder.GetSpendData()); + out.tr_trees[output] = builder; out.pubkeys.emplace(keys[0].GetID(), keys[0]); return Vector(GetScriptForDestination(output)); } @@ -898,7 +914,7 @@ protected: } std::string tmp; if (!m_subdescriptor_args[pos]->ToStringHelper(arg, tmp, type, cache)) return false; - ret += std::move(tmp); + ret += tmp; while (!path.empty() && path.back()) { if (path.size() > 1) ret += '}'; path.pop_back(); @@ -917,6 +933,107 @@ public: bool IsSingleType() const final { return true; } }; +/* We instantiate Miniscript here with a simple integer as key type. + * The value of these key integers are an index in the + * DescriptorImpl::m_pubkey_args vector. + */ + +/** + * The context for converting a Miniscript descriptor into a Script. + */ +class ScriptMaker { + //! Keys contained in the Miniscript (the evaluation of DescriptorImpl::m_pubkey_args). + const std::vector<CPubKey>& m_keys; + +public: + ScriptMaker(const std::vector<CPubKey>& keys LIFETIMEBOUND) : m_keys(keys) {} + + std::vector<unsigned char> ToPKBytes(uint32_t key) const { + return {m_keys[key].begin(), m_keys[key].end()}; + } + + std::vector<unsigned char> ToPKHBytes(uint32_t key) const { + auto id = m_keys[key].GetID(); + return {id.begin(), id.end()}; + } +}; + +/** + * The context for converting a Miniscript descriptor to its textual form. + */ +class StringMaker { + //! To convert private keys for private descriptors. + const SigningProvider* m_arg; + //! Keys contained in the Miniscript (a reference to DescriptorImpl::m_pubkey_args). + const std::vector<std::unique_ptr<PubkeyProvider>>& m_pubkeys; + //! Whether to serialize keys as private or public. + bool m_private; + +public: + StringMaker(const SigningProvider* arg LIFETIMEBOUND, const std::vector<std::unique_ptr<PubkeyProvider>>& pubkeys LIFETIMEBOUND, bool priv) + : m_arg(arg), m_pubkeys(pubkeys), m_private(priv) {} + + std::optional<std::string> ToString(uint32_t key) const + { + std::string ret; + if (m_private) { + if (!m_pubkeys[key]->ToPrivateString(*m_arg, ret)) return {}; + } else { + ret = m_pubkeys[key]->ToString(); + } + return ret; + } +}; + +class MiniscriptDescriptor final : public DescriptorImpl +{ +private: + miniscript::NodeRef<uint32_t> m_node; + +protected: + std::vector<CScript> MakeScripts(const std::vector<CPubKey>& keys, Span<const CScript> scripts, + FlatSigningProvider& provider) const override + { + for (const auto& key : keys) provider.pubkeys.emplace(key.GetID(), key); + return Vector(m_node->ToScript(ScriptMaker(keys))); + } + +public: + MiniscriptDescriptor(std::vector<std::unique_ptr<PubkeyProvider>> providers, miniscript::NodeRef<uint32_t> node) + : DescriptorImpl(std::move(providers), "?"), m_node(std::move(node)) {} + + bool ToStringHelper(const SigningProvider* arg, std::string& out, const StringType type, + const DescriptorCache* cache = nullptr) const override + { + if (const auto res = m_node->ToString(StringMaker(arg, m_pubkey_args, type == StringType::PRIVATE))) { + out = *res; + return true; + } + return false; + } + + bool IsSolvable() const override { return false; } // For now, mark these descriptors as non-solvable (as we don't have signing logic for them). + bool IsSingleType() const final { return true; } +}; + +/** A parsed rawtr(...) descriptor. */ +class RawTRDescriptor final : public DescriptorImpl +{ +protected: + std::vector<CScript> MakeScripts(const std::vector<CPubKey>& keys, Span<const CScript> scripts, FlatSigningProvider& out) const override + { + assert(keys.size() == 1); + XOnlyPubKey xpk(keys[0]); + if (!xpk.IsFullyValid()) return {}; + WitnessV1Taproot output{xpk}; + return Vector(GetScriptForDestination(output)); + } +public: + RawTRDescriptor(std::unique_ptr<PubkeyProvider> output_key) : DescriptorImpl(Vector(std::move(output_key)), "rawtr") {} + std::optional<OutputType> GetOutputType() const override { return OutputType::BECH32M; } + bool IsSingleType() const final { return true; } +}; + //////////////////////////////////////////////////////////////////////////// // Parser // //////////////////////////////////////////////////////////////////////////// @@ -1058,6 +1175,94 @@ std::unique_ptr<PubkeyProvider> ParsePubkey(uint32_t key_exp_index, const Span<c return std::make_unique<OriginPubkeyProvider>(key_exp_index, std::move(info), std::move(provider)); } +std::unique_ptr<PubkeyProvider> InferPubkey(const CPubKey& pubkey, ParseScriptContext, const SigningProvider& provider) +{ + std::unique_ptr<PubkeyProvider> key_provider = std::make_unique<ConstPubkeyProvider>(0, pubkey, false); + KeyOriginInfo info; + if (provider.GetKeyOrigin(pubkey.GetID(), info)) { + return std::make_unique<OriginPubkeyProvider>(0, std::move(info), std::move(key_provider)); + } + return key_provider; +} + +std::unique_ptr<PubkeyProvider> InferXOnlyPubkey(const XOnlyPubKey& xkey, ParseScriptContext ctx, const SigningProvider& provider) +{ + unsigned char full_key[CPubKey::COMPRESSED_SIZE] = {0x02}; + std::copy(xkey.begin(), xkey.end(), full_key + 1); + CPubKey pubkey(full_key); + std::unique_ptr<PubkeyProvider> key_provider = std::make_unique<ConstPubkeyProvider>(0, pubkey, true); + KeyOriginInfo info; + if (provider.GetKeyOriginByXOnly(xkey, info)) { + return std::make_unique<OriginPubkeyProvider>(0, std::move(info), std::move(key_provider)); + } + return key_provider; +} + +/** + * The context for parsing a Miniscript descriptor (either from Script or from its textual representation). + */ +struct KeyParser { + //! The Key type is an index in DescriptorImpl::m_pubkey_args + using Key = uint32_t; + //! Must not be nullptr if parsing from string. + FlatSigningProvider* m_out; + //! Must not be nullptr if parsing from Script. + const SigningProvider* m_in; + //! List of keys contained in the Miniscript. + mutable std::vector<std::unique_ptr<PubkeyProvider>> m_keys; + //! Used to detect key parsing errors within a Miniscript. + mutable std::string m_key_parsing_error; + + KeyParser(FlatSigningProvider* out LIFETIMEBOUND, const SigningProvider* in LIFETIMEBOUND) : m_out(out), m_in(in) {} + + bool KeyCompare(const Key& a, const Key& b) const { + return *m_keys.at(a) < *m_keys.at(b); + } + + template<typename I> std::optional<Key> FromString(I begin, I end) const + { + assert(m_out); + Key key = m_keys.size(); + auto pk = ParsePubkey(key, {&*begin, &*end}, ParseScriptContext::P2WSH, *m_out, m_key_parsing_error); + if (!pk) return {}; + m_keys.push_back(std::move(pk)); + return key; + } + + std::optional<std::string> ToString(const Key& key) const + { + return m_keys.at(key)->ToString(); + } + + template<typename I> std::optional<Key> FromPKBytes(I begin, I end) const + { + assert(m_in); + CPubKey pubkey(begin, end); + if (pubkey.IsValid()) { + Key key = m_keys.size(); + m_keys.push_back(InferPubkey(pubkey, ParseScriptContext::P2WSH, *m_in)); + return key; + } + return {}; + } + + template<typename I> std::optional<Key> FromPKHBytes(I begin, I end) const + { + assert(end - begin == 20); + assert(m_in); + uint160 hash; + std::copy(begin, end, hash.begin()); + CKeyID keyid(hash); + CPubKey pubkey; + if (m_in->GetPubKey(keyid, pubkey)) { + Key key = m_keys.size(); + m_keys.push_back(InferPubkey(pubkey, ParseScriptContext::P2WSH, *m_in)); + return key; + } + return {}; + } +}; + /** Parse a script in a particular context. */ std::unique_ptr<DescriptorImpl> ParseScript(uint32_t& key_exp_index, Span<const char>& sp, ParseScriptContext ctx, FlatSigningProvider& out, std::string& error) { @@ -1267,6 +1472,20 @@ std::unique_ptr<DescriptorImpl> ParseScript(uint32_t& key_exp_index, Span<const error = "Can only have tr at top level"; return nullptr; } + if (ctx == ParseScriptContext::TOP && Func("rawtr", expr)) { + auto arg = Expr(expr); + if (expr.size()) { + error = strprintf("rawtr(): only one key expected."); + return nullptr; + } + auto output_key = ParsePubkey(key_exp_index, arg, ParseScriptContext::P2TR, out, error); + if (!output_key) return nullptr; + ++key_exp_index; + return std::make_unique<RawTRDescriptor>(std::move(output_key)); + } else if (Func("rawtr", expr)) { + error = "Can only have rawtr at top level"; + return nullptr; + } if (ctx == ParseScriptContext::TOP && Func("raw", expr)) { std::string str(expr.begin(), expr.end()); if (!IsHex(str)) { @@ -1279,6 +1498,45 @@ std::unique_ptr<DescriptorImpl> ParseScript(uint32_t& key_exp_index, Span<const error = "Can only have raw() at top level"; return nullptr; } + // Process miniscript expressions. + { + KeyParser parser(&out, nullptr); + auto node = miniscript::FromString(std::string(expr.begin(), expr.end()), parser); + if (node) { + if (ctx != ParseScriptContext::P2WSH) { + error = "Miniscript expressions can only be used in wsh"; + return nullptr; + } + if (parser.m_key_parsing_error != "") { + error = std::move(parser.m_key_parsing_error); + return nullptr; + } + if (!node->IsSane()) { + // Try to find the first insane sub for better error reporting. + auto insane_node = node.get(); + if (const auto sub = node->FindInsaneSub()) insane_node = sub; + if (const auto str = insane_node->ToString(parser)) error = *str; + if (!insane_node->IsValid()) { + error += " is invalid"; + } else { + error += " is not sane"; + if (!insane_node->IsNonMalleable()) { + error += ": malleable witnesses exist"; + } else if (insane_node == node.get() && !insane_node->NeedsSignature()) { + error += ": witnesses without signature exist"; + } else if (!insane_node->CheckTimeLocksMix()) { + error += ": contains mixes of timelocks expressed in blocks and seconds"; + } else if (!insane_node->CheckDuplicateKey()) { + error += ": contains duplicate public keys"; + } else if (!insane_node->ValidSatisfactions()) { + error += ": needs witnesses that may exceed resource limits"; + } + } + return nullptr; + } + return std::make_unique<MiniscriptDescriptor>(std::move(parser.m_keys), std::move(node)); + } + } if (ctx == ParseScriptContext::P2SH) { error = "A function is needed within P2SH"; return nullptr; @@ -1290,29 +1548,6 @@ std::unique_ptr<DescriptorImpl> ParseScript(uint32_t& key_exp_index, Span<const return nullptr; } -std::unique_ptr<PubkeyProvider> InferPubkey(const CPubKey& pubkey, ParseScriptContext, const SigningProvider& provider) -{ - std::unique_ptr<PubkeyProvider> key_provider = std::make_unique<ConstPubkeyProvider>(0, pubkey, false); - KeyOriginInfo info; - if (provider.GetKeyOrigin(pubkey.GetID(), info)) { - return std::make_unique<OriginPubkeyProvider>(0, std::move(info), std::move(key_provider)); - } - return key_provider; -} - -std::unique_ptr<PubkeyProvider> InferXOnlyPubkey(const XOnlyPubKey& xkey, ParseScriptContext ctx, const SigningProvider& provider) -{ - unsigned char full_key[CPubKey::COMPRESSED_SIZE] = {0x02}; - std::copy(xkey.begin(), xkey.end(), full_key + 1); - CPubKey pubkey(full_key); - std::unique_ptr<PubkeyProvider> key_provider = std::make_unique<ConstPubkeyProvider>(0, pubkey, true); - KeyOriginInfo info; - if (provider.GetKeyOriginByXOnly(xkey, info)) { - return std::make_unique<OriginPubkeyProvider>(0, std::move(info), std::move(key_provider)); - } - return key_provider; -} - std::unique_ptr<DescriptorImpl> InferMultiA(const CScript& script, ParseScriptContext ctx, const SigningProvider& provider) { auto match = MatchMultiA(script); @@ -1424,6 +1659,21 @@ std::unique_ptr<DescriptorImpl> InferScript(const CScript& script, ParseScriptCo } } } + // If the above doesn't work, construct a rawtr() descriptor with just the encoded x-only pubkey. + if (pubkey.IsFullyValid()) { + auto key = InferXOnlyPubkey(pubkey, ParseScriptContext::P2TR, provider); + if (key) { + return std::make_unique<RawTRDescriptor>(std::move(key)); + } + } + } + + if (ctx == ParseScriptContext::P2WSH) { + KeyParser parser(nullptr, &provider); + auto node = miniscript::FromScript(script, parser); + if (node && node->IsSane()) { + return std::make_unique<MiniscriptDescriptor>(std::move(parser.m_keys), std::move(node)); + } } CTxDestination dest; diff --git a/src/script/interpreter.cpp b/src/script/interpreter.cpp index 9f56301377..38bb11aad4 100644 --- a/src/script/interpreter.cpp +++ b/src/script/interpreter.cpp @@ -1342,7 +1342,7 @@ public: template <class T> uint256 GetPrevoutsSHA256(const T& txTo) { - CHashWriter ss(SER_GETHASH, 0); + HashWriter ss{}; for (const auto& txin : txTo.vin) { ss << txin.prevout; } @@ -1353,7 +1353,7 @@ uint256 GetPrevoutsSHA256(const T& txTo) template <class T> uint256 GetSequencesSHA256(const T& txTo) { - CHashWriter ss(SER_GETHASH, 0); + HashWriter ss{}; for (const auto& txin : txTo.vin) { ss << txin.nSequence; } @@ -1364,7 +1364,7 @@ uint256 GetSequencesSHA256(const T& txTo) template <class T> uint256 GetOutputsSHA256(const T& txTo) { - CHashWriter ss(SER_GETHASH, 0); + HashWriter ss{}; for (const auto& txout : txTo.vout) { ss << txout; } @@ -1374,7 +1374,7 @@ uint256 GetOutputsSHA256(const T& txTo) /** Compute the (single) SHA256 of the concatenation of all amounts spent by a tx. */ uint256 GetSpentAmountsSHA256(const std::vector<CTxOut>& outputs_spent) { - CHashWriter ss(SER_GETHASH, 0); + HashWriter ss{}; for (const auto& txout : outputs_spent) { ss << txout.nValue; } @@ -1384,7 +1384,7 @@ uint256 GetSpentAmountsSHA256(const std::vector<CTxOut>& outputs_spent) /** Compute the (single) SHA256 of the concatenation of all scriptPubKeys spent by a tx. */ uint256 GetSpentScriptsSHA256(const std::vector<CTxOut>& outputs_spent) { - CHashWriter ss(SER_GETHASH, 0); + HashWriter ss{}; for (const auto& txout : outputs_spent) { ss << txout.scriptPubKey; } @@ -1458,9 +1458,9 @@ template void PrecomputedTransactionData::Init(const CMutableTransaction& txTo, template PrecomputedTransactionData::PrecomputedTransactionData(const CTransaction& txTo); template PrecomputedTransactionData::PrecomputedTransactionData(const CMutableTransaction& txTo); -const CHashWriter HASHER_TAPSIGHASH = TaggedHash("TapSighash"); -const CHashWriter HASHER_TAPLEAF = TaggedHash("TapLeaf"); -const CHashWriter HASHER_TAPBRANCH = TaggedHash("TapBranch"); +const HashWriter HASHER_TAPSIGHASH{TaggedHash("TapSighash")}; +const HashWriter HASHER_TAPLEAF{TaggedHash("TapLeaf")}; +const HashWriter HASHER_TAPBRANCH{TaggedHash("TapBranch")}; static bool HandleMissingData(MissingDataBehavior mdb) { @@ -1499,7 +1499,7 @@ bool SignatureHashSchnorr(uint256& hash_out, ScriptExecutionData& execdata, cons return HandleMissingData(mdb); } - CHashWriter ss = HASHER_TAPSIGHASH; + HashWriter ss{HASHER_TAPSIGHASH}; // Epoch static constexpr uint8_t EPOCH = 0; @@ -1544,7 +1544,7 @@ bool SignatureHashSchnorr(uint256& hash_out, ScriptExecutionData& execdata, cons if (output_type == SIGHASH_SINGLE) { if (in_pos >= tx_to.vout.size()) return false; if (!execdata.m_output_hash) { - CHashWriter sha_single_output(SER_GETHASH, 0); + HashWriter sha_single_output{}; sha_single_output << tx_to.vout[in_pos]; execdata.m_output_hash = sha_single_output.GetSHA256(); } @@ -1587,12 +1587,12 @@ uint256 SignatureHash(const CScript& scriptCode, const T& txTo, unsigned int nIn if ((nHashType & 0x1f) != SIGHASH_SINGLE && (nHashType & 0x1f) != SIGHASH_NONE) { hashOutputs = cacheready ? cache->hashOutputs : SHA256Uint256(GetOutputsSHA256(txTo)); } else if ((nHashType & 0x1f) == SIGHASH_SINGLE && nIn < txTo.vout.size()) { - CHashWriter ss(SER_GETHASH, 0); + HashWriter ss{}; ss << txTo.vout[nIn]; hashOutputs = ss.GetHash(); } - CHashWriter ss(SER_GETHASH, 0); + HashWriter ss{}; // Version ss << txTo.nVersion; // Input prevouts/nSequence (none/all, depending on flags) @@ -1627,7 +1627,7 @@ uint256 SignatureHash(const CScript& scriptCode, const T& txTo, unsigned int nIn CTransactionSignatureSerializer<T> txTmp(txTo, scriptCode, nIn, nHashType); // Serialize and hash - CHashWriter ss(SER_GETHASH, 0); + HashWriter ss{}; ss << txTmp << nHashType; return ss.GetHash(); } @@ -1827,7 +1827,7 @@ static bool ExecuteWitnessScript(const Span<const valtype>& stack_span, const CS uint256 ComputeTapleafHash(uint8_t leaf_version, const CScript& script) { - return (CHashWriter(HASHER_TAPLEAF) << leaf_version << script).GetSHA256(); + return (HashWriter{HASHER_TAPLEAF} << leaf_version << script).GetSHA256(); } uint256 ComputeTaprootMerkleRoot(Span<const unsigned char> control, const uint256& tapleaf_hash) @@ -1839,7 +1839,7 @@ uint256 ComputeTaprootMerkleRoot(Span<const unsigned char> control, const uint25 const int path_len = (control.size() - TAPROOT_CONTROL_BASE_SIZE) / TAPROOT_CONTROL_NODE_SIZE; uint256 k = tapleaf_hash; for (int i = 0; i < path_len; ++i) { - CHashWriter ss_branch{HASHER_TAPBRANCH}; + HashWriter ss_branch{HASHER_TAPBRANCH}; Span node{Span{control}.subspan(TAPROOT_CONTROL_BASE_SIZE + TAPROOT_CONTROL_NODE_SIZE * i, TAPROOT_CONTROL_NODE_SIZE)}; if (std::lexicographical_compare(k.begin(), k.end(), node.begin(), node.end())) { ss_branch << k << node; @@ -1902,7 +1902,7 @@ static bool VerifyWitnessProgram(const CScriptWitness& witness, int witversion, if (stack.size() >= 2 && !stack.back().empty() && stack.back()[0] == ANNEX_TAG) { // Drop annex (this is non-standard; see IsWitnessStandard) const valtype& annex = SpanPopBack(stack); - execdata.m_annex_hash = (CHashWriter(SER_GETHASH, 0) << annex).GetSHA256(); + execdata.m_annex_hash = (HashWriter{} << annex).GetSHA256(); execdata.m_annex_present = true; } else { execdata.m_annex_present = false; diff --git a/src/script/interpreter.h b/src/script/interpreter.h index adf454aa15..ba910cc945 100644 --- a/src/script/interpreter.h +++ b/src/script/interpreter.h @@ -233,9 +233,9 @@ static constexpr size_t TAPROOT_CONTROL_NODE_SIZE = 32; static constexpr size_t TAPROOT_CONTROL_MAX_NODE_COUNT = 128; static constexpr size_t TAPROOT_CONTROL_MAX_SIZE = TAPROOT_CONTROL_BASE_SIZE + TAPROOT_CONTROL_NODE_SIZE * TAPROOT_CONTROL_MAX_NODE_COUNT; -extern const CHashWriter HASHER_TAPSIGHASH; //!< Hasher with tag "TapSighash" pre-fed to it. -extern const CHashWriter HASHER_TAPLEAF; //!< Hasher with tag "TapLeaf" pre-fed to it. -extern const CHashWriter HASHER_TAPBRANCH; //!< Hasher with tag "TapBranch" pre-fed to it. +extern const HashWriter HASHER_TAPSIGHASH; //!< Hasher with tag "TapSighash" pre-fed to it. +extern const HashWriter HASHER_TAPLEAF; //!< Hasher with tag "TapLeaf" pre-fed to it. +extern const HashWriter HASHER_TAPBRANCH; //!< Hasher with tag "TapBranch" pre-fed to it. template <class T> uint256 SignatureHash(const CScript& scriptCode, const T& txTo, unsigned int nIn, int nHashType, const CAmount& amount, SigVersion sigversion, const PrecomputedTransactionData* cache = nullptr); @@ -307,10 +307,10 @@ using MutableTransactionSignatureChecker = GenericTransactionSignatureChecker<CM class DeferringSignatureChecker : public BaseSignatureChecker { protected: - BaseSignatureChecker& m_checker; + const BaseSignatureChecker& m_checker; public: - DeferringSignatureChecker(BaseSignatureChecker& checker) : m_checker(checker) {} + DeferringSignatureChecker(const BaseSignatureChecker& checker) : m_checker(checker) {} bool CheckECDSASignature(const std::vector<unsigned char>& scriptSig, const std::vector<unsigned char>& vchPubKey, const CScript& scriptCode, SigVersion sigversion) const override { diff --git a/src/script/miniscript.h b/src/script/miniscript.h index 2c239c2678..6faf2624fd 100644 --- a/src/script/miniscript.h +++ b/src/script/miniscript.h @@ -13,8 +13,8 @@ #include <string> #include <vector> -#include <stdlib.h> #include <assert.h> +#include <cstdlib> #include <policy/policy.h> #include <primitives/transaction.h> @@ -276,6 +276,8 @@ struct StackSize { StackSize(MaxInt<uint32_t> in_sat, MaxInt<uint32_t> in_dsat) : sat(in_sat), dsat(in_dsat) {}; }; +struct NoDupCheck {}; + } // namespace internal //! A node in a miniscript expression. @@ -301,8 +303,13 @@ private: const Type typ; //! Cached script length (computed by CalcScriptLen). const size_t scriptlen; - //! Whether a public key appears more than once in this node. - const bool duplicate_key; + //! Whether a public key appears more than once in this node. This value is initialized + //! by all constructors except the NoDupCheck ones. The NoDupCheck ones skip the + //! computation, requiring it to be done manually by invoking DuplicateKeyCheck(). + //! DuplicateKeyCheck(), or a non-NoDupCheck constructor, will compute has_duplicate_keys + //! for all subnodes as well. + mutable std::optional<bool> has_duplicate_keys; + //! Compute the length of the script for this miniscript (including children). size_t CalcScriptLen() const { @@ -429,6 +436,21 @@ private: )); } + /** Like TreeEval, but without downfn or State type. + * upfn takes (const Node&, Span<Result>) and returns Result. */ + template<typename Result, typename UpFn> + Result TreeEval(UpFn upfn) const + { + struct DummyState {}; + return std::move(*TreeEvalMaybe<Result>(DummyState{}, + [](DummyState, const Node&, size_t) { return DummyState{}; }, + [&upfn](DummyState, const Node& node, Span<Result> subs) { + Result res{upfn(node, subs)}; + return std::optional<Result>(std::move(res)); + } + )); + } + /** Compare two miniscript subtrees, using a non-recursive algorithm. */ friend int Compare(const Node<Key>& node1, const Node<Key>& node2) { @@ -639,6 +661,7 @@ public: return TreeEvalMaybe<std::string>(false, downfn, upfn); } +private: internal::Ops CalcOps() const { switch (fragment) { case Fragment::JUST_1: return {0, 0, {}}; @@ -762,11 +785,14 @@ public: assert(false); } - /** Check whether any key is repeated. +public: + /** Update duplicate key information in this Node. + * * This uses a custom key comparator provided by the context in order to still detect duplicates * for more complicated types. */ - template<typename Ctx> bool ContainsDuplicateKey(const Ctx& ctx) const { + template<typename Ctx> void DuplicateKeyCheck(const Ctx& ctx) const + { // We cannot use a lambda here, as lambdas are non assignable, and the set operations // below require moving the comparators around. struct Comp { @@ -774,31 +800,55 @@ public: Comp(const Ctx& ctx) : ctx_ptr(&ctx) {} bool operator()(const Key& a, const Key& b) const { return ctx_ptr->KeyCompare(a, b); } }; - using set = std::set<Key, Comp>; - auto upfn = [this, &ctx](const Node& node, Span<set> subs) -> std::optional<set> { - if (&node != this && node.duplicate_key) return {}; + // state in the recursive computation: + // - std::nullopt means "this node has duplicates" + // - an std::set means "this node has no duplicate keys, and they are: ...". + using keyset = std::set<Key, Comp>; + using state = std::optional<keyset>; + + auto upfn = [&ctx](const Node& node, Span<state> subs) -> state { + // If this node is already known to have duplicates, nothing left to do. + if (node.has_duplicate_keys.has_value() && *node.has_duplicate_keys) return {}; + // Check if one of the children is already known to have duplicates. + for (auto& sub : subs) { + if (!sub.has_value()) { + node.has_duplicate_keys = true; + return {}; + } + } + + // Start building the set of keys involved in this node and children. + // Start by keys in this node directly. size_t keys_count = node.keys.size(); - set key_set{node.keys.begin(), node.keys.end(), Comp(ctx)}; - if (key_set.size() != keys_count) return {}; + keyset key_set{node.keys.begin(), node.keys.end(), Comp(ctx)}; + if (key_set.size() != keys_count) { + // It already has duplicates; bail out. + node.has_duplicate_keys = true; + return {}; + } - for (auto& sub: subs) { - keys_count += sub.size(); + // Merge the keys from the children into this set. + for (auto& sub : subs) { + keys_count += sub->size(); // Small optimization: std::set::merge is linear in the size of the second arg but // logarithmic in the size of the first. - if (key_set.size() < sub.size()) std::swap(key_set, sub); - key_set.merge(sub); - if (key_set.size() != keys_count) return {}; + if (key_set.size() < sub->size()) std::swap(key_set, *sub); + key_set.merge(*sub); + if (key_set.size() != keys_count) { + node.has_duplicate_keys = true; + return {}; + } } + node.has_duplicate_keys = false; return key_set; }; - return !TreeEvalMaybe<set>(upfn); + TreeEval<state>(upfn); } -public: //! Return the size of the script for this expression (faster than ToScript().size()). size_t ScriptSize() const { return scriptlen; } @@ -818,6 +868,15 @@ public: //! Return the expression type. Type GetType() const { return typ; } + //! Find an insane subnode which has no insane children. Nullptr if there is none. + const Node* FindInsaneSub() const { + return TreeEval<const Node*>([](const Node& node, Span<const Node*> subs) -> const Node* { + for (auto& sub: subs) if (sub) return sub; + if (!node.IsSaneSubexpression()) return &node; + return nullptr; + }); + } + //! Check whether this node is valid at all. bool IsValid() const { return !(GetType() == ""_mst) && ScriptSize() <= MAX_STANDARD_P2WSH_SCRIPT_SIZE; } @@ -834,7 +893,7 @@ public: bool CheckTimeLocksMix() const { return GetType() << "k"_mst; } //! Check whether there is no duplicate key across this fragment and all its sub-fragments. - bool CheckDuplicateKey() const { return !duplicate_key; } + bool CheckDuplicateKey() const { return has_duplicate_keys && !*has_duplicate_keys; } //! Whether successful non-malleable satisfactions are guaranteed to be valid. bool ValidSatisfactions() const { return IsValid() && CheckOpsLimit() && CheckStackSize(); } @@ -848,13 +907,21 @@ public: //! Equality testing. bool operator==(const Node<Key>& arg) const { return Compare(*this, arg) == 0; } - // Constructors with various argument combinations. - template <typename Ctx> Node(const Ctx& ctx, Fragment nt, std::vector<NodeRef<Key>> sub, std::vector<unsigned char> arg, uint32_t val = 0) : fragment(nt), k(val), data(std::move(arg)), subs(std::move(sub)), ops(CalcOps()), ss(CalcStackSize()), typ(CalcType()), scriptlen(CalcScriptLen()), duplicate_key(ContainsDuplicateKey(ctx)) {} - template <typename Ctx> Node(const Ctx& ctx, Fragment nt, std::vector<unsigned char> arg, uint32_t val = 0) : fragment(nt), k(val), data(std::move(arg)), ops(CalcOps()), ss(CalcStackSize()), typ(CalcType()), scriptlen(CalcScriptLen()), duplicate_key(ContainsDuplicateKey(ctx)) {} - template <typename Ctx> Node(const Ctx& ctx, Fragment nt, std::vector<NodeRef<Key>> sub, std::vector<Key> key, uint32_t val = 0) : fragment(nt), k(val), keys(std::move(key)), subs(std::move(sub)), ops(CalcOps()), ss(CalcStackSize()), typ(CalcType()), scriptlen(CalcScriptLen()), duplicate_key(ContainsDuplicateKey(ctx)) {} - template <typename Ctx> Node(const Ctx& ctx, Fragment nt, std::vector<Key> key, uint32_t val = 0) : fragment(nt), k(val), keys(std::move(key)), ops(CalcOps()), ss(CalcStackSize()), typ(CalcType()), scriptlen(CalcScriptLen()), duplicate_key(ContainsDuplicateKey(ctx)) {} - template <typename Ctx> Node(const Ctx& ctx, Fragment nt, std::vector<NodeRef<Key>> sub, uint32_t val = 0) : fragment(nt), k(val), subs(std::move(sub)), ops(CalcOps()), ss(CalcStackSize()), typ(CalcType()), scriptlen(CalcScriptLen()), duplicate_key(ContainsDuplicateKey(ctx)) {} - template <typename Ctx> Node(const Ctx& ctx, Fragment nt, uint32_t val = 0) : fragment(nt), k(val), ops(CalcOps()), ss(CalcStackSize()), typ(CalcType()), scriptlen(CalcScriptLen()), duplicate_key(ContainsDuplicateKey(ctx)) {} + // Constructors with various argument combinations, which bypass the duplicate key check. + Node(internal::NoDupCheck, Fragment nt, std::vector<NodeRef<Key>> sub, std::vector<unsigned char> arg, uint32_t val = 0) : fragment(nt), k(val), data(std::move(arg)), subs(std::move(sub)), ops(CalcOps()), ss(CalcStackSize()), typ(CalcType()), scriptlen(CalcScriptLen()) {} + Node(internal::NoDupCheck, Fragment nt, std::vector<unsigned char> arg, uint32_t val = 0) : fragment(nt), k(val), data(std::move(arg)), ops(CalcOps()), ss(CalcStackSize()), typ(CalcType()), scriptlen(CalcScriptLen()) {} + Node(internal::NoDupCheck, Fragment nt, std::vector<NodeRef<Key>> sub, std::vector<Key> key, uint32_t val = 0) : fragment(nt), k(val), keys(std::move(key)), subs(std::move(sub)), ops(CalcOps()), ss(CalcStackSize()), typ(CalcType()), scriptlen(CalcScriptLen()) {} + Node(internal::NoDupCheck, Fragment nt, std::vector<Key> key, uint32_t val = 0) : fragment(nt), k(val), keys(std::move(key)), ops(CalcOps()), ss(CalcStackSize()), typ(CalcType()), scriptlen(CalcScriptLen()) {} + Node(internal::NoDupCheck, Fragment nt, std::vector<NodeRef<Key>> sub, uint32_t val = 0) : fragment(nt), k(val), subs(std::move(sub)), ops(CalcOps()), ss(CalcStackSize()), typ(CalcType()), scriptlen(CalcScriptLen()) {} + Node(internal::NoDupCheck, Fragment nt, uint32_t val = 0) : fragment(nt), k(val), ops(CalcOps()), ss(CalcStackSize()), typ(CalcType()), scriptlen(CalcScriptLen()) {} + + // Constructors with various argument combinations, which do perform the duplicate key check. + template <typename Ctx> Node(const Ctx& ctx, Fragment nt, std::vector<NodeRef<Key>> sub, std::vector<unsigned char> arg, uint32_t val = 0) : Node(internal::NoDupCheck{}, nt, std::move(sub), std::move(arg), val) { DuplicateKeyCheck(ctx); } + template <typename Ctx> Node(const Ctx& ctx, Fragment nt, std::vector<unsigned char> arg, uint32_t val = 0) : Node(internal::NoDupCheck{}, nt, std::move(arg), val) { DuplicateKeyCheck(ctx);} + template <typename Ctx> Node(const Ctx& ctx, Fragment nt, std::vector<NodeRef<Key>> sub, std::vector<Key> key, uint32_t val = 0) : Node(internal::NoDupCheck{}, nt, std::move(sub), std::move(key), val) { DuplicateKeyCheck(ctx); } + template <typename Ctx> Node(const Ctx& ctx, Fragment nt, std::vector<Key> key, uint32_t val = 0) : Node(internal::NoDupCheck{}, nt, std::move(key), val) { DuplicateKeyCheck(ctx); } + template <typename Ctx> Node(const Ctx& ctx, Fragment nt, std::vector<NodeRef<Key>> sub, uint32_t val = 0) : Node(internal::NoDupCheck{}, nt, std::move(sub), val) { DuplicateKeyCheck(ctx); } + template <typename Ctx> Node(const Ctx& ctx, Fragment nt, uint32_t val = 0) : Node(internal::NoDupCheck{}, nt, val) { DuplicateKeyCheck(ctx); } }; namespace internal { @@ -941,24 +1008,40 @@ std::optional<std::pair<std::vector<unsigned char>, int>> ParseHexStrEnd(Span<co } /** BuildBack pops the last two elements off `constructed` and wraps them in the specified Fragment */ -template<typename Key, typename Ctx> -void BuildBack(const Ctx& ctx, Fragment nt, std::vector<NodeRef<Key>>& constructed, const bool reverse = false) +template<typename Key> +void BuildBack(Fragment nt, std::vector<NodeRef<Key>>& constructed, const bool reverse = false) { NodeRef<Key> child = std::move(constructed.back()); constructed.pop_back(); if (reverse) { - constructed.back() = MakeNodeRef<Key>(ctx, nt, Vector(std::move(child), std::move(constructed.back()))); + constructed.back() = MakeNodeRef<Key>(internal::NoDupCheck{}, nt, Vector(std::move(child), std::move(constructed.back()))); } else { - constructed.back() = MakeNodeRef<Key>(ctx, nt, Vector(std::move(constructed.back()), std::move(child))); + constructed.back() = MakeNodeRef<Key>(internal::NoDupCheck{}, nt, Vector(std::move(constructed.back()), std::move(child))); } } -//! Parse a miniscript from its textual descriptor form. +/** + * Parse a miniscript from its textual descriptor form. + * This does not check whether the script is valid, let alone sane. The caller is expected to use + * the `IsValidTopLevel()` and `IsSaneTopLevel()` to check for these properties on the node. + */ template<typename Key, typename Ctx> inline NodeRef<Key> Parse(Span<const char> in, const Ctx& ctx) { using namespace spanparsing; + // Account for the minimum script size for all parsed fragments so far. It "borrows" 1 + // script byte from all leaf nodes, counting it instead whenever a space for a recursive + // expression is added (through andor, and_*, or_*, thresh). This guarantees that all fragments + // increment the script_size by at least one, except for: + // - "0", "1": these leafs are only a single byte, so their subtracted-from increment is 0. + // This is not an issue however, as "space" for them has to be created by combinators, + // which do increment script_size. + // - "v:": the v wrapper adds nothing as in some cases it results in no opcode being added + // (instead transforming another opcode into its VERIFY form). However, the v: wrapper has + // to be interleaved with other fragments to be valid, so this is not a concern. + size_t script_size{1}; + // The two integers are used to hold state for thresh() std::vector<std::tuple<ParseContext, int64_t, int64_t>> to_parse; std::vector<NodeRef<Key>> constructed; @@ -966,14 +1049,16 @@ inline NodeRef<Key> Parse(Span<const char> in, const Ctx& ctx) to_parse.emplace_back(ParseContext::WRAPPED_EXPR, -1, -1); while (!to_parse.empty()) { + if (script_size > MAX_STANDARD_P2WSH_SCRIPT_SIZE) return {}; + // Get the current context we are decoding within auto [cur_context, n, k] = to_parse.back(); to_parse.pop_back(); switch (cur_context) { case ParseContext::WRAPPED_EXPR: { - int colon_index = -1; - for (int i = 1; i < (int)in.size(); ++i) { + std::optional<size_t> colon_index{}; + for (size_t i = 1; i < in.size(); ++i) { if (in[i] == ':') { colon_index = i; break; @@ -981,106 +1066,131 @@ inline NodeRef<Key> Parse(Span<const char> in, const Ctx& ctx) if (in[i] < 'a' || in[i] > 'z') break; } // If there is no colon, this loop won't execute - for (int j = 0; j < colon_index; ++j) { + bool last_was_v{false}; + for (size_t j = 0; colon_index && j < *colon_index; ++j) { + if (script_size > MAX_STANDARD_P2WSH_SCRIPT_SIZE) return {}; if (in[j] == 'a') { + script_size += 2; to_parse.emplace_back(ParseContext::ALT, -1, -1); } else if (in[j] == 's') { + script_size += 1; to_parse.emplace_back(ParseContext::SWAP, -1, -1); } else if (in[j] == 'c') { + script_size += 1; to_parse.emplace_back(ParseContext::CHECK, -1, -1); } else if (in[j] == 'd') { + script_size += 3; to_parse.emplace_back(ParseContext::DUP_IF, -1, -1); } else if (in[j] == 'j') { + script_size += 4; to_parse.emplace_back(ParseContext::NON_ZERO, -1, -1); } else if (in[j] == 'n') { + script_size += 1; to_parse.emplace_back(ParseContext::ZERO_NOTEQUAL, -1, -1); } else if (in[j] == 'v') { + // do not permit "...vv...:"; it's not valid, and also doesn't trigger early + // failure as script_size isn't incremented. + if (last_was_v) return {}; to_parse.emplace_back(ParseContext::VERIFY, -1, -1); } else if (in[j] == 'u') { + script_size += 4; to_parse.emplace_back(ParseContext::WRAP_U, -1, -1); } else if (in[j] == 't') { + script_size += 1; to_parse.emplace_back(ParseContext::WRAP_T, -1, -1); } else if (in[j] == 'l') { // The l: wrapper is equivalent to or_i(0,X) - constructed.push_back(MakeNodeRef<Key>(ctx, Fragment::JUST_0)); + script_size += 4; + constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::JUST_0)); to_parse.emplace_back(ParseContext::OR_I, -1, -1); } else { return {}; } + last_was_v = (in[j] == 'v'); } to_parse.emplace_back(ParseContext::EXPR, -1, -1); - in = in.subspan(colon_index + 1); + if (colon_index) in = in.subspan(*colon_index + 1); break; } case ParseContext::EXPR: { if (Const("0", in)) { - constructed.push_back(MakeNodeRef<Key>(ctx, Fragment::JUST_0)); + constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::JUST_0)); } else if (Const("1", in)) { - constructed.push_back(MakeNodeRef<Key>(ctx, Fragment::JUST_1)); + constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::JUST_1)); } else if (Const("pk(", in)) { auto res = ParseKeyEnd<Key, Ctx>(in, ctx); if (!res) return {}; auto& [key, key_size] = *res; - constructed.push_back(MakeNodeRef<Key>(ctx, Fragment::WRAP_C, Vector(MakeNodeRef<Key>(ctx, Fragment::PK_K, Vector(std::move(key)))))); + constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::WRAP_C, Vector(MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::PK_K, Vector(std::move(key)))))); in = in.subspan(key_size + 1); + script_size += 34; } else if (Const("pkh(", in)) { auto res = ParseKeyEnd<Key>(in, ctx); if (!res) return {}; auto& [key, key_size] = *res; - constructed.push_back(MakeNodeRef<Key>(ctx, Fragment::WRAP_C, Vector(MakeNodeRef<Key>(ctx, Fragment::PK_H, Vector(std::move(key)))))); + constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::WRAP_C, Vector(MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::PK_H, Vector(std::move(key)))))); in = in.subspan(key_size + 1); + script_size += 24; } else if (Const("pk_k(", in)) { auto res = ParseKeyEnd<Key>(in, ctx); if (!res) return {}; auto& [key, key_size] = *res; - constructed.push_back(MakeNodeRef<Key>(ctx, Fragment::PK_K, Vector(std::move(key)))); + constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::PK_K, Vector(std::move(key)))); in = in.subspan(key_size + 1); + script_size += 33; } else if (Const("pk_h(", in)) { auto res = ParseKeyEnd<Key>(in, ctx); if (!res) return {}; auto& [key, key_size] = *res; - constructed.push_back(MakeNodeRef<Key>(ctx, Fragment::PK_H, Vector(std::move(key)))); + constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::PK_H, Vector(std::move(key)))); in = in.subspan(key_size + 1); + script_size += 23; } else if (Const("sha256(", in)) { auto res = ParseHexStrEnd(in, 32, ctx); if (!res) return {}; auto& [hash, hash_size] = *res; - constructed.push_back(MakeNodeRef<Key>(ctx, Fragment::SHA256, std::move(hash))); + constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::SHA256, std::move(hash))); in = in.subspan(hash_size + 1); + script_size += 38; } else if (Const("ripemd160(", in)) { auto res = ParseHexStrEnd(in, 20, ctx); if (!res) return {}; auto& [hash, hash_size] = *res; - constructed.push_back(MakeNodeRef<Key>(ctx, Fragment::RIPEMD160, std::move(hash))); + constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::RIPEMD160, std::move(hash))); in = in.subspan(hash_size + 1); + script_size += 26; } else if (Const("hash256(", in)) { auto res = ParseHexStrEnd(in, 32, ctx); if (!res) return {}; auto& [hash, hash_size] = *res; - constructed.push_back(MakeNodeRef<Key>(ctx, Fragment::HASH256, std::move(hash))); + constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::HASH256, std::move(hash))); in = in.subspan(hash_size + 1); + script_size += 38; } else if (Const("hash160(", in)) { auto res = ParseHexStrEnd(in, 20, ctx); if (!res) return {}; auto& [hash, hash_size] = *res; - constructed.push_back(MakeNodeRef<Key>(ctx, Fragment::HASH160, std::move(hash))); + constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::HASH160, std::move(hash))); in = in.subspan(hash_size + 1); + script_size += 26; } else if (Const("after(", in)) { int arg_size = FindNextChar(in, ')'); if (arg_size < 1) return {}; int64_t num; if (!ParseInt64(std::string(in.begin(), in.begin() + arg_size), &num)) return {}; if (num < 1 || num >= 0x80000000L) return {}; - constructed.push_back(MakeNodeRef<Key>(ctx, Fragment::AFTER, num)); + constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::AFTER, num)); in = in.subspan(arg_size + 1); + script_size += 1 + (num > 16) + (num > 0x7f) + (num > 0x7fff) + (num > 0x7fffff); } else if (Const("older(", in)) { int arg_size = FindNextChar(in, ')'); if (arg_size < 1) return {}; int64_t num; if (!ParseInt64(std::string(in.begin(), in.begin() + arg_size), &num)) return {}; if (num < 1 || num >= 0x80000000L) return {}; - constructed.push_back(MakeNodeRef<Key>(ctx, Fragment::OLDER, num)); + constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::OLDER, num)); in = in.subspan(arg_size + 1); + script_size += 1 + (num > 16) + (num > 0x7f) + (num > 0x7fff) + (num > 0x7fffff); } else if (Const("multi(", in)) { // Get threshold int next_comma = FindNextChar(in, ','); @@ -1100,7 +1210,8 @@ inline NodeRef<Key> Parse(Span<const char> in, const Ctx& ctx) } if (keys.size() < 1 || keys.size() > 20) return {}; if (k < 1 || k > (int64_t)keys.size()) return {}; - constructed.push_back(MakeNodeRef<Key>(ctx, Fragment::MULTI, std::move(keys), k)); + script_size += 2 + (keys.size() > 16) + (k > 16) + 34 * keys.size(); + constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::MULTI, std::move(keys), k)); } else if (Const("thresh(", in)) { int next_comma = FindNextChar(in, ','); if (next_comma < 1) return {}; @@ -1110,6 +1221,7 @@ inline NodeRef<Key> Parse(Span<const char> in, const Ctx& ctx) // n = 1 here because we read the first WRAPPED_EXPR before reaching THRESH to_parse.emplace_back(ParseContext::THRESH, 1, k); to_parse.emplace_back(ParseContext::WRAPPED_EXPR, -1, -1); + script_size += 2 + (k > 16) + (k > 0x7f) + (k > 0x7fff) + (k > 0x7fffff); } else if (Const("andor(", in)) { to_parse.emplace_back(ParseContext::ANDOR, -1, -1); to_parse.emplace_back(ParseContext::CLOSE_BRACKET, -1, -1); @@ -1118,21 +1230,29 @@ inline NodeRef<Key> Parse(Span<const char> in, const Ctx& ctx) to_parse.emplace_back(ParseContext::WRAPPED_EXPR, -1, -1); to_parse.emplace_back(ParseContext::COMMA, -1, -1); to_parse.emplace_back(ParseContext::WRAPPED_EXPR, -1, -1); + script_size += 5; } else { if (Const("and_n(", in)) { to_parse.emplace_back(ParseContext::AND_N, -1, -1); + script_size += 5; } else if (Const("and_b(", in)) { to_parse.emplace_back(ParseContext::AND_B, -1, -1); + script_size += 2; } else if (Const("and_v(", in)) { to_parse.emplace_back(ParseContext::AND_V, -1, -1); + script_size += 1; } else if (Const("or_b(", in)) { to_parse.emplace_back(ParseContext::OR_B, -1, -1); + script_size += 2; } else if (Const("or_c(", in)) { to_parse.emplace_back(ParseContext::OR_C, -1, -1); + script_size += 3; } else if (Const("or_d(", in)) { to_parse.emplace_back(ParseContext::OR_D, -1, -1); + script_size += 4; } else if (Const("or_i(", in)) { to_parse.emplace_back(ParseContext::OR_I, -1, -1); + script_size += 4; } else { return {}; } @@ -1144,69 +1264,70 @@ inline NodeRef<Key> Parse(Span<const char> in, const Ctx& ctx) break; } case ParseContext::ALT: { - constructed.back() = MakeNodeRef<Key>(ctx, Fragment::WRAP_A, Vector(std::move(constructed.back()))); + constructed.back() = MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::WRAP_A, Vector(std::move(constructed.back()))); break; } case ParseContext::SWAP: { - constructed.back() = MakeNodeRef<Key>(ctx, Fragment::WRAP_S, Vector(std::move(constructed.back()))); + constructed.back() = MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::WRAP_S, Vector(std::move(constructed.back()))); break; } case ParseContext::CHECK: { - constructed.back() = MakeNodeRef<Key>(ctx, Fragment::WRAP_C, Vector(std::move(constructed.back()))); + constructed.back() = MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::WRAP_C, Vector(std::move(constructed.back()))); break; } case ParseContext::DUP_IF: { - constructed.back() = MakeNodeRef<Key>(ctx, Fragment::WRAP_D, Vector(std::move(constructed.back()))); + constructed.back() = MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::WRAP_D, Vector(std::move(constructed.back()))); break; } case ParseContext::NON_ZERO: { - constructed.back() = MakeNodeRef<Key>(ctx, Fragment::WRAP_J, Vector(std::move(constructed.back()))); + constructed.back() = MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::WRAP_J, Vector(std::move(constructed.back()))); break; } case ParseContext::ZERO_NOTEQUAL: { - constructed.back() = MakeNodeRef<Key>(ctx, Fragment::WRAP_N, Vector(std::move(constructed.back()))); + constructed.back() = MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::WRAP_N, Vector(std::move(constructed.back()))); break; } case ParseContext::VERIFY: { - constructed.back() = MakeNodeRef<Key>(ctx, Fragment::WRAP_V, Vector(std::move(constructed.back()))); + script_size += (constructed.back()->GetType() << "x"_mst); + constructed.back() = MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::WRAP_V, Vector(std::move(constructed.back()))); break; } case ParseContext::WRAP_U: { - constructed.back() = MakeNodeRef<Key>(ctx, Fragment::OR_I, Vector(std::move(constructed.back()), MakeNodeRef<Key>(ctx, Fragment::JUST_0))); + constructed.back() = MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::OR_I, Vector(std::move(constructed.back()), MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::JUST_0))); break; } case ParseContext::WRAP_T: { - constructed.back() = MakeNodeRef<Key>(ctx, Fragment::AND_V, Vector(std::move(constructed.back()), MakeNodeRef<Key>(ctx, Fragment::JUST_1))); + constructed.back() = MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::AND_V, Vector(std::move(constructed.back()), MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::JUST_1))); break; } case ParseContext::AND_B: { - BuildBack(ctx, Fragment::AND_B, constructed); + BuildBack(Fragment::AND_B, constructed); break; } case ParseContext::AND_N: { auto mid = std::move(constructed.back()); constructed.pop_back(); - constructed.back() = MakeNodeRef<Key>(ctx, Fragment::ANDOR, Vector(std::move(constructed.back()), std::move(mid), MakeNodeRef<Key>(ctx, Fragment::JUST_0))); + constructed.back() = MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::ANDOR, Vector(std::move(constructed.back()), std::move(mid), MakeNodeRef<Key>(ctx, Fragment::JUST_0))); break; } case ParseContext::AND_V: { - BuildBack(ctx, Fragment::AND_V, constructed); + BuildBack(Fragment::AND_V, constructed); break; } case ParseContext::OR_B: { - BuildBack(ctx, Fragment::OR_B, constructed); + BuildBack(Fragment::OR_B, constructed); break; } case ParseContext::OR_C: { - BuildBack(ctx, Fragment::OR_C, constructed); + BuildBack(Fragment::OR_C, constructed); break; } case ParseContext::OR_D: { - BuildBack(ctx, Fragment::OR_D, constructed); + BuildBack(Fragment::OR_D, constructed); break; } case ParseContext::OR_I: { - BuildBack(ctx, Fragment::OR_I, constructed); + BuildBack(Fragment::OR_I, constructed); break; } case ParseContext::ANDOR: { @@ -1214,7 +1335,7 @@ inline NodeRef<Key> Parse(Span<const char> in, const Ctx& ctx) constructed.pop_back(); auto mid = std::move(constructed.back()); constructed.pop_back(); - constructed.back() = MakeNodeRef<Key>(ctx, Fragment::ANDOR, Vector(std::move(constructed.back()), std::move(mid), std::move(right))); + constructed.back() = MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::ANDOR, Vector(std::move(constructed.back()), std::move(mid), std::move(right))); break; } case ParseContext::THRESH: { @@ -1223,6 +1344,7 @@ inline NodeRef<Key> Parse(Span<const char> in, const Ctx& ctx) in = in.subspan(1); to_parse.emplace_back(ParseContext::THRESH, n+1, k); to_parse.emplace_back(ParseContext::WRAPPED_EXPR, -1, -1); + script_size += 2; } else if (in[0] == ')') { if (k > n) return {}; in = in.subspan(1); @@ -1233,7 +1355,7 @@ inline NodeRef<Key> Parse(Span<const char> in, const Ctx& ctx) constructed.pop_back(); } std::reverse(subs.begin(), subs.end()); - constructed.push_back(MakeNodeRef<Key>(ctx, Fragment::THRESH, std::move(subs), k)); + constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::THRESH, std::move(subs), k)); } else { return {}; } @@ -1254,9 +1376,10 @@ inline NodeRef<Key> Parse(Span<const char> in, const Ctx& ctx) // Sanity checks on the produced miniscript assert(constructed.size() == 1); + assert(constructed[0]->ScriptSize() == script_size); if (in.size() > 0) return {}; const NodeRef<Key> tl_node = std::move(constructed.front()); - if (!tl_node->IsValidTopLevel()) return {}; + tl_node->DuplicateKeyCheck(ctx); return tl_node; } @@ -1368,12 +1491,12 @@ inline NodeRef<Key> DecodeScript(I& in, I last, const Ctx& ctx) // Constants if (in[0].first == OP_1) { ++in; - constructed.push_back(MakeNodeRef<Key>(ctx, Fragment::JUST_1)); + constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::JUST_1)); break; } if (in[0].first == OP_0) { ++in; - constructed.push_back(MakeNodeRef<Key>(ctx, Fragment::JUST_0)); + constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::JUST_0)); break; } // Public keys @@ -1381,14 +1504,14 @@ inline NodeRef<Key> DecodeScript(I& in, I last, const Ctx& ctx) auto key = ctx.FromPKBytes(in[0].second.begin(), in[0].second.end()); if (!key) return {}; ++in; - constructed.push_back(MakeNodeRef<Key>(ctx, Fragment::PK_K, Vector(std::move(*key)))); + constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::PK_K, Vector(std::move(*key)))); break; } if (last - in >= 5 && in[0].first == OP_VERIFY && in[1].first == OP_EQUAL && in[3].first == OP_HASH160 && in[4].first == OP_DUP && in[2].second.size() == 20) { auto key = ctx.FromPKHBytes(in[2].second.begin(), in[2].second.end()); if (!key) return {}; in += 5; - constructed.push_back(MakeNodeRef<Key>(ctx, Fragment::PK_H, Vector(std::move(*key)))); + constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::PK_H, Vector(std::move(*key)))); break; } // Time locks @@ -1396,31 +1519,31 @@ inline NodeRef<Key> DecodeScript(I& in, I last, const Ctx& ctx) if (last - in >= 2 && in[0].first == OP_CHECKSEQUENCEVERIFY && (num = ParseScriptNumber(in[1]))) { in += 2; if (*num < 1 || *num > 0x7FFFFFFFL) return {}; - constructed.push_back(MakeNodeRef<Key>(ctx, Fragment::OLDER, *num)); + constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::OLDER, *num)); break; } if (last - in >= 2 && in[0].first == OP_CHECKLOCKTIMEVERIFY && (num = ParseScriptNumber(in[1]))) { in += 2; if (num < 1 || num > 0x7FFFFFFFL) return {}; - constructed.push_back(MakeNodeRef<Key>(ctx, Fragment::AFTER, *num)); + constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::AFTER, *num)); break; } // Hashes if (last - in >= 7 && in[0].first == OP_EQUAL && in[3].first == OP_VERIFY && in[4].first == OP_EQUAL && (num = ParseScriptNumber(in[5])) && num == 32 && in[6].first == OP_SIZE) { if (in[2].first == OP_SHA256 && in[1].second.size() == 32) { - constructed.push_back(MakeNodeRef<Key>(ctx, Fragment::SHA256, in[1].second)); + constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::SHA256, in[1].second)); in += 7; break; } else if (in[2].first == OP_RIPEMD160 && in[1].second.size() == 20) { - constructed.push_back(MakeNodeRef<Key>(ctx, Fragment::RIPEMD160, in[1].second)); + constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::RIPEMD160, in[1].second)); in += 7; break; } else if (in[2].first == OP_HASH256 && in[1].second.size() == 32) { - constructed.push_back(MakeNodeRef<Key>(ctx, Fragment::HASH256, in[1].second)); + constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::HASH256, in[1].second)); in += 7; break; } else if (in[2].first == OP_HASH160 && in[1].second.size() == 20) { - constructed.push_back(MakeNodeRef<Key>(ctx, Fragment::HASH160, in[1].second)); + constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::HASH160, in[1].second)); in += 7; break; } @@ -1441,7 +1564,7 @@ inline NodeRef<Key> DecodeScript(I& in, I last, const Ctx& ctx) if (!k || *k < 1 || *k > *n) return {}; in += 3 + *n; std::reverse(keys.begin(), keys.end()); - constructed.push_back(MakeNodeRef<Key>(ctx, Fragment::MULTI, std::move(keys), *k)); + constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::MULTI, std::move(keys), *k)); break; } /** In the following wrappers, we only need to push SINGLE_BKV_EXPR rather @@ -1536,63 +1659,63 @@ inline NodeRef<Key> DecodeScript(I& in, I last, const Ctx& ctx) case DecodeContext::SWAP: { if (in >= last || in[0].first != OP_SWAP || constructed.empty()) return {}; ++in; - constructed.back() = MakeNodeRef<Key>(ctx, Fragment::WRAP_S, Vector(std::move(constructed.back()))); + constructed.back() = MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::WRAP_S, Vector(std::move(constructed.back()))); break; } case DecodeContext::ALT: { if (in >= last || in[0].first != OP_TOALTSTACK || constructed.empty()) return {}; ++in; - constructed.back() = MakeNodeRef<Key>(ctx, Fragment::WRAP_A, Vector(std::move(constructed.back()))); + constructed.back() = MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::WRAP_A, Vector(std::move(constructed.back()))); break; } case DecodeContext::CHECK: { if (constructed.empty()) return {}; - constructed.back() = MakeNodeRef<Key>(ctx, Fragment::WRAP_C, Vector(std::move(constructed.back()))); + constructed.back() = MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::WRAP_C, Vector(std::move(constructed.back()))); break; } case DecodeContext::DUP_IF: { if (constructed.empty()) return {}; - constructed.back() = MakeNodeRef<Key>(ctx, Fragment::WRAP_D, Vector(std::move(constructed.back()))); + constructed.back() = MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::WRAP_D, Vector(std::move(constructed.back()))); break; } case DecodeContext::VERIFY: { if (constructed.empty()) return {}; - constructed.back() = MakeNodeRef<Key>(ctx, Fragment::WRAP_V, Vector(std::move(constructed.back()))); + constructed.back() = MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::WRAP_V, Vector(std::move(constructed.back()))); break; } case DecodeContext::NON_ZERO: { if (constructed.empty()) return {}; - constructed.back() = MakeNodeRef<Key>(ctx, Fragment::WRAP_J, Vector(std::move(constructed.back()))); + constructed.back() = MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::WRAP_J, Vector(std::move(constructed.back()))); break; } case DecodeContext::ZERO_NOTEQUAL: { if (constructed.empty()) return {}; - constructed.back() = MakeNodeRef<Key>(ctx, Fragment::WRAP_N, Vector(std::move(constructed.back()))); + constructed.back() = MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::WRAP_N, Vector(std::move(constructed.back()))); break; } case DecodeContext::AND_V: { if (constructed.size() < 2) return {}; - BuildBack(ctx, Fragment::AND_V, constructed, /*reverse=*/true); + BuildBack(Fragment::AND_V, constructed, /*reverse=*/true); break; } case DecodeContext::AND_B: { if (constructed.size() < 2) return {}; - BuildBack(ctx, Fragment::AND_B, constructed, /*reverse=*/true); + BuildBack(Fragment::AND_B, constructed, /*reverse=*/true); break; } case DecodeContext::OR_B: { if (constructed.size() < 2) return {}; - BuildBack(ctx, Fragment::OR_B, constructed, /*reverse=*/true); + BuildBack(Fragment::OR_B, constructed, /*reverse=*/true); break; } case DecodeContext::OR_C: { if (constructed.size() < 2) return {}; - BuildBack(ctx, Fragment::OR_C, constructed, /*reverse=*/true); + BuildBack(Fragment::OR_C, constructed, /*reverse=*/true); break; } case DecodeContext::OR_D: { if (constructed.size() < 2) return {}; - BuildBack(ctx, Fragment::OR_D, constructed, /*reverse=*/true); + BuildBack(Fragment::OR_D, constructed, /*reverse=*/true); break; } case DecodeContext::ANDOR: { @@ -1602,7 +1725,7 @@ inline NodeRef<Key> DecodeScript(I& in, I last, const Ctx& ctx) NodeRef<Key> right = std::move(constructed.back()); constructed.pop_back(); NodeRef<Key> mid = std::move(constructed.back()); - constructed.back() = MakeNodeRef<Key>(ctx, Fragment::ANDOR, Vector(std::move(left), std::move(mid), std::move(right))); + constructed.back() = MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::ANDOR, Vector(std::move(left), std::move(mid), std::move(right))); break; } case DecodeContext::THRESH_W: { @@ -1626,7 +1749,7 @@ inline NodeRef<Key> DecodeScript(I& in, I last, const Ctx& ctx) constructed.pop_back(); subs.push_back(std::move(sub)); } - constructed.push_back(MakeNodeRef<Key>(ctx, Fragment::THRESH, std::move(subs), k)); + constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::THRESH, std::move(subs), k)); break; } case DecodeContext::ENDIF: { @@ -1676,7 +1799,7 @@ inline NodeRef<Key> DecodeScript(I& in, I last, const Ctx& ctx) if (in >= last) return {}; if (in[0].first == OP_IF) { ++in; - BuildBack(ctx, Fragment::OR_I, constructed, /*reverse=*/true); + BuildBack(Fragment::OR_I, constructed, /*reverse=*/true); } else if (in[0].first == OP_NOTIF) { ++in; to_parse.emplace_back(DecodeContext::ANDOR, -1, -1); @@ -1691,6 +1814,7 @@ inline NodeRef<Key> DecodeScript(I& in, I last, const Ctx& ctx) } if (constructed.size() != 1) return {}; const NodeRef<Key> tl_node = std::move(constructed.front()); + tl_node->DuplicateKeyCheck(ctx); // Note that due to how ComputeType works (only assign the type to the node if the // subs' types are valid) this would fail if any node of tree is badly typed. if (!tl_node->IsValidTopLevel()) return {}; @@ -1707,6 +1831,8 @@ inline NodeRef<typename Ctx::Key> FromString(const std::string& str, const Ctx& template<typename Ctx> inline NodeRef<typename Ctx::Key> FromScript(const CScript& script, const Ctx& ctx) { using namespace internal; + // A too large Script is necessarily invalid, don't bother parsing it. + if (script.size() > MAX_STANDARD_P2WSH_SCRIPT_SIZE) return {}; auto decomposed = DecomposeScript(script); if (!decomposed) return {}; auto it = decomposed->begin(); diff --git a/src/script/script.h b/src/script/script.h index 3b799ad637..1e5f694d52 100644 --- a/src/script/script.h +++ b/src/script/script.h @@ -588,7 +588,6 @@ CScript BuildScript(Ts&&... inputs) int cnt{0}; ([&ret, &cnt] (Ts&& input) { - cnt++; if constexpr (std::is_same_v<std::remove_cv_t<std::remove_reference_t<Ts>>, CScript>) { // If it is a CScript, extend ret with it. Move or copy the first element instead. if (cnt == 0) { @@ -600,6 +599,7 @@ CScript BuildScript(Ts&&... inputs) // Otherwise invoke CScript::operator<<. ret << input; } + cnt++; } (std::forward<Ts>(inputs)), ...); return ret; diff --git a/src/script/sigcache.cpp b/src/script/sigcache.cpp index 4e2acc463a..e507ec7528 100644 --- a/src/script/sigcache.cpp +++ b/src/script/sigcache.cpp @@ -14,6 +14,7 @@ #include <algorithm> #include <mutex> +#include <optional> #include <shared_mutex> #include <vector> @@ -75,7 +76,7 @@ public: std::unique_lock<std::shared_mutex> lock(cs_sigcache); setValid.insert(entry); } - uint32_t setup_bytes(size_t n) + std::optional<std::pair<uint32_t, size_t>> setup_bytes(size_t n) { return setValid.setup_bytes(n); } @@ -92,14 +93,15 @@ static CSignatureCache signatureCache; // To be called once in AppInitMain/BasicTestingSetup to initialize the // signatureCache. -void InitSignatureCache() +bool InitSignatureCache(size_t max_size_bytes) { - // nMaxCacheSize is unsigned. If -maxsigcachesize is set to zero, - // setup_bytes creates the minimum possible cache (2 elements). - size_t nMaxCacheSize = std::min(std::max((int64_t)0, gArgs.GetIntArg("-maxsigcachesize", DEFAULT_MAX_SIG_CACHE_SIZE) / 2), MAX_MAX_SIG_CACHE_SIZE) * ((size_t) 1 << 20); - size_t nElems = signatureCache.setup_bytes(nMaxCacheSize); - LogPrintf("Using %zu MiB out of %zu/2 requested for signature cache, able to store %zu elements\n", - (nElems*sizeof(uint256)) >>20, (nMaxCacheSize*2)>>20, nElems); + auto setup_results = signatureCache.setup_bytes(max_size_bytes); + if (!setup_results) return false; + + const auto [num_elems, approx_size_bytes] = *setup_results; + LogPrintf("Using %zu MiB out of %zu MiB requested for signature cache, able to store %zu elements\n", + approx_size_bytes >> 20, max_size_bytes >> 20, num_elems); + return true; } bool CachingTransactionSignatureChecker::VerifyECDSASignature(const std::vector<unsigned char>& vchSig, const CPubKey& pubkey, const uint256& sighash) const diff --git a/src/script/sigcache.h b/src/script/sigcache.h index 1e21d6f340..290955090d 100644 --- a/src/script/sigcache.h +++ b/src/script/sigcache.h @@ -10,14 +10,13 @@ #include <span.h> #include <util/hasher.h> +#include <optional> #include <vector> -// DoS prevention: limit cache size to 32MB (over 1000000 entries on 64-bit +// DoS prevention: limit cache size to 32MiB (over 1000000 entries on 64-bit // systems). Due to how we count cache size, actual memory usage is slightly -// more (~32.25 MB) -static const unsigned int DEFAULT_MAX_SIG_CACHE_SIZE = 32; -// Maximum sig cache size allowed -static const int64_t MAX_MAX_SIG_CACHE_SIZE = 16384; +// more (~32.25 MiB) +static constexpr size_t DEFAULT_MAX_SIG_CACHE_BYTES{32 << 20}; class CPubKey; @@ -33,6 +32,6 @@ public: bool VerifySchnorrSignature(Span<const unsigned char> sig, const XOnlyPubKey& pubkey, const uint256& sighash) const override; }; -void InitSignatureCache(); +[[nodiscard]] bool InitSignatureCache(size_t max_size_bytes); #endif // BITCOIN_SCRIPT_SIGCACHE_H diff --git a/src/script/sign.cpp b/src/script/sign.cpp index 2d569d674a..5da0d076d8 100644 --- a/src/script/sign.cpp +++ b/src/script/sign.cpp @@ -150,6 +150,7 @@ static bool CreateTaprootScriptSig(const BaseSignatureCreator& creator, Signatur auto it = sigdata.taproot_script_sigs.find(lookup_key); if (it != sigdata.taproot_script_sigs.end()) { sig_out = it->second; + return true; } if (creator.CreateSchnorrSig(provider, sig_out, pubkey, &leaf_hash, nullptr, sigversion)) { sigdata.taproot_script_sigs[lookup_key] = sig_out; @@ -164,11 +165,22 @@ static bool SignTaprootScript(const SigningProvider& provider, const BaseSignatu if (leaf_version != TAPROOT_LEAF_TAPSCRIPT) return false; SigVersion sigversion = SigVersion::TAPSCRIPT; - uint256 leaf_hash = (CHashWriter(HASHER_TAPLEAF) << uint8_t(leaf_version) << script).GetSHA256(); + uint256 leaf_hash = (HashWriter{HASHER_TAPLEAF} << uint8_t(leaf_version) << script).GetSHA256(); // <xonly pubkey> OP_CHECKSIG if (script.size() == 34 && script[33] == OP_CHECKSIG && script[0] == 0x20) { XOnlyPubKey pubkey{Span{script}.subspan(1, 32)}; + + KeyOriginInfo info; + if (provider.GetKeyOriginByXOnly(pubkey, info)) { + auto it = sigdata.taproot_misc_pubkeys.find(pubkey); + if (it == sigdata.taproot_misc_pubkeys.end()) { + sigdata.taproot_misc_pubkeys.emplace(pubkey, std::make_pair(std::set<uint256>({leaf_hash}), info)); + } else { + it->second.first.insert(leaf_hash); + } + } + std::vector<unsigned char> sig; if (CreateTaprootScriptSig(creator, sigdata, provider, sig, pubkey, leaf_hash, sigversion)) { result = Vector(std::move(sig)); @@ -205,17 +217,34 @@ static bool SignTaprootScript(const SigningProvider& provider, const BaseSignatu static bool SignTaproot(const SigningProvider& provider, const BaseSignatureCreator& creator, const WitnessV1Taproot& output, SignatureData& sigdata, std::vector<valtype>& result) { TaprootSpendData spenddata; + TaprootBuilder builder; // Gather information about this output. if (provider.GetTaprootSpendData(output, spenddata)) { sigdata.tr_spenddata.Merge(spenddata); } + if (provider.GetTaprootBuilder(output, builder)) { + sigdata.tr_builder = builder; + } // Try key path spending. { + KeyOriginInfo info; + if (provider.GetKeyOriginByXOnly(sigdata.tr_spenddata.internal_key, info)) { + auto it = sigdata.taproot_misc_pubkeys.find(sigdata.tr_spenddata.internal_key); + if (it == sigdata.taproot_misc_pubkeys.end()) { + sigdata.taproot_misc_pubkeys.emplace(sigdata.tr_spenddata.internal_key, std::make_pair(std::set<uint256>(), info)); + } + } + std::vector<unsigned char> sig; if (sigdata.taproot_key_path_sig.size() == 0) { - if (creator.CreateSchnorrSig(provider, sig, spenddata.internal_key, nullptr, &spenddata.merkle_root, SigVersion::TAPROOT)) { + if (creator.CreateSchnorrSig(provider, sig, sigdata.tr_spenddata.internal_key, nullptr, &sigdata.tr_spenddata.merkle_root, SigVersion::TAPROOT)) { + sigdata.taproot_key_path_sig = sig; + } + } + if (sigdata.taproot_key_path_sig.size() == 0) { + if (creator.CreateSchnorrSig(provider, sig, output, nullptr, nullptr, SigVersion::TAPROOT)) { sigdata.taproot_key_path_sig = sig; } } @@ -567,8 +596,11 @@ public: bool CheckECDSASignature(const std::vector<unsigned char>& scriptSig, const std::vector<unsigned char>& vchPubKey, const CScript& scriptCode, SigVersion sigversion) const override { return true; } bool CheckSchnorrSignature(Span<const unsigned char> sig, Span<const unsigned char> pubkey, SigVersion sigversion, ScriptExecutionData& execdata, ScriptError* serror) const override { return true; } }; -const DummySignatureChecker DUMMY_CHECKER; +} +const BaseSignatureChecker& DUMMY_CHECKER = DummySignatureChecker(); + +namespace { class DummySignatureCreator final : public BaseSignatureCreator { private: char m_r_len = 32; @@ -603,25 +635,6 @@ public: const BaseSignatureCreator& DUMMY_SIGNATURE_CREATOR = DummySignatureCreator(32, 32); const BaseSignatureCreator& DUMMY_MAXIMUM_SIGNATURE_CREATOR = DummySignatureCreator(33, 32); -bool IsSolvable(const SigningProvider& provider, const CScript& script) -{ - // This check is to make sure that the script we created can actually be solved for and signed by us - // if we were to have the private keys. This is just to make sure that the script is valid and that, - // if found in a transaction, we would still accept and relay that transaction. In particular, - // it will reject witness outputs that require signing with an uncompressed public key. - SignatureData sigs; - // Make sure that STANDARD_SCRIPT_VERIFY_FLAGS includes SCRIPT_VERIFY_WITNESS_PUBKEYTYPE, the most - // important property this function is designed to test for. - static_assert(STANDARD_SCRIPT_VERIFY_FLAGS & SCRIPT_VERIFY_WITNESS_PUBKEYTYPE, "IsSolvable requires standard script flags to include WITNESS_PUBKEYTYPE"); - if (ProduceSignature(provider, DUMMY_SIGNATURE_CREATOR, script, sigs)) { - // VerifyScript check is just defensive, and should never fail. - bool verified = VerifyScript(sigs.scriptSig, script, &sigs.scriptWitness, STANDARD_SCRIPT_VERIFY_FLAGS, DUMMY_CHECKER); - assert(verified); - return true; - } - return false; -} - bool IsSegWitOutput(const SigningProvider& provider, const CScript& script) { int version; diff --git a/src/script/sign.h b/src/script/sign.h index 71203d08ec..813dfe04e3 100644 --- a/src/script/sign.h +++ b/src/script/sign.h @@ -52,6 +52,8 @@ public: bool CreateSchnorrSig(const SigningProvider& provider, std::vector<unsigned char>& sig, const XOnlyPubKey& pubkey, const uint256* leaf_hash, const uint256* merkle_root, SigVersion sigversion) const override; }; +/** A signature checker that accepts all signatures */ +extern const BaseSignatureChecker& DUMMY_CHECKER; /** A signature creator that just produces 71-byte empty signatures. */ extern const BaseSignatureCreator& DUMMY_SIGNATURE_CREATOR; /** A signature creator that just produces 72-byte empty signatures. */ @@ -70,10 +72,12 @@ struct SignatureData { CScript witness_script; ///< The witnessScript (if any) for the input. witnessScripts are used in P2WSH outputs. CScriptWitness scriptWitness; ///< The scriptWitness of an input. Contains complete signatures or the traditional partial signatures format. scriptWitness is part of a transaction input per BIP 144. TaprootSpendData tr_spenddata; ///< Taproot spending data. + std::optional<TaprootBuilder> tr_builder; ///< Taproot tree used to build tr_spenddata. std::map<CKeyID, SigPair> signatures; ///< BIP 174 style partial signatures for the input. May contain all signatures necessary for producing a final scriptSig or scriptWitness. std::map<CKeyID, std::pair<CPubKey, KeyOriginInfo>> misc_pubkeys; std::vector<unsigned char> taproot_key_path_sig; /// Schnorr signature for key path spending std::map<std::pair<XOnlyPubKey, uint256>, std::vector<unsigned char>> taproot_script_sigs; ///< (Partial) schnorr signatures, indexed by XOnlyPubKey and leaf_hash. + std::map<XOnlyPubKey, std::pair<std::set<uint256>, KeyOriginInfo>> taproot_misc_pubkeys; ///< Miscellaneous Taproot pubkeys involved in this input along with their leaf script hashes and key origin data. Also includes the Taproot internal key (may have no leaf script hashes). std::vector<CKeyID> missing_pubkeys; ///< KeyIDs of pubkeys which could not be found std::vector<CKeyID> missing_sigs; ///< KeyIDs of pubkeys for signatures which could not be found uint160 missing_redeem_script; ///< ScriptID of the missing redeemScript (if any) @@ -95,12 +99,6 @@ bool SignSignature(const SigningProvider &provider, const CTransaction& txFrom, SignatureData DataFromTransaction(const CMutableTransaction& tx, unsigned int nIn, const CTxOut& txout); void UpdateInput(CTxIn& input, const SignatureData& data); -/* Check whether we know how to sign for an output like this, assuming we - * have all private keys. While this function does not need private keys, the passed - * provider is used to look up public keys and redeemscripts by hash. - * Solvability is unrelated to whether we consider this output to be ours. */ -bool IsSolvable(const SigningProvider& provider, const CScript& script); - /** Check whether a scriptPubKey is known to be segwit. */ bool IsSegWitOutput(const SigningProvider& provider, const CScript& script); diff --git a/src/script/signingprovider.cpp b/src/script/signingprovider.cpp index 552934e0eb..a82a8b252a 100644 --- a/src/script/signingprovider.cpp +++ b/src/script/signingprovider.cpp @@ -48,6 +48,10 @@ bool HidingSigningProvider::GetTaprootSpendData(const XOnlyPubKey& output_key, T { return m_provider->GetTaprootSpendData(output_key, spenddata); } +bool HidingSigningProvider::GetTaprootBuilder(const XOnlyPubKey& output_key, TaprootBuilder& builder) const +{ + return m_provider->GetTaprootBuilder(output_key, builder); +} bool FlatSigningProvider::GetCScript(const CScriptID& scriptid, CScript& script) const { return LookupHelper(scripts, scriptid, script); } bool FlatSigningProvider::GetPubKey(const CKeyID& keyid, CPubKey& pubkey) const { return LookupHelper(pubkeys, keyid, pubkey); } @@ -61,25 +65,26 @@ bool FlatSigningProvider::GetKeyOrigin(const CKeyID& keyid, KeyOriginInfo& info) bool FlatSigningProvider::GetKey(const CKeyID& keyid, CKey& key) const { return LookupHelper(keys, keyid, key); } bool FlatSigningProvider::GetTaprootSpendData(const XOnlyPubKey& output_key, TaprootSpendData& spenddata) const { - return LookupHelper(tr_spenddata, output_key, spenddata); + TaprootBuilder builder; + if (LookupHelper(tr_trees, output_key, builder)) { + spenddata = builder.GetSpendData(); + return true; + } + return false; +} +bool FlatSigningProvider::GetTaprootBuilder(const XOnlyPubKey& output_key, TaprootBuilder& builder) const +{ + return LookupHelper(tr_trees, output_key, builder); } -FlatSigningProvider Merge(const FlatSigningProvider& a, const FlatSigningProvider& b) +FlatSigningProvider& FlatSigningProvider::Merge(FlatSigningProvider&& b) { - FlatSigningProvider ret; - ret.scripts = a.scripts; - ret.scripts.insert(b.scripts.begin(), b.scripts.end()); - ret.pubkeys = a.pubkeys; - ret.pubkeys.insert(b.pubkeys.begin(), b.pubkeys.end()); - ret.keys = a.keys; - ret.keys.insert(b.keys.begin(), b.keys.end()); - ret.origins = a.origins; - ret.origins.insert(b.origins.begin(), b.origins.end()); - ret.tr_spenddata = a.tr_spenddata; - for (const auto& [output_key, spenddata] : b.tr_spenddata) { - ret.tr_spenddata[output_key].Merge(spenddata); - } - return ret; + scripts.merge(b.scripts); + pubkeys.merge(b.pubkeys); + keys.merge(b.keys); + origins.merge(b.origins); + tr_trees.merge(b.tr_trees); + return *this; } void FillableSigningProvider::ImplicitlyLearnRelatedKeyScripts(const CPubKey& pubkey) diff --git a/src/script/signingprovider.h b/src/script/signingprovider.h index f1bded1a8c..2d4234ea0b 100644 --- a/src/script/signingprovider.h +++ b/src/script/signingprovider.h @@ -6,6 +6,7 @@ #ifndef BITCOIN_SCRIPT_SIGNINGPROVIDER_H #define BITCOIN_SCRIPT_SIGNINGPROVIDER_H +#include <attributes.h> #include <key.h> #include <pubkey.h> #include <script/keyorigin.h> @@ -25,6 +26,7 @@ public: virtual bool HaveKey(const CKeyID &address) const { return false; } virtual bool GetKeyOrigin(const CKeyID& keyid, KeyOriginInfo& info) const { return false; } virtual bool GetTaprootSpendData(const XOnlyPubKey& output_key, TaprootSpendData& spenddata) const { return false; } + virtual bool GetTaprootBuilder(const XOnlyPubKey& output_key, TaprootBuilder& builder) const { return false; } bool GetKeyByXOnly(const XOnlyPubKey& pubkey, CKey& key) const { @@ -67,6 +69,7 @@ public: bool GetKey(const CKeyID& keyid, CKey& key) const override; bool GetKeyOrigin(const CKeyID& keyid, KeyOriginInfo& info) const override; bool GetTaprootSpendData(const XOnlyPubKey& output_key, TaprootSpendData& spenddata) const override; + bool GetTaprootBuilder(const XOnlyPubKey& output_key, TaprootBuilder& builder) const override; }; struct FlatSigningProvider final : public SigningProvider @@ -75,16 +78,17 @@ struct FlatSigningProvider final : public SigningProvider std::map<CKeyID, CPubKey> pubkeys; std::map<CKeyID, std::pair<CPubKey, KeyOriginInfo>> origins; std::map<CKeyID, CKey> keys; - std::map<XOnlyPubKey, TaprootSpendData> tr_spenddata; /** Map from output key to spend data. */ + std::map<XOnlyPubKey, TaprootBuilder> tr_trees; /** Map from output key to Taproot tree (which can then make the TaprootSpendData */ bool GetCScript(const CScriptID& scriptid, CScript& script) const override; bool GetPubKey(const CKeyID& keyid, CPubKey& pubkey) const override; bool GetKeyOrigin(const CKeyID& keyid, KeyOriginInfo& info) const override; bool GetKey(const CKeyID& keyid, CKey& key) const override; bool GetTaprootSpendData(const XOnlyPubKey& output_key, TaprootSpendData& spenddata) const override; -}; + bool GetTaprootBuilder(const XOnlyPubKey& output_key, TaprootBuilder& builder) const override; -FlatSigningProvider Merge(const FlatSigningProvider& a, const FlatSigningProvider& b); + FlatSigningProvider& Merge(FlatSigningProvider&& b) LIFETIMEBOUND; +}; /** Fillable signing provider that keeps keys in an address->secret map */ class FillableSigningProvider : public SigningProvider diff --git a/src/script/standard.cpp b/src/script/standard.cpp index e25155d3dd..6101738061 100644 --- a/src/script/standard.cpp +++ b/src/script/standard.cpp @@ -16,9 +16,6 @@ typedef std::vector<unsigned char> valtype; -bool fAcceptDatacarrier = DEFAULT_ACCEPT_DATACARRIER; -unsigned nMaxDatacarrierBytes = MAX_OP_RETURN_RELAY; - CScriptID::CScriptID(const CScript& in) : BaseHash(Hash160(in)) {} CScriptID::CScriptID(const ScriptHash& in) : BaseHash(static_cast<uint160>(in)) {} @@ -375,9 +372,9 @@ bool IsValidDestination(const CTxDestination& dest) { } /* Lexicographically sort a and b's hash, and compute parent hash. */ if (a.hash < b.hash) { - ret.hash = (CHashWriter(HASHER_TAPBRANCH) << a.hash << b.hash).GetSHA256(); + ret.hash = (HashWriter{HASHER_TAPBRANCH} << a.hash << b.hash).GetSHA256(); } else { - ret.hash = (CHashWriter(HASHER_TAPBRANCH) << b.hash << a.hash).GetSHA256(); + ret.hash = (HashWriter{HASHER_TAPBRANCH} << b.hash << a.hash).GetSHA256(); } return ret; } @@ -452,7 +449,7 @@ TaprootBuilder& TaprootBuilder::Add(int depth, const CScript& script, int leaf_v if (!IsValid()) return *this; /* Construct NodeInfo object with leaf hash and (if track is true) also leaf information. */ NodeInfo node; - node.hash = (CHashWriter{HASHER_TAPLEAF} << uint8_t(leaf_version) << script).GetSHA256(); + node.hash = (HashWriter{HASHER_TAPLEAF} << uint8_t(leaf_version) << script).GetSHA256(); if (track) node.leaves.emplace_back(LeafInfo{script, leaf_version, {}}); /* Insert into the branch. */ Insert(std::move(node), depth); @@ -485,6 +482,7 @@ WitnessV1Taproot TaprootBuilder::GetOutput() { return WitnessV1Taproot{m_output_ TaprootSpendData TaprootBuilder::GetSpendData() const { assert(IsComplete()); + assert(m_output_key.IsFullyValid()); TaprootSpendData spd; spd.merkle_root = m_branch.size() == 0 ? uint256() : m_branch[0]->hash; spd.internal_key = m_internal_key; @@ -609,7 +607,7 @@ std::optional<std::vector<std::tuple<int, CScript, int>>> InferTaprootTree(const node.done = true; stack.pop_back(); } else if (node.sub[0]->done && !node.sub[1]->done && !node.sub[1]->explored && !node.sub[1]->hash.IsNull() && - (CHashWriter{HASHER_TAPBRANCH} << node.sub[1]->hash << node.sub[1]->hash).GetSHA256() == node.hash) { + (HashWriter{HASHER_TAPBRANCH} << node.sub[1]->hash << node.sub[1]->hash).GetSHA256() == node.hash) { // Whenever there are nodes with two identical subtrees under it, we run into a problem: // the control blocks for the leaves underneath those will be identical as well, and thus // they will all be matched to the same path in the tree. The result is that at the location @@ -642,3 +640,19 @@ std::optional<std::vector<std::tuple<int, CScript, int>>> InferTaprootTree(const return ret; } + +std::vector<std::tuple<uint8_t, uint8_t, CScript>> TaprootBuilder::GetTreeTuples() const +{ + assert(IsComplete()); + std::vector<std::tuple<uint8_t, uint8_t, CScript>> tuples; + if (m_branch.size()) { + const auto& leaves = m_branch[0]->leaves; + for (const auto& leaf : leaves) { + assert(leaf.merkle_branch.size() <= TAPROOT_CONTROL_MAX_NODE_COUNT); + uint8_t depth = (uint8_t)leaf.merkle_branch.size(); + uint8_t leaf_ver = (uint8_t)leaf.leaf_version; + tuples.push_back(std::make_tuple(depth, leaf_ver, leaf.script)); + } + } + return tuples; +} diff --git a/src/script/standard.h b/src/script/standard.h index 6a15ba4e3d..1e6769782a 100644 --- a/src/script/standard.h +++ b/src/script/standard.h @@ -33,21 +33,12 @@ public: }; /** - * Default setting for nMaxDatacarrierBytes. 80 bytes of data, +1 for OP_RETURN, + * Default setting for -datacarriersize. 80 bytes of data, +1 for OP_RETURN, * +2 for the pushdata opcodes. */ static const unsigned int MAX_OP_RETURN_RELAY = 83; /** - * A data carrying output is an unspendable output containing data. The script - * type is designated as TxoutType::NULL_DATA. - */ -extern bool fAcceptDatacarrier; - -/** Maximum size of TxoutType::NULL_DATA scripts that this node considers standard. */ -extern unsigned nMaxDatacarrierBytes; - -/** * Mandatory script verification flags that all new blocks must comply with for * them to be valid. (but old blocks may not comply with) Currently just P2SH, * but in the future other flags may be added. @@ -322,6 +313,8 @@ public: static bool ValidDepths(const std::vector<int>& depths); /** Compute spending data (after Finalize()). */ TaprootSpendData GetSpendData() const; + /** Returns a vector of tuples representing the depth, leaf version, and script */ + std::vector<std::tuple<uint8_t, uint8_t, CScript>> GetTreeTuples() const; }; /** Given a TaprootSpendData and the output key, reconstruct its script tree. diff --git a/src/secp256k1/build-aux/m4/bitcoin_secp.m4 b/src/secp256k1/build-aux/m4/bitcoin_secp.m4 index dda770e001..9cb54de098 100644 --- a/src/secp256k1/build-aux/m4/bitcoin_secp.m4 +++ b/src/secp256k1/build-aux/m4/bitcoin_secp.m4 @@ -1,7 +1,7 @@ dnl escape "$0x" below using the m4 quadrigaph @S|@, and escape it again with a \ for the shell. AC_DEFUN([SECP_64BIT_ASM_CHECK],[ AC_MSG_CHECKING(for x86_64 assembly availability) -AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ +AC_LINK_IFELSE([AC_LANG_PROGRAM([[ #include <stdint.h>]],[[ uint64_t a = 11, tmp; __asm__ __volatile__("movq \@S|@0x100000000,%1; mulq %%rsi" : "+a"(a) : "S"(tmp) : "cc", "%rdx"); diff --git a/src/secp256k1/include/secp256k1.h b/src/secp256k1/include/secp256k1.h index 86ab7e556d..dddab346ae 100644 --- a/src/secp256k1/include/secp256k1.h +++ b/src/secp256k1/include/secp256k1.h @@ -141,9 +141,13 @@ typedef int (*secp256k1_nonce_function)( # define SECP256K1_NO_BUILD #endif +/** At secp256k1 build-time DLL_EXPORT is defined when building objects destined + * for a shared library, but not for those intended for static libraries. + */ + #ifndef SECP256K1_API # if defined(_WIN32) -# ifdef SECP256K1_BUILD +# if defined(SECP256K1_BUILD) && defined(DLL_EXPORT) # define SECP256K1_API __declspec(dllexport) # else # define SECP256K1_API diff --git a/src/secp256k1/sage/prove_group_implementations.sage b/src/secp256k1/sage/prove_group_implementations.sage index 96ce33506a..652bd87f11 100644 --- a/src/secp256k1/sage/prove_group_implementations.sage +++ b/src/secp256k1/sage/prove_group_implementations.sage @@ -40,29 +40,26 @@ def formula_secp256k1_gej_add_var(branch, a, b): s2 = s2 * a.Z h = -u1 h = h + u2 - i = -s1 - i = i + s2 + i = -s2 + i = i + s1 if branch == 2: r = formula_secp256k1_gej_double_var(a) return (constraints(), constraints(zero={h : 'h=0', i : 'i=0', a.Infinity : 'a_finite', b.Infinity : 'b_finite'}), r) if branch == 3: return (constraints(), constraints(zero={h : 'h=0', a.Infinity : 'a_finite', b.Infinity : 'b_finite'}, nonzero={i : 'i!=0'}), point_at_infinity()) - i2 = i^2 + t = h * b.Z + rz = a.Z * t h2 = h^2 + h2 = -h2 h3 = h2 * h - h = h * b.Z - rz = a.Z * h t = u1 * h2 - rx = t - rx = rx * 2 + rx = i^2 rx = rx + h3 - rx = -rx - rx = rx + i2 - ry = -rx - ry = ry + t - ry = ry * i + rx = rx + t + rx = rx + t + t = t + rx + ry = t * i h3 = h3 * s1 - h3 = -h3 ry = ry + h3 return (constraints(), constraints(zero={a.Infinity : 'a_finite', b.Infinity : 'b_finite'}, nonzero={h : 'h!=0'}), jacobianpoint(rx, ry, rz)) @@ -80,28 +77,25 @@ def formula_secp256k1_gej_add_ge_var(branch, a, b): s2 = s2 * a.Z h = -u1 h = h + u2 - i = -s1 - i = i + s2 + i = -s2 + i = i + s1 if (branch == 2): r = formula_secp256k1_gej_double_var(a) return (constraints(zero={b.Z - 1 : 'b.z=1'}), constraints(zero={a.Infinity : 'a_finite', b.Infinity : 'b_finite', h : 'h=0', i : 'i=0'}), r) if (branch == 3): return (constraints(zero={b.Z - 1 : 'b.z=1'}), constraints(zero={a.Infinity : 'a_finite', b.Infinity : 'b_finite', h : 'h=0'}, nonzero={i : 'i!=0'}), point_at_infinity()) - i2 = i^2 - h2 = h^2 - h3 = h * h2 rz = a.Z * h + h2 = h^2 + h2 = -h2 + h3 = h2 * h t = u1 * h2 - rx = t - rx = rx * 2 + rx = i^2 rx = rx + h3 - rx = -rx - rx = rx + i2 - ry = -rx - ry = ry + t - ry = ry * i + rx = rx + t + rx = rx + t + t = t + rx + ry = t * i h3 = h3 * s1 - h3 = -h3 ry = ry + h3 return (constraints(zero={b.Z - 1 : 'b.z=1'}), constraints(zero={a.Infinity : 'a_finite', b.Infinity : 'b_finite'}, nonzero={h : 'h!=0'}), jacobianpoint(rx, ry, rz)) @@ -109,14 +103,15 @@ def formula_secp256k1_gej_add_zinv_var(branch, a, b): """libsecp256k1's secp256k1_gej_add_zinv_var""" bzinv = b.Z^(-1) if branch == 0: - return (constraints(), constraints(nonzero={b.Infinity : 'b_infinite'}), a) - if branch == 1: + rinf = b.Infinity bzinv2 = bzinv^2 bzinv3 = bzinv2 * bzinv rx = b.X * bzinv2 ry = b.Y * bzinv3 rz = 1 - return (constraints(), constraints(zero={b.Infinity : 'b_finite'}, nonzero={a.Infinity : 'a_infinite'}), jacobianpoint(rx, ry, rz)) + return (constraints(), constraints(nonzero={a.Infinity : 'a_infinite'}), jacobianpoint(rx, ry, rz, rinf)) + if branch == 1: + return (constraints(), constraints(zero={a.Infinity : 'a_finite'}, nonzero={b.Infinity : 'b_infinite'}), a) azz = a.Z * bzinv z12 = azz^2 u1 = a.X @@ -126,29 +121,25 @@ def formula_secp256k1_gej_add_zinv_var(branch, a, b): s2 = s2 * azz h = -u1 h = h + u2 - i = -s1 - i = i + s2 + i = -s2 + i = i + s1 if branch == 2: r = formula_secp256k1_gej_double_var(a) return (constraints(), constraints(zero={a.Infinity : 'a_finite', b.Infinity : 'b_finite', h : 'h=0', i : 'i=0'}), r) if branch == 3: return (constraints(), constraints(zero={a.Infinity : 'a_finite', b.Infinity : 'b_finite', h : 'h=0'}, nonzero={i : 'i!=0'}), point_at_infinity()) - i2 = i^2 + rz = a.Z * h h2 = h^2 - h3 = h * h2 - rz = a.Z - rz = rz * h + h2 = -h2 + h3 = h2 * h t = u1 * h2 - rx = t - rx = rx * 2 + rx = i^2 rx = rx + h3 - rx = -rx - rx = rx + i2 - ry = -rx - ry = ry + t - ry = ry * i + rx = rx + t + rx = rx + t + t = t + rx + ry = t * i h3 = h3 * s1 - h3 = -h3 ry = ry + h3 return (constraints(), constraints(zero={a.Infinity : 'a_finite', b.Infinity : 'b_finite'}, nonzero={h : 'h!=0'}), jacobianpoint(rx, ry, rz)) diff --git a/src/secp256k1/src/bench_internal.c b/src/secp256k1/src/bench_internal.c index 3c145f306c..7eb3af28d7 100644 --- a/src/secp256k1/src/bench_internal.c +++ b/src/secp256k1/src/bench_internal.c @@ -254,6 +254,15 @@ void bench_group_add_affine_var(void* arg, int iters) { } } +void bench_group_add_zinv_var(void* arg, int iters) { + int i; + bench_inv *data = (bench_inv*)arg; + + for (i = 0; i < iters; i++) { + secp256k1_gej_add_zinv_var(&data->gej[0], &data->gej[0], &data->ge[1], &data->gej[0].y); + } +} + void bench_group_to_affine_var(void* arg, int iters) { int i; bench_inv *data = (bench_inv*)arg; @@ -376,6 +385,7 @@ int main(int argc, char **argv) { if (d || have_flag(argc, argv, "group") || have_flag(argc, argv, "add")) run_benchmark("group_add_var", bench_group_add_var, bench_setup, NULL, &data, 10, iters*10); if (d || have_flag(argc, argv, "group") || have_flag(argc, argv, "add")) run_benchmark("group_add_affine", bench_group_add_affine, bench_setup, NULL, &data, 10, iters*10); if (d || have_flag(argc, argv, "group") || have_flag(argc, argv, "add")) run_benchmark("group_add_affine_var", bench_group_add_affine_var, bench_setup, NULL, &data, 10, iters*10); + if (d || have_flag(argc, argv, "group") || have_flag(argc, argv, "add")) run_benchmark("group_add_zinv_var", bench_group_add_zinv_var, bench_setup, NULL, &data, 10, iters*10); if (d || have_flag(argc, argv, "group") || have_flag(argc, argv, "to_affine")) run_benchmark("group_to_affine_var", bench_group_to_affine_var, bench_setup, NULL, &data, 10, iters); if (d || have_flag(argc, argv, "ecmult") || have_flag(argc, argv, "wnaf")) run_benchmark("wnaf_const", bench_wnaf_const, bench_setup, NULL, &data, 10, iters); diff --git a/src/secp256k1/src/group_impl.h b/src/secp256k1/src/group_impl.h index b19b02a01f..63735ab682 100644 --- a/src/secp256k1/src/group_impl.h +++ b/src/secp256k1/src/group_impl.h @@ -330,15 +330,14 @@ static void secp256k1_gej_double_var(secp256k1_gej *r, const secp256k1_gej *a, s } static void secp256k1_gej_add_var(secp256k1_gej *r, const secp256k1_gej *a, const secp256k1_gej *b, secp256k1_fe *rzr) { - /* Operations: 12 mul, 4 sqr, 2 normalize, 12 mul_int/add/negate */ - secp256k1_fe z22, z12, u1, u2, s1, s2, h, i, i2, h2, h3, t; + /* 12 mul, 4 sqr, 11 add/negate/normalizes_to_zero (ignoring special cases) */ + secp256k1_fe z22, z12, u1, u2, s1, s2, h, i, h2, h3, t; if (a->infinity) { VERIFY_CHECK(rzr == NULL); *r = *b; return; } - if (b->infinity) { if (rzr != NULL) { secp256k1_fe_set_int(rzr, 1); @@ -347,7 +346,6 @@ static void secp256k1_gej_add_var(secp256k1_gej *r, const secp256k1_gej *a, cons return; } - r->infinity = 0; secp256k1_fe_sqr(&z22, &b->z); secp256k1_fe_sqr(&z12, &a->z); secp256k1_fe_mul(&u1, &a->x, &z22); @@ -355,7 +353,7 @@ static void secp256k1_gej_add_var(secp256k1_gej *r, const secp256k1_gej *a, cons secp256k1_fe_mul(&s1, &a->y, &z22); secp256k1_fe_mul(&s1, &s1, &b->z); secp256k1_fe_mul(&s2, &b->y, &z12); secp256k1_fe_mul(&s2, &s2, &a->z); secp256k1_fe_negate(&h, &u1, 1); secp256k1_fe_add(&h, &u2); - secp256k1_fe_negate(&i, &s1, 1); secp256k1_fe_add(&i, &s2); + secp256k1_fe_negate(&i, &s2, 1); secp256k1_fe_add(&i, &s1); if (secp256k1_fe_normalizes_to_zero_var(&h)) { if (secp256k1_fe_normalizes_to_zero_var(&i)) { secp256k1_gej_double_var(r, a, rzr); @@ -367,24 +365,33 @@ static void secp256k1_gej_add_var(secp256k1_gej *r, const secp256k1_gej *a, cons } return; } - secp256k1_fe_sqr(&i2, &i); - secp256k1_fe_sqr(&h2, &h); - secp256k1_fe_mul(&h3, &h, &h2); - secp256k1_fe_mul(&h, &h, &b->z); + + r->infinity = 0; + secp256k1_fe_mul(&t, &h, &b->z); if (rzr != NULL) { - *rzr = h; + *rzr = t; } - secp256k1_fe_mul(&r->z, &a->z, &h); + secp256k1_fe_mul(&r->z, &a->z, &t); + + secp256k1_fe_sqr(&h2, &h); + secp256k1_fe_negate(&h2, &h2, 1); + secp256k1_fe_mul(&h3, &h2, &h); secp256k1_fe_mul(&t, &u1, &h2); - r->x = t; secp256k1_fe_mul_int(&r->x, 2); secp256k1_fe_add(&r->x, &h3); secp256k1_fe_negate(&r->x, &r->x, 3); secp256k1_fe_add(&r->x, &i2); - secp256k1_fe_negate(&r->y, &r->x, 5); secp256k1_fe_add(&r->y, &t); secp256k1_fe_mul(&r->y, &r->y, &i); - secp256k1_fe_mul(&h3, &h3, &s1); secp256k1_fe_negate(&h3, &h3, 1); + + secp256k1_fe_sqr(&r->x, &i); + secp256k1_fe_add(&r->x, &h3); + secp256k1_fe_add(&r->x, &t); + secp256k1_fe_add(&r->x, &t); + + secp256k1_fe_add(&t, &r->x); + secp256k1_fe_mul(&r->y, &t, &i); + secp256k1_fe_mul(&h3, &h3, &s1); secp256k1_fe_add(&r->y, &h3); } static void secp256k1_gej_add_ge_var(secp256k1_gej *r, const secp256k1_gej *a, const secp256k1_ge *b, secp256k1_fe *rzr) { - /* 8 mul, 3 sqr, 4 normalize, 12 mul_int/add/negate */ - secp256k1_fe z12, u1, u2, s1, s2, h, i, i2, h2, h3, t; + /* 8 mul, 3 sqr, 13 add/negate/normalize_weak/normalizes_to_zero (ignoring special cases) */ + secp256k1_fe z12, u1, u2, s1, s2, h, i, h2, h3, t; if (a->infinity) { VERIFY_CHECK(rzr == NULL); secp256k1_gej_set_ge(r, b); @@ -397,7 +404,6 @@ static void secp256k1_gej_add_ge_var(secp256k1_gej *r, const secp256k1_gej *a, c *r = *a; return; } - r->infinity = 0; secp256k1_fe_sqr(&z12, &a->z); u1 = a->x; secp256k1_fe_normalize_weak(&u1); @@ -405,7 +411,7 @@ static void secp256k1_gej_add_ge_var(secp256k1_gej *r, const secp256k1_gej *a, c s1 = a->y; secp256k1_fe_normalize_weak(&s1); secp256k1_fe_mul(&s2, &b->y, &z12); secp256k1_fe_mul(&s2, &s2, &a->z); secp256k1_fe_negate(&h, &u1, 1); secp256k1_fe_add(&h, &u2); - secp256k1_fe_negate(&i, &s1, 1); secp256k1_fe_add(&i, &s2); + secp256k1_fe_negate(&i, &s2, 1); secp256k1_fe_add(&i, &s1); if (secp256k1_fe_normalizes_to_zero_var(&h)) { if (secp256k1_fe_normalizes_to_zero_var(&i)) { secp256k1_gej_double_var(r, a, rzr); @@ -417,28 +423,33 @@ static void secp256k1_gej_add_ge_var(secp256k1_gej *r, const secp256k1_gej *a, c } return; } - secp256k1_fe_sqr(&i2, &i); - secp256k1_fe_sqr(&h2, &h); - secp256k1_fe_mul(&h3, &h, &h2); + + r->infinity = 0; if (rzr != NULL) { *rzr = h; } secp256k1_fe_mul(&r->z, &a->z, &h); + + secp256k1_fe_sqr(&h2, &h); + secp256k1_fe_negate(&h2, &h2, 1); + secp256k1_fe_mul(&h3, &h2, &h); secp256k1_fe_mul(&t, &u1, &h2); - r->x = t; secp256k1_fe_mul_int(&r->x, 2); secp256k1_fe_add(&r->x, &h3); secp256k1_fe_negate(&r->x, &r->x, 3); secp256k1_fe_add(&r->x, &i2); - secp256k1_fe_negate(&r->y, &r->x, 5); secp256k1_fe_add(&r->y, &t); secp256k1_fe_mul(&r->y, &r->y, &i); - secp256k1_fe_mul(&h3, &h3, &s1); secp256k1_fe_negate(&h3, &h3, 1); + + secp256k1_fe_sqr(&r->x, &i); + secp256k1_fe_add(&r->x, &h3); + secp256k1_fe_add(&r->x, &t); + secp256k1_fe_add(&r->x, &t); + + secp256k1_fe_add(&t, &r->x); + secp256k1_fe_mul(&r->y, &t, &i); + secp256k1_fe_mul(&h3, &h3, &s1); secp256k1_fe_add(&r->y, &h3); } static void secp256k1_gej_add_zinv_var(secp256k1_gej *r, const secp256k1_gej *a, const secp256k1_ge *b, const secp256k1_fe *bzinv) { - /* 9 mul, 3 sqr, 4 normalize, 12 mul_int/add/negate */ - secp256k1_fe az, z12, u1, u2, s1, s2, h, i, i2, h2, h3, t; + /* 9 mul, 3 sqr, 13 add/negate/normalize_weak/normalizes_to_zero (ignoring special cases) */ + secp256k1_fe az, z12, u1, u2, s1, s2, h, i, h2, h3, t; - if (b->infinity) { - *r = *a; - return; - } if (a->infinity) { secp256k1_fe bzinv2, bzinv3; r->infinity = b->infinity; @@ -449,7 +460,10 @@ static void secp256k1_gej_add_zinv_var(secp256k1_gej *r, const secp256k1_gej *a, secp256k1_fe_set_int(&r->z, 1); return; } - r->infinity = 0; + if (b->infinity) { + *r = *a; + return; + } /** We need to calculate (rx,ry,rz) = (ax,ay,az) + (bx,by,1/bzinv). Due to * secp256k1's isomorphism we can multiply the Z coordinates on both sides @@ -467,7 +481,7 @@ static void secp256k1_gej_add_zinv_var(secp256k1_gej *r, const secp256k1_gej *a, s1 = a->y; secp256k1_fe_normalize_weak(&s1); secp256k1_fe_mul(&s2, &b->y, &z12); secp256k1_fe_mul(&s2, &s2, &az); secp256k1_fe_negate(&h, &u1, 1); secp256k1_fe_add(&h, &u2); - secp256k1_fe_negate(&i, &s1, 1); secp256k1_fe_add(&i, &s2); + secp256k1_fe_negate(&i, &s2, 1); secp256k1_fe_add(&i, &s1); if (secp256k1_fe_normalizes_to_zero_var(&h)) { if (secp256k1_fe_normalizes_to_zero_var(&i)) { secp256k1_gej_double_var(r, a, NULL); @@ -476,14 +490,23 @@ static void secp256k1_gej_add_zinv_var(secp256k1_gej *r, const secp256k1_gej *a, } return; } - secp256k1_fe_sqr(&i2, &i); + + r->infinity = 0; + secp256k1_fe_mul(&r->z, &a->z, &h); + secp256k1_fe_sqr(&h2, &h); - secp256k1_fe_mul(&h3, &h, &h2); - r->z = a->z; secp256k1_fe_mul(&r->z, &r->z, &h); + secp256k1_fe_negate(&h2, &h2, 1); + secp256k1_fe_mul(&h3, &h2, &h); secp256k1_fe_mul(&t, &u1, &h2); - r->x = t; secp256k1_fe_mul_int(&r->x, 2); secp256k1_fe_add(&r->x, &h3); secp256k1_fe_negate(&r->x, &r->x, 3); secp256k1_fe_add(&r->x, &i2); - secp256k1_fe_negate(&r->y, &r->x, 5); secp256k1_fe_add(&r->y, &t); secp256k1_fe_mul(&r->y, &r->y, &i); - secp256k1_fe_mul(&h3, &h3, &s1); secp256k1_fe_negate(&h3, &h3, 1); + + secp256k1_fe_sqr(&r->x, &i); + secp256k1_fe_add(&r->x, &h3); + secp256k1_fe_add(&r->x, &t); + secp256k1_fe_add(&r->x, &t); + + secp256k1_fe_add(&t, &r->x); + secp256k1_fe_mul(&r->y, &t, &i); + secp256k1_fe_mul(&h3, &h3, &s1); secp256k1_fe_add(&r->y, &h3); } diff --git a/src/serialize.h b/src/serialize.h index a1cce78451..89a9f32240 100644 --- a/src/serialize.h +++ b/src/serialize.h @@ -520,6 +520,29 @@ struct CompactSizeFormatter } }; +template <typename U, bool LOSSY = false> +struct ChronoFormatter { + template <typename Stream, typename Tp> + void Unser(Stream& s, Tp& tp) + { + U u; + s >> u; + // Lossy deserialization does not make sense, so force Wnarrowing + tp = Tp{typename Tp::duration{typename Tp::duration::rep{u}}}; + } + template <typename Stream, typename Tp> + void Ser(Stream& s, Tp tp) + { + if constexpr (LOSSY) { + s << U(tp.time_since_epoch().count()); + } else { + s << U{tp.time_since_epoch().count()}; + } + } +}; +template <typename U> +using LossyChronoFormatter = ChronoFormatter<U, true>; + class CompactSizeWriter { protected: diff --git a/src/shutdown.cpp b/src/shutdown.cpp index fdf726b5f1..1dbc55aeb5 100644 --- a/src/shutdown.cpp +++ b/src/shutdown.cpp @@ -10,7 +10,7 @@ #endif #include <logging.h> -#include <node/ui_interface.h> +#include <node/interface_ui.h> #include <util/tokenpipe.h> #include <warnings.h> diff --git a/src/streams.h b/src/streams.h index 96b7696f72..ce1ba26d45 100644 --- a/src/streams.h +++ b/src/streams.h @@ -13,11 +13,11 @@ #include <algorithm> #include <assert.h> +#include <cstdio> #include <ios> #include <limits> #include <optional> #include <stdint.h> -#include <stdio.h> #include <string.h> #include <string> #include <utility> @@ -269,7 +269,6 @@ public: // Stream subset // bool eof() const { return size() == 0; } - CDataStream* rdbuf() { return this; } int in_avail() const { return size(); } void SetType(int n) { nType = n; } @@ -465,35 +464,28 @@ public: }; - /** Non-refcounted RAII wrapper for FILE* * * Will automatically close the file when it goes out of scope if not null. * If you're returning the file pointer, return file.release(). * If you need to close the file early, use file.fclose() instead of fclose(file). */ -class CAutoFile +class AutoFile { -private: - const int nType; - const int nVersion; - +protected: FILE* file; public: - CAutoFile(FILE* filenew, int nTypeIn, int nVersionIn) : nType(nTypeIn), nVersion(nVersionIn) - { - file = filenew; - } + explicit AutoFile(FILE* filenew) : file{filenew} {} - ~CAutoFile() + ~AutoFile() { fclose(); } // Disallow copies - CAutoFile(const CAutoFile&) = delete; - CAutoFile& operator=(const CAutoFile&) = delete; + AutoFile(const AutoFile&) = delete; + AutoFile& operator=(const AutoFile&) = delete; void fclose() { @@ -504,14 +496,14 @@ public: } /** Get wrapped FILE* with transfer of ownership. - * @note This will invalidate the CAutoFile object, and makes it the responsibility of the caller + * @note This will invalidate the AutoFile object, and makes it the responsibility of the caller * of this function to clean up the returned FILE*. */ FILE* release() { FILE* ret = file; file = nullptr; return ret; } /** Get wrapped FILE* without transfer of ownership. * @note Ownership of the FILE* will remain with this class. Use this only if the scope of the - * CAutoFile outlives use of the passed pointer. + * AutoFile outlives use of the passed pointer. */ FILE* Get() const { return file; } @@ -522,40 +514,62 @@ public: // // Stream subset // - int GetType() const { return nType; } - int GetVersion() const { return nVersion; } - void read(Span<std::byte> dst) { - if (!file) - throw std::ios_base::failure("CAutoFile::read: file handle is nullptr"); + if (!file) throw std::ios_base::failure("AutoFile::read: file handle is nullptr"); if (fread(dst.data(), 1, dst.size(), file) != dst.size()) { - throw std::ios_base::failure(feof(file) ? "CAutoFile::read: end of file" : "CAutoFile::read: fread failed"); + throw std::ios_base::failure(feof(file) ? "AutoFile::read: end of file" : "AutoFile::read: fread failed"); } } void ignore(size_t nSize) { - if (!file) - throw std::ios_base::failure("CAutoFile::ignore: file handle is nullptr"); + if (!file) throw std::ios_base::failure("AutoFile::ignore: file handle is nullptr"); unsigned char data[4096]; while (nSize > 0) { size_t nNow = std::min<size_t>(nSize, sizeof(data)); if (fread(data, 1, nNow, file) != nNow) - throw std::ios_base::failure(feof(file) ? "CAutoFile::ignore: end of file" : "CAutoFile::read: fread failed"); + throw std::ios_base::failure(feof(file) ? "AutoFile::ignore: end of file" : "AutoFile::read: fread failed"); nSize -= nNow; } } void write(Span<const std::byte> src) { - if (!file) - throw std::ios_base::failure("CAutoFile::write: file handle is nullptr"); + if (!file) throw std::ios_base::failure("AutoFile::write: file handle is nullptr"); if (fwrite(src.data(), 1, src.size(), file) != src.size()) { - throw std::ios_base::failure("CAutoFile::write: write failed"); + throw std::ios_base::failure("AutoFile::write: write failed"); } } + template <typename T> + AutoFile& operator<<(const T& obj) + { + if (!file) throw std::ios_base::failure("AutoFile::operator<<: file handle is nullptr"); + ::Serialize(*this, obj); + return *this; + } + + template <typename T> + AutoFile& operator>>(T&& obj) + { + if (!file) throw std::ios_base::failure("AutoFile::operator>>: file handle is nullptr"); + ::Unserialize(*this, obj); + return *this; + } +}; + +class CAutoFile : public AutoFile +{ +private: + const int nType; + const int nVersion; + +public: + CAutoFile(FILE* filenew, int nTypeIn, int nVersionIn) : AutoFile{filenew}, nType(nTypeIn), nVersion(nVersionIn) {} + int GetType() const { return nType; } + int GetVersion() const { return nVersion; } + template<typename T> CAutoFile& operator<<(const T& obj) { diff --git a/src/support/cleanse.h b/src/support/cleanse.h index 8c1210a114..b1227770c7 100644 --- a/src/support/cleanse.h +++ b/src/support/cleanse.h @@ -6,7 +6,7 @@ #ifndef BITCOIN_SUPPORT_CLEANSE_H #define BITCOIN_SUPPORT_CLEANSE_H -#include <stdlib.h> +#include <cstdlib> /** Secure overwrite a buffer (possibly containing secret data) with zero-bytes. The write * operation will not be optimized out by the compiler. */ diff --git a/src/support/lockedpool.cpp b/src/support/lockedpool.cpp index 6907749c6d..e48accf0a4 100644 --- a/src/support/lockedpool.cpp +++ b/src/support/lockedpool.cpp @@ -10,9 +10,6 @@ #endif #ifdef WIN32 -#ifndef NOMINMAX -#define NOMINMAX -#endif #include <windows.h> #else #include <sys/mman.h> // for mmap @@ -202,7 +199,10 @@ void Win32LockedPageAllocator::FreeLocked(void* addr, size_t len) size_t Win32LockedPageAllocator::GetLimit() { - // TODO is there a limit on Windows, how to get it? + size_t min, max; + if(GetProcessWorkingSetSize(GetCurrentProcess(), &min, &max) != 0) { + return min; + } return std::numeric_limits<size_t>::max(); } #endif diff --git a/src/sync.h b/src/sync.h index a175926113..a9d0091bd2 100644 --- a/src/sync.h +++ b/src/sync.h @@ -129,10 +129,22 @@ using RecursiveMutex = AnnotatedMixin<std::recursive_mutex>; /** Wrapped mutex: supports waiting but not recursive locking */ using Mutex = AnnotatedMixin<std::mutex>; +/** Different type to mark Mutex at global scope + * + * Thread safety analysis can't handle negative assertions about mutexes + * with global scope well, so mark them with a separate type, and + * eventually move all the mutexes into classes so they are not globally + * visible. + * + * See: https://github.com/bitcoin/bitcoin/pull/20272#issuecomment-720755781 + */ +class GlobalMutex : public Mutex { }; + #define AssertLockHeld(cs) AssertLockHeldInternal(#cs, __FILE__, __LINE__, &cs) inline void AssertLockNotHeldInline(const char* name, const char* file, int line, Mutex* cs) EXCLUSIVE_LOCKS_REQUIRED(!cs) { AssertLockNotHeldInternal(name, file, line, cs); } inline void AssertLockNotHeldInline(const char* name, const char* file, int line, RecursiveMutex* cs) LOCKS_EXCLUDED(cs) { AssertLockNotHeldInternal(name, file, line, cs); } +inline void AssertLockNotHeldInline(const char* name, const char* file, int line, GlobalMutex* cs) LOCKS_EXCLUDED(cs) { AssertLockNotHeldInternal(name, file, line, cs); } #define AssertLockNotHeld(cs) AssertLockNotHeldInline(#cs, __FILE__, __LINE__, &cs) /** Wrapper around std::unique_lock style lock for Mutex. */ @@ -153,11 +165,11 @@ private: bool TryEnter(const char* pszName, const char* pszFile, int nLine) { EnterCritical(pszName, pszFile, nLine, Base::mutex(), true); - Base::try_lock(); - if (!Base::owns_lock()) { - LeaveCritical(); + if (Base::try_lock()) { + return true; } - return Base::owns_lock(); + LeaveCritical(); + return false; } public: @@ -232,12 +244,26 @@ public: template<typename MutexArg> using DebugLock = UniqueLock<typename std::remove_reference<typename std::remove_pointer<MutexArg>::type>::type>; -#define LOCK(cs) DebugLock<decltype(cs)> UNIQUE_NAME(criticalblock)(cs, #cs, __FILE__, __LINE__) +// When locking a Mutex, require negative capability to ensure the lock +// is not already held +inline Mutex& MaybeCheckNotHeld(Mutex& cs) EXCLUSIVE_LOCKS_REQUIRED(!cs) LOCK_RETURNED(cs) { return cs; } +inline Mutex* MaybeCheckNotHeld(Mutex* cs) EXCLUSIVE_LOCKS_REQUIRED(!cs) LOCK_RETURNED(cs) { return cs; } + +// When locking a GlobalMutex, just check it is not locked in the surrounding scope +inline GlobalMutex& MaybeCheckNotHeld(GlobalMutex& cs) LOCKS_EXCLUDED(cs) LOCK_RETURNED(cs) { return cs; } +inline GlobalMutex* MaybeCheckNotHeld(GlobalMutex* cs) LOCKS_EXCLUDED(cs) LOCK_RETURNED(cs) { return cs; } + +// When locking a RecursiveMutex, it's okay to already hold the lock +// but check that it is not known to be locked in the surrounding scope anyway +inline RecursiveMutex& MaybeCheckNotHeld(RecursiveMutex& cs) LOCKS_EXCLUDED(cs) LOCK_RETURNED(cs) { return cs; } +inline RecursiveMutex* MaybeCheckNotHeld(RecursiveMutex* cs) LOCKS_EXCLUDED(cs) LOCK_RETURNED(cs) { return cs; } + +#define LOCK(cs) DebugLock<decltype(cs)> UNIQUE_NAME(criticalblock)(MaybeCheckNotHeld(cs), #cs, __FILE__, __LINE__) #define LOCK2(cs1, cs2) \ - DebugLock<decltype(cs1)> criticalblock1(cs1, #cs1, __FILE__, __LINE__); \ - DebugLock<decltype(cs2)> criticalblock2(cs2, #cs2, __FILE__, __LINE__); -#define TRY_LOCK(cs, name) DebugLock<decltype(cs)> name(cs, #cs, __FILE__, __LINE__, true) -#define WAIT_LOCK(cs, name) DebugLock<decltype(cs)> name(cs, #cs, __FILE__, __LINE__) + DebugLock<decltype(cs1)> criticalblock1(MaybeCheckNotHeld(cs1), #cs1, __FILE__, __LINE__); \ + DebugLock<decltype(cs2)> criticalblock2(MaybeCheckNotHeld(cs2), #cs2, __FILE__, __LINE__) +#define TRY_LOCK(cs, name) DebugLock<decltype(cs)> name(MaybeCheckNotHeld(cs), #cs, __FILE__, __LINE__, true) +#define WAIT_LOCK(cs, name) DebugLock<decltype(cs)> name(MaybeCheckNotHeld(cs), #cs, __FILE__, __LINE__) #define ENTER_CRITICAL_SECTION(cs) \ { \ @@ -276,7 +302,7 @@ using DebugLock = UniqueLock<typename std::remove_reference<typename std::remove //! //! The above is detectable at compile-time with the -Wreturn-local-addr flag in //! gcc and the -Wreturn-stack-address flag in clang, both enabled by default. -#define WITH_LOCK(cs, code) [&]() -> decltype(auto) { LOCK(cs); code; }() +#define WITH_LOCK(cs, code) (MaybeCheckNotHeld(cs), [&]() -> decltype(auto) { LOCK(cs); code; }()) class CSemaphore { diff --git a/src/test/addrman_tests.cpp b/src/test/addrman_tests.cpp index 12cf1176a6..b10d32ccec 100644 --- a/src/test/addrman_tests.cpp +++ b/src/test/addrman_tests.cpp @@ -225,7 +225,7 @@ BOOST_AUTO_TEST_CASE(addrman_new_multiplicity) { auto addrman = std::make_unique<AddrMan>(EMPTY_NETGROUPMAN, DETERMINISTIC, GetCheckRatio(m_node)); CAddress addr{CAddress(ResolveService("253.3.3.3", 8333), NODE_NONE)}; - int64_t start_time{GetAdjustedTime()}; + const auto start_time{Now<NodeSeconds>()}; addr.nTime = start_time; // test that multiplicity stays at 1 if nTime doesn't increase @@ -244,7 +244,7 @@ BOOST_AUTO_TEST_CASE(addrman_new_multiplicity) for (unsigned int i = 1; i < 400; ++i) { std::string addr_ip{ToString(i % 256) + "." + ToString(i >> 8 % 256) + ".1.1"}; CNetAddr source{ResolveIP(addr_ip)}; - addr.nTime = start_time + i; + addr.nTime = start_time + std::chrono::seconds{i}; addrman->Add({addr}, source); } AddressPosition addr_pos_multi = addrman->FindAddressEntry(addr).value(); @@ -295,15 +295,15 @@ BOOST_AUTO_TEST_CASE(addrman_getaddr) BOOST_CHECK_EQUAL(vAddr1.size(), 0U); CAddress addr1 = CAddress(ResolveService("250.250.2.1", 8333), NODE_NONE); - addr1.nTime = GetAdjustedTime(); // Set time so isTerrible = false + addr1.nTime = Now<NodeSeconds>(); // Set time so isTerrible = false CAddress addr2 = CAddress(ResolveService("250.251.2.2", 9999), NODE_NONE); - addr2.nTime = GetAdjustedTime(); + addr2.nTime = Now<NodeSeconds>(); CAddress addr3 = CAddress(ResolveService("251.252.2.3", 8333), NODE_NONE); - addr3.nTime = GetAdjustedTime(); + addr3.nTime = Now<NodeSeconds>(); CAddress addr4 = CAddress(ResolveService("252.253.3.4", 8333), NODE_NONE); - addr4.nTime = GetAdjustedTime(); + addr4.nTime = Now<NodeSeconds>(); CAddress addr5 = CAddress(ResolveService("252.254.4.5", 8333), NODE_NONE); - addr5.nTime = GetAdjustedTime(); + addr5.nTime = Now<NodeSeconds>(); CNetAddr source1 = ResolveIP("250.1.2.1"); CNetAddr source2 = ResolveIP("250.2.3.3"); @@ -329,7 +329,7 @@ BOOST_AUTO_TEST_CASE(addrman_getaddr) CAddress addr = CAddress(ResolveService(strAddr), NODE_NONE); // Ensure that for all addrs in addrman, isTerrible == false. - addr.nTime = GetAdjustedTime(); + addr.nTime = Now<NodeSeconds>(); addrman->Add({addr}, ResolveIP(strAddr)); if (i % 8 == 0) addrman->Good(addr); @@ -821,8 +821,8 @@ BOOST_AUTO_TEST_CASE(addrman_evictionworks) // Ensure test of address fails, so that it is evicted. // Update entry in tried by setting last good connection in the deep past. - BOOST_CHECK(!addrman->Good(info, /*nTime=*/1)); - addrman->Attempt(info, /*fCountFailure=*/false, /*nTime=*/GetAdjustedTime() - 61); + BOOST_CHECK(!addrman->Good(info, NodeSeconds{1s})); + addrman->Attempt(info, /*fCountFailure=*/false, Now<NodeSeconds>() - 61s); // Should swap 36 for 19. addrman->ResolveCollisions(); @@ -966,7 +966,7 @@ BOOST_AUTO_TEST_CASE(addrman_update_address) CNetAddr source{ResolveIP("252.2.2.2")}; CAddress addr{CAddress(ResolveService("250.1.1.1", 8333), NODE_NONE)}; - int64_t start_time{GetAdjustedTime() - 10000}; + const auto start_time{Now<NodeSeconds>() - 10000s}; addr.nTime = start_time; BOOST_CHECK(addrman->Add({addr}, source)); BOOST_CHECK_EQUAL(addrman->size(), 1U); @@ -978,7 +978,7 @@ BOOST_AUTO_TEST_CASE(addrman_update_address) addrman->SetServices(addr_diff_port, NODE_NETWORK_LIMITED); std::vector<CAddress> vAddr1{addrman->GetAddr(/*max_addresses=*/0, /*max_pct=*/0, /*network=*/std::nullopt)}; BOOST_CHECK_EQUAL(vAddr1.size(), 1U); - BOOST_CHECK_EQUAL(vAddr1.at(0).nTime, start_time); + BOOST_CHECK(vAddr1.at(0).nTime == start_time); BOOST_CHECK_EQUAL(vAddr1.at(0).nServices, NODE_NONE); // Updating an addrman entry with the correct port is successful @@ -986,7 +986,7 @@ BOOST_AUTO_TEST_CASE(addrman_update_address) addrman->SetServices(addr, NODE_NETWORK_LIMITED); std::vector<CAddress> vAddr2 = addrman->GetAddr(/*max_addresses=*/0, /*max_pct=*/0, /*network=*/std::nullopt); BOOST_CHECK_EQUAL(vAddr2.size(), 1U); - BOOST_CHECK(vAddr2.at(0).nTime >= start_time + 10000); + BOOST_CHECK(vAddr2.at(0).nTime >= start_time + 10000s); BOOST_CHECK_EQUAL(vAddr2.at(0).nServices, NODE_NETWORK_LIMITED); } diff --git a/src/test/banman_tests.cpp b/src/test/banman_tests.cpp index 27ce9ad638..ecf60834ce 100644 --- a/src/test/banman_tests.cpp +++ b/src/test/banman_tests.cpp @@ -27,7 +27,7 @@ BOOST_AUTO_TEST_CASE(file) " { \"version\": 1, \"ban_created\": 0, \"banned_until\": 778, \"address\": \"1.0.0.0/8\" }" "] }", }; - assert(WriteBinaryFile(banlist_path + ".json", entries_write)); + BOOST_REQUIRE(WriteBinaryFile(banlist_path + ".json", entries_write)); { // The invalid entries will be dropped, but the valid one remains ASSERT_DEBUG_LOG("Dropping entry with unparseable address or subnet (aaaaaaaaa) from ban list"); @@ -35,7 +35,7 @@ BOOST_AUTO_TEST_CASE(file) BanMan banman{banlist_path, /*client_interface=*/nullptr, /*default_ban_time=*/0}; banmap_t entries_read; banman.GetBanned(entries_read); - assert(entries_read.size() == 1); + BOOST_CHECK_EQUAL(entries_read.size(), 1); } } } diff --git a/src/test/base58_tests.cpp b/src/test/base58_tests.cpp index 00f32ddcee..249f89ae2f 100644 --- a/src/test/base58_tests.cpp +++ b/src/test/base58_tests.cpp @@ -25,7 +25,7 @@ BOOST_AUTO_TEST_CASE(base58_EncodeBase58) { UniValue tests = read_json(std::string(json_tests::base58_encode_decode, json_tests::base58_encode_decode + sizeof(json_tests::base58_encode_decode))); for (unsigned int idx = 0; idx < tests.size(); idx++) { - UniValue test = tests[idx]; + const UniValue& test = tests[idx]; std::string strTest = test.write(); if (test.size() < 2) // Allow for extra stuff (useful for comments) { @@ -47,7 +47,7 @@ BOOST_AUTO_TEST_CASE(base58_DecodeBase58) std::vector<unsigned char> result; for (unsigned int idx = 0; idx < tests.size(); idx++) { - UniValue test = tests[idx]; + const UniValue& test = tests[idx]; std::string strTest = test.write(); if (test.size() < 2) // Allow for extra stuff (useful for comments) { diff --git a/src/test/bip32_tests.cpp b/src/test/bip32_tests.cpp index 64cc924239..75b29ae0aa 100644 --- a/src/test/bip32_tests.cpp +++ b/src/test/bip32_tests.cpp @@ -184,4 +184,22 @@ BOOST_AUTO_TEST_CASE(bip32_test5) { } } +BOOST_AUTO_TEST_CASE(bip32_max_depth) { + CExtKey key_parent{DecodeExtKey(test1.vDerive[0].prv)}, key_child; + CExtPubKey pubkey_parent{DecodeExtPubKey(test1.vDerive[0].pub)}, pubkey_child; + + // We can derive up to the 255th depth.. + for (auto i = 0; i++ < 255;) { + BOOST_CHECK(key_parent.Derive(key_child, 0)); + std::swap(key_parent, key_child); + BOOST_CHECK(pubkey_parent.Derive(pubkey_child, 0)); + std::swap(pubkey_parent, pubkey_child); + } + + // But trying to derive a non-existent 256th depth will fail! + BOOST_CHECK(key_parent.nDepth == 255 && pubkey_parent.nDepth == 255); + BOOST_CHECK(!key_parent.Derive(key_child, 0)); + BOOST_CHECK(!pubkey_parent.Derive(pubkey_child, 0)); +} + BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/blockchain_tests.cpp b/src/test/blockchain_tests.cpp index c8e8cdeeb3..0157e25071 100644 --- a/src/test/blockchain_tests.cpp +++ b/src/test/blockchain_tests.cpp @@ -4,13 +4,13 @@ #include <boost/test/unit_test.hpp> -#include <stdlib.h> - #include <chain.h> #include <rpc/blockchain.h> #include <test/util/setup_common.h> #include <util/string.h> +#include <cstdlib> + /* Equality between doubles is imprecise. Comparison should be done * with a small threshold of tolerance, rather than exact equality. */ diff --git a/src/test/blockencodings_tests.cpp b/src/test/blockencodings_tests.cpp index 875241094d..78b82b9b20 100644 --- a/src/test/blockencodings_tests.cpp +++ b/src/test/blockencodings_tests.cpp @@ -54,7 +54,7 @@ constexpr long SHARED_TX_OFFSET{3}; BOOST_AUTO_TEST_CASE(SimpleRoundTripTest) { - CTxMemPool pool; + CTxMemPool& pool = *Assert(m_node.mempool); TestMemPoolEntryHelper entry; CBlock block(BuildBlockTestCase()); @@ -137,7 +137,7 @@ public: BOOST_AUTO_TEST_CASE(NonCoinbasePreforwardRTTest) { - CTxMemPool pool; + CTxMemPool& pool = *Assert(m_node.mempool); TestMemPoolEntryHelper entry; CBlock block(BuildBlockTestCase()); @@ -207,7 +207,7 @@ BOOST_AUTO_TEST_CASE(NonCoinbasePreforwardRTTest) BOOST_AUTO_TEST_CASE(SufficientPreforwardRTTest) { - CTxMemPool pool; + CTxMemPool& pool = *Assert(m_node.mempool); TestMemPoolEntryHelper entry; CBlock block(BuildBlockTestCase()); @@ -258,7 +258,7 @@ BOOST_AUTO_TEST_CASE(SufficientPreforwardRTTest) BOOST_AUTO_TEST_CASE(EmptyBlockRoundTripTest) { - CTxMemPool pool; + CTxMemPool& pool = *Assert(m_node.mempool); CMutableTransaction coinbase; coinbase.vin.resize(1); coinbase.vin[0].scriptSig.resize(10); diff --git a/src/test/blockfilter_index_tests.cpp b/src/test/blockfilter_index_tests.cpp index ba1eacfc78..2798e998af 100644 --- a/src/test/blockfilter_index_tests.cpp +++ b/src/test/blockfilter_index_tests.cpp @@ -7,6 +7,7 @@ #include <consensus/merkle.h> #include <consensus/validation.h> #include <index/blockfilterindex.h> +#include <interfaces/chain.h> #include <node/miner.h> #include <pow.h> #include <script/standard.h> @@ -65,7 +66,7 @@ CBlock BuildChainTestingSetup::CreateBlock(const CBlockIndex* prev, const std::vector<CMutableTransaction>& txns, const CScript& scriptPubKey) { - std::unique_ptr<CBlockTemplate> pblocktemplate = BlockAssembler{m_node.chainman->ActiveChainstate(), *m_node.mempool}.CreateNewBlock(scriptPubKey); + std::unique_ptr<CBlockTemplate> pblocktemplate = BlockAssembler{m_node.chainman->ActiveChainstate(), m_node.mempool.get()}.CreateNewBlock(scriptPubKey); CBlock& block = pblocktemplate->block; block.hashPrevBlock = prev->GetBlockHash(); block.nTime = prev->nTime + 1; @@ -100,7 +101,7 @@ bool BuildChainTestingSetup::BuildChain(const CBlockIndex* pindex, CBlockHeader header = block->GetBlockHeader(); BlockValidationState state; - if (!Assert(m_node.chainman)->ProcessNewBlockHeaders({header}, state, &pindex)) { + if (!Assert(m_node.chainman)->ProcessNewBlockHeaders({header}, true, state, &pindex)) { return false; } } @@ -110,7 +111,7 @@ bool BuildChainTestingSetup::BuildChain(const CBlockIndex* pindex, BOOST_FIXTURE_TEST_CASE(blockfilter_index_initial_sync, BuildChainTestingSetup) { - BlockFilterIndex filter_index(BlockFilterType::BASIC, 1 << 20, true); + BlockFilterIndex filter_index(interfaces::MakeChain(m_node), BlockFilterType::BASIC, 1 << 20, true); uint256 last_header; @@ -137,7 +138,7 @@ BOOST_FIXTURE_TEST_CASE(blockfilter_index_initial_sync, BuildChainTestingSetup) // BlockUntilSyncedToCurrentChain should return false before index is started. BOOST_CHECK(!filter_index.BlockUntilSyncedToCurrentChain()); - BOOST_REQUIRE(filter_index.Start(m_node.chainman->ActiveChainstate())); + BOOST_REQUIRE(filter_index.Start()); // Allow filter index to catch up with the block index. constexpr int64_t timeout_ms = 10 * 1000; @@ -177,7 +178,7 @@ BOOST_FIXTURE_TEST_CASE(blockfilter_index_initial_sync, BuildChainTestingSetup) uint256 chainA_last_header = last_header; for (size_t i = 0; i < 2; i++) { const auto& block = chainA[i]; - BOOST_REQUIRE(Assert(m_node.chainman)->ProcessNewBlock(block, true, nullptr)); + BOOST_REQUIRE(Assert(m_node.chainman)->ProcessNewBlock(block, true, true, nullptr)); } for (size_t i = 0; i < 2; i++) { const auto& block = chainA[i]; @@ -195,7 +196,7 @@ BOOST_FIXTURE_TEST_CASE(blockfilter_index_initial_sync, BuildChainTestingSetup) uint256 chainB_last_header = last_header; for (size_t i = 0; i < 3; i++) { const auto& block = chainB[i]; - BOOST_REQUIRE(Assert(m_node.chainman)->ProcessNewBlock(block, true, nullptr)); + BOOST_REQUIRE(Assert(m_node.chainman)->ProcessNewBlock(block, true, true, nullptr)); } for (size_t i = 0; i < 3; i++) { const auto& block = chainB[i]; @@ -226,7 +227,7 @@ BOOST_FIXTURE_TEST_CASE(blockfilter_index_initial_sync, BuildChainTestingSetup) // Reorg back to chain A. for (size_t i = 2; i < 4; i++) { const auto& block = chainA[i]; - BOOST_REQUIRE(Assert(m_node.chainman)->ProcessNewBlock(block, true, nullptr)); + BOOST_REQUIRE(Assert(m_node.chainman)->ProcessNewBlock(block, true, true, nullptr)); } // Check that chain A and B blocks can be retrieved. @@ -279,14 +280,14 @@ BOOST_FIXTURE_TEST_CASE(blockfilter_index_init_destroy, BasicTestingSetup) filter_index = GetBlockFilterIndex(BlockFilterType::BASIC); BOOST_CHECK(filter_index == nullptr); - BOOST_CHECK(InitBlockFilterIndex(BlockFilterType::BASIC, 1 << 20, true, false)); + BOOST_CHECK(InitBlockFilterIndex([&]{ return interfaces::MakeChain(m_node); }, BlockFilterType::BASIC, 1 << 20, true, false)); filter_index = GetBlockFilterIndex(BlockFilterType::BASIC); BOOST_CHECK(filter_index != nullptr); BOOST_CHECK(filter_index->GetFilterType() == BlockFilterType::BASIC); // Initialize returns false if index already exists. - BOOST_CHECK(!InitBlockFilterIndex(BlockFilterType::BASIC, 1 << 20, true, false)); + BOOST_CHECK(!InitBlockFilterIndex([&]{ return interfaces::MakeChain(m_node); }, BlockFilterType::BASIC, 1 << 20, true, false)); int iter_count = 0; ForEachBlockFilterIndex([&iter_count](BlockFilterIndex& _index) { iter_count++; }); @@ -301,7 +302,7 @@ BOOST_FIXTURE_TEST_CASE(blockfilter_index_init_destroy, BasicTestingSetup) BOOST_CHECK(filter_index == nullptr); // Reinitialize index. - BOOST_CHECK(InitBlockFilterIndex(BlockFilterType::BASIC, 1 << 20, true, false)); + BOOST_CHECK(InitBlockFilterIndex([&]{ return interfaces::MakeChain(m_node); }, BlockFilterType::BASIC, 1 << 20, true, false)); DestroyAllBlockFilterIndexes(); diff --git a/src/test/blockfilter_tests.cpp b/src/test/blockfilter_tests.cpp index 178757e7b4..0831188327 100644 --- a/src/test/blockfilter_tests.cpp +++ b/src/test/blockfilter_tests.cpp @@ -137,7 +137,7 @@ BOOST_AUTO_TEST_CASE(blockfilters_json_test) const UniValue& tests = json.get_array(); for (unsigned int i = 0; i < tests.size(); i++) { - UniValue test = tests[i]; + const UniValue& test = tests[i]; std::string strTest = test.write(); if (test.size() == 1) { diff --git a/src/test/coinstatsindex_tests.cpp b/src/test/coinstatsindex_tests.cpp index 50eb479035..2a6a777cfe 100644 --- a/src/test/coinstatsindex_tests.cpp +++ b/src/test/coinstatsindex_tests.cpp @@ -4,6 +4,8 @@ #include <chainparams.h> #include <index/coinstatsindex.h> +#include <interfaces/chain.h> +#include <kernel/coinstats.h> #include <test/util/setup_common.h> #include <test/util/validation.h> #include <util/time.h> @@ -13,9 +15,6 @@ #include <chrono> -using kernel::CCoinsStats; -using kernel::CoinStatsHashType; - BOOST_AUTO_TEST_SUITE(coinstatsindex_tests) static void IndexWaitSynced(BaseIndex& index) @@ -31,7 +30,7 @@ static void IndexWaitSynced(BaseIndex& index) BOOST_FIXTURE_TEST_CASE(coinstatsindex_initial_sync, TestChain100Setup) { - CoinStatsIndex coin_stats_index{1 << 20, true}; + CoinStatsIndex coin_stats_index{interfaces::MakeChain(m_node), 1 << 20, true}; const CBlockIndex* block_index; { @@ -40,13 +39,13 @@ BOOST_FIXTURE_TEST_CASE(coinstatsindex_initial_sync, TestChain100Setup) } // CoinStatsIndex should not be found before it is started. - BOOST_CHECK(!coin_stats_index.LookUpStats(block_index)); + BOOST_CHECK(!coin_stats_index.LookUpStats(*block_index)); // BlockUntilSyncedToCurrentChain should return false before CoinStatsIndex // is started. BOOST_CHECK(!coin_stats_index.BlockUntilSyncedToCurrentChain()); - BOOST_REQUIRE(coin_stats_index.Start(m_node.chainman->ActiveChainstate())); + BOOST_REQUIRE(coin_stats_index.Start()); IndexWaitSynced(coin_stats_index); @@ -56,10 +55,10 @@ BOOST_FIXTURE_TEST_CASE(coinstatsindex_initial_sync, TestChain100Setup) LOCK(cs_main); genesis_block_index = m_node.chainman->ActiveChain().Genesis(); } - BOOST_CHECK(coin_stats_index.LookUpStats(genesis_block_index)); + BOOST_CHECK(coin_stats_index.LookUpStats(*genesis_block_index)); // Check that CoinStatsIndex updates with new blocks. - BOOST_CHECK(coin_stats_index.LookUpStats(block_index)); + BOOST_CHECK(coin_stats_index.LookUpStats(*block_index)); const CScript script_pub_key{CScript() << ToByteVector(coinbaseKey.GetPubKey()) << OP_CHECKSIG}; std::vector<CMutableTransaction> noTxns; @@ -73,7 +72,7 @@ BOOST_FIXTURE_TEST_CASE(coinstatsindex_initial_sync, TestChain100Setup) LOCK(cs_main); new_block_index = m_node.chainman->ActiveChain().Tip(); } - BOOST_CHECK(coin_stats_index.LookUpStats(new_block_index)); + BOOST_CHECK(coin_stats_index.LookUpStats(*new_block_index)); BOOST_CHECK(block_index != new_block_index); @@ -87,11 +86,11 @@ BOOST_FIXTURE_TEST_CASE(coinstatsindex_initial_sync, TestChain100Setup) // make sure index is not corrupted and is able to reload. BOOST_FIXTURE_TEST_CASE(coinstatsindex_unclean_shutdown, TestChain100Setup) { - CChainState& chainstate = Assert(m_node.chainman)->ActiveChainstate(); + Chainstate& chainstate = Assert(m_node.chainman)->ActiveChainstate(); const CChainParams& params = Params(); { - CoinStatsIndex index{1 << 20}; - BOOST_REQUIRE(index.Start(chainstate)); + CoinStatsIndex index{interfaces::MakeChain(m_node), 1 << 20}; + BOOST_REQUIRE(index.Start()); IndexWaitSynced(index); std::shared_ptr<const CBlock> new_block; CBlockIndex* new_block_index = nullptr; @@ -104,7 +103,7 @@ BOOST_FIXTURE_TEST_CASE(coinstatsindex_unclean_shutdown, TestChain100Setup) LOCK(cs_main); BlockValidationState state; BOOST_CHECK(CheckBlock(block, state, params.GetConsensus())); - BOOST_CHECK(chainstate.AcceptBlock(new_block, state, &new_block_index, true, nullptr, nullptr)); + BOOST_CHECK(chainstate.AcceptBlock(new_block, state, &new_block_index, true, nullptr, nullptr, true)); CCoinsViewCache view(&chainstate.CoinsTip()); BOOST_CHECK(chainstate.ConnectBlock(block, state, new_block_index, view)); } @@ -116,9 +115,9 @@ BOOST_FIXTURE_TEST_CASE(coinstatsindex_unclean_shutdown, TestChain100Setup) } { - CoinStatsIndex index{1 << 20}; + CoinStatsIndex index{interfaces::MakeChain(m_node), 1 << 20}; // Make sure the index can be loaded. - BOOST_REQUIRE(index.Start(chainstate)); + BOOST_REQUIRE(index.Start()); index.Stop(); } } diff --git a/src/test/denialofservice_tests.cpp b/src/test/denialofservice_tests.cpp index 3b4a6f2637..7150698e64 100644 --- a/src/test/denialofservice_tests.cpp +++ b/src/test/denialofservice_tests.cpp @@ -15,6 +15,7 @@ #include <serialize.h> #include <test/util/net.h> #include <test/util/setup_common.h> +#include <timedata.h> #include <util/string.h> #include <util/system.h> #include <util/time.h> @@ -44,17 +45,17 @@ BOOST_FIXTURE_TEST_SUITE(denialofservice_tests, TestingSetup) // work. BOOST_AUTO_TEST_CASE(outbound_slow_chain_eviction) { - auto connman = std::make_unique<CConnman>(0x1337, 0x1337, *m_node.addrman, *m_node.netgroupman); + LOCK(NetEventsInterface::g_msgproc_mutex); + + ConnmanTestMsg& connman = static_cast<ConnmanTestMsg&>(*m_node.connman); // Disable inactivity checks for this test to avoid interference - static_cast<ConnmanTestMsg*>(connman.get())->SetPeerConnectTimeout(99999s); - auto peerLogic = PeerManager::make(*connman, *m_node.addrman, nullptr, - *m_node.chainman, *m_node.mempool, false); + connman.SetPeerConnectTimeout(99999s); + PeerManager& peerman = *m_node.peerman; // Mock an outbound peer CAddress addr1(ip(0xa0b0c001), NODE_NONE); NodeId id{0}; CNode dummyNode1{id++, - ServiceFlags(NODE_NETWORK | NODE_WITNESS), /*sock=*/nullptr, addr1, /*nKeyedNetGroupIn=*/0, @@ -63,10 +64,15 @@ BOOST_AUTO_TEST_CASE(outbound_slow_chain_eviction) /*addrNameIn=*/"", ConnectionType::OUTBOUND_FULL_RELAY, /*inbound_onion=*/false}; - dummyNode1.SetCommonVersion(PROTOCOL_VERSION); - peerLogic->InitializeNode(&dummyNode1); - dummyNode1.fSuccessfullyConnected = true; + connman.Handshake( + /*node=*/dummyNode1, + /*successfully_connected=*/true, + /*remote_services=*/ServiceFlags(NODE_NETWORK | NODE_WITNESS), + /*local_services=*/ServiceFlags(NODE_NETWORK | NODE_WITNESS), + /*version=*/PROTOCOL_VERSION, + /*relay_txs=*/true); + TestOnlyResetTimeData(); // This test requires that we have a chain with non-zero work. { @@ -76,10 +82,8 @@ BOOST_AUTO_TEST_CASE(outbound_slow_chain_eviction) } // Test starts here - { - LOCK(dummyNode1.cs_sendProcessing); - BOOST_CHECK(peerLogic->SendMessages(&dummyNode1)); // should result in getheaders - } + BOOST_CHECK(peerman.SendMessages(&dummyNode1)); // should result in getheaders + { LOCK(dummyNode1.cs_vSend); BOOST_CHECK(dummyNode1.vSendMsg.size() > 0); @@ -89,30 +93,23 @@ BOOST_AUTO_TEST_CASE(outbound_slow_chain_eviction) int64_t nStartTime = GetTime(); // Wait 21 minutes SetMockTime(nStartTime+21*60); - { - LOCK(dummyNode1.cs_sendProcessing); - BOOST_CHECK(peerLogic->SendMessages(&dummyNode1)); // should result in getheaders - } + BOOST_CHECK(peerman.SendMessages(&dummyNode1)); // should result in getheaders { LOCK(dummyNode1.cs_vSend); BOOST_CHECK(dummyNode1.vSendMsg.size() > 0); } // Wait 3 more minutes SetMockTime(nStartTime+24*60); - { - LOCK(dummyNode1.cs_sendProcessing); - BOOST_CHECK(peerLogic->SendMessages(&dummyNode1)); // should result in disconnect - } + BOOST_CHECK(peerman.SendMessages(&dummyNode1)); // should result in disconnect BOOST_CHECK(dummyNode1.fDisconnect == true); - peerLogic->FinalizeNode(dummyNode1); + peerman.FinalizeNode(dummyNode1); } static void AddRandomOutboundPeer(NodeId& id, std::vector<CNode*>& vNodes, PeerManager& peerLogic, ConnmanTestMsg& connman, ConnectionType connType) { CAddress addr(ip(g_insecure_rand_ctx.randbits(32)), NODE_NONE); vNodes.emplace_back(new CNode{id++, - ServiceFlags(NODE_NETWORK | NODE_WITNESS), /*sock=*/nullptr, addr, /*nKeyedNetGroupIn=*/0, @@ -124,7 +121,7 @@ static void AddRandomOutboundPeer(NodeId& id, std::vector<CNode*>& vNodes, PeerM CNode &node = *vNodes.back(); node.SetCommonVersion(PROTOCOL_VERSION); - peerLogic.InitializeNode(&node); + peerLogic.InitializeNode(node, ServiceFlags(NODE_NETWORK | NODE_WITNESS)); node.fSuccessfullyConnected = true; connman.AddTestNode(node); @@ -271,6 +268,8 @@ BOOST_AUTO_TEST_CASE(block_relay_only_eviction) BOOST_AUTO_TEST_CASE(peer_discouragement) { + LOCK(NetEventsInterface::g_msgproc_mutex); + auto banman = std::make_unique<BanMan>(m_args.GetDataDirBase() / "banlist", nullptr, DEFAULT_MISBEHAVING_BANTIME); auto connman = std::make_unique<ConnmanTestMsg>(0x1337, 0x1337, *m_node.addrman, *m_node.netgroupman); auto peerLogic = PeerManager::make(*connman, *m_node.addrman, banman.get(), @@ -292,7 +291,6 @@ BOOST_AUTO_TEST_CASE(peer_discouragement) banman->ClearBanned(); NodeId id{0}; nodes[0] = new CNode{id++, - NODE_NETWORK, /*sock=*/nullptr, addr[0], /*nKeyedNetGroupIn=*/0, @@ -302,20 +300,17 @@ BOOST_AUTO_TEST_CASE(peer_discouragement) ConnectionType::INBOUND, /*inbound_onion=*/false}; nodes[0]->SetCommonVersion(PROTOCOL_VERSION); - peerLogic->InitializeNode(nodes[0]); + peerLogic->InitializeNode(*nodes[0], NODE_NETWORK); nodes[0]->fSuccessfullyConnected = true; connman->AddTestNode(*nodes[0]); - peerLogic->Misbehaving(nodes[0]->GetId(), DISCOURAGEMENT_THRESHOLD, /*message=*/""); // Should be discouraged - { - LOCK(nodes[0]->cs_sendProcessing); - BOOST_CHECK(peerLogic->SendMessages(nodes[0])); - } + peerLogic->UnitTestMisbehaving(nodes[0]->GetId(), DISCOURAGEMENT_THRESHOLD); // Should be discouraged + BOOST_CHECK(peerLogic->SendMessages(nodes[0])); + BOOST_CHECK(banman->IsDiscouraged(addr[0])); BOOST_CHECK(nodes[0]->fDisconnect); BOOST_CHECK(!banman->IsDiscouraged(other_addr)); // Different address, not discouraged nodes[1] = new CNode{id++, - NODE_NETWORK, /*sock=*/nullptr, addr[1], /*nKeyedNetGroupIn=*/1, @@ -325,25 +320,19 @@ BOOST_AUTO_TEST_CASE(peer_discouragement) ConnectionType::INBOUND, /*inbound_onion=*/false}; nodes[1]->SetCommonVersion(PROTOCOL_VERSION); - peerLogic->InitializeNode(nodes[1]); + peerLogic->InitializeNode(*nodes[1], NODE_NETWORK); nodes[1]->fSuccessfullyConnected = true; connman->AddTestNode(*nodes[1]); - peerLogic->Misbehaving(nodes[1]->GetId(), DISCOURAGEMENT_THRESHOLD - 1, /*message=*/""); - { - LOCK(nodes[1]->cs_sendProcessing); - BOOST_CHECK(peerLogic->SendMessages(nodes[1])); - } + peerLogic->UnitTestMisbehaving(nodes[1]->GetId(), DISCOURAGEMENT_THRESHOLD - 1); + BOOST_CHECK(peerLogic->SendMessages(nodes[1])); // [0] is still discouraged/disconnected. BOOST_CHECK(banman->IsDiscouraged(addr[0])); BOOST_CHECK(nodes[0]->fDisconnect); // [1] is not discouraged/disconnected yet. BOOST_CHECK(!banman->IsDiscouraged(addr[1])); BOOST_CHECK(!nodes[1]->fDisconnect); - peerLogic->Misbehaving(nodes[1]->GetId(), 1, /*message=*/""); // [1] reaches discouragement threshold - { - LOCK(nodes[1]->cs_sendProcessing); - BOOST_CHECK(peerLogic->SendMessages(nodes[1])); - } + peerLogic->UnitTestMisbehaving(nodes[1]->GetId(), 1); // [1] reaches discouragement threshold + BOOST_CHECK(peerLogic->SendMessages(nodes[1])); // Expect both [0] and [1] to be discouraged/disconnected now. BOOST_CHECK(banman->IsDiscouraged(addr[0])); BOOST_CHECK(nodes[0]->fDisconnect); @@ -353,7 +342,6 @@ BOOST_AUTO_TEST_CASE(peer_discouragement) // Make sure non-IP peers are discouraged and disconnected properly. nodes[2] = new CNode{id++, - NODE_NETWORK, /*sock=*/nullptr, addr[2], /*nKeyedNetGroupIn=*/1, @@ -363,14 +351,11 @@ BOOST_AUTO_TEST_CASE(peer_discouragement) ConnectionType::OUTBOUND_FULL_RELAY, /*inbound_onion=*/false}; nodes[2]->SetCommonVersion(PROTOCOL_VERSION); - peerLogic->InitializeNode(nodes[2]); + peerLogic->InitializeNode(*nodes[2], NODE_NETWORK); nodes[2]->fSuccessfullyConnected = true; connman->AddTestNode(*nodes[2]); - peerLogic->Misbehaving(nodes[2]->GetId(), DISCOURAGEMENT_THRESHOLD, /*message=*/""); - { - LOCK(nodes[2]->cs_sendProcessing); - BOOST_CHECK(peerLogic->SendMessages(nodes[2])); - } + peerLogic->UnitTestMisbehaving(nodes[2]->GetId(), DISCOURAGEMENT_THRESHOLD); + BOOST_CHECK(peerLogic->SendMessages(nodes[2])); BOOST_CHECK(banman->IsDiscouraged(addr[0])); BOOST_CHECK(banman->IsDiscouraged(addr[1])); BOOST_CHECK(banman->IsDiscouraged(addr[2])); @@ -386,6 +371,8 @@ BOOST_AUTO_TEST_CASE(peer_discouragement) BOOST_AUTO_TEST_CASE(DoS_bantime) { + LOCK(NetEventsInterface::g_msgproc_mutex); + auto banman = std::make_unique<BanMan>(m_args.GetDataDirBase() / "banlist", nullptr, DEFAULT_MISBEHAVING_BANTIME); auto connman = std::make_unique<CConnman>(0x1337, 0x1337, *m_node.addrman, *m_node.netgroupman); auto peerLogic = PeerManager::make(*connman, *m_node.addrman, banman.get(), @@ -398,7 +385,6 @@ BOOST_AUTO_TEST_CASE(DoS_bantime) CAddress addr(ip(0xa0b0c001), NODE_NONE); NodeId id{0}; CNode dummyNode{id++, - NODE_NETWORK, /*sock=*/nullptr, addr, /*nKeyedNetGroupIn=*/4, @@ -408,14 +394,11 @@ BOOST_AUTO_TEST_CASE(DoS_bantime) ConnectionType::INBOUND, /*inbound_onion=*/false}; dummyNode.SetCommonVersion(PROTOCOL_VERSION); - peerLogic->InitializeNode(&dummyNode); + peerLogic->InitializeNode(dummyNode, NODE_NETWORK); dummyNode.fSuccessfullyConnected = true; - peerLogic->Misbehaving(dummyNode.GetId(), DISCOURAGEMENT_THRESHOLD, /*message=*/""); - { - LOCK(dummyNode.cs_sendProcessing); - BOOST_CHECK(peerLogic->SendMessages(&dummyNode)); - } + peerLogic->UnitTestMisbehaving(dummyNode.GetId(), DISCOURAGEMENT_THRESHOLD); + BOOST_CHECK(peerLogic->SendMessages(&dummyNode)); BOOST_CHECK(banman->IsDiscouraged(addr)); peerLogic->FinalizeNode(dummyNode); diff --git a/src/test/descriptor_tests.cpp b/src/test/descriptor_tests.cpp index 63c86a896d..6b2ef74e19 100644 --- a/src/test/descriptor_tests.cpp +++ b/src/test/descriptor_tests.cpp @@ -126,7 +126,7 @@ std::set<std::pair<CPubKey, KeyOriginInfo>> GetKeyOriginData(const FlatSigningPr return ret; } -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, +void DoCheck(const std::string& prv, const std::string& pub, 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; @@ -141,14 +141,13 @@ void DoCheck(const std::string& prv, const std::string& pub, const std::string& } else { parse_priv = Parse(prv, keys_priv, error); } + BOOST_CHECK_MESSAGE(parse_priv, error); if (replace_apostrophe_with_h_in_pub) { parse_pub = Parse(UseHInsteadOfApostrophe(pub), keys_pub, error); } else { parse_pub = Parse(pub, keys_pub, error); } - - BOOST_CHECK(parse_priv); - BOOST_CHECK(parse_pub); + BOOST_CHECK_MESSAGE(parse_pub, error); // Check that the correct OutputType is inferred BOOST_CHECK(parse_priv->GetOutputType() == type); @@ -161,8 +160,8 @@ void DoCheck(const std::string& prv, const std::string& pub, const std::string& // Check that both versions serialize back to the public version. std::string pub1 = parse_priv->ToString(); std::string pub2 = parse_pub->ToString(); - BOOST_CHECK(EqualDescriptor(pub, pub1)); - BOOST_CHECK(EqualDescriptor(pub, pub2)); + BOOST_CHECK_MESSAGE(EqualDescriptor(pub, pub1), "Private ser: " + pub1 + " Public desc: " + pub); + BOOST_CHECK_MESSAGE(EqualDescriptor(pub, pub2), "Public ser: " + pub2 + " Public desc: " + pub); // Check that both can be serialized with private key back to the private version, but not without private key. if (!(flags & MISSING_PRIVKEYS)) { @@ -234,7 +233,7 @@ void DoCheck(const std::string& prv, const std::string& pub, const std::string& for (const auto& xpub_pair : parent_xpub_cache) { const CExtPubKey& xpub = xpub_pair.second; CExtPubKey der; - xpub.Derive(der, i); + BOOST_CHECK(xpub.Derive(der, i)); pubkeys.insert(der.pubkey); } int count_pks = 0; @@ -266,7 +265,7 @@ void DoCheck(const std::string& prv, const std::string& pub, const std::string& const CExtPubKey& xpub = xpub_pair.second; pubkeys.insert(xpub.pubkey); CExtPubKey der; - xpub.Derive(der, i); + BOOST_CHECK(xpub.Derive(der, i)); pubkeys.insert(der.pubkey); } int count_pks = 0; @@ -303,7 +302,6 @@ void DoCheck(const std::string& prv, const std::string& pub, const std::string& // For each of the produced scripts, verify solvability, and when possible, try to sign a transaction spending it. for (size_t n = 0; n < spks.size(); ++n) { BOOST_CHECK_EQUAL(ref[n], HexStr(spks[n])); - BOOST_CHECK_EQUAL(IsSolvable(Merge(key_provider, script_provider), spks[n]), (flags & UNSOLVABLE) == 0); if (flags & SIGNABLE) { CMutableTransaction spend; @@ -314,7 +312,7 @@ void DoCheck(const std::string& prv, const std::string& pub, const std::string& txdata.Init(spend, std::move(utxos), /*force=*/true); MutableTransactionSignatureCreator creator{spend, 0, CAmount{0}, &txdata, SIGHASH_DEFAULT}; SignatureData sigdata; - BOOST_CHECK_MESSAGE(ProduceSignature(Merge(keys_priv, script_provider), creator, spks[n], sigdata), prv); + BOOST_CHECK_MESSAGE(ProduceSignature(FlatSigningProvider{keys_priv}.Merge(FlatSigningProvider{script_provider}), creator, spks[n], sigdata), prv); } /* Infer a descriptor from the generated script, and verify its solvability and that it roundtrips. */ @@ -325,7 +323,7 @@ void DoCheck(const std::string& prv, const std::string& pub, const std::string& BOOST_CHECK(inferred->Expand(0, provider_inferred, spks_inferred, provider_inferred)); BOOST_CHECK_EQUAL(spks_inferred.size(), 1U); BOOST_CHECK(spks_inferred[0] == spks[n]); - BOOST_CHECK_EQUAL(IsSolvable(provider_inferred, spks_inferred[0]), !(flags & UNSOLVABLE)); + BOOST_CHECK_EQUAL(InferDescriptor(spks_inferred[0], provider_inferred)->IsSolvable(), !(flags & UNSOLVABLE)); BOOST_CHECK(GetKeyOriginData(provider_inferred, flags) == GetKeyOriginData(script_provider, flags)); } @@ -342,29 +340,29 @@ 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 std::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_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; // Do not replace apostrophes with 'h' in prv and pub - DoCheck(prv, pub, norm_prv, norm_pub, flags, scripts, type, paths); + DoCheck(prv, pub, norm_pub, flags, scripts, type, paths); // Replace apostrophes with 'h' in prv but not in pub, if apostrophes are found in prv if (prv.find('\'') != std::string::npos) { found_apostrophes_in_prv = true; - DoCheck(prv, pub, norm_prv, norm_pub, flags, scripts, type, paths, /* replace_apostrophe_with_h_in_prv = */true, /*replace_apostrophe_with_h_in_pub = */false); + DoCheck(prv, pub, norm_pub, flags, scripts, type, paths, /* replace_apostrophe_with_h_in_prv = */true, /*replace_apostrophe_with_h_in_pub = */false); } // Replace apostrophes with 'h' in pub but not in prv, if apostrophes are found in pub if (pub.find('\'') != std::string::npos) { found_apostrophes_in_pub = true; - DoCheck(prv, pub, norm_prv, norm_pub, flags, scripts, type, paths, /* replace_apostrophe_with_h_in_prv = */false, /*replace_apostrophe_with_h_in_pub = */true); + DoCheck(prv, pub, norm_pub, flags, scripts, type, paths, /* replace_apostrophe_with_h_in_prv = */false, /*replace_apostrophe_with_h_in_pub = */true); } // Replace apostrophes with 'h' both in prv and in pub, if apostrophes are found in both if (found_apostrophes_in_prv && found_apostrophes_in_pub) { - DoCheck(prv, pub, norm_prv, norm_pub, flags, scripts, type, paths, /* replace_apostrophe_with_h_in_prv = */true, /*replace_apostrophe_with_h_in_pub = */true); + DoCheck(prv, pub, norm_pub, flags, scripts, type, paths, /* replace_apostrophe_with_h_in_prv = */true, /*replace_apostrophe_with_h_in_pub = */true); } } @@ -375,60 +373,61 @@ 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"}}, 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); - Check("tr(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)", "tr(a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", "tr(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)", "tr(a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", SIGNABLE | XONLY_KEYS, {{"512077aab6e066f8a7419c5ab714c12c67d25007ed55a43cadcacb4d7a970a093f11"}}, OutputType::BECH32M); + Check("combo(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)", "combo(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", "combo(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", SIGNABLE, {{"2103a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bdac","76a9149a1c78a507689f6f54b847ad1cef1e614ee23f1e88ac","00149a1c78a507689f6f54b847ad1cef1e614ee23f1e","a91484ab21b1b2fd065d4504ff693d832434b6108d7b87"}}, std::nullopt); + Check("pk(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)", "pk(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", "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']03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", SIGNABLE, {{"76a9149a1c78a507689f6f54b847ad1cef1e614ee23f1e88ac"}}, OutputType::LEGACY, {{1,0x80000002UL,3,0x80000004UL}}); + Check("wpkh(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)", "wpkh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", "wpkh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", SIGNABLE, {{"00149a1c78a507689f6f54b847ad1cef1e614ee23f1e"}}, OutputType::BECH32); + Check("sh(wpkh(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1))", "sh(wpkh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd))", "sh(wpkh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd))", SIGNABLE, {{"a91484ab21b1b2fd065d4504ff693d832434b6108d7b87"}}, OutputType::P2SH_SEGWIT); + Check("tr(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)", "tr(a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", "tr(a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", SIGNABLE | XONLY_KEYS, {{"512077aab6e066f8a7419c5ab714c12c67d25007ed55a43cadcacb4d7a970a093f11"}}, OutputType::BECH32M); CheckUnparsable("sh(wpkh(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY2))", "sh(wpkh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5))", "wpkh(): Pubkey '03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5' is invalid"); // Invalid pubkey CheckUnparsable("pkh(deadbeef/1/2'/3/4']L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)", "pkh(deadbeef/1/2'/3/4']03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", "pkh(): Key origin start '[ character expected but not found, got 'd' instead"); // Missing start bracket in key origin CheckUnparsable("pkh([deadbeef]/1/2'/3/4']L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)", "pkh([deadbeef]/1/2'/3/4']03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", "pkh(): 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"}}, 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); + Check("combo(5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss)", "combo(04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235)", "combo(04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235)",SIGNABLE, {{"4104a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235ac","76a914b5bd079c4d57cc7fc28ecf8213a6b791625b818388ac"}}, std::nullopt); + Check("pk(5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss)", "pk(04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235)", "pk(04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235)", SIGNABLE, {{"4104a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235ac"}}, std::nullopt); + Check("pkh(5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss)", "pkh(04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235)", "pkh(04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235)", SIGNABLE, {{"76a914b5bd079c4d57cc7fc28ecf8213a6b791625b818388ac"}}, OutputType::LEGACY); CheckUnparsable("wpkh(5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss)", "wpkh(04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235)", "wpkh(): Uncompressed keys are not allowed"); // No uncompressed keys in witness CheckUnparsable("wsh(pk(5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss))", "wsh(pk(04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235))", "pk(): Uncompressed keys are not allowed"); // No uncompressed keys in witness CheckUnparsable("sh(wpkh(5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss))", "sh(wpkh(04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235))", "wpkh(): Uncompressed keys are not allowed"); // No uncompressed keys in witness // Some unconventional single-key constructions - Check("sh(pk(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1))", "sh(pk(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd))", "sh(pk(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1))", "sh(pk(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd))", SIGNABLE, {{"a9141857af51a5e516552b3086430fd8ce55f7c1a52487"}}, OutputType::LEGACY); - Check("sh(pkh(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1))", "sh(pkh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd))", "sh(pkh(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1))", "sh(pkh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd))", SIGNABLE, {{"a9141a31ad23bf49c247dd531a623c2ef57da3c400c587"}}, OutputType::LEGACY); - Check("wsh(pk(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1))", "wsh(pk(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd))", "wsh(pk(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1))", "wsh(pk(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd))", SIGNABLE, {{"00202e271faa2325c199d25d22e1ead982e45b64eeb4f31e73dbdf41bd4b5fec23fa"}}, OutputType::BECH32); - Check("wsh(pkh(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1))", "wsh(pkh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd))", "wsh(pkh(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1))", "wsh(pkh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd))", SIGNABLE, {{"0020338e023079b91c58571b20e602d7805fb808c22473cbc391a41b1bd3a192e75b"}}, OutputType::BECH32); - Check("sh(wsh(pk(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)))", "sh(wsh(pk(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)))", "sh(wsh(pk(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)))", "sh(wsh(pk(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)))", SIGNABLE, {{"a91472d0c5a3bfad8c3e7bd5303a72b94240e80b6f1787"}}, OutputType::P2SH_SEGWIT); - Check("sh(wsh(pkh(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)))", "sh(wsh(pkh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)))", "sh(wsh(pkh(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)))", "sh(wsh(pkh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)))", SIGNABLE, {{"a914b61b92e2ca21bac1e72a3ab859a742982bea960a87"}}, OutputType::P2SH_SEGWIT); - Check("tr(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5,{pk(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5),{pk(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1),pk(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5)}})", "tr(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5,{pk(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5),{pk(a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd),pk(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5)}})", "tr(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5,{pk(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5),{pk(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1),pk(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5)}})", "tr(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5,{pk(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5),{pk(a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd),pk(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5)}})", XONLY_KEYS | SIGNABLE | MISSING_PRIVKEYS, {{"51201497ae16f30dacb88523ed9301bff17773b609e8a90518a3f96ea328a47d1500"}}, OutputType::BECH32M); + Check("sh(pk(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1))", "sh(pk(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd))", "sh(pk(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd))", SIGNABLE, {{"a9141857af51a5e516552b3086430fd8ce55f7c1a52487"}}, OutputType::LEGACY); + Check("sh(pkh(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1))", "sh(pkh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd))", "sh(pkh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd))", SIGNABLE, {{"a9141a31ad23bf49c247dd531a623c2ef57da3c400c587"}}, OutputType::LEGACY); + Check("wsh(pk(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1))", "wsh(pk(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd))", "wsh(pk(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd))", SIGNABLE, {{"00202e271faa2325c199d25d22e1ead982e45b64eeb4f31e73dbdf41bd4b5fec23fa"}}, OutputType::BECH32); + Check("wsh(pkh(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1))", "wsh(pkh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd))", "wsh(pkh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd))", SIGNABLE, {{"0020338e023079b91c58571b20e602d7805fb808c22473cbc391a41b1bd3a192e75b"}}, OutputType::BECH32); + Check("sh(wsh(pk(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)))", "sh(wsh(pk(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)))", "sh(wsh(pk(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)))", SIGNABLE, {{"a91472d0c5a3bfad8c3e7bd5303a72b94240e80b6f1787"}}, OutputType::P2SH_SEGWIT); + Check("sh(wsh(pkh(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)))", "sh(wsh(pkh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)))", "sh(wsh(pkh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)))", SIGNABLE, {{"a914b61b92e2ca21bac1e72a3ab859a742982bea960a87"}}, OutputType::P2SH_SEGWIT); + Check("tr(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5,{pk(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5),{pk(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1),pk(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5)}})", "tr(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5,{pk(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5),{pk(a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd),pk(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5)}})", "tr(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5,{pk(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5),{pk(a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd),pk(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5)}})", XONLY_KEYS | SIGNABLE | MISSING_PRIVKEYS, {{"51201497ae16f30dacb88523ed9301bff17773b609e8a90518a3f96ea328a47d1500"}}, OutputType::BECH32M); // Versions with BIP32 derivations - 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"}}, std::nullopt, {{0}, {1}}); - Check("tr(xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc/0/*,pk(xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc/1/*))", "tr(xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/0/*,pk(xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/1/*))", "tr(xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc/0/*,pk(xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc/1/*))", "tr(xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/0/*,pk(xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/1/*))", XONLY_KEYS | RANGE, {{"512078bc707124daa551b65af74de2ec128b7525e10f374dc67b64e00ce0ab8b3e12"}, {"512001f0a02a17808c20134b78faab80ef93ffba82261ccef0a2314f5d62b6438f11"}, {"512021024954fcec88237a9386fce80ef2ced5f1e91b422b26c59ccfc174c8d1ad25"}}, OutputType::BECH32M, {{0, 0}, {0, 1}, {0, 2}, {1, 0}, {1, 1}, {1, 2}}); + Check("combo([01234567]xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc)", "combo([01234567]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL)", "combo([01234567]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL)", SIGNABLE, {{"2102d2b36900396c9282fa14628566582f206a5dd0bcc8d5e892611806cafb0301f0ac","76a91431a507b815593dfc51ffc7245ae7e5aee304246e88ac","001431a507b815593dfc51ffc7245ae7e5aee304246e","a9142aafb926eb247cb18240a7f4c07983ad1f37922687"}}, std::nullopt); + Check("pk(xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0)", "pk(xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0)", "pk(xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0)", DEFAULT, {{"210379e45b3cf75f9c5f9befd8e9506fb962f6a9d185ac87001ec44a8d3df8d4a9e3ac"}}, std::nullopt, {{0}}); + Check("pkh(xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U/2147483647'/0)", "pkh(xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/2147483647'/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']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(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(xpub6FHa3pjLCk84BayeJxFW2SP4XRrFd1JYnxeLeU8EqN3vDfZmbqBqaGJAyiLjTAwm6ZLRQUMv1ZACTj37sR62cfN7fe5JnJ7dh8zL4fiyLHV/*)", RANGE, {{"2102df12b7035bdac8e3bab862a3a83d06ea6b17b6753d52edecba9be46f5d09e076ac","76a914f90e3178ca25f2c808dc76624032d352fdbdfaf288ac","0014f90e3178ca25f2c808dc76624032d352fdbdfaf2","a91408f3ea8c68d4a7585bf9e8bda226723f70e445f087"},{"21032869a233c9adff9a994e4966e5b821fd5bac066da6c3112488dc52383b4a98ecac","76a914a8409d1b6dfb1ed2a3e8aa5e0ef2ff26b15b75b788ac","0014a8409d1b6dfb1ed2a3e8aa5e0ef2ff26b15b75b7","a91473e39884cb71ae4e5ac9739e9225026c99763e6687"}}, std::nullopt, {{0}, {1}}); + Check("tr(xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc/0/*,pk(xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc/1/*))", "tr(xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/0/*,pk(xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/1/*))", "tr(xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/0/*,pk(xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/1/*))", XONLY_KEYS | RANGE, {{"512078bc707124daa551b65af74de2ec128b7525e10f374dc67b64e00ce0ab8b3e12"}, {"512001f0a02a17808c20134b78faab80ef93ffba82261ccef0a2314f5d62b6438f11"}, {"512021024954fcec88237a9386fce80ef2ced5f1e91b422b26c59ccfc174c8d1ad25"}}, OutputType::BECH32M, {{0, 0}, {0, 1}, {0, 2}, {1, 0}, {1, 1}, {1, 2}}); // Mixed xpubs and const pubkeys - Check("wsh(multi(1,xprvA2JDeKCSNNZky6uBCviVfJSKyQ1mDYahRjijr5idH2WwLsEd4Hsb2Tyh8RfQMuPh7f7RtyzTtdrbdqqsunu5Mm3wDvUAKRHSC34sJ7in334/0,L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1))","wsh(multi(1,xpub6FHa3pjLCk84BayeJxFW2SP4XRrFd1JYnxeLeU8EqN3vDfZmbqBqaGJAyiLjTAwm6ZLRQUMv1ZACTj37sR62cfN7fe5JnJ7dh8zL4fiyLHV/0,03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd))","wsh(multi(1,xprvA2JDeKCSNNZky6uBCviVfJSKyQ1mDYahRjijr5idH2WwLsEd4Hsb2Tyh8RfQMuPh7f7RtyzTtdrbdqqsunu5Mm3wDvUAKRHSC34sJ7in334/0,L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1))","wsh(multi(1,xpub6FHa3pjLCk84BayeJxFW2SP4XRrFd1JYnxeLeU8EqN3vDfZmbqBqaGJAyiLjTAwm6ZLRQUMv1ZACTj37sR62cfN7fe5JnJ7dh8zL4fiyLHV/0,03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd))", MIXED_PUBKEYS, {{"0020cb155486048b23a6da976d4c6fe071a2dbc8a7b57aaf225b8955f2e2a27b5f00"}},OutputType::BECH32,{{0},{}}); + Check("wsh(multi(1,xprvA2JDeKCSNNZky6uBCviVfJSKyQ1mDYahRjijr5idH2WwLsEd4Hsb2Tyh8RfQMuPh7f7RtyzTtdrbdqqsunu5Mm3wDvUAKRHSC34sJ7in334/0,L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1))","wsh(multi(1,xpub6FHa3pjLCk84BayeJxFW2SP4XRrFd1JYnxeLeU8EqN3vDfZmbqBqaGJAyiLjTAwm6ZLRQUMv1ZACTj37sR62cfN7fe5JnJ7dh8zL4fiyLHV/0,03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd))","wsh(multi(1,xpub6FHa3pjLCk84BayeJxFW2SP4XRrFd1JYnxeLeU8EqN3vDfZmbqBqaGJAyiLjTAwm6ZLRQUMv1ZACTj37sR62cfN7fe5JnJ7dh8zL4fiyLHV/0,03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd))", MIXED_PUBKEYS, {{"0020cb155486048b23a6da976d4c6fe071a2dbc8a7b57aaf225b8955f2e2a27b5f00"}},OutputType::BECH32,{{0},{}}); // Mixed range xpubs and const pubkeys - Check("multi(1,xprvA2JDeKCSNNZky6uBCviVfJSKyQ1mDYahRjijr5idH2WwLsEd4Hsb2Tyh8RfQMuPh7f7RtyzTtdrbdqqsunu5Mm3wDvUAKRHSC34sJ7in334/*,L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)","multi(1,xpub6FHa3pjLCk84BayeJxFW2SP4XRrFd1JYnxeLeU8EqN3vDfZmbqBqaGJAyiLjTAwm6ZLRQUMv1ZACTj37sR62cfN7fe5JnJ7dh8zL4fiyLHV/*,03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)","multi(1,xprvA2JDeKCSNNZky6uBCviVfJSKyQ1mDYahRjijr5idH2WwLsEd4Hsb2Tyh8RfQMuPh7f7RtyzTtdrbdqqsunu5Mm3wDvUAKRHSC34sJ7in334/*,L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)","multi(1,xpub6FHa3pjLCk84BayeJxFW2SP4XRrFd1JYnxeLeU8EqN3vDfZmbqBqaGJAyiLjTAwm6ZLRQUMv1ZACTj37sR62cfN7fe5JnJ7dh8zL4fiyLHV/*,03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", RANGE | MIXED_PUBKEYS, {{"512102df12b7035bdac8e3bab862a3a83d06ea6b17b6753d52edecba9be46f5d09e0762103a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd52ae"},{"5121032869a233c9adff9a994e4966e5b821fd5bac066da6c3112488dc52383b4a98ec2103a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd52ae"},{"5121035d30b6c66dc1e036c45369da8287518cf7e0d6ed1e2b905171c605708f14ca032103a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd52ae"}}, std::nullopt,{{2},{1},{0},{}}); + Check("multi(1,xprvA2JDeKCSNNZky6uBCviVfJSKyQ1mDYahRjijr5idH2WwLsEd4Hsb2Tyh8RfQMuPh7f7RtyzTtdrbdqqsunu5Mm3wDvUAKRHSC34sJ7in334/*,L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)","multi(1,xpub6FHa3pjLCk84BayeJxFW2SP4XRrFd1JYnxeLeU8EqN3vDfZmbqBqaGJAyiLjTAwm6ZLRQUMv1ZACTj37sR62cfN7fe5JnJ7dh8zL4fiyLHV/*,03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)","multi(1,xpub6FHa3pjLCk84BayeJxFW2SP4XRrFd1JYnxeLeU8EqN3vDfZmbqBqaGJAyiLjTAwm6ZLRQUMv1ZACTj37sR62cfN7fe5JnJ7dh8zL4fiyLHV/*,03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", RANGE | MIXED_PUBKEYS, {{"512102df12b7035bdac8e3bab862a3a83d06ea6b17b6753d52edecba9be46f5d09e0762103a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd52ae"},{"5121032869a233c9adff9a994e4966e5b821fd5bac066da6c3112488dc52383b4a98ec2103a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd52ae"},{"5121035d30b6c66dc1e036c45369da8287518cf7e0d6ed1e2b905171c605708f14ca032103a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd52ae"}}, std::nullopt,{{2},{1},{0},{}}); CheckUnparsable("combo([012345678]xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc)", "combo([012345678]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL)", "combo(): Fingerprint is not 4 bytes (9 characters instead of 8 characters)"); // Too long key fingerprint CheckUnparsable("pkh(xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U/2147483648)", "pkh(xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/2147483648)", "pkh(): Key path value 2147483648 is out of range"); // BIP 32 path element overflow CheckUnparsable("pkh(xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U/1aa)", "pkh(xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/1aa)", "pkh(): 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}}); + Check("pkh([01234567/10/20]xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U/2147483647'/0)", "pkh([01234567/10/20]xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/2147483647'/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"}}, 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"}}, 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); - Check("tr(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1,pk(KzoAz5CanayRKex3fSLQ2BwJpN7U52gZvxMyk78nDMHuqrUxuSJy))", "tr(a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd,pk(669b8afcec803a0d323e9a17f3ea8e68e8abe5a278020a929adbec52421adbd0))", "tr(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1,pk(KzoAz5CanayRKex3fSLQ2BwJpN7U52gZvxMyk78nDMHuqrUxuSJy))", "tr(a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd,pk(669b8afcec803a0d323e9a17f3ea8e68e8abe5a278020a929adbec52421adbd0))", SIGNABLE | XONLY_KEYS, {{"512017cf18db381d836d8923b1bdb246cfcd818da1a9f0e6e7907f187f0b2f937754"}}, OutputType::BECH32M); + Check("multi(1,L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1,5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss)", "multi(1,03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd,04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235)", "multi(1,03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd,04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235)", SIGNABLE, {{"512103a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd4104a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea23552ae"}}, std::nullopt); + Check("sortedmulti(1,L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1,5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss)", "sortedmulti(1,03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd,04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235)", "sortedmulti(1,03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd,04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235)", SIGNABLE, {{"512103a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd4104a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea23552ae"}}, std::nullopt); + Check("sortedmulti(1,5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss,L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)", "sortedmulti(1,04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235,03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", "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]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,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']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,03669b8afcec803a0d323e9a17f3ea8e68e8abe5a278020a929adbec52421adbd0,0260b2003c386519fc9eadf2b5cf124dd8eea4c4e68d5e154050a9346ea98ce600,0362a74e399c39ed5593852a30147f2959b56bb827dfa3e60e464b02ccf87dc5e8,0261345b53de74a4d721ef877c255429961b7e43714171ac06168d7e08c542a8b8,02da72e8b46901a65d4374fe6315538d8f368557dda3a1dcf9ea903f3afe7314c8,0318c82dd0b53fd3a932d16e0ba9e278fcc937c582d5781be626ff16e201f72286,0297ccef1ef99f9d73dec9ad37476ddb232f1238aff877af19e72ba04493361009,02e502cfd5c3f972fe9a3e2a18827820638f96b6f347e54d63deb839011fd5765d,03e687710f0e3ebe81c1037074da939d409c0025f17eb86adb9427d28f0f7ae0e9,02c04d3a5274952acdbc76987f3184b346a483d43be40874624b29e3692c1df5af,02ed06e0f418b5b43a7ec01d1d7d27290fa15f75771cb69b642a51471c29c84acd,036d46073cbb9ffee90473f3da429abc8de7f8751199da44485682a989a4bebb24,02f5d1ff7c9029a80a4e36b9a5497027ef7f3e73384a4a94fbfe7c4e9164eec8bc,02e41deffd1b7cce11cde209a781adcffdabd1b91c0ba0375857a2bfd9302419f3,02d76625f7956a7fc505ab02556c23ee72d832f1bac391bcd2d3abce5710a13d06,0399eb0a5487515802dc14544cf10b3666623762fbed2ec38a3975716e2c29c232)))", SIGNABLE, {{"a9147fc63e13dc25e8a95a3cee3d9a714ac3afd96f1e87"}}, OutputType::P2SH_SEGWIT); + Check("tr(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1,pk(KzoAz5CanayRKex3fSLQ2BwJpN7U52gZvxMyk78nDMHuqrUxuSJy))", "tr(a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd,pk(669b8afcec803a0d323e9a17f3ea8e68e8abe5a278020a929adbec52421adbd0))", "tr(a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd,pk(669b8afcec803a0d323e9a17f3ea8e68e8abe5a278020a929adbec52421adbd0))", SIGNABLE | XONLY_KEYS, {{"512017cf18db381d836d8923b1bdb246cfcd818da1a9f0e6e7907f187f0b2f937754"}}, OutputType::BECH32M); 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 CheckUnparsable("wsh(multi(2,[aaaaaaaa][aaaaaaaa]xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U/2147483647'/0,xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt/1/2/*,xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi/10/20/30/40/*'))", "wsh(multi(2,[aaaaaaaa][aaaaaaaa]xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/2147483647'/0,xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/1/2/*,xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8/10/20/30/40/*'))", "Multi: Multiple ']' characters found for a single pubkey"); // Double key origin descriptor CheckUnparsable("wsh(multi(2,[aaaagaaa]xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U/2147483647'/0,xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt/1/2/*,xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi/10/20/30/40/*'))", "wsh(multi(2,[aaagaaaa]xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/2147483647'/0,xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/1/2/*,xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8/10/20/30/40/*'))", "Multi: Fingerprint 'aaagaaaa' is not hex"); // Non hex fingerprint @@ -440,8 +439,8 @@ BOOST_AUTO_TEST_CASE(descriptor_test) CheckUnparsable("multi(3,L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1,5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss)", "multi(3,03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd,04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235)", "Multisig threshold cannot be larger than the number of keys; threshold is 3 but only 2 keys specified"); // Threshold larger than number of keys CheckUnparsable("multi(3,KzoAz5CanayRKex3fSLQ2BwJpN7U52gZvxMyk78nDMHuqrUxuSJy,KwGNz6YCCQtYvFzMtrC6D3tKTKdBBboMrLTsjr2NYVBwapCkn7Mr,KxogYhiNfwxuswvXV66eFyKcCpm7dZ7TqHVqujHAVUjJxyivxQ9X,L2BUNduTSyZwZjwNHynQTF14mv2uz2NRq5n5sYWTb4FkkmqgEE9f)", "multi(3,03669b8afcec803a0d323e9a17f3ea8e68e8abe5a278020a929adbec52421adbd0,0260b2003c386519fc9eadf2b5cf124dd8eea4c4e68d5e154050a9346ea98ce600,0362a74e399c39ed5593852a30147f2959b56bb827dfa3e60e464b02ccf87dc5e8,0261345b53de74a4d721ef877c255429961b7e43714171ac06168d7e08c542a8b8)", "Cannot have 4 pubkeys in bare multisig; only at most 3 pubkeys"); // Threshold larger than number of keys CheckUnparsable("sh(multi(16,KzoAz5CanayRKex3fSLQ2BwJpN7U52gZvxMyk78nDMHuqrUxuSJy,KwGNz6YCCQtYvFzMtrC6D3tKTKdBBboMrLTsjr2NYVBwapCkn7Mr,KxogYhiNfwxuswvXV66eFyKcCpm7dZ7TqHVqujHAVUjJxyivxQ9X,L2BUNduTSyZwZjwNHynQTF14mv2uz2NRq5n5sYWTb4FkkmqgEE9f,L1okJGHGn1kFjdXHKxXjwVVtmCMR2JA5QsbKCSpSb7ReQjezKeoD,KxDCNSST75HFPaW5QKpzHtAyaCQC7p9Vo3FYfi2u4dXD1vgMiboK,L5edQjFtnkcf5UWURn6UuuoFrabgDQUHdheKCziwN42aLwS3KizU,KzF8UWFcEC7BYTq8Go1xVimMkDmyNYVmXV5PV7RuDicvAocoPB8i,L3nHUboKG2w4VSJ5jYZ5CBM97oeK6YuKvfZxrefdShECcjEYKMWZ,KyjHo36dWkYhimKmVVmQTq3gERv3pnqA4xFCpvUgbGDJad7eS8WE,KwsfyHKRUTZPQtysN7M3tZ4GXTnuov5XRgjdF2XCG8faAPmFruRF,KzCUbGhN9LJhdeFfL9zQgTJMjqxdBKEekRGZX24hXdgCNCijkkap,KzgpMBwwsDLwkaC5UrmBgCYaBD2WgZ7PBoGYXR8KT7gCA9UTN5a3,KyBXTPy4T7YG4q9tcAM3LkvfRpD1ybHMvcJ2ehaWXaSqeGUxEdkP,KzJDe9iwJRPtKP2F2AoN6zBgzS7uiuAwhWCfGdNeYJ3PC1HNJ8M8,L1xbHrxynrqLKkoYc4qtoQPx6uy5qYXR5ZDYVYBSRmCV5piU3JG9,L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1))","sh(multi(16,03669b8afcec803a0d323e9a17f3ea8e68e8abe5a278020a929adbec52421adbd0,0260b2003c386519fc9eadf2b5cf124dd8eea4c4e68d5e154050a9346ea98ce600,0362a74e399c39ed5593852a30147f2959b56bb827dfa3e60e464b02ccf87dc5e8,0261345b53de74a4d721ef877c255429961b7e43714171ac06168d7e08c542a8b8,02da72e8b46901a65d4374fe6315538d8f368557dda3a1dcf9ea903f3afe7314c8,0318c82dd0b53fd3a932d16e0ba9e278fcc937c582d5781be626ff16e201f72286,0297ccef1ef99f9d73dec9ad37476ddb232f1238aff877af19e72ba04493361009,02e502cfd5c3f972fe9a3e2a18827820638f96b6f347e54d63deb839011fd5765d,03e687710f0e3ebe81c1037074da939d409c0025f17eb86adb9427d28f0f7ae0e9,02c04d3a5274952acdbc76987f3184b346a483d43be40874624b29e3692c1df5af,02ed06e0f418b5b43a7ec01d1d7d27290fa15f75771cb69b642a51471c29c84acd,036d46073cbb9ffee90473f3da429abc8de7f8751199da44485682a989a4bebb24,02f5d1ff7c9029a80a4e36b9a5497027ef7f3e73384a4a94fbfe7c4e9164eec8bc,02e41deffd1b7cce11cde209a781adcffdabd1b91c0ba0375857a2bfd9302419f3,02d76625f7956a7fc505ab02556c23ee72d832f1bac391bcd2d3abce5710a13d06,0399eb0a5487515802dc14544cf10b3666623762fbed2ec38a3975716e2c29c232,03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd))", "P2SH script is too large, 581 bytes is larger than 520 bytes"); // Cannot have more than 15 keys in a P2SH multisig, or we exceed maximum push size - Check("wsh(multi(20,KzoAz5CanayRKex3fSLQ2BwJpN7U52gZvxMyk78nDMHuqrUxuSJy,KwGNz6YCCQtYvFzMtrC6D3tKTKdBBboMrLTsjr2NYVBwapCkn7Mr,KxogYhiNfwxuswvXV66eFyKcCpm7dZ7TqHVqujHAVUjJxyivxQ9X,L2BUNduTSyZwZjwNHynQTF14mv2uz2NRq5n5sYWTb4FkkmqgEE9f,L1okJGHGn1kFjdXHKxXjwVVtmCMR2JA5QsbKCSpSb7ReQjezKeoD,KxDCNSST75HFPaW5QKpzHtAyaCQC7p9Vo3FYfi2u4dXD1vgMiboK,L5edQjFtnkcf5UWURn6UuuoFrabgDQUHdheKCziwN42aLwS3KizU,KzF8UWFcEC7BYTq8Go1xVimMkDmyNYVmXV5PV7RuDicvAocoPB8i,L3nHUboKG2w4VSJ5jYZ5CBM97oeK6YuKvfZxrefdShECcjEYKMWZ,KyjHo36dWkYhimKmVVmQTq3gERv3pnqA4xFCpvUgbGDJad7eS8WE,KwsfyHKRUTZPQtysN7M3tZ4GXTnuov5XRgjdF2XCG8faAPmFruRF,KzCUbGhN9LJhdeFfL9zQgTJMjqxdBKEekRGZX24hXdgCNCijkkap,KzgpMBwwsDLwkaC5UrmBgCYaBD2WgZ7PBoGYXR8KT7gCA9UTN5a3,KyBXTPy4T7YG4q9tcAM3LkvfRpD1ybHMvcJ2ehaWXaSqeGUxEdkP,KzJDe9iwJRPtKP2F2AoN6zBgzS7uiuAwhWCfGdNeYJ3PC1HNJ8M8,L1xbHrxynrqLKkoYc4qtoQPx6uy5qYXR5ZDYVYBSRmCV5piU3JG9,KzRedjSwMggebB3VufhbzpYJnvHfHe9kPJSjCU5QpJdAW3NSZxYS,Kyjtp5858xL7JfeV4PNRCKy2t6XvgqNNepArGY9F9F1SSPqNEMs3,L2D4RLHPiHBidkHS8ftx11jJk1hGFELvxh8LoxNQheaGT58dKenW,KyLPZdwY4td98bKkXqEXTEBX3vwEYTQo1yyLjX2jKXA63GBpmSjv))","wsh(multi(20,03669b8afcec803a0d323e9a17f3ea8e68e8abe5a278020a929adbec52421adbd0,0260b2003c386519fc9eadf2b5cf124dd8eea4c4e68d5e154050a9346ea98ce600,0362a74e399c39ed5593852a30147f2959b56bb827dfa3e60e464b02ccf87dc5e8,0261345b53de74a4d721ef877c255429961b7e43714171ac06168d7e08c542a8b8,02da72e8b46901a65d4374fe6315538d8f368557dda3a1dcf9ea903f3afe7314c8,0318c82dd0b53fd3a932d16e0ba9e278fcc937c582d5781be626ff16e201f72286,0297ccef1ef99f9d73dec9ad37476ddb232f1238aff877af19e72ba04493361009,02e502cfd5c3f972fe9a3e2a18827820638f96b6f347e54d63deb839011fd5765d,03e687710f0e3ebe81c1037074da939d409c0025f17eb86adb9427d28f0f7ae0e9,02c04d3a5274952acdbc76987f3184b346a483d43be40874624b29e3692c1df5af,02ed06e0f418b5b43a7ec01d1d7d27290fa15f75771cb69b642a51471c29c84acd,036d46073cbb9ffee90473f3da429abc8de7f8751199da44485682a989a4bebb24,02f5d1ff7c9029a80a4e36b9a5497027ef7f3e73384a4a94fbfe7c4e9164eec8bc,02e41deffd1b7cce11cde209a781adcffdabd1b91c0ba0375857a2bfd9302419f3,02d76625f7956a7fc505ab02556c23ee72d832f1bac391bcd2d3abce5710a13d06,0399eb0a5487515802dc14544cf10b3666623762fbed2ec38a3975716e2c29c232,02bc2feaa536991d269aae46abb8f3772a5b3ad592314945e51543e7da84c4af6e,0318bf32e5217c1eb771a6d5ce1cd39395dff7ff665704f175c9a5451d95a2f2ca,02c681a6243f16208c2004bb81f5a8a67edfdd3e3711534eadeec3dcf0b010c759,0249fdd6b69768b8d84b4893f8ff84b36835c50183de20fcae8f366a45290d01fd))", "wsh(multi(20,KzoAz5CanayRKex3fSLQ2BwJpN7U52gZvxMyk78nDMHuqrUxuSJy,KwGNz6YCCQtYvFzMtrC6D3tKTKdBBboMrLTsjr2NYVBwapCkn7Mr,KxogYhiNfwxuswvXV66eFyKcCpm7dZ7TqHVqujHAVUjJxyivxQ9X,L2BUNduTSyZwZjwNHynQTF14mv2uz2NRq5n5sYWTb4FkkmqgEE9f,L1okJGHGn1kFjdXHKxXjwVVtmCMR2JA5QsbKCSpSb7ReQjezKeoD,KxDCNSST75HFPaW5QKpzHtAyaCQC7p9Vo3FYfi2u4dXD1vgMiboK,L5edQjFtnkcf5UWURn6UuuoFrabgDQUHdheKCziwN42aLwS3KizU,KzF8UWFcEC7BYTq8Go1xVimMkDmyNYVmXV5PV7RuDicvAocoPB8i,L3nHUboKG2w4VSJ5jYZ5CBM97oeK6YuKvfZxrefdShECcjEYKMWZ,KyjHo36dWkYhimKmVVmQTq3gERv3pnqA4xFCpvUgbGDJad7eS8WE,KwsfyHKRUTZPQtysN7M3tZ4GXTnuov5XRgjdF2XCG8faAPmFruRF,KzCUbGhN9LJhdeFfL9zQgTJMjqxdBKEekRGZX24hXdgCNCijkkap,KzgpMBwwsDLwkaC5UrmBgCYaBD2WgZ7PBoGYXR8KT7gCA9UTN5a3,KyBXTPy4T7YG4q9tcAM3LkvfRpD1ybHMvcJ2ehaWXaSqeGUxEdkP,KzJDe9iwJRPtKP2F2AoN6zBgzS7uiuAwhWCfGdNeYJ3PC1HNJ8M8,L1xbHrxynrqLKkoYc4qtoQPx6uy5qYXR5ZDYVYBSRmCV5piU3JG9,KzRedjSwMggebB3VufhbzpYJnvHfHe9kPJSjCU5QpJdAW3NSZxYS,Kyjtp5858xL7JfeV4PNRCKy2t6XvgqNNepArGY9F9F1SSPqNEMs3,L2D4RLHPiHBidkHS8ftx11jJk1hGFELvxh8LoxNQheaGT58dKenW,KyLPZdwY4td98bKkXqEXTEBX3vwEYTQo1yyLjX2jKXA63GBpmSjv))","wsh(multi(20,03669b8afcec803a0d323e9a17f3ea8e68e8abe5a278020a929adbec52421adbd0,0260b2003c386519fc9eadf2b5cf124dd8eea4c4e68d5e154050a9346ea98ce600,0362a74e399c39ed5593852a30147f2959b56bb827dfa3e60e464b02ccf87dc5e8,0261345b53de74a4d721ef877c255429961b7e43714171ac06168d7e08c542a8b8,02da72e8b46901a65d4374fe6315538d8f368557dda3a1dcf9ea903f3afe7314c8,0318c82dd0b53fd3a932d16e0ba9e278fcc937c582d5781be626ff16e201f72286,0297ccef1ef99f9d73dec9ad37476ddb232f1238aff877af19e72ba04493361009,02e502cfd5c3f972fe9a3e2a18827820638f96b6f347e54d63deb839011fd5765d,03e687710f0e3ebe81c1037074da939d409c0025f17eb86adb9427d28f0f7ae0e9,02c04d3a5274952acdbc76987f3184b346a483d43be40874624b29e3692c1df5af,02ed06e0f418b5b43a7ec01d1d7d27290fa15f75771cb69b642a51471c29c84acd,036d46073cbb9ffee90473f3da429abc8de7f8751199da44485682a989a4bebb24,02f5d1ff7c9029a80a4e36b9a5497027ef7f3e73384a4a94fbfe7c4e9164eec8bc,02e41deffd1b7cce11cde209a781adcffdabd1b91c0ba0375857a2bfd9302419f3,02d76625f7956a7fc505ab02556c23ee72d832f1bac391bcd2d3abce5710a13d06,0399eb0a5487515802dc14544cf10b3666623762fbed2ec38a3975716e2c29c232,02bc2feaa536991d269aae46abb8f3772a5b3ad592314945e51543e7da84c4af6e,0318bf32e5217c1eb771a6d5ce1cd39395dff7ff665704f175c9a5451d95a2f2ca,02c681a6243f16208c2004bb81f5a8a67edfdd3e3711534eadeec3dcf0b010c759,0249fdd6b69768b8d84b4893f8ff84b36835c50183de20fcae8f366a45290d01fd))", SIGNABLE, {{"0020376bd8344b8b6ebe504ff85ef743eaa1aa9272178223bcb6887e9378efb341ac"}}, OutputType::BECH32); // In P2WSH we can have up to 20 keys -Check("sh(wsh(multi(20,KzoAz5CanayRKex3fSLQ2BwJpN7U52gZvxMyk78nDMHuqrUxuSJy,KwGNz6YCCQtYvFzMtrC6D3tKTKdBBboMrLTsjr2NYVBwapCkn7Mr,KxogYhiNfwxuswvXV66eFyKcCpm7dZ7TqHVqujHAVUjJxyivxQ9X,L2BUNduTSyZwZjwNHynQTF14mv2uz2NRq5n5sYWTb4FkkmqgEE9f,L1okJGHGn1kFjdXHKxXjwVVtmCMR2JA5QsbKCSpSb7ReQjezKeoD,KxDCNSST75HFPaW5QKpzHtAyaCQC7p9Vo3FYfi2u4dXD1vgMiboK,L5edQjFtnkcf5UWURn6UuuoFrabgDQUHdheKCziwN42aLwS3KizU,KzF8UWFcEC7BYTq8Go1xVimMkDmyNYVmXV5PV7RuDicvAocoPB8i,L3nHUboKG2w4VSJ5jYZ5CBM97oeK6YuKvfZxrefdShECcjEYKMWZ,KyjHo36dWkYhimKmVVmQTq3gERv3pnqA4xFCpvUgbGDJad7eS8WE,KwsfyHKRUTZPQtysN7M3tZ4GXTnuov5XRgjdF2XCG8faAPmFruRF,KzCUbGhN9LJhdeFfL9zQgTJMjqxdBKEekRGZX24hXdgCNCijkkap,KzgpMBwwsDLwkaC5UrmBgCYaBD2WgZ7PBoGYXR8KT7gCA9UTN5a3,KyBXTPy4T7YG4q9tcAM3LkvfRpD1ybHMvcJ2ehaWXaSqeGUxEdkP,KzJDe9iwJRPtKP2F2AoN6zBgzS7uiuAwhWCfGdNeYJ3PC1HNJ8M8,L1xbHrxynrqLKkoYc4qtoQPx6uy5qYXR5ZDYVYBSRmCV5piU3JG9,KzRedjSwMggebB3VufhbzpYJnvHfHe9kPJSjCU5QpJdAW3NSZxYS,Kyjtp5858xL7JfeV4PNRCKy2t6XvgqNNepArGY9F9F1SSPqNEMs3,L2D4RLHPiHBidkHS8ftx11jJk1hGFELvxh8LoxNQheaGT58dKenW,KyLPZdwY4td98bKkXqEXTEBX3vwEYTQo1yyLjX2jKXA63GBpmSjv)))","sh(wsh(multi(20,03669b8afcec803a0d323e9a17f3ea8e68e8abe5a278020a929adbec52421adbd0,0260b2003c386519fc9eadf2b5cf124dd8eea4c4e68d5e154050a9346ea98ce600,0362a74e399c39ed5593852a30147f2959b56bb827dfa3e60e464b02ccf87dc5e8,0261345b53de74a4d721ef877c255429961b7e43714171ac06168d7e08c542a8b8,02da72e8b46901a65d4374fe6315538d8f368557dda3a1dcf9ea903f3afe7314c8,0318c82dd0b53fd3a932d16e0ba9e278fcc937c582d5781be626ff16e201f72286,0297ccef1ef99f9d73dec9ad37476ddb232f1238aff877af19e72ba04493361009,02e502cfd5c3f972fe9a3e2a18827820638f96b6f347e54d63deb839011fd5765d,03e687710f0e3ebe81c1037074da939d409c0025f17eb86adb9427d28f0f7ae0e9,02c04d3a5274952acdbc76987f3184b346a483d43be40874624b29e3692c1df5af,02ed06e0f418b5b43a7ec01d1d7d27290fa15f75771cb69b642a51471c29c84acd,036d46073cbb9ffee90473f3da429abc8de7f8751199da44485682a989a4bebb24,02f5d1ff7c9029a80a4e36b9a5497027ef7f3e73384a4a94fbfe7c4e9164eec8bc,02e41deffd1b7cce11cde209a781adcffdabd1b91c0ba0375857a2bfd9302419f3,02d76625f7956a7fc505ab02556c23ee72d832f1bac391bcd2d3abce5710a13d06,0399eb0a5487515802dc14544cf10b3666623762fbed2ec38a3975716e2c29c232,02bc2feaa536991d269aae46abb8f3772a5b3ad592314945e51543e7da84c4af6e,0318bf32e5217c1eb771a6d5ce1cd39395dff7ff665704f175c9a5451d95a2f2ca,02c681a6243f16208c2004bb81f5a8a67edfdd3e3711534eadeec3dcf0b010c759,0249fdd6b69768b8d84b4893f8ff84b36835c50183de20fcae8f366a45290d01fd)))", "sh(wsh(multi(20,KzoAz5CanayRKex3fSLQ2BwJpN7U52gZvxMyk78nDMHuqrUxuSJy,KwGNz6YCCQtYvFzMtrC6D3tKTKdBBboMrLTsjr2NYVBwapCkn7Mr,KxogYhiNfwxuswvXV66eFyKcCpm7dZ7TqHVqujHAVUjJxyivxQ9X,L2BUNduTSyZwZjwNHynQTF14mv2uz2NRq5n5sYWTb4FkkmqgEE9f,L1okJGHGn1kFjdXHKxXjwVVtmCMR2JA5QsbKCSpSb7ReQjezKeoD,KxDCNSST75HFPaW5QKpzHtAyaCQC7p9Vo3FYfi2u4dXD1vgMiboK,L5edQjFtnkcf5UWURn6UuuoFrabgDQUHdheKCziwN42aLwS3KizU,KzF8UWFcEC7BYTq8Go1xVimMkDmyNYVmXV5PV7RuDicvAocoPB8i,L3nHUboKG2w4VSJ5jYZ5CBM97oeK6YuKvfZxrefdShECcjEYKMWZ,KyjHo36dWkYhimKmVVmQTq3gERv3pnqA4xFCpvUgbGDJad7eS8WE,KwsfyHKRUTZPQtysN7M3tZ4GXTnuov5XRgjdF2XCG8faAPmFruRF,KzCUbGhN9LJhdeFfL9zQgTJMjqxdBKEekRGZX24hXdgCNCijkkap,KzgpMBwwsDLwkaC5UrmBgCYaBD2WgZ7PBoGYXR8KT7gCA9UTN5a3,KyBXTPy4T7YG4q9tcAM3LkvfRpD1ybHMvcJ2ehaWXaSqeGUxEdkP,KzJDe9iwJRPtKP2F2AoN6zBgzS7uiuAwhWCfGdNeYJ3PC1HNJ8M8,L1xbHrxynrqLKkoYc4qtoQPx6uy5qYXR5ZDYVYBSRmCV5piU3JG9,KzRedjSwMggebB3VufhbzpYJnvHfHe9kPJSjCU5QpJdAW3NSZxYS,Kyjtp5858xL7JfeV4PNRCKy2t6XvgqNNepArGY9F9F1SSPqNEMs3,L2D4RLHPiHBidkHS8ftx11jJk1hGFELvxh8LoxNQheaGT58dKenW,KyLPZdwY4td98bKkXqEXTEBX3vwEYTQo1yyLjX2jKXA63GBpmSjv)))","sh(wsh(multi(20,03669b8afcec803a0d323e9a17f3ea8e68e8abe5a278020a929adbec52421adbd0,0260b2003c386519fc9eadf2b5cf124dd8eea4c4e68d5e154050a9346ea98ce600,0362a74e399c39ed5593852a30147f2959b56bb827dfa3e60e464b02ccf87dc5e8,0261345b53de74a4d721ef877c255429961b7e43714171ac06168d7e08c542a8b8,02da72e8b46901a65d4374fe6315538d8f368557dda3a1dcf9ea903f3afe7314c8,0318c82dd0b53fd3a932d16e0ba9e278fcc937c582d5781be626ff16e201f72286,0297ccef1ef99f9d73dec9ad37476ddb232f1238aff877af19e72ba04493361009,02e502cfd5c3f972fe9a3e2a18827820638f96b6f347e54d63deb839011fd5765d,03e687710f0e3ebe81c1037074da939d409c0025f17eb86adb9427d28f0f7ae0e9,02c04d3a5274952acdbc76987f3184b346a483d43be40874624b29e3692c1df5af,02ed06e0f418b5b43a7ec01d1d7d27290fa15f75771cb69b642a51471c29c84acd,036d46073cbb9ffee90473f3da429abc8de7f8751199da44485682a989a4bebb24,02f5d1ff7c9029a80a4e36b9a5497027ef7f3e73384a4a94fbfe7c4e9164eec8bc,02e41deffd1b7cce11cde209a781adcffdabd1b91c0ba0375857a2bfd9302419f3,02d76625f7956a7fc505ab02556c23ee72d832f1bac391bcd2d3abce5710a13d06,0399eb0a5487515802dc14544cf10b3666623762fbed2ec38a3975716e2c29c232,02bc2feaa536991d269aae46abb8f3772a5b3ad592314945e51543e7da84c4af6e,0318bf32e5217c1eb771a6d5ce1cd39395dff7ff665704f175c9a5451d95a2f2ca,02c681a6243f16208c2004bb81f5a8a67edfdd3e3711534eadeec3dcf0b010c759,0249fdd6b69768b8d84b4893f8ff84b36835c50183de20fcae8f366a45290d01fd)))", SIGNABLE, {{"a914c2c9c510e9d7f92fd6131e94803a8d34a8ef675e87"}}, OutputType::P2SH_SEGWIT); // Even if it's wrapped into P2SH + Check("wsh(multi(20,KzoAz5CanayRKex3fSLQ2BwJpN7U52gZvxMyk78nDMHuqrUxuSJy,KwGNz6YCCQtYvFzMtrC6D3tKTKdBBboMrLTsjr2NYVBwapCkn7Mr,KxogYhiNfwxuswvXV66eFyKcCpm7dZ7TqHVqujHAVUjJxyivxQ9X,L2BUNduTSyZwZjwNHynQTF14mv2uz2NRq5n5sYWTb4FkkmqgEE9f,L1okJGHGn1kFjdXHKxXjwVVtmCMR2JA5QsbKCSpSb7ReQjezKeoD,KxDCNSST75HFPaW5QKpzHtAyaCQC7p9Vo3FYfi2u4dXD1vgMiboK,L5edQjFtnkcf5UWURn6UuuoFrabgDQUHdheKCziwN42aLwS3KizU,KzF8UWFcEC7BYTq8Go1xVimMkDmyNYVmXV5PV7RuDicvAocoPB8i,L3nHUboKG2w4VSJ5jYZ5CBM97oeK6YuKvfZxrefdShECcjEYKMWZ,KyjHo36dWkYhimKmVVmQTq3gERv3pnqA4xFCpvUgbGDJad7eS8WE,KwsfyHKRUTZPQtysN7M3tZ4GXTnuov5XRgjdF2XCG8faAPmFruRF,KzCUbGhN9LJhdeFfL9zQgTJMjqxdBKEekRGZX24hXdgCNCijkkap,KzgpMBwwsDLwkaC5UrmBgCYaBD2WgZ7PBoGYXR8KT7gCA9UTN5a3,KyBXTPy4T7YG4q9tcAM3LkvfRpD1ybHMvcJ2ehaWXaSqeGUxEdkP,KzJDe9iwJRPtKP2F2AoN6zBgzS7uiuAwhWCfGdNeYJ3PC1HNJ8M8,L1xbHrxynrqLKkoYc4qtoQPx6uy5qYXR5ZDYVYBSRmCV5piU3JG9,KzRedjSwMggebB3VufhbzpYJnvHfHe9kPJSjCU5QpJdAW3NSZxYS,Kyjtp5858xL7JfeV4PNRCKy2t6XvgqNNepArGY9F9F1SSPqNEMs3,L2D4RLHPiHBidkHS8ftx11jJk1hGFELvxh8LoxNQheaGT58dKenW,KyLPZdwY4td98bKkXqEXTEBX3vwEYTQo1yyLjX2jKXA63GBpmSjv))","wsh(multi(20,03669b8afcec803a0d323e9a17f3ea8e68e8abe5a278020a929adbec52421adbd0,0260b2003c386519fc9eadf2b5cf124dd8eea4c4e68d5e154050a9346ea98ce600,0362a74e399c39ed5593852a30147f2959b56bb827dfa3e60e464b02ccf87dc5e8,0261345b53de74a4d721ef877c255429961b7e43714171ac06168d7e08c542a8b8,02da72e8b46901a65d4374fe6315538d8f368557dda3a1dcf9ea903f3afe7314c8,0318c82dd0b53fd3a932d16e0ba9e278fcc937c582d5781be626ff16e201f72286,0297ccef1ef99f9d73dec9ad37476ddb232f1238aff877af19e72ba04493361009,02e502cfd5c3f972fe9a3e2a18827820638f96b6f347e54d63deb839011fd5765d,03e687710f0e3ebe81c1037074da939d409c0025f17eb86adb9427d28f0f7ae0e9,02c04d3a5274952acdbc76987f3184b346a483d43be40874624b29e3692c1df5af,02ed06e0f418b5b43a7ec01d1d7d27290fa15f75771cb69b642a51471c29c84acd,036d46073cbb9ffee90473f3da429abc8de7f8751199da44485682a989a4bebb24,02f5d1ff7c9029a80a4e36b9a5497027ef7f3e73384a4a94fbfe7c4e9164eec8bc,02e41deffd1b7cce11cde209a781adcffdabd1b91c0ba0375857a2bfd9302419f3,02d76625f7956a7fc505ab02556c23ee72d832f1bac391bcd2d3abce5710a13d06,0399eb0a5487515802dc14544cf10b3666623762fbed2ec38a3975716e2c29c232,02bc2feaa536991d269aae46abb8f3772a5b3ad592314945e51543e7da84c4af6e,0318bf32e5217c1eb771a6d5ce1cd39395dff7ff665704f175c9a5451d95a2f2ca,02c681a6243f16208c2004bb81f5a8a67edfdd3e3711534eadeec3dcf0b010c759,0249fdd6b69768b8d84b4893f8ff84b36835c50183de20fcae8f366a45290d01fd))", "wsh(multi(20,03669b8afcec803a0d323e9a17f3ea8e68e8abe5a278020a929adbec52421adbd0,0260b2003c386519fc9eadf2b5cf124dd8eea4c4e68d5e154050a9346ea98ce600,0362a74e399c39ed5593852a30147f2959b56bb827dfa3e60e464b02ccf87dc5e8,0261345b53de74a4d721ef877c255429961b7e43714171ac06168d7e08c542a8b8,02da72e8b46901a65d4374fe6315538d8f368557dda3a1dcf9ea903f3afe7314c8,0318c82dd0b53fd3a932d16e0ba9e278fcc937c582d5781be626ff16e201f72286,0297ccef1ef99f9d73dec9ad37476ddb232f1238aff877af19e72ba04493361009,02e502cfd5c3f972fe9a3e2a18827820638f96b6f347e54d63deb839011fd5765d,03e687710f0e3ebe81c1037074da939d409c0025f17eb86adb9427d28f0f7ae0e9,02c04d3a5274952acdbc76987f3184b346a483d43be40874624b29e3692c1df5af,02ed06e0f418b5b43a7ec01d1d7d27290fa15f75771cb69b642a51471c29c84acd,036d46073cbb9ffee90473f3da429abc8de7f8751199da44485682a989a4bebb24,02f5d1ff7c9029a80a4e36b9a5497027ef7f3e73384a4a94fbfe7c4e9164eec8bc,02e41deffd1b7cce11cde209a781adcffdabd1b91c0ba0375857a2bfd9302419f3,02d76625f7956a7fc505ab02556c23ee72d832f1bac391bcd2d3abce5710a13d06,0399eb0a5487515802dc14544cf10b3666623762fbed2ec38a3975716e2c29c232,02bc2feaa536991d269aae46abb8f3772a5b3ad592314945e51543e7da84c4af6e,0318bf32e5217c1eb771a6d5ce1cd39395dff7ff665704f175c9a5451d95a2f2ca,02c681a6243f16208c2004bb81f5a8a67edfdd3e3711534eadeec3dcf0b010c759,0249fdd6b69768b8d84b4893f8ff84b36835c50183de20fcae8f366a45290d01fd))", SIGNABLE, {{"0020376bd8344b8b6ebe504ff85ef743eaa1aa9272178223bcb6887e9378efb341ac"}}, OutputType::BECH32); // In P2WSH we can have up to 20 keys + Check("sh(wsh(multi(20,KzoAz5CanayRKex3fSLQ2BwJpN7U52gZvxMyk78nDMHuqrUxuSJy,KwGNz6YCCQtYvFzMtrC6D3tKTKdBBboMrLTsjr2NYVBwapCkn7Mr,KxogYhiNfwxuswvXV66eFyKcCpm7dZ7TqHVqujHAVUjJxyivxQ9X,L2BUNduTSyZwZjwNHynQTF14mv2uz2NRq5n5sYWTb4FkkmqgEE9f,L1okJGHGn1kFjdXHKxXjwVVtmCMR2JA5QsbKCSpSb7ReQjezKeoD,KxDCNSST75HFPaW5QKpzHtAyaCQC7p9Vo3FYfi2u4dXD1vgMiboK,L5edQjFtnkcf5UWURn6UuuoFrabgDQUHdheKCziwN42aLwS3KizU,KzF8UWFcEC7BYTq8Go1xVimMkDmyNYVmXV5PV7RuDicvAocoPB8i,L3nHUboKG2w4VSJ5jYZ5CBM97oeK6YuKvfZxrefdShECcjEYKMWZ,KyjHo36dWkYhimKmVVmQTq3gERv3pnqA4xFCpvUgbGDJad7eS8WE,KwsfyHKRUTZPQtysN7M3tZ4GXTnuov5XRgjdF2XCG8faAPmFruRF,KzCUbGhN9LJhdeFfL9zQgTJMjqxdBKEekRGZX24hXdgCNCijkkap,KzgpMBwwsDLwkaC5UrmBgCYaBD2WgZ7PBoGYXR8KT7gCA9UTN5a3,KyBXTPy4T7YG4q9tcAM3LkvfRpD1ybHMvcJ2ehaWXaSqeGUxEdkP,KzJDe9iwJRPtKP2F2AoN6zBgzS7uiuAwhWCfGdNeYJ3PC1HNJ8M8,L1xbHrxynrqLKkoYc4qtoQPx6uy5qYXR5ZDYVYBSRmCV5piU3JG9,KzRedjSwMggebB3VufhbzpYJnvHfHe9kPJSjCU5QpJdAW3NSZxYS,Kyjtp5858xL7JfeV4PNRCKy2t6XvgqNNepArGY9F9F1SSPqNEMs3,L2D4RLHPiHBidkHS8ftx11jJk1hGFELvxh8LoxNQheaGT58dKenW,KyLPZdwY4td98bKkXqEXTEBX3vwEYTQo1yyLjX2jKXA63GBpmSjv)))","sh(wsh(multi(20,03669b8afcec803a0d323e9a17f3ea8e68e8abe5a278020a929adbec52421adbd0,0260b2003c386519fc9eadf2b5cf124dd8eea4c4e68d5e154050a9346ea98ce600,0362a74e399c39ed5593852a30147f2959b56bb827dfa3e60e464b02ccf87dc5e8,0261345b53de74a4d721ef877c255429961b7e43714171ac06168d7e08c542a8b8,02da72e8b46901a65d4374fe6315538d8f368557dda3a1dcf9ea903f3afe7314c8,0318c82dd0b53fd3a932d16e0ba9e278fcc937c582d5781be626ff16e201f72286,0297ccef1ef99f9d73dec9ad37476ddb232f1238aff877af19e72ba04493361009,02e502cfd5c3f972fe9a3e2a18827820638f96b6f347e54d63deb839011fd5765d,03e687710f0e3ebe81c1037074da939d409c0025f17eb86adb9427d28f0f7ae0e9,02c04d3a5274952acdbc76987f3184b346a483d43be40874624b29e3692c1df5af,02ed06e0f418b5b43a7ec01d1d7d27290fa15f75771cb69b642a51471c29c84acd,036d46073cbb9ffee90473f3da429abc8de7f8751199da44485682a989a4bebb24,02f5d1ff7c9029a80a4e36b9a5497027ef7f3e73384a4a94fbfe7c4e9164eec8bc,02e41deffd1b7cce11cde209a781adcffdabd1b91c0ba0375857a2bfd9302419f3,02d76625f7956a7fc505ab02556c23ee72d832f1bac391bcd2d3abce5710a13d06,0399eb0a5487515802dc14544cf10b3666623762fbed2ec38a3975716e2c29c232,02bc2feaa536991d269aae46abb8f3772a5b3ad592314945e51543e7da84c4af6e,0318bf32e5217c1eb771a6d5ce1cd39395dff7ff665704f175c9a5451d95a2f2ca,02c681a6243f16208c2004bb81f5a8a67edfdd3e3711534eadeec3dcf0b010c759,0249fdd6b69768b8d84b4893f8ff84b36835c50183de20fcae8f366a45290d01fd)))", "sh(wsh(multi(20,03669b8afcec803a0d323e9a17f3ea8e68e8abe5a278020a929adbec52421adbd0,0260b2003c386519fc9eadf2b5cf124dd8eea4c4e68d5e154050a9346ea98ce600,0362a74e399c39ed5593852a30147f2959b56bb827dfa3e60e464b02ccf87dc5e8,0261345b53de74a4d721ef877c255429961b7e43714171ac06168d7e08c542a8b8,02da72e8b46901a65d4374fe6315538d8f368557dda3a1dcf9ea903f3afe7314c8,0318c82dd0b53fd3a932d16e0ba9e278fcc937c582d5781be626ff16e201f72286,0297ccef1ef99f9d73dec9ad37476ddb232f1238aff877af19e72ba04493361009,02e502cfd5c3f972fe9a3e2a18827820638f96b6f347e54d63deb839011fd5765d,03e687710f0e3ebe81c1037074da939d409c0025f17eb86adb9427d28f0f7ae0e9,02c04d3a5274952acdbc76987f3184b346a483d43be40874624b29e3692c1df5af,02ed06e0f418b5b43a7ec01d1d7d27290fa15f75771cb69b642a51471c29c84acd,036d46073cbb9ffee90473f3da429abc8de7f8751199da44485682a989a4bebb24,02f5d1ff7c9029a80a4e36b9a5497027ef7f3e73384a4a94fbfe7c4e9164eec8bc,02e41deffd1b7cce11cde209a781adcffdabd1b91c0ba0375857a2bfd9302419f3,02d76625f7956a7fc505ab02556c23ee72d832f1bac391bcd2d3abce5710a13d06,0399eb0a5487515802dc14544cf10b3666623762fbed2ec38a3975716e2c29c232,02bc2feaa536991d269aae46abb8f3772a5b3ad592314945e51543e7da84c4af6e,0318bf32e5217c1eb771a6d5ce1cd39395dff7ff665704f175c9a5451d95a2f2ca,02c681a6243f16208c2004bb81f5a8a67edfdd3e3711534eadeec3dcf0b010c759,0249fdd6b69768b8d84b4893f8ff84b36835c50183de20fcae8f366a45290d01fd)))", SIGNABLE, {{"a914c2c9c510e9d7f92fd6131e94803a8d34a8ef675e87"}}, OutputType::P2SH_SEGWIT); // Even if it's wrapped into P2SH // Check for invalid nesting of structures CheckUnparsable("sh(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)", "sh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", "A function is needed within P2SH"); // P2SH needs a script, not a key CheckUnparsable("sh(combo(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1))", "sh(combo(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd))", "Can only have combo() at top level"); // Old must be top level @@ -452,8 +451,8 @@ Check("sh(wsh(multi(20,KzoAz5CanayRKex3fSLQ2BwJpN7U52gZvxMyk78nDMHuqrUxuSJy,KwGN CheckUnparsable("wsh(wsh(pk(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)))", "wsh(wsh(pk(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)))", "Can only have wsh() at top level or inside sh()"); // Cannot embed P2WSH inside P2WSH // Checksums - Check("sh(multi(2,[00000000/111'/222]xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0))#ggrsrxfy", "sh(multi(2,[00000000/111'/222]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0))#tjg09x5t", "sh(multi(2,[00000000/111'/222]xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0))#ggrsrxfy", "sh(multi(2,[00000000/111'/222]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0))#tjg09x5t", DEFAULT, {{"a91445a9a622a8b0a1269944be477640eedc447bbd8487"}}, OutputType::LEGACY, {{0x8000006FUL,222},{0}}); - 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("sh(multi(2,[00000000/111'/222]xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0))#ggrsrxfy", "sh(multi(2,[00000000/111'/222]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0))#tjg09x5t", "sh(multi(2,[00000000/111'/222]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0))#tjg09x5t", DEFAULT, {{"a91445a9a622a8b0a1269944be477640eedc447bbd8487"}}, OutputType::LEGACY, {{0x8000006FUL,222},{0}}); + 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]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0))", DEFAULT, {{"a91445a9a622a8b0a1269944be477640eedc447bbd8487"}}, OutputType::LEGACY, {{0x8000006FUL,222},{0}}); CheckUnparsable("sh(multi(2,[00000000/111'/222]xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0))#", "sh(multi(2,[00000000/111'/222]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0))#", "Expected 8 character checksum, not 0 characters"); // Empty checksum CheckUnparsable("sh(multi(2,[00000000/111'/222]xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0))#ggrsrxfyq", "sh(multi(2,[00000000/111'/222]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0))#tjg09x5tq", "Expected 8 character checksum, not 9 characters"); // Too long checksum CheckUnparsable("sh(multi(2,[00000000/111'/222]xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0))#ggrsrxf", "sh(multi(2,[00000000/111'/222]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0))#tjg09x5", "Expected 8 character checksum, not 7 characters"); // Too short checksum @@ -466,6 +465,29 @@ Check("sh(wsh(multi(20,KzoAz5CanayRKex3fSLQ2BwJpN7U52gZvxMyk78nDMHuqrUxuSJy,KwGN CheckUnparsable("", "raw(asdf)", "Raw script is not hex"); // Invalid script CheckUnparsable("", "raw(Ü)#00000000", "Invalid characters in payload"); // Invalid chars + Check( + "rawtr(xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt/86'/1'/0'/1/*)#a5gn3t7k", + "rawtr(xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/86'/1'/0'/1/*)#4ur3xhft", + "rawtr([5a61ff8e/86'/1'/0']xpub6DtZpc9PRL2B6pwoNGysmHAaBofDmWv5S6KQEKKGPKhf5fV62ywDtSziSApYVK3JnYY5KUSgiCwiXW5wtd8z7LNBxT9Mu5sEro8itdGfTeA/1/*)#llheyd9x", + RANGE | HARDENED | XONLY_KEYS, + {{"51205172af752f057d543ce8e4a6f8dcf15548ec6be44041bfa93b72e191cfc8c1ee"}, {"51201b66f20b86f700c945ecb9ad9b0ad1662b73084e2bfea48bee02126350b8a5b1"}, {"512063e70f66d815218abcc2306aa930aaca07c5cde73b75127eb27b5e8c16b58a25"}}, + OutputType::BECH32M, + {{0x80000056, 0x80000001, 0x80000000, 1, 0}, {0x80000056, 0x80000001, 0x80000000, 1, 1}, {0x80000056, 0x80000001, 0x80000000, 1, 2}}); + + + Check( + "rawtr(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)", + "rawtr(a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", + "rawtr(a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", + SIGNABLE | XONLY_KEYS, + {{"5120a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd"}}, + OutputType::BECH32M); + + CheckUnparsable( + "", + "rawtr(xpub68FQ9imX6mCWacw6eNRjaa8q8ynnHmUd5i7MVR51ZMPP5JycyfVHSLQVFPHMYiTybWJnSBL2tCBpy6aJTR2DYrshWYfwAxs8SosGXd66d8/*, xpub69Mvq3QMipdvnd9hAyeTnT5jrkcBuLErV212nsGf3qr7JPWysc9HnNhCsazdzj1etSx28hPSE8D7DnceFbNdw4Kg8SyRfjE2HFLv1P8TSGc/*)", + "rawtr(): only one key expected."); + // A 2of4 but using a direct push rather than OP_2 CScript nonminimalmultisig; CKey keys[4]; @@ -486,6 +508,29 @@ Check("sh(wsh(multi(20,KzoAz5CanayRKex3fSLQ2BwJpN7U52gZvxMyk78nDMHuqrUxuSJy,KwGN } nonminimalmultisig << std::vector<unsigned char>{4} << OP_CHECKMULTISIG; CheckInferRaw(nonminimalmultisig); + + // Miniscript tests + + // Invalid checksum + CheckUnparsable("wsh(and_v(vc:andor(pk(L4gM1FBdyHNpkzsFh9ipnofLhpZRp2mwobpeULy1a6dBTvw8Ywtd),pk_k(Kx9HCDjGiwFcgVNhTrS5z5NeZdD6veeam61eDxLDCkGWujvL4Gnn),and_v(v:older(1),pk_k(L4o2kDvXXDRH2VS9uBnouScLduWt4dZnM25se7kvEjJeQ285en2A))),after(10)))#abcdef12", "wsh(and_v(vc:andor(pk(03cdabb7f2dce7bfbd8a0b9570c6fd1e712e5d64045e9d6b517b3d5072251dc204),pk_k(032707170c71d8f75e4ca4e3fce870b9409dcaf12b051d3bcadff74747fa7619c0),and_v(v:older(1),pk_k(02aa27e5eb2c185e87cd1dbc3e0efc9cb1175235e0259df1713424941c3cb40402))),after(10)))#abcdef12", "Provided checksum 'abcdef12' does not match computed checksum 'tyzp6a7p'"); + // Only p2wsh context is valid + CheckUnparsable("sh(and_v(vc:andor(pk(L4gM1FBdyHNpkzsFh9ipnofLhpZRp2mwobpeULy1a6dBTvw8Ywtd),pk_k(Kx9HCDjGiwFcgVNhTrS5z5NeZdD6veeam61eDxLDCkGWujvL4Gnn),and_v(v:older(1),pk_k(L4o2kDvXXDRH2VS9uBnouScLduWt4dZnM25se7kvEjJeQ285en2A))),after(10)))", "sh(and_v(vc:andor(pk(03cdabb7f2dce7bfbd8a0b9570c6fd1e712e5d64045e9d6b517b3d5072251dc204),pk_k(032707170c71d8f75e4ca4e3fce870b9409dcaf12b051d3bcadff74747fa7619c0),and_v(v:older(1),pk_k(02aa27e5eb2c185e87cd1dbc3e0efc9cb1175235e0259df1713424941c3cb40402))),after(10)))", "Miniscript expressions can only be used in wsh"); + CheckUnparsable("tr(and_v(vc:andor(pk(L4gM1FBdyHNpkzsFh9ipnofLhpZRp2mwobpeULy1a6dBTvw8Ywtd),pk_k(Kx9HCDjGiwFcgVNhTrS5z5NeZdD6veeam61eDxLDCkGWujvL4Gnn),and_v(v:older(1),pk_k(L4o2kDvXXDRH2VS9uBnouScLduWt4dZnM25se7kvEjJeQ285en2A))),after(10)))", "tr(and_v(vc:andor(pk(03cdabb7f2dce7bfbd8a0b9570c6fd1e712e5d64045e9d6b517b3d5072251dc204),pk_k(032707170c71d8f75e4ca4e3fce870b9409dcaf12b051d3bcadff74747fa7619c0),and_v(v:older(1),pk_k(02aa27e5eb2c185e87cd1dbc3e0efc9cb1175235e0259df1713424941c3cb40402))),after(10)))", "tr(): key 'and_v(vc:andor(pk(03cdabb7f2dce7bfbd8a0b9570c6fd1e712e5d64045e9d6b517b3d5072251dc204),pk_k(032707170c71d8f75e4ca4e3fce870b9409dcaf12b051d3bcadff74747fa7619c0),and_v(v:older(1),pk_k(02aa27e5eb2c185e87cd1dbc3e0efc9cb1175235e0259df1713424941c3cb40402))),after(10))' is not valid"); + CheckUnparsable("raw(and_v(vc:andor(pk(L4gM1FBdyHNpkzsFh9ipnofLhpZRp2mwobpeULy1a6dBTvw8Ywtd),pk_k(Kx9HCDjGiwFcgVNhTrS5z5NeZdD6veeam61eDxLDCkGWujvL4Gnn),and_v(v:older(1),pk_k(L4o2kDvXXDRH2VS9uBnouScLduWt4dZnM25se7kvEjJeQ285en2A))),after(10)))", "sh(and_v(vc:andor(pk(03cdabb7f2dce7bfbd8a0b9570c6fd1e712e5d64045e9d6b517b3d5072251dc204),pk_k(032707170c71d8f75e4ca4e3fce870b9409dcaf12b051d3bcadff74747fa7619c0),and_v(v:older(1),pk_k(02aa27e5eb2c185e87cd1dbc3e0efc9cb1175235e0259df1713424941c3cb40402))),after(10)))", "Miniscript expressions can only be used in wsh"); + CheckUnparsable("", "tr(034D2224bbbbbbbbbbcbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb40,{{{{{{{{{{{{{{{{{{{{{{multi(1,xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc/967808'/9,xprvA1RpRA33e1JQ7ifknakTFNpgXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc/968/2/5/8/5/2/5/58/58/2/5/5/5/58/588/2/6/8/5/2/8/2/5/8/2/58/2/5/8/5/2/8/5/8/3/4/5/58/55/2/5/58/58/2/5/5/5/8/5/2/8/5/85/2/8/2/5/8/5/2/5/58/58/2/5/58/58/588/2/58/2/8/5/8/5/4/5/585/2/5/58/58/2/5/5/58/588/2/58/2/5/8/5/2/8/2/5/8/5/5/58/588/2/6/8/5/2/8/2/5/8/5/2/5/58/58/2/5/58/58/2/0/8/5/2/8/5/8/5/4/5/58/588/2/6/8/5/2/8/2/5/8/5/2/5/58/58/2/5/58/58/588/2/58/2/5/8/5/8/24/5/58/52/5/8/5/2/8/24/5/58/588/246/8/5/2/8/2/5/8/5/2/5/58/58/2/5/5/5/58/588/2/6/8/5/2/8/2/5/8/2/58/2/5/8/5/2/8/5/8/5/4/5/58/55/58/2/5/8/55/2/5/8/58/555/58/2/5/8/4//2/5/58/5w/2/5/8/5/2/4/5/58/5558'/2/5/58/58/2/5/5/58/588/2/58/2/5/8/5/2/8/2/5/8/5/5/8/58/2/5/58/58/2/5/8/9/588/2/58/2/5/8/5/2/8/5/8/5/4/5/58/588/2/6/8/5/2/8/2/5/8/5/2/5/58/58/2/5/5/58/588/2/58/2/5/8/5/2/82/5/8/5/5/58/52/6/8/5/2/8/{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{}{{{{{{{{{DDD2/5/8/5/2/5/58/58/2/5/58/58/588/2/58/2/8/5/8/5/4/5/58/588/2/6/8/5/2/8/2/5/8588/246/8/5/2DLDDDDDDDbbD3DDDD/8/2/5/8/5/2/5/58/58/2/5/5/5/58/588/2/6/8/5/2/8/2/5/8/2/58/2/5/8/5/2/8/5/8/3/4/5/58/55/2/5/58/58/2/5/5/5/8/5/2/8/5/85/2/8/2/5/8D)/5/2/5/58/58/2/5/58/58/58/588/2/58/2/5/8/5/25/58/58/2/5/58/58/2/5/8/9/588/2/58/2/6780,xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFW/8/5/2/5/58678008')", "'multi(1,xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc/967808'/9,xprvA1RpRA33e1JQ7ifknakTFNpgXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc/968/2/5/8/5/2/5/58/58/2/5/5/5/58/588/2/6/8/5/2/8/2/5/8/2/58/2/5/8/5/2/8/5/8/3/4/5/58/55/2/5/58/58/2/5/5/5/8/5/2/8/5/85/2/8/2/5/8/5/2/5/58/58/2/5/58/58/588/2/58/2/8/5/8/5/4/5/585/2/5/58/58/2/5/5/58/588/2/58/2/5/8/5/2/8/2/5/8/5/5/58/588/2/6/8/5/2/8/2/5/8/5/2/5/58/58/2/5/58/58/2/0/8/5/2/8/5/8/5/4/5/58/588/2/6/8/5/2/8/2/5/8/5/2/5/58/58/2/5/58/58/588/2/58/2/5/8/5/8/24/5/58/52/5/8/5/2/8/24/5/58/588/246/8/5/2/8/2/5/8/5/2/5/58/58/2/5/5/5/58/588/2/6/8/5/2/8/2/5/8/2/58/2/5/8/5/2/8/5/8/5/4/5/58/55/58/2/5/8/55/2/5/8/58/555/58/2/5/8/4//2/5/58/5w/2/5/8/5/2/4/5/58/5558'/2/5/58/58/2/5/5/58/588/2/58/2/5/8/5/2/8/2/5/8/5/5/8/58/2/5/58/58/2/5/8/9/588/2/58/2/5/8/5/2/8/5/8/5/4/5/58/588/2/6/8/5/2/8/2/5/8/5/2/5/58/58/2/5/5/58/588/2/58/2/5/8/5/2/82/5/8/5/5/58/52/6/8/5/2/8/{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{}{{{{{{{{{DDD2/5/8/5/2/5/58/58/2/5/58/58/588/2/58/2/8/5/8/5/4/5/58/588/2/6/8/5/2/8/2/5/8588/246/8/5/2DLDDDDDDDbbD3DDDD/8/2/5/8/5/2/5/58/58/2/5/5/5/58/588/2/6/8/5/2/8/2/5/8/2/58/2/5/8/5/2/8/5/8/3/4/5/58/55/2/5/58/58/2/5/5/5/8/5/2/8/5/85/2/8/2/5/8D)/5/2/5/58/58/2/5/58/58/58/588/2/58/2/5/8/5/25/58/58/2/5/58/58/2/5/8/9/588/2/58/2/6780,xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFW/8/5/2/5/58678008'' is not a valid descriptor function"); + // Insane at top level + CheckUnparsable("wsh(and_b(vc:andor(pk(L4gM1FBdyHNpkzsFh9ipnofLhpZRp2mwobpeULy1a6dBTvw8Ywtd),pk_k(Kx9HCDjGiwFcgVNhTrS5z5NeZdD6veeam61eDxLDCkGWujvL4Gnn),and_v(v:older(1),pk_k(L4o2kDvXXDRH2VS9uBnouScLduWt4dZnM25se7kvEjJeQ285en2A))),after(10)))", "wsh(and_b(vc:andor(pk(03cdabb7f2dce7bfbd8a0b9570c6fd1e712e5d64045e9d6b517b3d5072251dc204),pk_k(032707170c71d8f75e4ca4e3fce870b9409dcaf12b051d3bcadff74747fa7619c0),and_v(v:older(1),pk_k(02aa27e5eb2c185e87cd1dbc3e0efc9cb1175235e0259df1713424941c3cb40402))),after(10)))", "and_b(vc:andor(pk(03cdabb7f2dce7bfbd8a0b9570c6fd1e712e5d64045e9d6b517b3d5072251dc204),pk_k(032707170c71d8f75e4ca4e3fce870b9409dcaf12b051d3bcadff74747fa7619c0),and_v(v:older(1),pk_k(02aa27e5eb2c185e87cd1dbc3e0efc9cb1175235e0259df1713424941c3cb40402))),after(10)) is invalid"); + // Invalid sub + CheckUnparsable("wsh(and_v(vc:andor(v:pk_k(L4gM1FBdyHNpkzsFh9ipnofLhpZRp2mwobpeULy1a6dBTvw8Ywtd),pk_k(Kx9HCDjGiwFcgVNhTrS5z5NeZdD6veeam61eDxLDCkGWujvL4Gnn),and_v(v:older(1),pk_k(L4o2kDvXXDRH2VS9uBnouScLduWt4dZnM25se7kvEjJeQ285en2A))),after(10)))", "wsh(and_v(vc:andor(v:pk_k(03cdabb7f2dce7bfbd8a0b9570c6fd1e712e5d64045e9d6b517b3d5072251dc204),pk_k(032707170c71d8f75e4ca4e3fce870b9409dcaf12b051d3bcadff74747fa7619c0),and_v(v:older(1),pk_k(02aa27e5eb2c185e87cd1dbc3e0efc9cb1175235e0259df1713424941c3cb40402))),after(10)))", "v:pk_k(03cdabb7f2dce7bfbd8a0b9570c6fd1e712e5d64045e9d6b517b3d5072251dc204) is invalid"); + // Insane subs + CheckUnparsable("wsh(or_i(older(1),pk(L4gM1FBdyHNpkzsFh9ipnofLhpZRp2mwobpeULy1a6dBTvw8Ywtd)))", "wsh(or_i(older(1),pk(03cdabb7f2dce7bfbd8a0b9570c6fd1e712e5d64045e9d6b517b3d5072251dc204)))", "or_i(older(1),pk(03cdabb7f2dce7bfbd8a0b9570c6fd1e712e5d64045e9d6b517b3d5072251dc204)) is not sane: witnesses without signature exist"); + CheckUnparsable("wsh(or_b(sha256(cdabb7f2dce7bfbd8a0b9570c6fd1e712e5d64045e9d6b517b3d5072251dc204),s:pk(03cdabb7f2dce7bfbd8a0b9570c6fd1e712e5d64045e9d6b517b3d5072251dc204)))", "wsh(or_b(sha256(cdabb7f2dce7bfbd8a0b9570c6fd1e712e5d64045e9d6b517b3d5072251dc204),s:pk(03cdabb7f2dce7bfbd8a0b9570c6fd1e712e5d64045e9d6b517b3d5072251dc204)))", "or_b(sha256(cdabb7f2dce7bfbd8a0b9570c6fd1e712e5d64045e9d6b517b3d5072251dc204),s:pk(03cdabb7f2dce7bfbd8a0b9570c6fd1e712e5d64045e9d6b517b3d5072251dc204)) is not sane: malleable witnesses exist"); + CheckUnparsable("wsh(and_b(and_b(older(1),a:older(100000000)),s:pk(L4gM1FBdyHNpkzsFh9ipnofLhpZRp2mwobpeULy1a6dBTvw8Ywtd)))", "wsh(and_b(and_b(older(1),a:older(100000000)),s:pk(03cdabb7f2dce7bfbd8a0b9570c6fd1e712e5d64045e9d6b517b3d5072251dc204)))", "and_b(older(1),a:older(100000000)) is not sane: contains mixes of timelocks expressed in blocks and seconds"); + CheckUnparsable("wsh(and_b(or_b(pkh(L4gM1FBdyHNpkzsFh9ipnofLhpZRp2mwobpeULy1a6dBTvw8Ywtd),s:pk(Kx9HCDjGiwFcgVNhTrS5z5NeZdD6veeam61eDxLDCkGWujvL4Gnn)),s:pk(L4gM1FBdyHNpkzsFh9ipnofLhpZRp2mwobpeULy1a6dBTvw8Ywtd)))", "wsh(and_b(or_b(pkh(03cdabb7f2dce7bfbd8a0b9570c6fd1e712e5d64045e9d6b517b3d5072251dc204),s:pk(032707170c71d8f75e4ca4e3fce870b9409dcaf12b051d3bcadff74747fa7619c0)),s:pk(03cdabb7f2dce7bfbd8a0b9570c6fd1e712e5d64045e9d6b517b3d5072251dc204)))", "and_b(or_b(pkh(03cdabb7f2dce7bfbd8a0b9570c6fd1e712e5d64045e9d6b517b3d5072251dc204),s:pk(032707170c71d8f75e4ca4e3fce870b9409dcaf12b051d3bcadff74747fa7619c0)),s:pk(03cdabb7f2dce7bfbd8a0b9570c6fd1e712e5d64045e9d6b517b3d5072251dc204)) is not sane: contains duplicate public keys"); + // Valid with extended keys. + Check("wsh(and_v(v:ripemd160(095ff41131e5946f3c85f79e44adbcf8e27e080e),multi(1,xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0)))", "wsh(and_v(v:ripemd160(095ff41131e5946f3c85f79e44adbcf8e27e080e),multi(1,xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0)))", "wsh(and_v(v:ripemd160(095ff41131e5946f3c85f79e44adbcf8e27e080e),multi(1,xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0)))", UNSOLVABLE, {{"0020acf425291b98a1d7e0d4690139442abc289175be32ef1f75945e339924246d73"}}, OutputType::BECH32, {{},{0}}); + // Valid under sh(wsh()) and with a mix of xpubs and raw keys + Check("sh(wsh(thresh(1,pkh(L4gM1FBdyHNpkzsFh9ipnofLhpZRp2mwobpeULy1a6dBTvw8Ywtd),a:and_n(multi(1,xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0),n:older(2)))))", "sh(wsh(thresh(1,pkh(03cdabb7f2dce7bfbd8a0b9570c6fd1e712e5d64045e9d6b517b3d5072251dc204),a:and_n(multi(1,xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0),n:older(2)))))", "sh(wsh(thresh(1,pkh(03cdabb7f2dce7bfbd8a0b9570c6fd1e712e5d64045e9d6b517b3d5072251dc204),a:and_n(multi(1,xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0),n:older(2)))))", UNSOLVABLE | MIXED_PUBKEYS, {{"a914767e9119ff3b3ac0cb6dcfe21de1842ccf85f1c487"}}, OutputType::P2SH_SEGWIT, {{},{0}}); } BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/flatfile_tests.cpp b/src/test/flatfile_tests.cpp index d54d6b6471..605faa08e4 100644 --- a/src/test/flatfile_tests.cpp +++ b/src/test/flatfile_tests.cpp @@ -41,26 +41,26 @@ BOOST_AUTO_TEST_CASE(flatfile_open) // Write first line to file. { - CAutoFile file(seq.Open(FlatFilePos(0, pos1)), SER_DISK, CLIENT_VERSION); + AutoFile file{seq.Open(FlatFilePos(0, pos1))}; file << LIMITED_STRING(line1, 256); } // Attempt to append to file opened in read-only mode. { - CAutoFile file(seq.Open(FlatFilePos(0, pos2), true), SER_DISK, CLIENT_VERSION); + AutoFile file{seq.Open(FlatFilePos(0, pos2), true)}; BOOST_CHECK_THROW(file << LIMITED_STRING(line2, 256), std::ios_base::failure); } // Append second line to file. { - CAutoFile file(seq.Open(FlatFilePos(0, pos2)), SER_DISK, CLIENT_VERSION); + AutoFile file{seq.Open(FlatFilePos(0, pos2))}; file << LIMITED_STRING(line2, 256); } // Read text from file in read-only mode. { std::string text; - CAutoFile file(seq.Open(FlatFilePos(0, pos1), true), SER_DISK, CLIENT_VERSION); + AutoFile file{seq.Open(FlatFilePos(0, pos1), true)}; file >> LIMITED_STRING(text, 256); BOOST_CHECK_EQUAL(text, line1); @@ -72,7 +72,7 @@ BOOST_AUTO_TEST_CASE(flatfile_open) // Read text from file with position offset. { std::string text; - CAutoFile file(seq.Open(FlatFilePos(0, pos2)), SER_DISK, CLIENT_VERSION); + AutoFile file{seq.Open(FlatFilePos(0, pos2))}; file >> LIMITED_STRING(text, 256); BOOST_CHECK_EQUAL(text, line2); @@ -81,7 +81,7 @@ BOOST_AUTO_TEST_CASE(flatfile_open) // Ensure another file in the sequence has no data. { std::string text; - CAutoFile file(seq.Open(FlatFilePos(1, pos2)), SER_DISK, CLIENT_VERSION); + AutoFile file{seq.Open(FlatFilePos(1, pos2))}; BOOST_CHECK_THROW(file >> LIMITED_STRING(text, 256), std::ios_base::failure); } } diff --git a/src/test/fuzz/addrman.cpp b/src/test/fuzz/addrman.cpp index af7a282781..7668940cbc 100644 --- a/src/test/fuzz/addrman.cpp +++ b/src/test/fuzz/addrman.cpp @@ -113,11 +113,11 @@ void FillAddrman(AddrMan& addrman, FuzzedDataProvider& fuzzed_data_provider) for (size_t j = 0; j < num_addresses; ++j) { const auto addr = CAddress{CService{RandAddr(fuzzed_data_provider, fast_random_context), 8333}, NODE_NETWORK}; - const auto time_penalty = fast_random_context.randrange(100000001); + const std::chrono::seconds time_penalty{fast_random_context.randrange(100000001)}; addrman.Add({addr}, source, time_penalty); if (n > 0 && addrman.size() % n == 0) { - addrman.Good(addr, GetTime()); + addrman.Good(addr, Now<NodeSeconds>()); } // Add 10% of the addresses from more than one source. @@ -161,7 +161,7 @@ public: CSipHasher hasher(0, 0); auto addr_key = a.GetKey(); auto source_key = a.source.GetAddrBytes(); - hasher.Write(a.nLastSuccess); + hasher.Write(TicksSinceEpoch<std::chrono::seconds>(a.m_last_success)); hasher.Write(a.nAttempts); hasher.Write(a.nRefCount); hasher.Write(a.fInTried); @@ -175,8 +175,8 @@ public: }; auto addrinfo_eq = [](const AddrInfo& lhs, const AddrInfo& rhs) { - return std::tie(static_cast<const CService&>(lhs), lhs.source, lhs.nLastSuccess, lhs.nAttempts, lhs.nRefCount, lhs.fInTried) == - std::tie(static_cast<const CService&>(rhs), rhs.source, rhs.nLastSuccess, rhs.nAttempts, rhs.nRefCount, rhs.fInTried); + return std::tie(static_cast<const CService&>(lhs), lhs.source, lhs.m_last_success, lhs.nAttempts, lhs.nRefCount, lhs.fInTried) == + std::tie(static_cast<const CService&>(rhs), rhs.source, rhs.m_last_success, rhs.nAttempts, rhs.nRefCount, rhs.fInTried); }; using Addresses = std::unordered_set<AddrInfo, decltype(addrinfo_hasher), decltype(addrinfo_eq)>; @@ -269,25 +269,25 @@ FUZZ_TARGET_INIT(addrman, initialize_addrman) } const std::optional<CNetAddr> opt_net_addr = ConsumeDeserializable<CNetAddr>(fuzzed_data_provider); if (opt_net_addr) { - addr_man.Add(addresses, *opt_net_addr, fuzzed_data_provider.ConsumeIntegralInRange<int64_t>(0, 100000000)); + addr_man.Add(addresses, *opt_net_addr, std::chrono::seconds{ConsumeTime(fuzzed_data_provider, 0, 100000000)}); } }, [&] { const std::optional<CService> opt_service = ConsumeDeserializable<CService>(fuzzed_data_provider); if (opt_service) { - addr_man.Good(*opt_service, ConsumeTime(fuzzed_data_provider)); + addr_man.Good(*opt_service, NodeSeconds{std::chrono::seconds{ConsumeTime(fuzzed_data_provider)}}); } }, [&] { const std::optional<CService> opt_service = ConsumeDeserializable<CService>(fuzzed_data_provider); if (opt_service) { - addr_man.Attempt(*opt_service, fuzzed_data_provider.ConsumeBool(), ConsumeTime(fuzzed_data_provider)); + addr_man.Attempt(*opt_service, fuzzed_data_provider.ConsumeBool(), NodeSeconds{std::chrono::seconds{ConsumeTime(fuzzed_data_provider)}}); } }, [&] { const std::optional<CService> opt_service = ConsumeDeserializable<CService>(fuzzed_data_provider); if (opt_service) { - addr_man.Connected(*opt_service, ConsumeTime(fuzzed_data_provider)); + addr_man.Connected(*opt_service, NodeSeconds{std::chrono::seconds{ConsumeTime(fuzzed_data_provider)}}); } }, [&] { diff --git a/src/test/fuzz/autofile.cpp b/src/test/fuzz/autofile.cpp index 3b410930ed..1a8957d090 100644 --- a/src/test/fuzz/autofile.cpp +++ b/src/test/fuzz/autofile.cpp @@ -18,7 +18,7 @@ FUZZ_TARGET(autofile) { FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()}; FuzzedAutoFileProvider fuzzed_auto_file_provider = ConsumeAutoFile(fuzzed_data_provider); - CAutoFile auto_file = fuzzed_auto_file_provider.open(); + AutoFile auto_file{fuzzed_auto_file_provider.open()}; LIMITED_WHILE(fuzzed_data_provider.ConsumeBool(), 10000) { CallOneOf( fuzzed_data_provider, @@ -53,8 +53,6 @@ FUZZ_TARGET(autofile) }); } (void)auto_file.Get(); - (void)auto_file.GetType(); - (void)auto_file.GetVersion(); (void)auto_file.IsNull(); if (fuzzed_data_provider.ConsumeBool()) { FILE* f = auto_file.release(); diff --git a/src/test/fuzz/bitdeque.cpp b/src/test/fuzz/bitdeque.cpp new file mode 100644 index 0000000000..634a3de346 --- /dev/null +++ b/src/test/fuzz/bitdeque.cpp @@ -0,0 +1,541 @@ +// Copyright (c) 2022 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include <random.h> +#include <test/fuzz/FuzzedDataProvider.h> +#include <test/fuzz/util.h> +#include <util/bitdeque.h> + +#include <deque> +#include <vector> + +namespace { + +constexpr int LEN_BITS = 16; +constexpr int RANDDATA_BITS = 20; + +using bitdeque_type = bitdeque<128>; + +//! Deterministic random vector of bools, for begin/end insertions to draw from. +std::vector<bool> RANDDATA; + +void InitRandData() +{ + FastRandomContext ctx(true); + RANDDATA.clear(); + for (size_t i = 0; i < (1U << RANDDATA_BITS) + (1U << LEN_BITS); ++i) { + RANDDATA.push_back(ctx.randbool()); + } +} + +} // namespace + +FUZZ_TARGET_INIT(bitdeque, InitRandData) +{ + FuzzedDataProvider provider(buffer.data(), buffer.size()); + FastRandomContext ctx(true); + + size_t maxlen = (1U << provider.ConsumeIntegralInRange<size_t>(0, LEN_BITS)) - 1; + size_t limitlen = 4 * maxlen; + + std::deque<bool> deq; + bitdeque_type bitdeq; + + const auto& cdeq = deq; + const auto& cbitdeq = bitdeq; + + size_t initlen = provider.ConsumeIntegralInRange<size_t>(0, maxlen); + while (initlen) { + bool val = ctx.randbool(); + deq.push_back(val); + bitdeq.push_back(val); + --initlen; + } + + LIMITED_WHILE(provider.remaining_bytes() > 0, 900) + { + { + assert(deq.size() == bitdeq.size()); + auto it = deq.begin(); + auto bitit = bitdeq.begin(); + auto itend = deq.end(); + while (it != itend) { + assert(*it == *bitit); + ++it; + ++bitit; + } + } + + CallOneOf(provider, + [&] { + // constructor() + deq = std::deque<bool>{}; + bitdeq = bitdeque_type{}; + }, + [&] { + // clear() + deq.clear(); + bitdeq.clear(); + }, + [&] { + // resize() + auto count = provider.ConsumeIntegralInRange<size_t>(0, maxlen); + deq.resize(count); + bitdeq.resize(count); + }, + [&] { + // assign(count, val) + auto count = provider.ConsumeIntegralInRange<size_t>(0, maxlen); + bool val = ctx.randbool(); + deq.assign(count, val); + bitdeq.assign(count, val); + }, + [&] { + // constructor(count, val) + auto count = provider.ConsumeIntegralInRange<size_t>(0, maxlen); + bool val = ctx.randbool(); + deq = std::deque<bool>(count, val); + bitdeq = bitdeque_type(count, val); + }, + [&] { + // constructor(count) + auto count = provider.ConsumeIntegralInRange<size_t>(0, maxlen); + deq = std::deque<bool>(count); + bitdeq = bitdeque_type(count); + }, + [&] { + // construct(begin, end) + auto count = provider.ConsumeIntegralInRange<size_t>(0, maxlen); + auto rand_begin = RANDDATA.begin() + ctx.randbits(RANDDATA_BITS); + auto rand_end = rand_begin + count; + deq = std::deque<bool>(rand_begin, rand_end); + bitdeq = bitdeque_type(rand_begin, rand_end); + }, + [&] { + // assign(begin, end) + auto count = provider.ConsumeIntegralInRange<size_t>(0, maxlen); + auto rand_begin = RANDDATA.begin() + ctx.randbits(RANDDATA_BITS); + auto rand_end = rand_begin + count; + deq.assign(rand_begin, rand_end); + bitdeq.assign(rand_begin, rand_end); + }, + [&] { + // construct(initializer_list) + std::initializer_list<bool> ilist{ctx.randbool(), ctx.randbool(), ctx.randbool(), ctx.randbool(), ctx.randbool()}; + deq = std::deque<bool>(ilist); + bitdeq = bitdeque_type(ilist); + }, + [&] { + // assign(initializer_list) + std::initializer_list<bool> ilist{ctx.randbool(), ctx.randbool(), ctx.randbool()}; + deq.assign(ilist); + bitdeq.assign(ilist); + }, + [&] { + // operator=(const&) + auto count = provider.ConsumeIntegralInRange<size_t>(0, maxlen); + bool val = ctx.randbool(); + const std::deque<bool> deq2(count, val); + deq = deq2; + const bitdeque_type bitdeq2(count, val); + bitdeq = bitdeq2; + }, + [&] { + // operator=(&&) + auto count = provider.ConsumeIntegralInRange<size_t>(0, maxlen); + bool val = ctx.randbool(); + std::deque<bool> deq2(count, val); + deq = std::move(deq2); + bitdeque_type bitdeq2(count, val); + bitdeq = std::move(bitdeq2); + }, + [&] { + // deque swap + auto count = provider.ConsumeIntegralInRange<size_t>(0, maxlen); + auto rand_begin = RANDDATA.begin() + ctx.randbits(RANDDATA_BITS); + auto rand_end = rand_begin + count; + std::deque<bool> deq2(rand_begin, rand_end); + bitdeque_type bitdeq2(rand_begin, rand_end); + using std::swap; + assert(deq.size() == bitdeq.size()); + assert(deq2.size() == bitdeq2.size()); + swap(deq, deq2); + swap(bitdeq, bitdeq2); + assert(deq.size() == bitdeq.size()); + assert(deq2.size() == bitdeq2.size()); + }, + [&] { + // deque.swap + auto count = provider.ConsumeIntegralInRange<size_t>(0, maxlen); + auto rand_begin = RANDDATA.begin() + ctx.randbits(RANDDATA_BITS); + auto rand_end = rand_begin + count; + std::deque<bool> deq2(rand_begin, rand_end); + bitdeque_type bitdeq2(rand_begin, rand_end); + assert(deq.size() == bitdeq.size()); + assert(deq2.size() == bitdeq2.size()); + deq.swap(deq2); + bitdeq.swap(bitdeq2); + assert(deq.size() == bitdeq.size()); + assert(deq2.size() == bitdeq2.size()); + }, + [&] { + // operator=(initializer_list) + std::initializer_list<bool> ilist{ctx.randbool(), ctx.randbool(), ctx.randbool()}; + deq = ilist; + bitdeq = ilist; + }, + [&] { + // iterator arithmetic + auto pos1 = provider.ConsumeIntegralInRange<long>(0, cdeq.size()); + auto pos2 = provider.ConsumeIntegralInRange<long>(0, cdeq.size()); + auto it = deq.begin() + pos1; + auto bitit = bitdeq.begin() + pos1; + if ((size_t)pos1 != cdeq.size()) assert(*it == *bitit); + assert(it - deq.begin() == pos1); + assert(bitit - bitdeq.begin() == pos1); + if (provider.ConsumeBool()) { + it += pos2 - pos1; + bitit += pos2 - pos1; + } else { + it -= pos1 - pos2; + bitit -= pos1 - pos2; + } + if ((size_t)pos2 != cdeq.size()) assert(*it == *bitit); + assert(deq.end() - it == bitdeq.end() - bitit); + if (provider.ConsumeBool()) { + if ((size_t)pos2 != cdeq.size()) { + ++it; + ++bitit; + } + } else { + if (pos2 != 0) { + --it; + --bitit; + } + } + assert(deq.end() - it == bitdeq.end() - bitit); + }, + [&] { + // begin() and end() + assert(deq.end() - deq.begin() == bitdeq.end() - bitdeq.begin()); + }, + [&] { + // begin() and end() (const) + assert(cdeq.end() - cdeq.begin() == cbitdeq.end() - cbitdeq.begin()); + }, + [&] { + // rbegin() and rend() + assert(deq.rend() - deq.rbegin() == bitdeq.rend() - bitdeq.rbegin()); + }, + [&] { + // rbegin() and rend() (const) + assert(cdeq.rend() - cdeq.rbegin() == cbitdeq.rend() - cbitdeq.rbegin()); + }, + [&] { + // cbegin() and cend() + assert(cdeq.cend() - cdeq.cbegin() == cbitdeq.cend() - cbitdeq.cbegin()); + }, + [&] { + // crbegin() and crend() + assert(cdeq.crend() - cdeq.crbegin() == cbitdeq.crend() - cbitdeq.crbegin()); + }, + [&] { + // size() and maxsize() + assert(cdeq.size() == cbitdeq.size()); + assert(cbitdeq.size() <= cbitdeq.max_size()); + }, + [&] { + // empty + assert(cdeq.empty() == cbitdeq.empty()); + }, + [&] { + // at (in range) and flip + if (!cdeq.empty()) { + size_t pos = provider.ConsumeIntegralInRange<size_t>(0, cdeq.size() - 1); + auto& ref = deq.at(pos); + auto bitref = bitdeq.at(pos); + assert(ref == bitref); + if (ctx.randbool()) { + ref = !ref; + bitref.flip(); + } + } + }, + [&] { + // at (maybe out of range) and bit assign + size_t pos = provider.ConsumeIntegralInRange<size_t>(0, cdeq.size() + maxlen); + bool newval = ctx.randbool(); + bool throw_deq{false}, throw_bitdeq{false}; + bool val_deq{false}, val_bitdeq{false}; + try { + auto& ref = deq.at(pos); + val_deq = ref; + ref = newval; + } catch (const std::out_of_range&) { + throw_deq = true; + } + try { + auto ref = bitdeq.at(pos); + val_bitdeq = ref; + ref = newval; + } catch (const std::out_of_range&) { + throw_bitdeq = true; + } + assert(throw_deq == throw_bitdeq); + assert(throw_bitdeq == (pos >= cdeq.size())); + if (!throw_deq) assert(val_deq == val_bitdeq); + }, + [&] { + // at (maybe out of range) (const) + size_t pos = provider.ConsumeIntegralInRange<size_t>(0, cdeq.size() + maxlen); + bool throw_deq{false}, throw_bitdeq{false}; + bool val_deq{false}, val_bitdeq{false}; + try { + auto& ref = cdeq.at(pos); + val_deq = ref; + } catch (const std::out_of_range&) { + throw_deq = true; + } + try { + auto ref = cbitdeq.at(pos); + val_bitdeq = ref; + } catch (const std::out_of_range&) { + throw_bitdeq = true; + } + assert(throw_deq == throw_bitdeq); + assert(throw_bitdeq == (pos >= cdeq.size())); + if (!throw_deq) assert(val_deq == val_bitdeq); + }, + [&] { + // operator[] + if (!cdeq.empty()) { + size_t pos = provider.ConsumeIntegralInRange<size_t>(0, cdeq.size() - 1); + assert(deq[pos] == bitdeq[pos]); + if (ctx.randbool()) { + deq[pos] = !deq[pos]; + bitdeq[pos].flip(); + } + } + }, + [&] { + // operator[] const + if (!cdeq.empty()) { + size_t pos = provider.ConsumeIntegralInRange<size_t>(0, cdeq.size() - 1); + assert(deq[pos] == bitdeq[pos]); + } + }, + [&] { + // front() + if (!cdeq.empty()) { + auto& ref = deq.front(); + auto bitref = bitdeq.front(); + assert(ref == bitref); + if (ctx.randbool()) { + ref = !ref; + bitref = !bitref; + } + } + }, + [&] { + // front() const + if (!cdeq.empty()) { + auto& ref = cdeq.front(); + auto bitref = cbitdeq.front(); + assert(ref == bitref); + } + }, + [&] { + // back() and swap(bool, ref) + if (!cdeq.empty()) { + auto& ref = deq.back(); + auto bitref = bitdeq.back(); + assert(ref == bitref); + if (ctx.randbool()) { + ref = !ref; + bitref.flip(); + } + } + }, + [&] { + // back() const + if (!cdeq.empty()) { + const auto& cdeq = deq; + const auto& cbitdeq = bitdeq; + auto& ref = cdeq.back(); + auto bitref = cbitdeq.back(); + assert(ref == bitref); + } + }, + [&] { + // push_back() + if (cdeq.size() < limitlen) { + bool val = ctx.randbool(); + if (cdeq.empty()) { + deq.push_back(val); + bitdeq.push_back(val); + } else { + size_t pos = provider.ConsumeIntegralInRange<size_t>(0, cdeq.size() - 1); + auto& ref = deq[pos]; + auto bitref = bitdeq[pos]; + assert(ref == bitref); + deq.push_back(val); + bitdeq.push_back(val); + assert(ref == bitref); // references are not invalidated + } + } + }, + [&] { + // push_front() + if (cdeq.size() < limitlen) { + bool val = ctx.randbool(); + if (cdeq.empty()) { + deq.push_front(val); + bitdeq.push_front(val); + } else { + size_t pos = provider.ConsumeIntegralInRange<size_t>(0, cdeq.size() - 1); + auto& ref = deq[pos]; + auto bitref = bitdeq[pos]; + assert(ref == bitref); + deq.push_front(val); + bitdeq.push_front(val); + assert(ref == bitref); // references are not invalidated + } + } + }, + [&] { + // pop_back() + if (!cdeq.empty()) { + if (cdeq.size() == 1) { + deq.pop_back(); + bitdeq.pop_back(); + } else { + size_t pos = provider.ConsumeIntegralInRange<size_t>(0, cdeq.size() - 2); + auto& ref = deq[pos]; + auto bitref = bitdeq[pos]; + assert(ref == bitref); + deq.pop_back(); + bitdeq.pop_back(); + assert(ref == bitref); // references to other elements are not invalidated + } + } + }, + [&] { + // pop_front() + if (!cdeq.empty()) { + if (cdeq.size() == 1) { + deq.pop_front(); + bitdeq.pop_front(); + } else { + size_t pos = provider.ConsumeIntegralInRange<size_t>(1, cdeq.size() - 1); + auto& ref = deq[pos]; + auto bitref = bitdeq[pos]; + assert(ref == bitref); + deq.pop_front(); + bitdeq.pop_front(); + assert(ref == bitref); // references to other elements are not invalidated + } + } + }, + [&] { + // erase (in middle, single) + if (!cdeq.empty()) { + size_t before = provider.ConsumeIntegralInRange<size_t>(0, cdeq.size() - 1); + size_t after = cdeq.size() - 1 - before; + auto it = deq.erase(cdeq.begin() + before); + auto bitit = bitdeq.erase(cbitdeq.begin() + before); + assert(it == cdeq.begin() + before && it == cdeq.end() - after); + assert(bitit == cbitdeq.begin() + before && bitit == cbitdeq.end() - after); + } + }, + [&] { + // erase (at front, range) + size_t count = provider.ConsumeIntegralInRange<size_t>(0, cdeq.size()); + auto it = deq.erase(cdeq.begin(), cdeq.begin() + count); + auto bitit = bitdeq.erase(cbitdeq.begin(), cbitdeq.begin() + count); + assert(it == deq.begin()); + assert(bitit == bitdeq.begin()); + }, + [&] { + // erase (at back, range) + size_t count = provider.ConsumeIntegralInRange<size_t>(0, cdeq.size()); + auto it = deq.erase(cdeq.end() - count, cdeq.end()); + auto bitit = bitdeq.erase(cbitdeq.end() - count, cbitdeq.end()); + assert(it == deq.end()); + assert(bitit == bitdeq.end()); + }, + [&] { + // erase (in middle, range) + size_t count = provider.ConsumeIntegralInRange<size_t>(0, cdeq.size()); + size_t before = provider.ConsumeIntegralInRange<size_t>(0, cdeq.size() - count); + size_t after = cdeq.size() - count - before; + auto it = deq.erase(cdeq.begin() + before, cdeq.end() - after); + auto bitit = bitdeq.erase(cbitdeq.begin() + before, cbitdeq.end() - after); + assert(it == cdeq.begin() + before && it == cdeq.end() - after); + assert(bitit == cbitdeq.begin() + before && bitit == cbitdeq.end() - after); + }, + [&] { + // insert/emplace (in middle, single) + if (cdeq.size() < limitlen) { + size_t before = provider.ConsumeIntegralInRange<size_t>(0, cdeq.size()); + bool val = ctx.randbool(); + bool do_emplace = provider.ConsumeBool(); + auto it = deq.insert(cdeq.begin() + before, val); + auto bitit = do_emplace ? bitdeq.emplace(cbitdeq.begin() + before, val) + : bitdeq.insert(cbitdeq.begin() + before, val); + assert(it == deq.begin() + before); + assert(bitit == bitdeq.begin() + before); + } + }, + [&] { + // insert (at front, begin/end) + if (cdeq.size() < limitlen) { + size_t count = provider.ConsumeIntegralInRange<size_t>(0, maxlen); + auto rand_begin = RANDDATA.begin() + ctx.randbits(RANDDATA_BITS); + auto rand_end = rand_begin + count; + auto it = deq.insert(cdeq.begin(), rand_begin, rand_end); + auto bitit = bitdeq.insert(cbitdeq.begin(), rand_begin, rand_end); + assert(it == cdeq.begin()); + assert(bitit == cbitdeq.begin()); + } + }, + [&] { + // insert (at back, begin/end) + if (cdeq.size() < limitlen) { + size_t count = provider.ConsumeIntegralInRange<size_t>(0, maxlen); + auto rand_begin = RANDDATA.begin() + ctx.randbits(RANDDATA_BITS); + auto rand_end = rand_begin + count; + auto it = deq.insert(cdeq.end(), rand_begin, rand_end); + auto bitit = bitdeq.insert(cbitdeq.end(), rand_begin, rand_end); + assert(it == cdeq.end() - count); + assert(bitit == cbitdeq.end() - count); + } + }, + [&] { + // insert (in middle, range) + if (cdeq.size() < limitlen) { + size_t count = provider.ConsumeIntegralInRange<size_t>(0, maxlen); + size_t before = provider.ConsumeIntegralInRange<size_t>(0, cdeq.size()); + bool val = ctx.randbool(); + auto it = deq.insert(cdeq.begin() + before, count, val); + auto bitit = bitdeq.insert(cbitdeq.begin() + before, count, val); + assert(it == deq.begin() + before); + assert(bitit == bitdeq.begin() + before); + } + }, + [&] { + // insert (in middle, begin/end) + if (cdeq.size() < limitlen) { + size_t count = provider.ConsumeIntegralInRange<size_t>(0, maxlen); + size_t before = provider.ConsumeIntegralInRange<size_t>(0, cdeq.size()); + auto rand_begin = RANDDATA.begin() + ctx.randbits(RANDDATA_BITS); + auto rand_end = rand_begin + count; + auto it = deq.insert(cdeq.begin() + before, rand_begin, rand_end); + auto bitit = bitdeq.insert(cbitdeq.begin() + before, rand_begin, rand_end); + assert(it == deq.begin() + before); + assert(bitit == bitdeq.begin() + before); + } + } + ); + } +} diff --git a/src/test/fuzz/chain.cpp b/src/test/fuzz/chain.cpp index 8c0ed32d51..01edb06138 100644 --- a/src/test/fuzz/chain.cpp +++ b/src/test/fuzz/chain.cpp @@ -23,7 +23,7 @@ FUZZ_TARGET(chain) disk_block_index->phashBlock = &zero; { LOCK(::cs_main); - (void)disk_block_index->GetBlockHash(); + (void)disk_block_index->ConstructBlockHash(); (void)disk_block_index->GetBlockPos(); (void)disk_block_index->GetBlockTime(); (void)disk_block_index->GetBlockTimeMax(); @@ -31,7 +31,6 @@ FUZZ_TARGET(chain) (void)disk_block_index->GetUndoPos(); (void)disk_block_index->HaveTxsDownloaded(); (void)disk_block_index->IsValid(); - (void)disk_block_index->ToString(); } const CBlockHeader block_header = disk_block_index->GetBlockHeader(); diff --git a/src/test/fuzz/integer.cpp b/src/test/fuzz/integer.cpp index 72574612a2..f05248ab47 100644 --- a/src/test/fuzz/integer.cpp +++ b/src/test/fuzz/integer.cpp @@ -12,6 +12,7 @@ #include <key_io.h> #include <memusage.h> #include <netbase.h> +#include <policy/policy.h> #include <policy/settings.h> #include <pow.h> #include <protocol.h> @@ -87,9 +88,6 @@ FUZZ_TARGET_INIT(integer, initialize_integer) } (void)GetSizeOfCompactSize(u64); (void)GetSpecialScriptSize(u32); - if (!MultiplicationOverflow(i64, static_cast<int64_t>(::nBytesPerSigOp)) && !AdditionOverflow(i64 * ::nBytesPerSigOp, static_cast<int64_t>(4))) { - (void)GetVirtualTransactionSize(i64, i64); - } if (!MultiplicationOverflow(i64, static_cast<int64_t>(u32)) && !AdditionOverflow(i64, static_cast<int64_t>(4)) && !AdditionOverflow(i64 * u32, static_cast<int64_t>(4))) { (void)GetVirtualTransactionSize(i64, i64, u32); } diff --git a/src/test/fuzz/key.cpp b/src/test/fuzz/key.cpp index bfea9778f4..a76901e473 100644 --- a/src/test/fuzz/key.cpp +++ b/src/test/fuzz/key.cpp @@ -138,8 +138,6 @@ FUZZ_TARGET_INIT(key, initialize_key) assert(tx_multisig_script.size() == 37); FillableSigningProvider fillable_signing_provider; - assert(IsSolvable(fillable_signing_provider, tx_pubkey_script)); - assert(IsSolvable(fillable_signing_provider, tx_multisig_script)); assert(!IsSegWitOutput(fillable_signing_provider, tx_pubkey_script)); assert(!IsSegWitOutput(fillable_signing_provider, tx_multisig_script)); assert(fillable_signing_provider.GetKeys().size() == 0); @@ -157,12 +155,12 @@ FUZZ_TARGET_INIT(key, initialize_key) assert(fillable_signing_provider_pub.HaveKey(pubkey.GetID())); TxoutType which_type_tx_pubkey; - const bool is_standard_tx_pubkey = IsStandard(tx_pubkey_script, which_type_tx_pubkey); + const bool is_standard_tx_pubkey = IsStandard(tx_pubkey_script, std::nullopt, which_type_tx_pubkey); assert(is_standard_tx_pubkey); assert(which_type_tx_pubkey == TxoutType::PUBKEY); TxoutType which_type_tx_multisig; - const bool is_standard_tx_multisig = IsStandard(tx_multisig_script, which_type_tx_multisig); + const bool is_standard_tx_multisig = IsStandard(tx_multisig_script, std::nullopt, which_type_tx_multisig); assert(is_standard_tx_multisig); assert(which_type_tx_multisig == TxoutType::MULTISIG); diff --git a/src/test/fuzz/load_external_block_file.cpp b/src/test/fuzz/load_external_block_file.cpp index bfa977520b..f4b7dc08fd 100644 --- a/src/test/fuzz/load_external_block_file.cpp +++ b/src/test/fuzz/load_external_block_file.cpp @@ -31,6 +31,13 @@ FUZZ_TARGET_INIT(load_external_block_file, initialize_load_external_block_file) if (fuzzed_block_file == nullptr) { return; } - FlatFilePos flat_file_pos; - g_setup->m_node.chainman->ActiveChainstate().LoadExternalBlockFile(fuzzed_block_file, fuzzed_data_provider.ConsumeBool() ? &flat_file_pos : nullptr); + if (fuzzed_data_provider.ConsumeBool()) { + // Corresponds to the -reindex case (track orphan blocks across files). + FlatFilePos flat_file_pos; + std::multimap<uint256, FlatFilePos> blocks_with_unknown_parent; + g_setup->m_node.chainman->ActiveChainstate().LoadExternalBlockFile(fuzzed_block_file, &flat_file_pos, &blocks_with_unknown_parent); + } else { + // Corresponds to the -loadblock= case (orphan blocks aren't tracked across files). + g_setup->m_node.chainman->ActiveChainstate().LoadExternalBlockFile(fuzzed_block_file); + } } diff --git a/src/test/fuzz/net.cpp b/src/test/fuzz/net.cpp index 4981287152..741810f6a2 100644 --- a/src/test/fuzz/net.cpp +++ b/src/test/fuzz/net.cpp @@ -68,7 +68,6 @@ FUZZ_TARGET_INIT(net, initialize_net) (void)node.GetAddrLocal(); (void)node.GetId(); (void)node.GetLocalNonce(); - (void)node.GetLocalServices(); const int ref_count = node.GetRefCount(); assert(ref_count >= 0); (void)node.GetCommonVersion(); diff --git a/src/test/fuzz/node_eviction.cpp b/src/test/fuzz/node_eviction.cpp index 6a363f00f7..e27b254580 100644 --- a/src/test/fuzz/node_eviction.cpp +++ b/src/test/fuzz/node_eviction.cpp @@ -32,6 +32,8 @@ FUZZ_TARGET(node_eviction) /*prefer_evict=*/fuzzed_data_provider.ConsumeBool(), /*m_is_local=*/fuzzed_data_provider.ConsumeBool(), /*m_network=*/fuzzed_data_provider.PickValueInArray(ALL_NETWORKS), + /*m_noban=*/fuzzed_data_provider.ConsumeBool(), + /*m_conn_type=*/fuzzed_data_provider.PickValueInArray(ALL_CONNECTION_TYPES), }); } // Make a copy since eviction_candidates may be in some valid but otherwise diff --git a/src/test/fuzz/parse_univalue.cpp b/src/test/fuzz/parse_univalue.cpp index c7a76aa52f..0cc210f26f 100644 --- a/src/test/fuzz/parse_univalue.cpp +++ b/src/test/fuzz/parse_univalue.cpp @@ -26,7 +26,7 @@ FUZZ_TARGET_INIT(parse_univalue, initialize_parse_univalue) return ParseNonRFCJSONValue(random_string); } catch (const std::runtime_error&) { valid = false; - return NullUniValue; + return UniValue{}; } }(); if (!valid) { diff --git a/src/test/fuzz/policy_estimator.cpp b/src/test/fuzz/policy_estimator.cpp index e4d95f72a0..a3d57dbdd5 100644 --- a/src/test/fuzz/policy_estimator.cpp +++ b/src/test/fuzz/policy_estimator.cpp @@ -3,10 +3,12 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include <policy/fees.h> +#include <policy/fees_args.h> #include <primitives/transaction.h> #include <test/fuzz/FuzzedDataProvider.h> #include <test/fuzz/fuzz.h> #include <test/fuzz/util.h> +#include <test/fuzz/util/mempool.h> #include <test/util/setup_common.h> #include <txmempool.h> @@ -15,15 +17,20 @@ #include <string> #include <vector> +namespace { +const BasicTestingSetup* g_setup; +} // namespace + void initialize_policy_estimator() { static const auto testing_setup = MakeNoLogFileContext<>(); + g_setup = testing_setup.get(); } FUZZ_TARGET_INIT(policy_estimator, initialize_policy_estimator) { FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size()); - CBlockPolicyEstimator block_policy_estimator; + CBlockPolicyEstimator block_policy_estimator{FeeestPath(*g_setup->m_node.args)}; LIMITED_WHILE(fuzzed_data_provider.ConsumeBool(), 10000) { CallOneOf( fuzzed_data_provider, @@ -70,7 +77,7 @@ FUZZ_TARGET_INIT(policy_estimator, initialize_policy_estimator) } { FuzzedAutoFileProvider fuzzed_auto_file_provider = ConsumeAutoFile(fuzzed_data_provider); - CAutoFile fuzzed_auto_file = fuzzed_auto_file_provider.open(); + AutoFile fuzzed_auto_file{fuzzed_auto_file_provider.open()}; block_policy_estimator.Write(fuzzed_auto_file); block_policy_estimator.Read(fuzzed_auto_file); } diff --git a/src/test/fuzz/policy_estimator_io.cpp b/src/test/fuzz/policy_estimator_io.cpp index 9021d95954..436873c955 100644 --- a/src/test/fuzz/policy_estimator_io.cpp +++ b/src/test/fuzz/policy_estimator_io.cpp @@ -3,6 +3,7 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include <policy/fees.h> +#include <policy/fees_args.h> #include <test/fuzz/FuzzedDataProvider.h> #include <test/fuzz/fuzz.h> #include <test/fuzz/util.h> @@ -11,18 +12,23 @@ #include <cstdint> #include <vector> +namespace { +const BasicTestingSetup* g_setup; +} // namespace + void initialize_policy_estimator_io() { static const auto testing_setup = MakeNoLogFileContext<>(); + g_setup = testing_setup.get(); } FUZZ_TARGET_INIT(policy_estimator_io, initialize_policy_estimator_io) { FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size()); FuzzedAutoFileProvider fuzzed_auto_file_provider = ConsumeAutoFile(fuzzed_data_provider); - CAutoFile fuzzed_auto_file = fuzzed_auto_file_provider.open(); + AutoFile fuzzed_auto_file{fuzzed_auto_file_provider.open()}; // Re-using block_policy_estimator across runs to avoid costly creation of CBlockPolicyEstimator object. - static CBlockPolicyEstimator block_policy_estimator; + static CBlockPolicyEstimator block_policy_estimator{FeeestPath(*g_setup->m_node.args)}; if (block_policy_estimator.Read(fuzzed_auto_file)) { block_policy_estimator.Write(fuzzed_auto_file); } diff --git a/src/test/fuzz/pow.cpp b/src/test/fuzz/pow.cpp index 0004d82d66..eba03da773 100644 --- a/src/test/fuzz/pow.cpp +++ b/src/test/fuzz/pow.cpp @@ -83,3 +83,40 @@ FUZZ_TARGET_INIT(pow, initialize_pow) } } } + + +FUZZ_TARGET_INIT(pow_transition, initialize_pow) +{ + FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size()); + const Consensus::Params& consensus_params{Params().GetConsensus()}; + std::vector<std::unique_ptr<CBlockIndex>> blocks; + + const uint32_t old_time{fuzzed_data_provider.ConsumeIntegral<uint32_t>()}; + const uint32_t new_time{fuzzed_data_provider.ConsumeIntegral<uint32_t>()}; + const int32_t version{fuzzed_data_provider.ConsumeIntegral<int32_t>()}; + uint32_t nbits{fuzzed_data_provider.ConsumeIntegral<uint32_t>()}; + + const arith_uint256 pow_limit = UintToArith256(consensus_params.powLimit); + arith_uint256 old_target; + old_target.SetCompact(nbits); + if (old_target > pow_limit) { + nbits = pow_limit.GetCompact(); + } + // Create one difficulty adjustment period worth of headers + for (int height = 0; height < consensus_params.DifficultyAdjustmentInterval(); ++height) { + CBlockHeader header; + header.nVersion = version; + header.nTime = old_time; + header.nBits = nbits; + if (height == consensus_params.DifficultyAdjustmentInterval() - 1) { + header.nTime = new_time; + } + auto current_block{std::make_unique<CBlockIndex>(header)}; + current_block->pprev = blocks.empty() ? nullptr : blocks.back().get(); + current_block->nHeight = height; + blocks.emplace_back(std::move(current_block)); + } + auto last_block{blocks.back().get()}; + unsigned int new_nbits{GetNextWorkRequired(last_block, nullptr, consensus_params)}; + Assert(PermittedDifficultyTransition(consensus_params, last_block->nHeight + 1, last_block->nBits, new_nbits)); +} diff --git a/src/test/fuzz/process_message.cpp b/src/test/fuzz/process_message.cpp index 1763cd8af3..f6a35da4fc 100644 --- a/src/test/fuzz/process_message.cpp +++ b/src/test/fuzz/process_message.cpp @@ -73,6 +73,8 @@ void fuzz_target(FuzzBufferType buffer, const std::string& LIMIT_TO_MESSAGE_TYPE SetMockTime(1610000000); // any time to successfully reset ibd chainstate.ResetIbd(); + LOCK(NetEventsInterface::g_msgproc_mutex); + const std::string random_message_type{fuzzed_data_provider.ConsumeBytesAsString(CMessageHeader::COMMAND_SIZE).c_str()}; if (!LIMIT_TO_MESSAGE_TYPE.empty() && random_message_type != LIMIT_TO_MESSAGE_TYPE) { return; @@ -80,8 +82,7 @@ void fuzz_target(FuzzBufferType buffer, const std::string& LIMIT_TO_MESSAGE_TYPE CNode& p2p_node = *ConsumeNodeAsUniquePtr(fuzzed_data_provider).release(); connman.AddTestNode(p2p_node); - g_setup->m_node.peerman->InitializeNode(&p2p_node); - FillNode(fuzzed_data_provider, connman, *g_setup->m_node.peerman, p2p_node); + FillNode(fuzzed_data_provider, connman, p2p_node); const auto mock_time = ConsumeTime(fuzzed_data_provider); SetMockTime(mock_time); @@ -93,10 +94,7 @@ void fuzz_target(FuzzBufferType buffer, const std::string& LIMIT_TO_MESSAGE_TYPE GetTime<std::chrono::microseconds>(), std::atomic<bool>{false}); } catch (const std::ios_base::failure&) { } - { - LOCK(p2p_node.cs_sendProcessing); - g_setup->m_node.peerman->SendMessages(&p2p_node); - } + g_setup->m_node.peerman->SendMessages(&p2p_node); SyncWithValidationInterfaceQueue(); g_setup->m_node.connman->StopNodes(); } diff --git a/src/test/fuzz/process_messages.cpp b/src/test/fuzz/process_messages.cpp index e1c11e1afd..1df1717ec3 100644 --- a/src/test/fuzz/process_messages.cpp +++ b/src/test/fuzz/process_messages.cpp @@ -40,14 +40,15 @@ FUZZ_TARGET_INIT(process_messages, initialize_process_messages) SetMockTime(1610000000); // any time to successfully reset ibd chainstate.ResetIbd(); + LOCK(NetEventsInterface::g_msgproc_mutex); + std::vector<CNode*> peers; const auto num_peers_to_add = fuzzed_data_provider.ConsumeIntegralInRange(1, 3); for (int i = 0; i < num_peers_to_add; ++i) { peers.push_back(ConsumeNodeAsUniquePtr(fuzzed_data_provider, i).release()); CNode& p2p_node = *peers.back(); - g_setup->m_node.peerman->InitializeNode(&p2p_node); - FillNode(fuzzed_data_provider, connman, *g_setup->m_node.peerman, p2p_node); + FillNode(fuzzed_data_provider, connman, p2p_node); connman.AddTestNode(p2p_node); } @@ -71,10 +72,7 @@ FUZZ_TARGET_INIT(process_messages, initialize_process_messages) connman.ProcessMessagesOnce(random_node); } catch (const std::ios_base::failure&) { } - { - LOCK(random_node.cs_sendProcessing); - g_setup->m_node.peerman->SendMessages(&random_node); - } + g_setup->m_node.peerman->SendMessages(&random_node); } SyncWithValidationInterfaceQueue(); g_setup->m_node.connman->StopNodes(); diff --git a/src/test/fuzz/rbf.cpp b/src/test/fuzz/rbf.cpp index 8dcaa609b5..e06e57d919 100644 --- a/src/test/fuzz/rbf.cpp +++ b/src/test/fuzz/rbf.cpp @@ -2,12 +2,15 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. +#include <node/mempool_args.h> #include <policy/rbf.h> #include <primitives/transaction.h> #include <sync.h> #include <test/fuzz/FuzzedDataProvider.h> #include <test/fuzz/fuzz.h> #include <test/fuzz/util.h> +#include <test/fuzz/util/mempool.h> +#include <test/util/setup_common.h> #include <txmempool.h> #include <cstdint> @@ -15,7 +18,17 @@ #include <string> #include <vector> -FUZZ_TARGET(rbf) +namespace { +const BasicTestingSetup* g_setup; +} // namespace + +void initialize_rbf() +{ + static const auto testing_setup = MakeNoLogFileContext<>(); + g_setup = testing_setup.get(); +} + +FUZZ_TARGET_INIT(rbf, initialize_rbf) { FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size()); SetMockTime(ConsumeTime(fuzzed_data_provider)); @@ -23,8 +36,11 @@ FUZZ_TARGET(rbf) if (!mtx) { return; } - CTxMemPool pool; - LIMITED_WHILE(fuzzed_data_provider.ConsumeBool(), 10000) { + + CTxMemPool pool{MemPoolOptionsForTest(g_setup->m_node)}; + + LIMITED_WHILE(fuzzed_data_provider.ConsumeBool(), 10000) + { const std::optional<CMutableTransaction> another_mtx = ConsumeDeserializable<CMutableTransaction>(fuzzed_data_provider); if (!another_mtx) { break; diff --git a/src/test/fuzz/rpc.cpp b/src/test/fuzz/rpc.cpp index e4e83c3f32..26913a41d2 100644 --- a/src/test/fuzz/rpc.cpp +++ b/src/test/fuzz/rpc.cpp @@ -159,6 +159,7 @@ const std::vector<std::string> RPC_COMMANDS_SAFE_FOR_FUZZING{ "signrawtransactionwithkey", "submitblock", "submitheader", + "submitpackage", "syncwithvalidationinterfacequeue", "testmempoolaccept", "uptime", diff --git a/src/test/fuzz/script.cpp b/src/test/fuzz/script.cpp index fdcd0da37d..00d7b7e29a 100644 --- a/src/test/fuzz/script.cpp +++ b/src/test/fuzz/script.cpp @@ -55,7 +55,7 @@ FUZZ_TARGET_INIT(script, initialize_script) } TxoutType which_type; - bool is_standard_ret = IsStandard(script, which_type); + bool is_standard_ret = IsStandard(script, std::nullopt, which_type); if (!is_standard_ret) { assert(which_type == TxoutType::NONSTANDARD || which_type == TxoutType::NULL_DATA || @@ -89,7 +89,6 @@ FUZZ_TARGET_INIT(script, initialize_script) const FlatSigningProvider signing_provider; (void)InferDescriptor(script, signing_provider); (void)IsSegWitOutput(signing_provider, script); - (void)IsSolvable(signing_provider, script); (void)RecursiveDynamicUsage(script); diff --git a/src/test/fuzz/script_sigcache.cpp b/src/test/fuzz/script_sigcache.cpp index f7e45d6889..f6af7947df 100644 --- a/src/test/fuzz/script_sigcache.cpp +++ b/src/test/fuzz/script_sigcache.cpp @@ -10,18 +10,21 @@ #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> #include <string> #include <vector> +namespace { +const BasicTestingSetup* g_setup; +} // namespace + void initialize_script_sigcache() { - static const ECCVerifyHandle ecc_verify_handle; - ECC_Start(); - SelectParams(CBaseChainParams::REGTEST); - InitSignatureCache(); + static const auto testing_setup = MakeNoLogFileContext<>(); + g_setup = testing_setup.get(); } FUZZ_TARGET_INIT(script_sigcache, initialize_script_sigcache) diff --git a/src/test/fuzz/transaction.cpp b/src/test/fuzz/transaction.cpp index 273aa0dc5c..7fa4523800 100644 --- a/src/test/fuzz/transaction.cpp +++ b/src/test/fuzz/transaction.cpp @@ -69,8 +69,8 @@ FUZZ_TARGET_INIT(transaction, initialize_transaction) const CFeeRate dust_relay_fee{DUST_RELAY_TX_FEE}; std::string reason; - const bool is_standard_with_permit_bare_multisig = IsStandardTx(tx, /* permit_bare_multisig= */ true, dust_relay_fee, reason); - const bool is_standard_without_permit_bare_multisig = IsStandardTx(tx, /* permit_bare_multisig= */ false, dust_relay_fee, reason); + const bool is_standard_with_permit_bare_multisig = IsStandardTx(tx, std::nullopt, /* permit_bare_multisig= */ true, dust_relay_fee, reason); + const bool is_standard_without_permit_bare_multisig = IsStandardTx(tx, std::nullopt, /* permit_bare_multisig= */ false, dust_relay_fee, reason); if (is_standard_without_permit_bare_multisig) { assert(is_standard_with_permit_bare_multisig); } @@ -92,7 +92,6 @@ FUZZ_TARGET_INIT(transaction, initialize_transaction) (void)GetTransactionWeight(tx); (void)GetVirtualTransactionSize(tx); (void)IsFinalTx(tx, /* nBlockHeight= */ 1024, /* nBlockTime= */ 1024); - (void)IsStandardTx(tx, reason); (void)RecursiveDynamicUsage(tx); (void)SignalsOptInRBF(tx); diff --git a/src/test/fuzz/tx_pool.cpp b/src/test/fuzz/tx_pool.cpp index 771d7a11cb..a34e501fcc 100644 --- a/src/test/fuzz/tx_pool.cpp +++ b/src/test/fuzz/tx_pool.cpp @@ -3,10 +3,13 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include <consensus/validation.h> +#include <node/context.h> +#include <node/mempool_args.h> #include <node/miner.h> #include <test/fuzz/FuzzedDataProvider.h> #include <test/fuzz/fuzz.h> #include <test/fuzz/util.h> +#include <test/fuzz/util/mempool.h> #include <test/util/mining.h> #include <test/util/script.h> #include <test/util/setup_common.h> @@ -15,6 +18,7 @@ #include <validationinterface.h> using node::BlockAssembler; +using node::NodeContext; namespace { @@ -31,15 +35,6 @@ struct MockedTxPool : public CTxMemPool { } }; -class DummyChainState final : public CChainState -{ -public: - void SetMempool(CTxMemPool* mempool) - { - m_mempool = mempool; - } -}; - void initialize_tx_pool() { static const auto testing_setup = MakeNoLogFileContext<const TestingSetup>(); @@ -90,14 +85,14 @@ void SetMempoolConstraints(ArgsManager& args, FuzzedDataProvider& fuzzed_data_pr ToString(fuzzed_data_provider.ConsumeIntegralInRange<unsigned>(0, 999))); } -void Finish(FuzzedDataProvider& fuzzed_data_provider, MockedTxPool& tx_pool, CChainState& chainstate) +void Finish(FuzzedDataProvider& fuzzed_data_provider, MockedTxPool& tx_pool, Chainstate& chainstate) { WITH_LOCK(::cs_main, tx_pool.check(chainstate.CoinsTip(), chainstate.m_chain.Height() + 1)); { BlockAssembler::Options options; options.nBlockMaxWeight = fuzzed_data_provider.ConsumeIntegralInRange(0U, MAX_BLOCK_WEIGHT); options.blockMinFeeRate = CFeeRate{ConsumeMoney(fuzzed_data_provider, /*max=*/COIN)}; - auto assembler = BlockAssembler{chainstate, *static_cast<CTxMemPool*>(&tx_pool), options}; + auto assembler = BlockAssembler{chainstate, &tx_pool, options}; auto block_template = assembler.CreateNewBlock(CScript{} << OP_TRUE); Assert(block_template->block.vtx.size() >= 1); } @@ -113,7 +108,7 @@ void Finish(FuzzedDataProvider& fuzzed_data_provider, MockedTxPool& tx_pool, CCh SyncWithValidationInterfaceQueue(); } -void MockTime(FuzzedDataProvider& fuzzed_data_provider, const CChainState& chainstate) +void MockTime(FuzzedDataProvider& fuzzed_data_provider, const Chainstate& chainstate) { const auto time = ConsumeTime(fuzzed_data_provider, chainstate.m_chain.Tip()->GetMedianTimePast() + 1, @@ -121,6 +116,20 @@ void MockTime(FuzzedDataProvider& fuzzed_data_provider, const CChainState& chain SetMockTime(time); } +CTxMemPool MakeMempool(FuzzedDataProvider& fuzzed_data_provider, const NodeContext& node) +{ + // Take the default options for tests... + CTxMemPool::Options mempool_opts{MemPoolOptionsForTest(node)}; + + // ...override specific options for this specific fuzz suite + mempool_opts.estimator = nullptr; + mempool_opts.check_ratio = 1; + mempool_opts.require_standard = fuzzed_data_provider.ConsumeBool(); + + // ...and construct a CTxMemPool from it + return CTxMemPool{mempool_opts}; +} + FUZZ_TARGET_INIT(tx_pool_standard, initialize_tx_pool) { FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size()); @@ -128,7 +137,6 @@ FUZZ_TARGET_INIT(tx_pool_standard, initialize_tx_pool) auto& chainstate{static_cast<DummyChainState&>(node.chainman->ActiveChainstate())}; MockTime(fuzzed_data_provider, chainstate); - SetMempoolConstraints(*node.args, fuzzed_data_provider); // All RBF-spendable outpoints std::set<COutPoint> outpoints_rbf; @@ -142,7 +150,8 @@ FUZZ_TARGET_INIT(tx_pool_standard, initialize_tx_pool) // 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}; + SetMempoolConstraints(*node.args, fuzzed_data_provider); + CTxMemPool tx_pool_{MakeMempool(fuzzed_data_provider, node)}; MockedTxPool& tx_pool = *static_cast<MockedTxPool*>(&tx_pool_); chainstate.SetMempool(&tx_pool); @@ -213,9 +222,6 @@ FUZZ_TARGET_INIT(tx_pool_standard, initialize_tx_pool) MockTime(fuzzed_data_provider, chainstate); } if (fuzzed_data_provider.ConsumeBool()) { - SetMempoolConstraints(*node.args, fuzzed_data_provider); - } - if (fuzzed_data_provider.ConsumeBool()) { tx_pool.RollingFeeUpdate(); } if (fuzzed_data_provider.ConsumeBool()) { @@ -232,7 +238,6 @@ FUZZ_TARGET_INIT(tx_pool_standard, initialize_tx_pool) auto txr = std::make_shared<TransactionsDelta>(removed, added); RegisterSharedValidationInterface(txr); const bool bypass_limits = fuzzed_data_provider.ConsumeBool(); - ::fRequireStandard = fuzzed_data_provider.ConsumeBool(); // Make sure ProcessNewPackage on one transaction works. // The result is not guaranteed to be the same as what is returned by ATMP. @@ -308,7 +313,6 @@ FUZZ_TARGET_INIT(tx_pool, initialize_tx_pool) auto& chainstate = node.chainman->ActiveChainstate(); MockTime(fuzzed_data_provider, chainstate); - SetMempoolConstraints(*node.args, fuzzed_data_provider); std::vector<uint256> txids; for (const auto& outpoint : g_outpoints_coinbase_init_mature) { @@ -320,7 +324,8 @@ FUZZ_TARGET_INIT(tx_pool, initialize_tx_pool) txids.push_back(ConsumeUInt256(fuzzed_data_provider)); } - CTxMemPool tx_pool_{/*estimator=*/nullptr, /*check_ratio=*/1}; + SetMempoolConstraints(*node.args, fuzzed_data_provider); + CTxMemPool tx_pool_{MakeMempool(fuzzed_data_provider, node)}; MockedTxPool& tx_pool = *static_cast<MockedTxPool*>(&tx_pool_); LIMITED_WHILE(fuzzed_data_provider.ConsumeBool(), 300) @@ -331,9 +336,6 @@ FUZZ_TARGET_INIT(tx_pool, initialize_tx_pool) MockTime(fuzzed_data_provider, chainstate); } if (fuzzed_data_provider.ConsumeBool()) { - SetMempoolConstraints(*node.args, fuzzed_data_provider); - } - if (fuzzed_data_provider.ConsumeBool()) { tx_pool.RollingFeeUpdate(); } if (fuzzed_data_provider.ConsumeBool()) { @@ -346,7 +348,6 @@ FUZZ_TARGET_INIT(tx_pool, initialize_tx_pool) 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(chainstate, tx, GetTime(), bypass_limits, /*test_accept=*/false)); const bool accepted = res.m_result_type == MempoolAcceptResult::ResultType::VALID; if (accepted) { diff --git a/src/test/fuzz/txorphan.cpp b/src/test/fuzz/txorphan.cpp new file mode 100644 index 0000000000..55060f31cf --- /dev/null +++ b/src/test/fuzz/txorphan.cpp @@ -0,0 +1,146 @@ +// Copyright (c) 2022 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include <consensus/amount.h> +#include <consensus/validation.h> +#include <net_processing.h> +#include <node/eviction.h> +#include <policy/policy.h> +#include <primitives/transaction.h> +#include <script/script.h> +#include <sync.h> +#include <test/fuzz/FuzzedDataProvider.h> +#include <test/fuzz/fuzz.h> +#include <test/fuzz/util.h> +#include <test/util/setup_common.h> +#include <txorphanage.h> +#include <uint256.h> +#include <util/check.h> +#include <util/time.h> + +#include <cstdint> +#include <memory> +#include <set> +#include <utility> +#include <vector> + +void initialize_orphanage() +{ + static const auto testing_setup = MakeNoLogFileContext(); +} + +FUZZ_TARGET_INIT(txorphan, initialize_orphanage) +{ + FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size()); + SetMockTime(ConsumeTime(fuzzed_data_provider)); + + TxOrphanage orphanage; + std::set<uint256> orphan_work_set; + std::vector<COutPoint> outpoints; + // initial outpoints used to construct transactions later + for (uint8_t i = 0; i < 4; i++) { + outpoints.emplace_back(uint256{i}, 0); + } + // if true, allow duplicate input when constructing tx + const bool duplicate_input = fuzzed_data_provider.ConsumeBool(); + + LIMITED_WHILE(outpoints.size() < 200'000 && fuzzed_data_provider.ConsumeBool(), 10 * DEFAULT_MAX_ORPHAN_TRANSACTIONS) + { + // construct transaction + const CTransactionRef tx = [&] { + CMutableTransaction tx_mut; + const auto num_in = fuzzed_data_provider.ConsumeIntegralInRange<uint32_t>(1, outpoints.size()); + const auto num_out = fuzzed_data_provider.ConsumeIntegralInRange<uint32_t>(1, outpoints.size()); + // pick unique outpoints from outpoints as input + for (uint32_t i = 0; i < num_in; i++) { + auto& prevout = PickValue(fuzzed_data_provider, outpoints); + tx_mut.vin.emplace_back(prevout); + // pop the picked outpoint if duplicate input is not allowed + if (!duplicate_input) { + std::swap(prevout, outpoints.back()); + outpoints.pop_back(); + } + } + // output amount will not affect txorphanage + for (uint32_t i = 0; i < num_out; i++) { + tx_mut.vout.emplace_back(CAmount{0}, CScript{}); + } + // restore previously popped outpoints + for (auto& in : tx_mut.vin) { + outpoints.push_back(in.prevout); + } + const auto new_tx = MakeTransactionRef(tx_mut); + // add newly constructed transaction to outpoints + for (uint32_t i = 0; i < num_out; i++) { + outpoints.emplace_back(new_tx->GetHash(), i); + } + return new_tx; + }(); + + // trigger orphanage functions + LIMITED_WHILE(fuzzed_data_provider.ConsumeBool(), 10 * DEFAULT_MAX_ORPHAN_TRANSACTIONS) + { + NodeId peer_id = fuzzed_data_provider.ConsumeIntegral<NodeId>(); + + CallOneOf( + fuzzed_data_provider, + [&] { + LOCK(g_cs_orphans); + orphanage.AddChildrenToWorkSet(*tx, orphan_work_set); + }, + [&] { + bool have_tx = orphanage.HaveTx(GenTxid::Txid(tx->GetHash())) || orphanage.HaveTx(GenTxid::Wtxid(tx->GetHash())); + { + LOCK(g_cs_orphans); + bool get_tx = orphanage.GetTx(tx->GetHash()).first != nullptr; + Assert(have_tx == get_tx); + } + }, + [&] { + bool have_tx = orphanage.HaveTx(GenTxid::Txid(tx->GetHash())) || orphanage.HaveTx(GenTxid::Wtxid(tx->GetHash())); + // AddTx should return false if tx is too big or already have it + // tx weight is unknown, we only check when tx is already in orphanage + { + LOCK(g_cs_orphans); + bool add_tx = orphanage.AddTx(tx, peer_id); + // have_tx == true -> add_tx == false + Assert(!have_tx || !add_tx); + } + have_tx = orphanage.HaveTx(GenTxid::Txid(tx->GetHash())) || orphanage.HaveTx(GenTxid::Wtxid(tx->GetHash())); + { + LOCK(g_cs_orphans); + bool add_tx = orphanage.AddTx(tx, peer_id); + // if have_tx is still false, it must be too big + Assert(!have_tx == (GetTransactionWeight(*tx) > MAX_STANDARD_TX_WEIGHT)); + Assert(!have_tx || !add_tx); + } + }, + [&] { + bool have_tx = orphanage.HaveTx(GenTxid::Txid(tx->GetHash())) || orphanage.HaveTx(GenTxid::Wtxid(tx->GetHash())); + // EraseTx should return 0 if m_orphans doesn't have the tx + { + LOCK(g_cs_orphans); + Assert(have_tx == orphanage.EraseTx(tx->GetHash())); + } + have_tx = orphanage.HaveTx(GenTxid::Txid(tx->GetHash())) || orphanage.HaveTx(GenTxid::Wtxid(tx->GetHash())); + // have_tx should be false and EraseTx should fail + { + LOCK(g_cs_orphans); + Assert(!have_tx && !orphanage.EraseTx(tx->GetHash())); + } + }, + [&] { + LOCK(g_cs_orphans); + orphanage.EraseForPeer(peer_id); + }, + [&] { + // test mocktime and expiry + SetMockTime(ConsumeTime(fuzzed_data_provider)); + auto limit = fuzzed_data_provider.ConsumeIntegral<unsigned int>(); + WITH_LOCK(g_cs_orphans, orphanage.LimitOrphans(limit)); + Assert(orphanage.Size() <= limit); + }); + } + } +} diff --git a/src/test/fuzz/util.cpp b/src/test/fuzz/util.cpp index 033c6e18d5..0080b1e56e 100644 --- a/src/test/fuzz/util.cpp +++ b/src/test/fuzz/util.cpp @@ -24,10 +24,10 @@ FuzzedSock::FuzzedSock(FuzzedDataProvider& fuzzed_data_provider) FuzzedSock::~FuzzedSock() { // Sock::~Sock() will be called after FuzzedSock::~FuzzedSock() and it will call - // Sock::Reset() (not FuzzedSock::Reset()!) which will call CloseSocket(m_socket). + // close(m_socket) if m_socket is not INVALID_SOCKET. // Avoid closing an arbitrary file descriptor (m_socket is just a random very high number which // theoretically may concide with a real opened file descriptor). - Reset(); + m_socket = INVALID_SOCKET; } FuzzedSock& FuzzedSock::operator=(Sock&& other) @@ -36,11 +36,6 @@ FuzzedSock& FuzzedSock::operator=(Sock&& other) return *this; } -void FuzzedSock::Reset() -{ - m_socket = INVALID_SOCKET; -} - ssize_t FuzzedSock::Send(const void* data, size_t len, int flags) const { constexpr std::array send_errnos{ @@ -160,6 +155,45 @@ int FuzzedSock::Connect(const sockaddr*, socklen_t) const return 0; } +int FuzzedSock::Bind(const sockaddr*, socklen_t) const +{ + // Have a permanent error at bind_errnos[0] because when the fuzzed data is exhausted + // SetFuzzedErrNo() will always set the global errno to bind_errnos[0]. We want to + // avoid this method returning -1 and setting errno to a temporary error (like EAGAIN) + // repeatedly because proper code should retry on temporary errors, leading to an + // infinite loop. + constexpr std::array bind_errnos{ + EACCES, + EADDRINUSE, + EADDRNOTAVAIL, + EAGAIN, + }; + if (m_fuzzed_data_provider.ConsumeBool()) { + SetFuzzedErrNo(m_fuzzed_data_provider, bind_errnos); + return -1; + } + return 0; +} + +int FuzzedSock::Listen(int) const +{ + // Have a permanent error at listen_errnos[0] because when the fuzzed data is exhausted + // SetFuzzedErrNo() will always set the global errno to listen_errnos[0]. We want to + // avoid this method returning -1 and setting errno to a temporary error (like EAGAIN) + // repeatedly because proper code should retry on temporary errors, leading to an + // infinite loop. + constexpr std::array listen_errnos{ + EADDRINUSE, + EINVAL, + EOPNOTSUPP, + }; + if (m_fuzzed_data_provider.ConsumeBool()) { + SetFuzzedErrNo(m_fuzzed_data_provider, listen_errnos); + return -1; + } + return 0; +} + std::unique_ptr<Sock> FuzzedSock::Accept(sockaddr* addr, socklen_t* addr_len) const { constexpr std::array accept_errnos{ @@ -206,6 +240,20 @@ int FuzzedSock::SetSockOpt(int, int, const void*, socklen_t) const return 0; } +int FuzzedSock::GetSockName(sockaddr* name, socklen_t* name_len) const +{ + constexpr std::array getsockname_errnos{ + ECONNRESET, + ENOBUFS, + }; + if (m_fuzzed_data_provider.ConsumeBool()) { + SetFuzzedErrNo(m_fuzzed_data_provider, getsockname_errnos); + return -1; + } + *name_len = m_fuzzed_data_provider.ConsumeData(name, *name_len); + return 0; +} + bool FuzzedSock::Wait(std::chrono::milliseconds timeout, Event requested, Event* occurred) const { constexpr std::array wait_errnos{ @@ -223,6 +271,15 @@ bool FuzzedSock::Wait(std::chrono::milliseconds timeout, Event requested, Event* return true; } +bool FuzzedSock::WaitMany(std::chrono::milliseconds timeout, EventsPerSock& events_per_sock) const +{ + for (auto& [sock, events] : events_per_sock) { + (void)sock; + events.occurred = m_fuzzed_data_provider.ConsumeBool() ? events.requested : 0; + } + return true; +} + bool FuzzedSock::IsConnected(std::string& errmsg) const { if (m_fuzzed_data_provider.ConsumeBool()) { @@ -232,57 +289,14 @@ bool FuzzedSock::IsConnected(std::string& errmsg) const return false; } -void FillNode(FuzzedDataProvider& fuzzed_data_provider, ConnmanTestMsg& connman, PeerManager& peerman, CNode& node) noexcept +void FillNode(FuzzedDataProvider& fuzzed_data_provider, ConnmanTestMsg& connman, CNode& node) noexcept { - const bool successfully_connected{fuzzed_data_provider.ConsumeBool()}; - const ServiceFlags remote_services = ConsumeWeakEnum(fuzzed_data_provider, ALL_SERVICE_FLAGS); - const NetPermissionFlags permission_flags = ConsumeWeakEnum(fuzzed_data_provider, ALL_NET_PERMISSION_FLAGS); - const int32_t version = fuzzed_data_provider.ConsumeIntegralInRange<int32_t>(MIN_PEER_PROTO_VERSION, std::numeric_limits<int32_t>::max()); - const bool relay_txs{fuzzed_data_provider.ConsumeBool()}; - - const CNetMsgMaker mm{0}; - - CSerializedNetMsg msg_version{ - mm.Make(NetMsgType::VERSION, - version, // - Using<CustomUintFormatter<8>>(remote_services), // - int64_t{}, // dummy time - int64_t{}, // ignored service bits - CService{}, // dummy - int64_t{}, // ignored service bits - CService{}, // ignored - uint64_t{1}, // dummy nonce - std::string{}, // dummy subver - int32_t{}, // dummy starting_height - relay_txs), - }; - - (void)connman.ReceiveMsgFrom(node, msg_version); - node.fPauseSend = false; - connman.ProcessMessagesOnce(node); - { - LOCK(node.cs_sendProcessing); - peerman.SendMessages(&node); - } - if (node.fDisconnect) return; - assert(node.nVersion == version); - assert(node.GetCommonVersion() == std::min(version, PROTOCOL_VERSION)); - assert(node.nServices == remote_services); - CNodeStateStats statestats; - assert(peerman.GetNodeStateStats(node.GetId(), statestats)); - assert(statestats.m_relay_txs == (relay_txs && !node.IsBlockOnlyConn())); - node.m_permissionFlags = permission_flags; - if (successfully_connected) { - CSerializedNetMsg msg_verack{mm.Make(NetMsgType::VERACK)}; - (void)connman.ReceiveMsgFrom(node, msg_verack); - node.fPauseSend = false; - connman.ProcessMessagesOnce(node); - { - LOCK(node.cs_sendProcessing); - peerman.SendMessages(&node); - } - assert(node.fSuccessfullyConnected == true); - } + connman.Handshake(node, + /*successfully_connected=*/fuzzed_data_provider.ConsumeBool(), + /*remote_services=*/ConsumeWeakEnum(fuzzed_data_provider, ALL_SERVICE_FLAGS), + /*local_services=*/ConsumeWeakEnum(fuzzed_data_provider, ALL_SERVICE_FLAGS), + /*version=*/fuzzed_data_provider.ConsumeIntegralInRange<int32_t>(MIN_PEER_PROTO_VERSION, std::numeric_limits<int32_t>::max()), + /*relay_txs=*/fuzzed_data_provider.ConsumeBool()); } CAmount ConsumeMoney(FuzzedDataProvider& fuzzed_data_provider, const std::optional<CAmount>& max) noexcept @@ -293,8 +307,8 @@ CAmount ConsumeMoney(FuzzedDataProvider& fuzzed_data_provider, const std::option int64_t ConsumeTime(FuzzedDataProvider& fuzzed_data_provider, const std::optional<int64_t>& min, const std::optional<int64_t>& max) noexcept { // Avoid t=0 (1970-01-01T00:00:00Z) since SetMockTime(0) disables mocktime. - static const int64_t time_min{ParseISO8601DateTime("2000-01-01T00:00:01Z")}; - static const int64_t time_max{ParseISO8601DateTime("2100-12-31T23:59:59Z")}; + static const int64_t time_min{946684801}; // 2000-01-01T00:00:01Z + static const int64_t time_max{4133980799}; // 2100-12-31T23:59:59Z return fuzzed_data_provider.ConsumeIntegralInRange<int64_t>(min.value_or(time_min), max.value_or(time_max)); } @@ -464,21 +478,6 @@ CTxDestination ConsumeTxDestination(FuzzedDataProvider& fuzzed_data_provider) no return tx_destination; } -CTxMemPoolEntry ConsumeTxMemPoolEntry(FuzzedDataProvider& fuzzed_data_provider, const CTransaction& tx) noexcept -{ - // Avoid: - // policy/feerate.cpp:28:34: runtime error: signed integer overflow: 34873208148477500 * 1000 cannot be represented in type 'long' - // - // Reproduce using CFeeRate(348732081484775, 10).GetFeePerK() - const CAmount fee = std::min<CAmount>(ConsumeMoney(fuzzed_data_provider), std::numeric_limits<CAmount>::max() / static_cast<CAmount>(100000)); - assert(MoneyRange(fee)); - const int64_t time = fuzzed_data_provider.ConsumeIntegral<int64_t>(); - const unsigned int entry_height = fuzzed_data_provider.ConsumeIntegral<unsigned int>(); - const bool spends_coinbase = fuzzed_data_provider.ConsumeBool(); - const unsigned int sig_op_cost = fuzzed_data_provider.ConsumeIntegralInRange<unsigned int>(0, MAX_BLOCK_SIGOPS_COST); - return CTxMemPoolEntry{MakeTransactionRef(tx), fee, time, entry_height, spends_coinbase, sig_op_cost, {}}; -} - bool ContainsSpentInput(const CTransaction& tx, const CCoinsViewCache& inputs) noexcept { for (const CTxIn& tx_in : tx.vin) { @@ -512,6 +511,11 @@ CNetAddr ConsumeNetAddr(FuzzedDataProvider& fuzzed_data_provider) noexcept return net_addr; } +CAddress ConsumeAddress(FuzzedDataProvider& fuzzed_data_provider) noexcept +{ + return {ConsumeService(fuzzed_data_provider), ConsumeWeakEnum(fuzzed_data_provider, ALL_SERVICE_FLAGS), NodeSeconds{std::chrono::seconds{fuzzed_data_provider.ConsumeIntegral<uint32_t>()}}}; +} + FILE* FuzzedFileProvider::open() { SetFuzzedErrNo(m_fuzzed_data_provider); diff --git a/src/test/fuzz/util.h b/src/test/fuzz/util.h index 3fc6fa1cd5..a354df2bf7 100644 --- a/src/test/fuzz/util.h +++ b/src/test/fuzz/util.h @@ -8,7 +8,7 @@ #include <arith_uint256.h> #include <chainparamsbase.h> #include <coins.h> -#include <compat.h> +#include <compat/compat.h> #include <consensus/amount.h> #include <consensus/consensus.h> #include <merkleblock.h> @@ -23,7 +23,6 @@ #include <test/fuzz/FuzzedDataProvider.h> #include <test/fuzz/fuzz.h> #include <test/util/net.h> -#include <txmempool.h> #include <uint256.h> #include <version.h> @@ -55,22 +54,28 @@ public: FuzzedSock& operator=(Sock&& other) override; - void Reset() override; - ssize_t Send(const void* data, size_t len, int flags) const override; ssize_t Recv(void* buf, size_t len, int flags) const override; int Connect(const sockaddr*, socklen_t) const override; + int Bind(const sockaddr*, socklen_t) const override; + + int Listen(int backlog) const override; + std::unique_ptr<Sock> Accept(sockaddr* addr, socklen_t* addr_len) const override; int GetSockOpt(int level, int opt_name, void* opt_val, socklen_t* opt_len) const override; int SetSockOpt(int level, int opt_name, const void* opt_val, socklen_t opt_len) const override; + int GetSockName(sockaddr* name, socklen_t* name_len) const override; + bool Wait(std::chrono::milliseconds timeout, Event requested, Event* occurred = nullptr) const override; + bool WaitMany(std::chrono::milliseconds timeout, EventsPerSock& events_per_sock) const override; + bool IsConnected(std::string& errmsg) const override; }; @@ -207,8 +212,6 @@ template <typename WeakEnumType, size_t size> return UintToArith256(ConsumeUInt256(fuzzed_data_provider)); } -[[nodiscard]] CTxMemPoolEntry ConsumeTxMemPoolEntry(FuzzedDataProvider& fuzzed_data_provider, const CTransaction& tx) noexcept; - [[nodiscard]] CTxDestination ConsumeTxDestination(FuzzedDataProvider& fuzzed_data_provider) noexcept; template <typename T> @@ -281,16 +284,12 @@ inline CService ConsumeService(FuzzedDataProvider& fuzzed_data_provider) noexcep return {ConsumeNetAddr(fuzzed_data_provider), fuzzed_data_provider.ConsumeIntegral<uint16_t>()}; } -inline CAddress ConsumeAddress(FuzzedDataProvider& fuzzed_data_provider) noexcept -{ - return {ConsumeService(fuzzed_data_provider), ConsumeWeakEnum(fuzzed_data_provider, ALL_SERVICE_FLAGS), fuzzed_data_provider.ConsumeIntegral<uint32_t>()}; -} +CAddress ConsumeAddress(FuzzedDataProvider& fuzzed_data_provider) noexcept; template <bool ReturnUniquePtr = false> auto ConsumeNode(FuzzedDataProvider& fuzzed_data_provider, const std::optional<NodeId>& node_id_in = std::nullopt) noexcept { const NodeId node_id = node_id_in.value_or(fuzzed_data_provider.ConsumeIntegralInRange<NodeId>(0, std::numeric_limits<NodeId>::max())); - const ServiceFlags local_services = ConsumeWeakEnum(fuzzed_data_provider, ALL_SERVICE_FLAGS); const auto sock = std::make_shared<FuzzedSock>(fuzzed_data_provider); const CAddress address = ConsumeAddress(fuzzed_data_provider); const uint64_t keyed_net_group = fuzzed_data_provider.ConsumeIntegral<uint64_t>(); @@ -299,9 +298,9 @@ auto ConsumeNode(FuzzedDataProvider& fuzzed_data_provider, const std::optional<N const std::string addr_name = fuzzed_data_provider.ConsumeRandomLengthString(64); const ConnectionType conn_type = fuzzed_data_provider.PickValueInArray(ALL_CONNECTION_TYPES); const bool inbound_onion{conn_type == ConnectionType::INBOUND ? fuzzed_data_provider.ConsumeBool() : false}; + NetPermissionFlags permission_flags = ConsumeWeakEnum(fuzzed_data_provider, ALL_NET_PERMISSION_FLAGS); if constexpr (ReturnUniquePtr) { return std::make_unique<CNode>(node_id, - local_services, sock, address, keyed_net_group, @@ -309,10 +308,10 @@ auto ConsumeNode(FuzzedDataProvider& fuzzed_data_provider, const std::optional<N addr_bind, addr_name, conn_type, - inbound_onion); + inbound_onion, + CNodeOptions{ .permission_flags = permission_flags }); } else { return CNode{node_id, - local_services, sock, address, keyed_net_group, @@ -320,12 +319,13 @@ auto ConsumeNode(FuzzedDataProvider& fuzzed_data_provider, const std::optional<N addr_bind, addr_name, conn_type, - inbound_onion}; + inbound_onion, + CNodeOptions{ .permission_flags = permission_flags }}; } } inline std::unique_ptr<CNode> ConsumeNodeAsUniquePtr(FuzzedDataProvider& fdp, const std::optional<NodeId>& node_id_in = std::nullopt) { return ConsumeNode<true>(fdp, node_id_in); } -void FillNode(FuzzedDataProvider& fuzzed_data_provider, ConnmanTestMsg& connman, PeerManager& peerman, CNode& node) noexcept; +void FillNode(FuzzedDataProvider& fuzzed_data_provider, ConnmanTestMsg& connman, CNode& node) noexcept EXCLUSIVE_LOCKS_REQUIRED(NetEventsInterface::g_msgproc_mutex); class FuzzedFileProvider { @@ -355,17 +355,16 @@ public: class FuzzedAutoFileProvider { - FuzzedDataProvider& m_fuzzed_data_provider; FuzzedFileProvider m_fuzzed_file_provider; public: - FuzzedAutoFileProvider(FuzzedDataProvider& fuzzed_data_provider) : m_fuzzed_data_provider{fuzzed_data_provider}, m_fuzzed_file_provider{fuzzed_data_provider} + FuzzedAutoFileProvider(FuzzedDataProvider& fuzzed_data_provider) : m_fuzzed_file_provider{fuzzed_data_provider} { } - CAutoFile open() + AutoFile open() { - return {m_fuzzed_file_provider.open(), m_fuzzed_data_provider.ConsumeIntegral<int>(), m_fuzzed_data_provider.ConsumeIntegral<int>()}; + return AutoFile{m_fuzzed_file_provider.open()}; } }; diff --git a/src/test/fuzz/util/mempool.cpp b/src/test/fuzz/util/mempool.cpp new file mode 100644 index 0000000000..d0053f77d2 --- /dev/null +++ b/src/test/fuzz/util/mempool.cpp @@ -0,0 +1,27 @@ +// Copyright (c) 2022 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include <consensus/amount.h> +#include <primitives/transaction.h> +#include <test/fuzz/FuzzedDataProvider.h> +#include <test/fuzz/util.h> +#include <test/fuzz/util/mempool.h> +#include <txmempool.h> + +#include <limits> + +CTxMemPoolEntry ConsumeTxMemPoolEntry(FuzzedDataProvider& fuzzed_data_provider, const CTransaction& tx) noexcept +{ + // Avoid: + // policy/feerate.cpp:28:34: runtime error: signed integer overflow: 34873208148477500 * 1000 cannot be represented in type 'long' + // + // Reproduce using CFeeRate(348732081484775, 10).GetFeePerK() + const CAmount fee{ConsumeMoney(fuzzed_data_provider, /*max=*/std::numeric_limits<CAmount>::max() / CAmount{100'000})}; + assert(MoneyRange(fee)); + const int64_t time = fuzzed_data_provider.ConsumeIntegral<int64_t>(); + const unsigned int entry_height = fuzzed_data_provider.ConsumeIntegral<unsigned int>(); + const bool spends_coinbase = fuzzed_data_provider.ConsumeBool(); + const unsigned int sig_op_cost = fuzzed_data_provider.ConsumeIntegralInRange<unsigned int>(0, MAX_BLOCK_SIGOPS_COST); + return CTxMemPoolEntry{MakeTransactionRef(tx), fee, time, entry_height, spends_coinbase, sig_op_cost, {}}; +} diff --git a/src/test/fuzz/util/mempool.h b/src/test/fuzz/util/mempool.h new file mode 100644 index 0000000000..4304e5294e --- /dev/null +++ b/src/test/fuzz/util/mempool.h @@ -0,0 +1,24 @@ +// Copyright (c) 2022 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_TEST_FUZZ_UTIL_MEMPOOL_H +#define BITCOIN_TEST_FUZZ_UTIL_MEMPOOL_H + +#include <primitives/transaction.h> +#include <test/fuzz/FuzzedDataProvider.h> +#include <txmempool.h> +#include <validation.h> + +class DummyChainState final : public Chainstate +{ +public: + void SetMempool(CTxMemPool* mempool) + { + m_mempool = mempool; + } +}; + +[[nodiscard]] CTxMemPoolEntry ConsumeTxMemPoolEntry(FuzzedDataProvider& fuzzed_data_provider, const CTransaction& tx) noexcept; + +#endif // BITCOIN_TEST_FUZZ_UTIL_MEMPOOL_H diff --git a/src/test/fuzz/utxo_snapshot.cpp b/src/test/fuzz/utxo_snapshot.cpp index 33496a457e..8abb943266 100644 --- a/src/test/fuzz/utxo_snapshot.cpp +++ b/src/test/fuzz/utxo_snapshot.cpp @@ -39,13 +39,13 @@ FUZZ_TARGET_INIT(utxo_snapshot, initialize_chain) Assert(!chainman.SnapshotBlockhash()); { - CAutoFile outfile{fsbridge::fopen(snapshot_path, "wb"), SER_DISK, CLIENT_VERSION}; + AutoFile outfile{fsbridge::fopen(snapshot_path, "wb")}; const auto file_data{ConsumeRandomLengthByteVector(fuzzed_data_provider)}; outfile << Span{file_data}; } const auto ActivateFuzzedSnapshot{[&] { - CAutoFile infile{fsbridge::fopen(snapshot_path, "rb"), SER_DISK, CLIENT_VERSION}; + AutoFile infile{fsbridge::fopen(snapshot_path, "rb")}; SnapshotMetadata metadata; try { infile >> metadata; @@ -58,7 +58,7 @@ FUZZ_TARGET_INIT(utxo_snapshot, initialize_chain) if (fuzzed_data_provider.ConsumeBool()) { for (const auto& block : *g_chain) { BlockValidationState dummy; - bool processed{chainman.ProcessNewBlockHeaders({*block}, dummy)}; + bool processed{chainman.ProcessNewBlockHeaders({*block}, true, dummy)}; Assert(processed); const auto* index{WITH_LOCK(::cs_main, return chainman.m_blockman.LookupBlockIndex(block->GetHash()))}; Assert(index); diff --git a/src/test/fuzz/validation_load_mempool.cpp b/src/test/fuzz/validation_load_mempool.cpp index c2aaf486c5..d96609416b 100644 --- a/src/test/fuzz/validation_load_mempool.cpp +++ b/src/test/fuzz/validation_load_mempool.cpp @@ -2,10 +2,15 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. +#include <kernel/mempool_persist.h> + #include <chainparamsbase.h> +#include <node/mempool_args.h> +#include <node/mempool_persist_args.h> #include <test/fuzz/FuzzedDataProvider.h> #include <test/fuzz/fuzz.h> #include <test/fuzz/util.h> +#include <test/fuzz/util/mempool.h> #include <test/util/setup_common.h> #include <txmempool.h> #include <util/time.h> @@ -14,6 +19,10 @@ #include <cstdint> #include <vector> +using kernel::DumpMempool; + +using node::MempoolPath; + namespace { const TestingSetup* g_setup; } // namespace @@ -30,10 +39,14 @@ FUZZ_TARGET_INIT(validation_load_mempool, initialize_validation_load_mempool) SetMockTime(ConsumeTime(fuzzed_data_provider)); FuzzedFileProvider fuzzed_file_provider = ConsumeFile(fuzzed_data_provider); - CTxMemPool pool{}; + CTxMemPool pool{MemPoolOptionsForTest(g_setup->m_node)}; + + auto& chainstate{static_cast<DummyChainState&>(g_setup->m_node.chainman->ActiveChainstate())}; + chainstate.SetMempool(&pool); + auto fuzzed_fopen = [&](const fs::path&, const char*) { return fuzzed_file_provider.open(); }; - (void)LoadMempool(pool, g_setup->m_node.chainman->ActiveChainstate(), fuzzed_fopen); - (void)DumpMempool(pool, fuzzed_fopen, true); + (void)chainstate.LoadMempool(MempoolPath(g_setup->m_args), fuzzed_fopen); + (void)DumpMempool(pool, MempoolPath(g_setup->m_args), fuzzed_fopen, true); } diff --git a/src/test/headers_sync_chainwork_tests.cpp b/src/test/headers_sync_chainwork_tests.cpp new file mode 100644 index 0000000000..41241ebee2 --- /dev/null +++ b/src/test/headers_sync_chainwork_tests.cpp @@ -0,0 +1,146 @@ +// Copyright (c) 2022 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include <chain.h> +#include <chainparams.h> +#include <consensus/params.h> +#include <headerssync.h> +#include <pow.h> +#include <test/util/setup_common.h> +#include <validation.h> +#include <vector> + +#include <boost/test/unit_test.hpp> + +struct HeadersGeneratorSetup : public RegTestingSetup { + /** Search for a nonce to meet (regtest) proof of work */ + void FindProofOfWork(CBlockHeader& starting_header); + /** + * Generate headers in a chain that build off a given starting hash, using + * the given nVersion, advancing time by 1 second from the starting + * prev_time, and with a fixed merkle root hash. + */ + void GenerateHeaders(std::vector<CBlockHeader>& headers, size_t count, + const uint256& starting_hash, const int nVersion, int prev_time, + const uint256& merkle_root, const uint32_t nBits); +}; + +void HeadersGeneratorSetup::FindProofOfWork(CBlockHeader& starting_header) +{ + while (!CheckProofOfWork(starting_header.GetHash(), starting_header.nBits, Params().GetConsensus())) { + ++(starting_header.nNonce); + } +} + +void HeadersGeneratorSetup::GenerateHeaders(std::vector<CBlockHeader>& headers, + size_t count, const uint256& starting_hash, const int nVersion, int prev_time, + const uint256& merkle_root, const uint32_t nBits) +{ + uint256 prev_hash = starting_hash; + + while (headers.size() < count) { + headers.push_back(CBlockHeader()); + CBlockHeader& next_header = headers.back();; + next_header.nVersion = nVersion; + next_header.hashPrevBlock = prev_hash; + next_header.hashMerkleRoot = merkle_root; + next_header.nTime = prev_time+1; + next_header.nBits = nBits; + + FindProofOfWork(next_header); + prev_hash = next_header.GetHash(); + prev_time = next_header.nTime; + } + return; +} + +BOOST_FIXTURE_TEST_SUITE(headers_sync_chainwork_tests, HeadersGeneratorSetup) + +// In this test, we construct two sets of headers from genesis, one with +// sufficient proof of work and one without. +// 1. We deliver the first set of headers and verify that the headers sync state +// updates to the REDOWNLOAD phase successfully. +// 2. Then we deliver the second set of headers and verify that they fail +// processing (presumably due to commitments not matching). +// 3. Finally, we verify that repeating with the first set of headers in both +// phases is successful. +BOOST_AUTO_TEST_CASE(headers_sync_state) +{ + std::vector<CBlockHeader> first_chain; + std::vector<CBlockHeader> second_chain; + + std::unique_ptr<HeadersSyncState> hss; + + const int target_blocks = 15000; + arith_uint256 chain_work = target_blocks*2; + + // Generate headers for two different chains (using differing merkle roots + // to ensure the headers are different). + GenerateHeaders(first_chain, target_blocks-1, Params().GenesisBlock().GetHash(), + Params().GenesisBlock().nVersion, Params().GenesisBlock().nTime, + ArithToUint256(0), Params().GenesisBlock().nBits); + + GenerateHeaders(second_chain, target_blocks-2, Params().GenesisBlock().GetHash(), + Params().GenesisBlock().nVersion, Params().GenesisBlock().nTime, + ArithToUint256(1), Params().GenesisBlock().nBits); + + const CBlockIndex* chain_start = WITH_LOCK(::cs_main, return m_node.chainman->m_blockman.LookupBlockIndex(Params().GenesisBlock().GetHash())); + std::vector<CBlockHeader> headers_batch; + + // Feed the first chain to HeadersSyncState, by delivering 1 header + // initially and then the rest. + headers_batch.insert(headers_batch.end(), std::next(first_chain.begin()), first_chain.end()); + + hss.reset(new HeadersSyncState(0, Params().GetConsensus(), chain_start, chain_work)); + (void)hss->ProcessNextHeaders({first_chain.front()}, true); + // Pretend the first header is still "full", so we don't abort. + auto result = hss->ProcessNextHeaders(headers_batch, true); + + // This chain should look valid, and we should have met the proof-of-work + // requirement. + BOOST_CHECK(result.success); + BOOST_CHECK(result.request_more); + BOOST_CHECK(hss->GetState() == HeadersSyncState::State::REDOWNLOAD); + + // Try to sneakily feed back the second chain. + result = hss->ProcessNextHeaders(second_chain, true); + BOOST_CHECK(!result.success); // foiled! + BOOST_CHECK(hss->GetState() == HeadersSyncState::State::FINAL); + + // Now try again, this time feeding the first chain twice. + hss.reset(new HeadersSyncState(0, Params().GetConsensus(), chain_start, chain_work)); + (void)hss->ProcessNextHeaders(first_chain, true); + BOOST_CHECK(hss->GetState() == HeadersSyncState::State::REDOWNLOAD); + + result = hss->ProcessNextHeaders(first_chain, true); + BOOST_CHECK(result.success); + BOOST_CHECK(!result.request_more); + // All headers should be ready for acceptance: + BOOST_CHECK(result.pow_validated_headers.size() == first_chain.size()); + // Nothing left for the sync logic to do: + BOOST_CHECK(hss->GetState() == HeadersSyncState::State::FINAL); + + // Finally, verify that just trying to process the second chain would not + // succeed (too little work) + hss.reset(new HeadersSyncState(0, Params().GetConsensus(), chain_start, chain_work)); + BOOST_CHECK(hss->GetState() == HeadersSyncState::State::PRESYNC); + // Pretend just the first message is "full", so we don't abort. + (void)hss->ProcessNextHeaders({second_chain.front()}, true); + BOOST_CHECK(hss->GetState() == HeadersSyncState::State::PRESYNC); + + headers_batch.clear(); + headers_batch.insert(headers_batch.end(), std::next(second_chain.begin(), 1), second_chain.end()); + // Tell the sync logic that the headers message was not full, implying no + // more headers can be requested. For a low-work-chain, this should causes + // the sync to end with no headers for acceptance. + result = hss->ProcessNextHeaders(headers_batch, false); + BOOST_CHECK(hss->GetState() == HeadersSyncState::State::FINAL); + BOOST_CHECK(result.pow_validated_headers.empty()); + BOOST_CHECK(!result.request_more); + // Nevertheless, no validation errors should have been detected with the + // chain: + BOOST_CHECK(result.success); +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/i2p_tests.cpp b/src/test/i2p_tests.cpp index bd9ba4b8f7..9da1ee11f9 100644 --- a/src/test/i2p_tests.cpp +++ b/src/test/i2p_tests.cpp @@ -3,6 +3,7 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include <i2p.h> +#include <logging.h> #include <netaddress.h> #include <test/util/logging.h> #include <test/util/net.h> @@ -19,6 +20,8 @@ BOOST_FIXTURE_TEST_SUITE(i2p_tests, BasicTestingSetup) BOOST_AUTO_TEST_CASE(unlimited_recv) { + const auto prev_log_level{LogInstance().LogLevel()}; + LogInstance().SetLogLevel(BCLog::Level::Trace); auto CreateSockOrig = CreateSock; // Mock CreateSock() to create MockSock. @@ -30,7 +33,7 @@ BOOST_AUTO_TEST_CASE(unlimited_recv) i2p::sam::Session session(gArgs.GetDataDirNet() / "test_i2p_private_key", CService{}, &interrupt); { - ASSERT_DEBUG_LOG("Creating SAM session"); + ASSERT_DEBUG_LOG("Creating persistent SAM session"); ASSERT_DEBUG_LOG("too many bytes without a terminator"); i2p::Connection conn; @@ -39,6 +42,7 @@ BOOST_AUTO_TEST_CASE(unlimited_recv) } CreateSock = CreateSockOrig; + LogInstance().SetLogLevel(prev_log_level); } BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/interfaces_tests.cpp b/src/test/interfaces_tests.cpp index 49b7d2003b..cb901b2259 100644 --- a/src/test/interfaces_tests.cpp +++ b/src/test/interfaces_tests.cpp @@ -17,6 +17,7 @@ BOOST_FIXTURE_TEST_SUITE(interfaces_tests, TestChain100Setup) BOOST_AUTO_TEST_CASE(findBlock) { + LOCK(Assert(m_node.chainman)->GetMutex()); auto& chain = m_node.chain; const CChain& active = Assert(m_node.chainman)->ActiveChain(); @@ -61,6 +62,7 @@ BOOST_AUTO_TEST_CASE(findBlock) BOOST_AUTO_TEST_CASE(findFirstBlockWithTimeAndHeight) { + LOCK(Assert(m_node.chainman)->GetMutex()); auto& chain = m_node.chain; const CChain& active = Assert(m_node.chainman)->ActiveChain(); uint256 hash; @@ -73,6 +75,7 @@ BOOST_AUTO_TEST_CASE(findFirstBlockWithTimeAndHeight) BOOST_AUTO_TEST_CASE(findAncestorByHeight) { + LOCK(Assert(m_node.chainman)->GetMutex()); auto& chain = m_node.chain; const CChain& active = Assert(m_node.chainman)->ActiveChain(); uint256 hash; @@ -83,6 +86,7 @@ BOOST_AUTO_TEST_CASE(findAncestorByHeight) BOOST_AUTO_TEST_CASE(findAncestorByHash) { + LOCK(Assert(m_node.chainman)->GetMutex()); auto& chain = m_node.chain; const CChain& active = Assert(m_node.chainman)->ActiveChain(); int height = -1; @@ -94,7 +98,7 @@ BOOST_AUTO_TEST_CASE(findAncestorByHash) BOOST_AUTO_TEST_CASE(findCommonAncestor) { auto& chain = m_node.chain; - const CChain& active = Assert(m_node.chainman)->ActiveChain(); + const CChain& active = WITH_LOCK(Assert(m_node.chainman)->GetMutex(), return Assert(m_node.chainman)->ActiveChain()); auto* orig_tip = active.Tip(); for (int i = 0; i < 10; ++i) { BlockValidationState state; diff --git a/src/test/key_io_tests.cpp b/src/test/key_io_tests.cpp index e70b8b3dfd..1eac68de14 100644 --- a/src/test/key_io_tests.cpp +++ b/src/test/key_io_tests.cpp @@ -28,7 +28,7 @@ BOOST_AUTO_TEST_CASE(key_io_valid_parse) SelectParams(CBaseChainParams::MAIN); for (unsigned int idx = 0; idx < tests.size(); idx++) { - UniValue test = tests[idx]; + const UniValue& test = tests[idx]; std::string strTest = test.write(); if (test.size() < 3) { // Allow for extra stuff (useful for comments) BOOST_ERROR("Bad test: " << strTest); @@ -86,7 +86,7 @@ BOOST_AUTO_TEST_CASE(key_io_valid_gen) UniValue tests = read_json(std::string(json_tests::key_io_valid, json_tests::key_io_valid + sizeof(json_tests::key_io_valid))); for (unsigned int idx = 0; idx < tests.size(); idx++) { - UniValue test = tests[idx]; + const UniValue& test = tests[idx]; std::string strTest = test.write(); if (test.size() < 3) // Allow for extra stuff (useful for comments) { @@ -126,7 +126,7 @@ BOOST_AUTO_TEST_CASE(key_io_invalid) CTxDestination destination; for (unsigned int idx = 0; idx < tests.size(); idx++) { - UniValue test = tests[idx]; + const UniValue& test = tests[idx]; std::string strTest = test.write(); if (test.size() < 1) // Allow for extra stuff (useful for comments) { diff --git a/src/test/logging_tests.cpp b/src/test/logging_tests.cpp index 3f6a605945..a6f3a62c71 100644 --- a/src/test/logging_tests.cpp +++ b/src/test/logging_tests.cpp @@ -2,6 +2,7 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. +#include <init/common.h> #include <logging.h> #include <logging/timer.h> #include <test/util/setup_common.h> @@ -10,6 +11,7 @@ #include <chrono> #include <fstream> #include <iostream> +#include <unordered_map> #include <utility> #include <vector> @@ -17,6 +19,12 @@ BOOST_FIXTURE_TEST_SUITE(logging_tests, BasicTestingSetup) +static void ResetLogger() +{ + LogInstance().SetLogLevel(BCLog::DEFAULT_LOG_LEVEL); + LogInstance().SetCategoryLogLevel({}); +} + struct LogSetup : public BasicTestingSetup { fs::path prev_log_path; fs::path tmp_log_path; @@ -25,6 +33,8 @@ struct LogSetup : public BasicTestingSetup { bool prev_log_timestamps; bool prev_log_threadnames; bool prev_log_sourcelocations; + std::unordered_map<BCLog::LogFlags, BCLog::Level> prev_category_levels; + BCLog::Level prev_log_level; LogSetup() : prev_log_path{LogInstance().m_file_path}, tmp_log_path{m_args.GetDataDirBase() / "tmp_debug.log"}, @@ -32,14 +42,21 @@ struct LogSetup : public BasicTestingSetup { prev_print_to_file{LogInstance().m_print_to_file}, prev_log_timestamps{LogInstance().m_log_timestamps}, prev_log_threadnames{LogInstance().m_log_threadnames}, - prev_log_sourcelocations{LogInstance().m_log_sourcelocations} + prev_log_sourcelocations{LogInstance().m_log_sourcelocations}, + prev_category_levels{LogInstance().CategoryLevels()}, + prev_log_level{LogInstance().LogLevel()} { LogInstance().m_file_path = tmp_log_path; LogInstance().m_reopen_file = true; LogInstance().m_print_to_file = true; LogInstance().m_log_timestamps = false; LogInstance().m_log_threadnames = false; - LogInstance().m_log_sourcelocations = true; + + // Prevent tests from failing when the line number of the logs changes. + LogInstance().m_log_sourcelocations = false; + + LogInstance().SetLogLevel(BCLog::Level::Debug); + LogInstance().SetCategoryLogLevel({}); } ~LogSetup() @@ -51,6 +68,8 @@ struct LogSetup : public BasicTestingSetup { LogInstance().m_log_timestamps = prev_log_timestamps; LogInstance().m_log_threadnames = prev_log_threadnames; LogInstance().m_log_sourcelocations = prev_log_sourcelocations; + LogInstance().SetLogLevel(prev_log_level); + LogInstance().SetCategoryLogLevel(prev_category_levels); } }; @@ -74,6 +93,7 @@ BOOST_AUTO_TEST_CASE(logging_timer) BOOST_FIXTURE_TEST_CASE(logging_LogPrintf_, LogSetup) { + LogInstance().m_log_sourcelocations = true; LogPrintf_("fn1", "src1", 1, BCLog::LogFlags::NET, BCLog::Level::Debug, "foo1: %s", "bar1\n"); LogPrintf_("fn2", "src2", 2, BCLog::LogFlags::NET, BCLog::Level::None, "foo2: %s", "bar2\n"); LogPrintf_("fn3", "src3", 3, BCLog::LogFlags::NONE, BCLog::Level::Debug, "foo3: %s", "bar3\n"); @@ -94,15 +114,13 @@ BOOST_FIXTURE_TEST_CASE(logging_LogPrintf_, LogSetup) BOOST_FIXTURE_TEST_CASE(logging_LogPrintMacros, LogSetup) { - // Prevent tests from failing when the line number of the following log calls changes. - LogInstance().m_log_sourcelocations = false; - LogPrintf("foo5: %s\n", "bar5"); LogPrint(BCLog::NET, "foo6: %s\n", "bar6"); LogPrintLevel(BCLog::NET, BCLog::Level::Debug, "foo7: %s\n", "bar7"); LogPrintLevel(BCLog::NET, BCLog::Level::Info, "foo8: %s\n", "bar8"); LogPrintLevel(BCLog::NET, BCLog::Level::Warning, "foo9: %s\n", "bar9"); LogPrintLevel(BCLog::NET, BCLog::Level::Error, "foo10: %s\n", "bar10"); + LogPrintfCategory(BCLog::VALIDATION, "foo11: %s\n", "bar11"); std::ifstream file{tmp_log_path}; std::vector<std::string> log_lines; for (std::string log; std::getline(file, log);) { @@ -114,22 +132,22 @@ BOOST_FIXTURE_TEST_CASE(logging_LogPrintMacros, LogSetup) "[net:debug] foo7: bar7", "[net:info] foo8: bar8", "[net:warning] foo9: bar9", - "[net:error] foo10: bar10"}; + "[net:error] foo10: bar10", + "[validation] foo11: bar11", + }; BOOST_CHECK_EQUAL_COLLECTIONS(log_lines.begin(), log_lines.end(), expected.begin(), expected.end()); } BOOST_FIXTURE_TEST_CASE(logging_LogPrintMacros_CategoryName, LogSetup) { - // Prevent tests from failing when the line number of the following log calls changes. - LogInstance().m_log_sourcelocations = false; LogInstance().EnableCategory(BCLog::LogFlags::ALL); - const auto concated_categery_names = LogInstance().LogCategoriesString(); + const auto concatenated_category_names = LogInstance().LogCategoriesString(); std::vector<std::pair<BCLog::LogFlags, std::string>> expected_category_names; - const auto category_names = SplitString(concated_categery_names, ','); + const auto category_names = SplitString(concatenated_category_names, ','); for (const auto& category_name : category_names) { - BCLog::LogFlags category = BCLog::NONE; + BCLog::LogFlags category; const auto trimmed_category_name = TrimString(category_name); - BOOST_TEST(GetLogCategory(category, trimmed_category_name)); + BOOST_REQUIRE(GetLogCategory(category, trimmed_category_name)); expected_category_names.emplace_back(category, trimmed_category_name); } @@ -150,4 +168,92 @@ BOOST_FIXTURE_TEST_CASE(logging_LogPrintMacros_CategoryName, LogSetup) BOOST_CHECK_EQUAL_COLLECTIONS(log_lines.begin(), log_lines.end(), expected.begin(), expected.end()); } +BOOST_FIXTURE_TEST_CASE(logging_SeverityLevels, LogSetup) +{ + LogInstance().EnableCategory(BCLog::LogFlags::ALL); + + LogInstance().SetLogLevel(BCLog::Level::Debug); + LogInstance().SetCategoryLogLevel(/*category_str=*/"net", /*level_str=*/"info"); + + // Global log level + LogPrintLevel(BCLog::HTTP, BCLog::Level::Info, "foo1: %s\n", "bar1"); + LogPrintLevel(BCLog::MEMPOOL, BCLog::Level::Trace, "foo2: %s. This log level is lower than the global one.\n", "bar2"); + LogPrintLevel(BCLog::VALIDATION, BCLog::Level::Warning, "foo3: %s\n", "bar3"); + LogPrintLevel(BCLog::RPC, BCLog::Level::Error, "foo4: %s\n", "bar4"); + + // Category-specific log level + LogPrintLevel(BCLog::NET, BCLog::Level::Warning, "foo5: %s\n", "bar5"); + LogPrintLevel(BCLog::NET, BCLog::Level::Debug, "foo6: %s. This log level is the same as the global one but lower than the category-specific one, which takes precedence. \n", "bar6"); + LogPrintLevel(BCLog::NET, BCLog::Level::Error, "foo7: %s\n", "bar7"); + + std::vector<std::string> expected = { + "[http:info] foo1: bar1", + "[validation:warning] foo3: bar3", + "[rpc:error] foo4: bar4", + "[net:warning] foo5: bar5", + "[net:error] foo7: bar7", + }; + std::ifstream file{tmp_log_path}; + std::vector<std::string> log_lines; + for (std::string log; std::getline(file, log);) { + log_lines.push_back(log); + } + BOOST_CHECK_EQUAL_COLLECTIONS(log_lines.begin(), log_lines.end(), expected.begin(), expected.end()); +} + +BOOST_FIXTURE_TEST_CASE(logging_Conf, LogSetup) +{ + // Set global log level + { + ResetLogger(); + ArgsManager args; + args.AddArg("-loglevel", "...", ArgsManager::ALLOW_ANY, OptionsCategory::DEBUG_TEST); + const char* argv_test[] = {"bitcoind", "-loglevel=debug"}; + std::string err; + BOOST_REQUIRE(args.ParseParameters(2, argv_test, err)); + init::SetLoggingLevel(args); + BOOST_CHECK_EQUAL(LogInstance().LogLevel(), BCLog::Level::Debug); + } + + // Set category-specific log level + { + ResetLogger(); + ArgsManager args; + args.AddArg("-loglevel", "...", ArgsManager::ALLOW_ANY, OptionsCategory::DEBUG_TEST); + const char* argv_test[] = {"bitcoind", "-loglevel=net:trace"}; + std::string err; + BOOST_REQUIRE(args.ParseParameters(2, argv_test, err)); + init::SetLoggingLevel(args); + BOOST_CHECK_EQUAL(LogInstance().LogLevel(), BCLog::DEFAULT_LOG_LEVEL); + + const auto& category_levels{LogInstance().CategoryLevels()}; + const auto net_it{category_levels.find(BCLog::LogFlags::NET)}; + BOOST_REQUIRE(net_it != category_levels.end()); + BOOST_CHECK_EQUAL(net_it->second, BCLog::Level::Trace); + } + + // Set both global log level and category-specific log level + { + ResetLogger(); + ArgsManager args; + args.AddArg("-loglevel", "...", ArgsManager::ALLOW_ANY, OptionsCategory::DEBUG_TEST); + const char* argv_test[] = {"bitcoind", "-loglevel=debug", "-loglevel=net:trace", "-loglevel=http:info"}; + std::string err; + BOOST_REQUIRE(args.ParseParameters(4, argv_test, err)); + init::SetLoggingLevel(args); + BOOST_CHECK_EQUAL(LogInstance().LogLevel(), BCLog::Level::Debug); + + const auto& category_levels{LogInstance().CategoryLevels()}; + BOOST_CHECK_EQUAL(category_levels.size(), 2); + + const auto net_it{category_levels.find(BCLog::LogFlags::NET)}; + BOOST_CHECK(net_it != category_levels.end()); + BOOST_CHECK_EQUAL(net_it->second, BCLog::Level::Trace); + + const auto http_it{category_levels.find(BCLog::LogFlags::HTTP)}; + BOOST_CHECK(http_it != category_levels.end()); + BOOST_CHECK_EQUAL(http_it->second, BCLog::Level::Info); + } +} + BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/mempool_tests.cpp b/src/test/mempool_tests.cpp index 89424a0cd2..19f457b8dd 100644 --- a/src/test/mempool_tests.cpp +++ b/src/test/mempool_tests.cpp @@ -16,6 +16,12 @@ BOOST_FIXTURE_TEST_SUITE(mempool_tests, TestingSetup) static constexpr auto REMOVAL_REASON_DUMMY = MemPoolRemovalReason::REPLACED; +class MemPoolTest final : public CTxMemPool +{ +public: + using CTxMemPool::GetMinFee; +}; + BOOST_AUTO_TEST_CASE(MempoolRemoveTest) { // Test CTxMemPool::remove functionality @@ -56,7 +62,7 @@ BOOST_AUTO_TEST_CASE(MempoolRemoveTest) } - CTxMemPool testPool; + CTxMemPool& testPool = *Assert(m_node.mempool); LOCK2(cs_main, testPool.cs); // Nothing in pool, remove should do nothing: @@ -108,12 +114,12 @@ BOOST_AUTO_TEST_CASE(MempoolRemoveTest) BOOST_CHECK_EQUAL(testPool.size(), 0U); } -template<typename name> -static void CheckSort(CTxMemPool &pool, std::vector<std::string> &sortedOrder) EXCLUSIVE_LOCKS_REQUIRED(pool.cs) +template <typename name> +static void CheckSort(CTxMemPool& pool, std::vector<std::string>& sortedOrder) EXCLUSIVE_LOCKS_REQUIRED(pool.cs) { BOOST_CHECK_EQUAL(pool.size(), sortedOrder.size()); typename CTxMemPool::indexed_transaction_set::index<name>::type::iterator it = pool.mapTx.get<name>().begin(); - int count=0; + int count = 0; for (; it != pool.mapTx.get<name>().end(); ++it, ++count) { BOOST_CHECK_EQUAL(it->GetTx().GetHash().ToString(), sortedOrder[count]); } @@ -121,7 +127,7 @@ static void CheckSort(CTxMemPool &pool, std::vector<std::string> &sortedOrder) E BOOST_AUTO_TEST_CASE(MempoolIndexingTest) { - CTxMemPool pool; + CTxMemPool& pool = *Assert(m_node.mempool); LOCK2(cs_main, pool.cs); TestMemPoolEntryHelper entry; @@ -197,7 +203,7 @@ BOOST_AUTO_TEST_CASE(MempoolIndexingTest) CTxMemPool::setEntries setAncestorsCalculated; std::string dummy; - BOOST_CHECK_EQUAL(pool.CalculateMemPoolAncestors(entry.Fee(2000000LL).FromTx(tx7), setAncestorsCalculated, 100, 1000000, 1000, 1000000, dummy), true); + BOOST_CHECK_EQUAL(pool.CalculateMemPoolAncestors(entry.Fee(2'000'000LL).FromTx(tx7), setAncestorsCalculated, CTxMemPool::Limits::NoLimits(), dummy), true); BOOST_CHECK(setAncestorsCalculated == setAncestors); pool.addUnchecked(entry.FromTx(tx7), setAncestors); @@ -255,7 +261,7 @@ BOOST_AUTO_TEST_CASE(MempoolIndexingTest) tx10.vout[0].nValue = 10 * COIN; setAncestorsCalculated.clear(); - BOOST_CHECK_EQUAL(pool.CalculateMemPoolAncestors(entry.Fee(200000LL).Time(4).FromTx(tx10), setAncestorsCalculated, 100, 1000000, 1000, 1000000, dummy), true); + BOOST_CHECK_EQUAL(pool.CalculateMemPoolAncestors(entry.Fee(200'000LL).Time(4).FromTx(tx10), setAncestorsCalculated, CTxMemPool::Limits::NoLimits(), dummy), true); BOOST_CHECK(setAncestorsCalculated == setAncestors); pool.addUnchecked(entry.FromTx(tx10), setAncestors); @@ -294,7 +300,7 @@ BOOST_AUTO_TEST_CASE(MempoolIndexingTest) BOOST_AUTO_TEST_CASE(MempoolAncestorIndexingTest) { - CTxMemPool pool; + CTxMemPool& pool = *Assert(m_node.mempool); LOCK2(cs_main, pool.cs); TestMemPoolEntryHelper entry; @@ -423,7 +429,7 @@ BOOST_AUTO_TEST_CASE(MempoolAncestorIndexingTest) BOOST_AUTO_TEST_CASE(MempoolSizeLimitTest) { - CTxMemPool pool; + auto& pool = static_cast<MemPoolTest&>(*Assert(m_node.mempool)); LOCK2(cs_main, pool.cs); TestMemPoolEntryHelper entry; @@ -594,7 +600,7 @@ BOOST_AUTO_TEST_CASE(MempoolAncestryTests) { size_t ancestors, descendants; - CTxMemPool pool; + CTxMemPool& pool = *Assert(m_node.mempool); LOCK2(cs_main, pool.cs); TestMemPoolEntryHelper entry; @@ -753,7 +759,7 @@ BOOST_AUTO_TEST_CASE(MempoolAncestryTestsDiamond) { size_t ancestors, descendants; - CTxMemPool pool; + CTxMemPool& pool = *Assert(m_node.mempool); LOCK2(::cs_main, pool.cs); TestMemPoolEntryHelper entry; diff --git a/src/test/miner_tests.cpp b/src/test/miner_tests.cpp index 439ad174b3..696a799872 100644 --- a/src/test/miner_tests.cpp +++ b/src/test/miner_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 <chainparams.h> #include <coins.h> #include <consensus/consensus.h> #include <consensus/merkle.h> @@ -30,15 +29,24 @@ using node::CBlockTemplate; namespace miner_tests { struct MinerTestingSetup : public TestingSetup { - void TestPackageSelection(const CChainParams& chainparams, const CScript& scriptPubKey, const std::vector<CTransactionRef>& txFirst) EXCLUSIVE_LOCKS_REQUIRED(::cs_main, m_node.mempool->cs); - void TestBasicMining(const CChainParams& chainparams, const CScript& scriptPubKey, const std::vector<CTransactionRef>& txFirst, int baseheight) EXCLUSIVE_LOCKS_REQUIRED(::cs_main, m_node.mempool->cs); - void TestPrioritisedMining(const CChainParams& chainparams, const CScript& scriptPubKey, const std::vector<CTransactionRef>& txFirst) EXCLUSIVE_LOCKS_REQUIRED(::cs_main, m_node.mempool->cs); - bool TestSequenceLocks(const CTransaction& tx) EXCLUSIVE_LOCKS_REQUIRED(::cs_main, m_node.mempool->cs) + void TestPackageSelection(const CScript& scriptPubKey, const std::vector<CTransactionRef>& txFirst) EXCLUSIVE_LOCKS_REQUIRED(::cs_main); + void TestBasicMining(const CScript& scriptPubKey, const std::vector<CTransactionRef>& txFirst, int baseheight) EXCLUSIVE_LOCKS_REQUIRED(::cs_main); + void TestPrioritisedMining(const CScript& scriptPubKey, const std::vector<CTransactionRef>& txFirst) EXCLUSIVE_LOCKS_REQUIRED(::cs_main); + bool TestSequenceLocks(const CTransaction& tx, CTxMemPool& tx_mempool) EXCLUSIVE_LOCKS_REQUIRED(::cs_main) { - CCoinsViewMemPool view_mempool(&m_node.chainman->ActiveChainstate().CoinsTip(), *m_node.mempool); + CCoinsViewMemPool view_mempool{&m_node.chainman->ActiveChainstate().CoinsTip(), tx_mempool}; return CheckSequenceLocksAtTip(m_node.chainman->ActiveChain().Tip(), view_mempool, tx); } - BlockAssembler AssemblerForTest(const CChainParams& params); + CTxMemPool& MakeMempool() + { + // Delete the previous mempool to ensure with valgrind that the old + // pointer is not accessed, when the new one should be accessed + // instead. + m_node.mempool.reset(); + m_node.mempool = std::make_unique<CTxMemPool>(MemPoolOptionsForTest(m_node)); + return *m_node.mempool; + } + BlockAssembler AssemblerForTest(CTxMemPool& tx_mempool); }; } // namespace miner_tests @@ -46,13 +54,13 @@ BOOST_FIXTURE_TEST_SUITE(miner_tests, MinerTestingSetup) static CFeeRate blockMinFeeRate = CFeeRate(DEFAULT_BLOCK_MIN_TX_FEE); -BlockAssembler MinerTestingSetup::AssemblerForTest(const CChainParams& params) +BlockAssembler MinerTestingSetup::AssemblerForTest(CTxMemPool& tx_mempool) { BlockAssembler::Options options; options.nBlockMaxWeight = MAX_BLOCK_WEIGHT; options.blockMinFeeRate = blockMinFeeRate; - return BlockAssembler{m_node.chainman->ActiveChainstate(), *m_node.mempool, options}; + return BlockAssembler{m_node.chainman->ActiveChainstate(), &tx_mempool, options}; } constexpr static struct { @@ -89,8 +97,10 @@ static CBlockIndex CreateBlockIndex(int nHeight, CBlockIndex* active_chain_tip) // Test suite for ancestor feerate transaction selection. // Implemented as an additional function, rather than a separate test case, // to allow reusing the blockchain created in CreateNewBlock_validity. -void MinerTestingSetup::TestPackageSelection(const CChainParams& chainparams, const CScript& scriptPubKey, const std::vector<CTransactionRef>& txFirst) +void MinerTestingSetup::TestPackageSelection(const CScript& scriptPubKey, const std::vector<CTransactionRef>& txFirst) { + CTxMemPool& tx_mempool{MakeMempool()}; + LOCK(tx_mempool.cs); // Test the ancestor feerate transaction selection. TestMemPoolEntryHelper entry; @@ -105,21 +115,21 @@ void MinerTestingSetup::TestPackageSelection(const CChainParams& chainparams, co tx.vout[0].nValue = 5000000000LL - 1000; // This tx has a low fee: 1000 satoshis uint256 hashParentTx = tx.GetHash(); // save this txid for later use - m_node.mempool->addUnchecked(entry.Fee(1000).Time(GetTime()).SpendsCoinbase(true).FromTx(tx)); + tx_mempool.addUnchecked(entry.Fee(1000).Time(GetTime()).SpendsCoinbase(true).FromTx(tx)); // This tx has a medium fee: 10000 satoshis tx.vin[0].prevout.hash = txFirst[1]->GetHash(); tx.vout[0].nValue = 5000000000LL - 10000; uint256 hashMediumFeeTx = tx.GetHash(); - m_node.mempool->addUnchecked(entry.Fee(10000).Time(GetTime()).SpendsCoinbase(true).FromTx(tx)); + tx_mempool.addUnchecked(entry.Fee(10000).Time(GetTime()).SpendsCoinbase(true).FromTx(tx)); // This tx has a high fee, but depends on the first transaction tx.vin[0].prevout.hash = hashParentTx; tx.vout[0].nValue = 5000000000LL - 1000 - 50000; // 50k satoshi fee uint256 hashHighFeeTx = tx.GetHash(); - m_node.mempool->addUnchecked(entry.Fee(50000).Time(GetTime()).SpendsCoinbase(false).FromTx(tx)); + tx_mempool.addUnchecked(entry.Fee(50000).Time(GetTime()).SpendsCoinbase(false).FromTx(tx)); - std::unique_ptr<CBlockTemplate> pblocktemplate = AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey); + std::unique_ptr<CBlockTemplate> pblocktemplate = AssemblerForTest(tx_mempool).CreateNewBlock(scriptPubKey); BOOST_REQUIRE_EQUAL(pblocktemplate->block.vtx.size(), 4U); BOOST_CHECK(pblocktemplate->block.vtx[1]->GetHash() == hashParentTx); BOOST_CHECK(pblocktemplate->block.vtx[2]->GetHash() == hashHighFeeTx); @@ -129,7 +139,7 @@ void MinerTestingSetup::TestPackageSelection(const CChainParams& chainparams, co tx.vin[0].prevout.hash = hashHighFeeTx; tx.vout[0].nValue = 5000000000LL - 1000 - 50000; // 0 fee uint256 hashFreeTx = tx.GetHash(); - m_node.mempool->addUnchecked(entry.Fee(0).FromTx(tx)); + tx_mempool.addUnchecked(entry.Fee(0).FromTx(tx)); size_t freeTxSize = ::GetSerializeSize(tx, PROTOCOL_VERSION); // Calculate a fee on child transaction that will put the package just @@ -139,8 +149,8 @@ void MinerTestingSetup::TestPackageSelection(const CChainParams& chainparams, co tx.vin[0].prevout.hash = hashFreeTx; tx.vout[0].nValue = 5000000000LL - 1000 - 50000 - feeToUse; uint256 hashLowFeeTx = tx.GetHash(); - m_node.mempool->addUnchecked(entry.Fee(feeToUse).FromTx(tx)); - pblocktemplate = AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey); + tx_mempool.addUnchecked(entry.Fee(feeToUse).FromTx(tx)); + pblocktemplate = AssemblerForTest(tx_mempool).CreateNewBlock(scriptPubKey); // Verify that the free tx and the low fee tx didn't get selected for (size_t i=0; i<pblocktemplate->block.vtx.size(); ++i) { BOOST_CHECK(pblocktemplate->block.vtx[i]->GetHash() != hashFreeTx); @@ -150,11 +160,11 @@ void MinerTestingSetup::TestPackageSelection(const CChainParams& chainparams, co // Test that packages above the min relay fee do get included, even if one // of the transactions is below the min relay fee // Remove the low fee transaction and replace with a higher fee transaction - m_node.mempool->removeRecursive(CTransaction(tx), MemPoolRemovalReason::REPLACED); + tx_mempool.removeRecursive(CTransaction(tx), MemPoolRemovalReason::REPLACED); tx.vout[0].nValue -= 2; // Now we should be just over the min relay fee hashLowFeeTx = tx.GetHash(); - m_node.mempool->addUnchecked(entry.Fee(feeToUse+2).FromTx(tx)); - pblocktemplate = AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey); + tx_mempool.addUnchecked(entry.Fee(feeToUse + 2).FromTx(tx)); + pblocktemplate = AssemblerForTest(tx_mempool).CreateNewBlock(scriptPubKey); BOOST_REQUIRE_EQUAL(pblocktemplate->block.vtx.size(), 6U); BOOST_CHECK(pblocktemplate->block.vtx[4]->GetHash() == hashFreeTx); BOOST_CHECK(pblocktemplate->block.vtx[5]->GetHash() == hashLowFeeTx); @@ -167,7 +177,7 @@ void MinerTestingSetup::TestPackageSelection(const CChainParams& chainparams, co tx.vout[0].nValue = 5000000000LL - 100000000; tx.vout[1].nValue = 100000000; // 1BTC output uint256 hashFreeTx2 = tx.GetHash(); - m_node.mempool->addUnchecked(entry.Fee(0).SpendsCoinbase(true).FromTx(tx)); + tx_mempool.addUnchecked(entry.Fee(0).SpendsCoinbase(true).FromTx(tx)); // This tx can't be mined by itself tx.vin[0].prevout.hash = hashFreeTx2; @@ -175,8 +185,8 @@ void MinerTestingSetup::TestPackageSelection(const CChainParams& chainparams, co feeToUse = blockMinFeeRate.GetFee(freeTxSize); tx.vout[0].nValue = 5000000000LL - 100000000 - feeToUse; uint256 hashLowFeeTx2 = tx.GetHash(); - m_node.mempool->addUnchecked(entry.Fee(feeToUse).SpendsCoinbase(false).FromTx(tx)); - pblocktemplate = AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey); + tx_mempool.addUnchecked(entry.Fee(feeToUse).SpendsCoinbase(false).FromTx(tx)); + pblocktemplate = AssemblerForTest(tx_mempool).CreateNewBlock(scriptPubKey); // Verify that this tx isn't selected. for (size_t i=0; i<pblocktemplate->block.vtx.size(); ++i) { @@ -188,13 +198,13 @@ void MinerTestingSetup::TestPackageSelection(const CChainParams& chainparams, co // as well. tx.vin[0].prevout.n = 1; tx.vout[0].nValue = 100000000 - 10000; // 10k satoshi fee - m_node.mempool->addUnchecked(entry.Fee(10000).FromTx(tx)); - pblocktemplate = AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey); + tx_mempool.addUnchecked(entry.Fee(10000).FromTx(tx)); + pblocktemplate = AssemblerForTest(tx_mempool).CreateNewBlock(scriptPubKey); BOOST_REQUIRE_EQUAL(pblocktemplate->block.vtx.size(), 9U); BOOST_CHECK(pblocktemplate->block.vtx[8]->GetHash() == hashLowFeeTx2); } -void MinerTestingSetup::TestBasicMining(const CChainParams& chainparams, const CScript& scriptPubKey, const std::vector<CTransactionRef>& txFirst, int baseheight) +void MinerTestingSetup::TestBasicMining(const CScript& scriptPubKey, const std::vector<CTransactionRef>& txFirst, int baseheight) { uint256 hash; CMutableTransaction tx; @@ -202,175 +212,208 @@ void MinerTestingSetup::TestBasicMining(const CChainParams& chainparams, const C entry.nFee = 11; entry.nHeight = 11; - // Just to make sure we can still make simple blocks - auto pblocktemplate = AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey); - BOOST_CHECK(pblocktemplate); - - const CAmount BLOCKSUBSIDY = 50*COIN; + const CAmount BLOCKSUBSIDY = 50 * COIN; const CAmount LOWFEE = CENT; const CAmount HIGHFEE = COIN; - const CAmount HIGHERFEE = 4*COIN; + const CAmount HIGHERFEE = 4 * COIN; - // block sigops > limit: 1000 CHECKMULTISIG + 1 - tx.vin.resize(1); - // NOTE: OP_NOP is used to force 20 SigOps for the CHECKMULTISIG - tx.vin[0].scriptSig = CScript() << OP_0 << OP_0 << OP_0 << OP_NOP << OP_CHECKMULTISIG << OP_1; - tx.vin[0].prevout.hash = txFirst[0]->GetHash(); - tx.vin[0].prevout.n = 0; - tx.vout.resize(1); - tx.vout[0].nValue = BLOCKSUBSIDY; - for (unsigned int i = 0; i < 1001; ++i) { - tx.vout[0].nValue -= LOWFEE; - hash = tx.GetHash(); - bool spendsCoinbase = i == 0; // only first tx spends coinbase - // If we don't set the # of sig ops in the CTxMemPoolEntry, template creation fails - m_node.mempool->addUnchecked(entry.Fee(LOWFEE).Time(GetTime()).SpendsCoinbase(spendsCoinbase).FromTx(tx)); - tx.vin[0].prevout.hash = hash; + CTxMemPool& tx_mempool{MakeMempool()}; + LOCK(tx_mempool.cs); + + // Just to make sure we can still make simple blocks + auto pblocktemplate = AssemblerForTest(tx_mempool).CreateNewBlock(scriptPubKey); + BOOST_CHECK(pblocktemplate); + + // block sigops > limit: 1000 CHECKMULTISIG + 1 + tx.vin.resize(1); + // NOTE: OP_NOP is used to force 20 SigOps for the CHECKMULTISIG + tx.vin[0].scriptSig = CScript() << OP_0 << OP_0 << OP_0 << OP_NOP << OP_CHECKMULTISIG << OP_1; + tx.vin[0].prevout.hash = txFirst[0]->GetHash(); + tx.vin[0].prevout.n = 0; + tx.vout.resize(1); + tx.vout[0].nValue = BLOCKSUBSIDY; + for (unsigned int i = 0; i < 1001; ++i) { + tx.vout[0].nValue -= LOWFEE; + hash = tx.GetHash(); + bool spendsCoinbase = i == 0; // only first tx spends coinbase + // If we don't set the # of sig ops in the CTxMemPoolEntry, template creation fails + tx_mempool.addUnchecked(entry.Fee(LOWFEE).Time(GetTime()).SpendsCoinbase(spendsCoinbase).FromTx(tx)); + tx.vin[0].prevout.hash = hash; + } + + BOOST_CHECK_EXCEPTION(AssemblerForTest(tx_mempool).CreateNewBlock(scriptPubKey), std::runtime_error, HasReason("bad-blk-sigops")); } - BOOST_CHECK_EXCEPTION(AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey), std::runtime_error, HasReason("bad-blk-sigops")); - m_node.mempool->clear(); + { + CTxMemPool& tx_mempool{MakeMempool()}; + LOCK(tx_mempool.cs); + + tx.vin[0].prevout.hash = txFirst[0]->GetHash(); + tx.vout[0].nValue = BLOCKSUBSIDY; + for (unsigned int i = 0; i < 1001; ++i) { + tx.vout[0].nValue -= LOWFEE; + hash = tx.GetHash(); + bool spendsCoinbase = i == 0; // only first tx spends coinbase + // If we do set the # of sig ops in the CTxMemPoolEntry, template creation passes + tx_mempool.addUnchecked(entry.Fee(LOWFEE).Time(GetTime()).SpendsCoinbase(spendsCoinbase).SigOpsCost(80).FromTx(tx)); + tx.vin[0].prevout.hash = hash; + } + BOOST_CHECK(AssemblerForTest(tx_mempool).CreateNewBlock(scriptPubKey)); + } - tx.vin[0].prevout.hash = txFirst[0]->GetHash(); - tx.vout[0].nValue = BLOCKSUBSIDY; - for (unsigned int i = 0; i < 1001; ++i) { - tx.vout[0].nValue -= LOWFEE; - hash = tx.GetHash(); - bool spendsCoinbase = i == 0; // only first tx spends coinbase - // If we do set the # of sig ops in the CTxMemPoolEntry, template creation passes - m_node.mempool->addUnchecked(entry.Fee(LOWFEE).Time(GetTime()).SpendsCoinbase(spendsCoinbase).SigOpsCost(80).FromTx(tx)); - tx.vin[0].prevout.hash = hash; + CTxMemPool& tx_mempool{MakeMempool()}; + LOCK(tx_mempool.cs); + + // block size > limit + tx.vin[0].scriptSig = CScript(); + // 18 * (520char + DROP) + OP_1 = 9433 bytes + std::vector<unsigned char> vchData(520); + for (unsigned int i = 0; i < 18; ++i) { + tx.vin[0].scriptSig << vchData << OP_DROP; + } + tx.vin[0].scriptSig << OP_1; + tx.vin[0].prevout.hash = txFirst[0]->GetHash(); + tx.vout[0].nValue = BLOCKSUBSIDY; + for (unsigned int i = 0; i < 128; ++i) { + tx.vout[0].nValue -= LOWFEE; + hash = tx.GetHash(); + bool spendsCoinbase = i == 0; // only first tx spends coinbase + tx_mempool.addUnchecked(entry.Fee(LOWFEE).Time(GetTime()).SpendsCoinbase(spendsCoinbase).FromTx(tx)); + tx.vin[0].prevout.hash = hash; + } + BOOST_CHECK(AssemblerForTest(tx_mempool).CreateNewBlock(scriptPubKey)); } - BOOST_CHECK(pblocktemplate = AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey)); - m_node.mempool->clear(); - - // block size > limit - tx.vin[0].scriptSig = CScript(); - // 18 * (520char + DROP) + OP_1 = 9433 bytes - std::vector<unsigned char> vchData(520); - for (unsigned int i = 0; i < 18; ++i) - tx.vin[0].scriptSig << vchData << OP_DROP; - tx.vin[0].scriptSig << OP_1; - tx.vin[0].prevout.hash = txFirst[0]->GetHash(); - tx.vout[0].nValue = BLOCKSUBSIDY; - for (unsigned int i = 0; i < 128; ++i) + { - tx.vout[0].nValue -= LOWFEE; + CTxMemPool& tx_mempool{MakeMempool()}; + LOCK(tx_mempool.cs); + + // orphan in tx_mempool, template creation fails hash = tx.GetHash(); - bool spendsCoinbase = i == 0; // only first tx spends coinbase - m_node.mempool->addUnchecked(entry.Fee(LOWFEE).Time(GetTime()).SpendsCoinbase(spendsCoinbase).FromTx(tx)); - tx.vin[0].prevout.hash = hash; + tx_mempool.addUnchecked(entry.Fee(LOWFEE).Time(GetTime()).FromTx(tx)); + BOOST_CHECK_EXCEPTION(AssemblerForTest(tx_mempool).CreateNewBlock(scriptPubKey), std::runtime_error, HasReason("bad-txns-inputs-missingorspent")); } - BOOST_CHECK(pblocktemplate = AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey)); - m_node.mempool->clear(); - - // orphan in *m_node.mempool, template creation fails - hash = tx.GetHash(); - m_node.mempool->addUnchecked(entry.Fee(LOWFEE).Time(GetTime()).FromTx(tx)); - BOOST_CHECK_EXCEPTION(AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey), std::runtime_error, HasReason("bad-txns-inputs-missingorspent")); - m_node.mempool->clear(); - // child with higher feerate than parent - tx.vin[0].scriptSig = CScript() << OP_1; - tx.vin[0].prevout.hash = txFirst[1]->GetHash(); - tx.vout[0].nValue = BLOCKSUBSIDY-HIGHFEE; - hash = tx.GetHash(); - m_node.mempool->addUnchecked(entry.Fee(HIGHFEE).Time(GetTime()).SpendsCoinbase(true).FromTx(tx)); - tx.vin[0].prevout.hash = hash; - tx.vin.resize(2); - tx.vin[1].scriptSig = CScript() << OP_1; - tx.vin[1].prevout.hash = txFirst[0]->GetHash(); - tx.vin[1].prevout.n = 0; - tx.vout[0].nValue = tx.vout[0].nValue+BLOCKSUBSIDY-HIGHERFEE; //First txn output + fresh coinbase - new txn fee - hash = tx.GetHash(); - m_node.mempool->addUnchecked(entry.Fee(HIGHERFEE).Time(GetTime()).SpendsCoinbase(true).FromTx(tx)); - BOOST_CHECK(pblocktemplate = AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey)); - m_node.mempool->clear(); + { + CTxMemPool& tx_mempool{MakeMempool()}; + LOCK(tx_mempool.cs); - // coinbase in *m_node.mempool, template creation fails - tx.vin.resize(1); - tx.vin[0].prevout.SetNull(); - tx.vin[0].scriptSig = CScript() << OP_0 << OP_1; - tx.vout[0].nValue = 0; - hash = tx.GetHash(); - // give it a fee so it'll get mined - m_node.mempool->addUnchecked(entry.Fee(LOWFEE).Time(GetTime()).SpendsCoinbase(false).FromTx(tx)); - // Should throw bad-cb-multiple - BOOST_CHECK_EXCEPTION(AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey), std::runtime_error, HasReason("bad-cb-multiple")); - m_node.mempool->clear(); + // child with higher feerate than parent + tx.vin[0].scriptSig = CScript() << OP_1; + tx.vin[0].prevout.hash = txFirst[1]->GetHash(); + tx.vout[0].nValue = BLOCKSUBSIDY - HIGHFEE; + hash = tx.GetHash(); + tx_mempool.addUnchecked(entry.Fee(HIGHFEE).Time(GetTime()).SpendsCoinbase(true).FromTx(tx)); + tx.vin[0].prevout.hash = hash; + tx.vin.resize(2); + tx.vin[1].scriptSig = CScript() << OP_1; + tx.vin[1].prevout.hash = txFirst[0]->GetHash(); + tx.vin[1].prevout.n = 0; + tx.vout[0].nValue = tx.vout[0].nValue + BLOCKSUBSIDY - HIGHERFEE; // First txn output + fresh coinbase - new txn fee + hash = tx.GetHash(); + tx_mempool.addUnchecked(entry.Fee(HIGHERFEE).Time(GetTime()).SpendsCoinbase(true).FromTx(tx)); + BOOST_CHECK(AssemblerForTest(tx_mempool).CreateNewBlock(scriptPubKey)); + } - // double spend txn pair in *m_node.mempool, template creation fails - tx.vin[0].prevout.hash = txFirst[0]->GetHash(); - tx.vin[0].scriptSig = CScript() << OP_1; - tx.vout[0].nValue = BLOCKSUBSIDY-HIGHFEE; - tx.vout[0].scriptPubKey = CScript() << OP_1; - hash = tx.GetHash(); - m_node.mempool->addUnchecked(entry.Fee(HIGHFEE).Time(GetTime()).SpendsCoinbase(true).FromTx(tx)); - tx.vout[0].scriptPubKey = CScript() << OP_2; - hash = tx.GetHash(); - m_node.mempool->addUnchecked(entry.Fee(HIGHFEE).Time(GetTime()).SpendsCoinbase(true).FromTx(tx)); - BOOST_CHECK_EXCEPTION(AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey), std::runtime_error, HasReason("bad-txns-inputs-missingorspent")); - m_node.mempool->clear(); - - // subsidy changing - int nHeight = m_node.chainman->ActiveChain().Height(); - // Create an actual 209999-long block chain (without valid blocks). - while (m_node.chainman->ActiveChain().Tip()->nHeight < 209999) { - CBlockIndex* prev = m_node.chainman->ActiveChain().Tip(); - CBlockIndex* next = new CBlockIndex(); - next->phashBlock = new uint256(InsecureRand256()); - m_node.chainman->ActiveChainstate().CoinsTip().SetBestBlock(next->GetBlockHash()); - next->pprev = prev; - next->nHeight = prev->nHeight + 1; - next->BuildSkip(); - m_node.chainman->ActiveChain().SetTip(next); + { + CTxMemPool& tx_mempool{MakeMempool()}; + LOCK(tx_mempool.cs); + + // coinbase in tx_mempool, template creation fails + tx.vin.resize(1); + tx.vin[0].prevout.SetNull(); + tx.vin[0].scriptSig = CScript() << OP_0 << OP_1; + tx.vout[0].nValue = 0; + hash = tx.GetHash(); + // give it a fee so it'll get mined + tx_mempool.addUnchecked(entry.Fee(LOWFEE).Time(GetTime()).SpendsCoinbase(false).FromTx(tx)); + // Should throw bad-cb-multiple + BOOST_CHECK_EXCEPTION(AssemblerForTest(tx_mempool).CreateNewBlock(scriptPubKey), std::runtime_error, HasReason("bad-cb-multiple")); } - BOOST_CHECK(pblocktemplate = AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey)); - // Extend to a 210000-long block chain. - while (m_node.chainman->ActiveChain().Tip()->nHeight < 210000) { - CBlockIndex* prev = m_node.chainman->ActiveChain().Tip(); - CBlockIndex* next = new CBlockIndex(); - next->phashBlock = new uint256(InsecureRand256()); - m_node.chainman->ActiveChainstate().CoinsTip().SetBestBlock(next->GetBlockHash()); - next->pprev = prev; - next->nHeight = prev->nHeight + 1; - next->BuildSkip(); - m_node.chainman->ActiveChain().SetTip(next); + + { + CTxMemPool& tx_mempool{MakeMempool()}; + LOCK(tx_mempool.cs); + + // double spend txn pair in tx_mempool, template creation fails + tx.vin[0].prevout.hash = txFirst[0]->GetHash(); + tx.vin[0].scriptSig = CScript() << OP_1; + tx.vout[0].nValue = BLOCKSUBSIDY - HIGHFEE; + tx.vout[0].scriptPubKey = CScript() << OP_1; + hash = tx.GetHash(); + tx_mempool.addUnchecked(entry.Fee(HIGHFEE).Time(GetTime()).SpendsCoinbase(true).FromTx(tx)); + tx.vout[0].scriptPubKey = CScript() << OP_2; + hash = tx.GetHash(); + tx_mempool.addUnchecked(entry.Fee(HIGHFEE).Time(GetTime()).SpendsCoinbase(true).FromTx(tx)); + BOOST_CHECK_EXCEPTION(AssemblerForTest(tx_mempool).CreateNewBlock(scriptPubKey), std::runtime_error, HasReason("bad-txns-inputs-missingorspent")); } - BOOST_CHECK(pblocktemplate = AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey)); - // invalid p2sh txn in *m_node.mempool, template creation fails - tx.vin[0].prevout.hash = txFirst[0]->GetHash(); - tx.vin[0].prevout.n = 0; - tx.vin[0].scriptSig = CScript() << OP_1; - tx.vout[0].nValue = BLOCKSUBSIDY-LOWFEE; - CScript script = CScript() << OP_0; - tx.vout[0].scriptPubKey = GetScriptForDestination(ScriptHash(script)); - hash = tx.GetHash(); - m_node.mempool->addUnchecked(entry.Fee(LOWFEE).Time(GetTime()).SpendsCoinbase(true).FromTx(tx)); - tx.vin[0].prevout.hash = hash; - tx.vin[0].scriptSig = CScript() << std::vector<unsigned char>(script.begin(), script.end()); - tx.vout[0].nValue -= LOWFEE; - hash = tx.GetHash(); - m_node.mempool->addUnchecked(entry.Fee(LOWFEE).Time(GetTime()).SpendsCoinbase(false).FromTx(tx)); - // Should throw block-validation-failed - BOOST_CHECK_EXCEPTION(AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey), std::runtime_error, HasReason("block-validation-failed")); - m_node.mempool->clear(); - - // Delete the dummy blocks again. - while (m_node.chainman->ActiveChain().Tip()->nHeight > nHeight) { - CBlockIndex* del = m_node.chainman->ActiveChain().Tip(); - m_node.chainman->ActiveChain().SetTip(del->pprev); - m_node.chainman->ActiveChainstate().CoinsTip().SetBestBlock(del->pprev->GetBlockHash()); - delete del->phashBlock; - delete del; + { + CTxMemPool& tx_mempool{MakeMempool()}; + LOCK(tx_mempool.cs); + + // subsidy changing + int nHeight = m_node.chainman->ActiveChain().Height(); + // Create an actual 209999-long block chain (without valid blocks). + while (m_node.chainman->ActiveChain().Tip()->nHeight < 209999) { + CBlockIndex* prev = m_node.chainman->ActiveChain().Tip(); + CBlockIndex* next = new CBlockIndex(); + next->phashBlock = new uint256(InsecureRand256()); + m_node.chainman->ActiveChainstate().CoinsTip().SetBestBlock(next->GetBlockHash()); + next->pprev = prev; + next->nHeight = prev->nHeight + 1; + next->BuildSkip(); + m_node.chainman->ActiveChain().SetTip(*next); + } + BOOST_CHECK(AssemblerForTest(tx_mempool).CreateNewBlock(scriptPubKey)); + // Extend to a 210000-long block chain. + while (m_node.chainman->ActiveChain().Tip()->nHeight < 210000) { + CBlockIndex* prev = m_node.chainman->ActiveChain().Tip(); + CBlockIndex* next = new CBlockIndex(); + next->phashBlock = new uint256(InsecureRand256()); + m_node.chainman->ActiveChainstate().CoinsTip().SetBestBlock(next->GetBlockHash()); + next->pprev = prev; + next->nHeight = prev->nHeight + 1; + next->BuildSkip(); + m_node.chainman->ActiveChain().SetTip(*next); + } + BOOST_CHECK(AssemblerForTest(tx_mempool).CreateNewBlock(scriptPubKey)); + + // invalid p2sh txn in tx_mempool, template creation fails + tx.vin[0].prevout.hash = txFirst[0]->GetHash(); + tx.vin[0].prevout.n = 0; + tx.vin[0].scriptSig = CScript() << OP_1; + tx.vout[0].nValue = BLOCKSUBSIDY - LOWFEE; + CScript script = CScript() << OP_0; + tx.vout[0].scriptPubKey = GetScriptForDestination(ScriptHash(script)); + hash = tx.GetHash(); + tx_mempool.addUnchecked(entry.Fee(LOWFEE).Time(GetTime()).SpendsCoinbase(true).FromTx(tx)); + tx.vin[0].prevout.hash = hash; + tx.vin[0].scriptSig = CScript() << std::vector<unsigned char>(script.begin(), script.end()); + tx.vout[0].nValue -= LOWFEE; + hash = tx.GetHash(); + tx_mempool.addUnchecked(entry.Fee(LOWFEE).Time(GetTime()).SpendsCoinbase(false).FromTx(tx)); + // Should throw block-validation-failed + BOOST_CHECK_EXCEPTION(AssemblerForTest(tx_mempool).CreateNewBlock(scriptPubKey), std::runtime_error, HasReason("block-validation-failed")); + + // Delete the dummy blocks again. + while (m_node.chainman->ActiveChain().Tip()->nHeight > nHeight) { + CBlockIndex* del = m_node.chainman->ActiveChain().Tip(); + m_node.chainman->ActiveChain().SetTip(*Assert(del->pprev)); + m_node.chainman->ActiveChainstate().CoinsTip().SetBestBlock(del->pprev->GetBlockHash()); + delete del->phashBlock; + delete del; + } } + CTxMemPool& tx_mempool{MakeMempool()}; + LOCK(tx_mempool.cs); + // non-final txs in mempool - SetMockTime(m_node.chainman->ActiveChain().Tip()->GetMedianTimePast()+1); - const int flags{LOCKTIME_VERIFY_SEQUENCE | LOCKTIME_MEDIAN_TIME_PAST}; + SetMockTime(m_node.chainman->ActiveChain().Tip()->GetMedianTimePast() + 1); + const int flags{LOCKTIME_VERIFY_SEQUENCE}; // height map std::vector<int> prevheights; @@ -388,9 +431,9 @@ void MinerTestingSetup::TestBasicMining(const CChainParams& chainparams, const C tx.vout[0].scriptPubKey = CScript() << OP_1; tx.nLockTime = 0; hash = tx.GetHash(); - m_node.mempool->addUnchecked(entry.Fee(HIGHFEE).Time(GetTime()).SpendsCoinbase(true).FromTx(tx)); - BOOST_CHECK(CheckFinalTxAtTip(m_node.chainman->ActiveChain().Tip(), CTransaction{tx})); // Locktime passes - BOOST_CHECK(!TestSequenceLocks(CTransaction{tx})); // Sequence locks fail + tx_mempool.addUnchecked(entry.Fee(HIGHFEE).Time(GetTime()).SpendsCoinbase(true).FromTx(tx)); + BOOST_CHECK(CheckFinalTxAtTip(*Assert(m_node.chainman->ActiveChain().Tip()), CTransaction{tx})); // Locktime passes + BOOST_CHECK(!TestSequenceLocks(CTransaction{tx}, tx_mempool)); // Sequence locks fail { CBlockIndex* active_chain_tip = m_node.chainman->ActiveChain().Tip(); @@ -402,9 +445,9 @@ void MinerTestingSetup::TestBasicMining(const CChainParams& chainparams, const C tx.vin[0].nSequence = CTxIn::SEQUENCE_LOCKTIME_TYPE_FLAG | (((m_node.chainman->ActiveChain().Tip()->GetMedianTimePast()+1-m_node.chainman->ActiveChain()[1]->GetMedianTimePast()) >> CTxIn::SEQUENCE_LOCKTIME_GRANULARITY) + 1); // txFirst[1] is the 3rd block prevheights[0] = baseheight + 2; hash = tx.GetHash(); - m_node.mempool->addUnchecked(entry.Time(GetTime()).FromTx(tx)); - BOOST_CHECK(CheckFinalTxAtTip(m_node.chainman->ActiveChain().Tip(), CTransaction{tx})); // Locktime passes - BOOST_CHECK(!TestSequenceLocks(CTransaction{tx})); // Sequence locks fail + tx_mempool.addUnchecked(entry.Time(GetTime()).FromTx(tx)); + BOOST_CHECK(CheckFinalTxAtTip(*Assert(m_node.chainman->ActiveChain().Tip()), CTransaction{tx})); // Locktime passes + BOOST_CHECK(!TestSequenceLocks(CTransaction{tx}, tx_mempool)); // Sequence locks fail const int SEQUENCE_LOCK_TIME = 512; // Sequence locks pass 512 seconds later for (int i = 0; i < CBlockIndex::nMedianTimeSpan; ++i) @@ -425,9 +468,9 @@ void MinerTestingSetup::TestBasicMining(const CChainParams& chainparams, const C prevheights[0] = baseheight + 3; tx.nLockTime = m_node.chainman->ActiveChain().Tip()->nHeight + 1; hash = tx.GetHash(); - m_node.mempool->addUnchecked(entry.Time(GetTime()).FromTx(tx)); - BOOST_CHECK(!CheckFinalTxAtTip(m_node.chainman->ActiveChain().Tip(), CTransaction{tx})); // Locktime fails - BOOST_CHECK(TestSequenceLocks(CTransaction{tx})); // Sequence locks pass + tx_mempool.addUnchecked(entry.Time(GetTime()).FromTx(tx)); + BOOST_CHECK(!CheckFinalTxAtTip(*Assert(m_node.chainman->ActiveChain().Tip()), CTransaction{tx})); // Locktime fails + BOOST_CHECK(TestSequenceLocks(CTransaction{tx}, tx_mempool)); // Sequence locks pass BOOST_CHECK(IsFinalTx(CTransaction(tx), m_node.chainman->ActiveChain().Tip()->nHeight + 2, m_node.chainman->ActiveChain().Tip()->GetMedianTimePast())); // Locktime passes on 2nd block // absolute time locked @@ -436,9 +479,9 @@ void MinerTestingSetup::TestBasicMining(const CChainParams& chainparams, const C prevheights.resize(1); prevheights[0] = baseheight + 4; hash = tx.GetHash(); - m_node.mempool->addUnchecked(entry.Time(GetTime()).FromTx(tx)); - BOOST_CHECK(!CheckFinalTxAtTip(m_node.chainman->ActiveChain().Tip(), CTransaction{tx})); // Locktime fails - BOOST_CHECK(TestSequenceLocks(CTransaction{tx})); // Sequence locks pass + tx_mempool.addUnchecked(entry.Time(GetTime()).FromTx(tx)); + BOOST_CHECK(!CheckFinalTxAtTip(*Assert(m_node.chainman->ActiveChain().Tip()), CTransaction{tx})); // Locktime fails + BOOST_CHECK(TestSequenceLocks(CTransaction{tx}, tx_mempool)); // Sequence locks pass BOOST_CHECK(IsFinalTx(CTransaction(tx), m_node.chainman->ActiveChain().Tip()->nHeight + 2, m_node.chainman->ActiveChain().Tip()->GetMedianTimePast() + 1)); // Locktime passes 1 second later // mempool-dependent transactions (not added) @@ -446,16 +489,17 @@ void MinerTestingSetup::TestBasicMining(const CChainParams& chainparams, const C prevheights[0] = m_node.chainman->ActiveChain().Tip()->nHeight + 1; tx.nLockTime = 0; tx.vin[0].nSequence = 0; - BOOST_CHECK(CheckFinalTxAtTip(m_node.chainman->ActiveChain().Tip(), CTransaction{tx})); // Locktime passes - BOOST_CHECK(TestSequenceLocks(CTransaction{tx})); // Sequence locks pass + BOOST_CHECK(CheckFinalTxAtTip(*Assert(m_node.chainman->ActiveChain().Tip()), CTransaction{tx})); // Locktime passes + BOOST_CHECK(TestSequenceLocks(CTransaction{tx}, tx_mempool)); // Sequence locks pass tx.vin[0].nSequence = 1; - BOOST_CHECK(!TestSequenceLocks(CTransaction{tx})); // Sequence locks fail + BOOST_CHECK(!TestSequenceLocks(CTransaction{tx}, tx_mempool)); // Sequence locks fail tx.vin[0].nSequence = CTxIn::SEQUENCE_LOCKTIME_TYPE_FLAG; - BOOST_CHECK(TestSequenceLocks(CTransaction{tx})); // Sequence locks pass + BOOST_CHECK(TestSequenceLocks(CTransaction{tx}, tx_mempool)); // Sequence locks pass tx.vin[0].nSequence = CTxIn::SEQUENCE_LOCKTIME_TYPE_FLAG | 1; - BOOST_CHECK(!TestSequenceLocks(CTransaction{tx})); // Sequence locks fail + BOOST_CHECK(!TestSequenceLocks(CTransaction{tx}, tx_mempool)); // Sequence locks fail - BOOST_CHECK(pblocktemplate = AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey)); + auto pblocktemplate = AssemblerForTest(tx_mempool).CreateNewBlock(scriptPubKey); + BOOST_CHECK(pblocktemplate); // None of the of the absolute height/time locked tx should have made // it into the template because we still check IsFinalTx in CreateNewBlock, @@ -470,12 +514,15 @@ void MinerTestingSetup::TestBasicMining(const CChainParams& chainparams, const C m_node.chainman->ActiveChain().Tip()->nHeight++; SetMockTime(m_node.chainman->ActiveChain().Tip()->GetMedianTimePast() + 1); - BOOST_CHECK(pblocktemplate = AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey)); + BOOST_CHECK(pblocktemplate = AssemblerForTest(tx_mempool).CreateNewBlock(scriptPubKey)); BOOST_CHECK_EQUAL(pblocktemplate->block.vtx.size(), 5U); } -void MinerTestingSetup::TestPrioritisedMining(const CChainParams& chainparams, const CScript& scriptPubKey, const std::vector<CTransactionRef>& txFirst) +void MinerTestingSetup::TestPrioritisedMining(const CScript& scriptPubKey, const std::vector<CTransactionRef>& txFirst) { + CTxMemPool& tx_mempool{MakeMempool()}; + LOCK(tx_mempool.cs); + TestMemPoolEntryHelper entry; // Test that a tx below min fee but prioritised is included @@ -487,29 +534,29 @@ void MinerTestingSetup::TestPrioritisedMining(const CChainParams& chainparams, c tx.vout.resize(1); tx.vout[0].nValue = 5000000000LL; // 0 fee uint256 hashFreePrioritisedTx = tx.GetHash(); - m_node.mempool->addUnchecked(entry.Fee(0).Time(GetTime()).SpendsCoinbase(true).FromTx(tx)); - m_node.mempool->PrioritiseTransaction(hashFreePrioritisedTx, 5 * COIN); + tx_mempool.addUnchecked(entry.Fee(0).Time(GetTime()).SpendsCoinbase(true).FromTx(tx)); + tx_mempool.PrioritiseTransaction(hashFreePrioritisedTx, 5 * COIN); tx.vin[0].prevout.hash = txFirst[1]->GetHash(); tx.vin[0].prevout.n = 0; tx.vout[0].nValue = 5000000000LL - 1000; // This tx has a low fee: 1000 satoshis uint256 hashParentTx = tx.GetHash(); // save this txid for later use - m_node.mempool->addUnchecked(entry.Fee(1000).Time(GetTime()).SpendsCoinbase(true).FromTx(tx)); + tx_mempool.addUnchecked(entry.Fee(1000).Time(GetTime()).SpendsCoinbase(true).FromTx(tx)); // This tx has a medium fee: 10000 satoshis tx.vin[0].prevout.hash = txFirst[2]->GetHash(); tx.vout[0].nValue = 5000000000LL - 10000; uint256 hashMediumFeeTx = tx.GetHash(); - m_node.mempool->addUnchecked(entry.Fee(10000).Time(GetTime()).SpendsCoinbase(true).FromTx(tx)); - m_node.mempool->PrioritiseTransaction(hashMediumFeeTx, -5 * COIN); + tx_mempool.addUnchecked(entry.Fee(10000).Time(GetTime()).SpendsCoinbase(true).FromTx(tx)); + tx_mempool.PrioritiseTransaction(hashMediumFeeTx, -5 * COIN); // This tx also has a low fee, but is prioritised tx.vin[0].prevout.hash = hashParentTx; tx.vout[0].nValue = 5000000000LL - 1000 - 1000; // 1000 satoshi fee uint256 hashPrioritsedChild = tx.GetHash(); - m_node.mempool->addUnchecked(entry.Fee(1000).Time(GetTime()).SpendsCoinbase(false).FromTx(tx)); - m_node.mempool->PrioritiseTransaction(hashPrioritsedChild, 2 * COIN); + tx_mempool.addUnchecked(entry.Fee(1000).Time(GetTime()).SpendsCoinbase(false).FromTx(tx)); + tx_mempool.PrioritiseTransaction(hashPrioritsedChild, 2 * COIN); // Test that transaction selection properly updates ancestor fee calculations as prioritised // parents get included in a block. Create a transaction with two prioritised ancestors, each @@ -520,21 +567,21 @@ void MinerTestingSetup::TestPrioritisedMining(const CChainParams& chainparams, c tx.vin[0].prevout.hash = txFirst[3]->GetHash(); tx.vout[0].nValue = 5000000000LL; // 0 fee uint256 hashFreeParent = tx.GetHash(); - m_node.mempool->addUnchecked(entry.Fee(0).SpendsCoinbase(true).FromTx(tx)); - m_node.mempool->PrioritiseTransaction(hashFreeParent, 10 * COIN); + tx_mempool.addUnchecked(entry.Fee(0).SpendsCoinbase(true).FromTx(tx)); + tx_mempool.PrioritiseTransaction(hashFreeParent, 10 * COIN); tx.vin[0].prevout.hash = hashFreeParent; tx.vout[0].nValue = 5000000000LL; // 0 fee uint256 hashFreeChild = tx.GetHash(); - m_node.mempool->addUnchecked(entry.Fee(0).SpendsCoinbase(false).FromTx(tx)); - m_node.mempool->PrioritiseTransaction(hashFreeChild, 1 * COIN); + tx_mempool.addUnchecked(entry.Fee(0).SpendsCoinbase(false).FromTx(tx)); + tx_mempool.PrioritiseTransaction(hashFreeChild, 1 * COIN); tx.vin[0].prevout.hash = hashFreeChild; tx.vout[0].nValue = 5000000000LL; // 0 fee uint256 hashFreeGrandchild = tx.GetHash(); - m_node.mempool->addUnchecked(entry.Fee(0).SpendsCoinbase(false).FromTx(tx)); + tx_mempool.addUnchecked(entry.Fee(0).SpendsCoinbase(false).FromTx(tx)); - auto pblocktemplate = AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey); + auto pblocktemplate = AssemblerForTest(tx_mempool).CreateNewBlock(scriptPubKey); BOOST_REQUIRE_EQUAL(pblocktemplate->block.vtx.size(), 6U); BOOST_CHECK(pblocktemplate->block.vtx[1]->GetHash() == hashFreeParent); BOOST_CHECK(pblocktemplate->block.vtx[2]->GetHash() == hashFreePrioritisedTx); @@ -553,15 +600,12 @@ void MinerTestingSetup::TestPrioritisedMining(const CChainParams& chainparams, c BOOST_AUTO_TEST_CASE(CreateNewBlock_validity) { // Note that by default, these tests run with size accounting enabled. - const auto chainParams = CreateChainParams(*m_node.args, CBaseChainParams::MAIN); - const CChainParams& chainparams = *chainParams; CScript scriptPubKey = CScript() << ParseHex("04678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5f") << OP_CHECKSIG; std::unique_ptr<CBlockTemplate> pblocktemplate; - fCheckpointsEnabled = false; - + CTxMemPool& tx_mempool{*m_node.mempool}; // Simple block creation, nothing special yet: - BOOST_CHECK(pblocktemplate = AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey)); + BOOST_CHECK(pblocktemplate = AssemblerForTest(tx_mempool).CreateNewBlock(scriptPubKey)); // We can't make transactions until we have inputs // Therefore, load 110 blocks :) @@ -588,28 +632,23 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity) pblock->nNonce = bi.nonce; } std::shared_ptr<const CBlock> shared_pblock = std::make_shared<const CBlock>(*pblock); - BOOST_CHECK(Assert(m_node.chainman)->ProcessNewBlock(shared_pblock, true, nullptr)); + BOOST_CHECK(Assert(m_node.chainman)->ProcessNewBlock(shared_pblock, true, true, nullptr)); pblock->hashPrevBlock = pblock->GetHash(); } LOCK(cs_main); - LOCK(m_node.mempool->cs); - TestBasicMining(chainparams, scriptPubKey, txFirst, baseheight); + TestBasicMining(scriptPubKey, txFirst, baseheight); m_node.chainman->ActiveChain().Tip()->nHeight--; SetMockTime(0); - m_node.mempool->clear(); - TestPackageSelection(chainparams, scriptPubKey, txFirst); + TestPackageSelection(scriptPubKey, txFirst); m_node.chainman->ActiveChain().Tip()->nHeight--; SetMockTime(0); - m_node.mempool->clear(); - - TestPrioritisedMining(chainparams, scriptPubKey, txFirst); - fCheckpointsEnabled = true; + TestPrioritisedMining(scriptPubKey, txFirst); } BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/miniscript_tests.cpp b/src/test/miniscript_tests.cpp index 3877fea907..9387c01e73 100644 --- a/src/test/miniscript_tests.cpp +++ b/src/test/miniscript_tests.cpp @@ -111,11 +111,17 @@ struct KeyConverter { assert(it != g_testdata->pkmap.end()); return it->second; } + + std::optional<std::string> ToString(const Key& key) const { + return HexStr(ToPKBytes(key)); + } }; //! Singleton instance of KeyConverter. const KeyConverter CONVERTER{}; +// https://github.com/llvm/llvm-project/issues/53444 +// NOLINTNEXTLINE(misc-unused-using-decls) using miniscript::operator"" _mst; enum TestMode : int { @@ -276,7 +282,7 @@ BOOST_AUTO_TEST_CASE(fixed_tests) // (for now) have 'd:' be 'u'. This tests we can't use a 'd:' wrapper for a thresh, which requires // its subs to all be 'u' (taken from https://github.com/rust-bitcoin/rust-miniscript/discussions/341). const auto ms_minimalif = miniscript::FromString("thresh(3,c:pk_k(03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65),sc:pk_k(03fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556),sc:pk_k(0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798),sdv:older(32))", CONVERTER); - BOOST_CHECK(!ms_minimalif); + BOOST_CHECK(ms_minimalif && !ms_minimalif->IsValid()); // A Miniscript with duplicate keys is not sane const auto ms_dup1 = miniscript::FromString("and_v(v:pk(03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65),pk(03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65))", CONVERTER); BOOST_CHECK(ms_dup1); @@ -290,6 +296,18 @@ BOOST_AUTO_TEST_CASE(fixed_tests) // Same when the duplicates are on different levels in the tree const auto ms_dup4 = miniscript::FromString("thresh(2,pkh(03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65),s:pk(03fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556),a:and_b(dv:older(1),s:pk(03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65)))", CONVERTER); BOOST_CHECK(ms_dup4 && !ms_dup4->IsSane() && !ms_dup4->CheckDuplicateKey()); + // Sanity check the opposite is true, too. An otherwise sane Miniscript with no duplicate keys is sane. + const auto ms_nondup = miniscript::FromString("pk(03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65)", CONVERTER); + BOOST_CHECK(ms_nondup && ms_nondup->CheckDuplicateKey() && ms_nondup->IsSane()); + // Test we find the first insane sub closer to be a leaf node. This fragment is insane for two reasons: + // 1. It can be spent without a signature + // 2. It contains timelock mixes + // We'll report the timelock mix error, as it's "deeper" (closer to be a leaf node) than the "no 's' property" + // error is. + const auto ms_ins = miniscript::FromString("or_i(and_b(after(1),a:after(1000000000)),pk(03cdabb7f2dce7bfbd8a0b9570c6fd1e712e5d64045e9d6b517b3d5072251dc204))", CONVERTER); + BOOST_CHECK(ms_ins && ms_ins->IsValid() && !ms_ins->IsSane()); + const auto insane_sub = ms_ins->FindInsaneSub(); + BOOST_CHECK(insane_sub && *insane_sub->ToString(CONVERTER) == "and_b(after(1),a:after(1000000000))"); // Timelock tests Test("after(100)", "?", TESTMODE_VALID | TESTMODE_NONMAL); // only heightlock diff --git a/src/test/minisketch_tests.cpp b/src/test/minisketch_tests.cpp index 9c53ace633..81f2aad623 100644 --- a/src/test/minisketch_tests.cpp +++ b/src/test/minisketch_tests.cpp @@ -40,7 +40,7 @@ BOOST_AUTO_TEST_CASE(minisketch_test) Minisketch sketch_c = std::move(sketch_ar); sketch_c.Merge(sketch_br); auto dec = sketch_c.Decode(errors); - BOOST_CHECK(dec.has_value()); + BOOST_REQUIRE(dec.has_value()); auto sols = std::move(*dec); std::sort(sols.begin(), sols.end()); for (uint32_t i = 0; i < a_not_b; ++i) BOOST_CHECK_EQUAL(sols[i], start_a + i); diff --git a/src/test/multisig_tests.cpp b/src/test/multisig_tests.cpp index dccc7ce795..ce23d6013d 100644 --- a/src/test/multisig_tests.cpp +++ b/src/test/multisig_tests.cpp @@ -141,23 +141,30 @@ BOOST_AUTO_TEST_CASE(multisig_IsStandard) for (int i = 0; i < 4; i++) key[i].MakeNewKey(true); - TxoutType whichType; + const auto is_standard{[](const CScript& spk) { + TxoutType type; + bool res{::IsStandard(spk, std::nullopt, type)}; + if (res) { + BOOST_CHECK_EQUAL(type, TxoutType::MULTISIG); + } + return res; + }}; CScript a_and_b; a_and_b << OP_2 << ToByteVector(key[0].GetPubKey()) << ToByteVector(key[1].GetPubKey()) << OP_2 << OP_CHECKMULTISIG; - BOOST_CHECK(::IsStandard(a_and_b, whichType)); + BOOST_CHECK(is_standard(a_and_b)); CScript a_or_b; a_or_b << OP_1 << ToByteVector(key[0].GetPubKey()) << ToByteVector(key[1].GetPubKey()) << OP_2 << OP_CHECKMULTISIG; - BOOST_CHECK(::IsStandard(a_or_b, whichType)); + BOOST_CHECK(is_standard(a_or_b)); CScript escrow; escrow << OP_2 << ToByteVector(key[0].GetPubKey()) << ToByteVector(key[1].GetPubKey()) << ToByteVector(key[2].GetPubKey()) << OP_3 << OP_CHECKMULTISIG; - BOOST_CHECK(::IsStandard(escrow, whichType)); + BOOST_CHECK(is_standard(escrow)); CScript one_of_four; one_of_four << OP_1 << ToByteVector(key[0].GetPubKey()) << ToByteVector(key[1].GetPubKey()) << ToByteVector(key[2].GetPubKey()) << ToByteVector(key[3].GetPubKey()) << OP_4 << OP_CHECKMULTISIG; - BOOST_CHECK(!::IsStandard(one_of_four, whichType)); + BOOST_CHECK(!is_standard(one_of_four)); CScript malformed[6]; malformed[0] << OP_3 << ToByteVector(key[0].GetPubKey()) << ToByteVector(key[1].GetPubKey()) << OP_2 << OP_CHECKMULTISIG; @@ -167,8 +174,9 @@ BOOST_AUTO_TEST_CASE(multisig_IsStandard) malformed[4] << OP_1 << ToByteVector(key[0].GetPubKey()) << ToByteVector(key[1].GetPubKey()) << OP_CHECKMULTISIG; malformed[5] << OP_1 << ToByteVector(key[0].GetPubKey()) << ToByteVector(key[1].GetPubKey()); - for (int i = 0; i < 6; i++) - BOOST_CHECK(!::IsStandard(malformed[i], whichType)); + for (int i = 0; i < 6; i++) { + BOOST_CHECK(!is_standard(malformed[i])); + } } BOOST_AUTO_TEST_CASE(multisig_Sign) diff --git a/src/test/net_tests.cpp b/src/test/net_tests.cpp index e7c01bd6d0..f24509dd97 100644 --- a/src/test/net_tests.cpp +++ b/src/test/net_tests.cpp @@ -4,7 +4,7 @@ #include <chainparams.h> #include <clientversion.h> -#include <compat.h> +#include <compat/compat.h> #include <cstdint> #include <net.h> #include <net_processing.h> @@ -58,7 +58,6 @@ BOOST_AUTO_TEST_CASE(cnode_simple_test) std::string pszDest; std::unique_ptr<CNode> pnode1 = std::make_unique<CNode>(id++, - NODE_NETWORK, /*sock=*/nullptr, addr, /*nKeyedNetGroupIn=*/0, @@ -77,7 +76,6 @@ BOOST_AUTO_TEST_CASE(cnode_simple_test) BOOST_CHECK_EQUAL(pnode1->ConnectedThroughNetwork(), Network::NET_IPV4); std::unique_ptr<CNode> pnode2 = std::make_unique<CNode>(id++, - NODE_NETWORK, /*sock=*/nullptr, addr, /*nKeyedNetGroupIn=*/1, @@ -96,7 +94,6 @@ BOOST_AUTO_TEST_CASE(cnode_simple_test) BOOST_CHECK_EQUAL(pnode2->ConnectedThroughNetwork(), Network::NET_IPV4); std::unique_ptr<CNode> pnode3 = std::make_unique<CNode>(id++, - NODE_NETWORK, /*sock=*/nullptr, addr, /*nKeyedNetGroupIn=*/0, @@ -115,7 +112,6 @@ BOOST_AUTO_TEST_CASE(cnode_simple_test) BOOST_CHECK_EQUAL(pnode3->ConnectedThroughNetwork(), Network::NET_IPV4); std::unique_ptr<CNode> pnode4 = std::make_unique<CNode>(id++, - NODE_NETWORK, /*sock=*/nullptr, addr, /*nKeyedNetGroupIn=*/1, @@ -629,7 +625,6 @@ BOOST_AUTO_TEST_CASE(ipv4_peer_with_ipv6_addrMe_test) ipv4AddrPeer.s_addr = 0xa0b0c001; CAddress addr = CAddress(CService(ipv4AddrPeer, 7777), NODE_NETWORK); std::unique_ptr<CNode> pnode = std::make_unique<CNode>(/*id=*/0, - NODE_NETWORK, /*sock=*/nullptr, addr, /*nKeyedNetGroupIn=*/0, @@ -648,7 +643,7 @@ BOOST_AUTO_TEST_CASE(ipv4_peer_with_ipv6_addrMe_test) pnode->SetAddrLocal(addrLocal); // before patch, this causes undefined behavior detectable with clang's -fsanitize=memory - GetLocalAddrForPeer(&*pnode); + GetLocalAddrForPeer(*pnode); // suppress no-checks-run warning; if this test fails, it's by triggering a sanitizer BOOST_CHECK(1); @@ -678,13 +673,12 @@ BOOST_AUTO_TEST_CASE(get_local_addr_for_peer_port) // Our address:port as seen from the peer, completely different from the above. in_addr peer_us_addr; peer_us_addr.s_addr = htonl(0x02030405); - const CAddress peer_us{CService{peer_us_addr, 20002}, NODE_NETWORK}; + const CService peer_us{peer_us_addr, 20002}; // Create a peer with a routable IPv4 address (outbound). in_addr peer_out_in_addr; peer_out_in_addr.s_addr = htonl(0x01020304); CNode peer_out{/*id=*/0, - /*nLocalServicesIn=*/NODE_NETWORK, /*sock=*/nullptr, /*addrIn=*/CAddress{CService{peer_out_in_addr, 8333}, NODE_NETWORK}, /*nKeyedNetGroupIn=*/0, @@ -697,7 +691,7 @@ BOOST_AUTO_TEST_CASE(get_local_addr_for_peer_port) peer_out.SetAddrLocal(peer_us); // Without the fix peer_us:8333 is chosen instead of the proper peer_us:bind_port. - auto chosen_local_addr = GetLocalAddrForPeer(&peer_out); + auto chosen_local_addr = GetLocalAddrForPeer(peer_out); BOOST_REQUIRE(chosen_local_addr); const CService expected{peer_us_addr, bind_port}; BOOST_CHECK(*chosen_local_addr == expected); @@ -706,7 +700,6 @@ BOOST_AUTO_TEST_CASE(get_local_addr_for_peer_port) in_addr peer_in_in_addr; peer_in_in_addr.s_addr = htonl(0x05060708); CNode peer_in{/*id=*/0, - /*nLocalServicesIn=*/NODE_NETWORK, /*sock=*/nullptr, /*addrIn=*/CAddress{CService{peer_in_in_addr, 8333}, NODE_NETWORK}, /*nKeyedNetGroupIn=*/0, @@ -719,7 +712,7 @@ BOOST_AUTO_TEST_CASE(get_local_addr_for_peer_port) peer_in.SetAddrLocal(peer_us); // Without the fix peer_us:8333 is chosen instead of the proper peer_us:peer_us.GetPort(). - chosen_local_addr = GetLocalAddrForPeer(&peer_in); + chosen_local_addr = GetLocalAddrForPeer(peer_in); BOOST_REQUIRE(chosen_local_addr); BOOST_CHECK(*chosen_local_addr == peer_us); @@ -812,6 +805,8 @@ BOOST_AUTO_TEST_CASE(LocalAddress_BasicLifecycle) BOOST_AUTO_TEST_CASE(initial_advertise_from_version_message) { + LOCK(NetEventsInterface::g_msgproc_mutex); + // Tests the following scenario: // * -bind=3.4.5.6:20001 is specified // * we make an outbound connection to a peer @@ -834,7 +829,6 @@ BOOST_AUTO_TEST_CASE(initial_advertise_from_version_message) in_addr peer_in_addr; peer_in_addr.s_addr = htonl(0x01020304); CNode peer{/*id=*/0, - /*nLocalServicesIn=*/NODE_NETWORK, /*sock=*/nullptr, /*addrIn=*/CAddress{CService{peer_in_addr, 8333}, NODE_NETWORK}, /*nKeyedNetGroupIn=*/0, @@ -848,13 +842,13 @@ BOOST_AUTO_TEST_CASE(initial_advertise_from_version_message) const int64_t time{0}; const CNetMsgMaker msg_maker{PROTOCOL_VERSION}; - // Force CChainState::IsInitialBlockDownload() to return false. + // Force Chainstate::IsInitialBlockDownload() to return false. // Otherwise PushAddress() isn't called by PeerManager::ProcessMessage(). TestChainState& chainstate = *static_cast<TestChainState*>(&m_node.chainman->ActiveChainstate()); chainstate.JumpOutOfIbd(); - m_node.peerman->InitializeNode(&peer); + m_node.peerman->InitializeNode(peer, NODE_NETWORK); std::atomic<bool> interrupt_dummy{false}; std::chrono::microseconds time_received_dummy{0}; @@ -897,10 +891,7 @@ BOOST_AUTO_TEST_CASE(initial_advertise_from_version_message) } }; - { - LOCK(peer.cs_sendProcessing); - m_node.peerman->SendMessages(&peer); - } + m_node.peerman->SendMessages(&peer); BOOST_CHECK(sent); diff --git a/src/test/netbase_tests.cpp b/src/test/netbase_tests.cpp index 224dc88d0f..c2d2fa37b4 100644 --- a/src/test/netbase_tests.cpp +++ b/src/test/netbase_tests.cpp @@ -480,21 +480,21 @@ BOOST_AUTO_TEST_CASE(netbase_dont_resolve_strings_with_embedded_nul_characters) // try a few edge cases for port, service flags and time. static const std::vector<CAddress> fixture_addresses({ - CAddress( + CAddress{ CService(CNetAddr(in6_addr(IN6ADDR_LOOPBACK_INIT)), 0 /* port */), NODE_NONE, - 0x4966bc61U /* Fri Jan 9 02:54:25 UTC 2009 */ - ), - CAddress( + NodeSeconds{0x4966bc61s}, /* Fri Jan 9 02:54:25 UTC 2009 */ + }, + CAddress{ CService(CNetAddr(in6_addr(IN6ADDR_LOOPBACK_INIT)), 0x00f1 /* port */), NODE_NETWORK, - 0x83766279U /* Tue Nov 22 11:22:33 UTC 2039 */ - ), - CAddress( + NodeSeconds{0x83766279s}, /* Tue Nov 22 11:22:33 UTC 2039 */ + }, + CAddress{ CService(CNetAddr(in6_addr(IN6ADDR_LOOPBACK_INIT)), 0xf1f2 /* port */), static_cast<ServiceFlags>(NODE_WITNESS | NODE_COMPACT_FILTERS | NODE_NETWORK_LIMITED), - 0xffffffffU /* Sun Feb 7 06:28:15 UTC 2106 */ - ) + NodeSeconds{0xffffffffs}, /* Sun Feb 7 06:28:15 UTC 2106 */ + }, }); // fixture_addresses should equal to this when serialized in V1 format. diff --git a/src/test/policyestimator_tests.cpp b/src/test/policyestimator_tests.cpp index 06877898a4..3f66a8fc46 100644 --- a/src/test/policyestimator_tests.cpp +++ b/src/test/policyestimator_tests.cpp @@ -12,12 +12,12 @@ #include <boost/test/unit_test.hpp> -BOOST_FIXTURE_TEST_SUITE(policyestimator_tests, BasicTestingSetup) +BOOST_FIXTURE_TEST_SUITE(policyestimator_tests, ChainTestingSetup) BOOST_AUTO_TEST_CASE(BlockPolicyEstimates) { - CBlockPolicyEstimator feeEst; - CTxMemPool mpool(&feeEst); + CBlockPolicyEstimator& feeEst = *Assert(m_node.fee_estimator); + CTxMemPool& mpool = *Assert(m_node.mempool); LOCK2(cs_main, mpool.cs); TestMemPoolEntryHelper entry; CAmount basefee(2000); diff --git a/src/test/pow_tests.cpp b/src/test/pow_tests.cpp index 2f43ae52f7..3695ea9d16 100644 --- a/src/test/pow_tests.cpp +++ b/src/test/pow_tests.cpp @@ -20,7 +20,14 @@ BOOST_AUTO_TEST_CASE(get_next_work) pindexLast.nHeight = 32255; pindexLast.nTime = 1262152739; // Block #32255 pindexLast.nBits = 0x1d00ffff; - BOOST_CHECK_EQUAL(CalculateNextWorkRequired(&pindexLast, nLastRetargetTime, chainParams->GetConsensus()), 0x1d00d86aU); + + // Here (and below): expected_nbits is calculated in + // CalculateNextWorkRequired(); redoing the calculation here would be just + // reimplementing the same code that is written in pow.cpp. Rather than + // copy that code, we just hardcode the expected result. + unsigned int expected_nbits = 0x1d00d86aU; + BOOST_CHECK_EQUAL(CalculateNextWorkRequired(&pindexLast, nLastRetargetTime, chainParams->GetConsensus()), expected_nbits); + BOOST_CHECK(PermittedDifficultyTransition(chainParams->GetConsensus(), pindexLast.nHeight+1, pindexLast.nBits, expected_nbits)); } /* Test the constraint on the upper bound for next work */ @@ -32,7 +39,9 @@ BOOST_AUTO_TEST_CASE(get_next_work_pow_limit) pindexLast.nHeight = 2015; pindexLast.nTime = 1233061996; // Block #2015 pindexLast.nBits = 0x1d00ffff; - BOOST_CHECK_EQUAL(CalculateNextWorkRequired(&pindexLast, nLastRetargetTime, chainParams->GetConsensus()), 0x1d00ffffU); + unsigned int expected_nbits = 0x1d00ffffU; + BOOST_CHECK_EQUAL(CalculateNextWorkRequired(&pindexLast, nLastRetargetTime, chainParams->GetConsensus()), expected_nbits); + BOOST_CHECK(PermittedDifficultyTransition(chainParams->GetConsensus(), pindexLast.nHeight+1, pindexLast.nBits, expected_nbits)); } /* Test the constraint on the lower bound for actual time taken */ @@ -44,7 +53,12 @@ BOOST_AUTO_TEST_CASE(get_next_work_lower_limit_actual) pindexLast.nHeight = 68543; pindexLast.nTime = 1279297671; // Block #68543 pindexLast.nBits = 0x1c05a3f4; - BOOST_CHECK_EQUAL(CalculateNextWorkRequired(&pindexLast, nLastRetargetTime, chainParams->GetConsensus()), 0x1c0168fdU); + unsigned int expected_nbits = 0x1c0168fdU; + BOOST_CHECK_EQUAL(CalculateNextWorkRequired(&pindexLast, nLastRetargetTime, chainParams->GetConsensus()), expected_nbits); + BOOST_CHECK(PermittedDifficultyTransition(chainParams->GetConsensus(), pindexLast.nHeight+1, pindexLast.nBits, expected_nbits)); + // Test that reducing nbits further would not be a PermittedDifficultyTransition. + unsigned int invalid_nbits = expected_nbits-1; + BOOST_CHECK(!PermittedDifficultyTransition(chainParams->GetConsensus(), pindexLast.nHeight+1, pindexLast.nBits, invalid_nbits)); } /* Test the constraint on the upper bound for actual time taken */ @@ -56,7 +70,12 @@ BOOST_AUTO_TEST_CASE(get_next_work_upper_limit_actual) pindexLast.nHeight = 46367; pindexLast.nTime = 1269211443; // Block #46367 pindexLast.nBits = 0x1c387f6f; - BOOST_CHECK_EQUAL(CalculateNextWorkRequired(&pindexLast, nLastRetargetTime, chainParams->GetConsensus()), 0x1d00e1fdU); + unsigned int expected_nbits = 0x1d00e1fdU; + BOOST_CHECK_EQUAL(CalculateNextWorkRequired(&pindexLast, nLastRetargetTime, chainParams->GetConsensus()), expected_nbits); + BOOST_CHECK(PermittedDifficultyTransition(chainParams->GetConsensus(), pindexLast.nHeight+1, pindexLast.nBits, expected_nbits)); + // Test that increasing nbits further would not be a PermittedDifficultyTransition. + unsigned int invalid_nbits = expected_nbits+1; + BOOST_CHECK(!PermittedDifficultyTransition(chainParams->GetConsensus(), pindexLast.nHeight+1, pindexLast.nBits, invalid_nbits)); } BOOST_AUTO_TEST_CASE(CheckProofOfWork_test_negative_target) diff --git a/src/test/raii_event_tests.cpp b/src/test/raii_event_tests.cpp index c489ac04e9..d4487cd941 100644 --- a/src/test/raii_event_tests.cpp +++ b/src/test/raii_event_tests.cpp @@ -4,8 +4,8 @@ #include <event2/event.h> +#include <cstdlib> #include <map> -#include <stdlib.h> #include <support/events.h> diff --git a/src/test/random_tests.cpp b/src/test/random_tests.cpp index 9b2760fd1c..96fb28dc9f 100644 --- a/src/test/random_tests.cpp +++ b/src/test/random_tests.cpp @@ -53,6 +53,16 @@ BOOST_AUTO_TEST_CASE(fastrandom_tests) BOOST_CHECK_EQUAL(ctx1.randbits(3), ctx2.randbits(3)); BOOST_CHECK(ctx1.rand256() == ctx2.rand256()); BOOST_CHECK(ctx1.randbytes(50) == ctx2.randbytes(50)); + { + struct MicroClock { + using duration = std::chrono::microseconds; + }; + FastRandomContext ctx{true}; + // Check with clock type + BOOST_CHECK_EQUAL(47222, ctx.rand_uniform_duration<MicroClock>(1s).count()); + // Check with time-point type + BOOST_CHECK_EQUAL(2782, ctx.rand_uniform_duration<SteadySeconds>(9h).count()); + } // Check that a nondeterministic ones are not g_mock_deterministic_tests = false; diff --git a/src/test/rbf_tests.cpp b/src/test/rbf_tests.cpp new file mode 100644 index 0000000000..c88cd36688 --- /dev/null +++ b/src/test/rbf_tests.cpp @@ -0,0 +1,230 @@ +// 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 <policy/rbf.h> +#include <random.h> +#include <txmempool.h> +#include <util/system.h> +#include <util/time.h> + +#include <test/util/setup_common.h> + +#include <boost/test/unit_test.hpp> +#include <optional> +#include <vector> + +BOOST_FIXTURE_TEST_SUITE(rbf_tests, TestingSetup) + +static inline CTransactionRef make_tx(const std::vector<CTransactionRef>& inputs, + const std::vector<CAmount>& output_values) +{ + CMutableTransaction tx = CMutableTransaction(); + tx.vin.resize(inputs.size()); + tx.vout.resize(output_values.size()); + for (size_t i = 0; i < inputs.size(); ++i) { + tx.vin[i].prevout.hash = inputs[i]->GetHash(); + tx.vin[i].prevout.n = 0; + // Add a witness so wtxid != txid + CScriptWitness witness; + witness.stack.push_back(std::vector<unsigned char>(i + 10)); + tx.vin[i].scriptWitness = witness; + } + for (size_t i = 0; i < output_values.size(); ++i) { + tx.vout[i].scriptPubKey = CScript() << OP_11 << OP_EQUAL; + tx.vout[i].nValue = output_values[i]; + } + return MakeTransactionRef(tx); +} + +static void add_descendants(const CTransactionRef& tx, int32_t num_descendants, CTxMemPool& pool) + EXCLUSIVE_LOCKS_REQUIRED(::cs_main, pool.cs) +{ + AssertLockHeld(::cs_main); + AssertLockHeld(pool.cs); + TestMemPoolEntryHelper entry; + // Assumes this isn't already spent in mempool + auto tx_to_spend = tx; + for (int32_t i{0}; i < num_descendants; ++i) { + auto next_tx = make_tx(/*inputs=*/{tx_to_spend}, /*output_values=*/{(50 - i) * CENT}); + pool.addUnchecked(entry.FromTx(next_tx)); + tx_to_spend = next_tx; + } +} + +BOOST_FIXTURE_TEST_CASE(rbf_helper_functions, TestChain100Setup) +{ + CTxMemPool& pool = *Assert(m_node.mempool); + LOCK2(::cs_main, pool.cs); + TestMemPoolEntryHelper entry; + + const CAmount low_fee{CENT/100}; + const CAmount normal_fee{CENT/10}; + const CAmount high_fee{CENT}; + + // Create a parent tx1 and child tx2 with normal fees: + const auto tx1 = make_tx(/*inputs=*/ {m_coinbase_txns[0]}, /*output_values=*/ {10 * COIN}); + pool.addUnchecked(entry.Fee(normal_fee).FromTx(tx1)); + const auto tx2 = make_tx(/*inputs=*/ {tx1}, /*output_values=*/ {995 * CENT}); + pool.addUnchecked(entry.Fee(normal_fee).FromTx(tx2)); + + // Create a low-feerate parent tx3 and high-feerate child tx4 (cpfp) + const auto tx3 = make_tx(/*inputs=*/ {m_coinbase_txns[1]}, /*output_values=*/ {1099 * CENT}); + pool.addUnchecked(entry.Fee(low_fee).FromTx(tx3)); + const auto tx4 = make_tx(/*inputs=*/ {tx3}, /*output_values=*/ {999 * CENT}); + pool.addUnchecked(entry.Fee(high_fee).FromTx(tx4)); + + // Create a parent tx5 and child tx6 where both have very low fees + const auto tx5 = make_tx(/*inputs=*/ {m_coinbase_txns[2]}, /*output_values=*/ {1099 * CENT}); + pool.addUnchecked(entry.Fee(low_fee).FromTx(tx5)); + const auto tx6 = make_tx(/*inputs=*/ {tx5}, /*output_values=*/ {1098 * CENT}); + pool.addUnchecked(entry.Fee(low_fee).FromTx(tx6)); + // Make tx6's modified fee much higher than its base fee. This should cause it to pass + // the fee-related checks despite being low-feerate. + pool.PrioritiseTransaction(tx6->GetHash(), 1 * COIN); + + // Two independent high-feerate transactions, tx7 and tx8 + const auto tx7 = make_tx(/*inputs=*/ {m_coinbase_txns[3]}, /*output_values=*/ {999 * CENT}); + pool.addUnchecked(entry.Fee(high_fee).FromTx(tx7)); + const auto tx8 = make_tx(/*inputs=*/ {m_coinbase_txns[4]}, /*output_values=*/ {999 * CENT}); + pool.addUnchecked(entry.Fee(high_fee).FromTx(tx8)); + + const auto entry1 = pool.GetIter(tx1->GetHash()).value(); + const auto entry2 = pool.GetIter(tx2->GetHash()).value(); + const auto entry3 = pool.GetIter(tx3->GetHash()).value(); + const auto entry4 = pool.GetIter(tx4->GetHash()).value(); + const auto entry5 = pool.GetIter(tx5->GetHash()).value(); + const auto entry6 = pool.GetIter(tx6->GetHash()).value(); + const auto entry7 = pool.GetIter(tx7->GetHash()).value(); + const auto entry8 = pool.GetIter(tx8->GetHash()).value(); + + BOOST_CHECK_EQUAL(entry1->GetFee(), normal_fee); + BOOST_CHECK_EQUAL(entry2->GetFee(), normal_fee); + BOOST_CHECK_EQUAL(entry3->GetFee(), low_fee); + BOOST_CHECK_EQUAL(entry4->GetFee(), high_fee); + BOOST_CHECK_EQUAL(entry5->GetFee(), low_fee); + BOOST_CHECK_EQUAL(entry6->GetFee(), low_fee); + BOOST_CHECK_EQUAL(entry7->GetFee(), high_fee); + BOOST_CHECK_EQUAL(entry8->GetFee(), high_fee); + + CTxMemPool::setEntries set_12_normal{entry1, entry2}; + CTxMemPool::setEntries set_34_cpfp{entry3, entry4}; + CTxMemPool::setEntries set_56_low{entry5, entry6}; + CTxMemPool::setEntries all_entries{entry1, entry2, entry3, entry4, entry5, entry6, entry7, entry8}; + CTxMemPool::setEntries empty_set; + + const auto unused_txid{GetRandHash()}; + + // Tests for PaysMoreThanConflicts + // These tests use feerate, not absolute fee. + BOOST_CHECK(PaysMoreThanConflicts(/*iters_conflicting=*/set_12_normal, + /*replacement_feerate=*/CFeeRate(entry1->GetModifiedFee() + 1, entry1->GetTxSize() + 2), + /*txid=*/unused_txid).has_value()); + // Replacement must be strictly greater than the originals. + BOOST_CHECK(PaysMoreThanConflicts(set_12_normal, CFeeRate(entry1->GetModifiedFee(), entry1->GetTxSize()), unused_txid).has_value()); + BOOST_CHECK(PaysMoreThanConflicts(set_12_normal, CFeeRate(entry1->GetModifiedFee() + 1, entry1->GetTxSize()), unused_txid) == std::nullopt); + // These tests use modified fees (including prioritisation), not base fees. + BOOST_CHECK(PaysMoreThanConflicts({entry5}, CFeeRate(entry5->GetModifiedFee() + 1, entry5->GetTxSize()), unused_txid) == std::nullopt); + BOOST_CHECK(PaysMoreThanConflicts({entry6}, CFeeRate(entry6->GetFee() + 1, entry6->GetTxSize()), unused_txid).has_value()); + BOOST_CHECK(PaysMoreThanConflicts({entry6}, CFeeRate(entry6->GetModifiedFee() + 1, entry6->GetTxSize()), unused_txid) == std::nullopt); + // PaysMoreThanConflicts checks individual feerate, not ancestor feerate. This test compares + // replacement_feerate and entry4's feerate, which are the same. The replacement_feerate is + // considered too low even though entry4 has a low ancestor feerate. + BOOST_CHECK(PaysMoreThanConflicts(set_34_cpfp, CFeeRate(entry4->GetModifiedFee(), entry4->GetTxSize()), unused_txid).has_value()); + + // Tests for EntriesAndTxidsDisjoint + BOOST_CHECK(EntriesAndTxidsDisjoint(empty_set, {tx1->GetHash()}, unused_txid) == std::nullopt); + BOOST_CHECK(EntriesAndTxidsDisjoint(set_12_normal, {tx3->GetHash()}, unused_txid) == std::nullopt); + // EntriesAndTxidsDisjoint uses txids, not wtxids. + BOOST_CHECK(EntriesAndTxidsDisjoint({entry2}, {tx2->GetWitnessHash()}, unused_txid) == std::nullopt); + BOOST_CHECK(EntriesAndTxidsDisjoint({entry2}, {tx2->GetHash()}, unused_txid).has_value()); + BOOST_CHECK(EntriesAndTxidsDisjoint(set_12_normal, {tx1->GetHash()}, unused_txid).has_value()); + BOOST_CHECK(EntriesAndTxidsDisjoint(set_12_normal, {tx2->GetHash()}, unused_txid).has_value()); + // EntriesAndTxidsDisjoint does not calculate descendants of iters_conflicting; it uses whatever + // the caller passed in. As such, no error is returned even though entry2 is a descendant of tx1. + BOOST_CHECK(EntriesAndTxidsDisjoint({entry2}, {tx1->GetHash()}, unused_txid) == std::nullopt); + + // Tests for PaysForRBF + const CFeeRate incremental_relay_feerate{DEFAULT_INCREMENTAL_RELAY_FEE}; + const CFeeRate higher_relay_feerate{2 * DEFAULT_INCREMENTAL_RELAY_FEE}; + // Must pay at least as much as the original. + BOOST_CHECK(PaysForRBF(/*original_fees=*/high_fee, + /*replacement_fees=*/high_fee, + /*replacement_vsize=*/1, + /*relay_fee=*/CFeeRate(0), + /*txid=*/unused_txid) + == std::nullopt); + BOOST_CHECK(PaysForRBF(high_fee, high_fee - 1, 1, CFeeRate(0), unused_txid).has_value()); + BOOST_CHECK(PaysForRBF(high_fee + 1, high_fee, 1, CFeeRate(0), unused_txid).has_value()); + // Additional fees must cover the replacement's vsize at incremental relay fee + BOOST_CHECK(PaysForRBF(high_fee, high_fee + 1, 2, incremental_relay_feerate, unused_txid).has_value()); + BOOST_CHECK(PaysForRBF(high_fee, high_fee + 2, 2, incremental_relay_feerate, unused_txid) == std::nullopt); + BOOST_CHECK(PaysForRBF(high_fee, high_fee + 2, 2, higher_relay_feerate, unused_txid).has_value()); + BOOST_CHECK(PaysForRBF(high_fee, high_fee + 4, 2, higher_relay_feerate, unused_txid) == std::nullopt); + BOOST_CHECK(PaysForRBF(low_fee, high_fee, 99999999, incremental_relay_feerate, unused_txid).has_value()); + BOOST_CHECK(PaysForRBF(low_fee, high_fee + 99999999, 99999999, incremental_relay_feerate, unused_txid) == std::nullopt); + + // Tests for GetEntriesForConflicts + CTxMemPool::setEntries all_parents{entry1, entry3, entry5, entry7, entry8}; + CTxMemPool::setEntries all_children{entry2, entry4, entry6}; + const std::vector<CTransactionRef> parent_inputs({m_coinbase_txns[0], m_coinbase_txns[1], m_coinbase_txns[2], + m_coinbase_txns[3], m_coinbase_txns[4]}); + const auto conflicts_with_parents = make_tx(parent_inputs, {50 * CENT}); + CTxMemPool::setEntries all_conflicts; + BOOST_CHECK(GetEntriesForConflicts(/*tx=*/ *conflicts_with_parents.get(), + /*pool=*/ pool, + /*iters_conflicting=*/ all_parents, + /*all_conflicts=*/ all_conflicts) == std::nullopt); + BOOST_CHECK(all_conflicts == all_entries); + auto conflicts_size = all_conflicts.size(); + all_conflicts.clear(); + + add_descendants(tx2, 23, pool); + BOOST_CHECK(GetEntriesForConflicts(*conflicts_with_parents.get(), pool, all_parents, all_conflicts) == std::nullopt); + conflicts_size += 23; + BOOST_CHECK_EQUAL(all_conflicts.size(), conflicts_size); + all_conflicts.clear(); + + add_descendants(tx4, 23, pool); + BOOST_CHECK(GetEntriesForConflicts(*conflicts_with_parents.get(), pool, all_parents, all_conflicts) == std::nullopt); + conflicts_size += 23; + BOOST_CHECK_EQUAL(all_conflicts.size(), conflicts_size); + all_conflicts.clear(); + + add_descendants(tx6, 23, pool); + BOOST_CHECK(GetEntriesForConflicts(*conflicts_with_parents.get(), pool, all_parents, all_conflicts) == std::nullopt); + conflicts_size += 23; + BOOST_CHECK_EQUAL(all_conflicts.size(), conflicts_size); + all_conflicts.clear(); + + add_descendants(tx7, 23, pool); + BOOST_CHECK(GetEntriesForConflicts(*conflicts_with_parents.get(), pool, all_parents, all_conflicts) == std::nullopt); + conflicts_size += 23; + BOOST_CHECK_EQUAL(all_conflicts.size(), conflicts_size); + BOOST_CHECK_EQUAL(all_conflicts.size(), 100); + all_conflicts.clear(); + + // Exceeds maximum number of conflicts. + add_descendants(tx8, 1, pool); + BOOST_CHECK(GetEntriesForConflicts(*conflicts_with_parents.get(), pool, all_parents, all_conflicts).has_value()); + + // Tests for HasNoNewUnconfirmed + const auto spends_unconfirmed = make_tx({tx1}, {36 * CENT}); + for (const auto& input : spends_unconfirmed->vin) { + // Spends unconfirmed inputs. + BOOST_CHECK(pool.exists(GenTxid::Txid(input.prevout.hash))); + } + BOOST_CHECK(HasNoNewUnconfirmed(/*tx=*/ *spends_unconfirmed.get(), + /*pool=*/ pool, + /*iters_conflicting=*/ all_entries) == std::nullopt); + BOOST_CHECK(HasNoNewUnconfirmed(*spends_unconfirmed.get(), pool, {entry2}) == std::nullopt); + BOOST_CHECK(HasNoNewUnconfirmed(*spends_unconfirmed.get(), pool, empty_set).has_value()); + + const auto spends_new_unconfirmed = make_tx({tx1, tx8}, {36 * CENT}); + BOOST_CHECK(HasNoNewUnconfirmed(*spends_new_unconfirmed.get(), pool, {entry2}).has_value()); + BOOST_CHECK(HasNoNewUnconfirmed(*spends_new_unconfirmed.get(), pool, all_entries).has_value()); + + const auto spends_conflicting_confirmed = make_tx({m_coinbase_txns[0], m_coinbase_txns[1]}, {45 * CENT}); + BOOST_CHECK(HasNoNewUnconfirmed(*spends_conflicting_confirmed.get(), pool, {entry1, entry3}) == std::nullopt); +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/result_tests.cpp b/src/test/result_tests.cpp new file mode 100644 index 0000000000..6a23a7b895 --- /dev/null +++ b/src/test/result_tests.cpp @@ -0,0 +1,96 @@ +// Copyright (c) 2022 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include <util/result.h> + +#include <boost/test/unit_test.hpp> + +inline bool operator==(const bilingual_str& a, const bilingual_str& b) +{ + return a.original == b.original && a.translated == b.translated; +} + +inline std::ostream& operator<<(std::ostream& os, const bilingual_str& s) +{ + return os << "bilingual_str('" << s.original << "' , '" << s.translated << "')"; +} + +BOOST_AUTO_TEST_SUITE(result_tests) + +struct NoCopy { + NoCopy(int n) : m_n{std::make_unique<int>(n)} {} + std::unique_ptr<int> m_n; +}; + +bool operator==(const NoCopy& a, const NoCopy& b) +{ + return *a.m_n == *b.m_n; +} + +std::ostream& operator<<(std::ostream& os, const NoCopy& o) +{ + return os << "NoCopy(" << *o.m_n << ")"; +} + +util::Result<int> IntFn(int i, bool success) +{ + if (success) return i; + return util::Error{Untranslated(strprintf("int %i error.", i))}; +} + +util::Result<bilingual_str> StrFn(bilingual_str s, bool success) +{ + if (success) return s; + return util::Error{strprintf(Untranslated("str %s error."), s.original)}; +} + +util::Result<NoCopy> NoCopyFn(int i, bool success) +{ + if (success) return {i}; + return util::Error{Untranslated(strprintf("nocopy %i error.", i))}; +} + +template <typename T> +void ExpectResult(const util::Result<T>& result, bool success, const bilingual_str& str) +{ + BOOST_CHECK_EQUAL(bool(result), success); + BOOST_CHECK_EQUAL(util::ErrorString(result), str); +} + +template <typename T, typename... Args> +void ExpectSuccess(const util::Result<T>& result, const bilingual_str& str, Args&&... args) +{ + ExpectResult(result, true, str); + BOOST_CHECK_EQUAL(result.has_value(), true); + BOOST_CHECK_EQUAL(result.value(), T{std::forward<Args>(args)...}); + BOOST_CHECK_EQUAL(&result.value(), &*result); +} + +template <typename T, typename... Args> +void ExpectFail(const util::Result<T>& result, const bilingual_str& str) +{ + ExpectResult(result, false, str); +} + +BOOST_AUTO_TEST_CASE(check_returned) +{ + ExpectSuccess(IntFn(5, true), {}, 5); + ExpectFail(IntFn(5, false), Untranslated("int 5 error.")); + ExpectSuccess(NoCopyFn(5, true), {}, 5); + ExpectFail(NoCopyFn(5, false), Untranslated("nocopy 5 error.")); + ExpectSuccess(StrFn(Untranslated("S"), true), {}, Untranslated("S")); + ExpectFail(StrFn(Untranslated("S"), false), Untranslated("str S error.")); +} + +BOOST_AUTO_TEST_CASE(check_value_or) +{ + BOOST_CHECK_EQUAL(IntFn(10, true).value_or(20), 10); + BOOST_CHECK_EQUAL(IntFn(10, false).value_or(20), 20); + BOOST_CHECK_EQUAL(NoCopyFn(10, true).value_or(20), 10); + BOOST_CHECK_EQUAL(NoCopyFn(10, false).value_or(20), 20); + BOOST_CHECK_EQUAL(StrFn(Untranslated("A"), true).value_or(Untranslated("B")), Untranslated("A")); + BOOST_CHECK_EQUAL(StrFn(Untranslated("A"), false).value_or(Untranslated("B")), Untranslated("B")); +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/rpc_tests.cpp b/src/test/rpc_tests.cpp index 3e9e04da25..a52530e179 100644 --- a/src/test/rpc_tests.cpp +++ b/src/test/rpc_tests.cpp @@ -181,10 +181,10 @@ BOOST_AUTO_TEST_CASE(rpc_format_monetary_values) BOOST_CHECK_EQUAL(ValueFromAmount(std::numeric_limits<CAmount>::min()).write(), "-92233720368.54775808"); } -static UniValue ValueFromString(const std::string &str) +static UniValue ValueFromString(const std::string& str) noexcept { UniValue value; - BOOST_CHECK(value.setNumStr(str)); + value.setNumStr(str); return value; } diff --git a/src/test/script_p2sh_tests.cpp b/src/test/script_p2sh_tests.cpp index a221e02d2f..a02d51eecc 100644 --- a/src/test/script_p2sh_tests.cpp +++ b/src/test/script_p2sh_tests.cpp @@ -18,15 +18,18 @@ #include <boost/test/unit_test.hpp> // Helpers: -static std::vector<unsigned char> -Serialize(const CScript& s) +static bool IsStandardTx(const CTransaction& tx, std::string& reason) +{ + return IsStandardTx(tx, std::nullopt, DEFAULT_PERMIT_BAREMULTISIG, CFeeRate{DUST_RELAY_TX_FEE}, reason); +} + +static std::vector<unsigned char> Serialize(const CScript& s) { std::vector<unsigned char> sSerialized(s.begin(), s.end()); return sSerialized; } -static bool -Verify(const CScript& scriptSig, const CScript& scriptPubKey, bool fStrict, ScriptError& err) +static bool Verify(const CScript& scriptSig, const CScript& scriptPubKey, bool fStrict, ScriptError& err) { // Create dummy to/from transactions: CMutableTransaction txFrom; @@ -49,7 +52,6 @@ BOOST_FIXTURE_TEST_SUITE(script_p2sh_tests, BasicTestingSetup) BOOST_AUTO_TEST_CASE(sign) { - LOCK(cs_main); // Pay-to-script-hash looks like this: // scriptSig: <sig> <sig...> <serialized_script> // scriptPubKey: HASH160 <hash> EQUAL @@ -149,7 +151,6 @@ BOOST_AUTO_TEST_CASE(norecurse) BOOST_AUTO_TEST_CASE(set) { - LOCK(cs_main); // Test the CScript::Set* methods FillableSigningProvider keystore; CKey key[4]; @@ -263,7 +264,6 @@ BOOST_AUTO_TEST_CASE(switchover) BOOST_AUTO_TEST_CASE(AreInputsStandard) { - LOCK(cs_main); CCoinsView coinsDummy; CCoinsViewCache coins(&coinsDummy); FillableSigningProvider keystore; diff --git a/src/test/script_tests.cpp b/src/test/script_tests.cpp index 05bb89ab55..935194057c 100644 --- a/src/test/script_tests.cpp +++ b/src/test/script_tests.cpp @@ -942,7 +942,7 @@ BOOST_AUTO_TEST_CASE(script_json_test) UniValue tests = read_json(std::string(json_tests::script_tests, json_tests::script_tests + sizeof(json_tests::script_tests))); for (unsigned int idx = 0; idx < tests.size(); idx++) { - UniValue test = tests[idx]; + const UniValue& test = tests[idx]; std::string strTest = test.write(); CScriptWitness witness; CAmount nValue = 0; @@ -1514,8 +1514,8 @@ BOOST_AUTO_TEST_CASE(bitcoinconsensus_verify_script_returns_true) CScriptWitness wit; scriptPubKey << OP_1; - CTransaction creditTx = BuildCreditingTransaction(scriptPubKey, 1); - CTransaction spendTx = BuildSpendingTransaction(scriptSig, wit, creditTx); + CTransaction creditTx{BuildCreditingTransaction(scriptPubKey, 1)}; + CTransaction spendTx{BuildSpendingTransaction(scriptSig, wit, creditTx)}; CDataStream stream(SER_NETWORK, PROTOCOL_VERSION); stream << spendTx; @@ -1537,8 +1537,8 @@ BOOST_AUTO_TEST_CASE(bitcoinconsensus_verify_script_tx_index_err) CScriptWitness wit; scriptPubKey << OP_EQUAL; - CTransaction creditTx = BuildCreditingTransaction(scriptPubKey, 1); - CTransaction spendTx = BuildSpendingTransaction(scriptSig, wit, creditTx); + CTransaction creditTx{BuildCreditingTransaction(scriptPubKey, 1)}; + CTransaction spendTx{BuildSpendingTransaction(scriptSig, wit, creditTx)}; CDataStream stream(SER_NETWORK, PROTOCOL_VERSION); stream << spendTx; @@ -1560,8 +1560,8 @@ BOOST_AUTO_TEST_CASE(bitcoinconsensus_verify_script_tx_size) CScriptWitness wit; scriptPubKey << OP_EQUAL; - CTransaction creditTx = BuildCreditingTransaction(scriptPubKey, 1); - CTransaction spendTx = BuildSpendingTransaction(scriptSig, wit, creditTx); + CTransaction creditTx{BuildCreditingTransaction(scriptPubKey, 1)}; + CTransaction spendTx{BuildSpendingTransaction(scriptSig, wit, creditTx)}; CDataStream stream(SER_NETWORK, PROTOCOL_VERSION); stream << spendTx; @@ -1583,8 +1583,8 @@ BOOST_AUTO_TEST_CASE(bitcoinconsensus_verify_script_tx_serialization) CScriptWitness wit; scriptPubKey << OP_EQUAL; - CTransaction creditTx = BuildCreditingTransaction(scriptPubKey, 1); - CTransaction spendTx = BuildSpendingTransaction(scriptSig, wit, creditTx); + CTransaction creditTx{BuildCreditingTransaction(scriptPubKey, 1)}; + CTransaction spendTx{BuildSpendingTransaction(scriptSig, wit, creditTx)}; CDataStream stream(SER_NETWORK, PROTOCOL_VERSION); stream << 0xffffffff; @@ -1606,8 +1606,8 @@ BOOST_AUTO_TEST_CASE(bitcoinconsensus_verify_script_amount_required_err) CScriptWitness wit; scriptPubKey << OP_EQUAL; - CTransaction creditTx = BuildCreditingTransaction(scriptPubKey, 1); - CTransaction spendTx = BuildSpendingTransaction(scriptSig, wit, creditTx); + CTransaction creditTx{BuildCreditingTransaction(scriptPubKey, 1)}; + CTransaction spendTx{BuildSpendingTransaction(scriptSig, wit, creditTx)}; CDataStream stream(SER_NETWORK, PROTOCOL_VERSION); stream << spendTx; @@ -1629,8 +1629,8 @@ BOOST_AUTO_TEST_CASE(bitcoinconsensus_verify_script_invalid_flags) CScriptWitness wit; scriptPubKey << OP_EQUAL; - CTransaction creditTx = BuildCreditingTransaction(scriptPubKey, 1); - CTransaction spendTx = BuildSpendingTransaction(scriptSig, wit, creditTx); + CTransaction creditTx{BuildCreditingTransaction(scriptPubKey, 1)}; + CTransaction spendTx{BuildSpendingTransaction(scriptSig, wit, creditTx)}; CDataStream stream(SER_NETWORK, PROTOCOL_VERSION); stream << spendTx; @@ -1813,7 +1813,7 @@ BOOST_AUTO_TEST_CASE(bip341_keypath_test_vectors) BOOST_CHECK_EQUAL(HexStr(sighash), input["intermediary"]["sigHash"].get_str()); // To verify the sigmsg, hash the expected sigmsg, and compare it with the (expected) sighash. - BOOST_CHECK_EQUAL(HexStr((CHashWriter(HASHER_TAPSIGHASH) << Span{ParseHex(input["intermediary"]["sigMsg"].get_str())}).GetSHA256()), input["intermediary"]["sigHash"].get_str()); + BOOST_CHECK_EQUAL(HexStr((HashWriter{HASHER_TAPSIGHASH} << Span{ParseHex(input["intermediary"]["sigMsg"].get_str())}).GetSHA256()), input["intermediary"]["sigHash"].get_str()); } } diff --git a/src/test/sighash_tests.cpp b/src/test/sighash_tests.cpp index 7376f2ff5c..514798d8fa 100644 --- a/src/test/sighash_tests.cpp +++ b/src/test/sighash_tests.cpp @@ -165,7 +165,7 @@ BOOST_AUTO_TEST_CASE(sighash_from_data) UniValue tests = read_json(std::string(json_tests::sighash, json_tests::sighash + sizeof(json_tests::sighash))); for (unsigned int idx = 0; idx < tests.size(); idx++) { - UniValue test = tests[idx]; + const UniValue& test = tests[idx]; std::string strTest = test.write(); if (test.size() < 1) // Allow for extra stuff (useful for comments) { diff --git a/src/test/skiplist_tests.cpp b/src/test/skiplist_tests.cpp index 6dadf09176..9f5e3ab7ae 100644 --- a/src/test/skiplist_tests.cpp +++ b/src/test/skiplist_tests.cpp @@ -72,13 +72,13 @@ BOOST_AUTO_TEST_CASE(getlocator_test) // Build a CChain for the main branch. CChain chain; - chain.SetTip(&vBlocksMain.back()); + chain.SetTip(vBlocksMain.back()); // Test 100 random starting points for locators. for (int n=0; n<100; n++) { int r = InsecureRandRange(150000); CBlockIndex* tip = (r < 100000) ? &vBlocksMain[r] : &vBlocksSide[r - 100000]; - CBlockLocator locator = chain.GetLocator(tip); + CBlockLocator locator = GetLocator(tip); // The first result must be the block itself, the last one must be genesis. BOOST_CHECK(locator.vHave.front() == tip->GetBlockHash()); @@ -128,7 +128,7 @@ BOOST_AUTO_TEST_CASE(findearliestatleast_test) // Build a CChain for the main branch. CChain chain; - chain.SetTip(&vBlocksMain.back()); + chain.SetTip(vBlocksMain.back()); // Verify that FindEarliestAtLeast is correct. for (unsigned int i=0; i<10000; ++i) { @@ -155,7 +155,7 @@ BOOST_AUTO_TEST_CASE(findearliestatleast_edge_test) } CChain chain; - chain.SetTip(&blocks.back()); + chain.SetTip(blocks.back()); BOOST_CHECK_EQUAL(chain.FindEarliestAtLeast(50, 0)->nHeight, 0); BOOST_CHECK_EQUAL(chain.FindEarliestAtLeast(100, 0)->nHeight, 0); diff --git a/src/test/sock_tests.cpp b/src/test/sock_tests.cpp index 9e98f4f0b1..8376ec1a68 100644 --- a/src/test/sock_tests.cpp +++ b/src/test/sock_tests.cpp @@ -2,7 +2,7 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#include <compat.h> +#include <compat/compat.h> #include <test/util/setup_common.h> #include <threadinterrupt.h> #include <util/sock.h> @@ -69,24 +69,6 @@ BOOST_AUTO_TEST_CASE(move_assignment) BOOST_CHECK(SocketIsClosed(s)); } -BOOST_AUTO_TEST_CASE(release) -{ - SOCKET s = CreateSocket(); - Sock* sock = new Sock(s); - BOOST_CHECK_EQUAL(sock->Release(), s); - delete sock; - BOOST_CHECK(!SocketIsClosed(s)); - BOOST_REQUIRE(CloseSocket(s)); -} - -BOOST_AUTO_TEST_CASE(reset) -{ - const SOCKET s = CreateSocket(); - Sock sock(s); - sock.Reset(); - BOOST_CHECK(SocketIsClosed(s)); -} - #ifndef WIN32 // Windows does not have socketpair(2). static void CreateSocketPair(int s[2]) diff --git a/src/test/system_tests.cpp b/src/test/system_tests.cpp index 3f5353b5a2..11f4be7fef 100644 --- a/src/test/system_tests.cpp +++ b/src/test/system_tests.cpp @@ -3,15 +3,10 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. // #include <test/util/setup_common.h> -#include <util/system.h> +#include <common/run_command.h> #include <univalue.h> #ifdef ENABLE_EXTERNAL_SIGNER -#if defined(WIN32) && !defined(__kernel_entry) -// A workaround for boost 1.71 incompatibility with mingw-w64 compiler. -// For details see https://github.com/bitcoin/bitcoin/pull/22348. -#define __kernel_entry -#endif #if defined(__GNUC__) // Boost 1.78 requires the following workaround. // See: https://github.com/boostorg/process/issues/235 diff --git a/src/test/transaction_tests.cpp b/src/test/transaction_tests.cpp index 4e6c223ccc..952598f745 100644 --- a/src/test/transaction_tests.cpp +++ b/src/test/transaction_tests.cpp @@ -40,6 +40,9 @@ typedef std::vector<unsigned char> valtype; // In script_tests.cpp UniValue read_json(const std::string& jsondata); +static CFeeRate g_dust{DUST_RELAY_TX_FEE}; +static bool g_bare_multi{DEFAULT_PERMIT_BAREMULTISIG}; + static std::map<std::string, unsigned int> mapFlagNames = { {std::string("P2SH"), (unsigned int)SCRIPT_VERIFY_P2SH}, {std::string("STRICTENC"), (unsigned int)SCRIPT_VERIFY_STRICTENC}, @@ -191,7 +194,7 @@ BOOST_AUTO_TEST_CASE(tx_valid) UniValue tests = read_json(std::string(json_tests::tx_valid, json_tests::tx_valid + sizeof(json_tests::tx_valid))); for (unsigned int idx = 0; idx < tests.size(); idx++) { - UniValue test = tests[idx]; + const UniValue& test = tests[idx]; std::string strTest = test.write(); if (test[0].isArray()) { @@ -211,7 +214,7 @@ BOOST_AUTO_TEST_CASE(tx_valid) fValid = false; break; } - UniValue vinput = input.get_array(); + const UniValue& vinput = input.get_array(); if (vinput.size() < 3 || vinput.size() > 4) { fValid = false; @@ -279,7 +282,7 @@ BOOST_AUTO_TEST_CASE(tx_invalid) UniValue tests = read_json(std::string(json_tests::tx_invalid, json_tests::tx_invalid + sizeof(json_tests::tx_invalid))); for (unsigned int idx = 0; idx < tests.size(); idx++) { - UniValue test = tests[idx]; + const UniValue& test = tests[idx]; std::string strTest = test.write(); if (test[0].isArray()) { @@ -299,7 +302,7 @@ BOOST_AUTO_TEST_CASE(tx_invalid) fValid = false; break; } - UniValue vinput = input.get_array(); + const UniValue& vinput = input.get_array(); if (vinput.size() < 3 || vinput.size() > 4) { fValid = false; @@ -745,7 +748,6 @@ BOOST_AUTO_TEST_CASE(test_witness) BOOST_AUTO_TEST_CASE(test_IsStandard) { - LOCK(cs_main); FillableSigningProvider keystore; CCoinsView coinsDummy; CCoinsViewCache coins(&coinsDummy); @@ -765,19 +767,19 @@ BOOST_AUTO_TEST_CASE(test_IsStandard) constexpr auto CheckIsStandard = [](const auto& t) { std::string reason; - BOOST_CHECK(IsStandardTx(CTransaction(t), reason)); + BOOST_CHECK(IsStandardTx(CTransaction{t}, MAX_OP_RETURN_RELAY, g_bare_multi, g_dust, reason)); BOOST_CHECK(reason.empty()); }; constexpr auto CheckIsNotStandard = [](const auto& t, const std::string& reason_in) { std::string reason; - BOOST_CHECK(!IsStandardTx(CTransaction(t), reason)); + BOOST_CHECK(!IsStandardTx(CTransaction{t}, MAX_OP_RETURN_RELAY, g_bare_multi, g_dust, reason)); BOOST_CHECK_EQUAL(reason_in, reason); }; CheckIsStandard(t); // Check dust with default relay fee: - CAmount nDustThreshold = 182 * dustRelayFee.GetFeePerK() / 1000; + CAmount nDustThreshold = 182 * g_dust.GetFeePerK() / 1000; BOOST_CHECK_EQUAL(nDustThreshold, 546); // dust: t.vout[0].nValue = nDustThreshold - 1; @@ -805,14 +807,14 @@ BOOST_AUTO_TEST_CASE(test_IsStandard) // Check dust with odd relay fee to verify rounding: // nDustThreshold = 182 * 3702 / 1000 - dustRelayFee = CFeeRate(3702); + g_dust = CFeeRate(3702); // dust: t.vout[0].nValue = 674 - 1; CheckIsNotStandard(t, "dust"); // not dust: t.vout[0].nValue = 674; CheckIsStandard(t); - dustRelayFee = CFeeRate(DUST_RELAY_TX_FEE); + g_dust = CFeeRate{DUST_RELAY_TX_FEE}; t.vout[0].scriptPubKey = CScript() << OP_1; CheckIsNotStandard(t, "scriptpubkey"); @@ -924,16 +926,16 @@ BOOST_AUTO_TEST_CASE(test_IsStandard) BOOST_CHECK_EQUAL(GetTransactionWeight(CTransaction(t)), 400004); CheckIsNotStandard(t, "tx-size"); - // Check bare multisig (standard if policy flag fIsBareMultisigStd is set) - fIsBareMultisigStd = true; + // Check bare multisig (standard if policy flag g_bare_multi is set) + g_bare_multi = true; t.vout[0].scriptPubKey = GetScriptForMultisig(1, {key.GetPubKey()}); // simple 1-of-1 t.vin.resize(1); t.vin[0].scriptSig = CScript() << std::vector<unsigned char>(65, 0); CheckIsStandard(t); - fIsBareMultisigStd = false; + g_bare_multi = false; CheckIsNotStandard(t, "bare-multisig"); - fIsBareMultisigStd = DEFAULT_PERMIT_BAREMULTISIG; + g_bare_multi = DEFAULT_PERMIT_BAREMULTISIG; // Check P2WPKH outputs dust threshold t.vout[0].scriptPubKey = CScript() << OP_0 << ParseHex("ffffffffffffffffffffffffffffffffffffffff"); diff --git a/src/test/txindex_tests.cpp b/src/test/txindex_tests.cpp index 15213f826b..62c7ddb673 100644 --- a/src/test/txindex_tests.cpp +++ b/src/test/txindex_tests.cpp @@ -4,6 +4,7 @@ #include <chainparams.h> #include <index/txindex.h> +#include <interfaces/chain.h> #include <script/standard.h> #include <test/util/setup_common.h> #include <util/time.h> @@ -15,7 +16,7 @@ BOOST_AUTO_TEST_SUITE(txindex_tests) BOOST_FIXTURE_TEST_CASE(txindex_initial_sync, TestChain100Setup) { - TxIndex txindex(1 << 20, true); + TxIndex txindex(interfaces::MakeChain(m_node), 1 << 20, true); CTransactionRef tx_disk; uint256 block_hash; @@ -28,7 +29,7 @@ BOOST_FIXTURE_TEST_CASE(txindex_initial_sync, TestChain100Setup) // BlockUntilSyncedToCurrentChain should return false before txindex is started. BOOST_CHECK(!txindex.BlockUntilSyncedToCurrentChain()); - BOOST_REQUIRE(txindex.Start(m_node.chainman->ActiveChainstate())); + BOOST_REQUIRE(txindex.Start()); // Allow tx index to catch up with the block index. constexpr int64_t timeout_ms = 10 * 1000; diff --git a/src/test/txvalidationcache_tests.cpp b/src/test/txvalidationcache_tests.cpp index dd4bc5af75..633f75ff4f 100644 --- a/src/test/txvalidationcache_tests.cpp +++ b/src/test/txvalidationcache_tests.cpp @@ -161,11 +161,6 @@ BOOST_FIXTURE_TEST_CASE(checkinputs_test, Dersig100Setup) { // Test that passing CheckInputScripts with one set of script flags doesn't imply // that we would pass again with a different set of flags. - { - LOCK(cs_main); - InitScriptExecutionCache(); - } - CScript p2pk_scriptPubKey = CScript() << ToByteVector(coinbaseKey.GetPubKey()) << OP_CHECKSIG; CScript p2sh_scriptPubKey = GetScriptForDestination(ScriptHash(p2pk_scriptPubKey)); CScript p2pkh_scriptPubKey = GetScriptForDestination(PKHash(coinbaseKey.GetPubKey())); diff --git a/src/test/util/chainstate.h b/src/test/util/chainstate.h index 5ac504c24f..2f0021b114 100644 --- a/src/test/util/chainstate.h +++ b/src/test/util/chainstate.h @@ -7,6 +7,7 @@ #include <clientversion.h> #include <fs.h> +#include <logging.h> #include <node/context.h> #include <node/utxo_snapshot.h> #include <rpc/blockchain.h> @@ -14,9 +15,7 @@ #include <univalue.h> -#include <boost/test/unit_test.hpp> - -const auto NoMalleation = [](CAutoFile& file, node::SnapshotMetadata& meta){}; +const auto NoMalleation = [](AutoFile& file, node::SnapshotMetadata& meta){}; /** * Create and activate a UTXO snapshot, optionally providing a function to @@ -32,17 +31,17 @@ CreateAndActivateUTXOSnapshot(node::NodeContext& node, const fs::path root, F ma WITH_LOCK(::cs_main, height = node.chainman->ActiveHeight()); fs::path snapshot_path = root / fs::u8path(tfm::format("test_snapshot.%d.dat", height)); FILE* outfile{fsbridge::fopen(snapshot_path, "wb")}; - CAutoFile auto_outfile{outfile, SER_DISK, CLIENT_VERSION}; + AutoFile auto_outfile{outfile}; UniValue result = CreateUTXOSnapshot( node, node.chainman->ActiveChainstate(), auto_outfile, snapshot_path, snapshot_path); - BOOST_TEST_MESSAGE( - "Wrote UTXO snapshot to " << fs::PathToString(snapshot_path.make_preferred()) << ": " << result.write()); + LogPrintf( + "Wrote UTXO snapshot to %s: %s", fs::PathToString(snapshot_path.make_preferred()), result.write()); // Read the written snapshot in and then activate it. // FILE* infile{fsbridge::fopen(snapshot_path, "rb")}; - CAutoFile auto_infile{infile, SER_DISK, CLIENT_VERSION}; + AutoFile auto_infile{infile}; node::SnapshotMetadata metadata; auto_infile >> metadata; diff --git a/src/test/util/mining.cpp b/src/test/util/mining.cpp index a6d624fe84..faa0b2878c 100644 --- a/src/test/util/mining.cpp +++ b/src/test/util/mining.cpp @@ -68,7 +68,7 @@ CTxIn MineBlock(const NodeContext& node, const CScript& coinbase_scriptPubKey) assert(block->nNonce); } - bool processed{Assert(node.chainman)->ProcessNewBlock(block, true, nullptr)}; + bool processed{Assert(node.chainman)->ProcessNewBlock(block, true, true, nullptr)}; assert(processed); return CTxIn{block->vtx[0]->GetHash(), 0}; @@ -77,7 +77,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.chainman)->ActiveChainstate(), *Assert(node.mempool)} + BlockAssembler{Assert(node.chainman)->ActiveChainstate(), Assert(node.mempool.get())} .CreateNewBlock(coinbase_scriptPubKey) ->block); diff --git a/src/test/util/net.cpp b/src/test/util/net.cpp index 62b770753a..2e3e16e681 100644 --- a/src/test/util/net.cpp +++ b/src/test/util/net.cpp @@ -5,11 +5,63 @@ #include <test/util/net.h> #include <chainparams.h> +#include <node/eviction.h> #include <net.h> +#include <net_processing.h> +#include <netmessagemaker.h> #include <span.h> #include <vector> +void ConnmanTestMsg::Handshake(CNode& node, + bool successfully_connected, + ServiceFlags remote_services, + ServiceFlags local_services, + int32_t version, + bool relay_txs) +{ + auto& peerman{static_cast<PeerManager&>(*m_msgproc)}; + auto& connman{*this}; + const CNetMsgMaker mm{0}; + + peerman.InitializeNode(node, local_services); + + CSerializedNetMsg msg_version{ + mm.Make(NetMsgType::VERSION, + version, // + Using<CustomUintFormatter<8>>(remote_services), // + int64_t{}, // dummy time + int64_t{}, // ignored service bits + CService{}, // dummy + int64_t{}, // ignored service bits + CService{}, // ignored + uint64_t{1}, // dummy nonce + std::string{}, // dummy subver + int32_t{}, // dummy starting_height + relay_txs), + }; + + (void)connman.ReceiveMsgFrom(node, msg_version); + node.fPauseSend = false; + connman.ProcessMessagesOnce(node); + peerman.SendMessages(&node); + if (node.fDisconnect) return; + assert(node.nVersion == version); + assert(node.GetCommonVersion() == std::min(version, PROTOCOL_VERSION)); + CNodeStateStats statestats; + assert(peerman.GetNodeStateStats(node.GetId(), statestats)); + assert(statestats.m_relay_txs == (relay_txs && !node.IsBlockOnlyConn())); + assert(statestats.their_services == remote_services); + if (successfully_connected) { + CSerializedNetMsg msg_verack{mm.Make(NetMsgType::VERACK)}; + (void)connman.ReceiveMsgFrom(node, msg_verack); + node.fPauseSend = false; + connman.ProcessMessagesOnce(node); + peerman.SendMessages(&node); + assert(node.fSuccessfullyConnected == true); + } +} + void ConnmanTestMsg::NodeReceiveMsgBytes(CNode& node, Span<const uint8_t> msg_bytes, bool& complete) const { assert(node.ReceiveMsgBytes(msg_bytes, complete)); @@ -58,6 +110,8 @@ std::vector<NodeEvictionCandidate> GetRandomNodeEvictionCandidates(int n_candida /*prefer_evict=*/random_context.randbool(), /*m_is_local=*/random_context.randbool(), /*m_network=*/ALL_NETWORKS[random_context.randrange(ALL_NETWORKS.size())], + /*m_noban=*/false, + /*m_conn_type=*/ConnectionType::INBOUND, }); } return candidates; diff --git a/src/test/util/net.h b/src/test/util/net.h index e980fe4967..73543de4ca 100644 --- a/src/test/util/net.h +++ b/src/test/util/net.h @@ -5,7 +5,8 @@ #ifndef BITCOIN_TEST_UTIL_NET_H #define BITCOIN_TEST_UTIL_NET_H -#include <compat.h> +#include <compat/compat.h> +#include <node/eviction.h> #include <netaddress.h> #include <net.h> #include <util/sock.h> @@ -38,7 +39,15 @@ struct ConnmanTestMsg : public CConnman { m_nodes.clear(); } - void ProcessMessagesOnce(CNode& node) { m_msgproc->ProcessMessages(&node, flagInterruptMsgProc); } + void Handshake(CNode& node, + bool successfully_connected, + ServiceFlags remote_services, + ServiceFlags local_services, + int32_t version, + bool relay_txs) + EXCLUSIVE_LOCKS_REQUIRED(NetEventsInterface::g_msgproc_mutex); + + void ProcessMessagesOnce(CNode& node) EXCLUSIVE_LOCKS_REQUIRED(NetEventsInterface::g_msgproc_mutex) { m_msgproc->ProcessMessages(&node, flagInterruptMsgProc); } void NodeReceiveMsgBytes(CNode& node, Span<const uint8_t> msg_bytes, bool& complete) const; @@ -100,7 +109,7 @@ public: m_socket = INVALID_SOCKET - 1; } - ~StaticContentsSock() override { Reset(); } + ~StaticContentsSock() override { m_socket = INVALID_SOCKET; } StaticContentsSock& operator=(Sock&& other) override { @@ -108,11 +117,6 @@ public: return *this; } - void Reset() override - { - m_socket = INVALID_SOCKET; - } - ssize_t Send(const void*, size_t len, int) const override { return len; } ssize_t Recv(void* buf, size_t len, int flags) const override @@ -127,6 +131,10 @@ public: int Connect(const sockaddr*, socklen_t) const override { return 0; } + int Bind(const sockaddr*, socklen_t) const override { return 0; } + + int Listen(int) const override { return 0; } + std::unique_ptr<Sock> Accept(sockaddr* addr, socklen_t* addr_len) const override { if (addr != nullptr) { @@ -152,6 +160,12 @@ public: int SetSockOpt(int, int, const void*, socklen_t) const override { return 0; } + int GetSockName(sockaddr* name, socklen_t* name_len) const override + { + std::memset(name, 0x0, *name_len); + return 0; + } + bool Wait(std::chrono::milliseconds timeout, Event requested, Event* occurred = nullptr) const override @@ -162,6 +176,15 @@ public: return true; } + bool WaitMany(std::chrono::milliseconds timeout, EventsPerSock& events_per_sock) const override + { + for (auto& [sock, events] : events_per_sock) { + (void)sock; + events.occurred = events.requested; + } + return true; + } + private: const std::string m_contents; mutable size_t m_consumed; diff --git a/src/test/util/setup_common.cpp b/src/test/util/setup_common.cpp index 8f03481f72..74b055ee45 100644 --- a/src/test/util/setup_common.cpp +++ b/src/test/util/setup_common.cpp @@ -4,6 +4,8 @@ #include <test/util/setup_common.h> +#include <kernel/validation_cache_sizes.h> + #include <addrman.h> #include <banman.h> #include <chainparams.h> @@ -18,9 +20,13 @@ #include <net_processing.h> #include <node/blockstorage.h> #include <node/chainstate.h> +#include <node/context.h> +#include <node/mempool_args.h> #include <node/miner.h> +#include <node/validation_cache_args.h> #include <noui.h> #include <policy/fees.h> +#include <policy/fees_args.h> #include <pow.h> #include <rpc/blockchain.h> #include <rpc/register.h> @@ -32,6 +38,7 @@ #include <test/util/net.h> #include <timedata.h> #include <txdb.h> +#include <txmempool.h> #include <util/strencodings.h> #include <util/string.h> #include <util/thread.h> @@ -48,13 +55,14 @@ #include <functional> #include <stdexcept> +using kernel::ValidationCacheSizes; +using node::ApplyArgsManOptions; using node::BlockAssembler; using node::CalculateCacheSizes; using node::LoadChainstate; +using node::NodeContext; using node::RegenerateCommitments; using node::VerifyLoadedChainstate; -using node::fPruneMode; -using node::fReindex; const std::function<std::string(const char*)> G_TRANSLATION_FUN = nullptr; UrlDecodeFn* const URL_DECODE = nullptr; @@ -100,6 +108,7 @@ BasicTestingSetup::BasicTestingSetup(const std::string& chainName, const std::ve "-logsourcelocations", "-logtimemicros", "-logthreadnames", + "-loglevel=trace", "-debug", "-debugexclude=libevent", "-debugexclude=leveldb", @@ -130,8 +139,12 @@ BasicTestingSetup::BasicTestingSetup(const std::string& chainName, const std::ve m_node.kernel = std::make_unique<kernel::Context>(); SetupEnvironment(); SetupNetworking(); - InitSignatureCache(); - InitScriptExecutionCache(); + + ValidationCacheSizes validation_cache_sizes{}; + ApplyArgsManOptions(*m_node.args, validation_cache_sizes); + Assert(InitSignatureCache(validation_cache_sizes.signature_cache_bytes)); + Assert(InitScriptExecutionCache(validation_cache_sizes.script_execution_cache_bytes)); + m_node.chain = interfaces::MakeChain(m_node); fCheckBlockIndex = true; static bool noui_connected = false; @@ -149,6 +162,19 @@ BasicTestingSetup::~BasicTestingSetup() gArgs.ClearArgs(); } +CTxMemPool::Options MemPoolOptionsForTest(const NodeContext& node) +{ + CTxMemPool::Options mempool_opts{ + .estimator = node.fee_estimator.get(), + // Default to always checking mempool regardless of + // chainparams.DefaultConsistencyChecks for tests + .check_ratio = 1, + }; + const auto err{ApplyArgsManOptions(*node.args, ::Params(), mempool_opts)}; + Assert(!err); + return mempool_opts; +} + ChainTestingSetup::ChainTestingSetup(const std::string& chainName, const std::vector<const char*>& extra_args) : BasicTestingSetup(chainName, extra_args) { @@ -160,14 +186,14 @@ ChainTestingSetup::ChainTestingSetup(const std::string& chainName, const std::ve m_node.scheduler->m_service_thread = std::thread(util::TraceThread, "scheduler", [&] { m_node.scheduler->serviceQueue(); }); GetMainSignals().RegisterBackgroundSignalScheduler(*m_node.scheduler); - m_node.fee_estimator = std::make_unique<CBlockPolicyEstimator>(); - m_node.mempool = std::make_unique<CTxMemPool>(m_node.fee_estimator.get(), m_node.args->GetIntArg("-checkmempool", 1)); + m_node.fee_estimator = std::make_unique<CBlockPolicyEstimator>(FeeestPath(*m_node.args)); + m_node.mempool = std::make_unique<CTxMemPool>(MemPoolOptionsForTest(m_node)); m_cache_sizes = CalculateCacheSizes(m_args); const ChainstateManager::Options chainman_opts{ - chainparams, - GetAdjustedTime, + .chainparams = chainparams, + .adjusted_time_callback = GetAdjustedTime, }; m_node.chainman = std::make_unique<ChainstateManager>(chainman_opts); m_node.chainman->m_blockman.m_block_tree_db = std::make_unique<CBlockTreeDB>(m_cache_sizes.block_tree_db, true); @@ -201,25 +227,20 @@ TestingSetup::TestingSetup(const std::string& chainName, const std::vector<const // instead of unit tests, but for now we need these here. RegisterAllCoreRPCCommands(tableRPC); - auto maybe_load_error = LoadChainstate(fReindex.load(), - *Assert(m_node.chainman.get()), - Assert(m_node.mempool.get()), - fPruneMode, - m_args.GetBoolArg("-reindex-chainstate", false), - m_cache_sizes.block_tree_db, - m_cache_sizes.coins_db, - m_cache_sizes.coins, - /*block_tree_db_in_memory=*/true, - /*coins_db_in_memory=*/true); - assert(!maybe_load_error.has_value()); - - auto maybe_verify_error = VerifyLoadedChainstate( - *Assert(m_node.chainman), - fReindex.load(), - m_args.GetBoolArg("-reindex-chainstate", false), - m_args.GetIntArg("-checkblocks", DEFAULT_CHECKBLOCKS), - m_args.GetIntArg("-checklevel", DEFAULT_CHECKLEVEL)); - assert(!maybe_verify_error.has_value()); + node::ChainstateLoadOptions options; + options.mempool = Assert(m_node.mempool.get()); + options.block_tree_db_in_memory = true; + options.coins_db_in_memory = true; + options.reindex = node::fReindex; + options.reindex_chainstate = m_args.GetBoolArg("-reindex-chainstate", false); + options.prune = node::fPruneMode; + options.check_blocks = m_args.GetIntArg("-checkblocks", DEFAULT_CHECKBLOCKS); + options.check_level = m_args.GetIntArg("-checklevel", DEFAULT_CHECKLEVEL); + auto [status, error] = LoadChainstate(*Assert(m_node.chainman), m_cache_sizes, options); + assert(status == node::ChainstateLoadStatus::SUCCESS); + + std::tie(status, error) = VerifyLoadedChainstate(*Assert(m_node.chainman), options); + assert(status == node::ChainstateLoadStatus::SUCCESS); BlockValidationState state; if (!m_node.chainman->ActiveChainstate().ActivateBestChain(state)) { @@ -275,10 +296,9 @@ void TestChain100Setup::mineBlocks(int num_blocks) CBlock TestChain100Setup::CreateBlock( const std::vector<CMutableTransaction>& txns, const CScript& scriptPubKey, - CChainState& chainstate) + Chainstate& chainstate) { - CTxMemPool empty_pool; - CBlock block = BlockAssembler{chainstate, empty_pool}.CreateNewBlock(scriptPubKey)->block; + CBlock block = BlockAssembler{chainstate, nullptr}.CreateNewBlock(scriptPubKey)->block; Assert(block.vtx.size() == 1); for (const CMutableTransaction& tx : txns) { @@ -294,7 +314,7 @@ CBlock TestChain100Setup::CreateBlock( CBlock TestChain100Setup::CreateAndProcessBlock( const std::vector<CMutableTransaction>& txns, const CScript& scriptPubKey, - CChainState* chainstate) + Chainstate* chainstate) { if (!chainstate) { chainstate = &Assert(m_node.chainman)->ActiveChainstate(); @@ -302,7 +322,7 @@ CBlock TestChain100Setup::CreateAndProcessBlock( const CBlock block = this->CreateBlock(txns, scriptPubKey, *chainstate); std::shared_ptr<const CBlock> shared_pblock = std::make_shared<const CBlock>(block); - Assert(m_node.chainman)->ProcessNewBlock(shared_pblock, true, nullptr); + Assert(m_node.chainman)->ProcessNewBlock(shared_pblock, true, true, nullptr); return block; } diff --git a/src/test/util/setup_common.h b/src/test/util/setup_common.h index 37407bcb92..136ee1fd62 100644 --- a/src/test/util/setup_common.h +++ b/src/test/util/setup_common.h @@ -90,6 +90,9 @@ struct BasicTestingSetup { ArgsManager m_args; }; + +CTxMemPool::Options MemPoolOptionsForTest(const node::NodeContext& node); + /** Testing setup that performs all steps up until right before * ChainstateManager gets initialized. Meant for testing ChainstateManager * initialization behaviour. @@ -131,7 +134,7 @@ struct TestChain100Setup : public TestingSetup { */ CBlock CreateAndProcessBlock(const std::vector<CMutableTransaction>& txns, const CScript& scriptPubKey, - CChainState* chainstate = nullptr); + Chainstate* chainstate = nullptr); /** * Create a new block with just given transactions, coinbase paying to @@ -140,7 +143,7 @@ struct TestChain100Setup : public TestingSetup { CBlock CreateBlock( const std::vector<CMutableTransaction>& txns, const CScript& scriptPubKey, - CChainState& chainstate); + Chainstate& chainstate); //! Mine a series of new blocks on the active chain. void mineBlocks(int num_blocks); diff --git a/src/test/util/validation.h b/src/test/util/validation.h index b0bc717b6c..cbe7745b81 100644 --- a/src/test/util/validation.h +++ b/src/test/util/validation.h @@ -9,7 +9,7 @@ class CValidationInterface; -struct TestChainState : public CChainState { +struct TestChainState : public Chainstate { /** Reset the ibd cache to its initial state */ void ResetIbd(); /** Toggle IsInitialBlockDownload from true to false */ diff --git a/src/test/util/wallet.cpp b/src/test/util/wallet.cpp index 52aaeabccf..b54774cbb9 100644 --- a/src/test/util/wallet.cpp +++ b/src/test/util/wallet.cpp @@ -8,6 +8,7 @@ #include <outputtype.h> #include <script/standard.h> #ifdef ENABLE_WALLET +#include <util/check.h> #include <util/translation.h> #include <wallet/wallet.h> #endif @@ -20,11 +21,7 @@ const std::string ADDRESS_BCRT1_UNSPENDABLE = "bcrt1qqqqqqqqqqqqqqqqqqqqqqqqqqqq std::string getnewaddress(CWallet& w) { constexpr auto output_type = OutputType::BECH32; - CTxDestination dest; - bilingual_str error; - if (!w.GetNewDestination(output_type, "", dest, error)) assert(false); - - return EncodeDestination(dest); + return EncodeDestination(*Assert(w.GetNewDestination(output_type, ""))); } #endif // ENABLE_WALLET diff --git a/src/test/util_tests.cpp b/src/test/util_tests.cpp index fda56ccff7..6e59d2e8e6 100644 --- a/src/test/util_tests.cpp +++ b/src/test/util_tests.cpp @@ -23,6 +23,7 @@ #include <util/string.h> #include <util/time.h> #include <util/vector.h> +#include <util/bitdeque.h> #include <array> #include <fstream> @@ -238,15 +239,31 @@ BOOST_AUTO_TEST_CASE(span_write_bytes) BOOST_AUTO_TEST_CASE(util_Join) { // Normal version - BOOST_CHECK_EQUAL(Join({}, ", "), ""); - BOOST_CHECK_EQUAL(Join({"foo"}, ", "), "foo"); - BOOST_CHECK_EQUAL(Join({"foo", "bar"}, ", "), "foo, bar"); + BOOST_CHECK_EQUAL(Join(std::vector<std::string>{}, ", "), ""); + BOOST_CHECK_EQUAL(Join(std::vector<std::string>{"foo"}, ", "), "foo"); + BOOST_CHECK_EQUAL(Join(std::vector<std::string>{"foo", "bar"}, ", "), "foo, bar"); // Version with unary operator const auto op_upper = [](const std::string& s) { return ToUpper(s); }; - BOOST_CHECK_EQUAL(Join<std::string>({}, ", ", op_upper), ""); - BOOST_CHECK_EQUAL(Join<std::string>({"foo"}, ", ", op_upper), "FOO"); - BOOST_CHECK_EQUAL(Join<std::string>({"foo", "bar"}, ", ", op_upper), "FOO, BAR"); + BOOST_CHECK_EQUAL(Join(std::list<std::string>{}, ", ", op_upper), ""); + BOOST_CHECK_EQUAL(Join(std::list<std::string>{"foo"}, ", ", op_upper), "FOO"); + BOOST_CHECK_EQUAL(Join(std::list<std::string>{"foo", "bar"}, ", ", op_upper), "FOO, BAR"); +} + +BOOST_AUTO_TEST_CASE(util_ReplaceAll) +{ + const std::string original("A test \"%s\" string '%s'."); + auto test_replaceall = [&original](const std::string& search, const std::string& substitute, const std::string& expected) { + auto test = original; + ReplaceAll(test, search, substitute); + BOOST_CHECK_EQUAL(test, expected); + }; + + test_replaceall("", "foo", original); + test_replaceall(original, "foo", "foo"); + test_replaceall("%s", "foo", "A test \"foo\" string 'foo'."); + test_replaceall("\"", "foo", "A test foo%sfoo string '%s'."); + test_replaceall("'", "foo", "A test \"%s\" string foo%sfoo."); } BOOST_AUTO_TEST_CASE(util_TrimString) @@ -265,14 +282,10 @@ BOOST_AUTO_TEST_CASE(util_TrimString) BOOST_CHECK_EQUAL(TrimStringView(std::string("\x05\x04\x03\x02\x01\x00", 6), std::string("\x05\x04\x03\x02\x01\x00", 6)), ""); } -BOOST_AUTO_TEST_CASE(util_FormatParseISO8601DateTime) +BOOST_AUTO_TEST_CASE(util_FormatISO8601DateTime) { BOOST_CHECK_EQUAL(FormatISO8601DateTime(1317425777), "2011-09-30T23:36:17Z"); BOOST_CHECK_EQUAL(FormatISO8601DateTime(0), "1970-01-01T00:00:00Z"); - - BOOST_CHECK_EQUAL(ParseISO8601DateTime("1970-01-01T00:00:00Z"), 0); - BOOST_CHECK_EQUAL(ParseISO8601DateTime("1960-01-01T00:00:00Z"), 0); - BOOST_CHECK_EQUAL(ParseISO8601DateTime("2011-09-30T23:36:17Z"), 1317425777); } BOOST_AUTO_TEST_CASE(util_FormatISO8601Date) @@ -2494,13 +2507,13 @@ BOOST_AUTO_TEST_CASE(test_tracked_vector) auto v2 = Vector(std::move(t2)); BOOST_CHECK_EQUAL(v2.size(), 1U); - BOOST_CHECK(v2[0].origin == &t2); + BOOST_CHECK(v2[0].origin == &t2); // NOLINT(*-use-after-move) BOOST_CHECK_EQUAL(v2[0].copies, 0); auto v3 = Vector(t1, std::move(t2)); BOOST_CHECK_EQUAL(v3.size(), 2U); BOOST_CHECK(v3[0].origin == &t1); - BOOST_CHECK(v3[1].origin == &t2); + BOOST_CHECK(v3[1].origin == &t2); // NOLINT(*-use-after-move) BOOST_CHECK_EQUAL(v3[0].copies, 1); BOOST_CHECK_EQUAL(v3[1].copies, 0); @@ -2508,7 +2521,7 @@ BOOST_AUTO_TEST_CASE(test_tracked_vector) BOOST_CHECK_EQUAL(v4.size(), 3U); BOOST_CHECK(v4[0].origin == &t1); BOOST_CHECK(v4[1].origin == &t2); - BOOST_CHECK(v4[2].origin == &t3); + BOOST_CHECK(v4[2].origin == &t3); // NOLINT(*-use-after-move) BOOST_CHECK_EQUAL(v4[0].copies, 1); BOOST_CHECK_EQUAL(v4[1].copies, 1); BOOST_CHECK_EQUAL(v4[2].copies, 0); diff --git a/src/test/validation_block_tests.cpp b/src/test/validation_block_tests.cpp index 331da691b5..bb1ade153a 100644 --- a/src/test/validation_block_tests.cpp +++ b/src/test/validation_block_tests.cpp @@ -65,7 +65,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.chainman->ActiveChainstate(), *m_node.mempool}.CreateNewBlock(CScript{} << i++ << OP_TRUE); + auto ptemplate = BlockAssembler{m_node.chainman->ActiveChainstate(), m_node.mempool.get()}.CreateNewBlock(CScript{} << i++ << OP_TRUE); auto pblock = std::make_shared<CBlock>(ptemplate->block); pblock->hashPrevBlock = prev_hash; pblock->nTime = ++time; @@ -100,7 +100,7 @@ std::shared_ptr<CBlock> MinerTestingSetup::FinalizeBlock(std::shared_ptr<CBlock> // submit block header, so that miner can get the block height from the // global state and the node has the topology of the chain BlockValidationState ignored; - BOOST_CHECK(Assert(m_node.chainman)->ProcessNewBlockHeaders({pblock->GetBlockHeader()}, ignored)); + BOOST_CHECK(Assert(m_node.chainman)->ProcessNewBlockHeaders({pblock->GetBlockHeader()}, true, ignored)); return pblock; } @@ -157,7 +157,7 @@ BOOST_AUTO_TEST_CASE(processnewblock_signals_ordering) bool ignored; // Connect the genesis block and drain any outstanding events - BOOST_CHECK(Assert(m_node.chainman)->ProcessNewBlock(std::make_shared<CBlock>(Params().GenesisBlock()), true, &ignored)); + BOOST_CHECK(Assert(m_node.chainman)->ProcessNewBlock(std::make_shared<CBlock>(Params().GenesisBlock()), true, true, &ignored)); SyncWithValidationInterfaceQueue(); // subscribe to events (this subscriber will validate event ordering) @@ -179,13 +179,13 @@ BOOST_AUTO_TEST_CASE(processnewblock_signals_ordering) FastRandomContext insecure; for (int i = 0; i < 1000; i++) { auto block = blocks[insecure.randrange(blocks.size() - 1)]; - Assert(m_node.chainman)->ProcessNewBlock(block, true, &ignored); + Assert(m_node.chainman)->ProcessNewBlock(block, true, true, &ignored); } // to make sure that eventually we process the full chain - do it here - for (auto block : blocks) { + for (const auto& block : blocks) { if (block->vtx.size() == 1) { - bool processed = Assert(m_node.chainman)->ProcessNewBlock(block, true, &ignored); + bool processed = Assert(m_node.chainman)->ProcessNewBlock(block, true, true, &ignored); assert(processed); } } @@ -224,7 +224,7 @@ BOOST_AUTO_TEST_CASE(mempool_locks_reorg) { bool ignored; auto ProcessBlock = [&](std::shared_ptr<const CBlock> block) -> bool { - return Assert(m_node.chainman)->ProcessNewBlock(block, /*force_processing=*/true, /*new_block=*/&ignored); + return Assert(m_node.chainman)->ProcessNewBlock(block, /*force_processing=*/true, /*min_pow_checked=*/true, /*new_block=*/&ignored); }; // Process all mined blocks @@ -234,7 +234,7 @@ BOOST_AUTO_TEST_CASE(mempool_locks_reorg) // Run the test multiple times for (int test_runs = 3; test_runs > 0; --test_runs) { - BOOST_CHECK_EQUAL(last_mined->GetHash(), m_node.chainman->ActiveChain().Tip()->GetBlockHash()); + BOOST_CHECK_EQUAL(last_mined->GetHash(), WITH_LOCK(Assert(m_node.chainman)->GetMutex(), return m_node.chainman->ActiveChain().Tip()->GetBlockHash())); // Later on split from here const uint256 split_hash{last_mined->hashPrevBlock}; @@ -316,7 +316,7 @@ BOOST_AUTO_TEST_CASE(mempool_locks_reorg) ProcessBlock(b); } // Check that the reorg was eventually successful - BOOST_CHECK_EQUAL(last_mined->GetHash(), m_node.chainman->ActiveChain().Tip()->GetBlockHash()); + BOOST_CHECK_EQUAL(last_mined->GetHash(), WITH_LOCK(Assert(m_node.chainman)->GetMutex(), return m_node.chainman->ActiveChain().Tip()->GetBlockHash())); // We can join the other thread, which returns when the reorg was successful rpc_thread.join(); @@ -325,9 +325,10 @@ BOOST_AUTO_TEST_CASE(mempool_locks_reorg) BOOST_AUTO_TEST_CASE(witness_commitment_index) { + LOCK(Assert(m_node.chainman)->GetMutex()); CScript pubKey; pubKey << 1 << OP_TRUE; - auto ptemplate = BlockAssembler{m_node.chainman->ActiveChainstate(), *m_node.mempool}.CreateNewBlock(pubKey); + auto ptemplate = BlockAssembler{m_node.chainman->ActiveChainstate(), m_node.mempool.get()}.CreateNewBlock(pubKey); CBlock pblock = ptemplate->block; CTxOut witness; diff --git a/src/test/validation_chainstate_tests.cpp b/src/test/validation_chainstate_tests.cpp index 98cb713a81..347a967b33 100644 --- a/src/test/validation_chainstate_tests.cpp +++ b/src/test/validation_chainstate_tests.cpp @@ -9,7 +9,6 @@ #include <sync.h> #include <test/util/chainstate.h> #include <test/util/setup_common.h> -#include <timedata.h> #include <uint256.h> #include <validation.h> @@ -17,20 +16,14 @@ #include <boost/test/unit_test.hpp> -BOOST_FIXTURE_TEST_SUITE(validation_chainstate_tests, TestingSetup) +BOOST_FIXTURE_TEST_SUITE(validation_chainstate_tests, ChainTestingSetup) -//! Test resizing coins-related CChainState caches during runtime. +//! Test resizing coins-related Chainstate caches during runtime. //! BOOST_AUTO_TEST_CASE(validation_chainstate_resize_caches) { - const ChainstateManager::Options chainman_opts{ - Params(), - GetAdjustedTime, - }; - ChainstateManager manager{chainman_opts}; - - WITH_LOCK(::cs_main, manager.m_blockman.m_block_tree_db = std::make_unique<CBlockTreeDB>(1 << 20, true)); - CTxMemPool mempool; + ChainstateManager& manager = *Assert(m_node.chainman); + CTxMemPool& mempool = *Assert(m_node.mempool); //! Create and add a Coin with DynamicMemoryUsage of 80 bytes to the given view. auto add_coin = [](CCoinsViewCache& coins_view) -> COutPoint { @@ -45,7 +38,7 @@ BOOST_AUTO_TEST_CASE(validation_chainstate_resize_caches) return outp; }; - CChainState& c1 = WITH_LOCK(cs_main, return manager.InitializeChainstate(&mempool)); + Chainstate& c1 = WITH_LOCK(cs_main, return manager.InitializeChainstate(&mempool)); c1.InitCoinsDB( /*cache_size_bytes=*/1 << 23, /*in_memory=*/true, /*should_wipe=*/false); WITH_LOCK(::cs_main, c1.InitCoinsCache(1 << 23)); @@ -113,8 +106,8 @@ BOOST_FIXTURE_TEST_CASE(chainstate_update_tip, TestChain100Setup) BOOST_CHECK_EQUAL(chainman.GetAll().size(), 2); - CChainState& background_cs{*[&] { - for (CChainState* cs : chainman.GetAll()) { + Chainstate& background_cs{*[&] { + for (Chainstate* cs : chainman.GetAll()) { if (cs != &chainman.ActiveChainstate()) { return cs; } @@ -139,7 +132,7 @@ BOOST_FIXTURE_TEST_CASE(chainstate_update_tip, TestChain100Setup) bool checked = CheckBlock(*pblock, state, chainparams.GetConsensus()); BOOST_CHECK(checked); bool accepted = background_cs.AcceptBlock( - pblock, state, &pindex, true, nullptr, &newblock); + pblock, state, &pindex, true, nullptr, &newblock, true); BOOST_CHECK(accepted); } // UpdateTip is called here diff --git a/src/test/validation_chainstatemanager_tests.cpp b/src/test/validation_chainstatemanager_tests.cpp index 6dc522b421..9fcb7d315a 100644 --- a/src/test/validation_chainstatemanager_tests.cpp +++ b/src/test/validation_chainstatemanager_tests.cpp @@ -32,13 +32,13 @@ BOOST_AUTO_TEST_CASE(chainstatemanager) ChainstateManager& manager = *m_node.chainman; CTxMemPool& mempool = *m_node.mempool; - std::vector<CChainState*> chainstates; + std::vector<Chainstate*> chainstates; BOOST_CHECK(!manager.SnapshotBlockhash().has_value()); // Create a legacy (IBD) chainstate. // - CChainState& c1 = WITH_LOCK(::cs_main, return manager.InitializeChainstate(&mempool)); + Chainstate& c1 = WITH_LOCK(::cs_main, return manager.InitializeChainstate(&mempool)); chainstates.push_back(&c1); c1.InitCoinsDB( /*cache_size_bytes=*/1 << 23, /*in_memory=*/true, /*should_wipe=*/false); @@ -49,12 +49,12 @@ BOOST_AUTO_TEST_CASE(chainstatemanager) auto all = manager.GetAll(); BOOST_CHECK_EQUAL_COLLECTIONS(all.begin(), all.end(), chainstates.begin(), chainstates.end()); - auto& active_chain = manager.ActiveChain(); + auto& active_chain = WITH_LOCK(manager.GetMutex(), return manager.ActiveChain()); BOOST_CHECK_EQUAL(&active_chain, &c1.m_chain); - BOOST_CHECK_EQUAL(manager.ActiveHeight(), -1); + BOOST_CHECK_EQUAL(WITH_LOCK(manager.GetMutex(), return manager.ActiveHeight()), -1); - auto active_tip = manager.ActiveTip(); + auto active_tip = WITH_LOCK(manager.GetMutex(), return manager.ActiveTip()); auto exp_tip = c1.m_chain.Tip(); BOOST_CHECK_EQUAL(active_tip, exp_tip); @@ -63,7 +63,7 @@ BOOST_AUTO_TEST_CASE(chainstatemanager) // Create a snapshot-based chainstate. // const uint256 snapshot_blockhash = GetRandHash(); - CChainState& c2 = WITH_LOCK(::cs_main, return manager.InitializeChainstate( + Chainstate& c2 = WITH_LOCK(::cs_main, return manager.InitializeChainstate( &mempool, snapshot_blockhash)); chainstates.push_back(&c2); @@ -84,12 +84,12 @@ BOOST_AUTO_TEST_CASE(chainstatemanager) auto all2 = manager.GetAll(); BOOST_CHECK_EQUAL_COLLECTIONS(all2.begin(), all2.end(), chainstates.begin(), chainstates.end()); - auto& active_chain2 = manager.ActiveChain(); + auto& active_chain2 = WITH_LOCK(manager.GetMutex(), return manager.ActiveChain()); BOOST_CHECK_EQUAL(&active_chain2, &c2.m_chain); - BOOST_CHECK_EQUAL(manager.ActiveHeight(), 0); + BOOST_CHECK_EQUAL(WITH_LOCK(manager.GetMutex(), return manager.ActiveHeight()), 0); - auto active_tip2 = manager.ActiveTip(); + auto active_tip2 = WITH_LOCK(manager.GetMutex(), return manager.ActiveTip()); auto exp_tip2 = c2.m_chain.Tip(); BOOST_CHECK_EQUAL(active_tip2, exp_tip2); @@ -111,11 +111,11 @@ BOOST_AUTO_TEST_CASE(chainstatemanager_rebalance_caches) manager.m_total_coinsdb_cache = max_cache; manager.m_total_coinstip_cache = max_cache; - std::vector<CChainState*> chainstates; + std::vector<Chainstate*> chainstates; // Create a legacy (IBD) chainstate. // - CChainState& c1 = WITH_LOCK(cs_main, return manager.InitializeChainstate(&mempool)); + Chainstate& c1 = WITH_LOCK(cs_main, return manager.InitializeChainstate(&mempool)); chainstates.push_back(&c1); c1.InitCoinsDB( /*cache_size_bytes=*/1 << 23, /*in_memory=*/true, /*should_wipe=*/false); @@ -133,7 +133,7 @@ BOOST_AUTO_TEST_CASE(chainstatemanager_rebalance_caches) // Create a snapshot-based chainstate. // - CChainState& c2 = WITH_LOCK(cs_main, return manager.InitializeChainstate(&mempool, GetRandHash())); + Chainstate& c2 = WITH_LOCK(cs_main, return manager.InitializeChainstate(&mempool, GetRandHash())); chainstates.push_back(&c2); c2.InitCoinsDB( /*cache_size_bytes=*/1 << 23, /*in_memory=*/true, /*should_wipe=*/false); @@ -193,7 +193,7 @@ BOOST_FIXTURE_TEST_CASE(chainstatemanager_activate_snapshot, TestChain100Setup) // Should not load malleated snapshots BOOST_REQUIRE(!CreateAndActivateUTXOSnapshot( - m_node, m_path_root, [](CAutoFile& auto_infile, SnapshotMetadata& metadata) { + m_node, m_path_root, [](AutoFile& auto_infile, SnapshotMetadata& metadata) { // A UTXO is missing but count is correct metadata.m_coins_count -= 1; @@ -204,22 +204,22 @@ BOOST_FIXTURE_TEST_CASE(chainstatemanager_activate_snapshot, TestChain100Setup) auto_infile >> coin; })); BOOST_REQUIRE(!CreateAndActivateUTXOSnapshot( - m_node, m_path_root, [](CAutoFile& auto_infile, SnapshotMetadata& metadata) { + m_node, m_path_root, [](AutoFile& auto_infile, SnapshotMetadata& metadata) { // Coins count is larger than coins in file metadata.m_coins_count += 1; })); BOOST_REQUIRE(!CreateAndActivateUTXOSnapshot( - m_node, m_path_root, [](CAutoFile& auto_infile, SnapshotMetadata& metadata) { + m_node, m_path_root, [](AutoFile& auto_infile, SnapshotMetadata& metadata) { // Coins count is smaller than coins in file metadata.m_coins_count -= 1; })); BOOST_REQUIRE(!CreateAndActivateUTXOSnapshot( - m_node, m_path_root, [](CAutoFile& auto_infile, SnapshotMetadata& metadata) { + m_node, m_path_root, [](AutoFile& auto_infile, SnapshotMetadata& metadata) { // Wrong hash metadata.m_base_blockhash = uint256::ZERO; })); BOOST_REQUIRE(!CreateAndActivateUTXOSnapshot( - m_node, m_path_root, [](CAutoFile& auto_infile, SnapshotMetadata& metadata) { + m_node, m_path_root, [](AutoFile& auto_infile, SnapshotMetadata& metadata) { // Wrong hash metadata.m_base_blockhash = uint256::ONE; })); @@ -236,7 +236,7 @@ BOOST_FIXTURE_TEST_CASE(chainstatemanager_activate_snapshot, TestChain100Setup) BOOST_CHECK(WITH_LOCK(::cs_main, return !chainman.ActiveChain().Genesis()->IsAssumedValid())); const AssumeutxoData& au_data = *ExpectedAssumeutxo(snapshot_height, ::Params()); - const CBlockIndex* tip = chainman.ActiveTip(); + const CBlockIndex* tip = WITH_LOCK(chainman.GetMutex(), return chainman.ActiveTip()); BOOST_CHECK_EQUAL(tip->nChainTx, au_data.nChainTx); @@ -250,7 +250,7 @@ BOOST_FIXTURE_TEST_CASE(chainstatemanager_activate_snapshot, TestChain100Setup) LOCK(::cs_main); int chains_tested{0}; - for (CChainState* chainstate : chainman.GetAll()) { + for (Chainstate* chainstate : chainman.GetAll()) { BOOST_TEST_MESSAGE("Checking coins in " << chainstate->ToString()); CCoinsViewCache& coinscache = chainstate->CoinsTip(); @@ -283,7 +283,7 @@ BOOST_FIXTURE_TEST_CASE(chainstatemanager_activate_snapshot, TestChain100Setup) size_t coins_in_background{0}; size_t coins_missing_from_background{0}; - for (CChainState* chainstate : chainman.GetAll()) { + for (Chainstate* chainstate : chainman.GetAll()) { BOOST_TEST_MESSAGE("Checking coins in " << chainstate->ToString()); CCoinsViewCache& coinscache = chainstate->CoinsTip(); bool is_background = chainstate != &chainman.ActiveChainstate(); @@ -314,7 +314,7 @@ BOOST_FIXTURE_TEST_CASE(chainstatemanager_activate_snapshot, TestChain100Setup) //! Test LoadBlockIndex behavior when multiple chainstates are in use. //! -//! - First, verfiy that setBlockIndexCandidates is as expected when using a single, +//! - First, verify that setBlockIndexCandidates is as expected when using a single, //! fully-validating chainstate. //! //! - Then mark a region of the chain BLOCK_ASSUMED_VALID and introduce a second chainstate @@ -326,7 +326,7 @@ BOOST_FIXTURE_TEST_CASE(chainstatemanager_loadblockindex, TestChain100Setup) { ChainstateManager& chainman = *Assert(m_node.chainman); CTxMemPool& mempool = *m_node.mempool; - CChainState& cs1 = chainman.ActiveChainstate(); + Chainstate& cs1 = chainman.ActiveChainstate(); int num_indexes{0}; int num_assumed_valid{0}; @@ -335,10 +335,10 @@ BOOST_FIXTURE_TEST_CASE(chainstatemanager_loadblockindex, TestChain100Setup) const int assumed_valid_start_idx = last_assumed_valid_idx - expected_assumed_valid; CBlockIndex* validated_tip{nullptr}; - CBlockIndex* assumed_tip{chainman.ActiveChain().Tip()}; + CBlockIndex* assumed_tip{WITH_LOCK(chainman.GetMutex(), return chainman.ActiveChain().Tip())}; auto reload_all_block_indexes = [&]() { - for (CChainState* cs : chainman.GetAll()) { + for (Chainstate* cs : chainman.GetAll()) { LOCK(::cs_main); cs->UnloadBlockIndex(); BOOST_CHECK(cs->setBlockIndexCandidates.empty()); @@ -373,7 +373,7 @@ BOOST_FIXTURE_TEST_CASE(chainstatemanager_loadblockindex, TestChain100Setup) BOOST_CHECK_EQUAL(expected_assumed_valid, num_assumed_valid); - CChainState& cs2 = WITH_LOCK(::cs_main, + Chainstate& cs2 = WITH_LOCK(::cs_main, return chainman.InitializeChainstate(&mempool, GetRandHash())); reload_all_block_indexes(); diff --git a/src/test/validation_flush_tests.cpp b/src/test/validation_flush_tests.cpp index a34895d4ae..c06e6c8d3b 100644 --- a/src/test/validation_flush_tests.cpp +++ b/src/test/validation_flush_tests.cpp @@ -4,27 +4,20 @@ // #include <sync.h> #include <test/util/setup_common.h> -#include <txmempool.h> #include <validation.h> #include <boost/test/unit_test.hpp> -using node::BlockManager; - -BOOST_FIXTURE_TEST_SUITE(validation_flush_tests, ChainTestingSetup) +BOOST_FIXTURE_TEST_SUITE(validation_flush_tests, TestingSetup) //! Test utilities for detecting when we need to flush the coins cache based //! on estimated memory usage. //! -//! @sa CChainState::GetCoinsCacheSizeState() +//! @sa Chainstate::GetCoinsCacheSizeState() //! BOOST_AUTO_TEST_CASE(getcoinscachesizestate) { - CTxMemPool mempool; - BlockManager blockman{}; - CChainState chainstate{&mempool, blockman, *Assert(m_node.chainman)}; - chainstate.InitCoinsDB(/*cache_size_bytes=*/1 << 10, /*in_memory=*/true, /*should_wipe=*/false); - WITH_LOCK(::cs_main, chainstate.InitCoinsCache(1 << 10)); + Chainstate& chainstate{m_node.chainman->ActiveChainstate()}; constexpr bool is_64_bit = sizeof(void*) == 8; diff --git a/src/threadinterrupt.cpp b/src/threadinterrupt.cpp index 340106ed99..e28b447c1d 100644 --- a/src/threadinterrupt.cpp +++ b/src/threadinterrupt.cpp @@ -28,18 +28,8 @@ void CThreadInterrupt::operator()() cond.notify_all(); } -bool CThreadInterrupt::sleep_for(std::chrono::milliseconds rel_time) +bool CThreadInterrupt::sleep_for(Clock::duration rel_time) { WAIT_LOCK(mut, lock); return !cond.wait_for(lock, rel_time, [this]() { return flag.load(std::memory_order_acquire); }); } - -bool CThreadInterrupt::sleep_for(std::chrono::seconds rel_time) -{ - return sleep_for(std::chrono::duration_cast<std::chrono::milliseconds>(rel_time)); -} - -bool CThreadInterrupt::sleep_for(std::chrono::minutes rel_time) -{ - return sleep_for(std::chrono::duration_cast<std::chrono::milliseconds>(rel_time)); -} diff --git a/src/threadinterrupt.h b/src/threadinterrupt.h index 992016b4f6..979bc2ee3e 100644 --- a/src/threadinterrupt.h +++ b/src/threadinterrupt.h @@ -6,6 +6,7 @@ #define BITCOIN_THREADINTERRUPT_H #include <sync.h> +#include <threadsafety.h> #include <atomic> #include <chrono> @@ -19,13 +20,12 @@ class CThreadInterrupt { public: + using Clock = std::chrono::steady_clock; CThreadInterrupt(); explicit operator bool() const; void operator()() EXCLUSIVE_LOCKS_REQUIRED(!mut); void reset(); - bool sleep_for(std::chrono::milliseconds rel_time) EXCLUSIVE_LOCKS_REQUIRED(!mut); - bool sleep_for(std::chrono::seconds rel_time) EXCLUSIVE_LOCKS_REQUIRED(!mut); - bool sleep_for(std::chrono::minutes rel_time) EXCLUSIVE_LOCKS_REQUIRED(!mut); + bool sleep_for(Clock::duration rel_time) EXCLUSIVE_LOCKS_REQUIRED(!mut); private: std::condition_variable cond; diff --git a/src/timedata.cpp b/src/timedata.cpp index 7faf22aba0..fe9a5fbed7 100644 --- a/src/timedata.cpp +++ b/src/timedata.cpp @@ -9,14 +9,14 @@ #include <timedata.h> #include <netaddress.h> -#include <node/ui_interface.h> +#include <node/interface_ui.h> #include <sync.h> #include <tinyformat.h> #include <util/system.h> #include <util/translation.h> #include <warnings.h> -static Mutex g_timeoffset_mutex; +static GlobalMutex g_timeoffset_mutex; static int64_t nTimeOffset GUARDED_BY(g_timeoffset_mutex) = 0; /** @@ -32,9 +32,9 @@ int64_t GetTimeOffset() return nTimeOffset; } -int64_t GetAdjustedTime() +NodeClock::time_point GetAdjustedTime() { - return GetTime() + GetTimeOffset(); + return NodeClock::now() + std::chrono::seconds{GetTimeOffset()}; } #define BITCOIN_TIMEDATA_MAX_SAMPLES 200 diff --git a/src/timedata.h b/src/timedata.h index 2f039d5465..669a571f47 100644 --- a/src/timedata.h +++ b/src/timedata.h @@ -5,9 +5,12 @@ #ifndef BITCOIN_TIMEDATA_H #define BITCOIN_TIMEDATA_H +#include <util/time.h> + #include <algorithm> -#include <assert.h> -#include <stdint.h> +#include <cassert> +#include <chrono> +#include <cstdint> #include <vector> static const int64_t DEFAULT_MAX_TIME_ADJUSTMENT = 70 * 60; @@ -72,7 +75,7 @@ public: /** Functions to keep track of adjusted P2P time */ int64_t GetTimeOffset(); -int64_t GetAdjustedTime(); +NodeClock::time_point GetAdjustedTime(); void AddTimeData(const CNetAddr& ip, int64_t nTime); /** diff --git a/src/torcontrol.cpp b/src/torcontrol.cpp index 82b8529d76..3a21a79a34 100644 --- a/src/torcontrol.cpp +++ b/src/torcontrol.cpp @@ -7,7 +7,7 @@ #include <chainparams.h> #include <chainparamsbase.h> -#include <compat.h> +#include <compat/compat.h> #include <crypto/hmac_sha256.h> #include <net.h> #include <netaddress.h> @@ -421,7 +421,7 @@ void TorController::add_onion_cb(TorControlConnection& _conn, const TorControlRe return; } service = LookupNumeric(std::string(service_id+".onion"), Params().GetDefaultPort()); - LogPrintf("tor: Got service ID %s, advertising service %s\n", service_id, service.ToString()); + LogPrintfCategory(BCLog::TOR, "Got service ID %s, advertising service %s\n", service_id, service.ToString()); if (WriteBinaryFile(GetPrivateKeyFile(), private_key)) { LogPrint(BCLog::TOR, "Cached service private key to %s\n", fs::PathToString(GetPrivateKeyFile())); } else { diff --git a/src/txdb.cpp b/src/txdb.cpp index a0939873ad..bad3bb80a9 100644 --- a/src/txdb.cpp +++ b/src/txdb.cpp @@ -211,7 +211,6 @@ public: bool GetKey(COutPoint &key) const override; bool GetValue(Coin &coin) const override; - unsigned int GetValueSize() const override; bool Valid() const override; void Next() override; @@ -257,11 +256,6 @@ bool CCoinsViewDBCursor::GetValue(Coin &coin) const return pcursor->GetValue(coin); } -unsigned int CCoinsViewDBCursor::GetValueSize() const -{ - return pcursor->GetValueSize(); -} - bool CCoinsViewDBCursor::Valid() const { return keyTmp.first == DB_COIN; @@ -316,7 +310,7 @@ bool CBlockTreeDB::LoadBlockIndexGuts(const Consensus::Params& consensusParams, CDiskBlockIndex diskindex; if (pcursor->GetValue(diskindex)) { // Construct block index object - CBlockIndex* pindexNew = insertBlockIndex(diskindex.GetBlockHash()); + CBlockIndex* pindexNew = insertBlockIndex(diskindex.ConstructBlockHash()); pindexNew->pprev = insertBlockIndex(diskindex.hashPrev); pindexNew->nHeight = diskindex.nHeight; pindexNew->nFile = diskindex.nFile; diff --git a/src/txdb.h b/src/txdb.h index faa543b412..a04596f7bb 100644 --- a/src/txdb.h +++ b/src/txdb.h @@ -8,6 +8,7 @@ #include <coins.h> #include <dbwrapper.h> +#include <sync.h> #include <memory> #include <optional> diff --git a/src/txmempool.cpp b/src/txmempool.cpp index 65c8b4ea60..84ed2e9ef5 100644 --- a/src/txmempool.cpp +++ b/src/txmempool.cpp @@ -14,7 +14,9 @@ #include <policy/policy.h> #include <policy/settings.h> #include <reverse_iterator.h> +#include <util/check.h> #include <util/moneystr.h> +#include <util/overflow.h> #include <util/system.h> #include <util/time.h> #include <validationinterface.h> @@ -22,38 +24,6 @@ #include <cmath> #include <optional> -// Helpers for modifying CTxMemPool::mapTx, which is a boost multi_index. -struct update_descendant_state -{ - update_descendant_state(int64_t _modifySize, CAmount _modifyFee, int64_t _modifyCount) : - modifySize(_modifySize), modifyFee(_modifyFee), modifyCount(_modifyCount) - {} - - void operator() (CTxMemPoolEntry &e) - { e.UpdateDescendantState(modifySize, modifyFee, modifyCount); } - - private: - int64_t modifySize; - CAmount modifyFee; - int64_t modifyCount; -}; - -struct update_ancestor_state -{ - update_ancestor_state(int64_t _modifySize, CAmount _modifyFee, int64_t _modifyCount, int64_t _modifySigOpsCost) : - modifySize(_modifySize), modifyFee(_modifyFee), modifyCount(_modifyCount), modifySigOpsCost(_modifySigOpsCost) - {} - - void operator() (CTxMemPoolEntry &e) - { e.UpdateAncestorState(modifySize, modifyFee, modifyCount, modifySigOpsCost); } - - private: - int64_t modifySize; - CAmount modifyFee; - int64_t modifyCount; - int64_t modifySigOpsCost; -}; - bool TestLockPointValidity(CChain& active_chain, const LockPoints& lp) { AssertLockHeld(cs_main); @@ -82,6 +52,7 @@ CTxMemPoolEntry::CTxMemPoolEntry(const CTransactionRef& tx, CAmount fee, entryHeight{entry_height}, spendsCoinbase{spends_coinbase}, sigOpCost{sigops_cost}, + m_modified_fee{nFee}, lockPoints{lp}, nSizeWithDescendants{GetTxSize()}, nModFeesWithDescendants{nFee}, @@ -89,11 +60,11 @@ CTxMemPoolEntry::CTxMemPoolEntry(const CTransactionRef& tx, CAmount fee, nModFeesWithAncestors{nFee}, nSigOpCostWithAncestors{sigOpCost} {} -void CTxMemPoolEntry::UpdateFeeDelta(CAmount newFeeDelta) +void CTxMemPoolEntry::UpdateModifiedFee(CAmount fee_diff) { - nModFeesWithDescendants += newFeeDelta - feeDelta; - nModFeesWithAncestors += newFeeDelta - feeDelta; - feeDelta = newFeeDelta; + nModFeesWithDescendants = SaturatingAdd(nModFeesWithDescendants, fee_diff); + nModFeesWithAncestors = SaturatingAdd(nModFeesWithAncestors, fee_diff); + m_modified_fee = SaturatingAdd(m_modified_fee, fee_diff); } void CTxMemPoolEntry::UpdateLockPoints(const LockPoints& lp) @@ -103,12 +74,11 @@ void CTxMemPoolEntry::UpdateLockPoints(const LockPoints& lp) size_t CTxMemPoolEntry::GetTxSize() const { - return GetVirtualTransactionSize(nTxWeight, sigOpCost); + return GetVirtualTransactionSize(nTxWeight, sigOpCost, ::nBytesPerSigOp); } void CTxMemPool::UpdateForDescendants(txiter updateIt, cacheMap& cachedDescendants, - const std::set<uint256>& setExclude, std::set<uint256>& descendants_to_remove, - uint64_t ancestor_size_limit, uint64_t ancestor_count_limit) + const std::set<uint256>& setExclude, std::set<uint256>& descendants_to_remove) { CTxMemPoolEntry::Children stageEntries, descendants; stageEntries = updateIt->GetMemPoolChildrenConst(); @@ -144,19 +114,21 @@ void CTxMemPool::UpdateForDescendants(txiter updateIt, cacheMap& cachedDescendan modifyCount++; cachedDescendants[updateIt].insert(mapTx.iterator_to(descendant)); // Update ancestor state for each descendant - mapTx.modify(mapTx.iterator_to(descendant), update_ancestor_state(updateIt->GetTxSize(), updateIt->GetModifiedFee(), 1, updateIt->GetSigOpCost())); + mapTx.modify(mapTx.iterator_to(descendant), [=](CTxMemPoolEntry& e) { + e.UpdateAncestorState(updateIt->GetTxSize(), updateIt->GetModifiedFee(), 1, updateIt->GetSigOpCost()); + }); // Don't directly remove the transaction here -- doing so would // invalidate iterators in cachedDescendants. Mark it for removal // by inserting into descendants_to_remove. - if (descendant.GetCountWithAncestors() > ancestor_count_limit || descendant.GetSizeWithAncestors() > ancestor_size_limit) { + if (descendant.GetCountWithAncestors() > uint64_t(m_limits.ancestor_count) || descendant.GetSizeWithAncestors() > uint64_t(m_limits.ancestor_size_vbytes)) { descendants_to_remove.insert(descendant.GetTx().GetHash()); } } } - mapTx.modify(updateIt, update_descendant_state(modifySize, modifyFee, modifyCount)); + mapTx.modify(updateIt, [=](CTxMemPoolEntry& e) { e.UpdateDescendantState(modifySize, modifyFee, modifyCount); }); } -void CTxMemPool::UpdateTransactionsFromBlock(const std::vector<uint256> &vHashesToUpdate, uint64_t ancestor_size_limit, uint64_t ancestor_count_limit) +void CTxMemPool::UpdateTransactionsFromBlock(const std::vector<uint256>& vHashesToUpdate) { AssertLockHeld(cs); // For each entry in vHashesToUpdate, store the set of in-mempool, but not @@ -173,7 +145,7 @@ void CTxMemPool::UpdateTransactionsFromBlock(const std::vector<uint256> &vHashes // Iterate in reverse, so that whenever we are looking at a transaction // we are sure that all in-mempool descendants have already been processed. // This maximizes the benefit of the descendant cache and guarantees that - // CTxMemPool::m_children will be updated, an assumption made in + // CTxMemPoolEntry::m_children will be updated, an assumption made in // UpdateForDescendants. for (const uint256 &hash : reverse_iterate(vHashesToUpdate)) { // calculate children from mapNextTx @@ -182,7 +154,7 @@ void CTxMemPool::UpdateTransactionsFromBlock(const std::vector<uint256> &vHashes continue; } auto iter = mapNextTx.lower_bound(COutPoint(hash, 0)); - // First calculate the children, and update CTxMemPool::m_children to + // First calculate the children, and update CTxMemPoolEntry::m_children to // include them, and update their CTxMemPoolEntry::m_parents to include this tx. // we cache the in-mempool children to avoid duplicate updates { @@ -199,7 +171,7 @@ void CTxMemPool::UpdateTransactionsFromBlock(const std::vector<uint256> &vHashes } } } // release epoch guard for UpdateForDescendants - UpdateForDescendants(it, mapMemPoolDescendantsToUpdate, setAlreadyIncluded, descendants_to_remove, ancestor_size_limit, ancestor_count_limit); + UpdateForDescendants(it, mapMemPoolDescendantsToUpdate, setAlreadyIncluded, descendants_to_remove); } for (const auto& txid : descendants_to_remove) { @@ -215,10 +187,7 @@ bool CTxMemPool::CalculateAncestorsAndCheckLimits(size_t entry_size, size_t entry_count, setEntries& setAncestors, CTxMemPoolEntry::Parents& staged_ancestors, - uint64_t limitAncestorCount, - uint64_t limitAncestorSize, - uint64_t limitDescendantCount, - uint64_t limitDescendantSize, + const Limits& limits, std::string &errString) const { size_t totalSizeWithAncestors = entry_size; @@ -231,14 +200,14 @@ bool CTxMemPool::CalculateAncestorsAndCheckLimits(size_t entry_size, staged_ancestors.erase(stage); totalSizeWithAncestors += stageit->GetTxSize(); - if (stageit->GetSizeWithDescendants() + entry_size > limitDescendantSize) { - errString = strprintf("exceeds descendant size limit for tx %s [limit: %u]", stageit->GetTx().GetHash().ToString(), limitDescendantSize); + if (stageit->GetSizeWithDescendants() + entry_size > static_cast<uint64_t>(limits.descendant_size_vbytes)) { + errString = strprintf("exceeds descendant size limit for tx %s [limit: %u]", stageit->GetTx().GetHash().ToString(), limits.descendant_size_vbytes); return false; - } else if (stageit->GetCountWithDescendants() + entry_count > limitDescendantCount) { - errString = strprintf("too many descendants for tx %s [limit: %u]", stageit->GetTx().GetHash().ToString(), limitDescendantCount); + } else if (stageit->GetCountWithDescendants() + entry_count > static_cast<uint64_t>(limits.descendant_count)) { + errString = strprintf("too many descendants for tx %s [limit: %u]", stageit->GetTx().GetHash().ToString(), limits.descendant_count); return false; - } else if (totalSizeWithAncestors > limitAncestorSize) { - errString = strprintf("exceeds ancestor size limit [limit: %u]", limitAncestorSize); + } else if (totalSizeWithAncestors > static_cast<uint64_t>(limits.ancestor_size_vbytes)) { + errString = strprintf("exceeds ancestor size limit [limit: %u]", limits.ancestor_size_vbytes); return false; } @@ -250,8 +219,8 @@ bool CTxMemPool::CalculateAncestorsAndCheckLimits(size_t entry_size, if (setAncestors.count(parent_it) == 0) { staged_ancestors.insert(parent); } - if (staged_ancestors.size() + setAncestors.size() + entry_count > limitAncestorCount) { - errString = strprintf("too many unconfirmed ancestors [limit: %u]", limitAncestorCount); + if (staged_ancestors.size() + setAncestors.size() + entry_count > static_cast<uint64_t>(limits.ancestor_count)) { + errString = strprintf("too many unconfirmed ancestors [limit: %u]", limits.ancestor_count); return false; } } @@ -261,10 +230,7 @@ bool CTxMemPool::CalculateAncestorsAndCheckLimits(size_t entry_size, } bool CTxMemPool::CheckPackageLimits(const Package& package, - uint64_t limitAncestorCount, - uint64_t limitAncestorSize, - uint64_t limitDescendantCount, - uint64_t limitDescendantSize, + const Limits& limits, std::string &errString) const { CTxMemPoolEntry::Parents staged_ancestors; @@ -275,8 +241,8 @@ bool CTxMemPool::CheckPackageLimits(const Package& package, std::optional<txiter> piter = GetIter(input.prevout.hash); if (piter) { staged_ancestors.insert(**piter); - if (staged_ancestors.size() + package.size() > limitAncestorCount) { - errString = strprintf("too many unconfirmed parents [limit: %u]", limitAncestorCount); + if (staged_ancestors.size() + package.size() > static_cast<uint64_t>(limits.ancestor_count)) { + errString = strprintf("too many unconfirmed parents [limit: %u]", limits.ancestor_count); return false; } } @@ -288,8 +254,7 @@ bool CTxMemPool::CheckPackageLimits(const Package& package, setEntries setAncestors; const auto ret = CalculateAncestorsAndCheckLimits(total_size, package.size(), setAncestors, staged_ancestors, - limitAncestorCount, limitAncestorSize, - limitDescendantCount, limitDescendantSize, errString); + limits, errString); // It's possible to overestimate the ancestor/descendant totals. if (!ret) errString.insert(0, "possibly "); return ret; @@ -297,10 +262,7 @@ bool CTxMemPool::CheckPackageLimits(const Package& package, bool CTxMemPool::CalculateMemPoolAncestors(const CTxMemPoolEntry &entry, setEntries &setAncestors, - uint64_t limitAncestorCount, - uint64_t limitAncestorSize, - uint64_t limitDescendantCount, - uint64_t limitDescendantSize, + const Limits& limits, std::string &errString, bool fSearchForParents /* = true */) const { @@ -315,8 +277,8 @@ bool CTxMemPool::CalculateMemPoolAncestors(const CTxMemPoolEntry &entry, std::optional<txiter> piter = GetIter(tx.vin[i].prevout.hash); if (piter) { staged_ancestors.insert(**piter); - if (staged_ancestors.size() + 1 > limitAncestorCount) { - errString = strprintf("too many unconfirmed parents [limit: %u]", limitAncestorCount); + if (staged_ancestors.size() + 1 > static_cast<uint64_t>(limits.ancestor_count)) { + errString = strprintf("too many unconfirmed parents [limit: %u]", limits.ancestor_count); return false; } } @@ -330,8 +292,7 @@ bool CTxMemPool::CalculateMemPoolAncestors(const CTxMemPoolEntry &entry, return CalculateAncestorsAndCheckLimits(entry.GetTxSize(), /*entry_count=*/1, setAncestors, staged_ancestors, - limitAncestorCount, limitAncestorSize, - limitDescendantCount, limitDescendantSize, errString); + limits, errString); } void CTxMemPool::UpdateAncestorsOf(bool add, txiter it, setEntries &setAncestors) @@ -345,7 +306,7 @@ void CTxMemPool::UpdateAncestorsOf(bool add, txiter it, setEntries &setAncestors const int64_t updateSize = updateCount * it->GetTxSize(); const CAmount updateFee = updateCount * it->GetModifiedFee(); for (txiter ancestorIt : setAncestors) { - mapTx.modify(ancestorIt, update_descendant_state(updateSize, updateFee, updateCount)); + mapTx.modify(ancestorIt, [=](CTxMemPoolEntry& e) { e.UpdateDescendantState(updateSize, updateFee, updateCount); }); } } @@ -360,7 +321,7 @@ void CTxMemPool::UpdateEntryForAncestors(txiter it, const setEntries &setAncesto updateFee += ancestorIt->GetModifiedFee(); updateSigOpsCost += ancestorIt->GetSigOpCost(); } - mapTx.modify(it, update_ancestor_state(updateSize, updateFee, updateCount, updateSigOpsCost)); + mapTx.modify(it, [=](CTxMemPoolEntry& e){ e.UpdateAncestorState(updateSize, updateFee, updateCount, updateSigOpsCost); }); } void CTxMemPool::UpdateChildrenForRemoval(txiter it) @@ -375,7 +336,6 @@ void CTxMemPool::UpdateForRemoveFromMempool(const setEntries &entriesToRemove, b { // For each entry, walk back all ancestors and decrement size associated with this // transaction - const uint64_t nNoLimit = std::numeric_limits<uint64_t>::max(); if (updateDescendants) { // updateDescendants should be true whenever we're not recursively // removing a tx and all its descendants, eg when a transaction is @@ -391,7 +351,7 @@ void CTxMemPool::UpdateForRemoveFromMempool(const setEntries &entriesToRemove, b CAmount modifyFee = -removeIt->GetModifiedFee(); int modifySigOps = -removeIt->GetSigOpCost(); for (txiter dit : setDescendants) { - mapTx.modify(dit, update_ancestor_state(modifySize, modifyFee, -1, modifySigOps)); + mapTx.modify(dit, [=](CTxMemPoolEntry& e){ e.UpdateAncestorState(modifySize, modifyFee, -1, modifySigOps); }); } } } @@ -418,7 +378,7 @@ void CTxMemPool::UpdateForRemoveFromMempool(const setEntries &entriesToRemove, b // mempool parents we'd calculate by searching, and it's important that // we use the cached notion of ancestor transactions as the set of // things to update for removal. - CalculateMemPoolAncestors(entry, setAncestors, nNoLimit, nNoLimit, nNoLimit, nNoLimit, dummy, false); + CalculateMemPoolAncestors(entry, setAncestors, Limits::NoLimits(), dummy, false); // Note that UpdateAncestorsOf severs the child links that point to // removeIt in the entries for the parents of removeIt. UpdateAncestorsOf(false, removeIt, setAncestors); @@ -435,7 +395,7 @@ void CTxMemPoolEntry::UpdateDescendantState(int64_t modifySize, CAmount modifyFe { nSizeWithDescendants += modifySize; assert(int64_t(nSizeWithDescendants) > 0); - nModFeesWithDescendants += modifyFee; + nModFeesWithDescendants = SaturatingAdd(nModFeesWithDescendants, modifyFee); nCountWithDescendants += modifyCount; assert(int64_t(nCountWithDescendants) > 0); } @@ -444,15 +404,26 @@ void CTxMemPoolEntry::UpdateAncestorState(int64_t modifySize, CAmount modifyFee, { nSizeWithAncestors += modifySize; assert(int64_t(nSizeWithAncestors) > 0); - nModFeesWithAncestors += modifyFee; + nModFeesWithAncestors = SaturatingAdd(nModFeesWithAncestors, modifyFee); nCountWithAncestors += modifyCount; assert(int64_t(nCountWithAncestors) > 0); nSigOpCostWithAncestors += modifySigOps; assert(int(nSigOpCostWithAncestors) >= 0); } -CTxMemPool::CTxMemPool(CBlockPolicyEstimator* estimator, int check_ratio) - : m_check_ratio(check_ratio), minerPolicyEstimator(estimator) +CTxMemPool::CTxMemPool(const Options& opts) + : m_check_ratio{opts.check_ratio}, + minerPolicyEstimator{opts.estimator}, + m_max_size_bytes{opts.max_size_bytes}, + m_expiry{opts.expiry}, + m_incremental_relay_feerate{opts.incremental_relay_feerate}, + m_min_relay_feerate{opts.min_relay_feerate}, + m_dust_relay_feerate{opts.dust_relay_feerate}, + m_permit_bare_multisig{opts.permit_bare_multisig}, + m_max_datacarrier_bytes{opts.max_datacarrier_bytes}, + m_require_standard{opts.require_standard}, + m_full_rbf{opts.full_rbf}, + m_limits{opts.limits} { _clear(); //lock free clear } @@ -483,8 +454,10 @@ void CTxMemPool::addUnchecked(const CTxMemPoolEntry &entry, setEntries &setAnces // Update transaction for any feeDelta created by PrioritiseTransaction CAmount delta{0}; ApplyDelta(entry.GetTx().GetHash(), delta); + // The following call to UpdateModifiedFee assumes no previous fee modifications + Assume(entry.GetFee() == entry.GetModifiedFee()); if (delta) { - mapTx.modify(newit, [&delta](CTxMemPoolEntry& e) { e.UpdateFeeDelta(delta); }); + mapTx.modify(newit, [&delta](CTxMemPoolEntry& e) { e.UpdateModifiedFee(delta); }); } // Update cachedInnerUsage to include contained transaction's usage. @@ -759,9 +732,8 @@ void CTxMemPool::check(const CCoinsViewCache& active_coins_tip, int64_t spendhei assert(std::equal(setParentCheck.begin(), setParentCheck.end(), it->GetMemPoolParentsConst().begin(), comp)); // Verify ancestor state is correct. setEntries setAncestors; - uint64_t nNoLimit = std::numeric_limits<uint64_t>::max(); std::string dummy; - CalculateMemPoolAncestors(*it, setAncestors, nNoLimit, nNoLimit, nNoLimit, nNoLimit, dummy); + CalculateMemPoolAncestors(*it, setAncestors, Limits::NoLimits(), dummy); uint64_t nCountCheck = setAncestors.size() + 1; uint64_t nSizeCheck = it->GetTxSize(); CAmount nFeesCheck = it->GetModifiedFee(); @@ -917,24 +889,23 @@ void CTxMemPool::PrioritiseTransaction(const uint256& hash, const CAmount& nFeeD { LOCK(cs); CAmount &delta = mapDeltas[hash]; - delta += nFeeDelta; + delta = SaturatingAdd(delta, nFeeDelta); txiter it = mapTx.find(hash); if (it != mapTx.end()) { - mapTx.modify(it, [&delta](CTxMemPoolEntry& e) { e.UpdateFeeDelta(delta); }); + mapTx.modify(it, [&nFeeDelta](CTxMemPoolEntry& e) { e.UpdateModifiedFee(nFeeDelta); }); // Now update all ancestors' modified fees with descendants setEntries setAncestors; - uint64_t nNoLimit = std::numeric_limits<uint64_t>::max(); std::string dummy; - CalculateMemPoolAncestors(*it, setAncestors, nNoLimit, nNoLimit, nNoLimit, nNoLimit, dummy, false); + CalculateMemPoolAncestors(*it, setAncestors, Limits::NoLimits(), dummy, false); for (txiter ancestorIt : setAncestors) { - mapTx.modify(ancestorIt, update_descendant_state(0, nFeeDelta, 0)); + mapTx.modify(ancestorIt, [=](CTxMemPoolEntry& e){ e.UpdateDescendantState(0, nFeeDelta, 0);}); } // Now update all descendants' modified fees with ancestors setEntries setDescendants; CalculateDescendants(it, setDescendants); setDescendants.erase(it); for (txiter descendantIt : setDescendants) { - mapTx.modify(descendantIt, update_ancestor_state(0, nFeeDelta, 0, 0)); + mapTx.modify(descendantIt, [=](CTxMemPoolEntry& e){ e.UpdateAncestorState(0, nFeeDelta, 0, 0); }); } ++nTransactionsUpdated; } @@ -1064,9 +1035,8 @@ int CTxMemPool::Expire(std::chrono::seconds time) void CTxMemPool::addUnchecked(const CTxMemPoolEntry &entry, bool validFeeEstimate) { setEntries setAncestors; - uint64_t nNoLimit = std::numeric_limits<uint64_t>::max(); std::string dummy; - CalculateMemPoolAncestors(entry, setAncestors, nNoLimit, nNoLimit, nNoLimit, nNoLimit, dummy); + CalculateMemPoolAncestors(entry, setAncestors, Limits::NoLimits(), dummy); return addUnchecked(entry, setAncestors, validFeeEstimate); } @@ -1108,12 +1078,12 @@ CFeeRate CTxMemPool::GetMinFee(size_t sizelimit) const { rollingMinimumFeeRate = rollingMinimumFeeRate / pow(2.0, (time - lastRollingFeeUpdate) / halflife); lastRollingFeeUpdate = time; - if (rollingMinimumFeeRate < (double)incrementalRelayFee.GetFeePerK() / 2) { + if (rollingMinimumFeeRate < (double)m_incremental_relay_feerate.GetFeePerK() / 2) { rollingMinimumFeeRate = 0; return CFeeRate(0); } } - return std::max(CFeeRate(llround(rollingMinimumFeeRate)), incrementalRelayFee); + return std::max(CFeeRate(llround(rollingMinimumFeeRate)), m_incremental_relay_feerate); } void CTxMemPool::trackPackageRemoved(const CFeeRate& rate) { @@ -1137,7 +1107,7 @@ void CTxMemPool::TrimToSize(size_t sizelimit, std::vector<COutPoint>* pvNoSpends // to have 0 fee). This way, we don't allow txn to enter mempool with feerate // equal to txn which were removed with no block in between. CFeeRate removed(it->GetModFeesWithDescendants(), it->GetSizeWithDescendants()); - removed += incrementalRelayFee; + removed += m_incremental_relay_feerate; trackPackageRemoved(removed); maxFeeRateRemoved = std::max(maxFeeRateRemoved, removed); @@ -1201,14 +1171,14 @@ void CTxMemPool::GetTransactionAncestry(const uint256& txid, size_t& ancestors, } } -bool CTxMemPool::IsLoaded() const +bool CTxMemPool::GetLoadTried() const { LOCK(cs); - return m_is_loaded; + return m_load_tried; } -void CTxMemPool::SetIsLoaded(bool loaded) +void CTxMemPool::SetLoadTried(bool load_tried) { LOCK(cs); - m_is_loaded = loaded; + m_load_tried = load_tried; } diff --git a/src/txmempool.h b/src/txmempool.h index f5d5abc62e..4afaac0506 100644 --- a/src/txmempool.h +++ b/src/txmempool.h @@ -14,6 +14,9 @@ #include <utility> #include <vector> +#include <kernel/mempool_limits.h> +#include <kernel/mempool_options.h> + #include <coins.h> #include <consensus/amount.h> #include <indirectmap.h> @@ -32,7 +35,7 @@ class CBlockIndex; class CChain; -class CChainState; +class Chainstate; extern RecursiveMutex cs_main; /** Fake height value used in Coin to signify they are only in the memory pool (since 0.8) */ @@ -101,7 +104,7 @@ private: const unsigned int entryHeight; //!< Chain height when entering the mempool const bool spendsCoinbase; //!< keep track of transactions that spend a coinbase const int64_t sigOpCost; //!< Total sigop cost - CAmount feeDelta{0}; //!< Used for determining the priority of the transaction for mining in a block + CAmount m_modified_fee; //!< Used for determining the priority of the transaction for mining in a block LockPoints lockPoints; //!< Track the height and time at which tx was final // Information about descendants of this transaction that are in the @@ -131,7 +134,7 @@ public: std::chrono::seconds GetTime() const { return std::chrono::seconds{nTime}; } unsigned int GetHeight() const { return entryHeight; } int64_t GetSigOpCost() const { return sigOpCost; } - CAmount GetModifiedFee() const { return nFee + feeDelta; } + CAmount GetModifiedFee() const { return m_modified_fee; } size_t DynamicMemoryUsage() const { return nUsageSize; } const LockPoints& GetLockPoints() const { return lockPoints; } @@ -139,9 +142,8 @@ public: void UpdateDescendantState(int64_t modifySize, CAmount modifyFee, int64_t modifyCount); // Adjusts the ancestor state void UpdateAncestorState(int64_t modifySize, CAmount modifyFee, int64_t modifyCount, int64_t modifySigOps); - // Updates the fee delta used for mining priority score, and the - // modified fees with descendants/ancestors. - void UpdateFeeDelta(CAmount newFeeDelta); + // Updates the modified fees with descendants/ancestors. + void UpdateModifiedFee(CAmount fee_diff); // Update the LockPoints after a reorg void UpdateLockPoints(const LockPoints& lp); @@ -363,7 +365,7 @@ enum class MemPoolRemovalReason { * - a transaction which doesn't meet the minimum fee requirements. * - a new transaction that double-spends an input of a transaction already in * the pool where the new transaction does not meet the Replace-By-Fee - * requirements as defined in BIP 125. + * requirements as defined in doc/policy/mempool-replacements.md. * - a non-standard transaction. * * CTxMemPool::mapTx, and CTxMemPoolEntry bookkeeping: @@ -449,7 +451,9 @@ protected: void trackPackageRemoved(const CFeeRate& rate) EXCLUSIVE_LOCKS_REQUIRED(cs); - bool m_is_loaded GUARDED_BY(cs){false}; + bool m_load_tried GUARDED_BY(cs){false}; + + CFeeRate GetMinFee(size_t sizelimit) const; public: @@ -522,6 +526,8 @@ public: typedef std::set<txiter, CompareIteratorByHash> setEntries; + using Limits = kernel::MemPoolLimits; + uint64_t CalculateDescendantMaximum(txiter entry) const EXCLUSIVE_LOCKS_REQUIRED(cs); private: typedef std::map<txiter, setEntries, CompareIteratorByHash> cacheMap; @@ -541,34 +547,48 @@ private: /** * Helper function to calculate all in-mempool ancestors of staged_ancestors and apply ancestor * and descendant limits (including staged_ancestors thsemselves, entry_size and entry_count). - * param@[in] entry_size Virtual size to include in the limits. - * param@[in] entry_count How many entries to include in the limits. - * param@[in] staged_ancestors Should contain entries in the mempool. - * param@[out] setAncestors Will be populated with all mempool ancestors. + * + * @param[in] entry_size Virtual size to include in the limits. + * @param[in] entry_count How many entries to include in the limits. + * @param[out] setAncestors Will be populated with all mempool ancestors. + * @param[in] staged_ancestors Should contain entries in the mempool. + * @param[in] limits Maximum number and size of ancestors and descendants + * @param[out] errString Populated with error reason if any limits are hit + * + * @return true if no limits were hit and all in-mempool ancestors were calculated, false + * otherwise */ bool CalculateAncestorsAndCheckLimits(size_t entry_size, size_t entry_count, setEntries& setAncestors, CTxMemPoolEntry::Parents &staged_ancestors, - uint64_t limitAncestorCount, - uint64_t limitAncestorSize, - uint64_t limitDescendantCount, - uint64_t limitDescendantSize, + const Limits& limits, std::string &errString) const EXCLUSIVE_LOCKS_REQUIRED(cs); public: indirectmap<COutPoint, const CTransaction*> mapNextTx GUARDED_BY(cs); std::map<uint256, CAmount> mapDeltas GUARDED_BY(cs); + using Options = kernel::MemPoolOptions; + + const int64_t m_max_size_bytes; + const std::chrono::seconds m_expiry; + const CFeeRate m_incremental_relay_feerate; + const CFeeRate m_min_relay_feerate; + const CFeeRate m_dust_relay_feerate; + const bool m_permit_bare_multisig; + const std::optional<unsigned> m_max_datacarrier_bytes; + const bool m_require_standard; + const bool m_full_rbf; + + const Limits m_limits; + /** Create a new CTxMemPool. * Sanity checks will be off by default for performance, because otherwise * accepting transactions becomes O(N^2) where N is the number of transactions * in the pool. - * - * @param[in] estimator is used to estimate appropriate transaction fees. - * @param[in] check_ratio is the ratio used to determine how often sanity checks will run. */ - explicit CTxMemPool(CBlockPolicyEstimator* estimator = nullptr, int check_ratio = 0); + explicit CTxMemPool(const Options& opts); /** * If sanity-checking is turned on, check makes sure the pool is @@ -648,46 +668,44 @@ public: * * @param[in] vHashesToUpdate The set of txids from the * disconnected block that have been accepted back into the mempool. - * @param[in] ancestor_size_limit The maximum allowed size in virtual - * bytes of an entry and its ancestors - * @param[in] ancestor_count_limit The maximum allowed number of - * transactions including the entry and its ancestors. */ - void UpdateTransactionsFromBlock(const std::vector<uint256>& vHashesToUpdate, - uint64_t ancestor_size_limit, uint64_t ancestor_count_limit) EXCLUSIVE_LOCKS_REQUIRED(cs, cs_main) LOCKS_EXCLUDED(m_epoch); - - /** Try to calculate all in-mempool ancestors of entry. - * (these are all calculated including the tx itself) - * limitAncestorCount = max number of ancestors - * limitAncestorSize = max size of ancestors - * limitDescendantCount = max number of descendants any ancestor can have - * limitDescendantSize = max size of descendants any ancestor can have - * errString = populated with error reason if any limits are hit - * fSearchForParents = whether to search a tx's vin for in-mempool parents, or - * look up parents from mapLinks. Must be true for entries not in the mempool + void UpdateTransactionsFromBlock(const std::vector<uint256>& vHashesToUpdate) EXCLUSIVE_LOCKS_REQUIRED(cs, cs_main) LOCKS_EXCLUDED(m_epoch); + + /** + * Try to calculate all in-mempool ancestors of entry. + * (these are all calculated including the tx itself) + * + * @param[in] entry CTxMemPoolEntry of which all in-mempool ancestors are calculated + * @param[out] setAncestors Will be populated with all mempool ancestors. + * @param[in] limits Maximum number and size of ancestors and descendants + * @param[out] errString Populated with error reason if any limits are hit + * @param[in] fSearchForParents Whether to search a tx's vin for in-mempool parents, or look + * up parents from mapLinks. Must be true for entries not in + * the mempool + * + * @return true if no limits were hit and all in-mempool ancestors were calculated, false + * otherwise */ - bool CalculateMemPoolAncestors(const CTxMemPoolEntry& entry, setEntries& setAncestors, uint64_t limitAncestorCount, uint64_t limitAncestorSize, uint64_t limitDescendantCount, uint64_t limitDescendantSize, std::string& errString, bool fSearchForParents = true) const EXCLUSIVE_LOCKS_REQUIRED(cs); + bool CalculateMemPoolAncestors(const CTxMemPoolEntry& entry, + setEntries& setAncestors, + const Limits& limits, + std::string& errString, + bool fSearchForParents = true) const EXCLUSIVE_LOCKS_REQUIRED(cs); /** Calculate all in-mempool ancestors of a set of transactions not already in the mempool and * check ancestor and descendant limits. Heuristics are used to estimate the ancestor and * descendant count of all entries if the package were to be added to the mempool. The limits * are applied to the union of all package transactions. For example, if the package has 3 - * transactions and limitAncestorCount = 25, the union of all 3 sets of ancestors (including the + * transactions and limits.ancestor_count = 25, the union of all 3 sets of ancestors (including the * transactions themselves) must be <= 22. * @param[in] package Transaction package being evaluated for acceptance * to mempool. The transactions need not be direct * ancestors/descendants of each other. - * @param[in] limitAncestorCount Max number of txns including ancestors. - * @param[in] limitAncestorSize Max virtual size including ancestors. - * @param[in] limitDescendantCount Max number of txns including descendants. - * @param[in] limitDescendantSize Max virtual size including descendants. + * @param[in] limits Maximum number and size of ancestors and descendants * @param[out] errString Populated with error reason if a limit is hit. */ bool CheckPackageLimits(const Package& package, - uint64_t limitAncestorCount, - uint64_t limitAncestorSize, - uint64_t limitDescendantCount, - uint64_t limitDescendantSize, + const Limits& limits, std::string &errString) const EXCLUSIVE_LOCKS_REQUIRED(cs); /** Populate setDescendants with all in-mempool descendants of hash. @@ -696,12 +714,14 @@ public: void CalculateDescendants(txiter it, setEntries& setDescendants) const EXCLUSIVE_LOCKS_REQUIRED(cs); /** The minimum fee to get into the mempool, which may itself not be enough - * for larger-sized transactions. - * The incrementalRelayFee policy variable is used to bound the time it - * takes the fee rate to go back down all the way to 0. When the feerate - * would otherwise be half of this, it is set to 0 instead. - */ - CFeeRate GetMinFee(size_t sizelimit) const; + * for larger-sized transactions. + * The m_incremental_relay_feerate policy variable is used to bound the time it + * takes the fee rate to go back down all the way to 0. When the feerate + * would otherwise be half of this, it is set to 0 instead. + */ + CFeeRate GetMinFee() const { + return GetMinFee(m_max_size_bytes); + } /** Remove transactions from the mempool until its dynamic size is <= sizelimit. * pvNoSpendsRemaining, if set, will be populated with the list of outpoints @@ -720,11 +740,17 @@ public: */ void GetTransactionAncestry(const uint256& txid, size_t& ancestors, size_t& descendants, size_t* ancestorsize = nullptr, CAmount* ancestorfees = nullptr) const; - /** @returns true if the mempool is fully loaded */ - bool IsLoaded() const; + /** + * @returns true if we've made an attempt to load the mempool regardless of + * whether the attempt was successful or not + */ + bool GetLoadTried() const; - /** Sets the current loaded state */ - void SetIsLoaded(bool loaded); + /** + * Set whether or not we've made an attempt to load the mempool (regardless + * of whether the attempt was successful or not) + */ + void SetLoadTried(bool load_tried); unsigned long size() const { @@ -805,7 +831,7 @@ private: * mempool but may have child transactions in the mempool, eg during a * chain reorg. * - * @pre CTxMemPool::m_children is correct for the given tx and all + * @pre CTxMemPoolEntry::m_children is correct for the given tx and all * descendants. * @pre cachedDescendants is an accurate cache where each entry has all * descendants of the corresponding key, including those that should @@ -827,14 +853,9 @@ private: * @param[out] descendants_to_remove Populated with the txids of entries that * exceed ancestor limits. It's the responsibility of the caller to * removeRecursive them. - * @param[in] ancestor_size_limit the max number of ancestral bytes allowed - * for any descendant - * @param[in] ancestor_count_limit the max number of ancestor transactions - * allowed for any descendant */ void UpdateForDescendants(txiter updateIt, cacheMap& cachedDescendants, - const std::set<uint256>& setExclude, std::set<uint256>& descendants_to_remove, - uint64_t ancestor_size_limit, uint64_t ancestor_count_limit) EXCLUSIVE_LOCKS_REQUIRED(cs); + const std::set<uint256>& setExclude, std::set<uint256>& descendants_to_remove) EXCLUSIVE_LOCKS_REQUIRED(cs); /** Update ancestors of hash to add/remove it as a descendant transaction. */ void UpdateAncestorsOf(bool add, txiter hash, setEntries &setAncestors) EXCLUSIVE_LOCKS_REQUIRED(cs); /** Set ancestor state for an entry */ diff --git a/src/txorphanage.cpp b/src/txorphanage.cpp index ed4783f1a5..69ae8ea582 100644 --- a/src/txorphanage.cpp +++ b/src/txorphanage.cpp @@ -102,7 +102,7 @@ void TxOrphanage::EraseForPeer(NodeId peer) if (nErased > 0) LogPrint(BCLog::MEMPOOL, "Erased %d orphan tx from peer=%d\n", nErased, peer); } -unsigned int TxOrphanage::LimitOrphans(unsigned int max_orphans) +void TxOrphanage::LimitOrphans(unsigned int max_orphans) { AssertLockHeld(g_cs_orphans); @@ -135,7 +135,7 @@ unsigned int TxOrphanage::LimitOrphans(unsigned int max_orphans) EraseTx(m_orphan_list[randompos]->first); ++nEvicted; } - return nEvicted; + if (nEvicted > 0) LogPrint(BCLog::MEMPOOL, "orphanage overflow, removed %u tx\n", nEvicted); } void TxOrphanage::AddChildrenToWorkSet(const CTransaction& tx, std::set<uint256>& orphan_work_set) const diff --git a/src/txorphanage.h b/src/txorphanage.h index 24c8318f36..9363e6f733 100644 --- a/src/txorphanage.h +++ b/src/txorphanage.h @@ -41,7 +41,7 @@ public: void EraseForBlock(const CBlock& block) LOCKS_EXCLUDED(::g_cs_orphans); /** Limit the orphanage to the given maximum */ - unsigned int LimitOrphans(unsigned int max_orphans) EXCLUSIVE_LOCKS_REQUIRED(g_cs_orphans); + void 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) */ diff --git a/src/uint256.h b/src/uint256.h index 5c3a2f5409..e74b9ff7b1 100644 --- a/src/uint256.h +++ b/src/uint256.h @@ -6,6 +6,7 @@ #ifndef BITCOIN_UINT256_H #define BITCOIN_UINT256_H +#include <crypto/common.h> #include <span.h> #include <assert.h> @@ -84,15 +85,7 @@ public: uint64_t GetUint64(int pos) const { - const uint8_t* ptr = m_data + pos * 8; - return ((uint64_t)ptr[0]) | \ - ((uint64_t)ptr[1]) << 8 | \ - ((uint64_t)ptr[2]) << 16 | \ - ((uint64_t)ptr[3]) << 24 | \ - ((uint64_t)ptr[4]) << 32 | \ - ((uint64_t)ptr[5]) << 40 | \ - ((uint64_t)ptr[6]) << 48 | \ - ((uint64_t)ptr[7]) << 56; + return ReadLE64(m_data + pos * 8); } template<typename Stream> diff --git a/src/univalue/.cirrus.yml b/src/univalue/.cirrus.yml deleted file mode 100644 index f140fee12b..0000000000 --- a/src/univalue/.cirrus.yml +++ /dev/null @@ -1,44 +0,0 @@ -env: - MAKEJOBS: "-j4" - RUN_TESTS: "true" - BASE_OUTDIR: "$CIRRUS_WORKING_DIR/out_dir_base" - DEBIAN_FRONTEND: "noninteractive" - -task: - container: - image: ubuntu:focal - cpu: 1 - memory: 1G - greedy: true # https://medium.com/cirruslabs/introducing-greedy-container-instances-29aad06dc2b4 - - matrix: - - name: "gcc" - env: - CC: "gcc" - CXX: "g++" - APT_PKGS: "gcc" - - name: "clang" - env: - CC: "clang" - CXX: "clang++" - APT_PKGS: "clang" - - name: "mingw" - env: - CC: "" - CXX: "" - UNIVALUE_CONFIG: "--host=x86_64-w64-mingw32" - APT_PKGS: "g++-mingw-w64-x86-64 gcc-mingw-w64-x86-64 binutils-mingw-w64-x86-64" - RUN_TESTS: "false" - - install_script: - - apt update - - apt install -y pkg-config build-essential libtool autotools-dev automake bsdmainutils - - apt install -y $APT_PKGS - autogen_script: - - ./autogen.sh - configure_script: - - ./configure --cache-file=config.cache --bindir=$BASE_OUTDIR/bin --libdir=$BASE_OUTDIR/lib $UNIVALUE_CONFIG - make_script: - - make $MAKEJOBS V=1 - test_script: - - if [ "$RUN_TESTS" = "true" ]; then make $MAKEJOBS distcheck; fi diff --git a/src/univalue/COPYING b/src/univalue/COPYING deleted file mode 100644 index 1fb429f356..0000000000 --- a/src/univalue/COPYING +++ /dev/null @@ -1,19 +0,0 @@ - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. - diff --git a/src/univalue/Makefile.am b/src/univalue/Makefile.am deleted file mode 100644 index 476f14b922..0000000000 --- a/src/univalue/Makefile.am +++ /dev/null @@ -1,58 +0,0 @@ -include sources.mk -ACLOCAL_AMFLAGS = -I build-aux/m4 -.PHONY: gen FORCE -.INTERMEDIATE: $(GENBIN) - -include_HEADERS = $(UNIVALUE_DIST_HEADERS_INT) -noinst_HEADERS = $(UNIVALUE_LIB_HEADERS_INT) - -lib_LTLIBRARIES = libunivalue.la - -pkgconfigdir = $(libdir)/pkgconfig -pkgconfig_DATA = pc/libunivalue.pc - -libunivalue_la_SOURCES = $(UNIVALUE_LIB_SOURCES_INT) - -libunivalue_la_LDFLAGS = \ - -version-info $(LIBUNIVALUE_CURRENT):$(LIBUNIVALUE_REVISION):$(LIBUNIVALUE_AGE) \ - -no-undefined -libunivalue_la_CXXFLAGS = -I$(top_srcdir)/include - -TESTS = test/object test/unitester test/no_nul - -GENBIN = gen/gen$(BUILD_EXEEXT) -GEN_SRCS = gen/gen.cpp - -$(GENBIN): $(GEN_SRCS) - @echo Building $@ - $(AM_V_at)c++ -I$(top_srcdir)/include -o $@ $< - -gen: $(GENBIN) FORCE - @echo Updating lib/univalue_escapes.h - $(AM_V_at)$(GENBIN) > lib/univalue_escapes.h - -noinst_PROGRAMS = $(TESTS) test/test_json - -test_unitester_SOURCES = $(UNIVALUE_TEST_UNITESTER_INT) -test_unitester_LDADD = libunivalue.la -test_unitester_CXXFLAGS = -I$(top_srcdir)/include -DJSON_TEST_SRC=\"$(srcdir)/$(UNIVALUE_TEST_DATA_DIR_INT)\" -test_unitester_LDFLAGS = -static $(LIBTOOL_APP_LDFLAGS) - -test_test_json_SOURCES = $(UNIVALUE_TEST_JSON_INT) -test_test_json_LDADD = libunivalue.la -test_test_json_CXXFLAGS = -I$(top_srcdir)/include -test_test_json_LDFLAGS = -static $(LIBTOOL_APP_LDFLAGS) - -test_no_nul_SOURCES = $(UNIVALUE_TEST_NO_NUL_INT) -test_no_nul_LDADD = libunivalue.la -test_no_nul_CXXFLAGS = -I$(top_srcdir)/include -test_no_nul_LDFLAGS = -static $(LIBTOOL_APP_LDFLAGS) - -test_object_SOURCES = $(UNIVALUE_TEST_OBJECT_INT) -test_object_LDADD = libunivalue.la -test_object_CXXFLAGS = -I$(top_srcdir)/include -test_object_LDFLAGS = -static $(LIBTOOL_APP_LDFLAGS) - -TEST_FILES = $(UNIVALUE_TEST_FILES_INT) - -EXTRA_DIST=$(UNIVALUE_TEST_FILES_INT) $(GEN_SRCS) diff --git a/src/univalue/README.md b/src/univalue/README.md deleted file mode 100644 index d622f5b1e0..0000000000 --- a/src/univalue/README.md +++ /dev/null @@ -1,21 +0,0 @@ - -# UniValue - -## Summary - -A universal value class, with JSON encoding and decoding. - -UniValue is an abstract data type that may be a null, boolean, string, -number, array container, or a key/value dictionary container, nested to -an arbitrary depth. - -This class is aligned with the JSON standard, [RFC -7159](https://tools.ietf.org/html/rfc7159.html). - -## Library usage - -This is a fork of univalue used by Bitcoin Core. It is not maintained for usage -by other projects. Notably, the API is broken in non-backward-compatible ways. - -Other projects looking for a maintained library should use the upstream -univalue at https://github.com/jgarzik/univalue. diff --git a/src/univalue/autogen.sh b/src/univalue/autogen.sh deleted file mode 100755 index 4b38721faa..0000000000 --- a/src/univalue/autogen.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/bin/sh -set -e -srcdir="$(dirname $0)" -cd "$srcdir" -if [ -z ${LIBTOOLIZE} ] && GLIBTOOLIZE="`which glibtoolize 2>/dev/null`"; then - LIBTOOLIZE="${GLIBTOOLIZE}" - export LIBTOOLIZE -fi -autoreconf --install --force diff --git a/src/univalue/build-aux/m4/.gitignore b/src/univalue/build-aux/m4/.gitignore deleted file mode 100644 index f063686524..0000000000 --- a/src/univalue/build-aux/m4/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/*.m4 diff --git a/src/univalue/build-aux/m4/ax_cxx_compile_stdcxx.m4 b/src/univalue/build-aux/m4/ax_cxx_compile_stdcxx.m4 deleted file mode 100644 index f7e5137003..0000000000 --- a/src/univalue/build-aux/m4/ax_cxx_compile_stdcxx.m4 +++ /dev/null @@ -1,962 +0,0 @@ -# =========================================================================== -# https://www.gnu.org/software/autoconf-archive/ax_cxx_compile_stdcxx.html -# =========================================================================== -# -# SYNOPSIS -# -# AX_CXX_COMPILE_STDCXX(VERSION, [ext|noext], [mandatory|optional]) -# -# DESCRIPTION -# -# Check for baseline language coverage in the compiler for the specified -# version of the C++ standard. If necessary, add switches to CXX and -# CXXCPP to enable support. VERSION may be '11' (for the C++11 standard) -# or '14' (for the C++14 standard). -# -# The second argument, if specified, indicates whether you insist on an -# extended mode (e.g. -std=gnu++11) or a strict conformance mode (e.g. -# -std=c++11). If neither is specified, you get whatever works, with -# preference for no added switch, and then for an extended mode. -# -# The third argument, if specified 'mandatory' or if left unspecified, -# indicates that baseline support for the specified C++ standard is -# required and that the macro should error out if no mode with that -# support is found. If specified 'optional', then configuration proceeds -# regardless, after defining HAVE_CXX${VERSION} if and only if a -# supporting mode is found. -# -# LICENSE -# -# Copyright (c) 2008 Benjamin Kosnik <bkoz@redhat.com> -# Copyright (c) 2012 Zack Weinberg <zackw@panix.com> -# Copyright (c) 2013 Roy Stogner <roystgnr@ices.utexas.edu> -# Copyright (c) 2014, 2015 Google Inc.; contributed by Alexey Sokolov <sokolov@google.com> -# Copyright (c) 2015 Paul Norman <penorman@mac.com> -# Copyright (c) 2015 Moritz Klammler <moritz@klammler.eu> -# Copyright (c) 2016, 2018 Krzesimir Nowak <qdlacz@gmail.com> -# Copyright (c) 2019 Enji Cooper <yaneurabeya@gmail.com> -# Copyright (c) 2020 Jason Merrill <jason@redhat.com> -# -# Copying and distribution of this file, with or without modification, are -# permitted in any medium without royalty provided the copyright notice -# and this notice are preserved. This file is offered as-is, without any -# warranty. - -#serial 12 - -dnl This macro is based on the code from the AX_CXX_COMPILE_STDCXX_11 macro -dnl (serial version number 13). - -AC_DEFUN([AX_CXX_COMPILE_STDCXX], [dnl - m4_if([$1], [11], [ax_cxx_compile_alternatives="11 0x"], - [$1], [14], [ax_cxx_compile_alternatives="14 1y"], - [$1], [17], [ax_cxx_compile_alternatives="17 1z"], - [m4_fatal([invalid first argument `$1' to AX_CXX_COMPILE_STDCXX])])dnl - m4_if([$2], [], [], - [$2], [ext], [], - [$2], [noext], [], - [m4_fatal([invalid second argument `$2' to AX_CXX_COMPILE_STDCXX])])dnl - m4_if([$3], [], [ax_cxx_compile_cxx$1_required=true], - [$3], [mandatory], [ax_cxx_compile_cxx$1_required=true], - [$3], [optional], [ax_cxx_compile_cxx$1_required=false], - [m4_fatal([invalid third argument `$3' to AX_CXX_COMPILE_STDCXX])]) - AC_LANG_PUSH([C++])dnl - ac_success=no - - m4_if([$2], [], [dnl - AC_CACHE_CHECK(whether $CXX supports C++$1 features by default, - ax_cv_cxx_compile_cxx$1, - [AC_COMPILE_IFELSE([AC_LANG_SOURCE([_AX_CXX_COMPILE_STDCXX_testbody_$1])], - [ax_cv_cxx_compile_cxx$1=yes], - [ax_cv_cxx_compile_cxx$1=no])]) - if test x$ax_cv_cxx_compile_cxx$1 = xyes; then - ac_success=yes - fi]) - - m4_if([$2], [noext], [], [dnl - if test x$ac_success = xno; then - for alternative in ${ax_cxx_compile_alternatives}; do - switch="-std=gnu++${alternative}" - cachevar=AS_TR_SH([ax_cv_cxx_compile_cxx$1_$switch]) - AC_CACHE_CHECK(whether $CXX supports C++$1 features with $switch, - $cachevar, - [ac_save_CXX="$CXX" - CXX="$CXX $switch" - AC_COMPILE_IFELSE([AC_LANG_SOURCE([_AX_CXX_COMPILE_STDCXX_testbody_$1])], - [eval $cachevar=yes], - [eval $cachevar=no]) - CXX="$ac_save_CXX"]) - if eval test x\$$cachevar = xyes; then - CXX="$CXX $switch" - if test -n "$CXXCPP" ; then - CXXCPP="$CXXCPP $switch" - fi - ac_success=yes - break - fi - done - fi]) - - m4_if([$2], [ext], [], [dnl - if test x$ac_success = xno; then - dnl HP's aCC needs +std=c++11 according to: - dnl http://h21007.www2.hp.com/portal/download/files/unprot/aCxx/PDF_Release_Notes/769149-001.pdf - dnl Cray's crayCC needs "-h std=c++11" - for alternative in ${ax_cxx_compile_alternatives}; do - for switch in -std=c++${alternative} +std=c++${alternative} "-h std=c++${alternative}"; do - cachevar=AS_TR_SH([ax_cv_cxx_compile_cxx$1_$switch]) - AC_CACHE_CHECK(whether $CXX supports C++$1 features with $switch, - $cachevar, - [ac_save_CXX="$CXX" - CXX="$CXX $switch" - AC_COMPILE_IFELSE([AC_LANG_SOURCE([_AX_CXX_COMPILE_STDCXX_testbody_$1])], - [eval $cachevar=yes], - [eval $cachevar=no]) - CXX="$ac_save_CXX"]) - if eval test x\$$cachevar = xyes; then - CXX="$CXX $switch" - if test -n "$CXXCPP" ; then - CXXCPP="$CXXCPP $switch" - fi - ac_success=yes - break - fi - done - if test x$ac_success = xyes; then - break - fi - done - fi]) - AC_LANG_POP([C++]) - if test x$ax_cxx_compile_cxx$1_required = xtrue; then - if test x$ac_success = xno; then - AC_MSG_ERROR([*** A compiler with support for C++$1 language features is required.]) - fi - fi - if test x$ac_success = xno; then - HAVE_CXX$1=0 - AC_MSG_NOTICE([No compiler with C++$1 support was found]) - else - HAVE_CXX$1=1 - AC_DEFINE(HAVE_CXX$1,1, - [define if the compiler supports basic C++$1 syntax]) - fi - AC_SUBST(HAVE_CXX$1) -]) - - -dnl Test body for checking C++11 support - -m4_define([_AX_CXX_COMPILE_STDCXX_testbody_11], - _AX_CXX_COMPILE_STDCXX_testbody_new_in_11 -) - - -dnl Test body for checking C++14 support - -m4_define([_AX_CXX_COMPILE_STDCXX_testbody_14], - _AX_CXX_COMPILE_STDCXX_testbody_new_in_11 - _AX_CXX_COMPILE_STDCXX_testbody_new_in_14 -) - -m4_define([_AX_CXX_COMPILE_STDCXX_testbody_17], - _AX_CXX_COMPILE_STDCXX_testbody_new_in_11 - _AX_CXX_COMPILE_STDCXX_testbody_new_in_14 - _AX_CXX_COMPILE_STDCXX_testbody_new_in_17 -) - -dnl Tests for new features in C++11 - -m4_define([_AX_CXX_COMPILE_STDCXX_testbody_new_in_11], [[ - -// If the compiler admits that it is not ready for C++11, why torture it? -// Hopefully, this will speed up the test. - -#ifndef __cplusplus - -#error "This is not a C++ compiler" - -#elif __cplusplus < 201103L - -#error "This is not a C++11 compiler" - -#else - -namespace cxx11 -{ - - namespace test_static_assert - { - - template <typename T> - struct check - { - static_assert(sizeof(int) <= sizeof(T), "not big enough"); - }; - - } - - namespace test_final_override - { - - struct Base - { - virtual ~Base() {} - virtual void f() {} - }; - - struct Derived : public Base - { - virtual ~Derived() override {} - virtual void f() override {} - }; - - } - - namespace test_double_right_angle_brackets - { - - template < typename T > - struct check {}; - - typedef check<void> single_type; - typedef check<check<void>> double_type; - typedef check<check<check<void>>> triple_type; - typedef check<check<check<check<void>>>> quadruple_type; - - } - - namespace test_decltype - { - - int - f() - { - int a = 1; - decltype(a) b = 2; - return a + b; - } - - } - - namespace test_type_deduction - { - - template < typename T1, typename T2 > - struct is_same - { - static const bool value = false; - }; - - template < typename T > - struct is_same<T, T> - { - static const bool value = true; - }; - - template < typename T1, typename T2 > - auto - add(T1 a1, T2 a2) -> decltype(a1 + a2) - { - return a1 + a2; - } - - int - test(const int c, volatile int v) - { - static_assert(is_same<int, decltype(0)>::value == true, ""); - static_assert(is_same<int, decltype(c)>::value == false, ""); - static_assert(is_same<int, decltype(v)>::value == false, ""); - auto ac = c; - auto av = v; - auto sumi = ac + av + 'x'; - auto sumf = ac + av + 1.0; - static_assert(is_same<int, decltype(ac)>::value == true, ""); - static_assert(is_same<int, decltype(av)>::value == true, ""); - static_assert(is_same<int, decltype(sumi)>::value == true, ""); - static_assert(is_same<int, decltype(sumf)>::value == false, ""); - static_assert(is_same<int, decltype(add(c, v))>::value == true, ""); - return (sumf > 0.0) ? sumi : add(c, v); - } - - } - - namespace test_noexcept - { - - int f() { return 0; } - int g() noexcept { return 0; } - - static_assert(noexcept(f()) == false, ""); - static_assert(noexcept(g()) == true, ""); - - } - - namespace test_constexpr - { - - template < typename CharT > - unsigned long constexpr - strlen_c_r(const CharT *const s, const unsigned long acc) noexcept - { - return *s ? strlen_c_r(s + 1, acc + 1) : acc; - } - - template < typename CharT > - unsigned long constexpr - strlen_c(const CharT *const s) noexcept - { - return strlen_c_r(s, 0UL); - } - - static_assert(strlen_c("") == 0UL, ""); - static_assert(strlen_c("1") == 1UL, ""); - static_assert(strlen_c("example") == 7UL, ""); - static_assert(strlen_c("another\0example") == 7UL, ""); - - } - - namespace test_rvalue_references - { - - template < int N > - struct answer - { - static constexpr int value = N; - }; - - answer<1> f(int&) { return answer<1>(); } - answer<2> f(const int&) { return answer<2>(); } - answer<3> f(int&&) { return answer<3>(); } - - void - test() - { - int i = 0; - const int c = 0; - static_assert(decltype(f(i))::value == 1, ""); - static_assert(decltype(f(c))::value == 2, ""); - static_assert(decltype(f(0))::value == 3, ""); - } - - } - - namespace test_uniform_initialization - { - - struct test - { - static const int zero {}; - static const int one {1}; - }; - - static_assert(test::zero == 0, ""); - static_assert(test::one == 1, ""); - - } - - namespace test_lambdas - { - - void - test1() - { - auto lambda1 = [](){}; - auto lambda2 = lambda1; - lambda1(); - lambda2(); - } - - int - test2() - { - auto a = [](int i, int j){ return i + j; }(1, 2); - auto b = []() -> int { return '0'; }(); - auto c = [=](){ return a + b; }(); - auto d = [&](){ return c; }(); - auto e = [a, &b](int x) mutable { - const auto identity = [](int y){ return y; }; - for (auto i = 0; i < a; ++i) - a += b--; - return x + identity(a + b); - }(0); - return a + b + c + d + e; - } - - int - test3() - { - const auto nullary = [](){ return 0; }; - const auto unary = [](int x){ return x; }; - using nullary_t = decltype(nullary); - using unary_t = decltype(unary); - const auto higher1st = [](nullary_t f){ return f(); }; - const auto higher2nd = [unary](nullary_t f1){ - return [unary, f1](unary_t f2){ return f2(unary(f1())); }; - }; - return higher1st(nullary) + higher2nd(nullary)(unary); - } - - } - - namespace test_variadic_templates - { - - template <int...> - struct sum; - - template <int N0, int... N1toN> - struct sum<N0, N1toN...> - { - static constexpr auto value = N0 + sum<N1toN...>::value; - }; - - template <> - struct sum<> - { - static constexpr auto value = 0; - }; - - static_assert(sum<>::value == 0, ""); - static_assert(sum<1>::value == 1, ""); - static_assert(sum<23>::value == 23, ""); - static_assert(sum<1, 2>::value == 3, ""); - static_assert(sum<5, 5, 11>::value == 21, ""); - static_assert(sum<2, 3, 5, 7, 11, 13>::value == 41, ""); - - } - - // http://stackoverflow.com/questions/13728184/template-aliases-and-sfinae - // Clang 3.1 fails with headers of libstd++ 4.8.3 when using std::function - // because of this. - namespace test_template_alias_sfinae - { - - struct foo {}; - - template<typename T> - using member = typename T::member_type; - - template<typename T> - void func(...) {} - - template<typename T> - void func(member<T>*) {} - - void test(); - - void test() { func<foo>(0); } - - } - -} // namespace cxx11 - -#endif // __cplusplus >= 201103L - -]]) - - -dnl Tests for new features in C++14 - -m4_define([_AX_CXX_COMPILE_STDCXX_testbody_new_in_14], [[ - -// If the compiler admits that it is not ready for C++14, why torture it? -// Hopefully, this will speed up the test. - -#ifndef __cplusplus - -#error "This is not a C++ compiler" - -#elif __cplusplus < 201402L - -#error "This is not a C++14 compiler" - -#else - -namespace cxx14 -{ - - namespace test_polymorphic_lambdas - { - - int - test() - { - const auto lambda = [](auto&&... args){ - const auto istiny = [](auto x){ - return (sizeof(x) == 1UL) ? 1 : 0; - }; - const int aretiny[] = { istiny(args)... }; - return aretiny[0]; - }; - return lambda(1, 1L, 1.0f, '1'); - } - - } - - namespace test_binary_literals - { - - constexpr auto ivii = 0b0000000000101010; - static_assert(ivii == 42, "wrong value"); - - } - - namespace test_generalized_constexpr - { - - template < typename CharT > - constexpr unsigned long - strlen_c(const CharT *const s) noexcept - { - auto length = 0UL; - for (auto p = s; *p; ++p) - ++length; - return length; - } - - static_assert(strlen_c("") == 0UL, ""); - static_assert(strlen_c("x") == 1UL, ""); - static_assert(strlen_c("test") == 4UL, ""); - static_assert(strlen_c("another\0test") == 7UL, ""); - - } - - namespace test_lambda_init_capture - { - - int - test() - { - auto x = 0; - const auto lambda1 = [a = x](int b){ return a + b; }; - const auto lambda2 = [a = lambda1(x)](){ return a; }; - return lambda2(); - } - - } - - namespace test_digit_separators - { - - constexpr auto ten_million = 100'000'000; - static_assert(ten_million == 100000000, ""); - - } - - namespace test_return_type_deduction - { - - auto f(int& x) { return x; } - decltype(auto) g(int& x) { return x; } - - template < typename T1, typename T2 > - struct is_same - { - static constexpr auto value = false; - }; - - template < typename T > - struct is_same<T, T> - { - static constexpr auto value = true; - }; - - int - test() - { - auto x = 0; - static_assert(is_same<int, decltype(f(x))>::value, ""); - static_assert(is_same<int&, decltype(g(x))>::value, ""); - return x; - } - - } - -} // namespace cxx14 - -#endif // __cplusplus >= 201402L - -]]) - - -dnl Tests for new features in C++17 - -m4_define([_AX_CXX_COMPILE_STDCXX_testbody_new_in_17], [[ - -// If the compiler admits that it is not ready for C++17, why torture it? -// Hopefully, this will speed up the test. - -#ifndef __cplusplus - -#error "This is not a C++ compiler" - -#elif __cplusplus < 201703L - -#error "This is not a C++17 compiler" - -#else - -#include <initializer_list> -#include <utility> -#include <type_traits> - -namespace cxx17 -{ - - namespace test_constexpr_lambdas - { - - constexpr int foo = [](){return 42;}(); - - } - - namespace test::nested_namespace::definitions - { - - } - - namespace test_fold_expression - { - - template<typename... Args> - int multiply(Args... args) - { - return (args * ... * 1); - } - - template<typename... Args> - bool all(Args... args) - { - return (args && ...); - } - - } - - namespace test_extended_static_assert - { - - static_assert (true); - - } - - namespace test_auto_brace_init_list - { - - auto foo = {5}; - auto bar {5}; - - static_assert(std::is_same<std::initializer_list<int>, decltype(foo)>::value); - static_assert(std::is_same<int, decltype(bar)>::value); - } - - namespace test_typename_in_template_template_parameter - { - - template<template<typename> typename X> struct D; - - } - - namespace test_fallthrough_nodiscard_maybe_unused_attributes - { - - int f1() - { - return 42; - } - - [[nodiscard]] int f2() - { - [[maybe_unused]] auto unused = f1(); - - switch (f1()) - { - case 17: - f1(); - [[fallthrough]]; - case 42: - f1(); - } - return f1(); - } - - } - - namespace test_extended_aggregate_initialization - { - - struct base1 - { - int b1, b2 = 42; - }; - - struct base2 - { - base2() { - b3 = 42; - } - int b3; - }; - - struct derived : base1, base2 - { - int d; - }; - - derived d1 {{1, 2}, {}, 4}; // full initialization - derived d2 {{}, {}, 4}; // value-initialized bases - - } - - namespace test_general_range_based_for_loop - { - - struct iter - { - int i; - - int& operator* () - { - return i; - } - - const int& operator* () const - { - return i; - } - - iter& operator++() - { - ++i; - return *this; - } - }; - - struct sentinel - { - int i; - }; - - bool operator== (const iter& i, const sentinel& s) - { - return i.i == s.i; - } - - bool operator!= (const iter& i, const sentinel& s) - { - return !(i == s); - } - - struct range - { - iter begin() const - { - return {0}; - } - - sentinel end() const - { - return {5}; - } - }; - - void f() - { - range r {}; - - for (auto i : r) - { - [[maybe_unused]] auto v = i; - } - } - - } - - namespace test_lambda_capture_asterisk_this_by_value - { - - struct t - { - int i; - int foo() - { - return [*this]() - { - return i; - }(); - } - }; - - } - - namespace test_enum_class_construction - { - - enum class byte : unsigned char - {}; - - byte foo {42}; - - } - - namespace test_constexpr_if - { - - template <bool cond> - int f () - { - if constexpr(cond) - { - return 13; - } - else - { - return 42; - } - } - - } - - namespace test_selection_statement_with_initializer - { - - int f() - { - return 13; - } - - int f2() - { - if (auto i = f(); i > 0) - { - return 3; - } - - switch (auto i = f(); i + 4) - { - case 17: - return 2; - - default: - return 1; - } - } - - } - - namespace test_template_argument_deduction_for_class_templates - { - - template <typename T1, typename T2> - struct pair - { - pair (T1 p1, T2 p2) - : m1 {p1}, - m2 {p2} - {} - - T1 m1; - T2 m2; - }; - - void f() - { - [[maybe_unused]] auto p = pair{13, 42u}; - } - - } - - namespace test_non_type_auto_template_parameters - { - - template <auto n> - struct B - {}; - - B<5> b1; - B<'a'> b2; - - } - - namespace test_structured_bindings - { - - int arr[2] = { 1, 2 }; - std::pair<int, int> pr = { 1, 2 }; - - auto f1() -> int(&)[2] - { - return arr; - } - - auto f2() -> std::pair<int, int>& - { - return pr; - } - - struct S - { - int x1 : 2; - volatile double y1; - }; - - S f3() - { - return {}; - } - - auto [ x1, y1 ] = f1(); - auto& [ xr1, yr1 ] = f1(); - auto [ x2, y2 ] = f2(); - auto& [ xr2, yr2 ] = f2(); - const auto [ x3, y3 ] = f3(); - - } - - namespace test_exception_spec_type_system - { - - struct Good {}; - struct Bad {}; - - void g1() noexcept; - void g2(); - - template<typename T> - Bad - f(T*, T*); - - template<typename T1, typename T2> - Good - f(T1*, T2*); - - static_assert (std::is_same_v<Good, decltype(f(g1, g2))>); - - } - - namespace test_inline_variables - { - - template<class T> void f(T) - {} - - template<class T> inline T g(T) - { - return T{}; - } - - template<> inline void f<>(int) - {} - - template<> int g<>(int) - { - return 5; - } - - } - -} // namespace cxx17 - -#endif // __cplusplus < 201703L - -]]) diff --git a/src/univalue/configure.ac b/src/univalue/configure.ac deleted file mode 100644 index ed9c5f0c5c..0000000000 --- a/src/univalue/configure.ac +++ /dev/null @@ -1,72 +0,0 @@ -m4_define([libunivalue_major_version], [1]) -m4_define([libunivalue_minor_version], [1]) -m4_define([libunivalue_micro_version], [4]) -m4_define([libunivalue_interface_age], [4]) -# If you need a modifier for the version number. -# Normally empty, but can be used to make "fixup" releases. -m4_define([libunivalue_extraversion], []) - -dnl libtool versioning from libunivalue -m4_define([libunivalue_current], [m4_eval(100 * libunivalue_minor_version + libunivalue_micro_version - libunivalue_interface_age)]) -m4_define([libunivalue_binary_age], [m4_eval(100 * libunivalue_minor_version + libunivalue_micro_version)]) -m4_define([libunivalue_revision], [libunivalue_interface_age]) -m4_define([libunivalue_age], [m4_eval(libunivalue_binary_age - libunivalue_interface_age)]) -m4_define([libunivalue_version], [libunivalue_major_version().libunivalue_minor_version().libunivalue_micro_version()libunivalue_extraversion()]) - - -AC_INIT([univalue], [1.0.4], - [http://github.com/jgarzik/univalue/]) - -dnl make the compilation flags quiet unless V=1 is used -m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])]) - -AC_PREREQ(2.60) -AC_CONFIG_SRCDIR([lib/univalue.cpp]) -AC_CONFIG_AUX_DIR([build-aux]) -AC_CONFIG_MACRO_DIR([build-aux/m4]) -AC_CONFIG_HEADERS([univalue-config.h]) -AM_INIT_AUTOMAKE([subdir-objects foreign]) - -LIBUNIVALUE_MAJOR_VERSION=libunivalue_major_version -LIBUNIVALUE_MINOR_VERSION=libunivalue_minor_version -LIBUNIVALUE_MICRO_VERSION=libunivalue_micro_version -LIBUNIVALUE_INTERFACE_AGE=libunivalue_interface_age - -# ABI version -# http://www.gnu.org/software/libtool/manual/html_node/Updating-version-info.html -LIBUNIVALUE_CURRENT=libunivalue_current -LIBUNIVALUE_REVISION=libunivalue_revision -LIBUNIVALUE_AGE=libunivalue_age - -AC_SUBST(LIBUNIVALUE_CURRENT) -AC_SUBST(LIBUNIVALUE_REVISION) -AC_SUBST(LIBUNIVALUE_AGE) - -LT_INIT -LT_LANG([C++]) - -dnl Require C++17 compiler (no GNU extensions) -AX_CXX_COMPILE_STDCXX([17], [noext], [mandatory], [nodefault]) - -case $host in - *mingw*) - LIBTOOL_APP_LDFLAGS="$LIBTOOL_APP_LDFLAGS -all-static" - ;; -esac - -BUILD_EXEEXT= -case $build in - *mingw*) - BUILD_EXEEXT=".exe" - ;; -esac - -AC_CONFIG_FILES([ - Makefile - pc/libunivalue.pc - pc/libunivalue-uninstalled.pc]) - -AC_SUBST(LIBTOOL_APP_LDFLAGS) -AC_SUBST(BUILD_EXEEXT) -AC_OUTPUT - diff --git a/src/univalue/gen/gen.cpp b/src/univalue/gen/gen.cpp deleted file mode 100644 index ca5b470ddc..0000000000 --- a/src/univalue/gen/gen.cpp +++ /dev/null @@ -1,84 +0,0 @@ -// Copyright 2014 BitPay Inc. -// Distributed under the MIT software license, see the accompanying -// file COPYING or https://opensource.org/licenses/mit-license.php. - -// -// To re-create univalue_escapes.h: -// $ g++ -o gen gen.cpp -// $ ./gen > univalue_escapes.h -// - -#include <univalue.h> - -#include <cstdio> -#include <cstring> -#include <string> - -static bool initEscapes; -static std::string escapes[256]; - -static void initJsonEscape() -{ - // Escape all lower control characters (some get overridden with smaller sequences below) - for (int ch=0x00; ch<0x20; ++ch) { - char tmpbuf[20]; - snprintf(tmpbuf, sizeof(tmpbuf), "\\u%04x", ch); - escapes[ch] = std::string(tmpbuf); - } - - escapes[(int)'"'] = "\\\""; - escapes[(int)'\\'] = "\\\\"; - escapes[(int)'\b'] = "\\b"; - escapes[(int)'\f'] = "\\f"; - escapes[(int)'\n'] = "\\n"; - escapes[(int)'\r'] = "\\r"; - escapes[(int)'\t'] = "\\t"; - escapes[(int)'\x7f'] = "\\u007f"; // U+007F DELETE - - initEscapes = true; -} - -static void outputEscape() -{ - printf( "// Automatically generated file. Do not modify.\n" - "#ifndef BITCOIN_UNIVALUE_UNIVALUE_ESCAPES_H\n" - "#define BITCOIN_UNIVALUE_UNIVALUE_ESCAPES_H\n" - "static const char *escapes[256] = {\n"); - - for (unsigned int i = 0; i < 256; i++) { - if (escapes[i].empty()) { - printf("\tnullptr,\n"); - } else { - printf("\t\""); - - unsigned int si; - for (si = 0; si < escapes[i].size(); si++) { - char ch = escapes[i][si]; - switch (ch) { - case '"': - printf("\\\""); - break; - case '\\': - printf("\\\\"); - break; - default: - printf("%c", escapes[i][si]); - break; - } - } - - printf("\",\n"); - } - } - - printf( "};\n" - "#endif // BITCOIN_UNIVALUE_UNIVALUE_ESCAPES_H\n"); -} - -int main (int argc, char *argv[]) -{ - initJsonEscape(); - outputEscape(); - return 0; -} - diff --git a/src/univalue/include/univalue.h b/src/univalue/include/univalue.h index f0d4de2035..850d0a1cbc 100644 --- a/src/univalue/include/univalue.h +++ b/src/univalue/include/univalue.h @@ -3,8 +3,8 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or https://opensource.org/licenses/mit-license.php. -#ifndef __UNIVALUE_H__ -#define __UNIVALUE_H__ +#ifndef BITCOIN_UNIVALUE_INCLUDE_UNIVALUE_H +#define BITCOIN_UNIVALUE_INCLUDE_UNIVALUE_H #include <charconv> #include <cstdint> @@ -19,46 +19,49 @@ class UniValue { public: enum VType { VNULL, VOBJ, VARR, VSTR, VNUM, VBOOL, }; + class type_error : public std::runtime_error + { + using std::runtime_error::runtime_error; + }; + UniValue() { typ = VNULL; } UniValue(UniValue::VType initialType, const std::string& initialStr = "") { typ = initialType; val = initialStr; } - UniValue(uint64_t val_) { - setInt(val_); - } - UniValue(int64_t val_) { - setInt(val_); - } - UniValue(bool val_) { - setBool(val_); - } - UniValue(int val_) { - setInt(val_); - } - UniValue(double val_) { - setFloat(val_); - } - UniValue(const std::string& val_) { - setStr(val_); - } - UniValue(const char *val_) { - std::string s(val_); - setStr(s); + template <typename Ref, typename T = std::remove_cv_t<std::remove_reference_t<Ref>>, + std::enable_if_t<std::is_floating_point_v<T> || // setFloat + std::is_same_v<bool, T> || // setBool + std::is_signed_v<T> || std::is_unsigned_v<T> || // setInt + std::is_constructible_v<std::string, T>, // setStr + bool> = true> + UniValue(Ref&& val) + { + if constexpr (std::is_floating_point_v<T>) { + setFloat(val); + } else if constexpr (std::is_same_v<bool, T>) { + setBool(val); + } else if constexpr (std::is_signed_v<T>) { + setInt(int64_t{val}); + } else if constexpr (std::is_unsigned_v<T>) { + setInt(uint64_t{val}); + } else { + setStr(std::string{std::forward<Ref>(val)}); + } } void clear(); - bool setNull(); - bool setBool(bool val); - bool setNumStr(const std::string& val); - bool setInt(uint64_t val); - bool setInt(int64_t val); - bool setInt(int val_) { return setInt((int64_t)val_); } - bool setFloat(double val); - bool setStr(const std::string& val); - bool setArray(); - bool setObject(); + void setNull(); + void setBool(bool val); + void setNumStr(const std::string& val); + void setInt(uint64_t val); + void setInt(int64_t val); + void setInt(int val_) { return setInt(int64_t{val_}); } + void setFloat(double val); + void setStr(const std::string& val); + void setArray(); + void setObject(); enum VType getType() const { return typ; } const std::string& getValStr() const { return val; } @@ -82,12 +85,14 @@ public: bool isArray() const { return (typ == VARR); } bool isObject() const { return (typ == VOBJ); } - bool push_back(const UniValue& val); - bool push_backV(const std::vector<UniValue>& vec); + void push_back(UniValue val); + void push_backV(const std::vector<UniValue>& vec); + template <class It> + void push_backV(It first, It last); - void __pushKV(const std::string& key, const UniValue& val); - bool pushKV(const std::string& key, const UniValue& val); - bool pushKVs(const UniValue& obj); + void __pushKV(std::string key, UniValue val); + void pushKV(std::string key, UniValue val); + void pushKVs(UniValue obj); std::string write(unsigned int prettyIndent = 0, unsigned int indentLevel = 0) const; @@ -104,6 +109,7 @@ private: std::vector<std::string> keys; std::vector<UniValue> values; + void checkType(const VType& expected) const; bool findKey(const std::string& key, size_t& retIdx) const; void writeArray(unsigned int prettyIndent, unsigned int indentLevel, std::string& s) const; void writeObject(unsigned int prettyIndent, unsigned int indentLevel, std::string& s) const; @@ -114,19 +120,7 @@ public: const std::vector<std::string>& getKeys() const; const std::vector<UniValue>& getValues() const; template <typename Int> - auto getInt() const - { - static_assert(std::is_integral<Int>::value); - if (typ != VNUM) { - throw std::runtime_error("JSON value is not an integer as expected"); - } - Int result; - const auto [first_nonmatching, error_condition] = std::from_chars(val.data(), val.data() + val.size(), result); - if (first_nonmatching != val.data() + val.size() || error_condition != std::errc{}) { - throw std::runtime_error("JSON integer out of range"); - } - return result; - } + Int getInt() const; bool get_bool() const; const std::string& get_str() const; double get_real() const; @@ -137,6 +131,26 @@ public: friend const UniValue& find_value( const UniValue& obj, const std::string& name); }; +template <class It> +void UniValue::push_backV(It first, It last) +{ + checkType(VARR); + values.insert(values.end(), first, last); +} + +template <typename Int> +Int UniValue::getInt() const +{ + static_assert(std::is_integral<Int>::value); + checkType(VNUM); + Int result; + const auto [first_nonmatching, error_condition] = std::from_chars(val.data(), val.data() + val.size(), result); + if (first_nonmatching != val.data() + val.size() || error_condition != std::errc{}) { + throw std::runtime_error("JSON integer out of range"); + } + return result; +} + enum jtokentype { JTOK_ERR = -1, JTOK_NONE = 0, // eof @@ -194,4 +208,4 @@ extern const UniValue NullUniValue; const UniValue& find_value( const UniValue& obj, const std::string& name); -#endif // __UNIVALUE_H__ +#endif // BITCOIN_UNIVALUE_INCLUDE_UNIVALUE_H diff --git a/src/univalue/lib/univalue_escapes.h b/src/univalue/include/univalue_escapes.h index 3f714f8e5b..83767e8ac5 100644 --- a/src/univalue/lib/univalue_escapes.h +++ b/src/univalue/include/univalue_escapes.h @@ -1,6 +1,5 @@ -// Automatically generated file. Do not modify. -#ifndef BITCOIN_UNIVALUE_UNIVALUE_ESCAPES_H -#define BITCOIN_UNIVALUE_UNIVALUE_ESCAPES_H +#ifndef BITCOIN_UNIVALUE_INCLUDE_UNIVALUE_ESCAPES_H +#define BITCOIN_UNIVALUE_INCLUDE_UNIVALUE_ESCAPES_H static const char *escapes[256] = { "\\u0000", "\\u0001", @@ -259,4 +258,4 @@ static const char *escapes[256] = { nullptr, nullptr, }; -#endif // BITCOIN_UNIVALUE_UNIVALUE_ESCAPES_H +#endif // BITCOIN_UNIVALUE_INCLUDE_UNIVALUE_ESCAPES_H diff --git a/src/univalue/lib/univalue_utffilter.h b/src/univalue/include/univalue_utffilter.h index c24ac58eaf..f688eaaa30 100644 --- a/src/univalue/lib/univalue_utffilter.h +++ b/src/univalue/include/univalue_utffilter.h @@ -1,8 +1,8 @@ // Copyright 2016 Wladimir J. van der Laan // Distributed under the MIT software license, see the accompanying // file COPYING or https://opensource.org/licenses/mit-license.php. -#ifndef UNIVALUE_UTFFILTER_H -#define UNIVALUE_UTFFILTER_H +#ifndef BITCOIN_UNIVALUE_INCLUDE_UNIVALUE_UTFFILTER_H +#define BITCOIN_UNIVALUE_INCLUDE_UNIVALUE_UTFFILTER_H #include <string> @@ -116,4 +116,4 @@ private: } }; -#endif +#endif // BITCOIN_UNIVALUE_INCLUDE_UNIVALUE_UTFFILTER_H diff --git a/src/univalue/lib/univalue.cpp b/src/univalue/lib/univalue.cpp index 3553995c28..4448981d3e 100644 --- a/src/univalue/lib/univalue.cpp +++ b/src/univalue/lib/univalue.cpp @@ -23,19 +23,17 @@ void UniValue::clear() values.clear(); } -bool UniValue::setNull() +void UniValue::setNull() { clear(); - return true; } -bool UniValue::setBool(bool val_) +void UniValue::setBool(bool val_) { clear(); typ = VBOOL; if (val_) val = "1"; - return true; } static bool validNumStr(const std::string& s) @@ -46,18 +44,18 @@ static bool validNumStr(const std::string& s) return (tt == JTOK_NUMBER); } -bool UniValue::setNumStr(const std::string& val_) +void UniValue::setNumStr(const std::string& val_) { - if (!validNumStr(val_)) - return false; + if (!validNumStr(val_)) { + throw std::runtime_error{"The string '" + val_ + "' is not a valid JSON number"}; + } clear(); typ = VNUM; val = val_; - return true; } -bool UniValue::setInt(uint64_t val_) +void UniValue::setInt(uint64_t val_) { std::ostringstream oss; @@ -66,7 +64,7 @@ bool UniValue::setInt(uint64_t val_) return setNumStr(oss.str()); } -bool UniValue::setInt(int64_t val_) +void UniValue::setInt(int64_t val_) { std::ostringstream oss; @@ -75,86 +73,74 @@ bool UniValue::setInt(int64_t val_) return setNumStr(oss.str()); } -bool UniValue::setFloat(double val_) +void UniValue::setFloat(double val_) { std::ostringstream oss; oss << std::setprecision(16) << val_; - bool ret = setNumStr(oss.str()); - typ = VNUM; - return ret; + return setNumStr(oss.str()); } -bool UniValue::setStr(const std::string& val_) +void UniValue::setStr(const std::string& val_) { clear(); typ = VSTR; val = val_; - return true; } -bool UniValue::setArray() +void UniValue::setArray() { clear(); typ = VARR; - return true; } -bool UniValue::setObject() +void UniValue::setObject() { clear(); typ = VOBJ; - return true; } -bool UniValue::push_back(const UniValue& val_) +void UniValue::push_back(UniValue val) { - if (typ != VARR) - return false; + checkType(VARR); - values.push_back(val_); - return true; + values.push_back(std::move(val)); } -bool UniValue::push_backV(const std::vector<UniValue>& vec) +void UniValue::push_backV(const std::vector<UniValue>& vec) { - if (typ != VARR) - return false; + checkType(VARR); values.insert(values.end(), vec.begin(), vec.end()); - - return true; } -void UniValue::__pushKV(const std::string& key, const UniValue& val_) +void UniValue::__pushKV(std::string key, UniValue val) { - keys.push_back(key); - values.push_back(val_); + checkType(VOBJ); + + keys.push_back(std::move(key)); + values.push_back(std::move(val)); } -bool UniValue::pushKV(const std::string& key, const UniValue& val_) +void UniValue::pushKV(std::string key, UniValue val) { - if (typ != VOBJ) - return false; + checkType(VOBJ); size_t idx; if (findKey(key, idx)) - values[idx] = val_; + values[idx] = std::move(val); else - __pushKV(key, val_); - return true; + __pushKV(std::move(key), std::move(val)); } -bool UniValue::pushKVs(const UniValue& obj) +void UniValue::pushKVs(UniValue obj) { - if (typ != VOBJ || obj.typ != VOBJ) - return false; + checkType(VOBJ); + obj.checkType(VOBJ); for (size_t i = 0; i < obj.keys.size(); i++) - __pushKV(obj.keys[i], obj.values.at(i)); - - return true; + __pushKV(std::move(obj.keys.at(i)), std::move(obj.values.at(i))); } void UniValue::getObjMap(std::map<std::string,UniValue>& kv) const @@ -221,6 +207,14 @@ const UniValue& UniValue::operator[](size_t index) const return values.at(index); } +void UniValue::checkType(const VType& expected) const +{ + if (typ != expected) { + throw type_error{"JSON value of type " + std::string{uvTypeName(typ)} + " is not of expected type " + + std::string{uvTypeName(expected)}}; + } +} + const char *uvTypeName(UniValue::VType t) { switch (t) { diff --git a/src/univalue/lib/univalue_get.cpp b/src/univalue/lib/univalue_get.cpp index 9bbdb1fe69..5c58f388dd 100644 --- a/src/univalue/lib/univalue_get.cpp +++ b/src/univalue/lib/univalue_get.cpp @@ -46,8 +46,7 @@ bool ParseDouble(const std::string& str, double *out) const std::vector<std::string>& UniValue::getKeys() const { - if (typ != VOBJ) - throw std::runtime_error("JSON value is not an object as expected"); + checkType(VOBJ); return keys; } @@ -60,22 +59,19 @@ const std::vector<UniValue>& UniValue::getValues() const bool UniValue::get_bool() const { - if (typ != VBOOL) - throw std::runtime_error("JSON value is not a boolean as expected"); + checkType(VBOOL); return getBool(); } const std::string& UniValue::get_str() const { - if (typ != VSTR) - throw std::runtime_error("JSON value is not a string as expected"); + checkType(VSTR); return getValStr(); } double UniValue::get_real() const { - if (typ != VNUM) - throw std::runtime_error("JSON value is not a number as expected"); + checkType(VNUM); double retval; if (!ParseDouble(getValStr(), &retval)) throw std::runtime_error("JSON double out of range"); @@ -84,15 +80,12 @@ double UniValue::get_real() const const UniValue& UniValue::get_obj() const { - if (typ != VOBJ) - throw std::runtime_error("JSON value is not an object as expected"); + checkType(VOBJ); return *this; } const UniValue& UniValue::get_array() const { - if (typ != VARR) - throw std::runtime_error("JSON value is not an array as expected"); + checkType(VARR); return *this; } - diff --git a/src/univalue/lib/univalue_read.cpp b/src/univalue/lib/univalue_read.cpp index a6ed75e57a..2f2385383c 100644 --- a/src/univalue/lib/univalue_read.cpp +++ b/src/univalue/lib/univalue_read.cpp @@ -3,7 +3,7 @@ // file COPYING or https://opensource.org/licenses/mit-license.php. #include <univalue.h> -#include "univalue_utffilter.h" +#include <univalue_utffilter.h> #include <cstdio> #include <cstdint> diff --git a/src/univalue/lib/univalue_write.cpp b/src/univalue/lib/univalue_write.cpp index 18833077b7..4a3cbba20f 100644 --- a/src/univalue/lib/univalue_write.cpp +++ b/src/univalue/lib/univalue_write.cpp @@ -3,7 +3,7 @@ // file COPYING or https://opensource.org/licenses/mit-license.php. #include <univalue.h> -#include "univalue_escapes.h" +#include <univalue_escapes.h> #include <memory> #include <string> diff --git a/src/univalue/pc/libunivalue-uninstalled.pc.in b/src/univalue/pc/libunivalue-uninstalled.pc.in deleted file mode 100644 index b7f53e875e..0000000000 --- a/src/univalue/pc/libunivalue-uninstalled.pc.in +++ /dev/null @@ -1,9 +0,0 @@ -prefix=@prefix@ -exec_prefix=@exec_prefix@ -libdir=@libdir@ -includedir=@includedir@ - -Name: libunivalue -Description: libunivalue, C++ universal value object and JSON library -Version: @VERSION@ -Libs: ${pc_top_builddir}/${pcfiledir}/libunivalue.la diff --git a/src/univalue/pc/libunivalue.pc.in b/src/univalue/pc/libunivalue.pc.in deleted file mode 100644 index 358a2d5f73..0000000000 --- a/src/univalue/pc/libunivalue.pc.in +++ /dev/null @@ -1,10 +0,0 @@ -prefix=@prefix@ -exec_prefix=@exec_prefix@ -libdir=@libdir@ -includedir=@includedir@ - -Name: libunivalue -Description: libunivalue, C++ universal value object and JSON library -Version: @VERSION@ -Libs: -L${libdir} -lunivalue -Cflags: -I${includedir} diff --git a/src/univalue/sources.mk b/src/univalue/sources.mk index efab6d277f..5e4d9c3831 100644 --- a/src/univalue/sources.mk +++ b/src/univalue/sources.mk @@ -1,21 +1,15 @@ -# - All variables are namespaced with UNIVALUE_ to avoid colliding with -# downstream makefiles. # - All Variables ending in _HEADERS or _SOURCES confuse automake, so the # _INT postfix is applied. # - Convenience variables, for example a UNIVALUE_TEST_DIR should not be used # as they interfere with automatic dependency generation -# - The %reldir% is the relative path from the Makefile.am. This allows -# downstreams to use these variables without having to manually account for -# the path change. +# - The %reldir% is the relative path from the Makefile.am. UNIVALUE_INCLUDE_DIR_INT = %reldir%/include UNIVALUE_DIST_HEADERS_INT = UNIVALUE_DIST_HEADERS_INT += %reldir%/include/univalue.h - -UNIVALUE_LIB_HEADERS_INT = -UNIVALUE_LIB_HEADERS_INT += %reldir%/lib/univalue_utffilter.h -UNIVALUE_LIB_HEADERS_INT += %reldir%/lib/univalue_escapes.h +UNIVALUE_DIST_HEADERS_INT += %reldir%/include/univalue_utffilter.h +UNIVALUE_DIST_HEADERS_INT += %reldir%/include/univalue_escapes.h UNIVALUE_LIB_SOURCES_INT = UNIVALUE_LIB_SOURCES_INT += %reldir%/lib/univalue.cpp @@ -31,9 +25,6 @@ UNIVALUE_TEST_UNITESTER_INT += %reldir%/test/unitester.cpp UNIVALUE_TEST_JSON_INT = UNIVALUE_TEST_JSON_INT += %reldir%/test/test_json.cpp -UNIVALUE_TEST_NO_NUL_INT = -UNIVALUE_TEST_NO_NUL_INT += %reldir%/test/no_nul.cpp - UNIVALUE_TEST_OBJECT_INT = UNIVALUE_TEST_OBJECT_INT += %reldir%/test/object.cpp diff --git a/src/univalue/test/.gitignore b/src/univalue/test/.gitignore index 7b27cf0da2..5812c96b14 100644 --- a/src/univalue/test/.gitignore +++ b/src/univalue/test/.gitignore @@ -2,7 +2,6 @@ object unitester test_json -no_nul *.trs *.log diff --git a/src/univalue/test/no_nul.cpp b/src/univalue/test/no_nul.cpp deleted file mode 100644 index 3a7a727abb..0000000000 --- a/src/univalue/test/no_nul.cpp +++ /dev/null @@ -1,8 +0,0 @@ -#include <univalue.h> - -int main (int argc, char *argv[]) -{ - char buf[] = "___[1,2,3]___"; - UniValue val; - return val.read(buf + 3, 7) ? 0 : 1; -} diff --git a/src/univalue/test/object.cpp b/src/univalue/test/object.cpp index 8a35bf914d..94d7343ff3 100644 --- a/src/univalue/test/object.cpp +++ b/src/univalue/test/object.cpp @@ -13,9 +13,6 @@ #include <string> #include <vector> -#define BOOST_FIXTURE_TEST_SUITE(a, b) -#define BOOST_AUTO_TEST_CASE(funcName) void funcName() -#define BOOST_AUTO_TEST_SUITE_END() #define BOOST_CHECK(expr) assert(expr) #define BOOST_CHECK_EQUAL(v1, v2) assert((v1) == (v2)) #define BOOST_CHECK_THROW(stmt, excMatch) { \ @@ -35,9 +32,7 @@ } \ } -BOOST_FIXTURE_TEST_SUITE(univalue_tests, BasicTestingSetup) - -BOOST_AUTO_TEST_CASE(univalue_constructor) +void univalue_constructor() { UniValue v1; BOOST_CHECK(v1.isNull()); @@ -50,7 +45,7 @@ BOOST_AUTO_TEST_CASE(univalue_constructor) BOOST_CHECK_EQUAL(v3.getValStr(), "foo"); UniValue numTest; - BOOST_CHECK(numTest.setNumStr("82")); + numTest.setNumStr("82"); BOOST_CHECK(numTest.isNum()); BOOST_CHECK_EQUAL(numTest.getValStr(), "82"); @@ -85,36 +80,46 @@ BOOST_AUTO_TEST_CASE(univalue_constructor) BOOST_CHECK_EQUAL(v9.getValStr(), "zappa"); } -BOOST_AUTO_TEST_CASE(univalue_typecheck) +void univalue_push_throw() +{ + UniValue j; + BOOST_CHECK_THROW(j.push_back(1), std::runtime_error); + BOOST_CHECK_THROW(j.push_backV({1}), std::runtime_error); + BOOST_CHECK_THROW(j.__pushKV("k", 1), std::runtime_error); + BOOST_CHECK_THROW(j.pushKV("k", 1), std::runtime_error); + BOOST_CHECK_THROW(j.pushKVs({}), std::runtime_error); +} + +void univalue_typecheck() { UniValue v1; - BOOST_CHECK(v1.setNumStr("1")); + v1.setNumStr("1"); BOOST_CHECK(v1.isNum()); BOOST_CHECK_THROW(v1.get_bool(), std::runtime_error); { UniValue v_negative; - BOOST_CHECK(v_negative.setNumStr("-1")); + v_negative.setNumStr("-1"); BOOST_CHECK_THROW(v_negative.getInt<uint8_t>(), std::runtime_error); BOOST_CHECK_EQUAL(v_negative.getInt<int8_t>(), -1); } UniValue v2; - BOOST_CHECK(v2.setBool(true)); + v2.setBool(true); BOOST_CHECK_EQUAL(v2.get_bool(), true); BOOST_CHECK_THROW(v2.getInt<int>(), std::runtime_error); UniValue v3; - BOOST_CHECK(v3.setNumStr("32482348723847471234")); + v3.setNumStr("32482348723847471234"); BOOST_CHECK_THROW(v3.getInt<int64_t>(), std::runtime_error); - BOOST_CHECK(v3.setNumStr("1000")); + v3.setNumStr("1000"); BOOST_CHECK_EQUAL(v3.getInt<int64_t>(), 1000); UniValue v4; - BOOST_CHECK(v4.setNumStr("2147483648")); + v4.setNumStr("2147483648"); BOOST_CHECK_EQUAL(v4.getInt<int64_t>(), 2147483648); BOOST_CHECK_THROW(v4.getInt<int>(), std::runtime_error); - BOOST_CHECK(v4.setNumStr("1000")); + v4.setNumStr("1000"); BOOST_CHECK_EQUAL(v4.getInt<int>(), 1000); BOOST_CHECK_THROW(v4.get_str(), std::runtime_error); BOOST_CHECK_EQUAL(v4.get_real(), 1000); @@ -134,77 +139,77 @@ BOOST_AUTO_TEST_CASE(univalue_typecheck) BOOST_CHECK_THROW(vals[1].get_bool(), std::runtime_error); } -BOOST_AUTO_TEST_CASE(univalue_set) +void univalue_set() { UniValue v(UniValue::VSTR, "foo"); v.clear(); BOOST_CHECK(v.isNull()); BOOST_CHECK_EQUAL(v.getValStr(), ""); - BOOST_CHECK(v.setObject()); + v.setObject(); BOOST_CHECK(v.isObject()); BOOST_CHECK_EQUAL(v.size(), 0); BOOST_CHECK_EQUAL(v.getType(), UniValue::VOBJ); BOOST_CHECK(v.empty()); - BOOST_CHECK(v.setArray()); + v.setArray(); BOOST_CHECK(v.isArray()); BOOST_CHECK_EQUAL(v.size(), 0); - BOOST_CHECK(v.setStr("zum")); + v.setStr("zum"); BOOST_CHECK(v.isStr()); BOOST_CHECK_EQUAL(v.getValStr(), "zum"); - BOOST_CHECK(v.setFloat(-1.01)); + v.setFloat(-1.01); BOOST_CHECK(v.isNum()); BOOST_CHECK_EQUAL(v.getValStr(), "-1.01"); - BOOST_CHECK(v.setInt((int)1023)); + v.setInt(int{1023}); BOOST_CHECK(v.isNum()); BOOST_CHECK_EQUAL(v.getValStr(), "1023"); - BOOST_CHECK(v.setInt((int64_t)-1023LL)); + v.setInt(int64_t{-1023LL}); BOOST_CHECK(v.isNum()); BOOST_CHECK_EQUAL(v.getValStr(), "-1023"); - BOOST_CHECK(v.setInt((uint64_t)1023ULL)); + v.setInt(uint64_t{1023ULL}); BOOST_CHECK(v.isNum()); BOOST_CHECK_EQUAL(v.getValStr(), "1023"); - BOOST_CHECK(v.setNumStr("-688")); + v.setNumStr("-688"); BOOST_CHECK(v.isNum()); BOOST_CHECK_EQUAL(v.getValStr(), "-688"); - BOOST_CHECK(v.setBool(false)); + v.setBool(false); BOOST_CHECK_EQUAL(v.isBool(), true); BOOST_CHECK_EQUAL(v.isTrue(), false); BOOST_CHECK_EQUAL(v.isFalse(), true); BOOST_CHECK_EQUAL(v.getBool(), false); - BOOST_CHECK(v.setBool(true)); + v.setBool(true); BOOST_CHECK_EQUAL(v.isBool(), true); BOOST_CHECK_EQUAL(v.isTrue(), true); BOOST_CHECK_EQUAL(v.isFalse(), false); BOOST_CHECK_EQUAL(v.getBool(), true); - BOOST_CHECK(!v.setNumStr("zombocom")); + BOOST_CHECK_THROW(v.setNumStr("zombocom"), std::runtime_error); - BOOST_CHECK(v.setNull()); + v.setNull(); BOOST_CHECK(v.isNull()); } -BOOST_AUTO_TEST_CASE(univalue_array) +void univalue_array() { UniValue arr(UniValue::VARR); UniValue v((int64_t)1023LL); - BOOST_CHECK(arr.push_back(v)); + arr.push_back(v); std::string vStr("zippy"); - BOOST_CHECK(arr.push_back(vStr)); + arr.push_back(vStr); const char *s = "pippy"; - BOOST_CHECK(arr.push_back(s)); + arr.push_back(s); std::vector<UniValue> vec; v.setStr("boing"); @@ -213,13 +218,13 @@ BOOST_AUTO_TEST_CASE(univalue_array) v.setStr("going"); vec.push_back(v); - BOOST_CHECK(arr.push_backV(vec)); + arr.push_backV(vec); - BOOST_CHECK(arr.push_back((uint64_t) 400ULL)); - BOOST_CHECK(arr.push_back((int64_t) -400LL)); - BOOST_CHECK(arr.push_back((int) -401)); - BOOST_CHECK(arr.push_back(-40.1)); - BOOST_CHECK(arr.push_back(true)); + arr.push_back(uint64_t{400ULL}); + arr.push_back(int64_t{-400LL}); + arr.push_back(int{-401}); + arr.push_back(-40.1); + arr.push_back(true); BOOST_CHECK_EQUAL(arr.empty(), false); BOOST_CHECK_EQUAL(arr.size(), 10); @@ -252,7 +257,7 @@ BOOST_AUTO_TEST_CASE(univalue_array) BOOST_CHECK_EQUAL(arr.size(), 0); } -BOOST_AUTO_TEST_CASE(univalue_object) +void univalue_object() { UniValue obj(UniValue::VOBJ); std::string strKey, strVal; @@ -260,39 +265,39 @@ BOOST_AUTO_TEST_CASE(univalue_object) strKey = "age"; v.setInt(100); - BOOST_CHECK(obj.pushKV(strKey, v)); + obj.pushKV(strKey, v); strKey = "first"; strVal = "John"; - BOOST_CHECK(obj.pushKV(strKey, strVal)); + obj.pushKV(strKey, strVal); strKey = "last"; - const char *cVal = "Smith"; - BOOST_CHECK(obj.pushKV(strKey, cVal)); + const char* cVal = "Smith"; + obj.pushKV(strKey, cVal); strKey = "distance"; - BOOST_CHECK(obj.pushKV(strKey, (int64_t) 25)); + obj.pushKV(strKey, int64_t{25}); strKey = "time"; - BOOST_CHECK(obj.pushKV(strKey, (uint64_t) 3600)); + obj.pushKV(strKey, uint64_t{3600}); strKey = "calories"; - BOOST_CHECK(obj.pushKV(strKey, (int) 12)); + obj.pushKV(strKey, int{12}); strKey = "temperature"; - BOOST_CHECK(obj.pushKV(strKey, (double) 90.012)); + obj.pushKV(strKey, double{90.012}); strKey = "moon"; - BOOST_CHECK(obj.pushKV(strKey, true)); + obj.pushKV(strKey, true); strKey = "spoon"; - BOOST_CHECK(obj.pushKV(strKey, false)); + obj.pushKV(strKey, false); UniValue obj2(UniValue::VOBJ); - BOOST_CHECK(obj2.pushKV("cat1", 9000)); - BOOST_CHECK(obj2.pushKV("cat2", 12345)); + obj2.pushKV("cat1", 9000); + obj2.pushKV("cat2", 12345); - BOOST_CHECK(obj.pushKVs(obj2)); + obj.pushKVs(obj2); BOOST_CHECK_EQUAL(obj.empty(), false); BOOST_CHECK_EQUAL(obj.size(), 11); @@ -347,7 +352,7 @@ BOOST_AUTO_TEST_CASE(univalue_object) BOOST_CHECK_EQUAL(obj.size(), 0); BOOST_CHECK_EQUAL(obj.getType(), UniValue::VNULL); - BOOST_CHECK_EQUAL(obj.setObject(), true); + obj.setObject(); UniValue uv; uv.setInt(42); obj.__pushKV("age", uv); @@ -371,7 +376,7 @@ BOOST_AUTO_TEST_CASE(univalue_object) static const char *json1 = "[1.10000000,{\"key1\":\"str\\u0000\",\"key2\":800,\"key3\":{\"name\":\"martian http://test.com\"}}]"; -BOOST_AUTO_TEST_CASE(univalue_readwrite) +void univalue_readwrite() { UniValue v; BOOST_CHECK(v.read(json1)); @@ -414,11 +419,10 @@ BOOST_AUTO_TEST_CASE(univalue_readwrite) BOOST_CHECK(!v.read("{} 42")); } -BOOST_AUTO_TEST_SUITE_END() - -int main (int argc, char *argv[]) +int main(int argc, char* argv[]) { univalue_constructor(); + univalue_push_throw(); univalue_typecheck(); univalue_set(); univalue_array(); @@ -426,4 +430,3 @@ int main (int argc, char *argv[]) univalue_readwrite(); return 0; } - diff --git a/src/univalue/test/unitester.cpp b/src/univalue/test/unitester.cpp index 81b1c5d3b1..6344a5a0ab 100644 --- a/src/univalue/test/unitester.cpp +++ b/src/univalue/test/unitester.cpp @@ -12,15 +12,7 @@ #error JSON_TEST_SRC must point to test source directory #endif -#ifndef ARRAY_SIZE -#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0])) -#endif - std::string srcdir(JSON_TEST_SRC); -static bool test_failed = false; - -#define d_assert(expr) { if (!(expr)) { test_failed = true; fprintf(stderr, "%s failed\n", filename.c_str()); } } -#define f_assert(expr) { if (!(expr)) { test_failed = true; fprintf(stderr, "%s failed\n", __func__); } } static std::string rtrim(std::string s) { @@ -41,9 +33,9 @@ static void runtest(std::string filename, const std::string& jdata) bool testResult = val.read(jdata); if (wantPass) { - d_assert(testResult == true); + assert(testResult == true); } else { - d_assert(testResult == false); + assert(testResult == false); } if (wantRoundTrip) { @@ -141,30 +133,38 @@ void unescape_unicode_test() bool testResult; // Escaped ASCII (quote) testResult = val.read("[\"\\u0022\"]"); - f_assert(testResult); - f_assert(val[0].get_str() == "\""); + assert(testResult); + assert(val[0].get_str() == "\""); // Escaped Basic Plane character, two-byte UTF-8 testResult = val.read("[\"\\u0191\"]"); - f_assert(testResult); - f_assert(val[0].get_str() == "\xc6\x91"); + assert(testResult); + assert(val[0].get_str() == "\xc6\x91"); // Escaped Basic Plane character, three-byte UTF-8 testResult = val.read("[\"\\u2191\"]"); - f_assert(testResult); - f_assert(val[0].get_str() == "\xe2\x86\x91"); + assert(testResult); + assert(val[0].get_str() == "\xe2\x86\x91"); // Escaped Supplementary Plane character U+1d161 testResult = val.read("[\"\\ud834\\udd61\"]"); - f_assert(testResult); - f_assert(val[0].get_str() == "\xf0\x9d\x85\xa1"); + assert(testResult); + assert(val[0].get_str() == "\xf0\x9d\x85\xa1"); +} + +void no_nul_test() +{ + char buf[] = "___[1,2,3]___"; + UniValue val; + assert(val.read(buf + 3, 7)); } int main (int argc, char *argv[]) { - for (unsigned int fidx = 0; fidx < ARRAY_SIZE(filenames); fidx++) { - runtest_file(filenames[fidx]); + for (const auto& f: filenames) { + runtest_file(f); } unescape_unicode_test(); + no_nul_test(); - return test_failed ? 1 : 0; + return 0; } diff --git a/src/util/asmap.cpp b/src/util/asmap.cpp index ceb8379c1c..33258d9962 100644 --- a/src/util/asmap.cpp +++ b/src/util/asmap.cpp @@ -8,10 +8,13 @@ #include <crypto/common.h> #include <fs.h> #include <logging.h> +#include <serialize.h> #include <streams.h> +#include <algorithm> #include <cassert> -#include <map> +#include <cstdio> +#include <utility> #include <vector> namespace { @@ -195,7 +198,7 @@ std::vector<bool> DecodeAsmap(fs::path path) { std::vector<bool> bits; FILE *filestr = fsbridge::fopen(path, "rb"); - CAutoFile file(filestr, SER_DISK, CLIENT_VERSION); + AutoFile file{filestr}; if (file.IsNull()) { LogPrintf("Failed to open asmap file from disk\n"); return bits; diff --git a/src/util/bip32.cpp b/src/util/bip32.cpp index 4c7e948368..796af4a544 100644 --- a/src/util/bip32.cpp +++ b/src/util/bip32.cpp @@ -2,12 +2,13 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#include <sstream> -#include <stdio.h> #include <tinyformat.h> #include <util/bip32.h> #include <util/strencodings.h> +#include <cstdint> +#include <cstdio> +#include <sstream> bool ParseHDKeypath(const std::string& keypath_str, std::vector<uint32_t>& keypath) { diff --git a/src/util/bip32.h b/src/util/bip32.h index aa4eac3791..0872bc88de 100644 --- a/src/util/bip32.h +++ b/src/util/bip32.h @@ -5,6 +5,7 @@ #ifndef BITCOIN_UTIL_BIP32_H #define BITCOIN_UTIL_BIP32_H +#include <cstdint> #include <string> #include <vector> diff --git a/src/util/bitdeque.h b/src/util/bitdeque.h new file mode 100644 index 0000000000..1e34b72475 --- /dev/null +++ b/src/util/bitdeque.h @@ -0,0 +1,469 @@ +// Copyright (c) 2022 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_UTIL_BITDEQUE_H +#define BITCOIN_UTIL_BITDEQUE_H + +#include <bitset> +#include <cstddef> +#include <deque> +#include <limits> +#include <stdexcept> +#include <tuple> + +/** Class that mimics std::deque<bool>, but with std::vector<bool>'s bit packing. + * + * BlobSize selects the (minimum) number of bits that are allocated at once. + * Larger values reduce the asymptotic memory usage overhead, at the cost of + * needing larger up-front allocations. The default is 4096 bytes. + */ +template<int BlobSize = 4096 * 8> +class bitdeque +{ + // Internal definitions + using word_type = std::bitset<BlobSize>; + using deque_type = std::deque<word_type>; + static_assert(BlobSize > 0); + static constexpr int BITS_PER_WORD = BlobSize; + + // Forward and friend declarations of iterator types. + template<bool Const> class Iterator; + template<bool Const> friend class Iterator; + + /** Iterator to a bitdeque element, const or not. */ + template<bool Const> + class Iterator + { + using deque_iterator = std::conditional_t<Const, typename deque_type::const_iterator, typename deque_type::iterator>; + + deque_iterator m_it; + int m_bitpos{0}; + Iterator(const deque_iterator& it, int bitpos) : m_it(it), m_bitpos(bitpos) {} + friend class bitdeque; + + public: + using iterator_category = std::random_access_iterator_tag; + using value_type = bool; + using pointer = void; + using const_pointer = void; + using reference = std::conditional_t<Const, bool, typename word_type::reference>; + using const_reference = bool; + using difference_type = std::ptrdiff_t; + + /** Default constructor. */ + Iterator() = default; + + /** Default copy constructor. */ + Iterator(const Iterator&) = default; + + /** Conversion from non-const to const iterator. */ + template<bool ConstArg = Const, typename = std::enable_if_t<Const && ConstArg>> + Iterator(const Iterator<false>& x) : m_it(x.m_it), m_bitpos(x.m_bitpos) {} + + Iterator& operator+=(difference_type dist) + { + if (dist > 0) { + if (dist + m_bitpos >= BITS_PER_WORD) { + ++m_it; + dist -= BITS_PER_WORD - m_bitpos; + m_bitpos = 0; + } + auto jump = dist / BITS_PER_WORD; + m_it += jump; + m_bitpos += dist - jump * BITS_PER_WORD; + } else if (dist < 0) { + dist = -dist; + if (dist > m_bitpos) { + --m_it; + dist -= m_bitpos + 1; + m_bitpos = BITS_PER_WORD - 1; + } + auto jump = dist / BITS_PER_WORD; + m_it -= jump; + m_bitpos -= dist - jump * BITS_PER_WORD; + } + return *this; + } + + friend difference_type operator-(const Iterator& x, const Iterator& y) + { + return BITS_PER_WORD * (x.m_it - y.m_it) + x.m_bitpos - y.m_bitpos; + } + + Iterator& operator=(const Iterator&) = default; + Iterator& operator-=(difference_type dist) { return operator+=(-dist); } + Iterator& operator++() { ++m_bitpos; if (m_bitpos == BITS_PER_WORD) { m_bitpos = 0; ++m_it; }; return *this; } + Iterator operator++(int) { auto ret{*this}; operator++(); return ret; } + Iterator& operator--() { if (m_bitpos == 0) { m_bitpos = BITS_PER_WORD; --m_it; }; --m_bitpos; return *this; } + Iterator operator--(int) { auto ret{*this}; operator--(); return ret; } + friend Iterator operator+(Iterator x, difference_type dist) { x += dist; return x; } + friend Iterator operator+(difference_type dist, Iterator x) { x += dist; return x; } + friend Iterator operator-(Iterator x, difference_type dist) { x -= dist; return x; } + friend bool operator<(const Iterator& x, const Iterator& y) { return std::tie(x.m_it, x.m_bitpos) < std::tie(y.m_it, y.m_bitpos); } + friend bool operator>(const Iterator& x, const Iterator& y) { return std::tie(x.m_it, x.m_bitpos) > std::tie(y.m_it, y.m_bitpos); } + friend bool operator<=(const Iterator& x, const Iterator& y) { return std::tie(x.m_it, x.m_bitpos) <= std::tie(y.m_it, y.m_bitpos); } + friend bool operator>=(const Iterator& x, const Iterator& y) { return std::tie(x.m_it, x.m_bitpos) >= std::tie(y.m_it, y.m_bitpos); } + friend bool operator==(const Iterator& x, const Iterator& y) { return x.m_it == y.m_it && x.m_bitpos == y.m_bitpos; } + friend bool operator!=(const Iterator& x, const Iterator& y) { return x.m_it != y.m_it || x.m_bitpos != y.m_bitpos; } + reference operator*() const { return (*m_it)[m_bitpos]; } + reference operator[](difference_type pos) const { return *(*this + pos); } + }; + +public: + using value_type = bool; + using size_type = std::size_t; + using difference_type = typename deque_type::difference_type; + using reference = typename word_type::reference; + using const_reference = bool; + using iterator = Iterator<false>; + using const_iterator = Iterator<true>; + using pointer = void; + using const_pointer = void; + using reverse_iterator = std::reverse_iterator<iterator>; + using const_reverse_iterator = std::reverse_iterator<const_iterator>; + +private: + /** Deque of bitsets storing the actual bit data. */ + deque_type m_deque; + + /** Number of unused bits at the front of m_deque.front(). */ + int m_pad_begin; + + /** Number of unused bits at the back of m_deque.back(). */ + int m_pad_end; + + /** Shrink the container by n bits, removing from the end. */ + void erase_back(size_type n) + { + if (n >= static_cast<size_type>(BITS_PER_WORD - m_pad_end)) { + n -= BITS_PER_WORD - m_pad_end; + m_pad_end = 0; + m_deque.erase(m_deque.end() - 1 - (n / BITS_PER_WORD), m_deque.end()); + n %= BITS_PER_WORD; + } + if (n) { + auto& last = m_deque.back(); + while (n) { + last.reset(BITS_PER_WORD - 1 - m_pad_end); + ++m_pad_end; + --n; + } + } + } + + /** Extend the container by n bits, adding at the end. */ + void extend_back(size_type n) + { + if (n > static_cast<size_type>(m_pad_end)) { + n -= m_pad_end + 1; + m_pad_end = BITS_PER_WORD - 1; + m_deque.insert(m_deque.end(), 1 + (n / BITS_PER_WORD), {}); + n %= BITS_PER_WORD; + } + m_pad_end -= n; + } + + /** Shrink the container by n bits, removing from the beginning. */ + void erase_front(size_type n) + { + if (n >= static_cast<size_type>(BITS_PER_WORD - m_pad_begin)) { + n -= BITS_PER_WORD - m_pad_begin; + m_pad_begin = 0; + m_deque.erase(m_deque.begin(), m_deque.begin() + 1 + (n / BITS_PER_WORD)); + n %= BITS_PER_WORD; + } + if (n) { + auto& first = m_deque.front(); + while (n) { + first.reset(m_pad_begin); + ++m_pad_begin; + --n; + } + } + } + + /** Extend the container by n bits, adding at the beginning. */ + void extend_front(size_type n) + { + if (n > static_cast<size_type>(m_pad_begin)) { + n -= m_pad_begin + 1; + m_pad_begin = BITS_PER_WORD - 1; + m_deque.insert(m_deque.begin(), 1 + (n / BITS_PER_WORD), {}); + n %= BITS_PER_WORD; + } + m_pad_begin -= n; + } + + /** Insert a sequence of falses anywhere in the container. */ + void insert_zeroes(size_type before, size_type count) + { + size_type after = size() - before; + if (before < after) { + extend_front(count); + std::move(begin() + count, begin() + count + before, begin()); + } else { + extend_back(count); + std::move_backward(begin() + before, begin() + before + after, end()); + } + } + +public: + /** Construct an empty container. */ + explicit bitdeque() : m_pad_begin{0}, m_pad_end{0} {} + + /** Set the container equal to count times the value of val. */ + void assign(size_type count, bool val) + { + m_deque.clear(); + m_deque.resize((count + BITS_PER_WORD - 1) / BITS_PER_WORD); + m_pad_begin = 0; + m_pad_end = 0; + if (val) { + for (auto& elem : m_deque) elem.flip(); + } + if (count % BITS_PER_WORD) { + erase_back(BITS_PER_WORD - (count % BITS_PER_WORD)); + } + } + + /** Construct a container containing count times the value of val. */ + bitdeque(size_type count, bool val) { assign(count, val); } + + /** Construct a container containing count false values. */ + explicit bitdeque(size_t count) { assign(count, false); } + + /** Copy constructor. */ + bitdeque(const bitdeque&) = default; + + /** Move constructor. */ + bitdeque(bitdeque&&) noexcept = default; + + /** Copy assignment operator. */ + bitdeque& operator=(const bitdeque& other) = default; + + /** Move assignment operator. */ + bitdeque& operator=(bitdeque&& other) noexcept = default; + + // Iterator functions. + iterator begin() noexcept { return {m_deque.begin(), m_pad_begin}; } + iterator end() noexcept { return iterator{m_deque.end(), 0} - m_pad_end; } + const_iterator begin() const noexcept { return const_iterator{m_deque.cbegin(), m_pad_begin}; } + const_iterator cbegin() const noexcept { return const_iterator{m_deque.cbegin(), m_pad_begin}; } + const_iterator end() const noexcept { return const_iterator{m_deque.cend(), 0} - m_pad_end; } + const_iterator cend() const noexcept { return const_iterator{m_deque.cend(), 0} - m_pad_end; } + reverse_iterator rbegin() noexcept { return reverse_iterator{end()}; } + reverse_iterator rend() noexcept { return reverse_iterator{begin()}; } + const_reverse_iterator rbegin() const noexcept { return const_reverse_iterator{cend()}; } + const_reverse_iterator crbegin() const noexcept { return const_reverse_iterator{cend()}; } + const_reverse_iterator rend() const noexcept { return const_reverse_iterator{cbegin()}; } + const_reverse_iterator crend() const noexcept { return const_reverse_iterator{cbegin()}; } + + /** Count the number of bits in the container. */ + size_type size() const noexcept { return m_deque.size() * BITS_PER_WORD - m_pad_begin - m_pad_end; } + + /** Determine whether the container is empty. */ + bool empty() const noexcept + { + return m_deque.size() == 0 || (m_deque.size() == 1 && (m_pad_begin + m_pad_end == BITS_PER_WORD)); + } + + /** Return the maximum size of the container. */ + size_type max_size() const noexcept + { + if (m_deque.max_size() < std::numeric_limits<difference_type>::max() / BITS_PER_WORD) { + return m_deque.max_size() * BITS_PER_WORD; + } else { + return std::numeric_limits<difference_type>::max(); + } + } + + /** Set the container equal to the bits in [first,last). */ + template<typename It> + void assign(It first, It last) + { + size_type count = std::distance(first, last); + assign(count, false); + auto it = begin(); + while (first != last) { + *(it++) = *(first++); + } + } + + /** Set the container equal to the bits in ilist. */ + void assign(std::initializer_list<bool> ilist) + { + assign(ilist.size(), false); + auto it = begin(); + auto init = ilist.begin(); + while (init != ilist.end()) { + *(it++) = *(init++); + } + } + + /** Set the container equal to the bits in ilist. */ + bitdeque& operator=(std::initializer_list<bool> ilist) + { + assign(ilist); + return *this; + } + + /** Construct a container containing the bits in [first,last). */ + template<typename It> + bitdeque(It first, It last) { assign(first, last); } + + /** Construct a container containing the bits in ilist. */ + bitdeque(std::initializer_list<bool> ilist) { assign(ilist); } + + // Access an element of the container, with bounds checking. + reference at(size_type position) + { + if (position >= size()) throw std::out_of_range("bitdeque::at() out of range"); + return begin()[position]; + } + const_reference at(size_type position) const + { + if (position >= size()) throw std::out_of_range("bitdeque::at() out of range"); + return cbegin()[position]; + } + + // Access elements of the container without bounds checking. + reference operator[](size_type position) { return begin()[position]; } + const_reference operator[](size_type position) const { return cbegin()[position]; } + reference front() { return *begin(); } + const_reference front() const { return *cbegin(); } + reference back() { return end()[-1]; } + const_reference back() const { return cend()[-1]; } + + /** Release unused memory. */ + void shrink_to_fit() + { + m_deque.shrink_to_fit(); + } + + /** Empty the container. */ + void clear() noexcept + { + m_deque.clear(); + m_pad_begin = m_pad_end = 0; + } + + // Append an element to the container. + void push_back(bool val) + { + extend_back(1); + back() = val; + } + reference emplace_back(bool val) + { + extend_back(1); + auto ref = back(); + ref = val; + return ref; + } + + // Prepend an element to the container. + void push_front(bool val) + { + extend_front(1); + front() = val; + } + reference emplace_front(bool val) + { + extend_front(1); + auto ref = front(); + ref = val; + return ref; + } + + // Remove the last element from the container. + void pop_back() + { + erase_back(1); + } + + // Remove the first element from the container. + void pop_front() + { + erase_front(1); + } + + /** Resize the container. */ + void resize(size_type n) + { + if (n < size()) { + erase_back(size() - n); + } else { + extend_back(n - size()); + } + } + + // Swap two containers. + void swap(bitdeque& other) noexcept + { + std::swap(m_deque, other.m_deque); + std::swap(m_pad_begin, other.m_pad_begin); + std::swap(m_pad_end, other.m_pad_end); + } + friend void swap(bitdeque& b1, bitdeque& b2) noexcept { b1.swap(b2); } + + // Erase elements from the container. + iterator erase(const_iterator first, const_iterator last) + { + size_type before = std::distance(cbegin(), first); + size_type dist = std::distance(first, last); + size_type after = std::distance(last, cend()); + if (before < after) { + std::move_backward(begin(), begin() + before, end() - after); + erase_front(dist); + return begin() + before; + } else { + std::move(end() - after, end(), begin() + before); + erase_back(dist); + return end() - after; + } + } + + iterator erase(iterator first, iterator last) { return erase(const_iterator{first}, const_iterator{last}); } + iterator erase(const_iterator pos) { return erase(pos, pos + 1); } + iterator erase(iterator pos) { return erase(const_iterator{pos}, const_iterator{pos} + 1); } + + // Insert elements into the container. + iterator insert(const_iterator pos, bool val) + { + size_type before = pos - cbegin(); + insert_zeroes(before, 1); + auto it = begin() + before; + *it = val; + return it; + } + + iterator emplace(const_iterator pos, bool val) { return insert(pos, val); } + + iterator insert(const_iterator pos, size_type count, bool val) + { + size_type before = pos - cbegin(); + insert_zeroes(before, count); + auto it_begin = begin() + before; + auto it = it_begin; + auto it_end = it + count; + while (it != it_end) *(it++) = val; + return it_begin; + } + + template<typename It> + iterator insert(const_iterator pos, It first, It last) + { + size_type before = pos - cbegin(); + size_type count = std::distance(first, last); + insert_zeroes(before, count); + auto it_begin = begin() + before; + auto it = it_begin; + while (first != last) { + *(it++) = *(first++); + } + return it_begin; + } +}; + +#endif // BITCOIN_UTIL_BITDEQUE_H diff --git a/src/util/bytevectorhash.cpp b/src/util/bytevectorhash.cpp index 9054db4759..6d777613e6 100644 --- a/src/util/bytevectorhash.cpp +++ b/src/util/bytevectorhash.cpp @@ -6,6 +6,8 @@ #include <random.h> #include <util/bytevectorhash.h> +#include <vector> + ByteVectorHash::ByteVectorHash() : m_k0(GetRand<uint64_t>()), m_k1(GetRand<uint64_t>()) diff --git a/src/util/bytevectorhash.h b/src/util/bytevectorhash.h index b88c17460b..c2322b8daf 100644 --- a/src/util/bytevectorhash.h +++ b/src/util/bytevectorhash.h @@ -5,7 +5,8 @@ #ifndef BITCOIN_UTIL_BYTEVECTORHASH_H #define BITCOIN_UTIL_BYTEVECTORHASH_H -#include <stdint.h> +#include <cstdint> +#include <cstddef> #include <vector> /** diff --git a/src/util/designator.h b/src/util/designator.h deleted file mode 100644 index 3670b11e00..0000000000 --- a/src/util/designator.h +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright (c) 2022 The Bitcoin Core developers -// Distributed under the MIT software license, see the accompanying -// file COPYING or http://www.opensource.org/licenses/mit-license.php. - -#ifndef BITCOIN_UTIL_DESIGNATOR_H -#define BITCOIN_UTIL_DESIGNATOR_H - -/** - * Designated initializers can be used to avoid ordering mishaps in aggregate - * initialization. However, they do not prevent uninitialized members. The - * checks can be disabled by defining DISABLE_DESIGNATED_INITIALIZER_ERRORS. - * This should only be needed on MSVC 2019. MSVC 2022 supports them with the - * option "/std:c++20" - */ -#ifndef DISABLE_DESIGNATED_INITIALIZER_ERRORS -#define Desig(field_name) .field_name = -#else -#define Desig(field_name) -#endif - -#endif // BITCOIN_UTIL_DESIGNATOR_H diff --git a/src/util/error.cpp b/src/util/error.cpp index af8cbd0353..33a35a6d59 100644 --- a/src/util/error.cpp +++ b/src/util/error.cpp @@ -5,9 +5,11 @@ #include <util/error.h> #include <tinyformat.h> -#include <util/system.h> #include <util/translation.h> +#include <cassert> +#include <string> + bilingual_str TransactionErrorString(const TransactionError err) { switch (err) { @@ -35,6 +37,8 @@ bilingual_str TransactionErrorString(const TransactionError err) return Untranslated("External signer not found"); case TransactionError::EXTERNAL_SIGNER_FAILED: return Untranslated("External signer failed to sign"); + case TransactionError::INVALID_PACKAGE: + return Untranslated("Transaction rejected due to invalid package"); // no default case, so the compiler can warn about missing cases } assert(false); diff --git a/src/util/error.h b/src/util/error.h index 4cc35eb1fd..0429de651a 100644 --- a/src/util/error.h +++ b/src/util/error.h @@ -32,6 +32,7 @@ enum class TransactionError { MAX_FEE_EXCEEDED, EXTERNAL_SIGNER_NOT_FOUND, EXTERNAL_SIGNER_FAILED, + INVALID_PACKAGE, }; bilingual_str TransactionErrorString(const TransactionError error); diff --git a/src/util/hasher.cpp b/src/util/hasher.cpp index c21941eb88..a80f20c894 100644 --- a/src/util/hasher.cpp +++ b/src/util/hasher.cpp @@ -2,11 +2,11 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. +#include <crypto/siphash.h> #include <random.h> +#include <span.h> #include <util/hasher.h> -#include <limits> - SaltedTxidHasher::SaltedTxidHasher() : k0(GetRand<uint64_t>()), k1(GetRand<uint64_t>()) {} SaltedOutpointHasher::SaltedOutpointHasher() : k0(GetRand<uint64_t>()), k1(GetRand<uint64_t>()) {} diff --git a/src/util/hasher.h b/src/util/hasher.h index 3d24a4d23c..426b8990e6 100644 --- a/src/util/hasher.h +++ b/src/util/hasher.h @@ -5,10 +5,16 @@ #ifndef BITCOIN_UTIL_HASHER_H #define BITCOIN_UTIL_HASHER_H +#include <crypto/common.h> #include <crypto/siphash.h> #include <primitives/transaction.h> #include <uint256.h> +#include <cstdint> +#include <cstring> + +template <typename C> class Span; + class SaltedTxidHasher { private: diff --git a/src/util/message.cpp b/src/util/message.cpp index f58876f915..7d6f3403f4 100644 --- a/src/util/message.cpp +++ b/src/util/message.cpp @@ -3,16 +3,19 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#include <hash.h> // For CHashWriter -#include <key.h> // For CKey -#include <key_io.h> // For DecodeDestination() -#include <pubkey.h> // For CPubKey -#include <script/standard.h> // For CTxDestination, IsValidDestination(), PKHash -#include <serialize.h> // For SER_GETHASH +#include <hash.h> +#include <key.h> +#include <key_io.h> +#include <pubkey.h> +#include <script/standard.h> +#include <uint256.h> #include <util/message.h> -#include <util/strencodings.h> // For DecodeBase64() +#include <util/strencodings.h> +#include <cassert> +#include <optional> #include <string> +#include <variant> #include <vector> /** @@ -70,7 +73,7 @@ bool MessageSign( uint256 MessageHash(const std::string& message) { - CHashWriter hasher(SER_GETHASH, 0); + HashWriter hasher{}; hasher << MESSAGE_MAGIC << message; return hasher.GetHash(); diff --git a/src/util/message.h b/src/util/message.h index b31c5f5761..1b7febe60a 100644 --- a/src/util/message.h +++ b/src/util/message.h @@ -6,11 +6,12 @@ #ifndef BITCOIN_UTIL_MESSAGE_H #define BITCOIN_UTIL_MESSAGE_H -#include <key.h> // For CKey #include <uint256.h> #include <string> +class CKey; + extern const std::string MESSAGE_MAGIC; /** The result of a signed message verification. diff --git a/src/util/moneystr.cpp b/src/util/moneystr.cpp index 8c4bc6e6f4..d9e6cef600 100644 --- a/src/util/moneystr.cpp +++ b/src/util/moneystr.cpp @@ -10,6 +10,7 @@ #include <util/strencodings.h> #include <util/string.h> +#include <cstdint> #include <optional> std::string FormatMoney(const CAmount n) diff --git a/src/util/readwritefile.cpp b/src/util/readwritefile.cpp index 628e6a3980..3ec08119e7 100644 --- a/src/util/readwritefile.cpp +++ b/src/util/readwritefile.cpp @@ -5,8 +5,9 @@ #include <fs.h> +#include <algorithm> +#include <cstdio> #include <limits> -#include <stdio.h> #include <string> #include <utility> diff --git a/src/util/result.h b/src/util/result.h new file mode 100644 index 0000000000..972b1aada0 --- /dev/null +++ b/src/util/result.h @@ -0,0 +1,84 @@ +// Copyright (c) 2022 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or https://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_UTIL_RESULT_H +#define BITCOIN_UTIL_RESULT_H + +#include <attributes.h> +#include <util/translation.h> + +#include <variant> + +namespace util { + +struct Error { + bilingual_str message; +}; + +//! The util::Result class provides a standard way for functions to return +//! either error messages or result values. +//! +//! It is intended for high-level functions that need to report error strings to +//! end users. Lower-level functions that don't need this error-reporting and +//! only need error-handling should avoid util::Result and instead use standard +//! classes like std::optional, std::variant, and std::tuple, or custom structs +//! and enum types to return function results. +//! +//! Usage examples can be found in \example ../test/result_tests.cpp, but in +//! general code returning `util::Result<T>` values is very similar to code +//! returning `std::optional<T>` values. Existing functions returning +//! `std::optional<T>` can be updated to return `util::Result<T>` and return +//! error strings usually just replacing `return std::nullopt;` with `return +//! util::Error{error_string};`. +template <class T> +class Result +{ +private: + std::variant<bilingual_str, T> m_variant; + + template <typename FT> + friend bilingual_str ErrorString(const Result<FT>& result); + +public: + Result(T obj) : m_variant{std::in_place_index_t<1>{}, std::move(obj)} {} + Result(Error error) : m_variant{std::in_place_index_t<0>{}, std::move(error.message)} {} + + //! std::optional methods, so functions returning optional<T> can change to + //! return Result<T> with minimal changes to existing code, and vice versa. + bool has_value() const noexcept { return m_variant.index() == 1; } + const T& value() const LIFETIMEBOUND + { + assert(has_value()); + return std::get<1>(m_variant); + } + T& value() LIFETIMEBOUND + { + assert(has_value()); + return std::get<1>(m_variant); + } + template <class U> + T value_or(U&& default_value) const& + { + return has_value() ? value() : std::forward<U>(default_value); + } + template <class U> + T value_or(U&& default_value) && + { + return has_value() ? std::move(value()) : std::forward<U>(default_value); + } + explicit operator bool() const noexcept { return has_value(); } + const T* operator->() const LIFETIMEBOUND { return &value(); } + const T& operator*() const LIFETIMEBOUND { return value(); } + T* operator->() LIFETIMEBOUND { return &value(); } + T& operator*() LIFETIMEBOUND { return value(); } +}; + +template <typename T> +bilingual_str ErrorString(const Result<T>& result) +{ + return result ? bilingual_str{} : std::get<0>(result.m_variant); +} +} // namespace util + +#endif // BITCOIN_UTIL_RESULT_H diff --git a/src/util/serfloat.h b/src/util/serfloat.h index 4d912b0176..343ccb9d3a 100644 --- a/src/util/serfloat.h +++ b/src/util/serfloat.h @@ -5,7 +5,7 @@ #ifndef BITCOIN_UTIL_SERFLOAT_H #define BITCOIN_UTIL_SERFLOAT_H -#include <stdint.h> +#include <cstdint> /* Encode a double using the IEEE 754 binary64 format. All NaNs are encoded as x86/ARM's * positive quiet NaN with payload 0. */ diff --git a/src/util/sock.cpp b/src/util/sock.cpp index 3579af4458..125dbc7f18 100644 --- a/src/util/sock.cpp +++ b/src/util/sock.cpp @@ -2,7 +2,7 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#include <compat.h> +#include <compat/compat.h> #include <logging.h> #include <threadinterrupt.h> #include <tinyformat.h> @@ -39,11 +39,11 @@ Sock::Sock(Sock&& other) other.m_socket = INVALID_SOCKET; } -Sock::~Sock() { Reset(); } +Sock::~Sock() { Close(); } Sock& Sock::operator=(Sock&& other) { - Reset(); + Close(); m_socket = other.m_socket; other.m_socket = INVALID_SOCKET; return *this; @@ -51,15 +51,6 @@ Sock& Sock::operator=(Sock&& other) SOCKET Sock::Get() const { return m_socket; } -SOCKET Sock::Release() -{ - const SOCKET s = m_socket; - m_socket = INVALID_SOCKET; - return s; -} - -void Sock::Reset() { CloseSocket(m_socket); } - ssize_t Sock::Send(const void* data, size_t len, int flags) const { return send(m_socket, static_cast<const char*>(data), len, flags); @@ -75,6 +66,16 @@ int Sock::Connect(const sockaddr* addr, socklen_t addr_len) const return connect(m_socket, addr, addr_len); } +int Sock::Bind(const sockaddr* addr, socklen_t addr_len) const +{ + return bind(m_socket, addr, addr_len); +} + +int Sock::Listen(int backlog) const +{ + return listen(m_socket, backlog); +} + std::unique_ptr<Sock> Sock::Accept(sockaddr* addr, socklen_t* addr_len) const { #ifdef WIN32 @@ -111,65 +112,110 @@ int Sock::SetSockOpt(int level, int opt_name, const void* opt_val, socklen_t opt return setsockopt(m_socket, level, opt_name, static_cast<const char*>(opt_val), opt_len); } +int Sock::GetSockName(sockaddr* name, socklen_t* name_len) const +{ + return getsockname(m_socket, name, name_len); +} + bool Sock::Wait(std::chrono::milliseconds timeout, Event requested, Event* occurred) const { -#ifdef USE_POLL - pollfd fd; - fd.fd = m_socket; - fd.events = 0; - if (requested & RECV) { - fd.events |= POLLIN; - } - if (requested & SEND) { - fd.events |= POLLOUT; - } + // We need a `shared_ptr` owning `this` for `WaitMany()`, but don't want + // `this` to be destroyed when the `shared_ptr` goes out of scope at the + // end of this function. Create it with a custom noop deleter. + std::shared_ptr<const Sock> shared{this, [](const Sock*) {}}; - if (poll(&fd, 1, count_milliseconds(timeout)) == SOCKET_ERROR) { + EventsPerSock events_per_sock{std::make_pair(shared, Events{requested})}; + + if (!WaitMany(timeout, events_per_sock)) { return false; } if (occurred != nullptr) { - *occurred = 0; - if (fd.revents & POLLIN) { - *occurred |= RECV; + *occurred = events_per_sock.begin()->second.occurred; + } + + return true; +} + +bool Sock::WaitMany(std::chrono::milliseconds timeout, EventsPerSock& events_per_sock) const +{ +#ifdef USE_POLL + std::vector<pollfd> pfds; + for (const auto& [sock, events] : events_per_sock) { + pfds.emplace_back(); + auto& pfd = pfds.back(); + pfd.fd = sock->m_socket; + if (events.requested & RECV) { + pfd.events |= POLLIN; } - if (fd.revents & POLLOUT) { - *occurred |= SEND; + if (events.requested & SEND) { + pfd.events |= POLLOUT; } } - return true; -#else - if (!IsSelectableSocket(m_socket)) { + if (poll(pfds.data(), pfds.size(), count_milliseconds(timeout)) == SOCKET_ERROR) { return false; } - fd_set fdset_recv; - fd_set fdset_send; - FD_ZERO(&fdset_recv); - FD_ZERO(&fdset_send); - - if (requested & RECV) { - FD_SET(m_socket, &fdset_recv); + assert(pfds.size() == events_per_sock.size()); + size_t i{0}; + for (auto& [sock, events] : events_per_sock) { + assert(sock->m_socket == static_cast<SOCKET>(pfds[i].fd)); + events.occurred = 0; + if (pfds[i].revents & POLLIN) { + events.occurred |= RECV; + } + if (pfds[i].revents & POLLOUT) { + events.occurred |= SEND; + } + if (pfds[i].revents & (POLLERR | POLLHUP)) { + events.occurred |= ERR; + } + ++i; } - if (requested & SEND) { - FD_SET(m_socket, &fdset_send); + return true; +#else + fd_set recv; + fd_set send; + fd_set err; + FD_ZERO(&recv); + FD_ZERO(&send); + FD_ZERO(&err); + SOCKET socket_max{0}; + + for (const auto& [sock, events] : events_per_sock) { + const auto& s = sock->m_socket; + if (!IsSelectableSocket(s)) { + return false; + } + if (events.requested & RECV) { + FD_SET(s, &recv); + } + if (events.requested & SEND) { + FD_SET(s, &send); + } + FD_SET(s, &err); + socket_max = std::max(socket_max, s); } - timeval timeout_struct = MillisToTimeval(timeout); + timeval tv = MillisToTimeval(timeout); - if (select(m_socket + 1, &fdset_recv, &fdset_send, nullptr, &timeout_struct) == SOCKET_ERROR) { + if (select(socket_max + 1, &recv, &send, &err, &tv) == SOCKET_ERROR) { return false; } - if (occurred != nullptr) { - *occurred = 0; - if (FD_ISSET(m_socket, &fdset_recv)) { - *occurred |= RECV; + for (auto& [sock, events] : events_per_sock) { + const auto& s = sock->m_socket; + events.occurred = 0; + if (FD_ISSET(s, &recv)) { + events.occurred |= RECV; } - if (FD_ISSET(m_socket, &fdset_send)) { - *occurred |= SEND; + if (FD_ISSET(s, &send)) { + events.occurred |= SEND; + } + if (FD_ISSET(s, &err)) { + events.occurred |= ERR; } } @@ -326,6 +372,22 @@ bool Sock::IsConnected(std::string& errmsg) const } } +void Sock::Close() +{ + if (m_socket == INVALID_SOCKET) { + return; + } +#ifdef WIN32 + int ret = closesocket(m_socket); +#else + int ret = close(m_socket); +#endif + if (ret) { + LogPrintf("Error closing socket %d: %s\n", m_socket, NetworkErrorString(WSAGetLastError())); + } + m_socket = INVALID_SOCKET; +} + #ifdef WIN32 std::string NetworkErrorString(int err) { @@ -349,19 +411,3 @@ std::string NetworkErrorString(int err) return SysErrorString(err); } #endif - -bool CloseSocket(SOCKET& hSocket) -{ - if (hSocket == INVALID_SOCKET) - return false; -#ifdef WIN32 - int ret = closesocket(hSocket); -#else - int ret = close(hSocket); -#endif - if (ret) { - LogPrintf("Socket close failed: %d. Error: %s\n", hSocket, NetworkErrorString(WSAGetLastError())); - } - hSocket = INVALID_SOCKET; - return ret != SOCKET_ERROR; -} diff --git a/src/util/sock.h b/src/util/sock.h index dd2913a66c..38a7dc80d6 100644 --- a/src/util/sock.h +++ b/src/util/sock.h @@ -5,13 +5,14 @@ #ifndef BITCOIN_UTIL_SOCK_H #define BITCOIN_UTIL_SOCK_H -#include <compat.h> +#include <compat/compat.h> #include <threadinterrupt.h> #include <util/time.h> #include <chrono> #include <memory> #include <string> +#include <unordered_map> /** * Maximum time to wait for I/O readiness. @@ -68,18 +69,6 @@ public: [[nodiscard]] virtual SOCKET Get() const; /** - * Get the value of the contained socket and drop ownership. It will not be closed by the - * destructor after this call. - * @return socket or INVALID_SOCKET if empty - */ - virtual SOCKET Release(); - - /** - * Close if non-empty. - */ - virtual void Reset(); - - /** * send(2) wrapper. Equivalent to `send(this->Get(), data, len, flags);`. Code that uses this * wrapper can be unit tested if this method is overridden by a mock Sock implementation. */ @@ -98,6 +87,18 @@ public: [[nodiscard]] virtual int Connect(const sockaddr* addr, socklen_t addr_len) const; /** + * bind(2) wrapper. Equivalent to `bind(this->Get(), addr, addr_len)`. Code that uses this + * wrapper can be unit tested if this method is overridden by a mock Sock implementation. + */ + [[nodiscard]] virtual int Bind(const sockaddr* addr, socklen_t addr_len) const; + + /** + * listen(2) wrapper. Equivalent to `listen(this->Get(), backlog)`. Code that uses this + * wrapper can be unit tested if this method is overridden by a mock Sock implementation. + */ + [[nodiscard]] virtual int Listen(int backlog) const; + + /** * accept(2) wrapper. Equivalent to `std::make_unique<Sock>(accept(this->Get(), addr, addr_len))`. * Code that uses this wrapper can be unit tested if this method is overridden by a mock Sock * implementation. @@ -125,31 +126,96 @@ public: const void* opt_val, socklen_t opt_len) const; + /** + * getsockname(2) wrapper. Equivalent to + * `getsockname(this->Get(), name, name_len)`. Code that uses this + * wrapper can be unit tested if this method is overridden by a mock Sock implementation. + */ + [[nodiscard]] virtual int GetSockName(sockaddr* name, socklen_t* name_len) const; + using Event = uint8_t; /** * If passed to `Wait()`, then it will wait for readiness to read from the socket. */ - static constexpr Event RECV = 0b01; + static constexpr Event RECV = 0b001; /** * If passed to `Wait()`, then it will wait for readiness to send to the socket. */ - static constexpr Event SEND = 0b10; + static constexpr Event SEND = 0b010; + + /** + * Ignored if passed to `Wait()`, but could be set in the occurred events if an + * exceptional condition has occurred on the socket or if it has been disconnected. + */ + static constexpr Event ERR = 0b100; /** * 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 + * @param[out] occurred If not nullptr and the function returns `true`, then this + * indicates which of the requested events occurred (`ERR` will be added, even if + * not requested, if an exceptional event occurs on the socket). + * A timeout is indicated by return value of `true` and `occurred` being set to 0. + * @return true on success (or timeout, if `occurred` of 0 is returned), false otherwise */ [[nodiscard]] virtual bool Wait(std::chrono::milliseconds timeout, Event requested, Event* occurred = nullptr) const; + /** + * Auxiliary requested/occurred events to wait for in `WaitMany()`. + */ + struct Events { + explicit Events(Event req) : requested{req}, occurred{0} {} + Event requested; + Event occurred; + }; + + struct HashSharedPtrSock { + size_t operator()(const std::shared_ptr<const Sock>& s) const + { + return s ? s->m_socket : std::numeric_limits<SOCKET>::max(); + } + }; + + struct EqualSharedPtrSock { + bool operator()(const std::shared_ptr<const Sock>& lhs, + const std::shared_ptr<const Sock>& rhs) const + { + if (lhs && rhs) { + return lhs->m_socket == rhs->m_socket; + } + if (!lhs && !rhs) { + return true; + } + return false; + } + }; + + /** + * On which socket to wait for what events in `WaitMany()`. + * The `shared_ptr` is copied into the map to ensure that the `Sock` object + * is not destroyed (its destructor would close the underlying socket). + * If this happens shortly before or after we call `poll(2)` and a new + * socket gets created under the same file descriptor number then the report + * from `WaitMany()` will be bogus. + */ + using EventsPerSock = std::unordered_map<std::shared_ptr<const Sock>, Events, HashSharedPtrSock, EqualSharedPtrSock>; + + /** + * Same as `Wait()`, but wait on many sockets within the same timeout. + * @param[in] timeout Wait this long for at least one of the requested events to occur. + * @param[in,out] events_per_sock Wait for the requested events on these sockets and set + * `occurred` for the events that actually occurred. + * @return true on success (or timeout, if all `what[].occurred` are returned as 0), + * false otherwise + */ + [[nodiscard]] virtual bool WaitMany(std::chrono::milliseconds timeout, + EventsPerSock& events_per_sock) const; + /* Higher level, convenience, methods. These may throw. */ /** @@ -193,12 +259,15 @@ protected: * Contained socket. `INVALID_SOCKET` designates the object is empty. */ SOCKET m_socket; + +private: + /** + * Close `m_socket` if it is not `INVALID_SOCKET`. + */ + void Close(); }; /** Return readable error string for a network error code */ std::string NetworkErrorString(int err); -/** Close socket and set hSocket to INVALID_SOCKET */ -bool CloseSocket(SOCKET& hSocket); - #endif // BITCOIN_UTIL_SOCK_H diff --git a/src/util/spanparsing.cpp b/src/util/spanparsing.cpp index 8614bd1176..565c867e18 100644 --- a/src/util/spanparsing.cpp +++ b/src/util/spanparsing.cpp @@ -6,8 +6,9 @@ #include <span.h> +#include <algorithm> +#include <cstddef> #include <string> -#include <vector> namespace spanparsing { diff --git a/src/util/strencodings.cpp b/src/util/strencodings.cpp index 675fe7d2d7..b5ac151374 100644 --- a/src/util/strencodings.cpp +++ b/src/util/strencodings.cpp @@ -3,17 +3,17 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. +#include <span.h> #include <util/strencodings.h> -#include <util/string.h> -#include <tinyformat.h> - -#include <algorithm> #include <array> -#include <cstdlib> +#include <cassert> #include <cstring> #include <limits> #include <optional> +#include <ostream> +#include <string> +#include <vector> static const std::string CHARS_ALPHA_NUM = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; diff --git a/src/util/strencodings.h b/src/util/strencodings.h index 9a96bbe67b..14867b21b2 100644 --- a/src/util/strencodings.h +++ b/src/util/strencodings.h @@ -13,11 +13,14 @@ #include <util/string.h> #include <charconv> +#include <cstddef> #include <cstdint> -#include <iterator> #include <limits> #include <optional> #include <string> +#include <string_view> +#include <system_error> +#include <type_traits> #include <vector> /** Used by SanitizeString() */ diff --git a/src/util/string.cpp b/src/util/string.cpp index d05222e8b8..e994c85f1c 100644 --- a/src/util/string.cpp +++ b/src/util/string.cpp @@ -4,9 +4,11 @@ #include <util/string.h> -#include <boost/algorithm/string/replace.hpp> +#include <regex> +#include <string> -void ReplaceAll(std::string& in_out, std::string_view search, std::string_view substitute) +void ReplaceAll(std::string& in_out, const std::string& search, const std::string& substitute) { - boost::replace_all(in_out, search, substitute); + if (search.empty()) return; + in_out = std::regex_replace(in_out, std::regex(search), substitute); } diff --git a/src/util/string.h b/src/util/string.h index df20e34ae9..9b4c9a7e28 100644 --- a/src/util/string.h +++ b/src/util/string.h @@ -7,7 +7,6 @@ #include <util/spanparsing.h> -#include <algorithm> #include <array> #include <cstdint> #include <cstring> @@ -17,7 +16,7 @@ #include <string_view> #include <vector> -void ReplaceAll(std::string& in_out, std::string_view search, std::string_view substitute); +void ReplaceAll(std::string& in_out, const std::string& search, const std::string& substitute); [[nodiscard]] inline std::vector<std::string> SplitString(std::string_view str, char sep) { @@ -58,34 +57,30 @@ void ReplaceAll(std::string& in_out, std::string_view search, std::string_view s } /** - * Join a list of items + * Join all container items. Typically used to concatenate strings but accepts + * containers with elements of any type. * - * @param list The list to join - * @param separator The separator - * @param unary_op Apply this operator to each item in the list + * @param container The items to join + * @param separator The separator + * @param unary_op Apply this operator to each item */ -template <typename T, typename BaseType, typename UnaryOp> -auto Join(const std::vector<T>& list, const BaseType& separator, UnaryOp unary_op) - -> decltype(unary_op(list.at(0))) +template <typename C, typename S, typename UnaryOp> +auto Join(const C& container, const S& separator, UnaryOp unary_op) { - decltype(unary_op(list.at(0))) ret; - for (size_t i = 0; i < list.size(); ++i) { - if (i > 0) ret += separator; - ret += unary_op(list.at(i)); + decltype(unary_op(*container.begin())) ret; + bool first{true}; + for (const auto& item : container) { + if (!first) ret += separator; + ret += unary_op(item); + first = false; } return ret; } -template <typename T, typename T2> -T Join(const std::vector<T>& list, const T2& separator) +template <typename C, typename S> +auto Join(const C& container, const S& separator) { - return Join(list, separator, [](const T& i) { return i; }); -} - -// Explicit overload needed for c_str arguments, which would otherwise cause a substitution failure in the template above. -inline std::string Join(const std::vector<std::string>& list, std::string_view separator) -{ - return Join<std::string>(list, separator); + return Join(container, separator, [](const auto& i) { return i; }); } /** diff --git a/src/util/syserror.cpp b/src/util/syserror.cpp index 391ddd3560..5270f55366 100644 --- a/src/util/syserror.cpp +++ b/src/util/syserror.cpp @@ -10,6 +10,7 @@ #include <util/syserror.h> #include <cstring> +#include <string> std::string SysErrorString(int err) { diff --git a/src/util/system.cpp b/src/util/system.cpp index f88b0fac77..ce5d846eb9 100644 --- a/src/util/system.cpp +++ b/src/util/system.cpp @@ -5,19 +5,6 @@ #include <util/system.h> -#ifdef ENABLE_EXTERNAL_SIGNER -#if defined(__GNUC__) -// Boost 1.78 requires the following workaround. -// See: https://github.com/boostorg/process/issues/235 -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wnarrowing" -#endif -#include <boost/process.hpp> -#if defined(__GNUC__) -#pragma GCC diagnostic pop -#endif -#endif // ENABLE_EXTERNAL_SIGNER - #include <chainparamsbase.h> #include <fs.h> #include <sync.h> @@ -55,16 +42,6 @@ #else -#ifdef _MSC_VER -#pragma warning(disable:4786) -#pragma warning(disable:4804) -#pragma warning(disable:4805) -#pragma warning(disable:4717) -#endif - -#ifndef NOMINMAX -#define NOMINMAX -#endif #include <codecvt> #include <io.h> /* for _commit */ @@ -96,7 +73,7 @@ const char * const BITCOIN_SETTINGS_FILENAME = "settings.json"; ArgsManager gArgs; /** Mutex to protect dir_locks. */ -static Mutex cs_dir_locks; +static GlobalMutex cs_dir_locks; /** A map that contains all the currently held directory locks. After * successful locking, these will be held here until the global destructor * cleans them up and thus automatically unlocks them, or ReleaseDirectoryLocks @@ -532,7 +509,7 @@ bool ArgsManager::InitSettings(std::string& error) bool ArgsManager::GetSettingsPath(fs::path* filepath, bool temp, bool backup) const { - fs::path settings = GetPathArg("-settings", fs::path{BITCOIN_SETTINGS_FILENAME}); + fs::path settings = GetPathArg("-settings", BITCOIN_SETTINGS_FILENAME); if (settings.empty()) { return false; } @@ -613,35 +590,75 @@ bool ArgsManager::IsArgNegated(const std::string& strArg) const std::string ArgsManager::GetArg(const std::string& strArg, const std::string& strDefault) const { + return GetArg(strArg).value_or(strDefault); +} + +std::optional<std::string> ArgsManager::GetArg(const std::string& strArg) const +{ const util::SettingsValue value = GetSetting(strArg); - return SettingToString(value, strDefault); + return SettingToString(value); +} + +std::optional<std::string> SettingToString(const util::SettingsValue& value) +{ + if (value.isNull()) return std::nullopt; + if (value.isFalse()) return "0"; + if (value.isTrue()) return "1"; + if (value.isNum()) return value.getValStr(); + return value.get_str(); } std::string SettingToString(const util::SettingsValue& value, const std::string& strDefault) { - return value.isNull() ? strDefault : value.isFalse() ? "0" : value.isTrue() ? "1" : value.isNum() ? value.getValStr() : value.get_str(); + return SettingToString(value).value_or(strDefault); } int64_t ArgsManager::GetIntArg(const std::string& strArg, int64_t nDefault) const { + return GetIntArg(strArg).value_or(nDefault); +} + +std::optional<int64_t> ArgsManager::GetIntArg(const std::string& strArg) const +{ const util::SettingsValue value = GetSetting(strArg); - return SettingToInt(value, nDefault); + return SettingToInt(value); +} + +std::optional<int64_t> SettingToInt(const util::SettingsValue& value) +{ + if (value.isNull()) return std::nullopt; + if (value.isFalse()) return 0; + if (value.isTrue()) return 1; + if (value.isNum()) return value.getInt<int64_t>(); + return LocaleIndependentAtoi<int64_t>(value.get_str()); } int64_t SettingToInt(const util::SettingsValue& value, int64_t nDefault) { - return value.isNull() ? nDefault : value.isFalse() ? 0 : value.isTrue() ? 1 : value.isNum() ? value.getInt<int64_t>() : LocaleIndependentAtoi<int64_t>(value.get_str()); + return SettingToInt(value).value_or(nDefault); } bool ArgsManager::GetBoolArg(const std::string& strArg, bool fDefault) const { + return GetBoolArg(strArg).value_or(fDefault); +} + +std::optional<bool> ArgsManager::GetBoolArg(const std::string& strArg) const +{ const util::SettingsValue value = GetSetting(strArg); - return SettingToBool(value, fDefault); + return SettingToBool(value); +} + +std::optional<bool> SettingToBool(const util::SettingsValue& value) +{ + if (value.isNull()) return std::nullopt; + if (value.isBool()) return value.get_bool(); + return InterpretBool(value.get_str()); } bool SettingToBool(const util::SettingsValue& value, bool fDefault) { - return value.isNull() ? fDefault : value.isBool() ? value.get_bool() : InterpretBool(value.get_str()); + return SettingToBool(value).value_or(fDefault); } bool ArgsManager::SoftSetArg(const std::string& strArg, const std::string& strValue) @@ -710,7 +727,7 @@ std::string ArgsManager::GetHelpMessage() const { const bool show_debug = GetBoolArg("-help-debug", false); - std::string usage = ""; + std::string usage; LOCK(cs_args); for (const auto& arg_map : m_available_args) { switch(arg_map.first) { @@ -801,7 +818,7 @@ std::string HelpMessageOpt(const std::string &option, const std::string &message std::string("\n\n"); } -static std::string FormatException(const std::exception* pex, const char* pszThread) +static std::string FormatException(const std::exception* pex, std::string_view thread_name) { #ifdef WIN32 char pszModule[MAX_PATH] = ""; @@ -811,15 +828,15 @@ static std::string FormatException(const std::exception* pex, const char* pszThr #endif if (pex) return strprintf( - "EXCEPTION: %s \n%s \n%s in %s \n", typeid(*pex).name(), pex->what(), pszModule, pszThread); + "EXCEPTION: %s \n%s \n%s in %s \n", typeid(*pex).name(), pex->what(), pszModule, thread_name); else return strprintf( - "UNKNOWN EXCEPTION \n%s in %s \n", pszModule, pszThread); + "UNKNOWN EXCEPTION \n%s in %s \n", pszModule, thread_name); } -void PrintExceptionContinue(const std::exception* pex, const char* pszThread) +void PrintExceptionContinue(const std::exception* pex, std::string_view thread_name) { - std::string message = FormatException(pex, pszThread); + std::string message = FormatException(pex, thread_name); LogPrintf("\n\n************************\n%s\n", message); tfm::format(std::cerr, "\n\n************************\n%s\n", message); } @@ -855,9 +872,9 @@ bool CheckDataDirOption() return datadir.empty() || fs::is_directory(fs::absolute(datadir)); } -fs::path GetConfigFile(const std::string& confPath) +fs::path GetConfigFile(const fs::path& configuration_file_path) { - return AbsPathForConfigVal(fs::PathFromString(confPath), false); + return AbsPathForConfigVal(configuration_file_path, /*net_specific=*/false); } static bool GetConfigOptions(std::istream& stream, const std::string& filepath, std::string& error, std::vector<std::pair<std::string, std::string>>& options, std::list<SectionInfo>& sections) @@ -941,17 +958,17 @@ bool ArgsManager::ReadConfigFiles(std::string& error, bool ignore_invalid_keys) m_config_sections.clear(); } - const std::string confPath = GetArg("-conf", BITCOIN_CONF_FILENAME); - std::ifstream stream{GetConfigFile(confPath)}; + const fs::path conf_path = GetPathArg("-conf", BITCOIN_CONF_FILENAME); + std::ifstream stream{GetConfigFile(conf_path)}; // not ok to have a config file specified that cannot be opened if (IsArgSet("-conf") && !stream.good()) { - error = strprintf("specified config file \"%s\" could not be opened.", confPath); + error = strprintf("specified config file \"%s\" could not be opened.", fs::PathToString(conf_path)); return false; } // ok to not have a config file if (stream.good()) { - if (!ReadConfigStream(stream, confPath, error, ignore_invalid_keys)) { + if (!ReadConfigStream(stream, fs::PathToString(conf_path), error, ignore_invalid_keys)) { return false; } // `-includeconf` cannot be included in the command line arguments except @@ -989,7 +1006,7 @@ bool ArgsManager::ReadConfigFiles(std::string& error, bool ignore_invalid_keys) const size_t default_includes = add_includes({}); for (const std::string& conf_file_name : conf_file_names) { - std::ifstream conf_file_stream{GetConfigFile(conf_file_name)}; + std::ifstream conf_file_stream{GetConfigFile(fs::PathFromString(conf_file_name))}; if (conf_file_stream.good()) { if (!ReadConfigStream(conf_file_stream, conf_file_name, error, ignore_invalid_keys)) { return false; @@ -1302,44 +1319,6 @@ void runCommand(const std::string& strCommand) } #endif -UniValue RunCommandParseJSON(const std::string& str_command, const std::string& str_std_in) -{ -#ifdef ENABLE_EXTERNAL_SIGNER - namespace bp = boost::process; - - UniValue result_json; - bp::opstream stdin_stream; - bp::ipstream stdout_stream; - bp::ipstream stderr_stream; - - if (str_command.empty()) return UniValue::VNULL; - - bp::child c( - str_command, - bp::std_out > stdout_stream, - bp::std_err > stderr_stream, - bp::std_in < stdin_stream - ); - if (!str_std_in.empty()) { - stdin_stream << str_std_in << std::endl; - } - stdin_stream.pipe().close(); - - std::string result; - std::string error; - std::getline(stdout_stream, result); - std::getline(stderr_stream, error); - - c.wait(); - const int n_error = c.exit_code(); - if (n_error) throw std::runtime_error(strprintf("RunCommandParseJSON error: process(%s) returned %d: %s\n", str_command, n_error, error)); - if (!result_json.read(result)) throw std::runtime_error("Unable to parse JSON: " + result); - - return result_json; -#else - throw std::runtime_error("Compiled without external signing support (required for external signing)."); -#endif // ENABLE_EXTERNAL_SIGNER -} void SetupEnvironment() { diff --git a/src/util/system.h b/src/util/system.h index 07d7a533aa..29629e547e 100644 --- a/src/util/system.h +++ b/src/util/system.h @@ -14,7 +14,7 @@ #include <config/bitcoin-config.h> #endif -#include <compat.h> +#include <compat/compat.h> #include <compat/assumptions.h> #include <fs.h> #include <logging.h> @@ -51,7 +51,7 @@ bool error(const char* fmt, const Args&... args) return false; } -void PrintExceptionContinue(const std::exception *pex, const char* pszThread); +void PrintExceptionContinue(const std::exception* pex, std::string_view thread_name); /** * Ensure file contents are fully committed to disk, using a platform-specific @@ -97,7 +97,7 @@ bool TryCreateDirectories(const fs::path& p); fs::path GetDefaultDataDir(); // Return true if -datadir option points to a valid directory or is not specified. bool CheckDataDirOption(); -fs::path GetConfigFile(const std::string& confPath); +fs::path GetConfigFile(const fs::path& configuration_file_path); #ifdef WIN32 fs::path GetSpecialFolderPath(int nFolder, bool fCreate = true); #endif @@ -107,14 +107,6 @@ std::string ShellEscape(const std::string& arg); #if HAVE_SYSTEM void runCommand(const std::string& strCommand); #endif -/** - * Execute a command which returns JSON, and parse the result. - * - * @param str_command The command to execute, including any arguments - * @param str_std_in string to pass to stdin - * @return parsed JSON - */ -UniValue RunCommandParseJSON(const std::string& str_command, const std::string& str_std_in=""); /** * Most paths passed as configuration arguments are treated as relative to @@ -161,8 +153,13 @@ struct SectionInfo }; std::string SettingToString(const util::SettingsValue&, const std::string&); +std::optional<std::string> SettingToString(const util::SettingsValue&); + int64_t SettingToInt(const util::SettingsValue&, int64_t); +std::optional<int64_t> SettingToInt(const util::SettingsValue&); + bool SettingToBool(const util::SettingsValue&, bool); +std::optional<bool> SettingToBool(const util::SettingsValue&); class ArgsManager { @@ -335,6 +332,7 @@ protected: * @return command-line argument or default value */ std::string GetArg(const std::string& strArg, const std::string& strDefault) const; + std::optional<std::string> GetArg(const std::string& strArg) const; /** * Return path argument or default value @@ -356,6 +354,7 @@ protected: * @return command-line argument (0 if invalid number) or default value */ int64_t GetIntArg(const std::string& strArg, int64_t nDefault) const; + std::optional<int64_t> GetIntArg(const std::string& strArg) const; /** * Return boolean argument or default value @@ -365,6 +364,7 @@ protected: * @return command-line argument or default value */ bool GetBoolArg(const std::string& strArg, bool fDefault) const; + std::optional<bool> GetBoolArg(const std::string& strArg) const; /** * Set an argument if it doesn't already have a value diff --git a/src/util/thread.cpp b/src/util/thread.cpp index 14be668685..ae98abdb3d 100644 --- a/src/util/thread.cpp +++ b/src/util/thread.cpp @@ -9,10 +9,13 @@ #include <util/threadnames.h> #include <exception> +#include <functional> +#include <string> +#include <utility> -void util::TraceThread(const char* thread_name, std::function<void()> thread_func) +void util::TraceThread(std::string_view thread_name, std::function<void()> thread_func) { - util::ThreadRename(thread_name); + util::ThreadRename(std::string{thread_name}); try { LogPrintf("%s thread start\n", thread_name); thread_func(); diff --git a/src/util/thread.h b/src/util/thread.h index ca2eccc0c3..b80bf046a0 100644 --- a/src/util/thread.h +++ b/src/util/thread.h @@ -6,12 +6,13 @@ #define BITCOIN_UTIL_THREAD_H #include <functional> +#include <string> namespace util { /** * A wrapper for do-something-once thread functions. */ -void TraceThread(const char* thread_name, std::function<void()> thread_func); +void TraceThread(std::string_view thread_name, std::function<void()> thread_func); } // namespace util diff --git a/src/util/time.cpp b/src/util/time.cpp index 7d9d6bcff1..0b20849079 100644 --- a/src/util/time.cpp +++ b/src/util/time.cpp @@ -7,17 +7,18 @@ #include <config/bitcoin-config.h> #endif -#include <compat.h> +#include <compat/compat.h> +#include <tinyformat.h> #include <util/time.h> - #include <util/check.h> #include <atomic> -#include <boost/date_time/posix_time/posix_time.hpp> +#include <chrono> #include <ctime> +#include <locale> #include <thread> - -#include <tinyformat.h> +#include <sstream> +#include <string> void UninterruptibleSleep(const std::chrono::microseconds& n) { std::this_thread::sleep_for(n); } @@ -139,20 +140,6 @@ std::string FormatISO8601Date(int64_t nTime) { return strprintf("%04i-%02i-%02i", ts.tm_year + 1900, ts.tm_mon + 1, ts.tm_mday); } -int64_t ParseISO8601DateTime(const std::string& str) -{ - static const boost::posix_time::ptime epoch = boost::posix_time::from_time_t(0); - static const std::locale loc(std::locale::classic(), - new boost::posix_time::time_input_facet("%Y-%m-%dT%H:%M:%SZ")); - std::istringstream iss(str); - iss.imbue(loc); - boost::posix_time::ptime ptime(boost::date_time::not_a_date_time); - iss >> ptime; - if (ptime.is_not_a_date_time() || epoch > ptime) - return 0; - return (ptime - epoch).total_seconds(); -} - struct timeval MillisToTimeval(int64_t nTimeout) { struct timeval timeout; diff --git a/src/util/time.h b/src/util/time.h index ad91a72860..10a581a44c 100644 --- a/src/util/time.h +++ b/src/util/time.h @@ -6,10 +6,10 @@ #ifndef BITCOIN_UTIL_TIME_H #define BITCOIN_UTIL_TIME_H -#include <compat.h> +#include <compat/compat.h> #include <chrono> -#include <stdint.h> +#include <cstdint> #include <string> using namespace std::chrono_literals; @@ -24,6 +24,7 @@ struct NodeClock : public std::chrono::system_clock { }; using NodeSeconds = std::chrono::time_point<NodeClock, std::chrono::seconds>; +using SteadyClock = std::chrono::steady_clock; using SteadySeconds = std::chrono::time_point<std::chrono::steady_clock, std::chrono::seconds>; using SteadyMilliseconds = std::chrono::time_point<std::chrono::steady_clock, std::chrono::milliseconds>; using SteadyMicroseconds = std::chrono::time_point<std::chrono::steady_clock, std::chrono::microseconds>; @@ -40,21 +41,23 @@ void UninterruptibleSleep(const std::chrono::microseconds& n); * This helper is used to convert durations/time_points before passing them over an * interface that doesn't support std::chrono (e.g. RPC, debug log, or the GUI) */ +template <typename Dur1, typename Dur2> +constexpr auto Ticks(Dur2 d) +{ + return std::chrono::duration_cast<Dur1>(d).count(); +} template <typename Duration, typename Timepoint> constexpr auto TicksSinceEpoch(Timepoint t) { - return std::chrono::time_point_cast<Duration>(t).time_since_epoch().count(); + return Ticks<Duration>(t.time_since_epoch()); } constexpr int64_t count_seconds(std::chrono::seconds t) { return t.count(); } constexpr int64_t count_milliseconds(std::chrono::milliseconds t) { return t.count(); } constexpr int64_t count_microseconds(std::chrono::microseconds t) { return t.count(); } +using HoursDouble = std::chrono::duration<double, std::chrono::hours::period>; 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(); } +using MillisecondsDouble = std::chrono::duration<double, std::chrono::milliseconds::period>; /** * DEPRECATED @@ -107,7 +110,6 @@ T GetTime() */ std::string FormatISO8601DateTime(int64_t nTime); std::string FormatISO8601Date(int64_t nTime); -int64_t ParseISO8601DateTime(const std::string& str); /** * Convert milliseconds to a struct timeval for e.g. select. diff --git a/src/util/translation.h b/src/util/translation.h index aee601d9c1..07b7f43c8a 100644 --- a/src/util/translation.h +++ b/src/util/translation.h @@ -6,7 +6,9 @@ #define BITCOIN_UTIL_TRANSLATION_H #include <tinyformat.h> + #include <functional> +#include <string> /** * Bilingual messages: diff --git a/src/util/url.cpp b/src/util/url.cpp index e42d93bce8..ea9323e666 100644 --- a/src/util/url.cpp +++ b/src/util/url.cpp @@ -5,7 +5,8 @@ #include <util/url.h> #include <event2/http.h> -#include <stdlib.h> + +#include <cstdlib> #include <string> std::string urlDecode(const std::string &urlEncoded) { diff --git a/src/util/vector.h b/src/util/vector.h index dab65ded2a..ed745affe5 100644 --- a/src/util/vector.h +++ b/src/util/vector.h @@ -7,6 +7,7 @@ #include <initializer_list> #include <type_traits> +#include <utility> #include <vector> /** Construct a vector with the specified elements. diff --git a/src/validation.cpp b/src/validation.cpp index 23ad221ffe..514a528314 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -5,6 +5,9 @@ #include <validation.h> +#include <kernel/coinstats.h> +#include <kernel/mempool_persist.h> + #include <arith_uint256.h> #include <chain.h> #include <chainparams.h> @@ -17,12 +20,12 @@ #include <consensus/validation.h> #include <cuckoocache.h> #include <flatfile.h> +#include <fs.h> #include <hash.h> -#include <kernel/coinstats.h> #include <logging.h> #include <logging/timer.h> #include <node/blockstorage.h> -#include <node/ui_interface.h> +#include <node/interface_ui.h> #include <node/utxo_snapshot.h> #include <policy/policy.h> #include <policy/rbf.h> @@ -47,12 +50,15 @@ #include <util/rbf.h> #include <util/strencodings.h> #include <util/system.h> +#include <util/time.h> #include <util/trace.h> #include <util/translation.h> #include <validationinterface.h> #include <warnings.h> #include <algorithm> +#include <cassert> +#include <chrono> #include <deque> #include <numeric> #include <optional> @@ -61,8 +67,9 @@ using kernel::CCoinsStats; using kernel::CoinStatsHashType; using kernel::ComputeUTXOStats; +using kernel::LoadMempool; -using node::BLOCKFILE_CHUNK_SIZE; +using fsbridge::FopenFn; using node::BlockManager; using node::BlockMap; using node::CBlockIndexHeightOnlyComparator; @@ -70,23 +77,11 @@ using node::CBlockIndexWorkComparator; using node::fImporting; using node::fPruneMode; using node::fReindex; -using node::nPruneTarget; -using node::OpenBlockFile; using node::ReadBlockFromDisk; using node::SnapshotMetadata; -using node::UNDOFILE_CHUNK_SIZE; using node::UndoReadFromDisk; using node::UnlinkPrunedFiles; -#define MICRO 0.000001 -#define MILLI 0.001 - -/** - * An extra transaction can be added to a package, as long as it only has one - * ancestor and is no larger than this. Not really any reason to make this - * configurable as it doesn't materially change DoS parameters. - */ -static const unsigned int EXTRA_DESCENDANT_TX_SIZE_LIMIT = 10000; /** Maximum kilobytes for transactions to store for processing during reorg */ static const unsigned int MAX_DISCONNECTED_TX_POOL_SIZE = 20000; /** Time to wait between writing blocks/block index to disk. */ @@ -122,11 +117,10 @@ static constexpr int PRUNE_LOCK_BUFFER{10}; */ RecursiveMutex cs_main; -Mutex g_best_block_mutex; +GlobalMutex g_best_block_mutex; std::condition_variable g_best_block_cv; uint256 g_best_block; bool g_parallel_script_checks{false}; -bool fRequireStandard = true; bool fCheckBlockIndex = false; bool fCheckpointsEnabled = DEFAULT_CHECKPOINTS_ENABLED; int64_t nMaxTipAge = DEFAULT_MAX_TIP_AGE; @@ -134,7 +128,7 @@ int64_t nMaxTipAge = DEFAULT_MAX_TIP_AGE; uint256 hashAssumeValid; arith_uint256 nMinimumChainWork; -const CBlockIndex* CChainState::FindForkInGlobalIndex(const CBlockLocator& locator) const +const CBlockIndex* Chainstate::FindForkInGlobalIndex(const CBlockLocator& locator) const { AssertLockHeld(cs_main); @@ -160,10 +154,9 @@ bool CheckInputScripts(const CTransaction& tx, TxValidationState& state, std::vector<CScriptCheck>* pvChecks = nullptr) EXCLUSIVE_LOCKS_REQUIRED(cs_main); -bool CheckFinalTxAtTip(const CBlockIndex* active_chain_tip, const CTransaction& tx) +bool CheckFinalTxAtTip(const CBlockIndex& active_chain_tip, const CTransaction& tx) { AssertLockHeld(cs_main); - assert(active_chain_tip); // TODO: Make active_chain_tip a reference // CheckFinalTxAtTip() uses active_chain_tip.Height()+1 to evaluate // nLockTime because when IsFinalTx() is called within @@ -171,14 +164,14 @@ bool CheckFinalTxAtTip(const CBlockIndex* active_chain_tip, const CTransaction& // 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 active_chain_tip.Height(). - const int nBlockHeight = active_chain_tip->nHeight + 1; + const int nBlockHeight = active_chain_tip.nHeight + 1; // BIP113 requires that time-locked transactions have nLockTime set to // less than the median time of the previous block they're contained in. // When the next block is created its previous block will be the current // chain tip, so we use that to calculate the median time passed to // IsFinalTx(). - const int64_t nBlockTime{active_chain_tip->GetMedianTimePast()}; + const int64_t nBlockTime{active_chain_tip.GetMedianTimePast()}; return IsFinalTx(tx, nBlockHeight, nBlockTime); } @@ -261,23 +254,23 @@ bool CheckSequenceLocksAtTip(CBlockIndex* tip, // Returns the script flags which should be checked for a given block static unsigned int GetBlockScriptFlags(const CBlockIndex& block_index, const ChainstateManager& chainman); -static void LimitMempoolSize(CTxMemPool& pool, CCoinsViewCache& coins_cache, size_t limit, std::chrono::seconds age) +static void LimitMempoolSize(CTxMemPool& pool, CCoinsViewCache& coins_cache) EXCLUSIVE_LOCKS_REQUIRED(::cs_main, pool.cs) { AssertLockHeld(::cs_main); AssertLockHeld(pool.cs); - int expired = pool.Expire(GetTime<std::chrono::seconds>() - age); + int expired = pool.Expire(GetTime<std::chrono::seconds>() - pool.m_expiry); if (expired != 0) { LogPrint(BCLog::MEMPOOL, "Expired %i transactions from the memory pool\n", expired); } std::vector<COutPoint> vNoSpendsRemaining; - pool.TrimToSize(limit, &vNoSpendsRemaining); + pool.TrimToSize(pool.m_max_size_bytes, &vNoSpendsRemaining); for (const COutPoint& removed : vNoSpendsRemaining) coins_cache.Uncache(removed); } -static bool IsCurrentForFeeEstimation(CChainState& active_chainstate) EXCLUSIVE_LOCKS_REQUIRED(cs_main) +static bool IsCurrentForFeeEstimation(Chainstate& active_chainstate) EXCLUSIVE_LOCKS_REQUIRED(cs_main) { AssertLockHeld(cs_main); if (active_chainstate.IsInitialBlockDownload()) @@ -290,7 +283,7 @@ static bool IsCurrentForFeeEstimation(CChainState& active_chainstate) EXCLUSIVE_ return true; } -void CChainState::MaybeUpdateMempoolForReorg( +void Chainstate::MaybeUpdateMempoolForReorg( DisconnectedBlockTransactions& disconnectpool, bool fAddToMempool) { @@ -326,9 +319,7 @@ void CChainState::MaybeUpdateMempoolForReorg( // previously-confirmed transactions back to the mempool. // UpdateTransactionsFromBlock finds descendants of any transactions in // the disconnectpool that were added back and cleans up the mempool state. - const uint64_t ancestor_count_limit = gArgs.GetIntArg("-limitancestorcount", DEFAULT_ANCESTOR_LIMIT); - const uint64_t ancestor_size_limit = gArgs.GetIntArg("-limitancestorsize", DEFAULT_ANCESTOR_SIZE_LIMIT) * 1000; - m_mempool->UpdateTransactionsFromBlock(vHashUpdate, ancestor_size_limit, ancestor_count_limit); + m_mempool->UpdateTransactionsFromBlock(vHashUpdate); // Predicate to use for filtering transactions in removeForReorg. // Checks whether the transaction is still final and, if it spends a coinbase output, mature. @@ -342,7 +333,7 @@ void CChainState::MaybeUpdateMempoolForReorg( const CTransaction& tx = it->GetTx(); // The transaction must be final. - if (!CheckFinalTxAtTip(m_chain.Tip(), tx)) return true; + if (!CheckFinalTxAtTip(*Assert(m_chain.Tip()), tx)) return true; LockPoints lp = it->GetLockPoints(); const bool validLP{TestLockPointValidity(m_chain, lp)}; CCoinsViewMemPool view_mempool(&CoinsTip(), *m_mempool); @@ -380,11 +371,7 @@ void CChainState::MaybeUpdateMempoolForReorg( // We also need to remove any now-immature transactions m_mempool->removeForReorg(m_chain, filter_final_and_mature); // Re-limit mempool size, in case we added any transactions - LimitMempoolSize( - *m_mempool, - this->CoinsTip(), - gArgs.GetIntArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000, - std::chrono::hours{gArgs.GetIntArg("-mempoolexpiry", DEFAULT_MEMPOOL_EXPIRY)}); + LimitMempoolSize(*m_mempool, this->CoinsTip()); } /** @@ -434,11 +421,13 @@ namespace { class MemPoolAccept { public: - explicit MemPoolAccept(CTxMemPool& mempool, CChainState& active_chainstate) : m_pool(mempool), m_view(&m_dummy), m_viewmempool(&active_chainstate.CoinsTip(), m_pool), m_active_chainstate(active_chainstate), - m_limit_ancestors(gArgs.GetIntArg("-limitancestorcount", DEFAULT_ANCESTOR_LIMIT)), - m_limit_ancestor_size(gArgs.GetIntArg("-limitancestorsize", DEFAULT_ANCESTOR_SIZE_LIMIT)*1000), - m_limit_descendants(gArgs.GetIntArg("-limitdescendantcount", DEFAULT_DESCENDANT_LIMIT)), - m_limit_descendant_size(gArgs.GetIntArg("-limitdescendantsize", DEFAULT_DESCENDANT_SIZE_LIMIT)*1000) { + explicit MemPoolAccept(CTxMemPool& mempool, Chainstate& active_chainstate) : + m_pool(mempool), + m_view(&m_dummy), + m_viewmempool(&active_chainstate.CoinsTip(), m_pool), + m_active_chainstate(active_chainstate), + m_limits{m_pool.m_limits} + { } // We put the arguments we're handed into a struct, so we can pass them @@ -459,7 +448,7 @@ public: /** Whether we allow transactions to replace mempool transactions by BIP125 rules. If false, * any transaction spending the same inputs as a transaction in the mempool is considered * a conflict. */ - const bool m_allow_bip125_replacement; + const bool m_allow_replacement; /** When true, the mempool will not be trimmed when individual transactions are submitted in * Finalize(). Instead, limits should be enforced at the end to ensure the package is not * partially submitted. @@ -479,7 +468,7 @@ public: /* m_bypass_limits */ bypass_limits, /* m_coins_to_uncache */ coins_to_uncache, /* m_test_accept */ test_accept, - /* m_allow_bip125_replacement */ true, + /* m_allow_replacement */ true, /* m_package_submission */ false, /* m_package_feerates */ false, }; @@ -493,7 +482,7 @@ public: /* m_bypass_limits */ false, /* m_coins_to_uncache */ coins_to_uncache, /* m_test_accept */ true, - /* m_allow_bip125_replacement */ false, + /* m_allow_replacement */ false, /* m_package_submission */ false, // not submitting to mempool /* m_package_feerates */ false, }; @@ -507,7 +496,7 @@ public: /* m_bypass_limits */ false, /* m_coins_to_uncache */ coins_to_uncache, /* m_test_accept */ false, - /* m_allow_bip125_replacement */ false, + /* m_allow_replacement */ false, /* m_package_submission */ true, /* m_package_feerates */ true, }; @@ -520,7 +509,7 @@ public: /* m_bypass_limits */ false, /* m_coins_to_uncache */ package_args.m_coins_to_uncache, /* m_test_accept */ package_args.m_test_accept, - /* m_allow_bip125_replacement */ true, + /* m_allow_replacement */ true, /* m_package_submission */ false, /* m_package_feerates */ false, // only 1 transaction }; @@ -534,7 +523,7 @@ public: bool bypass_limits, std::vector<COutPoint>& coins_to_uncache, bool test_accept, - bool allow_bip125_replacement, + bool allow_replacement, bool package_submission, bool package_feerates) : m_chainparams{chainparams}, @@ -542,7 +531,7 @@ public: m_bypass_limits{bypass_limits}, m_coins_to_uncache{coins_to_uncache}, m_test_accept{test_accept}, - m_allow_bip125_replacement{allow_bip125_replacement}, + m_allow_replacement{allow_replacement}, m_package_submission{package_submission}, m_package_feerates{package_feerates} { @@ -650,13 +639,14 @@ private: { AssertLockHeld(::cs_main); AssertLockHeld(m_pool.cs); - CAmount mempoolRejectFee = m_pool.GetMinFee(gArgs.GetIntArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000).GetFee(package_size); + CAmount mempoolRejectFee = m_pool.GetMinFee().GetFee(package_size); if (mempoolRejectFee > 0 && package_fee < mempoolRejectFee) { return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "mempool min fee not met", strprintf("%d < %d", package_fee, mempoolRejectFee)); } - if (package_fee < ::minRelayTxFee.GetFee(package_size)) { - return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "min relay fee not met", strprintf("%d < %d", package_fee, ::minRelayTxFee.GetFee(package_size))); + if (package_fee < m_pool.m_min_relay_feerate.GetFee(package_size)) { + return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "min relay fee not met", + strprintf("%d < %d", package_fee, m_pool.m_min_relay_feerate.GetFee(package_size))); } return true; } @@ -667,15 +657,9 @@ private: CCoinsViewMemPool m_viewmempool; CCoinsView m_dummy; - CChainState& m_active_chainstate; + Chainstate& m_active_chainstate; - // The package limits in effect at the time of invocation. - const size_t m_limit_ancestors; - const size_t m_limit_ancestor_size; - // These may be modified while evaluating a transaction (eg to account for - // in-mempool conflicts; see below). - size_t m_limit_descendants; - size_t m_limit_descendant_size; + CTxMemPool::Limits m_limits; /** Whether the transaction(s) would replace any mempool transactions. If so, RBF rules apply. */ bool m_rbf{false}; @@ -708,8 +692,9 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws) // Rather not work on nonstandard transactions (unless -testnet/-regtest) std::string reason; - if (fRequireStandard && !IsStandardTx(tx, reason)) + if (m_pool.m_require_standard && !IsStandardTx(tx, m_pool.m_max_datacarrier_bytes, m_pool.m_permit_bare_multisig, m_pool.m_dust_relay_feerate, reason)) { return state.Invalid(TxValidationResult::TX_NOT_STANDARD, reason); + } // Do not work on transactions that are too small. // A transaction with 1 segwit input and 1 P2WPHK output has non-witness size of 82 bytes. @@ -721,7 +706,7 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws) // Only accept nLockTime-using transactions that can be mined in the next // block; we don't want our mempool filled up with transactions that can't // be mined yet. - if (!CheckFinalTxAtTip(m_active_chainstate.m_chain.Tip(), tx)) { + if (!CheckFinalTxAtTip(*Assert(m_active_chainstate.m_chain.Tip()), tx)) { return state.Invalid(TxValidationResult::TX_PREMATURE_SPEND, "non-final"); } @@ -739,7 +724,7 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws) { const CTransaction* ptxConflicting = m_pool.GetConflictTx(txin.prevout); if (ptxConflicting) { - if (!args.m_allow_bip125_replacement) { + if (!args.m_allow_replacement) { // Transaction conflicts with a mempool tx, but we're not allowing replacements. return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "bip125-replacement-disallowed"); } @@ -752,7 +737,10 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws) // Applications relying on first-seen mempool behavior should // check all unconfirmed ancestors; otherwise an opt-in ancestor // might be replaced, causing removal of this descendant. - if (!SignalsOptInRBF(*ptxConflicting)) { + // + // If replaceability signaling is ignored due to node setting, + // replacement is always allowed. + if (!m_pool.m_full_rbf && !SignalsOptInRBF(*ptxConflicting)) { return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "txn-mempool-conflict"); } @@ -812,13 +800,14 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws) return false; // state filled in by CheckTxInputs } - if (fRequireStandard && !AreInputsStandard(tx, m_view)) { + if (m_pool.m_require_standard && !AreInputsStandard(tx, m_view)) { return state.Invalid(TxValidationResult::TX_INPUTS_NOT_STANDARD, "bad-txns-nonstandard-inputs"); } // Check for non-standard witnesses. - if (tx.HasWitness() && fRequireStandard && !IsWitnessStandard(tx, m_view)) + if (tx.HasWitness() && m_pool.m_require_standard && !IsWitnessStandard(tx, m_view)) { return state.Invalid(TxValidationResult::TX_WITNESS_MUTATED, "bad-witness-nonstandard"); + } int64_t nSigOpsCost = GetTransactionSigOpCost(tx, m_view, STANDARD_SCRIPT_VERIFY_FLAGS); @@ -845,7 +834,7 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws) return state.Invalid(TxValidationResult::TX_NOT_STANDARD, "bad-txns-too-many-sigops", strprintf("%d", nSigOpsCost)); - // No individual transactions are allowed below minRelayTxFee and mempool min fee except from + // No individual transactions are allowed below the min relay feerate and mempool min feerate except from // disconnected blocks and transactions in a package. Package transactions will be checked using // package feerate later. if (!bypass_limits && !args.m_package_feerates && !CheckFeeRate(ws.m_vsize, ws.m_modified_fees, state)) return false; @@ -865,8 +854,8 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws) // Specifically, the subset of RBF transactions which we allow despite chain limits are those which // conflict directly with exactly one other transaction (but may evict children of said transaction), // and which are not adding any new mempool dependencies. Note that the "no new mempool dependencies" - // check is accomplished later, so we don't bother doing anything about it here, but if BIP 125 is - // amended, we may need to move that check to here instead of removing it wholesale. + // check is accomplished later, so we don't bother doing anything about it here, but if our + // policy changes, we may need to move that check to here instead of removing it wholesale. // // Such transactions are clearly not merging any existing packages, so we are only concerned with // ensuring that (a) no package is growing past the package size (not count) limits and (b) we are @@ -883,12 +872,12 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws) assert(ws.m_iters_conflicting.size() == 1); CTxMemPool::txiter conflict = *ws.m_iters_conflicting.begin(); - m_limit_descendants += 1; - m_limit_descendant_size += conflict->GetSizeWithDescendants(); + m_limits.descendant_count += 1; + m_limits.descendant_size_vbytes += conflict->GetSizeWithDescendants(); } std::string errString; - if (!m_pool.CalculateMemPoolAncestors(*entry, ws.m_ancestors, m_limit_ancestors, m_limit_ancestor_size, m_limit_descendants, m_limit_descendant_size, errString)) { + if (!m_pool.CalculateMemPoolAncestors(*entry, ws.m_ancestors, m_limits, errString)) { ws.m_ancestors.clear(); // If CalculateMemPoolAncestors fails second time, we want the original error string. std::string dummy_err_string; @@ -903,8 +892,16 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws) // to be secure by simply only having two immediately-spendable // outputs - one for each counterparty. For more info on the uses for // this, see https://lists.linuxfoundation.org/pipermail/bitcoin-dev/2018-November/016518.html + CTxMemPool::Limits cpfp_carve_out_limits{ + .ancestor_count = 2, + .ancestor_size_vbytes = m_limits.ancestor_size_vbytes, + .descendant_count = m_limits.descendant_count + 1, + .descendant_size_vbytes = m_limits.descendant_size_vbytes + EXTRA_DESCENDANT_TX_SIZE_LIMIT, + }; if (ws.m_vsize > EXTRA_DESCENDANT_TX_SIZE_LIMIT || - !m_pool.CalculateMemPoolAncestors(*entry, ws.m_ancestors, 2, m_limit_ancestor_size, m_limit_descendants + 1, m_limit_descendant_size + EXTRA_DESCENDANT_TX_SIZE_LIMIT, dummy_err_string)) { + !m_pool.CalculateMemPoolAncestors(*entry, ws.m_ancestors, + cpfp_carve_out_limits, + dummy_err_string)) { return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "too-long-mempool-chain", errString); } } @@ -933,7 +930,7 @@ bool MemPoolAccept::ReplacementChecks(Workspace& ws) TxValidationState& state = ws.m_state; CFeeRate newFeeRate(ws.m_modified_fees, ws.m_vsize); - // The replacement transaction must have a higher feerate than its direct conflicts. + // Enforce Rule #6. The replacement transaction must have a higher feerate than its direct conflicts. // - The motivation for this check is to ensure that the replacement transaction is preferable for // block-inclusion, compared to what would be removed from the mempool. // - This logic predates ancestor feerate-based transaction selection, which is why it doesn't @@ -946,24 +943,24 @@ bool MemPoolAccept::ReplacementChecks(Workspace& ws) return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "insufficient fee", *err_string); } - // Calculate all conflicting entries and enforce BIP125 Rule #5. + // Calculate all conflicting entries and enforce Rule #5. if (const auto err_string{GetEntriesForConflicts(tx, m_pool, ws.m_iters_conflicting, ws.m_all_conflicting)}) { return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "too many potential replacements", *err_string); } - // Enforce BIP125 Rule #2. + // Enforce Rule #2. if (const auto err_string{HasNoNewUnconfirmed(tx, m_pool, ws.m_iters_conflicting)}) { return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "replacement-adds-unconfirmed", *err_string); } // Check if it's economically rational to mine this transaction rather than the ones it - // replaces and pays for its own relay fees. Enforce BIP125 Rules #3 and #4. + // replaces and pays for its own relay fees. Enforce Rules #3 and #4. for (CTxMemPool::txiter it : ws.m_all_conflicting) { ws.m_conflicting_fees += it->GetModifiedFee(); ws.m_conflicting_size += it->GetTxSize(); } if (const auto err_string{PaysForRBF(ws.m_conflicting_fees, ws.m_modified_fees, ws.m_vsize, - ::incrementalRelayFee, hash)}) { + m_pool.m_incremental_relay_feerate, hash)}) { return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "insufficient fee", *err_string); } return true; @@ -980,8 +977,7 @@ bool MemPoolAccept::PackageMempoolChecks(const std::vector<CTransactionRef>& txn { return !m_pool.exists(GenTxid::Txid(tx->GetHash()));})); std::string err_string; - if (!m_pool.CheckPackageLimits(txns, m_limit_ancestors, m_limit_ancestor_size, m_limit_descendants, - m_limit_descendant_size, err_string)) { + if (!m_pool.CheckPackageLimits(txns, m_limits, err_string)) { // This is a package-wide error, separate from an individual transaction error. return package_state.Invalid(PackageValidationResult::PCKG_POLICY, "package-mempool-limits", err_string); } @@ -1088,7 +1084,7 @@ bool MemPoolAccept::Finalize(const ATMPArgs& args, Workspace& ws) // in the package. LimitMempoolSize() should be called at the very end to make sure the mempool // is still within limits and package submission happens atomically. if (!args.m_package_submission && !bypass_limits) { - LimitMempoolSize(m_pool, m_active_chainstate.CoinsTip(), gArgs.GetIntArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000, std::chrono::hours{gArgs.GetIntArg("-mempoolexpiry", DEFAULT_MEMPOOL_EXPIRY)}); + LimitMempoolSize(m_pool, m_active_chainstate.CoinsTip()); if (!m_pool.exists(GenTxid::Txid(hash))) return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "mempool full"); } @@ -1125,9 +1121,7 @@ bool MemPoolAccept::SubmitPackage(const ATMPArgs& args, std::vector<Workspace>& // Re-calculate mempool ancestors to call addUnchecked(). They may have changed since the // last calculation done in PreChecks, since package ancestors have already been submitted. std::string unused_err_string; - if(!m_pool.CalculateMemPoolAncestors(*ws.m_entry, ws.m_ancestors, m_limit_ancestors, - m_limit_ancestor_size, m_limit_descendants, - m_limit_descendant_size, unused_err_string)) { + if(!m_pool.CalculateMemPoolAncestors(*ws.m_entry, ws.m_ancestors, m_limits, unused_err_string)) { results.emplace(ws.m_ptx->GetWitnessHash(), MempoolAcceptResult::Failure(ws.m_state)); // Since PreChecks() and PackageMempoolChecks() both enforce limits, this should never fail. Assume(false); @@ -1153,9 +1147,7 @@ bool MemPoolAccept::SubmitPackage(const ATMPArgs& args, std::vector<Workspace>& // It may or may not be the case that all the transactions made it into the mempool. Regardless, // make sure we haven't exceeded max mempool size. - LimitMempoolSize(m_pool, m_active_chainstate.CoinsTip(), - gArgs.GetIntArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000, - std::chrono::hours{gArgs.GetIntArg("-mempoolexpiry", DEFAULT_MEMPOOL_EXPIRY)}); + LimitMempoolSize(m_pool, m_active_chainstate.CoinsTip()); // Find the wtxids of the transactions that made it into the mempool. Allow partial submission, // but don't report success unless they all made it into the mempool. @@ -1230,7 +1222,7 @@ PackageMempoolAcceptResult MemPoolAccept::AcceptMultipleTransactions(const std:: // package to spend. Since we already checked conflicts in the package and we don't allow // replacements, we don't need to track the coins spent. Note that this logic will need to be // updated if package replace-by-fee is allowed in the future. - assert(!args.m_allow_bip125_replacement); + assert(!args.m_allow_replacement); m_viewmempool.PackageAddTransaction(ws.m_ptx); } @@ -1417,7 +1409,7 @@ PackageMempoolAcceptResult MemPoolAccept::AcceptPackage(const Package& package, } // anon namespace -MempoolAcceptResult AcceptToMemoryPool(CChainState& active_chainstate, const CTransactionRef& tx, +MempoolAcceptResult AcceptToMemoryPool(Chainstate& active_chainstate, const CTransactionRef& tx, int64_t accept_time, bool bypass_limits, bool test_accept) EXCLUSIVE_LOCKS_REQUIRED(::cs_main) { @@ -1444,7 +1436,7 @@ MempoolAcceptResult AcceptToMemoryPool(CChainState& active_chainstate, const CTr return result; } -PackageMempoolAcceptResult ProcessNewPackage(CChainState& active_chainstate, CTxMemPool& pool, +PackageMempoolAcceptResult ProcessNewPackage(Chainstate& active_chainstate, CTxMemPool& pool, const Package& package, bool test_accept) { AssertLockHeld(cs_main); @@ -1503,7 +1495,7 @@ void CoinsViews::InitCache() m_cacheview = std::make_unique<CCoinsViewCache>(&m_catcherview); } -CChainState::CChainState( +Chainstate::Chainstate( CTxMemPool* mempool, BlockManager& blockman, ChainstateManager& chainman, @@ -1514,7 +1506,7 @@ CChainState::CChainState( m_chainman(chainman), m_from_snapshot_blockhash(from_snapshot_blockhash) {} -void CChainState::InitCoinsDB( +void Chainstate::InitCoinsDB( size_t cache_size_bytes, bool in_memory, bool should_wipe, @@ -1528,7 +1520,7 @@ void CChainState::InitCoinsDB( leveldb_name, cache_size_bytes, in_memory, should_wipe); } -void CChainState::InitCoinsCache(size_t cache_size_bytes) +void Chainstate::InitCoinsCache(size_t cache_size_bytes) { AssertLockHeld(::cs_main); assert(m_coins_views != nullptr); @@ -1538,10 +1530,10 @@ void CChainState::InitCoinsCache(size_t cache_size_bytes) // Note that though this is marked const, we may end up modifying `m_cached_finished_ibd`, which // is a performance-related implementation detail. This function must be marked -// `const` so that `CValidationInterface` clients (which are given a `const CChainState*`) +// `const` so that `CValidationInterface` clients (which are given a `const Chainstate*`) // can call it. // -bool CChainState::IsInitialBlockDownload() const +bool Chainstate::IsInitialBlockDownload() const { // Optimization: pre-test latch before taking the lock. if (m_cached_finished_ibd.load(std::memory_order_relaxed)) @@ -1583,7 +1575,7 @@ static void AlertNotify(const std::string& strMessage) #endif } -void CChainState::CheckForkWarningConditions() +void Chainstate::CheckForkWarningConditions() { AssertLockHeld(cs_main); @@ -1602,7 +1594,7 @@ void CChainState::CheckForkWarningConditions() } // Called both upon regular invalid block discovery *and* InvalidateBlock -void CChainState::InvalidChainFound(CBlockIndex* pindexNew) +void Chainstate::InvalidChainFound(CBlockIndex* pindexNew) { AssertLockHeld(cs_main); if (!m_chainman.m_best_invalid || pindexNew->nChainWork > m_chainman.m_best_invalid->nChainWork) { @@ -1625,7 +1617,7 @@ void CChainState::InvalidChainFound(CBlockIndex* pindexNew) // Same as InvalidChainFound, above, except not called directly from InvalidateBlock, // which does its own setBlockIndexCandidates management. -void CChainState::InvalidBlockFound(CBlockIndex* pindex, const BlockValidationState& state) +void Chainstate::InvalidBlockFound(CBlockIndex* pindex, const BlockValidationState& state) { AssertLockHeld(cs_main); if (state.GetResult() != BlockValidationResult::BLOCK_MUTATED) { @@ -1661,7 +1653,8 @@ bool CScriptCheck::operator()() { static CuckooCache::cache<uint256, SignatureCacheHasher> g_scriptExecutionCache; static CSHA256 g_scriptExecutionCacheHasher; -void InitScriptExecutionCache() { +bool InitScriptExecutionCache(size_t max_size_bytes) +{ // Setup the salted hasher uint256 nonce = GetRandHash(); // We want the nonce to be 64 bytes long to force the hasher to process @@ -1669,12 +1662,14 @@ void InitScriptExecutionCache() { // just write our 32-byte entropy twice to fill the 64 bytes. g_scriptExecutionCacheHasher.Write(nonce.begin(), 32); g_scriptExecutionCacheHasher.Write(nonce.begin(), 32); - // nMaxCacheSize is unsigned. If -maxsigcachesize is set to zero, - // setup_bytes creates the minimum possible cache (2 elements). - size_t nMaxCacheSize = std::min(std::max((int64_t)0, gArgs.GetIntArg("-maxsigcachesize", DEFAULT_MAX_SIG_CACHE_SIZE) / 2), MAX_MAX_SIG_CACHE_SIZE) * ((size_t) 1 << 20); - size_t nElems = g_scriptExecutionCache.setup_bytes(nMaxCacheSize); - LogPrintf("Using %zu MiB out of %zu/2 requested for script execution cache, able to store %zu elements\n", - (nElems*sizeof(uint256)) >>20, (nMaxCacheSize*2)>>20, nElems); + + auto setup_results = g_scriptExecutionCache.setup_bytes(max_size_bytes); + if (!setup_results) return false; + + const auto [num_elems, approx_size_bytes] = *setup_results; + LogPrintf("Using %zu MiB out of %zu MiB requested for script execution cache, able to store %zu elements\n", + approx_size_bytes >> 20, max_size_bytes >> 20, num_elems); + return true; } /** @@ -1827,7 +1822,7 @@ int ApplyTxInUndo(Coin&& undo, CCoinsViewCache& view, const COutPoint& out) /** Undo the effects of this block (with given index) on the UTXO set represented by coins. * When FAILED is returned, view is left in an indeterminate state. */ -DisconnectResult CChainState::DisconnectBlock(const CBlock& block, const CBlockIndex* pindex, CCoinsViewCache& view) +DisconnectResult Chainstate::DisconnectBlock(const CBlock& block, const CBlockIndex* pindex, CCoinsViewCache& view) { AssertLockHeld(::cs_main); bool fClean = true; @@ -1968,19 +1963,19 @@ static unsigned int GetBlockScriptFlags(const CBlockIndex& block_index, const Ch } -static int64_t nTimeCheck = 0; -static int64_t nTimeForks = 0; -static int64_t nTimeConnect = 0; -static int64_t nTimeVerify = 0; -static int64_t nTimeUndo = 0; -static int64_t nTimeIndex = 0; -static int64_t nTimeTotal = 0; -static int64_t nBlocksTotal = 0; +static SteadyClock::duration time_check{}; +static SteadyClock::duration time_forks{}; +static SteadyClock::duration time_connect{}; +static SteadyClock::duration time_verify{}; +static SteadyClock::duration time_undo{}; +static SteadyClock::duration time_index{}; +static SteadyClock::duration time_total{}; +static int64_t num_blocks_total = 0; /** Apply the effects of this block (with given index) on the UTXO set represented by coins. * Validity checks that depend on the UTXO set are also done; ConnectBlock() * can fail if those validity checks fail (among other reasons). */ -bool CChainState::ConnectBlock(const CBlock& block, BlockValidationState& state, CBlockIndex* pindex, +bool Chainstate::ConnectBlock(const CBlock& block, BlockValidationState& state, CBlockIndex* pindex, CCoinsViewCache& view, bool fJustCheck) { AssertLockHeld(cs_main); @@ -1989,7 +1984,7 @@ bool CChainState::ConnectBlock(const CBlock& block, BlockValidationState& state, uint256 block_hash{block.GetHash()}; assert(*pindex->phashBlock == block_hash); - int64_t nTimeStart = GetTimeMicros(); + const auto time_start{SteadyClock::now()}; // Check it again in case a previous version let a bad block in // NOTE: We don't currently (re-)invoke ContextualCheckBlock() or @@ -2018,7 +2013,7 @@ bool CChainState::ConnectBlock(const CBlock& block, BlockValidationState& state, uint256 hashPrevBlock = pindex->pprev == nullptr ? uint256() : pindex->pprev->GetBlockHash(); assert(hashPrevBlock == view.GetBestBlock()); - nBlocksTotal++; + num_blocks_total++; // Special case for the genesis block, skipping connection of its transactions // (its coinbase is unspendable) @@ -2059,8 +2054,12 @@ bool CChainState::ConnectBlock(const CBlock& block, BlockValidationState& state, } } - int64_t nTime1 = GetTimeMicros(); nTimeCheck += nTime1 - nTimeStart; - LogPrint(BCLog::BENCH, " - Sanity checks: %.2fms [%.2fs (%.2fms/blk)]\n", MILLI * (nTime1 - nTimeStart), nTimeCheck * MICRO, nTimeCheck * MILLI / nBlocksTotal); + const auto time_1{SteadyClock::now()}; + time_check += time_1 - time_start; + LogPrint(BCLog::BENCH, " - Sanity checks: %.2fms [%.2fs (%.2fms/blk)]\n", + Ticks<MillisecondsDouble>(time_1 - time_start), + Ticks<SecondsDouble>(time_check), + Ticks<MillisecondsDouble>(time_check) / num_blocks_total); // Do not allow blocks that contain transactions which 'overwrite' older transactions, // unless those are already completely spent. @@ -2158,8 +2157,12 @@ bool CChainState::ConnectBlock(const CBlock& block, BlockValidationState& state, // Get the script flags for this block unsigned int flags{GetBlockScriptFlags(*pindex, m_chainman)}; - int64_t nTime2 = GetTimeMicros(); nTimeForks += nTime2 - nTime1; - LogPrint(BCLog::BENCH, " - Fork checks: %.2fms [%.2fs (%.2fms/blk)]\n", MILLI * (nTime2 - nTime1), nTimeForks * MICRO, nTimeForks * MILLI / nBlocksTotal); + const auto time_2{SteadyClock::now()}; + time_forks += time_2 - time_1; + LogPrint(BCLog::BENCH, " - Fork checks: %.2fms [%.2fs (%.2fms/blk)]\n", + Ticks<MillisecondsDouble>(time_2 - time_1), + Ticks<SecondsDouble>(time_forks), + Ticks<MillisecondsDouble>(time_forks) / num_blocks_total); CBlockUndo blockundo; @@ -2243,8 +2246,13 @@ bool CChainState::ConnectBlock(const CBlock& block, BlockValidationState& state, } UpdateCoins(tx, view, i == 0 ? undoDummy : blockundo.vtxundo.back(), pindex->nHeight); } - int64_t nTime3 = GetTimeMicros(); nTimeConnect += nTime3 - nTime2; - LogPrint(BCLog::BENCH, " - Connect %u transactions: %.2fms (%.3fms/tx, %.3fms/txin) [%.2fs (%.2fms/blk)]\n", (unsigned)block.vtx.size(), MILLI * (nTime3 - nTime2), MILLI * (nTime3 - nTime2) / block.vtx.size(), nInputs <= 1 ? 0 : MILLI * (nTime3 - nTime2) / (nInputs-1), nTimeConnect * MICRO, nTimeConnect * MILLI / nBlocksTotal); + const auto time_3{SteadyClock::now()}; + time_connect += time_3 - time_2; + LogPrint(BCLog::BENCH, " - Connect %u transactions: %.2fms (%.3fms/tx, %.3fms/txin) [%.2fs (%.2fms/blk)]\n", (unsigned)block.vtx.size(), + Ticks<MillisecondsDouble>(time_3 - time_2), Ticks<MillisecondsDouble>(time_3 - time_2) / block.vtx.size(), + nInputs <= 1 ? 0 : Ticks<MillisecondsDouble>(time_3 - time_2) / (nInputs - 1), + Ticks<SecondsDouble>(time_connect), + Ticks<MillisecondsDouble>(time_connect) / num_blocks_total); CAmount blockReward = nFees + GetBlockSubsidy(pindex->nHeight, m_params.GetConsensus()); if (block.vtx[0]->GetValueOut() > blockReward) { @@ -2256,8 +2264,13 @@ bool CChainState::ConnectBlock(const CBlock& block, BlockValidationState& state, LogPrintf("ERROR: %s: CheckQueue failed\n", __func__); return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "block-validation-failed"); } - int64_t nTime4 = GetTimeMicros(); nTimeVerify += nTime4 - nTime2; - LogPrint(BCLog::BENCH, " - Verify %u txins: %.2fms (%.3fms/txin) [%.2fs (%.2fms/blk)]\n", nInputs - 1, MILLI * (nTime4 - nTime2), nInputs <= 1 ? 0 : MILLI * (nTime4 - nTime2) / (nInputs-1), nTimeVerify * MICRO, nTimeVerify * MILLI / nBlocksTotal); + const auto time_4{SteadyClock::now()}; + time_verify += time_4 - time_2; + LogPrint(BCLog::BENCH, " - Verify %u txins: %.2fms (%.3fms/txin) [%.2fs (%.2fms/blk)]\n", nInputs - 1, + Ticks<MillisecondsDouble>(time_4 - time_2), + nInputs <= 1 ? 0 : Ticks<MillisecondsDouble>(time_4 - time_2) / (nInputs - 1), + Ticks<SecondsDouble>(time_verify), + Ticks<MillisecondsDouble>(time_verify) / num_blocks_total); if (fJustCheck) return true; @@ -2266,20 +2279,27 @@ bool CChainState::ConnectBlock(const CBlock& block, BlockValidationState& state, return false; } - int64_t nTime5 = GetTimeMicros(); nTimeUndo += nTime5 - nTime4; - LogPrint(BCLog::BENCH, " - Write undo data: %.2fms [%.2fs (%.2fms/blk)]\n", MILLI * (nTime5 - nTime4), nTimeUndo * MICRO, nTimeUndo * MILLI / nBlocksTotal); + const auto time_5{SteadyClock::now()}; + time_undo += time_5 - time_4; + LogPrint(BCLog::BENCH, " - Write undo data: %.2fms [%.2fs (%.2fms/blk)]\n", + Ticks<MillisecondsDouble>(time_5 - time_4), + Ticks<SecondsDouble>(time_undo), + Ticks<MillisecondsDouble>(time_undo) / num_blocks_total); if (!pindex->IsValid(BLOCK_VALID_SCRIPTS)) { pindex->RaiseValidity(BLOCK_VALID_SCRIPTS); m_blockman.m_dirty_blockindex.insert(pindex); } - assert(pindex->phashBlock); // add this block to the view's block chain view.SetBestBlock(pindex->GetBlockHash()); - int64_t nTime6 = GetTimeMicros(); nTimeIndex += nTime6 - nTime5; - LogPrint(BCLog::BENCH, " - Index writing: %.2fms [%.2fs (%.2fms/blk)]\n", MILLI * (nTime6 - nTime5), nTimeIndex * MICRO, nTimeIndex * MILLI / nBlocksTotal); + const auto time_6{SteadyClock::now()}; + time_index += time_6 - time_5; + LogPrint(BCLog::BENCH, " - Index writing: %.2fms [%.2fs (%.2fms/blk)]\n", + Ticks<MillisecondsDouble>(time_6 - time_5), + Ticks<SecondsDouble>(time_index), + Ticks<MillisecondsDouble>(time_index) / num_blocks_total); TRACE6(validation, block_connected, block_hash.data(), @@ -2287,21 +2307,21 @@ bool CChainState::ConnectBlock(const CBlock& block, BlockValidationState& state, block.vtx.size(), nInputs, nSigOpsCost, - nTime5 - nTimeStart // in microseconds (µs) + time_5 - time_start // in microseconds (µs) ); return true; } -CoinsCacheSizeState CChainState::GetCoinsCacheSizeState() +CoinsCacheSizeState Chainstate::GetCoinsCacheSizeState() { AssertLockHeld(::cs_main); return this->GetCoinsCacheSizeState( m_coinstip_cache_size_bytes, - gArgs.GetIntArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000); + m_mempool ? m_mempool->m_max_size_bytes : 0); } -CoinsCacheSizeState CChainState::GetCoinsCacheSizeState( +CoinsCacheSizeState Chainstate::GetCoinsCacheSizeState( size_t max_coins_cache_size_bytes, size_t max_mempool_size_bytes) { @@ -2325,7 +2345,7 @@ CoinsCacheSizeState CChainState::GetCoinsCacheSizeState( return CoinsCacheSizeState::OK; } -bool CChainState::FlushStateToDisk( +bool Chainstate::FlushStateToDisk( BlockValidationState &state, FlushStateMode mode, int nManualPruneHeight) @@ -2468,7 +2488,7 @@ bool CChainState::FlushStateToDisk( return true; } -void CChainState::ForceFlushStateToDisk() +void Chainstate::ForceFlushStateToDisk() { BlockValidationState state; if (!this->FlushStateToDisk(state, FlushStateMode::ALWAYS)) { @@ -2476,7 +2496,7 @@ void CChainState::ForceFlushStateToDisk() } } -void CChainState::PruneAndFlush() +void Chainstate::PruneAndFlush() { BlockValidationState state; m_blockman.m_check_for_pruning = true; @@ -2523,7 +2543,7 @@ static void UpdateTipLog( !warning_messages.empty() ? strprintf(" warning='%s'", warning_messages) : ""); } -void CChainState::UpdateTip(const CBlockIndex* pindexNew) +void Chainstate::UpdateTip(const CBlockIndex* pindexNew) { AssertLockHeld(::cs_main); const auto& coins_tip = this->CoinsTip(); @@ -2579,13 +2599,14 @@ void CChainState::UpdateTip(const CBlockIndex* pindexNew) * disconnectpool (note that the caller is responsible for mempool consistency * in any case). */ -bool CChainState::DisconnectTip(BlockValidationState& state, DisconnectedBlockTransactions* disconnectpool) +bool Chainstate::DisconnectTip(BlockValidationState& state, DisconnectedBlockTransactions* disconnectpool) { AssertLockHeld(cs_main); if (m_mempool) AssertLockHeld(m_mempool->cs); CBlockIndex *pindexDelete = m_chain.Tip(); assert(pindexDelete); + assert(pindexDelete->pprev); // Read block from disk. std::shared_ptr<CBlock> pblock = std::make_shared<CBlock>(); CBlock& block = *pblock; @@ -2593,7 +2614,7 @@ bool CChainState::DisconnectTip(BlockValidationState& state, DisconnectedBlockTr return error("DisconnectTip(): Failed to read block"); } // Apply the block atomically to the chain state. - int64_t nStart = GetTimeMicros(); + const auto time_start{SteadyClock::now()}; { CCoinsViewCache view(&CoinsTip()); assert(view.GetBestBlock() == pindexDelete->GetBlockHash()); @@ -2602,7 +2623,8 @@ bool CChainState::DisconnectTip(BlockValidationState& state, DisconnectedBlockTr bool flushed = view.Flush(); assert(flushed); } - LogPrint(BCLog::BENCH, "- Disconnect block: %.2fms\n", (GetTimeMicros() - nStart) * MILLI); + LogPrint(BCLog::BENCH, "- Disconnect block: %.2fms\n", + Ticks<MillisecondsDouble>(SteadyClock::now() - time_start)); { // Prune locks that began at or after the tip should be moved backward so they get a chance to reorg @@ -2633,7 +2655,7 @@ bool CChainState::DisconnectTip(BlockValidationState& state, DisconnectedBlockTr } } - m_chain.SetTip(pindexDelete->pprev); + m_chain.SetTip(*pindexDelete->pprev); UpdateTip(pindexDelete->pprev); // Let wallets know transactions went from 1-confirmed to @@ -2642,11 +2664,11 @@ bool CChainState::DisconnectTip(BlockValidationState& state, DisconnectedBlockTr return true; } -static int64_t nTimeReadFromDiskTotal = 0; -static int64_t nTimeConnectTotal = 0; -static int64_t nTimeFlush = 0; -static int64_t nTimeChainState = 0; -static int64_t nTimePostConnect = 0; +static SteadyClock::duration time_read_from_disk_total{}; +static SteadyClock::duration time_connect_total{}; +static SteadyClock::duration time_flush{}; +static SteadyClock::duration time_chainstate{}; +static SteadyClock::duration time_post_connect{}; struct PerBlockConnectTrace { CBlockIndex* pindex = nullptr; @@ -2694,14 +2716,14 @@ public: * * The block is added to connectTrace if connection succeeds. */ -bool CChainState::ConnectTip(BlockValidationState& state, CBlockIndex* pindexNew, const std::shared_ptr<const CBlock>& pblock, ConnectTrace& connectTrace, DisconnectedBlockTransactions& disconnectpool) +bool Chainstate::ConnectTip(BlockValidationState& state, CBlockIndex* pindexNew, const std::shared_ptr<const CBlock>& pblock, ConnectTrace& connectTrace, DisconnectedBlockTransactions& disconnectpool) { AssertLockHeld(cs_main); if (m_mempool) AssertLockHeld(m_mempool->cs); assert(pindexNew->pprev == m_chain.Tip()); // Read block from disk. - int64_t nTime1 = GetTimeMicros(); + const auto time_1{SteadyClock::now()}; std::shared_ptr<const CBlock> pthisBlock; if (!pblock) { std::shared_ptr<CBlock> pblockNew = std::make_shared<CBlock>(); @@ -2715,9 +2737,13 @@ bool CChainState::ConnectTip(BlockValidationState& state, CBlockIndex* pindexNew } const CBlock& blockConnecting = *pthisBlock; // Apply the block atomically to the chain state. - int64_t nTime2 = GetTimeMicros(); nTimeReadFromDiskTotal += nTime2 - nTime1; - int64_t nTime3; - LogPrint(BCLog::BENCH, " - Load block from disk: %.2fms [%.2fs (%.2fms/blk)]\n", (nTime2 - nTime1) * MILLI, nTimeReadFromDiskTotal * MICRO, nTimeReadFromDiskTotal * MILLI / nBlocksTotal); + const auto time_2{SteadyClock::now()}; + time_read_from_disk_total += time_2 - time_1; + SteadyClock::time_point time_3; + LogPrint(BCLog::BENCH, " - Load block from disk: %.2fms [%.2fs (%.2fms/blk)]\n", + Ticks<MillisecondsDouble>(time_2 - time_1), + Ticks<SecondsDouble>(time_read_from_disk_total), + Ticks<MillisecondsDouble>(time_read_from_disk_total) / num_blocks_total); { CCoinsViewCache view(&CoinsTip()); bool rv = ConnectBlock(blockConnecting, state, pindexNew, view); @@ -2727,32 +2753,52 @@ bool CChainState::ConnectTip(BlockValidationState& state, CBlockIndex* pindexNew InvalidBlockFound(pindexNew, state); return error("%s: ConnectBlock %s failed, %s", __func__, pindexNew->GetBlockHash().ToString(), state.ToString()); } - nTime3 = GetTimeMicros(); nTimeConnectTotal += nTime3 - nTime2; - assert(nBlocksTotal > 0); - LogPrint(BCLog::BENCH, " - Connect total: %.2fms [%.2fs (%.2fms/blk)]\n", (nTime3 - nTime2) * MILLI, nTimeConnectTotal * MICRO, nTimeConnectTotal * MILLI / nBlocksTotal); + time_3 = SteadyClock::now(); + time_connect_total += time_3 - time_2; + assert(num_blocks_total > 0); + LogPrint(BCLog::BENCH, " - Connect total: %.2fms [%.2fs (%.2fms/blk)]\n", + Ticks<MillisecondsDouble>(time_3 - time_2), + Ticks<SecondsDouble>(time_connect_total), + Ticks<MillisecondsDouble>(time_connect_total) / num_blocks_total); bool flushed = view.Flush(); assert(flushed); } - int64_t nTime4 = GetTimeMicros(); nTimeFlush += nTime4 - nTime3; - LogPrint(BCLog::BENCH, " - Flush: %.2fms [%.2fs (%.2fms/blk)]\n", (nTime4 - nTime3) * MILLI, nTimeFlush * MICRO, nTimeFlush * MILLI / nBlocksTotal); + const auto time_4{SteadyClock::now()}; + time_flush += time_4 - time_3; + LogPrint(BCLog::BENCH, " - Flush: %.2fms [%.2fs (%.2fms/blk)]\n", + Ticks<MillisecondsDouble>(time_4 - time_3), + Ticks<SecondsDouble>(time_flush), + Ticks<MillisecondsDouble>(time_flush) / num_blocks_total); // Write the chain state to disk, if necessary. if (!FlushStateToDisk(state, FlushStateMode::IF_NEEDED)) { return false; } - int64_t nTime5 = GetTimeMicros(); nTimeChainState += nTime5 - nTime4; - LogPrint(BCLog::BENCH, " - Writing chainstate: %.2fms [%.2fs (%.2fms/blk)]\n", (nTime5 - nTime4) * MILLI, nTimeChainState * MICRO, nTimeChainState * MILLI / nBlocksTotal); + const auto time_5{SteadyClock::now()}; + time_chainstate += time_5 - time_4; + LogPrint(BCLog::BENCH, " - Writing chainstate: %.2fms [%.2fs (%.2fms/blk)]\n", + Ticks<MillisecondsDouble>(time_5 - time_4), + Ticks<SecondsDouble>(time_chainstate), + Ticks<MillisecondsDouble>(time_chainstate) / num_blocks_total); // Remove conflicting transactions from the mempool.; if (m_mempool) { m_mempool->removeForBlock(blockConnecting.vtx, pindexNew->nHeight); disconnectpool.removeForBlock(blockConnecting.vtx); } // Update m_chain & related variables. - m_chain.SetTip(pindexNew); + m_chain.SetTip(*pindexNew); UpdateTip(pindexNew); - 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); - LogPrint(BCLog::BENCH, "- Connect block: %.2fms [%.2fs (%.2fms/blk)]\n", (nTime6 - nTime1) * MILLI, nTimeTotal * MICRO, nTimeTotal * MILLI / nBlocksTotal); + const auto time_6{SteadyClock::now()}; + time_post_connect += time_6 - time_5; + time_total += time_6 - time_1; + LogPrint(BCLog::BENCH, " - Connect postprocess: %.2fms [%.2fs (%.2fms/blk)]\n", + Ticks<MillisecondsDouble>(time_6 - time_5), + Ticks<SecondsDouble>(time_post_connect), + Ticks<MillisecondsDouble>(time_post_connect) / num_blocks_total); + LogPrint(BCLog::BENCH, "- Connect block: %.2fms [%.2fs (%.2fms/blk)]\n", + Ticks<MillisecondsDouble>(time_6 - time_1), + Ticks<SecondsDouble>(time_total), + Ticks<MillisecondsDouble>(time_total) / num_blocks_total); connectTrace.BlockConnected(pindexNew, std::move(pthisBlock)); return true; @@ -2762,7 +2808,7 @@ bool CChainState::ConnectTip(BlockValidationState& state, CBlockIndex* pindexNew * Return the tip of the chain with the most work in it, that isn't * known to be invalid (it's however far from certain to be valid). */ -CBlockIndex* CChainState::FindMostWorkChain() +CBlockIndex* Chainstate::FindMostWorkChain() { AssertLockHeld(::cs_main); do { @@ -2821,7 +2867,7 @@ CBlockIndex* CChainState::FindMostWorkChain() } /** Delete all entries in setBlockIndexCandidates that are worse than the current tip. */ -void CChainState::PruneBlockIndexCandidates() { +void Chainstate::PruneBlockIndexCandidates() { // Note that we can't delete the current block itself, as we may need to return to it later in case a // reorganization to a better block fails. std::set<CBlockIndex*, CBlockIndexWorkComparator>::iterator it = setBlockIndexCandidates.begin(); @@ -2838,7 +2884,7 @@ void CChainState::PruneBlockIndexCandidates() { * * @returns true unless a system error occurred */ -bool CChainState::ActivateBestChainStep(BlockValidationState& state, CBlockIndex* pindexMostWork, const std::shared_ptr<const CBlock>& pblock, bool& fInvalidFound, ConnectTrace& connectTrace) +bool Chainstate::ActivateBestChainStep(BlockValidationState& state, CBlockIndex* pindexMostWork, const std::shared_ptr<const CBlock>& pblock, bool& fInvalidFound, ConnectTrace& connectTrace) { AssertLockHeld(cs_main); if (m_mempool) AssertLockHeld(m_mempool->cs); @@ -2930,7 +2976,7 @@ static SynchronizationState GetSynchronizationState(bool init) return SynchronizationState::INIT_DOWNLOAD; } -static bool NotifyHeaderTip(CChainState& chainstate) LOCKS_EXCLUDED(cs_main) { +static bool NotifyHeaderTip(Chainstate& chainstate) LOCKS_EXCLUDED(cs_main) { bool fNotify = false; bool fInitialBlockDownload = false; static CBlockIndex* pindexHeaderOld = nullptr; @@ -2947,7 +2993,7 @@ static bool NotifyHeaderTip(CChainState& chainstate) LOCKS_EXCLUDED(cs_main) { } // Send block tip changed notifications without cs_main if (fNotify) { - uiInterface.NotifyHeaderTip(GetSynchronizationState(fInitialBlockDownload), pindexHeader); + uiInterface.NotifyHeaderTip(GetSynchronizationState(fInitialBlockDownload), pindexHeader->nHeight, pindexHeader->nTime, false); } return fNotify; } @@ -2960,7 +3006,7 @@ static void LimitValidationInterfaceQueue() LOCKS_EXCLUDED(cs_main) { } } -bool CChainState::ActivateBestChain(BlockValidationState& state, std::shared_ptr<const CBlock> pblock) +bool Chainstate::ActivateBestChain(BlockValidationState& state, std::shared_ptr<const CBlock> pblock) { AssertLockNotHeld(m_chainstate_mutex); @@ -3062,7 +3108,7 @@ bool CChainState::ActivateBestChain(BlockValidationState& state, std::shared_ptr return true; } -bool CChainState::PreciousBlock(BlockValidationState& state, CBlockIndex* pindex) +bool Chainstate::PreciousBlock(BlockValidationState& state, CBlockIndex* pindex) { AssertLockNotHeld(m_chainstate_mutex); AssertLockNotHeld(::cs_main); @@ -3093,7 +3139,7 @@ bool CChainState::PreciousBlock(BlockValidationState& state, CBlockIndex* pindex return ActivateBestChain(state, std::shared_ptr<const CBlock>()); } -bool CChainState::InvalidateBlock(BlockValidationState& state, CBlockIndex* pindex) +bool Chainstate::InvalidateBlock(BlockValidationState& state, CBlockIndex* pindex) { AssertLockNotHeld(m_chainstate_mutex); AssertLockNotHeld(::cs_main); @@ -3236,7 +3282,7 @@ bool CChainState::InvalidateBlock(BlockValidationState& state, CBlockIndex* pind return true; } -void CChainState::ResetBlockFailureFlags(CBlockIndex *pindex) { +void Chainstate::ResetBlockFailureFlags(CBlockIndex *pindex) { AssertLockHeld(cs_main); int nHeight = pindex->nHeight; @@ -3269,7 +3315,7 @@ void CChainState::ResetBlockFailureFlags(CBlockIndex *pindex) { } /** Mark a block as having its data received and checked (up to BLOCK_VALID_TRANSACTIONS). */ -void CChainState::ReceivedBlockTransactions(const CBlock& block, CBlockIndex* pindexNew, const FlatFilePos& pos) +void Chainstate::ReceivedBlockTransactions(const CBlock& block, CBlockIndex* pindexNew, const FlatFilePos& pos) { AssertLockHeld(cs_main); pindexNew->nTx = block.vtx.size(); @@ -3435,6 +3481,22 @@ std::vector<unsigned char> ChainstateManager::GenerateCoinbaseCommitment(CBlock& return commitment; } +bool HasValidProofOfWork(const std::vector<CBlockHeader>& headers, const Consensus::Params& consensusParams) +{ + return std::all_of(headers.cbegin(), headers.cend(), + [&](const auto& header) { return CheckProofOfWork(header.GetHash(), header.nBits, consensusParams);}); +} + +arith_uint256 CalculateHeadersWork(const std::vector<CBlockHeader>& headers) +{ + arith_uint256 total_work{0}; + for (const CBlockHeader& header : headers) { + CBlockIndex dummy(header); + total_work += GetBlockProof(dummy); + } + return total_work; +} + /** Context-dependent validity checks. * By "context", we mean only the previous block headers, but not the UTXO * set; UTXO-related validity checks are done in ConnectBlock(). @@ -3444,7 +3506,7 @@ std::vector<unsigned char> ChainstateManager::GenerateCoinbaseCommitment(CBlock& * in ConnectBlock(). * Note that -reindex-chainstate skips the validation that happens here! */ -static bool ContextualCheckBlockHeader(const CBlockHeader& block, BlockValidationState& state, BlockManager& blockman, const ChainstateManager& chainman, const CBlockIndex* pindexPrev, int64_t nAdjustedTime) EXCLUSIVE_LOCKS_REQUIRED(::cs_main) +static bool ContextualCheckBlockHeader(const CBlockHeader& block, BlockValidationState& state, BlockManager& blockman, const ChainstateManager& chainman, const CBlockIndex* pindexPrev, NodeClock::time_point now) EXCLUSIVE_LOCKS_REQUIRED(::cs_main) { AssertLockHeld(::cs_main); assert(pindexPrev != nullptr); @@ -3472,8 +3534,9 @@ static bool ContextualCheckBlockHeader(const CBlockHeader& block, BlockValidatio return state.Invalid(BlockValidationResult::BLOCK_INVALID_HEADER, "time-too-old", "block's timestamp is too early"); // Check timestamp - if (block.GetBlockTime() > nAdjustedTime + MAX_FUTURE_BLOCK_TIME) + if (block.Time() > now + std::chrono::seconds{MAX_FUTURE_BLOCK_TIME}) { return state.Invalid(BlockValidationResult::BLOCK_TIME_FUTURE, "time-too-new", "block timestamp too far in the future"); + } // Reject blocks with outdated version if ((block.nVersion < 2 && DeploymentActiveAfter(pindexPrev, chainman, Consensus::DEPLOYMENT_HEIGHTINCB)) || @@ -3497,15 +3560,15 @@ static bool ContextualCheckBlock(const CBlock& block, BlockValidationState& stat const int nHeight = pindexPrev == nullptr ? 0 : pindexPrev->nHeight + 1; // Enforce BIP113 (Median Time Past). - int nLockTimeFlags = 0; + bool enforce_locktime_median_time_past{false}; if (DeploymentActiveAfter(pindexPrev, chainman, Consensus::DEPLOYMENT_CSV)) { assert(pindexPrev != nullptr); - nLockTimeFlags |= LOCKTIME_MEDIAN_TIME_PAST; + enforce_locktime_median_time_past = true; } - int64_t nLockTimeCutoff = (nLockTimeFlags & LOCKTIME_MEDIAN_TIME_PAST) - ? pindexPrev->GetMedianTimePast() - : block.GetBlockTime(); + const int64_t nLockTimeCutoff{enforce_locktime_median_time_past ? + pindexPrev->GetMedianTimePast() : + block.GetBlockTime()}; // Check that all transactions are finalized for (const auto& tx : block.vtx) { @@ -3574,9 +3637,10 @@ static bool ContextualCheckBlock(const CBlock& block, BlockValidationState& stat return true; } -bool ChainstateManager::AcceptBlockHeader(const CBlockHeader& block, BlockValidationState& state, CBlockIndex** ppindex) +bool ChainstateManager::AcceptBlockHeader(const CBlockHeader& block, BlockValidationState& state, CBlockIndex** ppindex, bool min_pow_checked) { AssertLockHeld(cs_main); + // Check for duplicate uint256 hash = block.GetHash(); BlockMap::iterator miSelf{m_blockman.m_block_index.find(hash)}; @@ -3610,7 +3674,7 @@ bool ChainstateManager::AcceptBlockHeader(const CBlockHeader& block, BlockValida LogPrint(BCLog::VALIDATION, "%s: %s prev block invalid\n", __func__, hash.ToString()); return state.Invalid(BlockValidationResult::BLOCK_INVALID_PREV, "bad-prevblk"); } - if (!ContextualCheckBlockHeader(block, state, m_blockman, *this, pindexPrev, m_adjusted_time_callback())) { + if (!ContextualCheckBlockHeader(block, state, m_blockman, *this, pindexPrev, m_options.adjusted_time_callback())) { LogPrint(BCLog::VALIDATION, "%s: Consensus::ContextualCheckBlockHeader: %s, %s\n", __func__, hash.ToString(), state.ToString()); return false; } @@ -3654,6 +3718,10 @@ bool ChainstateManager::AcceptBlockHeader(const CBlockHeader& block, BlockValida } } } + if (!min_pow_checked) { + LogPrint(BCLog::VALIDATION, "%s: not adding new block header %s, missing anti-dos proof-of-work validation\n", __func__, hash.ToString()); + return state.Invalid(BlockValidationResult::BLOCK_HEADER_LOW_WORK, "too-little-chainwork"); + } CBlockIndex* pindex{m_blockman.AddToBlockIndex(block, m_best_header)}; if (ppindex) @@ -3663,14 +3731,14 @@ bool ChainstateManager::AcceptBlockHeader(const CBlockHeader& block, BlockValida } // Exposed wrapper for AcceptBlockHeader -bool ChainstateManager::ProcessNewBlockHeaders(const std::vector<CBlockHeader>& headers, BlockValidationState& state, const CBlockIndex** ppindex) +bool ChainstateManager::ProcessNewBlockHeaders(const std::vector<CBlockHeader>& headers, bool min_pow_checked, BlockValidationState& state, const CBlockIndex** ppindex) { AssertLockNotHeld(cs_main); { LOCK(cs_main); for (const CBlockHeader& header : headers) { CBlockIndex *pindex = nullptr; // Use a temp pindex instead of ppindex to avoid a const_cast - bool accepted{AcceptBlockHeader(header, state, &pindex)}; + bool accepted{AcceptBlockHeader(header, state, &pindex, min_pow_checked)}; ActiveChainstate().CheckBlockIndex(); if (!accepted) { @@ -3692,8 +3760,33 @@ bool ChainstateManager::ProcessNewBlockHeaders(const std::vector<CBlockHeader>& return true; } +void ChainstateManager::ReportHeadersPresync(const arith_uint256& work, int64_t height, int64_t timestamp) +{ + AssertLockNotHeld(cs_main); + const auto& chainstate = ActiveChainstate(); + { + LOCK(cs_main); + // Don't report headers presync progress if we already have a post-minchainwork header chain. + // This means we lose reporting for potentially legitimate, but unlikely, deep reorgs, but + // prevent attackers that spam low-work headers from filling our logs. + if (m_best_header->nChainWork >= UintToArith256(GetConsensus().nMinimumChainWork)) return; + // Rate limit headers presync updates to 4 per second, as these are not subject to DoS + // protection. + auto now = std::chrono::steady_clock::now(); + if (now < m_last_presync_update + std::chrono::milliseconds{250}) return; + m_last_presync_update = now; + } + bool initial_download = chainstate.IsInitialBlockDownload(); + uiInterface.NotifyHeaderTip(GetSynchronizationState(initial_download), height, timestamp, /*presync=*/true); + if (initial_download) { + const int64_t blocks_left{(GetTime() - timestamp) / GetConsensus().nPowTargetSpacing}; + const double progress{100.0 * height / (height + blocks_left)}; + LogPrintf("Pre-synchronizing blockheaders, height: %d (~%.2f%%)\n", height, progress); + } +} + /** Store block on disk. If dbp is non-nullptr, the file is known to already reside on disk */ -bool CChainState::AcceptBlock(const std::shared_ptr<const CBlock>& pblock, BlockValidationState& state, CBlockIndex** ppindex, bool fRequested, const FlatFilePos* dbp, bool* fNewBlock) +bool Chainstate::AcceptBlock(const std::shared_ptr<const CBlock>& pblock, BlockValidationState& state, CBlockIndex** ppindex, bool fRequested, const FlatFilePos* dbp, bool* fNewBlock, bool min_pow_checked) { const CBlock& block = *pblock; @@ -3703,7 +3796,7 @@ bool CChainState::AcceptBlock(const std::shared_ptr<const CBlock>& pblock, Block CBlockIndex *pindexDummy = nullptr; CBlockIndex *&pindex = ppindex ? *ppindex : pindexDummy; - bool accepted_header{m_chainman.AcceptBlockHeader(block, state, &pindex)}; + bool accepted_header{m_chainman.AcceptBlockHeader(block, state, &pindex, min_pow_checked)}; CheckBlockIndex(); if (!accepted_header) @@ -3776,7 +3869,7 @@ bool CChainState::AcceptBlock(const std::shared_ptr<const CBlock>& pblock, Block return true; } -bool ChainstateManager::ProcessNewBlock(const std::shared_ptr<const CBlock>& block, bool force_processing, bool* new_block) +bool ChainstateManager::ProcessNewBlock(const std::shared_ptr<const CBlock>& block, bool force_processing, bool min_pow_checked, bool* new_block) { AssertLockNotHeld(cs_main); @@ -3797,7 +3890,7 @@ bool ChainstateManager::ProcessNewBlock(const std::shared_ptr<const CBlock>& blo bool ret = CheckBlock(*block, state, GetConsensus()); if (ret) { // Store to disk - ret = ActiveChainstate().AcceptBlock(block, state, &pindex, force_processing, nullptr, new_block); + ret = ActiveChainstate().AcceptBlock(block, state, &pindex, force_processing, nullptr, new_block, min_pow_checked); } if (!ret) { GetMainSignals().BlockChecked(*block, state); @@ -3818,7 +3911,7 @@ bool ChainstateManager::ProcessNewBlock(const std::shared_ptr<const CBlock>& blo MempoolAcceptResult ChainstateManager::ProcessTransaction(const CTransactionRef& tx, bool test_accept) { AssertLockHeld(cs_main); - CChainState& active_chainstate = ActiveChainstate(); + Chainstate& active_chainstate = ActiveChainstate(); if (!active_chainstate.GetMempool()) { TxValidationState state; state.Invalid(TxValidationResult::TX_NO_MEMPOOL, "no-mempool"); @@ -3831,10 +3924,10 @@ MempoolAcceptResult ChainstateManager::ProcessTransaction(const CTransactionRef& bool TestBlockValidity(BlockValidationState& state, const CChainParams& chainparams, - CChainState& chainstate, + Chainstate& chainstate, const CBlock& block, CBlockIndex* pindexPrev, - const std::function<int64_t()>& adjusted_time_callback, + const std::function<NodeClock::time_point()>& adjusted_time_callback, bool fCheckPOW, bool fCheckMerkleRoot) { @@ -3863,7 +3956,7 @@ bool TestBlockValidity(BlockValidationState& state, } /* This function is called from the RPC code for pruneblockchain */ -void PruneBlockFilesManual(CChainState& active_chainstate, int nManualPruneHeight) +void PruneBlockFilesManual(Chainstate& active_chainstate, int nManualPruneHeight) { BlockValidationState state; if (!active_chainstate.FlushStateToDisk( @@ -3872,16 +3965,14 @@ void PruneBlockFilesManual(CChainState& active_chainstate, int nManualPruneHeigh } } -void CChainState::LoadMempool(const ArgsManager& args) +void Chainstate::LoadMempool(const fs::path& load_path, FopenFn mockable_fopen_function) { if (!m_mempool) return; - if (args.GetBoolArg("-persistmempool", DEFAULT_PERSIST_MEMPOOL)) { - ::LoadMempool(*m_mempool, *this); - } - m_mempool->SetIsLoaded(!ShutdownRequested()); + ::LoadMempool(*m_mempool, load_path, *this, mockable_fopen_function); + m_mempool->SetLoadTried(!ShutdownRequested()); } -bool CChainState::LoadChainTip() +bool Chainstate::LoadChainTip() { AssertLockHeld(cs_main); const CCoinsViewCache& coins_cache = CoinsTip(); @@ -3897,7 +3988,7 @@ bool CChainState::LoadChainTip() if (!pindex) { return false; } - m_chain.SetTip(pindex); + m_chain.SetTip(*pindex); PruneBlockIndexCandidates(); tip = m_chain.Tip(); @@ -3920,7 +4011,7 @@ CVerifyDB::~CVerifyDB() } bool CVerifyDB::VerifyDB( - CChainState& chainstate, + Chainstate& chainstate, const Consensus::Params& consensus_params, CCoinsView& coinsview, int nCheckLevel, int nCheckDepth) @@ -4036,7 +4127,7 @@ bool CVerifyDB::VerifyDB( } /** Apply the effects of a block on the utxo cache, ignoring that it may already have been applied. */ -bool CChainState::RollforwardBlock(const CBlockIndex* pindex, CCoinsViewCache& inputs) +bool Chainstate::RollforwardBlock(const CBlockIndex* pindex, CCoinsViewCache& inputs) { AssertLockHeld(cs_main); // TODO: merge with ConnectBlock @@ -4057,7 +4148,7 @@ bool CChainState::RollforwardBlock(const CBlockIndex* pindex, CCoinsViewCache& i return true; } -bool CChainState::ReplayBlocks() +bool Chainstate::ReplayBlocks() { LOCK(cs_main); @@ -4125,7 +4216,7 @@ bool CChainState::ReplayBlocks() return true; } -bool CChainState::NeedsRedownload() const +bool Chainstate::NeedsRedownload() const { AssertLockHeld(cs_main); @@ -4143,7 +4234,7 @@ bool CChainState::NeedsRedownload() const return false; } -void CChainState::UnloadBlockIndex() +void Chainstate::UnloadBlockIndex() { AssertLockHeld(::cs_main); nBlockSequenceId = 1; @@ -4212,7 +4303,7 @@ bool ChainstateManager::LoadBlockIndex() // detecting "holistically" whether the block index under consideration // relied on an assumed-valid ancestor, but this proved to be too slow to // be practical. - for (CChainState* chainstate : GetAll()) { + for (Chainstate* chainstate : GetAll()) { if (chainstate->reliesOnAssumedValid() || pindex->nHeight < first_assumed_valid_height) { chainstate->setBlockIndexCandidates.insert(pindex); @@ -4241,7 +4332,7 @@ bool ChainstateManager::LoadBlockIndex() return true; } -bool CChainState::LoadGenesisBlock() +bool Chainstate::LoadGenesisBlock() { LOCK(cs_main); @@ -4267,12 +4358,17 @@ bool CChainState::LoadGenesisBlock() return true; } -void CChainState::LoadExternalBlockFile(FILE* fileIn, FlatFilePos* dbp) +void Chainstate::LoadExternalBlockFile( + FILE* fileIn, + FlatFilePos* dbp, + std::multimap<uint256, FlatFilePos>* blocks_with_unknown_parent) { AssertLockNotHeld(m_chainstate_mutex); - // Map of disk positions for blocks with unknown parent (only used for reindex) - static std::multimap<uint256, FlatFilePos> mapBlocksUnknownParent; - int64_t nStart = GetTimeMillis(); + + // Either both should be specified (-reindex), or neither (-loadblock). + assert(!dbp == !blocks_with_unknown_parent); + + const auto start{SteadyClock::now()}; int nLoaded = 0; try { @@ -4321,8 +4417,9 @@ void CChainState::LoadExternalBlockFile(FILE* fileIn, FlatFilePos* dbp) if (hash != m_params.GetConsensus().hashGenesisBlock && !m_blockman.LookupBlockIndex(block.hashPrevBlock)) { LogPrint(BCLog::REINDEX, "%s: Out of order block %s, parent %s not known\n", __func__, hash.ToString(), block.hashPrevBlock.ToString()); - if (dbp) - mapBlocksUnknownParent.insert(std::make_pair(block.hashPrevBlock, *dbp)); + if (dbp && blocks_with_unknown_parent) { + blocks_with_unknown_parent->emplace(block.hashPrevBlock, *dbp); + } continue; } @@ -4330,7 +4427,7 @@ void CChainState::LoadExternalBlockFile(FILE* fileIn, FlatFilePos* dbp) const CBlockIndex* pindex = m_blockman.LookupBlockIndex(hash); if (!pindex || (pindex->nStatus & BLOCK_HAVE_DATA) == 0) { BlockValidationState state; - if (AcceptBlock(pblock, state, nullptr, true, dbp, nullptr)) { + if (AcceptBlock(pblock, state, nullptr, true, dbp, nullptr, true)) { nLoaded++; } if (state.IsError()) { @@ -4351,13 +4448,15 @@ void CChainState::LoadExternalBlockFile(FILE* fileIn, FlatFilePos* dbp) NotifyHeaderTip(*this); + if (!blocks_with_unknown_parent) continue; + // Recursively process earlier encountered successors of this block std::deque<uint256> queue; queue.push_back(hash); while (!queue.empty()) { uint256 head = queue.front(); queue.pop_front(); - std::pair<std::multimap<uint256, FlatFilePos>::iterator, std::multimap<uint256, FlatFilePos>::iterator> range = mapBlocksUnknownParent.equal_range(head); + auto range = blocks_with_unknown_parent->equal_range(head); while (range.first != range.second) { std::multimap<uint256, FlatFilePos>::iterator it = range.first; std::shared_ptr<CBlock> pblockrecursive = std::make_shared<CBlock>(); @@ -4366,13 +4465,13 @@ void CChainState::LoadExternalBlockFile(FILE* fileIn, FlatFilePos* dbp) head.ToString()); LOCK(cs_main); BlockValidationState dummy; - if (AcceptBlock(pblockrecursive, dummy, nullptr, true, &it->second, nullptr)) { + if (AcceptBlock(pblockrecursive, dummy, nullptr, true, &it->second, nullptr, true)) { nLoaded++; queue.push_back(pblockrecursive->GetHash()); } } range.first++; - mapBlocksUnknownParent.erase(it); + blocks_with_unknown_parent->erase(it); NotifyHeaderTip(*this); } } @@ -4383,10 +4482,10 @@ void CChainState::LoadExternalBlockFile(FILE* fileIn, FlatFilePos* dbp) } catch (const std::runtime_error& e) { AbortNode(std::string("System error: ") + e.what()); } - LogPrintf("Loaded %i blocks from external file in %dms\n", nLoaded, GetTimeMillis() - nStart); + LogPrintf("Loaded %i blocks from external file in %dms\n", nLoaded, Ticks<std::chrono::milliseconds>(SteadyClock::now() - start)); } -void CChainState::CheckBlockIndex() +void Chainstate::CheckBlockIndex() { if (!fCheckBlockIndex) { return; @@ -4608,7 +4707,7 @@ void CChainState::CheckBlockIndex() assert(nNodes == forward.size()); } -std::string CChainState::ToString() +std::string Chainstate::ToString() { AssertLockHeld(::cs_main); CBlockIndex* tip = m_chain.Tip(); @@ -4617,7 +4716,7 @@ std::string CChainState::ToString() tip ? tip->nHeight : -1, tip ? tip->GetBlockHash().ToString() : "null"); } -bool CChainState::ResizeCoinsCaches(size_t coinstip_size, size_t coinsdb_size) +bool Chainstate::ResizeCoinsCaches(size_t coinstip_size, size_t coinsdb_size) { AssertLockHeld(::cs_main); if (coinstip_size == m_coinstip_cache_size_bytes && @@ -4649,153 +4748,6 @@ bool CChainState::ResizeCoinsCaches(size_t coinstip_size, size_t coinsdb_size) return ret; } -static const uint64_t MEMPOOL_DUMP_VERSION = 1; - -bool LoadMempool(CTxMemPool& pool, CChainState& active_chainstate, FopenFn mockable_fopen_function) -{ - int64_t nExpiryTimeout = gArgs.GetIntArg("-mempoolexpiry", DEFAULT_MEMPOOL_EXPIRY) * 60 * 60; - FILE* filestr{mockable_fopen_function(gArgs.GetDataDirNet() / "mempool.dat", "rb")}; - CAutoFile file(filestr, SER_DISK, CLIENT_VERSION); - if (file.IsNull()) { - LogPrintf("Failed to open mempool file from disk. Continuing anyway.\n"); - return false; - } - - int64_t count = 0; - int64_t expired = 0; - int64_t failed = 0; - int64_t already_there = 0; - int64_t unbroadcast = 0; - int64_t nNow = GetTime(); - - try { - uint64_t version; - file >> version; - if (version != MEMPOOL_DUMP_VERSION) { - return false; - } - uint64_t num; - file >> num; - while (num) { - --num; - CTransactionRef tx; - int64_t nTime; - int64_t nFeeDelta; - file >> tx; - file >> nTime; - file >> nFeeDelta; - - CAmount amountdelta = nFeeDelta; - if (amountdelta) { - pool.PrioritiseTransaction(tx->GetHash(), amountdelta); - } - if (nTime > nNow - nExpiryTimeout) { - LOCK(cs_main); - const auto& accepted = AcceptToMemoryPool(active_chainstate, tx, nTime, /*bypass_limits=*/false, /*test_accept=*/false); - if (accepted.m_result_type == MempoolAcceptResult::ResultType::VALID) { - ++count; - } else { - // mempool may contain the transaction already, e.g. from - // wallet(s) having loaded it while we were processing - // mempool transactions; consider these as valid, instead of - // failed, but mark them as 'already there' - if (pool.exists(GenTxid::Txid(tx->GetHash()))) { - ++already_there; - } else { - ++failed; - } - } - } else { - ++expired; - } - if (ShutdownRequested()) - return false; - } - std::map<uint256, CAmount> mapDeltas; - file >> mapDeltas; - - for (const auto& i : mapDeltas) { - pool.PrioritiseTransaction(i.first, i.second); - } - - std::set<uint256> unbroadcast_txids; - file >> unbroadcast_txids; - unbroadcast = unbroadcast_txids.size(); - for (const auto& txid : unbroadcast_txids) { - // Ensure transactions were accepted to mempool then add to - // unbroadcast set. - if (pool.get(txid) != nullptr) pool.AddUnbroadcastTx(txid); - } - } catch (const std::exception& e) { - LogPrintf("Failed to deserialize mempool data on disk: %s. Continuing anyway.\n", e.what()); - return false; - } - - LogPrintf("Imported mempool transactions from disk: %i succeeded, %i failed, %i expired, %i already there, %i waiting for initial broadcast\n", count, failed, expired, already_there, unbroadcast); - return true; -} - -bool DumpMempool(const CTxMemPool& pool, FopenFn mockable_fopen_function, bool skip_file_commit) -{ - int64_t start = GetTimeMicros(); - - std::map<uint256, CAmount> mapDeltas; - std::vector<TxMempoolInfo> vinfo; - std::set<uint256> unbroadcast_txids; - - static Mutex dump_mutex; - LOCK(dump_mutex); - - { - LOCK(pool.cs); - for (const auto &i : pool.mapDeltas) { - mapDeltas[i.first] = i.second; - } - vinfo = pool.infoAll(); - unbroadcast_txids = pool.GetUnbroadcastTxs(); - } - - int64_t mid = GetTimeMicros(); - - try { - FILE* filestr{mockable_fopen_function(gArgs.GetDataDirNet() / "mempool.dat.new", "wb")}; - if (!filestr) { - return false; - } - - CAutoFile file(filestr, SER_DISK, CLIENT_VERSION); - - uint64_t version = MEMPOOL_DUMP_VERSION; - file << version; - - file << (uint64_t)vinfo.size(); - for (const auto& i : vinfo) { - file << *(i.tx); - file << int64_t{count_seconds(i.m_time)}; - file << int64_t{i.nFeeDelta}; - mapDeltas.erase(i.tx->GetHash()); - } - - file << mapDeltas; - - LogPrintf("Writing %d unbroadcast transactions to disk.\n", unbroadcast_txids.size()); - file << unbroadcast_txids; - - if (!skip_file_commit && !FileCommit(file.Get())) - throw std::runtime_error("FileCommit failed"); - file.fclose(); - if (!RenameOver(gArgs.GetDataDirNet() / "mempool.dat.new", gArgs.GetDataDirNet() / "mempool.dat")) { - throw std::runtime_error("Rename failed"); - } - int64_t last = GetTimeMicros(); - LogPrintf("Dumped mempool: %gs to copy, %gs to dump\n", (mid-start)*MICRO, (last-mid)*MICRO); - } catch (const std::exception& e) { - LogPrintf("Failed to dump mempool: %s. Continuing anyway.\n", e.what()); - return false; - } - return true; -} - //! Guess how far we are in the verification process at the given block index //! require cs_main if pindex has not been validated yet (because nChainTx might be unset) double GuessVerificationProgress(const ChainTxData& data, const CBlockIndex *pindex) { @@ -4825,10 +4777,10 @@ std::optional<uint256> ChainstateManager::SnapshotBlockhash() const return std::nullopt; } -std::vector<CChainState*> ChainstateManager::GetAll() +std::vector<Chainstate*> ChainstateManager::GetAll() { LOCK(::cs_main); - std::vector<CChainState*> out; + std::vector<Chainstate*> out; if (!IsSnapshotValidated() && m_ibd_chainstate) { out.push_back(m_ibd_chainstate.get()); @@ -4841,18 +4793,18 @@ std::vector<CChainState*> ChainstateManager::GetAll() return out; } -CChainState& ChainstateManager::InitializeChainstate( +Chainstate& ChainstateManager::InitializeChainstate( CTxMemPool* mempool, const std::optional<uint256>& snapshot_blockhash) { AssertLockHeld(::cs_main); bool is_snapshot = snapshot_blockhash.has_value(); - std::unique_ptr<CChainState>& to_modify = + std::unique_ptr<Chainstate>& to_modify = is_snapshot ? m_snapshot_chainstate : m_ibd_chainstate; if (to_modify) { throw std::logic_error("should not be overwriting a chainstate"); } - to_modify.reset(new CChainState(mempool, m_blockman, *this, snapshot_blockhash)); + to_modify.reset(new Chainstate(mempool, m_blockman, *this, snapshot_blockhash)); // Snapshot chainstates and initial IBD chaintates always become active. if (is_snapshot || (!is_snapshot && !m_active_chainstate)) { @@ -4878,7 +4830,7 @@ const AssumeutxoData* ExpectedAssumeutxo( } bool ChainstateManager::ActivateSnapshot( - CAutoFile& coins_file, + AutoFile& coins_file, const SnapshotMetadata& metadata, bool in_memory) { @@ -4922,7 +4874,7 @@ bool ChainstateManager::ActivateSnapshot( } auto snapshot_chainstate = WITH_LOCK(::cs_main, - return std::make_unique<CChainState>( + return std::make_unique<Chainstate>( /*mempool=*/nullptr, m_blockman, *this, base_blockhash)); { @@ -4972,8 +4924,8 @@ static void FlushSnapshotToDisk(CCoinsViewCache& coins_cache, bool snapshot_load } bool ChainstateManager::PopulateAndValidateSnapshot( - CChainState& snapshot_chainstate, - CAutoFile& coins_file, + Chainstate& snapshot_chainstate, + AutoFile& coins_file, const SnapshotMetadata& metadata) { // It's okay to release cs_main before we're done using `coins_cache` because we know @@ -5066,7 +5018,7 @@ bool ChainstateManager::PopulateAndValidateSnapshot( // Important that we set this. This and the coins_cache accesses above are // sort of a layer violation, but either we reach into the innards of - // CCoinsViewCache here or we have to invert some of the CChainState to + // CCoinsViewCache here or we have to invert some of the Chainstate to // embed them in a snapshot-activation-specific CCoinsViewCache bulk load // method. coins_cache.SetBestBlock(base_blockhash); @@ -5113,7 +5065,7 @@ bool ChainstateManager::PopulateAndValidateSnapshot( return false; } - snapshot_chainstate.m_chain.SetTip(snapshot_start_block); + snapshot_chainstate.m_chain.SetTip(*snapshot_start_block); // The remainder of this function requires modifying data protected by cs_main. LOCK(::cs_main); @@ -5145,7 +5097,7 @@ bool ChainstateManager::PopulateAndValidateSnapshot( index->nStatus |= BLOCK_ASSUMED_VALID; } - // Fake BLOCK_OPT_WITNESS so that CChainState::NeedsRedownload() + // Fake BLOCK_OPT_WITNESS so that Chainstate::NeedsRedownload() // won't ask to rewind the entire assumed-valid chain on startup. if (DeploymentActiveAt(*index, *this, Consensus::DEPLOYMENT_SEGWIT)) { index->nStatus |= BLOCK_OPT_WITNESS; @@ -5168,7 +5120,7 @@ bool ChainstateManager::PopulateAndValidateSnapshot( return true; } -CChainState& ChainstateManager::ActiveChainstate() const +Chainstate& ChainstateManager::ActiveChainstate() const { LOCK(::cs_main); assert(m_active_chainstate); diff --git a/src/validation.h b/src/validation.h index cc94add3cb..c882eac408 100644 --- a/src/validation.h +++ b/src/validation.h @@ -21,6 +21,7 @@ #include <node/blockstorage.h> #include <policy/feerate.h> #include <policy/packages.h> +#include <policy/policy.h> #include <script/script_error.h> #include <sync.h> #include <txdb.h> @@ -42,7 +43,7 @@ #include <utility> #include <vector> -class CChainState; +class Chainstate; class CBlockTreeDB; class CTxMemPool; class ChainstateManager; @@ -58,37 +59,12 @@ namespace Consensus { struct Params; } // namespace Consensus -/** Default for -limitancestorcount, max number of in-mempool ancestors */ -static const unsigned int DEFAULT_ANCESTOR_LIMIT = 25; -/** Default for -limitancestorsize, maximum kilobytes of tx + all in-mempool ancestors */ -static const unsigned int DEFAULT_ANCESTOR_SIZE_LIMIT = 101; -/** Default for -limitdescendantcount, max number of in-mempool descendants */ -static const unsigned int DEFAULT_DESCENDANT_LIMIT = 25; -/** Default for -limitdescendantsize, maximum kilobytes of in-mempool descendants */ -static const unsigned int DEFAULT_DESCENDANT_SIZE_LIMIT = 101; - -// If a package is submitted, it must be within the mempool's ancestor/descendant limits. Since a -// submitted package must be child-with-unconfirmed-parents (all of the transactions are an ancestor -// of the child), package limits are ultimately bounded by mempool package limits. Ensure that the -// defaults reflect this constraint. -static_assert(DEFAULT_DESCENDANT_LIMIT >= MAX_PACKAGE_COUNT); -static_assert(DEFAULT_ANCESTOR_LIMIT >= MAX_PACKAGE_COUNT); -static_assert(DEFAULT_ANCESTOR_SIZE_LIMIT >= MAX_PACKAGE_SIZE); -static_assert(DEFAULT_DESCENDANT_SIZE_LIMIT >= MAX_PACKAGE_SIZE); - -/** Default for -mempoolexpiry, expiration time for mempool transactions in hours */ -static const unsigned int DEFAULT_MEMPOOL_EXPIRY = 336; /** Maximum number of dedicated script-checking threads allowed */ static const int MAX_SCRIPTCHECK_THREADS = 15; /** -par default (number of script-checking threads, 0 = auto) */ static const int DEFAULT_SCRIPTCHECK_THREADS = 0; static const int64_t DEFAULT_MAX_TIP_AGE = 24 * 60 * 60; static const bool DEFAULT_CHECKPOINTS_ENABLED = true; -static const bool DEFAULT_TXINDEX = false; -static constexpr bool DEFAULT_COINSTATSINDEX{false}; -static const char* const DEFAULT_BLOCKFILTERINDEX = "0"; -/** Default for -persistmempool */ -static const bool DEFAULT_PERSIST_MEMPOOL = true; /** Default for -stopatheight */ static const int DEFAULT_STOPATHEIGHT = 0; /** Block files containing a block-height within MIN_BLOCKS_TO_KEEP of ActiveChain().Tip() will not be pruned. */ @@ -113,7 +89,7 @@ enum class SynchronizationState { }; extern RecursiveMutex cs_main; -extern Mutex g_best_block_mutex; +extern GlobalMutex g_best_block_mutex; extern std::condition_variable g_best_block_cv; /** Used to notify getblocktemplate RPC of new tips. */ extern uint256 g_best_block; @@ -121,7 +97,6 @@ extern uint256 g_best_block; * False indicates all script checking is done on the main threadMessageHandler thread. */ extern bool g_parallel_script_checks; -extern bool fRequireStandard; extern bool fCheckBlockIndex; extern bool fCheckpointsEnabled; /** If the tip is older than this (in seconds), the node is considered to be in initial block download. */ @@ -149,7 +124,7 @@ bool AbortNode(BlockValidationState& state, const std::string& strMessage, const double GuessVerificationProgress(const ChainTxData& data, const CBlockIndex* pindex); /** Prune block files up to a given height */ -void PruneBlockFilesManual(CChainState& active_chainstate, int nManualPruneHeight); +void PruneBlockFilesManual(Chainstate& active_chainstate, int nManualPruneHeight); /** * Validation result for a single transaction mempool acceptance. @@ -169,7 +144,7 @@ struct MempoolAcceptResult { const TxValidationState m_state; // The following fields are only present when m_result_type = ResultType::VALID or MEMPOOL_ENTRY - /** Mempool transactions replaced by the tx per BIP 125 rules. */ + /** Mempool transactions replaced by the tx. */ const std::optional<std::list<CTransactionRef>> m_replaced_transactions; /** Virtual size as used by the mempool, calculated using serialized size and sigops. */ const std::optional<int64_t> m_vsize; @@ -262,7 +237,7 @@ struct PackageMempoolAcceptResult * * @returns a MempoolAcceptResult indicating whether the transaction was accepted/rejected with reason. */ -MempoolAcceptResult AcceptToMemoryPool(CChainState& active_chainstate, const CTransactionRef& tx, +MempoolAcceptResult AcceptToMemoryPool(Chainstate& active_chainstate, const CTransactionRef& tx, int64_t accept_time, bool bypass_limits, bool test_accept) EXCLUSIVE_LOCKS_REQUIRED(cs_main); @@ -274,16 +249,16 @@ MempoolAcceptResult AcceptToMemoryPool(CChainState& active_chainstate, const CTr * If a transaction fails, validation will exit early and some results may be missing. It is also * possible for the package to be partially submitted. */ -PackageMempoolAcceptResult ProcessNewPackage(CChainState& active_chainstate, CTxMemPool& pool, +PackageMempoolAcceptResult ProcessNewPackage(Chainstate& active_chainstate, CTxMemPool& pool, const Package& txns, bool test_accept) EXCLUSIVE_LOCKS_REQUIRED(cs_main); -/* Transaction policy functions */ +/* Mempool validation helper functions */ /** * Check if transaction will be final in the next block to be created. */ -bool CheckFinalTxAtTip(const CBlockIndex* active_chain_tip, const CTransaction& tx) EXCLUSIVE_LOCKS_REQUIRED(::cs_main); +bool CheckFinalTxAtTip(const CBlockIndex& active_chain_tip, const CTransaction& tx) EXCLUSIVE_LOCKS_REQUIRED(::cs_main); /** * Check if transaction will be BIP68 final in the next block to be created on top of tip. @@ -345,7 +320,7 @@ public: }; /** Initializes the script-execution cache */ -void InitScriptExecutionCache(); +[[nodiscard]] bool InitScriptExecutionCache(size_t max_size_bytes); /** Functions for validating blocks and updating the block tree */ @@ -355,20 +330,26 @@ bool CheckBlock(const CBlock& block, BlockValidationState& state, const Consensu /** Check a block is completely valid from start to finish (only works on top of our current best block) */ bool TestBlockValidity(BlockValidationState& state, const CChainParams& chainparams, - CChainState& chainstate, + Chainstate& chainstate, const CBlock& block, CBlockIndex* pindexPrev, - const std::function<int64_t()>& adjusted_time_callback, + const std::function<NodeClock::time_point()>& adjusted_time_callback, bool fCheckPOW = true, bool fCheckMerkleRoot = true) EXCLUSIVE_LOCKS_REQUIRED(cs_main); +/** Check with the proof of work on each blockheader matches the value in nBits */ +bool HasValidProofOfWork(const std::vector<CBlockHeader>& headers, const Consensus::Params& consensusParams); + +/** Return the sum of the work on a given set of headers */ +arith_uint256 CalculateHeadersWork(const std::vector<CBlockHeader>& headers); + /** RAII wrapper for VerifyDB: Verify consistency of the block and coin databases */ class CVerifyDB { public: CVerifyDB(); ~CVerifyDB(); bool VerifyDB( - CChainState& chainstate, + Chainstate& chainstate, const Consensus::Params& consensus_params, CCoinsView& coinsview, int nCheckLevel, @@ -384,7 +365,7 @@ enum DisconnectResult class ConnectTrace; -/** @see CChainState::FlushStateToDisk */ +/** @see Chainstate::FlushStateToDisk */ enum class FlushStateMode { NONE, IF_NEEDED, @@ -437,7 +418,7 @@ enum class CoinsCacheSizeState }; /** - * CChainState stores and provides an API to update our local knowledge of the + * Chainstate stores and provides an API to update our local knowledge of the * current best chain. * * Eventually, the API here is targeted at being exposed externally as a @@ -450,7 +431,7 @@ enum class CoinsCacheSizeState * whereas block information and metadata independent of the current tip is * kept in `BlockManager`. */ -class CChainState +class Chainstate { protected: /** @@ -488,7 +469,7 @@ protected: public: //! Reference to a BlockManager instance which itself is shared across all - //! CChainState instances. + //! Chainstate instances. node::BlockManager& m_blockman; /** Chain parameters for this chainstate */ @@ -500,7 +481,7 @@ public: //! chainstate within deeply nested method calls. ChainstateManager& m_chainman; - explicit CChainState( + explicit Chainstate( CTxMemPool* mempool, node::BlockManager& blockman, ChainstateManager& chainman, @@ -596,8 +577,36 @@ public: bool ResizeCoinsCaches(size_t coinstip_size, size_t coinsdb_size) EXCLUSIVE_LOCKS_REQUIRED(::cs_main); - /** Import blocks from an external file */ - void LoadExternalBlockFile(FILE* fileIn, FlatFilePos* dbp = nullptr) + /** + * Import blocks from an external file + * + * During reindexing, this function is called for each block file (datadir/blocks/blk?????.dat). + * It reads all blocks contained in the given file and attempts to process them (add them to the + * block index). The blocks may be out of order within each file and across files. Often this + * function reads a block but finds that its parent hasn't been read yet, so the block can't be + * processed yet. The function will add an entry to the blocks_with_unknown_parent map (which is + * passed as an argument), so that when the block's parent is later read and processed, this + * function can re-read the child block from disk and process it. + * + * Because a block's parent may be in a later file, not just later in the same file, the + * blocks_with_unknown_parent map must be passed in and out with each call. It's a multimap, + * rather than just a map, because multiple blocks may have the same parent (when chain splits + * or stale blocks exist). It maps from parent-hash to child-disk-position. + * + * This function can also be used to read blocks from user-specified block files using the + * -loadblock= option. There's no unknown-parent tracking, so the last two arguments are omitted. + * + * + * @param[in] fileIn FILE handle to file containing blocks to read + * @param[in] dbp (optional) Disk block position (only for reindex) + * @param[in,out] blocks_with_unknown_parent (optional) Map of disk positions for blocks with + * unknown parent, key is parent block hash + * (only used for reindex) + * */ + void LoadExternalBlockFile( + FILE* fileIn, + FlatFilePos* dbp = nullptr, + std::multimap<uint256, FlatFilePos>* blocks_with_unknown_parent = nullptr) EXCLUSIVE_LOCKS_REQUIRED(!m_chainstate_mutex); /** @@ -644,7 +653,7 @@ public: EXCLUSIVE_LOCKS_REQUIRED(!m_chainstate_mutex) LOCKS_EXCLUDED(::cs_main); - bool AcceptBlock(const std::shared_ptr<const CBlock>& pblock, BlockValidationState& state, CBlockIndex** ppindex, bool fRequested, const FlatFilePos* dbp, bool* fNewBlock) EXCLUSIVE_LOCKS_REQUIRED(cs_main); + bool AcceptBlock(const std::shared_ptr<const CBlock>& pblock, BlockValidationState& state, CBlockIndex** ppindex, bool fRequested, const FlatFilePos* dbp, bool* fNewBlock, bool min_pow_checked) EXCLUSIVE_LOCKS_REQUIRED(cs_main); // Block (dis)connection on a given view: DisconnectResult DisconnectBlock(const CBlock& block, const CBlockIndex* pindex, CCoinsViewCache& view) @@ -698,7 +707,7 @@ public: void CheckBlockIndex(); /** Load the persisted mempool from disk */ - void LoadMempool(const ArgsManager& args); + void LoadMempool(const fs::path& load_path, fsbridge::FopenFn mockable_fopen_function = fsbridge::fopen); /** Update the chain tip based on database information, i.e. CoinsTip()'s best block. */ bool LoadChainTip() EXCLUSIVE_LOCKS_REQUIRED(cs_main); @@ -802,7 +811,7 @@ private: //! This is especially important when, e.g., calling ActivateBestChain() //! on all chainstates because we are not able to hold ::cs_main going into //! that call. - std::unique_ptr<CChainState> m_ibd_chainstate GUARDED_BY(::cs_main); + std::unique_ptr<Chainstate> m_ibd_chainstate GUARDED_BY(::cs_main); //! A chainstate initialized on the basis of a UTXO snapshot. If this is //! non-null, it is always our active chainstate. @@ -813,7 +822,7 @@ private: //! This is especially important when, e.g., calling ActivateBestChain() //! on all chainstates because we are not able to hold ::cs_main going into //! that call. - std::unique_ptr<CChainState> m_snapshot_chainstate GUARDED_BY(::cs_main); + std::unique_ptr<Chainstate> m_snapshot_chainstate GUARDED_BY(::cs_main); //! Points to either the ibd or snapshot chainstate; indicates our //! most-work chain. @@ -824,7 +833,7 @@ private: //! This is especially important when, e.g., calling ActivateBestChain() //! on all chainstates because we are not able to hold ::cs_main going into //! that call. - CChainState* m_active_chainstate GUARDED_BY(::cs_main) {nullptr}; + Chainstate* m_active_chainstate GUARDED_BY(::cs_main) {nullptr}; //! If true, the assumed-valid chainstate has been fully validated //! by the background validation chainstate. @@ -832,36 +841,54 @@ private: CBlockIndex* m_best_invalid GUARDED_BY(::cs_main){nullptr}; - const CChainParams m_chainparams; - - const std::function<int64_t()> m_adjusted_time_callback; - //! Internal helper for ActivateSnapshot(). [[nodiscard]] bool PopulateAndValidateSnapshot( - CChainState& snapshot_chainstate, - CAutoFile& coins_file, + Chainstate& snapshot_chainstate, + AutoFile& coins_file, const node::SnapshotMetadata& metadata); /** * If a block header hasn't already been seen, call CheckBlockHeader on it, ensure * that it doesn't descend from an invalid block, and then add it to m_block_index. + * Caller must set min_pow_checked=true in order to add a new header to the + * block index (permanent memory storage), indicating that the header is + * known to be part of a sufficiently high-work chain (anti-dos check). */ bool AcceptBlockHeader( const CBlockHeader& block, BlockValidationState& state, - CBlockIndex** ppindex) EXCLUSIVE_LOCKS_REQUIRED(cs_main); - friend CChainState; + CBlockIndex** ppindex, + bool min_pow_checked) EXCLUSIVE_LOCKS_REQUIRED(cs_main); + friend Chainstate; + + /** Most recent headers presync progress update, for rate-limiting. */ + std::chrono::time_point<std::chrono::steady_clock> m_last_presync_update GUARDED_BY(::cs_main) {}; public: - using Options = ChainstateManagerOpts; + using Options = kernel::ChainstateManagerOpts; - explicit ChainstateManager(const Options& opts) - : m_chainparams{opts.chainparams}, - m_adjusted_time_callback{Assert(opts.adjusted_time_callback)} {}; + explicit ChainstateManager(Options options) : m_options{std::move(options)} + { + Assert(m_options.adjusted_time_callback); + } - const CChainParams& GetParams() const { return m_chainparams; } - const Consensus::Params& GetConsensus() const { return m_chainparams.GetConsensus(); } + const CChainParams& GetParams() const { return m_options.chainparams; } + const Consensus::Params& GetConsensus() const { return m_options.chainparams.GetConsensus(); } + + /** + * Alias for ::cs_main. + * Should be used in new code to make it easier to make ::cs_main a member + * of this class. + * Generally, methods of this class should be annotated to require this + * mutex. This will make calling code more verbose, but also help to: + * - Clarify that the method will acquire a mutex that heavily affects + * overall performance. + * - Force call sites to think how long they need to acquire the mutex to + * get consistent results. + */ + RecursiveMutex& GetMutex() const LOCK_RETURNED(::cs_main) { return ::cs_main; } + const Options m_options; std::thread m_load_block; //! A single BlockManager instance is shared across each constructed //! chainstate to avoid duplicating block metadata. @@ -906,19 +933,19 @@ public: // constructor //! @param[in] snapshot_blockhash If given, signify that this chainstate //! is based on a snapshot. - CChainState& InitializeChainstate( + Chainstate& InitializeChainstate( CTxMemPool* mempool, const std::optional<uint256>& snapshot_blockhash = std::nullopt) LIFETIMEBOUND EXCLUSIVE_LOCKS_REQUIRED(::cs_main); //! Get all chainstates currently being used. - std::vector<CChainState*> GetAll(); + std::vector<Chainstate*> GetAll(); //! Construct and activate a Chainstate on the basis of UTXO snapshot data. //! //! Steps: //! - //! - Initialize an unused CChainState. + //! - Initialize an unused Chainstate. //! - Load its `CoinsViews` contents from `coins_file`. //! - Verify that the hash of the resulting coinsdb matches the expected hash //! per assumeutxo chain parameters. @@ -928,13 +955,13 @@ public: //! - Move the new chainstate to `m_snapshot_chainstate` and make it our //! ChainstateActive(). [[nodiscard]] bool ActivateSnapshot( - CAutoFile& coins_file, const node::SnapshotMetadata& metadata, bool in_memory); + AutoFile& coins_file, const node::SnapshotMetadata& metadata, bool in_memory); //! The most-work chain. - CChainState& ActiveChainstate() const; - CChain& ActiveChain() const { return ActiveChainstate().m_chain; } - int ActiveHeight() const { return ActiveChain().Height(); } - CBlockIndex* ActiveTip() const { return ActiveChain().Tip(); } + Chainstate& ActiveChainstate() const; + CChain& ActiveChain() const EXCLUSIVE_LOCKS_REQUIRED(GetMutex()) { return ActiveChainstate().m_chain; } + int ActiveHeight() const EXCLUSIVE_LOCKS_REQUIRED(GetMutex()) { return ActiveChain().Height(); } + CBlockIndex* ActiveTip() const EXCLUSIVE_LOCKS_REQUIRED(GetMutex()) { return ActiveChain().Tip(); } node::BlockMap& BlockIndex() EXCLUSIVE_LOCKS_REQUIRED(::cs_main) { @@ -972,10 +999,15 @@ public: * * @param[in] block The block we want to process. * @param[in] force_processing Process this block even if unrequested; used for non-network block sources. + * @param[in] min_pow_checked True if proof-of-work anti-DoS checks have + * been done by caller for headers chain + * (note: only affects headers acceptance; if + * block header is already present in block + * index then this parameter has no effect) * @param[out] new_block A boolean which is set to indicate if the block was first received via this call * @returns If the block was processed, independently of block validity */ - bool ProcessNewBlock(const std::shared_ptr<const CBlock>& block, bool force_processing, bool* new_block) LOCKS_EXCLUDED(cs_main); + bool ProcessNewBlock(const std::shared_ptr<const CBlock>& block, bool force_processing, bool min_pow_checked, bool* new_block) LOCKS_EXCLUDED(cs_main); /** * Process incoming block headers. @@ -984,10 +1016,11 @@ public: * validationinterface callback. * * @param[in] block The block headers themselves + * @param[in] min_pow_checked True if proof-of-work anti-DoS checks have been done by caller for headers chain * @param[out] state This may be set to an Error state if any error occurred processing them * @param[out] ppindex If set, the pointer will be set to point to the last new block index object for the given headers */ - bool ProcessNewBlockHeaders(const std::vector<CBlockHeader>& block, BlockValidationState& state, const CBlockIndex** ppindex = nullptr) LOCKS_EXCLUDED(cs_main); + bool ProcessNewBlockHeaders(const std::vector<CBlockHeader>& block, bool min_pow_checked, BlockValidationState& state, const CBlockIndex** ppindex = nullptr) LOCKS_EXCLUDED(cs_main); /** * Try to add a transaction to the memory pool. @@ -1011,6 +1044,12 @@ public: /** Produce the necessary coinbase commitment for a block (modifies the hash, don't call for mined blocks). */ std::vector<unsigned char> GenerateCoinbaseCommitment(CBlock& block, const CBlockIndex* pindexPrev) const; + /** This is used by net_processing to report pre-synchronization progress of headers, as + * headers are not yet fed to validation during that time, but validation is (for now) + * responsible for logging and signalling through NotifyHeaderTip, so it needs this + * information. */ + void ReportHeadersPresync(const arith_uint256& work, int64_t height, int64_t timestamp); + ~ChainstateManager(); }; @@ -1033,14 +1072,6 @@ bool DeploymentEnabled(const ChainstateManager& chainman, DEP dep) return DeploymentEnabled(chainman.GetConsensus(), dep); } -using FopenFn = std::function<FILE*(const fs::path&, const char*)>; - -/** Dump the mempool to disk. */ -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, FopenFn mockable_fopen_function = fsbridge::fopen); - /** * Return the expected assumeutxo value for a given height, if one exists. * diff --git a/src/wallet/bdb.cpp b/src/wallet/bdb.cpp index f8230f7a1d..3a9d277f65 100644 --- a/src/wallet/bdb.cpp +++ b/src/wallet/bdb.cpp @@ -3,6 +3,7 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. +#include <compat/compat.h> #include <fs.h> #include <wallet/bdb.h> #include <wallet/db.h> @@ -315,12 +316,6 @@ BerkeleyBatch::BerkeleyBatch(BerkeleyDatabase& database, const bool read_only, b env = database.env.get(); pdb = database.m_db.get(); strFile = fs::PathToString(database.m_filename); - if (!Exists(std::string("version"))) { - bool fTmp = fReadOnly; - fReadOnly = false; - Write(std::string("version"), CLIENT_VERSION); - fReadOnly = fTmp; - } } void BerkeleyDatabase::Open() @@ -437,7 +432,7 @@ void BerkeleyEnvironment::ReloadDbEnv() }); std::vector<fs::path> filenames; - for (auto it : m_databases) { + for (const auto& it : m_databases) { filenames.push_back(it.first); } // Close the individual Db's @@ -538,7 +533,7 @@ bool BerkeleyDatabase::Rewrite(const char* pszSkip) void BerkeleyEnvironment::Flush(bool fShutdown) { - int64_t nStart = GetTimeMillis(); + const auto start{SteadyClock::now()}; // Flush log data to the actual data file on all files that are not in use LogPrint(BCLog::WALLETDB, "BerkeleyEnvironment::Flush: [%s] Flush(%s)%s\n", strPath, fShutdown ? "true" : "false", fDbEnvInit ? "" : " database not started"); if (!fDbEnvInit) @@ -566,7 +561,7 @@ void BerkeleyEnvironment::Flush(bool fShutdown) no_dbs_accessed = false; } } - LogPrint(BCLog::WALLETDB, "BerkeleyEnvironment::Flush: Flush(%s)%s took %15dms\n", fShutdown ? "true" : "false", fDbEnvInit ? "" : " database not started", GetTimeMillis() - nStart); + LogPrint(BCLog::WALLETDB, "BerkeleyEnvironment::Flush: Flush(%s)%s took %15dms\n", fShutdown ? "true" : "false", fDbEnvInit ? "" : " database not started", Ticks<std::chrono::milliseconds>(SteadyClock::now() - start)); if (fShutdown) { char** listp; if (no_dbs_accessed) { @@ -596,14 +591,14 @@ bool BerkeleyDatabase::PeriodicFlush() const std::string strFile = fs::PathToString(m_filename); LogPrint(BCLog::WALLETDB, "Flushing %s\n", strFile); - int64_t nStart = GetTimeMillis(); + const auto start{SteadyClock::now()}; // Flush wallet file so it's self contained env->CloseDb(m_filename); env->CheckpointLSN(strFile); m_refcount = -1; - LogPrint(BCLog::WALLETDB, "Flushed %s %dms\n", strFile, GetTimeMillis() - nStart); + LogPrint(BCLog::WALLETDB, "Flushed %s %dms\n", strFile, Ticks<std::chrono::milliseconds>(SteadyClock::now() - start)); return true; } diff --git a/src/wallet/coincontrol.h b/src/wallet/coincontrol.h index 65a5c83366..d08d3664c4 100644 --- a/src/wallet/coincontrol.h +++ b/src/wallet/coincontrol.h @@ -33,12 +33,11 @@ public: CTxDestination destChange = CNoDestination(); //! Override the default change type if set, ignored if destChange is set std::optional<OutputType> m_change_type; - //! If false, only selected inputs are used - bool m_add_inputs = true; //! If false, only safe inputs will be used bool m_include_unsafe_inputs = false; - //! If false, allows unselected inputs, but requires all selected inputs be used - bool fAllowOtherInputs = false; + //! If true, the selection process can add extra unselected inputs from the wallet + //! while requires all selected inputs be used + bool m_allow_other_inputs = false; //! Includes watch only addresses which are solvable bool fAllowWatchOnly = false; //! Override automatic min/max checks on fee, m_feerate must be set if true diff --git a/src/wallet/coinselection.cpp b/src/wallet/coinselection.cpp index 07df8d9fc8..b568e90998 100644 --- a/src/wallet/coinselection.cpp +++ b/src/wallet/coinselection.cpp @@ -104,9 +104,6 @@ std::optional<SelectionResult> SelectCoinsBnB(std::vector<OutputGroup>& utxo_poo if (curr_waste <= best_waste) { best_selection = curr_selection; best_waste = curr_waste; - if (best_waste == 0) { - break; - } } curr_waste -= (curr_value - selection_target); // Remove the excess value as we will be selecting different coins now backtrack = true; @@ -159,7 +156,7 @@ std::optional<SelectionResult> SelectCoinsBnB(std::vector<OutputGroup>& utxo_poo for (const size_t& i : best_selection) { result.AddInput(utxo_pool.at(i)); } - result.ComputeAndSetWaste(CAmount{0}); + result.ComputeAndSetWaste(cost_of_change, cost_of_change, CAmount{0}); assert(best_waste == result.GetWaste()); return result; @@ -169,6 +166,12 @@ std::optional<SelectionResult> SelectCoinsSRD(const std::vector<OutputGroup>& ut { SelectionResult result(target_value, SelectionAlgorithm::SRD); + // Include change for SRD as we want to avoid making really small change if the selection just + // barely meets the target. Just use the lower bound change target instead of the randomly + // generated one, since SRD will result in a random change amount anyway; avoid making the + // target needlessly large. + target_value += CHANGE_LOWER; + std::vector<size_t> indexes; indexes.resize(utxo_pool.size()); std::iota(indexes.begin(), indexes.end(), 0); @@ -392,20 +395,26 @@ CAmount GetSelectionWaste(const std::set<COutput>& inputs, CAmount change_cost, return waste; } -CAmount GenerateChangeTarget(CAmount payment_value, FastRandomContext& rng) +CAmount GenerateChangeTarget(const CAmount payment_value, const CAmount change_fee, FastRandomContext& rng) { if (payment_value <= CHANGE_LOWER / 2) { - return CHANGE_LOWER; + return change_fee + CHANGE_LOWER; } else { // random value between 50ksat and min (payment_value * 2, 1milsat) const auto upper_bound = std::min(payment_value * 2, CHANGE_UPPER); - return rng.randrange(upper_bound - CHANGE_LOWER) + CHANGE_LOWER; + return change_fee + rng.randrange(upper_bound - CHANGE_LOWER) + CHANGE_LOWER; } } -void SelectionResult::ComputeAndSetWaste(CAmount change_cost) +void SelectionResult::ComputeAndSetWaste(const CAmount min_viable_change, const CAmount change_cost, const CAmount change_fee) { - m_waste = GetSelectionWaste(m_selected_inputs, change_cost, m_target, m_use_effective); + const CAmount change = GetChange(min_viable_change, change_fee); + + if (change > 0) { + m_waste = GetSelectionWaste(m_selected_inputs, change_cost, m_target, m_use_effective); + } else { + m_waste = GetSelectionWaste(m_selected_inputs, 0, m_target, m_use_effective); + } } CAmount SelectionResult::GetWaste() const @@ -418,6 +427,11 @@ CAmount SelectionResult::GetSelectedValue() const return std::accumulate(m_selected_inputs.cbegin(), m_selected_inputs.cend(), CAmount{0}, [](CAmount sum, const auto& coin) { return sum + coin.txout.nValue; }); } +CAmount SelectionResult::GetSelectedEffectiveValue() const +{ + return std::accumulate(m_selected_inputs.cbegin(), m_selected_inputs.cend(), CAmount{0}, [](CAmount sum, const auto& coin) { return sum + coin.GetEffectiveValue(); }); +} + void SelectionResult::Clear() { m_selected_inputs.clear(); @@ -430,6 +444,16 @@ void SelectionResult::AddInput(const OutputGroup& group) m_use_effective = !group.m_subtract_fee_outputs; } +void SelectionResult::Merge(const SelectionResult& other) +{ + m_target += other.m_target; + m_use_effective |= other.m_use_effective; + if (m_algo == SelectionAlgorithm::MANUAL) { + m_algo = other.m_algo; + } + util::insert(m_selected_inputs, other.m_selected_inputs); +} + const std::set<COutput>& SelectionResult::GetInputSet() const { return m_selected_inputs; @@ -467,4 +491,24 @@ std::string GetAlgorithmName(const SelectionAlgorithm algo) } assert(false); } + +CAmount SelectionResult::GetChange(const CAmount min_viable_change, const CAmount change_fee) const +{ + // change = SUM(inputs) - SUM(outputs) - fees + // 1) With SFFO we don't pay any fees + // 2) Otherwise we pay all the fees: + // - input fees are covered by GetSelectedEffectiveValue() + // - non_input_fee is included in m_target + // - change_fee + const CAmount change = m_use_effective + ? GetSelectedEffectiveValue() - m_target - change_fee + : GetSelectedValue() - m_target; + + if (change < min_viable_change) { + return 0; + } + + return change; +} + } // namespace wallet diff --git a/src/wallet/coinselection.h b/src/wallet/coinselection.h index 9135e48104..761c2be0b3 100644 --- a/src/wallet/coinselection.h +++ b/src/wallet/coinselection.h @@ -123,10 +123,12 @@ struct CoinSelectionParams { /** Mininmum change to target in Knapsack solver: select coins to cover the payment and * at least this value of change. */ CAmount m_min_change_target{0}; + /** Minimum amount for creating a change output. + * If change budget is smaller than min_change then we forgo creation of change output. + */ + CAmount min_viable_change{0}; /** Cost of creating the change output. */ CAmount m_change_fee{0}; - /** The pre-determined minimum value to target when funding a change output. */ - CAmount m_change_target{0}; /** Cost of creating the change output + cost of spending the change output in the future. */ CAmount m_cost_of_change{0}; /** The targeted feerate of the transaction being built. */ @@ -254,6 +256,7 @@ struct OutputGroup /** Choose a random change target for each transaction to make it harder to fingerprint the Core * wallet based on the change output values of transactions it creates. + * Change target covers at least change fees and adds a random value on top of it. * The random value is between 50ksat and min(2 * payment_value, 1milsat) * When payment_value <= 25ksat, the value is just 50ksat. * @@ -263,8 +266,9 @@ struct OutputGroup * coins selected are just sufficient to cover the payment amount ("unnecessary input" heuristic). * * @param[in] payment_value Average payment value of the transaction output(s). + * @param[in] change_fee Fee for creating a change output. */ -[[nodiscard]] CAmount GenerateChangeTarget(CAmount payment_value, FastRandomContext& rng); +[[nodiscard]] CAmount GenerateChangeTarget(const CAmount payment_value, const CAmount change_fee, FastRandomContext& rng); enum class SelectionAlgorithm : uint8_t { @@ -281,17 +285,16 @@ struct SelectionResult private: /** Set of inputs selected by the algorithm to use in the transaction */ std::set<COutput> m_selected_inputs; + /** The target the algorithm selected for. Equal to the recipient amount plus non-input fees */ + CAmount m_target; + /** The algorithm used to produce this result */ + SelectionAlgorithm m_algo; /** Whether the input values for calculations should be the effective value (true) or normal value (false) */ bool m_use_effective{false}; /** The computed waste */ std::optional<CAmount> m_waste; public: - /** The target the algorithm selected for. Note that this may not be equal to the recipient amount as it can include non-input fees */ - const CAmount m_target; - /** The algorithm used to produce this result */ - const SelectionAlgorithm m_algo; - explicit SelectionResult(const CAmount target, SelectionAlgorithm algo) : m_target(target), m_algo(algo) {} @@ -300,20 +303,47 @@ public: /** Get the sum of the input values */ [[nodiscard]] CAmount GetSelectedValue() const; + [[nodiscard]] CAmount GetSelectedEffectiveValue() const; + void Clear(); void AddInput(const OutputGroup& group); /** Calculates and stores the waste for this selection via GetSelectionWaste */ - void ComputeAndSetWaste(CAmount change_cost); + void ComputeAndSetWaste(const CAmount min_viable_change, const CAmount change_cost, const CAmount change_fee); [[nodiscard]] CAmount GetWaste() const; + void Merge(const SelectionResult& other); + /** Get m_selected_inputs */ const std::set<COutput>& GetInputSet() const; /** Get the vector of COutputs that will be used to fill in a CTransaction's vin */ std::vector<COutput> GetShuffledInputVector() const; bool operator<(SelectionResult other) const; + + /** Get the amount for the change output after paying needed fees. + * + * The change amount is not 100% precise due to discrepancies in fee calculation. + * The final change amount (if any) should be corrected after calculating the final tx fees. + * When there is a discrepancy, most of the time the final change would be slightly bigger than estimated. + * + * Following are the possible factors of discrepancy: + * + non-input fees always include segwit flags + * + input fee estimation always include segwit stack size + * + input fees are rounded individually and not collectively, which leads to small rounding errors + * - input counter size is always assumed to be 1vbyte + * + * @param[in] min_viable_change Minimum amount for change output, if change would be less then we forgo change + * @param[in] change_fee Fees to include change output in the tx + * @returns Amount for change output, 0 when there is no change. + * + */ + CAmount GetChange(const CAmount min_viable_change, const CAmount change_fee) const; + + CAmount GetTarget() const { return m_target; } + + SelectionAlgorithm GetAlgo() const { return m_algo; } }; std::optional<SelectionResult> SelectCoinsBnB(std::vector<OutputGroup>& utxo_pool, const CAmount& selection_target, const CAmount& cost_of_change); diff --git a/src/wallet/dump.cpp b/src/wallet/dump.cpp index d80c3e25b0..f7fee443d0 100644 --- a/src/wallet/dump.cpp +++ b/src/wallet/dump.cpp @@ -41,7 +41,7 @@ bool DumpWallet(const ArgsManager& args, CWallet& wallet, bilingual_str& error) return false; } - CHashWriter hasher(0, 0); + HashWriter hasher{}; WalletDatabase& db = wallet.GetDatabase(); std::unique_ptr<DatabaseBatch> batch = db.MakeBatch(); @@ -132,7 +132,7 @@ bool CreateFromDump(const ArgsManager& args, const std::string& name, const fs:: std::ifstream dump_file{dump_path}; // Compute the checksum - CHashWriter hasher(0, 0); + HashWriter hasher{}; uint256 checksum; // Check the magic and version diff --git a/src/wallet/external_signer_scriptpubkeyman.cpp b/src/wallet/external_signer_scriptpubkeyman.cpp index 9d5f58b784..76de51ac3e 100644 --- a/src/wallet/external_signer_scriptpubkeyman.cpp +++ b/src/wallet/external_signer_scriptpubkeyman.cpp @@ -45,7 +45,8 @@ ExternalSigner ExternalSignerScriptPubKeyMan::GetExternalSigner() { std::vector<ExternalSigner> signers; ExternalSigner::Enumerate(command, signers, Params().NetworkIDString()); if (signers.empty()) throw std::runtime_error(std::string(__func__) + ": No external signers found"); - // TODO: add fingerprint argument in case of multiple signers + // TODO: add fingerprint argument instead of failing in case of multiple signers. + if (signers.size() > 1) throw std::runtime_error(std::string(__func__) + ": More than one external signer found. Please connect only one at a time."); return signers[0]; } diff --git a/src/wallet/feebumper.cpp b/src/wallet/feebumper.cpp index afd2b83971..6d7bb299cc 100644 --- a/src/wallet/feebumper.cpp +++ b/src/wallet/feebumper.cpp @@ -2,6 +2,7 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. +#include <consensus/validation.h> #include <interfaces/chain.h> #include <policy/fees.h> #include <policy/policy.h> @@ -19,9 +20,9 @@ namespace wallet { //! Check whether transaction has descendant in wallet or mempool, or has been //! mined, or conflicts with a mined transaction. Return a feebumper::Result. -static feebumper::Result PreconditionChecks(const CWallet& wallet, const CWalletTx& wtx, std::vector<bilingual_str>& errors) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet) +static feebumper::Result PreconditionChecks(const CWallet& wallet, const CWalletTx& wtx, bool require_mine, std::vector<bilingual_str>& errors) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet) { - if (wallet.HasWalletSpend(wtx.GetHash())) { + if (wallet.HasWalletSpend(wtx.tx)) { errors.push_back(Untranslated("Transaction has descendants in the wallet")); return feebumper::Result::INVALID_PARAMETER; } @@ -48,20 +49,21 @@ static feebumper::Result PreconditionChecks(const CWallet& wallet, const CWallet return feebumper::Result::WALLET_ERROR; } - // check that original tx consists entirely of our inputs - // if not, we can't bump the fee, because the wallet has no way of knowing the value of the other inputs (thus the fee) - isminefilter filter = wallet.GetLegacyScriptPubKeyMan() && wallet.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) ? ISMINE_WATCH_ONLY : ISMINE_SPENDABLE; - if (!AllInputsMine(wallet, *wtx.tx, filter)) { - errors.push_back(Untranslated("Transaction contains inputs that don't belong to this wallet")); - return feebumper::Result::WALLET_ERROR; + if (require_mine) { + // check that original tx consists entirely of our inputs + // if not, we can't bump the fee, because the wallet has no way of knowing the value of the other inputs (thus the fee) + isminefilter filter = wallet.GetLegacyScriptPubKeyMan() && wallet.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) ? ISMINE_WATCH_ONLY : ISMINE_SPENDABLE; + if (!AllInputsMine(wallet, *wtx.tx, filter)) { + errors.push_back(Untranslated("Transaction contains inputs that don't belong to this wallet")); + return feebumper::Result::WALLET_ERROR; + } } - return feebumper::Result::OK; } //! Check if the user provided a valid feeRate -static feebumper::Result CheckFeeRate(const CWallet& wallet, const CWalletTx& wtx, const CFeeRate& newFeerate, const int64_t maxTxSize, std::vector<bilingual_str>& errors) +static feebumper::Result CheckFeeRate(const CWallet& wallet, const CWalletTx& wtx, const CFeeRate& newFeerate, const int64_t maxTxSize, CAmount old_fee, std::vector<bilingual_str>& errors) { // check that fee rate is higher than mempool's minimum fee // (no point in bumping fee if we know that the new tx won't be accepted to the mempool) @@ -83,8 +85,6 @@ static feebumper::Result CheckFeeRate(const CWallet& wallet, const CWalletTx& wt CFeeRate incrementalRelayFee = std::max(wallet.chain().relayIncrementalFee(), CFeeRate(WALLET_INCREMENTAL_RELAY_FEE)); // Given old total fee and transaction size, calculate the old feeRate - isminefilter filter = wallet.GetLegacyScriptPubKeyMan() && wallet.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) ? ISMINE_WATCH_ONLY : ISMINE_SPENDABLE; - CAmount old_fee = CachedTxGetDebit(wallet, wtx, filter) - wtx.tx->GetValueOut(); const int64_t txSize = GetVirtualTransactionSize(*(wtx.tx)); CFeeRate nOldFeeRate(old_fee, txSize); // Min total fee is old fee + relay fee @@ -128,8 +128,8 @@ static CFeeRate EstimateFeeRate(const CWallet& wallet, const CWalletTx& wtx, con // WALLET_INCREMENTAL_RELAY_FEE value to future proof against changes to // network wide policy for incremental relay fee that our node may not be // aware of. This ensures we're over the required relay fee rate - // (BIP 125 rule 4). The replacement tx will be at least as large as the - // original tx, so the total fee will be greater (BIP 125 rule 3) + // (Rule 4). The replacement tx will be at least as large as the + // original tx, so the total fee will be greater (Rule 3) CFeeRate node_incremental_relay_fee = wallet.chain().relayIncrementalFee(); CFeeRate wallet_incremental_relay_fee = CFeeRate(WALLET_INCREMENTAL_RELAY_FEE); feerate += std::max(node_incremental_relay_fee, wallet_incremental_relay_fee); @@ -150,12 +150,12 @@ bool TransactionCanBeBumped(const CWallet& wallet, const uint256& txid) if (wtx == nullptr) return false; std::vector<bilingual_str> errors_dummy; - feebumper::Result res = PreconditionChecks(wallet, *wtx, errors_dummy); + feebumper::Result res = PreconditionChecks(wallet, *wtx, /* require_mine=*/ true, errors_dummy); return res == feebumper::Result::OK; } Result CreateRateBumpTransaction(CWallet& wallet, const uint256& txid, const CCoinControl& coin_control, std::vector<bilingual_str>& errors, - CAmount& old_fee, CAmount& new_fee, CMutableTransaction& mtx) + CAmount& old_fee, CAmount& new_fee, CMutableTransaction& mtx, bool require_mine) { // We are going to modify coin control later, copy to re-use CCoinControl new_coin_control(coin_control); @@ -169,13 +169,63 @@ Result CreateRateBumpTransaction(CWallet& wallet, const uint256& txid, const CCo } const CWalletTx& wtx = it->second; - Result result = PreconditionChecks(wallet, wtx, errors); + // Retrieve all of the UTXOs and add them to coin control + // While we're here, calculate the input amount + std::map<COutPoint, Coin> coins; + CAmount input_value = 0; + std::vector<CTxOut> spent_outputs; + for (const CTxIn& txin : wtx.tx->vin) { + coins[txin.prevout]; // Create empty map entry keyed by prevout. + } + wallet.chain().findCoins(coins); + for (const CTxIn& txin : wtx.tx->vin) { + const Coin& coin = coins.at(txin.prevout); + if (coin.out.IsNull()) { + errors.push_back(Untranslated(strprintf("%s:%u is already spent", txin.prevout.hash.GetHex(), txin.prevout.n))); + return Result::MISC_ERROR; + } + if (wallet.IsMine(txin.prevout)) { + new_coin_control.Select(txin.prevout); + } else { + new_coin_control.SelectExternal(txin.prevout, coin.out); + } + input_value += coin.out.nValue; + spent_outputs.push_back(coin.out); + } + + // Figure out if we need to compute the input weight, and do so if necessary + PrecomputedTransactionData txdata; + txdata.Init(*wtx.tx, std::move(spent_outputs), /* force=*/ true); + for (unsigned int i = 0; i < wtx.tx->vin.size(); ++i) { + const CTxIn& txin = wtx.tx->vin.at(i); + const Coin& coin = coins.at(txin.prevout); + + if (new_coin_control.IsExternalSelected(txin.prevout)) { + // For external inputs, we estimate the size using the size of this input + int64_t input_weight = GetTransactionInputWeight(txin); + // Because signatures can have different sizes, we need to figure out all of the + // signature sizes and replace them with the max sized signature. + // In order to do this, we verify the script with a special SignatureChecker which + // will observe the signatures verified and record their sizes. + SignatureWeights weights; + TransactionSignatureChecker tx_checker(wtx.tx.get(), i, coin.out.nValue, txdata, MissingDataBehavior::FAIL); + SignatureWeightChecker size_checker(weights, tx_checker); + VerifyScript(txin.scriptSig, coin.out.scriptPubKey, &txin.scriptWitness, STANDARD_SCRIPT_VERIFY_FLAGS, size_checker); + // Add the difference between max and current to input_weight so that it represents the largest the input could be + input_weight += weights.GetWeightDiffToMax(); + new_coin_control.SetInputWeight(txin.prevout, input_weight); + } + } + + Result result = PreconditionChecks(wallet, wtx, require_mine, errors); if (result != Result::OK) { return result; } // Fill in recipients(and preserve a single change key if there is one) + // While we're here, calculate the output amount std::vector<CRecipient> recipients; + CAmount output_value = 0; for (const auto& output : wtx.tx->vout) { if (!OutputIsChange(wallet, output)) { CRecipient recipient = {output.scriptPubKey, output.nValue, false}; @@ -185,16 +235,22 @@ Result CreateRateBumpTransaction(CWallet& wallet, const uint256& txid, const CCo ExtractDestination(output.scriptPubKey, change_dest); new_coin_control.destChange = change_dest; } + output_value += output.nValue; } - isminefilter filter = wallet.GetLegacyScriptPubKeyMan() && wallet.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) ? ISMINE_WATCH_ONLY : ISMINE_SPENDABLE; - old_fee = CachedTxGetDebit(wallet, wtx, filter) - wtx.tx->GetValueOut(); + old_fee = input_value - output_value; if (coin_control.m_feerate) { // The user provided a feeRate argument. // We calculate this here to avoid compiler warning on the cs_wallet lock - const int64_t maxTxSize{CalculateMaximumSignedTxSize(*wtx.tx, &wallet).vsize}; - Result res = CheckFeeRate(wallet, wtx, *new_coin_control.m_feerate, maxTxSize, errors); + // We need to make a temporary transaction with no input witnesses as the dummy signer expects them to be empty for external inputs + CMutableTransaction mtx{*wtx.tx}; + for (auto& txin : mtx.vin) { + txin.scriptSig.clear(); + txin.scriptWitness.SetNull(); + } + const int64_t maxTxSize{CalculateMaximumSignedTxSize(CTransaction(mtx), &wallet, &new_coin_control).vsize}; + Result res = CheckFeeRate(wallet, wtx, *new_coin_control.m_feerate, maxTxSize, old_fee, errors); if (res != Result::OK) { return res; } @@ -213,25 +269,24 @@ Result CreateRateBumpTransaction(CWallet& wallet, const uint256& txid, const CCo for (const auto& inputs : wtx.tx->vin) { new_coin_control.Select(COutPoint(inputs.prevout)); } - new_coin_control.fAllowOtherInputs = true; + new_coin_control.m_allow_other_inputs = true; // We cannot source new unconfirmed inputs(bip125 rule 2) new_coin_control.m_min_depth = 1; constexpr int RANDOM_CHANGE_POSITION = -1; - bilingual_str fail_reason; - FeeCalculation fee_calc_out; - std::optional<CreatedTransactionResult> txr = CreateTransaction(wallet, recipients, RANDOM_CHANGE_POSITION, fail_reason, new_coin_control, fee_calc_out, false); - if (!txr) { - errors.push_back(Untranslated("Unable to create transaction.") + Untranslated(" ") + fail_reason); + auto res = CreateTransaction(wallet, recipients, RANDOM_CHANGE_POSITION, new_coin_control, false); + if (!res) { + errors.push_back(Untranslated("Unable to create transaction.") + Untranslated(" ") + util::ErrorString(res)); return Result::WALLET_ERROR; } + const auto& txr = *res; // Write back new fee if successful - new_fee = txr->fee; + new_fee = txr.fee; // Write back transaction - mtx = CMutableTransaction(*txr->tx); + mtx = CMutableTransaction(*txr.tx); return Result::OK; } @@ -255,7 +310,7 @@ Result CommitTransaction(CWallet& wallet, const uint256& txid, CMutableTransacti const CWalletTx& oldWtx = it->second; // make sure the transaction still has no descendants and hasn't been mined in the meantime - Result result = PreconditionChecks(wallet, oldWtx, errors); + Result result = PreconditionChecks(wallet, oldWtx, /* require_mine=*/ false, errors); if (result != Result::OK) { return result; } diff --git a/src/wallet/feebumper.h b/src/wallet/feebumper.h index 191878a137..760ab58e5c 100644 --- a/src/wallet/feebumper.h +++ b/src/wallet/feebumper.h @@ -5,6 +5,8 @@ #ifndef BITCOIN_WALLET_FEEBUMPER_H #define BITCOIN_WALLET_FEEBUMPER_H +#include <consensus/consensus.h> +#include <script/interpreter.h> #include <primitives/transaction.h> class uint256; @@ -31,14 +33,25 @@ enum class Result //! Return whether transaction can be bumped. bool TransactionCanBeBumped(const CWallet& wallet, const uint256& txid); -//! Create bumpfee transaction based on feerate estimates. +/** Create bumpfee transaction based on feerate estimates. + * + * @param[in] wallet The wallet to use for this bumping + * @param[in] txid The txid of the transaction to bump + * @param[in] coin_control A CCoinControl object which provides feerates and other information used for coin selection + * @param[out] errors Errors + * @param[out] old_fee The fee the original transaction pays + * @param[out] new_fee the fee that the bump transaction pays + * @param[out] mtx The bump transaction itself + * @param[in] require_mine Whether the original transaction must consist of inputs that can be spent by the wallet + */ Result CreateRateBumpTransaction(CWallet& wallet, const uint256& txid, const CCoinControl& coin_control, std::vector<bilingual_str>& errors, CAmount& old_fee, CAmount& new_fee, - CMutableTransaction& mtx); + CMutableTransaction& mtx, + bool require_mine); //! Sign the new transaction, //! @return false if the tx couldn't be found or if it was @@ -55,6 +68,55 @@ Result CommitTransaction(CWallet& wallet, std::vector<bilingual_str>& errors, uint256& bumped_txid); +struct SignatureWeights +{ +private: + int m_sigs_count{0}; + int64_t m_sigs_weight{0}; + +public: + void AddSigWeight(const size_t weight, const SigVersion sigversion) + { + switch (sigversion) { + case SigVersion::BASE: + m_sigs_weight += weight * WITNESS_SCALE_FACTOR; + m_sigs_count += 1 * WITNESS_SCALE_FACTOR; + break; + case SigVersion::WITNESS_V0: + m_sigs_weight += weight; + m_sigs_count++; + break; + case SigVersion::TAPROOT: + case SigVersion::TAPSCRIPT: + assert(false); + } + } + + int64_t GetWeightDiffToMax() const + { + // Note: the witness scaling factor is already accounted for because the count is multiplied by it. + return (/* max signature size=*/ 72 * m_sigs_count) - m_sigs_weight; + } +}; + +class SignatureWeightChecker : public DeferringSignatureChecker +{ +private: + SignatureWeights& m_weights; + +public: + SignatureWeightChecker(SignatureWeights& weights, const BaseSignatureChecker& checker) : DeferringSignatureChecker(checker), m_weights(weights) {} + + bool CheckECDSASignature(const std::vector<unsigned char>& sig, const std::vector<unsigned char>& pubkey, const CScript& script, SigVersion sigversion) const override + { + if (m_checker.CheckECDSASignature(sig, pubkey, script, sigversion)) { + m_weights.AddSigWeight(sig.size(), sigversion); + return true; + } + return false; + } +}; + } // namespace feebumper } // namespace wallet diff --git a/src/wallet/fees.cpp b/src/wallet/fees.cpp index 6f81fa30a1..3514d018b7 100644 --- a/src/wallet/fees.cpp +++ b/src/wallet/fees.cpp @@ -87,7 +87,7 @@ CFeeRate GetDiscardRate(const CWallet& wallet) CFeeRate discard_rate = wallet.chain().estimateSmartFee(highest_target, false /* conservative */); // Don't let discard_rate be greater than longest possible fee estimate if we get a valid fee estimate discard_rate = (discard_rate == CFeeRate(0)) ? wallet.m_discard_rate : std::min(discard_rate, wallet.m_discard_rate); - // Discard rate must be at least dustRelayFee + // Discard rate must be at least dust relay feerate discard_rate = std::max(discard_rate, wallet.chain().relayDustFee()); return discard_rate; } diff --git a/src/wallet/init.cpp b/src/wallet/init.cpp index 7f038eda84..174c68744c 100644 --- a/src/wallet/init.cpp +++ b/src/wallet/init.cpp @@ -9,7 +9,7 @@ #include <interfaces/wallet.h> #include <net.h> #include <node/context.h> -#include <node/ui_interface.h> +#include <node/interface_ui.h> #include <outputtype.h> #include <univalue.h> #include <util/check.h> diff --git a/src/wallet/interfaces.cpp b/src/wallet/interfaces.cpp index e1203817e0..9cf2b677e6 100644 --- a/src/wallet/interfaces.cpp +++ b/src/wallet/interfaces.cpp @@ -48,6 +48,8 @@ using interfaces::WalletTxStatus; using interfaces::WalletValueMap; namespace wallet { +// All members of the classes in this namespace are intentionally public, as the +// classes themselves are private. namespace { //! Construct wallet tx struct. WalletTx MakeWalletTx(CWallet& wallet, const CWalletTx& wtx) @@ -110,7 +112,7 @@ WalletTxOut MakeWalletTxOut(const CWallet& wallet, result.txout = wtx.tx->vout[n]; result.time = wtx.GetTxTime(); result.depth_in_main_chain = depth; - result.is_spent = wallet.IsSpent(wtx.GetHash(), n); + result.is_spent = wallet.IsSpent(COutPoint(wtx.GetHash(), n)); return result; } @@ -121,7 +123,7 @@ WalletTxOut MakeWalletTxOut(const CWallet& wallet, result.txout = output.txout; result.time = output.time; result.depth_in_main_chain = output.depth; - result.is_spent = wallet.IsSpent(output.outpoint.hash, output.outpoint.n); + result.is_spent = wallet.IsSpent(output.outpoint); return result; } @@ -146,11 +148,10 @@ public: void abortRescan() override { m_wallet->AbortRescan(); } bool backupWallet(const std::string& filename) override { return m_wallet->BackupWallet(filename); } std::string getWalletName() override { return m_wallet->GetName(); } - bool getNewDestination(const OutputType type, const std::string label, CTxDestination& dest) override + util::Result<CTxDestination> getNewDestination(const OutputType type, const std::string& label) override { LOCK(m_wallet->cs_wallet); - bilingual_str error; - return m_wallet->GetNewDestination(type, label, dest, error); + return m_wallet->GetNewDestination(type, label); } bool getPubKey(const CScript& script, const CKeyID& address, CPubKey& pub_key) override { @@ -191,29 +192,27 @@ public: std::string* purpose) override { LOCK(m_wallet->cs_wallet); - auto it = m_wallet->m_address_book.find(dest); - if (it == m_wallet->m_address_book.end() || it->second.IsChange()) { - return false; - } + const auto& entry = m_wallet->FindAddressBookEntry(dest, /*allow_change=*/false); + if (!entry) return false; // addr not found if (name) { - *name = it->second.GetLabel(); + *name = entry->GetLabel(); } if (is_mine) { *is_mine = m_wallet->IsMine(dest); } if (purpose) { - *purpose = it->second.purpose; + *purpose = entry->purpose; } return true; } - std::vector<WalletAddress> getAddresses() override + std::vector<WalletAddress> getAddresses() const override { LOCK(m_wallet->cs_wallet); std::vector<WalletAddress> result; - for (const auto& item : m_wallet->m_address_book) { - if (item.second.IsChange()) continue; - result.emplace_back(item.first, m_wallet->IsMine(item.first), item.second.GetLabel(), item.second.purpose); - } + m_wallet->ForEachAddrBookEntry([&](const CTxDestination& dest, const std::string& label, const std::string& purpose, bool is_change) EXCLUSIVE_LOCKS_REQUIRED(m_wallet->cs_wallet) { + if (is_change) return; + result.emplace_back(dest, m_wallet->IsMine(dest), label, purpose); + }); return result; } std::vector<std::string> getAddressReceiveRequests() override { @@ -245,29 +244,28 @@ public: bool isLockedCoin(const COutPoint& output) override { LOCK(m_wallet->cs_wallet); - return m_wallet->IsLockedCoin(output.hash, output.n); + return m_wallet->IsLockedCoin(output); } void listLockedCoins(std::vector<COutPoint>& outputs) override { LOCK(m_wallet->cs_wallet); return m_wallet->ListLockedCoins(outputs); } - CTransactionRef createTransaction(const std::vector<CRecipient>& recipients, + util::Result<CTransactionRef> createTransaction(const std::vector<CRecipient>& recipients, const CCoinControl& coin_control, bool sign, int& change_pos, - CAmount& fee, - bilingual_str& fail_reason) override + CAmount& fee) override { LOCK(m_wallet->cs_wallet); - FeeCalculation fee_calc_out; - std::optional<CreatedTransactionResult> txr = CreateTransaction(*m_wallet, recipients, change_pos, - fail_reason, coin_control, fee_calc_out, sign); - if (!txr) return {}; - fee = txr->fee; - change_pos = txr->change_pos; + auto res = CreateTransaction(*m_wallet, recipients, change_pos, + coin_control, sign); + if (!res) return util::Error{util::ErrorString(res)}; + const auto& txr = *res; + fee = txr.fee; + change_pos = txr.change_pos; - return txr->tx; + return txr.tx; } void commitTransaction(CTransactionRef tx, WalletValueMap value_map, @@ -293,7 +291,7 @@ public: CAmount& new_fee, CMutableTransaction& mtx) override { - return feebumper::CreateRateBumpTransaction(*m_wallet.get(), txid, coin_control, errors, old_fee, new_fee, mtx) == feebumper::Result::OK; + return feebumper::CreateRateBumpTransaction(*m_wallet.get(), txid, coin_control, errors, old_fee, new_fee, mtx, /* require_mine= */ true) == feebumper::Result::OK; } bool signBumpTransaction(CMutableTransaction& mtx) override { return feebumper::SignTransaction(*m_wallet.get(), mtx); } bool commitBumpTransaction(const uint256& txid, @@ -322,13 +320,12 @@ public: } return {}; } - std::vector<WalletTx> getWalletTxs() override + std::set<WalletTx> getWalletTxs() override { LOCK(m_wallet->cs_wallet); - std::vector<WalletTx> result; - result.reserve(m_wallet->mapWallet.size()); + std::set<WalletTx> result; for (const auto& entry : m_wallet->mapWallet) { - result.emplace_back(MakeWalletTx(*m_wallet, entry.second)); + result.emplace(MakeWalletTx(*m_wallet, entry.second)); } return result; } @@ -554,30 +551,46 @@ public: void setMockTime(int64_t time) override { return SetMockTime(time); } //! WalletLoader methods - std::unique_ptr<Wallet> createWallet(const std::string& name, const SecureString& passphrase, uint64_t wallet_creation_flags, bilingual_str& error, std::vector<bilingual_str>& warnings) override + util::Result<std::unique_ptr<Wallet>> createWallet(const std::string& name, const SecureString& passphrase, uint64_t wallet_creation_flags, std::vector<bilingual_str>& warnings) override { - std::shared_ptr<CWallet> wallet; DatabaseOptions options; DatabaseStatus status; ReadDatabaseArgs(*m_context.args, options); options.require_create = true; options.create_flags = wallet_creation_flags; options.create_passphrase = passphrase; - return MakeWallet(m_context, CreateWallet(m_context, name, true /* load_on_start */, options, status, error, warnings)); + bilingual_str error; + std::unique_ptr<Wallet> wallet{MakeWallet(m_context, CreateWallet(m_context, name, /*load_on_start=*/true, options, status, error, warnings))}; + if (wallet) { + return {std::move(wallet)}; + } else { + return util::Error{error}; + } } - std::unique_ptr<Wallet> loadWallet(const std::string& name, bilingual_str& error, std::vector<bilingual_str>& warnings) override + util::Result<std::unique_ptr<Wallet>> loadWallet(const std::string& name, std::vector<bilingual_str>& warnings) override { DatabaseOptions options; DatabaseStatus status; ReadDatabaseArgs(*m_context.args, options); options.require_existing = true; - return MakeWallet(m_context, LoadWallet(m_context, name, true /* load_on_start */, options, status, error, warnings)); + bilingual_str error; + std::unique_ptr<Wallet> wallet{MakeWallet(m_context, LoadWallet(m_context, name, /*load_on_start=*/true, options, status, error, warnings))}; + if (wallet) { + return {std::move(wallet)}; + } else { + return util::Error{error}; + } } - std::unique_ptr<Wallet> restoreWallet(const fs::path& backup_file, const std::string& wallet_name, bilingual_str& error, std::vector<bilingual_str>& warnings) override + util::Result<std::unique_ptr<Wallet>> restoreWallet(const fs::path& backup_file, const std::string& wallet_name, std::vector<bilingual_str>& warnings) override { DatabaseStatus status; - - return MakeWallet(m_context, RestoreWallet(m_context, backup_file, wallet_name, /*load_on_start=*/true, status, error, warnings)); + bilingual_str error; + std::unique_ptr<Wallet> wallet{MakeWallet(m_context, RestoreWallet(m_context, backup_file, wallet_name, /*load_on_start=*/true, status, error, warnings))}; + if (wallet) { + return {std::move(wallet)}; + } else { + return util::Error{error}; + } } std::string getWalletDir() override { diff --git a/src/wallet/load.cpp b/src/wallet/load.cpp index c06513588b..6eb0ef5e7a 100644 --- a/src/wallet/load.cpp +++ b/src/wallet/load.cpp @@ -151,7 +151,7 @@ void StartWallets(WalletContext& context, CScheduler& scheduler) if (context.args->GetBoolArg("-flushwallet", DEFAULT_FLUSHWALLET)) { scheduler.scheduleEvery([&context] { MaybeCompactWalletDB(context); }, std::chrono::milliseconds{500}); } - scheduler.scheduleEvery([&context] { MaybeResendWalletTxs(context); }, std::chrono::milliseconds{1000}); + scheduler.scheduleEvery([&context] { MaybeResendWalletTxs(context); }, 1min); } void FlushWallets(WalletContext& context) diff --git a/src/wallet/receive.cpp b/src/wallet/receive.cpp index 8cce07b921..7fbf2ff8cf 100644 --- a/src/wallet/receive.cpp +++ b/src/wallet/receive.cpp @@ -9,15 +9,12 @@ #include <wallet/wallet.h> namespace wallet { -isminetype InputIsMine(const CWallet& wallet, const CTxIn &txin) +isminetype InputIsMine(const CWallet& wallet, const CTxIn& txin) { AssertLockHeld(wallet.cs_wallet); - std::map<uint256, CWalletTx>::const_iterator mi = wallet.mapWallet.find(txin.prevout.hash); - if (mi != wallet.mapWallet.end()) - { - const CWalletTx& prev = (*mi).second; - if (txin.prevout.n < prev.tx->vout.size()) - return wallet.IsMine(prev.tx->vout[txin.prevout.n]); + const CWalletTx* prev = wallet.GetWalletTx(txin.prevout.hash); + if (prev && txin.prevout.n < prev->tx->vout.size()) { + return wallet.IsMine(prev->tx->vout[txin.prevout.n]); } return ISMINE_NO; } @@ -25,20 +22,8 @@ isminetype InputIsMine(const CWallet& wallet, const CTxIn &txin) bool AllInputsMine(const CWallet& wallet, const CTransaction& tx, const isminefilter& filter) { LOCK(wallet.cs_wallet); - - for (const CTxIn& txin : tx.vin) - { - auto mi = wallet.mapWallet.find(txin.prevout.hash); - if (mi == wallet.mapWallet.end()) - return false; // any unknown inputs can't be from us - - const CWalletTx& prev = (*mi).second; - - if (txin.prevout.n >= prev.tx->vout.size()) - return false; // invalid input! - - if (!(wallet.IsMine(prev.tx->vout[txin.prevout.n]) & filter)) - return false; + for (const CTxIn& txin : tx.vin) { + if (!(InputIsMine(wallet, txin) & filter)) return false; } return true; } @@ -111,10 +96,10 @@ CAmount TxGetChange(const CWallet& wallet, const CTransaction& tx) return nChange; } -static CAmount GetCachableAmount(const CWallet& wallet, const CWalletTx& wtx, CWalletTx::AmountType type, const isminefilter& filter, bool recalculate = false) +static CAmount GetCachableAmount(const CWallet& wallet, const CWalletTx& wtx, CWalletTx::AmountType type, const isminefilter& filter) { auto& amount = wtx.m_amounts[type]; - if (recalculate || !amount.m_cached[filter]) { + if (!amount.m_cached[filter]) { amount.Set(filter, type == CWalletTx::DEBIT ? wallet.GetDebit(*wtx.tx, filter) : TxGetCredit(wallet, *wtx.tx, filter)); wtx.m_is_cache_empty = false; } @@ -130,12 +115,10 @@ CAmount CachedTxGetCredit(const CWallet& wallet, const CWalletTx& wtx, const ism return 0; CAmount credit = 0; - if (filter & ISMINE_SPENDABLE) { + const isminefilter get_amount_filter{filter & ISMINE_ALL}; + if (get_amount_filter) { // GetBalance can assume transactions in mapWallet won't change - credit += GetCachableAmount(wallet, wtx, CWalletTx::CREDIT, ISMINE_SPENDABLE); - } - if (filter & ISMINE_WATCH_ONLY) { - credit += GetCachableAmount(wallet, wtx, CWalletTx::CREDIT, ISMINE_WATCH_ONLY); + credit += GetCachableAmount(wallet, wtx, CWalletTx::CREDIT, get_amount_filter); } return credit; } @@ -146,11 +129,9 @@ CAmount CachedTxGetDebit(const CWallet& wallet, const CWalletTx& wtx, const ismi return 0; CAmount debit = 0; - if (filter & ISMINE_SPENDABLE) { - debit += GetCachableAmount(wallet, wtx, CWalletTx::DEBIT, ISMINE_SPENDABLE); - } - if (filter & ISMINE_WATCH_ONLY) { - debit += GetCachableAmount(wallet, wtx, CWalletTx::DEBIT, ISMINE_WATCH_ONLY); + const isminefilter get_amount_filter{filter & ISMINE_ALL}; + if (get_amount_filter) { + debit += GetCachableAmount(wallet, wtx, CWalletTx::DEBIT, get_amount_filter); } return debit; } @@ -164,29 +145,18 @@ CAmount CachedTxGetChange(const CWallet& wallet, const CWalletTx& wtx) return wtx.nChangeCached; } -CAmount CachedTxGetImmatureCredit(const CWallet& wallet, const CWalletTx& wtx, bool fUseCache) -{ - AssertLockHeld(wallet.cs_wallet); - - if (wallet.IsTxImmatureCoinBase(wtx) && wallet.IsTxInMainChain(wtx)) { - return GetCachableAmount(wallet, wtx, CWalletTx::IMMATURE_CREDIT, ISMINE_SPENDABLE, !fUseCache); - } - - return 0; -} - -CAmount CachedTxGetImmatureWatchOnlyCredit(const CWallet& wallet, const CWalletTx& wtx, const bool fUseCache) +CAmount CachedTxGetImmatureCredit(const CWallet& wallet, const CWalletTx& wtx, const isminefilter& filter) { AssertLockHeld(wallet.cs_wallet); if (wallet.IsTxImmatureCoinBase(wtx) && wallet.IsTxInMainChain(wtx)) { - return GetCachableAmount(wallet, wtx, CWalletTx::IMMATURE_CREDIT, ISMINE_WATCH_ONLY, !fUseCache); + return GetCachableAmount(wallet, wtx, CWalletTx::IMMATURE_CREDIT, filter); } return 0; } -CAmount CachedTxGetAvailableCredit(const CWallet& wallet, const CWalletTx& wtx, bool fUseCache, const isminefilter& filter) +CAmount CachedTxGetAvailableCredit(const CWallet& wallet, const CWalletTx& wtx, const isminefilter& filter) { AssertLockHeld(wallet.cs_wallet); @@ -197,17 +167,16 @@ CAmount CachedTxGetAvailableCredit(const CWallet& wallet, const CWalletTx& wtx, if (wallet.IsTxImmatureCoinBase(wtx)) return 0; - if (fUseCache && allow_cache && wtx.m_amounts[CWalletTx::AVAILABLE_CREDIT].m_cached[filter]) { + if (allow_cache && wtx.m_amounts[CWalletTx::AVAILABLE_CREDIT].m_cached[filter]) { return wtx.m_amounts[CWalletTx::AVAILABLE_CREDIT].m_value[filter]; } bool allow_used_addresses = (filter & ISMINE_USED) || !wallet.IsWalletFlagSet(WALLET_FLAG_AVOID_REUSE); CAmount nCredit = 0; uint256 hashTx = wtx.GetHash(); - for (unsigned int i = 0; i < wtx.tx->vout.size(); i++) - { - if (!wallet.IsSpent(hashTx, i) && (allow_used_addresses || !wallet.IsSpentKey(hashTx, i))) { - const CTxOut &txout = wtx.tx->vout[i]; + for (unsigned int i = 0; i < wtx.tx->vout.size(); i++) { + const CTxOut& txout = wtx.tx->vout[i]; + if (!wallet.IsSpent(COutPoint(hashTx, i)) && (allow_used_addresses || !wallet.IsSpentKey(txout.scriptPubKey))) { nCredit += OutputGetCredit(wallet, txout, filter); if (!MoneyRange(nCredit)) throw std::runtime_error(std::string(__func__) + " : value out of range"); @@ -224,7 +193,8 @@ CAmount CachedTxGetAvailableCredit(const CWallet& wallet, const CWalletTx& wtx, void CachedTxGetAmounts(const CWallet& wallet, const CWalletTx& wtx, std::list<COutputEntry>& listReceived, - std::list<COutputEntry>& listSent, CAmount& nFee, const isminefilter& filter) + std::list<COutputEntry>& listSent, CAmount& nFee, const isminefilter& filter, + bool include_change) { nFee = 0; listReceived.clear(); @@ -249,8 +219,7 @@ void CachedTxGetAmounts(const CWallet& wallet, const CWalletTx& wtx, // 2) the output is to us (received) if (nDebit > 0) { - // Don't report 'change' txouts - if (OutputIsChange(wallet, txout)) + if (!include_change && OutputIsChange(wallet, txout)) continue; } else if (!(fIsMine & filter)) @@ -333,8 +302,8 @@ Balance GetBalance(const CWallet& wallet, const int min_depth, bool avoid_reuse) const CWalletTx& wtx = entry.second; const bool is_trusted{CachedTxIsTrusted(wallet, wtx, trusted_parents)}; const int tx_depth{wallet.GetTxDepthInMainChain(wtx)}; - const CAmount tx_credit_mine{CachedTxGetAvailableCredit(wallet, wtx, /*fUseCache=*/true, ISMINE_SPENDABLE | reuse_filter)}; - const CAmount tx_credit_watchonly{CachedTxGetAvailableCredit(wallet, wtx, /*fUseCache=*/true, ISMINE_WATCH_ONLY | reuse_filter)}; + const CAmount tx_credit_mine{CachedTxGetAvailableCredit(wallet, wtx, ISMINE_SPENDABLE | reuse_filter)}; + const CAmount tx_credit_watchonly{CachedTxGetAvailableCredit(wallet, wtx, ISMINE_WATCH_ONLY | reuse_filter)}; if (is_trusted && tx_depth >= min_depth) { ret.m_mine_trusted += tx_credit_mine; ret.m_watchonly_trusted += tx_credit_watchonly; @@ -343,8 +312,8 @@ Balance GetBalance(const CWallet& wallet, const int min_depth, bool avoid_reuse) ret.m_mine_untrusted_pending += tx_credit_mine; ret.m_watchonly_untrusted_pending += tx_credit_watchonly; } - ret.m_mine_immature += CachedTxGetImmatureCredit(wallet, wtx); - ret.m_watchonly_immature += CachedTxGetImmatureWatchOnlyCredit(wallet, wtx); + ret.m_mine_immature += CachedTxGetImmatureCredit(wallet, wtx, ISMINE_SPENDABLE); + ret.m_watchonly_immature += CachedTxGetImmatureCredit(wallet, wtx, ISMINE_WATCH_ONLY); } } return ret; @@ -371,15 +340,15 @@ std::map<CTxDestination, CAmount> GetAddressBalances(const CWallet& wallet) if (nDepth < (CachedTxIsFromMe(wallet, wtx, ISMINE_ALL) ? 0 : 1)) continue; - for (unsigned int i = 0; i < wtx.tx->vout.size(); i++) - { + for (unsigned int i = 0; i < wtx.tx->vout.size(); i++) { + const auto& output = wtx.tx->vout[i]; CTxDestination addr; - if (!wallet.IsMine(wtx.tx->vout[i])) + if (!wallet.IsMine(output)) continue; - if(!ExtractDestination(wtx.tx->vout[i].scriptPubKey, addr)) + if(!ExtractDestination(output.scriptPubKey, addr)) continue; - CAmount n = wallet.IsSpent(walletEntry.first, i) ? 0 : wtx.tx->vout[i].nValue; + CAmount n = wallet.IsSpent(COutPoint(walletEntry.first, i)) ? 0 : output.nValue; balances[addr] += n; } } @@ -447,7 +416,7 @@ std::set< std::set<CTxDestination> > GetAddressGroupings(const CWallet& wallet) std::set< std::set<CTxDestination>* > uniqueGroupings; // a set of pointers to groups of addresses std::map< CTxDestination, std::set<CTxDestination>* > setmap; // map addresses to the unique group containing it - for (std::set<CTxDestination> _grouping : groupings) + for (const std::set<CTxDestination>& _grouping : groupings) { // make a set of all the groups hit by this new group std::set< std::set<CTxDestination>* > hits; diff --git a/src/wallet/receive.h b/src/wallet/receive.h index 1caef293f2..9125b1e9aa 100644 --- a/src/wallet/receive.h +++ b/src/wallet/receive.h @@ -29,11 +29,9 @@ CAmount CachedTxGetCredit(const CWallet& wallet, const CWalletTx& wtx, const ism //! filter decides which addresses will count towards the debit CAmount CachedTxGetDebit(const CWallet& wallet, const CWalletTx& wtx, const isminefilter& filter); CAmount CachedTxGetChange(const CWallet& wallet, const CWalletTx& wtx); -CAmount CachedTxGetImmatureCredit(const CWallet& wallet, const CWalletTx& wtx, bool fUseCache = true) +CAmount CachedTxGetImmatureCredit(const CWallet& wallet, const CWalletTx& wtx, const isminefilter& filter) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet); -CAmount CachedTxGetImmatureWatchOnlyCredit(const CWallet& wallet, const CWalletTx& wtx, const bool fUseCache = true) - EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet); -CAmount CachedTxGetAvailableCredit(const CWallet& wallet, const CWalletTx& wtx, bool fUseCache = true, const isminefilter& filter = ISMINE_SPENDABLE) +CAmount CachedTxGetAvailableCredit(const CWallet& wallet, const CWalletTx& wtx, const isminefilter& filter = ISMINE_SPENDABLE) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet); struct COutputEntry { @@ -44,7 +42,8 @@ struct COutputEntry void CachedTxGetAmounts(const CWallet& wallet, const CWalletTx& wtx, std::list<COutputEntry>& listReceived, std::list<COutputEntry>& listSent, - CAmount& nFee, const isminefilter& filter); + CAmount& nFee, const isminefilter& filter, + bool include_change); bool CachedTxIsFromMe(const CWallet& wallet, const CWalletTx& wtx, const isminefilter& filter); bool CachedTxIsTrusted(const CWallet& wallet, const CWalletTx& wtx, std::set<uint256>& trusted_parents) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet); bool CachedTxIsTrusted(const CWallet& wallet, const CWalletTx& wtx); diff --git a/src/wallet/rpc/addresses.cpp b/src/wallet/rpc/addresses.cpp index f25ad59528..903a569cb9 100644 --- a/src/wallet/rpc/addresses.cpp +++ b/src/wallet/rpc/addresses.cpp @@ -34,7 +34,7 @@ RPCHelpMan getnewaddress() [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); - if (!pwallet) return NullUniValue; + if (!pwallet) return UniValue::VNULL; LOCK(pwallet->cs_wallet); @@ -58,13 +58,12 @@ RPCHelpMan getnewaddress() output_type = parsed.value(); } - CTxDestination dest; - bilingual_str error; - if (!pwallet->GetNewDestination(output_type, label, dest, error)) { - throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, error.original); + auto op_dest = pwallet->GetNewDestination(output_type, label); + if (!op_dest) { + throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, util::ErrorString(op_dest).original); } - return EncodeDestination(dest); + return EncodeDestination(*op_dest); }, }; } @@ -87,7 +86,7 @@ RPCHelpMan getrawchangeaddress() [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); - if (!pwallet) return NullUniValue; + if (!pwallet) return UniValue::VNULL; LOCK(pwallet->cs_wallet); @@ -106,12 +105,11 @@ RPCHelpMan getrawchangeaddress() output_type = parsed.value(); } - CTxDestination dest; - bilingual_str error; - if (!pwallet->GetNewChangeDestination(output_type, dest, error)) { - throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, error.original); + auto op_dest = pwallet->GetNewChangeDestination(output_type); + if (!op_dest) { + throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, util::ErrorString(op_dest).original); } - return EncodeDestination(dest); + return EncodeDestination(*op_dest); }, }; } @@ -133,7 +131,7 @@ RPCHelpMan setlabel() [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); - if (!pwallet) return NullUniValue; + if (!pwallet) return UniValue::VNULL; LOCK(pwallet->cs_wallet); @@ -150,7 +148,7 @@ RPCHelpMan setlabel() pwallet->SetAddressBook(dest, label, "send"); } - return NullUniValue; + return UniValue::VNULL; }, }; } @@ -183,7 +181,7 @@ RPCHelpMan listaddressgroupings() [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request); - if (!pwallet) return NullUniValue; + if (!pwallet) return UniValue::VNULL; // 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 @@ -254,7 +252,7 @@ RPCHelpMan addmultisigaddress() [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); - if (!pwallet) return NullUniValue; + if (!pwallet) return UniValue::VNULL; LegacyScriptPubKeyMan& spk_man = EnsureLegacyScriptPubKeyMan(*pwallet); @@ -329,7 +327,7 @@ RPCHelpMan keypoolrefill() [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); - if (!pwallet) return NullUniValue; + if (!pwallet) return UniValue::VNULL; if (pwallet->IsLegacy() && pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { throw JSONRPCError(RPC_WALLET_ERROR, "Error: Private keys are disabled for this wallet"); @@ -352,7 +350,7 @@ RPCHelpMan keypoolrefill() throw JSONRPCError(RPC_WALLET_ERROR, "Error refreshing keypool."); } - return NullUniValue; + return UniValue::VNULL; }, }; } @@ -376,14 +374,14 @@ RPCHelpMan newkeypool() [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); - if (!pwallet) return NullUniValue; + if (!pwallet) return UniValue::VNULL; LOCK(pwallet->cs_wallet); LegacyScriptPubKeyMan& spk_man = EnsureLegacyScriptPubKeyMan(*pwallet, true); spk_man.NewKeyPool(); - return NullUniValue; + return UniValue::VNULL; }, }; } @@ -550,7 +548,7 @@ RPCHelpMan getaddressinfo() [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request); - if (!pwallet) return NullUniValue; + if (!pwallet) return UniValue::VNULL; LOCK(pwallet->cs_wallet); @@ -580,7 +578,7 @@ RPCHelpMan getaddressinfo() if (provider) { auto inferred = InferDescriptor(scriptPubKey, *provider); - bool solvable = inferred->IsSolvable() || IsSolvable(*provider, scriptPubKey); + bool solvable = inferred->IsSolvable(); ret.pushKV("solvable", solvable); if (solvable) { ret.pushKV("desc", inferred->ToString()); @@ -637,17 +635,6 @@ RPCHelpMan getaddressinfo() }; } -/** Convert CAddressBookData to JSON record. */ -static UniValue AddressBookDataToJSON(const CAddressBookData& data, const bool verbose) -{ - UniValue ret(UniValue::VOBJ); - if (verbose) { - ret.pushKV("name", data.GetLabel()); - } - ret.pushKV("purpose", data.purpose); - return ret; -} - RPCHelpMan getaddressesbylabel() { return RPCHelpMan{"getaddressesbylabel", @@ -671,7 +658,7 @@ RPCHelpMan getaddressesbylabel() [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request); - if (!pwallet) return NullUniValue; + if (!pwallet) return UniValue::VNULL; LOCK(pwallet->cs_wallet); @@ -680,10 +667,10 @@ RPCHelpMan getaddressesbylabel() // Find all addresses that have the given label UniValue ret(UniValue::VOBJ); std::set<std::string> addresses; - for (const std::pair<const CTxDestination, CAddressBookData>& item : pwallet->m_address_book) { - if (item.second.IsChange()) continue; - if (item.second.GetLabel() == label) { - std::string address = EncodeDestination(item.first); + pwallet->ForEachAddrBookEntry([&](const CTxDestination& _dest, const std::string& _label, const std::string& _purpose, bool _is_change) { + if (_is_change) return; + if (_label == label) { + std::string address = EncodeDestination(_dest); // CWallet::m_address_book is not expected to contain duplicate // address strings, but build a separate set as a precaution just in // case it does. @@ -693,9 +680,11 @@ RPCHelpMan getaddressesbylabel() // and since duplicate addresses are unexpected (checked with // std::set in O(log(N))), UniValue::__pushKV is used instead, // which currently is O(1). - ret.__pushKV(address, AddressBookDataToJSON(item.second, false)); + UniValue value(UniValue::VOBJ); + value.pushKV("purpose", _purpose); + ret.__pushKV(address, value); } - } + }); if (ret.empty()) { throw JSONRPCError(RPC_WALLET_INVALID_LABEL_NAME, std::string("No addresses with label " + label)); @@ -732,7 +721,7 @@ RPCHelpMan listlabels() [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request); - if (!pwallet) return NullUniValue; + if (!pwallet) return UniValue::VNULL; LOCK(pwallet->cs_wallet); @@ -742,13 +731,7 @@ RPCHelpMan listlabels() } // Add to a set to sort by label name, then insert into Univalue array - std::set<std::string> label_set; - for (const std::pair<const CTxDestination, CAddressBookData>& entry : pwallet->m_address_book) { - if (entry.second.IsChange()) continue; - if (purpose.empty() || entry.second.purpose == purpose) { - label_set.insert(entry.second.GetLabel()); - } - } + std::set<std::string> label_set = pwallet->ListAddrBookLabels(purpose); UniValue ret(UniValue::VARR); for (const std::string& name : label_set) { @@ -780,7 +763,7 @@ RPCHelpMan walletdisplayaddress() [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); - if (!wallet) return NullUniValue; + if (!wallet) return UniValue::VNULL; CWallet* const pwallet = wallet.get(); LOCK(pwallet->cs_wallet); diff --git a/src/wallet/rpc/backup.cpp b/src/wallet/rpc/backup.cpp index aa9f23c886..8babbb4298 100644 --- a/src/wallet/rpc/backup.cpp +++ b/src/wallet/rpc/backup.cpp @@ -100,11 +100,13 @@ RPCHelpMan importprivkey() "Hint: use importmulti to import more than one private key.\n" "\nNote: This call can take over an hour to complete if rescan is true, during that time, other rpc calls\n" "may report that the imported key exists but related transactions are still missing, leading to temporarily incorrect/bogus balances and unspent outputs until rescan completes.\n" + "The rescan parameter can be set to false if the key was never used to create transactions. If it is set to false,\n" + "but the key was used to create transactions, rescanblockchain needs to be called with the appropriate block range.\n" "Note: Use \"getwalletinfo\" to query the scanning progress.\n", { {"privkey", RPCArg::Type::STR, RPCArg::Optional::NO, "The private key (see dumpprivkey)"}, {"label", RPCArg::Type::STR, RPCArg::DefaultHint{"current label if address exists, otherwise \"\""}, "An optional label"}, - {"rescan", RPCArg::Type::BOOL, RPCArg::Default{true}, "Rescan the wallet for transactions"}, + {"rescan", RPCArg::Type::BOOL, RPCArg::Default{true}, "Scan the chain and mempool for wallet transactions."}, }, RPCResult{RPCResult::Type::NONE, "", ""}, RPCExamples{ @@ -122,7 +124,7 @@ RPCHelpMan importprivkey() [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); - if (!pwallet) return NullUniValue; + if (!pwallet) return UniValue::VNULL; if (pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { throw JSONRPCError(RPC_WALLET_ERROR, "Cannot import private keys to a wallet with private keys disabled"); @@ -138,7 +140,7 @@ RPCHelpMan importprivkey() EnsureWalletIsUnlocked(*pwallet); std::string strSecret = request.params[0].get_str(); - std::string strLabel = ""; + std::string strLabel; if (!request.params[1].isNull()) strLabel = request.params[1].get_str(); @@ -190,7 +192,7 @@ RPCHelpMan importprivkey() RescanWallet(*pwallet, reserver); } - return NullUniValue; + return UniValue::VNULL; }, }; } @@ -198,18 +200,21 @@ RPCHelpMan importprivkey() RPCHelpMan importaddress() { return RPCHelpMan{"importaddress", - "\nAdds an address or script (in hex) that can be watched as if it were in your wallet but cannot be used to spend. Requires a new wallet backup.\n" + "\nAdds an address or script (in hex) that can be watched as if it were in your wallet but cannot be used to spend. Requires a new wallet backup.\n" "\nNote: This call can take over an hour to complete if rescan is true, during that time, other rpc calls\n" "may report that the imported address exists but related transactions are still missing, leading to temporarily incorrect/bogus balances and unspent outputs until rescan completes.\n" + "The rescan parameter can be set to false if the key was never used to create transactions. If it is set to false,\n" + "but the key was used to create transactions, rescanblockchain needs to be called with the appropriate block range.\n" "If you have the full public key, you should call importpubkey instead of this.\n" "Hint: use importmulti to import more than one address.\n" "\nNote: If you import a non-standard raw script in hex form, outputs sending to it will be treated\n" "as change, and not show up in many RPCs.\n" - "Note: Use \"getwalletinfo\" to query the scanning progress.\n", + "Note: Use \"getwalletinfo\" to query the scanning progress.\n" + "Note: This command is only compatible with legacy wallets. Use \"importdescriptors\" with \"addr(X)\" for descriptor wallets.\n", { {"address", RPCArg::Type::STR, RPCArg::Optional::NO, "The Bitcoin address (or hex-encoded script)"}, {"label", RPCArg::Type::STR, RPCArg::Default{""}, "An optional label"}, - {"rescan", RPCArg::Type::BOOL, RPCArg::Default{true}, "Rescan the wallet for transactions"}, + {"rescan", RPCArg::Type::BOOL, RPCArg::Default{true}, "Scan the chain and mempool for wallet transactions."}, {"p2sh", RPCArg::Type::BOOL, RPCArg::Default{false}, "Add the P2SH version of the script as well"}, }, RPCResult{RPCResult::Type::NONE, "", ""}, @@ -224,7 +229,7 @@ RPCHelpMan importaddress() [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); - if (!pwallet) return NullUniValue; + if (!pwallet) return UniValue::VNULL; EnsureLegacyScriptPubKeyMan(*pwallet, true); @@ -290,11 +295,11 @@ RPCHelpMan importaddress() RescanWallet(*pwallet, reserver); { LOCK(pwallet->cs_wallet); - pwallet->ReacceptWalletTransactions(); + pwallet->ResubmitWalletTransactions(/*relay=*/false, /*force=*/true); } } - return NullUniValue; + return UniValue::VNULL; }, }; } @@ -312,7 +317,7 @@ RPCHelpMan importprunedfunds() [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); - if (!pwallet) return NullUniValue; + if (!pwallet) return UniValue::VNULL; CMutableTransaction tx; if (!DecodeHexTx(tx, request.params[0].get_str())) { @@ -347,7 +352,7 @@ RPCHelpMan importprunedfunds() CTransactionRef tx_ref = MakeTransactionRef(tx); if (pwallet->IsMine(*tx_ref)) { pwallet->AddToWallet(std::move(tx_ref), TxStateConfirmed{merkleBlock.header.GetHash(), height, static_cast<int>(txnIndex)}); - return NullUniValue; + return UniValue::VNULL; } throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "No addresses in wallet correspond to included transaction"); @@ -371,7 +376,7 @@ RPCHelpMan removeprunedfunds() [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); - if (!pwallet) return NullUniValue; + if (!pwallet) return UniValue::VNULL; LOCK(pwallet->cs_wallet); @@ -388,7 +393,7 @@ RPCHelpMan removeprunedfunds() throw JSONRPCError(RPC_INVALID_PARAMETER, "Transaction does not exist in wallet."); } - return NullUniValue; + return UniValue::VNULL; }, }; } @@ -400,11 +405,13 @@ RPCHelpMan importpubkey() "Hint: use importmulti to import more than one public key.\n" "\nNote: This call can take over an hour to complete if rescan is true, during that time, other rpc calls\n" "may report that the imported pubkey exists but related transactions are still missing, leading to temporarily incorrect/bogus balances and unspent outputs until rescan completes.\n" + "The rescan parameter can be set to false if the key was never used to create transactions. If it is set to false,\n" + "but the key was used to create transactions, rescanblockchain needs to be called with the appropriate block range.\n" "Note: Use \"getwalletinfo\" to query the scanning progress.\n", { {"pubkey", RPCArg::Type::STR, RPCArg::Optional::NO, "The hex-encoded public key"}, {"label", RPCArg::Type::STR, RPCArg::Default{""}, "An optional label"}, - {"rescan", RPCArg::Type::BOOL, RPCArg::Default{true}, "Rescan the wallet for transactions"}, + {"rescan", RPCArg::Type::BOOL, RPCArg::Default{true}, "Scan the chain and mempool for wallet transactions."}, }, RPCResult{RPCResult::Type::NONE, "", ""}, RPCExamples{ @@ -418,7 +425,7 @@ RPCHelpMan importpubkey() [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); - if (!pwallet) return NullUniValue; + if (!pwallet) return UniValue::VNULL; EnsureLegacyScriptPubKeyMan(*pwallet, true); @@ -469,11 +476,11 @@ RPCHelpMan importpubkey() RescanWallet(*pwallet, reserver); { LOCK(pwallet->cs_wallet); - pwallet->ReacceptWalletTransactions(); + pwallet->ResubmitWalletTransactions(/*relay=*/false, /*force=*/true); } } - return NullUniValue; + return UniValue::VNULL; }, }; } @@ -483,7 +490,7 @@ RPCHelpMan importwallet() { return RPCHelpMan{"importwallet", "\nImports keys from a wallet dump file (see dumpwallet). Requires a new wallet backup to include imported keys.\n" - "Note: Use \"getwalletinfo\" to query the scanning progress.\n", + "Note: Blockchain and Mempool will be rescanned after a successful import. Use \"getwalletinfo\" to query the scanning progress.\n", { {"filename", RPCArg::Type::STR, RPCArg::Optional::NO, "The wallet file"}, }, @@ -499,7 +506,7 @@ RPCHelpMan importwallet() [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); - if (!pwallet) return NullUniValue; + if (!pwallet) return UniValue::VNULL; EnsureLegacyScriptPubKeyMan(*pwallet, true); @@ -630,7 +637,7 @@ RPCHelpMan importwallet() if (!fGood) throw JSONRPCError(RPC_WALLET_ERROR, "Error adding some keys/scripts to wallet"); - return NullUniValue; + return UniValue::VNULL; }, }; } @@ -654,7 +661,7 @@ RPCHelpMan dumpprivkey() [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request); - if (!pwallet) return NullUniValue; + if (!pwallet) return UniValue::VNULL; const LegacyScriptPubKeyMan& spk_man = EnsureConstLegacyScriptPubKeyMan(*pwallet); @@ -704,7 +711,7 @@ RPCHelpMan dumpwallet() [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request); - if (!pwallet) return NullUniValue; + if (!pwallet) return UniValue::VNULL; const CWallet& wallet = *pwallet; const LegacyScriptPubKeyMan& spk_man = EnsureConstLegacyScriptPubKeyMan(wallet); @@ -1249,6 +1256,8 @@ RPCHelpMan importmulti() "Conversely, if all the private keys are provided and the address/script is spendable, the watchonly option must be set to false, or a warning will be returned.\n" "\nNote: This call can take over an hour to complete if rescan is true, during that time, other rpc calls\n" "may report that the imported keys, addresses or scripts exist but related transactions are still missing.\n" + "The rescan parameter can be set to false if the key was never used to create transactions. If it is set to false,\n" + "but the key was used to create transactions, rescanblockchain needs to be called with the appropriate block range.\n" "Note: Use \"getwalletinfo\" to query the scanning progress.\n", { {"requests", RPCArg::Type::ARR, RPCArg::Optional::NO, "Data to be imported", @@ -1257,15 +1266,15 @@ RPCHelpMan importmulti() { {"desc", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "Descriptor to import. If using descriptor, do not also provide address/scriptPubKey, scripts, or pubkeys"}, {"scriptPubKey", RPCArg::Type::STR, RPCArg::Optional::NO, "Type of scriptPubKey (string for script, json for address). Should not be provided if using a descriptor", - /*oneline_description=*/"", {"\"<script>\" | { \"address\":\"<address>\" }", "string / json"} + RPCArgOptions{.type_str={"\"<script>\" | { \"address\":\"<address>\" }", "string / json"}} }, {"timestamp", RPCArg::Type::NUM, RPCArg::Optional::NO, "Creation time of the key expressed in " + UNIX_EPOCH_TIME + ",\n" - " or the string \"now\" to substitute the current synced blockchain time. The timestamp of the oldest\n" - " key will determine how far back blockchain rescans need to begin for missing wallet transactions.\n" - " \"now\" can be specified to bypass scanning, for keys which are known to never have been used, and\n" - " 0 can be specified to scan the entire blockchain. Blocks up to 2 hours before the earliest key\n" - " creation time of all keys being imported by the importmulti call will be scanned.", - /*oneline_description=*/"", {"timestamp | \"now\"", "integer / string"} + "or the string \"now\" to substitute the current synced blockchain time. The timestamp of the oldest\n" + "key will determine how far back blockchain rescans need to begin for missing wallet transactions.\n" + "\"now\" can be specified to bypass scanning, for keys which are known to never have been used, and\n" + "0 can be specified to scan the entire blockchain. Blocks up to 2 hours before the earliest key\n" + "creation time of all keys being imported by the importmulti call will be scanned.", + RPCArgOptions{.type_str={"timestamp | \"now\"", "integer / string"}} }, {"redeemscript", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "Allowed only if the scriptPubKey is a P2SH or P2SH-P2WSH address/scriptPubKey"}, {"witnessscript", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "Allowed only if the scriptPubKey is a P2SH-P2WSH or P2WSH address/scriptPubKey"}, @@ -1287,12 +1296,12 @@ RPCHelpMan importmulti() }, }, }, - "\"requests\""}, + RPCArgOptions{.oneline_description="\"requests\""}}, {"options", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED_NAMED_ARG, "", { - {"rescan", RPCArg::Type::BOOL, RPCArg::Default{true}, "Stating if should rescan the blockchain after all imports"}, + {"rescan", RPCArg::Type::BOOL, RPCArg::Default{true}, "Scan the chain and mempool for wallet transactions after all imports."}, }, - "\"options\""}, + RPCArgOptions{.oneline_description="\"options\""}}, }, RPCResult{ RPCResult::Type::ARR, "", "Response is an array with the same size as the input that has the execution result", @@ -1319,7 +1328,7 @@ RPCHelpMan importmulti() [&](const RPCHelpMan& self, const JSONRPCRequest& mainRequest) -> UniValue { std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(mainRequest); - if (!pwallet) return NullUniValue; + if (!pwallet) return UniValue::VNULL; CWallet& wallet{*pwallet}; // Make sure the results are valid at least up to the most recent block @@ -1354,7 +1363,18 @@ RPCHelpMan importmulti() UniValue response(UniValue::VARR); { LOCK(pwallet->cs_wallet); - EnsureWalletIsUnlocked(*pwallet); + + // Check all requests are watchonly + bool is_watchonly{true}; + for (size_t i = 0; i < requests.size(); ++i) { + const UniValue& request = requests[i]; + if (!request.exists("watchonly") || !request["watchonly"].get_bool()) { + is_watchonly = false; + break; + } + } + // Wallet does not need to be unlocked if all requests are watchonly + if (!is_watchonly) EnsureWalletIsUnlocked(wallet); // Verify all timestamps are present before importing any keys. CHECK_NONFATAL(pwallet->chain().findBlock(pwallet->GetLastBlockHash(), FoundBlock().time(nLowestTimestamp).mtpTime(now))); @@ -1388,7 +1408,7 @@ RPCHelpMan importmulti() int64_t scannedTime = pwallet->RescanFromTime(nLowestTimestamp, reserver, true /* update */); { LOCK(pwallet->cs_wallet); - pwallet->ReacceptWalletTransactions(); + pwallet->ResubmitWalletTransactions(/*relay=*/false, /*force=*/true); } if (pwallet->IsAbortingRescan()) { @@ -1589,18 +1609,18 @@ RPCHelpMan importdescriptors() {"range", RPCArg::Type::RANGE, RPCArg::Optional::OMITTED, "If a ranged descriptor is used, this specifies the end or the range (in the form [begin,end]) to import"}, {"next_index", RPCArg::Type::NUM, RPCArg::Optional::OMITTED, "If a ranged descriptor is set to active, this specifies the next index to generate addresses from"}, {"timestamp", RPCArg::Type::NUM, RPCArg::Optional::NO, "Time from which to start rescanning the blockchain for this descriptor, in " + UNIX_EPOCH_TIME + "\n" - " Use the string \"now\" to substitute the current synced blockchain time.\n" - " \"now\" can be specified to bypass scanning, for outputs which are known to never have been used, and\n" - " 0 can be specified to scan the entire blockchain. Blocks up to 2 hours before the earliest timestamp\n" - " of all descriptors being imported will be scanned.", - /*oneline_description=*/"", {"timestamp | \"now\"", "integer / string"} + "Use the string \"now\" to substitute the current synced blockchain time.\n" + "\"now\" can be specified to bypass scanning, for outputs which are known to never have been used, and\n" + "0 can be specified to scan the entire blockchain. Blocks up to 2 hours before the earliest timestamp\n" + "of all descriptors being imported will be scanned as well as the mempool.", + RPCArgOptions{.type_str={"timestamp | \"now\"", "integer / string"}} }, {"internal", RPCArg::Type::BOOL, RPCArg::Default{false}, "Whether matching outputs should be treated as not incoming payments (e.g. change)"}, {"label", RPCArg::Type::STR, RPCArg::Default{""}, "Label to assign to the address, only allowed with internal=false. Disabled for ranged descriptors"}, }, }, }, - "\"requests\""}, + RPCArgOptions{.oneline_description="\"requests\""}}, }, RPCResult{ RPCResult::Type::ARR, "", "Response is an array with the same size as the input that has the execution result", @@ -1627,7 +1647,7 @@ RPCHelpMan importdescriptors() [&](const RPCHelpMan& self, const JSONRPCRequest& main_request) -> UniValue { std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(main_request); - if (!pwallet) return NullUniValue; + if (!pwallet) return UniValue::VNULL; CWallet& wallet{*pwallet}; // Make sure the results are valid at least up to the most recent block @@ -1682,7 +1702,7 @@ RPCHelpMan importdescriptors() int64_t scanned_time = pwallet->RescanFromTime(lowest_timestamp, reserver, true /* update */); { LOCK(pwallet->cs_wallet); - pwallet->ReacceptWalletTransactions(); + pwallet->ResubmitWalletTransactions(/*relay=*/false, /*force=*/true); } if (pwallet->IsAbortingRescan()) { @@ -1740,7 +1760,7 @@ RPCHelpMan listdescriptors() }, RPCResult{RPCResult::Type::OBJ, "", "", { {RPCResult::Type::STR, "wallet_name", "Name of wallet this operation was performed on"}, - {RPCResult::Type::ARR, "descriptors", "Array of descriptor objects", + {RPCResult::Type::ARR, "descriptors", "Array of descriptor objects (sorted by descriptor string representation)", { {RPCResult::Type::OBJ, "", "", { {RPCResult::Type::STR, "desc", "Descriptor string representation"}, @@ -1762,7 +1782,7 @@ RPCHelpMan listdescriptors() [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { const std::shared_ptr<const CWallet> wallet = GetWalletForJSONRPCRequest(request); - if (!wallet) return NullUniValue; + if (!wallet) return UniValue::VNULL; if (!wallet->IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS)) { throw JSONRPCError(RPC_WALLET_ERROR, "listdescriptors is not available for non-descriptor wallets"); @@ -1775,34 +1795,59 @@ RPCHelpMan listdescriptors() LOCK(wallet->cs_wallet); - UniValue descriptors(UniValue::VARR); const auto active_spk_mans = wallet->GetActiveScriptPubKeyMans(); + + struct WalletDescInfo { + std::string descriptor; + uint64_t creation_time; + bool active; + std::optional<bool> internal; + std::optional<std::pair<int64_t,int64_t>> range; + int64_t next_index; + }; + + std::vector<WalletDescInfo> wallet_descriptors; for (const auto& spk_man : wallet->GetAllScriptPubKeyMans()) { const auto desc_spk_man = dynamic_cast<DescriptorScriptPubKeyMan*>(spk_man); if (!desc_spk_man) { throw JSONRPCError(RPC_WALLET_ERROR, "Unexpected ScriptPubKey manager type."); } - UniValue spk(UniValue::VOBJ); LOCK(desc_spk_man->cs_desc_man); const auto& wallet_descriptor = desc_spk_man->GetWalletDescriptor(); std::string descriptor; - if (!desc_spk_man->GetDescriptorString(descriptor, priv)) { throw JSONRPCError(RPC_WALLET_ERROR, "Can't get descriptor string."); } - spk.pushKV("desc", descriptor); - spk.pushKV("timestamp", wallet_descriptor.creation_time); - spk.pushKV("active", active_spk_mans.count(desc_spk_man) != 0); - const auto internal = wallet->IsInternalScriptPubKeyMan(desc_spk_man); - if (internal.has_value()) { - spk.pushKV("internal", *internal); + const bool is_range = wallet_descriptor.descriptor->IsRange(); + wallet_descriptors.push_back({ + descriptor, + wallet_descriptor.creation_time, + active_spk_mans.count(desc_spk_man) != 0, + wallet->IsInternalScriptPubKeyMan(desc_spk_man), + is_range ? std::optional(std::make_pair(wallet_descriptor.range_start, wallet_descriptor.range_end)) : std::nullopt, + wallet_descriptor.next_index + }); + } + + std::sort(wallet_descriptors.begin(), wallet_descriptors.end(), [](const auto& a, const auto& b) { + return a.descriptor < b.descriptor; + }); + + UniValue descriptors(UniValue::VARR); + for (const WalletDescInfo& info : wallet_descriptors) { + UniValue spk(UniValue::VOBJ); + spk.pushKV("desc", info.descriptor); + spk.pushKV("timestamp", info.creation_time); + spk.pushKV("active", info.active); + if (info.internal.has_value()) { + spk.pushKV("internal", info.internal.value()); } - if (wallet_descriptor.descriptor->IsRange()) { + if (info.range.has_value()) { UniValue range(UniValue::VARR); - range.push_back(wallet_descriptor.range_start); - range.push_back(wallet_descriptor.range_end - 1); + range.push_back(info.range->first); + range.push_back(info.range->second - 1); spk.pushKV("range", range); - spk.pushKV("next", wallet_descriptor.next_index); + spk.pushKV("next", info.next_index); } descriptors.push_back(spk); } @@ -1831,7 +1876,7 @@ RPCHelpMan backupwallet() [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request); - if (!pwallet) return NullUniValue; + if (!pwallet) return UniValue::VNULL; // 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 @@ -1844,7 +1889,7 @@ RPCHelpMan backupwallet() throw JSONRPCError(RPC_WALLET_ERROR, "Error: Wallet backup failed!"); } - return NullUniValue; + return UniValue::VNULL; }, }; } diff --git a/src/wallet/rpc/coins.cpp b/src/wallet/rpc/coins.cpp index 2649fa586c..9c0c953a7a 100644 --- a/src/wallet/rpc/coins.cpp +++ b/src/wallet/rpc/coins.cpp @@ -18,10 +18,10 @@ namespace wallet { static CAmount GetReceived(const CWallet& wallet, const UniValue& params, bool by_label) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet) { - std::set<CTxDestination> addresses; + std::vector<CTxDestination> addresses; if (by_label) { // Get the set of addresses assigned to label - addresses = wallet.GetLabelAddresses(LabelFromValue(params[0])); + addresses = wallet.ListAddrBookAddresses(CWallet::AddrBookFilter{LabelFromValue(params[0])}); if (addresses.empty()) throw JSONRPCError(RPC_WALLET_ERROR, "Label not found in wallet"); } else { // Get the address @@ -29,7 +29,7 @@ static CAmount GetReceived(const CWallet& wallet, const UniValue& params, bool b if (!IsValidDestination(dest)) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Bitcoin address"); } - addresses.insert(dest); + addresses.emplace_back(dest); } // Filter by own scripts only @@ -103,7 +103,7 @@ RPCHelpMan getreceivedbyaddress() [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request); - if (!pwallet) return NullUniValue; + if (!pwallet) return UniValue::VNULL; // 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 @@ -144,7 +144,7 @@ RPCHelpMan getreceivedbylabel() [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request); - if (!pwallet) return NullUniValue; + if (!pwallet) return UniValue::VNULL; // 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 @@ -184,7 +184,7 @@ RPCHelpMan getbalance() [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request); - if (!pwallet) return NullUniValue; + if (!pwallet) return UniValue::VNULL; // 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 @@ -223,7 +223,7 @@ RPCHelpMan getunconfirmedbalance() [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request); - if (!pwallet) return NullUniValue; + if (!pwallet) return UniValue::VNULL; // 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 @@ -282,7 +282,7 @@ RPCHelpMan lockunspent() [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); - if (!pwallet) return NullUniValue; + if (!pwallet) return UniValue::VNULL; // 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 @@ -290,8 +290,6 @@ RPCHelpMan lockunspent() LOCK(pwallet->cs_wallet); - RPCTypeCheckArgument(request.params[0], UniValue::VBOOL); - bool fUnlock = request.params[0].get_bool(); const bool persistent{request.params[2].isNull() ? false : request.params[2].get_bool()}; @@ -304,9 +302,7 @@ RPCHelpMan lockunspent() return true; } - RPCTypeCheckArgument(request.params[1], UniValue::VARR); - - const UniValue& output_params = request.params[1]; + const UniValue& output_params = request.params[1].get_array(); // Create and validate the COutPoints first. @@ -341,11 +337,11 @@ RPCHelpMan lockunspent() throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, vout index out of bounds"); } - if (pwallet->IsSpent(outpt.hash, outpt.n)) { + if (pwallet->IsSpent(outpt)) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, expected unspent output"); } - const bool is_locked = pwallet->IsLockedCoin(outpt.hash, outpt.n); + const bool is_locked = pwallet->IsLockedCoin(outpt); if (fUnlock && !is_locked) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, expected locked output"); @@ -407,7 +403,7 @@ RPCHelpMan listlockunspent() [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request); - if (!pwallet) return NullUniValue; + if (!pwallet) return UniValue::VNULL; LOCK(pwallet->cs_wallet); @@ -459,7 +455,7 @@ RPCHelpMan getbalances() [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { const std::shared_ptr<const CWallet> rpc_wallet = GetWalletForJSONRPCRequest(request); - if (!rpc_wallet) return NullUniValue; + if (!rpc_wallet) return UniValue::VNULL; const CWallet& wallet = *rpc_wallet; // Make sure the results are valid at least up to the most recent block @@ -520,7 +516,7 @@ RPCHelpMan listunspent() {"maximumCount", RPCArg::Type::NUM, RPCArg::DefaultHint{"unlimited"}, "Maximum number of UTXOs"}, {"minimumSumAmount", RPCArg::Type::AMOUNT, RPCArg::DefaultHint{"unlimited"}, "Minimum sum value of all UTXOs in " + CURRENCY_UNIT + ""}, }, - "query_options"}, + RPCArgOptions{.oneline_description="query_options"}}, }, RPCResult{ RPCResult::Type::ARR, "", "", @@ -543,6 +539,9 @@ RPCHelpMan listunspent() {RPCResult::Type::BOOL, "solvable", "Whether we know how to spend this output, ignoring the lack of keys"}, {RPCResult::Type::BOOL, "reused", /*optional=*/true, "(only present if avoid_reuse is set) Whether this output is reused/dirty (sent to an address that was previously spent from)"}, {RPCResult::Type::STR, "desc", /*optional=*/true, "(only when solvable) A descriptor for spending this output"}, + {RPCResult::Type::ARR, "parent_descs", /*optional=*/false, "List of parent descriptors for the scriptPubKey of this coin.", { + {RPCResult::Type::STR, "desc", "The descriptor string."}, + }}, {RPCResult::Type::BOOL, "safe", "Whether this output is considered safe to spend. Unconfirmed transactions\n" "from outside keys and unconfirmed replacement transactions are considered unsafe\n" "and are not eligible for spending by fundrawtransaction and sendtoaddress."}, @@ -559,23 +558,20 @@ RPCHelpMan listunspent() [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request); - if (!pwallet) return NullUniValue; + if (!pwallet) return UniValue::VNULL; int nMinDepth = 1; if (!request.params[0].isNull()) { - RPCTypeCheckArgument(request.params[0], UniValue::VNUM); nMinDepth = request.params[0].getInt<int>(); } int nMaxDepth = 9999999; if (!request.params[1].isNull()) { - RPCTypeCheckArgument(request.params[1], UniValue::VNUM); nMaxDepth = request.params[1].getInt<int>(); } std::set<CTxDestination> destinations; if (!request.params[2].isNull()) { - RPCTypeCheckArgument(request.params[2], UniValue::VARR); UniValue inputs = request.params[2].get_array(); for (unsigned int idx = 0; idx < inputs.size(); idx++) { const UniValue& input = inputs[idx]; @@ -591,7 +587,6 @@ RPCHelpMan listunspent() bool include_unsafe = true; if (!request.params[3].isNull()) { - RPCTypeCheckArgument(request.params[3], UniValue::VBOOL); include_unsafe = request.params[3].get_bool(); } @@ -638,7 +633,7 @@ RPCHelpMan listunspent() cctl.m_max_depth = nMaxDepth; cctl.m_include_unsafe_inputs = include_unsafe; LOCK(pwallet->cs_wallet); - AvailableCoinsListUnspent(*pwallet, vecOutputs, &cctl, nMinimumAmount, nMaximumAmount, nMinimumSumAmount, nMaximumCount); + vecOutputs = AvailableCoinsListUnspent(*pwallet, &cctl, nMinimumAmount, nMaximumAmount, nMinimumSumAmount, nMaximumCount).All(); } LOCK(pwallet->cs_wallet); @@ -649,7 +644,7 @@ RPCHelpMan listunspent() CTxDestination address; const CScript& scriptPubKey = out.txout.scriptPubKey; bool fValidAddress = ExtractDestination(scriptPubKey, address); - bool reused = avoid_reuse && pwallet->IsSpentKey(out.outpoint.hash, out.outpoint.n); + bool reused = avoid_reuse && pwallet->IsSpentKey(scriptPubKey); if (destinations.size() && (!fValidAddress || !destinations.count(address))) continue; @@ -722,6 +717,7 @@ RPCHelpMan listunspent() entry.pushKV("desc", descriptor->ToString()); } } + PushParentDescriptors(*pwallet, scriptPubKey, entry); if (avoid_reuse) entry.pushKV("reused", reused); entry.pushKV("safe", out.safe); results.push_back(entry); diff --git a/src/wallet/rpc/encrypt.cpp b/src/wallet/rpc/encrypt.cpp index 931c64ca06..a68f52a718 100644 --- a/src/wallet/rpc/encrypt.cpp +++ b/src/wallet/rpc/encrypt.cpp @@ -32,7 +32,7 @@ RPCHelpMan walletpassphrase() [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); - if (!wallet) return NullUniValue; + if (!wallet) return UniValue::VNULL; CWallet* const pwallet = wallet.get(); int64_t nSleepTime; @@ -98,7 +98,7 @@ RPCHelpMan walletpassphrase() } }, nSleepTime); - return NullUniValue; + return UniValue::VNULL; }, }; } @@ -120,7 +120,7 @@ RPCHelpMan walletpassphrasechange() [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); - if (!pwallet) return NullUniValue; + if (!pwallet) return UniValue::VNULL; LOCK(pwallet->cs_wallet); @@ -146,7 +146,7 @@ RPCHelpMan walletpassphrasechange() throw JSONRPCError(RPC_WALLET_PASSPHRASE_INCORRECT, "Error: The wallet passphrase entered was incorrect."); } - return NullUniValue; + return UniValue::VNULL; }, }; } @@ -173,7 +173,7 @@ RPCHelpMan walletlock() [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); - if (!pwallet) return NullUniValue; + if (!pwallet) return UniValue::VNULL; LOCK(pwallet->cs_wallet); @@ -184,7 +184,7 @@ RPCHelpMan walletlock() pwallet->Lock(); pwallet->nRelockTime = 0; - return NullUniValue; + return UniValue::VNULL; }, }; } @@ -217,7 +217,7 @@ RPCHelpMan encryptwallet() [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); - if (!pwallet) return NullUniValue; + if (!pwallet) return UniValue::VNULL; LOCK(pwallet->cs_wallet); diff --git a/src/wallet/rpc/signmessage.cpp b/src/wallet/rpc/signmessage.cpp index 438d290030..ae4bd4fbc5 100644 --- a/src/wallet/rpc/signmessage.cpp +++ b/src/wallet/rpc/signmessage.cpp @@ -36,7 +36,7 @@ RPCHelpMan signmessage() [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request); - if (!pwallet) return NullUniValue; + if (!pwallet) return UniValue::VNULL; LOCK(pwallet->cs_wallet); diff --git a/src/wallet/rpc/spend.cpp b/src/wallet/rpc/spend.cpp index d1a0ba50f6..ebf694157b 100644 --- a/src/wallet/rpc/spend.cpp +++ b/src/wallet/rpc/spend.cpp @@ -156,18 +156,16 @@ UniValue SendMoney(CWallet& wallet, const CCoinControl &coin_control, std::vecto // Send constexpr int RANDOM_CHANGE_POSITION = -1; - bilingual_str error; - FeeCalculation fee_calc_out; - std::optional<CreatedTransactionResult> txr = CreateTransaction(wallet, recipients, RANDOM_CHANGE_POSITION, error, coin_control, fee_calc_out, true); - if (!txr) { - throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, error.original); + auto res = CreateTransaction(wallet, recipients, RANDOM_CHANGE_POSITION, coin_control, true); + if (!res) { + throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, util::ErrorString(res).original); } - CTransactionRef tx = txr->tx; + const CTransactionRef& tx = res->tx; wallet.CommitTransaction(tx, std::move(map_value), {} /* orderForm */); if (verbose) { UniValue entry(UniValue::VOBJ); entry.pushKV("txid", tx->GetHash().GetHex()); - entry.pushKV("fee_reason", StringForFeeReason(fee_calc_out.reason)); + entry.pushKV("fee_reason", StringForFeeReason(res->fee_calc.reason)); return entry; } return tx->GetHash().GetHex(); @@ -226,10 +224,10 @@ RPCHelpMan sendtoaddress() "transaction, just kept in your wallet."}, {"subtractfeefromamount", RPCArg::Type::BOOL, RPCArg::Default{false}, "The fee will be deducted from the amount being sent.\n" "The recipient will receive less bitcoins than you enter in the amount field."}, - {"replaceable", RPCArg::Type::BOOL, RPCArg::DefaultHint{"wallet default"}, "Allow this transaction to be replaced by a transaction with higher fees via BIP 125"}, + {"replaceable", RPCArg::Type::BOOL, RPCArg::DefaultHint{"wallet default"}, "Signal that this transaction can be replaced by a transaction (BIP 125)"}, {"conf_target", RPCArg::Type::NUM, RPCArg::DefaultHint{"wallet -txconfirmtarget"}, "Confirmation target in blocks"}, - {"estimate_mode", RPCArg::Type::STR, RPCArg::Default{"unset"}, std::string() + "The fee estimate mode, must be one of (case insensitive):\n" - " \"" + FeeModes("\"\n\"") + "\""}, + {"estimate_mode", RPCArg::Type::STR, RPCArg::Default{"unset"}, "The fee estimate mode, must be one of (case insensitive):\n" + "\"" + FeeModes("\"\n\"") + "\""}, {"avoid_reuse", RPCArg::Type::BOOL, RPCArg::Default{true}, "(only available if avoid_reuse wallet flag is set) Avoid spending from dirty addresses; addresses are considered\n" "dirty if they have previously been used in a transaction. If true, this also activates avoidpartialspends, grouping outputs by their addresses."}, {"fee_rate", RPCArg::Type::AMOUNT, RPCArg::DefaultHint{"not set, fall back to wallet fee estimation"}, "Specify a fee rate in " + CURRENCY_ATOM + "/vB."}, @@ -263,7 +261,7 @@ RPCHelpMan sendtoaddress() [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); - if (!pwallet) return NullUniValue; + if (!pwallet) return UniValue::VNULL; // 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 @@ -319,7 +317,7 @@ RPCHelpMan sendmany() "\nSend multiple times. Amounts are double-precision floating point numbers." + HELP_REQUIRING_PASSPHRASE, { - {"dummy", RPCArg::Type::STR, RPCArg::Optional::NO, "Must be set to \"\" for backwards compatibility.", "\"\""}, + {"dummy", RPCArg::Type::STR, RPCArg::Optional::NO, "Must be set to \"\" for backwards compatibility.", RPCArgOptions{.oneline_description="\"\""}}, {"amounts", RPCArg::Type::OBJ_USER_KEYS, RPCArg::Optional::NO, "The addresses and amounts", { {"address", RPCArg::Type::AMOUNT, RPCArg::Optional::NO, "The bitcoin address is the key, the numeric amount (can be string) in " + CURRENCY_UNIT + " is the value"}, @@ -335,12 +333,12 @@ RPCHelpMan sendmany() {"address", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "Subtract fee from this address"}, }, }, - {"replaceable", RPCArg::Type::BOOL, RPCArg::DefaultHint{"wallet default"}, "Allow this transaction to be replaced by a transaction with higher fees via BIP 125"}, + {"replaceable", RPCArg::Type::BOOL, RPCArg::DefaultHint{"wallet default"}, "Signal that this transaction can be replaced by a transaction (BIP 125)"}, {"conf_target", RPCArg::Type::NUM, RPCArg::DefaultHint{"wallet -txconfirmtarget"}, "Confirmation target in blocks"}, - {"estimate_mode", RPCArg::Type::STR, RPCArg::Default{"unset"}, std::string() + "The fee estimate mode, must be one of (case insensitive):\n" - " \"" + FeeModes("\"\n\"") + "\""}, + {"estimate_mode", RPCArg::Type::STR, RPCArg::Default{"unset"}, "The fee estimate mode, must be one of (case insensitive):\n" + "\"" + FeeModes("\"\n\"") + "\""}, {"fee_rate", RPCArg::Type::AMOUNT, RPCArg::DefaultHint{"not set, fall back to wallet fee estimation"}, "Specify a fee rate in " + CURRENCY_ATOM + "/vB."}, - {"verbose", RPCArg::Type::BOOL, RPCArg::Default{false}, "If true, return extra infomration about the transaction."}, + {"verbose", RPCArg::Type::BOOL, RPCArg::Default{false}, "If true, return extra information about the transaction."}, }, { RPCResult{"if verbose is not set or set to false", @@ -369,7 +367,7 @@ RPCHelpMan sendmany() [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); - if (!pwallet) return NullUniValue; + if (!pwallet) return UniValue::VNULL; // 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 @@ -424,7 +422,7 @@ RPCHelpMan settxfee() [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); - if (!pwallet) return NullUniValue; + if (!pwallet) return UniValue::VNULL; LOCK(pwallet->cs_wallet); @@ -453,8 +451,8 @@ static std::vector<RPCArg> FundTxDoc(bool solving_data = true) { std::vector<RPCArg> args = { {"conf_target", RPCArg::Type::NUM, RPCArg::DefaultHint{"wallet -txconfirmtarget"}, "Confirmation target in blocks"}, - {"estimate_mode", RPCArg::Type::STR, RPCArg::Default{"unset"}, std::string() + "The fee estimate mode, must be one of (case insensitive):\n" - " \"" + FeeModes("\"\n\"") + "\""}, + {"estimate_mode", RPCArg::Type::STR, RPCArg::Default{"unset"}, "The fee estimate mode, must be one of (case insensitive):\n" + "\"" + FeeModes("\"\n\"") + "\""}, { "replaceable", RPCArg::Type::BOOL, RPCArg::DefaultHint{"wallet default"}, "Marks this transaction as BIP125-replaceable.\n" "Allows this transaction to be replaced by a transaction with higher fees" @@ -505,7 +503,6 @@ void FundTransaction(CWallet& wallet, CMutableTransaction& tx, CAmount& fee_out, coinControl.fAllowWatchOnly = options.get_bool(); } else { - RPCTypeCheckArgument(options, UniValue::VOBJ); RPCTypeCheckObj(options, { {"add_inputs", UniValueType(UniValue::VBOOL)}, @@ -535,8 +532,8 @@ void FundTransaction(CWallet& wallet, CMutableTransaction& tx, CAmount& fee_out, }, true, true); - if (options.exists("add_inputs") ) { - coinControl.m_add_inputs = options["add_inputs"].get_bool(); + if (options.exists("add_inputs")) { + coinControl.m_allow_other_inputs = options["add_inputs"].get_bool(); } if (options.exists("changeAddress") || options.exists("change_address")) { @@ -646,7 +643,7 @@ void FundTransaction(CWallet& wallet, CMutableTransaction& tx, CAmount& fee_out, throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Unable to parse descriptor '%s': %s", desc_str, error)); } desc->Expand(0, desc_out, scripts_temp, desc_out); - coinControl.m_external_provider = Merge(coinControl.m_external_provider, desc_out); + coinControl.m_external_provider.Merge(std::move(desc_out)); } } } @@ -699,19 +696,6 @@ void FundTransaction(CWallet& wallet, CMutableTransaction& tx, CAmount& fee_out, setSubtractFeeFromOutputs.insert(pos); } - // Fetch specified UTXOs from the UTXO set to get the scriptPubKeys and values of the outputs being selected - // and to match with the given solving_data. Only used for non-wallet outputs. - std::map<COutPoint, Coin> coins; - for (const CTxIn& txin : tx.vin) { - coins[txin.prevout]; // Create empty map entry keyed by prevout. - } - wallet.chain().findCoins(coins); - for (const auto& coin : coins) { - if (!coin.second.out.IsNull()) { - coinControl.SelectExternal(coin.first, coin.second.out); - } - } - bilingual_str error; if (!FundTransaction(wallet, tx, fee_out, change_position, error, lockUnspents, setSubtractFeeFromOutputs, coinControl)) { @@ -790,7 +774,7 @@ RPCHelpMan fundrawtransaction() }, }, FundTxDoc()), - "options"}, + RPCArgOptions{.oneline_description="options"}}, {"iswitness", RPCArg::Type::BOOL, RPCArg::DefaultHint{"depends on heuristic tests"}, "Whether the transaction hex is a serialized witness transaction.\n" "If iswitness is not present, heuristic tests will be used in decoding.\n" "If true, only witness deserialization will be tried.\n" @@ -820,7 +804,7 @@ RPCHelpMan fundrawtransaction() [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); - if (!pwallet) return NullUniValue; + if (!pwallet) return UniValue::VNULL; RPCTypeCheck(request.params, {UniValue::VSTR, UniValueType(), UniValue::VBOOL}); @@ -836,7 +820,7 @@ RPCHelpMan fundrawtransaction() int change_position; CCoinControl coin_control; // Automatically select (additional) coins. Can be overridden by options.add_inputs. - coin_control.m_add_inputs = true; + coin_control.m_allow_other_inputs = true; FundTransaction(*pwallet, tx, fee, change_position, request.params[1], coin_control, /*override_min_fee=*/true); UniValue result(UniValue::VOBJ); @@ -910,7 +894,7 @@ RPCHelpMan signrawtransactionwithwallet() [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request); - if (!pwallet) return NullUniValue; + if (!pwallet) return UniValue::VNULL; RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VARR, UniValue::VSTR}, true); @@ -982,9 +966,9 @@ static RPCHelpMan bumpfee_helper(std::string method_name) "still be replaceable in practice, for example if it has unconfirmed ancestors which\n" "are replaceable).\n"}, {"estimate_mode", RPCArg::Type::STR, RPCArg::Default{"unset"}, "The fee estimate mode, must be one of (case insensitive):\n" - "\"" + FeeModes("\"\n\"") + "\""}, + "\"" + FeeModes("\"\n\"") + "\""}, }, - "options"}, + RPCArgOptions{.oneline_description="options"}}, }, RPCResult{ RPCResult::Type::OBJ, "", "", Cat( @@ -1007,7 +991,7 @@ static RPCHelpMan bumpfee_helper(std::string method_name) [want_psbt](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); - if (!pwallet) return NullUniValue; + if (!pwallet) return UniValue::VNULL; 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."); @@ -1060,7 +1044,7 @@ static RPCHelpMan bumpfee_helper(std::string method_name) CMutableTransaction mtx; feebumper::Result res; // Targeting feerate bump. - res = feebumper::CreateRateBumpTransaction(*pwallet, hash, coin_control, errors, old_fee, new_fee, mtx); + res = feebumper::CreateRateBumpTransaction(*pwallet, hash, coin_control, errors, old_fee, new_fee, mtx, /*require_mine=*/ !want_psbt); if (res != feebumper::Result::OK) { switch(res) { case feebumper::Result::INVALID_ADDRESS_OR_KEY: @@ -1146,13 +1130,13 @@ RPCHelpMan send() }, }, {"conf_target", RPCArg::Type::NUM, RPCArg::DefaultHint{"wallet -txconfirmtarget"}, "Confirmation target in blocks"}, - {"estimate_mode", RPCArg::Type::STR, RPCArg::Default{"unset"}, std::string() + "The fee estimate mode, must be one of (case insensitive):\n" - " \"" + FeeModes("\"\n\"") + "\""}, + {"estimate_mode", RPCArg::Type::STR, RPCArg::Default{"unset"}, "The fee estimate mode, must be one of (case insensitive):\n" + "\"" + FeeModes("\"\n\"") + "\""}, {"fee_rate", RPCArg::Type::AMOUNT, RPCArg::DefaultHint{"not set, fall back to wallet fee estimation"}, "Specify a fee rate in " + CURRENCY_ATOM + "/vB."}, {"options", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED_NAMED_ARG, "", Cat<std::vector<RPCArg>>( { - {"add_inputs", RPCArg::Type::BOOL, RPCArg::Default{false}, "If inputs are specified, automatically include more if they are not enough."}, + {"add_inputs", RPCArg::Type::BOOL, RPCArg::DefaultHint{"false when \"inputs\" are specified, true otherwise"},"Automatically include coins from the wallet to cover the target amount.\n"}, {"include_unsafe", RPCArg::Type::BOOL, RPCArg::Default{false}, "Include inputs that are not safe to spend (unconfirmed transactions from outside keys and unconfirmed replacement transactions).\n" "Warning: the resulting transaction may become invalid if one of the unsafe inputs disappears.\n" "If that happens, you will need to fund the transaction with different inputs and republish it."}, @@ -1189,7 +1173,7 @@ RPCHelpMan send() }, }, FundTxDoc()), - "options"}, + RPCArgOptions{.oneline_description="options"}}, }, RPCResult{ RPCResult::Type::OBJ, "", "", @@ -1224,7 +1208,7 @@ RPCHelpMan send() ); std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); - if (!pwallet) return NullUniValue; + if (!pwallet) return UniValue::VNULL; UniValue options{request.params[4].isNull() ? UniValue::VOBJ : request.params[4]}; InterpretFeeEstimationInstructions(/*conf_target=*/request.params[1], /*estimate_mode=*/request.params[2], /*fee_rate=*/request.params[3], options); @@ -1238,7 +1222,7 @@ RPCHelpMan send() CCoinControl coin_control; // Automatically select coins, unless at least one is manually selected. Can // be overridden by options.add_inputs. - coin_control.m_add_inputs = rawTx.vin.size() == 0; + coin_control.m_allow_other_inputs = rawTx.vin.size() == 0; SetOptionsInputWeights(options["inputs"], options); FundTransaction(*pwallet, rawTx, fee, change_position, options, coin_control, /*override_min_fee=*/false); @@ -1267,8 +1251,8 @@ RPCHelpMan sendall() }, }, {"conf_target", RPCArg::Type::NUM, RPCArg::DefaultHint{"wallet -txconfirmtarget"}, "Confirmation target in blocks"}, - {"estimate_mode", RPCArg::Type::STR, RPCArg::Default{"unset"}, std::string() + "The fee estimate mode, must be one of (case insensitive):\n" - " \"" + FeeModes("\"\n\"") + "\""}, + {"estimate_mode", RPCArg::Type::STR, RPCArg::Default{"unset"}, "The fee estimate mode, must be one of (case insensitive):\n" + "\"" + FeeModes("\"\n\"") + "\""}, {"fee_rate", RPCArg::Type::AMOUNT, RPCArg::DefaultHint{"not set, fall back to wallet fee estimation"}, "Specify a fee rate in " + CURRENCY_ATOM + "/vB."}, { "options", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED_NAMED_ARG, "", @@ -1279,11 +1263,15 @@ RPCHelpMan sendall() {"include_watching", RPCArg::Type::BOOL, RPCArg::DefaultHint{"true for watch-only wallets, otherwise false"}, "Also select inputs which are watch-only.\n" "Only solvable inputs can be used. Watch-only destinations are solvable if the public key and/or output script was imported,\n" "e.g. with 'importpubkey' or 'importmulti' with the 'pubkeys' or 'desc' field."}, - {"inputs", RPCArg::Type::ARR, RPCArg::Default{UniValue::VARR}, "Use exactly the specified inputs to build the transaction. Specifying inputs is incompatible with send_max. A JSON array of JSON objects", + {"inputs", RPCArg::Type::ARR, RPCArg::Default{UniValue::VARR}, "Use exactly the specified inputs to build the transaction. Specifying inputs is incompatible with send_max.", { - {"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The transaction id"}, - {"vout", RPCArg::Type::NUM, RPCArg::Optional::NO, "The output number"}, - {"sequence", RPCArg::Type::NUM, RPCArg::Optional::NO, "The sequence number"}, + {"", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED, "", + { + {"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The transaction id"}, + {"vout", RPCArg::Type::NUM, RPCArg::Optional::NO, "The output number"}, + {"sequence", RPCArg::Type::NUM, RPCArg::DefaultHint{"depends on the value of the 'replaceable' and 'locktime' arguments"}, "The sequence number"}, + }, + }, }, }, {"locktime", RPCArg::Type::NUM, RPCArg::Default{0}, "Raw locktime. Non-0 value also locktime-activates inputs"}, @@ -1293,7 +1281,7 @@ RPCHelpMan sendall() }, FundTxDoc() ), - "options" + RPCArgOptions{.oneline_description="options"} }, }, RPCResult{ @@ -1329,7 +1317,7 @@ RPCHelpMan sendall() ); std::shared_ptr<CWallet> const pwallet{GetWalletForJSONRPCRequest(request)}; - if (!pwallet) return NullUniValue; + if (!pwallet) return UniValue::VNULL; // 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(); @@ -1380,7 +1368,6 @@ RPCHelpMan sendall() CMutableTransaction rawTx{ConstructTransaction(options["inputs"], recipient_key_value_pairs, options["locktime"], rbf)}; LOCK(pwallet->cs_wallet); - std::vector<COutput> all_the_utxos; CAmount total_input_value(0); bool send_max{options.exists("send_max") ? options["send_max"].get_bool() : false}; @@ -1388,7 +1375,7 @@ RPCHelpMan sendall() throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot combine send_max with specific inputs."); } else if (options.exists("inputs")) { for (const CTxIn& input : rawTx.vin) { - if (pwallet->IsSpent(input.prevout.hash, input.prevout.n)) { + if (pwallet->IsSpent(input.prevout)) { throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Input not available. UTXO (%s:%d) was already spent.", input.prevout.hash.ToString(), input.prevout.n)); } const CWalletTx* tx{pwallet->GetWalletTx(input.prevout.hash)}; @@ -1398,8 +1385,7 @@ RPCHelpMan sendall() total_input_value += tx->tx->vout[input.prevout.n].nValue; } } else { - AvailableCoins(*pwallet, all_the_utxos, &coin_control, fee_rate, /*nMinimumAmount=*/0); - for (const COutput& output : all_the_utxos) { + for (const COutput& output : AvailableCoins(*pwallet, &coin_control, fee_rate, /*nMinimumAmount=*/0).All()) { CHECK_NONFATAL(output.input_bytes > 0); if (send_max && fee_rate.GetFee(output.input_bytes) > output.txout.nValue) { continue; @@ -1415,6 +1401,10 @@ RPCHelpMan sendall() const CAmount fee_from_size{fee_rate.GetFee(tx_size.vsize)}; const CAmount effective_value{total_input_value - fee_from_size}; + if (fee_from_size > pwallet->m_default_max_tx_fee) { + throw JSONRPCError(RPC_WALLET_ERROR, TransactionErrorString(TransactionError::MAX_FEE_EXCEEDED).original); + } + if (effective_value <= 0) { if (send_max) { throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, "Total value of UTXO pool too low to pay for transaction, try using lower feerate."); @@ -1423,8 +1413,13 @@ RPCHelpMan sendall() } } + // If this transaction is too large, e.g. because the wallet has many UTXOs, it will be rejected by the node's mempool. + if (tx_size.weight > MAX_STANDARD_TX_WEIGHT) { + throw JSONRPCError(RPC_WALLET_ERROR, "Transaction too large."); + } + CAmount output_amounts_claimed{0}; - for (CTxOut out : rawTx.vout) { + for (const CTxOut& out : rawTx.vout) { output_amounts_claimed += out.nValue; } @@ -1507,7 +1502,7 @@ RPCHelpMan walletprocesspsbt() [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request); - if (!pwallet) return NullUniValue; + if (!pwallet) return UniValue::VNULL; const CWallet& wallet{*pwallet}; // Make sure the results are valid at least up to the most recent block @@ -1595,7 +1590,7 @@ RPCHelpMan walletcreatefundedpsbt() {"options", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED_NAMED_ARG, "", Cat<std::vector<RPCArg>>( { - {"add_inputs", RPCArg::Type::BOOL, RPCArg::Default{false}, "If inputs are specified, automatically include more if they are not enough."}, + {"add_inputs", RPCArg::Type::BOOL, RPCArg::DefaultHint{"false when \"inputs\" are specified, true otherwise"}, "Automatically include coins from the wallet to cover the target amount.\n"}, {"include_unsafe", RPCArg::Type::BOOL, RPCArg::Default{false}, "Include inputs that are not safe to spend (unconfirmed transactions from outside keys and unconfirmed replacement transactions).\n" "Warning: the resulting transaction may become invalid if one of the unsafe inputs disappears.\n" "If that happens, you will need to fund the transaction with different inputs and republish it."}, @@ -1616,7 +1611,7 @@ RPCHelpMan walletcreatefundedpsbt() }, }, FundTxDoc()), - "options"}, + RPCArgOptions{.oneline_description="options"}}, {"bip32derivs", RPCArg::Type::BOOL, RPCArg::Default{true}, "Include BIP 32 derivation paths for public keys if we know them"}, }, RPCResult{ @@ -1634,7 +1629,7 @@ RPCHelpMan walletcreatefundedpsbt() [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); - if (!pwallet) return NullUniValue; + if (!pwallet) return UniValue::VNULL; CWallet& wallet{*pwallet}; // Make sure the results are valid at least up to the most recent block @@ -1650,21 +1645,20 @@ RPCHelpMan walletcreatefundedpsbt() }, true ); - UniValue options = request.params[3]; + UniValue options{request.params[3].isNull() ? UniValue::VOBJ : request.params[3]}; CAmount fee; int change_position; bool rbf{wallet.m_signal_rbf}; const UniValue &replaceable_arg = options["replaceable"]; if (!replaceable_arg.isNull()) { - RPCTypeCheckArgument(replaceable_arg, UniValue::VBOOL); rbf = replaceable_arg.isTrue(); } CMutableTransaction rawTx = ConstructTransaction(request.params[0], request.params[1], request.params[2], rbf); CCoinControl coin_control; // Automatically select coins, unless at least one is manually selected. Can // be overridden by options.add_inputs. - coin_control.m_add_inputs = rawTx.vin.size() == 0; + coin_control.m_allow_other_inputs = rawTx.vin.size() == 0; SetOptionsInputWeights(request.params[0], options); FundTransaction(wallet, rawTx, fee, change_position, options, coin_control, /*override_min_fee=*/true); diff --git a/src/wallet/rpc/transactions.cpp b/src/wallet/rpc/transactions.cpp index fae9bf3ea5..0e13e4756b 100644 --- a/src/wallet/rpc/transactions.cpp +++ b/src/wallet/rpc/transactions.cpp @@ -85,14 +85,12 @@ static UniValue ListReceived(const CWallet& wallet, const UniValue& params, cons filter |= ISMINE_WATCH_ONLY; } - bool has_filtered_address = false; - CTxDestination filtered_address = CNoDestination(); + std::optional<CTxDestination> filtered_address{std::nullopt}; if (!by_label && !params[3].isNull() && !params[3].get_str().empty()) { if (!IsValidDestinationString(params[3].get_str())) { throw JSONRPCError(RPC_WALLET_ERROR, "address_filter parameter was invalid"); } filtered_address = DecodeDestination(params[3].get_str()); - has_filtered_address = true; } // Tally @@ -106,23 +104,21 @@ static UniValue ListReceived(const CWallet& wallet, const UniValue& params, cons // Coinbase with less than 1 confirmation is no longer in the main chain if ((wtx.IsCoinBase() && (nDepth < 1)) - || (wallet.IsTxImmatureCoinBase(wtx) && !include_immature_coinbase)) - { + || (wallet.IsTxImmatureCoinBase(wtx) && !include_immature_coinbase)) { continue; } - for (const CTxOut& txout : wtx.tx->vout) - { + for (const CTxOut& txout : wtx.tx->vout) { CTxDestination address; if (!ExtractDestination(txout.scriptPubKey, address)) continue; - if (has_filtered_address && !(filtered_address == address)) { + if (filtered_address && !(filtered_address == address)) { continue; } isminefilter mine = wallet.IsMine(address); - if(!(mine & filter)) + if (!(mine & filter)) continue; tallyitem& item = mapTally[address]; @@ -138,70 +134,55 @@ static UniValue ListReceived(const CWallet& wallet, const UniValue& params, cons UniValue ret(UniValue::VARR); std::map<std::string, tallyitem> label_tally; - // Create m_address_book iterator - // If we aren't filtering, go from begin() to 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 = wallet.m_address_book.find(filtered_address); - if (start != end) { - end = std::next(start); - } - } + const auto& func = [&](const CTxDestination& address, const std::string& label, const std::string& purpose, bool is_change) { + if (is_change) return; // no change addresses - for (auto item_it = start; item_it != end; ++item_it) - { - if (item_it->second.IsChange()) continue; - const CTxDestination& address = item_it->first; - const std::string& label = item_it->second.GetLabel(); auto it = mapTally.find(address); if (it == mapTally.end() && !fIncludeEmpty) - continue; + return; CAmount nAmount = 0; int nConf = std::numeric_limits<int>::max(); bool fIsWatchonly = false; - if (it != mapTally.end()) - { + if (it != mapTally.end()) { nAmount = (*it).second.nAmount; nConf = (*it).second.nConf; fIsWatchonly = (*it).second.fIsWatchonly; } - if (by_label) - { + if (by_label) { tallyitem& _item = label_tally[label]; _item.nAmount += nAmount; _item.nConf = std::min(_item.nConf, nConf); _item.fIsWatchonly = fIsWatchonly; - } - else - { + } else { UniValue obj(UniValue::VOBJ); - if(fIsWatchonly) - obj.pushKV("involvesWatchonly", true); + if (fIsWatchonly) obj.pushKV("involvesWatchonly", true); obj.pushKV("address", EncodeDestination(address)); obj.pushKV("amount", ValueFromAmount(nAmount)); obj.pushKV("confirmations", (nConf == std::numeric_limits<int>::max() ? 0 : nConf)); obj.pushKV("label", label); UniValue transactions(UniValue::VARR); - if (it != mapTally.end()) - { - for (const uint256& _item : (*it).second.txids) - { + if (it != mapTally.end()) { + for (const uint256& _item : (*it).second.txids) { transactions.push_back(_item.GetHex()); } } obj.pushKV("txids", transactions); ret.push_back(obj); } + }; + + if (filtered_address) { + const auto& entry = wallet.FindAddressBookEntry(*filtered_address, /*allow_change=*/false); + if (entry) func(*filtered_address, entry->GetLabel(), entry->purpose, /*is_change=*/false); + } else { + // No filtered addr, walk-through the addressbook entry + wallet.ForEachAddrBookEntry(func); } - if (by_label) - { - for (const auto& entry : label_tally) - { + if (by_label) { + for (const auto& entry : label_tally) { CAmount nAmount = entry.second.nAmount; int nConf = entry.second.nConf; UniValue obj(UniValue::VOBJ); @@ -255,7 +236,7 @@ RPCHelpMan listreceivedbyaddress() [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request); - if (!pwallet) return NullUniValue; + if (!pwallet) return UniValue::VNULL; // 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 @@ -300,7 +281,7 @@ RPCHelpMan listreceivedbylabel() [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request); - if (!pwallet) return NullUniValue; + if (!pwallet) return UniValue::VNULL; // 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 @@ -329,17 +310,21 @@ static void MaybePushAddress(UniValue & entry, const CTxDestination &dest) * @param wtx The wallet transaction. * @param nMinDepth The minimum confirmation depth. * @param fLong Whether to include the JSON version of the transaction. - * @param ret The UniValue into which the result is stored. + * @param ret The vector into which the result is stored. * @param filter_ismine The "is mine" filter flags. * @param filter_label Optional label string to filter incoming transactions. */ -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) +template <class Vec> +static void ListTransactions(const CWallet& wallet, const CWalletTx& wtx, int nMinDepth, bool fLong, + Vec& ret, const isminefilter& filter_ismine, const std::string* filter_label, + bool include_change = false) + EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet) { CAmount nFee; std::list<COutputEntry> listReceived; std::list<COutputEntry> listSent; - CachedTxGetAmounts(wallet, wtx, listReceived, listSent, nFee, filter_ismine); + CachedTxGetAmounts(wallet, wtx, listReceived, listSent, nFee, filter_ismine, include_change); bool involvesWatchonly = CachedTxIsFromMe(wallet, wtx, ISMINE_WATCH_ONLY); @@ -385,6 +370,7 @@ static void ListTransactions(const CWallet& wallet, const CWalletTx& wtx, int nM entry.pushKV("involvesWatchonly", true); } MaybePushAddress(entry, r.destination); + PushParentDescriptors(wallet, wtx.tx->vout.at(r.vout).scriptPubKey, entry); if (wtx.IsCoinBase()) { if (wallet.GetTxDepthInMainChain(wtx) < 1) @@ -435,8 +421,12 @@ static const std::vector<RPCResult> TransactionDescriptionString() {RPCResult::Type::NUM_TIME, "time", "The transaction time expressed in " + UNIX_EPOCH_TIME + "."}, {RPCResult::Type::NUM_TIME, "timereceived", "The time received expressed in " + UNIX_EPOCH_TIME + "."}, {RPCResult::Type::STR, "comment", /*optional=*/true, "If a comment is associated with the transaction, only present if not empty."}, - {RPCResult::Type::STR, "bip125-replaceable", "(\"yes|no|unknown\") Whether this transaction could be replaced due to BIP125 (replace-by-fee);\n" - "may be unknown for unconfirmed transactions not in the mempool."}}; + {RPCResult::Type::STR, "bip125-replaceable", "(\"yes|no|unknown\") Whether this transaction signals BIP125 replaceability or has an unconfirmed ancestor signaling BIP125 replaceability.\n" + "May be unknown for unconfirmed transactions not in the mempool because their unconfirmed ancestors are unknown."}, + {RPCResult::Type::ARR, "parent_descs", /*optional=*/true, "Only if 'category' is 'received'. List of parent descriptors for the scriptPubKey of this coin.", { + {RPCResult::Type::STR, "desc", "The descriptor string."}, + }}, + }; } RPCHelpMan listtransactions() @@ -489,7 +479,7 @@ RPCHelpMan listtransactions() [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request); - if (!pwallet) return NullUniValue; + if (!pwallet) return UniValue::VNULL; // 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 @@ -519,8 +509,7 @@ RPCHelpMan listtransactions() if (nFrom < 0) throw JSONRPCError(RPC_INVALID_PARAMETER, "Negative from"); - UniValue ret(UniValue::VARR); - + std::vector<UniValue> ret; { LOCK(pwallet->cs_wallet); @@ -542,9 +531,9 @@ RPCHelpMan listtransactions() if ((nFrom + nCount) > (int)ret.size()) nCount = ret.size() - nFrom; - const std::vector<UniValue>& txs = ret.getValues(); + auto txs_rev_it{std::make_move_iterator(ret.rend())}; UniValue result{UniValue::VARR}; - result.push_backV({ txs.rend() - nFrom - nCount, txs.rend() - nFrom }); // Return oldest to newest + result.push_backV(txs_rev_it - nFrom - nCount, txs_rev_it - nFrom); // Return oldest to newest return result; }, }; @@ -562,6 +551,7 @@ RPCHelpMan listsinceblock() {"include_watchonly", RPCArg::Type::BOOL, RPCArg::DefaultHint{"true for watch-only wallets, otherwise false"}, "Include transactions to watch-only addresses (see 'importaddress')"}, {"include_removed", RPCArg::Type::BOOL, RPCArg::Default{true}, "Show transactions that were removed due to a reorg in the \"removed\" array\n" "(not guaranteed to work on pruned nodes)"}, + {"include_change", RPCArg::Type::BOOL, RPCArg::Default{false}, "Also add entries for change outputs.\n"}, }, RPCResult{ RPCResult::Type::OBJ, "", "", @@ -605,7 +595,7 @@ RPCHelpMan listsinceblock() [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request); - if (!pwallet) return NullUniValue; + if (!pwallet) return UniValue::VNULL; const CWallet& wallet = *pwallet; // Make sure the results are valid at least up to the most recent block @@ -642,6 +632,7 @@ RPCHelpMan listsinceblock() } bool include_removed = (request.params[3].isNull() || request.params[3].get_bool()); + bool include_change = (!request.params[4].isNull() && request.params[4].get_bool()); int depth = height ? wallet.GetLastBlockHeight() + 1 - *height : -1; @@ -651,7 +642,7 @@ RPCHelpMan listsinceblock() const CWalletTx& tx = pairWtx.second; if (depth == -1 || abs(wallet.GetTxDepthInMainChain(tx)) < depth) { - ListTransactions(wallet, tx, 0, true, transactions, filter, nullptr /* filter_label */); + ListTransactions(wallet, tx, 0, true, transactions, filter, nullptr /* filter_label */, /*include_change=*/include_change); } } @@ -668,7 +659,7 @@ 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 */, /*include_change=*/include_change); } } blockId = block.hashPrevBlock; @@ -728,6 +719,9 @@ RPCHelpMan gettransaction() "'send' category of transactions."}, {RPCResult::Type::BOOL, "abandoned", /*optional=*/true, "'true' if the transaction has been abandoned (inputs are respendable). Only available for the \n" "'send' category of transactions."}, + {RPCResult::Type::ARR, "parent_descs", /*optional=*/true, "Only if 'category' is 'received'. List of parent descriptors for the scriptPubKey of this coin.", { + {RPCResult::Type::STR, "desc", "The descriptor string."}, + }}, }}, }}, {RPCResult::Type::STR_HEX, "hex", "Raw data for transaction"}, @@ -746,7 +740,7 @@ RPCHelpMan gettransaction() [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request); - if (!pwallet) return NullUniValue; + if (!pwallet) return UniValue::VNULL; // 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 @@ -819,7 +813,7 @@ RPCHelpMan abandontransaction() [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); - if (!pwallet) return NullUniValue; + if (!pwallet) return UniValue::VNULL; // 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 @@ -836,7 +830,7 @@ RPCHelpMan abandontransaction() throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Transaction not eligible for abandonment"); } - return NullUniValue; + return UniValue::VNULL; }, }; } @@ -864,7 +858,7 @@ RPCHelpMan rescanblockchain() [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); - if (!pwallet) return NullUniValue; + if (!pwallet) return UniValue::VNULL; CWallet& wallet{*pwallet}; // Make sure the results are valid at least up to the most recent block @@ -908,7 +902,7 @@ RPCHelpMan rescanblockchain() } CWallet::ScanResult result = - pwallet->ScanForWalletTransactions(start_block, start_height, stop_height, reserver, true /* fUpdate */); + pwallet->ScanForWalletTransactions(start_block, start_height, stop_height, reserver, /*fUpdate=*/true, /*save_progress=*/false); switch (result.status) { case CWallet::ScanResult::SUCCESS: break; @@ -944,7 +938,7 @@ RPCHelpMan abortrescan() [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); - if (!pwallet) return NullUniValue; + if (!pwallet) return UniValue::VNULL; if (!pwallet->IsScanning() || pwallet->IsAbortingRescan()) return false; pwallet->AbortRescan(); diff --git a/src/wallet/rpc/util.cpp b/src/wallet/rpc/util.cpp index 59683c5fd8..1aa2a87e99 100644 --- a/src/wallet/rpc/util.cpp +++ b/src/wallet/rpc/util.cpp @@ -12,10 +12,26 @@ #include <univalue.h> +#include <boost/date_time/posix_time/posix_time.hpp> + namespace wallet { static const std::string WALLET_ENDPOINT_BASE = "/wallet/"; const std::string HELP_REQUIRING_PASSPHRASE{"\nRequires wallet passphrase to be set with walletpassphrase call if wallet is encrypted.\n"}; +int64_t ParseISO8601DateTime(const std::string& str) +{ + static const boost::posix_time::ptime epoch = boost::posix_time::from_time_t(0); + static const std::locale loc(std::locale::classic(), + new boost::posix_time::time_input_facet("%Y-%m-%dT%H:%M:%SZ")); + std::istringstream iss(str); + iss.imbue(loc); + boost::posix_time::ptime ptime(boost::date_time::not_a_date_time); + iss >> ptime; + if (ptime.is_not_a_date_time() || epoch > ptime) + return 0; + return (ptime - epoch).total_seconds(); +} + 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(); @@ -64,12 +80,11 @@ std::shared_ptr<CWallet> GetWalletForJSONRPCRequest(const JSONRPCRequest& reques return pwallet; } - std::vector<std::shared_ptr<CWallet>> wallets = GetWallets(context); - if (wallets.size() == 1) { - return wallets[0]; - } + size_t count{0}; + auto wallet = GetDefaultWallet(context, count); + if (wallet) return wallet; - if (wallets.empty()) { + if (count == 0) { throw JSONRPCError( RPC_WALLET_NOT_FOUND, "No wallet is loaded. Load a wallet using loadwallet or create a new one with createwallet. (Note: A default wallet is no longer automatically created)"); } @@ -101,7 +116,7 @@ LegacyScriptPubKeyMan& EnsureLegacyScriptPubKeyMan(CWallet& wallet, bool also_cr spk_man = wallet.GetOrCreateLegacyScriptPubKeyMan(); } if (!spk_man) { - throw JSONRPCError(RPC_WALLET_ERROR, "This type of wallet does not support this command"); + throw JSONRPCError(RPC_WALLET_ERROR, "Only legacy wallets are supported by this command"); } return *spk_man; } @@ -110,7 +125,7 @@ const LegacyScriptPubKeyMan& EnsureConstLegacyScriptPubKeyMan(const CWallet& wal { const LegacyScriptPubKeyMan* spk_man = wallet.GetLegacyScriptPubKeyMan(); if (!spk_man) { - throw JSONRPCError(RPC_WALLET_ERROR, "This type of wallet does not support this command"); + throw JSONRPCError(RPC_WALLET_ERROR, "Only legacy wallets are supported by this command"); } return *spk_man; } @@ -123,6 +138,15 @@ std::string LabelFromValue(const UniValue& value) return label; } +void PushParentDescriptors(const CWallet& wallet, const CScript& script_pubkey, UniValue& entry) +{ + UniValue parent_descs(UniValue::VARR); + for (const auto& desc: wallet.GetWalletDescriptors(script_pubkey)) { + parent_descs.push_back(desc.descriptor->ToString()); + } + entry.pushKV("parent_descs", parent_descs); +} + void HandleWalletError(const std::shared_ptr<CWallet> wallet, DatabaseStatus& status, bilingual_str& error) { if (!wallet) { diff --git a/src/wallet/rpc/util.h b/src/wallet/rpc/util.h index 7b810eb06e..87d34f7c11 100644 --- a/src/wallet/rpc/util.h +++ b/src/wallet/rpc/util.h @@ -5,6 +5,8 @@ #ifndef BITCOIN_WALLET_RPC_UTIL_H #define BITCOIN_WALLET_RPC_UTIL_H +#include <script/script.h> + #include <any> #include <memory> #include <string> @@ -39,8 +41,12 @@ const LegacyScriptPubKeyMan& EnsureConstLegacyScriptPubKeyMan(const CWallet& wal bool GetAvoidReuseFlag(const CWallet& wallet, const UniValue& param); bool ParseIncludeWatchonly(const UniValue& include_watchonly, const CWallet& wallet); std::string LabelFromValue(const UniValue& value); +//! Fetch parent descriptors of this scriptPubKey. +void PushParentDescriptors(const CWallet& wallet, const CScript& script_pubkey, UniValue& entry); void HandleWalletError(const std::shared_ptr<CWallet> wallet, DatabaseStatus& status, bilingual_str& error); + +int64_t ParseISO8601DateTime(const std::string& str); } // namespace wallet #endif // BITCOIN_WALLET_RPC_UTIL_H diff --git a/src/wallet/rpc/wallet.cpp b/src/wallet/rpc/wallet.cpp index 4c44c333a6..675c4a759d 100644 --- a/src/wallet/rpc/wallet.cpp +++ b/src/wallet/rpc/wallet.cpp @@ -68,7 +68,7 @@ static RPCHelpMan getwalletinfo() [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request); - if (!pwallet) return NullUniValue; + if (!pwallet) return UniValue::VNULL; // 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 @@ -241,7 +241,7 @@ static RPCHelpMan loadwallet() static RPCHelpMan setwalletflag() { - std::string flags = ""; + std::string flags; for (auto& it : WALLET_FLAG_MAP) if (it.second & MUTABLE_WALLET_FLAGS) flags += (flags == "" ? "" : ", ") + it.first; @@ -267,7 +267,7 @@ static RPCHelpMan setwalletflag() [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); - if (!pwallet) return NullUniValue; + if (!pwallet) return UniValue::VNULL; std::string flag_str = request.params[0].get_str(); bool value = request.params[1].isNull() || request.params[1].get_bool(); @@ -480,7 +480,7 @@ static RPCHelpMan sethdseed() [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); - if (!pwallet) return NullUniValue; + if (!pwallet) return UniValue::VNULL; LegacyScriptPubKeyMan& spk_man = EnsureLegacyScriptPubKeyMan(*pwallet, true); @@ -521,7 +521,7 @@ static RPCHelpMan sethdseed() spk_man.SetHDSeed(master_pub_key); if (flush_key_pool) spk_man.NewKeyPool(); - return NullUniValue; + return UniValue::VNULL; }, }; } @@ -532,7 +532,7 @@ static RPCHelpMan upgradewallet() "\nUpgrade the wallet. Upgrades to the latest version if no version number is specified.\n" "New keys may be generated and a new wallet backup will need to be made.", { - {"version", RPCArg::Type::NUM, RPCArg::Default{FEATURE_LATEST}, "The version number to upgrade to. Default is the latest wallet version."} + {"version", RPCArg::Type::NUM, RPCArg::Default{int{FEATURE_LATEST}}, "The version number to upgrade to. Default is the latest wallet version."} }, RPCResult{ RPCResult::Type::OBJ, "", "", @@ -551,7 +551,7 @@ static RPCHelpMan upgradewallet() [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); - if (!pwallet) return NullUniValue; + if (!pwallet) return UniValue::VNULL; RPCTypeCheck(request.params, {UniValue::VNUM}, true); @@ -590,6 +590,170 @@ static RPCHelpMan upgradewallet() }; } +RPCHelpMan simulaterawtransaction() +{ + return RPCHelpMan{"simulaterawtransaction", + "\nCalculate the balance change resulting in the signing and broadcasting of the given transaction(s).\n", + { + {"rawtxs", RPCArg::Type::ARR, RPCArg::Optional::OMITTED_NAMED_ARG, "An array of hex strings of raw transactions.\n", + { + {"rawtx", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, ""}, + }, + }, + {"options", RPCArg::Type::OBJ_USER_KEYS, RPCArg::Optional::OMITTED_NAMED_ARG, "Options", + { + {"include_watchonly", RPCArg::Type::BOOL, RPCArg::DefaultHint{"true for watch-only wallets, otherwise false"}, "Whether to include watch-only addresses (see RPC importaddress)"}, + }, + }, + }, + RPCResult{ + RPCResult::Type::OBJ, "", "", + { + {RPCResult::Type::STR_AMOUNT, "balance_change", "The wallet balance change (negative means decrease)."}, + } + }, + RPCExamples{ + HelpExampleCli("simulaterawtransaction", "[\"myhex\"]") + + HelpExampleRpc("simulaterawtransaction", "[\"myhex\"]") + }, + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue +{ + const std::shared_ptr<const CWallet> rpc_wallet = GetWalletForJSONRPCRequest(request); + if (!rpc_wallet) return UniValue::VNULL; + const CWallet& wallet = *rpc_wallet; + + RPCTypeCheck(request.params, {UniValue::VARR, UniValue::VOBJ}, true); + + LOCK(wallet.cs_wallet); + + UniValue include_watchonly(UniValue::VNULL); + if (request.params[1].isObject()) { + UniValue options = request.params[1]; + RPCTypeCheckObj(options, + { + {"include_watchonly", UniValueType(UniValue::VBOOL)}, + }, + true, true); + + include_watchonly = options["include_watchonly"]; + } + + isminefilter filter = ISMINE_SPENDABLE; + if (ParseIncludeWatchonly(include_watchonly, wallet)) { + filter |= ISMINE_WATCH_ONLY; + } + + const auto& txs = request.params[0].get_array(); + CAmount changes{0}; + std::map<COutPoint, CAmount> new_utxos; // UTXO:s that were made available in transaction array + std::set<COutPoint> spent; + + for (size_t i = 0; i < txs.size(); ++i) { + CMutableTransaction mtx; + if (!DecodeHexTx(mtx, txs[i].get_str(), /* try_no_witness */ true, /* try_witness */ true)) { + throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "Transaction hex string decoding failure."); + } + + // Fetch previous transactions (inputs) + std::map<COutPoint, Coin> coins; + for (const CTxIn& txin : mtx.vin) { + coins[txin.prevout]; // Create empty map entry keyed by prevout. + } + wallet.chain().findCoins(coins); + + // Fetch debit; we are *spending* these; if the transaction is signed and + // broadcast, we will lose everything in these + for (const auto& txin : mtx.vin) { + const auto& outpoint = txin.prevout; + if (spent.count(outpoint)) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Transaction(s) are spending the same output more than once"); + } + if (new_utxos.count(outpoint)) { + changes -= new_utxos.at(outpoint); + new_utxos.erase(outpoint); + } else { + if (coins.at(outpoint).IsSpent()) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "One or more transaction inputs are missing or have been spent already"); + } + changes -= wallet.GetDebit(txin, filter); + } + spent.insert(outpoint); + } + + // Iterate over outputs; we are *receiving* these, if the wallet considers + // them "mine"; if the transaction is signed and broadcast, we will receive + // everything in these + // Also populate new_utxos in case these are spent in later transactions + + const auto& hash = mtx.GetHash(); + for (size_t i = 0; i < mtx.vout.size(); ++i) { + const auto& txout = mtx.vout[i]; + bool is_mine = 0 < (wallet.IsMine(txout) & filter); + changes += new_utxos[COutPoint(hash, i)] = is_mine ? txout.nValue : 0; + } + } + + UniValue result(UniValue::VOBJ); + result.pushKV("balance_change", ValueFromAmount(changes)); + + return result; +} + }; +} + +static RPCHelpMan migratewallet() +{ + return RPCHelpMan{"migratewallet", + "EXPERIMENTAL warning: This call may not work as expected and may be changed in future releases\n" + "\nMigrate the wallet to a descriptor wallet.\n" + "A new wallet backup will need to be made.\n" + "\nThe migration process will create a backup of the wallet before migrating. This backup\n" + "file will be named <wallet name>-<timestamp>.legacy.bak and can be found in the directory\n" + "for this wallet. In the event of an incorrect migration, the backup can be restored using restorewallet." + + HELP_REQUIRING_PASSPHRASE, + {}, + RPCResult{ + RPCResult::Type::OBJ, "", "", + { + {RPCResult::Type::STR, "wallet_name", "The name of the primary migrated wallet"}, + {RPCResult::Type::STR, "watchonly_name", /*optional=*/true, "The name of the migrated wallet containing the watchonly scripts"}, + {RPCResult::Type::STR, "solvables_name", /*optional=*/true, "The name of the migrated wallet containing solvable but not watched scripts"}, + {RPCResult::Type::STR, "backup_path", "The location of the backup of the original wallet"}, + } + }, + RPCExamples{ + HelpExampleCli("migratewallet", "") + + HelpExampleRpc("migratewallet", "") + }, + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue + { + std::shared_ptr<CWallet> wallet = GetWalletForJSONRPCRequest(request); + if (!wallet) return NullUniValue; + + EnsureWalletIsUnlocked(*wallet); + + WalletContext& context = EnsureWalletContext(request.context); + + util::Result<MigrationResult> res = MigrateLegacyToDescriptor(std::move(wallet), context); + if (!res) { + throw JSONRPCError(RPC_WALLET_ERROR, util::ErrorString(res).original); + } + + UniValue r{UniValue::VOBJ}; + r.pushKV("wallet_name", res->wallet_name); + if (res->watchonly_wallet) { + r.pushKV("watchonly_name", res->watchonly_wallet->GetName()); + } + if (res->solvables_wallet) { + r.pushKV("solvables_name", res->solvables_wallet->GetName()); + } + r.pushKV("backup_path", res->backup_path.u8string()); + + return r; + }, + }; +} + // addresses RPCHelpMan getaddressinfo(); RPCHelpMan getnewaddress(); @@ -709,6 +873,7 @@ Span<const CRPCCommand> GetWalletRPCCommands() {"wallet", &listwallets}, {"wallet", &loadwallet}, {"wallet", &lockunspent}, + {"wallet", &migratewallet}, {"wallet", &newkeypool}, {"wallet", &removeprunedfunds}, {"wallet", &rescanblockchain}, @@ -721,6 +886,7 @@ Span<const CRPCCommand> GetWalletRPCCommands() {"wallet", &setwalletflag}, {"wallet", &signmessage}, {"wallet", &signrawtransactionwithwallet}, + {"wallet", &simulaterawtransaction}, {"wallet", &sendall}, {"wallet", &unloadwallet}, {"wallet", &upgradewallet}, diff --git a/src/wallet/scriptpubkeyman.cpp b/src/wallet/scriptpubkeyman.cpp index 8633e7c62c..6da0741d59 100644 --- a/src/wallet/scriptpubkeyman.cpp +++ b/src/wallet/scriptpubkeyman.cpp @@ -21,26 +21,22 @@ namespace wallet { //! 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; -bool LegacyScriptPubKeyMan::GetNewDestination(const OutputType type, CTxDestination& dest, bilingual_str& error) +util::Result<CTxDestination> LegacyScriptPubKeyMan::GetNewDestination(const OutputType type) { if (LEGACY_OUTPUT_TYPES.count(type) == 0) { - error = _("Error: Legacy wallets only support the \"legacy\", \"p2sh-segwit\", and \"bech32\" address types"); - return false; + return util::Error{_("Error: Legacy wallets only support the \"legacy\", \"p2sh-segwit\", and \"bech32\" address types")}; } assert(type != OutputType::BECH32M); LOCK(cs_KeyStore); - error.clear(); // Generate a new key that is added to wallet CPubKey new_key; if (!GetKeyFromPool(new_key, type)) { - error = _("Error: Keypool ran out, please call keypoolrefill first"); - return false; + return util::Error{_("Error: Keypool ran out, please call keypoolrefill first")}; } LearnRelatedScripts(new_key, type); - dest = GetDestinationForKey(new_key, type); - return true; + return GetDestinationForKey(new_key, type); } typedef std::vector<unsigned char> valtype; @@ -296,26 +292,22 @@ bool LegacyScriptPubKeyMan::Encrypt(const CKeyingMaterial& master_key, WalletBat return true; } -bool LegacyScriptPubKeyMan::GetReservedDestination(const OutputType type, bool internal, CTxDestination& address, int64_t& index, CKeyPool& keypool, bilingual_str& error) +util::Result<CTxDestination> LegacyScriptPubKeyMan::GetReservedDestination(const OutputType type, bool internal, int64_t& index, CKeyPool& keypool) { if (LEGACY_OUTPUT_TYPES.count(type) == 0) { - error = _("Error: Legacy wallets only support the \"legacy\", \"p2sh-segwit\", and \"bech32\" address types"); - return false; + return util::Error{_("Error: Legacy wallets only support the \"legacy\", \"p2sh-segwit\", and \"bech32\" address types")}; } assert(type != OutputType::BECH32M); LOCK(cs_KeyStore); if (!CanGetAddresses(internal)) { - error = _("Error: Keypool ran out, please call keypoolrefill first"); - return false; + return util::Error{_("Error: Keypool ran out, please call keypoolrefill first")}; } if (!ReserveKeyFromKeyPool(index, keypool, internal)) { - error = _("Error: Keypool ran out, please call keypoolrefill first"); - return false; + return util::Error{_("Error: Keypool ran out, please call keypoolrefill first")}; } - address = GetDestinationForKey(keypool.vchPubKey, type); - return true; + return GetDestinationForKey(keypool.vchPubKey, type); } bool LegacyScriptPubKeyMan::TopUpInactiveHDChain(const CKeyID seed_id, int64_t index, bool internal) @@ -594,7 +586,7 @@ bool LegacyScriptPubKeyMan::CanProvide(const CScript& script, SignatureData& sig // or solving information, even if not able to sign fully. return true; } else { - // If, given the stuff in sigdata, we could make a valid sigature, then we can provide for this script + // If, given the stuff in sigdata, we could make a valid signature, then we can provide for this script ProduceSignature(*this, DUMMY_SIGNATURE_CREATOR, script, sigdata); if (!sigdata.signatures.empty()) { // If we could make signatures, make sure we have a private key to actually make a signature @@ -1007,9 +999,10 @@ bool LegacyScriptPubKeyMan::GetKeyOrigin(const CKeyID& keyID, KeyOriginInfo& inf { LOCK(cs_KeyStore); auto it = mapKeyMetadata.find(keyID); - if (it != mapKeyMetadata.end()) { - meta = it->second; + if (it == mapKeyMetadata.end()) { + return false; } + meta = it->second; } if (meta.has_key_origin) { std::copy(meta.key_origin.fingerprint, meta.key_origin.fingerprint + 4, info.fingerprint); @@ -1088,6 +1081,13 @@ CPubKey LegacyScriptPubKeyMan::GenerateNewKey(WalletBatch &batch, CHDChain& hd_c return pubkey; } +//! Try to derive an extended key, throw if it fails. +static void DeriveExtKey(CExtKey& key_in, unsigned int index, CExtKey& key_out) { + if (!key_in.Derive(key_out, index)) { + throw std::runtime_error("Could not derive extended key"); + } +} + void LegacyScriptPubKeyMan::DeriveNewChildKey(WalletBatch &batch, CKeyMetadata& metadata, CKey& secret, CHDChain& hd_chain, bool internal) { // for now we use a fixed keypath scheme of m/0'/0'/k @@ -1105,11 +1105,11 @@ void LegacyScriptPubKeyMan::DeriveNewChildKey(WalletBatch &batch, CKeyMetadata& // derive m/0' // use hardened derivation (child keys >= 0x80000000 are hardened after bip32) - masterKey.Derive(accountKey, BIP32_HARDENED_KEY_LIMIT); + DeriveExtKey(masterKey, BIP32_HARDENED_KEY_LIMIT, accountKey); // derive m/0'/0' (external chain) OR m/0'/1' (internal chain) assert(internal ? m_storage.CanSupportFeature(FEATURE_HD_SPLIT) : true); - accountKey.Derive(chainChildKey, BIP32_HARDENED_KEY_LIMIT+(internal ? 1 : 0)); + DeriveExtKey(accountKey, BIP32_HARDENED_KEY_LIMIT+(internal ? 1 : 0), chainChildKey); // derive child key at next index, skip keys already known to the wallet do { @@ -1117,7 +1117,7 @@ void LegacyScriptPubKeyMan::DeriveNewChildKey(WalletBatch &batch, CKeyMetadata& // childIndex | BIP32_HARDENED_KEY_LIMIT = derive childIndex in hardened child-index-range // example: 1 | BIP32_HARDENED_KEY_LIMIT == 0x80000001 == 2147483649 if (internal) { - chainChildKey.Derive(childKey, hd_chain.nInternalChainCounter | BIP32_HARDENED_KEY_LIMIT); + DeriveExtKey(chainChildKey, hd_chain.nInternalChainCounter | BIP32_HARDENED_KEY_LIMIT, childKey); metadata.hdKeypath = "m/0'/1'/" + ToString(hd_chain.nInternalChainCounter) + "'"; metadata.key_origin.path.push_back(0 | BIP32_HARDENED_KEY_LIMIT); metadata.key_origin.path.push_back(1 | BIP32_HARDENED_KEY_LIMIT); @@ -1125,7 +1125,7 @@ void LegacyScriptPubKeyMan::DeriveNewChildKey(WalletBatch &batch, CKeyMetadata& hd_chain.nInternalChainCounter++; } else { - chainChildKey.Derive(childKey, hd_chain.nExternalChainCounter | BIP32_HARDENED_KEY_LIMIT); + DeriveExtKey(chainChildKey, hd_chain.nExternalChainCounter | BIP32_HARDENED_KEY_LIMIT, childKey); metadata.hdKeypath = "m/0'/0'/" + ToString(hd_chain.nExternalChainCounter) + "'"; metadata.key_origin.path.push_back(0 | BIP32_HARDENED_KEY_LIMIT); metadata.key_origin.path.push_back(0 | BIP32_HARDENED_KEY_LIMIT); @@ -1457,7 +1457,8 @@ void LegacyScriptPubKeyMan::LearnRelatedScripts(const CPubKey& key, OutputType t CTxDestination witdest = WitnessV0KeyHash(key.GetID()); CScript witprog = GetScriptForDestination(witdest); // Make sure the resulting program is solvable. - assert(IsSolvable(*this, witprog)); + const auto desc = InferDescriptor(witprog, *this); + assert(desc && desc->IsSolvable()); AddCScript(witprog); } } @@ -1658,12 +1659,323 @@ std::set<CKeyID> LegacyScriptPubKeyMan::GetKeys() const return set_address; } -bool DescriptorScriptPubKeyMan::GetNewDestination(const OutputType type, CTxDestination& dest, bilingual_str& error) +const std::unordered_set<CScript, SaltedSipHasher> LegacyScriptPubKeyMan::GetScriptPubKeys() const +{ + LOCK(cs_KeyStore); + std::unordered_set<CScript, SaltedSipHasher> spks; + + // All keys have at least P2PK and P2PKH + for (const auto& key_pair : mapKeys) { + const CPubKey& pub = key_pair.second.GetPubKey(); + spks.insert(GetScriptForRawPubKey(pub)); + spks.insert(GetScriptForDestination(PKHash(pub))); + } + for (const auto& key_pair : mapCryptedKeys) { + const CPubKey& pub = key_pair.second.first; + spks.insert(GetScriptForRawPubKey(pub)); + spks.insert(GetScriptForDestination(PKHash(pub))); + } + + // For every script in mapScript, only the ISMINE_SPENDABLE ones are being tracked. + // The watchonly ones will be in setWatchOnly which we deal with later + // For all keys, if they have segwit scripts, those scripts will end up in mapScripts + for (const auto& script_pair : mapScripts) { + const CScript& script = script_pair.second; + if (IsMine(script) == ISMINE_SPENDABLE) { + // Add ScriptHash for scripts that are not already P2SH + if (!script.IsPayToScriptHash()) { + spks.insert(GetScriptForDestination(ScriptHash(script))); + } + // For segwit scripts, we only consider them spendable if we have the segwit spk + int wit_ver = -1; + std::vector<unsigned char> witprog; + if (script.IsWitnessProgram(wit_ver, witprog) && wit_ver == 0) { + spks.insert(script); + } + } else { + // Multisigs are special. They don't show up as ISMINE_SPENDABLE unless they are in a P2SH + // So check the P2SH of a multisig to see if we should insert it + std::vector<std::vector<unsigned char>> sols; + TxoutType type = Solver(script, sols); + if (type == TxoutType::MULTISIG) { + CScript ms_spk = GetScriptForDestination(ScriptHash(script)); + if (IsMine(ms_spk) != ISMINE_NO) { + spks.insert(ms_spk); + } + } + } + } + + // All watchonly scripts are raw + spks.insert(setWatchOnly.begin(), setWatchOnly.end()); + + return spks; +} + +std::optional<MigrationData> LegacyScriptPubKeyMan::MigrateToDescriptor() +{ + LOCK(cs_KeyStore); + if (m_storage.IsLocked()) { + return std::nullopt; + } + + MigrationData out; + + std::unordered_set<CScript, SaltedSipHasher> spks{GetScriptPubKeys()}; + + // Get all key ids + std::set<CKeyID> keyids; + for (const auto& key_pair : mapKeys) { + keyids.insert(key_pair.first); + } + for (const auto& key_pair : mapCryptedKeys) { + keyids.insert(key_pair.first); + } + + // Get key metadata and figure out which keys don't have a seed + // Note that we do not ignore the seeds themselves because they are considered IsMine! + for (auto keyid_it = keyids.begin(); keyid_it != keyids.end();) { + const CKeyID& keyid = *keyid_it; + const auto& it = mapKeyMetadata.find(keyid); + if (it != mapKeyMetadata.end()) { + const CKeyMetadata& meta = it->second; + if (meta.hdKeypath == "s" || meta.hdKeypath == "m") { + keyid_it++; + continue; + } + if (m_hd_chain.seed_id == meta.hd_seed_id || m_inactive_hd_chains.count(meta.hd_seed_id) > 0) { + keyid_it = keyids.erase(keyid_it); + continue; + } + } + keyid_it++; + } + + // keyids is now all non-HD keys. Each key will have its own combo descriptor + for (const CKeyID& keyid : keyids) { + CKey key; + if (!GetKey(keyid, key)) { + assert(false); + } + + // Get birthdate from key meta + uint64_t creation_time = 0; + const auto& it = mapKeyMetadata.find(keyid); + if (it != mapKeyMetadata.end()) { + creation_time = it->second.nCreateTime; + } + + // Get the key origin + // Maybe this doesn't matter because floating keys here shouldn't have origins + KeyOriginInfo info; + bool has_info = GetKeyOrigin(keyid, info); + std::string origin_str = has_info ? "[" + HexStr(info.fingerprint) + FormatHDKeypath(info.path) + "]" : ""; + + // Construct the combo descriptor + std::string desc_str = "combo(" + origin_str + HexStr(key.GetPubKey()) + ")"; + FlatSigningProvider keys; + std::string error; + std::unique_ptr<Descriptor> desc = Parse(desc_str, keys, error, false); + WalletDescriptor w_desc(std::move(desc), creation_time, 0, 0, 0); + + // Make the DescriptorScriptPubKeyMan and get the scriptPubKeys + auto desc_spk_man = std::unique_ptr<DescriptorScriptPubKeyMan>(new DescriptorScriptPubKeyMan(m_storage, w_desc)); + desc_spk_man->AddDescriptorKey(key, key.GetPubKey()); + desc_spk_man->TopUp(); + auto desc_spks = desc_spk_man->GetScriptPubKeys(); + + // Remove the scriptPubKeys from our current set + for (const CScript& spk : desc_spks) { + size_t erased = spks.erase(spk); + assert(erased == 1); + assert(IsMine(spk) == ISMINE_SPENDABLE); + } + + out.desc_spkms.push_back(std::move(desc_spk_man)); + } + + // Handle HD keys by using the CHDChains + std::vector<CHDChain> chains; + chains.push_back(m_hd_chain); + for (const auto& chain_pair : m_inactive_hd_chains) { + chains.push_back(chain_pair.second); + } + for (const CHDChain& chain : chains) { + for (int i = 0; i < 2; ++i) { + // Skip if doing internal chain and split chain is not supported + if (chain.seed_id.IsNull() || (i == 1 && !m_storage.CanSupportFeature(FEATURE_HD_SPLIT))) { + continue; + } + // Get the master xprv + CKey seed_key; + if (!GetKey(chain.seed_id, seed_key)) { + assert(false); + } + CExtKey master_key; + master_key.SetSeed(seed_key); + + // Make the combo descriptor + std::string xpub = EncodeExtPubKey(master_key.Neuter()); + std::string desc_str = "combo(" + xpub + "/0'/" + ToString(i) + "'/*')"; + FlatSigningProvider keys; + std::string error; + std::unique_ptr<Descriptor> desc = Parse(desc_str, keys, error, false); + uint32_t chain_counter = std::max((i == 1 ? chain.nInternalChainCounter : chain.nExternalChainCounter), (uint32_t)0); + WalletDescriptor w_desc(std::move(desc), 0, 0, chain_counter, 0); + + // Make the DescriptorScriptPubKeyMan and get the scriptPubKeys + auto desc_spk_man = std::unique_ptr<DescriptorScriptPubKeyMan>(new DescriptorScriptPubKeyMan(m_storage, w_desc)); + desc_spk_man->AddDescriptorKey(master_key.key, master_key.key.GetPubKey()); + desc_spk_man->TopUp(); + auto desc_spks = desc_spk_man->GetScriptPubKeys(); + + // Remove the scriptPubKeys from our current set + for (const CScript& spk : desc_spks) { + size_t erased = spks.erase(spk); + assert(erased == 1); + assert(IsMine(spk) == ISMINE_SPENDABLE); + } + + out.desc_spkms.push_back(std::move(desc_spk_man)); + } + } + // Add the current master seed to the migration data + if (!m_hd_chain.seed_id.IsNull()) { + CKey seed_key; + if (!GetKey(m_hd_chain.seed_id, seed_key)) { + assert(false); + } + out.master_key.SetSeed(seed_key); + } + + // Handle the rest of the scriptPubKeys which must be imports and may not have all info + for (auto it = spks.begin(); it != spks.end();) { + const CScript& spk = *it; + + // Get birthdate from script meta + uint64_t creation_time = 0; + const auto& mit = m_script_metadata.find(CScriptID(spk)); + if (mit != m_script_metadata.end()) { + creation_time = mit->second.nCreateTime; + } + + // InferDescriptor as that will get us all the solving info if it is there + std::unique_ptr<Descriptor> desc = InferDescriptor(spk, *GetSolvingProvider(spk)); + // Get the private keys for this descriptor + std::vector<CScript> scripts; + FlatSigningProvider keys; + if (!desc->Expand(0, DUMMY_SIGNING_PROVIDER, scripts, keys)) { + assert(false); + } + std::set<CKeyID> privkeyids; + for (const auto& key_orig_pair : keys.origins) { + privkeyids.insert(key_orig_pair.first); + } + + std::vector<CScript> desc_spks; + + // Make the descriptor string with private keys + std::string desc_str; + bool watchonly = !desc->ToPrivateString(*this, desc_str); + if (watchonly && !m_storage.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { + out.watch_descs.push_back({desc->ToString(), creation_time}); + + // Get the scriptPubKeys without writing this to the wallet + FlatSigningProvider provider; + desc->Expand(0, provider, desc_spks, provider); + } else { + // Make the DescriptorScriptPubKeyMan and get the scriptPubKeys + WalletDescriptor w_desc(std::move(desc), creation_time, 0, 0, 0); + auto desc_spk_man = std::unique_ptr<DescriptorScriptPubKeyMan>(new DescriptorScriptPubKeyMan(m_storage, w_desc)); + for (const auto& keyid : privkeyids) { + CKey key; + if (!GetKey(keyid, key)) { + continue; + } + desc_spk_man->AddDescriptorKey(key, key.GetPubKey()); + } + desc_spk_man->TopUp(); + auto desc_spks_set = desc_spk_man->GetScriptPubKeys(); + desc_spks.insert(desc_spks.end(), desc_spks_set.begin(), desc_spks_set.end()); + + out.desc_spkms.push_back(std::move(desc_spk_man)); + } + + // Remove the scriptPubKeys from our current set + for (const CScript& desc_spk : desc_spks) { + auto del_it = spks.find(desc_spk); + assert(del_it != spks.end()); + assert(IsMine(desc_spk) != ISMINE_NO); + it = spks.erase(del_it); + } + } + + // Multisigs are special. They don't show up as ISMINE_SPENDABLE unless they are in a P2SH + // So we have to check if any of our scripts are a multisig and if so, add the P2SH + for (const auto& script_pair : mapScripts) { + const CScript script = script_pair.second; + + // Get birthdate from script meta + uint64_t creation_time = 0; + const auto& it = m_script_metadata.find(CScriptID(script)); + if (it != m_script_metadata.end()) { + creation_time = it->second.nCreateTime; + } + + std::vector<std::vector<unsigned char>> sols; + TxoutType type = Solver(script, sols); + if (type == TxoutType::MULTISIG) { + CScript sh_spk = GetScriptForDestination(ScriptHash(script)); + CTxDestination witdest = WitnessV0ScriptHash(script); + CScript witprog = GetScriptForDestination(witdest); + CScript sh_wsh_spk = GetScriptForDestination(ScriptHash(witprog)); + + // We only want the multisigs that we have not already seen, i.e. they are not watchonly and not spendable + // For P2SH, a multisig is not ISMINE_NO when: + // * All keys are in the wallet + // * The multisig itself is watch only + // * The P2SH is watch only + // For P2SH-P2WSH, if the script is in the wallet, then it will have the same conditions as P2SH. + // For P2WSH, a multisig is not ISMINE_NO when, other than the P2SH conditions: + // * The P2WSH script is in the wallet and it is being watched + std::vector<std::vector<unsigned char>> keys(sols.begin() + 1, sols.begin() + sols.size() - 1); + if (HaveWatchOnly(sh_spk) || HaveWatchOnly(script) || HaveKeys(keys, *this) || (HaveCScript(CScriptID(witprog)) && HaveWatchOnly(witprog))) { + // The above emulates IsMine for these 3 scriptPubKeys, so double check that by running IsMine + assert(IsMine(sh_spk) != ISMINE_NO || IsMine(witprog) != ISMINE_NO || IsMine(sh_wsh_spk) != ISMINE_NO); + continue; + } + assert(IsMine(sh_spk) == ISMINE_NO && IsMine(witprog) == ISMINE_NO && IsMine(sh_wsh_spk) == ISMINE_NO); + + std::unique_ptr<Descriptor> sh_desc = InferDescriptor(sh_spk, *GetSolvingProvider(sh_spk)); + out.solvable_descs.push_back({sh_desc->ToString(), creation_time}); + + const auto desc = InferDescriptor(witprog, *this); + if (desc->IsSolvable()) { + std::unique_ptr<Descriptor> wsh_desc = InferDescriptor(witprog, *GetSolvingProvider(witprog)); + out.solvable_descs.push_back({wsh_desc->ToString(), creation_time}); + std::unique_ptr<Descriptor> sh_wsh_desc = InferDescriptor(sh_wsh_spk, *GetSolvingProvider(sh_wsh_spk)); + out.solvable_descs.push_back({sh_wsh_desc->ToString(), creation_time}); + } + } + } + + // Make sure that we have accounted for all scriptPubKeys + assert(spks.size() == 0); + return out; +} + +bool LegacyScriptPubKeyMan::DeleteRecords() +{ + LOCK(cs_KeyStore); + WalletBatch batch(m_storage.GetDatabase()); + return batch.EraseRecords(DBKeys::LEGACY_TYPES); +} + +util::Result<CTxDestination> DescriptorScriptPubKeyMan::GetNewDestination(const OutputType type) { // Returns true if this descriptor supports getting new addresses. Conditions where we may be unable to fetch them (e.g. locked) are caught later if (!CanGetAddresses()) { - error = _("No addresses available"); - return false; + return util::Error{_("No addresses available")}; } { LOCK(cs_desc_man); @@ -1681,15 +1993,14 @@ bool DescriptorScriptPubKeyMan::GetNewDestination(const OutputType type, CTxDest std::vector<CScript> scripts_temp; if (m_wallet_descriptor.range_end <= m_max_cached_index && !TopUp(1)) { // We can't generate anymore keys - error = _("Error: Keypool ran out, please call keypoolrefill first"); - return false; + return util::Error{_("Error: Keypool ran out, please call keypoolrefill first")}; } if (!m_wallet_descriptor.descriptor->ExpandFromCache(m_wallet_descriptor.next_index, m_wallet_descriptor.cache, scripts_temp, out_keys)) { // We can't generate anymore keys - error = _("Error: Keypool ran out, please call keypoolrefill first"); - return false; + return util::Error{_("Error: Keypool ran out, please call keypoolrefill first")}; } + CTxDestination dest; std::optional<OutputType> out_script_type = m_wallet_descriptor.descriptor->GetOutputType(); if (out_script_type && out_script_type == type) { ExtractDestination(scripts_temp[0], dest); @@ -1698,7 +2009,7 @@ bool DescriptorScriptPubKeyMan::GetNewDestination(const OutputType type, CTxDest } m_wallet_descriptor.next_index++; WalletBatch(m_storage.GetDatabase()).WriteDescriptor(GetID(), m_wallet_descriptor); - return true; + return dest; } } @@ -1766,12 +2077,12 @@ bool DescriptorScriptPubKeyMan::Encrypt(const CKeyingMaterial& master_key, Walle return true; } -bool DescriptorScriptPubKeyMan::GetReservedDestination(const OutputType type, bool internal, CTxDestination& address, int64_t& index, CKeyPool& keypool, bilingual_str& error) +util::Result<CTxDestination> DescriptorScriptPubKeyMan::GetReservedDestination(const OutputType type, bool internal, int64_t& index, CKeyPool& keypool) { LOCK(cs_desc_man); - bool result = GetNewDestination(type, address, error); + auto op_dest = GetNewDestination(type); index = m_wallet_descriptor.next_index - 1; - return result; + return op_dest; } void DescriptorScriptPubKeyMan::ReturnDestination(int64_t index, bool internal, const CTxDestination& addr) @@ -1790,7 +2101,7 @@ std::map<CKeyID, CKey> DescriptorScriptPubKeyMan::GetKeys() const AssertLockHeld(cs_desc_man); if (m_storage.HasEncryptionKeys() && !m_storage.IsLocked()) { KeyMap keys; - for (auto key_pair : m_map_crypted_keys) { + for (const auto& key_pair : m_map_crypted_keys) { const CPubKey& pubkey = key_pair.second.first; const std::vector<unsigned char>& crypted_secret = key_pair.second.second; CKey key; @@ -1967,6 +2278,11 @@ bool DescriptorScriptPubKeyMan::SetupDescriptorGeneration(const CExtKey& master_ desc_prefix = "tr(" + xpub + "/86'"; break; } + case OutputType::UNKNOWN: { + // We should never have a DescriptorScriptPubKeyMan for an UNKNOWN OutputType, + // so if we get to this point something is wrong + assert(false); + } } // no default case, so the compiler can warn about missing cases assert(!desc_prefix.empty()); @@ -2076,10 +2392,21 @@ std::unique_ptr<FlatSigningProvider> DescriptorScriptPubKeyMan::GetSigningProvid std::unique_ptr<FlatSigningProvider> DescriptorScriptPubKeyMan::GetSigningProvider(int32_t index, bool include_private) const { AssertLockHeld(cs_desc_man); - // Get the scripts, keys, and key origins for this script + 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; + + // Fetch SigningProvider from cache to avoid re-deriving + auto it = m_map_signing_providers.find(index); + if (it != m_map_signing_providers.end()) { + out_keys->Merge(FlatSigningProvider{it->second}); + } else { + // Get the scripts, keys, and key origins for this script + std::vector<CScript> scripts_temp; + if (!m_wallet_descriptor.descriptor->ExpandFromCache(index, m_wallet_descriptor.cache, scripts_temp, *out_keys)) return nullptr; + + // Cache SigningProvider so we don't need to re-derive if we need this SigningProvider again + m_map_signing_providers[index] = *out_keys; + } if (HavePrivateKeys() && include_private) { FlatSigningProvider master_provider; @@ -2108,7 +2435,7 @@ bool DescriptorScriptPubKeyMan::SignTransaction(CMutableTransaction& tx, const s if (!coin_keys) { continue; } - *keys = Merge(*keys, *coin_keys); + keys->Merge(std::move(*coin_keys)); } return ::SignTransaction(tx, keys.get(), coins, sighash, input_errors); @@ -2169,7 +2496,7 @@ TransactionError DescriptorScriptPubKeyMan::FillPSBT(PartiallySignedTransaction& 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); + keys->Merge(std::move(*script_keys)); } else { // Maybe there are pubkeys listed that we can sign for script_keys = std::make_unique<FlatSigningProvider>(); @@ -2177,7 +2504,20 @@ TransactionError DescriptorScriptPubKeyMan::FillPSBT(PartiallySignedTransaction& const CPubKey& pubkey = pk_pair.first; std::unique_ptr<FlatSigningProvider> pk_keys = GetSigningProvider(pubkey); if (pk_keys) { - *keys = Merge(*keys, *pk_keys); + keys->Merge(std::move(*pk_keys)); + } + } + for (const auto& pk_pair : input.m_tap_bip32_paths) { + const XOnlyPubKey& pubkey = pk_pair.first; + for (unsigned char prefix : {0x02, 0x03}) { + unsigned char b[33] = {prefix}; + std::copy(pubkey.begin(), pubkey.end(), b + 1); + CPubKey fullpubkey; + fullpubkey.Set(b, b + 33); + std::unique_ptr<FlatSigningProvider> pk_keys = GetSigningProvider(fullpubkey); + if (pk_keys) { + keys->Merge(std::move(*pk_keys)); + } } } } @@ -2300,14 +2640,14 @@ const WalletDescriptor DescriptorScriptPubKeyMan::GetWalletDescriptor() const return m_wallet_descriptor; } -const std::vector<CScript> DescriptorScriptPubKeyMan::GetScriptPubKeys() const +const std::unordered_set<CScript, SaltedSipHasher> DescriptorScriptPubKeyMan::GetScriptPubKeys() const { LOCK(cs_desc_man); - std::vector<CScript> script_pub_keys; + std::unordered_set<CScript, SaltedSipHasher> script_pub_keys; script_pub_keys.reserve(m_map_script_pub_keys.size()); for (auto const& script_pub_key: m_map_script_pub_keys) { - script_pub_keys.push_back(script_pub_key.first); + script_pub_keys.insert(script_pub_key.first); } return script_pub_keys; } diff --git a/src/wallet/scriptpubkeyman.h b/src/wallet/scriptpubkeyman.h index ad924791af..3ab489c374 100644 --- a/src/wallet/scriptpubkeyman.h +++ b/src/wallet/scriptpubkeyman.h @@ -11,6 +11,7 @@ #include <script/standard.h> #include <util/error.h> #include <util/message.h> +#include <util/result.h> #include <util/time.h> #include <wallet/crypter.h> #include <wallet/ismine.h> @@ -171,14 +172,14 @@ protected: public: explicit ScriptPubKeyMan(WalletStorage& storage) : m_storage(storage) {} virtual ~ScriptPubKeyMan() {}; - virtual bool GetNewDestination(const OutputType type, CTxDestination& dest, bilingual_str& error) { return false; } + virtual util::Result<CTxDestination> GetNewDestination(const OutputType type) { return util::Error{Untranslated("Not supported")}; } virtual isminetype IsMine(const CScript& script) const { return ISMINE_NO; } //! Check that the given decryption key is valid for this ScriptPubKeyMan, i.e. it decrypts all of the keys handled by it. virtual bool CheckDecryptionKey(const CKeyingMaterial& master_key, bool accept_no_keys = false) { return false; } virtual bool Encrypt(const CKeyingMaterial& master_key, WalletBatch* batch) { return false; } - virtual bool GetReservedDestination(const OutputType type, bool internal, CTxDestination& address, int64_t& index, CKeyPool& keypool, bilingual_str& error) { return false; } + virtual util::Result<CTxDestination> GetReservedDestination(const OutputType type, bool internal, int64_t& index, CKeyPool& keypool) { return util::Error{Untranslated("Not supported")}; } virtual void KeepDestination(int64_t index, const OutputType& type) {} virtual void ReturnDestination(int64_t index, bool internal, const CTxDestination& addr) {} @@ -241,6 +242,9 @@ public: virtual uint256 GetID() const { return uint256(); } + /** Returns a set of all the scriptPubKeys that this ScriptPubKeyMan watches */ + virtual const std::unordered_set<CScript, SaltedSipHasher> GetScriptPubKeys() const { return {}; }; + /** Prepends the wallet name in logging output to ease debugging in multi-wallet use cases */ template<typename... Params> void WalletLogPrintf(std::string fmt, Params... parameters) const { @@ -261,6 +265,8 @@ static const std::unordered_set<OutputType> LEGACY_OUTPUT_TYPES { OutputType::BECH32, }; +class DescriptorScriptPubKeyMan; + class LegacyScriptPubKeyMan : public ScriptPubKeyMan, public FillableSigningProvider { private: @@ -359,13 +365,13 @@ private: public: using ScriptPubKeyMan::ScriptPubKeyMan; - bool GetNewDestination(const OutputType type, CTxDestination& dest, bilingual_str& error) override; + util::Result<CTxDestination> GetNewDestination(const OutputType type) override; isminetype IsMine(const CScript& script) const override; bool CheckDecryptionKey(const CKeyingMaterial& master_key, bool accept_no_keys = false) override; bool Encrypt(const CKeyingMaterial& master_key, WalletBatch* batch) override; - bool GetReservedDestination(const OutputType type, bool internal, CTxDestination& address, int64_t& index, CKeyPool& keypool, bilingual_str& error) override; + util::Result<CTxDestination> GetReservedDestination(const OutputType type, bool internal, int64_t& index, CKeyPool& keypool) override; void KeepDestination(int64_t index, const OutputType& type) override; void ReturnDestination(int64_t index, bool internal, const CTxDestination&) override; @@ -506,6 +512,13 @@ public: const std::map<CKeyID, int64_t>& GetAllReserveKeys() const { return m_pool_key_to_index; } std::set<CKeyID> GetKeys() const override; + const std::unordered_set<CScript, SaltedSipHasher> GetScriptPubKeys() const override; + + /** Get the DescriptorScriptPubKeyMans (with private keys) that have the same scriptPubKeys as this LegacyScriptPubKeyMan. + * Does not modify this ScriptPubKeyMan. */ + std::optional<MigrationData> MigrateToDescriptor(); + /** Delete all the records ofthis LegacyScriptPubKeyMan from disk*/ + bool DeleteRecords(); }; /** Wraps a LegacyScriptPubKeyMan so that it can be returned in a new unique_ptr. Does not provide privkeys */ @@ -546,6 +559,8 @@ private: KeyMap GetKeys() const EXCLUSIVE_LOCKS_REQUIRED(cs_desc_man); + // Cached FlatSigningProviders to avoid regenerating them each time they are needed. + mutable std::map<int32_t, FlatSigningProvider> m_map_signing_providers; // Fetch the SigningProvider for the given script and optionally include private keys std::unique_ptr<FlatSigningProvider> GetSigningProvider(const CScript& script, bool include_private = false) const; // Fetch the SigningProvider for the given pubkey and always include private keys. This should only be called by signing code. @@ -567,13 +582,13 @@ public: mutable RecursiveMutex cs_desc_man; - bool GetNewDestination(const OutputType type, CTxDestination& dest, bilingual_str& error) override; + util::Result<CTxDestination> GetNewDestination(const OutputType type) override; isminetype IsMine(const CScript& script) const override; bool CheckDecryptionKey(const CKeyingMaterial& master_key, bool accept_no_keys = false) override; bool Encrypt(const CKeyingMaterial& master_key, WalletBatch* batch) override; - bool GetReservedDestination(const OutputType type, bool internal, CTxDestination& address, int64_t& index, CKeyPool& keypool, bilingual_str& error) override; + util::Result<CTxDestination> GetReservedDestination(const OutputType type, bool internal, int64_t& index, CKeyPool& keypool) override; void ReturnDestination(int64_t index, bool internal, const CTxDestination& addr) override; // Tops up the descriptor cache and m_map_script_pub_keys. The cache is stored in the wallet file @@ -627,7 +642,7 @@ public: void WriteDescriptor(); const WalletDescriptor GetWalletDescriptor() const EXCLUSIVE_LOCKS_REQUIRED(cs_desc_man); - const std::vector<CScript> GetScriptPubKeys() const; + const std::unordered_set<CScript, SaltedSipHasher> GetScriptPubKeys() const override; bool GetDescriptorString(std::string& out, const bool priv) const; diff --git a/src/wallet/spend.cpp b/src/wallet/spend.cpp index 6f79ae4e9b..6833f9a095 100644 --- a/src/wallet/spend.cpp +++ b/src/wallet/spend.cpp @@ -27,25 +27,20 @@ using interfaces::FoundBlock; namespace wallet { static constexpr size_t OUTPUT_GROUP_MAX_ENTRIES{100}; -int GetTxSpendSize(const CWallet& wallet, const CWalletTx& wtx, unsigned int out, bool use_max_sig) -{ - return CalculateMaximumSignedInputSize(wtx.tx->vout[out], &wallet, use_max_sig); -} - -int CalculateMaximumSignedInputSize(const CTxOut& txout, const SigningProvider* provider, bool use_max_sig) +int CalculateMaximumSignedInputSize(const CTxOut& txout, const COutPoint outpoint, const SigningProvider* provider, const CCoinControl* coin_control) { CMutableTransaction txn; - txn.vin.push_back(CTxIn(COutPoint())); - if (!provider || !DummySignInput(*provider, txn.vin[0], txout, use_max_sig)) { + txn.vin.push_back(CTxIn(outpoint)); + if (!provider || !DummySignInput(*provider, txn.vin[0], txout, coin_control)) { return -1; } return GetVirtualTransactionInputSize(txn.vin[0]); } -int CalculateMaximumSignedInputSize(const CTxOut& txout, const CWallet* wallet, bool use_max_sig) +int CalculateMaximumSignedInputSize(const CTxOut& txout, const CWallet* wallet, const CCoinControl* coin_control) { const std::unique_ptr<SigningProvider> provider = wallet->GetSolvingProvider(txout.scriptPubKey); - return CalculateMaximumSignedInputSize(txout, provider.get(), use_max_sig); + return CalculateMaximumSignedInputSize(txout, COutPoint(), provider.get(), coin_control); } // txouts needs to be in the order of tx.vin @@ -84,12 +79,82 @@ TxSize CalculateMaximumSignedTxSize(const CTransaction &tx, const CWallet *walle return CalculateMaximumSignedTxSize(tx, wallet, txouts, coin_control); } -void AvailableCoins(const CWallet& wallet, std::vector<COutput>& vCoins, const CCoinControl* coinControl, std::optional<CFeeRate> feerate, const CAmount& nMinimumAmount, const CAmount& nMaximumAmount, const CAmount& nMinimumSumAmount, const uint64_t nMaximumCount) +size_t CoinsResult::Size() const +{ + size_t size{0}; + for (const auto& it : coins) { + size += it.second.size(); + } + return size; +} + +std::vector<COutput> CoinsResult::All() const +{ + std::vector<COutput> all; + all.reserve(coins.size()); + for (const auto& it : coins) { + all.insert(all.end(), it.second.begin(), it.second.end()); + } + return all; +} + +void CoinsResult::Clear() { + coins.clear(); +} + +void CoinsResult::Erase(std::set<COutPoint>& preset_coins) +{ + for (auto& it : coins) { + auto& vec = it.second; + auto i = std::find_if(vec.begin(), vec.end(), [&](const COutput &c) { return preset_coins.count(c.outpoint);}); + if (i != vec.end()) { + vec.erase(i); + break; + } + } +} + +void CoinsResult::Shuffle(FastRandomContext& rng_fast) +{ + for (auto& it : coins) { + ::Shuffle(it.second.begin(), it.second.end(), rng_fast); + } +} + +void CoinsResult::Add(OutputType type, const COutput& out) +{ + coins[type].emplace_back(out); +} + +static OutputType GetOutputType(TxoutType type, bool is_from_p2sh) +{ + switch (type) { + case TxoutType::WITNESS_V1_TAPROOT: + return OutputType::BECH32M; + case TxoutType::WITNESS_V0_KEYHASH: + case TxoutType::WITNESS_V0_SCRIPTHASH: + if (is_from_p2sh) return OutputType::P2SH_SEGWIT; + else return OutputType::BECH32; + case TxoutType::SCRIPTHASH: + case TxoutType::PUBKEYHASH: + return OutputType::LEGACY; + default: + return OutputType::UNKNOWN; + } +} + +CoinsResult AvailableCoins(const CWallet& wallet, + const CCoinControl* coinControl, + std::optional<CFeeRate> feerate, + const CAmount& nMinimumAmount, + const CAmount& nMaximumAmount, + const CAmount& nMinimumSumAmount, + const uint64_t nMaximumCount, + bool only_spendable) { AssertLockHeld(wallet.cs_wallet); - vCoins.clear(); - CAmount nTotal = 0; + CoinsResult result; // Either the WALLET_FLAG_AVOID_REUSE flag is not set (in which case we always allow), or we default to avoiding, and only in the case where // a coin control object is provided, and has the avoid address reuse flag set to false, do we allow already used addresses bool allow_used_addresses = !wallet.IsWalletFlagSet(WALLET_FLAG_AVOID_REUSE) || (coinControl && !coinControl->m_avoid_address_reuse); @@ -159,76 +224,95 @@ void AvailableCoins(const CWallet& wallet, std::vector<COutput>& vCoins, const C bool tx_from_me = CachedTxIsFromMe(wallet, wtx, ISMINE_ALL); for (unsigned int i = 0; i < wtx.tx->vout.size(); i++) { - // Only consider selected coins if add_inputs is false - if (coinControl && !coinControl->m_add_inputs && !coinControl->IsSelected(COutPoint(entry.first, i))) { - continue; - } + const CTxOut& output = wtx.tx->vout[i]; + const COutPoint outpoint(wtxid, i); - if (wtx.tx->vout[i].nValue < nMinimumAmount || wtx.tx->vout[i].nValue > nMaximumAmount) + if (output.nValue < nMinimumAmount || output.nValue > nMaximumAmount) continue; - if (coinControl && coinControl->HasSelected() && !coinControl->fAllowOtherInputs && !coinControl->IsSelected(COutPoint(entry.first, i))) + if (coinControl && coinControl->HasSelected() && !coinControl->m_allow_other_inputs && !coinControl->IsSelected(outpoint)) continue; - if (wallet.IsLockedCoin(entry.first, i)) + if (wallet.IsLockedCoin(outpoint)) continue; - if (wallet.IsSpent(wtxid, i)) + if (wallet.IsSpent(outpoint)) continue; - isminetype mine = wallet.IsMine(wtx.tx->vout[i]); + isminetype mine = wallet.IsMine(output); if (mine == ISMINE_NO) { continue; } - if (!allow_used_addresses && wallet.IsSpentKey(wtxid, i)) { + if (!allow_used_addresses && wallet.IsSpentKey(output.scriptPubKey)) { continue; } - std::unique_ptr<SigningProvider> provider = wallet.GetSolvingProvider(wtx.tx->vout[i].scriptPubKey); + std::unique_ptr<SigningProvider> provider = wallet.GetSolvingProvider(output.scriptPubKey); - bool solvable = provider ? IsSolvable(*provider, wtx.tx->vout[i].scriptPubKey) : false; + int input_bytes = CalculateMaximumSignedInputSize(output, COutPoint(), provider.get(), coinControl); + // Because CalculateMaximumSignedInputSize just uses ProduceSignature and makes a dummy signature, + // it is safe to assume that this input is solvable if input_bytes is greater -1. + bool solvable = input_bytes > -1; bool spendable = ((mine & ISMINE_SPENDABLE) != ISMINE_NO) || (((mine & ISMINE_WATCH_ONLY) != ISMINE_NO) && (coinControl && coinControl->fAllowWatchOnly && solvable)); - int input_bytes = GetTxSpendSize(wallet, wtx, i, (coinControl && coinControl->fAllowWatchOnly)); - vCoins.emplace_back(COutPoint(wtx.GetHash(), i), wtx.tx->vout.at(i), nDepth, input_bytes, spendable, solvable, safeTx, wtx.GetTxTime(), tx_from_me, feerate); + // Filter by spendable outputs only + if (!spendable && only_spendable) continue; + + // Obtain script type + std::vector<std::vector<uint8_t>> script_solutions; + TxoutType type = Solver(output.scriptPubKey, script_solutions); + + // If the output is P2SH and solvable, we want to know if it is + // a P2SH (legacy) or one of P2SH-P2WPKH, P2SH-P2WSH (P2SH-Segwit). We can determine + // this from the redeemScript. If the output is not solvable, it will be classified + // as a P2SH (legacy), since we have no way of knowing otherwise without the redeemScript + bool is_from_p2sh{false}; + if (type == TxoutType::SCRIPTHASH && solvable) { + CScript script; + if (!provider->GetCScript(CScriptID(uint160(script_solutions[0])), script)) continue; + type = Solver(script, script_solutions); + is_from_p2sh = true; + } + + result.Add(GetOutputType(type, is_from_p2sh), + COutput(outpoint, output, nDepth, input_bytes, spendable, solvable, safeTx, wtx.GetTxTime(), tx_from_me, feerate)); + // Cache total amount as we go + result.total_amount += output.nValue; // Checks the sum amount of all UTXO's. if (nMinimumSumAmount != MAX_MONEY) { - nTotal += wtx.tx->vout[i].nValue; - - if (nTotal >= nMinimumSumAmount) { - return; + if (result.total_amount >= nMinimumSumAmount) { + return result; } } // Checks the maximum number of UTXO's. - if (nMaximumCount > 0 && vCoins.size() >= nMaximumCount) { - return; + if (nMaximumCount > 0 && result.Size() >= nMaximumCount) { + return result; } } } + + return result; } -void AvailableCoinsListUnspent(const CWallet& wallet, std::vector<COutput>& vCoins, const CCoinControl* coinControl, const CAmount& nMinimumAmount, const CAmount& nMaximumAmount, const CAmount& nMinimumSumAmount, const uint64_t nMaximumCount) +CoinsResult AvailableCoinsListUnspent(const CWallet& wallet, const CCoinControl* coinControl, const CAmount& nMinimumAmount, const CAmount& nMaximumAmount, const CAmount& nMinimumSumAmount, const uint64_t nMaximumCount) { - AvailableCoins(wallet, vCoins, coinControl, /*feerate=*/ std::nullopt, nMinimumAmount, nMaximumAmount, nMinimumSumAmount, nMaximumCount); + return AvailableCoins(wallet, coinControl, /*feerate=*/ std::nullopt, nMinimumAmount, nMaximumAmount, nMinimumSumAmount, nMaximumCount, /*only_spendable=*/false); } CAmount GetAvailableBalance(const CWallet& wallet, const CCoinControl* coinControl) { LOCK(wallet.cs_wallet); - - CAmount balance = 0; - std::vector<COutput> vCoins; - AvailableCoinsListUnspent(wallet, vCoins, coinControl); - for (const COutput& out : vCoins) { - if (out.spendable) { - balance += out.txout.nValue; - } - } - return balance; + return AvailableCoins(wallet, coinControl, + /*feerate=*/ std::nullopt, + /*nMinimumAmount=*/ 1, + /*nMaximumAmount=*/ MAX_MONEY, + /*nMinimumSumAmount=*/ MAX_MONEY, + /*nMaximumCount=*/ 0 + ).total_amount; } const CTxOut& FindNonChangeParentOutput(const CWallet& wallet, const CTransaction& tx, int output) @@ -260,11 +344,8 @@ std::map<CTxDestination, std::vector<COutput>> ListCoins(const CWallet& wallet) AssertLockHeld(wallet.cs_wallet); std::map<CTxDestination, std::vector<COutput>> result; - std::vector<COutput> availableCoins; - - AvailableCoinsListUnspent(wallet, availableCoins); - for (const COutput& coin : availableCoins) { + for (COutput& coin : AvailableCoinsListUnspent(wallet).All()) { CTxDestination address; if ((coin.spendable || (wallet.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) && coin.solvable)) && ExtractDestination(FindNonChangeParentOutput(wallet, coin.outpoint).scriptPubKey, address)) { @@ -287,8 +368,9 @@ std::map<CTxDestination, std::vector<COutput>> ListCoins(const CWallet& wallet) ) { CTxDestination address; if (ExtractDestination(FindNonChangeParentOutput(wallet, *wtx.tx, output.n).scriptPubKey, address)) { + const auto out = wtx.tx->vout.at(output.n); result[address].emplace_back( - COutPoint(wtx.GetHash(), output.n), wtx.tx->vout.at(output.n), depth, GetTxSpendSize(wallet, wtx, output.n), /*spendable=*/ true, /*solvable=*/ true, /*safe=*/ false, wtx.GetTxTime(), CachedTxIsFromMe(wallet, wtx, ISMINE_ALL)); + COutPoint(wtx.GetHash(), output.n), out, depth, CalculateMaximumSignedInputSize(out, &wallet, /*coin_control=*/nullptr), /*spendable=*/ true, /*solvable=*/ true, /*safe=*/ false, wtx.GetTxTime(), CachedTxIsFromMe(wallet, wtx, ISMINE_ALL)); } } } @@ -381,35 +463,51 @@ std::vector<OutputGroup> GroupOutputs(const CWallet& wallet, const std::vector<C return groups_out; } -std::optional<SelectionResult> AttemptSelection(const CWallet& wallet, const CAmount& nTargetValue, const CoinEligibilityFilter& eligibility_filter, std::vector<COutput> coins, - const CoinSelectionParams& coin_selection_params) +std::optional<SelectionResult> AttemptSelection(const CWallet& wallet, const CAmount& nTargetValue, const CoinEligibilityFilter& eligibility_filter, const CoinsResult& available_coins, + const CoinSelectionParams& coin_selection_params, bool allow_mixed_output_types) +{ + // Run coin selection on each OutputType and compute the Waste Metric + std::vector<SelectionResult> results; + for (const auto& it : available_coins.coins) { + if (auto result{ChooseSelectionResult(wallet, nTargetValue, eligibility_filter, it.second, coin_selection_params)}) { + results.push_back(*result); + } + } + // If we have at least one solution for funding the transaction without mixing, choose the minimum one according to waste metric + // and return the result + if (results.size() > 0) return *std::min_element(results.begin(), results.end()); + + // If we can't fund the transaction from any individual OutputType, run coin selection one last time + // over all available coins, which would allow mixing + if (allow_mixed_output_types) { + if (auto result{ChooseSelectionResult(wallet, nTargetValue, eligibility_filter, available_coins.All(), coin_selection_params)}) { + return result; + } + } + // Either mixing is not allowed and we couldn't find a solution from any single OutputType, or mixing was allowed and we still couldn't + // find a solution using all available coins + return std::nullopt; +}; + +std::optional<SelectionResult> ChooseSelectionResult(const CWallet& wallet, const CAmount& nTargetValue, const CoinEligibilityFilter& eligibility_filter, const std::vector<COutput>& available_coins, const CoinSelectionParams& coin_selection_params) { // Vector of results. We will choose the best one based on waste. std::vector<SelectionResult> results; - // Note that unlike KnapsackSolver, we do not include the fee for creating a change output as BnB will not create a change output. - std::vector<OutputGroup> positive_groups = GroupOutputs(wallet, coins, coin_selection_params, eligibility_filter, true /* positive_only */); + std::vector<OutputGroup> positive_groups = GroupOutputs(wallet, available_coins, coin_selection_params, eligibility_filter, /*positive_only=*/true); if (auto bnb_result{SelectCoinsBnB(positive_groups, nTargetValue, coin_selection_params.m_cost_of_change)}) { results.push_back(*bnb_result); } // The knapsack solver has some legacy behavior where it will spend dust outputs. We retain this behavior, so don't filter for positive only here. - std::vector<OutputGroup> all_groups = GroupOutputs(wallet, coins, coin_selection_params, eligibility_filter, false /* positive_only */); - // While nTargetValue includes the transaction fees for non-input things, it does not include the fee for creating a change output. - // So we need to include that for KnapsackSolver as well, as we are expecting to create a change output. - if (auto knapsack_result{KnapsackSolver(all_groups, nTargetValue + coin_selection_params.m_change_fee, - coin_selection_params.m_min_change_target, coin_selection_params.rng_fast)}) { - knapsack_result->ComputeAndSetWaste(coin_selection_params.m_cost_of_change); + std::vector<OutputGroup> all_groups = GroupOutputs(wallet, available_coins, coin_selection_params, eligibility_filter, /*positive_only=*/false); + if (auto knapsack_result{KnapsackSolver(all_groups, nTargetValue, coin_selection_params.m_min_change_target, coin_selection_params.rng_fast)}) { + knapsack_result->ComputeAndSetWaste(coin_selection_params.min_viable_change, coin_selection_params.m_cost_of_change, coin_selection_params.m_change_fee); results.push_back(*knapsack_result); } - // Include change for SRD as we want to avoid making really small change if the selection just - // barely meets the target. Just use the lower bound change target instead of the randomly - // generated one, since SRD will result in a random change amount anyway; avoid making the - // target needlessly large. - const CAmount srd_target = nTargetValue + coin_selection_params.m_change_fee + CHANGE_LOWER; - if (auto srd_result{SelectCoinsSRD(positive_groups, srd_target, coin_selection_params.rng_fast)}) { - srd_result->ComputeAndSetWaste(coin_selection_params.m_cost_of_change); + if (auto srd_result{SelectCoinsSRD(positive_groups, nTargetValue, coin_selection_params.rng_fast)}) { + srd_result->ComputeAndSetWaste(coin_selection_params.min_viable_change, coin_selection_params.m_cost_of_change, coin_selection_params.m_change_fee); results.push_back(*srd_result); } @@ -424,30 +522,12 @@ std::optional<SelectionResult> AttemptSelection(const CWallet& wallet, const CAm return best_result; } -std::optional<SelectionResult> SelectCoins(const CWallet& wallet, const std::vector<COutput>& vAvailableCoins, const CAmount& nTargetValue, const CCoinControl& coin_control, const CoinSelectionParams& coin_selection_params) +std::optional<SelectionResult> SelectCoins(const CWallet& wallet, CoinsResult& available_coins, const CAmount& nTargetValue, const CCoinControl& coin_control, const CoinSelectionParams& coin_selection_params) { - std::vector<COutput> vCoins(vAvailableCoins); CAmount value_to_select = nTargetValue; OutputGroup preset_inputs(coin_selection_params); - // coin control -> return all selected outputs (we want all selected to go into the transaction for sure) - if (coin_control.HasSelected() && !coin_control.fAllowOtherInputs) - { - for (const COutput& out : vCoins) { - if (!out.spendable) continue; - /* Set ancestors and descendants to 0 as these don't matter for preset inputs as no actual selection is being done. - * positive_only is set to false because we want to include all preset inputs, even if they are dust. - */ - preset_inputs.Insert(out, /*ancestors=*/ 0, /*descendants=*/ 0, /*positive_only=*/ false); - } - SelectionResult result(nTargetValue, SelectionAlgorithm::MANUAL); - result.AddInput(preset_inputs); - if (result.GetSelectedValue() < nTargetValue) return std::nullopt; - result.ComputeAndSetWaste(coin_selection_params.m_cost_of_change); - return result; - } - // calculate value from preset inputs and store them std::set<COutPoint> preset_coins; @@ -456,22 +536,25 @@ std::optional<SelectionResult> SelectCoins(const CWallet& wallet, const std::vec for (const COutPoint& outpoint : vPresetInputs) { int input_bytes = -1; CTxOut txout; - std::map<uint256, CWalletTx>::const_iterator it = wallet.mapWallet.find(outpoint.hash); - if (it != wallet.mapWallet.end()) { - const CWalletTx& wtx = it->second; + auto ptr_wtx = wallet.GetWalletTx(outpoint.hash); + if (ptr_wtx) { // Clearly invalid input, fail - if (wtx.tx->vout.size() <= outpoint.n) { + if (ptr_wtx->tx->vout.size() <= outpoint.n) { return std::nullopt; } - input_bytes = GetTxSpendSize(wallet, wtx, outpoint.n, false); - txout = wtx.tx->vout.at(outpoint.n); + txout = ptr_wtx->tx->vout.at(outpoint.n); + input_bytes = CalculateMaximumSignedInputSize(txout, &wallet, &coin_control); } else { // The input is external. We did not find the tx in mapWallet. if (!coin_control.GetExternalOutput(outpoint, txout)) { return std::nullopt; } - input_bytes = CalculateMaximumSignedInputSize(txout, &coin_control.m_external_provider, /*use_max_sig=*/true); } + + if (input_bytes == -1) { + input_bytes = CalculateMaximumSignedInputSize(txout, outpoint, &coin_control.m_external_provider, &coin_control); + } + // If available, override calculated size with coin control specified size if (coin_control.HasInputWeight(outpoint)) { input_bytes = GetVirtualTransactionSize(coin_control.GetInputWeight(outpoint), 0, 0); @@ -495,13 +578,24 @@ std::optional<SelectionResult> SelectCoins(const CWallet& wallet, const std::vec preset_inputs.Insert(output, /*ancestors=*/ 0, /*descendants=*/ 0, /*positive_only=*/ false); } - // remove preset inputs from vCoins so that Coin Selection doesn't pick them. - for (std::vector<COutput>::iterator it = vCoins.begin(); it != vCoins.end() && coin_control.HasSelected();) - { - if (preset_coins.count(it->outpoint)) - it = vCoins.erase(it); - else - ++it; + // coin control -> return all selected outputs (we want all selected to go into the transaction for sure) + if (coin_control.HasSelected() && !coin_control.m_allow_other_inputs) { + SelectionResult result(nTargetValue, SelectionAlgorithm::MANUAL); + result.AddInput(preset_inputs); + + if (!coin_selection_params.m_subtract_fee_outputs && result.GetSelectedEffectiveValue() < nTargetValue) { + return std::nullopt; + } else if (result.GetSelectedValue() < nTargetValue) { + return std::nullopt; + } + + result.ComputeAndSetWaste(coin_selection_params.min_viable_change, coin_selection_params.m_cost_of_change, coin_selection_params.m_change_fee); + return result; + } + + // remove preset inputs from coins so that Coin Selection doesn't pick them. + if (coin_control.HasSelected()) { + available_coins.Erase(preset_coins); } unsigned int limit_ancestor_count = 0; @@ -513,42 +607,46 @@ std::optional<SelectionResult> SelectCoins(const CWallet& wallet, const std::vec // form groups from remaining coins; note that preset coins will not // automatically have their associated (same address) coins included - if (coin_control.m_avoid_partial_spends && vCoins.size() > OUTPUT_GROUP_MAX_ENTRIES) { + if (coin_control.m_avoid_partial_spends && available_coins.Size() > OUTPUT_GROUP_MAX_ENTRIES) { // Cases where we have 101+ outputs all pointing to the same destination may result in // privacy leaks as they will potentially be deterministically sorted. We solve that by // explicitly shuffling the outputs before processing - Shuffle(vCoins.begin(), vCoins.end(), coin_selection_params.rng_fast); + available_coins.Shuffle(coin_selection_params.rng_fast); } + SelectionResult preselected(preset_inputs.GetSelectionAmount(), SelectionAlgorithm::MANUAL); + preselected.AddInput(preset_inputs); + // Coin Selection attempts to select inputs from a pool of eligible UTXOs to fund the // transaction at a target feerate. If an attempt fails, more attempts may be made using a more // permissive CoinEligibilityFilter. std::optional<SelectionResult> res = [&] { // Pre-selected inputs already cover the target amount. - if (value_to_select <= 0) return std::make_optional(SelectionResult(nTargetValue, SelectionAlgorithm::MANUAL)); + if (value_to_select <= 0) return std::make_optional(SelectionResult(value_to_select, SelectionAlgorithm::MANUAL)); // If possible, fund the transaction with confirmed UTXOs only. Prefer at least six // confirmations on outputs received from other wallets and only spend confirmed change. - if (auto r1{AttemptSelection(wallet, value_to_select, CoinEligibilityFilter(1, 6, 0), vCoins, coin_selection_params)}) return r1; - if (auto r2{AttemptSelection(wallet, value_to_select, CoinEligibilityFilter(1, 1, 0), vCoins, coin_selection_params)}) return r2; + if (auto r1{AttemptSelection(wallet, value_to_select, CoinEligibilityFilter(1, 6, 0), available_coins, coin_selection_params, /*allow_mixed_output_types=*/false)}) return r1; + // Allow mixing only if no solution from any single output type can be found + if (auto r2{AttemptSelection(wallet, value_to_select, CoinEligibilityFilter(1, 1, 0), available_coins, coin_selection_params, /*allow_mixed_output_types=*/true)}) return r2; // Fall back to using zero confirmation change (but with as few ancestors in the mempool as // possible) if we cannot fund the transaction otherwise. if (wallet.m_spend_zero_conf_change) { - if (auto r3{AttemptSelection(wallet, value_to_select, CoinEligibilityFilter(0, 1, 2), vCoins, coin_selection_params)}) return r3; + if (auto r3{AttemptSelection(wallet, value_to_select, CoinEligibilityFilter(0, 1, 2), available_coins, coin_selection_params, /*allow_mixed_output_types=*/true)}) return r3; if (auto r4{AttemptSelection(wallet, value_to_select, CoinEligibilityFilter(0, 1, std::min((size_t)4, max_ancestors/3), std::min((size_t)4, max_descendants/3)), - vCoins, coin_selection_params)}) { + available_coins, coin_selection_params, /*allow_mixed_output_types=*/true)}) { return r4; } if (auto r5{AttemptSelection(wallet, value_to_select, CoinEligibilityFilter(0, 1, max_ancestors/2, max_descendants/2), - vCoins, coin_selection_params)}) { + available_coins, coin_selection_params, /*allow_mixed_output_types=*/true)}) { return r5; } // If partial groups are allowed, relax the requirement of spending OutputGroups (groups // of UTXOs sent to the same address, which are obviously controlled by a single wallet) // in their entirety. if (auto r6{AttemptSelection(wallet, value_to_select, CoinEligibilityFilter(0, 1, max_ancestors-1, max_descendants-1, true /* include_partial_groups */), - vCoins, coin_selection_params)}) { + available_coins, coin_selection_params, /*allow_mixed_output_types=*/true)}) { return r6; } // Try with unsafe inputs if they are allowed. This may spend unconfirmed outputs @@ -556,7 +654,7 @@ std::optional<SelectionResult> SelectCoins(const CWallet& wallet, const std::vec if (coin_control.m_include_unsafe_inputs) { if (auto r7{AttemptSelection(wallet, value_to_select, CoinEligibilityFilter(0 /* conf_mine */, 0 /* conf_theirs */, max_ancestors-1, max_descendants-1, true /* include_partial_groups */), - vCoins, coin_selection_params)}) { + available_coins, coin_selection_params, /*allow_mixed_output_types=*/true)}) { return r7; } } @@ -566,7 +664,7 @@ std::optional<SelectionResult> SelectCoins(const CWallet& wallet, const std::vec if (!fRejectLongChains) { if (auto r8{AttemptSelection(wallet, value_to_select, CoinEligibilityFilter(0, 1, std::numeric_limits<uint64_t>::max(), std::numeric_limits<uint64_t>::max(), true /* include_partial_groups */), - vCoins, coin_selection_params)}) { + available_coins, coin_selection_params, /*allow_mixed_output_types=*/true)}) { return r8; } } @@ -578,9 +676,9 @@ std::optional<SelectionResult> SelectCoins(const CWallet& wallet, const std::vec if (!res) return std::nullopt; // Add preset inputs to result - res->AddInput(preset_inputs); - if (res->m_algo == SelectionAlgorithm::MANUAL) { - res->ComputeAndSetWaste(coin_selection_params.m_cost_of_change); + res->Merge(preselected); + if (res->GetAlgo() == SelectionAlgorithm::MANUAL) { + res->ComputeAndSetWaste(coin_selection_params.min_viable_change, coin_selection_params.m_cost_of_change, coin_selection_params.m_change_fee); } return res; @@ -660,19 +758,16 @@ static void DiscourageFeeSniping(CMutableTransaction& tx, FastRandomContext& rng } } -static std::optional<CreatedTransactionResult> CreateTransactionInternal( +static util::Result<CreatedTransactionResult> CreateTransactionInternal( CWallet& wallet, const std::vector<CRecipient>& vecSend, int change_pos, - bilingual_str& error, const CCoinControl& coin_control, - FeeCalculation& fee_calc_out, bool sign) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet) { AssertLockHeld(wallet.cs_wallet); // out variables, to be packed into returned result structure - CTransactionRef tx; CAmount nFeeRet; int nChangePosInOut = change_pos; @@ -697,10 +792,10 @@ static std::optional<CreatedTransactionResult> CreateTransactionInternal( coin_selection_params.m_subtract_fee_outputs = true; } } - coin_selection_params.m_change_target = GenerateChangeTarget(std::floor(recipients_sum / vecSend.size()), rng_fast); // Create change script that will be used if we need change CScript scriptChange; + bilingual_str error; // possible error str // coin control: send change to custom address if (!std::get_if<CNoDestination>(&coin_control.destChange)) { @@ -716,11 +811,13 @@ static std::optional<CreatedTransactionResult> CreateTransactionInternal( // Reserve a new key pair from key pool. If it fails, provide a dummy // destination in case we don't need change. CTxDestination dest; - bilingual_str dest_err; - if (!reservedest.GetReservedDestination(dest, true, dest_err)) { - error = _("Transaction needs a change address, but we can't generate it.") + Untranslated(" ") + dest_err; + auto op_dest = reservedest.GetReservedDestination(true); + if (!op_dest) { + error = _("Transaction needs a change address, but we can't generate it.") + Untranslated(" ") + util::ErrorString(op_dest); + } else { + dest = *op_dest; + scriptChange = GetScriptForDestination(dest); } - scriptChange = GetScriptForDestination(dest); // A valid destination implies a change script (and // vice-versa). An empty change script will abort later, if the // change keypool ran out, but change is required. @@ -748,13 +845,11 @@ static std::optional<CreatedTransactionResult> CreateTransactionInternal( // 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 && 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 std::nullopt; + return util::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))}; } if (feeCalc.reason == FeeReason::FALLBACK && !wallet.m_allow_fallback_fee) { // eventually allow a fallback fee - error = _("Fee estimation failed. Fallbackfee is disabled. Wait a few blocks or enable -fallbackfee."); - return std::nullopt; + return util::Error{_("Fee estimation failed. Fallbackfee is disabled. Wait a few blocks or enable -fallbackfee.")}; } // Calculate the cost of change @@ -765,9 +860,19 @@ static std::optional<CreatedTransactionResult> CreateTransactionInternal( coin_selection_params.m_change_fee = coin_selection_params.m_effective_feerate.GetFee(coin_selection_params.change_output_size); coin_selection_params.m_cost_of_change = coin_selection_params.m_discard_feerate.GetFee(coin_selection_params.change_spend_size) + coin_selection_params.m_change_fee; + coin_selection_params.m_min_change_target = GenerateChangeTarget(std::floor(recipients_sum / vecSend.size()), coin_selection_params.m_change_fee, rng_fast); + + // The smallest change amount should be: + // 1. at least equal to dust threshold + // 2. at least 1 sat greater than fees to spend it at m_discard_feerate + const auto dust = GetDustThreshold(change_prototype_txout, coin_selection_params.m_discard_feerate); + const auto change_spend_fee = coin_selection_params.m_discard_feerate.GetFee(coin_selection_params.change_spend_size); + coin_selection_params.min_viable_change = std::max(change_spend_fee + 1, dust); + // vouts to the payees if (!coin_selection_params.m_subtract_fee_outputs) { - coin_selection_params.tx_noinputs_size = 11; // Static vsize overhead + outputs vsize. 4 nVersion, 4 nLocktime, 1 input count, 1 output count, 1 witness overhead (dummy, flag, stack size) + coin_selection_params.tx_noinputs_size = 10; // Static vsize overhead + outputs vsize. 4 nVersion, 4 nLocktime, 1 input count, 1 witness overhead (dummy, flag, stack size) + coin_selection_params.tx_noinputs_size += GetSizeOfCompactSize(vecSend.size()); // bytes for output count } for (const auto& recipient : vecSend) { @@ -778,10 +883,8 @@ static std::optional<CreatedTransactionResult> CreateTransactionInternal( coin_selection_params.tx_noinputs_size += ::GetSerializeSize(txout, PROTOCOL_VERSION); } - if (IsDust(txout, wallet.chain().relayDustFee())) - { - error = _("Transaction amount too small"); - return std::nullopt; + if (IsDust(txout, wallet.chain().relayDustFee())) { + return util::Error{_("Transaction amount too small")}; } txNew.vout.push_back(txout); } @@ -791,36 +894,35 @@ static std::optional<CreatedTransactionResult> CreateTransactionInternal( CAmount selection_target = recipients_sum + not_input_fees; // Get available coins - std::vector<COutput> vAvailableCoins; - AvailableCoins(wallet, vAvailableCoins, &coin_control, coin_selection_params.m_effective_feerate, 1, MAX_MONEY, MAX_MONEY, 0); + auto available_coins = AvailableCoins(wallet, + &coin_control, + coin_selection_params.m_effective_feerate, + 1, /*nMinimumAmount*/ + MAX_MONEY, /*nMaximumAmount*/ + MAX_MONEY, /*nMinimumSumAmount*/ + 0); /*nMaximumCount*/ // Choose coins to use - std::optional<SelectionResult> result = SelectCoins(wallet, vAvailableCoins, /*nTargetValue=*/selection_target, coin_control, coin_selection_params); + std::optional<SelectionResult> result = SelectCoins(wallet, available_coins, /*nTargetValue=*/selection_target, coin_control, coin_selection_params); if (!result) { - error = _("Insufficient funds"); - return std::nullopt; + return util::Error{_("Insufficient funds")}; } - TRACE5(coin_selection, selected_coins, wallet.GetName().c_str(), GetAlgorithmName(result->m_algo).c_str(), result->m_target, result->GetWaste(), result->GetSelectedValue()); - - // Always make a change output - // We will reduce the fee from this change output later, and remove the output if it is too small. - const CAmount change_and_fee = result->GetSelectedValue() - recipients_sum; - assert(change_and_fee >= 0); - CTxOut newTxOut(change_and_fee, scriptChange); - - if (nChangePosInOut == -1) { - // Insert change txn at random position: - nChangePosInOut = rng_fast.randrange(txNew.vout.size() + 1); - } - else if ((unsigned int)nChangePosInOut > txNew.vout.size()) - { - error = _("Transaction change output index out of range"); - return std::nullopt; + TRACE5(coin_selection, selected_coins, wallet.GetName().c_str(), GetAlgorithmName(result->GetAlgo()).c_str(), result->GetTarget(), result->GetWaste(), result->GetSelectedValue()); + + const CAmount change_amount = result->GetChange(coin_selection_params.min_viable_change, coin_selection_params.m_change_fee); + if (change_amount > 0) { + CTxOut newTxOut(change_amount, scriptChange); + if (nChangePosInOut == -1) { + // Insert change txn at random position: + nChangePosInOut = rng_fast.randrange(txNew.vout.size() + 1); + } else if ((unsigned int)nChangePosInOut > txNew.vout.size()) { + return util::Error{_("Transaction change output index out of range")}; + } + txNew.vout.insert(txNew.vout.begin() + nChangePosInOut, newTxOut); + } else { + nChangePosInOut = -1; } - assert(nChangePosInOut != -1); - auto change_position = txNew.vout.insert(txNew.vout.begin() + nChangePosInOut, newTxOut); - // Shuffle selected coins and fill in final vin std::vector<COutput> selected_coins = result->GetShuffledInputVector(); @@ -842,45 +944,25 @@ static std::optional<CreatedTransactionResult> CreateTransactionInternal( TxSize tx_sizes = CalculateMaximumSignedTxSize(CTransaction(txNew), &wallet, &coin_control); int nBytes = tx_sizes.vsize; if (nBytes == -1) { - error = _("Missing solving data for estimating transaction size"); - return std::nullopt; - } - nFeeRet = coin_selection_params.m_effective_feerate.GetFee(nBytes); - - // Subtract fee from the change output if not subtracting it from recipient outputs - CAmount fee_needed = nFeeRet; - if (!coin_selection_params.m_subtract_fee_outputs) { - change_position->nValue -= fee_needed; + return util::Error{_("Missing solving data for estimating transaction size")}; } + CAmount fee_needed = coin_selection_params.m_effective_feerate.GetFee(nBytes); + nFeeRet = result->GetSelectedValue() - recipients_sum - change_amount; - // We want to drop the change to fees if: - // 1. The change output would be dust - // 2. The change is within the (almost) exact match window, i.e. it is less than or equal to the cost of the change output (cost_of_change) - CAmount change_amount = change_position->nValue; - if (IsDust(*change_position, coin_selection_params.m_discard_feerate) || change_amount <= coin_selection_params.m_cost_of_change) - { - nChangePosInOut = -1; - change_amount = 0; - txNew.vout.erase(change_position); - - // Because we have dropped this change, the tx size and required fee will be different, so let's recalculate those - tx_sizes = CalculateMaximumSignedTxSize(CTransaction(txNew), &wallet, &coin_control); - nBytes = tx_sizes.vsize; - fee_needed = coin_selection_params.m_effective_feerate.GetFee(nBytes); - } - - // The only time that fee_needed should be less than the amount available for fees (in change_and_fee - change_amount) is when + // The only time that fee_needed should be less than the amount available for fees is when // we are subtracting the fee from the outputs. If this occurs at any other time, it is a bug. - assert(coin_selection_params.m_subtract_fee_outputs || fee_needed <= change_and_fee - change_amount); + assert(coin_selection_params.m_subtract_fee_outputs || fee_needed <= nFeeRet); - // Update nFeeRet in case fee_needed changed due to dropping the change output - if (fee_needed <= change_and_fee - change_amount) { - nFeeRet = change_and_fee - change_amount; + // If there is a change output and we overpay the fees then increase the change to match the fee needed + if (nChangePosInOut != -1 && fee_needed < nFeeRet) { + auto& change = txNew.vout.at(nChangePosInOut); + change.nValue += nFeeRet - fee_needed; + nFeeRet = fee_needed; } // Reduce output values for subtractFeeFromAmount if (coin_selection_params.m_subtract_fee_outputs) { - CAmount to_reduce = fee_needed + change_amount - change_and_fee; + CAmount to_reduce = fee_needed - nFeeRet; int i = 0; bool fFirst = true; for (const auto& recipient : vecSend) @@ -903,11 +985,10 @@ static std::optional<CreatedTransactionResult> CreateTransactionInternal( // Error if this output is reduced to be below dust if (IsDust(txout, wallet.chain().relayDustFee())) { if (txout.nValue < 0) { - error = _("The transaction amount is too small to pay the fee"); + return util::Error{_("The transaction amount is too small to pay the fee")}; } else { - error = _("The transaction amount is too small to send after the fee has been deducted"); + return util::Error{_("The transaction amount is too small to send after the fee has been deducted")}; } - return std::nullopt; } } ++i; @@ -917,42 +998,37 @@ static std::optional<CreatedTransactionResult> CreateTransactionInternal( // Give up if change keypool ran out and change is required if (scriptChange.empty() && nChangePosInOut != -1) { - return std::nullopt; + return util::Error{error}; } if (sign && !wallet.SignTransaction(txNew)) { - error = _("Signing transaction failed"); - return std::nullopt; + return util::Error{_("Signing transaction failed")}; } // Return the constructed transaction data. - tx = MakeTransactionRef(std::move(txNew)); + CTransactionRef tx = MakeTransactionRef(std::move(txNew)); // Limit size if ((sign && GetTransactionWeight(*tx) > MAX_STANDARD_TX_WEIGHT) || (!sign && tx_sizes.weight > MAX_STANDARD_TX_WEIGHT)) { - error = _("Transaction too large"); - return std::nullopt; + return util::Error{_("Transaction too large")}; } if (nFeeRet > wallet.m_default_max_tx_fee) { - error = TransactionErrorString(TransactionError::MAX_FEE_EXCEEDED); - return std::nullopt; + return util::Error{TransactionErrorString(TransactionError::MAX_FEE_EXCEEDED)}; } if (gArgs.GetBoolArg("-walletrejectlongchains", DEFAULT_WALLET_REJECT_LONG_CHAINS)) { // Lastly, ensure this tx will pass the mempool's chain limits if (!wallet.chain().checkChainLimits(tx)) { - error = _("Transaction has too long of a mempool chain"); - return std::nullopt; + return util::Error{_("Transaction has too long of a mempool chain")}; } } // Before we return success, we assume any change key will be used to prevent // accidental re-use. reservedest.KeepDestination(); - fee_calc_out = feeCalc; wallet.WalletLogPrintf("Fee Calculation: Fee:%d Bytes:%u Tgt:%d (requested %d) Reason:\"%s\" Decay %.5f: Estimation: (%g - %g) %.2f%% %.1f/(%.1f %d mem %.1f out) Fail: (%g - %g) %.2f%% %.1f/(%.1f %d mem %.1f out)\n", nFeeRet, nBytes, feeCalc.returnedTarget, feeCalc.desiredTarget, StringForFeeReason(feeCalc.reason), feeCalc.est.decay, @@ -962,52 +1038,48 @@ static std::optional<CreatedTransactionResult> CreateTransactionInternal( feeCalc.est.fail.start, feeCalc.est.fail.end, (feeCalc.est.fail.totalConfirmed + feeCalc.est.fail.inMempool + feeCalc.est.fail.leftMempool) > 0.0 ? 100 * feeCalc.est.fail.withinTarget / (feeCalc.est.fail.totalConfirmed + feeCalc.est.fail.inMempool + feeCalc.est.fail.leftMempool) : 0.0, feeCalc.est.fail.withinTarget, feeCalc.est.fail.totalConfirmed, feeCalc.est.fail.inMempool, feeCalc.est.fail.leftMempool); - return CreatedTransactionResult(tx, nFeeRet, nChangePosInOut); + return CreatedTransactionResult(tx, nFeeRet, nChangePosInOut, feeCalc); } -std::optional<CreatedTransactionResult> CreateTransaction( +util::Result<CreatedTransactionResult> CreateTransaction( CWallet& wallet, const std::vector<CRecipient>& vecSend, int change_pos, - bilingual_str& error, const CCoinControl& coin_control, - FeeCalculation& fee_calc_out, bool sign) { if (vecSend.empty()) { - error = _("Transaction must have at least one recipient"); - return std::nullopt; + return util::Error{_("Transaction must have at least one recipient")}; } if (std::any_of(vecSend.cbegin(), vecSend.cend(), [](const auto& recipient){ return recipient.nAmount < 0; })) { - error = _("Transaction amounts must not be negative"); - return std::nullopt; + return util::Error{_("Transaction amounts must not be negative")}; } LOCK(wallet.cs_wallet); - std::optional<CreatedTransactionResult> txr_ungrouped = CreateTransactionInternal(wallet, vecSend, change_pos, error, coin_control, fee_calc_out, sign); - TRACE4(coin_selection, normal_create_tx_internal, wallet.GetName().c_str(), txr_ungrouped.has_value(), - txr_ungrouped.has_value() ? txr_ungrouped->fee : 0, txr_ungrouped.has_value() ? txr_ungrouped->change_pos : 0); - if (!txr_ungrouped) return std::nullopt; + auto res = CreateTransactionInternal(wallet, vecSend, change_pos, coin_control, sign); + TRACE4(coin_selection, normal_create_tx_internal, wallet.GetName().c_str(), bool(res), + res ? res->fee : 0, res ? res->change_pos : 0); + if (!res) return res; + const auto& txr_ungrouped = *res; // try with avoidpartialspends unless it's enabled already - if (txr_ungrouped->fee > 0 /* 0 means non-functional fee rate estimation */ && wallet.m_max_aps_fee > -1 && !coin_control.m_avoid_partial_spends) { + if (txr_ungrouped.fee > 0 /* 0 means non-functional fee rate estimation */ && wallet.m_max_aps_fee > -1 && !coin_control.m_avoid_partial_spends) { TRACE1(coin_selection, attempting_aps_create_tx, wallet.GetName().c_str()); CCoinControl tmp_cc = coin_control; tmp_cc.m_avoid_partial_spends = true; - bilingual_str error2; // fired and forgotten; if an error occurs, we discard the results - std::optional<CreatedTransactionResult> txr_grouped = CreateTransactionInternal(wallet, vecSend, change_pos, error2, tmp_cc, fee_calc_out, sign); + auto txr_grouped = CreateTransactionInternal(wallet, vecSend, change_pos, tmp_cc, sign); // if fee of this alternative one is within the range of the max fee, we use this one - const bool use_aps{txr_grouped.has_value() ? (txr_grouped->fee <= txr_ungrouped->fee + wallet.m_max_aps_fee) : false}; + const bool use_aps{txr_grouped.has_value() ? (txr_grouped->fee <= txr_ungrouped.fee + wallet.m_max_aps_fee) : false}; TRACE5(coin_selection, aps_create_tx_internal, wallet.GetName().c_str(), use_aps, txr_grouped.has_value(), txr_grouped.has_value() ? txr_grouped->fee : 0, txr_grouped.has_value() ? txr_grouped->change_pos : 0); if (txr_grouped) { wallet.WalletLogPrintf("Fee non-grouped = %lld, grouped = %lld, using %s\n", - txr_ungrouped->fee, txr_grouped->fee, use_aps ? "grouped" : "non-grouped"); + txr_ungrouped.fee, txr_grouped->fee, use_aps ? "grouped" : "non-grouped"); if (use_aps) return txr_grouped; } } - return txr_ungrouped; + return res; } bool FundTransaction(CWallet& wallet, CMutableTransaction& tx, CAmount& nFeeRet, int& nChangePosInOut, bilingual_str& error, bool lockUnspents, const std::set<int>& setSubtractFeeFromOutputs, CCoinControl coinControl) @@ -1021,22 +1093,41 @@ bool FundTransaction(CWallet& wallet, CMutableTransaction& tx, CAmount& nFeeRet, vecSend.push_back(recipient); } - coinControl.fAllowOtherInputs = true; + // Acquire the locks to prevent races to the new locked unspents between the + // CreateTransaction call and LockCoin calls (when lockUnspents is true). + LOCK(wallet.cs_wallet); + // Fetch specified UTXOs from the UTXO set to get the scriptPubKeys and values of the outputs being selected + // and to match with the given solving_data. Only used for non-wallet outputs. + std::map<COutPoint, Coin> coins; for (const CTxIn& txin : tx.vin) { - coinControl.Select(txin.prevout); + coins[txin.prevout]; // Create empty map entry keyed by prevout. } + wallet.chain().findCoins(coins); - // Acquire the locks to prevent races to the new locked unspents between the - // CreateTransaction call and LockCoin calls (when lockUnspents is true). - LOCK(wallet.cs_wallet); + for (const CTxIn& txin : tx.vin) { + const auto& outPoint = txin.prevout; + if (wallet.IsMine(outPoint)) { + // The input was found in the wallet, so select as internal + coinControl.Select(outPoint); + } else if (coins[outPoint].out.IsNull()) { + error = _("Unable to find UTXO for external input"); + return false; + } else { + // The input was not in the wallet, but is in the UTXO set, so select as external + coinControl.SelectExternal(outPoint, coins[outPoint].out); + } + } - FeeCalculation fee_calc_out; - std::optional<CreatedTransactionResult> txr = CreateTransaction(wallet, vecSend, nChangePosInOut, error, coinControl, fee_calc_out, false); - if (!txr) return false; - CTransactionRef tx_new = txr->tx; - nFeeRet = txr->fee; - nChangePosInOut = txr->change_pos; + auto res = CreateTransaction(wallet, vecSend, nChangePosInOut, coinControl, false); + if (!res) { + error = util::ErrorString(res); + return false; + } + const auto& txr = *res; + CTransactionRef tx_new = txr.tx; + nFeeRet = txr.fee; + nChangePosInOut = txr.change_pos; if (nChangePosInOut != -1) { tx.vout.insert(tx.vout.begin() + nChangePosInOut, tx_new->vout[nChangePosInOut]); diff --git a/src/wallet/spend.h b/src/wallet/spend.h index 988058a25a..c29e5be5c7 100644 --- a/src/wallet/spend.h +++ b/src/wallet/spend.h @@ -6,6 +6,8 @@ #define BITCOIN_WALLET_SPEND_H #include <consensus/amount.h> +#include <policy/fees.h> // for FeeCalculation +#include <util/result.h> #include <wallet/coinselection.h> #include <wallet/transaction.h> #include <wallet/wallet.h> @@ -14,36 +16,62 @@ namespace wallet { /** Get the marginal bytes if spending the specified output from this transaction. - * use_max_sig indicates whether to use the maximum sized, 72 byte signature when calculating the - * size of the input spend. This should only be set when watch-only outputs are allowed */ -int GetTxSpendSize(const CWallet& wallet, const CWalletTx& wtx, unsigned int out, bool use_max_sig = false); - -//Get the marginal bytes of spending the specified output -int CalculateMaximumSignedInputSize(const CTxOut& txout, const CWallet* pwallet, bool use_max_sig = false); -int CalculateMaximumSignedInputSize(const CTxOut& txout, const SigningProvider* pwallet, bool use_max_sig = false); - + * Use CoinControl to determine whether to expect signature grinding when calculating the size of the input spend. */ +int CalculateMaximumSignedInputSize(const CTxOut& txout, const CWallet* pwallet, const CCoinControl* coin_control = nullptr); +int CalculateMaximumSignedInputSize(const CTxOut& txout, const COutPoint outpoint, const SigningProvider* pwallet, const CCoinControl* coin_control = nullptr); struct TxSize { int64_t vsize{-1}; int64_t weight{-1}; }; -/** Calculate the size of the transaction assuming all signatures are max size -* Use DummySignatureCreator, which inserts 71 byte signatures everywhere. -* NOTE: this requires that all inputs must be in mapWallet (eg the tx should -* be AllInputsMine). */ +/** Calculate the size of the transaction using CoinControl to determine + * whether to expect signature grinding when calculating the size of the input spend. */ TxSize CalculateMaximumSignedTxSize(const CTransaction& tx, const CWallet* wallet, const std::vector<CTxOut>& txouts, const CCoinControl* coin_control = nullptr); TxSize CalculateMaximumSignedTxSize(const CTransaction& tx, const CWallet* wallet, const CCoinControl* coin_control = nullptr) EXCLUSIVE_LOCKS_REQUIRED(wallet->cs_wallet); /** - * populate vCoins with vector of available COutputs. + * COutputs available for spending, stored by OutputType. + * This struct is really just a wrapper around OutputType vectors with a convenient + * method for concatenating and returning all COutputs as one vector. + * + * Size(), Clear(), Erase(), Shuffle(), and Add() methods are implemented to + * allow easy interaction with the struct. + */ +struct CoinsResult { + std::map<OutputType, std::vector<COutput>> coins; + + /** Concatenate and return all COutputs as one vector */ + std::vector<COutput> All() const; + + /** The following methods are provided so that CoinsResult can mimic a vector, + * i.e., methods can work with individual OutputType vectors or on the entire object */ + size_t Size() const; + void Clear(); + void Erase(std::set<COutPoint>& preset_coins); + void Shuffle(FastRandomContext& rng_fast); + void Add(OutputType type, const COutput& out); + + /** Sum of all available coins */ + CAmount total_amount{0}; +}; + +/** + * Populate the CoinsResult struct with vectors of available COutputs, organized by OutputType. */ -void AvailableCoins(const CWallet& wallet, std::vector<COutput>& vCoins, const CCoinControl* coinControl = nullptr, std::optional<CFeeRate> feerate = std::nullopt, const CAmount& nMinimumAmount = 1, const CAmount& nMaximumAmount = MAX_MONEY, const CAmount& nMinimumSumAmount = MAX_MONEY, const uint64_t nMaximumCount = 0) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet); +CoinsResult AvailableCoins(const CWallet& wallet, + const CCoinControl* coinControl = nullptr, + std::optional<CFeeRate> feerate = std::nullopt, + const CAmount& nMinimumAmount = 1, + const CAmount& nMaximumAmount = MAX_MONEY, + const CAmount& nMinimumSumAmount = MAX_MONEY, + const uint64_t nMaximumCount = 0, + bool only_spendable = true) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet); /** * Wrapper function for AvailableCoins which skips the `feerate` parameter. Use this function * to list all available coins (e.g. listunspent RPC) while not intending to fund a transaction. */ -void AvailableCoinsListUnspent(const CWallet& wallet, std::vector<COutput>& vCoins, const CCoinControl* coinControl = nullptr, const CAmount& nMinimumAmount = 1, const CAmount& nMaximumAmount = MAX_MONEY, const CAmount& nMinimumSumAmount = MAX_MONEY, const uint64_t nMaximumCount = 0) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet); +CoinsResult AvailableCoinsListUnspent(const CWallet& wallet, const CCoinControl* coinControl = nullptr, const CAmount& nMinimumAmount = 1, const CAmount& nMaximumAmount = MAX_MONEY, const CAmount& nMinimumSumAmount = MAX_MONEY, const uint64_t nMaximumCount = 0) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet); CAmount GetAvailableBalance(const CWallet& wallet, const CCoinControl* coinControl = nullptr); @@ -59,45 +87,63 @@ const CTxOut& FindNonChangeParentOutput(const CWallet& wallet, const COutPoint& std::map<CTxDestination, std::vector<COutput>> ListCoins(const CWallet& wallet) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet); std::vector<OutputGroup> GroupOutputs(const CWallet& wallet, const std::vector<COutput>& outputs, const CoinSelectionParams& coin_sel_params, const CoinEligibilityFilter& filter, bool positive_only); +/** + * Attempt to find a valid input set that preserves privacy by not mixing OutputTypes. + * `ChooseSelectionResult()` will be called on each OutputType individually and the best + * the solution (according to the waste metric) will be chosen. If a valid input cannot be found from any + * single OutputType, fallback to running `ChooseSelectionResult()` over all available coins. + * + * param@[in] wallet The wallet which provides solving data for the coins + * param@[in] nTargetValue The target value + * param@[in] eligilibity_filter A filter containing rules for which coins are allowed to be included in this selection + * param@[in] available_coins The struct of coins, organized by OutputType, available for selection prior to filtering + * param@[in] coin_selection_params Parameters for the coin selection + * param@[in] allow_mixed_output_types Relax restriction that SelectionResults must be of the same OutputType + * returns If successful, a SelectionResult containing the input set + * If failed, a nullopt + */ +std::optional<SelectionResult> AttemptSelection(const CWallet& wallet, const CAmount& nTargetValue, const CoinEligibilityFilter& eligibility_filter, const CoinsResult& available_coins, + const CoinSelectionParams& coin_selection_params, bool allow_mixed_output_types); /** * Attempt to find a valid input set that meets the provided eligibility filter and target. * Multiple coin selection algorithms will be run and the input set that produces the least waste * (according to the waste metric) will be chosen. * - * param@[in] wallet The wallet which provides solving data for the coins - * param@[in] nTargetValue The target value - * param@[in] eligilibity_filter A filter containing rules for which coins are allowed to be included in this selection - * param@[in] coins The vector of coins available for selection prior to filtering - * param@[in] coin_selection_params Parameters for the coin selection - * returns If successful, a SelectionResult containing the input set - * If failed, a nullopt + * param@[in] wallet The wallet which provides solving data for the coins + * param@[in] nTargetValue The target value + * param@[in] eligilibity_filter A filter containing rules for which coins are allowed to be included in this selection + * param@[in] available_coins The struct of coins, organized by OutputType, available for selection prior to filtering + * param@[in] coin_selection_params Parameters for the coin selection + * returns If successful, a SelectionResult containing the input set + * If failed, a nullopt */ -std::optional<SelectionResult> AttemptSelection(const CWallet& wallet, const CAmount& nTargetValue, const CoinEligibilityFilter& eligibility_filter, std::vector<COutput> coins, +std::optional<SelectionResult> ChooseSelectionResult(const CWallet& wallet, const CAmount& nTargetValue, const CoinEligibilityFilter& eligibility_filter, const std::vector<COutput>& available_coins, const CoinSelectionParams& coin_selection_params); /** * Select a set of coins such that nTargetValue is met and at least * all coins from coin_control are selected; never select unconfirmed coins if they are not ours * param@[in] wallet The wallet which provides data necessary to spend the selected coins - * param@[in] vAvailableCoins The vector of coins available to be spent + * param@[in] available_coins The struct of coins, organized by OutputType, available for selection prior to filtering * param@[in] nTargetValue The target value * param@[in] coin_selection_params Parameters for this coin selection such as feerates, whether to avoid partial spends, * and whether to subtract the fee from the outputs. * returns If successful, a SelectionResult containing the selected coins * If failed, a nullopt. */ -std::optional<SelectionResult> SelectCoins(const CWallet& wallet, const std::vector<COutput>& vAvailableCoins, const CAmount& nTargetValue, const CCoinControl& coin_control, +std::optional<SelectionResult> SelectCoins(const CWallet& wallet, CoinsResult& available_coins, const CAmount& nTargetValue, const CCoinControl& coin_control, const CoinSelectionParams& coin_selection_params) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet); struct CreatedTransactionResult { CTransactionRef tx; CAmount fee; + FeeCalculation fee_calc; int change_pos; - CreatedTransactionResult(CTransactionRef tx, CAmount fee, int change_pos) - : tx(tx), fee(fee), change_pos(change_pos) {} + CreatedTransactionResult(CTransactionRef _tx, CAmount _fee, int _change_pos, const FeeCalculation& _fee_calc) + : tx(_tx), fee(_fee), fee_calc(_fee_calc), change_pos(_change_pos) {} }; /** @@ -105,7 +151,7 @@ struct CreatedTransactionResult * selected by SelectCoins(); Also create the change output, when needed * @note passing change_pos as -1 will result in setting a random position */ -std::optional<CreatedTransactionResult> CreateTransaction(CWallet& wallet, const std::vector<CRecipient>& vecSend, int change_pos, bilingual_str& error, const CCoinControl& coin_control, FeeCalculation& fee_calc_out, bool sign = true); +util::Result<CreatedTransactionResult> CreateTransaction(CWallet& wallet, const std::vector<CRecipient>& vecSend, int change_pos, const CCoinControl& coin_control, bool sign = true); /** * Insert additional inputs into the transaction by diff --git a/src/wallet/sqlite.cpp b/src/wallet/sqlite.cpp index 2515df3177..053fb8f983 100644 --- a/src/wallet/sqlite.cpp +++ b/src/wallet/sqlite.cpp @@ -23,7 +23,7 @@ namespace wallet { static constexpr int32_t WALLET_SCHEMA_VERSION = 0; -static Mutex g_sqlite_mutex; +static GlobalMutex g_sqlite_mutex; static int g_sqlite_count GUARDED_BY(g_sqlite_mutex) = 0; static void ErrorLogCallback(void* arg, int code, const char* msg) diff --git a/src/wallet/test/availablecoins_tests.cpp b/src/wallet/test/availablecoins_tests.cpp new file mode 100644 index 0000000000..2427a343d5 --- /dev/null +++ b/src/wallet/test/availablecoins_tests.cpp @@ -0,0 +1,107 @@ +// Copyright (c) 2022 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or https://www.opensource.org/licenses/mit-license.php. + +#include <validation.h> +#include <wallet/coincontrol.h> +#include <wallet/spend.h> +#include <wallet/test/util.h> +#include <wallet/test/wallet_test_fixture.h> + +#include <boost/test/unit_test.hpp> + +namespace wallet { +BOOST_FIXTURE_TEST_SUITE(availablecoins_tests, WalletTestingSetup) +class AvailableCoinsTestingSetup : public TestChain100Setup +{ +public: + AvailableCoinsTestingSetup() + { + CreateAndProcessBlock({}, {}); + wallet = CreateSyncedWallet(*m_node.chain, m_node.chainman->ActiveChain(), m_args, coinbaseKey); + } + + ~AvailableCoinsTestingSetup() + { + wallet.reset(); + } + CWalletTx& AddTx(CRecipient recipient) + { + CTransactionRef tx; + CCoinControl dummy; + { + constexpr int RANDOM_CHANGE_POSITION = -1; + auto res = CreateTransaction(*wallet, {recipient}, RANDOM_CHANGE_POSITION, dummy); + BOOST_CHECK(res); + tx = res->tx; + } + wallet->CommitTransaction(tx, {}, {}); + CMutableTransaction blocktx; + { + LOCK(wallet->cs_wallet); + blocktx = CMutableTransaction(*wallet->mapWallet.at(tx->GetHash()).tx); + } + CreateAndProcessBlock({CMutableTransaction(blocktx)}, GetScriptForRawPubKey(coinbaseKey.GetPubKey())); + + LOCK(wallet->cs_wallet); + LOCK(m_node.chainman->GetMutex()); + wallet->SetLastBlockProcessed(wallet->GetLastBlockHeight() + 1, m_node.chainman->ActiveChain().Tip()->GetBlockHash()); + auto it = wallet->mapWallet.find(tx->GetHash()); + BOOST_CHECK(it != wallet->mapWallet.end()); + it->second.m_state = TxStateConfirmed{m_node.chainman->ActiveChain().Tip()->GetBlockHash(), m_node.chainman->ActiveChain().Height(), /*index=*/1}; + return it->second; + } + + std::unique_ptr<CWallet> wallet; +}; + +BOOST_FIXTURE_TEST_CASE(BasicOutputTypesTest, AvailableCoinsTestingSetup) +{ + CoinsResult available_coins; + util::Result<CTxDestination> dest{util::Error{}}; + LOCK(wallet->cs_wallet); + + // Verify our wallet has one usable coinbase UTXO before starting + // This UTXO is a P2PK, so it should show up in the Other bucket + available_coins = AvailableCoins(*wallet); + BOOST_CHECK_EQUAL(available_coins.Size(), 1U); + BOOST_CHECK_EQUAL(available_coins.coins[OutputType::UNKNOWN].size(), 1U); + + // We will create a self transfer for each of the OutputTypes and + // verify it is put in the correct bucket after running GetAvailablecoins + // + // For each OutputType, We expect 2 UTXOs in our wallet following the self transfer: + // 1. One UTXO as the recipient + // 2. One UTXO from the change, due to payment address matching logic + + // Bech32m + dest = wallet->GetNewDestination(OutputType::BECH32M, ""); + BOOST_ASSERT(dest); + AddTx(CRecipient{{GetScriptForDestination(*dest)}, 1 * COIN, /*fSubtractFeeFromAmount=*/true}); + available_coins = AvailableCoins(*wallet); + BOOST_CHECK_EQUAL(available_coins.coins[OutputType::BECH32M].size(), 2U); + + // Bech32 + dest = wallet->GetNewDestination(OutputType::BECH32, ""); + BOOST_ASSERT(dest); + AddTx(CRecipient{{GetScriptForDestination(*dest)}, 2 * COIN, /*fSubtractFeeFromAmount=*/true}); + available_coins = AvailableCoins(*wallet); + BOOST_CHECK_EQUAL(available_coins.coins[OutputType::BECH32].size(), 2U); + + // P2SH-SEGWIT + dest = wallet->GetNewDestination(OutputType::P2SH_SEGWIT, ""); + BOOST_ASSERT(dest); + AddTx(CRecipient{{GetScriptForDestination(*dest)}, 3 * COIN, /*fSubtractFeeFromAmount=*/true}); + available_coins = AvailableCoins(*wallet); + BOOST_CHECK_EQUAL(available_coins.coins[OutputType::P2SH_SEGWIT].size(), 2U); + + // Legacy (P2PKH) + dest = wallet->GetNewDestination(OutputType::LEGACY, ""); + BOOST_ASSERT(dest); + AddTx(CRecipient{{GetScriptForDestination(*dest)}, 4 * COIN, /*fSubtractFeeFromAmount=*/true}); + available_coins = AvailableCoins(*wallet); + BOOST_CHECK_EQUAL(available_coins.coins[OutputType::LEGACY].size(), 2U); +} + +BOOST_AUTO_TEST_SUITE_END() +} // namespace wallet diff --git a/src/wallet/test/coinselector_tests.cpp b/src/wallet/test/coinselector_tests.cpp index 76f28917a4..23f024247d 100644 --- a/src/wallet/test/coinselector_tests.cpp +++ b/src/wallet/test/coinselector_tests.cpp @@ -67,18 +67,14 @@ static void add_coin(const CAmount& nValue, int nInput, CoinSet& set, CAmount fe set.insert(coin); } -static void add_coin(std::vector<COutput>& coins, CWallet& wallet, const CAmount& nValue, CFeeRate feerate = CFeeRate(0), int nAge = 6*24, bool fIsFromMe = false, int nInput=0, bool spendable = false) +static void add_coin(CoinsResult& available_coins, CWallet& wallet, const CAmount& nValue, CFeeRate feerate = CFeeRate(0), int nAge = 6*24, bool fIsFromMe = false, int nInput =0, bool spendable = false) { CMutableTransaction tx; tx.nLockTime = nextLockTime++; // so all transactions get different hashes tx.vout.resize(nInput + 1); tx.vout[nInput].nValue = nValue; if (spendable) { - CTxDestination dest; - bilingual_str error; - const bool destination_ok = wallet.GetNewDestination(OutputType::BECH32, "", dest, error); - assert(destination_ok); - tx.vout[nInput].scriptPubKey = GetScriptForDestination(dest); + tx.vout[nInput].scriptPubKey = GetScriptForDestination(*Assert(wallet.GetNewDestination(OutputType::BECH32, ""))); } uint256 txid = tx.GetHash(); @@ -86,7 +82,8 @@ static void add_coin(std::vector<COutput>& coins, CWallet& wallet, const CAmount auto ret = wallet.mapWallet.emplace(std::piecewise_construct, std::forward_as_tuple(txid), std::forward_as_tuple(MakeTransactionRef(std::move(tx)), TxStateInactive{})); assert(ret.second); CWalletTx& wtx = (*ret.first).second; - coins.emplace_back(COutPoint(wtx.GetHash(), nInput), wtx.tx->vout.at(nInput), nAge, GetTxSpendSize(wallet, wtx, nInput), /*spendable=*/ true, /*solvable=*/ true, /*safe=*/ true, wtx.GetTxTime(), fIsFromMe, feerate); + const auto& txout = wtx.tx->vout.at(nInput); + available_coins.coins[OutputType::BECH32].emplace_back(COutPoint(wtx.GetHash(), nInput), txout, nAge, CalculateMaximumSignedInputSize(txout, &wallet, /*coin_control=*/nullptr), /*spendable=*/ true, /*solvable=*/ true, /*safe=*/ true, wtx.GetTxTime(), fIsFromMe, feerate); } /** Check if SelectionResult a is equivalent to SelectionResult b. @@ -130,18 +127,18 @@ static CAmount make_hard_case(int utxos, std::vector<COutput>& utxo_pool) return target; } -inline std::vector<OutputGroup>& GroupCoins(const std::vector<COutput>& coins) +inline std::vector<OutputGroup>& GroupCoins(const std::vector<COutput>& available_coins) { static std::vector<OutputGroup> static_groups; static_groups.clear(); - for (auto& coin : coins) { + for (auto& coin : available_coins) { static_groups.emplace_back(); static_groups.back().Insert(coin, /*ancestors=*/ 0, /*descendants=*/ 0, /*positive_only=*/ false); } return static_groups; } -inline std::vector<OutputGroup>& KnapsackGroupOutputs(const std::vector<COutput>& coins, CWallet& wallet, const CoinEligibilityFilter& filter) +inline std::vector<OutputGroup>& KnapsackGroupOutputs(const std::vector<COutput>& available_coins, CWallet& wallet, const CoinEligibilityFilter& filter) { FastRandomContext rand{}; CoinSelectionParams coin_selection_params{ @@ -156,7 +153,7 @@ inline std::vector<OutputGroup>& KnapsackGroupOutputs(const std::vector<COutput> /*avoid_partial=*/ false, }; static std::vector<OutputGroup> static_groups; - static_groups = GroupOutputs(wallet, coins, coin_selection_params, filter, /*positive_only=*/false); + static_groups = GroupOutputs(wallet, available_coins, coin_selection_params, filter, /*positive_only=*/false); return static_groups; } @@ -198,8 +195,8 @@ BOOST_AUTO_TEST_CASE(bnb_search_test) expected_result.Clear(); // Select 5 Cent - add_coin(4 * CENT, 4, expected_result); - add_coin(1 * CENT, 1, expected_result); + add_coin(3 * CENT, 3, expected_result); + add_coin(2 * CENT, 2, expected_result); const auto result3 = SelectCoinsBnB(GroupCoins(utxo_pool), 5 * CENT, 0.5 * CENT); BOOST_CHECK(result3); BOOST_CHECK(EquivalentResult(expected_result, *result3)); @@ -224,8 +221,9 @@ BOOST_AUTO_TEST_CASE(bnb_search_test) // Select 10 Cent add_coin(5 * CENT, 5, utxo_pool); - add_coin(5 * CENT, 5, expected_result); add_coin(4 * CENT, 4, expected_result); + add_coin(3 * CENT, 3, expected_result); + add_coin(2 * CENT, 2, expected_result); add_coin(1 * CENT, 1, expected_result); const auto result5 = SelectCoinsBnB(GroupCoins(utxo_pool), 10 * CENT, 0.5 * CENT); BOOST_CHECK(result5); @@ -250,9 +248,9 @@ BOOST_AUTO_TEST_CASE(bnb_search_test) // Iteration exhaustion test CAmount target = make_hard_case(17, utxo_pool); - BOOST_CHECK(!SelectCoinsBnB(GroupCoins(utxo_pool), target, 0)); // Should exhaust + BOOST_CHECK(!SelectCoinsBnB(GroupCoins(utxo_pool), target, 1)); // Should exhaust target = make_hard_case(14, utxo_pool); - const auto result7 = SelectCoinsBnB(GroupCoins(utxo_pool), target, 0); // Should not exhaust + const auto result7 = SelectCoinsBnB(GroupCoins(utxo_pool), target, 1); // Should not exhaust BOOST_CHECK(result7); // Test same value early bailout optimization @@ -291,8 +289,8 @@ BOOST_AUTO_TEST_CASE(bnb_search_test) // Make sure that effective value is working in AttemptSelection when BnB is used CoinSelectionParams coin_selection_params_bnb{ rand, - /*change_output_size=*/ 0, - /*change_spend_size=*/ 0, + /*change_output_size=*/ 31, + /*change_spend_size=*/ 68, /*min_change_target=*/ 0, /*effective_feerate=*/ CFeeRate(3000), /*long_term_feerate=*/ CFeeRate(1000), @@ -300,6 +298,9 @@ BOOST_AUTO_TEST_CASE(bnb_search_test) /*tx_noinputs_size=*/ 0, /*avoid_partial=*/ false, }; + coin_selection_params_bnb.m_change_fee = coin_selection_params_bnb.m_effective_feerate.GetFee(coin_selection_params_bnb.change_output_size); + coin_selection_params_bnb.m_cost_of_change = coin_selection_params_bnb.m_effective_feerate.GetFee(coin_selection_params_bnb.change_spend_size) + coin_selection_params_bnb.m_change_fee; + coin_selection_params_bnb.min_viable_change = coin_selection_params_bnb.m_effective_feerate.GetFee(coin_selection_params_bnb.change_spend_size); { std::unique_ptr<CWallet> wallet = std::make_unique<CWallet>(m_node.chain.get(), "", m_args, CreateMockWalletDatabase()); wallet->LoadWallet(); @@ -307,18 +308,18 @@ BOOST_AUTO_TEST_CASE(bnb_search_test) wallet->SetWalletFlag(WALLET_FLAG_DESCRIPTORS); wallet->SetupDescriptorScriptPubKeyMans(); - std::vector<COutput> coins; + CoinsResult available_coins; - add_coin(coins, *wallet, 1, coin_selection_params_bnb.m_effective_feerate); - coins.at(0).input_bytes = 40; // Make sure that it has a negative effective value. The next check should assert if this somehow got through. Otherwise it will fail - BOOST_CHECK(!SelectCoinsBnB(GroupCoins(coins), 1 * CENT, coin_selection_params_bnb.m_cost_of_change)); + add_coin(available_coins, *wallet, 1, coin_selection_params_bnb.m_effective_feerate); + available_coins.All().at(0).input_bytes = 40; // Make sure that it has a negative effective value. The next check should assert if this somehow got through. Otherwise it will fail + BOOST_CHECK(!SelectCoinsBnB(GroupCoins(available_coins.All()), 1 * CENT, coin_selection_params_bnb.m_cost_of_change)); // Test fees subtracted from output: - coins.clear(); - add_coin(coins, *wallet, 1 * CENT, coin_selection_params_bnb.m_effective_feerate); - coins.at(0).input_bytes = 40; + available_coins.Clear(); + add_coin(available_coins, *wallet, 1 * CENT, coin_selection_params_bnb.m_effective_feerate); + available_coins.All().at(0).input_bytes = 40; coin_selection_params_bnb.m_subtract_fee_outputs = true; - const auto result9 = SelectCoinsBnB(GroupCoins(coins), 1 * CENT, coin_selection_params_bnb.m_cost_of_change); + const auto result9 = SelectCoinsBnB(GroupCoins(available_coins.All()), 1 * CENT, coin_selection_params_bnb.m_cost_of_change); BOOST_CHECK(result9); BOOST_CHECK_EQUAL(result9->GetSelectedValue(), 1 * CENT); } @@ -330,16 +331,16 @@ BOOST_AUTO_TEST_CASE(bnb_search_test) wallet->SetWalletFlag(WALLET_FLAG_DESCRIPTORS); wallet->SetupDescriptorScriptPubKeyMans(); - std::vector<COutput> coins; + CoinsResult available_coins; - add_coin(coins, *wallet, 5 * CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true); - add_coin(coins, *wallet, 3 * CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true); - add_coin(coins, *wallet, 2 * CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true); + add_coin(available_coins, *wallet, 5 * CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true); + add_coin(available_coins, *wallet, 3 * CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true); + add_coin(available_coins, *wallet, 2 * CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true); CCoinControl coin_control; - coin_control.fAllowOtherInputs = true; - coin_control.Select(coins.at(0).outpoint); + coin_control.m_allow_other_inputs = true; + coin_control.Select(available_coins.All().at(0).outpoint); coin_selection_params_bnb.m_effective_feerate = CFeeRate(0); - const auto result10 = SelectCoins(*wallet, coins, 10 * CENT, coin_control, coin_selection_params_bnb); + const auto result10 = SelectCoins(*wallet, available_coins, 10 * CENT, coin_control, coin_selection_params_bnb); BOOST_CHECK(result10); } { @@ -349,52 +350,52 @@ BOOST_AUTO_TEST_CASE(bnb_search_test) wallet->SetWalletFlag(WALLET_FLAG_DESCRIPTORS); wallet->SetupDescriptorScriptPubKeyMans(); - std::vector<COutput> coins; + CoinsResult available_coins; // single coin should be selected when effective fee > long term fee coin_selection_params_bnb.m_effective_feerate = CFeeRate(5000); coin_selection_params_bnb.m_long_term_feerate = CFeeRate(3000); - add_coin(coins, *wallet, 10 * CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true); - add_coin(coins, *wallet, 9 * CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true); - add_coin(coins, *wallet, 1 * CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true); + add_coin(available_coins, *wallet, 10 * CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true); + add_coin(available_coins, *wallet, 9 * CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true); + add_coin(available_coins, *wallet, 1 * CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true); expected_result.Clear(); add_coin(10 * CENT, 2, expected_result); CCoinControl coin_control; - const auto result11 = SelectCoins(*wallet, coins, 10 * CENT, coin_control, coin_selection_params_bnb); + const auto result11 = SelectCoins(*wallet, available_coins, 10 * CENT, coin_control, coin_selection_params_bnb); BOOST_CHECK(EquivalentResult(expected_result, *result11)); - coins.clear(); + available_coins.Clear(); // more coins should be selected when effective fee < long term fee coin_selection_params_bnb.m_effective_feerate = CFeeRate(3000); coin_selection_params_bnb.m_long_term_feerate = CFeeRate(5000); - add_coin(coins, *wallet, 10 * CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true); - add_coin(coins, *wallet, 9 * CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true); - add_coin(coins, *wallet, 1 * CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true); + add_coin(available_coins, *wallet, 10 * CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true); + add_coin(available_coins, *wallet, 9 * CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true); + add_coin(available_coins, *wallet, 1 * CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true); expected_result.Clear(); add_coin(9 * CENT, 2, expected_result); add_coin(1 * CENT, 2, expected_result); - const auto result12 = SelectCoins(*wallet, coins, 10 * CENT, coin_control, coin_selection_params_bnb); + const auto result12 = SelectCoins(*wallet, available_coins, 10 * CENT, coin_control, coin_selection_params_bnb); BOOST_CHECK(EquivalentResult(expected_result, *result12)); - coins.clear(); + available_coins.Clear(); // pre selected coin should be selected even if disadvantageous coin_selection_params_bnb.m_effective_feerate = CFeeRate(5000); coin_selection_params_bnb.m_long_term_feerate = CFeeRate(3000); - add_coin(coins, *wallet, 10 * CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true); - add_coin(coins, *wallet, 9 * CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true); - add_coin(coins, *wallet, 1 * CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true); + add_coin(available_coins, *wallet, 10 * CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true); + add_coin(available_coins, *wallet, 9 * CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true); + add_coin(available_coins, *wallet, 1 * CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true); expected_result.Clear(); add_coin(9 * CENT, 2, expected_result); add_coin(1 * CENT, 2, expected_result); - coin_control.fAllowOtherInputs = true; - coin_control.Select(coins.at(1).outpoint); // pre select 9 coin - const auto result13 = SelectCoins(*wallet, coins, 10 * CENT, coin_control, coin_selection_params_bnb); + coin_control.m_allow_other_inputs = true; + coin_control.Select(available_coins.All().at(1).outpoint); // pre select 9 coin + const auto result13 = SelectCoins(*wallet, available_coins, 10 * CENT, coin_control, coin_selection_params_bnb); BOOST_CHECK(EquivalentResult(expected_result, *result13)); } } @@ -410,175 +411,175 @@ BOOST_AUTO_TEST_CASE(knapsack_solver_test) wallet->SetWalletFlag(WALLET_FLAG_DESCRIPTORS); wallet->SetupDescriptorScriptPubKeyMans(); - std::vector<COutput> coins; + CoinsResult available_coins; // test multiple times to allow for differences in the shuffle order for (int i = 0; i < RUN_TESTS; i++) { - coins.clear(); + available_coins.Clear(); // with an empty wallet we can't even pay one cent - BOOST_CHECK(!KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_standard), 1 * CENT, CENT)); + BOOST_CHECK(!KnapsackSolver(KnapsackGroupOutputs(available_coins.All(), *wallet, filter_standard), 1 * CENT, CENT)); - add_coin(coins, *wallet, 1*CENT, CFeeRate(0), 4); // add a new 1 cent coin + add_coin(available_coins, *wallet, 1*CENT, CFeeRate(0), 4); // add a new 1 cent coin // with a new 1 cent coin, we still can't find a mature 1 cent - BOOST_CHECK(!KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_standard), 1 * CENT, CENT)); + BOOST_CHECK(!KnapsackSolver(KnapsackGroupOutputs(available_coins.All(), *wallet, filter_standard), 1 * CENT, CENT)); // but we can find a new 1 cent - const auto result1 = KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_confirmed), 1 * CENT, CENT); + const auto result1 = KnapsackSolver(KnapsackGroupOutputs(available_coins.All(), *wallet, filter_confirmed), 1 * CENT, CENT); BOOST_CHECK(result1); BOOST_CHECK_EQUAL(result1->GetSelectedValue(), 1 * CENT); - add_coin(coins, *wallet, 2*CENT); // add a mature 2 cent coin + add_coin(available_coins, *wallet, 2*CENT); // add a mature 2 cent coin // we can't make 3 cents of mature coins - BOOST_CHECK(!KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_standard), 3 * CENT, CENT)); + BOOST_CHECK(!KnapsackSolver(KnapsackGroupOutputs(available_coins.All(), *wallet, filter_standard), 3 * CENT, CENT)); // we can make 3 cents of new coins - const auto result2 = KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_confirmed), 3 * CENT, CENT); + const auto result2 = KnapsackSolver(KnapsackGroupOutputs(available_coins.All(), *wallet, filter_confirmed), 3 * CENT, CENT); BOOST_CHECK(result2); BOOST_CHECK_EQUAL(result2->GetSelectedValue(), 3 * CENT); - add_coin(coins, *wallet, 5*CENT); // add a mature 5 cent coin, - add_coin(coins, *wallet, 10*CENT, CFeeRate(0), 3, true); // a new 10 cent coin sent from one of our own addresses - add_coin(coins, *wallet, 20*CENT); // and a mature 20 cent coin + add_coin(available_coins, *wallet, 5*CENT); // add a mature 5 cent coin, + add_coin(available_coins, *wallet, 10*CENT, CFeeRate(0), 3, true); // a new 10 cent coin sent from one of our own addresses + add_coin(available_coins, *wallet, 20*CENT); // and a mature 20 cent coin // now we have new: 1+10=11 (of which 10 was self-sent), and mature: 2+5+20=27. total = 38 // we can't make 38 cents only if we disallow new coins: - BOOST_CHECK(!KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_standard), 38 * CENT, CENT)); + BOOST_CHECK(!KnapsackSolver(KnapsackGroupOutputs(available_coins.All(), *wallet, filter_standard), 38 * CENT, CENT)); // we can't even make 37 cents if we don't allow new coins even if they're from us - BOOST_CHECK(!KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_standard_extra), 38 * CENT, CENT)); + BOOST_CHECK(!KnapsackSolver(KnapsackGroupOutputs(available_coins.All(), *wallet, filter_standard_extra), 38 * CENT, CENT)); // but we can make 37 cents if we accept new coins from ourself - const auto result3 = KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_standard), 37 * CENT, CENT); + const auto result3 = KnapsackSolver(KnapsackGroupOutputs(available_coins.All(), *wallet, filter_standard), 37 * CENT, CENT); BOOST_CHECK(result3); BOOST_CHECK_EQUAL(result3->GetSelectedValue(), 37 * CENT); // and we can make 38 cents if we accept all new coins - const auto result4 = KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_confirmed), 38 * CENT, CENT); + const auto result4 = KnapsackSolver(KnapsackGroupOutputs(available_coins.All(), *wallet, filter_confirmed), 38 * CENT, CENT); BOOST_CHECK(result4); BOOST_CHECK_EQUAL(result4->GetSelectedValue(), 38 * CENT); // try making 34 cents from 1,2,5,10,20 - we can't do it exactly - const auto result5 = KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_confirmed), 34 * CENT, CENT); + const auto result5 = KnapsackSolver(KnapsackGroupOutputs(available_coins.All(), *wallet, filter_confirmed), 34 * CENT, CENT); BOOST_CHECK(result5); BOOST_CHECK_EQUAL(result5->GetSelectedValue(), 35 * CENT); // but 35 cents is closest BOOST_CHECK_EQUAL(result5->GetInputSet().size(), 3U); // the best should be 20+10+5. it's incredibly unlikely the 1 or 2 got included (but possible) // when we try making 7 cents, the smaller coins (1,2,5) are enough. We should see just 2+5 - const auto result6 = KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_confirmed), 7 * CENT, CENT); + const auto result6 = KnapsackSolver(KnapsackGroupOutputs(available_coins.All(), *wallet, filter_confirmed), 7 * CENT, CENT); BOOST_CHECK(result6); BOOST_CHECK_EQUAL(result6->GetSelectedValue(), 7 * CENT); BOOST_CHECK_EQUAL(result6->GetInputSet().size(), 2U); // when we try making 8 cents, the smaller coins (1,2,5) are exactly enough. - const auto result7 = KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_confirmed), 8 * CENT, CENT); + const auto result7 = KnapsackSolver(KnapsackGroupOutputs(available_coins.All(), *wallet, filter_confirmed), 8 * CENT, CENT); BOOST_CHECK(result7); BOOST_CHECK(result7->GetSelectedValue() == 8 * CENT); BOOST_CHECK_EQUAL(result7->GetInputSet().size(), 3U); // when we try making 9 cents, no subset of smaller coins is enough, and we get the next bigger coin (10) - const auto result8 = KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_confirmed), 9 * CENT, CENT); + const auto result8 = KnapsackSolver(KnapsackGroupOutputs(available_coins.All(), *wallet, filter_confirmed), 9 * CENT, CENT); BOOST_CHECK(result8); BOOST_CHECK_EQUAL(result8->GetSelectedValue(), 10 * CENT); BOOST_CHECK_EQUAL(result8->GetInputSet().size(), 1U); // now clear out the wallet and start again to test choosing between subsets of smaller coins and the next biggest coin - coins.clear(); + available_coins.Clear(); - add_coin(coins, *wallet, 6*CENT); - add_coin(coins, *wallet, 7*CENT); - add_coin(coins, *wallet, 8*CENT); - add_coin(coins, *wallet, 20*CENT); - add_coin(coins, *wallet, 30*CENT); // now we have 6+7+8+20+30 = 71 cents total + add_coin(available_coins, *wallet, 6*CENT); + add_coin(available_coins, *wallet, 7*CENT); + add_coin(available_coins, *wallet, 8*CENT); + add_coin(available_coins, *wallet, 20*CENT); + add_coin(available_coins, *wallet, 30*CENT); // now we have 6+7+8+20+30 = 71 cents total // check that we have 71 and not 72 - const auto result9 = KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_confirmed), 71 * CENT, CENT); + const auto result9 = KnapsackSolver(KnapsackGroupOutputs(available_coins.All(), *wallet, filter_confirmed), 71 * CENT, CENT); BOOST_CHECK(result9); - BOOST_CHECK(!KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_confirmed), 72 * CENT, CENT)); + BOOST_CHECK(!KnapsackSolver(KnapsackGroupOutputs(available_coins.All(), *wallet, filter_confirmed), 72 * CENT, CENT)); // now try making 16 cents. the best smaller coins can do is 6+7+8 = 21; not as good at the next biggest coin, 20 - const auto result10 = KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_confirmed), 16 * CENT, CENT); + const auto result10 = KnapsackSolver(KnapsackGroupOutputs(available_coins.All(), *wallet, filter_confirmed), 16 * CENT, CENT); BOOST_CHECK(result10); BOOST_CHECK_EQUAL(result10->GetSelectedValue(), 20 * CENT); // we should get 20 in one coin BOOST_CHECK_EQUAL(result10->GetInputSet().size(), 1U); - add_coin(coins, *wallet, 5*CENT); // now we have 5+6+7+8+20+30 = 75 cents total + add_coin(available_coins, *wallet, 5*CENT); // now we have 5+6+7+8+20+30 = 75 cents total // now if we try making 16 cents again, the smaller coins can make 5+6+7 = 18 cents, better than the next biggest coin, 20 - const auto result11 = KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_confirmed), 16 * CENT, CENT); + const auto result11 = KnapsackSolver(KnapsackGroupOutputs(available_coins.All(), *wallet, filter_confirmed), 16 * CENT, CENT); BOOST_CHECK(result11); BOOST_CHECK_EQUAL(result11->GetSelectedValue(), 18 * CENT); // we should get 18 in 3 coins BOOST_CHECK_EQUAL(result11->GetInputSet().size(), 3U); - add_coin(coins, *wallet, 18*CENT); // now we have 5+6+7+8+18+20+30 + add_coin(available_coins, *wallet, 18*CENT); // now we have 5+6+7+8+18+20+30 // and now if we try making 16 cents again, the smaller coins can make 5+6+7 = 18 cents, the same as the next biggest coin, 18 - const auto result12 = KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_confirmed), 16 * CENT, CENT); + const auto result12 = KnapsackSolver(KnapsackGroupOutputs(available_coins.All(), *wallet, filter_confirmed), 16 * CENT, CENT); BOOST_CHECK(result12); BOOST_CHECK_EQUAL(result12->GetSelectedValue(), 18 * CENT); // we should get 18 in 1 coin BOOST_CHECK_EQUAL(result12->GetInputSet().size(), 1U); // because in the event of a tie, the biggest coin wins // now try making 11 cents. we should get 5+6 - const auto result13 = KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_confirmed), 11 * CENT, CENT); + const auto result13 = KnapsackSolver(KnapsackGroupOutputs(available_coins.All(), *wallet, filter_confirmed), 11 * CENT, CENT); BOOST_CHECK(result13); BOOST_CHECK_EQUAL(result13->GetSelectedValue(), 11 * CENT); BOOST_CHECK_EQUAL(result13->GetInputSet().size(), 2U); // check that the smallest bigger coin is used - add_coin(coins, *wallet, 1*COIN); - add_coin(coins, *wallet, 2*COIN); - add_coin(coins, *wallet, 3*COIN); - add_coin(coins, *wallet, 4*COIN); // now we have 5+6+7+8+18+20+30+100+200+300+400 = 1094 cents - const auto result14 = KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_confirmed), 95 * CENT, CENT); + add_coin(available_coins, *wallet, 1*COIN); + add_coin(available_coins, *wallet, 2*COIN); + add_coin(available_coins, *wallet, 3*COIN); + add_coin(available_coins, *wallet, 4*COIN); // now we have 5+6+7+8+18+20+30+100+200+300+400 = 1094 cents + const auto result14 = KnapsackSolver(KnapsackGroupOutputs(available_coins.All(), *wallet, filter_confirmed), 95 * CENT, CENT); BOOST_CHECK(result14); BOOST_CHECK_EQUAL(result14->GetSelectedValue(), 1 * COIN); // we should get 1 BTC in 1 coin BOOST_CHECK_EQUAL(result14->GetInputSet().size(), 1U); - const auto result15 = KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_confirmed), 195 * CENT, CENT); + const auto result15 = KnapsackSolver(KnapsackGroupOutputs(available_coins.All(), *wallet, filter_confirmed), 195 * CENT, CENT); BOOST_CHECK(result15); BOOST_CHECK_EQUAL(result15->GetSelectedValue(), 2 * COIN); // we should get 2 BTC in 1 coin BOOST_CHECK_EQUAL(result15->GetInputSet().size(), 1U); // empty the wallet and start again, now with fractions of a cent, to test small change avoidance - coins.clear(); - add_coin(coins, *wallet, CENT * 1 / 10); - add_coin(coins, *wallet, CENT * 2 / 10); - add_coin(coins, *wallet, CENT * 3 / 10); - add_coin(coins, *wallet, CENT * 4 / 10); - add_coin(coins, *wallet, CENT * 5 / 10); + available_coins.Clear(); + add_coin(available_coins, *wallet, CENT * 1 / 10); + add_coin(available_coins, *wallet, CENT * 2 / 10); + add_coin(available_coins, *wallet, CENT * 3 / 10); + add_coin(available_coins, *wallet, CENT * 4 / 10); + add_coin(available_coins, *wallet, CENT * 5 / 10); // try making 1 * CENT from the 1.5 * CENT // we'll get change smaller than CENT whatever happens, so can expect CENT exactly - const auto result16 = KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_confirmed), CENT, CENT); + const auto result16 = KnapsackSolver(KnapsackGroupOutputs(available_coins.All(), *wallet, filter_confirmed), CENT, CENT); BOOST_CHECK(result16); BOOST_CHECK_EQUAL(result16->GetSelectedValue(), CENT); // but if we add a bigger coin, small change is avoided - add_coin(coins, *wallet, 1111*CENT); + add_coin(available_coins, *wallet, 1111*CENT); // try making 1 from 0.1 + 0.2 + 0.3 + 0.4 + 0.5 + 1111 = 1112.5 - const auto result17 = KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_confirmed), 1 * CENT, CENT); + const auto result17 = KnapsackSolver(KnapsackGroupOutputs(available_coins.All(), *wallet, filter_confirmed), 1 * CENT, CENT); BOOST_CHECK(result17); BOOST_CHECK_EQUAL(result17->GetSelectedValue(), 1 * CENT); // we should get the exact amount // if we add more small coins: - add_coin(coins, *wallet, CENT * 6 / 10); - add_coin(coins, *wallet, CENT * 7 / 10); + add_coin(available_coins, *wallet, CENT * 6 / 10); + add_coin(available_coins, *wallet, CENT * 7 / 10); // and try again to make 1.0 * CENT - const auto result18 = KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_confirmed), 1 * CENT, CENT); + const auto result18 = KnapsackSolver(KnapsackGroupOutputs(available_coins.All(), *wallet, filter_confirmed), 1 * CENT, CENT); BOOST_CHECK(result18); BOOST_CHECK_EQUAL(result18->GetSelectedValue(), 1 * CENT); // we should get the exact amount // run the 'mtgox' test (see https://blockexplorer.com/tx/29a3efd3ef04f9153d47a990bd7b048a4b2d213daaa5fb8ed670fb85f13bdbcf) // they tried to consolidate 10 50k coins into one 500k coin, and ended up with 50k in change - coins.clear(); + available_coins.Clear(); for (int j = 0; j < 20; j++) - add_coin(coins, *wallet, 50000 * COIN); + add_coin(available_coins, *wallet, 50000 * COIN); - const auto result19 = KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_confirmed), 500000 * COIN, CENT); + const auto result19 = KnapsackSolver(KnapsackGroupOutputs(available_coins.All(), *wallet, filter_confirmed), 500000 * COIN, CENT); BOOST_CHECK(result19); BOOST_CHECK_EQUAL(result19->GetSelectedValue(), 500000 * COIN); // we should get the exact amount BOOST_CHECK_EQUAL(result19->GetInputSet().size(), 10U); // in ten coins @@ -587,41 +588,41 @@ BOOST_AUTO_TEST_CASE(knapsack_solver_test) // we need to try finding an exact subset anyway // sometimes it will fail, and so we use the next biggest coin: - coins.clear(); - add_coin(coins, *wallet, CENT * 5 / 10); - add_coin(coins, *wallet, CENT * 6 / 10); - add_coin(coins, *wallet, CENT * 7 / 10); - add_coin(coins, *wallet, 1111 * CENT); - const auto result20 = KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_confirmed), 1 * CENT, CENT); + available_coins.Clear(); + add_coin(available_coins, *wallet, CENT * 5 / 10); + add_coin(available_coins, *wallet, CENT * 6 / 10); + add_coin(available_coins, *wallet, CENT * 7 / 10); + add_coin(available_coins, *wallet, 1111 * CENT); + const auto result20 = KnapsackSolver(KnapsackGroupOutputs(available_coins.All(), *wallet, filter_confirmed), 1 * CENT, CENT); BOOST_CHECK(result20); BOOST_CHECK_EQUAL(result20->GetSelectedValue(), 1111 * CENT); // we get the bigger coin BOOST_CHECK_EQUAL(result20->GetInputSet().size(), 1U); // but sometimes it's possible, and we use an exact subset (0.4 + 0.6 = 1.0) - coins.clear(); - add_coin(coins, *wallet, CENT * 4 / 10); - add_coin(coins, *wallet, CENT * 6 / 10); - add_coin(coins, *wallet, CENT * 8 / 10); - add_coin(coins, *wallet, 1111 * CENT); - const auto result21 = KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_confirmed), CENT, CENT); + available_coins.Clear(); + add_coin(available_coins, *wallet, CENT * 4 / 10); + add_coin(available_coins, *wallet, CENT * 6 / 10); + add_coin(available_coins, *wallet, CENT * 8 / 10); + add_coin(available_coins, *wallet, 1111 * CENT); + const auto result21 = KnapsackSolver(KnapsackGroupOutputs(available_coins.All(), *wallet, filter_confirmed), CENT, CENT); BOOST_CHECK(result21); BOOST_CHECK_EQUAL(result21->GetSelectedValue(), CENT); // we should get the exact amount BOOST_CHECK_EQUAL(result21->GetInputSet().size(), 2U); // in two coins 0.4+0.6 // test avoiding small change - coins.clear(); - add_coin(coins, *wallet, CENT * 5 / 100); - add_coin(coins, *wallet, CENT * 1); - add_coin(coins, *wallet, CENT * 100); + available_coins.Clear(); + add_coin(available_coins, *wallet, CENT * 5 / 100); + add_coin(available_coins, *wallet, CENT * 1); + add_coin(available_coins, *wallet, CENT * 100); // trying to make 100.01 from these three coins - const auto result22 = KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_confirmed), CENT * 10001 / 100, CENT); + const auto result22 = KnapsackSolver(KnapsackGroupOutputs(available_coins.All(), *wallet, filter_confirmed), CENT * 10001 / 100, CENT); BOOST_CHECK(result22); BOOST_CHECK_EQUAL(result22->GetSelectedValue(), CENT * 10105 / 100); // we should get all coins BOOST_CHECK_EQUAL(result22->GetInputSet().size(), 3U); // but if we try to make 99.9, we should take the bigger of the two small coins to avoid small change - const auto result23 = KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_confirmed), CENT * 9990 / 100, CENT); + const auto result23 = KnapsackSolver(KnapsackGroupOutputs(available_coins.All(), *wallet, filter_confirmed), CENT * 9990 / 100, CENT); BOOST_CHECK(result23); BOOST_CHECK_EQUAL(result23->GetSelectedValue(), 101 * CENT); BOOST_CHECK_EQUAL(result23->GetInputSet().size(), 2U); @@ -629,14 +630,14 @@ BOOST_AUTO_TEST_CASE(knapsack_solver_test) // test with many inputs for (CAmount amt=1500; amt < COIN; amt*=10) { - coins.clear(); + available_coins.Clear(); // Create 676 inputs (= (old MAX_STANDARD_TX_SIZE == 100000) / 148 bytes per input) for (uint16_t j = 0; j < 676; j++) - add_coin(coins, *wallet, amt); + add_coin(available_coins, *wallet, amt); // We only create the wallet once to save time, but we still run the coin selection RUN_TESTS times. for (int i = 0; i < RUN_TESTS; i++) { - const auto result24 = KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_confirmed), 2000, CENT); + const auto result24 = KnapsackSolver(KnapsackGroupOutputs(available_coins.All(), *wallet, filter_confirmed), 2000, CENT); BOOST_CHECK(result24); if (amt - 2000 < CENT) { @@ -655,17 +656,17 @@ BOOST_AUTO_TEST_CASE(knapsack_solver_test) // test randomness { - coins.clear(); + available_coins.Clear(); for (int i2 = 0; i2 < 100; i2++) - add_coin(coins, *wallet, COIN); + add_coin(available_coins, *wallet, COIN); // Again, we only create the wallet once to save time, but we still run the coin selection RUN_TESTS times. for (int i = 0; i < RUN_TESTS; i++) { // picking 50 from 100 coins doesn't depend on the shuffle, // but does depend on randomness in the stochastic approximation code - const auto result25 = KnapsackSolver(GroupCoins(coins), 50 * COIN, CENT); + const auto result25 = KnapsackSolver(GroupCoins(available_coins.All()), 50 * COIN, CENT); BOOST_CHECK(result25); - const auto result26 = KnapsackSolver(GroupCoins(coins), 50 * COIN, CENT); + const auto result26 = KnapsackSolver(GroupCoins(available_coins.All()), 50 * COIN, CENT); BOOST_CHECK(result26); BOOST_CHECK(!EqualResult(*result25, *result26)); @@ -676,9 +677,9 @@ BOOST_AUTO_TEST_CASE(knapsack_solver_test) // When choosing 1 from 100 identical coins, 1% of the time, this test will choose the same coin twice // which will cause it to fail. // To avoid that issue, run the test RANDOM_REPEATS times and only complain if all of them fail - const auto result27 = KnapsackSolver(GroupCoins(coins), COIN, CENT); + const auto result27 = KnapsackSolver(GroupCoins(available_coins.All()), COIN, CENT); BOOST_CHECK(result27); - const auto result28 = KnapsackSolver(GroupCoins(coins), COIN, CENT); + const auto result28 = KnapsackSolver(GroupCoins(available_coins.All()), COIN, CENT); BOOST_CHECK(result28); if (EqualResult(*result27, *result28)) fails++; @@ -689,19 +690,19 @@ BOOST_AUTO_TEST_CASE(knapsack_solver_test) // add 75 cents in small change. not enough to make 90 cents, // then try making 90 cents. there are multiple competing "smallest bigger" coins, // one of which should be picked at random - add_coin(coins, *wallet, 5 * CENT); - add_coin(coins, *wallet, 10 * CENT); - add_coin(coins, *wallet, 15 * CENT); - add_coin(coins, *wallet, 20 * CENT); - add_coin(coins, *wallet, 25 * CENT); + add_coin(available_coins, *wallet, 5 * CENT); + add_coin(available_coins, *wallet, 10 * CENT); + add_coin(available_coins, *wallet, 15 * CENT); + add_coin(available_coins, *wallet, 20 * CENT); + add_coin(available_coins, *wallet, 25 * CENT); for (int i = 0; i < RUN_TESTS; i++) { int fails = 0; for (int j = 0; j < RANDOM_REPEATS; j++) { - const auto result29 = KnapsackSolver(GroupCoins(coins), 90 * CENT, CENT); + const auto result29 = KnapsackSolver(GroupCoins(available_coins.All()), 90 * CENT, CENT); BOOST_CHECK(result29); - const auto result30 = KnapsackSolver(GroupCoins(coins), 90 * CENT, CENT); + const auto result30 = KnapsackSolver(GroupCoins(available_coins.All()), 90 * CENT, CENT); BOOST_CHECK(result30); if (EqualResult(*result29, *result30)) fails++; @@ -720,14 +721,14 @@ BOOST_AUTO_TEST_CASE(ApproximateBestSubset) wallet->SetWalletFlag(WALLET_FLAG_DESCRIPTORS); wallet->SetupDescriptorScriptPubKeyMans(); - std::vector<COutput> coins; + CoinsResult available_coins; // Test vValue sort order for (int i = 0; i < 1000; i++) - add_coin(coins, *wallet, 1000 * COIN); - add_coin(coins, *wallet, 3 * COIN); + add_coin(available_coins, *wallet, 1000 * COIN); + add_coin(available_coins, *wallet, 3 * COIN); - const auto result = KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_standard), 1003 * COIN, CENT, rand); + const auto result = KnapsackSolver(KnapsackGroupOutputs(available_coins.All(), *wallet, filter_standard), 1003 * COIN, CENT, rand); BOOST_CHECK(result); BOOST_CHECK_EQUAL(result->GetSelectedValue(), 1003 * COIN); BOOST_CHECK_EQUAL(result->GetInputSet().size(), 2U); @@ -750,14 +751,14 @@ BOOST_AUTO_TEST_CASE(SelectCoins_test) // Run this test 100 times for (int i = 0; i < 100; ++i) { - std::vector<COutput> coins; + CoinsResult available_coins; CAmount balance{0}; // Make a wallet with 1000 exponentially distributed random inputs for (int j = 0; j < 1000; ++j) { CAmount val = distribution(generator)*10000000; - add_coin(coins, *wallet, val); + add_coin(available_coins, *wallet, val); balance += val; } @@ -779,8 +780,10 @@ BOOST_AUTO_TEST_CASE(SelectCoins_test) /*tx_noinputs_size=*/ 0, /*avoid_partial=*/ false, }; + cs_params.m_cost_of_change = 1; + cs_params.min_viable_change = 1; CCoinControl cc; - const auto result = SelectCoins(*wallet, coins, target, cc, cs_params); + const auto result = SelectCoins(*wallet, available_coins, target, cc, cs_params); BOOST_CHECK(result); BOOST_CHECK_GE(result->GetSelectedValue(), target); } @@ -865,7 +868,23 @@ BOOST_AUTO_TEST_CASE(waste_test) const CAmount new_target{in_amt - fee * 2 - fee_diff * 2}; add_coin(1 * COIN, 1, selection, fee, fee + fee_diff); add_coin(2 * COIN, 2, selection, fee, fee + fee_diff); - BOOST_CHECK_EQUAL(0, GetSelectionWaste(selection, /* change cost */ 0, new_target)); + BOOST_CHECK_EQUAL(0, GetSelectionWaste(selection, /*change_cost=*/ 0, new_target)); + selection.clear(); + + // Negative waste when the long term fee is greater than the current fee and the selected value == target + const CAmount exact_target1{3 * COIN - 2 * fee}; + const CAmount target_waste1{-2 * fee_diff}; // = (2 * fee) - (2 * (fee + fee_diff)) + add_coin(1 * COIN, 1, selection, fee, fee + fee_diff); + add_coin(2 * COIN, 2, selection, fee, fee + fee_diff); + BOOST_CHECK_EQUAL(target_waste1, GetSelectionWaste(selection, /*change_cost=*/ 0, exact_target1)); + selection.clear(); + + // Negative waste when the long term fee is greater than the current fee and change_cost < - (inputs * (fee - long_term_fee)) + const CAmount large_fee_diff{90}; + const CAmount target_waste2{-2 * large_fee_diff + change_cost}; // = (2 * fee) - (2 * (fee + large_fee_diff)) + change_cost + add_coin(1 * COIN, 1, selection, fee, fee + large_fee_diff); + add_coin(2 * COIN, 2, selection, fee, fee + large_fee_diff); + BOOST_CHECK_EQUAL(target_waste2, GetSelectionWaste(selection, change_cost, target)); } BOOST_AUTO_TEST_CASE(effective_value_test) @@ -903,5 +922,52 @@ BOOST_AUTO_TEST_CASE(effective_value_test) BOOST_CHECK_EQUAL(output5.GetEffectiveValue(), nValue); // The effective value should be equal to the absolute value if input_bytes is -1 } +BOOST_AUTO_TEST_CASE(SelectCoins_effective_value_test) +{ + // Test that the effective value is used to check whether preset inputs provide sufficient funds when subtract_fee_outputs is not used. + // This test creates a coin whose value is higher than the target but whose effective value is lower than the target. + // The coin is selected using coin control, with m_allow_other_inputs = false. SelectCoins should fail due to insufficient funds. + + std::unique_ptr<CWallet> wallet = std::make_unique<CWallet>(m_node.chain.get(), "", m_args, CreateMockWalletDatabase()); + wallet->LoadWallet(); + LOCK(wallet->cs_wallet); + wallet->SetWalletFlag(WALLET_FLAG_DESCRIPTORS); + wallet->SetupDescriptorScriptPubKeyMans(); + + CoinsResult available_coins; + { + std::unique_ptr<CWallet> dummyWallet = std::make_unique<CWallet>(m_node.chain.get(), "dummy", m_args, CreateMockWalletDatabase()); + dummyWallet->LoadWallet(); + LOCK(dummyWallet->cs_wallet); + dummyWallet->SetWalletFlag(WALLET_FLAG_DESCRIPTORS); + dummyWallet->SetupDescriptorScriptPubKeyMans(); + + add_coin(available_coins, *dummyWallet, 100000); // 0.001 BTC + } + + CAmount target{99900}; // 0.000999 BTC + + FastRandomContext rand; + CoinSelectionParams cs_params{ + rand, + /*change_output_size=*/34, + /*change_spend_size=*/148, + /*min_change_target=*/1000, + /*effective_feerate=*/CFeeRate(3000), + /*long_term_feerate=*/CFeeRate(1000), + /*discard_feerate=*/CFeeRate(1000), + /*tx_noinputs_size=*/0, + /*avoid_partial=*/false, + }; + CCoinControl cc; + cc.m_allow_other_inputs = false; + COutput output = available_coins.All().at(0); + cc.SetInputWeight(output.outpoint, 148); + cc.SelectExternal(output.outpoint, output.txout); + + const auto result = SelectCoins(*wallet, available_coins, target, cc, cs_params); + BOOST_CHECK(!result); +} + BOOST_AUTO_TEST_SUITE_END() } // namespace wallet diff --git a/src/wallet/test/feebumper_tests.cpp b/src/wallet/test/feebumper_tests.cpp new file mode 100644 index 0000000000..6add86dc3d --- /dev/null +++ b/src/wallet/test/feebumper_tests.cpp @@ -0,0 +1,54 @@ +// Copyright (c) 2022 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or https://www.opensource.org/licenses/mit-license.php. + +#include <primitives/transaction.h> +#include <script/script.h> +#include <util/strencodings.h> +#include <wallet/feebumper.h> +#include <wallet/test/util.h> +#include <wallet/test/wallet_test_fixture.h> + +#include <boost/test/unit_test.hpp> + +namespace wallet { +namespace feebumper { +BOOST_FIXTURE_TEST_SUITE(feebumper_tests, WalletTestingSetup) + +static void CheckMaxWeightComputation(const std::string& script_str, const std::vector<std::string>& witness_str_stack, const std::string& prevout_script_str, int64_t expected_max_weight) +{ + std::vector script_data(ParseHex(script_str)); + CScript script(script_data.begin(), script_data.end()); + CTxIn input(uint256(), 0, script); + + for (const auto& s : witness_str_stack) { + input.scriptWitness.stack.push_back(ParseHex(s)); + } + + std::vector prevout_script_data(ParseHex(prevout_script_str)); + CScript prevout_script(prevout_script_data.begin(), prevout_script_data.end()); + + int64_t weight = GetTransactionInputWeight(input); + SignatureWeights weights; + SignatureWeightChecker size_checker(weights, DUMMY_CHECKER); + bool script_ok = VerifyScript(input.scriptSig, prevout_script, &input.scriptWitness, STANDARD_SCRIPT_VERIFY_FLAGS, size_checker); + BOOST_CHECK(script_ok); + weight += weights.GetWeightDiffToMax(); + BOOST_CHECK_EQUAL(weight, expected_max_weight); +} + +BOOST_AUTO_TEST_CASE(external_max_weight_test) +{ + // P2PKH + CheckMaxWeightComputation("453042021f03c8957c5ce12940ee6e3333ecc3f633d9a1ac53a55b3ce0351c617fa96abe021f0dccdcce3ef45a63998be9ec748b561baf077b8e862941d0cd5ec08f5afe68012102fccfeb395f0ecd3a77e7bc31c3bc61dc987418b18e395d441057b42ca043f22c", {}, "76a914f60dcfd3392b28adc7662669603641f578eed72d88ac", 593); + // P2SH-P2WPKH + CheckMaxWeightComputation("160014001dca1b22c599b5a56a87c78417ad2ff39552f1", {"3042021f5443c58eaf45f3e5ef46f8516f966b334a7d497cedda4edb2b9fad57c90c3b021f63a77cb56cde848e2e2dd20b487eec2f53101f634193786083f60b4d23a82301", "026cfe86116f161057deb240201d6b82ebd4f161e0200d63dc9aca65a1d6b38bb7"}, "a9147c8ab5ad7708b97ccb6b483d57aba48ee85214df87", 364); + // P2WPKH + CheckMaxWeightComputation("", {"3042021f0f8906f0394979d5b737134773e5b88bf036c7d63542301d600ab677ba5a59021f0e9fe07e62c113045fa1c1532e2914720e8854d189c4f5b8c88f57956b704401", "0359edba11ed1a0568094a6296a16c4d5ee4c8cfe2f5e2e6826871b5ecf8188f79"}, "00149961a78658030cc824af4c54fbf5294bec0cabdd", 272); + // P2WSH HTLC + CheckMaxWeightComputation("", {"3042021f5c4c29e6b686aae5b6d0751e90208592ea96d26bc81d78b0d3871a94a21fa8021f74dc2f971e438ccece8699c8fd15704c41df219ab37b63264f2147d15c34d801", "01", "6321024cf55e52ec8af7866617dc4e7ff8433758e98799906d80e066c6f32033f685f967029000b275210214827893e2dcbe4ad6c20bd743288edad21100404eb7f52ccd6062fd0e7808f268ac"}, "002089e84892873c679b1129edea246e484fd914c2601f776d4f2f4a001eb8059703", 318); +} + +BOOST_AUTO_TEST_SUITE_END() +} // namespace feebumper +} // namespace wallet diff --git a/src/wallet/test/fuzz/coinselection.cpp b/src/wallet/test/fuzz/coinselection.cpp index 3465f2f331..1a0708c43a 100644 --- a/src/wallet/test/fuzz/coinselection.cpp +++ b/src/wallet/test/fuzz/coinselection.cpp @@ -30,7 +30,7 @@ static void GroupCoins(FuzzedDataProvider& fuzzed_data_provider, const std::vect bool valid_outputgroup{false}; for (auto& coin : coins) { output_group.Insert(coin, /*ancestors=*/0, /*descendants=*/0, positive_only); - // If positive_only was specified, nothing may have been inserted, leading to an empty outpout group + // If positive_only was specified, nothing may have been inserted, leading to an empty output group // that would be invalid for the BnB algorithm valid_outputgroup = !positive_only || output_group.GetSelectionAmount() > 0; if (valid_outputgroup && fuzzed_data_provider.ConsumeBool()) { @@ -58,6 +58,8 @@ FUZZ_TARGET(coinselection) coin_params.m_subtract_fee_outputs = subtract_fee_outputs; coin_params.m_long_term_feerate = long_term_fee_rate; coin_params.m_effective_feerate = effective_fee_rate; + coin_params.change_output_size = fuzzed_data_provider.ConsumeIntegralInRange<int>(10, 1000); + coin_params.m_change_fee = effective_fee_rate.GetFee(coin_params.change_output_size); // Create some coins CAmount total_balance{0}; @@ -83,11 +85,11 @@ FUZZ_TARGET(coinselection) const auto result_bnb = SelectCoinsBnB(group_pos, target, cost_of_change); auto result_srd = SelectCoinsSRD(group_pos, target, fast_random_context); - if (result_srd) result_srd->ComputeAndSetWaste(cost_of_change); + if (result_srd) result_srd->ComputeAndSetWaste(cost_of_change, cost_of_change, 0); - CAmount change_target{GenerateChangeTarget(target, fast_random_context)}; + CAmount change_target{GenerateChangeTarget(target, coin_params.m_change_fee, fast_random_context)}; auto result_knapsack = KnapsackSolver(group_all, target, change_target, fast_random_context); - if (result_knapsack) result_knapsack->ComputeAndSetWaste(cost_of_change); + if (result_knapsack) result_knapsack->ComputeAndSetWaste(cost_of_change, cost_of_change, 0); // If the total balance is sufficient for the target and we are not using // effective values, Knapsack should always find a solution. diff --git a/src/wallet/test/fuzz/notifications.cpp b/src/wallet/test/fuzz/notifications.cpp index 9089c8ff46..5e9cd4001b 100644 --- a/src/wallet/test/fuzz/notifications.cpp +++ b/src/wallet/test/fuzz/notifications.cpp @@ -69,15 +69,13 @@ struct FuzzedWallet { CScript GetScriptPubKey(FuzzedDataProvider& fuzzed_data_provider) { auto type{fuzzed_data_provider.PickValueInArray(OUTPUT_TYPES)}; - CTxDestination dest; - bilingual_str error; + util::Result<CTxDestination> op_dest{util::Error{}}; if (fuzzed_data_provider.ConsumeBool()) { - assert(wallet->GetNewDestination(type, "", dest, error)); + op_dest = wallet->GetNewDestination(type, ""); } else { - assert(wallet->GetNewChangeDestination(type, dest, error)); + op_dest = wallet->GetNewChangeDestination(type); } - assert(error.empty()); - return GetScriptForDestination(dest); + return GetScriptForDestination(*Assert(op_dest)); } }; @@ -138,8 +136,13 @@ FUZZ_TARGET_INIT(wallet_notifications, initialize_setup) block.vtx.emplace_back(MakeTransactionRef(tx)); } // Mine block - a.wallet->blockConnected(block, chain.size()); - b.wallet->blockConnected(block, chain.size()); + const uint256& hash = block.GetHash(); + interfaces::BlockInfo info{hash}; + info.prev_hash = &block.hashPrevBlock; + info.height = chain.size(); + info.data = █ + a.wallet->blockConnected(info); + b.wallet->blockConnected(info); // Store the coins for the next block Coins coins_new; for (const auto& tx : block.vtx) { @@ -155,8 +158,13 @@ FUZZ_TARGET_INIT(wallet_notifications, initialize_setup) auto& [coins, block]{chain.back()}; if (block.vtx.empty()) return; // Can only disconnect if the block was submitted first // Disconnect block - a.wallet->blockDisconnected(block, chain.size() - 1); - b.wallet->blockDisconnected(block, chain.size() - 1); + const uint256& hash = block.GetHash(); + interfaces::BlockInfo info{hash}; + info.prev_hash = &block.hashPrevBlock; + info.height = chain.size() - 1; + info.data = █ + a.wallet->blockDisconnected(info); + b.wallet->blockDisconnected(info); chain.pop_back(); }); auto& [coins, first_block]{chain.front()}; diff --git a/src/test/fuzz/parse_iso8601.cpp b/src/wallet/test/fuzz/parse_iso8601.cpp index 0fef9a9a1d..5be248c2fb 100644 --- a/src/test/fuzz/parse_iso8601.cpp +++ b/src/wallet/test/fuzz/parse_iso8601.cpp @@ -5,6 +5,7 @@ #include <test/fuzz/FuzzedDataProvider.h> #include <test/fuzz/fuzz.h> #include <util/time.h> +#include <wallet/rpc/util.h> #include <cassert> #include <cstdint> @@ -20,7 +21,7 @@ FUZZ_TARGET(parse_iso8601) const std::string iso8601_datetime = FormatISO8601DateTime(random_time); (void)FormatISO8601Date(random_time); - const int64_t parsed_time_1 = ParseISO8601DateTime(iso8601_datetime); + const int64_t parsed_time_1 = wallet::ParseISO8601DateTime(iso8601_datetime); if (random_time >= 0) { assert(parsed_time_1 >= 0); if (iso8601_datetime.length() == 20) { @@ -28,6 +29,6 @@ FUZZ_TARGET(parse_iso8601) } } - const int64_t parsed_time_2 = ParseISO8601DateTime(random_string); + const int64_t parsed_time_2 = wallet::ParseISO8601DateTime(random_string); assert(parsed_time_2 >= 0); } diff --git a/src/wallet/test/ismine_tests.cpp b/src/wallet/test/ismine_tests.cpp index dd5cd0af46..68146eb079 100644 --- a/src/wallet/test/ismine_tests.cpp +++ b/src/wallet/test/ismine_tests.cpp @@ -43,11 +43,13 @@ BOOST_AUTO_TEST_CASE(ismine_standard) // Keystore does not have key result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey); BOOST_CHECK_EQUAL(result, ISMINE_NO); + BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->GetScriptPubKeys().count(scriptPubKey) == 0); // Keystore has key BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddKey(keys[0])); result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey); BOOST_CHECK_EQUAL(result, ISMINE_SPENDABLE); + BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->GetScriptPubKeys().count(scriptPubKey) == 1); } // P2PK uncompressed @@ -60,11 +62,13 @@ BOOST_AUTO_TEST_CASE(ismine_standard) // Keystore does not have key result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey); BOOST_CHECK_EQUAL(result, ISMINE_NO); + BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->GetScriptPubKeys().count(scriptPubKey) == 0); // Keystore has key BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddKey(uncompressedKey)); result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey); BOOST_CHECK_EQUAL(result, ISMINE_SPENDABLE); + BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->GetScriptPubKeys().count(scriptPubKey) == 1); } // P2PKH compressed @@ -77,11 +81,13 @@ BOOST_AUTO_TEST_CASE(ismine_standard) // Keystore does not have key result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey); BOOST_CHECK_EQUAL(result, ISMINE_NO); + BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->GetScriptPubKeys().count(scriptPubKey) == 0); // Keystore has key BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddKey(keys[0])); result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey); BOOST_CHECK_EQUAL(result, ISMINE_SPENDABLE); + BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->GetScriptPubKeys().count(scriptPubKey) == 1); } // P2PKH uncompressed @@ -94,11 +100,13 @@ BOOST_AUTO_TEST_CASE(ismine_standard) // Keystore does not have key result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey); BOOST_CHECK_EQUAL(result, ISMINE_NO); + BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->GetScriptPubKeys().count(scriptPubKey) == 0); // Keystore has key BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddKey(uncompressedKey)); result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey); BOOST_CHECK_EQUAL(result, ISMINE_SPENDABLE); + BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->GetScriptPubKeys().count(scriptPubKey) == 1); } // P2SH @@ -113,16 +121,19 @@ BOOST_AUTO_TEST_CASE(ismine_standard) // Keystore does not have redeemScript or key result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey); BOOST_CHECK_EQUAL(result, ISMINE_NO); + BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->GetScriptPubKeys().count(scriptPubKey) == 0); // Keystore has redeemScript but no key BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddCScript(redeemScript)); result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey); BOOST_CHECK_EQUAL(result, ISMINE_NO); + BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->GetScriptPubKeys().count(scriptPubKey) == 0); // Keystore has redeemScript and key BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddKey(keys[0])); result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey); BOOST_CHECK_EQUAL(result, ISMINE_SPENDABLE); + BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->GetScriptPubKeys().count(scriptPubKey) == 1); } // (P2PKH inside) P2SH inside P2SH (invalid) @@ -141,6 +152,7 @@ BOOST_AUTO_TEST_CASE(ismine_standard) BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddKey(keys[0])); result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey); BOOST_CHECK_EQUAL(result, ISMINE_NO); + BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->GetScriptPubKeys().count(scriptPubKey) == 0); } // (P2PKH inside) P2SH inside P2WSH (invalid) @@ -159,6 +171,7 @@ BOOST_AUTO_TEST_CASE(ismine_standard) BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddKey(keys[0])); result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey); BOOST_CHECK_EQUAL(result, ISMINE_NO); + BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->GetScriptPubKeys().count(scriptPubKey) == 0); } // P2WPKH inside P2WSH (invalid) @@ -175,6 +188,7 @@ BOOST_AUTO_TEST_CASE(ismine_standard) BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddKey(keys[0])); result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey); BOOST_CHECK_EQUAL(result, ISMINE_NO); + BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->GetScriptPubKeys().count(scriptPubKey) == 0); } // (P2PKH inside) P2WSH inside P2WSH (invalid) @@ -193,6 +207,7 @@ BOOST_AUTO_TEST_CASE(ismine_standard) BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddKey(keys[0])); result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey); BOOST_CHECK_EQUAL(result, ISMINE_NO); + BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->GetScriptPubKeys().count(scriptPubKey) == 0); } // P2WPKH compressed @@ -208,6 +223,7 @@ BOOST_AUTO_TEST_CASE(ismine_standard) BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddCScript(scriptPubKey)); result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey); BOOST_CHECK_EQUAL(result, ISMINE_SPENDABLE); + BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->GetScriptPubKeys().count(scriptPubKey) == 1); } // P2WPKH uncompressed @@ -222,11 +238,13 @@ BOOST_AUTO_TEST_CASE(ismine_standard) // Keystore has key, but no P2SH redeemScript result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey); BOOST_CHECK_EQUAL(result, ISMINE_NO); + BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->GetScriptPubKeys().count(scriptPubKey) == 0); // Keystore has key and P2SH redeemScript BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddCScript(scriptPubKey)); result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey); BOOST_CHECK_EQUAL(result, ISMINE_NO); + BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->GetScriptPubKeys().count(scriptPubKey) == 0); } // scriptPubKey multisig @@ -240,24 +258,28 @@ BOOST_AUTO_TEST_CASE(ismine_standard) // Keystore does not have any keys result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey); BOOST_CHECK_EQUAL(result, ISMINE_NO); + BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->GetScriptPubKeys().count(scriptPubKey) == 0); // Keystore has 1/2 keys BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddKey(uncompressedKey)); result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey); BOOST_CHECK_EQUAL(result, ISMINE_NO); + BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->GetScriptPubKeys().count(scriptPubKey) == 0); // Keystore has 2/2 keys BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddKey(keys[1])); result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey); BOOST_CHECK_EQUAL(result, ISMINE_NO); + BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->GetScriptPubKeys().count(scriptPubKey) == 0); // Keystore has 2/2 keys and the script BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddCScript(scriptPubKey)); result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey); BOOST_CHECK_EQUAL(result, ISMINE_NO); + BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->GetScriptPubKeys().count(scriptPubKey) == 0); } // P2SH multisig @@ -274,11 +296,13 @@ BOOST_AUTO_TEST_CASE(ismine_standard) // Keystore has no redeemScript result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey); BOOST_CHECK_EQUAL(result, ISMINE_NO); + BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->GetScriptPubKeys().count(scriptPubKey) == 0); // Keystore has redeemScript BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddCScript(redeemScript)); result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey); BOOST_CHECK_EQUAL(result, ISMINE_SPENDABLE); + BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->GetScriptPubKeys().count(scriptPubKey) == 1); } // P2WSH multisig with compressed keys @@ -295,16 +319,19 @@ BOOST_AUTO_TEST_CASE(ismine_standard) // Keystore has keys, but no witnessScript or P2SH redeemScript result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey); BOOST_CHECK_EQUAL(result, ISMINE_NO); + BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->GetScriptPubKeys().count(scriptPubKey) == 0); // Keystore has keys and witnessScript, but no P2SH redeemScript BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddCScript(witnessScript)); result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey); BOOST_CHECK_EQUAL(result, ISMINE_NO); + BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->GetScriptPubKeys().count(scriptPubKey) == 0); // Keystore has keys, witnessScript, P2SH redeemScript BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddCScript(scriptPubKey)); result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey); BOOST_CHECK_EQUAL(result, ISMINE_SPENDABLE); + BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->GetScriptPubKeys().count(scriptPubKey) == 1); } // P2WSH multisig with uncompressed key @@ -321,16 +348,19 @@ BOOST_AUTO_TEST_CASE(ismine_standard) // Keystore has keys, but no witnessScript or P2SH redeemScript result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey); BOOST_CHECK_EQUAL(result, ISMINE_NO); + BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->GetScriptPubKeys().count(scriptPubKey) == 0); // Keystore has keys and witnessScript, but no P2SH redeemScript BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddCScript(witnessScript)); result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey); BOOST_CHECK_EQUAL(result, ISMINE_NO); + BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->GetScriptPubKeys().count(scriptPubKey) == 0); // Keystore has keys, witnessScript, P2SH redeemScript BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddCScript(scriptPubKey)); result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey); BOOST_CHECK_EQUAL(result, ISMINE_NO); + BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->GetScriptPubKeys().count(scriptPubKey) == 0); } // P2WSH multisig wrapped in P2SH @@ -346,18 +376,21 @@ BOOST_AUTO_TEST_CASE(ismine_standard) // Keystore has no witnessScript, P2SH redeemScript, or keys result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey); BOOST_CHECK_EQUAL(result, ISMINE_NO); + BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->GetScriptPubKeys().count(scriptPubKey) == 0); // Keystore has witnessScript and P2SH redeemScript, but no keys BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddCScript(redeemScript)); BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddCScript(witnessScript)); result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey); BOOST_CHECK_EQUAL(result, ISMINE_NO); + BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->GetScriptPubKeys().count(scriptPubKey) == 0); // Keystore has keys, witnessScript, P2SH redeemScript BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddKey(keys[0])); BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddKey(keys[1])); result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey); BOOST_CHECK_EQUAL(result, ISMINE_SPENDABLE); + BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->GetScriptPubKeys().count(scriptPubKey) == 1); } // OP_RETURN @@ -372,6 +405,7 @@ BOOST_AUTO_TEST_CASE(ismine_standard) result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey); BOOST_CHECK_EQUAL(result, ISMINE_NO); + BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->GetScriptPubKeys().count(scriptPubKey) == 0); } // witness unspendable @@ -386,6 +420,7 @@ BOOST_AUTO_TEST_CASE(ismine_standard) result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey); BOOST_CHECK_EQUAL(result, ISMINE_NO); + BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->GetScriptPubKeys().count(scriptPubKey) == 0); } // witness unknown @@ -400,6 +435,7 @@ BOOST_AUTO_TEST_CASE(ismine_standard) result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey); BOOST_CHECK_EQUAL(result, ISMINE_NO); + BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->GetScriptPubKeys().count(scriptPubKey) == 0); } // Nonstandard @@ -414,6 +450,7 @@ BOOST_AUTO_TEST_CASE(ismine_standard) result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey); BOOST_CHECK_EQUAL(result, ISMINE_NO); + BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->GetScriptPubKeys().count(scriptPubKey) == 0); } } diff --git a/src/wallet/test/rpc_util_tests.cpp b/src/wallet/test/rpc_util_tests.cpp new file mode 100644 index 0000000000..32f6f5ab46 --- /dev/null +++ b/src/wallet/test/rpc_util_tests.cpp @@ -0,0 +1,24 @@ +// Copyright (c) 2022 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include <wallet/rpc/util.h> + +#include <boost/test/unit_test.hpp> + +namespace wallet { + +BOOST_AUTO_TEST_SUITE(wallet_util_tests) + +BOOST_AUTO_TEST_CASE(util_ParseISO8601DateTime) +{ + BOOST_CHECK_EQUAL(ParseISO8601DateTime("1970-01-01T00:00:00Z"), 0); + BOOST_CHECK_EQUAL(ParseISO8601DateTime("1960-01-01T00:00:00Z"), 0); + BOOST_CHECK_EQUAL(ParseISO8601DateTime("2000-01-01T00:00:01Z"), 946684801); + BOOST_CHECK_EQUAL(ParseISO8601DateTime("2011-09-30T23:36:17Z"), 1317425777); + BOOST_CHECK_EQUAL(ParseISO8601DateTime("2100-12-31T23:59:59Z"), 4133980799); +} + +BOOST_AUTO_TEST_SUITE_END() + +} // namespace wallet diff --git a/src/wallet/test/spend_tests.cpp b/src/wallet/test/spend_tests.cpp index bdc148afb4..a75b014870 100644 --- a/src/wallet/test/spend_tests.cpp +++ b/src/wallet/test/spend_tests.cpp @@ -18,7 +18,7 @@ BOOST_FIXTURE_TEST_SUITE(spend_tests, WalletTestingSetup) BOOST_FIXTURE_TEST_CASE(SubtractFee, TestChain100Setup) { CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey())); - auto wallet = CreateSyncedWallet(*m_node.chain, m_node.chainman->ActiveChain(), m_args, coinbaseKey); + auto wallet = CreateSyncedWallet(*m_node.chain, WITH_LOCK(Assert(m_node.chainman)->GetMutex(), return m_node.chainman->ActiveChain()), m_args, coinbaseKey); // Check that a subtract-from-recipient transaction slightly less than the // coinbase input amount does not create a change output (because it would @@ -28,19 +28,18 @@ BOOST_FIXTURE_TEST_CASE(SubtractFee, TestChain100Setup) auto check_tx = [&wallet](CAmount leftover_input_amount) { CRecipient recipient{GetScriptForRawPubKey({}), 50 * COIN - leftover_input_amount, true /* subtract fee */}; constexpr int RANDOM_CHANGE_POSITION = -1; - bilingual_str error; CCoinControl coin_control; coin_control.m_feerate.emplace(10000); coin_control.fOverrideFeeRate = true; // We need to use a change type with high cost of change so that the leftover amount will be dropped to fee instead of added as a change output coin_control.m_change_type = OutputType::LEGACY; - FeeCalculation fee_calc; - std::optional<CreatedTransactionResult> txr = CreateTransaction(*wallet, {recipient}, RANDOM_CHANGE_POSITION, error, coin_control, fee_calc); - BOOST_CHECK(txr.has_value()); - BOOST_CHECK_EQUAL(txr->tx->vout.size(), 1); - BOOST_CHECK_EQUAL(txr->tx->vout[0].nValue, recipient.nAmount + leftover_input_amount - txr->fee); - BOOST_CHECK_GT(txr->fee, 0); - return txr->fee; + auto res = CreateTransaction(*wallet, {recipient}, RANDOM_CHANGE_POSITION, coin_control); + BOOST_CHECK(res); + const auto& txr = *res; + BOOST_CHECK_EQUAL(txr.tx->vout.size(), 1); + BOOST_CHECK_EQUAL(txr.tx->vout[0].nValue, recipient.nAmount + leftover_input_amount - txr.fee); + BOOST_CHECK_GT(txr.fee, 0); + return txr.fee; }; // Send full input amount to recipient, check that only nonzero fee is diff --git a/src/wallet/test/util.cpp b/src/wallet/test/util.cpp index aa3121511d..ab72721f9d 100644 --- a/src/wallet/test/util.cpp +++ b/src/wallet/test/util.cpp @@ -38,7 +38,7 @@ std::unique_ptr<CWallet> CreateSyncedWallet(interfaces::Chain& chain, CChain& cc } WalletRescanReserver reserver(*wallet); reserver.reserve(); - CWallet::ScanResult result = wallet->ScanForWalletTransactions(cchain.Genesis()->GetBlockHash(), 0 /* start_height */, {} /* max_height */, reserver, false /* update */); + CWallet::ScanResult result = wallet->ScanForWalletTransactions(cchain.Genesis()->GetBlockHash(), /*start_height=*/0, /*max_height=*/{}, reserver, /*fUpdate=*/false, /*save_progress=*/false); BOOST_CHECK_EQUAL(result.status, CWallet::ScanResult::SUCCESS); BOOST_CHECK_EQUAL(result.last_scanned_block, cchain.Tip()->GetBlockHash()); BOOST_CHECK_EQUAL(*result.last_scanned_height, cchain.Height()); diff --git a/src/wallet/test/wallet_tests.cpp b/src/wallet/test/wallet_tests.cpp index 70863f5464..60fdbde71b 100644 --- a/src/wallet/test/wallet_tests.cpp +++ b/src/wallet/test/wallet_tests.cpp @@ -4,7 +4,6 @@ #include <wallet/wallet.h> -#include <any> #include <future> #include <memory> #include <stdint.h> @@ -13,7 +12,6 @@ #include <interfaces/chain.h> #include <key_io.h> #include <node/blockstorage.h> -#include <node/context.h> #include <policy/policy.h> #include <rpc/server.h> #include <test/util/logging.h> @@ -55,9 +53,6 @@ static const std::shared_ptr<CWallet> TestLoadWallet(WalletContext& context) auto database = MakeWalletDatabase("", options, status, error); auto wallet = CWallet::Create(context, "", std::move(database), options.create_flags, error, warnings); NotifyWalletLoaded(context, wallet); - if (context.chain) { - wallet->postInitProcess(); - } return wallet; } @@ -96,23 +91,24 @@ static void AddKey(CWallet& wallet, const CKey& key) BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup) { // Cap last block file size, and mine new block in a new block file. - CBlockIndex* oldTip = m_node.chainman->ActiveChain().Tip(); + CBlockIndex* oldTip = WITH_LOCK(Assert(m_node.chainman)->GetMutex(), return m_node.chainman->ActiveChain().Tip()); WITH_LOCK(::cs_main, m_node.chainman->m_blockman.GetBlockFileInfo(oldTip->GetBlockPos().nFile)->nSize = MAX_BLOCKFILE_SIZE); CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey())); - CBlockIndex* newTip = m_node.chainman->ActiveChain().Tip(); + CBlockIndex* newTip = WITH_LOCK(Assert(m_node.chainman)->GetMutex(), return m_node.chainman->ActiveChain().Tip()); // Verify ScanForWalletTransactions fails to read an unknown start block. { CWallet wallet(m_node.chain.get(), "", m_args, CreateDummyWalletDatabase()); { LOCK(wallet.cs_wallet); + LOCK(Assert(m_node.chainman)->GetMutex()); wallet.SetWalletFlag(WALLET_FLAG_DESCRIPTORS); wallet.SetLastBlockProcessed(m_node.chainman->ActiveChain().Height(), m_node.chainman->ActiveChain().Tip()->GetBlockHash()); } AddKey(wallet, coinbaseKey); WalletRescanReserver reserver(wallet); reserver.reserve(); - CWallet::ScanResult result = wallet.ScanForWalletTransactions({} /* start_block */, 0 /* start_height */, {} /* max_height */, reserver, false /* update */); + CWallet::ScanResult result = wallet.ScanForWalletTransactions(/*start_block=*/{}, /*start_height=*/0, /*max_height=*/{}, reserver, /*fUpdate=*/false, /*save_progress=*/false); BOOST_CHECK_EQUAL(result.status, CWallet::ScanResult::FAILURE); BOOST_CHECK(result.last_failed_block.IsNull()); BOOST_CHECK(result.last_scanned_block.IsNull()); @@ -123,21 +119,37 @@ BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup) // Verify ScanForWalletTransactions picks up transactions in both the old // and new block files. { - CWallet wallet(m_node.chain.get(), "", m_args, CreateDummyWalletDatabase()); + CWallet wallet(m_node.chain.get(), "", m_args, CreateMockWalletDatabase()); { LOCK(wallet.cs_wallet); + LOCK(Assert(m_node.chainman)->GetMutex()); wallet.SetWalletFlag(WALLET_FLAG_DESCRIPTORS); wallet.SetLastBlockProcessed(m_node.chainman->ActiveChain().Height(), m_node.chainman->ActiveChain().Tip()->GetBlockHash()); } AddKey(wallet, coinbaseKey); WalletRescanReserver reserver(wallet); + std::chrono::steady_clock::time_point fake_time; + reserver.setNow([&] { fake_time += 60s; return fake_time; }); reserver.reserve(); - CWallet::ScanResult result = wallet.ScanForWalletTransactions(oldTip->GetBlockHash(), oldTip->nHeight, {} /* max_height */, reserver, false /* update */); + + { + CBlockLocator locator; + BOOST_CHECK(!WalletBatch{wallet.GetDatabase()}.ReadBestBlock(locator)); + BOOST_CHECK(locator.IsNull()); + } + + CWallet::ScanResult result = wallet.ScanForWalletTransactions(/*start_block=*/oldTip->GetBlockHash(), /*start_height=*/oldTip->nHeight, /*max_height=*/{}, reserver, /*fUpdate=*/false, /*save_progress=*/true); BOOST_CHECK_EQUAL(result.status, CWallet::ScanResult::SUCCESS); BOOST_CHECK(result.last_failed_block.IsNull()); BOOST_CHECK_EQUAL(result.last_scanned_block, newTip->GetBlockHash()); BOOST_CHECK_EQUAL(*result.last_scanned_height, newTip->nHeight); BOOST_CHECK_EQUAL(GetBalance(wallet).m_mine_immature, 100 * COIN); + + { + CBlockLocator locator; + BOOST_CHECK(WalletBatch{wallet.GetDatabase()}.ReadBestBlock(locator)); + BOOST_CHECK(!locator.IsNull()); + } } // Prune the older block file. @@ -155,13 +167,14 @@ BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup) CWallet wallet(m_node.chain.get(), "", m_args, CreateDummyWalletDatabase()); { LOCK(wallet.cs_wallet); + LOCK(Assert(m_node.chainman)->GetMutex()); wallet.SetWalletFlag(WALLET_FLAG_DESCRIPTORS); wallet.SetLastBlockProcessed(m_node.chainman->ActiveChain().Height(), m_node.chainman->ActiveChain().Tip()->GetBlockHash()); } AddKey(wallet, coinbaseKey); WalletRescanReserver reserver(wallet); reserver.reserve(); - CWallet::ScanResult result = wallet.ScanForWalletTransactions(oldTip->GetBlockHash(), oldTip->nHeight, {} /* max_height */, reserver, false /* update */); + CWallet::ScanResult result = wallet.ScanForWalletTransactions(/*start_block=*/oldTip->GetBlockHash(), /*start_height=*/oldTip->nHeight, /*max_height=*/{}, reserver, /*fUpdate=*/false, /*save_progress=*/false); BOOST_CHECK_EQUAL(result.status, CWallet::ScanResult::FAILURE); BOOST_CHECK_EQUAL(result.last_failed_block, oldTip->GetBlockHash()); BOOST_CHECK_EQUAL(result.last_scanned_block, newTip->GetBlockHash()); @@ -182,13 +195,14 @@ BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup) CWallet wallet(m_node.chain.get(), "", m_args, CreateDummyWalletDatabase()); { LOCK(wallet.cs_wallet); + LOCK(Assert(m_node.chainman)->GetMutex()); wallet.SetWalletFlag(WALLET_FLAG_DESCRIPTORS); wallet.SetLastBlockProcessed(m_node.chainman->ActiveChain().Height(), m_node.chainman->ActiveChain().Tip()->GetBlockHash()); } AddKey(wallet, coinbaseKey); WalletRescanReserver reserver(wallet); reserver.reserve(); - CWallet::ScanResult result = wallet.ScanForWalletTransactions(oldTip->GetBlockHash(), oldTip->nHeight, {} /* max_height */, reserver, false /* update */); + CWallet::ScanResult result = wallet.ScanForWalletTransactions(/*start_block=*/oldTip->GetBlockHash(), /*start_height=*/oldTip->nHeight, /*max_height=*/{}, reserver, /*fUpdate=*/false, /*save_progress=*/false); BOOST_CHECK_EQUAL(result.status, CWallet::ScanResult::FAILURE); BOOST_CHECK_EQUAL(result.last_failed_block, newTip->GetBlockHash()); BOOST_CHECK(result.last_scanned_block.IsNull()); @@ -200,10 +214,10 @@ BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup) BOOST_FIXTURE_TEST_CASE(importmulti_rescan, TestChain100Setup) { // Cap last block file size, and mine new block in a new block file. - CBlockIndex* oldTip = m_node.chainman->ActiveChain().Tip(); + CBlockIndex* oldTip = WITH_LOCK(Assert(m_node.chainman)->GetMutex(), return m_node.chainman->ActiveChain().Tip()); WITH_LOCK(::cs_main, m_node.chainman->m_blockman.GetBlockFileInfo(oldTip->GetBlockPos().nFile)->nSize = MAX_BLOCKFILE_SIZE); CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey())); - CBlockIndex* newTip = m_node.chainman->ActiveChain().Tip(); + CBlockIndex* newTip = WITH_LOCK(Assert(m_node.chainman)->GetMutex(), return m_node.chainman->ActiveChain().Tip()); // Prune the older block file. int file_number; @@ -267,7 +281,7 @@ BOOST_FIXTURE_TEST_CASE(importwallet_rescan, TestChain100Setup) { // Create two blocks with same timestamp to verify that importwallet rescan // will pick up both blocks, not just the first. - const int64_t BLOCK_TIME = m_node.chainman->ActiveChain().Tip()->GetBlockTimeMax() + 5; + const int64_t BLOCK_TIME = WITH_LOCK(Assert(m_node.chainman)->GetMutex(), return m_node.chainman->ActiveChain().Tip()->GetBlockTimeMax() + 5); SetMockTime(BLOCK_TIME); m_coinbase_txns.emplace_back(CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey())).vtx[0]); m_coinbase_txns.emplace_back(CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey())).vtx[0]); @@ -292,6 +306,7 @@ BOOST_FIXTURE_TEST_CASE(importwallet_rescan, TestChain100Setup) spk_man->AddKeyPubKey(coinbaseKey, coinbaseKey.GetPubKey()); AddWallet(context, wallet); + LOCK(Assert(m_node.chainman)->GetMutex()); wallet->SetLastBlockProcessed(m_node.chainman->ActiveChain().Height(), m_node.chainman->ActiveChain().Tip()->GetBlockHash()); } JSONRPCRequest request; @@ -317,6 +332,7 @@ BOOST_FIXTURE_TEST_CASE(importwallet_rescan, TestChain100Setup) request.params.setArray(); request.params.push_back(backup_file); AddWallet(context, wallet); + LOCK(Assert(m_node.chainman)->GetMutex()); wallet->SetLastBlockProcessed(m_node.chainman->ActiveChain().Height(), m_node.chainman->ActiveChain().Tip()->GetBlockHash()); wallet::importwallet().HandleRequest(request); RemoveWallet(context, wallet, /* load_on_start= */ std::nullopt); @@ -340,9 +356,10 @@ BOOST_FIXTURE_TEST_CASE(importwallet_rescan, TestChain100Setup) BOOST_FIXTURE_TEST_CASE(coin_mark_dirty_immature_credit, TestChain100Setup) { CWallet wallet(m_node.chain.get(), "", m_args, CreateDummyWalletDatabase()); - CWalletTx wtx{m_coinbase_txns.back(), TxStateConfirmed{m_node.chainman->ActiveChain().Tip()->GetBlockHash(), m_node.chainman->ActiveChain().Height(), /*index=*/0}}; LOCK(wallet.cs_wallet); + LOCK(Assert(m_node.chainman)->GetMutex()); + CWalletTx wtx{m_coinbase_txns.back(), TxStateConfirmed{m_node.chainman->ActiveChain().Tip()->GetBlockHash(), m_node.chainman->ActiveChain().Height(), /*index=*/0}}; wallet.SetWalletFlag(WALLET_FLAG_DESCRIPTORS); wallet.SetupDescriptorScriptPubKeyMans(); @@ -350,13 +367,13 @@ BOOST_FIXTURE_TEST_CASE(coin_mark_dirty_immature_credit, TestChain100Setup) // Call GetImmatureCredit() once before adding the key to the wallet to // cache the current immature credit amount, which is 0. - BOOST_CHECK_EQUAL(CachedTxGetImmatureCredit(wallet, wtx), 0); + BOOST_CHECK_EQUAL(CachedTxGetImmatureCredit(wallet, wtx, ISMINE_SPENDABLE), 0); // Invalidate the cached value, add the key, and make sure a new immature // credit amount is calculated. wtx.MarkDirty(); AddKey(wallet, coinbaseKey); - BOOST_CHECK_EQUAL(CachedTxGetImmatureCredit(wallet, wtx), 50*COIN); + BOOST_CHECK_EQUAL(CachedTxGetImmatureCredit(wallet, wtx, ISMINE_SPENDABLE), 50*COIN); } static int64_t AddTx(ChainstateManager& chainman, CWallet& wallet, uint32_t lockTime, int64_t mockTime, int64_t blockTime) @@ -510,7 +527,7 @@ public: ListCoinsTestingSetup() { CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey())); - wallet = CreateSyncedWallet(*m_node.chain, m_node.chainman->ActiveChain(), m_args, coinbaseKey); + wallet = CreateSyncedWallet(*m_node.chain, WITH_LOCK(Assert(m_node.chainman)->GetMutex(), return m_node.chainman->ActiveChain()), m_args, coinbaseKey); } ~ListCoinsTestingSetup() @@ -521,14 +538,12 @@ public: CWalletTx& AddTx(CRecipient recipient) { CTransactionRef tx; - bilingual_str error; CCoinControl dummy; - FeeCalculation fee_calc_out; { constexpr int RANDOM_CHANGE_POSITION = -1; - std::optional<CreatedTransactionResult> txr = CreateTransaction(*wallet, {recipient}, RANDOM_CHANGE_POSITION, error, dummy, fee_calc_out); - BOOST_CHECK(txr.has_value()); - tx = txr->tx; + auto res = CreateTransaction(*wallet, {recipient}, RANDOM_CHANGE_POSITION, dummy); + BOOST_CHECK(res); + tx = res->tx; } wallet->CommitTransaction(tx, {}, {}); CMutableTransaction blocktx; @@ -539,6 +554,7 @@ public: CreateAndProcessBlock({CMutableTransaction(blocktx)}, GetScriptForRawPubKey(coinbaseKey.GetPubKey())); LOCK(wallet->cs_wallet); + LOCK(Assert(m_node.chainman)->GetMutex()); wallet->SetLastBlockProcessed(wallet->GetLastBlockHeight() + 1, m_node.chainman->ActiveChain().Tip()->GetBlockHash()); auto it = wallet->mapWallet.find(tx->GetHash()); BOOST_CHECK(it != wallet->mapWallet.end()); @@ -583,9 +599,7 @@ BOOST_FIXTURE_TEST_CASE(ListCoinsTest, ListCoinsTestingSetup) // Lock both coins. Confirm number of available coins drops to 0. { LOCK(wallet->cs_wallet); - std::vector<COutput> available; - AvailableCoinsListUnspent(*wallet, available); - BOOST_CHECK_EQUAL(available.size(), 2U); + BOOST_CHECK_EQUAL(AvailableCoinsListUnspent(*wallet).Size(), 2U); } for (const auto& group : list) { for (const auto& coin : group.second) { @@ -595,9 +609,7 @@ BOOST_FIXTURE_TEST_CASE(ListCoinsTest, ListCoinsTestingSetup) } { LOCK(wallet->cs_wallet); - std::vector<COutput> available; - AvailableCoinsListUnspent(*wallet, available); - BOOST_CHECK_EQUAL(available.size(), 0U); + BOOST_CHECK_EQUAL(AvailableCoinsListUnspent(*wallet).Size(), 0U); } // Confirm ListCoins still returns same result as before, despite coins // being locked. @@ -618,9 +630,7 @@ BOOST_FIXTURE_TEST_CASE(wallet_disableprivkeys, TestChain100Setup) wallet->SetMinVersion(FEATURE_LATEST); wallet->SetWalletFlag(WALLET_FLAG_DISABLE_PRIVATE_KEYS); BOOST_CHECK(!wallet->TopUpKeyPool(1000)); - CTxDestination dest; - bilingual_str error; - BOOST_CHECK(!wallet->GetNewDestination(OutputType::BECH32, "", dest, error)); + BOOST_CHECK(!wallet->GetNewDestination(OutputType::BECH32, "")); } { const std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(m_node.chain.get(), "", m_args, CreateDummyWalletDatabase()); @@ -628,9 +638,7 @@ BOOST_FIXTURE_TEST_CASE(wallet_disableprivkeys, TestChain100Setup) wallet->SetWalletFlag(WALLET_FLAG_DESCRIPTORS); wallet->SetMinVersion(FEATURE_LATEST); wallet->SetWalletFlag(WALLET_FLAG_DISABLE_PRIVATE_KEYS); - CTxDestination dest; - bilingual_str error; - BOOST_CHECK(!wallet->GetNewDestination(OutputType::BECH32, "", dest, error)); + BOOST_CHECK(!wallet->GetNewDestination(OutputType::BECH32, "")); } } @@ -765,6 +773,7 @@ BOOST_FIXTURE_TEST_CASE(CreateWallet, TestChain100Setup) // being blocked wallet = TestLoadWallet(context); BOOST_CHECK(rescan_completed); + // AddToWallet events for block_tx and mempool_tx BOOST_CHECK_EQUAL(addtx_count, 2); { LOCK(wallet->cs_wallet); @@ -777,6 +786,8 @@ BOOST_FIXTURE_TEST_CASE(CreateWallet, TestChain100Setup) // transactionAddedToMempool events are processed promise.set_value(); SyncWithValidationInterfaceQueue(); + // AddToWallet events for block_tx and mempool_tx events are counted a + // second time as the notification queue is processed BOOST_CHECK_EQUAL(addtx_count, 4); @@ -800,7 +811,7 @@ BOOST_FIXTURE_TEST_CASE(CreateWallet, TestChain100Setup) SyncWithValidationInterfaceQueue(); }); wallet = TestLoadWallet(context); - BOOST_CHECK_EQUAL(addtx_count, 4); + BOOST_CHECK_EQUAL(addtx_count, 2); { LOCK(wallet->cs_wallet); BOOST_CHECK_EQUAL(wallet->mapWallet.count(block_tx.GetHash()), 1U); @@ -840,21 +851,126 @@ BOOST_FIXTURE_TEST_CASE(ZapSelectTx, TestChain100Setup) { auto block_hash = block_tx.GetHash(); - auto prev_hash = m_coinbase_txns[0]->GetHash(); + auto prev_tx = m_coinbase_txns[0]; LOCK(wallet->cs_wallet); - BOOST_CHECK(wallet->HasWalletSpend(prev_hash)); + BOOST_CHECK(wallet->HasWalletSpend(prev_tx)); BOOST_CHECK_EQUAL(wallet->mapWallet.count(block_hash), 1u); std::vector<uint256> vHashIn{ block_hash }, vHashOut; BOOST_CHECK_EQUAL(wallet->ZapSelectTx(vHashIn, vHashOut), DBErrors::LOAD_OK); - BOOST_CHECK(!wallet->HasWalletSpend(prev_hash)); + BOOST_CHECK(!wallet->HasWalletSpend(prev_tx)); BOOST_CHECK_EQUAL(wallet->mapWallet.count(block_hash), 0u); } TestUnloadWallet(std::move(wallet)); } +/** RAII class that provides access to a FailDatabase. Which fails if needed. */ +class FailBatch : public DatabaseBatch +{ +private: + bool m_pass{true}; + bool ReadKey(CDataStream&& key, CDataStream& value) override { return m_pass; } + bool WriteKey(CDataStream&& key, CDataStream&& value, bool overwrite=true) override { return m_pass; } + bool EraseKey(CDataStream&& key) override { return m_pass; } + bool HasKey(CDataStream&& key) override { return m_pass; } + +public: + explicit FailBatch(bool pass) : m_pass(pass) {} + void Flush() override {} + void Close() override {} + + bool StartCursor() override { return true; } + bool ReadAtCursor(CDataStream& ssKey, CDataStream& ssValue, bool& complete) override { return false; } + void CloseCursor() override {} + bool TxnBegin() override { return false; } + bool TxnCommit() override { return false; } + bool TxnAbort() override { return false; } +}; + +/** A dummy WalletDatabase that does nothing, only fails if needed.**/ +class FailDatabase : public WalletDatabase +{ +public: + bool m_pass{true}; // false when this db should fail + + void Open() override {}; + void AddRef() override {} + void RemoveRef() override {} + bool Rewrite(const char* pszSkip=nullptr) override { return true; } + bool Backup(const std::string& strDest) const override { return true; } + void Close() override {} + void Flush() override {} + bool PeriodicFlush() override { return true; } + void IncrementUpdateCounter() override { ++nUpdateCounter; } + void ReloadDbEnv() override {} + std::string Filename() override { return "faildb"; } + std::string Format() override { return "faildb"; } + std::unique_ptr<DatabaseBatch> MakeBatch(bool flush_on_close = true) override { return std::make_unique<FailBatch>(m_pass); } +}; + +/** + * Checks a wallet invalid state where the inputs (prev-txs) of a new arriving transaction are not marked dirty, + * while the transaction that spends them exist inside the in-memory wallet tx map (not stored on db due a db write failure). + */ +BOOST_FIXTURE_TEST_CASE(wallet_sync_tx_invalid_state_test, TestingSetup) +{ + CWallet wallet(m_node.chain.get(), "", m_args, std::make_unique<FailDatabase>()); + { + LOCK(wallet.cs_wallet); + wallet.SetWalletFlag(WALLET_FLAG_DESCRIPTORS); + wallet.SetupDescriptorScriptPubKeyMans(); + } + + // Add tx to wallet + const auto& op_dest = wallet.GetNewDestination(OutputType::BECH32M, ""); + BOOST_ASSERT(op_dest); + + CMutableTransaction mtx; + mtx.vout.push_back({COIN, GetScriptForDestination(*op_dest)}); + mtx.vin.push_back(CTxIn(g_insecure_rand_ctx.rand256(), 0)); + const auto& tx_id_to_spend = wallet.AddToWallet(MakeTransactionRef(mtx), TxStateInMempool{})->GetHash(); + + { + // Cache and verify available balance for the wtx + LOCK(wallet.cs_wallet); + const CWalletTx* wtx_to_spend = wallet.GetWalletTx(tx_id_to_spend); + BOOST_CHECK_EQUAL(CachedTxGetAvailableCredit(wallet, *wtx_to_spend), 1 * COIN); + } + + // Now the good case: + // 1) Add a transaction that spends the previously created transaction + // 2) Verify that the available balance of this new tx and the old one is updated (prev tx is marked dirty) + + mtx.vin.clear(); + mtx.vin.push_back(CTxIn(tx_id_to_spend, 0)); + wallet.transactionAddedToMempool(MakeTransactionRef(mtx), 0); + const uint256& good_tx_id = mtx.GetHash(); + + { + // Verify balance update for the new tx and the old one + LOCK(wallet.cs_wallet); + const CWalletTx* new_wtx = wallet.GetWalletTx(good_tx_id); + BOOST_CHECK_EQUAL(CachedTxGetAvailableCredit(wallet, *new_wtx), 1 * COIN); + + // Now the old wtx + const CWalletTx* wtx_to_spend = wallet.GetWalletTx(tx_id_to_spend); + BOOST_CHECK_EQUAL(CachedTxGetAvailableCredit(wallet, *wtx_to_spend), 0 * COIN); + } + + // Now the bad case: + // 1) Make db always fail + // 2) Try to add a transaction that spends the previously created transaction and + // verify that we are not moving forward if the wallet cannot store it + static_cast<FailDatabase&>(wallet.GetDatabase()).m_pass = false; + mtx.vin.clear(); + mtx.vin.push_back(CTxIn(good_tx_id, 0)); + BOOST_CHECK_EXCEPTION(wallet.transactionAddedToMempool(MakeTransactionRef(mtx), 0), + std::runtime_error, + HasReason("DB error adding transaction to wallet, write failed")); +} + BOOST_AUTO_TEST_SUITE_END() } // namespace wallet diff --git a/src/wallet/test/walletload_tests.cpp b/src/wallet/test/walletload_tests.cpp new file mode 100644 index 0000000000..f45b69a418 --- /dev/null +++ b/src/wallet/test/walletload_tests.cpp @@ -0,0 +1,54 @@ +// Copyright (c) 2022 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or https://www.opensource.org/licenses/mit-license.php. + +#include <wallet/wallet.h> +#include <test/util/setup_common.h> + +#include <boost/test/unit_test.hpp> + +namespace wallet { + +BOOST_AUTO_TEST_SUITE(walletload_tests) + +class DummyDescriptor final : public Descriptor { +private: + std::string desc; +public: + explicit DummyDescriptor(const std::string& descriptor) : desc(descriptor) {}; + ~DummyDescriptor() = default; + + std::string ToString() const override { return desc; } + std::optional<OutputType> GetOutputType() const override { return OutputType::UNKNOWN; } + + bool IsRange() const override { return false; } + bool IsSolvable() const override { return false; } + bool IsSingleType() const override { return true; } + bool ToPrivateString(const SigningProvider& provider, std::string& out) const override { return false; } + bool ToNormalizedString(const SigningProvider& provider, std::string& out, const DescriptorCache* cache = nullptr) const override { return false; } + bool Expand(int pos, const SigningProvider& provider, std::vector<CScript>& output_scripts, FlatSigningProvider& out, DescriptorCache* write_cache = nullptr) const override { return false; }; + bool ExpandFromCache(int pos, const DescriptorCache& read_cache, std::vector<CScript>& output_scripts, FlatSigningProvider& out) const override { return false; } + void ExpandPrivate(int pos, const SigningProvider& provider, FlatSigningProvider& out) const override {} +}; + +BOOST_FIXTURE_TEST_CASE(wallet_load_unknown_descriptor, TestingSetup) +{ + std::unique_ptr<WalletDatabase> database = CreateMockWalletDatabase(); + { + // Write unknown active descriptor + WalletBatch batch(*database, false); + std::string unknown_desc = "trx(tpubD6NzVbkrYhZ4Y4S7m6Y5s9GD8FqEMBy56AGphZXuagajudVZEnYyBahZMgHNCTJc2at82YX6s8JiL1Lohu5A3v1Ur76qguNH4QVQ7qYrBQx/86'/1'/0'/0/*)#8pn8tzdt"; + WalletDescriptor wallet_descriptor(std::make_shared<DummyDescriptor>(unknown_desc), 0, 0, 0, 0); + BOOST_CHECK(batch.WriteDescriptor(uint256(), wallet_descriptor)); + BOOST_CHECK(batch.WriteActiveScriptPubKeyMan(static_cast<uint8_t>(OutputType::UNKNOWN), uint256(), false)); + } + + { + // Now try to load the wallet and verify the error. + const std::shared_ptr<CWallet> wallet(new CWallet(m_node.chain.get(), "", m_args, std::move(database))); + BOOST_CHECK_EQUAL(wallet->LoadWallet(), DBErrors::UNKNOWN_DESCRIPTOR); + } +} + +BOOST_AUTO_TEST_SUITE_END() +} // namespace wallet diff --git a/src/wallet/transaction.h b/src/wallet/transaction.h index 271d698e56..27983e356d 100644 --- a/src/wallet/transaction.h +++ b/src/wallet/transaction.h @@ -305,6 +305,13 @@ public: CWalletTx(CWalletTx const &) = delete; void operator=(CWalletTx const &x) = delete; }; + +struct WalletTxOrderComparator { + bool operator()(const CWalletTx* a, const CWalletTx* b) const + { + return a->nOrderPos < b->nOrderPos; + } +}; } // namespace wallet #endif // BITCOIN_WALLET_TRANSACTION_H diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 0c83c3d954..461e64751a 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -124,7 +124,7 @@ bool RemoveWallet(WalletContext& context, const std::shared_ptr<CWallet>& wallet interfaces::Chain& chain = wallet->chain(); std::string name = wallet->GetName(); - // Unregister with the validation interface which also drops shared ponters. + // Unregister with the validation interface which also drops shared pointers. wallet->m_chain_notifications_handler.reset(); LOCK(context.wallets_mutex); std::vector<std::shared_ptr<CWallet>>::iterator i = std::find(context.wallets.begin(), context.wallets.end(), wallet); @@ -149,6 +149,13 @@ std::vector<std::shared_ptr<CWallet>> GetWallets(WalletContext& context) return context.wallets; } +std::shared_ptr<CWallet> GetDefaultWallet(WalletContext& context, size_t& count) +{ + LOCK(context.wallets_mutex); + count = context.wallets.size(); + return count == 1 ? context.wallets[0] : nullptr; +} + std::shared_ptr<CWallet> GetWallet(WalletContext& context, const std::string& name) { LOCK(context.wallets_mutex); @@ -173,8 +180,8 @@ void NotifyWalletLoaded(WalletContext& context, const std::shared_ptr<CWallet>& } } -static Mutex g_loading_wallet_mutex; -static Mutex g_wallet_release_mutex; +static GlobalMutex g_loading_wallet_mutex; +static GlobalMutex g_wallet_release_mutex; static std::condition_variable g_wallet_release_cv; static std::set<std::string> g_loading_wallet_set GUARDED_BY(g_loading_wallet_mutex); static std::set<std::string> g_unloading_wallet_set GUARDED_BY(g_wallet_release_mutex); @@ -379,25 +386,31 @@ std::shared_ptr<CWallet> RestoreWallet(WalletContext& context, const fs::path& b ReadDatabaseArgs(*context.args, options); options.require_existing = true; - if (!fs::exists(backup_file)) { - error = Untranslated("Backup file does not exist"); - status = DatabaseStatus::FAILED_INVALID_BACKUP_FILE; - return nullptr; - } - const fs::path wallet_path = fsbridge::AbsPathJoin(GetWalletDir(), fs::u8path(wallet_name)); + auto wallet_file = wallet_path / "wallet.dat"; + std::shared_ptr<CWallet> wallet; - if (fs::exists(wallet_path) || !TryCreateDirectories(wallet_path)) { - error = Untranslated(strprintf("Failed to create database path '%s'. Database already exists.", fs::PathToString(wallet_path))); - status = DatabaseStatus::FAILED_ALREADY_EXISTS; - return nullptr; - } + try { + if (!fs::exists(backup_file)) { + error = Untranslated("Backup file does not exist"); + status = DatabaseStatus::FAILED_INVALID_BACKUP_FILE; + return nullptr; + } - auto wallet_file = wallet_path / "wallet.dat"; - fs::copy_file(backup_file, wallet_file, fs::copy_options::none); + if (fs::exists(wallet_path) || !TryCreateDirectories(wallet_path)) { + error = Untranslated(strprintf("Failed to create database path '%s'. Database already exists.", fs::PathToString(wallet_path))); + status = DatabaseStatus::FAILED_ALREADY_EXISTS; + return nullptr; + } - auto wallet = LoadWallet(context, wallet_name, load_on_start, options, status, error, warnings); + fs::copy_file(backup_file, wallet_file, fs::copy_options::none); + wallet = LoadWallet(context, wallet_name, load_on_start, options, status, error, warnings); + } catch (const std::exception& e) { + assert(!wallet); + if (!error.empty()) error += Untranslated("\n"); + error += strprintf(Untranslated("Unexpected exception: %s"), e.what()); + } if (!wallet) { fs::remove(wallet_file); fs::remove(wallet_path); @@ -414,7 +427,7 @@ std::shared_ptr<CWallet> RestoreWallet(WalletContext& context, const fs::path& b const CWalletTx* CWallet::GetWalletTx(const uint256& hash) const { AssertLockHeld(cs_wallet); - std::map<uint256, CWalletTx>::const_iterator it = mapWallet.find(hash); + const auto it = mapWallet.find(hash); if (it == mapWallet.end()) return nullptr; return &(it->second); @@ -535,6 +548,7 @@ void CWallet::SetMinVersion(enum WalletFeature nVersion, WalletBatch* batch_in) LOCK(cs_wallet); if (nWalletVersion >= nVersion) return; + WalletLogPrintf("Setting minversion to %d\n", nVersion); nWalletVersion = nVersion; { @@ -551,7 +565,7 @@ std::set<uint256> CWallet::GetConflicts(const uint256& txid) const std::set<uint256> result; AssertLockHeld(cs_wallet); - std::map<uint256, CWalletTx>::const_iterator it = mapWallet.find(txid); + const auto it = mapWallet.find(txid); if (it == mapWallet.end()) return result; const CWalletTx& wtx = it->second; @@ -569,11 +583,17 @@ std::set<uint256> CWallet::GetConflicts(const uint256& txid) const return result; } -bool CWallet::HasWalletSpend(const uint256& txid) const +bool CWallet::HasWalletSpend(const CTransactionRef& tx) const { AssertLockHeld(cs_wallet); - auto iter = mapTxSpends.lower_bound(COutPoint(txid, 0)); - return (iter != mapTxSpends.end() && iter->first.hash == txid); + const uint256& txid = tx->GetHash(); + for (unsigned int i = 0; i < tx->vout.size(); ++i) { + auto iter = mapTxSpends.find(COutPoint(txid, i)); + if (iter != mapTxSpends.end()) { + return true; + } + } + return false; } void CWallet::Flush() @@ -629,16 +649,14 @@ void CWallet::SyncMetaData(std::pair<TxSpends::iterator, TxSpends::iterator> ran * Outpoint is spent if any non-conflicted transaction * spends it: */ -bool CWallet::IsSpent(const uint256& hash, unsigned int n) const +bool CWallet::IsSpent(const COutPoint& outpoint) const { - const COutPoint outpoint(hash, n); std::pair<TxSpends::const_iterator, TxSpends::const_iterator> range; range = mapTxSpends.equal_range(outpoint); - for (TxSpends::const_iterator it = range.first; it != range.second; ++it) - { + for (TxSpends::const_iterator it = range.first; it != range.second; ++it) { const uint256& wtxid = it->second; - std::map<uint256, CWalletTx>::const_iterator mit = mapWallet.find(wtxid); + const auto mit = mapWallet.find(wtxid); if (mit != mapWallet.end()) { int depth = GetTxDepthInMainChain(mit->second); if (depth > 0 || (depth == 0 && !mit->second.isAbandoned())) @@ -665,16 +683,13 @@ void CWallet::AddToSpends(const COutPoint& outpoint, const uint256& wtxid, Walle } -void CWallet::AddToSpends(const uint256& wtxid, WalletBatch* batch) +void CWallet::AddToSpends(const CWalletTx& wtx, WalletBatch* batch) { - auto it = mapWallet.find(wtxid); - assert(it != mapWallet.end()); - const CWalletTx& thisTx = it->second; - if (thisTx.IsCoinBase()) // Coinbases don't spend anything! + if (wtx.IsCoinBase()) // Coinbases don't spend anything! return; - for (const CTxIn& txin : thisTx.tx->vin) - AddToSpends(txin.prevout, wtxid, batch); + for (const CTxIn& txin : wtx.tx->vin) + AddToSpends(txin.prevout, wtx.GetHash(), batch); } bool CWallet::EncryptWallet(const SecureString& strWalletPassphrase) @@ -873,7 +888,7 @@ bool CWallet::MarkReplaced(const uint256& originalHash, const uint256& newHash) wtx.mapValue["replaced_by_txid"] = newHash.ToString(); - // Refresh mempool status without waiting for transactionRemovedFromMempool + // Refresh mempool status without waiting for transactionRemovedFromMempool or transactionAddedToMempool RefreshMempoolStatus(wtx, chain()); WalletBatch batch(GetDatabase()); @@ -908,35 +923,31 @@ void CWallet::SetSpentKeyState(WalletBatch& batch, const uint256& hash, unsigned } } -bool CWallet::IsSpentKey(const uint256& hash, unsigned int n) const +bool CWallet::IsSpentKey(const CScript& scriptPubKey) const { AssertLockHeld(cs_wallet); - const CWalletTx* srctx = GetWalletTx(hash); - if (srctx) { - assert(srctx->tx->vout.size() > n); - CTxDestination dest; - if (!ExtractDestination(srctx->tx->vout[n].scriptPubKey, dest)) { - return false; - } - if (IsAddressUsed(dest)) { - return true; - } - if (IsLegacy()) { - LegacyScriptPubKeyMan* spk_man = GetLegacyScriptPubKeyMan(); - assert(spk_man != nullptr); - for (const auto& keyid : GetAffectedKeys(srctx->tx->vout[n].scriptPubKey, *spk_man)) { - WitnessV0KeyHash wpkh_dest(keyid); - if (IsAddressUsed(wpkh_dest)) { - return true; - } - ScriptHash sh_wpkh_dest(GetScriptForDestination(wpkh_dest)); - if (IsAddressUsed(sh_wpkh_dest)) { - return true; - } - PKHash pkh_dest(keyid); - if (IsAddressUsed(pkh_dest)) { - return true; - } + CTxDestination dest; + if (!ExtractDestination(scriptPubKey, dest)) { + return false; + } + if (IsAddressUsed(dest)) { + return true; + } + if (IsLegacy()) { + LegacyScriptPubKeyMan* spk_man = GetLegacyScriptPubKeyMan(); + assert(spk_man != nullptr); + for (const auto& keyid : GetAffectedKeys(scriptPubKey, *spk_man)) { + WitnessV0KeyHash wpkh_dest(keyid); + if (IsAddressUsed(wpkh_dest)) { + return true; + } + ScriptHash sh_wpkh_dest(GetScriptForDestination(wpkh_dest)); + if (IsAddressUsed(sh_wpkh_dest)) { + return true; + } + PKHash pkh_dest(keyid); + if (IsAddressUsed(pkh_dest)) { + return true; } } } @@ -973,7 +984,7 @@ CWalletTx* CWallet::AddToWallet(CTransactionRef tx, const TxState& state, const wtx.nOrderPos = IncOrderPosNext(&batch); wtx.m_it_wtxOrdered = wtxOrdered.insert(std::make_pair(wtx.nOrderPos, &wtx)); wtx.nTimeSmart = ComputeTimeSmart(wtx, rescanning_old_block); - AddToSpends(hash, &batch); + AddToSpends(wtx, &batch); } if (!fInsertedNew) @@ -1072,7 +1083,7 @@ bool CWallet::LoadToWallet(const uint256& hash, const UpdateWalletTxFn& fill_wtx if (/* insertion took place */ ins.second) { wtx.m_it_wtxOrdered = wtxOrdered.insert(std::make_pair(wtx.nOrderPos, &wtx)); } - AddToSpends(hash); + AddToSpends(wtx); for (const CTxIn& txin : wtx.tx->vin) { auto it = mapWallet.find(txin.prevout.hash); if (it != mapWallet.end()) { @@ -1139,7 +1150,13 @@ bool CWallet::AddToWalletIfInvolvingMe(const CTransactionRef& ptx, const SyncTxS // Block disconnection override an abandoned tx as unconfirmed // which means user may have to call abandontransaction again TxState tx_state = std::visit([](auto&& s) -> TxState { return s; }, state); - return AddToWallet(MakeTransactionRef(tx), tx_state, /*update_wtx=*/nullptr, /*fFlushOnClose=*/false, rescanning_old_block); + CWalletTx* wtx = AddToWallet(MakeTransactionRef(tx), tx_state, /*update_wtx=*/nullptr, /*fFlushOnClose=*/false, rescanning_old_block); + if (!wtx) { + // Can only be nullptr if there was a db write error (missing db, read-only db or a db engine internal writing error). + // As we only store arriving transaction in this process, and we don't want an inconsistent state, let's throw an error. + throw std::runtime_error("DB error adding transaction to wallet, write failed"); + } + return true; } } return false; @@ -1200,12 +1217,13 @@ bool CWallet::AbandonTransaction(const uint256& hashTx) batch.WriteTx(wtx); NotifyTransactionChanged(wtx.GetHash(), CT_UPDATED); // Iterate over all its outputs, and mark transactions in the wallet that spend them abandoned too - TxSpends::const_iterator iter = mapTxSpends.lower_bound(COutPoint(now, 0)); - while (iter != mapTxSpends.end() && iter->first.hash == now) { - if (!done.count(iter->second)) { - todo.insert(iter->second); + for (unsigned int i = 0; i < wtx.tx->vout.size(); ++i) { + std::pair<TxSpends::const_iterator, TxSpends::const_iterator> range = mapTxSpends.equal_range(COutPoint(now, i)); + for (TxSpends::const_iterator iter = range.first; iter != range.second; ++iter) { + if (!done.count(iter->second)) { + todo.insert(iter->second); + } } - iter++; } // If a transaction changes 'conflicted' state, that changes the balance // available of the outputs it spends. So force those to be recomputed @@ -1251,12 +1269,13 @@ void CWallet::MarkConflicted(const uint256& hashBlock, int conflicting_height, c wtx.MarkDirty(); batch.WriteTx(wtx); // Iterate over all its outputs, and mark transactions in the wallet that spend them conflicted too - TxSpends::const_iterator iter = mapTxSpends.lower_bound(COutPoint(now, 0)); - while (iter != mapTxSpends.end() && iter->first.hash == now) { - if (!done.count(iter->second)) { - todo.insert(iter->second); - } - iter++; + for (unsigned int i = 0; i < wtx.tx->vout.size(); ++i) { + std::pair<TxSpends::const_iterator, TxSpends::const_iterator> range = mapTxSpends.equal_range(COutPoint(now, i)); + for (TxSpends::const_iterator iter = range.first; iter != range.second; ++iter) { + if (!done.count(iter->second)) { + todo.insert(iter->second); + } + } } // If a transaction changes 'conflicted' state, that changes the balance // available of the outputs it spends. So force those to be recomputed @@ -1323,30 +1342,31 @@ void CWallet::transactionRemovedFromMempool(const CTransactionRef& tx, MemPoolRe } } -void CWallet::blockConnected(const CBlock& block, int height) +void CWallet::blockConnected(const interfaces::BlockInfo& block) { - const uint256& block_hash = block.GetHash(); + assert(block.data); LOCK(cs_wallet); - m_last_block_processed_height = height; - m_last_block_processed = block_hash; - for (size_t index = 0; index < block.vtx.size(); index++) { - SyncTransaction(block.vtx[index], TxStateConfirmed{block_hash, height, static_cast<int>(index)}); - transactionRemovedFromMempool(block.vtx[index], MemPoolRemovalReason::BLOCK, 0 /* mempool_sequence */); + m_last_block_processed_height = block.height; + m_last_block_processed = block.hash; + for (size_t index = 0; index < block.data->vtx.size(); index++) { + SyncTransaction(block.data->vtx[index], TxStateConfirmed{block.hash, block.height, static_cast<int>(index)}); + transactionRemovedFromMempool(block.data->vtx[index], MemPoolRemovalReason::BLOCK, 0 /* mempool_sequence */); } } -void CWallet::blockDisconnected(const CBlock& block, int height) +void CWallet::blockDisconnected(const interfaces::BlockInfo& block) { + assert(block.data); LOCK(cs_wallet); // At block disconnection, this will change an abandoned transaction to // be unconfirmed, whether or not the transaction is added back to the mempool. // User may have to call abandontransaction again. It may be addressed in the // future with a stickier abandoned state or even removing abandontransaction call. - m_last_block_processed_height = height - 1; - m_last_block_processed = block.hashPrevBlock; - for (const CTransactionRef& ptx : block.vtx) { + m_last_block_processed_height = block.height - 1; + m_last_block_processed = *Assert(block.prev_hash); + for (const CTransactionRef& ptx : Assert(block.data)->vtx) { SyncTransaction(ptx, TxStateInactive{}); } } @@ -1372,7 +1392,7 @@ CAmount CWallet::GetDebit(const CTxIn &txin, const isminefilter& filter) const { { LOCK(cs_wallet); - std::map<uint256, CWalletTx>::const_iterator mi = mapWallet.find(txin.prevout.hash); + const auto mi = mapWallet.find(txin.prevout.hash); if (mi != mapWallet.end()) { const CWalletTx& prev = (*mi).second; @@ -1415,6 +1435,19 @@ bool CWallet::IsMine(const CTransaction& tx) const return false; } +isminetype CWallet::IsMine(const COutPoint& outpoint) const +{ + AssertLockHeld(cs_wallet); + auto wtx = GetWalletTx(outpoint.hash); + if (!wtx) { + return ISMINE_NO; + } + if (outpoint.n >= wtx->tx->vout.size()) { + return ISMINE_NO; + } + return IsMine(wtx->tx->vout[outpoint.n]); +} + bool CWallet::IsFromMe(const CTransaction& tx) const { return (GetDebit(tx, ISMINE_ALL) > 0); @@ -1500,26 +1533,33 @@ bool CWallet::LoadWalletFlags(uint64_t flags) return true; } -bool CWallet::AddWalletFlags(uint64_t flags) +void CWallet::InitWalletFlags(uint64_t flags) { LOCK(cs_wallet); + // We should never be writing unknown non-tolerable wallet flags assert(((flags & KNOWN_WALLET_FLAGS) >> 32) == (flags >> 32)); + // This should only be used once, when creating a new wallet - so current flags are expected to be blank + assert(m_wallet_flags == 0); + if (!WalletBatch(GetDatabase()).WriteWalletFlags(flags)) { throw std::runtime_error(std::string(__func__) + ": writing wallet flags failed"); } - return LoadWalletFlags(flags); + if (!LoadWalletFlags(flags)) assert(false); } // Helper for producing a max-sized low-S low-R signature (eg 71 bytes) -// or a max-sized low-S signature (e.g. 72 bytes) if use_max_sig is true -bool DummySignInput(const SigningProvider& provider, CTxIn &tx_in, const CTxOut &txout, bool use_max_sig) +// or a max-sized low-S signature (e.g. 72 bytes) depending on coin_control +bool DummySignInput(const SigningProvider& provider, CTxIn &tx_in, const CTxOut &txout, const CCoinControl* coin_control) { // Fill in dummy signatures for fee calculation. const CScript& scriptPubKey = txout.scriptPubKey; SignatureData sigdata; + // Use max sig if watch only inputs were used or if this particular input is an external input + // to ensure a sufficient fee is attained for the requested feerate. + const bool use_max_sig = coin_control && (coin_control->fAllowWatchOnly || coin_control->IsExternalSelected(tx_in.prevout)); if (!ProduceSignature(provider, use_max_sig ? DUMMY_MAXIMUM_SIGNATURE_CREATOR : DUMMY_SIGNATURE_CREATOR, scriptPubKey, sigdata)) { return false; } @@ -1586,12 +1626,9 @@ bool CWallet::DummySignTx(CMutableTransaction &txNew, const std::vector<CTxOut> nIn++; continue; } - // Use max sig if watch only inputs were used or if this particular input is an external input - // to ensure a sufficient fee is attained for the requested feerate. - const bool use_max_sig = coin_control && (coin_control->fAllowWatchOnly || coin_control->IsExternalSelected(txin.prevout)); const std::unique_ptr<SigningProvider> provider = GetSolvingProvider(txout.scriptPubKey); - if (!provider || !DummySignInput(*provider, txin, txout, use_max_sig)) { - if (!coin_control || !DummySignInput(coin_control->m_external_provider, txin, txout, use_max_sig)) { + if (!provider || !DummySignInput(*provider, txin, txout, coin_control)) { + if (!coin_control || !DummySignInput(coin_control->m_external_provider, txin, txout, coin_control)) { return false; } } @@ -1674,7 +1711,7 @@ int64_t CWallet::RescanFromTime(int64_t startTime, const WalletRescanReserver& r if (start) { // TODO: this should take into account failure by ScanResult::USER_ABORT - ScanResult result = ScanForWalletTransactions(start_block, start_height, {} /* max_height */, reserver, update); + ScanResult result = ScanForWalletTransactions(start_block, start_height, /*max_height=*/{}, reserver, /*fUpdate=*/update, /*save_progress=*/false); if (result.status == ScanResult::FAILURE) { int64_t time_max; CHECK_NONFATAL(chain().findBlock(result.last_failed_block, FoundBlock().maxTime(time_max))); @@ -1687,7 +1724,8 @@ int64_t CWallet::RescanFromTime(int64_t startTime, const WalletRescanReserver& r /** * Scan the block chain (starting in start_block) for transactions * from or to us. If fUpdate is true, found transactions that already - * exist in the wallet will be updated. + * exist in the wallet will be updated. If max_height is not set, the + * mempool will be scanned as well. * * @param[in] start_block Scan starting block. If block is not on the active * chain, the scan will return SUCCESS immediately. @@ -1705,12 +1743,11 @@ 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, std::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, const bool save_progress) { - using Clock = std::chrono::steady_clock; - constexpr auto LOG_INTERVAL{60s}; - auto current_time{Clock::now()}; - auto start_time{Clock::now()}; + constexpr auto INTERVAL_TIME{60s}; + auto current_time{reserver.now()}; + auto start_time{reserver.now()}; assert(reserver.isReserved()); @@ -1737,8 +1774,10 @@ CWallet::ScanResult CWallet::ScanForWalletTransactions(const uint256& start_bloc if (block_height % 100 == 0 && progress_end - progress_begin > 0.0) { ShowProgress(strprintf("%s " + _("Rescanning…").translated, GetDisplayName()), std::max(1, std::min(99, (int)(m_scanning_progress * 100)))); } - if (Clock::now() >= current_time + LOG_INTERVAL) { - current_time = Clock::now(); + + bool next_interval = reserver.now() >= current_time + INTERVAL_TIME; + if (next_interval) { + current_time = reserver.now(); WalletLogPrintf("Still rescanning. At block %d. Progress=%f\n", block_height, progress_current); } @@ -1768,6 +1807,16 @@ CWallet::ScanResult CWallet::ScanForWalletTransactions(const uint256& start_bloc // scan succeeded, record block as most recent successfully scanned result.last_scanned_block = block_hash; result.last_scanned_height = block_height; + + if (save_progress && next_interval) { + CBlockLocator loc = m_chain->getActiveChainLocator(block_hash); + + if (!loc.IsNull()) { + WalletLogPrintf("Saving scan progress %d.\n", block_height); + WalletBatch batch(GetDatabase()); + batch.WriteBestBlock(loc); + } + } } else { // could not scan block, keep scanning but record this block as the most recent failure result.last_failed_block = block_hash; @@ -1797,6 +1846,10 @@ CWallet::ScanResult CWallet::ScanForWalletTransactions(const uint256& start_bloc } } } + if (!max_height) { + WalletLogPrintf("Scanning current mempool transactions.\n"); + WITH_LOCK(cs_wallet, chain().requestMempoolTransactions(*this)); + } ShowProgress(strprintf("%s " + _("Rescanning…").translated, GetDisplayName()), 100); // hide progress dialog in GUI if (block_height && fAbortRescan) { WalletLogPrintf("Rescan aborted at block %d. Progress=%f\n", block_height, progress_current); @@ -1805,40 +1858,11 @@ CWallet::ScanResult CWallet::ScanForWalletTransactions(const uint256& start_bloc WalletLogPrintf("Rescan interrupted by shutdown request at block %d. Progress=%f\n", block_height, progress_current); result.status = ScanResult::USER_ABORT; } else { - auto duration_milliseconds = std::chrono::duration_cast<std::chrono::milliseconds>(Clock::now() - start_time); - WalletLogPrintf("Rescan completed in %15dms\n", duration_milliseconds.count()); + WalletLogPrintf("Rescan completed in %15dms\n", Ticks<std::chrono::milliseconds>(reserver.now() - start_time)); } return result; } -void CWallet::ReacceptWalletTransactions() -{ - // If transactions aren't being broadcasted, don't let them into local mempool either - if (!fBroadcastTransactions) - return; - std::map<int64_t, CWalletTx*> mapSorted; - - // Sort pending wallet transactions based on their initial wallet insertion order - for (std::pair<const uint256, CWalletTx>& item : mapWallet) { - const uint256& wtxid = item.first; - CWalletTx& wtx = item.second; - assert(wtx.GetHash() == wtxid); - - int nDepth = GetTxDepthInMainChain(wtx); - - if (!wtx.IsCoinBase() && (nDepth == 0 && !wtx.isAbandoned())) { - mapSorted.insert(std::make_pair(wtx.nOrderPos, &wtx)); - } - } - - // Try to add wallet transactions to memory pool - for (const std::pair<const int64_t, CWalletTx*>& item : mapSorted) { - CWalletTx& wtx = *(item.second); - std::string unused_err_string; - SubmitTxMemoryPoolAndRelay(wtx, unused_err_string, false); - } -} - bool CWallet::SubmitTxMemoryPoolAndRelay(CWalletTx& wtx, std::string& err_string, bool relay) const { AssertLockHeld(cs_wallet); @@ -1879,43 +1903,69 @@ std::set<uint256> CWallet::GetTxConflicts(const CWalletTx& wtx) const return result; } -// Rebroadcast transactions from the wallet. We do this on a random timer -// to slightly obfuscate which transactions come from our wallet. +// Resubmit transactions from the wallet to the mempool, optionally asking the +// mempool to relay them. On startup, we will do this for all unconfirmed +// transactions but will not ask the mempool to relay them. We do this on startup +// to ensure that our own mempool is aware of our transactions, and to also +// initialize m_next_resend so that the actual rebroadcast is scheduled. There +// is a privacy side effect here as not broadcasting on startup also means that we won't +// inform the world of our wallet's state, particularly if the wallet (or node) is not +// yet synced. +// +// Otherwise this function is called periodically in order to relay our unconfirmed txs. +// We do this on a random timer to slightly obfuscate which transactions +// come from our wallet. // -// Ideally, we'd only resend transactions that we think should have been +// TODO: Ideally, we'd only resend transactions that we think should have been // mined in the most recent block. Any transaction that wasn't in the top // blockweight of transactions in the mempool shouldn't have been mined, // and so is probably just sitting in the mempool waiting to be confirmed. // Rebroadcasting does nothing to speed up confirmation and only damages // privacy. -void CWallet::ResendWalletTransactions() +// +// The `force` option results in all unconfirmed transactions being submitted to +// the mempool. This does not necessarily result in those transactions being relayed, +// that depends on the `relay` option. Periodic rebroadcast uses the pattern +// relay=true force=false, while loading into the mempool +// (on start, or after import) uses relay=false force=true. +void CWallet::ResubmitWalletTransactions(bool relay, bool force) { + // Don't attempt to resubmit if the wallet is configured to not broadcast, + // even if forcing. + if (!fBroadcastTransactions) return; + // During reindex, importing and IBD, old wallet transactions become // unconfirmed. Don't resend them as that would spam other nodes. - if (!chain().isReadyToBroadcast()) return; + // We only allow forcing mempool submission when not relaying to avoid this spam. + if (!force && relay && !chain().isReadyToBroadcast()) return; // Do this infrequently and randomly to avoid giving away // that these are our transactions. - if (GetTime() < nNextResend || !fBroadcastTransactions) return; - bool fFirst = (nNextResend == 0); + if (!force && GetTime() < m_next_resend) return; // resend 12-36 hours from now, ~1 day on average. - nNextResend = GetTime() + (12 * 60 * 60) + GetRand(24 * 60 * 60); - if (fFirst) return; + m_next_resend = GetTime() + (12 * 60 * 60) + GetRand(24 * 60 * 60); int submitted_tx_count = 0; { // cs_wallet scope LOCK(cs_wallet); - // Relay transactions - for (std::pair<const uint256, CWalletTx>& item : mapWallet) { - CWalletTx& wtx = item.second; - // Attempt to rebroadcast all txes more than 5 minutes older than - // the last block. SubmitTxMemoryPoolAndRelay() will not rebroadcast - // any confirmed or conflicting txs. - if (wtx.nTimeReceived > m_best_block_time - 5 * 60) continue; + // First filter for the transactions we want to rebroadcast. + // We use a set with WalletTxOrderComparator so that rebroadcasting occurs in insertion order + std::set<CWalletTx*, WalletTxOrderComparator> to_submit; + for (auto& [txid, wtx] : mapWallet) { + // Only rebroadcast unconfirmed txs + if (!wtx.isUnconfirmed()) continue; + + // attempt to rebroadcast all txes more than 5 minutes older than + // the last block, or all txs if forcing. + if (!force && wtx.nTimeReceived > m_best_block_time - 5 * 60) continue; + to_submit.insert(&wtx); + } + // Now try submitting the transactions to the memory pool and (optionally) relay them. + for (auto wtx : to_submit) { std::string unused_err_string; - if (SubmitTxMemoryPoolAndRelay(wtx, unused_err_string, true)) ++submitted_tx_count; + if (SubmitTxMemoryPoolAndRelay(*wtx, unused_err_string, relay)) ++submitted_tx_count; } } // cs_wallet @@ -1929,7 +1979,7 @@ void CWallet::ResendWalletTransactions() void MaybeResendWalletTxs(WalletContext& context) { for (const std::shared_ptr<CWallet>& pwallet : GetWallets(context)) { - pwallet->ResendWalletTransactions(); + pwallet->ResubmitWalletTransactions(/*relay=*/true, /*force=*/false); } } @@ -1946,7 +1996,7 @@ bool CWallet::SignTransaction(CMutableTransaction& tx) const // Build coins map std::map<COutPoint, Coin> coins; for (auto& input : tx.vin) { - std::map<uint256, CWalletTx>::const_iterator mi = mapWallet.find(input.prevout.hash); + const auto mi = mapWallet.find(input.prevout.hash); if(mi == mapWallet.end() || input.prevout.n >= mi->second.tx->vout.size()) { return false; } @@ -1978,7 +2028,6 @@ TransactionError CWallet::FillPSBT(PartiallySignedTransaction& psbtx, bool& comp if (n_signed) { *n_signed = 0; } - const PrecomputedTransactionData txdata = PrecomputePSBTData(psbtx); LOCK(cs_wallet); // Get all of the previous transactions for (unsigned int i = 0; i < psbtx.tx->vin.size(); ++i) { @@ -2002,6 +2051,8 @@ TransactionError CWallet::FillPSBT(PartiallySignedTransaction& psbtx, bool& comp } } + const PrecomputedTransactionData txdata = PrecomputePSBTData(psbtx); + // Fill in information from ScriptPubKeyMans for (ScriptPubKeyMan* spk_man : GetAllScriptPubKeyMans()) { int n_signed_this_spkm = 0; @@ -2015,6 +2066,35 @@ TransactionError CWallet::FillPSBT(PartiallySignedTransaction& psbtx, bool& comp } } + // Only drop non_witness_utxos if sighash_type != SIGHASH_ANYONECANPAY + if ((sighash_type & 0x80) != SIGHASH_ANYONECANPAY) { + // Figure out if any non_witness_utxos should be dropped + std::vector<unsigned int> to_drop; + for (unsigned int i = 0; i < psbtx.inputs.size(); ++i) { + const auto& input = psbtx.inputs.at(i); + int wit_ver; + std::vector<unsigned char> wit_prog; + if (input.witness_utxo.IsNull() || !input.witness_utxo.scriptPubKey.IsWitnessProgram(wit_ver, wit_prog)) { + // There's a non-segwit input or Segwit v0, so we cannot drop any witness_utxos + to_drop.clear(); + break; + } + if (wit_ver == 0) { + // Segwit v0, so we cannot drop any non_witness_utxos + to_drop.clear(); + break; + } + if (input.non_witness_utxo) { + to_drop.push_back(i); + } + } + + // Drop the non_witness_utxos that we can drop + for (unsigned int i : to_drop) { + psbtx.inputs.at(i).non_witness_utxo = nullptr; + } + } + // Complete if every input is now signed complete = true; for (const auto& input : psbtx.inputs) { @@ -2030,6 +2110,7 @@ SigningResult CWallet::SignMessage(const std::string& message, const PKHash& pkh CScript script_pub_key = GetScriptForDestination(pkhash); for (const auto& spk_man_pair : m_spk_managers) { if (spk_man_pair.second->CanProvide(script_pub_key, sigdata)) { + LOCK(cs_wallet); // DescriptorScriptPubKeyMan calls IsLocked which can lock cs_wallet in a deadlocking order return spk_man_pair.second->SignMessage(message, pkhash, str_sig); } } @@ -2287,37 +2368,32 @@ bool CWallet::TopUpKeyPool(unsigned int kpSize) return res; } -bool CWallet::GetNewDestination(const OutputType type, const std::string label, CTxDestination& dest, bilingual_str& error) +util::Result<CTxDestination> CWallet::GetNewDestination(const OutputType type, const std::string label) { LOCK(cs_wallet); - error.clear(); - bool result = false; auto spk_man = GetScriptPubKeyMan(type, false /* internal */); - if (spk_man) { - spk_man->TopUp(); - result = spk_man->GetNewDestination(type, dest, error); - } else { - error = strprintf(_("Error: No %s addresses available."), FormatOutputType(type)); + if (!spk_man) { + return util::Error{strprintf(_("Error: No %s addresses available."), FormatOutputType(type))}; } - if (result) { - SetAddressBook(dest, label, "receive"); + + spk_man->TopUp(); + auto op_dest = spk_man->GetNewDestination(type); + if (op_dest) { + SetAddressBook(*op_dest, label, "receive"); } - return result; + return op_dest; } -bool CWallet::GetNewChangeDestination(const OutputType type, CTxDestination& dest, bilingual_str& error) +util::Result<CTxDestination> CWallet::GetNewChangeDestination(const OutputType type) { LOCK(cs_wallet); - error.clear(); ReserveDestination reservedest(this, type); - if (!reservedest.GetReservedDestination(dest, true, error)) { - return false; - } + auto op_dest = reservedest.GetReservedDestination(true); + if (op_dest) reservedest.KeepDestination(); - reservedest.KeepDestination(); - return true; + return op_dest; } std::optional<int64_t> CWallet::GetOldestKeyPoolTime() const @@ -2348,42 +2424,63 @@ void CWallet::MarkDestinationsDirty(const std::set<CTxDestination>& destinations } } -std::set<CTxDestination> CWallet::GetLabelAddresses(const std::string& label) const +void CWallet::ForEachAddrBookEntry(const ListAddrBookFunc& func) const { AssertLockHeld(cs_wallet); - std::set<CTxDestination> result; - for (const std::pair<const CTxDestination, CAddressBookData>& item : m_address_book) - { - if (item.second.IsChange()) continue; - const CTxDestination& address = item.first; - const std::string& strName = item.second.GetLabel(); - if (strName == label) - result.insert(address); + for (const std::pair<const CTxDestination, CAddressBookData>& item : m_address_book) { + const auto& entry = item.second; + func(item.first, entry.GetLabel(), entry.purpose, entry.IsChange()); } +} + +std::vector<CTxDestination> CWallet::ListAddrBookAddresses(const std::optional<AddrBookFilter>& _filter) const +{ + AssertLockHeld(cs_wallet); + std::vector<CTxDestination> result; + AddrBookFilter filter = _filter ? *_filter : AddrBookFilter(); + ForEachAddrBookEntry([&result, &filter](const CTxDestination& dest, const std::string& label, const std::string& purpose, bool is_change) { + // Filter by change + if (filter.ignore_change && is_change) return; + // Filter by label + if (filter.m_op_label && *filter.m_op_label != label) return; + // All good + result.emplace_back(dest); + }); return result; } -bool ReserveDestination::GetReservedDestination(CTxDestination& dest, bool internal, bilingual_str& error) +std::set<std::string> CWallet::ListAddrBookLabels(const std::string& purpose) const +{ + AssertLockHeld(cs_wallet); + std::set<std::string> label_set; + ForEachAddrBookEntry([&](const CTxDestination& _dest, const std::string& _label, + const std::string& _purpose, bool _is_change) { + if (_is_change) return; + if (purpose.empty() || _purpose == purpose) { + label_set.insert(_label); + } + }); + return label_set; +} + +util::Result<CTxDestination> ReserveDestination::GetReservedDestination(bool internal) { m_spk_man = pwallet->GetScriptPubKeyMan(type, internal); if (!m_spk_man) { - error = strprintf(_("Error: No %s addresses available."), FormatOutputType(type)); - return false; + return util::Error{strprintf(_("Error: No %s addresses available."), FormatOutputType(type))}; } - if (nIndex == -1) { m_spk_man->TopUp(); CKeyPool keypool; - if (!m_spk_man->GetReservedDestination(type, internal, address, nIndex, keypool, error)) { - return false; - } + auto op_address = m_spk_man->GetReservedDestination(type, internal, nIndex, keypool); + if (!op_address) return op_address; + address = *op_address; fInternal = keypool.fInternal; } - dest = address; - return true; + return address; } void ReserveDestination::KeepDestination() @@ -2450,12 +2547,10 @@ bool CWallet::UnlockAllCoins() return success; } -bool CWallet::IsLockedCoin(uint256 hash, unsigned int n) const +bool CWallet::IsLockedCoin(const COutPoint& output) const { AssertLockHeld(cs_wallet); - COutPoint outpt(hash, n); - - return (setLockedCoins.count(outpt) > 0); + return setLockedCoins.count(output) > 0; } void CWallet::ListLockedCoins(std::vector<COutPoint>& vOutpts) const @@ -2698,7 +2793,7 @@ std::shared_ptr<CWallet> CWallet::Create(WalletContext& context, const std::stri ArgsManager& args = *Assert(context.args); const std::string& walletFile = database->Filename(); - int64_t nStart = GetTimeMillis(); + const auto start{SteadyClock::now()}; // TODO: Can't use std::make_shared because we need a custom deleter but // should be possible to use std::allocate_shared. const std::shared_ptr<CWallet> walletInstance(new CWallet(chain, name, args, std::move(database)), ReleaseWallet); @@ -2731,8 +2826,12 @@ std::shared_ptr<CWallet> CWallet::Create(WalletContext& context, const std::stri warnings.push_back(strprintf(_("Error reading %s! Transaction data may be missing or incorrect." " Rescanning wallet."), walletFile)); rescan_required = true; - } - else { + } else if (nLoadWalletRet == DBErrors::UNKNOWN_DESCRIPTOR) { + error = strprintf(_("Unrecognized descriptor found. Loading wallet %s\n\n" + "The wallet might had been created on a newer version.\n" + "Please try running the latest software version.\n"), walletFile); + return nullptr; + } else { error = strprintf(_("Error loading %s"), walletFile); return nullptr; } @@ -2747,7 +2846,7 @@ std::shared_ptr<CWallet> CWallet::Create(WalletContext& context, const std::stri // ensure this wallet.dat can only be opened by clients supporting HD with chain split and expects no default key walletInstance->SetMinVersion(FEATURE_LATEST); - walletInstance->AddWalletFlags(wallet_creation_flags); + walletInstance->InitWalletFlags(wallet_creation_flags); // Only create LegacyScriptPubKeyMan when not descriptor wallet if (!walletInstance->IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS)) { @@ -2915,7 +3014,7 @@ std::shared_ptr<CWallet> CWallet::Create(WalletContext& context, const std::stri walletInstance->m_spend_zero_conf_change = args.GetBoolArg("-spendzeroconfchange", DEFAULT_SPEND_ZEROCONF_CHANGE); walletInstance->m_signal_rbf = args.GetBoolArg("-walletrbf", DEFAULT_WALLET_RBF); - walletInstance->WalletLogPrintf("Wallet completed loading in %15dms\n", GetTimeMillis() - nStart); + walletInstance->WalletLogPrintf("Wallet completed loading in %15dms\n", Ticks<std::chrono::milliseconds>(SteadyClock::now() - start)); // Try to top up keypool. No-op if the wallet is locked. walletInstance->TopUpKeyPool(); @@ -2993,20 +3092,33 @@ bool CWallet::AttachChain(const std::shared_ptr<CWallet>& walletInstance, interf if (tip_height && *tip_height != rescan_height) { - if (chain.havePruned()) { + // Technically we could execute the code below in any case, but performing the + // `while` loop below can make startup very slow, so only check blocks on disk + // if necessary. + if (chain.havePruned() || chain.hasAssumedValidChain()) { int block_height = *tip_height; while (block_height > 0 && chain.haveBlockOnDisk(block_height - 1) && rescan_height != block_height) { --block_height; } if (rescan_height != block_height) { - // We can't rescan beyond non-pruned blocks, stop and throw an error. + // We can't rescan beyond blocks we don't have data for, stop and throw an error. // This might happen if a user uses an old wallet within a pruned node // or if they ran -disablewallet for a longer time, then decided to re-enable // Exit early and print an error. + // It also may happen if an assumed-valid chain is in use and therefore not + // all block data is available. // If a block is pruned after this check, we will load the wallet, // but fail the rescan with a generic error. - error = _("Prune: last wallet synchronisation goes beyond pruned data. You need to -reindex (download the whole blockchain again in case of pruned node)"); + + error = chain.havePruned() ? + _("Prune: last wallet synchronisation goes beyond pruned data. You need to -reindex (download the whole blockchain again in case of pruned node)") : + strprintf(_( + "Error loading wallet. Wallet requires blocks to be downloaded, " + "and software does not currently support loading wallets while " + "blocks are being downloaded out of order when using assumeutxo " + "snapshots. Wallet should be able to load successfully after " + "node sync reaches height %s"), block_height); return false; } } @@ -3027,7 +3139,7 @@ bool CWallet::AttachChain(const std::shared_ptr<CWallet>& walletInstance, interf { WalletRescanReserver reserver(*walletInstance); - if (!reserver.reserve() || (ScanResult::SUCCESS != walletInstance->ScanForWalletTransactions(chain.getBlockHash(rescan_height), rescan_height, {} /* max height */, reserver, true /* update */).status)) { + if (!reserver.reserve() || (ScanResult::SUCCESS != walletInstance->ScanForWalletTransactions(chain.getBlockHash(rescan_height), rescan_height, /*max_height=*/{}, reserver, /*fUpdate=*/true, /*save_progress=*/true).status)) { error = _("Failed to rescan the wallet during initialization"); return false; } @@ -3090,7 +3202,7 @@ void CWallet::postInitProcess() // Add wallet transactions that aren't already in a block to mempool // Do this here as mempool requires genesis block to be loaded - ReacceptWalletTransactions(); + ResubmitWalletTransactions(/*relay=*/false, /*force=*/true); // Update wallet transactions with current mempool transactions. chain().requestMempoolTransactions(*this); @@ -3260,6 +3372,18 @@ std::unique_ptr<SigningProvider> CWallet::GetSolvingProvider(const CScript& scri return nullptr; } +std::vector<WalletDescriptor> CWallet::GetWalletDescriptors(const CScript& script) const +{ + std::vector<WalletDescriptor> descs; + for (const auto spk_man: GetScriptPubKeyMans(script)) { + if (const auto desc_spk_man = dynamic_cast<DescriptorScriptPubKeyMan*>(spk_man)) { + LOCK(desc_spk_man->cs_desc_man); + descs.push_back(desc_spk_man->GetWalletDescriptor()); + } + } + return descs; +} + LegacyScriptPubKeyMan* CWallet::GetLegacyScriptPubKeyMan() const { if (IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS)) { @@ -3321,6 +3445,29 @@ void CWallet::LoadDescriptorScriptPubKeyMan(uint256 id, WalletDescriptor& desc) } } +void CWallet::SetupDescriptorScriptPubKeyMans(const CExtKey& master_key) +{ + AssertLockHeld(cs_wallet); + + for (bool internal : {false, true}) { + for (OutputType t : OUTPUT_TYPES) { + auto spk_manager = std::unique_ptr<DescriptorScriptPubKeyMan>(new DescriptorScriptPubKeyMan(*this)); + if (IsCrypted()) { + if (IsLocked()) { + throw std::runtime_error(std::string(__func__) + ": Wallet is locked, cannot setup new descriptors"); + } + if (!spk_manager->CheckDecryptionKey(vMasterKey) && !spk_manager->Encrypt(vMasterKey, nullptr)) { + throw std::runtime_error(std::string(__func__) + ": Could not encrypt new descriptors"); + } + } + spk_manager->SetupDescriptorGeneration(master_key, t, internal); + uint256 id = spk_manager->GetID(); + m_spk_managers[id] = std::move(spk_manager); + AddActiveScriptPubKeyMan(id, t, internal); + } + } +} + void CWallet::SetupDescriptorScriptPubKeyMans() { AssertLockHeld(cs_wallet); @@ -3336,23 +3483,7 @@ void CWallet::SetupDescriptorScriptPubKeyMans() CExtKey master_key; master_key.SetSeed(seed_key); - for (bool internal : {false, true}) { - for (OutputType t : OUTPUT_TYPES) { - auto spk_manager = std::unique_ptr<DescriptorScriptPubKeyMan>(new DescriptorScriptPubKeyMan(*this)); - if (IsCrypted()) { - if (IsLocked()) { - throw std::runtime_error(std::string(__func__) + ": Wallet is locked, cannot setup new descriptors"); - } - if (!spk_manager->CheckDecryptionKey(vMasterKey) && !spk_manager->Encrypt(vMasterKey, nullptr)) { - throw std::runtime_error(std::string(__func__) + ": Could not encrypt new descriptors"); - } - } - spk_manager->SetupDescriptorGeneration(master_key, t, internal); - uint256 id = spk_manager->GetID(); - m_spk_managers[id] = std::move(spk_manager); - AddActiveScriptPubKeyMan(id, t, internal); - } - } + SetupDescriptorScriptPubKeyMans(master_key); } else { ExternalSigner signer = ExternalSignerScriptPubKeyMan::GetExternalSigner(); @@ -3365,7 +3496,7 @@ void CWallet::SetupDescriptorScriptPubKeyMans() const UniValue& descriptor_vals = find_value(signer_res, internal ? "internal" : "receive"); if (!descriptor_vals.isArray()) throw std::runtime_error(std::string(__func__) + ": Unexpected result"); for (const UniValue& desc_val : descriptor_vals.get_array().getValues()) { - std::string desc_str = desc_val.getValStr(); + const std::string& desc_str = desc_val.getValStr(); FlatSigningProvider keys; std::string desc_error; std::unique_ptr<Descriptor> desc = Parse(desc_str, keys, desc_error, false); @@ -3401,7 +3532,7 @@ void CWallet::LoadActiveScriptPubKeyMan(uint256 id, OutputType type, bool intern // Legacy wallets have only one ScriptPubKeyManager and it's active for all output and change types. Assert(IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS)); - WalletLogPrintf("Setting spkMan to active: id = %s, type = %d, internal = %d\n", id.ToString(), static_cast<int>(type), static_cast<int>(internal)); + WalletLogPrintf("Setting spkMan to active: id = %s, type = %s, internal = %s\n", id.ToString(), FormatOutputType(type), internal ? "true" : "false"); auto& spk_mans = internal ? m_internal_spk_managers : m_external_spk_managers; auto& spk_mans_other = internal ? m_external_spk_managers : m_internal_spk_managers; auto spk_man = m_spk_managers.at(id).get(); @@ -3419,7 +3550,7 @@ void CWallet::DeactivateScriptPubKeyMan(uint256 id, OutputType type, bool intern { auto spk_man = GetScriptPubKeyMan(type, internal); if (spk_man != nullptr && spk_man->GetID() == id) { - WalletLogPrintf("Deactivate spkMan: id = %s, type = %d, internal = %d\n", id.ToString(), static_cast<int>(type), static_cast<int>(internal)); + WalletLogPrintf("Deactivate spkMan: id = %s, type = %s, internal = %s\n", id.ToString(), FormatOutputType(type), internal ? "true" : "false"); WalletBatch batch(GetDatabase()); if (!batch.EraseActiveScriptPubKeyMan(static_cast<uint8_t>(type), internal)) { throw std::runtime_error(std::string(__func__) + ": erasing active ScriptPubKeyMan id failed"); @@ -3520,9 +3651,13 @@ ScriptPubKeyMan* CWallet::AddWalletDescriptor(WalletDescriptor& desc, const Flat return nullptr; } - CTxDestination dest; - if (!internal && ExtractDestination(script_pub_keys.at(0), dest)) { - SetAddressBook(dest, label, "receive"); + if (!internal) { + for (const auto& script : script_pub_keys) { + CTxDestination dest; + if (ExtractDestination(script, dest)) { + SetAddressBook(dest, label, "receive"); + } + } } } @@ -3531,4 +3666,472 @@ ScriptPubKeyMan* CWallet::AddWalletDescriptor(WalletDescriptor& desc, const Flat return spk_man; } + +bool CWallet::MigrateToSQLite(bilingual_str& error) +{ + AssertLockHeld(cs_wallet); + + WalletLogPrintf("Migrating wallet storage database from BerkeleyDB to SQLite.\n"); + + if (m_database->Format() == "sqlite") { + error = _("Error: This wallet already uses SQLite"); + return false; + } + + // Get all of the records for DB type migration + std::unique_ptr<DatabaseBatch> batch = m_database->MakeBatch(); + std::vector<std::pair<SerializeData, SerializeData>> records; + if (!batch->StartCursor()) { + error = _("Error: Unable to begin reading all records in the database"); + return false; + } + bool complete = false; + while (true) { + CDataStream ss_key(SER_DISK, CLIENT_VERSION); + CDataStream ss_value(SER_DISK, CLIENT_VERSION); + bool ret = batch->ReadAtCursor(ss_key, ss_value, complete); + if (!ret) { + break; + } + SerializeData key(ss_key.begin(), ss_key.end()); + SerializeData value(ss_value.begin(), ss_value.end()); + records.emplace_back(key, value); + } + batch->CloseCursor(); + batch.reset(); + if (!complete) { + error = _("Error: Unable to read all records in the database"); + return false; + } + + // Close this database and delete the file + fs::path db_path = fs::PathFromString(m_database->Filename()); + fs::path db_dir = db_path.parent_path(); + m_database->Close(); + fs::remove(db_path); + + // Make new DB + DatabaseOptions opts; + opts.require_create = true; + opts.require_format = DatabaseFormat::SQLITE; + DatabaseStatus db_status; + std::unique_ptr<WalletDatabase> new_db = MakeDatabase(db_dir, opts, db_status, error); + assert(new_db); // This is to prevent doing anything further with this wallet. The original file was deleted, but a backup exists. + m_database.reset(); + m_database = std::move(new_db); + + // Write existing records into the new DB + batch = m_database->MakeBatch(); + bool began = batch->TxnBegin(); + assert(began); // This is a critical error, the new db could not be written to. The original db exists as a backup, but we should not continue execution. + for (const auto& [key, value] : records) { + CDataStream ss_key(key, SER_DISK, CLIENT_VERSION); + CDataStream ss_value(value, SER_DISK, CLIENT_VERSION); + if (!batch->Write(ss_key, ss_value)) { + batch->TxnAbort(); + m_database->Close(); + fs::remove(m_database->Filename()); + assert(false); // This is a critical error, the new db could not be written to. The original db exists as a backup, but we should not continue execution. + } + } + bool committed = batch->TxnCommit(); + assert(committed); // This is a critical error, the new db could not be written to. The original db exists as a backup, but we should not continue execution. + return true; +} + +std::optional<MigrationData> CWallet::GetDescriptorsForLegacy(bilingual_str& error) const +{ + AssertLockHeld(cs_wallet); + + LegacyScriptPubKeyMan* legacy_spkm = GetLegacyScriptPubKeyMan(); + if (!legacy_spkm) { + error = _("Error: This wallet is already a descriptor wallet"); + return std::nullopt; + } + + std::optional<MigrationData> res = legacy_spkm->MigrateToDescriptor(); + if (res == std::nullopt) { + error = _("Error: Unable to produce descriptors for this legacy wallet. Make sure the wallet is unlocked first"); + return std::nullopt; + } + return res; +} + +bool CWallet::ApplyMigrationData(MigrationData& data, bilingual_str& error) +{ + AssertLockHeld(cs_wallet); + + LegacyScriptPubKeyMan* legacy_spkm = GetLegacyScriptPubKeyMan(); + if (!legacy_spkm) { + error = _("Error: This wallet is already a descriptor wallet"); + return false; + } + + for (auto& desc_spkm : data.desc_spkms) { + if (m_spk_managers.count(desc_spkm->GetID()) > 0) { + error = _("Error: Duplicate descriptors created during migration. Your wallet may be corrupted."); + return false; + } + m_spk_managers[desc_spkm->GetID()] = std::move(desc_spkm); + } + + // Remove the LegacyScriptPubKeyMan from disk + if (!legacy_spkm->DeleteRecords()) { + return false; + } + + // Remove the LegacyScriptPubKeyMan from memory + m_spk_managers.erase(legacy_spkm->GetID()); + m_external_spk_managers.clear(); + m_internal_spk_managers.clear(); + + // Setup new descriptors + SetWalletFlag(WALLET_FLAG_DESCRIPTORS); + if (!IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { + // Use the existing master key if we have it + if (data.master_key.key.IsValid()) { + SetupDescriptorScriptPubKeyMans(data.master_key); + } else { + // Setup with a new seed if we don't. + SetupDescriptorScriptPubKeyMans(); + } + } + + // Check if the transactions in the wallet are still ours. Either they belong here, or they belong in the watchonly wallet. + // We need to go through these in the tx insertion order so that lookups to spends works. + std::vector<uint256> txids_to_delete; + for (const auto& [_pos, wtx] : wtxOrdered) { + if (!IsMine(*wtx->tx) && !IsFromMe(*wtx->tx)) { + // Check it is the watchonly wallet's + // solvable_wallet doesn't need to be checked because transactions for those scripts weren't being watched for + if (data.watchonly_wallet) { + LOCK(data.watchonly_wallet->cs_wallet); + if (data.watchonly_wallet->IsMine(*wtx->tx) || data.watchonly_wallet->IsFromMe(*wtx->tx)) { + // Add to watchonly wallet + if (!data.watchonly_wallet->AddToWallet(wtx->tx, wtx->m_state)) { + error = _("Error: Could not add watchonly tx to watchonly wallet"); + return false; + } + // Mark as to remove from this wallet + txids_to_delete.push_back(wtx->GetHash()); + continue; + } + } + // Both not ours and not in the watchonly wallet + error = strprintf(_("Error: Transaction %s in wallet cannot be identified to belong to migrated wallets"), wtx->GetHash().GetHex()); + return false; + } + } + // Do the removes + if (txids_to_delete.size() > 0) { + std::vector<uint256> deleted_txids; + if (ZapSelectTx(txids_to_delete, deleted_txids) != DBErrors::LOAD_OK) { + error = _("Error: Could not delete watchonly transactions"); + return false; + } + if (deleted_txids != txids_to_delete) { + error = _("Error: Not all watchonly txs could be deleted"); + return false; + } + // Tell the GUI of each tx + for (const uint256& txid : deleted_txids) { + NotifyTransactionChanged(txid, CT_UPDATED); + } + } + + // Check the address book data in the same way we did for transactions + std::vector<CTxDestination> dests_to_delete; + for (const auto& addr_pair : m_address_book) { + // Labels applied to receiving addresses should go based on IsMine + if (addr_pair.second.purpose == "receive") { + if (!IsMine(addr_pair.first)) { + // Check the address book data is the watchonly wallet's + if (data.watchonly_wallet) { + LOCK(data.watchonly_wallet->cs_wallet); + if (data.watchonly_wallet->IsMine(addr_pair.first)) { + // Add to the watchonly. Preserve the labels, purpose, and change-ness + std::string label = addr_pair.second.GetLabel(); + std::string purpose = addr_pair.second.purpose; + if (!purpose.empty()) { + data.watchonly_wallet->m_address_book[addr_pair.first].purpose = purpose; + } + if (!addr_pair.second.IsChange()) { + data.watchonly_wallet->m_address_book[addr_pair.first].SetLabel(label); + } + dests_to_delete.push_back(addr_pair.first); + continue; + } + } + if (data.solvable_wallet) { + LOCK(data.solvable_wallet->cs_wallet); + if (data.solvable_wallet->IsMine(addr_pair.first)) { + // Add to the solvable. Preserve the labels, purpose, and change-ness + std::string label = addr_pair.second.GetLabel(); + std::string purpose = addr_pair.second.purpose; + if (!purpose.empty()) { + data.solvable_wallet->m_address_book[addr_pair.first].purpose = purpose; + } + if (!addr_pair.second.IsChange()) { + data.solvable_wallet->m_address_book[addr_pair.first].SetLabel(label); + } + dests_to_delete.push_back(addr_pair.first); + continue; + } + } + // Not ours, not in watchonly wallet, and not in solvable + error = _("Error: Address book data in wallet cannot be identified to belong to migrated wallets"); + return false; + } + } else { + // Labels for everything else (send) should be cloned to all + if (data.watchonly_wallet) { + LOCK(data.watchonly_wallet->cs_wallet); + // Add to the watchonly. Preserve the labels, purpose, and change-ness + std::string label = addr_pair.second.GetLabel(); + std::string purpose = addr_pair.second.purpose; + if (!purpose.empty()) { + data.watchonly_wallet->m_address_book[addr_pair.first].purpose = purpose; + } + if (!addr_pair.second.IsChange()) { + data.watchonly_wallet->m_address_book[addr_pair.first].SetLabel(label); + } + continue; + } + if (data.solvable_wallet) { + LOCK(data.solvable_wallet->cs_wallet); + // Add to the solvable. Preserve the labels, purpose, and change-ness + std::string label = addr_pair.second.GetLabel(); + std::string purpose = addr_pair.second.purpose; + if (!purpose.empty()) { + data.solvable_wallet->m_address_book[addr_pair.first].purpose = purpose; + } + if (!addr_pair.second.IsChange()) { + data.solvable_wallet->m_address_book[addr_pair.first].SetLabel(label); + } + continue; + } + } + } + // Remove the things to delete + if (dests_to_delete.size() > 0) { + for (const auto& dest : dests_to_delete) { + if (!DelAddressBook(dest)) { + error = _("Error: Unable to remove watchonly address book data"); + return false; + } + } + } + + // Connect the SPKM signals + ConnectScriptPubKeyManNotifiers(); + NotifyCanGetAddressesChanged(); + + WalletLogPrintf("Wallet migration complete.\n"); + + return true; +} + +bool DoMigration(CWallet& wallet, WalletContext& context, bilingual_str& error, MigrationResult& res) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet) +{ + AssertLockHeld(wallet.cs_wallet); + + // Get all of the descriptors from the legacy wallet + std::optional<MigrationData> data = wallet.GetDescriptorsForLegacy(error); + if (data == std::nullopt) return false; + + // Create the watchonly and solvable wallets if necessary + if (data->watch_descs.size() > 0 || data->solvable_descs.size() > 0) { + DatabaseOptions options; + options.require_existing = false; + options.require_create = true; + + // Make the wallets + options.create_flags = WALLET_FLAG_DISABLE_PRIVATE_KEYS | WALLET_FLAG_BLANK_WALLET | WALLET_FLAG_DESCRIPTORS; + if (wallet.IsWalletFlagSet(WALLET_FLAG_AVOID_REUSE)) { + options.create_flags |= WALLET_FLAG_AVOID_REUSE; + } + if (wallet.IsWalletFlagSet(WALLET_FLAG_KEY_ORIGIN_METADATA)) { + options.create_flags |= WALLET_FLAG_KEY_ORIGIN_METADATA; + } + if (data->watch_descs.size() > 0) { + wallet.WalletLogPrintf("Making a new watchonly wallet containing the watched scripts\n"); + + DatabaseStatus status; + std::vector<bilingual_str> warnings; + std::string wallet_name = wallet.GetName() + "_watchonly"; + data->watchonly_wallet = CreateWallet(context, wallet_name, std::nullopt, options, status, error, warnings); + if (status != DatabaseStatus::SUCCESS) { + error = _("Error: Failed to create new watchonly wallet"); + return false; + } + res.watchonly_wallet = data->watchonly_wallet; + LOCK(data->watchonly_wallet->cs_wallet); + + // Parse the descriptors and add them to the new wallet + for (const auto& [desc_str, creation_time] : data->watch_descs) { + // Parse the descriptor + FlatSigningProvider keys; + std::string parse_err; + std::unique_ptr<Descriptor> desc = Parse(desc_str, keys, parse_err, /* require_checksum */ true); + assert(desc); // It shouldn't be possible to have the LegacyScriptPubKeyMan make an invalid descriptor + assert(!desc->IsRange()); // It shouldn't be possible to have LegacyScriptPubKeyMan make a ranged watchonly descriptor + + // Add to the wallet + WalletDescriptor w_desc(std::move(desc), creation_time, 0, 0, 0); + data->watchonly_wallet->AddWalletDescriptor(w_desc, keys, "", false); + } + + // Add the wallet to settings + UpdateWalletSetting(*context.chain, wallet_name, /*load_on_startup=*/true, warnings); + } + if (data->solvable_descs.size() > 0) { + wallet.WalletLogPrintf("Making a new watchonly wallet containing the unwatched solvable scripts\n"); + + DatabaseStatus status; + std::vector<bilingual_str> warnings; + std::string wallet_name = wallet.GetName() + "_solvables"; + data->solvable_wallet = CreateWallet(context, wallet_name, std::nullopt, options, status, error, warnings); + if (status != DatabaseStatus::SUCCESS) { + error = _("Error: Failed to create new watchonly wallet"); + return false; + } + res.solvables_wallet = data->solvable_wallet; + LOCK(data->solvable_wallet->cs_wallet); + + // Parse the descriptors and add them to the new wallet + for (const auto& [desc_str, creation_time] : data->solvable_descs) { + // Parse the descriptor + FlatSigningProvider keys; + std::string parse_err; + std::unique_ptr<Descriptor> desc = Parse(desc_str, keys, parse_err, /* require_checksum */ true); + assert(desc); // It shouldn't be possible to have the LegacyScriptPubKeyMan make an invalid descriptor + assert(!desc->IsRange()); // It shouldn't be possible to have LegacyScriptPubKeyMan make a ranged watchonly descriptor + + // Add to the wallet + WalletDescriptor w_desc(std::move(desc), creation_time, 0, 0, 0); + data->solvable_wallet->AddWalletDescriptor(w_desc, keys, "", false); + } + + // Add the wallet to settings + UpdateWalletSetting(*context.chain, wallet_name, /*load_on_startup=*/true, warnings); + } + } + + // Add the descriptors to wallet, remove LegacyScriptPubKeyMan, and cleanup txs and address book data + if (!wallet.ApplyMigrationData(*data, error)) { + return false; + } + return true; +} + +util::Result<MigrationResult> MigrateLegacyToDescriptor(std::shared_ptr<CWallet>&& wallet, WalletContext& context) +{ + MigrationResult res; + bilingual_str error; + std::vector<bilingual_str> warnings; + + // Make a backup of the DB + std::string wallet_name = wallet->GetName(); + fs::path this_wallet_dir = fs::absolute(fs::PathFromString(wallet->GetDatabase().Filename())).parent_path(); + fs::path backup_filename = fs::PathFromString(strprintf("%s-%d.legacy.bak", wallet_name, GetTime())); + fs::path backup_path = this_wallet_dir / backup_filename; + if (!wallet->BackupWallet(fs::PathToString(backup_path))) { + return util::Error{_("Error: Unable to make a backup of your wallet")}; + } + res.backup_path = backup_path; + + // Unload the wallet so that nothing else tries to use it while we're changing it + if (!RemoveWallet(context, wallet, /*load_on_start=*/std::nullopt, warnings)) { + return util::Error{_("Unable to unload the wallet before migrating")}; + } + UnloadWallet(std::move(wallet)); + + // Load the wallet but only in the context of this function. + // No signals should be connected nor should anything else be aware of this wallet + WalletContext empty_context; + empty_context.args = context.args; + DatabaseOptions options; + options.require_existing = true; + DatabaseStatus status; + std::unique_ptr<WalletDatabase> database = MakeWalletDatabase(wallet_name, options, status, error); + if (!database) { + return util::Error{Untranslated("Wallet file verification failed.") + Untranslated(" ") + error}; + } + + std::shared_ptr<CWallet> local_wallet = CWallet::Create(empty_context, wallet_name, std::move(database), options.create_flags, error, warnings); + if (!local_wallet) { + return util::Error{Untranslated("Wallet loading failed.") + Untranslated(" ") + error}; + } + + bool success = false; + { + LOCK(local_wallet->cs_wallet); + + // First change to using SQLite + if (!local_wallet->MigrateToSQLite(error)) return util::Error{error}; + + // Do the migration, and cleanup if it fails + success = DoMigration(*local_wallet, context, error, res); + } + + if (success) { + // Migration successful, unload the wallet locally, then reload it. + assert(local_wallet.use_count() == 1); + local_wallet.reset(); + LoadWallet(context, wallet_name, /*load_on_start=*/std::nullopt, options, status, error, warnings); + res.wallet_name = wallet_name; + } else { + // Migration failed, cleanup + // Copy the backup to the actual wallet dir + fs::path temp_backup_location = fsbridge::AbsPathJoin(GetWalletDir(), backup_filename); + fs::copy_file(backup_path, temp_backup_location, fs::copy_options::none); + + // Remember this wallet's walletdir to remove after unloading + std::vector<fs::path> wallet_dirs; + wallet_dirs.push_back(fs::PathFromString(local_wallet->GetDatabase().Filename()).parent_path()); + + // Unload the wallet locally + assert(local_wallet.use_count() == 1); + local_wallet.reset(); + + // Make list of wallets to cleanup + std::vector<std::shared_ptr<CWallet>> created_wallets; + created_wallets.push_back(std::move(res.watchonly_wallet)); + created_wallets.push_back(std::move(res.solvables_wallet)); + + // Get the directories to remove after unloading + for (std::shared_ptr<CWallet>& w : created_wallets) { + wallet_dirs.push_back(fs::PathFromString(w->GetDatabase().Filename()).parent_path()); + } + + // Unload the wallets + for (std::shared_ptr<CWallet>& w : created_wallets) { + if (!RemoveWallet(context, w, /*load_on_start=*/false)) { + error += _("\nUnable to cleanup failed migration"); + return util::Error{error}; + } + UnloadWallet(std::move(w)); + } + + // Delete the wallet directories + for (fs::path& dir : wallet_dirs) { + fs::remove_all(dir); + } + + // Restore the backup + DatabaseStatus status; + std::vector<bilingual_str> warnings; + if (!RestoreWallet(context, temp_backup_location, wallet_name, /*load_on_start=*/std::nullopt, status, error, warnings)) { + error += _("\nUnable to restore backup of wallet."); + return util::Error{error}; + } + + // Move the backup to the wallet dir + fs::copy_file(temp_backup_location, backup_path, fs::copy_options::none); + fs::remove(temp_backup_location); + + return util::Error{error}; + } + return res; +} } // namespace wallet diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 7da601c3b7..953d3e5bde 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -14,7 +14,9 @@ #include <policy/feerate.h> #include <psbt.h> #include <tinyformat.h> +#include <util/hasher.h> #include <util/message.h> +#include <util/result.h> #include <util/strencodings.h> #include <util/string.h> #include <util/system.h> @@ -36,6 +38,7 @@ #include <stdint.h> #include <string> #include <utility> +#include <unordered_map> #include <vector> #include <boost/signals2/signal.hpp> @@ -45,7 +48,6 @@ using LoadWalletFn = std::function<void(std::unique_ptr<interfaces::Wallet> wall class CScript; enum class FeeEstimateMode; -struct FeeCalculation; struct bilingual_str; namespace wallet { @@ -62,6 +64,7 @@ bool AddWallet(WalletContext& context, const std::shared_ptr<CWallet>& wallet); bool RemoveWallet(WalletContext& context, const std::shared_ptr<CWallet>& wallet, std::optional<bool> load_on_start, std::vector<bilingual_str>& warnings); bool RemoveWallet(WalletContext& context, const std::shared_ptr<CWallet>& wallet, std::optional<bool> load_on_start); std::vector<std::shared_ptr<CWallet>> GetWallets(WalletContext& context); +std::shared_ptr<CWallet> GetDefaultWallet(WalletContext& context, size_t& count); std::shared_ptr<CWallet> GetWallet(WalletContext& context, const std::string& name); std::shared_ptr<CWallet> LoadWallet(WalletContext& context, 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(WalletContext& context, const std::string& name, std::optional<bool> load_on_start, DatabaseOptions& options, DatabaseStatus& status, bilingual_str& error, std::vector<bilingual_str>& warnings); @@ -90,7 +93,7 @@ static const CAmount DEFAULT_CONSOLIDATE_FEERATE{10000}; // 10 sat/vbyte static const CAmount DEFAULT_MAX_AVOIDPARTIALSPEND_FEE = 0; //! discourage APS fee higher than this amount constexpr CAmount HIGH_APS_FEE{COIN / 10000}; -//! minimum recommended increment for BIP 125 replacement txs +//! minimum recommended increment for replacement txs static const CAmount WALLET_INCREMENTAL_RELAY_FEE = 5000; //! Default for -spendzeroconfchange static const bool DEFAULT_SPEND_ZEROCONF_CHANGE = true; @@ -99,7 +102,7 @@ static const bool DEFAULT_WALLET_REJECT_LONG_CHAINS{true}; //! -txconfirmtarget default static const unsigned int DEFAULT_TX_CONFIRM_TARGET = 6; //! -walletrbf default -static const bool DEFAULT_WALLET_RBF = false; +static const bool DEFAULT_WALLET_RBF = true; static const bool DEFAULT_WALLETBROADCAST = true; static const bool DEFAULT_DISABLE_WALLET = false; static const bool DEFAULT_WALLETCROSSCHAIN = false; @@ -189,10 +192,10 @@ public: } //! Reserve an address - bool GetReservedDestination(CTxDestination& pubkey, bool internal, bilingual_str& error); + util::Result<CTxDestination> GetReservedDestination(bool internal); //! Return reserved address void ReturnDestination(); - //! Keep the address. Do not return it's key to the keypool when this object goes out of scope + //! Keep the address. Do not return its key to the keypool when this object goes out of scope void KeepDestination(); }; @@ -247,7 +250,7 @@ private: int nWalletVersion GUARDED_BY(cs_wallet){FEATURE_BASE}; /** The next scheduled rebroadcast of wallet transactions. */ - int64_t nNextResend = 0; + std::atomic<int64_t> m_next_resend{}; /** Whether this wallet will submit newly created transactions to the node's mempool and * prompt rebroadcasts (see ResendWalletTransactions()). */ bool fBroadcastTransactions = false; @@ -259,10 +262,10 @@ private: * detect and report conflicts (double-spends or * mutated transactions where the mutant gets mined). */ - typedef std::multimap<COutPoint, uint256> TxSpends; + typedef std::unordered_multimap<COutPoint, uint256, SaltedOutpointHasher> TxSpends; TxSpends mapTxSpends GUARDED_BY(cs_wallet); void AddToSpends(const COutPoint& outpoint, const uint256& wtxid, WalletBatch* batch = nullptr) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); - void AddToSpends(const uint256& wtxid, WalletBatch* batch = nullptr) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + void AddToSpends(const CWalletTx& wtx, WalletBatch* batch = nullptr) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); /** * Add a transaction to the wallet, or update it. confirm.block_* should @@ -313,7 +316,7 @@ private: std::string m_name; /** Internal database handle. */ - std::unique_ptr<WalletDatabase> const m_database; + std::unique_ptr<WalletDatabase> m_database; /** * The following is used to keep track of how far behind the wallet is @@ -390,7 +393,7 @@ public: /** Map from txid to CWalletTx for all transactions this wallet is * interested in, including received and sent transactions. */ - std::map<uint256, CWalletTx> mapWallet GUARDED_BY(cs_wallet); + std::unordered_map<uint256, CWalletTx, SaltedTxidHasher> mapWallet GUARDED_BY(cs_wallet); typedef std::multimap<int64_t, CWalletTx*> TxItems; TxItems wtxOrdered; @@ -441,16 +444,16 @@ public: //! check whether we support the named feature bool CanSupportFeature(enum WalletFeature wf) const override EXCLUSIVE_LOCKS_REQUIRED(cs_wallet) { AssertLockHeld(cs_wallet); return IsFeatureSupported(nWalletVersion, wf); } - bool IsSpent(const uint256& hash, unsigned int n) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + bool IsSpent(const COutPoint& outpoint) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); - // Whether this or any known UTXO with the same single key has been spent. - bool IsSpentKey(const uint256& hash, unsigned int n) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + // Whether this or any known scriptPubKey with the same single key has been spent. + bool IsSpentKey(const CScript& scriptPubKey) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); void SetSpentKeyState(WalletBatch& batch, const uint256& hash, unsigned int n, bool used, std::set<CTxDestination>& tx_destinations) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); /** Display address on an external signer. Returns false if external signer support is not compiled */ bool DisplayAddress(const CTxDestination& dest) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); - bool IsLockedCoin(uint256 hash, unsigned int n) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + bool IsLockedCoin(const COutPoint& output) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); bool LockCoin(const COutPoint& output, WalletBatch* batch = nullptr) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); bool UnlockCoin(const COutPoint& output, WalletBatch* batch = nullptr) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); bool UnlockAllCoins() EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); @@ -505,11 +508,15 @@ public: //! @return true if wtx is changed and needs to be saved to disk, otherwise false using UpdateWalletTxFn = std::function<bool(CWalletTx& wtx, bool new_tx)>; + /** + * Add the transaction to the wallet, wrapping it up inside a CWalletTx + * @return the recently added wtx pointer or nullptr if there was a db write error. + */ CWalletTx* AddToWallet(CTransactionRef tx, const TxState& state, const UpdateWalletTxFn& update_wtx=nullptr, bool fFlushOnClose=true, bool rescanning_old_block = false); bool LoadToWallet(const uint256& hash, const UpdateWalletTxFn& fill_wtx) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); void transactionAddedToMempool(const CTransactionRef& tx, uint64_t mempool_sequence) override; - void blockConnected(const CBlock& block, int height) override; - void blockDisconnected(const CBlock& block, int height) override; + void blockConnected(const interfaces::BlockInfo& block) override; + void blockDisconnected(const interfaces::BlockInfo& block) override; void updatedBlockTip() override; int64_t RescanFromTime(int64_t startTime, const WalletRescanReserver& reserver, bool update); @@ -528,10 +535,9 @@ public: //! USER_ABORT. uint256 last_failed_block; }; - ScanResult ScanForWalletTransactions(const uint256& start_block, int start_height, std::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, const bool save_progress); void transactionRemovedFromMempool(const CTransactionRef& tx, MemPoolRemovalReason reason, uint64_t mempool_sequence) override; - void ReacceptWalletTransactions() EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); - void ResendWalletTransactions(); + void ResubmitWalletTransactions(bool relay, bool force); OutputType TransactionChangeType(const std::optional<OutputType>& change_type, const std::vector<CRecipient>& vecSend) const; @@ -635,7 +641,30 @@ public: std::optional<int64_t> GetOldestKeyPoolTime() const; - std::set<CTxDestination> GetLabelAddresses(const std::string& label) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + // Filter struct for 'ListAddrBookAddresses' + struct AddrBookFilter { + // Fetch addresses with the provided label + std::optional<std::string> m_op_label{std::nullopt}; + // Don't include change addresses by default + bool ignore_change{true}; + }; + + /** + * Filter and retrieve destinations stored in the addressbook + */ + std::vector<CTxDestination> ListAddrBookAddresses(const std::optional<AddrBookFilter>& filter) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + + /** + * Retrieve all the known labels in the address book + */ + std::set<std::string> ListAddrBookLabels(const std::string& purpose) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + + /** + * Walk-through the address book entries. + * Stops when the provided 'ListAddrBookFunc' returns false. + */ + using ListAddrBookFunc = std::function<void(const CTxDestination& dest, const std::string& label, const std::string& purpose, bool is_change)>; + void ForEachAddrBookEntry(const ListAddrBookFunc& func) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); /** * Marks all outputs in each one of the destinations dirty, so their cache is @@ -643,8 +672,8 @@ public: */ void MarkDestinationsDirty(const std::set<CTxDestination>& destinations) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); - bool GetNewDestination(const OutputType type, const std::string label, CTxDestination& dest, bilingual_str& error); - bool GetNewChangeDestination(const OutputType type, CTxDestination& dest, bilingual_str& error); + util::Result<CTxDestination> GetNewDestination(const OutputType type, const std::string label); + util::Result<CTxDestination> GetNewChangeDestination(const OutputType type); isminetype IsMine(const CTxDestination& dest) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); isminetype IsMine(const CScript& script) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); @@ -655,6 +684,7 @@ public: CAmount GetDebit(const CTxIn& txin, const isminefilter& filter) const; isminetype IsMine(const CTxOut& txout) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); bool IsMine(const CTransaction& tx) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + isminetype IsMine(const COutPoint& outpoint) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); /** should probably be renamed to IsRelevantToMe */ bool IsFromMe(const CTransaction& tx) const; CAmount GetDebit(const CTransaction& tx, const isminefilter& filter) const; @@ -685,7 +715,7 @@ public: std::set<uint256> GetConflicts(const uint256& txid) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); //! Check if a given transaction has any of its outputs spent by another transaction in the wallet - bool HasWalletSpend(const uint256& txid) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + bool HasWalletSpend(const CTransactionRef& tx) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); //! Flush wallet (bitdb flush) void Flush(); @@ -737,7 +767,7 @@ public: /* Mark a transaction (and it in-wallet descendants) as abandoned so its inputs may be respent. */ bool AbandonTransaction(const uint256& hashTx); - /** Mark a transaction as replaced by another transaction (e.g., BIP 125). */ + /** Mark a transaction as replaced by another transaction. */ bool MarkReplaced(const uint256& originalHash, const uint256& newHash); /* Initializes the wallet, returns a new CWallet instance or a null pointer in case of an error */ @@ -775,8 +805,9 @@ public: bool IsWalletFlagSet(uint64_t flag) const override; /** overwrite all flags by the given uint64_t - returns false if unknown, non-tolerable flags are present */ - bool AddWalletFlags(uint64_t flags); + flags must be uninitialised (or 0) + only known flags may be present */ + void InitWalletFlags(uint64_t flags); /** Loads the flags into the wallet. (used by LoadWallet) */ bool LoadWalletFlags(uint64_t flags); @@ -816,6 +847,9 @@ public: std::unique_ptr<SigningProvider> GetSolvingProvider(const CScript& script) const; std::unique_ptr<SigningProvider> GetSolvingProvider(const CScript& script, SignatureData& sigdata) const; + //! Get the wallet descriptors for a script. + std::vector<WalletDescriptor> GetWalletDescriptors(const CScript& script) const; + //! Get the LegacyScriptPubKeyMan which is used for all types, internal, and external. LegacyScriptPubKeyMan* GetLegacyScriptPubKeyMan() const; LegacyScriptPubKeyMan* GetOrCreateLegacyScriptPubKeyMan(); @@ -872,6 +906,7 @@ public: void DeactivateScriptPubKeyMan(uint256 id, OutputType type, bool internal); //! Create new DescriptorScriptPubKeyMans and add them to the wallet + void SetupDescriptorScriptPubKeyMans(const CExtKey& master_key) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); void SetupDescriptorScriptPubKeyMans() EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); //! Return the DescriptorScriptPubKeyMan for a WalletDescriptor if it is already in the wallet @@ -884,6 +919,20 @@ public: //! Add a descriptor to the wallet, return a ScriptPubKeyMan & associated output type ScriptPubKeyMan* AddWalletDescriptor(WalletDescriptor& desc, const FlatSigningProvider& signing_provider, const std::string& label, bool internal) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + + /** Move all records from the BDB database to a new SQLite database for storage. + * The original BDB file will be deleted and replaced with a new SQLite file. + * A backup is not created. + * May crash if something unexpected happens in the filesystem. + */ + bool MigrateToSQLite(bilingual_str& error) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + + //! Get all of the descriptors from a legacy wallet + std::optional<MigrationData> GetDescriptorsForLegacy(bilingual_str& error) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + + //! Adds the ScriptPubKeyMans given in MigrationData to this wallet, removes LegacyScriptPubKeyMan, + //! and where needed, moves tx and address book entries to watchonly_wallet or solvable_wallet + bool ApplyMigrationData(MigrationData& data, bilingual_str& error) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); }; /** @@ -896,8 +945,11 @@ void MaybeResendWalletTxs(WalletContext& context); class WalletRescanReserver { private: + using Clock = std::chrono::steady_clock; + using NowFn = std::function<Clock::time_point()>; CWallet& m_wallet; bool m_could_reserve; + NowFn m_now; public: explicit WalletRescanReserver(CWallet& w) : m_wallet(w), m_could_reserve(false) {} @@ -918,6 +970,10 @@ public: return (m_could_reserve && m_wallet.fScanningWallet); } + Clock::time_point now() const { return m_now ? m_now() : Clock::now(); }; + + void setNow(NowFn now) { m_now = std::move(now); } + ~WalletRescanReserver() { if (m_could_reserve) { @@ -932,9 +988,19 @@ bool AddWalletSetting(interfaces::Chain& chain, const std::string& wallet_name); //! Remove wallet name from persistent configuration so it will not be loaded on startup. bool RemoveWalletSetting(interfaces::Chain& chain, const std::string& wallet_name); -bool DummySignInput(const SigningProvider& provider, CTxIn &tx_in, const CTxOut &txout, bool use_max_sig); +bool DummySignInput(const SigningProvider& provider, CTxIn &tx_in, const CTxOut &txout, const CCoinControl* coin_control = nullptr); bool FillInputToWeight(CTxIn& txin, int64_t target_weight); + +struct MigrationResult { + std::string wallet_name; + std::shared_ptr<CWallet> watchonly_wallet; + std::shared_ptr<CWallet> solvables_wallet; + fs::path backup_path; +}; + +//! Do all steps to migrate a legacy wallet to a descriptor wallet +util::Result<MigrationResult> MigrateLegacyToDescriptor(std::shared_ptr<CWallet>&& wallet, WalletContext& context); } // namespace wallet #endif // BITCOIN_WALLET_WALLET_H diff --git a/src/wallet/walletdb.cpp b/src/wallet/walletdb.cpp index 79e0a330b7..6a8f0d2481 100644 --- a/src/wallet/walletdb.cpp +++ b/src/wallet/walletdb.cpp @@ -59,6 +59,7 @@ const std::string WALLETDESCRIPTORCKEY{"walletdescriptorckey"}; const std::string WALLETDESCRIPTORKEY{"walletdescriptorkey"}; const std::string WATCHMETA{"watchmeta"}; const std::string WATCHS{"watchs"}; +const std::unordered_set<std::string> LEGACY_TYPES{CRYPTED_KEY, CSCRIPT, DEFAULTKEY, HDCHAIN, KEYMETA, KEY, OLD_KEY, POOL, WATCHMETA, WATCHS}; } // namespace DBKeys // @@ -313,6 +314,7 @@ public: std::map<std::pair<uint256, CKeyID>, std::pair<CPubKey, std::vector<unsigned char>>> m_descriptor_crypt_keys; std::map<uint160, CHDChain> m_hd_chains; bool tx_corrupt{false}; + bool descriptor_unknown{false}; CWalletScanState() = default; }; @@ -626,7 +628,13 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue, uint256 id; ssKey >> id; WalletDescriptor desc; - ssValue >> desc; + try { + ssValue >> desc; + } catch (const std::ios_base::failure& e) { + strErr = e.what(); + wss.descriptor_unknown = true; + return false; + } if (wss.m_descriptor_caches.count(id) == 0) { wss.m_descriptor_caches[id] = DescriptorCache(); } @@ -766,6 +774,12 @@ DBErrors WalletBatch::LoadWallet(CWallet* pwallet) DBErrors result = DBErrors::LOAD_OK; LOCK(pwallet->cs_wallet); + + // Last client version to open this wallet + int last_client = CLIENT_VERSION; + bool has_last_client = m_batch->Read(DBKeys::VERSION, last_client); + pwallet->WalletLogPrintf("Wallet file version = %d, last client version = %d\n", pwallet->GetVersion(), last_client); + try { int nMinVersion = 0; if (m_batch->Read(DBKeys::MINVERSION, nMinVersion)) { @@ -831,6 +845,13 @@ DBErrors WalletBatch::LoadWallet(CWallet* pwallet) // Set tx_corrupt back to false so that the error is only printed once (per corrupt tx) wss.tx_corrupt = false; result = DBErrors::CORRUPT; + } else if (wss.descriptor_unknown) { + strErr = strprintf("Error: Unrecognized descriptor found in wallet %s. ", pwallet->GetName()); + strErr += (last_client > CLIENT_VERSION) ? "The wallet might had been created on a newer version. " : + "The database might be corrupted or the software version is not compatible with one of your wallet descriptors. "; + strErr += "Please try running the latest software version"; + pwallet->WalletLogPrintf("%s\n", strErr); + return DBErrors::UNKNOWN_DESCRIPTOR; } else { // Leave other errors alone, if we try to fix them we might make things worse. fNoncriticalErrors = true; // ... but do warn the user there is something wrong. @@ -856,18 +877,18 @@ DBErrors WalletBatch::LoadWallet(CWallet* pwallet) } // Set the descriptor caches - for (auto desc_cache_pair : wss.m_descriptor_caches) { + for (const auto& desc_cache_pair : wss.m_descriptor_caches) { auto spk_man = pwallet->GetScriptPubKeyMan(desc_cache_pair.first); assert(spk_man); ((DescriptorScriptPubKeyMan*)spk_man)->SetCache(desc_cache_pair.second); } // Set the descriptor keys - for (auto desc_key_pair : wss.m_descriptor_keys) { + for (const auto& desc_key_pair : wss.m_descriptor_keys) { auto spk_man = pwallet->GetScriptPubKeyMan(desc_key_pair.first.first); ((DescriptorScriptPubKeyMan*)spk_man)->AddKey(desc_key_pair.first.second, desc_key_pair.second); } - for (auto desc_key_pair : wss.m_descriptor_crypt_keys) { + for (const auto& desc_key_pair : wss.m_descriptor_crypt_keys) { auto spk_man = pwallet->GetScriptPubKeyMan(desc_key_pair.first.first); ((DescriptorScriptPubKeyMan*)spk_man)->AddCryptedKey(desc_key_pair.first.second, desc_key_pair.second.first, desc_key_pair.second.second); } @@ -883,13 +904,6 @@ DBErrors WalletBatch::LoadWallet(CWallet* pwallet) if (result != DBErrors::LOAD_OK) return result; - // Last client version to open this wallet, was previously the file version number - int last_client = CLIENT_VERSION; - m_batch->Read(DBKeys::VERSION, last_client); - - int wallet_version = pwallet->GetVersion(); - pwallet->WalletLogPrintf("Wallet File Version = %d\n", wallet_version > 0 ? wallet_version : last_client); - pwallet->WalletLogPrintf("Keys: %u plaintext, %u encrypted, %u w/ metadata, %u total. Unknown wallet records: %u\n", wss.nKeys, wss.nCKeys, wss.nKeyMeta, wss.nKeys + wss.nCKeys, wss.m_unknown_records); @@ -909,7 +923,7 @@ DBErrors WalletBatch::LoadWallet(CWallet* pwallet) if (wss.fIsEncrypted && (last_client == 40000 || last_client == 50000)) return DBErrors::NEED_REWRITE; - if (last_client < CLIENT_VERSION) // Update + if (!has_last_client || last_client != CLIENT_VERSION) // Update m_batch->Write(DBKeys::VERSION, CLIENT_VERSION); if (wss.fAnyUnordered) @@ -1085,6 +1099,45 @@ bool WalletBatch::WriteWalletFlags(const uint64_t flags) return WriteIC(DBKeys::FLAGS, flags); } +bool WalletBatch::EraseRecords(const std::unordered_set<std::string>& types) +{ + // Get cursor + if (!m_batch->StartCursor()) + { + return false; + } + + // Iterate the DB and look for any records that have the type prefixes + while (true) + { + // Read next record + CDataStream key(SER_DISK, CLIENT_VERSION); + CDataStream value(SER_DISK, CLIENT_VERSION); + bool complete; + bool ret = m_batch->ReadAtCursor(key, value, complete); + if (complete) { + break; + } + else if (!ret) + { + m_batch->CloseCursor(); + return false; + } + + // Make a copy of key to avoid data being deleted by the following read of the type + Span<const unsigned char> key_data = MakeUCharSpan(key); + + std::string type; + key >> type; + + if (types.count(type) > 0) { + m_batch->Erase(key_data); + } + } + m_batch->CloseCursor(); + return true; +} + bool WalletBatch::TxnBegin() { return m_batch->TxnBegin(); @@ -1186,13 +1239,36 @@ std::unique_ptr<WalletDatabase> CreateDummyWalletDatabase() } /** Return object for accessing temporary in-memory database. */ -std::unique_ptr<WalletDatabase> CreateMockWalletDatabase() +std::unique_ptr<WalletDatabase> CreateMockWalletDatabase(DatabaseOptions& options) { - DatabaseOptions options; + + std::optional<DatabaseFormat> format; + if (options.require_format) format = options.require_format; + if (!format) { +#ifdef USE_BDB + format = DatabaseFormat::BERKELEY; +#endif +#ifdef USE_SQLITE + format = DatabaseFormat::SQLITE; +#endif + } + + if (format == DatabaseFormat::SQLITE) { #ifdef USE_SQLITE - return std::make_unique<SQLiteDatabase>("", "", options, true); -#elif USE_BDB + return std::make_unique<SQLiteDatabase>(":memory:", "", options, true); +#endif + assert(false); + } + +#ifdef USE_BDB return std::make_unique<BerkeleyDatabase>(std::make_shared<BerkeleyEnvironment>(), "", options); #endif + assert(false); +} + +std::unique_ptr<WalletDatabase> CreateMockWalletDatabase() +{ + DatabaseOptions options; + return CreateMockWalletDatabase(options); } } // namespace wallet diff --git a/src/wallet/walletdb.h b/src/wallet/walletdb.h index 3dfe781d56..da6efe534b 100644 --- a/src/wallet/walletdb.h +++ b/src/wallet/walletdb.h @@ -51,7 +51,8 @@ enum class DBErrors EXTERNAL_SIGNER_SUPPORT_REQUIRED, LOAD_FAIL, NEED_REWRITE, - NEED_RESCAN + NEED_RESCAN, + UNKNOWN_DESCRIPTOR }; namespace DBKeys { @@ -84,6 +85,9 @@ extern const std::string WALLETDESCRIPTORCKEY; extern const std::string WALLETDESCRIPTORKEY; extern const std::string WATCHMETA; extern const std::string WATCHS; + +// Keys in this set pertain only to the legacy wallet (LegacyScriptPubKeyMan) and are removed during migration from legacy to descriptors. +extern const std::unordered_set<std::string> LEGACY_TYPES; } // namespace DBKeys /* simple HD chain data model */ @@ -276,6 +280,9 @@ public: //! write the hdchain model (external chain child index counter) bool WriteHDChain(const CHDChain& chain); + //! Delete records of the given types + bool EraseRecords(const std::unordered_set<std::string>& types); + bool WriteWalletFlags(const uint64_t flags); //! Begin a new transaction bool TxnBegin(); @@ -301,6 +308,7 @@ bool ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue, st std::unique_ptr<WalletDatabase> CreateDummyWalletDatabase(); /** Return object for accessing temporary in-memory database. */ +std::unique_ptr<WalletDatabase> CreateMockWalletDatabase(DatabaseOptions& options); std::unique_ptr<WalletDatabase> CreateMockWalletDatabase(); } // namespace wallet diff --git a/src/wallet/wallettool.cpp b/src/wallet/wallettool.cpp index 769175b5a8..e991bc0814 100644 --- a/src/wallet/wallettool.cpp +++ b/src/wallet/wallettool.cpp @@ -34,7 +34,7 @@ static void WalletCreate(CWallet* wallet_instance, uint64_t wallet_creation_flag LOCK(wallet_instance->cs_wallet); wallet_instance->SetMinVersion(FEATURE_LATEST); - wallet_instance->AddWalletFlags(wallet_creation_flags); + wallet_instance->InitWalletFlags(wallet_creation_flags); if (!wallet_instance->IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS)) { auto spk_man = wallet_instance->GetOrCreateLegacyScriptPubKeyMan(); diff --git a/src/wallet/walletutil.h b/src/wallet/walletutil.h index 788d41ceb7..8434d64fb5 100644 --- a/src/wallet/walletutil.h +++ b/src/wallet/walletutil.h @@ -104,6 +104,20 @@ public: WalletDescriptor() {} WalletDescriptor(std::shared_ptr<Descriptor> descriptor, uint64_t creation_time, int32_t range_start, int32_t range_end, int32_t next_index) : descriptor(descriptor), creation_time(creation_time), range_start(range_start), range_end(range_end), next_index(next_index) {} }; + +class CWallet; +class DescriptorScriptPubKeyMan; + +/** struct containing information needed for migrating legacy wallets to descriptor wallets */ +struct MigrationData +{ + CExtKey master_key; + std::vector<std::pair<std::string, int64_t>> watch_descs; + std::vector<std::pair<std::string, int64_t>> solvable_descs; + std::vector<std::unique_ptr<DescriptorScriptPubKeyMan>> desc_spkms; + std::shared_ptr<CWallet> watchonly_wallet{nullptr}; + std::shared_ptr<CWallet> solvable_wallet{nullptr}; +}; } // namespace wallet #endif // BITCOIN_WALLET_WALLETUTIL_H diff --git a/src/warnings.cpp b/src/warnings.cpp index 60388cc713..dabb194ce1 100644 --- a/src/warnings.cpp +++ b/src/warnings.cpp @@ -12,7 +12,7 @@ #include <vector> -static Mutex g_warnings_mutex; +static GlobalMutex g_warnings_mutex; static bilingual_str g_misc_warnings GUARDED_BY(g_warnings_mutex); static bool fLargeWorkInvalidChainFound GUARDED_BY(g_warnings_mutex) = false; diff --git a/src/zmq/zmqnotificationinterface.cpp b/src/zmq/zmqnotificationinterface.cpp index 26618735f6..b9b7019a3c 100644 --- a/src/zmq/zmqnotificationinterface.cpp +++ b/src/zmq/zmqnotificationinterface.cpp @@ -8,7 +8,7 @@ #include <zmq.h> -#include <validation.h> +#include <primitives/block.h> #include <util/system.h> CZMQNotificationInterface::CZMQNotificationInterface() : pcontext(nullptr) diff --git a/src/zmq/zmqpublishnotifier.cpp b/src/zmq/zmqpublishnotifier.cpp index 011336bf72..51c8ad515e 100644 --- a/src/zmq/zmqpublishnotifier.cpp +++ b/src/zmq/zmqpublishnotifier.cpp @@ -11,7 +11,6 @@ #include <rpc/server.h> #include <streams.h> #include <util/system.h> -#include <validation.h> // For cs_main #include <zmq/zmqutil.h> #include <zmq.h> diff --git a/test/README.md b/test/README.md index 6ca7cc0016..fdbb91832a 100644 --- a/test/README.md +++ b/test/README.md @@ -95,12 +95,14 @@ Run all possible tests with test/functional/test_runner.py --extended ``` -In order to run backwards compatibility tests, download the previous node binaries: +In order to run backwards compatibility tests, first run: ``` -test/get_previous_releases.py -b v23.0 v22.0 v0.21.0 v0.20.1 v0.19.1 v0.18.1 v0.17.2 v0.16.3 v0.15.2 v0.14.3 +test/get_previous_releases.py -b ``` +to download the necessary previous release binaries. + By default, up to 4 tests will be run in parallel by test_runner. To specify how many jobs to run, append `--jobs=n` @@ -270,8 +272,8 @@ gdb /home/example/bitcoind <pid> Note: gdb attach step may require ptrace_scope to be modified, or `sudo` preceding the `gdb`. See this link for considerations: https://www.kernel.org/doc/Documentation/security/Yama.txt -Often while debugging rpc calls from functional tests, the test might reach timeout before -process can return a response. Use `--timeout-factor 0` to disable all rpc timeouts for that partcular +Often while debugging RPC calls in functional tests, the test might time out before the +process can return a response. Use `--timeout-factor 0` to disable all RPC timeouts for that particular functional test. Ex: `test/functional/wallet_hd.py --timeout-factor 0`. ##### Profiling diff --git a/test/functional/README.md b/test/functional/README.md index 914dbfd977..1bd618a0c3 100644 --- a/test/functional/README.md +++ b/test/functional/README.md @@ -28,7 +28,9 @@ don't have test cases for. could lead to bugs and issues in the test code. - Use [type hints](https://docs.python.org/3/library/typing.html) in your code to improve code readability and to detect possible bugs earlier. -- Avoid wildcard imports +- Avoid wildcard imports. +- If more than one name from a module is needed, use lexicographically sorted multi-line imports + in order to reduce the possibility of potential merge conflicts. - Use a module-level docstring to describe what the test is testing, and how it is testing it. - When subclassing the BitcoinTestFramework, place overrides for the diff --git a/test/functional/data/rpc_decodescript.json b/test/functional/data/rpc_decodescript.json index 8903f5efac..4a15ae8792 100644 --- a/test/functional/data/rpc_decodescript.json +++ b/test/functional/data/rpc_decodescript.json @@ -4,7 +4,7 @@ { "asm": "1 eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee", "address": "bcrt1pamhwamhwamhwamhwamhwamhwamhwamhwamhwamhwamhwamhwamhqz6nvlh", - "desc": "addr(bcrt1pamhwamhwamhwamhwamhwamhwamhwamhwamhwamhwamhwamhwamhqz6nvlh)#v52jnujz", + "desc": "rawtr(eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee)#jk7c6kys", "type": "witness_v1_taproot" } ], diff --git a/test/functional/data/rpc_psbt.json b/test/functional/data/rpc_psbt.json index 8672400a92..657faebffc 100644 --- a/test/functional/data/rpc_psbt.json +++ b/test/functional/data/rpc_psbt.json @@ -27,7 +27,24 @@ "cHNidP8BADMBAAAAAREREREREREREREREREREREREfrK3hERERERERERERERfwAAAAD/////AAAAAAAAAQQAAQQBagA=", "cHNidP8BADMBAAAAAREREREREREREREREREREREREfrK3hERERERERERERERfwAAAAD/////AAAAAAAAAQEJAOH1BQAAAAAAAQUAAQUBUQA=", "cHNidP8BADMBAAAAAREREREREREREREREREREREREfrK3hERERERERERERERfwAAAAD/////AAAAAAAAAQcAAQcBUQA=", - "cHNidP8BADMBAAAAAREREREREREREREREREREREREfrK3hERERERERERERERfwAAAAD/////AAAAAAAAAQEJAOH1BQAAAAAAAQgBAAEIAwEBUQA=" + "cHNidP8BADMBAAAAAREREREREREREREREREREREREfrK3hERERERERERERERfwAAAAD/////AAAAAAAAAQEJAOH1BQAAAAAAAQgBAAEIAwEBUQA=", + "cHNidP8BAHECAAAAASd0Srq/MCf+DWzyOpbu4u+xiO9SMBlUWFiD5ptmJLJCAAAAAAD/////Anh8AQAAAAAAFgAUg6fjS9mf8DpJYu+KGhAbspVGHs5gawQqAQAAABYAFHrDad8bIOAz1hFmI5V7CsSfPFLoAAAAAAABASsA8gUqAQAAACJRIFosLPW1LPMfg60ujaY/8DGD7Nj2CcdRCuikjgORCgdXARchAv40kGTJjW4qhT+jybEr2LMEoZwZXGDvp+4jkwRtP6IyAAAA", + "cHNidP8BAHECAAAAASd0Srq/MCf+DWzyOpbu4u+xiO9SMBlUWFiD5ptmJLJCAAAAAAD/////Anh8AQAAAAAAFgAUg6fjS9mf8DpJYu+KGhAbspVGHs5gawQqAQAAABYAFHrDad8bIOAz1hFmI5V7CsSfPFLoAAAAAAABASsA8gUqAQAAACJRIFosLPW1LPMfg60ujaY/8DGD7Nj2CcdRCuikjgORCgdXARM/Fzuz02wHSvtxb+xjB6BpouRQuZXzyCeFlFq43w4kJg3NcDsMvzTeOZGEqUgawrNYbbZgHwJqd/fkk4SBvDR1AAAA", + "cHNidP8BAHECAAAAASd0Srq/MCf+DWzyOpbu4u+xiO9SMBlUWFiD5ptmJLJCAAAAAAD/////Anh8AQAAAAAAFgAUg6fjS9mf8DpJYu+KGhAbspVGHs5gawQqAQAAABYAFHrDad8bIOAz1hFmI5V7CsSfPFLoAAAAAAABASsA8gUqAQAAACJRIFosLPW1LPMfg60ujaY/8DGD7Nj2CcdRCuikjgORCgdXARNCFzuz02wHSvtxb+xjB6BpouRQuZXzyCeFlFq43w4kJg3NcDsMvzTeOZGEqUgawrNYbbZgHwJqd/fkk4SBvDR1FwGqAAAA", + "cHNidP8BAHECAAAAASd0Srq/MCf+DWzyOpbu4u+xiO9SMBlUWFiD5ptmJLJCAAAAAAD/////Anh8AQAAAAAAFgAUg6fjS9mf8DpJYu+KGhAbspVGHs5gawQqAQAAABYAFHrDad8bIOAz1hFmI5V7CsSfPFLoAAAAAAABASsA8gUqAQAAACJRIFosLPW1LPMfg60ujaY/8DGD7Nj2CcdRCuikjgORCgdXIhYC/jSQZMmNbiqFP6PJsSvYswShnBlcYO+n7iOTBG0/ojIZAHcrLadWAACAAQAAgAAAAIABAAAAAAAAAAAAAA==", + "cHNidP8BAH0CAAAAASd0Srq/MCf+DWzyOpbu4u+xiO9SMBlUWFiD5ptmJLJCAAAAAAD/////Aoh7AQAAAAAAFgAUI4KHHH6EIaAAk/dU2RKB5nWHS59gawQqAQAAACJRIFosLPW1LPMfg60ujaY/8DGD7Nj2CcdRCuikjgORCgdXAAAAAAABASsA8gUqAQAAACJRIFosLPW1LPMfg60ujaY/8DGD7Nj2CcdRCuikjgORCgdXAAABBSEC/jSQZMmNbiqFP6PJsSvYswShnBlcYO+n7iOTBG0/ojIA", + "cHNidP8BAH0CAAAAASd0Srq/MCf+DWzyOpbu4u+xiO9SMBlUWFiD5ptmJLJCAAAAAAD/////Aoh7AQAAAAAAFgAUI4KHHH6EIaAAk/dU2RKB5nWHS59gawQqAQAAACJRIFosLPW1LPMfg60ujaY/8DGD7Nj2CcdRCuikjgORCgdXAAAAAAABASsA8gUqAQAAACJRIFosLPW1LPMfg60ujaY/8DGD7Nj2CcdRCuikjgORCgdXAAAiBwL+NJBkyY1uKoU/o8mxK9izBKGcGVxg76fuI5MEbT+iMhkAdystp1YAAIABAACAAAAAgAEAAAAAAAAAAA==", + "cHNidP8BAF4CAAAAAZvUh2UjC/mnLmYgAflyVW5U8Mb5f+tWvLVgDYF/aZUmAQAAAAD/////AUjmBSoBAAAAIlEgAw2k/OT32yjCyylRYx4ANxOFZZf+ljiCy1AOaBEsymMAAAAAAAEBKwDyBSoBAAAAIlEgwiR++/2SrEf29AuNQtFpF1oZ+p+hDkol1/NetN2FtpJCFAIssTrGgkjegGqmo2Wc88A+toIdCcgRSk6Gj+vehlu20s2XDhX1P8DIL5UP1WD/qRm3YXK+AXNoqJkTrwdPQAsJQIl1aqNznMxonsD886NgvjLMC1mxbpOh6LtGBXJrLKej/3BsQXZkljKyzGjh+RK4pXjjcZzncQiFx6lm9JvNQ8sAAA==", + "cHNidP8BAF4CAAAAAZvUh2UjC/mnLmYgAflyVW5U8Mb5f+tWvLVgDYF/aZUmAQAAAAD/////AUjmBSoBAAAAIlEgAw2k/OT32yjCyylRYx4ANxOFZZf+ljiCy1AOaBEsymMAAAAAAAEBKwDyBSoBAAAAIlEgwiR++/2SrEf29AuNQtFpF1oZ+p+hDkol1/NetN2FtpJBFCyxOsaCSN6AaqajZZzzwD62gh0JyBFKToaP696GW7bSzZcOFfU/wMgvlQ/VYP+pGbdhcr4Bc2iomROvB09ACwlCiXVqo3OczGiewPzzo2C+MswLWbFuk6Hou0YFcmssp6P/cGxBdmSWMrLMaOH5ErileONxnOdxCIXHqWb0m81DywEBAAA=", + "cHNidP8BAF4CAAAAAZvUh2UjC/mnLmYgAflyVW5U8Mb5f+tWvLVgDYF/aZUmAQAAAAD/////AUjmBSoBAAAAIlEgAw2k/OT32yjCyylRYx4ANxOFZZf+ljiCy1AOaBEsymMAAAAAAAEBKwDyBSoBAAAAIlEgwiR++/2SrEf29AuNQtFpF1oZ+p+hDkol1/NetN2FtpJBFCyxOsaCSN6AaqajZZzzwD62gh0JyBFKToaP696GW7bSzZcOFfU/wMgvlQ/VYP+pGbdhcr4Bc2iomROvB09ACwk5iXVqo3OczGiewPzzo2C+MswLWbFuk6Hou0YFcmssp6P/cGxBdmSWMrLMaOH5ErileONxnOdxCIXHqWb0m81DywAA", + "cHNidP8BAF4CAAAAAZvUh2UjC/mnLmYgAflyVW5U8Mb5f+tWvLVgDYF/aZUmAQAAAAD/////AUjmBSoBAAAAIlEgAw2k/OT32yjCyylRYx4ANxOFZZf+ljiCy1AOaBEsymMAAAAAAAEBKwDyBSoBAAAAIlEgwiR++/2SrEf29AuNQtFpF1oZ+p+hDkol1/NetN2FtpJjFcFQkpt0waBJVLeLS2A16XpeB4paDyjsltVHv+6azoA6wG99YgWelJehpKJnVp2YdtpgEBr/OONSm5uTnOf5GulwEV8uSQr3zEXE94UR82BXzlxaXFYyWin7RN/CA/NW4fgAIyAssTrGgkjegGqmo2Wc88A+toIdCcgRSk6Gj+vehlu20qzAAAA=", + "cHNidP8BAF4CAAAAAZvUh2UjC/mnLmYgAflyVW5U8Mb5f+tWvLVgDYF/aZUmAQAAAAD/////AUjmBSoBAAAAIlEgAw2k/OT32yjCyylRYx4ANxOFZZf+ljiCy1AOaBEsymMAAAAAAAEBKwDyBSoBAAAAIlEgwiR++/2SrEf29AuNQtFpF1oZ+p+hDkol1/NetN2FtpJhFcFQkpt0waBJVLeLS2A16XpeB4paDyjsltVHv+6azoA6wG99YgWelJehpKJnVp2YdtpgEBr/OONSm5uTnOf5GulwEV8uSQr3zEXE94UR82BXzlxaXFYyWin7RN/CA/NW4SMgLLE6xoJI3oBqpqNlnPPAPraCHQnIEUpOho/r3oZbttKswAAA" + ], + "invalid_with_msg": [ + [ + "cHNidP8BAKOro2MDAwMDA5ggCAAA////CQAtAAD+///1AAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJAAAAAAAAAAAAAAAAAAAAAAAAAD+///1Zm9ybmV3nWx1Y2vmelLmegAAAAAAAAAAAAAAAAAAAAMKAwMDAwMDAwMDAwMACvMBA3FkAAAAAAAAAAAABAAlAAAAAAAAACEWDQ0zDQ0NDQ0NDQ0NCwEAAH9/f39/fwMAAABNo6P///kAAA==", + "Input Taproot BIP32 keypath has an invalid length" + ] ], "valid" : [ "cHNidP8BAHUCAAAAASaBcTce3/KF6Tet7qSze3gADAVmy7OtZGQXE8pCFxv2AAAAAAD+////AtPf9QUAAAAAGXapFNDFmQPFusKGh2DpD9UhpGZap2UgiKwA4fUFAAAAABepFDVF5uM7gyxHBQ8k0+65PJwDlIvHh7MuEwAAAQD9pQEBAAAAAAECiaPHHqtNIOA3G7ukzGmPopXJRjr6Ljl/hTPMti+VZ+UBAAAAFxYAFL4Y0VKpsBIDna89p95PUzSe7LmF/////4b4qkOnHf8USIk6UwpyN+9rRgi7st0tAXHmOuxqSJC0AQAAABcWABT+Pp7xp0XpdNkCxDVZQ6vLNL1TU/////8CAMLrCwAAAAAZdqkUhc/xCX/Z4Ai7NK9wnGIZeziXikiIrHL++E4sAAAAF6kUM5cluiHv1irHU6m80GfWx6ajnQWHAkcwRAIgJxK+IuAnDzlPVoMR3HyppolwuAJf3TskAinwf4pfOiQCIAGLONfc0xTnNMkna9b7QPZzMlvEuqFEyADS8vAtsnZcASED0uFWdJQbrUqZY3LLh+GFbTZSYG2YVi/jnF6efkE/IQUCSDBFAiEA0SuFLYXc2WHS9fSrZgZU327tzHlMDDPOXMMJ/7X85Y0CIGczio4OFyXBl/saiK9Z9R5E5CVbIBZ8hoQDHAXR8lkqASECI7cr7vCWXRC+B3jv7NYfysb3mk6haTkzgHNEZPhPKrMAAAAAAAAA", @@ -43,7 +60,13 @@ "cHNidP8BAHUCAAAAASaBcTce3/KF6Tet7qSze3gADAVmy7OtZGQXE8pCFxv2AAAAAAD+////AtPf9QUAAAAAGXapFNDFmQPFusKGh2DpD9UhpGZap2UgiKwA4fUFAAAAABepFDVF5uM7gyxHBQ8k0+65PJwDlIvHh7MuEwAAAQD9pQEBAAAAAAECiaPHHqtNIOA3G7ukzGmPopXJRjr6Ljl/hTPMti+VZ+UBAAAAFxYAFL4Y0VKpsBIDna89p95PUzSe7LmF/////4b4qkOnHf8USIk6UwpyN+9rRgi7st0tAXHmOuxqSJC0AQAAABcWABT+Pp7xp0XpdNkCxDVZQ6vLNL1TU/////8CAMLrCwAAAAAZdqkUhc/xCX/Z4Ai7NK9wnGIZeziXikiIrHL++E4sAAAAF6kUM5cluiHv1irHU6m80GfWx6ajnQWHAkcwRAIgJxK+IuAnDzlPVoMR3HyppolwuAJf3TskAinwf4pfOiQCIAGLONfc0xTnNMkna9b7QPZzMlvEuqFEyADS8vAtsnZcASED0uFWdJQbrUqZY3LLh+GFbTZSYG2YVi/jnF6efkE/IQUCSDBFAiEA0SuFLYXc2WHS9fSrZgZU327tzHlMDDPOXMMJ/7X85Y0CIGczio4OFyXBl/saiK9Z9R5E5CVbIBZ8hoQDHAXR8lkqASECI7cr7vCWXRC+B3jv7NYfysb3mk6haTkzgHNEZPhPKrMAAAAAFQoYn3yLGjhv/o7tkbODDHp7zR53jAIBAhUK8pG6UBXfNIyAhT+luw95RvXJ4bMBAQAAAA==", "cHNidP8BAHUCAAAAASaBcTce3/KF6Tet7qSze3gADAVmy7OtZGQXE8pCFxv2AAAAAAD+////AtPf9QUAAAAAGXapFNDFmQPFusKGh2DpD9UhpGZap2UgiKwA4fUFAAAAABepFDVF5uM7gyxHBQ8k0+65PJwDlIvHh7MuEwAAAQD9pQEBAAAAAAECiaPHHqtNIOA3G7ukzGmPopXJRjr6Ljl/hTPMti+VZ+UBAAAAFxYAFL4Y0VKpsBIDna89p95PUzSe7LmF/////4b4qkOnHf8USIk6UwpyN+9rRgi7st0tAXHmOuxqSJC0AQAAABcWABT+Pp7xp0XpdNkCxDVZQ6vLNL1TU/////8CAMLrCwAAAAAZdqkUhc/xCX/Z4Ai7NK9wnGIZeziXikiIrHL++E4sAAAAF6kUM5cluiHv1irHU6m80GfWx6ajnQWHAkcwRAIgJxK+IuAnDzlPVoMR3HyppolwuAJf3TskAinwf4pfOiQCIAGLONfc0xTnNMkna9b7QPZzMlvEuqFEyADS8vAtsnZcASED0uFWdJQbrUqZY3LLh+GFbTZSYG2YVi/jnF6efkE/IQUCSDBFAiEA0SuFLYXc2WHS9fSrZgZU327tzHlMDDPOXMMJ/7X85Y0CIGczio4OFyXBl/saiK9Z9R5E5CVbIBZ8hoQDHAXR8lkqASECI7cr7vCWXRC+B3jv7NYfysb3mk6haTkzgHNEZPhPKrMAAAAAIQtL9RIvNEVUxTveLruM0rfj0WAK1jHDhaXXzOI8d4VFmgEBIQuhKHH+4hD7hhkpHq6hlFgcvSUx5LI3WdIl9oBpI/YyIgIBAgAAAA==", "cHNidP8BAHUCAAAAASaBcTce3/KF6Tet7qSze3gADAVmy7OtZGQXE8pCFxv2AAAAAAD+////AtPf9QUAAAAAGXapFNDFmQPFusKGh2DpD9UhpGZap2UgiKwA4fUFAAAAABepFDVF5uM7gyxHBQ8k0+65PJwDlIvHh7MuEwAAAQD9pQEBAAAAAAECiaPHHqtNIOA3G7ukzGmPopXJRjr6Ljl/hTPMti+VZ+UBAAAAFxYAFL4Y0VKpsBIDna89p95PUzSe7LmF/////4b4qkOnHf8USIk6UwpyN+9rRgi7st0tAXHmOuxqSJC0AQAAABcWABT+Pp7xp0XpdNkCxDVZQ6vLNL1TU/////8CAMLrCwAAAAAZdqkUhc/xCX/Z4Ai7NK9wnGIZeziXikiIrHL++E4sAAAAF6kUM5cluiHv1irHU6m80GfWx6ajnQWHAkcwRAIgJxK+IuAnDzlPVoMR3HyppolwuAJf3TskAinwf4pfOiQCIAGLONfc0xTnNMkna9b7QPZzMlvEuqFEyADS8vAtsnZcASED0uFWdJQbrUqZY3LLh+GFbTZSYG2YVi/jnF6efkE/IQUCSDBFAiEA0SuFLYXc2WHS9fSrZgZU327tzHlMDDPOXMMJ/7X85Y0CIGczio4OFyXBl/saiK9Z9R5E5CVbIBZ8hoQDHAXR8lkqASECI7cr7vCWXRC+B3jv7NYfysb3mk6haTkzgHNEZPhPKrMAAAAAFQwVzEnhkcvFINkZRGAKXLd69qoykQIBAhUMxRtmvO1eRJEAG9cCZpdw3M9ECYIBAQAAAA==", - "cHNidP8BAHUCAAAAASaBcTce3/KF6Tet7qSze3gADAVmy7OtZGQXE8pCFxv2AAAAAAD+////AtPf9QUAAAAAGXapFNDFmQPFusKGh2DpD9UhpGZap2UgiKwA4fUFAAAAABepFDVF5uM7gyxHBQ8k0+65PJwDlIvHh7MuEwAAAQD9pQEBAAAAAAECiaPHHqtNIOA3G7ukzGmPopXJRjr6Ljl/hTPMti+VZ+UBAAAAFxYAFL4Y0VKpsBIDna89p95PUzSe7LmF/////4b4qkOnHf8USIk6UwpyN+9rRgi7st0tAXHmOuxqSJC0AQAAABcWABT+Pp7xp0XpdNkCxDVZQ6vLNL1TU/////8CAMLrCwAAAAAZdqkUhc/xCX/Z4Ai7NK9wnGIZeziXikiIrHL++E4sAAAAF6kUM5cluiHv1irHU6m80GfWx6ajnQWHAkcwRAIgJxK+IuAnDzlPVoMR3HyppolwuAJf3TskAinwf4pfOiQCIAGLONfc0xTnNMkna9b7QPZzMlvEuqFEyADS8vAtsnZcASED0uFWdJQbrUqZY3LLh+GFbTZSYG2YVi/jnF6efkE/IQUCSDBFAiEA0SuFLYXc2WHS9fSrZgZU327tzHlMDDPOXMMJ/7X85Y0CIGczio4OFyXBl/saiK9Z9R5E5CVbIBZ8hoQDHAXR8lkqASECI7cr7vCWXRC+B3jv7NYfysb3mk6haTkzgHNEZPhPKrMAAAAAIQ12pWrO2RXSUT3NhMLDeLLoqlzWMrW3HKLyrFsOOmSb2wIBAiENnBLP3ATHRYTXh6w9I3chMsGFJLx6so3sQhm4/FtCX3ABAQAAAA==" + "cHNidP8BAHUCAAAAASaBcTce3/KF6Tet7qSze3gADAVmy7OtZGQXE8pCFxv2AAAAAAD+////AtPf9QUAAAAAGXapFNDFmQPFusKGh2DpD9UhpGZap2UgiKwA4fUFAAAAABepFDVF5uM7gyxHBQ8k0+65PJwDlIvHh7MuEwAAAQD9pQEBAAAAAAECiaPHHqtNIOA3G7ukzGmPopXJRjr6Ljl/hTPMti+VZ+UBAAAAFxYAFL4Y0VKpsBIDna89p95PUzSe7LmF/////4b4qkOnHf8USIk6UwpyN+9rRgi7st0tAXHmOuxqSJC0AQAAABcWABT+Pp7xp0XpdNkCxDVZQ6vLNL1TU/////8CAMLrCwAAAAAZdqkUhc/xCX/Z4Ai7NK9wnGIZeziXikiIrHL++E4sAAAAF6kUM5cluiHv1irHU6m80GfWx6ajnQWHAkcwRAIgJxK+IuAnDzlPVoMR3HyppolwuAJf3TskAinwf4pfOiQCIAGLONfc0xTnNMkna9b7QPZzMlvEuqFEyADS8vAtsnZcASED0uFWdJQbrUqZY3LLh+GFbTZSYG2YVi/jnF6efkE/IQUCSDBFAiEA0SuFLYXc2WHS9fSrZgZU327tzHlMDDPOXMMJ/7X85Y0CIGczio4OFyXBl/saiK9Z9R5E5CVbIBZ8hoQDHAXR8lkqASECI7cr7vCWXRC+B3jv7NYfysb3mk6haTkzgHNEZPhPKrMAAAAAIQ12pWrO2RXSUT3NhMLDeLLoqlzWMrW3HKLyrFsOOmSb2wIBAiENnBLP3ATHRYTXh6w9I3chMsGFJLx6so3sQhm4/FtCX3ABAQAAAA==", + "cHNidP8BAFICAAAAASd0Srq/MCf+DWzyOpbu4u+xiO9SMBlUWFiD5ptmJLJCAAAAAAD/////AUjmBSoBAAAAFgAUdo4e60z0IIZgM/gKzv8PlyB0SWkAAAAAAAEBKwDyBSoBAAAAIlEgWiws9bUs8x+DrS6Npj/wMYPs2PYJx1EK6KSOA5EKB1chFv40kGTJjW4qhT+jybEr2LMEoZwZXGDvp+4jkwRtP6IyGQB3Ky2nVgAAgAEAAIAAAACAAQAAAAAAAAABFyD+NJBkyY1uKoU/o8mxK9izBKGcGVxg76fuI5MEbT+iMgAiAgNrdyptt02HU8mKgnlY3mx4qzMSEJ830+AwRIQkLs5z2Bh3Ky2nVAAAgAEAAIAAAACAAAAAAAAAAAAA", + "cHNidP8BAFICAAAAASd0Srq/MCf+DWzyOpbu4u+xiO9SMBlUWFiD5ptmJLJCAAAAAAD/////AUjmBSoBAAAAFgAUdo4e60z0IIZgM/gKzv8PlyB0SWkAAAAAAAEBKwDyBSoBAAAAIlEgWiws9bUs8x+DrS6Npj/wMYPs2PYJx1EK6KSOA5EKB1cBE0C7U+yRe62dkGrxuocYHEi4as5aritTYFpyXKdGJWMUdvxvW67a9PLuD0d/NvWPOXDVuCc7fkl7l68uPxJcl680IRb+NJBkyY1uKoU/o8mxK9izBKGcGVxg76fuI5MEbT+iMhkAdystp1YAAIABAACAAAAAgAEAAAAAAAAAARcg/jSQZMmNbiqFP6PJsSvYswShnBlcYO+n7iOTBG0/ojIAIgIDa3cqbbdNh1PJioJ5WN5seKszEhCfN9PgMESEJC7Oc9gYdystp1QAAIABAACAAAAAgAAAAAAAAAAAAA==", + "cHNidP8BAF4CAAAAASd0Srq/MCf+DWzyOpbu4u+xiO9SMBlUWFiD5ptmJLJCAAAAAAD/////AUjmBSoBAAAAIlEgg2mORYxmZOFZXXXaJZfeHiLul9eY5wbEwKS1qYI810MAAAAAAAEBKwDyBSoBAAAAIlEgWiws9bUs8x+DrS6Npj/wMYPs2PYJx1EK6KSOA5EKB1chFv40kGTJjW4qhT+jybEr2LMEoZwZXGDvp+4jkwRtP6IyGQB3Ky2nVgAAgAEAAIAAAACAAQAAAAAAAAABFyD+NJBkyY1uKoU/o8mxK9izBKGcGVxg76fuI5MEbT+iMgABBSARJNp67JLM0GyVRWJkf0N7E4uVchqEvivyJ2u92rPmcSEHESTaeuySzNBslUViZH9DexOLlXIahL4r8idrvdqz5nEZAHcrLadWAACAAQAAgAAAAIAAAAAABQAAAAA=", + "cHNidP8BAF4CAAAAAZvUh2UjC/mnLmYgAflyVW5U8Mb5f+tWvLVgDYF/aZUmAQAAAAD/////AUjmBSoBAAAAIlEgg2mORYxmZOFZXXXaJZfeHiLul9eY5wbEwKS1qYI810MAAAAAAAEBKwDyBSoBAAAAIlEgwiR++/2SrEf29AuNQtFpF1oZ+p+hDkol1/NetN2FtpJiFcFQkpt0waBJVLeLS2A16XpeB4paDyjsltVHv+6azoA6wG99YgWelJehpKJnVp2YdtpgEBr/OONSm5uTnOf5GulwEV8uSQr3zEXE94UR82BXzlxaXFYyWin7RN/CA/NW4fgjICyxOsaCSN6AaqajZZzzwD62gh0JyBFKToaP696GW7bSrMBCFcFQkpt0waBJVLeLS2A16XpeB4paDyjsltVHv+6azoA6wJfG5v6l/3FP9XJEmZkIEOQG6YqhD1v35fZ4S8HQqabOIyBDILC/FvARtT6nvmFZJKp/J+XSmtIOoRVdhIZ2w7rRsqzAYhXBUJKbdMGgSVS3i0tgNel6XgeKWg8o7JbVR7/ums6AOsDNlw4V9T/AyC+VD9Vg/6kZt2FyvgFzaKiZE68HT0ALCRFfLkkK98xFxPeFEfNgV85cWlxWMlop+0TfwgPzVuH4IyD6D3o87zsdDAps59JuF62gsuXJLRnvrUi0GFnLikUcqazAIRYssTrGgkjegGqmo2Wc88A+toIdCcgRSk6Gj+vehlu20jkBzZcOFfU/wMgvlQ/VYP+pGbdhcr4Bc2iomROvB09ACwl3Ky2nVgAAgAEAAIACAACAAAAAAAAAAAAhFkMgsL8W8BG1Pqe+YVkkqn8n5dKa0g6hFV2EhnbDutGyOQERXy5JCvfMRcT3hRHzYFfOXFpcVjJaKftE38ID81bh+HcrLadWAACAAQAAgAEAAIAAAAAAAAAAACEWUJKbdMGgSVS3i0tgNel6XgeKWg8o7JbVR7/ums6AOsAFAHxGHl0hFvoPejzvOx0MCmzn0m4XraCy5cktGe+tSLQYWcuKRRypOQFvfWIFnpSXoaSiZ1admHbaYBAa/zjjUpubk5zn+RrpcHcrLadWAACAAQAAgAMAAIAAAAAAAAAAAAEXIFCSm3TBoElUt4tLYDXpel4HiloPKOyW1Ue/7prOgDrAARgg8DYuL3Wm9CClvePrIh2WrmcgzyX4GJDJWx13WstRXmUAAQUgESTaeuySzNBslUViZH9DexOLlXIahL4r8idrvdqz5nEhBxEk2nrskszQbJVFYmR/Q3sTi5VyGoS+K/Ina73as+ZxGQB3Ky2nVgAAgAEAAIAAAACAAAAAAAUAAAAA", + "cHNidP8BAF4CAAAAASd0Srq/MCf+DWzyOpbu4u+xiO9SMBlUWFiD5ptmJLJCAAAAAAD/////AUjmBSoBAAAAIlEgCoy9yG3hzhwPnK6yLW33ztNoP+Qj4F0eQCqHk0HW9vUAAAAAAAEBKwDyBSoBAAAAIlEgWiws9bUs8x+DrS6Npj/wMYPs2PYJx1EK6KSOA5EKB1chFv40kGTJjW4qhT+jybEr2LMEoZwZXGDvp+4jkwRtP6IyGQB3Ky2nVgAAgAEAAIAAAACAAQAAAAAAAAABFyD+NJBkyY1uKoU/o8mxK9izBKGcGVxg76fuI5MEbT+iMgABBSBQkpt0waBJVLeLS2A16XpeB4paDyjsltVHv+6azoA6wAEGbwLAIiBzblcpAP4SUliaIUPI88efcaBBLSNTr3VelwHHgmlKAqwCwCIgYxxfO1gyuPvev7GXBM7rMjwh9A96JPQ9aO8MwmsSWWmsAcAiIET6pJoDON5IjI3//s37bzKfOAvVZu8gyN9tgT6rHEJzrCEHRPqkmgM43kiMjf/+zftvMp84C9Vm7yDI322BPqscQnM5AfBreYuSoQ7ZqdC7/Trxc6U7FhfaOkFZygCCFs2Fay4Odystp1YAAIABAACAAQAAgAAAAAADAAAAIQdQkpt0waBJVLeLS2A16XpeB4paDyjsltVHv+6azoA6wAUAfEYeXSEHYxxfO1gyuPvev7GXBM7rMjwh9A96JPQ9aO8MwmsSWWk5ARis5AmIl4Xg6nDO67jhyokqenjq7eDy4pbPQ1lhqPTKdystp1YAAIABAACAAgAAgAAAAAADAAAAIQdzblcpAP4SUliaIUPI88efcaBBLSNTr3VelwHHgmlKAjkBKaW0kVCQFi11mv0/4Pk/ozJgVtC0CIy5M8rngmy42Cx3Ky2nVgAAgAEAAIADAACAAAAAAAMAAAAA", + "cHNidP8BAF4CAAAAAZvUh2UjC/mnLmYgAflyVW5U8Mb5f+tWvLVgDYF/aZUmAQAAAAD/////AUjmBSoBAAAAIlEgg2mORYxmZOFZXXXaJZfeHiLul9eY5wbEwKS1qYI810MAAAAAAAEBKwDyBSoBAAAAIlEgwiR++/2SrEf29AuNQtFpF1oZ+p+hDkol1/NetN2FtpJBFCyxOsaCSN6AaqajZZzzwD62gh0JyBFKToaP696GW7bSzZcOFfU/wMgvlQ/VYP+pGbdhcr4Bc2iomROvB09ACwlAv4GNl1fW/+tTi6BX+0wfxOD17xhudlvrVkeR4Cr1/T1eJVHU404z2G8na4LJnHmu0/A5Wgge/NLMLGXdfmk9eUEUQyCwvxbwEbU+p75hWSSqfyfl0prSDqEVXYSGdsO60bIRXy5JCvfMRcT3hRHzYFfOXFpcVjJaKftE38ID81bh+EDh8atvq/omsjbyGDNxncHUKKt2jYD5H5mI2KvvR7+4Y7sfKlKfdowV8AzjTsKDzcB+iPhCi+KPbvZAQ8MpEYEaQRT6D3o87zsdDAps59JuF62gsuXJLRnvrUi0GFnLikUcqW99YgWelJehpKJnVp2YdtpgEBr/OONSm5uTnOf5GulwQOwfA3kgZGHIM0IoVCMyZwirAx8NpKJT7kWq+luMkgNNi2BUkPjNE+APmJmJuX4hX6o28S3uNpPS2szzeBwXV/ZiFcFQkpt0waBJVLeLS2A16XpeB4paDyjsltVHv+6azoA6wG99YgWelJehpKJnVp2YdtpgEBr/OONSm5uTnOf5GulwEV8uSQr3zEXE94UR82BXzlxaXFYyWin7RN/CA/NW4fgjICyxOsaCSN6AaqajZZzzwD62gh0JyBFKToaP696GW7bSrMBCFcFQkpt0waBJVLeLS2A16XpeB4paDyjsltVHv+6azoA6wJfG5v6l/3FP9XJEmZkIEOQG6YqhD1v35fZ4S8HQqabOIyBDILC/FvARtT6nvmFZJKp/J+XSmtIOoRVdhIZ2w7rRsqzAYhXBUJKbdMGgSVS3i0tgNel6XgeKWg8o7JbVR7/ums6AOsDNlw4V9T/AyC+VD9Vg/6kZt2FyvgFzaKiZE68HT0ALCRFfLkkK98xFxPeFEfNgV85cWlxWMlop+0TfwgPzVuH4IyD6D3o87zsdDAps59JuF62gsuXJLRnvrUi0GFnLikUcqazAIRYssTrGgkjegGqmo2Wc88A+toIdCcgRSk6Gj+vehlu20jkBzZcOFfU/wMgvlQ/VYP+pGbdhcr4Bc2iomROvB09ACwl3Ky2nVgAAgAEAAIACAACAAAAAAAAAAAAhFkMgsL8W8BG1Pqe+YVkkqn8n5dKa0g6hFV2EhnbDutGyOQERXy5JCvfMRcT3hRHzYFfOXFpcVjJaKftE38ID81bh+HcrLadWAACAAQAAgAEAAIAAAAAAAAAAACEWUJKbdMGgSVS3i0tgNel6XgeKWg8o7JbVR7/ums6AOsAFAHxGHl0hFvoPejzvOx0MCmzn0m4XraCy5cktGe+tSLQYWcuKRRypOQFvfWIFnpSXoaSiZ1admHbaYBAa/zjjUpubk5zn+RrpcHcrLadWAACAAQAAgAMAAIAAAAAAAAAAAAEXIFCSm3TBoElUt4tLYDXpel4HiloPKOyW1Ue/7prOgDrAARgg8DYuL3Wm9CClvePrIh2WrmcgzyX4GJDJWx13WstRXmUAAQUgESTaeuySzNBslUViZH9DexOLlXIahL4r8idrvdqz5nEhBxEk2nrskszQbJVFYmR/Q3sTi5VyGoS+K/Ina73as+ZxGQB3Ky2nVgAAgAEAAIAAAACAAAAAAAUAAAAA" ], "creator" : [ { diff --git a/test/functional/example_test.py b/test/functional/example_test.py index 2ad96da854..9cf756060e 100755 --- a/test/functional/example_test.py +++ b/test/functional/example_test.py @@ -14,8 +14,15 @@ is testing and *how* it's being tested from collections import defaultdict # Avoid wildcard * imports -from test_framework.blocktools import (create_block, create_coinbase) -from test_framework.messages import CInv, MSG_BLOCK +# Use lexicographically sorted multi-line imports +from test_framework.blocktools import ( + create_block, + create_coinbase, +) +from test_framework.messages import ( + CInv, + MSG_BLOCK, +) from test_framework.p2p import ( P2PInterface, msg_block, diff --git a/test/functional/feature_addrman.py b/test/functional/feature_addrman.py index 5e49d0214a..63abf0d9f8 100755 --- a/test/functional/feature_addrman.py +++ b/test/functional/feature_addrman.py @@ -95,7 +95,7 @@ class AddrmanTest(BitcoinTestFramework): with open(peers_dat, "wb") as f: f.write(serialize_addrman()[:-1]) self.nodes[0].assert_start_raises_init_error( - expected_msg=init_error("CAutoFile::read: end of file.*"), + expected_msg=init_error("AutoFile::read: end of file.*"), match=ErrorMatch.FULL_REGEX, ) diff --git a/test/functional/feature_bind_extra.py b/test/functional/feature_bind_extra.py index 6802da8d48..5de9ff203c 100755 --- a/test/functional/feature_bind_extra.py +++ b/test/functional/feature_bind_extra.py @@ -18,12 +18,12 @@ from test_framework.test_framework import ( SkipTest, ) from test_framework.util import ( - PORT_MIN, - PORT_RANGE, assert_equal, + p2p_port, rpc_port, ) + class BindExtraTest(BitcoinTestFramework): def set_test_params(self): self.setup_clean_chain = True @@ -33,11 +33,6 @@ class BindExtraTest(BitcoinTestFramework): self.num_nodes = 2 def setup_network(self): - # Override setup_network() because we want to put the result of - # p2p_port() in self.extra_args[], before the nodes are started. - # p2p_port() is not usable in set_test_params() because PortSeed.n is - # not set at that time. - # Due to OS-specific network stats queries, we only run on Linux. self.log.info("Checking for Linux") if not sys.platform.startswith('linux'): @@ -45,8 +40,8 @@ class BindExtraTest(BitcoinTestFramework): loopback_ipv4 = addr_to_hex("127.0.0.1") - # Start custom ports after p2p and rpc ports. - port = PORT_MIN + 2 * PORT_RANGE + # Start custom ports by reusing unused p2p ports + port = p2p_port(self.num_nodes) # Array of tuples [command line arguments, expected bind addresses]. self.expected = [] diff --git a/test/functional/feature_bip68_sequence.py b/test/functional/feature_bip68_sequence.py index 05d274a9fe..5b43fe4f8e 100755 --- a/test/functional/feature_bip68_sequence.py +++ b/test/functional/feature_bip68_sequence.py @@ -10,15 +10,21 @@ from test_framework.blocktools import ( NORMAL_GBT_REQUEST_PARAMS, add_witness_commitment, create_block, + script_to_p2wsh_script, ) from test_framework.messages import ( COIN, COutPoint, CTransaction, CTxIn, + CTxInWitness, CTxOut, tx_from_hex, ) +from test_framework.script import ( + CScript, + OP_TRUE, +) from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_equal, @@ -26,7 +32,8 @@ from test_framework.util import ( assert_raises_rpc_error, softfork_active, ) -from test_framework.script_util import DUMMY_P2WPKH_SCRIPT + +SCRIPT_W0_SH_OP_TRUE = script_to_p2wsh_script(CScript([OP_TRUE])) SEQUENCE_LOCKTIME_DISABLE_FLAG = (1<<31) SEQUENCE_LOCKTIME_TYPE_FLAG = (1<<22) # this means use time (0 means height) @@ -42,11 +49,9 @@ class BIP68Test(BitcoinTestFramework): self.extra_args = [ [ '-testactivationheight=csv@432', - "-acceptnonstdtxn=1", ], [ '-testactivationheight=csv@432', - "-acceptnonstdtxn=0", ], ] @@ -100,7 +105,7 @@ class BIP68Test(BitcoinTestFramework): # input to mature. sequence_value = SEQUENCE_LOCKTIME_DISABLE_FLAG | 1 tx1.vin = [CTxIn(COutPoint(int(utxo["txid"], 16), utxo["vout"]), nSequence=sequence_value)] - tx1.vout = [CTxOut(value, DUMMY_P2WPKH_SCRIPT)] + tx1.vout = [CTxOut(value, SCRIPT_W0_SH_OP_TRUE)] tx1_signed = self.nodes[0].signrawtransactionwithwallet(tx1.serialize().hex())["hex"] tx1_id = self.nodes[0].sendrawtransaction(tx1_signed) @@ -112,7 +117,9 @@ class BIP68Test(BitcoinTestFramework): tx2.nVersion = 2 sequence_value = sequence_value & 0x7fffffff tx2.vin = [CTxIn(COutPoint(tx1_id, 0), nSequence=sequence_value)] - tx2.vout = [CTxOut(int(value - self.relayfee * COIN), DUMMY_P2WPKH_SCRIPT)] + tx2.wit.vtxinwit = [CTxInWitness()] + tx2.wit.vtxinwit[0].scriptWitness.stack = [CScript([OP_TRUE])] + tx2.vout = [CTxOut(int(value - self.relayfee * COIN), SCRIPT_W0_SH_OP_TRUE)] tx2.rehash() assert_raises_rpc_error(-26, NOT_FINAL_ERROR, self.nodes[0].sendrawtransaction, tx2.serialize().hex()) @@ -207,7 +214,7 @@ class BIP68Test(BitcoinTestFramework): value += utxos[j]["amount"]*COIN # Overestimate the size of the tx - signatures should be less than 120 bytes, and leave 50 for the output tx_size = len(tx.serialize().hex())//2 + 120*num_inputs + 50 - tx.vout.append(CTxOut(int(value-self.relayfee*tx_size*COIN/1000), DUMMY_P2WPKH_SCRIPT)) + tx.vout.append(CTxOut(int(value - self.relayfee * tx_size * COIN / 1000), SCRIPT_W0_SH_OP_TRUE)) rawtx = self.nodes[0].signrawtransactionwithwallet(tx.serialize().hex())["hex"] if (using_sequence_locks and not should_pass): @@ -236,7 +243,7 @@ class BIP68Test(BitcoinTestFramework): tx2 = CTransaction() tx2.nVersion = 2 tx2.vin = [CTxIn(COutPoint(tx1.sha256, 0), nSequence=0)] - tx2.vout = [CTxOut(int(tx1.vout[0].nValue - self.relayfee*COIN), DUMMY_P2WPKH_SCRIPT)] + tx2.vout = [CTxOut(int(tx1.vout[0].nValue - self.relayfee * COIN), SCRIPT_W0_SH_OP_TRUE)] tx2_raw = self.nodes[0].signrawtransactionwithwallet(tx2.serialize().hex())["hex"] tx2 = tx_from_hex(tx2_raw) tx2.rehash() @@ -254,7 +261,9 @@ class BIP68Test(BitcoinTestFramework): tx = CTransaction() tx.nVersion = 2 tx.vin = [CTxIn(COutPoint(orig_tx.sha256, 0), nSequence=sequence_value)] - tx.vout = [CTxOut(int(orig_tx.vout[0].nValue - relayfee * COIN), DUMMY_P2WPKH_SCRIPT)] + tx.wit.vtxinwit = [CTxInWitness()] + tx.wit.vtxinwit[0].scriptWitness.stack = [CScript([OP_TRUE])] + tx.vout = [CTxOut(int(orig_tx.vout[0].nValue - relayfee * COIN), SCRIPT_W0_SH_OP_TRUE)] tx.rehash() if (orig_tx.hash in node.getrawmempool()): @@ -367,7 +376,7 @@ class BIP68Test(BitcoinTestFramework): tx2 = CTransaction() tx2.nVersion = 1 tx2.vin = [CTxIn(COutPoint(tx1.sha256, 0), nSequence=0)] - tx2.vout = [CTxOut(int(tx1.vout[0].nValue - self.relayfee*COIN), DUMMY_P2WPKH_SCRIPT)] + tx2.vout = [CTxOut(int(tx1.vout[0].nValue - self.relayfee * COIN), SCRIPT_W0_SH_OP_TRUE)] # sign tx2 tx2_raw = self.nodes[0].signrawtransactionwithwallet(tx2.serialize().hex())["hex"] @@ -382,7 +391,9 @@ class BIP68Test(BitcoinTestFramework): tx3 = CTransaction() tx3.nVersion = 2 tx3.vin = [CTxIn(COutPoint(tx2.sha256, 0), nSequence=sequence_value)] - tx3.vout = [CTxOut(int(tx2.vout[0].nValue - self.relayfee * COIN), DUMMY_P2WPKH_SCRIPT)] + tx3.wit.vtxinwit = [CTxInWitness()] + tx3.wit.vtxinwit[0].scriptWitness.stack = [CScript([OP_TRUE])] + tx3.vout = [CTxOut(int(tx2.vout[0].nValue - self.relayfee * COIN), SCRIPT_W0_SH_OP_TRUE)] tx3.rehash() assert_raises_rpc_error(-26, NOT_FINAL_ERROR, self.nodes[0].sendrawtransaction, tx3.serialize().hex()) diff --git a/test/functional/feature_block.py b/test/functional/feature_block.py index 462deeae32..850cb8334c 100755 --- a/test/functional/feature_block.py +++ b/test/functional/feature_block.py @@ -1297,7 +1297,7 @@ class FullBlockTest(BitcoinTestFramework): blocks2 = [] for i in range(89, LARGE_REORG_SIZE + 89): blocks2.append(self.next_block("alt" + str(i))) - self.send_blocks(blocks2, False, force_send=True) + self.send_blocks(blocks2, False, force_send=False) # extend alt chain to trigger re-org block = self.next_block("alt" + str(chain1_tip + 1)) diff --git a/test/functional/feature_config_args.py b/test/functional/feature_config_args.py index 6c51a5ac31..eb31bca29a 100755 --- a/test/functional/feature_config_args.py +++ b/test/functional/feature_config_args.py @@ -186,11 +186,12 @@ class ConfArgsTest(BitcoinTestFramework): with self.nodes[0].assert_debug_log(expected_msgs=[ "Loaded 0 addresses from peers.dat", "DNS seeding disabled", - "Adding fixed seeds as -dnsseed=0, -addnode is not provided and all -seednode(s) attempted\n", + "Adding fixed seeds as -dnsseed=0 (or IPv4/IPv6 connections are disabled via -onlynet), -addnode is not provided and all -seednode(s) attempted\n", ]): self.start_node(0, extra_args=['-dnsseed=0', '-fixedseeds=1']) assert time.time() - start < 60 self.stop_node(0) + self.nodes[0].assert_start_raises_init_error(['-dnsseed=1', '-onlynet=i2p', '-i2psam=127.0.0.1:7656'], "Error: Incompatible options: -dnsseed=1 was explicitly specified, but -onlynet forbids connections to IPv4/IPv6") # No peers.dat exists and dns seeds are disabled. # We expect the node will not add fixed seeds when explicitly disabled. diff --git a/test/functional/feature_dbcrash.py b/test/functional/feature_dbcrash.py index a3a5e9e27d..f606f26e70 100755 --- a/test/functional/feature_dbcrash.py +++ b/test/functional/feature_dbcrash.py @@ -62,8 +62,8 @@ class ChainstateWriteCrashTest(BitcoinTestFramework): self.node2_args = ["-dbcrashratio=24", "-dbcache=16"] + self.base_args # Node3 is a normal node with default args, except will mine full blocks - # and non-standard txs (e.g. txs with "dust" outputs) - self.node3_args = ["-blockmaxweight=4000000", "-acceptnonstdtxn"] + # and txs with "dust" outputs + self.node3_args = ["-blockmaxweight=4000000", "-dustrelayfee=0"] self.extra_args = [self.node0_args, self.node1_args, self.node2_args, self.node3_args] def setup_network(self): @@ -192,14 +192,12 @@ class ChainstateWriteCrashTest(BitcoinTestFramework): # Sanity check -- if we chose inputs that are too small, skip continue - tx = self.wallet.create_self_transfer_multi( + self.wallet.send_self_transfer_multi( from_node=node, utxos_to_spend=utxos_to_spend, num_outputs=3, - fee_per_output=FEE // 3) - - # Send the transaction to get into the mempool (skip fee-checks to run faster) - node.sendrawtransaction(hexstring=tx.serialize().hex(), maxfeerate=0) + fee_per_output=FEE // 3, + ) num_transactions += 1 def run_test(self): @@ -213,7 +211,9 @@ class ChainstateWriteCrashTest(BitcoinTestFramework): self.crashed_on_restart = 0 # Track count of crashes during recovery # Start by creating a lot of utxos on node3 - utxo_list = self.wallet.send_self_transfer_multi(from_node=self.nodes[3], num_outputs=5000)['new_utxos'] + utxo_list = [] + for _ in range(5): + utxo_list.extend(self.wallet.send_self_transfer_multi(from_node=self.nodes[3], num_outputs=1000)['new_utxos']) self.generate(self.nodes[3], 1, sync_fun=self.no_op) assert_equal(len(self.nodes[3].getrawmempool()), 0) self.log.info(f"Prepped {len(utxo_list)} utxo entries") diff --git a/test/functional/feature_fee_estimation.py b/test/functional/feature_fee_estimation.py index 422612a78e..b0cbcf4edf 100755 --- a/test/functional/feature_fee_estimation.py +++ b/test/functional/feature_fee_estimation.py @@ -51,9 +51,9 @@ def small_txpuzzle_randfee( if total_in <= amount + fee: raise RuntimeError(f"Insufficient funds: need {amount + fee}, have {total_in}") tx = wallet.create_self_transfer_multi( - from_node=from_node, utxos_to_spend=utxos_to_spend, - fee_per_output=0) + fee_per_output=0, + )["tx"] tx.vout[0].nValue = int((total_in - amount - fee) * COIN) tx.vout.append(deepcopy(tx.vout[0])) tx.vout[1].nValue = int(amount * COIN) diff --git a/test/functional/feature_maxuploadtarget.py b/test/functional/feature_maxuploadtarget.py index 0b9d651226..3ea412002a 100755 --- a/test/functional/feature_maxuploadtarget.py +++ b/test/functional/feature_maxuploadtarget.py @@ -46,7 +46,7 @@ class MaxUploadTest(BitcoinTestFramework): self.num_nodes = 1 self.extra_args = [[ "-maxuploadtarget=800M", - "-acceptnonstdtxn=1", + "-datacarriersize=100000", ]] self.supports_cli = False diff --git a/test/functional/feature_minchainwork.py b/test/functional/feature_minchainwork.py index fa10855a98..fb4024b1b0 100755 --- a/test/functional/feature_minchainwork.py +++ b/test/functional/feature_minchainwork.py @@ -82,7 +82,7 @@ class MinimumChainWorkTest(BitcoinTestFramework): msg.hashstop = 0 peer.send_and_ping(msg) time.sleep(5) - assert "headers" not in peer.last_message + assert "headers" not in peer.last_message or len(peer.last_message["headers"].headers) == 0 self.log.info("Generating one more block") self.generate(self.nodes[0], 1) diff --git a/test/functional/feature_nulldummy.py b/test/functional/feature_nulldummy.py index 7a84098a83..9bfb79057e 100755 --- a/test/functional/feature_nulldummy.py +++ b/test/functional/feature_nulldummy.py @@ -19,9 +19,11 @@ from test_framework.blocktools import ( NORMAL_GBT_REQUEST_PARAMS, add_witness_commitment, create_block, - create_transaction, ) -from test_framework.messages import CTransaction +from test_framework.messages import ( + CTransaction, + tx_from_hex, +) from test_framework.script import ( OP_0, OP_TRUE, @@ -31,6 +33,9 @@ from test_framework.util import ( assert_equal, assert_raises_rpc_error, ) +from test_framework.wallet import getnewdestination +from test_framework.key import ECKey +from test_framework.wallet_util import bytes_to_wif NULLDUMMY_ERROR = "non-mandatory-script-verify-flag (Dummy CHECKMULTISIG argument must be zero)" @@ -55,22 +60,26 @@ class NULLDUMMYTest(BitcoinTestFramework): '-par=1', # Use only one script thread to get the exact reject reason for testing ]] - def skip_test_if_missing_module(self): - self.skip_if_no_wallet() + def create_transaction(self, *, txid, input_details=None, addr, amount, privkey): + input = {"txid": txid, "vout": 0} + output = {addr: amount} + rawtx = self.nodes[0].createrawtransaction([input], output) + # Details only needed for scripthash or witness spends + input = None if not input_details else [{**input, **input_details}] + signedtx = self.nodes[0].signrawtransactionwithkey(rawtx, [privkey], input) + return tx_from_hex(signedtx["hex"]) def run_test(self): - self.nodes[0].createwallet(wallet_name='wmulti', disable_private_keys=True) - wmulti = self.nodes[0].get_wallet_rpc('wmulti') - w0 = self.nodes[0].get_wallet_rpc(self.default_wallet_name) - self.address = w0.getnewaddress() - self.pubkey = w0.getaddressinfo(self.address)['pubkey'] - self.ms_address = wmulti.addmultisigaddress(1, [self.pubkey])['address'] - self.wit_address = w0.getnewaddress(address_type='p2sh-segwit') - self.wit_ms_address = wmulti.addmultisigaddress(1, [self.pubkey], '', 'p2sh-segwit')['address'] - if not self.options.descriptors: - # Legacy wallets need to import these so that they are watched by the wallet. This is unnecessary (and does not need to be tested) for descriptor wallets - wmulti.importaddress(self.ms_address) - wmulti.importaddress(self.wit_ms_address) + eckey = ECKey() + eckey.generate() + self.privkey = bytes_to_wif(eckey.get_bytes()) + self.pubkey = eckey.get_pubkey().get_bytes().hex() + cms = self.nodes[0].createmultisig(1, [self.pubkey]) + wms = self.nodes[0].createmultisig(1, [self.pubkey], 'p2sh-segwit') + self.ms_address = cms["address"] + ms_unlock_details = {"scriptPubKey": self.nodes[0].validateaddress(self.ms_address)["scriptPubKey"], + "redeemScript": cms["redeemScript"]} + self.wit_ms_address = wms['address'] self.coinbase_blocks = self.generate(self.nodes[0], 2) # block height = 2 coinbase_txid = [] @@ -82,16 +91,23 @@ class NULLDUMMYTest(BitcoinTestFramework): self.lastblocktime = int(time.time()) + self.lastblockheight self.log.info(f"Test 1: NULLDUMMY compliant base transactions should be accepted to mempool and mined before activation [{COINBASE_MATURITY + 3}]") - test1txs = [create_transaction(self.nodes[0], coinbase_txid[0], self.ms_address, amount=49)] + test1txs = [self.create_transaction(txid=coinbase_txid[0], addr=self.ms_address, amount=49, + privkey=self.nodes[0].get_deterministic_priv_key().key)] txid1 = self.nodes[0].sendrawtransaction(test1txs[0].serialize_with_witness().hex(), 0) - test1txs.append(create_transaction(self.nodes[0], txid1, self.ms_address, amount=48)) + test1txs.append(self.create_transaction(txid=txid1, input_details=ms_unlock_details, + addr=self.ms_address, amount=48, + privkey=self.privkey)) txid2 = self.nodes[0].sendrawtransaction(test1txs[1].serialize_with_witness().hex(), 0) - test1txs.append(create_transaction(self.nodes[0], coinbase_txid[1], self.wit_ms_address, amount=49)) + test1txs.append(self.create_transaction(txid=coinbase_txid[1], + addr=self.wit_ms_address, amount=49, + privkey=self.nodes[0].get_deterministic_priv_key().key)) txid3 = self.nodes[0].sendrawtransaction(test1txs[2].serialize_with_witness().hex(), 0) self.block_submit(self.nodes[0], test1txs, accept=True) self.log.info("Test 2: Non-NULLDUMMY base multisig transaction should not be accepted to mempool before activation") - test2tx = create_transaction(self.nodes[0], txid2, self.ms_address, amount=47) + test2tx = self.create_transaction(txid=txid2, input_details=ms_unlock_details, + addr=self.ms_address, amount=47, + privkey=self.privkey) invalidate_nulldummy_tx(test2tx) assert_raises_rpc_error(-26, NULLDUMMY_ERROR, self.nodes[0].sendrawtransaction, test2tx.serialize_with_witness().hex(), 0) @@ -99,14 +115,19 @@ class NULLDUMMYTest(BitcoinTestFramework): self.block_submit(self.nodes[0], [test2tx], accept=True) self.log.info("Test 4: Non-NULLDUMMY base multisig transaction is invalid after activation") - test4tx = create_transaction(self.nodes[0], test2tx.hash, self.address, amount=46) + test4tx = self.create_transaction(txid=test2tx.hash, input_details=ms_unlock_details, + addr=getnewdestination()[2], amount=46, + privkey=self.privkey) test6txs = [CTransaction(test4tx)] invalidate_nulldummy_tx(test4tx) assert_raises_rpc_error(-26, NULLDUMMY_ERROR, self.nodes[0].sendrawtransaction, test4tx.serialize_with_witness().hex(), 0) self.block_submit(self.nodes[0], [test4tx], accept=False) self.log.info("Test 5: Non-NULLDUMMY P2WSH multisig transaction invalid after activation") - test5tx = create_transaction(self.nodes[0], txid3, self.wit_address, amount=48) + test5tx = self.create_transaction(txid=txid3, input_details={"scriptPubKey": test1txs[2].vout[0].scriptPubKey.hex(), + "amount": 49, "witnessScript": wms["redeemScript"]}, + addr=getnewdestination(address_type='p2sh-segwit')[2], amount=48, + privkey=self.privkey) test6txs.append(CTransaction(test5tx)) test5tx.wit.vtxinwit[0].scriptWitness.stack[0] = b'\x01' assert_raises_rpc_error(-26, NULLDUMMY_ERROR, self.nodes[0].sendrawtransaction, test5tx.serialize_with_witness().hex(), 0) diff --git a/test/functional/feature_proxy.py b/test/functional/feature_proxy.py index 50e0e2c4cc..c90852562e 100755 --- a/test/functional/feature_proxy.py +++ b/test/functional/feature_proxy.py @@ -40,19 +40,15 @@ addnode connect to a CJDNS address """ import socket -import os from test_framework.socks5 import Socks5Configuration, Socks5Command, Socks5Server, AddressType from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( - PORT_MIN, - PORT_RANGE, assert_equal, + p2p_port, ) from test_framework.netutil import test_ipv6_local -RANGE_BEGIN = PORT_MIN + 2 * PORT_RANGE # Start after p2p and rpc ports - # Networks returned by RPC getpeerinfo. NET_UNROUTABLE = "not_publicly_routable" NET_IPV4 = "ipv4" @@ -75,19 +71,19 @@ class ProxyTest(BitcoinTestFramework): # Create two proxies on different ports # ... one unauthenticated self.conf1 = Socks5Configuration() - self.conf1.addr = ('127.0.0.1', RANGE_BEGIN + (os.getpid() % 1000)) + self.conf1.addr = ('127.0.0.1', p2p_port(self.num_nodes)) self.conf1.unauth = True self.conf1.auth = False # ... one supporting authenticated and unauthenticated (Tor) self.conf2 = Socks5Configuration() - self.conf2.addr = ('127.0.0.1', RANGE_BEGIN + 1000 + (os.getpid() % 1000)) + self.conf2.addr = ('127.0.0.1', p2p_port(self.num_nodes + 1)) self.conf2.unauth = True self.conf2.auth = True if self.have_ipv6: # ... one on IPv6 with similar configuration self.conf3 = Socks5Configuration() self.conf3.af = socket.AF_INET6 - self.conf3.addr = ('::1', RANGE_BEGIN + 2000 + (os.getpid() % 1000)) + self.conf3.addr = ('::1', p2p_port(self.num_nodes + 2)) self.conf3.unauth = True self.conf3.auth = True else: @@ -336,20 +332,37 @@ class ProxyTest(BitcoinTestFramework): msg = "Error: Invalid -i2psam address or hostname: 'def:xyz'" self.nodes[1].assert_start_raises_init_error(expected_msg=msg) - msg = ( - "Error: Outbound connections restricted to Tor (-onlynet=onion) but " - "the proxy for reaching the Tor network is not provided (no -proxy= " - "and no -onion= given) or it is explicitly forbidden (-onion=0)" - ) - self.log.info("Test passing -onlynet=onion without -proxy or -onion raises expected init error") - self.nodes[1].extra_args = ["-onlynet=onion"] + self.log.info("Test passing invalid -onlynet=i2p without -i2psam raises expected init error") + self.nodes[1].extra_args = ["-onlynet=i2p"] + msg = "Error: Outbound connections restricted to i2p (-onlynet=i2p) but -i2psam is not provided" + self.nodes[1].assert_start_raises_init_error(expected_msg=msg) + + self.log.info("Test passing invalid -onlynet=cjdns without -cjdnsreachable raises expected init error") + self.nodes[1].extra_args = ["-onlynet=cjdns"] + msg = "Error: Outbound connections restricted to CJDNS (-onlynet=cjdns) but -cjdnsreachable is not provided" self.nodes[1].assert_start_raises_init_error(expected_msg=msg) self.log.info("Test passing -onlynet=onion with -onion=0/-noonion raises expected init error") + msg = ( + "Error: Outbound connections restricted to Tor (-onlynet=onion) but " + "the proxy for reaching the Tor network is explicitly forbidden: -onion=0" + ) for arg in ["-onion=0", "-noonion"]: self.nodes[1].extra_args = ["-onlynet=onion", arg] self.nodes[1].assert_start_raises_init_error(expected_msg=msg) + self.log.info("Test passing -onlynet=onion without -proxy, -onion or -listenonion raises expected init error") + self.nodes[1].extra_args = ["-onlynet=onion", "-listenonion=0"] + msg = ( + "Error: Outbound connections restricted to Tor (-onlynet=onion) but the proxy for " + "reaching the Tor network is not provided: none of -proxy, -onion or -listenonion is given" + ) + self.nodes[1].assert_start_raises_init_error(expected_msg=msg) + + self.log.info("Test passing -onlynet=onion without -proxy or -onion but with -listenonion=1 is ok") + self.start_node(1, extra_args=["-onlynet=onion", "-listenonion=1"]) + self.stop_node(1) + self.log.info("Test passing unknown network to -onlynet raises expected init error") self.nodes[1].extra_args = ["-onlynet=abc"] msg = "Error: Unknown network specified in -onlynet: 'abc'" diff --git a/test/functional/feature_rbf.py b/test/functional/feature_rbf.py index a8492bd6eb..7603248ae5 100755 --- a/test/functional/feature_rbf.py +++ b/test/functional/feature_rbf.py @@ -4,28 +4,18 @@ # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test the RBF code.""" -from copy import deepcopy from decimal import Decimal from test_framework.messages import ( - BIP125_SEQUENCE_NUMBER, + MAX_BIP125_RBF_SEQUENCE, COIN, - COutPoint, - CTransaction, - CTxIn, - CTxOut, SEQUENCE_FINAL, ) -from test_framework.script import CScript, OP_DROP from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_equal, assert_raises_rpc_error, ) -from test_framework.script_util import ( - DUMMY_P2WPKH_SCRIPT, - DUMMY_2_P2WPKH_SCRIPT, -) from test_framework.wallet import MiniWallet from test_framework.address import ADDRESS_BCRT1_UNSPENDABLE @@ -35,7 +25,6 @@ class ReplaceByFeeTest(BitcoinTestFramework): self.num_nodes = 2 self.extra_args = [ [ - "-acceptnonstdtxn=1", "-maxorphantx=1000", "-limitancestorcount=50", "-limitancestorsize=101", @@ -94,17 +83,19 @@ class ReplaceByFeeTest(BitcoinTestFramework): self.log.info("Running test replacement relay fee...") self.test_replacement_relay_fee() + self.log.info("Running test full replace by fee...") + self.test_fullrbf() + self.log.info("Passed") - def make_utxo(self, node, amount, confirmed=True, scriptPubKey=DUMMY_P2WPKH_SCRIPT): + def make_utxo(self, node, amount, *, confirmed=True, scriptPubKey=None): """Create a txout with a given amount and scriptPubKey - confirmed - txouts created will be confirmed in the blockchain; + confirmed - txout created will be confirmed in the blockchain; unconfirmed otherwise. """ - txid, n = self.wallet.send_to(from_node=node, scriptPubKey=scriptPubKey, amount=amount) + txid, n = self.wallet.send_to(from_node=node, scriptPubKey=scriptPubKey or self.wallet.get_scriptPubKey(), amount=amount) - # If requested, ensure txouts are confirmed. if confirmed: mempool_size = len(node.getrawmempool()) while mempool_size > 0: @@ -115,30 +106,24 @@ class ReplaceByFeeTest(BitcoinTestFramework): assert new_size < mempool_size mempool_size = new_size - return COutPoint(int(txid, 16), n) + return self.wallet.get_utxo(txid=txid, vout=n) def test_simple_doublespend(self): """Simple doublespend""" # we use MiniWallet to create a transaction template with inputs correctly set, # and modify the output (amount, scriptPubKey) according to our needs - tx_template = self.wallet.create_self_transfer()['tx'] - - tx1a = deepcopy(tx_template) - tx1a.vout = [CTxOut(1 * COIN, DUMMY_P2WPKH_SCRIPT)] - tx1a_hex = tx1a.serialize().hex() - tx1a_txid = self.nodes[0].sendrawtransaction(tx1a_hex, 0) + tx = self.wallet.create_self_transfer()["tx"] + tx1a_txid = self.nodes[0].sendrawtransaction(tx.serialize().hex()) # Should fail because we haven't changed the fee - tx1b = deepcopy(tx_template) - tx1b.vout = [CTxOut(1 * COIN, DUMMY_2_P2WPKH_SCRIPT)] - tx1b_hex = tx1b.serialize().hex() + tx.vout[0].scriptPubKey[-1] ^= 1 # This will raise an exception due to insufficient fee - assert_raises_rpc_error(-26, "insufficient fee", self.nodes[0].sendrawtransaction, tx1b_hex, 0) + assert_raises_rpc_error(-26, "insufficient fee", self.nodes[0].sendrawtransaction, tx.serialize().hex(), 0) # Extra 0.1 BTC fee - tx1b.vout[0].nValue -= int(0.1 * COIN) - tx1b_hex = tx1b.serialize().hex() + tx.vout[0].nValue -= int(0.1 * COIN) + tx1b_hex = tx.serialize().hex() # Works when enabled tx1b_txid = self.nodes[0].sendrawtransaction(tx1b_hex, 0) @@ -160,28 +145,28 @@ class ReplaceByFeeTest(BitcoinTestFramework): chain_txids = [] while remaining_value > 1 * COIN: remaining_value -= int(0.1 * COIN) - tx = CTransaction() - tx.vin = [CTxIn(prevout, nSequence=0)] - tx.vout = [CTxOut(remaining_value, CScript([1, OP_DROP] * 15 + [1]))] - tx_hex = tx.serialize().hex() - txid = self.nodes[0].sendrawtransaction(tx_hex, 0) - chain_txids.append(txid) - prevout = COutPoint(int(txid, 16), 0) + prevout = self.wallet.send_self_transfer( + from_node=self.nodes[0], + utxo_to_spend=prevout, + sequence=0, + fee=Decimal("0.1"), + )["new_utxo"] + chain_txids.append(prevout["txid"]) # Whether the double-spend is allowed is evaluated by including all # child fees - 4 BTC - so this attempt is rejected. - dbl_tx = CTransaction() - dbl_tx.vin = [CTxIn(tx0_outpoint, nSequence=0)] - dbl_tx.vout = [CTxOut(initial_nValue - 3 * COIN, DUMMY_P2WPKH_SCRIPT)] + dbl_tx = self.wallet.create_self_transfer( + utxo_to_spend=tx0_outpoint, + sequence=0, + fee=Decimal("3"), + )["tx"] dbl_tx_hex = dbl_tx.serialize().hex() # This will raise an exception due to insufficient fee assert_raises_rpc_error(-26, "insufficient fee", self.nodes[0].sendrawtransaction, dbl_tx_hex, 0) # Accepted with sufficient fee - dbl_tx = CTransaction() - dbl_tx.vin = [CTxIn(tx0_outpoint, nSequence=0)] - dbl_tx.vout = [CTxOut(int(0.1 * COIN), DUMMY_P2WPKH_SCRIPT)] + dbl_tx.vout[0].nValue = int(0.1 * COIN) dbl_tx_hex = dbl_tx.serialize().hex() self.nodes[0].sendrawtransaction(dbl_tx_hex, 0) @@ -205,22 +190,19 @@ class ReplaceByFeeTest(BitcoinTestFramework): if txout_value < fee: return - vout = [CTxOut(txout_value, CScript([i+1])) - for i in range(tree_width)] - tx = CTransaction() - tx.vin = [CTxIn(prevout, nSequence=0)] - tx.vout = vout - tx_hex = tx.serialize().hex() + tx = self.wallet.send_self_transfer_multi( + utxos_to_spend=[prevout], + from_node=self.nodes[0], + sequence=0, + num_outputs=tree_width, + amount_per_output=txout_value, + ) - assert len(tx.serialize()) < 100000 - txid = self.nodes[0].sendrawtransaction(tx_hex, 0) - yield tx + yield tx["txid"] _total_txs[0] += 1 - txid = int(txid, 16) - - for i, txout in enumerate(tx.vout): - for x in branch(COutPoint(txid, i), txout_value, + for utxo in tx["new_utxos"]: + for x in branch(utxo, txout_value, max_txs, tree_width=tree_width, fee=fee, _total_txs=_total_txs): @@ -232,25 +214,26 @@ class ReplaceByFeeTest(BitcoinTestFramework): assert_equal(len(tree_txs), n) # Attempt double-spend, will fail because too little fee paid - dbl_tx = CTransaction() - dbl_tx.vin = [CTxIn(tx0_outpoint, nSequence=0)] - dbl_tx.vout = [CTxOut(initial_nValue - fee * n, DUMMY_P2WPKH_SCRIPT)] - dbl_tx_hex = dbl_tx.serialize().hex() + dbl_tx_hex = self.wallet.create_self_transfer( + utxo_to_spend=tx0_outpoint, + sequence=0, + fee=(Decimal(fee) / COIN) * n, + )["hex"] # This will raise an exception due to insufficient fee assert_raises_rpc_error(-26, "insufficient fee", self.nodes[0].sendrawtransaction, dbl_tx_hex, 0) # 0.1 BTC fee is enough - dbl_tx = CTransaction() - dbl_tx.vin = [CTxIn(tx0_outpoint, nSequence=0)] - dbl_tx.vout = [CTxOut(initial_nValue - fee * n - int(0.1 * COIN), DUMMY_P2WPKH_SCRIPT)] - dbl_tx_hex = dbl_tx.serialize().hex() + dbl_tx_hex = self.wallet.create_self_transfer( + utxo_to_spend=tx0_outpoint, + sequence=0, + fee=(Decimal(fee) / COIN) * n + Decimal("0.1"), + )["hex"] self.nodes[0].sendrawtransaction(dbl_tx_hex, 0) mempool = self.nodes[0].getrawmempool() - for tx in tree_txs: - tx.rehash() - assert tx.hash not in mempool + for txid in tree_txs: + assert txid not in mempool # Try again, but with more total transactions than the "max txs # double-spent at once" anti-DoS limit. @@ -260,33 +243,36 @@ class ReplaceByFeeTest(BitcoinTestFramework): tree_txs = list(branch(tx0_outpoint, initial_nValue, n, fee=fee)) assert_equal(len(tree_txs), n) - dbl_tx = CTransaction() - dbl_tx.vin = [CTxIn(tx0_outpoint, nSequence=0)] - dbl_tx.vout = [CTxOut(initial_nValue - 2 * fee * n, DUMMY_P2WPKH_SCRIPT)] - dbl_tx_hex = dbl_tx.serialize().hex() + dbl_tx_hex = self.wallet.create_self_transfer( + utxo_to_spend=tx0_outpoint, + sequence=0, + fee=2 * (Decimal(fee) / COIN) * n, + )["hex"] # This will raise an exception assert_raises_rpc_error(-26, "too many potential replacements", self.nodes[0].sendrawtransaction, dbl_tx_hex, 0) - for tx in tree_txs: - tx.rehash() - self.nodes[0].getrawtransaction(tx.hash) + for txid in tree_txs: + self.nodes[0].getrawtransaction(txid) def test_replacement_feeperkb(self): """Replacement requires fee-per-KB to be higher""" tx0_outpoint = self.make_utxo(self.nodes[0], int(1.1 * COIN)) - tx1a = CTransaction() - tx1a.vin = [CTxIn(tx0_outpoint, nSequence=0)] - tx1a.vout = [CTxOut(1 * COIN, DUMMY_P2WPKH_SCRIPT)] - tx1a_hex = tx1a.serialize().hex() - self.nodes[0].sendrawtransaction(tx1a_hex, 0) + self.wallet.send_self_transfer( + from_node=self.nodes[0], + utxo_to_spend=tx0_outpoint, + sequence=0, + fee=Decimal("0.1"), + ) # Higher fee, but the fee per KB is much lower, so the replacement is # rejected. - tx1b = CTransaction() - tx1b.vin = [CTxIn(tx0_outpoint, nSequence=0)] - tx1b.vout = [CTxOut(int(0.001 * COIN), CScript([b'a' * 999000]))] - tx1b_hex = tx1b.serialize().hex() + tx1b_hex = self.wallet.create_self_transfer_multi( + utxos_to_spend=[tx0_outpoint], + sequence=0, + num_outputs=100, + amount_per_output=1000, + )["hex"] # This will raise an exception due to insufficient fee assert_raises_rpc_error(-26, "insufficient fee", self.nodes[0].sendrawtransaction, tx1b_hex, 0) @@ -296,37 +282,36 @@ class ReplaceByFeeTest(BitcoinTestFramework): utxo1 = self.make_utxo(self.nodes[0], int(1.2 * COIN)) utxo2 = self.make_utxo(self.nodes[0], 3 * COIN) - tx1a = CTransaction() - tx1a.vin = [CTxIn(utxo1, nSequence=0)] - tx1a.vout = [CTxOut(int(1.1 * COIN), DUMMY_P2WPKH_SCRIPT)] - tx1a_hex = tx1a.serialize().hex() - tx1a_txid = self.nodes[0].sendrawtransaction(tx1a_hex, 0) - - tx1a_txid = int(tx1a_txid, 16) + tx1a_utxo = self.wallet.send_self_transfer( + from_node=self.nodes[0], + utxo_to_spend=utxo1, + sequence=0, + fee=Decimal("0.1"), + )["new_utxo"] # Direct spend an output of the transaction we're replacing. - tx2 = CTransaction() - tx2.vin = [CTxIn(utxo1, nSequence=0), CTxIn(utxo2, nSequence=0)] - tx2.vin.append(CTxIn(COutPoint(tx1a_txid, 0), nSequence=0)) - tx2.vout = tx1a.vout - tx2_hex = tx2.serialize().hex() + tx2_hex = self.wallet.create_self_transfer_multi( + utxos_to_spend=[utxo1, utxo2, tx1a_utxo], + sequence=0, + amount_per_output=int(COIN * tx1a_utxo["value"]), + )["hex"] # This will raise an exception assert_raises_rpc_error(-26, "bad-txns-spends-conflicting-tx", self.nodes[0].sendrawtransaction, tx2_hex, 0) # Spend tx1a's output to test the indirect case. - tx1b = CTransaction() - tx1b.vin = [CTxIn(COutPoint(tx1a_txid, 0), nSequence=0)] - tx1b.vout = [CTxOut(1 * COIN, DUMMY_P2WPKH_SCRIPT)] - tx1b_hex = tx1b.serialize().hex() - tx1b_txid = self.nodes[0].sendrawtransaction(tx1b_hex, 0) - tx1b_txid = int(tx1b_txid, 16) + tx1b_utxo = self.wallet.send_self_transfer( + from_node=self.nodes[0], + utxo_to_spend=tx1a_utxo, + sequence=0, + fee=Decimal("0.1"), + )["new_utxo"] - tx2 = CTransaction() - tx2.vin = [CTxIn(utxo1, nSequence=0), CTxIn(utxo2, nSequence=0), - CTxIn(COutPoint(tx1b_txid, 0))] - tx2.vout = tx1a.vout - tx2_hex = tx2.serialize().hex() + tx2_hex = self.wallet.create_self_transfer_multi( + utxos_to_spend=[utxo1, utxo2, tx1b_utxo], + sequence=0, + amount_per_output=int(COIN * tx1a_utxo["value"]), + )["hex"] # This will raise an exception assert_raises_rpc_error(-26, "bad-txns-spends-conflicting-tx", self.nodes[0].sendrawtransaction, tx2_hex, 0) @@ -334,18 +319,20 @@ class ReplaceByFeeTest(BitcoinTestFramework): def test_new_unconfirmed_inputs(self): """Replacements that add new unconfirmed inputs are rejected""" confirmed_utxo = self.make_utxo(self.nodes[0], int(1.1 * COIN)) - unconfirmed_utxo = self.make_utxo(self.nodes[0], int(0.1 * COIN), False) + unconfirmed_utxo = self.make_utxo(self.nodes[0], int(0.1 * COIN), confirmed=False) - tx1 = CTransaction() - tx1.vin = [CTxIn(confirmed_utxo)] - tx1.vout = [CTxOut(1 * COIN, DUMMY_P2WPKH_SCRIPT)] - tx1_hex = tx1.serialize().hex() - self.nodes[0].sendrawtransaction(tx1_hex, 0) + self.wallet.send_self_transfer( + from_node=self.nodes[0], + utxo_to_spend=confirmed_utxo, + sequence=0, + fee=Decimal("0.1"), + ) - tx2 = CTransaction() - tx2.vin = [CTxIn(confirmed_utxo), CTxIn(unconfirmed_utxo)] - tx2.vout = tx1.vout - tx2_hex = tx2.serialize().hex() + tx2_hex = self.wallet.create_self_transfer_multi( + utxos_to_spend=[confirmed_utxo, unconfirmed_utxo], + sequence=0, + amount_per_output=1 * COIN, + )["hex"] # This will raise an exception assert_raises_rpc_error(-26, "replacement-adds-unconfirmed", self.nodes[0].sendrawtransaction, tx2_hex, 0) @@ -361,51 +348,45 @@ class ReplaceByFeeTest(BitcoinTestFramework): fee = int(0.0001 * COIN) split_value = int((initial_nValue - fee) / (MAX_REPLACEMENT_LIMIT + 1)) - outputs = [] - for _ in range(MAX_REPLACEMENT_LIMIT + 1): - outputs.append(CTxOut(split_value, CScript([1]))) - - splitting_tx = CTransaction() - splitting_tx.vin = [CTxIn(utxo, nSequence=0)] - splitting_tx.vout = outputs - splitting_tx_hex = splitting_tx.serialize().hex() - - txid = self.nodes[0].sendrawtransaction(splitting_tx_hex, 0) - txid = int(txid, 16) + splitting_tx_utxos = self.wallet.send_self_transfer_multi( + from_node=self.nodes[0], + utxos_to_spend=[utxo], + sequence=0, + num_outputs=MAX_REPLACEMENT_LIMIT + 1, + amount_per_output=split_value, + )["new_utxos"] # Now spend each of those outputs individually - for i in range(MAX_REPLACEMENT_LIMIT + 1): - tx_i = CTransaction() - tx_i.vin = [CTxIn(COutPoint(txid, i), nSequence=0)] - tx_i.vout = [CTxOut(split_value - fee, DUMMY_P2WPKH_SCRIPT)] - tx_i_hex = tx_i.serialize().hex() - self.nodes[0].sendrawtransaction(tx_i_hex, 0) + for utxo in splitting_tx_utxos: + self.wallet.send_self_transfer( + from_node=self.nodes[0], + utxo_to_spend=utxo, + sequence=0, + fee=Decimal(fee) / COIN, + ) # Now create doublespend of the whole lot; should fail. # Need a big enough fee to cover all spending transactions and have # a higher fee rate double_spend_value = (split_value - 100 * fee) * (MAX_REPLACEMENT_LIMIT + 1) - inputs = [] - for i in range(MAX_REPLACEMENT_LIMIT + 1): - inputs.append(CTxIn(COutPoint(txid, i), nSequence=0)) - double_tx = CTransaction() - double_tx.vin = inputs - double_tx.vout = [CTxOut(double_spend_value, CScript([b'a']))] + double_tx = self.wallet.create_self_transfer_multi( + utxos_to_spend=splitting_tx_utxos, + sequence=0, + amount_per_output=double_spend_value, + )["tx"] double_tx_hex = double_tx.serialize().hex() # This will raise an exception assert_raises_rpc_error(-26, "too many potential replacements", self.nodes[0].sendrawtransaction, double_tx_hex, 0) # If we remove an input, it should pass - double_tx = CTransaction() - double_tx.vin = inputs[0:-1] - double_tx.vout = [CTxOut(double_spend_value, CScript([b'a']))] + double_tx.vin.pop() double_tx_hex = double_tx.serialize().hex() self.nodes[0].sendrawtransaction(double_tx_hex, 0) def test_too_many_replacements_with_default_mempool_params(self): """ - Test rule 5 of BIP125 (do not allow replacements that cause more than 100 + Test rule 5 (do not allow replacements that cause more than 100 evictions) without having to rely on non-default mempool parameters. In order to do this, create a number of "root" UTXOs, and then hang @@ -424,7 +405,7 @@ class ReplaceByFeeTest(BitcoinTestFramework): # limit; 10 works. num_tx_graphs = 10 - # (Number of transactions per graph, BIP125 rule 5 failure expected) + # (Number of transactions per graph, rule 5 failure expected) cases = [ # Test the base case of evicting fewer than MAX_REPLACEMENT_LIMIT # transactions. @@ -447,7 +428,7 @@ class ReplaceByFeeTest(BitcoinTestFramework): optin_parent_tx = wallet.send_self_transfer_multi( from_node=normal_node, - sequence=BIP125_SEQUENCE_NUMBER, + sequence=MAX_BIP125_RBF_SEQUENCE, utxos_to_spend=[root_utxos[graph_num]], num_outputs=txs_per_graph, ) @@ -472,12 +453,10 @@ class ReplaceByFeeTest(BitcoinTestFramework): # Now attempt to submit a tx that double-spends all the root tx inputs, which # would invalidate `num_txs_invalidated` transactions. - double_tx = wallet.create_self_transfer_multi( - from_node=normal_node, + tx_hex = wallet.create_self_transfer_multi( utxos_to_spend=root_utxos, fee_per_output=10_000_000, # absurdly high feerate - ) - tx_hex = double_tx.serialize().hex() + )["hex"] if failure_expected: assert_raises_rpc_error( @@ -496,20 +475,22 @@ class ReplaceByFeeTest(BitcoinTestFramework): tx0_outpoint = self.make_utxo(self.nodes[0], int(1.1 * COIN)) # Create a non-opting in transaction - tx1a = CTransaction() - tx1a.vin = [CTxIn(tx0_outpoint, nSequence=SEQUENCE_FINAL)] - tx1a.vout = [CTxOut(1 * COIN, DUMMY_P2WPKH_SCRIPT)] - tx1a_hex = tx1a.serialize().hex() - tx1a_txid = self.nodes[0].sendrawtransaction(tx1a_hex, 0) + tx1a_utxo = self.wallet.send_self_transfer( + from_node=self.nodes[0], + utxo_to_spend=tx0_outpoint, + sequence=SEQUENCE_FINAL, + fee=Decimal("0.1"), + )["new_utxo"] # This transaction isn't shown as replaceable - assert_equal(self.nodes[0].getmempoolentry(tx1a_txid)['bip125-replaceable'], False) + assert_equal(self.nodes[0].getmempoolentry(tx1a_utxo["txid"])['bip125-replaceable'], False) # Shouldn't be able to double-spend - tx1b = CTransaction() - tx1b.vin = [CTxIn(tx0_outpoint, nSequence=0)] - tx1b.vout = [CTxOut(int(0.9 * COIN), DUMMY_P2WPKH_SCRIPT)] - tx1b_hex = tx1b.serialize().hex() + tx1b_hex = self.wallet.create_self_transfer( + utxo_to_spend=tx0_outpoint, + sequence=0, + fee=Decimal("0.2"), + )["hex"] # This will raise an exception assert_raises_rpc_error(-26, "txn-mempool-conflict", self.nodes[0].sendrawtransaction, tx1b_hex, 0) @@ -517,17 +498,19 @@ class ReplaceByFeeTest(BitcoinTestFramework): tx1_outpoint = self.make_utxo(self.nodes[0], int(1.1 * COIN)) # Create a different non-opting in transaction - tx2a = CTransaction() - tx2a.vin = [CTxIn(tx1_outpoint, nSequence=0xfffffffe)] - tx2a.vout = [CTxOut(1 * COIN, DUMMY_P2WPKH_SCRIPT)] - tx2a_hex = tx2a.serialize().hex() - tx2a_txid = self.nodes[0].sendrawtransaction(tx2a_hex, 0) + tx2a_utxo = self.wallet.send_self_transfer( + from_node=self.nodes[0], + utxo_to_spend=tx1_outpoint, + sequence=0xfffffffe, + fee=Decimal("0.1"), + )["new_utxo"] # Still shouldn't be able to double-spend - tx2b = CTransaction() - tx2b.vin = [CTxIn(tx1_outpoint, nSequence=0)] - tx2b.vout = [CTxOut(int(0.9 * COIN), DUMMY_P2WPKH_SCRIPT)] - tx2b_hex = tx2b.serialize().hex() + tx2b_hex = self.wallet.create_self_transfer( + utxo_to_spend=tx1_outpoint, + sequence=0, + fee=Decimal("0.2"), + )["hex"] # This will raise an exception assert_raises_rpc_error(-26, "txn-mempool-conflict", self.nodes[0].sendrawtransaction, tx2b_hex, 0) @@ -536,34 +519,31 @@ class ReplaceByFeeTest(BitcoinTestFramework): # opt-in on one of the inputs # Transaction should be replaceable on either input - tx1a_txid = int(tx1a_txid, 16) - tx2a_txid = int(tx2a_txid, 16) - - tx3a = CTransaction() - tx3a.vin = [CTxIn(COutPoint(tx1a_txid, 0), nSequence=SEQUENCE_FINAL), - CTxIn(COutPoint(tx2a_txid, 0), nSequence=0xfffffffd)] - tx3a.vout = [CTxOut(int(0.9 * COIN), CScript([b'c'])), CTxOut(int(0.9 * COIN), CScript([b'd']))] - tx3a_hex = tx3a.serialize().hex() - - tx3a_txid = self.nodes[0].sendrawtransaction(tx3a_hex, 0) + tx3a_txid = self.wallet.send_self_transfer_multi( + from_node=self.nodes[0], + utxos_to_spend=[tx1a_utxo, tx2a_utxo], + sequence=[SEQUENCE_FINAL, 0xfffffffd], + fee_per_output=int(0.1 * COIN), + )["txid"] # This transaction is shown as replaceable assert_equal(self.nodes[0].getmempoolentry(tx3a_txid)['bip125-replaceable'], True) - tx3b = CTransaction() - tx3b.vin = [CTxIn(COutPoint(tx1a_txid, 0), nSequence=0)] - tx3b.vout = [CTxOut(int(0.5 * COIN), DUMMY_P2WPKH_SCRIPT)] - tx3b_hex = tx3b.serialize().hex() - - tx3c = CTransaction() - tx3c.vin = [CTxIn(COutPoint(tx2a_txid, 0), nSequence=0)] - tx3c.vout = [CTxOut(int(0.5 * COIN), DUMMY_P2WPKH_SCRIPT)] - tx3c_hex = tx3c.serialize().hex() + self.wallet.send_self_transfer( + from_node=self.nodes[0], + utxo_to_spend=tx1a_utxo, + sequence=0, + fee=Decimal("0.4"), + ) - self.nodes[0].sendrawtransaction(tx3b_hex, 0) # If tx3b was accepted, tx3c won't look like a replacement, # but make sure it is accepted anyway - self.nodes[0].sendrawtransaction(tx3c_hex, 0) + self.wallet.send_self_transfer( + from_node=self.nodes[0], + utxo_to_spend=tx2a_utxo, + sequence=0, + fee=Decimal("0.4"), + ) def test_prioritised_transactions(self): # Ensure that fee deltas used via prioritisetransaction are @@ -572,17 +552,20 @@ class ReplaceByFeeTest(BitcoinTestFramework): # 1. Check that feeperkb uses modified fees tx0_outpoint = self.make_utxo(self.nodes[0], int(1.1 * COIN)) - tx1a = CTransaction() - tx1a.vin = [CTxIn(tx0_outpoint, nSequence=0)] - tx1a.vout = [CTxOut(1 * COIN, DUMMY_P2WPKH_SCRIPT)] - tx1a_hex = tx1a.serialize().hex() - tx1a_txid = self.nodes[0].sendrawtransaction(tx1a_hex, 0) + tx1a_txid = self.wallet.send_self_transfer( + from_node=self.nodes[0], + utxo_to_spend=tx0_outpoint, + sequence=0, + fee=Decimal("0.1"), + )["txid"] # Higher fee, but the actual fee per KB is much lower. - tx1b = CTransaction() - tx1b.vin = [CTxIn(tx0_outpoint, nSequence=0)] - tx1b.vout = [CTxOut(int(0.001 * COIN), CScript([b'a' * 740000]))] - tx1b_hex = tx1b.serialize().hex() + tx1b_hex = self.wallet.create_self_transfer_multi( + utxos_to_spend=[tx0_outpoint], + sequence=0, + num_outputs=100, + amount_per_output=int(0.00001 * COIN), + )["hex"] # Verify tx1b cannot replace tx1a. assert_raises_rpc_error(-26, "insufficient fee", self.nodes[0].sendrawtransaction, tx1b_hex, 0) @@ -598,27 +581,29 @@ class ReplaceByFeeTest(BitcoinTestFramework): # 2. Check that absolute fee checks use modified fee. tx1_outpoint = self.make_utxo(self.nodes[0], int(1.1 * COIN)) - tx2a = CTransaction() - tx2a.vin = [CTxIn(tx1_outpoint, nSequence=0)] - tx2a.vout = [CTxOut(1 * COIN, DUMMY_P2WPKH_SCRIPT)] - tx2a_hex = tx2a.serialize().hex() - self.nodes[0].sendrawtransaction(tx2a_hex, 0) + # tx2a + self.wallet.send_self_transfer( + from_node=self.nodes[0], + utxo_to_spend=tx1_outpoint, + sequence=0, + fee=Decimal("0.1"), + ) # Lower fee, but we'll prioritise it - tx2b = CTransaction() - tx2b.vin = [CTxIn(tx1_outpoint, nSequence=0)] - tx2b.vout = [CTxOut(int(1.01 * COIN), DUMMY_P2WPKH_SCRIPT)] - tx2b.rehash() - tx2b_hex = tx2b.serialize().hex() + tx2b = self.wallet.create_self_transfer( + utxo_to_spend=tx1_outpoint, + sequence=0, + fee=Decimal("0.09"), + ) # Verify tx2b cannot replace tx2a. - assert_raises_rpc_error(-26, "insufficient fee", self.nodes[0].sendrawtransaction, tx2b_hex, 0) + assert_raises_rpc_error(-26, "insufficient fee", self.nodes[0].sendrawtransaction, tx2b["hex"], 0) # Now prioritise tx2b to have a higher modified fee - self.nodes[0].prioritisetransaction(txid=tx2b.hash, fee_delta=int(0.1 * COIN)) + self.nodes[0].prioritisetransaction(txid=tx2b["txid"], fee_delta=int(0.1 * COIN)) # tx2b should now be accepted - tx2b_txid = self.nodes[0].sendrawtransaction(tx2b_hex, 0) + tx2b_txid = self.nodes[0].sendrawtransaction(tx2b["hex"], 0) assert tx2b_txid in self.nodes[0].getrawmempool() @@ -651,14 +636,14 @@ class ReplaceByFeeTest(BitcoinTestFramework): optin_parent_tx = self.wallet.send_self_transfer( from_node=self.nodes[0], utxo_to_spend=confirmed_utxo, - sequence=BIP125_SEQUENCE_NUMBER, + sequence=MAX_BIP125_RBF_SEQUENCE, fee_rate=Decimal('0.01'), ) assert_equal(True, self.nodes[0].getmempoolentry(optin_parent_tx['txid'])['bip125-replaceable']) replacement_parent_tx = self.wallet.create_self_transfer( utxo_to_spend=confirmed_utxo, - sequence=BIP125_SEQUENCE_NUMBER, + sequence=MAX_BIP125_RBF_SEQUENCE, fee_rate=Decimal('0.02'), ) @@ -684,7 +669,6 @@ class ReplaceByFeeTest(BitcoinTestFramework): utxo_to_spend=parent_utxo, sequence=SEQUENCE_FINAL, fee_rate=Decimal('0.02'), - mempool_valid=False, ) # Broadcast replacement child tx @@ -713,8 +697,37 @@ class ReplaceByFeeTest(BitcoinTestFramework): # Higher fee, higher feerate, different txid, but the replacement does not provide a relay # fee conforming to node's `incrementalrelayfee` policy of 1000 sat per KB. + assert_equal(self.nodes[0].getmempoolinfo()["incrementalrelayfee"], Decimal("0.00001")) tx.vout[0].nValue -= 1 assert_raises_rpc_error(-26, "insufficient fee", self.nodes[0].sendrawtransaction, tx.serialize().hex()) + def test_fullrbf(self): + + confirmed_utxo = self.make_utxo(self.nodes[0], int(2 * COIN)) + self.restart_node(0, extra_args=["-mempoolfullrbf=1"]) + assert self.nodes[0].getmempoolinfo()["fullrbf"] + + # Create an explicitly opt-out transaction + optout_tx = self.wallet.send_self_transfer( + from_node=self.nodes[0], + utxo_to_spend=confirmed_utxo, + sequence=MAX_BIP125_RBF_SEQUENCE + 1, + fee_rate=Decimal('0.01'), + ) + assert_equal(False, self.nodes[0].getmempoolentry(optout_tx['txid'])['bip125-replaceable']) + + conflicting_tx = self.wallet.create_self_transfer( + utxo_to_spend=confirmed_utxo, + sequence=SEQUENCE_FINAL, + fee_rate=Decimal('0.02'), + ) + + # Send the replacement transaction, conflicting with the optout_tx. + self.nodes[0].sendrawtransaction(conflicting_tx['hex'], 0) + + # Optout_tx is not anymore in the mempool. + assert optout_tx['txid'] not in self.nodes[0].getrawmempool() + assert conflicting_tx['txid'] in self.nodes[0].getrawmempool() + if __name__ == '__main__': ReplaceByFeeTest().main() diff --git a/test/functional/feature_segwit.py b/test/functional/feature_segwit.py index f0faf1421b..7f2a615be1 100755 --- a/test/functional/feature_segwit.py +++ b/test/functional/feature_segwit.py @@ -609,6 +609,11 @@ class SegWitTest(BitcoinTestFramework): assert_equal(self.nodes[1].gettransaction(txid, True)["txid"], txid) assert_equal(self.nodes[1].listtransactions("*", 1, 0, True)[0]["txid"], txid) + self.log.info('Test negative and unknown rpcserialversion throw an init error') + self.stop_node(0) + self.nodes[0].assert_start_raises_init_error(["-rpcserialversion=-1"], "Error: rpcserialversion must be non-negative.") + self.nodes[0].assert_start_raises_init_error(["-rpcserialversion=100"], "Error: Unknown rpcserialversion requested.") + def mine_and_test_listunspent(self, script_list, ismine): utxo = find_spendable_utxo(self.nodes[0], 50) tx = CTransaction() diff --git a/test/functional/feature_signet.py b/test/functional/feature_signet.py index 6578caee3f..4c1e48af6d 100755 --- a/test/functional/feature_signet.py +++ b/test/functional/feature_signet.py @@ -39,6 +39,14 @@ class SignetBasicTest(BitcoinTestFramework): shared_args3, shared_args3, ] + def setup_network(self): + self.setup_nodes() + + # Setup the three signets, which are incompatible with each other + self.connect_nodes(0, 1) + self.connect_nodes(2, 3) + self.connect_nodes(4, 5) + def run_test(self): self.log.info("basic tests using OP_TRUE challenge") diff --git a/test/functional/feature_taproot.py b/test/functional/feature_taproot.py index 0e44038196..cbb2e0338b 100755 --- a/test/functional/feature_taproot.py +++ b/test/functional/feature_taproot.py @@ -91,7 +91,11 @@ from test_framework.script_util import ( script_to_p2wsh_script, ) from test_framework.test_framework import BitcoinTestFramework -from test_framework.util import assert_raises_rpc_error, assert_equal +from test_framework.util import ( + assert_raises_rpc_error, + assert_equal, + random_bytes, +) from test_framework.key import generate_privkey, compute_xonly_pubkey, sign_schnorr, tweak_add_privkey, ECKey from test_framework.address import ( hash160, @@ -566,10 +570,6 @@ def random_checksig_style(pubkey): ret = CScript([pubkey, opcode]) return bytes(ret) -def random_bytes(n): - """Return a random bytes object of length n.""" - return bytes(random.getrandbits(8) for i in range(n)) - def bitflipper(expr): """Return a callable that evaluates expr and returns it with a random bitflip.""" def fn(ctx): @@ -1007,13 +1007,13 @@ def spenders_taproot_active(): # input a valid signature with the passed pk followed by a dummy push of bytes that are to be dropped, and # will execute sigops signature checks. SIGOPS_RATIO_SCRIPTS = [ - # n OP_CHECKSIGVERFIYs and 1 OP_CHECKSIG. + # n OP_CHECKSIGVERIFYs and 1 OP_CHECKSIG. lambda n, pk: (CScript([OP_DROP, pk] + [OP_2DUP, OP_CHECKSIGVERIFY] * n + [OP_CHECKSIG]), n + 1), # n OP_CHECKSIGVERIFYs and 1 OP_CHECKSIGADD, but also one unexecuted OP_CHECKSIGVERIFY. lambda n, pk: (CScript([OP_DROP, pk, OP_0, OP_IF, OP_2DUP, OP_CHECKSIGVERIFY, OP_ENDIF] + [OP_2DUP, OP_CHECKSIGVERIFY] * n + [OP_2, OP_SWAP, OP_CHECKSIGADD, OP_3, OP_EQUAL]), n + 1), # n OP_CHECKSIGVERIFYs and 1 OP_CHECKSIGADD, but also one unexecuted OP_CHECKSIG. lambda n, pk: (CScript([random_bytes(220), OP_2DROP, pk, OP_1, OP_NOTIF, OP_2DUP, OP_CHECKSIG, OP_VERIFY, OP_ENDIF] + [OP_2DUP, OP_CHECKSIGVERIFY] * n + [OP_4, OP_SWAP, OP_CHECKSIGADD, OP_5, OP_EQUAL]), n + 1), - # n OP_CHECKSIGVERFIYs and 1 OP_CHECKSIGADD, but also one unexecuted OP_CHECKSIGADD. + # n OP_CHECKSIGVERIFYs and 1 OP_CHECKSIGADD, but also one unexecuted OP_CHECKSIGADD. lambda n, pk: (CScript([OP_DROP, pk, OP_1, OP_IF, OP_ELSE, OP_2DUP, OP_6, OP_SWAP, OP_CHECKSIGADD, OP_7, OP_EQUALVERIFY, OP_ENDIF] + [OP_2DUP, OP_CHECKSIGVERIFY] * n + [OP_8, OP_SWAP, OP_CHECKSIGADD, OP_9, OP_EQUAL]), n + 1), # n+1 OP_CHECKSIGs, but also one OP_CHECKSIG with an empty signature. lambda n, pk: (CScript([OP_DROP, OP_0, pk, OP_CHECKSIG, OP_NOT, OP_VERIFY, pk] + [OP_2DUP, OP_CHECKSIG, OP_VERIFY] * n + [OP_CHECKSIG]), n + 1), @@ -1131,6 +1131,12 @@ def spenders_taproot_active(): tap = taproot_construct(pubs[0], scripts) add_spender(spenders, "alwaysvalid/notsuccessx", tap=tap, leaf="op_success", inputs=[], standard=False, failure={"leaf": "normal"}) # err_msg differs based on opcode + # == Test case for https://github.com/bitcoin/bitcoin/issues/24765 == + + zero_fn = lambda h: bytes([0 for _ in range(32)]) + tap = taproot_construct(pubs[0], [("leaf", CScript([pubs[1], OP_CHECKSIG, pubs[1], OP_CHECKSIGADD, OP_2, OP_EQUAL])), zero_fn]) + add_spender(spenders, "case24765", tap=tap, leaf="leaf", inputs=[getter("sign"), getter("sign")], key=secs[1], no_fail=True) + # == Legacy tests == # Also add a few legacy spends into the mix, so that transactions which combine taproot and pre-taproot spends get tested too. diff --git a/test/functional/interface_rest.py b/test/functional/interface_rest.py index f36bbda3af..610a28c56b 100755 --- a/test/functional/interface_rest.py +++ b/test/functional/interface_rest.py @@ -288,6 +288,10 @@ class RESTTest (BitcoinTestFramework): # See if we can get 5 headers in one response self.generate(self.nodes[1], 5) + expected_filter = { + 'basic block filter index': {'synced': True, 'best_block_height': 208}, + } + self.wait_until(lambda: self.nodes[0].getindexinfo() == expected_filter) json_obj = self.test_rest_request(f"/headers/{bb_hash}", query_params={"count": 5}) assert_equal(len(json_obj), 5) # now we should have 5 header objects json_obj = self.test_rest_request(f"/blockfilterheaders/basic/{bb_hash}", query_params={"count": 5}) diff --git a/test/functional/interface_usdt_net.py b/test/functional/interface_usdt_net.py index 9522cd8c59..2235da702b 100755 --- a/test/functional/interface_usdt_net.py +++ b/test/functional/interface_usdt_net.py @@ -109,7 +109,7 @@ class NetTracepointTest(BitcoinTestFramework): self.log.info( "hook into the net:inbound_message and net:outbound_message tracepoints") - ctx = USDT(path=str(self.options.bitcoind)) + ctx = USDT(pid=self.nodes[0].process.pid) ctx.enable_probe(probe="net:inbound_message", fn_name="trace_inbound_message") ctx.enable_probe(probe="net:outbound_message", diff --git a/test/functional/interface_usdt_utxocache.py b/test/functional/interface_usdt_utxocache.py index 0c7f351e66..2280de1479 100755 --- a/test/functional/interface_usdt_utxocache.py +++ b/test/functional/interface_usdt_utxocache.py @@ -169,12 +169,11 @@ class UTXOCacheTracepointTest(BitcoinTestFramework): # Create a transaction and invalidate it by changing the txid of the previous # output to the coinbase txid of the block at height 1. - invalid_tx = self.wallet.create_self_transfer( - from_node=self.nodes[0])["tx"] + invalid_tx = self.wallet.create_self_transfer()["tx"] invalid_tx.vin[0].prevout.hash = int(block_1_coinbase_txid, 16) self.log.info("hooking into the utxocache:uncache tracepoint") - ctx = USDT(path=str(self.options.bitcoind)) + ctx = USDT(pid=self.nodes[0].process.pid) ctx.enable_probe(probe="utxocache:uncache", fn_name="trace_utxocache_uncache") bpf = BPF(text=utxocache_changes_program, usdt_contexts=[ctx], debug=0) @@ -239,7 +238,7 @@ class UTXOCacheTracepointTest(BitcoinTestFramework): self.log.info( "hook into the utxocache:add and utxocache:spent tracepoints") - ctx = USDT(path=str(self.options.bitcoind)) + ctx = USDT(pid=self.nodes[0].process.pid) ctx.enable_probe(probe="utxocache:add", fn_name="trace_utxocache_add") ctx.enable_probe(probe="utxocache:spent", fn_name="trace_utxocache_spent") @@ -335,7 +334,7 @@ class UTXOCacheTracepointTest(BitcoinTestFramework): self.log.info("test the utxocache:flush tracepoint API") self.log.info("hook into the utxocache:flush tracepoint") - ctx = USDT(path=str(self.options.bitcoind)) + ctx = USDT(pid=self.nodes[0].process.pid) ctx.enable_probe(probe="utxocache:flush", fn_name="trace_utxocache_flush") bpf = BPF(text=utxocache_flushes_program, usdt_contexts=[ctx], debug=0) @@ -346,16 +345,17 @@ class UTXOCacheTracepointTest(BitcoinTestFramework): # that the handle_* functions succeeded. EXPECTED_HANDLE_FLUSH_SUCCESS = 3 handle_flush_succeeds = 0 - possible_cache_sizes = set() - expected_flushes = [] + expected_flushes = list() def handle_utxocache_flush(_, data, __): nonlocal handle_flush_succeeds event = ctypes.cast(data, ctypes.POINTER(UTXOCacheFlush)).contents self.log.info(f"handle_utxocache_flush(): {event}") - expected = expected_flushes.pop(0) - assert_equal(expected["mode"], FLUSHMODE_NAME[event.mode]) - possible_cache_sizes.remove(event.size) # fails if size not in set + expected_flushes.remove({ + "mode": FLUSHMODE_NAME[event.mode], + "for_prune": event.for_prune, + "size": event.size + }) # sanity checks only assert(event.memory > 0) assert(event.duration > 0) @@ -364,20 +364,19 @@ class UTXOCacheTracepointTest(BitcoinTestFramework): bpf["utxocache_flush"].open_perf_buffer(handle_utxocache_flush) self.log.info("stop the node to flush the UTXO cache") - UTXOS_IN_CACHE = 104 # might need to be changed if the eariler tests are modified + UTXOS_IN_CACHE = 2 # might need to be changed if the eariler tests are modified # A node shutdown causes two flushes. One that flushes UTXOS_IN_CACHE # UTXOs and one that flushes 0 UTXOs. Normally the 0-UTXO-flush is the # second flush, however it can happen that the order changes. - possible_cache_sizes = {UTXOS_IN_CACHE, 0} - flush_for_shutdown = {"mode": "ALWAYS", "for_prune": False} - expected_flushes.extend([flush_for_shutdown, flush_for_shutdown]) + expected_flushes.append({"mode": "ALWAYS", "for_prune": False, "size": UTXOS_IN_CACHE}) + expected_flushes.append({"mode": "ALWAYS", "for_prune": False, "size": 0}) self.stop_node(0) bpf.perf_buffer_poll(timeout=200) + bpf.cleanup() self.log.info("check that we don't expect additional flushes") assert_equal(0, len(expected_flushes)) - assert_equal(0, len(possible_cache_sizes)) self.log.info("restart the node with -prune") self.start_node(0, ["-fastprune=1", "-prune=1"]) @@ -385,12 +384,17 @@ class UTXOCacheTracepointTest(BitcoinTestFramework): BLOCKS_TO_MINE = 350 self.log.info(f"mine {BLOCKS_TO_MINE} blocks to be able to prune") self.generate(self.wallet, BLOCKS_TO_MINE) - # we added BLOCKS_TO_MINE coinbase UTXOs to the cache - possible_cache_sizes = {BLOCKS_TO_MINE} - expected_flushes.append( - {"mode": "NONE", "for_prune": True, "size_fn": lambda x: x == BLOCKS_TO_MINE}) + + self.log.info("test the utxocache:flush tracepoint API with pruning") + self.log.info("hook into the utxocache:flush tracepoint") + ctx = USDT(pid=self.nodes[0].process.pid) + ctx.enable_probe(probe="utxocache:flush", + fn_name="trace_utxocache_flush") + bpf = BPF(text=utxocache_flushes_program, usdt_contexts=[ctx], debug=0) + bpf["utxocache_flush"].open_perf_buffer(handle_utxocache_flush) self.log.info(f"prune blockchain to trigger a flush for pruning") + expected_flushes.append({"mode": "NONE", "for_prune": True, "size": 0}) self.nodes[0].pruneblockchain(315) bpf.perf_buffer_poll(timeout=500) @@ -399,7 +403,6 @@ class UTXOCacheTracepointTest(BitcoinTestFramework): self.log.info( f"check that we don't expect additional flushes and that the handle_* function succeeded") assert_equal(0, len(expected_flushes)) - assert_equal(0, len(possible_cache_sizes)) assert_equal(EXPECTED_HANDLE_FLUSH_SUCCESS, handle_flush_succeeds) diff --git a/test/functional/interface_usdt_validation.py b/test/functional/interface_usdt_validation.py index d11809273b..8953dd023b 100755 --- a/test/functional/interface_usdt_validation.py +++ b/test/functional/interface_usdt_validation.py @@ -91,10 +91,10 @@ class ValidationTracepointTest(BitcoinTestFramework): # that the handle_* functions succeeded. BLOCKS_EXPECTED = 2 blocks_checked = 0 - expected_blocks = list() + expected_blocks = dict() self.log.info("hook into the validation:block_connected tracepoint") - ctx = USDT(path=str(self.options.bitcoind)) + ctx = USDT(pid=self.nodes[0].process.pid) ctx.enable_probe(probe="validation:block_connected", fn_name="trace_block_connected") bpf = BPF(text=validation_blockconnected_program, @@ -104,15 +104,16 @@ class ValidationTracepointTest(BitcoinTestFramework): nonlocal expected_blocks, blocks_checked event = ctypes.cast(data, ctypes.POINTER(Block)).contents self.log.info(f"handle_blockconnected(): {event}") - block = expected_blocks.pop(0) - assert_equal(block["hash"], bytes(event.hash[::-1]).hex()) + block_hash = bytes(event.hash[::-1]).hex() + block = expected_blocks[block_hash] + assert_equal(block["hash"], block_hash) assert_equal(block["height"], event.height) assert_equal(len(block["tx"]), event.transactions) assert_equal(len([tx["vin"] for tx in block["tx"]]), event.inputs) assert_equal(0, event.sigops) # no sigops in coinbase tx # only plausibility checks assert(event.duration > 0) - + del expected_blocks[block_hash] blocks_checked += 1 bpf["block_connected"].open_perf_buffer( @@ -122,7 +123,7 @@ class ValidationTracepointTest(BitcoinTestFramework): block_hashes = self.generatetoaddress( self.nodes[0], BLOCKS_EXPECTED, ADDRESS_BCRT1_UNSPENDABLE) for block_hash in block_hashes: - expected_blocks.append(self.nodes[0].getblock(block_hash, 2)) + expected_blocks[block_hash] = self.nodes[0].getblock(block_hash, 2) bpf.perf_buffer_poll(timeout=200) bpf.cleanup() diff --git a/test/functional/mempool_accept.py b/test/functional/mempool_accept.py index 85464b8d0d..02ec18140c 100755 --- a/test/functional/mempool_accept.py +++ b/test/functional/mempool_accept.py @@ -11,7 +11,7 @@ import math from test_framework.test_framework import BitcoinTestFramework from test_framework.key import ECKey from test_framework.messages import ( - BIP125_SEQUENCE_NUMBER, + MAX_BIP125_RBF_SEQUENCE, COIN, COutPoint, CTxIn, @@ -65,7 +65,7 @@ class MempoolAcceptanceTest(BitcoinTestFramework): assert_equal(node.getmempoolinfo()['size'], self.mempool_size) self.log.info('Should not accept garbage to testmempoolaccept') - assert_raises_rpc_error(-3, 'Expected type array, got string', lambda: node.testmempoolaccept(rawtxs='ff00baar')) + assert_raises_rpc_error(-3, 'JSON value of type string is not of expected type array', lambda: node.testmempoolaccept(rawtxs='ff00baar')) assert_raises_rpc_error(-8, 'Array must contain between 1 and 25 transactions.', lambda: node.testmempoolaccept(rawtxs=['ff22']*26)) assert_raises_rpc_error(-8, 'Array must contain between 1 and 25 transactions.', lambda: node.testmempoolaccept(rawtxs=[])) assert_raises_rpc_error(-22, 'TX decode failed', lambda: node.testmempoolaccept(rawtxs=['ff00baar'])) @@ -87,7 +87,7 @@ class MempoolAcceptanceTest(BitcoinTestFramework): self.log.info('A transaction not in the mempool') fee = Decimal('0.000007') utxo_to_spend = self.wallet.get_utxo(txid=txid_in_block) # use 0.3 BTC UTXO - tx = self.wallet.create_self_transfer(utxo_to_spend=utxo_to_spend, sequence=BIP125_SEQUENCE_NUMBER)['tx'] + tx = self.wallet.create_self_transfer(utxo_to_spend=utxo_to_spend, sequence=MAX_BIP125_RBF_SEQUENCE)['tx'] tx.vout[0].nValue = int((Decimal('0.3') - fee) * COIN) raw_tx_0 = tx.serialize().hex() txid_0 = tx.rehash() @@ -125,7 +125,7 @@ class MempoolAcceptanceTest(BitcoinTestFramework): self.log.info('A transaction that replaces a mempool transaction') tx = tx_from_hex(raw_tx_0) tx.vout[0].nValue -= int(fee * COIN) # Double the fee - tx.vin[0].nSequence = BIP125_SEQUENCE_NUMBER + 1 # Now, opt out of RBF + tx.vin[0].nSequence = MAX_BIP125_RBF_SEQUENCE + 1 # Now, opt out of RBF raw_tx_0 = tx.serialize().hex() txid_0 = tx.rehash() self.check_mempool_result( diff --git a/test/functional/mempool_datacarrier.py b/test/functional/mempool_datacarrier.py new file mode 100755 index 0000000000..13df564a37 --- /dev/null +++ b/test/functional/mempool_datacarrier.py @@ -0,0 +1,71 @@ +#!/usr/bin/env python3 +# 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. +"""Test datacarrier functionality""" +from test_framework.messages import ( + CTxOut, + MAX_OP_RETURN_RELAY, +) +from test_framework.script import ( + CScript, + OP_RETURN, +) +from test_framework.test_framework import BitcoinTestFramework +from test_framework.test_node import TestNode +from test_framework.util import ( + assert_raises_rpc_error, + random_bytes, +) +from test_framework.wallet import MiniWallet + + +class DataCarrierTest(BitcoinTestFramework): + def set_test_params(self): + self.num_nodes = 3 + self.extra_args = [ + [], + ["-datacarrier=0"], + ["-datacarrier=1", f"-datacarriersize={MAX_OP_RETURN_RELAY - 1}"] + ] + + def test_null_data_transaction(self, node: TestNode, data: bytes, success: bool) -> None: + tx = self.wallet.create_self_transfer(fee_rate=0)["tx"] + tx.vout.append(CTxOut(nValue=0, scriptPubKey=CScript([OP_RETURN, data]))) + tx.vout[0].nValue -= tx.get_vsize() # simply pay 1sat/vbyte fee + + tx_hex = tx.serialize().hex() + + if success: + self.wallet.sendrawtransaction(from_node=node, tx_hex=tx_hex) + assert tx.rehash() in node.getrawmempool(True), f'{tx_hex} not in mempool' + else: + assert_raises_rpc_error(-26, "scriptpubkey", self.wallet.sendrawtransaction, from_node=node, tx_hex=tx_hex) + + def run_test(self): + self.wallet = MiniWallet(self.nodes[0]) + self.wallet.rescan_utxos() + + # By default, only 80 bytes are used for data (+1 for OP_RETURN, +2 for the pushdata opcodes). + default_size_data = random_bytes(MAX_OP_RETURN_RELAY - 3) + too_long_data = random_bytes(MAX_OP_RETURN_RELAY - 2) + small_data = random_bytes(MAX_OP_RETURN_RELAY - 4) + + self.log.info("Testing null data transaction with default -datacarrier and -datacarriersize values.") + self.test_null_data_transaction(node=self.nodes[0], data=default_size_data, success=True) + + self.log.info("Testing a null data transaction larger than allowed by the default -datacarriersize value.") + self.test_null_data_transaction(node=self.nodes[0], data=too_long_data, success=False) + + self.log.info("Testing a null data transaction with -datacarrier=false.") + self.test_null_data_transaction(node=self.nodes[1], data=default_size_data, success=False) + + self.log.info("Testing a null data transaction with a size larger than accepted by -datacarriersize.") + self.test_null_data_transaction(node=self.nodes[2], data=default_size_data, success=False) + + self.log.info("Testing a null data transaction with a size smaller than accepted by -datacarriersize.") + self.test_null_data_transaction(node=self.nodes[2], data=small_data, success=True) + + +if __name__ == '__main__': + DataCarrierTest().main() diff --git a/test/functional/mempool_expiry.py b/test/functional/mempool_expiry.py index f301b29c25..21721177e6 100755 --- a/test/functional/mempool_expiry.py +++ b/test/functional/mempool_expiry.py @@ -5,7 +5,7 @@ """Tests that a mempool transaction expires after a given timeout and that its children are removed as well. -Both the default expiry timeout defined by DEFAULT_MEMPOOL_EXPIRY and a user +Both the default expiry timeout defined by DEFAULT_MEMPOOL_EXPIRY_HOURS and a user definable expiry timeout via the '-mempoolexpiry=<n>' command line argument (<n> is the timeout in hours) are tested. """ @@ -13,6 +13,7 @@ definable expiry timeout via the '-mempoolexpiry=<n>' command line argument from datetime import timedelta from test_framework.blocktools import COINBASE_MATURITY +from test_framework.messages import DEFAULT_MEMPOOL_EXPIRY_HOURS from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_equal, @@ -20,7 +21,6 @@ from test_framework.util import ( ) from test_framework.wallet import MiniWallet -DEFAULT_MEMPOOL_EXPIRY = 336 # hours CUSTOM_MEMPOOL_EXPIRY = 10 # hours @@ -98,8 +98,8 @@ class MempoolExpiryTest(BitcoinTestFramework): def run_test(self): self.log.info('Test default mempool expiry timeout of %d hours.' % - DEFAULT_MEMPOOL_EXPIRY) - self.test_transaction_expiry(DEFAULT_MEMPOOL_EXPIRY) + DEFAULT_MEMPOOL_EXPIRY_HOURS) + self.test_transaction_expiry(DEFAULT_MEMPOOL_EXPIRY_HOURS) self.log.info('Test custom mempool expiry timeout of %d hours.' % CUSTOM_MEMPOOL_EXPIRY) diff --git a/test/functional/mempool_limit.py b/test/functional/mempool_limit.py index 3619e05761..7080662b49 100755 --- a/test/functional/mempool_limit.py +++ b/test/functional/mempool_limit.py @@ -7,12 +7,12 @@ from decimal import Decimal from test_framework.blocktools import COINBASE_MATURITY -from test_framework.messages import COIN from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_equal, assert_greater_than, assert_raises_rpc_error, + create_lots_of_big_transactions, gen_return_txouts, ) from test_framework.wallet import MiniWallet @@ -23,22 +23,12 @@ class MempoolLimitTest(BitcoinTestFramework): self.setup_clean_chain = True self.num_nodes = 1 self.extra_args = [[ - "-acceptnonstdtxn=1", + "-datacarriersize=100000", "-maxmempool=5", "-spendzeroconfchange=0", ]] self.supports_cli = False - def send_large_txs(self, node, miniwallet, txouts, fee, tx_batch_size): - for _ in range(tx_batch_size): - tx = miniwallet.create_self_transfer(from_node=node, fee_rate=0, mempool_valid=False)['tx'] - for txout in txouts: - tx.vout.append(txout) - tx.vout[0].nValue -= int(fee * COIN) - res = node.testmempoolaccept([tx.serialize().hex()])[0] - assert_equal(res['fees']['base'], fee) - miniwallet.sendrawtransaction(from_node=node, tx_hex=tx.serialize().hex()) - def run_test(self): txouts = gen_return_txouts() node = self.nodes[0] @@ -71,7 +61,7 @@ class MempoolLimitTest(BitcoinTestFramework): self.log.info("Fill up the mempool with txs with higher fee rate") for batch_of_txid in range(num_of_batches): fee = (batch_of_txid + 1) * base_fee - self.send_large_txs(node, miniwallet, txouts, fee, tx_batch_size) + create_lots_of_big_transactions(miniwallet, node, fee, tx_batch_size, txouts) self.log.info('The tx should be evicted by now') # The number of transactions created should be greater than the ones present in the mempool @@ -85,7 +75,11 @@ class MempoolLimitTest(BitcoinTestFramework): # Deliberately try to create a tx with a fee less than the minimum mempool fee to assert that it does not get added to the mempool self.log.info('Create a mempool tx that will not pass mempoolminfee') - assert_raises_rpc_error(-26, "mempool min fee not met", miniwallet.send_self_transfer, from_node=node, fee_rate=relayfee, mempool_valid=False) + assert_raises_rpc_error(-26, "mempool min fee not met", miniwallet.send_self_transfer, from_node=node, fee_rate=relayfee) + + self.log.info('Test passing a value below the minimum (5 MB) to -maxmempool throws an error') + self.stop_node(0) + self.nodes[0].assert_start_raises_init_error(["-maxmempool=4"], "Error: -maxmempool must be at least 5 MB") if __name__ == '__main__': diff --git a/test/functional/mempool_package_limits.py b/test/functional/mempool_package_limits.py index 89a5c83826..1f12e93982 100755 --- a/test/functional/mempool_package_limits.py +++ b/test/functional/mempool_package_limits.py @@ -6,28 +6,16 @@ from decimal import Decimal -from test_framework.address import ADDRESS_BCRT1_P2WSH_OP_TRUE +from test_framework.blocktools import COINBASE_MATURITY from test_framework.test_framework import BitcoinTestFramework from test_framework.messages import ( COIN, - CTransaction, - CTxInWitness, - tx_from_hex, WITNESS_SCALE_FACTOR, ) -from test_framework.script import ( - CScript, - OP_TRUE, -) from test_framework.util import ( assert_equal, ) -from test_framework.wallet import ( - bulk_transaction, - create_child_with_parents, - make_chain, - DEFAULT_FEE, -) +from test_framework.wallet import MiniWallet class MempoolPackageLimitsTest(BitcoinTestFramework): def set_test_params(self): @@ -35,19 +23,10 @@ class MempoolPackageLimitsTest(BitcoinTestFramework): self.setup_clean_chain = True def run_test(self): - self.log.info("Generate blocks to create UTXOs") - node = self.nodes[0] - self.privkeys = [node.get_deterministic_priv_key().key] - self.address = node.get_deterministic_priv_key().address - self.coins = [] - # The last 100 coinbase transactions are premature - for b in self.generatetoaddress(node, 200, self.address)[:100]: - coinbase = node.getblock(blockhash=b, verbosity=2)["tx"][0] - self.coins.append({ - "txid": coinbase["txid"], - "amount": coinbase["vout"][0]["value"], - "scriptPubKey": coinbase["vout"][0]["scriptPubKey"], - }) + self.wallet = MiniWallet(self.nodes[0]) + # Add enough mature utxos to the wallet so that all txs spend confirmed coins. + self.generate(self.wallet, 35) + self.generate(self.nodes[0], COINBASE_MATURITY) self.test_chain_limits() self.test_desc_count_limits() @@ -56,30 +35,22 @@ class MempoolPackageLimitsTest(BitcoinTestFramework): self.test_anc_count_limits_2() self.test_anc_count_limits_bushy() - # The node will accept our (nonstandard) extra large OP_RETURN outputs - self.restart_node(0, extra_args=["-acceptnonstdtxn=1"]) + # The node will accept (nonstandard) extra large OP_RETURN outputs + self.restart_node(0, extra_args=["-datacarriersize=100000"]) self.test_anc_size_limits() self.test_desc_size_limits() def test_chain_limits_helper(self, mempool_count, package_count): node = self.nodes[0] assert_equal(0, node.getmempoolinfo()["size"]) - first_coin = self.coins.pop() - spk = None - txid = first_coin["txid"] chain_hex = [] - chain_txns = [] - value = first_coin["amount"] - - for i in range(mempool_count + package_count): - (tx, txhex, value, spk) = make_chain(node, self.address, self.privkeys, txid, value, 0, spk) - txid = tx.rehash() - if i < mempool_count: - node.sendrawtransaction(txhex) - assert_equal(node.getmempoolentry(txid)["ancestorcount"], i + 1) - else: - chain_hex.append(txhex) - chain_txns.append(tx) + + chaintip_utxo = self.wallet.send_self_transfer_chain(from_node=node, chain_length=mempool_count) + # in-package transactions + for _ in range(package_count): + tx = self.wallet.create_self_transfer(utxo_to_spend=chaintip_utxo) + chaintip_utxo = tx["new_utxo"] + chain_hex.append(tx["hex"]) testres_too_long = node.testmempoolaccept(rawtxs=chain_hex) for txres in testres_too_long: assert_equal(txres["package-error"], "package-mempool-limits") @@ -125,49 +96,20 @@ class MempoolPackageLimitsTest(BitcoinTestFramework): assert_equal(0, node.getmempoolinfo()["size"]) self.log.info("Check that in-mempool and in-package descendants are calculated properly in packages") # Top parent in mempool, M1 - first_coin = self.coins.pop() - parent_value = (first_coin["amount"] - Decimal("0.0002")) / 2 # Deduct reasonable fee and make 2 outputs - inputs = [{"txid": first_coin["txid"], "vout": 0}] - outputs = [{self.address : parent_value}, {ADDRESS_BCRT1_P2WSH_OP_TRUE : parent_value}] - rawtx = node.createrawtransaction(inputs, outputs) - - parent_signed = node.signrawtransactionwithkey(hexstring=rawtx, privkeys=self.privkeys) - assert parent_signed["complete"] - parent_tx = tx_from_hex(parent_signed["hex"]) - parent_txid = parent_tx.rehash() - node.sendrawtransaction(parent_signed["hex"]) + m1_utxos = self.wallet.send_self_transfer_multi(from_node=node, num_outputs=2)['new_utxos'] package_hex = [] - - # Chain A - spk = parent_tx.vout[0].scriptPubKey.hex() - value = parent_value - txid = parent_txid - for i in range(12): - (tx, txhex, value, spk) = make_chain(node, self.address, self.privkeys, txid, value, 0, spk) - txid = tx.rehash() - if i < 11: # M2a... M12a - node.sendrawtransaction(txhex) - else: # Pa - package_hex.append(txhex) - - # Chain B - value = parent_value - Decimal("0.0001") - rawtx_b = node.createrawtransaction([{"txid": parent_txid, "vout": 1}], {self.address : value}) - tx_child_b = tx_from_hex(rawtx_b) # M2b - tx_child_b.wit.vtxinwit = [CTxInWitness()] - tx_child_b.wit.vtxinwit[0].scriptWitness.stack = [CScript([OP_TRUE])] - tx_child_b_hex = tx_child_b.serialize().hex() - node.sendrawtransaction(tx_child_b_hex) - spk = tx_child_b.vout[0].scriptPubKey.hex() - txid = tx_child_b.rehash() - for i in range(12): - (tx, txhex, value, spk) = make_chain(node, self.address, self.privkeys, txid, value, 0, spk) - txid = tx.rehash() - if i < 11: # M3b... M13b - node.sendrawtransaction(txhex) - else: # Pb - package_hex.append(txhex) + # Chain A (M2a... M12a) + chain_a_tip_utxo = self.wallet.send_self_transfer_chain(from_node=node, chain_length=11, utxo_to_spend=m1_utxos[0]) + # Pa + pa_hex = self.wallet.create_self_transfer(utxo_to_spend=chain_a_tip_utxo)["hex"] + package_hex.append(pa_hex) + + # Chain B (M2b... M13b) + chain_b_tip_utxo = self.wallet.send_self_transfer_chain(from_node=node, chain_length=12, utxo_to_spend=m1_utxos[1]) + # Pb + pb_hex = self.wallet.create_self_transfer(utxo_to_spend=chain_b_tip_utxo)["hex"] + package_hex.append(pb_hex) assert_equal(24, node.getmempoolinfo()["size"]) assert_equal(2, len(package_hex)) @@ -200,41 +142,18 @@ class MempoolPackageLimitsTest(BitcoinTestFramework): node = self.nodes[0] package_hex = [] # M1 - first_coin_a = self.coins.pop() - parent_value = (first_coin_a["amount"] - DEFAULT_FEE) / 2 # Deduct reasonable fee and make 2 outputs - inputs = [{"txid": first_coin_a["txid"], "vout": 0}] - outputs = [{self.address : parent_value}, {ADDRESS_BCRT1_P2WSH_OP_TRUE : parent_value}] - rawtx = node.createrawtransaction(inputs, outputs) - - parent_signed = node.signrawtransactionwithkey(hexstring=rawtx, privkeys=self.privkeys) - assert parent_signed["complete"] - parent_tx = tx_from_hex(parent_signed["hex"]) - parent_txid = parent_tx.rehash() - node.sendrawtransaction(parent_signed["hex"]) + m1_utxos = self.wallet.send_self_transfer_multi(from_node=node, num_outputs=2)['new_utxos'] # Chain M2...M24 - spk = parent_tx.vout[0].scriptPubKey.hex() - value = parent_value - txid = parent_txid - for i in range(23): # M2...M24 - (tx, txhex, value, spk) = make_chain(node, self.address, self.privkeys, txid, value, 0, spk) - txid = tx.rehash() - node.sendrawtransaction(txhex) + self.wallet.send_self_transfer_chain(from_node=node, chain_length=23, utxo_to_spend=m1_utxos[0]) # P1 - value_p1 = (parent_value - DEFAULT_FEE) - rawtx_p1 = node.createrawtransaction([{"txid": parent_txid, "vout": 1}], [{self.address : value_p1}]) - tx_child_p1 = tx_from_hex(rawtx_p1) - tx_child_p1.wit.vtxinwit = [CTxInWitness()] - tx_child_p1.wit.vtxinwit[0].scriptWitness.stack = [CScript([OP_TRUE])] - tx_child_p1_hex = tx_child_p1.serialize().hex() - txid_child_p1 = tx_child_p1.rehash() - package_hex.append(tx_child_p1_hex) - tx_child_p1_spk = tx_child_p1.vout[0].scriptPubKey.hex() + p1_tx = self.wallet.create_self_transfer(utxo_to_spend=m1_utxos[1]) + package_hex.append(p1_tx["hex"]) # P2 - (_, tx_child_p2_hex, _, _) = make_chain(node, self.address, self.privkeys, txid_child_p1, value_p1, 0, tx_child_p1_spk) - package_hex.append(tx_child_p2_hex) + p2_tx = self.wallet.create_self_transfer(utxo_to_spend=p1_tx["new_utxo"]) + package_hex.append(p2_tx["hex"]) assert_equal(24, node.getmempoolinfo()["size"]) assert_equal(2, len(package_hex)) @@ -266,32 +185,21 @@ class MempoolPackageLimitsTest(BitcoinTestFramework): node = self.nodes[0] assert_equal(0, node.getmempoolinfo()["size"]) package_hex = [] - parents_tx = [] - values = [] - scripts = [] + pc_parent_utxos = [] self.log.info("Check that in-mempool and in-package ancestors are calculated properly in packages") # Two chains of 13 transactions each for _ in range(2): - spk = None - top_coin = self.coins.pop() - txid = top_coin["txid"] - value = top_coin["amount"] - for i in range(13): - (tx, txhex, value, spk) = make_chain(node, self.address, self.privkeys, txid, value, 0, spk) - txid = tx.rehash() - if i < 12: - node.sendrawtransaction(txhex) - else: # Save the 13th transaction for the package - package_hex.append(txhex) - parents_tx.append(tx) - scripts.append(spk) - values.append(value) + chain_tip_utxo = self.wallet.send_self_transfer_chain(from_node=node, chain_length=12) + # Save the 13th transaction for the package + tx = self.wallet.create_self_transfer(utxo_to_spend=chain_tip_utxo) + package_hex.append(tx["hex"]) + pc_parent_utxos.append(tx["new_utxo"]) # Child Pc - child_hex = create_child_with_parents(node, self.address, self.privkeys, parents_tx, values, scripts) - package_hex.append(child_hex) + pc_hex = self.wallet.create_self_transfer_multi(utxos_to_spend=pc_parent_utxos)["hex"] + package_hex.append(pc_hex) assert_equal(24, node.getmempoolinfo()["size"]) assert_equal(3, len(package_hex)) @@ -321,45 +229,29 @@ class MempoolPackageLimitsTest(BitcoinTestFramework): """ node = self.nodes[0] assert_equal(0, node.getmempoolinfo()["size"]) - parents_tx = [] - values = [] - scripts = [] + pc_parent_utxos = [] self.log.info("Check that in-mempool and in-package ancestors are calculated properly in packages") # Two chains of 12 transactions each for _ in range(2): - spk = None - top_coin = self.coins.pop() - txid = top_coin["txid"] - value = top_coin["amount"] - for i in range(12): - (tx, txhex, value, spk) = make_chain(node, self.address, self.privkeys, txid, value, 0, spk) - txid = tx.rehash() - value -= Decimal("0.0001") - node.sendrawtransaction(txhex) - if i == 11: - # last 2 transactions will be the parents of Pc - parents_tx.append(tx) - values.append(value) - scripts.append(spk) + chaintip_utxo = self.wallet.send_self_transfer_chain(from_node=node, chain_length=12) + # last 2 transactions will be the parents of Pc + pc_parent_utxos.append(chaintip_utxo) # Child Pc - pc_hex = create_child_with_parents(node, self.address, self.privkeys, parents_tx, values, scripts) - pc_tx = tx_from_hex(pc_hex) - pc_value = sum(values) - Decimal("0.0002") - pc_spk = pc_tx.vout[0].scriptPubKey.hex() + pc_tx = self.wallet.create_self_transfer_multi(utxos_to_spend=pc_parent_utxos) # Child Pd - (_, pd_hex, _, _) = make_chain(node, self.address, self.privkeys, pc_tx.rehash(), pc_value, 0, pc_spk) + pd_tx = self.wallet.create_self_transfer(utxo_to_spend=pc_tx["new_utxos"][0]) assert_equal(24, node.getmempoolinfo()["size"]) - testres_too_long = node.testmempoolaccept(rawtxs=[pc_hex, pd_hex]) + testres_too_long = node.testmempoolaccept(rawtxs=[pc_tx["hex"], pd_tx["hex"]]) for txres in testres_too_long: assert_equal(txres["package-error"], "package-mempool-limits") # Clear mempool and check that the package passes now self.generate(node, 1) - assert all([res["allowed"] for res in node.testmempoolaccept(rawtxs=[pc_hex, pd_hex])]) + assert all([res["allowed"] for res in node.testmempoolaccept(rawtxs=[pc_tx["hex"], pd_tx["hex"]])]) def test_anc_count_limits_bushy(self): """Create a tree with 20 transactions in the mempool and 6 in the package: @@ -375,31 +267,18 @@ class MempoolPackageLimitsTest(BitcoinTestFramework): node = self.nodes[0] assert_equal(0, node.getmempoolinfo()["size"]) package_hex = [] - parent_txns = [] - parent_values = [] - scripts = [] + pc_parent_utxos = [] for _ in range(5): # Make package transactions P0 ... P4 - gp_tx = [] - gp_values = [] - gp_scripts = [] + pc_grandparent_utxos = [] for _ in range(4): # Make mempool transactions M(4i+1)...M(4i+4) - parent_coin = self.coins.pop() - value = parent_coin["amount"] - txid = parent_coin["txid"] - (tx, txhex, value, spk) = make_chain(node, self.address, self.privkeys, txid, value) - gp_tx.append(tx) - gp_values.append(value) - gp_scripts.append(spk) - node.sendrawtransaction(txhex) + pc_grandparent_utxos.append(self.wallet.send_self_transfer(from_node=node)["new_utxo"]) # Package transaction Pi - pi_hex = create_child_with_parents(node, self.address, self.privkeys, gp_tx, gp_values, gp_scripts) - package_hex.append(pi_hex) - pi_tx = tx_from_hex(pi_hex) - parent_txns.append(pi_tx) - parent_values.append(Decimal(pi_tx.vout[0].nValue) / COIN) - scripts.append(pi_tx.vout[0].scriptPubKey.hex()) + pi_tx = self.wallet.create_self_transfer_multi(utxos_to_spend=pc_grandparent_utxos) + package_hex.append(pi_tx["hex"]) + pc_parent_utxos.append(pi_tx["new_utxos"][0]) # Package transaction PC - package_hex.append(create_child_with_parents(node, self.address, self.privkeys, parent_txns, parent_values, scripts)) + pc_hex = self.wallet.create_self_transfer_multi(utxos_to_spend=pc_parent_utxos)["hex"] + package_hex.append(pc_hex) assert_equal(20, node.getmempoolinfo()["size"]) assert_equal(6, len(package_hex)) @@ -424,51 +303,30 @@ class MempoolPackageLimitsTest(BitcoinTestFramework): """ node = self.nodes[0] assert_equal(0, node.getmempoolinfo()["size"]) - parents_tx = [] - values = [] - scripts = [] + parent_utxos = [] target_weight = WITNESS_SCALE_FACTOR * 1000 * 30 # 30KvB high_fee = Decimal("0.003") # 10 sats/vB self.log.info("Check that in-mempool and in-package ancestor size limits are calculated properly in packages") # Mempool transactions A and B for _ in range(2): - spk = None - top_coin = self.coins.pop() - txid = top_coin["txid"] - value = top_coin["amount"] - (tx, _, _, _) = make_chain(node, self.address, self.privkeys, txid, value, 0, spk, high_fee) - bulked_tx = bulk_transaction(tx, node, target_weight, self.privkeys) - node.sendrawtransaction(bulked_tx.serialize().hex()) - parents_tx.append(bulked_tx) - values.append(Decimal(bulked_tx.vout[0].nValue) / COIN) - scripts.append(bulked_tx.vout[0].scriptPubKey.hex()) + bulked_tx = self.wallet.create_self_transfer(target_weight=target_weight) + self.wallet.sendrawtransaction(from_node=node, tx_hex=bulked_tx["hex"]) + parent_utxos.append(bulked_tx["new_utxo"]) # Package transaction C - small_pc_hex = create_child_with_parents(node, self.address, self.privkeys, parents_tx, values, scripts, high_fee) - pc_tx = bulk_transaction(tx_from_hex(small_pc_hex), node, target_weight, self.privkeys) - pc_value = Decimal(pc_tx.vout[0].nValue) / COIN - pc_spk = pc_tx.vout[0].scriptPubKey.hex() - pc_hex = pc_tx.serialize().hex() + pc_tx = self.wallet.create_self_transfer_multi(utxos_to_spend=parent_utxos, fee_per_output=int(high_fee * COIN), target_weight=target_weight) # Package transaction D - (small_pd, _, val, spk) = make_chain(node, self.address, self.privkeys, pc_tx.rehash(), pc_value, 0, pc_spk, high_fee) - prevtxs = [{ - "txid": pc_tx.rehash(), - "vout": 0, - "scriptPubKey": spk, - "amount": val, - }] - pd_tx = bulk_transaction(small_pd, node, target_weight, self.privkeys, prevtxs) - pd_hex = pd_tx.serialize().hex() + pd_tx = self.wallet.create_self_transfer(utxo_to_spend=pc_tx["new_utxos"][0], target_weight=target_weight) assert_equal(2, node.getmempoolinfo()["size"]) - testres_too_heavy = node.testmempoolaccept(rawtxs=[pc_hex, pd_hex]) + testres_too_heavy = node.testmempoolaccept(rawtxs=[pc_tx["hex"], pd_tx["hex"]]) for txres in testres_too_heavy: assert_equal(txres["package-error"], "package-mempool-limits") # Clear mempool and check that the package passes now self.generate(node, 1) - assert all([res["allowed"] for res in node.testmempoolaccept(rawtxs=[pc_hex, pd_hex])]) + assert all([res["allowed"] for res in node.testmempoolaccept(rawtxs=[pc_tx["hex"], pd_tx["hex"]])]) def test_desc_size_limits(self): """Create 3 mempool transactions and 2 package transactions (25KvB each): @@ -486,50 +344,18 @@ class MempoolPackageLimitsTest(BitcoinTestFramework): high_fee = Decimal("0.0021") # 10 sats/vB self.log.info("Check that in-mempool and in-package descendant sizes are calculated properly in packages") # Top parent in mempool, Ma - first_coin = self.coins.pop() - parent_value = (first_coin["amount"] - high_fee) / 2 # Deduct fee and make 2 outputs - inputs = [{"txid": first_coin["txid"], "vout": 0}] - outputs = [{self.address : parent_value}, {ADDRESS_BCRT1_P2WSH_OP_TRUE: parent_value}] - rawtx = node.createrawtransaction(inputs, outputs) - parent_tx = bulk_transaction(tx_from_hex(rawtx), node, target_weight, self.privkeys) - node.sendrawtransaction(parent_tx.serialize().hex()) + ma_tx = self.wallet.create_self_transfer_multi(num_outputs=2, fee_per_output=int(high_fee / 2 * COIN), target_weight=target_weight) + self.wallet.sendrawtransaction(from_node=node, tx_hex=ma_tx["hex"]) package_hex = [] for j in range(2): # Two legs (left and right) # Mempool transaction (Mb and Mc) - mempool_tx = CTransaction() - spk = parent_tx.vout[j].scriptPubKey.hex() - value = Decimal(parent_tx.vout[j].nValue) / COIN - txid = parent_tx.rehash() - prevtxs = [{ - "txid": txid, - "vout": j, - "scriptPubKey": spk, - "amount": value, - }] - if j == 0: # normal key - (tx_small, _, _, _) = make_chain(node, self.address, self.privkeys, txid, value, j, spk, high_fee) - mempool_tx = bulk_transaction(tx_small, node, target_weight, self.privkeys, prevtxs) - else: # OP_TRUE - inputs = [{"txid": txid, "vout": 1}] - outputs = {self.address: value - high_fee} - small_tx = tx_from_hex(node.createrawtransaction(inputs, outputs)) - mempool_tx = bulk_transaction(small_tx, node, target_weight, None, prevtxs) - node.sendrawtransaction(mempool_tx.serialize().hex()) + mempool_tx = self.wallet.create_self_transfer(utxo_to_spend=ma_tx["new_utxos"][j], target_weight=target_weight) + self.wallet.sendrawtransaction(from_node=node, tx_hex=mempool_tx["hex"]) # Package transaction (Pd and Pe) - spk = mempool_tx.vout[0].scriptPubKey.hex() - value = Decimal(mempool_tx.vout[0].nValue) / COIN - txid = mempool_tx.rehash() - (tx_small, _, _, _) = make_chain(node, self.address, self.privkeys, txid, value, 0, spk, high_fee) - prevtxs = [{ - "txid": txid, - "vout": 0, - "scriptPubKey": spk, - "amount": value, - }] - package_tx = bulk_transaction(tx_small, node, target_weight, self.privkeys, prevtxs) - package_hex.append(package_tx.serialize().hex()) + package_tx = self.wallet.create_self_transfer(utxo_to_spend=mempool_tx["new_utxo"], target_weight=target_weight) + package_hex.append(package_tx["hex"]) assert_equal(3, node.getmempoolinfo()["size"]) assert_equal(2, len(package_hex)) diff --git a/test/functional/mempool_package_onemore.py b/test/functional/mempool_package_onemore.py index 423a5bf2ee..9a981bd5a5 100755 --- a/test/functional/mempool_package_onemore.py +++ b/test/functional/mempool_package_onemore.py @@ -7,6 +7,9 @@ size. """ +from test_framework.messages import ( + DEFAULT_ANCESTOR_LIMIT, +) from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_equal, @@ -15,10 +18,6 @@ from test_framework.util import ( from test_framework.wallet import MiniWallet -MAX_ANCESTORS = 25 -MAX_DESCENDANTS = 25 - - class MempoolPackagesTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 1 @@ -34,19 +33,19 @@ class MempoolPackagesTest(BitcoinTestFramework): self.wallet = MiniWallet(self.nodes[0]) self.wallet.rescan_utxos() - # MAX_ANCESTORS transactions off a confirmed tx should be fine + # DEFAULT_ANCESTOR_LIMIT transactions off a confirmed tx should be fine chain = [] utxo = self.wallet.get_utxo() for _ in range(4): utxo, utxo2 = self.chain_tx([utxo], num_outputs=2) chain.append(utxo2) - for _ in range(MAX_ANCESTORS - 4): + for _ in range(DEFAULT_ANCESTOR_LIMIT - 4): utxo, = self.chain_tx([utxo]) chain.append(utxo) second_chain, = self.chain_tx([self.wallet.get_utxo()]) - # Check mempool has MAX_ANCESTORS + 1 transactions in it - assert_equal(len(self.nodes[0].getrawmempool()), MAX_ANCESTORS + 1) + # Check mempool has DEFAULT_ANCESTOR_LIMIT + 1 transactions in it + assert_equal(len(self.nodes[0].getrawmempool()), DEFAULT_ANCESTOR_LIMIT + 1) # Adding one more transaction on to the chain should fail. assert_raises_rpc_error(-26, "too-long-mempool-chain, too many unconfirmed ancestors [limit: 25]", self.chain_tx, [utxo]) @@ -67,7 +66,7 @@ class MempoolPackagesTest(BitcoinTestFramework): self.nodes[0].sendrawtransaction(replacable_tx.serialize().hex()) # Finally, check that we added two transactions - assert_equal(len(self.nodes[0].getrawmempool()), MAX_ANCESTORS + 3) + assert_equal(len(self.nodes[0].getrawmempool()), DEFAULT_ANCESTOR_LIMIT + 3) if __name__ == '__main__': diff --git a/test/functional/mempool_packages.py b/test/functional/mempool_packages.py index a2a2caf324..def0b1fce4 100755 --- a/test/functional/mempool_packages.py +++ b/test/functional/mempool_packages.py @@ -7,7 +7,11 @@ from decimal import Decimal from test_framework.blocktools import COINBASE_MATURITY -from test_framework.messages import COIN +from test_framework.messages import ( + COIN, + DEFAULT_ANCESTOR_LIMIT, + DEFAULT_DESCENDANT_LIMIT, +) from test_framework.p2p import P2PTxInvStore from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( @@ -16,13 +20,12 @@ from test_framework.util import ( chain_transaction, ) -# default limits -MAX_ANCESTORS = 25 -MAX_DESCENDANTS = 25 + # custom limits for node1 -MAX_ANCESTORS_CUSTOM = 5 -MAX_DESCENDANTS_CUSTOM = 10 -assert MAX_DESCENDANTS_CUSTOM >= MAX_ANCESTORS_CUSTOM +CUSTOM_ANCESTOR_LIMIT = 5 +CUSTOM_DESCENDANT_LIMIT = 10 +assert CUSTOM_DESCENDANT_LIMIT >= CUSTOM_ANCESTOR_LIMIT + class MempoolPackagesTest(BitcoinTestFramework): def set_test_params(self): @@ -34,8 +37,8 @@ class MempoolPackagesTest(BitcoinTestFramework): ], [ "-maxorphantx=1000", - "-limitancestorcount={}".format(MAX_ANCESTORS_CUSTOM), - "-limitdescendantcount={}".format(MAX_DESCENDANTS_CUSTOM), + "-limitancestorcount={}".format(CUSTOM_ANCESTOR_LIMIT), + "-limitdescendantcount={}".format(CUSTOM_DESCENDANT_LIMIT), ], ] @@ -55,12 +58,12 @@ class MempoolPackagesTest(BitcoinTestFramework): assert 'ancestorfees' not in utxo[0] fee = Decimal("0.0001") - # MAX_ANCESTORS transactions off a confirmed tx should be fine + # DEFAULT_ANCESTOR_LIMIT transactions off a confirmed tx should be fine chain = [] witness_chain = [] ancestor_vsize = 0 ancestor_fees = Decimal(0) - for i in range(MAX_ANCESTORS): + for i in range(DEFAULT_ANCESTOR_LIMIT): (txid, sent_value) = chain_transaction(self.nodes[0], [txid], [0], value, fee, 1) value = sent_value chain.append(txid) @@ -81,16 +84,16 @@ class MempoolPackagesTest(BitcoinTestFramework): # Otherwise, getrawmempool may be inconsistent with getmempoolentry if unbroadcast changes in between peer_inv_store.wait_for_broadcast(witness_chain) - # Check mempool has MAX_ANCESTORS transactions in it, and descendant and ancestor + # Check mempool has DEFAULT_ANCESTOR_LIMIT transactions in it, and descendant and ancestor # count and fees should look correct mempool = self.nodes[0].getrawmempool(True) - assert_equal(len(mempool), MAX_ANCESTORS) + assert_equal(len(mempool), DEFAULT_ANCESTOR_LIMIT) descendant_count = 1 descendant_fees = 0 descendant_vsize = 0 assert_equal(ancestor_vsize, sum([mempool[tx]['vsize'] for tx in mempool])) - ancestor_count = MAX_ANCESTORS + ancestor_count = DEFAULT_ANCESTOR_LIMIT assert_equal(ancestor_fees, sum([mempool[tx]['fees']['base'] for tx in mempool])) descendants = [] @@ -213,9 +216,9 @@ class MempoolPackagesTest(BitcoinTestFramework): # Check that node1's mempool is as expected (-> custom ancestor limit) mempool0 = self.nodes[0].getrawmempool(False) mempool1 = self.nodes[1].getrawmempool(False) - assert_equal(len(mempool1), MAX_ANCESTORS_CUSTOM) + assert_equal(len(mempool1), CUSTOM_ANCESTOR_LIMIT) assert set(mempool1).issubset(set(mempool0)) - for tx in chain[:MAX_ANCESTORS_CUSTOM]: + for tx in chain[:CUSTOM_ANCESTOR_LIMIT]: assert tx in mempool1 # TODO: more detailed check of node1's mempool (fees etc.) # check transaction unbroadcast info (should be false if in both mempools) @@ -240,7 +243,7 @@ class MempoolPackagesTest(BitcoinTestFramework): # Sign and send up to MAX_DESCENDANT transactions chained off the parent tx chain = [] # save sent txs for the purpose of checking node1's mempool later (see below) - for _ in range(MAX_DESCENDANTS - 1): + for _ in range(DEFAULT_DESCENDANT_LIMIT - 1): utxo = transaction_package.pop(0) (txid, sent_value) = chain_transaction(self.nodes[0], [utxo['txid']], [utxo['vout']], utxo['amount'], fee, 10) chain.append(txid) @@ -250,7 +253,7 @@ class MempoolPackagesTest(BitcoinTestFramework): transaction_package.append({'txid': txid, 'vout': j, 'amount': sent_value}) mempool = self.nodes[0].getrawmempool(True) - assert_equal(mempool[parent_transaction]['descendantcount'], MAX_DESCENDANTS) + assert_equal(mempool[parent_transaction]['descendantcount'], DEFAULT_DESCENDANT_LIMIT) assert_equal(sorted(mempool[parent_transaction]['spentby']), sorted(tx_children)) for child in tx_children: @@ -265,14 +268,14 @@ class MempoolPackagesTest(BitcoinTestFramework): # - parent tx for descendant test # - txs chained off parent tx (-> custom descendant limit) self.wait_until(lambda: len(self.nodes[1].getrawmempool()) == - MAX_ANCESTORS_CUSTOM + 1 + MAX_DESCENDANTS_CUSTOM, timeout=10) + CUSTOM_ANCESTOR_LIMIT + 1 + CUSTOM_DESCENDANT_LIMIT, timeout=10) mempool0 = self.nodes[0].getrawmempool(False) mempool1 = self.nodes[1].getrawmempool(False) assert set(mempool1).issubset(set(mempool0)) assert parent_transaction in mempool1 - for tx in chain[:MAX_DESCENDANTS_CUSTOM]: + for tx in chain[:CUSTOM_DESCENDANT_LIMIT]: assert tx in mempool1 - for tx in chain[MAX_DESCENDANTS_CUSTOM:]: + for tx in chain[CUSTOM_DESCENDANT_LIMIT:]: assert tx not in mempool1 # TODO: more detailed check of node1's mempool (fees etc.) diff --git a/test/functional/mempool_persist.py b/test/functional/mempool_persist.py index 8c9379b90b..b6fa7fbd91 100755 --- a/test/functional/mempool_persist.py +++ b/test/functional/mempool_persist.py @@ -105,6 +105,11 @@ class MempoolPersistTest(BitcoinTestFramework): assert_equal(len(self.nodes[0].p2ps), 0) self.mini_wallet.send_self_transfer(from_node=self.nodes[0]) + # Test persistence of prioritisation for transactions not in the mempool. + # Create a tx and prioritise but don't submit until after the restart. + tx_prioritised_not_submitted = self.mini_wallet.create_self_transfer() + self.nodes[0].prioritisetransaction(txid=tx_prioritised_not_submitted['txid'], fee_delta=9999) + self.log.debug("Stop-start the nodes. Verify that node0 has the transactions in its mempool and node1 does not. Verify that node2 calculates its balance correctly after loading wallet transactions.") self.stop_nodes() # Give this node a head-start, so we can be "extra-sure" that it didn't load anything later @@ -125,6 +130,9 @@ class MempoolPersistTest(BitcoinTestFramework): self.log.debug('Verify all fields are loaded correctly') assert_equal(last_entry, self.nodes[0].getmempoolentry(txid=last_txid)) + self.nodes[0].sendrawtransaction(tx_prioritised_not_submitted['hex']) + entry_prioritised_before_restart = self.nodes[0].getmempoolentry(txid=tx_prioritised_not_submitted['txid']) + assert_equal(entry_prioritised_before_restart['fees']['base'] + Decimal('0.00009999'), entry_prioritised_before_restart['fees']['modified']) # Verify accounting of mempool transactions after restart is correct if self.is_sqlite_compiled(): @@ -133,6 +141,16 @@ class MempoolPersistTest(BitcoinTestFramework): self.nodes[2].syncwithvalidationinterfacequeue() # Flush mempool to wallet assert_equal(node2_balance, wallet_watch.getbalance()) + mempooldat0 = os.path.join(self.nodes[0].datadir, self.chain, 'mempool.dat') + mempooldat1 = os.path.join(self.nodes[1].datadir, self.chain, 'mempool.dat') + + self.log.debug("Force -persistmempool=0 node1 to savemempool to disk via RPC") + assert not os.path.exists(mempooldat1) + result1 = self.nodes[1].savemempool() + assert os.path.isfile(mempooldat1) + assert_equal(result1['filename'], mempooldat1) + os.remove(mempooldat1) + self.log.debug("Stop-start node0 with -persistmempool=0. Verify that it doesn't load its mempool.dat file.") self.stop_nodes() self.start_node(0, extra_args=["-persistmempool=0"]) @@ -143,22 +161,20 @@ class MempoolPersistTest(BitcoinTestFramework): self.stop_nodes() self.start_node(0) assert self.nodes[0].getmempoolinfo()["loaded"] - assert_equal(len(self.nodes[0].getrawmempool()), 6) + assert_equal(len(self.nodes[0].getrawmempool()), 7) - mempooldat0 = os.path.join(self.nodes[0].datadir, self.chain, 'mempool.dat') - mempooldat1 = os.path.join(self.nodes[1].datadir, self.chain, 'mempool.dat') self.log.debug("Remove the mempool.dat file. Verify that savemempool to disk via RPC re-creates it") os.remove(mempooldat0) result0 = self.nodes[0].savemempool() assert os.path.isfile(mempooldat0) assert_equal(result0['filename'], mempooldat0) - self.log.debug("Stop nodes, make node1 use mempool.dat from node0. Verify it has 6 transactions") + self.log.debug("Stop nodes, make node1 use mempool.dat from node0. Verify it has 7 transactions") os.rename(mempooldat0, mempooldat1) self.stop_nodes() self.start_node(1, extra_args=["-persistmempool"]) assert self.nodes[1].getmempoolinfo()["loaded"] - assert_equal(len(self.nodes[1].getrawmempool()), 6) + assert_equal(len(self.nodes[1].getrawmempool()), 7) self.log.debug("Prevent bitcoind from writing mempool.dat to disk. Verify that `savemempool` fails") # to test the exception we are creating a tmp folder called mempool.dat.new diff --git a/test/functional/mempool_reorg.py b/test/functional/mempool_reorg.py index 91f2d0051a..47ff520713 100755 --- a/test/functional/mempool_reorg.py +++ b/test/functional/mempool_reorg.py @@ -53,8 +53,7 @@ class MempoolCoinbaseTest(BitcoinTestFramework): utxo = wallet.get_utxo(txid=coinbase_txids[0]) timelock_tx = wallet.create_self_transfer( utxo_to_spend=utxo, - mempool_valid=False, - locktime=self.nodes[0].getblockcount() + 2 + locktime=self.nodes[0].getblockcount() + 2, )['hex'] self.log.info("Check that the time-locked transaction is too immature to spend") @@ -69,10 +68,8 @@ class MempoolCoinbaseTest(BitcoinTestFramework): assert_raises_rpc_error(-26, 'non-final', self.nodes[0].sendrawtransaction, timelock_tx) self.log.info("Create spend_2_1 and spend_3_1") - spend_2_utxo = wallet.get_utxo(txid=spend_2['txid']) - spend_2_1 = wallet.create_self_transfer(utxo_to_spend=spend_2_utxo) - spend_3_utxo = wallet.get_utxo(txid=spend_3['txid']) - spend_3_1 = wallet.create_self_transfer(utxo_to_spend=spend_3_utxo) + spend_2_1 = wallet.create_self_transfer(utxo_to_spend=spend_2["new_utxo"]) + spend_3_1 = wallet.create_self_transfer(utxo_to_spend=spend_3["new_utxo"]) self.log.info("Broadcast and mine spend_3_1") spend_3_1_id = self.nodes[0].sendrawtransaction(spend_3_1['hex']) diff --git a/test/functional/mempool_spend_coinbase.py b/test/functional/mempool_spend_coinbase.py index 9c43ddaf6f..3585871350 100755 --- a/test/functional/mempool_spend_coinbase.py +++ b/test/functional/mempool_spend_coinbase.py @@ -40,7 +40,7 @@ class MempoolSpendCoinbaseTest(BitcoinTestFramework): spend_mature_id = wallet.send_self_transfer(from_node=self.nodes[0], utxo_to_spend=utxo_mature)["txid"] # other coinbase should be too immature to spend - immature_tx = wallet.create_self_transfer(utxo_to_spend=utxo_immature, mempool_valid=False) + immature_tx = wallet.create_self_transfer(utxo_to_spend=utxo_immature) assert_raises_rpc_error(-26, "bad-txns-premature-spend-of-coinbase", lambda: self.nodes[0].sendrawtransaction(immature_tx['hex'])) diff --git a/test/functional/mempool_unbroadcast.py b/test/functional/mempool_unbroadcast.py index 37ef4a9157..7587adc257 100755 --- a/test/functional/mempool_unbroadcast.py +++ b/test/functional/mempool_unbroadcast.py @@ -40,7 +40,7 @@ class MempoolUnbroadcastTest(BitcoinTestFramework): wallet_tx_hsh = node.sendtoaddress(addr, 0.0001) # generate a txn using sendrawtransaction - txFS = self.wallet.create_self_transfer(from_node=node) + txFS = self.wallet.create_self_transfer() rpc_tx_hsh = node.sendrawtransaction(txFS["hex"]) # check transactions are in unbroadcast using rpc diff --git a/test/functional/mempool_updatefromblock.py b/test/functional/mempool_updatefromblock.py index 51de582ce0..f97c2223a6 100755 --- a/test/functional/mempool_updatefromblock.py +++ b/test/functional/mempool_updatefromblock.py @@ -12,6 +12,9 @@ import time from decimal import Decimal from test_framework.test_framework import BitcoinTestFramework from test_framework.util import assert_equal +from test_framework.address import key_to_p2pkh +from test_framework.wallet_util import bytes_to_wif +from test_framework.key import ECKey class MempoolUpdateFromBlockTest(BitcoinTestFramework): @@ -19,8 +22,13 @@ class MempoolUpdateFromBlockTest(BitcoinTestFramework): self.num_nodes = 1 self.extra_args = [['-limitdescendantsize=1000', '-limitancestorsize=1000', '-limitancestorcount=100']] - def skip_test_if_missing_module(self): - self.skip_if_no_wallet() + def get_new_address(self): + key = ECKey() + key.generate() + pubkey = key.get_pubkey().get_bytes() + address = key_to_p2pkh(pubkey) + self.priv_keys.append(bytes_to_wif(key.get_bytes())) + return address def transaction_graph_test(self, size, n_tx_to_mine=None, start_input_txid='', end_address='', fee=Decimal(0.00100000)): """Create an acyclic tournament (a type of directed graph) of transactions and use it for testing. @@ -38,11 +46,12 @@ class MempoolUpdateFromBlockTest(BitcoinTestFramework): More details: https://en.wikipedia.org/wiki/Tournament_(graph_theory) """ + self.priv_keys = [self.nodes[0].get_deterministic_priv_key().key] if not start_input_txid: start_input_txid = self.nodes[0].getblock(self.nodes[0].getblockhash(1))['tx'][0] if not end_address: - end_address = self.nodes[0].getnewaddress() + end_address = self.get_new_address() first_block_hash = '' tx_id = [] @@ -74,7 +83,7 @@ class MempoolUpdateFromBlockTest(BitcoinTestFramework): output_value = ((inputs_value - fee) / Decimal(n_outputs)).quantize(Decimal('0.00000001')) outputs = {} for _ in range(n_outputs): - outputs[self.nodes[0].getnewaddress()] = output_value + outputs[self.get_new_address()] = output_value else: output_value = (inputs_value - fee).quantize(Decimal('0.00000001')) outputs = {end_address: output_value} @@ -84,7 +93,7 @@ class MempoolUpdateFromBlockTest(BitcoinTestFramework): # Create a new transaction. unsigned_raw_tx = self.nodes[0].createrawtransaction(inputs, outputs) - signed_raw_tx = self.nodes[0].signrawtransactionwithwallet(unsigned_raw_tx) + signed_raw_tx = self.nodes[0].signrawtransactionwithkey(unsigned_raw_tx, self.priv_keys) tx_id.append(self.nodes[0].sendrawtransaction(signed_raw_tx['hex'])) tx_size.append(self.nodes[0].getmempoolentry(tx_id[-1])['vsize']) diff --git a/test/functional/mining_prioritisetransaction.py b/test/functional/mining_prioritisetransaction.py index a15fbe5a24..581cf5896e 100755 --- a/test/functional/mining_prioritisetransaction.py +++ b/test/functional/mining_prioritisetransaction.py @@ -7,29 +7,31 @@ from decimal import Decimal import time -from test_framework.blocktools import COINBASE_MATURITY -from test_framework.messages import COIN, MAX_BLOCK_WEIGHT +from test_framework.messages import ( + COIN, + MAX_BLOCK_WEIGHT, +) from test_framework.test_framework import BitcoinTestFramework -from test_framework.util import assert_equal, assert_raises_rpc_error, create_confirmed_utxos, create_lots_of_big_transactions, gen_return_txouts +from test_framework.util import ( + assert_equal, + assert_raises_rpc_error, + create_lots_of_big_transactions, + gen_return_txouts, +) from test_framework.wallet import MiniWallet class PrioritiseTransactionTest(BitcoinTestFramework): def set_test_params(self): - self.setup_clean_chain = True self.num_nodes = 1 self.extra_args = [[ "-printpriority=1", - "-acceptnonstdtxn=1", + "-datacarriersize=100000", ]] * self.num_nodes self.supports_cli = False - def skip_test_if_missing_module(self): - self.skip_if_no_wallet() - def test_diamond(self): self.log.info("Test diamond-shape package with priority") - self.generate(self.wallet, COINBASE_MATURITY + 1) mock_time = int(time.time()) self.nodes[0].setmocktime(mock_time) @@ -104,6 +106,7 @@ class PrioritiseTransactionTest(BitcoinTestFramework): def run_test(self): self.wallet = MiniWallet(self.nodes[0]) + self.wallet.rescan_utxos() # Test `prioritisetransaction` required parameters assert_raises_rpc_error(-1, "prioritisetransaction", self.nodes[0].prioritisetransaction) @@ -119,11 +122,11 @@ class PrioritiseTransactionTest(BitcoinTestFramework): # Test `prioritisetransaction` invalid `dummy` txid = '1d1d4e24ed99057e84c3f80fd8fbec79ed9e1acee37da269356ecea000000000' - assert_raises_rpc_error(-1, "JSON value is not a number as expected", self.nodes[0].prioritisetransaction, txid, 'foo', 0) + assert_raises_rpc_error(-3, "JSON value of type string is not of expected type number", self.nodes[0].prioritisetransaction, txid, 'foo', 0) assert_raises_rpc_error(-8, "Priority is no longer supported, dummy argument to prioritisetransaction must be 0.", self.nodes[0].prioritisetransaction, txid, 1, 0) # Test `prioritisetransaction` invalid `fee_delta` - assert_raises_rpc_error(-1, "JSON value is not an integer as expected", self.nodes[0].prioritisetransaction, txid=txid, fee_delta='foo') + assert_raises_rpc_error(-3, "JSON value of type string is not of expected type number", self.nodes[0].prioritisetransaction, txid=txid, fee_delta='foo') self.test_diamond() @@ -131,7 +134,10 @@ class PrioritiseTransactionTest(BitcoinTestFramework): self.relayfee = self.nodes[0].getnetworkinfo()['relayfee'] utxo_count = 90 - utxos = create_confirmed_utxos(self, self.relayfee, self.nodes[0], utxo_count) + utxos = self.wallet.send_self_transfer_multi(from_node=self.nodes[0], num_outputs=utxo_count)['new_utxos'] + self.generate(self.wallet, 1) + assert_equal(len(self.nodes[0].getrawmempool()), 0) + base_fee = self.relayfee*100 # our transactions are smaller than 100kb txids = [] @@ -141,7 +147,13 @@ class PrioritiseTransactionTest(BitcoinTestFramework): txids.append([]) start_range = i * range_size end_range = start_range + range_size - txids[i] = create_lots_of_big_transactions(self.nodes[0], self.txouts, utxos[start_range:end_range], end_range - start_range, (i+1)*base_fee) + txids[i] = create_lots_of_big_transactions( + self.wallet, + self.nodes[0], + (i+1) * base_fee, + end_range - start_range, + self.txouts, + utxos[start_range:end_range]) # Make sure that the size of each group of transactions exceeds # MAX_BLOCK_WEIGHT // 4 -- otherwise the test needs to be revised to @@ -200,17 +212,9 @@ class PrioritiseTransactionTest(BitcoinTestFramework): assert x not in mempool # Create a free transaction. Should be rejected. - utxo_list = self.nodes[0].listunspent() - assert len(utxo_list) > 0 - utxo = utxo_list[0] - - inputs = [] - outputs = {} - inputs.append({"txid" : utxo["txid"], "vout" : utxo["vout"]}) - outputs[self.nodes[0].getnewaddress()] = utxo["amount"] - raw_tx = self.nodes[0].createrawtransaction(inputs, outputs) - tx_hex = self.nodes[0].signrawtransactionwithwallet(raw_tx)["hex"] - tx_id = self.nodes[0].decoderawtransaction(tx_hex)["txid"] + tx_res = self.wallet.create_self_transfer(fee_rate=0) + tx_hex = tx_res['hex'] + tx_id = tx_res['txid'] # This will raise an exception due to min relay fee not being met assert_raises_rpc_error(-26, "min relay fee not met", self.nodes[0].sendrawtransaction, tx_hex) diff --git a/test/functional/mocks/invalid_signer.py b/test/functional/mocks/invalid_signer.py index e30cc9e20b..14f9fed72e 100755 --- a/test/functional/mocks/invalid_signer.py +++ b/test/functional/mocks/invalid_signer.py @@ -18,7 +18,7 @@ def perform_pre_checks(): sys.exit(int(mock_result[0])) def enumerate(args): - sys.stdout.write(json.dumps([{"fingerprint": "b3c19bfc", "type": "trezor", "model": "trezor_t"}, {"fingerprint": "00000002"}])) + sys.stdout.write(json.dumps([{"fingerprint": "b3c19bfc", "type": "trezor", "model": "trezor_t"}])) def getdescriptors(args): xpub_pkh = "xpub6CRhJvXV8x2AKWvqi1ZSMFU6cbxzQiYrv3dxSUXCawjMJ1JzpqVsveH4way1yCmJm29KzH1zrVZmVwes4Qo6oXVE1HFn4fdiKrYJngqFFc6" diff --git a/test/functional/mocks/multi_signers.py b/test/functional/mocks/multi_signers.py new file mode 100755 index 0000000000..88f93e23de --- /dev/null +++ b/test/functional/mocks/multi_signers.py @@ -0,0 +1,30 @@ +#!/usr/bin/env python3 +# Copyright (c) 2022 The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. + +import argparse +import json +import sys + +def enumerate(args): + sys.stdout.write(json.dumps([{"fingerprint": "00000001", "type": "trezor", "model": "trezor_t"}, + {"fingerprint": "00000002", "type": "trezor", "model": "trezor_one"}])) + +parser = argparse.ArgumentParser(prog='./multi_signers.py', description='External multi-signer mock') + +subparsers = parser.add_subparsers(description='Commands', dest='command') +subparsers.required = True + +parser_enumerate = subparsers.add_parser('enumerate', help='list available signers') +parser_enumerate.set_defaults(func=enumerate) + + +if not sys.stdin.isatty(): + buffer = sys.stdin.read() + if buffer and buffer.rstrip() != "": + sys.argv.extend(buffer.rstrip().split(" ")) + +args = parser.parse_args() + +args.func(args) diff --git a/test/functional/mocks/signer.py b/test/functional/mocks/signer.py index b732b26a53..c5a8f7b1e9 100755 --- a/test/functional/mocks/signer.py +++ b/test/functional/mocks/signer.py @@ -18,7 +18,7 @@ def perform_pre_checks(): sys.exit(int(mock_result[0])) def enumerate(args): - sys.stdout.write(json.dumps([{"fingerprint": "00000001", "type": "trezor", "model": "trezor_t"}, {"fingerprint": "00000002"}])) + sys.stdout.write(json.dumps([{"fingerprint": "00000001", "type": "trezor", "model": "trezor_t"}])) def getdescriptors(args): xpub = "tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B" diff --git a/test/functional/p2p_blocksonly.py b/test/functional/p2p_blocksonly.py index 12ee4b3c27..8ac38bff3a 100755 --- a/test/functional/p2p_blocksonly.py +++ b/test/functional/p2p_blocksonly.py @@ -89,6 +89,11 @@ class P2PBlocksOnly(BitcoinTestFramework): assert_equal(self.nodes[0].getpeerinfo()[0]['relaytxes'], False) _, txid, _, tx_hex = self.check_p2p_tx_violation() + self.log.info("Tests with node in normal mode with block-relay-only connection, sending an inv") + conn = self.nodes[0].add_outbound_p2p_connection(P2PInterface(), p2p_idx=0, connection_type="block-relay-only") + assert_equal(self.nodes[0].getpeerinfo()[0]['relaytxes'], False) + self.check_p2p_inv_violation(conn) + self.log.info("Check that txs from RPC are not sent to blockrelay connection") conn = self.nodes[0].add_outbound_p2p_connection(P2PTxInvStore(), p2p_idx=1, connection_type="block-relay-only") @@ -100,6 +105,13 @@ class P2PBlocksOnly(BitcoinTestFramework): conn.sync_send_with_ping() assert(int(txid, 16) not in conn.get_invs()) + def check_p2p_inv_violation(self, peer): + self.log.info("Check that tx-invs from P2P are rejected and result in disconnect") + with self.nodes[0].assert_debug_log(["inv sent in violation of protocol, disconnecting peer"]): + peer.send_message(msg_inv([CInv(t=MSG_WTX, h=0x12345)])) + peer.wait_for_disconnect() + self.nodes[0].disconnect_p2ps() + def check_p2p_tx_violation(self): self.log.info('Check that txs from P2P are rejected and result in disconnect') spendtx = self.miniwallet.create_self_transfer() @@ -108,9 +120,7 @@ class P2PBlocksOnly(BitcoinTestFramework): self.nodes[0].p2ps[0].send_message(msg_tx(spendtx['tx'])) self.nodes[0].p2ps[0].wait_for_disconnect() assert_equal(self.nodes[0].getmempoolinfo()['size'], 0) - - # Remove the disconnected peer - del self.nodes[0].p2ps[0] + self.nodes[0].disconnect_p2ps() return spendtx['tx'], spendtx['txid'], spendtx['wtxid'], spendtx['hex'] diff --git a/test/functional/p2p_compactblocks.py b/test/functional/p2p_compactblocks.py index b9ac3c32c5..3cbb948e3c 100755 --- a/test/functional/p2p_compactblocks.py +++ b/test/functional/p2p_compactblocks.py @@ -606,6 +606,36 @@ class CompactBlocksTest(BitcoinTestFramework): assert_equal(test_node.last_message["block"].block.sha256, int(block_hash, 16)) assert "blocktxn" not in test_node.last_message + # Request with out-of-bounds tx index results in disconnect + bad_peer = self.nodes[0].add_p2p_connection(TestP2PConn()) + block_hash = node.getblockhash(chain_height) + block = from_hex(CBlock(), node.getblock(block_hash, False)) + msg.block_txn_request = BlockTransactionsRequest(int(block_hash, 16), [len(block.vtx)]) + with node.assert_debug_log(['getblocktxn with out-of-bounds tx indices']): + bad_peer.send_message(msg) + bad_peer.wait_for_disconnect() + + def test_low_work_compactblocks(self, test_node): + # A compactblock with insufficient work won't get its header included + node = self.nodes[0] + hashPrevBlock = int(node.getblockhash(node.getblockcount() - 150), 16) + block = self.build_block_on_tip(node) + block.hashPrevBlock = hashPrevBlock + block.solve() + + comp_block = HeaderAndShortIDs() + comp_block.initialize_from_block(block) + with self.nodes[0].assert_debug_log(['[net] Ignoring low-work compact block from peer 0']): + test_node.send_and_ping(msg_cmpctblock(comp_block.to_p2p())) + + tips = node.getchaintips() + found = False + for x in tips: + if x["hash"] == block.hash: + found = True + break + assert not found + def test_compactblocks_not_at_tip(self, test_node): node = self.nodes[0] # Test that requesting old compactblocks doesn't work. @@ -824,6 +854,9 @@ class CompactBlocksTest(BitcoinTestFramework): self.log.info("Testing compactblock requests/announcements not at chain tip...") self.test_compactblocks_not_at_tip(self.segwit_node) + self.log.info("Testing handling of low-work compact blocks...") + self.test_low_work_compactblocks(self.segwit_node) + self.log.info("Testing handling of incorrect blocktxn responses...") self.test_incorrect_blocktxn_response(self.segwit_node) diff --git a/test/functional/p2p_dos_header_tree.py b/test/functional/p2p_dos_header_tree.py index fde1e4bfa2..7e26994511 100755 --- a/test/functional/p2p_dos_header_tree.py +++ b/test/functional/p2p_dos_header_tree.py @@ -22,6 +22,7 @@ class RejectLowDifficultyHeadersTest(BitcoinTestFramework): self.setup_clean_chain = True self.chain = 'testnet3' # Use testnet chain because it has an early checkpoint self.num_nodes = 2 + self.extra_args = [["-minimumchainwork=0x0"], ["-minimumchainwork=0x0"]] def add_options(self, parser): parser.add_argument( @@ -62,7 +63,7 @@ class RejectLowDifficultyHeadersTest(BitcoinTestFramework): self.log.info("Feed all fork headers (succeeds without checkpoint)") # On node 0 it succeeds because checkpoints are disabled - self.restart_node(0, extra_args=['-nocheckpoints']) + self.restart_node(0, extra_args=['-nocheckpoints', "-minimumchainwork=0x0"]) peer_no_checkpoint = self.nodes[0].add_p2p_connection(P2PInterface()) peer_no_checkpoint.send_and_ping(msg_headers(self.headers_fork)) assert { diff --git a/test/functional/p2p_getaddr_caching.py b/test/functional/p2p_getaddr_caching.py index c934a97729..8907c34a89 100755 --- a/test/functional/p2p_getaddr_caching.py +++ b/test/functional/p2p_getaddr_caching.py @@ -14,8 +14,7 @@ from test_framework.p2p import ( from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_equal, - PORT_MIN, - PORT_RANGE, + p2p_port, ) # As defined in net_processing. @@ -44,10 +43,9 @@ class AddrReceiver(P2PInterface): class AddrTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 1 - # Start onion ports after p2p and rpc ports. - port = PORT_MIN + 2 * PORT_RANGE - self.onion_port1 = port - self.onion_port2 = port + 1 + # Use some of the remaining p2p ports for the onion binds. + self.onion_port1 = p2p_port(self.num_nodes) + self.onion_port2 = p2p_port(self.num_nodes + 1) self.extra_args = [ [f"-bind=127.0.0.1:{self.onion_port1}=onion", f"-bind=127.0.0.1:{self.onion_port2}=onion"], ] diff --git a/test/functional/p2p_headers_sync_with_minchainwork.py b/test/functional/p2p_headers_sync_with_minchainwork.py new file mode 100755 index 0000000000..991e3348ed --- /dev/null +++ b/test/functional/p2p_headers_sync_with_minchainwork.py @@ -0,0 +1,164 @@ +#!/usr/bin/env python3 +# Copyright (c) 2019-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. +"""Test that we reject low difficulty headers to prevent our block tree from filling up with useless bloat""" + +from test_framework.test_framework import BitcoinTestFramework + +from test_framework.p2p import ( + P2PInterface, +) + +from test_framework.messages import ( + msg_headers, +) + +from test_framework.blocktools import ( + NORMAL_GBT_REQUEST_PARAMS, + create_block, +) + +from test_framework.util import assert_equal + +NODE1_BLOCKS_REQUIRED = 15 +NODE2_BLOCKS_REQUIRED = 2047 + + +class RejectLowDifficultyHeadersTest(BitcoinTestFramework): + def set_test_params(self): + self.setup_clean_chain = True + self.num_nodes = 4 + # Node0 has no required chainwork; node1 requires 15 blocks on top of the genesis block; node2 requires 2047 + self.extra_args = [["-minimumchainwork=0x0", "-checkblockindex=0"], ["-minimumchainwork=0x1f", "-checkblockindex=0"], ["-minimumchainwork=0x1000", "-checkblockindex=0"], ["-minimumchainwork=0x1000", "-checkblockindex=0", "-whitelist=noban@127.0.0.1"]] + + def setup_network(self): + self.setup_nodes() + self.reconnect_all() + self.sync_all() + + def disconnect_all(self): + self.disconnect_nodes(0, 1) + self.disconnect_nodes(0, 2) + self.disconnect_nodes(0, 3) + + def reconnect_all(self): + self.connect_nodes(0, 1) + self.connect_nodes(0, 2) + self.connect_nodes(0, 3) + + def test_chains_sync_when_long_enough(self): + self.log.info("Generate blocks on the node with no required chainwork, and verify nodes 1 and 2 have no new headers in their headers tree") + with self.nodes[1].assert_debug_log(expected_msgs=["[net] Ignoring low-work chain (height=14)"]), self.nodes[2].assert_debug_log(expected_msgs=["[net] Ignoring low-work chain (height=14)"]), self.nodes[3].assert_debug_log(expected_msgs=["Synchronizing blockheaders, height: 14"]): + self.generate(self.nodes[0], NODE1_BLOCKS_REQUIRED-1, sync_fun=self.no_op) + + # Node3 should always allow headers due to noban permissions + self.log.info("Check that node3 will sync headers (due to noban permissions)") + + def check_node3_chaintips(num_tips, tip_hash, height): + node3_chaintips = self.nodes[3].getchaintips() + assert(len(node3_chaintips) == num_tips) + assert { + 'height': height, + 'hash': tip_hash, + 'branchlen': height, + 'status': 'headers-only', + } in node3_chaintips + + check_node3_chaintips(2, self.nodes[0].getbestblockhash(), NODE1_BLOCKS_REQUIRED-1) + + for node in self.nodes[1:3]: + chaintips = node.getchaintips() + assert(len(chaintips) == 1) + assert { + 'height': 0, + 'hash': '0f9188f13cb7b2c71f2a335e3a4fc328bf5beb436012afca590b1a11466e2206', + 'branchlen': 0, + 'status': 'active', + } in chaintips + + self.log.info("Generate more blocks to satisfy node1's minchainwork requirement, and verify node2 still has no new headers in headers tree") + with self.nodes[2].assert_debug_log(expected_msgs=["[net] Ignoring low-work chain (height=15)"]), self.nodes[3].assert_debug_log(expected_msgs=["Synchronizing blockheaders, height: 15"]): + self.generate(self.nodes[0], NODE1_BLOCKS_REQUIRED - self.nodes[0].getblockcount(), sync_fun=self.no_op) + self.sync_blocks(self.nodes[0:2]) # node3 will sync headers (noban permissions) but not blocks (due to minchainwork) + + assert { + 'height': 0, + 'hash': '0f9188f13cb7b2c71f2a335e3a4fc328bf5beb436012afca590b1a11466e2206', + 'branchlen': 0, + 'status': 'active', + } in self.nodes[2].getchaintips() + + assert(len(self.nodes[2].getchaintips()) == 1) + + self.log.info("Check that node3 accepted these headers as well") + check_node3_chaintips(2, self.nodes[0].getbestblockhash(), NODE1_BLOCKS_REQUIRED) + + self.log.info("Generate long chain for node0/node1/node3") + self.generate(self.nodes[0], NODE2_BLOCKS_REQUIRED-self.nodes[0].getblockcount(), sync_fun=self.no_op) + + self.log.info("Verify that node2 and node3 will sync the chain when it gets long enough") + self.sync_blocks() + + def test_peerinfo_includes_headers_presync_height(self): + self.log.info("Test that getpeerinfo() includes headers presync height") + + # Disconnect network, so that we can find our own peer connection more + # easily + self.disconnect_all() + + p2p = self.nodes[0].add_p2p_connection(P2PInterface()) + node = self.nodes[0] + + # Ensure we have a long chain already + current_height = self.nodes[0].getblockcount() + if (current_height < 3000): + self.generate(node, 3000-current_height, sync_fun=self.no_op) + + # Send a group of 2000 headers, forking from genesis. + new_blocks = [] + hashPrevBlock = int(node.getblockhash(0), 16) + for i in range(2000): + block = create_block(hashprev = hashPrevBlock, tmpl=node.getblocktemplate(NORMAL_GBT_REQUEST_PARAMS)) + block.solve() + new_blocks.append(block) + hashPrevBlock = block.sha256 + + headers_message = msg_headers(headers=new_blocks) + p2p.send_and_ping(headers_message) + + # getpeerinfo should show a sync in progress + assert_equal(node.getpeerinfo()[0]['presynced_headers'], 2000) + + def test_large_reorgs_can_succeed(self): + self.log.info("Test that a 2000+ block reorg, starting from a point that is more than 2000 blocks before a locator entry, can succeed") + + self.sync_all() # Ensure all nodes are synced. + self.disconnect_all() + + # locator(block at height T) will have heights: + # [T, T-1, ..., T-10, T-12, T-16, T-24, T-40, T-72, T-136, T-264, + # T-520, T-1032, T-2056, T-4104, ...] + # So mine a number of blocks > 4104 to ensure that the first window of + # received headers during a sync are fully between locator entries. + BLOCKS_TO_MINE = 4110 + + self.generate(self.nodes[0], BLOCKS_TO_MINE, sync_fun=self.no_op) + self.generate(self.nodes[1], BLOCKS_TO_MINE+2, sync_fun=self.no_op) + + self.reconnect_all() + + self.sync_blocks(timeout=300) # Ensure tips eventually agree + + + def run_test(self): + self.test_chains_sync_when_long_enough() + + self.test_large_reorgs_can_succeed() + + self.test_peerinfo_includes_headers_presync_height() + + + +if __name__ == '__main__': + RejectLowDifficultyHeadersTest().main() diff --git a/test/functional/p2p_i2p_sessions.py b/test/functional/p2p_i2p_sessions.py new file mode 100755 index 0000000000..4e52522b81 --- /dev/null +++ b/test/functional/p2p_i2p_sessions.py @@ -0,0 +1,36 @@ +#!/usr/bin/env python3 +# Copyright (c) 2022-2022 The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. +""" +Test whether persistent or transient I2P sessions are being used, based on `-i2pacceptincoming`. +""" + +from test_framework.test_framework import BitcoinTestFramework + + +class I2PSessions(BitcoinTestFramework): + def set_test_params(self): + self.num_nodes = 2 + # The test assumes that an I2P SAM proxy is not listening here. + self.extra_args = [ + ["-i2psam=127.0.0.1:60000", "-i2pacceptincoming=1"], + ["-i2psam=127.0.0.1:60000", "-i2pacceptincoming=0"], + ] + + def run_test(self): + addr = "zsxwyo6qcn3chqzwxnseusqgsnuw3maqnztkiypyfxtya4snkoka.b32.i2p" + + self.log.info("Ensure we create a persistent session when -i2pacceptincoming=1") + node0 = self.nodes[0] + with node0.assert_debug_log(expected_msgs=[f"Creating persistent SAM session"]): + node0.addnode(node=addr, command="onetry") + + self.log.info("Ensure we create a transient session when -i2pacceptincoming=0") + node1 = self.nodes[1] + with node1.assert_debug_log(expected_msgs=[f"Creating transient SAM session"]): + node1.addnode(node=addr, command="onetry") + + +if __name__ == '__main__': + I2PSessions().main() diff --git a/test/functional/p2p_initial_headers_sync.py b/test/functional/p2p_initial_headers_sync.py new file mode 100755 index 0000000000..e67c384da7 --- /dev/null +++ b/test/functional/p2p_initial_headers_sync.py @@ -0,0 +1,105 @@ +#!/usr/bin/env python3 +# Copyright (c) 2022 The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. +"""Test initial headers download + +Test that we only try to initially sync headers from one peer (until our chain +is close to caught up), and that each block announcement results in only one +additional peer receiving a getheaders message. +""" + +from test_framework.test_framework import BitcoinTestFramework +from test_framework.messages import ( + CInv, + MSG_BLOCK, + msg_headers, + msg_inv, +) +from test_framework.p2p import ( + p2p_lock, + P2PInterface, +) +from test_framework.util import ( + assert_equal, +) +import random + +class HeadersSyncTest(BitcoinTestFramework): + def set_test_params(self): + self.setup_clean_chain = True + self.num_nodes = 1 + + def announce_random_block(self, peers): + new_block_announcement = msg_inv(inv=[CInv(MSG_BLOCK, random.randrange(1<<256))]) + for p in peers: + p.send_and_ping(new_block_announcement) + + def run_test(self): + self.log.info("Adding a peer to node0") + peer1 = self.nodes[0].add_p2p_connection(P2PInterface()) + + # Wait for peer1 to receive a getheaders + peer1.wait_for_getheaders() + # An empty reply will clear the outstanding getheaders request, + # allowing additional getheaders requests to be sent to this peer in + # the future. + peer1.send_message(msg_headers()) + + self.log.info("Connecting two more peers to node0") + # Connect 2 more peers; they should not receive a getheaders yet + peer2 = self.nodes[0].add_p2p_connection(P2PInterface()) + peer3 = self.nodes[0].add_p2p_connection(P2PInterface()) + + all_peers = [peer1, peer2, peer3] + + self.log.info("Verify that peer2 and peer3 don't receive a getheaders after connecting") + for p in all_peers: + p.sync_with_ping() + with p2p_lock: + assert "getheaders" not in peer2.last_message + assert "getheaders" not in peer3.last_message + + with p2p_lock: + peer1.last_message.pop("getheaders", None) + + self.log.info("Have all peers announce a new block") + self.announce_random_block(all_peers) + + self.log.info("Check that peer1 receives a getheaders in response") + peer1.wait_for_getheaders() + peer1.send_message(msg_headers()) # Send empty response, see above + with p2p_lock: + peer1.last_message.pop("getheaders", None) + + self.log.info("Check that exactly 1 of {peer2, peer3} received a getheaders in response") + count = 0 + peer_receiving_getheaders = None + for p in [peer2, peer3]: + with p2p_lock: + if "getheaders" in p.last_message: + count += 1 + peer_receiving_getheaders = p + p.last_message.pop("getheaders", None) + p.send_message(msg_headers()) # Send empty response, see above + + assert_equal(count, 1) + + self.log.info("Announce another new block, from all peers") + self.announce_random_block(all_peers) + + self.log.info("Check that peer1 receives a getheaders in response") + peer1.wait_for_getheaders() + + self.log.info("Check that the remaining peer received a getheaders as well") + expected_peer = peer2 + if peer2 == peer_receiving_getheaders: + expected_peer = peer3 + + expected_peer.wait_for_getheaders() + + self.log.info("Success!") + +if __name__ == '__main__': + HeadersSyncTest().main() + diff --git a/test/functional/p2p_invalid_tx.py b/test/functional/p2p_invalid_tx.py index 139f4d64e7..28efd5a81e 100755 --- a/test/functional/p2p_invalid_tx.py +++ b/test/functional/p2p_invalid_tx.py @@ -60,7 +60,6 @@ class InvalidTxRequestTest(BitcoinTestFramework): block.solve() # Save the coinbase for later block1 = block - tip = block.sha256 node.p2ps[0].send_blocks_and_test([block], node, success=True) self.log.info("Mature the block.") @@ -93,24 +92,24 @@ class InvalidTxRequestTest(BitcoinTestFramework): SCRIPT_PUB_KEY_OP_TRUE = b'\x51\x75' * 15 + b'\x51' tx_withhold = CTransaction() tx_withhold.vin.append(CTxIn(outpoint=COutPoint(block1.vtx[0].sha256, 0))) - tx_withhold.vout.append(CTxOut(nValue=50 * COIN - 12000, scriptPubKey=SCRIPT_PUB_KEY_OP_TRUE)) + tx_withhold.vout = [CTxOut(nValue=25 * COIN - 12000, scriptPubKey=SCRIPT_PUB_KEY_OP_TRUE)] * 2 tx_withhold.calc_sha256() # Our first orphan tx with some outputs to create further orphan txs tx_orphan_1 = CTransaction() tx_orphan_1.vin.append(CTxIn(outpoint=COutPoint(tx_withhold.sha256, 0))) - tx_orphan_1.vout = [CTxOut(nValue=10 * COIN, scriptPubKey=SCRIPT_PUB_KEY_OP_TRUE)] * 3 + tx_orphan_1.vout = [CTxOut(nValue=8 * COIN, scriptPubKey=SCRIPT_PUB_KEY_OP_TRUE)] * 3 tx_orphan_1.calc_sha256() # A valid transaction with low fee tx_orphan_2_no_fee = CTransaction() tx_orphan_2_no_fee.vin.append(CTxIn(outpoint=COutPoint(tx_orphan_1.sha256, 0))) - tx_orphan_2_no_fee.vout.append(CTxOut(nValue=10 * COIN, scriptPubKey=SCRIPT_PUB_KEY_OP_TRUE)) + tx_orphan_2_no_fee.vout.append(CTxOut(nValue=8 * COIN, scriptPubKey=SCRIPT_PUB_KEY_OP_TRUE)) # A valid transaction with sufficient fee tx_orphan_2_valid = CTransaction() tx_orphan_2_valid.vin.append(CTxIn(outpoint=COutPoint(tx_orphan_1.sha256, 1))) - tx_orphan_2_valid.vout.append(CTxOut(nValue=10 * COIN - 12000, scriptPubKey=SCRIPT_PUB_KEY_OP_TRUE)) + tx_orphan_2_valid.vout.append(CTxOut(nValue=8 * COIN - 12000, scriptPubKey=SCRIPT_PUB_KEY_OP_TRUE)) tx_orphan_2_valid.calc_sha256() # An invalid transaction with negative fee @@ -157,6 +156,7 @@ class InvalidTxRequestTest(BitcoinTestFramework): with node.assert_debug_log(['orphanage overflow, removed 1 tx']): node.p2ps[0].send_txs_and_test(orphan_tx_pool, node, success=False) + self.log.info('Test orphan with rejected parents') rejected_parent = CTransaction() rejected_parent.vin.append(CTxIn(outpoint=COutPoint(tx_orphan_2_invalid.sha256, 0))) rejected_parent.vout.append(CTxOut(nValue=11 * COIN, scriptPubKey=SCRIPT_PUB_KEY_OP_TRUE)) @@ -164,6 +164,64 @@ class InvalidTxRequestTest(BitcoinTestFramework): with node.assert_debug_log(['not keeping orphan with rejected parents {}'.format(rejected_parent.hash)]): node.p2ps[0].send_txs_and_test([rejected_parent], node, success=False) + self.log.info('Test that a peer disconnection causes erase its transactions from the orphan pool') + with node.assert_debug_log(['Erased 100 orphan tx from peer=25']): + self.reconnect_p2p(num_connections=1) + + self.log.info('Test that a transaction in the orphan pool is included in a new tip block causes erase this transaction from the orphan pool') + tx_withhold_until_block_A = CTransaction() + tx_withhold_until_block_A.vin.append(CTxIn(outpoint=COutPoint(tx_withhold.sha256, 1))) + tx_withhold_until_block_A.vout = [CTxOut(nValue=12 * COIN, scriptPubKey=SCRIPT_PUB_KEY_OP_TRUE)] * 2 + tx_withhold_until_block_A.calc_sha256() + + tx_orphan_include_by_block_A = CTransaction() + tx_orphan_include_by_block_A.vin.append(CTxIn(outpoint=COutPoint(tx_withhold_until_block_A.sha256, 0))) + tx_orphan_include_by_block_A.vout.append(CTxOut(nValue=12 * COIN - 12000, scriptPubKey=SCRIPT_PUB_KEY_OP_TRUE)) + tx_orphan_include_by_block_A.calc_sha256() + + self.log.info('Send the orphan ... ') + node.p2ps[0].send_txs_and_test([tx_orphan_include_by_block_A], node, success=False) + + tip = int(node.getbestblockhash(), 16) + height = node.getblockcount() + 1 + block_A = create_block(tip, create_coinbase(height)) + block_A.vtx.extend([tx_withhold, tx_withhold_until_block_A, tx_orphan_include_by_block_A]) + block_A.hashMerkleRoot = block_A.calc_merkle_root() + block_A.solve() + + self.log.info('Send the block that includes the previous orphan ... ') + with node.assert_debug_log(["Erased 1 orphan tx included or conflicted by block"]): + node.p2ps[0].send_blocks_and_test([block_A], node, success=True) + + self.log.info('Test that a transaction in the orphan pool conflicts with a new tip block causes erase this transaction from the orphan pool') + tx_withhold_until_block_B = CTransaction() + tx_withhold_until_block_B.vin.append(CTxIn(outpoint=COutPoint(tx_withhold_until_block_A.sha256, 1))) + tx_withhold_until_block_B.vout.append(CTxOut(nValue=11 * COIN, scriptPubKey=SCRIPT_PUB_KEY_OP_TRUE)) + tx_withhold_until_block_B.calc_sha256() + + tx_orphan_include_by_block_B = CTransaction() + tx_orphan_include_by_block_B.vin.append(CTxIn(outpoint=COutPoint(tx_withhold_until_block_B.sha256, 0))) + tx_orphan_include_by_block_B.vout.append(CTxOut(nValue=10 * COIN, scriptPubKey=SCRIPT_PUB_KEY_OP_TRUE)) + tx_orphan_include_by_block_B.calc_sha256() + + tx_orphan_conflict_by_block_B = CTransaction() + tx_orphan_conflict_by_block_B.vin.append(CTxIn(outpoint=COutPoint(tx_withhold_until_block_B.sha256, 0))) + tx_orphan_conflict_by_block_B.vout.append(CTxOut(nValue=9 * COIN, scriptPubKey=SCRIPT_PUB_KEY_OP_TRUE)) + tx_orphan_conflict_by_block_B.calc_sha256() + self.log.info('Send the orphan ... ') + node.p2ps[0].send_txs_and_test([tx_orphan_conflict_by_block_B], node, success=False) + + tip = int(node.getbestblockhash(), 16) + height = node.getblockcount() + 1 + block_B = create_block(tip, create_coinbase(height)) + block_B.vtx.extend([tx_withhold_until_block_B, tx_orphan_include_by_block_B]) + block_B.hashMerkleRoot = block_B.calc_merkle_root() + block_B.solve() + + self.log.info('Send the block that includes a transaction which conflicts with the previous orphan ... ') + with node.assert_debug_log(["Erased 1 orphan tx included or conflicted by block"]): + node.p2ps[0].send_blocks_and_test([block_B], node, success=True) + if __name__ == '__main__': InvalidTxRequestTest().main() diff --git a/test/functional/p2p_leak.py b/test/functional/p2p_leak.py index af8e45d578..936c22197c 100755 --- a/test/functional/p2p_leak.py +++ b/test/functional/p2p_leak.py @@ -138,6 +138,9 @@ class P2PLeakTest(BitcoinTestFramework): # Give the node enough time to possibly leak out a message time.sleep(PEER_TIMEOUT + 2) + self.log.info("Connect peer to ensure the net thread runs the disconnect logic at least once") + self.nodes[0].add_p2p_connection(P2PInterface()) + # Make sure only expected messages came in assert not no_version_idle_peer.unexpected_msg assert not no_version_idle_peer.got_wtxidrelay @@ -169,7 +172,7 @@ 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) - with self.nodes[0].assert_debug_log(['peer=4 using obsolete version 31799; disconnecting']): + with self.nodes[0].assert_debug_log(["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/p2p_permissions.py b/test/functional/p2p_permissions.py index 1dc3a5b9a0..453a0920cc 100755 --- a/test/functional/p2p_permissions.py +++ b/test/functional/p2p_permissions.py @@ -91,6 +91,7 @@ class P2PPermissionsTests(BitcoinTestFramework): self.nodes[1].assert_start_raises_init_error(["-whitelist=oopsie@127.0.0.1"], "Invalid P2P permission", match=ErrorMatch.PARTIAL_REGEX) self.nodes[1].assert_start_raises_init_error(["-whitelist=noban@127.0.0.1:230"], "Invalid netmask specified in", match=ErrorMatch.PARTIAL_REGEX) self.nodes[1].assert_start_raises_init_error(["-whitebind=noban@127.0.0.1/10"], "Cannot resolve -whitebind address", match=ErrorMatch.PARTIAL_REGEX) + self.nodes[1].assert_start_raises_init_error(["-whitebind=noban@127.0.0.1", "-bind=127.0.0.1", "-listen=0"], "Cannot set -bind or -whitebind together with -listen=0", match=ErrorMatch.PARTIAL_REGEX) def check_tx_relay(self): block_op_true = self.nodes[0].getblock(self.generatetoaddress(self.nodes[0], 100, ADDRESS_BCRT1_P2WSH_OP_TRUE)[0]) @@ -110,7 +111,8 @@ class P2PPermissionsTests(BitcoinTestFramework): 'vout': 0, }], outputs=[{ ADDRESS_BCRT1_P2WSH_OP_TRUE: 5, - }]), + }], + replaceable=False), ) tx.wit.vtxinwit = [CTxInWitness()] tx.wit.vtxinwit[0].scriptWitness.stack = [CScript([OP_TRUE])] diff --git a/test/functional/p2p_segwit.py b/test/functional/p2p_segwit.py index 89ddfd3bcf..311b0b67db 100755 --- a/test/functional/p2p_segwit.py +++ b/test/functional/p2p_segwit.py @@ -16,7 +16,7 @@ from test_framework.blocktools import ( ) from test_framework.key import ECKey from test_framework.messages import ( - BIP125_SEQUENCE_NUMBER, + MAX_BIP125_RBF_SEQUENCE, CBlockHeader, CInv, COutPoint, @@ -245,7 +245,7 @@ class SegWitTest(BitcoinTestFramework): self.test_node = self.nodes[0].add_p2p_connection(TestP2PConn(), services=P2P_SERVICES) # self.old_node sets only NODE_NETWORK self.old_node = self.nodes[0].add_p2p_connection(TestP2PConn(), services=NODE_NETWORK) - # self.std_node is for testing node1 (fRequireStandard=true) + # self.std_node is for testing node1 (requires standard txs) self.std_node = self.nodes[1].add_p2p_connection(TestP2PConn(), services=P2P_SERVICES) # self.std_wtx_node is for testing node1 with wtxid relay self.std_wtx_node = self.nodes[1].add_p2p_connection(TestP2PConn(wtxidrelay=True), services=P2P_SERVICES) @@ -371,6 +371,10 @@ class SegWitTest(BitcoinTestFramework): block1 = self.build_next_block() block1.solve() + # Send an empty headers message, to clear out any prior getheaders + # messages that our peer may be waiting for us on. + self.test_node.send_message(msg_headers()) + self.test_node.announce_block_and_wait_for_getdata(block1, use_header=False) assert self.test_node.last_message["getdata"].inv[0].type == blocktype test_witness_block(self.nodes[0], self.test_node, block1, True) @@ -585,7 +589,7 @@ class SegWitTest(BitcoinTestFramework): tx.vin = [CTxIn(COutPoint(p2sh_tx.sha256, 0), CScript([witness_script]))] tx.vout = [CTxOut(p2sh_tx.vout[0].nValue - 10000, script_pubkey)] tx.vout.append(CTxOut(8000, script_pubkey)) # Might burn this later - tx.vin[0].nSequence = BIP125_SEQUENCE_NUMBER # Just to have the option to bump this tx from the mempool + tx.vin[0].nSequence = MAX_BIP125_RBF_SEQUENCE # Just to have the option to bump this tx from the mempool tx.rehash() # This is always accepted, since the mempool policy is to consider segwit as always active @@ -1378,7 +1382,7 @@ class SegWitTest(BitcoinTestFramework): tx3.vout.append(CTxOut(total_value - 1000, script_pubkey)) tx3.rehash() - # First we test this transaction against fRequireStandard=true node + # First we test this transaction against std_node # making sure the txid is added to the reject filter self.std_node.announce_tx_and_wait_for_getdata(tx3) test_transaction_acceptance(self.nodes[1], self.std_node, tx3, with_witness=True, accepted=False, reason="bad-txns-nonstandard-inputs") @@ -1386,7 +1390,7 @@ class SegWitTest(BitcoinTestFramework): self.std_node.announce_tx_and_wait_for_getdata(tx3, success=False) # Spending a higher version witness output is not allowed by policy, - # even with fRequireStandard=false. + # even with the node that accepts non-standard txs. test_transaction_acceptance(self.nodes[0], self.test_node, tx3, with_witness=True, accepted=False, reason="reserved for soft-fork upgrades") # Building a block with the transaction must be valid, however. @@ -1998,7 +2002,7 @@ class SegWitTest(BitcoinTestFramework): def serialize(self): return serialize_with_bogus_witness(self.tx) - tx = self.wallet.create_self_transfer(from_node=self.nodes[0])['tx'] + tx = self.wallet.create_self_transfer()['tx'] assert_raises_rpc_error(-22, "TX decode failed", self.nodes[0].decoderawtransaction, hexstring=serialize_with_bogus_witness(tx).hex(), iswitness=True) with self.nodes[0].assert_debug_log(['Unknown transaction optional data']): self.test_node.send_and_ping(msg_bogus_tx(tx)) diff --git a/test/functional/p2p_timeouts.py b/test/functional/p2p_timeouts.py index f0abbc7d8b..15a879ae3c 100755 --- a/test/functional/p2p_timeouts.py +++ b/test/functional/p2p_timeouts.py @@ -94,6 +94,11 @@ class TimeoutsTest(BitcoinTestFramework): no_version_node.wait_for_disconnect(timeout=1) no_send_node.wait_for_disconnect(timeout=1) + self.stop_nodes(0) + self.nodes[0].assert_start_raises_init_error( + expected_msg='Error: peertimeout must be a positive integer.', + extra_args=['-peertimeout=0'], + ) if __name__ == '__main__': TimeoutsTest().main() diff --git a/test/functional/p2p_unrequested_blocks.py b/test/functional/p2p_unrequested_blocks.py index 76d9b045ce..5030e7af26 100755 --- a/test/functional/p2p_unrequested_blocks.py +++ b/test/functional/p2p_unrequested_blocks.py @@ -72,6 +72,13 @@ class AcceptBlockTest(BitcoinTestFramework): def setup_network(self): self.setup_nodes() + def check_hash_in_chaintips(self, node, blockhash): + tips = node.getchaintips() + for x in tips: + if x["hash"] == blockhash: + return True + return False + def run_test(self): test_node = self.nodes[0].add_p2p_connection(P2PInterface()) min_work_node = self.nodes[1].add_p2p_connection(P2PInterface()) @@ -89,10 +96,15 @@ class AcceptBlockTest(BitcoinTestFramework): blocks_h2[i].solve() block_time += 1 test_node.send_and_ping(msg_block(blocks_h2[0])) - min_work_node.send_and_ping(msg_block(blocks_h2[1])) + + with self.nodes[1].assert_debug_log(expected_msgs=[f"AcceptBlockHeader: not adding new block header {blocks_h2[1].hash}, missing anti-dos proof-of-work validation"]): + min_work_node.send_and_ping(msg_block(blocks_h2[1])) assert_equal(self.nodes[0].getblockcount(), 2) assert_equal(self.nodes[1].getblockcount(), 1) + + # Ensure that the header of the second block was also not accepted by node1 + assert_equal(self.check_hash_in_chaintips(self.nodes[1], blocks_h2[1].hash), False) self.log.info("First height 2 block accepted by node0; correctly rejected by node1") # 3. Send another block that builds on genesis. diff --git a/test/functional/rpc_blockchain.py b/test/functional/rpc_blockchain.py index 193bd3f1cd..d07d28879e 100755 --- a/test/functional/rpc_blockchain.py +++ b/test/functional/rpc_blockchain.py @@ -38,6 +38,7 @@ from test_framework.messages import ( msg_block, ) from test_framework.p2p import P2PInterface +from test_framework.script import hash256 from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_equal, @@ -261,12 +262,12 @@ class BlockchainTest(BitcoinTestFramework): assert_raises_rpc_error(-1, 'getchaintxstats', self.nodes[0].getchaintxstats, 0, '', 0) # Test `getchaintxstats` invalid `nblocks` - assert_raises_rpc_error(-1, "JSON value is not an integer as expected", self.nodes[0].getchaintxstats, '') + assert_raises_rpc_error(-3, "JSON value of type string is not of expected type number", self.nodes[0].getchaintxstats, '') assert_raises_rpc_error(-8, "Invalid block count: should be between 0 and the block's height - 1", self.nodes[0].getchaintxstats, -1) assert_raises_rpc_error(-8, "Invalid block count: should be between 0 and the block's height - 1", self.nodes[0].getchaintxstats, self.nodes[0].getblockcount()) # Test `getchaintxstats` invalid `blockhash` - assert_raises_rpc_error(-1, "JSON value is not a string as expected", self.nodes[0].getchaintxstats, blockhash=0) + assert_raises_rpc_error(-3, "JSON value of type number is not of expected type string", self.nodes[0].getchaintxstats, blockhash=0) assert_raises_rpc_error(-8, "blockhash must be of length 64 (not 1, for '0')", self.nodes[0].getchaintxstats, blockhash='0') assert_raises_rpc_error(-8, "blockhash must be hexadecimal string (not 'ZZZ0000000000000000000000000000000000000000000000000000000000000')", self.nodes[0].getchaintxstats, blockhash='ZZZ0000000000000000000000000000000000000000000000000000000000000') assert_raises_rpc_error(-5, "Block not found", self.nodes[0].getchaintxstats, blockhash='0000000000000000000000000000000000000000000000000000000000000000') @@ -452,8 +453,9 @@ class BlockchainTest(BitcoinTestFramework): # (Previously this was broken based on setting # `rpc/blockchain.cpp:latestblock` incorrectly.) # - b20hash = node.getblockhash(20) - b20 = node.getblock(b20hash) + fork_height = current_height - 100 # choose something vaguely near our tip + fork_hash = node.getblockhash(fork_height) + fork_block = node.getblock(fork_hash) def solve_and_send_block(prevhash, height, time): b = create_block(prevhash, create_coinbase(height), time) @@ -461,10 +463,10 @@ class BlockchainTest(BitcoinTestFramework): peer.send_and_ping(msg_block(b)) return b - b21f = solve_and_send_block(int(b20hash, 16), 21, b20['time'] + 1) - b22f = solve_and_send_block(b21f.sha256, 22, b21f.nTime + 1) + b1 = solve_and_send_block(int(fork_hash, 16), fork_height+1, fork_block['time'] + 1) + b2 = solve_and_send_block(b1.sha256, fork_height+2, b1.nTime + 1) - node.invalidateblock(b22f.hash) + node.invalidateblock(b2.hash) def assert_waitforheight(height, timeout=2): assert_equal( @@ -484,6 +486,10 @@ class BlockchainTest(BitcoinTestFramework): self.wallet.send_self_transfer(fee_rate=fee_per_kb, from_node=node) blockhash = self.generate(node, 1)[0] + def assert_hexblock_hashes(verbosity): + block = node.getblock(blockhash, verbosity) + assert_equal(blockhash, hash256(bytes.fromhex(block[:160]))[::-1].hex()) + def assert_fee_not_in_block(verbosity): block = node.getblock(blockhash, verbosity) assert 'fee' not in block['tx'][1] @@ -518,8 +524,13 @@ class BlockchainTest(BitcoinTestFramework): for vin in tx["vin"]: assert "prevout" not in vin + self.log.info("Test that getblock with verbosity 0 hashes to expected value") + assert_hexblock_hashes(0) + assert_hexblock_hashes(False) + self.log.info("Test that getblock with verbosity 1 doesn't include fee") assert_fee_not_in_block(1) + assert_fee_not_in_block(True) self.log.info('Test that getblock with verbosity 2 and 3 includes expected fee') assert_fee_in_block(2) @@ -536,7 +547,7 @@ class BlockchainTest(BitcoinTestFramework): datadir = get_datadir_path(self.options.tmpdir, 0) self.log.info("Test getblock with invalid verbosity type returns proper error message") - assert_raises_rpc_error(-1, "JSON value is not an integer as expected", node.getblock, blockhash, "2") + assert_raises_rpc_error(-3, "JSON value of type string is not of expected type number", node.getblock, blockhash, "2") def move_block_file(old, new): old_path = os.path.join(datadir, self.chain, 'blocks', old) diff --git a/test/functional/rpc_createmultisig.py b/test/functional/rpc_createmultisig.py index 716ee8f7ef..1ad3fc24c9 100755 --- a/test/functional/rpc_createmultisig.py +++ b/test/functional/rpc_createmultisig.py @@ -43,7 +43,7 @@ class RpcCreateMultiSigTest(BitcoinTestFramework): if self.is_bdb_compiled(): self.final = node2.getnewaddress() else: - self.final = getnewdestination()[2] + self.final = getnewdestination('bech32')[2] def run_test(self): node0, node1, node2 = self.nodes @@ -66,9 +66,7 @@ class RpcCreateMultiSigTest(BitcoinTestFramework): # Test mixed compressed and uncompressed pubkeys self.log.info('Mixed compressed and uncompressed multisigs are not allowed') - pk0 = getnewdestination()[0].hex() - pk1 = getnewdestination()[0].hex() - pk2 = getnewdestination()[0].hex() + pk0, pk1, pk2 = [getnewdestination('bech32')[0].hex() for _ in range(3)] # decompress pk2 pk_obj = ECPubKey() diff --git a/test/functional/rpc_estimatefee.py b/test/functional/rpc_estimatefee.py index 51b7efb4c3..b057400887 100755 --- a/test/functional/rpc_estimatefee.py +++ b/test/functional/rpc_estimatefee.py @@ -22,15 +22,15 @@ class EstimateFeeTest(BitcoinTestFramework): assert_raises_rpc_error(-1, "estimaterawfee", self.nodes[0].estimaterawfee) # wrong type for conf_target - assert_raises_rpc_error(-3, "Expected type number, got string", self.nodes[0].estimatesmartfee, 'foo') - assert_raises_rpc_error(-3, "Expected type number, got string", self.nodes[0].estimaterawfee, 'foo') + assert_raises_rpc_error(-3, "JSON value of type string is not of expected type number", self.nodes[0].estimatesmartfee, 'foo') + assert_raises_rpc_error(-3, "JSON value of type string is not of expected type number", self.nodes[0].estimaterawfee, 'foo') # wrong type for estimatesmartfee(estimate_mode) - assert_raises_rpc_error(-3, "Expected type string, got number", self.nodes[0].estimatesmartfee, 1, 1) + assert_raises_rpc_error(-3, "JSON value of type number is not of expected type string", self.nodes[0].estimatesmartfee, 1, 1) assert_raises_rpc_error(-8, 'Invalid estimate_mode parameter, must be one of: "unset", "economical", "conservative"', self.nodes[0].estimatesmartfee, 1, 'foo') # wrong type for estimaterawfee(threshold) - assert_raises_rpc_error(-3, "Expected type number, got string", self.nodes[0].estimaterawfee, 1, 'foo') + assert_raises_rpc_error(-3, "JSON value of type string is not of expected type number", self.nodes[0].estimaterawfee, 1, 'foo') # extra params assert_raises_rpc_error(-1, "estimatesmartfee", self.nodes[0].estimatesmartfee, 1, 'ECONOMICAL', 1) diff --git a/test/functional/rpc_fundrawtransaction.py b/test/functional/rpc_fundrawtransaction.py index 759e43194b..17c6fce9c2 100755 --- a/test/functional/rpc_fundrawtransaction.py +++ b/test/functional/rpc_fundrawtransaction.py @@ -11,6 +11,9 @@ from math import ceil from test_framework.descriptors import descsum_create from test_framework.key import ECKey +from test_framework.messages import ( + COIN, +) from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_approx, @@ -103,6 +106,8 @@ class RawTransactionsTest(BitcoinTestFramework): self.generate(self.nodes[2], 1) self.generate(self.nodes[0], 121) + self.test_add_inputs_default_value() + self.test_weight_calculation() self.test_change_position() self.test_simple() self.test_simple_two_coins() @@ -297,7 +302,7 @@ class RawTransactionsTest(BitcoinTestFramework): inputs = [ {'txid' : utx['txid'], 'vout' : utx['vout']} ] outputs = { self.nodes[0].getnewaddress() : Decimal(4.0) } rawtx = self.nodes[2].createrawtransaction(inputs, outputs) - assert_raises_rpc_error(-1, "JSON value is not a string as expected", self.nodes[2].fundrawtransaction, rawtx, {'change_type': None}) + assert_raises_rpc_error(-3, "JSON value of type null is not of expected type string", self.nodes[2].fundrawtransaction, rawtx, {'change_type': None}) assert_raises_rpc_error(-5, "Unknown change type ''", self.nodes[2].fundrawtransaction, rawtx, {'change_type': ''}) rawtx = self.nodes[2].fundrawtransaction(rawtx, {'change_type': 'bech32'}) dec_tx = self.nodes[2].decoderawtransaction(rawtx['hex']) @@ -404,7 +409,7 @@ class RawTransactionsTest(BitcoinTestFramework): inputs = [ {'txid' : "1c7f966dab21119bac53213a2bc7532bff1fa844c124fd750a7d0b1332440bd1", 'vout' : 0} ] #invalid vin! outputs = { self.nodes[0].getnewaddress() : 1.0} rawtx = self.nodes[2].createrawtransaction(inputs, outputs) - assert_raises_rpc_error(-4, "Insufficient funds", self.nodes[2].fundrawtransaction, rawtx) + assert_raises_rpc_error(-4, "Unable to find UTXO for external input", self.nodes[2].fundrawtransaction, rawtx) def test_fee_p2pkh(self): """Compare fee of a standard pubkeyhash transaction.""" @@ -631,7 +636,7 @@ class RawTransactionsTest(BitcoinTestFramework): self.log.info("Test fundrawtxn fee with many inputs") # Empty node1, send some small coins from node0 to node1. - self.nodes[1].sendtoaddress(self.nodes[0].getnewaddress(), self.nodes[1].getbalance(), "", "", True) + self.nodes[1].sendall(recipients=[self.nodes[0].getnewaddress()]) self.generate(self.nodes[1], 1) for _ in range(20): @@ -657,7 +662,7 @@ class RawTransactionsTest(BitcoinTestFramework): self.log.info("Test fundrawtxn sign+send with many inputs") # Again, empty node1, send some small coins from node0 to node1. - self.nodes[1].sendtoaddress(self.nodes[0].getnewaddress(), self.nodes[1].getbalance(), "", "", True) + self.nodes[1].sendall(recipients=[self.nodes[0].getnewaddress()]) self.generate(self.nodes[1], 1) for _ in range(20): @@ -1069,6 +1074,153 @@ class RawTransactionsTest(BitcoinTestFramework): self.nodes[2].unloadwallet("extfund") + def test_add_inputs_default_value(self): + self.log.info("Test 'add_inputs' default value") + + # Create and fund the wallet with 5 BTC + self.nodes[2].createwallet("test_preset_inputs") + wallet = self.nodes[2].get_wallet_rpc("test_preset_inputs") + addr1 = wallet.getnewaddress(address_type="bech32") + self.nodes[0].sendtoaddress(addr1, 5) + self.generate(self.nodes[0], 1) + + # Covered cases: + # 1. Default add_inputs value with no preset inputs (add_inputs=true): + # Expect: automatically add coins from the wallet to the tx. + # 2. Default add_inputs value with preset inputs (add_inputs=false): + # Expect: disallow automatic coin selection. + # 3. Explicit add_inputs=true and preset inputs (with preset inputs not-covering the target amount). + # Expect: include inputs from the wallet. + # 4. Explicit add_inputs=true and preset inputs (with preset inputs covering the target amount). + # Expect: only preset inputs are used. + # 5. Explicit add_inputs=true, no preset inputs (same as (1) but with an explicit set): + # Expect: include inputs from the wallet. + + # Case (1), 'send' command + # 'add_inputs' value is true unless "inputs" are specified, in such case, add_inputs=false. + # So, the wallet will automatically select coins and create the transaction if only the outputs are provided. + tx = wallet.send(outputs=[{addr1: 3}]) + assert tx["complete"] + + # Case (2), 'send' command + # Select an input manually, which doesn't cover the entire output amount and + # verify that the dynamically set 'add_inputs=false' value works. + + # Fund wallet with 2 outputs, 5 BTC each. + addr2 = wallet.getnewaddress(address_type="bech32") + source_tx = self.nodes[0].send(outputs=[{addr1: 5}, {addr2: 5}], options={"change_position": 0}) + self.generate(self.nodes[0], 1) + + # Select only one input. + options = { + "inputs": [ + { + "txid": source_tx["txid"], + "vout": 1 # change position was hardcoded to index 0 + } + ] + } + assert_raises_rpc_error(-4, "Insufficient funds", wallet.send, outputs=[{addr1: 8}], options=options) + + # Case (3), Explicit add_inputs=true and preset inputs (with preset inputs not-covering the target amount) + options["add_inputs"] = True + options["add_to_wallet"] = False + tx = wallet.send(outputs=[{addr1: 8}], options=options) + assert tx["complete"] + + # Case (4), Explicit add_inputs=true and preset inputs (with preset inputs covering the target amount) + options["inputs"].append({ + "txid": source_tx["txid"], + "vout": 2 # change position was hardcoded to index 0 + }) + tx = wallet.send(outputs=[{addr1: 8}], options=options) + assert tx["complete"] + # Check that only the preset inputs were added to the tx + decoded_psbt_inputs = self.nodes[0].decodepsbt(tx["psbt"])['tx']['vin'] + assert_equal(len(decoded_psbt_inputs), 2) + for input in decoded_psbt_inputs: + assert_equal(input["txid"], source_tx["txid"]) + + # Case (5), assert that inputs are added to the tx by explicitly setting add_inputs=true + options = {"add_inputs": True, "add_to_wallet": True} + tx = wallet.send(outputs=[{addr1: 8}], options=options) + assert tx["complete"] + + ################################################ + + # Case (1), 'walletcreatefundedpsbt' command + # Default add_inputs value with no preset inputs (add_inputs=true) + inputs = [] + outputs = {self.nodes[1].getnewaddress(): 8} + assert "psbt" in wallet.walletcreatefundedpsbt(inputs=inputs, outputs=outputs) + + # Case (2), 'walletcreatefundedpsbt' command + # Default add_inputs value with preset inputs (add_inputs=false). + inputs = [{ + "txid": source_tx["txid"], + "vout": 1 # change position was hardcoded to index 0 + }] + outputs = {self.nodes[1].getnewaddress(): 8} + assert_raises_rpc_error(-4, "Insufficient funds", wallet.walletcreatefundedpsbt, inputs=inputs, outputs=outputs) + + # Case (3), Explicit add_inputs=true and preset inputs (with preset inputs not-covering the target amount) + options["add_inputs"] = True + options["add_to_wallet"] = False + assert "psbt" in wallet.walletcreatefundedpsbt(outputs=[{addr1: 8}], inputs=inputs, options=options) + + # Case (4), Explicit add_inputs=true and preset inputs (with preset inputs covering the target amount) + inputs.append({ + "txid": source_tx["txid"], + "vout": 2 # change position was hardcoded to index 0 + }) + psbt_tx = wallet.walletcreatefundedpsbt(outputs=[{addr1: 8}], inputs=inputs, options=options) + # Check that only the preset inputs were added to the tx + decoded_psbt_inputs = self.nodes[0].decodepsbt(psbt_tx["psbt"])['tx']['vin'] + assert_equal(len(decoded_psbt_inputs), 2) + for input in decoded_psbt_inputs: + assert_equal(input["txid"], source_tx["txid"]) + + # Case (5), 'walletcreatefundedpsbt' command + # Explicit add_inputs=true, no preset inputs + options = { + "add_inputs": True + } + assert "psbt" in wallet.walletcreatefundedpsbt(inputs=[], outputs=outputs, options=options) + + self.nodes[2].unloadwallet("test_preset_inputs") + + def test_weight_calculation(self): + self.log.info("Test weight calculation with external inputs") + + self.nodes[2].createwallet("test_weight_calculation") + wallet = self.nodes[2].get_wallet_rpc("test_weight_calculation") + + addr = wallet.getnewaddress(address_type="bech32") + ext_addr = self.nodes[0].getnewaddress(address_type="bech32") + txid = self.nodes[0].send([{addr: 5}, {ext_addr: 5}])["txid"] + vout = find_vout_for_address(self.nodes[0], txid, addr) + ext_vout = find_vout_for_address(self.nodes[0], txid, ext_addr) + + self.nodes[0].sendtoaddress(wallet.getnewaddress(address_type="bech32"), 5) + self.generate(self.nodes[0], 1) + + rawtx = wallet.createrawtransaction([{'txid': txid, 'vout': vout}], [{self.nodes[0].getnewaddress(address_type="bech32"): 8}]) + fundedtx = wallet.fundrawtransaction(rawtx, {'fee_rate': 10, "change_type": "bech32"}) + # with 71-byte signatures we should expect following tx size + # tx overhead (10) + 2 inputs (41 each) + 2 p2wpkh (31 each) + (segwit marker and flag (2) + 2 p2wpkh 71 byte sig witnesses (107 each)) / witness scaling factor (4) + tx_size = ceil(10 + 41*2 + 31*2 + (2 + 107*2)/4) + assert_equal(fundedtx['fee'] * COIN, tx_size * 10) + + # Using the other output should have 72 byte sigs + rawtx = wallet.createrawtransaction([{'txid': txid, 'vout': ext_vout}], [{self.nodes[0].getnewaddress(): 13}]) + ext_desc = self.nodes[0].getaddressinfo(ext_addr)["desc"] + fundedtx = wallet.fundrawtransaction(rawtx, {'fee_rate': 10, "change_type": "bech32", "solving_data": {"descriptors": [ext_desc]}}) + # tx overhead (10) + 3 inputs (41 each) + 2 p2wpkh(31 each) + (segwit marker and flag (2) + 2 p2wpkh 71 bytes sig witnesses (107 each) + p2wpkh 72 byte sig witness (108)) / witness scaling factor (4) + tx_size = ceil(10 + 41*3 + 31*2 + (2 + 107*2 + 108)/4) + assert_equal(fundedtx['fee'] * COIN, tx_size * 10) + + self.nodes[2].unloadwallet("test_weight_calculation") + def test_include_unsafe(self): self.log.info("Test fundrawtxn with unsafe inputs") diff --git a/test/functional/rpc_getblockfrompeer.py b/test/functional/rpc_getblockfrompeer.py index a7628b5591..278a343b2b 100755 --- a/test/functional/rpc_getblockfrompeer.py +++ b/test/functional/rpc_getblockfrompeer.py @@ -54,14 +54,17 @@ class GetBlockFromPeerTest(BitcoinTestFramework): assert_equal(len(peers), 1) peer_0_peer_1_id = peers[0]["id"] - self.log.info("Arguments must be sensible") - assert_raises_rpc_error(-8, "hash must be of length 64 (not 4, for '1234')", self.nodes[0].getblockfrompeer, "1234", 0) + self.log.info("Arguments must be valid") + assert_raises_rpc_error(-8, "hash must be of length 64 (not 4, for '1234')", self.nodes[0].getblockfrompeer, "1234", peer_0_peer_1_id) + assert_raises_rpc_error(-3, "JSON value of type number is not of expected type string", self.nodes[0].getblockfrompeer, 1234, peer_0_peer_1_id) + assert_raises_rpc_error(-3, "JSON value of type string is not of expected type number", self.nodes[0].getblockfrompeer, short_tip, "0") self.log.info("We must already have the header") assert_raises_rpc_error(-1, "Block header missing", self.nodes[0].getblockfrompeer, "00" * 32, 0) self.log.info("Non-existent peer generates error") - assert_raises_rpc_error(-1, "Peer does not exist", self.nodes[0].getblockfrompeer, short_tip, peer_0_peer_1_id + 1) + for peer_id in [-1, peer_0_peer_1_id + 1]: + assert_raises_rpc_error(-1, "Peer does not exist", self.nodes[0].getblockfrompeer, short_tip, peer_id) self.log.info("Fetching from pre-segwit peer generates error") self.nodes[0].add_p2p_connection(P2PInterface(), services=P2P_SERVICES & ~NODE_WITNESS) diff --git a/test/functional/rpc_getdescriptorinfo.py b/test/functional/rpc_getdescriptorinfo.py index 5e6fd66aab..1b0f411e52 100755 --- a/test/functional/rpc_getdescriptorinfo.py +++ b/test/functional/rpc_getdescriptorinfo.py @@ -29,7 +29,7 @@ class DescriptorTest(BitcoinTestFramework): def run_test(self): assert_raises_rpc_error(-1, 'getdescriptorinfo', self.nodes[0].getdescriptorinfo) - assert_raises_rpc_error(-3, 'Expected type string', self.nodes[0].getdescriptorinfo, 1) + assert_raises_rpc_error(-3, 'JSON value of type number is not of expected type string', self.nodes[0].getdescriptorinfo, 1) assert_raises_rpc_error(-5, "'' is not a valid descriptor function", self.nodes[0].getdescriptorinfo, "") # P2PK output with the specified public key. diff --git a/test/functional/rpc_help.py b/test/functional/rpc_help.py index 3b6413d4a6..f683577c47 100755 --- a/test/functional/rpc_help.py +++ b/test/functional/rpc_help.py @@ -92,7 +92,7 @@ class HelpRpcTest(BitcoinTestFramework): assert_raises_rpc_error(-1, 'help', node.help, 'foo', 'bar') # invalid argument - assert_raises_rpc_error(-1, 'JSON value is not a string as expected', node.help, 0) + assert_raises_rpc_error(-3, "JSON value of type number is not of expected type string", node.help, 0) # help of unknown command assert_equal(node.help('foo'), 'help: unknown command: foo') diff --git a/test/functional/rpc_invalidateblock.py b/test/functional/rpc_invalidateblock.py index f1c2537ef9..1e33e7ca9c 100755 --- a/test/functional/rpc_invalidateblock.py +++ b/test/functional/rpc_invalidateblock.py @@ -8,6 +8,7 @@ from test_framework.test_framework import BitcoinTestFramework from test_framework.address import ADDRESS_BCRT1_UNSPENDABLE_DESCRIPTOR from test_framework.util import ( assert_equal, + assert_raises_rpc_error, ) @@ -83,6 +84,10 @@ class InvalidateTest(BitcoinTestFramework): # Should be back at the tip by now assert_equal(self.nodes[1].getbestblockhash(), blocks[-1]) + self.log.info("Verify that invalidating an unknown block throws an error") + assert_raises_rpc_error(-5, "Block not found", self.nodes[1].invalidateblock, "00" * 32) + assert_equal(self.nodes[1].getbestblockhash(), blocks[-1]) + if __name__ == '__main__': InvalidateTest().main() diff --git a/test/functional/rpc_mempool_info.py b/test/functional/rpc_mempool_info.py index cd7a48d387..9b5a3b9112 100755 --- a/test/functional/rpc_mempool_info.py +++ b/test/functional/rpc_mempool_info.py @@ -4,7 +4,6 @@ # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test RPCs that retrieve information from the mempool.""" -from test_framework.blocktools import COINBASE_MATURITY from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_equal, @@ -19,7 +18,6 @@ class RPCMempoolInfoTest(BitcoinTestFramework): def run_test(self): self.wallet = MiniWallet(self.nodes[0]) - self.generate(self.wallet, COINBASE_MATURITY + 1) self.wallet.rescan_utxos() confirmed_utxo = self.wallet.get_utxo() diff --git a/test/functional/rpc_packages.py b/test/functional/rpc_packages.py index 63533affd0..9a563cbf5f 100755 --- a/test/functional/rpc_packages.py +++ b/test/functional/rpc_packages.py @@ -10,21 +10,25 @@ import random from test_framework.address import ADDRESS_BCRT1_P2WSH_OP_TRUE from test_framework.test_framework import BitcoinTestFramework from test_framework.messages import ( - BIP125_SEQUENCE_NUMBER, + MAX_BIP125_RBF_SEQUENCE, COIN, CTxInWitness, tx_from_hex, ) +from test_framework.p2p import P2PTxInvStore from test_framework.script import ( CScript, OP_TRUE, ) from test_framework.util import ( assert_equal, + assert_fee_amount, + assert_raises_rpc_error, ) from test_framework.wallet import ( create_child_with_parents, create_raw_chain, + DEFAULT_FEE, make_chain, ) @@ -51,7 +55,7 @@ class RPCPackagesTest(BitcoinTestFramework): self.address = node.get_deterministic_priv_key().address self.coins = [] # The last 100 coinbase transactions are premature - for b in self.generatetoaddress(node, 200, self.address)[:100]: + for b in self.generatetoaddress(node, 220, self.address)[:-100]: coinbase = node.getblock(blockhash=b, verbosity=2)["tx"][0] self.coins.append({ "txid": coinbase["txid"], @@ -82,7 +86,7 @@ class RPCPackagesTest(BitcoinTestFramework): self.test_multiple_parents() self.test_conflicting() self.test_rbf() - + self.test_submitpackage() def test_independent(self): self.log.info("Test multiple independent transactions in a package") @@ -132,8 +136,7 @@ class RPCPackagesTest(BitcoinTestFramework): def test_chain(self): node = self.nodes[0] - first_coin = self.coins.pop() - (chain_hex, chain_txns) = create_raw_chain(node, first_coin, self.address, self.privkeys) + (chain_hex, chain_txns) = create_raw_chain(node, self.coins.pop(), self.address, self.privkeys) self.log.info("Check that testmempoolaccept requires packages to be sorted by dependency") assert_equal(node.testmempoolaccept(rawtxs=chain_hex[::-1]), [{"txid": tx.rehash(), "wtxid": tx.getwtxid(), "package-error": "package-not-sorted"} for tx in chain_txns[::-1]]) @@ -270,7 +273,7 @@ class RPCPackagesTest(BitcoinTestFramework): def test_rbf(self): node = self.nodes[0] coin = self.coins.pop() - inputs = [{"txid": coin["txid"], "vout": 0, "sequence": BIP125_SEQUENCE_NUMBER}] + inputs = [{"txid": coin["txid"], "vout": 0, "sequence": MAX_BIP125_RBF_SEQUENCE}] fee = Decimal('0.00125000') output = {node.get_deterministic_priv_key().address: 50 - fee} raw_replaceable_tx = node.createrawtransaction(inputs, output) @@ -306,5 +309,127 @@ class RPCPackagesTest(BitcoinTestFramework): }] self.assert_testres_equal(self.independent_txns_hex + [signed_replacement_tx["hex"]], testres_rbf_package) + def assert_equal_package_results(self, node, testmempoolaccept_result, submitpackage_result): + """Assert that a successful submitpackage result is consistent with testmempoolaccept + results and getmempoolentry info. Note that the result structs are different and, due to + policy differences between testmempoolaccept and submitpackage (i.e. package feerate), + some information may be different. + """ + for testres_tx in testmempoolaccept_result: + # Grab this result from the submitpackage_result + submitres_tx = submitpackage_result["tx-results"][testres_tx["wtxid"]] + assert_equal(submitres_tx["txid"], testres_tx["txid"]) + # No "allowed" if the tx was already in the mempool + if "allowed" in testres_tx and testres_tx["allowed"]: + assert_equal(submitres_tx["vsize"], testres_tx["vsize"]) + assert_equal(submitres_tx["fees"]["base"], testres_tx["fees"]["base"]) + entry_info = node.getmempoolentry(submitres_tx["txid"]) + assert_equal(submitres_tx["vsize"], entry_info["vsize"]) + assert_equal(submitres_tx["fees"]["base"], entry_info["fees"]["base"]) + + def test_submit_child_with_parents(self, num_parents, partial_submit): + node = self.nodes[0] + peer = node.add_p2p_connection(P2PTxInvStore()) + # Test a package with num_parents parents and 1 child transaction. + package_hex = [] + package_txns = [] + values = [] + scripts = [] + for _ in range(num_parents): + parent_coin = self.coins.pop() + value = parent_coin["amount"] + (tx, txhex, value, spk) = make_chain(node, self.address, self.privkeys, parent_coin["txid"], value) + package_hex.append(txhex) + package_txns.append(tx) + values.append(value) + scripts.append(spk) + if partial_submit and random.choice([True, False]): + node.sendrawtransaction(txhex) + child_hex = create_child_with_parents(node, self.address, self.privkeys, package_txns, values, scripts) + package_hex.append(child_hex) + package_txns.append(tx_from_hex(child_hex)) + + testmempoolaccept_result = node.testmempoolaccept(rawtxs=package_hex) + submitpackage_result = node.submitpackage(package=package_hex) + + # Check that each result is present, with the correct size and fees + for i in range(num_parents + 1): + tx = package_txns[i] + wtxid = tx.getwtxid() + assert wtxid in submitpackage_result["tx-results"] + tx_result = submitpackage_result["tx-results"][wtxid] + assert_equal(tx_result, { + "txid": tx.rehash(), + "vsize": tx.get_vsize(), + "fees": { + "base": DEFAULT_FEE, + } + }) + + # submitpackage result should be consistent with testmempoolaccept and getmempoolentry + self.assert_equal_package_results(node, testmempoolaccept_result, submitpackage_result) + + # Package feerate is calculated for the remaining transactions after deduplication and + # individual submission. If only 0 or 1 transaction is left, e.g. because all transactions + # had high-feerates or were already in the mempool, no package feerate is provided. + # In this case, since all of the parents have high fees, each is accepted individually. + assert "package-feerate" not in submitpackage_result + + # The node should announce each transaction. No guarantees for propagation. + peer.wait_for_broadcast([tx.getwtxid() for tx in package_txns]) + self.generate(node, 1) + + + def test_submit_cpfp(self): + node = self.nodes[0] + peer = node.add_p2p_connection(P2PTxInvStore()) + + # 2 parent 1 child CPFP. First parent pays high fees, second parent pays 0 fees and is + # fee-bumped by the child. + coin_rich = self.coins.pop() + coin_poor = self.coins.pop() + tx_rich, hex_rich, value_rich, spk_rich = make_chain(node, self.address, self.privkeys, coin_rich["txid"], coin_rich["amount"]) + tx_poor, hex_poor, value_poor, spk_poor = make_chain(node, self.address, self.privkeys, coin_poor["txid"], coin_poor["amount"], fee=0) + package_txns = [tx_rich, tx_poor] + hex_child = create_child_with_parents(node, self.address, self.privkeys, package_txns, [value_rich, value_poor], [spk_rich, spk_poor]) + tx_child = tx_from_hex(hex_child) + package_txns.append(tx_child) + + submitpackage_result = node.submitpackage([hex_rich, hex_poor, hex_child]) + + rich_parent_result = submitpackage_result["tx-results"][tx_rich.getwtxid()] + poor_parent_result = submitpackage_result["tx-results"][tx_poor.getwtxid()] + child_result = submitpackage_result["tx-results"][tx_child.getwtxid()] + assert_equal(rich_parent_result["fees"]["base"], DEFAULT_FEE) + assert_equal(poor_parent_result["fees"]["base"], 0) + assert_equal(child_result["fees"]["base"], DEFAULT_FEE) + # Package feerate is calculated for the remaining transactions after deduplication and + # individual submission. Since this package had a 0-fee parent, package feerate must have + # been used and returned. + assert "package-feerate" in submitpackage_result + assert_fee_amount(DEFAULT_FEE, rich_parent_result["vsize"] + child_result["vsize"], submitpackage_result["package-feerate"]) + + # The node will broadcast each transaction, still abiding by its peer's fee filter + peer.wait_for_broadcast([tx.getwtxid() for tx in package_txns]) + self.generate(node, 1) + + + def test_submitpackage(self): + node = self.nodes[0] + + self.log.info("Submitpackage valid packages with 1 child and some number of parents") + for num_parents in [1, 2, 24]: + self.test_submit_child_with_parents(num_parents, False) + self.test_submit_child_with_parents(num_parents, True) + + self.log.info("Submitpackage valid packages with CPFP") + self.test_submit_cpfp() + + self.log.info("Submitpackage only allows packages of 1 child with its parents") + # Chain of 3 transactions has too many generations + chain_hex, _ = create_raw_chain(node, self.coins.pop(), self.address, self.privkeys, 3) + assert_raises_rpc_error(-25, "not-child-with-parents", node.submitpackage, chain_hex) + + if __name__ == "__main__": RPCPackagesTest().main() diff --git a/test/functional/rpc_psbt.py b/test/functional/rpc_psbt.py index 444e56610e..4583ca25cf 100755 --- a/test/functional/rpc_psbt.py +++ b/test/functional/rpc_psbt.py @@ -9,10 +9,24 @@ from decimal import Decimal from itertools import product from test_framework.descriptors import descsum_create -from test_framework.key import ECKey +from test_framework.key import ECKey, H_POINT from test_framework.messages import ( - ser_compact_size, + COutPoint, + CTransaction, + CTxIn, + CTxOut, + MAX_BIP125_RBF_SEQUENCE, WITNESS_SCALE_FACTOR, + ser_compact_size, +) +from test_framework.psbt import ( + PSBT, + PSBTMap, + PSBT_GLOBAL_UNSIGNED_TX, + PSBT_IN_RIPEMD160, + PSBT_IN_SHA256, + PSBT_IN_HASH160, + PSBT_IN_HASH256, ) from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( @@ -21,13 +35,14 @@ from test_framework.util import ( assert_greater_than, assert_raises_rpc_error, find_output, + find_vout_for_address, + random_bytes, ) from test_framework.wallet_util import bytes_to_wif import json import os -MAX_BIP125_RBF_SEQUENCE = 0xfffffffd # Create one-input, one-output, no-fee transaction: class PSBTTest(BitcoinTestFramework): @@ -435,6 +450,7 @@ class PSBTTest(BitcoinTestFramework): with open(os.path.join(os.path.dirname(os.path.realpath(__file__)), 'data/rpc_psbt.json'), encoding='utf-8') as f: d = json.load(f) invalids = d['invalid'] + invalid_with_msgs = d["invalid_with_msg"] valids = d['valid'] creators = d['creator'] signers = d['signer'] @@ -445,6 +461,9 @@ class PSBTTest(BitcoinTestFramework): # Invalid PSBTs for invalid in invalids: assert_raises_rpc_error(-22, "TX decode failed", self.nodes[0].decodepsbt, invalid) + for invalid in invalid_with_msgs: + psbt, msg = invalid + assert_raises_rpc_error(-22, f"TX decode failed {msg}", self.nodes[0].decodepsbt, psbt) # Valid PSBTs for valid in valids: @@ -452,7 +471,7 @@ class PSBTTest(BitcoinTestFramework): # Creator Tests for creator in creators: - created_tx = self.nodes[0].createpsbt(creator['inputs'], creator['outputs']) + created_tx = self.nodes[0].createpsbt(inputs=creator['inputs'], outputs=creator['outputs'], replaceable=False) assert_equal(created_tx, creator['result']) # Signer tests @@ -723,5 +742,98 @@ class PSBTTest(BitcoinTestFramework): ) assert_equal(psbt2["fee"], psbt3["fee"]) + self.log.info("Test signing inputs that the wallet has keys for but is not watching the scripts") + self.nodes[1].createwallet(wallet_name="scriptwatchonly", disable_private_keys=True) + watchonly = self.nodes[1].get_wallet_rpc("scriptwatchonly") + + eckey = ECKey() + eckey.generate() + privkey = bytes_to_wif(eckey.get_bytes()) + + desc = descsum_create("wsh(pkh({}))".format(eckey.get_pubkey().get_bytes().hex())) + if self.options.descriptors: + res = watchonly.importdescriptors([{"desc": desc, "timestamp": "now"}]) + else: + res = watchonly.importmulti([{"desc": desc, "timestamp": "now"}]) + assert res[0]["success"] + addr = self.nodes[0].deriveaddresses(desc)[0] + self.nodes[0].sendtoaddress(addr, 10) + self.generate(self.nodes[0], 1) + self.nodes[0].importprivkey(privkey) + + psbt = watchonly.sendall([wallet.getnewaddress()])["psbt"] + psbt = self.nodes[0].walletprocesspsbt(psbt)["psbt"] + self.nodes[0].sendrawtransaction(self.nodes[0].finalizepsbt(psbt)["hex"]) + + # Same test but for taproot + if self.options.descriptors: + eckey = ECKey() + eckey.generate() + privkey = bytes_to_wif(eckey.get_bytes()) + + desc = descsum_create("tr({},pk({}))".format(H_POINT, eckey.get_pubkey().get_bytes().hex())) + res = watchonly.importdescriptors([{"desc": desc, "timestamp": "now"}]) + assert res[0]["success"] + addr = self.nodes[0].deriveaddresses(desc)[0] + self.nodes[0].sendtoaddress(addr, 10) + self.generate(self.nodes[0], 1) + self.nodes[0].importdescriptors([{"desc": descsum_create("tr({})".format(privkey)), "timestamp":"now"}]) + + psbt = watchonly.sendall([wallet.getnewaddress()])["psbt"] + psbt = self.nodes[0].walletprocesspsbt(psbt)["psbt"] + self.nodes[0].sendrawtransaction(self.nodes[0].finalizepsbt(psbt)["hex"]) + + self.log.info("Test that walletprocesspsbt both updates and signs a non-updated psbt containing Taproot inputs") + addr = self.nodes[0].getnewaddress("", "bech32m") + txid = self.nodes[0].sendtoaddress(addr, 1) + vout = find_vout_for_address(self.nodes[0], txid, addr) + psbt = self.nodes[0].createpsbt([{"txid": txid, "vout": vout}], [{self.nodes[0].getnewaddress(): 0.9999}]) + signed = self.nodes[0].walletprocesspsbt(psbt) + rawtx = self.nodes[0].finalizepsbt(signed["psbt"])["hex"] + self.nodes[0].sendrawtransaction(rawtx) + self.generate(self.nodes[0], 1) + + self.log.info("Test decoding PSBT with per-input preimage types") + # note that the decodepsbt RPC doesn't check whether preimages and hashes match + hash_ripemd160, preimage_ripemd160 = random_bytes(20), random_bytes(50) + hash_sha256, preimage_sha256 = random_bytes(32), random_bytes(50) + hash_hash160, preimage_hash160 = random_bytes(20), random_bytes(50) + hash_hash256, preimage_hash256 = random_bytes(32), random_bytes(50) + + tx = CTransaction() + tx.vin = [CTxIn(outpoint=COutPoint(hash=int('aa' * 32, 16), n=0), scriptSig=b""), + CTxIn(outpoint=COutPoint(hash=int('bb' * 32, 16), n=0), scriptSig=b""), + CTxIn(outpoint=COutPoint(hash=int('cc' * 32, 16), n=0), scriptSig=b""), + CTxIn(outpoint=COutPoint(hash=int('dd' * 32, 16), n=0), scriptSig=b"")] + tx.vout = [CTxOut(nValue=0, scriptPubKey=b"")] + psbt = PSBT() + psbt.g = PSBTMap({PSBT_GLOBAL_UNSIGNED_TX: tx.serialize()}) + psbt.i = [PSBTMap({bytes([PSBT_IN_RIPEMD160]) + hash_ripemd160: preimage_ripemd160}), + PSBTMap({bytes([PSBT_IN_SHA256]) + hash_sha256: preimage_sha256}), + PSBTMap({bytes([PSBT_IN_HASH160]) + hash_hash160: preimage_hash160}), + PSBTMap({bytes([PSBT_IN_HASH256]) + hash_hash256: preimage_hash256})] + psbt.o = [PSBTMap()] + res_inputs = self.nodes[0].decodepsbt(psbt.to_base64())["inputs"] + assert_equal(len(res_inputs), 4) + preimage_keys = ["ripemd160_preimages", "sha256_preimages", "hash160_preimages", "hash256_preimages"] + expected_hashes = [hash_ripemd160, hash_sha256, hash_hash160, hash_hash256] + expected_preimages = [preimage_ripemd160, preimage_sha256, preimage_hash160, preimage_hash256] + for res_input, preimage_key, hash, preimage in zip(res_inputs, preimage_keys, expected_hashes, expected_preimages): + assert preimage_key in res_input + assert_equal(len(res_input[preimage_key]), 1) + assert hash.hex() in res_input[preimage_key] + assert_equal(res_input[preimage_key][hash.hex()], preimage.hex()) + + self.log.info("Test that combining PSBTs with different transactions fails") + tx = CTransaction() + tx.vin = [CTxIn(outpoint=COutPoint(hash=int('aa' * 32, 16), n=0), scriptSig=b"")] + tx.vout = [CTxOut(nValue=0, scriptPubKey=b"")] + psbt1 = PSBT(g=PSBTMap({PSBT_GLOBAL_UNSIGNED_TX: tx.serialize()}), i=[PSBTMap()], o=[PSBTMap()]).to_base64() + tx.vout[0].nValue += 1 # slightly modify tx + psbt2 = PSBT(g=PSBTMap({PSBT_GLOBAL_UNSIGNED_TX: tx.serialize()}), i=[PSBTMap()], o=[PSBTMap()]).to_base64() + assert_raises_rpc_error(-8, "PSBTs not compatible (different transactions)", self.nodes[0].combinepsbt, [psbt1, psbt2]) + assert_equal(self.nodes[0].combinepsbt([psbt1, psbt1]), psbt1) + + if __name__ == '__main__': PSBTTest().main() diff --git a/test/functional/rpc_rawtransaction.py b/test/functional/rpc_rawtransaction.py index 1f95814e18..930aaaa897 100755 --- a/test/functional/rpc_rawtransaction.py +++ b/test/functional/rpc_rawtransaction.py @@ -17,7 +17,7 @@ from decimal import Decimal from test_framework.blocktools import COINBASE_MATURITY from test_framework.messages import ( - BIP125_SEQUENCE_NUMBER, + MAX_BIP125_RBF_SEQUENCE, CTransaction, tx_from_hex, ) @@ -124,13 +124,13 @@ class RawTransactionsTest(BitcoinTestFramework): # 6. invalid parameters - supply txid and invalid boolean values (strings) for verbose for value in ["True", "False"]: - assert_raises_rpc_error(-1, "not a boolean", self.nodes[n].getrawtransaction, txid=txId, verbose=value) + assert_raises_rpc_error(-3, "not of expected type bool", self.nodes[n].getrawtransaction, txid=txId, verbose=value) # 7. invalid parameters - supply txid and empty array - assert_raises_rpc_error(-1, "not a boolean", self.nodes[n].getrawtransaction, txId, []) + assert_raises_rpc_error(-3, "not of expected type bool", self.nodes[n].getrawtransaction, txId, []) # 8. invalid parameters - supply txid and empty dict - assert_raises_rpc_error(-1, "not a boolean", self.nodes[n].getrawtransaction, txId, {}) + assert_raises_rpc_error(-3, "not of expected type bool", self.nodes[n].getrawtransaction, txId, {}) # Make a tx by sending, then generate 2 blocks; block1 has the tx in it tx = self.wallet.send_self_transfer(from_node=self.nodes[2])['txid'] @@ -152,7 +152,7 @@ class RawTransactionsTest(BitcoinTestFramework): # We should not get the tx if we provide an unrelated block assert_raises_rpc_error(-5, "No such transaction found", self.nodes[n].getrawtransaction, txid=tx, blockhash=block2) # An invalid block hash should raise the correct errors - assert_raises_rpc_error(-1, "JSON value is not a string as expected", self.nodes[n].getrawtransaction, txid=tx, blockhash=True) + assert_raises_rpc_error(-3, "JSON value of type bool is not of expected type string", self.nodes[n].getrawtransaction, txid=tx, blockhash=True) assert_raises_rpc_error(-8, "parameter 3 must be of length 64 (not 6, for 'foobar')", self.nodes[n].getrawtransaction, txid=tx, blockhash="foobar") assert_raises_rpc_error(-8, "parameter 3 must be of length 64 (not 8, for 'abcd1234')", self.nodes[n].getrawtransaction, txid=tx, blockhash="abcd1234") foo = "ZZZ0000000000000000000000000000000000000000000000000000000000000" @@ -180,9 +180,9 @@ class RawTransactionsTest(BitcoinTestFramework): assert_raises_rpc_error(-1, "createrawtransaction", self.nodes[0].createrawtransaction, [], {}, 0, False, 'foo') # Test `createrawtransaction` invalid `inputs` - assert_raises_rpc_error(-3, "Expected type array", self.nodes[0].createrawtransaction, 'foo', {}) - assert_raises_rpc_error(-1, "JSON value is not an object as expected", self.nodes[0].createrawtransaction, ['foo'], {}) - assert_raises_rpc_error(-1, "JSON value is not a string as expected", self.nodes[0].createrawtransaction, [{}], {}) + assert_raises_rpc_error(-3, "JSON value of type string is not of expected type array", self.nodes[0].createrawtransaction, 'foo', {}) + assert_raises_rpc_error(-3, "JSON value of type string is not of expected type object", self.nodes[0].createrawtransaction, ['foo'], {}) + assert_raises_rpc_error(-3, "JSON value of type null is not of expected type string", self.nodes[0].createrawtransaction, [{}], {}) assert_raises_rpc_error(-8, "txid must be of length 64 (not 3, for 'foo')", self.nodes[0].createrawtransaction, [{'txid': 'foo'}], {}) txid = "ZZZ7bb8b1697ea987f3b223ba7819250cae33efacb068d23dc24859824a77844" assert_raises_rpc_error(-8, f"txid must be hexadecimal string (not '{txid}')", self.nodes[0].createrawtransaction, [{'txid': txid}], {}) @@ -207,7 +207,7 @@ class RawTransactionsTest(BitcoinTestFramework): # Test `createrawtransaction` invalid `outputs` address = getnewdestination()[2] - assert_raises_rpc_error(-1, "JSON value is not an array as expected", self.nodes[0].createrawtransaction, [], 'foo') + assert_raises_rpc_error(-3, "JSON value of type string is not of expected type array", self.nodes[0].createrawtransaction, [], 'foo') self.nodes[0].createrawtransaction(inputs=[], outputs={}) # Should not throw for backwards compatibility self.nodes[0].createrawtransaction(inputs=[], outputs=[]) assert_raises_rpc_error(-8, "Data must be hexadecimal string", self.nodes[0].createrawtransaction, [], {'data': 'foo'}) @@ -223,15 +223,15 @@ class RawTransactionsTest(BitcoinTestFramework): # Test `createrawtransaction` mismatch between sequence number(s) and `replaceable` option assert_raises_rpc_error(-8, "Invalid parameter combination: Sequence number(s) contradict replaceable option", - self.nodes[0].createrawtransaction, [{'txid': TXID, 'vout': 0, 'sequence': BIP125_SEQUENCE_NUMBER+1}], {}, 0, True) + self.nodes[0].createrawtransaction, [{'txid': TXID, 'vout': 0, 'sequence': MAX_BIP125_RBF_SEQUENCE+1}], {}, 0, True) # Test `createrawtransaction` invalid `locktime` - assert_raises_rpc_error(-3, "Expected type number", self.nodes[0].createrawtransaction, [], {}, 'foo') + assert_raises_rpc_error(-3, "JSON value of type string is not of expected type number", self.nodes[0].createrawtransaction, [], {}, 'foo') assert_raises_rpc_error(-8, "Invalid parameter, locktime out of range", self.nodes[0].createrawtransaction, [], {}, -1) assert_raises_rpc_error(-8, "Invalid parameter, locktime out of range", self.nodes[0].createrawtransaction, [], {}, 4294967296) # Test `createrawtransaction` invalid `replaceable` - assert_raises_rpc_error(-3, "Expected type bool", self.nodes[0].createrawtransaction, [], {}, 0, 'foo') + assert_raises_rpc_error(-3, "JSON value of type string is not of expected type bool", self.nodes[0].createrawtransaction, [], {}, 0, 'foo') # Test that createrawtransaction accepts an array and object as outputs # One output @@ -285,7 +285,7 @@ class RawTransactionsTest(BitcoinTestFramework): # Test a transaction with a large fee. # Fee rate is 0.20000000 BTC/kvB - tx = self.wallet.create_self_transfer(mempool_valid=False, from_node=self.nodes[0], fee_rate=Decimal('0.20000000')) + tx = self.wallet.create_self_transfer(fee_rate=Decimal("0.20000000")) # Thus, testmempoolaccept should reject testres = self.nodes[2].testmempoolaccept([tx['hex']])[0] assert_equal(testres['allowed'], False) diff --git a/test/functional/rpc_signer.py b/test/functional/rpc_signer.py index f1107197c5..de17b2b929 100755 --- a/test/functional/rpc_signer.py +++ b/test/functional/rpc_signer.py @@ -77,10 +77,7 @@ class RPCSignerTest(BitcoinTestFramework): ) self.clear_mock_result(self.nodes[1]) - result = self.nodes[1].enumeratesigners() - assert_equal(len(result['signers']), 2) - assert_equal(result['signers'][0]["fingerprint"], "00000001") - assert_equal(result['signers'][0]["name"], "trezor_t") + assert_equal({'fingerprint': '00000001', 'name': 'trezor_t'} in self.nodes[1].enumeratesigners()['signers'], True) if __name__ == '__main__': RPCSignerTest().main() diff --git a/test/functional/rpc_signmessagewithprivkey.py b/test/functional/rpc_signmessagewithprivkey.py index 80555eab75..6635da150f 100755 --- a/test/functional/rpc_signmessagewithprivkey.py +++ b/test/functional/rpc_signmessagewithprivkey.py @@ -4,27 +4,44 @@ # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test RPC commands for signing messages with private key.""" +from test_framework.descriptors import ( + descsum_create, +) from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_equal, assert_raises_rpc_error, ) + class SignMessagesWithPrivTest(BitcoinTestFramework): def set_test_params(self): self.setup_clean_chain = True self.num_nodes = 1 + def addresses_from_privkey(self, priv_key): + '''Return addresses for a given WIF private key in legacy (P2PKH), + nested segwit (P2SH-P2WPKH) and native segwit (P2WPKH) formats.''' + descriptors = f'pkh({priv_key})', f'sh(wpkh({priv_key}))', f'wpkh({priv_key})' + return [self.nodes[0].deriveaddresses(descsum_create(desc))[0] for desc in descriptors] + def run_test(self): message = 'This is just a test message' self.log.info('test signing with priv_key') priv_key = 'cUeKHd5orzT3mz8P9pxyREHfsWtVfgsfDjiZZBcjUBAaGk1BTj7N' - address = 'mpLQjfK79b7CCV4VMJWEWAj5Mpx8Up5zxB' expected_signature = 'INbVnW4e6PeRmsv2Qgu8NuopvrVjkcxob+sX8OcZG0SALhWybUjzMLPdAsXI46YZGb0KQTRii+wWIQzRpG/U+S0=' signature = self.nodes[0].signmessagewithprivkey(priv_key, message) assert_equal(expected_signature, signature) - assert self.nodes[0].verifymessage(address, signature, message) + + self.log.info('test that verifying with P2PKH address succeeds') + addresses = self.addresses_from_privkey(priv_key) + assert_equal(addresses[0], 'mpLQjfK79b7CCV4VMJWEWAj5Mpx8Up5zxB') + assert self.nodes[0].verifymessage(addresses[0], signature, message) + + self.log.info('test that verifying with non-P2PKH addresses throws error') + for non_p2pkh_address in addresses[1:]: + assert_raises_rpc_error(-3, "Address does not refer to key", self.nodes[0].verifymessage, non_p2pkh_address, signature, message) self.log.info('test parameter validity and error codes') # signmessagewithprivkey has two required parameters @@ -41,5 +58,6 @@ class SignMessagesWithPrivTest(BitcoinTestFramework): # malformed signature provided assert_raises_rpc_error(-3, "Malformed base64 encoding", self.nodes[0].verifymessage, 'mpLQjfK79b7CCV4VMJWEWAj5Mpx8Up5zxB', "invalid_sig", message) + if __name__ == '__main__': SignMessagesWithPrivTest().main() diff --git a/test/functional/rpc_signrawtransactionwithkey.py b/test/functional/rpc_signrawtransactionwithkey.py new file mode 100755 index 0000000000..0da5a99fdb --- /dev/null +++ b/test/functional/rpc_signrawtransactionwithkey.py @@ -0,0 +1,140 @@ +#!/usr/bin/env python3 +# Copyright (c) 2015-2022 The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. +"""Test transaction signing using the signrawtransactionwithkey RPC.""" + +from test_framework.blocktools import ( + COINBASE_MATURITY, +) +from test_framework.address import ( + script_to_p2sh, +) +from test_framework.key import ECKey +from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import ( + assert_equal, + find_vout_for_address, +) +from test_framework.script_util import ( + key_to_p2pk_script, + key_to_p2pkh_script, + script_to_p2sh_p2wsh_script, + script_to_p2wsh_script, +) +from test_framework.wallet_util import ( + bytes_to_wif, +) + +from decimal import ( + Decimal, +) +from test_framework.wallet import ( + getnewdestination, +) + + +class SignRawTransactionWithKeyTest(BitcoinTestFramework): + def set_test_params(self): + self.setup_clean_chain = True + self.num_nodes = 2 + + def send_to_address(self, addr, amount): + input = {"txid": self.nodes[0].getblock(self.block_hash[self.blk_idx])["tx"][0], "vout": 0} + output = {addr: amount} + self.blk_idx += 1 + rawtx = self.nodes[0].createrawtransaction([input], output) + txid = self.nodes[0].sendrawtransaction(self.nodes[0].signrawtransactionwithkey(rawtx, [self.nodes[0].get_deterministic_priv_key().key])["hex"], 0) + return txid + + def successful_signing_test(self): + """Create and sign a valid raw transaction with one input. + + Expected results: + + 1) The transaction has a complete set of signatures + 2) No script verification error occurred""" + self.log.info("Test valid raw transaction with one input") + privKeys = ['cUeKHd5orzT3mz8P9pxyREHfsWtVfgsfDjiZZBcjUBAaGk1BTj7N', 'cVKpPfVKSJxKqVpE9awvXNWuLHCa5j5tiE7K6zbUSptFpTEtiFrA'] + + inputs = [ + # Valid pay-to-pubkey scripts + {'txid': '9b907ef1e3c26fc71fe4a4b3580bc75264112f95050014157059c736f0202e71', 'vout': 0, + 'scriptPubKey': '76a91460baa0f494b38ce3c940dea67f3804dc52d1fb9488ac'}, + {'txid': '83a4f6a6b73660e13ee6cb3c6063fa3759c50c9b7521d0536022961898f4fb02', 'vout': 0, + 'scriptPubKey': '76a914669b857c03a5ed269d5d85a1ffac9ed5d663072788ac'}, + ] + + outputs = {'mpLQjfK79b7CCV4VMJWEWAj5Mpx8Up5zxB': 0.1} + + rawTx = self.nodes[0].createrawtransaction(inputs, outputs) + rawTxSigned = self.nodes[0].signrawtransactionwithkey(rawTx, privKeys, inputs) + + # 1) The transaction has a complete set of signatures + assert rawTxSigned['complete'] + + # 2) No script verification error occurred + assert 'errors' not in rawTxSigned + + def witness_script_test(self): + self.log.info("Test signing transaction to P2SH-P2WSH addresses without wallet") + # Create a new P2SH-P2WSH 1-of-1 multisig address: + eckey = ECKey() + eckey.generate() + embedded_privkey = bytes_to_wif(eckey.get_bytes()) + embedded_pubkey = eckey.get_pubkey().get_bytes().hex() + p2sh_p2wsh_address = self.nodes[1].createmultisig(1, [embedded_pubkey], "p2sh-segwit") + # send transaction to P2SH-P2WSH 1-of-1 multisig address + self.block_hash = self.generate(self.nodes[0], COINBASE_MATURITY + 1) + self.blk_idx = 0 + self.send_to_address(p2sh_p2wsh_address["address"], 49.999) + self.generate(self.nodes[0], 1) + # Get the UTXO info from scantxoutset + unspent_output = self.nodes[1].scantxoutset('start', [p2sh_p2wsh_address['descriptor']])['unspents'][0] + spk = script_to_p2sh_p2wsh_script(p2sh_p2wsh_address['redeemScript']).hex() + unspent_output['witnessScript'] = p2sh_p2wsh_address['redeemScript'] + unspent_output['redeemScript'] = script_to_p2wsh_script(unspent_output['witnessScript']).hex() + assert_equal(spk, unspent_output['scriptPubKey']) + # Now create and sign a transaction spending that output on node[0], which doesn't know the scripts or keys + spending_tx = self.nodes[0].createrawtransaction([unspent_output], {getnewdestination()[2]: Decimal("49.998")}) + spending_tx_signed = self.nodes[0].signrawtransactionwithkey(spending_tx, [embedded_privkey], [unspent_output]) + # Check the signing completed successfully + assert 'complete' in spending_tx_signed + assert_equal(spending_tx_signed['complete'], True) + + # Now test with P2PKH and P2PK scripts as the witnessScript + for tx_type in ['P2PKH', 'P2PK']: # these tests are order-independent + self.verify_txn_with_witness_script(tx_type) + + def verify_txn_with_witness_script(self, tx_type): + self.log.info("Test with a {} script as the witnessScript".format(tx_type)) + eckey = ECKey() + eckey.generate() + embedded_privkey = bytes_to_wif(eckey.get_bytes()) + embedded_pubkey = eckey.get_pubkey().get_bytes().hex() + witness_script = { + 'P2PKH': key_to_p2pkh_script(embedded_pubkey).hex(), + 'P2PK': key_to_p2pk_script(embedded_pubkey).hex() + }.get(tx_type, "Invalid tx_type") + redeem_script = script_to_p2wsh_script(witness_script).hex() + addr = script_to_p2sh(redeem_script) + script_pub_key = self.nodes[1].validateaddress(addr)['scriptPubKey'] + # Fund that address + txid = self.send_to_address(addr, 10) + vout = find_vout_for_address(self.nodes[0], txid, addr) + self.generate(self.nodes[0], 1) + # Now create and sign a transaction spending that output on node[0], which doesn't know the scripts or keys + spending_tx = self.nodes[0].createrawtransaction([{'txid': txid, 'vout': vout}], {getnewdestination()[2]: Decimal("9.999")}) + spending_tx_signed = self.nodes[0].signrawtransactionwithkey(spending_tx, [embedded_privkey], [{'txid': txid, 'vout': vout, 'scriptPubKey': script_pub_key, 'redeemScript': redeem_script, 'witnessScript': witness_script, 'amount': 10}]) + # Check the signing completed successfully + assert 'complete' in spending_tx_signed + assert_equal(spending_tx_signed['complete'], True) + self.nodes[0].sendrawtransaction(spending_tx_signed['hex']) + + def run_test(self): + self.successful_signing_test() + self.witness_script_test() + + +if __name__ == '__main__': + SignRawTransactionWithKeyTest().main() diff --git a/test/functional/test_framework/address.py b/test/functional/test_framework/address.py index fcea24655b..92244b5ed8 100644 --- a/test/functional/test_framework/address.py +++ b/test/functional/test_framework/address.py @@ -47,8 +47,7 @@ def create_deterministic_address_bcrt1_p2tr_op_true(): Returns a tuple with the generated address and the internal key. """ internal_key = (1).to_bytes(32, 'big') - scriptPubKey = taproot_construct(internal_key, [(None, CScript([OP_TRUE]))]).scriptPubKey - address = encode_segwit_address("bcrt", 1, scriptPubKey[2:]) + address = output_key_to_p2tr(taproot_construct(internal_key, [(None, CScript([OP_TRUE]))]).output_pubkey) assert_equal(address, 'bcrt1p9yfmy5h72durp7zrhlw9lf7jpwjgvwdg0jr0lqmmjtgg83266lqsekaqka') return (address, internal_key) @@ -141,6 +140,10 @@ def script_to_p2sh_p2wsh(script, main=False): p2shscript = CScript([OP_0, sha256(script)]) return script_to_p2sh(p2shscript, main) +def output_key_to_p2tr(key, main=False): + assert len(key) == 32 + return program_to_witness(1, key, main) + def check_key(key): if (type(key) is str): key = bytes.fromhex(key) # Assuming this is hex string diff --git a/test/functional/test_framework/blocktools.py b/test/functional/test_framework/blocktools.py index 40fcbf7761..574ea10356 100644 --- a/test/functional/test_framework/blocktools.py +++ b/test/functional/test_framework/blocktools.py @@ -162,30 +162,6 @@ def create_tx_with_script(prevtx, n, script_sig=b"", *, amount, script_pub_key=C tx.calc_sha256() return tx -def create_transaction(node, txid, to_address, *, amount): - """ Return signed transaction spending the first output of the - input txid. Note that the node must have a wallet that can - sign for the output that is being spent. - """ - raw_tx = create_raw_transaction(node, txid, to_address, amount=amount) - tx = tx_from_hex(raw_tx) - return tx - -def create_raw_transaction(node, txid, to_address, *, amount): - """ Return raw signed transaction spending the first output of the - input txid. Note that the node must have a wallet that can sign - for the output that is being spent. - """ - psbt = node.createpsbt(inputs=[{"txid": txid, "vout": 0}], outputs={to_address: amount}) - for _ in range(2): - for w in node.listwallets(): - wrpc = node.get_wallet_rpc(w) - signed_psbt = wrpc.walletprocesspsbt(psbt) - psbt = signed_psbt['psbt'] - final_psbt = node.finalizepsbt(psbt) - assert_equal(final_psbt["complete"], True) - return final_psbt['hex'] - def get_legacy_sigopcount_block(block, accurate=True): count = 0 for tx in block.vtx: diff --git a/test/functional/test_framework/key.py b/test/functional/test_framework/key.py index e5dea66963..68afc1383d 100644 --- a/test/functional/test_framework/key.py +++ b/test/functional/test_framework/key.py @@ -15,6 +15,10 @@ import unittest from .util import modinv +# Point with no known discrete log. +H_POINT = "50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0" + + def TaggedHash(tag, data): ss = hashlib.sha256(tag.encode('utf-8')).digest() ss += ss diff --git a/test/functional/test_framework/messages.py b/test/functional/test_framework/messages.py index aae44c0ac0..8a928a1e50 100755 --- a/test/functional/test_framework/messages.py +++ b/test/functional/test_framework/messages.py @@ -39,7 +39,7 @@ MAX_BLOOM_HASH_FUNCS = 50 COIN = 100000000 # 1 btc in satoshis MAX_MONEY = 21000000 * COIN -BIP125_SEQUENCE_NUMBER = 0xfffffffd # Sequence number that is rbf-opt-in (BIP 125) and csv-opt-out (BIP 68) +MAX_BIP125_RBF_SEQUENCE = 0xfffffffd # Sequence number that is rbf-opt-in (BIP 125) and csv-opt-out (BIP 68) SEQUENCE_FINAL = 0xffffffff # Sequence number that disables nLockTime if set for every input of a tx MAX_PROTOCOL_MESSAGE_LENGTH = 4000000 # Maximum length of incoming protocol messages @@ -65,6 +65,13 @@ FILTER_TYPE_BASIC = 0 WITNESS_SCALE_FACTOR = 4 +DEFAULT_ANCESTOR_LIMIT = 25 # default max number of in-mempool ancestors +DEFAULT_DESCENDANT_LIMIT = 25 # default max number of in-mempool descendants + +# Default setting for -datacarriersize. 80 bytes of data, +1 for OP_RETURN, +2 for the pushdata opcodes. +MAX_OP_RETURN_RELAY = 83 + +DEFAULT_MEMPOOL_EXPIRY_HOURS = 336 # hours def sha256(s): return hashlib.sha256(s).digest() @@ -208,6 +215,20 @@ def tx_from_hex(hex_string): return from_hex(CTransaction(), hex_string) +# like from_hex, but without the hex part +def from_binary(cls, stream): + """deserialize a binary stream (or bytes object) into an object""" + # handle bytes object by turning it into a stream + was_bytes = isinstance(stream, bytes) + if was_bytes: + stream = BytesIO(stream) + obj = cls() + obj.deserialize(stream) + if was_bytes: + assert len(stream.read()) == 0 + return obj + + # Objects that map to bitcoind objects, which can be serialized/deserialized diff --git a/test/functional/test_framework/psbt.py b/test/functional/test_framework/psbt.py new file mode 100644 index 0000000000..68945e7e84 --- /dev/null +++ b/test/functional/test_framework/psbt.py @@ -0,0 +1,131 @@ +#!/usr/bin/env python3 +# Copyright (c) 2022 The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. + +import base64 + +from .messages import ( + CTransaction, + deser_string, + from_binary, + ser_compact_size, +) + + +# global types +PSBT_GLOBAL_UNSIGNED_TX = 0x00 +PSBT_GLOBAL_XPUB = 0x01 +PSBT_GLOBAL_TX_VERSION = 0x02 +PSBT_GLOBAL_FALLBACK_LOCKTIME = 0x03 +PSBT_GLOBAL_INPUT_COUNT = 0x04 +PSBT_GLOBAL_OUTPUT_COUNT = 0x05 +PSBT_GLOBAL_TX_MODIFIABLE = 0x06 +PSBT_GLOBAL_VERSION = 0xfb +PSBT_GLOBAL_PROPRIETARY = 0xfc + +# per-input types +PSBT_IN_NON_WITNESS_UTXO = 0x00 +PSBT_IN_WITNESS_UTXO = 0x01 +PSBT_IN_PARTIAL_SIG = 0x02 +PSBT_IN_SIGHASH_TYPE = 0x03 +PSBT_IN_REDEEM_SCRIPT = 0x04 +PSBT_IN_WITNESS_SCRIPT = 0x05 +PSBT_IN_BIP32_DERIVATION = 0x06 +PSBT_IN_FINAL_SCRIPTSIG = 0x07 +PSBT_IN_FINAL_SCRIPTWITNESS = 0x08 +PSBT_IN_POR_COMMITMENT = 0x09 +PSBT_IN_RIPEMD160 = 0x0a +PSBT_IN_SHA256 = 0x0b +PSBT_IN_HASH160 = 0x0c +PSBT_IN_HASH256 = 0x0d +PSBT_IN_PREVIOUS_TXID = 0x0e +PSBT_IN_OUTPUT_INDEX = 0x0f +PSBT_IN_SEQUENCE = 0x10 +PSBT_IN_REQUIRED_TIME_LOCKTIME = 0x11 +PSBT_IN_REQUIRED_HEIGHT_LOCKTIME = 0x12 +PSBT_IN_TAP_KEY_SIG = 0x13 +PSBT_IN_TAP_SCRIPT_SIG = 0x14 +PSBT_IN_TAP_LEAF_SCRIPT = 0x15 +PSBT_IN_TAP_BIP32_DERIVATION = 0x16 +PSBT_IN_TAP_INTERNAL_KEY = 0x17 +PSBT_IN_TAP_MERKLE_ROOT = 0x18 +PSBT_IN_PROPRIETARY = 0xfc + +# per-output types +PSBT_OUT_REDEEM_SCRIPT = 0x00 +PSBT_OUT_WITNESS_SCRIPT = 0x01 +PSBT_OUT_BIP32_DERIVATION = 0x02 +PSBT_OUT_AMOUNT = 0x03 +PSBT_OUT_SCRIPT = 0x04 +PSBT_OUT_TAP_INTERNAL_KEY = 0x05 +PSBT_OUT_TAP_TREE = 0x06 +PSBT_OUT_TAP_BIP32_DERIVATION = 0x07 +PSBT_OUT_PROPRIETARY = 0xfc + + +class PSBTMap: + """Class for serializing and deserializing PSBT maps""" + + def __init__(self, map=None): + self.map = map if map is not None else {} + + def deserialize(self, f): + m = {} + while True: + k = deser_string(f) + if len(k) == 0: + break + v = deser_string(f) + if len(k) == 1: + k = k[0] + assert k not in m + m[k] = v + self.map = m + + def serialize(self): + m = b"" + for k,v in self.map.items(): + if isinstance(k, int) and 0 <= k and k <= 255: + k = bytes([k]) + m += ser_compact_size(len(k)) + k + m += ser_compact_size(len(v)) + v + m += b"\x00" + return m + +class PSBT: + """Class for serializing and deserializing PSBTs""" + + def __init__(self, *, g=None, i=None, o=None): + self.g = g if g is not None else PSBTMap() + self.i = i if i is not None else [] + self.o = o if o is not None else [] + self.tx = None + + def deserialize(self, f): + assert f.read(5) == b"psbt\xff" + self.g = from_binary(PSBTMap, f) + assert 0 in self.g.map + self.tx = from_binary(CTransaction, self.g.map[0]) + self.i = [from_binary(PSBTMap, f) for _ in self.tx.vin] + self.o = [from_binary(PSBTMap, f) for _ in self.tx.vout] + return self + + def serialize(self): + assert isinstance(self.g, PSBTMap) + assert isinstance(self.i, list) and all(isinstance(x, PSBTMap) for x in self.i) + assert isinstance(self.o, list) and all(isinstance(x, PSBTMap) for x in self.o) + assert 0 in self.g.map + tx = from_binary(CTransaction, self.g.map[0]) + assert len(tx.vin) == len(self.i) + assert len(tx.vout) == len(self.o) + + psbt = [x.serialize() for x in [self.g] + self.i + self.o] + return b"psbt\xff" + b"".join(psbt) + + def to_base64(self): + return base64.b64encode(self.serialize()).decode("utf8") + + @classmethod + def from_base64(cls, b64psbt): + return from_binary(cls, base64.b64decode(b64psbt)) diff --git a/test/functional/test_framework/script_util.py b/test/functional/test_framework/script_util.py index f7d8422eee..b114002145 100755 --- a/test/functional/test_framework/script_util.py +++ b/test/functional/test_framework/script_util.py @@ -105,6 +105,11 @@ def script_to_p2sh_p2wsh_script(script): return script_to_p2sh_script(p2shscript) +def output_key_to_p2tr_script(key): + assert len(key) == 32 + return program_to_witness_script(1, key) + + def check_key(key): if isinstance(key, str): key = bytes.fromhex(key) # Assuming this is hex string diff --git a/test/functional/test_framework/test_framework.py b/test/functional/test_framework/test_framework.py index a39ee003ef..b1164b98fd 100755 --- a/test/functional/test_framework/test_framework.py +++ b/test/functional/test_framework/test_framework.py @@ -223,11 +223,11 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass): # It still needs to exist and be None in order for tests to work however. self.options.descriptors = None + PortSeed.n = self.options.port_seed + def setup(self): """Call this method to start up the test framework object with options set.""" - PortSeed.n = self.options.port_seed - check_json_precision() self.options.cachedir = os.path.abspath(self.options.cachedir) @@ -581,6 +581,8 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass): def connect_nodes(self, a, b): from_connection = self.nodes[a] to_connection = self.nodes[b] + from_num_peers = 1 + len(from_connection.getpeerinfo()) + to_num_peers = 1 + len(to_connection.getpeerinfo()) ip_port = "127.0.0.1:" + str(p2p_port(b)) from_connection.addnode(ip_port, "onetry") # poll until version handshake complete to avoid race conditions @@ -588,30 +590,30 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass): # See comments in net_processing: # * Must have a version message before anything else # * Must have a verack message before anything else - wait_until_helper(lambda: all(peer['version'] != 0 for peer in from_connection.getpeerinfo())) - wait_until_helper(lambda: all(peer['version'] != 0 for peer in to_connection.getpeerinfo())) - wait_until_helper(lambda: all(peer['bytesrecv_per_msg'].pop('verack', 0) == 24 for peer in from_connection.getpeerinfo())) - wait_until_helper(lambda: all(peer['bytesrecv_per_msg'].pop('verack', 0) == 24 for peer in to_connection.getpeerinfo())) + self.wait_until(lambda: sum(peer['version'] != 0 for peer in from_connection.getpeerinfo()) == from_num_peers) + self.wait_until(lambda: sum(peer['version'] != 0 for peer in to_connection.getpeerinfo()) == to_num_peers) + self.wait_until(lambda: sum(peer['bytesrecv_per_msg'].pop('verack', 0) == 24 for peer in from_connection.getpeerinfo()) == from_num_peers) + self.wait_until(lambda: sum(peer['bytesrecv_per_msg'].pop('verack', 0) == 24 for peer in to_connection.getpeerinfo()) == to_num_peers) def disconnect_nodes(self, a, b): - def disconnect_nodes_helper(from_connection, node_num): - def get_peer_ids(): + def disconnect_nodes_helper(node_a, node_b): + def get_peer_ids(from_connection, node_num): result = [] for peer in from_connection.getpeerinfo(): if "testnode{}".format(node_num) in peer['subver']: result.append(peer['id']) return result - peer_ids = get_peer_ids() + peer_ids = get_peer_ids(node_a, node_b.index) if not peer_ids: self.log.warning("disconnect_nodes: {} and {} were not connected".format( - from_connection.index, - node_num, + node_a.index, + node_b.index, )) return for peer_id in peer_ids: try: - from_connection.disconnectnode(nodeid=peer_id) + node_a.disconnectnode(nodeid=peer_id) except JSONRPCException as e: # If this node is disconnected between calculating the peer id # and issuing the disconnect, don't worry about it. @@ -620,9 +622,10 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass): raise # wait to disconnect - wait_until_helper(lambda: not get_peer_ids(), timeout=5) + self.wait_until(lambda: not get_peer_ids(node_a, node_b.index), timeout=5) + self.wait_until(lambda: not get_peer_ids(node_b, node_a.index), timeout=5) - disconnect_nodes_helper(self.nodes[a], b) + disconnect_nodes_helper(self.nodes[a], self.nodes[b]) def split_network(self): """ diff --git a/test/functional/test_framework/test_node.py b/test/functional/test_framework/test_node.py index 03f6c8adea..e35cae006f 100755 --- a/test/functional/test_framework/test_node.py +++ b/test/functional/test_framework/test_node.py @@ -118,6 +118,8 @@ class TestNode(): self.args.append("-logthreadnames") if self.version_is_at_least(219900): self.args.append("-logsourcelocations") + if self.version_is_at_least(239000): + self.args.append("-loglevel=trace") self.cli = TestNodeCLI(bitcoin_cli, self.datadir) self.use_cli = use_cli diff --git a/test/functional/test_framework/util.py b/test/functional/test_framework/util.py index b043d1a70d..bfc835f272 100644 --- a/test/functional/test_framework/util.py +++ b/test/functional/test_framework/util.py @@ -12,6 +12,7 @@ import inspect import json import logging import os +import random import re import time import unittest @@ -28,6 +29,10 @@ logger = logging.getLogger("TestFramework.utils") def assert_approx(v, vexp, vspan=0.00001): """Assert that `v` is within `vspan` of `vexp`""" + if isinstance(v, Decimal) or isinstance(vexp, Decimal): + v=Decimal(v) + vexp=Decimal(vexp) + vspan=Decimal(vspan) if v < vexp - vspan: raise AssertionError("%s < [%s..%s]" % (str(v), str(vexp - vspan), str(vexp + vspan))) if v > vexp + vspan: @@ -286,6 +291,13 @@ def sha256sum_file(filename): d = f.read(4096) return h.digest() + +# TODO: Remove and use random.randbytes(n) instead, available in Python 3.9 +def random_bytes(n): + """Return a random bytes object of length n.""" + return bytes(random.getrandbits(8) for i in range(n)) + + # RPC/P2P connection constants and functions ############################################ @@ -476,39 +488,6 @@ def find_output(node, txid, amount, *, blockhash=None): raise RuntimeError("find_output txid %s : %s not found" % (txid, str(amount))) -# Helper to create at least "count" utxos -# Pass in a fee that is sufficient for relay and mining new transactions. -def create_confirmed_utxos(test_framework, fee, node, count, **kwargs): - to_generate = int(0.5 * count) + 101 - while to_generate > 0: - test_framework.generate(node, min(25, to_generate), **kwargs) - to_generate -= 25 - utxos = node.listunspent() - iterations = count - len(utxos) - addr1 = node.getnewaddress() - addr2 = node.getnewaddress() - if iterations <= 0: - return utxos - for _ in range(iterations): - t = utxos.pop() - inputs = [] - inputs.append({"txid": t["txid"], "vout": t["vout"]}) - outputs = {} - send_value = t['amount'] - fee - outputs[addr1] = satoshi_round(send_value / 2) - outputs[addr2] = satoshi_round(send_value / 2) - raw_tx = node.createrawtransaction(inputs, outputs) - signed_tx = node.signrawtransactionwithwallet(raw_tx)["hex"] - node.sendrawtransaction(signed_tx) - - while (node.getmempoolinfo()['size'] > 0): - test_framework.generate(node, 1, **kwargs) - - utxos = node.listunspent() - assert len(utxos) >= count - return utxos - - def chain_transaction(node, parent_txids, vouts, value, fee, num_outputs): """Build and send a transaction that spends the given inputs (specified by lists of parent_txid:vout each), with the desired total value and fee, @@ -532,45 +511,30 @@ def chain_transaction(node, parent_txids, vouts, value, fee, num_outputs): # Create large OP_RETURN txouts that can be appended to a transaction -# to make it large (helper for constructing large transactions). +# to make it large (helper for constructing large transactions). The +# total serialized size of the txouts is about 66k vbytes. def gen_return_txouts(): - # Some pre-processing to create a bunch of OP_RETURN txouts to insert into transactions we create - # So we have big transactions (and therefore can't fit very many into each block) - # create one script_pubkey - script_pubkey = "6a4d0200" # OP_RETURN OP_PUSH2 512 bytes - for _ in range(512): - script_pubkey = script_pubkey + "01" - # concatenate 128 txouts of above script_pubkey which we'll insert before the txout for change - txouts = [] from .messages import CTxOut - txout = CTxOut() - txout.nValue = 0 - txout.scriptPubKey = bytes.fromhex(script_pubkey) - for _ in range(128): - txouts.append(txout) + from .script import CScript, OP_RETURN + txouts = [CTxOut(nValue=0, scriptPubKey=CScript([OP_RETURN, b'\x01'*67437]))] + assert_equal(sum([len(txout.serialize()) for txout in txouts]), 67456) return txouts # Create a spend of each passed-in utxo, splicing in "txouts" to each raw # transaction to make it large. See gen_return_txouts() above. -def create_lots_of_big_transactions(node, txouts, utxos, num, fee): - addr = node.getnewaddress() +def create_lots_of_big_transactions(mini_wallet, node, fee, tx_batch_size, txouts, utxos=None): txids = [] - from .messages import tx_from_hex - for _ in range(num): - t = utxos.pop() - inputs = [{"txid": t["txid"], "vout": t["vout"]}] - outputs = {} - change = t['amount'] - fee - outputs[addr] = satoshi_round(change) - rawtx = node.createrawtransaction(inputs, outputs) - tx = tx_from_hex(rawtx) - for txout in txouts: - tx.vout.append(txout) - newtx = tx.serialize().hex() - signresult = node.signrawtransactionwithwallet(newtx, None, "NONE") - txid = node.sendrawtransaction(signresult["hex"], 0) - txids.append(txid) + use_internal_utxos = utxos is None + for _ in range(tx_batch_size): + tx = mini_wallet.create_self_transfer( + utxo_to_spend=None if use_internal_utxos else utxos.pop(), + fee=fee, + )["tx"] + tx.vout.extend(txouts) + res = node.testmempoolaccept([tx.serialize().hex()])[0] + assert_equal(res['fees']['base'], fee) + txids.append(node.sendrawtransaction(tx.serialize().hex())) return txids @@ -578,13 +542,8 @@ def mine_large_block(test_framework, mini_wallet, node): # generate a 66k transaction, # and 14 of them is close to the 1MB block limit txouts = gen_return_txouts() - from .messages import COIN - fee = 100 * int(node.getnetworkinfo()["relayfee"] * COIN) - for _ in range(14): - tx = mini_wallet.create_self_transfer(from_node=node, fee_rate=0, mempool_valid=False)['tx'] - tx.vout[0].nValue -= fee - tx.vout.extend(txouts) - mini_wallet.sendrawtransaction(from_node=node, tx_hex=tx.serialize().hex()) + fee = 100 * node.getnetworkinfo()["relayfee"] + create_lots_of_big_transactions(mini_wallet, node, fee, 14, txouts) test_framework.generate(node, 1) diff --git a/test/functional/test_framework/wallet.py b/test/functional/test_framework/wallet.py index e43dd9f61a..374fda5c23 100644 --- a/test/functional/test_framework/wallet.py +++ b/test/functional/test_framework/wallet.py @@ -7,7 +7,6 @@ from copy import deepcopy from decimal import Decimal from enum import Enum -from random import choice from typing import ( Any, List, @@ -19,9 +18,13 @@ from test_framework.address import ( key_to_p2pkh, key_to_p2sh_p2wpkh, key_to_p2wpkh, + output_key_to_p2tr, ) from test_framework.descriptors import descsum_create -from test_framework.key import ECKey +from test_framework.key import ( + ECKey, + compute_xonly_pubkey, +) from test_framework.messages import ( COIN, COutPoint, @@ -36,8 +39,10 @@ from test_framework.script import ( LegacySignatureHash, LEAF_VERSION_TAPSCRIPT, OP_NOP, + OP_RETURN, OP_TRUE, SIGHASH_ALL, + taproot_construct, ) from test_framework.script_util import ( key_to_p2pk_script, @@ -81,8 +86,7 @@ class MiniWallet: def __init__(self, test_node, *, mode=MiniWalletMode.ADDRESS_OP_TRUE): self._test_node = test_node self._utxos = [] - self._priv_key = None - self._address = None + self._mode = mode assert isinstance(mode, MiniWalletMode) if mode == MiniWalletMode.RAW_OP_TRUE: @@ -97,6 +101,21 @@ class MiniWallet: self._address, self._internal_key = create_deterministic_address_bcrt1_p2tr_op_true() self._scriptPubKey = bytes.fromhex(self._test_node.validateaddress(self._address)['scriptPubKey']) + def _create_utxo(self, *, txid, vout, value, height): + return {"txid": txid, "vout": vout, "value": value, "height": height} + + def _bulk_tx(self, tx, target_weight): + """Pad a transaction with extra outputs until it reaches a target weight (or higher). + returns the tx + """ + tx.vout.append(CTxOut(nValue=0, scriptPubKey=CScript([OP_RETURN, b'a']))) + dummy_vbytes = (target_weight - tx.get_weight() + 3) // 4 + tx.vout[-1].scriptPubKey = CScript([OP_RETURN, b'a' * dummy_vbytes]) + # Lower bound should always be off by at most 3 + assert_greater_than_or_equal(tx.get_weight(), target_weight) + # Higher bound should always be off by at most 3 + 12 weight (for encoding the length) + assert_greater_than_or_equal(target_weight + 15, tx.get_weight()) + def get_balance(self): return sum(u['value'] for u in self._utxos) @@ -106,17 +125,26 @@ class MiniWallet: res = self._test_node.scantxoutset(action="start", scanobjects=[self.get_descriptor()]) assert_equal(True, res['success']) for utxo in res['unspents']: - self._utxos.append({'txid': utxo['txid'], 'vout': utxo['vout'], 'value': utxo['amount'], 'height': utxo['height']}) + self._utxos.append(self._create_utxo(txid=utxo["txid"], vout=utxo["vout"], value=utxo["amount"], height=utxo["height"])) def scan_tx(self, tx): - """Scan the tx for self._scriptPubKey outputs and add them to self._utxos""" + """Scan the tx and adjust the internal list of owned utxos""" + for spent in tx["vin"]: + # Mark spent. This may happen when the caller has ownership of a + # utxo that remained in this wallet. For example, by passing + # mark_as_spent=False to get_utxo or by using an utxo returned by a + # create_self_transfer* call. + try: + self.get_utxo(txid=spent["txid"], vout=spent["vout"]) + except StopIteration: + pass for out in tx['vout']: if out['scriptPubKey']['hex'] == self._scriptPubKey.hex(): - self._utxos.append({'txid': tx['txid'], 'vout': out['n'], 'value': out['value'], 'height': 0}) + self._utxos.append(self._create_utxo(txid=tx["txid"], vout=out["n"], value=out["value"], height=0)) def sign_tx(self, tx, fixed_length=True): """Sign tx that has been created by MiniWallet in P2PK mode""" - assert self._priv_key is not None + assert_equal(self._mode, MiniWalletMode.RAW_P2PK) (sighash, err) = LegacySignatureHash(CScript(self._scriptPubKey), tx, 0, SIGHASH_ALL) assert err is None # for exact fee calculation, create only signatures with fixed size by default (>49.89% probability): @@ -131,12 +159,16 @@ class MiniWallet: tx.rehash() def generate(self, num_blocks, **kwargs): - """Generate blocks with coinbase outputs to the internal address, and append the outputs to the internal list""" + """Generate blocks with coinbase outputs to the internal address, and call rescan_utxos""" blocks = self._test_node.generatetodescriptor(num_blocks, self.get_descriptor(), **kwargs) - for b in blocks: - block_info = self._test_node.getblock(blockhash=b, verbosity=2) - cb_tx = block_info['tx'][0] - self._utxos.append({'txid': cb_tx['txid'], 'vout': 0, 'value': cb_tx['vout'][0]['value'], 'height': block_info['height']}) + # Calling rescan_utxos here makes sure that after a generate the utxo + # set is in a clean state. For example, the wallet will update + # - if the caller consumed utxos, but never used them + # - if the caller sent a transaction that is not mined or got rbf'd + # - after block re-orgs + # - the utxo height for mined mempool txs + # - However, the wallet will not consider remaining mempool txs + self.rescan_utxos() return blocks def get_scriptPubKey(self): @@ -146,6 +178,7 @@ class MiniWallet: return descsum_create(f'raw({self._scriptPubKey.hex()})') def get_address(self): + assert_equal(self._mode, MiniWalletMode.ADDRESS_OP_TRUE) return self._address def get_utxo(self, *, txid: str = '', vout: Optional[int] = None, mark_as_spent=True) -> dict: @@ -175,10 +208,10 @@ class MiniWallet: self._utxos = [] return utxos - def send_self_transfer(self, **kwargs): - """Create and send a tx with the specified fee_rate. Fee may be exact or at most one satoshi higher than needed.""" + def send_self_transfer(self, *, from_node, **kwargs): + """Call create_self_transfer and send the transaction.""" tx = self.create_self_transfer(**kwargs) - self.sendrawtransaction(from_node=kwargs['from_node'], tx_hex=tx['hex']) + self.sendrawtransaction(from_node=from_node, tx_hex=tx['hex']) return tx def send_to(self, *, from_node, scriptPubKey, amount, fee=1000): @@ -193,47 +226,46 @@ class MiniWallet: Returns a tuple (txid, n) referring to the created external utxo outpoint. """ - tx = self.create_self_transfer(from_node=from_node, fee_rate=0, mempool_valid=False)['tx'] + tx = self.create_self_transfer(fee_rate=0)["tx"] assert_greater_than_or_equal(tx.vout[0].nValue, amount + fee) tx.vout[0].nValue -= (amount + fee) # change output -> MiniWallet tx.vout.append(CTxOut(amount, scriptPubKey)) # arbitrary output -> to be returned txid = self.sendrawtransaction(from_node=from_node, tx_hex=tx.serialize().hex()) return txid, 1 - def send_self_transfer_multi(self, **kwargs): - """ - Create and send a transaction that spends the given UTXOs and creates a - certain number of outputs with equal amounts. - - Returns a dictionary with - - txid - - serialized transaction in hex format - - transaction as CTransaction instance - - list of newly created UTXOs, ordered by vout index - """ + def send_self_transfer_multi(self, *, from_node, **kwargs): + """Call create_self_transfer_multi and send the transaction.""" tx = self.create_self_transfer_multi(**kwargs) - txid = self.sendrawtransaction(from_node=kwargs['from_node'], tx_hex=tx.serialize().hex()) - return {'new_utxos': [self.get_utxo(txid=txid, vout=vout) for vout in range(len(tx.vout))], - 'txid': txid, 'hex': tx.serialize().hex(), 'tx': tx} + self.sendrawtransaction(from_node=from_node, tx_hex=tx["hex"]) + return tx def create_self_transfer_multi( - self, *, from_node, - utxos_to_spend: Optional[List[dict]] = None, - num_outputs=1, - sequence=0, - fee_per_output=1000): + self, + *, + utxos_to_spend: Optional[List[dict]] = None, + num_outputs=1, + amount_per_output=0, + sequence=0, + fee_per_output=1000, + target_weight=0 + ): """ Create and return a transaction that spends the given UTXOs and creates a - certain number of outputs with equal amounts. + certain number of outputs with equal amounts. The output amounts can be + set by amount_per_output or automatically calculated with a fee_per_output. """ utxos_to_spend = utxos_to_spend or [self.get_utxo()] + sequence = [sequence] * len(utxos_to_spend) if type(sequence) is int else sequence + assert_equal(len(utxos_to_spend), len(sequence)) # create simple tx template (1 input, 1 output) tx = self.create_self_transfer( - fee_rate=0, from_node=from_node, - utxo_to_spend=utxos_to_spend[0], sequence=sequence, mempool_valid=False)['tx'] + fee_rate=0, + utxo_to_spend=utxos_to_spend[0])["tx"] # duplicate inputs, witnesses and outputs tx.vin = [deepcopy(tx.vin[0]) for _ in range(len(utxos_to_spend))] + for txin, seq in zip(tx.vin, sequence): + txin.nSequence = seq tx.wit.vtxinwit = [deepcopy(tx.wit.vtxinwit[0]) for _ in range(len(utxos_to_spend))] tx.vout = [deepcopy(tx.vout[0]) for _ in range(num_outputs)] @@ -245,56 +277,83 @@ class MiniWallet: inputs_value_total = sum([int(COIN * utxo['value']) for utxo in utxos_to_spend]) outputs_value_total = inputs_value_total - fee_per_output * num_outputs for o in tx.vout: - o.nValue = outputs_value_total // num_outputs - return tx + o.nValue = amount_per_output or (outputs_value_total // num_outputs) + + if target_weight: + self._bulk_tx(tx, target_weight) - def create_self_transfer(self, *, fee_rate=Decimal("0.003"), from_node=None, utxo_to_spend=None, mempool_valid=True, locktime=0, sequence=0): - """Create and return a tx with the specified fee_rate. Fee may be exact or at most one satoshi higher than needed. - Checking mempool validity via the testmempoolaccept RPC can be skipped by setting mempool_valid to False.""" - from_node = from_node or self._test_node + txid = tx.rehash() + return { + "new_utxos": [self._create_utxo( + txid=txid, + vout=i, + value=Decimal(tx.vout[i].nValue) / COIN, + height=0, + ) for i in range(len(tx.vout))], + "txid": txid, + "hex": tx.serialize().hex(), + "tx": tx, + } + + def create_self_transfer(self, *, fee_rate=Decimal("0.003"), fee=Decimal("0"), utxo_to_spend=None, locktime=0, sequence=0, target_weight=0): + """Create and return a tx with the specified fee. If fee is 0, use fee_rate, where the resulting fee may be exact or at most one satoshi higher than needed.""" utxo_to_spend = utxo_to_spend or self.get_utxo() - if self._priv_key is None: + assert fee_rate >= 0 + assert fee >= 0 + if self._mode in (MiniWalletMode.RAW_OP_TRUE, MiniWalletMode.ADDRESS_OP_TRUE): vsize = Decimal(104) # anyone-can-spend - else: + elif self._mode == MiniWalletMode.RAW_P2PK: vsize = Decimal(168) # P2PK (73 bytes scriptSig + 35 bytes scriptPubKey + 60 bytes other) - send_value = int(COIN * (utxo_to_spend['value'] - fee_rate * (vsize / 1000))) + else: + assert False + send_value = utxo_to_spend["value"] - (fee or (fee_rate * vsize / 1000)) assert send_value > 0 tx = CTransaction() tx.vin = [CTxIn(COutPoint(int(utxo_to_spend['txid'], 16), utxo_to_spend['vout']), nSequence=sequence)] - tx.vout = [CTxOut(send_value, self._scriptPubKey)] + tx.vout = [CTxOut(int(COIN * send_value), bytearray(self._scriptPubKey))] tx.nLockTime = locktime - if not self._address: - # raw script - if self._priv_key is not None: - # P2PK, need to sign - self.sign_tx(tx) - else: - # anyone-can-spend - tx.vin[0].scriptSig = CScript([OP_NOP] * 43) # pad to identical size - else: + if self._mode == MiniWalletMode.RAW_P2PK: + self.sign_tx(tx) + elif self._mode == MiniWalletMode.RAW_OP_TRUE: + tx.vin[0].scriptSig = CScript([OP_NOP] * 43) # pad to identical size + elif self._mode == MiniWalletMode.ADDRESS_OP_TRUE: tx.wit.vtxinwit = [CTxInWitness()] tx.wit.vtxinwit[0].scriptWitness.stack = [CScript([OP_TRUE]), bytes([LEAF_VERSION_TAPSCRIPT]) + self._internal_key] - tx_hex = tx.serialize().hex() + else: + assert False - if mempool_valid: - tx_info = from_node.testmempoolaccept([tx_hex])[0] - assert_equal(tx_info['allowed'], True) - assert_equal(tx_info['vsize'], vsize) - assert_equal(tx_info['fees']['base'], utxo_to_spend['value'] - Decimal(send_value) / COIN) + assert_equal(tx.get_vsize(), vsize) + + if target_weight: + self._bulk_tx(tx, target_weight) + + tx_hex = tx.serialize().hex() + new_utxo = self._create_utxo(txid=tx.rehash(), vout=0, value=send_value, height=0) - return {'txid': tx.rehash(), 'wtxid': tx.getwtxid(), 'hex': tx_hex, 'tx': tx} + return {"txid": new_utxo["txid"], "wtxid": tx.getwtxid(), "hex": tx_hex, "tx": tx, "new_utxo": new_utxo} def sendrawtransaction(self, *, from_node, tx_hex, maxfeerate=0, **kwargs): txid = from_node.sendrawtransaction(hexstring=tx_hex, maxfeerate=maxfeerate, **kwargs) self.scan_tx(from_node.decoderawtransaction(tx_hex)) return txid + def send_self_transfer_chain(self, *, from_node, chain_length, utxo_to_spend=None): + """Create and send a "chain" of chain_length transactions. The nth transaction in + the chain is a child of the n-1th transaction and parent of the n+1th transaction. -def getnewdestination(address_type='bech32'): + Returns the chaintip (nth) utxo + """ + chaintip_utxo = utxo_to_spend or self.get_utxo() + for _ in range(chain_length): + chaintip_utxo = self.send_self_transfer(utxo_to_spend=chaintip_utxo, from_node=from_node)["new_utxo"] + return chaintip_utxo + + +def getnewdestination(address_type='bech32m'): """Generate a random destination of the specified type and return the corresponding public key, scriptPubKey and address. Supported types are - 'legacy', 'p2sh-segwit' and 'bech32'. Can be used when a random + 'legacy', 'p2sh-segwit', 'bech32' and 'bech32m'. Can be used when a random destination is needed, but no compiled wallet is available (e.g. as replacement to the getnewaddress/getaddressinfo RPCs).""" key = ECKey() @@ -309,7 +368,11 @@ def getnewdestination(address_type='bech32'): elif address_type == 'bech32': scriptpubkey = key_to_p2wpkh_script(pubkey) address = key_to_p2wpkh(pubkey) - # TODO: also support bech32m (need to generate x-only-pubkey) + elif address_type == 'bech32m': + tap = taproot_construct(compute_xonly_pubkey(key.get_bytes())[0]) + pubkey = tap.output_pubkey + scriptpubkey = tap.scriptPubKey + address = output_key_to_p2tr(pubkey) else: assert False return pubkey, scriptpubkey, address @@ -378,23 +441,3 @@ def create_raw_chain(node, first_coin, address, privkeys, chain_length=25): chain_txns.append(tx) return (chain_hex, chain_txns) - -def bulk_transaction(tx, node, target_weight, privkeys, prevtxs=None): - """Pad a transaction with extra outputs until it reaches a target weight (or higher). - returns CTransaction object - """ - tx_heavy = deepcopy(tx) - assert_greater_than_or_equal(target_weight, tx_heavy.get_weight()) - while tx_heavy.get_weight() < target_weight: - random_spk = "6a4d0200" # OP_RETURN OP_PUSH2 512 bytes - for _ in range(512*2): - random_spk += choice("0123456789ABCDEF") - tx_heavy.vout.append(CTxOut(0, bytes.fromhex(random_spk))) - # Re-sign the transaction - if privkeys: - signed = node.signrawtransactionwithkey(tx_heavy.serialize().hex(), privkeys, prevtxs) - return tx_from_hex(signed["hex"]) - # OP_TRUE - tx_heavy.wit.vtxinwit = [CTxInWitness()] - tx_heavy.wit.vtxinwit[0].scriptWitness.stack = [CScript([OP_TRUE])] - return tx_heavy diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py index 6a44f9d21d..d78c1c634f 100755 --- a/test/functional/test_runner.py +++ b/test/functional/test_runner.py @@ -130,6 +130,7 @@ BASE_SCRIPTS = [ 'wallet_address_types.py --descriptors', 'feature_bip68_sequence.py', 'p2p_feefilter.py', + 'rpc_packages.py', 'feature_reindex.py', 'feature_abortnode.py', # vv Tests less than 30s vv @@ -155,6 +156,7 @@ BASE_SCRIPTS = [ 'mempool_spend_coinbase.py', 'wallet_avoidreuse.py --legacy-wallet', 'wallet_avoidreuse.py --descriptors', + 'wallet_avoid_mixing_output_types.py --descriptors', 'mempool_reorg.py', 'mempool_persist.py', 'p2p_block_sync.py', @@ -181,8 +183,10 @@ BASE_SCRIPTS = [ 'rpc_whitelist.py', 'feature_proxy.py', 'feature_syscall_sandbox.py', - 'rpc_signrawtransaction.py --legacy-wallet', - 'rpc_signrawtransaction.py --descriptors', + 'wallet_signrawtransactionwithwallet.py --legacy-wallet', + 'wallet_signrawtransactionwithwallet.py --descriptors', + 'rpc_signrawtransactionwithkey.py', + 'p2p_headers_sync_with_minchainwork.py', 'rpc_rawtransaction.py --legacy-wallet', 'wallet_groups.py --legacy-wallet', 'wallet_transactiontime_rescan.py --descriptors', @@ -203,6 +207,7 @@ BASE_SCRIPTS = [ 'wallet_keypool.py --legacy-wallet', 'wallet_keypool.py --descriptors', 'wallet_descriptor.py --descriptors', + 'wallet_miniscript.py', 'feature_maxtipage.py', 'p2p_nobloomfilter_messages.py', 'p2p_filter.py', @@ -226,12 +231,10 @@ BASE_SCRIPTS = [ 'rpc_getblockfrompeer.py', 'rpc_invalidateblock.py', 'feature_utxo_set_hash.py', - 'feature_rbf.py --legacy-wallet', - 'feature_rbf.py --descriptors', + 'feature_rbf.py', 'mempool_packages.py', 'mempool_package_onemore.py', 'rpc_createmultisig.py', - 'rpc_packages.py', 'mempool_package_limits.py', 'feature_versionbits_warning.py', 'rpc_preciousblock.py', @@ -244,8 +247,8 @@ BASE_SCRIPTS = [ 'rpc_generate.py', 'wallet_balance.py --legacy-wallet', 'wallet_balance.py --descriptors', - 'feature_nulldummy.py --legacy-wallet', - 'feature_nulldummy.py --descriptors', + 'p2p_initial_headers_sync.py', + 'feature_nulldummy.py', 'mempool_accept.py', 'mempool_expiry.py', 'wallet_import_rescan.py --legacy-wallet', @@ -263,6 +266,8 @@ BASE_SCRIPTS = [ 'wallet_implicitsegwit.py --legacy-wallet', 'rpc_named_arguments.py', 'feature_startupnotify.py', + 'wallet_simulaterawtx.py --legacy-wallet', + 'wallet_simulaterawtx.py --descriptors', 'wallet_listsinceblock.py --legacy-wallet', 'wallet_listsinceblock.py --descriptors', 'wallet_listdescriptors.py --descriptors', @@ -316,6 +321,7 @@ BASE_SCRIPTS = [ 'feature_unsupported_utxo_db.py', 'feature_logging.py', 'feature_anchors.py', + 'mempool_datacarrier.py', 'feature_coinstatsindex.py', 'wallet_orphanedreward.py', 'wallet_timelock.py', @@ -324,6 +330,7 @@ BASE_SCRIPTS = [ 'feature_blocksdir.py', 'wallet_startup.py', 'p2p_i2p_ports.py', + 'p2p_i2p_sessions.py', 'feature_config_args.py', 'feature_presegwit_node_upgrade.py', 'feature_settings.py', @@ -333,6 +340,7 @@ BASE_SCRIPTS = [ 'feature_dirsymlinks.py', 'feature_help.py', 'feature_shutdown.py', + 'wallet_migration.py', 'p2p_ibd_txrelay.py', # Don't append tests at the end to avoid merge conflicts # Put them in a random line within the section that fits their approximate run-time @@ -546,14 +554,14 @@ def run_tests(*, test_list, src_dir, build_dir, tmpdir, jobs=1, enable_coverage= while i < test_count: if failfast and not all_passed: break - for test_result, testdir, stdout, stderr in job_queue.get_next(): + for test_result, testdir, stdout, stderr, skip_reason in job_queue.get_next(): test_results.append(test_result) i += 1 done_str = "{}/{} - {}{}{}".format(i, test_count, BOLD[1], test_result.name, BOLD[0]) if test_result.status == "Passed": logging.debug("%s passed, Duration: %s s" % (done_str, test_result.time)) elif test_result.status == "Skipped": - logging.debug("%s skipped" % (done_str)) + logging.debug(f"{done_str} skipped ({skip_reason})") else: all_passed = False print("%s failed, Duration: %s s\n" % (done_str, test_result.time)) @@ -677,10 +685,12 @@ class TestHandler: log_out.seek(0), log_err.seek(0) [stdout, stderr] = [log_file.read().decode('utf-8') for log_file in (log_out, log_err)] log_out.close(), log_err.close() + skip_reason = None if proc.returncode == TEST_EXIT_PASSED and stderr == "": status = "Passed" elif proc.returncode == TEST_EXIT_SKIPPED: status = "Skipped" + skip_reason = re.search(r"Test Skipped: (.*)", stdout).group(1) else: status = "Failed" self.num_running -= 1 @@ -689,7 +699,7 @@ class TestHandler: clearline = '\r' + (' ' * dot_count) + '\r' print(clearline, end='', flush=True) dot_count = 0 - ret.append((TestResult(name, status, int(time.time() - start_time)), testdir, stdout, stderr)) + ret.append((TestResult(name, status, int(time.time() - start_time)), testdir, stdout, stderr, skip_reason)) if ret: return ret if self.use_term_control: diff --git a/test/functional/tool_wallet.py b/test/functional/tool_wallet.py index 2cb9dc4523..1e5ce513cb 100755 --- a/test/functional/tool_wallet.py +++ b/test/functional/tool_wallet.py @@ -68,7 +68,7 @@ class ToolWalletTest(BitcoinTestFramework): result = 'unchanged' if new == old else 'increased!' self.log.debug('Wallet file timestamp {}'.format(result)) - def get_expected_info_output(self, name="", transactions=0, keypool=2, address=0): + def get_expected_info_output(self, name="", transactions=0, keypool=2, address=0, imported_privs=0): wallet_name = self.default_wallet_name if name == "" else name if self.options.descriptors: output_types = 4 # p2pkh, p2sh, segwit, bech32m @@ -83,7 +83,7 @@ class ToolWalletTest(BitcoinTestFramework): Keypool Size: %d Transactions: %d Address Book: %d - ''' % (wallet_name, keypool * output_types, transactions, address)) + ''' % (wallet_name, keypool * output_types, transactions, imported_privs * 3 + address)) else: output_types = 3 # p2pkh, p2sh, segwit. Legacy wallets do not support bech32m. return textwrap.dedent('''\ @@ -97,7 +97,7 @@ class ToolWalletTest(BitcoinTestFramework): Keypool Size: %d Transactions: %d Address Book: %d - ''' % (wallet_name, keypool, transactions, address * output_types)) + ''' % (wallet_name, keypool, transactions, (address + imported_privs) * output_types)) def read_dump(self, filename): dump = OrderedDict() @@ -219,7 +219,7 @@ class ToolWalletTest(BitcoinTestFramework): # shasum_before = self.wallet_shasum() timestamp_before = self.wallet_timestamp() self.log.debug('Wallet file timestamp before calling info: {}'.format(timestamp_before)) - out = self.get_expected_info_output(address=1) + out = self.get_expected_info_output(imported_privs=1) self.assert_tool_output(out, '-wallet=' + self.default_wallet_name, 'info') timestamp_after = self.wallet_timestamp() self.log.debug('Wallet file timestamp after calling info: {}'.format(timestamp_after)) @@ -250,7 +250,7 @@ class ToolWalletTest(BitcoinTestFramework): shasum_before = self.wallet_shasum() timestamp_before = self.wallet_timestamp() self.log.debug('Wallet file timestamp before calling info: {}'.format(timestamp_before)) - out = self.get_expected_info_output(transactions=1, address=1) + out = self.get_expected_info_output(transactions=1, imported_privs=1) self.assert_tool_output(out, '-wallet=' + self.default_wallet_name, 'info') shasum_after = self.wallet_shasum() timestamp_after = self.wallet_timestamp() diff --git a/test/functional/wallet_abandonconflict.py b/test/functional/wallet_abandonconflict.py index 36fcdb36d6..d7850b41ac 100755 --- a/test/functional/wallet_abandonconflict.py +++ b/test/functional/wallet_abandonconflict.py @@ -24,6 +24,9 @@ class AbandonConflictTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 2 self.extra_args = [["-minrelaytxfee=0.00001"], []] + # whitelist peers to speed up tx relay / mempool sync + for args in self.extra_args: + args.append("-whitelist=noban@127.0.0.1") def skip_test_if_missing_module(self): self.skip_if_no_wallet() diff --git a/test/functional/wallet_address_types.py b/test/functional/wallet_address_types.py index f7c80f805c..5b836f693f 100755 --- a/test/functional/wallet_address_types.py +++ b/test/functional/wallet_address_types.py @@ -345,31 +345,19 @@ class AddressTypeTest(BitcoinTestFramework): self.log.info("Nodes with addresstype=legacy never use a P2WPKH change output (unless changetype is set otherwise):") self.test_change_output_type(0, [to_address_bech32_1], 'legacy') - if self.options.descriptors: - self.log.info("Nodes with addresstype=p2sh-segwit match the change output") - self.test_change_output_type(1, [to_address_p2sh], 'p2sh-segwit') - self.test_change_output_type(1, [to_address_bech32_1], 'bech32') - self.test_change_output_type(1, [to_address_p2sh, to_address_bech32_1], 'bech32') - self.test_change_output_type(1, [to_address_bech32_1, to_address_bech32_2], 'bech32') - else: - self.log.info("Nodes with addresstype=p2sh-segwit match the change output") - self.test_change_output_type(1, [to_address_p2sh], 'p2sh-segwit') - self.test_change_output_type(1, [to_address_bech32_1], 'bech32') - self.test_change_output_type(1, [to_address_p2sh, to_address_bech32_1], 'bech32') - self.test_change_output_type(1, [to_address_bech32_1, to_address_bech32_2], 'bech32') + self.log.info("Nodes with addresstype=p2sh-segwit match the change output") + self.test_change_output_type(1, [to_address_p2sh], 'p2sh-segwit') + self.test_change_output_type(1, [to_address_bech32_1], 'bech32') + self.test_change_output_type(1, [to_address_p2sh, to_address_bech32_1], 'bech32') + self.test_change_output_type(1, [to_address_bech32_1, to_address_bech32_2], 'bech32') self.log.info("Nodes with change_type=bech32 always use a P2WPKH change output:") self.test_change_output_type(2, [to_address_bech32_1], 'bech32') self.test_change_output_type(2, [to_address_p2sh], 'bech32') - if self.options.descriptors: - self.log.info("Nodes with addresstype=bech32 match the change output (unless changetype is set otherwise):") - self.test_change_output_type(3, [to_address_bech32_1], 'bech32') - self.test_change_output_type(3, [to_address_p2sh], 'p2sh-segwit') - else: - self.log.info("Nodes with addresstype=bech32 match the change output (unless changetype is set otherwise):") - self.test_change_output_type(3, [to_address_bech32_1], 'bech32') - self.test_change_output_type(3, [to_address_p2sh], 'p2sh-segwit') + self.log.info("Nodes with addresstype=bech32 match the change output (unless changetype is set otherwise):") + self.test_change_output_type(3, [to_address_bech32_1], 'bech32') + self.test_change_output_type(3, [to_address_p2sh], 'p2sh-segwit') self.log.info('getrawchangeaddress defaults to addresstype if -changetype is not set and argument is absent') self.test_address(3, self.nodes[3].getrawchangeaddress(), multisig=False, typ='bech32') diff --git a/test/functional/wallet_avoid_mixing_output_types.py b/test/functional/wallet_avoid_mixing_output_types.py new file mode 100755 index 0000000000..cad9d02808 --- /dev/null +++ b/test/functional/wallet_avoid_mixing_output_types.py @@ -0,0 +1,177 @@ +#!/usr/bin/env python3 +# Copyright (c) 2022 The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or https://www.opensource.org/licenses/mit-license.php. +"""Test output type mixing during coin selection + +A wallet may have different types of UTXOs to choose from during coin selection, +where output type is one of the following: + - BECH32M + - BECH32 + - P2SH-SEGWIT + - LEGACY + +This test verifies that mixing different output types is avoided unless +absolutely necessary. Both wallets start with zero funds. Alice mines +enough blocks to have spendable coinbase outputs. Alice sends three +random value payments which sum to 10BTC for each output type to Bob, +for a total of 40BTC in Bob's wallet. + +Bob then sends random valued payments back to Alice, some of which need +unconfirmed change, and we verify that none of these payments contain mixed +inputs. Finally, Bob sends the remainder of his funds, which requires mixing. + +The payment values are random, but chosen such that they sum up to a specified +total. This ensures we are not relying on specific values for the UTXOs, +but still know when to expect mixing due to the wallet being close to empty. + +""" + +import random +from test_framework.test_framework import BitcoinTestFramework +from test_framework.blocktools import COINBASE_MATURITY + +ADDRESS_TYPES = [ + "bech32m", + "bech32", + "p2sh-segwit", + "legacy", +] + + +def is_bech32_address(node, addr): + """Check if an address contains a bech32 output.""" + addr_info = node.getaddressinfo(addr) + return addr_info['desc'].startswith('wpkh(') + + +def is_bech32m_address(node, addr): + """Check if an address contains a bech32m output.""" + addr_info = node.getaddressinfo(addr) + return addr_info['desc'].startswith('tr(') + + +def is_p2sh_segwit_address(node, addr): + """Check if an address contains a P2SH-Segwit output. + Note: this function does not actually determine the type + of P2SH output, but is sufficient for this test in that + we are only generating P2SH-Segwit outputs. + """ + addr_info = node.getaddressinfo(addr) + return addr_info['desc'].startswith('sh(wpkh(') + + +def is_legacy_address(node, addr): + """Check if an address contains a legacy output.""" + addr_info = node.getaddressinfo(addr) + return addr_info['desc'].startswith('pkh(') + + +def is_same_type(node, tx): + """Check that all inputs are of the same OutputType""" + vins = node.getrawtransaction(tx, True)['vin'] + inputs = [] + for vin in vins: + prev_tx, n = vin['txid'], vin['vout'] + inputs.append( + node.getrawtransaction( + prev_tx, + True, + )['vout'][n]['scriptPubKey']['address'] + ) + has_legacy = False + has_p2sh = False + has_bech32 = False + has_bech32m = False + + for addr in inputs: + if is_legacy_address(node, addr): + has_legacy = True + if is_p2sh_segwit_address(node, addr): + has_p2sh = True + if is_bech32_address(node, addr): + has_bech32 = True + if is_bech32m_address(node, addr): + has_bech32m = True + + return (sum([has_legacy, has_p2sh, has_bech32, has_bech32m]) == 1) + + +def generate_payment_values(n, m): + """Return a randomly chosen list of n positive integers summing to m. + Each such list is equally likely to occur.""" + + dividers = sorted(random.sample(range(1, m), n - 1)) + return [a - b for a, b in zip(dividers + [m], [0] + dividers)] + + +class AddressInputTypeGrouping(BitcoinTestFramework): + def set_test_params(self): + self.setup_clean_chain = True + self.num_nodes = 2 + self.extra_args = [ + [ + "-addresstype=bech32", + "-whitelist=noban@127.0.0.1", + "-txindex", + ], + [ + "-addresstype=p2sh-segwit", + "-whitelist=noban@127.0.0.1", + "-txindex", + ], + ] + + def skip_test_if_missing_module(self): + self.skip_if_no_wallet() + self.skip_if_no_sqlite() + + def make_payment(self, A, B, v, addr_type): + fee_rate = random.randint(1, 20) + self.log.debug(f"Making payment of {v} BTC at fee_rate {fee_rate}") + tx = B.sendtoaddress( + address=A.getnewaddress(address_type=addr_type), + amount=v, + fee_rate=fee_rate, + ) + return tx + + def run_test(self): + + # alias self.nodes[i] to A, B for readability + A, B = self.nodes[0], self.nodes[1] + self.generate(A, COINBASE_MATURITY + 5) + + self.log.info("Creating mixed UTXOs in B's wallet") + for v in generate_payment_values(3, 10): + self.log.debug(f"Making payment of {v} BTC to legacy") + A.sendtoaddress(B.getnewaddress(address_type="legacy"), v) + + for v in generate_payment_values(3, 10): + self.log.debug(f"Making payment of {v} BTC to p2sh") + A.sendtoaddress(B.getnewaddress(address_type="p2sh-segwit"), v) + + for v in generate_payment_values(3, 10): + self.log.debug(f"Making payment of {v} BTC to bech32") + A.sendtoaddress(B.getnewaddress(address_type="bech32"), v) + + for v in generate_payment_values(3, 10): + self.log.debug(f"Making payment of {v} BTC to bech32m") + A.sendtoaddress(B.getnewaddress(address_type="bech32m"), v) + + self.generate(A, 1) + + self.log.info("Sending payments from B to A") + for v in generate_payment_values(5, 9): + tx = self.make_payment( + A, B, v, random.choice(ADDRESS_TYPES) + ) + self.generate(A, 1) + assert is_same_type(B, tx) + + tx = self.make_payment(A, B, 30.99, random.choice(ADDRESS_TYPES)) + assert not is_same_type(B, tx) + + +if __name__ == '__main__': + AddressInputTypeGrouping().main() diff --git a/test/functional/wallet_balance.py b/test/functional/wallet_balance.py index 0c93821e7f..ec58ace4a2 100755 --- a/test/functional/wallet_balance.py +++ b/test/functional/wallet_balance.py @@ -55,6 +55,9 @@ class WalletTest(BitcoinTestFramework): ['-limitdescendantcount=3', '-walletrejectlongchains=0'], [], ] + # whitelist peers to speed up tx relay / mempool sync + for args in self.extra_args: + args.append("-whitelist=noban@127.0.0.1") def skip_test_if_missing_module(self): self.skip_if_no_wallet() @@ -263,7 +266,6 @@ class WalletTest(BitcoinTestFramework): self.nodes[1].invalidateblock(block_reorg) assert_equal(self.nodes[0].getbalance(minconf=0), 0) # wallet txs not in the mempool are untrusted self.generatetoaddress(self.nodes[0], 1, ADDRESS_WATCHONLY, sync_fun=self.no_op) - assert_equal(self.nodes[0].getbalance(minconf=0), 0) # wallet txs not in the mempool are untrusted # Now confirm tx_orig self.restart_node(1, ['-persistmempool=0']) @@ -273,6 +275,26 @@ class WalletTest(BitcoinTestFramework): self.generatetoaddress(self.nodes[1], 1, ADDRESS_WATCHONLY) assert_equal(self.nodes[0].getbalance(minconf=0), total_amount + 1) # The reorg recovered our fee of 1 coin + if not self.options.descriptors: + self.log.info('Check if mempool is taken into account after import*') + address = self.nodes[0].getnewaddress() + privkey = self.nodes[0].dumpprivkey(address) + self.nodes[0].sendtoaddress(address, 0.1) + self.nodes[0].unloadwallet('') + # check importaddress on fresh wallet + self.nodes[0].createwallet('w1', False, True) + self.nodes[0].importaddress(address) + assert_equal(self.nodes[0].getbalances()['mine']['untrusted_pending'], 0) + assert_equal(self.nodes[0].getbalances()['watchonly']['untrusted_pending'], Decimal('0.1')) + self.nodes[0].importprivkey(privkey) + assert_equal(self.nodes[0].getbalances()['mine']['untrusted_pending'], Decimal('0.1')) + assert_equal(self.nodes[0].getbalances()['watchonly']['untrusted_pending'], 0) + self.nodes[0].unloadwallet('w1') + # check importprivkey on fresh wallet + self.nodes[0].createwallet('w2', False, True) + self.nodes[0].importprivkey(privkey) + assert_equal(self.nodes[0].getbalances()['mine']['untrusted_pending'], Decimal('0.1')) + if __name__ == '__main__': WalletTest().main() diff --git a/test/functional/wallet_basic.py b/test/functional/wallet_basic.py index a6c93ba5f9..20c577ceb3 100755 --- a/test/functional/wallet_basic.py +++ b/test/functional/wallet_basic.py @@ -7,6 +7,7 @@ from decimal import Decimal from itertools import product from test_framework.blocktools import COINBASE_MATURITY +from test_framework.descriptors import descsum_create from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_array_result, @@ -25,7 +26,7 @@ class WalletTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 4 self.extra_args = [[ - "-acceptnonstdtxn=1", "-walletrejectlongchains=0" + "-dustrelayfee=0", "-walletrejectlongchains=0", "-whitelist=noban@127.0.0.1" ]] * self.num_nodes self.setup_clean_chain = True self.supports_cli = False @@ -414,7 +415,7 @@ class WalletTest(BitcoinTestFramework): assert_raises_rpc_error(-3, "Invalid amount", self.nodes[0].sendtoaddress, self.nodes[2].getnewaddress(), "1f-4") # This will raise an exception since generate does not accept a string - assert_raises_rpc_error(-1, "not an integer", self.generate, self.nodes[0], "2") + assert_raises_rpc_error(-3, "not of expected type number", self.generate, self.nodes[0], "2") if not self.options.descriptors: @@ -584,15 +585,9 @@ class WalletTest(BitcoinTestFramework): # ==Check that wallet prefers to use coins that don't exceed mempool limits ===== - # Get all non-zero utxos together + # Get all non-zero utxos together and split into two chains chain_addrs = [self.nodes[0].getnewaddress(), self.nodes[0].getnewaddress()] - singletxid = self.nodes[0].sendtoaddress(chain_addrs[0], self.nodes[0].getbalance(), "", "", True) - self.generate(self.nodes[0], 1, sync_fun=self.no_op) - node0_balance = self.nodes[0].getbalance() - # Split into two chains - rawtx = self.nodes[0].createrawtransaction([{"txid": singletxid, "vout": 0}], {chain_addrs[0]: node0_balance / 2 - Decimal('0.01'), chain_addrs[1]: node0_balance / 2 - Decimal('0.01')}) - signedtx = self.nodes[0].signrawtransactionwithwallet(rawtx) - singletxid = self.nodes[0].sendrawtransaction(hexstring=signedtx["hex"], maxfeerate=0) + self.nodes[0].sendall(recipients=chain_addrs) self.generate(self.nodes[0], 1, sync_fun=self.no_op) # Make a long chain of unconfirmed payments without hitting mempool limit @@ -700,6 +695,39 @@ class WalletTest(BitcoinTestFramework): txid_feeReason_four = self.nodes[2].sendmany(dummy='', amounts={address: 5}, verbose=False) assert_equal(self.nodes[2].gettransaction(txid_feeReason_four)['txid'], txid_feeReason_four) + if self.options.descriptors: + self.log.info("Testing 'listunspent' outputs the parent descriptor(s) of coins") + # Create two multisig descriptors, and send a UTxO each. + multi_a = descsum_create("wsh(multi(1,tpubD6NzVbkrYhZ4YBNjUo96Jxd1u4XKWgnoc7LsA1jz3Yc2NiDbhtfBhaBtemB73n9V5vtJHwU6FVXwggTbeoJWQ1rzdz8ysDuQkpnaHyvnvzR/*,tpubD6NzVbkrYhZ4YHdDGMAYGaWxMSC1B6tPRTHuU5t3BcfcS3nrF523iFm5waFd1pP3ZvJt4Jr8XmCmsTBNx5suhcSgtzpGjGMASR3tau1hJz4/*))") + multi_b = descsum_create("wsh(multi(1,tpubD6NzVbkrYhZ4YHdDGMAYGaWxMSC1B6tPRTHuU5t3BcfcS3nrF523iFm5waFd1pP3ZvJt4Jr8XmCmsTBNx5suhcSgtzpGjGMASR3tau1hJz4/*,tpubD6NzVbkrYhZ4Y2RLiuEzNQkntjmsLpPYDm3LTRBYynUQtDtpzeUKAcb9sYthSFL3YR74cdFgF5mW8yKxv2W2CWuZDFR2dUpE5PF9kbrVXNZ/*))") + addr_a = self.nodes[0].deriveaddresses(multi_a, 0)[0] + addr_b = self.nodes[0].deriveaddresses(multi_b, 0)[0] + txid_a = self.nodes[0].sendtoaddress(addr_a, 0.01) + txid_b = self.nodes[0].sendtoaddress(addr_b, 0.01) + self.generate(self.nodes[0], 1, sync_fun=self.no_op) + # Now import the descriptors, make sure we can identify on which descriptor each coin was received. + self.nodes[0].createwallet(wallet_name="wo", descriptors=True, disable_private_keys=True) + wo_wallet = self.nodes[0].get_wallet_rpc("wo") + wo_wallet.importdescriptors([ + { + "desc": multi_a, + "active": False, + "timestamp": "now", + }, + { + "desc": multi_b, + "active": False, + "timestamp": "now", + }, + ]) + coins = wo_wallet.listunspent(minconf=0) + assert_equal(len(coins), 2) + coin_a = next(c for c in coins if c["txid"] == txid_a) + assert_equal(coin_a["parent_descs"][0], multi_a) + coin_b = next(c for c in coins if c["txid"] == txid_b) + assert_equal(coin_b["parent_descs"][0], multi_b) + self.nodes[0].unloadwallet("wo") + if __name__ == '__main__': WalletTest().main() diff --git a/test/functional/wallet_bumpfee.py b/test/functional/wallet_bumpfee.py index 3b23ee8e94..158ef66110 100755 --- a/test/functional/wallet_bumpfee.py +++ b/test/functional/wallet_bumpfee.py @@ -23,7 +23,7 @@ from test_framework.blocktools import ( send_to_witness, ) from test_framework.messages import ( - BIP125_SEQUENCE_NUMBER, + MAX_BIP125_RBF_SEQUENCE, ) from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( @@ -53,6 +53,7 @@ class BumpFeeTest(BitcoinTestFramework): "-walletrbf={}".format(i), "-mintxfee=0.00002", "-addresstype=bech32", + "-whitelist=noban@127.0.0.1", ] for i in range(self.num_nodes)] def skip_test_if_missing_module(self): @@ -86,12 +87,13 @@ class BumpFeeTest(BitcoinTestFramework): self.test_invalid_parameters(rbf_node, peer_node, dest_address) test_segwit_bumpfee_succeeds(self, rbf_node, dest_address) test_nonrbf_bumpfee_fails(self, peer_node, dest_address) - test_notmine_bumpfee_fails(self, rbf_node, peer_node, dest_address) + test_notmine_bumpfee(self, rbf_node, peer_node, dest_address) test_bumpfee_with_descendant_fails(self, rbf_node, rbf_node_address, dest_address) test_dust_to_fee(self, rbf_node, dest_address) test_watchonly_psbt(self, peer_node, rbf_node, dest_address) test_rebumping(self, rbf_node, dest_address) test_rebumping_not_replaceable(self, rbf_node, dest_address) + test_bumpfee_already_spent(self, rbf_node, dest_address) test_unconfirmed_not_spendable(self, rbf_node, rbf_node_address) test_bumpfee_metadata(self, rbf_node, dest_address) test_locked_wallet_fails(self, rbf_node, dest_address) @@ -212,7 +214,7 @@ def test_segwit_bumpfee_succeeds(self, rbf_node, dest_address): rbfraw = rbf_node.createrawtransaction([{ 'txid': segwitid, 'vout': 0, - "sequence": BIP125_SEQUENCE_NUMBER + "sequence": MAX_BIP125_RBF_SEQUENCE }], {dest_address: Decimal("0.0005"), rbf_node.getrawchangeaddress(): Decimal("0.0003")}) rbfsigned = rbf_node.signrawtransactionwithwallet(rbfraw) @@ -228,11 +230,11 @@ def test_segwit_bumpfee_succeeds(self, rbf_node, dest_address): def test_nonrbf_bumpfee_fails(self, peer_node, dest_address): self.log.info('Test that we cannot replace a non RBF transaction') not_rbfid = peer_node.sendtoaddress(dest_address, Decimal("0.00090000")) - assert_raises_rpc_error(-4, "not BIP 125 replaceable", peer_node.bumpfee, not_rbfid) + assert_raises_rpc_error(-4, "Transaction is not BIP 125 replaceable", peer_node.bumpfee, not_rbfid) self.clear_mempool() -def test_notmine_bumpfee_fails(self, rbf_node, peer_node, dest_address): +def test_notmine_bumpfee(self, rbf_node, peer_node, dest_address): self.log.info('Test that it cannot bump fee if non-owned inputs are included') # here, the rbftx has a peer_node coin and then adds a rbf_node input # Note that this test depends upon the RPC code checking input ownership prior to change outputs @@ -243,15 +245,34 @@ def test_notmine_bumpfee_fails(self, rbf_node, peer_node, dest_address): "txid": utxo["txid"], "vout": utxo["vout"], "address": utxo["address"], - "sequence": BIP125_SEQUENCE_NUMBER + "sequence": MAX_BIP125_RBF_SEQUENCE } for utxo in utxos] output_val = sum(utxo["amount"] for utxo in utxos) - fee rawtx = rbf_node.createrawtransaction(inputs, {dest_address: output_val}) signedtx = rbf_node.signrawtransactionwithwallet(rawtx) signedtx = peer_node.signrawtransactionwithwallet(signedtx["hex"]) rbfid = rbf_node.sendrawtransaction(signedtx["hex"]) + entry = rbf_node.getmempoolentry(rbfid) + old_fee = entry["fees"]["base"] + old_feerate = int(old_fee / entry["vsize"] * Decimal(1e8)) assert_raises_rpc_error(-4, "Transaction contains inputs that don't belong to this wallet", rbf_node.bumpfee, rbfid) + + def finish_psbtbumpfee(psbt): + psbt = rbf_node.walletprocesspsbt(psbt) + psbt = peer_node.walletprocesspsbt(psbt["psbt"]) + final = rbf_node.finalizepsbt(psbt["psbt"]) + res = rbf_node.testmempoolaccept([final["hex"]]) + assert res[0]["allowed"] + assert_greater_than(res[0]["fees"]["base"], old_fee) + + self.log.info("Test that psbtbumpfee works for non-owned inputs") + psbt = rbf_node.psbtbumpfee(txid=rbfid) + finish_psbtbumpfee(psbt["psbt"]) + + psbt = rbf_node.psbtbumpfee(txid=rbfid, options={"fee_rate": old_feerate + 10}) + finish_psbtbumpfee(psbt["psbt"]) + self.clear_mempool() @@ -479,7 +500,8 @@ def test_rebumping(self, rbf_node, dest_address): self.log.info('Test that re-bumping the original tx fails, but bumping successor works') rbfid = spend_one_input(rbf_node, dest_address) bumped = rbf_node.bumpfee(rbfid, {"fee_rate": ECONOMICAL}) - assert_raises_rpc_error(-4, "already bumped", rbf_node.bumpfee, rbfid, {"fee_rate": NORMAL}) + assert_raises_rpc_error(-4, f"Cannot bump transaction {rbfid} which was already bumped by transaction {bumped['txid']}", + rbf_node.bumpfee, rbfid, {"fee_rate": NORMAL}) rbf_node.bumpfee(bumped["txid"], {"fee_rate": NORMAL}) self.clear_mempool() @@ -493,6 +515,15 @@ def test_rebumping_not_replaceable(self, rbf_node, dest_address): self.clear_mempool() +def test_bumpfee_already_spent(self, rbf_node, dest_address): + self.log.info('Test that bumping tx with already spent coin fails') + txid = spend_one_input(rbf_node, dest_address) + self.generate(rbf_node, 1) # spend coin simply by mining block with tx + spent_input = rbf_node.gettransaction(txid=txid, verbose=True)['decoded']['vin'][0] + assert_raises_rpc_error(-1, f"{spent_input['txid']}:{spent_input['vout']} is already spent", + rbf_node.bumpfee, txid, {"fee_rate": NORMAL}) + + def test_unconfirmed_not_spendable(self, rbf_node, rbf_node_address): self.log.info('Test that unconfirmed outputs from bumped txns are not spendable') rbfid = spend_one_input(rbf_node, rbf_node_address) @@ -578,7 +609,7 @@ def test_change_script_match(self, rbf_node, dest_address): def spend_one_input(node, dest_address, change_size=Decimal("0.00049000")): tx_input = dict( - sequence=BIP125_SEQUENCE_NUMBER, **next(u for u in node.listunspent() if u["amount"] == Decimal("0.00100000"))) + sequence=MAX_BIP125_RBF_SEQUENCE, **next(u for u in node.listunspent() if u["amount"] == Decimal("0.00100000"))) destinations = {dest_address: Decimal("0.00050000")} if change_size > 0: destinations[node.getrawchangeaddress()] = change_size @@ -604,7 +635,7 @@ def test_no_more_inputs_fails(self, rbf_node, dest_address): # feerate rbf requires confirmed outputs when change output doesn't exist or is insufficient self.generatetoaddress(rbf_node, 1, dest_address) # spend all funds, no change output - rbfid = rbf_node.sendtoaddress(rbf_node.getnewaddress(), rbf_node.getbalance(), "", "", True) + rbfid = rbf_node.sendall(recipients=[rbf_node.getnewaddress()])['txid'] assert_raises_rpc_error(-4, "Unable to create transaction. Insufficient funds", rbf_node.bumpfee, rbfid) self.clear_mempool() diff --git a/test/functional/wallet_coinbase_category.py b/test/functional/wallet_coinbase_category.py index 5a6b6cee59..c2a8e612cf 100755 --- a/test/functional/wallet_coinbase_category.py +++ b/test/functional/wallet_coinbase_category.py @@ -15,6 +15,7 @@ from test_framework.util import ( class CoinbaseCategoryTest(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() diff --git a/test/functional/wallet_descriptor.py b/test/functional/wallet_descriptor.py index e47d021210..5dc23ba245 100755 --- a/test/functional/wallet_descriptor.py +++ b/test/functional/wallet_descriptor.py @@ -93,15 +93,15 @@ class WalletDescriptorTest(BitcoinTestFramework): # Make sure things are disabled self.log.info("Test disabled RPCs") - assert_raises_rpc_error(-4, "This type of wallet does not support this command", recv_wrpc.rpc.importprivkey, "cVpF924EspNh8KjYsfhgY96mmxvT6DgdWiTYMtMjuM74hJaU5psW") - assert_raises_rpc_error(-4, "This type of wallet does not support this command", recv_wrpc.rpc.importpubkey, send_wrpc.getaddressinfo(send_wrpc.getnewaddress())) - assert_raises_rpc_error(-4, "This type of wallet does not support this command", recv_wrpc.rpc.importaddress, recv_wrpc.getnewaddress()) - assert_raises_rpc_error(-4, "This type of wallet does not support this command", recv_wrpc.rpc.importmulti, []) - assert_raises_rpc_error(-4, "This type of wallet does not support this command", recv_wrpc.rpc.addmultisigaddress, 1, [recv_wrpc.getnewaddress()]) - assert_raises_rpc_error(-4, "This type of wallet does not support this command", recv_wrpc.rpc.dumpprivkey, recv_wrpc.getnewaddress()) - assert_raises_rpc_error(-4, "This type of wallet does not support this command", recv_wrpc.rpc.dumpwallet, 'wallet.dump') - assert_raises_rpc_error(-4, "This type of wallet does not support this command", recv_wrpc.rpc.importwallet, 'wallet.dump') - assert_raises_rpc_error(-4, "This type of wallet does not support this command", recv_wrpc.rpc.sethdseed) + assert_raises_rpc_error(-4, "Only legacy wallets are supported by this command", recv_wrpc.rpc.importprivkey, "cVpF924EspNh8KjYsfhgY96mmxvT6DgdWiTYMtMjuM74hJaU5psW") + assert_raises_rpc_error(-4, "Only legacy wallets are supported by this command", recv_wrpc.rpc.importpubkey, send_wrpc.getaddressinfo(send_wrpc.getnewaddress())) + assert_raises_rpc_error(-4, "Only legacy wallets are supported by this command", recv_wrpc.rpc.importaddress, recv_wrpc.getnewaddress()) + assert_raises_rpc_error(-4, "Only legacy wallets are supported by this command", recv_wrpc.rpc.importmulti, []) + assert_raises_rpc_error(-4, "Only legacy wallets are supported by this command", recv_wrpc.rpc.addmultisigaddress, 1, [recv_wrpc.getnewaddress()]) + assert_raises_rpc_error(-4, "Only legacy wallets are supported by this command", recv_wrpc.rpc.dumpprivkey, recv_wrpc.getnewaddress()) + assert_raises_rpc_error(-4, "Only legacy wallets are supported by this command", recv_wrpc.rpc.dumpwallet, 'wallet.dump') + assert_raises_rpc_error(-4, "Only legacy wallets are supported by this command", recv_wrpc.rpc.importwallet, 'wallet.dump') + assert_raises_rpc_error(-4, "Only legacy wallets are supported by this command", recv_wrpc.rpc.sethdseed) self.log.info("Test encryption") # Get the master fingerprint before encrypt diff --git a/test/functional/wallet_encryption.py b/test/functional/wallet_encryption.py index 0c9106f800..37c1c4bff3 100755 --- a/test/functional/wallet_encryption.py +++ b/test/functional/wallet_encryption.py @@ -9,8 +9,7 @@ import time from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_raises_rpc_error, - assert_greater_than, - assert_greater_than_or_equal, + assert_equal, ) @@ -76,21 +75,18 @@ class WalletEncryptionTest(BitcoinTestFramework): self.log.info('Check a timeout less than the limit') MAX_VALUE = 100000000 - expected_time = int(time.time()) + MAX_VALUE - 600 + now = int(time.time()) + self.nodes[0].setmocktime(now) + expected_time = now + MAX_VALUE - 600 self.nodes[0].walletpassphrase(passphrase2, MAX_VALUE - 600) - # give buffer for walletpassphrase, since it iterates over all encrypted keys - expected_time_with_buffer = time.time() + MAX_VALUE - 600 actual_time = self.nodes[0].getwalletinfo()['unlocked_until'] - assert_greater_than_or_equal(actual_time, expected_time) - assert_greater_than(expected_time_with_buffer, actual_time) + assert_equal(actual_time, expected_time) self.log.info('Check a timeout greater than the limit') - expected_time = int(time.time()) + MAX_VALUE - 1 + expected_time = now + MAX_VALUE self.nodes[0].walletpassphrase(passphrase2, MAX_VALUE + 1000) - expected_time_with_buffer = time.time() + MAX_VALUE actual_time = self.nodes[0].getwalletinfo()['unlocked_until'] - assert_greater_than_or_equal(actual_time, expected_time) - assert_greater_than(expected_time_with_buffer, actual_time) + assert_equal(actual_time, expected_time) if __name__ == '__main__': diff --git a/test/functional/wallet_groups.py b/test/functional/wallet_groups.py index eb305c5fa2..e5e4cf03bf 100755 --- a/test/functional/wallet_groups.py +++ b/test/functional/wallet_groups.py @@ -26,6 +26,11 @@ class WalletGroupTest(BitcoinTestFramework): ["-maxapsfee=0.00002719"], ["-maxapsfee=0.00002720"], ] + + for args in self.extra_args: + args.append("-whitelist=noban@127.0.0.1") # whitelist peers to speed up tx relay / mempool sync + args.append(f"-paytxfee={20 * 1e3 / 1e8}") # apply feerate of 20 sats/vB across all nodes + self.rpc_timeout = 480 def skip_test_if_missing_module(self): @@ -150,7 +155,7 @@ class WalletGroupTest(BitcoinTestFramework): assert_equal(2, len(tx6["vout"])) # Empty out node2's wallet - self.nodes[2].sendtoaddress(address=self.nodes[0].getnewaddress(), amount=self.nodes[2].getbalance(), subtractfeefromamount=True) + self.nodes[2].sendall(recipients=[self.nodes[0].getnewaddress()]) self.sync_all() self.generate(self.nodes[0], 1) diff --git a/test/functional/wallet_hd.py b/test/functional/wallet_hd.py index ac878ea0aa..220c856498 100755 --- a/test/functional/wallet_hd.py +++ b/test/functional/wallet_hd.py @@ -20,6 +20,10 @@ class WalletHDTest(BitcoinTestFramework): self.setup_clean_chain = True self.num_nodes = 2 self.extra_args = [[], ['-keypool=0']] + # whitelist peers to speed up tx relay / mempool sync + for args in self.extra_args: + args.append("-whitelist=noban@127.0.0.1") + self.supports_cli = False def skip_test_if_missing_module(self): @@ -173,8 +177,8 @@ class WalletHDTest(BitcoinTestFramework): # Sethdseed parameter validity assert_raises_rpc_error(-1, 'sethdseed', self.nodes[0].sethdseed, False, new_seed, 0) assert_raises_rpc_error(-5, "Invalid private key", self.nodes[1].sethdseed, False, "not_wif") - assert_raises_rpc_error(-1, "JSON value is not a boolean as expected", self.nodes[1].sethdseed, "Not_bool") - assert_raises_rpc_error(-1, "JSON value is not a string as expected", self.nodes[1].sethdseed, False, True) + assert_raises_rpc_error(-3, "JSON value of type string is not of expected type bool", self.nodes[1].sethdseed, "Not_bool") + assert_raises_rpc_error(-3, "JSON value of type bool is not of expected type string", self.nodes[1].sethdseed, False, True) assert_raises_rpc_error(-5, "Already have this key", self.nodes[1].sethdseed, False, new_seed) assert_raises_rpc_error(-5, "Already have this key", self.nodes[1].sethdseed, False, self.nodes[1].dumpprivkey(self.nodes[1].getnewaddress())) diff --git a/test/functional/wallet_import_rescan.py b/test/functional/wallet_import_rescan.py index d9acc8cea5..085ad51c79 100755 --- a/test/functional/wallet_import_rescan.py +++ b/test/functional/wallet_import_rescan.py @@ -87,6 +87,7 @@ class Variant(collections.namedtuple("Variant", "call data address_type rescan p assert_equal(len(txs), self.expected_txs) addresses = self.node.listreceivedbyaddress(minconf=0, include_watchonly=True, address_filter=self.address['address']) + if self.expected_txs: assert_equal(len(addresses[0]["txids"]), self.expected_txs) @@ -98,13 +99,18 @@ class Variant(collections.namedtuple("Variant", "call data address_type rescan p assert_equal(tx["category"], "receive") assert_equal(tx["label"], self.label) assert_equal(tx["txid"], txid) - assert_equal(tx["confirmations"], 1 + current_height - confirmation_height) - assert "trusted" not in tx + + # If no confirmation height is given, the tx is still in the + # mempool. + confirmations = (1 + current_height - confirmation_height) if confirmation_height else 0 + assert_equal(tx["confirmations"], confirmations) + if confirmations: + assert "trusted" not in tx address, = [ad for ad in addresses if txid in ad["txids"]] assert_equal(address["address"], self.address["address"]) assert_equal(address["amount"], self.expected_balance) - assert_equal(address["confirmations"], 1 + current_height - confirmation_height) + assert_equal(address["confirmations"], confirmations) # Verify the transaction is correctly marked watchonly depending on # whether the transaction pays to an imported public key or # imported private key. The test setup ensures that transaction @@ -162,11 +168,12 @@ class ImportRescanTest(BitcoinTestFramework): self.import_deterministic_coinbase_privkeys() self.stop_nodes() - self.start_nodes() + self.start_nodes(extra_args=[["-whitelist=noban@127.0.0.1"]] * self.num_nodes) for i in range(1, self.num_nodes): self.connect_nodes(i, 0) def run_test(self): + # Create one transaction on node 0 with a unique amount for # each possible type of wallet import RPC. for i, variant in enumerate(IMPORT_VARIANTS): @@ -207,7 +214,7 @@ class ImportRescanTest(BitcoinTestFramework): variant.check() # Create new transactions sending to each address. - for i, variant in enumerate(IMPORT_VARIANTS): + for variant in IMPORT_VARIANTS: variant.sent_amount = get_rand_amount() variant.sent_txid = self.nodes[0].sendtoaddress(variant.address["address"], variant.sent_amount) self.generate(self.nodes[0], 1) # Generate one block for each send @@ -223,6 +230,46 @@ class ImportRescanTest(BitcoinTestFramework): variant.expected_txs += 1 variant.check(variant.sent_txid, variant.sent_amount, variant.confirmation_height) + self.log.info('Test that the mempool is rescanned as well if the rescan parameter is set to true') + + # The late timestamp and pruned variants are not necessary when testing mempool rescan + mempool_variants = [variant for variant in IMPORT_VARIANTS if variant.rescan != Rescan.late_timestamp and not variant.prune] + # No further blocks are mined so the timestamp will stay the same + timestamp = self.nodes[0].getblockheader(self.nodes[0].getbestblockhash())["time"] + + # Create one transaction on node 0 with a unique amount for + # each possible type of wallet import RPC. + for i, variant in enumerate(mempool_variants): + variant.label = "mempool label {} {}".format(i, variant) + variant.address = self.nodes[1].getaddressinfo(self.nodes[1].getnewaddress( + label=variant.label, + address_type=variant.address_type.value, + )) + variant.key = self.nodes[1].dumpprivkey(variant.address["address"]) + variant.initial_amount = get_rand_amount() + variant.initial_txid = self.nodes[0].sendtoaddress(variant.address["address"], variant.initial_amount) + variant.confirmation_height = 0 + variant.timestamp = timestamp + + assert_equal(len(self.nodes[0].getrawmempool()), len(mempool_variants)) + self.sync_mempools() + + # For each variation of wallet key import, invoke the import RPC and + # check the results from getbalance and listtransactions. + for variant in mempool_variants: + self.log.info('Run import for mempool variant {}'.format(variant)) + expect_rescan = variant.rescan == Rescan.yes + variant.node = self.nodes[2 + IMPORT_NODES.index(ImportNode(variant.prune, expect_rescan))] + variant.do_import(variant.timestamp) + if expect_rescan: + variant.expected_balance = variant.initial_amount + variant.expected_txs = 1 + variant.check(variant.initial_txid, variant.initial_amount) + else: + variant.expected_balance = 0 + variant.expected_txs = 0 + variant.check() + if __name__ == "__main__": ImportRescanTest().main() diff --git a/test/functional/wallet_importdescriptors.py b/test/functional/wallet_importdescriptors.py index ac74ff2484..9744009af8 100755 --- a/test/functional/wallet_importdescriptors.py +++ b/test/functional/wallet_importdescriptors.py @@ -35,6 +35,9 @@ class ImportDescriptorsTest(BitcoinTestFramework): self.extra_args = [["-addresstype=legacy"], ["-addresstype=bech32", "-keypool=5"] ] + # whitelist peers to speed up tx relay / mempool sync + for args in self.extra_args: + args.append("-whitelist=noban@127.0.0.1") self.setup_clean_chain = True self.wallet_names = [] @@ -454,7 +457,7 @@ class ImportDescriptorsTest(BitcoinTestFramework): send_txid = wmulti_priv.sendtoaddress(w0.getnewaddress(), 8) decoded = wmulti_priv.gettransaction(txid=send_txid, verbose=True)['decoded'] assert_equal(len(decoded['vin'][0]['txinwitness']), 4) - self.generate(self.nodes[0], 6) + self.sync_all() self.nodes[1].createwallet(wallet_name="wmulti_pub", disable_private_keys=True, blank=True, descriptors=True) wmulti_pub = self.nodes[1].get_wallet_rpc("wmulti_pub") @@ -480,7 +483,9 @@ class ImportDescriptorsTest(BitcoinTestFramework): addr = wmulti_pub.getnewaddress('', 'bech32') assert_equal(addr, 'bcrt1qp8s25ckjl7gr6x2q3dx3tn2pytwp05upkjztk6ey857tt50r5aeqn6mvr9') # Derived at m/84'/0'/0'/1 change_addr = wmulti_pub.getrawchangeaddress('bech32') - assert_equal(change_addr, 'bcrt1qt9uhe3a9hnq7vajl7a094z4s3crm9ttf8zw3f5v9gr2nyd7e3lnsy44n8e') + assert_equal(change_addr, 'bcrt1qzxl0qz2t88kljdnkzg4n4gapr6kte26390gttrg79x66nt4p04fssj53nl') + assert(send_txid in self.nodes[0].getrawmempool(True)) + assert(send_txid in (x['txid'] for x in wmulti_pub.listunspent(0))) assert_equal(wmulti_pub.getwalletinfo()['keypoolsize'], 999) # generate some utxos for next tests diff --git a/test/functional/wallet_importmulti.py b/test/functional/wallet_importmulti.py index 3953851491..62a1a3341d 100755 --- a/test/functional/wallet_importmulti.py +++ b/test/functional/wallet_importmulti.py @@ -874,6 +874,25 @@ class ImportMultiTest(BitcoinTestFramework): addr = wrpc.getnewaddress('', 'bech32') assert_equal(addr, addresses[i]) + # Create wallet with passphrase + self.log.info('Test watchonly imports on a wallet with a passphrase, without unlocking') + self.nodes[1].createwallet(wallet_name='w1', blank=True, passphrase='pass') + wrpc = self.nodes[1].get_wallet_rpc('w1') + assert_raises_rpc_error(-13, "Please enter the wallet passphrase with walletpassphrase first.", + wrpc.importmulti, [{ + 'desc': descsum_create('wpkh(' + pub1 + ')'), + "timestamp": "now", + }]) + + result = wrpc.importmulti( + [{ + 'desc': descsum_create('wpkh(' + pub1 + ')'), + "timestamp": "now", + "watchonly": True, + }] + ) + assert result[0]['success'] + if __name__ == '__main__': ImportMultiTest().main() diff --git a/test/functional/wallet_listdescriptors.py b/test/functional/wallet_listdescriptors.py index 202ef92887..d5372f5aee 100755 --- a/test/functional/wallet_listdescriptors.py +++ b/test/functional/wallet_listdescriptors.py @@ -52,6 +52,10 @@ class ListDescriptorsTest(BitcoinTestFramework): assert item['range'] == [0, 0] assert item['timestamp'] is not None + self.log.info('Test that descriptor strings are returned in lexicographically sorted order.') + descriptor_strings = [descriptor['desc'] for descriptor in result['descriptors']] + assert_equal(descriptor_strings, sorted(descriptor_strings)) + self.log.info('Test descriptors with hardened derivations are listed in importable form.') xprv = 'tprv8ZgxMBicQKsPeuVhWwi6wuMQGfPKi9Li5GtX35jVNknACgqe3CY4g5xgkfDDJcmtF7o1QnxWDRYw4H5P26PXq7sbcUkEqeR4fg3Kxp2tigg' xpub_acc = 'tpubDCMVLhErorrAGfApiJSJzEKwqeaf2z3NrkVMxgYQjZLzMjXMBeRw2muGNYbvaekAE8rUFLftyEar4LdrG2wXyyTJQZ26zptmeTEjPTaATts' diff --git a/test/functional/wallet_listreceivedby.py b/test/functional/wallet_listreceivedby.py index db1d8eb54a..f1d7de2f27 100755 --- a/test/functional/wallet_listreceivedby.py +++ b/test/functional/wallet_listreceivedby.py @@ -18,6 +18,8 @@ from test_framework.wallet_util import test_address class ReceivedByTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 2 + # whitelist peers to speed up tx relay / mempool sync + self.extra_args = [["-whitelist=noban@127.0.0.1"]] * self.num_nodes def skip_test_if_missing_module(self): self.skip_if_no_wallet() @@ -57,6 +59,11 @@ class ReceivedByTest(BitcoinTestFramework): {"address": empty_addr}, {"address": empty_addr, "label": "", "amount": 0, "confirmations": 0, "txids": []}) + # No returned addy should be a change addr + for node in self.nodes: + for addr_obj in node.listreceivedbyaddress(): + assert_equal(node.getaddressinfo(addr_obj["address"])["ischange"], False) + # Test Address filtering # Only on addr expected = {"address": addr, "label": "", "amount": Decimal("0.1"), "confirmations": 10, "txids": [txid, ]} diff --git a/test/functional/wallet_listsinceblock.py b/test/functional/wallet_listsinceblock.py index fc06565983..f259449bef 100755 --- a/test/functional/wallet_listsinceblock.py +++ b/test/functional/wallet_listsinceblock.py @@ -6,9 +6,10 @@ from test_framework.address import key_to_p2wpkh from test_framework.blocktools import COINBASE_MATURITY +from test_framework.descriptors import descsum_create from test_framework.key import ECKey from test_framework.test_framework import BitcoinTestFramework -from test_framework.messages import BIP125_SEQUENCE_NUMBER +from test_framework.messages import MAX_BIP125_RBF_SEQUENCE from test_framework.util import ( assert_array_result, assert_equal, @@ -22,6 +23,8 @@ class ListSinceBlockTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 4 self.setup_clean_chain = True + # whitelist peers to speed up tx relay / mempool sync + self.extra_args = [["-whitelist=noban@127.0.0.1"]] * self.num_nodes def skip_test_if_missing_module(self): self.skip_if_no_wallet() @@ -39,6 +42,9 @@ class ListSinceBlockTest(BitcoinTestFramework): self.test_double_send() self.double_spends_filtered() self.test_targetconfirmations() + if self.options.descriptors: + self.test_desc() + self.test_send_to_self() def test_no_blockhash(self): self.log.info("Test no blockhash") @@ -346,7 +352,7 @@ class ListSinceBlockTest(BitcoinTestFramework): dest_address = spending_node.getnewaddress() tx_input = dict( - sequence=BIP125_SEQUENCE_NUMBER, **next(u for u in spending_node.listunspent())) + sequence=MAX_BIP125_RBF_SEQUENCE, **next(u for u in spending_node.listunspent())) rawtx = spending_node.createrawtransaction( [tx_input], {dest_address: tx_input["amount"] - Decimal("0.00051000"), spending_node.getrawchangeaddress(): Decimal("0.00050000")}) @@ -383,5 +389,65 @@ class ListSinceBlockTest(BitcoinTestFramework): assert_equal(original_found, False) assert_equal(double_found, False) + def test_desc(self): + """Make sure we can track coins by descriptor.""" + self.log.info("Test descriptor lookup by scriptPubKey.") + + # Create a watchonly wallet tracking two multisig descriptors. + multi_a = descsum_create("wsh(multi(1,tpubD6NzVbkrYhZ4YBNjUo96Jxd1u4XKWgnoc7LsA1jz3Yc2NiDbhtfBhaBtemB73n9V5vtJHwU6FVXwggTbeoJWQ1rzdz8ysDuQkpnaHyvnvzR/*,tpubD6NzVbkrYhZ4YHdDGMAYGaWxMSC1B6tPRTHuU5t3BcfcS3nrF523iFm5waFd1pP3ZvJt4Jr8XmCmsTBNx5suhcSgtzpGjGMASR3tau1hJz4/*))") + multi_b = descsum_create("wsh(multi(1,tpubD6NzVbkrYhZ4YHdDGMAYGaWxMSC1B6tPRTHuU5t3BcfcS3nrF523iFm5waFd1pP3ZvJt4Jr8XmCmsTBNx5suhcSgtzpGjGMASR3tau1hJz4/*,tpubD6NzVbkrYhZ4Y2RLiuEzNQkntjmsLpPYDm3LTRBYynUQtDtpzeUKAcb9sYthSFL3YR74cdFgF5mW8yKxv2W2CWuZDFR2dUpE5PF9kbrVXNZ/*))") + self.nodes[0].createwallet(wallet_name="wo", descriptors=True, disable_private_keys=True) + wo_wallet = self.nodes[0].get_wallet_rpc("wo") + wo_wallet.importdescriptors([ + { + "desc": multi_a, + "active": False, + "timestamp": "now", + }, + { + "desc": multi_b, + "active": False, + "timestamp": "now", + }, + ]) + + # Send a coin to each descriptor. + assert_equal(len(wo_wallet.listsinceblock()["transactions"]), 0) + addr_a = self.nodes[0].deriveaddresses(multi_a, 0)[0] + addr_b = self.nodes[0].deriveaddresses(multi_b, 0)[0] + self.nodes[2].sendtoaddress(addr_a, 1) + self.nodes[2].sendtoaddress(addr_b, 2) + self.generate(self.nodes[2], 1) + + # We can identify on which descriptor each coin was received. + coins = wo_wallet.listsinceblock()["transactions"] + assert_equal(len(coins), 2) + coin_a = next(c for c in coins if c["amount"] == 1) + assert_equal(coin_a["parent_descs"][0], multi_a) + coin_b = next(c for c in coins if c["amount"] == 2) + assert_equal(coin_b["parent_descs"][0], multi_b) + + def test_send_to_self(self): + """We can make listsinceblock output our change outputs.""" + self.log.info("Test the inclusion of change outputs in the output.") + + # Create a UTxO paying to one of our change addresses. + block_hash = self.nodes[2].getbestblockhash() + addr = self.nodes[2].getrawchangeaddress() + self.nodes[2].sendtoaddress(addr, 1) + + # If we don't list change, we won't have an entry for it. + coins = self.nodes[2].listsinceblock(blockhash=block_hash)["transactions"] + assert not any(c["address"] == addr for c in coins) + + # Now if we list change, we'll get both the send (to a change address) and + # the actual change. + res = self.nodes[2].listsinceblock(blockhash=block_hash, include_change=True) + coins = [entry for entry in res["transactions"] if entry["category"] == "receive"] + assert_equal(len(coins), 2) + assert any(c["address"] == addr for c in coins) + assert all(self.nodes[2].getaddressinfo(c["address"])["ischange"] for c in coins) + + if __name__ == '__main__': ListSinceBlockTest().main() diff --git a/test/functional/wallet_listtransactions.py b/test/functional/wallet_listtransactions.py index f75877f256..7c16b6328d 100755 --- a/test/functional/wallet_listtransactions.py +++ b/test/functional/wallet_listtransactions.py @@ -25,7 +25,7 @@ class ListTransactionsTest(BitcoinTestFramework): self.num_nodes = 3 # This test isn't testing txn relay/timing, so set whitelist on the # peers for instant txn relay. This speeds up the test run time 2-3x. - self.extra_args = [["-whitelist=noban@127.0.0.1"]] * self.num_nodes + self.extra_args = [["-whitelist=noban@127.0.0.1", "-walletrbf=0"]] * self.num_nodes def skip_test_if_missing_module(self): self.skip_if_no_wallet() @@ -146,7 +146,7 @@ class ListTransactionsTest(BitcoinTestFramework): # Create tx2 using createrawtransaction inputs = [{"txid": utxo_to_use["txid"], "vout": utxo_to_use["vout"]}] outputs = {self.nodes[0].getnewaddress(): 0.999} - tx2 = self.nodes[1].createrawtransaction(inputs, outputs) + tx2 = self.nodes[1].createrawtransaction(inputs=inputs, outputs=outputs, replaceable=False) tx2_signed = self.nodes[1].signrawtransactionwithwallet(tx2)["hex"] txid_2 = self.nodes[1].sendrawtransaction(tx2_signed) @@ -178,7 +178,7 @@ class ListTransactionsTest(BitcoinTestFramework): utxo_to_use = get_unconfirmed_utxo_entry(self.nodes[1], txid_3) inputs = [{"txid": txid_3, "vout": utxo_to_use["vout"]}] outputs = {self.nodes[0].getnewaddress(): 0.997} - tx4 = self.nodes[1].createrawtransaction(inputs, outputs) + tx4 = self.nodes[1].createrawtransaction(inputs=inputs, outputs=outputs, replaceable=False) tx4_signed = self.nodes[1].signrawtransactionwithwallet(tx4)["hex"] txid_4 = self.nodes[1].sendrawtransaction(tx4_signed) diff --git a/test/functional/wallet_migration.py b/test/functional/wallet_migration.py new file mode 100755 index 0000000000..3c1cb6ac32 --- /dev/null +++ b/test/functional/wallet_migration.py @@ -0,0 +1,407 @@ +#!/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 Migrating a wallet from legacy to descriptor.""" + +import os +import random +from test_framework.descriptors import descsum_create +from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import ( + assert_equal, + assert_raises_rpc_error, + find_vout_for_address, +) +from test_framework.wallet_util import ( + get_generate_key, +) + + +class WalletMigrationTest(BitcoinTestFramework): + def set_test_params(self): + self.setup_clean_chain = True + self.num_nodes = 1 + self.extra_args = [[]] + self.supports_cli = False + + def skip_test_if_missing_module(self): + self.skip_if_no_wallet() + self.skip_if_no_sqlite() + self.skip_if_no_bdb() + + def assert_is_sqlite(self, wallet_name): + wallet_file_path = os.path.join(self.nodes[0].datadir, "regtest/wallets", wallet_name, self.wallet_data_filename) + with open(wallet_file_path, 'rb') as f: + file_magic = f.read(16) + assert_equal(file_magic, b'SQLite format 3\x00') + assert_equal(self.nodes[0].get_wallet_rpc(wallet_name).getwalletinfo()["format"], "sqlite") + + def create_legacy_wallet(self, wallet_name): + self.nodes[0].createwallet(wallet_name=wallet_name) + wallet = self.nodes[0].get_wallet_rpc(wallet_name) + assert_equal(wallet.getwalletinfo()["descriptors"], False) + assert_equal(wallet.getwalletinfo()["format"], "bdb") + return wallet + + def assert_addr_info_equal(self, addr_info, addr_info_old): + assert_equal(addr_info["address"], addr_info_old["address"]) + assert_equal(addr_info["scriptPubKey"], addr_info_old["scriptPubKey"]) + assert_equal(addr_info["ismine"], addr_info_old["ismine"]) + assert_equal(addr_info["hdkeypath"], addr_info_old["hdkeypath"]) + assert_equal(addr_info["solvable"], addr_info_old["solvable"]) + assert_equal(addr_info["ischange"], addr_info_old["ischange"]) + assert_equal(addr_info["hdmasterfingerprint"], addr_info_old["hdmasterfingerprint"]) + + def assert_list_txs_equal(self, received_list_txs, expected_list_txs): + for d in received_list_txs: + if "parent_descs" in d: + del d["parent_descs"] + for d in expected_list_txs: + if "parent_descs" in d: + del d["parent_descs"] + assert_equal(received_list_txs, expected_list_txs) + + def test_basic(self): + default = self.nodes[0].get_wallet_rpc(self.default_wallet_name) + + self.log.info("Test migration of a basic keys only wallet without balance") + basic0 = self.create_legacy_wallet("basic0") + + addr = basic0.getnewaddress() + change = basic0.getrawchangeaddress() + + old_addr_info = basic0.getaddressinfo(addr) + old_change_addr_info = basic0.getaddressinfo(change) + assert_equal(old_addr_info["ismine"], True) + assert_equal(old_addr_info["hdkeypath"], "m/0'/0'/0'") + assert_equal(old_change_addr_info["ismine"], True) + assert_equal(old_change_addr_info["hdkeypath"], "m/0'/1'/0'") + + # Note: migration could take a while. + basic0.migratewallet() + + # Verify created descriptors + assert_equal(basic0.getwalletinfo()["descriptors"], True) + self.assert_is_sqlite("basic0") + + # The wallet should create the following descriptors: + # * BIP32 descriptors in the form of "0'/0'/*" and "0'/1'/*" (2 descriptors) + # * BIP44 descriptors in the form of "44'/1'/0'/0/*" and "44'/1'/0'/1/*" (2 descriptors) + # * BIP49 descriptors, P2SH(P2WPKH), in the form of "86'/1'/0'/0/*" and "86'/1'/0'/1/*" (2 descriptors) + # * BIP84 descriptors, P2WPKH, in the form of "84'/1'/0'/1/*" and "84'/1'/0'/1/*" (2 descriptors) + # * BIP86 descriptors, P2TR, in the form of "86'/1'/0'/0/*" and "86'/1'/0'/1/*" (2 descriptors) + # * A combo(PK) descriptor for the wallet master key. + # So, should have a total of 11 descriptors on it. + assert_equal(len(basic0.listdescriptors()["descriptors"]), 11) + + # Compare addresses info + addr_info = basic0.getaddressinfo(addr) + change_addr_info = basic0.getaddressinfo(change) + self.assert_addr_info_equal(addr_info, old_addr_info) + self.assert_addr_info_equal(change_addr_info, old_change_addr_info) + + addr_info = basic0.getaddressinfo(basic0.getnewaddress("", "bech32")) + assert_equal(addr_info["hdkeypath"], "m/84'/1'/0'/0/0") + + self.log.info("Test migration of a basic keys only wallet with a balance") + basic1 = self.create_legacy_wallet("basic1") + + for _ in range(0, 10): + default.sendtoaddress(basic1.getnewaddress(), 1) + + self.generate(self.nodes[0], 1) + + for _ in range(0, 5): + basic1.sendtoaddress(default.getnewaddress(), 0.5) + + self.generate(self.nodes[0], 1) + bal = basic1.getbalance() + txs = basic1.listtransactions() + + basic1.migratewallet() + assert_equal(basic1.getwalletinfo()["descriptors"], True) + self.assert_is_sqlite("basic1") + assert_equal(basic1.getbalance(), bal) + self.assert_list_txs_equal(basic1.listtransactions(), txs) + + # restart node and verify that everything is still there + self.restart_node(0) + default = self.nodes[0].get_wallet_rpc(self.default_wallet_name) + self.nodes[0].loadwallet("basic1") + basic1 = self.nodes[0].get_wallet_rpc("basic1") + assert_equal(basic1.getwalletinfo()["descriptors"], True) + self.assert_is_sqlite("basic1") + assert_equal(basic1.getbalance(), bal) + self.assert_list_txs_equal(basic1.listtransactions(), txs) + + self.log.info("Test migration of a wallet with balance received on the seed") + basic2 = self.create_legacy_wallet("basic2") + basic2_seed = get_generate_key() + basic2.sethdseed(True, basic2_seed.privkey) + assert_equal(basic2.getbalance(), 0) + + # Receive coins on different output types for the same seed + basic2_balance = 0 + for addr in [basic2_seed.p2pkh_addr, basic2_seed.p2wpkh_addr, basic2_seed.p2sh_p2wpkh_addr]: + send_value = random.randint(1, 4) + default.sendtoaddress(addr, send_value) + basic2_balance += send_value + self.generate(self.nodes[0], 1) + assert_equal(basic2.getbalance(), basic2_balance) + basic2_txs = basic2.listtransactions() + + # Now migrate and test that we still see have the same balance/transactions + basic2.migratewallet() + assert_equal(basic2.getwalletinfo()["descriptors"], True) + self.assert_is_sqlite("basic2") + assert_equal(basic2.getbalance(), basic2_balance) + self.assert_list_txs_equal(basic2.listtransactions(), basic2_txs) + + def test_multisig(self): + default = self.nodes[0].get_wallet_rpc(self.default_wallet_name) + + # Contrived case where all the multisig keys are in a single wallet + self.log.info("Test migration of a wallet with all keys for a multisig") + multisig0 = self.create_legacy_wallet("multisig0") + addr1 = multisig0.getnewaddress() + addr2 = multisig0.getnewaddress() + addr3 = multisig0.getnewaddress() + + ms_info = multisig0.addmultisigaddress(2, [addr1, addr2, addr3]) + + multisig0.migratewallet() + assert_equal(multisig0.getwalletinfo()["descriptors"], True) + self.assert_is_sqlite("multisig0") + ms_addr_info = multisig0.getaddressinfo(ms_info["address"]) + assert_equal(ms_addr_info["ismine"], True) + assert_equal(ms_addr_info["desc"], ms_info["descriptor"]) + assert_equal("multisig0_watchonly" in self.nodes[0].listwallets(), False) + assert_equal("multisig0_solvables" in self.nodes[0].listwallets(), False) + + pub1 = multisig0.getaddressinfo(addr1)["pubkey"] + pub2 = multisig0.getaddressinfo(addr2)["pubkey"] + + # Some keys in multisig do not belong to this wallet + self.log.info("Test migration of a wallet that has some keys in a multisig") + self.nodes[0].createwallet(wallet_name="multisig1") + multisig1 = self.nodes[0].get_wallet_rpc("multisig1") + ms_info = multisig1.addmultisigaddress(2, [multisig1.getnewaddress(), pub1, pub2]) + ms_info2 = multisig1.addmultisigaddress(2, [multisig1.getnewaddress(), pub1, pub2]) + assert_equal(multisig1.getwalletinfo()["descriptors"], False) + + addr1 = ms_info["address"] + addr2 = ms_info2["address"] + txid = default.sendtoaddress(addr1, 10) + multisig1.importaddress(addr1) + assert_equal(multisig1.getaddressinfo(addr1)["ismine"], False) + assert_equal(multisig1.getaddressinfo(addr1)["iswatchonly"], True) + assert_equal(multisig1.getaddressinfo(addr1)["solvable"], True) + self.generate(self.nodes[0], 1) + multisig1.gettransaction(txid) + assert_equal(multisig1.getbalances()["watchonly"]["trusted"], 10) + assert_equal(multisig1.getaddressinfo(addr2)["ismine"], False) + assert_equal(multisig1.getaddressinfo(addr2)["iswatchonly"], False) + assert_equal(multisig1.getaddressinfo(addr2)["solvable"], True) + + # Migrating multisig1 should see the multisig is no longer part of multisig1 + # A new wallet multisig1_watchonly is created which has the multisig address + # Transaction to multisig is in multisig1_watchonly and not multisig1 + multisig1.migratewallet() + assert_equal(multisig1.getwalletinfo()["descriptors"], True) + self.assert_is_sqlite("multisig1") + assert_equal(multisig1.getaddressinfo(addr1)["ismine"], False) + assert_equal(multisig1.getaddressinfo(addr1)["iswatchonly"], False) + assert_equal(multisig1.getaddressinfo(addr1)["solvable"], False) + assert_raises_rpc_error(-5, "Invalid or non-wallet transaction id", multisig1.gettransaction, txid) + assert_equal(multisig1.getbalance(), 0) + assert_equal(multisig1.listtransactions(), []) + + assert_equal("multisig1_watchonly" in self.nodes[0].listwallets(), True) + ms1_watchonly = self.nodes[0].get_wallet_rpc("multisig1_watchonly") + ms1_wallet_info = ms1_watchonly.getwalletinfo() + assert_equal(ms1_wallet_info['descriptors'], True) + assert_equal(ms1_wallet_info['private_keys_enabled'], False) + self.assert_is_sqlite("multisig1_watchonly") + assert_equal(ms1_watchonly.getaddressinfo(addr1)["ismine"], True) + assert_equal(ms1_watchonly.getaddressinfo(addr1)["solvable"], True) + # Because addr2 was not being watched, it isn't in multisig1_watchonly but rather multisig1_solvables + assert_equal(ms1_watchonly.getaddressinfo(addr2)["ismine"], False) + assert_equal(ms1_watchonly.getaddressinfo(addr2)["solvable"], False) + ms1_watchonly.gettransaction(txid) + assert_equal(ms1_watchonly.getbalance(), 10) + + # Migrating multisig1 should see the second multisig is no longer part of multisig1 + # A new wallet multisig1_solvables is created which has the second address + # This should have no transactions + assert_equal("multisig1_solvables" in self.nodes[0].listwallets(), True) + ms1_solvable = self.nodes[0].get_wallet_rpc("multisig1_solvables") + ms1_wallet_info = ms1_solvable.getwalletinfo() + assert_equal(ms1_wallet_info['descriptors'], True) + assert_equal(ms1_wallet_info['private_keys_enabled'], False) + self.assert_is_sqlite("multisig1_solvables") + assert_equal(ms1_solvable.getaddressinfo(addr1)["ismine"], False) + assert_equal(ms1_solvable.getaddressinfo(addr1)["solvable"], False) + assert_equal(ms1_solvable.getaddressinfo(addr2)["ismine"], True) + assert_equal(ms1_solvable.getaddressinfo(addr2)["solvable"], True) + assert_equal(ms1_solvable.getbalance(), 0) + assert_equal(ms1_solvable.listtransactions(), []) + + + def test_other_watchonly(self): + default = self.nodes[0].get_wallet_rpc(self.default_wallet_name) + + # Wallet with an imported address. Should be the same thing as the multisig test + self.log.info("Test migration of a wallet with watchonly imports") + self.nodes[0].createwallet(wallet_name="imports0") + imports0 = self.nodes[0].get_wallet_rpc("imports0") + assert_equal(imports0.getwalletinfo()["descriptors"], False) + + # Exteranl address label + imports0.setlabel(default.getnewaddress(), "external") + + # Normal non-watchonly tx + received_addr = imports0.getnewaddress() + imports0.setlabel(received_addr, "Receiving") + received_txid = default.sendtoaddress(received_addr, 10) + + # Watchonly tx + import_addr = default.getnewaddress() + imports0.importaddress(import_addr) + imports0.setlabel(import_addr, "imported") + received_watchonly_txid = default.sendtoaddress(import_addr, 10) + + # Received watchonly tx that is then spent + import_sent_addr = default.getnewaddress() + imports0.importaddress(import_sent_addr) + received_sent_watchonly_txid = default.sendtoaddress(import_sent_addr, 10) + received_sent_watchonly_vout = find_vout_for_address(self.nodes[0], received_sent_watchonly_txid, import_sent_addr) + send = default.sendall(recipients=[default.getnewaddress()], options={"inputs": [{"txid": received_sent_watchonly_txid, "vout": received_sent_watchonly_vout}]}) + sent_watchonly_txid = send["txid"] + + self.generate(self.nodes[0], 1) + + balances = imports0.getbalances() + spendable_bal = balances["mine"]["trusted"] + watchonly_bal = balances["watchonly"]["trusted"] + assert_equal(len(imports0.listtransactions(include_watchonly=True)), 4) + + # Migrate + imports0.migratewallet() + assert_equal(imports0.getwalletinfo()["descriptors"], True) + self.assert_is_sqlite("imports0") + assert_raises_rpc_error(-5, "Invalid or non-wallet transaction id", imports0.gettransaction, received_watchonly_txid) + assert_raises_rpc_error(-5, "Invalid or non-wallet transaction id", imports0.gettransaction, received_sent_watchonly_txid) + assert_raises_rpc_error(-5, "Invalid or non-wallet transaction id", imports0.gettransaction, sent_watchonly_txid) + assert_equal(len(imports0.listtransactions(include_watchonly=True)), 1) + imports0.gettransaction(received_txid) + assert_equal(imports0.getbalance(), spendable_bal) + + assert_equal("imports0_watchonly" in self.nodes[0].listwallets(), True) + watchonly = self.nodes[0].get_wallet_rpc("imports0_watchonly") + watchonly_info = watchonly.getwalletinfo() + assert_equal(watchonly_info["descriptors"], True) + self.assert_is_sqlite("imports0_watchonly") + assert_equal(watchonly_info["private_keys_enabled"], False) + watchonly.gettransaction(received_watchonly_txid) + watchonly.gettransaction(received_sent_watchonly_txid) + watchonly.gettransaction(sent_watchonly_txid) + assert_equal(watchonly.getbalance(), watchonly_bal) + assert_raises_rpc_error(-5, "Invalid or non-wallet transaction id", watchonly.gettransaction, received_txid) + assert_equal(len(watchonly.listtransactions(include_watchonly=True)), 3) + + def test_no_privkeys(self): + default = self.nodes[0].get_wallet_rpc(self.default_wallet_name) + + # Migrating an actual watchonly wallet should not create a new watchonly wallet + self.log.info("Test migration of a pure watchonly wallet") + self.nodes[0].createwallet(wallet_name="watchonly0", disable_private_keys=True) + watchonly0 = self.nodes[0].get_wallet_rpc("watchonly0") + info = watchonly0.getwalletinfo() + assert_equal(info["descriptors"], False) + assert_equal(info["private_keys_enabled"], False) + + addr = default.getnewaddress() + desc = default.getaddressinfo(addr)["desc"] + res = watchonly0.importmulti([ + { + "desc": desc, + "watchonly": True, + "timestamp": "now", + }]) + assert_equal(res[0]['success'], True) + default.sendtoaddress(addr, 10) + self.generate(self.nodes[0], 1) + + watchonly0.migratewallet() + assert_equal("watchonly0_watchonly" in self.nodes[0].listwallets(), False) + info = watchonly0.getwalletinfo() + assert_equal(info["descriptors"], True) + assert_equal(info["private_keys_enabled"], False) + self.assert_is_sqlite("watchonly0") + + # Migrating a wallet with pubkeys added to the keypool + self.log.info("Test migration of a pure watchonly wallet with pubkeys in keypool") + self.nodes[0].createwallet(wallet_name="watchonly1", disable_private_keys=True) + watchonly1 = self.nodes[0].get_wallet_rpc("watchonly1") + info = watchonly1.getwalletinfo() + assert_equal(info["descriptors"], False) + assert_equal(info["private_keys_enabled"], False) + + addr1 = default.getnewaddress(address_type="bech32") + addr2 = default.getnewaddress(address_type="bech32") + desc1 = default.getaddressinfo(addr1)["desc"] + desc2 = default.getaddressinfo(addr2)["desc"] + res = watchonly1.importmulti([ + { + "desc": desc1, + "keypool": True, + "timestamp": "now", + }, + { + "desc": desc2, + "keypool": True, + "timestamp": "now", + } + ]) + assert_equal(res[0]["success"], True) + assert_equal(res[1]["success"], True) + # Before migrating, we can fetch addr1 from the keypool + assert_equal(watchonly1.getnewaddress(address_type="bech32"), addr1) + + watchonly1.migratewallet() + info = watchonly1.getwalletinfo() + assert_equal(info["descriptors"], True) + assert_equal(info["private_keys_enabled"], False) + self.assert_is_sqlite("watchonly1") + # After migrating, the "keypool" is empty + assert_raises_rpc_error(-4, "Error: This wallet has no available keys", watchonly1.getnewaddress) + + def test_pk_coinbases(self): + self.log.info("Test migration of a wallet using old pk() coinbases") + wallet = self.create_legacy_wallet("pkcb") + + addr = wallet.getnewaddress() + addr_info = wallet.getaddressinfo(addr) + desc = descsum_create("pk(" + addr_info["pubkey"] + ")") + + self.nodes[0].generatetodescriptor(1, desc, invalid_call=False) + + bals = wallet.getbalances() + + wallet.migratewallet() + + assert_equal(bals, wallet.getbalances()) + + def run_test(self): + self.generate(self.nodes[0], 101) + + # TODO: Test the actual records in the wallet for these tests too. The behavior may be correct, but the data written may not be what we actually want + self.test_basic() + self.test_multisig() + self.test_other_watchonly() + self.test_no_privkeys() + self.test_pk_coinbases() + +if __name__ == '__main__': + WalletMigrationTest().main() diff --git a/test/functional/wallet_miniscript.py b/test/functional/wallet_miniscript.py new file mode 100755 index 0000000000..2252f1e424 --- /dev/null +++ b/test/functional/wallet_miniscript.py @@ -0,0 +1,93 @@ +#!/usr/bin/env python3 +# Copyright (c) 2022 The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. +"""Test Miniscript descriptors integration in the wallet.""" + +from test_framework.descriptors import descsum_create +from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import assert_equal + + +MINISCRIPTS = [ + # One of two keys + "or_b(pk(tpubD6NzVbkrYhZ4XRMcMFMMFvzVt6jaDAtjZhD7JLwdPdMm9xa76DnxYYP7w9TZGJDVFkek3ArwVsuacheqqPog8TH5iBCX1wuig8PLXim4n9a/*),s:pk(tpubD6NzVbkrYhZ4WsqRzDmkL82SWcu42JzUvKWzrJHQ8EC2vEHRHkXj1De93sD3biLrKd8XGnamXURGjMbYavbszVDXpjXV2cGUERucLJkE6cy/*))", + # A script similar (same spending policy) to BOLT3's offered HTLC (with anchor outputs) + "or_d(pk(tpubD6NzVbkrYhZ4XRMcMFMMFvzVt6jaDAtjZhD7JLwdPdMm9xa76DnxYYP7w9TZGJDVFkek3ArwVsuacheqqPog8TH5iBCX1wuig8PLXim4n9a/*),and_v(and_v(v:pk(tpubD6NzVbkrYhZ4WsqRzDmkL82SWcu42JzUvKWzrJHQ8EC2vEHRHkXj1De93sD3biLrKd8XGnamXURGjMbYavbszVDXpjXV2cGUERucLJkE6cy/*),or_c(pk(tpubD6NzVbkrYhZ4YNwtTWrKRJQzQX3PjPKeUQg1gYh1hiLMkk1cw8SRLgB1yb7JzE8bHKNt6EcZXkJ6AqpCZL1aaRSjnG36mLgbQvJZBNsjWnG/*),v:hash160(7f999c905d5e35cefd0a37673f746eb13fba3640))),older(1)))", + # A Revault Unvault policy with the older() replaced by an after() + "andor(multi(2,tpubD6NzVbkrYhZ4YMQC15JS7QcrsAyfGrGiykweqMmPxTkEVScu7vCZLNpPXW1XphHwzsgmqdHWDQAfucbM72EEB1ZEyfgZxYvkZjYVXx1xS9p/*,tpubD6NzVbkrYhZ4WkCyc7E3z6g6NkypHMiecnwc4DpWHTPqFdteRGkEKukdrSSyJGNnGrHNMfy4BCw2UXo5soYRCtCDDfy4q8pc8oyB7RgTFv8/*),and_v(v:multi(4,030f64b922aee2fd597f104bc6cb3b670f1ca2c6c49b1071a1a6c010575d94fe5a,02abe475b199ec3d62fa576faee16a334fdb86ffb26dce75becebaaedf328ac3fe,0314f3dc33595b0d016bb522f6fe3a67680723d842c1b9b8ae6b59fdd8ab5cccb4,025eba3305bd3c829e4e1551aac7358e4178832c739e4fc4729effe428de0398ab),after(424242)),thresh(4,pkh(tpubD6NzVbkrYhZ4YVrNggiT2ptVHwnFbLBqDkCtV5HkxR4WtcRLAQReKTkqZGNcV6GE7cQsmpBzzSzhk16DUwB1gn1L7ZPnJF2dnNePP1uMBCY/*),a:pkh(tpubD6NzVbkrYhZ4YU9vM1s53UhD75UyJatx8EMzMZ3VUjR2FciNfLLkAw6a4pWACChzobTseNqdWk4G7ZdBqRDLtLSACKykTScmqibb1ZrCvJu/*),a:pkh(tpubD6NzVbkrYhZ4YUHcFfuH9iEBLiH8CBRJTpS7X3qjHmh82m1KCNbzs6w9gyK8oWHSZmKHWcakAXCGfbKg6xoCvKzQCWAHyxaC7QcWfmzyBf4/*),a:pkh(tpubD6NzVbkrYhZ4XXEmQtS3sgxpJbMyMg4McqRR1Af6ULzyrTRnhwjyr1etPD7svap9oFtJf4MM72brUb5o7uvF2Jyszc5c1t836fJW7SX2e8D/*)))", + # Liquid-like federated pegin with emergency recovery keys + "or_i(and_b(pk(029ffbe722b147f3035c87cb1c60b9a5947dd49c774cc31e94773478711a929ac0),a:and_b(pk(025f05815e3a1a8a83bfbb03ce016c9a2ee31066b98f567f6227df1d76ec4bd143),a:and_b(pk(025625f41e4a065efc06d5019cbbd56fe8c07595af1231e7cbc03fafb87ebb71ec),a:and_b(pk(02a27c8b850a00f67da3499b60562673dcf5fdfb82b7e17652a7ac54416812aefd),s:pk(03e618ec5f384d6e19ca9ebdb8e2119e5bef978285076828ce054e55c4daf473e2))))),and_v(v:thresh(2,pkh(tpubD6NzVbkrYhZ4YK67cd5fDe4fBVmGB2waTDrAt1q4ey9HPq9veHjWkw3VpbaCHCcWozjkhgAkWpFrxuPMUrmXVrLHMfEJ9auoZA6AS1g3grC/*),a:pkh(033841045a531e1adf9910a6ec279589a90b3b8a904ee64ffd692bd08a8996c1aa),a:pkh(02aebf2d10b040eb936a6f02f44ee82f8b34f5c1ccb20ff3949c2b28206b7c1068)),older(4209713)))", +] + + +class WalletMiniscriptTest(BitcoinTestFramework): + def set_test_params(self): + self.num_nodes = 1 + + def skip_test_if_missing_module(self): + self.skip_if_no_wallet() + self.skip_if_no_sqlite() + + def watchonly_test(self, ms): + self.log.info(f"Importing Miniscript '{ms}'") + desc = descsum_create(f"wsh({ms})") + assert self.ms_wo_wallet.importdescriptors( + [ + { + "desc": desc, + "active": True, + "range": 2, + "next_index": 0, + "timestamp": "now", + } + ] + )[0]["success"] + + self.log.info("Testing we derive new addresses for it") + assert_equal( + self.ms_wo_wallet.getnewaddress(), self.funder.deriveaddresses(desc, 0)[0] + ) + assert_equal( + self.ms_wo_wallet.getnewaddress(), self.funder.deriveaddresses(desc, 1)[1] + ) + + self.log.info("Testing we detect funds sent to one of them") + addr = self.ms_wo_wallet.getnewaddress() + txid = self.funder.sendtoaddress(addr, 0.01) + self.wait_until( + lambda: len(self.ms_wo_wallet.listunspent(minconf=0, addresses=[addr])) == 1 + ) + utxo = self.ms_wo_wallet.listunspent(minconf=0, addresses=[addr])[0] + assert utxo["txid"] == txid and not utxo["solvable"] # No satisfaction logic (yet) + + def run_test(self): + self.log.info("Making a descriptor wallet") + self.funder = self.nodes[0].get_wallet_rpc(self.default_wallet_name) + self.nodes[0].createwallet( + wallet_name="ms_wo", descriptors=True, disable_private_keys=True + ) + self.ms_wo_wallet = self.nodes[0].get_wallet_rpc("ms_wo") + + # Sanity check we wouldn't let an insane Miniscript descriptor in + res = self.ms_wo_wallet.importdescriptors( + [ + { + "desc": descsum_create( + "wsh(and_b(ripemd160(1fd9b55a054a2b3f658d97e6b84cf3ee00be429a),a:1))" + ), + "active": False, + "timestamp": "now", + } + ] + )[0] + assert not res["success"] + assert "is not sane: witnesses without signature exist" in res["error"]["message"] + + # Test we can track any type of Miniscript + for ms in MINISCRIPTS: + self.watchonly_test(ms) + + +if __name__ == "__main__": + WalletMiniscriptTest().main() diff --git a/test/functional/wallet_multiwallet.py b/test/functional/wallet_multiwallet.py index dcb82bbbe9..1c890d7207 100755 --- a/test/functional/wallet_multiwallet.py +++ b/test/functional/wallet_multiwallet.py @@ -356,7 +356,7 @@ class MultiWalletTest(BitcoinTestFramework): self.log.info("Test dynamic wallet unloading") # Test `unloadwallet` errors - assert_raises_rpc_error(-1, "JSON value is not a string as expected", self.nodes[0].unloadwallet) + assert_raises_rpc_error(-3, "JSON value of type null is not of expected type string", self.nodes[0].unloadwallet) assert_raises_rpc_error(-18, "Requested wallet does not exist or is not loaded", self.nodes[0].unloadwallet, "dummy") assert_raises_rpc_error(-18, "Requested wallet does not exist or is not loaded", node.get_wallet_rpc("dummy").unloadwallet) assert_raises_rpc_error(-8, "RPC endpoint wallet and wallet_name parameter specify different wallets", w1.unloadwallet, "w2"), diff --git a/test/functional/wallet_resendwallettransactions.py b/test/functional/wallet_resendwallettransactions.py index 6552bfe60c..b3d02fbfc9 100755 --- a/test/functional/wallet_resendwallettransactions.py +++ b/test/functional/wallet_resendwallettransactions.py @@ -9,10 +9,13 @@ from test_framework.blocktools import ( create_block, create_coinbase, ) +from test_framework.messages import DEFAULT_MEMPOOL_EXPIRY_HOURS from test_framework.p2p import P2PTxInvStore 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 ResendWalletTransactionsTest(BitcoinTestFramework): def set_test_params(self): @@ -27,13 +30,9 @@ class ResendWalletTransactionsTest(BitcoinTestFramework): peer_first = node.add_p2p_connection(P2PTxInvStore()) self.log.info("Create a new transaction and wait until it's broadcast") - txid = node.sendtoaddress(node.getnewaddress(), 1) - - # Wallet rebroadcast is first scheduled 1 sec after startup (see - # nNextResend in ResendWalletTransactions()). Tell scheduler to call - # MaybeResendWalletTxn now to initialize nNextResend before the first - # setmocktime call below. - node.mockscheduler(1) + parent_utxo, indep_utxo = node.listunspent()[:2] + addr = node.getnewaddress() + txid = node.send(outputs=[{addr: 1}], options={"inputs": [parent_utxo]})["txid"] # Can take a few seconds due to transaction trickling peer_first.wait_for_broadcast([txid]) @@ -51,7 +50,7 @@ class ResendWalletTransactionsTest(BitcoinTestFramework): block.solve() node.submitblock(block.serialize().hex()) - # Set correct m_best_block_time, which is used in ResendWalletTransactions + # Set correct m_best_block_time, which is used in ResubmitWalletTransactions node.syncwithvalidationinterfacequeue() now = int(time.time()) @@ -60,20 +59,67 @@ class ResendWalletTransactionsTest(BitcoinTestFramework): twelve_hrs = 12 * 60 * 60 two_min = 2 * 60 node.setmocktime(now + twelve_hrs - two_min) - node.mockscheduler(1) # Tell scheduler to call MaybeResendWalletTxn now + node.mockscheduler(60) # Tell scheduler to call MaybeResendWalletTxs now assert_equal(int(txid, 16) in peer_second.get_invs(), False) self.log.info("Bump time & check that transaction is rebroadcast") # Transaction should be rebroadcast approximately 24 hours in the future, # but can range from 12-36. So bump 36 hours to be sure. - with node.assert_debug_log(['ResendWalletTransactions: resubmit 1 unconfirmed transactions']): + with node.assert_debug_log(['resubmit 1 unconfirmed transactions']): node.setmocktime(now + 36 * 60 * 60) - # Tell scheduler to call MaybeResendWalletTxn now. - node.mockscheduler(1) + # Tell scheduler to call MaybeResendWalletTxs now. + node.mockscheduler(60) # Give some time for trickle to occur node.setmocktime(now + 36 * 60 * 60 + 600) peer_second.wait_for_broadcast([txid]) + self.log.info("Chain of unconfirmed not-in-mempool txs are rebroadcast") + # This tests that the node broadcasts the parent transaction before the child transaction. + # To test that scenario, we need a method to reliably get a child transaction placed + # in mapWallet positioned before the parent. We cannot predict the position in mapWallet, + # but we can observe it using listreceivedbyaddress and other related RPCs. + # + # So we will create the child transaction, use listreceivedbyaddress to see what the + # ordering of mapWallet is, if the child is not before the parent, we will create a new + # child (via bumpfee) and remove the old child (via removeprunedfunds) until we get the + # ordering of child before parent. + child_txid = node.send(outputs=[{addr: 0.5}], options={"inputs": [{"txid":txid, "vout":0}]})["txid"] + while True: + txids = node.listreceivedbyaddress(minconf=0, address_filter=addr)[0]["txids"] + if txids == [child_txid, txid]: + break + bumped = node.bumpfee(child_txid) + # The scheduler queue creates a copy of the added tx after + # send/bumpfee and re-adds it to the wallet (undoing the next + # removeprunedfunds). So empty the scheduler queue: + node.syncwithvalidationinterfacequeue() + node.removeprunedfunds(child_txid) + child_txid = bumped["txid"] + entry_time = node.getmempoolentry(child_txid)["time"] + + block_time = entry_time + 6 * 60 + node.setmocktime(block_time) + block = create_block(int(node.getbestblockhash(), 16), create_coinbase(node.getblockcount() + 1), block_time) + block.solve() + node.submitblock(block.serialize().hex()) + # Set correct m_best_block_time, which is used in ResubmitWalletTransactions + node.syncwithvalidationinterfacequeue() + + # Evict these txs from the mempool + evict_time = block_time + 60 * 60 * DEFAULT_MEMPOOL_EXPIRY_HOURS + 5 + node.setmocktime(evict_time) + indep_send = node.send(outputs=[{node.getnewaddress(): 1}], options={"inputs": [indep_utxo]}) + node.getmempoolentry(indep_send["txid"]) + assert_raises_rpc_error(-5, "Transaction not in mempool", node.getmempoolentry, txid) + assert_raises_rpc_error(-5, "Transaction not in mempool", node.getmempoolentry, child_txid) + + # Rebroadcast and check that parent and child are both in the mempool + with node.assert_debug_log(['resubmit 2 unconfirmed transactions']): + node.setmocktime(evict_time + 36 * 60 * 60) # 36 hrs is the upper limit of the resend timer + node.mockscheduler(60) + node.getmempoolentry(txid) + node.getmempoolentry(child_txid) + if __name__ == '__main__': ResendWalletTransactionsTest().main() diff --git a/test/functional/wallet_sendall.py b/test/functional/wallet_sendall.py index aa8d2a9d2c..db4f32fe16 100755 --- a/test/functional/wallet_sendall.py +++ b/test/functional/wallet_sendall.py @@ -264,6 +264,32 @@ class SendallTest(BitcoinTestFramework): recipients=[self.remainder_target], options={"inputs": [utxo], "send_max": True}) + @cleanup + def sendall_fails_on_high_fee(self): + self.log.info("Test sendall fails if the transaction fee exceeds the maxtxfee") + self.add_utxos([21]) + + assert_raises_rpc_error( + -4, + "Fee exceeds maximum configured by user", + self.wallet.sendall, + recipients=[self.remainder_target], + fee_rate=100000) + + # This tests needs to be the last one otherwise @cleanup will fail with "Transaction too large" error + def sendall_fails_with_transaction_too_large(self): + self.log.info("Test that sendall fails if resulting transaction is too large") + # create many inputs + outputs = {self.wallet.getnewaddress(): 0.000025 for _ in range(1600)} + self.def_wallet.sendmany(amounts=outputs) + self.generate(self.nodes[0], 1) + + assert_raises_rpc_error( + -4, + "Transaction too large.", + self.wallet.sendall, + recipients=[self.remainder_target]) + def run_test(self): self.nodes[0].createwallet("activewallet") self.wallet = self.nodes[0].get_wallet_rpc("activewallet") @@ -312,5 +338,11 @@ class SendallTest(BitcoinTestFramework): # Sendall fails when using send_max while specifying inputs self.sendall_fails_on_specific_inputs_with_send_max() + # Sendall fails when providing a fee that is too high + self.sendall_fails_on_high_fee() + + # Sendall fails when many inputs result to too large transaction + self.sendall_fails_with_transaction_too_large() + if __name__ == '__main__': SendallTest().main() diff --git a/test/functional/wallet_signer.py b/test/functional/wallet_signer.py index 8e4e1f5d36..5609ac9bf5 100755 --- a/test/functional/wallet_signer.py +++ b/test/functional/wallet_signer.py @@ -32,6 +32,13 @@ class WalletSignerTest(BitcoinTestFramework): else: return path + def mock_multi_signers_path(self): + path = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'mocks', 'multi_signers.py') + if platform.system() == "Windows": + return "py " + path + else: + return path + def set_test_params(self): self.num_nodes = 2 # The experimental syscall sandbox feature (-sandbox) is not compatible with -signer (which @@ -58,6 +65,8 @@ class WalletSignerTest(BitcoinTestFramework): self.test_valid_signer() self.restart_node(1, [f"-signer={self.mock_invalid_signer_path()}", "-keypool=10"]) self.test_invalid_signer() + self.restart_node(1, [f"-signer={self.mock_multi_signers_path()}", "-keypool=10"]) + self.test_multiple_signers() def test_valid_signer(self): self.log.debug(f"-signer={self.mock_signer_path()}") @@ -212,5 +221,11 @@ class WalletSignerTest(BitcoinTestFramework): self.log.info('Test invalid external signer') assert_raises_rpc_error(-1, "Invalid descriptor", self.nodes[1].createwallet, wallet_name='hww_invalid', disable_private_keys=True, descriptors=True, external_signer=True) + def test_multiple_signers(self): + self.log.debug(f"-signer={self.mock_multi_signers_path()}") + self.log.info('Test multiple external signers') + + assert_raises_rpc_error(-1, "GetExternalSigner: More than one external signer found", self.nodes[1].createwallet, wallet_name='multi_hww', disable_private_keys=True, descriptors=True, external_signer=True) + if __name__ == '__main__': WalletSignerTest().main() diff --git a/test/functional/rpc_signrawtransaction.py b/test/functional/wallet_signrawtransactionwithwallet.py index 8da2cfa72b..6b30386b7e 100755 --- a/test/functional/rpc_signrawtransaction.py +++ b/test/functional/wallet_signrawtransactionwithwallet.py @@ -2,16 +2,14 @@ # Copyright (c) 2015-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. -"""Test transaction signing using the signrawtransaction* RPCs.""" +"""Test transaction signing using the signrawtransactionwithwallet RPC.""" from test_framework.blocktools import ( COINBASE_MATURITY, ) from test_framework.address import ( - script_to_p2sh, script_to_p2wsh, ) -from test_framework.key import ECKey from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_equal, @@ -29,20 +27,13 @@ from test_framework.script import ( OP_DROP, OP_TRUE, ) -from test_framework.script_util import ( - key_to_p2pk_script, - key_to_p2pkh_script, - script_to_p2sh_p2wsh_script, - script_to_p2wsh_script, -) -from test_framework.wallet_util import bytes_to_wif from decimal import ( Decimal, getcontext, ) -class SignRawTransactionsTest(BitcoinTestFramework): +class SignRawTransactionWithWalletTest(BitcoinTestFramework): def set_test_params(self): self.setup_clean_chain = True self.num_nodes = 2 @@ -50,35 +41,6 @@ class SignRawTransactionsTest(BitcoinTestFramework): def skip_test_if_missing_module(self): self.skip_if_no_wallet() - def successful_signing_test(self): - """Create and sign a valid raw transaction with one input. - - Expected results: - - 1) The transaction has a complete set of signatures - 2) No script verification error occurred""" - self.log.info("Test valid raw transaction with one input") - privKeys = ['cUeKHd5orzT3mz8P9pxyREHfsWtVfgsfDjiZZBcjUBAaGk1BTj7N', 'cVKpPfVKSJxKqVpE9awvXNWuLHCa5j5tiE7K6zbUSptFpTEtiFrA'] - - inputs = [ - # Valid pay-to-pubkey scripts - {'txid': '9b907ef1e3c26fc71fe4a4b3580bc75264112f95050014157059c736f0202e71', 'vout': 0, - 'scriptPubKey': '76a91460baa0f494b38ce3c940dea67f3804dc52d1fb9488ac'}, - {'txid': '83a4f6a6b73660e13ee6cb3c6063fa3759c50c9b7521d0536022961898f4fb02', 'vout': 0, - 'scriptPubKey': '76a914669b857c03a5ed269d5d85a1ffac9ed5d663072788ac'}, - ] - - outputs = {'mpLQjfK79b7CCV4VMJWEWAj5Mpx8Up5zxB': 0.1} - - rawTx = self.nodes[0].createrawtransaction(inputs, outputs) - rawTxSigned = self.nodes[0].signrawtransactionwithkey(rawTx, privKeys, inputs) - - # 1) The transaction has a complete set of signatures - assert rawTxSigned['complete'] - - # 2) No script verification error occurred - assert 'errors' not in rawTxSigned - def test_with_lock_outputs(self): self.log.info("Test correct error reporting when trying to sign a locked output") self.nodes[0].encryptwallet("password") @@ -191,60 +153,6 @@ class SignRawTransactionsTest(BitcoinTestFramework): assert_equal(signedtx["hex"], signedtx2["hex"]) self.nodes[0].walletlock() - def witness_script_test(self): - self.log.info("Test signing transaction to P2SH-P2WSH addresses without wallet") - # Create a new P2SH-P2WSH 1-of-1 multisig address: - eckey = ECKey() - eckey.generate() - embedded_privkey = bytes_to_wif(eckey.get_bytes()) - embedded_pubkey = eckey.get_pubkey().get_bytes().hex() - p2sh_p2wsh_address = self.nodes[1].createmultisig(1, [embedded_pubkey], "p2sh-segwit") - # send transaction to P2SH-P2WSH 1-of-1 multisig address - self.generate(self.nodes[0], COINBASE_MATURITY + 1) - self.nodes[0].sendtoaddress(p2sh_p2wsh_address["address"], 49.999) - self.generate(self.nodes[0], 1) - # Get the UTXO info from scantxoutset - unspent_output = self.nodes[1].scantxoutset('start', [p2sh_p2wsh_address['descriptor']])['unspents'][0] - spk = script_to_p2sh_p2wsh_script(p2sh_p2wsh_address['redeemScript']).hex() - unspent_output['witnessScript'] = p2sh_p2wsh_address['redeemScript'] - unspent_output['redeemScript'] = script_to_p2wsh_script(unspent_output['witnessScript']).hex() - assert_equal(spk, unspent_output['scriptPubKey']) - # Now create and sign a transaction spending that output on node[0], which doesn't know the scripts or keys - spending_tx = self.nodes[0].createrawtransaction([unspent_output], {self.nodes[1].get_wallet_rpc(self.default_wallet_name).getnewaddress(): Decimal("49.998")}) - spending_tx_signed = self.nodes[0].signrawtransactionwithkey(spending_tx, [embedded_privkey], [unspent_output]) - # Check the signing completed successfully - assert 'complete' in spending_tx_signed - assert_equal(spending_tx_signed['complete'], True) - - # Now test with P2PKH and P2PK scripts as the witnessScript - for tx_type in ['P2PKH', 'P2PK']: # these tests are order-independent - self.verify_txn_with_witness_script(tx_type) - - def verify_txn_with_witness_script(self, tx_type): - self.log.info("Test with a {} script as the witnessScript".format(tx_type)) - eckey = ECKey() - eckey.generate() - embedded_privkey = bytes_to_wif(eckey.get_bytes()) - embedded_pubkey = eckey.get_pubkey().get_bytes().hex() - witness_script = { - 'P2PKH': key_to_p2pkh_script(embedded_pubkey).hex(), - 'P2PK': key_to_p2pk_script(embedded_pubkey).hex() - }.get(tx_type, "Invalid tx_type") - redeem_script = script_to_p2wsh_script(witness_script).hex() - addr = script_to_p2sh(redeem_script) - script_pub_key = self.nodes[1].validateaddress(addr)['scriptPubKey'] - # Fund that address - txid = self.nodes[0].sendtoaddress(addr, 10) - vout = find_vout_for_address(self.nodes[0], txid, addr) - self.generate(self.nodes[0], 1) - # Now create and sign a transaction spending that output on node[0], which doesn't know the scripts or keys - spending_tx = self.nodes[0].createrawtransaction([{'txid': txid, 'vout': vout}], {self.nodes[1].getnewaddress(): Decimal("9.999")}) - spending_tx_signed = self.nodes[0].signrawtransactionwithkey(spending_tx, [embedded_privkey], [{'txid': txid, 'vout': vout, 'scriptPubKey': script_pub_key, 'redeemScript': redeem_script, 'witnessScript': witness_script, 'amount': 10}]) - # Check the signing completed successfully - assert 'complete' in spending_tx_signed - assert_equal(spending_tx_signed['complete'], True) - self.nodes[0].sendrawtransaction(spending_tx_signed['hex']) - def OP_1NEGATE_test(self): self.log.info("Test OP_1NEGATE (0x4f) satisfies BIP62 minimal push standardness rule") hex_str = ( @@ -385,9 +293,7 @@ class SignRawTransactionsTest(BitcoinTestFramework): ]) def run_test(self): - self.successful_signing_test() self.script_verification_error_test() - self.witness_script_test() self.OP_1NEGATE_test() self.test_with_lock_outputs() self.test_fully_signed_tx() @@ -397,4 +303,4 @@ class SignRawTransactionsTest(BitcoinTestFramework): if __name__ == '__main__': - SignRawTransactionsTest().main() + SignRawTransactionWithWalletTest().main() diff --git a/test/functional/wallet_simulaterawtx.py b/test/functional/wallet_simulaterawtx.py new file mode 100755 index 0000000000..a408b99515 --- /dev/null +++ b/test/functional/wallet_simulaterawtx.py @@ -0,0 +1,129 @@ +#!/usr/bin/env python3 +# 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. +"""Test simulaterawtransaction. +""" + +from decimal import Decimal +from test_framework.blocktools import COINBASE_MATURITY +from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import ( + assert_approx, + assert_equal, + assert_raises_rpc_error, +) + +class SimulateTxTest(BitcoinTestFramework): + def set_test_params(self): + self.setup_clean_chain = True + self.num_nodes = 1 + + def skip_test_if_missing_module(self): + self.skip_if_no_wallet() + + def setup_network(self, split=False): + self.setup_nodes() + + def run_test(self): + node = self.nodes[0] + + self.generate(node, 1, sync_fun=self.no_op) # Leave IBD + + node.createwallet(wallet_name='w0') + node.createwallet(wallet_name='w1') + node.createwallet(wallet_name='w2', disable_private_keys=True) + w0 = node.get_wallet_rpc('w0') + w1 = node.get_wallet_rpc('w1') + w2 = node.get_wallet_rpc('w2') + + self.generatetoaddress(node, COINBASE_MATURITY + 1, w0.getnewaddress()) + assert_equal(w0.getbalance(), 50.0) + assert_equal(w1.getbalance(), 0.0) + + address1 = w1.getnewaddress() + address2 = w1.getnewaddress() + + # Add address1 as watch-only to w2 + w2.importpubkey(pubkey=w1.getaddressinfo(address1)["pubkey"]) + + tx1 = node.createrawtransaction([], [{address1: 5.0}]) + tx2 = node.createrawtransaction([], [{address2: 10.0}]) + + # w0 should be unaffected, w2 should see +5 for tx1 + assert_equal(w0.simulaterawtransaction([tx1])["balance_change"], 0.0) + assert_equal(w2.simulaterawtransaction([tx1])["balance_change"], 5.0) + + # w1 should see +5 balance for tx1 + assert_equal(w1.simulaterawtransaction([tx1])["balance_change"], 5.0) + + # w0 should be unaffected, w2 should see +5 for both transactions + assert_equal(w0.simulaterawtransaction([tx1, tx2])["balance_change"], 0.0) + assert_equal(w2.simulaterawtransaction([tx1, tx2])["balance_change"], 5.0) + + # w1 should see +15 balance for both transactions + assert_equal(w1.simulaterawtransaction([tx1, tx2])["balance_change"], 15.0) + + # w0 funds transaction; it should now see a decrease in (tx fee and payment), and w1 should see the same as above + funding = w0.fundrawtransaction(tx1) + tx1 = funding["hex"] + tx1changepos = funding["changepos"] + bitcoin_fee = Decimal(funding["fee"]) + + # w0 sees fee + 5 btc decrease, w2 sees + 5 btc + assert_approx(w0.simulaterawtransaction([tx1])["balance_change"], -(Decimal("5") + bitcoin_fee)) + assert_approx(w2.simulaterawtransaction([tx1])["balance_change"], Decimal("5")) + + # w1 sees same as before + assert_equal(w1.simulaterawtransaction([tx1])["balance_change"], 5.0) + + # same inputs (tx) more than once should error + assert_raises_rpc_error(-8, "Transaction(s) are spending the same output more than once", w0.simulaterawtransaction, [tx1,tx1]) + + tx1ob = node.decoderawtransaction(tx1) + tx1hex = tx1ob["txid"] + tx1vout = 1 - tx1changepos + # tx3 spends new w1 UTXO paying to w0 + tx3 = node.createrawtransaction([{"txid": tx1hex, "vout": tx1vout}], {w0.getnewaddress(): 4.9999}) + # tx4 spends new w1 UTXO paying to w1 + tx4 = node.createrawtransaction([{"txid": tx1hex, "vout": tx1vout}], {w1.getnewaddress(): 4.9999}) + + # on their own, both should fail due to missing input(s) + assert_raises_rpc_error(-8, "One or more transaction inputs are missing or have been spent already", w0.simulaterawtransaction, [tx3]) + assert_raises_rpc_error(-8, "One or more transaction inputs are missing or have been spent already", w1.simulaterawtransaction, [tx3]) + assert_raises_rpc_error(-8, "One or more transaction inputs are missing or have been spent already", w0.simulaterawtransaction, [tx4]) + assert_raises_rpc_error(-8, "One or more transaction inputs are missing or have been spent already", w1.simulaterawtransaction, [tx4]) + + # they should succeed when including tx1: + # wallet tx3 tx4 + # w0 -5 - bitcoin_fee + 4.9999 -5 - bitcoin_fee + # w1 0 +4.9999 + assert_approx(w0.simulaterawtransaction([tx1, tx3])["balance_change"], -Decimal("5") - bitcoin_fee + Decimal("4.9999")) + assert_approx(w1.simulaterawtransaction([tx1, tx3])["balance_change"], 0) + assert_approx(w0.simulaterawtransaction([tx1, tx4])["balance_change"], -Decimal("5") - bitcoin_fee) + assert_approx(w1.simulaterawtransaction([tx1, tx4])["balance_change"], Decimal("4.9999")) + + # they should fail if attempting to include both tx3 and tx4 + assert_raises_rpc_error(-8, "Transaction(s) are spending the same output more than once", w0.simulaterawtransaction, [tx1, tx3, tx4]) + assert_raises_rpc_error(-8, "Transaction(s) are spending the same output more than once", w1.simulaterawtransaction, [tx1, tx3, tx4]) + + # send tx1 to avoid reusing same UTXO below + node.sendrawtransaction(w0.signrawtransactionwithwallet(tx1)["hex"]) + self.generate(node, 1, sync_fun=self.no_op) # Confirm tx to trigger error below + self.sync_all() + + # w0 funds transaction 2; it should now see a decrease in (tx fee and payment), and w1 should see the same as above + funding = w0.fundrawtransaction(tx2) + tx2 = funding["hex"] + bitcoin_fee2 = Decimal(funding["fee"]) + assert_approx(w0.simulaterawtransaction([tx2])["balance_change"], -(Decimal("10") + bitcoin_fee2)) + assert_approx(w1.simulaterawtransaction([tx2])["balance_change"], +(Decimal("10"))) + assert_approx(w2.simulaterawtransaction([tx2])["balance_change"], 0) + + # w0-w2 error due to tx1 already being mined + assert_raises_rpc_error(-8, "One or more transaction inputs are missing or have been spent already", w0.simulaterawtransaction, [tx1, tx2]) + assert_raises_rpc_error(-8, "One or more transaction inputs are missing or have been spent already", w1.simulaterawtransaction, [tx1, tx2]) + assert_raises_rpc_error(-8, "One or more transaction inputs are missing or have been spent already", w2.simulaterawtransaction, [tx1, tx2]) + +if __name__ == '__main__': + SimulateTxTest().main() diff --git a/test/functional/wallet_taproot.py b/test/functional/wallet_taproot.py index a4d836c8fe..3c630ba433 100755 --- a/test/functional/wallet_taproot.py +++ b/test/functional/wallet_taproot.py @@ -7,13 +7,14 @@ import random from decimal import Decimal +from test_framework.address import output_key_to_p2tr +from test_framework.key import H_POINT from test_framework.test_framework import BitcoinTestFramework from test_framework.util import assert_equal from test_framework.descriptors import descsum_create from test_framework.script import ( CScript, MAX_PUBKEYS_PER_MULTI_A, - OP_1, OP_CHECKSIG, OP_CHECKSIGADD, OP_NUMEQUAL, @@ -158,9 +159,6 @@ KEYS = [ CHANGE_XPRV = "tprv8ZgxMBicQKsPcyDrWwiecVnTtFmfRwbfFqEfR4ZGWvq5aTTwLBWmAm5zrbMcYtb9gQNFfhRfqhhrBG37U3nhmXxEgeEPBJGHAPrHCrAd1WX" CHANGE_XPUB = "tpubD6NzVbkrYhZ4WSFeQbPF1uSaTHHbbGnZq8qShabZwCdUQwihxaLMMFhs2kidGF2qrRKiQVqw8VoyuTHj1bZqmMXMeciaU1gBjWA1sim2zUB" -# Point with no known discrete log. -H_POINT = "50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0" - def key(hex_key): """Construct an x-only pubkey from its hex representation.""" @@ -183,10 +181,10 @@ def multi_a(k, hex_keys, sort=False): def compute_taproot_address(pubkey, scripts): """Compute the address for a taproot output with given inner key and scripts.""" - tap = taproot_construct(pubkey, scripts) - assert tap.scriptPubKey[0] == OP_1 - assert tap.scriptPubKey[1] == 0x20 - return encode_segwit_address("bcrt", 1, tap.scriptPubKey[2:]) + return output_key_to_p2tr(taproot_construct(pubkey, scripts).output_pubkey) + +def compute_raw_taproot_address(pubkey): + return encode_segwit_address("bcrt", 1, pubkey) class WalletTaprootTest(BitcoinTestFramework): """Test generation and spending of P2TR address outputs.""" @@ -222,7 +220,12 @@ class WalletTaprootTest(BitcoinTestFramework): args = [] for j in range(len(keys)): args.append(keys[j]['pubs'][i]) - return compute_taproot_address(*treefn(*args)) + tree = treefn(*args) + if isinstance(tree, tuple): + return compute_taproot_address(*tree) + if isinstance(tree, bytes): + return compute_raw_taproot_address(tree) + assert False def do_test_addr(self, comment, pattern, privmap, treefn, keys): self.log.info("Testing %s address derivation" % comment) @@ -305,9 +308,21 @@ class WalletTaprootTest(BitcoinTestFramework): test_balance = int(self.psbt_online.getbalance() * 100000000) ret_amnt = random.randrange(100000, test_balance) # Increase fee_rate to compensate for the wallet's inability to estimate fees for script path spends. - psbt = self.psbt_online.walletcreatefundedpsbt([], [{self.boring.getnewaddress(): Decimal(ret_amnt) / 100000000}], None, {"subtractFeeFromOutputs":[0], "fee_rate": 200})['psbt'] - res = self.psbt_offline.walletprocesspsbt(psbt) - assert(res['complete']) + psbt = self.psbt_online.walletcreatefundedpsbt([], [{self.boring.getnewaddress(): Decimal(ret_amnt) / 100000000}], None, {"subtractFeeFromOutputs":[0], "fee_rate": 200, "change_type": "bech32m"})['psbt'] + res = self.psbt_offline.walletprocesspsbt(psbt=psbt, finalize=False) + + decoded = self.psbt_offline.decodepsbt(res["psbt"]) + if pattern.startswith("tr("): + for psbtin in decoded["inputs"]: + assert "non_witness_utxo" not in psbtin + assert "witness_utxo" in psbtin + assert "taproot_internal_key" in psbtin + assert "taproot_bip32_derivs" in psbtin + assert "taproot_key_path_sig" in psbtin or "taproot_script_path_sigs" in psbtin + if "taproot_script_path_sigs" in psbtin: + assert "taproot_merkle_root" in psbtin + assert "taproot_scripts" in psbtin + rawtx = self.nodes[0].finalizepsbt(res['psbt'])['hex'] txid = self.nodes[0].sendrawtransaction(rawtx) self.generatetoaddress(self.nodes[0], 1, self.boring.getnewaddress(), sync_fun=self.no_op) @@ -438,6 +453,12 @@ class WalletTaprootTest(BitcoinTestFramework): [True, False], lambda k1, k2: (key(k2), [multi_a(1, ([H_POINT] * rnd_pos) + [k1] + ([H_POINT] * (MAX_PUBKEYS_PER_MULTI_A - 1 - rnd_pos)))]) ) + self.do_test( + "rawtr(XPRV)", + "rawtr($1/*)", + [True], + lambda k1: key(k1) + ) self.log.info("Sending everything back...") @@ -446,8 +467,7 @@ class WalletTaprootTest(BitcoinTestFramework): assert(self.rpc_online.gettransaction(txid)["confirmations"] > 0) psbt = self.psbt_online.sendall(recipients=[self.boring.getnewaddress()], options={"psbt": True})["psbt"] - res = self.psbt_offline.walletprocesspsbt(psbt) - assert(res['complete']) + res = self.psbt_offline.walletprocesspsbt(psbt=psbt, finalize=False) rawtx = self.nodes[0].finalizepsbt(res['psbt'])['hex'] txid = self.nodes[0].sendrawtransaction(rawtx) self.generatetoaddress(self.nodes[0], 1, self.boring.getnewaddress(), sync_fun=self.no_op) diff --git a/test/functional/wallet_transactiontime_rescan.py b/test/functional/wallet_transactiontime_rescan.py index 21941084a3..9caa1fa3d0 100755 --- a/test/functional/wallet_transactiontime_rescan.py +++ b/test/functional/wallet_transactiontime_rescan.py @@ -11,6 +11,7 @@ from test_framework.blocktools import COINBASE_MATURITY from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_equal, + assert_raises_rpc_error, set_node_times, ) @@ -158,5 +159,11 @@ class TransactionTimeRescanTest(BitcoinTestFramework): assert_equal(tx['time'], cur_time + ten_days + ten_days + ten_days) + self.log.info('Test handling of invalid parameters for rescanblockchain') + assert_raises_rpc_error(-8, "Invalid start_height", restorewo_wallet.rescanblockchain, -1, 10) + assert_raises_rpc_error(-8, "Invalid stop_height", restorewo_wallet.rescanblockchain, 1, -1) + assert_raises_rpc_error(-8, "stop_height must be greater than start_height", restorewo_wallet.rescanblockchain, 20, 10) + + if __name__ == '__main__': TransactionTimeRescanTest().main() diff --git a/test/get_previous_releases.py b/test/get_previous_releases.py index cbdb67216c..c983dce619 100755 --- a/test/get_previous_releases.py +++ b/test/get_previous_releases.py @@ -20,66 +20,66 @@ import sys import hashlib SHA256_SUMS = { - "0e2819135366f150d9906e294b61dff58fd1996ebd26c2f8e979d6c0b7a79580": "bitcoin-0.14.3-aarch64-linux-gnu.tar.gz", - "d86fc90824a85c38b25c8488115178d5785dbc975f5ff674f9f5716bc8ad6e65": "bitcoin-0.14.3-arm-linux-gnueabihf.tar.gz", - "1b0a7408c050e3d09a8be8e21e183ef7ee570385dc41216698cc3ab392a484e7": "bitcoin-0.14.3-osx64.tar.gz", - "706e0472dbc933ed2757650d54cbcd780fd3829ebf8f609b32780c7eedebdbc9": "bitcoin-0.14.3-x86_64-linux-gnu.tar.gz", + "0e2819135366f150d9906e294b61dff58fd1996ebd26c2f8e979d6c0b7a79580": {"tag": "v0.14.3", "tarball": "bitcoin-0.14.3-aarch64-linux-gnu.tar.gz"}, + "d86fc90824a85c38b25c8488115178d5785dbc975f5ff674f9f5716bc8ad6e65": {"tag": "v0.14.3", "tarball": "bitcoin-0.14.3-arm-linux-gnueabihf.tar.gz"}, + "1b0a7408c050e3d09a8be8e21e183ef7ee570385dc41216698cc3ab392a484e7": {"tag": "v0.14.3", "tarball": "bitcoin-0.14.3-osx64.tar.gz"}, + "706e0472dbc933ed2757650d54cbcd780fd3829ebf8f609b32780c7eedebdbc9": {"tag": "v0.14.3", "tarball": "bitcoin-0.14.3-x86_64-linux-gnu.tar.gz"}, # - "d40f18b4e43c6e6370ef7db9131f584fbb137276ec2e3dba67a4b267f81cb644": "bitcoin-0.15.2-aarch64-linux-gnu.tar.gz", - "54fb877a148a6ad189a1e1ab1ff8b11181e58ff2aaf430da55b3fd46ae549a6b": "bitcoin-0.15.2-arm-linux-gnueabihf.tar.gz", - "87e9340ff3d382d543b2b69112376077f0c8b4f7450d372e83b68f5a1e22b2df": "bitcoin-0.15.2-osx64.tar.gz", - "566be44190fd76daa01f13d428939dadfb8e3daacefc8fa17f433cad28f73bd5": "bitcoin-0.15.2-x86_64-linux-gnu.tar.gz", + "d40f18b4e43c6e6370ef7db9131f584fbb137276ec2e3dba67a4b267f81cb644": {"tag": "v0.15.2", "tarball": "bitcoin-0.15.2-aarch64-linux-gnu.tar.gz"}, + "54fb877a148a6ad189a1e1ab1ff8b11181e58ff2aaf430da55b3fd46ae549a6b": {"tag": "v0.15.2", "tarball": "bitcoin-0.15.2-arm-linux-gnueabihf.tar.gz"}, + "87e9340ff3d382d543b2b69112376077f0c8b4f7450d372e83b68f5a1e22b2df": {"tag": "v0.15.2", "tarball": "bitcoin-0.15.2-osx64.tar.gz"}, + "566be44190fd76daa01f13d428939dadfb8e3daacefc8fa17f433cad28f73bd5": {"tag": "v0.15.2", "tarball": "bitcoin-0.15.2-x86_64-linux-gnu.tar.gz"}, # - "0768c6c15caffbaca6524824c9563b42c24f70633c681c2744649158aa3fd484": "bitcoin-0.16.3-aarch64-linux-gnu.tar.gz", - "fb2818069854a6ad20ea03b28b55dbd35d8b1f7d453e90b83eace5d0098a2a87": "bitcoin-0.16.3-arm-linux-gnueabihf.tar.gz", - "78c3bff3b619a19aed575961ea43cc9e142959218835cf51aede7f0b764fc25d": "bitcoin-0.16.3-osx64.tar.gz", - "5d422a9d544742bc0df12427383f9c2517433ce7b58cf672b9a9b17c2ef51e4f": "bitcoin-0.16.3-x86_64-linux-gnu.tar.gz", + "0768c6c15caffbaca6524824c9563b42c24f70633c681c2744649158aa3fd484": {"tag": "v0.16.3", "tarball": "bitcoin-0.16.3-aarch64-linux-gnu.tar.gz"}, + "fb2818069854a6ad20ea03b28b55dbd35d8b1f7d453e90b83eace5d0098a2a87": {"tag": "v0.16.3", "tarball": "bitcoin-0.16.3-arm-linux-gnueabihf.tar.gz"}, + "78c3bff3b619a19aed575961ea43cc9e142959218835cf51aede7f0b764fc25d": {"tag": "v0.16.3", "tarball": "bitcoin-0.16.3-osx64.tar.gz"}, + "5d422a9d544742bc0df12427383f9c2517433ce7b58cf672b9a9b17c2ef51e4f": {"tag": "v0.16.3", "tarball": "bitcoin-0.16.3-x86_64-linux-gnu.tar.gz"}, # - "5a6b35d1a348a402f2d2d6ab5aed653a1a1f13bc63aaaf51605e3501b0733b7a": "bitcoin-0.17.2-aarch64-linux-gnu.tar.gz", - "d1913a5d19c8e8da4a67d1bd5205d03c8614dfd2e02bba2fe3087476643a729e": "bitcoin-0.17.2-arm-linux-gnueabihf.tar.gz", - "a783ba20706dbfd5b47fbedf42165fce70fbbc7d78003305d964f6b3da14887f": "bitcoin-0.17.2-osx64.tar.gz", - "943f9362b9f11130177839116f48f809d83478b4c28591d486ee9a7e35179da6": "bitcoin-0.17.2-x86_64-linux-gnu.tar.gz", + "5a6b35d1a348a402f2d2d6ab5aed653a1a1f13bc63aaaf51605e3501b0733b7a": {"tag": "v0.17.2", "tarball": "bitcoin-0.17.2-aarch64-linux-gnu.tar.gz"}, + "d1913a5d19c8e8da4a67d1bd5205d03c8614dfd2e02bba2fe3087476643a729e": {"tag": "v0.17.2", "tarball": "bitcoin-0.17.2-arm-linux-gnueabihf.tar.gz"}, + "a783ba20706dbfd5b47fbedf42165fce70fbbc7d78003305d964f6b3da14887f": {"tag": "v0.17.2", "tarball": "bitcoin-0.17.2-osx64.tar.gz"}, + "943f9362b9f11130177839116f48f809d83478b4c28591d486ee9a7e35179da6": {"tag": "v0.17.2", "tarball": "bitcoin-0.17.2-x86_64-linux-gnu.tar.gz"}, # - "88f343af72803b851c7da13874cc5525026b0b55e63e1b5e1298390c4688adc6": "bitcoin-0.18.1-aarch64-linux-gnu.tar.gz", - "cc7d483e4b20c5dabd4dcaf304965214cf4934bcc029ca99cbc9af00d3771a1f": "bitcoin-0.18.1-arm-linux-gnueabihf.tar.gz", - "b7bbcee7a7540f711b171d6981f939ca8482005fde22689bc016596d80548bb1": "bitcoin-0.18.1-osx64.tar.gz", - "425ee5ec631ae8da71ebc1c3f5c0269c627cf459379b9b030f047107a28e3ef8": "bitcoin-0.18.1-riscv64-linux-gnu.tar.gz", - "600d1db5e751fa85903e935a01a74f5cc57e1e7473c15fd3e17ed21e202cfe5a": "bitcoin-0.18.1-x86_64-linux-gnu.tar.gz", + "88f343af72803b851c7da13874cc5525026b0b55e63e1b5e1298390c4688adc6": {"tag": "v0.18.1", "tarball": "bitcoin-0.18.1-aarch64-linux-gnu.tar.gz"}, + "cc7d483e4b20c5dabd4dcaf304965214cf4934bcc029ca99cbc9af00d3771a1f": {"tag": "v0.18.1", "tarball": "bitcoin-0.18.1-arm-linux-gnueabihf.tar.gz"}, + "b7bbcee7a7540f711b171d6981f939ca8482005fde22689bc016596d80548bb1": {"tag": "v0.18.1", "tarball": "bitcoin-0.18.1-osx64.tar.gz"}, + "425ee5ec631ae8da71ebc1c3f5c0269c627cf459379b9b030f047107a28e3ef8": {"tag": "v0.18.1", "tarball": "bitcoin-0.18.1-riscv64-linux-gnu.tar.gz"}, + "600d1db5e751fa85903e935a01a74f5cc57e1e7473c15fd3e17ed21e202cfe5a": {"tag": "v0.18.1", "tarball": "bitcoin-0.18.1-x86_64-linux-gnu.tar.gz"}, # - "3a80431717842672df682bdb619e66523b59541483297772a7969413be3502ff": "bitcoin-0.19.1-aarch64-linux-gnu.tar.gz", - "657f28213823d240dd3324d14829702f9ad6f0710f8bdd1c379cb3c447197f48": "bitcoin-0.19.1-arm-linux-gnueabihf.tar.gz", - "1ae1b87de26487075cd2fd22e0d4ead87d969bd55c44f2f1d873ecdc6147ebb3": "bitcoin-0.19.1-osx64.tar.gz", - "aa7a9563b48aa79252c8e7b6a41c07a5441bd9f14c5e4562cc72720ea6cb0ee5": "bitcoin-0.19.1-riscv64-linux-gnu.tar.gz", - "5fcac9416e486d4960e1a946145566350ca670f9aaba99de6542080851122e4c": "bitcoin-0.19.1-x86_64-linux-gnu.tar.gz", + "3a80431717842672df682bdb619e66523b59541483297772a7969413be3502ff": {"tag": "v0.19.1", "tarball": "bitcoin-0.19.1-aarch64-linux-gnu.tar.gz"}, + "657f28213823d240dd3324d14829702f9ad6f0710f8bdd1c379cb3c447197f48": {"tag": "v0.19.1", "tarball": "bitcoin-0.19.1-arm-linux-gnueabihf.tar.gz"}, + "1ae1b87de26487075cd2fd22e0d4ead87d969bd55c44f2f1d873ecdc6147ebb3": {"tag": "v0.19.1", "tarball": "bitcoin-0.19.1-osx64.tar.gz"}, + "aa7a9563b48aa79252c8e7b6a41c07a5441bd9f14c5e4562cc72720ea6cb0ee5": {"tag": "v0.19.1", "tarball": "bitcoin-0.19.1-riscv64-linux-gnu.tar.gz"}, + "5fcac9416e486d4960e1a946145566350ca670f9aaba99de6542080851122e4c": {"tag": "v0.19.1", "tarball": "bitcoin-0.19.1-x86_64-linux-gnu.tar.gz"}, # - "60c93e3462c303eb080be7cf623f1a7684b37fd47a018ad3848bc23e13c84e1c": "bitcoin-0.20.1-aarch64-linux-gnu.tar.gz", - "55b577e0fb306fb429d4be6c9316607753e8543e5946b542d75d876a2f08654c": "bitcoin-0.20.1-arm-linux-gnueabihf.tar.gz", - "b9024dde373ea7dad707363e07ec7e265383204127539ae0c234bff3a61da0d1": "bitcoin-0.20.1-osx64.tar.gz", - "fa71cb52ee5e0459cbf5248cdec72df27995840c796f58b304607a1ed4c165af": "bitcoin-0.20.1-riscv64-linux-gnu.tar.gz", - "376194f06596ecfa40331167c39bc70c355f960280bd2a645fdbf18f66527397": "bitcoin-0.20.1-x86_64-linux-gnu.tar.gz", + "60c93e3462c303eb080be7cf623f1a7684b37fd47a018ad3848bc23e13c84e1c": {"tag": "v0.20.1", "tarball": "bitcoin-0.20.1-aarch64-linux-gnu.tar.gz"}, + "55b577e0fb306fb429d4be6c9316607753e8543e5946b542d75d876a2f08654c": {"tag": "v0.20.1", "tarball": "bitcoin-0.20.1-arm-linux-gnueabihf.tar.gz"}, + "b9024dde373ea7dad707363e07ec7e265383204127539ae0c234bff3a61da0d1": {"tag": "v0.20.1", "tarball": "bitcoin-0.20.1-osx64.tar.gz"}, + "fa71cb52ee5e0459cbf5248cdec72df27995840c796f58b304607a1ed4c165af": {"tag": "v0.20.1", "tarball": "bitcoin-0.20.1-riscv64-linux-gnu.tar.gz"}, + "376194f06596ecfa40331167c39bc70c355f960280bd2a645fdbf18f66527397": {"tag": "v0.20.1", "tarball": "bitcoin-0.20.1-x86_64-linux-gnu.tar.gz"}, - "43416854330914992bbba2d0e9adf2a6fff4130be9af8ae2ef1186e743d9a3fe": "bitcoin-0.21.0-aarch64-linux-gnu.tar.gz", - "f028af308eda45a3c4c90f9332f96b075bf21e3495c945ebce48597151808176": "bitcoin-0.21.0-arm-linux-gnueabihf.tar.gz", - "695fb624fa6423f5da4f443b60763dd1d77488bfe5ef63760904a7b54e91298d": "bitcoin-0.21.0-osx64.tar.gz", - "f8b2adfeae021a672effbc7bd40d5c48d6b94e53b2dd660f787340bf1a52e4e9": "bitcoin-0.21.0-riscv64-linux-gnu.tar.gz", - "da7766775e3f9c98d7a9145429f2be8297c2672fe5b118fd3dc2411fb48e0032": "bitcoin-0.21.0-x86_64-linux-gnu.tar.gz", + "43416854330914992bbba2d0e9adf2a6fff4130be9af8ae2ef1186e743d9a3fe": {"tag": "v0.21.0", "tarball": "bitcoin-0.21.0-aarch64-linux-gnu.tar.gz"}, + "f028af308eda45a3c4c90f9332f96b075bf21e3495c945ebce48597151808176": {"tag": "v0.21.0", "tarball": "bitcoin-0.21.0-arm-linux-gnueabihf.tar.gz"}, + "695fb624fa6423f5da4f443b60763dd1d77488bfe5ef63760904a7b54e91298d": {"tag": "v0.21.0", "tarball": "bitcoin-0.21.0-osx64.tar.gz"}, + "f8b2adfeae021a672effbc7bd40d5c48d6b94e53b2dd660f787340bf1a52e4e9": {"tag": "v0.21.0", "tarball": "bitcoin-0.21.0-riscv64-linux-gnu.tar.gz"}, + "da7766775e3f9c98d7a9145429f2be8297c2672fe5b118fd3dc2411fb48e0032": {"tag": "v0.21.0", "tarball": "bitcoin-0.21.0-x86_64-linux-gnu.tar.gz"}, - "ac718fed08570a81b3587587872ad85a25173afa5f9fbbd0c03ba4d1714cfa3e": "bitcoin-22.0-aarch64-linux-gnu.tar.gz", - "b8713c6c5f03f5258b54e9f436e2ed6d85449aa24c2c9972f91963d413e86311": "bitcoin-22.0-arm-linux-gnueabihf.tar.gz", - "2744d199c3343b2d94faffdfb2c94d75a630ba27301a70e47b0ad30a7e0155e9": "bitcoin-22.0-osx64.tar.gz", - "2cca5f99007d060aca9d8c7cbd035dfe2f040dd8200b210ce32cdf858479f70d": "bitcoin-22.0-powerpc64-linux-gnu.tar.gz", - "91b1e012975c5a363b5b5fcc81b5b7495e86ff703ec8262d4b9afcfec633c30d": "bitcoin-22.0-powerpc64le-linux-gnu.tar.gz", - "9cc3a62c469fe57e11485fdd32c916f10ce7a2899299855a2e479256ff49ff3c": "bitcoin-22.0-riscv64-linux-gnu.tar.gz", - "59ebd25dd82a51638b7a6bb914586201e67db67b919b2a1ff08925a7936d1b16": "bitcoin-22.0-x86_64-linux-gnu.tar.gz", + "ac718fed08570a81b3587587872ad85a25173afa5f9fbbd0c03ba4d1714cfa3e": {"tag": "v22.0", "tarball": "bitcoin-22.0-aarch64-linux-gnu.tar.gz"}, + "b8713c6c5f03f5258b54e9f436e2ed6d85449aa24c2c9972f91963d413e86311": {"tag": "v22.0", "tarball": "bitcoin-22.0-arm-linux-gnueabihf.tar.gz"}, + "2744d199c3343b2d94faffdfb2c94d75a630ba27301a70e47b0ad30a7e0155e9": {"tag": "v22.0", "tarball": "bitcoin-22.0-osx64.tar.gz"}, + "2cca5f99007d060aca9d8c7cbd035dfe2f040dd8200b210ce32cdf858479f70d": {"tag": "v22.0", "tarball": "bitcoin-22.0-powerpc64-linux-gnu.tar.gz"}, + "91b1e012975c5a363b5b5fcc81b5b7495e86ff703ec8262d4b9afcfec633c30d": {"tag": "v22.0", "tarball": "bitcoin-22.0-powerpc64le-linux-gnu.tar.gz"}, + "9cc3a62c469fe57e11485fdd32c916f10ce7a2899299855a2e479256ff49ff3c": {"tag": "v22.0", "tarball": "bitcoin-22.0-riscv64-linux-gnu.tar.gz"}, + "59ebd25dd82a51638b7a6bb914586201e67db67b919b2a1ff08925a7936d1b16": {"tag": "v22.0", "tarball": "bitcoin-22.0-x86_64-linux-gnu.tar.gz"}, - "06f4c78271a77752ba5990d60d81b1751507f77efda1e5981b4e92fd4d9969fb": "bitcoin-23.0-aarch64-linux-gnu.tar.gz", - "952c574366aff76f6d6ad1c9ee45a361d64fa04155e973e926dfe7e26f9703a3": "bitcoin-23.0-arm-linux-gnueabihf.tar.gz", - "7c8bc63731aa872b7b334a8a7d96e33536ad77d49029bad179b09dca32cd77ac": "bitcoin-23.0-arm64-apple-darwin.tar.gz", - "2caa5898399e415f61d9af80a366a3008e5856efa15aaff74b88acf429674c99": "bitcoin-23.0-powerpc64-linux-gnu.tar.gz", - "217dd0469d0f4962d22818c368358575f6a0abcba8804807bb75325eb2f28b19": "bitcoin-23.0-powerpc64le-linux-gnu.tar.gz", - "078f96b1e92895009c798ab827fb3fde5f6719eee886bd0c0e93acab18ea4865": "bitcoin-23.0-riscv64-linux-gnu.tar.gz", - "c816780583009a9dad426dc0c183c89be9da98906e1e2c7ebae91041c1aaaaf3": "bitcoin-23.0-x86_64-apple-darwin.tar.gz", - "2cca490c1f2842884a3c5b0606f179f9f937177da4eadd628e3f7fd7e25d26d0": "bitcoin-23.0-x86_64-linux-gnu.tar.gz", + "06f4c78271a77752ba5990d60d81b1751507f77efda1e5981b4e92fd4d9969fb": {"tag": "v23.0", "tarball": "bitcoin-23.0-aarch64-linux-gnu.tar.gz"}, + "952c574366aff76f6d6ad1c9ee45a361d64fa04155e973e926dfe7e26f9703a3": {"tag": "v23.0", "tarball": "bitcoin-23.0-arm-linux-gnueabihf.tar.gz"}, + "7c8bc63731aa872b7b334a8a7d96e33536ad77d49029bad179b09dca32cd77ac": {"tag": "v23.0", "tarball": "bitcoin-23.0-arm64-apple-darwin.tar.gz"}, + "2caa5898399e415f61d9af80a366a3008e5856efa15aaff74b88acf429674c99": {"tag": "v23.0", "tarball": "bitcoin-23.0-powerpc64-linux-gnu.tar.gz"}, + "217dd0469d0f4962d22818c368358575f6a0abcba8804807bb75325eb2f28b19": {"tag": "v23.0", "tarball": "bitcoin-23.0-powerpc64le-linux-gnu.tar.gz"}, + "078f96b1e92895009c798ab827fb3fde5f6719eee886bd0c0e93acab18ea4865": {"tag": "v23.0", "tarball": "bitcoin-23.0-riscv64-linux-gnu.tar.gz"}, + "c816780583009a9dad426dc0c183c89be9da98906e1e2c7ebae91041c1aaaaf3": {"tag": "v23.0", "tarball": "bitcoin-23.0-x86_64-apple-darwin.tar.gz"}, + "2cca490c1f2842884a3c5b0606f179f9f937177da4eadd628e3f7fd7e25d26d0": {"tag": "v23.0", "tarball": "bitcoin-23.0-x86_64-linux-gnu.tar.gz"}, } @@ -106,7 +106,7 @@ def download_binary(tag, args) -> int: bin_path = 'bin/bitcoin-core-{}/test.{}'.format( match.group(1), match.group(2)) platform = args.platform - if tag < "v23" and platform in ["x86_64-apple-darwin", "aarch64-apple-darwin"]: + if tag < "v23" and platform in ["x86_64-apple-darwin", "arm64-apple-darwin"]: platform = "osx64" tarball = 'bitcoin-{tag}-{platform}.tar.gz'.format( tag=tag[1:], platform=platform) @@ -135,7 +135,7 @@ def download_binary(tag, args) -> int: hasher.update(afile.read()) tarballHash = hasher.hexdigest() - if tarballHash not in SHA256_SUMS or SHA256_SUMS[tarballHash] != tarball: + if tarballHash not in SHA256_SUMS or SHA256_SUMS[tarballHash]['tarball'] != tarball: if tarball in SHA256_SUMS.values(): print("Checksum did not match") return 1 @@ -214,7 +214,7 @@ def check_host(args) -> int: 'aarch64-*-linux*': 'aarch64-linux-gnu', 'x86_64-*-linux*': 'x86_64-linux-gnu', 'x86_64-apple-darwin*': 'x86_64-apple-darwin', - 'aarch64-apple-darwin*': 'aarch64-apple-darwin', + 'aarch64-apple-darwin*': 'arm64-apple-darwin', } args.platform = '' for pattern, target in platforms.items(): @@ -260,7 +260,12 @@ if __name__ == '__main__': help='download release binary.') parser.add_argument('-t', '--target-dir', action='store', help='target directory.', default='releases') - parser.add_argument('tags', nargs='+', - help="release tags. e.g.: v0.18.1 v0.20.0rc2") + parser.add_argument('tags', nargs='*', default=set( + [v['tag'] for v in SHA256_SUMS.values()] + ), + help='release tags. e.g.: v0.18.1 v0.20.0rc2 ' + '(if not specified, the full list needed for' + 'backwards compatibility tests will be used)' + ) args = parser.parse_args() sys.exit(main(args)) diff --git a/test/lint/README.md b/test/lint/README.md index a23211a72b..8d592c3282 100644 --- a/test/lint/README.md +++ b/test/lint/README.md @@ -28,7 +28,6 @@ To do a full check with `-r`, make sure that you have fetched the upstream repos maintained: * for `src/secp256k1`: https://github.com/bitcoin-core/secp256k1.git (branch master) * for `src/leveldb`: https://github.com/bitcoin-core/leveldb-subtree.git (branch bitcoin-fork) -* for `src/univalue`: https://github.com/bitcoin-core/univalue-subtree.git (branch master) * for `src/crypto/ctaes`: https://github.com/bitcoin-core/ctaes.git (branch master) * for `src/crc32c`: https://github.com/bitcoin-core/crc32c-subtree.git (branch bitcoin-fork) * for `src/minisketch`: https://github.com/sipa/minisketch.git (branch master) diff --git a/test/lint/lint-circular-dependencies.py b/test/lint/lint-circular-dependencies.py index 5d157eb4b1..a0f17ac119 100755 --- a/test/lint/lint-circular-dependencies.py +++ b/test/lint/lint-circular-dependencies.py @@ -22,6 +22,10 @@ EXPECTED_CIRCULAR_DEPENDENCIES = ( "wallet/fees -> wallet/wallet -> wallet/fees", "wallet/wallet -> wallet/walletdb -> wallet/wallet", "kernel/coinstats -> validation -> kernel/coinstats", + "kernel/mempool_persist -> validation -> kernel/mempool_persist", + + # Temporary, removed in followup https://github.com/bitcoin/bitcoin/pull/24230 + "index/base -> node/context -> net_processing -> index/blockfilterindex -> index/base", ) CODE_DIR = "src" diff --git a/test/lint/lint-files.py b/test/lint/lint-files.py index dbb51ce54e..123bee2cbc 100755 --- a/test/lint/lint-files.py +++ b/test/lint/lint-files.py @@ -21,7 +21,7 @@ ALL_SOURCE_FILENAMES_REGEXP = r"^.*\.(cpp|h|py|sh)$" ALLOWED_FILENAME_REGEXP = "^[a-zA-Z0-9/_.@][a-zA-Z0-9/_.@-]*$" ALLOWED_SOURCE_FILENAME_REGEXP = "^[a-z0-9_./-]+$" ALLOWED_SOURCE_FILENAME_EXCEPTION_REGEXP = ( - "^src/(secp256k1/|minisketch/|univalue/|test/fuzz/FuzzedDataProvider.h)" + "^src/(secp256k1/|minisketch/|test/fuzz/FuzzedDataProvider.h)" ) ALLOWED_PERMISSION_NON_EXECUTABLES = 0o644 ALLOWED_PERMISSION_EXECUTABLES = 0o755 diff --git a/test/lint/lint-format-strings.py b/test/lint/lint-format-strings.py index 412cf86791..f54126e023 100755 --- a/test/lint/lint-format-strings.py +++ b/test/lint/lint-format-strings.py @@ -22,6 +22,7 @@ FUNCTION_NAMES_AND_NUMBER_OF_LEADING_ARGUMENTS = [ 'LogConnectFailure,1', 'LogPrint,1', 'LogPrintf,0', + 'LogPrintfCategory,1', 'LogPrintLevel,2', 'printf,0', 'snprintf,2', @@ -76,7 +77,7 @@ def main(): matching_files_filtered = [] for matching_file in matching_files: - if not re.search('^src/(leveldb|secp256k1|minisketch|tinyformat|univalue|test/fuzz/strprintf.cpp)', matching_file): + if not re.search('^src/(leveldb|secp256k1|minisketch|tinyformat|test/fuzz/strprintf.cpp)', matching_file): matching_files_filtered.append(matching_file) matching_files_filtered.sort() diff --git a/test/lint/lint-include-guards.py b/test/lint/lint-include-guards.py index 86284517d5..5867aae028 100755 --- a/test/lint/lint-include-guards.py +++ b/test/lint/lint-include-guards.py @@ -22,7 +22,6 @@ EXCLUDE_FILES_WITH_PREFIX = ['src/crypto/ctaes', 'src/crc32c', 'src/secp256k1', 'src/minisketch', - 'src/univalue', 'src/tinyformat.h', 'src/bench/nanobench.h', 'src/test/fuzz/FuzzedDataProvider.h'] diff --git a/test/lint/lint-includes.py b/test/lint/lint-includes.py index ae62994642..b3fa4b9303 100755 --- a/test/lint/lint-includes.py +++ b/test/lint/lint-includes.py @@ -19,10 +19,9 @@ EXCLUDED_DIRS = ["src/leveldb/", "src/crc32c/", "src/secp256k1/", "src/minisketch/", - "src/univalue/"] + ] -EXPECTED_BOOST_INCLUDES = ["boost/algorithm/string/replace.hpp", - "boost/date_time/posix_time/posix_time.hpp", +EXPECTED_BOOST_INCLUDES = ["boost/date_time/posix_time/posix_time.hpp", "boost/multi_index/hashed_index.hpp", "boost/multi_index/ordered_index.hpp", "boost/multi_index/sequenced_index.hpp", diff --git a/test/lint/lint-locale-dependence.py b/test/lint/lint-locale-dependence.py index 9b2cf4587a..ce7444cd1a 100755 --- a/test/lint/lint-locale-dependence.py +++ b/test/lint/lint-locale-dependence.py @@ -60,7 +60,6 @@ REGEXP_EXTERNAL_DEPENDENCIES_EXCLUSIONS = [ "src/secp256k1/", "src/minisketch/", "src/tinyformat.h", - "src/univalue/" ] LOCALE_DEPENDENT_FUNCTIONS = [ @@ -251,7 +250,7 @@ def main(): exit_code = 1 if exit_code == 1: - print("Unnecessary locale depedence can cause bugs that are very tricky to isolate and fix. Please avoid using locale dependent functions if possible.\n") + print("Unnecessary locale dependence can cause bugs that are very tricky to isolate and fix. Please avoid using locale-dependent functions if possible.\n") print(f"Advice not applicable in this specific case? Add an exception by updating the ignore list in {sys.argv[0]}") sys.exit(exit_code) diff --git a/test/lint/lint-logs.py b/test/lint/lint-logs.py index de53729b4e..aaf697467d 100755 --- a/test/lint/lint-logs.py +++ b/test/lint/lint-logs.py @@ -16,12 +16,12 @@ from subprocess import check_output def main(): - logs_list = check_output(["git", "grep", "--extended-regexp", r"(LogPrintLevel|LogPrintf?)\(", "--", "*.cpp"], universal_newlines=True, encoding="utf8").splitlines() + logs_list = check_output(["git", "grep", "--extended-regexp", r"(LogPrintLevel|LogPrintfCategory|LogPrintf?)\(", "--", "*.cpp"], universal_newlines=True, encoding="utf8").splitlines() unterminated_logs = [line for line in logs_list if not re.search(r'(\\n"|/\* Continued \*/)', line)] if unterminated_logs != []: - print("All calls to LogPrintf(), LogPrint(), LogPrintLevel(), and WalletLogPrintf() should be terminated with \"\\n\".") + print("All calls to LogPrintf(), LogPrintfCategory(), LogPrint(), LogPrintLevel(), and WalletLogPrintf() should be terminated with \"\\n\".") print("") for line in unterminated_logs: diff --git a/test/lint/lint-shell-locale.py b/test/lint/lint-shell-locale.py index f3dfe18a95..309c426374 100755 --- a/test/lint/lint-shell-locale.py +++ b/test/lint/lint-shell-locale.py @@ -41,7 +41,7 @@ def main(): exit_code = 0 shell_files = get_shell_files_list() for file_path in shell_files: - if re.search('src/(secp256k1|minisketch|univalue)/', file_path): + if re.search('src/(secp256k1|minisketch)/', file_path): continue with open(file_path, 'r', encoding='utf-8') as file_obj: diff --git a/test/lint/lint-shell.py b/test/lint/lint-shell.py index f1e4494350..ed95024ef5 100755 --- a/test/lint/lint-shell.py +++ b/test/lint/lint-shell.py @@ -68,7 +68,7 @@ def main(): ] files = get_files(files_cmd) # remove everything that doesn't match this regex - reg = re.compile(r'src/[leveldb,secp256k1,minisketch,univalue]') + reg = re.compile(r'src/[leveldb,secp256k1,minisketch]') files[:] = [file for file in files if not reg.match(file)] # build the `shellcheck` command diff --git a/test/lint/lint-spelling.py b/test/lint/lint-spelling.py index 5da1b243f7..14d7d13a75 100755 --- a/test/lint/lint-spelling.py +++ b/test/lint/lint-spelling.py @@ -12,7 +12,7 @@ Note: Will exit successfully regardless of spelling errors. from subprocess import check_output, STDOUT, CalledProcessError IGNORE_WORDS_FILE = 'test/lint/spelling.ignore-words.txt' -FILES_ARGS = ['git', 'ls-files', '--', ":(exclude)build-aux/m4/", ":(exclude)contrib/seeds/*.txt", ":(exclude)depends/", ":(exclude)doc/release-notes/", ":(exclude)src/leveldb/", ":(exclude)src/crc32c/", ":(exclude)src/qt/locale/", ":(exclude)src/qt/*.qrc", ":(exclude)src/secp256k1/", ":(exclude)src/minisketch/", ":(exclude)src/univalue/", ":(exclude)contrib/builder-keys/keys.txt", ":(exclude)contrib/guix/patches"] +FILES_ARGS = ['git', 'ls-files', '--', ":(exclude)build-aux/m4/", ":(exclude)contrib/seeds/*.txt", ":(exclude)depends/", ":(exclude)doc/release-notes/", ":(exclude)src/leveldb/", ":(exclude)src/crc32c/", ":(exclude)src/qt/locale/", ":(exclude)src/qt/*.qrc", ":(exclude)src/secp256k1/", ":(exclude)src/minisketch/", ":(exclude)contrib/builder-keys/keys.txt", ":(exclude)contrib/guix/patches"] def check_codespell_install(): diff --git a/test/lint/lint-whitespace.py b/test/lint/lint-whitespace.py index d98fc8d9a2..3fb5b80013 100755 --- a/test/lint/lint-whitespace.py +++ b/test/lint/lint-whitespace.py @@ -22,7 +22,6 @@ EXCLUDED_DIRS = ["depends/patches/", "src/crc32c/", "src/secp256k1/", "src/minisketch/", - "src/univalue/", "doc/release-notes/", "src/qt/locale"] diff --git a/test/lint/spelling.ignore-words.txt b/test/lint/spelling.ignore-words.txt index 0efd298408..82f18010c1 100644 --- a/test/lint/spelling.ignore-words.txt +++ b/test/lint/spelling.ignore-words.txt @@ -1,21 +1,23 @@ asend ba blockin +bu cachable -creat -desig +clen fo fpr hights -hist -inout +inflight invokable keypair mor nd nin ser +siz +stap unparseable unser useable +warmup wit diff --git a/test/sanitizer_suppressions/tsan b/test/sanitizer_suppressions/tsan index 3acf575d07..d331991273 100644 --- a/test/sanitizer_suppressions/tsan +++ b/test/sanitizer_suppressions/tsan @@ -13,7 +13,7 @@ race:zmq::* race:bitcoin-qt # deadlock (TODO fix) -deadlock:CChainState::ConnectTip +deadlock:Chainstate::ConnectTip # Intentional deadlock in tests deadlock:sync_tests::potential_deadlock_detected diff --git a/test/sanitizer_suppressions/ubsan b/test/sanitizer_suppressions/ubsan index e6cfe5f81a..67ef512895 100644 --- a/test/sanitizer_suppressions/ubsan +++ b/test/sanitizer_suppressions/ubsan @@ -1,10 +1,10 @@ # -fsanitize=undefined suppressions # ================================= -# This would be `signed-integer-overflow:CTxMemPool::PrioritiseTransaction`, +# The suppressions would be `sanitize-type:ClassName::MethodName`, # however due to a bug in clang the symbolizer is disabled and thus no symbol # names can be used. # See https://github.com/google/sanitizers/issues/1364 -signed-integer-overflow:txmempool.cpp + # https://github.com/bitcoin/bitcoin/pull/21798#issuecomment-829180719 signed-integer-overflow:policy/feerate.cpp |