diff options
997 files changed, 40231 insertions, 24788 deletions
diff --git a/.cirrus.yml b/.cirrus.yml index 9b0e2dbd2e..f2a08cdd48 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -2,6 +2,7 @@ env: # Global defaults PACKAGE_MANAGER_INSTALL: "apt-get update && apt-get install -y" MAKEJOBS: "-j10" TEST_RUNNER_PORT_MIN: "14000" # Must be larger than 12321, which is used for the http cache. See https://cirrus-ci.org/guide/writing-tasks/#http-cache + CI_FAILFAST_TEST_LEAVE_DANGLING: "1" # Cirrus CI does not care about dangling process and setting this variable avoids killing the CI script itself on error CCACHE_SIZE: "200M" CCACHE_DIR: "/tmp/ccache_dir" CCACHE_NOHASHDIR: "1" # Debug info might contain a stale path if the build dir changes, but this is fine @@ -34,22 +35,30 @@ base_template: &BASE_TEMPLATE 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 @@ -72,24 +81,40 @@ task: << : *CIRRUS_EPHEMERAL_WORKER_TEMPLATE_ENV task: - name: "Win64 native [msvc]" + name: 'tidy [jammy]' + << : *GLOBAL_TASK_TEMPLATE + container: + image: ubuntu:jammy + cpu: 2 + memory: 5G + # For faster CI feedback, immediately schedule the linters + << : *CREDITS_TEMPLATE + env: + << : *CIRRUS_EPHEMERAL_WORKER_TEMPLATE_ENV + FILE_ENV: "./ci/test/00_setup_env_native_tidy.sh" + +task: + name: "Win64 native [vs2022]" << : *FILTER_TEMPLATE windows_container: cpu: 4 memory: 8G - image: cirrusci/windowsservercore:visualstudio2019 + 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: '2021.05.12' + CI_VCPKG_TAG: '2022.06.16.1' VCPKG_DOWNLOADS: 'C:\Users\ContainerAdministrator\AppData\Local\vcpkg\downloads' VCPKG_DEFAULT_BINARY_CACHE: 'C:\Users\ContainerAdministrator\AppData\Local\vcpkg\archives' - QT_DOWNLOAD_URL: 'https://download.qt.io/official_releases/qt/5.15/5.15.2/single/qt-everywhere-src-5.15.2.zip' - QT_LOCAL_PATH: 'C:\qt-everywhere-src-5.15.2.zip' - QT_SOURCE_DIR: 'C:\qt-everywhere-src-5.15.2' + 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.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" @@ -104,7 +129,7 @@ task: folder: "%QTBASEDIR%" reupload_on_changes: false fingerprint_script: - - echo %QT_DOWNLOAD_URL% + - echo %QT_DOWNLOAD_URL% %QT_CONFIGURE_COMMAND% - msbuild -version populate_script: - curl -L -o C:\jom.zip http://download.qt.io/official_releases/jom/jom.zip @@ -116,7 +141,7 @@ task: - cd %QT_SOURCE_DIR% - mkdir build - cd build - - ..\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 -prefix %QTBASEDIR% + - '%QT_CONFIGURE_COMMAND% -prefix %QTBASEDIR%' - jom - jom install vcpkg_tools_cache: @@ -130,12 +155,17 @@ task: reupload_on_changes: true fingerprint_script: - echo %CI_VCPKG_TAG% + - type build_msvc\vcpkg.json - msbuild -version populate_script: - mkdir %VCPKG_DEFAULT_BINARY_CACHE% - install_python_script: + ccache_cache: + folder: '%CCACHE_DIR%' + install_tools_script: + - choco install --yes --no-progress ccache - choco install --yes --no-progress python3 --version=3.9.6 - pip install zmq + - ccache --version - python -VV install_vcpkg_script: - cd .. @@ -147,9 +177,12 @@ task: - .\vcpkg integrate install - .\vcpkg version build_script: + - '%x64_NATIVE_TOOLS%' - cd %CIRRUS_WORKING_DIR% + - ccache --zero-stats --max-size=%CCACHE_SIZE% - python build_msvc\msvc-autogen.py - - msbuild build_msvc\bitcoin.sln -property:Configuration=Release -maxCpuCount -verbosity:minimal -noLogo + - msbuild build_msvc\bitcoin.sln -property:CLToolExe=%WRAPPED_CL% -property:Configuration=Release -maxCpuCount -verbosity:minimal -noLogo + - ccache --show-stats unit_tests_script: - src\test_bitcoin.exe -l test_suite - src\bench_bitcoin.exe > NUL @@ -195,7 +228,7 @@ task: FILE_ENV: "./ci/test/00_setup_env_i686_centos.sh" task: - name: '[previous releases, uses qt5 dev package and some depends packages, DEBUG] [unsigned char] [bionic]' + name: '[previous releases, uses qt5 dev package and some depends packages, DEBUG] [unsigned char] [buster]' previous_releases_cache: folder: "releases" << : *GLOBAL_TASK_TEMPLATE @@ -226,12 +259,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 @@ -258,13 +299,13 @@ task: FILE_ENV: "./ci/test/00_setup_env_i686_multiprocess.sh" task: - name: '[no wallet] [bionic]' + name: '[no wallet, libbitcoinkernel] [bionic]' << : *GLOBAL_TASK_TEMPLATE container: image: ubuntu:bionic env: << : *CIRRUS_EPHEMERAL_WORKER_TEMPLATE_ENV - FILE_ENV: "./ci/test/00_setup_env_native_nowallet.sh" + FILE_ENV: "./ci/test/00_setup_env_native_nowallet_libbitcoinkernel.sh" task: name: 'macOS 10.15 [gui, no tests] [focal]' @@ -281,25 +322,23 @@ 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.2 # https://cirrus-ci.org/guide/macOS + 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]' << : *BASE_TEMPLATE android_sdk_cache: folder: "depends/SDKs/android" - fingerprint_key: "ANDROID_API_LEVEL=28 ANDROID_BUILD_TOOLS_VERSION=28.0.3 ANDROID_NDK_VERSION=23.1.7779620" + fingerprint_key: "ANDROID_API_LEVEL=28 ANDROID_BUILD_TOOLS_VERSION=28.0.3 ANDROID_NDK_VERSION=23.2.8568313" depends_sources_cache: folder: "depends/sources" fingerprint_script: git rev-list -1 HEAD ./depends diff --git a/.gitignore b/.gitignore index f84a53178e..6ca9d39a16 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ src/bitcoin-gui src/bitcoin-node src/bitcoin-tx src/bitcoin-util +src/bitcoin-chainstate src/bitcoin-wallet src/test/fuzz/fuzz src/test/test_bitcoin @@ -43,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 @@ -76,7 +75,6 @@ src/qt/bitcoin-qt.includes *.log *.trs *.dmg -*.iso *.json.h *.raw.h @@ -150,3 +148,5 @@ osx_volname dist/ /guix-build-* + +/ci/scratch/ 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/CONTRIBUTING.md b/CONTRIBUTING.md index 254e610393..0ae4ff1a92 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -10,10 +10,9 @@ First, in terms of structure, there is no particular concept of "Bitcoin Core developers" in the sense of privileged people. Open source often naturally revolves around a meritocracy where contributors earn trust from the developer community over time. Nevertheless, some hierarchy is necessary for practical -purposes. As such, there are repository "maintainers" who are responsible for -merging pull requests, as well as a "lead maintainer" who is responsible for the -[release cycle](/doc/release-process.md) as well as overall merging, moderation -and appointment of maintainers. +purposes. As such, there are repository maintainers who are responsible for +merging pull requests, the [release cycle](/doc/release-process.md), and +moderation. Getting Started --------------- @@ -153,7 +152,8 @@ the pull request affects. Valid areas as: - `test`, `qa` or `ci` for changes to the unit tests, QA tests or CI code - `util` or `lib` for changes to the utils or libraries - `wallet` for changes to the wallet code - - `build` for changes to the GNU Autotools or reproducible builds + - `build` for changes to the GNU Autotools or MSVC builds + - `guix` for changes to the GUIX reproducible builds Examples: @@ -216,9 +216,9 @@ Please update the resulting commit message, if needed. It should read as a coherent message. In most cases, this means not just listing the interim commits. -If you have problems with squashing or other git workflows, you can enable -"Allow edits from maintainers" in the right-hand sidebar of the GitHub web -interface and ask for help in the pull request. +If your change contains a merge commit, the above workflow may not work and you +will need to remove the merge commit first. See the next section for details on +how to rebase. Please refrain from creating several pull requests for the same change. Use the pull request that is already open (or was created earlier) to amend @@ -231,7 +231,9 @@ pull request to pull request. ### Rebasing Changes When a pull request conflicts with the target branch, you may be asked to rebase it on top of the current target branch. -The `git rebase` command will take care of rebuilding your commits on top of the new base. + + git fetch https://github.com/bitcoin/bitcoin # Fetch the latest upstream commit + git rebase FETCH_HEAD # Rebuild commits on top of the new base This project aims to have a clean git history, where code changes are only made in non-merge commits. This simplifies auditability because merge commits can be assumed to not contain arbitrary code changes. Merge commits should be signed, @@ -292,7 +294,7 @@ projects such as libsecp256k1), and is not to be confused with overall Bitcoin Network Protocol consensus changes. Whether a pull request is merged into Bitcoin Core rests with the project merge -maintainers and ultimately the project lead. +maintainers. Maintainers will take into consideration if a patch is in line with the general principles of the project; meets the minimum standards for inclusion; and will diff --git a/Makefile.am b/Makefile.am index 1412624d54..8b61763ae4 100644 --- a/Makefile.am +++ b/Makefile.am @@ -12,7 +12,7 @@ if ENABLE_MAN SUBDIRS += doc/man endif .PHONY: deploy FORCE -.INTERMEDIATE: $(OSX_TEMP_ISO) $(COVERAGE_INFO) +.INTERMEDIATE: $(COVERAGE_INFO) export PYTHONPATH @@ -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) @@ -37,8 +38,6 @@ space := $(empty) $(empty) OSX_APP=Bitcoin-Qt.app OSX_VOLNAME = $(subst $(space),-,$(PACKAGE_NAME)) OSX_DMG = $(OSX_VOLNAME).dmg -OSX_TEMP_ISO = $(OSX_DMG:.dmg=).temp.iso -OSX_BACKGROUND_IMAGE=$(top_srcdir)/contrib/macdeploy/background.tiff OSX_DEPLOY_SCRIPT=$(top_srcdir)/contrib/macdeploy/macdeployqtplus OSX_INSTALLER_ICONS=$(top_srcdir)/src/qt/res/icons/bitcoin.icns OSX_PLIST=$(top_builddir)/share/qt/Info.plist #not installed @@ -48,7 +47,8 @@ DIST_CONTRIB = \ $(top_srcdir)/test/sanitizer_suppressions/tsan \ $(top_srcdir)/test/sanitizer_suppressions/ubsan \ $(top_srcdir)/contrib/linearize/linearize-data.py \ - $(top_srcdir)/contrib/linearize/linearize-hashes.py + $(top_srcdir)/contrib/linearize/linearize-hashes.py \ + $(top_srcdir)/contrib/signet/miner DIST_SHARE = \ $(top_srcdir)/share/genbuild.sh \ @@ -64,7 +64,6 @@ WINDOWS_PACKAGING = $(top_srcdir)/share/pixmaps/bitcoin.ico \ $(top_srcdir)/doc/README_windows.txt OSX_PACKAGING = $(OSX_DEPLOY_SCRIPT) $(OSX_INSTALLER_ICONS) \ - $(top_srcdir)/contrib/macdeploy/detached-sig-apply.sh \ $(top_srcdir)/contrib/macdeploy/detached-sig-create.sh COVERAGE_INFO = $(COV_TOOL_WRAPPER) baseline.info \ @@ -80,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 @@ -130,31 +130,16 @@ $(OSX_DMG): $(OSX_APP_BUILT) $(OSX_PACKAGING) deploydir: $(OSX_DMG) else !BUILD_DARWIN APP_DIST_DIR=$(top_builddir)/dist -APP_DIST_EXTRAS=$(APP_DIST_DIR)/.background/background.tiff $(APP_DIST_DIR)/.DS_Store $(APP_DIST_DIR)/Applications -$(APP_DIST_DIR)/Applications: - @rm -f $@ - @cd $(@D); $(LN_S) /Applications $(@F) - -$(APP_DIST_EXTRAS): $(APP_DIST_DIR)/$(OSX_APP)/Contents/MacOS/Bitcoin-Qt - -$(OSX_TEMP_ISO): $(APP_DIST_EXTRAS) +$(OSX_DMG): deploydir $(XORRISOFS) -D -l -V "$(OSX_VOLNAME)" -no-pad -r -dir-mode 0755 -o $@ $(APP_DIST_DIR) -- $(if $(SOURCE_DATE_EPOCH),-volume_date all_file_dates =$(SOURCE_DATE_EPOCH)) -$(OSX_DMG): $(OSX_TEMP_ISO) - $(DMG) dmg "$<" "$@" - -$(APP_DIST_DIR)/.background/background.tiff: - $(MKDIR_P) $(@D) - cp $(OSX_BACKGROUND_IMAGE) $@ - $(APP_DIST_DIR)/$(OSX_APP)/Contents/MacOS/Bitcoin-Qt: $(OSX_APP_BUILT) $(OSX_PACKAGING) - INSTALLNAMETOOL=$(INSTALLNAMETOOL) OTOOL=$(OTOOL) STRIP=$(STRIP) $(PYTHON) $(OSX_DEPLOY_SCRIPT) $(OSX_APP) $(OSX_VOLNAME) -translations-dir=$(QT_TRANSLATION_DIR) + INSTALL_NAME_TOOL=$(INSTALL_NAME_TOOL) OTOOL=$(OTOOL) STRIP=$(STRIP) $(PYTHON) $(OSX_DEPLOY_SCRIPT) $(OSX_APP) $(OSX_VOLNAME) -translations-dir=$(QT_TRANSLATION_DIR) -deploydir: $(APP_DIST_EXTRAS) +deploydir: $(APP_DIST_DIR)/$(OSX_APP)/Contents/MacOS/Bitcoin-Qt endif !BUILD_DARWIN -appbundle: $(OSX_APP_BUILT) deploy: $(OSX_DMG) endif @@ -191,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" \ @@ -6,18 +6,14 @@ https://bitcoincore.org For an immediately usable, binary version of the Bitcoin Core software, see https://bitcoincore.org/en/download/. -Further information about Bitcoin Core is available in the [doc folder](/doc). - -What is Bitcoin? ----------------- +What is Bitcoin Core? +--------------------- -Bitcoin is an experimental digital currency that enables instant payments to -anyone, anywhere in the world. Bitcoin uses peer-to-peer technology to operate -with no central authority: managing transactions and issuing money are carried -out collectively by the network. Bitcoin Core is the name of open source -software which enables the use of this currency. +Bitcoin Core connects to the Bitcoin peer-to-peer network to download and fully +validate blocks and transactions. It also includes a wallet and graphical user +interface, which can be optionally built. -For more information read the original Bitcoin whitepaper. +Further information about Bitcoin Core is available in the [doc folder](/doc). License ------- @@ -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.sh @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..c6a496edf1 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -13,7 +13,6 @@ 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 | diff --git a/build-aux/m4/ax_cxx_compile_stdcxx.m4 b/build-aux/m4/ax_cxx_compile_stdcxx.m4 index 43087b2e68..51a35054d0 100644 --- a/build-aux/m4/ax_cxx_compile_stdcxx.m4 +++ b/build-aux/m4/ax_cxx_compile_stdcxx.m4 @@ -10,13 +10,13 @@ # # 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). +# CXXCPP to enable support. VERSION may be '11', '14', '17', or '20' for +# the respective C++ standard version. # # 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 an extended mode. +# 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 @@ -35,13 +35,15 @@ # 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> +# Copyright (c) 2021 Jörn Heusipp <osmanx@problemloesungsmaschine.de> # # 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 11 +#serial 14 dnl This macro is based on the code from the AX_CXX_COMPILE_STDCXX_11 macro dnl (serial version number 13). @@ -50,6 +52,7 @@ 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"], + [$1], [20], [ax_cxx_compile_alternatives="20"], [m4_fatal([invalid first argument `$1' to AX_CXX_COMPILE_STDCXX])])dnl m4_if([$2], [], [], [$2], [ext], [], @@ -62,6 +65,16 @@ AC_DEFUN([AX_CXX_COMPILE_STDCXX], [dnl 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 @@ -140,7 +153,6 @@ 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], @@ -148,12 +160,24 @@ m4_define([_AX_CXX_COMPILE_STDCXX_testbody_14], _AX_CXX_COMPILE_STDCXX_testbody_new_in_14 ) +dnl Test body for checking C++17 support + 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 Test body for checking C++20 support + +m4_define([_AX_CXX_COMPILE_STDCXX_testbody_20], + _AX_CXX_COMPILE_STDCXX_testbody_new_in_11 + _AX_CXX_COMPILE_STDCXX_testbody_new_in_14 + _AX_CXX_COMPILE_STDCXX_testbody_new_in_17 + _AX_CXX_COMPILE_STDCXX_testbody_new_in_20 +) + + dnl Tests for new features in C++11 m4_define([_AX_CXX_COMPILE_STDCXX_testbody_new_in_11], [[ @@ -949,3 +973,33 @@ namespace cxx17 #endif // __cplusplus < 201703L ]]) + + +dnl Tests for new features in C++20 + +m4_define([_AX_CXX_COMPILE_STDCXX_testbody_new_in_20], [[ + +#ifndef __cplusplus + +#error "This is not a C++ compiler" + +#elif __cplusplus < 202002L + +#error "This is not a C++20 compiler" + +#else + +#include <version> + +namespace cxx20 +{ + +// As C++20 supports feature test macros in the standard, there is no +// immediate need to actually test for feature availability on the +// Autoconf side. + +} // namespace cxx20 + +#endif // __cplusplus < 202002L + +]]) diff --git a/build-aux/m4/bitcoin_qt.m4 b/build-aux/m4/bitcoin_qt.m4 index 1454e33f6e..a716cd9a27 100644 --- a/build-aux/m4/bitcoin_qt.m4 +++ b/build-aux/m4/bitcoin_qt.m4 @@ -116,8 +116,8 @@ AC_DEFUN([BITCOIN_QT_CONFIGURE],[ BITCOIN_QT_CHECK([ TEMP_CPPFLAGS=$CPPFLAGS TEMP_CXXFLAGS=$CXXFLAGS - CPPFLAGS="$QT_INCLUDES $CPPFLAGS" - CXXFLAGS="$PIC_FLAGS $CXXFLAGS" + CPPFLAGS="$QT_INCLUDES $CORE_CPPFLAGS $CPPFLAGS" + CXXFLAGS="$PIC_FLAGS $CORE_CXXFLAGS $CXXFLAGS" _BITCOIN_QT_IS_STATIC if test "$bitcoin_cv_static_qt" = "yes"; then _BITCOIN_QT_CHECK_STATIC_LIBS @@ -178,8 +178,8 @@ AC_DEFUN([BITCOIN_QT_CONFIGURE],[ AC_MSG_CHECKING([whether -fPIE can be used with this Qt config]) TEMP_CPPFLAGS=$CPPFLAGS TEMP_CXXFLAGS=$CXXFLAGS - CPPFLAGS="$QT_INCLUDES $CPPFLAGS" - CXXFLAGS="$PIE_FLAGS $CXXFLAGS" + CPPFLAGS="$QT_INCLUDES $CORE_CPPFLAGS $CPPFLAGS" + CXXFLAGS="$PIE_FLAGS $CORE_CXXFLAGS $CXXFLAGS" AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ #include <QtCore/qconfig.h> #ifndef QT_VERSION @@ -201,7 +201,7 @@ AC_DEFUN([BITCOIN_QT_CONFIGURE],[ BITCOIN_QT_CHECK([ AC_MSG_CHECKING([whether -fPIC is needed with this Qt config]) TEMP_CPPFLAGS=$CPPFLAGS - CPPFLAGS="$QT_INCLUDES $CPPFLAGS" + CPPFLAGS="$QT_INCLUDES $CORE_CPPFLAGS $CPPFLAGS" AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ #include <QtCore/qconfig.h> #ifndef QT_VERSION 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/.gitignore b/build_msvc/.gitignore index b0e557bc0c..b2eb9313a0 100644 --- a/build_msvc/.gitignore +++ b/build_msvc/.gitignore @@ -22,6 +22,7 @@ bench_bitcoin/bench_bitcoin.vcxproj libtest_util/libtest_util.vcxproj /bitcoin_config.h +/common.init.vcxproj */Win32 libbitcoin_qt/QtGeneratedFiles/* diff --git a/build_msvc/README.md b/build_msvc/README.md index cabe4d55ec..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-src-5.15.2.zip`](https://download.qt.io/official_releases/qt/5.15/5.15.2/single/qt-everywhere-src-5.15.2.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 --------------------- @@ -83,4 +85,4 @@ If is it enabled then in the output `Dynamic base` will be listed in the `DLL ch Terminal Server Aware ``` -This may not disable all stack randomization as versions of windows employ additional stack randomization protections. These protections must be turned off in the OS configuration.
\ No newline at end of file +This may not disable all stack randomization as versions of windows employ additional stack randomization protections. These protections must be turned off in the OS configuration. diff --git a/build_msvc/bitcoin_config.h.in b/build_msvc/bitcoin_config.h.in index e25024e871..5f715282eb 100644 --- a/build_msvc/bitcoin_config.h.in +++ b/build_msvc/bitcoin_config.h.in @@ -50,8 +50,8 @@ /* 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 if the compiler supports basic C++20 syntax */ +#define HAVE_CXX20 1 /* Define to 1 if you have the declaration of `be16toh', and to 0 if you don't. */ @@ -125,10 +125,6 @@ don't. */ #define HAVE_DECL_STRERROR_R 0 -/* Define to 1 if you have the declaration of `strnlen', and to 0 if you - don't. */ -#define HAVE_DECL_STRNLEN 1 - /* Define if the dllexport attribute is supported. */ #define HAVE_DLLEXPORT_ATTRIBUTE 1 diff --git a/build_msvc/common.init.vcxproj b/build_msvc/common.init.vcxproj.in index 0cbe2effd5..7f623c6296 100644 --- a/build_msvc/common.init.vcxproj +++ b/build_msvc/common.init.vcxproj.in @@ -39,7 +39,7 @@ <PropertyGroup Condition="'$(Configuration)'=='Release'" Label="Configuration"> <LinkIncremental>false</LinkIncremental> <UseDebugLibraries>false</UseDebugLibraries> - <PlatformToolset>v142</PlatformToolset> + <PlatformToolset>@TOOLSET@</PlatformToolset> <CharacterSet>Unicode</CharacterSet> <GenerateManifest>No</GenerateManifest> <OutDir>$(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\</OutDir> @@ -49,7 +49,7 @@ <PropertyGroup Condition="'$(Configuration)'=='Debug'" Label="Configuration"> <LinkIncremental>true</LinkIncremental> <UseDebugLibraries>true</UseDebugLibraries> - <PlatformToolset>v142</PlatformToolset> + <PlatformToolset>@TOOLSET@</PlatformToolset> <CharacterSet>Unicode</CharacterSet> <OutDir>$(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\</OutDir> <IntDir>$(Platform)\$(Configuration)\$(ProjectName)\</IntDir> @@ -87,10 +87,10 @@ <ClCompile> <WarningLevel>Level3</WarningLevel> <PrecompiledHeader>NotUsing</PrecompiledHeader> - <AdditionalOptions>/utf-8 /Zc:__cplusplus /std:c++17 %(AdditionalOptions)</AdditionalOptions> + <AdditionalOptions>/utf-8 /Zc:__cplusplus /std:c++20 %(AdditionalOptions)</AdditionalOptions> <DisableSpecificWarnings>4018;4244;4267;4334;4715;4805;4834</DisableSpecificWarnings> <TreatWarningAsError>true</TreatWarningAsError> - <PreprocessorDefinitions>_SILENCE_CXX17_CODECVT_HEADER_DEPRECATION_WARNING;_SILENCE_CXX17_OLD_ALLOCATOR_MEMBERS_DEPRECATION_WARNING;ZMQ_STATIC;NOMINMAX;WIN32;HAVE_CONFIG_H;_CRT_SECURE_NO_WARNINGS;_SCL_SECURE_NO_WARNINGS;_CONSOLE;_WIN32_WINNT=0x0601;_WIN32_IE=0x0501;WIN32_LEAN_AND_MEAN;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <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/libsecp256k1.vcxproj b/build_msvc/libsecp256k1/libsecp256k1.vcxproj index f9b0a7975c..16ee32d87e 100644 --- a/build_msvc/libsecp256k1/libsecp256k1.vcxproj +++ b/build_msvc/libsecp256k1/libsecp256k1.vcxproj @@ -8,6 +8,8 @@ <ConfigurationType>StaticLibrary</ConfigurationType> </PropertyGroup> <ItemGroup> + <ClCompile Include="..\..\src\secp256k1\src\precomputed_ecmult.c" /> + <ClCompile Include="..\..\src\secp256k1\src\precomputed_ecmult_gen.c" /> <ClCompile Include="..\..\src\secp256k1\src\secp256k1.c" /> </ItemGroup> <ItemDefinitionGroup> 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 2a70cd9332..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', @@ -50,13 +50,6 @@ def parse_makefile(makefile): lib_sources[current_lib] = [] break -def set_common_properties(toolset): - with open(os.path.join(SOURCE_DIR, '../build_msvc/common.init.vcxproj'), 'r', encoding='utf-8') as rfile: - s = rfile.read() - s = re.sub('<PlatformToolset>.*?</PlatformToolset>', '<PlatformToolset>'+toolset+'</PlatformToolset>', s) - with open(os.path.join(SOURCE_DIR, '../build_msvc/common.init.vcxproj'), 'w', encoding='utf-8',newline='\n') as wfile: - wfile.write(s) - def parse_config_into_btc_config(): def find_between( s, first, last ): try: @@ -92,13 +85,18 @@ def parse_config_into_btc_config(): with open(os.path.join(SOURCE_DIR,'../build_msvc/bitcoin_config.h'), "w", encoding="utf8") as btc_config: btc_config.writelines(template) +def set_properties(vcxproj_filename, placeholder, content): + with open(vcxproj_filename + '.in', 'r', encoding='utf-8') as vcxproj_in_file: + with open(vcxproj_filename, 'w', encoding='utf-8') as vcxproj_file: + vcxproj_file.write(vcxproj_in_file.read().replace(placeholder, content)) + def main(): parser = argparse.ArgumentParser(description='Bitcoin-core msbuild configuration initialiser.') - parser.add_argument('-toolset', nargs='?',help='Optionally sets the msbuild platform toolset, e.g. v142 for Visual Studio 2019.' + parser.add_argument('-toolset', nargs='?', default=DEFAULT_PLATFORM_TOOLSET, + help='Optionally sets the msbuild platform toolset, e.g. v143 for Visual Studio 2022.' ' default is %s.'%DEFAULT_PLATFORM_TOOLSET) args = parser.parse_args() - if args.toolset: - set_common_properties(args.toolset) + set_properties(os.path.join(SOURCE_DIR, '../build_msvc/common.init.vcxproj'), '@TOOLSET@', args.toolset) for makefile_name in os.listdir(SOURCE_DIR): if 'Makefile' in makefile_name: @@ -110,10 +108,7 @@ def main(): content += ' <ClCompile Include="..\\..\\src\\' + source_filename + '">\n' content += ' <ObjectFileName>$(IntDir)' + object_filename + '</ObjectFileName>\n' content += ' </ClCompile>\n' - with open(vcxproj_filename + '.in', 'r', encoding='utf-8') as vcxproj_in_file: - with open(vcxproj_filename, 'w', encoding='utf-8') as vcxproj_file: - vcxproj_file.write(vcxproj_in_file.read().replace( - '@SOURCE_FILES@\n', content)) + set_properties(vcxproj_filename, '@SOURCE_FILES@\n', content) parse_config_into_btc_config() copyfile(os.path.join(SOURCE_DIR,'../build_msvc/bitcoin_config.h'), os.path.join(SOURCE_DIR, 'config/bitcoin-config.h')) copyfile(os.path.join(SOURCE_DIR,'../build_msvc/libsecp256k1_config.h'), os.path.join(SOURCE_DIR, 'secp256k1/src/libsecp256k1-config.h')) diff --git a/build_msvc/test_bitcoin-qt/test_bitcoin-qt.vcxproj b/build_msvc/test_bitcoin-qt/test_bitcoin-qt.vcxproj index d3be693e99..ec572b4f2e 100644 --- a/build_msvc/test_bitcoin-qt/test_bitcoin-qt.vcxproj +++ b/build_msvc/test_bitcoin-qt/test_bitcoin-qt.vcxproj @@ -12,6 +12,7 @@ <ClCompile Include="..\..\src\test\util\setup_common.cpp" /> <ClCompile Include="..\..\src\qt\test\addressbooktests.cpp" /> <ClCompile Include="..\..\src\qt\test\apptests.cpp" /> + <ClCompile Include="..\..\src\qt\test\optiontests.cpp" /> <ClCompile Include="..\..\src\qt\test\rpcnestedtests.cpp" /> <ClCompile Include="..\..\src\qt\test\test_main.cpp" /> <ClCompile Include="..\..\src\qt\test\uritests.cpp" /> @@ -20,6 +21,7 @@ <ClCompile Include="..\..\src\wallet\test\wallet_test_fixture.cpp" /> <ClCompile Include="$(GeneratedFilesOutDir)\moc\moc_addressbooktests.cpp" /> <ClCompile Include="$(GeneratedFilesOutDir)\moc\moc_apptests.cpp" /> + <ClCompile Include="$(GeneratedFilesOutDir)\moc\moc_optiontests.cpp" /> <ClCompile Include="$(GeneratedFilesOutDir)\moc\moc_rpcnestedtests.cpp" /> <ClCompile Include="$(GeneratedFilesOutDir)\moc\moc_uritests.cpp" /> <ClCompile Include="$(GeneratedFilesOutDir)\moc\moc_wallettests.cpp" /> @@ -88,6 +90,7 @@ <ItemGroup> <MocTestFiles Include="..\..\src\qt\test\addressbooktests.h" /> <MocTestFiles Include="..\..\src\qt\test\apptests.h" /> + <MocTestFiles Include="..\..\src\qt\test\optiontests.h" /> <MocTestFiles Include="..\..\src\qt\test\rpcnestedtests.h" /> <MocTestFiles Include="..\..\src\qt\test\uritests.h" /> <MocTestFiles Include="..\..\src\qt\test\wallettests.h" /> diff --git a/build_msvc/vcpkg.json b/build_msvc/vcpkg.json index d8753ec21f..86773d1fd3 100644 --- a/build_msvc/vcpkg.json +++ b/build_msvc/vcpkg.json @@ -8,7 +8,6 @@ "boost-signals2", "boost-test", "sqlite3", - "double-conversion", { "name": "libevent", "features": ["thread"] diff --git a/ci/lint/04_install.sh b/ci/lint/04_install.sh index 1518c033f4..8330df87eb 100755 --- a/ci/lint/04_install.sh +++ b/ci/lint/04_install.sh @@ -11,9 +11,9 @@ ${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} pip3 install codespell==2.0.0 -${CI_RETRY_EXE} pip3 install flake8==3.8.3 -${CI_RETRY_EXE} pip3 install mypy==0.910 +${CI_RETRY_EXE} pip3 install codespell==2.1.0 +${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 ${CI_RETRY_EXE} pip3 install vulture==2.3 diff --git a/ci/lint/06_script.sh b/ci/lint/06_script.sh index fa9bf4c646..1f14dd079f 100755 --- a/ci/lint/06_script.sh +++ b/ci/lint/06_script.sh @@ -18,11 +18,10 @@ 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 -test/lint/lint-all.sh +test/lint/all-lint.py if [ "$CIRRUS_REPO_FULL_NAME" = "bitcoin/bitcoin" ] && [ "$CIRRUS_PR" = "" ] ; then # Sanity check only the last few commits to get notified of missing sigs, diff --git a/ci/test/00_setup_env.sh b/ci/test/00_setup_env.sh index e806683128..5a150d5f80 100755 --- a/ci/test/00_setup_env.sh +++ b/ci/test/00_setup_env.sh @@ -37,6 +37,7 @@ export USE_BUSY_BOX=${USE_BUSY_BOX:-false} export RUN_UNIT_TESTS=${RUN_UNIT_TESTS:-true} export RUN_FUNCTIONAL_TESTS=${RUN_FUNCTIONAL_TESTS:-true} +export RUN_TIDY=${RUN_TIDY:-false} export RUN_SECURITY_TESTS=${RUN_SECURITY_TESTS:-false} # By how much to scale the test_runner timeouts (option --timeout-factor). # This is needed because some ci machines have slow CPU or disk, so sanitizers diff --git a/ci/test/00_setup_env_android.sh b/ci/test/00_setup_env_android.sh index 522a5497fa..6732db36ad 100755 --- a/ci/test/00_setup_env_android.sh +++ b/ci/test/00_setup_env_android.sh @@ -7,7 +7,7 @@ export LC_ALL=C.UTF-8 export HOST=aarch64-linux-android -export PACKAGES="clang llvm unzip openjdk-8-jdk gradle" +export PACKAGES="unzip openjdk-8-jdk gradle" export CONTAINER_NAME=ci_android export DOCKER_NAME_TAG="ubuntu:focal" @@ -16,8 +16,8 @@ export RUN_FUNCTIONAL_TESTS=false export ANDROID_API_LEVEL=28 export ANDROID_BUILD_TOOLS_VERSION=28.0.3 -export ANDROID_NDK_VERSION=23.1.7779620 -export ANDROID_TOOLS_URL=https://dl.google.com/android/repository/commandlinetools-linux-6609375_latest.zip +export ANDROID_NDK_VERSION=23.2.8568313 +export ANDROID_TOOLS_URL=https://dl.google.com/android/repository/commandlinetools-linux-8512546_latest.zip export ANDROID_HOME="${DEPENDS_DIR}/SDKs/android" export ANDROID_NDK_HOME="${ANDROID_HOME}/ndk/${ANDROID_NDK_VERSION}" export DEP_OPTS="ANDROID_SDK=${ANDROID_HOME} ANDROID_NDK=${ANDROID_NDK_HOME} ANDROID_API_LEVEL=${ANDROID_API_LEVEL} ANDROID_TOOLCHAIN_BIN=${ANDROID_NDK_HOME}/toolchains/llvm/prebuilt/linux-x86_64/bin/" diff --git a/ci/test/00_setup_env_i686_multiprocess.sh b/ci/test/00_setup_env_i686_multiprocess.sh index b333635759..766424769d 100755 --- a/ci/test/00_setup_env_i686_multiprocess.sh +++ b/ci/test/00_setup_env_i686_multiprocess.sh @@ -15,4 +15,3 @@ export GOAL="install" export BITCOIN_CONFIG="--enable-debug CC='clang -m32' CXX='clang++ -m32' LDFLAGS='--rtlib=compiler-rt -lgcc_s'" export TEST_RUNNER_ENV="BITCOIND=bitcoin-node" export TEST_RUNNER_EXTRA="--nosandbox" -export PIP_PACKAGES="lief" 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 b03a7edb54..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-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_msan.sh b/ci/test/00_setup_env_native_fuzz_with_msan.sh index 619acc4677..071bac8fb3 100755 --- a/ci/test/00_setup_env_native_fuzz_with_msan.sh +++ b/ci/test/00_setup_env_native_fuzz_with_msan.sh @@ -13,10 +13,11 @@ LIBCXX_FLAGS="-nostdinc++ -stdlib=libc++ -L${LIBCXX_DIR}lib -lc++abi -I${LIBCXX_ export MSAN_AND_LIBCXX_FLAGS="${MSAN_FLAGS} ${LIBCXX_FLAGS}" export CONTAINER_NAME="ci_native_msan" -export PACKAGES="clang-9 llvm-9 cmake" +export PACKAGES="clang-12 llvm-12 cmake" +# BDB generates false-positives and will be removed in future export DEP_OPTS="NO_BDB=1 NO_QT=1 CC='clang' CXX='clang++' CFLAGS='${MSAN_FLAGS}' CXXFLAGS='${MSAN_AND_LIBCXX_FLAGS}' libevent_cflags='${MSAN_FLAGS}' sqlite_cflags='${MSAN_FLAGS}' zeromq_cxxflags='-std=c++17 ${MSAN_AND_LIBCXX_FLAGS}'" export GOAL="install" -export BITCOIN_CONFIG="--enable-fuzz --with-sanitizers=fuzzer,memory --with-asm=no --prefix=${DEPENDS_DIR}/x86_64-pc-linux-gnu/ CC=clang CXX=clang++ CFLAGS='${MSAN_FLAGS}' CXXFLAGS='${MSAN_AND_LIBCXX_FLAGS}'" +export BITCOIN_CONFIG="--enable-fuzz --with-sanitizers=fuzzer,memory --disable-hardening --with-asm=no --prefix=${DEPENDS_DIR}/x86_64-pc-linux-gnu/ CC=clang CXX=clang++ CFLAGS='${MSAN_FLAGS}' CXXFLAGS='${MSAN_AND_LIBCXX_FLAGS}'" export USE_MEMORY_SANITIZER="true" export RUN_UNIT_TESTS="false" export RUN_FUNCTIONAL_TESTS="false" 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 a7715a6ded..97c530e19e 100755 --- a/ci/test/00_setup_env_native_fuzz_with_valgrind.sh +++ b/ci/test/00_setup_env_native_fuzz_with_valgrind.sh @@ -15,5 +15,6 @@ export RUN_FUNCTIONAL_TESTS=false export RUN_FUZZ_TESTS=true export FUZZ_TESTS_CONFIG="--valgrind" export GOAL="install" -export BITCOIN_CONFIG="--enable-fuzz --with-sanitizers=fuzzer CC=clang CXX=clang++" +# Temporarily pin dwarf 4, until valgrind can understand clang's dwarf 5 +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_msan.sh b/ci/test/00_setup_env_native_msan.sh index b8418106c6..34a792ec8f 100755 --- a/ci/test/00_setup_env_native_msan.sh +++ b/ci/test/00_setup_env_native_msan.sh @@ -11,13 +11,13 @@ LIBCXX_DIR="${BASE_SCRATCH_DIR}/msan/build/" export MSAN_FLAGS="-fsanitize=memory -fsanitize-memory-track-origins=2 -fno-omit-frame-pointer -g -O1 -fno-optimize-sibling-calls" LIBCXX_FLAGS="-nostdinc++ -stdlib=libc++ -L${LIBCXX_DIR}lib -lc++abi -I${LIBCXX_DIR}include -I${LIBCXX_DIR}include/c++/v1 -lpthread -Wl,-rpath,${LIBCXX_DIR}lib -Wno-unused-command-line-argument" export MSAN_AND_LIBCXX_FLAGS="${MSAN_FLAGS} ${LIBCXX_FLAGS}" -export BDB_PREFIX="${BASE_ROOT_DIR}/db4" export CONTAINER_NAME="ci_native_msan" -export PACKAGES="clang-9 llvm-9 cmake" +export PACKAGES="clang-12 llvm-12 cmake" +# BDB generates false-positives and will be removed in future export DEP_OPTS="NO_BDB=1 NO_QT=1 CC='clang' CXX='clang++' CFLAGS='${MSAN_FLAGS}' CXXFLAGS='${MSAN_AND_LIBCXX_FLAGS}' libevent_cflags='${MSAN_FLAGS}' sqlite_cflags='${MSAN_FLAGS}' zeromq_cxxflags='-std=c++17 ${MSAN_AND_LIBCXX_FLAGS}'" export GOAL="install" -export BITCOIN_CONFIG="--enable-wallet --with-sanitizers=memory --with-asm=no --prefix=${DEPENDS_DIR}/x86_64-pc-linux-gnu/ CC=clang CXX=clang++ CFLAGS='${MSAN_FLAGS}' CXXFLAGS='${MSAN_AND_LIBCXX_FLAGS}' BDB_LIBS='-L${BDB_PREFIX}/lib -ldb_cxx-4.8' BDB_CFLAGS='-I${BDB_PREFIX}/include'" +export BITCOIN_CONFIG="--with-sanitizers=memory --disable-hardening --with-asm=no --prefix=${DEPENDS_DIR}/x86_64-pc-linux-gnu/ CC=clang CXX=clang++ CFLAGS='${MSAN_FLAGS}' CXXFLAGS='${MSAN_AND_LIBCXX_FLAGS}'" export USE_MEMORY_SANITIZER="true" export RUN_FUNCTIONAL_TESTS="false" export CCACHE_SIZE=250M diff --git a/ci/test/00_setup_env_native_nowallet.sh b/ci/test/00_setup_env_native_nowallet.sh deleted file mode 100755 index d80a7f9633..0000000000 --- a/ci/test/00_setup_env_native_nowallet.sh +++ /dev/null @@ -1,14 +0,0 @@ -#!/usr/bin/env bash -# -# 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. - -export LC_ALL=C.UTF-8 - -export CONTAINER_NAME=ci_native_nowallet -export DOCKER_NAME_TAG=ubuntu:18.04 # Use bionic to have one config run the tests in python3.6, see doc/dependencies.md -export PACKAGES="python3-zmq clang-7 llvm-7 libc++abi-7-dev libc++-7-dev" # Use clang-7 to test C++17 compatibility, see doc/dependencies.md -export DEP_OPTS="NO_WALLET=1 CC=clang-7 CXX='clang++-7 -stdlib=libc++'" -export GOAL="install" -export BITCOIN_CONFIG="--enable-reduce-exports CC=clang-7 CXX='clang++-7 -stdlib=libc++'" diff --git a/ci/test/00_setup_env_native_nowallet_libbitcoinkernel.sh b/ci/test/00_setup_env_native_nowallet_libbitcoinkernel.sh new file mode 100755 index 0000000000..63560a5f5c --- /dev/null +++ b/ci/test/00_setup_env_native_nowallet_libbitcoinkernel.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env bash +# +# 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. + +export LC_ALL=C.UTF-8 + +export CONTAINER_NAME=ci_native_nowallet_libbitcoinkernel +export DOCKER_NAME_TAG=ubuntu:18.04 # Use bionic to have one config run the tests in python3.6, see doc/dependencies.md +export PACKAGES="python3-zmq clang-8 llvm-8 libc++abi-8-dev libc++-8-dev" # Use clang-8 to test C++17 compatibility, see doc/dependencies.md +export DEP_OPTS="NO_WALLET=1 CC=clang-8 CXX='clang++-8 -stdlib=libc++'" +export GOAL="install" +export BITCOIN_CONFIG="--enable-reduce-exports CC=clang-8 CXX='clang++-8 -stdlib=libc++' --enable-experimental-util-chainstate --with-experimental-kernel-lib --enable-shared" diff --git a/ci/test/00_setup_env_native_qt5.sh b/ci/test/00_setup_env_native_qt5.sh index f8b366e77f..8a6ea62d5c 100755 --- a/ci/test/00_setup_env_native_qt5.sh +++ b/ci/test/00_setup_env_native_qt5.sh @@ -7,13 +7,13 @@ export LC_ALL=C.UTF-8 export CONTAINER_NAME=ci_native_qt5 -export DOCKER_NAME_TAG=ubuntu:18.04 # Check that bionic gcc-8 can compile our C++17 and run our functional tests in python3, see doc/dependencies.md +export DOCKER_NAME_TAG=debian:buster # Check that buster gcc-8 can compile our C++17 and run our functional tests in python3, see doc/dependencies.md export PACKAGES="gcc-8 g++-8 python3-zmq qtbase5-dev qttools5-dev-tools libdbus-1-dev libharfbuzz-dev" export DEP_OPTS="NO_QT=1 NO_UPNP=1 NO_NATPMP=1 DEBUG=1 ALLOW_HOST_PACKAGES=1 CC=gcc-8 CXX=g++-8" export TEST_RUNNER_EXTRA="--previous-releases --coverage --extended --exclude feature_dbcrash" # Run extended tests so that coverage does not fail, but exclude the very slow dbcrash export RUN_UNIT_TESTS_SEQUENTIAL="true" export RUN_UNIT_TESTS="false" export GOAL="install" -export PREVIOUS_RELEASES_TO_DOWNLOAD="v0.15.2 v0.16.3 v0.17.2 v0.18.1 v0.19.1 v0.20.1 v0.21.0 v22.0" +export DOWNLOAD_PREVIOUS_RELEASES="true" export BITCOIN_CONFIG="--enable-zmq --with-libs=no --with-gui=qt5 --enable-reduce-exports \ ---enable-debug --disable-fuzz-binary CFLAGS=\"-g0 -O2 -funsigned-char\" CXXFLAGS=\"-g0 -O2 -funsigned-char\" CC=gcc-8 CXX=g++-8" +--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 new file mode 100755 index 0000000000..11a12e336a --- /dev/null +++ b/ci/test/00_setup_env_native_tidy.sh @@ -0,0 +1,19 @@ +#!/usr/bin/env bash +# +# 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. + +export LC_ALL=C.UTF-8 + +export DOCKER_NAME_TAG="ubuntu:22.04" +export CONTAINER_NAME=ci_native_tidy +export PACKAGES="clang libclang-dev llvm-dev clang-tidy bear cmake libevent-dev libboost-dev libminiupnpc-dev libnatpmp-dev libzmq3-dev systemtap-sdt-dev libqt5gui5 libqt5core5a libqt5dbus5 qttools5-dev qttools5-dev-tools libqrencode-dev libsqlite3-dev libdb++-dev" +export NO_DEPENDS=1 +export RUN_UNIT_TESTS=false +export RUN_FUNCTIONAL_TESTS=false +export RUN_FUZZ_TESTS=false +export RUN_TIDY=true +export GOAL="install" +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_tsan.sh b/ci/test/00_setup_env_native_tsan.sh index 0036255caf..6bf8391209 100755 --- a/ci/test/00_setup_env_native_tsan.sh +++ b/ci/test/00_setup_env_native_tsan.sh @@ -8,7 +8,7 @@ export LC_ALL=C.UTF-8 export CONTAINER_NAME=ci_native_tsan export DOCKER_NAME_TAG=ubuntu:22.04 -export PACKAGES="clang llvm libc++abi-dev libc++-dev python3-zmq" -export DEP_OPTS="CC=clang CXX='clang++ -stdlib=libc++'" +export PACKAGES="clang-13 llvm-13 libc++abi-13-dev libc++-13-dev python3-zmq" +export DEP_OPTS="CC=clang-13 CXX='clang++-13 -stdlib=libc++'" export GOAL="install" -export BITCOIN_CONFIG="--enable-zmq CPPFLAGS='-DARENA_DEBUG -DDEBUG_LOCKORDER' CXXFLAGS='-g' --with-sanitizers=thread CC=clang CXX='clang++ -stdlib=libc++'" +export BITCOIN_CONFIG="--enable-zmq CPPFLAGS='-DARENA_DEBUG -DDEBUG_LOCKORDER -DDEBUG_LOCKCONTENTION' CXXFLAGS='-g' --with-sanitizers=thread CC=clang-13 CXX='clang++-13 -stdlib=libc++'" diff --git a/ci/test/00_setup_env_native_valgrind.sh b/ci/test/00_setup_env_native_valgrind.sh index 646070a84e..d8c08fca39 100755 --- a/ci/test/00_setup_env_native_valgrind.sh +++ b/ci/test/00_setup_env_native_valgrind.sh @@ -13,4 +13,5 @@ export USE_VALGRIND=1 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" -export BITCOIN_CONFIG="--enable-zmq --with-incompatible-bdb --with-gui=no CC=clang CXX=clang++" # TODO enable GUI +# 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++ CFLAGS='-gdwarf-4' CXXFLAGS='-gdwarf-4'" # TODO enable GUI diff --git a/ci/test/00_setup_env_win64.sh b/ci/test/00_setup_env_win64.sh index 3f43bf227b..3600113551 100755 --- a/ci/test/00_setup_env_win64.sh +++ b/ci/test/00_setup_env_win64.sh @@ -10,7 +10,7 @@ export CONTAINER_NAME=ci_win64 export DOCKER_NAME_TAG=ubuntu:22.04 # Check that Jammy can cross-compile to win64 export HOST=x86_64-w64-mingw32 export DPKG_ADD_ARCH="i386" -export PACKAGES="python3 nsis g++-mingw-w64-x86-64 wine-binfmt wine64 wine32 file" +export PACKAGES="python3 nsis g++-mingw-w64-x86-64-posix wine-binfmt wine64 wine32 file" export RUN_FUNCTIONAL_TESTS=false export GOAL="deploy" export BITCOIN_CONFIG="--enable-reduce-exports --disable-external-signer --disable-gui-tests" diff --git a/ci/test/04_install.sh b/ci/test/04_install.sh index e409df62eb..b706fb0906 100755 --- a/ci/test/04_install.sh +++ b/ci/test/04_install.sh @@ -68,6 +68,14 @@ 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 @@ -103,14 +111,24 @@ fi CI_EXEC mkdir -p "${BASE_SCRATCH_DIR}/sanitizer-output/" if [[ ${USE_MEMORY_SANITIZER} == "true" ]]; then - CI_EXEC "update-alternatives --install /usr/bin/clang++ clang++ \$(which clang++-9) 100" - CI_EXEC "update-alternatives --install /usr/bin/clang clang \$(which clang-9) 100" + CI_EXEC "update-alternatives --install /usr/bin/clang++ clang++ \$(which clang++-12) 100" + CI_EXEC "update-alternatives --install /usr/bin/clang clang \$(which clang-12) 100" CI_EXEC "mkdir -p ${BASE_SCRATCH_DIR}/msan/build/" CI_EXEC "git clone --depth=1 https://github.com/llvm/llvm-project -b llvmorg-12.0.0 ${BASE_SCRATCH_DIR}/msan/llvm-project" - CI_EXEC "cd ${BASE_SCRATCH_DIR}/msan/build/ && cmake -DLLVM_ENABLE_PROJECTS='libcxx;libcxxabi' -DCMAKE_BUILD_TYPE=Release -DLLVM_USE_SANITIZER=Memory -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -DLLVM_TARGETS_TO_BUILD=X86 ../llvm-project/llvm/" + CI_EXEC "cd ${BASE_SCRATCH_DIR}/msan/build/ && cmake -DLLVM_ENABLE_PROJECTS='libcxx;libcxxabi' -DCMAKE_BUILD_TYPE=Release -DLLVM_USE_SANITIZER=MemoryWithOrigins -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -DLLVM_TARGETS_TO_BUILD=X86 ../llvm-project/llvm/" CI_EXEC "cd ${BASE_SCRATCH_DIR}/msan/build/ && make $MAKEJOBS cxx" fi +if [[ "${RUN_TIDY}" == "true" ]]; then + export DIR_IWYU="${BASE_SCRATCH_DIR}/iwyu" + if [ ! -d "${DIR_IWYU}" ]; then + CI_EXEC "mkdir -p ${DIR_IWYU}/build/" + CI_EXEC "git clone --depth=1 https://github.com/include-what-you-use/include-what-you-use -b clang_14 ${DIR_IWYU}/include-what-you-use" + CI_EXEC "cd ${DIR_IWYU}/build && cmake -G 'Unix Makefiles' -DCMAKE_PREFIX_PATH=/usr/lib/llvm-14 ../include-what-you-use" + CI_EXEC "cd ${DIR_IWYU}/build && make install $MAKEJOBS" + fi +fi + if [ -z "$DANGER_RUN_CI_ON_HOST" ]; then echo "Create $BASE_ROOT_DIR" CI_EXEC rsync -a /ro_base/ "$BASE_ROOT_DIR" diff --git a/ci/test/05_before_script.sh b/ci/test/05_before_script.sh index 8f75fbd1fa..ef3dff86ca 100755 --- a/ci/test/05_before_script.sh +++ b/ci/test/05_before_script.sh @@ -31,16 +31,9 @@ if [ -n "$ANDROID_HOME" ] && [ ! -d "$ANDROID_HOME" ]; then if [ ! -f "$ANDROID_TOOLS_PATH" ]; then CI_EXEC curl --location --fail "${ANDROID_TOOLS_URL}" -o "$ANDROID_TOOLS_PATH" fi - CI_EXEC mkdir -p "${ANDROID_HOME}/cmdline-tools" - CI_EXEC unzip -o "$ANDROID_TOOLS_PATH" -d "${ANDROID_HOME}/cmdline-tools" - CI_EXEC "yes | ${ANDROID_HOME}/cmdline-tools/tools/bin/sdkmanager --install \"build-tools;${ANDROID_BUILD_TOOLS_VERSION}\" \"platform-tools\" \"platforms;android-${ANDROID_API_LEVEL}\" \"ndk;${ANDROID_NDK_VERSION}\"" -fi - -if [[ ${USE_MEMORY_SANITIZER} == "true" ]]; then - # Use BDB compiled using install_db4.sh script to work around linking issue when using BDB - # from depends. See https://github.com/bitcoin/bitcoin/pull/18288#discussion_r433189350 for - # details. - CI_EXEC "contrib/install_db4.sh \$(pwd) --enable-umrw CC=clang CXX=clang++ CFLAGS='${MSAN_FLAGS}' CXXFLAGS='${MSAN_AND_LIBCXX_FLAGS}'" + CI_EXEC mkdir -p "$ANDROID_HOME" + CI_EXEC unzip -o "$ANDROID_TOOLS_PATH" -d "$ANDROID_HOME" + CI_EXEC "yes | ${ANDROID_HOME}/cmdline-tools/bin/sdkmanager --sdk_root=\"${ANDROID_HOME}\" --install \"build-tools;${ANDROID_BUILD_TOOLS_VERSION}\" \"platform-tools\" \"platforms;android-${ANDROID_API_LEVEL}\" \"ndk;${ANDROID_NDK_VERSION}\"" fi if [ -z "$NO_DEPENDS" ]; then @@ -52,8 +45,8 @@ if [ -z "$NO_DEPENDS" ]; then else SHELL_OPTS="CONFIG_SHELL=" fi - CI_EXEC "$SHELL_OPTS" make "$MAKEJOBS" -C depends HOST="$HOST" "$DEP_OPTS" + 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 7158b69b4e..13693a2ecf 100755 --- a/ci/test/06_script_a.sh +++ b/ci/test/06_script_a.sh @@ -6,19 +6,24 @@ export LC_ALL=C.UTF-8 +BITCOIN_CONFIG_ALL="--enable-suppress-external-warnings --disable-dependency-tracking --prefix=$DEPENDS_DIR/$HOST" +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" --prefix="${DEPENDS_DIR}/aarch64-linux-android" || ( (CI_EXEC cat config.log) && false) + 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="--enable-external-signer --enable-suppress-external-warnings --disable-dependency-tracking --prefix=$DEPENDS_DIR/$HOST --bindir=$BASE_OUTDIR/bin --libdir=$BASE_OUTDIR/lib" -if [ -z "$NO_WERROR" ]; then - BITCOIN_CONFIG_ALL="${BITCOIN_CONFIG_ALL} --enable-werror" -fi -CI_EXEC "ccache --zero-stats --max-size=$CCACHE_SIZE" +BITCOIN_CONFIG_ALL="${BITCOIN_CONFIG_ALL} --enable-external-signer --bindir=$BASE_OUTDIR/bin --libdir=$BASE_OUTDIR/lib" if [ -n "$CONFIG_SHELL" ]; then CI_EXEC "$CONFIG_SHELL" -c "./autogen.sh" @@ -48,8 +53,13 @@ if [[ ${USE_MEMORY_SANITIZER} == "true" ]]; then CI_EXEC 'grep -v HAVE_SYS_GETRANDOM src/config/bitcoin-config.h > src/config/bitcoin-config.h.tmp && mv src/config/bitcoin-config.h.tmp src/config/bitcoin-config.h' fi -CI_EXEC make "$MAKEJOBS" "$GOAL" || ( echo "Build failure. Verbose build follows." && CI_EXEC make "$GOAL" V=1 ; false ) +if [[ "${RUN_TIDY}" == "true" ]]; then + MAYBE_BEAR="bear --config src/.bear-tidy-config" + MAYBE_TOKEN="--" +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 de42aa6eb1..f1b0c1ac15 100755 --- a/ci/test/06_script_b.sh +++ b/ci/test/06_script_b.sh @@ -27,13 +27,49 @@ if [ "$RUN_UNIT_TESTS" = "true" ]; then fi if [ "$RUN_UNIT_TESTS_SEQUENTIAL" = "true" ]; then - CI_EXEC "${TEST_RUNNER_ENV}" DIR_UNIT_TEST_DATA="${DIR_UNIT_TEST_DATA}" LD_LIBRARY_PATH="${DEPENDS_DIR}/${HOST}/lib" "${BASE_BUILD_DIR}/bitcoin-*/src/test/test_bitcoin*" --catch_system_errors=no -l test_suite + CI_EXEC "${TEST_RUNNER_ENV}" DIR_UNIT_TEST_DATA="${DIR_UNIT_TEST_DATA}" LD_LIBRARY_PATH="${DEPENDS_DIR}/${HOST}/lib" "${BASE_OUTDIR}/bin/test_bitcoin" --catch_system_errors=no -l test_suite fi if [ "$RUN_FUNCTIONAL_TESTS" = "true" ]; then CI_EXEC LD_LIBRARY_PATH="${DEPENDS_DIR}/${HOST}/lib" "${TEST_RUNNER_ENV}" test/functional/test_runner.py --ci "$MAKEJOBS" --tmpdirprefix "${BASE_SCRATCH_DIR}/test_runner/" --ansi --combinedlogslen=4000 --timeout-factor="${TEST_RUNNER_TIMEOUT_FACTOR}" "${TEST_RUNNER_EXTRA}" --quiet --failfast fi +if [ "${RUN_TIDY}" = "true" ]; then + set -eo pipefail + export P_CI_DIR="${BASE_BUILD_DIR}/bitcoin-$HOST/src/" + ( 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/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 + if [ "$RUN_SECURITY_TESTS" = "true" ]; then CI_EXEC make test-security-check 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/ci/test/wrapped-cl.bat b/ci/test/wrapped-cl.bat new file mode 100644 index 0000000000..fc2a604c58 --- /dev/null +++ b/ci/test/wrapped-cl.bat @@ -0,0 +1 @@ +ccache cl %* diff --git a/configure.ac b/configure.ac index 1e6ac93e78..1752fda344 100644 --- a/configure.ac +++ b/configure.ac @@ -1,5 +1,5 @@ AC_PREREQ([2.69]) -define(_CLIENT_VERSION_MAJOR, 22) +define(_CLIENT_VERSION_MAJOR, 23) define(_CLIENT_VERSION_MINOR, 99) define(_CLIENT_VERSION_BUILD, 0) define(_CLIENT_VERSION_RC, 0) @@ -19,17 +19,30 @@ 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 +BITCOIN_CHAINSTATE_NAME=bitcoin-chainstate BITCOIN_WALLET_TOOL_NAME=bitcoin-wallet dnl Multi Process 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" @@ -41,14 +54,9 @@ AH_TOP([#ifndef BITCOIN_CONFIG_H]) AH_TOP([#define BITCOIN_CONFIG_H]) AH_BOTTOM([#endif //BITCOIN_CONFIG_H]) -dnl faketime breaks configure and is only needed for make. Disable it here. -unset FAKETIME - dnl Automake init set-up and checks AM_INIT_AUTOMAKE([1.13 no-define subdir-objects foreign]) -dnl faketime messes with timestamps and causes configure to be re-run. -dnl --disable-maintainer-mode can be used to bypass this. AM_MAINTAINER_MODE([enable]) dnl make the compilation flags quiet unless V=1 is used @@ -77,11 +85,18 @@ AC_ARG_WITH([seccomp], [seccomp_found=$withval], [seccomp_found=auto]) +AC_ARG_ENABLE([c++20], + [AS_HELP_STRING([--enable-c++20], + [enable compilation in c++20 mode (disabled by default)])], + [use_cxx20=$enableval], + [use_cxx20=no]) + dnl Require C++17 compiler (no GNU extensions) +if test "$use_cxx20" = "no"; then AX_CXX_COMPILE_STDCXX([17], [noext], [mandatory]) - -dnl Check if -latomic is required for <std::atomic> -CHECK_ATOMIC +else +AX_CXX_COMPILE_STDCXX([20], [noext], [mandatory]) +fi dnl check if additional link flags are required for std::filesystem CHECK_FILESYSTEM @@ -95,17 +110,13 @@ fi AC_PROG_OBJCXX ]) -dnl Since libtool 1.5.2 (released 2004-01-25), on Linux libtool no longer -dnl sets RPATH for any directories in the dynamic linker search path. -dnl See more: https://wiki.debian.org/RpathIssue -LT_PREREQ([1.5.2]) +dnl OpenBSD ships with 2.4.2 +LT_PREREQ([2.4.2]) dnl Libtool init checks. LT_INIT([pic-only win32-dll]) dnl Check/return PATH for base programs. AC_PATH_TOOL([AR], [ar]) -AC_PATH_TOOL([RANLIB], [ranlib]) -AC_PATH_TOOL([STRIP], [strip]) AC_PATH_TOOL([GCOV], [gcov]) AC_PATH_TOOL([LLVM_COV], [llvm-cov]) AC_PATH_PROG([LCOV], [lcov]) @@ -322,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]) @@ -357,7 +368,9 @@ case $host in esac if test "$enable_debug" = "yes"; then - dnl Clear default -g -O2 flags + dnl If debugging is enabled, and the user hasn't overridden CXXFLAGS, clear + dnl them, to prevent autoconfs "-g -O2" being added. Otherwise we'd end up + dnl with "-O0 -g3 -g -O2". if test "$CXXFLAGS_overridden" = "no"; then CXXFLAGS="" fi @@ -374,6 +387,8 @@ if test "$enable_debug" = "yes"; then AX_CHECK_PREPROC_FLAG([-DDEBUG], [DEBUG_CPPFLAGS="$DEBUG_CPPFLAGS -DDEBUG"], [], [$CXXFLAG_WERROR]) AX_CHECK_PREPROC_FLAG([-DDEBUG_LOCKORDER], [DEBUG_CPPFLAGS="$DEBUG_CPPFLAGS -DDEBUG_LOCKORDER"], [], [$CXXFLAG_WERROR]) + AX_CHECK_PREPROC_FLAG([-DDEBUG_LOCKCONTENTION], [DEBUG_CPPFLAGS="$DEBUG_CPPFLAGS -DDEBUG_LOCKCONTENTION"], [], [$CXXFLAG_WERROR]) + AX_CHECK_PREPROC_FLAG([-DRPC_DOC_CHECK], [DEBUG_CPPFLAGS="$DEBUG_CPPFLAGS -DRPC_DOC_CHECK"], [], [$CXXFLAG_WERROR]) AX_CHECK_PREPROC_FLAG([-DABORT_ON_FAILED_ASSUME], [DEBUG_CPPFLAGS="$DEBUG_CPPFLAGS -DABORT_ON_FAILED_ASSUME"], [], [$CXXFLAG_WERROR]) AX_CHECK_COMPILE_FLAG([-ftrapv], [DEBUG_CXXFLAGS="$DEBUG_CXXFLAGS -ftrapv"], [], [$CXXFLAG_WERROR]) fi @@ -464,8 +479,10 @@ if test "$CXXFLAGS_overridden" = "no"; then fi dnl Don't allow extended (non-ASCII) symbols in identifiers. This is easier for code review. -AX_CHECK_COMPILE_FLAG([-fno-extended-identifiers], [CXXFLAGS="$CXXFLAGS -fno-extended-identifiers"], [], [$CXXFLAG_WERROR]) +AX_CHECK_COMPILE_FLAG([-fno-extended-identifiers], [CORE_CXXFLAGS="$CORE_CXXFLAGS -fno-extended-identifiers"], [], [$CXXFLAG_WERROR]) +enable_arm_crc=no +enable_arm_shani=no enable_sse42=no enable_sse41=no enable_avx2=no @@ -502,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> @@ -524,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> @@ -539,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> @@ -554,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> @@ -575,7 +592,7 @@ AX_CHECK_COMPILE_FLAG([-march=armv8-a+crc+crypto], [ARM_CRC_CXXFLAGS="-march=arm AX_CHECK_COMPILE_FLAG([-march=armv8-a+crc+crypto], [ARM_SHANI_CXXFLAGS="-march=armv8-a+crc+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> @@ -594,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> @@ -613,7 +630,7 @@ CXXFLAGS="$TEMP_CXXFLAGS" fi -CPPFLAGS="$CPPFLAGS -DHAVE_BUILD_INFO" +CORE_CPPFLAGS="$CORE_CPPFLAGS -DHAVE_BUILD_INFO" AC_ARG_WITH([utils], [AS_HELP_STRING([--with-utils], @@ -645,12 +662,24 @@ AC_ARG_ENABLE([util-util], [build_bitcoin_util=$enableval], [build_bitcoin_util=$build_bitcoin_utils]) +AC_ARG_ENABLE([experimental-util-chainstate], + [AS_HELP_STRING([--enable-experimental-util-chainstate], + [build experimental bitcoin-chainstate executable (default=no)])], + [build_bitcoin_chainstate=$enableval], + [build_bitcoin_chainstate=no]) + AC_ARG_WITH([libs], [AS_HELP_STRING([--with-libs], [build libraries (default=yes)])], [build_bitcoin_libs=$withval], [build_bitcoin_libs=yes]) +AC_ARG_WITH([experimental-kernel-lib], + [AS_HELP_STRING([--with-experimental-kernel-lib], + [build experimental bitcoinkernel library (default is to build if we're building libraries and the experimental build-chainstate executable)])], + [build_experimental_kernel_lib=$withval], + [build_experimental_kernel_lib=auto]) + AC_ARG_WITH([daemon], [AS_HELP_STRING([--with-daemon], [build bitcoind daemon (default=yes)])], @@ -689,7 +718,10 @@ case $host in AC_MSG_ERROR([windres not found]) fi - CPPFLAGS="$CPPFLAGS -D_MT -DWIN32 -D_WINDOWS -D_WIN32_WINNT=0x0601 -D_WIN32_IE=0x0501 -DWIN32_LEAN_AND_MEAN" + 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 @@ -700,7 +732,7 @@ case $host in postdeps_CXX= dnl We require Windows 7 (NT 6.1) or later - AX_CHECK_LINK_FLAG([-Wl,--major-subsystem-version -Wl,6 -Wl,--minor-subsystem-version -Wl,1], [LDFLAGS="$LDFLAGS -Wl,--major-subsystem-version -Wl,6 -Wl,--minor-subsystem-version -Wl,1"], [], [$LDFLAG_WERROR]) + AX_CHECK_LINK_FLAG([-Wl,--major-subsystem-version -Wl,6 -Wl,--minor-subsystem-version -Wl,1], [CORE_LDFLAGS="$CORE_LDFLAGS -Wl,--major-subsystem-version -Wl,6 -Wl,--minor-subsystem-version -Wl,1"], [], [$LDFLAG_WERROR]) ;; *darwin*) TARGET_OS=darwin @@ -738,20 +770,20 @@ case $host in if test "$use_upnp" != "no" && $BREW list --versions miniupnpc >/dev/null; then miniupnpc_prefix=$($BREW --prefix miniupnpc 2>/dev/null) if test "$suppress_external_warnings" != "no"; then - CPPFLAGS="$CPPFLAGS -isystem $miniupnpc_prefix/include" + MINIUPNPC_CPPFLAGS="-isystem $miniupnpc_prefix/include" else - CPPFLAGS="$CPPFLAGS -I$miniupnpc_prefix/include" + MINIUPNPC_CPPFLAGS="-I$miniupnpc_prefix/include" fi - LDFLAGS="$LDFLAGS -L$miniupnpc_prefix/lib" + MINIUPNPC_LIBS="-L$miniupnpc_prefix/lib" fi if test "$use_natpmp" != "no" && $BREW list --versions libnatpmp >/dev/null; then libnatpmp_prefix=$($BREW --prefix libnatpmp 2>/dev/null) if test "$suppress_external_warnings" != "no"; then - CPPFLAGS="$CPPFLAGS -isystem $libnatpmp_prefix/include" + NATPMP_CPPFLAGS="-isystem $libnatpmp_prefix/include" else - CPPFLAGS="$CPPFLAGS -I$libnatpmp_prefix/include" + NATPMP_CPPFLAGS="-I$libnatpmp_prefix/include" fi - LDFLAGS="$LDFLAGS -L$libnatpmp_prefix/lib" + NATPMP_LIBS="-L$libnatpmp_prefix/lib" fi ;; esac @@ -763,10 +795,9 @@ case $host in ;; *) AC_PATH_TOOL([DSYMUTIL], [dsymutil], [dsymutil]) - AC_PATH_TOOL([INSTALLNAMETOOL], [install_name_tool], [install_name_tool]) + AC_PATH_TOOL([INSTALL_NAME_TOOL], [install_name_tool], [install_name_tool]) AC_PATH_TOOL([OTOOL], [otool], [otool]) AC_PATH_PROGS([XORRISOFS], [xorrisofs], [xorrisofs]) - AC_PATH_PROGS([DMG], [dmg], [dmg]) dnl libtool will try to strip the static lib, which is a problem for dnl cross-builds because strip attempts to call a hard-coded ld, @@ -777,8 +808,8 @@ case $host in esac fi - AX_CHECK_LINK_FLAG([-Wl,-headerpad_max_install_names], [LDFLAGS="$LDFLAGS -Wl,-headerpad_max_install_names"], [], [$LDFLAG_WERROR]) - CPPFLAGS="$CPPFLAGS -DMAC_OSX -DOBJC_OLD_DISPATCH_PROTOTYPES=0" + AX_CHECK_LINK_FLAG([-Wl,-headerpad_max_install_names], [CORE_LDFLAGS="$CORE_LDFLAGS -Wl,-headerpad_max_install_names"], [], [$LDFLAG_WERROR]) + CORE_CPPFLAGS="$CORE_CPPFLAGS -DMAC_OSX -DOBJC_OLD_DISPATCH_PROTOTYPES=0" OBJCXXFLAGS="$CXXFLAGS" ;; *android*) @@ -841,11 +872,17 @@ if test "$use_lcov" = "yes"; then AC_SUBST(COV_TOOL_WRAPPER, "cov_tool_wrapper.sh") LCOV="$LCOV --gcov-tool $(pwd)/$COV_TOOL_WRAPPER" - AX_CHECK_LINK_FLAG([--coverage], [LDFLAGS="$LDFLAGS --coverage"], + AX_CHECK_LINK_FLAG([--coverage], [CORE_LDFLAGS="$CORE_LDFLAGS --coverage"], [AC_MSG_ERROR([lcov testing requested but --coverage linker flag does not work])]) - AX_CHECK_COMPILE_FLAG([--coverage],[CXXFLAGS="$CXXFLAGS --coverage"], + AX_CHECK_COMPILE_FLAG([--coverage],[CORE_CXXFLAGS="$CORE_CXXFLAGS --coverage"], [AC_MSG_ERROR([lcov testing requested but --coverage flag does not work])]) - CXXFLAGS="$CXXFLAGS -Og" + dnl If coverage is enabled, and the user hasn't overridden CXXFLAGS, clear + dnl them, to prevent autoconfs "-g -O2" being added. Otherwise we'd end up + dnl with "--coverage -Og -O0 -g -O2". + if test "$CXXFLAGS_overridden" = "no"; then + CXXFLAGS="" + fi + CORE_CXXFLAGS="$CORE_CXXFLAGS -Og -O0" fi if test "$use_lcov_branch" != "no"; then @@ -858,6 +895,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. @@ -868,13 +908,13 @@ AC_FUNC_STRERROR_R if test "$ac_cv_sys_file_offset_bits" != "" && test "$ac_cv_sys_file_offset_bits" != "no" && test "$ac_cv_sys_file_offset_bits" != "unknown"; then - CPPFLAGS="$CPPFLAGS -D_FILE_OFFSET_BITS=$ac_cv_sys_file_offset_bits" + CORE_CPPFLAGS="$CORE_CPPFLAGS -D_FILE_OFFSET_BITS=$ac_cv_sys_file_offset_bits" fi if test "$ac_cv_sys_large_files" != "" && test "$ac_cv_sys_large_files" != "no" && test "$ac_cv_sys_large_files" != "unknown"; then - CPPFLAGS="$CPPFLAGS -D_LARGE_FILES=$ac_cv_sys_large_files" + CORE_CPPFLAGS="$CORE_CPPFLAGS -D_LARGE_FILES=$ac_cv_sys_large_files" fi AC_SEARCH_LIBS([clock_gettime],[rt]) @@ -910,10 +950,7 @@ if test "$use_hardening" != "no"; then AX_CHECK_COMPILE_FLAG([-Wstack-protector], [HARDENED_CXXFLAGS="$HARDENED_CXXFLAGS -Wstack-protector"]) AX_CHECK_COMPILE_FLAG([-fstack-protector-all], [HARDENED_CXXFLAGS="$HARDENED_CXXFLAGS -fstack-protector-all"]) - dnl -fcf-protection used with Clang 7 causes ld to emit warnings: - dnl ld: error: ... <corrupt x86 feature size: 0x8> - dnl Use CHECK_LINK_FLAG & --fatal-warnings to ensure we won't use the flag in this case. - AX_CHECK_LINK_FLAG([-fcf-protection=full], [HARDENED_CXXFLAGS="$HARDENED_CXXFLAGS -fcf-protection=full"], [], [$LDFLAG_WERROR]) + AX_CHECK_COMPILE_FLAG([-fcf-protection=full], [HARDENED_CXXFLAGS="$HARDENED_CXXFLAGS -fcf-protection=full"]) case $host in *mingw*) @@ -958,8 +995,8 @@ dnl These flags are specific to ld64, and may cause issues with other linkers. dnl For example: GNU ld will interpret -dead_strip as -de and then try and use dnl "ad_strip" as the symbol for the entry point. if test "$TARGET_OS" = "darwin"; then - AX_CHECK_LINK_FLAG([-Wl,-dead_strip], [LDFLAGS="$LDFLAGS -Wl,-dead_strip"], [], [$LDFLAG_WERROR]) - AX_CHECK_LINK_FLAG([-Wl,-dead_strip_dylibs], [LDFLAGS="$LDFLAGS -Wl,-dead_strip_dylibs"], [], [$LDFLAG_WERROR]) + AX_CHECK_LINK_FLAG([-Wl,-dead_strip], [CORE_LDFLAGS="$CORE_LDFLAGS -Wl,-dead_strip"], [], [$LDFLAG_WERROR]) + AX_CHECK_LINK_FLAG([-Wl,-dead_strip_dylibs], [CORE_LDFLAGS="$CORE_LDFLAGS -Wl,-dead_strip_dylibs"], [], [$LDFLAG_WERROR]) AX_CHECK_LINK_FLAG([-Wl,-bind_at_load], [HARDENED_LDFLAGS="$HARDENED_LDFLAGS -Wl,-bind_at_load"], [], [$LDFLAG_WERROR]) fi @@ -969,7 +1006,6 @@ AC_CHECK_DECLS([getifaddrs, freeifaddrs],[CHECK_SOCKET],, [#include <sys/types.h> #include <ifaddrs.h>] ) -AC_CHECK_DECLS([strnlen]) dnl These are used for daemonization in bitcoind AC_CHECK_DECLS([fork]) @@ -977,6 +1013,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> @@ -1238,8 +1276,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]) ] @@ -1259,7 +1297,7 @@ dnl Do not change "-I/usr/include" to "-isystem /usr/include" because that dnl is not necessary (/usr/include is already a system directory) and because dnl it would break GCC's #include_next. AC_DEFUN([SUPPRESS_WARNINGS], - [$(echo $1 |${SED} -E -e 's/(^| )-I/\1-isystem /g' -e 's;-isystem /usr/include([/ ]|$);-I/usr/include\1;g')]) + [[$(echo $1 |${SED} -E -e 's/(^| )-I/\1-isystem /g' -e 's;-isystem /usr/include/*( |$);-I/usr/include\1;g')]]) dnl enable-fuzz should disable all other targets if test "$enable_fuzz" = "yes"; then @@ -1268,6 +1306,7 @@ if test "$enable_fuzz" = "yes"; then build_bitcoin_cli=no build_bitcoin_tx=no build_bitcoin_util=no + build_bitcoin_chainstate=no build_bitcoin_wallet=no build_bitcoind=no build_bitcoin_libs=no @@ -1275,6 +1314,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 @@ -1282,26 +1322,11 @@ if test "$enable_fuzz" = "yes"; then enable_fuzz_binary=yes AX_CHECK_PREPROC_FLAG([-DABORT_ON_FAILED_ASSUME], [DEBUG_CPPFLAGS="$DEBUG_CPPFLAGS -DABORT_ON_FAILED_ASSUME"], [], [$CXXFLAG_WERROR]) - - AC_MSG_CHECKING([whether main function is needed for fuzz binary]) - AX_CHECK_LINK_FLAG( - [-fsanitize=$use_sanitizers], - [AC_MSG_RESULT([no])], - [AC_MSG_RESULT([yes]); CPPFLAGS="$CPPFLAGS -DPROVIDE_FUZZ_MAIN_FUNCTION"], - [], - [AC_LANG_PROGRAM([[ - #include <cstdint> - #include <cstddef> - extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { return 0; } - /* comment to remove the main function ... - ]],[[ - */ int not_main() { - ]])]) else BITCOIN_QT_INIT dnl sets $bitcoin_enable_qt, $bitcoin_enable_qt_test, $bitcoin_enable_qt_dbus - BITCOIN_QT_CONFIGURE([5.9.5]) + BITCOIN_QT_CONFIGURE([5.11.3]) dnl Keep a copy of the original $QT_INCLUDES and use it when invoking qt's moc QT_INCLUDES_UNSUPPRESSED=$QT_INCLUDES @@ -1310,8 +1335,25 @@ else QT_DBUS_INCLUDES=SUPPRESS_WARNINGS($QT_DBUS_INCLUDES) QT_TEST_INCLUDES=SUPPRESS_WARNINGS($QT_TEST_INCLUDES) fi +fi + +if test "$enable_fuzz_binary" = "yes"; then + AC_MSG_CHECKING([whether main function is needed for fuzz binary]) + AX_CHECK_LINK_FLAG( + [], + [AC_MSG_RESULT([no])], + [AC_MSG_RESULT([yes]); CORE_CPPFLAGS="$CORE_CPPFLAGS -DPROVIDE_FUZZ_MAIN_FUNCTION"], + [$SANITIZER_LDFLAGS], + [AC_LANG_PROGRAM([[ + #include <cstdint> + #include <cstddef> + extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { return 0; } + /* comment to remove the main function ... + ]],[[ + */ int not_main() { + ]])]) - CPPFLAGS="$CPPFLAGS -DPROVIDE_FUZZ_MAIN_FUNCTION" + CHECK_RUNTIME_LIB fi if test "$enable_wallet" != "no"; then @@ -1363,6 +1405,7 @@ if test "$use_usdt" != "no"; then [AC_MSG_RESULT([no]); use_usdt=no;] ) fi +AM_CONDITIONAL([ENABLE_USDT_TRACEPOINTS], [test "$use_usdt" = "yes"]) if test "$build_bitcoin_cli$build_bitcoin_tx$build_bitcoin_util$build_bitcoind$bitcoin_enable_qt$use_bench$use_tests" = "nonononononono"; then use_upnp=no @@ -1372,38 +1415,44 @@ fi dnl Check for libminiupnpc (optional) if test "$use_upnp" != "no"; then + TEMP_CPPFLAGS="$CPPFLAGS" + CPPFLAGS="$CPPFLAGS $MINIUPNPC_CPPFLAGS" AC_CHECK_HEADERS( [miniupnpc/miniupnpc.h miniupnpc/upnpcommands.h miniupnpc/upnperrors.h], - [AC_CHECK_LIB([miniupnpc], [upnpDiscover], [MINIUPNPC_LIBS=-lminiupnpc], [have_miniupnpc=no])], + [AC_CHECK_LIB([miniupnpc], [upnpDiscover], [MINIUPNPC_LIBS="$MINIUPNPC_LIBS -lminiupnpc"], [have_miniupnpc=no], [$MINIUPNPC_LIBS])], [have_miniupnpc=no] ) -dnl The minimum supported miniUPnPc API version is set to 10. This keeps compatibility -dnl with Ubuntu 16.04 LTS and Debian 8 libminiupnpc-dev packages. -if test "$have_miniupnpc" != "no"; then - AC_MSG_CHECKING([whether miniUPnPc API version is supported]) - AC_PREPROC_IFELSE([AC_LANG_PROGRAM([[ - @%:@include <miniupnpc/miniupnpc.h> - ]], [[ - #if MINIUPNPC_API_VERSION >= 10 - // Everything is okay - #else - # error miniUPnPc API version is too old - #endif - ]])],[ - AC_MSG_RESULT([yes]) - ],[ - AC_MSG_RESULT([no]) - AC_MSG_WARN([miniUPnPc API version < 10 is unsupported, disabling UPnP support.]) - have_miniupnpc=no - ]) -fi + dnl The minimum supported miniUPnPc API version is set to 10. This keeps compatibility + dnl with Ubuntu 16.04 LTS and Debian 8 libminiupnpc-dev packages. + if test "$have_miniupnpc" != "no"; then + AC_MSG_CHECKING([whether miniUPnPc API version is supported]) + AC_PREPROC_IFELSE([AC_LANG_PROGRAM([[ + @%:@include <miniupnpc/miniupnpc.h> + ]], [[ + #if MINIUPNPC_API_VERSION >= 10 + // Everything is okay + #else + # error miniUPnPc API version is too old + #endif + ]])],[ + AC_MSG_RESULT([yes]) + ],[ + AC_MSG_RESULT([no]) + AC_MSG_WARN([miniUPnPc API version < 10 is unsupported, disabling UPnP support.]) + have_miniupnpc=no + ]) + fi + CPPFLAGS="$TEMP_CPPFLAGS" fi dnl Check for libnatpmp (optional). if test "$use_natpmp" != "no"; then + TEMP_CPPFLAGS="$CPPFLAGS" + CPPFLAGS="$CPPFLAGS $NATPMP_CPPFLAGS" AC_CHECK_HEADERS([natpmp.h], - [AC_CHECK_LIB([natpmp], [initnatpmp], [NATPMP_LIBS=-lnatpmp], [have_natpmp=no])], + [AC_CHECK_LIB([natpmp], [initnatpmp], [NATPMP_LIBS="$NATPMP_LIBS -lnatpmp"], [have_natpmp=no], [$NATPMP_LIBS])], [have_natpmp=no]) + CPPFLAGS="$TEMP_CPPFLAGS" fi if test "$build_bitcoin_wallet$build_bitcoin_cli$build_bitcoin_tx$build_bitcoind$bitcoin_enable_qt$use_tests$use_bench" = "nonononononono"; then @@ -1420,6 +1469,18 @@ if test "$use_boost" = "yes"; then AC_MSG_ERROR([only libbitcoinconsensus can be built without Boost]) fi + 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 + if test "$suppress_external_warnings" != "no"; then BOOST_CPPFLAGS=SUPPRESS_WARNINGS($BOOST_CPPFLAGS) fi @@ -1438,6 +1499,12 @@ if test "$use_external_signer" != "no"; then ;; *) AC_MSG_CHECKING([whether Boost.Process can be used]) + TEMP_CXXFLAGS="$CXXFLAGS" + dnl Boost 1.78 requires the following workaround. + dnl See: https://github.com/boostorg/process/issues/235 + CXXFLAGS="$CXXFLAGS -Wno-error=narrowing" + TEMP_CPPFLAGS="$CPPFLAGS" + CPPFLAGS="$CPPFLAGS $BOOST_CPPFLAGS" TEMP_LDFLAGS="$LDFLAGS" dnl Boost 1.73 and older require the following workaround. LDFLAGS="$LDFLAGS $PTHREAD_CFLAGS" @@ -1445,6 +1512,8 @@ if test "$use_external_signer" != "no"; then [have_boost_process="yes"], [have_boost_process="no"]) LDFLAGS="$TEMP_LDFLAGS" + CPPFLAGS="$TEMP_CPPFLAGS" + CXXFLAGS="$TEMP_CXXFLAGS" AC_MSG_RESULT([$have_boost_process]) if test "$have_boost_process" = "yes"; then use_external_signer="yes" @@ -1492,7 +1561,7 @@ AM_CONDITIONAL([ENABLE_SYSCALL_SANDBOX], [test "$use_syscall_sandbox" != "no"]) dnl Check for reduced exports if test "$use_reduce_exports" = "yes"; then - AX_CHECK_COMPILE_FLAG([-fvisibility=hidden], [CXXFLAGS="$CXXFLAGS -fvisibility=hidden"], + AX_CHECK_COMPILE_FLAG([-fvisibility=hidden], [CORE_CXXFLAGS="$CORE_CXXFLAGS -fvisibility=hidden"], [AC_MSG_ERROR([Cannot set hidden symbol visibility. Use --disable-reduce-exports.])], [$CXXFLAG_WERROR]) AX_CHECK_LINK_FLAG([-Wl,--exclude-libs,ALL], [RELDFLAGS="-Wl,--exclude-libs,ALL"], [], [$LDFLAG_WERROR]) fi @@ -1506,10 +1575,11 @@ fi dnl libevent check -if test "$build_bitcoin_cli$build_bitcoind$bitcoin_enable_qt$use_tests$use_bench" != "nonononono"; then - PKG_CHECK_MODULES([EVENT], [libevent >= 2.0.21], [use_libevent=yes], [AC_MSG_ERROR([libevent version 2.0.21 or greater not found.])]) +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.0.21],, [AC_MSG_ERROR([libevent_pthreads version 2.0.21 or greater not found.])]) + PKG_CHECK_MODULES([EVENT_PTHREADS], [libevent_pthreads >= 2.1.8], [], [AC_MSG_ERROR([libevent_pthreads version 2.1.8 or greater not found.])]) fi if test "$suppress_external_warnings" != "no"; then @@ -1624,12 +1694,24 @@ AC_MSG_CHECKING([whether to build bitcoin-util]) 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"]) +fi +AC_MSG_RESULT($build_bitcoin_chainstate) + AC_MSG_CHECKING([whether to build libraries]) AM_CONDITIONAL([BUILD_BITCOIN_LIBS], [test $build_bitcoin_libs = "yes"]) + if test "$build_bitcoin_libs" = "yes"; then AC_DEFINE([HAVE_CONSENSUS_LIB], [1], [Define this symbol if the consensus lib has been built]) AC_CONFIG_FILES([libbitcoinconsensus.pc:libbitcoinconsensus.pc.in]) fi + +AM_CONDITIONAL([BUILD_BITCOIN_KERNEL_LIB], [test "$build_experimental_kernel_lib" != "no" && ( test "$build_experimental_kernel_lib" = "yes" || test "$build_bitcoin_chainstate" = "yes" )]) + AC_MSG_RESULT($build_bitcoin_libs) AC_LANG_POP @@ -1686,7 +1768,7 @@ else AC_MSG_RESULT([$use_upnp_default]) AC_DEFINE_UNQUOTED([USE_UPNP],[$upnp_setting],[UPnP support not compiled if undefined, otherwise value (0 or 1) determines default state]) if test "$TARGET_OS" = "windows"; then - MINIUPNPC_CPPFLAGS="-DSTATICLIB -DMINIUPNP_STATICLIB" + MINIUPNPC_CPPFLAGS="$MINIUPNPC_CPPFLAGS -DSTATICLIB -DMINIUPNP_STATICLIB" fi else AC_MSG_RESULT([no]) @@ -1714,7 +1796,7 @@ else AC_MSG_RESULT($use_natpmp_default) AC_DEFINE_UNQUOTED([USE_NATPMP], [$natpmp_setting], [NAT-PMP support not compiled if undefined, otherwise value (0 or 1) determines default state]) if test "$TARGET_OS" = "windows"; then - NATPMP_CPPFLAGS="-DSTATICLIB -DNATPMP_STATICLIB" + NATPMP_CPPFLAGS="$NATPMP_CPPFLAGS -DSTATICLIB -DNATPMP_STATICLIB" fi else AC_MSG_RESULT([no]) @@ -1781,12 +1863,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]) -fi - -if test "$enable_fuzz_binary" = "yes"; then - CHECK_RUNTIME_LIB +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"]) @@ -1840,14 +1918,19 @@ 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) +AC_SUBST(BITCOIN_CHAINSTATE_NAME) AC_SUBST(BITCOIN_WALLET_TOOL_NAME) AC_SUBST(BITCOIN_MP_NODE_NAME) AC_SUBST(BITCOIN_MP_GUI_NAME) AC_SUBST(RELDFLAGS) +AC_SUBST(CORE_LDFLAGS) +AC_SUBST(CORE_CPPFLAGS) +AC_SUBST(CORE_CXXFLAGS) AC_SUBST(DEBUG_CPPFLAGS) AC_SUBST(WARN_CXXFLAGS) AC_SUBST(NOWARN_CXXFLAGS) @@ -1898,12 +1981,18 @@ AC_CONFIG_LINKS([contrib/devtools/security-check.py:contrib/devtools/security-ch AC_CONFIG_LINKS([contrib/devtools/symbol-check.py:contrib/devtools/symbol-check.py]) AC_CONFIG_LINKS([contrib/devtools/test-security-check.py:contrib/devtools/test-security-check.py]) AC_CONFIG_LINKS([contrib/devtools/test-symbol-check.py:contrib/devtools/test-symbol-check.py]) +AC_CONFIG_LINKS([contrib/devtools/iwyu/bitcoin.core.imp:contrib/devtools/iwyu/bitcoin.core.imp]) AC_CONFIG_LINKS([contrib/filter-lcov.py:contrib/filter-lcov.py]) AC_CONFIG_LINKS([contrib/macdeploy/background.tiff:contrib/macdeploy/background.tiff]) +AC_CONFIG_LINKS([src/.bear-tidy-config:src/.bear-tidy-config]) +AC_CONFIG_LINKS([src/.clang-tidy:src/.clang-tidy]) 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. @@ -1920,15 +2009,7 @@ LIBS_TEMP="$LIBS" unset LIBS LIBS="$LIBS_TEMP" -PKGCONFIG_PATH_TEMP="$PKG_CONFIG_PATH" -unset PKG_CONFIG_PATH -PKG_CONFIG_PATH="$PKGCONFIG_PATH_TEMP" - -PKGCONFIG_LIBDIR_TEMP="$PKG_CONFIG_LIBDIR" -unset PKG_CONFIG_LIBDIR -PKG_CONFIG_LIBDIR="$PKGCONFIG_LIBDIR_TEMP" - -ac_configure_args="${ac_configure_args} --disable-shared --with-pic --enable-benchmark=no --enable-module-recovery --enable-module-schnorrsig --enable-experimental" +ac_configure_args="${ac_configure_args} --disable-shared --with-pic --enable-benchmark=no --enable-module-recovery --enable-module-schnorrsig" AC_CONFIG_SUBDIRS([src/secp256k1]) AC_OUTPUT @@ -1979,9 +2060,10 @@ echo " build os = $build_os" echo echo " CC = $CC" echo " CFLAGS = $PTHREAD_CFLAGS $CFLAGS" -echo " CPPFLAGS = $DEBUG_CPPFLAGS $HARDENED_CPPFLAGS $CPPFLAGS" +echo " CPPFLAGS = $DEBUG_CPPFLAGS $HARDENED_CPPFLAGS $CORE_CPPFLAGS $CPPFLAGS" echo " CXX = $CXX" -echo " CXXFLAGS = $LTO_CXXFLAGS $DEBUG_CXXFLAGS $HARDENED_CXXFLAGS $WARN_CXXFLAGS $NOWARN_CXXFLAGS $ERROR_CXXFLAGS $GPROF_CXXFLAGS $CXXFLAGS" -echo " LDFLAGS = $LTO_LDFLAGS $PTHREAD_LIBS $HARDENED_LDFLAGS $GPROF_LDFLAGS $LDFLAGS" +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/README.md b/contrib/builder-keys/README.md index 56bd87d0af..a6179d6012 100644 --- a/contrib/builder-keys/README.md +++ b/contrib/builder-keys/README.md @@ -19,9 +19,15 @@ gpg --refresh-keys To fetch keys of builders and active developers, feed the list of fingerprints of the primary keys into gpg: +On \*NIX: ```sh while read fingerprint keyholder_name; do gpg --keyserver hkps://keys.openpgp.org --recv-keys ${fingerprint}; done < ./keys.txt ``` +On Windows (requires Gpg4win >= 4.0.0): +``` +FOR /F "tokens=1" %i IN (keys.txt) DO gpg --keyserver hkps://keys.openpgp.org --recv-keys %i +``` + Add your key to the list if you provided Guix attestations for two major or minor releases of Bitcoin Core. diff --git a/contrib/builder-keys/keys.txt b/contrib/builder-keys/keys.txt index e8032f66ee..f8377cce33 100644 --- a/contrib/builder-keys/keys.txt +++ b/contrib/builder-keys/keys.txt @@ -13,11 +13,13 @@ F20F56EF6A067F70E8A5C99FFF95FAA971697405 centaur (centaur) C060A6635913D98A3587D7DB1C2491FFEB0EF770 Cory Fields (cfields) BF6273FAEF7CC0BA1F562E50989F6B3048A116B5 Dev Random (devrandom) 6D3170C1DC2C6FD0AEEBCA6743811D1A26623924 Douglas Roark (droark) +948444FCE03B05BA5AB0591EC37B1C1D44C786EE Duncan Dean (dunxen) 1C6621605EC50319C463D56C7F81D87985D61612 Emanuele Cisbani (cisba) 9A1689B60D1B3CCE9262307A2F40A9BF167FBA47 Erik Mossberg (erkmos) 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) @@ -50,5 +52,6 @@ ED9BDF7AD6A55E232E84524257FF9BDBCC301009 Sjors Provoost (sjors) 9EDAFF80E080659604F4A76B2EBB056FD847F8A7 Stephan Oeste (Emzy) 6DEEF79B050C4072509B743F8C275BC595448867 Tomas Kanocz (KanoczTomas) AEC1884398647C47413C1C3FB1179EB7347DC10D Warren Togami (wtogami) +74E2DEF5D77260B98BC19438099BAD163C70FBFA Will Clark (will8clark) 79D00BAC68B56D422F945A8F8E3A8F3247DBCBBF Willy Ko (willyko) 71A3B16735405025D447E8F274810B012346C9A6 Wladimir J. van der Laan (laanwj) diff --git a/contrib/devtools/README.md b/contrib/devtools/README.md index 79b0134adc..54b1a85588 100644 --- a/contrib/devtools/README.md +++ b/contrib/devtools/README.md @@ -90,6 +90,21 @@ example: BUILDDIR=$PWD/build contrib/devtools/gen-manpages.py ``` +gen-bitcoin-conf.sh +=================== + +Generates a bitcoin.conf file in `share/examples/` by parsing the output from `bitcoind --help`. This script is run during the +release process to include a bitcoin.conf with the release binaries and can also be run by users to generate a file locally. +When generating a file as part of the release process, make sure to commit the changes after running the script. + +With in-tree builds this tool can be run from any directory within the +repository. To use this tool with out-of-tree builds set `BUILDDIR`. For +example: + +```bash +BUILDDIR=$PWD/build contrib/devtools/gen-bitcoin-conf.sh +``` + security-check.py and test-security-check.py ============================================ diff --git a/contrib/devtools/copyright_header.py b/contrib/devtools/copyright_header.py index 38f3df77c9..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/", ] @@ -320,15 +319,13 @@ def get_most_recent_git_change_year(filename): ################################################################################ def read_file_lines(filename): - f = open(filename, 'r', encoding="utf8") - file_lines = f.readlines() - f.close() + with open(filename, 'r', encoding="utf8") as f: + file_lines = f.readlines() return file_lines def write_file_lines(filename, file_lines): - f = open(filename, 'w', encoding="utf8") - f.write(''.join(file_lines)) - f.close() + with open(filename, 'w', encoding="utf8") as f: + f.write(''.join(file_lines)) ################################################################################ # update header years execution diff --git a/contrib/devtools/gen-bitcoin-conf.sh b/contrib/devtools/gen-bitcoin-conf.sh new file mode 100755 index 0000000000..2ebbd42022 --- /dev/null +++ b/contrib/devtools/gen-bitcoin-conf.sh @@ -0,0 +1,83 @@ +#!/usr/bin/env bash +# 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. + +export LC_ALL=C +TOPDIR=${TOPDIR:-$(git rev-parse --show-toplevel)} +BUILDDIR=${BUILDDIR:-$TOPDIR} +BINDIR=${BINDIR:-$BUILDDIR/src} +BITCOIND=${BITCOIND:-$BINDIR/bitcoind} +SHARE_EXAMPLES_DIR=${SHARE_EXAMPLES_DIR:-$TOPDIR/share/examples} +EXAMPLE_CONF_FILE=${EXAMPLE_CONF_FILE:-$SHARE_EXAMPLES_DIR/bitcoin.conf} + +[ ! -x "$BITCOIND" ] && echo "$BITCOIND not found or not executable." && exit 1 + +DIRTY="" +VERSION_OUTPUT=$($BITCOIND --version) +if [[ $VERSION_OUTPUT == *"dirty"* ]]; then + DIRTY="${DIRTY}${BITCOIND}\n" +fi + +if [ -n "$DIRTY" ] +then + echo -e "WARNING: $BITCOIND was built from a dirty tree.\n" + echo -e "To safely generate a bitcoin.conf file, please commit your changes to $BITCOIND, rebuild, then run this script again.\n" +fi + +echo 'Generating example bitcoin.conf file in share/examples/' + +# create the directory, if it doesn't exist +mkdir -p "${SHARE_EXAMPLES_DIR}" + +# create the header text +cat > "${EXAMPLE_CONF_FILE}" << 'EOF' +## +## bitcoin.conf configuration file. +## Generated by contrib/devtools/gen-bitcoin-conf.sh. +## +## Lines beginning with # are comments. +## All possible configuration options are provided. To use, copy this file +## to your data directory (default or specified by -datadir), uncomment +## options you would like to change, and save the file. +## + + +### Options +EOF + +# parse the output from bitcoind --help +# adding newlines is a bit funky to ensure portability for BSD +# see here for more details: https://stackoverflow.com/a/24575385 +${BITCOIND} --help \ + | sed '1,/Print this help message and exit/d' \ + | sed -E 's/^[[:space:]]{2}\-/#/' \ + | sed -E 's/^[[:space:]]{7}/# /' \ + | sed -E '/[=[:space:]]/!s/#.*$/&=1/' \ + | awk '/^#[a-z]/{x=$0;next}{if (NF==0) print x"\n",x="";else print}' \ + | sed 's,\(^[[:upper:]].*\)\:$,\ +### \1,' \ + | sed 's/[[:space:]]*$//' >> "${EXAMPLE_CONF_FILE}" + +# create the footer text +cat >> "${EXAMPLE_CONF_FILE}" << 'EOF' + +# [Sections] +# Most options will apply to all networks. To confine an option to a specific +# network, add it under the relevant section below. +# +# Note: If not specified under a network section, the options addnode, connect, +# port, bind, rpcport, rpcbind, and wallet will only apply to mainnet. + +# Options for mainnet +[main] + +# Options for testnet +[test] + +# Options for signet +[signet] + +# Options for regtest +[regtest] +EOF diff --git a/contrib/devtools/iwyu/bitcoin.core.imp b/contrib/devtools/iwyu/bitcoin.core.imp new file mode 100644 index 0000000000..919ffab102 --- /dev/null +++ b/contrib/devtools/iwyu/bitcoin.core.imp @@ -0,0 +1,7 @@ +# Fixups / upstreamed changes +[ + { 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/security-check.py b/contrib/devtools/security-check.py index e6a29b73b9..05c0af029e 100755 --- a/contrib/devtools/security-check.py +++ b/contrib/devtools/security-check.py @@ -12,10 +12,6 @@ from typing import List import lief #type:ignore -# temporary constant, to be replaced with lief.ELF.ARCH.RISCV -# https://github.com/lief-project/LIEF/pull/562 -LIEF_ELF_ARCH_RISCV = lief.ELF.ARCH(243) - def check_ELF_RELRO(binary) -> bool: ''' Check for read-only relocations. @@ -101,7 +97,6 @@ def check_ELF_separate_code(binary): for segment in binary.segments: if segment.type == lief.ELF.SEGMENT_TYPES.LOAD: for section in segment.sections: - assert(section.name not in flags_per_section) flags_per_section[section.name] = segment.flags # Spot-check ELF LOAD program header flags per section # If these sections exist, check them against the expected R/W/E flags @@ -222,7 +217,7 @@ CHECKS = { lief.ARCHITECTURES.ARM: BASE_ELF, lief.ARCHITECTURES.ARM64: BASE_ELF, lief.ARCHITECTURES.PPC: BASE_ELF, - LIEF_ELF_ARCH_RISCV: BASE_ELF, + lief.ARCHITECTURES.RISCV: BASE_ELF, }, lief.EXE_FORMATS.PE: { lief.ARCHITECTURES.X86: BASE_PE, @@ -250,12 +245,9 @@ if __name__ == '__main__': continue if arch == lief.ARCHITECTURES.NONE: - if binary.header.machine_type == LIEF_ELF_ARCH_RISCV: - arch = LIEF_ELF_ARCH_RISCV - else: - print(f'{filename}: unknown architecture') - retval = 1 - continue + print(f'{filename}: unknown architecture') + retval = 1 + continue failed: List[str] = [] for (name, func) in CHECKS[etype][arch]: diff --git a/contrib/devtools/symbol-check.py b/contrib/devtools/symbol-check.py index 461132ae63..23d29af3f1 100755 --- a/contrib/devtools/symbol-check.py +++ b/contrib/devtools/symbol-check.py @@ -15,10 +15,6 @@ from typing import List, Dict import lief #type:ignore -# temporary constant, to be replaced with lief.ELF.ARCH.RISCV -# https://github.com/lief-project/LIEF/pull/562 -LIEF_ELF_ARCH_RISCV = lief.ELF.ARCH(243) - # Debian 9 (Stretch) EOL: 2022. https://wiki.debian.org/DebianReleases#Production_Releases # # - g++ version 6.3.0 (https://packages.debian.org/search?suite=stretch&arch=any&searchon=names&keywords=g%2B%2B) @@ -44,7 +40,7 @@ MAX_VERSIONS = { lief.ELF.ARCH.ARM: (2,18), lief.ELF.ARCH.AARCH64:(2,18), lief.ELF.ARCH.PPC64: (2,18), - LIEF_ELF_ARCH_RISCV: (2,27), + lief.ELF.ARCH.RISCV: (2,27), }, 'LIBATOMIC': (1,0), 'V': (0,5,0), # xkb (bitcoin-qt only) @@ -54,9 +50,8 @@ MAX_VERSIONS = { # 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: @@ -78,7 +73,7 @@ ELF_INTERPRETER_NAMES: Dict[lief.ELF.ARCH, Dict[lief.ENDIANNESS, str]] = { lief.ENDIANNESS.BIG: "/lib64/ld64.so.1", lief.ENDIANNESS.LITTLE: "/lib64/ld64.so.2", }, - LIEF_ELF_ARCH_RISCV: { + lief.ELF.ARCH.RISCV: { lief.ENDIANNESS.LITTLE: "/lib/ld-linux-riscv64-lp64d.so.1", }, } @@ -200,7 +195,7 @@ def check_exported_symbols(binary) -> bool: if not symbol.exported: continue name = symbol.name - if binary.header.machine_type == LIEF_ELF_ARCH_RISCV or name in IGNORE_EXPORTS: + if binary.header.machine_type == lief.ELF.ARCH.RISCV or name in IGNORE_EXPORTS: continue print(f'{binary.name}: export of symbol {name} not allowed!') ok = False diff --git a/contrib/devtools/test-symbol-check.py b/contrib/devtools/test-symbol-check.py index e1a2ebc491..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") @@ -187,7 +187,7 @@ class TestSymbolChecks(unittest.TestCase): executable = 'test3.exe' with open(source, 'w', encoding="utf8") as f: f.write(''' - #include <windows.h> + #include <combaseapi.h> int main() { 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 90289f9d40..ed6ac8d589 100644 --- a/contrib/guix/README.md +++ b/contrib/guix/README.md @@ -75,7 +75,7 @@ crucial differences: 1. Since only Windows and macOS build outputs require codesigning, the `HOSTS` environment variable will have a sane default value of `x86_64-w64-mingw32 - x86_64-apple-darwin` instead of all the platforms. + x86_64-apple-darwin arm64-apple-darwin` instead of all the platforms. 2. The `guix-codesign` command ***requires*** a `DETACHED_SIGS_REPO` flag. * _**DETACHED_SIGS_REPO**_ @@ -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/guix-attest b/contrib/guix/guix-attest index 6e12cbead7..b0ef28dc3f 100755 --- a/contrib/guix/guix-attest +++ b/contrib/guix/guix-attest @@ -19,8 +19,16 @@ source "$(dirname "${BASH_SOURCE[0]}")/libexec/prelude.bash" ################ check_tools cat env basename mkdir diff sort + if [ -z "$NO_SIGN" ]; then - check_tools gpg + # make it possible to override the gpg binary + GPG=${GPG:-gpg} + + # $GPG can contain extra arguments passed to the binary + # so let's check only the existence of arg[0] + # shellcheck disable=SC2206 + GPG_ARRAY=($GPG) + check_tools "${GPG_ARRAY[0]}" fi ################ @@ -90,7 +98,7 @@ if [ -z "${signer_name}" ]; then signer_name="$gpg_key_name" fi -if [ -z "$NO_SIGN" ] && ! gpg --dry-run --list-secret-keys "${gpg_key_name}" >/dev/null 2>&1; then +if [ -z "$NO_SIGN" ] && ! ${GPG} --dry-run --list-secret-keys "${gpg_key_name}" >/dev/null 2>&1; then echo "ERR: GPG can't seem to find any key named '${gpg_key_name}'" exit 1 fi @@ -239,11 +247,11 @@ mkdir -p "$outsigdir" echo "Signing SHA256SUMS to produce SHA256SUMS.asc" for i in *.SHA256SUMS; do if [ ! -e "$i".asc ]; then - gpg --detach-sign \ - --digest-algo sha256 \ - --local-user "$gpg_key_name" \ - --armor \ - --output "$i".asc "$i" + ${GPG} --detach-sign \ + --digest-algo sha256 \ + --local-user "$gpg_key_name" \ + --armor \ + --output "$i".asc "$i" else echo "Signature already there" fi diff --git a/contrib/guix/guix-build b/contrib/guix/guix-build index b38f91f3c7..74b24b9612 100755 --- a/contrib/guix/guix-build +++ b/contrib/guix/guix-build @@ -121,7 +121,7 @@ else fi ################ -# When building for darwin, the macOS SDK should exists +# When building for darwin, the macOS SDK should exist ################ for host in $HOSTS; do @@ -130,8 +130,9 @@ for host in $HOSTS; do OSX_SDK="$(make -C "${PWD}/depends" --no-print-directory HOST="$host" print-OSX_SDK | sed 's@^[^=]\+=@@g')" if [ -e "$OSX_SDK" ]; then echo "Found macOS SDK at '${OSX_SDK}', using..." + break else - echo "macOS SDK does not exist at '${OSX_SDK}', please place the extracted, untarred SDK there to perform darwin builds, exiting..." + echo "macOS SDK does not exist at '${OSX_SDK}', please place the extracted, untarred SDK there to perform darwin builds, or define SDK_PATH environment variable. Exiting..." exit 1 fi ;; @@ -234,21 +235,6 @@ host_to_commonname() { # Determine the reference time used for determinism (overridable by environment) SOURCE_DATE_EPOCH="${SOURCE_DATE_EPOCH:-$(git -c log.showSignature=false log --format=%at -1)}" -# Execute "$@" in a pinned, possibly older version of Guix, for reproducibility -# across time. -time-machine() { - # shellcheck disable=SC2086 - guix time-machine --url=https://git.savannah.gnu.org/git/guix.git \ - --commit=ae03f401381e956c4c41b4cf495cbde964fa43d0 \ - --cores="$JOBS" \ - --keep-failed \ - --fallback \ - ${SUBSTITUTE_URLS:+--substitute-urls="$SUBSTITUTE_URLS"} \ - ${ADDITIONAL_GUIX_COMMON_FLAGS} ${ADDITIONAL_GUIX_TIMEMACHINE_FLAGS} \ - -- "$@" -} - - # Precious directories are those which should not be cleaned between successive # guix builds depends_precious_dir_names='SOURCES_PATH BASE_CACHE SDK_PATH' diff --git a/contrib/guix/guix-codesign b/contrib/guix/guix-codesign index 2dd30bfa64..3279d431aa 100755 --- a/contrib/guix/guix-codesign +++ b/contrib/guix/guix-codesign @@ -152,10 +152,10 @@ outdir_for_host() { unsigned_tarball_for_host() { case "$1" in *mingw*) - echo "$(outdir_for_host "$1")/${DISTNAME}-win-unsigned.tar.gz" + echo "$(outdir_for_host "$1")/${DISTNAME}-win64-unsigned.tar.gz" ;; *darwin*) - echo "$(outdir_for_host "$1")/${DISTNAME}-osx-unsigned.tar.gz" + echo "$(outdir_for_host "$1")/${DISTNAME}-${1}-unsigned.tar.gz" ;; *) exit 1 @@ -222,20 +222,6 @@ JOBS="${JOBS:-$(nproc)}" # Determine the reference time used for determinism (overridable by environment) SOURCE_DATE_EPOCH="${SOURCE_DATE_EPOCH:-$(git -c log.showSignature=false log --format=%at -1)}" -# Execute "$@" in a pinned, possibly older version of Guix, for reproducibility -# across time. -time-machine() { - # shellcheck disable=SC2086 - guix time-machine --url=https://git.savannah.gnu.org/git/guix.git \ - --commit=aa34d4d28dfe25ba47d5800d05000fb7221788c0 \ - --cores="$JOBS" \ - --keep-failed \ - --fallback \ - ${SUBSTITUTE_URLS:+--substitute-urls="$SUBSTITUTE_URLS"} \ - ${ADDITIONAL_GUIX_COMMON_FLAGS} ${ADDITIONAL_GUIX_TIMEMACHINE_FLAGS} \ - -- "$@" -} - # Make sure an output directory exists for our builds OUTDIR_BASE="${OUTDIR_BASE:-${VERSION_BASE}/output}" mkdir -p "$OUTDIR_BASE" diff --git a/contrib/guix/libexec/build.sh b/contrib/guix/libexec/build.sh index e06a469338..2795a90036 100755 --- a/contrib/guix/libexec/build.sh +++ b/contrib/guix/libexec/build.sh @@ -79,19 +79,6 @@ prepend_to_search_env_var() { export "${1}=${2}${!1:+:}${!1}" } -case "$HOST" in - *darwin*) - # When targeting darwin, zlib is required by native_libdmg-hfsplus. - zlib_store_path=$(store_path "zlib") - zlib_static_store_path=$(store_path "zlib" static) - - prepend_to_search_env_var LIBRARY_PATH "${zlib_static_store_path}/lib:${zlib_store_path}/lib" - prepend_to_search_env_var C_INCLUDE_PATH "${zlib_store_path}/include" - prepend_to_search_env_var CPLUS_INCLUDE_PATH "${zlib_store_path}/include" - prepend_to_search_env_var OBJC_INCLUDE_PATH "${zlib_store_path}/include" - prepend_to_search_env_var OBJCPLUS_INCLUDE_PATH "${zlib_store_path}/include" -esac - # Set environment variables to point the CROSS toolchain to the right # includes/libs for $HOST case "$HOST" in @@ -167,7 +154,6 @@ case "$HOST" in *linux*) glibc_dynamic_linker=$( case "$HOST" in - i686-linux-gnu) echo /lib/ld-linux.so.2 ;; x86_64-linux-gnu) echo /lib64/ld-linux-x86-64.so.2 ;; arm-linux-gnueabihf) echo /lib/ld-linux-armhf.so.3 ;; aarch64-linux-gnu) echo /lib/ld-linux-aarch64.so.1 ;; @@ -204,19 +190,12 @@ make -C depends --jobs="$JOBS" HOST="$HOST" \ ${SOURCES_PATH+SOURCES_PATH="$SOURCES_PATH"} \ ${BASE_CACHE+BASE_CACHE="$BASE_CACHE"} \ ${SDK_PATH+SDK_PATH="$SDK_PATH"} \ - i686_linux_CC=i686-linux-gnu-gcc \ - i686_linux_CXX=i686-linux-gnu-g++ \ - i686_linux_AR=i686-linux-gnu-ar \ - i686_linux_RANLIB=i686-linux-gnu-ranlib \ - i686_linux_NM=i686-linux-gnu-nm \ - i686_linux_STRIP=i686-linux-gnu-strip \ x86_64_linux_CC=x86_64-linux-gnu-gcc \ x86_64_linux_CXX=x86_64-linux-gnu-g++ \ x86_64_linux_AR=x86_64-linux-gnu-ar \ 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_i686_linux='-platform linux-g++ -xplatform bitcoin-linux-g++' \ qt_config_opts_x86_64_linux='-platform linux-g++ -xplatform bitcoin-linux-g++' \ FORCE_USE_SYSTEM_CLANG=1 @@ -244,6 +223,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" ;; @@ -270,10 +250,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" @@ -340,18 +316,17 @@ mkdir -p "$DISTSRC" mkdir -p "unsigned-app-${HOST}" cp --target-directory="unsigned-app-${HOST}" \ osx_volname \ - contrib/macdeploy/detached-sig-{apply,create}.sh \ - "${BASEPREFIX}/${HOST}"/native/bin/dmg + contrib/macdeploy/detached-sig-create.sh mv --target-directory="unsigned-app-${HOST}" dist ( cd "unsigned-app-${HOST}" find . -print0 \ | sort --zero-terminated \ | tar --create --no-recursion --mode='u+rw,go+r-w,a+X' --null --files-from=- \ - | gzip -9n > "${OUTDIR}/${DISTNAME}-osx-unsigned.tar.gz" \ - || ( rm -f "${OUTDIR}/${DISTNAME}-osx-unsigned.tar.gz" && exit 1 ) + | gzip -9n > "${OUTDIR}/${DISTNAME}-${HOST}-unsigned.tar.gz" \ + || ( rm -f "${OUTDIR}/${DISTNAME}-${HOST}-unsigned.tar.gz" && exit 1 ) ) - make deploy ${V:+V=1} OSX_DMG="${OUTDIR}/${DISTNAME}-osx-unsigned.dmg" + make deploy ${V:+V=1} OSX_DMG="${OUTDIR}/${DISTNAME}-${HOST}-unsigned.dmg" ;; esac ( @@ -390,6 +365,12 @@ mkdir -p "$DISTSRC" ;; esac + # copy over the example bitcoin.conf file. if contrib/devtools/gen-bitcoin-conf.sh + # 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 @@ -423,8 +404,8 @@ mkdir -p "$DISTSRC" find "${DISTNAME}" -print0 \ | sort --zero-terminated \ | tar --create --no-recursion --mode='u+rw,go+r-w,a+X' --null --files-from=- \ - | gzip -9n > "${OUTDIR}/${DISTNAME}-${HOST//x86_64-apple-darwin/osx64}.tar.gz" \ - || ( rm -f "${OUTDIR}/${DISTNAME}-${HOST//x86_64-apple-darwin/osx64}.tar.gz" && exit 1 ) + | gzip -9n > "${OUTDIR}/${DISTNAME}-${HOST}.tar.gz" \ + || ( rm -f "${OUTDIR}/${DISTNAME}-${HOST}.tar.gz" && exit 1 ) ;; esac ) # $DISTSRC/installed @@ -439,8 +420,8 @@ mkdir -p "$DISTSRC" find . -print0 \ | sort --zero-terminated \ | tar --create --no-recursion --mode='u+rw,go+r-w,a+X' --null --files-from=- \ - | gzip -9n > "${OUTDIR}/${DISTNAME}-win-unsigned.tar.gz" \ - || ( rm -f "${OUTDIR}/${DISTNAME}-win-unsigned.tar.gz" && exit 1 ) + | gzip -9n > "${OUTDIR}/${DISTNAME}-win64-unsigned.tar.gz" \ + || ( rm -f "${OUTDIR}/${DISTNAME}-win64-unsigned.tar.gz" && exit 1 ) ) ;; esac diff --git a/contrib/guix/libexec/codesign.sh b/contrib/guix/libexec/codesign.sh index a8867ca351..9a5d3a1ce5 100755 --- a/contrib/guix/libexec/codesign.sh +++ b/contrib/guix/libexec/codesign.sh @@ -84,14 +84,11 @@ mkdir -p "$DISTSRC" # Apply detached codesignatures to dist/ (in-place) signapple apply dist/Bitcoin-Qt.app codesignatures/osx/dist - # Make an uncompressed DMG from dist/ + # Make a DMG from dist/ xorrisofs -D -l -V "$(< osx_volname)" -no-pad -r -dir-mode 0755 \ - -o uncompressed.dmg \ + -o "${OUTDIR}/${DISTNAME}-${HOST}.dmg" \ dist \ -- -volume_date all_file_dates ="$SOURCE_DATE_EPOCH" - - # Compress uncompressed.dmg and output to OUTDIR - ./dmg dmg uncompressed.dmg "${OUTDIR}/${DISTNAME}-osx-signed.dmg" ;; *) exit 1 diff --git a/contrib/guix/libexec/prelude.bash b/contrib/guix/libexec/prelude.bash index 1f287e9bbb..3eb8fc02da 100644 --- a/contrib/guix/libexec/prelude.bash +++ b/contrib/guix/libexec/prelude.bash @@ -46,6 +46,22 @@ exit 1 fi ################ +# Execute "$@" in a pinned, possibly older version of Guix, for reproducibility +# across time. +time-machine() { + # shellcheck disable=SC2086 + guix time-machine --url=https://git.savannah.gnu.org/git/guix.git \ + --commit=998eda3067c7d21e0d9bb3310d2f5a14b8f1c681 \ + --cores="$JOBS" \ + --keep-failed \ + --fallback \ + ${SUBSTITUTE_URLS:+--substitute-urls="$SUBSTITUTE_URLS"} \ + ${ADDITIONAL_GUIX_COMMON_FLAGS} ${ADDITIONAL_GUIX_TIMEMACHINE_FLAGS} \ + -- "$@" +} + + +################ # Set common variables ################ diff --git a/contrib/guix/manifest.scm b/contrib/guix/manifest.scm index d296eb9543..7c45a5b866 100644 --- a/contrib/guix/manifest.scm +++ b/contrib/guix/manifest.scm @@ -130,27 +130,21 @@ chain for " target " development.")) (license (package-license xgcc))))) (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 gcc-7) - (base-kernel-headers linux-libre-headers-4.9) - (base-libc (make-glibc-without-ssp glibc-2.24)) - (base-gcc (make-gcc-rpath-link base-gcc))) + (base-gcc-for-libc base-gcc) + (base-kernel-headers base-linux-kernel-headers) + (base-libc (make-glibc-with-bind-now (make-glibc-without-werror glibc-2.24))) + (base-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 @@ -160,11 +154,15 @@ 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")) -;; Required to support std::filesystem for mingw-w64 target. -(define (make-gcc-without-newlib gcc) - (package-with-extra-configure-variable gcc "--with-newlib" "no")) +(define (make-mingw-w64-cross-gcc cross-gcc) + (package-with-extra-patches cross-gcc + (search-our-patches "vmov-alignment.patch" + "gcc-broken-longjmp.patch"))) (define (make-mingw-pthreads-cross-toolchain target) "Create a cross-compilation toolchain package for TARGET" @@ -172,7 +170,7 @@ desirable for building Bitcoin Core release binaries." (pthreads-xlibc mingw-w64-x86_64-winpthreads) (pthreads-xgcc (make-gcc-with-pthreads (cross-gcc target - #:xgcc (make-gcc-without-newlib (make-ssp-fixed-gcc base-gcc)) + #:xgcc (make-ssp-fixed-gcc (make-mingw-w64-cross-gcc base-gcc)) #:xbinutils xbinutils #:libc pthreads-xlibc)))) ;; Define a meta-package that propagates the resulting XBINUTILS, XLIBC, and @@ -196,12 +194,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.11.5") + (version "0.12.1") (source (origin (method git-fetch) @@ -211,8 +214,15 @@ chain for " target " development.")) (file-name (git-file-name name version)) (sha256 (base32 - "0qahjfg1n0x76ps2mbyljvws1l3qhkqvmxqbahps4qgywl2hbdkj")))) + "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") @@ -254,7 +264,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)) @@ -267,8 +277,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 @@ -352,7 +361,7 @@ thus should be able to compile on most platforms where these exist.") #t))))))) (define-public python-certvalidator - (let ((commit "e5bdb4bfcaa09fa0af355eb8867d00dfeecba08c")) + (let ((commit "a145bf25eb75a9f014b3e7678826132efbba6213")) (package (name "python-certvalidator") (version (git-version "0.1" "1" commit)) @@ -365,7 +374,7 @@ thus should be able to compile on most platforms where these exist.") (file-name (git-file-name name commit)) (sha256 (base32 - "18pvxkvpkfkzgvfylv0kx65pmxfcv1hpsg03cip93krfvrrl4c75")))) + "1qw2k7xis53179lpqdqyylbcmp76lj7sagp883wmxg5i7chhc96k")))) (build-system python-build-system) (propagated-inputs `(("python-asn1crypto" ,python-asn1crypto) @@ -490,7 +499,7 @@ and endian independent.") (license license:expat))) (define-public python-signapple - (let ((commit "b084cbbf44d5330448ffce0c7d118f75781b64bd")) + (let ((commit "8a945a2e7583be2665cf3a6a89d665b70ecd1ab6")) (package (name "python-signapple") (version (git-version "0.1" "1" commit)) @@ -503,7 +512,7 @@ and endian independent.") (file-name (git-file-name name commit)) (sha256 (base32 - "0k7inccl2mzac3wq4asbr0kl8s4cghm8982z54kfascqg45shv01")))) + "0fr1hangvfyiwflca6jg5g8zvg3jc9qr7vd2c12ff89pznf38dlg")))) (build-system python-build-system) (propagated-inputs `(("python-asn1crypto" ,python-asn1crypto) @@ -511,8 +520,7 @@ and endian independent.") ("python-certvalidator" ,python-certvalidator) ("python-elfesteem" ,python-elfesteem) ("python-requests" ,python-requests) - ("python-macholib" ,python-macholib) - ("libcrypto" ,openssl))) + ("python-macholib" ,python-macholib))) ;; There are no tests, but attempting to run python setup.py test leads to ;; problems, just disable the test (arguments '(#:tests? #f)) @@ -522,6 +530,15 @@ and endian independent.") inspecting signatures in Mach-O binaries.") (license license:expat)))) +(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 +555,9 @@ inspecting signatures in Mach-O binaries.") (patches (search-our-patches "glibc-ldd-x86_64.patch" "glibc-versioned-locpath.patch" "glibc-2.24-elfm-loadaddr-dynamic-rewrite.patch" - "glibc-2.24-no-build-time-cxx-header-run.patch")))))) + "glibc-2.24-no-build-time-cxx-header-run.patch" + "glibc-2.24-fcommon.patch" + "glibc-2.24-guix-prefix.patch")))))) (define-public glibc-2.27/bitcoin-patched (package @@ -554,7 +573,9 @@ inspecting signatures in Mach-O binaries.") (base32 "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-riscv64-Use-__has_include-to-include-asm-syscalls.h.patch" + "glibc-2.27-dont-redefine-nss-database.patch" + "glibc-2.27-guix-prefix.patch")))))) (packages->manifest (append @@ -578,25 +599,23 @@ inspecting signatures in Mach-O binaries.") bzip2 gzip xz - zlib - (list zlib "static") ;; Build tools gnu-make - libtool + libtool-2.4.7 autoconf-2.71 automake pkg-config bison + ;; Native GCC 10 toolchain + gcc-toolchain-10 + (list gcc-toolchain-10 "static") ;; Scripting perl python-3 ;; Git git ;; Tests - lief - ;; Native gcc 7 toolchain - gcc-toolchain-7 - (list gcc-toolchain-7 "static")) + (fix-ppc64-nx-default lief)) (let ((target (getenv "HOST"))) (cond ((string-suffix? "-mingw32" target) ;; Windows @@ -607,8 +626,8 @@ inspecting signatures in Mach-O binaries.") ((string-contains target "-linux-") (list (cond ((string-contains target "riscv64-") (make-bitcoin-cross-toolchain target - #:base-libc glibc-2.27/bitcoin-patched - #:base-kernel-headers linux-libre-headers-4.19)) + #: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/gcc-broken-longjmp.patch b/contrib/guix/patches/gcc-broken-longjmp.patch new file mode 100644 index 0000000000..1cfc0918b0 --- /dev/null +++ b/contrib/guix/patches/gcc-broken-longjmp.patch @@ -0,0 +1,68 @@ +commit eb5698897c52702498938592d7f76e67d126451f +Author: Eric Botcazou <ebotcazou@adacore.com> +Date: Wed May 5 22:48:51 2021 +0200 + + Fix PR target/100402 + + This is a regression for 64-bit Windows present from mainline down to the 9 + branch and introduced by the fix for PR target/99234. Again SEH, but with + a twist related to the way MinGW implements setjmp/longjmp, which turns out + to be piggybacked on SEH with recent versions of MinGW, i.e. the longjmp + performs a bona-fide unwinding of the stack, because it calls RtlUnwindEx + with the second argument initially passed to setjmp, which is the result of + __builtin_frame_address (0) in the MinGW header file: + + define setjmp(BUF) _setjmp((BUF), __builtin_frame_address (0)) + + This means that we directly expose the frame pointer to the SEH machinery + here (unlike with regular exception handling where we use an intermediate + CFA) and thus that we cannot do whatever we want with it. The old code + would leave it unaligned, i.e. not multiple of 16, whereas the new code + aligns it, but this breaks for some reason; at least it appears that a + .seh_setframe directive with 0 as second argument always works, so the + fix aligns it this way. + + gcc/ + PR target/100402 + * config/i386/i386.c (ix86_compute_frame_layout): For a SEH target, + always return the establisher frame for __builtin_frame_address (0). + gcc/testsuite/ + * gcc.c-torture/execute/20210505-1.c: New test. + +diff --git a/gcc/config/i386/i386.c b/gcc/config/i386/i386.c +index 2f838840e96..06ad1b2274e 100644 +--- a/gcc/config/i386/i386.c ++++ b/gcc/config/i386/i386.c +@@ -6356,12 +6356,29 @@ ix86_compute_frame_layout (void) + area, see the SEH code in config/i386/winnt.c for the rationale. */ + frame->hard_frame_pointer_offset = frame->sse_reg_save_offset; + +- /* If we can leave the frame pointer where it is, do so. Also, return ++ /* If we can leave the frame pointer where it is, do so; however return + the establisher frame for __builtin_frame_address (0) or else if the +- frame overflows the SEH maximum frame size. */ ++ frame overflows the SEH maximum frame size. ++ ++ Note that the value returned by __builtin_frame_address (0) is quite ++ constrained, because setjmp is piggybacked on the SEH machinery with ++ recent versions of MinGW: ++ ++ # elif defined(__SEH__) ++ # if defined(__aarch64__) || defined(_ARM64_) ++ # define setjmp(BUF) _setjmp((BUF), __builtin_sponentry()) ++ # elif (__MINGW_GCC_VERSION < 40702) ++ # define setjmp(BUF) _setjmp((BUF), mingw_getsp()) ++ # else ++ # define setjmp(BUF) _setjmp((BUF), __builtin_frame_address (0)) ++ # endif ++ ++ and the second argument passed to _setjmp, if not null, is forwarded ++ to the TargetFrame parameter of RtlUnwindEx by longjmp (after it has ++ built an ExceptionRecord on the fly describing the setjmp buffer). */ + const HOST_WIDE_INT diff + = frame->stack_pointer_offset - frame->hard_frame_pointer_offset; +- if (diff <= 255) ++ if (diff <= 255 && !crtl->accesses_prior_frames) + { + /* The resulting diff will be a multiple of 16 lower than 255, + i.e. at most 240 as required by the unwind data structure. */ diff --git a/contrib/guix/patches/glibc-2.24-fcommon.patch b/contrib/guix/patches/glibc-2.24-fcommon.patch new file mode 100644 index 0000000000..2bc32ede90 --- /dev/null +++ b/contrib/guix/patches/glibc-2.24-fcommon.patch @@ -0,0 +1,32 @@ +commit 264a4a0dbe1f4369db315080034b500bed66016c +Author: fanquake <fanquake@gmail.com> +Date: Fri May 6 11:03:04 2022 +0100 + + build: use -fcommon to retain legacy behaviour with GCC 10 + + GCC 10 started using -fno-common by default, which causes issues with + the powerpc builds using gibc 2.24. A patch was commited to glibc to fix + the issue, 18363b4f010da9ba459b13310b113ac0647c2fcc but is non-trvial + to backport, and was broken in at least one way, see the followup in + commit 7650321ce037302bfc2f026aa19e0213b8d02fe6. + + For now, retain the legacy GCC behaviour by passing -fcommon when + building glibc. + + https://gcc.gnu.org/onlinedocs/gcc/Code-Gen-Options.html. + https://sourceware.org/git/?p=glibc.git;a=commit;h=18363b4f010da9ba459b13310b113ac0647c2fcc + https://sourceware.org/git/?p=glibc.git;a=commit;h=7650321ce037302bfc2f026aa19e0213b8d02fe6 + +diff --git a/Makeconfig b/Makeconfig +index ee379f5852..63c4a2f234 100644 +--- a/Makeconfig ++++ b/Makeconfig +@@ -824,7 +824,7 @@ ifeq "$(strip $(+cflags))" "" + +cflags := $(default_cflags) + endif # $(+cflags) == "" + +-+cflags += $(cflags-cpu) $(+gccwarn) $(+merge-constants) $(+math-flags) +++cflags += $(cflags-cpu) $(+gccwarn) $(+merge-constants) $(+math-flags) -fcommon + +gcc-nowarn := -w + + # Don't duplicate options if we inherited variables from the parent. 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..cba2f59a8d --- /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 " -fdebug-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-dont-redefine-nss-database.patch b/contrib/guix/patches/glibc-2.27-dont-redefine-nss-database.patch new file mode 100644 index 0000000000..16a595d613 --- /dev/null +++ b/contrib/guix/patches/glibc-2.27-dont-redefine-nss-database.patch @@ -0,0 +1,87 @@ +commit 78a90c2f74a2012dd3eff302189e47ff6779a757 +Author: Andreas Schwab <schwab@linux-m68k.org> +Date: Fri Mar 2 23:07:14 2018 +0100 + + Fix multiple definitions of __nss_*_database (bug 22918) + + (cherry picked from commit eaf6753f8aac33a36deb98c1031d1bad7b593d2d) + +diff --git a/nscd/gai.c b/nscd/gai.c +index d081747797..576fd0045b 100644 +--- a/nscd/gai.c ++++ b/nscd/gai.c +@@ -45,3 +45,6 @@ + #ifdef HAVE_LIBIDN + # include <libidn/idn-stub.c> + #endif ++ ++/* Some variables normally defined in libc. */ ++service_user *__nss_hosts_database attribute_hidden; +diff --git a/nss/nsswitch.c b/nss/nsswitch.c +index d5e655974f..b0f0c11a3e 100644 +--- a/nss/nsswitch.c ++++ b/nss/nsswitch.c +@@ -62,7 +62,7 @@ static service_library *nss_new_service (name_database *database, + + /* Declare external database variables. */ + #define DEFINE_DATABASE(name) \ +- extern service_user *__nss_##name##_database attribute_hidden; \ ++ service_user *__nss_##name##_database attribute_hidden; \ + weak_extern (__nss_##name##_database) + #include "databases.def" + #undef DEFINE_DATABASE +diff --git a/nss/nsswitch.h b/nss/nsswitch.h +index eccb535ef5..63573b9ebc 100644 +--- a/nss/nsswitch.h ++++ b/nss/nsswitch.h +@@ -226,10 +226,10 @@ libc_hidden_proto (__nss_hostname_digits_dots) + #define MAX_NR_ADDRS 48 + + /* Prototypes for __nss_*_lookup2 functions. */ +-#define DEFINE_DATABASE(arg) \ +- service_user *__nss_##arg##_database attribute_hidden; \ +- int __nss_##arg##_lookup2 (service_user **, const char *, \ +- const char *, void **); \ ++#define DEFINE_DATABASE(arg) \ ++ extern service_user *__nss_##arg##_database attribute_hidden; \ ++ int __nss_##arg##_lookup2 (service_user **, const char *, \ ++ const char *, void **); \ + libc_hidden_proto (__nss_##arg##_lookup2) + #include "databases.def" + #undef DEFINE_DATABASE +diff --git a/posix/tst-rfc3484-2.c b/posix/tst-rfc3484-2.c +index f509534ca9..8c64ac59ff 100644 +--- a/posix/tst-rfc3484-2.c ++++ b/posix/tst-rfc3484-2.c +@@ -58,6 +58,7 @@ _res_hconf_init (void) + #undef USE_NSCD + #include "../sysdeps/posix/getaddrinfo.c" + ++service_user *__nss_hosts_database attribute_hidden; + + /* This is the beginning of the real test code. The above defines + (among other things) the function rfc3484_sort. */ +diff --git a/posix/tst-rfc3484-3.c b/posix/tst-rfc3484-3.c +index ae44087a10..1c61aaf844 100644 +--- a/posix/tst-rfc3484-3.c ++++ b/posix/tst-rfc3484-3.c +@@ -58,6 +58,7 @@ _res_hconf_init (void) + #undef USE_NSCD + #include "../sysdeps/posix/getaddrinfo.c" + ++service_user *__nss_hosts_database attribute_hidden; + + /* This is the beginning of the real test code. The above defines + (among other things) the function rfc3484_sort. */ +diff --git a/posix/tst-rfc3484.c b/posix/tst-rfc3484.c +index 7f191abbbc..8f45848e44 100644 +--- a/posix/tst-rfc3484.c ++++ b/posix/tst-rfc3484.c +@@ -58,6 +58,7 @@ _res_hconf_init (void) + #undef USE_NSCD + #include "../sysdeps/posix/getaddrinfo.c" + ++service_user *__nss_hosts_database attribute_hidden; + + /* This is the beginning of the real test code. The above defines + (among other things) the function rfc3484_sort. */ diff --git a/contrib/guix/patches/glibc-2.27-guix-prefix.patch b/contrib/guix/patches/glibc-2.27-guix-prefix.patch new file mode 100644 index 0000000000..cdb3971f7a --- /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 " -fdebug-prefix-map={}=/usr" \;` ++ + libtype.o := lib%.a + object-suffixes += .o + ifeq (yes,$(build-shared)) +-- +2.35.1 + diff --git a/contrib/guix/patches/glibc-2.27-riscv64-Use-__has_include__-to-include-asm-syscalls.h.patch b/contrib/guix/patches/glibc-2.27-riscv64-Use-__has_include-to-include-asm-syscalls.h.patch index d6217157ee..c0f8495c41 100644 --- a/contrib/guix/patches/glibc-2.27-riscv64-Use-__has_include__-to-include-asm-syscalls.h.patch +++ b/contrib/guix/patches/glibc-2.27-riscv64-Use-__has_include-to-include-asm-syscalls.h.patch @@ -1,3 +1,7 @@ +Note that this has been modified from the original commit, to use __has_include +instead of __has_include__, as the later was causing build failures with GCC 10. +See also: http://lists.busybox.net/pipermail/buildroot/2020-July/590376.html. + https://sourceware.org/git/?p=glibc.git;a=commit;h=0b9c84906f653978fb8768c7ebd0ee14a47e662e From 562c52cc81a4e456a62e6455feb32732049e9070 Mon Sep 17 00:00:00 2001 @@ -59,7 +63,7 @@ index d612ef4c6c..0b2042620b 100644 #include <atomic.h> #include <sys/cachectl.h> -#include <asm/syscalls.h> -+#if __has_include__ (<asm/syscalls.h>) ++#if __has_include (<asm/syscalls.h>) +# include <asm/syscalls.h> +#else +# include <asm/unistd.h> 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/guix/patches/vmov-alignment.patch b/contrib/guix/patches/vmov-alignment.patch new file mode 100644 index 0000000000..072f76eafd --- /dev/null +++ b/contrib/guix/patches/vmov-alignment.patch @@ -0,0 +1,267 @@ +Description: Use unaligned VMOV instructions +Author: Stephen Kitt <skitt@debian.org> +Bug-Debian: https://bugs.debian.org/939559 + +Based on a patch originally by Claude Heiland-Allen <claude@mathr.co.uk> + +--- a/gcc/config/i386/sse.md ++++ b/gcc/config/i386/sse.md +@@ -1058,17 +1058,11 @@ + { + if (FLOAT_MODE_P (GET_MODE_INNER (<MODE>mode))) + { +- if (misaligned_operand (operands[1], <MODE>mode)) +- return "vmovu<ssemodesuffix>\t{%1, %0%{%3%}%N2|%0%{%3%}%N2, %1}"; +- else +- return "vmova<ssemodesuffix>\t{%1, %0%{%3%}%N2|%0%{%3%}%N2, %1}"; ++ return "vmovu<ssemodesuffix>\t{%1, %0%{%3%}%N2|%0%{%3%}%N2, %1}"; + } + else + { +- if (misaligned_operand (operands[1], <MODE>mode)) +- return "vmovdqu<ssescalarsize>\t{%1, %0%{%3%}%N2|%0%{%3%}%N2, %1}"; +- else +- return "vmovdqa<ssescalarsize>\t{%1, %0%{%3%}%N2|%0%{%3%}%N2, %1}"; ++ return "vmovdqu<ssescalarsize>\t{%1, %0%{%3%}%N2|%0%{%3%}%N2, %1}"; + } + } + [(set_attr "type" "ssemov") +@@ -1184,17 +1178,11 @@ + { + if (FLOAT_MODE_P (GET_MODE_INNER (<MODE>mode))) + { +- if (misaligned_operand (operands[0], <MODE>mode)) +- return "vmovu<ssemodesuffix>\t{%1, %0%{%2%}|%0%{%2%}, %1}"; +- else +- return "vmova<ssemodesuffix>\t{%1, %0%{%2%}|%0%{%2%}, %1}"; ++ return "vmovu<ssemodesuffix>\t{%1, %0%{%2%}|%0%{%2%}, %1}"; + } + else + { +- if (misaligned_operand (operands[0], <MODE>mode)) +- return "vmovdqu<ssescalarsize>\t{%1, %0%{%2%}|%0%{%2%}, %1}"; +- else +- return "vmovdqa<ssescalarsize>\t{%1, %0%{%2%}|%0%{%2%}, %1}"; ++ return "vmovdqu<ssescalarsize>\t{%1, %0%{%2%}|%0%{%2%}, %1}"; + } + } + [(set_attr "type" "ssemov") +@@ -7806,7 +7794,7 @@ + "TARGET_SSE && !(MEM_P (operands[0]) && MEM_P (operands[1]))" + "@ + %vmovlps\t{%1, %0|%q0, %1} +- %vmovaps\t{%1, %0|%0, %1} ++ %vmovups\t{%1, %0|%0, %1} + %vmovlps\t{%1, %d0|%d0, %q1}" + [(set_attr "type" "ssemov") + (set_attr "prefix" "maybe_vex") +@@ -13997,29 +13985,15 @@ + switch (<MODE>mode) + { + case E_V8DFmode: +- if (misaligned_operand (operands[2], <ssequartermode>mode)) +- return "vmovupd\t{%2, %x0|%x0, %2}"; +- else +- return "vmovapd\t{%2, %x0|%x0, %2}"; ++ return "vmovupd\t{%2, %x0|%x0, %2}"; + case E_V16SFmode: +- if (misaligned_operand (operands[2], <ssequartermode>mode)) +- return "vmovups\t{%2, %x0|%x0, %2}"; +- else +- return "vmovaps\t{%2, %x0|%x0, %2}"; ++ return "vmovups\t{%2, %x0|%x0, %2}"; + case E_V8DImode: +- if (misaligned_operand (operands[2], <ssequartermode>mode)) +- return which_alternative == 2 ? "vmovdqu64\t{%2, %x0|%x0, %2}" ++ return which_alternative == 2 ? "vmovdqu64\t{%2, %x0|%x0, %2}" + : "vmovdqu\t{%2, %x0|%x0, %2}"; +- else +- return which_alternative == 2 ? "vmovdqa64\t{%2, %x0|%x0, %2}" +- : "vmovdqa\t{%2, %x0|%x0, %2}"; + case E_V16SImode: +- if (misaligned_operand (operands[2], <ssequartermode>mode)) +- return which_alternative == 2 ? "vmovdqu32\t{%2, %x0|%x0, %2}" ++ return which_alternative == 2 ? "vmovdqu32\t{%2, %x0|%x0, %2}" + : "vmovdqu\t{%2, %x0|%x0, %2}"; +- else +- return which_alternative == 2 ? "vmovdqa32\t{%2, %x0|%x0, %2}" +- : "vmovdqa\t{%2, %x0|%x0, %2}"; + default: + gcc_unreachable (); + } +@@ -21225,63 +21199,27 @@ + switch (get_attr_mode (insn)) + { + case MODE_V16SF: +- if (misaligned_operand (operands[1], <ssehalfvecmode>mode)) +- return "vmovups\t{%1, %t0|%t0, %1}"; +- else +- return "vmovaps\t{%1, %t0|%t0, %1}"; ++ return "vmovups\t{%1, %t0|%t0, %1}"; + case MODE_V8DF: +- if (misaligned_operand (operands[1], <ssehalfvecmode>mode)) +- return "vmovupd\t{%1, %t0|%t0, %1}"; +- else +- return "vmovapd\t{%1, %t0|%t0, %1}"; ++ return "vmovupd\t{%1, %t0|%t0, %1}"; + case MODE_V8SF: +- if (misaligned_operand (operands[1], <ssehalfvecmode>mode)) +- return "vmovups\t{%1, %x0|%x0, %1}"; +- else +- return "vmovaps\t{%1, %x0|%x0, %1}"; ++ return "vmovups\t{%1, %x0|%x0, %1}"; + case MODE_V4DF: +- if (misaligned_operand (operands[1], <ssehalfvecmode>mode)) +- return "vmovupd\t{%1, %x0|%x0, %1}"; +- else +- return "vmovapd\t{%1, %x0|%x0, %1}"; ++ return "vmovupd\t{%1, %x0|%x0, %1}"; + case MODE_XI: +- if (misaligned_operand (operands[1], <ssehalfvecmode>mode)) +- { +- if (which_alternative == 2) +- return "vmovdqu\t{%1, %t0|%t0, %1}"; +- else if (GET_MODE_SIZE (<ssescalarmode>mode) == 8) +- return "vmovdqu64\t{%1, %t0|%t0, %1}"; +- else +- return "vmovdqu32\t{%1, %t0|%t0, %1}"; +- } ++ if (which_alternative == 2) ++ return "vmovdqu\t{%1, %t0|%t0, %1}"; ++ else if (GET_MODE_SIZE (<ssescalarmode>mode) == 8) ++ return "vmovdqu64\t{%1, %t0|%t0, %1}"; + else +- { +- if (which_alternative == 2) +- return "vmovdqa\t{%1, %t0|%t0, %1}"; +- else if (GET_MODE_SIZE (<ssescalarmode>mode) == 8) +- return "vmovdqa64\t{%1, %t0|%t0, %1}"; +- else +- return "vmovdqa32\t{%1, %t0|%t0, %1}"; +- } ++ return "vmovdqu32\t{%1, %t0|%t0, %1}"; + case MODE_OI: +- if (misaligned_operand (operands[1], <ssehalfvecmode>mode)) +- { +- if (which_alternative == 2) +- return "vmovdqu\t{%1, %x0|%x0, %1}"; +- else if (GET_MODE_SIZE (<ssescalarmode>mode) == 8) +- return "vmovdqu64\t{%1, %x0|%x0, %1}"; +- else +- return "vmovdqu32\t{%1, %x0|%x0, %1}"; +- } ++ if (which_alternative == 2) ++ return "vmovdqu\t{%1, %x0|%x0, %1}"; ++ else if (GET_MODE_SIZE (<ssescalarmode>mode) == 8) ++ return "vmovdqu64\t{%1, %x0|%x0, %1}"; + else +- { +- if (which_alternative == 2) +- return "vmovdqa\t{%1, %x0|%x0, %1}"; +- else if (GET_MODE_SIZE (<ssescalarmode>mode) == 8) +- return "vmovdqa64\t{%1, %x0|%x0, %1}"; +- else +- return "vmovdqa32\t{%1, %x0|%x0, %1}"; +- } ++ return "vmovdqu32\t{%1, %x0|%x0, %1}"; + default: + gcc_unreachable (); + } +--- a/gcc/config/i386/i386.c ++++ b/gcc/config/i386/i386.c +@@ -4981,13 +4981,13 @@ + switch (type) + { + case opcode_int: +- opcode = misaligned_p ? "vmovdqu32" : "vmovdqa32"; ++ opcode = "vmovdqu32"; + break; + case opcode_float: +- opcode = misaligned_p ? "vmovups" : "vmovaps"; ++ opcode = "vmovups"; + break; + case opcode_double: +- opcode = misaligned_p ? "vmovupd" : "vmovapd"; ++ opcode = "vmovupd"; + break; + } + } +@@ -4996,16 +4996,16 @@ + switch (scalar_mode) + { + case E_SFmode: +- opcode = misaligned_p ? "%vmovups" : "%vmovaps"; ++ opcode = "%vmovups"; + break; + case E_DFmode: +- opcode = misaligned_p ? "%vmovupd" : "%vmovapd"; ++ opcode = "%vmovupd"; + break; + case E_TFmode: + if (evex_reg_p) +- opcode = misaligned_p ? "vmovdqu64" : "vmovdqa64"; ++ opcode = "vmovdqu64"; + else +- opcode = misaligned_p ? "%vmovdqu" : "%vmovdqa"; ++ opcode = "%vmovdqu"; + break; + default: + gcc_unreachable (); +@@ -5017,48 +5017,32 @@ + { + case E_QImode: + if (evex_reg_p) +- opcode = (misaligned_p +- ? (TARGET_AVX512BW +- ? "vmovdqu8" +- : "vmovdqu64") +- : "vmovdqa64"); ++ opcode = TARGET_AVX512BW ? "vmovdqu8" : "vmovdqu64"; + else +- opcode = (misaligned_p +- ? (TARGET_AVX512BW +- ? "vmovdqu8" +- : "%vmovdqu") +- : "%vmovdqa"); ++ opcode = TARGET_AVX512BW ? "vmovdqu8" : "%vmovdqu"; + break; + case E_HImode: + if (evex_reg_p) +- opcode = (misaligned_p +- ? (TARGET_AVX512BW +- ? "vmovdqu16" +- : "vmovdqu64") +- : "vmovdqa64"); ++ opcode = TARGET_AVX512BW ? "vmovdqu16" : "vmovdqu64"; + else +- opcode = (misaligned_p +- ? (TARGET_AVX512BW +- ? "vmovdqu16" +- : "%vmovdqu") +- : "%vmovdqa"); ++ opcode = TARGET_AVX512BW ? "vmovdqu16" : "%vmovdqu"; + break; + case E_SImode: + if (evex_reg_p) +- opcode = misaligned_p ? "vmovdqu32" : "vmovdqa32"; ++ opcode = "vmovdqu32"; + else +- opcode = misaligned_p ? "%vmovdqu" : "%vmovdqa"; ++ opcode = "%vmovdqu"; + break; + case E_DImode: + case E_TImode: + case E_OImode: + if (evex_reg_p) +- opcode = misaligned_p ? "vmovdqu64" : "vmovdqa64"; ++ opcode = "vmovdqu64"; + else +- opcode = misaligned_p ? "%vmovdqu" : "%vmovdqa"; ++ opcode = "%vmovdqu"; + break; + case E_XImode: +- opcode = misaligned_p ? "vmovdqu64" : "vmovdqa64"; ++ opcode = "vmovdqu64"; + break; + default: + gcc_unreachable (); diff --git a/contrib/linearize/linearize-data.py b/contrib/linearize/linearize-data.py index 441b5da764..b72c7b0d08 100755 --- a/contrib/linearize/linearize-data.py +++ b/contrib/linearize/linearize-data.py @@ -20,49 +20,9 @@ from collections import namedtuple settings = {} -def hex_switchEndian(s): - """ Switches the endianness of a hex string (in pairs of hex chars) """ - pairList = [s[i:i+2].encode() for i in range(0, len(s), 2)] - return b''.join(pairList[::-1]).decode() - -def uint32(x): - return x & 0xffffffff - -def bytereverse(x): - return uint32(( ((x) << 24) | (((x) << 8) & 0x00ff0000) | - (((x) >> 8) & 0x0000ff00) | ((x) >> 24) )) - -def bufreverse(in_buf): - out_words = [] - for i in range(0, len(in_buf), 4): - word = struct.unpack('@I', in_buf[i:i+4])[0] - out_words.append(struct.pack('@I', bytereverse(word))) - return b''.join(out_words) - -def wordreverse(in_buf): - out_words = [] - for i in range(0, len(in_buf), 4): - out_words.append(in_buf[i:i+4]) - out_words.reverse() - return b''.join(out_words) - -def calc_hdr_hash(blk_hdr): - hash1 = hashlib.sha256() - hash1.update(blk_hdr) - hash1_o = hash1.digest() - - hash2 = hashlib.sha256() - hash2.update(hash1_o) - hash2_o = hash2.digest() - - return hash2_o - def calc_hash_str(blk_hdr): - hash = calc_hdr_hash(blk_hdr) - hash = bufreverse(hash) - hash = wordreverse(hash) - hash_str = hash.hex() - return hash_str + blk_hdr_hash = hashlib.sha256(hashlib.sha256(blk_hdr).digest()).digest() + return blk_hdr_hash[::-1].hex() def get_blk_dt(blk_hdr): members = struct.unpack("<I", blk_hdr[68:68+4]) @@ -74,12 +34,12 @@ def get_blk_dt(blk_hdr): # When getting the list of block hashes, undo any byte reversals. def get_block_hashes(settings): blkindex = [] - f = open(settings['hashlist'], "r", encoding="utf8") - for line in f: - line = line.rstrip() - if settings['rev_hash_bytes'] == 'true': - line = hex_switchEndian(line) - blkindex.append(line) + with open(settings['hashlist'], "r", encoding="utf8") as f: + for line in f: + line = line.rstrip() + if settings['rev_hash_bytes'] == 'true': + line = bytes.fromhex(line)[::-1].hex() + blkindex.append(line) print("Read " + str(len(blkindex)) + " hashes") @@ -289,19 +249,18 @@ if __name__ == '__main__': print("Usage: linearize-data.py CONFIG-FILE") sys.exit(1) - f = open(sys.argv[1], encoding="utf8") - for line in f: - # skip comment lines - m = re.search(r'^\s*#', line) - if m: - continue - - # parse key=value lines - m = re.search(r'^(\w+)\s*=\s*(\S.*)$', line) - if m is None: - continue - settings[m.group(1)] = m.group(2) - f.close() + with open(sys.argv[1], encoding="utf8") as f: + for line in f: + # skip comment lines + m = re.search(r'^\s*#', line) + if m: + continue + + # parse key=value lines + m = re.search(r'^(\w+)\s*=\s*(\S.*)$', line) + if m is None: + continue + settings[m.group(1)] = m.group(2) # Force hash byte format setting to be lowercase to make comparisons easier. # Also place upfront in case any settings need to know about it. diff --git a/contrib/linearize/linearize-hashes.py b/contrib/linearize/linearize-hashes.py index fed6e665b8..5959300e74 100755 --- a/contrib/linearize/linearize-hashes.py +++ b/contrib/linearize/linearize-hashes.py @@ -17,11 +17,6 @@ import os.path settings = {} -def hex_switchEndian(s): - """ Switches the endianness of a hex string (in pairs of hex chars) """ - pairList = [s[i:i+2].encode() for i in range(0, len(s), 2)] - return b''.join(pairList[::-1]).decode() - class BitcoinRPC: def __init__(self, host, port, username, password): authpair = "%s:%s" % (username, password) @@ -85,7 +80,7 @@ def get_block_hashes(settings, max_blocks_per_call=10000): sys.exit(1) assert(resp_obj['id'] == x) # assume replies are in-sequence if settings['rev_hash_bytes'] == 'true': - resp_obj['result'] = hex_switchEndian(resp_obj['result']) + resp_obj['result'] = bytes.fromhex(resp_obj['result'])[::-1].hex() print(resp_obj['result']) height += num_blocks @@ -103,19 +98,18 @@ if __name__ == '__main__': print("Usage: linearize-hashes.py CONFIG-FILE") sys.exit(1) - f = open(sys.argv[1], encoding="utf8") - for line in f: - # skip comment lines - m = re.search(r'^\s*#', line) - if m: - continue - - # parse key=value lines - m = re.search(r'^(\w+)\s*=\s*(\S.*)$', line) - if m is None: - continue - settings[m.group(1)] = m.group(2) - f.close() + with open(sys.argv[1], encoding="utf8") as f: + for line in f: + # skip comment lines + m = re.search(r'^\s*#', line) + if m: + continue + + # parse key=value lines + m = re.search(r'^(\w+)\s*=\s*(\S.*)$', line) + if m is None: + continue + settings[m.group(1)] = m.group(2) if 'host' not in settings: settings['host'] = '127.0.0.1' diff --git a/contrib/macdeploy/README.md b/contrib/macdeploy/README.md index ce69079e29..599a0bfa6c 100644 --- a/contrib/macdeploy/README.md +++ b/contrib/macdeploy/README.md @@ -15,13 +15,16 @@ When complete, it will have produced `Bitcoin-Core.dmg`. A free Apple Developer Account is required to proceed. Our current macOS SDK -(`Xcode-12.2-12B45b-extracted-SDK-with-libcxx-headers.tar.gz`) can be -extracted from +(`Xcode-12.2-12B45b-extracted-SDK-with-libcxx-headers.tar.gz`) +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. -The `sha256sum` of the archive should be `28d352f8c14a43d9b8a082ac6338dc173cb153f964c6e8fb6ba389e5be528bd0`. + +The `sha256sum` of the downloaded XIP archive should be `28d352f8c14a43d9b8a082ac6338dc173cb153f964c6e8fb6ba389e5be528bd0`. After Xcode version 7.x, Apple started shipping the `Xcode.app` in a `.xip` archive. This makes the SDK less-trivial to extract on non-macOS machines. One @@ -55,7 +58,10 @@ previous stage) as the first argument. ./contrib/macdeploy/gen-sdk '/path/to/Xcode.app' ``` +The `sha256sum` of the generated TAR.GZ archive should be `df75d30ecafc429e905134333aeae56ac65fac67cb4182622398fd717df77619`. + ## Deterministic macOS DMG Notes + Working macOS DMGs are created in Linux by combining a recent `clang`, the Apple `binutils` (`ld`, `ar`, etc) and DMG authoring tools. @@ -89,16 +95,7 @@ redistributed. [`xorrisofs`](https://www.gnu.org/software/xorriso/) is used to create the DMG. -`xorrisofs` cannot compress DMGs, so afterwards, the DMG tool from the -`libdmg-hfsplus` project is used to compress it. There are several bugs in this -tool and its maintainer has seemingly abandoned the project. - -The DMG tool has the ability to create DMGs from scratch as well, but this functionality is -broken. Only the compression feature is currently used. Ideally, the creation could be fixed -and `xorrisofs` would no longer be necessary. - -Background images and other features can be added to DMG files by inserting a -`.DS_Store` during creation. +A background image is added to DMG files by inserting a `.DS_Store` during creation. As of OS X 10.9 Mavericks, using an Apple-blessed key to sign binaries is a requirement in order to satisfy the new Gatekeeper requirements. Because this private key cannot be diff --git a/contrib/macdeploy/detached-sig-apply.sh b/contrib/macdeploy/detached-sig-apply.sh deleted file mode 100755 index c7296387eb..0000000000 --- a/contrib/macdeploy/detached-sig-apply.sh +++ /dev/null @@ -1,27 +0,0 @@ -#!/bin/sh -# Copyright (c) 2014-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. - -export LC_ALL=C -set -e - -UNSIGNED="$1" -SIGNATURE="$2" -ROOTDIR=dist -OUTDIR=signed-app -SIGNAPPLE=signapple - -if [ -z "$UNSIGNED" ]; then - echo "usage: $0 <unsigned app> <signature>" - exit 1 -fi - -if [ -z "$SIGNATURE" ]; then - echo "usage: $0 <unsigned app> <signature>" - exit 1 -fi - -${SIGNAPPLE} apply "${UNSIGNED}" "${SIGNATURE}" -mv ${ROOTDIR} ${OUTDIR} -echo "Signed: ${OUTDIR}" diff --git a/contrib/macdeploy/detached-sig-create.sh b/contrib/macdeploy/detached-sig-create.sh index 0d61ceb9df..f393331084 100755 --- a/contrib/macdeploy/detached-sig-create.sh +++ b/contrib/macdeploy/detached-sig-create.sh @@ -8,9 +8,11 @@ set -e ROOTDIR=dist BUNDLE="${ROOTDIR}/Bitcoin-Qt.app" +BINARY="${BUNDLE}/Contents/MacOS/Bitcoin-Qt" SIGNAPPLE=signapple TEMPDIR=sign.temp -OUT=signature-osx.tar.gz +ARCH=$(${SIGNAPPLE} info ${BINARY} | head -n 1 | cut -d " " -f 1) +OUT="signature-osx-${ARCH}.tar.gz" OUTROOT=osx/dist if [ -z "$1" ]; then diff --git a/contrib/macdeploy/gen-sdk b/contrib/macdeploy/gen-sdk index ebef1d2db0..6efaaccb8e 100755 --- a/contrib/macdeploy/gen-sdk +++ b/contrib/macdeploy/gen-sdk @@ -8,6 +8,21 @@ import gzip import os import contextlib +# monkey-patch Python 3.8 and older to fix wrong TAR header handling +# see https://github.com/bitcoin/bitcoin/pull/24534 +# and https://github.com/python/cpython/pull/18080 for more info +if sys.version_info < (3, 9): + _old_create_header = tarfile.TarInfo._create_header + def _create_header(info, format, encoding, errors): + buf = _old_create_header(info, format, encoding, errors) + # replace devmajor/devminor with binary zeroes + buf = buf[:329] + bytes(16) + buf[345:] + # recompute checksum + chksum = tarfile.calc_chksums(buf)[0] + buf = buf[:-364] + bytes("%06o\0" % chksum, "ascii") + buf[-357:] + return buf + tarfile.TarInfo._create_header = staticmethod(_create_header) + @contextlib.contextmanager def cd(path): """Context manager that restores PWD even if an exception was raised.""" @@ -75,14 +90,21 @@ def run(): tarinfo.name = str(pathlib.Path(alt_base_dir, tarinfo.name)) if tarinfo.linkname and tarinfo.linkname.startswith("./"): tarinfo.linkname = str(pathlib.Path(alt_base_dir, tarinfo.linkname)) + # make metadata deterministic + tarinfo.mtime = 0 + tarinfo.uid, tarinfo.uname = 0, '' + tarinfo.gid, tarinfo.gname = 0, '' + # don't use isdir() as there are also executable files present + tarinfo.mode = 0o0755 if tarinfo.mode & 0o0100 else 0o0644 return tarinfo with cd(dir_to_add): + # recursion already adds entries in sorted order tarfp.add(".", recursive=True, filter=change_tarinfo_base) print("Creating output .tar.gz file...") with out_sdktgz_path.open("wb") as fp: with gzip.GzipFile(fileobj=fp, mode='wb', compresslevel=9, mtime=0) as gzf: - with tarfile.open(mode="w", fileobj=gzf) as tarfp: + with tarfile.open(mode="w", fileobj=gzf, format=tarfile.GNU_FORMAT) as tarfp: print("Adding MacOSX SDK {} files...".format(sdk_version)) tarfp_add_with_base_change(tarfp, sdk_dir, out_name) print("Adding libc++ headers...") diff --git a/contrib/macdeploy/macdeployqtplus b/contrib/macdeploy/macdeployqtplus index 0455a137f1..2420539b7c 100755 --- a/contrib/macdeploy/macdeployqtplus +++ b/contrib/macdeploy/macdeployqtplus @@ -211,7 +211,7 @@ def getFrameworks(binaryPath: str, verbose: int) -> List[FrameworkInfo]: return libraries def runInstallNameTool(action: str, *args): - installnametoolbin=os.getenv("INSTALLNAMETOOL", "install_name_tool") + installnametoolbin=os.getenv("INSTALL_NAME_TOOL", "install_name_tool") run([installnametoolbin, "-"+action] + list(args), check=True) def changeInstallName(oldName: str, newName: str, binaryPath: str, verbose: int): @@ -544,6 +544,22 @@ ds.close() if platform.system() == "Darwin": subprocess.check_call(f"codesign --deep --force --sign - {target}", shell=True) +print("+ Installing background.tiff +") + +bg_path = os.path.join('dist', '.background', 'background.tiff') +os.mkdir(os.path.dirname(bg_path)) + +tiff_path = os.path.join('contrib', 'macdeploy', 'background.tiff') +shutil.copy2(tiff_path, bg_path) + +# ------------------------------------------------ + +print("+ Generating symlink for /Applications +") + +os.symlink("/Applications", os.path.join('dist', "Applications")) + +# ------------------------------------------------ + if config.dmg is not None: print("+ Preparing .dmg disk image +") @@ -567,19 +583,6 @@ if config.dmg is not None: print("Attaching temp image...") output = run(["hdiutil", "attach", tempname, "-readwrite"], check=True, universal_newlines=True, stdout=PIPE).stdout - m = re.search(r"/Volumes/(.+$)", output) - disk_root = m.group(0) - - print("+ Applying fancy settings +") - - bg_path = os.path.join(disk_root, ".background", os.path.basename('background.tiff')) - os.mkdir(os.path.dirname(bg_path)) - if verbose: - print('background.tiff', "->", bg_path) - shutil.copy2('contrib/macdeploy/background.tiff', bg_path) - - os.symlink("/Applications", os.path.join(disk_root, "Applications")) - print("+ Finalizing .dmg disk image +") run(["hdiutil", "detach", f"/Volumes/{appname}"], universal_newlines=True) diff --git a/contrib/message-capture/message-capture-parser.py b/contrib/message-capture/message-capture-parser.py index 9988478f1b..eefd22a60e 100755 --- a/contrib/message-capture/message-capture-parser.py +++ b/contrib/message-capture/message-capture-parser.py @@ -79,7 +79,8 @@ 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: + assert 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 2b377f6c01..37c6f5fd7c 100755 --- a/contrib/seeds/makeseeds.py +++ b/contrib/seeds/makeseeds.py @@ -6,22 +6,23 @@ # Generate seeds.txt from Pieter's DNS seeder # +import argparse +import ipaddress import re import sys -import dns.resolver import collections +from typing import List, Dict, Union -NSEEDS=512 - -MAX_SEEDS_PER_ASN=2 +from asmap import ASMap, net_to_prefix -MIN_BLOCKS = 337600 +NSEEDS=512 -# These are hosts that have been observed to be behaving strangely (e.g. -# aggressively connecting to every node). -with open("suspicious_hosts.txt", mode="r", encoding="utf-8") as f: - SUSPICIOUS_HOSTS = {s.strip() for s in f if s.strip()} +MAX_SEEDS_PER_ASN = { + 'ipv4': 2, + 'ipv6': 10, +} +MIN_BLOCKS = 730000 PATTERN_IPV4 = re.compile(r"^((\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})):(\d+)$") PATTERN_IPV6 = re.compile(r"^\[([0-9a-z:]+)\]:(\d+)$") @@ -40,10 +41,14 @@ PATTERN_AGENT = re.compile( r"23.99" r")") -def parseline(line): +def parseline(line: str) -> Union[dict, None]: + """ Parses a line from `seeds_main.txt` into a dictionary of details for that line. + or `None`, if the line could not be parsed. + """ sline = line.split() if len(sline) < 11: - return None + # line too short to be valid, skip it. + return None m = PATTERN_IPV4.match(sline[0]) sortkey = None ip = None @@ -107,98 +112,95 @@ def parseline(line): 'sortkey': sortkey, } -def dedup(ips): - '''deduplicate by address,port''' +def dedup(ips: List[Dict]) -> List[Dict]: + """ Remove duplicates from `ips` where multiple ips share address and port. """ d = {} for ip in ips: d[ip['ip'],ip['port']] = ip return list(d.values()) -def filtermultiport(ips): - '''Filter out hosts with more nodes per IP''' +def filtermultiport(ips: List[Dict]) -> List[Dict]: + """ Filter out hosts with more nodes per IP""" hist = collections.defaultdict(list) for ip in ips: hist[ip['sortkey']].append(ip) return [value[0] for (key,value) in list(hist.items()) if len(value)==1] -def lookup_asn(net, ip): - ''' - Look up the asn for an IP (4 or 6) address by querying cymru.com, 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, max_per_asn, max_per_net): +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. + """ # Sift out ips by type ips_ipv46 = [ip for ip in ips if ip['net'] in ['ipv4', 'ipv6']] ips_onion = [ip for ip in ips if ip['net'] == 'onion'] # Filter IPv46 by ASN, and limit to max_per_net per network result = [] - net_count = collections.defaultdict(int) - asn_count = collections.defaultdict(int) - for ip in ips_ipv46: + net_count: Dict[str, int] = collections.defaultdict(int) + asn_count: Dict[int, int] = collections.defaultdict(int) + + for i, ip in enumerate(ips_ipv46): 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: + 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) result.extend(ips_onion[0:max_per_net]) return result -def ip_stats(ips): - hist = collections.defaultdict(int) +def ip_stats(ips: List[Dict]) -> str: + """ Format and return pretty string from `ips`. """ + hist: Dict[str, int] = collections.defaultdict(int) for ip in ips: if ip is not None: hist[ip['net']] += 1 - return '%6d %6d %6d' % (hist['ipv4'], hist['ipv6'], hist['onion']) + 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('%s Initial' % (ip_stats(ips)), file=sys.stderr) + print(f'{ip_stats(ips):s} Initial', file=sys.stderr) # Skip entries with invalid address. ips = [ip for ip in ips if ip is not None] - print('%s Skip entries with invalid address' % (ip_stats(ips)), file=sys.stderr) + print(f'{ip_stats(ips):s} Skip entries with invalid address', file=sys.stderr) # Skip duplicates (in case multiple seeds files were concatenated) ips = dedup(ips) - print('%s After removing duplicates' % (ip_stats(ips)), file=sys.stderr) - # Skip entries from suspicious hosts. - ips = [ip for ip in ips if ip['ip'] not in SUSPICIOUS_HOSTS] - print('%s Skip entries from suspicious hosts' % (ip_stats(ips)), file=sys.stderr) + print(f'{ip_stats(ips):s} After removing duplicates', file=sys.stderr) # Enforce minimal number of blocks. ips = [ip for ip in ips if ip['blocks'] >= MIN_BLOCKS] - print('%s Enforce minimal number of blocks' % (ip_stats(ips)), file=sys.stderr) + print(f'{ip_stats(ips):s} Enforce minimal number of blocks', file=sys.stderr) # Require service bit 1. ips = [ip for ip in ips if (ip['service'] & 1) == 1] - print('%s Require service bit 1' % (ip_stats(ips)), file=sys.stderr) + print(f'{ip_stats(ips):s} Require service bit 1', file=sys.stderr) # Require at least 50% 30-day uptime for clearnet, 10% for onion. req_uptime = { 'ipv4': 50, @@ -206,25 +208,28 @@ def main(): 'onion': 10, } ips = [ip for ip in ips if ip['uptime'] > req_uptime[ip['net']]] - print('%s Require minimum uptime' % (ip_stats(ips)), file=sys.stderr) + print(f'{ip_stats(ips):s} Require minimum uptime', file=sys.stderr) # Require a known and recent user agent. ips = [ip for ip in ips if PATTERN_AGENT.match(ip['agent'])] - print('%s Require a known and recent user agent' % (ip_stats(ips)), file=sys.stderr) + print(f'{ip_stats(ips):s} Require a known and recent user agent', file=sys.stderr) # Sort by availability (and use last success as tie breaker) ips.sort(key=lambda x: (x['uptime'], x['lastsuccess'], x['ip']), reverse=True) # Filter out hosts with multiple bitcoin ports, these are likely abusive ips = filtermultiport(ips) - print('%s Filter out hosts with multiple bitcoin ports' % (ip_stats(ips)), file=sys.stderr) + 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) - print('%s Look up ASNs and limit results per ASN and per net' % (ip_stats(ips)), file=sys.stderr) + 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/suspicious_hosts.txt b/contrib/seeds/suspicious_hosts.txt deleted file mode 100644 index 13385cc816..0000000000 --- a/contrib/seeds/suspicious_hosts.txt +++ /dev/null @@ -1,16 +0,0 @@ -130.211.129.106 -148.251.238.178 -176.9.46.6 -178.63.107.226 -54.173.72.127 -54.174.10.182 -54.183.64.54 -54.194.231.211 -54.66.214.167 -54.66.220.137 -54.67.33.14 -54.77.251.214 -54.94.195.96 -54.94.200.247 -83.81.130.26 -88.198.17.7
\ No newline at end of file diff --git a/contrib/signet/miner b/contrib/signet/miner index 012bd6cc31..fdcd20ae3b 100755 --- a/contrib/signet/miner +++ b/contrib/signet/miner @@ -4,25 +4,23 @@ # file COPYING or http://www.opensource.org/licenses/mit-license.php. import argparse -import base64 import json import logging import math -import os.path +import os import re import struct 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) @@ -493,10 +404,11 @@ def do_generate(args): logging.debug("Mining block delta=%s start=%s mine=%s", seconds_to_hms(mine_time-bestheader["time"]), mine_time, is_mine) mined_blocks += 1 psbt = generate_psbt(tmpl, reward_spk, blocktime=mine_time) - psbt_signed = json.loads(args.bcli("-stdin", "walletprocesspsbt", input=psbt.encode('utf8'))) + input_stream = os.linesep.join([psbt, "true", "ALL"]).encode('utf8') + psbt_signed = json.loads(args.bcli("-stdin", "walletprocesspsbt", input=input_stream)) if not psbt_signed.get("complete",False): logging.debug("Generated PSBT: %s" % (psbt,)) - sys.stderr.write("PSBT signing failed") + sys.stderr.write("PSBT signing failed\n") return 1 block, signet_solution = do_decode_psbt(psbt_signed["psbt"]) block = finish_block(block, signet_solution, args.grind_cmd) @@ -626,5 +538,3 @@ def main(): if __name__ == "__main__": main() - - diff --git a/contrib/testgen/README.md b/contrib/testgen/README.md index fcc5a378e2..2f0288df16 100644 --- a/contrib/testgen/README.md +++ b/contrib/testgen/README.md @@ -2,7 +2,7 @@ Utilities to generate test vectors for the data-driven Bitcoin tests. -Usage: +To use inside a scripted-diff (or just execute directly): - PYTHONPATH=../../test/functional/test_framework ./gen_key_io_test_vectors.py valid 70 > ../../src/test/data/key_io_valid.json - PYTHONPATH=../../test/functional/test_framework ./gen_key_io_test_vectors.py invalid 70 > ../../src/test/data/key_io_invalid.json + ./gen_key_io_test_vectors.py valid 70 > ../../src/test/data/key_io_valid.json + ./gen_key_io_test_vectors.py invalid 70 > ../../src/test/data/key_io_invalid.json diff --git a/contrib/testgen/base58.py b/contrib/testgen/base58.py deleted file mode 100644 index 87341ccf96..0000000000 --- a/contrib/testgen/base58.py +++ /dev/null @@ -1,115 +0,0 @@ -# Copyright (c) 2012-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. -''' -Bitcoin base58 encoding and decoding. - -Based on https://bitcointalk.org/index.php?topic=1026.0 (public domain) -''' -import hashlib - -# for compatibility with following code... -class SHA256: - new = hashlib.sha256 - -if str != bytes: - # Python 3.x - def ord(c): - return c - def chr(n): - return bytes( (n,) ) - -__b58chars = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz' -__b58base = len(__b58chars) -b58chars = __b58chars - -def b58encode(v): - """ encode v, which is a string of bytes, to base58. - """ - long_value = 0 - for (i, c) in enumerate(v[::-1]): - if isinstance(c, str): - c = ord(c) - long_value += (256**i) * c - - result = '' - while long_value >= __b58base: - div, mod = divmod(long_value, __b58base) - result = __b58chars[mod] + result - long_value = div - result = __b58chars[long_value] + result - - # Bitcoin does a little leading-zero-compression: - # leading 0-bytes in the input become leading-1s - nPad = 0 - for c in v: - if c == 0: - nPad += 1 - else: - break - - return (__b58chars[0]*nPad) + result - -def b58decode(v, length = None): - """ decode v into a string of len bytes - """ - long_value = 0 - for i, c in enumerate(v[::-1]): - pos = __b58chars.find(c) - assert pos != -1 - long_value += pos * (__b58base**i) - - result = bytes() - while long_value >= 256: - div, mod = divmod(long_value, 256) - result = chr(mod) + result - long_value = div - result = chr(long_value) + result - - nPad = 0 - for c in v: - if c == __b58chars[0]: - nPad += 1 - continue - break - - result = bytes(nPad) + result - if length is not None and len(result) != length: - return None - - return result - -def checksum(v): - """Return 32-bit checksum based on SHA256""" - return SHA256.new(SHA256.new(v).digest()).digest()[0:4] - -def b58encode_chk(v): - """b58encode a string, with 32-bit checksum""" - return b58encode(v + checksum(v)) - -def b58decode_chk(v): - """decode a base58 string, check and remove checksum""" - result = b58decode(v) - if result is None: - return None - if result[-4:] == checksum(result[:-4]): - return result[:-4] - else: - return None - -def get_bcaddress_version(strAddress): - """ Returns None if strAddress is invalid. Otherwise returns integer version of address. """ - addr = b58decode_chk(strAddress) - if addr is None or len(addr)!=21: - return None - version = addr[0] - return ord(version) - -if __name__ == '__main__': - # Test case (from http://gitorious.org/bitcoin/python-base58.git) - assert get_bcaddress_version('15VjRaDX9zpbA8LVnbrCAFzrVzN7ixHNsC') == 0 - _ohai = 'o hai'.encode('ascii') - _tmp = b58encode(_ohai) - assert _tmp == 'DYB3oMS' - assert b58decode(_tmp, 5) == _ohai - print("Tests passed") diff --git a/contrib/testgen/gen_key_io_test_vectors.py b/contrib/testgen/gen_key_io_test_vectors.py index eafaaaeceb..7bfb1d76a8 100755 --- a/contrib/testgen/gen_key_io_test_vectors.py +++ b/contrib/testgen/gen_key_io_test_vectors.py @@ -1,21 +1,21 @@ #!/usr/bin/env python3 -# Copyright (c) 2012-2021 The Bitcoin Core developers +# Copyright (c) 2012-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 valid and invalid base58/bech32(m) address and private key test vectors. - -Usage: - PYTHONPATH=../../test/functional/test_framework ./gen_key_io_test_vectors.py valid 70 > ../../src/test/data/key_io_valid.json - PYTHONPATH=../../test/functional/test_framework ./gen_key_io_test_vectors.py invalid 70 > ../../src/test/data/key_io_invalid.json ''' -# 2012 Wladimir J. van der Laan -# Released under MIT License -import os + from itertools import islice -from base58 import b58encode_chk, b58decode_chk, b58chars +import os import random -from segwit_addr import bech32_encode, decode_segwit_address, convertbits, CHARSET, Encoding +import sys + +sys.path.append(os.path.join(os.path.dirname(__file__), '../../test/functional')) + +from test_framework.address import base58_to_byte, byte_to_base58, b58chars # noqa: E402 +from test_framework.script import OP_0, OP_1, OP_2, OP_3, OP_16, OP_DUP, OP_EQUAL, OP_EQUALVERIFY, OP_HASH160, OP_CHECKSIG # noqa: E402 +from test_framework.segwit_addr import bech32_encode, decode_segwit_address, convertbits, CHARSET, Encoding # noqa: E402 # key types PUBKEY_ADDRESS = 0 @@ -29,16 +29,6 @@ PRIVKEY_TEST = 239 PRIVKEY_REGTEST = 239 # script -OP_0 = 0x00 -OP_1 = 0x51 -OP_2 = 0x52 -OP_3 = 0x53 -OP_16 = 0x60 -OP_DUP = 0x76 -OP_EQUAL = 0x87 -OP_EQUALVERIFY = 0x88 -OP_HASH160 = 0xa9 -OP_CHECKSIG = 0xac pubkey_prefix = (OP_DUP, OP_HASH160, 20) pubkey_suffix = (OP_EQUALVERIFY, OP_CHECKSIG) script_prefix = (OP_HASH160, 20) @@ -114,8 +104,10 @@ def is_valid(v): '''Check vector v for validity''' if len(set(v) - set(b58chars)) > 0: return is_valid_bech32(v) - result = b58decode_chk(v) - if result is None: + try: + payload, version = base58_to_byte(v) + result = bytes([version]) + payload + except ValueError: # thrown if checksum doesn't match return is_valid_bech32(v) for template in templates: prefix = bytearray(template[0]) @@ -135,18 +127,19 @@ def is_valid_bech32(v): def gen_valid_base58_vector(template): '''Generate valid base58 vector''' prefix = bytearray(template[0]) - payload = bytearray(os.urandom(template[1])) + payload = rand_bytes(size=template[1]) suffix = bytearray(template[2]) dst_prefix = bytearray(template[4]) dst_suffix = bytearray(template[5]) - rv = b58encode_chk(prefix + payload + suffix) + assert len(prefix) == 1 + rv = byte_to_base58(payload + suffix, prefix[0]) return rv, dst_prefix + payload + dst_suffix def gen_valid_bech32_vector(template): '''Generate valid bech32 vector''' hrp = template[0] witver = template[1] - witprog = bytearray(os.urandom(template[2])) + witprog = rand_bytes(size=template[2]) encoding = template[4] dst_prefix = bytearray(template[5]) rv = bech32_encode(encoding, hrp, [witver] + convertbits(witprog, 8, 5)) @@ -176,21 +169,22 @@ def gen_invalid_base58_vector(template): corrupt_suffix = randbool(0.2) if corrupt_prefix: - prefix = os.urandom(1) + prefix = rand_bytes(size=1) else: prefix = bytearray(template[0]) if randomize_payload_size: - payload = os.urandom(max(int(random.expovariate(0.5)), 50)) + payload = rand_bytes(size=max(int(random.expovariate(0.5)), 50)) else: - payload = os.urandom(template[1]) + payload = rand_bytes(size=template[1]) if corrupt_suffix: - suffix = os.urandom(len(template[2])) + suffix = rand_bytes(size=len(template[2])) else: suffix = bytearray(template[2]) - val = b58encode_chk(prefix + payload + suffix) + assert len(prefix) == 1 + val = byte_to_base58(payload + suffix, prefix[0]) if random.randint(0,10)<1: # line corruption if randbool(): # add random character to end val += random.choice(b58chars) @@ -206,7 +200,7 @@ def gen_invalid_bech32_vector(template): to_upper = randbool(0.1) hrp = template[0] witver = template[1] - witprog = bytearray(os.urandom(template[2])) + witprog = rand_bytes(size=template[2]) encoding = template[3] if no_data: @@ -236,6 +230,9 @@ def randbool(p = 0.5): '''Return True with P(p)''' return random.random() < p +def rand_bytes(*, size): + return bytearray(random.getrandbits(8) for _ in range(size)) + def gen_invalid_vectors(): '''Generate invalid test vectors''' # start with some manual edge-cases @@ -250,9 +247,9 @@ def gen_invalid_vectors(): yield val, if __name__ == '__main__': - import sys import json iters = {'valid':gen_valid_vectors, 'invalid':gen_invalid_vectors} + random.seed(42) try: uiter = iters[sys.argv[1]] except IndexError: diff --git a/contrib/valgrind.supp b/contrib/valgrind.supp index 99ca305fe7..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 @@ -113,16 +92,6 @@ fun:GetCoin } { - Suppress boost warning - Memcheck:Leak - fun:_Znwm - ... - fun:_ZN5boost9unit_test9framework5state17execute_test_treeEmjPKNS2_23random_generator_helperE - fun:_ZN5boost9unit_test9framework3runEmb - fun:_ZN5boost9unit_test14unit_test_mainEPFbvEiPPc - fun:main -} -{ Suppress LogInstance still reachable memory warning Memcheck:Leak match-leak-kinds: reachable diff --git a/contrib/verify-commits/trusted-git-root b/contrib/verify-commits/trusted-git-root index c60f8ab695..1c42195961 100644 --- a/contrib/verify-commits/trusted-git-root +++ b/contrib/verify-commits/trusted-git-root @@ -1 +1 @@ -82bcf405f6db1d55b684a1f63a4aabad376cdad7 +577bd51a4b8de066466a445192c1c653872657e2 diff --git a/contrib/verify-commits/trusted-keys b/contrib/verify-commits/trusted-keys index e83bfd7345..5ca65e7b0d 100644 --- a/contrib/verify-commits/trusted-keys +++ b/contrib/verify-commits/trusted-keys @@ -1,8 +1,6 @@ 71A3B16735405025D447E8F274810B012346C9A6 -133EAC179436F14A5CF1B794860FEB804E669320 -32EE5C4C3FA15CCADB46ABE529D4BCB6416F53EC B8B3F1C0E58C15DB6A81D30C3648A882F4316B9B -CA03882CB1FC067B5D3ACFE4D300116E1C875A3D E777299FC265DD04793070EB944D35F9AC3DB76A D1DBF2C4B96F2DEBF4C16654410108112E7EA81F 152812300785C96444D3334D17565732E08E5E41 +6B002C6EA3F91B1B0DF0C9BC8F617F1200A6D25C diff --git a/contrib/verify-commits/verify-commits.py b/contrib/verify-commits/verify-commits.py index 7e46c6fd47..2ff14c1f86 100755 --- a/contrib/verify-commits/verify-commits.py +++ b/contrib/verify-commits/verify-commits.py @@ -82,11 +82,16 @@ def main(): # get directory of this program and read data files dirname = os.path.dirname(os.path.abspath(__file__)) print("Using verify-commits data from " + dirname) - verified_root = open(dirname + "/trusted-git-root", "r", encoding="utf8").read().splitlines()[0] - verified_sha512_root = open(dirname + "/trusted-sha512-root-commit", "r", encoding="utf8").read().splitlines()[0] - revsig_allowed = open(dirname + "/allow-revsig-commits", "r", encoding="utf-8").read().splitlines() - unclean_merge_allowed = open(dirname + "/allow-unclean-merge-commits", "r", encoding="utf-8").read().splitlines() - incorrect_sha512_allowed = open(dirname + "/allow-incorrect-sha512-commits", "r", encoding="utf-8").read().splitlines() + with open(dirname + "/trusted-git-root", "r", encoding="utf8") as f: + verified_root = f.read().splitlines()[0] + with open(dirname + "/trusted-sha512-root-commit", "r", encoding="utf8") as f: + verified_sha512_root = f.read().splitlines()[0] + with open(dirname + "/allow-revsig-commits", "r", encoding="utf8") as f: + revsig_allowed = f.read().splitlines() + with open(dirname + "/allow-unclean-merge-commits", "r", encoding="utf8") as f: + unclean_merge_allowed = f.read().splitlines() + with open(dirname + "/allow-incorrect-sha512-commits", "r", encoding="utf8") as f: + incorrect_sha512_allowed = f.read().splitlines() # Set commit and branch and set variables current_commit = args.commit diff --git a/contrib/windeploy/win-codesign.cert b/contrib/windeploy/win-codesign.cert index e763df5847..22f17296b6 100644 --- a/contrib/windeploy/win-codesign.cert +++ b/contrib/windeploy/win-codesign.cert @@ -1,89 +1,112 @@ +-----BEGIN CERTIFICATE-----
+MIIHfDCCBWSgAwIBAgIQCmVvdQal72U2QxbUTT3SRTANBgkqhkiG9w0BAQsFADBp
+MQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xQTA/BgNVBAMT
+OERpZ2lDZXJ0IFRydXN0ZWQgRzQgQ29kZSBTaWduaW5nIFJTQTQwOTYgU0hBMzg0
+IDIwMjEgQ0ExMB4XDTIyMDUyNDAwMDAwMFoXDTI0MDUyOTIzNTk1OVowgYAxCzAJ
+BgNVBAYTAlVTMREwDwYDVQQIEwhEZWxhd2FyZTEOMAwGA1UEBxMFTGV3ZXMxJjAk
+BgNVBAoTHUJpdGNvaW4gQ29yZSBDb2RlIFNpZ25pbmcgTExDMSYwJAYDVQQDEx1C
+aXRjb2luIENvcmUgQ29kZSBTaWduaW5nIExMQzCCAiIwDQYJKoZIhvcNAQEBBQAD
+ggIPADCCAgoCggIBALewxfjztuRTDNAGf7zkqqWNEt28CZmVJHoYltVRxtE1BP45
+BfmptH5eM1JC/XosTPytHRFeOkO4YVAtiELxK9S/82OZlKA7Mx7PW6vv1184u8+m
+P3WpTN/KAZTaW9fB0ELTSCuqsvXq2crM2T7NudJnSyWh2VBjLfPPCAcYwzyGKQbl
+jQWjFEJDJWFK83t9mK/v0WQgA3jGJeaz+V6CYXMS7UgpdG8dUhg9o63gYJZAW5pY
+RIsNRcJCM5LHhwEMW5329UsTmYCfP7/53emepbQ0n8ijVZjgaJ+LZ8NspBLSeCiF
+9UPCKX82uWiQAUTbYHCfSi3I0f3wQidXL9ZY+PXmalM7BMuQ+c2xEcl97CnhrDzx
+EBwZvvOC9wGoG+8+epV4TjUZWf+7QN1ZYeg1rai7c7c8u9ILogE8su2xVoz333TH
+CDvScIgnQXmk+cbKMBtg9kM0F+aLWsN2xVf0uAj3U7sdXLrfJeW0DZIktWtTBQzX
+O/OE4Ka+1WFnDg0HJIih0cTjl9YYvfe53L4pCGy+qGt/XGBRqCMfXp3g+H9FGR5r
+pensVVcsrv3GbTfYdlpdmp9OHH5G57GTAZueobCZg7r7RKK0zPU9EiTLJxzyXuai
+v/Ksd8eIhHRjewMaQuAtQM1tO+oKAbLF0v2M7v7/aVT76X32JllYAizm3zjvAgMB
+AAGjggIGMIICAjAfBgNVHSMEGDAWgBRoN+Drtjv4XxGG+/5hewiIZfROQjAdBgNV
+HQ4EFgQUvCpU58PIuofv0kHJ3Ty0YDKEy3cwDgYDVR0PAQH/BAQDAgeAMBMGA1Ud
+JQQMMAoGCCsGAQUFBwMDMIG1BgNVHR8Ega0wgaowU6BRoE+GTWh0dHA6Ly9jcmwz
+LmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFRydXN0ZWRHNENvZGVTaWduaW5nUlNBNDA5
+NlNIQTM4NDIwMjFDQTEuY3JsMFOgUaBPhk1odHRwOi8vY3JsNC5kaWdpY2VydC5j
+b20vRGlnaUNlcnRUcnVzdGVkRzRDb2RlU2lnbmluZ1JTQTQwOTZTSEEzODQyMDIx
+Q0ExLmNybDA+BgNVHSAENzA1MDMGBmeBDAEEATApMCcGCCsGAQUFBwIBFhtodHRw
+Oi8vd3d3LmRpZ2ljZXJ0LmNvbS9DUFMwgZQGCCsGAQUFBwEBBIGHMIGEMCQGCCsG
+AQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20wXAYIKwYBBQUHMAKGUGh0
+dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFRydXN0ZWRHNENvZGVT
+aWduaW5nUlNBNDA5NlNIQTM4NDIwMjFDQTEuY3J0MAwGA1UdEwEB/wQCMAAwDQYJ
+KoZIhvcNAQELBQADggIBABhpTZufRws1vrtI0xB1/UWrSEJxdPHivfpXE708dzum
+Jh3TFzpsEUCQX5BJJet1l7x92sKNeAL7votA+8O8YvMD64Kim7VKA2BB8AOHKQbp
+r1c2iZBwwofInviRYvsrvQta6KBy2KOe1L/l0KnpUazL9Tv4VKvuWAw/Qc0/eTQr
+NZRsmADORxnZ1qW+SpF+/WbazIYjod/Oqb1U3on+PzyiGD3SjzNhsdFRptqzrIaY
+UVV+2XHG4fN6A8wkyQL5NIVXGiK7rqS5VrRAv58Lf1ZZTghdAL+5SySE0OsR9t0K
+W73ZB9pxbuZZ6Zfxjotjw+IilCEm3ADbc7Eb2ijI4x8mix0XWMUrhL34s7/jRyDi
+P+30aSgjWp611tp/EYRW5kpIaFR8AesDdM0DSSCCRXOMwQG2Tq2+CnqItB5oLNPp
+2XySwlIWvmjbzsREfIpE3yh3bxmHY+vFIc2R0nNkbWNIT6AGtaEQ7oWkgpK8YMkA
+QCf4EUC4Qa7qHiH6YSmYJhjApBLC7UDwevgwxuDrwimWAj+tDkzdnENMcBp4SAy6
+LwUuDi2IU6HRSXWdh2YEkDbc3FdwknnnEWaB4dlRL85YjHyLXN0KiE7SKTj1LfR4
+dGeDqVUlDj9D5+X4a7F89wLP/um40/52HUQv5t5WcNr/47r9aVkx9DHs1b8oUnLg
+-----END CERTIFICATE-----
-----BEGIN CERTIFICATE----- -MIIGQzCCBSugAwIBAgIQBSN7Cm16Z0UT9p7lA2jiKDANBgkqhkiG9w0BAQsFADBy +MIIGsDCCBJigAwIBAgIQCK1AsmDSnEyfXs2pvZOu2TANBgkqhkiG9w0BAQwFADBi MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 -d3cuZGlnaWNlcnQuY29tMTEwLwYDVQQDEyhEaWdpQ2VydCBTSEEyIEFzc3VyZWQg -SUQgQ29kZSBTaWduaW5nIENBMB4XDTIxMDUyMTAwMDAwMFoXDTIyMDUyNjIzNTk1 -OVowgYAxCzAJBgNVBAYTAlVTMREwDwYDVQQIEwhEZWxhd2FyZTEOMAwGA1UEBxMF -TGV3ZXMxJjAkBgNVBAoTHUJpdGNvaW4gQ29yZSBDb2RlIFNpZ25pbmcgTExDMSYw -JAYDVQQDEx1CaXRjb2luIENvcmUgQ29kZSBTaWduaW5nIExMQzCCAiIwDQYJKoZI -hvcNAQEBBQADggIPADCCAgoCggIBAKe6xtFgKAQ68MvxwCjNtpgPobfDQCLKvCAN -uBKGYuub6ufQB5dhCLN9fjMgfg33AyauvU3PcEUDUWD3/k925bPqgxHC3E7YqoB+ -11b/2Y7a86okqUgcGgvKhaKoHmXxElpM9EjQHjJ0yL4QAR1Lp+9CMMW3wIulBYKt -wLIArFvbuQhMO/6rxL8frpK049v//WfQzB16GXuFnzN/6fDK7oOt5IrKTg4H6EY2 -fj4+QaUj0lNX7aHnZ6Ki45h2RUPDgN1ipRIuhM67npyZ/tdzPPjI3PUgfXCccN6D -+qWWnbbbvPuOht4ziPciVnPd57PqJmAOnLI86gisDfd7VKlcpOSEaagdUGvMbU6f -uAps818GwnJzwCGllxlKASCgXDAckLLvMuit4RfYAhhdhw5R0AsaWK0HW88oHOqi -U7eWlMCbSGk34x9hBrxYl7tvcNcLPWIPYrrhFWNFpkV8bVVIoV5rUNRgWvBcdOq1 -CCPTfsJp3nEH2WCoBghZquDZLSW12wMw2UsQyEojBeGhrR1inn8uK93wSnVCC8F4 -21yWNRMNe/LQVhmZDgFOen9r/WijBsBdQw1bL8N4zGdYv8+soqkrWzW417FfSx81 -pj4j5FEXYXXV5k/4/eBpIARXVRR8xya0nGkhNJmBk0jjDGD8fPW2gFQbqnUwAQ34 -vOr8NUqHAgMBAAGjggHEMIIBwDAfBgNVHSMEGDAWgBRaxLl7KgqjpepxA8Bg+S32 -ZXUOWDAdBgNVHQ4EFgQUVSLtZnifEHvd8z3E7AyLYNuDiaMwDgYDVR0PAQH/BAQD -AgeAMBMGA1UdJQQMMAoGCCsGAQUFBwMDMHcGA1UdHwRwMG4wNaAzoDGGL2h0dHA6 -Ly9jcmwzLmRpZ2ljZXJ0LmNvbS9zaGEyLWFzc3VyZWQtY3MtZzEuY3JsMDWgM6Ax -hi9odHRwOi8vY3JsNC5kaWdpY2VydC5jb20vc2hhMi1hc3N1cmVkLWNzLWcxLmNy -bDBLBgNVHSAERDBCMDYGCWCGSAGG/WwDATApMCcGCCsGAQUFBwIBFhtodHRwOi8v -d3d3LmRpZ2ljZXJ0LmNvbS9DUFMwCAYGZ4EMAQQBMIGEBggrBgEFBQcBAQR4MHYw -JAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBOBggrBgEFBQcw -AoZCaHR0cDovL2NhY2VydHMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0U0hBMkFzc3Vy -ZWRJRENvZGVTaWduaW5nQ0EuY3J0MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQEL -BQADggEBAOaJneI91NJgqghUxgc0AWQ01SAJTgN4z7xMQ3W0ZAtwGbA0byT7YRlj -j7h+j+hMX/JYkRJETTh8Nalq2tPWJBiMMEPOGFVttFER1pwouHkK9pSKyp4xRvNU -L0LPh7fE4EYMJoynys6ZTpMCHLku+X3jFat1+1moh9TJRvK5+ETZYGl0seFNU3mJ -dZzusObm4scffIGgi40kmmISKd5ZRuooRTu9FFR/3vpfbA+7Vg4RSH3CcQPo9bfk -+h/qRQhSfQInTBn7obRpIlvEcK782qivqseJGdtnTmcdVRShD5ckTVza1yv25uQz -l/yTqmG2LXlYjl5iMSdF0C1xYq6IsOA= +d3cuZGlnaWNlcnQuY29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3Qg +RzQwHhcNMjEwNDI5MDAwMDAwWhcNMzYwNDI4MjM1OTU5WjBpMQswCQYDVQQGEwJV +UzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xQTA/BgNVBAMTOERpZ2lDZXJ0IFRy +dXN0ZWQgRzQgQ29kZSBTaWduaW5nIFJTQTQwOTYgU0hBMzg0IDIwMjEgQ0ExMIIC +IjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA1bQvQtAorXi3XdU5WRuxiEL1 +M4zrPYGXcMW7xIUmMJ+kjmjYXPXrNCQH4UtP03hD9BfXHtr50tVnGlJPDqFX/IiZ +wZHMgQM+TXAkZLON4gh9NH1MgFcSa0OamfLFOx/y78tHWhOmTLMBICXzENOLsvsI +8IrgnQnAZaf6mIBJNYc9URnokCF4RS6hnyzhGMIazMXuk0lwQjKP+8bqHPNlaJGi +TUyCEUhSaN4QvRRXXegYE2XFf7JPhSxIpFaENdb5LpyqABXRN/4aBpTCfMjqGzLm +ysL0p6MDDnSlrzm2q2AS4+jWufcx4dyt5Big2MEjR0ezoQ9uo6ttmAaDG7dqZy3S +vUQakhCBj7A7CdfHmzJawv9qYFSLScGT7eG0XOBv6yb5jNWy+TgQ5urOkfW+0/tv +k2E0XLyTRSiDNipmKF+wc86LJiUGsoPUXPYVGUztYuBeM/Lo6OwKp7ADK5GyNnm+ +960IHnWmZcy740hQ83eRGv7bUKJGyGFYmPV8AhY8gyitOYbs1LcNU9D4R+Z1MI3s +MJN2FKZbS110YU0/EpF23r9Yy3IQKUHw1cVtJnZoEUETWJrcJisB9IlNWdt4z4FK +PkBHX8mBUHOFECMhWWCKZFTBzCEa6DgZfGYczXg4RTCZT/9jT0y7qg0IU0F8WD1H +s/q27IwyCQLMbDwMVhECAwEAAaOCAVkwggFVMBIGA1UdEwEB/wQIMAYBAf8CAQAw +HQYDVR0OBBYEFGg34Ou2O/hfEYb7/mF7CIhl9E5CMB8GA1UdIwQYMBaAFOzX44LS +cV1kTN8uZz/nupiuHA9PMA4GA1UdDwEB/wQEAwIBhjATBgNVHSUEDDAKBggrBgEF +BQcDAzB3BggrBgEFBQcBAQRrMGkwJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRp +Z2ljZXJ0LmNvbTBBBggrBgEFBQcwAoY1aHR0cDovL2NhY2VydHMuZGlnaWNlcnQu +Y29tL0RpZ2lDZXJ0VHJ1c3RlZFJvb3RHNC5jcnQwQwYDVR0fBDwwOjA4oDagNIYy +aHR0cDovL2NybDMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0VHJ1c3RlZFJvb3RHNC5j +cmwwHAYDVR0gBBUwEzAHBgVngQwBAzAIBgZngQwBBAEwDQYJKoZIhvcNAQEMBQAD +ggIBADojRD2NCHbuj7w6mdNW4AIapfhINPMstuZ0ZveUcrEAyq9sMCcTEp6QRJ9L +/Z6jfCbVN7w6XUhtldU/SfQnuxaBRVD9nL22heB2fjdxyyL3WqqQz/WTauPrINHV +UHmImoqKwba9oUgYftzYgBoRGRjNYZmBVvbJ43bnxOQbX0P4PpT/djk9ntSZz0rd +KOtfJqGVWEjVGv7XJz/9kNF2ht0csGBc8w2o7uCJob054ThO2m67Np375SFTWsPK +6Wrxoj7bQ7gzyE84FJKZ9d3OVG3ZXQIUH0AzfAPilbLCIXVzUstG2MQ0HKKlS43N +b3Y3LIU/Gs4m6Ri+kAewQ3+ViCCCcPDMyu/9KTVcH4k4Vfc3iosJocsL6TEa/y4Z +XDlx4b6cpwoG1iZnt5LmTl/eeqxJzy6kdJKt2zyknIYf48FWGysj/4+16oh7cGvm +oLr9Oj9FpsToFpFSi0HASIRLlk2rREDjjfAVKM7t8RhWByovEMQMCGQ8M4+uKIw8 +y4+ICw2/O/TOHnuO77Xry7fwdxPm5yg/rBKupS8ibEH5glwVZsxsDsrFhsP2JjMM +B0ug0wcCampAMEhLNKhRILutG4UI4lkNbcoFUCvqShyepf2gpx8GdOfy1lKQ/a+F +SCH5Vzu0nAPthkX0tGFuv2jiJmCG6sivqf6UHedjGzqGVnhO -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- -MIIFMDCCBBigAwIBAgIQBAkYG1/Vu2Z1U0O1b5VQCDANBgkqhkiG9w0BAQsFADBl +MIIFkDCCA3igAwIBAgIQBZsbV56OITLiOQe9p3d1XDANBgkqhkiG9w0BAQwFADBi MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 -d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJv -b3QgQ0EwHhcNMTMxMDIyMTIwMDAwWhcNMjgxMDIyMTIwMDAwWjByMQswCQYDVQQG -EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNl -cnQuY29tMTEwLwYDVQQDEyhEaWdpQ2VydCBTSEEyIEFzc3VyZWQgSUQgQ29kZSBT -aWduaW5nIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA+NOzHH8O -Ea9ndwfTCzFJGc/Q+0WZsTrbRPV/5aid2zLXcep2nQUut4/6kkPApfmJ1DcZ17aq -8JyGpdglrA55KDp+6dFn08b7KSfH03sjlOSRI5aQd4L5oYQjZhJUM1B0sSgmuyRp -wsJS8hRniolF1C2ho+mILCCVrhxKhwjfDPXiTWAYvqrEsq5wMWYzcT6scKKrzn/p -fMuSoeU7MRzP6vIK5Fe7SrXpdOYr/mzLfnQ5Ng2Q7+S1TqSp6moKq4TzrGdOtcT3 -jNEgJSPrCGQ+UpbB8g8S9MWOD8Gi6CxR93O8vYWxYoNzQYIH5DiLanMg0A9kczye -n6Yzqf0Z3yWT0QIDAQABo4IBzTCCAckwEgYDVR0TAQH/BAgwBgEB/wIBADAOBgNV -HQ8BAf8EBAMCAYYwEwYDVR0lBAwwCgYIKwYBBQUHAwMweQYIKwYBBQUHAQEEbTBr -MCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20wQwYIKwYBBQUH -MAKGN2h0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEFzc3VyZWRJ -RFJvb3RDQS5jcnQwgYEGA1UdHwR6MHgwOqA4oDaGNGh0dHA6Ly9jcmw0LmRpZ2lj -ZXJ0LmNvbS9EaWdpQ2VydEFzc3VyZWRJRFJvb3RDQS5jcmwwOqA4oDaGNGh0dHA6 -Ly9jcmwzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEFzc3VyZWRJRFJvb3RDQS5jcmww -TwYDVR0gBEgwRjA4BgpghkgBhv1sAAIEMCowKAYIKwYBBQUHAgEWHGh0dHBzOi8v -d3d3LmRpZ2ljZXJ0LmNvbS9DUFMwCgYIYIZIAYb9bAMwHQYDVR0OBBYEFFrEuXsq -CqOl6nEDwGD5LfZldQ5YMB8GA1UdIwQYMBaAFEXroq/0ksuCMS1Ri6enIZ3zbcgP -MA0GCSqGSIb3DQEBCwUAA4IBAQA+7A1aJLPzItEVyCx8JSl2qB1dHC06GsTvMGHX -fgtg/cM9D8Svi/3vKt8gVTew4fbRknUPUbRupY5a4l4kgU4QpO4/cY5jDhNLrddf -RHnzNhQGivecRk5c/5CxGwcOkRX7uq+1UcKNJK4kxscnKqEpKBo6cSgCPC6Ro8Al -EeKcFEehemhor5unXCBc2XGxDI+7qPjFEmifz0DLQESlE/DmZAwlCEIysjaKJAL+ -L3J+HNdJRZboWR3p+nRka7LrZkPas7CM1ekN3fYBIM6ZMWM9CBoYs4GbT8aTEAb8 -B4H6i9r5gkn3Ym6hU/oSlBiFLpKR6mhsRDKyZqHnGKSaZFHv +d3cuZGlnaWNlcnQuY29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3Qg +RzQwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1MTIwMDAwWjBiMQswCQYDVQQGEwJV +UzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQu +Y29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3QgRzQwggIiMA0GCSqG +SIb3DQEBAQUAA4ICDwAwggIKAoICAQC/5pBzaN675F1KPDAiMGkz7MKnJS7JIT3y +ithZwuEppz1Yq3aaza57G4QNxDAf8xukOBbrVsaXbR2rsnnyyhHS5F/WBTxSD1If +xp4VpX6+n6lXFllVcq9ok3DCsrp1mWpzMpTREEQQLt+C8weE5nQ7bXHiLQwb7iDV +ySAdYyktzuxeTsiT+CFhmzTrBcZe7FsavOvJz82sNEBfsXpm7nfISKhmV1efVFiO +DCu3T6cw2Vbuyntd463JT17lNecxy9qTXtyOj4DatpGYQJB5w3jHtrHEtWoYOAMQ +jdjUN6QuBX2I9YI+EJFwq1WCQTLX2wRzKm6RAXwhTNS8rhsDdV14Ztk6MUSaM0C/ +CNdaSaTC5qmgZ92kJ7yhTzm1EVgX9yRcRo9k98FpiHaYdj1ZXUJ2h4mXaXpI8OCi +EhtmmnTK3kse5w5jrubU75KSOp493ADkRSWJtppEGSt+wJS00mFt6zPZxd9LBADM +fRyVw4/3IbKyEbe7f/LVjHAsQWCqsWMYRJUadmJ+9oCw++hkpjPRiQfhvbfmQ6QY +uKZ3AeEPlAwhHbJUKSWJbOUOUlFHdL4mrLZBdd56rF+NP8m800ERElvlEFDrMcXK +chYiCd98THU/Y+whX8QgUWtvsauGi0/C1kVfnSD8oR7FwI+isX4KJpn15GkvmB0t +9dmpsh3lGwIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB +hjAdBgNVHQ4EFgQU7NfjgtJxXWRM3y5nP+e6mK4cD08wDQYJKoZIhvcNAQEMBQAD +ggIBALth2X2pbL4XxJEbw6GiAI3jZGgPVs93rnD5/ZpKmbnJeFwMDF/k5hQpVgs2 +SV1EY+CtnJYYZhsjDT156W1r1lT40jzBQ0CuHVD1UvyQO7uYmWlrx8GnqGikJ9yd ++SeuMIW59mdNOj6PWTkiU0TryF0Dyu1Qen1iIQqAyHNm0aAFYF/opbSnr6j3bTWc +fFqK1qI4mfN4i/RN0iAL3gTujJtHgXINwBQy7zBZLq7gcfJW5GqXb5JQbZaNaHqa +sjYUegbyJLkJEVDXCLG4iXqEI2FCKeWjzaIgQdfRnGTZ6iahixTXTBmyUEFxPT9N +cCOGDErcgdLMMpSEDQgJlxxPwO5rIHQw0uA5NBCFIRUBCOhVMt5xSdkoF1BN5r5N +0XWs0Mr7QbhDparTwwVETyw2m+L64kW4I1NsBm9nVX9GtUw/bihaeSbSpKhil9Ie +4u1Ki7wb/UdKDd9nZn6yW0HQO+T0O/QEY+nvwlQAUaCKKsnOeMzV6ocEGLPOr0mI +r/OSmbaz5mEP0oUA51Aa5BuVnRmhuZyxm7EAHu/QD09CbMkKvO5D+jpxpchNJqU1 +/YldvIViHTLSoCtU7ZpXwdv6EM8Zt4tKG48BtieVU+i2iW1bvGjUI+iLUaJW+fCm +gKDWHrO8Dw9TdSmq6hN35N6MgSGtBxBHEa2HPQfRdbzP82Z+ -----END CERTIFICATE----- ------BEGIN CERTIFICATE----- -MIIDtzCCAp+gAwIBAgIQDOfg5RfYRv6P5WD8G/AwOTANBgkqhkiG9w0BAQUFADBl -MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 -d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJv -b3QgQ0EwHhcNMDYxMTEwMDAwMDAwWhcNMzExMTEwMDAwMDAwWjBlMQswCQYDVQQG -EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNl -cnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgQ0EwggEi -MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCtDhXO5EOAXLGH87dg+XESpa7c -JpSIqvTO9SA5KFhgDPiA2qkVlTJhPLWxKISKityfCgyDF3qPkKyK53lTXDGEKvYP -mDI2dsze3Tyoou9q+yHyUmHfnyDXH+Kx2f4YZNISW1/5WBg1vEfNoTb5a3/UsDg+ -wRvDjDPZ2C8Y/igPs6eD1sNuRMBhNZYW/lmci3Zt1/GiSw0r/wty2p5g0I6QNcZ4 -VYcgoc/lbQrISXwxmDNsIumH0DJaoroTghHtORedmTpyoeb6pNnVFzF1roV9Iq4/ -AUaG9ih5yLHa5FcXxH4cDrC0kqZWs72yl+2qp/C3xag/lRbQ/6GW6whfGHdPAgMB -AAGjYzBhMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQW -BBRF66Kv9JLLgjEtUYunpyGd823IDzAfBgNVHSMEGDAWgBRF66Kv9JLLgjEtUYun -pyGd823IDzANBgkqhkiG9w0BAQUFAAOCAQEAog683+Lt8ONyc3pklL/3cmbYMuRC -dWKuh+vy1dneVrOfzM4UKLkNl2BcEkxY5NM9g0lFWJc1aRqoR+pWxnmrEthngYTf -fwk8lOa4JiwgvT2zKIn3X/8i4peEH+ll74fg38FnSbNd67IJKusm7Xi+fT8r87cm -NW1fiQG2SVufAQWbqz0lwcy2f8Lxb4bG+mRo64EtlOtCt/qMHt1i8b5QZ7dsvfPx -H2sMNgcWfzd8qVttevESRmCD1ycEvkvOl77DZypoEd+A5wwzZr8TDRRu838fYxAe -+o0bJW1sj6W3YQGx0qMmoRBxna3iw/nDmVG3KwcIzi7mULKn+gpFL6Lw8g== ------END CERTIFICATE----- - diff --git a/depends/Makefile b/depends/Makefile index 723509c81d..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) @@ -222,6 +226,9 @@ $(host_prefix)/share/config.site : config.site.in $(host_prefix)/.stamp_$(final_ -e 's|@RANLIB@|$(host_RANLIB)|' \ -e 's|@NM@|$(host_NM)|' \ -e 's|@STRIP@|$(host_STRIP)|' \ + -e 's|@OTOOL@|$(host_OTOOL)|' \ + -e 's|@INSTALL_NAME_TOOL@|$(host_INSTALL_NAME_TOOL)|' \ + -e 's|@DSYMUTIL@|$(host_DSYMUTIL)|' \ -e 's|@build_os@|$(build_os)|' \ -e 's|@host_os@|$(host_os)|' \ -e 's|@CFLAGS@|$(strip $(host_CFLAGS) $(host_$(release_type)_CFLAGS))|' \ @@ -239,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 $@ @@ -271,7 +279,7 @@ clean-all: clean @rm -rf $(SOURCES_PATH) x86_64* i686* mips* arm* aarch64* powerpc* riscv32* riscv64* s390x* clean: - @rm -rf $(WORK_PATH) $(BASE_CACHE) $(BUILD) + @rm -rf $(WORK_PATH) $(BASE_CACHE) $(BUILD) *.log install: check-packages $(host_prefix)/share/config.site diff --git a/depends/README.md b/depends/README.md index f7647198c6..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 @@ -113,7 +115,11 @@ The following can be set when running make: `make FOO=bar` - `BUILD_ID_SALT`: Optional salt to use when generating build package ids - `FORCE_USE_SYSTEM_CLANG`: (EXPERTS ONLY) When cross-compiling for macOS, use Clang found in the system's `$PATH` rather than the default prebuilt release of Clang - from llvm.org. Clang 8 or later is required. + from llvm.org. Clang 8 or later is required +- `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/builders/darwin.mk b/depends/builders/darwin.mk index ced01229dd..8ed82b276d 100644 --- a/depends/builders/darwin.mk +++ b/depends/builders/darwin.mk @@ -6,6 +6,7 @@ build_darwin_STRIP:=$(shell xcrun -f strip) build_darwin_OTOOL:=$(shell xcrun -f otool) build_darwin_NM:=$(shell xcrun -f nm) build_darwin_INSTALL_NAME_TOOL:=$(shell xcrun -f install_name_tool) +build_darwin_DSYMUTIL:=$(shell xcrun -f dsymutil) build_darwin_SHA256SUM=shasum -a 256 build_darwin_DOWNLOAD=curl --location --fail --connect-timeout $(DOWNLOAD_CONNECT_TIMEOUT) --retry $(DOWNLOAD_RETRIES) -o @@ -19,6 +20,7 @@ darwin_LIBTOOL:=$(shell xcrun -f libtool) darwin_OTOOL:=$(shell xcrun -f otool) darwin_NM:=$(shell xcrun -f nm) darwin_INSTALL_NAME_TOOL:=$(shell xcrun -f install_name_tool) +darwin_DSYMUTIL:=$(shell xcrun -f dsymutil) darwin_native_binutils= darwin_native_toolchain= diff --git a/depends/builders/default.mk b/depends/builders/default.mk index 0370fb9acb..cc6dec66c2 100644 --- a/depends/builders/default.mk +++ b/depends/builders/default.mk @@ -5,15 +5,13 @@ default_build_TAR = tar default_build_RANLIB = ranlib default_build_STRIP = strip default_build_NM = nm -default_build_OTOOL = otool -default_build_INSTALL_NAME_TOOL = install_name_tool define add_build_tool_func build_$(build_os)_$1 ?= $$(default_build_$1) build_$(build_arch)_$(build_os)_$1 ?= $$(build_$(build_os)_$1) build_$1=$$(build_$(build_arch)_$(build_os)_$1) endef -$(foreach var,CC CXX AR TAR RANLIB NM STRIP SHA256SUM DOWNLOAD OTOOL INSTALL_NAME_TOOL,$(eval $(call add_build_tool_func,$(var)))) +$(foreach var,CC CXX AR TAR RANLIB NM STRIP SHA256SUM DOWNLOAD OTOOL INSTALL_NAME_TOOL DSYMUTIL,$(eval $(call add_build_tool_func,$(var)))) define add_build_flags_func build_$(build_arch)_$(build_os)_$1 += $(build_$(build_os)_$1) build_$1=$$(build_$(build_arch)_$(build_os)_$1) diff --git a/depends/config.site.in b/depends/config.site.in index 95e6ae85cf..8f6849214d 100644 --- a/depends/config.site.in +++ b/depends/config.site.in @@ -78,15 +78,15 @@ if test "@host_os@" = darwin; then BREW=no fi -PATH="${depends_prefix}/native/bin:${PATH}" +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}" @@ -102,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 @@ -115,6 +115,28 @@ if test -n "@NM@"; then ac_cv_path_ac_pt_NM="${NM}" fi +if test -n "@STRIP@"; then + STRIP="@STRIP@" + ac_cv_path_ac_pt_STRIP="${STRIP}" +fi + +if test "@host_os@" = darwin; then + if test -n "@OTOOL@"; then + OTOOL="@OTOOL@" + ac_cv_path_OTOOL="${OTOOL}" + fi + + if test -n "@INSTALL_NAME_TOOL@"; then + 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_DSYMUTIL="${DSYMUTIL}" + fi +fi + if test -n "@debug@"; then enable_reduce_exports=no fi diff --git a/depends/funcs.mk b/depends/funcs.mk index c4ad109c69..a00f380236 100644 --- a/depends/funcs.mk +++ b/depends/funcs.mk @@ -67,6 +67,7 @@ $(1)_cached_checksum:=$(BASE_CACHE)/$(host)/$(1)/$(1)-$($(1)_version)-$($(1)_bui $(1)_patch_dir:=$(base_build_dir)/$(host)/$(1)/$($(1)_version)-$($(1)_build_id)/.patches-$($(1)_build_id) $(1)_prefixbin:=$($($(1)_type)_prefix)/bin/ $(1)_cached:=$(BASE_CACHE)/$(host)/$(1)/$(1)-$($(1)_version)-$($(1)_build_id).tar.gz +$(1)_build_log:=$(BASEDIR)/$(1)-$($(1)_version)-$($(1)_build_id).log $(1)_all_sources=$($(1)_file_name) $($(1)_extra_sources) #stamps @@ -85,7 +86,7 @@ $(1)_download_path_fixed=$(subst :,\:,$$($(1)_download_path)) # The default behavior for tar will try to set ownership when running as uid 0 and may not succeed, --no-same-owner disables this behavior $(1)_fetch_cmds ?= $(call fetch_file,$(1),$(subst \:,:,$$($(1)_download_path_fixed)),$$($(1)_download_file),$($(1)_file_name),$($(1)_sha256_hash)) $(1)_extract_cmds ?= mkdir -p $$($(1)_extract_dir) && echo "$$($(1)_sha256_hash) $$($(1)_source)" > $$($(1)_extract_dir)/.$$($(1)_file_name).hash && $(build_SHA256SUM) -c $$($(1)_extract_dir)/.$$($(1)_file_name).hash && $(build_TAR) --no-same-owner --strip-components=1 -xf $$($(1)_source) -$(1)_preprocess_cmds ?= +$(1)_preprocess_cmds ?= true $(1)_build_cmds ?= $(1)_config_cmds ?= $(1)_stage_cmds ?= @@ -174,7 +175,7 @@ $(1)_cmake=env CC="$$($(1)_cc)" \ CXX="$$($(1)_cxx)" \ CXXFLAGS="$$($(1)_cppflags) $$($(1)_cxxflags)" \ LDFLAGS="$$($(1)_ldflags)" \ - cmake -DCMAKE_INSTALL_PREFIX:PATH="$$($($(1)_type)_prefix)" + cmake -DCMAKE_INSTALL_PREFIX:PATH="$$($($(1)_type)_prefix)" $$($(1)_cmake_opts) ifeq ($($(1)_type),build) $(1)_cmake += -DCMAKE_INSTALL_RPATH:PATH="$$($($(1)_type)_prefix)/lib" else @@ -187,44 +188,48 @@ endif endef define int_add_cmds +ifneq ($(LOG),) +$(1)_logging = >>$$($(1)_build_log) 2>&1 || { if test -f $$($(1)_build_log); then cat $$($(1)_build_log); fi; exit 1; } +endif + $($(1)_fetched): mkdir -p $$(@D) $(SOURCES_PATH) rm -f $$@ touch $$@ - cd $$(@D); $(call $(1)_fetch_cmds,$(1)) + cd $$(@D); $($(1)_fetch_cmds) cd $($(1)_source_dir); $(foreach source,$($(1)_all_sources),$(build_SHA256SUM) $(source) >> $$(@);) touch $$@ $($(1)_extracted): | $($(1)_fetched) echo Extracting $(1)... mkdir -p $$(@D) - cd $$(@D); $(call $(1)_extract_cmds,$(1)) + cd $$(@D); $($(1)_extract_cmds) touch $$@ $($(1)_preprocessed): | $($(1)_extracted) echo Preprocessing $(1)... mkdir -p $$(@D) $($(1)_patch_dir) $(foreach patch,$($(1)_patches),cd $(PATCHES_PATH)/$(1); cp $(patch) $($(1)_patch_dir) ;) - cd $$(@D); $(call $(1)_preprocess_cmds, $(1)) + { cd $$(@D); $($(1)_preprocess_cmds); } $$($(1)_logging) touch $$@ $($(1)_configured): | $($(1)_dependencies) $($(1)_preprocessed) echo Configuring $(1)... rm -rf $(host_prefix); mkdir -p $(host_prefix)/lib; cd $(host_prefix); $(foreach package,$($(1)_all_dependencies), $(build_TAR) --no-same-owner -xf $($(package)_cached); ) mkdir -p $$(@D) - +cd $$(@D); $($(1)_config_env) $(call $(1)_config_cmds, $(1)) + +{ cd $$(@D); $($(1)_config_env) $($(1)_config_cmds); } $$($(1)_logging) touch $$@ $($(1)_built): | $($(1)_configured) echo Building $(1)... mkdir -p $$(@D) - +cd $$(@D); $($(1)_build_env) $(call $(1)_build_cmds, $(1)) + +{ cd $$(@D); $($(1)_build_env) $($(1)_build_cmds); } $$($(1)_logging) touch $$@ $($(1)_staged): | $($(1)_built) echo Staging $(1)... mkdir -p $($(1)_staging_dir)/$(host_prefix) - cd $($(1)_build_dir); $($(1)_stage_env) $(call $(1)_stage_cmds, $(1)) + +{ cd $($(1)_build_dir); $($(1)_stage_env) $($(1)_stage_cmds); } $$($(1)_logging) rm -rf $($(1)_extract_dir) touch $$@ $($(1)_postprocessed): | $($(1)_staged) echo Postprocessing $(1)... - cd $($(1)_staging_prefix_dir); $(call $(1)_postprocess_cmds) + cd $($(1)_staging_prefix_dir); $($(1)_postprocess_cmds) touch $$@ $($(1)_cached): | $($(1)_dependencies) $($(1)_postprocessed) echo Caching $(1)... @@ -233,6 +238,7 @@ $($(1)_cached): | $($(1)_dependencies) $($(1)_postprocessed) rm -rf $$(@D) && mkdir -p $$(@D) mv $$($(1)_staging_dir)/$$(@F) $$(@) rm -rf $($(1)_staging_dir) + if test -f $($(1)_build_log); then mv $($(1)_build_log) $$(@D); fi $($(1)_cached_checksum): $($(1)_cached) cd $$(@D); $(build_SHA256SUM) $$(<F) > $$(@) 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 6bf30b499a..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,11 +35,13 @@ 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 +cctools_TOOLS=AR RANLIB STRIP NM LIBTOOL OTOOL INSTALL_NAME_TOOL DSYMUTIL # Make-only lowercase function lc = $(subst A,a,$(subst B,b,$(subst C,c,$(subst D,d,$(subst E,e,$(subst F,f,$(subst G,g,$(subst H,h,$(subst I,i,$(subst J,j,$(subst K,k,$(subst L,l,$(subst M,m,$(subst N,n,$(subst O,o,$(subst P,p,$(subst Q,q,$(subst R,r,$(subst S,s,$(subst T,t,$(subst U,u,$(subst V,v,$(subst W,w,$(subst X,x,$(subst Y,y,$(subst Z,z,$1)))))))))))))))))))))))))) @@ -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 258619a9d0..7c76331ab4 100644 --- a/depends/hosts/default.mk +++ b/depends/hosts/default.mk @@ -8,9 +8,8 @@ default_host_AR = $(host_toolchain)ar default_host_RANLIB = $(host_toolchain)ranlib default_host_STRIP = $(host_toolchain)strip default_host_LIBTOOL = $(host_toolchain)libtool -default_host_INSTALL_NAME_TOOL = $(host_toolchain)install_name_tool -default_host_OTOOL = $(host_toolchain)otool default_host_NM = $(host_toolchain)nm +default_host_OBJCOPY = $(host_toolchain)objcopy define add_host_tool_func ifneq ($(filter $(origin $1),undefined default),) @@ -35,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,$(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 2f370d2b87..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) @@ -13,4 +23,4 @@ mingw32_debug_CXXFLAGS=$(mingw32_debug_CFLAGS) mingw32_debug_CPPFLAGS=-D_GLIBCXX_DEBUG -D_GLIBCXX_DEBUG_PEDANTIC -mingw_cmake_system=Windows +mingw32_cmake_system=Windows 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 dc8393e04c..d330e94d2e 100644 --- a/depends/hosts/openbsd.mk +++ b/depends/hosts/openbsd.mk @@ -1,11 +1,17 @@ -openbsd_CFLAGS=-pipe -openbsd_CFLAGS_CXXFLAGS=$(openbsd_CFLAGS) +openbsd_CFLAGS=-pipe -std=$(C_STANDARD) +openbsd_CXXFLAGS=-pipe -std=$(CXX_STANDARD) -openbsd_CFLAGS_release_CFLAGS=-O2 -openbsd_CFLAGS_release_CXXFLAGS=$(openbsd_release_CFLAGS) +ifneq ($(LTO),) +openbsd_CFLAGS += -flto +openbsd_CXXFLAGS += -flto +openbsd_LDFLAGS += -flto +endif + +openbsd_release_CFLAGS=-O2 +openbsd_release_CXXFLAGS=$(openbsd_release_CFLAGS) -openbsd_CFLAGS_debug_CFLAGS=-O1 -openbsd_CFLAGS_debug_CXXFLAGS=$(openbsd_debug_CFLAGS) +openbsd_debug_CFLAGS=-O1 +openbsd_debug_CXXFLAGS=$(openbsd_debug_CFLAGS) ifeq (86,$(findstring 86,$(build_arch))) i686_openbsd_CC=clang -m32 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/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 748ed510c1..1efe6220d3 100644 --- a/depends/packages/libevent.mk +++ b/depends/packages/libevent.mk @@ -36,5 +36,6 @@ define $(package)_stage_cmds endef define $(package)_postprocess_cmds - rm lib/*.la + rm lib/*.la && \ + rm include/ev*.h endef diff --git a/depends/packages/libmultiprocess.mk b/depends/packages/libmultiprocess.mk index 40ab3c68ea..9b66207fc5 100644 --- a/depends/packages/libmultiprocess.mk +++ b/depends/packages/libmultiprocess.mk @@ -4,9 +4,19 @@ $(package)_download_path=$(native_$(package)_download_path) $(package)_file_name=$(native_$(package)_file_name) $(package)_sha256_hash=$(native_$(package)_sha256_hash) $(package)_dependencies=native_$(package) capnp +ifneq ($(host),$(build)) +$(package)_dependencies += native_capnp +endif + +define $(package)_set_vars := +ifneq ($(host),$(build)) +$(package)_cmake_opts := -DCAPNP_EXECUTABLE="$$(native_capnp_prefixbin)/capnp" +$(package)_cmake_opts += -DCAPNPC_CXX_EXECUTABLE="$$(native_capnp_prefixbin)/capnpc-c++" +endif +endef define $(package)_config_cmds - $($(package)_cmake) + $($(package)_cmake) . endef define $(package)_build_cmds 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_libdmg-hfsplus.mk b/depends/packages/native_libdmg-hfsplus.mk deleted file mode 100644 index c7c8adef41..0000000000 --- a/depends/packages/native_libdmg-hfsplus.mk +++ /dev/null @@ -1,24 +0,0 @@ -package=native_libdmg-hfsplus -$(package)_version=7ac55ec64c96f7800d9818ce64c79670e7f02b67 -$(package)_download_path=https://github.com/planetbeing/libdmg-hfsplus/archive -$(package)_file_name=$($(package)_version).tar.gz -$(package)_sha256_hash=56fbdc48ec110966342f0ecddd6f8f89202f4143ed2a3336e42bbf88f940850c -$(package)_build_subdir=build -$(package)_patches=remove-libcrypto-dependency.patch - -define $(package)_preprocess_cmds - patch -p1 < $($(package)_patch_dir)/remove-libcrypto-dependency.patch && \ - mkdir build -endef - -define $(package)_config_cmds - $($(package)_cmake) -DCMAKE_C_FLAGS="$$($(1)_cflags) -Wl,--build-id=none" -DCMAKE_SKIP_RPATH="ON" -DCMAKE_EXE_LINKER_FLAGS="-static" -DCMAKE_FIND_LIBRARY_SUFFIXES=".a" .. -endef - -define $(package)_build_cmds - $(MAKE) -C dmg -endef - -define $(package)_stage_cmds - $(MAKE) DESTDIR=$($(package)_staging_dir) -C dmg install -endef diff --git a/depends/packages/native_libmultiprocess.mk b/depends/packages/native_libmultiprocess.mk index 14653ce9fb..6e600c5720 100644 --- a/depends/packages/native_libmultiprocess.mk +++ b/depends/packages/native_libmultiprocess.mk @@ -6,7 +6,7 @@ $(package)_sha256_hash=9f8b055c8bba755dc32fe799b67c20b91e7b13e67cadafbc54c0f1def $(package)_dependencies=native_capnp define $(package)_config_cmds - $($(package)_cmake) + $($(package)_cmake) . endef define $(package)_build_cmds 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/packages.mk b/depends/packages/packages.mk index 991db7f46e..998cc0221c 100644 --- a/depends/packages/packages.mk +++ b/depends/packages/packages.mk @@ -26,7 +26,7 @@ usdt_linux_packages=systemtap darwin_native_packages = native_ds_store native_mac_alias ifneq ($(build_os),darwin) -darwin_native_packages += native_cctools native_libtapi native_libdmg-hfsplus +darwin_native_packages += native_cctools native_libtapi ifeq ($(strip $(FORCE_USE_SYSTEM_CLANG)),) darwin_native_packages+= native_clang diff --git a/depends/packages/qt.mk b/depends/packages/qt.mk index 9cdfd21d2c..e854843dab 100644 --- a/depends/packages/qt.mk +++ b/depends/packages/qt.mk @@ -1,25 +1,33 @@ package=qt -$(package)_version=5.15.2 +$(package)_version=5.15.5 $(package)_download_path=https://download.qt.io/official_releases/qt/5.15/$($(package)_version)/submodules -$(package)_suffix=everywhere-src-$($(package)_version).tar.xz +$(package)_suffix=everywhere-opensource-src-$($(package)_version).tar.xz $(package)_file_name=qtbase-$($(package)_suffix) -$(package)_sha256_hash=909fad2591ee367993a75d7e2ea50ad4db332f05e1c38dd7a5a274e156a4e0f8 +$(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 -$(package)_patches = qt.pro qttools_src.pro -$(package)_patches += fix_qt_pkgconfig.patch mac-qmake.conf fix_no_printer.patch no-xlib.patch -$(package)_patches += dont_use_avx_android_x86_64.patch dont_hardcode_x86_64.patch fix_montery_include.patch -$(package)_patches += fix_android_jni_static.patch dont_hardcode_pwd.patch -$(package)_patches += qtbase-moc-ignore-gcc-macro.patch fix_limits_header.patch -$(package)_patches += fix_bigsur_style.patch use_android_ndk23.patch +$(package)_patches = qt.pro +$(package)_patches += qttools_src.pro +$(package)_patches += mac-qmake.conf +$(package)_patches += fix_qt_pkgconfig.patch +$(package)_patches += no-xlib.patch +$(package)_patches += dont_hardcode_x86_64.patch +$(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 += 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=d5788e86257b21d5323f1efd94376a213e091d1e5e03b45a95dd052b5f570db8 +$(package)_qttranslations_sha256_hash=c92af4171397a0ed272330b4fa0669790fcac8d050b07c8b8cc565ebeba6735e $(package)_qttools_file_name=qttools-$($(package)_suffix) -$(package)_qttools_sha256_hash=c189d0ce1ff7c739db9a3ace52ac3e24cb8fd6dbf234e49f075249b38f43c1cc +$(package)_qttools_sha256_hash=6d0778b71b2742cb527561791d1d3d255366163d54a10f78c683a398f09ffc6c $(package)_extra_sources = $($(package)_qttranslations_file_name) $(package)_extra_sources += $($(package)_qttools_file_name) @@ -123,6 +131,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) @@ -145,18 +156,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 @@ -164,8 +170,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 @@ -215,45 +222,41 @@ 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 && \ patch -p1 -i $($(package)_patch_dir)/dont_hardcode_pwd.patch && \ patch -p1 -i $($(package)_patch_dir)/fix_qt_pkgconfig.patch && \ - patch -p1 -i $($(package)_patch_dir)/fix_no_printer.patch && \ patch -p1 -i $($(package)_patch_dir)/fix_android_jni_static.patch && \ patch -p1 -i $($(package)_patch_dir)/no-xlib.patch && \ - patch -p1 -i $($(package)_patch_dir)/dont_use_avx_android_x86_64.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)/fix_bigsur_style.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/native_libdmg-hfsplus/remove-libcrypto-dependency.patch b/depends/patches/native_libdmg-hfsplus/remove-libcrypto-dependency.patch deleted file mode 100644 index f346c8f2cf..0000000000 --- a/depends/patches/native_libdmg-hfsplus/remove-libcrypto-dependency.patch +++ /dev/null @@ -1,45 +0,0 @@ -From 3e5fd3fb56bc9ff03beb535979e33dcf83fe1f70 Mon Sep 17 00:00:00 2001 -From: Cory Fields <cory-nospam-@coryfields.com> -Date: Thu, 8 May 2014 12:39:42 -0400 -Subject: [PATCH] dmg: remove libcrypto dependency - ---- - dmg/CMakeLists.txt | 16 ---------------- - 1 file changed, 16 deletions(-) - -diff --git a/dmg/CMakeLists.txt b/dmg/CMakeLists.txt -index eec62d6..3969f64 100644 ---- a/dmg/CMakeLists.txt -+++ b/dmg/CMakeLists.txt -@@ -1,12 +1,5 @@ --INCLUDE(FindOpenSSL) - INCLUDE(FindZLIB) - --FIND_LIBRARY(CRYPTO_LIBRARIES crypto -- PATHS -- /usr/lib -- /usr/local/lib -- ) -- - IF(NOT ZLIB_FOUND) - message(FATAL_ERROR "zlib is required for dmg!") - ENDIF(NOT ZLIB_FOUND) -@@ -18,15 +11,6 @@ link_directories(${PROJECT_BINARY_DIR}/common ${PROJECT_BINARY_DIR}/hfs) - - add_library(dmg adc.c base64.c checksum.c dmgfile.c dmglib.c filevault.c io.c partition.c resources.c udif.c) - --IF(OPENSSL_FOUND) -- add_definitions(-DHAVE_CRYPT) -- include_directories(${OPENSSL_INCLUDE_DIR}) -- target_link_libraries(dmg ${CRYPTO_LIBRARIES}) -- IF(WIN32) -- TARGET_LINK_LIBRARIES(dmg gdi32) -- ENDIF(WIN32) --ENDIF(OPENSSL_FOUND) -- - target_link_libraries(dmg common hfs z) - - add_executable(dmg-bin dmg.c) --- -2.22.0 - 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/dont_use_avx_android_x86_64.patch b/depends/patches/qt/dont_use_avx_android_x86_64.patch deleted file mode 100644 index b12ac8f4d0..0000000000 --- a/depends/patches/qt/dont_use_avx_android_x86_64.patch +++ /dev/null @@ -1,32 +0,0 @@ -Android: don't use avx and avx2 when building for Android x86_64 - -AVX and AVX2 are not supported on x86_64 ABI. -See: - - https://developer.android.com/ndk/guides/abis#86-64 - - https://bugreports.qt.io/browse/QTBUG-86785 - -Upstream commits: - - Qt 6.0: ff1a44be33f4bc05d502a2ca49171e0408992f61 - - Qt 5.15: 8b2cc0f6deb038a4c9d4f0d9b690c7726bd809a9 - - ---- old/qtbase/configure.json -+++ new/qtbase/configure.json -@@ -1098,7 +1098,7 @@ - }, - "avx": { - "label": "AVX", -- "condition": "features.sse4_2 && tests.avx", -+ "condition": "features.sse4_2 && tests.avx && (!config.android || !arch.x86_64)", - "output": [ - "privateConfig", - { "type": "define", "name": "QT_COMPILER_SUPPORTS_AVX", "value": 1 } -@@ -1114,7 +1114,7 @@ - }, - "avx2": { - "label": "AVX2", -- "condition": "features.avx && tests.avx2", -+ "condition": "features.avx && tests.avx2 && (!config.android || !arch.x86_64)", - "output": [ - "privateConfig", - "privateFeature", diff --git a/depends/patches/qt/duplicate_lcqpafonts.patch b/depends/patches/qt/duplicate_lcqpafonts.patch new file mode 100644 index 0000000000..c460b51dcf --- /dev/null +++ b/depends/patches/qt/duplicate_lcqpafonts.patch @@ -0,0 +1,104 @@ +QtGui: Fix duplication of logging category lcQpaFonts + +Move it to qplatformfontdatabase.h. + +Upstream commit: + - Qt 6.0: ab01885e48873fb2ad71841a3f1627fe4d9cd835 + +--- a/qtbase/src/gui/text/qplatformfontdatabase.cpp ++++ b/qtbase/src/gui/text/qplatformfontdatabase.cpp +@@ -52,6 +52,8 @@ + + QT_BEGIN_NAMESPACE + ++Q_LOGGING_CATEGORY(lcQpaFonts, "qt.qpa.fonts") ++ + void qt_registerFont(const QString &familyname, const QString &stylename, + const QString &foundryname, int weight, + QFont::Style style, int stretch, bool antialiased, + +--- a/qtbase/src/gui/text/qplatformfontdatabase.h ++++ b/qtbase/src/gui/text/qplatformfontdatabase.h +@@ -50,6 +50,7 @@ + // + + #include <QtGui/qtguiglobal.h> ++#include <QtCore/qloggingcategory.h> + #include <QtCore/QString> + #include <QtCore/QStringList> + #include <QtCore/QList> +@@ -62,6 +63,7 @@ + + QT_BEGIN_NAMESPACE + ++Q_DECLARE_LOGGING_CATEGORY(lcQpaFonts) + + class QWritingSystemsPrivate; + + +--- a/qtbase/src/platformsupport/fontdatabases/mac/qfontengine_coretext.mm ++++ b/qtbase/src/platformsupport/fontdatabases/mac/qfontengine_coretext.mm +@@ -86,8 +86,6 @@ + + QT_BEGIN_NAMESPACE + +-Q_LOGGING_CATEGORY(lcQpaFonts, "qt.qpa.fonts") +- + static float SYNTHETIC_ITALIC_SKEW = std::tan(14.f * std::acos(0.f) / 90.f); + + bool QCoreTextFontEngine::ct_getSfntTable(void *user_data, uint tag, uchar *buffer, uint *length) + +--- a/qtbase/src/platformsupport/fontdatabases/mac/qfontengine_coretext_p.h ++++ b/qtbase/src/platformsupport/fontdatabases/mac/qfontengine_coretext_p.h +@@ -64,8 +64,6 @@ + + QT_BEGIN_NAMESPACE + +-Q_DECLARE_LOGGING_CATEGORY(lcQpaFonts) +- + class QCoreTextFontEngine : public QFontEngine + { + Q_GADGET + +--- a/qtbase/src/platformsupport/fontdatabases/windows/qwindowsfontdatabase.cpp ++++ b/qtbase/src/platformsupport/fontdatabases/windows/qwindowsfontdatabase.cpp +@@ -68,8 +68,6 @@ + + QT_BEGIN_NAMESPACE + +-Q_LOGGING_CATEGORY(lcQpaFonts, "qt.qpa.fonts") +- + #ifndef QT_NO_DIRECTWRITE + // ### fixme: Consider direct linking of dwrite.dll once Windows Vista pre SP2 is dropped (QTBUG-49711) + + +--- a/qtbase/src/platformsupport/fontdatabases/windows/qwindowsfontdatabase_p.h ++++ b/qtbase/src/platformsupport/fontdatabases/windows/qwindowsfontdatabase_p.h +@@ -63,8 +63,6 @@ + + QT_BEGIN_NAMESPACE + +-Q_DECLARE_LOGGING_CATEGORY(lcQpaFonts) +- + class QWindowsFontEngineData + { + Q_DISABLE_COPY_MOVE(QWindowsFontEngineData) + +--- a/qtbase/src/platformsupport/themes/genericunix/qgenericunixthemes.cpp ++++ b/qtbase/src/platformsupport/themes/genericunix/qgenericunixthemes.cpp +@@ -40,6 +40,7 @@ + #include "qgenericunixthemes_p.h" + + #include "qpa/qplatformtheme_p.h" ++#include "qpa/qplatformfontdatabase.h" + + #include <QtGui/QPalette> + #include <QtGui/QFont> +@@ -76,7 +77,6 @@ + QT_BEGIN_NAMESPACE + + Q_DECLARE_LOGGING_CATEGORY(qLcTray) +-Q_LOGGING_CATEGORY(lcQpaFonts, "qt.qpa.fonts") + + ResourceHelper::ResourceHelper() + { 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 bb64661761..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 -@@ -914,6 +914,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_bigsur_style.patch b/depends/patches/qt/fix_bigsur_style.patch deleted file mode 100644 index 0f6c848bb0..0000000000 --- a/depends/patches/qt/fix_bigsur_style.patch +++ /dev/null @@ -1,90 +0,0 @@ -Fix rendering in macOS BigSur - -See: https://bugreports.qt.io/browse/QTBUG-86513. - -Upstream commits (combined in this patch): - - Qt 6.0: 40fb97e97f550b8afd13ecc3a038d9d0c2d82bbb - - Qt 6.0: 3857f104cac127f62e64e55a20613f0ac2e6b843 - - Qt 6.1: abee4cdd5925a8513f51784754fca8fa35031732 - ---- old/qtbase/src/plugins/styles/mac/qmacstyle_mac.mm -+++ new/qtbase/src/plugins/styles/mac/qmacstyle_mac.mm -@@ -3870,6 +3870,7 @@ void QMacStyle::drawControl(ControlElement ce, const QStyleOption *opt, QPainter - const auto cs = d->effectiveAquaSizeConstrain(opt, w); - // Extra hacks to get the proper pressed appreance when not selected or selected and inactive - const bool needsInactiveHack = (!isActive && isSelected); -+ const bool isBigSurOrAbove = QOperatingSystemVersion::current() >= QOperatingSystemVersion::MacOSBigSur; - const auto ct = !needsInactiveHack && (isSelected || tp == QStyleOptionTab::OnlyOneTab) ? - QMacStylePrivate::Button_PushButton : - QMacStylePrivate::Button_PopupButton; -@@ -3878,6 +3879,12 @@ void QMacStyle::drawControl(ControlElement ce, const QStyleOption *opt, QPainter - auto *pb = static_cast<NSButton *>(d->cocoaControl(cw)); - - auto vOffset = isPopupButton ? 1 : 2; -+ if (isBigSurOrAbove) { -+ // Make it 1, otherwise, offset is very visible compared -+ // to selected tab (which is not a popup button). -+ vOffset = 1; -+ } -+ - if (tabDirection == QMacStylePrivate::East) - vOffset -= 1; - const auto outerAdjust = isPopupButton ? 1 : 4; -@@ -3894,9 +3901,22 @@ void QMacStyle::drawControl(ControlElement ce, const QStyleOption *opt, QPainter - frameRect = frameRect.adjusted(-innerAdjust, 0, outerAdjust, 0); - else - frameRect = frameRect.adjusted(-outerAdjust, 0, innerAdjust, 0); -+ -+ if (isSelected && isBigSurOrAbove) { -+ // 1 pixed of 'roundness' is still visible on the right -+ // (the left is OK, it's rounded). -+ frameRect = frameRect.adjusted(0, 0, 1, 0); -+ } -+ - break; - case QStyleOptionTab::Middle: - frameRect = frameRect.adjusted(-innerAdjust, 0, innerAdjust, 0); -+ -+ if (isSelected && isBigSurOrAbove) { -+ // 1 pixel of 'roundness' is still visible on both -+ // sides - left and right. -+ frameRect = frameRect.adjusted(-1, 0, 1, 0); -+ } - break; - case QStyleOptionTab::End: - // Pressed state hack: tweak adjustments in preparation for flip below -@@ -3904,6 +3924,11 @@ void QMacStyle::drawControl(ControlElement ce, const QStyleOption *opt, QPainter - frameRect = frameRect.adjusted(-innerAdjust, 0, outerAdjust, 0); - else - frameRect = frameRect.adjusted(-outerAdjust, 0, innerAdjust, 0); -+ -+ if (isSelected && isBigSurOrAbove) { -+ // 1 pixel of 'roundness' is still visible on the left. -+ frameRect = frameRect.adjusted(-1, 0, 0, 0); -+ } - break; - case QStyleOptionTab::OnlyOneTab: - frameRect = frameRect.adjusted(-outerAdjust, 0, outerAdjust, 0); -@@ -3951,7 +3976,10 @@ void QMacStyle::drawControl(ControlElement ce, const QStyleOption *opt, QPainter - NSPopUpArrowPosition oldPosition = NSPopUpArrowAtCenter; - NSPopUpButtonCell *pbCell = nil; - auto rAdjusted = r; -- if (isPopupButton && tp == QStyleOptionTab::OnlyOneTab) { -+ if (isPopupButton && (tp == QStyleOptionTab::OnlyOneTab || isBigSurOrAbove)) { -+ // Note: starting from macOS BigSur NSPopupButton has this -+ // arrow 'button' in a different place and it became -+ // quite visible 'in between' inactive tabs. - pbCell = static_cast<NSPopUpButtonCell *>(pb.cell); - oldPosition = pbCell.arrowPosition; - pbCell.arrowPosition = NSPopUpNoArrow; -@@ -3959,6 +3987,10 @@ void QMacStyle::drawControl(ControlElement ce, const QStyleOption *opt, QPainter - // NSPopUpButton in this state is smaller. - rAdjusted.origin.x -= 3; - rAdjusted.size.width += 6; -+ if (isBigSurOrAbove) { -+ if (tp == QStyleOptionTab::End) -+ rAdjusted.origin.x -= 2; -+ } - } - } - diff --git a/depends/patches/qt/fix_limits_header.patch b/depends/patches/qt/fix_limits_header.patch deleted file mode 100644 index cb5a8cd1b5..0000000000 --- a/depends/patches/qt/fix_limits_header.patch +++ /dev/null @@ -1,64 +0,0 @@ -Fix compiling with GCC 11 - -See: https://bugreports.qt.io/browse/QTBUG-90395. - -Upstream commits: - - Qt 5.15 -- unavailable as open source - - Qt 6.0: b2af6332ea37e45ab230a7a5d2d278f86d961b83 - - Qt 6.1: 9c56d4da2ff631a8c1c30475bd792f6c86bda53c - ---- old/qtbase/src/corelib/global/qendian.h -+++ new/qtbase/src/corelib/global/qendian.h -@@ -44,6 +44,8 @@ - #include <QtCore/qfloat16.h> - #include <QtCore/qglobal.h> - -+#include <limits> -+ - // include stdlib.h and hope that it defines __GLIBC__ for glibc-based systems - #include <stdlib.h> - #include <string.h> - ---- old/qtbase/src/corelib/global/qfloat16.h -+++ new/qtbase/src/corelib/global/qfloat16.h -@@ -43,6 +43,7 @@ - - #include <QtCore/qglobal.h> - #include <QtCore/qmetatype.h> -+#include <limits> - #include <string.h> - - #if defined(QT_COMPILER_SUPPORTS_F16C) && defined(__AVX2__) && !defined(__F16C__) - ---- old/qtbase/src/tools/moc/generator.cpp -+++ new/qtbase/src/tools/moc/generator.cpp -@@ -40,6 +40,8 @@ - #include <QtCore/qplugin.h> - #include <QtCore/qstringview.h> - -+#include <limits> -+ - #include <math.h> - #include <stdio.h> - ---- 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 - - ---- 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/fix_no_printer.patch b/depends/patches/qt/fix_no_printer.patch deleted file mode 100644 index 1372356138..0000000000 --- a/depends/patches/qt/fix_no_printer.patch +++ /dev/null @@ -1,19 +0,0 @@ ---- x/qtbase/src/plugins/platforms/cocoa/qprintengine_mac_p.h -+++ y/qtbase/src/plugins/platforms/cocoa/qprintengine_mac_p.h -@@ -52,6 +52,7 @@ - // - - #include <QtCore/qglobal.h> -+#include <qpa/qplatformprintdevice.h> - - #ifndef QT_NO_PRINTER - ---- x/qtbase/src/plugins/plugins.pro -+++ y/qtbase/src/plugins/plugins.pro -@@ -9,6 +9,3 @@ qtHaveModule(gui) { - !android:qtConfig(library): SUBDIRS *= generic - } - qtHaveModule(widgets): SUBDIRS += styles -- --!winrt:qtHaveModule(printsupport): \ -- SUBDIRS += printsupport diff --git a/depends/patches/qt/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 e4bfaa1463..cb94bf07b4 100644 --- a/depends/patches/qt/mac-qmake.conf +++ b/depends/patches/qt/mac-qmake.conf @@ -15,10 +15,8 @@ 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 -QMAKE_LIBTOOL=$${CROSS_COMPILE}libtool -QMAKE_INSTALL_NAME_TOOL=$${CROSS_COMPILE}install_name_tool load(qt_config) diff --git a/depends/patches/qt/use_android_ndk23.patch b/depends/patches/qt/use_android_ndk23.patch index 85b8690218..f22367d527 100644 --- a/depends/patches/qt/use_android_ndk23.patch +++ b/depends/patches/qt/use_android_ndk23.patch @@ -2,7 +2,7 @@ Use Android NDK r23 LTS --- old/qtbase/mkspecs/features/android/default_pre.prf +++ new/qtbase/mkspecs/features/android/default_pre.prf -@@ -73,7 +73,7 @@ else: equals(QT_ARCH, x86_64): CROSS_COMPILE = $$NDK_LLVM_PATH/bin/x86_64-linux- +@@ -76,7 +76,7 @@ else: equals(QT_ARCH, x86_64): CROSS_COMPILE = $$NDK_LLVM_PATH/bin/x86_64-linux- else: equals(QT_ARCH, arm64-v8a): CROSS_COMPILE = $$NDK_LLVM_PATH/bin/aarch64-linux-android- else: CROSS_COMPILE = $$NDK_LLVM_PATH/bin/arm-linux-androideabi- 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 c200ac3753..31c95afab0 100644 --- a/doc/README.md +++ b/doc/README.md @@ -64,6 +64,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,8 +72,8 @@ 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) - [Fuzz-testing](fuzzing.md) - [I2P Support](i2p.md) diff --git a/doc/REST-interface.md b/doc/REST-interface.md index 1f0a07a284..4b46f29153 100644 --- a/doc/REST-interface.md +++ b/doc/REST-interface.md @@ -47,18 +47,24 @@ The HTTP request and response are both handled entirely in-memory. With the /notxdetails/ option JSON response will only contain the transaction hash instead of the complete transaction details. The option only affects the JSON response. #### Blockheaders -`GET /rest/headers/<COUNT>/<BLOCK-HASH>.<bin|hex|json>` +`GET /rest/headers/<BLOCK-HASH>.<bin|hex|json>?count=<COUNT=5>` Given a block hash: returns <COUNT> amount of blockheaders in upward direction. Returns empty if the block doesn't exist or it isn't in the active chain. +*Deprecated (but not removed) since 24.0:* +`GET /rest/headers/<COUNT>/<BLOCK-HASH>.<bin|hex|json>` + #### Blockfilter Headers -`GET /rest/blockfilterheaders/<FILTERTYPE>/<COUNT>/<BLOCK-HASH>.<bin|hex|json>` +`GET /rest/blockfilterheaders/<FILTERTYPE>/<BLOCK-HASH>.<bin|hex|json>?count=<COUNT=5>` Given a block hash: returns <COUNT> amount of blockfilter headers in upward direction for the filter type <FILTERTYPE>. Returns empty if the block doesn't exist or it isn't in the active chain. +*Deprecated (but not removed) since 24.0:* +`GET /rest/blockfilterheaders/<FILTERTYPE>/<COUNT>/<BLOCK-HASH>.<bin|hex|json>` + #### Blockfilters `GET /rest/blockfilter/<FILTERTYPE>/<BLOCK-HASH>.<bin|hex|json>` @@ -76,17 +82,7 @@ Given a height: returns hash of block in best-block-chain at height provided. Returns various state info regarding block chain processing. Only supports JSON as output format. -* chain : (string) current network name (main, test, signet, regtest) -* blocks : (numeric) the current number of blocks processed in the server -* headers : (numeric) the current number of headers we have validated -* bestblockhash : (string) the hash of the currently best block -* difficulty : (numeric) the current difficulty -* mediantime : (numeric) the median time of the 11 blocks before the most recent block on the blockchain -* verificationprogress : (numeric) estimate of verification progress [0..1] -* chainwork : (string) total amount of work in active chain, in hexadecimal -* pruned : (boolean) if the blocks are subject to pruning -* pruneheight : (numeric) highest block available -* softforks : (array) status of softforks in progress +Refer to the `getblockchaininfo` RPC help for details. #### Query UTXO set `GET /rest/getutxos/<checkmempool>/<txid>-<n>/<txid>-<n>/.../<txid>-<n>.<bin|hex|json>` @@ -121,14 +117,15 @@ $ curl localhost:18332/rest/getutxos/checkmempool/b2cdfd7b89def827ff8af7cd9bff76 #### Memory pool `GET /rest/mempool/info.json` -Returns various information about the TX mempool. +Returns various information about the transaction mempool. Only supports JSON as output format. -Refer to the `getmempoolinfo` RPC for documentation of the fields. +Refer to the `getmempoolinfo` RPC help for details. `GET /rest/mempool/contents.json` -Returns transactions in the TX mempool. +Returns the transactions in the mempool. Only supports JSON as output format. +Refer to the `getrawmempool` RPC help for details. Risks ------------- diff --git a/doc/bitcoin-conf.md b/doc/bitcoin-conf.md index 8c9035c45b..acd767f234 100644 --- a/doc/bitcoin-conf.md +++ b/doc/bitcoin-conf.md @@ -61,4 +61,12 @@ Windows | `%APPDATA%\Bitcoin\` | `C:\Users\username\AppData\Roaming\Bitcoin\bitc Linux | `$HOME/.bitcoin/` | `/home/username/.bitcoin/bitcoin.conf` macOS | `$HOME/Library/Application Support/Bitcoin/` | `/Users/username/Library/Application Support/Bitcoin/bitcoin.conf` -You can find an example bitcoin.conf file in [share/examples/bitcoin.conf](../share/examples/bitcoin.conf). +An example configuration file can be generated by [contrib/devtools/gen-bitcoin-conf.sh](../contrib/devtools/gen-bitcoin-conf.sh). +Run this script after compiling to generate an up-to-date configuration file. +The output is placed under `share/examples/bitcoin.conf`. +To use the generated configuration file, copy the example file into your data directory and edit it there, like so: + +``` +# example copy command for linux user +cp share/examples/bitcoin.conf ~/.bitcoin +``` diff --git a/doc/build-freebsd.md b/doc/build-freebsd.md index da2ab61c2a..a8e643a2ab 100644 --- a/doc/build-freebsd.md +++ b/doc/build-freebsd.md @@ -1,48 +1,21 @@ # FreeBSD Build Guide -**Updated for FreeBSD [12.2](https://www.freebsd.org/releases/12.2R/announce.html)** +**Updated for FreeBSD [12.3](https://www.freebsd.org/releases/12.3R/announce/)** This guide describes how to build bitcoind, command-line utilities, and GUI on FreeBSD. -## Dependencies - -The following dependencies are **required**: - - Library | Purpose | Description - ----------------------------------------------------------------------|------------|---------------------- - [autoconf](https://svnweb.freebsd.org/ports/head/devel/autoconf/) | Build | Automatically configure software source code - [automake](https://svnweb.freebsd.org/ports/head/devel/automake/) | Build | Generate makefile (requires autoconf) - [libtool](https://svnweb.freebsd.org/ports/head/devel/libtool/) | Build | Shared library support - [pkgconf](https://svnweb.freebsd.org/ports/head/devel/pkgconf/) | Build | Configure compiler and linker flags - [git](https://svnweb.freebsd.org/ports/head/devel/git/) | Clone | Version control system - [gmake](https://svnweb.freebsd.org/ports/head/devel/gmake/) | Compile | Generate executables - [boost-libs](https://svnweb.freebsd.org/ports/head/devel/boost-libs/) | Utility | Library for threading, data structures, etc - [libevent](https://svnweb.freebsd.org/ports/head/devel/libevent/) | Networking | OS independent asynchronous networking - - -The following dependencies are **optional**: - - Library | Purpose | Description - ---------------------------------------------------------------------------|------------------|---------------------- - [db5](https://svnweb.freebsd.org/ports/head/databases/db5/) | Berkeley DB | Wallet storage (only needed when wallet enabled) - [qt5](https://svnweb.freebsd.org/ports/head/devel/qt5/) | GUI | GUI toolkit (only needed when GUI enabled) - [libqrencode](https://svnweb.freebsd.org/ports/head/graphics/libqrencode/) | QR codes in GUI | Generating QR codes (only needed when GUI enabled) - [libzmq4](https://svnweb.freebsd.org/ports/head/net/libzmq4/) | ZMQ notification | Allows generating ZMQ notifications (requires ZMQ version >= 4.0.0) - [sqlite3](https://svnweb.freebsd.org/ports/head/databases/sqlite3/) | SQLite DB | Wallet storage (only needed when wallet enabled) - [python3](https://svnweb.freebsd.org/ports/head/lang/python3/) | Testing | Python Interpreter (only needed when running the test suite) - - See [dependencies.md](dependencies.md) for a complete overview. - ## Preparation ### 1. Install Required Dependencies -Install the required dependencies the usual way you [install software on FreeBSD](https://www.freebsd.org/doc/en/books/handbook/ports.html) - either with `pkg` or via the Ports collection. The example commands below use `pkg` which is usually run as `root` or via `sudo`. If you want to use `sudo`, and you haven't set it up: [use this guide](http://www.freebsdwiki.net/index.php/Sudo%2C_configuring) to setup `sudo` access on FreeBSD. +Run the following as root to install the base dependencies for building. ```bash pkg install autoconf automake boost-libs git gmake libevent libtool pkgconf ``` +See [dependencies.md](dependencies.md) for a complete overview. + ### 2. Clone Bitcoin Repo Now that `git` and all the required dependencies are installed, let's clone the Bitcoin Core repository to a directory. All build scripts and commands will run from this directory. ``` bash @@ -52,21 +25,23 @@ 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. To enable legacy wallets, you must install `db5`. To enable [descriptor wallets](https://github.com/bitcoin/bitcoin/blob/master/doc/descriptors.md), `sqlite3` is required. Skip `db5` if you intend to *exclusively* use descriptor wallets - -###### Legacy Wallet Support -`db5` is required to enable support for legacy wallets. Skip if you don't intend to use legacy wallets - -```bash -pkg install db5 -``` +It is not necessary to build wallet functionality to run either `bitcoind` or `bitcoin-qt`. ###### Descriptor Wallet Support -`sqlite3` is required to enable support for descriptor wallets. Skip if you don't intend to use descriptor wallets. +`sqlite3` is required to support [descriptor wallets](descriptors.md). +Skip if you don't intend to use descriptor wallets. ``` bash pkg install sqlite3 ``` + +###### Legacy Wallet Support +`db5` is only required to support legacy wallets. +Skip if you don't intend to use legacy wallets. + +```bash +pkg install db5 +``` --- #### GUI Dependencies @@ -84,6 +59,14 @@ pkg install libqrencode ``` --- +#### Notifications +###### ZeroMQ + +Bitcoin Core can provide notifications via ZeroMQ. If the package is installed, support will be compiled in. +```bash +pkg install libzmq4 +``` + #### 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: @@ -98,8 +81,17 @@ pkg install python3 ### 1. Configuration There are many ways to configure Bitcoin Core, here are a few common examples: -##### Wallet (BDB + SQlite) Support, No GUI: -This explicitly enables legacy wallet support and disables the GUI. If `sqlite3` is installed, then descriptor wallet support will be built. + +##### Descriptor Wallet and GUI: +This explicitly enables the GUI and disables legacy wallet support, assuming `sqlite` and `qt` are installed. +```bash +./autogen.sh +./configure --without-bdb --with-gui=yes MAKE=gmake +``` + +##### Descriptor & Legacy Wallet. No GUI: +This enables support for both wallet types and disables the GUI, assuming +`sqlite3` and `db5` are both installed. ```bash ./autogen.sh ./configure --with-gui=no --with-incompatible-bdb \ @@ -108,12 +100,6 @@ This explicitly enables legacy wallet support and disables the GUI. If `sqlite3` MAKE=gmake ``` -##### Wallet (only SQlite) and GUI Support: -This explicitly enables the GUI and disables legacy wallet support. If `qt5` is not installed, this will throw an error. If `sqlite3` is installed then descriptor wallet functionality will be built. If `sqlite3` is not installed, then wallet functionality will be disabled. -```bash -./autogen.sh -./configure --without-bdb --with-gui=yes MAKE=gmake -``` ##### No Wallet or GUI ``` bash ./autogen.sh diff --git a/doc/build-netbsd.md b/doc/build-netbsd.md index edabd71611..0f05cdcba7 100644 --- a/doc/build-netbsd.md +++ b/doc/build-netbsd.md @@ -1,81 +1,116 @@ -NetBSD build guide -====================== -(updated for NetBSD 8.0) +# 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 BerkeleyDB +### 2. Clone Bitcoin Repo + +Clone the Bitcoin Core repository to a directory. All build scripts and commands will run from this directory. -BerkeleyDB is only necessary for the wallet functionality. To skip this, pass -`--disable-wallet` to `./configure` and skip to the next section. +```bash +git clone https://github.com/bitcoin/bitcoin.git +``` + +### 3. Install Optional Dependencies + +#### Wallet Dependencies -It is recommended to use Berkeley DB 4.8. You cannot use the BerkeleyDB library -from ports, for the same reason as boost above (g++/libstd++ incompatibility). -If you have to build it yourself, you can use [the installation script included -in contrib/](/contrib/install_db4.sh) like so: +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). ```bash -./contrib/install_db4.sh `pwd` +pkgin install sqlite3 ``` -from the root of the repository. Then set `BDB_PREFIX` for the next section: +###### Legacy Wallet Support + +`db4` is required to enable support for legacy wallets. ```bash -export BDB_PREFIX="$PWD/db4" +pkgin install db4 ``` -### Building Bitcoin Core +#### GUI Dependencies -**Important**: Use `gmake` (the non-GNU `make` will exit with an error). +Bitcoin Core includes a GUI built with the cross-platform Qt Framework. To compile the GUI, we need to install `qt5`. -With wallet: ```bash -./autogen.sh -./configure --with-gui=no CPPFLAGS="-I/usr/pkg/include" \ - LDFLAGS="-L/usr/pkg/lib" \ - BOOST_CPPFLAGS="-I/usr/pkg/include" \ - BOOST_LDFLAGS="-L/usr/pkg/lib" \ - BDB_LIBS="-L${BDB_PREFIX}/lib -ldb_cxx-4.8" \ - BDB_CFLAGS="-I${BDB_PREFIX}/include" \ - MAKE=gmake +pkgin install qt5 +``` + +The GUI can encode addresses in a QR Code. To build in QR support for the GUI, install `qrencode`. + +```bash +pkgin install qrencode ``` -Without wallet: +#### 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 +pkgin install python37 +``` + +### 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" \ - BOOST_LDFLAGS="-L/usr/pkg/lib" \ 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-openbsd.md b/doc/build-openbsd.md index 275b7ce124..afbb5c8e75 100644 --- a/doc/build-openbsd.md +++ b/doc/build-openbsd.md @@ -1,123 +1,130 @@ -OpenBSD build guide -====================== -(updated for OpenBSD 6.9) +# OpenBSD Build Guide -This guide describes how to build bitcoind, bitcoin-qt, and command-line utilities on OpenBSD. +**Updated for OpenBSD [7.1](https://www.openbsd.org/71.html)** -Preparation -------------- +This guide describes how to build bitcoind, command-line utilities, and GUI on OpenBSD. -Run the following as root to install the base dependencies for building: +## Preparation + +### 1. Install Required Dependencies +Run the following as root to install the base dependencies for building. ```bash -pkg_add git gmake libevent libtool boost -pkg_add qt5 # (optional for enabling the GUI) -pkg_add autoconf # (select highest version, e.g. 2.69) -pkg_add automake # (select highest version, e.g. 1.16) -pkg_add python # (select highest version, e.g. 3.8) -pkg_add bash +pkg_add bash git gmake libevent libtool boost +# Select the newest version of the following packages: +pkg_add autoconf automake python +``` +See [dependencies.md](dependencies.md) for a complete overview. + +### 2. Clone Bitcoin Repo +Clone the Bitcoin Core repository to a directory. All build scripts and commands will run from this directory. +``` bash git clone https://github.com/bitcoin/bitcoin.git ``` -See [dependencies.md](dependencies.md) for a complete overview. +### 3. Install Optional Dependencies -**Important**: From OpenBSD 6.2 onwards a C++11-supporting clang compiler is -part of the base image, and while building it is necessary to make sure that -this compiler is used and not ancient g++ 4.2.1. This is done by appending -`CC=cc CXX=c++` to configuration commands. Mixing different compilers within -the same executable will result in errors. +#### Wallet Dependencies -### Building BerkeleyDB +It is not necessary to build wallet functionality to run either `bitcoind` or `bitcoin-qt`. + +###### Descriptor Wallet Support + +`sqlite3` is required to support [descriptor wallets](descriptors.md). + +``` bash +pkg_add sqlite3 +``` -BerkeleyDB is only necessary for the wallet functionality. To skip this, pass -`--disable-wallet` to `./configure` and skip to the next section. +###### Legacy Wallet Support +BerkeleyDB is only required to support legacy wallets. It is recommended to use Berkeley DB 4.8. You cannot use the BerkeleyDB library -from ports, for the same reason as boost above (g++/libstd++ incompatibility). -If you have to build it yourself, you can use [the installation script included -in contrib/](/contrib/install_db4.sh) like so: +from ports. However you can build it yourself, [using the installation script included in contrib/](/contrib/install_db4.sh), like so, from the root of the repository. ```bash -./contrib/install_db4.sh `pwd` CC=cc CXX=c++ +./contrib/install_db4.sh `pwd` ``` -from the root of the repository. Then set `BDB_PREFIX` for the next section: +Then set `BDB_PREFIX`: ```bash export BDB_PREFIX="$PWD/db4" ``` -### Building Bitcoin Core +#### GUI Dependencies +###### Qt5 + +Bitcoin Core includes a GUI built with the cross-platform Qt Framework. To compile the GUI, Qt 5 is required. + +```bash +pkg_add qt5 +``` + +## Building Bitcoin Core **Important**: Use `gmake` (the non-GNU `make` will exit with an error). Preparation: ```bash -# Replace this with the autoconf version that you installed. Include only -# the major and minor parts of the version: use "2.69" for "autoconf-2.69p2". -export AUTOCONF_VERSION=2.69 - -# Replace this with the automake version that you installed. Include only -# the major and minor parts of the version: use "1.16" for "automake-1.16.1". +# Adapt the following for the version you installed (major.minor only): +export AUTOCONF_VERSION=2.71 export AUTOMAKE_VERSION=1.16 ./autogen.sh ``` -Make sure `BDB_PREFIX` is set to the appropriate path from the above steps. -Note that building with external signer support currently fails on OpenBSD, -hence you have to explicitly disable it by passing the parameter -`--disable-external-signer` to the configure script. -(Background: the feature requires the header-only library boost::process, which -is available on OpenBSD 6.9 via Boost 1.72.0, but contains certain system calls -and preprocessor defines like `waitid()` and `WEXITED` that are not available.) +### 1. Configuration -To configure with wallet: -```bash -./configure --with-gui=no --disable-external-signer CC=cc CXX=c++ \ - BDB_LIBS="-L${BDB_PREFIX}/lib -ldb_cxx-4.8" \ - BDB_CFLAGS="-I${BDB_PREFIX}/include" \ - MAKE=gmake -``` +Note that external signer support is currently not available on OpenBSD, since +the used header-only library Boost.Process fails to compile (certain system +calls and preprocessor defines like `waitid()` and `WEXITED` are missing). + +There are many ways to configure Bitcoin Core, here are a few common examples: + +##### Descriptor Wallet and GUI: +This enables the GUI and descriptor wallet support, assuming `sqlite` and `qt5` are installed. -To configure without wallet: ```bash -./configure --disable-wallet --with-gui=no --disable-external-signer CC=cc CXX=c++ MAKE=gmake +./configure MAKE=gmake ``` -To configure with GUI: +##### Descriptor & Legacy Wallet. No GUI: +This enables support for both wallet types and disables the GUI: + ```bash -./configure --with-gui=yes --disable-external-signer CC=cc CXX=c++ \ +./configure --with-gui=no \ BDB_LIBS="-L${BDB_PREFIX}/lib -ldb_cxx-4.8" \ BDB_CFLAGS="-I${BDB_PREFIX}/include" \ MAKE=gmake ``` -Build and run the tests: +### 2. Compile +**Important**: Use `gmake` (the non-GNU `make` will exit with an error). + ```bash -gmake # use "-j N" here for N parallel jobs -gmake check +gmake # use "-j N" for N parallel jobs +gmake check # Run tests if Python 3 is available ``` -Resource limits -------------------- +## Resource limits If the build runs into out-of-memory errors, the instructions in this section might help. The standard ulimit restrictions in OpenBSD are very strict: - - data(kbytes) 1572864 +```bash +data(kbytes) 1572864 +``` This is, unfortunately, in some cases not enough to compile some `.cpp` files in the project, (see issue [#6658](https://github.com/bitcoin/bitcoin/issues/6658)). If your user is in the `staff` group the limit can be raised with: - - ulimit -d 3000000 - +```bash +ulimit -d 3000000 +``` The change will only affect the current shell and processes spawned by it. To make the change system-wide, change `datasize-cur` and `datasize-max` in `/etc/login.conf`, and reboot. - diff --git a/doc/build-osx.md b/doc/build-osx.md index 16dc224aed..fdf0a9d414 100644 --- a/doc/build-osx.md +++ b/doc/build-osx.md @@ -4,42 +4,6 @@ This guide describes how to build bitcoind, command-line utilities, and GUI on macOS -**Note:** The following is for Intel Macs only! - -## Dependencies - -The following dependencies are **required**: - -Library | Purpose | Description ------------------------------------------------------------|------------|---------------------- -[automake](https://formulae.brew.sh/formula/automake) | Build | Generate makefile -[libtool](https://formulae.brew.sh/formula/libtool) | Build | Shared library support -[pkg-config](https://formulae.brew.sh/formula/pkg-config) | Build | Configure compiler and linker flags -[boost](https://formulae.brew.sh/formula/boost) | Utility | Library for threading, data structures, etc -[libevent](https://formulae.brew.sh/formula/libevent) | Networking | OS independent asynchronous networking - -The following dependencies are **optional**: - -Library | Purpose | Description ---------------------------------------------------------------- |------------------|---------------------- -[berkeley-db@4](https://formulae.brew.sh/formula/berkeley-db@4) | Berkeley DB | Wallet storage (only needed when wallet enabled) -[qt@5](https://formulae.brew.sh/formula/qt@5) | GUI | GUI toolkit (only needed when GUI enabled) -[qrencode](https://formulae.brew.sh/formula/qrencode) | QR codes in GUI | Generating QR codes (only needed when GUI enabled) -[zeromq](https://formulae.brew.sh/formula/zeromq) | ZMQ notification | Allows generating ZMQ notifications (requires ZMQ version >= 4.0.0) -[sqlite](https://formulae.brew.sh/formula/sqlite) | SQLite DB | Wallet storage (only needed when wallet enabled) -[miniupnpc](https://formulae.brew.sh/formula/miniupnpc) | UPnP Support | Firewall-jumping support (needed for port mapping support) -[libnatpmp](https://formulae.brew.sh/formula/libnatpmp) | NAT-PMP Support | Firewall-jumping support (needed for port mapping support) -[python3](https://formulae.brew.sh/formula/python@3.9) | Testing | Python Interpreter (only needed when running the test suite) - -The following dependencies are **optional** packages required for deploying: - -Library | Purpose | Description -----------------------------------------------------|------------------|---------------------- -[ds_store](https://pypi.org/project/ds-store/) | Deploy Dependency| Examine and modify .DS_Store files -[mac_alias](https://pypi.org/project/mac-alias/) | Deploy Dependency| Generate/Read binary alias and bookmark records - -See [dependencies.md](dependencies.md) for a complete overview. - ## Preparation The commands in this guide should be executed in a Terminal application. @@ -78,6 +42,9 @@ Note: If you run into issues while installing Homebrew or pulling packages, refe The first step is to download the required dependencies. These dependencies represent the packages required to get a barebones installation up and running. + +See [dependencies.md](dependencies.md) for a complete overview. + To install, run the following from your terminal: ``` bash @@ -99,29 +66,21 @@ git clone https://github.com/bitcoin/bitcoin.git #### Wallet Dependencies It is not necessary to build wallet functionality to run `bitcoind` or `bitcoin-qt`. -To enable legacy wallets, you must install `berkeley-db@4`. -To enable [descriptor wallets](https://github.com/bitcoin/bitcoin/blob/master/doc/descriptors.md), `sqlite` is required. -Skip `berkeley-db@4` if you intend to *exclusively* use descriptor wallets. - -###### Legacy Wallet Support -`berkeley-db@4` is required to enable support for legacy wallets. -Skip if you don't intend to use legacy wallets. +###### Descriptor Wallet Support -``` bash -brew install berkeley-db@4 -``` +`sqlite` is required to support for descriptor wallets. -###### Descriptor Wallet Support +macOS ships with a useable `sqlite` package, meaning you don't need to +install anything. -Note: Apple has included a useable `sqlite` package since macOS 10.14. -You may not need to install this package. +###### Legacy Wallet Support -`sqlite` is required to enable support for descriptor wallets. -Skip if you don't intend to use descriptor wallets. +`berkeley-db@4` is only required to support for legacy wallets. +Skip if you don't intend to use legacy wallets. ``` bash -brew install sqlite +brew install berkeley-db@4 ``` --- diff --git a/doc/build-unix.md b/doc/build-unix.md index 15fe63d047..874015707a 100644 --- a/doc/build-unix.md +++ b/doc/build-unix.md @@ -26,30 +26,7 @@ make install # optional This will build bitcoin-qt as well, if the dependencies are met. -Dependencies ---------------------- - -These dependencies are required: - - Library | Purpose | Description - ------------|------------------|---------------------- - libboost | Utility | Library for threading, data structures, etc - libevent | Networking | OS independent asynchronous networking - -Optional dependencies: - - Library | Purpose | Description - ------------|------------------|---------------------- - miniupnpc | UPnP Support | Firewall-jumping support - libnatpmp | NAT-PMP Support | Firewall-jumping support - libdb4.8 | Berkeley DB | Wallet storage (only needed when legacy wallet enabled) - qt | GUI | GUI toolkit (only needed when GUI enabled) - libqrencode | QR codes in GUI | QR code generation (only needed when GUI enabled) - libzmq3 | ZMQ notification | ZMQ notifications (requires ZMQ version >= 4.0.0) - sqlite3 | SQLite DB | Wallet storage (only needed when descriptor wallet enabled) - systemtap | Tracing (USDT) | Statically defined tracepoints - -For the versions used, see [dependencies.md](dependencies.md) +See [dependencies.md](dependencies.md) for a complete overview. Memory Requirements -------------------- @@ -232,7 +209,7 @@ from the root of the repository. Otherwise, you can build Bitcoin Core from self-compiled [depends](/depends/README.md). -**Note**: You only need Berkeley DB if the wallet is enabled (see [*Disable-wallet mode*](#disable-wallet-mode)). +**Note**: You only need Berkeley DB if the legacy wallet is enabled (see [*Disable-wallet mode*](#disable-wallet-mode)). Security -------- @@ -282,12 +259,12 @@ Hardening enables the following features: Disable-wallet mode -------------------- -When the intention is to run only a P2P node without a wallet, Bitcoin Core may be compiled in -disable-wallet mode with: +When the intention is to only run a P2P node, without a wallet, Bitcoin Core can +be compiled in disable-wallet mode with: ./configure --disable-wallet -In this case there is no dependency on Berkeley DB 4.8 and SQLite. +In this case there is no dependency on SQLite or Berkeley DB. Mining is also possible in disable-wallet mode using the `getblocktemplate` RPC call. @@ -300,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/cjdns.md b/doc/cjdns.md new file mode 100644 index 0000000000..b69564729f --- /dev/null +++ b/doc/cjdns.md @@ -0,0 +1,116 @@ +# CJDNS support in Bitcoin Core + +It is possible to run Bitcoin Core over CJDNS, an encrypted IPv6 network that +uses public-key cryptography for address allocation and a distributed hash table +for routing. + +## What is CJDNS? + +CJDNS is like a distributed, shared VPN with multiple entry points where every +participant can reach any other participant. All participants use addresses from +the `fc00::/8` network (reserved IPv6 range). Installation and configuration is +done outside of Bitcoin Core, similarly to a VPN (either in the host/OS or on +the network router). See https://github.com/cjdelisle/cjdns#readme and +https://github.com/hyperboria/docs#hyperboriadocs for more information. + +Compared to IPv4/IPv6, CJDNS provides end-to-end encryption and protects nodes +from traffic analysis and filtering. + +Used with Tor and I2P, CJDNS is a complementary option that can enhance network +redundancy and robustness for both the Bitcoin network and individual nodes. + +Each network has different characteristics. For instance, Tor is widely used but +somewhat centralized. I2P connections have a source address and I2P is slow. +CJDNS is fast but does not hide the sender and the recipient from intermediate +routers. + +## Installing CJDNS and finding a peer to connect to the network + +To install and set up CJDNS, follow the instructions at +https://github.com/cjdelisle/cjdns#how-to-install-cjdns. + +You need to initiate an outbound connection to a peer on the CJDNS network +before it will work with your Bitcoin Core node. This is described in steps +["2. Find a friend"](https://github.com/cjdelisle/cjdns#2-find-a-friend) and +["3. Connect your node to your friend's +node"](https://github.com/cjdelisle/cjdns#3-connect-your-node-to-your-friends-node) +in the CJDNS documentation. + +One quick way to accomplish these two steps is to query for available public +peers on [Hyperboria](https://github.com/hyperboria) by running the following: + +``` +git clone https://github.com/hyperboria/peers hyperboria-peers +cd hyperboria-peers +./testAvailable.py +``` + +For each peer, the `./testAvailable.py` script prints the filename of the peer's +credentials followed by the ping result. + +Choose one or several peers, copy their credentials from their respective files, +paste them into the relevant IPv4 or IPv6 "connectTo" JSON object in the +`cjdroute.conf` file you created in step ["1. Generate a new configuration +file"](https://github.com/cjdelisle/cjdns#1-generate-a-new-configuration-file), +and save the file. + +## Launching CJDNS + +Typically, CJDNS might be launched from its directory with +`sudo ./cjdroute < cjdroute.conf` and it sheds permissions after setting up the +[TUN](https://en.wikipedia.org/wiki/TUN/TAP) interface. You may also [launch it as an +unprivileged user](https://github.com/cjdelisle/cjdns/blob/master/doc/non-root-user.md) +with some additional setup. + +The network connection can be checked by running `./tools/peerStats` from the +CJDNS directory. + +## Run Bitcoin Core with CJDNS + +Once you are connected to the CJDNS network, the following Bitcoin Core +configuration option makes CJDNS peers automatically reachable: + +``` +-cjdnsreachable +``` + +When enabled, this option tells Bitcoin Core that it is running in an +environment where a connection to an `fc00::/8` address will be to the CJDNS +network instead of to an [RFC4193](https://datatracker.ietf.org/doc/html/rfc4193) +IPv6 local network. This helps Bitcoin Core perform better address management: + - Your node can consider incoming `fc00::/8` connections to be from the CJDNS + network rather than from an IPv6 private one. + - If one of your node's local addresses is `fc00::/8`, then it can choose to + gossip that address to peers. + +## Additional configuration options related to CJDNS + +``` +-onlynet=cjdns +``` + +Make automatic outbound connections only to CJDNS addresses. Inbound and manual +connections are not affected by this option. It can be specified multiple times +to allow multiple networks, e.g. onlynet=cjdns, onlynet=i2p, onlynet=onion. + +CJDNS support was added to Bitcoin Core in version 23.0 and there may be fewer +CJDNS peers than Tor or IP ones. You can use `bitcoin-cli -addrinfo` to see the +number of CJDNS addresses known to your node. + +In general, a node can be run with both an onion service and CJDNS (or any/all +of IPv4/IPv6/onion/I2P/CJDNS), which can provide a potential fallback if one of +the networks has issues. There are a number of ways to configure this; see +[doc/tor.md](https://github.com/bitcoin/bitcoin/blob/master/doc/tor.md) for +details. + +## CJDNS-related information in Bitcoin Core + +There are several ways to see your CJDNS address in Bitcoin Core: +- in the "Local addresses" output of CLI `-netinfo` +- in the "localaddresses" output of RPC `getnetworkinfo` + +To see which CJDNS peers your node is connected to, use `bitcoin-cli -netinfo 4` +or the `getpeerinfo` RPC (i.e. `bitcoin-cli getpeerinfo`). + +To see which CJDNS addresses your node knows, use the `getnodeaddresses 0 cjdns` +RPC. diff --git a/doc/dependencies.md b/doc/dependencies.md index 99ff26f457..8b8259ab0a 100644 --- a/doc/dependencies.md +++ b/doc/dependencies.md @@ -1,48 +1,50 @@ -Dependencies -============ - -These are the dependencies currently used by Bitcoin Core. You can find instructions for installing them in the `build-*.md` file for your platform. - -| Dependency | Version used | Minimum required | CVEs | Shared | [Bundled Qt library](https://doc.qt.io/qt-5/configure-options.html#third-party-libraries) | -| --- | --- | --- | --- | --- | --- | -| Berkeley DB | [4.8.30](https://www.oracle.com/technetwork/database/database-technologies/berkeleydb/downloads/index.html) | 4.8.x | No | | | -| Boost | [1.77.0](https://www.boost.org/users/download/) | [1.64.0](https://github.com/bitcoin/bitcoin/pull/22320) | No | | | -| Clang<sup>[ \* ](#note1)</sup> | | [7.0](https://releases.llvm.org/download.html) (C++17 & std::filesystem support) | | | | -| Fontconfig | [2.12.6](https://www.freedesktop.org/software/fontconfig/release/) | | No | Yes | | -| FreeType | [2.11.0](https://download.savannah.gnu.org/releases/freetype) | | No | | [Yes](https://github.com/bitcoin/bitcoin/blob/master/depends/packages/qt.mk) (Android only) | -| GCC | | [8.1](https://gcc.gnu.org/) (C++17 & std::filesystem support) | | | | -| glibc | | [2.18](https://www.gnu.org/software/libc/) | | | | | -| HarfBuzz-NG | | | | | [Yes](https://github.com/bitcoin/bitcoin/blob/master/depends/packages/qt.mk) | -| libevent | [2.1.12-stable](https://github.com/libevent/libevent/releases) | [2.0.21](https://github.com/bitcoin/bitcoin/pull/18676) | No | | | -| libnatpmp | git commit [4536032...](https://github.com/miniupnp/libnatpmp/tree/4536032ae32268a45c073a4d5e91bbab4534773a) | | No | | | -| libpng | | | | | [Yes](https://github.com/bitcoin/bitcoin/blob/master/depends/packages/qt.mk) | -| MiniUPnPc | [2.2.2](https://miniupnp.tuxfamily.org/files) | | No | | | -| PCRE | | | | | [Yes](https://github.com/bitcoin/bitcoin/blob/master/depends/packages/qt.mk) | -| Python (tests) | | [3.6](https://www.python.org/downloads) | | | | -| qrencode | [3.4.4](https://fukuchi.org/works/qrencode) | | No | | | -| Qt | [5.15.2](https://download.qt.io/official_releases/qt/) | [5.9.5](https://github.com/bitcoin/bitcoin/issues/20104) | No | | | -| SQLite | [3.32.1](https://sqlite.org/download.html) | [3.7.17](https://github.com/bitcoin/bitcoin/pull/19077) | | | | -| XCB | | | | | [Yes](https://github.com/bitcoin/bitcoin/blob/master/depends/packages/qt.mk) (Linux only) | -| systemtap ([tracing](tracing.md))| [4.5](https://sourceware.org/systemtap/ftp/releases/) | | | | | -| xkbcommon | | | | | [Yes](https://github.com/bitcoin/bitcoin/blob/master/depends/packages/qt.mk) (Linux only) | -| ZeroMQ | [4.3.1](https://github.com/zeromq/libzmq/releases) | 4.0.0 | No | | | -| zlib | | | | | [Yes](https://github.com/bitcoin/bitcoin/blob/master/depends/packages/qt.mk) | - -<a name="note1">Note \*</a> : When compiling with `-stdlib=libc++`, the minimum supported libc++ version is 7.0. - -Controlling dependencies ------------------------- -Some dependencies are not needed in all configurations. The following are some factors that affect the dependency list. - -#### Options passed to `./configure` -* MiniUPnPc is not needed with `--without-miniupnpc`. -* libnatpmp is not needed with `--without-natpmp`. -* Berkeley DB is not needed with `--disable-wallet` or `--without-bdb`. -* SQLite is not needed with `--disable-wallet` or `--without-sqlite`. -* Qt is not needed with `--without-gui`. -* If the qrencode dependency is absent, QR support won't be added. To force an error when that happens, pass `--with-qrencode`. -* If the systemtap dependency is absent, USDT support won't compiled in. -* ZeroMQ is needed only with the `--with-zmq` option. - -#### Other -* Not-Qt-bundled zlib is required to build the [DMG tool](../contrib/macdeploy/README.md#deterministic-macos-dmg-notes) from the libdmg-hfsplus project. +# Dependencies + +These are the dependencies used by Bitcoin Core. +You can find installation instructions in the `build-*.md` file for your platform. +"Runtime" and "Version Used" are both in reference to the release binaries. + +| Dependency | Minimum required | +| --- | --- | +| [Autoconf](https://www.gnu.org/software/autoconf/) | [2.69](https://github.com/bitcoin/bitcoin/pull/17769) | +| [Automake](https://www.gnu.org/software/automake/) | [1.13](https://github.com/bitcoin/bitcoin/pull/18290) | +| [Clang](https://clang.llvm.org) | [8.0](https://github.com/bitcoin/bitcoin/pull/24164) | +| [GCC](https://gcc.gnu.org) | [8.1](https://github.com/bitcoin/bitcoin/pull/23060) | +| [Python](https://www.python.org) (tests) | [3.6](https://github.com/bitcoin/bitcoin/pull/19504) | +| [systemtap](https://sourceware.org/systemtap/) ([tracing](tracing.md))| N/A | + +## Required + +| 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 | +| [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 | + +## Optional + +### GUI +| Dependency | Releases | Version used | Minimum required | Runtime | +| --- | --- | --- | --- | --- | +| [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.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 | +| [MiniUPnPc](../depends/packages/miniupnpc.mk) | [link](https://miniupnp.tuxfamily.org/) | [2.2.2](https://github.com/bitcoin/bitcoin/pull/20421) | 1.9 | No | + +### Notifications +| Dependency | Releases | Version used | Minimum required | Runtime | +| --- | --- | --- | --- | --- | +| [ZeroMQ](../depends/packages/zeromq.mk) | [link](https://github.com/zeromq/libzmq/releases) | [4.3.4](https://github.com/bitcoin/bitcoin/pull/23956) | 4.0.0 | No | + +### Wallet +| Dependency | Releases | Version used | Minimum required | Runtime | +| --- | --- | --- | --- | --- | +| [Berkeley DB](../depends/packages/bdb.mk) (legacy wallet) | [link](https://www.oracle.com/technetwork/database/database-technologies/berkeleydb/downloads/index.html) | 4.8.30 | 4.8.x | No | +| [SQLite](../depends/packages/sqlite.mk) | [link](https://sqlite.org) | [3.32.1](https://github.com/bitcoin/bitcoin/pull/19077) | [3.7.17](https://github.com/bitcoin/bitcoin/pull/19077) | No | diff --git a/doc/descriptors.md b/doc/descriptors.md index 318d065fdb..60cc36db07 100644 --- a/doc/descriptors.md +++ b/doc/descriptors.md @@ -33,6 +33,7 @@ Output descriptors currently support: - Pay-to-taproot outputs (P2TR), through the `tr` function. - Multisig scripts, through the `multi` function. - Multisig scripts where the public keys are sorted lexicographically, through the `sortedmulti` function. +- Multisig scripts inside taproot script trees, through the `multi_a` (and `sortedmulti_a`) function. - Any type of supported address through the `addr` function. - Raw hex scripts through the `raw` function. - Public keys (compressed and uncompressed) in hex notation, or BIP32 extended pubkeys with derivation paths. @@ -56,6 +57,7 @@ Output descriptors currently support: - `wsh(multi(1,xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/1/0/*,xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/0/0/*))` describes a set of *1-of-2* P2WSH multisig outputs where the first multisig key is the *1/0/`i`* child of the first specified xpub and the second multisig key is the *0/0/`i`* child of the second specified xpub, and `i` is any number in a configurable range (`0-1000` by default). - `wsh(sortedmulti(1,xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/1/0/*,xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/0/0/*))` describes a set of *1-of-2* P2WSH multisig outputs where one multisig key is the *1/0/`i`* child of the first specified xpub and the other multisig key is the *0/0/`i`* child of the second specified xpub, and `i` is any number in a configurable range (`0-1000` by default). The order of public keys in the resulting witnessScripts is determined by the lexicographic order of the public keys at that index. - `tr(c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5,{pk(fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556),pk(e493dbf1c10d80f3581e4904930b1404cc6c13900ee0758474fa94abe8c4cd13)})` describes a P2TR output with the `c6...` x-only pubkey as internal key, and two script paths. +- `tr(c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5,sortedmulti_a(2,2f8bde4d1a07209355b4a7250a5c5128e88b84bddc619ab7cba8d569b240efe4,5cbdf0646e5db4eaa398f365f2ea7a0e3d419b7e0330e39ce92bddedcac4f9bc))` describes a P2TR output with the `c6...` x-only pubkey as internal key, and a single `multi_a` script that needs 2 signatures with 2 specified x-only keys, which will be sorted lexicographically. ## Reference @@ -68,11 +70,14 @@ Descriptors consist of several types of expressions. The top level expression is - `pkh(KEY)` (not inside `tr`): P2PKH output for the given public key (use `addr` if you only know the pubkey hash). - `wpkh(KEY)` (top level or inside `sh` only): P2WPKH output for the given compressed pubkey. - `combo(KEY)` (top level only): an alias for the collection of `pk(KEY)` and `pkh(KEY)`. If the key is compressed, it also includes `wpkh(KEY)` and `sh(wpkh(KEY))`. -- `multi(k,KEY_1,KEY_2,...,KEY_n)` (not inside `tr`): k-of-n multisig script. +- `multi(k,KEY_1,KEY_2,...,KEY_n)` (not inside `tr`): k-of-n multisig script using OP_CHECKMULTISIG. - `sortedmulti(k,KEY_1,KEY_2,...,KEY_n)` (not inside `tr`): k-of-n multisig script with keys sorted lexicographically in the resulting script. +- `multi_a(k,KEY_1,KEY_2,...,KEY_N)` (only inside `tr`): k-of-n multisig script using OP_CHECKSIG, OP_CHECKSIGADD, and OP_NUMEQUAL. +- `sortedmulti_a(k,KEY_1,KEY_2,...,KEY_N)` (only inside `tr`): similar to `multi_a`, but the (x-only) public keys in it will be sorted lexicographically. - `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: @@ -83,7 +88,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..2726cf779b 100644 --- a/doc/assumeutxo.md +++ b/doc/design/assumeutxo.md 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 bfb64093e1..1aa4e4d690 100644 --- a/doc/developer-notes.md +++ b/doc/developer-notes.md @@ -17,6 +17,7 @@ Developer Notes - [`debug.log`](#debuglog) - [Signet, testnet, and regtest modes](#signet-testnet-and-regtest-modes) - [DEBUG_LOCKORDER](#debug_lockorder) + - [DEBUG_LOCKCONTENTION](#debug_lockcontention) - [Valgrind suppressions file](#valgrind-suppressions-file) - [Compiling for test coverage](#compiling-for-test-coverage) - [Performance profiling with perf](#performance-profiling-with-perf) @@ -31,6 +32,7 @@ Developer Notes - [C++ data structures](#c-data-structures) - [Strings and formatting](#strings-and-formatting) - [Shadowing](#shadowing) + - [Lifetimebound](#lifetimebound) - [Threads and synchronization](#threads-and-synchronization) - [Scripts](#scripts) - [Shebang](#shebang) @@ -95,7 +97,10 @@ code. Guidelines](https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Renum-caps), which recommend using `snake_case`. Please use what seems appropriate. - Class names, function names, and method names are UpperCamelCase - (PascalCase). Do not prefix class names with `C`. + (PascalCase). Do not prefix class names with `C`. See [Internal interface + naming style](#internal-interface-naming-style) for an exception to this + convention. + - Test suite naming convention: The Boost test suite in file `src/test/foo_tests.cpp` should be named `foo_tests`. Test suite names must be unique. @@ -105,6 +110,28 @@ code. - `nullptr` is preferred over `NULL` or `(void*)0`. - `static_assert` is preferred over `assert` where possible. Generally; compile-time checking is preferred over run-time checking. +For function calls a namespace should be specified explicitly, unless such functions have been declared within it. +Otherwise, [argument-dependent lookup](https://en.cppreference.com/w/cpp/language/adl), also known as ADL, could be +triggered that makes code harder to maintain and reason about: +```c++ +#include <filesystem> + +namespace fs { +class path : public std::filesystem::path +{ +}; +// The intention is to disallow this function. +bool exists(const fs::path& p) = delete; +} // namespace fs + +int main() +{ + //fs::path p; // error + std::filesystem::path p; // compiled + exists(p); // ADL being used for unqualified name lookup +} +``` + Block style example: ```c++ int g_count = 0; @@ -137,11 +164,78 @@ public: } // namespace foo ``` +Coding Style (C++ functions and methods) +-------------------- + +- When ordering function parameters, place input parameters first, then any + in-out parameters, followed by any output parameters. + +- *Rationale*: API consistency. + +- Prefer returning values directly to using in-out or output parameters. Use + `std::optional` where helpful for returning values. + +- *Rationale*: Less error-prone (no need for assumptions about what the output + is initialized to on failure), easier to read, and often the same or better + performance. + +- Generally, use `std::optional` to represent optional by-value inputs (and + instead of a magic default value, if there is no real default). Non-optional + input parameters should usually be values or const references, while + non-optional in-out and output parameters should usually be references, as + they cannot be null. + +Coding Style (C++ named arguments) +------------------------------ + +When passing named arguments, use a format that clang-tidy understands. The +argument names can otherwise not be verified by clang-tidy. + +For example: + +```c++ +void function(Addrman& addrman, bool clear); + +int main() +{ + function(g_addrman, /*clear=*/false); +} +``` + +### Running clang-tidy + +To run clang-tidy on Ubuntu/Debian, install the dependencies: + +```sh +apt install clang-tidy bear clang +``` + +Then, pass clang as compiler to configure, and use bear to produce the `compile_commands.json`: + +```sh +./autogen.sh && ./configure CC=clang CXX=clang++ +make clean && bear make -j $(nproc) # For bear 2.x +make clean && bear -- make -j $(nproc) # For bear 3.x +``` + +To run clang-tidy on all source files: + +```sh +( cd ./src/ && run-clang-tidy -j $(nproc) ) +``` + +To run clang-tidy on the changed source lines: + +```sh +git diff | ( cd ./src/ && clang-tidy-diff -p2 -j $(nproc) ) +``` + Coding Style (Python) --------------------- Refer to [/test/functional/README.md#style-guidelines](/test/functional/README.md#style-guidelines). + Coding Style (Doxygen-compatible comments) ------------------------------------------ @@ -316,6 +410,21 @@ configure option adds `-DDEBUG_LOCKORDER` to the compiler flags. This inserts run-time checks to keep track of which locks are held and adds warnings to the `debug.log` file if inconsistencies are detected. +### DEBUG_LOCKCONTENTION + +Defining `DEBUG_LOCKCONTENTION` adds a "lock" logging category to the logging +RPC that, when enabled, logs the location and duration of each lock contention +to the `debug.log` file. + +The `--enable-debug` configure option adds `-DDEBUG_LOCKCONTENTION` to the +compiler flags. You may also enable it manually for a non-debug build by running +configure with `-DDEBUG_LOCKCONTENTION` added to your CPPFLAGS, +i.e. `CPPFLAGS="-DDEBUG_LOCKCONTENTION"`, then build and run bitcoind. + +You can then use the `-debug=lock` configuration option at bitcoind startup or +`bitcoin-cli logging '["lock"]'` at runtime to turn on lock contention logging. +It can be toggled off again with `bitcoin-cli logging [] '["lock"]'`. + ### Assertions and Checks The util file `src/util/check.h` offers helpers to protect against coding and @@ -494,10 +603,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) @@ -513,7 +622,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: @@ -525,7 +634,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) @@ -537,6 +646,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 -------------------------- @@ -597,10 +709,6 @@ Wallet - Make sure that no crashes happen with run-time option `-disablewallet`. -- Include `db_cxx.h` (BerkeleyDB header) only when `ENABLE_WALLET` is set. - - - *Rationale*: Otherwise compilation of the disable-wallet build will fail in environments without BerkeleyDB. - General C++ ------------- @@ -726,11 +834,6 @@ int GetInt(Tabs tab) Strings and formatting ------------------------ -- Be careful of `LogPrint` versus `LogPrintf`. `LogPrint` takes a `category` argument, `LogPrintf` does not. - - - *Rationale*: Confusion of these can result in runtime exceptions due to - formatting mismatch, and it is easy to get wrong because of subtly similar naming. - - Use `std::string`, avoid C string manipulation functions. - *Rationale*: C++ string handling is marginally safer, less scope for @@ -803,22 +906,37 @@ from using a different variable with the same name), please name variables so that their names do not shadow variables defined in the source code. When using nested cycles, do not name the inner cycle variable the same as in -the upper cycle, etc. +the outer cycle, etc. + +Lifetimebound +-------------- + +The [Clang `lifetimebound` +attribute](https://clang.llvm.org/docs/AttributeReference.html#lifetimebound) +can be used to tell the compiler that a lifetime is bound to an object and +potentially see a compile-time warning if the object has a shorter lifetime from +the invalid use of a temporary. You can use the attribute by adding a `LIFETIMEBOUND` +annotation defined in `src/attributes.h`; please grep the codebase for examples. Threads and synchronization ---------------------------- -- Prefer `Mutex` type to `RecursiveMutex` one +- 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 @@ -842,21 +960,37 @@ void CTxMemPool::UpdateTransactionsFromBlock(...) ```C++ // validation.h -class ChainstateManager +class CChainState { +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 CChainState::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>()); } ``` @@ -887,6 +1021,8 @@ TRY_LOCK(cs_vNodes, lockNodes); Scripts -------------------------- +Write scripts in Python rather than bash, when possible. + ### Shebang - Use `#!/usr/bin/env bash` instead of obsolete `#!/bin/bash`. @@ -1025,10 +1161,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. @@ -1160,7 +1292,10 @@ A few guidelines for introducing and reviewing new RPC interfaces: - *Rationale*: Consistency with the existing interface. -- Argument naming: use snake case `fee_delta` (and not, e.g. camel case `feeDelta`) +- Argument and field naming: please consider whether there is already a naming + style or spelling convention in the API for the type of object in question + (`blockhash`, for example), and if so, try to use that. If not, use snake case + `fee_delta` (and not, e.g. `feedelta` or camel case `feeDelta`). - *Rationale*: Consistency with the existing interface. @@ -1326,22 +1461,9 @@ communication: virtual boost::signals2::scoped_connection connectTipChanged(TipChangedFn fn) = 0; ``` -- For consistency and friendliness to code generation tools, interface method - input and inout parameters should be ordered first and output parameters - should come last. +- Interface methods should not be overloaded. - Example: - - ```c++ - // Good: error output param is last - virtual bool broadcastTransaction(const CTransactionRef& tx, CAmount max_fee, std::string& error) = 0; - - // Bad: error output param is between input params - virtual bool broadcastTransaction(const CTransactionRef& tx, std::string& error, CAmount max_fee) = 0; - ``` - -- For friendliness to code generation tools, interface methods should not be - overloaded: + *Rationale*: consistency and friendliness to code generation tools. Example: @@ -1355,10 +1477,13 @@ communication: virtual bool disconnect(NodeId id) = 0; ``` -- For consistency and friendliness to code generation tools, interface method - names should be `lowerCamelCase` and standalone function names should be +### Internal interface naming style + +- Interface method names should be `lowerCamelCase` and standalone function names should be `UpperCamelCase`. + *Rationale*: consistency and friendliness to code generation tools. + Examples: ```c++ diff --git a/doc/i2p.md b/doc/i2p.md index ee650f3999..39f65c4e5f 100644 --- a/doc/i2p.md +++ b/doc/i2p.md @@ -65,9 +65,9 @@ logging` for more information. -onlynet=i2p ``` -Make outgoing connections only to I2P addresses. Incoming connections are not -affected by this option. It can be specified multiple times to allow multiple -network types, e.g. onlynet=onion, onlynet=i2p. +Make automatic outbound connections only to I2P addresses. Inbound and manual +connections are not affected by this option. It can be specified multiple times +to allow multiple networks, e.g. onlynet=onion, onlynet=i2p. I2P support was added to Bitcoin Core in version 22.0 and there may be fewer I2P peers than Tor or IP ones. Therefore, using I2P alone without other networks may @@ -80,15 +80,15 @@ phase when syncing up a new node can be very slow. This phase can be sped up by using other networks, for instance `onlynet=onion`, at the same time. In general, a node can be run with both onion and I2P hidden services (or -any/all of IPv4/IPv6/onion/I2P), which can provide a potential fallback if one -of the networks has issues. +any/all of IPv4/IPv6/onion/I2P/CJDNS), which can provide a potential fallback if +one of the networks has issues. ## I2P-related information in Bitcoin Core There are several ways to see your I2P address in Bitcoin Core: -- in the debug log (grep for `AddLocal`, the I2P address ends in `.b32.i2p`) -- in the output of the `getnetworkinfo` RPC in the "localaddresses" section -- in the output of `bitcoin-cli -netinfo` peer connections dashboard +- 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`) To see which I2P peers your node is connected to, use `bitcoin-cli -netinfo 4` or the `getpeerinfo` RPC (e.g. `bitcoin-cli getpeerinfo`). diff --git a/doc/multisig-tutorial.md b/doc/multisig-tutorial.md index 0793040418..1d2b3244bc 100644 --- a/doc/multisig-tutorial.md +++ b/doc/multisig-tutorial.md @@ -29,7 +29,7 @@ These three wallets should not be used directly for privacy reasons (public key ```bash for ((n=1;n<=3;n++)) do - ./src/bitcoin-cli -signet -named createwallet wallet_name="participant_${n}" descriptors=true + ./src/bitcoin-cli -signet createwallet "participant_${n}" done ``` @@ -109,7 +109,7 @@ Then import the descriptors created in the previous step using the `importdescri After that, `getwalletinfo` can be used to check if the wallet was created successfully. ```bash -./src/bitcoin-cli -signet -named createwallet wallet_name="multisig_wallet_01" disable_private_keys=true blank=true descriptors=true +./src/bitcoin-cli -signet -named createwallet wallet_name="multisig_wallet_01" disable_private_keys=true blank=true ./src/bitcoin-cli -signet -rpcwallet="multisig_wallet_01" importdescriptors "$multisig_desc" @@ -238,4 +238,4 @@ psbt_2=$(./src/bitcoin-cli -signet -rpcwallet="participant_2" walletprocesspsbt finalized_psbt_hex=$(./src/bitcoin-cli -signet finalizepsbt $psbt_2 | jq -r '.hex') ./src/bitcoin-cli -signet sendrawtransaction $finalized_psbt_hex -```
\ No newline at end of file +``` diff --git a/doc/p2p-bad-ports.md b/doc/p2p-bad-ports.md index 0dd7d36cf4..4f717f97a2 100644 --- a/doc/p2p-bad-ports.md +++ b/doc/p2p-bad-ports.md @@ -1,6 +1,6 @@ -When Bitcoin Core automatically opens outgoing P2P connections it chooses +When Bitcoin Core automatically opens outgoing P2P connections, it chooses a peer (address and port) from its list of potential peers. This list is -populated with unchecked data, gossiped over the P2P network by other peers. +populated with unchecked data gossiped over the P2P network by other peers. A malicious actor may gossip an address:port where no Bitcoin node is listening, or one where a service is listening that is not related to the Bitcoin network. @@ -17,7 +17,7 @@ authentication are unlikely to be considered a malicious action, e.g. port 80 (http). Below is a list of "bad" ports which Bitcoin Core avoids when choosing a peer to -connect to. If a node is listening on such a port, it will likely receive less +connect to. If a node is listening on such a port, it will likely receive fewer incoming connections. 1: tcpmux 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 7f7fbe18cd..03c26da86b 100644 --- a/doc/policy/packages.md +++ b/doc/policy/packages.md @@ -72,3 +72,48 @@ test accepts): a competing package or transaction with a mutated witness, even though the two same-txid-different-witness transactions are conflicting and cannot replace each other, the honest package should still be considered for acceptance. + +### Package Fees and Feerate + +*Package Feerate* is the total modified fees (base fees + any fee delta from +`prioritisetransaction`) divided by the total virtual size of all transactions in the package. +If any transactions in the package are already in the mempool, they are not submitted again +("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 +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. + +*Rationale*: This can be thought of as "CPFP within a package," solving the issue of a parent not +meeting minimum fees on its own. This would allow contracting applications to adjust their fees at +broadcast time instead of overshooting or risking becoming stuck or pinned. + +*Rationale*: It would be incorrect to use the fees of transactions that are already in the mempool, as +we do not want a transaction's fees to be double-counted. + +Implementation Note: Transactions within a package are always validated individually first, and +package validation is used for the transactions that failed. Since package feerate is only +calculated using transactions that are not in the mempool, this implementation detail affects the +outcome of package validation. + +*Rationale*: Packages are intended for incentive-compatible fee-bumping: transaction B is a +"legitimate" fee-bump for transaction A only if B is a descendant of A and has a *higher* feerate +than A. We want to prevent "parents pay for children" behavior; fees of parents should not help +their children, since the parents can be mined without the child. More generally, if transaction A +is not needed in order for transaction B to be mined, A's fees cannot help B. In a +child-with-parents package, simply excluding any parent transactions that meet feerate requirements +individually is sufficient to ensure this. + +*Rationale*: We must not allow a low-feerate child to prevent its parent from being accepted; fees +of children should not negatively impact their parents, since they are not necessary for the parents +to be mined. More generally, if transaction B is not needed in order for transaction A to be mined, +B's fees cannot harm A. In a child-with-parents package, simply validating parents individually +first is sufficient to ensure this. + +*Rationale*: As a principle, we want to avoid accidentally restricting policy in order to be +backward-compatible for users and applications that rely on p2p transaction relay. Concretely, +package validation should not prevent the acceptance of a transaction that would otherwise be +policy-valid on its own. By always accepting a transaction that passes individual validation before +trying package validation, we prevent any unintentional restriction of policy. 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-15936.md b/doc/release-notes-15936.md new file mode 100644 index 0000000000..90c0413b9a --- /dev/null +++ b/doc/release-notes-15936.md @@ -0,0 +1,15 @@ +GUI changes +----------- + +Configuration changes made in the bitcoin GUI (such as the pruning setting, +proxy settings, UPNP preferences) are now saved to `<datadir>/settings.json` +file rather than to the Qt settings backend (windows registry or unix desktop +config files), so these settings will now apply to bitcoind, instead of being +ignored. + +Also, the interaction between GUI settings and `bitcoin.conf` settings is +simplified. Settings from `bitcoin.conf` are now displayed normally in the GUI +settings dialog, instead of in a separate warning message ("Options set in this +dialog are overridden by the configuration file: -setting=value"). And these +settings can now be edited because `settings.json` values take precedence over +`bitcoin.conf` values. diff --git a/doc/release-notes-24098.md b/doc/release-notes-24098.md new file mode 100644 index 0000000000..79e047e9a5 --- /dev/null +++ b/doc/release-notes-24098.md @@ -0,0 +1,22 @@ +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 new file mode 100644 index 0000000000..16f23c7d00 --- /dev/null +++ b/doc/release-notes-24118.md @@ -0,0 +1,10 @@ +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-24148.md b/doc/release-notes-24148.md new file mode 100644 index 0000000000..f7a0fd6fa1 --- /dev/null +++ b/doc/release-notes-24148.md @@ -0,0 +1,23 @@ +Notable changes +=============== + +Wallet +------ + +- The `wsh()` output descriptor was extended with Miniscript support. You can import Miniscript + descriptors for P2WSH in a watchonly wallet to track coins, but you can't spend from them using + the Bitcoin Core wallet yet. + You can find more about Miniscript on the [reference website](https://bitcoin.sipa.be/miniscript/). + + +Low-level changes +================= + +RPC +--- + +- The `deriveaddresses`, `getdescriptorinfo`, `importdescriptors` and `scantxoutset` commands now + accept Miniscript expression within a `wsh()` descriptor. + +- The `getaddressinfo`, `decodescript`, `listdescriptors` and `listunspent` commands may now output + a Miniscript descriptor inside a `wsh()` where a `wsh(raw())` descriptor was previously returned. diff --git a/doc/release-notes-24198.md b/doc/release-notes-24198.md new file mode 100644 index 0000000000..e41b2a8e26 --- /dev/null +++ b/doc/release-notes-24198.md @@ -0,0 +1,6 @@ +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 new file mode 100644 index 0000000000..afbb926433 --- /dev/null +++ b/doc/release-notes-24494.md @@ -0,0 +1,2 @@ +To help prevent fingerprinting transactions created by the Bitcoin Core wallet, change output +amounts are now randomized. (#24494) diff --git a/doc/release-notes-25504.md b/doc/release-notes-25504.md new file mode 100644 index 0000000000..bf80f18031 --- /dev/null +++ b/doc/release-notes-25504.md @@ -0,0 +1,6 @@ +Updated RPCs +------------ + +- The `listsinceblock`, `listtransactions` and `gettransaction` output now contain a new + `parent_descs` field for every "receive" entry. +- A new optional `include_change` parameter was added to the `listsinceblock` command. diff --git a/doc/release-notes-25610.md b/doc/release-notes-25610.md new file mode 100644 index 0000000000..743a7709bf --- /dev/null +++ b/doc/release-notes-25610.md @@ -0,0 +1,12 @@ +Wallet +------ + +- The `-walletrbf` startup option will now default to `true`. The + wallet will now default to opt-in RBF on transactions that it creates. + +Updated RPCs +------------ + +- The `replaceable` option for the `createrawtransaction` and + `createpsbt` RPCs will now default to `true`. Transactions created + with these RPCs will default to having opt-in RBF enabled. diff --git a/doc/release-notes-empty-template.md b/doc/release-notes-empty-template.md new file mode 100644 index 0000000000..8462714898 --- /dev/null +++ b/doc/release-notes-empty-template.md @@ -0,0 +1,99 @@ +*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 +------------ + + +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 +------ + +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.md b/doc/release-notes.md index 5d1aa85593..444926087b 100644 --- a/doc/release-notes.md +++ b/doc/release-notes.md @@ -1,17 +1,7 @@ -*After branching off for a major version release of Bitcoin Core, use this -template to create the initial release notes draft.* - *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.* -*Create the draft, named* "*version* Release Notes Draft" -*(e.g. "22.0 Release Notes Draft"), as a collaborative wiki in:* - -https://github.com/bitcoin-core/bitcoin-devwiki/wiki/ - -*Before the final release, move the notes back to this git repository.* - *version* Release Notes Draft =============================== @@ -57,170 +47,72 @@ Notable changes P2P and network changes ----------------------- -- A bitcoind node will no longer rumour addresses to inbound peers by default. - They will become eligible for address gossip after sending an ADDR, ADDRV2, - or GETADDR message. (#21528) +Updated RPCs +------------ -Fee estimation changes ----------------------- +- 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. -- Fee estimation now takes the feerate of replacement (RBF) transactions into - account. (#22539) +- 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) -Rescan startup parameter removed --------------------------------- +- 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) -The `-rescan` startup parameter has been removed. Wallets which require -rescanning due to corruption will still be rescanned on startup. -Otherwise, please use the `rescanblockchain` RPC to trigger a rescan. (#23123) - -Updated RPCs ------------- - -- The `validateaddress` RPC now returns an `error_locations` array for invalid - addresses, with the indices of invalid character locations in the address (if - known). For example, this will attempt to locate up to two Bech32 errors, and - return their locations if successful. Success and correctness are only guaranteed - if fewer than two substitution errors have been made. - The error message returned in the `error` field now also returns more specific - errors when decoding fails. (#16807) - -- The `-deprecatedrpc=addresses` configuration option has been removed. RPCs - `gettxout`, `getrawtransaction`, `decoderawtransaction`, `decodescript`, - `gettransaction verbose=true` and REST endpoints `/rest/tx`, `/rest/getutxos`, - `/rest/block` no longer return the `addresses` and `reqSigs` fields, which - were previously deprecated in 22.0. (#22650) -- The `getblock` RPC command now supports verbosity level 3 containing transaction inputs' - `prevout` information. The existing `/rest/block/` REST endpoint is modified to contain - this information too. Every `vin` field will contain an additional `prevout` subfield - describing the spent output. `prevout` contains the following keys: - - `generated` - true if the spent coins was a coinbase. - - `height` - - `value` - - `scriptPubKey` - -- The top-level fee fields `fee`, `modifiedfee`, `ancestorfees` and `descendantfees` - returned by RPCs `getmempoolentry`,`getrawmempool(verbose=true)`, - `getmempoolancestors(verbose=true)` and `getmempooldescendants(verbose=true)` - are deprecated and will be removed in the next major version (use - `-deprecated=fees` if needed in this version). The same fee fields can be accessed - through the `fees` object in the result. WARNING: deprecated - fields `ancestorfees` and `descendantfees` are denominated in sats, whereas all - fields in the `fees` object are denominated in BTC. (#22689) - -- Both `createmultisig` and `addmultisigaddress` now include a `warnings` - field, which will show a warning if a non-legacy address type is requested - when using uncompressed public keys. (#23113) +Changes to wallet related RPCs can be found in the Wallet section below. New RPCs -------- -- Information on soft fork status has been moved from `getblockchaininfo` - to the new `getdeploymentinfo` RPC which allows querying soft fork status at any - block, rather than just at the chain tip. Inclusion of soft fork - status in `getblockchaininfo` can currently be restored using the - configuration `-deprecatedrpc=softforks`, but this will be removed in - a future release. Note that in either case, the `status` field - now reflects the status of the current block rather than the next - block. (#23508) - Build System ------------ -Files ------ - -* On startup, the list of banned hosts and networks (via `setban` RPC) in - `banlist.dat` is ignored and only `banlist.json` is considered. Bitcoin Core - version 22.x is the only version that can read `banlist.dat` and also write - it to `banlist.json`. If `banlist.json` already exists, version 22.x will not - try to translate the `banlist.dat` into json. After an upgrade, `listbanned` - can be used to double check the parsed entries. (#22570) - -New settings ------------- - Updated settings ---------------- -- In previous releases, the meaning of the command line option - `-persistmempool` (without a value provided) incorrectly disabled mempool - persistence. `-persistmempool` is now treated like other boolean options to - mean `-persistmempool=1`. Passing `-persistmempool=0`, `-persistmempool=1` - and `-nopersistmempool` is unaffected. (#23061) -- `-maxuploadtarget` now allows human readable byte units [k|K|m|M|g|G|t|T]. - E.g. `-maxuploadtarget=500g`. No whitespace, +- or fractions allowed. - Default is `M` if no suffix provided. (#23249) +Changes to GUI or wallet related settings can be found in the GUI or Wallet section below. + +New settings +------------ -- If `-proxy=` is given together with `-noonion` then the provided proxy will - not be set as a proxy for reaching the Tor network. So it will not be - possible to open manual connections to the Tor network for example with the - `addnode` RPC. To mimic the old behavior use `-proxy=` together with - `-onlynet=` listing all relevant networks except `onion`. (#22834) +- A new `mempoolfullrbf` option has been added, which enables the mempool to + accept transaction replacement without enforcing BIP125 replaceability + signaling. (#25353) Tools and Utilities ------------------- -- Update `-getinfo` to return data in a user-friendly format that also reduces vertical space. (#21832) - -- CLI `-addrinfo` now returns a single field for the number of `onion` addresses - known to the node instead of separate `torv2` and `torv3` fields, as support - for Tor V2 addresses was removed from Bitcoin Core in 22.0. (#22544) - Wallet ------ -- `upgradewallet` will now automatically flush the keypool if upgrading - from a non-HD wallet to an HD wallet, to immediately start using the - newly-generated HD keys. (#23093) - -- a new RPC `newkeypool` has been added, which will flush (entirely - clear and refill) the keypool. (#23093) - -- `listunspent` now includes `ancestorcount`, `ancestorsize`, and - `ancestorfees` for each transaction output that is still in the mempool. - (#12677) - -- `lockunspent` now optionally takes a third parameter, `persistent`, which - causes the lock to be written persistently to the wallet database. This - allows UTXOs to remain locked even after node restarts or crashes. (#23065) - -- `receivedby` RPCs now include coinbase transactions. Previously, the - following wallet RPCs excluded coinbase transactions: `getreceivedbyaddress`, - `getreceivedbylabel`, `listreceivedbyaddress`, `listreceivedbylabel`. This - release changes this behaviour and returns results accounting for received - coins from coinbase outputs. The previous behaviour can be restored using the - configuration `-deprecatedrpc=exclude_coinbase`, but may be removed in a - future release. (#14707) - -- A new option in the same `receivedby` RPCs, `include_immature_coinbase` - (default=`false`), determines whether to account for immature coinbase - transactions. Immature coinbase transactions are coinbase transactions that - have 100 or fewer confirmations, and are not spendable. (#14707) +- RPC `getreceivedbylabel` now returns an error, "Label not found + in wallet" (-4), if the label is not in the address book. (#25122) GUI changes ----------- -- UTXOs which are locked via the GUI are now stored persistently in the - wallet database, so are not lost on node shutdown or crash. (#23065) - -- The Bech32 checkbox has been replaced with a dropdown for all address types, including the new Bech32m (BIP-350) standard for Taproot enabled wallets. - Low-level changes ================= RPC --- -- `getblockchaininfo` now returns a new `time` field, that provides the chain tip time. (#22407) - Tests ----- -- For the `regtest` network the activation heights of several softforks were - set to block height 1. They can be changed by the runtime setting - `-testactivationheight=name@height`. (#22818) +*version* change log +==================== Credits ======= diff --git a/doc/release-notes/release-notes-23.0.md b/doc/release-notes/release-notes-23.0.md new file mode 100644 index 0000000000..b1467a0f71 --- /dev/null +++ b/doc/release-notes/release-notes-23.0.md @@ -0,0 +1,373 @@ +23.0 Release Notes +================== + +Bitcoin Core version 23.0 is now available from: + + <https://bitcoincore.org/bin/bitcoin-core-23.0/> + +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 +----------------------- + +- A bitcoind node will no longer rumour addresses to inbound peers by default. + They will become eligible for address gossip after sending an ADDR, ADDRV2, + or GETADDR message. (#21528) + +- Before this release, Bitcoin Core had a strong preference to try to connect only to peers that listen on port 8333. As a result of that, Bitcoin nodes listening on non-standard ports would likely not get any Bitcoin Core peers connecting to them. This preference has been removed. (#23542) + +- Full support has been added for the CJDNS network. See the new option `-cjdnsreachable` and [doc/cjdns.md](https://github.com/bitcoin/bitcoin/tree/23.x/doc/cjdns.md) (#23077) + +Fee estimation changes +---------------------- + +- Fee estimation now takes the feerate of replacement (RBF) transactions into + account. (#22539) + +Rescan startup parameter removed +-------------------------------- + +The `-rescan` startup parameter has been removed. Wallets which require +rescanning due to corruption will still be rescanned on startup. +Otherwise, please use the `rescanblockchain` RPC to trigger a rescan. (#23123) + +Tracepoints and Userspace, Statically Defined Tracing support +------------------------------------------------------------- + +Bitcoin Core release binaries for Linux now include experimental tracepoints which +act as an interface for process-internal events. These can be used for review, +debugging, monitoring, and more. The tracepoint API is semi-stable. While the API +is tested, process internals might change between releases requiring changes to the +tracepoints. Information about the existing tracepoints can be found under +[doc/tracing.md](https://github.com/bitcoin/bitcoin/blob/23.x/doc/tracing.md) and +usage examples are provided in [contrib/tracing/](https://github.com/bitcoin/bitcoin/tree/23.x/contrib/tracing). + +Updated RPCs +------------ + +- The `validateaddress` RPC now returns an `error_locations` array for invalid + addresses, with the indices of invalid character locations in the address (if + known). For example, this will attempt to locate up to two Bech32 errors, and + return their locations if successful. Success and correctness are only guaranteed + if fewer than two substitution errors have been made. + The error message returned in the `error` field now also returns more specific + errors when decoding fails. (#16807) + +- The `-deprecatedrpc=addresses` configuration option has been removed. RPCs + `gettxout`, `getrawtransaction`, `decoderawtransaction`, `decodescript`, + `gettransaction verbose=true` and REST endpoints `/rest/tx`, `/rest/getutxos`, + `/rest/block` no longer return the `addresses` and `reqSigs` fields, which + were previously deprecated in 22.0. (#22650) +- The `getblock` RPC command now supports verbosity level 3 containing transaction inputs' + `prevout` information. The existing `/rest/block/` REST endpoint is modified to contain + this information too. Every `vin` field will contain an additional `prevout` subfield + describing the spent output. `prevout` contains the following keys: + - `generated` - true if the spent coins was a coinbase. + - `height` + - `value` + - `scriptPubKey` + +- The top-level fee fields `fee`, `modifiedfee`, `ancestorfees` and `descendantfees` + returned by RPCs `getmempoolentry`,`getrawmempool(verbose=true)`, + `getmempoolancestors(verbose=true)` and `getmempooldescendants(verbose=true)` + are deprecated and will be removed in the next major version (use + `-deprecated=fees` if needed in this version). The same fee fields can be accessed + through the `fees` object in the result. WARNING: deprecated + fields `ancestorfees` and `descendantfees` are denominated in sats, whereas all + fields in the `fees` object are denominated in BTC. (#22689) + +- Both `createmultisig` and `addmultisigaddress` now include a `warnings` + field, which will show a warning if a non-legacy address type is requested + when using uncompressed public keys. (#23113) + +Changes to wallet related RPCs can be found in the Wallet section below. + +New RPCs +-------- + +- Information on soft fork status has been moved from `getblockchaininfo` + to the new `getdeploymentinfo` RPC which allows querying soft fork status at any + block, rather than just at the chain tip. Inclusion of soft fork + status in `getblockchaininfo` can currently be restored using the + configuration `-deprecatedrpc=softforks`, but this will be removed in + a future release. Note that in either case, the `status` field + now reflects the status of the current block rather than the next + block. (#23508) + +Files +----- + +* On startup, the list of banned hosts and networks (via `setban` RPC) in + `banlist.dat` is ignored and only `banlist.json` is considered. Bitcoin Core + version 22.x is the only version that can read `banlist.dat` and also write + it to `banlist.json`. If `banlist.json` already exists, version 22.x will not + try to translate the `banlist.dat` into json. After an upgrade, `listbanned` + can be used to double check the parsed entries. (#22570) + +Updated settings +---------------- + +- In previous releases, the meaning of the command line option + `-persistmempool` (without a value provided) incorrectly disabled mempool + persistence. `-persistmempool` is now treated like other boolean options to + mean `-persistmempool=1`. Passing `-persistmempool=0`, `-persistmempool=1` + and `-nopersistmempool` is unaffected. (#23061) + +- `-maxuploadtarget` now allows human readable byte units [k|K|m|M|g|G|t|T]. + E.g. `-maxuploadtarget=500g`. No whitespace, +- or fractions allowed. + Default is `M` if no suffix provided. (#23249) + +- If `-proxy=` is given together with `-noonion` then the provided proxy will + not be set as a proxy for reaching the Tor network. So it will not be + possible to open manual connections to the Tor network for example with the + `addnode` RPC. To mimic the old behavior use `-proxy=` together with + `-onlynet=` listing all relevant networks except `onion`. (#22834) + +Tools and Utilities +------------------- + +- Update `-getinfo` to return data in a user-friendly format that also reduces vertical space. (#21832) + +- CLI `-addrinfo` now returns a single field for the number of `onion` addresses + known to the node instead of separate `torv2` and `torv3` fields, as support + for Tor V2 addresses was removed from Bitcoin Core in 22.0. (#22544) + +Wallet +------ + +- Descriptor wallets are now the default wallet type. Newly created wallets + will use descriptors unless `descriptors=false` is set during `createwallet`, or + the `Descriptor wallet` checkbox is unchecked in the GUI. + + Note that wallet RPC commands like `importmulti` and `dumpprivkey` cannot be + used with descriptor wallets, so if your client code relies on these commands + without specifying `descriptors=false` during wallet creation, you will need + to update your code. + +- Newly created descriptor wallets will contain an automatically generated `tr()` + descriptor which allows for creating single key Taproot receiving addresses. + +- `upgradewallet` will now automatically flush the keypool if upgrading + from a non-HD wallet to an HD wallet, to immediately start using the + newly-generated HD keys. (#23093) + +- a new RPC `newkeypool` has been added, which will flush (entirely + clear and refill) the keypool. (#23093) + +- `listunspent` now includes `ancestorcount`, `ancestorsize`, and + `ancestorfees` for each transaction output that is still in the mempool. + (#12677) + +- `lockunspent` now optionally takes a third parameter, `persistent`, which + causes the lock to be written persistently to the wallet database. This + allows UTXOs to remain locked even after node restarts or crashes. (#23065) + +- `receivedby` RPCs now include coinbase transactions. Previously, the + following wallet RPCs excluded coinbase transactions: `getreceivedbyaddress`, + `getreceivedbylabel`, `listreceivedbyaddress`, `listreceivedbylabel`. This + release changes this behaviour and returns results accounting for received + coins from coinbase outputs. The previous behaviour can be restored using the + configuration `-deprecatedrpc=exclude_coinbase`, but may be removed in a + future release. (#14707) + +- A new option in the same `receivedby` RPCs, `include_immature_coinbase` + (default=`false`), determines whether to account for immature coinbase + transactions. Immature coinbase transactions are coinbase transactions that + have 100 or fewer confirmations, and are not spendable. (#14707) + +GUI changes +----------- + +- UTXOs which are locked via the GUI are now stored persistently in the + wallet database, so are not lost on node shutdown or crash. (#23065) + +- The Bech32 checkbox has been replaced with a dropdown for all address types, including the new Bech32m (BIP-350) standard for Taproot enabled wallets. + +Low-level changes +================= + +RPC +--- + +- `getblockchaininfo` now returns a new `time` field, that provides the chain tip time. (#22407) + +Tests +----- + +- For the `regtest` network the activation heights of several softforks were + set to block height 1. They can be changed by the runtime setting + `-testactivationheight=name@height`. (#22818) + +Credits +======= + +Thanks to everyone who directly contributed to this release: + +- 0xb10c +- 0xree +- Aaron Clauson +- Adrian-Stefan Mares +- agroce +- aitorjs +- Alex Groce +- amadeuszpawlik +- Amiti Uttarwar +- Andrew Chow +- Andrew Poelstra +- Andrew Toth +- anouar kappitou +- Anthony Towns +- Antoine Poinsot +- Arnab Sen +- Ben Woosley +- benthecarman +- Bitcoin Hodler +- BitcoinTsunami +- brianddk +- Bruno Garcia +- CallMeMisterOwl +- Calvin Kim +- Carl Dong +- Cory Fields +- Cuong V. Nguyen +- Darius Parvin +- Dhruv Mehta +- Dimitri Deijs +- Dimitris Apostolou +- Dmitry Goncharov +- Douglas Chimento +- eugene +- Fabian Jahr +- fanquake +- Florian Baumgartl +- fyquah +- Gleb Naumenko +- glozow +- Gregory Sanders +- Heebs +- Hennadii Stepanov +- hg333 +- HiLivin +- Igor Cota +- Jadi +- James O'Beirne +- Jameson Lopp +- Jarol Rodriguez +- Jeremy Rand +- Jeremy Rubin +- Joan Karadimov +- John Newbery +- Jon Atack +- João Barbosa +- josibake +- junderw +- Karl-Johan Alm +- katesalazar +- Kennan Mell +- Kiminuo +- Kittywhiskers Van Gogh +- Klement Tan +- Kristaps Kaupe +- Kuro +- Larry Ruane +- lsilva01 +- lucash-dev +- Luke Dashjr +- MarcoFalke +- Martin Leitner-Ankerl +- Martin Zumsande +- Matt Corallo +- Matt Whitlock +- MeshCollider +- Michael Dietz +- Murch +- naiza +- Nathan Garabedian +- Nelson Galdeman +- NikhilBartwal +- Niklas Gögge +- node01 +- nthumann +- Pasta +- Patrick Kamin +- Pavel Safronov +- Pavol Rusnak +- Perlover +- Pieter Wuille +- practicalswift +- pradumnasaraf +- pranabp-bit +- Prateek Sancheti +- Prayank +- Rafael Sadowski +- rajarshimaitra +- randymcmillan +- ritickgoenka +- Rob Fielding +- Rojar Smith +- Russell Yanofsky +- S3RK +- Saibato +- Samuel Dobson +- sanket1729 +- seaona +- Sebastian Falbesoner +- sh15h4nk +- Shashwat +- Shorya +- ShubhamPalriwala +- Shubhankar Gambhir +- Sjors Provoost +- sogoagain +- sstone +- stratospher +- Suriyaa Rocky Sundararuban +- Taeik Lim +- TheCharlatan +- Tim Ruffing +- Tobin Harding +- Troy Giorshev +- Tyler Chambers +- Vasil Dimov +- W. J. van der Laan +- w0xlt +- willcl-ark +- William Casarin +- zealsham +- Zero-1729 + +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 new file mode 100644 index 0000000000..1072ec786a --- /dev/null +++ b/doc/release-notes/release-notes-24408.md @@ -0,0 +1,5 @@ +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/doc/release-notes/release-notes-471.md b/doc/release-notes/release-notes-471.md new file mode 100644 index 0000000000..7cebedd8b3 --- /dev/null +++ b/doc/release-notes/release-notes-471.md @@ -0,0 +1,4 @@ +GUI changes +-------- + +- A new menu item to restore a wallet from a backup file has been added (#471).
\ No newline at end of file diff --git a/doc/release-process.md b/doc/release-process.md index 5a74f72b6e..17a03f7dcd 100644 --- a/doc/release-process.md +++ b/doc/release-process.md @@ -8,6 +8,7 @@ Release Process * Update translations see [translation_process.md](https://github.com/bitcoin/bitcoin/blob/master/doc/translation_process.md#synchronising-translations). * Update release candidate version in `configure.ac` (`CLIENT_VERSION_RC`). * Update manpages (after rebuilding the binaries), see [gen-manpages.py](https://github.com/bitcoin/bitcoin/blob/master/contrib/devtools/README.md#gen-manpagespy). +* Update bitcoin.conf and commit, see [gen-bitcoin-conf.sh](https://github.com/bitcoin/bitcoin/blob/master/contrib/devtools/README.md#gen-bitcoin-confsh). ### Before every major and minor release @@ -28,16 +29,23 @@ Release Process #### Before branch-off * Update hardcoded [seeds](/contrib/seeds/README.md), see [this pull request](https://github.com/bitcoin/bitcoin/pull/7415) for an example. -* Update [`src/chainparams.cpp`](/src/chainparams.cpp) m_assumed_blockchain_size and m_assumed_chain_state_size with the current size plus some overhead (see [this](#how-to-calculate-assumed-blockchain-and-chain-state-size) for information on how to calculate them). -* Update [`src/chainparams.cpp`](/src/chainparams.cpp) chainTxData with statistics about the transaction count and rate. Use the output of the `getchaintxstats` RPC, see - [this pull request](https://github.com/bitcoin/bitcoin/pull/20263) for an example. Reviewers can verify the results by running `getchaintxstats <window_block_count> <window_final_block_hash>` with the `window_block_count` and `window_final_block_hash` from your output. -* Update `src/chainparams.cpp` nMinimumChainWork and defaultAssumeValid (and the block height comment) with information from the `getblockheader` (and `getblockhash`) RPCs. - - The selected value must not be orphaned so it may be useful to set the value two blocks back from the tip. - - Testnet should be set some tens of thousands back from the tip due to reorgs there. - - This update should be reviewed with a reindex-chainstate with assumevalid=0 to catch any defect - that causes rejection of blocks in the past history. +* Update the following variables in [`src/chainparams.cpp`](/src/chainparams.cpp) for mainnet, testnet, and signet: + - `m_assumed_blockchain_size` and `m_assumed_chain_state_size` with the current size plus some overhead (see + [this](#how-to-calculate-assumed-blockchain-and-chain-state-size) for information on how to calculate them). + - The following updates should be reviewed with `reindex-chainstate` and `assumevalid=0` to catch any defect + that causes rejection of blocks in the past history. + - `chainTxData` with statistics about the transaction count and rate. Use the output of the `getchaintxstats` RPC with an + `nBlocks` of 4096 (28 days) and a `bestblockhash` of RPC `getbestblockhash`; see + [this pull request](https://github.com/bitcoin/bitcoin/pull/20263) for an example. Reviewers can verify the results by running + `getchaintxstats <window_block_count> <window_final_block_hash>` with the `window_block_count` and `window_final_block_hash` from your output. + - `defaultAssumeValid` with the output of RPC `getblockhash` using the `height` of `window_final_block_height` above + (and update the block height comment with that height), taking into account the following: + - On mainnet, the selected value must not be orphaned, so it may be useful to set the height two blocks back from the tip. + - Testnet should be set with a height some tens of thousands back from the tip, due to reorgs there. + - `nMinimumChainWork` with the "chainwork" value of RPC `getblockheader` using the same height as that selected for the previous step. - Clear the release notes and move them to the wiki (see "Write the release notes" below). -- Translations on Transifex +- Translations on Transifex: + - Pull translations from Transifex into the master branch. - Create [a new resource](https://www.transifex.com/bitcoin/bitcoin/content/) named after the major version with the slug `[bitcoin.qt-translation-<RRR>x]`, where `RRR` is the major branch number padded with zeros. Use `src/qt/locale/bitcoin_en.xlf` to create it. - In the project workflow settings, ensure that [Translation Memory Fill-up](https://docs.transifex.com/translation-memory/enabling-autofill) is enabled and that [Translation Memory Context Matching](https://docs.transifex.com/translation-memory/translation-memory-with-context) is disabled. - Update the Transifex slug in [`.tx/config`](/.tx/config) to the slug of the resource created in the first step. This identifies which resource the translations will be synchronized from. @@ -47,13 +55,15 @@ Release Process #### After branch-off (on the major release branch) - Update the versions. +- Create the draft, named "*version* Release Notes Draft", as a [collaborative wiki](https://github.com/bitcoin-core/bitcoin-devwiki/wiki/_new). +- Clear the release notes: `cp doc/release-notes-empty-template.md doc/release-notes.md` - Create a pinned meta-issue for testing the release candidate (see [this issue](https://github.com/bitcoin/bitcoin/issues/17079) for an example) and provide a link to it in the release announcements where useful. - Translations on Transifex - Change the auto-update URL for the new major version's resource away from `master` and to the branch, e.g. `https://raw.githubusercontent.com/bitcoin/bitcoin/<branch>/src/qt/locale/bitcoin_en.xlf`. Do not forget this or it will keep tracking the translations on master instead, drifting away from the specific major release. #### Before final release -- Merge the release notes from the wiki into the branch. +- Merge the release notes from [the wiki](https://github.com/bitcoin-core/bitcoin-devwiki/wiki/) into the branch. - Ensure the "Needs release note" label is removed from all relevant pull requests and issues. #### Tagging a release (candidate) @@ -110,28 +120,24 @@ against other `guix-attest` signatures. git -C ./guix.sigs pull ``` -### Create the macOS SDK tarball: (first time, or when SDK version changes) +### Create the macOS SDK tarball (first time, or when SDK version changes) Create the macOS SDK tarball, see the [macdeploy instructions](/contrib/macdeploy/README.md#deterministic-macos-dmg-notes) for details. -### Build and attest to build outputs: +### Build and attest to build outputs Follow the relevant Guix README.md sections: - [Building](/contrib/guix/README.md#building) - [Attesting to build outputs](/contrib/guix/README.md#attesting-to-build-outputs) -### Verify other builders' signatures to your own. (Optional) - -Add other builders keys to your gpg keyring, and/or refresh keys: See `../bitcoin/contrib/builder-keys/README.md`. +### Verify other builders' signatures to your own (optional) -Follow the relevant Guix README.md sections: +- [Add other builders keys to your gpg keyring, and/or refresh keys](/contrib/builder-keys/README.md) - [Verifying build output attestations](/contrib/guix/README.md#verifying-build-output-attestations) -### Next steps: - -Commit your signature to guix.sigs: +### Commit your non codesigned signature to guix.sigs ```sh pushd ./guix.sigs @@ -141,29 +147,27 @@ git push # Assuming you can push to the guix.sigs tree popd ``` -Codesigner only: Create Windows/macOS detached signatures: -- Only one person handles codesigning. Everyone else should skip to the next step. -- Only once the Windows/macOS builds each have 3 matching signatures may they be signed with their respective release keys. +## Codesigning -Codesigner only: Sign the macOS binary: +### macOS codesigner only: Create detached macOS signatures (assuming [signapple](https://github.com/achow101/signapple/) is installed and up to date with master branch) - transfer bitcoin-osx-unsigned.tar.gz to macOS for signing tar xf bitcoin-osx-unsigned.tar.gz - ./detached-sig-create.sh -s "Key ID" + ./detached-sig-create.sh /path/to/codesign.p12 Enter the keychain password and authorize the signature - Move signature-osx.tar.gz back to the guix-build host + signature-osx.tar.gz will be created -Codesigner only: Sign the windows binaries: +### Windows codesigner only: Create detached Windows signatures tar xf bitcoin-win-unsigned.tar.gz ./detached-sig-create.sh -key /path/to/codesign.key Enter the passphrase for the key when prompted signature-win.tar.gz will be created -Code-signer only: It is advised to test that the code signature attaches properly prior to tagging by performing the `guix-codesign` step. +### Windows and macOS codesigners only: test code signatures +It is advised to test that the code signature attaches properly prior to tagging by performing the `guix-codesign` step. However if this is done, once the release has been tagged in the bitcoin-detached-sigs repo, the `guix-codesign` step must be performed again in order for the guix attestation to be valid when compared against the attestations of non-codesigner builds. -Codesigner only: Commit the detached codesign payloads: +### Windows and macOS codesigners only: Commit the detached codesign payloads ```sh pushd ./bitcoin-detached-sigs @@ -178,16 +182,21 @@ git push the current branch and new tag popd ``` -Non-codesigners: wait for Windows/macOS detached signatures: +### Non-codesigners: wait for Windows and macOS detached signatures -- Once the Windows/macOS builds each have 3 matching signatures, they will be signed with their respective release keys. +- Once the Windows and macOS builds each have 3 matching signatures, they will be signed with their respective release keys. - Detached signatures will then be committed to the [bitcoin-detached-sigs](https://github.com/bitcoin-core/bitcoin-detached-sigs) repository, which can be combined with the unsigned apps to create signed binaries. -Create (and optionally verify) the codesigned outputs: +### Create the codesigned build outputs -- [Codesigning](/contrib/guix/README.md#codesigning) +- [Codesigning build outputs](/contrib/guix/README.md#codesigning-build-outputs) -Commit your signature for the signed macOS/Windows binaries: +### Verify other builders' signatures to your own (optional) + +- [Add other builders keys to your gpg keyring, and/or refresh keys](/contrib/builder-keys/README.md) +- [Verifying build output attestations](/contrib/guix/README.md#verifying-build-output-attestations) + +### Commit your codesigned signature to guix.sigs (for the signed macOS/Windows binaries) ```sh pushd ./guix.sigs @@ -197,7 +206,7 @@ git push # Assuming you can push to the guix.sigs tree popd ``` -### After 3 or more people have guix-built and their results match: +## After 3 or more people have guix-built and their results match Combine the `all.SHA256SUMS.asc` file from all signers into `SHA256SUMS.asc`: @@ -300,15 +309,16 @@ cat "$VERSION"/*/all.SHA256SUMS.asc > SHA256SUMS.asc Both variables are used as a guideline for how much space the user needs on their drive in total, not just strictly for the blockchain. Note that all values should be taken from a **fully synced** node and have an overhead of 5-10% added on top of its base value. -To calculate `m_assumed_blockchain_size`: -- For `mainnet` -> Take the size of the data directory, excluding `/regtest` and `/testnet3` directories. -- For `testnet` -> Take the size of the `/testnet3` directory. - +To calculate `m_assumed_blockchain_size`, take the size in GiB of these directories: +- For `mainnet` -> the data directory, excluding the `/testnet3`, `/signet`, and `/regtest` directories and any overly large files, e.g. a huge `debug.log` +- For `testnet` -> `/testnet3` +- For `signet` -> `/signet` -To calculate `m_assumed_chain_state_size`: -- For `mainnet` -> Take the size of the `/chainstate` directory. -- For `testnet` -> Take the size of the `/testnet3/chainstate` directory. +To calculate `m_assumed_chain_state_size`, take the size in GiB of these directories: +- For `mainnet` -> `/chainstate` +- For `testnet` -> `/testnet3/chainstate` +- For `signet` -> `/signet/chainstate` Notes: - When taking the size for `m_assumed_blockchain_size`, there's no need to exclude the `/chainstate` directory since it's a guideline value and an overhead will be added anyway. -- The expected overhead for growth may change over time, so it may not be the same value as last release; pay attention to that when changing the variables. +- The expected overhead for growth may change over time. Consider whether the percentage needs to be changed in response; if so, update it here in this section. diff --git a/doc/tor.md b/doc/tor.md index 086e6747bf..08d031d084 100644 --- a/doc/tor.md +++ b/doc/tor.md @@ -16,9 +16,9 @@ configure Tor. ## How to see information about your Tor configuration via Bitcoin Core There are several ways to see your local onion address in Bitcoin Core: -- in the debug log (grep for "tor:" or "AddLocal") -- in the output of RPC `getnetworkinfo` in the "localaddresses" section -- in the output of the CLI `-netinfo` peer connections dashboard +- in the "Local addresses" output of CLI `-netinfo` +- in the "localaddresses" output of RPC `getnetworkinfo` +- in the debug log (grep for "AddLocal"; the Tor address ends in `.onion`) You may set the `-debug=tor` config logging option to have additional information in the debug log about your Tor configuration. @@ -27,6 +27,9 @@ CLI `-addrinfo` returns the number of addresses known to your node per network. This can be useful to see how many onion peers your node knows, e.g. for `-onlynet=onion`. +To fetch a number of onion addresses that your node knows, for example seven +addresses, use the `getnodeaddresses 7 onion` RPC. + ## 1. Run Bitcoin Core behind a Tor proxy The first step is running Bitcoin Core behind a Tor proxy. This will already anonymize all @@ -55,10 +58,10 @@ outgoing connections, but more is possible. -seednode=X SOCKS5. In Tor mode, such addresses can also be exchanged with other P2P nodes. - -onlynet=onion Make outgoing connections only to .onion addresses. Incoming - connections are not affected by this option. This option can be - specified multiple times to allow multiple network types, e.g. - onlynet=onion, onlynet=i2p. + -onlynet=onion Make automatic outbound connections only to .onion addresses. + Inbound and manual connections are not affected by this option. + It can be specified multiple times to allow multiple networks, + e.g. onlynet=onion, onlynet=i2p, onlynet=cjdns. In a typical situation, this suffices to run behind a Tor proxy: diff --git a/doc/tracing.md b/doc/tracing.md index 6ec6a6c218..b6e3b9263a 100644 --- a/doc/tracing.md +++ b/doc/tracing.md @@ -168,6 +168,49 @@ Arguments passed: 4. Value of the coin as `int64` 5. If the coin is a coinbase as `bool` +### Context `coin_selection` + +#### Tracepoint `coin_selection:selected_coins` + +Is called when `SelectCoins` completes. + +Arguments passed: +1. Wallet name as `pointer to C-style string` +2. Coin selection algorithm name as `pointer to C-style string` +3. Selection target value as `int64` +4. Calculated waste metric of the solution as `int64` +5. Total value of the selected inputs as `int64` + +#### Tracepoint `coin_selection:normal_create_tx_internal` + +Is called when the first `CreateTransactionInternal` completes. + +Arguments passed: +1. Wallet name as `pointer to C-style string` +2. Whether `CreateTransactionInternal` succeeded as `bool` +3. The expected transaction fee as an `int64` +4. The position of the change output as an `int32` + +#### Tracepoint `coin_selection:attempting_aps_create_tx` + +Is called when `CreateTransactionInternal` is called the second time for the optimistic +Avoid Partial Spends selection attempt. This is used to determine whether the next +tracepoints called are for the Avoid Partial Spends solution, or a different transaction. + +Arguments passed: +1. Wallet name as `pointer to C-style string` + +#### Tracepoint `coin_selection:aps_create_tx_internal` + +Is called when the second `CreateTransactionInternal` with Avoid Partial Spends enabled completes. + +Arguments passed: +1. Wallet name as `pointer to C-style string` +2. Whether the Avoid Partial Spends solution will be used as `bool` +3. Whether `CreateTransactionInternal` succeeded as` bool` +4. The expected transaction fee as an `int64` +5. The position of the change output as an `int32` + ## Adding tracepoints to Bitcoin Core To add a new tracepoint, `#include <util/trace.h>` in the compilation unit where diff --git a/doc/zmq.md b/doc/zmq.md index b832e71734..4055505d74 100644 --- a/doc/zmq.md +++ b/doc/zmq.md @@ -76,7 +76,7 @@ The option to set the PUB socket's outbound message high water mark -zmqpubhashblockhwm=n -zmqpubrawblockhwm=n -zmqpubrawtxhwm=n - -zmqpubsequencehwm=address + -zmqpubsequencehwm=n The high water mark value must be an integer greater than or equal to 0. diff --git a/share/examples/bitcoin.conf b/share/examples/bitcoin.conf index c5b79709c7..5bee4bf92e 100644 --- a/share/examples/bitcoin.conf +++ b/share/examples/bitcoin.conf @@ -1,191 +1 @@ -## -## bitcoin.conf configuration file. Lines beginning with # are comments. -## - -# Network-related settings: - -# Note that if you use testnet, signet or regtest, particularly with the options -# addnode, connect, port, bind, rpcport, rpcbind or wallet, you will also -# want to read "[Sections]" further down. - -# Run on the testnet network -#testnet=0 - -# Run on a signet network -#signet=0 - -# Run a regression test network -#regtest=0 - -# Connect via a SOCKS5 proxy -#proxy=127.0.0.1:9050 - -# Bind to given address and always listen on it. Use [host]:port notation for IPv6 -#bind=<addr> - -# Bind to given address and add permission flags to peers connecting to it. Use [host]:port notation for IPv6 -#whitebind=perm@<addr> - -############################################################## -## Quick Primer on addnode vs connect ## -## Let's say for instance you use addnode=4.2.2.4 ## -## addnode will connect you to and tell you about the ## -## nodes connected to 4.2.2.4. In addition it will tell ## -## the other nodes connected to it that you exist so ## -## they can connect to you. ## -## connect will not do the above when you 'connect' to it. ## -## It will *only* connect you to 4.2.2.4 and no one else.## -## ## -## So if you're behind a firewall, or have other problems ## -## finding nodes, add some using 'addnode'. ## -## ## -## If you want to stay private, use 'connect' to only ## -## connect to "trusted" nodes. ## -## ## -## If you run multiple nodes on a LAN, there's no need for ## -## all of them to open lots of connections. Instead ## -## 'connect' them all to one node that is port forwarded ## -## and has lots of connections. ## -## Thanks goes to [Noodle] on Freenode. ## -############################################################## - -# Use as many addnode= settings as you like to connect to specific peers -#addnode=69.164.218.197 -#addnode=10.0.0.2:8333 - -# Alternatively use as many connect= settings as you like to connect ONLY to specific peers -#connect=69.164.218.197 -#connect=10.0.0.1:8333 - -# Listening mode, enabled by default except when 'connect' is being used -#listen=1 - -# Port on which to listen for connections (default: 8333, testnet: 18333, signet: 38333, regtest: 18444) -#port= - -# Maximum number of inbound + outbound connections (default: 125). This option -# applies only if inbound connections are enabled; otherwise, the number of connections -# will not be more than 11: 8 full-relay connections, 2 block-relay-only ones, and -# occasionally 1 short-lived feeler or extra outbound block-relay-only connection. -# These limits do not apply to connections added manually with the -addnode -# configuration option or the addnode RPC, which have a separate limit of 8 connections. -#maxconnections= - -# Maximum upload bandwidth target in MiB per day (e.g. 'maxuploadtarget=1024' is 1 GiB per day). -# This limits the upload bandwidth for those with bandwidth limits. 0 = no limit (default: 0). -# -maxuploadtarget does not apply to peers with 'download' permission. -# For more information on reducing bandwidth utilization, see: doc/reduce-traffic.md. -#maxuploadtarget= - -# -# JSON-RPC options (for controlling a running Bitcoin/bitcoind process) -# - -# server=1 tells Bitcoin-Qt and bitcoind to accept JSON-RPC commands -#server=0 - -# Bind to given address to listen for JSON-RPC connections. -# Refer to the manpage or bitcoind -help for further details. -#rpcbind=<addr> - -# If no rpcpassword is set, rpc cookie auth is sought. The default `-rpccookiefile` name -# is .cookie and found in the `-datadir` being used for bitcoind. This option is typically used -# when the server and client are run as the same user. -# -# If not, you must set rpcuser and rpcpassword to secure the JSON-RPC API. -# -# The config option `rpcauth` can be added to server startup argument. It is set at initialization time -# using the output from the script in share/rpcauth/rpcauth.py after providing a username: -# -# ./share/rpcauth/rpcauth.py alice -# String to be appended to bitcoin.conf: -# rpcauth=alice:f7efda5c189b999524f151318c0c86$d5b51b3beffbc02b724e5d095828e0bc8b2456e9ac8757ae3211a5d9b16a22ae -# Your password: -# DONT_USE_THIS_YOU_WILL_GET_ROBBED_8ak1gI25KFTvjovL3gAM967mies3E= -# -# On client-side, you add the normal user/password pair to send commands: -#rpcuser=alice -#rpcpassword=DONT_USE_THIS_YOU_WILL_GET_ROBBED_8ak1gI25KFTvjovL3gAM967mies3E= -# -# You can even add multiple entries of these to the server conf file, and client can use any of them: -# rpcauth=bob:b2dd077cb54591a2f3139e69a897ac$4e71f08d48b4347cf8eff3815c0e25ae2e9a4340474079f55705f40574f4ec99 - -# How many seconds bitcoin will wait for a complete RPC HTTP request. -# after the HTTP connection is established. -#rpcclienttimeout=30 - -# By default, only RPC connections from localhost are allowed. -# Specify as many rpcallowip= settings as you like to allow connections from other hosts, -# either as a single IPv4/IPv6 or with a subnet specification. - -# NOTE: opening up the RPC port to hosts outside your local trusted network is NOT RECOMMENDED, -# because the rpcpassword is transmitted over the network unencrypted. - -# server=1 tells Bitcoin-Qt to accept JSON-RPC commands. -# it is also read by bitcoind to determine if RPC should be enabled -#rpcallowip=10.1.1.34/255.255.255.0 -#rpcallowip=1.2.3.4/24 -#rpcallowip=2001:db8:85a3:0:0:8a2e:370:7334/96 - -# Listen for RPC connections on this TCP port: -#rpcport=8332 - -# You can use Bitcoin or bitcoind to send commands to Bitcoin/bitcoind -# running on another host using this option: -#rpcconnect=127.0.0.1 - -# Wallet options - -# Specify where to find wallet, lockfile and logs. If not present, those files will be -# created as new. -#wallet=</path/to/dir> - -# Create transactions that have enough fees so they are likely to begin confirmation within n blocks (default: 6). -# This setting is over-ridden by the -paytxfee option. -#txconfirmtarget=n - -# Pay a transaction fee every time you send bitcoins. -#paytxfee=0.000x - -# Miscellaneous options - -# Pre-generate this many public/private key pairs, so wallet backups will be valid for -# both prior transactions and several dozen future transactions. -#keypool=100 - -# Maintain coinstats index used by the gettxoutsetinfo RPC (default: 0). -#coinstatsindex=1 - -# Enable pruning to reduce storage requirements by deleting old blocks. -# This mode is incompatible with -txindex and -coinstatsindex. -# 0 = default (no pruning). -# 1 = allows manual pruning via RPC. -# >=550 = target to stay under in MiB. -#prune=550 - -# User interface options - -# Start Bitcoin minimized -#min=1 - -# Minimize to the system tray -#minimizetotray=1 - -# [Sections] -# Most options apply to mainnet, testnet, signet and regtest. -# If you want to confine an option to just one network, you should add it in the -# relevant section below. -# EXCEPTIONS: The options addnode, connect, port, bind, rpcport, rpcbind and wallet -# only apply to mainnet unless they appear in the appropriate section below. - -# Options only for mainnet -[main] - -# Options only for testnet -[test] - -# Options only for signet -[signet] - -# Options only for regtest -[regtest] +# This is a placeholder file. Please follow the instructions in `contrib/devtools/README.md` to generate a bitcoin.conf 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/.bear-tidy-config b/src/.bear-tidy-config new file mode 100644 index 0000000000..111ef6ee44 --- /dev/null +++ b/src/.bear-tidy-config @@ -0,0 +1,15 @@ +{ + "output": { + "content": { + "include_only_existing_source": true, + "paths_to_include": [], + "paths_to_exclude": [ + "src/leveldb" + ] + }, + "format": { + "command_as_array": true, + "drop_output_field": false + } + } +} diff --git a/src/.clang-tidy b/src/.clang-tidy index 27616ad072..f59f80f8ce 100644 --- a/src/.clang-tidy +++ b/src/.clang-tidy @@ -1,2 +1,21 @@ -Checks: '-*,bugprone-argument-comment' -WarningsAsErrors: bugprone-argument-comment +Checks: ' +-*, +bugprone-argument-comment, +misc-unused-using-decls, +modernize-use-default-member-init, +modernize-use-nullptr, +performance-for-range-copy, +performance-unnecessary-copy-initialization, +readability-redundant-declaration, +readability-redundant-string-init, +' +WarningsAsErrors: ' +bugprone-argument-comment, +misc-unused-using-decls, +modernize-use-default-member-init, +modernize-use-nullptr, +performance-for-range-copy, +performance-unnecessary-copy-initialization, +readability-redundant-declaration, +readability-redundant-string-init, +' diff --git a/src/Makefile.am b/src/Makefile.am index 417a611181..18c6c25b96 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -8,21 +8,29 @@ print-%: FORCE DIST_SUBDIRS = secp256k1 -AM_LDFLAGS = $(LIBTOOL_LDFLAGS) $(HARDENED_LDFLAGS) $(GPROF_LDFLAGS) $(SANITIZER_LDFLAGS) $(LTO_LDFLAGS) -AM_CXXFLAGS = $(DEBUG_CXXFLAGS) $(HARDENED_CXXFLAGS) $(WARN_CXXFLAGS) $(NOWARN_CXXFLAGS) $(ERROR_CXXFLAGS) $(GPROF_CXXFLAGS) $(SANITIZER_CXXFLAGS) $(LTO_CXXFLAGS) -AM_CPPFLAGS = $(DEBUG_CPPFLAGS) $(HARDENED_CPPFLAGS) +AM_LDFLAGS = $(LIBTOOL_LDFLAGS) $(HARDENED_LDFLAGS) $(GPROF_LDFLAGS) $(SANITIZER_LDFLAGS) $(LTO_LDFLAGS) $(CORE_LDFLAGS) +AM_CXXFLAGS = $(DEBUG_CXXFLAGS) $(HARDENED_CXXFLAGS) $(WARN_CXXFLAGS) $(NOWARN_CXXFLAGS) $(ERROR_CXXFLAGS) $(GPROF_CXXFLAGS) $(SANITIZER_CXXFLAGS) $(LTO_CXXFLAGS) $(CORE_CXXFLAGS) +AM_CPPFLAGS = $(DEBUG_CPPFLAGS) $(HARDENED_CPPFLAGS) $(CORE_CPPFLAGS) AM_LIBTOOLFLAGS = --preserve-dup-deps PTHREAD_FLAGS = $(PTHREAD_CFLAGS) $(PTHREAD_LIBS) EXTRA_LIBRARIES = -BITCOIN_INCLUDES=-I$(builddir) -I$(srcdir)/$(MINISKETCH_INCLUDE_DIR_INT) -I$(srcdir)/secp256k1/include -I$(srcdir)/$(UNIVALUE_INCLUDE_DIR_INT) $(BDB_CPPFLAGS) $(BOOST_CPPFLAGS) $(LEVELDB_CPPFLAGS) +lib_LTLIBRARIES = +noinst_LTLIBRARIES = + +bin_PROGRAMS = +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) LIBBITCOIN_NODE=libbitcoin_node.a LIBBITCOIN_COMMON=libbitcoin_common.a LIBBITCOIN_CONSENSUS=libbitcoin_consensus.a LIBBITCOIN_CLI=libbitcoin_cli.a LIBBITCOIN_UTIL=libbitcoin_util.a -LIBBITCOIN_CRYPTO_BASE=crypto/libbitcoin_crypto_base.a +LIBBITCOIN_CRYPTO_BASE=crypto/libbitcoin_crypto_base.la LIBBITCOINQT=qt/libbitcoinqt.a LIBSECP256K1=secp256k1/libsecp256k1.la @@ -32,28 +40,32 @@ endif if BUILD_BITCOIN_LIBS LIBBITCOINCONSENSUS=libbitcoinconsensus.la endif +if BUILD_BITCOIN_KERNEL_LIB +LIBBITCOINKERNEL=libbitcoinkernel.la +endif if ENABLE_WALLET LIBBITCOIN_WALLET=libbitcoin_wallet.a LIBBITCOIN_WALLET_TOOL=libbitcoin_wallet_tool.a endif -LIBBITCOIN_CRYPTO= $(LIBBITCOIN_CRYPTO_BASE) +LIBBITCOIN_CRYPTO = $(LIBBITCOIN_CRYPTO_BASE) if ENABLE_SSE41 -LIBBITCOIN_CRYPTO_SSE41 = crypto/libbitcoin_crypto_sse41.a +LIBBITCOIN_CRYPTO_SSE41 = crypto/libbitcoin_crypto_sse41.la LIBBITCOIN_CRYPTO += $(LIBBITCOIN_CRYPTO_SSE41) endif if ENABLE_AVX2 -LIBBITCOIN_CRYPTO_AVX2 = crypto/libbitcoin_crypto_avx2.a +LIBBITCOIN_CRYPTO_AVX2 = crypto/libbitcoin_crypto_avx2.la LIBBITCOIN_CRYPTO += $(LIBBITCOIN_CRYPTO_AVX2) endif if ENABLE_X86_SHANI -LIBBITCOIN_CRYPTO_X86_SHANI = crypto/libbitcoin_crypto_x86_shani.a +LIBBITCOIN_CRYPTO_X86_SHANI = crypto/libbitcoin_crypto_x86_shani.la LIBBITCOIN_CRYPTO += $(LIBBITCOIN_CRYPTO_X86_SHANI) endif if ENABLE_ARM_SHANI -LIBBITCOIN_CRYPTO_ARM_SHANI = crypto/libbitcoin_crypto_arm_shani.a +LIBBITCOIN_CRYPTO_ARM_SHANI = crypto/libbitcoin_crypto_arm_shani.la LIBBITCOIN_CRYPTO += $(LIBBITCOIN_CRYPTO_ARM_SHANI) endif +noinst_LTLIBRARIES += $(LIBBITCOIN_CRYPTO) $(LIBSECP256K1): $(wildcard secp256k1/src/*.h) $(wildcard secp256k1/src/*.c) $(wildcard secp256k1/include/*) $(AM_V_at)$(MAKE) $(AM_MAKEFLAGS) -C $(@D) $(@F) @@ -61,7 +73,6 @@ $(LIBSECP256K1): $(wildcard secp256k1/src/*.h) $(wildcard secp256k1/src/*.c) $(w # Make is not made aware of per-object dependencies to avoid limiting building parallelization # But to build the less dependent modules first, we manually select their order here: EXTRA_LIBRARIES += \ - $(LIBBITCOIN_CRYPTO) \ $(LIBBITCOIN_UTIL) \ $(LIBBITCOIN_COMMON) \ $(LIBBITCOIN_CONSENSUS) \ @@ -72,14 +83,6 @@ EXTRA_LIBRARIES += \ $(LIBBITCOIN_WALLET_TOOL) \ $(LIBBITCOIN_ZMQ) -lib_LTLIBRARIES = $(LIBBITCOINCONSENSUS) -noinst_LTLIBRARIES = - -bin_PROGRAMS = -noinst_PROGRAMS = -TESTS = -BENCHMARKS = - if BUILD_BITCOIND bin_PROGRAMS += bitcoind endif @@ -106,6 +109,10 @@ if BUILD_BITCOIN_UTIL bin_PROGRAMS += bitcoin-util endif +if BUILD_BITCOIN_CHAINSTATE + bin_PROGRAMS += bitcoin-chainstate +endif + .PHONY: FORCE check-symbols check-security # bitcoin core # BITCOIN_CORE_H = \ @@ -126,12 +133,11 @@ BITCOIN_CORE_H = \ clientversion.h \ coins.h \ common/bloom.h \ - compat.h \ compat/assumptions.h \ compat/byteswap.h \ + compat/compat.h \ compat/cpuid.h \ compat/endian.h \ - compat/sanity.h \ compressor.h \ consensus/consensus.h \ consensus/tx_check.h \ @@ -163,6 +169,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 \ @@ -176,23 +191,29 @@ BITCOIN_CORE_H = \ net_types.h \ netaddress.h \ netbase.h \ + netgroup.h \ netmessagemaker.h \ node/blockstorage.h \ node/caches.h \ node/chainstate.h \ node/coin.h \ - node/coinstats.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 \ @@ -202,9 +223,11 @@ BITCOIN_CORE_H = \ psbt.h \ random.h \ randomenv.h \ + rest.h \ reverse_iterator.h \ rpc/blockchain.h \ rpc/client.h \ + rpc/mempool.h \ rpc/mining.h \ rpc/protocol.h \ rpc/rawtransaction_util.h \ @@ -216,6 +239,7 @@ BITCOIN_CORE_H = \ scheduler.h \ script/descriptor.h \ script/keyorigin.h \ + script/miniscript.h \ script/sigcache.h \ script/sign.h \ script/signingprovider.h \ @@ -257,12 +281,14 @@ BITCOIN_CORE_H = \ util/overloaded.h \ util/rbf.h \ util/readwritefile.h \ + util/result.h \ util/serfloat.h \ util/settings.h \ util/sock.h \ util/spanparsing.h \ util/string.h \ util/syscall_sandbox.h \ + util/syserror.h \ util/system.h \ util/thread.h \ util/threadnames.h \ @@ -316,7 +342,6 @@ obj/build.h: FORCE "$(abs_top_srcdir)" libbitcoin_util_a-clientversion.$(OBJEXT): obj/build.h -ipc/capnp/libbitcoin_ipc_a-ipc.$(OBJEXT): $(libbitcoin_ipc_mpgen_input:=.h) # server: shared between bitcoind and bitcoin-qt # Contains code accessing mempool and chain state that is meant to be separated @@ -343,35 +368,51 @@ 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 \ net_processing.cpp \ + netgroup.cpp \ node/blockstorage.cpp \ node/caches.cpp \ node/chainstate.cpp \ node/coin.cpp \ - node/coinstats.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 \ pow.cpp \ rest.cpp \ rpc/blockchain.cpp \ + rpc/fees.cpp \ + rpc/mempool.cpp \ rpc/mining.cpp \ - rpc/misc.cpp \ rpc/net.cpp \ + rpc/node.cpp \ + rpc/output_script.cpp \ rpc/rawtransaction.cpp \ rpc/server.cpp \ rpc/server_util.cpp \ + rpc/signmessage.cpp \ + rpc/txoutproof.cpp \ script/sigcache.cpp \ shutdown.cpp \ signet.cpp \ @@ -388,6 +429,7 @@ libbitcoin_node_a_SOURCES = \ if ENABLE_WALLET libbitcoin_node_a_SOURCES += wallet/init.cpp +libbitcoin_node_a_CPPFLAGS += $(BDB_CPPFLAGS) endif if !ENABLE_WALLET libbitcoin_node_a_SOURCES += dummywallet.cpp @@ -407,7 +449,7 @@ endif # wallet: shared between bitcoind and bitcoin-qt, but only linked # when wallet enabled -libbitcoin_wallet_a_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) $(SQLITE_CFLAGS) +libbitcoin_wallet_a_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) $(BDB_CPPFLAGS) $(SQLITE_CFLAGS) libbitcoin_wallet_a_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) libbitcoin_wallet_a_SOURCES = \ wallet/coincontrol.cpp \ @@ -453,9 +495,16 @@ libbitcoin_wallet_tool_a_SOURCES = \ $(BITCOIN_CORE_H) # crypto primitives library -crypto_libbitcoin_crypto_base_a_CPPFLAGS = $(AM_CPPFLAGS) -crypto_libbitcoin_crypto_base_a_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) -crypto_libbitcoin_crypto_base_a_SOURCES = \ +crypto_libbitcoin_crypto_base_la_CPPFLAGS = $(AM_CPPFLAGS) + +# Specify -static in both CXXFLAGS and LDFLAGS so libtool will only build a +# static version of this library. We don't need a dynamic version, and a dynamic +# version can't be used on windows anyway because the library doesn't currently +# export DLL symbols. +crypto_libbitcoin_crypto_base_la_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) -static +crypto_libbitcoin_crypto_base_la_LDFLAGS = $(AM_LDFLAGS) -static + +crypto_libbitcoin_crypto_base_la_SOURCES = \ crypto/aes.cpp \ crypto/aes.h \ crypto/chacha_poly_aead.h \ @@ -487,32 +536,44 @@ crypto_libbitcoin_crypto_base_a_SOURCES = \ crypto/siphash.h if USE_ASM -crypto_libbitcoin_crypto_base_a_SOURCES += crypto/sha256_sse4.cpp +crypto_libbitcoin_crypto_base_la_SOURCES += crypto/sha256_sse4.cpp endif -crypto_libbitcoin_crypto_sse41_a_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) -crypto_libbitcoin_crypto_sse41_a_CPPFLAGS = $(AM_CPPFLAGS) -crypto_libbitcoin_crypto_sse41_a_CXXFLAGS += $(SSE41_CXXFLAGS) -crypto_libbitcoin_crypto_sse41_a_CPPFLAGS += -DENABLE_SSE41 -crypto_libbitcoin_crypto_sse41_a_SOURCES = crypto/sha256_sse41.cpp - -crypto_libbitcoin_crypto_avx2_a_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) -crypto_libbitcoin_crypto_avx2_a_CPPFLAGS = $(AM_CPPFLAGS) -crypto_libbitcoin_crypto_avx2_a_CXXFLAGS += $(AVX2_CXXFLAGS) -crypto_libbitcoin_crypto_avx2_a_CPPFLAGS += -DENABLE_AVX2 -crypto_libbitcoin_crypto_avx2_a_SOURCES = crypto/sha256_avx2.cpp - -crypto_libbitcoin_crypto_x86_shani_a_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) -crypto_libbitcoin_crypto_x86_shani_a_CPPFLAGS = $(AM_CPPFLAGS) -crypto_libbitcoin_crypto_x86_shani_a_CXXFLAGS += $(X86_SHANI_CXXFLAGS) -crypto_libbitcoin_crypto_x86_shani_a_CPPFLAGS += -DENABLE_X86_SHANI -crypto_libbitcoin_crypto_x86_shani_a_SOURCES = crypto/sha256_x86_shani.cpp - -crypto_libbitcoin_crypto_arm_shani_a_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) -crypto_libbitcoin_crypto_arm_shani_a_CPPFLAGS = $(AM_CPPFLAGS) -crypto_libbitcoin_crypto_arm_shani_a_CXXFLAGS += $(ARM_SHANI_CXXFLAGS) -crypto_libbitcoin_crypto_arm_shani_a_CPPFLAGS += -DENABLE_ARM_SHANI -crypto_libbitcoin_crypto_arm_shani_a_SOURCES = crypto/sha256_arm_shani.cpp +# See explanation for -static in crypto_libbitcoin_crypto_base_la's LDFLAGS and +# CXXFLAGS above +crypto_libbitcoin_crypto_sse41_la_LDFLAGS = $(AM_LDFLAGS) -static +crypto_libbitcoin_crypto_sse41_la_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) -static +crypto_libbitcoin_crypto_sse41_la_CPPFLAGS = $(AM_CPPFLAGS) +crypto_libbitcoin_crypto_sse41_la_CXXFLAGS += $(SSE41_CXXFLAGS) +crypto_libbitcoin_crypto_sse41_la_CPPFLAGS += -DENABLE_SSE41 +crypto_libbitcoin_crypto_sse41_la_SOURCES = crypto/sha256_sse41.cpp + +# See explanation for -static in crypto_libbitcoin_crypto_base_la's LDFLAGS and +# CXXFLAGS above +crypto_libbitcoin_crypto_avx2_la_LDFLAGS = $(AM_LDFLAGS) -static +crypto_libbitcoin_crypto_avx2_la_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) -static +crypto_libbitcoin_crypto_avx2_la_CPPFLAGS = $(AM_CPPFLAGS) +crypto_libbitcoin_crypto_avx2_la_CXXFLAGS += $(AVX2_CXXFLAGS) +crypto_libbitcoin_crypto_avx2_la_CPPFLAGS += -DENABLE_AVX2 +crypto_libbitcoin_crypto_avx2_la_SOURCES = crypto/sha256_avx2.cpp + +# See explanation for -static in crypto_libbitcoin_crypto_base_la's LDFLAGS and +# CXXFLAGS above +crypto_libbitcoin_crypto_x86_shani_la_LDFLAGS = $(AM_LDFLAGS) -static +crypto_libbitcoin_crypto_x86_shani_la_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) -static +crypto_libbitcoin_crypto_x86_shani_la_CPPFLAGS = $(AM_CPPFLAGS) +crypto_libbitcoin_crypto_x86_shani_la_CXXFLAGS += $(X86_SHANI_CXXFLAGS) +crypto_libbitcoin_crypto_x86_shani_la_CPPFLAGS += -DENABLE_X86_SHANI +crypto_libbitcoin_crypto_x86_shani_la_SOURCES = crypto/sha256_x86_shani.cpp + +# See explanation for -static in crypto_libbitcoin_crypto_base_la's LDFLAGS and +# CXXFLAGS above +crypto_libbitcoin_crypto_arm_shani_la_LDFLAGS = $(AM_LDFLAGS) -static +crypto_libbitcoin_crypto_arm_shani_la_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) -static +crypto_libbitcoin_crypto_arm_shani_la_CPPFLAGS = $(AM_CPPFLAGS) +crypto_libbitcoin_crypto_arm_shani_la_CXXFLAGS += $(ARM_SHANI_CXXFLAGS) +crypto_libbitcoin_crypto_arm_shani_la_CPPFLAGS += -DENABLE_ARM_SHANI +crypto_libbitcoin_crypto_arm_shani_la_SOURCES = crypto/sha256_arm_shani.cpp # consensus: shared between all executables that validate any consensus rules. libbitcoin_consensus_a_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) @@ -583,6 +644,7 @@ libbitcoin_common_a_SOURCES = \ rpc/util.cpp \ scheduler.cpp \ script/descriptor.cpp \ + script/miniscript.cpp \ script/sign.cpp \ script/signingprovider.cpp \ script/standard.cpp \ @@ -590,16 +652,12 @@ libbitcoin_common_a_SOURCES = \ $(BITCOIN_CORE_H) # util: shared between all executables. -# This library *must* be included to make sure that the glibc -# sanity checks are linked. libbitcoin_util_a_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) libbitcoin_util_a_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) libbitcoin_util_a_SOURCES = \ support/lockedpool.cpp \ chainparamsbase.cpp \ clientversion.cpp \ - compat/glibcxx_sanity.cpp \ - compat/strnlen.cpp \ fs.cpp \ interfaces/echo.cpp \ interfaces/handler.cpp \ @@ -614,11 +672,13 @@ libbitcoin_util_a_SOURCES = \ util/asmap.cpp \ util/bip32.cpp \ util/bytevectorhash.cpp \ + util/check.cpp \ util/error.cpp \ util/fees.cpp \ util/getuniquepath.cpp \ util/hasher.cpp \ util/sock.cpp \ + util/syserror.cpp \ util/system.cpp \ util/message.cpp \ util/moneystr.cpp \ @@ -671,7 +731,6 @@ bitcoin_bin_ldadd = \ $(LIBBITCOIN_CONSENSUS) \ $(LIBBITCOIN_CRYPTO) \ $(LIBLEVELDB) \ - $(LIBLEVELDB_SSE42) \ $(LIBMEMENV) \ $(LIBSECP256K1) @@ -769,10 +828,136 @@ bitcoin_util_LDADD = \ $(LIBSECP256K1) # +# bitcoin-chainstate binary # +bitcoin_chainstate_SOURCES = bitcoin-chainstate.cpp +bitcoin_chainstate_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) +bitcoin_chainstate_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) + +# $(LIBTOOL_APP_LDFLAGS) deliberately omitted here so that we can test linking +# bitcoin-chainstate against libbitcoinkernel as a shared or static library by +# setting --{en,dis}able-shared. +bitcoin_chainstate_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(PTHREAD_FLAGS) +bitcoin_chainstate_LDADD = $(LIBBITCOINKERNEL) +# + +# bitcoinkernel library # +if BUILD_BITCOIN_KERNEL_LIB +lib_LTLIBRARIES += $(LIBBITCOINKERNEL) + +libbitcoinkernel_la_LDFLAGS = $(AM_LDFLAGS) -no-undefined $(RELDFLAGS) $(PTHREAD_FLAGS) +libbitcoinkernel_la_LIBADD = $(LIBBITCOIN_CRYPTO) $(LIBUNIVALUE) $(LIBLEVELDB) $(LIBMEMENV) $(LIBSECP256K1) +libbitcoinkernel_la_CPPFLAGS = $(AM_CPPFLAGS) -I$(builddir)/obj -I$(srcdir)/secp256k1/include -DBUILD_BITCOIN_INTERNAL $(BOOST_CPPFLAGS) $(LEVELDB_CPPFLAGS) -I$(srcdir)/$(UNIVALUE_INCLUDE_DIR_INT) + +# libbitcoinkernel requires default symbol visibility, explicitly specify that +# here so that things still work even when user configures with +# --enable-reduce-exports +# +# Note this is a quick hack that will be removed as we incrementally define what +# to export from the library. +libbitcoinkernel_la_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) -fvisibility=default + +# TODO: For now, Specify -static in both CXXFLAGS and LDFLAGS when building for +# windows targets so libtool will only build a static version of this +# library. There are unresolved problems when building dll's for mingw-w64 +# and attempting to statically embed libstdc++, libpthread, etc. +if TARGET_WINDOWS +libbitcoinkernel_la_LDFLAGS += -static +libbitcoinkernel_la_CXXFLAGS += -static +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. +libbitcoinkernel_la_SOURCES = \ + kernel/bitcoinkernel.cpp \ + arith_uint256.cpp \ + chain.cpp \ + chainparamsbase.cpp \ + chainparams.cpp \ + clientversion.cpp \ + coins.cpp \ + compressor.cpp \ + consensus/merkle.cpp \ + consensus/tx_check.cpp \ + consensus/tx_verify.cpp \ + core_read.cpp \ + dbwrapper.cpp \ + deploymentinfo.cpp \ + deploymentstatus.cpp \ + 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/interface_ui.cpp \ + policy/feerate.cpp \ + policy/fees.cpp \ + policy/packages.cpp \ + policy/policy.cpp \ + policy/rbf.cpp \ + policy/settings.cpp \ + pow.cpp \ + primitives/block.cpp \ + primitives/transaction.cpp \ + pubkey.cpp \ + random.cpp \ + randomenv.cpp \ + scheduler.cpp \ + script/interpreter.cpp \ + script/script.cpp \ + script/script_error.cpp \ + script/sigcache.cpp \ + script/standard.cpp \ + shutdown.cpp \ + signet.cpp \ + support/cleanse.cpp \ + support/lockedpool.cpp \ + sync.cpp \ + threadinterrupt.cpp \ + txdb.cpp \ + txmempool.cpp \ + uint256.cpp \ + util/bytevectorhash.cpp \ + util/check.cpp \ + util/getuniquepath.cpp \ + util/hasher.cpp \ + util/moneystr.cpp \ + util/rbf.cpp \ + util/serfloat.cpp \ + util/settings.cpp \ + util/strencodings.cpp \ + util/string.cpp \ + util/syscall_sandbox.cpp \ + util/syserror.cpp \ + util/system.cpp \ + util/thread.cpp \ + util/threadnames.cpp \ + util/time.cpp \ + util/tokenpipe.cpp \ + validation.cpp \ + validationinterface.cpp \ + versionbits.cpp \ + warnings.cpp + +# Required for obj/build.h to be generated first. +# More details: https://www.gnu.org/software/automake/manual/html_node/Built-Sources-Example.html +libbitcoinkernel_la-clientversion.l$(OBJEXT): obj/build.h +endif # BUILD_BITCOIN_KERNEL_LIB +# + # bitcoinconsensus library # if BUILD_BITCOIN_LIBS +lib_LTLIBRARIES += $(LIBBITCOINCONSENSUS) + include_HEADERS = script/bitcoinconsensus.h -libbitcoinconsensus_la_SOURCES = support/cleanse.cpp $(crypto_libbitcoin_crypto_base_a_SOURCES) $(libbitcoin_consensus_a_SOURCES) +libbitcoinconsensus_la_SOURCES = support/cleanse.cpp $(crypto_libbitcoin_crypto_base_la_SOURCES) $(libbitcoin_consensus_a_SOURCES) libbitcoinconsensus_la_LDFLAGS = $(AM_LDFLAGS) -no-undefined $(RELDFLAGS) libbitcoinconsensus_la_LIBADD = $(LIBSECP256K1) @@ -847,6 +1032,10 @@ libbitcoin_ipc_mpgen_input = \ EXTRA_DIST += $(libbitcoin_ipc_mpgen_input) %.capnp: +# Explicitly list dependencies on generated headers as described in +# https://www.gnu.org/software/automake/manual/html_node/Built-Sources-Example.html#Recording-Dependencies-manually +ipc/capnp/libbitcoin_ipc_a-protocol.$(OBJEXT): $(libbitcoin_ipc_mpgen_input:=.h) + if BUILD_MULTIPROCESS LIBBITCOIN_IPC=libbitcoin_ipc.a libbitcoin_ipc_a_SOURCES = \ @@ -876,6 +1065,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 @@ -884,9 +1082,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 0bcce6ebe1..a23d872250 100644 --- a/src/Makefile.bench.include +++ b/src/Makefile.bench.include @@ -13,53 +13,55 @@ GENERATED_BENCH_FILES = $(RAW_BENCH_FILES:.raw=.raw.h) bench_bench_bitcoin_SOURCES = \ $(RAW_BENCH_FILES) \ bench/addrman.cpp \ - bench/bench_bitcoin.cpp \ + bench/base58.cpp \ + bench/bech32.cpp \ bench/bench.cpp \ bench/bench.h \ + bench/bench_bitcoin.cpp \ bench/block_assemble.cpp \ + bench/ccoins_caching.cpp \ + bench/chacha20.cpp \ + bench/chacha_poly_aead.cpp \ bench/checkblock.cpp \ bench/checkqueue.cpp \ - bench/data.h \ + bench/crypto_hash.cpp \ bench/data.cpp \ + bench/data.h \ + bench/descriptors.cpp \ bench/duplicate_inputs.cpp \ bench/examples.cpp \ - bench/rollingbloom.cpp \ - bench/chacha20.cpp \ - bench/chacha_poly_aead.cpp \ - bench/crypto_hash.cpp \ - bench/ccoins_caching.cpp \ bench/gcs_filter.cpp \ bench/hashpadding.cpp \ - bench/merkle_root.cpp \ + bench/lockedpool.cpp \ + bench/logging.cpp \ bench/mempool_eviction.cpp \ bench/mempool_stress.cpp \ - bench/nanobench.h \ + bench/merkle_root.cpp \ bench/nanobench.cpp \ + bench/nanobench.h \ bench/peer_eviction.cpp \ + bench/poly1305.cpp \ + bench/prevector.cpp \ + bench/rollingbloom.cpp \ bench/rpc_blockchain.cpp \ bench/rpc_mempool.cpp \ + bench/strencodings.cpp \ bench/util_time.cpp \ - bench/verify_script.cpp \ - bench/base58.cpp \ - bench/bech32.cpp \ - bench/lockedpool.cpp \ - bench/poly1305.cpp \ - bench/prevector.cpp + bench/verify_script.cpp 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_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) bench_bench_bitcoin_LDADD = \ + $(LIBTEST_UTIL) \ $(LIBBITCOIN_NODE) \ $(LIBBITCOIN_WALLET) \ $(LIBBITCOIN_COMMON) \ $(LIBBITCOIN_UTIL) \ $(LIBBITCOIN_CONSENSUS) \ $(LIBBITCOIN_CRYPTO) \ - $(LIBTEST_UTIL) \ $(LIBLEVELDB) \ - $(LIBLEVELDB_SSE42) \ $(LIBMEMENV) \ $(LIBSECP256K1) \ $(LIBUNIVALUE) \ @@ -73,6 +75,7 @@ endif 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 endif bench_bench_bitcoin_LDADD += $(BDB_LIBS) $(EVENT_PTHREADS_LIBS) $(EVENT_LIBS) $(MINIUPNPC_LIBS) $(NATPMP_LIBS) $(SQLITE_LIBS) @@ -91,12 +94,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.crc32c.include b/src/Makefile.crc32c.include index 3cbe71792c..c4dd84991d 100644 --- a/src/Makefile.crc32c.include +++ b/src/Makefile.crc32c.include @@ -2,10 +2,9 @@ # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. -LIBCRC32C_INT = crc32c/libcrc32c.a -LIBLEVELDB_SSE42_INT = leveldb/libleveldb_sse42.a +LIBCRC32C_INT = crc32c/libcrc32c.la -EXTRA_LIBRARIES += $(LIBCRC32C_INT) +noinst_LTLIBRARIES += $(LIBCRC32C_INT) LIBCRC32C = $(LIBCRC32C_INT) @@ -34,41 +33,49 @@ else CRC32C_CPPFLAGS_INT += -DBYTE_ORDER_BIG_ENDIAN=0 endif -crc32c_libcrc32c_a_CPPFLAGS = $(AM_CPPFLAGS) $(CRC32C_CPPFLAGS_INT) $(CRC32C_CPPFLAGS) -crc32c_libcrc32c_a_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) - -crc32c_libcrc32c_a_SOURCES = -crc32c_libcrc32c_a_SOURCES += crc32c/include/crc32c/crc32c.h -crc32c_libcrc32c_a_SOURCES += crc32c/src/crc32c_arm64.h -crc32c_libcrc32c_a_SOURCES += crc32c/src/crc32c_arm64_check.h -crc32c_libcrc32c_a_SOURCES += crc32c/src/crc32c_internal.h -crc32c_libcrc32c_a_SOURCES += crc32c/src/crc32c_prefetch.h -crc32c_libcrc32c_a_SOURCES += crc32c/src/crc32c_read_le.h -crc32c_libcrc32c_a_SOURCES += crc32c/src/crc32c_round_up.h -crc32c_libcrc32c_a_SOURCES += crc32c/src/crc32c_sse42_check.h -crc32c_libcrc32c_a_SOURCES += crc32c/src/crc32c_sse42.h - -crc32c_libcrc32c_a_SOURCES += crc32c/src/crc32c.cc -crc32c_libcrc32c_a_SOURCES += crc32c/src/crc32c_portable.cc +crc32c_libcrc32c_la_CPPFLAGS = $(AM_CPPFLAGS) $(CRC32C_CPPFLAGS_INT) $(CRC32C_CPPFLAGS) + +# Specify -static in both CXXFLAGS and LDFLAGS so libtool will only build a +# static version of this library. We don't need a dynamic version, and a dynamic +# version can't be used on windows anyway because the library doesn't currently +# export DLL symbols. +crc32c_libcrc32c_la_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) -static +crc32c_libcrc32c_la_LDFLAGS = $(AM_LDFLAGS) -static + +crc32c_libcrc32c_la_SOURCES = +crc32c_libcrc32c_la_SOURCES += crc32c/include/crc32c/crc32c.h +crc32c_libcrc32c_la_SOURCES += crc32c/src/crc32c_arm64.h +crc32c_libcrc32c_la_SOURCES += crc32c/src/crc32c_arm64_check.h +crc32c_libcrc32c_la_SOURCES += crc32c/src/crc32c_internal.h +crc32c_libcrc32c_la_SOURCES += crc32c/src/crc32c_prefetch.h +crc32c_libcrc32c_la_SOURCES += crc32c/src/crc32c_read_le.h +crc32c_libcrc32c_la_SOURCES += crc32c/src/crc32c_round_up.h +crc32c_libcrc32c_la_SOURCES += crc32c/src/crc32c_sse42_check.h +crc32c_libcrc32c_la_SOURCES += crc32c/src/crc32c_sse42.h + +crc32c_libcrc32c_la_SOURCES += crc32c/src/crc32c.cc +crc32c_libcrc32c_la_SOURCES += crc32c/src/crc32c_portable.cc if ENABLE_SSE42 -LIBCRC32C_SSE42_INT = crc32c/libcrc32c_sse42.a -EXTRA_LIBRARIES += $(LIBCRC32C_SSE42_INT) +LIBCRC32C_SSE42_INT = crc32c/libcrc32c_sse42.la +noinst_LTLIBRARIES += $(LIBCRC32C_SSE42_INT) LIBCRC32C += $(LIBCRC32C_SSE42_INT) -crc32c_libcrc32c_sse42_a_CPPFLAGS = $(crc32c_libcrc32c_a_CPPFLAGS) -crc32c_libcrc32c_sse42_a_CXXFLAGS = $(crc32c_libcrc32c_a_CXXFLAGS) $(SSE42_CXXFLAGS) +crc32c_libcrc32c_sse42_la_CPPFLAGS = $(crc32c_libcrc32c_la_CPPFLAGS) +crc32c_libcrc32c_sse42_la_CXXFLAGS = $(crc32c_libcrc32c_la_CXXFLAGS) $(SSE42_CXXFLAGS) +crc32c_libcrc32c_sse42_la_LDFLAGS = $(crc32c_libcrc32c_la_LDFLAGS) -crc32c_libcrc32c_sse42_a_SOURCES = crc32c/src/crc32c_sse42.cc +crc32c_libcrc32c_sse42_la_SOURCES = crc32c/src/crc32c_sse42.cc endif if ENABLE_ARM_CRC -LIBCRC32C_ARM_CRC_INT = crc32c/libcrc32c_arm_crc.a -EXTRA_LIBRARIES += $(LIBCRC32C_ARM_CRC_INT) +LIBCRC32C_ARM_CRC_INT = crc32c/libcrc32c_arm_crc.la +noinst_LTLIBRARIES += $(LIBCRC32C_ARM_CRC_INT) LIBCRC32C += $(LIBCRC32C_ARM_CRC_INT) -crc32c_libcrc32c_arm_crc_a_CPPFLAGS = $(crc32c_libcrc32c_a_CPPFLAGS) -crc32c_libcrc32c_arm_crc_a_CXXFLAGS = $(crc32c_libcrc32c_a_CXXFLAGS) $(ARM_CRC_CXXFLAGS) +crc32c_libcrc32c_arm_crc_la_CPPFLAGS = $(crc32c_libcrc32c_la_CPPFLAGS) +crc32c_libcrc32c_arm_crc_la_CXXFLAGS = $(crc32c_libcrc32c_la_CXXFLAGS) $(ARM_CRC_CXXFLAGS) +crc32c_libcrc32c_arm_crc_la_LDFLAGS = $(crc32c_libcrc32c_la_LDFLAGS) -crc32c_libcrc32c_arm_crc_a_SOURCES = crc32c/src/crc32c_arm64.cc +crc32c_libcrc32c_arm_crc_la_SOURCES = crc32c/src/crc32c_arm64.cc endif diff --git a/src/Makefile.leveldb.include b/src/Makefile.leveldb.include index 3bec92482a..bf14fe206b 100644 --- a/src/Makefile.leveldb.include +++ b/src/Makefile.leveldb.include @@ -2,18 +2,17 @@ # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. -LIBLEVELDB_INT = leveldb/libleveldb.a -LIBMEMENV_INT = leveldb/libmemenv.a +LIBLEVELDB_INT = leveldb/libleveldb.la +LIBMEMENV_INT = leveldb/libmemenv.la -EXTRA_LIBRARIES += $(LIBLEVELDB_INT) -EXTRA_LIBRARIES += $(LIBMEMENV_INT) +noinst_LTLIBRARIES += $(LIBLEVELDB_INT) +noinst_LTLIBRARIES += $(LIBMEMENV_INT) LIBLEVELDB = $(LIBLEVELDB_INT) $(LIBCRC32C) 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 @@ -37,111 +36,118 @@ else LEVELDB_CPPFLAGS_INT += -DLEVELDB_PLATFORM_POSIX endif -leveldb_libleveldb_a_CPPFLAGS = $(AM_CPPFLAGS) $(LEVELDB_CPPFLAGS_INT) $(LEVELDB_CPPFLAGS) -leveldb_libleveldb_a_CXXFLAGS = $(filter-out -Wconditional-uninitialized -Werror=conditional-uninitialized -Wsuggest-override -Werror=suggest-override, $(AM_CXXFLAGS)) $(PIE_FLAGS) +leveldb_libleveldb_la_CPPFLAGS = $(AM_CPPFLAGS) $(LEVELDB_CPPFLAGS_INT) $(LEVELDB_CPPFLAGS) -leveldb_libleveldb_a_SOURCES= -leveldb_libleveldb_a_SOURCES += leveldb/port/port_stdcxx.h -leveldb_libleveldb_a_SOURCES += leveldb/port/port.h -leveldb_libleveldb_a_SOURCES += leveldb/port/thread_annotations.h -leveldb_libleveldb_a_SOURCES += leveldb/include/leveldb/db.h -leveldb_libleveldb_a_SOURCES += leveldb/include/leveldb/options.h -leveldb_libleveldb_a_SOURCES += leveldb/include/leveldb/comparator.h -leveldb_libleveldb_a_SOURCES += leveldb/include/leveldb/filter_policy.h -leveldb_libleveldb_a_SOURCES += leveldb/include/leveldb/slice.h -leveldb_libleveldb_a_SOURCES += leveldb/include/leveldb/table_builder.h -leveldb_libleveldb_a_SOURCES += leveldb/include/leveldb/env.h -leveldb_libleveldb_a_SOURCES += leveldb/include/leveldb/export.h -leveldb_libleveldb_a_SOURCES += leveldb/include/leveldb/c.h -leveldb_libleveldb_a_SOURCES += leveldb/include/leveldb/iterator.h -leveldb_libleveldb_a_SOURCES += leveldb/include/leveldb/cache.h -leveldb_libleveldb_a_SOURCES += leveldb/include/leveldb/dumpfile.h -leveldb_libleveldb_a_SOURCES += leveldb/include/leveldb/table.h -leveldb_libleveldb_a_SOURCES += leveldb/include/leveldb/write_batch.h -leveldb_libleveldb_a_SOURCES += leveldb/include/leveldb/status.h -leveldb_libleveldb_a_SOURCES += leveldb/db/log_format.h -leveldb_libleveldb_a_SOURCES += leveldb/db/memtable.h -leveldb_libleveldb_a_SOURCES += leveldb/db/version_set.h -leveldb_libleveldb_a_SOURCES += leveldb/db/write_batch_internal.h -leveldb_libleveldb_a_SOURCES += leveldb/db/filename.h -leveldb_libleveldb_a_SOURCES += leveldb/db/version_edit.h -leveldb_libleveldb_a_SOURCES += leveldb/db/dbformat.h -leveldb_libleveldb_a_SOURCES += leveldb/db/builder.h -leveldb_libleveldb_a_SOURCES += leveldb/db/log_writer.h -leveldb_libleveldb_a_SOURCES += leveldb/db/db_iter.h -leveldb_libleveldb_a_SOURCES += leveldb/db/skiplist.h -leveldb_libleveldb_a_SOURCES += leveldb/db/db_impl.h -leveldb_libleveldb_a_SOURCES += leveldb/db/table_cache.h -leveldb_libleveldb_a_SOURCES += leveldb/db/snapshot.h -leveldb_libleveldb_a_SOURCES += leveldb/db/log_reader.h -leveldb_libleveldb_a_SOURCES += leveldb/table/filter_block.h -leveldb_libleveldb_a_SOURCES += leveldb/table/block_builder.h -leveldb_libleveldb_a_SOURCES += leveldb/table/block.h -leveldb_libleveldb_a_SOURCES += leveldb/table/two_level_iterator.h -leveldb_libleveldb_a_SOURCES += leveldb/table/merger.h -leveldb_libleveldb_a_SOURCES += leveldb/table/format.h -leveldb_libleveldb_a_SOURCES += leveldb/table/iterator_wrapper.h -leveldb_libleveldb_a_SOURCES += leveldb/util/crc32c.h -leveldb_libleveldb_a_SOURCES += leveldb/util/env_posix_test_helper.h -leveldb_libleveldb_a_SOURCES += leveldb/util/env_windows_test_helper.h -leveldb_libleveldb_a_SOURCES += leveldb/util/arena.h -leveldb_libleveldb_a_SOURCES += leveldb/util/random.h -leveldb_libleveldb_a_SOURCES += leveldb/util/posix_logger.h -leveldb_libleveldb_a_SOURCES += leveldb/util/hash.h -leveldb_libleveldb_a_SOURCES += leveldb/util/histogram.h -leveldb_libleveldb_a_SOURCES += leveldb/util/coding.h -leveldb_libleveldb_a_SOURCES += leveldb/util/testutil.h -leveldb_libleveldb_a_SOURCES += leveldb/util/mutexlock.h -leveldb_libleveldb_a_SOURCES += leveldb/util/logging.h -leveldb_libleveldb_a_SOURCES += leveldb/util/no_destructor.h -leveldb_libleveldb_a_SOURCES += leveldb/util/testharness.h -leveldb_libleveldb_a_SOURCES += leveldb/util/windows_logger.h +# Specify -static in both CXXFLAGS and LDFLAGS so libtool will only build a +# static version of this library. We don't need a dynamic version, and a dynamic +# version can't be used on windows anyway because the library doesn't currently +# export DLL symbols. +leveldb_libleveldb_la_CXXFLAGS = $(filter-out -Wconditional-uninitialized -Werror=conditional-uninitialized -Wsuggest-override -Werror=suggest-override, $(AM_CXXFLAGS)) $(PIE_FLAGS) -static +leveldb_libleveldb_la_LDFLAGS = $(AM_LDFLAGS) -static -leveldb_libleveldb_a_SOURCES += leveldb/db/builder.cc -leveldb_libleveldb_a_SOURCES += leveldb/db/c.cc -leveldb_libleveldb_a_SOURCES += leveldb/db/dbformat.cc -leveldb_libleveldb_a_SOURCES += leveldb/db/db_impl.cc -leveldb_libleveldb_a_SOURCES += leveldb/db/db_iter.cc -leveldb_libleveldb_a_SOURCES += leveldb/db/dumpfile.cc -leveldb_libleveldb_a_SOURCES += leveldb/db/filename.cc -leveldb_libleveldb_a_SOURCES += leveldb/db/log_reader.cc -leveldb_libleveldb_a_SOURCES += leveldb/db/log_writer.cc -leveldb_libleveldb_a_SOURCES += leveldb/db/memtable.cc -leveldb_libleveldb_a_SOURCES += leveldb/db/repair.cc -leveldb_libleveldb_a_SOURCES += leveldb/db/table_cache.cc -leveldb_libleveldb_a_SOURCES += leveldb/db/version_edit.cc -leveldb_libleveldb_a_SOURCES += leveldb/db/version_set.cc -leveldb_libleveldb_a_SOURCES += leveldb/db/write_batch.cc -leveldb_libleveldb_a_SOURCES += leveldb/table/block_builder.cc -leveldb_libleveldb_a_SOURCES += leveldb/table/block.cc -leveldb_libleveldb_a_SOURCES += leveldb/table/filter_block.cc -leveldb_libleveldb_a_SOURCES += leveldb/table/format.cc -leveldb_libleveldb_a_SOURCES += leveldb/table/iterator.cc -leveldb_libleveldb_a_SOURCES += leveldb/table/merger.cc -leveldb_libleveldb_a_SOURCES += leveldb/table/table_builder.cc -leveldb_libleveldb_a_SOURCES += leveldb/table/table.cc -leveldb_libleveldb_a_SOURCES += leveldb/table/two_level_iterator.cc -leveldb_libleveldb_a_SOURCES += leveldb/util/arena.cc -leveldb_libleveldb_a_SOURCES += leveldb/util/bloom.cc -leveldb_libleveldb_a_SOURCES += leveldb/util/cache.cc -leveldb_libleveldb_a_SOURCES += leveldb/util/coding.cc -leveldb_libleveldb_a_SOURCES += leveldb/util/comparator.cc -leveldb_libleveldb_a_SOURCES += leveldb/util/crc32c.cc -leveldb_libleveldb_a_SOURCES += leveldb/util/env.cc -leveldb_libleveldb_a_SOURCES += leveldb/util/filter_policy.cc -leveldb_libleveldb_a_SOURCES += leveldb/util/hash.cc -leveldb_libleveldb_a_SOURCES += leveldb/util/histogram.cc -leveldb_libleveldb_a_SOURCES += leveldb/util/logging.cc -leveldb_libleveldb_a_SOURCES += leveldb/util/options.cc -leveldb_libleveldb_a_SOURCES += leveldb/util/status.cc +leveldb_libleveldb_la_SOURCES= +leveldb_libleveldb_la_SOURCES += leveldb/port/port_stdcxx.h +leveldb_libleveldb_la_SOURCES += leveldb/port/port.h +leveldb_libleveldb_la_SOURCES += leveldb/port/thread_annotations.h +leveldb_libleveldb_la_SOURCES += leveldb/include/leveldb/db.h +leveldb_libleveldb_la_SOURCES += leveldb/include/leveldb/options.h +leveldb_libleveldb_la_SOURCES += leveldb/include/leveldb/comparator.h +leveldb_libleveldb_la_SOURCES += leveldb/include/leveldb/filter_policy.h +leveldb_libleveldb_la_SOURCES += leveldb/include/leveldb/slice.h +leveldb_libleveldb_la_SOURCES += leveldb/include/leveldb/table_builder.h +leveldb_libleveldb_la_SOURCES += leveldb/include/leveldb/env.h +leveldb_libleveldb_la_SOURCES += leveldb/include/leveldb/export.h +leveldb_libleveldb_la_SOURCES += leveldb/include/leveldb/c.h +leveldb_libleveldb_la_SOURCES += leveldb/include/leveldb/iterator.h +leveldb_libleveldb_la_SOURCES += leveldb/include/leveldb/cache.h +leveldb_libleveldb_la_SOURCES += leveldb/include/leveldb/dumpfile.h +leveldb_libleveldb_la_SOURCES += leveldb/include/leveldb/table.h +leveldb_libleveldb_la_SOURCES += leveldb/include/leveldb/write_batch.h +leveldb_libleveldb_la_SOURCES += leveldb/include/leveldb/status.h +leveldb_libleveldb_la_SOURCES += leveldb/db/log_format.h +leveldb_libleveldb_la_SOURCES += leveldb/db/memtable.h +leveldb_libleveldb_la_SOURCES += leveldb/db/version_set.h +leveldb_libleveldb_la_SOURCES += leveldb/db/write_batch_internal.h +leveldb_libleveldb_la_SOURCES += leveldb/db/filename.h +leveldb_libleveldb_la_SOURCES += leveldb/db/version_edit.h +leveldb_libleveldb_la_SOURCES += leveldb/db/dbformat.h +leveldb_libleveldb_la_SOURCES += leveldb/db/builder.h +leveldb_libleveldb_la_SOURCES += leveldb/db/log_writer.h +leveldb_libleveldb_la_SOURCES += leveldb/db/db_iter.h +leveldb_libleveldb_la_SOURCES += leveldb/db/skiplist.h +leveldb_libleveldb_la_SOURCES += leveldb/db/db_impl.h +leveldb_libleveldb_la_SOURCES += leveldb/db/table_cache.h +leveldb_libleveldb_la_SOURCES += leveldb/db/snapshot.h +leveldb_libleveldb_la_SOURCES += leveldb/db/log_reader.h +leveldb_libleveldb_la_SOURCES += leveldb/table/filter_block.h +leveldb_libleveldb_la_SOURCES += leveldb/table/block_builder.h +leveldb_libleveldb_la_SOURCES += leveldb/table/block.h +leveldb_libleveldb_la_SOURCES += leveldb/table/two_level_iterator.h +leveldb_libleveldb_la_SOURCES += leveldb/table/merger.h +leveldb_libleveldb_la_SOURCES += leveldb/table/format.h +leveldb_libleveldb_la_SOURCES += leveldb/table/iterator_wrapper.h +leveldb_libleveldb_la_SOURCES += leveldb/util/crc32c.h +leveldb_libleveldb_la_SOURCES += leveldb/util/env_posix_test_helper.h +leveldb_libleveldb_la_SOURCES += leveldb/util/env_windows_test_helper.h +leveldb_libleveldb_la_SOURCES += leveldb/util/arena.h +leveldb_libleveldb_la_SOURCES += leveldb/util/random.h +leveldb_libleveldb_la_SOURCES += leveldb/util/posix_logger.h +leveldb_libleveldb_la_SOURCES += leveldb/util/hash.h +leveldb_libleveldb_la_SOURCES += leveldb/util/histogram.h +leveldb_libleveldb_la_SOURCES += leveldb/util/coding.h +leveldb_libleveldb_la_SOURCES += leveldb/util/testutil.h +leveldb_libleveldb_la_SOURCES += leveldb/util/mutexlock.h +leveldb_libleveldb_la_SOURCES += leveldb/util/logging.h +leveldb_libleveldb_la_SOURCES += leveldb/util/no_destructor.h +leveldb_libleveldb_la_SOURCES += leveldb/util/testharness.h +leveldb_libleveldb_la_SOURCES += leveldb/util/windows_logger.h + +leveldb_libleveldb_la_SOURCES += leveldb/db/builder.cc +leveldb_libleveldb_la_SOURCES += leveldb/db/c.cc +leveldb_libleveldb_la_SOURCES += leveldb/db/dbformat.cc +leveldb_libleveldb_la_SOURCES += leveldb/db/db_impl.cc +leveldb_libleveldb_la_SOURCES += leveldb/db/db_iter.cc +leveldb_libleveldb_la_SOURCES += leveldb/db/dumpfile.cc +leveldb_libleveldb_la_SOURCES += leveldb/db/filename.cc +leveldb_libleveldb_la_SOURCES += leveldb/db/log_reader.cc +leveldb_libleveldb_la_SOURCES += leveldb/db/log_writer.cc +leveldb_libleveldb_la_SOURCES += leveldb/db/memtable.cc +leveldb_libleveldb_la_SOURCES += leveldb/db/repair.cc +leveldb_libleveldb_la_SOURCES += leveldb/db/table_cache.cc +leveldb_libleveldb_la_SOURCES += leveldb/db/version_edit.cc +leveldb_libleveldb_la_SOURCES += leveldb/db/version_set.cc +leveldb_libleveldb_la_SOURCES += leveldb/db/write_batch.cc +leveldb_libleveldb_la_SOURCES += leveldb/table/block_builder.cc +leveldb_libleveldb_la_SOURCES += leveldb/table/block.cc +leveldb_libleveldb_la_SOURCES += leveldb/table/filter_block.cc +leveldb_libleveldb_la_SOURCES += leveldb/table/format.cc +leveldb_libleveldb_la_SOURCES += leveldb/table/iterator.cc +leveldb_libleveldb_la_SOURCES += leveldb/table/merger.cc +leveldb_libleveldb_la_SOURCES += leveldb/table/table_builder.cc +leveldb_libleveldb_la_SOURCES += leveldb/table/table.cc +leveldb_libleveldb_la_SOURCES += leveldb/table/two_level_iterator.cc +leveldb_libleveldb_la_SOURCES += leveldb/util/arena.cc +leveldb_libleveldb_la_SOURCES += leveldb/util/bloom.cc +leveldb_libleveldb_la_SOURCES += leveldb/util/cache.cc +leveldb_libleveldb_la_SOURCES += leveldb/util/coding.cc +leveldb_libleveldb_la_SOURCES += leveldb/util/comparator.cc +leveldb_libleveldb_la_SOURCES += leveldb/util/crc32c.cc +leveldb_libleveldb_la_SOURCES += leveldb/util/env.cc +leveldb_libleveldb_la_SOURCES += leveldb/util/filter_policy.cc +leveldb_libleveldb_la_SOURCES += leveldb/util/hash.cc +leveldb_libleveldb_la_SOURCES += leveldb/util/histogram.cc +leveldb_libleveldb_la_SOURCES += leveldb/util/logging.cc +leveldb_libleveldb_la_SOURCES += leveldb/util/options.cc +leveldb_libleveldb_la_SOURCES += leveldb/util/status.cc if TARGET_WINDOWS -leveldb_libleveldb_a_SOURCES += leveldb/util/env_windows.cc +leveldb_libleveldb_la_SOURCES += leveldb/util/env_windows.cc else -leveldb_libleveldb_a_SOURCES += leveldb/util/env_posix.cc +leveldb_libleveldb_la_SOURCES += leveldb/util/env_posix.cc endif -leveldb_libmemenv_a_CPPFLAGS = $(leveldb_libleveldb_a_CPPFLAGS) -leveldb_libmemenv_a_CXXFLAGS = $(leveldb_libleveldb_a_CXXFLAGS) -leveldb_libmemenv_a_SOURCES = leveldb/helpers/memenv/memenv.cc -leveldb_libmemenv_a_SOURCES += leveldb/helpers/memenv/memenv.h +leveldb_libmemenv_la_CPPFLAGS = $(leveldb_libleveldb_la_CPPFLAGS) +leveldb_libmemenv_la_CXXFLAGS = $(leveldb_libleveldb_la_CXXFLAGS) +leveldb_libmemenv_la_LDFLAGS = $(leveldb_libleveldb_la_LDFLAGS) +leveldb_libmemenv_la_SOURCES = leveldb/helpers/memenv/memenv.cc +leveldb_libmemenv_la_SOURCES += leveldb/helpers/memenv/memenv.h diff --git a/src/Makefile.qt.include b/src/Makefile.qt.include index 3491f07ee0..b4acc47aa1 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 \ @@ -330,7 +331,7 @@ endif if ENABLE_ZMQ bitcoin_qt_ldadd += $(LIBBITCOIN_ZMQ) $(ZMQ_LIBS) endif -bitcoin_qt_ldadd += $(LIBBITCOIN_CLI) $(LIBBITCOIN_COMMON) $(LIBBITCOIN_UTIL) $(LIBBITCOIN_CONSENSUS) $(LIBBITCOIN_CRYPTO) $(LIBUNIVALUE) $(LIBLEVELDB) $(LIBLEVELDB_SSE42) $(LIBMEMENV) \ +bitcoin_qt_ldadd += $(LIBBITCOIN_CLI) $(LIBBITCOIN_COMMON) $(LIBBITCOIN_UTIL) $(LIBBITCOIN_CONSENSUS) $(LIBBITCOIN_CRYPTO) $(LIBUNIVALUE) $(LIBLEVELDB) $(LIBMEMENV) \ $(QT_LIBS) $(QT_DBUS_LIBS) $(QR_LIBS) $(BDB_LIBS) $(MINIUPNPC_LIBS) $(NATPMP_LIBS) $(LIBSECP256K1) \ $(EVENT_PTHREADS_LIBS) $(EVENT_LIBS) $(SQLITE_LIBS) bitcoin_qt_ldflags = $(RELDFLAGS) $(AM_LDFLAGS) $(QT_LDFLAGS) $(LIBTOOL_APP_LDFLAGS) $(PTHREAD_FLAGS) diff --git a/src/Makefile.qttest.include b/src/Makefile.qttest.include index 8e6fa2eb0d..fa822f2954 100644 --- a/src/Makefile.qttest.include +++ b/src/Makefile.qttest.include @@ -7,6 +7,7 @@ TESTS += qt/test/test_bitcoin-qt TEST_QT_MOC_CPP = \ qt/test/moc_apptests.cpp \ + qt/test/moc_optiontests.cpp \ qt/test/moc_rpcnestedtests.cpp \ qt/test/moc_uritests.cpp @@ -19,6 +20,7 @@ endif # ENABLE_WALLET TEST_QT_H = \ qt/test/addressbooktests.h \ qt/test/apptests.h \ + qt/test/optiontests.h \ qt/test/rpcnestedtests.h \ qt/test/uritests.h \ qt/test/util.h \ @@ -30,6 +32,7 @@ qt_test_test_bitcoin_qt_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) $(BITCOIN_ qt_test_test_bitcoin_qt_SOURCES = \ init/bitcoin-qt.cpp \ qt/test/apptests.cpp \ + qt/test/optiontests.cpp \ qt/test/rpcnestedtests.cpp \ qt/test/test_main.cpp \ qt/test/uritests.cpp \ @@ -52,7 +55,7 @@ if ENABLE_ZMQ qt_test_test_bitcoin_qt_LDADD += $(LIBBITCOIN_ZMQ) $(ZMQ_LIBS) endif qt_test_test_bitcoin_qt_LDADD += $(LIBBITCOIN_CLI) $(LIBBITCOIN_COMMON) $(LIBBITCOIN_UTIL) $(LIBBITCOIN_CONSENSUS) $(LIBBITCOIN_CRYPTO) $(LIBUNIVALUE) $(LIBLEVELDB) \ - $(LIBLEVELDB_SSE42) $(LIBMEMENV) $(QT_LIBS) $(QT_DBUS_LIBS) $(QT_TEST_LIBS) \ + $(LIBMEMENV) $(QT_LIBS) $(QT_DBUS_LIBS) $(QT_TEST_LIBS) \ $(QR_LIBS) $(BDB_LIBS) $(MINIUPNPC_LIBS) $(NATPMP_LIBS) $(LIBSECP256K1) \ $(EVENT_PTHREADS_LIBS) $(EVENT_LIBS) $(SQLITE_LIBS) qt_test_test_bitcoin_qt_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(QT_LDFLAGS) $(LIBTOOL_APP_LDFLAGS) $(PTHREAD_FLAGS) diff --git a/src/Makefile.test.include b/src/Makefile.test.include index e2e08468a7..75a404c50a 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 @@ -47,7 +47,6 @@ FUZZ_SUITE_LD_COMMON = \ $(LIBBITCOIN_CLI) \ $(LIBUNIVALUE) \ $(LIBLEVELDB) \ - $(LIBLEVELDB_SSE42) \ $(LIBMEMENV) \ $(LIBSECP256K1) \ $(MINISKETCH_LIBS) \ @@ -94,6 +93,7 @@ BITCOIN_TESTS =\ test/fs_tests.cpp \ test/getarg_tests.cpp \ test/hash_tests.cpp \ + test/httpserver_tests.cpp \ test/i2p_tests.cpp \ test/interfaces_tests.cpp \ test/key_io_tests.cpp \ @@ -103,11 +103,13 @@ BITCOIN_TESTS =\ test/merkle_tests.cpp \ test/merkleblock_tests.cpp \ test/miner_tests.cpp \ + test/miniscript_tests.cpp \ test/minisketch_tests.cpp \ test/multisig_tests.cpp \ test/net_peer_eviction_tests.cpp \ test/net_tests.cpp \ test/netbase_tests.cpp \ + test/orphanage_tests.cpp \ test/pmt_tests.cpp \ test/policy_fee_tests.cpp \ test/policyestimator_tests.cpp \ @@ -115,12 +117,16 @@ 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 \ test/scheduler_tests.cpp \ test/script_p2sh_tests.cpp \ test/script_parse_tests.cpp \ + test/script_segwit_tests.cpp \ test/script_standard_tests.cpp \ test/script_tests.cpp \ test/scriptnum10.h \ @@ -163,6 +169,7 @@ 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 @@ -175,8 +182,11 @@ if USE_BDB BITCOIN_TESTS += wallet/test/db_tests.cpp endif -if USE_SQLITE FUZZ_WALLET_SRC = \ + wallet/test/fuzz/coinselection.cpp + +if USE_SQLITE +FUZZ_WALLET_SRC += \ wallet/test/fuzz/notifications.cpp endif # USE_SQLITE @@ -194,10 +204,11 @@ test_test_bitcoin_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) $(TESTDEFS) $(EV test_test_bitcoin_LDADD = $(LIBTEST_UTIL) if ENABLE_WALLET test_test_bitcoin_LDADD += $(LIBBITCOIN_WALLET) +test_test_bitcoin_CPPFLAGS += $(BDB_CPPFLAGS) endif test_test_bitcoin_LDADD += $(LIBBITCOIN_NODE) $(LIBBITCOIN_CLI) $(LIBBITCOIN_COMMON) $(LIBBITCOIN_UTIL) $(LIBBITCOIN_CONSENSUS) $(LIBBITCOIN_CRYPTO) $(LIBUNIVALUE) \ - $(LIBLEVELDB) $(LIBLEVELDB_SSE42) $(LIBMEMENV) $(LIBSECP256K1) $(EVENT_LIBS) $(EVENT_PTHREADS_LIBS) $(MINISKETCH_LIBS) + $(LIBLEVELDB) $(LIBMEMENV) $(LIBSECP256K1) $(EVENT_LIBS) $(EVENT_PTHREADS_LIBS) $(MINISKETCH_LIBS) test_test_bitcoin_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) test_test_bitcoin_LDADD += $(BDB_LIBS) $(MINIUPNPC_LIBS) $(NATPMP_LIBS) $(SQLITE_LIBS) @@ -262,6 +273,7 @@ test_fuzz_fuzz_SOURCES = \ test/fuzz/locale.cpp \ test/fuzz/merkleblock.cpp \ test/fuzz/message.cpp \ + test/fuzz/miniscript.cpp \ test/fuzz/minisketch.cpp \ test/fuzz/muhash.cpp \ test/fuzz/multiplication_overflow.cpp \ @@ -316,6 +328,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 \ @@ -326,7 +339,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) @@ -356,14 +369,14 @@ endif if TARGET_WINDOWS else if ENABLE_BENCH - @echo "Running bench/bench_bitcoin ..." - $(BENCH_BINARY) > /dev/null + @echo "Running bench/bench_bitcoin (one iteration sanity check)..." + $(BENCH_BINARY) --sanity-check > /dev/null 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) @@ -372,11 +385,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) @@ -384,8 +392,19 @@ univalue_test_object_LDFLAGS = -static $(LIBTOOL_APP_LDFLAGS) endif %.cpp.test: %.cpp - @echo Running tests: `cat $< | grep -E "(BOOST_FIXTURE_TEST_SUITE\\(|BOOST_AUTO_TEST_SUITE\\()" | cut -d '(' -f 2 | cut -d ',' -f 1 | cut -d ')' -f 1` from $< - $(AM_V_at)$(TEST_BINARY) --catch_system_errors=no -l test_suite -t "`cat $< | grep -E "(BOOST_FIXTURE_TEST_SUITE\\(|BOOST_AUTO_TEST_SUITE\\()" | cut -d '(' -f 2 | cut -d ',' -f 1 | cut -d ')' -f 1`" -- DEBUG_LOG_OUT > $<.log 2>&1 || (cat $<.log && false) + @echo Running tests: $$(\ + cat $< | \ + grep -E "(BOOST_FIXTURE_TEST_SUITE\\(|BOOST_AUTO_TEST_SUITE\\()" | \ + cut -d '(' -f 2 | cut -d ',' -f 1 | cut -d ')' -f 1\ + ) from $< + $(AM_V_at)export TEST_LOGFILE=$(abs_builddir)/$$(\ + echo $< | grep -E -o "(wallet/test/.*\.cpp|test/.*\.cpp)" | $(SED) -e s/\.cpp/.log/ \ + ) && \ + $(TEST_BINARY) --catch_system_errors=no -l test_suite -t "$$(\ + cat $< | \ + grep -E "(BOOST_FIXTURE_TEST_SUITE\\(|BOOST_AUTO_TEST_SUITE\\()" | \ + cut -d '(' -f 2 | cut -d ',' -f 1 | cut -d ')' -f 1\ + )" -- DEBUG_LOG_OUT > "$$TEST_LOGFILE" 2>&1 || (cat "$$TEST_LOGFILE" && false) %.json.h: %.json @$(MKDIR_P) $(@D) @@ -396,12 +415,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 9574454fd2..b43816636f 100644 --- a/src/Makefile.test_fuzz.include +++ b/src/Makefile.test_fuzz.include @@ -10,6 +10,7 @@ EXTRA_LIBRARIES += \ TEST_FUZZ_H = \ test/fuzz/fuzz.h \ test/fuzz/FuzzedDataProvider.h \ + test/fuzz/mempool_utils.h \ test/fuzz/util.h libtest_fuzz_a_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) $(MINIUPNPC_CPPFLAGS) $(NATPMP_CPPFLAGS) $(EVENT_CFLAGS) $(EVENT_PTHREADS_CFLAGS) @@ -18,8 +19,3 @@ libtest_fuzz_a_SOURCES = \ test/fuzz/fuzz.cpp \ test/fuzz/util.cpp \ $(TEST_FUZZ_H) - -LIBTEST_FUZZ += $(LIBBITCOIN_NODE) -LIBTEST_FUZZ += $(LIBBITCOIN_COMMON) -LIBTEST_FUZZ += $(LIBBITCOIN_UTIL) -LIBTEST_FUZZ += $(LIBBITCOIN_CRYPTO_BASE) diff --git a/src/Makefile.test_util.include b/src/Makefile.test_util.include index 92cb8a5ce6..9306bb6fcc 100644 --- a/src/Makefile.test_util.include +++ b/src/Makefile.test_util.include @@ -34,8 +34,3 @@ libtest_util_a_SOURCES = \ test/util/validation.cpp \ test/util/wallet.cpp \ $(TEST_UTIL_H) - -LIBTEST_UTIL += $(LIBBITCOIN_NODE) -LIBTEST_UTIL += $(LIBBITCOIN_COMMON) -LIBTEST_UTIL += $(LIBBITCOIN_UTIL) -LIBTEST_UTIL += $(LIBBITCOIN_CRYPTO_BASE) diff --git a/src/addrdb.cpp b/src/addrdb.cpp index 0fa8f3c3da..31f8eadf98 100644 --- a/src/addrdb.cpp +++ b/src/addrdb.cpp @@ -13,6 +13,7 @@ #include <hash.h> #include <logging/timer.h> #include <netbase.h> +#include <netgroup.h> #include <random.h> #include <streams.h> #include <tinyformat.h> @@ -48,12 +49,11 @@ template <typename Data> bool SerializeFileDB(const std::string& prefix, const fs::path& path, const Data& data, int version) { // Generate random temporary filename - uint16_t randv = 0; - GetRandBytes((unsigned char*)&randv, sizeof(randv)); + const uint16_t randv{GetRand<uint16_t>()}; std::string tmpfn = strprintf("%s.%04x", prefix, randv); // open temp output file, and associate with CAutoFile - fs::path pathTmp = gArgs.GetDataDirNet() / tmpfn; + fs::path pathTmp = gArgs.GetDataDirNet() / fs::u8path(tmpfn); FILE *file = fsbridge::fopen(pathTmp, "wb"); CAutoFile fileout(file, SER_DISK, version); if (fileout.IsNull()) { @@ -182,10 +182,10 @@ void ReadFromStream(AddrMan& addr, CDataStream& ssPeers) DeserializeDB(ssPeers, addr, false); } -std::optional<bilingual_str> LoadAddrman(const std::vector<bool>& asmap, const ArgsManager& args, std::unique_ptr<AddrMan>& addrman) +std::optional<bilingual_str> LoadAddrman(const NetGroupManager& netgroupman, const ArgsManager& args, std::unique_ptr<AddrMan>& addrman) { auto check_addrman = std::clamp<int32_t>(args.GetIntArg("-checkaddrman", DEFAULT_ADDRMAN_CONSISTENCY_CHECKS), 0, 1000000); - addrman = std::make_unique<AddrMan>(asmap, /* deterministic */ false, /* consistency_check_ratio */ check_addrman); + addrman = std::make_unique<AddrMan>(netgroupman, /*deterministic=*/false, /*consistency_check_ratio=*/check_addrman); int64_t nStart = GetTimeMillis(); const auto path_addr{args.GetDataDirNet() / "peers.dat"}; @@ -194,7 +194,7 @@ std::optional<bilingual_str> LoadAddrman(const std::vector<bool>& asmap, const A LogPrintf("Loaded %i addresses from peers.dat %dms\n", addrman->size(), GetTimeMillis() - nStart); } catch (const DbNotFoundError&) { // Addrman can be in an inconsistent state after failure, reset it - addrman = std::make_unique<AddrMan>(asmap, /* deterministic */ false, /* consistency_check_ratio */ check_addrman); + addrman = std::make_unique<AddrMan>(netgroupman, /*deterministic=*/false, /*consistency_check_ratio=*/check_addrman); LogPrintf("Creating peers.dat because the file was not found (%s)\n", fs::quoted(fs::PathToString(path_addr))); DumpPeerAddresses(args, *addrman); } catch (const InvalidAddrManVersionError&) { @@ -203,7 +203,7 @@ std::optional<bilingual_str> LoadAddrman(const std::vector<bool>& asmap, const A return strprintf(_("Failed to rename invalid peers.dat file. Please move or delete it and try again.")); } // Addrman can be in an inconsistent state after failure, reset it - addrman = std::make_unique<AddrMan>(asmap, /* deterministic */ false, /* consistency_check_ratio */ check_addrman); + addrman = std::make_unique<AddrMan>(netgroupman, /*deterministic=*/false, /*consistency_check_ratio=*/check_addrman); LogPrintf("Creating new peers.dat because the file version was not compatible (%s). Original backed up to peers.dat.bak\n", fs::quoted(fs::PathToString(path_addr))); DumpPeerAddresses(args, *addrman); } catch (const std::exception& e) { diff --git a/src/addrdb.h b/src/addrdb.h index 4bdafb64e4..627ef3ac3c 100644 --- a/src/addrdb.h +++ b/src/addrdb.h @@ -17,6 +17,7 @@ class ArgsManager; class AddrMan; class CAddress; class CDataStream; +class NetGroupManager; struct bilingual_str; bool DumpPeerAddresses(const ArgsManager& args, const AddrMan& addr); @@ -48,7 +49,7 @@ public: }; /** Returns an error string on failure */ -std::optional<bilingual_str> LoadAddrman(const std::vector<bool>& asmap, const ArgsManager& args, std::unique_ptr<AddrMan>& addrman); +std::optional<bilingual_str> LoadAddrman(const NetGroupManager& netgroupman, const ArgsManager& args, std::unique_ptr<AddrMan>& addrman); /** * Dump the anchor IP address database (anchors.dat) diff --git a/src/addrman.cpp b/src/addrman.cpp index f91a979934..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,31 +29,31 @@ 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 std::vector<bool>& asmap) const +int AddrInfo::GetTriedBucket(const uint256& nKey, const NetGroupManager& netgroupman) const { uint64_t hash1 = (CHashWriter(SER_GETHASH, 0) << nKey << GetKey()).GetCheapHash(); - uint64_t hash2 = (CHashWriter(SER_GETHASH, 0) << nKey << GetGroup(asmap) << (hash1 % ADDRMAN_TRIED_BUCKETS_PER_GROUP)).GetCheapHash(); + uint64_t hash2 = (CHashWriter(SER_GETHASH, 0) << nKey << netgroupman.GetGroup(*this) << (hash1 % ADDRMAN_TRIED_BUCKETS_PER_GROUP)).GetCheapHash(); return hash2 % ADDRMAN_TRIED_BUCKET_COUNT; } -int AddrInfo::GetNewBucket(const uint256& nKey, const CNetAddr& src, const std::vector<bool>& asmap) const +int AddrInfo::GetNewBucket(const uint256& nKey, const CNetAddr& src, const NetGroupManager& netgroupman) const { - std::vector<unsigned char> vchSourceGroupKey = src.GetGroup(asmap); - uint64_t hash1 = (CHashWriter(SER_GETHASH, 0) << nKey << GetGroup(asmap) << vchSourceGroupKey).GetCheapHash(); + std::vector<unsigned char> vchSourceGroupKey = netgroupman.GetGroup(src); + uint64_t hash1 = (CHashWriter(SER_GETHASH, 0) << nKey << netgroupman.GetGroup(*this) << vchSourceGroupKey).GetCheapHash(); uint64_t hash2 = (CHashWriter(SER_GETHASH, 0) << nKey << vchSourceGroupKey << (hash1 % ADDRMAN_NEW_BUCKETS_PER_SOURCE_GROUP)).GetCheapHash(); return hash2 % ADDRMAN_NEW_BUCKET_COUNT; } @@ -64,34 +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 (nLastTry && nLastTry >= nNow - 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 (nTime == 0 || 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)); @@ -99,11 +104,11 @@ double AddrInfo::GetChance(int64_t nNow) const return fChance; } -AddrManImpl::AddrManImpl(std::vector<bool>&& asmap, bool deterministic, int32_t consistency_check_ratio) +AddrManImpl::AddrManImpl(const NetGroupManager& netgroupman, bool deterministic, int32_t consistency_check_ratio) : insecure_rand{deterministic} , nKey{deterministic ? uint256{1} : insecure_rand.rand256()} , m_consistency_check_ratio{consistency_check_ratio} - , m_asmap{std::move(asmap)} + , m_netgroupman{netgroupman} { for (auto& bucket : vvNew) { for (auto& entry : bucket) { @@ -218,11 +223,7 @@ void AddrManImpl::Serialize(Stream& s_) const } // Store asmap checksum after bucket entries so that it // can be ignored by older clients for backward compatibility. - uint256 asmap_checksum; - if (m_asmap.size() != 0) { - asmap_checksum = SerializeHash(m_asmap); - } - s << asmap_checksum; + s << m_netgroupman.GetAsmapChecksum(); } template <typename Stream> @@ -246,12 +247,18 @@ void AddrManImpl::Unserialize(Stream& s_) uint8_t compat; s >> compat; + if (compat < INCOMPATIBILITY_BASE) { + throw std::ios_base::failure(strprintf( + "Corrupted addrman database: The compat value (%u) " + "is lower than the expected minimum value %u.", + compat, INCOMPATIBILITY_BASE)); + } const uint8_t lowest_compatible = compat - INCOMPATIBILITY_BASE; if (lowest_compatible > FILE_FORMAT) { throw InvalidAddrManVersionError(strprintf( "Unsupported format of addrman database: %u. It is compatible with formats >=%u, " "but the maximum supported by this version of %s is %u.", - uint8_t{format}, uint8_t{lowest_compatible}, PACKAGE_NAME, uint8_t{FILE_FORMAT})); + uint8_t{format}, lowest_compatible, PACKAGE_NAME, uint8_t{FILE_FORMAT})); } s >> nKey; @@ -292,7 +299,7 @@ void AddrManImpl::Unserialize(Stream& s_) for (int n = 0; n < nTried; n++) { AddrInfo info; s >> info; - int nKBucket = info.GetTriedBucket(nKey, m_asmap); + int nKBucket = info.GetTriedBucket(nKey, m_netgroupman); int nKBucketPos = info.GetBucketPosition(nKey, false, nKBucket); if (info.IsValid() && vvTried[nKBucket][nKBucketPos] == -1) { @@ -329,10 +336,7 @@ void AddrManImpl::Unserialize(Stream& s_) // If the bucket count and asmap checksum haven't changed, then attempt // to restore the entries to the buckets/positions they were in before // serialization. - uint256 supplied_asmap_checksum; - if (m_asmap.size() != 0) { - supplied_asmap_checksum = SerializeHash(m_asmap); - } + uint256 supplied_asmap_checksum{m_netgroupman.GetAsmapChecksum()}; uint256 serialized_asmap_checksum; if (format >= Format::V2_ASMAP) { s >> serialized_asmap_checksum; @@ -365,7 +369,7 @@ void AddrManImpl::Unserialize(Stream& s_) } else { // In case the new table data cannot be used (bucket count wrong or new asmap), // try to give them a reference based on their primary source address. - bucket = info.GetNewBucket(nKey, m_asmap); + bucket = info.GetNewBucket(nKey, m_netgroupman); bucket_position = info.GetBucketPosition(nKey, true, bucket); if (vvNew[bucket][bucket_position] == -1) { vvNew[bucket][bucket_position] = entry_index; @@ -489,7 +493,7 @@ void AddrManImpl::MakeTried(AddrInfo& info, int nId) AssertLockHeld(cs); // remove the entry from all new buckets - const int start_bucket{info.GetNewBucket(nKey, m_asmap)}; + const int start_bucket{info.GetNewBucket(nKey, m_netgroupman)}; for (int n = 0; n < ADDRMAN_NEW_BUCKET_COUNT; ++n) { const int bucket{(start_bucket + n) % ADDRMAN_NEW_BUCKET_COUNT}; const int pos{info.GetBucketPosition(nKey, true, bucket)}; @@ -504,7 +508,7 @@ void AddrManImpl::MakeTried(AddrInfo& info, int nId) assert(info.nRefCount == 0); // which tried bucket to move the entry to - int nKBucket = info.GetTriedBucket(nKey, m_asmap); + int nKBucket = info.GetTriedBucket(nKey, m_netgroupman); int nKBucketPos = info.GetBucketPosition(nKey, false, nKBucket); // first make space to add it (the existing tried entry there is moved to new, deleting whatever is there). @@ -520,7 +524,7 @@ void AddrManImpl::MakeTried(AddrInfo& info, int nId) nTried--; // find which new bucket it belongs to - int nUBucket = infoOld.GetNewBucket(nKey, m_asmap); + int nUBucket = infoOld.GetNewBucket(nKey, m_netgroupman); int nUBucketPos = infoOld.GetBucketPosition(nKey, true, nUBucket); ClearNew(nUBucket, nUBucketPos); assert(vvNew[nUBucket][nUBucketPos] == -1); @@ -539,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); @@ -551,22 +555,24 @@ 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 (addr.nTime && (!pinfo->nTime || 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 pinfo->nServices = ServiceFlags(pinfo->nServices | addr.nServices); // do not update if no new information is present - if (!addr.nTime || (pinfo->nTime && addr.nTime <= pinfo->nTime)) + if (addr.nTime <= pinfo->nTime) { return false; + } // do not update if the entry was already in the "tried" table if (pinfo->fInTried) @@ -584,11 +590,11 @@ 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++; } - int nUBucket = pinfo->GetNewBucket(nKey, source, m_asmap); + int nUBucket = pinfo->GetNewBucket(nKey, source, m_netgroupman); int nUBucketPos = pinfo->GetBucketPosition(nKey, true, nUBucket); bool fInsert = vvNew[nUBucket][nUBucketPos] == -1; if (vvNew[nUBucket][nUBucketPos] != nId) { @@ -604,7 +610,7 @@ bool AddrManImpl::AddSingle(const CAddress& addr, const CNetAddr& source, int64_ pinfo->nRefCount++; vvNew[nUBucket][nUBucketPos] = nId; LogPrint(BCLog::ADDRMAN, "Added %s mapped to AS%i to new[%i][%i]\n", - addr.ToString(), addr.GetMappedAS(m_asmap), nUBucket, nUBucketPos); + addr.ToString(), m_netgroupman.GetMappedAS(addr), nUBucket, nUBucketPos); } else { if (pinfo->nRefCount == 0) { Delete(nId); @@ -614,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); @@ -630,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. @@ -644,7 +650,7 @@ bool AddrManImpl::Good_(const CService& addr, bool test_before_evict, int64_t nT // which tried bucket to move the entry to - int tried_bucket = info.GetTriedBucket(nKey, m_asmap); + int tried_bucket = info.GetTriedBucket(nKey, m_netgroupman); int tried_bucket_pos = info.GetBucketPosition(nKey, false, tried_bucket); // Will moving this address into tried evict another entry? @@ -663,16 +669,16 @@ bool AddrManImpl::Good_(const CService& addr, bool test_before_evict, int64_t nT // move nId to the tried tables MakeTried(info, nId); LogPrint(BCLog::ADDRMAN, "Moved %s mapped to AS%i to tried[%i][%i]\n", - addr.ToString(), addr.GetMappedAS(m_asmap), tried_bucket, tried_bucket_pos); + addr.ToString(), m_netgroupman.GetMappedAS(addr), tried_bucket, tried_bucket_pos); return true; } } -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); @@ -680,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); @@ -693,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); @@ -733,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; @@ -761,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; @@ -782,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) @@ -807,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); @@ -820,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) @@ -857,7 +864,7 @@ void AddrManImpl::ResolveCollisions_() AddrInfo& info_new = mapInfo[id_new]; // Which tried bucket to move the entry to. - int tried_bucket = info_new.GetTriedBucket(nKey, m_asmap); + int tried_bucket = info_new.GetTriedBucket(nKey, m_netgroupman); int tried_bucket_pos = info_new.GetBucketPosition(nKey, false, tried_bucket); if (!info_new.IsValid()) { // id_new may no longer map to a valid address erase_collision = true; @@ -867,29 +874,31 @@ void AddrManImpl::ResolveCollisions_() int id_old = vvTried[tried_bucket][tried_bucket_pos]; AddrInfo& info_old = mapInfo[id_old]; + const auto current_time{Now<NodeSeconds>()}; + // Has successfully connected in last X hours - if (GetAdjustedTime() - info_old.nLastSuccess < ADDRMAN_REPLACEMENT_HOURS*(60*60)) { + if (current_time - info_old.m_last_success < ADDRMAN_REPLACEMENT) { erase_collision = true; - } else if (GetAdjustedTime() - 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 (GetAdjustedTime() - 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, GetAdjustedTime()); + Good_(info_new, false, current_time); erase_collision = true; } - } else if (GetAdjustedTime() - 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. LogPrint(BCLog::ADDRMAN, "Unable to test; replacing %s with %s in tried table anyway\n", info_old.ToString(), info_new.ToString()); - Good_(info_new, false, GetAdjustedTime()); + Good_(info_new, false, current_time); 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; } } @@ -902,7 +911,7 @@ void AddrManImpl::ResolveCollisions_() } } -std::pair<CAddress, int64_t> AddrManImpl::SelectTriedCollision_() +std::pair<CAddress, NodeSeconds> AddrManImpl::SelectTriedCollision_() { AssertLockHeld(cs); @@ -923,11 +932,11 @@ std::pair<CAddress, int64_t> AddrManImpl::SelectTriedCollision_() const AddrInfo& newInfo = mapInfo[id_new]; // which tried bucket to move the entry to - int tried_bucket = newInfo.GetTriedBucket(nKey, m_asmap); + int tried_bucket = newInfo.GetTriedBucket(nKey, m_netgroupman); 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) @@ -939,17 +948,17 @@ std::optional<AddressPosition> AddrManImpl::FindAddressEntry_(const CAddress& ad if (!addr_info) return std::nullopt; if(addr_info->fInTried) { - int bucket{addr_info->GetTriedBucket(nKey, m_asmap)}; - return AddressPosition(/*tried=*/true, - /*multiplicity=*/1, - /*bucket=*/bucket, - /*position=*/addr_info->GetBucketPosition(nKey, false, bucket)); + int bucket{addr_info->GetTriedBucket(nKey, m_netgroupman)}; + return AddressPosition(/*tried_in=*/true, + /*multiplicity_in=*/1, + /*bucket_in=*/bucket, + /*position_in=*/addr_info->GetBucketPosition(nKey, false, bucket)); } else { - int bucket{addr_info->GetNewBucket(nKey, m_asmap)}; - return AddressPosition(/*tried=*/false, - /*multiplicity=*/addr_info->nRefCount, - /*bucket=*/bucket, - /*position=*/addr_info->GetBucketPosition(nKey, true, bucket)); + int bucket{addr_info->GetNewBucket(nKey, m_netgroupman)}; + return AddressPosition(/*tried_in=*/false, + /*multiplicity_in=*/addr_info->nRefCount, + /*bucket_in=*/bucket, + /*position_in=*/addr_info->GetBucketPosition(nKey, true, bucket)); } } @@ -985,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); @@ -1003,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) @@ -1020,7 +1032,7 @@ int AddrManImpl::CheckAddrman() const if (!setTried.count(vvTried[n][i])) return -11; const auto it{mapInfo.find(vvTried[n][i])}; - if (it == mapInfo.end() || it->second.GetTriedBucket(nKey, m_asmap) != n) { + if (it == mapInfo.end() || it->second.GetTriedBucket(nKey, m_netgroupman) != n) { return -17; } if (it->second.GetBucketPosition(nKey, false, n) != i) { @@ -1062,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(); } @@ -1096,7 +1108,7 @@ void AddrManImpl::ResolveCollisions() Check(); } -std::pair<CAddress, int64_t> AddrManImpl::SelectTriedCollision() +std::pair<CAddress, NodeSeconds> AddrManImpl::SelectTriedCollision() { LOCK(cs); Check(); @@ -1105,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(); @@ -1123,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(); } @@ -1148,13 +1160,8 @@ std::optional<AddressPosition> AddrManImpl::FindAddressEntry(const CAddress& add return entry; } -const std::vector<bool>& AddrManImpl::GetAsmap() const -{ - return m_asmap; -} - -AddrMan::AddrMan(std::vector<bool> asmap, bool deterministic, int32_t consistency_check_ratio) - : m_impl(std::make_unique<AddrManImpl>(std::move(asmap), deterministic, consistency_check_ratio)) {} +AddrMan::AddrMan(const NetGroupManager& netgroupman, bool deterministic, int32_t consistency_check_ratio) + : m_impl(std::make_unique<AddrManImpl>(netgroupman, deterministic, consistency_check_ratio)) {} AddrMan::~AddrMan() = default; @@ -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) @@ -1229,11 +1236,6 @@ void AddrMan::SetServices(const CService& addr, ServiceFlags nServices) m_impl->SetServices(addr, nServices); } -const std::vector<bool>& AddrMan::GetAsmap() const -{ - return m_impl->GetAsmap(); -} - std::optional<AddressPosition> AddrMan::FindAddressEntry(const CAddress& addr) { return m_impl->FindAddressEntry(addr); diff --git a/src/addrman.h b/src/addrman.h index 472282833b..5099c8c7a3 100644 --- a/src/addrman.h +++ b/src/addrman.h @@ -7,9 +7,10 @@ #define BITCOIN_ADDRMAN_H #include <netaddress.h> +#include <netgroup.h> #include <protocol.h> #include <streams.h> -#include <timedata.h> +#include <util/time.h> #include <cstdint> #include <memory> @@ -88,7 +89,7 @@ protected: const std::unique_ptr<AddrManImpl> m_impl; public: - explicit AddrMan(std::vector<bool> asmap, bool deterministic, int32_t consistency_check_ratio); + explicit AddrMan(const NetGroupManager& netgroupman, bool deterministic, int32_t consistency_check_ratio); ~AddrMan(); @@ -106,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(); @@ -132,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. @@ -165,15 +166,13 @@ 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); - const std::vector<bool>& GetAsmap() const; - /** Test-only function * Find the address record in AddrMan and return information about its * position. diff --git a/src/addrman_impl.h b/src/addrman_impl.h index 5e76f72342..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) @@ -76,31 +78,31 @@ public: } //! Calculate in which "tried" bucket this entry belongs - int GetTriedBucket(const uint256 &nKey, const std::vector<bool> &asmap) const; + int GetTriedBucket(const uint256& nKey, const NetGroupManager& netgroupman) const; //! Calculate in which "new" bucket this entry belongs, given a certain source - int GetNewBucket(const uint256 &nKey, const CNetAddr& src, const std::vector<bool> &asmap) const; + int GetNewBucket(const uint256& nKey, const CNetAddr& src, const NetGroupManager& netgroupman) const; //! Calculate in which "new" bucket this entry belongs, using its default source - int GetNewBucket(const uint256 &nKey, const std::vector<bool> &asmap) const + int GetNewBucket(const uint256& nKey, const NetGroupManager& netgroupman) const { - return GetNewBucket(nKey, source, asmap); + return GetNewBucket(nKey, source, netgroupman); } //! Calculate in which position of a bucket to store this entry. 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 { public: - AddrManImpl(std::vector<bool>&& asmap, bool deterministic, int32_t consistency_check_ratio); + AddrManImpl(const NetGroupManager& netgroupman, bool deterministic, int32_t consistency_check_ratio); ~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) @@ -140,8 +142,6 @@ public: std::optional<AddressPosition> FindAddressEntry(const CAddress& addr) EXCLUSIVE_LOCKS_REQUIRED(!cs); - const std::vector<bool>& GetAsmap() const; - friend class AddrManDeterministic; private: @@ -204,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; @@ -212,21 +212,8 @@ private: /** Perform consistency checks every m_consistency_check_ratio operations (if non-zero). */ const int32_t m_consistency_check_ratio; - // Compressed IP->ASN mapping, loaded from a file when a node starts. - // Should be always empty if no file was provided. - // This mapping is then used for bucketing nodes in Addrman. - // - // If asmap is provided, nodes will be bucketed by - // AS they belong to, in order to make impossible for a node - // to connect to several nodes hosted in a single AS. - // This is done in response to Erebus attack, but also to generally - // diversify the connections every node creates, - // especially useful when a large fraction of nodes - // operate under a couple of cloud providers. - // - // If a new asmap was provided, the existing records - // would be re-bucketed accordingly. - const std::vector<bool> m_asmap; + /** Reference to the netgroup manager. netgroupman must be constructed before addrman and destructed after. */ + const NetGroupManager& m_netgroupman; //! Find an entry. AddrInfo* Find(const CService& addr, int* pnId = nullptr) EXCLUSIVE_LOCKS_REQUIRED(cs); @@ -248,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/arith_uint256.cpp b/src/arith_uint256.cpp index f7f62dfc68..e614102de3 100644 --- a/src/arith_uint256.cpp +++ b/src/arith_uint256.cpp @@ -146,13 +146,21 @@ double base_uint<BITS>::getdouble() const template <unsigned int BITS> std::string base_uint<BITS>::GetHex() const { - return ArithToUint256(*this).GetHex(); + base_blob<BITS> b; + for (int x = 0; x < this->WIDTH; ++x) { + WriteLE32(b.begin() + x*4, this->pn[x]); + } + return b.GetHex(); } template <unsigned int BITS> void base_uint<BITS>::SetHex(const char* psz) { - *this = UintToArith256(uint256S(psz)); + base_blob<BITS> b; + b.SetHex(psz); + for (int x = 0; x < this->WIDTH; ++x) { + this->pn[x] = ReadLE32(b.begin() + x*4); + } } template <unsigned int BITS> @@ -164,7 +172,7 @@ void base_uint<BITS>::SetHex(const std::string& str) template <unsigned int BITS> std::string base_uint<BITS>::ToString() const { - return (GetHex()); + return GetHex(); } template <unsigned int BITS> @@ -183,20 +191,7 @@ unsigned int base_uint<BITS>::bits() const } // Explicit instantiations for base_uint<256> -template base_uint<256>::base_uint(const std::string&); -template base_uint<256>& base_uint<256>::operator<<=(unsigned int); -template base_uint<256>& base_uint<256>::operator>>=(unsigned int); -template base_uint<256>& base_uint<256>::operator*=(uint32_t b32); -template base_uint<256>& base_uint<256>::operator*=(const base_uint<256>& b); -template base_uint<256>& base_uint<256>::operator/=(const base_uint<256>& b); -template int base_uint<256>::CompareTo(const base_uint<256>&) const; -template bool base_uint<256>::EqualTo(uint64_t) const; -template double base_uint<256>::getdouble() const; -template std::string base_uint<256>::GetHex() const; -template std::string base_uint<256>::ToString() const; -template void base_uint<256>::SetHex(const char*); -template void base_uint<256>::SetHex(const std::string&); -template unsigned int base_uint<256>::bits() const; +template class base_uint<256>; // This implementation directly uses shifts instead of going // through an intermediate MPI representation. diff --git a/src/arith_uint256.h b/src/arith_uint256.h index a0a0429c2a..b7b3b3a285 100644 --- a/src/arith_uint256.h +++ b/src/arith_uint256.h @@ -24,22 +24,19 @@ template<unsigned int BITS> class base_uint { protected: + static_assert(BITS / 32 > 0 && BITS % 32 == 0, "Template parameter BITS must be a positive multiple of 32."); static constexpr int WIDTH = BITS / 32; uint32_t pn[WIDTH]; public: base_uint() { - static_assert(BITS/32 > 0 && BITS%32 == 0, "Template parameter BITS must be a positive multiple of 32."); - for (int i = 0; i < WIDTH; i++) pn[i] = 0; } base_uint(const base_uint& b) { - static_assert(BITS/32 > 0 && BITS%32 == 0, "Template parameter BITS must be a positive multiple of 32."); - for (int i = 0; i < WIDTH; i++) pn[i] = b.pn[i]; } @@ -53,8 +50,6 @@ public: base_uint(uint64_t b) { - static_assert(BITS/32 > 0 && BITS%32 == 0, "Template parameter BITS must be a positive multiple of 32."); - pn[0] = (unsigned int)b; pn[1] = (unsigned int)(b >> 32); for (int i = 2; i < WIDTH; i++) @@ -284,4 +279,6 @@ public: uint256 ArithToUint256(const arith_uint256 &); arith_uint256 UintToArith256(const uint256 &); +extern template class base_uint<256>; + #endif // BITCOIN_ARITH_UINT256_H diff --git a/src/banman.cpp b/src/banman.cpp index b28e3f7f7c..508383d9f2 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> @@ -16,6 +16,19 @@ BanMan::BanMan(fs::path ban_file, CClientUIInterface* client_interface, int64_t default_ban_time) : m_client_interface(client_interface), m_ban_db(std::move(ban_file)), m_default_ban_time(default_ban_time) { + LoadBanlist(); + DumpBanlist(); +} + +BanMan::~BanMan() +{ + DumpBanlist(); +} + +void BanMan::LoadBanlist() +{ + LOCK(m_cs_banned); + if (m_client_interface) m_client_interface->InitMessage(_("Loading banlist…").translated); int64_t n_start = GetTimeMillis(); @@ -29,13 +42,6 @@ BanMan::BanMan(fs::path ban_file, CClientUIInterface* client_interface, int64_t m_banned = {}; m_is_dirty = true; } - - DumpBanlist(); -} - -BanMan::~BanMan() -{ - DumpBanlist(); } void BanMan::DumpBanlist() @@ -173,23 +179,24 @@ void BanMan::GetBanned(banmap_t& banmap) void BanMan::SweepBanned() { + AssertLockHeld(m_cs_banned); + int64_t now = GetTime(); bool notify_ui = false; - { - LOCK(m_cs_banned); - banmap_t::iterator it = m_banned.begin(); - while (it != m_banned.end()) { - CSubNet sub_net = (*it).first; - CBanEntry ban_entry = (*it).second; - if (!sub_net.IsValid() || now > ban_entry.nBanUntil) { - m_banned.erase(it++); - m_is_dirty = true; - notify_ui = true; - LogPrint(BCLog::NET, "Removed banned node address/subnet: %s\n", sub_net.ToString()); - } else - ++it; + banmap_t::iterator it = m_banned.begin(); + while (it != m_banned.end()) { + CSubNet sub_net = (*it).first; + CBanEntry ban_entry = (*it).second; + if (!sub_net.IsValid() || now > ban_entry.nBanUntil) { + m_banned.erase(it++); + m_is_dirty = true; + notify_ui = true; + LogPrint(BCLog::NET, "Removed banned node address/subnet: %s\n", sub_net.ToString()); + } else { + ++it; } } + // update UI if (notify_ui && m_client_interface) { m_client_interface->BannedListChanged(); diff --git a/src/banman.h b/src/banman.h index f268fffa5a..77b043f081 100644 --- a/src/banman.h +++ b/src/banman.h @@ -80,11 +80,12 @@ public: void DumpBanlist(); private: + void LoadBanlist() EXCLUSIVE_LOCKS_REQUIRED(!m_cs_banned); bool BannedSetIsDirty(); //!set the "dirty" flag for the banlist void SetBannedSetDirty(bool dirty = true); //!clean unused entries (if bantime has expired) - void SweepBanned(); + void SweepBanned() EXCLUSIVE_LOCKS_REQUIRED(m_cs_banned); RecursiveMutex m_cs_banned; banmap_t m_banned GUARDED_BY(m_cs_banned); diff --git a/src/base58.cpp b/src/base58.cpp index dfa2e8db55..11c1ce7397 100644 --- a/src/base58.cpp +++ b/src/base58.cpp @@ -126,7 +126,7 @@ std::string EncodeBase58(Span<const unsigned char> input) bool DecodeBase58(const std::string& str, std::vector<unsigned char>& vchRet, int max_ret_len) { - if (!ValidAsCString(str)) { + if (!ContainsNoNUL(str)) { return false; } return DecodeBase58(str.c_str(), vchRet, max_ret_len); @@ -160,7 +160,7 @@ std::string EncodeBase58Check(Span<const unsigned char> input) bool DecodeBase58Check(const std::string& str, std::vector<unsigned char>& vchRet, int max_ret) { - if (!ValidAsCString(str)) { + if (!ContainsNoNUL(str)) { return false; } return DecodeBase58Check(str.c_str(), vchRet, max_ret); diff --git a/src/base58.h b/src/base58.h index 9ba5af73e0..d2a8d5e3bc 100644 --- a/src/base58.h +++ b/src/base58.h @@ -14,7 +14,6 @@ #ifndef BITCOIN_BASE58_H #define BITCOIN_BASE58_H -#include <attributes.h> #include <span.h> #include <string> diff --git a/src/bench/addrman.cpp b/src/bench/addrman.cpp index 3ca58b923e..f14d1f89b6 100644 --- a/src/bench/addrman.cpp +++ b/src/bench/addrman.cpp @@ -4,6 +4,7 @@ #include <addrman.h> #include <bench/bench.h> +#include <netgroup.h> #include <random.h> #include <util/check.h> #include <util/time.h> @@ -16,7 +17,7 @@ static constexpr size_t NUM_SOURCES = 64; static constexpr size_t NUM_ADDRESSES_PER_SOURCE = 256; -static const std::vector<bool> EMPTY_ASMAP; +static NetGroupManager EMPTY_NETGROUPMAN{std::vector<bool>()}; static constexpr uint32_t ADDRMAN_CONSISTENCY_CHECK_RATIO{0}; static std::vector<CAddress> g_sources; @@ -42,7 +43,7 @@ static void CreateAddresses() CAddress ret(CService(addr, port), NODE_NETWORK); - ret.nTime = GetAdjustedTime(); + ret.nTime = Now<NodeSeconds>(); return ret; }; @@ -77,14 +78,14 @@ static void AddrManAdd(benchmark::Bench& bench) CreateAddresses(); bench.run([&] { - AddrMan addrman{EMPTY_ASMAP, /*deterministic=*/false, ADDRMAN_CONSISTENCY_CHECK_RATIO}; + AddrMan addrman{EMPTY_NETGROUPMAN, /*deterministic=*/false, ADDRMAN_CONSISTENCY_CHECK_RATIO}; AddAddressesToAddrMan(addrman); }); } static void AddrManSelect(benchmark::Bench& bench) { - AddrMan addrman{EMPTY_ASMAP, /*deterministic=*/false, ADDRMAN_CONSISTENCY_CHECK_RATIO}; + AddrMan addrman{EMPTY_NETGROUPMAN, /*deterministic=*/false, ADDRMAN_CONSISTENCY_CHECK_RATIO}; FillAddrMan(addrman); @@ -96,12 +97,12 @@ static void AddrManSelect(benchmark::Bench& bench) static void AddrManGetAddr(benchmark::Bench& bench) { - AddrMan addrman{EMPTY_ASMAP, /*deterministic=*/false, ADDRMAN_CONSISTENCY_CHECK_RATIO}; + AddrMan addrman{EMPTY_NETGROUPMAN, /*deterministic=*/false, ADDRMAN_CONSISTENCY_CHECK_RATIO}; FillAddrMan(addrman); bench.run([&] { - const auto& addresses = addrman.GetAddr(/* max_addresses */ 2500, /* max_pct */ 23, /* network */ std::nullopt); + const auto& addresses = addrman.GetAddr(/*max_addresses=*/2500, /*max_pct=*/23, /*network=*/std::nullopt); assert(addresses.size() > 0); }); } @@ -125,7 +126,7 @@ static void AddrManAddThenGood(benchmark::Bench& bench) // // This has some overhead (exactly the result of AddrManAdd benchmark), but that overhead is constant so improvements in // AddrMan::Good() will still be noticeable. - AddrMan addrman{EMPTY_ASMAP, /*deterministic=*/false, ADDRMAN_CONSISTENCY_CHECK_RATIO}; + AddrMan addrman{EMPTY_NETGROUPMAN, /*deterministic=*/false, ADDRMAN_CONSISTENCY_CHECK_RATIO}; AddAddressesToAddrMan(addrman); markSomeAsGood(addrman); diff --git a/src/bench/bench.cpp b/src/bench/bench.cpp index 033d319750..26975bb59d 100644 --- a/src/bench/bench.cpp +++ b/src/bench/bench.cpp @@ -57,6 +57,10 @@ void benchmark::BenchRunner::RunAll(const Args& args) std::regex reFilter(args.regex_filter); std::smatch baseMatch; + if (args.sanity_check) { + std::cout << "Running with --sanity-check option, benchmark results will be useless." << std::endl; + } + std::vector<ankerl::nanobench::Result> benchmarkResults; for (const auto& p : benchmarks()) { if (!std::regex_match(p.first, baseMatch, reFilter)) { @@ -69,6 +73,9 @@ void benchmark::BenchRunner::RunAll(const Args& args) } Bench bench; + if (args.sanity_check) { + bench.epochs(1).epochIterations(1); + } bench.name(p.first); if (args.min_time > 0ms) { // convert to nanos before dividing to reduce rounding errors diff --git a/src/bench/bench.h b/src/bench/bench.h index 6634138beb..17535e4e81 100644 --- a/src/bench/bench.h +++ b/src/bench/bench.h @@ -43,6 +43,7 @@ typedef std::function<void(Bench&)> BenchFunction; struct Args { bool is_list_only; + bool sanity_check; std::chrono::milliseconds min_time; std::vector<double> asymptote; fs::path output_csv; diff --git a/src/bench/bench_bitcoin.cpp b/src/bench/bench_bitcoin.cpp index 3f8bff4bcf..1bb4d34db9 100644 --- a/src/bench/bench_bitcoin.cpp +++ b/src/bench/bench_bitcoin.cpp @@ -26,9 +26,10 @@ static void SetupBenchArgs(ArgsManager& argsman) argsman.AddArg("-asymptote=<n1,n2,n3,...>", "Test asymptotic growth of the runtime of an algorithm, if supported by the benchmark", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); argsman.AddArg("-filter=<regex>", strprintf("Regular expression filter to select benchmark by name (default: %s)", DEFAULT_BENCH_FILTER), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); argsman.AddArg("-list", "List benchmarks without executing them", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); - argsman.AddArg("-min_time=<milliseconds>", strprintf("Minimum runtime per benchmark, in milliseconds (default: %d)", DEFAULT_MIN_TIME_MS), ArgsManager::ALLOW_ANY | ArgsManager::DISALLOW_NEGATION, OptionsCategory::OPTIONS); - argsman.AddArg("-output_csv=<output.csv>", "Generate CSV file with the most important benchmark results", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); - argsman.AddArg("-output_json=<output.json>", "Generate JSON file with all benchmark results", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); + argsman.AddArg("-min-time=<milliseconds>", strprintf("Minimum runtime per benchmark, in milliseconds (default: %d)", DEFAULT_MIN_TIME_MS), ArgsManager::ALLOW_ANY | ArgsManager::DISALLOW_NEGATION, OptionsCategory::OPTIONS); + argsman.AddArg("-output-csv=<output.csv>", "Generate CSV file with the most important benchmark results", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); + argsman.AddArg("-output-json=<output.json>", "Generate JSON file with all benchmark results", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); + argsman.AddArg("-sanity-check", "Run benchmarks for only one iteration", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); } // parses a comma separated list like "10,20,30,50" @@ -73,7 +74,7 @@ int main(int argc, char** argv) " sure each run has exactly the same preconditions.\n" "\n" " * If results are still not reliable, increase runtime with e.g.\n" - " -min_time=5000 to let a benchmark run for at least 5 seconds.\n" + " -min-time=5000 to let a benchmark run for at least 5 seconds.\n" "\n" " * bench_bitcoin uses nanobench [3] for which there is extensive\n" " documentation available online.\n" @@ -108,10 +109,11 @@ int main(int argc, char** argv) benchmark::Args args; args.asymptote = parseAsymptote(argsman.GetArg("-asymptote", "")); args.is_list_only = argsman.GetBoolArg("-list", false); - args.min_time = std::chrono::milliseconds(argsman.GetIntArg("-min_time", DEFAULT_MIN_TIME_MS)); - args.output_csv = fs::PathFromString(argsman.GetArg("-output_csv", "")); - args.output_json = fs::PathFromString(argsman.GetArg("-output_json", "")); + args.min_time = std::chrono::milliseconds(argsman.GetIntArg("-min-time", DEFAULT_MIN_TIME_MS)); + args.output_csv = argsman.GetPathArg("-output-csv"); + args.output_json = argsman.GetPathArg("-output-json"); args.regex_filter = argsman.GetArg("-filter", DEFAULT_BENCH_FILTER); + args.sanity_check = argsman.GetBoolArg("-sanity-check", false); benchmark::BenchRunner::RunAll(args); 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/checkqueue.cpp b/src/bench/checkqueue.cpp index d7b8c1badc..602081fb9b 100644 --- a/src/bench/checkqueue.cpp +++ b/src/bench/checkqueue.cpp @@ -30,8 +30,7 @@ static void CCheckQueueSpeedPrevectorJob(benchmark::Bench& bench) struct PrevectorJob { prevector<PREVECTOR_SIZE, uint8_t> p; - PrevectorJob(){ - } + PrevectorJob() = default; explicit PrevectorJob(FastRandomContext& insecure_rand){ p.resize(insecure_rand.randrange(PREVECTOR_SIZE*2)); } @@ -39,7 +38,10 @@ static void CCheckQueueSpeedPrevectorJob(benchmark::Bench& bench) { return true; } - void swap(PrevectorJob& x){p.swap(x.p);}; + void swap(PrevectorJob& x) noexcept + { + p.swap(x.p); + }; }; CCheckQueue<PrevectorJob> queue {QUEUE_BATCH_SIZE}; // The main thread should be counted to prevent thread oversubscription, and diff --git a/src/bench/coin_selection.cpp b/src/bench/coin_selection.cpp index 609c592d20..6ada28115e 100644 --- a/src/bench/coin_selection.cpp +++ b/src/bench/coin_selection.cpp @@ -13,7 +13,7 @@ using node::NodeContext; using wallet::AttemptSelection; -using wallet::CInputCoin; +using wallet::CHANGE_LOWER; using wallet::COutput; using wallet::CWallet; using wallet::CWalletTx; @@ -56,35 +56,42 @@ 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(wallet, *wtx, 0 /* iIn */, 6 * 24 /* nDepthIn */, true /* spendable */, true /* solvable */, true /* safe */); + 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); - const CoinSelectionParams coin_selection_params(/* change_output_size= */ 34, - /* change_spend_size= */ 148, /* effective_feerate= */ CFeeRate(0), - /* long_term_feerate= */ CFeeRate(0), /* discard_feerate= */ CFeeRate(0), - /* tx_noinputs_size= */ 0, /* avoid_partial= */ false); + FastRandomContext rand{}; + const CoinSelectionParams coin_selection_params{ + rand, + /*change_output_size=*/ 34, + /*change_spend_size=*/ 148, + /*min_change_target=*/ CHANGE_LOWER, + /*effective_feerate=*/ CFeeRate(0), + /*long_term_feerate=*/ CFeeRate(0), + /*discard_feerate=*/ CFeeRate(0), + /*tx_noinputs_size=*/ 0, + /*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); }); } -typedef std::set<CInputCoin> CoinSet; - // Copied from src/wallet/test/coinselector_tests.cpp static void add_coin(const CAmount& nValue, int nInput, std::vector<OutputGroup>& set) { CMutableTransaction tx; tx.vout.resize(nInput + 1); tx.vout[nInput].nValue = nValue; - CInputCoin coin(MakeTransactionRef(tx), nInput); + COutput output(COutPoint(tx.GetHash(), nInput), tx.vout.at(nInput), /*depth=*/ 0, /*input_bytes=*/ -1, /*spendable=*/ true, /*solvable=*/ true, /*safe=*/ true, /*time=*/ 0, /*from_me=*/ true, /*fees=*/ 0); set.emplace_back(); - set.back().Insert(coin, 0, true, 0, 0, false); + set.back().Insert(output, /*ancestors=*/ 0, /*descendants=*/ 0, /*positive_only=*/ false); } // Copied from src/wallet/test/coinselector_tests.cpp static CAmount make_hard_case(int utxos, std::vector<OutputGroup>& utxo_pool) 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/logging.cpp b/src/bench/logging.cpp new file mode 100644 index 0000000000..d28777df9e --- /dev/null +++ b/src/bench/logging.cpp @@ -0,0 +1,48 @@ +// Copyright (c) 2020 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include <bench/bench.h> +#include <logging.h> +#include <test/util/setup_common.h> + + +static void Logging(benchmark::Bench& bench, const std::vector<const char*>& extra_args, const std::function<void()>& log) +{ + TestingSetup test_setup{ + CBaseChainParams::REGTEST, + extra_args, + }; + + bench.run([&] { log(); }); +} + +static void LoggingYoThreadNames(benchmark::Bench& bench) +{ + Logging(bench, {"-logthreadnames=1"}, [] { LogPrintf("%s\n", "test"); }); +} +static void LoggingNoThreadNames(benchmark::Bench& bench) +{ + Logging(bench, {"-logthreadnames=0"}, [] { LogPrintf("%s\n", "test"); }); +} +static void LoggingYoCategory(benchmark::Bench& bench) +{ + Logging(bench, {"-logthreadnames=0", "-debug=net"}, [] { LogPrint(BCLog::NET, "%s\n", "test"); }); +} +static void LoggingNoCategory(benchmark::Bench& bench) +{ + Logging(bench, {"-logthreadnames=0", "-debug=0"}, [] { LogPrint(BCLog::NET, "%s\n", "test"); }); +} +static void LoggingNoFile(benchmark::Bench& bench) +{ + Logging(bench, {"-nodebuglogfile", "-debug=1"}, [] { + LogPrintf("%s\n", "test"); + LogPrint(BCLog::NET, "%s\n", "test"); + }); +} + +BENCHMARK(LoggingYoThreadNames); +BENCHMARK(LoggingNoThreadNames); +BENCHMARK(LoggingYoCategory); +BENCHMARK(LoggingNoCategory); +BENCHMARK(LoggingNoFile); 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/mempool_stress.cpp b/src/bench/mempool_stress.cpp index afa4618e1b..725a6f8f5b 100644 --- a/src/bench/mempool_stress.cpp +++ b/src/bench/mempool_stress.cpp @@ -86,9 +86,9 @@ static void ComplexMemPool(benchmark::Bench& bench) if (bench.complexityN() > 1) { childTxs = static_cast<int>(bench.complexityN()); } - std::vector<CTransactionRef> ordered_coins = CreateOrderedCoins(det_rand, childTxs, /* min_ancestors */ 1); + std::vector<CTransactionRef> ordered_coins = CreateOrderedCoins(det_rand, childTxs, /*min_ancestors=*/1); const auto testing_setup = MakeNoLogFileContext<const TestingSetup>(CBaseChainParams::MAIN); - CTxMemPool pool; + CTxMemPool& pool = *testing_setup.get()->m_node.mempool; LOCK2(cs_main, pool.cs); bench.run([&]() NO_THREAD_SAFETY_ANALYSIS { for (auto& tx : ordered_coins) { @@ -102,16 +102,15 @@ static void ComplexMemPool(benchmark::Bench& bench) static void MempoolCheck(benchmark::Bench& bench) { FastRandomContext det_rand{true}; - const int childTxs = bench.complexityN() > 1 ? static_cast<int>(bench.complexityN()) : 2000; - const std::vector<CTransactionRef> ordered_coins = CreateOrderedCoins(det_rand, childTxs, /* min_ancestors */ 5); - const auto testing_setup = MakeNoLogFileContext<const TestingSetup>(CBaseChainParams::MAIN, {"-checkmempool=1"}); - CTxMemPool pool; + auto testing_setup = MakeNoLogFileContext<TestChain100Setup>(CBaseChainParams::REGTEST, {"-checkmempool=1"}); + CTxMemPool& pool = *testing_setup.get()->m_node.mempool; LOCK2(cs_main, pool.cs); + testing_setup->PopulateMempool(det_rand, 400, true); const CCoinsViewCache& coins_tip = testing_setup.get()->m_node.chainman->ActiveChainstate().CoinsTip(); - for (auto& tx : ordered_coins) AddTx(tx, pool); bench.run([&]() NO_THREAD_SAFETY_ANALYSIS { - pool.check(coins_tip, /* spendheight */ 2); + // Bump up the spendheight so we don't hit premature coinbase spend errors. + pool.check(coins_tip, /*spendheight=*/300); }); } diff --git a/src/bench/prevector.cpp b/src/bench/prevector.cpp index 6343ed7848..b3688bab1b 100644 --- a/src/bench/prevector.cpp +++ b/src/bench/prevector.cpp @@ -10,8 +10,8 @@ #include <bench/bench.h> struct nontrivial_t { - int x; - nontrivial_t() :x(-1) {} + int x{-1}; + nontrivial_t() = default; SERIALIZE_METHODS(nontrivial_t, obj) { READWRITE(obj.x); } }; static_assert(!std::is_trivially_default_constructible<nontrivial_t>::value, diff --git a/src/bench/rollingbloom.cpp b/src/bench/rollingbloom.cpp index 9f1679aa84..8f05e3bad0 100644 --- a/src/bench/rollingbloom.cpp +++ b/src/bench/rollingbloom.cpp @@ -5,6 +5,9 @@ #include <bench/bench.h> #include <common/bloom.h> +#include <crypto/common.h> + +#include <vector> static void RollingBloom(benchmark::Bench& bench) { @@ -13,16 +16,10 @@ static void RollingBloom(benchmark::Bench& bench) uint32_t count = 0; bench.run([&] { count++; - data[0] = count & 0xFF; - data[1] = (count >> 8) & 0xFF; - data[2] = (count >> 16) & 0xFF; - data[3] = (count >> 24) & 0xFF; + WriteLE32(data.data(), count); filter.insert(data); - data[0] = (count >> 24) & 0xFF; - data[1] = (count >> 16) & 0xFF; - data[2] = (count >> 8) & 0xFF; - data[3] = count & 0xFF; + WriteBE32(data.data(), count); filter.contains(data); }); } diff --git a/src/bench/rpc_blockchain.cpp b/src/bench/rpc_blockchain.cpp index 2143bcf950..e6fc8d21f4 100644 --- a/src/bench/rpc_blockchain.cpp +++ b/src/bench/rpc_blockchain.cpp @@ -40,7 +40,7 @@ static void BlockToJsonVerbose(benchmark::Bench& bench) { TestBlockAndIndex data; bench.run([&] { - auto univalue = blockToJSON(data.block, &data.blockindex, &data.blockindex, TxVerbosity::SHOW_DETAILS_AND_PREVOUT); + auto univalue = blockToJSON(data.testing_setup->m_node.chainman->m_blockman, data.block, &data.blockindex, &data.blockindex, TxVerbosity::SHOW_DETAILS_AND_PREVOUT); ankerl::nanobench::doNotOptimizeAway(univalue); }); } @@ -50,7 +50,7 @@ BENCHMARK(BlockToJsonVerbose); static void BlockToJsonVerboseWrite(benchmark::Bench& bench) { TestBlockAndIndex data; - auto univalue = blockToJSON(data.block, &data.blockindex, &data.blockindex, TxVerbosity::SHOW_DETAILS_AND_PREVOUT); + auto univalue = blockToJSON(data.testing_setup->m_node.chainman->m_blockman, data.block, &data.blockindex, &data.blockindex, TxVerbosity::SHOW_DETAILS_AND_PREVOUT); bench.run([&] { auto str = univalue.write(); ankerl::nanobench::doNotOptimizeAway(str); diff --git a/src/bench/rpc_mempool.cpp b/src/bench/rpc_mempool.cpp index 12dcff5844..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 <rpc/blockchain.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) { @@ -29,11 +32,11 @@ static void RpcMempool(benchmark::Bench& bench) tx.vout[0].scriptPubKey = CScript() << OP_1 << OP_EQUAL; tx.vout[0].nValue = i; const CTransactionRef tx_r{MakeTransactionRef(tx)}; - AddTx(tx_r, /* fee */ i, pool); + AddTx(tx_r, /*fee=*/i, pool); } bench.run([&] { - (void)MempoolToJSON(pool, /*verbose*/ true); + (void)MempoolToJSON(pool, /*verbose=*/true); }); } diff --git a/src/bench/strencodings.cpp b/src/bench/strencodings.cpp new file mode 100644 index 0000000000..69b3a83cbf --- /dev/null +++ b/src/bench/strencodings.cpp @@ -0,0 +1,18 @@ +// Copyright (c) 2022 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include <bench/bench.h> +#include <bench/data.h> +#include <util/strencodings.h> + +static void HexStrBench(benchmark::Bench& bench) +{ + auto const& data = benchmark::data::block413567; + bench.batch(data.size()).unit("byte").run([&] { + auto hex = HexStr(data); + ankerl::nanobench::doNotOptimizeAway(hex); + }); +} + +BENCHMARK(HexStrBench); diff --git a/src/bench/wallet_balance.cpp b/src/bench/wallet_balance.cpp index d4b8794c6d..a5dd2f3bb1 100644 --- a/src/bench/wallet_balance.cpp +++ b/src/bench/wallet_balance.cpp @@ -52,10 +52,10 @@ static void WalletBalance(benchmark::Bench& bench, const bool set_dirty, const b }); } -static void WalletBalanceDirty(benchmark::Bench& bench) { WalletBalance(bench, /* set_dirty */ true, /* add_mine */ true); } -static void WalletBalanceClean(benchmark::Bench& bench) { WalletBalance(bench, /* set_dirty */ false, /* add_mine */ true); } -static void WalletBalanceMine(benchmark::Bench& bench) { WalletBalance(bench, /* set_dirty */ false, /* add_mine */ true); } -static void WalletBalanceWatch(benchmark::Bench& bench) { WalletBalance(bench, /* set_dirty */ false, /* add_mine */ false); } +static void WalletBalanceDirty(benchmark::Bench& bench) { WalletBalance(bench, /*set_dirty=*/true, /*add_mine=*/true); } +static void WalletBalanceClean(benchmark::Bench& bench) { WalletBalance(bench, /*set_dirty=*/false, /*add_mine=*/true); } +static void WalletBalanceMine(benchmark::Bench& bench) { WalletBalance(bench, /*set_dirty=*/false, /*add_mine=*/true); } +static void WalletBalanceWatch(benchmark::Bench& bench) { WalletBalance(bench, /*set_dirty=*/false, /*add_mine=*/false); } BENCHMARK(WalletBalanceDirty); BENCHMARK(WalletBalanceClean); diff --git a/src/bench/wallet_loading.cpp b/src/bench/wallet_loading.cpp new file mode 100644 index 0000000000..27e4dd015d --- /dev/null +++ b/src/bench/wallet_loading.cpp @@ -0,0 +1,127 @@ +// 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 <bench/bench.h> +#include <interfaces/chain.h> +#include <node/context.h> +#include <test/util/mining.h> +#include <test/util/setup_common.h> +#include <test/util/wallet.h> +#include <util/translation.h> +#include <validationinterface.h> +#include <wallet/context.h> +#include <wallet/receive.h> +#include <wallet/wallet.h> + +#include <optional> + +using wallet::CWallet; +using wallet::DatabaseFormat; +using wallet::DatabaseOptions; +using wallet::TxStateInactive; +using wallet::WALLET_FLAG_DESCRIPTORS; +using wallet::WalletContext; +using wallet::WalletDatabase; + +static const std::shared_ptr<CWallet> BenchLoadWallet(std::unique_ptr<WalletDatabase> database, WalletContext& context, DatabaseOptions& options) +{ + bilingual_str error; + std::vector<bilingual_str> warnings; + auto wallet = CWallet::Create(context, "", std::move(database), options.create_flags, error, warnings); + NotifyWalletLoaded(context, wallet); + if (context.chain) { + wallet->postInitProcess(); + } + return wallet; +} + +static void BenchUnloadWallet(std::shared_ptr<CWallet>&& wallet) +{ + SyncWithValidationInterfaceQueue(); + wallet->m_chain_notifications_handler.reset(); + 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; + context.chain = test_setup->m_node.chain.get(); + + // Setup the wallet + // Loading the wallet will also create it + DatabaseOptions 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 < 1000; ++i) { + AddTx(*wallet); + } + + database = DuplicateMockDatabase(wallet->GetDatabase(), options); + + // reload the wallet for the actual benchmark + BenchUnloadWallet(std::move(wallet)); + + 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); } +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 new file mode 100644 index 0000000000..87f18371b1 --- /dev/null +++ b/src/bitcoin-chainstate.cpp @@ -0,0 +1,261 @@ +// 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. +// +// The bitcoin-chainstate executable serves to surface the dependencies required +// by a program wishing to use Bitcoin Core's consensus engine as it is right +// now. +// +// DEVELOPER NOTE: Since this is a "demo-only", experimental, etc. executable, +// it may diverge from Bitcoin Core's coding style. +// +// It is part of the libbitcoinkernel project. + +#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> +#include <util/system.h> +#include <util/thread.h> +#include <validation.h> +#include <validationinterface.h> + +#include <cassert> +#include <filesystem> +#include <functional> +#include <iosfwd> + +int main(int argc, char* argv[]) +{ + // SETUP: Argument parsing and handling + if (argc != 2) { + std::cerr + << "Usage: " << argv[0] << " DATADIR" << std::endl + << "Display DATADIR information, and process hex-encoded blocks on standard input." << std::endl + << std::endl + << "IMPORTANT: THIS EXECUTABLE IS EXPERIMENTAL, FOR TESTING ONLY, AND EXPECTED TO" << std::endl + << " BREAK IN FUTURE VERSIONS. DO NOT USE ON YOUR ACTUAL DATADIR." << std::endl; + return 1; + } + std::filesystem::path abs_datadir = std::filesystem::absolute(argv[1]); + std::filesystem::create_directories(abs_datadir); + gArgs.ForceSetArg("-datadir", abs_datadir.string()); + + + // SETUP: Misc Globals + SelectParams(CBaseChainParams::MAIN); + const CChainParams& chainparams = Params(); + + kernel::Context kernel_context{}; + // We can't use a goto here, but we can use an assert since none of the + // things instantiated so far requires running the epilogue to be torn down + // properly + assert(!kernel::SanityChecks(kernel_context).has_value()); + + // 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. + 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 + CScheduler scheduler{}; + // Start the lightweight task scheduler thread + scheduler.m_service_thread = std::thread(util::TraceThread, "scheduler", [&] { scheduler.serviceQueue(); }); + + // Gather some entropy once per minute. + scheduler.scheduleEvery(RandAddPeriodic, std::chrono::minutes{1}); + + GetMainSignals().RegisterBackgroundSignalScheduler(scheduler); + + + // SETUP: Chainstate + const ChainstateManager::Options chainman_opts{ + .chainparams = chainparams, + .adjusted_time_callback = static_cast<int64_t (*)()>(GetTime), + }; + ChainstateManager chainman{chainman_opts}; + + 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 { + 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())) { + BlockValidationState state; + if (!chainstate->ActivateBestChain(state, nullptr)) { + std::cerr << "Failed to connect best block (" << state.ToString() << ")" << std::endl; + goto epilogue; + } + } + + // 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; + { + 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; + } + } + + for (std::string line; std::getline(std::cin, line);) { + if (line.empty()) { + std::cerr << "Empty line found" << std::endl; + break; + } + + std::shared_ptr<CBlock> blockptr = std::make_shared<CBlock>(); + CBlock& block = *blockptr; + + if (!DecodeHexBlk(block, line)) { + std::cerr << "Block decode failed" << std::endl; + break; + } + + if (block.vtx.empty() || !block.vtx[0]->IsCoinBase()) { + std::cerr << "Block does not start with a coinbase" << std::endl; + break; + } + + uint256 hash = block.GetHash(); + { + LOCK(cs_main); + const CBlockIndex* pindex = chainman.m_blockman.LookupBlockIndex(hash); + if (pindex) { + if (pindex->IsValid(BLOCK_VALID_SCRIPTS)) { + std::cerr << "duplicate" << std::endl; + break; + } + if (pindex->nStatus & BLOCK_FAILED_MASK) { + std::cerr << "duplicate-invalid" << std::endl; + break; + } + } + } + + { + LOCK(cs_main); + const CBlockIndex* pindex = chainman.m_blockman.LookupBlockIndex(block.hashPrevBlock); + if (pindex) { + chainman.UpdateUncommittedBlockStructures(block, pindex); + } + } + + // Adapted from rpc/mining.cpp + class submitblock_StateCatcher final : public CValidationInterface + { + public: + uint256 hash; + bool found; + BlockValidationState state; + + explicit submitblock_StateCatcher(const uint256& hashIn) : hash(hashIn), found(false), state() {} + + protected: + void BlockChecked(const CBlock& block, const BlockValidationState& stateIn) override + { + if (block.GetHash() != hash) + return; + found = true; + state = stateIn; + } + }; + + 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); + UnregisterSharedValidationInterface(sc); + if (!new_block && accepted) { + std::cerr << "duplicate" << std::endl; + break; + } + if (!sc->found) { + std::cerr << "inconclusive" << std::endl; + break; + } + std::cout << sc->state.ToString() << std::endl; + switch (sc->state.GetResult()) { + case BlockValidationResult::BLOCK_RESULT_UNSET: + std::cerr << "initial value. Block has not yet been rejected" << std::endl; + break; + case BlockValidationResult::BLOCK_CONSENSUS: + std::cerr << "invalid by consensus rules (excluding any below reasons)" << std::endl; + break; + case BlockValidationResult::BLOCK_RECENT_CONSENSUS_CHANGE: + std::cerr << "Invalid by a change to consensus rules more recent than SegWit." << std::endl; + break; + case BlockValidationResult::BLOCK_CACHED_INVALID: + std::cerr << "this block was cached as being invalid and we didn't store the reason why" << std::endl; + break; + case BlockValidationResult::BLOCK_INVALID_HEADER: + std::cerr << "invalid proof of work or time too old" << std::endl; + break; + case BlockValidationResult::BLOCK_MUTATED: + std::cerr << "the block's data didn't match the data committed to by the PoW" << std::endl; + break; + case BlockValidationResult::BLOCK_MISSING_PREV: + std::cerr << "We don't have the previous block the checked one is built on" << std::endl; + break; + case BlockValidationResult::BLOCK_INVALID_PREV: + std::cerr << "A block this one builds on is invalid" << std::endl; + break; + case BlockValidationResult::BLOCK_TIME_FUTURE: + std::cerr << "block timestamp was > 2 hours in the future (or our clock is bad)" << std::endl; + break; + case BlockValidationResult::BLOCK_CHECKPOINT: + std::cerr << "the block failed to meet one of our checkpoints" << std::endl; + break; + } + } + +epilogue: + // Without this precise shutdown sequence, there will be a lot of nullptr + // dereferencing and UB. + scheduler.stop(); + if (chainman.m_load_block.joinable()) chainman.m_load_block.join(); + StopScriptCheckWorkerThreads(); + + GetMainSignals().FlushBackgroundCallbacks(); + { + LOCK(cs_main); + for (CChainState* chainstate : chainman.GetAll()) { + if (chainstate->CanFlushToDisk()) { + chainstate->ForceFlushStateToDisk(); + chainstate->ResetCoinsViews(); + } + } + } + GetMainSignals().UnregisterBackgroundSignalScheduler(); +} diff --git a/src/bitcoin-cli.cpp b/src/bitcoin-cli.cpp index 5523fff3b2..e64e2202ba 100644 --- a/src/bitcoin-cli.cpp +++ b/src/bitcoin-cli.cpp @@ -9,23 +9,27 @@ #include <chainparamsbase.h> #include <clientversion.h> +#include <compat/compat.h> +#include <compat/stdin.h> #include <policy/feerate.h> #include <rpc/client.h> #include <rpc/mining.h> #include <rpc/protocol.h> #include <rpc/request.h> #include <tinyformat.h> +#include <univalue.h> #include <util/strencodings.h> #include <util/system.h> #include <util/translation.h> #include <util/url.h> #include <algorithm> +#include <chrono> #include <cmath> +#include <cstdio> #include <functional> #include <memory> #include <optional> -#include <stdio.h> #include <string> #include <tuple> @@ -37,8 +41,10 @@ #include <event2/keyvalq_struct.h> #include <support/events.h> -#include <univalue.h> -#include <compat/stdin.h> +// The server returns time values from a mockable system clock, but it is not +// trivial to get the mocked time from the server, nor is it needed for now, so +// just use a plain system_clock. +using CliClock = std::chrono::system_clock; const std::function<std::string(const char*)> G_TRANSLATION_FUN = nullptr; UrlDecodeFn* const URL_DECODE = urlDecode; @@ -174,17 +180,16 @@ static int AppInitRPC(int argc, char* argv[]) /** Reply structure for request_done to fill in */ struct HTTPReply { - HTTPReply(): status(0), error(-1) {} + HTTPReply() = default; - int status; - int error; + int status{0}; + int error{-1}; std::string body; }; static std::string http_errorstring(int code) { switch(code) { -#if LIBEVENT_VERSION_NUMBER >= 0x02010300 case EVREQ_HTTP_TIMEOUT: return "timeout reached"; case EVREQ_HTTP_EOF: @@ -197,7 +202,6 @@ static std::string http_errorstring(int code) return "request was canceled"; case EVREQ_HTTP_DATA_TOO_LONG: return "response body is larger than allowed"; -#endif default: return "unknown"; } @@ -228,13 +232,11 @@ static void http_request_done(struct evhttp_request *req, void *ctx) } } -#if LIBEVENT_VERSION_NUMBER >= 0x02010300 static void http_error_cb(enum evhttp_request_error err, void *ctx) { HTTPReply *reply = static_cast<HTTPReply*>(ctx); reply->error = err; } -#endif /** Class that handles the conversion from a command-line to a JSON-RPC request, * as well as converting back to a JSON object that can be shown as result. @@ -242,7 +244,7 @@ static void http_error_cb(enum evhttp_request_error err, void *ctx) class BaseRequestHandler { public: - virtual ~BaseRequestHandler() {} + virtual ~BaseRequestHandler() = default; virtual UniValue PrepareRequest(const std::string& method, const std::vector<std::string>& args) = 0; virtual UniValue ProcessReply(const UniValue &batch_in) = 0; }; @@ -411,8 +413,8 @@ private: bool is_addr_relay_enabled; bool is_bip152_hb_from; bool is_bip152_hb_to; - bool is_block_relay; bool is_outbound; + bool is_tx_relay; bool operator<(const Peer& rhs) const { return std::tie(is_outbound, min_ping) < std::tie(rhs.is_outbound, rhs.min_ping); } }; std::vector<Peer> m_peers; @@ -437,7 +439,6 @@ private: if (conn_type == "addr-fetch") return "addr"; return ""; } - const int64_t m_time_now{GetTimeSeconds()}; public: static constexpr int ID_PEERINFO = 0; @@ -466,9 +467,10 @@ public: if (!batch[ID_NETWORKINFO]["error"].isNull()) return batch[ID_NETWORKINFO]; const UniValue& networkinfo{batch[ID_NETWORKINFO]["result"]}; - if (networkinfo["version"].get_int() < 209900) { + if (networkinfo["version"].getInt<int>() < 209900) { throw std::runtime_error("-netinfo requires bitcoind server to be running v0.21.0 and up"); } + const int64_t time_now{TicksSinceEpoch<std::chrono::seconds>(CliClock::now())}; // Count peer connection totals, and if DetailsRequested(), store peer data in a vector of structs. for (const UniValue& peer : batch[ID_PEERINFO]["result"].getValues()) { @@ -476,7 +478,7 @@ public: const int8_t network_id{NetworkStringToId(network)}; if (network_id == UNKNOWN_NETWORK) continue; const bool is_outbound{!peer["inbound"].get_bool()}; - const bool is_block_relay{!peer["relaytxes"].get_bool()}; + const bool is_tx_relay{peer["relaytxes"].isNull() ? true : peer["relaytxes"].get_bool()}; const std::string conn_type{peer["connection_type"].get_str()}; ++m_counts.at(is_outbound).at(network_id); // in/out by network ++m_counts.at(is_outbound).at(NETWORKS.size()); // in/out overall @@ -486,25 +488,25 @@ public: if (conn_type == "manual") ++m_manual_peers_count; if (DetailsRequested()) { // Push data for this peer to the peers vector. - const int peer_id{peer["id"].get_int()}; - const int mapped_as{peer["mapped_as"].isNull() ? 0 : peer["mapped_as"].get_int()}; - const int version{peer["version"].get_int()}; - const int64_t addr_processed{peer["addr_processed"].isNull() ? 0 : peer["addr_processed"].get_int64()}; - const int64_t addr_rate_limited{peer["addr_rate_limited"].isNull() ? 0 : peer["addr_rate_limited"].get_int64()}; - const int64_t conn_time{peer["conntime"].get_int64()}; - const int64_t last_blck{peer["last_block"].get_int64()}; - const int64_t last_recv{peer["lastrecv"].get_int64()}; - const int64_t last_send{peer["lastsend"].get_int64()}; - const int64_t last_trxn{peer["last_transaction"].get_int64()}; + const int peer_id{peer["id"].getInt<int>()}; + const int mapped_as{peer["mapped_as"].isNull() ? 0 : peer["mapped_as"].getInt<int>()}; + const int version{peer["version"].getInt<int>()}; + const int64_t addr_processed{peer["addr_processed"].isNull() ? 0 : peer["addr_processed"].getInt<int64_t>()}; + const int64_t addr_rate_limited{peer["addr_rate_limited"].isNull() ? 0 : peer["addr_rate_limited"].getInt<int64_t>()}; + const int64_t conn_time{peer["conntime"].getInt<int64_t>()}; + const int64_t last_blck{peer["last_block"].getInt<int64_t>()}; + const int64_t last_recv{peer["lastrecv"].getInt<int64_t>()}; + const int64_t last_send{peer["lastsend"].getInt<int64_t>()}; + const int64_t last_trxn{peer["last_transaction"].getInt<int64_t>()}; const double min_ping{peer["minping"].isNull() ? -1 : peer["minping"].get_real()}; const double ping{peer["pingtime"].isNull() ? -1 : peer["pingtime"].get_real()}; const std::string addr{peer["addr"].get_str()}; - const std::string age{conn_time == 0 ? "" : ToString((m_time_now - conn_time) / 60)}; + const std::string age{conn_time == 0 ? "" : ToString((time_now - conn_time) / 60)}; const std::string sub_version{peer["subver"].get_str()}; const bool is_addr_relay_enabled{peer["addr_relay_enabled"].isNull() ? false : peer["addr_relay_enabled"].get_bool()}; const bool is_bip152_hb_from{peer["bip152_hb_from"].get_bool()}; const bool is_bip152_hb_to{peer["bip152_hb_to"].get_bool()}; - m_peers.push_back({addr, sub_version, conn_type, network, age, min_ping, ping, addr_processed, addr_rate_limited, last_blck, last_recv, last_send, last_trxn, peer_id, mapped_as, version, is_addr_relay_enabled, is_bip152_hb_from, is_bip152_hb_to, is_block_relay, is_outbound}); + m_peers.push_back({addr, sub_version, conn_type, network, age, min_ping, ping, addr_processed, addr_rate_limited, last_blck, last_recv, last_send, last_trxn, peer_id, mapped_as, version, is_addr_relay_enabled, is_bip152_hb_from, is_bip152_hb_to, is_outbound, is_tx_relay}); m_max_addr_length = std::max(addr.length() + 1, m_max_addr_length); m_max_addr_processed_length = std::max(ToString(addr_processed).length(), m_max_addr_processed_length); m_max_addr_rate_limited_length = std::max(ToString(addr_rate_limited).length(), m_max_addr_rate_limited_length); @@ -515,7 +517,7 @@ public: } // Generate report header. - std::string result{strprintf("%s client %s%s - server %i%s\n\n", PACKAGE_NAME, FormatFullVersion(), ChainToString(), networkinfo["protocolversion"].get_int(), networkinfo["subversion"].get_str())}; + std::string result{strprintf("%s client %s%s - server %i%s\n\n", PACKAGE_NAME, FormatFullVersion(), ChainToString(), networkinfo["protocolversion"].getInt<int>(), networkinfo["subversion"].get_str())}; // Report detailed peer connections list sorted by direction and minimum ping time. if (DetailsRequested() && !m_peers.empty()) { @@ -535,10 +537,10 @@ public: peer.network, PingTimeToString(peer.min_ping), PingTimeToString(peer.ping), - peer.last_send ? ToString(m_time_now - peer.last_send) : "", - peer.last_recv ? ToString(m_time_now - peer.last_recv) : "", - peer.last_trxn ? ToString((m_time_now - peer.last_trxn) / 60) : peer.is_block_relay ? "*" : "", - peer.last_blck ? ToString((m_time_now - peer.last_blck) / 60) : "", + peer.last_send ? ToString(time_now - peer.last_send) : "", + peer.last_recv ? ToString(time_now - peer.last_recv) : "", + peer.last_trxn ? ToString((time_now - peer.last_trxn) / 60) : peer.is_tx_relay ? "" : "*", + peer.last_blck ? ToString((time_now - peer.last_blck) / 60) : "", strprintf("%s%s", peer.is_bip152_hb_to ? "." : " ", peer.is_bip152_hb_from ? "*" : " "), m_max_addr_processed_length, // variable spacing peer.addr_processed ? ToString(peer.addr_processed) : peer.is_addr_relay_enabled ? "" : ".", @@ -596,7 +598,7 @@ public: max_addr_size = std::max(addr["address"].get_str().length() + 1, max_addr_size); } for (const UniValue& addr : local_addrs) { - result += strprintf("\n%-*s port %6i score %6i", max_addr_size, addr["address"].get_str(), addr["port"].get_int(), addr["score"].get_int()); + result += strprintf("\n%-*s port %6i score %6i", max_addr_size, addr["address"].get_str(), addr["port"].getInt<int>(), addr["score"].getInt<int>()); } } @@ -745,11 +747,11 @@ static UniValue CallRPC(BaseRequestHandler* rh, const std::string& strMethod, co HTTPReply response; raii_evhttp_request req = obtain_evhttp_request(http_request_done, (void*)&response); - if (req == nullptr) + if (req == nullptr) { throw std::runtime_error("create http request failed"); -#if LIBEVENT_VERSION_NUMBER >= 0x02010300 + } + evhttp_request_set_error_cb(req.get(), http_error_cb); -#endif // Get credentials std::string strRPCUserColonPass; @@ -805,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"); } @@ -842,21 +844,20 @@ static UniValue ConnectAndCallRPC(BaseRequestHandler* rh, const std::string& str // Execute and handle connection failures with -rpcwait. const bool fWait = gArgs.GetBoolArg("-rpcwait", false); const int timeout = gArgs.GetIntArg("-rpcwaittimeout", DEFAULT_WAIT_CLIENT_TIMEOUT); - const auto deadline{GetTime<std::chrono::microseconds>() + 1s * timeout}; + const auto deadline{std::chrono::steady_clock::now() + 1s * timeout}; do { try { response = CallRPC(rh, strMethod, args, rpcwallet); if (fWait) { const UniValue& error = find_value(response, "error"); - if (!error.isNull() && error["code"].get_int() == RPC_IN_WARMUP) { + if (!error.isNull() && error["code"].getInt<int>() == RPC_IN_WARMUP) { throw CConnectionFailed("server in warmup"); } } break; // Connection succeeded, no need to retry. } catch (const CConnectionFailed& e) { - const auto now{GetTime<std::chrono::microseconds>()}; - if (fWait && (timeout <= 0 || now < deadline)) { + if (fWait && (timeout <= 0 || std::chrono::steady_clock::now() < deadline)) { UninterruptibleSleep(1s); } else { throw CConnectionFailed(strprintf("timeout on transient error: %s", e.what())); @@ -885,13 +886,13 @@ static void ParseError(const UniValue& error, std::string& strPrint, int& nRet) if (err_msg.isStr()) { strPrint += ("error message:\n" + err_msg.get_str()); } - if (err_code.isNum() && err_code.get_int() == RPC_WALLET_NOT_SPECIFIED) { + if (err_code.isNum() && err_code.getInt<int>() == RPC_WALLET_NOT_SPECIFIED) { strPrint += "\nTry adding \"-rpcwallet=<filename>\" option to bitcoin-cli command line."; } } else { strPrint = "error: " + error.write(); } - nRet = abs(error["code"].get_int()); + nRet = abs(error["code"].getInt<int>()); } /** @@ -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); @@ -1059,7 +1060,9 @@ static void ParseGetInfoResult(UniValue& result) result_string += "\n"; } - result_string += strprintf("%sWarnings:%s %s", YELLOW, RESET, result["warnings"].getValStr()); + const std::string warnings{result["warnings"].getValStr()}; + result_string += strprintf("%sWarnings:%s %s", YELLOW, RESET, warnings.empty() ? "(none)" : warnings); + result.setStr(result_string); } @@ -1210,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 b297081cab..c2bb89b8cf 100644 --- a/src/bitcoin-tx.cpp +++ b/src/bitcoin-tx.cpp @@ -8,6 +8,7 @@ #include <clientversion.h> #include <coins.h> +#include <compat/compat.h> #include <consensus/amount.h> #include <consensus/consensus.h> #include <core_io.h> @@ -31,8 +32,6 @@ #include <memory> #include <stdio.h> -#include <boost/algorithm/string.hpp> - static bool fCreateBlank; static std::map<std::string,UniValue> registers; static const int CONTINUE_EXECUTION=-1; @@ -242,7 +241,7 @@ static void MutateTxRBFOptIn(CMutableTransaction& tx, const std::string& strInId template <typename T> static T TrimAndParse(const std::string& int_str, const std::string& err) { - const auto parsed{ToIntegral<T>(TrimString(int_str))}; + const auto parsed{ToIntegral<T>(TrimStringView(int_str))}; if (!parsed.has_value()) { throw std::runtime_error(err + " '" + int_str + "'"); } @@ -251,8 +250,7 @@ static T TrimAndParse(const std::string& int_str, const std::string& err) static void MutateTxAddInput(CMutableTransaction& tx, const std::string& strInput) { - std::vector<std::string> vStrInputParts; - boost::split(vStrInputParts, strInput, boost::is_any_of(":")); + std::vector<std::string> vStrInputParts = SplitString(strInput, ':'); // separate TXID:VOUT in string if (vStrInputParts.size()<2) @@ -287,8 +285,7 @@ static void MutateTxAddInput(CMutableTransaction& tx, const std::string& strInpu static void MutateTxAddOutAddr(CMutableTransaction& tx, const std::string& strInput) { // Separate into VALUE:ADDRESS - std::vector<std::string> vStrInputParts; - boost::split(vStrInputParts, strInput, boost::is_any_of(":")); + std::vector<std::string> vStrInputParts = SplitString(strInput, ':'); if (vStrInputParts.size() != 2) throw std::runtime_error("TX output missing or too many separators"); @@ -312,8 +309,7 @@ static void MutateTxAddOutAddr(CMutableTransaction& tx, const std::string& strIn static void MutateTxAddOutPubKey(CMutableTransaction& tx, const std::string& strInput) { // Separate into VALUE:PUBKEY[:FLAGS] - std::vector<std::string> vStrInputParts; - boost::split(vStrInputParts, strInput, boost::is_any_of(":")); + std::vector<std::string> vStrInputParts = SplitString(strInput, ':'); if (vStrInputParts.size() < 2 || vStrInputParts.size() > 3) throw std::runtime_error("TX output missing or too many separators"); @@ -356,8 +352,7 @@ static void MutateTxAddOutPubKey(CMutableTransaction& tx, const std::string& str static void MutateTxAddOutMultiSig(CMutableTransaction& tx, const std::string& strInput) { // Separate into VALUE:REQUIRED:NUMKEYS:PUBKEY1:PUBKEY2:....[:FLAGS] - std::vector<std::string> vStrInputParts; - boost::split(vStrInputParts, strInput, boost::is_any_of(":")); + std::vector<std::string> vStrInputParts = SplitString(strInput, ':'); // Check that there are enough parameters if (vStrInputParts.size()<3) @@ -460,8 +455,7 @@ static void MutateTxAddOutData(CMutableTransaction& tx, const std::string& strIn static void MutateTxAddOutScript(CMutableTransaction& tx, const std::string& strInput) { // separate VALUE:SCRIPT[:FLAGS] - std::vector<std::string> vStrInputParts; - boost::split(vStrInputParts, strInput, boost::is_any_of(":")); + std::vector<std::string> vStrInputParts = SplitString(strInput, ':'); if (vStrInputParts.size() < 2) throw std::runtime_error("TX output missing separator"); @@ -602,7 +596,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"); @@ -619,7 +613,7 @@ static void MutateTxSign(CMutableTransaction& tx, const std::string& flagStr) throw std::runtime_error("txid must be hexadecimal string (not '" + prevOut["txid"].get_str() + "')"); } - const int nOut = prevOut["vout"].get_int(); + const int nOut = prevOut["vout"].getInt<int>(); if (nOut < 0) throw std::runtime_error("vout cannot be negative"); @@ -674,7 +668,7 @@ static void MutateTxSign(CMutableTransaction& tx, const std::string& flagStr) SignatureData sigdata = DataFromTransaction(mergedTx, i, coin.out); // Only sign SIGHASH_SINGLE if there's a corresponding output: if (!fHashSingle || (i < mergedTx.vout.size())) - ProduceSignature(keystore, MutableTransactionSignatureCreator(&mergedTx, i, amount, nHashType), prevPubKey, sigdata); + ProduceSignature(keystore, MutableTransactionSignatureCreator(mergedTx, i, amount, nHashType), prevPubKey, sigdata); if (amount == MAX_MONEY && !sigdata.scriptWitness.IsNull()) { throw std::runtime_error(strprintf("Missing amount for CTxOut with scriptPubKey=%s", HexStr(prevPubKey))); @@ -750,7 +744,7 @@ static void MutateTx(CMutableTransaction& tx, const std::string& command, static void OutputTxJSON(const CTransaction& tx) { UniValue entry(UniValue::VOBJ); - TxToUniv(tx, uint256(), entry); + TxToUniv(tx, /*block_hash=*/uint256(), entry); std::string jsonOutput = entry.write(4); tfm::format(std::cout, "%s\n", jsonOutput); @@ -861,7 +855,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 b457e0b354..fb184c0486 100644 --- a/src/bitcoin-util.cpp +++ b/src/bitcoin-util.cpp @@ -11,10 +11,12 @@ #include <chainparams.h> #include <chainparamsbase.h> #include <clientversion.h> +#include <compat/compat.h> #include <core_io.h> #include <streams.h> #include <util/system.h> #include <util/translation.h> +#include <version.h> #include <atomic> #include <cstdio> @@ -22,8 +24,6 @@ #include <memory> #include <thread> -#include <boost/algorithm/string.hpp> - static const int CONTINUE_EXECUTION=-1; const std::function<std::string(const char*)> G_TRANSLATION_FUN = nullptr; @@ -143,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..a7d49452b0 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> @@ -88,7 +89,7 @@ static bool WalletAppInit(ArgsManager& args, int argc, char* argv[]) return true; } -int main(int argc, char* argv[]) +MAIN_FUNCTION { ArgsManager& args = gArgs; #ifdef WIN32 diff --git a/src/bitcoind.cpp b/src/bitcoind.cpp index 9843382682..85ba88f6ab 100644 --- a/src/bitcoind.cpp +++ b/src/bitcoind.cpp @@ -9,17 +9,18 @@ #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> #include <util/strencodings.h> #include <util/syscall_sandbox.h> +#include <util/syserror.h> #include <util/system.h> #include <util/threadnames.h> #include <util/tokenpipe.h> @@ -187,11 +188,14 @@ static bool AppInit(NodeContext& node, int argc, char* argv[]) // InitError will have been called with detailed error, which ends up on console return false; } - if (!AppInitSanityChecks()) + + node.kernel = std::make_unique<kernel::Context>(); + if (!AppInitSanityChecks(*node.kernel)) { // InitError will have been called with detailed error, which ends up on console return false; } + if (args.GetBoolArg("-daemon", DEFAULT_DAEMON) || args.GetBoolArg("-daemonwait", DEFAULT_DAEMONWAIT)) { #if HAVE_DECL_FORK tfm::format(std::cout, PACKAGE_NAME " starting\n"); @@ -206,7 +210,7 @@ static bool AppInit(NodeContext& node, int argc, char* argv[]) } break; case -1: // Error happened. - return InitError(Untranslated(strprintf("fork_daemon() failed: %s\n", strerror(errno)))); + return InitError(Untranslated(strprintf("fork_daemon() failed: %s\n", SysErrorString(errno)))); default: { // Parent: wait and exit. int token = daemon_ep.TokenRead(); if (token) { // Success @@ -252,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/blockencodings.cpp b/src/blockencodings.cpp index aa111b5939..f96353510f 100644 --- a/src/blockencodings.cpp +++ b/src/blockencodings.cpp @@ -16,15 +16,15 @@ #include <unordered_map> -CBlockHeaderAndShortTxIDs::CBlockHeaderAndShortTxIDs(const CBlock& block, bool fUseWTXID) : - nonce(GetRand(std::numeric_limits<uint64_t>::max())), +CBlockHeaderAndShortTxIDs::CBlockHeaderAndShortTxIDs(const CBlock& block) : + nonce(GetRand<uint64_t>()), shorttxids(block.vtx.size() - 1), prefilledtxn(1), header(block) { FillShortTxIDSelector(); //TODO: Use our mempool prior to block acceptance to predictively fill more than just the coinbase prefilledtxn[0] = {0, block.vtx[0]}; for (size_t i = 1; i < block.vtx.size(); i++) { const CTransaction& tx = *block.vtx[i]; - shorttxids[i - 1] = GetShortID(fUseWTXID ? tx.GetWitnessHash() : tx.GetHash()); + shorttxids[i - 1] = GetShortID(tx.GetWitnessHash()); } } diff --git a/src/blockencodings.h b/src/blockencodings.h index 326db1b4a7..67c4e57156 100644 --- a/src/blockencodings.h +++ b/src/blockencodings.h @@ -104,7 +104,7 @@ public: // Dummy for deserialization CBlockHeaderAndShortTxIDs() {} - CBlockHeaderAndShortTxIDs(const CBlock& block, bool fUseWTXID); + CBlockHeaderAndShortTxIDs(const CBlock& block); uint64_t GetShortID(const uint256& txhash) const; diff --git a/src/blockfilter.cpp b/src/blockfilter.cpp index 63a9ba498f..43d88df720 100644 --- a/src/blockfilter.cpp +++ b/src/blockfilter.cpp @@ -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); } }); @@ -183,7 +185,7 @@ const std::string& ListBlockFilterTypes() std::call_once(flag, []() { std::stringstream ret; bool first = true; - for (auto entry : g_filter_types) { + for (const auto& entry : g_filter_types) { if (!first) ret << ", "; ret << entry.second; first = false; @@ -219,14 +221,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..d6a51e95c2 100644 --- a/src/blockfilter.h +++ b/src/blockfilter.h @@ -59,7 +59,7 @@ 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); @@ -122,7 +122,7 @@ 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); @@ -164,7 +164,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..446bb216c2 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; diff --git a/src/chain.h b/src/chain.h index 24b5026aba..1c5cd3f1f6 100644 --- a/src/chain.h +++ b/src/chain.h @@ -11,7 +11,6 @@ #include <flatfile.h> #include <primitives/block.h> #include <sync.h> -#include <tinyformat.h> #include <uint256.h> #include <vector> @@ -138,7 +137,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 +262,7 @@ public: uint256 GetBlockHash() const { + assert(phashBlock != nullptr); return *phashBlock; } @@ -301,13 +301,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 +396,7 @@ public: READWRITE(obj.nNonce); } - uint256 GetBlockHash() const + uint256 ConstructBlockHash() const { CBlockHeader block; block.nVersion = nVersion; @@ -414,16 +408,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,7 +465,7 @@ 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; diff --git a/src/chainparams.cpp b/src/chainparams.cpp index d3ae6f4cb2..dd7b93234d 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -9,13 +9,12 @@ #include <consensus/merkle.h> #include <deploymentinfo.h> #include <hash.h> // for signet block challenge hash +#include <script/interpreter.h> +#include <util/string.h> #include <util/system.h> #include <assert.h> -#include <boost/algorithm/string/classification.hpp> -#include <boost/algorithm/string/split.hpp> - static CBlock CreateGenesisBlock(const char* pszTimestamp, const CScript& genesisOutputScript, uint32_t nTime, uint32_t nNonce, uint32_t nBits, int32_t nVersion, const CAmount& genesisReward) { CMutableTransaction txNew; @@ -65,7 +64,10 @@ public: consensus.signet_blocks = false; consensus.signet_challenge.clear(); consensus.nSubsidyHalvingInterval = 210000; - consensus.BIP16Exception = uint256S("0x00000000000002dc756eebf4f49723ed8d30cc28a5f108eb94b1ba88ac4f9c22"); + consensus.script_flag_exceptions.emplace( // BIP16 exception + uint256S("0x00000000000002dc756eebf4f49723ed8d30cc28a5f108eb94b1ba88ac4f9c22"), SCRIPT_VERIFY_NONE); + consensus.script_flag_exceptions.emplace( // Taproot exception + uint256S("0x0000000000000000000f14c35b2d841e986ab5441de8c585d5ffe55ea1e395ad"), SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_WITNESS); consensus.BIP34Height = 227931; consensus.BIP34Hash = uint256S("0x000000000000024b89b42a942fe0d9fea3bb44ab7bd1b19115dd6a759c0808b8"); consensus.BIP65Height = 388381; // 000000000000000004c2b624ed5d7756c508d90fd0da2c7c679febfa6c4735f0 @@ -184,7 +186,8 @@ public: consensus.signet_blocks = false; consensus.signet_challenge.clear(); consensus.nSubsidyHalvingInterval = 210000; - consensus.BIP16Exception = uint256S("0x00000000dd30457c001f4095d208cc1296b0eed002427aa599874af7a432b105"); + consensus.script_flag_exceptions.emplace( // BIP16 exception + uint256S("0x00000000dd30457c001f4095d208cc1296b0eed002427aa599874af7a432b105"), SCRIPT_VERIFY_NONE); consensus.BIP34Height = 21111; consensus.BIP34Hash = uint256S("0x0000000023b3a96d3484e5abb3755c413e7d41500f8e2a5c3f0dd01299cd8ef8"); consensus.BIP65Height = 581885; // 00000000007f6655f22f98e72ed80d8b06dc761d5da09df0fa1dc4be4f861eb6 @@ -323,7 +326,6 @@ public: consensus.signet_blocks = true; consensus.signet_challenge.assign(bin.begin(), bin.end()); consensus.nSubsidyHalvingInterval = 210000; - consensus.BIP16Exception = uint256{}; consensus.BIP34Height = 1; consensus.BIP34Hash = uint256{}; consensus.BIP65Height = 1; @@ -350,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); @@ -391,13 +393,12 @@ public: consensus.signet_blocks = false; consensus.signet_challenge.clear(); consensus.nSubsidyHalvingInterval = 150; - consensus.BIP16Exception = uint256(); consensus.BIP34Height = 1; // Always active unless overridden consensus.BIP34Hash = uint256(); consensus.BIP65Height = 1; // Always active unless overridden consensus.BIP66Height = 1; // Always active unless overridden consensus.CSVHeight = 1; // Always active unless overridden - consensus.SegwitHeight = 1; // Always active unless overridden + consensus.SegwitHeight = 0; // Always active unless overridden consensus.MinBIP9WarningHeight = 0; consensus.powLimit = uint256S("7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); consensus.nPowTargetTimespan = 14 * 24 * 60 * 60; // two weeks @@ -525,8 +526,7 @@ void CRegTestParams::UpdateActivationParametersFromArgs(const ArgsManager& args) if (!args.IsArgSet("-vbparams")) return; for (const std::string& strDeployment : args.GetArgs("-vbparams")) { - std::vector<std::string> vDeploymentParams; - boost::split(vDeploymentParams, strDeployment, boost::is_any_of(":")); + std::vector<std::string> vDeploymentParams = SplitString(strDeployment, ':'); if (vDeploymentParams.size() < 3 || 4 < vDeploymentParams.size()) { throw std::runtime_error("Version bits parameters malformed, expecting deployment:start:end[:min_activation_height]"); } diff --git a/src/checkqueue.h b/src/checkqueue.h index d0e88a3410..bead6f0c6f 100644 --- a/src/checkqueue.h +++ b/src/checkqueue.h @@ -66,7 +66,7 @@ private: bool m_request_stop GUARDED_BY(m_mutex){false}; /** Internal function that does bulk of the verification work. */ - bool Loop(bool fMaster) + bool Loop(bool fMaster) EXCLUSIVE_LOCKS_REQUIRED(!m_mutex) { std::condition_variable& cond = fMaster ? m_master_cv : m_worker_cv; std::vector<T> vChecks; @@ -140,7 +140,7 @@ public: } //! Create a pool of new worker threads. - void StartWorkerThreads(const int threads_num) + void StartWorkerThreads(const int threads_num) EXCLUSIVE_LOCKS_REQUIRED(!m_mutex) { { LOCK(m_mutex); @@ -159,13 +159,13 @@ public: } //! Wait until execution finishes, and return whether all evaluations were successful. - bool Wait() + bool Wait() EXCLUSIVE_LOCKS_REQUIRED(!m_mutex) { return Loop(true /* master thread */); } //! Add a batch of checks to the queue - void Add(std::vector<T>& vChecks) + void Add(std::vector<T>& vChecks) EXCLUSIVE_LOCKS_REQUIRED(!m_mutex) { if (vChecks.empty()) { return; @@ -188,7 +188,7 @@ public: } //! Stop all of the worker threads. - void StopWorkerThreads() + void StopWorkerThreads() EXCLUSIVE_LOCKS_REQUIRED(!m_mutex) { WITH_LOCK(m_mutex, m_request_stop = true); m_worker_cv.notify_all(); 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/bloom.cpp b/src/common/bloom.cpp index 8b32a6c94a..aa3fcf1ce2 100644 --- a/src/common/bloom.cpp +++ b/src/common/bloom.cpp @@ -239,7 +239,7 @@ bool CRollingBloomFilter::contains(Span<const unsigned char> vKey) const void CRollingBloomFilter::reset() { - nTweak = GetRand(std::numeric_limits<unsigned int>::max()); + nTweak = GetRand<unsigned int>(); nEntriesThisGeneration = 0; nGeneration = 1; std::fill(data.begin(), data.end(), 0); diff --git a/src/compat/byteswap.h b/src/compat/byteswap.h index 27ef1a18df..2f4232fa5c 100644 --- a/src/compat/byteswap.h +++ b/src/compat/byteswap.h @@ -9,7 +9,7 @@ #include <config/bitcoin-config.h> #endif -#include <stdint.h> +#include <cstdint> #if defined(HAVE_BYTESWAP_H) #include <byteswap.h> diff --git a/src/compat.h b/src/compat/compat.h index 237b881b11..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,59 +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 -#if HAVE_DECL_STRNLEN == 0 -size_t strnlen( const char *start, size_t max_len); -#endif // HAVE_DECL_STRNLEN - +// 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 @@ -115,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/compat/cpuid.h b/src/compat/cpuid.h index 0877ad47d3..e78c1ce6d1 100644 --- a/src/compat/cpuid.h +++ b/src/compat/cpuid.h @@ -10,6 +10,8 @@ #include <cpuid.h> +#include <cstdint> + // We can't use cpuid.h's __get_cpuid as it does not support subleafs. void static inline GetCPUID(uint32_t leaf, uint32_t subleaf, uint32_t& a, uint32_t& b, uint32_t& c, uint32_t& d) { diff --git a/src/compat/endian.h b/src/compat/endian.h index c5cf7a46cc..bdd8b84c1b 100644 --- a/src/compat/endian.h +++ b/src/compat/endian.h @@ -11,7 +11,7 @@ #include <compat/byteswap.h> -#include <stdint.h> +#include <cstdint> #if defined(HAVE_ENDIAN_H) #include <endian.h> diff --git a/src/compat/glibcxx_sanity.cpp b/src/compat/glibcxx_sanity.cpp deleted file mode 100644 index e6e6208e40..0000000000 --- a/src/compat/glibcxx_sanity.cpp +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright (c) 2009-2018 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 <list> -#include <locale> -#include <stdexcept> - -namespace -{ -// trigger: use ctype<char>::widen to trigger ctype<char>::_M_widen_init(). -// test: convert a char from narrow to wide and back. Verify that the result -// matches the original. -bool sanity_test_widen(char testchar) -{ - const std::ctype<char>& test(std::use_facet<std::ctype<char> >(std::locale())); - return test.narrow(test.widen(testchar), 'b') == testchar; -} - -// trigger: use list::push_back and list::pop_back to trigger _M_hook and -// _M_unhook. -// test: Push a sequence of integers into a list. Pop them off and verify that -// they match the original sequence. -bool sanity_test_list(unsigned int size) -{ - std::list<unsigned int> test; - for (unsigned int i = 0; i != size; ++i) - test.push_back(i + 1); - - if (test.size() != size) - return false; - - while (!test.empty()) { - if (test.back() != test.size()) - return false; - test.pop_back(); - } - return true; -} - -} // namespace - -// trigger: string::at(x) on an empty string to trigger __throw_out_of_range_fmt. -// test: force std::string to throw an out_of_range exception. Verify that -// it's caught correctly. -bool sanity_test_range_fmt() -{ - std::string test; - try { - test.at(1); - } catch (const std::out_of_range&) { - return true; - } catch (...) { - } - return false; -} - -bool glibcxx_sanity_test() -{ - return sanity_test_widen('a') && sanity_test_list(100) && sanity_test_range_fmt(); -} diff --git a/src/compat/sanity.h b/src/compat/sanity.h deleted file mode 100644 index 8e5811f1fd..0000000000 --- a/src/compat/sanity.h +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright (c) 2009-2021 The Bitcoin Core developers -// Distributed under the MIT software license, see the accompanying -// file COPYING or http://www.opensource.org/licenses/mit-license.php. - -#ifndef BITCOIN_COMPAT_SANITY_H -#define BITCOIN_COMPAT_SANITY_H - -bool glibcxx_sanity_test(); - -#endif // BITCOIN_COMPAT_SANITY_H diff --git a/src/compat/stdin.cpp b/src/compat/stdin.cpp index 0fc4e0fcf2..61d66e39f2 100644 --- a/src/compat/stdin.cpp +++ b/src/compat/stdin.cpp @@ -2,23 +2,19 @@ // 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 <compat/stdin.h> -#include <cstdio> // for fileno(), stdin +#include <cstdio> #ifdef WIN32 -#include <windows.h> // for SetStdinEcho() -#include <io.h> // for isatty() +#include <windows.h> +#include <io.h> #else -#include <termios.h> // for SetStdinEcho() -#include <unistd.h> // for SetStdinEcho(), isatty() -#include <poll.h> // for StdinReady() +#include <termios.h> +#include <unistd.h> +#include <poll.h> #endif -#include <compat/stdin.h> - // https://stackoverflow.com/questions/1413445/reading-a-password-from-stdcin void SetStdinEcho(bool enable) { diff --git a/src/compat/strnlen.cpp b/src/compat/strnlen.cpp deleted file mode 100644 index 93a034a664..0000000000 --- a/src/compat/strnlen.cpp +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright (c) 2009-2018 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 <cstring> - -#if HAVE_DECL_STRNLEN == 0 -size_t strnlen( const char *start, size_t max_len) -{ - const char *end = (const char *)memchr(start, '\0', max_len); - - return end ? (size_t)(end - start) : max_len; -} -#endif // HAVE_DECL_STRNLEN diff --git a/src/consensus/consensus.h b/src/consensus/consensus.h index 788fa4e55b..b2a31e3ba4 100644 --- a/src/consensus/consensus.h +++ b/src/consensus/consensus.h @@ -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 1ed5ca469f..794e0f5383 100644 --- a/src/consensus/params.h +++ b/src/consensus/params.h @@ -7,7 +7,9 @@ #define BITCOIN_CONSENSUS_PARAMS_H #include <uint256.h> + #include <limits> +#include <map> namespace Consensus { @@ -38,11 +40,11 @@ constexpr bool ValidDeployment(DeploymentPos dep) { return dep < MAX_VERSION_BIT */ struct BIP9Deployment { /** Bit position to select the particular bit in nVersion. */ - int bit; + int bit{28}; /** Start MedianTime for version bits miner confirmation. Can be a date in the past */ - int64_t nStartTime; + int64_t nStartTime{NEVER_ACTIVE}; /** Timeout/expiry MedianTime for the deployment attempt. */ - int64_t nTimeout; + int64_t nTimeout{NEVER_ACTIVE}; /** If lock in occurs, delay activation until at least this block * height. Note that activation will only occur on a retarget * boundary. @@ -70,8 +72,13 @@ struct BIP9Deployment { struct Params { uint256 hashGenesisBlock; int nSubsidyHalvingInterval; - /* Block hash that is excepted from BIP16 enforcement */ - uint256 BIP16Exception; + /** + * Hashes of blocks that + * - are known to be consensus valid, and + * - buried in the chain, and + * - fail if the default script verify flags are applied. + */ + std::map<uint256, uint32_t> script_flag_exceptions; /** Block height and hash at which BIP34 becomes active */ int BIP34Height; uint256 BIP34Hash; diff --git a/src/consensus/tx_verify.cpp b/src/consensus/tx_verify.cpp index 5738c333ce..154146f08d 100644 --- a/src/consensus/tx_verify.cpp +++ b/src/consensus/tx_verify.cpp @@ -11,6 +11,7 @@ #include <consensus/validation.h> #include <primitives/transaction.h> #include <script/interpreter.h> +#include <util/check.h> #include <util/moneystr.h> bool IsFinalTx(const CTransaction &tx, int nBlockHeight, int64_t nBlockTime) @@ -74,7 +75,7 @@ std::pair<int, int64_t> CalculateSequenceLocks(const CTransaction &tx, int flags int nCoinHeight = prevHeights[txinIndex]; if (txin.nSequence & CTxIn::SEQUENCE_LOCKTIME_TYPE_FLAG) { - int64_t nCoinTime = block.GetAncestor(std::max(nCoinHeight-1, 0))->GetMedianTimePast(); + const int64_t nCoinTime{Assert(block.GetAncestor(std::max(nCoinHeight - 1, 0)))->GetMedianTimePast()}; // NOTE: Subtract 1 to maintain nLockTime semantics // BIP 68 relative lock times have the semantics of calculating // the first block or time at which the transaction would be diff --git a/src/core_io.h b/src/core_io.h index 69b9ac3ebd..c91c8199d8 100644 --- a/src/core_io.h +++ b/src/core_io.h @@ -6,7 +6,6 @@ #define BITCOIN_CORE_IO_H #include <consensus/amount.h> -#include <attributes.h> #include <string> #include <vector> @@ -53,8 +52,7 @@ UniValue ValueFromAmount(const CAmount amount); std::string FormatScript(const CScript& script); std::string EncodeHexTx(const CTransaction& tx, const int serializeFlags = 0); std::string SighashToStr(unsigned char sighash_type); -void ScriptPubKeyToUniv(const CScript& scriptPubKey, UniValue& out, bool include_hex, bool include_address = true); -void ScriptToUniv(const CScript& script, UniValue& out); -void TxToUniv(const CTransaction& tx, const uint256& hashBlock, UniValue& entry, bool include_hex = true, int serialize_flags = 0, const CTxUndo* txundo = nullptr, TxVerbosity verbosity = TxVerbosity::SHOW_DETAILS); +void ScriptToUniv(const CScript& script, UniValue& out, bool include_hex = true, bool include_address = false); +void TxToUniv(const CTransaction& tx, const uint256& block_hash, UniValue& entry, bool include_hex = true, int serialize_flags = 0, const CTxUndo* txundo = nullptr, TxVerbosity verbosity = TxVerbosity::SHOW_DETAILS); #endif // BITCOIN_CORE_IO_H diff --git a/src/core_read.cpp b/src/core_read.cpp index 3bab5b5d98..ec21a2f7f4 100644 --- a/src/core_read.cpp +++ b/src/core_read.cpp @@ -14,9 +14,6 @@ #include <util/strencodings.h> #include <version.h> -#include <boost/algorithm/string/classification.hpp> -#include <boost/algorithm/string/split.hpp> - #include <algorithm> #include <string> @@ -66,12 +63,11 @@ CScript ParseScript(const std::string& s) { CScript result; - std::vector<std::string> words; - boost::algorithm::split(words, s, boost::algorithm::is_any_of(" \t\n"), boost::algorithm::token_compress_on); + std::vector<std::string> words = SplitString(s, " \t\n"); for (const std::string& w : words) { if (w.empty()) { - // Empty string, ignore. (boost::split given '' will return one word) + // Empty string, ignore. (SplitString doesn't combine multiple separators) } else if (std::all_of(w.begin(), w.end(), ::IsDigit) || (w.front() == '-' && w.size() > 1 && std::all_of(w.begin() + 1, w.end(), ::IsDigit))) { @@ -269,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/core_write.cpp b/src/core_write.cpp index c4b6b8d27e..cfd4cb1b49 100644 --- a/src/core_write.cpp +++ b/src/core_write.cpp @@ -19,6 +19,10 @@ #include <util/strencodings.h> #include <util/system.h> +#include <map> +#include <string> +#include <vector> + UniValue ValueFromAmount(const CAmount amount) { static_assert(COIN > 1); @@ -143,29 +147,28 @@ std::string EncodeHexTx(const CTransaction& tx, const int serializeFlags) return HexStr(ssTx); } -void ScriptToUniv(const CScript& script, UniValue& out) -{ - ScriptPubKeyToUniv(script, out, /* include_hex */ true, /* include_address */ false); -} - -void ScriptPubKeyToUniv(const CScript& scriptPubKey, UniValue& out, bool include_hex, bool include_address) +void ScriptToUniv(const CScript& script, UniValue& out, bool include_hex, bool include_address) { CTxDestination address; - out.pushKV("asm", ScriptToAsmStr(scriptPubKey)); - out.pushKV("desc", InferDescriptor(scriptPubKey, DUMMY_SIGNING_PROVIDER)->ToString()); - if (include_hex) out.pushKV("hex", HexStr(scriptPubKey)); + out.pushKV("asm", ScriptToAsmStr(script)); + if (include_address) { + out.pushKV("desc", InferDescriptor(script, DUMMY_SIGNING_PROVIDER)->ToString()); + } + if (include_hex) { + out.pushKV("hex", HexStr(script)); + } std::vector<std::vector<unsigned char>> solns; - const TxoutType type{Solver(scriptPubKey, solns)}; + const TxoutType type{Solver(script, solns)}; - if (include_address && ExtractDestination(scriptPubKey, address) && type != TxoutType::PUBKEY) { + if (include_address && ExtractDestination(script, address) && type != TxoutType::PUBKEY) { out.pushKV("address", EncodeDestination(address)); } out.pushKV("type", GetTxnOutputType(type)); } -void TxToUniv(const CTransaction& tx, const uint256& hashBlock, UniValue& entry, bool include_hex, int serialize_flags, const CTxUndo* txundo, TxVerbosity verbosity) +void TxToUniv(const CTransaction& tx, const uint256& block_hash, UniValue& entry, bool include_hex, int serialize_flags, const CTxUndo* txundo, TxVerbosity verbosity) { entry.pushKV("txid", tx.GetHash().GetHex()); entry.pushKV("hash", tx.GetWitnessHash().GetHex()); @@ -213,7 +216,7 @@ void TxToUniv(const CTransaction& tx, const uint256& hashBlock, UniValue& entry, if (verbosity == TxVerbosity::SHOW_DETAILS_AND_PREVOUT) { UniValue o_script_pub_key(UniValue::VOBJ); - ScriptPubKeyToUniv(prev_txout.scriptPubKey, o_script_pub_key, /*include_hex=*/ true); + ScriptToUniv(prev_txout.scriptPubKey, /*out=*/o_script_pub_key, /*include_hex=*/true, /*include_address=*/true); UniValue p(UniValue::VOBJ); p.pushKV("generated", bool(prev_coin.fCoinBase)); @@ -238,7 +241,7 @@ void TxToUniv(const CTransaction& tx, const uint256& hashBlock, UniValue& entry, out.pushKV("n", (int64_t)i); UniValue o(UniValue::VOBJ); - ScriptPubKeyToUniv(txout.scriptPubKey, o, true); + ScriptToUniv(txout.scriptPubKey, /*out=*/o, /*include_hex=*/true, /*include_address=*/true); out.pushKV("scriptPubKey", o); vout.push_back(out); @@ -254,8 +257,9 @@ void TxToUniv(const CTransaction& tx, const uint256& hashBlock, UniValue& entry, entry.pushKV("fee", ValueFromAmount(fee)); } - if (!hashBlock.IsNull()) - entry.pushKV("blockhash", hashBlock.GetHex()); + if (!block_hash.IsNull()) { + entry.pushKV("blockhash", block_hash.GetHex()); + } if (include_hex) { entry.pushKV("hex", EncodeHexTx(tx, serialize_flags)); // The hex-encoded transaction. Used the name "hex" to be consistent with the verbose output of "getrawtransaction". 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.cpp b/src/crypto/chacha20.cpp index f3ff4268ee..c7e12b0612 100644 --- a/src/crypto/chacha20.cpp +++ b/src/crypto/chacha20.cpp @@ -18,6 +18,8 @@ constexpr static inline uint32_t rotl32(uint32_t v, int c) { return (v << c) | ( a += b; d = rotl32(d ^ a, 8); \ c += d; b = rotl32(b ^ c, 7); +#define REPEAT10(a) do { {a}; {a}; {a}; {a}; {a}; {a}; {a}; {a}; {a}; {a}; } while(0) + static const unsigned char sigma[] = "expand 32-byte k"; static const unsigned char tau[] = "expand 16-byte k"; @@ -119,16 +121,19 @@ void ChaCha20::Keystream(unsigned char* c, size_t bytes) x13 = j13; x14 = j14; x15 = j15; - for (i = 20;i > 0;i -= 2) { - QUARTERROUND( x0, x4, x8,x12) - QUARTERROUND( x1, x5, x9,x13) - QUARTERROUND( x2, x6,x10,x14) - QUARTERROUND( x3, x7,x11,x15) - QUARTERROUND( x0, x5,x10,x15) - QUARTERROUND( x1, x6,x11,x12) - QUARTERROUND( x2, x7, x8,x13) - QUARTERROUND( x3, x4, x9,x14) - } + + // The 20 inner ChaCha20 rounds are unrolled here for performance. + REPEAT10( + QUARTERROUND( x0, x4, x8,x12); + QUARTERROUND( x1, x5, x9,x13); + QUARTERROUND( x2, x6,x10,x14); + QUARTERROUND( x3, x7,x11,x15); + QUARTERROUND( x0, x5,x10,x15); + QUARTERROUND( x1, x6,x11,x12); + QUARTERROUND( x2, x7, x8,x13); + QUARTERROUND( x3, x4, x9,x14); + ); + x0 += j0; x1 += j1; x2 += j2; @@ -231,16 +236,19 @@ void ChaCha20::Crypt(const unsigned char* m, unsigned char* c, size_t bytes) x13 = j13; x14 = j14; x15 = j15; - for (i = 20;i > 0;i -= 2) { - QUARTERROUND( x0, x4, x8,x12) - QUARTERROUND( x1, x5, x9,x13) - QUARTERROUND( x2, x6,x10,x14) - QUARTERROUND( x3, x7,x11,x15) - QUARTERROUND( x0, x5,x10,x15) - QUARTERROUND( x1, x6,x11,x12) - QUARTERROUND( x2, x7, x8,x13) - QUARTERROUND( x3, x4, x9,x14) - } + + // The 20 inner ChaCha20 rounds are unrolled here for performance. + REPEAT10( + QUARTERROUND( x0, x4, x8,x12); + QUARTERROUND( x1, x5, x9,x13); + QUARTERROUND( x2, x6,x10,x14); + QUARTERROUND( x3, x7,x11,x15); + QUARTERROUND( x0, x5,x10,x15); + QUARTERROUND( x1, x6,x11,x12); + QUARTERROUND( x2, x7, x8,x13); + QUARTERROUND( x3, x4, x9,x14); + ); + x0 += j0; x1 += j1; x2 += j2; 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/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/sha256.cpp b/src/crypto/sha256.cpp index cde543e68c..196f81ea16 100644 --- a/src/crypto/sha256.cpp +++ b/src/crypto/sha256.cpp @@ -586,17 +586,9 @@ std::string SHA256AutoDetect() bool have_sse4 = false; bool have_xsave = false; bool have_avx = false; - bool have_avx2 = false; - bool have_x86_shani = false; - bool enabled_avx = false; - - (void)AVXEnabled; - (void)have_sse4; - (void)have_avx; - (void)have_xsave; - (void)have_avx2; - (void)have_x86_shani; - (void)enabled_avx; + [[maybe_unused]] bool have_avx2 = false; + [[maybe_unused]] bool have_x86_shani = false; + [[maybe_unused]] bool enabled_avx = false; uint32_t eax, ebx, ecx, edx; GetCPUID(1, 0, eax, ebx, ecx, edx); @@ -641,7 +633,7 @@ std::string SHA256AutoDetect() ret += ",avx2(8way)"; } #endif -#endif +#endif // defined(USE_ASM) && defined(HAVE_GETCPUID) #if defined(ENABLE_ARM_SHANI) && !defined(BUILD_BITCOIN_INTERNAL) bool have_arm_shani = false; 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 b0ea80ea1a..4dbc839941 100644 --- a/src/dbwrapper.cpp +++ b/src/dbwrapper.cpp @@ -4,22 +4,35 @@ #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: // This code is adapted from posix_logger.h, which is why it is using vsprintf. // Please do not do this in normal code void Logv(const char * format, va_list ap) override { - if (!LogAcceptCategory(BCLog::LEVELDB)) { + if (!LogAcceptCategory(BCLog::LEVELDB, BCLog::Level::Debug)) { return; } char buffer[500]; @@ -63,7 +76,7 @@ public: assert(p <= limit); base[std::min(bufsize - 1, (int)(p - base))] = '\0'; - LogPrintf("leveldb: %s", base); /* Continued */ + LogPrintLevel(BCLog::LEVELDB, BCLog::Level::Debug, "%s", base); /* Continued */ if (base != buffer) { delete[] base; } @@ -186,7 +199,7 @@ CDBWrapper::~CDBWrapper() bool CDBWrapper::WriteBatch(CDBBatch& batch, bool fSync) { - const bool log_memory = LogAcceptCategory(BCLog::LEVELDB); + const bool log_memory = LogAcceptCategory(BCLog::LEVELDB, BCLog::Level::Debug); double mem_before = 0; if (log_memory) { mem_before = DynamicMemoryUsage() / 1024.0 / 1024; @@ -227,7 +240,7 @@ const unsigned int CDBWrapper::OBFUSCATE_KEY_NUM_BYTES = 8; std::vector<unsigned char> CDBWrapper::CreateObfuscateKey() const { std::vector<uint8_t> ret(OBFUSCATE_KEY_NUM_BYTES); - GetRandBytes(ret.data(), OBFUSCATE_KEY_NUM_BYTES); + GetRandBytes(ret); return ret; } 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/deploymentstatus.cpp b/src/deploymentstatus.cpp index ae19a6e40d..3a524af29e 100644 --- a/src/deploymentstatus.cpp +++ b/src/deploymentstatus.cpp @@ -9,8 +9,6 @@ #include <type_traits> -VersionBitsCache g_versionbitscache; - /* Basic sanity checking for BuriedDeployment/DeploymentPos enums and * ValidDeployment check */ diff --git a/src/deploymentstatus.h b/src/deploymentstatus.h index ba5103de74..9f5919f71b 100644 --- a/src/deploymentstatus.h +++ b/src/deploymentstatus.h @@ -10,33 +10,30 @@ #include <limits> -/** Global cache for versionbits deployment status */ -extern VersionBitsCache g_versionbitscache; - /** Determine if a deployment is active for the next block */ -inline bool DeploymentActiveAfter(const CBlockIndex* pindexPrev, const Consensus::Params& params, Consensus::BuriedDeployment dep) +inline bool DeploymentActiveAfter(const CBlockIndex* pindexPrev, const Consensus::Params& params, Consensus::BuriedDeployment dep, [[maybe_unused]] VersionBitsCache& versionbitscache) { assert(Consensus::ValidDeployment(dep)); return (pindexPrev == nullptr ? 0 : pindexPrev->nHeight + 1) >= params.DeploymentHeight(dep); } -inline bool DeploymentActiveAfter(const CBlockIndex* pindexPrev, const Consensus::Params& params, Consensus::DeploymentPos dep) +inline bool DeploymentActiveAfter(const CBlockIndex* pindexPrev, const Consensus::Params& params, Consensus::DeploymentPos dep, VersionBitsCache& versionbitscache) { assert(Consensus::ValidDeployment(dep)); - return ThresholdState::ACTIVE == g_versionbitscache.State(pindexPrev, params, dep); + return ThresholdState::ACTIVE == versionbitscache.State(pindexPrev, params, dep); } /** Determine if a deployment is active for this block */ -inline bool DeploymentActiveAt(const CBlockIndex& index, const Consensus::Params& params, Consensus::BuriedDeployment dep) +inline bool DeploymentActiveAt(const CBlockIndex& index, const Consensus::Params& params, Consensus::BuriedDeployment dep, [[maybe_unused]] VersionBitsCache& versionbitscache) { assert(Consensus::ValidDeployment(dep)); return index.nHeight >= params.DeploymentHeight(dep); } -inline bool DeploymentActiveAt(const CBlockIndex& index, const Consensus::Params& params, Consensus::DeploymentPos dep) +inline bool DeploymentActiveAt(const CBlockIndex& index, const Consensus::Params& params, Consensus::DeploymentPos dep, VersionBitsCache& versionbitscache) { assert(Consensus::ValidDeployment(dep)); - return DeploymentActiveAfter(index.pprev, params, dep); + return DeploymentActiveAfter(index.pprev, params, dep, versionbitscache); } /** Determine if a deployment is enabled (can ever be active) */ diff --git a/src/dummywallet.cpp b/src/dummywallet.cpp index 2b94ed611b..028c6ebae1 100644 --- a/src/dummywallet.cpp +++ b/src/dummywallet.cpp @@ -50,6 +50,7 @@ void DummyWalletInit::AddWalletOptions(ArgsManager& argsman) const "-flushwallet", "-privdb", "-walletrejectlongchains", + "-walletcrosschain", "-unsafesqlitesync", }); } diff --git a/src/external_signer.cpp b/src/external_signer.cpp index 75070899c6..094314e878 100644 --- a/src/external_signer.cpp +++ b/src/external_signer.cpp @@ -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(); @@ -74,11 +74,12 @@ bool ExternalSigner::SignTransaction(PartiallySignedTransaction& psbtx, std::str // Serialize the PSBT CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION); ssTx << psbtx; - + // parse ExternalSigner master fingerprint + std::vector<unsigned char> parsed_m_fingerprint = ParseHex(m_fingerprint); // Check if signer fingerprint matches any input master key fingerprint auto matches_signer_fingerprint = [&](const PSBTInput& input) { for (const auto& entry : input.hd_keypaths) { - if (m_fingerprint == strprintf("%08x", ReadBE32(entry.second.fingerprint))) return true; + if (parsed_m_fingerprint == MakeUCharSpan(entry.second.fingerprint)) return true; } return false; }; diff --git a/src/flatfile.cpp b/src/flatfile.cpp index d6cada0c46..0fecf4f504 100644 --- a/src/flatfile.cpp +++ b/src/flatfile.cpp @@ -27,7 +27,7 @@ std::string FlatFilePos::ToString() const fs::path FlatFileSeq::FileName(const FlatFilePos& pos) const { - return m_dir / strprintf("%s%05u.dat", m_prefix, pos.nFile); + return m_dir / fs::u8path(strprintf("%s%05u.dat", m_prefix, pos.nFile)); } FILE* FlatFileSeq::Open(const FlatFilePos& pos, bool read_only) diff --git a/src/fs.cpp b/src/fs.cpp index 219fdee959..74b167e313 100644 --- a/src/fs.cpp +++ b/src/fs.cpp @@ -3,6 +3,7 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include <fs.h> +#include <util/syserror.h> #ifndef WIN32 #include <cstring> @@ -11,9 +12,6 @@ #include <sys/utsname.h> #include <unistd.h> #else -#ifndef NOMINMAX -#define NOMINMAX -#endif #include <codecvt> #include <limits> #include <windows.h> @@ -44,7 +42,7 @@ fs::path AbsPathJoin(const fs::path& base, const fs::path& path) static std::string GetErrorReason() { - return std::strerror(errno); + return SysErrorString(errno); } FileLock::FileLock(const fs::path& file) @@ -9,6 +9,7 @@ #include <cstdio> #include <filesystem> +#include <functional> #include <iomanip> #include <ios> #include <ostream> @@ -51,12 +52,30 @@ public: // Disallow std::string conversion method to avoid locale-dependent encoding on windows. std::string string() const = delete; + std::string u8string() const + { + const auto& utf8_str{std::filesystem::path::u8string()}; + // utf8_str might either be std::string (C++17) or std::u8string + // (C++20). Convert both to std::string. This method can be removed + // after switching to C++20. + return std::string{utf8_str.begin(), utf8_str.end()}; + } + // Required for path overloads in <fstream>. // See https://gcc.gnu.org/git/?p=gcc.git;a=commit;h=96e0367ead5d8dcac3bec2865582e76e2fbab190 path& make_preferred() { std::filesystem::path::make_preferred(); return *this; } path filename() const { return std::filesystem::path::filename(); } }; +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 // locale-dependent encoding on windows. static inline path absolute(const path& p) @@ -78,11 +97,30 @@ static inline auto quoted(const std::string& s) } // Allow safe path append operations. -static inline path operator+(path p1, path p2) +static inline path operator/(path p1, path p2) +{ + p1 /= std::move(p2); + return p1; +} +static inline path operator/(path p1, const char* p2) +{ + p1 /= p2; + return p1; +} +static inline path operator+(path p1, const char* p2) { - p1 += std::move(p2); + p1 += p2; return p1; } +static inline path operator+(path p1, path::value_type p2) +{ + p1 += p2; + return p1; +} + +// Disallow unsafe path append operations. +template<typename T> static inline path operator/(path p1, T p2) = delete; +template<typename T> static inline path operator+(path p1, T p2) = delete; // Disallow implicit std::string conversion for copy_file // to avoid locale-dependent encoding on Windows. @@ -116,8 +154,8 @@ static inline std::string PathToString(const path& path) // use here, because these methods encode the path using C++'s narrow // multibyte encoding, which on Windows corresponds to the current "code // page", which is unpredictable and typically not able to represent all - // valid paths. So std::filesystem::path::u8string() and - // std::filesystem::u8path() functions are used instead on Windows. On + // valid paths. So fs::path::u8string() and + // fs::u8path() functions are used instead on Windows. On // POSIX, u8string/u8path functions are not safe to use because paths are // not always valid UTF-8, so plain string methods which do not transform // the path there are used. @@ -166,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..111b707964 100644 --- a/src/hash.cpp +++ b/src/hash.cpp @@ -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 9f582842c1..b1ff3acc7d 100644 --- a/src/hash.h +++ b/src/hash.h @@ -6,7 +6,6 @@ #ifndef BITCOIN_HASH_H #define BITCOIN_HASH_H -#include <attributes.h> #include <crypto/common.h> #include <crypto/ripemd160.h> #include <crypto/sha256.h> @@ -97,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()); @@ -145,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 @@ -204,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/httprpc.cpp b/src/httprpc.cpp index 5d0b59f7cb..4e7e72b037 100644 --- a/src/httprpc.cpp +++ b/src/httprpc.cpp @@ -4,7 +4,6 @@ #include <httprpc.h> -#include <chainparams.h> #include <crypto/hmac_sha256.h> #include <httpserver.h> #include <rpc/protocol.h> @@ -12,18 +11,15 @@ #include <util/strencodings.h> #include <util/string.h> #include <util/system.h> -#include <util/translation.h> #include <walletinitinterface.h> #include <algorithm> #include <iterator> #include <map> #include <memory> -#include <stdio.h> #include <set> #include <string> - -#include <boost/algorithm/string.hpp> +#include <vector> /** WWW-Authenticate to present with 401 Unauthorized response */ static const char* WWW_AUTH_HEADER_DATA = "Basic realm=\"jsonrpc\""; @@ -79,7 +75,7 @@ static void JSONErrorReply(HTTPRequest* req, const UniValue& objError, const Uni { // Send error reply from json-rpc error object int nStatus = HTTP_INTERNAL_SERVER_ERROR; - int code = find_value(objError, "code").get_int(); + int code = find_value(objError, "code").getInt<int>(); if (code == RPC_INVALID_REQUEST) nStatus = HTTP_BAD_REQUEST; @@ -131,8 +127,11 @@ static bool RPCAuthorized(const std::string& strAuth, std::string& strAuthUserna return false; if (strAuth.substr(0, 6) != "Basic ") return false; - std::string strUserPass64 = TrimString(strAuth.substr(6)); - std::string strUserPass = DecodeBase64(strUserPass64); + std::string_view strUserPass64 = TrimStringView(std::string_view{strAuth}.substr(6)); + auto userpass_data = DecodeBase64(strUserPass64); + std::string strUserPass; + if (!userpass_data) return false; + strUserPass.assign(userpass_data->begin(), userpass_data->end()); if (strUserPass.find(':') != std::string::npos) strAuthUsernameOut = strUserPass.substr(0, strUserPass.find(':')); @@ -251,13 +250,14 @@ static bool InitRPCAuthentication() LogPrintf("Config options rpcuser and rpcpassword will soon be deprecated. Locally-run instances may remove rpcuser to use cookie-based auth, or may be replaced with rpcauth. Please see share/rpcauth for rpcauth auth generation.\n"); strRPCUserColonPass = gArgs.GetArg("-rpcuser", "") + ":" + gArgs.GetArg("-rpcpassword", ""); } - if (gArgs.GetArg("-rpcauth","") != "") - { + if (gArgs.GetArg("-rpcauth", "") != "") { LogPrintf("Using rpcauth authentication.\n"); for (const std::string& rpcauth : gArgs.GetArgs("-rpcauth")) { - std::vector<std::string> fields; - boost::split(fields, rpcauth, boost::is_any_of(":$")); - if (fields.size() == 3) { + std::vector<std::string> fields{SplitString(rpcauth, ':')}; + const std::vector<std::string> salt_hmac{SplitString(fields.back(), '$')}; + if (fields.size() == 2 && salt_hmac.size() == 2) { + fields.pop_back(); + fields.insert(fields.end(), salt_hmac.begin(), salt_hmac.end()); g_rpcauth.push_back(fields); } else { LogPrintf("Invalid -rpcauth argument.\n"); @@ -274,8 +274,10 @@ static bool InitRPCAuthentication() std::set<std::string>& whitelist = g_rpc_whitelist[strUser]; if (pos != std::string::npos) { std::string strWhitelist = strRPCWhitelist.substr(pos + 1); - std::set<std::string> new_whitelist; - boost::split(new_whitelist, strWhitelist, boost::is_any_of(", ")); + std::vector<std::string> whitelist_split = SplitString(strWhitelist, ", "); + std::set<std::string> new_whitelist{ + std::make_move_iterator(whitelist_split.begin()), + std::make_move_iterator(whitelist_split.end())}; if (intersect) { std::set<std::string> tmp_whitelist; std::set_intersection(new_whitelist.begin(), new_whitelist.end(), diff --git a/src/httpserver.cpp b/src/httpserver.cpp index e00c68585e..8e00a6278f 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> @@ -23,6 +23,7 @@ #include <deque> #include <memory> +#include <optional> #include <stdio.h> #include <stdlib.h> #include <string> @@ -30,11 +31,12 @@ #include <sys/types.h> #include <sys/stat.h> -#include <event2/thread.h> #include <event2/buffer.h> #include <event2/bufferevent.h> -#include <event2/util.h> +#include <event2/http.h> #include <event2/keyvalq_struct.h> +#include <event2/thread.h> +#include <event2/util.h> #include <support/events.h> @@ -71,21 +73,18 @@ private: Mutex cs; std::condition_variable cond GUARDED_BY(cs); std::deque<std::unique_ptr<WorkItem>> queue GUARDED_BY(cs); - bool running GUARDED_BY(cs); + bool running GUARDED_BY(cs){true}; const size_t maxDepth; public: - explicit WorkQueue(size_t _maxDepth) : running(true), - maxDepth(_maxDepth) + explicit WorkQueue(size_t _maxDepth) : maxDepth(_maxDepth) { } /** Precondition: worker threads have all stopped (they have been joined). */ - ~WorkQueue() - { - } + ~WorkQueue() = default; /** Enqueue a work item */ - bool Enqueue(WorkItem* item) + bool Enqueue(WorkItem* item) EXCLUSIVE_LOCKS_REQUIRED(!cs) { LOCK(cs); if (!running || queue.size() >= maxDepth) { @@ -96,7 +95,7 @@ public: return true; } /** Thread function */ - void Run() + void Run() EXCLUSIVE_LOCKS_REQUIRED(!cs) { while (true) { std::unique_ptr<WorkItem> i; @@ -113,7 +112,7 @@ public: } } /** Interrupt and exit loops */ - void Interrupt() + void Interrupt() EXCLUSIVE_LOCKS_REQUIRED(!cs) { LOCK(cs); running = false; @@ -345,10 +344,22 @@ static void HTTPWorkQueueRun(WorkQueue<HTTPClosure>* queue, int worker_num) /** libevent event log callback */ static void libevent_log_cb(int severity, const char *msg) { - if (severity >= EVENT_LOG_WARN) // Log warn messages and higher without debug category - LogPrintf("libevent: %s\n", msg); - else - LogPrint(BCLog::LIBEVENT, "libevent: %s\n", msg); + BCLog::Level level; + switch (severity) { + case EVENT_LOG_DEBUG: + level = BCLog::Level::Debug; + break; + case EVENT_LOG_MSG: + level = BCLog::Level::Info; + break; + case EVENT_LOG_WARN: + level = BCLog::Level::Warning; + break; + default: // EVENT_LOG_ERR and others are mapped to error + level = BCLog::Level::Error; + break; + } + LogPrintLevel(BCLog::LIBEVENT, level, "%s\n", msg); } bool InitHTTPServer() @@ -358,12 +369,8 @@ bool InitHTTPServer() // Redirect libevent's logging to our own log event_set_log_callback(&libevent_log_cb); - // Update libevent's log handling. Returns false if our version of - // libevent doesn't support debug logging, in which case we should - // clear the BCLog::LIBEVENT flag. - if (!UpdateHTTPServerLogging(LogInstance().WillLogCategory(BCLog::LIBEVENT))) { - LogInstance().DisableCategory(BCLog::LIBEVENT); - } + // Update libevent's log handling. + UpdateHTTPServerLogging(LogInstance().WillLogCategory(BCLog::LIBEVENT)); #ifdef WIN32 evthread_use_windows_threads(); @@ -393,7 +400,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() @@ -402,18 +409,12 @@ bool InitHTTPServer() return true; } -bool UpdateHTTPServerLogging(bool enable) { -#if LIBEVENT_VERSION_NUMBER >= 0x02010100 +void UpdateHTTPServerLogging(bool enable) { if (enable) { event_enable_debug_logging(EVENT_DBG_ALL); } else { event_enable_debug_logging(EVENT_DBG_NONE); } - return true; -#else - // Can't update libevent logging if version < 02010100 - return false; -#endif } static std::thread g_thread_http; @@ -423,7 +424,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++) { @@ -639,6 +640,37 @@ HTTPRequest::RequestMethod HTTPRequest::GetRequestMethod() const } } +std::optional<std::string> HTTPRequest::GetQueryParameter(const std::string& key) const +{ + const char* uri{evhttp_request_get_uri(req)}; + + return GetQueryParameterFromUri(uri, key); +} + +std::optional<std::string> GetQueryParameterFromUri(const char* uri, const std::string& key) +{ + evhttp_uri* uri_parsed{evhttp_uri_parse(uri)}; + const char* query{evhttp_uri_get_query(uri_parsed)}; + std::optional<std::string> result; + + if (query) { + // Parse the query string into a key-value queue and iterate over it + struct evkeyvalq params_q; + evhttp_parse_query_str(query, ¶ms_q); + + for (struct evkeyval* param{params_q.tqh_first}; param != nullptr; param = param->next.tqe_next) { + if (param->key == key) { + result = param->value; + break; + } + } + evhttp_clear_headers(¶ms_q); + } + evhttp_uri_free(uri_parsed); + + return result; +} + void RegisterHTTPHandler(const std::string &prefix, bool exactMatch, const HTTPRequestHandler &handler) { LogPrint(BCLog::HTTP, "Registering HTTP handler for %s (exactmatch %d)\n", prefix, exactMatch); diff --git a/src/httpserver.h b/src/httpserver.h index 97cd63778a..5ab3f18927 100644 --- a/src/httpserver.h +++ b/src/httpserver.h @@ -5,8 +5,9 @@ #ifndef BITCOIN_HTTPSERVER_H #define BITCOIN_HTTPSERVER_H -#include <string> #include <functional> +#include <optional> +#include <string> static const int DEFAULT_HTTP_THREADS=4; static const int DEFAULT_HTTP_WORKQUEUE=16; @@ -31,9 +32,8 @@ void InterruptHTTPServer(); /** Stop HTTP server */ void StopHTTPServer(); -/** Change logging level for libevent. Removes BCLog::LIBEVENT from log categories if - * libevent doesn't support debug logging.*/ -bool UpdateHTTPServerLogging(bool enable); +/** Change logging level for libevent. */ +void UpdateHTTPServerLogging(bool enable); /** Handler for requests to a certain HTTP path */ typedef std::function<bool(HTTPRequest* req, const std::string &)> HTTPRequestHandler; @@ -83,6 +83,17 @@ public: */ RequestMethod GetRequestMethod() const; + /** Get the query parameter value from request uri for a specified key, or std::nullopt if the + * key is not found. + * + * If the query string contains duplicate keys, the first value is returned. Many web frameworks + * would instead parse this as an array of values, but this is not (yet) implemented as it is + * currently not needed in any of the endpoints. + * + * @param[in] key represents the query parameter of which the value is returned + */ + std::optional<std::string> GetQueryParameter(const std::string& key) const; + /** * Get the request header specified by hdr, or an empty string. * Return a pair (isPresent,string). @@ -115,6 +126,20 @@ public: void WriteReply(int nStatus, const std::string& strReply = ""); }; +/** Get the query parameter value from request uri for a specified key, or std::nullopt if the key + * is not found. + * + * If the query string contains duplicate keys, the first value is returned. Many web frameworks + * would instead parse this as an array of values, but this is not (yet) implemented as it is + * currently not needed in any of the endpoints. + * + * Helper function for HTTPRequest::GetQueryParameter. + * + * @param[in] uri is the entire request uri + * @param[in] key represents the query parameter of which the value is returned + */ +std::optional<std::string> GetQueryParameterFromUri(const char* uri, const std::string& key); + /** Event handler closure. */ class HTTPClosure diff --git a/src/i2p.cpp b/src/i2p.cpp index ccba14d63d..c45bcc15d2 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> @@ -69,12 +69,11 @@ static std::string SwapBase64(const std::string& from) static Binary DecodeI2PBase64(const std::string& i2p_b64) { const std::string& std_b64 = SwapBase64(i2p_b64); - bool invalid; - Binary decoded = DecodeBase64(std_b64.c_str(), &invalid); - if (invalid) { + auto decoded = DecodeBase64(std_b64); + if (!decoded) { throw std::runtime_error(strprintf("Cannot decode Base64: \"%s\"", i2p_b64)); } - return decoded; + return std::move(*decoded); } /** @@ -151,8 +150,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; } @@ -243,7 +242,7 @@ std::string Session::Reply::Get(const std::string& key) const template <typename... Args> void Session::Log(const std::string& fmt, const Args&... args) const { - LogPrint(BCLog::I2P, "I2P: %s\n", tfm::format(fmt, args...)); + LogPrint(BCLog::I2P, "%s\n", tfm::format(fmt, args...)); } Session::Reply Session::SendRequestAndGetReply(const Sock& sock, @@ -377,8 +376,8 @@ void Session::CreateIfNotCreatedAlready() 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()); + LogPrintfCategory(BCLog::I2P, "SAM session created: session id=%s, my address=%s\n", + m_session_id, m_my_addr.ToString()); } std::unique_ptr<Sock> Session::StreamAccept() @@ -411,7 +410,7 @@ void Session::Disconnect() Log("Destroying 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> @@ -84,7 +84,7 @@ public: * to the listening socket and address. * @return true on success */ - bool Listen(Connection& conn); + bool Listen(Connection& conn) EXCLUSIVE_LOCKS_REQUIRED(!m_mutex); /** * Wait for and accept a new incoming connection. @@ -103,7 +103,7 @@ public: * it is set to `false`. Only set if `false` is returned. * @return true on success */ - bool Connect(const CService& to, Connection& conn, bool& proxy_error); + bool Connect(const CService& to, Connection& conn, bool& proxy_error) EXCLUSIVE_LOCKS_REQUIRED(!m_mutex); private: /** @@ -172,7 +172,7 @@ private: /** * Check the control socket for errors and possibly disconnect. */ - void CheckControlSock(); + void CheckControlSock() EXCLUSIVE_LOCKS_REQUIRED(!m_mutex); /** * Generate a new destination with the SAM proxy and set `m_private_key` to it. diff --git a/src/index/base.cpp b/src/index/base.cpp index 8fe30f8960..1ebe89ef7c 100644 --- a/src/index/base.cpp +++ b/src/index/base.cpp @@ -4,11 +4,15 @@ #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 @@ -18,8 +22,8 @@ using node::ReadBlockFromDisk; constexpr uint8_t DB_BEST_BLOCK{'B'}; -constexpr int64_t SYNC_LOG_INTERVAL = 30; // seconds -constexpr int64_t SYNC_LOCATOR_WRITE_INTERVAL = 30; // seconds +constexpr auto SYNC_LOG_INTERVAL{30s}; +constexpr auto SYNC_LOCATOR_WRITE_INTERVAL{30s}; template <typename... Args> static void FatalError(const char* fmt, const Args&... args) @@ -31,6 +35,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 +62,9 @@ void BaseIndex::DB::WriteBestBlock(CDBBatch& batch, const CBlockLocator& locator batch.Write(DB_BEST_BLOCK, locator); } +BaseIndex::BaseIndex(std::unique_ptr<interfaces::Chain> chain) + : m_chain{std::move(chain)} {} + BaseIndex::~BaseIndex() { Interrupt(); @@ -65,21 +81,21 @@ bool BaseIndex::Init() LOCK(cs_main); CChain& active_chain = m_chainstate->m_chain; if (locator.IsNull()) { - m_best_block_index = nullptr; + SetBestBlockIndex(nullptr); } else { - m_best_block_index = m_chainstate->FindForkInGlobalIndex(locator); + 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; if (!m_best_block_index) { // index is not built yet // make sure we have all block data back to the genesis - const CBlockIndex* block = active_chain.Tip(); - while (block->pprev && (block->pprev->nStatus & BLOCK_HAVE_DATA)) { - block = block->pprev; - } - prune_violation = block != active_chain.Genesis(); + prune_violation = m_chainstate->m_blockman.GetFirstStoredBlock(*active_chain.Tip()) != active_chain.Genesis(); } // in case the index has a best block set and is not fully synced // check if we have the required blocks to continue building the index @@ -134,11 +150,11 @@ void BaseIndex::ThreadSync() if (!m_synced) { auto& consensus_params = Params().GetConsensus(); - int64_t last_log_time = 0; - int64_t last_locator_write_time = 0; + std::chrono::steady_clock::time_point last_log_time{0s}; + std::chrono::steady_clock::time_point last_locator_write_time{0s}; while (true) { if (m_interrupt) { - m_best_block_index = pindex; + SetBestBlockIndex(pindex); // No need to handle errors in Commit. If it fails, the error will be already be // logged. The best way to recover is to continue, as index cannot be corrupted by // a missed commit to disk for an advanced index state. @@ -150,7 +166,7 @@ void BaseIndex::ThreadSync() LOCK(cs_main); const CBlockIndex* pindex_next = NextSyncBlock(pindex, m_chainstate->m_chain); if (!pindex_next) { - m_best_block_index = pindex; + SetBestBlockIndex(pindex); m_synced = true; // No need to handle errors in Commit. See rationale above. Commit(); @@ -164,7 +180,7 @@ void BaseIndex::ThreadSync() pindex = pindex_next; } - int64_t current_time = GetTime(); + auto current_time{std::chrono::steady_clock::now()}; if (last_log_time + SYNC_LOG_INTERVAL < current_time) { LogPrintf("Syncing %s with block chain from height %d\n", GetName(), pindex->nHeight); @@ -172,19 +188,22 @@ void BaseIndex::ThreadSync() } if (last_locator_write_time + SYNC_LOCATOR_WRITE_INTERVAL < current_time) { - m_best_block_index = pindex; + SetBestBlockIndex(pindex->pprev); last_locator_write_time = current_time; // No need to handle errors in Commit. See rationale above. Commit(); } 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; @@ -201,22 +220,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; } @@ -225,15 +242,19 @@ 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. // In case we reorg beyond the pruned depth, ReadBlockFromDisk would // throw and lead to a graceful shutdown - m_best_block_index = new_tip; + SetBestBlockIndex(new_tip); if (!Commit()) { // If commit fails, revert the best block index to avoid corruption. - m_best_block_index = current_tip; + SetBestBlockIndex(current_tip); return false; } @@ -272,9 +293,9 @@ void BaseIndex::BlockConnected(const std::shared_ptr<const CBlock>& block, const return; } } - - if (WriteBlock(*block, pindex)) { - m_best_block_index = pindex; + interfaces::BlockInfo block_info = kernel::MakeBlockInfo(pindex, block.get()); + if (CustomAppend(block_info)) { + SetBestBlockIndex(pindex); } else { FatalError("%s: Failed to write block %s to index", __func__, pindex->GetBlockHash().ToString()); @@ -350,13 +371,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,3 +407,14 @@ IndexSummary BaseIndex::GetSummary() const summary.best_block_height = m_best_block_index ? m_best_block_index.load()->nHeight : 0; return summary; } + +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)); + } +} diff --git a/src/index/base.h b/src/index/base.h index c4a8215bc4..5a484377e7 100644 --- a/src/index/base.h +++ b/src/index/base.h @@ -6,12 +6,16 @@ #define BITCOIN_INDEX_BASE_H #include <dbwrapper.h> +#include <interfaces/chain.h> #include <threadinterrupt.h> #include <validationinterface.h> class CBlock; class CBlockIndex; class CChainState; +namespace interfaces { +class Chain; +} // namespace interfaces struct IndexSummary { std::string name; @@ -51,6 +55,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 +67,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 @@ -75,35 +86,44 @@ private: /// to a chain reorganization), the index must halt until Commit succeeds or else it could end up /// 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: + std::unique_ptr<interfaces::Chain> m_chain; CChainState* m_chainstate{nullptr}; 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; + /// 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); /// Destructor interrupts sync thread if running and blocks until it exits. virtual ~BaseIndex(); @@ -118,7 +138,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 4f99eddfd7..f4837f3456 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,14 +95,14 @@ 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)), m_filter_type(filter_type) { const std::string& filter_name = BlockFilterTypeName(filter_type); if (filter_name.empty()) throw std::invalid_argument("unknown filter_type"); - fs::path path = gArgs.GetDataDirNet() / "indexes" / "blockfilter" / filter_name; + fs::path path = gArgs.GetDataDirNet() / "indexes" / "blockfilter" / fs::u8path(filter_name); fs::create_directories(path); m_name = filter_name + " block filter index"; @@ -108,7 +110,7 @@ BlockFilterIndex::BlockFilterIndex(BlockFilterType filter_type, 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 a049019c02..968eccb6b3 100644 --- a/src/index/blockfilterindex.h +++ b/src/index/blockfilterindex.h @@ -31,21 +31,23 @@ private: 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; /** cache of block hash to filter header, to avoid disk access when responding to getcfcheckpt. */ std::unordered_map<uint256, uint256, FilterHeaderHasher> m_headers_cache GUARDED_BY(m_cs_headers_cache); + 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; } @@ -53,7 +55,7 @@ protected: 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; } @@ -62,7 +64,7 @@ public: bool LookupFilter(const CBlockIndex* block_index, BlockFilter& filter_out) const; /** Get a single filter header by block. */ - bool LookupFilterHeader(const CBlockIndex* block_index, uint256& header_out); + bool LookupFilterHeader(const CBlockIndex* block_index, uint256& header_out) EXCLUSIVE_LOCKS_REQUIRED(!m_cs_headers_cache); /** Get a range of filters between two heights on a chain. */ bool LookupFilterRange(int start_height, const CBlockIndex* stop_index, @@ -86,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 a1c8a5937c..b9029e946a 100644 --- a/src/index/coinstatsindex.cpp +++ b/src/index/coinstatsindex.cpp @@ -10,12 +10,14 @@ #include <serialize.h> #include <txdb.h> #include <undo.h> +#include <util/system.h> #include <validation.h> -using node::CCoinsStats; -using node::GetBogoSize; +using kernel::CCoinsStats; +using kernel::GetBogoSize; +using kernel::TxOutSer; + using node::ReadBlockFromDisk; -using node::TxOutSer; using node::UndoReadFromDisk; static constexpr uint8_t DB_BLOCK_HASH{'s'}; @@ -101,7 +103,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)) { fs::path path{gArgs.GetDataDirNet() / "indexes" / "coinstats"}; fs::create_directories(path); @@ -109,24 +112,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()); @@ -138,12 +144,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()) { @@ -154,7 +161,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 @@ -210,7 +217,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; @@ -228,10 +235,9 @@ bool CoinStatsIndex::WriteBlock(const CBlock& block, const CBlockIndex* pindex) m_muhash.Finalize(out); value.second.muhash = out; - CDBBatch batch(*m_db); - batch.Write(DBHeightKey(pindex->nHeight), value); - batch.Write(DB_MUHASH, m_muhash); - return m_db->WriteBatch(batch); + // 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(block.height), value); } static bool CopyHeightIndexToHashIndex(CDBIterator& db_it, CDBBatch& batch, @@ -260,17 +266,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 +282,8 @@ bool CoinStatsIndex::Rewind(const CBlockIndex* current_tip, const CBlockIndex* n { LOCK(cs_main); - 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,56 +297,59 @@ 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); } -bool CoinStatsIndex::LookUpStats(const CBlockIndex* block_index, CCoinsStats& coins_stats) const +std::optional<CCoinsStats> CoinStatsIndex::LookUpStats(const CBlockIndex* block_index) const { + CCoinsStats stats{Assert(block_index)->nHeight, block_index->GetBlockHash()}; + stats.index_used = true; + DBVal entry; - if (!LookUpOne(*m_db, block_index, entry)) { - return false; + if (!LookUpOne(*m_db, {block_index->GetBlockHash(), block_index->nHeight}, entry)) { + return std::nullopt; } - coins_stats.hashSerialized = entry.muhash; - coins_stats.nTransactionOutputs = entry.transaction_output_count; - coins_stats.nBogoSize = entry.bogo_size; - coins_stats.total_amount = entry.total_amount; - coins_stats.total_subsidy = entry.total_subsidy; - coins_stats.total_unspendable_amount = entry.total_unspendable_amount; - coins_stats.total_prevout_spent_amount = entry.total_prevout_spent_amount; - coins_stats.total_new_outputs_ex_coinbase_amount = entry.total_new_outputs_ex_coinbase_amount; - coins_stats.total_coinbase_amount = entry.total_coinbase_amount; - coins_stats.total_unspendables_genesis_block = entry.total_unspendables_genesis_block; - coins_stats.total_unspendables_bip30 = entry.total_unspendables_bip30; - coins_stats.total_unspendables_scripts = entry.total_unspendables_scripts; - coins_stats.total_unspendables_unclaimed_rewards = entry.total_unspendables_unclaimed_rewards; - - return true; + stats.hashSerialized = entry.muhash; + stats.nTransactionOutputs = entry.transaction_output_count; + stats.nBogoSize = entry.bogo_size; + stats.total_amount = entry.total_amount; + stats.total_subsidy = entry.total_subsidy; + stats.total_unspendable_amount = entry.total_unspendable_amount; + stats.total_prevout_spent_amount = entry.total_prevout_spent_amount; + stats.total_new_outputs_ex_coinbase_amount = entry.total_new_outputs_ex_coinbase_amount; + stats.total_coinbase_amount = entry.total_coinbase_amount; + stats.total_unspendables_genesis_block = entry.total_unspendables_genesis_block; + stats.total_unspendables_bip30 = entry.total_unspendables_bip30; + stats.total_unspendables_scripts = entry.total_unspendables_scripts; + stats.total_unspendables_unclaimed_rewards = entry.total_unspendables_unclaimed_rewards; + + 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 @@ -353,13 +361,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()); } @@ -388,6 +392,14 @@ bool CoinStatsIndex::Init() return true; } +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 true; +} + // Reverse a single block as part of a reorg bool CoinStatsIndex::ReverseBlock(const CBlock& block, const CBlockIndex* pindex) { @@ -489,5 +501,5 @@ bool CoinStatsIndex::ReverseBlock(const CBlock& block, const CBlockIndex* pindex Assert(m_total_unspendables_scripts == read_out.second.total_unspendables_scripts); Assert(m_total_unspendables_unclaimed_rewards == read_out.second.total_unspendables_unclaimed_rewards); - return m_db->Write(DB_MUHASH, m_muhash); + return true; } diff --git a/src/index/coinstatsindex.h b/src/index/coinstatsindex.h index d2a6c9c964..c4af223388 100644 --- a/src/index/coinstatsindex.h +++ b/src/index/coinstatsindex.h @@ -9,7 +9,7 @@ #include <crypto/muhash.h> #include <flatfile.h> #include <index/base.h> -#include <node/coinstats.h> +#include <kernel/coinstats.h> /** * CoinStatsIndex maintains statistics on the UTXO set. @@ -36,12 +36,16 @@ private: bool ReverseBlock(const CBlock& block, const CBlockIndex* pindex); + bool AllowPrune() const override { return true; } + protected: - bool Init() override; + bool CustomInit(const std::optional<interfaces::BlockKey>& block) 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; } @@ -49,10 +53,10 @@ protected: 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 - bool LookUpStats(const CBlockIndex* block_index, node::CCoinsStats& coins_stats) 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 e1d807f39a..b719aface8 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)), m_db(std::make_unique<TxIndex::DB>(n_cache_size, f_memory, f_wipe)) {} -TxIndex::~TxIndex() {} +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 2bbc602631..be240c4582 100644 --- a/src/index/txindex.h +++ b/src/index/txindex.h @@ -20,8 +20,10 @@ protected: private: const std::unique_ptr<DB> m_db; + 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; @@ -29,7 +31,7 @@ protected: 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 a3d53c3fae..480b8c7705 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -9,12 +9,15 @@ #include <init.h> +#include <kernel/checks.h> +#include <kernel/mempool_persist.h> +#include <kernel/validation_cache_sizes.h> + #include <addrman.h> #include <banman.h> #include <blockfilter.h> #include <chain.h> #include <chainparams.h> -#include <compat/sanity.h> #include <consensus/amount.h> #include <deploymentstatus.h> #include <fs.h> @@ -33,14 +36,19 @@ #include <net_permissions.h> #include <net_processing.h> #include <netbase.h> +#include <netgroup.h> #include <node/blockstorage.h> #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> @@ -64,6 +72,7 @@ #include <util/strencodings.h> #include <util/string.h> #include <util/syscall_sandbox.h> +#include <util/syserror.h> #include <util/system.h> #include <util/thread.h> #include <util/threadnames.h> @@ -72,6 +81,7 @@ #include <validationinterface.h> #include <walletinitinterface.h> +#include <algorithm> #include <condition_variable> #include <cstdint> #include <cstdio> @@ -83,13 +93,11 @@ #include <vector> #ifndef WIN32 -#include <attributes.h> #include <cerrno> #include <signal.h> #include <sys/stat.h> #endif -#include <boost/algorithm/string/replace.hpp> #include <boost/signals2/signal.hpp> #if ENABLE_ZMQ @@ -98,18 +106,21 @@ #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; -using node::fHavePruned; using node::fPruneMode; using node::fReindex; using node::nPruneTarget; @@ -135,7 +146,7 @@ static const char* BITCOIN_PID_FILENAME = "bitcoind.pid"; static fs::path GetPidFile(const ArgsManager& args) { - return AbsPathForConfigVal(fs::PathFromString(args.GetArg("-pid", BITCOIN_PID_FILENAME))); + return AbsPathForConfigVal(args.GetPathArg("-pid", BITCOIN_PID_FILENAME)); } [[nodiscard]] static bool CreatePidFile(const ArgsManager& args) @@ -149,7 +160,7 @@ static fs::path GetPidFile(const ArgsManager& args) #endif return true; } else { - return InitError(strprintf(_("Unable to create the PID file '%s': %s"), fs::PathToString(GetPidFile(args)), std::strerror(errno))); + return InitError(strprintf(_("Unable to create the PID file '%s': %s"), fs::PathToString(GetPidFile(args)), SysErrorString(errno))); } } @@ -240,9 +251,10 @@ void Shutdown(NodeContext& node) node.connman.reset(); node.banman.reset(); 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. @@ -304,7 +316,7 @@ void Shutdown(NodeContext& node) node.chain_clients.clear(); UnregisterAllValidationInterfaces(); GetMainSignals().UnregisterBackgroundSignalScheduler(); - init::UnsetGlobals(); + node.kernel.reset(); node.mempool.reset(); node.fee_estimator.reset(); node.chainman.reset(); @@ -318,7 +330,6 @@ void Shutdown(NodeContext& node) LogPrintf("%s: Unable to remove PID file: %s\n", __func__, fsbridge::get_filesystem_error_message(e)); } - node.args = nullptr; LogPrintf("%s: done\n", __func__); } @@ -406,25 +417,25 @@ void SetupServerArgs(ArgsManager& argsman) argsman.AddArg("-blockreconstructionextratxn=<n>", strprintf("Extra transactions to keep in memory for compact block reconstructions (default: %u)", DEFAULT_BLOCK_RECONSTRUCTION_EXTRA_TXN), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); argsman.AddArg("-blocksonly", strprintf("Whether to reject transactions from network peers. Automatic broadcast and rebroadcast of any transactions from inbound peers is disabled, unless the peer has the 'forcerelay' permission. RPC transactions are not affected. (default: %u)", DEFAULT_BLOCKSONLY), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); argsman.AddArg("-coinstatsindex", strprintf("Maintain coinstats index used by the gettxoutsetinfo RPC (default: %u)", DEFAULT_COINSTATSINDEX), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); - argsman.AddArg("-conf=<file>", strprintf("Specify path to read-only configuration file. Relative paths will be prefixed by datadir location. (default: %s)", BITCOIN_CONF_FILENAME), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); + argsman.AddArg("-conf=<file>", strprintf("Specify path to read-only configuration file. Relative paths will be prefixed by datadir location (only useable from command line, not configuration file) (default: %s)", BITCOIN_CONF_FILENAME), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); argsman.AddArg("-datadir=<dir>", "Specify data directory", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); argsman.AddArg("-dbbatchsize", strprintf("Maximum database write batch size in bytes (default: %u)", nDefaultDbBatchSize), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::OPTIONS); 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); argsman.AddArg("-persistmempool", strprintf("Whether to save the mempool on shutdown and load on restart (default: %u)", DEFAULT_PERSIST_MEMPOOL), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); argsman.AddArg("-pid=<file>", strprintf("Specify pid file. Relative paths will be prefixed by a net-specific datadir location. (default: %s)", BITCOIN_PID_FILENAME), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); - argsman.AddArg("-prune=<n>", strprintf("Reduce storage requirements by enabling pruning (deleting) of old blocks. This allows the pruneblockchain RPC to be called to delete specific blocks, and enables automatic pruning of old blocks if a target size in MiB is provided. This mode is incompatible with -txindex and -coinstatsindex. " + argsman.AddArg("-prune=<n>", strprintf("Reduce storage requirements by enabling pruning (deleting) of old blocks. This allows the pruneblockchain RPC to be called to delete specific blocks and enables automatic pruning of old blocks if a target size in MiB is provided. This mode is incompatible with -txindex. " "Warning: Reverting this setting requires re-downloading the entire blockchain. " "(default: 0 = disable pruning blocks, 1 = allow manual pruning via RPC, >=%u = automatically prune block files to stay under the specified target size in MiB)", MIN_DISK_SPACE_FOR_BLOCK_FILES / 1024 / 1024), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); - argsman.AddArg("-reindex", "Rebuild chain state and block index from the blk*.dat files on disk", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); - argsman.AddArg("-reindex-chainstate", "Rebuild chain state from the currently indexed blocks. When in pruning mode or if blocks on disk might be corrupted, use full -reindex instead.", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); + argsman.AddArg("-reindex", "Rebuild chain state and block index from the blk*.dat files on disk. This will also rebuild active optional indexes.", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); + argsman.AddArg("-reindex-chainstate", "Rebuild chain state from the currently indexed blocks. When in pruning mode or if blocks on disk might be corrupted, use full -reindex instead. Deactivate all optional indexes before running this.", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); argsman.AddArg("-settings=<file>", strprintf("Specify path to dynamic settings data file. Can be disabled with -nosettings. File is written at runtime and not meant to be edited by users (use %s instead for custom settings). Relative paths will be prefixed by datadir location. (default: %s)", BITCOIN_CONF_FILENAME, BITCOIN_SETTINGS_FILENAME), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); #if HAVE_SYSTEM argsman.AddArg("-startupnotify=<cmd>", "Execute command on startup.", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); @@ -444,7 +455,7 @@ void SetupServerArgs(ArgsManager& argsman) argsman.AddArg("-asmap=<file>", strprintf("Specify asn mapping used for bucketing of the peers (default: %s). Relative paths will be prefixed by the net-specific datadir location.", DEFAULT_ASMAP_FILENAME), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); argsman.AddArg("-bantime=<n>", strprintf("Default duration (in seconds) of manually configured bans (default: %u)", DEFAULT_MISBEHAVING_BANTIME), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); argsman.AddArg("-bind=<addr>[:<port>][=onion]", strprintf("Bind to given address and always listen on it (default: 0.0.0.0). Use [host]:port notation for IPv6. Append =onion to tag any incoming connections to that address and port as incoming Tor connections (default: 127.0.0.1:%u=onion, testnet: 127.0.0.1:%u=onion, signet: 127.0.0.1:%u=onion, regtest: 127.0.0.1:%u=onion)", defaultBaseParams->OnionServiceTargetPort(), testnetBaseParams->OnionServiceTargetPort(), signetBaseParams->OnionServiceTargetPort(), regtestBaseParams->OnionServiceTargetPort()), ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::CONNECTION); - argsman.AddArg("-cjdnsreachable", "If set then this host is configured for CJDNS (connecting to fc00::/8 addresses would lead us to the CJDNS network) (default: 0)", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); + argsman.AddArg("-cjdnsreachable", "If set, then this host is configured for CJDNS (connecting to fc00::/8 addresses would lead us to the CJDNS network, see doc/cjdns.md) (default: 0)", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); argsman.AddArg("-connect=<ip>", "Connect only to the specified node; -noconnect disables automatic connections (the rules for this peer are the same as for -addnode). This option can be specified multiple times to connect to multiple nodes.", ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::CONNECTION); argsman.AddArg("-discover", "Discover own IP addresses (default: 1 when listening and no -externalip or -proxy)", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); argsman.AddArg("-dns", strprintf("Allow DNS lookups for -addnode, -seednode and -connect (default: %u)", DEFAULT_NAME_LOOKUP), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); @@ -457,19 +468,19 @@ void SetupServerArgs(ArgsManager& argsman) argsman.AddArg("-maxconnections=<n>", strprintf("Maintain at most <n> connections to peers (default: %u). This limit does not apply to connections manually added via -addnode or the addnode RPC, which have a separate limit of %u.", DEFAULT_MAX_PEER_CONNECTIONS, MAX_ADDNODE_CONNECTIONS), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); argsman.AddArg("-maxreceivebuffer=<n>", strprintf("Maximum per-connection receive buffer, <n>*1000 bytes (default: %u)", DEFAULT_MAXRECEIVEBUFFER), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); argsman.AddArg("-maxsendbuffer=<n>", strprintf("Maximum per-connection send buffer, <n>*1000 bytes (default: %u)", DEFAULT_MAXSENDBUFFER), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); - argsman.AddArg("-maxtimeadjustment", strprintf("Maximum allowed median peer time offset adjustment. Local perspective of time may be influenced by peers forward or backward by this amount. (default: %u seconds)", DEFAULT_MAX_TIME_ADJUSTMENT), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); + argsman.AddArg("-maxtimeadjustment", strprintf("Maximum allowed median peer time offset adjustment. Local perspective of time may be influenced by outbound peers forward or backward by this amount (default: %u seconds).", DEFAULT_MAX_TIME_ADJUSTMENT), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); argsman.AddArg("-maxuploadtarget=<n>", strprintf("Tries to keep outbound traffic under the given target per 24h. Limit does not apply to peers with 'download' permission or blocks created within past week. 0 = no limit (default: %s). Optional suffix units [k|K|m|M|g|G|t|T] (default: M). Lowercase is 1000 base while uppercase is 1024 base", DEFAULT_MAX_UPLOAD_TARGET), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); argsman.AddArg("-onion=<ip:port>", "Use separate SOCKS5 proxy to reach peers via Tor onion services, set -noonion to disable (default: -proxy)", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); argsman.AddArg("-i2psam=<ip:port>", "I2P SAM proxy to reach I2P peers and accept I2P connections (default: none)", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); argsman.AddArg("-i2pacceptincoming", "If set and -i2psam is also set then incoming I2P connections are accepted via the SAM proxy. If this is not set but -i2psam is set then only outgoing connections will be made to the I2P network. Ignored if -i2psam is not set. Listening for incoming I2P connections is done through the SAM proxy, not by binding to a local address and port (default: 1)", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); - argsman.AddArg("-onlynet=<net>", "Make automatic outgoing connections only through network <net> (" + Join(GetNetworkNames(), ", ") + "). Incoming connections are not affected by this option. This option can be specified multiple times to allow multiple networks.", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); + 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); - argsman.AddArg("-proxy=<ip:port>", "Connect through SOCKS5 proxy, set -noproxy to disable (default: disabled)", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); + argsman.AddArg("-proxy=<ip:port>", "Connect through SOCKS5 proxy, set -noproxy to disable (default: disabled)", ArgsManager::ALLOW_ANY | ArgsManager::DISALLOW_ELISION, OptionsCategory::CONNECTION); argsman.AddArg("-proxyrandomize", strprintf("Randomize credentials for every proxy connection. This enables Tor stream isolation (default: %u)", DEFAULT_PROXYRANDOMIZE), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); argsman.AddArg("-seednode=<ip>", "Connect to a node to retrieve peer addresses, and disconnect. This option can be specified multiple times to connect to multiple nodes.", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); argsman.AddArg("-networkactive", "Enable all P2P network activity (default: 1). Can be changed by the setnetworkactive RPC command", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); @@ -535,13 +546,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); @@ -554,6 +565,7 @@ void SetupServerArgs(ArgsManager& argsman) 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("-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); @@ -568,6 +580,7 @@ void SetupServerArgs(ArgsManager& argsman) argsman.AddArg("-rpcallowip=<ip>", "Allow JSON-RPC connections from specified source. Valid for <ip> are a single IP (e.g. 1.2.3.4), a network/netmask (e.g. 1.2.3.4/255.255.255.0) or a network/CIDR (e.g. 1.2.3.4/24). This option can be specified multiple times", ArgsManager::ALLOW_ANY, OptionsCategory::RPC); argsman.AddArg("-rpcauth=<userpw>", "Username and HMAC-SHA-256 hashed password for JSON-RPC connections. The field <userpw> comes in the format: <USERNAME>:<SALT>$<HASH>. A canonical python script is included in share/rpcauth. The client then connects normally using the rpcuser=<USERNAME>/rpcpassword=<PASSWORD> pair of arguments. This option can be specified multiple times", ArgsManager::ALLOW_ANY | ArgsManager::SENSITIVE, OptionsCategory::RPC); argsman.AddArg("-rpcbind=<addr>[:port]", "Bind to given address to listen for JSON-RPC connections. Do not expose the RPC server to untrusted networks such as the public internet! This option is ignored unless -rpcallowip is also passed. Port is optional and overrides -rpcport. Use [host]:port notation for IPv6. This option can be specified multiple times (default: 127.0.0.1 and ::1 i.e., localhost)", ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY | ArgsManager::SENSITIVE, OptionsCategory::RPC); + argsman.AddArg("-rpcdoccheck", strprintf("Throw a non-fatal error at runtime if the documentation for an RPC is incorrect (default: %u)", DEFAULT_RPC_DOC_CHECK), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::RPC); argsman.AddArg("-rpccookiefile=<loc>", "Location of the auth cookie. Relative paths will be prefixed by a net-specific datadir location. (default: data dir)", ArgsManager::ALLOW_ANY, OptionsCategory::RPC); argsman.AddArg("-rpcpassword=<pw>", "Password for JSON-RPC connections", ArgsManager::ALLOW_ANY | ArgsManager::SENSITIVE, OptionsCategory::RPC); argsman.AddArg("-rpcport=<port>", strprintf("Listen for JSON-RPC connections on <port> (default: %u, testnet: %u, signet: %u, regtest: %u)", defaultBaseParams->RPCPort(), testnetBaseParams->RPCPort(), signetBaseParams->RPCPort(), regtestBaseParams->RPCPort()), ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::RPC); @@ -597,7 +610,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) @@ -660,7 +673,8 @@ void InitParameterInteraction(ArgsManager& args) LogPrintf("%s: parameter interaction: -connect set -> setting -listen=0\n", __func__); } - if (args.IsArgSet("-proxy")) { + std::string proxy_arg = args.GetArg("-proxy", ""); + if (proxy_arg != "" && proxy_arg != "0") { // to protect privacy, do not listen by default if a default proxy server is specified if (args.SoftSetBoolArg("-listen", false)) LogPrintf("%s: parameter interaction: -proxy set -> setting -listen=0\n", __func__); @@ -792,7 +806,7 @@ bool AppInitBasicSetup(const ArgsManager& args) return true; } -bool AppInitParameterInteraction(const ArgsManager& args) +bool AppInitParameterInteraction(const ArgsManager& args, bool use_syscall_sandbox) { const CChainParams& chainparams = Params(); // ********************************************************* Step 2: parameter interactions @@ -853,12 +867,12 @@ bool AppInitParameterInteraction(const ArgsManager& args) nLocalServices = ServiceFlags(nLocalServices | NODE_COMPACT_FILTERS); } - // if using block pruning, then disallow txindex and coinstatsindex if (args.GetIntArg("-prune", 0)) { if (args.GetBoolArg("-txindex", DEFAULT_TXINDEX)) return InitError(_("Prune mode is incompatible with -txindex.")); - if (args.GetBoolArg("-coinstatsindex", DEFAULT_COINSTATSINDEX)) - return InitError(_("Prune mode is incompatible with -coinstatsindex.")); + if (args.GetBoolArg("-reindex-chainstate", false)) { + return InitError(_("Prune mode is incompatible with -reindex-chainstate. Use full -reindex instead.")); + } } // If -forcednsseed is set to true, ensure -dnsseed has not been set to false @@ -925,21 +939,6 @@ bool AppInitParameterInteraction(const ArgsManager& args) 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); if (nPruneArg < 0) { @@ -965,20 +964,7 @@ bool AppInitParameterInteraction(const ArgsManager& args) 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 @@ -989,28 +975,10 @@ bool AppInitParameterInteraction(const ArgsManager& args) } } - // 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 @@ -1025,8 +993,17 @@ bool AppInitParameterInteraction(const ArgsManager& args) nMaxTipAge = args.GetIntArg("-maxtipage", DEFAULT_MAX_TIP_AGE); - if (args.IsArgSet("-proxy") && args.GetArg("-proxy", "").empty()) { - return InitError(_("No proxy server specified. Use -proxy=<ip> or -proxy=<ip:port>.")); + if (args.GetBoolArg("-reindex-chainstate", false)) { + // indexes that must be deactivated to prevent index corruption, see #24630 + if (args.GetBoolArg("-coinstatsindex", DEFAULT_COINSTATSINDEX)) { + return InitError(_("-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.")); + } + if (g_enabled_filter_types.count(BlockFilterType::BASIC)) { + return InitError(_("-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.")); + } + if (args.GetBoolArg("-txindex", DEFAULT_TXINDEX)) { + return InitError(_("-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.")); + } } #if defined(USE_SYSCALL_SANDBOX) @@ -1056,6 +1033,9 @@ bool AppInitParameterInteraction(const ArgsManager& args) if (!SetupSyscallSandbox(log_syscall_violation_before_terminating)) { return InitError(Untranslated("Installation of the syscall sandbox failed.")); } + if (use_syscall_sandbox) { + SetSyscallSandboxPolicy(SyscallSandboxPolicy::INITIALIZATION); + } LogPrintf("Experimental syscall sandbox enabled (-sandbox=%s): bitcoind will terminate if an unexpected (not allowlisted) syscall is invoked.\n", sandbox_arg); } #endif // USE_SYSCALL_SANDBOX @@ -1066,7 +1046,7 @@ bool AppInitParameterInteraction(const ArgsManager& args) 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))); } @@ -1076,13 +1056,11 @@ static bool LockDataDirectory(bool probeOnly) return true; } -bool AppInitSanityChecks() +bool AppInitSanityChecks(const kernel::Context& kernel) { // ********************************************************* Step 4: sanity checks - - init::SetGlobals(); - - if (!init::SanityChecks()) { + if (auto error = kernel::SanityChecks(kernel)) { + InitError(*error); return InitError(strprintf(_("Initialization sanity check failed. %s is shutting down."), PACKAGE_NAME)); } @@ -1141,8 +1119,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) { @@ -1223,16 +1206,11 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) const bool ignores_incoming_txs{args.GetBoolArg("-blocksonly", DEFAULT_BLOCKSONLY)}; { - // Initialize addrman - assert(!node.addrman); // Read asmap file if configured std::vector<bool> asmap; if (args.IsArgSet("-asmap")) { - fs::path asmap_path = fs::PathFromString(args.GetArg("-asmap", "")); - if (asmap_path.empty()) { - asmap_path = fs::PathFromString(DEFAULT_ASMAP_FILENAME); - } + fs::path asmap_path = args.GetPathArg("-asmap", DEFAULT_ASMAP_FILENAME); if (!asmap_path.is_absolute()) { asmap_path = gArgs.GetDataDirNet() / asmap_path; } @@ -1251,8 +1229,14 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) LogPrintf("Using /16 prefix for IP bucketing\n"); } + // Initialize netgroup manager + assert(!node.netgroupman); + node.netgroupman = std::make_unique<NetGroupManager>(std::move(asmap)); + + // Initialize addrman + assert(!node.addrman); uiInterface.InitMessage(_("Loading P2P addresses…").translated); - if (const auto error{LoadAddrman(asmap, args, node.addrman)}) { + if (const auto error{LoadAddrman(*node.netgroupman, args, node.addrman)}) { return InitError(*error); } } @@ -1260,25 +1244,14 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) assert(!node.banman); node.banman = std::make_unique<BanMan>(gArgs.GetDataDirNet() / "banlist", &uiInterface, args.GetIntArg("-bantime", DEFAULT_MISBEHAVING_BANTIME)); assert(!node.connman); - node.connman = std::make_unique<CConnman>(GetRand(std::numeric_limits<uint64_t>::max()), GetRand(std::numeric_limits<uint64_t>::max()), *node.addrman, args.GetBoolArg("-networkactive", true)); + node.connman = std::make_unique<CConnman>(GetRand<uint64_t>(), + GetRand<uint64_t>(), + *node.addrman, *node.netgroupman, args.GetBoolArg("-networkactive", true)); 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>(); - - assert(!node.mempool); - int check_ratio = std::min<int>(std::max<int>(args.GetIntArg("-checkmempool", chainparams.DefaultConsistencyChecks() ? 1 : 0), 0), 1000000); - node.mempool = std::make_unique<CTxMemPool>(node.fee_estimator.get(), check_ratio); - - assert(!node.chainman); - node.chainman = std::make_unique<ChainstateManager>(); - ChainstateManager& chainman = *node.chainman; - - assert(!node.peerman); - node.peerman = PeerManager::make(chainparams, *node.connman, *node.addrman, node.banman.get(), - chainman, *node.mempool, ignores_incoming_txs); - RegisterValidationInterface(node.peerman.get()); + 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; @@ -1303,6 +1276,7 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) } for (int n = 0; n < NET_MAX; n++) { enum Network net = (enum Network)n; + assert(IsReachable(net)); if (!nets.count(net)) SetReachable(net, false); } @@ -1394,7 +1368,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)) { @@ -1405,118 +1378,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)); - bool fLoaded = false; - while (!fLoaded && !ShutdownRequested()) { - const bool fReset = fReindex; - bilingual_str strLoadError; + assert(!node.mempool); + assert(!node.chainman); + + 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>(mempool_opts); + + const ChainstateManager::Options chainman_opts{ + .chainparams = chainparams, + .adjusted_time_callback = GetAdjustedTime, + }; + node.chainman = std::make_unique<ChainstateManager>(chainman_opts); + ChainstateManager& chainman = *node.chainman; + + 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, - chainparams.GetConsensus(), - 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: - strLoadError = _("Error upgrading chainstate database"); - break; - 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."), - chainparams.GetConsensus().SegwitHeight); - break; - case ChainstateLoadingError::SHUTDOWN_PROBED: - break; - } - } else { - std::optional<ChainstateLoadVerifyError> maybe_verify_error; + auto catch_exceptions = [](auto&& f) { try { - uiInterface.InitMessage(_("Verifying blocks…").translated); - auto check_blocks = args.GetIntArg("-checkblocks", DEFAULT_CHECKBLOCKS); - if (fHavePruned && 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, - chainparams.GetConsensus(), - check_blocks, - args.GetIntArg("-checklevel", DEFAULT_CHECKLEVEL), - /*get_unix_time_seconds=*/static_cast<int64_t(*)()>(GetTime)); + 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); } } + 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; @@ -1526,7 +1464,7 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) return false; } } else { - return InitError(strLoadError); + return InitError(error); } } } @@ -1539,28 +1477,35 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) return false; } + ChainstateManager& chainman = *Assert(node.chainman); + + assert(!node.peerman); + node.peerman = PeerManager::make(*node.connman, *node.addrman, node.banman.get(), + chainman, *node.mempool, ignores_incoming_txs); + RegisterValidationInterface(node.peerman.get()); + // ********************************************************* Step 8: start indexers if (args.GetBoolArg("-txindex", DEFAULT_TXINDEX)) { - if (const auto error{CheckLegacyTxindex(*Assert(chainman.m_blockman.m_block_tree_db))}) { + if (const auto error{WITH_LOCK(cs_main, return CheckLegacyTxindex(*Assert(chainman.m_blockman.m_block_tree_db)))}) { 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; } } @@ -1602,7 +1547,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; @@ -1614,7 +1559,7 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) uiInterface.NotifyBlockTip_connect([block_notify](SynchronizationState sync_state, const CBlockIndex* pBlockIndex) { if (sync_state != SynchronizationState::POST_INIT || !pBlockIndex) return; std::string command = block_notify; - boost::replace_all(command, "%s", pBlockIndex->GetBlockHash().GetHex()); + ReplaceAll(command, "%s", pBlockIndex->GetBlockHash().GetHex()); std::thread t(runCommand, command); t.detach(); // thread runs free }); @@ -1627,7 +1572,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 @@ -1657,12 +1602,12 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) chain_active_height = chainman.ActiveChain().Height(); if (tip_info) { tip_info->block_height = chain_active_height; - tip_info->block_time = chainman.ActiveChain().Tip() ? chainman.ActiveChain().Tip()->GetBlockTime() : Params().GenesisBlock().GetBlockTime(); - tip_info->verification_progress = GuessVerificationProgress(Params().TxData(), chainman.ActiveChain().Tip()); + tip_info->block_time = chainman.ActiveChain().Tip() ? chainman.ActiveChain().Tip()->GetBlockTime() : chainman.GetParams().GenesisBlock().GetBlockTime(); + tip_info->verification_progress = GuessVerificationProgress(chainman.GetParams().TxData(), chainman.ActiveChain().Tip()); } - if (tip_info && ::pindexBestHeader) { - tip_info->header_height = ::pindexBestHeader->nHeight; - tip_info->header_time = ::pindexBestHeader->GetBlockTime(); + if (tip_info && chainman.m_best_header) { + tip_info->header_height = chainman.m_best_header->nHeight; + tip_info->header_time = chainman.m_best_header->GetBlockTime(); } } LogPrintf("nBestHeight = %d\n", chain_active_height); @@ -1806,12 +1751,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.h b/src/init.h index ddd439f619..e8e6a55eba 100644 --- a/src/init.h +++ b/src/init.h @@ -19,6 +19,9 @@ class ArgsManager; namespace interfaces { struct BlockAndHeaderTipInfo; } +namespace kernel { +struct Context; +} namespace node { struct NodeContext; } // namespace node @@ -41,13 +44,13 @@ bool AppInitBasicSetup(const ArgsManager& args); * @note This can be done before daemonization. Do not call Shutdown() if this function fails. * @pre Parameters should be parsed and config file should be read, AppInitBasicSetup should have been called. */ -bool AppInitParameterInteraction(const ArgsManager& args); +bool AppInitParameterInteraction(const ArgsManager& args, bool use_syscall_sandbox = true); /** - * Initialization sanity checks: ecc init, sanity checks, dir lock. + * Initialization sanity checks. * @note This can be done before daemonization. Do not call Shutdown() if this function fails. * @pre Parameters should be parsed and config file should be read, AppInitParameterInteraction should have been called. */ -bool AppInitSanityChecks(); +bool AppInitSanityChecks(const kernel::Context& kernel); /** * Lock bitcoin core data directory. * @note This should only be done after daemonization. Do not call Shutdown() if this function fails. diff --git a/src/init/bitcoin-gui.cpp b/src/init/bitcoin-gui.cpp index e297b48718..2fa4add4e5 100644 --- a/src/init/bitcoin-gui.cpp +++ b/src/init/bitcoin-gui.cpp @@ -9,6 +9,7 @@ #include <interfaces/node.h> #include <interfaces/wallet.h> #include <node/context.h> +#include <util/check.h> #include <util/system.h> #include <memory> diff --git a/src/init/bitcoin-node.cpp b/src/init/bitcoin-node.cpp index 511a872bc0..78bc3e5980 100644 --- a/src/init/bitcoin-node.cpp +++ b/src/init/bitcoin-node.cpp @@ -9,6 +9,7 @@ #include <interfaces/node.h> #include <interfaces/wallet.h> #include <node/context.h> +#include <util/check.h> #include <util/system.h> #include <memory> diff --git a/src/init/bitcoin-qt.cpp b/src/init/bitcoin-qt.cpp index 37d4e426c5..bb3bb945d0 100644 --- a/src/init/bitcoin-qt.cpp +++ b/src/init/bitcoin-qt.cpp @@ -8,6 +8,7 @@ #include <interfaces/node.h> #include <interfaces/wallet.h> #include <node/context.h> +#include <util/check.h> #include <util/system.h> #include <memory> diff --git a/src/init/bitcoin-wallet.cpp b/src/init/bitcoin-wallet.cpp index e9dcde72fe..c8d499da10 100644 --- a/src/init/bitcoin-wallet.cpp +++ b/src/init/bitcoin-wallet.cpp @@ -4,6 +4,8 @@ #include <interfaces/init.h> +#include <memory> + namespace interfaces { std::unique_ptr<Init> MakeWalletInit(int argc, char* argv[], int& exit_status) { diff --git a/src/init/bitcoind.cpp b/src/init/bitcoind.cpp index 2addff07c1..b473ebb805 100644 --- a/src/init/bitcoind.cpp +++ b/src/init/bitcoind.cpp @@ -8,6 +8,7 @@ #include <interfaces/node.h> #include <interfaces/wallet.h> #include <node/context.h> +#include <util/check.h> #include <util/system.h> #include <memory> diff --git a/src/init/common.cpp b/src/init/common.cpp index 38c60366e3..bdd7f16307 100644 --- a/src/init/common.cpp +++ b/src/init/common.cpp @@ -7,57 +7,19 @@ #endif #include <clientversion.h> -#include <compat/sanity.h> -#include <crypto/sha256.h> -#include <key.h> +#include <fs.h> #include <logging.h> -#include <node/ui_interface.h> -#include <pubkey.h> -#include <random.h> +#include <node/interface_ui.h> +#include <tinyformat.h> #include <util/system.h> #include <util/time.h> #include <util/translation.h> -#include <memory> - -static std::unique_ptr<ECCVerifyHandle> globalVerifyHandle; +#include <algorithm> +#include <string> +#include <vector> namespace init { -void SetGlobals() -{ - std::string sha256_algo = SHA256AutoDetect(); - LogPrintf("Using the '%s' SHA256 implementation\n", sha256_algo); - RandomInit(); - ECC_Start(); - globalVerifyHandle.reset(new ECCVerifyHandle()); -} - -void UnsetGlobals() -{ - globalVerifyHandle.reset(); - ECC_Stop(); -} - -bool SanityChecks() -{ - if (!ECC_InitSanityCheck()) { - return InitError(Untranslated("Elliptic curve cryptography sanity check failure. Aborting.")); - } - - if (!glibcxx_sanity_test()) - return false; - - if (!Random_SanityCheck()) { - return InitError(Untranslated("OS cryptographic RNG sanity check failure. Aborting.")); - } - - if (!ChronoSanityCheck()) { - return InitError(Untranslated("Clock epoch mismatch. Aborting.")); - } - - return true; -} - 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); @@ -81,7 +43,7 @@ void AddLoggingArgs(ArgsManager& argsman) void SetLoggingOptions(const ArgsManager& args) { LogInstance().m_print_to_file = !args.IsArgNegated("-debuglogfile"); - LogInstance().m_file_path = AbsPathForConfigVal(fs::PathFromString(args.GetArg("-debuglogfile", DEFAULT_DEBUGLOGFILE))); + LogInstance().m_file_path = AbsPathForConfigVal(args.GetPathArg("-debuglogfile", DEFAULT_DEBUGLOGFILE)); LogInstance().m_print_to_console = args.GetBoolArg("-printtoconsole", !args.GetBoolArg("-daemon", false)); LogInstance().m_log_timestamps = args.GetBoolArg("-logtimestamps", DEFAULT_LOGTIMESTAMPS); LogInstance().m_log_time_micros = args.GetBoolArg("-logtimemicros", DEFAULT_LOGTIMEMICROS); @@ -137,7 +99,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 fc4bc1b280..2c7f485908 100644 --- a/src/init/common.h +++ b/src/init/common.h @@ -11,13 +11,6 @@ class ArgsManager; namespace init { -void SetGlobals(); -void UnsetGlobals(); -/** - * Ensure a usable environment with all - * necessary library support. - */ -bool SanityChecks(); void AddLoggingArgs(ArgsManager& args); void SetLoggingOptions(const ArgsManager& args); void SetLoggingCategories(const ArgsManager& args); 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 c4dc303dd5..2c31e12ada 100644 --- a/src/interfaces/node.h +++ b/src/interfaces/node.h @@ -5,12 +5,13 @@ #ifndef BITCOIN_INTERFACES_NODE_H #define BITCOIN_INTERFACES_NODE_H -#include <consensus/amount.h> -#include <net.h> // For NodeId -#include <net_types.h> // For banmap_t -#include <netaddress.h> // For Network -#include <netbase.h> // For ConnectionDirection +#include <consensus/amount.h> // For CAmount +#include <net.h> // For NodeId +#include <net_types.h> // For banmap_t +#include <netaddress.h> // For Network +#include <netbase.h> // For ConnectionDirection #include <support/allocators/secure.h> // For SecureString +#include <util/settings.h> // For util::SettingsValue #include <util/translation.h> #include <functional> @@ -97,6 +98,24 @@ public: //! Return whether shutdown was requested. virtual bool shutdownRequested() = 0; + //! Return whether a particular setting in <datadir>/settings.json is or + //! would be ignored because it is also specified in the command line. + virtual bool isSettingIgnored(const std::string& name) = 0; + + //! Return setting value from <datadir>/settings.json or bitcoin.conf. + virtual util::SettingsValue getPersistentSetting(const std::string& name) = 0; + + //! Update a setting in <datadir>/settings.json. + virtual void updateRwSetting(const std::string& name, const util::SettingsValue& value) = 0; + + //! Force a setting value to be applied, overriding any other configuration + //! source, but not being persisted. + virtual void forceSetting(const std::string& name, const util::SettingsValue& value) = 0; + + //! Clear all settings in <datadir>/settings.json and store a backup of + //! previous settings in <datadir>/settings.json.bak. + virtual void resetSettings() = 0; + //! Map port. virtual void mapPort(bool use_upnp, bool use_natpmp) = 0; 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/kernel/bitcoinkernel.cpp b/src/kernel/bitcoinkernel.cpp new file mode 100644 index 0000000000..bb101ba186 --- /dev/null +++ b/src/kernel/bitcoinkernel.cpp @@ -0,0 +1,10 @@ +// 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 <functional> +#include <string> + +// Define G_TRANSLATION_FUN symbol in libbitcoinkernel library so users of the +// library aren't required to export this symbol +extern const std::function<std::string(const char*)> G_TRANSLATION_FUN = nullptr; 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 new file mode 100644 index 0000000000..510a1f9edc --- /dev/null +++ b/src/kernel/chainstatemanager_opts.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_KERNEL_CHAINSTATEMANAGER_OPTS_H +#define BITCOIN_KERNEL_CHAINSTATEMANAGER_OPTS_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 + * `ChainstateManager`. + */ +struct ChainstateManagerOpts { + const CChainParams& chainparams; + const std::function<int64_t()> adjusted_time_callback{nullptr}; +}; + +} // namespace kernel + +#endif // BITCOIN_KERNEL_CHAINSTATEMANAGER_OPTS_H diff --git a/src/kernel/checks.cpp b/src/kernel/checks.cpp new file mode 100644 index 0000000000..4c303c172c --- /dev/null +++ b/src/kernel/checks.cpp @@ -0,0 +1,33 @@ +// 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/checks.h> + +#include <key.h> +#include <random.h> +#include <util/time.h> +#include <util/translation.h> + +#include <memory> + +namespace kernel { + +std::optional<bilingual_str> SanityChecks(const Context&) +{ + if (!ECC_InitSanityCheck()) { + return Untranslated("Elliptic curve cryptography sanity check failure. Aborting."); + } + + if (!Random_SanityCheck()) { + return Untranslated("OS cryptographic RNG sanity check failure. Aborting."); + } + + if (!ChronoSanityCheck()) { + return Untranslated("Clock epoch mismatch. Aborting."); + } + + return std::nullopt; +} + +} diff --git a/src/kernel/checks.h b/src/kernel/checks.h new file mode 100644 index 0000000000..3eb14824fb --- /dev/null +++ b/src/kernel/checks.h @@ -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. + +#ifndef BITCOIN_KERNEL_CHECKS_H +#define BITCOIN_KERNEL_CHECKS_H + +#include <optional> + +struct bilingual_str; + +namespace kernel { + +struct Context; + +/** + * Ensure a usable environment with all necessary library support. + */ +std::optional<bilingual_str> SanityChecks(const Context&); + +} + +#endif // BITCOIN_KERNEL_CHECKS_H diff --git a/src/node/coinstats.cpp b/src/kernel/coinstats.cpp index eed43a1bc7..06a4b8c974 100644 --- a/src/node/coinstats.cpp +++ b/src/kernel/coinstats.cpp @@ -1,23 +1,42 @@ -// Copyright (c) 2010 Satoshi Nakamoto -// Copyright (c) 2009-2021 The Bitcoin Core developers +// 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/coinstats.h> +#include <kernel/coinstats.h> +#include <chain.h> #include <coins.h> #include <crypto/muhash.h> #include <hash.h> -#include <index/coinstatsindex.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 { + +CCoinsStats::CCoinsStats(int block_height, const uint256& block_hash) + : nHeight(block_height), + hashBlock(block_hash) {} -namespace node { // Database-independent metric indicating the UTXO set size uint64_t GetBogoSize(const CScript& script_pub_key) { @@ -49,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()) { @@ -93,24 +112,11 @@ static void ApplyStats(CCoinsStats& stats, const uint256& hash, const std::map<u //! Calculate statistics about the unspent transaction output set template <typename T> -static bool GetUTXOStats(CCoinsView* view, BlockManager& blockman, CCoinsStats& stats, T hash_obj, const std::function<void()>& interruption_point, const CBlockIndex* pindex) +static bool ComputeUTXOStats(CCoinsView* view, CCoinsStats& stats, T hash_obj, const std::function<void()>& interruption_point) { std::unique_ptr<CCoinsViewCursor> pcursor(view->Cursor()); assert(pcursor); - if (!pindex) { - LOCK(cs_main); - pindex = blockman.LookupBlockIndex(view->GetBestBlock()); - } - stats.nHeight = Assert(pindex)->nHeight; - stats.hashBlock = pindex->GetBlockHash(); - - // Use CoinStatsIndex if it is requested and available and a hash_type of Muhash or None was requested - if ((stats.m_hash_type == CoinStatsHashType::MUHASH || stats.m_hash_type == CoinStatsHashType::NONE) && g_coin_stats_index && stats.index_requested) { - stats.index_used = true; - return g_coin_stats_index->LookUpStats(pindex, stats); - } - PrepareHash(hash_obj, stats); uint256 prevkey; @@ -141,29 +147,40 @@ static bool GetUTXOStats(CCoinsView* view, BlockManager& blockman, CCoinsStats& FinalizeHash(hash_obj, stats); stats.nDiskSize = view->EstimateSize(); + return true; } -bool GetUTXOStats(CCoinsView* view, BlockManager& blockman, CCoinsStats& stats, const std::function<void()>& interruption_point, const CBlockIndex* pindex) +std::optional<CCoinsStats> ComputeUTXOStats(CoinStatsHashType hash_type, CCoinsView* view, node::BlockManager& blockman, const std::function<void()>& interruption_point) { - switch (stats.m_hash_type) { - case(CoinStatsHashType::HASH_SERIALIZED): { - CHashWriter ss(SER_GETHASH, PROTOCOL_VERSION); - return GetUTXOStats(view, blockman, stats, ss, interruption_point, pindex); - } - case(CoinStatsHashType::MUHASH): { - MuHash3072 muhash; - return GetUTXOStats(view, blockman, stats, muhash, interruption_point, pindex); - } - case(CoinStatsHashType::NONE): { - return GetUTXOStats(view, blockman, stats, nullptr, interruption_point, pindex); + CBlockIndex* pindex = WITH_LOCK(::cs_main, return blockman.LookupBlockIndex(view->GetBestBlock())); + CCoinsStats stats{Assert(pindex)->nHeight, pindex->GetBlockHash()}; + + bool success = [&]() -> bool { + switch (hash_type) { + case(CoinStatsHashType::HASH_SERIALIZED): { + HashWriter ss{}; + return ComputeUTXOStats(view, stats, ss, interruption_point); + } + case(CoinStatsHashType::MUHASH): { + MuHash3072 muhash; + return ComputeUTXOStats(view, stats, muhash, interruption_point); + } + case(CoinStatsHashType::NONE): { + return ComputeUTXOStats(view, stats, nullptr, interruption_point); + } + } // no default case, so the compiler can warn about missing cases + assert(false); + }(); + + if (!success) { + return std::nullopt; } - } // no default case, so the compiler can warn about missing cases - assert(false); + return stats; } // 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; } @@ -171,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(); } @@ -182,4 +199,5 @@ static void FinalizeHash(MuHash3072& muhash, CCoinsStats& stats) stats.hashSerialized = out; } static void FinalizeHash(std::nullptr_t, CCoinsStats& stats) {} -} // namespace node + +} // namespace kernel diff --git a/src/node/coinstats.h b/src/kernel/coinstats.h index aa771b18b0..b7c1328e93 100644 --- a/src/node/coinstats.h +++ b/src/kernel/coinstats.h @@ -1,26 +1,27 @@ -// Copyright (c) 2010 Satoshi Nakamoto -// Copyright (c) 2009-2021 The Bitcoin Core developers +// 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_COINSTATS_H -#define BITCOIN_NODE_COINSTATS_H +#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 -namespace node { +namespace kernel { enum class CoinStatsHashType { HASH_SERIALIZED, MUHASH, @@ -28,9 +29,6 @@ enum class CoinStatsHashType { }; struct CCoinsStats { - //! Which hash type to use - const CoinStatsHashType m_hash_type; - int nHeight{0}; uint256 hashBlock{}; uint64_t nTransactions{0}; @@ -44,8 +42,6 @@ struct CCoinsStats { //! The number of coins contained. uint64_t coins_count{0}; - //! Signals if the coinstatsindex should be used (when available). - bool index_requested{true}; //! Signals if the coinstatsindex was used to retrieve the statistics. bool index_used{false}; @@ -70,15 +66,15 @@ struct CCoinsStats { //! Total cumulative amount of coins lost due to unclaimed miner rewards up to and including this block CAmount total_unspendables_unclaimed_rewards{0}; - CCoinsStats(CoinStatsHashType hash_type) : m_hash_type(hash_type) {} + CCoinsStats() = default; + CCoinsStats(int block_height, const uint256& block_hash); }; -//! Calculate statistics about the unspent transaction output set -bool GetUTXOStats(CCoinsView* view, node::BlockManager& blockman, CCoinsStats& stats, const std::function<void()>& interruption_point = {}, const CBlockIndex* pindex = nullptr); - uint64_t GetBogoSize(const CScript& script_pub_key); CDataStream TxOutSer(const COutPoint& outpoint, const Coin& coin); -} // namespace node -#endif // BITCOIN_NODE_COINSTATS_H +std::optional<CCoinsStats> ComputeUTXOStats(CoinStatsHashType hash_type, CCoinsView* view, node::BlockManager& blockman, const std::function<void()>& interruption_point = {}); +} // namespace kernel + +#endif // BITCOIN_KERNEL_COINSTATS_H diff --git a/src/kernel/context.cpp b/src/kernel/context.cpp new file mode 100644 index 0000000000..15413c1840 --- /dev/null +++ b/src/kernel/context.cpp @@ -0,0 +1,33 @@ +// 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/context.h> + +#include <crypto/sha256.h> +#include <key.h> +#include <logging.h> +#include <pubkey.h> +#include <random.h> + +#include <string> + + +namespace kernel { + +Context::Context() +{ + std::string sha256_algo = SHA256AutoDetect(); + LogPrintf("Using the '%s' SHA256 implementation\n", sha256_algo); + RandomInit(); + ECC_Start(); + ecc_verify_handle.reset(new ECCVerifyHandle()); +} + +Context::~Context() +{ + ecc_verify_handle.reset(); + ECC_Stop(); +} + +} // namespace kernel diff --git a/src/kernel/context.h b/src/kernel/context.h new file mode 100644 index 0000000000..9746ef994b --- /dev/null +++ b/src/kernel/context.h @@ -0,0 +1,31 @@ +// 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_CONTEXT_H +#define BITCOIN_KERNEL_CONTEXT_H + +#include <memory> + +class ECCVerifyHandle; + +namespace kernel { +//! Context struct holding the kernel library's logically global state, and +//! passed to external libbitcoin_kernel functions which need access to this +//! state. The kernel library API is a work in progress, so state organization +//! and member list will evolve over time. +//! +//! State stored directly in this struct should be simple. More complex state +//! should be stored to std::unique_ptr members pointing to opaque types. +struct Context { + std::unique_ptr<ECCVerifyHandle> ecc_verify_handle; + + //! Declare default constructor and destructor that are not inline, so code + //! instantiating the kernel::Context struct doesn't need to #include class + //! definitions for all the unique_ptr members. + Context(); + ~Context(); +}; +} // namespace kernel + +#endif // BITCOIN_KERNEL_CONTEXT_H diff --git a/src/kernel/mempool_limits.h b/src/kernel/mempool_limits.h new file mode 100644 index 0000000000..e192e7e6cd --- /dev/null +++ b/src/kernel/mempool_limits.h @@ -0,0 +1,30 @@ +// 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}; +}; +} // 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..1a1cf2bbdc --- /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, CChainState& 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..9a15ec6dca --- /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 CChainState; +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, + CChainState& 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 354bd097ce..199808505d 100644 --- a/src/key.cpp +++ b/src/key.cpp @@ -159,7 +159,7 @@ bool CKey::Check(const unsigned char *vch) { void CKey::MakeNewKey(bool fCompressedIn) { do { - GetStrongRandBytes(keydata.data(), keydata.size()); + GetStrongRandBytes(keydata); } while (!Check(keydata.data())); fValid = true; fCompressed = fCompressedIn; @@ -244,7 +244,7 @@ bool CKey::VerifyPubKey(const CPubKey& pubkey) const { } unsigned char rnd[8]; std::string str = "Bitcoin key verification\n"; - GetRandBytes(rnd, sizeof(rnd)); + GetRandBytes(rnd); uint256 hash; CHash256().Write(MakeUCharSpan(str)).Write(rnd).Finalize(hash); std::vector<unsigned char> vchSig; @@ -288,7 +288,7 @@ bool CKey::SignSchnorr(const uint256& hash, Span<unsigned char> sig, const uint2 uint256 tweak = XOnlyPubKey(pubkey_bytes).ComputeTapTweakHash(merkle_root->IsNull() ? nullptr : merkle_root); if (!secp256k1_keypair_xonly_tweak_add(GetVerifyContext(), &keypair, tweak.data())) return false; } - bool ret = secp256k1_schnorrsig_sign(secp256k1_context_sign, sig.data(), hash.data(), &keypair, aux.data()); + bool ret = secp256k1_schnorrsig_sign32(secp256k1_context_sign, sig.data(), hash.data(), &keypair, aux.data()); if (ret) { // Additional verification step to prevent using a potentially corrupted signature secp256k1_xonly_pubkey pubkey_verify; @@ -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); @@ -340,11 +341,11 @@ bool CExtKey::Derive(CExtKey &out, unsigned int _nChild) const { return key.Derive(out.key, out.chaincode, _nChild, chaincode); } -void CExtKey::SetSeed(Span<const uint8_t> seed) +void CExtKey::SetSeed(Span<const std::byte> seed) { static const unsigned char hashkey[] = {'B','i','t','c','o','i','n',' ','s','e','e','d'}; std::vector<unsigned char, secure_allocator<unsigned char>> vout(64); - CHMAC_SHA512{hashkey, sizeof(hashkey)}.Write(seed.data(), seed.size()).Finalize(vout.data()); + CHMAC_SHA512{hashkey, sizeof(hashkey)}.Write(UCharCast(seed.data()), seed.size()).Finalize(vout.data()); key.Set(vout.data(), vout.data() + 32, true); memcpy(chaincode.begin(), vout.data() + 32, 32); nDepth = 0; @@ -397,7 +398,7 @@ void ECC_Start() { { // Pass in a random blinding seed to the secp256k1 context. std::vector<unsigned char, secure_allocator<unsigned char>> vseed(32); - GetRandBytes(vseed.data(), 32); + GetRandBytes(vseed); bool ret = secp256k1_context_randomize(ctx, vseed.data()); assert(ret); } @@ -85,7 +85,7 @@ public: //! Simple read-only vector-like interface. unsigned int size() const { return (fValid ? keydata.size() : 0); } - const unsigned char* data() const { return keydata.data(); } + const std::byte* data() const { return reinterpret_cast<const std::byte*>(keydata.data()); } const unsigned char* begin() const { return keydata.data(); } const unsigned char* end() const { return keydata.data() + size(); } @@ -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,9 +176,9 @@ 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 uint8_t> seed); + void SetSeed(Span<const std::byte> seed); }; /** Initialize the elliptic curve support. May not be called twice without calling ECC_Stop first. */ diff --git a/src/leveldb/util/env_posix.cc b/src/leveldb/util/env_posix.cc index 9f5863a0f3..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; @@ -850,7 +850,7 @@ class SingletonEnv { public: SingletonEnv() { #if !defined(NDEBUG) - env_initialized_.store(true, std::memory_order::memory_order_relaxed); + env_initialized_.store(true, std::memory_order_relaxed); #endif // !defined(NDEBUG) static_assert(sizeof(env_storage_) >= sizeof(EnvType), "env_storage_ will not fit the Env"); @@ -867,7 +867,7 @@ class SingletonEnv { static void AssertEnvNotInitialized() { #if !defined(NDEBUG) - assert(!env_initialized_.load(std::memory_order::memory_order_relaxed)); + assert(!env_initialized_.load(std::memory_order_relaxed)); #endif // !defined(NDEBUG) } diff --git a/src/leveldb/util/env_windows.cc b/src/leveldb/util/env_windows.cc index 1834206562..4dcba222a1 100644 --- a/src/leveldb/util/env_windows.cc +++ b/src/leveldb/util/env_windows.cc @@ -798,7 +798,7 @@ class SingletonEnv { public: SingletonEnv() { #if !defined(NDEBUG) - env_initialized_.store(true, std::memory_order::memory_order_relaxed); + env_initialized_.store(true, std::memory_order_relaxed); #endif // !defined(NDEBUG) static_assert(sizeof(env_storage_) >= sizeof(EnvType), "env_storage_ will not fit the Env"); @@ -815,7 +815,7 @@ class SingletonEnv { static void AssertEnvNotInitialized() { #if !defined(NDEBUG) - assert(!env_initialized_.load(std::memory_order::memory_order_relaxed)); + assert(!env_initialized_.load(std::memory_order_relaxed)); #endif // !defined(NDEBUG) } diff --git a/src/logging.cpp b/src/logging.cpp index 764941c8ea..73c4e458bd 100644 --- a/src/logging.cpp +++ b/src/logging.cpp @@ -160,7 +160,9 @@ const CLogCategoryDesc LogCategories[] = {BCLog::VALIDATION, "validation"}, {BCLog::I2P, "i2p"}, {BCLog::IPC, "ipc"}, +#ifdef DEBUG_LOCKCONTENTION {BCLog::LOCK, "lock"}, +#endif {BCLog::UTIL, "util"}, {BCLog::BLOCKSTORE, "blockstorage"}, {BCLog::ALL, "1"}, @@ -169,7 +171,7 @@ const CLogCategoryDesc LogCategories[] = bool GetLogCategory(BCLog::LogFlags& flag, const std::string& str) { - if (str == "") { + if (str.empty()) { flag = BCLog::ALL; return true; } @@ -182,6 +184,91 @@ bool GetLogCategory(BCLog::LogFlags& flag, const std::string& str) return false; } +std::string LogLevelToStr(BCLog::Level level) +{ + switch (level) { + case BCLog::Level::None: + return "none"; + case BCLog::Level::Debug: + return "debug"; + case BCLog::Level::Info: + return "info"; + case BCLog::Level::Warning: + return "warning"; + case BCLog::Level::Error: + return "error"; + } + assert(false); +} + +std::string LogCategoryToStr(BCLog::LogFlags category) +{ + // Each log category string representation should sync with LogCategories + switch (category) { + case BCLog::LogFlags::NONE: + return "none"; + case BCLog::LogFlags::NET: + return "net"; + case BCLog::LogFlags::TOR: + return "tor"; + case BCLog::LogFlags::MEMPOOL: + return "mempool"; + case BCLog::LogFlags::HTTP: + return "http"; + case BCLog::LogFlags::BENCH: + return "bench"; + case BCLog::LogFlags::ZMQ: + return "zmq"; + case BCLog::LogFlags::WALLETDB: + return "walletdb"; + case BCLog::LogFlags::RPC: + return "rpc"; + case BCLog::LogFlags::ESTIMATEFEE: + return "estimatefee"; + case BCLog::LogFlags::ADDRMAN: + return "addrman"; + case BCLog::LogFlags::SELECTCOINS: + return "selectcoins"; + case BCLog::LogFlags::REINDEX: + return "reindex"; + case BCLog::LogFlags::CMPCTBLOCK: + return "cmpctblock"; + case BCLog::LogFlags::RAND: + return "rand"; + case BCLog::LogFlags::PRUNE: + return "prune"; + case BCLog::LogFlags::PROXY: + return "proxy"; + case BCLog::LogFlags::MEMPOOLREJ: + return "mempoolrej"; + case BCLog::LogFlags::LIBEVENT: + return "libevent"; + case BCLog::LogFlags::COINDB: + return "coindb"; + case BCLog::LogFlags::QT: + return "qt"; + case BCLog::LogFlags::LEVELDB: + return "leveldb"; + case BCLog::LogFlags::VALIDATION: + return "validation"; + case BCLog::LogFlags::I2P: + return "i2p"; + case BCLog::LogFlags::IPC: + return "ipc"; +#ifdef DEBUG_LOCKCONTENTION + case BCLog::LogFlags::LOCK: + return "lock"; +#endif + case BCLog::LogFlags::UTIL: + return "util"; + case BCLog::LogFlags::BLOCKSTORE: + return "blockstorage"; + case BCLog::LogFlags::ALL: + return "all"; + } + assert(false); +} + std::vector<LogCategory> BCLog::Logger::LogCategoriesList() const { // Sort log categories by alphabetical order. @@ -247,17 +334,38 @@ 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) +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) { StdLockGuard scoped_lock(m_cs); std::string str_prefixed = LogEscapeMessage(str); + if ((category != LogFlags::NONE || level != Level::None) && m_started_new_line) { + std::string s{"["}; + + if (category != LogFlags::NONE) { + s += LogCategoryToStr(category); + } + + if (category != LogFlags::NONE && level != Level::None) { + // Only add separator if both flag and level are not NONE + s += ":"; + } + + if (level != Level::None) { + s += LogLevelToStr(level); + } + + s += "] "; + str_prefixed.insert(0, s); + } + if (m_log_sourcelocations && m_started_new_line) { str_prefixed.insert(0, "[" + RemovePrefix(source_file, "./") + ":" + ToString(source_line) + "] [" + logging_function + "] "); } if (m_log_threadnames && m_started_new_line) { - str_prefixed.insert(0, "[" + util::ThreadGetInternalName() + "] "); + const auto& threadname = util::ThreadGetInternalName(); + str_prefixed.insert(0, "[" + (threadname.empty() ? "unknown" : threadname) + "] "); } str_prefixed = LogTimestampStr(str_prefixed); diff --git a/src/logging.h b/src/logging.h index 710e6c4c32..50869ad89a 100644 --- a/src/logging.h +++ b/src/logging.h @@ -60,11 +60,20 @@ namespace BCLog { VALIDATION = (1 << 21), I2P = (1 << 22), IPC = (1 << 23), +#ifdef DEBUG_LOCKCONTENTION LOCK = (1 << 24), +#endif UTIL = (1 << 25), BLOCKSTORE = (1 << 26), ALL = ~(uint32_t)0, }; + enum class Level { + Debug = 0, + None = 1, + Info = 2, + Warning = 3, + Error = 4, + }; class Logger { @@ -103,7 +112,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); + 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); /** Returns whether logs will be written to any output */ bool Enabled() const @@ -157,9 +166,14 @@ namespace BCLog { BCLog::Logger& LogInstance(); -/** Return true if log accepts specified category */ -static inline bool LogAcceptCategory(BCLog::LogFlags category) +/** 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); } @@ -171,7 +185,7 @@ bool GetLogCategory(BCLog::LogFlags& flag, const std::string& str); // peer can fill up a user's disk with debug.log entries. template <typename... Args> -static inline void LogPrintf_(const std::string& logging_function, const std::string& source_file, const int source_line, const char* fmt, const Args&... args) +static inline void LogPrintf_(const std::string& logging_function, const std::string& source_file, const int source_line, const BCLog::LogFlags flag, const BCLog::Level level, const char* fmt, const Args&... args) { if (LogInstance().Enabled()) { std::string log_msg; @@ -181,19 +195,35 @@ static inline void LogPrintf_(const std::string& logging_function, const std::st /* Original format string will have newline so don't add one here */ log_msg = "Error \"" + std::string(fmterr.what()) + "\" while formatting log message: " + fmt; } - LogInstance().LogPrintStr(log_msg, logging_function, source_file, source_line); + LogInstance().LogPrintStr(log_msg, logging_function, source_file, source_line, flag, level); } } -#define LogPrintf(...) LogPrintf_(__func__, __FILE__, __LINE__, __VA_ARGS__) +#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. -#define LogPrint(category, ...) \ - do { \ - if (LogAcceptCategory((category))) { \ - LogPrintf(__VA_ARGS__); \ - } \ + +// Log conditionally, prefixing the output with the passed category name. +#define LogPrint(category, ...) \ + do { \ + if (LogAcceptCategory((category), BCLog::Level::Debug)) { \ + LogPrintLevel_(category, BCLog::Level::None, __VA_ARGS__); \ + } \ + } while (0) + +// Log conditionally, prefixing the output with the passed category name and severity level. +#define LogPrintLevel(category, level, ...) \ + do { \ + if (LogAcceptCategory((category), (level))) { \ + LogPrintLevel_(category, level, __VA_ARGS__); \ + } \ } while (0) #endif // BITCOIN_LOGGING_H diff --git a/src/logging/timer.h b/src/logging/timer.h index fc5307bc62..d954e46301 100644 --- a/src/logging/timer.h +++ b/src/logging/timer.h @@ -97,13 +97,13 @@ private: #define LOG_TIME_MICROS_WITH_CATEGORY(end_msg, log_category) \ - BCLog::Timer<std::chrono::microseconds> PASTE2(logging_timer, __COUNTER__)(__func__, end_msg, log_category) + BCLog::Timer<std::chrono::microseconds> UNIQUE_NAME(logging_timer)(__func__, end_msg, log_category) #define LOG_TIME_MILLIS_WITH_CATEGORY(end_msg, log_category) \ - BCLog::Timer<std::chrono::milliseconds> PASTE2(logging_timer, __COUNTER__)(__func__, end_msg, log_category) + BCLog::Timer<std::chrono::milliseconds> UNIQUE_NAME(logging_timer)(__func__, end_msg, log_category) #define LOG_TIME_MILLIS_WITH_CATEGORY_MSG_ONCE(end_msg, log_category) \ - BCLog::Timer<std::chrono::milliseconds> PASTE2(logging_timer, __COUNTER__)(__func__, end_msg, log_category, /* msg_on_completion=*/false) + BCLog::Timer<std::chrono::milliseconds> UNIQUE_NAME(logging_timer)(__func__, end_msg, log_category, /* msg_on_completion=*/false) #define LOG_TIME_SECONDS(end_msg) \ - BCLog::Timer<std::chrono::seconds> PASTE2(logging_timer, __COUNTER__)(__func__, end_msg) + BCLog::Timer<std::chrono::seconds> UNIQUE_NAME(logging_timer)(__func__, end_msg) #endif // BITCOIN_LOGGING_TIMER_H diff --git a/src/mapport.cpp b/src/mapport.cpp index 42ca366089..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 @@ -193,7 +193,7 @@ static bool ProcessUpnp() std::string strDesc = PACKAGE_NAME " " + FormatFullVersion(); do { - r = UPNP_AddPortMapping(urls.controlURL, data.first.servicetype, port.c_str(), port.c_str(), lanaddr, strDesc.c_str(), "TCP", 0, "0"); + r = UPNP_AddPortMapping(urls.controlURL, data.first.servicetype, port.c_str(), port.c_str(), lanaddr, strDesc.c_str(), "TCP", nullptr, "0"); if (r != UPNPCOMMAND_SUCCESS) { ret = false; @@ -206,7 +206,7 @@ static bool ProcessUpnp() } while (g_mapport_interrupt.sleep_for(PORT_MAPPING_REANNOUNCE_PERIOD)); g_mapport_interrupt.reset(); - r = UPNP_DeletePortMapping(urls.controlURL, data.first.servicetype, port.c_str(), "TCP", 0); + r = UPNP_DeletePortMapping(urls.controlURL, data.first.servicetype, port.c_str(), "TCP", nullptr); LogPrintf("UPNP_DeletePortMapping() returned: %d\n", r); freeUPNPDevlist(devlist); devlist = nullptr; FreeUPNPUrls(&urls); 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 955eec46e3..865ce2ea97 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -13,15 +13,16 @@ #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> @@ -84,8 +85,8 @@ 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 { @@ -103,7 +104,7 @@ enum BindFlags { // The sleep time needs to be small to avoid new sockets stalling static const uint64_t SELECT_TIMEOUT_MILLISECONDS = 50; -const std::string NET_MESSAGE_COMMAND_OTHER = "*other*"; +const std::string NET_MESSAGE_TYPE_OTHER = "*other*"; static const uint64_t RANDOMIZER_ID_NETGROUP = 0x6c0edd8036ef4036ULL; // SHA256("netgroup")[0:8] static const uint64_t RANDOMIZER_ID_LOCALHOSTNONCE = 0xd93e69e2bbfa5735ULL; // SHA256("localhostnonce")[0:8] @@ -113,7 +114,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; @@ -186,7 +187,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); @@ -194,7 +195,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); } @@ -205,16 +206,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) @@ -232,35 +230,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. @@ -421,16 +419,16 @@ 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 { - LogPrint(BCLog::NET, "Warning: getsockname failed\n"); + LogPrintLevel(BCLog::NET, BCLog::Level::Warning, "getsockname failed\n"); } } return addr_bind; @@ -453,10 +451,9 @@ CNode* CConnman::ConnectNode(CAddress addrConnect, const char *pszDest, bool fCo } } - /// debug print - LogPrint(BCLog::NET, "trying connection %s lastseen=%.1fhrs\n", + 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); + Ticks<HoursDouble>(pszDest ? 0h : Now<NodeSeconds>() - addrConnect.nTime)); // Resolve const uint16_t default_port{pszDest != nullptr ? Params().GetDefaultPort(pszDest) : @@ -539,10 +536,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), @@ -575,26 +571,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); @@ -622,16 +598,9 @@ Network CNode::ConnectedThroughNetwork() const void CNode::CopyStats(CNodeStats& stats) { stats.nodeid = this->GetId(); - X(nServices); X(addr); X(addrBind); stats.m_network = ConnectedThroughNetwork(); - if (m_tx_relay != nullptr) { - LOCK(m_tx_relay->cs_filter); - stats.fRelayTxes = m_tx_relay->fRelayTxes; - } else { - stats.fRelayTxes = false; - } X(m_last_send); X(m_last_recv); X(m_last_tx_time); @@ -649,20 +618,15 @@ void CNode::CopyStats(CNodeStats& stats) X(m_bip152_highbandwidth_from); { LOCK(cs_vSend); - X(mapSendBytesPerMsgCmd); + X(mapSendBytesPerMsgType); X(nSendBytes); } { LOCK(cs_vRecv); - X(mapRecvBytesPerMsgCmd); + X(mapRecvBytesPerMsgType); X(nRecvBytes); } X(m_permissionFlags); - if (m_tx_relay != nullptr) { - stats.minFeeFilter = m_tx_relay->minFeeFilter; - } else { - stats.minFeeFilter = 0; - } X(m_last_ping_time); X(m_min_ping_time); @@ -695,19 +659,19 @@ bool CNode::ReceiveMsgBytes(Span<const uint8_t> msg_bytes, bool& complete) bool reject_message{false}; CNetMessage msg = m_deserializer->GetMessage(time, reject_message); if (reject_message) { - // Message deserialization failed. Drop the message but don't disconnect the peer. + // Message deserialization failed. Drop the message but don't disconnect the peer. // store the size of the corrupt message - mapRecvBytesPerMsgCmd.at(NET_MESSAGE_COMMAND_OTHER) += msg.m_raw_message_size; + mapRecvBytesPerMsgType.at(NET_MESSAGE_TYPE_OTHER) += msg.m_raw_message_size; continue; } - // Store received bytes per message command - // to prevent a memory DOS, only allow valid commands - auto i = mapRecvBytesPerMsgCmd.find(msg.m_type); - if (i == mapRecvBytesPerMsgCmd.end()) { - i = mapRecvBytesPerMsgCmd.find(NET_MESSAGE_COMMAND_OTHER); + // Store received bytes per message type. + // To prevent a memory DOS, only allow known message types. + auto i = mapRecvBytesPerMsgType.find(msg.m_type); + if (i == mapRecvBytesPerMsgType.end()) { + i = mapRecvBytesPerMsgType.find(NET_MESSAGE_TYPE_OTHER); } - assert(i != mapRecvBytesPerMsgCmd.end()); + assert(i != mapRecvBytesPerMsgType.end()); i->second += msg.m_raw_message_size; // push the message to the process queue, @@ -792,7 +756,7 @@ CNetMessage V1TransportDeserializer::GetMessage(const std::chrono::microseconds // decompose a single CNetMessage from the TransportDeserializer CNetMessage msg(std::move(vRecv)); - // store command string, time, and sizes + // store message type string, time, and sizes msg.m_type = hdr.GetCommand(); msg.m_time = time; msg.m_message_size = hdr.nMessageSize; @@ -803,7 +767,7 @@ CNetMessage V1TransportDeserializer::GetMessage(const std::chrono::microseconds // We just received a message off the wire, harvest entropy from the time (and the message checksum) RandAddEvent(ReadLE32(hash.begin())); - // Check checksum and header command string + // Check checksum and header message type string if (memcmp(hash.begin(), hdr.pchChecksum, CMessageHeader::CHECKSUM_SIZE) != 0) { LogPrint(BCLog::NET, "Header error: Wrong checksum (%s, %u bytes), expected %s was %s, peer=%d\n", SanitizeString(msg.m_type), msg.m_message_size, @@ -887,210 +851,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.fRelayTxes != b.fRelayTxes) return b.fRelayTxes; - 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.fRelayTxes != b.fRelayTxes) return a.fRelayTxes; - 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.fRelayTxes && n.fRelevantServices; }); - - // Protect 4 nodes that most recently sent us novel blocks. - // An attacker cannot manipulate this metric without performing useful work. - EraseLastKElements(vEvictionCandidates, CompareNodeBlockTime, 4); - - // Protect 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. @@ -1106,25 +866,24 @@ 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; - bool peer_relay_txes = false; - bool peer_filter_not_null = false; - if (node->m_tx_relay != nullptr) { - LOCK(node->m_tx_relay->cs_filter); - peer_relay_txes = node->m_tx_relay->fRelayTxes; - peer_filter_not_null = node->m_tx_relay->pfilter != nullptr; - } - NodeEvictionCandidate candidate = {node->GetId(), node->m_connected, node->m_min_ping_time, - node->m_last_block_time, node->m_last_tx_time, - HasAllDesirableServiceFlags(node->nServices), - peer_relay_txes, peer_filter_not_null, node->nKeyedNetGroup, - node->m_prefer_evict, node->addr.IsLocal(), - node->ConnectedThroughNetwork()}; + NodeEvictionCandidate candidate{ + .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); } } @@ -1158,12 +917,12 @@ void CConnman::AcceptConnection(const ListenSocket& hListenSocket) { } if (!addr.SetSockAddr((const struct sockaddr*)&sockaddr)) { - LogPrintf("Warning: Unknown socket family\n"); + LogPrintLevel(BCLog::NET, BCLog::Level::Warning, "Unknown socket family\n"); } else { 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); @@ -1208,7 +967,11 @@ void CConnman::CreateNodeFromAcceptedSocket(std::unique_ptr<Sock>&& sock, // According to the internet TCP_NODELAY is not carried into accepted sockets // on all platforms. Set it again here just to be sure. - SetSocketNoDelay(sock->Get()); + const int on{1}; + if (sock->SetSockOpt(IPPROTO_TCP, TCP_NODELAY, &on, sizeof(on)) == SOCKET_ERROR) { + LogPrint(BCLog::NET, "connection from %s: unable to set TCP_NODELAY, continuing anyway\n", + addr.ToString()); + } // Don't accept connections from banned peers. bool banned = m_banman && m_banman->IsBanned(addr); @@ -1245,7 +1008,6 @@ void CConnman::CreateNodeFromAcceptedSocket(std::unique_ptr<Sock>&& sock, 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), @@ -1257,7 +1019,7 @@ void CConnman::CreateNodeFromAcceptedSocket(std::unique_ptr<Sock>&& sock, 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()); @@ -1409,13 +1171,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) { @@ -1442,171 +1203,52 @@ 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() { - std::set<SOCKET> recv_set; - std::set<SOCKET> send_set; - std::set<SOCKET> error_set; + AssertLockNotHeld(m_total_bytes_sent_mutex); + + 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); + for (CNode* pnode : nodes) { if (interruptNet) return; @@ -1622,9 +1264,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) { @@ -1694,13 +1339,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); } } @@ -1708,6 +1354,8 @@ void CConnman::SocketHandlerListening(const std::set<SOCKET>& recv_set) void CConnman::ThreadSocketHandler() { + AssertLockNotHeld(m_total_bytes_sent_mutex); + SetSyscallSandboxPolicy(SyscallSandboxPolicy::NET); while (!interruptNet) { @@ -1819,9 +1467,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++; } @@ -1872,7 +1519,13 @@ bool CConnman::GetTryNewOutboundPeer() const void CConnman::SetTryNewOutboundPeer(bool flag) { m_try_another_outbound_peer = flag; - LogPrint(BCLog::NET, "net: setting try another outbound peer=%s\n", flag ? "true" : "false"); + LogPrint(BCLog::NET, "setting try another outbound peer=%s\n", flag ? "true" : "false"); +} + +void CConnman::StartExtraBlockRelayPeers() +{ + LogPrint(BCLog::NET, "enabling extra block-relay-only peers\n"); + m_start_extra_block_relay_peers = true; } // Return the number of peers we have over our outbound connection limit @@ -1912,6 +1565,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()) { @@ -2015,7 +1669,7 @@ void CConnman::ThreadOpenConnections(const std::vector<std::string> connect) case ConnectionType::BLOCK_RELAY: case ConnectionType::ADDR_FETCH: case ConnectionType::FEELER: - setConnected.insert(pnode->addr.GetGroup(addrman.GetAsmap())); + setConnected.insert(m_netgroupman.GetGroup(pnode->addr)); } // no default case, so the compiler can warn about missing cases } } @@ -2080,7 +1734,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) { @@ -2089,7 +1743,7 @@ void CConnman::ThreadOpenConnections(const std::vector<std::string> connect) m_anchors.pop_back(); if (!addr.IsValid() || IsLocal(addr) || !IsReachable(addr) || !HasAllDesirableServiceFlags(addr.nServices) || - setConnected.count(addr.GetGroup(addrman.GetAsmap()))) continue; + setConnected.count(m_netgroupman.GetGroup(addr))) continue; addrConnect = addr; LogPrint(BCLog::NET, "Trying to make an anchor connection to %s\n", addrConnect.ToString()); break; @@ -2103,7 +1757,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 @@ -2130,7 +1784,7 @@ void CConnman::ThreadOpenConnections(const std::vector<std::string> connect) } // Require outbound connections, other than feelers, to be to distinct network groups - if (!fFeeler && setConnected.count(addr.GetGroup(addrman.GetAsmap()))) { + if (!fFeeler && setConnected.count(m_netgroupman.GetGroup(addr))) { break; } @@ -2143,8 +1797,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 @@ -2165,12 +1820,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 = GetRandInt(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()); } @@ -2303,7 +1957,7 @@ 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); @@ -2399,51 +2053,59 @@ bool CConnman::BindListenPort(const CService& addrBind, bilingual_str& strError, socklen_t len = sizeof(sockaddr); if (!addrBind.GetSockAddr((struct sockaddr*)&sockaddr, &len)) { - strError = strprintf(Untranslated("Error: Bind address family for %s not supported"), addrBind.ToString()); - LogPrintf("%s\n", strError.original); + strError = strprintf(Untranslated("Bind address family for %s not supported"), addrBind.ToString()); + LogPrintLevel(BCLog::NET, BCLog::Level::Error, "%s\n", strError.original); return false; } std::unique_ptr<Sock> sock = CreateSock(addrBind); if (!sock) { - strError = strprintf(Untranslated("Error: Couldn't open socket for incoming connections (socket returned error %s)"), NetworkErrorString(WSAGetLastError())); - LogPrintf("%s\n", strError.original); + strError = strprintf(Untranslated("Couldn't open socket for incoming connections (socket returned error %s)"), NetworkErrorString(WSAGetLastError())); + LogPrintLevel(BCLog::NET, BCLog::Level::Error, "%s\n", strError.original); return false; } // Allow binding if the port is still in TIME_WAIT state after // the program was closed and restarted. - setsockopt(sock->Get(), SOL_SOCKET, SO_REUSEADDR, (sockopt_arg_type)&nOne, sizeof(int)); + if (sock->SetSockOpt(SOL_SOCKET, SO_REUSEADDR, (sockopt_arg_type)&nOne, sizeof(int)) == SOCKET_ERROR) { + strError = strprintf(Untranslated("Error setting SO_REUSEADDR on socket: %s, continuing anyway"), NetworkErrorString(WSAGetLastError())); + LogPrintf("%s\n", strError.original); + } // some systems don't have IPV6_V6ONLY but are always v6only; others do have the option // and enable it by default or not. Try to enable it, if possible. if (addrBind.IsIPv6()) { #ifdef IPV6_V6ONLY - setsockopt(sock->Get(), IPPROTO_IPV6, IPV6_V6ONLY, (sockopt_arg_type)&nOne, sizeof(int)); + if (sock->SetSockOpt(IPPROTO_IPV6, IPV6_V6ONLY, (sockopt_arg_type)&nOne, sizeof(int)) == SOCKET_ERROR) { + strError = strprintf(Untranslated("Error setting IPV6_V6ONLY on socket: %s, continuing anyway"), NetworkErrorString(WSAGetLastError())); + LogPrintf("%s\n", strError.original); + } #endif #ifdef WIN32 int nProtLevel = PROTECTION_LEVEL_UNRESTRICTED; - setsockopt(sock->Get(), IPPROTO_IPV6, IPV6_PROTECTION_LEVEL, (const char*)&nProtLevel, sizeof(int)); + if (sock->SetSockOpt(IPPROTO_IPV6, IPV6_PROTECTION_LEVEL, (const char*)&nProtLevel, sizeof(int)) == SOCKET_ERROR) { + strError = strprintf(Untranslated("Error setting IPV6_PROTECTION_LEVEL on socket: %s, continuing anyway"), NetworkErrorString(WSAGetLastError())); + LogPrintf("%s\n", strError.original); + } #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); else strError = strprintf(_("Unable to bind to %s on this computer (bind returned error %s)"), addrBind.ToString(), NetworkErrorString(nErr)); - LogPrintf("%s\n", strError.original); + LogPrintLevel(BCLog::NET, BCLog::Level::Error, "%s\n", strError.original); return false; } 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(_("Error: Listening for incoming connections failed (listen returned error %s)"), NetworkErrorString(WSAGetLastError())); - LogPrintf("%s\n", strError.original); + strError = strprintf(_("Listening for incoming connections failed (listen returned error %s)"), NetworkErrorString(WSAGetLastError())); + LogPrintLevel(BCLog::NET, BCLog::Level::Error, "%s\n", strError.original); return false; } @@ -2517,8 +2179,12 @@ void CConnman::SetNetworkActive(bool active) } } -CConnman::CConnman(uint64_t nSeed0In, uint64_t nSeed1In, AddrMan& addrman_in, bool network_active) - : addrman(addrman_in), nSeed0(nSeed0In), nSeed1(nSeed1In) +CConnman::CConnman(uint64_t nSeed0In, uint64_t nSeed1In, AddrMan& addrman_in, + const NetGroupManager& netgroupman, bool network_active) + : addrman(addrman_in) + , m_netgroupman{netgroupman} + , nSeed0(nSeed0In) + , nSeed1(nSeed1In) { SetTryNewOutboundPeer(false); @@ -2579,6 +2245,7 @@ bool CConnman::InitBinds(const Options& options) bool CConnman::Start(CScheduler& scheduler, const Options& connOptions) { + AssertLockNotHeld(m_total_bytes_sent_mutex); Init(connOptions); if (fListen && !InitBinds(connOptions)) { @@ -2679,7 +2346,7 @@ bool CConnman::Start(CScheduler& scheduler, const Options& connOptions) class CNetCleanup { public: - CNetCleanup() {} + CNetCleanup() = default; ~CNetCleanup() { @@ -2793,14 +2460,17 @@ std::vector<CAddress> CConnman::GetAddresses(CNode& requestor, size_t max_addres { auto local_socket_bytes = requestor.addrBind.GetAddrBytes(); uint64_t cache_id = GetDeterministicRandomizer(RANDOMIZER_ID_ADDRCACHE) - .Write(requestor.addr.GetNetwork()) + .Write(requestor.ConnectedThroughNetwork()) .Write(local_socket_bytes.data(), local_socket_bytes.size()) + // For outbound connections, the port of the bound address is randomly + // assigned by the OS and would therefore not be useful for seeding. + .Write(requestor.IsInboundConn() ? requestor.addrBind.GetPort() : 0) .Finalize(); const auto current_time = GetTime<std::chrono::microseconds>(); auto r = m_addr_response_caches.emplace(cache_id, CachedAddrResponse{}); CachedAddrResponse& cache_entry = r.first->second; if (cache_entry.m_cache_entry_expiration < current_time) { // If emplace() added new one it has expiration 0. - cache_entry.m_addrs_response_cache = GetAddresses(max_addresses, max_pct, /* network */ std::nullopt); + cache_entry.m_addrs_response_cache = GetAddresses(max_addresses, max_pct, /*network=*/std::nullopt); // Choosing a proper cache lifetime is a trade-off between the privacy leak minimization // and the usefulness of ADDR responses to honest users. // @@ -2877,7 +2547,7 @@ void CConnman::GetNodeStats(std::vector<CNodeStats>& vstats) const for (CNode* pnode : m_nodes) { vstats.emplace_back(); pnode->CopyStats(vstats.back()); - vstats.back().m_mapped_as = pnode->addr.GetMappedAS(addrman.GetAsmap()); + vstats.back().m_mapped_as = m_netgroupman.GetMappedAS(pnode->addr); } } @@ -2931,7 +2601,9 @@ void CConnman::RecordBytesRecv(uint64_t bytes) void CConnman::RecordBytesSent(uint64_t bytes) { - LOCK(cs_totalBytesSent); + AssertLockNotHeld(m_total_bytes_sent_mutex); + LOCK(m_total_bytes_sent_mutex); + nTotalBytesSent += bytes; const auto now = GetTime<std::chrono::seconds>(); @@ -2947,7 +2619,8 @@ void CConnman::RecordBytesSent(uint64_t bytes) uint64_t CConnman::GetMaxOutboundTarget() const { - LOCK(cs_totalBytesSent); + AssertLockNotHeld(m_total_bytes_sent_mutex); + LOCK(m_total_bytes_sent_mutex); return nMaxOutboundLimit; } @@ -2958,7 +2631,15 @@ std::chrono::seconds CConnman::GetMaxOutboundTimeframe() const std::chrono::seconds CConnman::GetMaxOutboundTimeLeftInCycle() const { - LOCK(cs_totalBytesSent); + AssertLockNotHeld(m_total_bytes_sent_mutex); + LOCK(m_total_bytes_sent_mutex); + return GetMaxOutboundTimeLeftInCycle_(); +} + +std::chrono::seconds CConnman::GetMaxOutboundTimeLeftInCycle_() const +{ + AssertLockHeld(m_total_bytes_sent_mutex); + if (nMaxOutboundLimit == 0) return 0s; @@ -2972,14 +2653,15 @@ std::chrono::seconds CConnman::GetMaxOutboundTimeLeftInCycle() const bool CConnman::OutboundTargetReached(bool historicalBlockServingLimit) const { - LOCK(cs_totalBytesSent); + AssertLockNotHeld(m_total_bytes_sent_mutex); + LOCK(m_total_bytes_sent_mutex); if (nMaxOutboundLimit == 0) return false; if (historicalBlockServingLimit) { // keep a large enough buffer to at least relay each block once - const std::chrono::seconds timeLeftInCycle = GetMaxOutboundTimeLeftInCycle(); + const std::chrono::seconds timeLeftInCycle = GetMaxOutboundTimeLeftInCycle_(); const uint64_t buffer = timeLeftInCycle / std::chrono::minutes{10} * MAX_BLOCK_SERIALIZED_SIZE; if (buffer >= nMaxOutboundLimit || nMaxOutboundTotalBytesSentInCycle >= nMaxOutboundLimit - buffer) return true; @@ -2992,7 +2674,8 @@ bool CConnman::OutboundTargetReached(bool historicalBlockServingLimit) const uint64_t CConnman::GetOutboundTargetBytesLeft() const { - LOCK(cs_totalBytesSent); + AssertLockNotHeld(m_total_bytes_sent_mutex); + LOCK(m_total_bytes_sent_mutex); if (nMaxOutboundLimit == 0) return 0; @@ -3006,7 +2689,8 @@ uint64_t CConnman::GetTotalBytesRecv() const uint64_t CConnman::GetTotalBytesSent() const { - LOCK(cs_totalBytesSent); + AssertLockNotHeld(m_total_bytes_sent_mutex); + LOCK(m_total_bytes_sent_mutex); return nTotalBytesSent; } @@ -3017,7 +2701,10 @@ 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) +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) : m_sock{sock}, m_connected{GetTime<std::chrono::seconds>()}, addr(addrIn), @@ -3027,17 +2714,13 @@ CNode::CNode(NodeId idIn, ServiceFlags nLocalServicesIn, std::shared_ptr<Sock> s nKeyedNetGroup(nKeyedNetGroupIn), id(idIn), nLocalHostNonce(nLocalHostNonceIn), - m_conn_type(conn_type_in), - nLocalServices(nLocalServicesIn) + m_conn_type(conn_type_in) { if (inbound_onion) assert(conn_type_in == ConnectionType::INBOUND); - if (conn_type_in != ConnectionType::BLOCK_RELAY) { - m_tx_relay = std::make_unique<TxRelay>(); - } for (const std::string &msg : getAllNetMessageTypes()) - mapRecvBytesPerMsgCmd[msg] = 0; - mapRecvBytesPerMsgCmd[NET_MESSAGE_COMMAND_OTHER] = 0; + mapRecvBytesPerMsgType[msg] = 0; + mapRecvBytesPerMsgType[NET_MESSAGE_TYPE_OTHER] = 0; if (fLogIPs) { LogPrint(BCLog::NET, "Added connection to %s peer=%d\n", m_addr_name, id); @@ -3056,6 +2739,7 @@ bool CConnman::NodeFullyConnected(const CNode* pnode) void CConnman::PushMessage(CNode* pnode, CSerializedNetMsg&& msg) { + AssertLockNotHeld(m_total_bytes_sent_mutex); size_t nMessageSize = msg.data.size(); LogPrint(BCLog::NET, "sending %s (%d bytes) peer=%d\n", msg.m_type, nMessageSize, pnode->GetId()); if (gArgs.GetBoolArg("-capturemessages", false)) { @@ -3082,7 +2766,7 @@ void CConnman::PushMessage(CNode* pnode, CSerializedNetMsg&& msg) bool optimisticSend(pnode->vSendMsg.empty()); //log total amount of bytes per message type - pnode->mapSendBytesPerMsgCmd[msg.m_type] += nTotalSize; + pnode->mapSendBytesPerMsgType[msg.m_type] += nTotalSize; pnode->nSendSize += nTotalSize; if (pnode->nSendSize > nSendBufferMaxSize) pnode->fPauseSend = true; @@ -3113,9 +2797,9 @@ CSipHasher CConnman::GetDeterministicRandomizer(uint64_t id) const return CSipHasher(nSeed0, nSeed1).Write(id); } -uint64_t CConnman::CalculateKeyedNetGroup(const CAddress& ad) const +uint64_t CConnman::CalculateKeyedNetGroup(const CAddress& address) const { - std::vector<unsigned char> vchNetGroup(ad.GetGroup(addrman.GetAsmap())); + std::vector<unsigned char> vchNetGroup(m_netgroupman.GetGroup(address)); return GetDeterministicRandomizer(RANDOMIZER_ID_NETGROUP).Write(vchNetGroup.data(), vchNetGroup.size()).Finalize(); } @@ -3135,11 +2819,11 @@ void CaptureMessageToFile(const CAddress& addr, std::string clean_addr = addr.ToString(); std::replace(clean_addr.begin(), clean_addr.end(), ':', '_'); - fs::path base_path = gArgs.GetDataDirNet() / "message_capture" / clean_addr; + fs::path base_path = gArgs.GetDataDirNet() / "message_capture" / fs::u8path(clean_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> @@ -16,6 +17,7 @@ #include <net_permissions.h> #include <netaddress.h> #include <netbase.h> +#include <netgroup.h> #include <policy/feerate.h> #include <protocol.h> #include <random.h> @@ -32,6 +34,7 @@ #include <cstdint> #include <deque> #include <functional> +#include <list> #include <map> #include <memory> #include <optional> @@ -99,91 +102,26 @@ struct AddedNodeInfo class CNodeStats; class CClientUIInterface; -struct CSerializedNetMsg -{ +struct CSerializedNetMsg { CSerializedNetMsg() = default; CSerializedNetMsg(CSerializedNetMsg&&) = default; CSerializedNetMsg& operator=(CSerializedNetMsg&&) = default; - // No copying, only moves. + // No implicit copying, only moves. CSerializedNetMsg(const CSerializedNetMsg& msg) = delete; CSerializedNetMsg& operator=(const CSerializedNetMsg&) = delete; + CSerializedNetMsg Copy() const + { + CSerializedNetMsg copy; + copy.data = data; + copy.m_type = m_type; + return copy; + } + std::vector<unsigned char> data; 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. @@ -206,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) @@ -225,7 +163,7 @@ 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); extern bool fDiscover; @@ -239,18 +177,16 @@ 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_COMMAND_OTHER; -typedef std::map<std::string, uint64_t> mapMsgCmdSize; //command, total bytes +extern const std::string NET_MESSAGE_TYPE_OTHER; +using mapMsgTypeSize = std::map</* message type */ std::string, /* total bytes */ uint64_t>; class CNodeStats { public: NodeId nodeid; - ServiceFlags nServices; - bool fRelayTxes; std::chrono::seconds m_last_send; std::chrono::seconds m_last_recv; std::chrono::seconds m_last_tx_time; @@ -265,13 +201,12 @@ public: bool m_bip152_highbandwidth_from; int m_starting_height; uint64_t nSendBytes; - mapMsgCmdSize mapSendBytesPerMsgCmd; + mapMsgTypeSize mapSendBytesPerMsgType; uint64_t nRecvBytes; - mapMsgCmdSize mapRecvBytesPerMsgCmd; + mapMsgTypeSize mapRecvBytesPerMsgType; NetPermissionFlags m_permissionFlags; std::chrono::microseconds m_last_ping_time; std::chrono::microseconds m_min_ping_time; - CAmount minFeeFilter; // Our address, as reported by the peer std::string addrLocal; // Address of this peer @@ -307,7 +242,7 @@ public: /** The TransportDeserializer takes care of holding and deserializing the * network receive buffer. It can deserialize the network buffer into a - * transport protocol agnostic CNetMessage (command & payload) + * transport protocol agnostic CNetMessage (message type & payload) */ class TransportDeserializer { public: @@ -410,7 +345,6 @@ public: std::unique_ptr<TransportSerializer> m_serializer; NetPermissionFlags m_permissionFlags{NetPermissionFlags::None}; - std::atomic<ServiceFlags> nServices{NODE_NONE}; /** * Socket used for communication with the node. @@ -463,8 +397,6 @@ public: bool HasPermission(NetPermissionFlags permission) const { return NetPermissions::HasFlag(m_permissionFlags, 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 @@ -548,34 +480,18 @@ public: // Peer selected us as (compact blocks) high-bandwidth peer (BIP152) std::atomic<bool> m_bip152_highbandwidth_from{false}; - struct TxRelay { - mutable RecursiveMutex cs_filter; - // We use fRelayTxes for two purposes - - // a) it allows us to not relay tx invs before receiving the peer's version message - // b) the peer may tell us in its version message that we should not relay tx invs - // unless it loads a bloom filter. - bool fRelayTxes GUARDED_BY(cs_filter){false}; - std::unique_ptr<CBloomFilter> pfilter PT_GUARDED_BY(cs_filter) GUARDED_BY(cs_filter){nullptr}; - - mutable RecursiveMutex cs_tx_inventory; - CRollingBloomFilter filterInventoryKnown GUARDED_BY(cs_tx_inventory){50000, 0.000001}; - // Set of transaction ids we still have to announce. - // They are sorted by the mempool before relay, so the order is not important. - std::set<uint256> setInventoryTxToSend; - // Used for BIP35 mempool sending - bool fSendMempool GUARDED_BY(cs_tx_inventory){false}; - // Last time a "MEMPOOL" request was serviced. - std::atomic<std::chrono::seconds> m_last_mempool_req{0s}; - std::chrono::microseconds nNextInvSend{0}; - - /** Minimum fee rate with which to filter inv's to this node */ - std::atomic<CAmount> minFeeFilter{0}; - CAmount lastSentFeeFilter{0}; - std::chrono::microseconds m_next_send_feefilter{0}; - }; + /** Whether this peer provides all services that we want. Used for eviction decisions */ + std::atomic_bool m_has_all_wanted_services{false}; - // m_tx_relay == nullptr if we're not relaying transactions with this peer - std::unique_ptr<TxRelay> m_tx_relay; + /** 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 + * back to false. Used only in inbound eviction logic. */ + std::atomic_bool m_relays_txs{false}; + + /** Whether this peer has loaded a bloom filter. Used only in inbound + * eviction logic. */ + std::atomic_bool m_bloom_filter_loaded{false}; /** UNIX epoch time of the last block received from this peer that we had * not yet seen (e.g. not already received from another peer), that passed @@ -597,7 +513,10 @@ 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); CNode(const CNode&) = delete; CNode& operator=(const CNode&) = delete; @@ -624,7 +543,7 @@ public: * @return True if the peer should stay connected, * False if the peer should be disconnected from. */ - bool ReceiveMsgBytes(Span<const uint8_t> msg_bytes, bool& complete); + bool ReceiveMsgBytes(Span<const uint8_t> msg_bytes, bool& complete) EXCLUSIVE_LOCKS_REQUIRED(!cs_vRecv); void SetCommonVersion(int greatest_common_version) { @@ -636,9 +555,9 @@ public: return m_greatest_common_version; } - CService GetAddrLocal() const LOCKS_EXCLUDED(m_addr_local_mutex); + CService GetAddrLocal() const EXCLUSIVE_LOCKS_REQUIRED(!m_addr_local_mutex); //! May not be called more than once - void SetAddrLocal(const CService& addrLocalIn) LOCKS_EXCLUDED(m_addr_local_mutex); + void SetAddrLocal(const CService& addrLocalIn) EXCLUSIVE_LOCKS_REQUIRED(!m_addr_local_mutex); CNode* AddRef() { @@ -651,31 +570,9 @@ public: nRefCount--; } - void AddKnownTx(const uint256& hash) - { - if (m_tx_relay != nullptr) { - LOCK(m_tx_relay->cs_tx_inventory); - m_tx_relay->filterInventoryKnown.insert(hash); - } - } - - void PushTxInventory(const uint256& hash) - { - if (m_tx_relay == nullptr) return; - LOCK(m_tx_relay->cs_tx_inventory); - if (!m_tx_relay->filterInventoryKnown.contains(hash)) { - m_tx_relay->setInventoryTxToSend.insert(hash); - } - } - - void CloseSocketDisconnect(); + void CloseSocketDisconnect() EXCLUSIVE_LOCKS_REQUIRED(!m_sock_mutex); - void CopyStats(CNodeStats& stats); - - ServiceFlags GetLocalServices() const - { - return nLocalServices; - } + void CopyStats(CNodeStats& stats) EXCLUSIVE_LOCKS_REQUIRED(!m_subver_mutex, !m_addr_local_mutex, !cs_vSend, !cs_vRecv); std::string ConnectionTypeAsString() const { return ::ConnectionTypeAsString(m_conn_type); } @@ -691,31 +588,14 @@ 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 CService addrLocal GUARDED_BY(m_addr_local_mutex); mutable Mutex m_addr_local_mutex; - mapMsgCmdSize mapSendBytesPerMsgCmd GUARDED_BY(cs_vSend); - mapMsgCmdSize mapRecvBytesPerMsgCmd GUARDED_BY(cs_vRecv); + mapMsgTypeSize mapSendBytesPerMsgType GUARDED_BY(cs_vSend); + mapMsgTypeSize mapRecvBytesPerMsgType GUARDED_BY(cs_vRecv); }; /** @@ -725,7 +605,7 @@ class NetEventsInterface { public: /** 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; @@ -789,7 +669,10 @@ public: bool m_i2p_accept_incoming; }; - void Init(const Options& connOptions) { + void Init(const Options& connOptions) EXCLUSIVE_LOCKS_REQUIRED(!m_added_nodes_mutex, !m_total_bytes_sent_mutex) + { + AssertLockNotHeld(m_total_bytes_sent_mutex); + nLocalServices = connOptions.nLocalServices; nMaxConnections = connOptions.nMaxConnections; m_max_outbound_full_relay = std::min(connOptions.m_max_outbound_full_relay, connOptions.nMaxConnections); @@ -805,7 +688,7 @@ public: nReceiveFloodSize = connOptions.nReceiveFloodSize; m_peer_connect_timeout = std::chrono::seconds{connOptions.m_peer_connect_timeout}; { - LOCK(cs_totalBytesSent); + LOCK(m_total_bytes_sent_mutex); nMaxOutboundLimit = connOptions.nMaxOutboundLimit; } vWhitelistedRange = connOptions.vWhitelistedRange; @@ -816,9 +699,12 @@ public: m_onion_binds = connOptions.onion_binds; } - CConnman(uint64_t seed0, uint64_t seed1, AddrMan& addrman, bool network_active = true); + CConnman(uint64_t seed0, uint64_t seed1, AddrMan& addrman, const NetGroupManager& netgroupman, + bool network_active = true); + ~CConnman(); - bool Start(CScheduler& scheduler, const Options& options); + + bool Start(CScheduler& scheduler, const Options& options) EXCLUSIVE_LOCKS_REQUIRED(!m_total_bytes_sent_mutex, !m_added_nodes_mutex, !m_addr_fetches_mutex, !mutexMsgProc); void StopThreads(); void StopNodes(); @@ -828,7 +714,7 @@ public: StopNodes(); }; - void Interrupt(); + void Interrupt() EXCLUSIVE_LOCKS_REQUIRED(!mutexMsgProc); bool GetNetworkActive() const { return fNetworkActive; }; bool GetUseAddrmanOutgoing() const { return m_use_addrman_outgoing; }; void SetNetworkActive(bool active); @@ -837,7 +723,7 @@ public: bool ForNode(NodeId id, std::function<bool(CNode* pnode)> func); - void PushMessage(CNode* pnode, CSerializedNetMsg&& msg); + void PushMessage(CNode* pnode, CSerializedNetMsg&& msg) EXCLUSIVE_LOCKS_REQUIRED(!m_total_bytes_sent_mutex); using NodeFn = std::function<void(CNode*)>; void ForEachNode(const NodeFn& func) @@ -880,10 +766,7 @@ public: void SetTryNewOutboundPeer(bool flag); bool GetTryNewOutboundPeer() const; - void StartExtraBlockRelayPeers() { - LogPrint(BCLog::NET, "net: enabling extra block-relay-only peers\n"); - m_start_extra_block_relay_peers = true; - } + void StartExtraBlockRelayPeers(); // Return the number of outbound peers we have in excess of our target (eg, // if we previously called SetTryNewOutboundPeer(true), and have since set @@ -895,9 +778,9 @@ public: // Count the number of block-relay-only peers we have over our limit. int GetExtraBlockRelayCount() const; - bool AddNode(const std::string& node); - bool RemoveAddedNode(const std::string& node); - std::vector<AddedNodeInfo> GetAddedNodeInfo() const; + bool AddNode(const std::string& node) EXCLUSIVE_LOCKS_REQUIRED(!m_added_nodes_mutex); + bool RemoveAddedNode(const std::string& node) EXCLUSIVE_LOCKS_REQUIRED(!m_added_nodes_mutex); + std::vector<AddedNodeInfo> GetAddedNodeInfo() const EXCLUSIVE_LOCKS_REQUIRED(!m_added_nodes_mutex); /** * Attempts to open a connection. Currently only used from tests. @@ -928,31 +811,29 @@ public: //! that peer during `net_processing.cpp:PushNodeVersion()`. ServiceFlags GetLocalServices() const; - uint64_t GetMaxOutboundTarget() const; + uint64_t GetMaxOutboundTarget() const EXCLUSIVE_LOCKS_REQUIRED(!m_total_bytes_sent_mutex); std::chrono::seconds GetMaxOutboundTimeframe() const; //! check if the outbound target is reached //! if param historicalBlockServingLimit is set true, the function will //! response true if the limit for serving historical blocks has been reached - bool OutboundTargetReached(bool historicalBlockServingLimit) const; + bool OutboundTargetReached(bool historicalBlockServingLimit) const EXCLUSIVE_LOCKS_REQUIRED(!m_total_bytes_sent_mutex); //! response the bytes left in the current max outbound cycle //! in case of no limit, it will always response 0 - uint64_t GetOutboundTargetBytesLeft() const; + uint64_t GetOutboundTargetBytesLeft() const EXCLUSIVE_LOCKS_REQUIRED(!m_total_bytes_sent_mutex); - //! returns the time left in the current max outbound cycle - //! in case of no limit, it will always return 0 - std::chrono::seconds GetMaxOutboundTimeLeftInCycle() const; + std::chrono::seconds GetMaxOutboundTimeLeftInCycle() const EXCLUSIVE_LOCKS_REQUIRED(!m_total_bytes_sent_mutex); uint64_t GetTotalBytesRecv() const; - uint64_t GetTotalBytesSent() const; + uint64_t GetTotalBytesSent() const EXCLUSIVE_LOCKS_REQUIRED(!m_total_bytes_sent_mutex); /** Get a unique deterministic randomizer. */ CSipHasher GetDeterministicRandomizer(uint64_t id) const; unsigned int GetReceiveFloodSize() const; - void WakeMessageHandler(); + void WakeMessageHandler() EXCLUSIVE_LOCKS_REQUIRED(!mutexMsgProc); /** Return true if we should disconnect the peer for failing an inactivity check. */ bool ShouldRunInactivityChecks(const CNode& node, std::chrono::seconds now) const; @@ -971,15 +852,19 @@ private: NetPermissionFlags m_permissions; }; + //! returns the time left in the current max outbound cycle + //! in case of no limit, it will always return 0 + std::chrono::seconds GetMaxOutboundTimeLeftInCycle_() const EXCLUSIVE_LOCKS_REQUIRED(m_total_bytes_sent_mutex); + bool BindListenPort(const CService& bindAddr, bilingual_str& strError, NetPermissionFlags permissions); bool Bind(const CService& addr, unsigned int flags, NetPermissionFlags permissions); bool InitBinds(const Options& options); - void ThreadOpenAddedConnections(); - void AddAddrFetch(const std::string& strDest); - void ProcessAddrFetch(); - void ThreadOpenConnections(std::vector<std::string> connect); - void ThreadMessageHandler(); + void ThreadOpenAddedConnections() EXCLUSIVE_LOCKS_REQUIRED(!m_added_nodes_mutex); + void AddAddrFetch(const std::string& strDest) EXCLUSIVE_LOCKS_REQUIRED(!m_addr_fetches_mutex); + void ProcessAddrFetch() EXCLUSIVE_LOCKS_REQUIRED(!m_addr_fetches_mutex); + void ThreadOpenConnections(std::vector<std::string> connect) EXCLUSIVE_LOCKS_REQUIRED(!m_addr_fetches_mutex, !m_added_nodes_mutex, !m_nodes_mutex); + void ThreadMessageHandler() EXCLUSIVE_LOCKS_REQUIRED(!mutexMsgProc); void ThreadI2PAcceptIncoming(); void AcceptConnection(const ListenSocket& hListenSocket); @@ -1004,55 +889,32 @@ 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. */ - void SocketHandler(); + void SocketHandler() EXCLUSIVE_LOCKS_REQUIRED(!m_total_bytes_sent_mutex, !mutexMsgProc); /** * 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(); - void ThreadDNSAddressSeed(); + void ThreadSocketHandler() EXCLUSIVE_LOCKS_REQUIRED(!m_total_bytes_sent_mutex, !mutexMsgProc); + void ThreadDNSAddressSeed() EXCLUSIVE_LOCKS_REQUIRED(!m_addr_fetches_mutex, !m_nodes_mutex); uint64_t CalculateKeyedNetGroup(const CAddress& ad) const; @@ -1080,7 +942,7 @@ private: // Network stats void RecordBytesRecv(uint64_t bytes); - void RecordBytesSent(uint64_t bytes); + void RecordBytesSent(uint64_t bytes) EXCLUSIVE_LOCKS_REQUIRED(!m_total_bytes_sent_mutex); /** * Return vector of current BLOCK_RELAY peers. @@ -1091,14 +953,14 @@ private: static bool NodeFullyConnected(const CNode* pnode); // Network usage totals - mutable RecursiveMutex cs_totalBytesSent; + mutable Mutex m_total_bytes_sent_mutex; std::atomic<uint64_t> nTotalBytesRecv{0}; - uint64_t nTotalBytesSent GUARDED_BY(cs_totalBytesSent) {0}; + uint64_t nTotalBytesSent GUARDED_BY(m_total_bytes_sent_mutex) {0}; // outbound limit & stats - uint64_t nMaxOutboundTotalBytesSentInCycle GUARDED_BY(cs_totalBytesSent) {0}; - std::chrono::seconds nMaxOutboundCycleStartTime GUARDED_BY(cs_totalBytesSent) {0}; - uint64_t nMaxOutboundLimit GUARDED_BY(cs_totalBytesSent); + uint64_t nMaxOutboundTotalBytesSentInCycle GUARDED_BY(m_total_bytes_sent_mutex) {0}; + std::chrono::seconds nMaxOutboundCycleStartTime GUARDED_BY(m_total_bytes_sent_mutex) {0}; + uint64_t nMaxOutboundLimit GUARDED_BY(m_total_bytes_sent_mutex); // P2P timeout in seconds std::chrono::seconds m_peer_connect_timeout; @@ -1114,6 +976,7 @@ private: std::atomic<bool> fNetworkActive{true}; bool fAddressesInitialized{false}; AddrMan& addrman; + const NetGroupManager& m_netgroupman; std::deque<std::string> m_addr_fetches GUARDED_BY(m_addr_fetches_mutex); Mutex m_addr_fetches_mutex; std::vector<std::string> m_added_nodes GUARDED_BY(m_added_nodes_mutex); @@ -1152,16 +1015,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; @@ -1293,54 +1154,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 fRelayTxes; - 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 aee1e4c30c..b39412158f 100644 --- a/src/net_processing.cpp +++ b/src/net_processing.cpp @@ -21,6 +21,7 @@ #include <node/blockstorage.h> #include <policy/fees.h> #include <policy/policy.h> +#include <policy/settings.h> #include <primitives/block.h> #include <primitives/transaction.h> #include <random.h> @@ -28,6 +29,7 @@ #include <scheduler.h> #include <streams.h> #include <sync.h> +#include <timedata.h> #include <tinyformat.h> #include <txmempool.h> #include <txorphanage.h> @@ -41,6 +43,7 @@ #include <algorithm> #include <atomic> #include <chrono> +#include <future> #include <memory> #include <optional> #include <typeinfo> @@ -59,6 +62,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. */ @@ -134,6 +139,8 @@ static const unsigned int NODE_NETWORK_LIMITED_MIN_BLOCKS = 288; static constexpr auto AVG_LOCAL_ADDRESS_BROADCAST_INTERVAL{24h}; /** Average delay between peer address broadcasts */ static constexpr auto AVG_ADDRESS_BROADCAST_INTERVAL{30s}; +/** Delay between rotating the peers we relay a particular address to */ +static constexpr auto ROTATE_ADDR_RELAY_DEST_INTERVAL{24h}; /** Average delay between trickled inventory transmissions for inbound peers. * Blocks and peers with NetPermissionFlags::NoBan permission bypass this. */ static constexpr auto INBOUND_INVENTORY_BROADCAST_INTERVAL{5s}; @@ -172,6 +179,8 @@ static constexpr double MAX_ADDR_RATE_PER_SECOND{0.1}; * based increments won't go above this, but the MAX_ADDR_TO_SEND increment following GETADDR * is exempt from this limit). */ static constexpr size_t MAX_ADDR_PROCESSING_TOKEN_BUCKET{MAX_ADDR_TO_SEND}; +/** The compactblocks version we support. See BIP 152. */ +static constexpr uint64_t CMPCTBLOCKS_VERSION{2}; // Internal stuff namespace { @@ -199,6 +208,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 */ @@ -232,6 +258,65 @@ struct Peer { /** Whether a ping has been requested by the user */ std::atomic<bool> m_ping_queued{false}; + /** Whether this peer relays txs via wtxid */ + std::atomic<bool> m_wtxid_relay{false}; + /** 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}; + /** Timestamp after which we will send the next BIP133 `feefilter` message + * to the peer. */ + std::chrono::microseconds m_next_send_feefilter{0}; + + struct TxRelay { + mutable RecursiveMutex m_bloom_filter_mutex; + /** Whether the peer wishes to receive transaction announcements. + * + * This is initially set based on the fRelay flag in the received + * `version` message. If initially set to false, it can only be flipped + * to true if we have offered the peer NODE_BLOOM services and it sends + * us a `filterload` or `filterclear` message. See BIP37. */ + bool m_relay_txs GUARDED_BY(m_bloom_filter_mutex){false}; + /** A bloom filter for which transactions to announce to the peer. See BIP37. */ + std::unique_ptr<CBloomFilter> m_bloom_filter PT_GUARDED_BY(m_bloom_filter_mutex) GUARDED_BY(m_bloom_filter_mutex){nullptr}; + + mutable RecursiveMutex m_tx_inventory_mutex; + /** A filter of all the txids and wtxids that the peer has announced to + * us or we have announced to the peer. We use this to avoid announcing + * the same txid/wtxid to a peer that already has the transaction. */ + CRollingBloomFilter m_tx_inventory_known_filter GUARDED_BY(m_tx_inventory_mutex){50000, 0.000001}; + /** Set of transaction ids we still have to announce (txid for + * 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; + /** 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}; + /** The last time a BIP35 `mempool` request was serviced. */ + 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}; + + /** Minimum fee rate with which to filter transaction announcements to this node. See BIP133. */ + std::atomic<CAmount> m_fee_filter_received{0}; + }; + + /* Initializes a TxRelay struct for this peer. Can be called at most once for a peer. */ + TxRelay* SetTxRelay() EXCLUSIVE_LOCKS_REQUIRED(!m_tx_relay_mutex) + { + LOCK(m_tx_relay_mutex); + Assume(!m_tx_relay); + m_tx_relay = std::make_unique<Peer::TxRelay>(); + return m_tx_relay.get(); + }; + + 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; /** Probabilistic filter to track recent addr messages relayed with this @@ -285,71 +370,184 @@ 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{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); - explicit Peer(NodeId id) - : m_id(id) + /** Time of the last getheaders message to this peer */ + NodeClock::time_point m_last_getheaders_timestamp{}; + + explicit Peer(NodeId id, ServiceFlags our_services) + : m_id{id} + , m_our_services{our_services} {} + +private: + Mutex m_tx_relay_mutex; + + /** Transaction relay data. Will be a nullptr if we're not relaying + * transactions with this peer (e.g. if it's a block-relay-only peer or + * the peer has sent us fRelay=false with bloom filters disabled). */ + std::unique_ptr<TxRelay> m_tx_relay GUARDED_BY(m_tx_relay_mutex); }; using PeerRef = std::shared_ptr<Peer>; +/** + * Maintain validation-specific state about nodes, protected by cs_main, instead + * by CNode's own locks. This simplifies asynchronous operation, where + * processing of incoming data is done after the ProcessMessage call returns, + * and we're no longer holding the node's locks. + */ +struct CNodeState { + //! The best known block we know this peer has announced. + const CBlockIndex* pindexBestKnownBlock{nullptr}; + //! The hash of the last unknown block this peer has announced. + uint256 hashLastUnknownBlock{}; + //! The last full block we both have. + const CBlockIndex* pindexLastCommonBlock{nullptr}; + //! The best header we have sent our peer. + const CBlockIndex* pindexBestHeaderSent{nullptr}; + //! Length of current-streak of unconnecting headers announcements + int nUnconnectingHeaders{0}; + //! Whether we've started headers synchronization with this peer. + bool fSyncStarted{false}; + //! When to potentially disconnect peer for stalling headers download + std::chrono::microseconds m_headers_sync_timeout{0us}; + //! Since when we're stalling block download progress (in microseconds), or 0. + std::chrono::microseconds m_stalling_since{0us}; + std::list<QueuedBlock> vBlocksInFlight; + //! When the first entry in vBlocksInFlight started downloading. Don't care when vBlocksInFlight is empty. + std::chrono::microseconds m_downloading_since{0us}; + int nBlocksInFlight{0}; + //! Whether we consider this a preferred download peer. + bool fPreferredDownload{false}; + //! Whether this peer wants invs or headers (when possible) for block announcements. + bool fPreferHeaders{false}; + /** Whether this peer wants invs or cmpctblocks (when possible) for block announcements. */ + bool m_requested_hb_cmpctblocks{false}; + /** Whether this peer will send us cmpctblocks if we request them. */ + bool m_provides_cmpctblocks{false}; + + /** State used to enforce CHAIN_SYNC_TIMEOUT and EXTRA_PEER_CHECK_INTERVAL logic. + * + * Both are only in effect for outbound, non-manual, non-protected connections. + * Any peer protected (m_protect = true) is not chosen for eviction. A peer is + * marked as protected if all of these are true: + * - its connection type is IsBlockOnlyConn() == false + * - it gave us a valid connecting header + * - we haven't reached MAX_OUTBOUND_PEERS_TO_PROTECT_FROM_DISCONNECT yet + * - its chain tip has at least as much work as ours + * + * CHAIN_SYNC_TIMEOUT: if a peer's best known block has less work than our tip, + * set a timeout CHAIN_SYNC_TIMEOUT in the future: + * - If at timeout their best known block now has more work than our tip + * when the timeout was set, then either reset the timeout or clear it + * (after comparing against our current tip's work) + * - If at timeout their best known block still has less work than our + * tip did when the timeout was set, then send a getheaders message, + * and set a shorter timeout, HEADERS_RESPONSE_TIME seconds in future. + * If their best known block is still behind when that new timeout is + * reached, disconnect. + * + * EXTRA_PEER_CHECK_INTERVAL: after each interval, if we have too many outbound peers, + * drop the outbound one that least recently announced us a new block. + */ + struct ChainSyncTimeoutState { + //! A timeout used for checking whether our peer has sufficiently synced + std::chrono::seconds m_timeout{0s}; + //! A header with the work we require on our peer's chain + const CBlockIndex* m_work_header{nullptr}; + //! After timeout is reached, set to true after sending getheaders + bool m_sent_getheaders{false}; + //! Whether this peer is protected from disconnection due to a bad/slow chain + bool m_protect{false}; + }; + + ChainSyncTimeoutState m_chain_sync; + + //! Time of last new block announcement + int64_t m_last_block_announcement{0}; + + //! Whether this peer is an inbound connection + const bool m_is_inbound; + + //! A rolling bloom filter of all announced tx CInvs to this peer. + CRollingBloomFilter m_recently_announced_invs = CRollingBloomFilter{INVENTORY_MAX_RECENT_RELAY, 0.000001}; + + CNodeState(bool is_inbound) : m_is_inbound(is_inbound) {} +}; + class PeerManagerImpl final : public PeerManager { public: - PeerManagerImpl(const CChainParams& chainparams, CConnman& connman, AddrMan& addrman, + PeerManagerImpl(CConnman& connman, AddrMan& addrman, BanMan* banman, ChainstateManager& chainman, CTxMemPool& pool, bool ignore_incoming_txs); /** Overridden from CValidationInterface. */ - void BlockConnected(const std::shared_ptr<const CBlock>& pblock, const CBlockIndex* pindexConnected) override; - void BlockDisconnected(const std::shared_ptr<const CBlock> &block, const CBlockIndex* pindex) override; - void UpdatedBlockTip(const CBlockIndex *pindexNew, const CBlockIndex *pindexFork, bool fInitialDownload) override; - void BlockChecked(const CBlock& block, const BlockValidationState& state) override; - void NewPoWValidBlock(const CBlockIndex *pindex, const std::shared_ptr<const CBlock>& pblock) override; + void BlockConnected(const std::shared_ptr<const CBlock>& pblock, const CBlockIndex* pindexConnected) override + EXCLUSIVE_LOCKS_REQUIRED(!m_recent_confirmed_transactions_mutex); + void BlockDisconnected(const std::shared_ptr<const CBlock> &block, const CBlockIndex* pindex) override + EXCLUSIVE_LOCKS_REQUIRED(!m_recent_confirmed_transactions_mutex); + void UpdatedBlockTip(const CBlockIndex *pindexNew, const CBlockIndex *pindexFork, bool fInitialDownload) override + EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex); + void BlockChecked(const CBlock& block, const BlockValidationState& state) override + EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex); + void NewPoWValidBlock(const CBlockIndex *pindex, const std::shared_ptr<const CBlock>& pblock) override + EXCLUSIVE_LOCKS_REQUIRED(!m_most_recent_block_mutex); /** Implement NetEventsInterface */ - void InitializeNode(CNode* pnode) override; - void FinalizeNode(const CNode& node) override; - bool ProcessMessages(CNode* pfrom, std::atomic<bool>& interrupt) override; - bool SendMessages(CNode* pto) override EXCLUSIVE_LOCKS_REQUIRED(pto->cs_sendProcessing); + 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); + 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); /** Implement PeerManager */ void StartScheduledTasks(CScheduler& scheduler) override; void CheckForStaleTipAndEvictPeers() override; - std::optional<std::string> FetchBlock(NodeId peer_id, const CBlockIndex& block_index) override; - bool GetNodeStateStats(NodeId nodeid, CNodeStateStats& stats) const 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; - void RelayTransaction(const uint256& txid, const uint256& wtxid) override; + 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; + 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; + 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); + void UpdateLastBlockAnnounceTime(NodeId node, int64_t time_in_seconds) override; private: - void _RelayTransaction(const uint256& txid, const uint256& wtxid) - EXCLUSIVE_LOCKS_REQUIRED(cs_main); - /** 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); /** 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); /** Retrieve unbroadcast transactions from the mempool and reattempt sending to peers */ - void ReattemptInitialBroadcast(CScheduler& scheduler); + void ReattemptInitialBroadcast(CScheduler& scheduler) EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex); /** Get a shared pointer to the Peer object. * May return an empty shared_ptr if the Peer object can't be found. */ - PeerRef GetPeerRef(NodeId id) const; + PeerRef GetPeerRef(NodeId id) const EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex); /** Get a shared pointer to the Peer object and remove it from m_peer_map. * May return an empty shared_ptr if the Peer object can't be found. */ - PeerRef RemovePeer(NodeId id); + 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 @@ -362,14 +560,16 @@ private: * @return Returns true if the peer was punished (probably disconnected) */ bool MaybePunishNodeForBlock(NodeId nodeid, const BlockValidationState& state, - bool via_compact_block, const std::string& message = ""); + bool via_compact_block, const std::string& message = "") + EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex); /** * Potentially disconnect and discourage a node based on the contents of a TxValidationState object * * @return Returns true if the peer was punished (probably disconnected) */ - bool MaybePunishNodeForTx(NodeId nodeid, const TxValidationState& state, const std::string& message = ""); + bool MaybePunishNodeForTx(NodeId nodeid, const TxValidationState& state, const std::string& message = "") + EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex); /** Maybe disconnect a peer and discourage future connections from its address. * @@ -379,13 +579,31 @@ private: */ bool MaybeDiscourageAndDisconnect(CNode& pnode, Peer& peer); - void ProcessOrphanTx(std::set<uint256>& orphan_work_set) EXCLUSIVE_LOCKS_REQUIRED(cs_main, g_cs_orphans); + 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, + void ProcessHeadersMessage(CNode& pfrom, Peer& peer, const std::vector<CBlockHeader>& headers, - bool via_compact_block); + bool via_compact_block) + EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex); + /** Various helpers for headers processing, invoked by ProcessHeadersMessage() */ + /** 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); + /** Return true if the headers connect to each other, false otherwise */ + bool CheckHeadersAreContinuous(const std::vector<CBlockHeader>& headers) const; + /** 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); + /** 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); + 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 @@ -394,7 +612,7 @@ private: EXCLUSIVE_LOCKS_REQUIRED(::cs_main); /** Send a version message to a peer */ - void PushNodeVersion(CNode& pnode); + void PushNodeVersion(CNode& pnode, const Peer& peer); /** Send a ping message every PING_INTERVAL or if requested via RPC. May * mark the peer to be disconnected if a ping has timed out. @@ -412,10 +630,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); + void RelayAddress(NodeId originator, const CAddress& addr, bool fReachable) EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex); /** Send `feefilter` message. */ - void MaybeSendFeefilter(CNode& node, std::chrono::microseconds current_time) EXCLUSIVE_LOCKS_REQUIRED(cs_main); + void MaybeSendFeefilter(CNode& node, Peer& peer, std::chrono::microseconds current_time); const CChainParams& m_chainparams; CConnman& m_connman; @@ -432,9 +650,11 @@ private: /** Next time to check for stale tip */ std::chrono::seconds m_stale_tip_check_time{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}; @@ -450,11 +670,24 @@ private: */ std::map<NodeId, PeerRef> m_peer_map GUARDED_BY(m_peer_mutex); + /** Map maintaining per-node state. */ + std::map<NodeId, CNodeState> m_node_states GUARDED_BY(cs_main); + + /** Get a pointer to a const CNodeState, used when not mutating the CNodeState object. */ + const CNodeState* State(NodeId pnode) const EXCLUSIVE_LOCKS_REQUIRED(cs_main); + /** Get a pointer to a mutable CNodeState. */ + CNodeState* State(NodeId pnode) 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{}; + /** * Sources of received blocks, saved to be able punish them when processing * happens afterwards. @@ -464,12 +697,16 @@ private: std::map<uint256, std::pair<NodeId, bool>> mapBlockSource GUARDED_BY(cs_main); /** Number of peers with wtxid relay. */ - int m_wtxid_relay_peers GUARDED_BY(cs_main) = 0; + std::atomic<int> m_wtxid_relay_peers{0}; /** Number of outbound peers with m_chain_sync.m_protect. */ int m_outbound_peers_with_protect_from_disconnect GUARDED_BY(cs_main) = 0; - bool AlreadyHaveTx(const GenTxid& gtxid) EXCLUSIVE_LOCKS_REQUIRED(cs_main); + /** Number of preferable block download peers. */ + int m_num_preferred_download_peers GUARDED_BY(cs_main){0}; + + bool AlreadyHaveTx(const GenTxid& gtxid) + EXCLUSIVE_LOCKS_REQUIRED(cs_main, !m_recent_confirmed_transactions_mutex); /** * Filter for transactions that were recently rejected by the mempool. @@ -535,6 +772,16 @@ private: std::chrono::microseconds NextInvToInbounds(std::chrono::microseconds now, std::chrono::seconds average_interval); + + // All of the following cache a recent block, and are protected by m_most_recent_block_mutex + Mutex m_most_recent_block_mutex; + std::shared_ptr<const CBlock> m_most_recent_block GUARDED_BY(m_most_recent_block_mutex); + 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); + + /** Height of the highest block announced using BIP 152 high-bandwidth mode. */ + int m_highest_fast_announce{0}; + /** Have we requested this block from a peer */ bool IsBlockRequested(const uint256& hash) EXCLUSIVE_LOCKS_REQUIRED(cs_main); @@ -555,7 +802,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); @@ -565,7 +812,8 @@ private: /** Determine whether or not a peer can request a transaction, and return it (or nullptr if not found or not allowed). */ CTransactionRef FindTxForGetData(const CNode& peer, const GenTxid& gtxid, const std::chrono::seconds mempool_req, const std::chrono::seconds now) LOCKS_EXCLUDED(cs_main); - void ProcessGetData(CNode& pfrom, Peer& peer, const std::atomic<bool>& interruptMsgProc) EXCLUSIVE_LOCKS_REQUIRED(peer.m_getdata_requests_mutex) LOCKS_EXCLUDED(::cs_main); + void ProcessGetData(CNode& pfrom, Peer& peer, const std::atomic<bool>& interruptMsgProc) + 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); @@ -616,13 +864,15 @@ private: */ bool BlockRequestAllowed(const CBlockIndex* pindex) EXCLUSIVE_LOCKS_REQUIRED(cs_main); bool AlreadyHaveBlock(const uint256& block_hash) EXCLUSIVE_LOCKS_REQUIRED(cs_main); - void ProcessGetBlockData(CNode& pfrom, Peer& peer, const CInv& inv); + void ProcessGetBlockData(CNode& pfrom, Peer& peer, const CInv& inv) + EXCLUSIVE_LOCKS_REQUIRED(!m_most_recent_block_mutex); /** * Validation logic for compact filters request handling. * * 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 @@ -632,7 +882,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, @@ -643,30 +893,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. @@ -676,125 +929,20 @@ private: */ bool SetupAddressRelay(const CNode& node, Peer& peer); }; -} // namespace - -namespace { - /** Number of preferable block download peers. */ - int nPreferredDownload GUARDED_BY(cs_main) = 0; -} // namespace -namespace { -/** - * Maintain validation-specific state about nodes, protected by cs_main, instead - * by CNode's own locks. This simplifies asynchronous operation, where - * processing of incoming data is done after the ProcessMessage call returns, - * and we're no longer holding the node's locks. - */ -struct CNodeState { - //! The best known block we know this peer has announced. - const CBlockIndex* pindexBestKnownBlock{nullptr}; - //! The hash of the last unknown block this peer has announced. - uint256 hashLastUnknownBlock{}; - //! The last full block we both have. - const CBlockIndex* pindexLastCommonBlock{nullptr}; - //! The best header we have sent our peer. - const CBlockIndex* pindexBestHeaderSent{nullptr}; - //! Length of current-streak of unconnecting headers announcements - int nUnconnectingHeaders{0}; - //! Whether we've started headers synchronization with this peer. - bool fSyncStarted{false}; - //! When to potentially disconnect peer for stalling headers download - std::chrono::microseconds m_headers_sync_timeout{0us}; - //! Since when we're stalling block download progress (in microseconds), or 0. - std::chrono::microseconds m_stalling_since{0us}; - std::list<QueuedBlock> vBlocksInFlight; - //! When the first entry in vBlocksInFlight started downloading. Don't care when vBlocksInFlight is empty. - std::chrono::microseconds m_downloading_since{0us}; - int nBlocksInFlight{0}; - //! Whether we consider this a preferred download peer. - bool fPreferredDownload{false}; - //! Whether this peer wants invs or headers (when possible) for block announcements. - bool fPreferHeaders{false}; - //! Whether this peer wants invs or cmpctblocks (when possible) for block announcements. - bool fPreferHeaderAndIDs{false}; - /** - * Whether this peer will send us cmpctblocks if we request them. - * This is not used to gate request logic, as we really only care about fSupportsDesiredCmpctVersion, - * but is used as a flag to "lock in" the version of compact blocks (fWantsCmpctWitness) we send. - */ - bool fProvidesHeaderAndIDs{false}; - //! Whether this peer can give us witnesses - bool fHaveWitness{false}; - //! Whether this peer wants witnesses in cmpctblocks/blocktxns - bool fWantsCmpctWitness{false}; - /** - * If we've announced NODE_WITNESS to this peer: whether the peer sends witnesses in cmpctblocks/blocktxns, - * otherwise: whether this peer sends non-witnesses in cmpctblocks/blocktxns. - */ - bool fSupportsDesiredCmpctVersion{false}; - - /** State used to enforce CHAIN_SYNC_TIMEOUT and EXTRA_PEER_CHECK_INTERVAL logic. - * - * Both are only in effect for outbound, non-manual, non-protected connections. - * Any peer protected (m_protect = true) is not chosen for eviction. A peer is - * marked as protected if all of these are true: - * - its connection type is IsBlockOnlyConn() == false - * - it gave us a valid connecting header - * - we haven't reached MAX_OUTBOUND_PEERS_TO_PROTECT_FROM_DISCONNECT yet - * - its chain tip has at least as much work as ours - * - * CHAIN_SYNC_TIMEOUT: if a peer's best known block has less work than our tip, - * set a timeout CHAIN_SYNC_TIMEOUT in the future: - * - If at timeout their best known block now has more work than our tip - * when the timeout was set, then either reset the timeout or clear it - * (after comparing against our current tip's work) - * - If at timeout their best known block still has less work than our - * tip did when the timeout was set, then send a getheaders message, - * and set a shorter timeout, HEADERS_RESPONSE_TIME seconds in future. - * If their best known block is still behind when that new timeout is - * reached, disconnect. - * - * EXTRA_PEER_CHECK_INTERVAL: after each interval, if we have too many outbound peers, - * drop the outbound one that least recently announced us a new block. - */ - struct ChainSyncTimeoutState { - //! A timeout used for checking whether our peer has sufficiently synced - std::chrono::seconds m_timeout{0s}; - //! A header with the work we require on our peer's chain - const CBlockIndex* m_work_header{nullptr}; - //! After timeout is reached, set to true after sending getheaders - bool m_sent_getheaders{false}; - //! Whether this peer is protected from disconnection due to a bad/slow chain - bool m_protect{false}; - }; - - ChainSyncTimeoutState m_chain_sync; - - //! Time of last new block announcement - int64_t m_last_block_announcement{0}; - - //! Whether this peer is an inbound connection - const bool m_is_inbound; - - //! A rolling bloom filter of all announced tx CInvs to this peer. - CRollingBloomFilter m_recently_announced_invs = CRollingBloomFilter{INVENTORY_MAX_RECENT_RELAY, 0.000001}; - - //! Whether this peer relays txs via wtxid - bool m_wtxid_relay{false}; - - CNodeState(bool is_inbound) : m_is_inbound(is_inbound) {} -}; - -/** Map maintaining per-node state. */ -static std::map<NodeId, CNodeState> mapNodeState GUARDED_BY(cs_main); - -static CNodeState *State(NodeId pnode) EXCLUSIVE_LOCKS_REQUIRED(cs_main) { - std::map<NodeId, CNodeState>::iterator it = mapNodeState.find(pnode); - if (it == mapNodeState.end()) +const CNodeState* PeerManagerImpl::State(NodeId pnode) const EXCLUSIVE_LOCKS_REQUIRED(cs_main) +{ + std::map<NodeId, CNodeState>::const_iterator it = m_node_states.find(pnode); + if (it == m_node_states.end()) return nullptr; return &it->second; } +CNodeState* PeerManagerImpl::State(NodeId pnode) EXCLUSIVE_LOCKS_REQUIRED(cs_main) +{ + return const_cast<CNodeState*>(std::as_const(*this).State(pnode)); +} + /** * Whether the peer supports the address. For example, a peer that does not * implement BIP155 cannot receive Tor v3 addresses because it requires @@ -826,14 +974,33 @@ static void PushAddress(Peer& peer, const CAddress& addr, FastRandomContext& ins } } -static void UpdatePreferredDownload(const CNode& node, CNodeState* state) EXCLUSIVE_LOCKS_REQUIRED(cs_main) +static void AddKnownTx(Peer& peer, const uint256& hash) { - nPreferredDownload -= state->fPreferredDownload; + auto tx_relay = peer.GetTxRelay(); + if (!tx_relay) return; - // Whether this node should be marked as a preferred download node. - state->fPreferredDownload = (!node.IsInboundConn() || node.HasPermission(NetPermissionFlags::NoBan)) && !node.IsAddrFetchConn() && !node.fClient; + LOCK(tx_relay->m_tx_inventory_mutex); + tx_relay->m_tx_inventory_known_filter.insert(hash); +} - nPreferredDownload += state->fPreferredDownload; +/** 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, @@ -918,60 +1085,58 @@ 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; CNodeState* nodestate = State(nodeid); - if (!nodestate || !nodestate->fSupportsDesiredCmpctVersion) { - // Never ask from peers who can't provide witnesses. + if (!nodestate || !nodestate->m_provides_cmpctblocks) { + // Don't request compact blocks if the peer has not signalled support return; } - if (nodestate->fProvidesHeaderAndIDs) { - int num_outbound_hb_peers = 0; - for (std::list<NodeId>::iterator it = lNodesAnnouncingHeaderAndIDs.begin(); it != lNodesAnnouncingHeaderAndIDs.end(); it++) { - if (*it == nodeid) { - lNodesAnnouncingHeaderAndIDs.erase(it); - lNodesAnnouncingHeaderAndIDs.push_back(nodeid); - return; - } - CNodeState *state = State(*it); - if (state != nullptr && !state->m_is_inbound) ++num_outbound_hb_peers; - } - if (nodestate->m_is_inbound) { - // If we're adding an inbound HB peer, make sure we're not removing - // our last outbound HB peer in the process. - if (lNodesAnnouncingHeaderAndIDs.size() >= 3 && num_outbound_hb_peers == 1) { - CNodeState *remove_node = State(lNodesAnnouncingHeaderAndIDs.front()); - if (remove_node != nullptr && !remove_node->m_is_inbound) { - // Put the HB outbound peer in the second slot, so that it - // doesn't get removed. - std::swap(lNodesAnnouncingHeaderAndIDs.front(), *std::next(lNodesAnnouncingHeaderAndIDs.begin())); - } - } + + int num_outbound_hb_peers = 0; + for (std::list<NodeId>::iterator it = lNodesAnnouncingHeaderAndIDs.begin(); it != lNodesAnnouncingHeaderAndIDs.end(); it++) { + if (*it == nodeid) { + lNodesAnnouncingHeaderAndIDs.erase(it); + lNodesAnnouncingHeaderAndIDs.push_back(nodeid); + return; } - m_connman.ForNode(nodeid, [this](CNode* pfrom) EXCLUSIVE_LOCKS_REQUIRED(::cs_main) { - AssertLockHeld(::cs_main); - uint64_t nCMPCTBLOCKVersion = 2; - if (lNodesAnnouncingHeaderAndIDs.size() >= 3) { - // As per BIP152, we only get 3 of our peers to announce - // blocks using compact encodings. - m_connman.ForNode(lNodesAnnouncingHeaderAndIDs.front(), [this, nCMPCTBLOCKVersion](CNode* pnodeStop){ - m_connman.PushMessage(pnodeStop, CNetMsgMaker(pnodeStop->GetCommonVersion()).Make(NetMsgType::SENDCMPCT, /*fAnnounceUsingCMPCTBLOCK=*/false, nCMPCTBLOCKVersion)); - // save BIP152 bandwidth state: we select peer to be low-bandwidth - pnodeStop->m_bip152_highbandwidth_to = false; - return true; - }); - lNodesAnnouncingHeaderAndIDs.pop_front(); + CNodeState *state = State(*it); + if (state != nullptr && !state->m_is_inbound) ++num_outbound_hb_peers; + } + if (nodestate->m_is_inbound) { + // If we're adding an inbound HB peer, make sure we're not removing + // our last outbound HB peer in the process. + if (lNodesAnnouncingHeaderAndIDs.size() >= 3 && num_outbound_hb_peers == 1) { + CNodeState *remove_node = State(lNodesAnnouncingHeaderAndIDs.front()); + if (remove_node != nullptr && !remove_node->m_is_inbound) { + // Put the HB outbound peer in the second slot, so that it + // doesn't get removed. + std::swap(lNodesAnnouncingHeaderAndIDs.front(), *std::next(lNodesAnnouncingHeaderAndIDs.begin())); } - m_connman.PushMessage(pfrom, CNetMsgMaker(pfrom->GetCommonVersion()).Make(NetMsgType::SENDCMPCT, /*fAnnounceUsingCMPCTBLOCK=*/true, nCMPCTBLOCKVersion)); - // save BIP152 bandwidth state: we select peer to be high-bandwidth - pfrom->m_bip152_highbandwidth_to = true; - lNodesAnnouncingHeaderAndIDs.push_back(pfrom->GetId()); - return true; - }); + } } + m_connman.ForNode(nodeid, [this](CNode* pfrom) EXCLUSIVE_LOCKS_REQUIRED(::cs_main) { + AssertLockHeld(::cs_main); + if (lNodesAnnouncingHeaderAndIDs.size() >= 3) { + // As per BIP152, we only get 3 of our peers to announce + // blocks using compact encodings. + m_connman.ForNode(lNodesAnnouncingHeaderAndIDs.front(), [this](CNode* pnodeStop){ + m_connman.PushMessage(pnodeStop, CNetMsgMaker(pnodeStop->GetCommonVersion()).Make(NetMsgType::SENDCMPCT, /*high_bandwidth=*/false, /*version=*/CMPCTBLOCKS_VERSION)); + // save BIP152 bandwidth state: we select peer to be low-bandwidth + pnodeStop->m_bip152_highbandwidth_to = false; + return true; + }); + lNodesAnnouncingHeaderAndIDs.pop_front(); + } + m_connman.PushMessage(pfrom, CNetMsgMaker(pfrom->GetCommonVersion()).Make(NetMsgType::SENDCMPCT, /*high_bandwidth=*/true, /*version=*/CMPCTBLOCKS_VERSION)); + // save BIP152 bandwidth state: we select peer to be high-bandwidth + pfrom->m_bip152_highbandwidth_to = true; + lNodesAnnouncingHeaderAndIDs.push_back(pfrom->GetId()); + return true; + }); } bool PeerManagerImpl::TipMayBeStale() @@ -1031,17 +1196,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. @@ -1060,7 +1225,6 @@ void PeerManagerImpl::FindNextBlocksToDownload(NodeId nodeid, unsigned int count if (state->pindexLastCommonBlock == state->pindexBestKnownBlock) return; - const Consensus::Params& consensusParams = m_chainparams.GetConsensus(); std::vector<const CBlockIndex*> vToFetch; const CBlockIndex *pindexWalk = state->pindexLastCommonBlock; // Never fetch further than the best block we know the peer has, or more than BLOCK_DOWNLOAD_WINDOW + 1 beyond the last @@ -1090,7 +1254,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, consensusParams, 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; } @@ -1101,7 +1265,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; } @@ -1121,12 +1285,9 @@ void PeerManagerImpl::FindNextBlocksToDownload(NodeId nodeid, unsigned int count } // namespace -void PeerManagerImpl::PushNodeVersion(CNode& pnode) +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}; @@ -1136,7 +1297,7 @@ void PeerManagerImpl::PushNodeVersion(CNode& pnode) CService addr_you = addr.IsRoutable() && !IsProxy(addr) && addr.IsAddrV1Compatible() ? addr : CService(); uint64_t your_services{addr.nServices}; - const bool tx_relay = !m_ignore_incoming_txs && pnode.m_tx_relay != nullptr && !pnode.IsFeelerConn(); + const bool tx_relay = !m_ignore_incoming_txs && !pnode.IsBlockOnlyConn() && !pnode.IsFeelerConn(); m_connman.PushMessage(&pnode, CNetMsgMaker(INIT_PROTO_VERSION).Make(NetMsgType::VERSION, PROTOCOL_VERSION, my_services, nTime, your_services, addr_you, // Together the pre-version-31402 serialization of CAddress "addrYou" (without nTime) my_services, CService(), // Together the pre-version-31402 serialization of CAddress "addrMe" (without nTime) @@ -1176,30 +1337,28 @@ void PeerManagerImpl::AddTxAnnouncement(const CNode& node, const GenTxid& gtxid, m_txrequest.ReceivedInv(nodeid, gtxid, preferred, current_time + delay); } -// This function is used for testing the stale tip eviction logic, see -// denialofservice_tests.cpp -void UpdateLastBlockAnnounceTime(NodeId node, int64_t time_in_seconds) +void PeerManagerImpl::UpdateLastBlockAnnounceTime(NodeId node, int64_t time_in_seconds) { LOCK(cs_main); CNodeState *state = State(node); 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); - mapNodeState.emplace_hint(mapNodeState.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, our_services); { - PeerRef peer = std::make_shared<Peer>(nodeid); LOCK(m_peer_mutex); - m_peer_map.emplace_hint(m_peer_map.end(), nodeid, std::move(peer)); + m_peer_map.emplace_hint(m_peer_map.end(), nodeid, peer); } - if (!pnode->IsInboundConn()) { - PushNodeVersion(*pnode); + if (!node.IsInboundConn()) { + PushNodeVersion(node, *peer); } } @@ -1211,8 +1370,7 @@ void PeerManagerImpl::ReattemptInitialBroadcast(CScheduler& scheduler) CTransactionRef tx = m_mempool.get(txid); if (tx != nullptr) { - LOCK(cs_main); - _RelayTransaction(txid, tx->GetWitnessHash()); + RelayTransaction(txid, tx->GetWitnessHash()); } else { m_mempool.RemoveUnbroadcastTx(txid, true); } @@ -1239,6 +1397,8 @@ void PeerManagerImpl::FinalizeNode(const CNode& node) PeerRef peer = RemovePeer(nodeid); assert(peer != nullptr); misbehavior = WITH_LOCK(peer->m_misbehavior_mutex, return peer->m_misbehavior_score); + m_wtxid_relay_peers -= peer->m_wtxid_relay; + assert(m_wtxid_relay_peers >= 0); } CNodeState *state = State(nodeid); assert(state != nullptr); @@ -1251,20 +1411,18 @@ void PeerManagerImpl::FinalizeNode(const CNode& node) } WITH_LOCK(g_cs_orphans, m_orphanage.EraseForPeer(nodeid)); m_txrequest.DisconnectedPeer(nodeid); - nPreferredDownload -= state->fPreferredDownload; + m_num_preferred_download_peers -= state->fPreferredDownload; m_peers_downloading_from -= (state->nBlocksInFlight != 0); assert(m_peers_downloading_from >= 0); m_outbound_peers_with_protect_from_disconnect -= state->m_chain_sync.m_protect; assert(m_outbound_peers_with_protect_from_disconnect >= 0); - m_wtxid_relay_peers -= state->m_wtxid_relay; - assert(m_wtxid_relay_peers >= 0); - mapNodeState.erase(nodeid); + m_node_states.erase(nodeid); - if (mapNodeState.empty()) { + if (m_node_states.empty()) { // Do a consistency check after the last peer is removed. assert(mapBlocksInFlight.empty()); - assert(nPreferredDownload == 0); + assert(m_num_preferred_download_peers == 0); assert(m_peers_downloading_from == 0); assert(m_outbound_peers_with_protect_from_disconnect == 0); assert(m_wtxid_relay_peers == 0); @@ -1305,7 +1463,7 @@ bool PeerManagerImpl::GetNodeStateStats(NodeId nodeid, CNodeStateStats& stats) c { { LOCK(cs_main); - CNodeState* state = State(nodeid); + const CNodeState* state = State(nodeid); if (state == nullptr) return false; stats.nSyncHeight = state->pindexBestKnownBlock ? state->pindexBestKnownBlock->nHeight : -1; @@ -1318,6 +1476,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. @@ -1330,6 +1489,14 @@ bool PeerManagerImpl::GetNodeStateStats(NodeId nodeid, CNodeStateStats& stats) c ping_wait = GetTime<std::chrono::microseconds>() - peer->m_ping_start.load(); } + if (auto tx_relay = peer->GetTxRelay(); tx_relay != nullptr) { + stats.m_relay_txs = WITH_LOCK(tx_relay->m_bloom_filter_mutex, return tx_relay->m_relay_txs); + stats.m_fee_filter_received = tx_relay->m_fee_filter_received.load(); + } else { + stats.m_relay_txs = false; + stats.m_fee_filter_received = 0; + } + stats.m_ping_wait = ping_wait; stats.m_addr_processed = peer->m_addr_processed.load(); stats.m_addr_rate_limited = peer->m_addr_rate_limited.load(); @@ -1349,33 +1516,31 @@ 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; @@ -1383,7 +1548,7 @@ bool PeerManagerImpl::MaybePunishNodeForBlock(NodeId nodeid, const BlockValidati 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; @@ -1398,7 +1563,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; @@ -1406,12 +1571,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: @@ -1425,12 +1590,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: @@ -1455,9 +1621,9 @@ bool PeerManagerImpl::BlockRequestAllowed(const CBlockIndex* pindex) { AssertLockHeld(cs_main); if (m_chainman.ActiveChain().Contains(pindex)) return true; - return pindex->IsValid(BLOCK_VALID_SCRIPTS) && (pindexBestHeader != nullptr) && - (pindexBestHeader->GetBlockTime() - pindex->GetBlockTime() < STALE_RELAY_AGE_LIMIT) && - (GetBlockProofEquivalentTime(*pindexBestHeader, *pindex, *pindexBestHeader, m_chainparams.GetConsensus()) < STALE_RELAY_AGE_LIMIT); + return pindex->IsValid(BLOCK_VALID_SCRIPTS) && (m_chainman.m_best_header != nullptr) && + (m_chainman.m_best_header->GetBlockTime() - pindex->GetBlockTime() < STALE_RELAY_AGE_LIMIT) && + (GetBlockProofEquivalentTime(*m_chainman.m_best_header, *pindex, *m_chainman.m_best_header, m_chainparams.GetConsensus()) < STALE_RELAY_AGE_LIMIT); } std::optional<std::string> PeerManagerImpl::FetchBlock(NodeId peer_id, const CBlockIndex& block_index) @@ -1465,12 +1631,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 @@ -1495,17 +1663,17 @@ std::optional<std::string> PeerManagerImpl::FetchBlock(NodeId peer_id, const CBl return std::nullopt; } -std::unique_ptr<PeerManager> PeerManager::make(const CChainParams& chainparams, CConnman& connman, AddrMan& addrman, +std::unique_ptr<PeerManager> PeerManager::make(CConnman& connman, AddrMan& addrman, BanMan* banman, ChainstateManager& chainman, CTxMemPool& pool, bool ignore_incoming_txs) { - return std::make_unique<PeerManagerImpl>(chainparams, connman, addrman, banman, chainman, pool, ignore_incoming_txs); + return std::make_unique<PeerManagerImpl>(connman, addrman, banman, chainman, pool, ignore_incoming_txs); } -PeerManagerImpl::PeerManagerImpl(const CChainParams& chainparams, CConnman& connman, AddrMan& addrman, +PeerManagerImpl::PeerManagerImpl(CConnman& connman, AddrMan& addrman, BanMan* banman, ChainstateManager& chainman, CTxMemPool& pool, bool ignore_incoming_txs) - : m_chainparams(chainparams), + : m_chainparams(chainman.GetParams()), m_connman(connman), m_addrman(addrman), m_banman(banman), @@ -1571,56 +1739,50 @@ void PeerManagerImpl::BlockDisconnected(const std::shared_ptr<const CBlock> &blo m_recent_confirmed_transactions.reset(); } -// All of the following cache a recent block, and are protected by cs_most_recent_block -static RecursiveMutex cs_most_recent_block; -static std::shared_ptr<const CBlock> most_recent_block GUARDED_BY(cs_most_recent_block); -static std::shared_ptr<const CBlockHeaderAndShortTxIDs> most_recent_compact_block GUARDED_BY(cs_most_recent_block); -static uint256 most_recent_block_hash GUARDED_BY(cs_most_recent_block); -static bool fWitnessesPresentInMostRecentCompactBlock GUARDED_BY(cs_most_recent_block); - /** * Maintain state about the best-seen block and fast-announce a compact block * to compatible peers. */ void PeerManagerImpl::NewPoWValidBlock(const CBlockIndex *pindex, const std::shared_ptr<const CBlock>& pblock) { - std::shared_ptr<const CBlockHeaderAndShortTxIDs> pcmpctblock = std::make_shared<const CBlockHeaderAndShortTxIDs> (*pblock, true); + auto pcmpctblock = std::make_shared<const CBlockHeaderAndShortTxIDs>(*pblock); const CNetMsgMaker msgMaker(PROTOCOL_VERSION); LOCK(cs_main); - static int nHighestFastAnnounce = 0; - if (pindex->nHeight <= nHighestFastAnnounce) + if (pindex->nHeight <= m_highest_fast_announce) return; - nHighestFastAnnounce = pindex->nHeight; + m_highest_fast_announce = pindex->nHeight; + + if (!DeploymentActiveAt(*pindex, m_chainman, Consensus::DEPLOYMENT_SEGWIT)) return; - bool fWitnessEnabled = DeploymentActiveAt(*pindex, m_chainparams.GetConsensus(), Consensus::DEPLOYMENT_SEGWIT); uint256 hashBlock(pblock->GetHash()); + const std::shared_future<CSerializedNetMsg> lazy_ser{ + std::async(std::launch::deferred, [&] { return msgMaker.Make(NetMsgType::CMPCTBLOCK, *pcmpctblock); })}; { - LOCK(cs_most_recent_block); - most_recent_block_hash = hashBlock; - most_recent_block = pblock; - most_recent_compact_block = pcmpctblock; - fWitnessesPresentInMostRecentCompactBlock = fWitnessEnabled; + LOCK(m_most_recent_block_mutex); + m_most_recent_block_hash = hashBlock; + m_most_recent_block = pblock; + m_most_recent_compact_block = pcmpctblock; } - m_connman.ForEachNode([this, &pcmpctblock, pindex, &msgMaker, fWitnessEnabled, &hashBlock](CNode* pnode) EXCLUSIVE_LOCKS_REQUIRED(::cs_main) { + m_connman.ForEachNode([this, pindex, &lazy_ser, &hashBlock](CNode* pnode) EXCLUSIVE_LOCKS_REQUIRED(::cs_main) { AssertLockHeld(::cs_main); - // TODO: Avoid the repeated-serialization here if (pnode->GetCommonVersion() < INVALID_CB_NO_BAN_VERSION || pnode->fDisconnect) return; ProcessBlockAvailability(pnode->GetId()); CNodeState &state = *State(pnode->GetId()); // If the peer has, or we announced to them the previous block already, // but we don't think they have this one, go ahead and announce it - if (state.fPreferHeaderAndIDs && (!fWitnessEnabled || state.fWantsCmpctWitness) && - !PeerHasHeader(&state, pindex) && PeerHasHeader(&state, pindex->pprev)) { + if (state.m_requested_hb_cmpctblocks && !PeerHasHeader(&state, pindex) && PeerHasHeader(&state, pindex->pprev)) { LogPrint(BCLog::NET, "%s sending header-and-ids %s to peer=%d\n", "PeerManager::NewPoWValidBlock", hashBlock.ToString(), pnode->GetId()); - m_connman.PushMessage(pnode, msgMaker.Make(NetMsgType::CMPCTBLOCK, *pcmpctblock)); + + const CSerializedNetMsg& ser_cmpctblock{lazy_ser.get()}; + m_connman.PushMessage(pnode, ser_cmpctblock.Copy()); state.pindexBestHeaderSent = pindex; } }); @@ -1742,22 +1904,18 @@ void PeerManagerImpl::SendPings() void PeerManagerImpl::RelayTransaction(const uint256& txid, const uint256& wtxid) { - WITH_LOCK(cs_main, _RelayTransaction(txid, wtxid);); -} - -void PeerManagerImpl::_RelayTransaction(const uint256& txid, const uint256& wtxid) -{ - m_connman.ForEachNode([&txid, &wtxid](CNode* pnode) EXCLUSIVE_LOCKS_REQUIRED(::cs_main) { - AssertLockHeld(::cs_main); + LOCK(m_peer_mutex); + for(auto& it : m_peer_map) { + Peer& peer = *it.second; + auto tx_relay = peer.GetTxRelay(); + if (!tx_relay) continue; - CNodeState* state = State(pnode->GetId()); - if (state == nullptr) return; - if (state->m_wtxid_relay) { - pnode->PushTxInventory(wtxid); - } else { - pnode->PushTxInventory(txid); + const uint256& hash{peer.m_wtxid_relay ? wtxid : txid}; + LOCK(tx_relay->m_tx_inventory_mutex); + if (!tx_relay->m_tx_inventory_known_filter.contains(hash)) { + tx_relay->m_tx_inventory_to_send.insert(hash); } - }); + }; } void PeerManagerImpl::RelayAddress(NodeId originator, @@ -1776,9 +1934,12 @@ void PeerManagerImpl::RelayAddress(NodeId originator, // Use deterministic randomness to send to the same nodes for 24 hours // at a time so the m_addr_knowns of the chosen nodes prevent repeats const uint64_t hash_addr{CServiceHash(0, 0)(addr)}; + const auto current_time{GetTime<std::chrono::seconds>()}; + // Adding address hash makes exact rotation time different per address, while preserving periodicity. + const uint64_t time_addr{(static_cast<uint64_t>(count_seconds(current_time)) + hash_addr) / count_seconds(ROTATE_ADDR_RELAY_DEST_INTERVAL)}; const CSipHasher hasher{m_connman.GetDeterministicRandomizer(RANDOMIZER_ID_ADDRESS_RELAY) .Write(hash_addr) - .Write((GetTime() + hash_addr) / (24 * 60 * 60))}; + .Write(time_addr)}; FastRandomContext insecure_rand; // Relay reachable addresses to 2 peers. Unreachable addresses are relayed randomly to 1 or 2 peers. @@ -1811,12 +1972,10 @@ void PeerManagerImpl::ProcessGetBlockData(CNode& pfrom, Peer& peer, const CInv& { std::shared_ptr<const CBlock> a_recent_block; std::shared_ptr<const CBlockHeaderAndShortTxIDs> a_recent_compact_block; - bool fWitnessesPresentInARecentCompactBlock; { - LOCK(cs_most_recent_block); - a_recent_block = most_recent_block; - a_recent_compact_block = most_recent_compact_block; - fWitnessesPresentInARecentCompactBlock = fWitnessesPresentInMostRecentCompactBlock; + LOCK(m_most_recent_block_mutex); + a_recent_block = m_most_recent_block; + a_recent_compact_block = m_most_recent_compact_block; } bool need_activate_chain = false; @@ -1854,7 +2013,7 @@ void PeerManagerImpl::ProcessGetBlockData(CNode& pfrom, Peer& peer, const CInv& const CNetMsgMaker msgMaker(pfrom.GetCommonVersion()); // disconnect node in case we have reached the outbound limit for serving historical blocks if (m_connman.OutboundTargetReached(true) && - (((pindexBestHeader != nullptr) && (pindexBestHeader->GetBlockTime() - pindex->GetBlockTime() > HISTORICAL_BLOCK_AGE)) || inv.IsMsgFilteredBlk()) && + (((m_chainman.m_best_header != nullptr) && (m_chainman.m_best_header->GetBlockTime() - pindex->GetBlockTime() > HISTORICAL_BLOCK_AGE)) || inv.IsMsgFilteredBlk()) && !pfrom.HasPermission(NetPermissionFlags::Download) // nodes with the download permission may exceed target ) { LogPrint(BCLog::NET, "historical block serving limit reached, disconnect peer=%d\n", pfrom.GetId()); @@ -1863,7 +2022,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) @@ -1903,11 +2062,11 @@ void PeerManagerImpl::ProcessGetBlockData(CNode& pfrom, Peer& peer, const CInv& } else if (inv.IsMsgFilteredBlk()) { bool sendMerkleBlock = false; CMerkleBlock merkleBlock; - if (pfrom.m_tx_relay != nullptr) { - LOCK(pfrom.m_tx_relay->cs_filter); - if (pfrom.m_tx_relay->pfilter) { + if (auto tx_relay = peer.GetTxRelay(); tx_relay != nullptr) { + LOCK(tx_relay->m_bloom_filter_mutex); + if (tx_relay->m_bloom_filter) { sendMerkleBlock = true; - merkleBlock = CMerkleBlock(*pblock, *pfrom.m_tx_relay->pfilter); + merkleBlock = CMerkleBlock(*pblock, *tx_relay->m_bloom_filter); } } if (sendMerkleBlock) { @@ -1929,17 +2088,15 @@ void PeerManagerImpl::ProcessGetBlockData(CNode& pfrom, Peer& peer, const CInv& // they won't have a useful mempool to match against a compact block, // and we don't feel like constructing the object for them, so // instead we respond with the full, non-compact block. - bool fPeerWantsWitness = State(pfrom.GetId())->fWantsCmpctWitness; - int nSendFlags = fPeerWantsWitness ? 0 : SERIALIZE_TRANSACTION_NO_WITNESS; if (CanDirectFetch() && pindex->nHeight >= m_chainman.ActiveChain().Height() - MAX_CMPCTBLOCK_DEPTH) { - if ((fPeerWantsWitness || !fWitnessesPresentInARecentCompactBlock) && a_recent_compact_block && a_recent_compact_block->header.GetHash() == pindex->GetBlockHash()) { - m_connman.PushMessage(&pfrom, msgMaker.Make(nSendFlags, NetMsgType::CMPCTBLOCK, *a_recent_compact_block)); + if (a_recent_compact_block && a_recent_compact_block->header.GetHash() == pindex->GetBlockHash()) { + m_connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::CMPCTBLOCK, *a_recent_compact_block)); } else { - CBlockHeaderAndShortTxIDs cmpctblock(*pblock, fPeerWantsWitness); - m_connman.PushMessage(&pfrom, msgMaker.Make(nSendFlags, NetMsgType::CMPCTBLOCK, cmpctblock)); + CBlockHeaderAndShortTxIDs cmpctblock{*pblock}; + m_connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::CMPCTBLOCK, cmpctblock)); } } else { - m_connman.PushMessage(&pfrom, msgMaker.Make(nSendFlags, NetMsgType::BLOCK, *pblock)); + m_connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::BLOCK, *pblock)); } } } @@ -1990,13 +2147,15 @@ void PeerManagerImpl::ProcessGetData(CNode& pfrom, Peer& peer, const std::atomic { AssertLockNotHeld(cs_main); + auto tx_relay = peer.GetTxRelay(); + std::deque<CInv>::iterator it = peer.m_getdata_requests.begin(); std::vector<CInv> vNotFound; const CNetMsgMaker msgMaker(pfrom.GetCommonVersion()); const auto now{GetTime<std::chrono::seconds>()}; // Get last mempool request time - const auto mempool_req = pfrom.m_tx_relay != nullptr ? pfrom.m_tx_relay->m_last_mempool_req.load() : std::chrono::seconds::min(); + const auto mempool_req = tx_relay != nullptr ? tx_relay->m_last_mempool_req.load() : std::chrono::seconds::min(); // Process as many TX items from the front of the getdata queue as // possible, since they're common and it's efficient to batch process @@ -2009,8 +2168,9 @@ void PeerManagerImpl::ProcessGetData(CNode& pfrom, Peer& peer, const std::atomic const CInv &inv = *it++; - if (pfrom.m_tx_relay == nullptr) { - // Ignore GETDATA requests for transactions from blocks-only peers. + if (tx_relay == nullptr) { + // Ignore GETDATA requests for transactions from block-relay-only + // peers and peers that asked us not to announce transactions. continue; } @@ -2037,7 +2197,7 @@ void PeerManagerImpl::ProcessGetData(CNode& pfrom, Peer& peer, const std::atomic } for (const uint256& parent_txid : parent_ids_to_add) { // Relaying a transaction with a recent but unconfirmed parent. - if (WITH_LOCK(pfrom.m_tx_relay->cs_tx_inventory, return !pfrom.m_tx_relay->filterInventoryKnown.contains(parent_txid))) { + if (WITH_LOCK(tx_relay->m_tx_inventory_mutex, return !tx_relay->m_tx_inventory_known_filter.contains(parent_txid))) { LOCK(cs_main); State(pfrom.GetId())->m_recently_announced_invs.insert(parent_txid); } @@ -2079,31 +2239,228 @@ void PeerManagerImpl::ProcessGetData(CNode& pfrom, Peer& peer, const std::atomic } } -static uint32_t GetFetchFlags(const CNode& pfrom) 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]]; } + + const CNetMsgMaker msgMaker(pfrom.GetCommonVersion()); + m_connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::BLOCKTXN, resp)); +} + +/** + * 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()); + LOCK(cs_main); + CNodeState *nodestate = State(pfrom.GetId()); + + nodestate->nUnconnectingHeaders++; + // Try to fill in the missing headers. + if (MaybeSendGetHeaders(pfrom, m_chainman.ActiveChain().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 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; +} + +bool PeerManagerImpl::MaybeSendGetHeaders(CNode& pfrom, const CBlockLocator& locator, Peer& peer) +{ const CNetMsgMaker msgMaker(pfrom.GetCommonVersion()); - int nSendFlags = State(pfrom.GetId())->fWantsCmpctWitness ? 0 : SERIALIZE_TRANSACTION_NO_WITNESS; - m_connman.PushMessage(&pfrom, msgMaker.Make(nSendFlags, NetMsgType::BLOCKTXN, resp)); + + const auto current_time = NodeClock::now(); + + // 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; +} + +/* + * 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); + } + 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; + } + 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)); + } + } + } } -void PeerManagerImpl::ProcessHeadersMessage(CNode& pfrom, const Peer& peer, +/** + * 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; + } + } +} + +void PeerManagerImpl::ProcessHeadersMessage(CNode& pfrom, Peer& peer, const std::vector<CBlockHeader>& headers, bool via_compact_block) { @@ -2115,180 +2472,55 @@ void PeerManagerImpl::ProcessHeadersMessage(CNode& pfrom, const Peer& peer, return; } - bool received_new_header = false; const CBlockIndex *pindexLast = nullptr; - { - LOCK(cs_main); - CNodeState *nodestate = State(pfrom.GetId()); - // 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(pindexBestHeader), uint256())); - LogPrint(BCLog::NET, "received header %s: missing prev block %s, sending getheaders (%d) to end (peer=%d, nUnconnectingHeaders=%d)\n", - headers[0].GetHash().ToString(), - headers[0].hashPrevBlock.ToString(), - pindexBestHeader->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)); - } - return; - } + // Do these headers connect to something in our block index? + bool headers_connect_blockindex{WITH_LOCK(::cs_main, return m_chainman.m_blockman.LookupBlockIndex(headers[0].hashPrevBlock) != nullptr)}; - uint256 hashLastBlock; - for (const CBlockHeader& header : headers) { - if (!hashLastBlock.IsNull() && header.hashPrevBlock != hashLastBlock) { - Misbehaving(pfrom.GetId(), 20, "non-continuous headers sequence"); - return; - } - hashLastBlock = header.GetHash(); + 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 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; - } + // At this point, the headers connect to something in our block index. + if (!CheckHeadersAreContinuous(headers)) { + Misbehaving(peer, 20, "non-continuous headers sequence"); + return; } + // 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{WITH_LOCK(::cs_main, return m_chainman.m_blockman.LookupBlockIndex(headers.back().GetHash()) == nullptr)}; + BlockValidationState state; - if (!m_chainman.ProcessNewBlockHeaders(headers, state, m_chainparams, &pindexLast)) { + if (!m_chainman.ProcessNewBlockHeaders(headers, state, &pindexLast)) { if (state.IsInvalid()) { MaybePunishNodeForBlock(pfrom.GetId(), state, via_compact_block, "invalid header received"); return; } } - { - 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 (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 pindexBestHeader, continue - // from there instead. + // Consider fetching more headers. + if (nCount == MAX_HEADERS_RESULTS) { + // Headers message had its maximum size; the peer may have more headers. + if (MaybeSendGetHeaders(pfrom, WITH_LOCK(m_chainman.GetMutex(), return m_chainman.ActiveChain().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); - 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_chainparams.GetConsensus(), 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; - } - // 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); - } - if (vGetData.size() > 0) { - if (!m_ignore_incoming_txs && - nodestate->fSupportsDesiredCmpctVersion && - 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)); - } - } - } - // 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; - } - } - } - - // 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; - } + 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; } @@ -2317,7 +2549,7 @@ void PeerManagerImpl::ProcessOrphanTx(std::set<uint256>& orphan_work_set) if (result.m_result_type == MempoolAcceptResult::ResultType::VALID) { LogPrint(BCLog::MEMPOOL, " accepted orphan tx %s\n", orphanHash.ToString()); - _RelayTransaction(orphanHash, porphanTx->GetWitnessHash()); + RelayTransaction(orphanHash, porphanTx->GetWitnessHash()); m_orphanage.AddChildrenToWorkSet(*porphanTx, orphan_work_set); m_orphanage.EraseTx(orphanHash); for (const CTransactionRef& removedTx : result.m_replaced_transactions.value()) { @@ -2371,7 +2603,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, @@ -2379,11 +2611,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; } @@ -2394,8 +2626,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; } } @@ -2404,14 +2636,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; } @@ -2424,7 +2656,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; @@ -2436,7 +2668,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; } @@ -2449,13 +2681,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; @@ -2467,7 +2699,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; } @@ -2490,16 +2722,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; @@ -2510,7 +2742,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; @@ -2531,18 +2763,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) { bool new_block{false}; - m_chainman.ProcessNewBlock(m_chainparams, block, force_processing, &new_block); + m_chainman.ProcessNewBlock(block, force_processing, &new_block); if (new_block) { node.m_last_block_time = GetTime<std::chrono::seconds>(); } else { @@ -2633,7 +2865,7 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, // Inbound peers send us their version message when they connect. // We send our version message in response. if (pfrom.IsInboundConn()) { - PushNodeVersion(pfrom); + PushNodeVersion(pfrom, *peer); } // Change version @@ -2658,7 +2890,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); @@ -2666,27 +2899,26 @@ 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)); - - if (pfrom.m_tx_relay != nullptr) { - LOCK(pfrom.m_tx_relay->cs_filter); - pfrom.m_tx_relay->fRelayTxes = fRelay; // set to true after we get the first filter* message - } - - if((nServices & NODE_WITNESS)) - { - LOCK(cs_main); - State(pfrom.GetId())->fHaveWitness = true; + // 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 || (peer->m_our_services & NODE_BLOOM))) { + auto* const tx_relay = peer->SetTxRelay(); + { + LOCK(tx_relay->m_bloom_filter_mutex); + tx_relay->m_relay_txs = fRelay; // set to true after we get the first filter* message + } + if (fRelay) pfrom.m_relays_txs = true; } // Potentially mark this peer as a preferred download peer. { - LOCK(cs_main); - UpdatePreferredDownload(pfrom, State(pfrom.GetId())); + LOCK(cs_main); + CNodeState* state = State(pfrom.GetId()); + state->fPreferredDownload = (!pfrom.IsInboundConn() || pfrom.HasPermission(NetPermissionFlags::NoBan)) && !pfrom.IsAddrFetchConn() && CanServeBlocks(*peer); + m_num_preferred_download_peers += state->fPreferredDownload; } // Self advertisement & GETADDR logic @@ -2704,7 +2936,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()) { @@ -2808,16 +3040,12 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, m_connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::SENDHEADERS)); } if (pfrom.GetCommonVersion() >= SHORT_IDS_BLOCKS_VERSION) { - // Tell our peer we are willing to provide version 1 or 2 cmpctblocks + // Tell our peer we are willing to provide version 2 cmpctblocks. // However, we do not request new block announcements using // cmpctblock messages. // We send this to non-NODE NETWORK peers as well, because // they may wish to request compact blocks from us - bool fAnnounceUsingCMPCTBLOCK = false; - uint64_t nCMPCTBLOCKVersion = 2; - m_connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::SENDCMPCT, fAnnounceUsingCMPCTBLOCK, nCMPCTBLOCKVersion)); - nCMPCTBLOCKVersion = 1; - m_connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::SENDCMPCT, fAnnounceUsingCMPCTBLOCK, nCMPCTBLOCKVersion)); + m_connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::SENDCMPCT, /*high_bandwidth=*/false, /*version=*/CMPCTBLOCKS_VERSION)); } pfrom.fSuccessfullyConnected = true; return; @@ -2830,26 +3058,20 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, } if (msg_type == NetMsgType::SENDCMPCT) { - bool fAnnounceUsingCMPCTBLOCK = false; - uint64_t nCMPCTBLOCKVersion = 0; - vRecv >> fAnnounceUsingCMPCTBLOCK >> nCMPCTBLOCKVersion; - if (nCMPCTBLOCKVersion == 1 || nCMPCTBLOCKVersion == 2) { - LOCK(cs_main); - // fProvidesHeaderAndIDs is used to "lock in" version of compact blocks we send (fWantsCmpctWitness) - if (!State(pfrom.GetId())->fProvidesHeaderAndIDs) { - State(pfrom.GetId())->fProvidesHeaderAndIDs = true; - State(pfrom.GetId())->fWantsCmpctWitness = nCMPCTBLOCKVersion == 2; - } - if (State(pfrom.GetId())->fWantsCmpctWitness == (nCMPCTBLOCKVersion == 2)) { // ignore later version announces - State(pfrom.GetId())->fPreferHeaderAndIDs = fAnnounceUsingCMPCTBLOCK; - // save whether peer selects us as BIP152 high-bandwidth peer - // (receiving sendcmpct(1) signals high-bandwidth, sendcmpct(0) low-bandwidth) - pfrom.m_bip152_highbandwidth_from = fAnnounceUsingCMPCTBLOCK; - } - if (!State(pfrom.GetId())->fSupportsDesiredCmpctVersion) { - State(pfrom.GetId())->fSupportsDesiredCmpctVersion = (nCMPCTBLOCKVersion == 2); - } - } + bool sendcmpct_hb{false}; + uint64_t sendcmpct_version{0}; + vRecv >> sendcmpct_hb >> sendcmpct_version; + + // Only support compact block relay with witnesses + if (sendcmpct_version != CMPCTBLOCKS_VERSION) return; + + LOCK(cs_main); + CNodeState* nodestate = State(pfrom.GetId()); + nodestate->m_provides_cmpctblocks = true; + nodestate->m_requested_hb_cmpctblocks = sendcmpct_hb; + // save whether peer selects us as BIP152 high-bandwidth peer + // (receiving sendcmpct(1) signals high-bandwidth, sendcmpct(0) low-bandwidth) + pfrom.m_bip152_highbandwidth_from = sendcmpct_hb; return; } @@ -2863,9 +3085,8 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, return; } if (pfrom.GetCommonVersion() >= WTXID_RELAY_VERSION) { - LOCK(cs_main); - if (!State(pfrom.GetId())->m_wtxid_relay) { - State(pfrom.GetId())->m_wtxid_relay = true; + if (!peer->m_wtxid_relay) { + peer->m_wtxid_relay = true; m_wtxid_relay_peers++; } else { LogPrint(BCLog::NET, "ignoring duplicate wtxidrelay from peer=%d\n", pfrom.GetId()); @@ -2914,21 +3135,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; @@ -2957,8 +3177,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 @@ -2966,7 +3187,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); } @@ -2979,7 +3200,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 @@ -2995,18 +3216,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.m_tx_relay == nullptr)}; - - // 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); @@ -3019,7 +3233,7 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, // Ignore INVs that don't match wtxidrelay setting. // Note that orphan parent fetching always uses MSG_TX GETDATAs regardless of the wtxidrelay setting. // This is fine as no INV messages are involved in that process. - if (State(pfrom.GetId())->m_wtxid_relay) { + if (peer->m_wtxid_relay) { if (inv.IsMsgTx()) continue; } else { if (inv.IsMsgWtx()) continue; @@ -3032,8 +3246,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; @@ -3048,7 +3263,7 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, const bool fAlreadyHave = AlreadyHaveTx(gtxid); LogPrint(BCLog::NET, "got inv: %s %s peer=%d\n", inv.ToString(), fAlreadyHave ? "have" : "new", pfrom.GetId()); - pfrom.AddKnownTx(inv.hash); + AddKnownTx(*peer, inv.hash); if (!fAlreadyHave && !m_chainman.ActiveChainstate().IsInitialBlockDownload()) { AddTxAnnouncement(pfrom, gtxid, current_time); } @@ -3058,8 +3273,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(pindexBestHeader), *best_block)); - LogPrint(BCLog::NET, "getheaders (%d) %s to peer=%d\n", pindexBestHeader->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, m_chainman.ActiveChain().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; @@ -3070,7 +3308,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; } @@ -3110,8 +3348,8 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, { std::shared_ptr<const CBlock> a_recent_block; { - LOCK(cs_most_recent_block); - a_recent_block = most_recent_block; + LOCK(m_most_recent_block_mutex); + a_recent_block = m_most_recent_block; } BlockValidationState state; if (!m_chainman.ActiveChainstate().ActivateBestChain(state, a_recent_block)) { @@ -3162,13 +3400,13 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, std::shared_ptr<const CBlock> recent_block; { - LOCK(cs_most_recent_block); - if (most_recent_block_hash == req.blockhash) - recent_block = most_recent_block; - // Unlock cs_most_recent_block to avoid cs_main lock inversion + LOCK(m_most_recent_block_mutex); + if (m_most_recent_block_hash == req.blockhash) + recent_block = m_most_recent_block; + // 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; } @@ -3186,7 +3424,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; } } @@ -3199,9 +3437,7 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, // expensive disk reads, because it will require the peer to // actually receive all the data read from disk over the network. LogPrint(BCLog::NET, "Peer %d sent us a getblocktxn for a block > %i deep\n", pfrom.GetId(), MAX_BLOCKTXN_DEPTH); - CInv inv; - WITH_LOCK(cs_main, inv.type = State(pfrom.GetId())->fWantsCmpctWitness ? MSG_WITNESS_BLOCK : MSG_BLOCK); - inv.hash = req.blockhash; + CInv inv{MSG_WITNESS_BLOCK, req.blockhash}; WITH_LOCK(peer->m_getdata_requests_mutex, peer->m_getdata_requests.push_back(inv)); // The message processing loop will go around again (without pausing) and we'll respond then return; @@ -3218,9 +3454,26 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, return; } + if (fImporting || fReindex) { + LogPrint(BCLog::NET, "Ignoring getheaders from peer=%d while importing/reindexing\n", pfrom.GetId()); + return; + } + LOCK(cs_main); - if (m_chainman.ActiveChainstate().IsInitialBlockDownload() && !pfrom.HasPermission(NetPermissionFlags::Download)) { - LogPrint(BCLog::NET, "Ignoring getheaders from peer=%d because node is in initial block download\n", pfrom.GetId()); + + // Note that if we were to be on a chain that forks from the checkpointed + // chain, then serving those headers to a peer that has seen the + // checkpointed chain would cause that peer to disconnect us. Requiring + // that our chainwork exceed nMinimumChainWork is a protection against + // being fed a bogus chain when we started up for the first time and + // getting partitioned off the honest network for serving that chain to + // 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; 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; } @@ -3275,11 +3528,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 - // 2) This peer is a block-relay-only peer - if ((m_ignore_incoming_txs && !pfrom.HasPermission(NetPermissionFlags::Relay)) || (pfrom.m_tx_relay == nullptr)) - { + if (RejectIncomingTxs(pfrom)) { LogPrint(BCLog::NET, "transaction sent in violation of protocol peer=%d\n", pfrom.GetId()); pfrom.fDisconnect = true; return; @@ -3297,21 +3546,19 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, const uint256& txid = ptx->GetHash(); const uint256& wtxid = ptx->GetWitnessHash(); - LOCK2(cs_main, g_cs_orphans); - - CNodeState* nodestate = State(pfrom.GetId()); - - const uint256& hash = nodestate->m_wtxid_relay ? wtxid : txid; - pfrom.AddKnownTx(hash); - if (nodestate->m_wtxid_relay && txid != wtxid) { - // Insert txid into filterInventoryKnown, even for + const uint256& hash = peer->m_wtxid_relay ? wtxid : txid; + AddKnownTx(*peer, hash); + if (peer->m_wtxid_relay && txid != wtxid) { + // Insert txid into m_tx_inventory_known_filter, even for // wtxidrelay peers. This prevents re-adding of // unconfirmed parents to the recently_announced // filter, when a child tx is requested. See // ProcessGetData(). - pfrom.AddKnownTx(txid); + AddKnownTx(*peer, txid); } + LOCK2(cs_main, g_cs_orphans); + m_txrequest.ReceivedResponse(pfrom.GetId(), txid); if (tx.HasWitness()) m_txrequest.ReceivedResponse(pfrom.GetId(), wtxid); @@ -3336,7 +3583,7 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, LogPrintf("Not relaying non-mempool transaction %s from forcerelay peer=%d\n", tx.GetHash().ToString(), pfrom.GetId()); } else { LogPrintf("Force relaying tx %s from peer=%d\n", tx.GetHash().ToString(), pfrom.GetId()); - _RelayTransaction(tx.GetHash(), tx.GetWitnessHash()); + RelayTransaction(tx.GetHash(), tx.GetWitnessHash()); } } return; @@ -3350,7 +3597,7 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, // requests for it. m_txrequest.ForgetTxHash(tx.GetHash()); m_txrequest.ForgetTxHash(tx.GetWitnessHash()); - _RelayTransaction(tx.GetHash(), tx.GetWitnessHash()); + RelayTransaction(tx.GetHash(), tx.GetWitnessHash()); m_orphanage.AddChildrenToWorkSet(tx, peer->m_orphan_work_set); pfrom.m_last_tx_time = GetTime<std::chrono::seconds>(); @@ -3397,7 +3644,7 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, // Eventually we should replace this with an improved // protocol for getting all unconfirmed parents. const auto gtxid{GenTxid::Txid(parent_txid)}; - pfrom.AddKnownTx(parent_txid); + AddKnownTx(*peer, parent_txid); if (!AlreadyHaveTx(gtxid)) AddTxAnnouncement(pfrom, gtxid, current_time); } @@ -3411,10 +3658,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 @@ -3507,8 +3751,9 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, if (!m_chainman.m_blockman.LookupBlockIndex(cmpctblock.header.hashPrevBlock)) { // 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(pindexBestHeader), uint256())); + if (!m_chainman.ActiveChainstate().IsInitialBlockDownload()) { + MaybeSendGetHeaders(pfrom, m_chainman.ActiveChain().GetLocator(m_chainman.m_best_header), *peer); + } return; } @@ -3519,9 +3764,9 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, const CBlockIndex *pindex = nullptr; BlockValidationState state; - if (!m_chainman.ProcessNewBlockHeaders({cmpctblock.header}, state, m_chainparams, &pindex)) { + if (!m_chainman.ProcessNewBlockHeaders({cmpctblock.header}, state, &pindex)) { if (state.IsInvalid()) { - MaybePunishNodeForBlock(pfrom.GetId(), state, /*via_compact_block*/ true, "invalid header via cmpctblock"); + MaybePunishNodeForBlock(pfrom.GetId(), state, /*via_compact_block=*/true, "invalid header via cmpctblock"); return; } } @@ -3568,7 +3813,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; @@ -3579,12 +3824,6 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, return; } - if (DeploymentActiveAt(*pindex, m_chainparams.GetConsensus(), Consensus::DEPLOYMENT_SEGWIT) && !nodestate->fSupportsDesiredCmpctVersion) { - // Don't bother trying to process compact blocks from v1 peers - // after segwit activates. - return; - } - // We want to be a bit conservative just to be extra careful about DoS // possibilities in compact block processing... if (pindex->nHeight <= m_chainman.ActiveChain().Height() + 2) { @@ -3605,12 +3844,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; } @@ -3653,7 +3892,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 { @@ -3732,12 +3971,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 @@ -3788,12 +4027,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); @@ -3861,7 +4104,7 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, peer->m_addrs_to_send.clear(); std::vector<CAddress> vAddr; if (pfrom.HasPermission(NetPermissionFlags::Addr)) { - vAddr = m_connman.GetAddresses(MAX_ADDR_TO_SEND, MAX_PCT_ADDR_TO_SEND, /* network */ std::nullopt); + vAddr = m_connman.GetAddresses(MAX_ADDR_TO_SEND, MAX_PCT_ADDR_TO_SEND, /*network=*/std::nullopt); } else { vAddr = m_connman.GetAddresses(pfrom, MAX_ADDR_TO_SEND, MAX_PCT_ADDR_TO_SEND); } @@ -3873,7 +4116,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)) { @@ -3893,9 +4136,9 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, return; } - if (pfrom.m_tx_relay != nullptr) { - LOCK(pfrom.m_tx_relay->cs_tx_inventory); - pfrom.m_tx_relay->fSendMempool = true; + if (auto tx_relay = peer->GetTxRelay(); tx_relay != nullptr) { + LOCK(tx_relay->m_tx_inventory_mutex); + tx_relay->m_send_mempool = true; } return; } @@ -3976,7 +4219,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; @@ -3987,19 +4230,21 @@ 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"); - } - else if (pfrom.m_tx_relay != nullptr) - { - LOCK(pfrom.m_tx_relay->cs_filter); - pfrom.m_tx_relay->pfilter.reset(new CBloomFilter(filter)); - pfrom.m_tx_relay->fRelayTxes = true; + Misbehaving(*peer, 100, "too-large bloom filter"); + } else if (auto tx_relay = peer->GetTxRelay(); tx_relay != nullptr) { + { + LOCK(tx_relay->m_bloom_filter_mutex); + tx_relay->m_bloom_filter.reset(new CBloomFilter(filter)); + 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; @@ -4012,32 +4257,36 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, bool bad = false; if (vData.size() > MAX_SCRIPT_ELEMENT_SIZE) { bad = true; - } else if (pfrom.m_tx_relay != nullptr) { - LOCK(pfrom.m_tx_relay->cs_filter); - if (pfrom.m_tx_relay->pfilter) { - pfrom.m_tx_relay->pfilter->insert(vData); + } else if (auto tx_relay = peer->GetTxRelay(); tx_relay != nullptr) { + LOCK(tx_relay->m_bloom_filter_mutex); + if (tx_relay->m_bloom_filter) { + tx_relay->m_bloom_filter->insert(vData); } else { bad = true; } } 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; } - if (pfrom.m_tx_relay == nullptr) { - return; + auto tx_relay = peer->GetTxRelay(); + if (!tx_relay) return; + + { + LOCK(tx_relay->m_bloom_filter_mutex); + tx_relay->m_bloom_filter = nullptr; + tx_relay->m_relay_txs = true; } - LOCK(pfrom.m_tx_relay->cs_filter); - pfrom.m_tx_relay->pfilter = nullptr; - pfrom.m_tx_relay->fRelayTxes = true; + pfrom.m_bloom_filter_loaded = false; + pfrom.m_relays_txs = true; return; } @@ -4045,8 +4294,8 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, CAmount newFeeFilter = 0; vRecv >> newFeeFilter; if (MoneyRange(newFeeFilter)) { - if (pfrom.m_tx_relay != nullptr) { - pfrom.m_tx_relay->minFeeFilter = newFeeFilter; + if (auto tx_relay = peer->GetTxRelay(); tx_relay != nullptr) { + tx_relay->m_fee_filter_received = newFeeFilter; } LogPrint(BCLog::NET, "received: feefilter of %s from peer=%d\n", CFeeRate(newFeeFilter).ToString(), pfrom.GetId()); } @@ -4054,17 +4303,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; } @@ -4210,7 +4459,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); @@ -4248,10 +4497,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, + m_chainman.ActiveChain().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 @@ -4420,10 +4674,10 @@ void PeerManagerImpl::MaybeSendPing(CNode& node_to, Peer& peer, std::chrono::mic } if (pingSend) { - uint64_t nonce = 0; - while (nonce == 0) { - GetRandBytes((unsigned char*)&nonce, sizeof(nonce)); - } + uint64_t nonce; + do { + nonce = GetRand<uint64_t>(); + } while (nonce == 0); peer.m_ping_queued = false; peer.m_ping_start = now; if (node_to.GetCommonVersion() > BIP0031_VERSION) { @@ -4455,9 +4709,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); } @@ -4504,17 +4759,17 @@ void PeerManagerImpl::MaybeSendAddr(CNode& node, Peer& peer, std::chrono::micros } } -void PeerManagerImpl::MaybeSendFeefilter(CNode& pto, std::chrono::microseconds current_time) +void PeerManagerImpl::MaybeSendFeefilter(CNode& pto, Peer& peer, std::chrono::microseconds current_time) { - AssertLockHeld(cs_main); - if (m_ignore_incoming_txs) return; - if (!pto.m_tx_relay) return; if (pto.GetCommonVersion() < FEEFILTER_VERSION) return; // peers with the forcerelay permission should not filter txs to us if (pto.HasPermission(NetPermissionFlags::ForceRelay)) return; + // Don't send feefilter messages to outbound block-relay-only peers since they should never announce + // 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()) { @@ -4523,34 +4778,34 @@ void PeerManagerImpl::MaybeSendFeefilter(CNode& pto, std::chrono::microseconds c currentFilter = MAX_MONEY; } else { static const CAmount MAX_FILTER{g_filter_rounder.round(MAX_MONEY)}; - if (pto.m_tx_relay->lastSentFeeFilter == MAX_FILTER) { + if (peer.m_fee_filter_sent == MAX_FILTER) { // Send the current filter if we sent MAX_FILTER previously // and made it out of IBD. - pto.m_tx_relay->m_next_send_feefilter = 0us; + peer.m_next_send_feefilter = 0us; } } - if (current_time > pto.m_tx_relay->m_next_send_feefilter) { + 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()); - if (filterToSend != pto.m_tx_relay->lastSentFeeFilter) { + // 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)); - pto.m_tx_relay->lastSentFeeFilter = filterToSend; + peer.m_fee_filter_sent = filterToSend; } - pto.m_tx_relay->m_next_send_feefilter = GetExponentialRand(current_time, AVG_FEEFILTER_BROADCAST_INTERVAL); + peer.m_next_send_feefilter = GetExponentialRand(current_time, AVG_FEEFILTER_BROADCAST_INTERVAL); } // If the fee filter has changed substantially and it's still more than MAX_FEEFILTER_CHANGE_DELAY // until scheduled broadcast, then move the broadcast to within MAX_FEEFILTER_CHANGE_DELAY. - else if (current_time + MAX_FEEFILTER_CHANGE_DELAY < pto.m_tx_relay->m_next_send_feefilter && - (currentFilter < 3 * pto.m_tx_relay->lastSentFeeFilter / 4 || currentFilter > 4 * pto.m_tx_relay->lastSentFeeFilter / 3)) { - pto.m_tx_relay->m_next_send_feefilter = current_time + GetRandomDuration<std::chrono::microseconds>(MAX_FEEFILTER_CHANGE_DELAY); + else if (current_time + MAX_FEEFILTER_CHANGE_DELAY < peer.m_next_send_feefilter && + (currentFilter < 3 * peer.m_fee_filter_sent / 4 || currentFilter > 4 * peer.m_fee_filter_sent / 3)) { + peer.m_next_send_feefilter = current_time + GetRandomDuration<std::chrono::microseconds>(MAX_FEEFILTER_CHANGE_DELAY); } } namespace { class CompareInvMempoolOrder { - CTxMemPool *mp; + CTxMemPool* mp; bool m_wtxid_relay; public: explicit CompareInvMempoolOrder(CTxMemPool *_mempool, bool use_wtxid) @@ -4566,6 +4821,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) @@ -4622,33 +4886,57 @@ bool PeerManagerImpl::SendMessages(CNode* pto) CNodeState &state = *State(pto->GetId()); // Start block sync - if (pindexBestHeader == nullptr) - pindexBestHeader = m_chainman.ActiveChain().Tip(); - bool fFetch = state.fPreferredDownload || (nPreferredDownload == 0 && !pto->fClient && !pto->IsAddrFetchConn()); // Download if this is a nice peer, or we have no nice peers and this one might do. - if (!state.fSyncStarted && !pto->fClient && !fImporting && !fReindex) { + if (m_chainman.m_best_header == nullptr) { + m_chainman.m_best_header = m_chainman.ActiveChain().Tip(); + } + + // Determine whether we might try initial headers sync or parallel + // block download from this peer -- this mostly affects behavior while + // in IBD (once out of IBD, we sync from all peers). + bool sync_blocks_and_headers_from_peer = false; + if (state.fPreferredDownload) { + sync_blocks_and_headers_from_peer = true; + } 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. + // We prefer downloading blocks from outbound peers to avoid + // putting undue load on (say) some home user who is just making + // outbound connections to the network, but if our only source of + // the latest blocks is from an inbound peer, we have to be sure to + // eventually download it (and not just wait indefinitely for an + // outbound peer to have it). + if (m_num_preferred_download_peers == 0 || mapBlocksInFlight.empty()) { + sync_blocks_and_headers_from_peer = true; + } + } + + if (!state.fSyncStarted && CanServeBlocks(*peer) && !fImporting && !fReindex) { // Only actively request headers from a single peer, unless we're close to today. - if ((nSyncStarted == 0 && fFetch) || pindexBestHeader->GetBlockTime() > GetAdjustedTime() - 24 * 60 * 60) { - state.fSyncStarted = true; - state.m_headers_sync_timeout = current_time + HEADERS_DOWNLOAD_TIMEOUT_BASE + - ( - // Convert HEADERS_DOWNLOAD_TIMEOUT_PER_HEADER to microseconds before scaling - // to maintain precision - std::chrono::microseconds{HEADERS_DOWNLOAD_TIMEOUT_PER_HEADER} * - (GetAdjustedTime() - pindexBestHeader->GetBlockTime()) / consensusParams.nPowTargetSpacing - ); - nSyncStarted++; - const CBlockIndex *pindexStart = pindexBestHeader; + if ((nSyncStarted == 0 && sync_blocks_and_headers_from_peer) || m_chainman.m_best_header->GetBlockTime() > GetAdjustedTime() - 24 * 60 * 60) { + 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 non-empty list of headers back as long as the peer is up-to-date. With a non-empty response, we can initialise the peer's known best block. This wouldn't be possible - if we requested starting at pindexBestHeader and + if we requested starting at m_chainman.m_best_header and 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, m_chainman.ActiveChain().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} * + (GetAdjustedTime() - m_chainman.m_best_header->GetBlockTime()) / consensusParams.nPowTargetSpacing + ); + nSyncStarted++; + } } } @@ -4656,7 +4944,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. @@ -4666,7 +4954,7 @@ bool PeerManagerImpl::SendMessages(CNode* pto) LOCK(peer->m_block_inv_mutex); std::vector<CBlock> vHeaders; bool fRevertToInv = ((!state.fPreferHeaders && - (!state.fPreferHeaderAndIDs || peer->m_blocks_for_headers_relay.size() > 1)) || + (!state.m_requested_hb_cmpctblocks || peer->m_blocks_for_headers_relay.size() > 1)) || peer->m_blocks_for_headers_relay.size() > MAX_BLOCKS_TO_ANNOUNCE); const CBlockIndex *pBestIndex = nullptr; // last header queued for delivery ProcessBlockAvailability(pto->GetId()); // ensure pindexBestKnownBlock is up-to-date @@ -4719,33 +5007,27 @@ bool PeerManagerImpl::SendMessages(CNode* pto) } } if (!fRevertToInv && !vHeaders.empty()) { - if (vHeaders.size() == 1 && state.fPreferHeaderAndIDs) { + if (vHeaders.size() == 1 && state.m_requested_hb_cmpctblocks) { // We only send up to 1 block as header-and-ids, as otherwise // probably means we're doing an initial-ish-sync or they're slow LogPrint(BCLog::NET, "%s sending header-and-ids %s to peer=%d\n", __func__, vHeaders.front().GetHash().ToString(), pto->GetId()); - int nSendFlags = state.fWantsCmpctWitness ? 0 : SERIALIZE_TRANSACTION_NO_WITNESS; - - bool fGotBlockFromCache = false; + std::optional<CSerializedNetMsg> cached_cmpctblock_msg; { - LOCK(cs_most_recent_block); - if (most_recent_block_hash == pBestIndex->GetBlockHash()) { - if (state.fWantsCmpctWitness || !fWitnessesPresentInMostRecentCompactBlock) - m_connman.PushMessage(pto, msgMaker.Make(nSendFlags, NetMsgType::CMPCTBLOCK, *most_recent_compact_block)); - else { - CBlockHeaderAndShortTxIDs cmpctblock(*most_recent_block, state.fWantsCmpctWitness); - m_connman.PushMessage(pto, msgMaker.Make(nSendFlags, NetMsgType::CMPCTBLOCK, cmpctblock)); - } - fGotBlockFromCache = true; + LOCK(m_most_recent_block_mutex); + if (m_most_recent_block_hash == pBestIndex->GetBlockHash()) { + cached_cmpctblock_msg = msgMaker.Make(NetMsgType::CMPCTBLOCK, *m_most_recent_compact_block); } } - if (!fGotBlockFromCache) { + if (cached_cmpctblock_msg.has_value()) { + m_connman.PushMessage(pto, std::move(cached_cmpctblock_msg.value())); + } else { CBlock block; bool ret = ReadBlockFromDisk(block, pBestIndex, consensusParams); assert(ret); - CBlockHeaderAndShortTxIDs cmpctblock(block, state.fWantsCmpctWitness); - m_connman.PushMessage(pto, msgMaker.Make(nSendFlags, NetMsgType::CMPCTBLOCK, cmpctblock)); + CBlockHeaderAndShortTxIDs cmpctblock{block}; + m_connman.PushMessage(pto, msgMaker.Make(NetMsgType::CMPCTBLOCK, cmpctblock)); } state.pindexBestHeaderSent = pBestIndex; } else if (state.fPreferHeaders) { @@ -4810,45 +5092,45 @@ bool PeerManagerImpl::SendMessages(CNode* pto) peer->m_blocks_for_inv_relay.clear(); } - if (pto->m_tx_relay != nullptr) { - LOCK(pto->m_tx_relay->cs_tx_inventory); + if (auto tx_relay = peer->GetTxRelay(); tx_relay != nullptr) { + LOCK(tx_relay->m_tx_inventory_mutex); // Check whether periodic sends should happen bool fSendTrickle = pto->HasPermission(NetPermissionFlags::NoBan); - if (pto->m_tx_relay->nNextInvSend < current_time) { + if (tx_relay->m_next_inv_send_time < current_time) { fSendTrickle = true; if (pto->IsInboundConn()) { - pto->m_tx_relay->nNextInvSend = NextInvToInbounds(current_time, INBOUND_INVENTORY_BROADCAST_INTERVAL); + tx_relay->m_next_inv_send_time = NextInvToInbounds(current_time, INBOUND_INVENTORY_BROADCAST_INTERVAL); } else { - pto->m_tx_relay->nNextInvSend = GetExponentialRand(current_time, OUTBOUND_INVENTORY_BROADCAST_INTERVAL); + tx_relay->m_next_inv_send_time = GetExponentialRand(current_time, OUTBOUND_INVENTORY_BROADCAST_INTERVAL); } } // Time to send but the peer has requested we not relay transactions. if (fSendTrickle) { - LOCK(pto->m_tx_relay->cs_filter); - if (!pto->m_tx_relay->fRelayTxes) pto->m_tx_relay->setInventoryTxToSend.clear(); + LOCK(tx_relay->m_bloom_filter_mutex); + if (!tx_relay->m_relay_txs) tx_relay->m_tx_inventory_to_send.clear(); } // Respond to BIP35 mempool requests - if (fSendTrickle && pto->m_tx_relay->fSendMempool) { + if (fSendTrickle && tx_relay->m_send_mempool) { auto vtxinfo = m_mempool.infoAll(); - pto->m_tx_relay->fSendMempool = false; - const CFeeRate filterrate{pto->m_tx_relay->minFeeFilter.load()}; + tx_relay->m_send_mempool = false; + const CFeeRate filterrate{tx_relay->m_fee_filter_received.load()}; - LOCK(pto->m_tx_relay->cs_filter); + LOCK(tx_relay->m_bloom_filter_mutex); for (const auto& txinfo : vtxinfo) { - const uint256& hash = state.m_wtxid_relay ? txinfo.tx->GetWitnessHash() : txinfo.tx->GetHash(); - CInv inv(state.m_wtxid_relay ? MSG_WTX : MSG_TX, hash); - pto->m_tx_relay->setInventoryTxToSend.erase(hash); + const uint256& hash = peer->m_wtxid_relay ? txinfo.tx->GetWitnessHash() : txinfo.tx->GetHash(); + CInv inv(peer->m_wtxid_relay ? MSG_WTX : MSG_TX, hash); + tx_relay->m_tx_inventory_to_send.erase(hash); // Don't send transactions that peers will not put into their mempool if (txinfo.fee < filterrate.GetFee(txinfo.vsize)) { continue; } - if (pto->m_tx_relay->pfilter) { - if (!pto->m_tx_relay->pfilter->IsRelevantAndUpdate(*txinfo.tx)) continue; + if (tx_relay->m_bloom_filter) { + if (!tx_relay->m_bloom_filter->IsRelevantAndUpdate(*txinfo.tx)) continue; } - pto->m_tx_relay->filterInventoryKnown.insert(hash); + tx_relay->m_tx_inventory_known_filter.insert(hash); // Responses to MEMPOOL requests bypass the m_recently_announced_invs filter. vInv.push_back(inv); if (vInv.size() == MAX_INV_SZ) { @@ -4856,37 +5138,37 @@ bool PeerManagerImpl::SendMessages(CNode* pto) vInv.clear(); } } - pto->m_tx_relay->m_last_mempool_req = std::chrono::duration_cast<std::chrono::seconds>(current_time); + tx_relay->m_last_mempool_req = std::chrono::duration_cast<std::chrono::seconds>(current_time); } // Determine transactions to relay if (fSendTrickle) { // Produce a vector with all candidates for sending std::vector<std::set<uint256>::iterator> vInvTx; - vInvTx.reserve(pto->m_tx_relay->setInventoryTxToSend.size()); - for (std::set<uint256>::iterator it = pto->m_tx_relay->setInventoryTxToSend.begin(); it != pto->m_tx_relay->setInventoryTxToSend.end(); it++) { + vInvTx.reserve(tx_relay->m_tx_inventory_to_send.size()); + for (std::set<uint256>::iterator it = tx_relay->m_tx_inventory_to_send.begin(); it != tx_relay->m_tx_inventory_to_send.end(); it++) { vInvTx.push_back(it); } - const CFeeRate filterrate{pto->m_tx_relay->minFeeFilter.load()}; + const CFeeRate filterrate{tx_relay->m_fee_filter_received.load()}; // Topologically and fee-rate sort the inventory we send for privacy and priority reasons. // A heap is used so that not all items need sorting if only a few are being sent. - CompareInvMempoolOrder compareInvMempoolOrder(&m_mempool, state.m_wtxid_relay); + CompareInvMempoolOrder compareInvMempoolOrder(&m_mempool, peer->m_wtxid_relay); std::make_heap(vInvTx.begin(), vInvTx.end(), compareInvMempoolOrder); // No reason to drain out at many times the network's capacity, // especially since we have many peers and some will draw much shorter delays. unsigned int nRelayedTransactions = 0; - LOCK(pto->m_tx_relay->cs_filter); + LOCK(tx_relay->m_bloom_filter_mutex); while (!vInvTx.empty() && nRelayedTransactions < INVENTORY_BROADCAST_MAX) { // Fetch the top element from the heap std::pop_heap(vInvTx.begin(), vInvTx.end(), compareInvMempoolOrder); std::set<uint256>::iterator it = vInvTx.back(); vInvTx.pop_back(); uint256 hash = *it; - CInv inv(state.m_wtxid_relay ? MSG_WTX : MSG_TX, hash); + CInv inv(peer->m_wtxid_relay ? MSG_WTX : MSG_TX, hash); // Remove it from the to-be-sent set - pto->m_tx_relay->setInventoryTxToSend.erase(it); + tx_relay->m_tx_inventory_to_send.erase(it); // Check if not in the filter already - if (pto->m_tx_relay->filterInventoryKnown.contains(hash)) { + if (tx_relay->m_tx_inventory_known_filter.contains(hash)) { continue; } // Not in the mempool anymore? don't bother sending it. @@ -4900,7 +5182,7 @@ bool PeerManagerImpl::SendMessages(CNode* pto) if (txinfo.fee < filterrate.GetFee(txinfo.vsize)) { continue; } - if (pto->m_tx_relay->pfilter && !pto->m_tx_relay->pfilter->IsRelevantAndUpdate(*txinfo.tx)) continue; + if (tx_relay->m_bloom_filter && !tx_relay->m_bloom_filter->IsRelevantAndUpdate(*txinfo.tx)) continue; // Send State(pto->GetId())->m_recently_announced_invs.insert(hash); vInv.push_back(inv); @@ -4927,14 +5209,14 @@ bool PeerManagerImpl::SendMessages(CNode* pto) m_connman.PushMessage(pto, msgMaker.Make(NetMsgType::INV, vInv)); vInv.clear(); } - pto->m_tx_relay->filterInventoryKnown.insert(hash); + tx_relay->m_tx_inventory_known_filter.insert(hash); if (hash != txid) { - // Insert txid into filterInventoryKnown, even for + // Insert txid into m_tx_inventory_known_filter, even for // wtxidrelay peers. This prevents re-adding of // unconfirmed parents to the recently_announced // filter, when a child tx is requested. See // ProcessGetData(). - pto->m_tx_relay->filterInventoryKnown.insert(txid); + tx_relay->m_tx_inventory_known_filter.insert(txid); } } } @@ -4968,8 +5250,8 @@ 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 (pindexBestHeader->GetBlockTime() <= GetAdjustedTime() - 24 * 60 * 60) { - if (current_time > state.m_headers_sync_timeout && nSyncStarted == 1 && (nPreferredDownload - state.fPreferredDownload >= 1)) { + if (m_chainman.m_best_header->GetBlockTime() <= GetAdjustedTime() - 24 * 60 * 60) { + 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. // Note: If all our peers are inbound, then we won't @@ -5000,18 +5282,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 && ((fFetch && !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(), @@ -5038,7 +5320,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(); @@ -5054,8 +5336,7 @@ bool PeerManagerImpl::SendMessages(CNode* pto) if (!vGetData.empty()) m_connman.PushMessage(pto, msgMaker.Make(NetMsgType::GETDATA, vGetData)); - - MaybeSendFeefilter(*pto, current_time); } // release cs_main + MaybeSendFeefilter(*pto, *peer, current_time); return true; } diff --git a/src/net_processing.h b/src/net_processing.h index e30f9f516c..bcda9614d4 100644 --- a/src/net_processing.h +++ b/src/net_processing.h @@ -29,15 +29,18 @@ struct CNodeStateStats { int m_starting_height = -1; std::chrono::microseconds m_ping_wait; std::vector<int> vHeightInFlight; + bool m_relay_txs; + CAmount m_fee_filter_received; uint64_t m_addr_processed = 0; uint64_t m_addr_rate_limited = 0; bool m_addr_relay_enabled{false}; + ServiceFlags their_services; }; class PeerManager : public CValidationInterface, public NetEventsInterface { public: - static std::unique_ptr<PeerManager> make(const CChainParams& chainparams, CConnman& connman, AddrMan& addrman, + static std::unique_ptr<PeerManager> make(CConnman& connman, AddrMan& addrman, BanMan* banman, ChainstateManager& chainman, CTxMemPool& pool, bool ignore_incoming_txs); virtual ~PeerManager() { } @@ -69,12 +72,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. @@ -85,6 +84,9 @@ 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; + + /** 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; }; #endif // BITCOIN_NET_PROCESSING_H diff --git a/src/net_types.cpp b/src/net_types.cpp index e4101a9876..90346715f0 100644 --- a/src/net_types.cpp +++ b/src/net_types.cpp @@ -12,9 +12,9 @@ static const char* BANMAN_JSON_VERSION_KEY{"version"}; CBanEntry::CBanEntry(const UniValue& json) - : nVersion(json[BANMAN_JSON_VERSION_KEY].get_int()), - nCreateTime(json["ban_created"].get_int64()), - nBanUntil(json["banned_until"].get_int64()) + : nVersion(json[BANMAN_JSON_VERSION_KEY].getInt<int>()), + nCreateTime(json["ban_created"].getInt<int64_t>()), + nBanUntil(json["banned_until"].getInt<int64_t>()) { } @@ -58,7 +58,7 @@ UniValue BanMapToJson(const banmap_t& bans) void BanMapFromJson(const UniValue& bans_json, banmap_t& bans) { for (const auto& ban_entry_json : bans_json.getValues()) { - const int version{ban_entry_json[BANMAN_JSON_VERSION_KEY].get_int()}; + const int version{ban_entry_json[BANMAN_JSON_VERSION_KEY].getInt<int>()}; if (version != CBanEntry::CURRENT_VERSION) { LogPrintf("Dropping entry with unknown version (%s) from ban list\n", version); continue; diff --git a/src/netaddress.cpp b/src/netaddress.cpp index f7640329f8..ca148bfa51 100644 --- a/src/netaddress.cpp +++ b/src/netaddress.cpp @@ -10,7 +10,6 @@ #include <hash.h> #include <prevector.h> #include <tinyformat.h> -#include <util/asmap.h> #include <util/strencodings.h> #include <util/string.h> @@ -21,9 +20,6 @@ #include <iterator> #include <tuple> -constexpr size_t CNetAddr::V1_SERIALIZATION_SIZE; -constexpr size_t CNetAddr::MAX_ADDRV2_SIZE; - CNetAddr::BIP155Network CNetAddr::GetBIP155Network() const { switch (m_net) { @@ -102,7 +98,7 @@ bool CNetAddr::SetNetFromBIP155Network(uint8_t possible_bip155_net, size_t addre * * @note This address is considered invalid by CNetAddr::IsValid() */ -CNetAddr::CNetAddr() {} +CNetAddr::CNetAddr() = default; void CNetAddr::SetIP(const CNetAddr& ipIn) { @@ -211,7 +207,7 @@ static void Checksum(Span<const uint8_t> addr_pubkey, uint8_t (&checksum)[CHECKS bool CNetAddr::SetSpecial(const std::string& addr) { - if (!ValidAsCString(addr)) { + if (!ContainsNoNUL(addr)) { return false; } @@ -235,17 +231,16 @@ bool CNetAddr::SetTor(const std::string& addr) return false; } - bool invalid; - const auto& input = DecodeBase32(addr.substr(0, addr.size() - suffix_len).c_str(), &invalid); + auto input = DecodeBase32(std::string_view{addr}.substr(0, addr.size() - suffix_len)); - if (invalid) { + if (!input) { return false; } - if (input.size() == torv3::TOTAL_LEN) { - Span<const uint8_t> input_pubkey{input.data(), ADDR_TORV3_SIZE}; - Span<const uint8_t> input_checksum{input.data() + ADDR_TORV3_SIZE, torv3::CHECKSUM_LEN}; - Span<const uint8_t> input_version{input.data() + ADDR_TORV3_SIZE + torv3::CHECKSUM_LEN, sizeof(torv3::VERSION)}; + if (input->size() == torv3::TOTAL_LEN) { + Span<const uint8_t> input_pubkey{input->data(), ADDR_TORV3_SIZE}; + Span<const uint8_t> input_checksum{input->data() + ADDR_TORV3_SIZE, torv3::CHECKSUM_LEN}; + Span<const uint8_t> input_version{input->data() + ADDR_TORV3_SIZE + torv3::CHECKSUM_LEN, sizeof(torv3::VERSION)}; if (input_version != torv3::VERSION) { return false; @@ -281,15 +276,14 @@ bool CNetAddr::SetI2P(const std::string& addr) // can decode it. const std::string b32_padded = addr.substr(0, b32_len) + "===="; - bool invalid; - const auto& address_bytes = DecodeBase32(b32_padded.c_str(), &invalid); + auto address_bytes = DecodeBase32(b32_padded); - if (invalid || address_bytes.size() != ADDR_I2P_SIZE) { + if (!address_bytes || address_bytes->size() != ADDR_I2P_SIZE) { return false; } m_net = NET_I2P; - m_addr.assign(address_bytes.begin(), address_bytes.end()); + m_addr.assign(address_bytes->begin(), address_bytes->end()); return true; } @@ -722,107 +716,6 @@ Network CNetAddr::GetNetClass() const return m_net; } -uint32_t CNetAddr::GetMappedAS(const std::vector<bool> &asmap) const { - uint32_t net_class = GetNetClass(); - if (asmap.size() == 0 || (net_class != NET_IPV4 && net_class != NET_IPV6)) { - return 0; // Indicates not found, safe because AS0 is reserved per RFC7607. - } - std::vector<bool> ip_bits(128); - if (HasLinkedIPv4()) { - // For lookup, treat as if it was just an IPv4 address (IPV4_IN_IPV6_PREFIX + IPv4 bits) - for (int8_t byte_i = 0; byte_i < 12; ++byte_i) { - for (uint8_t bit_i = 0; bit_i < 8; ++bit_i) { - ip_bits[byte_i * 8 + bit_i] = (IPV4_IN_IPV6_PREFIX[byte_i] >> (7 - bit_i)) & 1; - } - } - uint32_t ipv4 = GetLinkedIPv4(); - for (int i = 0; i < 32; ++i) { - ip_bits[96 + i] = (ipv4 >> (31 - i)) & 1; - } - } else { - // Use all 128 bits of the IPv6 address otherwise - assert(IsIPv6()); - for (int8_t byte_i = 0; byte_i < 16; ++byte_i) { - uint8_t cur_byte = m_addr[byte_i]; - for (uint8_t bit_i = 0; bit_i < 8; ++bit_i) { - ip_bits[byte_i * 8 + bit_i] = (cur_byte >> (7 - bit_i)) & 1; - } - } - } - uint32_t mapped_as = Interpret(asmap, ip_bits); - return mapped_as; -} - -/** - * Get the canonical identifier of our network group - * - * The groups are assigned in a way where it should be costly for an attacker to - * obtain addresses with many different group identifiers, even if it is cheap - * to obtain addresses with the same identifier. - * - * @note No two connections will be attempted to addresses with the same network - * group. - */ -std::vector<unsigned char> CNetAddr::GetGroup(const std::vector<bool> &asmap) const -{ - std::vector<unsigned char> vchRet; - uint32_t net_class = GetNetClass(); - // If non-empty asmap is supplied and the address is IPv4/IPv6, - // return ASN to be used for bucketing. - uint32_t asn = GetMappedAS(asmap); - if (asn != 0) { // Either asmap was empty, or address has non-asmappable net class (e.g. TOR). - vchRet.push_back(NET_IPV6); // IPv4 and IPv6 with same ASN should be in the same bucket - for (int i = 0; i < 4; i++) { - vchRet.push_back((asn >> (8 * i)) & 0xFF); - } - return vchRet; - } - - vchRet.push_back(net_class); - int nBits{0}; - - if (IsLocal()) { - // all local addresses belong to the same group - } else if (IsInternal()) { - // all internal-usage addresses get their own group - nBits = ADDR_INTERNAL_SIZE * 8; - } else if (!IsRoutable()) { - // all other unroutable addresses belong to the same group - } else if (HasLinkedIPv4()) { - // IPv4 addresses (and mapped IPv4 addresses) use /16 groups - uint32_t ipv4 = GetLinkedIPv4(); - vchRet.push_back((ipv4 >> 24) & 0xFF); - vchRet.push_back((ipv4 >> 16) & 0xFF); - return vchRet; - } else if (IsTor() || IsI2P()) { - nBits = 4; - } else if (IsCJDNS()) { - // Treat in the same way as Tor and I2P because the address in all of - // them is "random" bytes (derived from a public key). However in CJDNS - // the first byte is a constant 0xfc, so the random bytes come after it. - // Thus skip the constant 8 bits at the start. - nBits = 12; - } else if (IsHeNet()) { - // for he.net, use /36 groups - nBits = 36; - } else { - // for the rest of the IPv6 network, use /32 groups - nBits = 32; - } - - // Push our address onto vchRet. - const size_t num_bytes = nBits / 8; - vchRet.insert(vchRet.end(), m_addr.begin(), m_addr.begin() + num_bytes); - nBits %= 8; - // ...for the last byte, push nBits and for the rest of the byte push 1's - if (nBits > 0) { - assert(num_bytes < m_addr.size()); - vchRet.push_back(m_addr[num_bytes] | ((1 << (8 - nBits)) - 1)); - } - - return vchRet; -} - std::vector<unsigned char> CNetAddr::GetAddrBytes() const { if (IsAddrV1Compatible()) { diff --git a/src/netaddress.h b/src/netaddress.h index 6d21dcd5cd..e52beb783d 100644 --- a/src/netaddress.h +++ b/src/netaddress.h @@ -9,8 +9,7 @@ #include <config/bitcoin-config.h> #endif -#include <attributes.h> -#include <compat.h> +#include <compat/compat.h> #include <crypto/siphash.h> #include <prevector.h> #include <random.h> @@ -202,12 +201,6 @@ public: //! Whether this address has a linked IPv4 address (see GetLinkedIPv4()). bool HasLinkedIPv4() const; - // The AS on the BGP path to the node we use to diversify - // peers in AddrMan bucketing based on the AS infrastructure. - // The ip->AS mapping depends on how asmap is constructed. - uint32_t GetMappedAS(const std::vector<bool>& asmap) const; - - std::vector<unsigned char> GetGroup(const std::vector<bool>& asmap) const; std::vector<unsigned char> GetAddrBytes() const; int GetReachabilityFrom(const CNetAddr* paddrPartner = nullptr) const; @@ -562,8 +555,8 @@ class CServiceHash { public: CServiceHash() - : m_salt_k0{GetRand(std::numeric_limits<uint64_t>::max())}, - m_salt_k1{GetRand(std::numeric_limits<uint64_t>::max())} + : m_salt_k0{GetRand<uint64_t>()}, + m_salt_k1{GetRand<uint64_t>()} { } diff --git a/src/netbase.cpp b/src/netbase.cpp index 9a0b800565..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; @@ -136,7 +136,7 @@ static bool LookupIntern(const std::string& name, std::vector<CNetAddr>& vIP, un { vIP.clear(); - if (!ValidAsCString(name)) { + if (!ContainsNoNUL(name)) { return false; } @@ -169,7 +169,7 @@ static bool LookupIntern(const std::string& name, std::vector<CNetAddr>& vIP, un bool LookupHost(const std::string& name, std::vector<CNetAddr>& vIP, unsigned int nMaxSolutions, bool fAllowLookup, DNSLookupFn dns_lookup_function) { - if (!ValidAsCString(name)) { + if (!ContainsNoNUL(name)) { return false; } std::string strHost = name; @@ -184,7 +184,7 @@ bool LookupHost(const std::string& name, std::vector<CNetAddr>& vIP, unsigned in bool LookupHost(const std::string& name, CNetAddr& addr, bool fAllowLookup, DNSLookupFn dns_lookup_function) { - if (!ValidAsCString(name)) { + if (!ContainsNoNUL(name)) { return false; } std::vector<CNetAddr> vIP; @@ -197,7 +197,7 @@ bool LookupHost(const std::string& name, CNetAddr& addr, bool fAllowLookup, DNSL bool Lookup(const std::string& name, std::vector<CService>& vAddr, uint16_t portDefault, bool fAllowLookup, unsigned int nMaxSolutions, DNSLookupFn dns_lookup_function) { - if (name.empty() || !ValidAsCString(name)) { + if (name.empty() || !ContainsNoNUL(name)) { return false; } uint16_t port{portDefault}; @@ -216,7 +216,7 @@ bool Lookup(const std::string& name, std::vector<CService>& vAddr, uint16_t port bool Lookup(const std::string& name, CService& addr, uint16_t portDefault, bool fAllowLookup, DNSLookupFn dns_lookup_function) { - if (!ValidAsCString(name)) { + if (!ContainsNoNUL(name)) { return false; } std::vector<CService> vService; @@ -229,7 +229,7 @@ bool Lookup(const std::string& name, CService& addr, uint16_t portDefault, bool CService LookupNumeric(const std::string& name, uint16_t portDefault, DNSLookupFn dns_lookup_function) { - if (!ValidAsCString(name)) { + if (!ContainsNoNUL(name)) { return {}; } CService addr; @@ -305,7 +305,7 @@ enum class IntrRecvError { * * @see This function can be interrupted by calling InterruptSocks5(bool). * Sockets can be made non-blocking with SetSocketNonBlocking(const - * SOCKET&, bool). + * SOCKET&). */ static IntrRecvError InterruptibleRecv(uint8_t* data, size_t len, int timeout, const Sock& sock) { @@ -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); @@ -499,10 +499,11 @@ std::unique_ptr<Sock> CreateSockTCP(const CService& address_family) return nullptr; } + auto sock = std::make_unique<Sock>(hSocket); + // Ensure that waiting for I/O on this socket won't result in undefined // behavior. - if (!IsSelectableSocket(hSocket)) { - CloseSocket(hSocket); + if (!IsSelectableSocket(sock->Get())) { LogPrintf("Cannot create connection: non-selectable socket created (fd >= FD_SETSIZE ?)\n"); return nullptr; } @@ -511,19 +512,24 @@ std::unique_ptr<Sock> CreateSockTCP(const CService& address_family) int set = 1; // Set the no-sigpipe option on the socket for BSD systems, other UNIXes // should use the MSG_NOSIGNAL flag for every send. - setsockopt(hSocket, SOL_SOCKET, SO_NOSIGPIPE, (void*)&set, sizeof(int)); + if (sock->SetSockOpt(SOL_SOCKET, SO_NOSIGPIPE, (void*)&set, sizeof(int)) == SOCKET_ERROR) { + LogPrintf("Error setting SO_NOSIGPIPE on socket: %s, continuing anyway\n", + NetworkErrorString(WSAGetLastError())); + } #endif // Set the no-delay option (disable Nagle's algorithm) on the TCP socket. - SetSocketNoDelay(hSocket); + const int on{1}; + if (sock->SetSockOpt(IPPROTO_TCP, TCP_NODELAY, &on, sizeof(on)) == SOCKET_ERROR) { + LogPrint(BCLog::NET, "Unable to set TCP_NODELAY on a newly created socket, continuing anyway\n"); + } // Set the non-blocking option on the socket. - if (!SetSocketNonBlocking(hSocket, true)) { - CloseSocket(hSocket); + if (!SetSocketNonBlocking(sock->Get())) { LogPrintf("Error setting socket to non-blocking: %s\n", NetworkErrorString(WSAGetLastError())); return nullptr; } - return std::make_unique<Sock>(hSocket); + return sock; } std::function<std::unique_ptr<Sock>(const CService&)> CreateSock = CreateSockTCP; @@ -669,7 +675,7 @@ bool ConnectThroughProxy(const Proxy& proxy, const std::string& strDest, uint16_ return false; } } else { - if (!Socks5(strDest, port, 0, sock)) { + if (!Socks5(strDest, port, nullptr, sock)) { return false; } } @@ -678,7 +684,7 @@ bool ConnectThroughProxy(const Proxy& proxy, const std::string& strDest, uint16_ bool LookupSubNet(const std::string& subnet_str, CSubNet& subnet_out) { - if (!ValidAsCString(subnet_str)) { + if (!ContainsNoNUL(subnet_str)) { return false; } @@ -711,40 +717,21 @@ bool LookupSubNet(const std::string& subnet_str, CSubNet& subnet_out) return false; } -bool SetSocketNonBlocking(const SOCKET& hSocket, bool fNonBlocking) +bool SetSocketNonBlocking(const SOCKET& hSocket) { - if (fNonBlocking) { -#ifdef WIN32 - u_long nOne = 1; - if (ioctlsocket(hSocket, FIONBIO, &nOne) == SOCKET_ERROR) { -#else - int fFlags = fcntl(hSocket, F_GETFL, 0); - if (fcntl(hSocket, F_SETFL, fFlags | O_NONBLOCK) == SOCKET_ERROR) { -#endif - return false; - } - } else { #ifdef WIN32 - u_long nZero = 0; - if (ioctlsocket(hSocket, FIONBIO, &nZero) == SOCKET_ERROR) { + u_long nOne = 1; + if (ioctlsocket(hSocket, FIONBIO, &nOne) == SOCKET_ERROR) { #else - int fFlags = fcntl(hSocket, F_GETFL, 0); - if (fcntl(hSocket, F_SETFL, fFlags & ~O_NONBLOCK) == SOCKET_ERROR) { + int fFlags = fcntl(hSocket, F_GETFL, 0); + if (fcntl(hSocket, F_SETFL, fFlags | O_NONBLOCK) == SOCKET_ERROR) { #endif - return false; - } + return false; } return true; } -bool SetSocketNoDelay(const SOCKET& hSocket) -{ - int set = 1; - int rc = setsockopt(hSocket, IPPROTO_TCP, TCP_NODELAY, (const char*)&set, sizeof(int)); - return rc == 0; -} - void InterruptSocks5(bool interrupt) { interruptSocks5Recv = interrupt; diff --git a/src/netbase.h b/src/netbase.h index f9e3872c16..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> @@ -221,10 +221,8 @@ bool ConnectSocketDirectly(const CService &addrConnect, const Sock& sock, int nT */ bool ConnectThroughProxy(const Proxy& proxy, const std::string& strDest, uint16_t port, const Sock& sock, int nTimeout, bool& outProxyConnectionFailed); -/** Disable or enable blocking-mode for a socket */ -bool SetSocketNonBlocking(const SOCKET& hSocket, bool fNonBlocking); -/** Set the TCP_NODELAY flag on a socket */ -bool SetSocketNoDelay(const SOCKET& hSocket); +/** Enable non-blocking mode for a socket */ +bool SetSocketNonBlocking(const SOCKET& hSocket); void InterruptSocks5(bool interrupt); /** diff --git a/src/netgroup.cpp b/src/netgroup.cpp new file mode 100644 index 0000000000..96b5e29684 --- /dev/null +++ b/src/netgroup.cpp @@ -0,0 +1,111 @@ +// 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 <netgroup.h> + +#include <hash.h> +#include <util/asmap.h> + +uint256 NetGroupManager::GetAsmapChecksum() const +{ + if (!m_asmap.size()) return {}; + + return SerializeHash(m_asmap); +} + +std::vector<unsigned char> NetGroupManager::GetGroup(const CNetAddr& address) const +{ + std::vector<unsigned char> vchRet; + // If non-empty asmap is supplied and the address is IPv4/IPv6, + // return ASN to be used for bucketing. + uint32_t asn = GetMappedAS(address); + if (asn != 0) { // Either asmap was empty, or address has non-asmappable net class (e.g. TOR). + vchRet.push_back(NET_IPV6); // IPv4 and IPv6 with same ASN should be in the same bucket + for (int i = 0; i < 4; i++) { + vchRet.push_back((asn >> (8 * i)) & 0xFF); + } + return vchRet; + } + + vchRet.push_back(address.GetNetClass()); + int nStartByte{0}; + int nBits{0}; + + if (address.IsLocal()) { + // all local addresses belong to the same group + } else if (address.IsInternal()) { + // All internal-usage addresses get their own group. + // Skip over the INTERNAL_IN_IPV6_PREFIX returned by CAddress::GetAddrBytes(). + nStartByte = INTERNAL_IN_IPV6_PREFIX.size(); + nBits = ADDR_INTERNAL_SIZE * 8; + } else if (!address.IsRoutable()) { + // all other unroutable addresses belong to the same group + } else if (address.HasLinkedIPv4()) { + // IPv4 addresses (and mapped IPv4 addresses) use /16 groups + uint32_t ipv4 = address.GetLinkedIPv4(); + vchRet.push_back((ipv4 >> 24) & 0xFF); + vchRet.push_back((ipv4 >> 16) & 0xFF); + return vchRet; + } else if (address.IsTor() || address.IsI2P()) { + nBits = 4; + } else if (address.IsCJDNS()) { + // Treat in the same way as Tor and I2P because the address in all of + // them is "random" bytes (derived from a public key). However in CJDNS + // the first byte is a constant 0xfc, so the random bytes come after it. + // Thus skip the constant 8 bits at the start. + nBits = 12; + } else if (address.IsHeNet()) { + // for he.net, use /36 groups + nBits = 36; + } else { + // for the rest of the IPv6 network, use /32 groups + nBits = 32; + } + + // Push our address onto vchRet. + auto addr_bytes = address.GetAddrBytes(); + const size_t num_bytes = nBits / 8; + vchRet.insert(vchRet.end(), addr_bytes.begin() + nStartByte, addr_bytes.begin() + nStartByte + num_bytes); + nBits %= 8; + // ...for the last byte, push nBits and for the rest of the byte push 1's + if (nBits > 0) { + assert(num_bytes < addr_bytes.size()); + vchRet.push_back(addr_bytes[num_bytes + nStartByte] | ((1 << (8 - nBits)) - 1)); + } + + return vchRet; +} + +uint32_t NetGroupManager::GetMappedAS(const CNetAddr& address) const +{ + uint32_t net_class = address.GetNetClass(); + if (m_asmap.size() == 0 || (net_class != NET_IPV4 && net_class != NET_IPV6)) { + return 0; // Indicates not found, safe because AS0 is reserved per RFC7607. + } + std::vector<bool> ip_bits(128); + if (address.HasLinkedIPv4()) { + // For lookup, treat as if it was just an IPv4 address (IPV4_IN_IPV6_PREFIX + IPv4 bits) + for (int8_t byte_i = 0; byte_i < 12; ++byte_i) { + for (uint8_t bit_i = 0; bit_i < 8; ++bit_i) { + ip_bits[byte_i * 8 + bit_i] = (IPV4_IN_IPV6_PREFIX[byte_i] >> (7 - bit_i)) & 1; + } + } + uint32_t ipv4 = address.GetLinkedIPv4(); + for (int i = 0; i < 32; ++i) { + ip_bits[96 + i] = (ipv4 >> (31 - i)) & 1; + } + } else { + // Use all 128 bits of the IPv6 address otherwise + assert(address.IsIPv6()); + auto addr_bytes = address.GetAddrBytes(); + for (int8_t byte_i = 0; byte_i < 16; ++byte_i) { + uint8_t cur_byte = addr_bytes[byte_i]; + for (uint8_t bit_i = 0; bit_i < 8; ++bit_i) { + ip_bits[byte_i * 8 + bit_i] = (cur_byte >> (7 - bit_i)) & 1; + } + } + } + uint32_t mapped_as = Interpret(m_asmap, ip_bits); + return mapped_as; +} diff --git a/src/netgroup.h b/src/netgroup.h new file mode 100644 index 0000000000..2dd63ec66b --- /dev/null +++ b/src/netgroup.h @@ -0,0 +1,66 @@ +// Copyright (c) 2021 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_NETGROUP_H +#define BITCOIN_NETGROUP_H + +#include <netaddress.h> +#include <uint256.h> + +#include <vector> + +/** + * Netgroup manager + */ +class NetGroupManager { +public: + explicit NetGroupManager(std::vector<bool> asmap) + : m_asmap{std::move(asmap)} + {} + + /** Get a checksum identifying the asmap being used. */ + uint256 GetAsmapChecksum() const; + + /** + * Get the canonical identifier of the network group for address. + * + * The groups are assigned in a way where it should be costly for an attacker to + * obtain addresses with many different group identifiers, even if it is cheap + * to obtain addresses with the same identifier. + * + * @note No two connections will be attempted to addresses with the same network + * group. + */ + std::vector<unsigned char> GetGroup(const CNetAddr& address) const; + + /** + * Get the autonomous system on the BGP path to address. + * + * The ip->AS mapping depends on how asmap is constructed. + */ + uint32_t GetMappedAS(const CNetAddr& address) const; + +private: + /** Compressed IP->ASN mapping, loaded from a file when a node starts. + * + * This mapping is then used for bucketing nodes in Addrman and for + * ensuring we connect to a diverse set of peers in Connman. The map is + * empty if no file was provided. + * + * If asmap is provided, nodes will be bucketed by AS they belong to, in + * order to make impossible for a node to connect to several nodes hosted + * in a single AS. This is done in response to Erebus attack, but also to + * generally diversify the connections every node creates, especially + * useful when a large fraction of nodes operate under a couple of cloud + * providers. + * + * If a new asmap is provided, the existing addrman records are + * re-bucketed. + * + * This is initialized in the constructor, const, and therefore is + * thread-safe. */ + const std::vector<bool> m_asmap; +}; + +#endif // BITCOIN_NETGROUP_H diff --git a/src/node/blockstorage.cpp b/src/node/blockstorage.cpp index 8a99130fd0..42b6b017fe 100644 --- a/src/node/blockstorage.cpp +++ b/src/node/blockstorage.cpp @@ -21,54 +21,96 @@ #include <util/system.h> #include <validation.h> +#include <map> +#include <unordered_map> + namespace node { std::atomic_bool fImporting(false); std::atomic_bool fReindex(false); -bool fHavePruned = false; bool fPruneMode = false; uint64_t nPruneTarget = 0; +bool CBlockIndexWorkComparator::operator()(const CBlockIndex* pa, const CBlockIndex* pb) const +{ + // First sort by most total work, ... + if (pa->nChainWork > pb->nChainWork) return false; + if (pa->nChainWork < pb->nChainWork) return true; + + // ... then by earliest time received, ... + if (pa->nSequenceId < pb->nSequenceId) return false; + if (pa->nSequenceId > pb->nSequenceId) return true; + + // Use pointer address as tie breaker (should only happen with blocks + // loaded from disk, as those all have id 0). + if (pa < pb) return false; + if (pa > pb) return true; + + // Identical blocks. + return false; +} + +bool CBlockIndexHeightOnlyComparator::operator()(const CBlockIndex* pa, const CBlockIndex* pb) const +{ + return pa->nHeight < pb->nHeight; +} + static FILE* OpenUndoFile(const FlatFilePos& pos, bool fReadOnly = false); static FlatFileSeq BlockFileSeq(); static FlatFileSeq UndoFileSeq(); -CBlockIndex* BlockManager::LookupBlockIndex(const uint256& hash) const +std::vector<CBlockIndex*> BlockManager::GetAllBlockIndices() +{ + AssertLockHeld(cs_main); + std::vector<CBlockIndex*> rv; + rv.reserve(m_block_index.size()); + for (auto& [_, block_index] : m_block_index) { + rv.push_back(&block_index); + } + return rv; +} + +CBlockIndex* BlockManager::LookupBlockIndex(const uint256& hash) +{ + AssertLockHeld(cs_main); + BlockMap::iterator it = m_block_index.find(hash); + return it == m_block_index.end() ? nullptr : &it->second; +} + +const CBlockIndex* BlockManager::LookupBlockIndex(const uint256& hash) const { AssertLockHeld(cs_main); BlockMap::const_iterator it = m_block_index.find(hash); - return it == m_block_index.end() ? nullptr : it->second; + return it == m_block_index.end() ? nullptr : &it->second; } -CBlockIndex* BlockManager::AddToBlockIndex(const CBlockHeader& block) +CBlockIndex* BlockManager::AddToBlockIndex(const CBlockHeader& block, CBlockIndex*& best_header) { AssertLockHeld(cs_main); - // Check for duplicate - uint256 hash = block.GetHash(); - BlockMap::iterator it = m_block_index.find(hash); - if (it != m_block_index.end()) { - return it->second; + auto [mi, inserted] = m_block_index.try_emplace(block.GetHash(), block); + if (!inserted) { + return &mi->second; } + CBlockIndex* pindexNew = &(*mi).second; - // Construct new block index object - CBlockIndex* pindexNew = new CBlockIndex(block); // We assign the sequence id to blocks only when the full data is available, // to avoid miners withholding blocks but broadcasting headers, to get a // competitive advantage. pindexNew->nSequenceId = 0; - BlockMap::iterator mi = m_block_index.insert(std::make_pair(hash, pindexNew)).first; + pindexNew->phashBlock = &((*mi).first); BlockMap::iterator miPrev = m_block_index.find(block.hashPrevBlock); if (miPrev != m_block_index.end()) { - pindexNew->pprev = (*miPrev).second; + pindexNew->pprev = &(*miPrev).second; pindexNew->nHeight = pindexNew->pprev->nHeight + 1; pindexNew->BuildSkip(); } pindexNew->nTimeMax = (pindexNew->pprev ? std::max(pindexNew->pprev->nTimeMax, pindexNew->nTime) : pindexNew->nTime); pindexNew->nChainWork = (pindexNew->pprev ? pindexNew->pprev->nChainWork : 0) + GetBlockProof(*pindexNew); pindexNew->RaiseValidity(BLOCK_VALID_TREE); - if (pindexBestHeader == nullptr || pindexBestHeader->nChainWork < pindexNew->nChainWork) - pindexBestHeader = pindexNew; + if (best_header == nullptr || best_header->nChainWork < pindexNew->nChainWork) { + best_header = pindexNew; + } m_dirty_blockindex.insert(pindexNew); @@ -80,8 +122,8 @@ void BlockManager::PruneOneBlockFile(const int fileNumber) AssertLockHeld(cs_main); LOCK(cs_LastBlockFile); - for (const auto& entry : m_block_index) { - CBlockIndex* pindex = entry.second; + for (auto& entry : m_block_index) { + CBlockIndex* pindex = &entry.second; if (pindex->nFile == fileNumber) { pindex->nStatus &= ~BLOCK_HAVE_DATA; pindex->nStatus &= ~BLOCK_HAVE_UNDO; @@ -185,12 +227,17 @@ void BlockManager::FindFilesToPrune(std::set<int>& setFilesToPrune, uint64_t nPr } } - LogPrint(BCLog::PRUNE, "Prune: target=%dMiB actual=%dMiB diff=%dMiB max_prune_height=%d removed %d blk/rev pairs\n", + LogPrint(BCLog::PRUNE, "target=%dMiB actual=%dMiB diff=%dMiB max_prune_height=%d removed %d blk/rev pairs\n", nPruneTarget/1024/1024, nCurrentUsage/1024/1024, ((int64_t)nPruneTarget - (int64_t)nCurrentUsage)/1024/1024, nLastBlockWeCanPrune, count); } +void BlockManager::UpdatePruneLock(const std::string& name, const PruneLockInfo& lock_info) { + AssertLockHeld(::cs_main); + m_prune_locks[name] = lock_info; +} + CBlockIndex* BlockManager::InsertBlockIndex(const uint256& hash) { AssertLockHeld(cs_main); @@ -199,60 +246,27 @@ CBlockIndex* BlockManager::InsertBlockIndex(const uint256& hash) return nullptr; } - // Return existing - BlockMap::iterator mi = m_block_index.find(hash); - if (mi != m_block_index.end()) { - return (*mi).second; + const auto [mi, inserted]{m_block_index.try_emplace(hash)}; + CBlockIndex* pindex = &(*mi).second; + if (inserted) { + pindex->phashBlock = &((*mi).first); } - - // Create new - CBlockIndex* pindexNew = new CBlockIndex(); - mi = m_block_index.insert(std::make_pair(hash, pindexNew)).first; - pindexNew->phashBlock = &((*mi).first); - - return pindexNew; + return pindex; } -bool BlockManager::LoadBlockIndex( - const Consensus::Params& consensus_params, - ChainstateManager& chainman) +bool BlockManager::LoadBlockIndex(const Consensus::Params& consensus_params) { if (!m_block_tree_db->LoadBlockIndexGuts(consensus_params, [this](const uint256& hash) EXCLUSIVE_LOCKS_REQUIRED(cs_main) { return this->InsertBlockIndex(hash); })) { return false; } // Calculate nChainWork - std::vector<std::pair<int, CBlockIndex*>> vSortedByHeight; - vSortedByHeight.reserve(m_block_index.size()); - for (const std::pair<const uint256, CBlockIndex*>& item : m_block_index) { - CBlockIndex* pindex = item.second; - vSortedByHeight.push_back(std::make_pair(pindex->nHeight, pindex)); - } - sort(vSortedByHeight.begin(), vSortedByHeight.end()); - - // Find start of assumed-valid region. - int first_assumed_valid_height = std::numeric_limits<int>::max(); - - for (const auto& [height, block] : vSortedByHeight) { - if (block->IsAssumedValid()) { - auto chainstates = chainman.GetAll(); - - // If we encounter an assumed-valid block index entry, ensure that we have - // one chainstate that tolerates assumed-valid entries and another that does - // not (i.e. the background validation chainstate), since assumed-valid - // entries should always be pending validation by a fully-validated chainstate. - auto any_chain = [&](auto fnc) { return std::any_of(chainstates.cbegin(), chainstates.cend(), fnc); }; - assert(any_chain([](auto chainstate) { return chainstate->reliesOnAssumedValid(); })); - assert(any_chain([](auto chainstate) { return !chainstate->reliesOnAssumedValid(); })); - - first_assumed_valid_height = height; - break; - } - } + std::vector<CBlockIndex*> vSortedByHeight{GetAllBlockIndices()}; + std::sort(vSortedByHeight.begin(), vSortedByHeight.end(), + CBlockIndexHeightOnlyComparator()); - for (const std::pair<int, CBlockIndex*>& item : vSortedByHeight) { + for (CBlockIndex* pindex : vSortedByHeight) { if (ShutdownRequested()) return false; - CBlockIndex* pindex = item.second; pindex->nChainWork = (pindex->pprev ? pindex->pprev->nChainWork : 0) + GetBlockProof(*pindex); pindex->nTimeMax = (pindex->pprev ? std::max(pindex->pprev->nTimeMax, pindex->nTime) : pindex->nTime); @@ -276,69 +290,14 @@ bool BlockManager::LoadBlockIndex( pindex->nStatus |= BLOCK_FAILED_CHILD; m_dirty_blockindex.insert(pindex); } - if (pindex->IsAssumedValid() || - (pindex->IsValid(BLOCK_VALID_TRANSACTIONS) && - (pindex->HaveTxsDownloaded() || pindex->pprev == nullptr))) { - - // Fill each chainstate's block candidate set. Only add assumed-valid - // blocks to the tip candidate set if the chainstate is allowed to rely on - // assumed-valid blocks. - // - // If all setBlockIndexCandidates contained the assumed-valid blocks, the - // background chainstate's ActivateBestChain() call would add assumed-valid - // blocks to the chain (based on how FindMostWorkChain() works). Obviously - // we don't want this since the purpose of the background validation chain - // is to validate assued-valid blocks. - // - // Note: This is considering all blocks whose height is greater or equal to - // the first assumed-valid block to be assumed-valid blocks, and excluding - // them from the background chainstate's setBlockIndexCandidates set. This - // does mean that some blocks which are not technically assumed-valid - // (later blocks on a fork beginning before the first assumed-valid block) - // might not get added to the background chainstate, but this is ok, - // because they will still be attached to the active chainstate if they - // actually contain more work. - // - // Instead of this height-based approach, an earlier attempt was made at - // 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 : chainman.GetAll()) { - if (chainstate->reliesOnAssumedValid() || - pindex->nHeight < first_assumed_valid_height) { - chainstate->setBlockIndexCandidates.insert(pindex); - } - } - } - if (pindex->nStatus & BLOCK_FAILED_MASK && (!chainman.m_best_invalid || pindex->nChainWork > chainman.m_best_invalid->nChainWork)) { - chainman.m_best_invalid = pindex; - } if (pindex->pprev) { pindex->BuildSkip(); } - if (pindex->IsValid(BLOCK_VALID_TREE) && (pindexBestHeader == nullptr || CBlockIndexWorkComparator()(pindexBestHeader, pindex))) - pindexBestHeader = pindex; } return true; } -void BlockManager::Unload() -{ - m_blocks_unlinked.clear(); - - for (const BlockMap::value_type& entry : m_block_index) { - delete entry.second; - } - - m_block_index.clear(); - - m_blockfile_info.clear(); - m_last_blockfile = 0; - m_dirty_blockindex.clear(); - m_dirty_fileinfo.clear(); -} - bool BlockManager::WriteBlockIndexDB() { AssertLockHeld(::cs_main); @@ -360,9 +319,9 @@ bool BlockManager::WriteBlockIndexDB() return true; } -bool BlockManager::LoadBlockIndexDB(ChainstateManager& chainman) +bool BlockManager::LoadBlockIndexDB(const Consensus::Params& consensus_params) { - if (!LoadBlockIndex(::Params().GetConsensus(), chainman)) { + if (!LoadBlockIndex(consensus_params)) { return false; } @@ -386,10 +345,9 @@ bool BlockManager::LoadBlockIndexDB(ChainstateManager& chainman) // Check presence of blk files LogPrintf("Checking all blk files are present...\n"); std::set<int> setBlkDataFiles; - for (const std::pair<const uint256, CBlockIndex*>& item : m_block_index) { - CBlockIndex* pindex = item.second; - if (pindex->nStatus & BLOCK_HAVE_DATA) { - setBlkDataFiles.insert(pindex->nFile); + for (const auto& [_, block_index] : m_block_index) { + if (block_index.nStatus & BLOCK_HAVE_DATA) { + setBlkDataFiles.insert(block_index.nFile); } } for (std::set<int>::iterator it = setBlkDataFiles.begin(); it != setBlkDataFiles.end(); it++) { @@ -400,8 +358,8 @@ bool BlockManager::LoadBlockIndexDB(ChainstateManager& chainman) } // Check whether we have ever pruned block & undo files - m_block_tree_db->ReadFlag("prunedblockfiles", fHavePruned); - if (fHavePruned) { + m_block_tree_db->ReadFlag("prunedblockfiles", m_have_pruned); + if (m_have_pruned) { LogPrintf("LoadBlockIndexDB(): Block files have previously been pruned\n"); } @@ -413,13 +371,13 @@ bool BlockManager::LoadBlockIndexDB(ChainstateManager& chainman) return true; } -CBlockIndex* BlockManager::GetLastCheckpoint(const CCheckpointData& data) +const CBlockIndex* BlockManager::GetLastCheckpoint(const CCheckpointData& data) { const MapCheckpoints& checkpoints = data.mapCheckpoints; for (const MapCheckpoints::value_type& i : reverse_iterate(checkpoints)) { const uint256& hash = i.second; - CBlockIndex* pindex = LookupBlockIndex(hash); + const CBlockIndex* pindex = LookupBlockIndex(hash); if (pindex) { return pindex; } @@ -427,10 +385,20 @@ CBlockIndex* BlockManager::GetLastCheckpoint(const CCheckpointData& data) return nullptr; } -bool IsBlockPruned(const CBlockIndex* pblockindex) +bool BlockManager::IsBlockPruned(const CBlockIndex* pblockindex) +{ + AssertLockHeld(::cs_main); + return (m_have_pruned && !(pblockindex->nStatus & BLOCK_HAVE_DATA) && pblockindex->nTx > 0); +} + +const CBlockIndex* BlockManager::GetFirstStoredBlock(const CBlockIndex& start_block) { AssertLockHeld(::cs_main); - return (fHavePruned && !(pblockindex->nStatus & BLOCK_HAVE_DATA) && pblockindex->nTx > 0); + const CBlockIndex* last_block = &start_block; + while (last_block->pprev && (last_block->pprev->nStatus & BLOCK_HAVE_DATA)) { + last_block = last_block->pprev; + } + return last_block; } // If we're using -prune with -reindex, then delete block files that will be ignored by the @@ -447,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) && @@ -504,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(); @@ -856,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(); @@ -867,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))) { @@ -877,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; @@ -926,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 42e46797d2..9b76371aae 100644 --- a/src/node/blockstorage.h +++ b/src/node/blockstorage.h @@ -5,13 +5,16 @@ #ifndef BITCOIN_NODE_BLOCKSTORAGE_H #define BITCOIN_NODE_BLOCKSTORAGE_H +#include <attributes.h> +#include <chain.h> #include <fs.h> -#include <protocol.h> // For CMessageHeader::MessageStartChars +#include <protocol.h> #include <sync.h> #include <txdb.h> #include <atomic> #include <cstdint> +#include <unordered_map> #include <vector> extern RecursiveMutex cs_main; @@ -20,7 +23,6 @@ class ArgsManager; class BlockValidationState; class CBlock; class CBlockFileInfo; -class CBlockIndex; class CBlockUndo; class CChain; class CChainParams; @@ -45,19 +47,30 @@ static const unsigned int MAX_BLOCKFILE_SIZE = 0x8000000; // 128 MiB extern std::atomic_bool fImporting; extern std::atomic_bool fReindex; /** Pruning-related variables and constants */ -/** True if any block files have ever been pruned. */ -extern bool fHavePruned; /** 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; -typedef std::unordered_map<uint256, CBlockIndex*, BlockHasher> BlockMap; +// Because validation code takes pointers to the map's CBlockIndex objects, if +// we ever switch to another associative container, we need to either use a +// container that has stable addressing (true of all std associative +// containers), or make the key a `std::unique_ptr<CBlockIndex>` +using BlockMap = std::unordered_map<uint256, CBlockIndex, BlockHasher>; struct CBlockIndexWorkComparator { bool operator()(const CBlockIndex* pa, const CBlockIndex* pb) const; }; +struct CBlockIndexHeightOnlyComparator { + /* Only compares the height of two block indices, doesn't try to tie-break */ + bool operator()(const CBlockIndex* pa, const CBlockIndex* pb) const; +}; + +struct PruneLockInfo { + int height_first{std::numeric_limits<int>::max()}; //! Height of earliest block that should be kept and not pruned +}; + /** * Maintains a tree of blocks (stored in `m_block_index`) which is consulted * to determine where the most-work tip is. @@ -71,6 +84,13 @@ class BlockManager friend ChainstateManager; private: + /** + * Load the blocktree off disk and into memory. Populate certain metadata + * per index entry (nStatus, nChainWork, nTimeMax, etc.) as well as peripheral + * collections like m_dirty_blockindex. + */ + bool LoadBlockIndex(const Consensus::Params& consensus_params) + EXCLUSIVE_LOCKS_REQUIRED(cs_main); void FlushBlockFile(bool fFinalize = false, bool finalize_undo = false); void FlushUndoFile(int block_file, bool finalize = false); bool FindBlockPos(FlatFilePos& pos, unsigned int nAddSize, unsigned int nHeight, CChain& active_chain, uint64_t nTime, bool fKnown); @@ -111,9 +131,19 @@ private: /** Dirty block file entries. */ std::set<int> m_dirty_fileinfo; + /** + * Map from external index name to oldest block that must not be pruned. + * + * @note Internally, only blocks at height (height_first - PRUNE_LOCK_BUFFER - 1) and + * below will be pruned, but callers should avoid assuming any particular buffer size. + */ + std::unordered_map<std::string, PruneLockInfo> m_prune_locks GUARDED_BY(::cs_main); + public: BlockMap m_block_index GUARDED_BY(cs_main); + std::vector<CBlockIndex*> GetAllBlockIndices() EXCLUSIVE_LOCKS_REQUIRED(::cs_main); + /** * All pairs A->B, where A (or one of its ancestors) misses transactions, but B has transactions. * Pruned nodes may have entries where B is missing data. @@ -123,28 +153,17 @@ public: std::unique_ptr<CBlockTreeDB> m_block_tree_db GUARDED_BY(::cs_main); bool WriteBlockIndexDB() EXCLUSIVE_LOCKS_REQUIRED(::cs_main); - bool LoadBlockIndexDB(ChainstateManager& chainman) EXCLUSIVE_LOCKS_REQUIRED(::cs_main); + bool LoadBlockIndexDB(const Consensus::Params& consensus_params) EXCLUSIVE_LOCKS_REQUIRED(::cs_main); - /** - * Load the blocktree off disk and into memory. Populate certain metadata - * per index entry (nStatus, nChainWork, nTimeMax, etc.) as well as peripheral - * collections like m_dirty_blockindex. - */ - bool LoadBlockIndex( - const Consensus::Params& consensus_params, - ChainstateManager& chainman) EXCLUSIVE_LOCKS_REQUIRED(cs_main); - - /** Clear all data members. */ - void Unload() EXCLUSIVE_LOCKS_REQUIRED(cs_main); - - CBlockIndex* AddToBlockIndex(const CBlockHeader& block) EXCLUSIVE_LOCKS_REQUIRED(cs_main); + CBlockIndex* AddToBlockIndex(const CBlockHeader& block, CBlockIndex*& best_header) EXCLUSIVE_LOCKS_REQUIRED(cs_main); /** Create a new block index entry for a given block hash */ CBlockIndex* InsertBlockIndex(const uint256& hash) EXCLUSIVE_LOCKS_REQUIRED(cs_main); //! Mark one block file as pruned (modify associated database entries) void PruneOneBlockFile(const int fileNumber) EXCLUSIVE_LOCKS_REQUIRED(cs_main); - CBlockIndex* LookupBlockIndex(const uint256& hash) const EXCLUSIVE_LOCKS_REQUIRED(cs_main); + CBlockIndex* LookupBlockIndex(const uint256& hash) EXCLUSIVE_LOCKS_REQUIRED(cs_main); + const CBlockIndex* LookupBlockIndex(const uint256& hash) const EXCLUSIVE_LOCKS_REQUIRED(cs_main); /** Get block file info entry for one block file */ CBlockFileInfo* GetBlockFileInfo(size_t n); @@ -158,16 +177,20 @@ public: uint64_t CalculateCurrentUsage(); //! Returns last CBlockIndex* that is a checkpoint - CBlockIndex* GetLastCheckpoint(const CCheckpointData& data) EXCLUSIVE_LOCKS_REQUIRED(cs_main); + const CBlockIndex* GetLastCheckpoint(const CCheckpointData& data) EXCLUSIVE_LOCKS_REQUIRED(cs_main); - ~BlockManager() - { - Unload(); - } -}; + //! Find the first block that is not pruned + const CBlockIndex* GetFirstStoredBlock(const CBlockIndex& start_block LIFETIMEBOUND) EXCLUSIVE_LOCKS_REQUIRED(::cs_main); -//! Check whether the block associated with this index entry is pruned or not. -bool IsBlockPruned(const CBlockIndex* pblockindex) EXCLUSIVE_LOCKS_REQUIRED(::cs_main); + /** True if any block files have ever been pruned. */ + bool m_have_pruned = false; + + //! Check whether the block associated with this index entry is pruned or not. + bool IsBlockPruned(const CBlockIndex* pblockindex) EXCLUSIVE_LOCKS_REQUIRED(::cs_main); + + //! Create or update a prune lock identified by its name + void UpdatePruneLock(const std::string& name, const PruneLockInfo& lock_info) EXCLUSIVE_LOCKS_REQUIRED(::cs_main); +}; void CleanupBlockRevFiles(); @@ -188,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/chainstate.cpp b/src/node/chainstate.cpp index d03b9dcac6..ad9293f172 100644 --- a/src/node/chainstate.cpp +++ b/src/node/chainstate.cpp @@ -4,69 +4,75 @@ #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, - const Consensus::Params& consensus_params, - 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(); + return options.reindex || options.reindex_chainstate || chainstate->CoinsTip().GetBestBlock().IsNull(); }; LOCK(cs_main); - chainman.InitializeChainstate(mempool); - chainman.m_total_coinstip_cache = nCoinCacheUsage; - chainman.m_total_coinsdb_cache = nCoinDBCache; - - UnloadBlockIndex(mempool, chainman); + 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 fHavePruned if we've ever removed a + // 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(consensus_params.hashGenesisBlock)) { - return ChainstateLoadingError::ERROR_BAD_GENESIS_BLOCK; + !chainman.m_blockman.LookupBlockIndex(chainman.GetConsensus().hashGenesisBlock)) { + // 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 (fHavePruned && !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. @@ -74,7 +80,7 @@ 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 @@ -82,59 +88,56 @@ std::optional<ChainstateLoadingError> LoadChainstate(bool fReset, for (CChainState* 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); } - // If necessary, upgrade from older database format. + // Refuse to load unsupported database format. // This is a no-op if we cleared the coinsviewdb with -reindex or -reindex-chainstate - if (!chainstate->CoinsDB().Upgrade()) { - return ChainstateLoadingError::ERROR_CHAINSTATE_UPGRADE_FAILED; + if (chainstate->CoinsDB().NeedsUpgrade()) { + 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; - } + 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, - const Consensus::Params& consensus_params, - int check_blocks, - int check_level, - std::function<int64_t()> get_unix_time_seconds) +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(); + return options.reindex || options.reindex_chainstate || chainstate->CoinsTip().GetBestBlock().IsNull(); }; LOCK(cs_main); @@ -142,19 +145,21 @@ std::optional<ChainstateLoadVerifyError> VerifyLoadedChainstate(ChainstateManage for (CChainState* chainstate : chainman.GetAll()) { if (!is_coinsview_empty(chainstate)) { const CBlockIndex* tip = chainstate->m_chain.Tip(); - if (tip && tip->nTime > get_unix_time_seconds() + MAX_FUTURE_BLOCK_TIME) { - return ChainstateLoadVerifyError::ERROR_BLOCK_FROM_FUTURE; + if (tip && tip->nTime > GetTime() + MAX_FUTURE_BLOCK_TIME) { + 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, consensus_params, chainstate->CoinsDB(), - check_level, - check_blocks)) { - return ChainstateLoadVerifyError::ERROR_CORRUPTED_BLOCK_DB; + *chainstate, chainman.GetConsensus(), chainstate->CoinsDB(), + 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 8ba04f1436..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,48 +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, - const Consensus::Params& consensus_params, - 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, - const Consensus::Params& consensus_params, - int check_blocks, - int check_level, - std::function<int64_t()> get_unix_time_seconds); +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/context.cpp b/src/node/context.cpp index 893c32f1bc..d80b8ca7a7 100644 --- a/src/node/context.cpp +++ b/src/node/context.cpp @@ -7,14 +7,16 @@ #include <addrman.h> #include <banman.h> #include <interfaces/chain.h> +#include <kernel/context.h> #include <net.h> #include <net_processing.h> +#include <netgroup.h> #include <policy/fees.h> #include <scheduler.h> #include <txmempool.h> #include <validation.h> namespace node { -NodeContext::NodeContext() {} -NodeContext::~NodeContext() {} +NodeContext::NodeContext() = default; +NodeContext::~NodeContext() = default; } // namespace node diff --git a/src/node/context.h b/src/node/context.h index 644c997531..31be308787 100644 --- a/src/node/context.h +++ b/src/node/context.h @@ -5,6 +5,8 @@ #ifndef BITCOIN_NODE_CONTEXT_H #define BITCOIN_NODE_CONTEXT_H +#include <kernel/context.h> + #include <cassert> #include <functional> #include <memory> @@ -18,6 +20,7 @@ class CConnman; class CScheduler; class CTxMemPool; class ChainstateManager; +class NetGroupManager; class PeerManager; namespace interfaces { class Chain; @@ -38,11 +41,14 @@ namespace node { //! any member functions. It should just be a collection of references that can //! be used without pulling in unwanted dependencies or functionality. struct NodeContext { + //! libbitcoin_kernel context + std::unique_ptr<kernel::Context> kernel; //! Init interface for initializing current process and connecting to other processes. interfaces::Init* init{nullptr}; std::unique_ptr<AddrMan> addrman; std::unique_ptr<CConnman> connman; std::unique_ptr<CTxMemPool> mempool; + std::unique_ptr<const NetGroupManager> netgroupman; std::unique_ptr<CBlockPolicyEstimator> fee_estimator; std::unique_ptr<PeerManager> peerman; std::unique_ptr<ChainstateManager> chainman; 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..370cde84f8 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> diff --git a/src/node/ui_interface.h b/src/node/interface_ui.h index d02238b549..37c0f6392b 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> @@ -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 cb063ae9f8..2c845d0127 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)); } @@ -90,8 +89,16 @@ public: uint32_t getLogCategories() override { return LogInstance().GetCategoryMask(); } bool baseInitialize() override { - return AppInitBasicSetup(gArgs) && AppInitParameterInteraction(gArgs) && AppInitSanityChecks() && - AppInitLockDataDirectory() && AppInitInterfaces(*m_context); + if (!AppInitBasicSetup(gArgs)) return false; + if (!AppInitParameterInteraction(gArgs, /*use_syscall_sandbox=*/false)) return false; + + m_context->kernel = std::make_unique<kernel::Context>(); + if (!AppInitSanityChecks(*m_context->kernel)) return false; + + if (!AppInitLockDataDirectory()) return false; + if (!AppInitInterfaces(*m_context)) return false; + + return true; } bool appInitMain(interfaces::BlockAndHeaderTipInfo* tip_info) override { @@ -112,6 +119,46 @@ public: } } bool shutdownRequested() override { return ShutdownRequested(); } + bool isSettingIgnored(const std::string& name) override + { + bool ignored = false; + gArgs.LockSettings([&](util::Settings& settings) { + if (auto* options = util::FindKey(settings.command_line_options, name)) { + ignored = !options->empty(); + } + }); + return ignored; + } + util::SettingsValue getPersistentSetting(const std::string& name) override { return gArgs.GetPersistentSetting(name); } + void updateRwSetting(const std::string& name, const util::SettingsValue& value) override + { + gArgs.LockSettings([&](util::Settings& settings) { + if (value.isNull()) { + settings.rw_settings.erase(name); + } else { + settings.rw_settings[name] = value; + } + }); + gArgs.WriteSettingsFile(); + } + void forceSetting(const std::string& name, const util::SettingsValue& value) override + { + gArgs.LockSettings([&](util::Settings& settings) { + if (value.isNull()) { + settings.forced_settings.erase(name); + } else { + settings.forced_settings[name] = value; + } + }); + } + void resetSettings() override + { + gArgs.WriteSettingsFile(/*errors=*/nullptr, /*backup=*/true); + gArgs.LockSettings([&](util::Settings& settings) { + settings.rw_settings.clear(); + }); + gArgs.WriteSettingsFile(); + } void mapPort(bool use_upnp, bool use_natpmp) override { StartMapPort(use_upnp, use_natpmp); } bool getProxy(Network net, Proxy& proxy_info) override { return GetProxy(net, proxy_info); } size_t getNodeCount(ConnectionDirection flags) override @@ -212,9 +259,10 @@ public: bool getHeaderTip(int& height, int64_t& block_time) override { LOCK(::cs_main); - if (::pindexBestHeader) { - height = ::pindexBestHeader->nHeight; - block_time = ::pindexBestHeader->GetBlockTime(); + auto best_header = chainman().m_best_header; + if (best_header) { + height = best_header->nHeight; + block_time = best_header->GetBlockTime(); return true; } return false; @@ -227,7 +275,7 @@ public: uint256 getBestBlockHash() override { const CBlockIndex* tip = WITH_LOCK(::cs_main, return chainman().ActiveChain().Tip()); - return tip ? tip->GetBlockHash() : Params().GenesisBlock().GetHash(); + return tip ? tip->GetBlockHash() : chainman().GetParams().GenesisBlock().GetHash(); } int64_t getLastBlockTime() override { @@ -235,16 +283,11 @@ public: if (chainman().ActiveChain().Tip()) { return chainman().ActiveChain().Tip()->GetBlockTime(); } - return Params().GenesisBlock().GetBlockTime(); // Genesis block's time of current network + return chainman().GetParams().GenesisBlock().GetBlockTime(); // Genesis block's time of current network } double getVerificationProgress() override { - const CBlockIndex* tip; - { - LOCK(::cs_main); - tip = chainman().ActiveChain().Tip(); - } - return GuessVerificationProgress(Params().TxData(), tip); + return GuessVerificationProgress(chainman().GetParams().TxData(), WITH_LOCK(::cs_main, return chainman().ActiveChain().Tip())); } bool isInitialBlockDownload() override { return chainman().ActiveChainstate().IsInitialBlockDownload(); @@ -258,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; @@ -340,6 +387,7 @@ public: { m_context = context; } + ChainstateManager& chainman() { return *Assert(m_context->chainman); } NodeContext* m_context{nullptr}; }; @@ -352,6 +400,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 = active.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); @@ -377,11 +426,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 { @@ -425,7 +474,7 @@ public: // try to handle the request. Otherwise, reraise the exception. if (!last_handler) { const UniValue& code = e["code"]; - if (code.isNum() && code.get_int() == RPC_WALLET_NOT_FOUND) { + if (code.isNum() && code.getInt<int>() == RPC_WALLET_NOT_FOUND) { return false; } } @@ -451,46 +500,40 @@ 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); + if (!index) return {}; + return chainman().ActiveChain().GetLocator(index); } std::optional<int> findLocatorFork(const CBlockLocator& locator) override { - LOCK(cs_main); - const CChainState& active = Assert(m_node.chainman)->ActiveChainstate(); - if (CBlockIndex* fork = active.FindForkInGlobalIndex(locator)) { + LOCK(::cs_main); + if (const CBlockIndex* fork = chainman().ActiveChainstate().FindForkInGlobalIndex(locator)) { return fork->nHeight; } return std::nullopt; @@ -498,20 +541,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); } @@ -521,18 +563,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 @@ -544,8 +585,8 @@ public: void findCoins(std::map<COutPoint, Coin>& coins) override { return FindCoins(m_node, coins); } double guessVerificationProgress(const uint256& block_hash) override { - LOCK(cs_main); - return GuessVerificationProgress(Params().TxData(), chainman().m_blockman.LookupBlockIndex(block_hash)); + 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 { @@ -557,7 +598,7 @@ public: // used to limit the range, and passing min_height that's too low or // max_height that's too high will not crash or change the result. LOCK(::cs_main); - if (CBlockIndex* block = chainman().m_blockman.LookupBlockIndex(block_hash)) { + if (const CBlockIndex* block = chainman().m_blockman.LookupBlockIndex(block_hash)) { if (max_height && block->nHeight >= *max_height) block = block->GetAncestor(*max_height); for (; block->nStatus & BLOCK_HAVE_DATA; block = block->pprev) { // Check pprev to not segfault if min_height is too low @@ -590,7 +631,7 @@ public: bool relay, std::string& err_string) override { - const TransactionError err = BroadcastTransaction(m_node, tx, err_string, max_tx_fee, relay, /*wait_callback*/ false); + const TransactionError err = BroadcastTransaction(m_node, tx, err_string, max_tx_fee, relay, /*wait_callback=*/false); // Chain clients only care about failures to accept the tx to the mempool. Disregard non-mempool related failures. // Note: this will need to be updated if BroadcastTransactions() is updated to return other non-mempool failures // that Chain clients do not need to know about. @@ -604,8 +645,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 { @@ -613,15 +658,12 @@ 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.ancestor_count, limits.ancestor_size_vbytes, + limits.descendant_count, limits.descendant_size_vbytes, unused_error_string); } CFeeRate estimateSmartFee(int num_blocks, bool conservative, FeeCalculation* calc) override { @@ -636,15 +678,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 node::fHavePruned; + LOCK(::cs_main); + return chainman().m_blockman.m_have_pruned; } bool isReadyToBroadcast() override { return !node::fImporting && !node::fReindex && !isInitialBlockDownload(); } bool isInitialBlockDownload() override { @@ -664,11 +718,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 @@ -718,6 +768,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..60993f1d8d --- /dev/null +++ b/src/node/mempool_args.cpp @@ -0,0 +1,99 @@ +// 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 <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 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 (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 7fe10ecabc..9db10feae4 100644 --- a/src/node/miner.cpp +++ b/src/node/miner.cpp @@ -50,8 +50,8 @@ void RegenerateCommitments(CBlock& block, ChainstateManager& chainman) tx.vout.erase(tx.vout.begin() + GetWitnessCommitmentIndex(block)); block.vtx.at(0) = MakeTransactionRef(tx); - CBlockIndex* prev_block = WITH_LOCK(::cs_main, return chainman.m_blockman.LookupBlockIndex(block.hashPrevBlock)); - GenerateCoinbaseCommitment(block, prev_block, Params().GetConsensus()); + const CBlockIndex* prev_block = WITH_LOCK(::cs_main, return chainman.m_blockman.LookupBlockIndex(block.hashPrevBlock)); + chainman.GenerateCoinbaseCommitment(block, prev_block); block.hashMerkleRoot = BlockMerkleRoot(block); } @@ -62,8 +62,8 @@ BlockAssembler::Options::Options() nBlockMaxWeight = DEFAULT_BLOCK_MAX_WEIGHT; } -BlockAssembler::BlockAssembler(CChainState& chainstate, const CTxMemPool& mempool, const CChainParams& params, const Options& options) - : chainparams(params), +BlockAssembler::BlockAssembler(CChainState& chainstate, const CTxMemPool* mempool, const Options& options) + : chainparams{chainstate.m_chainman.GetParams()}, m_mempool(mempool), m_chainstate(chainstate) { @@ -87,8 +87,8 @@ static BlockAssembler::Options DefaultOptions() return options; } -BlockAssembler::BlockAssembler(CChainState& chainstate, const CTxMemPool& mempool, const CChainParams& params) - : BlockAssembler(chainstate, mempool, params, DefaultOptions()) {} +BlockAssembler::BlockAssembler(CChainState& chainstate, const CTxMemPool* mempool) + : BlockAssembler(chainstate, mempool, DefaultOptions()) {} void BlockAssembler::resetBlock() { @@ -97,7 +97,6 @@ void BlockAssembler::resetBlock() // Reserve space for coinbase tx nBlockWeight = 4000; nBlockSigOpsCost = 400; - fIncludeWitness = false; // These counters do not include coinbase tx nBlockTx = 0; @@ -122,12 +121,12 @@ 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; - pblock->nVersion = g_versionbitscache.ComputeBlockVersion(pindexPrev, chainparams.GetConsensus()); + pblock->nVersion = m_chainstate.m_chainman.m_versionbitscache.ComputeBlockVersion(pindexPrev, chainparams.GetConsensus()); // -regtest only: allow overriding block.nVersion with // -blockversion=N to test forking scenarios if (chainparams.MineBlocksOnDemand()) { @@ -137,20 +136,12 @@ std::unique_ptr<CBlockTemplate> BlockAssembler::CreateNewBlock(const CScript& sc pblock->nTime = GetAdjustedTime(); m_lock_time_cutoff = pindexPrev->GetMedianTimePast(); - // Decide whether to include witness transactions - // This is only needed in case the witness softfork activation is reverted - // (which would require a very deep reorganization). - // Note that the mempool would accept transactions with witness data before - // the deployment is active, but we would only ever mine blocks after activation - // unless there is a massive block reorganization with the witness softfork - // not activated. - // TODO: replace this with a call to main to assess validity of a mempool - // transaction (which in most cases can be a no-op). - fIncludeWitness = DeploymentActiveAfter(pindexPrev, chainparams.GetConsensus(), Consensus::DEPLOYMENT_SEGWIT); - 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(); @@ -166,7 +157,7 @@ std::unique_ptr<CBlockTemplate> BlockAssembler::CreateNewBlock(const CScript& sc coinbaseTx.vout[0].nValue = nFees + GetBlockSubsidy(nHeight, chainparams.GetConsensus()); coinbaseTx.vin[0].scriptSig = CScript() << nHeight << OP_0; pblock->vtx[0] = MakeTransactionRef(std::move(coinbaseTx)); - pblocktemplate->vchCoinbaseCommitment = GenerateCoinbaseCommitment(*pblock, pindexPrev, chainparams.GetConsensus()); + pblocktemplate->vchCoinbaseCommitment = m_chainstate.m_chainman.GenerateCoinbaseCommitment(*pblock, pindexPrev); pblocktemplate->vTxFees[0] = -nFees; LogPrintf("CreateNewBlock(): block weight: %u txs: %u fees: %ld sigops %d\n", GetBlockWeight(*pblock), nBlockTx, nFees, nBlockSigOpsCost); @@ -179,7 +170,7 @@ std::unique_ptr<CBlockTemplate> BlockAssembler::CreateNewBlock(const CScript& sc pblocktemplate->vTxSigOpsCost[0] = WITNESS_SCALE_FACTOR * GetLegacySigOpCount(*pblock->vtx[0]); BlockValidationState state; - if (!TestBlockValidity(state, chainparams, m_chainstate, *pblock, pindexPrev, false, false)) { + 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(); @@ -215,17 +206,12 @@ bool BlockAssembler::TestPackage(uint64_t packageSize, int64_t packageSigOpsCost // Perform transaction-level checks before adding to block: // - transaction finality (locktime) -// - premature witness (in case segwit transactions are added to mempool before -// segwit activation) bool BlockAssembler::TestPackageTransactions(const CTxMemPool::setEntries& package) const { for (CTxMemPool::txiter it : package) { if (!IsFinalTx(it->GetTx(), nHeight, m_lock_time_cutoff)) { return false; } - if (!fIncludeWitness && it->GetTx().HasWitness()) { - return false; - } } return true; } @@ -249,15 +235,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)) { @@ -279,23 +269,6 @@ int BlockAssembler::UpdatePackagesForAdded(const CTxMemPool::setEntries& already 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 @@ -317,9 +290,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 @@ -327,11 +300,7 @@ void BlockAssembler::addPackageTxs(int& nPackagesSelected, int& nDescendantsUpda // Keep track of entries that failed inclusion, to avoid duplicate work CTxMemPool::setEntries failedTx; - // Start by adding all descendants of previously added txs to mapModifiedTx - // and modifying them for their already included ancestors - UpdatePackagesForAdded(inBlock, mapModifiedTx); - - 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 @@ -340,12 +309,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: @@ -353,13 +337,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 @@ -414,7 +398,7 @@ 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, nNoLimit, nNoLimit, nNoLimit, nNoLimit, dummy, false); onlyUnconfirmed(ancestors); ancestors.insert(iter); @@ -444,25 +428,7 @@ void BlockAssembler::addPackageTxs(int& nPackagesSelected, int& nDescendantsUpda ++nPackagesSelected; // Update transactions that depend on each of these - nDescendantsUpdated += UpdatePackagesForAdded(ancestors, mapModifiedTx); - } -} - -void IncrementExtraNonce(CBlock* pblock, const CBlockIndex* pindexPrev, unsigned int& nExtraNonce) -{ - // Update nExtraNonce - static uint256 hashPrevBlock; - if (hashPrevBlock != pblock->hashPrevBlock) { - nExtraNonce = 0; - hashPrevBlock = pblock->hashPrevBlock; + nDescendantsUpdated += UpdatePackagesForAdded(mempool, ancestors, mapModifiedTx); } - ++nExtraNonce; - unsigned int nHeight = pindexPrev->nHeight + 1; // Height first in coinbase required for block.version=2 - CMutableTransaction txCoinbase(*pblock->vtx[0]); - txCoinbase.vin[0].scriptSig = (CScript() << nHeight << CScriptNum(nExtraNonce)); - assert(txCoinbase.vin[0].scriptSig.size() <= 100); - - pblock->vtx[0] = MakeTransactionRef(std::move(txCoinbase)); - pblock->hashMerkleRoot = BlockMerkleRoot(*pblock); } } // namespace node diff --git a/src/node/miner.h b/src/node/miner.h index c96da874a7..26454df3df 100644 --- a/src/node/miner.h +++ b/src/node/miner.h @@ -45,7 +45,7 @@ struct CTxMemPoolModifiedEntry { nSigOpCostWithAncestors = entry->GetSigOpCostWithAncestors(); } - int64_t GetModifiedFee() const { return iter->GetModifiedFee(); } + CAmount GetModifiedFee() const { return iter->GetModifiedFee(); } uint64_t GetSizeWithAncestors() const { return nSizeWithAncestors; } CAmount GetModFeesWithAncestors() const { return nModFeesWithAncestors; } size_t GetTxSize() const { return iter->GetTxSize(); } @@ -116,7 +116,7 @@ struct update_for_parent_inclusion void operator() (CTxMemPoolModifiedEntry &e) { - e.nModFeesWithAncestors -= iter->GetFee(); + e.nModFeesWithAncestors -= iter->GetModifiedFee(); e.nSizeWithAncestors -= iter->GetTxSize(); e.nSigOpCostWithAncestors -= iter->GetSigOpCost(); } @@ -132,7 +132,6 @@ private: std::unique_ptr<CBlockTemplate> pblocktemplate; // Configuration parameters for the block size - bool fIncludeWitness; unsigned int nBlockMaxWeight; CFeeRate blockMinFeeRate; @@ -148,7 +147,7 @@ private: int64_t m_lock_time_cutoff; const CChainParams& chainparams; - const CTxMemPool& m_mempool; + const CTxMemPool* const m_mempool; CChainState& m_chainstate; public: @@ -158,8 +157,8 @@ public: CFeeRate blockMinFeeRate; }; - explicit BlockAssembler(CChainState& chainstate, const CTxMemPool& mempool, const CChainParams& params); - explicit BlockAssembler(CChainState& chainstate, const CTxMemPool& mempool, const CChainParams& params, const Options& options); + explicit BlockAssembler(CChainState& chainstate, const CTxMemPool* mempool); + explicit BlockAssembler(CChainState& chainstate, const CTxMemPool* mempool, const Options& options); /** Construct a new block template with coinbase to scriptPubKeyIn */ std::unique_ptr<CBlockTemplate> CreateNewBlock(const CScript& scriptPubKeyIn); @@ -178,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 */ @@ -190,19 +189,10 @@ 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); }; -/** Modify the extranonce in a block */ -void IncrementExtraNonce(CBlock* pblock, const CBlockIndex* pindexPrev, unsigned int& nExtraNonce); int64_t UpdateTime(CBlockHeader* pblock, const Consensus::Params& consensusParams, const CBlockIndex* pindexPrev); /** Update an old GenerateCoinbaseCommitment from CreateNewBlock after the block txs have changed */ 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/transaction.h b/src/node/transaction.h index b7cf225636..0604754a46 100644 --- a/src/node/transaction.h +++ b/src/node/transaction.h @@ -5,7 +5,6 @@ #ifndef BITCOIN_NODE_TRANSACTION_H #define BITCOIN_NODE_TRANSACTION_H -#include <attributes.h> #include <policy/feerate.h> #include <primitives/transaction.h> #include <util/error.h> 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 66fe489bb0..c59262591b 100644 --- a/src/outputtype.h +++ b/src/outputtype.h @@ -6,7 +6,6 @@ #ifndef BITCOIN_OUTPUTTYPE_H #define BITCOIN_OUTPUTTYPE_H -#include <attributes.h> #include <script/signingprovider.h> #include <script/standard.h> @@ -20,6 +19,7 @@ enum class OutputType { P2SH_SEGWIT, BECH32, BECH32M, + UNKNOWN, }; static constexpr auto OUTPUT_TYPES = std::array{ diff --git a/src/policy/feerate.cpp b/src/policy/feerate.cpp index 0ea56d8db7..82b767793d 100644 --- a/src/policy/feerate.cpp +++ b/src/policy/feerate.cpp @@ -3,8 +3,8 @@ // 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 <policy/feerate.h> - #include <tinyformat.h> #include <cmath> diff --git a/src/policy/feerate.h b/src/policy/feerate.h index 50fd6fd11b..a8d4d2fc63 100644 --- a/src/policy/feerate.h +++ b/src/policy/feerate.h @@ -9,7 +9,10 @@ #include <consensus/amount.h> #include <serialize.h> + +#include <cstdint> #include <string> +#include <type_traits> const std::string CURRENCY_UNIT = "BTC"; // One formatted unit const std::string CURRENCY_ATOM = "sat"; // One indivisible minimum value unit diff --git a/src/policy/fees.cpp b/src/policy/fees.cpp index 6499dbd97f..2b940be07e 100644 --- a/src/policy/fees.cpp +++ b/src/policy/fees.cpp @@ -6,14 +6,30 @@ #include <policy/fees.h> #include <clientversion.h> +#include <consensus/amount.h> #include <fs.h> #include <logging.h> +#include <policy/feerate.h> +#include <primitives/transaction.h> +#include <random.h> +#include <serialize.h> #include <streams.h> +#include <sync.h> +#include <tinyformat.h> #include <txmempool.h> +#include <uint256.h> #include <util/serfloat.h> #include <util/system.h> +#include <util/time.h> -static const char* FEE_ESTIMATES_FILENAME = "fee_estimates.dat"; +#include <algorithm> +#include <cassert> +#include <cmath> +#include <cstddef> +#include <cstdint> +#include <exception> +#include <stdexcept> +#include <utility> static constexpr double INF_FEERATE = 1e99; @@ -145,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); }; @@ -374,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; @@ -384,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 @@ -511,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; @@ -530,16 +546,13 @@ 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)); } } -CBlockPolicyEstimator::~CBlockPolicyEstimator() -{ -} +CBlockPolicyEstimator::~CBlockPolicyEstimator() = default; void CBlockPolicyEstimator::processTransaction(const CTxMemPoolEntry& entry, bool validFeeEstimate) { @@ -891,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); @@ -923,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); diff --git a/src/policy/fees.h b/src/policy/fees.h index 6e25bb42b8..e4628bf853 100644 --- a/src/policy/fees.h +++ b/src/policy/fees.h @@ -6,21 +6,22 @@ #define BITCOIN_POLICY_FEES_H #include <consensus/amount.h> +#include <fs.h> #include <policy/feerate.h> -#include <uint256.h> #include <random.h> #include <sync.h> +#include <threadsafety.h> +#include <uint256.h> #include <array> #include <map> #include <memory> +#include <set> #include <string> #include <vector> -class CAutoFile; -class CFeeRate; +class AutoFile; class CTxMemPoolEntry; -class CTxMemPool; class TxConfirmStats; /* Identifier for each of the 3 different TxConfirmStats which will track @@ -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.cpp b/src/policy/packages.cpp index 21f5488816..67918c9dec 100644 --- a/src/policy/packages.cpp +++ b/src/policy/packages.cpp @@ -2,12 +2,16 @@ // 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 <policy/packages.h> +#include <policy/policy.h> #include <primitives/transaction.h> #include <uint256.h> #include <util/hasher.h> +#include <algorithm> +#include <cassert> +#include <iterator> +#include <memory> #include <numeric> #include <unordered_set> diff --git a/src/policy/packages.h b/src/policy/packages.h index 9f274f6b7d..36c70e9e66 100644 --- a/src/policy/packages.h +++ b/src/policy/packages.h @@ -5,10 +5,12 @@ #ifndef BITCOIN_POLICY_PACKAGES_H #define BITCOIN_POLICY_PACKAGES_H +#include <consensus/consensus.h> #include <consensus/validation.h> #include <policy/policy.h> #include <primitives/transaction.h> +#include <cstdint> #include <vector> /** Default maximum number of transactions in a package. */ @@ -17,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 6aba6a4a5b..5086542865 100644 --- a/src/policy/policy.cpp +++ b/src/policy/policy.cpp @@ -7,10 +7,22 @@ #include <policy/policy.h> -#include <consensus/validation.h> #include <coins.h> +#include <consensus/amount.h> +#include <consensus/consensus.h> +#include <consensus/validation.h> +#include <policy/feerate.h> +#include <primitives/transaction.h> +#include <script/interpreter.h> +#include <script/script.h> +#include <script/standard.h> +#include <serialize.h> #include <span.h> +#include <algorithm> +#include <cstddef> +#include <vector> + CAmount GetDustThreshold(const CTxOut& txout, const CFeeRate& dustRelayFeeIn) { // "Dust" is defined in terms of dustRelayFee, @@ -55,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); @@ -70,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"; @@ -118,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 89f6e72618..3d2660b081 100644 --- a/src/policy/policy.h +++ b/src/policy/policy.h @@ -6,58 +6,75 @@ #ifndef BITCOIN_POLICY_POLICY_H #define BITCOIN_POLICY_POLICY_H +#include <consensus/amount.h> #include <consensus/consensus.h> -#include <policy/feerate.h> +#include <primitives/transaction.h> #include <script/interpreter.h> #include <script/standard.h> +#include <cstdint> #include <string> class CCoinsViewCache; -class CTxOut; +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; +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 BIP 125 replacement **/ -static const unsigned int DEFAULT_INCREMENTAL_RELAY_FEE = 1000; +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 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 | @@ -76,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 @@ -101,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 8fe4dc35b8..e25f5c7c5b 100644 --- a/src/policy/rbf.cpp +++ b/src/policy/rbf.cpp @@ -4,11 +4,19 @@ #include <policy/rbf.h> -#include <policy/settings.h> +#include <consensus/amount.h> +#include <policy/feerate.h> +#include <primitives/transaction.h> +#include <sync.h> #include <tinyformat.h> +#include <txmempool.h> +#include <uint256.h> #include <util/moneystr.h> #include <util/rbf.h> +#include <limits> +#include <vector> + RBFTransactionState IsRBFOptIn(const CTransaction& tx, const CTxMemPool& pool) { AssertLockHeld(pool.cs); diff --git a/src/policy/rbf.h b/src/policy/rbf.h index fcec7052ed..07f68c8fd4 100644 --- a/src/policy/rbf.h +++ b/src/policy/rbf.h @@ -5,13 +5,20 @@ #ifndef BITCOIN_POLICY_RBF_H #define BITCOIN_POLICY_RBF_H +#include <consensus/amount.h> #include <primitives/transaction.h> +#include <threadsafety.h> #include <txmempool.h> -#include <uint256.h> +#include <cstddef> +#include <cstdint> #include <optional> +#include <set> #include <string> +class CFeeRate; +class uint256; + /** Maximum number of transactions that can be replaced by BIP125 RBF (Rule #5). This includes all * mempool conflicts and their descendants. */ static constexpr uint32_t MAX_BIP125_REPLACEMENT_CANDIDATES{100}; diff --git a/src/policy/settings.cpp b/src/policy/settings.cpp index eb2ec56850..39e00f1111 100644 --- a/src/policy/settings.cpp +++ b/src/policy/settings.cpp @@ -5,10 +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); unsigned int nBytesPerSigOp = DEFAULT_BYTES_PER_SIGOP; diff --git a/src/policy/settings.h b/src/policy/settings.h index 0b4fc1e770..f0d6f779ae 100644 --- a/src/policy/settings.h +++ b/src/policy/settings.h @@ -6,30 +6,6 @@ #ifndef BITCOIN_POLICY_SETTINGS_H #define BITCOIN_POLICY_SETTINGS_H -#include <policy/policy.h> - -class CFeeRate; -class CTransaction; - -// Policy settings which are configurable at runtime. -extern CFeeRate incrementalRelayFee; -extern CFeeRate dustRelayFee; 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/prevector.h b/src/prevector.h index aa20efaaa7..a52510930a 100644 --- a/src/prevector.h +++ b/src/prevector.h @@ -35,6 +35,8 @@ */ template<unsigned int N, typename T, typename Size = uint32_t, typename Diff = int32_t> class prevector { + static_assert(std::is_trivially_copyable_v<T>); + public: typedef Size size_type; typedef Diff difference_type; @@ -411,15 +413,7 @@ public: // representation (with capacity N and size <= N). iterator p = first; char* endp = (char*)&(*end()); - if (!std::is_trivially_destructible<T>::value) { - while (p != last) { - (*p).~T(); - _size--; - ++p; - } - } else { - _size -= last - p; - } + _size -= last - p; memmove(&(*first), &(*last), endp - ((char*)(&(*last)))); return first; } @@ -458,15 +452,13 @@ public: return *item_ptr(size() - 1); } - void swap(prevector<N, T, Size, Diff>& other) { + void swap(prevector<N, T, Size, Diff>& other) noexcept + { std::swap(_union, other._union); std::swap(_size, other._size); } ~prevector() { - if (!std::is_trivially_destructible<T>::value) { - clear(); - } if (!is_direct()) { free(_union.indirect_contents.indirect); _union.indirect_contents.indirect = nullptr; 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.h b/src/protocol.h index fdeaa9a9c5..b85dc0d820 100644 --- a/src/protocol.h +++ b/src/protocol.h @@ -3,10 +3,6 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#ifndef __cplusplus -#error This header can only be compiled as C++. -#endif - #ifndef BITCOIN_PROTOCOL_H #define BITCOIN_PROTOCOL_H @@ -15,10 +11,10 @@ #include <serialize.h> #include <streams.h> #include <uint256.h> -#include <version.h> +#include <util/time.h> +#include <cstdint> #include <limits> -#include <stdint.h> #include <string> /** Message header. @@ -357,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 @@ -387,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) { @@ -420,8 +416,7 @@ public: use_v2 = s.GetVersion() & ADDRV2_FORMAT; } - SER_READ(obj, obj.nTime = TIME_INIT); - 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; @@ -436,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 c8c73e130b..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) { @@ -234,7 +296,7 @@ void UpdatePSBTOutput(const SigningProvider& provider, PartiallySignedTransactio // Construct a would-be spend of this output, to update sigdata with. // Note that ProduceSignature is used to fill in metadata (not actual signatures), // so provider does not need to provide any private keys (it can be a HidingSigningProvider). - MutableTransactionSignatureCreator creator(&tx, /*input_idx=*/0, out.nValue, SIGHASH_ALL); + MutableTransactionSignatureCreator creator(tx, /*input_idx=*/0, out.nValue, SIGHASH_ALL); ProduceSignature(provider, creator, out.scriptPubKey, sigdata); // Put redeem_script, witness_script, key paths, into PSBTOutput. @@ -301,7 +363,7 @@ bool SignPSBTInput(const SigningProvider& provider, PartiallySignedTransaction& if (txdata == nullptr) { sig_complete = ProduceSignature(provider, DUMMY_SIGNATURE_CREATOR, utxo.scriptPubKey, sigdata); } else { - MutableTransactionSignatureCreator creator(&tx, index, utxo.nValue, txdata, sighash); + MutableTransactionSignatureCreator creator(tx, index, utxo.nValue, txdata, sighash); sig_complete = ProduceSignature(provider, creator, utxo.scriptPubKey, sigdata); } // Verify that a witness signature was produced in case one was required. @@ -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 @@ -388,18 +451,17 @@ std::string PSBTRoleName(PSBTRole role) { bool DecodeBase64PSBT(PartiallySignedTransaction& psbt, const std::string& base64_tx, std::string& error) { - bool invalid; - std::string tx_data = DecodeBase64(base64_tx, &invalid); - if (invalid) { + auto tx_data = DecodeBase64(base64_tx); + if (!tx_data) { error = "invalid base64"; return false; } - return DecodeRawPSBT(psbt, tx_data, error); + return DecodeRawPSBT(psbt, MakeByteSpan(*tx_data), error); } -bool DecodeRawPSBT(PartiallySignedTransaction& psbt, const std::string& tx_data, std::string& error) +bool DecodeRawPSBT(PartiallySignedTransaction& psbt, Span<const std::byte> tx_data, std::string& error) { - CDataStream ss_data(MakeByteSpan(tx_data), SER_NETWORK, PROTOCOL_VERSION); + CDataStream ss_data(tx_data, SER_NETWORK, PROTOCOL_VERSION); try { ss_data >> psbt; if (!ss_data.empty()) { diff --git a/src/psbt.h b/src/psbt.h index f0ceb02481..d5c67802c7 100644 --- a/src/psbt.h +++ b/src/psbt.h @@ -5,7 +5,6 @@ #ifndef BITCOIN_PSBT_H #define BITCOIN_PSBT_H -#include <attributes.h> #include <node/transaction.h> #include <policy/feerate.h> #include <primitives/transaction.h> @@ -41,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 @@ -55,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; @@ -98,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 @@ -140,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) @@ -179,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; @@ -253,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 @@ -489,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; @@ -533,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; @@ -565,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; @@ -625,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; @@ -653,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"); } @@ -988,6 +1271,6 @@ bool FinalizeAndExtractPSBT(PartiallySignedTransaction& psbtx, CMutableTransacti //! Decode a base64ed PSBT into a PartiallySignedTransaction [[nodiscard]] bool DecodeBase64PSBT(PartiallySignedTransaction& decoded_psbt, const std::string& base64_psbt, std::string& error); //! Decode a raw (binary blob) PSBT into a PartiallySignedTransaction -[[nodiscard]] bool DecodeRawPSBT(PartiallySignedTransaction& decoded_psbt, const std::string& raw_psbt, std::string& error); +[[nodiscard]] bool DecodeRawPSBT(PartiallySignedTransaction& decoded_psbt, Span<const std::byte> raw_psbt, std::string& error); #endif // BITCOIN_PSBT_H 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/addressbookpage.cpp b/src/qt/addressbookpage.cpp index d59a4345f3..a82bd5f73e 100644 --- a/src/qt/addressbookpage.cpp +++ b/src/qt/addressbookpage.cpp @@ -19,6 +19,11 @@ #include <QMenu> #include <QMessageBox> #include <QSortFilterProxyModel> +#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) +#include <QRegularExpression> +#else +#include <QRegExp> +#endif class AddressBookSortFilterProxyModel final : public QSortFilterProxyModel { @@ -46,12 +51,13 @@ protected: auto address = model->index(row, AddressTableModel::Address, parent); - if (filterRegExp().indexIn(model->data(address).toString()) < 0 && - filterRegExp().indexIn(model->data(label).toString()) < 0) { - return false; - } - - return true; +#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) + const auto pattern = filterRegularExpression(); +#else + const auto pattern = filterRegExp(); +#endif + return (model->data(address).toString().contains(pattern) || + model->data(label).toString().contains(pattern)); } }; diff --git a/src/qt/addresstablemodel.cpp b/src/qt/addresstablemodel.cpp index dcab631d98..8b5da7f9f0 100644 --- a/src/qt/addresstablemodel.cpp +++ b/src/qt/addresstablemodel.cpp @@ -30,7 +30,7 @@ struct AddressTableEntry QString label; QString address; - AddressTableEntry() {} + AddressTableEntry() = default; AddressTableEntry(Type _type, const QString &_label, const QString &_address): type(_type), label(_label), address(_address) {} }; @@ -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/android/res/values/libs.xml b/src/qt/android/res/values/libs.xml index 0f20df4eb0..b4b77b1c7b 100644 --- a/src/qt/android/res/values/libs.xml +++ b/src/qt/android/res/values/libs.xml @@ -10,8 +10,5 @@ <item> x86_64;libbitcoin-qt_x86_64.so </item> - <item> - x86;libbitcoin-qt_x86.so - </item> </array> </resources> diff --git a/src/qt/bantablemodel.cpp b/src/qt/bantablemodel.cpp index e004fba308..3d0be69302 100644 --- a/src/qt/bantablemodel.cpp +++ b/src/qt/bantablemodel.cpp @@ -89,10 +89,7 @@ BanTableModel::BanTableModel(interfaces::Node& node, QObject* parent) : refresh(); } -BanTableModel::~BanTableModel() -{ - // Intentionally left empty -} +BanTableModel::~BanTableModel() = default; int BanTableModel::rowCount(const QModelIndex &parent) const { diff --git a/src/qt/bitcoin.cpp b/src/qt/bitcoin.cpp index d6f80e1558..33c60deafb 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> @@ -77,8 +77,6 @@ Q_DECLARE_METATYPE(CAmount) Q_DECLARE_METATYPE(SynchronizationState) Q_DECLARE_METATYPE(uint256) -using node::NodeContext; - static void RegisterMetaTypes() { // Register meta types used for QMetaObject::invokeMethod and Qt::QueuedConnection @@ -95,6 +93,12 @@ static void RegisterMetaTypes() qRegisterMetaType<std::function<void()>>("std::function<void()>"); 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() @@ -133,21 +137,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() @@ -257,9 +270,26 @@ void BitcoinApplication::createPaymentServer() } #endif -void BitcoinApplication::createOptionsModel(bool resetSettings) +bool BitcoinApplication::createOptionsModel(bool resetSettings) { - optionsModel = new OptionsModel(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) @@ -290,7 +320,6 @@ void BitcoinApplication::createNode(interfaces::Init& init) { assert(!m_node); m_node = init.makeNode(); - if (optionsModel) optionsModel->setNode(*m_node); if (m_splash) m_splash->setNode(*m_node); } @@ -306,7 +335,9 @@ void BitcoinApplication::startThread() /* communication to and from thread */ connect(&m_executor.value(), &InitExecutor::initializeResult, this, &BitcoinApplication::initializeResult); - connect(&m_executor.value(), &InitExecutor::shutdownResult, this, &QCoreApplication::quit); + connect(&m_executor.value(), &InitExecutor::shutdownResult, this, [] { + QCoreApplication::exit(0); + }); connect(&m_executor.value(), &InitExecutor::runawayException, this, &BitcoinApplication::handleRunawayException); connect(this, &BitcoinApplication::requestedInitialize, &m_executor.value(), &InitExecutor::initialize); connect(this, &BitcoinApplication::requestedShutdown, &m_executor.value(), &InitExecutor::shutdown); @@ -324,7 +355,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() @@ -497,9 +528,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); @@ -587,7 +620,7 @@ int GuiMain(int argc, char* argv[]) return EXIT_FAILURE; } #ifdef ENABLE_WALLET - // Parse URIs on command line -- this can affect Params() + // Parse URIs on command line PaymentServer::ipcParseCommandLine(argc, argv); #endif @@ -631,19 +664,22 @@ int GuiMain(int argc, char* argv[]) // Allow parameter interaction before we create the options model app.parameterSetup(); GUIUtil::LogQtInfo(); + + if (gArgs.GetBoolArg("-splash", DEFAULT_SPLASHSCREEN) && !gArgs.GetBoolArg("-min", false)) + app.createSplashScreen(networkStyle.data()); + + 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) app.InitPruneSetting(prune_MiB); } - if (gArgs.GetBoolArg("-splash", DEFAULT_SPLASHSCREEN) && !gArgs.GetBoolArg("-min", false)) - app.createSplashScreen(networkStyle.data()); - - app.createNode(*init); - int rv = EXIT_SUCCESS; try { 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/bitcoinamountfield.cpp b/src/qt/bitcoinamountfield.cpp index a257e250e0..c92aecd095 100644 --- a/src/qt/bitcoinamountfield.cpp +++ b/src/qt/bitcoinamountfield.cpp @@ -14,6 +14,9 @@ #include <QHBoxLayout> #include <QKeyEvent> #include <QLineEdit> +#include <QVariant> + +#include <cassert> /** QSpinBox that uses fixed-point numbers internally and uses our own * formatting/parsing functions. @@ -96,7 +99,7 @@ public: setValue(val); } - void setDisplayUnit(int unit) + void setDisplayUnit(BitcoinUnit unit) { bool valid = false; CAmount val = value(&valid); @@ -122,7 +125,7 @@ public: const QFontMetrics fm(fontMetrics()); int h = lineEdit()->minimumSizeHint().height(); - int w = GUIUtil::TextWidth(fm, BitcoinUnits::format(BitcoinUnits::BTC, BitcoinUnits::maxMoney(), false, BitcoinUnits::SeparatorStyle::ALWAYS)); + int w = GUIUtil::TextWidth(fm, BitcoinUnits::format(BitcoinUnit::BTC, BitcoinUnits::maxMoney(), false, BitcoinUnits::SeparatorStyle::ALWAYS)); w += 2; // cursor blinking space QStyleOptionSpinBox opt; @@ -141,14 +144,13 @@ public: opt.rect = rect(); - cachedMinimumSizeHint = style()->sizeFromContents(QStyle::CT_SpinBox, &opt, hint, this) - .expandedTo(QApplication::globalStrut()); + cachedMinimumSizeHint = style()->sizeFromContents(QStyle::CT_SpinBox, &opt, hint, this); } return cachedMinimumSizeHint; } private: - int currentUnit{BitcoinUnits::BTC}; + BitcoinUnit currentUnit{BitcoinUnit::BTC}; CAmount singleStep{CAmount(100000)}; // satoshis mutable QSize cachedMinimumSizeHint; bool m_allow_empty{true}; @@ -326,14 +328,14 @@ void BitcoinAmountField::unitChanged(int idx) unit->setToolTip(unit->itemData(idx, Qt::ToolTipRole).toString()); // Determine new unit ID - int newUnit = unit->itemData(idx, BitcoinUnits::UnitRole).toInt(); - - amount->setDisplayUnit(newUnit); + QVariant new_unit = unit->currentData(BitcoinUnits::UnitRole); + assert(new_unit.isValid()); + amount->setDisplayUnit(new_unit.value<BitcoinUnit>()); } -void BitcoinAmountField::setDisplayUnit(int newUnit) +void BitcoinAmountField::setDisplayUnit(BitcoinUnit new_unit) { - unit->setValue(newUnit); + unit->setValue(QVariant::fromValue(new_unit)); } void BitcoinAmountField::setSingleStep(const CAmount& step) diff --git a/src/qt/bitcoinamountfield.h b/src/qt/bitcoinamountfield.h index 366a6fc4b5..a40cd38332 100644 --- a/src/qt/bitcoinamountfield.h +++ b/src/qt/bitcoinamountfield.h @@ -6,6 +6,7 @@ #define BITCOIN_QT_BITCOINAMOUNTFIELD_H #include <consensus/amount.h> +#include <qt/bitcoinunits.h> #include <QWidget> @@ -52,7 +53,7 @@ public: bool validate(); /** Change unit used to display amount. */ - void setDisplayUnit(int unit); + void setDisplayUnit(BitcoinUnit new_unit); /** Make field empty and ready for new input. */ void clear(); diff --git a/src/qt/bitcoingui.cpp b/src/qt/bitcoingui.cpp index 7c22880dd1..90f228803c 100644 --- a/src/qt/bitcoingui.cpp +++ b/src/qt/bitcoingui.cpp @@ -26,7 +26,7 @@ #include <qt/walletview.h> #endif // ENABLE_WALLET -#ifdef Q_OS_MAC +#ifdef Q_OS_MACOS #include <qt/macdockiconhandler.h> #endif @@ -35,17 +35,20 @@ #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> #include <QAction> +#include <QActionGroup> #include <QApplication> #include <QComboBox> #include <QCursor> #include <QDateTime> #include <QDragEnterEvent> +#include <QInputDialog> +#include <QKeySequence> #include <QListWidget> #include <QMenu> #include <QMenuBar> @@ -67,7 +70,7 @@ const std::string BitcoinGUI::DEFAULT_UIPLATFORM = -#if defined(Q_OS_MAC) +#if defined(Q_OS_MACOS) "macosx" #elif defined(Q_OS_WIN) "windows" @@ -217,7 +220,7 @@ BitcoinGUI::BitcoinGUI(interfaces::Node& node, const PlatformStyle *_platformSty connect(labelBlocksIcon, &GUIUtil::ClickableLabel::clicked, this, &BitcoinGUI::showModalOverlay); connect(progressBar, &GUIUtil::ClickableProgressBar::clicked, this, &BitcoinGUI::showModalOverlay); -#ifdef Q_OS_MAC +#ifdef Q_OS_MACOS m_app_nap_inhibitor = new CAppNapInhibitor; #endif @@ -233,7 +236,7 @@ BitcoinGUI::~BitcoinGUI() settings.setValue("MainWindowGeometry", saveGeometry()); if(trayIcon) // Hide tray icon, as deleting will let it linger until quit (on Ubuntu) trayIcon->hide(); -#ifdef Q_OS_MAC +#ifdef Q_OS_MACOS delete m_app_nap_inhibitor; delete appMenuBar; MacDockIconHandler::cleanup(); @@ -251,28 +254,28 @@ void BitcoinGUI::createActions() overviewAction->setStatusTip(tr("Show general overview of wallet")); overviewAction->setToolTip(overviewAction->statusTip()); overviewAction->setCheckable(true); - overviewAction->setShortcut(QKeySequence(Qt::ALT + Qt::Key_1)); + overviewAction->setShortcut(QKeySequence(QStringLiteral("Alt+1"))); tabGroup->addAction(overviewAction); sendCoinsAction = new QAction(platformStyle->SingleColorIcon(":/icons/send"), tr("&Send"), this); sendCoinsAction->setStatusTip(tr("Send coins to a Bitcoin address")); sendCoinsAction->setToolTip(sendCoinsAction->statusTip()); sendCoinsAction->setCheckable(true); - sendCoinsAction->setShortcut(QKeySequence(Qt::ALT + Qt::Key_2)); + sendCoinsAction->setShortcut(QKeySequence(QStringLiteral("Alt+2"))); tabGroup->addAction(sendCoinsAction); receiveCoinsAction = new QAction(platformStyle->SingleColorIcon(":/icons/receiving_addresses"), tr("&Receive"), this); receiveCoinsAction->setStatusTip(tr("Request payments (generates QR codes and bitcoin: URIs)")); receiveCoinsAction->setToolTip(receiveCoinsAction->statusTip()); receiveCoinsAction->setCheckable(true); - receiveCoinsAction->setShortcut(QKeySequence(Qt::ALT + Qt::Key_3)); + receiveCoinsAction->setShortcut(QKeySequence(QStringLiteral("Alt+3"))); tabGroup->addAction(receiveCoinsAction); historyAction = new QAction(platformStyle->SingleColorIcon(":/icons/history"), tr("&Transactions"), this); historyAction->setStatusTip(tr("Browse transaction history")); historyAction->setToolTip(historyAction->statusTip()); historyAction->setCheckable(true); - historyAction->setShortcut(QKeySequence(Qt::ALT + Qt::Key_4)); + historyAction->setShortcut(QKeySequence(QStringLiteral("Alt+4"))); tabGroup->addAction(historyAction); #ifdef ENABLE_WALLET @@ -290,7 +293,7 @@ void BitcoinGUI::createActions() quitAction = new QAction(tr("E&xit"), this); quitAction->setStatusTip(tr("Quit application")); - quitAction->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_Q)); + quitAction->setShortcut(QKeySequence(tr("Ctrl+Q"))); quitAction->setMenuRole(QAction::QuitRole); aboutAction = new QAction(tr("&About %1").arg(PACKAGE_NAME), this); aboutAction->setStatusTip(tr("Show information about %1").arg(PACKAGE_NAME)); @@ -346,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")); @@ -354,7 +363,7 @@ void BitcoinGUI::createActions() showHelpMessageAction->setStatusTip(tr("Show the %1 help message to get a list with possible Bitcoin command-line options").arg(PACKAGE_NAME)); m_mask_values_action = new QAction(tr("&Mask values"), this); - m_mask_values_action->setShortcut(QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_M)); + m_mask_values_action->setShortcut(QKeySequence(Qt::CTRL | Qt::SHIFT | Qt::Key_M)); m_mask_values_action->setStatusTip(tr("Mask the values in the Overview tab")); m_mask_values_action->setCheckable(true); @@ -410,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); }); @@ -425,13 +459,13 @@ void BitcoinGUI::createActions() } #endif // ENABLE_WALLET - connect(new QShortcut(QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_C), this), &QShortcut::activated, this, &BitcoinGUI::showDebugWindowActivateConsole); - connect(new QShortcut(QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_D), this), &QShortcut::activated, this, &BitcoinGUI::showDebugWindow); + connect(new QShortcut(QKeySequence(Qt::CTRL | Qt::SHIFT | Qt::Key_C), this), &QShortcut::activated, this, &BitcoinGUI::showDebugWindowActivateConsole); + connect(new QShortcut(QKeySequence(Qt::CTRL | Qt::SHIFT | Qt::Key_D), this), &QShortcut::activated, this, &BitcoinGUI::showDebugWindow); } void BitcoinGUI::createMenuBar() { -#ifdef Q_OS_MAC +#ifdef Q_OS_MACOS // Create a decoupled menu bar on Mac which stays even if the window is closed appMenuBar = new QMenuBar(); #else @@ -448,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); @@ -472,7 +508,7 @@ void BitcoinGUI::createMenuBar() QMenu* window_menu = appMenuBar->addMenu(tr("&Window")); QAction* minimize_action = window_menu->addAction(tr("&Minimize")); - minimize_action->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_M)); + minimize_action->setShortcut(QKeySequence(tr("Ctrl+M"))); connect(minimize_action, &QAction::triggered, [] { QApplication::activeWindow()->showMinimized(); }); @@ -480,7 +516,7 @@ void BitcoinGUI::createMenuBar() minimize_action->setEnabled(window != nullptr && (window->flags() & Qt::Dialog) != Qt::Dialog && window->windowState() != Qt::WindowMinimized); }); -#ifdef Q_OS_MAC +#ifdef Q_OS_MACOS QAction* zoom_action = window_menu->addAction(tr("Zoom")); connect(zoom_action, &QAction::triggered, [] { QWindow* window = qApp->focusWindow(); @@ -497,7 +533,7 @@ void BitcoinGUI::createMenuBar() #endif if (walletFrame) { -#ifdef Q_OS_MAC +#ifdef Q_OS_MACOS window_menu->addSeparator(); QAction* main_window_action = window_menu->addAction(tr("Main Window")); connect(main_window_action, &QAction::triggered, [this] { @@ -640,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); @@ -753,7 +790,7 @@ void BitcoinGUI::createTrayIcon() { assert(QSystemTrayIcon::isSystemTrayAvailable()); -#ifndef Q_OS_MAC +#ifndef Q_OS_MACOS if (QSystemTrayIcon::isSystemTrayAvailable()) { trayIcon = new QSystemTrayIcon(m_network_style->getTrayAndWindowIcon(), this); QString toolTip = tr("%1 client").arg(PACKAGE_NAME) + " " + m_network_style->getTitleAddText(); @@ -764,17 +801,17 @@ void BitcoinGUI::createTrayIcon() void BitcoinGUI::createTrayIconMenu() { -#ifndef Q_OS_MAC +#ifndef Q_OS_MACOS if (!trayIcon) return; -#endif // Q_OS_MAC +#endif // Q_OS_MACOS // Configuration of the tray icon (or Dock icon) menu. QAction* show_hide_action{nullptr}; -#ifndef Q_OS_MAC +#ifndef Q_OS_MACOS // Note: On macOS, the Dock icon's menu already has Show / Hide action. show_hide_action = trayIconMenu->addAction(QString(), this, &BitcoinGUI::toggleHidden); trayIconMenu->addSeparator(); -#endif // Q_OS_MAC +#endif // Q_OS_MACOS QAction* send_action{nullptr}; QAction* receive_action{nullptr}; @@ -792,7 +829,7 @@ void BitcoinGUI::createTrayIconMenu() options_action->setMenuRole(QAction::PreferencesRole); QAction* node_window_action = trayIconMenu->addAction(openRPCConsoleAction->text(), openRPCConsoleAction, &QAction::trigger); QAction* quit_action{nullptr}; -#ifndef Q_OS_MAC +#ifndef Q_OS_MACOS // Note: On macOS, the Dock icon's menu already has Quit action. trayIconMenu->addSeparator(); quit_action = trayIconMenu->addAction(quitAction->text(), quitAction, &QAction::trigger); @@ -812,7 +849,7 @@ void BitcoinGUI::createTrayIconMenu() activateWindow(); }); trayIconMenu->setAsDockMenu(); -#endif // Q_OS_MAC +#endif // Q_OS_MACOS connect( // Using QSystemTrayIcon::Context is not reliable. @@ -852,7 +889,7 @@ void BitcoinGUI::aboutClicked() if(!clientModel) return; - auto dlg = new HelpMessageDialog(this, /* about */ true); + auto dlg = new HelpMessageDialog(this, /*about=*/true); GUIUtil::ShowModalDialogAsynchronously(dlg); } @@ -997,6 +1034,7 @@ 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); } @@ -1004,7 +1042,7 @@ void BitcoinGUI::openOptionsDialogWithTab(OptionsDialog::Tab tab) void BitcoinGUI::setNumBlocks(int count, const QDateTime& blockDate, double nVerificationProgress, bool header, SynchronizationState sync_state) { // Disabling macOS App Nap on initial sync, disk and reindex operations. -#ifdef Q_OS_MAC +#ifdef Q_OS_MACOS if (sync_state == SynchronizationState::POST_INIT) { m_app_nap_inhibitor->enableAppNap(); } else { @@ -1190,7 +1228,7 @@ void BitcoinGUI::changeEvent(QEvent *e) QMainWindow::changeEvent(e); -#ifndef Q_OS_MAC // Ignored on Mac +#ifndef Q_OS_MACOS // Ignored on Mac if(e->type() == QEvent::WindowStateChange) { if(clientModel && clientModel->getOptionsModel() && clientModel->getOptionsModel()->getMinimizeToTray()) @@ -1213,7 +1251,7 @@ void BitcoinGUI::changeEvent(QEvent *e) void BitcoinGUI::closeEvent(QCloseEvent *event) { -#ifndef Q_OS_MAC // Ignored on Mac +#ifndef Q_OS_MACOS // Ignored on Mac if(clientModel && clientModel->getOptionsModel()) { if(!clientModel->getOptionsModel()->getMinimizeOnClose()) @@ -1243,7 +1281,7 @@ void BitcoinGUI::showEvent(QShowEvent *event) } #ifdef ENABLE_WALLET -void BitcoinGUI::incomingTransaction(const QString& date, int unit, const CAmount& amount, const QString& type, const QString& address, const QString& label, const QString& walletName) +void BitcoinGUI::incomingTransaction(const QString& date, BitcoinUnit unit, const CAmount& amount, const QString& type, const QString& address, const QString& label, const QString& walletName) { // On new transaction, make an info balloon QString msg = tr("Date: %1\n").arg(date) + @@ -1316,6 +1354,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); @@ -1494,11 +1538,10 @@ UnitDisplayStatusBarControl::UnitDisplayStatusBarControl(const PlatformStyle *pl { createContextMenu(); setToolTip(tr("Unit to show amounts in. Click to select another unit.")); - QList<BitcoinUnits::Unit> units = BitcoinUnits::availableUnits(); + QList<BitcoinUnit> units = BitcoinUnits::availableUnits(); int max_width = 0; const QFontMetrics fm(font()); - for (const BitcoinUnits::Unit unit : units) - { + for (const BitcoinUnit unit : units) { max_width = qMax(max_width, GUIUtil::TextWidth(fm, BitcoinUnits::longName(unit))); } setMinimumSize(max_width, 0); @@ -1528,8 +1571,8 @@ void UnitDisplayStatusBarControl::changeEvent(QEvent* e) void UnitDisplayStatusBarControl::createContextMenu() { menu = new QMenu(this); - for (const BitcoinUnits::Unit u : BitcoinUnits::availableUnits()) { - menu->addAction(BitcoinUnits::longName(u))->setData(QVariant(u)); + for (const BitcoinUnit u : BitcoinUnits::availableUnits()) { + menu->addAction(BitcoinUnits::longName(u))->setData(QVariant::fromValue(u)); } connect(menu, &QMenu::triggered, this, &UnitDisplayStatusBarControl::onMenuSelection); } @@ -1550,7 +1593,7 @@ void UnitDisplayStatusBarControl::setOptionsModel(OptionsModel *_optionsModel) } /** When Display Units are changed on OptionsModel it will refresh the display text of the control on the status bar */ -void UnitDisplayStatusBarControl::updateDisplayUnit(int newUnits) +void UnitDisplayStatusBarControl::updateDisplayUnit(BitcoinUnit newUnits) { setText(BitcoinUnits::longName(newUnits)); } diff --git a/src/qt/bitcoingui.h b/src/qt/bitcoingui.h index 0ae5f7331e..b2e13245e1 100644 --- a/src/qt/bitcoingui.h +++ b/src/qt/bitcoingui.h @@ -9,6 +9,7 @@ #include <config/bitcoin-config.h> #endif +#include <qt/bitcoinunits.h> #include <qt/guiutil.h> #include <qt/optionsdialog.h> @@ -21,7 +22,7 @@ #include <QPoint> #include <QSystemTrayIcon> -#ifdef Q_OS_MAC +#ifdef Q_OS_MACOS #include <qt/macos_appnap.h> #endif @@ -156,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; @@ -174,7 +176,7 @@ private: QMenu* m_network_context_menu = new QMenu(this); -#ifdef Q_OS_MAC +#ifdef Q_OS_MACOS CAppNapInhibitor* m_app_nap_inhibitor = nullptr; #endif @@ -260,7 +262,7 @@ public Q_SLOTS: bool handlePaymentRequest(const SendCoinsRecipient& recipient); /** Show incoming transaction notification for new transactions. */ - void incomingTransaction(const QString& date, int unit, const CAmount& amount, const QString& type, const QString& address, const QString& label, const QString& walletName); + void incomingTransaction(const QString& date, BitcoinUnit unit, const CAmount& amount, const QString& type, const QString& address, const QString& label, const QString& walletName); #endif // ENABLE_WALLET private: @@ -303,7 +305,7 @@ public Q_SLOTS: /** Show window if hidden, unminimize when minimized, rise when obscured or show if hidden and fToggleHidden is true */ void showNormalIfMinimized() { showNormalIfMinimized(false); } void showNormalIfMinimized(bool fToggleHidden); - /** Simply calls showNormalIfMinimized(true) for use in SLOT() macro */ + /** Simply calls showNormalIfMinimized(true) */ void toggleHidden(); /** called by a timer to check if ShutdownRequested() has been set **/ @@ -341,7 +343,7 @@ private: private Q_SLOTS: /** When Display Units are changed on OptionsModel it will refresh the display text of the control on the status bar */ - void updateDisplayUnit(int newUnits); + void updateDisplayUnit(BitcoinUnit newUnits); /** Tells underlying optionsModel to update its current display unit. */ void onMenuSelection(QAction* action); }; diff --git a/src/qt/bitcoinstrings.cpp b/src/qt/bitcoinstrings.cpp index ba804d6955..13694c638d 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", "" @@ -51,7 +70,8 @@ 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)"), +"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."), @@ -77,6 +97,10 @@ 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 not provided (no -proxy= and no -onion= given) " +"or it is explicitly forbidden (-onion=0)"), +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 +109,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 +156,13 @@ QT_TRANSLATE_NOOP("bitcoin-core", "" "Unknown wallet file format \"%s\" provided. Please provide one of \"bdb\" or " "\"sqlite\"."), 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", "" @@ -169,7 +203,6 @@ 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: 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"), @@ -199,6 +232,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 +241,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,6 +273,7 @@ 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"), @@ -255,7 +288,6 @@ 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 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/bitcoinunits.cpp b/src/qt/bitcoinunits.cpp index 69caf64d5c..fe3eb3240b 100644 --- a/src/qt/bitcoinunits.cpp +++ b/src/qt/bitcoinunits.cpp @@ -18,94 +18,75 @@ BitcoinUnits::BitcoinUnits(QObject *parent): { } -QList<BitcoinUnits::Unit> BitcoinUnits::availableUnits() +QList<BitcoinUnit> BitcoinUnits::availableUnits() { - QList<BitcoinUnits::Unit> unitlist; - unitlist.append(BTC); - unitlist.append(mBTC); - unitlist.append(uBTC); - unitlist.append(SAT); + QList<BitcoinUnit> unitlist; + unitlist.append(Unit::BTC); + unitlist.append(Unit::mBTC); + unitlist.append(Unit::uBTC); + unitlist.append(Unit::SAT); return unitlist; } -bool BitcoinUnits::valid(int unit) +QString BitcoinUnits::longName(Unit unit) { - switch(unit) - { - case BTC: - case mBTC: - case uBTC: - case SAT: - return true; - default: - return false; - } -} - -QString BitcoinUnits::longName(int unit) -{ - switch(unit) - { - case BTC: return QString("BTC"); - case mBTC: return QString("mBTC"); - case uBTC: return QString::fromUtf8("µBTC (bits)"); - case SAT: return QString("Satoshi (sat)"); - default: return QString("???"); - } + switch (unit) { + case Unit::BTC: return QString("BTC"); + case Unit::mBTC: return QString("mBTC"); + case Unit::uBTC: return QString::fromUtf8("µBTC (bits)"); + case Unit::SAT: return QString("Satoshi (sat)"); + } // no default case, so the compiler can warn about missing cases + assert(false); } -QString BitcoinUnits::shortName(int unit) +QString BitcoinUnits::shortName(Unit unit) { - switch(unit) - { - case uBTC: return QString::fromUtf8("bits"); - case SAT: return QString("sat"); - default: return longName(unit); - } + switch (unit) { + case Unit::BTC: return longName(unit); + case Unit::mBTC: return longName(unit); + case Unit::uBTC: return QString("bits"); + case Unit::SAT: return QString("sat"); + } // no default case, so the compiler can warn about missing cases + assert(false); } -QString BitcoinUnits::description(int unit) +QString BitcoinUnits::description(Unit unit) { - switch(unit) - { - case BTC: return QString("Bitcoins"); - case mBTC: return QString("Milli-Bitcoins (1 / 1" THIN_SP_UTF8 "000)"); - case uBTC: return QString("Micro-Bitcoins (bits) (1 / 1" THIN_SP_UTF8 "000" THIN_SP_UTF8 "000)"); - case SAT: return QString("Satoshi (sat) (1 / 100" THIN_SP_UTF8 "000" THIN_SP_UTF8 "000)"); - default: return QString("???"); - } + switch (unit) { + case Unit::BTC: return QString("Bitcoins"); + case Unit::mBTC: return QString("Milli-Bitcoins (1 / 1" THIN_SP_UTF8 "000)"); + case Unit::uBTC: return QString("Micro-Bitcoins (bits) (1 / 1" THIN_SP_UTF8 "000" THIN_SP_UTF8 "000)"); + case Unit::SAT: return QString("Satoshi (sat) (1 / 100" THIN_SP_UTF8 "000" THIN_SP_UTF8 "000)"); + } // no default case, so the compiler can warn about missing cases + assert(false); } -qint64 BitcoinUnits::factor(int unit) +qint64 BitcoinUnits::factor(Unit unit) { - switch(unit) - { - case BTC: return 100000000; - case mBTC: return 100000; - case uBTC: return 100; - case SAT: return 1; - default: return 100000000; - } + switch (unit) { + case Unit::BTC: return 100'000'000; + case Unit::mBTC: return 100'000; + case Unit::uBTC: return 100; + case Unit::SAT: return 1; + } // no default case, so the compiler can warn about missing cases + assert(false); } -int BitcoinUnits::decimals(int unit) +int BitcoinUnits::decimals(Unit unit) { - switch(unit) - { - case BTC: return 8; - case mBTC: return 5; - case uBTC: return 2; - case SAT: return 0; - default: return 0; - } + switch (unit) { + case Unit::BTC: return 8; + case Unit::mBTC: return 5; + case Unit::uBTC: return 2; + case Unit::SAT: return 0; + } // no default case, so the compiler can warn about missing cases + assert(false); } -QString BitcoinUnits::format(int unit, const CAmount& nIn, bool fPlus, SeparatorStyle separators, bool justify) +QString BitcoinUnits::format(Unit unit, const CAmount& nIn, bool fPlus, SeparatorStyle separators, bool justify) { // Note: not using straight sprintf here because we do NOT want // localized number formatting. - if(!valid(unit)) - return QString(); // Refuse to format invalid unit qint64 n = (qint64)nIn; qint64 coin = factor(unit); int num_decimals = decimals(unit); @@ -147,19 +128,19 @@ QString BitcoinUnits::format(int unit, const CAmount& nIn, bool fPlus, Separator // Please take care to use formatHtmlWithUnit instead, when // appropriate. -QString BitcoinUnits::formatWithUnit(int unit, const CAmount& amount, bool plussign, SeparatorStyle separators) +QString BitcoinUnits::formatWithUnit(Unit unit, const CAmount& amount, bool plussign, SeparatorStyle separators) { return format(unit, amount, plussign, separators) + QString(" ") + shortName(unit); } -QString BitcoinUnits::formatHtmlWithUnit(int unit, const CAmount& amount, bool plussign, SeparatorStyle separators) +QString BitcoinUnits::formatHtmlWithUnit(Unit unit, const CAmount& amount, bool plussign, SeparatorStyle separators) { QString str(formatWithUnit(unit, amount, plussign, separators)); str.replace(QChar(THIN_SP_CP), QString(THIN_SP_HTML)); return QString("<span style='white-space: nowrap;'>%1</span>").arg(str); } -QString BitcoinUnits::formatWithPrivacy(int unit, const CAmount& amount, SeparatorStyle separators, bool privacy) +QString BitcoinUnits::formatWithPrivacy(Unit unit, const CAmount& amount, SeparatorStyle separators, bool privacy) { assert(amount >= 0); QString value; @@ -171,10 +152,11 @@ QString BitcoinUnits::formatWithPrivacy(int unit, const CAmount& amount, Separat return value + QString(" ") + shortName(unit); } -bool BitcoinUnits::parse(int unit, const QString &value, CAmount *val_out) +bool BitcoinUnits::parse(Unit unit, const QString& value, CAmount* val_out) { - if(!valid(unit) || value.isEmpty()) + if (value.isEmpty()) { return false; // Refuse to parse invalid unit or empty string + } int num_decimals = decimals(unit); // Ignore spaces and thin spaces when parsing @@ -210,14 +192,9 @@ bool BitcoinUnits::parse(int unit, const QString &value, CAmount *val_out) return ok; } -QString BitcoinUnits::getAmountColumnTitle(int unit) +QString BitcoinUnits::getAmountColumnTitle(Unit unit) { - QString amountTitle = QObject::tr("Amount"); - if (BitcoinUnits::valid(unit)) - { - amountTitle += " ("+BitcoinUnits::shortName(unit) + ")"; - } - return amountTitle; + return QObject::tr("Amount") + " (" + shortName(unit) + ")"; } int BitcoinUnits::rowCount(const QModelIndex &parent) const @@ -240,7 +217,7 @@ QVariant BitcoinUnits::data(const QModelIndex &index, int role) const case Qt::ToolTipRole: return QVariant(description(unit)); case UnitRole: - return QVariant(static_cast<int>(unit)); + return QVariant::fromValue(unit); } } return QVariant(); @@ -250,3 +227,40 @@ CAmount BitcoinUnits::maxMoney() { return MAX_MONEY; } + +namespace { +qint8 ToQint8(BitcoinUnit unit) +{ + switch (unit) { + case BitcoinUnit::BTC: return 0; + case BitcoinUnit::mBTC: return 1; + case BitcoinUnit::uBTC: return 2; + case BitcoinUnit::SAT: return 3; + } // no default case, so the compiler can warn about missing cases + assert(false); +} + +BitcoinUnit FromQint8(qint8 num) +{ + switch (num) { + case 0: return BitcoinUnit::BTC; + case 1: return BitcoinUnit::mBTC; + case 2: return BitcoinUnit::uBTC; + case 3: return BitcoinUnit::SAT; + } + assert(false); +} +} // namespace + +QDataStream& operator<<(QDataStream& out, const BitcoinUnit& unit) +{ + return out << ToQint8(unit); +} + +QDataStream& operator>>(QDataStream& in, BitcoinUnit& unit) +{ + qint8 input; + in >> input; + unit = FromQint8(input); + return in; +} diff --git a/src/qt/bitcoinunits.h b/src/qt/bitcoinunits.h index 9fedec0d4f..b3b5a8fc18 100644 --- a/src/qt/bitcoinunits.h +++ b/src/qt/bitcoinunits.h @@ -8,6 +8,7 @@ #include <consensus/amount.h> #include <QAbstractListModel> +#include <QDataStream> #include <QString> // U+2009 THIN SPACE = UTF-8 E2 80 89 @@ -38,13 +39,13 @@ public: /** Bitcoin units. @note Source: https://en.bitcoin.it/wiki/Units . Please add only sensible ones */ - enum Unit - { + enum class Unit { BTC, mBTC, uBTC, SAT }; + Q_ENUM(Unit) enum class SeparatorStyle { @@ -59,30 +60,28 @@ public: //! Get list of units, for drop-down box static QList<Unit> availableUnits(); - //! Is unit ID valid? - static bool valid(int unit); //! Long name - static QString longName(int unit); + static QString longName(Unit unit); //! Short name - static QString shortName(int unit); + static QString shortName(Unit unit); //! Longer description - static QString description(int unit); + static QString description(Unit unit); //! Number of Satoshis (1e-8) per unit - static qint64 factor(int unit); + static qint64 factor(Unit unit); //! Number of decimals left - static int decimals(int unit); + static int decimals(Unit unit); //! Format as string - static QString format(int unit, const CAmount& amount, bool plussign = false, SeparatorStyle separators = SeparatorStyle::STANDARD, bool justify = false); + static QString format(Unit unit, const CAmount& amount, bool plussign = false, SeparatorStyle separators = SeparatorStyle::STANDARD, bool justify = false); //! Format as string (with unit) - static QString formatWithUnit(int unit, const CAmount& amount, bool plussign=false, SeparatorStyle separators=SeparatorStyle::STANDARD); + static QString formatWithUnit(Unit unit, const CAmount& amount, bool plussign = false, SeparatorStyle separators = SeparatorStyle::STANDARD); //! Format as HTML string (with unit) - static QString formatHtmlWithUnit(int unit, const CAmount& amount, bool plussign=false, SeparatorStyle separators=SeparatorStyle::STANDARD); + static QString formatHtmlWithUnit(Unit unit, const CAmount& amount, bool plussign = false, SeparatorStyle separators = SeparatorStyle::STANDARD); //! Format as string (with unit) of fixed length to preserve privacy, if it is set. - static QString formatWithPrivacy(int unit, const CAmount& amount, SeparatorStyle separators, bool privacy); + static QString formatWithPrivacy(Unit unit, const CAmount& amount, SeparatorStyle separators, bool privacy); //! Parse string to coin amount - static bool parse(int unit, const QString &value, CAmount *val_out); + static bool parse(Unit unit, const QString& value, CAmount* val_out); //! Gets title for amount column including current display unit if optionsModel reference available */ - static QString getAmountColumnTitle(int unit); + static QString getAmountColumnTitle(Unit unit); ///@} //! @name AbstractListModel implementation @@ -107,8 +106,11 @@ public: static CAmount maxMoney(); private: - QList<BitcoinUnits::Unit> unitlist; + QList<Unit> unitlist; }; typedef BitcoinUnits::Unit BitcoinUnit; +QDataStream& operator<<(QDataStream& out, const BitcoinUnit& unit); +QDataStream& operator>>(QDataStream& in, BitcoinUnit& unit); + #endif // BITCOIN_QT_BITCOINUNITS_H diff --git a/src/qt/clientmodel.cpp b/src/qt/clientmodel.cpp index 4327d31787..f41da519df 100644 --- a/src/qt/clientmodel.cpp +++ b/src/qt/clientmodel.cpp @@ -21,9 +21,9 @@ #include <validation.h> #include <stdint.h> -#include <functional> #include <QDebug> +#include <QMetaObject> #include <QThread> #include <QTimer> @@ -148,21 +148,6 @@ uint256 ClientModel::getBestBlockHash() return m_cached_tip_blocks; } -void ClientModel::updateNumConnections(int numConnections) -{ - Q_EMIT numConnectionsChanged(numConnections); -} - -void ClientModel::updateNetworkActive(bool networkActive) -{ - Q_EMIT networkActiveChanged(networkActive); -} - -void ClientModel::updateAlert() -{ - Q_EMIT alertsChanged(getStatusBarWarnings()); -} - enum BlockSource ClientModel::getBlockSource() const { if (m_node.getReindex()) @@ -230,94 +215,65 @@ QString ClientModel::blocksDir() const return GUIUtil::PathToQString(gArgs.GetBlocksDirPath()); } -void ClientModel::updateBanlist() -{ - banTableModel->refresh(); -} - -// Handlers for core signals -static void ShowProgress(ClientModel *clientmodel, const std::string &title, int nProgress) -{ - // emits signal "showProgress" - bool invoked = QMetaObject::invokeMethod(clientmodel, "showProgress", Qt::QueuedConnection, - Q_ARG(QString, QString::fromStdString(title)), - Q_ARG(int, nProgress)); - assert(invoked); -} - -static void NotifyNumConnectionsChanged(ClientModel *clientmodel, int newNumConnections) -{ - // Too noisy: qDebug() << "NotifyNumConnectionsChanged: " + QString::number(newNumConnections); - bool invoked = QMetaObject::invokeMethod(clientmodel, "updateNumConnections", Qt::QueuedConnection, - Q_ARG(int, newNumConnections)); - assert(invoked); -} - -static void NotifyNetworkActiveChanged(ClientModel *clientmodel, bool networkActive) -{ - bool invoked = QMetaObject::invokeMethod(clientmodel, "updateNetworkActive", Qt::QueuedConnection, - Q_ARG(bool, networkActive)); - assert(invoked); -} - -static void NotifyAlertChanged(ClientModel *clientmodel) -{ - qDebug() << "NotifyAlertChanged"; - bool invoked = QMetaObject::invokeMethod(clientmodel, "updateAlert", Qt::QueuedConnection); - assert(invoked); -} - -static void BannedListChanged(ClientModel *clientmodel) -{ - qDebug() << QString("%1: Requesting update for peer banlist").arg(__func__); - bool invoked = QMetaObject::invokeMethod(clientmodel, "updateBanlist", Qt::QueuedConnection); - assert(invoked); -} - -static void BlockTipChanged(ClientModel* clientmodel, SynchronizationState sync_state, interfaces::BlockTip tip, double verificationProgress, bool fHeader) +void ClientModel::TipChanged(SynchronizationState sync_state, interfaces::BlockTip tip, double verification_progress, bool header) { - if (fHeader) { + if (header) { // cache best headers time and height to reduce future cs_main locks - clientmodel->cachedBestHeaderHeight = tip.block_height; - clientmodel->cachedBestHeaderTime = tip.block_time; + cachedBestHeaderHeight = tip.block_height; + cachedBestHeaderTime = tip.block_time; } else { - clientmodel->m_cached_num_blocks = tip.block_height; - WITH_LOCK(clientmodel->m_cached_tip_mutex, clientmodel->m_cached_tip_blocks = tip.block_hash;); + 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 && !fHeader) || sync_state == SynchronizationState::INIT_REINDEX; + const bool throttle = (sync_state != SynchronizationState::POST_INIT && !header) || sync_state == SynchronizationState::INIT_REINDEX; const int64_t now = throttle ? GetTimeMillis() : 0; - int64_t& nLastUpdateNotification = fHeader ? nLastHeaderTipUpdateNotification : nLastBlockTipUpdateNotification; + int64_t& nLastUpdateNotification = header ? nLastHeaderTipUpdateNotification : nLastBlockTipUpdateNotification; if (throttle && now < nLastUpdateNotification + count_milliseconds(MODEL_UPDATE_DELAY)) { return; } - bool invoked = QMetaObject::invokeMethod(clientmodel, "numBlocksChanged", Qt::QueuedConnection, - Q_ARG(int, tip.block_height), - Q_ARG(QDateTime, QDateTime::fromSecsSinceEpoch(tip.block_time)), - Q_ARG(double, verificationProgress), - Q_ARG(bool, fHeader), - Q_ARG(SynchronizationState, sync_state)); - assert(invoked); + Q_EMIT numBlocksChanged(tip.block_height, QDateTime::fromSecsSinceEpoch(tip.block_time), verification_progress, header, sync_state); nLastUpdateNotification = now; } void ClientModel::subscribeToCoreSignals() { - // Connect signals to client - m_handler_show_progress = m_node.handleShowProgress(std::bind(ShowProgress, this, std::placeholders::_1, std::placeholders::_2)); - m_handler_notify_num_connections_changed = m_node.handleNotifyNumConnectionsChanged(std::bind(NotifyNumConnectionsChanged, this, std::placeholders::_1)); - m_handler_notify_network_active_changed = m_node.handleNotifyNetworkActiveChanged(std::bind(NotifyNetworkActiveChanged, this, std::placeholders::_1)); - m_handler_notify_alert_changed = m_node.handleNotifyAlertChanged(std::bind(NotifyAlertChanged, this)); - m_handler_banned_list_changed = m_node.handleBannedListChanged(std::bind(BannedListChanged, this)); - m_handler_notify_block_tip = m_node.handleNotifyBlockTip(std::bind(BlockTipChanged, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, false)); - m_handler_notify_header_tip = m_node.handleNotifyHeaderTip(std::bind(BlockTipChanged, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, true)); + m_handler_show_progress = m_node.handleShowProgress( + [this](const std::string& title, int progress, [[maybe_unused]] bool resume_possible) { + Q_EMIT showProgress(QString::fromStdString(title), progress); + }); + m_handler_notify_num_connections_changed = m_node.handleNotifyNumConnectionsChanged( + [this](int new_num_connections) { + Q_EMIT numConnectionsChanged(new_num_connections); + }); + m_handler_notify_network_active_changed = m_node.handleNotifyNetworkActiveChanged( + [this](bool network_active) { + Q_EMIT networkActiveChanged(network_active); + }); + m_handler_notify_alert_changed = m_node.handleNotifyAlertChanged( + [this]() { + qDebug() << "ClientModel: NotifyAlertChanged"; + Q_EMIT alertsChanged(getStatusBarWarnings()); + }); + m_handler_banned_list_changed = m_node.handleBannedListChanged( + [this]() { + qDebug() << "ClienModel: Requesting update for peer banlist"; + QMetaObject::invokeMethod(banTableModel, [this] { banTableModel->refresh(); }); + }); + 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); + }); + 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); + }); } void ClientModel::unsubscribeFromCoreSignals() { - // Disconnect signals from client m_handler_show_progress->disconnect(); m_handler_notify_num_connections_changed->disconnect(); m_handler_notify_network_active_changed->disconnect(); diff --git a/src/qt/clientmodel.h b/src/qt/clientmodel.h index 846691c0c0..81f03a58ec 100644 --- a/src/qt/clientmodel.h +++ b/src/qt/clientmodel.h @@ -23,6 +23,7 @@ enum class SynchronizationState; namespace interfaces { class Handler; class Node; +struct BlockTip; } QT_BEGIN_NAMESPACE @@ -61,7 +62,7 @@ public: //! Return number of connections, default is in- and outbound (total) int getNumConnections(unsigned int flags = CONNECTIONS_ALL) const; int getNumBlocks() const; - uint256 getBestBlockHash(); + uint256 getBestBlockHash() EXCLUSIVE_LOCKS_REQUIRED(!m_cached_tip_mutex); int getHeaderTipHeight() const; int64_t getHeaderTipTime() const; @@ -104,6 +105,7 @@ 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) EXCLUSIVE_LOCKS_REQUIRED(!m_cached_tip_mutex); void subscribeToCoreSignals(); void unsubscribeFromCoreSignals(); @@ -120,12 +122,6 @@ Q_SIGNALS: // Show progress dialog e.g. for verifychain void showProgress(const QString &title, int nProgress); - -public Q_SLOTS: - void updateNumConnections(int numConnections); - void updateNetworkActive(bool networkActive); - void updateAlert(); - void updateBanlist(); }; #endif // BITCOIN_QT_CLIENTMODEL_H diff --git a/src/qt/coincontroldialog.cpp b/src/qt/coincontroldialog.cpp index d7a2aaaf19..bd9a90a890 100644 --- a/src/qt/coincontroldialog.cpp +++ b/src/qt/coincontroldialog.cpp @@ -16,10 +16,11 @@ #include <qt/platformstyle.h> #include <qt/walletmodel.h> -#include <wallet/coincontrol.h> #include <interfaces/node.h> #include <key_io.h> #include <policy/policy.h> +#include <wallet/coincontrol.h> +#include <wallet/coinselection.h> #include <wallet/wallet.h> #include <QApplication> @@ -32,7 +33,6 @@ #include <QTreeWidget> using wallet::CCoinControl; -using wallet::MIN_CHANGE; QList<CAmount> CoinControlDialog::payAmounts; bool CoinControlDialog::fSubtractFeeFromAmount = false; @@ -485,11 +485,10 @@ void CoinControlDialog::updateLabels(CCoinControl& m_coin_control, WalletModel * if (!CoinControlDialog::fSubtractFeeFromAmount) nChange -= nPayFee; - // Never create dust outputs; if we would, just add the dust to the fee. - if (nChange > 0 && nChange < MIN_CHANGE) - { + if (nChange > 0) { // Assumes a p2pkh script size CTxOut txout(nChange, CScript() << std::vector<unsigned char>(24, 0)); + // Never create dust outputs; if we would, just add the dust to the fee. if (IsDust(txout, model->node().getDustRelayFee())) { nPayFee += nChange; @@ -508,7 +507,7 @@ void CoinControlDialog::updateLabels(CCoinControl& m_coin_control, WalletModel * } // actually update labels - int nDisplayUnit = BitcoinUnits::BTC; + BitcoinUnit nDisplayUnit = BitcoinUnit::BTC; if (model && model->getOptionsModel()) nDisplayUnit = model->getOptionsModel()->getDisplayUnit(); @@ -589,9 +588,9 @@ void CoinControlDialog::updateView() ui->treeWidget->setEnabled(false); // performance, otherwise updateLabels would be called for every checked checkbox ui->treeWidget->setAlternatingRowColors(!treeMode); QFlags<Qt::ItemFlag> flgCheckbox = Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsUserCheckable; - QFlags<Qt::ItemFlag> flgTristate = Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsUserCheckable | Qt::ItemIsTristate; + QFlags<Qt::ItemFlag> flgTristate = Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsUserCheckable | Qt::ItemIsAutoTristate; - int nDisplayUnit = model->getOptionsModel()->getDisplayUnit(); + BitcoinUnit nDisplayUnit = model->getOptionsModel()->getDisplayUnit(); for (const auto& coins : model->wallet().listCoins()) { CCoinControlWidgetItem* itemWalletAddress{nullptr}; diff --git a/src/qt/forms/debugwindow.ui b/src/qt/forms/debugwindow.ui index 2196801023..ead977296a 100644 --- a/src/qt/forms/debugwindow.ui +++ b/src/qt/forms/debugwindow.ui @@ -1594,10 +1594,10 @@ <item row="23" column="0"> <widget class="QLabel" name="peerAddrRelayEnabledLabel"> <property name="toolTip"> - <string extracomment="Tooltip text for the Address Relay field in the peer details area.">Whether we relay addresses to this peer.</string> + <string extracomment="Tooltip text for the Address Relay field in the peer details area, which displays whether we relay addresses to this peer (Yes/No).">Whether we relay addresses to this peer.</string> </property> <property name="text"> - <string>Address Relay</string> + <string extracomment="Text title for the Address Relay field in the peer details area, which displays whether we relay addresses to this peer (Yes/No).">Address Relay</string> </property> </widget> </item> @@ -1620,10 +1620,10 @@ <item row="24" column="0"> <widget class="QLabel" name="peerAddrProcessedLabel"> <property name="toolTip"> - <string extracomment="Tooltip text for the Addresses Processed field in the peer details area.">Total number of addresses processed, excluding those dropped due to rate-limiting.</string> + <string 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).">The total number of addresses received from this peer that were processed (excludes addresses that were dropped due to rate-limiting).</string> </property> <property name="text"> - <string>Addresses Processed</string> + <string 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).">Addresses Processed</string> </property> </widget> </item> @@ -1646,10 +1646,10 @@ <item row="25" column="0"> <widget class="QLabel" name="peerAddrRateLimitedLabel"> <property name="toolTip"> - <string extracomment="Tooltip text for the Addresses Rate-Limited field in the peer details area.">Total number of addresses dropped due to rate-limiting.</string> + <string 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.">The total number of addresses received from this peer that were dropped (not processed) due to rate-limiting.</string> </property> <property name="text"> - <string>Addresses Rate-Limited</string> + <string 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.">Addresses Rate-Limited</string> </property> </widget> </item> 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 9565fa508f..5cc21dd40b 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> @@ -47,6 +44,7 @@ #include <QGuiApplication> #include <QJsonObject> #include <QKeyEvent> +#include <QKeySequence> #include <QLatin1String> #include <QLineEdit> #include <QList> @@ -55,10 +53,12 @@ #include <QMouseEvent> #include <QPluginLoader> #include <QProgressDialog> +#include <QRegularExpression> #include <QScreen> #include <QSettings> #include <QShortcut> #include <QSize> +#include <QStandardPaths> #include <QString> #include <QTextDocument> // for Qt::mightBeRichText #include <QThread> @@ -72,13 +72,15 @@ #include <string> #include <vector> -#if defined(Q_OS_MAC) +#if defined(Q_OS_MACOS) #include <QProcess> void ForceActivation(); #endif +using namespace std::chrono_literals; + namespace GUIUtil { QString dateTimeStr(const QDateTime &date) @@ -174,8 +176,7 @@ bool parseBitcoinURI(const QUrl &uri, SendCoinsRecipient *out) { if(!i->second.isEmpty()) { - if(!BitcoinUnits::parse(BitcoinUnits::BTC, i->second, &rv.amount)) - { + if (!BitcoinUnits::parse(BitcoinUnit::BTC, i->second, &rv.amount)) { return false; } } @@ -207,7 +208,7 @@ QString formatBitcoinURI(const SendCoinsRecipient &info) if (info.amount) { - ret += QString("?amount=%1").arg(BitcoinUnits::format(BitcoinUnits::BTC, info.amount, false, BitcoinUnits::SeparatorStyle::NEVER)); + ret += QString("?amount=%1").arg(BitcoinUnits::format(BitcoinUnit::BTC, info.amount, false, BitcoinUnits::SeparatorStyle::NEVER)); paramCount++; } @@ -289,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) @@ -306,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); @@ -354,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; } @@ -396,7 +396,7 @@ bool isObscured(QWidget *w) void bringToFront(QWidget* w) { -#ifdef Q_OS_MAC +#ifdef Q_OS_MACOS ForceActivation(); #endif @@ -414,7 +414,7 @@ void bringToFront(QWidget* w) void handleCloseWindowShortcut(QWidget* w) { - QObject::connect(new QShortcut(QKeySequence(Qt::CTRL + Qt::Key_W), w), &QShortcut::activated, w, &QWidget::close); + QObject::connect(new QShortcut(QKeySequence(QObject::tr("Ctrl+W")), w), &QShortcut::activated, w, &QWidget::close); } void openDebugLogfile() @@ -428,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}; @@ -440,7 +440,7 @@ bool openBitcoinConf() /* Open bitcoin.conf with the associated application */ bool res = QDesktopServices::openUrl(QUrl::fromLocalFile(PathToQString(pathConfig))); -#ifdef Q_OS_MAC +#ifdef Q_OS_MACOS // Workaround for macOS-specific behavior; see #15409. if (!res) { res = QProcess::startDetached("/usr/bin/open", QStringList{"-t", PathToQString(pathConfig)}); @@ -505,7 +505,7 @@ fs::path static StartupShortcutPath() return GetSpecialFolderPath(CSIDL_STARTUP) / "Bitcoin.lnk"; if (chain == CBaseChainParams::TESTNET) // Remove this special case when CBaseChainParams::TESTNET = "testnet4" return GetSpecialFolderPath(CSIDL_STARTUP) / "Bitcoin (testnet).lnk"; - return GetSpecialFolderPath(CSIDL_STARTUP) / strprintf("Bitcoin (%s).lnk", chain); + return GetSpecialFolderPath(CSIDL_STARTUP) / fs::u8path(strprintf("Bitcoin (%s).lnk", chain)); } bool GetStartOnSystemStartup() @@ -586,7 +586,7 @@ fs::path static GetAutostartFilePath() std::string chain = gArgs.GetChainName(); if (chain == CBaseChainParams::MAIN) return GetAutostartDir() / "bitcoin.desktop"; - return GetAutostartDir() / strprintf("bitcoin-%s.desktop", chain); + return GetAutostartDir() / fs::u8path(strprintf("bitcoin-%s.desktop", chain)); } bool GetStartOnSystemStartup() @@ -713,23 +713,28 @@ QString ConnectionTypeToQString(ConnectionType conn_type, bool prepend_direction QString formatDurationStr(std::chrono::seconds dur) { - const auto secs = count_seconds(dur); - QStringList strList; - int days = secs / 86400; - int hours = (secs % 86400) / 3600; - int mins = (secs % 3600) / 60; - int seconds = secs % 60; - - if (days) - strList.append(QObject::tr("%1 d").arg(days)); - if (hours) - strList.append(QObject::tr("%1 h").arg(hours)); - if (mins) - strList.append(QObject::tr("%1 m").arg(mins)); - if (seconds || (!days && !hours && !mins)) - strList.append(QObject::tr("%1 s").arg(seconds)); + using days = std::chrono::duration<int, std::ratio<86400>>; // can remove this line after C++20 + const auto d{std::chrono::duration_cast<days>(dur)}; + const auto h{std::chrono::duration_cast<std::chrono::hours>(dur - d)}; + const auto m{std::chrono::duration_cast<std::chrono::minutes>(dur - d - h)}; + const auto s{std::chrono::duration_cast<std::chrono::seconds>(dur - d - h - m)}; + QStringList str_list; + if (auto d2{d.count()}) str_list.append(QObject::tr("%1 d").arg(d2)); + if (auto h2{h.count()}) str_list.append(QObject::tr("%1 h").arg(h2)); + if (auto m2{m.count()}) str_list.append(QObject::tr("%1 m").arg(m2)); + const auto s2{s.count()}; + if (s2 || str_list.empty()) str_list.append(QObject::tr("%1 s").arg(s2)); + return str_list.join(" "); +} - return strList.join(" "); +QString FormatPeerAge(std::chrono::seconds time_connected) +{ + const auto time_now{GetTime<std::chrono::seconds>()}; + const auto age{time_now - time_connected}; + if (age >= 24h) return QObject::tr("%1 d").arg(age / 24h); + if (age >= 1h) return QObject::tr("%1 h").arg(age / 1h); + if (age >= 1min) return QObject::tr("%1 m").arg(age / 1min); + return QObject::tr("%1 s").arg(age / 1s); } QString formatServicesStr(quint64 mask) @@ -874,7 +879,7 @@ bool ItemDelegate::eventFilter(QObject *object, QEvent *event) void PolishProgressDialog(QProgressDialog* dialog) { -#ifdef Q_OS_MAC +#ifdef Q_OS_MACOS // Workaround for macOS-only Qt bug; see: QTBUG-65750, QTBUG-70357. const int margin = TextWidth(dialog->fontMetrics(), ("X")); dialog->resize(dialog->width() + 2 * margin, dialog->height()); @@ -888,11 +893,7 @@ void PolishProgressDialog(QProgressDialog* dialog) int TextWidth(const QFontMetrics& fm, const QString& text) { -#if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0)) return fm.horizontalAdvance(text); -#else - return fm.width(text); -#endif } void LogQtInfo() diff --git a/src/qt/guiutil.h b/src/qt/guiutil.h index 0224b18b4e..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. @@ -223,6 +231,9 @@ namespace GUIUtil /** Convert seconds into a QString with days, hours, mins, secs */ QString formatDurationStr(std::chrono::seconds dur); + /** Convert peer connection time to a QString denominated in the most relevant unit. */ + QString FormatPeerAge(std::chrono::seconds time_connected); + /** Format CNodeStats.nServices bitmask into a user-readable string */ QString formatServicesStr(quint64 mask); @@ -358,18 +369,6 @@ namespace GUIUtil #endif } - /** - * Queue a function to run in an object's event loop. This can be - * replaced by a call to the QMetaObject::invokeMethod functor overload after Qt 5.10, but - * for now use a QObject::connect for compatibility with older Qt versions, based on - * https://stackoverflow.com/questions/21646467/how-to-execute-a-functor-or-a-lambda-in-a-given-thread-in-qt-gcd-style - */ - template <typename Fn> - void ObjectInvoke(QObject* object, Fn&& function, Qt::ConnectionType connection = Qt::QueuedConnection) - { - QObject source; - QObject::connect(&source, &QObject::destroyed, object, std::forward<Fn>(function), connection); - } /** * Replaces a plain text link with an HTML tagged one. diff --git a/src/qt/initexecutor.cpp b/src/qt/initexecutor.cpp index 24ae7ba73d..d269dfec71 100644 --- a/src/qt/initexecutor.cpp +++ b/src/qt/initexecutor.cpp @@ -5,13 +5,13 @@ #include <qt/initexecutor.h> #include <interfaces/node.h> -#include <qt/guiutil.h> #include <util/system.h> #include <util/threadnames.h> #include <exception> #include <QDebug> +#include <QMetaObject> #include <QObject> #include <QString> #include <QThread> @@ -39,7 +39,7 @@ void InitExecutor::handleRunawayException(const std::exception* e) void InitExecutor::initialize() { - GUIUtil::ObjectInvoke(&m_context, [this] { + QMetaObject::invokeMethod(&m_context, [this] { try { util::ThreadRename("qt-init"); qDebug() << "Running initialization in thread"; @@ -56,7 +56,7 @@ void InitExecutor::initialize() void InitExecutor::shutdown() { - GUIUtil::ObjectInvoke(&m_context, [this] { + QMetaObject::invokeMethod(&m_context, [this] { try { qDebug() << "Running Shutdown in thread"; m_node.appShutdown(); diff --git a/src/qt/intro.cpp b/src/qt/intro.cpp index dcde7adec4..4c8b33bf28 100644 --- a/src/qt/intro.cpp +++ b/src/qt/intro.cpp @@ -226,7 +226,7 @@ bool Intro::showIfNeeded(bool& did_show_intro, int64_t& prune_MiB) } /* If current default data directory does not exist, let the user choose one */ - Intro intro(0, Params().AssumedBlockchainSize(), Params().AssumedChainStateSize()); + Intro intro(nullptr, Params().AssumedBlockchainSize(), Params().AssumedChainStateSize()); intro.setDataDirectory(dataDir); intro.setWindowIcon(QIcon(":icons/bitcoin")); did_show_intro = true; @@ -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(); @@ -298,12 +298,12 @@ void Intro::setStatus(int status, const QString &message, quint64 bytesAvailable void Intro::UpdateFreeSpaceLabel() { - QString freeString = tr("%1 GB of space available").arg(m_bytes_available / GB_BYTES); + QString freeString = tr("%n GB of space available", "", m_bytes_available / GB_BYTES); if (m_bytes_available < m_required_space_gb * GB_BYTES) { - freeString += " " + tr("(of %1 GB needed)").arg(m_required_space_gb); + freeString += " " + tr("(of %n GB needed)", "", m_required_space_gb); ui->freeSpace->setStyleSheet("QLabel { color: #800000 }"); } else if (m_bytes_available / GB_BYTES - m_required_space_gb < 10) { - freeString += " " + tr("(%1 GB needed for full chain)").arg(m_required_space_gb); + freeString += " " + tr("(%n GB needed for full chain)", "", m_required_space_gb); ui->freeSpace->setStyleSheet("QLabel { color: #999900 }"); } else { ui->freeSpace->setStyleSheet(""); diff --git a/src/qt/locale/bitcoin_en.ts b/src/qt/locale/bitcoin_en.ts index 35d8201877..0fe69051a4 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="+286"/> + <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="+429"/> <source>Proxy is <b>enabled</b>: %1</source> <translation type="unfinished"></translation> </message> <message> - <location line="-1109"/> + <location line="-1150"/> <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="+48"/> <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="-823"/> <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="+739"/> <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="-808"/> + <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,7 @@ Signing is only possible with addresses of the type 'legacy'.</source> <translation type="unfinished"></translation> </message> <message> - <location line="+158"/> + <location line="+159"/> <source>Error: %1</source> <translation type="unfinished"></translation> </message> @@ -849,7 +900,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 +1073,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 +1112,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 +1124,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 +1138,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 +1332,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 +1342,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> @@ -1353,20 +1409,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 +1476,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> @@ -1521,7 +1586,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 +1669,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 +1986,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 +1996,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 +2007,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 +2080,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 +2181,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="+186"/> <source>Privacy mode activated for the Overview tab. To unmask the values, uncheck Settings->Mask values.</source> <translation type="unfinished"></translation> </message> @@ -2271,7 +2353,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 +2363,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 +2397,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 +2409,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 +2457,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 +2472,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 +2539,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 +2653,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 +2665,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,7 +2680,7 @@ 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> @@ -2672,7 +2769,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 +2888,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 +2930,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 +3121,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 +3166,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 +3277,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 +3293,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 +3335,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 +3351,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 +3566,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 +3587,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="+759"/> <source>Send Coins</source> <translation>Send Coins</translation> </message> @@ -3657,7 +3810,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="-660"/> <source>Copy quantity</source> <translation type="unfinished"></translation> </message> @@ -3739,29 +3892,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 +3930,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 +3979,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 +4028,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 +4065,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 +4106,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 +4135,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 +4145,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 +4396,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 +4630,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 +4967,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 +5007,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 +5034,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> @@ -4924,7 +5047,7 @@ Go to File > Open Wallet to load a wallet. <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 +5124,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 +5134,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 +5183,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> @@ -5115,12 +5238,7 @@ Go to File > Open Wallet to load a wallet. <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="+6"/> <source>Fee estimation failed. Fallbackfee is disabled. Wait a few blocks or enable -fallbackfee.</source> <translation type="unfinished"></translation> </message> @@ -5161,6 +5279,11 @@ Go to File > Open Wallet to load a wallet. </message> <message> <location line="+3"/> + <source>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)</source> + <translation type="unfinished"></translation> + </message> + <message> + <location line="+4"/> <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 +5299,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> @@ -5241,6 +5369,16 @@ Go to File > Open Wallet to load a wallet. </message> <message> <location line="+3"/> + <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> @@ -5300,12 +5438,37 @@ Go to File > Open Wallet to load a wallet. <translation type="unfinished"></translation> </message> <message> - <location line="-58"/> + <location line="-65"/> <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="-104"/> + <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 +5478,12 @@ Go to File > Open Wallet to load a wallet. <translation type="unfinished"></translation> </message> <message> - <location line="+118"/> + <location line="+19"/> + <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="+114"/> <source>Config setting for %s only applied on %s network when in [%s] section.</source> <translation type="unfinished"></translation> </message> @@ -5416,11 +5584,6 @@ Go to File > Open Wallet to load a wallet. </message> <message> <location line="+1"/> - <source>Error upgrading chainstate database</source> - <translation type="unfinished"></translation> - </message> - <message> - <location line="+1"/> <source>Error: Couldn't create cursor into database</source> <translation type="unfinished"></translation> </message> @@ -5566,6 +5729,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 +5774,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 +5784,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 +5934,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> @@ -5846,11 +6009,6 @@ Go to File > Open Wallet to load a wallet. </message> <message> <location line="+1"/> - <source>Upgrading UTXO database</source> - <translation type="unfinished"></translation> - </message> - <message> - <location line="+1"/> <source>User Agent comment (%s) contains unsafe characters.</source> <translation type="unfinished"></translation> </message> @@ -5870,7 +6028,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..c5f0c933f4 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,610 @@ 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">286</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">464</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">465</context></context-group> </trans-unit> <trans-unit id="_msg61"> + <source xml:space="preserve">Internal error</source> + <context-group purpose="location"><context context-type="linenumber">474</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">475</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">181</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">205</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">598</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">604</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">619</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">693</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">174</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">197</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">1411</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">1074</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">1079</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">1081</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">1085</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">1091</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">1100</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">1123</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">1128</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">1147</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">1149</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">1174</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">1178</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">1182</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">1104</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">Error: %1</source> - <context-group purpose="location"><context context-type="linenumber">1137</context></context-group> + <context-group purpose="location"><context context-type="linenumber">1175</context></context-group> </trans-unit> - <trans-unit id="_msg157"> + <trans-unit id="_msg166"> <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">1179</context></context-group> </trans-unit> - <trans-unit id="_msg158"> + <trans-unit id="_msg167"> <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">1287</context></context-group> </trans-unit> - <trans-unit id="_msg159"> + <trans-unit id="_msg168"> <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">1288</context></context-group> </trans-unit> - <trans-unit id="_msg160"> + <trans-unit id="_msg169"> <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">1290</context></context-group> </trans-unit> - <trans-unit id="_msg161"> + <trans-unit id="_msg170"> <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">1292</context></context-group> </trans-unit> - <trans-unit id="_msg162"> + <trans-unit id="_msg171"> <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">1294</context></context-group> </trans-unit> - <trans-unit id="_msg163"> + <trans-unit id="_msg172"> <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">1296</context></context-group> </trans-unit> - <trans-unit id="_msg164"> + <trans-unit id="_msg173"> <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">1297</context></context-group> </trans-unit> - <trans-unit id="_msg165"> + <trans-unit id="_msg174"> <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">1297</context></context-group> </trans-unit> - <trans-unit id="_msg166"> + <trans-unit id="_msg175"> <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">1349</context></context-group> </trans-unit> - <trans-unit id="_msg167"> + <trans-unit id="_msg176"> <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">1349</context></context-group> </trans-unit> - <trans-unit id="_msg168"> + <trans-unit id="_msg177"> <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">1349</context></context-group> </trans-unit> - <trans-unit id="_msg169"> + <trans-unit id="_msg178"> <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">1372</context></context-group> </trans-unit> - <trans-unit id="_msg170"> + <trans-unit id="_msg179"> <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">1380</context></context-group> </trans-unit> - <trans-unit id="_msg171"> + <trans-unit id="_msg180"> <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">1499</context></context-group> </trans-unit> </group> <group restype="x-trolltech-linguist-context" resname="UnitDisplayStatusBarControl"> - <trans-unit id="_msg172"> + <trans-unit id="_msg181"> <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">1540</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="_msg182"> <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="_msg183"> <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="_msg184"> <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="_msg185"> <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="_msg186"> <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="_msg187"> <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="_msg188"> <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="_msg189"> <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="_msg190"> <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="_msg191"> <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="_msg192"> <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="_msg193"> <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="_msg194"> <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="_msg195"> <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="_msg196"> <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="_msg197"> <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="_msg198"> <source xml:space="preserve">Confirmed</source> <context-group purpose="location"><context context-type="linenumber">443</context></context-group> </trans-unit> @@ -836,232 +878,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="_msg199"> <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="_msg200"> <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="_msg201"> <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="_msg202"> <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="_msg203"> <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="_msg204"> <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="_msg205"> <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="_msg206"> <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="_msg207"> <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="_msg208"> <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="_msg209"> <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="_msg210"> <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="_msg211"> <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="_msg212"> <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="_msg213"> <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="_msg214"> <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="_msg215"> <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="_msg216"> <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="_msg217"> <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="_msg218"> <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="_msg219"> <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="_msg220"> <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="_msg221"> <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="_msg222"> <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="_msg223"> <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="_msg224"> <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="_msg225"> + <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="_msg226"> <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="_msg227"> <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="_msg228"> <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="_msg229"> <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="_msg230"> <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="_msg231"> <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="_msg232"> <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="_msg233"> + <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="_msg234"> + <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="_msg235"> + <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="_msg236"> + <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="_msg237"> + <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="_msg238"> <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="_msg239"> <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="_msg240"> <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="_msg241"> <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="_msg242"> <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="_msg243"> <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="_msg244"> <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="_msg245"> <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="_msg246"> <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="_msg247"> <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="_msg248"> <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="_msg249"> <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="_msg250"> <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="_msg251"> <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="_msg252"> <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="_msg253"> <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="_msg254"> <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="_msg255"> <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="_msg256"> <source xml:space="preserve">External signer</source> <context-group purpose="location"><context context-type="linenumber">121</context></context-group> </trans-unit> @@ -1069,15 +1142,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="_msg257"> <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="_msg258"> <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="_msg259"> <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 +1159,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="_msg260"> <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="_msg261"> <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="_msg262"> <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="_msg263"> <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="_msg264"> <source xml:space="preserve">&Address</source> <context-group purpose="location"><context context-type="linenumber">42</context></context-group> </trans-unit> @@ -1110,35 +1183,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="_msg265"> <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="_msg266"> <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="_msg267"> <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="_msg268"> <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="_msg269"> <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="_msg270"> <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="_msg271"> <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="_msg272"> <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 +1219,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="_msg273"> <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="_msg274"> <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="_msg275"> <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="_msg276"> <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="_msg277"> <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="_msg278"> <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="_msg279[0]"> + <source xml:space="preserve">%n GB of space available</source> + </trans-unit> + <trans-unit id="_msg279[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="_msg280[0]"> + <source xml:space="preserve">(of %n GB needed)</source> + </trans-unit> + <trans-unit id="_msg280[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="_msg281[0]"> + <source xml:space="preserve">(%n GB needed for full chain)</source> + </trans-unit> + <trans-unit id="_msg281[1]"> + <source xml:space="preserve">(%n GB needed for full chain)</source> + </trans-unit> + </group> + <trans-unit id="_msg282"> <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="_msg283"> <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="_msg284[0]"> <source xml:space="preserve">(sufficient to restore backups %n day(s) old)</source> </trans-unit> - <trans-unit id="_msg269[1]"> + <trans-unit id="_msg284[1]"> <source xml:space="preserve">(sufficient to restore backups %n day(s) old)</source> </trans-unit> </group> - <trans-unit id="_msg270"> + <trans-unit id="_msg285"> <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="_msg286"> <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="_msg287"> <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="_msg288"> <source xml:space="preserve">Error</source> <context-group purpose="location"><context context-type="linenumber">280</context></context-group> </trans-unit> @@ -1222,25 +1310,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="_msg289"> <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="_msg290"> <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="_msg291"> <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="_msg292"> <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="_msg293"> <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 +1336,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="_msg294"> <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="_msg295"> <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="_msg296"> <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"> + <trans-unit id="_msg297"> <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">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="_msg303"> <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="_msg304"> <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 +1384,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="_msg305"> <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="_msg306"> <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="_msg307"> <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="_msg308"> <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="_msg309"> <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> </trans-unit> - <trans-unit id="_msg295"> + <trans-unit id="_msg310"> <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="_msg311"> <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="_msg312"> <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="_msg313"> <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="_msg314"> <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="_msg315"> <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="_msg316"> <source xml:space="preserve">Esc</source> <context-group purpose="location"><context context-type="linenumber">345</context></context-group> </trans-unit> @@ -1351,17 +1439,17 @@ 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="_msg317"> <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="_msg318"> <source xml:space="preserve">Unknown. Syncing Headers (%1, %2%)…</source> <context-group purpose="location"><context context-type="linenumber">158</context></context-group> </trans-unit> </group> <group restype="x-trolltech-linguist-context" resname="QObject"> - <trans-unit id="_msg304"> + <trans-unit id="_msg319"> <source xml:space="preserve">unknown</source> <context-group purpose="location"><context context-type="linenumber">123</context></context-group> </trans-unit> @@ -1369,15 +1457,15 @@ Signing is only possible with addresses of the type 'legacy'.</source> </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="_msg320"> <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="_msg321"> <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="_msg322"> <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 +1474,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="_msg323"> <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="_msg324"> <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="_msg325"> <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="_msg326"> <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="_msg327"> <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="_msg328"> <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="_msg329"> <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="_msg330"> <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="_msg331"> <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="_msg332"> <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="_msg333"> + <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="_msg334"> <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="_msg335"> <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="_msg336"> <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="_msg337"> <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="_msg338"> <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="_msg339"> <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="_msg340"> <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="_msg341"> <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="_msg342"> <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="_msg343"> <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="_msg344"> <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="_msg345"> <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="_msg346"> <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="_msg347"> <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="_msg348"> <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="_msg349"> <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="_msg350"> <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="_msg351"> <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="_msg352"> <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="_msg353"> <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="_msg354"> <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="_msg355"> <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="_msg356"> <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="_msg357"> <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="_msg358"> <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="_msg359"> <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="_msg360"> <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="_msg361"> <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="_msg362"> <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="_msg363"> <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="_msg364"> <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="_msg365"> <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="_msg366"> <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="_msg367"> <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="_msg368"> <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="_msg369"> <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="_msg370"> <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="_msg371"> <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="_msg372"> <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="_msg373"> <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="_msg374"> <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="_msg375"> <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="_msg376"> <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="_msg377"> <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="_msg378"> <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="_msg379"> <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="_msg380"> <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="_msg381"> <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="_msg382"> <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="_msg383"> <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="_msg384"> <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="_msg385"> <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="_msg386"> <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="_msg387"> <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="_msg388"> <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="_msg389"> <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="_msg390"> <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="_msg391"> <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="_msg392"> <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="_msg393"> <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="_msg394"> <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="_msg395"> <source xml:space="preserve">&Cancel</source> <context-group purpose="location"><context context-type="linenumber">1053</context></context-group> </trans-unit> @@ -1697,140 +1785,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="_msg396"> <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="_msg397"> <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="_msg398"> <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="_msg399"> <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="_msg400"> <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="_msg401"> + <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="_msg402"> <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="_msg403"> <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="_msg404"> <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="_msg405"> <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="_msg406"> <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="_msg407"> <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="_msg408"> <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="_msg409"> <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="_msg410"> <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="_msg411"> + <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="_msg412"> <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="_msg413"> <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="_msg414"> <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="_msg415"> <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="_msg416"> <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="_msg417"> <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="_msg418"> <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="_msg419"> <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="_msg420"> <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="_msg421"> <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="_msg422"> <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="_msg423"> <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="_msg424"> <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="_msg425"> <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="_msg426"> <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="_msg427"> <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="_msg428"> <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="_msg429"> <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 +1942,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="_msg430"> <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">186</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="_msg431"> <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="_msg432"> <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="_msg433"> <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="_msg434"> <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="_msg435"> <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="_msg436"> <source xml:space="preserve">Close</source> <context-group purpose="location"><context context-type="linenumber">136</context></context-group> </trans-unit> @@ -1874,108 +1978,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="_msg437"> <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="_msg438"> <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="_msg439"> <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="_msg440"> <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="_msg441"> <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="_msg442"> <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="_msg443"> <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="_msg444"> <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="_msg445"> <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="_msg446"> <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="_msg447"> <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="_msg448"> <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="_msg449"> <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="_msg450"> <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="_msg451"> <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="_msg452"> <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="_msg453"> <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="_msg454"> <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="_msg455"> <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="_msg456"> <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="_msg457"> <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="_msg458"> <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="_msg459"> <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="_msg460"> <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="_msg461"> <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="_msg462"> <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 +2087,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="_msg463"> <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="_msg464"> <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="_msg465"> <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="_msg466"> <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="_msg467"> <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="_msg468"> <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="_msg469"> <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="_msg470"> <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="_msg471"> <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="_msg472"> <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="_msg473"> + <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="_msg474"> <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="_msg475"> <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="_msg476"> <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="_msg477"> <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="_msg478"> <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="_msg479"> <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="_msg480"> <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="_msg481"> <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="_msg482"> <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="_msg483"> <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="_msg484"> + <source xml:space="preserve">Ctrl+W</source> + <context-group purpose="location"><context context-type="linenumber">417</context></context-group> + </trans-unit> + <trans-unit id="_msg485"> <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="_msg486"> <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="_msg487"> <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="_msg488"> <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="_msg489"> <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="_msg490"> <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="_msg491"> <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="_msg492"> <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="_msg493"> <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="_msg494"> <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="_msg495"> <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="_msg496"> <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="_msg497"> <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="_msg498"> <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="_msg499"> <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="_msg500"> <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="_msg501[0]"> <source xml:space="preserve">%n second(s)</source> </trans-unit> - <trans-unit id="_msg482[1]"> + <trans-unit id="_msg501[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="_msg502[0]"> <source xml:space="preserve">%n minute(s)</source> </trans-unit> - <trans-unit id="_msg483[1]"> + <trans-unit id="_msg502[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="_msg503[0]"> <source xml:space="preserve">%n hour(s)</source> </trans-unit> - <trans-unit id="_msg484[1]"> + <trans-unit id="_msg503[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="_msg504[0]"> <source xml:space="preserve">%n day(s)</source> </trans-unit> - <trans-unit id="_msg485[1]"> + <trans-unit id="_msg504[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="_msg505[0]"> <source xml:space="preserve">%n week(s)</source> </trans-unit> - <trans-unit id="_msg486[1]"> + <trans-unit id="_msg505[1]"> <source xml:space="preserve">%n week(s)</source> </trans-unit> </group> - <trans-unit id="_msg487"> + <trans-unit id="_msg506"> <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="_msg507[0]"> <source xml:space="preserve">%n year(s)</source> </trans-unit> - <trans-unit id="_msg488[1]"> + <trans-unit id="_msg507[1]"> <source xml:space="preserve">%n year(s)</source> </trans-unit> </group> - <trans-unit id="_msg489"> + <trans-unit id="_msg508"> <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="_msg509"> <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="_msg510"> <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="_msg511"> <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="_msg512"> <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="_msg513"> <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="_msg514"> <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="_msg515"> <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="_msg516"> <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="_msg517"> <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="_msg518"> <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 +2397,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="_msg519"> <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 +2436,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="_msg520"> <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="_msg521"> <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="_msg522"> <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="_msg523"> <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="_msg524"> <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="_msg525"> <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="_msg526"> <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="_msg527"> <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="_msg528"> <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="_msg529"> <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="_msg530"> <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="_msg531"> <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="_msg532"> <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="_msg533"> <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="_msg534"> <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="_msg535"> <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="_msg536"> <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="_msg537"> <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="_msg538"> <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="_msg539"> <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="_msg540"> <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="_msg541"> <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="_msg542"> <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="_msg543"> <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="_msg544"> <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="_msg545"> <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="_msg546"> <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="_msg547"> <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="_msg548"> <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="_msg549"> <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="_msg550"> <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="_msg551"> <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="_msg552"> + <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="_msg553"> + <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="_msg554"> <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="_msg555"> <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="_msg556"> <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="_msg557"> <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="_msg558"> <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="_msg559"> <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="_msg560"> <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="_msg561"> <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="_msg562"> <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="_msg563"> <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="_msg564"> <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="_msg565"> <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="_msg566"> <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="_msg567"> <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="_msg568"> <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="_msg569"> <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="_msg570"> <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="_msg571"> <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="_msg572"> <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="_msg573"> <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="_msg574"> <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="_msg575"> <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="_msg576"> <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="_msg577"> <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="_msg578"> <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="_msg579"> <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="_msg580"> <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="_msg581"> <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="_msg582"> <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="_msg583"> <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="_msg584"> <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="_msg585"> <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="_msg586"> <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="_msg587"> <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="_msg588"> <source xml:space="preserve">Clear console</source> <context-group purpose="location"><context context-type="linenumber">515</context></context-group> </trans-unit> @@ -2610,123 +2730,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="_msg589"> <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="_msg590"> <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="_msg591"> <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="_msg592"> <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="_msg593"> <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="_msg594"> <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="_msg595"> <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="_msg596"> <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="_msg597"> <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="_msg598"> <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="_msg599"> <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="_msg600"> <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="_msg601"> <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="_msg602"> <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="_msg603"> <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="_msg604"> <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="_msg605"> <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="_msg606"> <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="_msg607"> <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="_msg608"> <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="_msg609"> <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="_msg610"> <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="_msg611"> <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="_msg612"> <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="_msg613"> <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="_msg614"> + <source xml:space="preserve">Ctrl+I</source> + <context-group purpose="location"><context context-type="linenumber">1351</context></context-group> + </trans-unit> + <trans-unit id="_msg615"> + <source xml:space="preserve">Ctrl+T</source> + <context-group purpose="location"><context context-type="linenumber">1352</context></context-group> + </trans-unit> + <trans-unit id="_msg616"> + <source xml:space="preserve">Ctrl+N</source> + <context-group purpose="location"><context context-type="linenumber">1353</context></context-group> + </trans-unit> + <trans-unit id="_msg617"> + <source xml:space="preserve">Ctrl+P</source> + <context-group purpose="location"><context context-type="linenumber">1354</context></context-group> + </trans-unit> + <trans-unit id="_msg618"> <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="_msg619"> <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 +2870,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="_msg620"> <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="_msg621"> <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="_msg622"> <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="_msg623"> <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="_msg624"> <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="_msg625"> <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="_msg626"> <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="_msg627"> <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="_msg628"> <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="_msg629"> <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="_msg630"> <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="_msg631"> <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="_msg632"> <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="_msg633"> <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="_msg634"> <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="_msg635"> <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="_msg636"> <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="_msg637"> <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="_msg638"> <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="_msg639"> <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="_msg640"> <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="_msg641"> <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="_msg642"> <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="_msg643"> <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="_msg644"> <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="_msg645"> <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="_msg646"> <source xml:space="preserve">Remove</source> <context-group purpose="location"><context context-type="linenumber">321</context></context-group> </trans-unit> @@ -2859,31 +2995,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="_msg647"> <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="_msg648"> <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="_msg649"> <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="_msg650"> <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="_msg651"> <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="_msg652"> <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="_msg653"> <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 +3027,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="_msg654"> <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="_msg655"> <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="_msg656"> <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="_msg657"> <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="_msg658"> <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="_msg659"> <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="_msg660"> <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="_msg661"> <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="_msg662"> <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="_msg663"> <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="_msg664"> <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="_msg665"> <source xml:space="preserve">Payment information</source> <context-group purpose="location"><context context-type="linenumber">39</context></context-group> </trans-unit> @@ -2943,7 +3079,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="_msg666"> <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 +3087,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="_msg667"> <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="_msg668"> <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="_msg669"> <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="_msg670"> <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="_msg671"> <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="_msg672"> <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="_msg673"> <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="_msg674"> <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">759</context></context-group> </trans-unit> - <trans-unit id="_msg652"> + <trans-unit id="_msg675"> <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="_msg676"> <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="_msg677"> <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="_msg678"> <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="_msg679"> <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="_msg680"> <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="_msg681"> <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="_msg682"> <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="_msg683"> <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="_msg684"> <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="_msg685"> <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="_msg686"> <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="_msg687"> <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="_msg688"> <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="_msg689"> <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="_msg690"> <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="_msg691"> <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="_msg692"> <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="_msg693"> <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="_msg694"> <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="_msg695"> <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="_msg696"> <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="_msg697"> <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="_msg698"> <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="_msg699"> <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="_msg700"> <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="_msg701"> <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="_msg702"> <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="_msg703"> <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="_msg704"> <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="_msg705"> <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="_msg706"> <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="_msg707"> <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="_msg708"> <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="_msg709"> <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="_msg710"> <source xml:space="preserve">S&end</source> <context-group purpose="location"><context context-type="linenumber">1120</context></context-group> </trans-unit> @@ -3138,422 +3274,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="_msg711"> <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="_msg712"> <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="_msg713"> <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="_msg714"> <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="_msg715"> <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="_msg716"> <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="_msg717"> <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="_msg718"> <source xml:space="preserve">%1 (%2 blocks)</source> <context-group purpose="location"><context context-type="linenumber">181</context></context-group> </trans-unit> - <trans-unit id="_msg696"> + <trans-unit id="_msg719"> <source xml:space="preserve">Sign on device</source> <context-group purpose="location"><context context-type="linenumber">211</context></context-group> <note annotates="source" from="developer">"device" usually means a hardware wallet.</note> </trans-unit> - <trans-unit id="_msg697"> + <trans-unit id="_msg720"> <source xml:space="preserve">Connect your hardware wallet first.</source> <context-group purpose="location"><context context-type="linenumber">214</context></context-group> </trans-unit> - <trans-unit id="_msg698"> + <trans-unit id="_msg721"> <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> <note annotates="source" from="developer">"External signer" means using devices such as hardware wallets.</note> </trans-unit> - <trans-unit id="_msg699"> + <trans-unit id="_msg722"> <source xml:space="preserve">Cr&eate Unsigned</source> <context-group purpose="location"><context context-type="linenumber">221</context></context-group> </trans-unit> - <trans-unit id="_msg700"> + <trans-unit id="_msg723"> <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> </trans-unit> - <trans-unit id="_msg701"> + <trans-unit id="_msg724"> <source xml:space="preserve"> from wallet '%1'</source> <context-group purpose="location"><context context-type="linenumber">312</context></context-group> </trans-unit> - <trans-unit id="_msg702"> + <trans-unit id="_msg725"> <source xml:space="preserve">%1 to '%2'</source> <context-group purpose="location"><context context-type="linenumber">323</context></context-group> </trans-unit> - <trans-unit id="_msg703"> + <trans-unit id="_msg726"> <source xml:space="preserve">%1 to %2</source> <context-group purpose="location"><context context-type="linenumber">328</context></context-group> </trans-unit> - <trans-unit id="_msg704"> + <trans-unit id="_msg727"> <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">394</context></context-group> </trans-unit> - <trans-unit id="_msg705"> + <trans-unit id="_msg728"> <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">454</context></context-group> </trans-unit> - <trans-unit id="_msg706"> + <trans-unit id="_msg729"> <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">459</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="_msg730"> <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">464</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="_msg731"> <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">430</context></context-group> </trans-unit> - <trans-unit id="_msg709"> + <trans-unit id="_msg732"> <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">432</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="_msg733"> <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">439</context></context-group> </trans-unit> - <trans-unit id="_msg711"> + <trans-unit id="_msg734"> <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">705</context></context-group> </trans-unit> - <trans-unit id="_msg712"> + <trans-unit id="_msg735"> <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">390</context></context-group> </trans-unit> - <trans-unit id="_msg713"> + <trans-unit id="_msg736"> <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> </trans-unit> - <trans-unit id="_msg714"> + <trans-unit id="_msg737"> <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> <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="_msg738"> <source xml:space="preserve">Do you want to create this transaction?</source> <context-group purpose="location"><context context-type="linenumber">336</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="_msg739"> <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> <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="_msg740"> <source xml:space="preserve">Please, review your transaction.</source> <context-group purpose="location"><context context-type="linenumber">350</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="_msg741"> <source xml:space="preserve">Transaction fee</source> <context-group purpose="location"><context context-type="linenumber">358</context></context-group> </trans-unit> - <trans-unit id="_msg719"> + <trans-unit id="_msg742"> <source xml:space="preserve">Not signalling Replace-By-Fee, BIP-125.</source> <context-group purpose="location"><context context-type="linenumber">374</context></context-group> </trans-unit> - <trans-unit id="_msg720"> + <trans-unit id="_msg743"> <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">387</context></context-group> </trans-unit> - <trans-unit id="_msg721"> + <trans-unit id="_msg744"> <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">486</context></context-group> </trans-unit> - <trans-unit id="_msg722"> + <trans-unit id="_msg745"> <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">708</context></context-group> </trans-unit> - <trans-unit id="_msg723"> + <trans-unit id="_msg746"> <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">732</context></context-group> </trans-unit> - <trans-unit id="_msg724"> + <trans-unit id="_msg747"> <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">735</context></context-group> </trans-unit> - <trans-unit id="_msg725"> + <trans-unit id="_msg748"> <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">738</context></context-group> </trans-unit> - <trans-unit id="_msg726"> + <trans-unit id="_msg749"> <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">741</context></context-group> </trans-unit> - <trans-unit id="_msg727"> + <trans-unit id="_msg750"> <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">744</context></context-group> </trans-unit> - <trans-unit id="_msg728"> + <trans-unit id="_msg751"> <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">747</context></context-group> </trans-unit> - <trans-unit id="_msg729"> + <trans-unit id="_msg752"> <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">751</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">874</context></context-group> + <trans-unit id="_msg753[0]"> <source xml:space="preserve">Estimated to begin confirmation within %n block(s).</source> </trans-unit> - <trans-unit id="_msg731[1]"> + <trans-unit id="_msg753[1]"> <source xml:space="preserve">Estimated to begin confirmation within %n block(s).</source> </trans-unit> </group> - <trans-unit id="_msg732"> + <trans-unit id="_msg754"> <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">975</context></context-group> </trans-unit> - <trans-unit id="_msg733"> + <trans-unit id="_msg755"> <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">980</context></context-group> </trans-unit> - <trans-unit id="_msg734"> + <trans-unit id="_msg756"> <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">983</context></context-group> </trans-unit> - <trans-unit id="_msg735"> + <trans-unit id="_msg757"> <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">983</context></context-group> </trans-unit> - <trans-unit id="_msg736"> + <trans-unit id="_msg758"> <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">1004</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="_msg759"> <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="_msg760"> <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="_msg761"> <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="_msg762"> <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="_msg763"> <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="_msg764"> <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="_msg765"> <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="_msg766"> <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="_msg767"> <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="_msg768"> <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="_msg769"> <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="_msg770"> <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="_msg771"> <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="_msg772"> <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="_msg773"> <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="_msg774"> <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="_msg775"> <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="_msg776"> <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="_msg777"> <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="_msg778"> <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="_msg779"> <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="_msg780"> <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="_msg781"> <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="_msg782"> <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="_msg783"> <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="_msg784"> <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="_msg785"> <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="_msg786"> <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="_msg787"> <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="_msg788"> <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="_msg789"> <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="_msg790"> <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="_msg791"> <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="_msg792"> <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="_msg793"> <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="_msg794"> <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="_msg795"> <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="_msg796"> <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="_msg797"> <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="_msg798"> <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="_msg799"> <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="_msg800"> <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 +3671,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="_msg801"> <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="_msg802"> <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="_msg803"> <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="_msg804"> <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="_msg805"> <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="_msg806"> <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="_msg807"> <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="_msg808"> <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="_msg809"> <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="_msg810"> <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="_msg811"> <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="_msg812"> <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="_msg813"> <source xml:space="preserve">Message verified.</source> <context-group purpose="location"><context context-type="linenumber">214</context></context-group> </trans-unit> @@ -3623,11 +3733,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="_msg814"> <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="_msg815"> <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 +3745,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="_msg816"> <source xml:space="preserve">kB/s</source> <context-group purpose="location"><context context-type="linenumber">79</context></context-group> </trans-unit> @@ -3643,190 +3753,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="_msg817"> <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="_msg818"> + <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="_msg819"> + <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="_msg820"> <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="_msg821"> <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="_msg822"> <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="_msg823"> <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="_msg824"> <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="_msg825"> <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="_msg826"> <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="_msg827"> <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="_msg828"> <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="_msg829"> <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="_msg830"> <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="_msg831"> <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="_msg832"> <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="_msg833"> <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="_msg834[0]"> <source xml:space="preserve">matures in %n more block(s)</source> </trans-unit> - <trans-unit id="_msg817[1]"> + <trans-unit id="_msg834[1]"> <source xml:space="preserve">matures in %n more block(s)</source> </trans-unit> </group> - <trans-unit id="_msg818"> + <trans-unit id="_msg835"> <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="_msg836"> <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="_msg837"> <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="_msg838"> <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="_msg839"> <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="_msg840"> <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="_msg841"> <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="_msg842"> <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="_msg843"> <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="_msg844"> <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="_msg845"> <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="_msg846"> <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="_msg847"> <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="_msg848"> <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="_msg849"> <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="_msg850"> <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="_msg851"> <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="_msg852"> <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="_msg853"> <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="_msg854"> <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="_msg855"> <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="_msg856"> <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 +3946,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="_msg857"> <source xml:space="preserve">Details for %1</source> <context-group purpose="location"><context context-type="linenumber">18</context></context-group> </trans-unit> @@ -3842,266 +3954,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="_msg858"> <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="_msg859"> <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="_msg860"> <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="_msg861"> <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="_msg862"> <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="_msg863"> <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="_msg864"> <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="_msg865"> <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="_msg866"> <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="_msg867"> <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="_msg868"> <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="_msg869"> <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="_msg870"> <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="_msg871"> <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="_msg872"> <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="_msg873"> <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="_msg874"> <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="_msg875"> <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="_msg876"> <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="_msg877"> <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="_msg878"> <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="_msg879"> <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="_msg880"> <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="_msg881"> <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="_msg882"> <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="_msg883"> <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="_msg884"> <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="_msg885"> <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="_msg886"> <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="_msg887"> <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="_msg888"> <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="_msg889"> <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="_msg890"> <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="_msg891"> <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="_msg892"> <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="_msg893"> <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="_msg894"> <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="_msg895"> <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="_msg896"> <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="_msg897"> <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="_msg898"> <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="_msg899"> <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="_msg900"> <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="_msg901"> <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="_msg902"> <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="_msg903"> <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="_msg904"> <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="_msg905"> <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="_msg906"> <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="_msg907"> <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="_msg908"> <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="_msg909"> <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="_msg910"> <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="_msg911"> <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="_msg912"> <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="_msg913"> <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="_msg914"> <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="_msg915"> <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="_msg916"> <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="_msg917"> <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="_msg918"> <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="_msg919"> <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="_msg920"> <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="_msg921"> <source xml:space="preserve">to</source> <context-group purpose="location"><context context-type="linenumber">566</context></context-group> </trans-unit> @@ -4109,810 +4221,838 @@ 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="_msg922"> <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="_msg923"> <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="_msg924"> <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="_msg925"> <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="_msg926"> <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="_msg927"> <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="_msg928"> <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="_msg929"> <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="_msg930"> <source xml:space="preserve">Send Coins</source> <context-group purpose="location"><context context-type="linenumber">221</context></context-group> </trans-unit> - <trans-unit id="_msg914"> + <trans-unit id="_msg931"> <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">484</context></context-group> + <context-group purpose="location"><context context-type="linenumber">536</context></context-group> + <context-group purpose="location"><context context-type="linenumber">551</context></context-group> + <context-group purpose="location"><context context-type="linenumber">556</context></context-group> </trans-unit> - <trans-unit id="_msg915"> + <trans-unit id="_msg932"> <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">484</context></context-group> </trans-unit> - <trans-unit id="_msg916"> + <trans-unit id="_msg933"> <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">491</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="_msg934"> <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">495</context></context-group> </trans-unit> - <trans-unit id="_msg918"> + <trans-unit id="_msg935"> <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">499</context></context-group> </trans-unit> - <trans-unit id="_msg919"> + <trans-unit id="_msg936"> <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">503</context></context-group> </trans-unit> - <trans-unit id="_msg920"> + <trans-unit id="_msg937"> <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">511</context></context-group> </trans-unit> - <trans-unit id="_msg921"> + <trans-unit id="_msg938"> <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">514</context></context-group> </trans-unit> - <trans-unit id="_msg922"> + <trans-unit id="_msg939"> <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">536</context></context-group> </trans-unit> - <trans-unit id="_msg923"> + <trans-unit id="_msg940"> <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">543</context></context-group> </trans-unit> - <trans-unit id="_msg924"> + <trans-unit id="_msg941"> <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">551</context></context-group> </trans-unit> - <trans-unit id="_msg925"> + <trans-unit id="_msg942"> <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">556</context></context-group> </trans-unit> - <trans-unit id="_msg926"> + <trans-unit id="_msg943"> <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">570</context></context-group> </trans-unit> - <trans-unit id="_msg927"> + <trans-unit id="_msg944"> <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">588</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="_msg945"> <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="_msg946"> <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="_msg947"> <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="_msg948"> <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="_msg949"> <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="_msg950"> <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="_msg951"> <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="_msg952"> <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="_msg953"> <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="_msg954"> <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="_msg955"> <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="_msg956"> <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="_msg957"> <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="_msg958"> <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="_msg959"> <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="_msg960"> <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="_msg961"> <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="_msg962"> <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="_msg963"> <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">62</context></context-group> </trans-unit> - <trans-unit id="_msg947"> + <trans-unit id="_msg964"> <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">64</context></context-group> </trans-unit> - <trans-unit id="_msg948"> + <trans-unit id="_msg965"> <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">66</context></context-group> </trans-unit> - <trans-unit id="_msg949"> + <trans-unit id="_msg966"> <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">69</context></context-group> </trans-unit> - <trans-unit id="_msg951"> + <trans-unit id="_msg967"> <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">75</context></context-group> </trans-unit> - <trans-unit id="_msg952"> + <trans-unit id="_msg968"> <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">78</context></context-group> </trans-unit> - <trans-unit id="_msg953"> + <trans-unit id="_msg969"> <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">81</context></context-group> </trans-unit> - <trans-unit id="_msg954"> + <trans-unit id="_msg970"> <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">84</context></context-group> </trans-unit> - <trans-unit id="_msg955"> + <trans-unit id="_msg971"> <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">88</context></context-group> </trans-unit> - <trans-unit id="_msg956"> + <trans-unit id="_msg972"> <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">91</context></context-group> </trans-unit> - <trans-unit id="_msg957"> + <trans-unit id="_msg973"> <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">94</context></context-group> </trans-unit> - <trans-unit id="_msg958"> + <trans-unit id="_msg974"> <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">96</context></context-group> </trans-unit> - <trans-unit id="_msg959"> + <trans-unit id="_msg975"> + <source xml:space="preserve">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)</source> + <context-group purpose="location"><context context-type="linenumber">99</context></context-group> + </trans-unit> + <trans-unit id="_msg976"> <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">103</context></context-group> </trans-unit> - <trans-unit id="_msg960"> + <trans-unit id="_msg977"> <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">106</context></context-group> </trans-unit> - <trans-unit id="_msg961"> + <trans-unit id="_msg978"> <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">109</context></context-group> </trans-unit> - <trans-unit id="_msg962"> + <trans-unit id="_msg979"> + <source xml:space="preserve">Prune mode is incompatible with -reindex-chainstate. Use full -reindex instead.</source> + <context-group purpose="location"><context context-type="linenumber">111</context></context-group> + </trans-unit> + <trans-unit id="_msg980"> <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">114</context></context-group> </trans-unit> - <trans-unit id="_msg963"> + <trans-unit id="_msg981"> <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">117</context></context-group> </trans-unit> - <trans-unit id="_msg964"> + <trans-unit id="_msg982"> <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">123</context></context-group> </trans-unit> - <trans-unit id="_msg965"> + <trans-unit id="_msg983"> <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">128</context></context-group> </trans-unit> - <trans-unit id="_msg966"> + <trans-unit id="_msg984"> <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">132</context></context-group> </trans-unit> - <trans-unit id="_msg967"> + <trans-unit id="_msg985"> <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">134</context></context-group> </trans-unit> - <trans-unit id="_msg968"> + <trans-unit id="_msg986"> <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">138</context></context-group> </trans-unit> - <trans-unit id="_msg969"> + <trans-unit id="_msg987"> <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">141</context></context-group> </trans-unit> - <trans-unit id="_msg970"> + <trans-unit id="_msg988"> <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">144</context></context-group> </trans-unit> - <trans-unit id="_msg971"> + <trans-unit id="_msg989"> <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">147</context></context-group> </trans-unit> - <trans-unit id="_msg972"> + <trans-unit id="_msg990"> <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">149</context></context-group> </trans-unit> - <trans-unit id="_msg973"> + <trans-unit id="_msg991"> <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">152</context></context-group> </trans-unit> - <trans-unit id="_msg974"> + <trans-unit id="_msg992"> <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">155</context></context-group> </trans-unit> - <trans-unit id="_msg975"> + <trans-unit id="_msg993"> + <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">158</context></context-group> + </trans-unit> + <trans-unit id="_msg994"> + <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">161</context></context-group> + </trans-unit> + <trans-unit id="_msg995"> <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">165</context></context-group> </trans-unit> - <trans-unit id="_msg976"> + <trans-unit id="_msg996"> <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">168</context></context-group> </trans-unit> - <trans-unit id="_msg977"> + <trans-unit id="_msg997"> <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">170</context></context-group> </trans-unit> - <trans-unit id="_msg978"> + <trans-unit id="_msg998"> <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">173</context></context-group> </trans-unit> - <trans-unit id="_msg979"> + <trans-unit id="_msg999"> <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">176</context></context-group> </trans-unit> - <trans-unit id="_msg980"> + <trans-unit id="_msg1000"> <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">179</context></context-group> </trans-unit> - <trans-unit id="_msg981"> + <trans-unit id="_msg1001"> <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">180</context></context-group> </trans-unit> - <trans-unit id="_msg982"> + <trans-unit id="_msg1002"> <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">181</context></context-group> </trans-unit> - <trans-unit id="_msg983"> + <trans-unit id="_msg1003"> <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">182</context></context-group> </trans-unit> - <trans-unit id="_msg984"> + <trans-unit id="_msg1004"> <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">183</context></context-group> </trans-unit> - <trans-unit id="_msg985"> + <trans-unit id="_msg1005"> <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">184</context></context-group> </trans-unit> - <trans-unit id="_msg986"> + <trans-unit id="_msg1006"> <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">185</context></context-group> </trans-unit> - <trans-unit id="_msg987"> + <trans-unit id="_msg1007"> <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">120</context></context-group> </trans-unit> - <trans-unit id="_msg988"> + <trans-unit id="_msg1008"> + <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="_msg1009"> + <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="_msg1010"> + <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="_msg1011"> + <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="_msg1012"> + <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="_msg1013"> <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="_msg1014"> <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="_msg1015"> + <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">72</context></context-group> + </trans-unit> + <trans-unit id="_msg1016"> <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">186</context></context-group> </trans-unit> - <trans-unit id="_msg991"> + <trans-unit id="_msg1017"> <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">187</context></context-group> </trans-unit> - <trans-unit id="_msg992"> + <trans-unit id="_msg1018"> <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">188</context></context-group> </trans-unit> - <trans-unit id="_msg993"> + <trans-unit id="_msg1019"> <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">189</context></context-group> </trans-unit> - <trans-unit id="_msg994"> + <trans-unit id="_msg1020"> <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">190</context></context-group> </trans-unit> - <trans-unit id="_msg995"> + <trans-unit id="_msg1021"> <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">191</context></context-group> </trans-unit> - <trans-unit id="_msg996"> + <trans-unit id="_msg1022"> <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">192</context></context-group> </trans-unit> - <trans-unit id="_msg997"> + <trans-unit id="_msg1023"> <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">193</context></context-group> </trans-unit> - <trans-unit id="_msg998"> + <trans-unit id="_msg1024"> <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">194</context></context-group> </trans-unit> - <trans-unit id="_msg999"> + <trans-unit id="_msg1025"> <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">195</context></context-group> </trans-unit> - <trans-unit id="_msg1000"> + <trans-unit id="_msg1026"> <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">196</context></context-group> </trans-unit> - <trans-unit id="_msg1001"> + <trans-unit id="_msg1027"> <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">197</context></context-group> </trans-unit> - <trans-unit id="_msg1002"> + <trans-unit id="_msg1028"> <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">198</context></context-group> </trans-unit> - <trans-unit id="_msg1003"> + <trans-unit id="_msg1029"> <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">199</context></context-group> </trans-unit> - <trans-unit id="_msg1004"> + <trans-unit id="_msg1030"> <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">200</context></context-group> </trans-unit> - <trans-unit id="_msg1005"> + <trans-unit id="_msg1031"> <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">201</context></context-group> </trans-unit> - <trans-unit id="_msg1006"> + <trans-unit id="_msg1032"> <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">202</context></context-group> </trans-unit> - <trans-unit id="_msg1007"> + <trans-unit id="_msg1033"> <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">203</context></context-group> </trans-unit> - <trans-unit id="_msg1008"> + <trans-unit id="_msg1034"> <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">204</context></context-group> </trans-unit> - <trans-unit id="_msg1009"> + <trans-unit id="_msg1035"> <source xml:space="preserve">Error reading next record from wallet database</source> - <context-group purpose="location"><context context-type="linenumber">171</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> + <context-group purpose="location"><context context-type="linenumber">205</context></context-group> </trans-unit> - <trans-unit id="_msg1011"> + <trans-unit id="_msg1036"> <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">206</context></context-group> </trans-unit> - <trans-unit id="_msg1012"> + <trans-unit id="_msg1037"> <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">207</context></context-group> </trans-unit> - <trans-unit id="_msg1013"> + <trans-unit id="_msg1038"> <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">208</context></context-group> </trans-unit> - <trans-unit id="_msg1014"> + <trans-unit id="_msg1039"> <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">209</context></context-group> </trans-unit> - <trans-unit id="_msg1015"> + <trans-unit id="_msg1040"> <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">210</context></context-group> </trans-unit> - <trans-unit id="_msg1016"> + <trans-unit id="_msg1041"> <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">211</context></context-group> </trans-unit> - <trans-unit id="_msg1017"> + <trans-unit id="_msg1042"> <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">212</context></context-group> </trans-unit> - <trans-unit id="_msg1018"> + <trans-unit id="_msg1043"> <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">213</context></context-group> </trans-unit> - <trans-unit id="_msg1019"> + <trans-unit id="_msg1044"> <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">214</context></context-group> </trans-unit> - <trans-unit id="_msg1020"> + <trans-unit id="_msg1045"> <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">215</context></context-group> </trans-unit> - <trans-unit id="_msg1021"> + <trans-unit id="_msg1046"> <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">216</context></context-group> </trans-unit> - <trans-unit id="_msg1022"> + <trans-unit id="_msg1047"> <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">217</context></context-group> </trans-unit> - <trans-unit id="_msg1023"> + <trans-unit id="_msg1048"> <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">218</context></context-group> </trans-unit> - <trans-unit id="_msg1024"> + <trans-unit id="_msg1049"> <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">219</context></context-group> </trans-unit> - <trans-unit id="_msg1025"> + <trans-unit id="_msg1050"> <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">220</context></context-group> </trans-unit> - <trans-unit id="_msg1026"> + <trans-unit id="_msg1051"> <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">221</context></context-group> </trans-unit> - <trans-unit id="_msg1027"> + <trans-unit id="_msg1052"> <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">222</context></context-group> </trans-unit> - <trans-unit id="_msg1028"> + <trans-unit id="_msg1053"> <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">223</context></context-group> </trans-unit> - <trans-unit id="_msg1029"> + <trans-unit id="_msg1054"> <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">224</context></context-group> </trans-unit> - <trans-unit id="_msg1030"> + <trans-unit id="_msg1055"> <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">225</context></context-group> </trans-unit> - <trans-unit id="_msg1031"> + <trans-unit id="_msg1056"> <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">226</context></context-group> </trans-unit> - <trans-unit id="_msg1032"> + <trans-unit id="_msg1057"> <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">227</context></context-group> </trans-unit> - <trans-unit id="_msg1033"> + <trans-unit id="_msg1058"> <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">228</context></context-group> </trans-unit> - <trans-unit id="_msg1034"> + <trans-unit id="_msg1059"> <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">229</context></context-group> </trans-unit> - <trans-unit id="_msg1035"> + <trans-unit id="_msg1060"> <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">230</context></context-group> </trans-unit> - <trans-unit id="_msg1036"> + <trans-unit id="_msg1061"> <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">231</context></context-group> </trans-unit> - <trans-unit id="_msg1037"> + <trans-unit id="_msg1062"> <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">232</context></context-group> </trans-unit> - <trans-unit id="_msg1038"> + <trans-unit id="_msg1063"> <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">233</context></context-group> </trans-unit> - <trans-unit id="_msg1039"> + <trans-unit id="_msg1064"> <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">234</context></context-group> </trans-unit> - <trans-unit id="_msg1040"> + <trans-unit id="_msg1065"> + <source xml:space="preserve">Listening for incoming connections failed (listen returned error %s)</source> + <context-group purpose="location"><context context-type="linenumber">235</context></context-group> + </trans-unit> + <trans-unit id="_msg1066"> <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">236</context></context-group> </trans-unit> - <trans-unit id="_msg1041"> + <trans-unit id="_msg1067"> <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">237</context></context-group> </trans-unit> - <trans-unit id="_msg1042"> + <trans-unit id="_msg1068"> <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">238</context></context-group> </trans-unit> - <trans-unit id="_msg1043"> + <trans-unit id="_msg1069"> <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">239</context></context-group> </trans-unit> - <trans-unit id="_msg1044"> + <trans-unit id="_msg1070"> <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">240</context></context-group> </trans-unit> - <trans-unit id="_msg1045"> + <trans-unit id="_msg1071"> <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">241</context></context-group> </trans-unit> - <trans-unit id="_msg1046"> + <trans-unit id="_msg1072"> <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">242</context></context-group> </trans-unit> - <trans-unit id="_msg1047"> + <trans-unit id="_msg1073"> <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">243</context></context-group> </trans-unit> - <trans-unit id="_msg1049"> + <trans-unit id="_msg1074"> <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">244</context></context-group> </trans-unit> - <trans-unit id="_msg1050"> + <trans-unit id="_msg1075"> <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">245</context></context-group> </trans-unit> - <trans-unit id="_msg1052"> + <trans-unit id="_msg1076"> <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">246</context></context-group> </trans-unit> - <trans-unit id="_msg1053"> + <trans-unit id="_msg1077"> <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">247</context></context-group> </trans-unit> - <trans-unit id="_msg1054"> + <trans-unit id="_msg1078"> <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">248</context></context-group> </trans-unit> - <trans-unit id="_msg1055"> + <trans-unit id="_msg1079"> <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">249</context></context-group> </trans-unit> - <trans-unit id="_msg1056"> + <trans-unit id="_msg1080"> <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">250</context></context-group> </trans-unit> - <trans-unit id="_msg1057"> + <trans-unit id="_msg1081"> <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">251</context></context-group> </trans-unit> - <trans-unit id="_msg1058"> + <trans-unit id="_msg1082"> <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">252</context></context-group> </trans-unit> - <trans-unit id="_msg1059"> + <trans-unit id="_msg1083"> <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">253</context></context-group> </trans-unit> - <trans-unit id="_msg1060"> + <trans-unit id="_msg1084"> <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">254</context></context-group> </trans-unit> - <trans-unit id="_msg1061"> + <trans-unit id="_msg1085"> <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">255</context></context-group> </trans-unit> - <trans-unit id="_msg1062"> + <trans-unit id="_msg1086"> <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">256</context></context-group> </trans-unit> - <trans-unit id="_msg1063"> + <trans-unit id="_msg1087"> <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">257</context></context-group> </trans-unit> - <trans-unit id="_msg1064"> + <trans-unit id="_msg1088"> <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">258</context></context-group> </trans-unit> - <trans-unit id="_msg1065"> + <trans-unit id="_msg1089"> <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">259</context></context-group> </trans-unit> - <trans-unit id="_msg1066"> + <trans-unit id="_msg1090"> <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">260</context></context-group> </trans-unit> - <trans-unit id="_msg1067"> + <trans-unit id="_msg1091"> <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">261</context></context-group> </trans-unit> - <trans-unit id="_msg1068"> + <trans-unit id="_msg1092"> <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">262</context></context-group> </trans-unit> - <trans-unit id="_msg1069"> + <trans-unit id="_msg1093"> <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">263</context></context-group> </trans-unit> - <trans-unit id="_msg1070"> + <trans-unit id="_msg1094"> <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">264</context></context-group> </trans-unit> - <trans-unit id="_msg1071"> + <trans-unit id="_msg1095"> <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">265</context></context-group> </trans-unit> - <trans-unit id="_msg1072"> + <trans-unit id="_msg1096"> <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">266</context></context-group> </trans-unit> - <trans-unit id="_msg1073"> + <trans-unit id="_msg1097"> <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">267</context></context-group> </trans-unit> - <trans-unit id="_msg1074"> + <trans-unit id="_msg1098"> <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">268</context></context-group> </trans-unit> - <trans-unit id="_msg1075"> + <trans-unit id="_msg1099"> <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">269</context></context-group> </trans-unit> - <trans-unit id="_msg1076"> + <trans-unit id="_msg1100"> <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">270</context></context-group> </trans-unit> - <trans-unit id="_msg1077"> + <trans-unit id="_msg1101"> <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">271</context></context-group> </trans-unit> - <trans-unit id="_msg1078"> + <trans-unit id="_msg1102"> <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">272</context></context-group> </trans-unit> - <trans-unit id="_msg1079"> + <trans-unit id="_msg1103"> <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">273</context></context-group> </trans-unit> - <trans-unit id="_msg1080"> + <trans-unit id="_msg1104"> <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">274</context></context-group> </trans-unit> - <trans-unit id="_msg1081"> + <trans-unit id="_msg1105"> <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">275</context></context-group> </trans-unit> - <trans-unit id="_msg1082"> + <trans-unit id="_msg1106"> + <source xml:space="preserve">Unable to allocate memory for -maxsigcachesize: '%s' MiB</source> + <context-group purpose="location"><context context-type="linenumber">276</context></context-group> + </trans-unit> + <trans-unit id="_msg1107"> <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">277</context></context-group> </trans-unit> - <trans-unit id="_msg1083"> + <trans-unit id="_msg1108"> <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">278</context></context-group> </trans-unit> - <trans-unit id="_msg1084"> + <trans-unit id="_msg1109"> <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">279</context></context-group> </trans-unit> - <trans-unit id="_msg1085"> + <trans-unit id="_msg1110"> <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">280</context></context-group> </trans-unit> - <trans-unit id="_msg1086"> + <trans-unit id="_msg1111"> <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">281</context></context-group> </trans-unit> - <trans-unit id="_msg1087"> + <trans-unit id="_msg1112"> <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">282</context></context-group> </trans-unit> - <trans-unit id="_msg1088"> + <trans-unit id="_msg1113"> <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">283</context></context-group> </trans-unit> - <trans-unit id="_msg1089"> + <trans-unit id="_msg1114"> <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">284</context></context-group> </trans-unit> - <trans-unit id="_msg1090"> + <trans-unit id="_msg1115"> <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">285</context></context-group> </trans-unit> - <trans-unit id="_msg1091"> + <trans-unit id="_msg1116"> <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">286</context></context-group> </trans-unit> - <trans-unit id="_msg1092"> + <trans-unit id="_msg1117"> <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">287</context></context-group> </trans-unit> - <trans-unit id="_msg1093"> + <trans-unit id="_msg1118"> <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">288</context></context-group> </trans-unit> - <trans-unit id="_msg1094"> + <trans-unit id="_msg1119"> <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">289</context></context-group> </trans-unit> - <trans-unit id="_msg1095"> + <trans-unit id="_msg1120"> <source xml:space="preserve">Unsupported logging category %s=%s.</source> - <context-group purpose="location"><context context-type="linenumber">257</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> + <context-group purpose="location"><context context-type="linenumber">290</context></context-group> </trans-unit> - <trans-unit id="_msg1097"> + <trans-unit id="_msg1121"> <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">291</context></context-group> </trans-unit> - <trans-unit id="_msg1098"> + <trans-unit id="_msg1122"> <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">292</context></context-group> </trans-unit> - <trans-unit id="_msg1099"> + <trans-unit id="_msg1123"> <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">293</context></context-group> </trans-unit> - <trans-unit id="_msg1100"> + <trans-unit id="_msg1124"> <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">294</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/notificator.cpp b/src/qt/notificator.cpp index b097ef080c..b311a9f32e 100644 --- a/src/qt/notificator.cpp +++ b/src/qt/notificator.cpp @@ -14,10 +14,11 @@ #include <QTemporaryFile> #include <QVariant> #ifdef USE_DBUS -#include <stdint.h> +#include <QDBusMetaType> #include <QtDBus> +#include <stdint.h> #endif -#ifdef Q_OS_MAC +#ifdef Q_OS_MACOS #include <qt/macnotificationhandler.h> #endif @@ -49,7 +50,7 @@ Notificator::Notificator(const QString &_programName, QSystemTrayIcon *_trayIcon mode = Freedesktop; } #endif -#ifdef Q_OS_MAC +#ifdef Q_OS_MACOS // check if users OS has support for NSUserNotification if( MacNotificationHandler::instance()->hasUserNotificationCenterSupport()) { mode = UserNotificationCenter; @@ -70,11 +71,9 @@ Notificator::~Notificator() class FreedesktopImage { public: - FreedesktopImage() {} + FreedesktopImage() = default; explicit FreedesktopImage(const QImage &img); - static int metaType(); - // Image to variant that can be marshalled over DBus static QVariant toVariant(const QImage &img); @@ -136,15 +135,10 @@ const QDBusArgument &operator>>(const QDBusArgument &a, FreedesktopImage &i) return a; } -int FreedesktopImage::metaType() -{ - return qDBusRegisterMetaType<FreedesktopImage>(); -} - QVariant FreedesktopImage::toVariant(const QImage &img) { FreedesktopImage fimg(img); - return QVariant(FreedesktopImage::metaType(), &fimg); + return QVariant(qDBusRegisterMetaType<FreedesktopImage>(), &fimg); } void Notificator::notifyDBus(Class cls, const QString &title, const QString &text, const QIcon &icon, int millisTimeout) @@ -216,7 +210,7 @@ void Notificator::notifySystray(Class cls, const QString &title, const QString & trayIcon->showMessage(title, text, sicon, millisTimeout); } -#ifdef Q_OS_MAC +#ifdef Q_OS_MACOS void Notificator::notifyMacUserNotificationCenter(const QString &title, const QString &text) { // icon is not supported by the user notification center yet. OSX will use the app icon. @@ -236,7 +230,7 @@ void Notificator::notify(Class cls, const QString &title, const QString &text, c case QSystemTray: notifySystray(cls, title, text, millisTimeout); break; -#ifdef Q_OS_MAC +#ifdef Q_OS_MACOS case UserNotificationCenter: notifyMacUserNotificationCenter(title, text); break; diff --git a/src/qt/notificator.h b/src/qt/notificator.h index 7d80b43e43..642d2b29ae 100644 --- a/src/qt/notificator.h +++ b/src/qt/notificator.h @@ -69,7 +69,7 @@ private: void notifyDBus(Class cls, const QString &title, const QString &text, const QIcon &icon, int millisTimeout); #endif void notifySystray(Class cls, const QString &title, const QString &text, int millisTimeout); -#ifdef Q_OS_MAC +#ifdef Q_OS_MACOS void notifyMacUserNotificationCenter(const QString &title, const QString &text); #endif }; diff --git a/src/qt/optionsdialog.cpp b/src/qt/optionsdialog.cpp index f90765fe5b..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); @@ -78,7 +75,7 @@ OptionsDialog::OptionsDialog(QWidget *parent, bool enableWallet) : connect(ui->connectSocksTor, &QPushButton::toggled, this, &OptionsDialog::updateProxyValidationState); /* Window elements init */ -#ifdef Q_OS_MAC +#ifdef Q_OS_MACOS /* remove Window tab on Mac */ ui->tabWidget->removeTab(ui->tabWidget->indexOf(ui->tabWindow)); /* hide launch at startup option on macOS */ @@ -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; @@ -261,7 +263,7 @@ void OptionsDialog::setMapper() mapper->addMapping(ui->proxyPortTor, OptionsModel::ProxyPortTor); /* Window */ -#ifndef Q_OS_MAC +#ifndef Q_OS_MACOS if (QSystemTrayIcon::isSystemTrayAvailable()) { mapper->addMapping(ui->showTrayIcon, OptionsModel::ShowTrayIcon); mapper->addMapping(ui->minimizeToTray, OptionsModel::MinimizeToTray); @@ -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 5d9ed5bf23..0b4359a917 100644 --- a/src/qt/optionsmodel.cpp +++ b/src/qt/optionsmodel.cpp @@ -19,20 +19,107 @@ #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> #include <QSettings> #include <QStringList> +#include <QVariant> + +#include <univalue.h> const char *DEFAULT_GUI_PROXY_HOST = "127.0.0.1"; static const QString GetDefaultProxyAddress(); -OptionsModel::OptionsModel(QObject *parent, bool resetSettings) : - QAbstractListModel(parent) +/** 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) @@ -41,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(); @@ -71,9 +165,16 @@ void OptionsModel::Init(bool resetSettings) fMinimizeOnClose = settings.value("fMinimizeOnClose").toBool(); // Display - if (!settings.contains("nDisplayUnit")) - settings.setValue("nDisplayUnit", BitcoinUnits::BTC); - nDisplayUnit = settings.value("nDisplayUnit").toInt(); + if (!settings.contains("DisplayBitcoinUnit")) { + settings.setValue("DisplayBitcoinUnit", QVariant::fromValue(BitcoinUnit::BTC)); + } + QVariant unit = settings.value("DisplayBitcoinUnit"); + if (unit.canConvert<BitcoinUnit>()) { + m_display_bitcoin_unit = unit.value<BitcoinUnit>(); + } else { + m_display_bitcoin_unit = BitcoinUnit::BTC; + settings.setValue("DisplayBitcoinUnit", QVariant::fromValue(m_display_bitcoin_unit)); + } if (!settings.contains("strThirdPartyTxUrls")) settings.setValue("strThirdPartyTxUrls", ""); @@ -90,110 +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); - if (!gArgs.SoftSetBoolArg("-listen", settings.value("fListen").toBool())) - addOverriddenOption("-listen"); - - 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. @@ -217,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 @@ -245,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 @@ -267,9 +298,14 @@ 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) +{ + return ParseProxyString(QString::fromStdString(proxy)); +} + +static std::string ProxyString(bool is_set, QString ip, QString port) { - settings.setValue(name, QString{ip_port.ip + QLatin1Char(':') + ip_port.port}); + return is_set ? QString(ip + ":" + port).toStdString() : ""; } static const QString GetDefaultProxyAddress() @@ -277,303 +313,319 @@ static const QString GetDefaultProxyAddress() return QString("%1:%2").arg(DEFAULT_GUI_PROXY_HOST).arg(DEFAULT_GUI_PROXY_PORT); } -void OptionsModel::SetPruneEnabled(bool prune, bool force) +void OptionsModel::SetPruneTargetGB(int prune_target_gb) { - 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"); + 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); } } -void OptionsModel::SetPruneTargetGB(int prune_target_gb, bool force) +// read QSettings values and return them +QVariant OptionsModel::data(const QModelIndex & index, int role) const { - const bool prune = prune_target_gb > 0; - if (prune) { - QSettings settings; - settings.setValue("nPruneSize", prune_target_gb); + if(role == Qt::EditRole) + { + return getOption(OptionID(index.row())); } - SetPruneEnabled(prune, force); + return QVariant(); } -// read QSettings values and return them -QVariant OptionsModel::data(const QModelIndex & index, int role) const +// write QSettings values +bool OptionsModel::setData(const QModelIndex & index, const QVariant & value, int role) { + bool successful = true; /* set to false on parse error */ if(role == Qt::EditRole) { - QSettings settings; - switch(index.row()) - { - case StartAtStartup: - return GUIUtil::GetStartOnSystemStartup(); - case ShowTrayIcon: - return m_show_tray_icon; - case MinimizeToTray: - return fMinimizeToTray; - case MapPortUPnP: + successful = setOption(OptionID(index.row()), value); + } + + Q_EMIT dataChanged(index, index); + + return successful; +} + +QVariant OptionsModel::getOption(OptionID option) const +{ + auto setting = [&]{ return node().getPersistentSetting(SettingName(option)); }; + + QSettings settings; + switch (option) { + case StartAtStartup: + return GUIUtil::GetStartOnSystemStartup(); + case ShowTrayIcon: + return m_show_tray_icon; + case MinimizeToTray: + return fMinimizeToTray; + case MapPortUPnP: #ifdef USE_UPNP - return settings.value("fUseUPnP"); + return SettingToBool(setting(), DEFAULT_UPNP); #else - return false; + return false; #endif // USE_UPNP - case MapPortNatpmp: + case MapPortNatpmp: #ifdef USE_NATPMP - return settings.value("fUseNatpmp"); + return SettingToBool(setting(), DEFAULT_NATPMP); #else - return false; + return false; #endif // USE_NATPMP - case MinimizeOnClose: - return fMinimizeOnClose; - - // default proxy - case ProxyUse: - return settings.value("fUseProxy", false); - case ProxyIP: - return GetProxySetting(settings, "addrProxy").ip; - case ProxyPort: - return GetProxySetting(settings, "addrProxy").port; - - // separate Tor proxy - case ProxyUseTor: - return settings.value("fUseSeparateProxyTor", false); - case ProxyIPTor: - return GetProxySetting(settings, "addrSeparateProxyTor").ip; - case ProxyPortTor: - return GetProxySetting(settings, "addrSeparateProxyTor").port; + case MinimizeOnClose: + return fMinimizeOnClose; + + // default proxy + case ProxyUse: + return ParseProxyString(SettingToString(setting(), "")).is_set; + case ProxyIP: + return m_proxy_ip; + case ProxyPort: + return m_proxy_port; + + // separate Tor proxy + case ProxyUseTor: + return ParseProxyString(SettingToString(setting(), "")).is_set; + case ProxyIPTor: + return m_onion_ip; + case ProxyPortTor: + return m_onion_port; #ifdef ENABLE_WALLET - case SpendZeroConfChange: - return settings.value("bSpendZeroConfChange"); - case ExternalSignerPath: - return settings.value("external_signer_path"); - case SubFeeFromAmount: - return m_sub_fee_from_amount; + case SpendZeroConfChange: + return SettingToBool(setting(), wallet::DEFAULT_SPEND_ZEROCONF_CHANGE); + case ExternalSignerPath: + return QString::fromStdString(SettingToString(setting(), "")); + case SubFeeFromAmount: + return m_sub_fee_from_amount; #endif - case DisplayUnit: - return nDisplayUnit; - case ThirdPartyTxUrls: - return strThirdPartyTxUrls; - case Language: - return settings.value("language"); - case UseEmbeddedMonospacedFont: - return m_use_embedded_monospaced_font; - case CoinControlFeatures: - return fCoinControlFeatures; - case EnablePSBTControls: - return settings.value("enable_psbt_controls"); - case Prune: - return settings.value("bPrune"); - case PruneSize: - return settings.value("nPruneSize"); - case DatabaseCache: - return settings.value("nDatabaseCache"); - case ThreadsScriptVerif: - return settings.value("nThreadsScriptVerif"); - case Listen: - return settings.value("fListen"); - case Server: - return settings.value("server"); - default: - return QVariant(); - } + case DisplayUnit: + return QVariant::fromValue(m_display_bitcoin_unit); + case ThirdPartyTxUrls: + return strThirdPartyTxUrls; + case Language: + return QString::fromStdString(SettingToString(setting(), "")); + case UseEmbeddedMonospacedFont: + return m_use_embedded_monospaced_font; + case CoinControlFeatures: + return fCoinControlFeatures; + case EnablePSBTControls: + return settings.value("enable_psbt_controls"); + case Prune: + return PruneEnabled(setting()); + case PruneSize: + return m_prune_size_gb; + case DatabaseCache: + return qlonglong(SettingToInt(setting(), nDefaultDbCache)); + case ThreadsScriptVerif: + return qlonglong(SettingToInt(setting(), DEFAULT_SCRIPTCHECK_THREADS)); + case Listen: + return SettingToBool(setting(), DEFAULT_LISTEN); + case Server: + return SettingToBool(setting(), false); + default: + return QVariant(); } - return QVariant(); } -// write QSettings values -bool OptionsModel::setData(const QModelIndex & index, const QVariant & value, int role) +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 */ - if(role == Qt::EditRole) - { - QSettings settings; - switch(index.row()) - { - case StartAtStartup: - successful = GUIUtil::SetStartOnSystemStartup(value.toBool()); - break; - case ShowTrayIcon: - m_show_tray_icon = value.toBool(); - settings.setValue("fHideTrayIcon", !m_show_tray_icon); - Q_EMIT showTrayIconChanged(m_show_tray_icon); - break; - case MinimizeToTray: - fMinimizeToTray = value.toBool(); - settings.setValue("fMinimizeToTray", fMinimizeToTray); - break; - case MapPortUPnP: // core option - can be changed on-the-fly - settings.setValue("fUseUPnP", value.toBool()); - break; - case MapPortNatpmp: // core option - can be changed on-the-fly - settings.setValue("fUseNatpmp", value.toBool()); - break; - case MinimizeOnClose: - fMinimizeOnClose = value.toBool(); - settings.setValue("fMinimizeOnClose", fMinimizeOnClose); - break; - - // default proxy - case ProxyUse: - if (settings.value("fUseProxy") != value) { - settings.setValue("fUseProxy", value.toBool()); - 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); + QSettings settings; + + switch (option) { + case StartAtStartup: + successful = GUIUtil::SetStartOnSystemStartup(value.toBool()); + break; + case ShowTrayIcon: + m_show_tray_icon = value.toBool(); + settings.setValue("fHideTrayIcon", !m_show_tray_icon); + Q_EMIT showTrayIconChanged(m_show_tray_icon); + break; + case MinimizeToTray: + fMinimizeToTray = value.toBool(); + settings.setValue("fMinimizeToTray", fMinimizeToTray); + break; + case MapPortUPnP: // core option - can be changed on-the-fly + if (changed()) { + update(value.toBool()); + node().mapPort(value.toBool(), getOption(MapPortNatpmp).toBool()); + } + break; + case MapPortNatpmp: // core option - can be changed on-the-fly + if (changed()) { + update(value.toBool()); + node().mapPort(getOption(MapPortUPnP).toBool(), value.toBool()); + } + break; + case MinimizeOnClose: + fMinimizeOnClose = value.toBool(); + settings.setValue("fMinimizeOnClose", fMinimizeOnClose); + break; + + // default proxy + case ProxyUse: + if (changed()) { + update(ProxyString(value.toBool(), m_proxy_ip, m_proxy_port)); + setRestartRequired(true); + } + break; + 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); + 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; - // separate Tor proxy - case ProxyUseTor: - if (settings.value("fUseSeparateProxyTor") != value) { - settings.setValue("fUseSeparateProxyTor", value.toBool()); - 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); + // separate Tor proxy + case ProxyUseTor: + if (changed()) { + update(ProxyString(value.toBool(), m_onion_ip, m_onion_port)); + setRestartRequired(true); + } + break; + 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); + 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; #ifdef ENABLE_WALLET - case SpendZeroConfChange: - if (settings.value("bSpendZeroConfChange") != value) { - settings.setValue("bSpendZeroConfChange", value); - setRestartRequired(true); - } - break; - case ExternalSignerPath: - if (settings.value("external_signer_path") != value.toString()) { - settings.setValue("external_signer_path", value.toString()); - setRestartRequired(true); - } - break; - case SubFeeFromAmount: - m_sub_fee_from_amount = value.toBool(); - settings.setValue("SubFeeFromAmount", m_sub_fee_from_amount); - break; + case SpendZeroConfChange: + if (changed()) { + update(value.toBool()); + setRestartRequired(true); + } + break; + case ExternalSignerPath: + if (changed()) { + update(value.toString().toStdString()); + setRestartRequired(true); + } + break; + case SubFeeFromAmount: + m_sub_fee_from_amount = value.toBool(); + settings.setValue("SubFeeFromAmount", m_sub_fee_from_amount); + break; #endif - case DisplayUnit: - setDisplayUnit(value); - break; - case ThirdPartyTxUrls: - if (strThirdPartyTxUrls != value.toString()) { - strThirdPartyTxUrls = value.toString(); - settings.setValue("strThirdPartyTxUrls", strThirdPartyTxUrls); - setRestartRequired(true); - } - break; - case Language: - if (settings.value("language") != value) { - settings.setValue("language", value); - setRestartRequired(true); - } - break; - case UseEmbeddedMonospacedFont: - m_use_embedded_monospaced_font = value.toBool(); - settings.setValue("UseEmbeddedMonospacedFont", m_use_embedded_monospaced_font); - Q_EMIT useEmbeddedMonospacedFontChanged(m_use_embedded_monospaced_font); - break; - case CoinControlFeatures: - fCoinControlFeatures = value.toBool(); - settings.setValue("fCoinControlFeatures", fCoinControlFeatures); - Q_EMIT coinControlFeaturesChanged(fCoinControlFeatures); - break; - case EnablePSBTControls: - m_enable_psbt_controls = value.toBool(); - settings.setValue("enable_psbt_controls", m_enable_psbt_controls); - break; - case Prune: - if (settings.value("bPrune") != value) { - settings.setValue("bPrune", value); - setRestartRequired(true); - } - break; - case PruneSize: - if (settings.value("nPruneSize") != value) { - settings.setValue("nPruneSize", value); - setRestartRequired(true); - } - break; - case DatabaseCache: - if (settings.value("nDatabaseCache") != value) { - settings.setValue("nDatabaseCache", value); - setRestartRequired(true); - } - break; - case ThreadsScriptVerif: - if (settings.value("nThreadsScriptVerif") != value) { - settings.setValue("nThreadsScriptVerif", value); - 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); + case DisplayUnit: + setDisplayUnit(value); + break; + case ThirdPartyTxUrls: + if (strThirdPartyTxUrls != value.toString()) { + strThirdPartyTxUrls = value.toString(); + settings.setValue("strThirdPartyTxUrls", strThirdPartyTxUrls); + setRestartRequired(true); + } + break; + case Language: + if (changed()) { + update(value.toString().toStdString()); + setRestartRequired(true); + } + break; + case UseEmbeddedMonospacedFont: + m_use_embedded_monospaced_font = value.toBool(); + settings.setValue("UseEmbeddedMonospacedFont", m_use_embedded_monospaced_font); + Q_EMIT useEmbeddedMonospacedFontChanged(m_use_embedded_monospaced_font); + break; + case CoinControlFeatures: + fCoinControlFeatures = value.toBool(); + settings.setValue("fCoinControlFeatures", fCoinControlFeatures); + Q_EMIT coinControlFeaturesChanged(fCoinControlFeatures); + break; + case EnablePSBTControls: + m_enable_psbt_controls = value.toBool(); + settings.setValue("enable_psbt_controls", m_enable_psbt_controls); + break; + case Prune: + if (changed()) { + update(PruneSetting(value.toBool(), m_prune_size_gb)); + setRestartRequired(true); + } + break; + case PruneSize: + if (changed()) { + m_prune_size_gb = ParsePruneSizeGB(value); + if (getOption(Prune).toBool()) { + update(PruneSetting(true, m_prune_size_gb)); setRestartRequired(true); } - break; - default: - break; } + break; + case DatabaseCache: + if (changed()) { + update(static_cast<int64_t>(value.toLongLong())); + setRestartRequired(true); + } + break; + case ThreadsScriptVerif: + if (changed()) { + update(static_cast<int64_t>(value.toLongLong())); + setRestartRequired(true); + } + break; + case Listen: + case Server: + if (changed()) { + update(value.toBool()); + setRestartRequired(true); + } + break; + default: + break; } - Q_EMIT dataChanged(index, index); - return successful; } -/** Updates current unit in memory, settings and emits displayUnitChanged(newUnit) signal */ -void OptionsModel::setDisplayUnit(const QVariant &value) +void OptionsModel::setDisplayUnit(const QVariant& new_unit) { - if (!value.isNull()) - { - QSettings settings; - nDisplayUnit = value.toInt(); - settings.setValue("nDisplayUnit", nDisplayUnit); - Q_EMIT displayUnitChanged(nDisplayUnit); - } + if (new_unit.isNull() || new_unit.value<BitcoinUnit>() == m_display_bitcoin_unit) return; + m_display_bitcoin_unit = new_unit.value<BitcoinUnit>(); + QSettings settings; + settings.setValue("DisplayBitcoinUnit", QVariant::fromValue(m_display_bitcoin_unit)); + Q_EMIT displayUnitChanged(m_display_bitcoin_unit); } void OptionsModel::setRestartRequired(bool fRequired) @@ -617,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 bb9a8c1f8c..42b89c5029 100644 --- a/src/qt/optionsmodel.h +++ b/src/qt/optionsmodel.h @@ -6,12 +6,14 @@ #define BITCOIN_QT_OPTIONSMODEL_H #include <cstdint> +#include <qt/bitcoinunits.h> #include <qt/guiconstants.h> #include <QAbstractListModel> #include <assert.h> +struct bilingual_str; namespace interfaces { class Node; } @@ -40,7 +42,7 @@ class OptionsModel : public QAbstractListModel Q_OBJECT public: - explicit OptionsModel(QObject *parent = nullptr, bool resetSettings = false); + explicit OptionsModel(interfaces::Node& node, QObject *parent = nullptr); enum OptionID { StartAtStartup, // bool @@ -55,7 +57,7 @@ public: ProxyUseTor, // bool ProxyIPTor, // QString ProxyPortTor, // int - DisplayUnit, // BitcoinUnits::Unit + DisplayUnit, // BitcoinUnit ThirdPartyTxUrls, // QString Language, // QString UseEmbeddedMonospacedFont, // bool @@ -73,20 +75,22 @@ public: OptionIDRowCount, }; - void Init(bool resetSettings = false); + bool Init(bilingual_str& error); void Reset(); int rowCount(const QModelIndex & parent = QModelIndex()) const override; QVariant data(const QModelIndex & index, int role = Qt::DisplayRole) const override; bool setData(const QModelIndex & index, const QVariant & value, int role = Qt::EditRole) override; - /** Updates current unit in memory, settings and emits displayUnitChanged(newUnit) signal */ - void setDisplayUnit(const QVariant &value); + QVariant getOption(OptionID option) const; + bool setOption(OptionID option, const QVariant& value); + /** Updates current unit in memory, settings and emits displayUnitChanged(new_unit) signal */ + void setDisplayUnit(const QVariant& new_unit); /* Explicit getters */ bool getShowTrayIcon() const { return m_show_tray_icon; } bool getMinimizeToTray() const { return fMinimizeToTray; } bool getMinimizeOnClose() const { return fMinimizeOnClose; } - int getDisplayUnit() const { return nDisplayUnit; } + BitcoinUnit getDisplayUnit() const { return m_display_bitcoin_unit; } QString getThirdPartyTxUrls() const { return strThirdPartyTxUrls; } bool getUseEmbeddedMonospacedFont() const { return m_use_embedded_monospaced_font; } bool getCoinControlFeatures() const { return fCoinControlFeatures; } @@ -95,29 +99,37 @@ 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); bool isRestartRequired() const; - interfaces::Node& node() const { assert(m_node); return *m_node; } - void setNode(interfaces::Node& node) { assert(!m_node); m_node = &node; } + interfaces::Node& node() const { return m_node; } private: - interfaces::Node* m_node = nullptr; + interfaces::Node& m_node; /* Qt-only settings */ bool m_show_tray_icon; bool fMinimizeToTray; bool fMinimizeOnClose; QString language; - int nDisplayUnit; + BitcoinUnit m_display_bitcoin_unit; QString strThirdPartyTxUrls; bool m_use_embedded_monospaced_font; 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; @@ -126,8 +138,9 @@ private: // Check settings version and upgrade default values if required void checkAndMigrate(); + Q_SIGNALS: - void displayUnitChanged(int unit); + void displayUnitChanged(BitcoinUnit unit); void coinControlFeaturesChanged(bool); void showTrayIconChanged(bool); void useEmbeddedMonospacedFontChanged(bool); diff --git a/src/qt/overviewpage.cpp b/src/qt/overviewpage.cpp index 7127706463..85a3c36f39 100644 --- a/src/qt/overviewpage.cpp +++ b/src/qt/overviewpage.cpp @@ -34,9 +34,8 @@ class TxViewDelegate : public QAbstractItemDelegate { Q_OBJECT public: - explicit TxViewDelegate(const PlatformStyle *_platformStyle, QObject *parent=nullptr): - QAbstractItemDelegate(parent), unit(BitcoinUnits::BTC), - platformStyle(_platformStyle) + explicit TxViewDelegate(const PlatformStyle* _platformStyle, QObject* parent = nullptr) + : QAbstractItemDelegate(parent), platformStyle(_platformStyle) { connect(this, &TxViewDelegate::width_changed, this, &TxViewDelegate::sizeHintChanged); } @@ -125,7 +124,7 @@ public: return {DECORATION_SIZE + 8 + minimum_text_width, DECORATION_SIZE}; } - int unit; + BitcoinUnit unit{BitcoinUnit::BTC}; Q_SIGNALS: //! An intermediate signal for emitting from the `paint() const` member function. @@ -148,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); @@ -178,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,8 +195,7 @@ OverviewPage::~OverviewPage() void OverviewPage::setBalance(const interfaces::WalletBalances& balances) { - int unit = walletModel->getOptionsModel()->getDisplayUnit(); - m_balances = balances; + BitcoinUnit unit = walletModel->getOptionsModel()->getDisplayUnit(); if (walletModel->wallet().isLegacy()) { if (walletModel->wallet().privateKeysDisabled()) { ui->labelBalance->setText(BitcoinUnits::formatWithPrivacy(unit, balances.watch_only_balance, BitcoinUnits::SeparatorStyle::ALWAYS, m_privacy)); @@ -277,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()); }); @@ -307,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 cb09035b45..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> @@ -78,32 +78,11 @@ void PaymentServer::ipcParseCommandLine(int argc, char* argv[]) for (int i = 1; i < argc; i++) { QString arg(argv[i]); - if (arg.startsWith("-")) - continue; + if (arg.startsWith("-")) continue; - // If the bitcoin: URI contains a payment request, we are not able to detect the - // network as that would require fetching and parsing the payment request. - // That means clicking such an URI which contains a testnet payment request - // will start a mainnet instance and throw a "wrong network" error. if (arg.startsWith(BITCOIN_IPC_PREFIX, Qt::CaseInsensitive)) // bitcoin: URI { - if (savedPaymentRequests.contains(arg)) continue; savedPaymentRequests.insert(arg); - - SendCoinsRecipient r; - if (GUIUtil::parseBitcoinURI(arg, &r) && !r.address.isEmpty()) - { - auto tempChainParams = CreateChainParams(gArgs, CBaseChainParams::MAIN); - - if (IsValidDestinationString(r.address.toStdString(), *tempChainParams)) { - SelectParams(CBaseChainParams::MAIN); - } else { - tempChainParams = CreateChainParams(gArgs, CBaseChainParams::TESTNET); - if (IsValidDestinationString(r.address.toStdString(), *tempChainParams)) { - SelectParams(CBaseChainParams::TESTNET); - } - } - } } } } @@ -179,9 +158,7 @@ PaymentServer::PaymentServer(QObject* parent, bool startLocalServer) : } } -PaymentServer::~PaymentServer() -{ -} +PaymentServer::~PaymentServer() = default; // // OSX-specific way of handling bitcoin: URIs diff --git a/src/qt/peertablemodel.cpp b/src/qt/peertablemodel.cpp index cd3193a1d2..b7de88225e 100644 --- a/src/qt/peertablemodel.cpp +++ b/src/qt/peertablemodel.cpp @@ -28,10 +28,7 @@ PeerTableModel::PeerTableModel(interfaces::Node& node, QObject* parent) : refresh(); } -PeerTableModel::~PeerTableModel() -{ - // Intentionally left empty -} +PeerTableModel::~PeerTableModel() = default; void PeerTableModel::startAutoRefresh() { @@ -71,6 +68,8 @@ QVariant PeerTableModel::data(const QModelIndex& index, int role) const switch (column) { case NetNodeId: return (qint64)rec->nodeStats.nodeid; + case Age: + return GUIUtil::FormatPeerAge(rec->nodeStats.m_connected); case Address: return QString::fromStdString(rec->nodeStats.m_addr_name); case Direction: @@ -80,7 +79,7 @@ QVariant PeerTableModel::data(const QModelIndex& index, int role) const //: An Outbound Connection to a Peer. tr("Outbound")); case ConnectionType: - return GUIUtil::ConnectionTypeToQString(rec->nodeStats.m_conn_type, /* prepend_direction */ false); + return GUIUtil::ConnectionTypeToQString(rec->nodeStats.m_conn_type, /*prepend_direction=*/false); case Network: return GUIUtil::NetworkToQString(rec->nodeStats.m_network); case Ping: @@ -96,6 +95,7 @@ QVariant PeerTableModel::data(const QModelIndex& index, int role) const } else if (role == Qt::TextAlignmentRole) { switch (column) { case NetNodeId: + case Age: return QVariant(Qt::AlignRight | Qt::AlignVCenter); case Address: return {}; diff --git a/src/qt/peertablemodel.h b/src/qt/peertablemodel.h index 11064cdbfe..e2515de775 100644 --- a/src/qt/peertablemodel.h +++ b/src/qt/peertablemodel.h @@ -47,6 +47,7 @@ public: enum ColumnIndex { NetNodeId = 0, + Age, Address, Direction, ConnectionType, @@ -82,6 +83,9 @@ private: /*: Title of Peers Table column which contains a unique number used to identify a connection. */ tr("Peer"), + /*: Title of Peers Table column which indicates the duration (length of time) + since the peer connection started. */ + tr("Age"), /*: Title of Peers Table column which contains the IP/Onion/I2P address of the connected peer. */ tr("Address"), diff --git a/src/qt/peertablesortproxy.cpp b/src/qt/peertablesortproxy.cpp index 26fedb4127..d87f10c365 100644 --- a/src/qt/peertablesortproxy.cpp +++ b/src/qt/peertablesortproxy.cpp @@ -24,6 +24,8 @@ bool PeerTableSortProxy::lessThan(const QModelIndex& left_index, const QModelInd switch (static_cast<PeerTableModel::ColumnIndex>(left_index.column())) { case PeerTableModel::NetNodeId: return left_stats.nodeid < right_stats.nodeid; + case PeerTableModel::Age: + return left_stats.m_connected > right_stats.m_connected; case PeerTableModel::Address: return left_stats.m_addr_name.compare(right_stats.m_addr_name) < 0; case PeerTableModel::Direction: diff --git a/src/qt/psbtoperationsdialog.cpp b/src/qt/psbtoperationsdialog.cpp index 6880c157c0..333766ce21 100644 --- a/src/qt/psbtoperationsdialog.cpp +++ b/src/qt/psbtoperationsdialog.cpp @@ -181,7 +181,7 @@ std::string PSBTOperationsDialog::renderTransaction(const PartiallySignedTransac ExtractDestination(out.scriptPubKey, address); totalAmount += out.nValue; tx_description.append(tr(" * Sends %1 to %2") - .arg(BitcoinUnits::formatWithUnit(BitcoinUnits::BTC, out.nValue)) + .arg(BitcoinUnits::formatWithUnit(BitcoinUnit::BTC, out.nValue)) .arg(QString::fromStdString(EncodeDestination(address)))); tx_description.append("<br>"); } @@ -193,7 +193,7 @@ std::string PSBTOperationsDialog::renderTransaction(const PartiallySignedTransac tx_description.append(tr("Unable to calculate transaction fee or total transaction amount.")); } else { tx_description.append(tr("Pays transaction fee: ")); - tx_description.append(BitcoinUnits::formatWithUnit(BitcoinUnits::BTC, *analysis.fee)); + tx_description.append(BitcoinUnits::formatWithUnit(BitcoinUnit::BTC, *analysis.fee)); // add total amount in all subdivision units tx_description.append("<hr />"); diff --git a/src/qt/recentrequeststablemodel.cpp b/src/qt/recentrequeststablemodel.cpp index 03ca9ad7dc..061513b58f 100644 --- a/src/qt/recentrequeststablemodel.cpp +++ b/src/qt/recentrequeststablemodel.cpp @@ -34,10 +34,7 @@ RecentRequestsTableModel::RecentRequestsTableModel(WalletModel *parent) : connect(walletModel->getOptionsModel(), &OptionsModel::displayUnitChanged, this, &RecentRequestsTableModel::updateDisplayUnit); } -RecentRequestsTableModel::~RecentRequestsTableModel() -{ - /* Intentionally left empty */ -} +RecentRequestsTableModel::~RecentRequestsTableModel() = default; int RecentRequestsTableModel::rowCount(const QModelIndex &parent) const { diff --git a/src/qt/rpcconsole.cpp b/src/qt/rpcconsole.cpp index c5e5e69df6..70fccdef1c 100644 --- a/src/qt/rpcconsole.cpp +++ b/src/qt/rpcconsole.cpp @@ -27,19 +27,12 @@ #include <univalue.h> -#ifdef ENABLE_WALLET -#ifdef USE_BDB -#include <wallet/bdb.h> -#endif -#include <wallet/db.h> -#include <wallet/wallet.h> -#endif - #include <QAbstractButton> #include <QAbstractItemModel> #include <QDateTime> #include <QFont> #include <QKeyEvent> +#include <QKeySequence> #include <QLatin1String> #include <QLocale> #include <QMenu> @@ -119,7 +112,7 @@ public: connect(&timer, &QTimer::timeout, [this]{ func(); }); timer.start(millis); } - ~QtRPCTimerBase() {} + ~QtRPCTimerBase() = default; private: QTimer timer; std::function<void()> func; @@ -128,7 +121,7 @@ private: class QtRPCTimerInterface: public RPCTimerInterface { public: - ~QtRPCTimerInterface() {} + ~QtRPCTimerInterface() = default; const char *Name() override { return "Qt"; } RPCTimerBase* NewTimer(std::function<void()>& func, int64_t millis) override { @@ -456,7 +449,7 @@ void RPCExecutor::request(const QString &command, const WalletModel* wallet_mode { try // Nice formatting for standard-format error { - int code = find_value(objError, "code").get_int(); + int code = find_value(objError, "code").getInt<int>(); std::string message = find_value(objError, "message").get_str(); Q_EMIT reply(RPCConsole::CMD_ERROR, QString::fromStdString(message) + " (code " + QString::number(code) + ")"); } @@ -617,17 +610,16 @@ bool RPCConsole::eventFilter(QObject* obj, QEvent *event) case Qt::Key_Down: if(obj == ui->lineEdit) { browseHistory(1); return true; } break; case Qt::Key_PageUp: /* pass paging keys to messages widget */ case Qt::Key_PageDown: - if(obj == ui->lineEdit) - { - QApplication::postEvent(ui->messagesWidget, new QKeyEvent(*keyevt)); + if (obj == ui->lineEdit) { + QApplication::sendEvent(ui->messagesWidget, keyevt); return true; } break; case Qt::Key_Return: case Qt::Key_Enter: // forward these events to lineEdit - if(obj == autoCompleter->popup()) { - QApplication::postEvent(ui->lineEdit, new QKeyEvent(*keyevt)); + if (obj == autoCompleter->popup()) { + QApplication::sendEvent(ui->lineEdit, keyevt); autoCompleter->popup()->hide(); return true; } @@ -641,7 +633,7 @@ bool RPCConsole::eventFilter(QObject* obj, QEvent *event) ((mod & Qt::ShiftModifier) && key == Qt::Key_Insert))) { ui->lineEdit->setFocus(); - QApplication::postEvent(ui->lineEdit, new QKeyEvent(*keyevt)); + QApplication::sendEvent(ui->lineEdit, keyevt); return true; } } @@ -693,6 +685,7 @@ void RPCConsole::setClientModel(ClientModel *model, int bestblock_height, int64_ ui->peerWidget->setColumnWidth(PeerTableModel::Subversion, SUBVERSION_COLUMN_WIDTH); ui->peerWidget->setColumnWidth(PeerTableModel::Ping, PING_COLUMN_WIDTH); } + ui->peerWidget->horizontalHeader()->setSectionResizeMode(PeerTableModel::Age, QHeaderView::ResizeToContents); ui->peerWidget->horizontalHeader()->setStretchLastSection(true); ui->peerWidget->setItemDelegateForColumn(PeerTableModel::NetNodeId, new PeerIdViewDelegate(this)); @@ -725,6 +718,7 @@ void RPCConsole::setClientModel(ClientModel *model, int bestblock_height, int64_ ui->banlistWidget->setColumnWidth(BanTableModel::Address, BANSUBNET_COLUMN_WIDTH); ui->banlistWidget->setColumnWidth(BanTableModel::Bantime, BANTIME_COLUMN_WIDTH); } + ui->banlistWidget->horizontalHeader()->setSectionResizeMode(BanTableModel::Address, QHeaderView::ResizeToContents); ui->banlistWidget->horizontalHeader()->setStretchLastSection(true); // create ban table context menu @@ -847,7 +841,7 @@ void RPCConsole::setFontSize(int newSize) // clear console (reset icon sizes, default stylesheet) and re-add the content float oldPosFactor = 1.0 / ui->messagesWidget->verticalScrollBar()->maximum() * ui->messagesWidget->verticalScrollBar()->value(); - clear(/* keep_prompt */ true); + clear(/*keep_prompt=*/true); ui->messagesWidget->setHtml(str); ui->messagesWidget->verticalScrollBar()->setValue(oldPosFactor * ui->messagesWidget->verticalScrollBar()->maximum()); } @@ -869,7 +863,7 @@ void RPCConsole::clear(bool keep_prompt) } // Set default style sheet -#ifdef Q_OS_MAC +#ifdef Q_OS_MACOS QFontInfo fixedFontInfo(GUIUtil::fixedPitchFont(/*use_embedded_font=*/true)); #else QFontInfo fixedFontInfo(GUIUtil::fixedPitchFont()); @@ -1030,8 +1024,9 @@ void RPCConsole::on_lineEdit_returnPressed() ui->lineEdit->clear(); + WalletModel* wallet_model{nullptr}; #ifdef ENABLE_WALLET - WalletModel* wallet_model = ui->WalletSelector->currentData().value<WalletModel*>(); + wallet_model = ui->WalletSelector->currentData().value<WalletModel*>(); if (m_last_wallet_model != wallet_model) { if (wallet_model) { @@ -1047,7 +1042,10 @@ void RPCConsole::on_lineEdit_returnPressed() //: A console message indicating an entered command is currently being executed. message(CMD_REPLY, tr("Executing…")); m_is_executing = true; - Q_EMIT cmdRequest(cmd, m_last_wallet_model); + + QMetaObject::invokeMethod(m_executor, [this, cmd, wallet_model] { + m_executor->request(cmd, wallet_model); + }); cmd = QString::fromStdString(strFilteredCmd); @@ -1089,11 +1087,11 @@ void RPCConsole::browseHistory(int offset) void RPCConsole::startExecutor() { - RPCExecutor *executor = new RPCExecutor(m_node); - executor->moveToThread(&thread); + m_executor = new RPCExecutor(m_node); + m_executor->moveToThread(&thread); // Replies from executor object must go to this object - connect(executor, &RPCExecutor::reply, this, [this](int category, const QString& command) { + connect(m_executor, &RPCExecutor::reply, this, [this](int category, const QString& command) { // Remove "Executing…" message. ui->messagesWidget->undo(); message(category, command); @@ -1101,16 +1099,13 @@ void RPCConsole::startExecutor() m_is_executing = false; }); - // Requests from this object must go to executor - connect(this, &RPCConsole::cmdRequest, executor, &RPCExecutor::request); - // Make sure executor object is deleted in its own thread - connect(&thread, &QThread::finished, executor, &RPCExecutor::deleteLater); + connect(&thread, &QThread::finished, m_executor, &RPCExecutor::deleteLater); // Default implementation of QThread::run() simply spins up an event loop in the thread, // which is what we want. thread.start(); - QTimer::singleShot(0, executor, []() { + QTimer::singleShot(0, m_executor, []() { util::ThreadRename("qt-rpcconsole"); }); } @@ -1167,8 +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)); - ui->peerRelayTxes->setText(stats->nodeStats.fRelayTxes ? ts.yes : ts.no); 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); @@ -1187,7 +1180,7 @@ void RPCConsole::updateDetailWidget() ui->timeoffset->setText(GUIUtil::formatTimeOffset(stats->nodeStats.nTimeOffset)); ui->peerVersion->setText(QString::number(stats->nodeStats.nVersion)); ui->peerSubversion->setText(QString::fromStdString(stats->nodeStats.cleanSubVer)); - ui->peerConnectionType->setText(GUIUtil::ConnectionTypeToQString(stats->nodeStats.m_conn_type, /* prepend_direction */ true)); + ui->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) { ui->peerPermissions->setText(ts.na); @@ -1203,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)); @@ -1220,6 +1214,7 @@ void RPCConsole::updateDetailWidget() ui->peerAddrRelayEnabled->setText(stats->nodeStateStats.m_addr_relay_enabled ? ts.yes : ts.no); ui->peerAddrProcessed->setText(QString::number(stats->nodeStateStats.m_addr_processed)); ui->peerAddrRateLimited->setText(QString::number(stats->nodeStateStats.m_addr_rate_limited)); + ui->peerRelayTxes->setText(stats->nodeStateStats.m_relay_txs ? ts.yes : ts.no); } ui->peersTabRightPanel->show(); @@ -1353,10 +1348,10 @@ QString RPCConsole::tabTitle(TabTypes tab_type) const QKeySequence RPCConsole::tabShortcut(TabTypes tab_type) const { switch (tab_type) { - case TabTypes::INFO: return QKeySequence(Qt::CTRL + Qt::Key_I); - case TabTypes::CONSOLE: return QKeySequence(Qt::CTRL + Qt::Key_T); - case TabTypes::GRAPH: return QKeySequence(Qt::CTRL + Qt::Key_N); - case TabTypes::PEERS: return QKeySequence(Qt::CTRL + Qt::Key_P); + case TabTypes::INFO: return QKeySequence(tr("Ctrl+I")); + case TabTypes::CONSOLE: return QKeySequence(tr("Ctrl+T")); + case TabTypes::GRAPH: return QKeySequence(tr("Ctrl+N")); + case TabTypes::PEERS: return QKeySequence(tr("Ctrl+P")); } // no default case, so the compiler can warn about missing cases assert(false); diff --git a/src/qt/rpcconsole.h b/src/qt/rpcconsole.h index 528e2bef7d..1a54fe0cad 100644 --- a/src/qt/rpcconsole.h +++ b/src/qt/rpcconsole.h @@ -5,6 +5,10 @@ #ifndef BITCOIN_QT_RPCCONSOLE_H #define BITCOIN_QT_RPCCONSOLE_H +#if defined(HAVE_CONFIG_H) +#include <config/bitcoin-config.h> +#endif + #include <qt/guiutil.h> #include <qt/peertablemodel.h> @@ -17,6 +21,7 @@ class ClientModel; class PlatformStyle; +class RPCExecutor; class RPCTimerInterface; class WalletModel; @@ -49,8 +54,11 @@ public: } void setClientModel(ClientModel *model = nullptr, int bestblock_height = 0, int64_t bestblock_date = 0, double verification_progress = 0.0); - void addWallet(WalletModel * const walletModel); + +#ifdef ENABLE_WALLET + void addWallet(WalletModel* const walletModel); void removeWallet(WalletModel* const walletModel); +#endif // ENABLE_WALLET enum MessageClass { MC_ERROR, @@ -129,10 +137,6 @@ public Q_SLOTS: /** set which tab has the focus (is visible) */ void setTabFocus(enum TabTypes tabType); -Q_SIGNALS: - // For RPC command executor - void cmdRequest(const QString &command, const WalletModel* wallet_model); - private: struct TranslatedStrings { const QString yes{tr("Yes")}, no{tr("No")}, to{tr("To")}, from{tr("From")}, @@ -166,6 +170,7 @@ private: int consoleFontSize = 0; QCompleter *autoCompleter = nullptr; QThread thread; + RPCExecutor* m_executor{nullptr}; WalletModel* m_last_wallet_model{nullptr}; bool m_is_executing{false}; QByteArray m_peer_widget_header_state; diff --git a/src/qt/sendcoinsdialog.cpp b/src/qt/sendcoinsdialog.cpp index 579ef0c3fd..a75c1098a5 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); @@ -380,8 +378,7 @@ bool SendCoinsDialog::PrepareSendText(QString& question_string, QString& informa question_string.append("<hr />"); CAmount totalAmount = m_current_transaction->getTotalTransactionAmount() + txFee; QStringList alternativeUnits; - for (const BitcoinUnits::Unit u : BitcoinUnits::availableUnits()) - { + for (const BitcoinUnit u : BitcoinUnits::availableUnits()) { if(u != model->getOptionsModel()->getDisplayUnit()) alternativeUnits.append(BitcoinUnits::formatHtmlWithUnit(u, totalAmount)); } @@ -401,6 +398,80 @@ bool SendCoinsDialog::PrepareSendText(QString& question_string, QString& informa return true; } +void SendCoinsDialog::presentPSBT(PartiallySignedTransaction& psbtx) +{ + // Serialize the PSBT + CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION); + ssTx << psbtx; + GUIUtil::setClipboard(EncodeBase64(ssTx.str()).c_str()); + QMessageBox msgBox; + msgBox.setText("Unsigned Transaction"); + msgBox.setInformativeText("The PSBT has been copied to the clipboard. You can also save it."); + msgBox.setStandardButtons(QMessageBox::Save | QMessageBox::Discard); + msgBox.setDefaultButton(QMessageBox::Discard); + switch (msgBox.exec()) { + case QMessageBox::Save: { + QString selectedFilter; + QString fileNameSuggestion = ""; + bool first = true; + for (const SendCoinsRecipient &rcp : m_current_transaction->getRecipients()) { + if (!first) { + fileNameSuggestion.append(" - "); + } + QString labelOrAddress = rcp.label.isEmpty() ? rcp.address : rcp.label; + QString amount = BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), rcp.amount); + fileNameSuggestion.append(labelOrAddress + "-" + amount); + first = false; + } + fileNameSuggestion.append(".psbt"); + QString filename = GUIUtil::getSaveFileName(this, + tr("Save Transaction Data"), fileNameSuggestion, + //: Expanded name of the binary PSBT file format. See: BIP 174. + tr("Partially Signed Transaction (Binary)") + QLatin1String(" (*.psbt)"), &selectedFilter); + if (filename.isEmpty()) { + return; + } + std::ofstream out{filename.toLocal8Bit().data(), std::ofstream::out | std::ofstream::binary}; + out << ssTx.str(); + out.close(); + Q_EMIT message(tr("PSBT saved"), "PSBT saved to disk", CClientUIInterface::MSG_INFORMATION); + break; + } + case QMessageBox::Discard: + break; + default: + assert(false); + } // msgBox.exec() +} + +bool SendCoinsDialog::signWithExternalSigner(PartiallySignedTransaction& psbtx, CMutableTransaction& mtx, bool& complete) { + TransactionError err; + try { + err = model->wallet().fillPSBT(SIGHASH_ALL, /*sign=*/true, /*bip32derivs=*/true, /*n_signed=*/nullptr, psbtx, complete); + } catch (const std::runtime_error& e) { + QMessageBox::critical(nullptr, tr("Sign failed"), e.what()); + return false; + } + if (err == TransactionError::EXTERNAL_SIGNER_NOT_FOUND) { + //: "External signer" means using devices such as hardware wallets. + QMessageBox::critical(nullptr, tr("External signer not found"), "External signer not found"); + return false; + } + if (err == TransactionError::EXTERNAL_SIGNER_FAILED) { + //: "External signer" means using devices such as hardware wallets. + QMessageBox::critical(nullptr, tr("External signer failure"), "External signer failure"); + return false; + } + if (err != TransactionError::OK) { + tfm::format(std::cerr, "Failed to sign PSBT"); + processSendCoinsReturn(WalletModel::TransactionCreationFailed); + return false; + } + // fillPSBT does not always properly finalize + complete = FinalizeAndExtractPSBT(psbtx, mtx); + return true; +} + void SendCoinsDialog::sendButtonClicked([[maybe_unused]] bool checked) { if(!model || !model->getOptionsModel()) @@ -411,7 +482,9 @@ void SendCoinsDialog::sendButtonClicked([[maybe_unused]] bool checked) assert(m_current_transaction); const QString confirmation = tr("Confirm send coins"); - auto confirmationDialog = new SendConfirmationDialog(confirmation, question_string, informative_text, detailed_text, SEND_CONFIRM_DELAY, !model->wallet().privateKeysDisabled(), model->getOptionsModel()->getEnablePSBTControls(), this); + const bool enable_send{!model->wallet().privateKeysDisabled() || model->wallet().hasExternalSigner()}; + const bool always_show_unsigned{model->getOptionsModel()->getEnablePSBTControls()}; + auto confirmationDialog = new SendConfirmationDialog(confirmation, question_string, informative_text, detailed_text, SEND_CONFIRM_DELAY, enable_send, always_show_unsigned, this); confirmationDialog->setAttribute(Qt::WA_DeleteOnClose); // TODO: Replace QDialog::exec() with safer QDialog::show(). const auto retval = static_cast<QMessageBox::StandardButton>(confirmationDialog->exec()); @@ -424,116 +497,52 @@ void SendCoinsDialog::sendButtonClicked([[maybe_unused]] bool checked) bool send_failure = false; if (retval == QMessageBox::Save) { + // "Create Unsigned" clicked CMutableTransaction mtx = CMutableTransaction{*(m_current_transaction->getWtx())}; PartiallySignedTransaction psbtx(mtx); bool complete = false; - // Always fill without signing first. This prevents an external signer - // from being called prematurely and is not expensive. - TransactionError err = model->wallet().fillPSBT(SIGHASH_ALL, false /* sign */, true /* bip32derivs */, nullptr, psbtx, complete); + // Fill without signing + TransactionError err = model->wallet().fillPSBT(SIGHASH_ALL, /*sign=*/false, /*bip32derivs=*/true, /*n_signed=*/nullptr, psbtx, complete); assert(!complete); assert(err == TransactionError::OK); - if (model->wallet().hasExternalSigner()) { - try { - err = model->wallet().fillPSBT(SIGHASH_ALL, true /* sign */, true /* bip32derivs */, nullptr, psbtx, complete); - } catch (const std::runtime_error& e) { - QMessageBox::critical(nullptr, tr("Sign failed"), e.what()); - send_failure = true; - return; - } - if (err == TransactionError::EXTERNAL_SIGNER_NOT_FOUND) { - //: "External signer" means using devices such as hardware wallets. - QMessageBox::critical(nullptr, tr("External signer not found"), "External signer not found"); - send_failure = true; - return; - } - if (err == TransactionError::EXTERNAL_SIGNER_FAILED) { - //: "External signer" means using devices such as hardware wallets. - QMessageBox::critical(nullptr, tr("External signer failure"), "External signer failure"); - send_failure = true; - return; - } - if (err != TransactionError::OK) { - tfm::format(std::cerr, "Failed to sign PSBT"); - processSendCoinsReturn(WalletModel::TransactionCreationFailed); - send_failure = true; - return; - } - // fillPSBT does not always properly finalize - complete = FinalizeAndExtractPSBT(psbtx, mtx); - } - - // Broadcast transaction if complete (even with an external signer this - // is not always the case, e.g. in a multisig wallet). - if (complete) { - const CTransactionRef tx = MakeTransactionRef(mtx); - m_current_transaction->setWtx(tx); - 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; - } - return; - } // Copy PSBT to clipboard and offer to save - assert(!complete); - // Serialize the PSBT - CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION); - ssTx << psbtx; - GUIUtil::setClipboard(EncodeBase64(ssTx.str()).c_str()); - QMessageBox msgBox; - msgBox.setText("Unsigned Transaction"); - msgBox.setInformativeText("The PSBT has been copied to the clipboard. You can also save it."); - msgBox.setStandardButtons(QMessageBox::Save | QMessageBox::Discard); - msgBox.setDefaultButton(QMessageBox::Discard); - switch (msgBox.exec()) { - case QMessageBox::Save: { - QString selectedFilter; - QString fileNameSuggestion = ""; - bool first = true; - for (const SendCoinsRecipient &rcp : m_current_transaction->getRecipients()) { - if (!first) { - fileNameSuggestion.append(" - "); + presentPSBT(psbtx); + } else { + // "Send" clicked + assert(!model->wallet().privateKeysDisabled() || model->wallet().hasExternalSigner()); + bool broadcast = true; + if (model->wallet().hasExternalSigner()) { + CMutableTransaction mtx = CMutableTransaction{*(m_current_transaction->getWtx())}; + PartiallySignedTransaction psbtx(mtx); + bool complete = false; + // Always fill without signing first. This prevents an external signer + // from being called prematurely and is not expensive. + TransactionError err = model->wallet().fillPSBT(SIGHASH_ALL, /*sign=*/false, /*bip32derivs=*/true, /*n_signed=*/nullptr, psbtx, complete); + assert(!complete); + assert(err == TransactionError::OK); + send_failure = !signWithExternalSigner(psbtx, mtx, complete); + // Don't broadcast when user rejects it on the device or there's a failure: + broadcast = complete && !send_failure; + if (!send_failure) { + // A transaction signed with an external signer is not always complete, + // e.g. in a multisig wallet. + if (complete) { + // Prepare transaction for broadcast transaction if complete + const CTransactionRef tx = MakeTransactionRef(mtx); + m_current_transaction->setWtx(tx); + } else { + presentPSBT(psbtx); } - QString labelOrAddress = rcp.label.isEmpty() ? rcp.address : rcp.label; - QString amount = BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), rcp.amount); - fileNameSuggestion.append(labelOrAddress + "-" + amount); - first = false; } - fileNameSuggestion.append(".psbt"); - QString filename = GUIUtil::getSaveFileName(this, - tr("Save Transaction Data"), fileNameSuggestion, - //: Expanded name of the binary PSBT file format. See: BIP 174. - tr("Partially Signed Transaction (Binary)") + QLatin1String(" (*.psbt)"), &selectedFilter); - if (filename.isEmpty()) { - return; - } - std::ofstream out{filename.toLocal8Bit().data(), std::ofstream::out | std::ofstream::binary}; - out << ssTx.str(); - out.close(); - Q_EMIT message(tr("PSBT saved"), "PSBT saved to disk", CClientUIInterface::MSG_INFORMATION); - break; } - case QMessageBox::Discard: - break; - default: - assert(false); - } // msgBox.exec() - } else { - assert(!model->wallet().privateKeysDisabled()); - // 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) { + // Broadcast the transaction, unless an external signer was used and it + // failed, or more signatures are needed. + if (broadcast) { + // now send the prepared transaction + model->sendCoins(*m_current_transaction); Q_EMIT coinsSent(m_current_transaction->getWtx()->GetHash()); - } else { - send_failure = true; } } if (!send_failure) { @@ -700,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(); } @@ -739,10 +748,6 @@ void SendCoinsDialog::processSendCoinsReturn(const WalletModel::SendCoinsReturn case WalletModel::AbsurdFee: msgParams.first = tr("A fee higher than %1 is considered an absurdly high fee.").arg(BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), model->wallet().getDefaultMaxTxFee())); break; - case WalletModel::PaymentRequestExpired: - msgParams.first = tr("Payment request expired."); - msgParams.second = CClientUIInterface::MSG_ERROR; - break; // included to prevent a compiler warning. case WalletModel::OK: default: @@ -779,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) { diff --git a/src/qt/sendcoinsdialog.h b/src/qt/sendcoinsdialog.h index 4a16702756..b58d4690a0 100644 --- a/src/qt/sendcoinsdialog.h +++ b/src/qt/sendcoinsdialog.h @@ -70,6 +70,8 @@ private: bool fFeeMinimized; const PlatformStyle *platformStyle; + // Copy PSBT to clipboard and offer to save it. + void presentPSBT(PartiallySignedTransaction& psbt); // Process WalletModel::SendCoinsReturn and generate a pair consisting // of a message and message flags for use in Q_EMIT message(). // Additional parameter msgArg can be used via .arg(msgArg). @@ -77,6 +79,15 @@ private: void minimizeFeeSection(bool fMinimize); // Format confirmation message bool PrepareSendText(QString& question_string, QString& informative_text, QString& detailed_text); + /* Sign PSBT using external signer. + * + * @param[in,out] psbtx the PSBT to sign + * @param[in,out] mtx needed to attempt to finalize + * @param[in,out] complete whether the PSBT is complete (a successfully signed multisig transaction may not be complete) + * + * @returns false if any failure occurred, which may include the user rejection of a transaction on the device. + */ + bool signWithExternalSigner(PartiallySignedTransaction& psbt, CMutableTransaction& mtx, bool& complete); void updateFeeMinimizedLabel(); void updateCoinControlState(); @@ -86,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); @@ -117,6 +128,8 @@ class SendConfirmationDialog : public QMessageBox public: SendConfirmationDialog(const QString& title, const QString& text, const QString& informative_text = "", const QString& detailed_text = "", int secDelay = SEND_CONFIRM_DELAY, bool enable_send = true, bool always_show_unsigned = true, QWidget* parent = nullptr); + /* Returns QMessageBox::Cancel, QMessageBox::Yes when "Send" is + clicked and QMessageBox::Save when "Create Unsigned" is clicked. */ int exec() override; private Q_SLOTS: 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/signverifymessagedialog.cpp b/src/qt/signverifymessagedialog.cpp index 74bedbf020..1f4b30534b 100644 --- a/src/qt/signverifymessagedialog.cpp +++ b/src/qt/signverifymessagedialog.cpp @@ -91,7 +91,7 @@ void SignVerifyMessageDialog::on_addressBookButton_SM_clicked() { if (model && model->getAddressTableModel()) { - model->refresh(/* pk_hash_only */ true); + model->refresh(/*pk_hash_only=*/true); AddressBookPage dlg(platformStyle, AddressBookPage::ForSelection, AddressBookPage::ReceivingTab, this); dlg.setModel(model->getAddressTableModel()); if (dlg.exec()) 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 66637a5dcf..581735263d 100644 --- a/src/qt/test/addressbooktests.cpp +++ b/src/qt/test/addressbooktests.cpp @@ -8,6 +8,7 @@ #include <interfaces/chain.h> #include <interfaces/node.h> +#include <qt/addressbookpage.h> #include <qt/clientmodel.h> #include <qt/editaddressdialog.h> #include <qt/optionsmodel.h> @@ -23,8 +24,10 @@ #include <chrono> #include <QApplication> -#include <QTimer> +#include <QLineEdit> #include <QMessageBox> +#include <QTableView> +#include <QTimer> using wallet::AddWallet; using wallet::CWallet; @@ -100,11 +103,13 @@ void TestAddAddressesToSendBook(interfaces::Node& node) QString s_label("already here (s)"); // Define a new address (which should add to the address book successfully). - QString new_address; + QString new_address_a; + QString new_address_b; std::tie(r_key_dest, preexisting_r_address) = build_address(); std::tie(s_key_dest, preexisting_s_address) = build_address(); - std::tie(std::ignore, new_address) = build_address(); + std::tie(std::ignore, new_address_a) = build_address(); + std::tie(std::ignore, new_address_b) = build_address(); { LOCK(wallet->cs_wallet); @@ -122,7 +127,9 @@ void TestAddAddressesToSendBook(interfaces::Node& node) // Initialize relevant QT models. std::unique_ptr<const PlatformStyle> platformStyle(PlatformStyle::instantiate("other")); - OptionsModel optionsModel; + OptionsModel optionsModel(node); + bilingual_str error; + QVERIFY(optionsModel.Init(error)); ClientModel clientModel(node, &optionsModel); WalletContext& context = *node.walletLoader().context(); AddWallet(context, wallet); @@ -131,14 +138,19 @@ void TestAddAddressesToSendBook(interfaces::Node& node) EditAddressDialog editAddressDialog(EditAddressDialog::NewSendingAddress); editAddressDialog.setModel(walletModel.getAddressTableModel()); + AddressBookPage address_book{platformStyle.get(), AddressBookPage::ForEditing, AddressBookPage::SendingTab}; + address_book.setModel(walletModel.getAddressTableModel()); + auto table_view = address_book.findChild<QTableView*>("tableView"); + QCOMPARE(table_view->model()->rowCount(), 1); + EditAddressAndSubmit( &editAddressDialog, QString("uhoh"), preexisting_r_address, QString( "Address \"%1\" already exists as a receiving address with label " "\"%2\" and so cannot be added as a sending address." ).arg(preexisting_r_address).arg(r_label)); - check_addbook_size(2); + QCOMPARE(table_view->model()->rowCount(), 1); EditAddressAndSubmit( &editAddressDialog, QString("uhoh, different"), preexisting_s_address, @@ -146,22 +158,65 @@ void TestAddAddressesToSendBook(interfaces::Node& node) "The entered address \"%1\" is already in the address book with " "label \"%2\"." ).arg(preexisting_s_address).arg(s_label)); - check_addbook_size(2); + QCOMPARE(table_view->model()->rowCount(), 1); // Submit a new address which should add successfully - we expect the // warning message to be blank. EditAddressAndSubmit( - &editAddressDialog, QString("new"), new_address, QString("")); - + &editAddressDialog, QString("io - new A"), new_address_a, QString("")); check_addbook_size(3); + QCOMPARE(table_view->model()->rowCount(), 2); + + EditAddressAndSubmit( + &editAddressDialog, QString("io - new B"), new_address_b, QString("")); + check_addbook_size(4); + QCOMPARE(table_view->model()->rowCount(), 3); + + auto search_line = address_book.findChild<QLineEdit*>("searchLineEdit"); + + search_line->setText(r_label); + QCOMPARE(table_view->model()->rowCount(), 0); + + search_line->setText(s_label); + QCOMPARE(table_view->model()->rowCount(), 1); + + search_line->setText("io"); + QCOMPARE(table_view->model()->rowCount(), 2); + + // Check wildcard "?". + search_line->setText("io?new"); + QCOMPARE(table_view->model()->rowCount(), 0); + search_line->setText("io???new"); + QCOMPARE(table_view->model()->rowCount(), 2); + + // Check wildcard "*". + search_line->setText("io*new"); + QCOMPARE(table_view->model()->rowCount(), 2); + search_line->setText("*"); + QCOMPARE(table_view->model()->rowCount(), 3); + + search_line->setText(preexisting_r_address); + QCOMPARE(table_view->model()->rowCount(), 0); + + search_line->setText(preexisting_s_address); + QCOMPARE(table_view->model()->rowCount(), 1); + + search_line->setText(new_address_a); + QCOMPARE(table_view->model()->rowCount(), 1); + + search_line->setText(new_address_b); + QCOMPARE(table_view->model()->rowCount(), 1); + + search_line->setText(""); + QCOMPARE(table_view->model()->rowCount(), 3); } } // namespace void AddressBookTests::addressBookTests() { -#ifdef Q_OS_MAC +#ifdef Q_OS_MACOS if (QApplication::platformName() == "minimal") { // Disable for mac on "minimal" platform to avoid crashes inside the Qt // framework when it tries to look up unimplemented cocoa functions, diff --git a/src/qt/test/apptests.cpp b/src/qt/test/apptests.cpp index 099ac5fcae..6fc7a52435 100644 --- a/src/qt/test/apptests.cpp +++ b/src/qt/test/apptests.cpp @@ -58,7 +58,7 @@ void TestRpcCommand(RPCConsole* console) //! Entry point for BitcoinApplication tests. void AppTests::appTests() { -#ifdef Q_OS_MAC +#ifdef Q_OS_MACOS if (QApplication::platformName() == "minimal") { // Disable for mac on "minimal" platform to avoid crashes inside the Qt // framework when it tries to look up unimplemented cocoa functions, @@ -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()); @@ -119,6 +114,6 @@ AppTests::HandleCallback::~HandleCallback() assert(it != callbacks.end()); callbacks.erase(it); if (callbacks.empty()) { - m_app_tests.m_app.quit(); + m_app_tests.m_app.exit(0); } } diff --git a/src/qt/test/optiontests.cpp b/src/qt/test/optiontests.cpp new file mode 100644 index 0000000000..17ffeb220b --- /dev/null +++ b/src/qt/test/optiontests.cpp @@ -0,0 +1,134 @@ +// Copyright (c) 2018 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 <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> + +#include <QSettings> +#include <QTest> + +#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 + // that setting integer prune value doesn't cause an exception to be thrown + // in the OptionsModel constructor + gArgs.LockSettings([&](util::Settings& settings) { + settings.forced_settings.erase("prune"); + settings.rw_settings["prune"] = 3814; + }); + gArgs.WriteSettingsFile(); + bilingual_str error; + QVERIFY(OptionsModel{m_node}.Init(error)); + gArgs.LockSettings([&](util::Settings& settings) { + settings.rw_settings.erase("prune"); + }); + gArgs.WriteSettingsFile(); +} + +void OptionTests::parametersInteraction() +{ + // Test that the bug https://github.com/bitcoin-core/gui/issues/567 does not resurface. + // 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.LockSettings([&](util::Settings& s) { + s.forced_settings.erase("listen"); + s.forced_settings.erase("listenonion"); + }); + QVERIFY(!gArgs.IsArgSet("-listen")); + QVERIFY(!gArgs.IsArgSet("-listenonion")); + + QSettings settings; + settings.setValue("fListen", false); + + bilingual_str error; + QVERIFY(OptionsModel{m_node}.Init(error)); + + const bool expected{false}; + + QVERIFY(gArgs.IsArgSet("-listen")); + QCOMPARE(gArgs.GetBoolArg("-listen", !expected), expected); + + QVERIFY(gArgs.IsArgSet("-listenonion")); + QCOMPARE(gArgs.GetBoolArg("-listenonion", !expected), expected); + + QVERIFY(AppInitParameterInteraction(gArgs)); + + // cleanup + settings.remove("fListen"); + 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 new file mode 100644 index 0000000000..57ec8bd0f2 --- /dev/null +++ b/src/qt/test/optiontests.h @@ -0,0 +1,32 @@ +// 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. + +#ifndef BITCOIN_QT_TEST_OPTIONTESTS_H +#define BITCOIN_QT_TEST_OPTIONTESTS_H + +#include <qt/optionsmodel.h> +#include <univalue.h> +#include <util/settings.h> + +#include <QObject> + +class OptionTests : public QObject +{ + Q_OBJECT +public: + 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 10b7e2ffe7..846fa519ee 100644 --- a/src/qt/test/test_main.cpp +++ b/src/qt/test/test_main.cpp @@ -10,6 +10,7 @@ #include <interfaces/node.h> #include <qt/bitcoin.h> #include <qt/test/apptests.h> +#include <qt/test/optiontests.h> #include <qt/test/rpcnestedtests.h> #include <qt/test/uritests.h> #include <test/util/setup_common.h> @@ -20,8 +21,10 @@ #endif // ENABLE_WALLET #include <QApplication> +#include <QDebug> #include <QObject> #include <QTest> + #include <functional> #if defined(QT_STATICPLUGIN) @@ -40,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{}; @@ -55,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"); @@ -68,8 +70,6 @@ int main(int argc, char* argv[]) gArgs.ForceSetArg("-upnp", "0"); gArgs.ForceSetArg("-natpmp", "0"); - bool fInvalid = false; - // Prefer the "minimal" platform for the test instead of the normal default // platform ("xcb", "windows", or "cocoa") so tests can't unintentionally // interfere with any background GUIs and don't require extra resources. @@ -85,28 +85,32 @@ int main(int argc, char* argv[]) app.setApplicationName("Bitcoin-Qt-test"); app.createNode(*init); + int num_test_failures{0}; + AppTests app_tests(app); - if (QTest::qExec(&app_tests) != 0) { - fInvalid = true; - } + num_test_failures += QTest::qExec(&app_tests); + + OptionTests options_tests(app.node()); + num_test_failures += QTest::qExec(&options_tests); + URITests test1; - if (QTest::qExec(&test1) != 0) { - fInvalid = true; - } + num_test_failures += QTest::qExec(&test1); + RPCNestedTests test3(app.node()); - if (QTest::qExec(&test3) != 0) { - fInvalid = true; - } + num_test_failures += QTest::qExec(&test3); + #ifdef ENABLE_WALLET WalletTests test5(app.node()); - if (QTest::qExec(&test5) != 0) { - fInvalid = true; - } + num_test_failures += QTest::qExec(&test5); + AddressBookTests test6(app.node()); - if (QTest::qExec(&test6) != 0) { - fInvalid = true; - } + num_test_failures += QTest::qExec(&test6); #endif - return fInvalid; + if (num_test_failures) { + qWarning("\nFailed tests: %d\n", num_test_failures); + } else { + qDebug("\nAll tests passed.\n"); + } + return num_test_failures; } diff --git a/src/qt/test/wallettests.cpp b/src/qt/test/wallettests.cpp index 6ab534764b..b71dfb0e9f 100644 --- a/src/qt/test/wallettests.cpp +++ b/src/qt/test/wallettests.cpp @@ -7,24 +7,25 @@ #include <interfaces/chain.h> #include <interfaces/node.h> +#include <key_io.h> #include <qt/bitcoinamountfield.h> +#include <qt/bitcoinunits.h> #include <qt/clientmodel.h> #include <qt/optionsmodel.h> +#include <qt/overviewpage.h> #include <qt/platformstyle.h> #include <qt/qvalidatedlineedit.h> +#include <qt/receivecoinsdialog.h> +#include <qt/receiverequestdialog.h> +#include <qt/recentrequeststablemodel.h> #include <qt/sendcoinsdialog.h> #include <qt/sendcoinsentry.h> #include <qt/transactiontablemodel.h> #include <qt/transactionview.h> #include <qt/walletmodel.h> -#include <key_io.h> #include <test/util/setup_common.h> #include <validation.h> #include <wallet/wallet.h> -#include <qt/overviewpage.h> -#include <qt/receivecoinsdialog.h> -#include <qt/recentrequeststablemodel.h> -#include <qt/receiverequestdialog.h> #include <chrono> #include <memory> @@ -128,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 @@ -167,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); @@ -183,7 +191,9 @@ void TestGUI(interfaces::Node& node) std::unique_ptr<const PlatformStyle> platformStyle(PlatformStyle::instantiate("other")); SendCoinsDialog sendCoinsDialog(platformStyle.get()); TransactionView transactionView(platformStyle.get()); - OptionsModel optionsModel; + OptionsModel optionsModel(node); + bilingual_str error; + QVERIFY(optionsModel.Init(error)); ClientModel clientModel(node, &optionsModel); WalletContext& context = *node.walletLoader().context(); AddWallet(context, wallet); @@ -192,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(); - int 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(); @@ -220,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(); - int 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()); @@ -314,7 +315,7 @@ void TestGUI(interfaces::Node& node) void WalletTests::walletTests() { -#ifdef Q_OS_MAC +#ifdef Q_OS_MACOS if (QApplication::platformName() == "minimal") { // Disable for mac on "minimal" platform to avoid crashes inside the Qt // framework when it tries to look up unimplemented cocoa functions, diff --git a/src/qt/transactiondesc.cpp b/src/qt/transactiondesc.cpp index be5851d627..a61d5407b3 100644 --- a/src/qt/transactiondesc.cpp +++ b/src/qt/transactiondesc.cpp @@ -32,20 +32,46 @@ using wallet::ISMINE_SPENDABLE; using wallet::ISMINE_WATCH_ONLY; using wallet::isminetype; -QString TransactionDesc::FormatTxStatus(const interfaces::WalletTx& wtx, const interfaces::WalletTxStatus& status, bool inMempool, int numBlocks) +QString TransactionDesc::FormatTxStatus(const interfaces::WalletTxStatus& status, bool inMempool) { - { - int nDepth = status.depth_in_main_chain; - if (nDepth < 0) { - return tr("conflicted with a transaction with %1 confirmations").arg(-nDepth); - } else if (nDepth == 0) { - const QString abandoned{status.is_abandoned ? QLatin1String(", ") + tr("abandoned") : QString()}; - return tr("0/unconfirmed, %1").arg(inMempool ? tr("in memory pool") : tr("not in memory pool")) + abandoned; - } else if (nDepth < 6) { - return tr("%1/unconfirmed").arg(nDepth); + int depth = status.depth_in_main_chain; + if (depth < 0) { + /*: 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. */ + return tr("conflicted with a transaction with %1 confirmations").arg(-depth); + } else if (depth == 0) { + QString s; + if (inMempool) { + /*: 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. */ + s = tr("0/unconfirmed, in memory pool"); } else { - return tr("%1 confirmations").arg(nDepth); + /*: 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. */ + s = tr("0/unconfirmed, not in memory pool"); + } + if (status.is_abandoned) { + /*: 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. */ + s += QLatin1String(", ") + tr("abandoned"); } + return s; + } else if (depth < 6) { + /*: 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. */ + return tr("%1/unconfirmed").arg(depth); + } else { + /*: 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. */ + return tr("%1 confirmations").arg(depth); } } @@ -77,7 +103,7 @@ bool GetPaymentRequestMerchant(const std::string& pr, QString& merchant) return false; } -QString TransactionDesc::toHTML(interfaces::Node& node, interfaces::Wallet& wallet, TransactionRecord *rec, int unit) +QString TransactionDesc::toHTML(interfaces::Node& node, interfaces::Wallet& wallet, TransactionRecord* rec, BitcoinUnit unit) { int numBlocks; interfaces::WalletTxStatus status; @@ -95,7 +121,7 @@ QString TransactionDesc::toHTML(interfaces::Node& node, interfaces::Wallet& wall CAmount nDebit = wtx.debit; CAmount nNet = nCredit - nDebit; - strHTML += "<b>" + tr("Status") + ":</b> " + FormatTxStatus(wtx, status, inMempool, numBlocks); + strHTML += "<b>" + tr("Status") + ":</b> " + FormatTxStatus(status, inMempool); strHTML += "<br>"; strHTML += "<b>" + tr("Date") + ":</b> " + (nTime ? GUIUtil::dateTimeStr(nTime) : "") + "<br>"; diff --git a/src/qt/transactiondesc.h b/src/qt/transactiondesc.h index cf955a433c..803e41b699 100644 --- a/src/qt/transactiondesc.h +++ b/src/qt/transactiondesc.h @@ -5,6 +5,8 @@ #ifndef BITCOIN_QT_TRANSACTIONDESC_H #define BITCOIN_QT_TRANSACTIONDESC_H +#include <qt/bitcoinunits.h> + #include <QObject> #include <QString> @@ -24,12 +26,12 @@ class TransactionDesc: public QObject Q_OBJECT public: - static QString toHTML(interfaces::Node& node, interfaces::Wallet& wallet, TransactionRecord *rec, int unit); + static QString toHTML(interfaces::Node& node, interfaces::Wallet& wallet, TransactionRecord* rec, BitcoinUnit unit); private: TransactionDesc() {} - static QString FormatTxStatus(const interfaces::WalletTx& wtx, const interfaces::WalletTxStatus& status, bool inMempool, int numBlocks); + static QString FormatTxStatus(const interfaces::WalletTxStatus& status, bool inMempool); }; #endif // BITCOIN_QT_TRANSACTIONDESC_H 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/transactionrecord.h b/src/qt/transactionrecord.h index dd34656d5f..d8748d7dc9 100644 --- a/src/qt/transactionrecord.h +++ b/src/qt/transactionrecord.h @@ -20,13 +20,7 @@ struct WalletTxStatus; /** UI model for transaction status. The transaction status is the part of a transaction that will change over time. */ -class TransactionStatus -{ -public: - TransactionStatus() : countsForBalance(false), sortKey(""), - matures_in(0), status(Unconfirmed), depth(0), open_for(0) - { } - +struct TransactionStatus { enum Status { Confirmed, /**< Have 6 or more confirmations (normal tx) or fully mature (mined tx) **/ /// Normal (sent/received) transactions @@ -40,28 +34,25 @@ public: }; /// Transaction counts towards available balance - bool countsForBalance; + bool countsForBalance{false}; /// Sorting key based on status std::string sortKey; /** @name Generated (mined) transactions @{*/ - int matures_in; + int matures_in{0}; /**@}*/ /** @name Reported status @{*/ - Status status; - qint64 depth; - qint64 open_for; /**< Timestamp if status==OpenUntilDate, otherwise number - of additional blocks that need to be mined before - finalization */ + Status status{Unconfirmed}; + qint64 depth{0}; /**@}*/ /** Current block hash (to know whether cached status is still valid) */ uint256 m_cur_block_hash{}; - bool needsUpdate; + bool needsUpdate{false}; }; /** UI model for a transaction. A core transaction can be represented by multiple UI transactions if it has diff --git a/src/qt/transactiontablemodel.cpp b/src/qt/transactiontablemodel.cpp index 44b4fee2e7..4312b3cd24 100644 --- a/src/qt/transactiontablemodel.cpp +++ b/src/qt/transactiontablemodel.cpp @@ -5,6 +5,7 @@ #include <qt/transactiontablemodel.h> #include <qt/addresstablemodel.h> +#include <qt/bitcoinunits.h> #include <qt/clientmodel.h> #include <qt/guiconstants.h> #include <qt/guiutil.h> @@ -32,11 +33,11 @@ // Amount column is right-aligned it contains numbers static int column_alignments[] = { - Qt::AlignLeft|Qt::AlignVCenter, /* status */ - Qt::AlignLeft|Qt::AlignVCenter, /* watchonly */ - Qt::AlignLeft|Qt::AlignVCenter, /* date */ - Qt::AlignLeft|Qt::AlignVCenter, /* type */ - Qt::AlignLeft|Qt::AlignVCenter, /* address */ + Qt::AlignLeft|Qt::AlignVCenter, /*status=*/ + Qt::AlignLeft|Qt::AlignVCenter, /*watchonly=*/ + Qt::AlignLeft|Qt::AlignVCenter, /*date=*/ + Qt::AlignLeft|Qt::AlignVCenter, /*type=*/ + Qt::AlignLeft|Qt::AlignVCenter, /*address=*/ Qt::AlignRight|Qt::AlignVCenter /* amount */ }; @@ -61,7 +62,7 @@ struct TxLessThan struct TransactionNotification { public: - TransactionNotification() {} + TransactionNotification() = default; TransactionNotification(uint256 _hash, ChangeType _status, bool _showTransaction): hash(_hash), status(_status), showTransaction(_showTransaction) {} @@ -232,7 +233,7 @@ public: return nullptr; } - QString describe(interfaces::Node& node, interfaces::Wallet& wallet, TransactionRecord *rec, int unit) + QString describe(interfaces::Node& node, interfaces::Wallet& wallet, TransactionRecord* rec, BitcoinUnit unit) { return TransactionDesc::toHTML(node, wallet, rec, unit); } diff --git a/src/qt/transactionview.cpp b/src/qt/transactionview.cpp index 778ef04b77..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> @@ -550,7 +550,7 @@ void TransactionView::openThirdPartyTxUrl(QString url) QWidget *TransactionView::createDateRangeWidget() { dateRangeWidget = new QFrame(); - dateRangeWidget->setFrameStyle(QFrame::Panel | QFrame::Raised); + dateRangeWidget->setFrameStyle(static_cast<int>(QFrame::Panel) | static_cast<int>(QFrame::Raised)); dateRangeWidget->setContentsMargins(1,1,1,1); QHBoxLayout *layout = new QHBoxLayout(dateRangeWidget); layout->setContentsMargins(0,0,0,0); diff --git a/src/qt/utilitydialog.cpp b/src/qt/utilitydialog.cpp index e68095f8e5..4894cac905 100644 --- a/src/qt/utilitydialog.cpp +++ b/src/qt/utilitydialog.cpp @@ -22,7 +22,8 @@ #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 b025bb367c..8762ba9ab3 100644 --- a/src/qt/walletcontroller.cpp +++ b/src/qt/walletcontroller.cpp @@ -24,6 +24,7 @@ #include <QApplication> #include <QMessageBox> +#include <QMetaObject> #include <QMutexLocker> #include <QThread> #include <QTimer> @@ -135,7 +136,7 @@ WalletModel* WalletController::getOrCreateWallet(std::unique_ptr<interfaces::Wal // handled on the GUI event loop. wallet_model->moveToThread(thread()); // setParent(parent) must be called in the thread which created the parent object. More details in #18948. - GUIUtil::ObjectInvoke(this, [wallet_model, this] { + QMetaObject::invokeMethod(this, [wallet_model, this] { wallet_model->setParent(this); }, GUIUtil::blockingGUIThreadConnection()); @@ -261,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); }); @@ -292,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); @@ -342,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); }); @@ -372,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 91ce420a33..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> @@ -55,9 +55,7 @@ WalletFrame::WalletFrame(const PlatformStyle* _platformStyle, QWidget* parent) walletStack->addWidget(no_wallet_group); } -WalletFrame::~WalletFrame() -{ -} +WalletFrame::~WalletFrame() = default; void WalletFrame::setClientModel(ClientModel *_clientModel) { @@ -194,16 +192,16 @@ void WalletFrame::gotoVerifyMessageTab(QString addr) void WalletFrame::gotoLoadPSBT(bool from_clipboard) { - std::string data; + std::vector<unsigned char> data; if (from_clipboard) { std::string raw = QApplication::clipboard()->text().toStdString(); - bool invalid; - data = DecodeBase64(raw, &invalid); - if (invalid) { + auto result = DecodeBase64(raw); + if (!result) { Q_EMIT message(tr("Error"), tr("Unable to decode PSBT from clipboard (invalid base64)"), CClientUIInterface::MSG_ERROR); return; } + data = std::move(*result); } else { QString filename = GUIUtil::getOpenFileName(this, tr("Load Transaction Data"), QString(), @@ -214,12 +212,20 @@ void WalletFrame::gotoLoadPSBT(bool from_clipboard) return; } std::ifstream in{filename.toLocal8Bit().data(), std::ios::binary}; - data = std::string(std::istreambuf_iterator<char>{in}, {}); + 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; PartiallySignedTransaction psbtx; - if (!DecodeRawPSBT(psbtx, data, error)) { + if (!DecodeRawPSBT(psbtx, MakeByteSpan(data), error)) { Q_EMIT message(tr("Error"), tr("Unable to decode PSBT") + "\n" + QString::fromStdString(error), CClientUIInterface::MSG_ERROR); return; } diff --git a/src/qt/walletmodel.cpp b/src/qt/walletmodel.cpp index 5ee32e79d5..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 @@ -145,7 +154,7 @@ void WalletModel::updateWatchOnlyFlag(bool fHaveWatchonly) Q_EMIT notifyWatchonlyChanged(fHaveWatchonly); } -bool WalletModel::validateAddress(const QString &address) +bool WalletModel::validateAddress(const QString& address) const { return IsValidDestinationString(address.toStdString()); } @@ -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,26 +291,24 @@ 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() +OptionsModel* WalletModel::getOptionsModel() const { return optionsModel; } -AddressTableModel *WalletModel::getAddressTableModel() +AddressTableModel* WalletModel::getAddressTableModel() const { return addressTableModel; } -TransactionTableModel *WalletModel::getTransactionTableModel() +TransactionTableModel* WalletModel::getTransactionTableModel() const { return transactionTableModel; } -RecentRequestsTableModel *WalletModel::getRecentRequestsTableModel() +RecentRequestsTableModel* WalletModel::getRecentRequestsTableModel() const { return recentRequestsTableModel; } @@ -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()) @@ -369,7 +383,7 @@ static void NotifyAddressBookChanged(WalletModel *walletmodel, QString strPurpose = QString::fromStdString(purpose); qDebug() << "NotifyAddressBookChanged: " + strAddress + " " + strLabel + " isMine=" + QString::number(isMine) + " purpose=" + strPurpose + " status=" + QString::number(status); - bool invoked = QMetaObject::invokeMethod(walletmodel, "updateAddressBook", Qt::QueuedConnection, + bool invoked = QMetaObject::invokeMethod(walletmodel, "updateAddressBook", Q_ARG(QString, strAddress), Q_ARG(QString, strLabel), Q_ARG(bool, isMine), @@ -557,7 +571,7 @@ bool WalletModel::bumpFee(uint256 hash, uint256& new_hash) return true; } -bool WalletModel::displayAddress(std::string sAddress) +bool WalletModel::displayAddress(std::string sAddress) const { CTxDestination dest = DecodeDestination(sAddress); bool res = false; @@ -585,7 +599,7 @@ QString WalletModel::getDisplayName() const return name.isEmpty() ? "["+tr("default wallet")+"]" : name; } -bool WalletModel::isMultiwallet() +bool WalletModel::isMultiwallet() const { return m_node.walletLoader().getWallets().size() > 1; } @@ -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 ad1239ccdc..73dfe0386a 100644 --- a/src/qt/walletmodel.h +++ b/src/qt/walletmodel.h @@ -66,26 +66,26 @@ public: AmountWithFeeExceedsBalance, DuplicateAddress, TransactionCreationFailed, // Error returned when wallet is still locked - AbsurdFee, - PaymentRequestExpired + AbsurdFee }; enum EncryptionStatus { + NoKeys, // wallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) Unencrypted, // !wallet->IsCrypted() Locked, // wallet->IsCrypted() && wallet->IsLocked() Unlocked // wallet->IsCrypted() && !wallet->IsLocked() }; - OptionsModel *getOptionsModel(); - AddressTableModel *getAddressTableModel(); - TransactionTableModel *getTransactionTableModel(); - RecentRequestsTableModel *getRecentRequestsTableModel(); + OptionsModel* getOptionsModel() const; + AddressTableModel* getAddressTableModel() const; + TransactionTableModel* getTransactionTableModel() const; + RecentRequestsTableModel* getRecentRequestsTableModel() const; EncryptionStatus getEncryptionStatus() const; // Check address for validity - bool validateAddress(const QString &address); + bool validateAddress(const QString& address) const; // Return status record for SendCoins, contains error id + information struct SendCoinsReturn @@ -103,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); @@ -137,7 +137,7 @@ public: UnlockContext requestUnlock(); bool bumpFee(uint256 hash, uint256& new_hash); - bool displayAddress(std::string sAddress); + bool displayAddress(std::string sAddress) const; static bool isWalletEnabled(); @@ -149,14 +149,19 @@ public: QString getWalletName() const; QString getDisplayName() const; - bool isMultiwallet(); - - AddressTableModel* getAddressTableModel() const { return addressTableModel; } + bool isMultiwallet() const; void refresh(bool pk_hash_only = false); 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 e7ec54721a..10fc0fb6d0 100644 --- a/src/qt/walletview.cpp +++ b/src/qt/walletview.cpp @@ -19,11 +19,10 @@ #include <qt/walletmodel.h> #include <interfaces/node.h> -#include <node/ui_interface.h> +#include <node/interface_ui.h> #include <util/strencodings.h> #include <QAction> -#include <QActionGroup> #include <QFileDialog> #include <QHBoxLayout> #include <QProgressDialog> @@ -112,9 +111,7 @@ WalletView::WalletView(WalletModel* wallet_model, const PlatformStyle* _platform connect(walletModel, &WalletModel::showProgress, this, &WalletView::showProgress); } -WalletView::~WalletView() -{ -} +WalletView::~WalletView() = default; void WalletView::setClientModel(ClientModel *_clientModel) { diff --git a/src/qt/walletview.h b/src/qt/walletview.h index 2f9d344bc8..301084ffa9 100644 --- a/src/qt/walletview.h +++ b/src/qt/walletview.h @@ -6,6 +6,7 @@ #define BITCOIN_QT_WALLETVIEW_H #include <consensus/amount.h> +#include <qt/bitcoinunits.h> #include <QStackedWidget> @@ -115,7 +116,7 @@ Q_SIGNALS: /** Encryption status of wallet changed */ void encryptionStatusChanged(); /** Notify that a new transaction appeared */ - void incomingTransaction(const QString& date, int unit, const CAmount& amount, const QString& type, const QString& address, const QString& label, const QString& walletName); + void incomingTransaction(const QString& date, BitcoinUnit unit, const CAmount& amount, const QString& type, const QString& address, const QString& label, const QString& walletName); /** Notify that the out of sync warning icon has been pressed */ void outOfSyncWarningClicked(); }; diff --git a/src/random.cpp b/src/random.cpp index b862510524..f92e679a00 100644 --- a/src/random.cpp +++ b/src/random.cpp @@ -10,12 +10,13 @@ #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> // for LogPrintf() +#include <logging.h> #include <randomenv.h> #include <support/allocators/secure.h> +#include <span.h> #include <sync.h> // for Mutex #include <util/time.h> // for GetTimeMicros() @@ -96,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"); @@ -369,11 +370,9 @@ public: InitHardwareRand(); } - ~RNGState() - { - } + ~RNGState() = default; - void AddEvent(uint32_t event_info) noexcept + void AddEvent(uint32_t event_info) noexcept EXCLUSIVE_LOCKS_REQUIRED(!m_events_mutex) { LOCK(m_events_mutex); @@ -387,7 +386,7 @@ public: /** * Feed (the hash of) all events added through AddEvent() to hasher. */ - void SeedEvents(CSHA512& hasher) noexcept + void SeedEvents(CSHA512& hasher) noexcept EXCLUSIVE_LOCKS_REQUIRED(!m_events_mutex) { // We use only SHA256 for the events hashing to get the ASM speedups we have for SHA256, // since we want it to be fast as network peers may be able to trigger it repeatedly. @@ -406,7 +405,7 @@ public: * * If this function has never been called with strong_seed = true, false is returned. */ - bool MixExtract(unsigned char* out, size_t num, CSHA512&& hasher, bool strong_seed) noexcept + bool MixExtract(unsigned char* out, size_t num, CSHA512&& hasher, bool strong_seed) noexcept EXCLUSIVE_LOCKS_REQUIRED(!m_mutex) { assert(num <= 32); unsigned char buf[64]; @@ -578,27 +577,22 @@ static void ProcRand(unsigned char* out, int num, RNGLevel level) noexcept } } -void GetRandBytes(unsigned char* buf, int num) noexcept { ProcRand(buf, num, RNGLevel::FAST); } -void GetStrongRandBytes(unsigned char* buf, int num) noexcept { ProcRand(buf, num, RNGLevel::SLOW); } +void GetRandBytes(Span<unsigned char> bytes) noexcept { ProcRand(bytes.data(), bytes.size(), RNGLevel::FAST); } +void GetStrongRandBytes(Span<unsigned char> bytes) noexcept { ProcRand(bytes.data(), bytes.size(), RNGLevel::SLOW); } void RandAddPeriodic() noexcept { ProcRand(nullptr, 0, RNGLevel::PERIODIC); } void RandAddEvent(const uint32_t event_info) noexcept { GetRNGState().AddEvent(event_info); } bool g_mock_deterministic_tests{false}; -uint64_t GetRand(uint64_t nMax) noexcept +uint64_t GetRandInternal(uint64_t nMax) noexcept { return FastRandomContext(g_mock_deterministic_tests).randrange(nMax); } -int GetRandInt(int nMax) noexcept -{ - return GetRand(nMax); -} - uint256 GetRandHash() noexcept { uint256 hash; - GetRandBytes((unsigned char*)&hash, sizeof(hash)); + GetRandBytes(hash); return hash; } diff --git a/src/random.h b/src/random.h index 97302d61ab..5fe20c5f76 100644 --- a/src/random.h +++ b/src/random.h @@ -8,8 +8,10 @@ #include <crypto/chacha20.h> #include <crypto/common.h> +#include <span.h> #include <uint256.h> +#include <cassert> #include <chrono> #include <cstdint> #include <limits> @@ -66,9 +68,19 @@ * * Thread-safe. */ -void GetRandBytes(unsigned char* buf, int num) noexcept; +void GetRandBytes(Span<unsigned char> bytes) noexcept; /** Generate a uniform random integer in the range [0..range). Precondition: range > 0 */ -uint64_t GetRand(uint64_t nMax) noexcept; +uint64_t GetRandInternal(uint64_t nMax) noexcept; +/** Generate a uniform random integer of type T in the range [0..nMax) + * nMax defaults to std::numeric_limits<T>::max() + * Precondition: nMax > 0, T is an integral type, no larger than uint64_t + */ +template<typename T> +T GetRand(T nMax=std::numeric_limits<T>::max()) noexcept { + static_assert(std::is_integral<T>(), "T must be integral"); + static_assert(std::numeric_limits<T>::max() <= std::numeric_limits<uint64_t>::max(), "GetRand only supports up to uint64_t"); + return T(GetRandInternal(nMax)); +} /** Generate a uniform random duration in the range [0..max). Precondition: max.count() > 0 */ template <typename D> D GetRandomDuration(typename std::common_type<D>::type max) noexcept @@ -94,7 +106,6 @@ constexpr auto GetRandMillis = GetRandomDuration<std::chrono::milliseconds>; * */ std::chrono::microseconds GetExponentialRand(std::chrono::microseconds now, std::chrono::seconds average_interval); -int GetRandInt(int nMax) noexcept; uint256 GetRandHash() noexcept; /** @@ -105,7 +116,7 @@ uint256 GetRandHash() noexcept; * * Thread-safe. */ -void GetStrongRandBytes(unsigned char* buf, int num) noexcept; +void GetStrongRandBytes(Span<unsigned char> bytes) noexcept; /** * Gather entropy from various expensive sources, and feed them to the PRNG state. @@ -222,6 +233,23 @@ public: /** Generate a random boolean. */ bool randbool() noexcept { return randbits(1); } + /** Return the time point advanced by a uniform random duration. */ + template <typename Tp> + Tp rand_uniform_delay(const Tp& time, typename Tp::duration range) + { + 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 388841289a..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> @@ -57,8 +57,7 @@ #include <sys/auxv.h> #endif -//! Necessary on some platforms -extern char** environ; +extern char** environ; // NOLINT(readability-redundant-declaration): Necessary on some platforms namespace { @@ -363,10 +362,10 @@ void RandAddStaticEnv(CSHA512& hasher) #if HAVE_DECL_GETIFADDRS && HAVE_DECL_FREEIFADDRS // Network interfaces - struct ifaddrs *ifad = NULL; + struct ifaddrs *ifad = nullptr; getifaddrs(&ifad); struct ifaddrs *ifit = ifad; - while (ifit != NULL) { + while (ifit != nullptr) { hasher.Write((const unsigned char*)&ifit, sizeof(ifit)); hasher.Write((const unsigned char*)ifit->ifa_name, strlen(ifit->ifa_name) + 1); hasher.Write((const unsigned char*)&ifit->ifa_flags, sizeof(ifit->ifa_flags)); diff --git a/src/rest.cpp b/src/rest.cpp index 063872b47a..7f00db2222 100644 --- a/src/rest.cpp +++ b/src/rest.cpp @@ -3,6 +3,8 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. +#include <rest.h> + #include <blockfilter.h> #include <chain.h> #include <chainparams.h> @@ -15,6 +17,7 @@ #include <primitives/block.h> #include <primitives/transaction.h> #include <rpc/blockchain.h> +#include <rpc/mempool.h> #include <rpc/protocol.h> #include <rpc/server.h> #include <rpc/server_util.h> @@ -27,34 +30,25 @@ #include <version.h> #include <any> - -#include <boost/algorithm/string.hpp> +#include <string> #include <univalue.h> using node::GetTransaction; -using node::IsBlockPruned; using node::NodeContext; using node::ReadBlockFromDisk; static const size_t MAX_GETUTXOS_OUTPOINTS = 15; //allow a max of 15 outpoints to be queried at once static constexpr unsigned int MAX_REST_HEADERS_RESULTS = 2000; -enum class RetFormat { - UNDEF, - BINARY, - HEX, - JSON, -}; - static const struct { - RetFormat rf; + RESTResponseFormat rf; const char* name; } rf_names[] = { - {RetFormat::UNDEF, ""}, - {RetFormat::BINARY, "bin"}, - {RetFormat::HEX, "hex"}, - {RetFormat::JSON, "json"}, + {RESTResponseFormat::UNDEF, ""}, + {RESTResponseFormat::BINARY, "bin"}, + {RESTResponseFormat::HEX, "hex"}, + {RESTResponseFormat::JSON, "json"}, }; struct CCoin { @@ -137,25 +131,28 @@ static ChainstateManager* GetChainman(const std::any& context, HTTPRequest* req) return node_context->chainman.get(); } -static RetFormat ParseDataFormat(std::string& param, const std::string& strReq) +RESTResponseFormat ParseDataFormat(std::string& param, const std::string& strReq) { - const std::string::size_type pos = strReq.rfind('.'); - if (pos == std::string::npos) - { - param = strReq; + // Remove query string (if any, separated with '?') as it should not interfere with + // parsing param and data format + param = strReq.substr(0, strReq.rfind('?')); + const std::string::size_type pos_format{param.rfind('.')}; + + // No format string is found + if (pos_format == std::string::npos) { return rf_names[0].rf; } - param = strReq.substr(0, pos); - const std::string suff(strReq, pos + 1); - + // Match format string to available formats + const std::string suffix(param, pos_format + 1); for (const auto& rf_name : rf_names) { - if (suff == rf_name.name) + if (suffix == rf_name.name) { + param.erase(pos_format); return rf_name.rf; + } } - /* If no suffix is found, return original string. */ - param = strReq; + // If no suffix is found, return RESTResponseFormat::UNDEF and original string without query string return rf_names[0].rf; } @@ -191,19 +188,28 @@ static bool rest_headers(const std::any& context, if (!CheckWarmup(req)) return false; std::string param; - const RetFormat rf = ParseDataFormat(param, strURIPart); - std::vector<std::string> path; - boost::split(path, param, boost::is_any_of("/")); - - if (path.size() != 2) - return RESTERR(req, HTTP_BAD_REQUEST, "No header count specified. Use /rest/headers/<count>/<hash>.<ext>."); + const RESTResponseFormat rf = ParseDataFormat(param, strURIPart); + std::vector<std::string> path = SplitString(param, '/'); - const auto parsed_count{ToIntegral<size_t>(path[0])}; + std::string raw_count; + std::string hashStr; + if (path.size() == 2) { + // deprecated path: /rest/headers/<count>/<hash> + hashStr = path[1]; + raw_count = path[0]; + } else if (path.size() == 1) { + // new path with query parameter: /rest/headers/<hash>?count=<count> + hashStr = path[0]; + raw_count = req->GetQueryParameter("count").value_or("5"); + } else { + return RESTERR(req, HTTP_BAD_REQUEST, "Invalid URI format. Expected /rest/headers/<hash>.<ext>?count=<count>"); + } + + const auto parsed_count{ToIntegral<size_t>(raw_count)}; if (!parsed_count.has_value() || *parsed_count < 1 || *parsed_count > MAX_REST_HEADERS_RESULTS) { - return RESTERR(req, HTTP_BAD_REQUEST, strprintf("Header count is invalid or out of acceptable range (1-%u): %s", MAX_REST_HEADERS_RESULTS, path[0])); + return RESTERR(req, HTTP_BAD_REQUEST, strprintf("Header count is invalid or out of acceptable range (1-%u): %s", MAX_REST_HEADERS_RESULTS, raw_count)); } - std::string hashStr = path[1]; uint256 hash; if (!ParseHashStr(hashStr, hash)) return RESTERR(req, HTTP_BAD_REQUEST, "Invalid hash: " + hashStr); @@ -229,7 +235,7 @@ static bool rest_headers(const std::any& context, } switch (rf) { - case RetFormat::BINARY: { + case RESTResponseFormat::BINARY: { CDataStream ssHeader(SER_NETWORK, PROTOCOL_VERSION); for (const CBlockIndex *pindex : headers) { ssHeader << pindex->GetBlockHeader(); @@ -241,7 +247,7 @@ static bool rest_headers(const std::any& context, return true; } - case RetFormat::HEX: { + case RESTResponseFormat::HEX: { CDataStream ssHeader(SER_NETWORK, PROTOCOL_VERSION); for (const CBlockIndex *pindex : headers) { ssHeader << pindex->GetBlockHeader(); @@ -252,7 +258,7 @@ static bool rest_headers(const std::any& context, req->WriteReply(HTTP_OK, strHex); return true; } - case RetFormat::JSON: { + case RESTResponseFormat::JSON: { UniValue jsonHeaders(UniValue::VARR); for (const CBlockIndex *pindex : headers) { jsonHeaders.push_back(blockheaderToJSON(tip, pindex)); @@ -276,19 +282,19 @@ static bool rest_block(const std::any& context, if (!CheckWarmup(req)) return false; std::string hashStr; - const RetFormat rf = ParseDataFormat(hashStr, strURIPart); + const RESTResponseFormat rf = ParseDataFormat(hashStr, strURIPart); uint256 hash; if (!ParseHashStr(hashStr, hash)) return RESTERR(req, HTTP_BAD_REQUEST, "Invalid hash: " + hashStr); CBlock block; - CBlockIndex* pblockindex = nullptr; - CBlockIndex* tip = nullptr; + const CBlockIndex* pblockindex = nullptr; + const CBlockIndex* tip = nullptr; + ChainstateManager* maybe_chainman = GetChainman(context, req); + if (!maybe_chainman) return false; + ChainstateManager& chainman = *maybe_chainman; { - ChainstateManager* maybe_chainman = GetChainman(context, req); - if (!maybe_chainman) return false; - ChainstateManager& chainman = *maybe_chainman; LOCK(cs_main); tip = chainman.ActiveChain().Tip(); pblockindex = chainman.m_blockman.LookupBlockIndex(hash); @@ -296,15 +302,15 @@ static bool rest_block(const std::any& context, return RESTERR(req, HTTP_NOT_FOUND, hashStr + " not found"); } - if (IsBlockPruned(pblockindex)) + if (chainman.m_blockman.IsBlockPruned(pblockindex)) return RESTERR(req, HTTP_NOT_FOUND, hashStr + " not available (pruned data)"); - if (!ReadBlockFromDisk(block, pblockindex, Params().GetConsensus())) + if (!ReadBlockFromDisk(block, pblockindex, chainman.GetParams().GetConsensus())) return RESTERR(req, HTTP_NOT_FOUND, hashStr + " not found"); } switch (rf) { - case RetFormat::BINARY: { + case RESTResponseFormat::BINARY: { CDataStream ssBlock(SER_NETWORK, PROTOCOL_VERSION | RPCSerializationFlags()); ssBlock << block; std::string binaryBlock = ssBlock.str(); @@ -313,7 +319,7 @@ static bool rest_block(const std::any& context, return true; } - case RetFormat::HEX: { + case RESTResponseFormat::HEX: { CDataStream ssBlock(SER_NETWORK, PROTOCOL_VERSION | RPCSerializationFlags()); ssBlock << block; std::string strHex = HexStr(ssBlock) + "\n"; @@ -322,8 +328,8 @@ static bool rest_block(const std::any& context, return true; } - case RetFormat::JSON: { - UniValue objBlock = blockToJSON(block, tip, pblockindex, tx_verbosity); + case RESTResponseFormat::JSON: { + UniValue objBlock = blockToJSON(chainman.m_blockman, block, tip, pblockindex, tx_verbosity); std::string strJSON = objBlock.write() + "\n"; req->WriteHeader("Content-Type", "application/json"); req->WriteReply(HTTP_OK, strJSON); @@ -351,17 +357,31 @@ static bool rest_filter_header(const std::any& context, HTTPRequest* req, const if (!CheckWarmup(req)) return false; std::string param; - const RetFormat rf = ParseDataFormat(param, strURIPart); - - std::vector<std::string> uri_parts; - boost::split(uri_parts, param, boost::is_any_of("/")); - if (uri_parts.size() != 3) { - return RESTERR(req, HTTP_BAD_REQUEST, "Invalid URI format. Expected /rest/blockfilterheaders/<filtertype>/<count>/<blockhash>"); + const RESTResponseFormat rf = ParseDataFormat(param, strURIPart); + + std::vector<std::string> uri_parts = SplitString(param, '/'); + std::string raw_count; + std::string raw_blockhash; + if (uri_parts.size() == 3) { + // deprecated path: /rest/blockfilterheaders/<filtertype>/<count>/<blockhash> + raw_blockhash = uri_parts[2]; + raw_count = uri_parts[1]; + } else if (uri_parts.size() == 2) { + // new path with query parameter: /rest/blockfilterheaders/<filtertype>/<blockhash>?count=<count> + raw_blockhash = uri_parts[1]; + raw_count = req->GetQueryParameter("count").value_or("5"); + } else { + return RESTERR(req, HTTP_BAD_REQUEST, "Invalid URI format. Expected /rest/blockfilterheaders/<filtertype>/<blockhash>.<ext>?count=<count>"); + } + + const auto parsed_count{ToIntegral<size_t>(raw_count)}; + if (!parsed_count.has_value() || *parsed_count < 1 || *parsed_count > MAX_REST_HEADERS_RESULTS) { + return RESTERR(req, HTTP_BAD_REQUEST, strprintf("Header count is invalid or out of acceptable range (1-%u): %s", MAX_REST_HEADERS_RESULTS, raw_count)); } uint256 block_hash; - if (!ParseHashStr(uri_parts[2], block_hash)) { - return RESTERR(req, HTTP_BAD_REQUEST, "Invalid hash: " + uri_parts[2]); + if (!ParseHashStr(raw_blockhash, block_hash)) { + return RESTERR(req, HTTP_BAD_REQUEST, "Invalid hash: " + raw_blockhash); } BlockFilterType filtertype; @@ -374,11 +394,6 @@ static bool rest_filter_header(const std::any& context, HTTPRequest* req, const return RESTERR(req, HTTP_BAD_REQUEST, "Index is not enabled for filtertype " + uri_parts[0]); } - const auto parsed_count{ToIntegral<size_t>(uri_parts[1])}; - if (!parsed_count.has_value() || *parsed_count < 1 || *parsed_count > MAX_REST_HEADERS_RESULTS) { - return RESTERR(req, HTTP_BAD_REQUEST, strprintf("Header count is invalid or out of acceptable range (1-%u): %s", MAX_REST_HEADERS_RESULTS, uri_parts[1])); - } - std::vector<const CBlockIndex*> headers; headers.reserve(*parsed_count); { @@ -417,7 +432,7 @@ static bool rest_filter_header(const std::any& context, HTTPRequest* req, const } switch (rf) { - case RetFormat::BINARY: { + case RESTResponseFormat::BINARY: { CDataStream ssHeader{SER_NETWORK, PROTOCOL_VERSION}; for (const uint256& header : filter_headers) { ssHeader << header; @@ -428,7 +443,7 @@ static bool rest_filter_header(const std::any& context, HTTPRequest* req, const req->WriteReply(HTTP_OK, binaryHeader); return true; } - case RetFormat::HEX: { + case RESTResponseFormat::HEX: { CDataStream ssHeader{SER_NETWORK, PROTOCOL_VERSION}; for (const uint256& header : filter_headers) { ssHeader << header; @@ -439,7 +454,7 @@ static bool rest_filter_header(const std::any& context, HTTPRequest* req, const req->WriteReply(HTTP_OK, strHex); return true; } - case RetFormat::JSON: { + case RESTResponseFormat::JSON: { UniValue jsonHeaders(UniValue::VARR); for (const uint256& header : filter_headers) { jsonHeaders.push_back(header.GetHex()); @@ -461,11 +476,10 @@ static bool rest_block_filter(const std::any& context, HTTPRequest* req, const s if (!CheckWarmup(req)) return false; std::string param; - const RetFormat rf = ParseDataFormat(param, strURIPart); + const RESTResponseFormat rf = ParseDataFormat(param, strURIPart); // request is sent over URI scheme /rest/blockfilter/filtertype/blockhash - std::vector<std::string> uri_parts; - boost::split(uri_parts, param, boost::is_any_of("/")); + std::vector<std::string> uri_parts = SplitString(param, '/'); if (uri_parts.size() != 2) { return RESTERR(req, HTTP_BAD_REQUEST, "Invalid URI format. Expected /rest/blockfilter/<filtertype>/<blockhash>"); } @@ -517,7 +531,7 @@ static bool rest_block_filter(const std::any& context, HTTPRequest* req, const s } switch (rf) { - case RetFormat::BINARY: { + case RESTResponseFormat::BINARY: { CDataStream ssResp{SER_NETWORK, PROTOCOL_VERSION}; ssResp << filter; @@ -526,7 +540,7 @@ static bool rest_block_filter(const std::any& context, HTTPRequest* req, const s req->WriteReply(HTTP_OK, binaryResp); return true; } - case RetFormat::HEX: { + case RESTResponseFormat::HEX: { CDataStream ssResp{SER_NETWORK, PROTOCOL_VERSION}; ssResp << filter; @@ -535,7 +549,7 @@ static bool rest_block_filter(const std::any& context, HTTPRequest* req, const s req->WriteReply(HTTP_OK, strHex); return true; } - case RetFormat::JSON: { + case RESTResponseFormat::JSON: { UniValue ret(UniValue::VOBJ); ret.pushKV("filter", HexStr(filter.GetEncodedFilter())); std::string strJSON = ret.write() + "\n"; @@ -557,10 +571,10 @@ static bool rest_chaininfo(const std::any& context, HTTPRequest* req, const std: if (!CheckWarmup(req)) return false; std::string param; - const RetFormat rf = ParseDataFormat(param, strURIPart); + const RESTResponseFormat rf = ParseDataFormat(param, strURIPart); switch (rf) { - case RetFormat::JSON: { + case RESTResponseFormat::JSON: { JSONRPCRequest jsonRequest; jsonRequest.context = context; jsonRequest.params = UniValue(UniValue::VARR); @@ -576,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 RetFormat rf = ParseDataFormat(param, strURIPart); - - switch (rf) { - case RetFormat::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 RetFormat rf = ParseDataFormat(param, strURIPart); switch (rf) { - case RetFormat::JSON: { - UniValue mempoolObject = MempoolToJSON(*mempool, true); + case RESTResponseFormat::JSON: { + 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: { @@ -628,7 +628,7 @@ static bool rest_tx(const std::any& context, HTTPRequest* req, const std::string if (!CheckWarmup(req)) return false; std::string hashStr; - const RetFormat rf = ParseDataFormat(hashStr, strURIPart); + const RESTResponseFormat rf = ParseDataFormat(hashStr, strURIPart); uint256 hash; if (!ParseHashStr(hashStr, hash)) @@ -641,13 +641,13 @@ static bool rest_tx(const std::any& context, HTTPRequest* req, const std::string const NodeContext* const node = GetNodeContext(context, req); if (!node) return false; uint256 hashBlock = uint256(); - const CTransactionRef tx = GetTransaction(/* block_index */ nullptr, node->mempool.get(), hash, Params().GetConsensus(), hashBlock); + const CTransactionRef tx = GetTransaction(/*block_index=*/nullptr, node->mempool.get(), hash, Params().GetConsensus(), hashBlock); if (!tx) { return RESTERR(req, HTTP_NOT_FOUND, hashStr + " not found"); } switch (rf) { - case RetFormat::BINARY: { + case RESTResponseFormat::BINARY: { CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION | RPCSerializationFlags()); ssTx << tx; @@ -657,7 +657,7 @@ static bool rest_tx(const std::any& context, HTTPRequest* req, const std::string return true; } - case RetFormat::HEX: { + case RESTResponseFormat::HEX: { CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION | RPCSerializationFlags()); ssTx << tx; @@ -667,9 +667,9 @@ static bool rest_tx(const std::any& context, HTTPRequest* req, const std::string return true; } - case RetFormat::JSON: { + case RESTResponseFormat::JSON: { UniValue objTx(UniValue::VOBJ); - TxToUniv(*tx, hashBlock, objTx); + TxToUniv(*tx, /*block_hash=*/hashBlock, /*entry=*/ objTx); std::string strJSON = objTx.write() + "\n"; req->WriteHeader("Content-Type", "application/json"); req->WriteReply(HTTP_OK, strJSON); @@ -687,13 +687,13 @@ static bool rest_getutxos(const std::any& context, HTTPRequest* req, const std:: if (!CheckWarmup(req)) return false; std::string param; - const RetFormat rf = ParseDataFormat(param, strURIPart); + const RESTResponseFormat rf = ParseDataFormat(param, strURIPart); std::vector<std::string> uriParts; if (param.length() > 1) { std::string strUriParams = param.substr(1); - boost::split(uriParts, strUriParams, boost::is_any_of("/")); + uriParts = SplitString(strUriParams, '/'); } // throw exception in case of an empty request @@ -734,14 +734,14 @@ static bool rest_getutxos(const std::any& context, HTTPRequest* req, const std:: } switch (rf) { - case RetFormat::HEX: { + case RESTResponseFormat::HEX: { // convert hex to bin, continue then with bin part std::vector<unsigned char> strRequestV = ParseHex(strRequestMutable); strRequestMutable.assign(strRequestV.begin(), strRequestV.end()); [[fallthrough]]; } - case RetFormat::BINARY: { + case RESTResponseFormat::BINARY: { try { //deserialize only if user sent a request if (strRequestMutable.size() > 0) @@ -761,7 +761,7 @@ static bool rest_getutxos(const std::any& context, HTTPRequest* req, const std:: break; } - case RetFormat::JSON: { + case RESTResponseFormat::JSON: { if (!fInputParsed) return RESTERR(req, HTTP_BAD_REQUEST, "Error: empty request"); break; @@ -784,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) { @@ -801,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) { @@ -815,11 +819,11 @@ static bool rest_getutxos(const std::any& context, HTTPRequest* req, const std:: } switch (rf) { - case RetFormat::BINARY: { + case RESTResponseFormat::BINARY: { // 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"); @@ -827,9 +831,9 @@ static bool rest_getutxos(const std::any& context, HTTPRequest* req, const std:: return true; } - case RetFormat::HEX: { + 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"); @@ -837,13 +841,13 @@ static bool rest_getutxos(const std::any& context, HTTPRequest* req, const std:: return true; } - case RetFormat::JSON: { + case RESTResponseFormat::JSON: { UniValue objGetUTXOResponse(UniValue::VOBJ); // 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); @@ -854,7 +858,7 @@ static bool rest_getutxos(const std::any& context, HTTPRequest* req, const std:: // include the script in a json output UniValue o(UniValue::VOBJ); - ScriptPubKeyToUniv(coin.out.scriptPubKey, o, true); + ScriptToUniv(coin.out.scriptPubKey, /*out=*/o, /*include_hex=*/true, /*include_address=*/true); utxo.pushKV("scriptPubKey", o); utxos.push_back(utxo); } @@ -877,7 +881,7 @@ static bool rest_blockhash_by_height(const std::any& context, HTTPRequest* req, { if (!CheckWarmup(req)) return false; std::string height_str; - const RetFormat rf = ParseDataFormat(height_str, str_uri_part); + const RESTResponseFormat rf = ParseDataFormat(height_str, str_uri_part); int32_t blockheight = -1; // Initialization done only to prevent valgrind false positive, see https://github.com/bitcoin/bitcoin/pull/18785 if (!ParseInt32(height_str, &blockheight) || blockheight < 0) { @@ -897,19 +901,19 @@ static bool rest_blockhash_by_height(const std::any& context, HTTPRequest* req, pblockindex = active_chain[blockheight]; } switch (rf) { - case RetFormat::BINARY: { + case RESTResponseFormat::BINARY: { CDataStream ss_blockhash(SER_NETWORK, PROTOCOL_VERSION); ss_blockhash << pblockindex->GetBlockHash(); req->WriteHeader("Content-Type", "application/octet-stream"); req->WriteReply(HTTP_OK, ss_blockhash.str()); return true; } - case RetFormat::HEX: { + case RESTResponseFormat::HEX: { req->WriteHeader("Content-Type", "text/plain"); req->WriteReply(HTTP_OK, pblockindex->GetBlockHash().GetHex() + "\n"); return true; } - case RetFormat::JSON: { + case RESTResponseFormat::JSON: { req->WriteHeader("Content-Type", "application/json"); UniValue resp = UniValue(UniValue::VOBJ); resp.pushKV("blockhash", pblockindex->GetBlockHash().GetHex()); @@ -932,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/rest.h b/src/rest.h new file mode 100644 index 0000000000..49b1c333d0 --- /dev/null +++ b/src/rest.h @@ -0,0 +1,28 @@ +// 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. + +#ifndef BITCOIN_REST_H +#define BITCOIN_REST_H + +#include <string> + +enum class RESTResponseFormat { + UNDEF, + BINARY, + HEX, + JSON, +}; + +/** + * Parse a URI to get the data format and URI without data format + * and query string. + * + * @param[out] param The strReq without the data format string and + * without the query string (if any). + * @param[in] strReq The URI to be parsed. + * @return RESTResponseFormat that was parsed from the URI. + */ +RESTResponseFormat ParseDataFormat(std::string& param, const std::string& strReq); + +#endif // BITCOIN_REST_H diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp index 9817c80cbd..8f116a05ef 100644 --- a/src/rpc/blockchain.cpp +++ b/src/rpc/blockchain.cpp @@ -19,17 +19,13 @@ #include <hash.h> #include <index/blockfilterindex.h> #include <index/coinstatsindex.h> +#include <kernel/coinstats.h> #include <logging/timer.h> #include <net.h> #include <net_processing.h> #include <node/blockstorage.h> -#include <node/coinstats.h> #include <node/context.h> #include <node/utxo_snapshot.h> -#include <policy/feerate.h> -#include <policy/fees.h> -#include <policy/policy.h> -#include <policy/rbf.h> #include <primitives/transaction.h> #include <rpc/server.h> #include <rpc/server_util.h> @@ -40,8 +36,10 @@ #include <txdb.h> #include <txmempool.h> #include <undo.h> +#include <univalue.h> +#include <util/check.h> #include <util/strencodings.h> -#include <util/string.h> +#include <util/system.h> #include <util/translation.h> #include <validation.h> #include <validationinterface.h> @@ -50,17 +48,14 @@ #include <stdint.h> -#include <univalue.h> - #include <condition_variable> #include <memory> #include <mutex> +using kernel::CCoinsStats; +using kernel::CoinStatsHashType; + using node::BlockManager; -using node::CCoinsStats; -using node::CoinStatsHashType; -using node::GetUTXOStats; -using node::IsBlockPruned; using node::NodeContext; using node::ReadBlockFromDisk; using node::SnapshotMetadata; @@ -72,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); @@ -110,12 +105,13 @@ static int ComputeNextBlockAndDepth(const CBlockIndex* tip, const CBlockIndex* b return blockindex == tip ? 1 : -1; } -CBlockIndex* ParseHashOrHeight(const UniValue& param, ChainstateManager& chainman) { +static const CBlockIndex* ParseHashOrHeight(const UniValue& param, ChainstateManager& chainman) +{ LOCK(::cs_main); CChain& active_chain = chainman.ActiveChain(); if (param.isNum()) { - const int height{param.get_int()}; + const int height{param.getInt<int>()}; if (height < 0) { throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Target block height %d is negative", height)); } @@ -127,7 +123,7 @@ CBlockIndex* ParseHashOrHeight(const UniValue& param, ChainstateManager& chainma return active_chain[height]; } else { const uint256 hash{ParseHashV(param, "hash_or_height")}; - CBlockIndex* pindex = chainman.m_blockman.LookupBlockIndex(hash); + const CBlockIndex* pindex = chainman.m_blockman.LookupBlockIndex(hash); if (!pindex) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found"); @@ -166,7 +162,7 @@ UniValue blockheaderToJSON(const CBlockIndex* tip, const CBlockIndex* blockindex return result; } -UniValue blockToJSON(const CBlock& block, const CBlockIndex* tip, const CBlockIndex* blockindex, TxVerbosity verbosity) +UniValue blockToJSON(BlockManager& blockman, const CBlock& block, const CBlockIndex* tip, const CBlockIndex* blockindex, TxVerbosity verbosity) { UniValue result = blockheaderToJSON(tip, blockindex); @@ -185,14 +181,14 @@ UniValue blockToJSON(const CBlock& block, const CBlockIndex* tip, const CBlockIn case TxVerbosity::SHOW_DETAILS: case TxVerbosity::SHOW_DETAILS_AND_PREVOUT: CBlockUndo blockUndo; - const bool have_undo{WITH_LOCK(::cs_main, return !IsBlockPruned(blockindex) && UndoReadFromDisk(blockUndo, blockindex))}; + const bool have_undo{WITH_LOCK(::cs_main, return !blockman.IsBlockPruned(blockindex) && UndoReadFromDisk(blockUndo, blockindex))}; for (size_t i = 0; i < block.vtx.size(); ++i) { const CTransactionRef& tx = block.vtx.at(i); // coinbase transaction (i.e. i == 0) doesn't have undo data const CTxUndo* txundo = (have_undo && i > 0) ? &blockUndo.vtxundo.at(i - 1) : nullptr; UniValue objTx(UniValue::VOBJ); - TxToUniv(*tx, uint256(), objTx, true, RPCSerializationFlags(), txundo, verbosity); + TxToUniv(*tx, /*block_hash=*/uint256(), /*entry=*/objTx, /*include_hex=*/true, RPCSerializationFlags(), txundo, verbosity); txs.push_back(objTx); } break; @@ -276,7 +272,7 @@ static RPCHelpMan waitfornewblock() { int timeout = 0; if (!request.params[0].isNull()) - timeout = request.params[0].get_int(); + timeout = request.params[0].getInt<int>(); CUpdatedBlock block; { @@ -322,7 +318,7 @@ static RPCHelpMan waitforblock() uint256 hash(ParseHashV(request.params[0], "blockhash")); if (!request.params[1].isNull()) - timeout = request.params[1].get_int(); + timeout = request.params[1].getInt<int>(); CUpdatedBlock block; { @@ -366,10 +362,10 @@ static RPCHelpMan waitforblockheight() { int timeout = 0; - int height = request.params[0].get_int(); + int height = request.params[0].getInt<int>(); if (!request.params[1].isNull()) - timeout = request.params[1].get_int(); + timeout = request.params[1].getInt<int>(); CUpdatedBlock block; { @@ -401,7 +397,7 @@ static RPCHelpMan syncwithvalidationinterfacequeue() [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { SyncWithValidationInterfaceQueue(); - return NullUniValue; + return UniValue::VNULL; }, }; } @@ -426,366 +422,6 @@ static RPCHelpMan getdifficulty() }; } -static std::vector<RPCResult> MempoolEntryDescription() { return { - RPCResult{RPCResult::Type::NUM, "vsize", "virtual transaction size as defined in BIP 141. This is different from actual serialized size for witness transactions as witness data is discounted."}, - RPCResult{RPCResult::Type::NUM, "weight", "transaction weight as defined in BIP 141."}, - RPCResult{RPCResult::Type::STR_AMOUNT, "fee", /*optional=*/true, - "transaction fee, denominated in " + CURRENCY_UNIT + " (DEPRECATED, returned only if config option -deprecatedrpc=fees is passed)"}, - RPCResult{RPCResult::Type::STR_AMOUNT, "modifiedfee", /*optional=*/true, - "transaction fee with fee deltas used for mining priority, denominated in " + CURRENCY_UNIT + - " (DEPRECATED, returned only if config option -deprecatedrpc=fees is passed)"}, - RPCResult{RPCResult::Type::NUM_TIME, "time", "local time transaction entered pool in seconds since 1 Jan 1970 GMT"}, - RPCResult{RPCResult::Type::NUM, "height", "block height when transaction entered pool"}, - RPCResult{RPCResult::Type::NUM, "descendantcount", "number of in-mempool descendant transactions (including this one)"}, - RPCResult{RPCResult::Type::NUM, "descendantsize", "virtual transaction size of in-mempool descendants (including this one)"}, - RPCResult{RPCResult::Type::STR_AMOUNT, "descendantfees", /*optional=*/true, - "transaction fees of in-mempool descendants (including this one) with fee deltas used for mining priority, denominated in " + - CURRENCY_ATOM + "s (DEPRECATED, returned only if config option -deprecatedrpc=fees is passed)"}, - RPCResult{RPCResult::Type::NUM, "ancestorcount", "number of in-mempool ancestor transactions (including this one)"}, - RPCResult{RPCResult::Type::NUM, "ancestorsize", "virtual transaction size of in-mempool ancestors (including this one)"}, - RPCResult{RPCResult::Type::STR_AMOUNT, "ancestorfees", /*optional=*/true, - "transaction fees of in-mempool ancestors (including this one) with fee deltas used for mining priority, denominated in " + - CURRENCY_ATOM + "s (DEPRECATED, returned only if config option -deprecatedrpc=fees is passed)"}, - RPCResult{RPCResult::Type::STR_HEX, "wtxid", "hash of serialized transaction, including witness data"}, - RPCResult{RPCResult::Type::OBJ, "fees", "", - { - RPCResult{RPCResult::Type::STR_AMOUNT, "base", "transaction fee, denominated in " + CURRENCY_UNIT}, - RPCResult{RPCResult::Type::STR_AMOUNT, "modified", "transaction fee with fee deltas used for mining priority, denominated in " + CURRENCY_UNIT}, - RPCResult{RPCResult::Type::STR_AMOUNT, "ancestor", "transaction fees of in-mempool ancestors (including this one) with fee deltas used for mining priority, denominated in " + CURRENCY_UNIT}, - RPCResult{RPCResult::Type::STR_AMOUNT, "descendant", "transaction fees of in-mempool descendants (including this one) with fee deltas used for mining priority, denominated in " + CURRENCY_UNIT}, - }}, - RPCResult{RPCResult::Type::ARR, "depends", "unconfirmed transactions used as inputs for this transaction", - {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, "unbroadcast", "Whether this transaction is currently unbroadcast (initial broadcast not yet acknowledged by any peers)"}, -};} - -static void entryToJSON(const CTxMemPool& pool, UniValue& info, const CTxMemPoolEntry& e) EXCLUSIVE_LOCKS_REQUIRED(pool.cs) -{ - AssertLockHeld(pool.cs); - - info.pushKV("vsize", (int)e.GetTxSize()); - info.pushKV("weight", (int)e.GetTxWeight()); - // TODO: top-level fee fields are deprecated. deprecated_fee_fields_enabled blocks should be removed in v24 - const bool deprecated_fee_fields_enabled{IsDeprecatedRPCEnabled("fees")}; - if (deprecated_fee_fields_enabled) { - info.pushKV("fee", ValueFromAmount(e.GetFee())); - info.pushKV("modifiedfee", ValueFromAmount(e.GetModifiedFee())); - } - info.pushKV("time", count_seconds(e.GetTime())); - info.pushKV("height", (int)e.GetHeight()); - info.pushKV("descendantcount", e.GetCountWithDescendants()); - info.pushKV("descendantsize", e.GetSizeWithDescendants()); - if (deprecated_fee_fields_enabled) { - info.pushKV("descendantfees", e.GetModFeesWithDescendants()); - } - info.pushKV("ancestorcount", e.GetCountWithAncestors()); - info.pushKV("ancestorsize", e.GetSizeWithAncestors()); - if (deprecated_fee_fields_enabled) { - info.pushKV("ancestorfees", e.GetModFeesWithAncestors()); - } - info.pushKV("wtxid", pool.vTxHashes[e.vTxHashesIdx].first.ToString()); - - UniValue fees(UniValue::VOBJ); - fees.pushKV("base", ValueFromAmount(e.GetFee())); - fees.pushKV("modified", ValueFromAmount(e.GetModifiedFee())); - fees.pushKV("ancestor", ValueFromAmount(e.GetModFeesWithAncestors())); - fees.pushKV("descendant", ValueFromAmount(e.GetModFeesWithDescendants())); - info.pushKV("fees", fees); - - const CTransaction& tx = e.GetTx(); - std::set<std::string> setDepends; - for (const CTxIn& txin : tx.vin) - { - if (pool.exists(GenTxid::Txid(txin.prevout.hash))) - setDepends.insert(txin.prevout.hash.ToString()); - } - - UniValue depends(UniValue::VARR); - for (const std::string& dep : setDepends) - { - depends.push_back(dep); - } - - info.pushKV("depends", depends); - - UniValue spent(UniValue::VARR); - const CTxMemPool::txiter& it = pool.mapTx.find(tx.GetHash()); - const CTxMemPoolEntry::Children& children = it->GetMemPoolChildrenConst(); - for (const CTxMemPoolEntry& child : children) { - spent.push_back(child.GetTx().GetHash().ToString()); - } - - info.pushKV("spentby", spent); - - // Add opt-in RBF status - bool rbfStatus = false; - RBFTransactionState rbfState = IsRBFOptIn(tx, pool); - if (rbfState == RBFTransactionState::UNKNOWN) { - throw JSONRPCError(RPC_MISC_ERROR, "Transaction is not in mempool"); - } else if (rbfState == RBFTransactionState::REPLACEABLE_BIP125) { - rbfStatus = true; - } - - info.pushKV("bip125-replaceable", rbfStatus); - info.pushKV("unbroadcast", pool.IsUnbroadcastTx(tx.GetHash())); -} - -UniValue MempoolToJSON(const CTxMemPool& pool, bool verbose, bool include_mempool_sequence) -{ - if (verbose) { - if (include_mempool_sequence) { - throw JSONRPCError(RPC_INVALID_PARAMETER, "Verbose results cannot contain mempool sequence values."); - } - LOCK(pool.cs); - UniValue o(UniValue::VOBJ); - for (const CTxMemPoolEntry& e : pool.mapTx) { - const uint256& hash = e.GetTx().GetHash(); - UniValue info(UniValue::VOBJ); - entryToJSON(pool, info, e); - // Mempool has unique entries so there is no advantage in using - // UniValue::pushKV, which checks if the key already exists in O(N). - // UniValue::__pushKV is used instead which currently is O(1). - o.__pushKV(hash.ToString(), info); - } - return o; - } else { - uint64_t mempool_sequence; - std::vector<uint256> vtxid; - { - LOCK(pool.cs); - pool.queryHashes(vtxid); - mempool_sequence = pool.GetSequence(); - } - UniValue a(UniValue::VARR); - for (const uint256& hash : vtxid) - a.push_back(hash.ToString()); - - if (!include_mempool_sequence) { - return a; - } else { - UniValue o(UniValue::VOBJ); - o.pushKV("txids", a); - o.pushKV("mempool_sequence", mempool_sequence); - return o; - } - } -} - -static RPCHelpMan getrawmempool() -{ - return RPCHelpMan{"getrawmempool", - "\nReturns all transaction ids in memory pool as a json array of string transaction ids.\n" - "\nHint: use getmempoolentry to fetch a specific transaction from the mempool.\n", - { - {"verbose", RPCArg::Type::BOOL, RPCArg::Default{false}, "True for a json object, false for array of transaction ids"}, - {"mempool_sequence", RPCArg::Type::BOOL, RPCArg::Default{false}, "If verbose=false, returns a json object with transaction list and mempool sequence number attached."}, - }, - { - RPCResult{"for verbose = false", - RPCResult::Type::ARR, "", "", - { - {RPCResult::Type::STR_HEX, "", "The transaction id"}, - }}, - RPCResult{"for verbose = true", - RPCResult::Type::OBJ_DYN, "", "", - { - {RPCResult::Type::OBJ, "transactionid", "", MempoolEntryDescription()}, - }}, - RPCResult{"for verbose = false and mempool_sequence = true", - RPCResult::Type::OBJ, "", "", - { - {RPCResult::Type::ARR, "txids", "", - { - {RPCResult::Type::STR_HEX, "", "The transaction id"}, - }}, - {RPCResult::Type::NUM, "mempool_sequence", "The mempool sequence value."}, - }}, - }, - RPCExamples{ - HelpExampleCli("getrawmempool", "true") - + HelpExampleRpc("getrawmempool", "true") - }, - [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue -{ - bool fVerbose = false; - if (!request.params[0].isNull()) - fVerbose = request.params[0].get_bool(); - - bool include_mempool_sequence = false; - if (!request.params[1].isNull()) { - include_mempool_sequence = request.params[1].get_bool(); - } - - return MempoolToJSON(EnsureAnyMemPool(request.context), fVerbose, include_mempool_sequence); -}, - }; -} - -static RPCHelpMan getmempoolancestors() -{ - return RPCHelpMan{"getmempoolancestors", - "\nIf txid is in the mempool, returns all in-mempool ancestors.\n", - { - {"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The transaction id (must be in mempool)"}, - {"verbose", RPCArg::Type::BOOL, RPCArg::Default{false}, "True for a json object, false for array of transaction ids"}, - }, - { - RPCResult{"for verbose = false", - RPCResult::Type::ARR, "", "", - {{RPCResult::Type::STR_HEX, "", "The transaction id of an in-mempool ancestor transaction"}}}, - RPCResult{"for verbose = true", - RPCResult::Type::OBJ_DYN, "", "", - { - {RPCResult::Type::OBJ, "transactionid", "", MempoolEntryDescription()}, - }}, - }, - RPCExamples{ - HelpExampleCli("getmempoolancestors", "\"mytxid\"") - + HelpExampleRpc("getmempoolancestors", "\"mytxid\"") - }, - [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue -{ - bool fVerbose = false; - if (!request.params[1].isNull()) - fVerbose = request.params[1].get_bool(); - - uint256 hash = ParseHashV(request.params[0], "parameter 1"); - - const CTxMemPool& mempool = EnsureAnyMemPool(request.context); - LOCK(mempool.cs); - - CTxMemPool::txiter it = mempool.mapTx.find(hash); - if (it == mempool.mapTx.end()) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Transaction not in mempool"); - } - - 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); - - if (!fVerbose) { - UniValue o(UniValue::VARR); - for (CTxMemPool::txiter ancestorIt : setAncestors) { - o.push_back(ancestorIt->GetTx().GetHash().ToString()); - } - return o; - } else { - UniValue o(UniValue::VOBJ); - for (CTxMemPool::txiter ancestorIt : setAncestors) { - const CTxMemPoolEntry &e = *ancestorIt; - const uint256& _hash = e.GetTx().GetHash(); - UniValue info(UniValue::VOBJ); - entryToJSON(mempool, info, e); - o.pushKV(_hash.ToString(), info); - } - return o; - } -}, - }; -} - -static RPCHelpMan getmempooldescendants() -{ - return RPCHelpMan{"getmempooldescendants", - "\nIf txid is in the mempool, returns all in-mempool descendants.\n", - { - {"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The transaction id (must be in mempool)"}, - {"verbose", RPCArg::Type::BOOL, RPCArg::Default{false}, "True for a json object, false for array of transaction ids"}, - }, - { - RPCResult{"for verbose = false", - RPCResult::Type::ARR, "", "", - {{RPCResult::Type::STR_HEX, "", "The transaction id of an in-mempool descendant transaction"}}}, - RPCResult{"for verbose = true", - RPCResult::Type::OBJ_DYN, "", "", - { - {RPCResult::Type::OBJ, "transactionid", "", MempoolEntryDescription()}, - }}, - }, - RPCExamples{ - HelpExampleCli("getmempooldescendants", "\"mytxid\"") - + HelpExampleRpc("getmempooldescendants", "\"mytxid\"") - }, - [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue -{ - bool fVerbose = false; - if (!request.params[1].isNull()) - fVerbose = request.params[1].get_bool(); - - uint256 hash = ParseHashV(request.params[0], "parameter 1"); - - const CTxMemPool& mempool = EnsureAnyMemPool(request.context); - LOCK(mempool.cs); - - CTxMemPool::txiter it = mempool.mapTx.find(hash); - if (it == mempool.mapTx.end()) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Transaction not in mempool"); - } - - CTxMemPool::setEntries setDescendants; - mempool.CalculateDescendants(it, setDescendants); - // CTxMemPool::CalculateDescendants will include the given tx - setDescendants.erase(it); - - if (!fVerbose) { - UniValue o(UniValue::VARR); - for (CTxMemPool::txiter descendantIt : setDescendants) { - o.push_back(descendantIt->GetTx().GetHash().ToString()); - } - - return o; - } else { - UniValue o(UniValue::VOBJ); - for (CTxMemPool::txiter descendantIt : setDescendants) { - const CTxMemPoolEntry &e = *descendantIt; - const uint256& _hash = e.GetTx().GetHash(); - UniValue info(UniValue::VOBJ); - entryToJSON(mempool, info, e); - o.pushKV(_hash.ToString(), info); - } - return o; - } -}, - }; -} - -static RPCHelpMan getmempoolentry() -{ - return RPCHelpMan{"getmempoolentry", - "\nReturns mempool data for given transaction\n", - { - {"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The transaction id (must be in mempool)"}, - }, - RPCResult{ - RPCResult::Type::OBJ, "", "", MempoolEntryDescription()}, - RPCExamples{ - HelpExampleCli("getmempoolentry", "\"mytxid\"") - + HelpExampleRpc("getmempoolentry", "\"mytxid\"") - }, - [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue -{ - uint256 hash = ParseHashV(request.params[0], "parameter 1"); - - const CTxMemPool& mempool = EnsureAnyMemPool(request.context); - LOCK(mempool.cs); - - CTxMemPool::txiter it = mempool.mapTx.find(hash); - if (it == mempool.mapTx.end()) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Transaction not in mempool"); - } - - const CTxMemPoolEntry &e = *it; - UniValue info(UniValue::VOBJ); - entryToJSON(mempool, info, e); - return info; -}, - }; -} - static RPCHelpMan getblockfrompeer() { return RPCHelpMan{ @@ -795,7 +431,7 @@ static RPCHelpMan getblockfrompeer() "Subsequent calls for the same block and a new peer will cause the response from the previous peer to be ignored.\n\n" "Returns an empty JSON object if the request was successfully scheduled.", { - {"block_hash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The block hash to try to fetch"}, + {"blockhash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The block hash to try to fetch"}, {"peer_id", RPCArg::Type::NUM, RPCArg::Optional::NO, "The peer to fetch it from (see getpeerinfo for peer IDs)"}, }, RPCResult{RPCResult::Type::OBJ, "", /*optional=*/false, "", {}}, @@ -805,12 +441,17 @@ 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); - const uint256& block_hash{ParseHashV(request.params[0], "block_hash")}; - const NodeId peer_id{request.params[1].get_int64()}; + const uint256& block_hash{ParseHashV(request.params[0], "blockhash")}; + const NodeId peer_id{request.params[1].getInt<int64_t>()}; const CBlockIndex* const index = WITH_LOCK(cs_main, return chainman.m_blockman.LookupBlockIndex(block_hash);); @@ -850,11 +491,11 @@ static RPCHelpMan getblockhash() LOCK(cs_main); const CChain& active_chain = chainman.ActiveChain(); - int nHeight = request.params[0].get_int(); + int nHeight = request.params[0].getInt<int>(); if (nHeight < 0 || nHeight > active_chain.Height()) throw JSONRPCError(RPC_INVALID_PARAMETER, "Block height out of range"); - CBlockIndex* pblockindex = active_chain[nHeight]; + const CBlockIndex* pblockindex = active_chain[nHeight]; return pblockindex->GetBlockHash().GetHex(); }, }; @@ -930,11 +571,11 @@ static RPCHelpMan getblockheader() }; } -static CBlock GetBlockChecked(const CBlockIndex* pblockindex) EXCLUSIVE_LOCKS_REQUIRED(::cs_main) +static CBlock GetBlockChecked(BlockManager& blockman, const CBlockIndex* pblockindex) EXCLUSIVE_LOCKS_REQUIRED(::cs_main) { AssertLockHeld(::cs_main); CBlock block; - if (IsBlockPruned(pblockindex)) { + if (blockman.IsBlockPruned(pblockindex)) { throw JSONRPCError(RPC_MISC_ERROR, "Block not available (pruned data)"); } @@ -948,11 +589,11 @@ static CBlock GetBlockChecked(const CBlockIndex* pblockindex) EXCLUSIVE_LOCKS_RE return block; } -static CBlockUndo GetUndoChecked(const CBlockIndex* pblockindex) EXCLUSIVE_LOCKS_REQUIRED(cs_main) +static CBlockUndo GetUndoChecked(BlockManager& blockman, const CBlockIndex* pblockindex) EXCLUSIVE_LOCKS_REQUIRED(cs_main) { AssertLockHeld(::cs_main); CBlockUndo blockUndo; - if (IsBlockPruned(pblockindex)) { + if (blockman.IsBlockPruned(pblockindex)) { throw JSONRPCError(RPC_MISC_ERROR, "Undo data not available (pruned data)"); } @@ -963,6 +604,30 @@ static CBlockUndo GetUndoChecked(const CBlockIndex* pblockindex) EXCLUSIVE_LOCKS 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", @@ -1022,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, }}, }}, }}, @@ -1059,15 +705,15 @@ static RPCHelpMan getblock() if (request.params[1].isBool()) { verbosity = request.params[1].get_bool() ? 1 : 0; } else { - verbosity = request.params[1].get_int(); + verbosity = request.params[1].getInt<int>(); } } CBlock block; const CBlockIndex* pblockindex; const CBlockIndex* tip; + ChainstateManager& chainman = EnsureAnyChainman(request.context); { - ChainstateManager& chainman = EnsureAnyChainman(request.context); LOCK(cs_main); pblockindex = chainman.m_blockman.LookupBlockIndex(hash); tip = chainman.ActiveChain().Tip(); @@ -1076,7 +722,7 @@ static RPCHelpMan getblock() throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found"); } - block = GetBlockChecked(pblockindex); + block = GetBlockChecked(chainman.m_blockman, pblockindex); } if (verbosity <= 0) @@ -1096,7 +742,7 @@ static RPCHelpMan getblock() tx_verbosity = TxVerbosity::SHOW_DETAILS_AND_PREVOUT; } - return blockToJSON(block, tip, pblockindex, tx_verbosity); + return blockToJSON(chainman.m_blockman, block, tip, pblockindex, tx_verbosity); }, }; } @@ -1124,15 +770,16 @@ static RPCHelpMan pruneblockchain() CChainState& active_chainstate = chainman.ActiveChainstate(); CChain& active_chain = active_chainstate.m_chain; - int heightParam = request.params[0].get_int(); - if (heightParam < 0) + int heightParam = request.params[0].getInt<int>(); + if (heightParam < 0) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Negative block height."); + } // Height value more than a billion is too high to be a block height, and // too low to be a block time (corresponds to timestamp from Sep 2001). if (heightParam > 1000000000) { // Add a 2 hour buffer to include blocks which might have had old timestamps - CBlockIndex* pindex = active_chain.FindEarliestAtLeast(heightParam - TIMESTAMP_WINDOW, 0); + const CBlockIndex* pindex = active_chain.FindEarliestAtLeast(heightParam - TIMESTAMP_WINDOW, 0); if (!pindex) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Could not find block with at least the specified timestamp."); } @@ -1141,22 +788,20 @@ static RPCHelpMan pruneblockchain() unsigned int height = (unsigned int) heightParam; unsigned int chainHeight = (unsigned int) active_chain.Height(); - if (chainHeight < Params().PruneAfterHeight()) + if (chainHeight < chainman.GetParams().PruneAfterHeight()) { throw JSONRPCError(RPC_MISC_ERROR, "Blockchain is too short for pruning."); - else if (height > chainHeight) + } else if (height > chainHeight) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Blockchain is shorter than the attempted prune height."); - else if (height > chainHeight - MIN_BLOCKS_TO_KEEP) { + } else if (height > chainHeight - MIN_BLOCKS_TO_KEEP) { LogPrint(BCLog::RPC, "Attempt to prune blocks close to the tip. Retaining the minimum number of blocks.\n"); height = chainHeight - MIN_BLOCKS_TO_KEEP; } PruneBlockFilesManual(active_chainstate, height); - const CBlockIndex* block = active_chain.Tip(); - CHECK_NONFATAL(block); - while (block->pprev && (block->pprev->nStatus & BLOCK_HAVE_DATA)) { - block = block->pprev; - } - return uint64_t(block->nHeight); + const CBlockIndex& block{*CHECK_NONFATAL(active_chain.Tip())}; + const CBlockIndex* last_block{active_chainstate.m_blockman.GetFirstStoredBlock(block)}; + + return static_cast<int64_t>(last_block->nHeight - 1); }, }; } @@ -1174,6 +819,36 @@ CoinStatsHashType ParseHashType(const std::string& hash_type_input) } } +/** + * Calculate statistics about the unspent transaction output set + * + * @param[in] index_requested Signals if the coinstatsindex should be used (when available). + */ +static std::optional<kernel::CCoinsStats> GetUTXOStats(CCoinsView* view, node::BlockManager& blockman, + kernel::CoinStatsHashType hash_type, + const std::function<void()>& interruption_point = {}, + const CBlockIndex* pindex = nullptr, + bool index_requested = true) +{ + // 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); + } else { + CBlockIndex* block_index = WITH_LOCK(::cs_main, return blockman.LookupBlockIndex(view->GetBestBlock())); + return g_coin_stats_index->LookUpStats(block_index); + } + } + + // If the coinstats index isn't requested or is otherwise not usable, the + // pindex should either be null or equal to the view's best block. This is + // because without the coinstats index we can only get coinstats about the + // best block. + CHECK_NONFATAL(!pindex || pindex->GetBlockHash() == view->GetBestBlock()); + + return kernel::ComputeUTXOStats(hash_type, view, blockman, interruption_point); +} + static RPCHelpMan gettxoutsetinfo() { return RPCHelpMan{"gettxoutsetinfo", @@ -1181,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).", "", {"", "string or numeric"}}, {"use_index", RPCArg::Type::BOOL, RPCArg::Default{true}, "Use coinstatsindex, if available."}, }, RPCResult{ @@ -1217,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)") + @@ -1226,10 +902,9 @@ static RPCHelpMan gettxoutsetinfo() { UniValue ret(UniValue::VOBJ); - CBlockIndex* pindex{nullptr}; + const CBlockIndex* pindex{nullptr}; const CoinStatsHashType hash_type{request.params[0].isNull() ? CoinStatsHashType::HASH_SERIALIZED : ParseHashType(request.params[0].get_str())}; - CCoinsStats stats{hash_type}; - stats.index_requested = request.params[2].isNull() || request.params[2].get_bool(); + bool index_requested = request.params[2].isNull() || request.params[2].get_bool(); NodeContext& node = EnsureAnyNodeContext(request.context); ChainstateManager& chainman = EnsureChainman(node); @@ -1250,14 +925,17 @@ static RPCHelpMan gettxoutsetinfo() throw JSONRPCError(RPC_INVALID_PARAMETER, "Querying specific block heights requires coinstatsindex"); } - if (stats.m_hash_type == CoinStatsHashType::HASH_SERIALIZED) { + if (hash_type == CoinStatsHashType::HASH_SERIALIZED) { 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); } - if (stats.index_requested && g_coin_stats_index) { + if (index_requested && g_coin_stats_index) { if (!g_coin_stats_index->BlockUntilSyncedToCurrentChain()) { const IndexSummary summary{g_coin_stats_index->GetSummary()}; @@ -1269,7 +947,9 @@ static RPCHelpMan gettxoutsetinfo() } } - if (GetUTXOStats(coins_view, *blockman, stats, node.rpc_interruption_point, pindex)) { + const std::optional<CCoinsStats> maybe_stats = GetUTXOStats(coins_view, *blockman, hash_type, node.rpc_interruption_point, pindex, index_requested); + if (maybe_stats.has_value()) { + const CCoinsStats& stats = maybe_stats.value(); ret.pushKV("height", (int64_t)stats.nHeight); ret.pushKV("bestblock", stats.hashBlock.GetHex()); ret.pushKV("txouts", (int64_t)stats.nTransactionOutputs); @@ -1288,10 +968,13 @@ static RPCHelpMan gettxoutsetinfo() } else { ret.pushKV("total_unspendable_amount", ValueFromAmount(stats.total_unspendable_amount)); - CCoinsStats prev_stats{hash_type}; - + CCoinsStats prev_stats{}; if (pindex->nHeight > 0) { - GetUTXOStats(coins_view, *blockman, prev_stats, node.rpc_interruption_point, pindex->pprev); + const std::optional<CCoinsStats> maybe_prev_stats = GetUTXOStats(coins_view, *blockman, hash_type, node.rpc_interruption_point, pindex->pprev, index_requested); + if (!maybe_prev_stats) { + throw JSONRPCError(RPC_INTERNAL_ERROR, "Unable to read UTXO set"); + } + prev_stats = maybe_prev_stats.value(); } UniValue block_info(UniValue::VOBJ); @@ -1333,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)"}, }}, @@ -1359,8 +1042,7 @@ static RPCHelpMan gettxout() UniValue ret(UniValue::VOBJ); uint256 hash(ParseHashV(request.params[0], "txid")); - int n = request.params[1].get_int(); - COutPoint out(hash, n); + COutPoint out{hash, request.params[1].getInt<uint32_t>()}; bool fMempool = true; if (!request.params[2].isNull()) fMempool = request.params[2].get_bool(); @@ -1374,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; } } @@ -1391,7 +1073,7 @@ static RPCHelpMan gettxout() } ret.pushKV("value", ValueFromAmount(coin.out.nValue)); UniValue o(UniValue::VOBJ); - ScriptPubKeyToUniv(coin.out.scriptPubKey, o, true); + ScriptToUniv(coin.out.scriptPubKey, /*out=*/o, /*include_hex=*/true, /*include_address=*/true); ret.pushKV("scriptPubKey", o); ret.pushKV("coinbase", (bool)coin.fCoinBase); @@ -1417,39 +1099,39 @@ static RPCHelpMan verifychain() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - const int check_level{request.params[0].isNull() ? DEFAULT_CHECKLEVEL : request.params[0].get_int()}; - const int check_depth{request.params[1].isNull() ? DEFAULT_CHECKBLOCKS : request.params[1].get_int()}; + const int check_level{request.params[0].isNull() ? DEFAULT_CHECKLEVEL : request.params[0].getInt<int>()}; + const int check_depth{request.params[1].isNull() ? DEFAULT_CHECKBLOCKS : request.params[1].getInt<int>()}; ChainstateManager& chainman = EnsureAnyChainman(request.context); LOCK(cs_main); CChainState& active_chainstate = chainman.ActiveChainstate(); return CVerifyDB().VerifyDB( - active_chainstate, Params().GetConsensus(), active_chainstate.CoinsTip(), check_level, check_depth); + active_chainstate, chainman.GetParams().GetConsensus(), active_chainstate.CoinsTip(), check_level, check_depth); }, }; } -static void SoftForkDescPushBack(const CBlockIndex* blockindex, UniValue& softforks, const Consensus::Params& params, Consensus::BuriedDeployment dep) +static void SoftForkDescPushBack(const CBlockIndex* blockindex, UniValue& softforks, const ChainstateManager& chainman, Consensus::BuriedDeployment dep) { // For buried deployments. - if (!DeploymentEnabled(params, dep)) return; + if (!DeploymentEnabled(chainman, dep)) return; UniValue rv(UniValue::VOBJ); rv.pushKV("type", "buried"); // getdeploymentinfo reports the softfork as active from when the chain height is // one below the activation height - rv.pushKV("active", DeploymentActiveAfter(blockindex, params, dep)); - rv.pushKV("height", params.DeploymentHeight(dep)); + rv.pushKV("active", DeploymentActiveAfter(blockindex, chainman, dep)); + rv.pushKV("height", chainman.GetConsensus().DeploymentHeight(dep)); softforks.pushKV(DeploymentName(dep), rv); } -static void SoftForkDescPushBack(const CBlockIndex* blockindex, UniValue& softforks, const Consensus::Params& consensusParams, Consensus::DeploymentPos id) +static void SoftForkDescPushBack(const CBlockIndex* blockindex, UniValue& softforks, const ChainstateManager& chainman, Consensus::DeploymentPos id) { // For BIP9 deployments. - if (!DeploymentEnabled(consensusParams, id)) return; + if (!DeploymentEnabled(chainman, id)) return; if (blockindex == nullptr) return; auto get_state_name = [](const ThresholdState state) -> std::string { @@ -1465,29 +1147,29 @@ static void SoftForkDescPushBack(const CBlockIndex* blockindex, UniValue& softfo UniValue bip9(UniValue::VOBJ); - const ThresholdState next_state = g_versionbitscache.State(blockindex, consensusParams, id); - const ThresholdState current_state = g_versionbitscache.State(blockindex->pprev, consensusParams, id); + const ThresholdState next_state = chainman.m_versionbitscache.State(blockindex, chainman.GetConsensus(), id); + const ThresholdState current_state = chainman.m_versionbitscache.State(blockindex->pprev, chainman.GetConsensus(), id); const bool has_signal = (ThresholdState::STARTED == current_state || ThresholdState::LOCKED_IN == current_state); // BIP9 parameters if (has_signal) { - bip9.pushKV("bit", consensusParams.vDeployments[id].bit); + bip9.pushKV("bit", chainman.GetConsensus().vDeployments[id].bit); } - bip9.pushKV("start_time", consensusParams.vDeployments[id].nStartTime); - bip9.pushKV("timeout", consensusParams.vDeployments[id].nTimeout); - bip9.pushKV("min_activation_height", consensusParams.vDeployments[id].min_activation_height); + bip9.pushKV("start_time", chainman.GetConsensus().vDeployments[id].nStartTime); + bip9.pushKV("timeout", chainman.GetConsensus().vDeployments[id].nTimeout); + bip9.pushKV("min_activation_height", chainman.GetConsensus().vDeployments[id].min_activation_height); // BIP9 status bip9.pushKV("status", get_state_name(current_state)); - bip9.pushKV("since", g_versionbitscache.StateSinceHeight(blockindex->pprev, consensusParams, id)); - bip9.pushKV("status-next", get_state_name(next_state)); + bip9.pushKV("since", chainman.m_versionbitscache.StateSinceHeight(blockindex->pprev, chainman.GetConsensus(), id)); + bip9.pushKV("status_next", get_state_name(next_state)); // BIP9 signalling status, if applicable if (has_signal) { UniValue statsUV(UniValue::VOBJ); std::vector<bool> signals; - BIP9Stats statsStruct = g_versionbitscache.Statistics(blockindex, consensusParams, id, &signals); + BIP9Stats statsStruct = chainman.m_versionbitscache.Statistics(blockindex, chainman.GetConsensus(), id, &signals); statsUV.pushKV("period", statsStruct.period); statsUV.pushKV("elapsed", statsStruct.elapsed); statsUV.pushKV("count", statsStruct.count); @@ -1508,7 +1190,7 @@ static void SoftForkDescPushBack(const CBlockIndex* blockindex, UniValue& softfo UniValue rv(UniValue::VOBJ); rv.pushKV("type", "bip9"); if (ThresholdState::ACTIVE == next_state) { - rv.pushKV("height", g_versionbitscache.StateSinceHeight(blockindex, consensusParams, id)); + rv.pushKV("height", chainman.m_versionbitscache.StateSinceHeight(blockindex, chainman.GetConsensus(), id)); } rv.pushKV("active", ThresholdState::ACTIVE == next_state); rv.pushKV("bip9", bip9); @@ -1516,49 +1198,36 @@ static void SoftForkDescPushBack(const CBlockIndex* blockindex, UniValue& softfo softforks.pushKV(DeploymentName(id), rv); } -namespace { -/* TODO: when -deprecatedrpc=softforks is removed, drop these */ -UniValue DeploymentInfo(const CBlockIndex* tip, const Consensus::Params& consensusParams); -extern const std::vector<RPCResult> RPCHelpForDeployment; -} - // used by rest.cpp:rest_chaininfo, so cannot be static RPCHelpMan getblockchaininfo() { - /* TODO: from v24, remove -deprecatedrpc=softforks */ return RPCHelpMan{"getblockchaininfo", - "Returns an object containing various state info regarding blockchain processing.\n", - {}, - RPCResult{ - RPCResult::Type::OBJ, "", "", - { - {RPCResult::Type::STR, "chain", "current network name (main, test, signet, regtest)"}, - {RPCResult::Type::NUM, "blocks", "the height of the most-work fully-validated chain. The genesis block has height 0"}, - {RPCResult::Type::NUM, "headers", "the current number of headers we have validated"}, - {RPCResult::Type::STR, "bestblockhash", "the hash of the currently best block"}, - {RPCResult::Type::NUM, "difficulty", "the current difficulty"}, - {RPCResult::Type::NUM_TIME, "time", "The block time expressed in " + UNIX_EPOCH_TIME}, - {RPCResult::Type::NUM_TIME, "mediantime", "The median block time expressed in " + UNIX_EPOCH_TIME}, - {RPCResult::Type::NUM, "verificationprogress", "estimate of verification progress [0..1]"}, - {RPCResult::Type::BOOL, "initialblockdownload", "(debug information) estimate of whether this node is in Initial Block Download mode"}, - {RPCResult::Type::STR_HEX, "chainwork", "total amount of work in active chain, in hexadecimal"}, - {RPCResult::Type::NUM, "size_on_disk", "the estimated size of the block and undo files on disk"}, - {RPCResult::Type::BOOL, "pruned", "if the blocks are subject to pruning"}, - {RPCResult::Type::NUM, "pruneheight", /*optional=*/true, "lowest-height complete block stored (only present if pruning is enabled)"}, - {RPCResult::Type::BOOL, "automatic_pruning", /*optional=*/true, "whether automatic pruning is enabled (only present if pruning is enabled)"}, - {RPCResult::Type::NUM, "prune_target_size", /*optional=*/true, "the target size used by pruning (only present if automatic pruning is enabled)"}, - {RPCResult::Type::OBJ_DYN, "softforks", "(DEPRECATED, returned only if config option -deprecatedrpc=softforks is passed) status of softforks", - { - {RPCResult::Type::OBJ, "xxxx", "name of the softfork", - RPCHelpForDeployment - }, - }}, - {RPCResult::Type::STR, "warnings", "any network and blockchain warnings"}, - }}, - RPCExamples{ - HelpExampleCli("getblockchaininfo", "") + "Returns an object containing various state info regarding blockchain processing.\n", + {}, + RPCResult{ + RPCResult::Type::OBJ, "", "", + { + {RPCResult::Type::STR, "chain", "current network name (main, test, signet, regtest)"}, + {RPCResult::Type::NUM, "blocks", "the height of the most-work fully-validated chain. The genesis block has height 0"}, + {RPCResult::Type::NUM, "headers", "the current number of headers we have validated"}, + {RPCResult::Type::STR, "bestblockhash", "the hash of the currently best block"}, + {RPCResult::Type::NUM, "difficulty", "the current difficulty"}, + {RPCResult::Type::NUM_TIME, "time", "The block time expressed in " + UNIX_EPOCH_TIME}, + {RPCResult::Type::NUM_TIME, "mediantime", "The median block time expressed in " + UNIX_EPOCH_TIME}, + {RPCResult::Type::NUM, "verificationprogress", "estimate of verification progress [0..1]"}, + {RPCResult::Type::BOOL, "initialblockdownload", "(debug information) estimate of whether this node is in Initial Block Download mode"}, + {RPCResult::Type::STR_HEX, "chainwork", "total amount of work in active chain, in hexadecimal"}, + {RPCResult::Type::NUM, "size_on_disk", "the estimated size of the block and undo files on disk"}, + {RPCResult::Type::BOOL, "pruned", "if the blocks are subject to pruning"}, + {RPCResult::Type::NUM, "pruneheight", /*optional=*/true, "height of the last block pruned, plus one (only present if pruning is enabled)"}, + {RPCResult::Type::BOOL, "automatic_pruning", /*optional=*/true, "whether automatic pruning is enabled (only present if pruning is enabled)"}, + {RPCResult::Type::NUM, "prune_target_size", /*optional=*/true, "the target size used by pruning (only present if automatic pruning is enabled)"}, + {RPCResult::Type::STR, "warnings", "any network and blockchain warnings"}, + }}, + RPCExamples{ + HelpExampleCli("getblockchaininfo", "") + HelpExampleRpc("getblockchaininfo", "") - }, + }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { const ArgsManager& args{EnsureAnyArgsman(request.context)}; @@ -1566,30 +1235,23 @@ RPCHelpMan getblockchaininfo() LOCK(cs_main); CChainState& active_chainstate = chainman.ActiveChainstate(); - const CBlockIndex* tip = active_chainstate.m_chain.Tip(); - CHECK_NONFATAL(tip); - const int height = tip->nHeight; + const CBlockIndex& tip{*CHECK_NONFATAL(active_chainstate.m_chain.Tip())}; + const int height{tip.nHeight}; UniValue obj(UniValue::VOBJ); - obj.pushKV("chain", Params().NetworkIDString()); - obj.pushKV("blocks", height); - obj.pushKV("headers", pindexBestHeader ? pindexBestHeader->nHeight : -1); - obj.pushKV("bestblockhash", tip->GetBlockHash().GetHex()); - obj.pushKV("difficulty", (double)GetDifficulty(tip)); - obj.pushKV("time", (int64_t)tip->nTime); - obj.pushKV("mediantime", (int64_t)tip->GetMedianTimePast()); - obj.pushKV("verificationprogress", GuessVerificationProgress(Params().TxData(), tip)); - obj.pushKV("initialblockdownload", active_chainstate.IsInitialBlockDownload()); - obj.pushKV("chainwork", tip->nChainWork.GetHex()); + obj.pushKV("chain", chainman.GetParams().NetworkIDString()); + obj.pushKV("blocks", height); + obj.pushKV("headers", chainman.m_best_header ? chainman.m_best_header->nHeight : -1); + obj.pushKV("bestblockhash", tip.GetBlockHash().GetHex()); + obj.pushKV("difficulty", GetDifficulty(&tip)); + obj.pushKV("time", tip.GetBlockTime()); + obj.pushKV("mediantime", tip.GetMedianTimePast()); + obj.pushKV("verificationprogress", GuessVerificationProgress(chainman.GetParams().TxData(), &tip)); + obj.pushKV("initialblockdownload", active_chainstate.IsInitialBlockDownload()); + obj.pushKV("chainwork", tip.nChainWork.GetHex()); obj.pushKV("size_on_disk", chainman.m_blockman.CalculateCurrentUsage()); - obj.pushKV("pruned", node::fPruneMode); + obj.pushKV("pruned", node::fPruneMode); if (node::fPruneMode) { - const CBlockIndex* block = tip; - CHECK_NONFATAL(block); - while (block->pprev && (block->pprev->nStatus & BLOCK_HAVE_DATA)) { - block = block->pprev; - } - - obj.pushKV("pruneheight", block->nHeight); + obj.pushKV("pruneheight", chainman.m_blockman.GetFirstStoredBlock(tip)->nHeight); // if 0, execution bypasses the whole if block. bool automatic_pruning{args.GetIntArg("-prune", 0) != 1}; @@ -1599,11 +1261,6 @@ RPCHelpMan getblockchaininfo() } } - if (IsDeprecatedRPCEnabled("softforks")) { - const Consensus::Params& consensusParams = Params().GetConsensus(); - obj.pushKV("softforks", DeploymentInfo(tip, consensusParams)); - } - obj.pushKV("warnings", GetWarnings(false).original); return obj; }, @@ -1623,7 +1280,7 @@ const std::vector<RPCResult> RPCHelpForDeployment{ {RPCResult::Type::NUM, "min_activation_height", "minimum height of blocks for which the rules may be enforced"}, {RPCResult::Type::STR, "status", "status of deployment at specified block (one of \"defined\", \"started\", \"locked_in\", \"active\", \"failed\")"}, {RPCResult::Type::NUM, "since", "height of the first block to which the status applies"}, - {RPCResult::Type::STR, "status-next", "status of deployment at the next block"}, + {RPCResult::Type::STR, "status_next", "status of deployment at the next block"}, {RPCResult::Type::OBJ, "statistics", /*optional=*/true, "numeric statistics about signalling for a softfork (only for \"started\" and \"locked_in\" status)", { {RPCResult::Type::NUM, "period", "the length in blocks of the signalling period"}, @@ -1632,20 +1289,20 @@ const std::vector<RPCResult> RPCHelpForDeployment{ {RPCResult::Type::NUM, "count", "the number of blocks with the version bit set in the current period"}, {RPCResult::Type::BOOL, "possible", /*optional=*/true, "returns false if there are not enough blocks left in this period to pass activation threshold (only for \"started\" status)"}, }}, - {RPCResult::Type::STR, "signalling", "indicates blocks that signalled with a # and blocks that did not with a -"}, + {RPCResult::Type::STR, "signalling", /*optional=*/true, "indicates blocks that signalled with a # and blocks that did not with a -"}, }}, }; -UniValue DeploymentInfo(const CBlockIndex* blockindex, const Consensus::Params& consensusParams) +UniValue DeploymentInfo(const CBlockIndex* blockindex, const ChainstateManager& chainman) { UniValue softforks(UniValue::VOBJ); - SoftForkDescPushBack(blockindex, softforks, consensusParams, Consensus::DEPLOYMENT_HEIGHTINCB); - SoftForkDescPushBack(blockindex, softforks, consensusParams, Consensus::DEPLOYMENT_DERSIG); - SoftForkDescPushBack(blockindex, softforks, consensusParams, Consensus::DEPLOYMENT_CLTV); - SoftForkDescPushBack(blockindex, softforks, consensusParams, Consensus::DEPLOYMENT_CSV); - SoftForkDescPushBack(blockindex, softforks, consensusParams, Consensus::DEPLOYMENT_SEGWIT); - SoftForkDescPushBack(blockindex, softforks, consensusParams, Consensus::DEPLOYMENT_TESTDUMMY); - SoftForkDescPushBack(blockindex, softforks, consensusParams, Consensus::DEPLOYMENT_TAPROOT); + SoftForkDescPushBack(blockindex, softforks, chainman, Consensus::DEPLOYMENT_HEIGHTINCB); + SoftForkDescPushBack(blockindex, softforks, chainman, Consensus::DEPLOYMENT_DERSIG); + SoftForkDescPushBack(blockindex, softforks, chainman, Consensus::DEPLOYMENT_CLTV); + SoftForkDescPushBack(blockindex, softforks, chainman, Consensus::DEPLOYMENT_CSV); + SoftForkDescPushBack(blockindex, softforks, chainman, Consensus::DEPLOYMENT_SEGWIT); + SoftForkDescPushBack(blockindex, softforks, chainman, Consensus::DEPLOYMENT_TESTDUMMY); + SoftForkDescPushBack(blockindex, softforks, chainman, Consensus::DEPLOYMENT_TAPROOT); return softforks; } } // anon namespace @@ -1661,7 +1318,7 @@ static RPCHelpMan getdeploymentinfo() RPCResult::Type::OBJ, "", "", { {RPCResult::Type::STR, "hash", "requested block hash (or tip)"}, {RPCResult::Type::NUM, "height", "requested block height (or tip)"}, - {RPCResult::Type::OBJ, "deployments", "", { + {RPCResult::Type::OBJ_DYN, "deployments", "", { {RPCResult::Type::OBJ, "xxxx", "name of the deployment", RPCHelpForDeployment} }}, } @@ -1675,8 +1332,7 @@ static RPCHelpMan getdeploymentinfo() const CBlockIndex* blockindex; if (request.params[0].isNull()) { - blockindex = active_chainstate.m_chain.Tip(); - CHECK_NONFATAL(blockindex); + blockindex = CHECK_NONFATAL(active_chainstate.m_chain.Tip()); } else { const uint256 hash(ParseHashV(request.params[0], "blockhash")); blockindex = chainman.m_blockman.LookupBlockIndex(hash); @@ -1685,12 +1341,10 @@ static RPCHelpMan getdeploymentinfo() } } - const Consensus::Params& consensusParams = Params().GetConsensus(); - UniValue deploymentinfo(UniValue::VOBJ); deploymentinfo.pushKV("hash", blockindex->GetBlockHash().ToString()); deploymentinfo.pushKV("height", blockindex->nHeight); - deploymentinfo.pushKV("deployments", DeploymentInfo(blockindex, consensusParams)); + deploymentinfo.pushKV("deployments", DeploymentInfo(blockindex, chainman)); return deploymentinfo; }, }; @@ -1753,10 +1407,10 @@ static RPCHelpMan getchaintips() std::set<const CBlockIndex*> setOrphans; std::set<const CBlockIndex*> setPrevs; - for (const std::pair<const uint256, CBlockIndex*>& item : chainman.BlockIndex()) { - if (!active_chain.Contains(item.second)) { - setOrphans.insert(item.second); - setPrevs.insert(item.second->pprev); + for (const auto& [_, block_index] : chainman.BlockIndex()) { + if (!active_chain.Contains(&block_index)) { + setOrphans.insert(&block_index); + setPrevs.insert(block_index.pprev); } } @@ -1809,53 +1463,6 @@ static RPCHelpMan getchaintips() }; } -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("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())); - size_t maxmempool = gArgs.GetIntArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000; - ret.pushKV("maxmempool", (int64_t) maxmempool); - ret.pushKV("mempoolminfee", ValueFromAmount(std::max(pool.GetMinFee(maxmempool), ::minRelayTxFee).GetFeePerK())); - ret.pushKV("minrelaytxfee", ValueFromAmount(::minRelayTxFee.GetFeePerK())); - ret.pushKV("unbroadcastcount", uint64_t{pool.GetUnbroadcastTxs().size()}); - return ret; -} - -static RPCHelpMan getmempoolinfo() -{ - return RPCHelpMan{"getmempoolinfo", - "\nReturns details on the active state of the TX memory pool.\n", - {}, - RPCResult{ - RPCResult::Type::OBJ, "", "", - { - {RPCResult::Type::BOOL, "loaded", "True if the mempool is fully loaded"}, - {RPCResult::Type::NUM, "size", "Current tx count"}, - {RPCResult::Type::NUM, "bytes", "Sum of all virtual transaction sizes as defined in BIP 141. Differs from actual serialized size because witness data is discounted"}, - {RPCResult::Type::NUM, "usage", "Total memory usage for the mempool"}, - {RPCResult::Type::STR_AMOUNT, "total_fee", "Total fees for the mempool in " + CURRENCY_UNIT + ", ignoring modified fees through prioritisetransaction"}, - {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"} - }}, - RPCExamples{ - HelpExampleCli("getmempoolinfo", "") - + HelpExampleRpc("getmempoolinfo", "") - }, - [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue -{ - return MempoolInfoToJSON(EnsureAnyMemPool(request.context)); -}, - }; -} - static RPCHelpMan preciousblock() { return RPCHelpMan{"preciousblock", @@ -1891,7 +1498,7 @@ static RPCHelpMan preciousblock() throw JSONRPCError(RPC_DATABASE_ERROR, state.ToString()); } - return NullUniValue; + return UniValue::VNULL; }, }; } @@ -1932,7 +1539,7 @@ static RPCHelpMan invalidateblock() throw JSONRPCError(RPC_DATABASE_ERROR, state.ToString()); } - return NullUniValue; + return UniValue::VNULL; }, }; } @@ -1972,7 +1579,7 @@ static RPCHelpMan reconsiderblock() throw JSONRPCError(RPC_DATABASE_ERROR, state.ToString()); } - return NullUniValue; + return UniValue::VNULL; }, }; } @@ -2005,7 +1612,7 @@ static RPCHelpMan getchaintxstats() { ChainstateManager& chainman = EnsureAnyChainman(request.context); const CBlockIndex* pindex; - int blockcount = 30 * 24 * 60 * 60 / Params().GetConsensus().nPowTargetSpacing; // By default: 1 month + int blockcount = 30 * 24 * 60 * 60 / chainman.GetParams().GetConsensus().nPowTargetSpacing; // By default: 1 month if (request.params[1].isNull()) { LOCK(cs_main); @@ -2027,16 +1634,16 @@ static RPCHelpMan getchaintxstats() if (request.params[0].isNull()) { blockcount = std::max(0, std::min(blockcount, pindex->nHeight - 1)); } else { - blockcount = request.params[0].get_int(); + blockcount = request.params[0].getInt<int>(); if (blockcount < 0 || (blockcount > 0 && blockcount >= pindex->nHeight)) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid block count: should be between 0 and the block's height - 1"); } } - const CBlockIndex* pindexPast = pindex->GetAncestor(pindex->nHeight - blockcount); - int nTimeDiff = pindex->GetMedianTimePast() - pindexPast->GetMedianTimePast(); - int nTxDiff = pindex->nChainTx - pindexPast->nChainTx; + const CBlockIndex& past_block{*CHECK_NONFATAL(pindex->GetAncestor(pindex->nHeight - blockcount))}; + const int64_t nTimeDiff{pindex->GetMedianTimePast() - past_block.GetMedianTimePast()}; + const int nTxDiff = pindex->nChainTx - past_block.nChainTx; UniValue ret(UniValue::VOBJ); ret.pushKV("time", (int64_t)pindex->nTime); @@ -2177,8 +1784,7 @@ static RPCHelpMan getblockstats() { ChainstateManager& chainman = EnsureAnyChainman(request.context); LOCK(cs_main); - CBlockIndex* pindex{ParseHashOrHeight(request.params[0], chainman)}; - CHECK_NONFATAL(pindex != nullptr); + const CBlockIndex& pindex{*CHECK_NONFATAL(ParseHashOrHeight(request.params[0], chainman))}; std::set<std::string> stats; if (!request.params[1].isNull()) { @@ -2189,8 +1795,8 @@ static RPCHelpMan getblockstats() } } - const CBlock block = GetBlockChecked(pindex); - const CBlockUndo blockUndo = GetUndoChecked(pindex); + const CBlock& block = GetBlockChecked(chainman.m_blockman, &pindex); + const CBlockUndo& blockUndo = GetUndoChecked(chainman.m_blockman, &pindex); const bool do_all = stats.size() == 0; // Calculate everything if nothing selected (default) const bool do_mediantxsize = do_all || stats.count("mediantxsize") != 0; @@ -2308,25 +1914,25 @@ static RPCHelpMan getblockstats() ret_all.pushKV("avgfee", (block.vtx.size() > 1) ? totalfee / (block.vtx.size() - 1) : 0); ret_all.pushKV("avgfeerate", total_weight ? (totalfee * WITNESS_SCALE_FACTOR) / total_weight : 0); // Unit: sat/vbyte ret_all.pushKV("avgtxsize", (block.vtx.size() > 1) ? total_size / (block.vtx.size() - 1) : 0); - ret_all.pushKV("blockhash", pindex->GetBlockHash().GetHex()); + ret_all.pushKV("blockhash", pindex.GetBlockHash().GetHex()); ret_all.pushKV("feerate_percentiles", feerates_res); - ret_all.pushKV("height", (int64_t)pindex->nHeight); + ret_all.pushKV("height", (int64_t)pindex.nHeight); ret_all.pushKV("ins", inputs); ret_all.pushKV("maxfee", maxfee); ret_all.pushKV("maxfeerate", maxfeerate); ret_all.pushKV("maxtxsize", maxtxsize); ret_all.pushKV("medianfee", CalculateTruncatedMedian(fee_array)); - ret_all.pushKV("mediantime", pindex->GetMedianTimePast()); + ret_all.pushKV("mediantime", pindex.GetMedianTimePast()); ret_all.pushKV("mediantxsize", CalculateTruncatedMedian(txsize_array)); ret_all.pushKV("minfee", (minfee == MAX_MONEY) ? 0 : minfee); ret_all.pushKV("minfeerate", (minfeerate == MAX_MONEY) ? 0 : minfeerate); ret_all.pushKV("mintxsize", mintxsize == MAX_BLOCK_SERIALIZED_SIZE ? 0 : mintxsize); ret_all.pushKV("outs", outputs); - ret_all.pushKV("subsidy", GetBlockSubsidy(pindex->nHeight, Params().GetConsensus())); + ret_all.pushKV("subsidy", GetBlockSubsidy(pindex.nHeight, chainman.GetParams().GetConsensus())); ret_all.pushKV("swtotal_size", swtotal_size); ret_all.pushKV("swtotal_weight", swtotal_weight); ret_all.pushKV("swtxs", swtxs); - ret_all.pushKV("time", pindex->GetBlockTime()); + ret_all.pushKV("time", pindex.GetBlockTime()); ret_all.pushKV("total_out", total_out); ret_all.pushKV("total_size", total_size); ret_all.pushKV("total_weight", total_weight); @@ -2352,41 +1958,6 @@ static RPCHelpMan getblockstats() }; } -static RPCHelpMan savemempool() -{ - return RPCHelpMan{"savemempool", - "\nDumps the mempool to disk. It will fail until the previous dump is fully loaded.\n", - {}, - RPCResult{ - RPCResult::Type::OBJ, "", "", - { - {RPCResult::Type::STR, "filename", "the directory and file where the mempool was saved"}, - }}, - RPCExamples{ - HelpExampleCli("savemempool", "") - + HelpExampleRpc("savemempool", "") - }, - [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue -{ - const ArgsManager& args{EnsureAnyArgsman(request.context)}; - const CTxMemPool& mempool = EnsureAnyMemPool(request.context); - - if (!mempool.IsLoaded()) { - throw JSONRPCError(RPC_MISC_ERROR, "The mempool was not loaded yet"); - } - - if (!DumpMempool(mempool)) { - 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()); - - return ret; -}, - }; -} - namespace { //! Search for a given set of pubkey scripts bool FindScriptPubKey(std::atomic<int>& scan_progress, const std::atomic<bool>& should_abort, int64_t& count, CCoinsViewCursor* cursor, const std::set<CScript>& needles, std::map<COutPoint, Coin>& out_results, std::function<void()>& interruption_point) @@ -2426,9 +1997,9 @@ static std::atomic<bool> g_should_abort_scan; class CoinsViewScanReserver { private: - bool m_could_reserve; + bool m_could_reserve{false}; public: - explicit CoinsViewScanReserver() : m_could_reserve(false) {} + explicit CoinsViewScanReserver() = default; bool reserve() { CHECK_NONFATAL(!m_could_reserve); @@ -2484,13 +2055,7 @@ static RPCHelpMan scantxoutset() "[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)"}, @@ -2509,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 + "\"]\'") + @@ -2527,9 +2098,9 @@ static RPCHelpMan scantxoutset() CoinsViewScanReserver reserver; if (reserver.reserve()) { // no scan in progress - return NullUniValue; + return UniValue::VNULL; } - result.pushKV("progress", g_scan_progress); + result.pushKV("progress", g_scan_progress.load()); return result; } else if (request.params[0].get_str() == "abort") { CoinsViewScanReserver reserver; @@ -2572,17 +2143,15 @@ static RPCHelpMan scantxoutset() g_should_abort_scan = false; int64_t count = 0; std::unique_ptr<CCoinsViewCursor> pcursor; - CBlockIndex* tip; + const CBlockIndex* tip; NodeContext& node = EnsureAnyNodeContext(request.context); { ChainstateManager& chainman = EnsureChainman(node); LOCK(cs_main); CChainState& active_chainstate = chainman.ActiveChainstate(); active_chainstate.ForceFlushStateToDisk(); - pcursor = active_chainstate.CoinsDB().Cursor(); - CHECK_NONFATAL(pcursor); - tip = active_chainstate.m_chain.Tip(); - CHECK_NONFATAL(tip); + pcursor = CHECK_NONFATAL(active_chainstate.CoinsDB().Cursor()); + tip = CHECK_NONFATAL(active_chainstate.m_chain.Tip()); } bool res = FindScriptPubKey(g_scan_progress, g_should_abort_scan, count, pcursor.get(), needles, coins, node.rpc_interruption_point); result.pushKV("success", res); @@ -2623,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, "", "", @@ -2638,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(); } @@ -2739,7 +2308,13 @@ 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, + "Couldn't open file " + temppath.u8string() + " for writing."); + } + NodeContext& node = EnsureAnyNodeContext(request.context); UniValue result = CreateUTXOSnapshot( node, node.chainman->ActiveChainstate(), afile, path, temppath); @@ -2754,13 +2329,13 @@ static RPCHelpMan dumptxoutset() UniValue CreateUTXOSnapshot( NodeContext& node, CChainState& chainstate, - CAutoFile& afile, + AutoFile& afile, const fs::path& path, const fs::path& temppath) { std::unique_ptr<CCoinsViewCursor> pcursor; - CCoinsStats stats{CoinStatsHashType::HASH_SERIALIZED}; - CBlockIndex* tip; + std::optional<CCoinsStats> maybe_stats; + const CBlockIndex* tip; { // We need to lock cs_main to ensure that the coinsdb isn't written to @@ -2779,20 +2354,20 @@ UniValue CreateUTXOSnapshot( chainstate.ForceFlushStateToDisk(); - if (!GetUTXOStats(&chainstate.CoinsDB(), chainstate.m_blockman, stats, node.rpc_interruption_point)) { + maybe_stats = GetUTXOStats(&chainstate.CoinsDB(), chainstate.m_blockman, CoinStatsHashType::HASH_SERIALIZED, node.rpc_interruption_point); + if (!maybe_stats) { throw JSONRPCError(RPC_INTERNAL_ERROR, "Unable to read UTXO set"); } pcursor = chainstate.CoinsDB().Cursor(); - tip = chainstate.m_blockman.LookupBlockIndex(stats.hashBlock); - CHECK_NONFATAL(tip); + tip = CHECK_NONFATAL(chainstate.m_blockman.LookupBlockIndex(maybe_stats->hashBlock)); } LOG_TIME_SECONDS(strprintf("writing UTXO snapshot at height %s (%s) to file %s (via %s)", tip->nHeight, tip->GetBlockHash().ToString(), fs::PathToString(path), fs::PathToString(temppath))); - SnapshotMetadata metadata{tip->GetBlockHash(), stats.coins_count, tip->nChainTx}; + SnapshotMetadata metadata{tip->GetBlockHash(), maybe_stats->coins_count, tip->nChainTx}; afile << metadata; @@ -2814,60 +2389,47 @@ UniValue CreateUTXOSnapshot( afile.fclose(); UniValue result(UniValue::VOBJ); - result.pushKV("coins_written", stats.coins_count); + result.pushKV("coins_written", maybe_stats->coins_count); result.pushKV("base_hash", tip->GetBlockHash().ToString()); result.pushKV("base_height", tip->nHeight); result.pushKV("path", path.u8string()); - result.pushKV("txoutset_hash", stats.hashSerialized.ToString()); + result.pushKV("txoutset_hash", maybe_stats->hashSerialized.ToString()); // Cast required because univalue doesn't have serialization specified for // `unsigned int`, nChainTx's type. result.pushKV("nchaintx", uint64_t{tip->nChainTx}); return result; } -void RegisterBlockchainRPCCommands(CRPCTable &t) -{ -// clang-format off -static const CRPCCommand commands[] = -{ // category actor (function) - // --------------------- ------------------------ - { "blockchain", &getblockchaininfo, }, - { "blockchain", &getchaintxstats, }, - { "blockchain", &getblockstats, }, - { "blockchain", &getbestblockhash, }, - { "blockchain", &getblockcount, }, - { "blockchain", &getblock, }, - { "blockchain", &getblockfrompeer, }, - { "blockchain", &getblockhash, }, - { "blockchain", &getblockheader, }, - { "blockchain", &getchaintips, }, - { "blockchain", &getdifficulty, }, - { "blockchain", &getdeploymentinfo, }, - { "blockchain", &getmempoolancestors, }, - { "blockchain", &getmempooldescendants, }, - { "blockchain", &getmempoolentry, }, - { "blockchain", &getmempoolinfo, }, - { "blockchain", &getrawmempool, }, - { "blockchain", &gettxout, }, - { "blockchain", &gettxoutsetinfo, }, - { "blockchain", &pruneblockchain, }, - { "blockchain", &savemempool, }, - { "blockchain", &verifychain, }, - - { "blockchain", &preciousblock, }, - { "blockchain", &scantxoutset, }, - { "blockchain", &getblockfilter, }, - - /* Not shown in help */ - { "hidden", &invalidateblock, }, - { "hidden", &reconsiderblock, }, - { "hidden", &waitfornewblock, }, - { "hidden", &waitforblock, }, - { "hidden", &waitforblockheight, }, - { "hidden", &syncwithvalidationinterfacequeue, }, - { "hidden", &dumptxoutset, }, -}; -// clang-format on +void RegisterBlockchainRPCCommands(CRPCTable& t) +{ + static const CRPCCommand commands[]{ + {"blockchain", &getblockchaininfo}, + {"blockchain", &getchaintxstats}, + {"blockchain", &getblockstats}, + {"blockchain", &getbestblockhash}, + {"blockchain", &getblockcount}, + {"blockchain", &getblock}, + {"blockchain", &getblockfrompeer}, + {"blockchain", &getblockhash}, + {"blockchain", &getblockheader}, + {"blockchain", &getchaintips}, + {"blockchain", &getdifficulty}, + {"blockchain", &getdeploymentinfo}, + {"blockchain", &gettxout}, + {"blockchain", &gettxoutsetinfo}, + {"blockchain", &pruneblockchain}, + {"blockchain", &verifychain}, + {"blockchain", &preciousblock}, + {"blockchain", &scantxoutset}, + {"blockchain", &getblockfilter}, + {"hidden", &invalidateblock}, + {"hidden", &reconsiderblock}, + {"hidden", &waitfornewblock}, + {"hidden", &waitforblock}, + {"hidden", &waitforblockheight}, + {"hidden", &syncwithvalidationinterfacequeue}, + {"hidden", &dumptxoutset}, + }; for (const auto& c : commands) { t.appendCommand(c.name, &c); } diff --git a/src/rpc/blockchain.h b/src/rpc/blockchain.h index 1f51d7c1ad..a332fd4892 100644 --- a/src/rpc/blockchain.h +++ b/src/rpc/blockchain.h @@ -10,6 +10,7 @@ #include <fs.h> #include <streams.h> #include <sync.h> +#include <validation.h> #include <any> #include <stdint.h> @@ -20,7 +21,6 @@ extern RecursiveMutex cs_main; class CBlock; class CBlockIndex; class CChainState; -class CTxMemPool; class UniValue; namespace node { struct NodeContext; @@ -40,13 +40,7 @@ double GetDifficulty(const CBlockIndex* blockindex); void RPCNotifyBlockChange(const CBlockIndex*); /** Block description to JSON */ -UniValue blockToJSON(const CBlock& block, const CBlockIndex* tip, const CBlockIndex* blockindex, TxVerbosity verbosity) LOCKS_EXCLUDED(cs_main); - -/** Mempool information to JSON */ -UniValue MempoolInfoToJSON(const CTxMemPool& pool); - -/** Mempool to JSON */ -UniValue MempoolToJSON(const CTxMemPool& pool, bool verbose = false, bool include_mempool_sequence = false); +UniValue blockToJSON(node::BlockManager& blockman, const CBlock& block, const CBlockIndex* tip, const CBlockIndex* blockindex, TxVerbosity verbosity) LOCKS_EXCLUDED(cs_main); /** Block header to JSON */ UniValue blockheaderToJSON(const CBlockIndex* tip, const CBlockIndex* blockindex) LOCKS_EXCLUDED(cs_main); @@ -61,7 +55,7 @@ void CalculatePercentilesByWeight(CAmount result[NUM_GETBLOCKSTATS_PERCENTILES], UniValue CreateUTXOSnapshot( node::NodeContext& node, CChainState& chainstate, - CAutoFile& afile, + AutoFile& afile, const fs::path& path, const fs::path& tmppath); diff --git a/src/rpc/client.cpp b/src/rpc/client.cpp index c480a093a4..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" }, @@ -142,6 +144,12 @@ static const CRPCConvertParam vRPCConvertParams[] = { "send", 1, "conf_target" }, { "send", 3, "fee_rate"}, { "send", 4, "options" }, + { "sendall", 0, "recipients" }, + { "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" }, @@ -169,6 +177,7 @@ static const CRPCConvertParam vRPCConvertParams[] = { "setwalletflag", 1, "value" }, { "getmempoolancestors", 1, "verbose" }, { "getmempooldescendants", 1, "verbose" }, + { "gettxspendingprevout", 0, "outputs" }, { "bumpfee", 1, "options" }, { "psbtbumpfee", 1, "options" }, { "logging", 0, "include" }, diff --git a/src/rpc/external_signer.cpp b/src/rpc/external_signer.cpp index 60ec15e904..4de7fc4205 100644 --- a/src/rpc/external_signer.cpp +++ b/src/rpc/external_signer.cpp @@ -22,7 +22,7 @@ static RPCHelpMan enumeratesigners() RPCResult{ RPCResult::Type::OBJ, "", "", { - {RPCResult::Type::ARR, "signers", /* optional */ false, "", + {RPCResult::Type::ARR, "signers", /*optional=*/false, "", { {RPCResult::Type::OBJ, "", "", { @@ -62,15 +62,11 @@ static RPCHelpMan enumeratesigners() }; } -void RegisterSignerRPCCommands(CRPCTable &t) +void RegisterSignerRPCCommands(CRPCTable& t) { -// clang-format off -static const CRPCCommand commands[] = -{ // category actor (function) - // --------------------- ------------------------ - { "signer", &enumeratesigners, }, -}; -// clang-format on + static const CRPCCommand commands[]{ + {"signer", &enumeratesigners}, + }; for (const auto& c : commands) { t.appendCommand(c.name, &c); } diff --git a/src/rpc/fees.cpp b/src/rpc/fees.cpp new file mode 100644 index 0000000000..aa047bdea8 --- /dev/null +++ b/src/rpc/fees.cpp @@ -0,0 +1,233 @@ +// Copyright (c) 2010 Satoshi Nakamoto +// Copyright (c) 2009-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 <core_io.h> +#include <policy/feerate.h> +#include <policy/fees.h> +#include <rpc/protocol.h> +#include <rpc/request.h> +#include <rpc/server.h> +#include <rpc/server_util.h> +#include <rpc/util.h> +#include <txmempool.h> +#include <univalue.h> +#include <util/fees.h> + +#include <algorithm> +#include <array> +#include <cmath> +#include <string> + +namespace node { +struct NodeContext; +} + +using node::NodeContext; + +static RPCHelpMan estimatesmartfee() +{ + return RPCHelpMan{"estimatesmartfee", + "\nEstimates the approximate fee per kilobyte needed for a transaction to begin\n" + "confirmation within conf_target blocks if possible and return the number of blocks\n" + "for which the estimate is valid. Uses virtual transaction size as defined\n" + "in BIP 141 (witness data is discounted).\n", + { + {"conf_target", RPCArg::Type::NUM, RPCArg::Optional::NO, "Confirmation target in blocks (1 - 1008)"}, + {"estimate_mode", RPCArg::Type::STR, RPCArg::Default{"conservative"}, "The fee estimate mode.\n" + "Whether to return a more conservative estimate which also satisfies\n" + "a longer history. A conservative estimate potentially returns a\n" + "higher feerate and is more likely to be sufficient for the desired\n" + "target, but is not as responsive to short term drops in the\n" + "prevailing fee market. Must be one of (case insensitive):\n" + "\"" + FeeModes("\"\n\"") + "\""}, + }, + RPCResult{ + RPCResult::Type::OBJ, "", "", + { + {RPCResult::Type::NUM, "feerate", /*optional=*/true, "estimate fee rate in " + CURRENCY_UNIT + "/kvB (only present if no errors were encountered)"}, + {RPCResult::Type::ARR, "errors", /*optional=*/true, "Errors encountered during processing (if there are any)", + { + {RPCResult::Type::STR, "", "error"}, + }}, + {RPCResult::Type::NUM, "blocks", "block number where estimate was found\n" + "The request target will be clamped between 2 and the highest target\n" + "fee estimation is able to return based on how long it has been running.\n" + "An error is returned if not enough transactions and blocks\n" + "have been observed to make an estimate for any number of blocks."}, + }}, + RPCExamples{ + HelpExampleCli("estimatesmartfee", "6") + + HelpExampleRpc("estimatesmartfee", "6") + }, + [&](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); + const CTxMemPool& mempool = EnsureMemPool(node); + + unsigned int max_target = fee_estimator.HighestTargetTracked(FeeEstimateHorizon::LONG_HALFLIFE); + unsigned int conf_target = ParseConfirmTarget(request.params[0], max_target); + bool conservative = true; + if (!request.params[1].isNull()) { + FeeEstimateMode fee_mode; + if (!FeeModeFromString(request.params[1].get_str(), fee_mode)) { + throw JSONRPCError(RPC_INVALID_PARAMETER, InvalidEstimateModeErrorMessage()); + } + if (fee_mode == FeeEstimateMode::ECONOMICAL) conservative = false; + } + + UniValue result(UniValue::VOBJ); + UniValue errors(UniValue::VARR); + FeeCalculation feeCalc; + CFeeRate feeRate{fee_estimator.estimateSmartFee(conf_target, &feeCalc, conservative)}; + if (feeRate != CFeeRate(0)) { + 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 { + errors.push_back("Insufficient data or no feerate found"); + result.pushKV("errors", errors); + } + result.pushKV("blocks", feeCalc.returnedTarget); + return result; + }, + }; +} + +static RPCHelpMan estimaterawfee() +{ + return RPCHelpMan{"estimaterawfee", + "\nWARNING: This interface is unstable and may disappear or change!\n" + "\nWARNING: This is an advanced API call that is tightly coupled to the specific\n" + "implementation of fee estimation. The parameters it can be called with\n" + "and the results it returns will change if the internal implementation changes.\n" + "\nEstimates the approximate fee per kilobyte needed for a transaction to begin\n" + "confirmation within conf_target blocks if possible. Uses virtual transaction size as\n" + "defined in BIP 141 (witness data is discounted).\n", + { + {"conf_target", RPCArg::Type::NUM, RPCArg::Optional::NO, "Confirmation target in blocks (1 - 1008)"}, + {"threshold", RPCArg::Type::NUM, RPCArg::Default{0.95}, "The proportion of transactions in a given feerate range that must have been\n" + "confirmed within conf_target in order to consider those feerates as high enough and proceed to check\n" + "lower buckets."}, + }, + RPCResult{ + RPCResult::Type::OBJ, "", "Results are returned for any horizon which tracks blocks up to the confirmation target", + { + {RPCResult::Type::OBJ, "short", /*optional=*/true, "estimate for short time horizon", + { + {RPCResult::Type::NUM, "feerate", /*optional=*/true, "estimate fee rate in " + CURRENCY_UNIT + "/kvB"}, + {RPCResult::Type::NUM, "decay", "exponential decay (per block) for historical moving average of confirmation data"}, + {RPCResult::Type::NUM, "scale", "The resolution of confirmation targets at this time horizon"}, + {RPCResult::Type::OBJ, "pass", /*optional=*/true, "information about the lowest range of feerates to succeed in meeting the threshold", + { + {RPCResult::Type::NUM, "startrange", "start of feerate range"}, + {RPCResult::Type::NUM, "endrange", "end of feerate range"}, + {RPCResult::Type::NUM, "withintarget", "number of txs over history horizon in the feerate range that were confirmed within target"}, + {RPCResult::Type::NUM, "totalconfirmed", "number of txs over history horizon in the feerate range that were confirmed at any point"}, + {RPCResult::Type::NUM, "inmempool", "current number of txs in mempool in the feerate range unconfirmed for at least target blocks"}, + {RPCResult::Type::NUM, "leftmempool", "number of txs over history horizon in the feerate range that left mempool unconfirmed after target"}, + }}, + {RPCResult::Type::OBJ, "fail", /*optional=*/true, "information about the highest range of feerates to fail to meet the threshold", + { + {RPCResult::Type::ELISION, "", ""}, + }}, + {RPCResult::Type::ARR, "errors", /*optional=*/true, "Errors encountered during processing (if there are any)", + { + {RPCResult::Type::STR, "error", ""}, + }}, + }}, + {RPCResult::Type::OBJ, "medium", /*optional=*/true, "estimate for medium time horizon", + { + {RPCResult::Type::ELISION, "", ""}, + }}, + {RPCResult::Type::OBJ, "long", /*optional=*/true, "estimate for long time horizon", + { + {RPCResult::Type::ELISION, "", ""}, + }}, + }}, + RPCExamples{ + HelpExampleCli("estimaterawfee", "6 0.9") + }, + [&](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); + + unsigned int max_target = fee_estimator.HighestTargetTracked(FeeEstimateHorizon::LONG_HALFLIFE); + unsigned int conf_target = ParseConfirmTarget(request.params[0], max_target); + double threshold = 0.95; + if (!request.params[1].isNull()) { + threshold = request.params[1].get_real(); + } + if (threshold < 0 || threshold > 1) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid threshold"); + } + + UniValue result(UniValue::VOBJ); + + for (const FeeEstimateHorizon horizon : ALL_FEE_ESTIMATE_HORIZONS) { + CFeeRate feeRate; + EstimationResult buckets; + + // Only output results for horizons which track the target + if (conf_target > fee_estimator.HighestTargetTracked(horizon)) continue; + + feeRate = fee_estimator.estimateRawFee(conf_target, threshold, horizon, &buckets); + UniValue horizon_result(UniValue::VOBJ); + UniValue errors(UniValue::VARR); + UniValue passbucket(UniValue::VOBJ); + passbucket.pushKV("startrange", round(buckets.pass.start)); + passbucket.pushKV("endrange", round(buckets.pass.end)); + passbucket.pushKV("withintarget", round(buckets.pass.withinTarget * 100.0) / 100.0); + passbucket.pushKV("totalconfirmed", round(buckets.pass.totalConfirmed * 100.0) / 100.0); + passbucket.pushKV("inmempool", round(buckets.pass.inMempool * 100.0) / 100.0); + passbucket.pushKV("leftmempool", round(buckets.pass.leftMempool * 100.0) / 100.0); + UniValue failbucket(UniValue::VOBJ); + failbucket.pushKV("startrange", round(buckets.fail.start)); + failbucket.pushKV("endrange", round(buckets.fail.end)); + failbucket.pushKV("withintarget", round(buckets.fail.withinTarget * 100.0) / 100.0); + failbucket.pushKV("totalconfirmed", round(buckets.fail.totalConfirmed * 100.0) / 100.0); + failbucket.pushKV("inmempool", round(buckets.fail.inMempool * 100.0) / 100.0); + failbucket.pushKV("leftmempool", round(buckets.fail.leftMempool * 100.0) / 100.0); + + // CFeeRate(0) is used to indicate error as a return value from estimateRawFee + if (feeRate != CFeeRate(0)) { + horizon_result.pushKV("feerate", ValueFromAmount(feeRate.GetFeePerK())); + horizon_result.pushKV("decay", buckets.decay); + horizon_result.pushKV("scale", (int)buckets.scale); + horizon_result.pushKV("pass", passbucket); + // buckets.fail.start == -1 indicates that all buckets passed, there is no fail bucket to output + if (buckets.fail.start != -1) horizon_result.pushKV("fail", failbucket); + } else { + // Output only information that is still meaningful in the event of error + horizon_result.pushKV("decay", buckets.decay); + horizon_result.pushKV("scale", (int)buckets.scale); + horizon_result.pushKV("fail", failbucket); + errors.push_back("Insufficient data or no feerate found which meets threshold"); + horizon_result.pushKV("errors", errors); + } + result.pushKV(StringForFeeEstimateHorizon(horizon), horizon_result); + } + return result; + }, + }; +} + +void RegisterFeeRPCCommands(CRPCTable& t) +{ + static const CRPCCommand commands[]{ + {"util", &estimatesmartfee}, + {"hidden", &estimaterawfee}, + }; + for (const auto& c : commands) { + t.appendCommand(c.name, &c); + } +} diff --git a/src/rpc/mempool.cpp b/src/rpc/mempool.cpp new file mode 100644 index 0000000000..02b51ce0a0 --- /dev/null +++ b/src/rpc/mempool.cpp @@ -0,0 +1,906 @@ +// Copyright (c) 2010 Satoshi Nakamoto +// Copyright (c) 2009-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 <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> +#include <rpc/server.h> +#include <rpc/server_util.h> +#include <rpc/util.h> +#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() +{ + return RPCHelpMan{"sendrawtransaction", + "\nSubmit a raw transaction (serialized, hex-encoded) to local node and network.\n" + "\nThe transaction will be sent unconditionally to all peers, so using sendrawtransaction\n" + "for manual rebroadcast may degrade privacy by leaking the transaction's origin, as\n" + "nodes will normally not rebroadcast non-wallet transactions already in their mempool.\n" + "\nA specific exception, RPC_TRANSACTION_ALREADY_IN_CHAIN, may throw if the transaction cannot be added to the mempool.\n" + "\nRelated RPCs: createrawtransaction, signrawtransactionwithkey\n", + { + {"hexstring", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The hex string of the raw transaction"}, + {"maxfeerate", RPCArg::Type::AMOUNT, RPCArg::Default{FormatMoney(DEFAULT_MAX_RAW_TX_FEE_RATE.GetFeePerK())}, + "Reject transactions whose fee rate is higher than the specified value, expressed in " + CURRENCY_UNIT + + "/kvB.\nSet to 0 to accept any fee rate.\n"}, + }, + RPCResult{ + RPCResult::Type::STR_HEX, "", "The transaction hash in hex" + }, + RPCExamples{ + "\nCreate a transaction\n" + + HelpExampleCli("createrawtransaction", "\"[{\\\"txid\\\" : \\\"mytxid\\\",\\\"vout\\\":0}]\" \"{\\\"myaddress\\\":0.01}\"") + + "Sign the transaction, and get back the hex\n" + + HelpExampleCli("signrawtransactionwithwallet", "\"myhex\"") + + "\nSend the transaction (signed hex)\n" + + HelpExampleCli("sendrawtransaction", "\"signedhex\"") + + "\nAs a JSON-RPC call\n" + + HelpExampleRpc("sendrawtransaction", "\"signedhex\"") + }, + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue + { + RPCTypeCheck(request.params, { + UniValue::VSTR, + UniValueType(), // VNUM or VSTR, checked inside AmountFromValue() + }); + + CMutableTransaction mtx; + if (!DecodeHexTx(mtx, request.params[0].get_str())) { + throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed. Make sure the tx has at least one input."); + } + CTransactionRef tx(MakeTransactionRef(std::move(mtx))); + + const CFeeRate max_raw_tx_fee_rate = request.params[1].isNull() ? + DEFAULT_MAX_RAW_TX_FEE_RATE : + CFeeRate(AmountFromValue(request.params[1])); + + int64_t virtual_size = GetVirtualTransactionSize(*tx); + CAmount max_raw_tx_fee = max_raw_tx_fee_rate.GetFee(virtual_size); + + std::string err_string; + AssertLockNotHeld(cs_main); + NodeContext& node = EnsureAnyNodeContext(request.context); + const TransactionError err = BroadcastTransaction(node, tx, err_string, max_raw_tx_fee, /*relay=*/true, /*wait_callback=*/true); + if (TransactionError::OK != err) { + throw JSONRPCTransactionError(err, err_string); + } + + return tx->GetHash().GetHex(); + }, + }; +} + +static RPCHelpMan testmempoolaccept() +{ + return RPCHelpMan{"testmempoolaccept", + "\nReturns result of mempool acceptance tests indicating if raw transaction(s) (serialized, hex-encoded) would be accepted by mempool.\n" + "\nIf multiple transactions are passed in, parents must come before children and package policies apply: the transactions cannot conflict with any mempool transactions or each other.\n" + "\nIf one transaction fails, other transactions may not be fully validated (the 'allowed' key will be blank).\n" + "\nThe maximum number of transactions allowed is " + ToString(MAX_PACKAGE_COUNT) + ".\n" + "\nThis checks if transactions violate the consensus or policy rules.\n" + "\nSee sendrawtransaction call.\n", + { + {"rawtxs", RPCArg::Type::ARR, RPCArg::Optional::NO, "An array of hex strings of raw transactions.", + { + {"rawtx", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, ""}, + }, + }, + {"maxfeerate", RPCArg::Type::AMOUNT, RPCArg::Default{FormatMoney(DEFAULT_MAX_RAW_TX_FEE_RATE.GetFeePerK())}, + "Reject transactions whose fee rate is higher than the specified value, expressed in " + CURRENCY_UNIT + "/kvB\n"}, + }, + RPCResult{ + RPCResult::Type::ARR, "", "The result of the mempool acceptance test for each raw transaction in the input array.\n" + "Returns results for each transaction in the same order they were passed in.\n" + "Transactions that cannot be fully validated due to failures in other transactions will not contain an 'allowed' result.\n", + { + {RPCResult::Type::OBJ, "", "", + { + {RPCResult::Type::STR_HEX, "txid", "The transaction hash in hex"}, + {RPCResult::Type::STR_HEX, "wtxid", "The transaction witness hash in hex"}, + {RPCResult::Type::STR, "package-error", /*optional=*/true, "Package validation error, if any (only possible if rawtxs had more than 1 transaction)."}, + {RPCResult::Type::BOOL, "allowed", /*optional=*/true, "Whether this tx would be accepted to the mempool and pass client-specified maxfeerate. " + "If not present, the tx was not fully validated due to a failure in another tx in the list."}, + {RPCResult::Type::NUM, "vsize", /*optional=*/true, "Virtual transaction size as defined in BIP 141. This is different from actual serialized size for witness transactions as witness data is discounted (only present when 'allowed' is true)"}, + {RPCResult::Type::OBJ, "fees", /*optional=*/true, "Transaction fees (only present if 'allowed' is true)", + { + {RPCResult::Type::STR_AMOUNT, "base", "transaction fee in " + CURRENCY_UNIT}, + }}, + {RPCResult::Type::STR, "reject-reason", /*optional=*/true, "Rejection string (only present when 'allowed' is false)"}, + }}, + } + }, + RPCExamples{ + "\nCreate a transaction\n" + + HelpExampleCli("createrawtransaction", "\"[{\\\"txid\\\" : \\\"mytxid\\\",\\\"vout\\\":0}]\" \"{\\\"myaddress\\\":0.01}\"") + + "Sign the transaction, and get back the hex\n" + + HelpExampleCli("signrawtransactionwithwallet", "\"myhex\"") + + "\nTest acceptance of the transaction (signed hex)\n" + + HelpExampleCli("testmempoolaccept", R"('["signedhex"]')") + + "\nAs a JSON-RPC call\n" + + HelpExampleRpc("testmempoolaccept", "[\"signedhex\"]") + }, + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue + { + RPCTypeCheck(request.params, { + UniValue::VARR, + UniValueType(), // VNUM or VSTR, checked inside AmountFromValue() + }); + 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."); + } + + const CFeeRate max_raw_tx_fee_rate = request.params[1].isNull() ? + DEFAULT_MAX_RAW_TX_FEE_RATE : + CFeeRate(AmountFromValue(request.params[1])); + + 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); + ChainstateManager& chainman = EnsureChainman(node); + CChainState& chainstate = chainman.ActiveChainstate(); + const PackageMempoolAcceptResult package_result = [&] { + LOCK(::cs_main); + if (txns.size() > 1) return ProcessNewPackage(chainstate, mempool, txns, /*test_accept=*/true); + return PackageMempoolAcceptResult(txns[0]->GetWitnessHash(), + chainman.ProcessTransaction(txns[0], /*test_accept=*/true)); + }(); + + UniValue rpc_result(UniValue::VARR); + // We will check transaction fees while we iterate through txns in order. If any transaction fee + // exceeds maxfeerate, we will leave the rest of the validation results blank, because it + // doesn't make sense to return a validation result for a transaction if its ancestor(s) would + // not be submitted. + bool exit_early{false}; + for (const auto& tx : txns) { + UniValue result_inner(UniValue::VOBJ); + result_inner.pushKV("txid", tx->GetHash().GetHex()); + result_inner.pushKV("wtxid", tx->GetWitnessHash().GetHex()); + if (package_result.m_state.GetResult() == PackageValidationResult::PCKG_POLICY) { + result_inner.pushKV("package-error", package_result.m_state.GetRejectReason()); + } + auto it = package_result.m_tx_results.find(tx->GetWitnessHash()); + if (exit_early || it == package_result.m_tx_results.end()) { + // Validation unfinished. Just return the txid and wtxid. + rpc_result.push_back(result_inner); + continue; + } + const auto& tx_result = it->second; + // Package testmempoolaccept doesn't allow transactions to already be in the mempool. + CHECK_NONFATAL(tx_result.m_result_type != MempoolAcceptResult::ResultType::MEMPOOL_ENTRY); + if (tx_result.m_result_type == MempoolAcceptResult::ResultType::VALID) { + const CAmount fee = tx_result.m_base_fees.value(); + // Check that fee does not exceed maximum fee + const int64_t virtual_size = tx_result.m_vsize.value(); + const CAmount max_raw_tx_fee = max_raw_tx_fee_rate.GetFee(virtual_size); + if (max_raw_tx_fee && fee > max_raw_tx_fee) { + result_inner.pushKV("allowed", false); + result_inner.pushKV("reject-reason", "max-fee-exceeded"); + exit_early = true; + } else { + // Only return the fee and vsize if the transaction would pass ATMP. + // These can be used to calculate the feerate. + result_inner.pushKV("allowed", true); + result_inner.pushKV("vsize", virtual_size); + UniValue fees(UniValue::VOBJ); + fees.pushKV("base", ValueFromAmount(fee)); + result_inner.pushKV("fees", fees); + } + } else { + result_inner.pushKV("allowed", false); + const TxValidationState state = tx_result.m_state; + if (state.GetResult() == TxValidationResult::TX_MISSING_INPUTS) { + result_inner.pushKV("reject-reason", "missing-inputs"); + } else { + result_inner.pushKV("reject-reason", state.GetRejectReason()); + } + } + rpc_result.push_back(result_inner); + } + return rpc_result; + }, + }; +} + +static std::vector<RPCResult> MempoolEntryDescription() +{ + return { + RPCResult{RPCResult::Type::NUM, "vsize", "virtual transaction size as defined in BIP 141. This is different from actual serialized size for witness transactions as witness data is discounted."}, + RPCResult{RPCResult::Type::NUM, "weight", "transaction weight as defined in BIP 141."}, + RPCResult{RPCResult::Type::NUM_TIME, "time", "local time transaction entered pool in seconds since 1 Jan 1970 GMT"}, + RPCResult{RPCResult::Type::NUM, "height", "block height when transaction entered pool"}, + RPCResult{RPCResult::Type::NUM, "descendantcount", "number of in-mempool descendant transactions (including this one)"}, + RPCResult{RPCResult::Type::NUM, "descendantsize", "virtual transaction size of in-mempool descendants (including this one)"}, + RPCResult{RPCResult::Type::NUM, "ancestorcount", "number of in-mempool ancestor transactions (including this one)"}, + RPCResult{RPCResult::Type::NUM, "ancestorsize", "virtual transaction size of in-mempool ancestors (including this one)"}, + RPCResult{RPCResult::Type::STR_HEX, "wtxid", "hash of serialized transaction, including witness data"}, + RPCResult{RPCResult::Type::OBJ, "fees", "", + { + RPCResult{RPCResult::Type::STR_AMOUNT, "base", "transaction fee, denominated in " + CURRENCY_UNIT}, + RPCResult{RPCResult::Type::STR_AMOUNT, "modified", "transaction fee with fee deltas used for mining priority, denominated in " + CURRENCY_UNIT}, + RPCResult{RPCResult::Type::STR_AMOUNT, "ancestor", "transaction fees of in-mempool ancestors (including this one) with fee deltas used for mining priority, denominated in " + CURRENCY_UNIT}, + RPCResult{RPCResult::Type::STR_AMOUNT, "descendant", "transaction fees of in-mempool descendants (including this one) with fee deltas used for mining priority, denominated in " + CURRENCY_UNIT}, + }}, + RPCResult{RPCResult::Type::ARR, "depends", "unconfirmed transactions used as inputs for this transaction", + {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, "unbroadcast", "Whether this transaction is currently unbroadcast (initial broadcast not yet acknowledged by any peers)"}, + }; +} + +static void entryToJSON(const CTxMemPool& pool, UniValue& info, const CTxMemPoolEntry& e) EXCLUSIVE_LOCKS_REQUIRED(pool.cs) +{ + AssertLockHeld(pool.cs); + + info.pushKV("vsize", (int)e.GetTxSize()); + info.pushKV("weight", (int)e.GetTxWeight()); + info.pushKV("time", count_seconds(e.GetTime())); + info.pushKV("height", (int)e.GetHeight()); + info.pushKV("descendantcount", e.GetCountWithDescendants()); + info.pushKV("descendantsize", e.GetSizeWithDescendants()); + info.pushKV("ancestorcount", e.GetCountWithAncestors()); + info.pushKV("ancestorsize", e.GetSizeWithAncestors()); + info.pushKV("wtxid", pool.vTxHashes[e.vTxHashesIdx].first.ToString()); + + UniValue fees(UniValue::VOBJ); + fees.pushKV("base", ValueFromAmount(e.GetFee())); + fees.pushKV("modified", ValueFromAmount(e.GetModifiedFee())); + fees.pushKV("ancestor", ValueFromAmount(e.GetModFeesWithAncestors())); + fees.pushKV("descendant", ValueFromAmount(e.GetModFeesWithDescendants())); + info.pushKV("fees", fees); + + const CTransaction& tx = e.GetTx(); + std::set<std::string> setDepends; + for (const CTxIn& txin : tx.vin) + { + if (pool.exists(GenTxid::Txid(txin.prevout.hash))) + setDepends.insert(txin.prevout.hash.ToString()); + } + + UniValue depends(UniValue::VARR); + for (const std::string& dep : setDepends) + { + depends.push_back(dep); + } + + info.pushKV("depends", depends); + + UniValue spent(UniValue::VARR); + const CTxMemPool::txiter& it = pool.mapTx.find(tx.GetHash()); + const CTxMemPoolEntry::Children& children = it->GetMemPoolChildrenConst(); + for (const CTxMemPoolEntry& child : children) { + spent.push_back(child.GetTx().GetHash().ToString()); + } + + info.pushKV("spentby", spent); + + // Add opt-in RBF status + bool rbfStatus = false; + RBFTransactionState rbfState = IsRBFOptIn(tx, pool); + if (rbfState == RBFTransactionState::UNKNOWN) { + throw JSONRPCError(RPC_MISC_ERROR, "Transaction is not in mempool"); + } else if (rbfState == RBFTransactionState::REPLACEABLE_BIP125) { + rbfStatus = true; + } + + info.pushKV("bip125-replaceable", rbfStatus); + info.pushKV("unbroadcast", pool.IsUnbroadcastTx(tx.GetHash())); +} + +UniValue MempoolToJSON(const CTxMemPool& pool, bool verbose, bool include_mempool_sequence) +{ + if (verbose) { + if (include_mempool_sequence) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Verbose results cannot contain mempool sequence values."); + } + LOCK(pool.cs); + UniValue o(UniValue::VOBJ); + for (const CTxMemPoolEntry& e : pool.mapTx) { + const uint256& hash = e.GetTx().GetHash(); + UniValue info(UniValue::VOBJ); + entryToJSON(pool, info, e); + // Mempool has unique entries so there is no advantage in using + // UniValue::pushKV, which checks if the key already exists in O(N). + // UniValue::__pushKV is used instead which currently is O(1). + o.__pushKV(hash.ToString(), info); + } + return o; + } else { + uint64_t mempool_sequence; + std::vector<uint256> vtxid; + { + LOCK(pool.cs); + pool.queryHashes(vtxid); + mempool_sequence = pool.GetSequence(); + } + UniValue a(UniValue::VARR); + for (const uint256& hash : vtxid) + a.push_back(hash.ToString()); + + if (!include_mempool_sequence) { + return a; + } else { + UniValue o(UniValue::VOBJ); + o.pushKV("txids", a); + o.pushKV("mempool_sequence", mempool_sequence); + return o; + } + } +} + +static RPCHelpMan getrawmempool() +{ + return RPCHelpMan{"getrawmempool", + "\nReturns all transaction ids in memory pool as a json array of string transaction ids.\n" + "\nHint: use getmempoolentry to fetch a specific transaction from the mempool.\n", + { + {"verbose", RPCArg::Type::BOOL, RPCArg::Default{false}, "True for a json object, false for array of transaction ids"}, + {"mempool_sequence", RPCArg::Type::BOOL, RPCArg::Default{false}, "If verbose=false, returns a json object with transaction list and mempool sequence number attached."}, + }, + { + RPCResult{"for verbose = false", + RPCResult::Type::ARR, "", "", + { + {RPCResult::Type::STR_HEX, "", "The transaction id"}, + }}, + RPCResult{"for verbose = true", + RPCResult::Type::OBJ_DYN, "", "", + { + {RPCResult::Type::OBJ, "transactionid", "", MempoolEntryDescription()}, + }}, + RPCResult{"for verbose = false and mempool_sequence = true", + RPCResult::Type::OBJ, "", "", + { + {RPCResult::Type::ARR, "txids", "", + { + {RPCResult::Type::STR_HEX, "", "The transaction id"}, + }}, + {RPCResult::Type::NUM, "mempool_sequence", "The mempool sequence value."}, + }}, + }, + RPCExamples{ + HelpExampleCli("getrawmempool", "true") + + HelpExampleRpc("getrawmempool", "true") + }, + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue +{ + bool fVerbose = false; + if (!request.params[0].isNull()) + fVerbose = request.params[0].get_bool(); + + bool include_mempool_sequence = false; + if (!request.params[1].isNull()) { + include_mempool_sequence = request.params[1].get_bool(); + } + + return MempoolToJSON(EnsureAnyMemPool(request.context), fVerbose, include_mempool_sequence); +}, + }; +} + +static RPCHelpMan getmempoolancestors() +{ + return RPCHelpMan{"getmempoolancestors", + "\nIf txid is in the mempool, returns all in-mempool ancestors.\n", + { + {"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The transaction id (must be in mempool)"}, + {"verbose", RPCArg::Type::BOOL, RPCArg::Default{false}, "True for a json object, false for array of transaction ids"}, + }, + { + RPCResult{"for verbose = false", + RPCResult::Type::ARR, "", "", + {{RPCResult::Type::STR_HEX, "", "The transaction id of an in-mempool ancestor transaction"}}}, + RPCResult{"for verbose = true", + RPCResult::Type::OBJ_DYN, "", "", + { + {RPCResult::Type::OBJ, "transactionid", "", MempoolEntryDescription()}, + }}, + }, + RPCExamples{ + HelpExampleCli("getmempoolancestors", "\"mytxid\"") + + HelpExampleRpc("getmempoolancestors", "\"mytxid\"") + }, + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue +{ + bool fVerbose = false; + if (!request.params[1].isNull()) + fVerbose = request.params[1].get_bool(); + + uint256 hash = ParseHashV(request.params[0], "parameter 1"); + + const CTxMemPool& mempool = EnsureAnyMemPool(request.context); + LOCK(mempool.cs); + + CTxMemPool::txiter it = mempool.mapTx.find(hash); + if (it == mempool.mapTx.end()) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Transaction not in mempool"); + } + + 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); + + if (!fVerbose) { + UniValue o(UniValue::VARR); + for (CTxMemPool::txiter ancestorIt : setAncestors) { + o.push_back(ancestorIt->GetTx().GetHash().ToString()); + } + return o; + } else { + UniValue o(UniValue::VOBJ); + for (CTxMemPool::txiter ancestorIt : setAncestors) { + const CTxMemPoolEntry &e = *ancestorIt; + const uint256& _hash = e.GetTx().GetHash(); + UniValue info(UniValue::VOBJ); + entryToJSON(mempool, info, e); + o.pushKV(_hash.ToString(), info); + } + return o; + } +}, + }; +} + +static RPCHelpMan getmempooldescendants() +{ + return RPCHelpMan{"getmempooldescendants", + "\nIf txid is in the mempool, returns all in-mempool descendants.\n", + { + {"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The transaction id (must be in mempool)"}, + {"verbose", RPCArg::Type::BOOL, RPCArg::Default{false}, "True for a json object, false for array of transaction ids"}, + }, + { + RPCResult{"for verbose = false", + RPCResult::Type::ARR, "", "", + {{RPCResult::Type::STR_HEX, "", "The transaction id of an in-mempool descendant transaction"}}}, + RPCResult{"for verbose = true", + RPCResult::Type::OBJ_DYN, "", "", + { + {RPCResult::Type::OBJ, "transactionid", "", MempoolEntryDescription()}, + }}, + }, + RPCExamples{ + HelpExampleCli("getmempooldescendants", "\"mytxid\"") + + HelpExampleRpc("getmempooldescendants", "\"mytxid\"") + }, + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue +{ + bool fVerbose = false; + if (!request.params[1].isNull()) + fVerbose = request.params[1].get_bool(); + + uint256 hash = ParseHashV(request.params[0], "parameter 1"); + + const CTxMemPool& mempool = EnsureAnyMemPool(request.context); + LOCK(mempool.cs); + + CTxMemPool::txiter it = mempool.mapTx.find(hash); + if (it == mempool.mapTx.end()) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Transaction not in mempool"); + } + + CTxMemPool::setEntries setDescendants; + mempool.CalculateDescendants(it, setDescendants); + // CTxMemPool::CalculateDescendants will include the given tx + setDescendants.erase(it); + + if (!fVerbose) { + UniValue o(UniValue::VARR); + for (CTxMemPool::txiter descendantIt : setDescendants) { + o.push_back(descendantIt->GetTx().GetHash().ToString()); + } + + return o; + } else { + UniValue o(UniValue::VOBJ); + for (CTxMemPool::txiter descendantIt : setDescendants) { + const CTxMemPoolEntry &e = *descendantIt; + const uint256& _hash = e.GetTx().GetHash(); + UniValue info(UniValue::VOBJ); + entryToJSON(mempool, info, e); + o.pushKV(_hash.ToString(), info); + } + return o; + } +}, + }; +} + +static RPCHelpMan getmempoolentry() +{ + return RPCHelpMan{"getmempoolentry", + "\nReturns mempool data for given transaction\n", + { + {"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The transaction id (must be in mempool)"}, + }, + RPCResult{ + RPCResult::Type::OBJ, "", "", MempoolEntryDescription()}, + RPCExamples{ + HelpExampleCli("getmempoolentry", "\"mytxid\"") + + HelpExampleRpc("getmempoolentry", "\"mytxid\"") + }, + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue +{ + uint256 hash = ParseHashV(request.params[0], "parameter 1"); + + const CTxMemPool& mempool = EnsureAnyMemPool(request.context); + LOCK(mempool.cs); + + CTxMemPool::txiter it = mempool.mapTx.find(hash); + if (it == mempool.mapTx.end()) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Transaction not in mempool"); + } + + const CTxMemPoolEntry &e = *it; + UniValue info(UniValue::VOBJ); + entryToJSON(mempool, info, e); + return info; +}, + }; +} + +static RPCHelpMan gettxspendingprevout() +{ + return RPCHelpMan{"gettxspendingprevout", + "Scans the mempool to find transactions spending any of the given outputs", + { + {"outputs", RPCArg::Type::ARR, RPCArg::Optional::NO, "The transaction outputs that we want to check, and within each, the txid (string) vout (numeric).", + { + {"", 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"}, + }, + }, + }, + }, + }, + RPCResult{ + RPCResult::Type::ARR, "", "", + { + {RPCResult::Type::OBJ, "", "", + { + {RPCResult::Type::STR_HEX, "txid", "the transaction id of the checked output"}, + {RPCResult::Type::NUM, "vout", "the vout value of the checked output"}, + {RPCResult::Type::STR_HEX, "spendingtxid", /*optional=*/true, "the transaction id of the mempool transaction spending this output (omitted if unspent)"}, + }}, + } + }, + RPCExamples{ + HelpExampleCli("gettxspendingprevout", "\"[{\\\"txid\\\":\\\"a08e6907dbbd3d809776dbfc5d82e371b764ed838b5655e72f463568df1aadf0\\\",\\\"vout\\\":3}]\"") + + HelpExampleRpc("gettxspendingprevout", "\"[{\\\"txid\\\":\\\"a08e6907dbbd3d809776dbfc5d82e371b764ed838b5655e72f463568df1aadf0\\\",\\\"vout\\\":3}]\"") + }, + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue + { + RPCTypeCheckArgument(request.params[0], UniValue::VARR); + const UniValue& output_params = request.params[0]; + if (output_params.empty()) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, outputs are missing"); + } + + std::vector<COutPoint> prevouts; + prevouts.reserve(output_params.size()); + + for (unsigned int idx = 0; idx < output_params.size(); idx++) { + const UniValue& o = output_params[idx].get_obj(); + + RPCTypeCheckObj(o, + { + {"txid", UniValueType(UniValue::VSTR)}, + {"vout", UniValueType(UniValue::VNUM)}, + }, /*fAllowNull=*/false, /*fStrict=*/true); + + const uint256 txid(ParseHashO(o, "txid")); + const int nOutput{find_value(o, "vout").getInt<int>()}; + if (nOutput < 0) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, vout cannot be negative"); + } + + prevouts.emplace_back(txid, nOutput); + } + + const CTxMemPool& mempool = EnsureAnyMemPool(request.context); + LOCK(mempool.cs); + + UniValue result{UniValue::VARR}; + + for (const COutPoint& prevout : prevouts) { + UniValue o(UniValue::VOBJ); + o.pushKV("txid", prevout.hash.ToString()); + o.pushKV("vout", (uint64_t)prevout.n); + + const CTransaction* spendingTx = mempool.GetConflictTx(prevout); + if (spendingTx != nullptr) { + o.pushKV("spendingtxid", spendingTx->GetHash().ToString()); + } + + result.push_back(o); + } + + return result; + }, + }; +} + +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.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())); + 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", + "Returns details on the active state of the TX memory pool.", + {}, + RPCResult{ + RPCResult::Type::OBJ, "", "", + { + {RPCResult::Type::BOOL, "loaded", "True if the mempool is fully loaded"}, + {RPCResult::Type::NUM, "size", "Current tx count"}, + {RPCResult::Type::NUM, "bytes", "Sum of all virtual transaction sizes as defined in BIP 141. Differs from actual serialized size because witness data is discounted"}, + {RPCResult::Type::NUM, "usage", "Total memory usage for the mempool"}, + {RPCResult::Type::STR_AMOUNT, "total_fee", "Total fees for the mempool in " + CURRENCY_UNIT + ", ignoring modified fees through prioritisetransaction"}, + {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, "incrementalrelayfee", "minimum fee rate increment for mempool limiting or BIP 125 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", "") + + HelpExampleRpc("getmempoolinfo", "") + }, + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue +{ + return MempoolInfoToJSON(EnsureAnyMemPool(request.context)); +}, + }; +} + +static RPCHelpMan savemempool() +{ + return RPCHelpMan{"savemempool", + "\nDumps the mempool to disk. It will fail until the previous dump is fully loaded.\n", + {}, + RPCResult{ + RPCResult::Type::OBJ, "", "", + { + {RPCResult::Type::STR, "filename", "the directory and file where the mempool was saved"}, + }}, + RPCExamples{ + HelpExampleCli("savemempool", "") + + HelpExampleRpc("savemempool", "") + }, + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue +{ + const ArgsManager& args{EnsureAnyArgsman(request.context)}; + const CTxMemPool& mempool = EnsureAnyMemPool(request.context); + + if (!mempool.GetLoadTried()) { + throw JSONRPCError(RPC_MISC_ERROR, "The mempool was not loaded yet"); + } + + 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", 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); + CChainState& 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[]{ + {"rawtransactions", &sendrawtransaction}, + {"rawtransactions", &testmempoolaccept}, + {"blockchain", &getmempoolancestors}, + {"blockchain", &getmempooldescendants}, + {"blockchain", &getmempoolentry}, + {"blockchain", &gettxspendingprevout}, + {"blockchain", &getmempoolinfo}, + {"blockchain", &getrawmempool}, + {"blockchain", &savemempool}, + {"hidden", &submitpackage}, + }; + for (const auto& c : commands) { + t.appendCommand(c.name, &c); + } +} diff --git a/src/rpc/mempool.h b/src/rpc/mempool.h new file mode 100644 index 0000000000..229d7d52dd --- /dev/null +++ b/src/rpc/mempool.h @@ -0,0 +1,17 @@ +// Copyright (c) 2017-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_RPC_MEMPOOL_H +#define BITCOIN_RPC_MEMPOOL_H + +class CTxMemPool; +class UniValue; + +/** Mempool information to JSON */ +UniValue MempoolInfoToJSON(const CTxMemPool& pool); + +/** Mempool to JSON */ +UniValue MempoolToJSON(const CTxMemPool& pool, bool verbose = false, bool include_mempool_sequence = false); + +#endif // BITCOIN_RPC_MEMPOOL_H diff --git a/src/rpc/mining.cpp b/src/rpc/mining.cpp index 1d1ae92c58..91feb2c24b 100644 --- a/src/rpc/mining.cpp +++ b/src/rpc/mining.cpp @@ -7,6 +7,7 @@ #include <chainparams.h> #include <consensus/amount.h> #include <consensus/consensus.h> +#include <consensus/merkle.h> #include <consensus/params.h> #include <consensus/validation.h> #include <core_io.h> @@ -16,7 +17,6 @@ #include <net.h> #include <node/context.h> #include <node/miner.h> -#include <policy/fees.h> #include <pow.h> #include <rpc/blockchain.h> #include <rpc/mining.h> @@ -27,9 +27,9 @@ #include <script/script.h> #include <script/signingprovider.h> #include <shutdown.h> +#include <timedata.h> #include <txmempool.h> #include <univalue.h> -#include <util/fees.h> #include <util/strencodings.h> #include <util/string.h> #include <util/system.h> @@ -43,7 +43,6 @@ using node::BlockAssembler; using node::CBlockTemplate; -using node::IncrementExtraNonce; using node::NodeContext; using node::RegenerateCommitments; using node::UpdateTime; @@ -111,23 +110,17 @@ static RPCHelpMan getnetworkhashps() { ChainstateManager& chainman = EnsureAnyChainman(request.context); LOCK(cs_main); - return GetNetworkHashPS(!request.params[0].isNull() ? request.params[0].get_int() : 120, !request.params[1].isNull() ? request.params[1].get_int() : -1, chainman.ActiveChain()); + return GetNetworkHashPS(!request.params[0].isNull() ? request.params[0].getInt<int>() : 120, !request.params[1].isNull() ? request.params[1].getInt<int>() : -1, chainman.ActiveChain()); }, }; } -static bool GenerateBlock(ChainstateManager& chainman, CBlock& block, uint64_t& max_tries, unsigned int& extra_nonce, uint256& block_hash) +static bool GenerateBlock(ChainstateManager& chainman, CBlock& block, uint64_t& max_tries, uint256& block_hash) { block_hash.SetNull(); + block.hashMerkleRoot = BlockMerkleRoot(block); - { - LOCK(cs_main); - IncrementExtraNonce(&block, chainman.ActiveChain().Tip(), extra_nonce); - } - - CChainParams chainparams(Params()); - - while (max_tries > 0 && block.nNonce < std::numeric_limits<uint32_t>::max() && !CheckProofOfWork(block.GetHash(), block.nBits, chainparams.GetConsensus()) && !ShutdownRequested()) { + while (max_tries > 0 && block.nNonce < std::numeric_limits<uint32_t>::max() && !CheckProofOfWork(block.GetHash(), block.nBits, chainman.GetConsensus()) && !ShutdownRequested()) { ++block.nNonce; --max_tries; } @@ -139,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(chainparams, shared_pblock, true, nullptr)) { + if (!chainman.ProcessNewBlock(shared_pblock, true, nullptr)) { throw JSONRPCError(RPC_INTERNAL_ERROR, "ProcessNewBlock, block not accepted"); } @@ -149,30 +142,20 @@ static bool GenerateBlock(ChainstateManager& chainman, CBlock& block, uint64_t& static UniValue generateBlocks(ChainstateManager& chainman, const CTxMemPool& mempool, const CScript& coinbase_script, int nGenerate, uint64_t nMaxTries) { - int nHeightEnd = 0; - int nHeight = 0; - - { // Don't keep cs_main locked - LOCK(cs_main); - nHeight = chainman.ActiveChain().Height(); - nHeightEnd = nHeight+nGenerate; - } - unsigned int nExtraNonce = 0; UniValue blockHashes(UniValue::VARR); - while (nHeight < nHeightEnd && !ShutdownRequested()) - { - std::unique_ptr<CBlockTemplate> pblocktemplate(BlockAssembler(chainman.ActiveChainstate(), mempool, Params()).CreateNewBlock(coinbase_script)); + while (nGenerate > 0 && !ShutdownRequested()) { + 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; uint256 block_hash; - if (!GenerateBlock(chainman, *pblock, nMaxTries, nExtraNonce, block_hash)) { + if (!GenerateBlock(chainman, *pblock, nMaxTries, block_hash)) { break; } if (!block_hash.IsNull()) { - ++nHeight; + --nGenerate; blockHashes.push_back(block_hash.GetHex()); } } @@ -233,8 +216,8 @@ static RPCHelpMan generatetodescriptor() "\nGenerate 11 blocks to mydesc\n" + HelpExampleCli("generatetodescriptor", "11 \"mydesc\"")}, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - const int num_blocks{request.params[0].get_int()}; - const uint64_t max_tries{request.params[2].isNull() ? DEFAULT_MAX_TRIES : request.params[2].get_int()}; + const int num_blocks{request.params[0].getInt<int>()}; + const uint64_t max_tries{request.params[2].isNull() ? DEFAULT_MAX_TRIES : request.params[2].getInt<int>()}; CScript coinbase_script; std::string error; @@ -280,8 +263,8 @@ static RPCHelpMan generatetoaddress() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - const int num_blocks{request.params[0].get_int()}; - const uint64_t max_tries{request.params[2].isNull() ? DEFAULT_MAX_TRIES : request.params[2].get_int()}; + const int num_blocks{request.params[0].getInt<int>()}; + const uint64_t max_tries{request.params[2].isNull() ? DEFAULT_MAX_TRIES : request.params[2].getInt<int>()}; CTxDestination destination = DecodeDestination(request.params[1].get_str()); if (!IsValidDestination(destination)) { @@ -365,15 +348,13 @@ static RPCHelpMan generateblock() } } - CChainParams chainparams(Params()); CBlock block; ChainstateManager& chainman = EnsureChainman(node); { LOCK(cs_main); - CTxMemPool empty_mempool; - std::unique_ptr<CBlockTemplate> blocktemplate(BlockAssembler(chainman.ActiveChainstate(), empty_mempool, chainparams).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"); } @@ -390,16 +371,15 @@ static RPCHelpMan generateblock() LOCK(cs_main); BlockValidationState state; - if (!TestBlockValidity(state, chainparams, chainman.ActiveChainstate(), block, chainman.m_blockman.LookupBlockIndex(block.hashPrevBlock), false, false)) { + if (!TestBlockValidity(state, chainman.GetParams(), chainman.ActiveChainstate(), block, chainman.m_blockman.LookupBlockIndex(block.hashPrevBlock), GetAdjustedTime, false, false)) { throw JSONRPCError(RPC_VERIFY_ERROR, strprintf("TestBlockValidity failed: %s", state.ToString())); } } uint256 block_hash; uint64_t max_tries{DEFAULT_MAX_TRIES}; - unsigned int extra_nonce{0}; - if (!GenerateBlock(chainman, block, max_tries, extra_nonce, block_hash) || block_hash.IsNull()) { + if (!GenerateBlock(chainman, block, max_tries, block_hash) || block_hash.IsNull()) { throw JSONRPCError(RPC_MISC_ERROR, "Failed to make block."); } @@ -446,7 +426,7 @@ static RPCHelpMan getmininginfo() obj.pushKV("difficulty", (double)GetDifficulty(active_chain.Tip())); obj.pushKV("networkhashps", getnetworkhashps().HandleRequest(request)); obj.pushKV("pooledtx", (uint64_t)mempool.size()); - obj.pushKV("chain", Params().NetworkIDString()); + obj.pushKV("chain", chainman.GetParams().NetworkIDString()); obj.pushKV("warnings", GetWarnings(false).original); return obj; }, @@ -479,7 +459,7 @@ static RPCHelpMan prioritisetransaction() LOCK(cs_main); uint256 hash(ParseHashV(request.params[0], "txid")); - CAmount nAmount = request.params[2].get_int64(); + CAmount nAmount = request.params[2].getInt<int64_t>(); if (!(request.params[1].isNull() || request.params[1].get_real() == 0)) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Priority is no longer supported, dummy argument to prioritisetransaction must be 0."); @@ -496,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()); @@ -618,7 +598,6 @@ static RPCHelpMan getblocktemplate() std::string strMode = "template"; UniValue lpval = NullUniValue; std::set<std::string> setClientRules; - int64_t nMaxVersionPreVB = -1; CChainState& active_chainstate = chainman.ActiveChainstate(); CChain& active_chain = active_chainstate.m_chain; if (!request.params[0].isNull()) @@ -660,7 +639,7 @@ static RPCHelpMan getblocktemplate() if (block.hashPrevBlock != pindexPrev->GetBlockHash()) return "inconclusive-not-best-prevblk"; BlockValidationState state; - TestBlockValidity(state, Params(), active_chainstate, block, pindexPrev, false, true); + TestBlockValidity(state, chainman.GetParams(), active_chainstate, block, pindexPrev, GetAdjustedTime, false, true); return BIP22ValidationResult(state); } @@ -670,19 +649,13 @@ 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.get_int64(); - } } } if (strMode != "template") throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid mode"); - if (!Params().IsTestChain()) { + if (!chainman.GetParams().IsTestChain()) { const CConnman& connman = EnsureConnman(node); if (connman.GetNodeCount(ConnectionDirection::Both) == 0) { throw JSONRPCError(RPC_CLIENT_NOT_CONNECTED, PACKAGE_NAME " is not connected!"); @@ -706,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)); @@ -743,7 +716,7 @@ static RPCHelpMan getblocktemplate() // TODO: Maybe recheck connections/IBD and (if something wrong) send an expires-immediately template to stop miners? } - const Consensus::Params& consensusParams = Params().GetConsensus(); + const Consensus::Params& consensusParams = chainman.GetParams().GetConsensus(); // GBT must be called with 'signet' set in the rules for signet chains if (consensusParams.signet_blocks && setClientRules.count("signet") != 1) { @@ -772,7 +745,7 @@ static RPCHelpMan getblocktemplate() // Create new block CScript scriptDummy = CScript() << OP_TRUE; - pblocktemplate = BlockAssembler(active_chainstate, mempool, Params()).CreateNewBlock(scriptDummy); + pblocktemplate = BlockAssembler{active_chainstate, &mempool}.CreateNewBlock(scriptDummy); if (!pblocktemplate) throw JSONRPCError(RPC_OUT_OF_MEMORY, "Out of memory"); @@ -787,7 +760,7 @@ static RPCHelpMan getblocktemplate() pblock->nNonce = 0; // NOTE: If at some point we support pre-segwit miners post-segwit-activation, this needs to take segwit support into consideration - const bool fPreSegWit = !DeploymentActiveAfter(pindexPrev, consensusParams, Consensus::DEPLOYMENT_SEGWIT); + const bool fPreSegWit = !DeploymentActiveAfter(pindexPrev, chainman, Consensus::DEPLOYMENT_SEGWIT); UniValue aCaps(UniValue::VARR); aCaps.push_back("proposal"); @@ -853,7 +826,7 @@ static RPCHelpMan getblocktemplate() UniValue vbavailable(UniValue::VOBJ); for (int j = 0; j < (int)Consensus::MAX_VERSION_BITS_DEPLOYMENTS; ++j) { Consensus::DeploymentPos pos = Consensus::DeploymentPos(j); - ThresholdState state = g_versionbitscache.State(pindexPrev, consensusParams, pos); + ThresholdState state = chainman.m_versionbitscache.State(pindexPrev, consensusParams, pos); switch (state) { case ThresholdState::DEFINED: case ThresholdState::FAILED: @@ -861,7 +834,7 @@ static RPCHelpMan getblocktemplate() break; case ThresholdState::LOCKED_IN: // Ensure bit is set in block version - pblock->nVersion |= g_versionbitscache.Mask(consensusParams, pos); + pblock->nVersion |= chainman.m_versionbitscache.Mask(consensusParams, pos); [[fallthrough]]; case ThresholdState::STARTED: { @@ -870,7 +843,7 @@ static RPCHelpMan getblocktemplate() if (setClientRules.find(vbinfo.name) == setClientRules.end()) { if (!vbinfo.gbt_force) { // If the client doesn't support this, don't indicate it in the [default] version - pblock->nVersion &= ~g_versionbitscache.Mask(consensusParams, pos); + pblock->nVersion &= ~chainman.m_versionbitscache.Mask(consensusParams, pos); } } break; @@ -883,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)); } } @@ -896,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); @@ -947,10 +911,10 @@ class submitblock_StateCatcher final : public CValidationInterface { public: uint256 hash; - bool found; + bool found{false}; BlockValidationState state; - explicit submitblock_StateCatcher(const uint256 &hashIn) : hash(hashIn), found(false), state() {} + explicit submitblock_StateCatcher(const uint256 &hashIn) : hash(hashIn), state() {} protected: void BlockChecked(const CBlock& block, const BlockValidationState& stateIn) override { @@ -1010,14 +974,14 @@ static RPCHelpMan submitblock() LOCK(cs_main); const CBlockIndex* pindex = chainman.m_blockman.LookupBlockIndex(block.hashPrevBlock); if (pindex) { - UpdateUncommittedBlockStructures(block, pindex, Params().GetConsensus()); + chainman.UpdateUncommittedBlockStructures(block, pindex); } } bool new_block; auto sc = std::make_shared<submitblock_StateCatcher>(block.GetHash()); RegisterSharedValidationInterface(sc); - bool accepted = chainman.ProcessNewBlock(Params(), blockptr, /*force_processing=*/true, /*new_block=*/&new_block); + bool accepted = chainman.ProcessNewBlock(blockptr, /*force_processing=*/true, /*new_block=*/&new_block); UnregisterSharedValidationInterface(sc); if (!new_block && accepted) { return "duplicate"; @@ -1059,8 +1023,8 @@ static RPCHelpMan submitheader() } BlockValidationState state; - chainman.ProcessNewBlockHeaders({h}, state, Params()); - if (state.IsValid()) return NullUniValue; + chainman.ProcessNewBlockHeaders({h}, state); + if (state.IsValid()) return UniValue::VNULL; if (state.IsError()) { throw JSONRPCError(RPC_VERIFY_ERROR, state.ToString()); } @@ -1069,225 +1033,21 @@ static RPCHelpMan submitheader() }; } -static RPCHelpMan estimatesmartfee() -{ - return RPCHelpMan{"estimatesmartfee", - "\nEstimates the approximate fee per kilobyte needed for a transaction to begin\n" - "confirmation within conf_target blocks if possible and return the number of blocks\n" - "for which the estimate is valid. Uses virtual transaction size as defined\n" - "in BIP 141 (witness data is discounted).\n", - { - {"conf_target", RPCArg::Type::NUM, RPCArg::Optional::NO, "Confirmation target in blocks (1 - 1008)"}, - {"estimate_mode", RPCArg::Type::STR, RPCArg::Default{"conservative"}, "The fee estimate mode.\n" - " Whether to return a more conservative estimate which also satisfies\n" - " a longer history. A conservative estimate potentially returns a\n" - " higher feerate and is more likely to be sufficient for the desired\n" - " target, but is not as responsive to short term drops in the\n" - " prevailing fee market. Must be one of (case insensitive):\n" - "\"" + FeeModes("\"\n\"") + "\""}, - }, - RPCResult{ - RPCResult::Type::OBJ, "", "", - { - {RPCResult::Type::NUM, "feerate", /*optional=*/true, "estimate fee rate in " + CURRENCY_UNIT + "/kvB (only present if no errors were encountered)"}, - {RPCResult::Type::ARR, "errors", /*optional=*/true, "Errors encountered during processing (if there are any)", - { - {RPCResult::Type::STR, "", "error"}, - }}, - {RPCResult::Type::NUM, "blocks", "block number where estimate was found\n" - "The request target will be clamped between 2 and the highest target\n" - "fee estimation is able to return based on how long it has been running.\n" - "An error is returned if not enough transactions and blocks\n" - "have been observed to make an estimate for any number of blocks."}, - }}, - RPCExamples{ - HelpExampleCli("estimatesmartfee", "6") + - HelpExampleRpc("estimatesmartfee", "6") - }, - [&](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); - const CTxMemPool& mempool = EnsureMemPool(node); - - unsigned int max_target = fee_estimator.HighestTargetTracked(FeeEstimateHorizon::LONG_HALFLIFE); - unsigned int conf_target = ParseConfirmTarget(request.params[0], max_target); - bool conservative = true; - if (!request.params[1].isNull()) { - FeeEstimateMode fee_mode; - if (!FeeModeFromString(request.params[1].get_str(), fee_mode)) { - throw JSONRPCError(RPC_INVALID_PARAMETER, InvalidEstimateModeErrorMessage()); - } - if (fee_mode == FeeEstimateMode::ECONOMICAL) conservative = false; - } - - UniValue result(UniValue::VOBJ); - UniValue errors(UniValue::VARR); - 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}; - feeRate = std::max({feeRate, min_mempool_feerate, min_relay_feerate}); - result.pushKV("feerate", ValueFromAmount(feeRate.GetFeePerK())); - } else { - errors.push_back("Insufficient data or no feerate found"); - result.pushKV("errors", errors); - } - result.pushKV("blocks", feeCalc.returnedTarget); - return result; -}, - }; -} - -static RPCHelpMan estimaterawfee() -{ - return RPCHelpMan{"estimaterawfee", - "\nWARNING: This interface is unstable and may disappear or change!\n" - "\nWARNING: This is an advanced API call that is tightly coupled to the specific\n" - " implementation of fee estimation. The parameters it can be called with\n" - " and the results it returns will change if the internal implementation changes.\n" - "\nEstimates the approximate fee per kilobyte needed for a transaction to begin\n" - "confirmation within conf_target blocks if possible. Uses virtual transaction size as\n" - "defined in BIP 141 (witness data is discounted).\n", - { - {"conf_target", RPCArg::Type::NUM, RPCArg::Optional::NO, "Confirmation target in blocks (1 - 1008)"}, - {"threshold", RPCArg::Type::NUM, RPCArg::Default{0.95}, "The proportion of transactions in a given feerate range that must have been\n" - " confirmed within conf_target in order to consider those feerates as high enough and proceed to check\n" - " lower buckets."}, - }, - RPCResult{ - RPCResult::Type::OBJ, "", "Results are returned for any horizon which tracks blocks up to the confirmation target", - { - {RPCResult::Type::OBJ, "short", /*optional=*/true, "estimate for short time horizon", - { - {RPCResult::Type::NUM, "feerate", /*optional=*/true, "estimate fee rate in " + CURRENCY_UNIT + "/kvB"}, - {RPCResult::Type::NUM, "decay", "exponential decay (per block) for historical moving average of confirmation data"}, - {RPCResult::Type::NUM, "scale", "The resolution of confirmation targets at this time horizon"}, - {RPCResult::Type::OBJ, "pass", /*optional=*/true, "information about the lowest range of feerates to succeed in meeting the threshold", - { - {RPCResult::Type::NUM, "startrange", "start of feerate range"}, - {RPCResult::Type::NUM, "endrange", "end of feerate range"}, - {RPCResult::Type::NUM, "withintarget", "number of txs over history horizon in the feerate range that were confirmed within target"}, - {RPCResult::Type::NUM, "totalconfirmed", "number of txs over history horizon in the feerate range that were confirmed at any point"}, - {RPCResult::Type::NUM, "inmempool", "current number of txs in mempool in the feerate range unconfirmed for at least target blocks"}, - {RPCResult::Type::NUM, "leftmempool", "number of txs over history horizon in the feerate range that left mempool unconfirmed after target"}, - }}, - {RPCResult::Type::OBJ, "fail", /*optional=*/true, "information about the highest range of feerates to fail to meet the threshold", - { - {RPCResult::Type::ELISION, "", ""}, - }}, - {RPCResult::Type::ARR, "errors", /*optional=*/true, "Errors encountered during processing (if there are any)", - { - {RPCResult::Type::STR, "error", ""}, - }}, - }}, - {RPCResult::Type::OBJ, "medium", /*optional=*/true, "estimate for medium time horizon", - { - {RPCResult::Type::ELISION, "", ""}, - }}, - {RPCResult::Type::OBJ, "long", /*optional=*/true, "estimate for long time horizon", - { - {RPCResult::Type::ELISION, "", ""}, - }}, - }}, - RPCExamples{ - HelpExampleCli("estimaterawfee", "6 0.9") - }, - [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue +void RegisterMiningRPCCommands(CRPCTable& t) { - RPCTypeCheck(request.params, {UniValue::VNUM, UniValue::VNUM}, true); - RPCTypeCheckArgument(request.params[0], UniValue::VNUM); - - CBlockPolicyEstimator& fee_estimator = EnsureAnyFeeEstimator(request.context); - - unsigned int max_target = fee_estimator.HighestTargetTracked(FeeEstimateHorizon::LONG_HALFLIFE); - unsigned int conf_target = ParseConfirmTarget(request.params[0], max_target); - double threshold = 0.95; - if (!request.params[1].isNull()) { - threshold = request.params[1].get_real(); - } - if (threshold < 0 || threshold > 1) { - throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid threshold"); - } - - UniValue result(UniValue::VOBJ); - - for (const FeeEstimateHorizon horizon : ALL_FEE_ESTIMATE_HORIZONS) { - CFeeRate feeRate; - EstimationResult buckets; - - // Only output results for horizons which track the target - if (conf_target > fee_estimator.HighestTargetTracked(horizon)) continue; - - feeRate = fee_estimator.estimateRawFee(conf_target, threshold, horizon, &buckets); - UniValue horizon_result(UniValue::VOBJ); - UniValue errors(UniValue::VARR); - UniValue passbucket(UniValue::VOBJ); - passbucket.pushKV("startrange", round(buckets.pass.start)); - passbucket.pushKV("endrange", round(buckets.pass.end)); - passbucket.pushKV("withintarget", round(buckets.pass.withinTarget * 100.0) / 100.0); - passbucket.pushKV("totalconfirmed", round(buckets.pass.totalConfirmed * 100.0) / 100.0); - passbucket.pushKV("inmempool", round(buckets.pass.inMempool * 100.0) / 100.0); - passbucket.pushKV("leftmempool", round(buckets.pass.leftMempool * 100.0) / 100.0); - UniValue failbucket(UniValue::VOBJ); - failbucket.pushKV("startrange", round(buckets.fail.start)); - failbucket.pushKV("endrange", round(buckets.fail.end)); - failbucket.pushKV("withintarget", round(buckets.fail.withinTarget * 100.0) / 100.0); - failbucket.pushKV("totalconfirmed", round(buckets.fail.totalConfirmed * 100.0) / 100.0); - failbucket.pushKV("inmempool", round(buckets.fail.inMempool * 100.0) / 100.0); - failbucket.pushKV("leftmempool", round(buckets.fail.leftMempool * 100.0) / 100.0); - - // CFeeRate(0) is used to indicate error as a return value from estimateRawFee - if (feeRate != CFeeRate(0)) { - horizon_result.pushKV("feerate", ValueFromAmount(feeRate.GetFeePerK())); - horizon_result.pushKV("decay", buckets.decay); - horizon_result.pushKV("scale", (int)buckets.scale); - horizon_result.pushKV("pass", passbucket); - // buckets.fail.start == -1 indicates that all buckets passed, there is no fail bucket to output - if (buckets.fail.start != -1) horizon_result.pushKV("fail", failbucket); - } else { - // Output only information that is still meaningful in the event of error - horizon_result.pushKV("decay", buckets.decay); - horizon_result.pushKV("scale", (int)buckets.scale); - horizon_result.pushKV("fail", failbucket); - errors.push_back("Insufficient data or no feerate found which meets threshold"); - horizon_result.pushKV("errors",errors); - } - result.pushKV(StringForFeeEstimateHorizon(horizon), horizon_result); - } - return result; -}, + static const CRPCCommand commands[]{ + {"mining", &getnetworkhashps}, + {"mining", &getmininginfo}, + {"mining", &prioritisetransaction}, + {"mining", &getblocktemplate}, + {"mining", &submitblock}, + {"mining", &submitheader}, + + {"hidden", &generatetoaddress}, + {"hidden", &generatetodescriptor}, + {"hidden", &generateblock}, + {"hidden", &generate}, }; -} - -void RegisterMiningRPCCommands(CRPCTable &t) -{ -// clang-format off -static const CRPCCommand commands[] = -{ // category actor (function) - // --------------------- ----------------------- - { "mining", &getnetworkhashps, }, - { "mining", &getmininginfo, }, - { "mining", &prioritisetransaction, }, - { "mining", &getblocktemplate, }, - { "mining", &submitblock, }, - { "mining", &submitheader, }, - - - { "hidden", &generatetoaddress, }, - { "hidden", &generatetodescriptor, }, - { "hidden", &generateblock, }, - - { "util", &estimatesmartfee, }, - - { "hidden", &estimaterawfee, }, - { "hidden", &generate, }, -}; -// clang-format on for (const auto& c : commands) { t.appendCommand(c.name, &c); } diff --git a/src/rpc/misc.cpp b/src/rpc/misc.cpp deleted file mode 100644 index 8d7b48d697..0000000000 --- a/src/rpc/misc.cpp +++ /dev/null @@ -1,833 +0,0 @@ -// Copyright (c) 2010 Satoshi Nakamoto -// Copyright (c) 2009-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 <httpserver.h> -#include <index/blockfilterindex.h> -#include <index/coinstatsindex.h> -#include <index/txindex.h> -#include <interfaces/chain.h> -#include <interfaces/echo.h> -#include <interfaces/init.h> -#include <interfaces/ipc.h> -#include <key_io.h> -#include <node/context.h> -#include <outputtype.h> -#include <rpc/blockchain.h> -#include <rpc/server.h> -#include <rpc/server_util.h> -#include <rpc/util.h> -#include <scheduler.h> -#include <script/descriptor.h> -#include <util/check.h> -#include <util/message.h> // For MessageSign(), MessageVerify() -#include <util/strencodings.h> -#include <util/syscall_sandbox.h> -#include <util/system.h> - -#include <optional> -#include <stdint.h> -#include <tuple> -#ifdef HAVE_MALLOC_INFO -#include <malloc.h> -#endif - -#include <univalue.h> - -using node::NodeContext; - -static RPCHelpMan validateaddress() -{ - return RPCHelpMan{ - "validateaddress", - "\nReturn information about the given bitcoin address.\n", - { - {"address", RPCArg::Type::STR, RPCArg::Optional::NO, "The bitcoin address to validate"}, - }, - RPCResult{ - RPCResult::Type::OBJ, "", "", - { - {RPCResult::Type::BOOL, "isvalid", "If the address is valid or not"}, - {RPCResult::Type::STR, "address", /*optional=*/true, "The bitcoin address validated"}, - {RPCResult::Type::STR_HEX, "scriptPubKey", /*optional=*/true, "The hex-encoded scriptPubKey generated by the address"}, - {RPCResult::Type::BOOL, "isscript", /*optional=*/true, "If the key is a script"}, - {RPCResult::Type::BOOL, "iswitness", /*optional=*/true, "If the address is a witness address"}, - {RPCResult::Type::NUM, "witness_version", /*optional=*/true, "The version number of the witness program"}, - {RPCResult::Type::STR_HEX, "witness_program", /*optional=*/true, "The hex value of the witness program"}, - {RPCResult::Type::STR, "error", /*optional=*/true, "Error message, if any"}, - {RPCResult::Type::ARR, "error_locations", /*optional=*/true, "Indices of likely error locations in address, if known (e.g. Bech32 errors)", - { - {RPCResult::Type::NUM, "index", "index of a potential error"}, - }}, - } - }, - RPCExamples{ - HelpExampleCli("validateaddress", "\"" + EXAMPLE_ADDRESS[0] + "\"") + - HelpExampleRpc("validateaddress", "\"" + EXAMPLE_ADDRESS[0] + "\"") - }, - [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue -{ - std::string error_msg; - std::vector<int> error_locations; - CTxDestination dest = DecodeDestination(request.params[0].get_str(), error_msg, &error_locations); - const bool isValid = IsValidDestination(dest); - CHECK_NONFATAL(isValid == error_msg.empty()); - - UniValue ret(UniValue::VOBJ); - ret.pushKV("isvalid", isValid); - if (isValid) { - std::string currentAddress = EncodeDestination(dest); - ret.pushKV("address", currentAddress); - - CScript scriptPubKey = GetScriptForDestination(dest); - ret.pushKV("scriptPubKey", HexStr(scriptPubKey)); - - UniValue detail = DescribeAddress(dest); - ret.pushKVs(detail); - } else { - UniValue error_indices(UniValue::VARR); - for (int i : error_locations) error_indices.push_back(i); - ret.pushKV("error_locations", error_indices); - ret.pushKV("error", error_msg); - } - - return ret; -}, - }; -} - -static RPCHelpMan createmultisig() -{ - return RPCHelpMan{"createmultisig", - "\nCreates a multi-signature address with n signature of m keys required.\n" - "It returns a json object with the address and redeemScript.\n", - { - {"nrequired", RPCArg::Type::NUM, RPCArg::Optional::NO, "The number of required signatures out of the n keys."}, - {"keys", RPCArg::Type::ARR, RPCArg::Optional::NO, "The hex-encoded public keys.", - { - {"key", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, "The hex-encoded public key"}, - }}, - {"address_type", RPCArg::Type::STR, RPCArg::Default{"legacy"}, "The address type to use. Options are \"legacy\", \"p2sh-segwit\", and \"bech32\"."}, - }, - RPCResult{ - RPCResult::Type::OBJ, "", "", - { - {RPCResult::Type::STR, "address", "The value of the new multisig address."}, - {RPCResult::Type::STR_HEX, "redeemScript", "The string value of the hex-encoded redemption script."}, - {RPCResult::Type::STR, "descriptor", "The descriptor for this multisig"}, - {RPCResult::Type::ARR, "warnings", /* optional */ true, "Any warnings resulting from the creation of this multisig", - { - {RPCResult::Type::STR, "", ""}, - }}, - } - }, - RPCExamples{ - "\nCreate a multisig address from 2 public keys\n" - + HelpExampleCli("createmultisig", "2 \"[\\\"03789ed0bb717d88f7d321a368d905e7430207ebbd82bd342cf11ae157a7ace5fd\\\",\\\"03dbc6764b8884a92e871274b87583e6d5c2a58819473e17e107ef3f6aa5a61626\\\"]\"") + - "\nAs a JSON-RPC call\n" - + HelpExampleRpc("createmultisig", "2, [\"03789ed0bb717d88f7d321a368d905e7430207ebbd82bd342cf11ae157a7ace5fd\",\"03dbc6764b8884a92e871274b87583e6d5c2a58819473e17e107ef3f6aa5a61626\"]") - }, - [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue -{ - int required = request.params[0].get_int(); - - // Get the public keys - const UniValue& keys = request.params[1].get_array(); - std::vector<CPubKey> pubkeys; - for (unsigned int i = 0; i < keys.size(); ++i) { - if (IsHex(keys[i].get_str()) && (keys[i].get_str().length() == 66 || keys[i].get_str().length() == 130)) { - pubkeys.push_back(HexToPubKey(keys[i].get_str())); - } else { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Invalid public key: %s\n.", keys[i].get_str())); - } - } - - // Get the output type - OutputType output_type = OutputType::LEGACY; - if (!request.params[2].isNull()) { - std::optional<OutputType> parsed = ParseOutputType(request.params[2].get_str()); - if (!parsed) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Unknown address type '%s'", request.params[2].get_str())); - } else if (parsed.value() == OutputType::BECH32M) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "createmultisig cannot create bech32m multisig addresses"); - } - output_type = parsed.value(); - } - - // Construct using pay-to-script-hash: - FillableSigningProvider keystore; - CScript inner; - const CTxDestination dest = AddAndGetMultisigDestination(required, pubkeys, output_type, keystore, inner); - - // Make the descriptor - std::unique_ptr<Descriptor> descriptor = InferDescriptor(GetScriptForDestination(dest), keystore); - - UniValue result(UniValue::VOBJ); - result.pushKV("address", EncodeDestination(dest)); - result.pushKV("redeemScript", HexStr(inner)); - result.pushKV("descriptor", descriptor->ToString()); - - UniValue warnings(UniValue::VARR); - if (!request.params[2].isNull() && OutputTypeFromDestination(dest) != output_type) { - // Only warns if the user has explicitly chosen an address type we cannot generate - warnings.push_back("Unable to make chosen address type, please ensure no uncompressed public keys are present."); - } - if (warnings.size()) result.pushKV("warnings", warnings); - - return result; -}, - }; -} - -static RPCHelpMan getdescriptorinfo() -{ - const std::string EXAMPLE_DESCRIPTOR = "wpkh([d34db33f/84h/0h/0h]0279be667ef9dcbbac55a06295Ce870b07029Bfcdb2dce28d959f2815b16f81798)"; - - return RPCHelpMan{"getdescriptorinfo", - {"\nAnalyses a descriptor.\n"}, - { - {"descriptor", RPCArg::Type::STR, RPCArg::Optional::NO, "The descriptor."}, - }, - RPCResult{ - RPCResult::Type::OBJ, "", "", - { - {RPCResult::Type::STR, "descriptor", "The descriptor in canonical form, without private keys"}, - {RPCResult::Type::STR, "checksum", "The checksum for the input descriptor"}, - {RPCResult::Type::BOOL, "isrange", "Whether the descriptor is ranged"}, - {RPCResult::Type::BOOL, "issolvable", "Whether the descriptor is solvable"}, - {RPCResult::Type::BOOL, "hasprivatekeys", "Whether the input descriptor contained at least one private key"}, - } - }, - RPCExamples{ - "Analyse a descriptor\n" + - HelpExampleCli("getdescriptorinfo", "\"" + EXAMPLE_DESCRIPTOR + "\"") + - HelpExampleRpc("getdescriptorinfo", "\"" + EXAMPLE_DESCRIPTOR + "\"") - }, - [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue -{ - RPCTypeCheck(request.params, {UniValue::VSTR}); - - FlatSigningProvider provider; - std::string error; - auto desc = Parse(request.params[0].get_str(), provider, error); - if (!desc) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, error); - } - - UniValue result(UniValue::VOBJ); - result.pushKV("descriptor", desc->ToString()); - result.pushKV("checksum", GetDescriptorChecksum(request.params[0].get_str())); - result.pushKV("isrange", desc->IsRange()); - result.pushKV("issolvable", desc->IsSolvable()); - result.pushKV("hasprivatekeys", provider.keys.size() > 0); - return result; -}, - }; -} - -static RPCHelpMan deriveaddresses() -{ - const std::string EXAMPLE_DESCRIPTOR = "wpkh([d34db33f/84h/0h/0h]xpub6DJ2dNUysrn5Vt36jH2KLBT2i1auw1tTSSomg8PhqNiUtx8QX2SvC9nrHu81fT41fvDUnhMjEzQgXnQjKEu3oaqMSzhSrHMxyyoEAmUHQbY/0/*)#cjjspncu"; - - return RPCHelpMan{"deriveaddresses", - {"\nDerives one or more addresses corresponding to an output descriptor.\n" - "Examples of output descriptors are:\n" - " pkh(<pubkey>) P2PKH outputs for the given pubkey\n" - " wpkh(<pubkey>) Native segwit P2PKH outputs for the given pubkey\n" - " sh(multi(<n>,<pubkey>,<pubkey>,...)) P2SH-multisig outputs for the given threshold and pubkeys\n" - " raw(<hex script>) Outputs whose scriptPubKey equals the specified hex scripts\n" - "\nIn the above, <pubkey> either refers to a fixed public key in hexadecimal notation, or to an xpub/xprv optionally followed by one\n" - "or more path elements separated by \"/\", where \"h\" represents a hardened child key.\n" - "For more information on output descriptors, see the documentation in the doc/descriptors.md file.\n"}, - { - {"descriptor", RPCArg::Type::STR, RPCArg::Optional::NO, "The descriptor."}, - {"range", RPCArg::Type::RANGE, RPCArg::Optional::OMITTED_NAMED_ARG, "If a ranged descriptor is used, this specifies the end or the range (in [begin,end] notation) to derive."}, - }, - RPCResult{ - RPCResult::Type::ARR, "", "", - { - {RPCResult::Type::STR, "address", "the derived addresses"}, - } - }, - RPCExamples{ - "First three native segwit receive addresses\n" + - HelpExampleCli("deriveaddresses", "\"" + EXAMPLE_DESCRIPTOR + "\" \"[0,2]\"") + - HelpExampleRpc("deriveaddresses", "\"" + EXAMPLE_DESCRIPTOR + "\", \"[0,2]\"") - }, - [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue -{ - RPCTypeCheck(request.params, {UniValue::VSTR, UniValueType()}); // Range argument is checked later - const std::string desc_str = request.params[0].get_str(); - - int64_t range_begin = 0; - int64_t range_end = 0; - - if (request.params.size() >= 2 && !request.params[1].isNull()) { - std::tie(range_begin, range_end) = ParseDescriptorRange(request.params[1]); - } - - FlatSigningProvider key_provider; - std::string error; - auto desc = Parse(desc_str, key_provider, error, /* require_checksum = */ true); - if (!desc) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, error); - } - - if (!desc->IsRange() && request.params.size() > 1) { - throw JSONRPCError(RPC_INVALID_PARAMETER, "Range should not be specified for an un-ranged descriptor"); - } - - if (desc->IsRange() && request.params.size() == 1) { - throw JSONRPCError(RPC_INVALID_PARAMETER, "Range must be specified for a ranged descriptor"); - } - - UniValue addresses(UniValue::VARR); - - for (int i = range_begin; i <= range_end; ++i) { - FlatSigningProvider provider; - std::vector<CScript> scripts; - if (!desc->Expand(i, key_provider, scripts, provider)) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Cannot derive script without private keys"); - } - - for (const CScript &script : scripts) { - CTxDestination dest; - if (!ExtractDestination(script, dest)) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Descriptor does not have a corresponding address"); - } - - addresses.push_back(EncodeDestination(dest)); - } - } - - // This should not be possible, but an assert seems overkill: - if (addresses.empty()) { - throw JSONRPCError(RPC_MISC_ERROR, "Unexpected empty result"); - } - - return addresses; -}, - }; -} - -static RPCHelpMan verifymessage() -{ - return RPCHelpMan{"verifymessage", - "Verify a signed message.", - { - {"address", RPCArg::Type::STR, RPCArg::Optional::NO, "The bitcoin address to use for the signature."}, - {"signature", RPCArg::Type::STR, RPCArg::Optional::NO, "The signature provided by the signer in base 64 encoding (see signmessage)."}, - {"message", RPCArg::Type::STR, RPCArg::Optional::NO, "The message that was signed."}, - }, - RPCResult{ - RPCResult::Type::BOOL, "", "If the signature is verified or not." - }, - RPCExamples{ - "\nUnlock the wallet for 30 seconds\n" - + HelpExampleCli("walletpassphrase", "\"mypassphrase\" 30") + - "\nCreate the signature\n" - + HelpExampleCli("signmessage", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\" \"my message\"") + - "\nVerify the signature\n" - + HelpExampleCli("verifymessage", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\" \"signature\" \"my message\"") + - "\nAs a JSON-RPC call\n" - + HelpExampleRpc("verifymessage", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\", \"signature\", \"my message\"") - }, - [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue -{ - LOCK(cs_main); - - std::string strAddress = request.params[0].get_str(); - std::string strSign = request.params[1].get_str(); - std::string strMessage = request.params[2].get_str(); - - switch (MessageVerify(strAddress, strSign, strMessage)) { - case MessageVerificationResult::ERR_INVALID_ADDRESS: - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid address"); - case MessageVerificationResult::ERR_ADDRESS_NO_KEY: - throw JSONRPCError(RPC_TYPE_ERROR, "Address does not refer to key"); - case MessageVerificationResult::ERR_MALFORMED_SIGNATURE: - throw JSONRPCError(RPC_TYPE_ERROR, "Malformed base64 encoding"); - case MessageVerificationResult::ERR_PUBKEY_NOT_RECOVERED: - case MessageVerificationResult::ERR_NOT_SIGNED: - return false; - case MessageVerificationResult::OK: - return true; - } - - return false; -}, - }; -} - -static RPCHelpMan signmessagewithprivkey() -{ - return RPCHelpMan{"signmessagewithprivkey", - "\nSign a message with the private key of an address\n", - { - {"privkey", RPCArg::Type::STR, RPCArg::Optional::NO, "The private key to sign the message with."}, - {"message", RPCArg::Type::STR, RPCArg::Optional::NO, "The message to create a signature of."}, - }, - RPCResult{ - RPCResult::Type::STR, "signature", "The signature of the message encoded in base 64" - }, - RPCExamples{ - "\nCreate the signature\n" - + HelpExampleCli("signmessagewithprivkey", "\"privkey\" \"my message\"") + - "\nVerify the signature\n" - + HelpExampleCli("verifymessage", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\" \"signature\" \"my message\"") + - "\nAs a JSON-RPC call\n" - + HelpExampleRpc("signmessagewithprivkey", "\"privkey\", \"my message\"") - }, - [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue -{ - std::string strPrivkey = request.params[0].get_str(); - std::string strMessage = request.params[1].get_str(); - - CKey key = DecodeSecret(strPrivkey); - if (!key.IsValid()) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid private key"); - } - - std::string signature; - - if (!MessageSign(key, strMessage, signature)) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Sign failed"); - } - - return signature; -}, - }; -} - -static RPCHelpMan setmocktime() -{ - return RPCHelpMan{"setmocktime", - "\nSet the local time to given timestamp (-regtest only)\n", - { - {"timestamp", RPCArg::Type::NUM, RPCArg::Optional::NO, UNIX_EPOCH_TIME + "\n" - "Pass 0 to go back to using the system time."}, - }, - RPCResult{RPCResult::Type::NONE, "", ""}, - RPCExamples{""}, - [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue -{ - if (!Params().IsMockableChain()) { - throw std::runtime_error("setmocktime is for regression testing (-regtest mode) only"); - } - - // For now, don't change mocktime if we're in the middle of validation, as - // this could have an effect on mempool time-based eviction, as well as - // IsCurrentForFeeEstimation() and IsInitialBlockDownload(). - // TODO: figure out the right way to synchronize around mocktime, and - // ensure all call sites of GetTime() are accessing this safely. - LOCK(cs_main); - - RPCTypeCheck(request.params, {UniValue::VNUM}); - const int64_t time{request.params[0].get_int64()}; - if (time < 0) { - throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Mocktime cannot be negative: %s.", time)); - } - SetMockTime(time); - auto node_context = util::AnyPtr<NodeContext>(request.context); - if (node_context) { - for (const auto& chain_client : node_context->chain_clients) { - chain_client->setMockTime(time); - } - } - - return NullUniValue; -}, - }; -} - -#if defined(USE_SYSCALL_SANDBOX) -static RPCHelpMan invokedisallowedsyscall() -{ - return RPCHelpMan{ - "invokedisallowedsyscall", - "\nInvoke a disallowed syscall to trigger a syscall sandbox violation. Used for testing purposes.\n", - {}, - RPCResult{RPCResult::Type::NONE, "", ""}, - RPCExamples{ - HelpExampleCli("invokedisallowedsyscall", "") + HelpExampleRpc("invokedisallowedsyscall", "")}, - [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - if (!Params().IsTestChain()) { - throw std::runtime_error("invokedisallowedsyscall is used for testing only."); - } - TestDisallowedSandboxCall(); - return NullUniValue; - }, - }; -} -#endif // USE_SYSCALL_SANDBOX - -static RPCHelpMan mockscheduler() -{ - return RPCHelpMan{"mockscheduler", - "\nBump the scheduler into the future (-regtest only)\n", - { - {"delta_time", RPCArg::Type::NUM, RPCArg::Optional::NO, "Number of seconds to forward the scheduler into the future." }, - }, - RPCResult{RPCResult::Type::NONE, "", ""}, - RPCExamples{""}, - [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue -{ - if (!Params().IsMockableChain()) { - throw std::runtime_error("mockscheduler is for regression testing (-regtest mode) only"); - } - - // check params are valid values - RPCTypeCheck(request.params, {UniValue::VNUM}); - int64_t delta_seconds = request.params[0].get_int64(); - if (delta_seconds <= 0 || delta_seconds > 3600) { - throw std::runtime_error("delta_time must be between 1 and 3600 seconds (1 hr)"); - } - - auto node_context = util::AnyPtr<NodeContext>(request.context); - // protect against null pointer dereference - CHECK_NONFATAL(node_context); - CHECK_NONFATAL(node_context->scheduler); - node_context->scheduler->MockForward(std::chrono::seconds(delta_seconds)); - - return NullUniValue; -}, - }; -} - -static UniValue RPCLockedMemoryInfo() -{ - LockedPool::Stats stats = LockedPoolManager::Instance().stats(); - UniValue obj(UniValue::VOBJ); - obj.pushKV("used", uint64_t(stats.used)); - obj.pushKV("free", uint64_t(stats.free)); - obj.pushKV("total", uint64_t(stats.total)); - obj.pushKV("locked", uint64_t(stats.locked)); - obj.pushKV("chunks_used", uint64_t(stats.chunks_used)); - obj.pushKV("chunks_free", uint64_t(stats.chunks_free)); - return obj; -} - -#ifdef HAVE_MALLOC_INFO -static std::string RPCMallocInfo() -{ - char *ptr = nullptr; - size_t size = 0; - FILE *f = open_memstream(&ptr, &size); - if (f) { - malloc_info(0, f); - fclose(f); - if (ptr) { - std::string rv(ptr, size); - free(ptr); - return rv; - } - } - return ""; -} -#endif - -static RPCHelpMan getmemoryinfo() -{ - /* Please, avoid using the word "pool" here in the RPC interface or help, - * as users will undoubtedly confuse it with the other "memory pool" - */ - return RPCHelpMan{"getmemoryinfo", - "Returns an object containing information about memory usage.\n", - { - {"mode", RPCArg::Type::STR, RPCArg::Default{"stats"}, "determines what kind of information is returned.\n" - " - \"stats\" returns general statistics about memory usage in the daemon.\n" - " - \"mallocinfo\" returns an XML string describing low-level heap state (only available if compiled with glibc 2.10+)."}, - }, - { - RPCResult{"mode \"stats\"", - RPCResult::Type::OBJ, "", "", - { - {RPCResult::Type::OBJ, "locked", "Information about locked memory manager", - { - {RPCResult::Type::NUM, "used", "Number of bytes used"}, - {RPCResult::Type::NUM, "free", "Number of bytes available in current arenas"}, - {RPCResult::Type::NUM, "total", "Total number of bytes managed"}, - {RPCResult::Type::NUM, "locked", "Amount of bytes that succeeded locking. If this number is smaller than total, locking pages failed at some point and key data could be swapped to disk."}, - {RPCResult::Type::NUM, "chunks_used", "Number allocated chunks"}, - {RPCResult::Type::NUM, "chunks_free", "Number unused chunks"}, - }}, - } - }, - RPCResult{"mode \"mallocinfo\"", - RPCResult::Type::STR, "", "\"<malloc version=\"1\">...\"" - }, - }, - RPCExamples{ - HelpExampleCli("getmemoryinfo", "") - + HelpExampleRpc("getmemoryinfo", "") - }, - [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue -{ - std::string mode = request.params[0].isNull() ? "stats" : request.params[0].get_str(); - if (mode == "stats") { - UniValue obj(UniValue::VOBJ); - obj.pushKV("locked", RPCLockedMemoryInfo()); - return obj; - } else if (mode == "mallocinfo") { -#ifdef HAVE_MALLOC_INFO - return RPCMallocInfo(); -#else - throw JSONRPCError(RPC_INVALID_PARAMETER, "mallocinfo mode not available"); -#endif - } else { - throw JSONRPCError(RPC_INVALID_PARAMETER, "unknown mode " + mode); - } -}, - }; -} - -static void EnableOrDisableLogCategories(UniValue cats, bool enable) { - cats = cats.get_array(); - for (unsigned int i = 0; i < cats.size(); ++i) { - std::string cat = cats[i].get_str(); - - bool success; - if (enable) { - success = LogInstance().EnableCategory(cat); - } else { - success = LogInstance().DisableCategory(cat); - } - - if (!success) { - throw JSONRPCError(RPC_INVALID_PARAMETER, "unknown logging category " + cat); - } - } -} - -static RPCHelpMan logging() -{ - return RPCHelpMan{"logging", - "Gets and sets the logging configuration.\n" - "When called without an argument, returns the list of categories with status that are currently being debug logged or not.\n" - "When called with arguments, adds or removes categories from debug logging and return the lists above.\n" - "The arguments are evaluated in order \"include\", \"exclude\".\n" - "If an item is both included and excluded, it will thus end up being excluded.\n" - "The valid logging categories are: " + LogInstance().LogCategoriesString() + "\n" - "In addition, the following are available as category names with special meanings:\n" - " - \"all\", \"1\" : represent all logging categories.\n" - " - \"none\", \"0\" : even if other logging categories are specified, ignore all of them.\n" - , - { - {"include", RPCArg::Type::ARR, RPCArg::Optional::OMITTED_NAMED_ARG, "The categories to add to debug logging", - { - {"include_category", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "the valid logging category"}, - }}, - {"exclude", RPCArg::Type::ARR, RPCArg::Optional::OMITTED_NAMED_ARG, "The categories to remove from debug logging", - { - {"exclude_category", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "the valid logging category"}, - }}, - }, - RPCResult{ - RPCResult::Type::OBJ_DYN, "", "keys are the logging categories, and values indicates its status", - { - {RPCResult::Type::BOOL, "category", "if being debug logged or not. false:inactive, true:active"}, - } - }, - RPCExamples{ - HelpExampleCli("logging", "\"[\\\"all\\\"]\" \"[\\\"http\\\"]\"") - + HelpExampleRpc("logging", "[\"all\"], [\"libevent\"]") - }, - [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue -{ - uint32_t original_log_categories = LogInstance().GetCategoryMask(); - if (request.params[0].isArray()) { - EnableOrDisableLogCategories(request.params[0], true); - } - if (request.params[1].isArray()) { - EnableOrDisableLogCategories(request.params[1], false); - } - uint32_t updated_log_categories = LogInstance().GetCategoryMask(); - uint32_t changed_log_categories = original_log_categories ^ updated_log_categories; - - // Update libevent logging if BCLog::LIBEVENT has changed. - // If the library version doesn't allow it, UpdateHTTPServerLogging() returns false, - // in which case we should clear the BCLog::LIBEVENT flag. - // Throw an error if the user has explicitly asked to change only the libevent - // flag and it failed. - if (changed_log_categories & BCLog::LIBEVENT) { - if (!UpdateHTTPServerLogging(LogInstance().WillLogCategory(BCLog::LIBEVENT))) { - LogInstance().DisableCategory(BCLog::LIBEVENT); - if (changed_log_categories == BCLog::LIBEVENT) { - throw JSONRPCError(RPC_INVALID_PARAMETER, "libevent logging cannot be updated when using libevent before v2.1.1."); - } - } - } - - UniValue result(UniValue::VOBJ); - for (const auto& logCatActive : LogInstance().LogCategoriesList()) { - result.pushKV(logCatActive.category, logCatActive.active); - } - - return result; -}, - }; -} - -static RPCHelpMan echo(const std::string& name) -{ - return RPCHelpMan{name, - "\nSimply echo back the input arguments. This command is for testing.\n" - "\nIt will return an internal bug report when arg9='trigger_internal_bug' is passed.\n" - "\nThe difference between echo and echojson is that echojson has argument conversion enabled in the client-side table in " - "bitcoin-cli and the GUI. There is no server-side difference.", - { - {"arg0", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, ""}, - {"arg1", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, ""}, - {"arg2", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, ""}, - {"arg3", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, ""}, - {"arg4", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, ""}, - {"arg5", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, ""}, - {"arg6", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, ""}, - {"arg7", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, ""}, - {"arg8", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, ""}, - {"arg9", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, ""}, - }, - RPCResult{RPCResult::Type::ANY, "", "Returns whatever was passed in"}, - RPCExamples{""}, - [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue -{ - if (request.params[9].isStr()) { - CHECK_NONFATAL(request.params[9].get_str() != "trigger_internal_bug"); - } - - return request.params; -}, - }; -} - -static RPCHelpMan echo() { return echo("echo"); } -static RPCHelpMan echojson() { return echo("echojson"); } - -static RPCHelpMan echoipc() -{ - return RPCHelpMan{ - "echoipc", - "\nEcho back the input argument, passing it through a spawned process in a multiprocess build.\n" - "This command is for testing.\n", - {{"arg", RPCArg::Type::STR, RPCArg::Optional::NO, "The string to echo",}}, - RPCResult{RPCResult::Type::STR, "echo", "The echoed string."}, - RPCExamples{HelpExampleCli("echo", "\"Hello world\"") + - HelpExampleRpc("echo", "\"Hello world\"")}, - [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - interfaces::Init& local_init = *EnsureAnyNodeContext(request.context).init; - std::unique_ptr<interfaces::Echo> echo; - if (interfaces::Ipc* ipc = local_init.ipc()) { - // Spawn a new bitcoin-node process and call makeEcho to get a - // client pointer to a interfaces::Echo instance running in - // that process. This is just for testing. A slightly more - // realistic test spawning a different executable instead of - // the same executable would add a new bitcoin-echo executable, - // and spawn bitcoin-echo below instead of bitcoin-node. But - // using bitcoin-node avoids the need to build and install a - // new executable just for this one test. - auto init = ipc->spawnProcess("bitcoin-node"); - echo = init->makeEcho(); - ipc->addCleanup(*echo, [init = init.release()] { delete init; }); - } else { - // IPC support is not available because this is a bitcoind - // process not a bitcoind-node process, so just create a local - // interfaces::Echo object and return it so the `echoipc` RPC - // method will work, and the python test calling `echoipc` - // can expect the same result. - echo = local_init.makeEcho(); - } - return echo->echo(request.params[0].get_str()); - }, - }; -} - -static UniValue SummaryToJSON(const IndexSummary&& summary, std::string index_name) -{ - UniValue ret_summary(UniValue::VOBJ); - if (!index_name.empty() && index_name != summary.name) return ret_summary; - - UniValue entry(UniValue::VOBJ); - entry.pushKV("synced", summary.synced); - entry.pushKV("best_block_height", summary.best_block_height); - ret_summary.pushKV(summary.name, entry); - return ret_summary; -} - -static RPCHelpMan getindexinfo() -{ - return RPCHelpMan{"getindexinfo", - "\nReturns the status of one or all available indices currently running in the node.\n", - { - {"index_name", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, "Filter results for an index with a specific name."}, - }, - RPCResult{ - RPCResult::Type::OBJ_DYN, "", "", { - { - RPCResult::Type::OBJ, "name", "The name of the index", - { - {RPCResult::Type::BOOL, "synced", "Whether the index is synced or not"}, - {RPCResult::Type::NUM, "best_block_height", "The block height to which the index is synced"}, - } - }, - }, - }, - RPCExamples{ - HelpExampleCli("getindexinfo", "") - + HelpExampleRpc("getindexinfo", "") - + HelpExampleCli("getindexinfo", "txindex") - + HelpExampleRpc("getindexinfo", "txindex") - }, - [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue -{ - UniValue result(UniValue::VOBJ); - const std::string index_name = request.params[0].isNull() ? "" : request.params[0].get_str(); - - if (g_txindex) { - result.pushKVs(SummaryToJSON(g_txindex->GetSummary(), index_name)); - } - - if (g_coin_stats_index) { - result.pushKVs(SummaryToJSON(g_coin_stats_index->GetSummary(), index_name)); - } - - ForEachBlockFilterIndex([&result, &index_name](const BlockFilterIndex& index) { - result.pushKVs(SummaryToJSON(index.GetSummary(), index_name)); - }); - - return result; -}, - }; -} - -void RegisterMiscRPCCommands(CRPCTable &t) -{ -// clang-format off -static const CRPCCommand commands[] = -{ // category actor (function) - // --------------------- ------------------------ - { "control", &getmemoryinfo, }, - { "control", &logging, }, - { "util", &validateaddress, }, - { "util", &createmultisig, }, - { "util", &deriveaddresses, }, - { "util", &getdescriptorinfo, }, - { "util", &verifymessage, }, - { "util", &signmessagewithprivkey, }, - { "util", &getindexinfo, }, - - /* Not shown in help */ - { "hidden", &setmocktime, }, - { "hidden", &mockscheduler, }, - { "hidden", &echo, }, - { "hidden", &echojson, }, - { "hidden", &echoipc, }, -#if defined(USE_SYSCALL_SANDBOX) - { "hidden", &invokedisallowedsyscall, }, -#endif // USE_SYSCALL_SANDBOX -}; -// clang-format on - for (const auto& c : commands) { - t.appendCommand(c.name, &c); - } -} diff --git a/src/rpc/net.cpp b/src/rpc/net.cpp index 1bde4fccbb..3b234bc317 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; }, }; } @@ -93,7 +94,7 @@ static RPCHelpMan getpeerinfo() { return RPCHelpMan{ "getpeerinfo", - "\nReturns data about each connected network node as a json array of objects.\n", + "Returns data about each connected network peer as a json array of objects.", {}, RPCResult{ RPCResult::Type::ARR, "", "", @@ -105,7 +106,7 @@ static RPCHelpMan getpeerinfo() {RPCResult::Type::STR, "addr", "(host:port) The IP address and port of the peer"}, {RPCResult::Type::STR, "addrbind", /*optional=*/true, "(ip:port) Bind address of the connection to the peer"}, {RPCResult::Type::STR, "addrlocal", /*optional=*/true, "(ip:port) Local address as reported by the peer"}, - {RPCResult::Type::STR, "network", "Network (" + Join(GetNetworkNames(/* append_unroutable */ true), ", ") + ")"}, + {RPCResult::Type::STR, "network", "Network (" + Join(GetNetworkNames(/*append_unroutable=*/true), ", ") + ")"}, {RPCResult::Type::NUM, "mapped_as", /*optional=*/true, "The AS in the BGP route to the peer used for diversifying\n" "peer selection (only available if the asmap config flag is set)"}, {RPCResult::Type::STR_HEX, "services", "The services offered"}, @@ -113,7 +114,7 @@ static RPCHelpMan getpeerinfo() { {RPCResult::Type::STR, "SERVICE_NAME", "the service name if it is recognised"} }}, - {RPCResult::Type::BOOL, "relaytxes", "Whether peer has asked us to relay transactions to it"}, + {RPCResult::Type::BOOL, "relaytxes", /*optional=*/true, "Whether peer has asked us to relay transactions to it"}, {RPCResult::Type::NUM_TIME, "lastsend", "The " + UNIX_EPOCH_TIME + " of the last send"}, {RPCResult::Type::NUM_TIME, "lastrecv", "The " + UNIX_EPOCH_TIME + " of the last receive"}, {RPCResult::Type::NUM_TIME, "last_transaction", "The " + UNIX_EPOCH_TIME + " of the last valid transaction received from this peer"}, @@ -144,7 +145,7 @@ static RPCHelpMan getpeerinfo() { {RPCResult::Type::STR, "permission_type", Join(NET_PERMISSIONS_DOC, ",\n") + ".\n"}, }}, - {RPCResult::Type::NUM, "minfeefilter", "The minimum fee rate for transactions this peer accepts"}, + {RPCResult::Type::NUM, "minfeefilter", /*optional=*/true, "The minimum fee rate for transactions this peer accepts"}, {RPCResult::Type::OBJ_DYN, "bytessent_per_msg", "", { {RPCResult::Type::NUM, "msg", "The total bytes sent aggregated by message type\n" @@ -156,7 +157,7 @@ static RPCHelpMan getpeerinfo() {RPCResult::Type::NUM, "msg", "The total bytes received aggregated by message type\n" "When a message type is not listed in this json object, the bytes received are 0.\n" "Only known message types can appear as keys in the object and all bytes received\n" - "of unknown message types are listed under '"+NET_MESSAGE_COMMAND_OTHER+"'."} + "of unknown message types are listed under '"+NET_MESSAGE_TYPE_OTHER+"'."} }}, {RPCResult::Type::STR, "connection_type", "Type of connection: \n" + Join(CONNECTION_TYPE_DOC, ",\n") + ".\n" "Please note this output is unlikely to be stable in upcoming releases as we iterate to\n" @@ -195,9 +196,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)); - obj.pushKV("relaytxes", stats.fRelayTxes); + 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)); @@ -207,13 +208,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 @@ -232,6 +233,8 @@ static RPCHelpMan getpeerinfo() heights.push_back(height); } obj.pushKV("inflight", heights); + obj.pushKV("relaytxes", statestats.m_relay_txs); + obj.pushKV("minfeefilter", ValueFromAmount(statestats.m_fee_filter_received)); obj.pushKV("addr_relay_enabled", statestats.m_addr_relay_enabled); obj.pushKV("addr_processed", statestats.m_addr_processed); obj.pushKV("addr_rate_limited", statestats.m_addr_rate_limited); @@ -241,21 +244,20 @@ static RPCHelpMan getpeerinfo() permissions.push_back(permission); } obj.pushKV("permissions", permissions); - obj.pushKV("minfeefilter", ValueFromAmount(stats.minFeeFilter)); - UniValue sendPerMsgCmd(UniValue::VOBJ); - for (const auto& i : stats.mapSendBytesPerMsgCmd) { + UniValue sendPerMsgType(UniValue::VOBJ); + for (const auto& i : stats.mapSendBytesPerMsgType) { if (i.second > 0) - sendPerMsgCmd.pushKV(i.first, i.second); + sendPerMsgType.pushKV(i.first, i.second); } - obj.pushKV("bytessent_per_msg", sendPerMsgCmd); + obj.pushKV("bytessent_per_msg", sendPerMsgType); - UniValue recvPerMsgCmd(UniValue::VOBJ); - for (const auto& i : stats.mapRecvBytesPerMsgCmd) { + UniValue recvPerMsgType(UniValue::VOBJ); + for (const auto& i : stats.mapRecvBytesPerMsgType) { if (i.second > 0) - recvPerMsgCmd.pushKV(i.first, i.second); + recvPerMsgType.pushKV(i.first, i.second); } - obj.pushKV("bytesrecv_per_msg", recvPerMsgCmd); + obj.pushKV("bytesrecv_per_msg", recvPerMsgType); obj.pushKV("connection_type", ConnectionTypeAsString(stats.m_conn_type)); ret.push_back(obj); @@ -303,7 +305,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 +321,7 @@ static RPCHelpMan addnode() } } - return NullUniValue; + return UniValue::VNULL; }, }; } @@ -412,7 +414,7 @@ static RPCHelpMan disconnectnode() success = connman.DisconnectNode(address_arg.get_str()); } else if (!id_arg.isNull() && (address_arg.isNull() || (address_arg.isStr() && address_arg.get_str().empty()))) { /* handle disconnect-by-id */ - NodeId nodeid = (NodeId) id_arg.get_int64(); + NodeId nodeid = (NodeId) id_arg.getInt<int64_t>(); success = connman.DisconnectNode(nodeid); } else { throw JSONRPCError(RPC_INVALID_PARAMS, "Only one of address and nodeid should be provided."); @@ -422,7 +424,7 @@ static RPCHelpMan disconnectnode() throw JSONRPCError(RPC_CLIENT_NODE_NOT_CONNECTED, "Node not found in connected nodes"); } - return NullUniValue; + return UniValue::VNULL; }, }; } @@ -639,13 +641,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); @@ -720,7 +725,7 @@ static RPCHelpMan setban() int64_t banTime = 0; //use standard bantime if not specified if (!request.params[2].isNull()) - banTime = request.params[2].get_int64(); + banTime = request.params[2].getInt<int64_t>(); bool absolute = false; if (request.params[3].isTrue()) @@ -744,7 +749,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 +823,7 @@ static RPCHelpMan clearbanned() node.banman->ClearBanned(); - return NullUniValue; + return UniValue::VNULL; }, }; } @@ -879,7 +884,7 @@ static RPCHelpMan getnodeaddresses() NodeContext& node = EnsureAnyNodeContext(request.context); const CConnman& connman = EnsureConnman(node); - const int count{request.params[0].isNull() ? 1 : request.params[0].get_int()}; + const int count{request.params[0].isNull() ? 1 : request.params[0].getInt<int>()}; if (count < 0) throw JSONRPCError(RPC_INVALID_PARAMETER, "Address count out of range"); const std::optional<Network> network{request.params[1].isNull() ? std::nullopt : std::optional<Network>{ParseNetwork(request.params[1].get_str())}}; @@ -888,12 +893,12 @@ static RPCHelpMan getnodeaddresses() } // returns a shuffled list of CAddress - const std::vector<CAddress> vAddr{connman.GetAddresses(count, /* max_pct */ 0, network)}; + const std::vector<CAddress> vAddr{connman.GetAddresses(count, /*max_pct=*/0, network)}; UniValue ret(UniValue::VARR); 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()); @@ -932,7 +937,7 @@ static RPCHelpMan addpeeraddress() } const std::string& addr_string{request.params[0].get_str()}; - const uint16_t port{static_cast<uint16_t>(request.params[1].get_int())}; + const auto port{request.params[1].getInt<uint16_t>()}; const bool tried{request.params[2].isTrue()}; UniValue obj(UniValue::VOBJ); @@ -941,7 +946,7 @@ static RPCHelpMan addpeeraddress() if (LookupHost(addr_string, net_addr, false)) { CAddress address{{net_addr, port}, ServiceFlags{NODE_NETWORK | NODE_WITNESS}}; - address.nTime = GetAdjustedTime(); + 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)) { @@ -959,30 +964,25 @@ static RPCHelpMan addpeeraddress() }; } -void RegisterNetRPCCommands(CRPCTable &t) -{ -// clang-format off -static const CRPCCommand commands[] = -{ // category actor - // --------------------- ----------------------- - { "network", &getconnectioncount, }, - { "network", &ping, }, - { "network", &getpeerinfo, }, - { "network", &addnode, }, - { "network", &disconnectnode, }, - { "network", &getaddednodeinfo, }, - { "network", &getnettotals, }, - { "network", &getnetworkinfo, }, - { "network", &setban, }, - { "network", &listbanned, }, - { "network", &clearbanned, }, - { "network", &setnetworkactive, }, - { "network", &getnodeaddresses, }, - - { "hidden", &addconnection, }, - { "hidden", &addpeeraddress, }, -}; -// clang-format on +void RegisterNetRPCCommands(CRPCTable& t) +{ + static const CRPCCommand commands[]{ + {"network", &getconnectioncount}, + {"network", &ping}, + {"network", &getpeerinfo}, + {"network", &addnode}, + {"network", &disconnectnode}, + {"network", &getaddednodeinfo}, + {"network", &getnettotals}, + {"network", &getnetworkinfo}, + {"network", &setban}, + {"network", &listbanned}, + {"network", &clearbanned}, + {"network", &setnetworkactive}, + {"network", &getnodeaddresses}, + {"hidden", &addconnection}, + {"hidden", &addpeeraddress}, + }; for (const auto& c : commands) { t.appendCommand(c.name, &c); } diff --git a/src/rpc/node.cpp b/src/rpc/node.cpp new file mode 100644 index 0000000000..605ebc15a7 --- /dev/null +++ b/src/rpc/node.cpp @@ -0,0 +1,440 @@ +// Copyright (c) 2010 Satoshi Nakamoto +// Copyright (c) 2009-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 <chainparams.h> +#include <httpserver.h> +#include <index/blockfilterindex.h> +#include <index/coinstatsindex.h> +#include <index/txindex.h> +#include <interfaces/chain.h> +#include <interfaces/echo.h> +#include <interfaces/init.h> +#include <interfaces/ipc.h> +#include <node/context.h> +#include <rpc/server.h> +#include <rpc/server_util.h> +#include <rpc/util.h> +#include <scheduler.h> +#include <univalue.h> +#include <util/check.h> +#include <util/syscall_sandbox.h> +#include <util/system.h> + +#include <stdint.h> +#ifdef HAVE_MALLOC_INFO +#include <malloc.h> +#endif + +using node::NodeContext; + +static RPCHelpMan setmocktime() +{ + return RPCHelpMan{"setmocktime", + "\nSet the local time to given timestamp (-regtest only)\n", + { + {"timestamp", RPCArg::Type::NUM, RPCArg::Optional::NO, UNIX_EPOCH_TIME + "\n" + "Pass 0 to go back to using the system time."}, + }, + RPCResult{RPCResult::Type::NONE, "", ""}, + RPCExamples{""}, + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue +{ + if (!Params().IsMockableChain()) { + throw std::runtime_error("setmocktime is for regression testing (-regtest mode) only"); + } + + // For now, don't change mocktime if we're in the middle of validation, as + // this could have an effect on mempool time-based eviction, as well as + // IsCurrentForFeeEstimation() and IsInitialBlockDownload(). + // TODO: figure out the right way to synchronize around mocktime, and + // ensure all call sites of GetTime() are accessing this safely. + LOCK(cs_main); + + RPCTypeCheck(request.params, {UniValue::VNUM}); + const int64_t time{request.params[0].getInt<int64_t>()}; + if (time < 0) { + throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Mocktime cannot be negative: %s.", time)); + } + SetMockTime(time); + auto node_context = util::AnyPtr<NodeContext>(request.context); + if (node_context) { + for (const auto& chain_client : node_context->chain_clients) { + chain_client->setMockTime(time); + } + } + + return UniValue::VNULL; +}, + }; +} + +#if defined(USE_SYSCALL_SANDBOX) +static RPCHelpMan invokedisallowedsyscall() +{ + return RPCHelpMan{ + "invokedisallowedsyscall", + "\nInvoke a disallowed syscall to trigger a syscall sandbox violation. Used for testing purposes.\n", + {}, + RPCResult{RPCResult::Type::NONE, "", ""}, + RPCExamples{ + HelpExampleCli("invokedisallowedsyscall", "") + HelpExampleRpc("invokedisallowedsyscall", "")}, + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { + if (!Params().IsTestChain()) { + throw std::runtime_error("invokedisallowedsyscall is used for testing only."); + } + TestDisallowedSandboxCall(); + return UniValue::VNULL; + }, + }; +} +#endif // USE_SYSCALL_SANDBOX + +static RPCHelpMan mockscheduler() +{ + return RPCHelpMan{"mockscheduler", + "\nBump the scheduler into the future (-regtest only)\n", + { + {"delta_time", RPCArg::Type::NUM, RPCArg::Optional::NO, "Number of seconds to forward the scheduler into the future." }, + }, + RPCResult{RPCResult::Type::NONE, "", ""}, + RPCExamples{""}, + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue +{ + if (!Params().IsMockableChain()) { + throw std::runtime_error("mockscheduler is for regression testing (-regtest mode) only"); + } + + // check params are valid values + RPCTypeCheck(request.params, {UniValue::VNUM}); + int64_t delta_seconds = request.params[0].getInt<int64_t>(); + if (delta_seconds <= 0 || delta_seconds > 3600) { + throw std::runtime_error("delta_time must be between 1 and 3600 seconds (1 hr)"); + } + + auto node_context = CHECK_NONFATAL(util::AnyPtr<NodeContext>(request.context)); + // protect against null pointer dereference + CHECK_NONFATAL(node_context->scheduler); + node_context->scheduler->MockForward(std::chrono::seconds(delta_seconds)); + + return UniValue::VNULL; +}, + }; +} + +static UniValue RPCLockedMemoryInfo() +{ + LockedPool::Stats stats = LockedPoolManager::Instance().stats(); + UniValue obj(UniValue::VOBJ); + obj.pushKV("used", uint64_t(stats.used)); + obj.pushKV("free", uint64_t(stats.free)); + obj.pushKV("total", uint64_t(stats.total)); + obj.pushKV("locked", uint64_t(stats.locked)); + obj.pushKV("chunks_used", uint64_t(stats.chunks_used)); + obj.pushKV("chunks_free", uint64_t(stats.chunks_free)); + return obj; +} + +#ifdef HAVE_MALLOC_INFO +static std::string RPCMallocInfo() +{ + char *ptr = nullptr; + size_t size = 0; + FILE *f = open_memstream(&ptr, &size); + if (f) { + malloc_info(0, f); + fclose(f); + if (ptr) { + std::string rv(ptr, size); + free(ptr); + return rv; + } + } + return ""; +} +#endif + +static RPCHelpMan getmemoryinfo() +{ + /* Please, avoid using the word "pool" here in the RPC interface or help, + * as users will undoubtedly confuse it with the other "memory pool" + */ + return RPCHelpMan{"getmemoryinfo", + "Returns an object containing information about memory usage.\n", + { + {"mode", RPCArg::Type::STR, RPCArg::Default{"stats"}, "determines what kind of information is returned.\n" + " - \"stats\" returns general statistics about memory usage in the daemon.\n" + " - \"mallocinfo\" returns an XML string describing low-level heap state (only available if compiled with glibc 2.10+)."}, + }, + { + RPCResult{"mode \"stats\"", + RPCResult::Type::OBJ, "", "", + { + {RPCResult::Type::OBJ, "locked", "Information about locked memory manager", + { + {RPCResult::Type::NUM, "used", "Number of bytes used"}, + {RPCResult::Type::NUM, "free", "Number of bytes available in current arenas"}, + {RPCResult::Type::NUM, "total", "Total number of bytes managed"}, + {RPCResult::Type::NUM, "locked", "Amount of bytes that succeeded locking. If this number is smaller than total, locking pages failed at some point and key data could be swapped to disk."}, + {RPCResult::Type::NUM, "chunks_used", "Number allocated chunks"}, + {RPCResult::Type::NUM, "chunks_free", "Number unused chunks"}, + }}, + } + }, + RPCResult{"mode \"mallocinfo\"", + RPCResult::Type::STR, "", "\"<malloc version=\"1\">...\"" + }, + }, + RPCExamples{ + HelpExampleCli("getmemoryinfo", "") + + HelpExampleRpc("getmemoryinfo", "") + }, + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue +{ + std::string mode = request.params[0].isNull() ? "stats" : request.params[0].get_str(); + if (mode == "stats") { + UniValue obj(UniValue::VOBJ); + obj.pushKV("locked", RPCLockedMemoryInfo()); + return obj; + } else if (mode == "mallocinfo") { +#ifdef HAVE_MALLOC_INFO + return RPCMallocInfo(); +#else + throw JSONRPCError(RPC_INVALID_PARAMETER, "mallocinfo mode not available"); +#endif + } else { + throw JSONRPCError(RPC_INVALID_PARAMETER, "unknown mode " + mode); + } +}, + }; +} + +static void EnableOrDisableLogCategories(UniValue cats, bool enable) { + cats = cats.get_array(); + for (unsigned int i = 0; i < cats.size(); ++i) { + std::string cat = cats[i].get_str(); + + bool success; + if (enable) { + success = LogInstance().EnableCategory(cat); + } else { + success = LogInstance().DisableCategory(cat); + } + + if (!success) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "unknown logging category " + cat); + } + } +} + +static RPCHelpMan logging() +{ + return RPCHelpMan{"logging", + "Gets and sets the logging configuration.\n" + "When called without an argument, returns the list of categories with status that are currently being debug logged or not.\n" + "When called with arguments, adds or removes categories from debug logging and return the lists above.\n" + "The arguments are evaluated in order \"include\", \"exclude\".\n" + "If an item is both included and excluded, it will thus end up being excluded.\n" + "The valid logging categories are: " + LogInstance().LogCategoriesString() + "\n" + "In addition, the following are available as category names with special meanings:\n" + " - \"all\", \"1\" : represent all logging categories.\n" + " - \"none\", \"0\" : even if other logging categories are specified, ignore all of them.\n" + , + { + {"include", RPCArg::Type::ARR, RPCArg::Optional::OMITTED_NAMED_ARG, "The categories to add to debug logging", + { + {"include_category", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "the valid logging category"}, + }}, + {"exclude", RPCArg::Type::ARR, RPCArg::Optional::OMITTED_NAMED_ARG, "The categories to remove from debug logging", + { + {"exclude_category", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "the valid logging category"}, + }}, + }, + RPCResult{ + RPCResult::Type::OBJ_DYN, "", "keys are the logging categories, and values indicates its status", + { + {RPCResult::Type::BOOL, "category", "if being debug logged or not. false:inactive, true:active"}, + } + }, + RPCExamples{ + HelpExampleCli("logging", "\"[\\\"all\\\"]\" \"[\\\"http\\\"]\"") + + HelpExampleRpc("logging", "[\"all\"], [\"libevent\"]") + }, + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue +{ + uint32_t original_log_categories = LogInstance().GetCategoryMask(); + if (request.params[0].isArray()) { + EnableOrDisableLogCategories(request.params[0], true); + } + if (request.params[1].isArray()) { + EnableOrDisableLogCategories(request.params[1], false); + } + uint32_t updated_log_categories = LogInstance().GetCategoryMask(); + uint32_t changed_log_categories = original_log_categories ^ updated_log_categories; + + // Update libevent logging if BCLog::LIBEVENT has changed. + if (changed_log_categories & BCLog::LIBEVENT) { + UpdateHTTPServerLogging(LogInstance().WillLogCategory(BCLog::LIBEVENT)); + } + + UniValue result(UniValue::VOBJ); + for (const auto& logCatActive : LogInstance().LogCategoriesList()) { + result.pushKV(logCatActive.category, logCatActive.active); + } + + return result; +}, + }; +} + +static RPCHelpMan echo(const std::string& name) +{ + return RPCHelpMan{name, + "\nSimply echo back the input arguments. This command is for testing.\n" + "\nIt will return an internal bug report when arg9='trigger_internal_bug' is passed.\n" + "\nThe difference between echo and echojson is that echojson has argument conversion enabled in the client-side table in " + "bitcoin-cli and the GUI. There is no server-side difference.", + { + {"arg0", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, ""}, + {"arg1", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, ""}, + {"arg2", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, ""}, + {"arg3", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, ""}, + {"arg4", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, ""}, + {"arg5", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, ""}, + {"arg6", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, ""}, + {"arg7", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, ""}, + {"arg8", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, ""}, + {"arg9", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, ""}, + }, + RPCResult{RPCResult::Type::ANY, "", "Returns whatever was passed in"}, + RPCExamples{""}, + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue +{ + if (request.params[9].isStr()) { + CHECK_NONFATAL(request.params[9].get_str() != "trigger_internal_bug"); + } + + return request.params; +}, + }; +} + +static RPCHelpMan echo() { return echo("echo"); } +static RPCHelpMan echojson() { return echo("echojson"); } + +static RPCHelpMan echoipc() +{ + return RPCHelpMan{ + "echoipc", + "\nEcho back the input argument, passing it through a spawned process in a multiprocess build.\n" + "This command is for testing.\n", + {{"arg", RPCArg::Type::STR, RPCArg::Optional::NO, "The string to echo",}}, + RPCResult{RPCResult::Type::STR, "echo", "The echoed string."}, + RPCExamples{HelpExampleCli("echo", "\"Hello world\"") + + HelpExampleRpc("echo", "\"Hello world\"")}, + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { + interfaces::Init& local_init = *EnsureAnyNodeContext(request.context).init; + std::unique_ptr<interfaces::Echo> echo; + if (interfaces::Ipc* ipc = local_init.ipc()) { + // Spawn a new bitcoin-node process and call makeEcho to get a + // client pointer to a interfaces::Echo instance running in + // that process. This is just for testing. A slightly more + // realistic test spawning a different executable instead of + // the same executable would add a new bitcoin-echo executable, + // and spawn bitcoin-echo below instead of bitcoin-node. But + // using bitcoin-node avoids the need to build and install a + // new executable just for this one test. + auto init = ipc->spawnProcess("bitcoin-node"); + echo = init->makeEcho(); + ipc->addCleanup(*echo, [init = init.release()] { delete init; }); + } else { + // IPC support is not available because this is a bitcoind + // process not a bitcoind-node process, so just create a local + // interfaces::Echo object and return it so the `echoipc` RPC + // method will work, and the python test calling `echoipc` + // can expect the same result. + echo = local_init.makeEcho(); + } + return echo->echo(request.params[0].get_str()); + }, + }; +} + +static UniValue SummaryToJSON(const IndexSummary&& summary, std::string index_name) +{ + UniValue ret_summary(UniValue::VOBJ); + if (!index_name.empty() && index_name != summary.name) return ret_summary; + + UniValue entry(UniValue::VOBJ); + entry.pushKV("synced", summary.synced); + entry.pushKV("best_block_height", summary.best_block_height); + ret_summary.pushKV(summary.name, entry); + return ret_summary; +} + +static RPCHelpMan getindexinfo() +{ + return RPCHelpMan{"getindexinfo", + "\nReturns the status of one or all available indices currently running in the node.\n", + { + {"index_name", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, "Filter results for an index with a specific name."}, + }, + RPCResult{ + RPCResult::Type::OBJ_DYN, "", "", { + { + RPCResult::Type::OBJ, "name", "The name of the index", + { + {RPCResult::Type::BOOL, "synced", "Whether the index is synced or not"}, + {RPCResult::Type::NUM, "best_block_height", "The block height to which the index is synced"}, + } + }, + }, + }, + RPCExamples{ + HelpExampleCli("getindexinfo", "") + + HelpExampleRpc("getindexinfo", "") + + HelpExampleCli("getindexinfo", "txindex") + + HelpExampleRpc("getindexinfo", "txindex") + }, + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue +{ + UniValue result(UniValue::VOBJ); + const std::string index_name = request.params[0].isNull() ? "" : request.params[0].get_str(); + + if (g_txindex) { + result.pushKVs(SummaryToJSON(g_txindex->GetSummary(), index_name)); + } + + if (g_coin_stats_index) { + result.pushKVs(SummaryToJSON(g_coin_stats_index->GetSummary(), index_name)); + } + + ForEachBlockFilterIndex([&result, &index_name](const BlockFilterIndex& index) { + result.pushKVs(SummaryToJSON(index.GetSummary(), index_name)); + }); + + return result; +}, + }; +} + +void RegisterNodeRPCCommands(CRPCTable& t) +{ + static const CRPCCommand commands[]{ + {"control", &getmemoryinfo}, + {"control", &logging}, + {"util", &getindexinfo}, + {"hidden", &setmocktime}, + {"hidden", &mockscheduler}, + {"hidden", &echo}, + {"hidden", &echojson}, + {"hidden", &echoipc}, +#if defined(USE_SYSCALL_SANDBOX) + {"hidden", &invokedisallowedsyscall}, +#endif // USE_SYSCALL_SANDBOX + }; + for (const auto& c : commands) { + t.appendCommand(c.name, &c); + } +} diff --git a/src/rpc/output_script.cpp b/src/rpc/output_script.cpp new file mode 100644 index 0000000000..744f809814 --- /dev/null +++ b/src/rpc/output_script.cpp @@ -0,0 +1,314 @@ +// Copyright (c) 2010 Satoshi Nakamoto +// Copyright (c) 2009-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 <key_io.h> +#include <outputtype.h> +#include <pubkey.h> +#include <rpc/protocol.h> +#include <rpc/request.h> +#include <rpc/server.h> +#include <rpc/util.h> +#include <script/descriptor.h> +#include <script/script.h> +#include <script/signingprovider.h> +#include <script/standard.h> +#include <tinyformat.h> +#include <univalue.h> +#include <util/check.h> +#include <util/strencodings.h> + +#include <cstdint> +#include <memory> +#include <optional> +#include <string> +#include <tuple> +#include <vector> + +static RPCHelpMan validateaddress() +{ + return RPCHelpMan{ + "validateaddress", + "\nReturn information about the given bitcoin address.\n", + { + {"address", RPCArg::Type::STR, RPCArg::Optional::NO, "The bitcoin address to validate"}, + }, + RPCResult{ + RPCResult::Type::OBJ, "", "", + { + {RPCResult::Type::BOOL, "isvalid", "If the address is valid or not"}, + {RPCResult::Type::STR, "address", /*optional=*/true, "The bitcoin address validated"}, + {RPCResult::Type::STR_HEX, "scriptPubKey", /*optional=*/true, "The hex-encoded scriptPubKey generated by the address"}, + {RPCResult::Type::BOOL, "isscript", /*optional=*/true, "If the key is a script"}, + {RPCResult::Type::BOOL, "iswitness", /*optional=*/true, "If the address is a witness address"}, + {RPCResult::Type::NUM, "witness_version", /*optional=*/true, "The version number of the witness program"}, + {RPCResult::Type::STR_HEX, "witness_program", /*optional=*/true, "The hex value of the witness program"}, + {RPCResult::Type::STR, "error", /*optional=*/true, "Error message, if any"}, + {RPCResult::Type::ARR, "error_locations", /*optional=*/true, "Indices of likely error locations in address, if known (e.g. Bech32 errors)", + { + {RPCResult::Type::NUM, "index", "index of a potential error"}, + }}, + } + }, + RPCExamples{ + HelpExampleCli("validateaddress", "\"" + EXAMPLE_ADDRESS[0] + "\"") + + HelpExampleRpc("validateaddress", "\"" + EXAMPLE_ADDRESS[0] + "\"") + }, + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue + { + std::string error_msg; + std::vector<int> error_locations; + CTxDestination dest = DecodeDestination(request.params[0].get_str(), error_msg, &error_locations); + const bool isValid = IsValidDestination(dest); + CHECK_NONFATAL(isValid == error_msg.empty()); + + UniValue ret(UniValue::VOBJ); + ret.pushKV("isvalid", isValid); + if (isValid) { + std::string currentAddress = EncodeDestination(dest); + ret.pushKV("address", currentAddress); + + CScript scriptPubKey = GetScriptForDestination(dest); + ret.pushKV("scriptPubKey", HexStr(scriptPubKey)); + + UniValue detail = DescribeAddress(dest); + ret.pushKVs(detail); + } else { + UniValue error_indices(UniValue::VARR); + for (int i : error_locations) error_indices.push_back(i); + ret.pushKV("error_locations", error_indices); + ret.pushKV("error", error_msg); + } + + return ret; + }, + }; +} + +static RPCHelpMan createmultisig() +{ + return RPCHelpMan{"createmultisig", + "\nCreates a multi-signature address with n signature of m keys required.\n" + "It returns a json object with the address and redeemScript.\n", + { + {"nrequired", RPCArg::Type::NUM, RPCArg::Optional::NO, "The number of required signatures out of the n keys."}, + {"keys", RPCArg::Type::ARR, RPCArg::Optional::NO, "The hex-encoded public keys.", + { + {"key", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, "The hex-encoded public key"}, + }}, + {"address_type", RPCArg::Type::STR, RPCArg::Default{"legacy"}, "The address type to use. Options are \"legacy\", \"p2sh-segwit\", and \"bech32\"."}, + }, + RPCResult{ + RPCResult::Type::OBJ, "", "", + { + {RPCResult::Type::STR, "address", "The value of the new multisig address."}, + {RPCResult::Type::STR_HEX, "redeemScript", "The string value of the hex-encoded redemption script."}, + {RPCResult::Type::STR, "descriptor", "The descriptor for this multisig"}, + {RPCResult::Type::ARR, "warnings", /*optional=*/true, "Any warnings resulting from the creation of this multisig", + { + {RPCResult::Type::STR, "", ""}, + }}, + } + }, + RPCExamples{ + "\nCreate a multisig address from 2 public keys\n" + + HelpExampleCli("createmultisig", "2 \"[\\\"03789ed0bb717d88f7d321a368d905e7430207ebbd82bd342cf11ae157a7ace5fd\\\",\\\"03dbc6764b8884a92e871274b87583e6d5c2a58819473e17e107ef3f6aa5a61626\\\"]\"") + + "\nAs a JSON-RPC call\n" + + HelpExampleRpc("createmultisig", "2, [\"03789ed0bb717d88f7d321a368d905e7430207ebbd82bd342cf11ae157a7ace5fd\",\"03dbc6764b8884a92e871274b87583e6d5c2a58819473e17e107ef3f6aa5a61626\"]") + }, + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue + { + int required = request.params[0].getInt<int>(); + + // Get the public keys + const UniValue& keys = request.params[1].get_array(); + std::vector<CPubKey> pubkeys; + for (unsigned int i = 0; i < keys.size(); ++i) { + if (IsHex(keys[i].get_str()) && (keys[i].get_str().length() == 66 || keys[i].get_str().length() == 130)) { + pubkeys.push_back(HexToPubKey(keys[i].get_str())); + } else { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Invalid public key: %s\n.", keys[i].get_str())); + } + } + + // Get the output type + OutputType output_type = OutputType::LEGACY; + if (!request.params[2].isNull()) { + std::optional<OutputType> parsed = ParseOutputType(request.params[2].get_str()); + if (!parsed) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Unknown address type '%s'", request.params[2].get_str())); + } else if (parsed.value() == OutputType::BECH32M) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "createmultisig cannot create bech32m multisig addresses"); + } + output_type = parsed.value(); + } + + // Construct using pay-to-script-hash: + FillableSigningProvider keystore; + CScript inner; + const CTxDestination dest = AddAndGetMultisigDestination(required, pubkeys, output_type, keystore, inner); + + // Make the descriptor + std::unique_ptr<Descriptor> descriptor = InferDescriptor(GetScriptForDestination(dest), keystore); + + UniValue result(UniValue::VOBJ); + result.pushKV("address", EncodeDestination(dest)); + result.pushKV("redeemScript", HexStr(inner)); + result.pushKV("descriptor", descriptor->ToString()); + + UniValue warnings(UniValue::VARR); + if (descriptor->GetOutputType() != output_type) { + // Only warns if the user has explicitly chosen an address type we cannot generate + warnings.push_back("Unable to make chosen address type, please ensure no uncompressed public keys are present."); + } + if (!warnings.empty()) result.pushKV("warnings", warnings); + + return result; + }, + }; +} + +static RPCHelpMan getdescriptorinfo() +{ + const std::string EXAMPLE_DESCRIPTOR = "wpkh([d34db33f/84h/0h/0h]0279be667ef9dcbbac55a06295Ce870b07029Bfcdb2dce28d959f2815b16f81798)"; + + return RPCHelpMan{"getdescriptorinfo", + {"\nAnalyses a descriptor.\n"}, + { + {"descriptor", RPCArg::Type::STR, RPCArg::Optional::NO, "The descriptor."}, + }, + RPCResult{ + RPCResult::Type::OBJ, "", "", + { + {RPCResult::Type::STR, "descriptor", "The descriptor in canonical form, without private keys"}, + {RPCResult::Type::STR, "checksum", "The checksum for the input descriptor"}, + {RPCResult::Type::BOOL, "isrange", "Whether the descriptor is ranged"}, + {RPCResult::Type::BOOL, "issolvable", "Whether the descriptor is solvable"}, + {RPCResult::Type::BOOL, "hasprivatekeys", "Whether the input descriptor contained at least one private key"}, + } + }, + RPCExamples{ + "Analyse a descriptor\n" + + HelpExampleCli("getdescriptorinfo", "\"" + EXAMPLE_DESCRIPTOR + "\"") + + HelpExampleRpc("getdescriptorinfo", "\"" + EXAMPLE_DESCRIPTOR + "\"") + }, + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue + { + RPCTypeCheck(request.params, {UniValue::VSTR}); + + FlatSigningProvider provider; + std::string error; + auto desc = Parse(request.params[0].get_str(), provider, error); + if (!desc) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, error); + } + + UniValue result(UniValue::VOBJ); + result.pushKV("descriptor", desc->ToString()); + result.pushKV("checksum", GetDescriptorChecksum(request.params[0].get_str())); + result.pushKV("isrange", desc->IsRange()); + result.pushKV("issolvable", desc->IsSolvable()); + result.pushKV("hasprivatekeys", provider.keys.size() > 0); + return result; + }, + }; +} + +static RPCHelpMan deriveaddresses() +{ + const std::string EXAMPLE_DESCRIPTOR = "wpkh([d34db33f/84h/0h/0h]xpub6DJ2dNUysrn5Vt36jH2KLBT2i1auw1tTSSomg8PhqNiUtx8QX2SvC9nrHu81fT41fvDUnhMjEzQgXnQjKEu3oaqMSzhSrHMxyyoEAmUHQbY/0/*)#cjjspncu"; + + return RPCHelpMan{"deriveaddresses", + {"\nDerives one or more addresses corresponding to an output descriptor.\n" + "Examples of output descriptors are:\n" + " pkh(<pubkey>) P2PKH outputs for the given pubkey\n" + " wpkh(<pubkey>) Native segwit P2PKH outputs for the given pubkey\n" + " sh(multi(<n>,<pubkey>,<pubkey>,...)) P2SH-multisig outputs for the given threshold and pubkeys\n" + " raw(<hex script>) Outputs whose scriptPubKey equals the specified hex scripts\n" + "\nIn the above, <pubkey> either refers to a fixed public key in hexadecimal notation, or to an xpub/xprv optionally followed by one\n" + "or more path elements separated by \"/\", where \"h\" represents a hardened child key.\n" + "For more information on output descriptors, see the documentation in the doc/descriptors.md file.\n"}, + { + {"descriptor", RPCArg::Type::STR, RPCArg::Optional::NO, "The descriptor."}, + {"range", RPCArg::Type::RANGE, RPCArg::Optional::OMITTED_NAMED_ARG, "If a ranged descriptor is used, this specifies the end or the range (in [begin,end] notation) to derive."}, + }, + RPCResult{ + RPCResult::Type::ARR, "", "", + { + {RPCResult::Type::STR, "address", "the derived addresses"}, + } + }, + RPCExamples{ + "First three native segwit receive addresses\n" + + HelpExampleCli("deriveaddresses", "\"" + EXAMPLE_DESCRIPTOR + "\" \"[0,2]\"") + + HelpExampleRpc("deriveaddresses", "\"" + EXAMPLE_DESCRIPTOR + "\", \"[0,2]\"") + }, + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue + { + RPCTypeCheck(request.params, {UniValue::VSTR, UniValueType()}); // Range argument is checked later + const std::string desc_str = request.params[0].get_str(); + + int64_t range_begin = 0; + int64_t range_end = 0; + + if (request.params.size() >= 2 && !request.params[1].isNull()) { + std::tie(range_begin, range_end) = ParseDescriptorRange(request.params[1]); + } + + FlatSigningProvider key_provider; + std::string error; + auto desc = Parse(desc_str, key_provider, error, /* require_checksum = */ true); + if (!desc) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, error); + } + + if (!desc->IsRange() && request.params.size() > 1) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Range should not be specified for an un-ranged descriptor"); + } + + if (desc->IsRange() && request.params.size() == 1) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Range must be specified for a ranged descriptor"); + } + + UniValue addresses(UniValue::VARR); + + for (int i = range_begin; i <= range_end; ++i) { + FlatSigningProvider provider; + std::vector<CScript> scripts; + if (!desc->Expand(i, key_provider, scripts, provider)) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Cannot derive script without private keys"); + } + + for (const CScript& script : scripts) { + CTxDestination dest; + if (!ExtractDestination(script, dest)) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Descriptor does not have a corresponding address"); + } + + addresses.push_back(EncodeDestination(dest)); + } + } + + // This should not be possible, but an assert seems overkill: + if (addresses.empty()) { + throw JSONRPCError(RPC_MISC_ERROR, "Unexpected empty result"); + } + + return addresses; + }, + }; +} + +void RegisterOutputScriptRPCCommands(CRPCTable& t) +{ + static const CRPCCommand commands[]{ + {"util", &validateaddress}, + {"util", &createmultisig}, + {"util", &deriveaddresses}, + {"util", &getdescriptorinfo}, + }; + for (const auto& c : commands) { + t.appendCommand(c.name, &c); + } +} diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp index 6272a7c8cf..7ffb499330 100644 --- a/src/rpc/rawtransaction.cpp +++ b/src/rpc/rawtransaction.cpp @@ -11,7 +11,6 @@ #include <core_io.h> #include <index/txindex.h> #include <key_io.h> -#include <merkleblock.h> #include <node/blockstorage.h> #include <node/coin.h> #include <node/context.h> @@ -34,9 +33,10 @@ #include <script/standard.h> #include <uint256.h> #include <util/bip32.h> -#include <util/moneystr.h> +#include <util/check.h> #include <util/strencodings.h> #include <util/string.h> +#include <util/vector.h> #include <validation.h> #include <validationinterface.h> @@ -46,13 +46,10 @@ #include <univalue.h> using node::AnalyzePSBT; -using node::BroadcastTransaction; -using node::DEFAULT_MAX_RAW_TX_FEE_RATE; 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) { @@ -61,13 +58,13 @@ static void TxToJSON(const CTransaction& tx, const uint256 hashBlock, UniValue& // Blockchain contextual information (confirmations and blocktime) is not // available to code in bitcoin-common, so we query them here and push the // data into the returned UniValue. - TxToUniv(tx, uint256(), entry, true, RPCSerializationFlags()); + TxToUniv(tx, /*block_hash=*/uint256(), entry, /*include_hex=*/true, RPCSerializationFlags()); if (!hashBlock.IsNull()) { LOCK(cs_main); entry.pushKV("blockhash", hashBlock.GetHex()); - CBlockIndex* pindex = active_chainstate.m_blockman.LookupBlockIndex(hashBlock); + const CBlockIndex* pindex = active_chainstate.m_blockman.LookupBlockIndex(hashBlock); if (pindex) { if (active_chainstate.m_chain.Contains(pindex)) { entry.pushKV("confirmations", 1 + active_chainstate.m_chain.Height() - pindex->nHeight); @@ -80,6 +77,54 @@ static void TxToJSON(const CTransaction& tx, const uint256 hashBlock, UniValue& } } +static std::vector<RPCResult> DecodeTxDoc(const std::string& txid_field_doc) +{ + return { + {RPCResult::Type::STR_HEX, "txid", txid_field_doc}, + {RPCResult::Type::STR_HEX, "hash", "The transaction hash (differs from txid for witness transactions)"}, + {RPCResult::Type::NUM, "size", "The serialized transaction size"}, + {RPCResult::Type::NUM, "vsize", "The virtual transaction size (differs from size for witness transactions)"}, + {RPCResult::Type::NUM, "weight", "The transaction's weight (between vsize*4-3 and vsize*4)"}, + {RPCResult::Type::NUM, "version", "The version"}, + {RPCResult::Type::NUM_TIME, "locktime", "The lock time"}, + {RPCResult::Type::ARR, "vin", "", + { + {RPCResult::Type::OBJ, "", "", + { + {RPCResult::Type::STR_HEX, "coinbase", /*optional=*/true, "The coinbase value (only if coinbase transaction)"}, + {RPCResult::Type::STR_HEX, "txid", /*optional=*/true, "The transaction id (if not coinbase transaction)"}, + {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", "Disassembly of the signature script"}, + {RPCResult::Type::STR_HEX, "hex", "The raw signature script bytes, hex-encoded"}, + }}, + {RPCResult::Type::ARR, "txinwitness", /*optional=*/true, "", + { + {RPCResult::Type::STR_HEX, "hex", "hex-encoded witness data (if any)"}, + }}, + {RPCResult::Type::NUM, "sequence", "The script sequence number"}, + }}, + }}, + {RPCResult::Type::ARR, "vout", "", + { + {RPCResult::Type::OBJ, "", "", + { + {RPCResult::Type::STR_AMOUNT, "value", "The value in " + CURRENCY_UNIT}, + {RPCResult::Type::NUM, "n", "index"}, + {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)"}, + }}, + }}, + }}, + }; +} + static std::vector<RPCArg> CreateTxDoc() { return { @@ -112,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."}, }; } @@ -121,7 +166,7 @@ static RPCHelpMan getrawtransaction() { return RPCHelpMan{ "getrawtransaction", - "\nReturn the raw transaction data.\n" + "Return the raw transaction data.\n" "\nBy default, this call only returns a transaction if it is in the mempool. If -txindex is enabled\n" "and no blockhash argument is passed, it will return the transaction if it is in the mempool or any block.\n" @@ -130,7 +175,7 @@ static RPCHelpMan getrawtransaction() "\nHint: Use gettransaction for wallet transactions.\n" "\nIf verbose is 'true', returns an Object with information about 'txid'.\n" - "If verbose is 'false' or omitted, returns a string that is serialized, hex-encoded data for 'txid'.\n", + "If verbose is 'false' or omitted, returns a string that is serialized, hex-encoded data for 'txid'.", { {"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The transaction id"}, {"verbose", RPCArg::Type::BOOL, RPCArg::Default{false}, "If false, return a string, otherwise return a json object"}, @@ -142,55 +187,16 @@ static RPCHelpMan getrawtransaction() }, RPCResult{"if verbose is set to true", RPCResult::Type::OBJ, "", "", + Cat<std::vector<RPCResult>>( { {RPCResult::Type::BOOL, "in_active_chain", /*optional=*/true, "Whether specified block is in the active chain or not (only present with explicit \"blockhash\" argument)"}, - {RPCResult::Type::STR_HEX, "hex", "The serialized, hex-encoded data for 'txid'"}, - {RPCResult::Type::STR_HEX, "txid", "The transaction id (same as provided)"}, - {RPCResult::Type::STR_HEX, "hash", "The transaction hash (differs from txid for witness transactions)"}, - {RPCResult::Type::NUM, "size", "The serialized transaction size"}, - {RPCResult::Type::NUM, "vsize", "The virtual transaction size (differs from size for witness transactions)"}, - {RPCResult::Type::NUM, "weight", "The transaction's weight (between vsize*4-3 and vsize*4)"}, - {RPCResult::Type::NUM, "version", "The version"}, - {RPCResult::Type::NUM_TIME, "locktime", "The lock time"}, - {RPCResult::Type::ARR, "vin", "", - { - {RPCResult::Type::OBJ, "", "", - { - {RPCResult::Type::STR_HEX, "txid", "The transaction id"}, - {RPCResult::Type::NUM, "vout", "The output number"}, - {RPCResult::Type::OBJ, "scriptSig", "The script", - { - {RPCResult::Type::STR, "asm", "asm"}, - {RPCResult::Type::STR_HEX, "hex", "hex"}, - }}, - {RPCResult::Type::NUM, "sequence", "The script sequence number"}, - {RPCResult::Type::ARR, "txinwitness", /*optional=*/true, "", - { - {RPCResult::Type::STR_HEX, "hex", "hex-encoded witness data (if any)"}, - }}, - }}, - }}, - {RPCResult::Type::ARR, "vout", "", - { - {RPCResult::Type::OBJ, "", "", - { - {RPCResult::Type::NUM, "value", "The value in " + CURRENCY_UNIT}, - {RPCResult::Type::NUM, "n", "index"}, - {RPCResult::Type::OBJ, "scriptPubKey", "", - { - {RPCResult::Type::STR, "asm", "the asm"}, - {RPCResult::Type::STR, "desc", "Inferred descriptor for the output"}, - {RPCResult::Type::STR, "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::STR_HEX, "blockhash", /*optional=*/true, "the block hash"}, {RPCResult::Type::NUM, "confirmations", /*optional=*/true, "The confirmations"}, {RPCResult::Type::NUM_TIME, "blocktime", /*optional=*/true, "The block time expressed in " + UNIX_EPOCH_TIME}, {RPCResult::Type::NUM, "time", /*optional=*/true, "Same as \"blocktime\""}, - } + {RPCResult::Type::STR_HEX, "hex", "The serialized, hex-encoded data for 'txid'"}, + }, + DecodeTxDoc(/*txid_field_doc=*/"The transaction id (same as provided)")), }, }, RPCExamples{ @@ -207,9 +213,9 @@ static RPCHelpMan getrawtransaction() bool in_active_chain = true; uint256 hash = ParseHashV(request.params[0], "parameter 1"); - CBlockIndex* blockindex = nullptr; + const CBlockIndex* blockindex = nullptr; - if (hash == Params().GenesisBlock().hashMerkleRoot) { + if (hash == chainman.GetParams().GenesisBlock().hashMerkleRoot) { // Special exception for the genesis block coinbase transaction throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "The genesis block coinbase is not considered an ordinary transaction and cannot be retrieved"); } @@ -217,7 +223,7 @@ static RPCHelpMan getrawtransaction() // Accept either a bool (true) or a num (>=1) to indicate verbose output. bool fVerbose = false; if (!request.params[1].isNull()) { - fVerbose = request.params[1].isNum() ? (request.params[1].get_int() != 0) : request.params[1].get_bool(); + fVerbose = request.params[1].isNum() ? (request.params[1].getInt<int>() != 0) : request.params[1].get_bool(); } if (!request.params[2].isNull()) { @@ -237,7 +243,7 @@ static RPCHelpMan getrawtransaction() } uint256 hash_block; - const CTransactionRef tx = GetTransaction(blockindex, node.mempool.get(), hash, Params().GetConsensus(), hash_block); + const CTransactionRef tx = GetTransaction(blockindex, node.mempool.get(), hash, chainman.GetConsensus(), hash_block); if (!tx) { std::string errmsg; if (blockindex) { @@ -268,155 +274,6 @@ static RPCHelpMan getrawtransaction() }; } -static RPCHelpMan gettxoutproof() -{ - return RPCHelpMan{"gettxoutproof", - "\nReturns a hex-encoded proof that \"txid\" was included in a block.\n" - "\nNOTE: By default this function only works sometimes. This is when there is an\n" - "unspent output in the utxo for this transaction. To make it always work,\n" - "you need to maintain a transaction index, using the -txindex command line option or\n" - "specify the block in which the transaction is included manually (by blockhash).\n", - { - {"txids", RPCArg::Type::ARR, RPCArg::Optional::NO, "The txids to filter", - { - {"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, "A transaction hash"}, - }, - }, - {"blockhash", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED_NAMED_ARG, "If specified, looks for txid in the block with this hash"}, - }, - RPCResult{ - RPCResult::Type::STR, "data", "A string that is a serialized, hex-encoded data for the proof." - }, - RPCExamples{""}, - [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue -{ - std::set<uint256> setTxids; - UniValue txids = request.params[0].get_array(); - if (txids.empty()) { - throw JSONRPCError(RPC_INVALID_PARAMETER, "Parameter 'txids' cannot be empty"); - } - for (unsigned int idx = 0; idx < txids.size(); idx++) { - auto ret = setTxids.insert(ParseHashV(txids[idx], "txid")); - if (!ret.second) { - throw JSONRPCError(RPC_INVALID_PARAMETER, std::string("Invalid parameter, duplicated txid: ") + txids[idx].get_str()); - } - } - - CBlockIndex* pblockindex = nullptr; - uint256 hashBlock; - ChainstateManager& chainman = EnsureAnyChainman(request.context); - if (!request.params[1].isNull()) { - LOCK(cs_main); - hashBlock = ParseHashV(request.params[1], "blockhash"); - pblockindex = chainman.m_blockman.LookupBlockIndex(hashBlock); - if (!pblockindex) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found"); - } - } else { - LOCK(cs_main); - CChainState& 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) { - const Coin& coin = AccessByTxid(active_chainstate.CoinsTip(), tx); - if (!coin.IsSpent()) { - pblockindex = active_chainstate.m_chain[coin.nHeight]; - break; - } - } - } - - - // Allow txindex to catch up if we need to query it and before we acquire cs_main. - if (g_txindex && !pblockindex) { - g_txindex->BlockUntilSyncedToCurrentChain(); - } - - LOCK(cs_main); - - if (pblockindex == nullptr) { - const CTransactionRef tx = GetTransaction(/* block_index */ nullptr, /* mempool */ nullptr, *setTxids.begin(), Params().GetConsensus(), hashBlock); - if (!tx || hashBlock.IsNull()) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Transaction not yet in block"); - } - pblockindex = chainman.m_blockman.LookupBlockIndex(hashBlock); - if (!pblockindex) { - throw JSONRPCError(RPC_INTERNAL_ERROR, "Transaction index corrupt"); - } - } - - CBlock block; - if (!ReadBlockFromDisk(block, pblockindex, Params().GetConsensus())) { - throw JSONRPCError(RPC_INTERNAL_ERROR, "Can't read block from disk"); - } - - unsigned int ntxFound = 0; - for (const auto& tx : block.vtx) { - if (setTxids.count(tx->GetHash())) { - ntxFound++; - } - } - if (ntxFound != setTxids.size()) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Not all transactions found in specified or retrieved block"); - } - - CDataStream ssMB(SER_NETWORK, PROTOCOL_VERSION | SERIALIZE_TRANSACTION_NO_WITNESS); - CMerkleBlock mb(block, setTxids); - ssMB << mb; - std::string strHex = HexStr(ssMB); - return strHex; -}, - }; -} - -static RPCHelpMan verifytxoutproof() -{ - return RPCHelpMan{"verifytxoutproof", - "\nVerifies that a proof points to a transaction in a block, returning the transaction it commits to\n" - "and throwing an RPC error if the block is not in our best chain\n", - { - {"proof", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The hex-encoded proof generated by gettxoutproof"}, - }, - RPCResult{ - RPCResult::Type::ARR, "", "", - { - {RPCResult::Type::STR_HEX, "txid", "The txid(s) which the proof commits to, or empty array if the proof cannot be validated."}, - } - }, - RPCExamples{""}, - [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue -{ - CDataStream ssMB(ParseHexV(request.params[0], "proof"), SER_NETWORK, PROTOCOL_VERSION | SERIALIZE_TRANSACTION_NO_WITNESS); - CMerkleBlock merkleBlock; - ssMB >> merkleBlock; - - UniValue res(UniValue::VARR); - - std::vector<uint256> vMatch; - std::vector<unsigned int> vIndex; - if (merkleBlock.txn.ExtractMatches(vMatch, vIndex) != merkleBlock.header.hashMerkleRoot) - return res; - - ChainstateManager& chainman = EnsureAnyChainman(request.context); - LOCK(cs_main); - - const CBlockIndex* pindex = chainman.m_blockman.LookupBlockIndex(merkleBlock.header.GetHash()); - if (!pindex || !chainman.ActiveChain().Contains(pindex) || pindex->nTx == 0) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found in chain"); - } - - // Check if proof is valid, only add results if so - if (pindex->nTx == merkleBlock.txn.GetNumTransactions()) { - for (const uint256& hash : vMatch) { - res.push_back(hash.GetHex()); - } - } - - return res; -}, - }; -} - static RPCHelpMan createrawtransaction() { return RPCHelpMan{"createrawtransaction", @@ -445,7 +302,7 @@ static RPCHelpMan createrawtransaction() }, true ); - bool rbf = false; + std::optional<bool> rbf; if (!request.params[3].isNull()) { rbf = request.params[3].isTrue(); } @@ -459,7 +316,7 @@ static RPCHelpMan createrawtransaction() static RPCHelpMan decoderawtransaction() { return RPCHelpMan{"decoderawtransaction", - "\nReturn a JSON object representing the serialized, hex-encoded transaction.\n", + "Return a JSON object representing the serialized, hex-encoded transaction.", { {"hexstring", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The transaction hex string"}, {"iswitness", RPCArg::Type::BOOL, RPCArg::DefaultHint{"depends on heuristic tests"}, "Whether the transaction hex is a serialized witness transaction.\n" @@ -472,50 +329,7 @@ static RPCHelpMan decoderawtransaction() }, RPCResult{ RPCResult::Type::OBJ, "", "", - { - {RPCResult::Type::STR_HEX, "txid", "The transaction id"}, - {RPCResult::Type::STR_HEX, "hash", "The transaction hash (differs from txid for witness transactions)"}, - {RPCResult::Type::NUM, "size", "The transaction size"}, - {RPCResult::Type::NUM, "vsize", "The virtual transaction size (differs from size for witness transactions)"}, - {RPCResult::Type::NUM, "weight", "The transaction's weight (between vsize*4 - 3 and vsize*4)"}, - {RPCResult::Type::NUM, "version", "The version"}, - {RPCResult::Type::NUM_TIME, "locktime", "The lock time"}, - {RPCResult::Type::ARR, "vin", "", - { - {RPCResult::Type::OBJ, "", "", - { - {RPCResult::Type::STR_HEX, "coinbase", /*optional=*/true, ""}, - {RPCResult::Type::STR_HEX, "txid", /*optional=*/true, "The transaction id"}, - {RPCResult::Type::NUM, "vout", /*optional=*/true, "The output number"}, - {RPCResult::Type::OBJ, "scriptSig", /*optional=*/true, "The script", - { - {RPCResult::Type::STR, "asm", "asm"}, - {RPCResult::Type::STR_HEX, "hex", "hex"}, - }}, - {RPCResult::Type::ARR, "txinwitness", /*optional=*/true, "", - { - {RPCResult::Type::STR_HEX, "hex", "hex-encoded witness data (if any)"}, - }}, - {RPCResult::Type::NUM, "sequence", "The script sequence number"}, - }}, - }}, - {RPCResult::Type::ARR, "vout", "", - { - {RPCResult::Type::OBJ, "", "", - { - {RPCResult::Type::NUM, "value", "The value in " + CURRENCY_UNIT}, - {RPCResult::Type::NUM, "n", "index"}, - {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)"}, - }}, - }}, - }}, - } + DecodeTxDoc(/*txid_field_doc=*/"The transaction id"), }, RPCExamples{ HelpExampleCli("decoderawtransaction", "\"hexstring\"") @@ -535,7 +349,7 @@ static RPCHelpMan decoderawtransaction() } UniValue result(UniValue::VOBJ); - TxToUniv(CTransaction(std::move(mtx)), uint256(), result, false); + TxToUniv(CTransaction(std::move(mtx)), /*block_hash=*/uint256(), /*entry=*/result, /*include_hex=*/false); return result; }, @@ -587,7 +401,7 @@ static RPCHelpMan decodescript() } else { // Empty scripts are valid } - ScriptPubKeyToUniv(script, r, /* include_hex */ false); + ScriptToUniv(script, /*out=*/r, /*include_hex=*/false, /*include_address=*/true); std::vector<std::vector<unsigned char>> solutions_data; const TxoutType which_type{Solver(script, solutions_data)}; @@ -651,7 +465,7 @@ static RPCHelpMan decodescript() // Should not be wrapped return false; } // no default case, so the compiler can warn about missing cases - CHECK_NONFATAL(false); + NONFATAL_UNREACHABLE(); }()}; if (can_wrap_P2WSH) { UniValue sr(UniValue::VOBJ); @@ -664,7 +478,7 @@ static RPCHelpMan decodescript() // Scripts that are not fit for P2WPKH are encoded as P2WSH. segwitScr = GetScriptForDestination(WitnessV0ScriptHash(script)); } - ScriptPubKeyToUniv(segwitScr, sr, /* include_hex */ true); + ScriptToUniv(segwitScr, /*out=*/sr, /*include_hex=*/true, /*include_address=*/true); sr.pushKV("p2sh-segwit", EncodeDestination(ScriptHash(segwitScr))); r.pushKV("segwit", sr); } @@ -751,7 +565,7 @@ static RPCHelpMan combinerawtransaction() sigdata.MergeSignatureData(DataFromTransaction(txv, i, coin.out)); } } - ProduceSignature(DUMMY_SIGNING_PROVIDER, MutableTransactionSignatureCreator(&mergedTx, i, coin.out.nValue, 1), coin.out.scriptPubKey, sigdata); + ProduceSignature(DUMMY_SIGNING_PROVIDER, MutableTransactionSignatureCreator(mergedTx, i, coin.out.nValue, 1), coin.out.scriptPubKey, sigdata); UpdateInput(txin, sigdata); } @@ -864,209 +678,199 @@ static RPCHelpMan signrawtransactionwithkey() }; } -static RPCHelpMan sendrawtransaction() -{ - return RPCHelpMan{"sendrawtransaction", - "\nSubmit a raw transaction (serialized, hex-encoded) to local node and network.\n" - "\nThe transaction will be sent unconditionally to all peers, so using sendrawtransaction\n" - "for manual rebroadcast may degrade privacy by leaking the transaction's origin, as\n" - "nodes will normally not rebroadcast non-wallet transactions already in their mempool.\n" - "\nA specific exception, RPC_TRANSACTION_ALREADY_IN_CHAIN, may throw if the transaction cannot be added to the mempool.\n" - "\nRelated RPCs: createrawtransaction, signrawtransactionwithkey\n", +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", "", { - {"hexstring", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The hex string of the raw transaction"}, - {"maxfeerate", RPCArg::Type::AMOUNT, RPCArg::Default{FormatMoney(DEFAULT_MAX_RAW_TX_FEE_RATE.GetFeePerK())}, - "Reject transactions whose fee rate is higher than the specified value, expressed in " + CURRENCY_UNIT + - "/kvB.\nSet to 0 to accept any fee rate.\n"}, - }, - RPCResult{ - RPCResult::Type::STR_HEX, "", "The transaction hash in hex" - }, - RPCExamples{ - "\nCreate a transaction\n" - + HelpExampleCli("createrawtransaction", "\"[{\\\"txid\\\" : \\\"mytxid\\\",\\\"vout\\\":0}]\" \"{\\\"myaddress\\\":0.01}\"") + - "Sign the transaction, and get back the hex\n" - + HelpExampleCli("signrawtransactionwithwallet", "\"myhex\"") + - "\nSend the transaction (signed hex)\n" - + HelpExampleCli("sendrawtransaction", "\"signedhex\"") + - "\nAs a JSON-RPC call\n" - + HelpExampleRpc("sendrawtransaction", "\"signedhex\"") - }, - [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue -{ - RPCTypeCheck(request.params, { - UniValue::VSTR, - UniValueType(), // VNUM or VSTR, checked inside AmountFromValue() - }); - - CMutableTransaction mtx; - if (!DecodeHexTx(mtx, request.params[0].get_str())) { - throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed. Make sure the tx has at least one input."); - } - CTransactionRef tx(MakeTransactionRef(std::move(mtx))); - - const CFeeRate max_raw_tx_fee_rate = request.params[1].isNull() ? - DEFAULT_MAX_RAW_TX_FEE_RATE : - CFeeRate(AmountFromValue(request.params[1])); - - int64_t virtual_size = GetVirtualTransactionSize(*tx); - CAmount max_raw_tx_fee = max_raw_tx_fee_rate.GetFee(virtual_size); - - std::string err_string; - AssertLockNotHeld(cs_main); - NodeContext& node = EnsureAnyNodeContext(request.context); - const TransactionError err = BroadcastTransaction(node, tx, err_string, max_raw_tx_fee, /*relay*/ true, /*wait_callback*/ true); - if (TransactionError::OK != err) { - throw JSONRPCTransactionError(err, err_string); - } - - return tx->GetHash().GetHex(); -}, - }; -} - -static RPCHelpMan testmempoolaccept() -{ - return RPCHelpMan{"testmempoolaccept", - "\nReturns result of mempool acceptance tests indicating if raw transaction(s) (serialized, hex-encoded) would be accepted by mempool.\n" - "\nIf multiple transactions are passed in, parents must come before children and package policies apply: the transactions cannot conflict with any mempool transactions or each other.\n" - "\nIf one transaction fails, other transactions may not be fully validated (the 'allowed' key will be blank).\n" - "\nThe maximum number of transactions allowed is " + ToString(MAX_PACKAGE_COUNT) + ".\n" - "\nThis checks if transactions violate the consensus or policy rules.\n" - "\nSee sendrawtransaction call.\n", + {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, "", "", { - {"rawtxs", RPCArg::Type::ARR, RPCArg::Optional::NO, "An array of hex strings of raw transactions.", - { - {"rawtx", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, ""}, - }, - }, - {"maxfeerate", RPCArg::Type::AMOUNT, RPCArg::Default{FormatMoney(DEFAULT_MAX_RAW_TX_FEE_RATE.GetFeePerK())}, - "Reject transactions whose fee rate is higher than the specified value, expressed in " + CURRENCY_UNIT + "/kvB\n"}, - }, - RPCResult{ - RPCResult::Type::ARR, "", "The result of the mempool acceptance test for each raw transaction in the input array.\n" - "Returns results for each transaction in the same order they were passed in.\n" - "Transactions that cannot be fully validated due to failures in other transactions will not contain an 'allowed' result.\n", + {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::OBJ, "", "", - { - {RPCResult::Type::STR_HEX, "txid", "The transaction hash in hex"}, - {RPCResult::Type::STR_HEX, "wtxid", "The transaction witness hash in hex"}, - {RPCResult::Type::STR, "package-error", /*optional=*/true, "Package validation error, if any (only possible if rawtxs had more than 1 transaction)."}, - {RPCResult::Type::BOOL, "allowed", /*optional=*/true, "Whether this tx would be accepted to the mempool and pass client-specified maxfeerate. " - "If not present, the tx was not fully validated due to a failure in another tx in the list."}, - {RPCResult::Type::NUM, "vsize", /*optional=*/true, "Virtual transaction size as defined in BIP 141. This is different from actual serialized size for witness transactions as witness data is discounted (only present when 'allowed' is true)"}, - {RPCResult::Type::OBJ, "fees", /*optional=*/true, "Transaction fees (only present if 'allowed' is true)", - { - {RPCResult::Type::STR_AMOUNT, "base", "transaction fee in " + CURRENCY_UNIT}, - }}, - {RPCResult::Type::STR, "reject-reason", /*optional=*/true, "Rejection string (only present when 'allowed' is false)"}, - }}, - } - }, - RPCExamples{ - "\nCreate a transaction\n" - + HelpExampleCli("createrawtransaction", "\"[{\\\"txid\\\" : \\\"mytxid\\\",\\\"vout\\\":0}]\" \"{\\\"myaddress\\\":0.01}\"") + - "Sign the transaction, and get back the hex\n" - + HelpExampleCli("signrawtransactionwithwallet", "\"myhex\"") + - "\nTest acceptance of the transaction (signed hex)\n" - + HelpExampleCli("testmempoolaccept", R"('["signedhex"]')") + - "\nAs a JSON-RPC call\n" - + HelpExampleRpc("testmempoolaccept", "[\"signedhex\"]") - }, - [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue -{ - RPCTypeCheck(request.params, { - UniValue::VARR, - UniValueType(), // VNUM or VSTR, checked inside AmountFromValue() - }); - 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."); - } - - const CFeeRate max_raw_tx_fee_rate = request.params[1].isNull() ? - DEFAULT_MAX_RAW_TX_FEE_RATE : - CFeeRate(AmountFromValue(request.params[1])); - - 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))); + {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"}, + }}, + }}, + }}, } +}; - NodeContext& node = EnsureAnyNodeContext(request.context); - CTxMemPool& mempool = EnsureMemPool(node); - ChainstateManager& chainman = EnsureChainman(node); - CChainState& chainstate = chainman.ActiveChainstate(); - const PackageMempoolAcceptResult package_result = [&] { - LOCK(::cs_main); - if (txns.size() > 1) return ProcessNewPackage(chainstate, mempool, txns, /* test_accept */ true); - return PackageMempoolAcceptResult(txns[0]->GetWitnessHash(), - chainman.ProcessTransaction(txns[0], /*test_accept=*/ true)); - }(); - - UniValue rpc_result(UniValue::VARR); - // We will check transaction fees while we iterate through txns in order. If any transaction fee - // exceeds maxfeerate, we will leave the rest of the validation results blank, because it - // doesn't make sense to return a validation result for a transaction if its ancestor(s) would - // not be submitted. - bool exit_early{false}; - for (const auto& tx : txns) { - UniValue result_inner(UniValue::VOBJ); - result_inner.pushKV("txid", tx->GetHash().GetHex()); - result_inner.pushKV("wtxid", tx->GetWitnessHash().GetHex()); - if (package_result.m_state.GetResult() == PackageValidationResult::PCKG_POLICY) { - result_inner.pushKV("package-error", package_result.m_state.GetRejectReason()); - } - auto it = package_result.m_tx_results.find(tx->GetWitnessHash()); - if (exit_early || it == package_result.m_tx_results.end()) { - // Validation unfinished. Just return the txid and wtxid. - rpc_result.push_back(result_inner); - continue; - } - const auto& tx_result = it->second; - // Package testmempoolaccept doesn't allow transactions to already be in the mempool. - CHECK_NONFATAL(tx_result.m_result_type != MempoolAcceptResult::ResultType::MEMPOOL_ENTRY); - if (tx_result.m_result_type == MempoolAcceptResult::ResultType::VALID) { - const CAmount fee = tx_result.m_base_fees.value(); - // Check that fee does not exceed maximum fee - const int64_t virtual_size = tx_result.m_vsize.value(); - const CAmount max_raw_tx_fee = max_raw_tx_fee_rate.GetFee(virtual_size); - if (max_raw_tx_fee && fee > max_raw_tx_fee) { - result_inner.pushKV("allowed", false); - result_inner.pushKV("reject-reason", "max-fee-exceeded"); - exit_early = true; - } else { - // Only return the fee and vsize if the transaction would pass ATMP. - // These can be used to calculate the feerate. - result_inner.pushKV("allowed", true); - result_inner.pushKV("vsize", virtual_size); - UniValue fees(UniValue::VOBJ); - fees.pushKV("base", ValueFromAmount(fee)); - result_inner.pushKV("fees", fees); - } - } else { - result_inner.pushKV("allowed", false); - const TxValidationState state = tx_result.m_state; - if (state.GetResult() == TxValidationResult::TX_MISSING_INPUTS) { - result_inner.pushKV("reject-reason", "missing-inputs"); - } else { - result_inner.pushKV("reject-reason", state.GetRejectReason()); - } - } - rpc_result.push_back(result_inner); +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"}, + }}, + }}, + }}, } - return rpc_result; -}, - }; -} +}; static RPCHelpMan decodepsbt() { @@ -1107,133 +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_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."}, } }, @@ -1255,7 +934,7 @@ static RPCHelpMan decodepsbt() // Add the decoded tx UniValue tx_univ(UniValue::VOBJ); - TxToUniv(CTransaction(*psbtx.tx), uint256(), tx_univ, false); + TxToUniv(CTransaction(*psbtx.tx), /*block_hash=*/uint256(), /*entry=*/tx_univ, /*include_hex=*/false); result.pushKV("tx", tx_univ); // Add the global xpubs @@ -1311,7 +990,7 @@ static RPCHelpMan decodepsbt() txout = input.witness_utxo; UniValue o(UniValue::VOBJ); - ScriptPubKeyToUniv(txout.scriptPubKey, o, /* include_hex */ true); + ScriptToUniv(txout.scriptPubKey, /*out=*/o, /*include_hex=*/true, /*include_address=*/true); UniValue out(UniValue::VOBJ); out.pushKV("amount", ValueFromAmount(txout.nValue)); @@ -1325,7 +1004,7 @@ static RPCHelpMan decodepsbt() txout = input.non_witness_utxo->vout[psbtx.tx->vin[i].prevout.n]; UniValue non_wit(UniValue::VOBJ); - TxToUniv(*input.non_witness_utxo, uint256(), non_wit, false); + TxToUniv(*input.non_witness_utxo, /*block_hash=*/uint256(), /*entry=*/non_wit, /*include_hex=*/false); in.pushKV("non_witness_utxo", non_wit); have_a_utxo = true; @@ -1358,12 +1037,12 @@ static RPCHelpMan decodepsbt() // Redeem script and witness script if (!input.redeem_script.empty()) { UniValue r(UniValue::VOBJ); - ScriptToUniv(input.redeem_script, r); + ScriptToUniv(input.redeem_script, /*out=*/r); in.pushKV("redeem_script", r); } if (!input.witness_script.empty()) { UniValue r(UniValue::VOBJ); - ScriptToUniv(input.witness_script, r); + ScriptToUniv(input.witness_script, /*out=*/r); in.pushKV("witness_script", r); } @@ -1432,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); @@ -1468,12 +1213,12 @@ static RPCHelpMan decodepsbt() // Redeem script and witness script if (!output.redeem_script.empty()) { UniValue r(UniValue::VOBJ); - ScriptToUniv(output.redeem_script, r); + ScriptToUniv(output.redeem_script, /*out=*/r); out.pushKV("redeem_script", r); } if (!output.witness_script.empty()) { UniValue r(UniValue::VOBJ); - ScriptToUniv(output.witness_script, r); + ScriptToUniv(output.witness_script, /*out=*/r); out.pushKV("witness_script", r); } @@ -1490,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); @@ -1665,7 +1451,7 @@ static RPCHelpMan createpsbt() }, true ); - bool rbf = false; + std::optional<bool> rbf; if (!request.params[3].isNull()) { rbf = request.params[3].isTrue(); } @@ -2067,33 +1853,24 @@ static RPCHelpMan analyzepsbt() }; } -void RegisterRawTransactionRPCCommands(CRPCTable &t) +void RegisterRawTransactionRPCCommands(CRPCTable& t) { -// clang-format off -static const CRPCCommand commands[] = -{ // category actor (function) - // --------------------- ----------------------- - { "rawtransactions", &getrawtransaction, }, - { "rawtransactions", &createrawtransaction, }, - { "rawtransactions", &decoderawtransaction, }, - { "rawtransactions", &decodescript, }, - { "rawtransactions", &sendrawtransaction, }, - { "rawtransactions", &combinerawtransaction, }, - { "rawtransactions", &signrawtransactionwithkey, }, - { "rawtransactions", &testmempoolaccept, }, - { "rawtransactions", &decodepsbt, }, - { "rawtransactions", &combinepsbt, }, - { "rawtransactions", &finalizepsbt, }, - { "rawtransactions", &createpsbt, }, - { "rawtransactions", &converttopsbt, }, - { "rawtransactions", &utxoupdatepsbt, }, - { "rawtransactions", &joinpsbts, }, - { "rawtransactions", &analyzepsbt, }, - - { "blockchain", &gettxoutproof, }, - { "blockchain", &verifytxoutproof, }, -}; -// clang-format on + static const CRPCCommand commands[]{ + {"rawtransactions", &getrawtransaction}, + {"rawtransactions", &createrawtransaction}, + {"rawtransactions", &decoderawtransaction}, + {"rawtransactions", &decodescript}, + {"rawtransactions", &combinerawtransaction}, + {"rawtransactions", &signrawtransactionwithkey}, + {"rawtransactions", &decodepsbt}, + {"rawtransactions", &combinepsbt}, + {"rawtransactions", &finalizepsbt}, + {"rawtransactions", &createpsbt}, + {"rawtransactions", &converttopsbt}, + {"rawtransactions", &utxoupdatepsbt}, + {"rawtransactions", &joinpsbts}, + {"rawtransactions", &analyzepsbt}, + }; for (const auto& c : commands) { t.appendCommand(c.name, &c); } diff --git a/src/rpc/rawtransaction_util.cpp b/src/rpc/rawtransaction_util.cpp index e23fe34480..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"); @@ -40,7 +40,7 @@ CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniVal CMutableTransaction rawTx; if (!locktime.isNull()) { - int64_t nLockTime = locktime.get_int64(); + int64_t nLockTime = locktime.getInt<int64_t>(); if (nLockTime < 0 || nLockTime > LOCKTIME_MAX) throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, locktime out of range"); rawTx.nLockTime = nLockTime; @@ -55,12 +55,13 @@ CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniVal const UniValue& vout_v = find_value(o, "vout"); if (!vout_v.isNum()) throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, missing vout key"); - int nOutput = vout_v.get_int(); + int nOutput = vout_v.getInt<int>(); if (nOutput < 0) 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 */ @@ -71,7 +72,7 @@ CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniVal // set the sequence number if passed in the parameters object const UniValue& sequenceObj = find_value(o, "sequence"); if (sequenceObj.isNum()) { - int64_t seqNr64 = sequenceObj.get_int64(); + int64_t seqNr64 = sequenceObj.getInt<int64_t>(); if (seqNr64 < 0 || seqNr64 > CTxIn::SEQUENCE_FINAL) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, sequence number is out of range"); } else { @@ -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, { @@ -177,7 +178,7 @@ void ParsePrevouts(const UniValue& prevTxsUnival, FillableSigningProvider* keyst uint256 txid = ParseHashO(prevOut, "txid"); - int nOut = find_value(prevOut, "vout").get_int(); + int nOut = find_value(prevOut, "vout").getInt<int>(); if (nOut < 0) { throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "vout cannot be negative"); } 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/register.h b/src/rpc/register.h index c5055cc9d7..301f410da3 100644 --- a/src/rpc/register.h +++ b/src/rpc/register.h @@ -9,29 +9,33 @@ * headers for everything under src/rpc/ */ class CRPCTable; -/** Register block chain RPC commands */ void RegisterBlockchainRPCCommands(CRPCTable &tableRPC); -/** Register P2P networking RPC commands */ -void RegisterNetRPCCommands(CRPCTable &tableRPC); -/** Register miscellaneous RPC commands */ -void RegisterMiscRPCCommands(CRPCTable &tableRPC); -/** Register mining RPC commands */ +void RegisterFeeRPCCommands(CRPCTable&); +void RegisterMempoolRPCCommands(CRPCTable&); void RegisterMiningRPCCommands(CRPCTable &tableRPC); -/** Register raw transaction RPC commands */ +void RegisterNodeRPCCommands(CRPCTable&); +void RegisterNetRPCCommands(CRPCTable&); +void RegisterOutputScriptRPCCommands(CRPCTable&); void RegisterRawTransactionRPCCommands(CRPCTable &tableRPC); -/** Register raw transaction RPC commands */ +void RegisterSignMessageRPCCommands(CRPCTable&); void RegisterSignerRPCCommands(CRPCTable &tableRPC); +void RegisterTxoutProofRPCCommands(CRPCTable&); static inline void RegisterAllCoreRPCCommands(CRPCTable &t) { RegisterBlockchainRPCCommands(t); - RegisterNetRPCCommands(t); - RegisterMiscRPCCommands(t); + RegisterFeeRPCCommands(t); + RegisterMempoolRPCCommands(t); RegisterMiningRPCCommands(t); + RegisterNodeRPCCommands(t); + RegisterNetRPCCommands(t); + RegisterOutputScriptRPCCommands(t); RegisterRawTransactionRPCCommands(t); + RegisterSignMessageRPCCommands(t); #ifdef ENABLE_EXTERNAL_SIGNER RegisterSignerRPCCommands(t); #endif // ENABLE_EXTERNAL_SIGNER + RegisterTxoutProofRPCCommands(t); } #endif // BITCOIN_RPC_REGISTER_H diff --git a/src/rpc/request.cpp b/src/rpc/request.cpp index 95a7c25b93..8595fa78bb 100644 --- a/src/rpc/request.cpp +++ b/src/rpc/request.cpp @@ -66,23 +66,23 @@ 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) { const size_t COOKIE_SIZE = 32; unsigned char rand_pwd[COOKIE_SIZE]; - GetRandBytes(rand_pwd, COOKIE_SIZE); + GetRandBytes(rand_pwd); std::string cookie = COOKIEAUTH_USER + ":" + HexStr(rand_pwd); /** the umask determines what permissions are used to create this file - @@ -146,7 +146,7 @@ std::vector<UniValue> JSONRPCProcessBatchReply(const UniValue& in) if (!rec.isObject()) { throw std::runtime_error("Batch member must be an object"); } - size_t id = rec["id"].get_int(); + size_t id = rec["id"].getInt<int>(); if (id >= num) { throw std::runtime_error("Batch member id is larger than batch size"); } diff --git a/src/rpc/server.cpp b/src/rpc/server.cpp index c70236cc1c..e9987d73be 100644 --- a/src/rpc/server.cpp +++ b/src/rpc/server.cpp @@ -9,32 +9,35 @@ #include <shutdown.h> #include <sync.h> #include <util/strencodings.h> +#include <util/string.h> #include <util/system.h> +#include <util/time.h> -#include <boost/algorithm/string/classification.hpp> -#include <boost/algorithm/string/split.hpp> #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; +using SteadyClock = std::chrono::steady_clock; + +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 @@ -51,7 +54,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() { @@ -167,7 +170,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", "", {}, /*hidden=*/true}, }, RPCResult{RPCResult::Type::STR, "", "A string with the content '" + RESULT + "'"}, RPCExamples{""}, @@ -177,7 +180,7 @@ static RPCHelpMan stop() // this reply will get back to the client. StartShutdown(); if (jsonRequest.params[0].isNum()) { - UninterruptibleSleep(std::chrono::milliseconds{jsonRequest.params[0].get_int()}); + UninterruptibleSleep(std::chrono::milliseconds{jsonRequest.params[0].getInt<int>()}); } return RESULT; }, @@ -232,7 +235,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); } @@ -248,17 +251,13 @@ static RPCHelpMan getrpcinfo() }; } -// clang-format off -static const CRPCCommand vRPCCommands[] = -{ // category actor (function) - // --------------------- ----------------------- +static const CRPCCommand vRPCCommands[]{ /* Overall control/query calls */ - { "control", &getrpcinfo, }, - { "control", &help, }, - { "control", &stop, }, - { "control", &uptime, }, + {"control", &getrpcinfo}, + {"control", &help}, + {"control", &stop}, + {"control", &uptime}, }; -// clang-format on CRPCTable::CRPCTable() { @@ -407,8 +406,7 @@ static inline JSONRPCRequest transformNamedArguments(const JSONRPCRequest& in, c // Process expected parameters. int hole = 0; for (const std::string &argNamePattern: argNames) { - std::vector<std::string> vargNames; - boost::algorithm::split(vargNames, argNamePattern, boost::algorithm::is_any_of("|")); + std::vector<std::string> vargNames = SplitString(argNamePattern, '|'); auto fr = argsIn.end(); for (const std::string & argName : vargNames) { fr = argsIn.find(argName); diff --git a/src/rpc/signmessage.cpp b/src/rpc/signmessage.cpp new file mode 100644 index 0000000000..8c752ba1fd --- /dev/null +++ b/src/rpc/signmessage.cpp @@ -0,0 +1,113 @@ +// Copyright (c) 2010 Satoshi Nakamoto +// Copyright (c) 2009-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 <key.h> +#include <key_io.h> +#include <rpc/protocol.h> +#include <rpc/request.h> +#include <rpc/server.h> +#include <rpc/util.h> +#include <univalue.h> +#include <util/message.h> + +#include <string> + +static RPCHelpMan verifymessage() +{ + return RPCHelpMan{"verifymessage", + "Verify a signed message.", + { + {"address", RPCArg::Type::STR, RPCArg::Optional::NO, "The bitcoin address to use for the signature."}, + {"signature", RPCArg::Type::STR, RPCArg::Optional::NO, "The signature provided by the signer in base 64 encoding (see signmessage)."}, + {"message", RPCArg::Type::STR, RPCArg::Optional::NO, "The message that was signed."}, + }, + RPCResult{ + RPCResult::Type::BOOL, "", "If the signature is verified or not." + }, + RPCExamples{ + "\nUnlock the wallet for 30 seconds\n" + + HelpExampleCli("walletpassphrase", "\"mypassphrase\" 30") + + "\nCreate the signature\n" + + HelpExampleCli("signmessage", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\" \"my message\"") + + "\nVerify the signature\n" + + HelpExampleCli("verifymessage", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\" \"signature\" \"my message\"") + + "\nAs a JSON-RPC call\n" + + HelpExampleRpc("verifymessage", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\", \"signature\", \"my message\"") + }, + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue + { + std::string strAddress = request.params[0].get_str(); + std::string strSign = request.params[1].get_str(); + std::string strMessage = request.params[2].get_str(); + + switch (MessageVerify(strAddress, strSign, strMessage)) { + case MessageVerificationResult::ERR_INVALID_ADDRESS: + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid address"); + case MessageVerificationResult::ERR_ADDRESS_NO_KEY: + throw JSONRPCError(RPC_TYPE_ERROR, "Address does not refer to key"); + case MessageVerificationResult::ERR_MALFORMED_SIGNATURE: + throw JSONRPCError(RPC_TYPE_ERROR, "Malformed base64 encoding"); + case MessageVerificationResult::ERR_PUBKEY_NOT_RECOVERED: + case MessageVerificationResult::ERR_NOT_SIGNED: + return false; + case MessageVerificationResult::OK: + return true; + } + + return false; + }, + }; +} + +static RPCHelpMan signmessagewithprivkey() +{ + return RPCHelpMan{"signmessagewithprivkey", + "\nSign a message with the private key of an address\n", + { + {"privkey", RPCArg::Type::STR, RPCArg::Optional::NO, "The private key to sign the message with."}, + {"message", RPCArg::Type::STR, RPCArg::Optional::NO, "The message to create a signature of."}, + }, + RPCResult{ + RPCResult::Type::STR, "signature", "The signature of the message encoded in base 64" + }, + RPCExamples{ + "\nCreate the signature\n" + + HelpExampleCli("signmessagewithprivkey", "\"privkey\" \"my message\"") + + "\nVerify the signature\n" + + HelpExampleCli("verifymessage", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\" \"signature\" \"my message\"") + + "\nAs a JSON-RPC call\n" + + HelpExampleRpc("signmessagewithprivkey", "\"privkey\", \"my message\"") + }, + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue + { + std::string strPrivkey = request.params[0].get_str(); + std::string strMessage = request.params[1].get_str(); + + CKey key = DecodeSecret(strPrivkey); + if (!key.IsValid()) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid private key"); + } + + std::string signature; + + if (!MessageSign(key, strMessage, signature)) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Sign failed"); + } + + return signature; + }, + }; +} + +void RegisterSignMessageRPCCommands(CRPCTable& t) +{ + static const CRPCCommand commands[]{ + {"util", &verifymessage}, + {"util", &signmessagewithprivkey}, + }; + for (const auto& c : commands) { + t.appendCommand(c.name, &c); + } +} diff --git a/src/rpc/txoutproof.cpp b/src/rpc/txoutproof.cpp new file mode 100644 index 0000000000..dcf6c6bee1 --- /dev/null +++ b/src/rpc/txoutproof.cpp @@ -0,0 +1,181 @@ +// Copyright (c) 2010 Satoshi Nakamoto +// Copyright (c) 2009-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 <coins.h> +#include <index/txindex.h> +#include <merkleblock.h> +#include <node/blockstorage.h> +#include <primitives/transaction.h> +#include <rpc/server.h> +#include <rpc/server_util.h> +#include <rpc/util.h> +#include <univalue.h> +#include <util/strencodings.h> +#include <validation.h> + +using node::GetTransaction; +using node::ReadBlockFromDisk; + +static RPCHelpMan gettxoutproof() +{ + return RPCHelpMan{"gettxoutproof", + "\nReturns a hex-encoded proof that \"txid\" was included in a block.\n" + "\nNOTE: By default this function only works sometimes. This is when there is an\n" + "unspent output in the utxo for this transaction. To make it always work,\n" + "you need to maintain a transaction index, using the -txindex command line option or\n" + "specify the block in which the transaction is included manually (by blockhash).\n", + { + {"txids", RPCArg::Type::ARR, RPCArg::Optional::NO, "The txids to filter", + { + {"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, "A transaction hash"}, + }, + }, + {"blockhash", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED_NAMED_ARG, "If specified, looks for txid in the block with this hash"}, + }, + RPCResult{ + RPCResult::Type::STR, "data", "A string that is a serialized, hex-encoded data for the proof." + }, + RPCExamples{""}, + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue + { + std::set<uint256> setTxids; + UniValue txids = request.params[0].get_array(); + if (txids.empty()) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Parameter 'txids' cannot be empty"); + } + for (unsigned int idx = 0; idx < txids.size(); idx++) { + auto ret = setTxids.insert(ParseHashV(txids[idx], "txid")); + if (!ret.second) { + throw JSONRPCError(RPC_INVALID_PARAMETER, std::string("Invalid parameter, duplicated txid: ") + txids[idx].get_str()); + } + } + + const CBlockIndex* pblockindex = nullptr; + uint256 hashBlock; + ChainstateManager& chainman = EnsureAnyChainman(request.context); + if (!request.params[1].isNull()) { + LOCK(cs_main); + hashBlock = ParseHashV(request.params[1], "blockhash"); + pblockindex = chainman.m_blockman.LookupBlockIndex(hashBlock); + if (!pblockindex) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found"); + } + } else { + LOCK(cs_main); + CChainState& 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) { + const Coin& coin = AccessByTxid(active_chainstate.CoinsTip(), tx); + if (!coin.IsSpent()) { + pblockindex = active_chainstate.m_chain[coin.nHeight]; + break; + } + } + } + + + // Allow txindex to catch up if we need to query it and before we acquire cs_main. + if (g_txindex && !pblockindex) { + g_txindex->BlockUntilSyncedToCurrentChain(); + } + + LOCK(cs_main); + + if (pblockindex == nullptr) { + const CTransactionRef tx = GetTransaction(/*block_index=*/nullptr, /*mempool=*/nullptr, *setTxids.begin(), chainman.GetConsensus(), hashBlock); + if (!tx || hashBlock.IsNull()) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Transaction not yet in block"); + } + pblockindex = chainman.m_blockman.LookupBlockIndex(hashBlock); + if (!pblockindex) { + throw JSONRPCError(RPC_INTERNAL_ERROR, "Transaction index corrupt"); + } + } + + CBlock block; + if (!ReadBlockFromDisk(block, pblockindex, chainman.GetConsensus())) { + throw JSONRPCError(RPC_INTERNAL_ERROR, "Can't read block from disk"); + } + + unsigned int ntxFound = 0; + for (const auto& tx : block.vtx) { + if (setTxids.count(tx->GetHash())) { + ntxFound++; + } + } + if (ntxFound != setTxids.size()) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Not all transactions found in specified or retrieved block"); + } + + CDataStream ssMB(SER_NETWORK, PROTOCOL_VERSION | SERIALIZE_TRANSACTION_NO_WITNESS); + CMerkleBlock mb(block, setTxids); + ssMB << mb; + std::string strHex = HexStr(ssMB); + return strHex; + }, + }; +} + +static RPCHelpMan verifytxoutproof() +{ + return RPCHelpMan{"verifytxoutproof", + "\nVerifies that a proof points to a transaction in a block, returning the transaction it commits to\n" + "and throwing an RPC error if the block is not in our best chain\n", + { + {"proof", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The hex-encoded proof generated by gettxoutproof"}, + }, + RPCResult{ + RPCResult::Type::ARR, "", "", + { + {RPCResult::Type::STR_HEX, "txid", "The txid(s) which the proof commits to, or empty array if the proof cannot be validated."}, + } + }, + RPCExamples{""}, + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue + { + CDataStream ssMB(ParseHexV(request.params[0], "proof"), SER_NETWORK, PROTOCOL_VERSION | SERIALIZE_TRANSACTION_NO_WITNESS); + CMerkleBlock merkleBlock; + ssMB >> merkleBlock; + + UniValue res(UniValue::VARR); + + std::vector<uint256> vMatch; + std::vector<unsigned int> vIndex; + if (merkleBlock.txn.ExtractMatches(vMatch, vIndex) != merkleBlock.header.hashMerkleRoot) + return res; + + ChainstateManager& chainman = EnsureAnyChainman(request.context); + LOCK(cs_main); + + const CBlockIndex* pindex = chainman.m_blockman.LookupBlockIndex(merkleBlock.header.GetHash()); + if (!pindex || !chainman.ActiveChain().Contains(pindex) || pindex->nTx == 0) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found in chain"); + } + + // Check if proof is valid, only add results if so + if (pindex->nTx == merkleBlock.txn.GetNumTransactions()) { + for (const uint256& hash : vMatch) { + res.push_back(hash.GetHex()); + } + } + + return res; + }, + }; +} + +void RegisterTxoutProofRPCCommands(CRPCTable& t) +{ + static const CRPCCommand commands[]{ + {"blockchain", &gettxoutproof}, + {"blockchain", &verifytxoutproof}, + }; + for (const auto& c : commands) { + t.appendCommand(c.name, &c); + } +} diff --git a/src/rpc/util.cpp b/src/rpc/util.cpp index 7c859268be..24ab21a947 100644 --- a/src/rpc/util.cpp +++ b/src/rpc/util.cpp @@ -9,15 +9,14 @@ #include <script/descriptor.h> #include <script/signingprovider.h> #include <tinyformat.h> +#include <util/check.h> #include <util/strencodings.h> #include <util/string.h> +#include <util/system.h> #include <util/translation.h> #include <tuple> -#include <boost/algorithm/string/classification.hpp> -#include <boost/algorithm/string/split.hpp> - const std::string UNIX_EPOCH_TIME = "UNIX epoch time"; const std::string EXAMPLE_ADDRESS[2] = {"bc1q09vm5lfy0j5reeulh4x5752q25uqqvz34hufdl", "bc1q02ad21edsxd23d32dfgqqsz4vv4nmtfzuklhy3"}; @@ -99,7 +98,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 @@ -269,7 +268,7 @@ CTxDestination AddAndGetMultisigDestination(const int required, const std::vecto class DescribeAddressVisitor { public: - explicit DescribeAddressVisitor() {} + explicit DescribeAddressVisitor() = default; UniValue operator()(const CNoDestination& dest) const { @@ -339,7 +338,7 @@ UniValue DescribeAddress(const CTxDestination& dest) unsigned int ParseConfirmTarget(const UniValue& value, unsigned int max_target) { - const int target{value.get_int()}; + const int target{value.getInt<int>()}; const unsigned int unsigned_target{static_cast<unsigned int>(target)}; if (target < 1 || unsigned_target > max_target) { throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Invalid conf_target, must be between %u and %u", 1, max_target)); @@ -421,7 +420,7 @@ struct Sections { if (arg.m_type_str.size() != 0 && push_name) { left += "\"" + arg.GetName() + "\": " + arg.m_type_str.at(0); } else { - left += push_name ? arg.ToStringObj(/* oneline */ false) : arg.ToString(/* oneline */ false); + left += push_name ? arg.ToStringObj(/*oneline=*/false) : arg.ToString(/*oneline=*/false); } left += ","; PushSection({left, arg.ToDescriptionString()}); @@ -513,8 +512,7 @@ RPCHelpMan::RPCHelpMan(std::string name, std::string description, std::vector<RP { std::set<std::string> named_args; for (const auto& arg : m_args) { - std::vector<std::string> names; - boost::split(names, arg.m_names, boost::is_any_of("|")); + std::vector<std::string> names = SplitString(arg.m_names, '|'); // Should have unique named arguments for (const std::string& name : names) { CHECK_NONFATAL(named_args.insert(name).second); @@ -542,7 +540,7 @@ RPCHelpMan::RPCHelpMan(std::string name, std::string description, std::vector<RP // Null values are accepted in all arguments break; default: - CHECK_NONFATAL(false); + NONFATAL_UNREACHABLE(); break; } } @@ -584,7 +582,9 @@ UniValue RPCHelpMan::HandleRequest(const JSONRPCRequest& request) const throw std::runtime_error(ToString()); } const UniValue ret = m_fun(*this, request); - CHECK_NONFATAL(std::any_of(m_results.m_results.begin(), m_results.m_results.end(), [ret](const RPCResult& res) { return res.MatchesType(ret); })); + if (gArgs.GetBoolArg("-rpcdoccheck", DEFAULT_RPC_DOC_CHECK)) { + CHECK_NONFATAL(std::any_of(m_results.m_results.begin(), m_results.m_results.end(), [&ret](const RPCResult& res) { return res.MatchesType(ret); })); + } return ret; } @@ -627,7 +627,7 @@ std::string RPCHelpMan::ToString() const if (was_optional) ret += ") "; was_optional = false; } - ret += arg.ToString(/* oneline */ true); + ret += arg.ToString(/*oneline=*/true); } if (was_optional) ret += " )"; @@ -665,8 +665,7 @@ UniValue RPCHelpMan::GetArgMap() const UniValue arr{UniValue::VARR}; for (int i{0}; i < int(m_args.size()); ++i) { const auto& arg = m_args.at(i); - std::vector<std::string> arg_names; - boost::split(arg_names, arg.m_names, boost::is_any_of("|")); + std::vector<std::string> arg_names = SplitString(arg.m_names, '|'); for (const auto& arg_name : arg_names) { UniValue map{UniValue::VARR}; map.push_back(m_name); @@ -774,7 +773,7 @@ void RPCResult::ToSections(Sections& sections, const OuterType outer_type, const // Elements in a JSON structure (dictionary or array) are separated by a comma const std::string maybe_separator{outer_type != OuterType::NONE ? "," : ""}; - // The key name if recursed into an dictionary + // The key name if recursed into a dictionary const std::string maybe_key{ outer_type == OuterType::OBJ ? "\"" + this->m_key_name + "\" : " : @@ -793,7 +792,7 @@ void RPCResult::ToSections(Sections& sections, const OuterType outer_type, const return; } case Type::ANY: { - CHECK_NONFATAL(false); // Only for testing + NONFATAL_UNREACHABLE(); // Only for testing } case Type::NONE: { sections.PushSection({indent + "null" + maybe_separator, Description("json null")}); @@ -860,15 +859,16 @@ void RPCResult::ToSections(Sections& sections, const OuterType outer_type, const return; } } // no default case, so the compiler can warn about missing cases - CHECK_NONFATAL(false); + NONFATAL_UNREACHABLE(); } bool RPCResult::MatchesType(const UniValue& result) const { - switch (m_type) { - case Type::ELISION: { - return false; + if (m_skip_type_check) { + return true; } + switch (m_type) { + case Type::ELISION: case Type::ANY: { return true; } @@ -889,14 +889,55 @@ bool RPCResult::MatchesType(const UniValue& result) const } case Type::ARR_FIXED: case Type::ARR: { - return UniValue::VARR == result.getType(); + if (UniValue::VARR != result.getType()) return false; + for (size_t i{0}; i < result.get_array().size(); ++i) { + // If there are more results than documented, re-use the last doc_inner. + const RPCResult& doc_inner{m_inner.at(std::min(m_inner.size() - 1, i))}; + if (!doc_inner.MatchesType(result.get_array()[i])) return false; + } + return true; // empty result array is valid } case Type::OBJ_DYN: case Type::OBJ: { - return UniValue::VOBJ == result.getType(); + if (UniValue::VOBJ != result.getType()) return false; + if (!m_inner.empty() && m_inner.at(0).m_type == Type::ELISION) return true; + if (m_type == Type::OBJ_DYN) { + const RPCResult& doc_inner{m_inner.at(0)}; // Assume all types are the same, randomly pick the first + for (size_t i{0}; i < result.get_obj().size(); ++i) { + if (!doc_inner.MatchesType(result.get_obj()[i])) { + return false; + } + } + return true; // empty result obj is valid + } + std::set<std::string> doc_keys; + for (const auto& doc_entry : m_inner) { + doc_keys.insert(doc_entry.m_key_name); + } + std::map<std::string, UniValue> result_obj; + result.getObjMap(result_obj); + for (const auto& result_entry : result_obj) { + if (doc_keys.find(result_entry.first) == doc_keys.end()) { + return false; // missing documentation + } + } + + for (const auto& doc_entry : m_inner) { + const auto result_it{result_obj.find(doc_entry.m_key_name)}; + if (result_it == result_obj.end()) { + if (!doc_entry.m_optional) { + return false; // result is missing a required key + } + continue; + } + if (!doc_entry.MatchesType(result_it->second)) { + return false; // wrong type + } + } + return true; } } // no default case, so the compiler can warn about missing cases - CHECK_NONFATAL(false); + NONFATAL_UNREACHABLE(); } void RPCResult::CheckInnerDoc() const @@ -942,9 +983,9 @@ std::string RPCArg::ToStringObj(const bool oneline) const case Type::OBJ: case Type::OBJ_USER_KEYS: // Currently unused, so avoid writing dead code - CHECK_NONFATAL(false); + NONFATAL_UNREACHABLE(); } // no default case, so the compiler can warn about missing cases - CHECK_NONFATAL(false); + NONFATAL_UNREACHABLE(); } std::string RPCArg::ToString(const bool oneline) const @@ -979,17 +1020,17 @@ std::string RPCArg::ToString(const bool oneline) const return "[" + res + "...]"; } } // no default case, so the compiler can warn about missing cases - CHECK_NONFATAL(false); + NONFATAL_UNREACHABLE(); } static std::pair<int64_t, int64_t> ParseRange(const UniValue& value) { if (value.isNum()) { - return {0, value.get_int64()}; + return {0, value.getInt<int64_t>()}; } if (value.isArray() && value.size() == 2 && value[0].isNum() && value[1].isNum()) { - int64_t low = value[0].get_int64(); - int64_t high = value[1].get_int64(); + int64_t low = value[0].getInt<int64_t>(); + int64_t high = value[1].getInt<int64_t>(); if (low > high) throw JSONRPCError(RPC_INVALID_PARAMETER, "Range specified as [begin,end] must not have begin after end"); return {low, high}; } diff --git a/src/rpc/util.h b/src/rpc/util.h index 89d32d4193..e883dc008e 100644 --- a/src/rpc/util.h +++ b/src/rpc/util.h @@ -5,7 +5,6 @@ #ifndef BITCOIN_RPC_UTIL_H #define BITCOIN_RPC_UTIL_H -#include <node/coinstats.h> #include <node/transaction.h> #include <outputtype.h> #include <protocol.h> @@ -22,6 +21,14 @@ #include <variant> #include <vector> +static constexpr bool DEFAULT_RPC_DOC_CHECK{ +#ifdef RPC_DOC_CHECK + true +#else + false +#endif +}; + /** * String used to describe UNIX epoch time in documentation, factored out to a * constant for consistency. @@ -256,6 +263,7 @@ struct RPCResult { const std::string m_key_name; //!< Only used for dicts const std::vector<RPCResult> m_inner; //!< Only used for arrays or dicts const bool m_optional; + const bool m_skip_type_check; const std::string m_description; const std::string m_cond; @@ -270,6 +278,7 @@ struct RPCResult { m_key_name{std::move(m_key_name)}, m_inner{std::move(inner)}, m_optional{optional}, + m_skip_type_check{false}, m_description{std::move(description)}, m_cond{std::move(cond)} { @@ -290,11 +299,13 @@ struct RPCResult { const std::string m_key_name, const bool optional, const std::string description, - const std::vector<RPCResult> inner = {}) + const std::vector<RPCResult> inner = {}, + bool skip_type_check = false) : m_type{std::move(type)}, m_key_name{std::move(m_key_name)}, m_inner{std::move(inner)}, m_optional{optional}, + m_skip_type_check{skip_type_check}, m_description{std::move(description)}, m_cond{} { @@ -305,8 +316,9 @@ struct RPCResult { const Type type, const std::string m_key_name, const std::string description, - const std::vector<RPCResult> inner = {}) - : RPCResult{type, m_key_name, false, description, inner} {} + const std::vector<RPCResult> inner = {}, + bool skip_type_check = false) + : RPCResult{type, m_key_name, false, description, inner, skip_type_check} {} /** Append the sections of the result. */ void ToSections(Sections& sections, OuterType outer_type = OuterType::NONE, const int current_indent = 0) const; diff --git a/src/scheduler.cpp b/src/scheduler.cpp index 0b2ad3c553..3df1d48b3c 100644 --- a/src/scheduler.cpp +++ b/src/scheduler.cpp @@ -4,17 +4,15 @@ #include <scheduler.h> -#include <random.h> +#include <sync.h> #include <util/syscall_sandbox.h> #include <util/time.h> -#include <assert.h> +#include <cassert> #include <functional> #include <utility> -CScheduler::CScheduler() -{ -} +CScheduler::CScheduler() = default; CScheduler::~CScheduler() { @@ -43,7 +41,7 @@ void CScheduler::serviceQueue() // the time of the first item on the queue: while (!shouldStop() && !taskQueue.empty()) { - std::chrono::system_clock::time_point timeToWaitFor = taskQueue.begin()->first; + std::chrono::steady_clock::time_point timeToWaitFor = taskQueue.begin()->first; if (newTaskScheduled.wait_until(lock, timeToWaitFor) == std::cv_status::timeout) { break; // Exit loop after timeout, it means we reached the time of the event } @@ -72,7 +70,7 @@ void CScheduler::serviceQueue() newTaskScheduled.notify_one(); } -void CScheduler::schedule(CScheduler::Function f, std::chrono::system_clock::time_point t) +void CScheduler::schedule(CScheduler::Function f, std::chrono::steady_clock::time_point t) { { LOCK(newTaskMutex); @@ -89,7 +87,7 @@ void CScheduler::MockForward(std::chrono::seconds delta_seconds) LOCK(newTaskMutex); // use temp_queue to maintain updated schedule - std::multimap<std::chrono::system_clock::time_point, Function> temp_queue; + std::multimap<std::chrono::steady_clock::time_point, Function> temp_queue; for (const auto& element : taskQueue) { temp_queue.emplace_hint(temp_queue.cend(), element.first - delta_seconds, element.second); @@ -111,11 +109,11 @@ static void Repeat(CScheduler& s, CScheduler::Function f, std::chrono::milliseco void CScheduler::scheduleEvery(CScheduler::Function f, std::chrono::milliseconds delta) { - scheduleFromNow([=] { Repeat(*this, f, delta); }, delta); + scheduleFromNow([this, f, delta] { Repeat(*this, f, delta); }, delta); } -size_t CScheduler::getQueueInfo(std::chrono::system_clock::time_point& first, - std::chrono::system_clock::time_point& last) const +size_t CScheduler::getQueueInfo(std::chrono::steady_clock::time_point& first, + std::chrono::steady_clock::time_point& last) const { LOCK(newTaskMutex); size_t result = taskQueue.size(); @@ -143,7 +141,7 @@ void SingleThreadedSchedulerClient::MaybeScheduleProcessQueue() if (m_are_callbacks_running) return; if (m_callbacks_pending.empty()) return; } - m_pscheduler->schedule(std::bind(&SingleThreadedSchedulerClient::ProcessQueue, this), std::chrono::system_clock::now()); + m_scheduler.schedule([this] { this->ProcessQueue(); }, std::chrono::steady_clock::now()); } void SingleThreadedSchedulerClient::ProcessQueue() @@ -179,8 +177,6 @@ void SingleThreadedSchedulerClient::ProcessQueue() void SingleThreadedSchedulerClient::AddToProcessQueue(std::function<void()> func) { - assert(m_pscheduler); - { LOCK(m_callbacks_mutex); m_callbacks_pending.emplace_back(std::move(func)); @@ -190,7 +186,7 @@ void SingleThreadedSchedulerClient::AddToProcessQueue(std::function<void()> func void SingleThreadedSchedulerClient::EmptyQueue() { - assert(!m_pscheduler->AreThreadsServicingQueue()); + assert(!m_scheduler.AreThreadsServicingQueue()); bool should_continue = true; while (should_continue) { ProcessQueue(); diff --git a/src/scheduler.h b/src/scheduler.h index bb0abfbf7a..749e5442b0 100644 --- a/src/scheduler.h +++ b/src/scheduler.h @@ -5,13 +5,18 @@ #ifndef BITCOIN_SCHEDULER_H #define BITCOIN_SCHEDULER_H +#include <attributes.h> +#include <sync.h> +#include <threadsafety.h> + +#include <chrono> #include <condition_variable> +#include <cstddef> #include <functional> #include <list> #include <map> #include <thread> - -#include <sync.h> +#include <utility> /** * Simple class for background tasks that should be run @@ -41,12 +46,12 @@ public: typedef std::function<void()> Function; /** Call func at/after time t */ - void schedule(Function f, std::chrono::system_clock::time_point t); + void schedule(Function f, std::chrono::steady_clock::time_point t) EXCLUSIVE_LOCKS_REQUIRED(!newTaskMutex); /** Call f once after the delta has passed */ - void scheduleFromNow(Function f, std::chrono::milliseconds delta) + void scheduleFromNow(Function f, std::chrono::milliseconds delta) EXCLUSIVE_LOCKS_REQUIRED(!newTaskMutex) { - schedule(std::move(f), std::chrono::system_clock::now() + delta); + schedule(std::move(f), std::chrono::steady_clock::now() + delta); } /** @@ -55,29 +60,29 @@ public: * The timing is not exact: Every time f is finished, it is rescheduled to run again after delta. If you need more * accurate scheduling, don't use this method. */ - void scheduleEvery(Function f, std::chrono::milliseconds delta); + void scheduleEvery(Function f, std::chrono::milliseconds delta) EXCLUSIVE_LOCKS_REQUIRED(!newTaskMutex); /** * Mock the scheduler to fast forward in time. * Iterates through items on taskQueue and reschedules them * to be delta_seconds sooner. */ - void MockForward(std::chrono::seconds delta_seconds); + void MockForward(std::chrono::seconds delta_seconds) EXCLUSIVE_LOCKS_REQUIRED(!newTaskMutex); /** * Services the queue 'forever'. Should be run in a thread. */ - void serviceQueue(); + void serviceQueue() EXCLUSIVE_LOCKS_REQUIRED(!newTaskMutex); /** Tell any threads running serviceQueue to stop as soon as the current task is done */ - void stop() + void stop() EXCLUSIVE_LOCKS_REQUIRED(!newTaskMutex) { WITH_LOCK(newTaskMutex, stopRequested = true); newTaskScheduled.notify_all(); if (m_service_thread.joinable()) m_service_thread.join(); } /** Tell any threads running serviceQueue to stop when there is no work left to be done */ - void StopWhenDrained() + void StopWhenDrained() EXCLUSIVE_LOCKS_REQUIRED(!newTaskMutex) { WITH_LOCK(newTaskMutex, stopWhenEmpty = true); newTaskScheduled.notify_all(); @@ -88,16 +93,17 @@ public: * Returns number of tasks waiting to be serviced, * and first and last task times */ - size_t getQueueInfo(std::chrono::system_clock::time_point& first, - std::chrono::system_clock::time_point& last) const; + size_t getQueueInfo(std::chrono::steady_clock::time_point& first, + std::chrono::steady_clock::time_point& last) const + EXCLUSIVE_LOCKS_REQUIRED(!newTaskMutex); /** Returns true if there are threads actively running in serviceQueue() */ - bool AreThreadsServicingQueue() const; + bool AreThreadsServicingQueue() const EXCLUSIVE_LOCKS_REQUIRED(!newTaskMutex); private: mutable Mutex newTaskMutex; std::condition_variable newTaskScheduled; - std::multimap<std::chrono::system_clock::time_point, Function> taskQueue GUARDED_BY(newTaskMutex); + std::multimap<std::chrono::steady_clock::time_point, Function> taskQueue GUARDED_BY(newTaskMutex); int nThreadsServicingQueue GUARDED_BY(newTaskMutex){0}; bool stopRequested GUARDED_BY(newTaskMutex){false}; bool stopWhenEmpty GUARDED_BY(newTaskMutex){false}; @@ -117,17 +123,17 @@ private: class SingleThreadedSchedulerClient { private: - CScheduler* m_pscheduler; + CScheduler& m_scheduler; Mutex m_callbacks_mutex; std::list<std::function<void()>> m_callbacks_pending GUARDED_BY(m_callbacks_mutex); bool m_are_callbacks_running GUARDED_BY(m_callbacks_mutex) = false; - void MaybeScheduleProcessQueue(); - void ProcessQueue(); + void MaybeScheduleProcessQueue() EXCLUSIVE_LOCKS_REQUIRED(!m_callbacks_mutex); + void ProcessQueue() EXCLUSIVE_LOCKS_REQUIRED(!m_callbacks_mutex); public: - explicit SingleThreadedSchedulerClient(CScheduler* pschedulerIn) : m_pscheduler(pschedulerIn) {} + explicit SingleThreadedSchedulerClient(CScheduler& scheduler LIFETIMEBOUND) : m_scheduler{scheduler} {} /** * Add a callback to be executed. Callbacks are executed serially @@ -135,15 +141,15 @@ public: * Practically, this means that callbacks can behave as if they are executed * in order by a single thread. */ - void AddToProcessQueue(std::function<void()> func); + void AddToProcessQueue(std::function<void()> func) EXCLUSIVE_LOCKS_REQUIRED(!m_callbacks_mutex); /** * Processes all remaining queue members on the calling thread, blocking until queue is empty * Must be called after the CScheduler has no remaining processing threads! */ - void EmptyQueue(); + void EmptyQueue() EXCLUSIVE_LOCKS_REQUIRED(!m_callbacks_mutex); - size_t CallbacksPending(); + size_t CallbacksPending() EXCLUSIVE_LOCKS_REQUIRED(!m_callbacks_mutex); }; #endif // BITCOIN_SCHEDULER_H diff --git a/src/script/descriptor.cpp b/src/script/descriptor.cpp index 798c4b3ea0..434e106dad 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. @@ -563,7 +577,7 @@ public: 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; @@ -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()); @@ -802,6 +816,30 @@ public: bool IsSingleType() const final { return true; } }; +/** A parsed (sorted)multi_a(...) descriptor. Always uses x-only pubkeys. */ +class MultiADescriptor final : public DescriptorImpl +{ + const int m_threshold; + const bool m_sorted; +protected: + std::string ToStringExtra() const override { return strprintf("%i", m_threshold); } + std::vector<CScript> MakeScripts(const std::vector<CPubKey>& keys, Span<const CScript>, FlatSigningProvider&) const override { + CScript ret; + std::vector<XOnlyPubKey> xkeys; + for (const auto& key : keys) xkeys.emplace_back(key); + if (m_sorted) std::sort(xkeys.begin(), xkeys.end()); + ret << ToByteVector(xkeys[0]) << OP_CHECKSIG; + for (size_t i = 1; i < keys.size(); ++i) { + ret << ToByteVector(xkeys[i]) << OP_CHECKSIGADD; + } + ret << m_threshold << OP_NUMEQUAL; + return Vector(std::move(ret)); + } +public: + MultiADescriptor(int threshold, std::vector<std::unique_ptr<PubkeyProvider>> providers, bool sorted = false) : DescriptorImpl(std::move(providers), sorted ? "sortedmulti_a" : "multi_a"), m_threshold(threshold), m_sorted(sorted) {} + bool IsSingleType() const final { return true; } +}; + /** A parsed sh(...) descriptor. */ class SHDescriptor final : public DescriptorImpl { @@ -858,7 +896,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)); } @@ -893,6 +931,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 // //////////////////////////////////////////////////////////////////////////// @@ -1034,22 +1173,115 @@ 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) { using namespace spanparsing; auto expr = Expr(sp); - bool sorted_multi = false; if (Func("pk", expr)) { auto pubkey = ParsePubkey(key_exp_index, expr, ctx, out, error); - if (!pubkey) return nullptr; + if (!pubkey) { + error = strprintf("pk(): %s", error); + return nullptr; + } ++key_exp_index; return std::make_unique<PKDescriptor>(std::move(pubkey), ctx == ParseScriptContext::P2TR); } if ((ctx == ParseScriptContext::TOP || ctx == ParseScriptContext::P2SH || ctx == ParseScriptContext::P2WSH) && Func("pkh", expr)) { auto pubkey = ParsePubkey(key_exp_index, expr, ctx, out, error); - if (!pubkey) return nullptr; + if (!pubkey) { + error = strprintf("pkh(): %s", error); + return nullptr; + } ++key_exp_index; return std::make_unique<PKHDescriptor>(std::move(pubkey)); } else if (Func("pkh", expr)) { @@ -1058,14 +1290,22 @@ std::unique_ptr<DescriptorImpl> ParseScript(uint32_t& key_exp_index, Span<const } if (ctx == ParseScriptContext::TOP && Func("combo", expr)) { auto pubkey = ParsePubkey(key_exp_index, expr, ctx, out, error); - if (!pubkey) return nullptr; + if (!pubkey) { + error = strprintf("combo(): %s", error); + return nullptr; + } ++key_exp_index; return std::make_unique<ComboDescriptor>(std::move(pubkey)); } else if (Func("combo", expr)) { error = "Can only have combo() at top level"; return nullptr; } - if ((ctx == ParseScriptContext::TOP || ctx == ParseScriptContext::P2SH || ctx == ParseScriptContext::P2WSH) && ((sorted_multi = Func("sortedmulti", expr)) || Func("multi", expr))) { + const bool multi = Func("multi", expr); + const bool sortedmulti = !multi && Func("sortedmulti", expr); + const bool multi_a = !(multi || sortedmulti) && Func("multi_a", expr); + const bool sortedmulti_a = !(multi || sortedmulti || multi_a) && Func("sortedmulti_a", expr); + if (((ctx == ParseScriptContext::TOP || ctx == ParseScriptContext::P2SH || ctx == ParseScriptContext::P2WSH) && (multi || sortedmulti)) || + (ctx == ParseScriptContext::P2TR && (multi_a || sortedmulti_a))) { auto threshold = Expr(expr); uint32_t thres; std::vector<std::unique_ptr<PubkeyProvider>> providers; @@ -1081,14 +1321,20 @@ std::unique_ptr<DescriptorImpl> ParseScript(uint32_t& key_exp_index, Span<const } auto arg = Expr(expr); auto pk = ParsePubkey(key_exp_index, arg, ctx, out, error); - if (!pk) return nullptr; + if (!pk) { + error = strprintf("Multi: %s", error); + return nullptr; + } script_size += pk->GetSize() + 1; providers.emplace_back(std::move(pk)); key_exp_index++; } - if (providers.empty() || providers.size() > MAX_PUBKEYS_PER_MULTISIG) { + if ((multi || sortedmulti) && (providers.empty() || providers.size() > MAX_PUBKEYS_PER_MULTISIG)) { error = strprintf("Cannot have %u keys in multisig; must have between 1 and %d keys, inclusive", providers.size(), MAX_PUBKEYS_PER_MULTISIG); return nullptr; + } else if ((multi_a || sortedmulti_a) && (providers.empty() || providers.size() > MAX_PUBKEYS_PER_MULTI_A)) { + error = strprintf("Cannot have %u keys in multi_a; must have between 1 and %d keys, inclusive", providers.size(), MAX_PUBKEYS_PER_MULTI_A); + return nullptr; } else if (thres < 1) { error = strprintf("Multisig threshold cannot be %d, must be at least 1", thres); return nullptr; @@ -1109,14 +1355,24 @@ std::unique_ptr<DescriptorImpl> ParseScript(uint32_t& key_exp_index, Span<const return nullptr; } } - return std::make_unique<MultisigDescriptor>(thres, std::move(providers), sorted_multi); - } else if (Func("sortedmulti", expr) || Func("multi", expr)) { + if (multi || sortedmulti) { + return std::make_unique<MultisigDescriptor>(thres, std::move(providers), sortedmulti); + } else { + return std::make_unique<MultiADescriptor>(thres, std::move(providers), sortedmulti_a); + } + } else if (multi || sortedmulti) { error = "Can only have multi/sortedmulti at top level, in sh(), or in wsh()"; return nullptr; + } else if (multi_a || sortedmulti_a) { + error = "Can only have multi_a/sortedmulti_a inside tr()"; + return nullptr; } if ((ctx == ParseScriptContext::TOP || ctx == ParseScriptContext::P2SH) && Func("wpkh", expr)) { auto pubkey = ParsePubkey(key_exp_index, expr, ParseScriptContext::P2WPKH, out, error); - if (!pubkey) return nullptr; + if (!pubkey) { + error = strprintf("wpkh(): %s", error); + return nullptr; + } key_exp_index++; return std::make_unique<WPKHDescriptor>(std::move(pubkey)); } else if (Func("wpkh", expr)) { @@ -1153,7 +1409,10 @@ std::unique_ptr<DescriptorImpl> ParseScript(uint32_t& key_exp_index, Span<const if (ctx == ParseScriptContext::TOP && Func("tr", expr)) { auto arg = Expr(expr); auto internal_key = ParsePubkey(key_exp_index, arg, ParseScriptContext::P2TR, out, error); - if (!internal_key) return nullptr; + if (!internal_key) { + error = strprintf("tr(): %s", error); + return nullptr; + } ++key_exp_index; std::vector<std::unique_ptr<DescriptorImpl>> subscripts; //!< list of script subexpressions std::vector<int> depths; //!< depth in the tree of each subexpression (same length subscripts) @@ -1211,6 +1470,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)) { @@ -1223,6 +1496,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; @@ -1234,27 +1546,19 @@ 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<DescriptorImpl> InferMultiA(const CScript& script, ParseScriptContext ctx, 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; + auto match = MatchMultiA(script); + if (!match) return {}; + std::vector<std::unique_ptr<PubkeyProvider>> keys; + keys.reserve(match->second.size()); + for (const auto keyspan : match->second) { + if (keyspan.size() != 32) return {}; + auto key = InferXOnlyPubkey(XOnlyPubKey{keyspan}, ctx, provider); + if (!key) return {}; + keys.push_back(std::move(key)); + } + return std::make_unique<MultiADescriptor>(match->first, std::move(keys)); } std::unique_ptr<DescriptorImpl> InferScript(const CScript& script, ParseScriptContext ctx, const SigningProvider& provider) @@ -1264,6 +1568,11 @@ std::unique_ptr<DescriptorImpl> InferScript(const CScript& script, ParseScriptCo return std::make_unique<PKDescriptor>(InferXOnlyPubkey(key, ctx, provider), true); } + if (ctx == ParseScriptContext::P2TR) { + auto ret = InferMultiA(script, ctx, provider); + if (ret) return ret; + } + std::vector<std::vector<unsigned char>> data; TxoutType txntype = Solver(script, data); @@ -1348,6 +1657,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 11b1a1c887..38bb11aad4 100644 --- a/src/script/interpreter.cpp +++ b/src/script/interpreter.cpp @@ -225,31 +225,6 @@ bool static CheckPubKeyEncoding(const valtype &vchPubKey, unsigned int flags, co return true; } -bool CheckMinimalPush(const valtype& data, opcodetype opcode) { - // Excludes OP_1NEGATE, OP_1-16 since they are by definition minimal - assert(0 <= opcode && opcode <= OP_PUSHDATA4); - if (data.size() == 0) { - // Should have used OP_0. - return opcode == OP_0; - } else if (data.size() == 1 && data[0] >= 1 && data[0] <= 16) { - // Should have used OP_1 .. OP_16. - return false; - } else if (data.size() == 1 && data[0] == 0x81) { - // Should have used OP_1NEGATE. - return false; - } else if (data.size() <= 75) { - // Must have used a direct push (opcode indicating number of bytes pushed + those bytes). - return opcode == data.size(); - } else if (data.size() <= 255) { - // Must have used OP_PUSHDATA. - return opcode == OP_PUSHDATA1; - } else if (data.size() <= 65535) { - // Must have used OP_PUSHDATA2. - return opcode == OP_PUSHDATA2; - } - return true; -} - int FindAndDelete(CScript& script, const CScript& b) { int nFound = 0; @@ -1367,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; } @@ -1378,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; } @@ -1389,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; } @@ -1399,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; } @@ -1409,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; } @@ -1483,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) { @@ -1524,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; @@ -1569,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(); } @@ -1612,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) @@ -1652,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(); } @@ -1852,15 +1827,19 @@ 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) { + assert(control.size() >= TAPROOT_CONTROL_BASE_SIZE); + assert(control.size() <= TAPROOT_CONTROL_MAX_SIZE); + assert((control.size() - TAPROOT_CONTROL_BASE_SIZE) % TAPROOT_CONTROL_NODE_SIZE == 0); + 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; @@ -1923,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; @@ -2009,7 +1988,7 @@ bool VerifyScript(const CScript& scriptSig, const CScript& scriptPubKey, const C // The scriptSig must be _exactly_ CScript(), otherwise we reintroduce malleability. return set_error(serror, SCRIPT_ERR_WITNESS_MALLEATED); } - if (!VerifyWitnessProgram(*witness, witnessversion, witnessprogram, flags, checker, serror, /* is_p2sh */ false)) { + if (!VerifyWitnessProgram(*witness, witnessversion, witnessprogram, flags, checker, serror, /*is_p2sh=*/false)) { return false; } // Bypass the cleanstack check at the end. The actual stack is obviously not clean @@ -2054,7 +2033,7 @@ bool VerifyScript(const CScript& scriptSig, const CScript& scriptPubKey, const C // reintroduce malleability. return set_error(serror, SCRIPT_ERR_WITNESS_MALLEATED_P2SH); } - if (!VerifyWitnessProgram(*witness, witnessversion, witnessprogram, flags, checker, serror, /* is_p2sh */ true)) { + if (!VerifyWitnessProgram(*witness, witnessversion, witnessprogram, flags, checker, serror, /*is_p2sh=*/true)) { return false; } // Bypass the cleanstack check at the end. The actual stack is obviously not clean diff --git a/src/script/interpreter.h b/src/script/interpreter.h index cf1953ad22..f91578d684 100644 --- a/src/script/interpreter.h +++ b/src/script/interpreter.h @@ -151,7 +151,7 @@ bool CheckSignatureEncoding(const std::vector<unsigned char> &vchSig, unsigned i struct PrecomputedTransactionData { // BIP341 precomputed data. - // These are single-SHA256, see https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki#cite_note-15. + // These are single-SHA256, see https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki#cite_note-16. uint256 m_prevouts_single_hash; uint256 m_sequences_single_hash; uint256 m_outputs_single_hash; @@ -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); @@ -344,8 +344,6 @@ bool VerifyScript(const CScript& scriptSig, const CScript& scriptPubKey, const C size_t CountWitnessSigOps(const CScript& scriptSig, const CScript& scriptPubKey, const CScriptWitness* witness, unsigned int flags); -bool CheckMinimalPush(const std::vector<unsigned char>& data, opcodetype opcode); - int FindAndDelete(CScript& script, const CScript& b); #endif // BITCOIN_SCRIPT_INTERPRETER_H diff --git a/src/script/miniscript.cpp b/src/script/miniscript.cpp new file mode 100644 index 0000000000..cb4d4cb783 --- /dev/null +++ b/src/script/miniscript.cpp @@ -0,0 +1,342 @@ +// 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 <string> +#include <vector> +#include <script/script.h> +#include <script/standard.h> +#include <script/miniscript.h> + +#include <assert.h> + +namespace miniscript { +namespace internal { + +Type SanitizeType(Type e) { + int num_types = (e << "K"_mst) + (e << "V"_mst) + (e << "B"_mst) + (e << "W"_mst); + if (num_types == 0) return ""_mst; // No valid type, don't care about the rest + assert(num_types == 1); // K, V, B, W all conflict with each other + assert(!(e << "z"_mst) || !(e << "o"_mst)); // z conflicts with o + assert(!(e << "n"_mst) || !(e << "z"_mst)); // n conflicts with z + assert(!(e << "n"_mst) || !(e << "W"_mst)); // n conflicts with W + assert(!(e << "V"_mst) || !(e << "d"_mst)); // V conflicts with d + assert(!(e << "K"_mst) || (e << "u"_mst)); // K implies u + assert(!(e << "V"_mst) || !(e << "u"_mst)); // V conflicts with u + assert(!(e << "e"_mst) || !(e << "f"_mst)); // e conflicts with f + assert(!(e << "e"_mst) || (e << "d"_mst)); // e implies d + assert(!(e << "V"_mst) || !(e << "e"_mst)); // V conflicts with e + assert(!(e << "d"_mst) || !(e << "f"_mst)); // d conflicts with f + assert(!(e << "V"_mst) || (e << "f"_mst)); // V implies f + assert(!(e << "K"_mst) || (e << "s"_mst)); // K implies s + assert(!(e << "z"_mst) || (e << "m"_mst)); // z implies m + return e; +} + +Type ComputeType(Fragment fragment, Type x, Type y, Type z, const std::vector<Type>& sub_types, uint32_t k, size_t data_size, size_t n_subs, size_t n_keys) { + // Sanity check on data + if (fragment == Fragment::SHA256 || fragment == Fragment::HASH256) { + assert(data_size == 32); + } else if (fragment == Fragment::RIPEMD160 || fragment == Fragment::HASH160) { + assert(data_size == 20); + } else { + assert(data_size == 0); + } + // Sanity check on k + if (fragment == Fragment::OLDER || fragment == Fragment::AFTER) { + assert(k >= 1 && k < 0x80000000UL); + } else if (fragment == Fragment::MULTI) { + assert(k >= 1 && k <= n_keys); + } else if (fragment == Fragment::THRESH) { + assert(k >= 1 && k <= n_subs); + } else { + assert(k == 0); + } + // Sanity check on subs + if (fragment == Fragment::AND_V || fragment == Fragment::AND_B || fragment == Fragment::OR_B || + fragment == Fragment::OR_C || fragment == Fragment::OR_I || fragment == Fragment::OR_D) { + assert(n_subs == 2); + } else if (fragment == Fragment::ANDOR) { + assert(n_subs == 3); + } else if (fragment == Fragment::WRAP_A || fragment == Fragment::WRAP_S || fragment == Fragment::WRAP_C || + fragment == Fragment::WRAP_D || fragment == Fragment::WRAP_V || fragment == Fragment::WRAP_J || + fragment == Fragment::WRAP_N) { + assert(n_subs == 1); + } else if (fragment != Fragment::THRESH) { + assert(n_subs == 0); + } + // Sanity check on keys + if (fragment == Fragment::PK_K || fragment == Fragment::PK_H) { + assert(n_keys == 1); + } else if (fragment == Fragment::MULTI) { + assert(n_keys >= 1 && n_keys <= 20); + } else { + assert(n_keys == 0); + } + + // Below is the per-fragment logic for computing the expression types. + // It heavily relies on Type's << operator (where "X << a_mst" means + // "X has all properties listed in a"). + switch (fragment) { + case Fragment::PK_K: return "Konudemsxk"_mst; + case Fragment::PK_H: return "Knudemsxk"_mst; + case Fragment::OLDER: return + "g"_mst.If(k & CTxIn::SEQUENCE_LOCKTIME_TYPE_FLAG) | + "h"_mst.If(!(k & CTxIn::SEQUENCE_LOCKTIME_TYPE_FLAG)) | + "Bzfmxk"_mst; + case Fragment::AFTER: return + "i"_mst.If(k >= LOCKTIME_THRESHOLD) | + "j"_mst.If(k < LOCKTIME_THRESHOLD) | + "Bzfmxk"_mst; + case Fragment::SHA256: return "Bonudmk"_mst; + case Fragment::RIPEMD160: return "Bonudmk"_mst; + case Fragment::HASH256: return "Bonudmk"_mst; + case Fragment::HASH160: return "Bonudmk"_mst; + case Fragment::JUST_1: return "Bzufmxk"_mst; + case Fragment::JUST_0: return "Bzudemsxk"_mst; + case Fragment::WRAP_A: return + "W"_mst.If(x << "B"_mst) | // W=B_x + (x & "ghijk"_mst) | // g=g_x, h=h_x, i=i_x, j=j_x, k=k_x + (x & "udfems"_mst) | // u=u_x, d=d_x, f=f_x, e=e_x, m=m_x, s=s_x + "x"_mst; // x + case Fragment::WRAP_S: return + "W"_mst.If(x << "Bo"_mst) | // W=B_x*o_x + (x & "ghijk"_mst) | // g=g_x, h=h_x, i=i_x, j=j_x, k=k_x + (x & "udfemsx"_mst); // u=u_x, d=d_x, f=f_x, e=e_x, m=m_x, s=s_x, x=x_x + case Fragment::WRAP_C: return + "B"_mst.If(x << "K"_mst) | // B=K_x + (x & "ghijk"_mst) | // g=g_x, h=h_x, i=i_x, j=j_x, k=k_x + (x & "ondfem"_mst) | // o=o_x, n=n_x, d=d_x, f=f_x, e=e_x, m=m_x + "us"_mst; // u, s + case Fragment::WRAP_D: return + "B"_mst.If(x << "Vz"_mst) | // B=V_x*z_x + "o"_mst.If(x << "z"_mst) | // o=z_x + "e"_mst.If(x << "f"_mst) | // e=f_x + (x & "ghijk"_mst) | // g=g_x, h=h_x, i=i_x, j=j_x, k=k_x + (x & "ms"_mst) | // m=m_x, s=s_x + // NOTE: 'd:' is not 'u' under P2WSH as MINIMALIF is only a policy rule there. + "ndx"_mst; // n, d, x + case Fragment::WRAP_V: return + "V"_mst.If(x << "B"_mst) | // V=B_x + (x & "ghijk"_mst) | // g=g_x, h=h_x, i=i_x, j=j_x, k=k_x + (x & "zonms"_mst) | // z=z_x, o=o_x, n=n_x, m=m_x, s=s_x + "fx"_mst; // f, x + case Fragment::WRAP_J: return + "B"_mst.If(x << "Bn"_mst) | // B=B_x*n_x + "e"_mst.If(x << "f"_mst) | // e=f_x + (x & "ghijk"_mst) | // g=g_x, h=h_x, i=i_x, j=j_x, k=k_x + (x & "oums"_mst) | // o=o_x, u=u_x, m=m_x, s=s_x + "ndx"_mst; // n, d, x + case Fragment::WRAP_N: return + (x & "ghijk"_mst) | // g=g_x, h=h_x, i=i_x, j=j_x, k=k_x + (x & "Bzondfems"_mst) | // B=B_x, z=z_x, o=o_x, n=n_x, d=d_x, f=f_x, e=e_x, m=m_x, s=s_x + "ux"_mst; // u, x + case Fragment::AND_V: return + (y & "KVB"_mst).If(x << "V"_mst) | // B=V_x*B_y, V=V_x*V_y, K=V_x*K_y + (x & "n"_mst) | (y & "n"_mst).If(x << "z"_mst) | // n=n_x+z_x*n_y + ((x | y) & "o"_mst).If((x | y) << "z"_mst) | // o=o_x*z_y+z_x*o_y + (x & y & "dmz"_mst) | // d=d_x*d_y, m=m_x*m_y, z=z_x*z_y + ((x | y) & "s"_mst) | // s=s_x+s_y + "f"_mst.If((y << "f"_mst) || (x << "s"_mst)) | // f=f_y+s_x + (y & "ux"_mst) | // u=u_y, x=x_y + ((x | y) & "ghij"_mst) | // g=g_x+g_y, h=h_x+h_y, i=i_x+i_y, j=j_x+j_y + "k"_mst.If(((x & y) << "k"_mst) && + !(((x << "g"_mst) && (y << "h"_mst)) || + ((x << "h"_mst) && (y << "g"_mst)) || + ((x << "i"_mst) && (y << "j"_mst)) || + ((x << "j"_mst) && (y << "i"_mst)))); // k=k_x*k_y*!(g_x*h_y + h_x*g_y + i_x*j_y + j_x*i_y) + case Fragment::AND_B: return + (x & "B"_mst).If(y << "W"_mst) | // B=B_x*W_y + ((x | y) & "o"_mst).If((x | y) << "z"_mst) | // o=o_x*z_y+z_x*o_y + (x & "n"_mst) | (y & "n"_mst).If(x << "z"_mst) | // n=n_x+z_x*n_y + (x & y & "e"_mst).If((x & y) << "s"_mst) | // e=e_x*e_y*s_x*s_y + (x & y & "dzm"_mst) | // d=d_x*d_y, z=z_x*z_y, m=m_x*m_y + "f"_mst.If(((x & y) << "f"_mst) || (x << "sf"_mst) || (y << "sf"_mst)) | // f=f_x*f_y + f_x*s_x + f_y*s_y + ((x | y) & "s"_mst) | // s=s_x+s_y + "ux"_mst | // u, x + ((x | y) & "ghij"_mst) | // g=g_x+g_y, h=h_x+h_y, i=i_x+i_y, j=j_x+j_y + "k"_mst.If(((x & y) << "k"_mst) && + !(((x << "g"_mst) && (y << "h"_mst)) || + ((x << "h"_mst) && (y << "g"_mst)) || + ((x << "i"_mst) && (y << "j"_mst)) || + ((x << "j"_mst) && (y << "i"_mst)))); // k=k_x*k_y*!(g_x*h_y + h_x*g_y + i_x*j_y + j_x*i_y) + case Fragment::OR_B: return + "B"_mst.If(x << "Bd"_mst && y << "Wd"_mst) | // B=B_x*d_x*W_x*d_y + ((x | y) & "o"_mst).If((x | y) << "z"_mst) | // o=o_x*z_y+z_x*o_y + (x & y & "m"_mst).If((x | y) << "s"_mst && (x & y) << "e"_mst) | // m=m_x*m_y*e_x*e_y*(s_x+s_y) + (x & y & "zse"_mst) | // z=z_x*z_y, s=s_x*s_y, e=e_x*e_y + "dux"_mst | // d, u, x + ((x | y) & "ghij"_mst) | // g=g_x+g_y, h=h_x+h_y, i=i_x+i_y, j=j_x+j_y + (x & y & "k"_mst); // k=k_x*k_y + case Fragment::OR_D: return + (y & "B"_mst).If(x << "Bdu"_mst) | // B=B_y*B_x*d_x*u_x + (x & "o"_mst).If(y << "z"_mst) | // o=o_x*z_y + (x & y & "m"_mst).If(x << "e"_mst && (x | y) << "s"_mst) | // m=m_x*m_y*e_x*(s_x+s_y) + (x & y & "zes"_mst) | // z=z_x*z_y, e=e_x*e_y, s=s_x*s_y + (y & "ufd"_mst) | // u=u_y, f=f_y, d=d_y + "x"_mst | // x + ((x | y) & "ghij"_mst) | // g=g_x+g_y, h=h_x+h_y, i=i_x+i_y, j=j_x+j_y + (x & y & "k"_mst); // k=k_x*k_y + case Fragment::OR_C: return + (y & "V"_mst).If(x << "Bdu"_mst) | // V=V_y*B_x*u_x*d_x + (x & "o"_mst).If(y << "z"_mst) | // o=o_x*z_y + (x & y & "m"_mst).If(x << "e"_mst && (x | y) << "s"_mst) | // m=m_x*m_y*e_x*(s_x+s_y) + (x & y & "zs"_mst) | // z=z_x*z_y, s=s_x*s_y + "fx"_mst | // f, x + ((x | y) & "ghij"_mst) | // g=g_x+g_y, h=h_x+h_y, i=i_x+i_y, j=j_x+j_y + (x & y & "k"_mst); // k=k_x*k_y + case Fragment::OR_I: return + (x & y & "VBKufs"_mst) | // V=V_x*V_y, B=B_x*B_y, K=K_x*K_y, u=u_x*u_y, f=f_x*f_y, s=s_x*s_y + "o"_mst.If((x & y) << "z"_mst) | // o=z_x*z_y + ((x | y) & "e"_mst).If((x | y) << "f"_mst) | // e=e_x*f_y+f_x*e_y + (x & y & "m"_mst).If((x | y) << "s"_mst) | // m=m_x*m_y*(s_x+s_y) + ((x | y) & "d"_mst) | // d=d_x+d_y + "x"_mst | // x + ((x | y) & "ghij"_mst) | // g=g_x+g_y, h=h_x+h_y, i=i_x+i_y, j=j_x+j_y + (x & y & "k"_mst); // k=k_x*k_y + case Fragment::ANDOR: return + (y & z & "BKV"_mst).If(x << "Bdu"_mst) | // B=B_x*d_x*u_x*B_y*B_z, K=B_x*d_x*u_x*K_y*K_z, V=B_x*d_x*u_x*V_y*V_z + (x & y & z & "z"_mst) | // z=z_x*z_y*z_z + ((x | (y & z)) & "o"_mst).If((x | (y & z)) << "z"_mst) | // o=o_x*z_y*z_z+z_x*o_y*o_z + (y & z & "u"_mst) | // u=u_y*u_z + (z & "f"_mst).If((x << "s"_mst) || (y << "f"_mst)) | // f=(s_x+f_y)*f_z + (z & "d"_mst) | // d=d_z + (x & z & "e"_mst).If(x << "s"_mst || y << "f"_mst) | // e=e_x*e_z*(s_x+f_y) + (x & y & z & "m"_mst).If(x << "e"_mst && (x | y | z) << "s"_mst) | // m=m_x*m_y*m_z*e_x*(s_x+s_y+s_z) + (z & (x | y) & "s"_mst) | // s=s_z*(s_x+s_y) + "x"_mst | // x + ((x | y | z) & "ghij"_mst) | // g=g_x+g_y+g_z, h=h_x+h_y+h_z, i=i_x+i_y+i_z, j=j_x+j_y_j_z + "k"_mst.If(((x & y & z) << "k"_mst) && + !(((x << "g"_mst) && (y << "h"_mst)) || + ((x << "h"_mst) && (y << "g"_mst)) || + ((x << "i"_mst) && (y << "j"_mst)) || + ((x << "j"_mst) && (y << "i"_mst)))); // k=k_x*k_y*k_z* !(g_x*h_y + h_x*g_y + i_x*j_y + j_x*i_y) + case Fragment::MULTI: return "Bnudemsk"_mst; + case Fragment::THRESH: { + bool all_e = true; + bool all_m = true; + uint32_t args = 0; + uint32_t num_s = 0; + Type acc_tl = "k"_mst; + for (size_t i = 0; i < sub_types.size(); ++i) { + Type t = sub_types[i]; + if (!(t << (i ? "Wdu"_mst : "Bdu"_mst))) return ""_mst; // Require Bdu, Wdu, Wdu, ... + if (!(t << "e"_mst)) all_e = false; + if (!(t << "m"_mst)) all_m = false; + if (t << "s"_mst) num_s += 1; + args += (t << "z"_mst) ? 0 : (t << "o"_mst) ? 1 : 2; + acc_tl = ((acc_tl | t) & "ghij"_mst) | + // Thresh contains a combination of timelocks if it has threshold > 1 and + // it contains two different children that have different types of timelocks + // Note how if any of the children don't have "k", the parent also does not have "k" + "k"_mst.If(((acc_tl & t) << "k"_mst) && ((k <= 1) || + ((k > 1) && !(((acc_tl << "g"_mst) && (t << "h"_mst)) || + ((acc_tl << "h"_mst) && (t << "g"_mst)) || + ((acc_tl << "i"_mst) && (t << "j"_mst)) || + ((acc_tl << "j"_mst) && (t << "i"_mst)))))); + } + return "Bdu"_mst | + "z"_mst.If(args == 0) | // z=all z + "o"_mst.If(args == 1) | // o=all z except one o + "e"_mst.If(all_e && num_s == n_subs) | // e=all e and all s + "m"_mst.If(all_e && all_m && num_s >= n_subs - k) | // m=all e, >=(n-k) s + "s"_mst.If(num_s >= n_subs - k + 1) | // s= >=(n-k+1) s + acc_tl; // timelock info + } + } + assert(false); +} + +size_t ComputeScriptLen(Fragment fragment, Type sub0typ, size_t subsize, uint32_t k, size_t n_subs, size_t n_keys) { + switch (fragment) { + case Fragment::JUST_1: + case Fragment::JUST_0: return 1; + case Fragment::PK_K: return 34; + case Fragment::PK_H: return 3 + 21; + case Fragment::OLDER: + case Fragment::AFTER: return 1 + BuildScript(k).size(); + case Fragment::HASH256: + case Fragment::SHA256: return 4 + 2 + 33; + case Fragment::HASH160: + case Fragment::RIPEMD160: return 4 + 2 + 21; + case Fragment::MULTI: return 1 + BuildScript(n_keys).size() + BuildScript(k).size() + 34 * n_keys; + case Fragment::AND_V: return subsize; + case Fragment::WRAP_V: return subsize + (sub0typ << "x"_mst); + case Fragment::WRAP_S: + case Fragment::WRAP_C: + case Fragment::WRAP_N: + case Fragment::AND_B: + case Fragment::OR_B: return subsize + 1; + case Fragment::WRAP_A: + case Fragment::OR_C: return subsize + 2; + case Fragment::WRAP_D: + case Fragment::OR_D: + case Fragment::OR_I: + case Fragment::ANDOR: return subsize + 3; + case Fragment::WRAP_J: return subsize + 4; + case Fragment::THRESH: return subsize + n_subs + BuildScript(k).size(); + } + assert(false); +} + +std::optional<std::vector<Opcode>> DecomposeScript(const CScript& script) +{ + std::vector<Opcode> out; + CScript::const_iterator it = script.begin(), itend = script.end(); + while (it != itend) { + std::vector<unsigned char> push_data; + opcodetype opcode; + if (!script.GetOp(it, opcode, push_data)) { + return {}; + } else if (opcode >= OP_1 && opcode <= OP_16) { + // Deal with OP_n (GetOp does not turn them into pushes). + push_data.assign(1, CScript::DecodeOP_N(opcode)); + } else if (opcode == OP_CHECKSIGVERIFY) { + // Decompose OP_CHECKSIGVERIFY into OP_CHECKSIG OP_VERIFY + out.emplace_back(OP_CHECKSIG, std::vector<unsigned char>()); + opcode = OP_VERIFY; + } else if (opcode == OP_CHECKMULTISIGVERIFY) { + // Decompose OP_CHECKMULTISIGVERIFY into OP_CHECKMULTISIG OP_VERIFY + out.emplace_back(OP_CHECKMULTISIG, std::vector<unsigned char>()); + opcode = OP_VERIFY; + } else if (opcode == OP_EQUALVERIFY) { + // Decompose OP_EQUALVERIFY into OP_EQUAL OP_VERIFY + out.emplace_back(OP_EQUAL, std::vector<unsigned char>()); + opcode = OP_VERIFY; + } else if (IsPushdataOp(opcode)) { + if (!CheckMinimalPush(push_data, opcode)) return {}; + } else if (it != itend && (opcode == OP_CHECKSIG || opcode == OP_CHECKMULTISIG || opcode == OP_EQUAL) && (*it == OP_VERIFY)) { + // Rule out non minimal VERIFY sequences + return {}; + } + out.emplace_back(opcode, std::move(push_data)); + } + std::reverse(out.begin(), out.end()); + return out; +} + +std::optional<int64_t> ParseScriptNumber(const Opcode& in) { + if (in.first == OP_0) { + return 0; + } + if (!in.second.empty()) { + if (IsPushdataOp(in.first) && !CheckMinimalPush(in.second, in.first)) return {}; + try { + return CScriptNum(in.second, true).GetInt64(); + } catch(const scriptnum_error&) {} + } + return {}; +} + +int FindNextChar(Span<const char> sp, const char m) +{ + for (int i = 0; i < (int)sp.size(); ++i) { + if (sp[i] == m) return i; + // We only search within the current parentheses + if (sp[i] == ')') break; + } + return -1; +} + +} // namespace internal +} // namespace miniscript diff --git a/src/script/miniscript.h b/src/script/miniscript.h new file mode 100644 index 0000000000..f783d1dafc --- /dev/null +++ b/src/script/miniscript.h @@ -0,0 +1,1747 @@ +// 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. + +#ifndef BITCOIN_SCRIPT_MINISCRIPT_H +#define BITCOIN_SCRIPT_MINISCRIPT_H + +#include <algorithm> +#include <functional> +#include <numeric> +#include <memory> +#include <optional> +#include <string> +#include <vector> + +#include <stdlib.h> +#include <assert.h> + +#include <policy/policy.h> +#include <primitives/transaction.h> +#include <script/script.h> +#include <span.h> +#include <util/spanparsing.h> +#include <util/strencodings.h> +#include <util/string.h> +#include <util/vector.h> + +namespace miniscript { + +/** This type encapsulates the miniscript type system properties. + * + * Every miniscript expression is one of 4 basic types, and additionally has + * a number of boolean type properties. + * + * The basic types are: + * - "B" Base: + * - Takes its inputs from the top of the stack. + * - When satisfied, pushes a nonzero value of up to 4 bytes onto the stack. + * - When dissatisfied, pushes a 0 onto the stack. + * - This is used for most expressions, and required for the top level one. + * - For example: older(n) = <n> OP_CHECKSEQUENCEVERIFY. + * - "V" Verify: + * - Takes its inputs from the top of the stack. + * - When satisfied, pushes nothing. + * - Cannot be dissatisfied. + * - This can be obtained by adding an OP_VERIFY to a B, modifying the last opcode + * of a B to its -VERIFY version (only for OP_CHECKSIG, OP_CHECKSIGVERIFY + * and OP_EQUAL), or by combining a V fragment under some conditions. + * - For example vc:pk_k(key) = <key> OP_CHECKSIGVERIFY + * - "K" Key: + * - Takes its inputs from the top of the stack. + * - Becomes a B when followed by OP_CHECKSIG. + * - Always pushes a public key onto the stack, for which a signature is to be + * provided to satisfy the expression. + * - For example pk_h(key) = OP_DUP OP_HASH160 <Hash160(key)> OP_EQUALVERIFY + * - "W" Wrapped: + * - Takes its input from one below the top of the stack. + * - When satisfied, pushes a nonzero value (like B) on top of the stack, or one below. + * - When dissatisfied, pushes 0 op top of the stack or one below. + * - Is always "OP_SWAP [B]" or "OP_TOALTSTACK [B] OP_FROMALTSTACK". + * - For example sc:pk_k(key) = OP_SWAP <key> OP_CHECKSIG + * + * There a type properties that help reasoning about correctness: + * - "z" Zero-arg: + * - Is known to always consume exactly 0 stack elements. + * - For example after(n) = <n> OP_CHECKLOCKTIMEVERIFY + * - "o" One-arg: + * - Is known to always consume exactly 1 stack element. + * - Conflicts with property 'z' + * - For example sha256(hash) = OP_SIZE 32 OP_EQUALVERIFY OP_SHA256 <hash> OP_EQUAL + * - "n" Nonzero: + * - For every way this expression can be satisfied, a satisfaction exists that never needs + * a zero top stack element. + * - Conflicts with property 'z' and with type 'W'. + * - "d" Dissatisfiable: + * - There is an easy way to construct a dissatisfaction for this expression. + * - Conflicts with type 'V'. + * - "u" Unit: + * - In case of satisfaction, an exact 1 is put on the stack (rather than just nonzero). + * - Conflicts with type 'V'. + * + * Additional type properties help reasoning about nonmalleability: + * - "e" Expression: + * - This implies property 'd', but the dissatisfaction is nonmalleable. + * - This generally requires 'e' for all subexpressions which are invoked for that + * dissatifsaction, and property 'f' for the unexecuted subexpressions in that case. + * - Conflicts with type 'V'. + * - "f" Forced: + * - Dissatisfactions (if any) for this expression always involve at least one signature. + * - Is always true for type 'V'. + * - "s" Safe: + * - Satisfactions for this expression always involve at least one signature. + * - "m" Nonmalleable: + * - For every way this expression can be satisfied (which may be none), + * a nonmalleable satisfaction exists. + * - This generally requires 'm' for all subexpressions, and 'e' for all subexpressions + * which are dissatisfied when satisfying the parent. + * + * One type property is an implementation detail: + * - "x" Expensive verify: + * - Expressions with this property have a script whose last opcode is not EQUAL, CHECKSIG, or CHECKMULTISIG. + * - Not having this property means that it can be converted to a V at no cost (by switching to the + * -VERIFY version of the last opcode). + * + * Five more type properties for representing timelock information. Spend paths + * in miniscripts containing conflicting timelocks and heightlocks cannot be spent together. + * This helps users detect if miniscript does not match the semantic behaviour the + * user expects. + * - "g" Whether the branch contains a relative time timelock + * - "h" Whether the branch contains a relative height timelock + * - "i" Whether the branch contains an absolute time timelock + * - "j" Whether the branch contains an absolute height timelock + * - "k" + * - Whether all satisfactions of this expression don't contain a mix of heightlock and timelock + * of the same type. + * - If the miniscript does not have the "k" property, the miniscript template will not match + * the user expectation of the corresponding spending policy. + * For each of these properties the subset rule holds: an expression with properties X, Y, and Z, is also + * valid in places where an X, a Y, a Z, an XY, ... is expected. +*/ +class Type { + //! Internal bitmap of properties (see ""_mst operator for details). + uint32_t m_flags; + + //! Internal constructor used by the ""_mst operator. + explicit constexpr Type(uint32_t flags) : m_flags(flags) {} + +public: + //! The only way to publicly construct a Type is using this literal operator. + friend constexpr Type operator"" _mst(const char* c, size_t l); + + //! Compute the type with the union of properties. + constexpr Type operator|(Type x) const { return Type(m_flags | x.m_flags); } + + //! Compute the type with the intersection of properties. + constexpr Type operator&(Type x) const { return Type(m_flags & x.m_flags); } + + //! Check whether the left hand's properties are superset of the right's (= left is a subtype of right). + constexpr bool operator<<(Type x) const { return (x.m_flags & ~m_flags) == 0; } + + //! Comparison operator to enable use in sets/maps (total ordering incompatible with <<). + constexpr bool operator<(Type x) const { return m_flags < x.m_flags; } + + //! Equality operator. + constexpr bool operator==(Type x) const { return m_flags == x.m_flags; } + + //! The empty type if x is false, itself otherwise. + constexpr Type If(bool x) const { return Type(x ? m_flags : 0); } +}; + +//! Literal operator to construct Type objects. +inline constexpr Type operator"" _mst(const char* c, size_t l) { + Type typ{0}; + + for (const char *p = c; p < c + l; p++) { + typ = typ | Type( + *p == 'B' ? 1 << 0 : // Base type + *p == 'V' ? 1 << 1 : // Verify type + *p == 'K' ? 1 << 2 : // Key type + *p == 'W' ? 1 << 3 : // Wrapped type + *p == 'z' ? 1 << 4 : // Zero-arg property + *p == 'o' ? 1 << 5 : // One-arg property + *p == 'n' ? 1 << 6 : // Nonzero arg property + *p == 'd' ? 1 << 7 : // Dissatisfiable property + *p == 'u' ? 1 << 8 : // Unit property + *p == 'e' ? 1 << 9 : // Expression property + *p == 'f' ? 1 << 10 : // Forced property + *p == 's' ? 1 << 11 : // Safe property + *p == 'm' ? 1 << 12 : // Nonmalleable property + *p == 'x' ? 1 << 13 : // Expensive verify + *p == 'g' ? 1 << 14 : // older: contains relative time timelock (csv_time) + *p == 'h' ? 1 << 15 : // older: contains relative height timelock (csv_height) + *p == 'i' ? 1 << 16 : // after: contains time timelock (cltv_time) + *p == 'j' ? 1 << 17 : // after: contains height timelock (cltv_height) + *p == 'k' ? 1 << 18 : // does not contain a combination of height and time locks + (throw std::logic_error("Unknown character in _mst literal"), 0) + ); + } + + return typ; +} + +using Opcode = std::pair<opcodetype, std::vector<unsigned char>>; + +template<typename Key> struct Node; +template<typename Key> using NodeRef = std::shared_ptr<const Node<Key>>; + +//! Construct a miniscript node as a shared_ptr. +template<typename Key, typename... Args> +NodeRef<Key> MakeNodeRef(Args&&... args) { return std::make_shared<const Node<Key>>(std::forward<Args>(args)...); } + +//! The different node types in miniscript. +enum class Fragment { + JUST_0, //!< OP_0 + JUST_1, //!< OP_1 + PK_K, //!< [key] + PK_H, //!< OP_DUP OP_HASH160 [keyhash] OP_EQUALVERIFY + OLDER, //!< [n] OP_CHECKSEQUENCEVERIFY + AFTER, //!< [n] OP_CHECKLOCKTIMEVERIFY + SHA256, //!< OP_SIZE 32 OP_EQUALVERIFY OP_SHA256 [hash] OP_EQUAL + HASH256, //!< OP_SIZE 32 OP_EQUALVERIFY OP_HASH256 [hash] OP_EQUAL + RIPEMD160, //!< OP_SIZE 32 OP_EQUALVERIFY OP_RIPEMD160 [hash] OP_EQUAL + HASH160, //!< OP_SIZE 32 OP_EQUALVERIFY OP_HASH160 [hash] OP_EQUAL + WRAP_A, //!< OP_TOALTSTACK [X] OP_FROMALTSTACK + WRAP_S, //!< OP_SWAP [X] + WRAP_C, //!< [X] OP_CHECKSIG + WRAP_D, //!< OP_DUP OP_IF [X] OP_ENDIF + WRAP_V, //!< [X] OP_VERIFY (or -VERIFY version of last opcode in X) + WRAP_J, //!< OP_SIZE OP_0NOTEQUAL OP_IF [X] OP_ENDIF + WRAP_N, //!< [X] OP_0NOTEQUAL + AND_V, //!< [X] [Y] + AND_B, //!< [X] [Y] OP_BOOLAND + OR_B, //!< [X] [Y] OP_BOOLOR + OR_C, //!< [X] OP_NOTIF [Y] OP_ENDIF + OR_D, //!< [X] OP_IFDUP OP_NOTIF [Y] OP_ENDIF + OR_I, //!< OP_IF [X] OP_ELSE [Y] OP_ENDIF + ANDOR, //!< [X] OP_NOTIF [Z] OP_ELSE [Y] OP_ENDIF + THRESH, //!< [X1] ([Xn] OP_ADD)* [k] OP_EQUAL + MULTI, //!< [k] [key_n]* [n] OP_CHECKMULTISIG + // AND_N(X,Y) is represented as ANDOR(X,Y,0) + // WRAP_T(X) is represented as AND_V(X,1) + // WRAP_L(X) is represented as OR_I(0,X) + // WRAP_U(X) is represented as OR_I(X,0) +}; + + +namespace internal { + +//! Helper function for Node::CalcType. +Type ComputeType(Fragment fragment, Type x, Type y, Type z, const std::vector<Type>& sub_types, uint32_t k, size_t data_size, size_t n_subs, size_t n_keys); + +//! Helper function for Node::CalcScriptLen. +size_t ComputeScriptLen(Fragment fragment, Type sub0typ, size_t subsize, uint32_t k, size_t n_subs, size_t n_keys); + +//! A helper sanitizer/checker for the output of CalcType. +Type SanitizeType(Type x); + +//! Class whose objects represent the maximum of a list of integers. +template<typename I> +struct MaxInt { + const bool valid; + const I value; + + MaxInt() : valid(false), value(0) {} + MaxInt(I val) : valid(true), value(val) {} + + friend MaxInt<I> operator+(const MaxInt<I>& a, const MaxInt<I>& b) { + if (!a.valid || !b.valid) return {}; + return a.value + b.value; + } + + friend MaxInt<I> operator|(const MaxInt<I>& a, const MaxInt<I>& b) { + if (!a.valid) return b; + if (!b.valid) return a; + return std::max(a.value, b.value); + } +}; + +struct Ops { + //! Non-push opcodes. + uint32_t count; + //! Number of keys in possibly executed OP_CHECKMULTISIG(VERIFY)s to satisfy. + MaxInt<uint32_t> sat; + //! Number of keys in possibly executed OP_CHECKMULTISIG(VERIFY)s to dissatisfy. + MaxInt<uint32_t> dsat; + + Ops(uint32_t in_count, MaxInt<uint32_t> in_sat, MaxInt<uint32_t> in_dsat) : count(in_count), sat(in_sat), dsat(in_dsat) {}; +}; + +struct StackSize { + //! Maximum stack size to satisfy; + MaxInt<uint32_t> sat; + //! Maximum stack size to dissatisfy; + MaxInt<uint32_t> dsat; + + StackSize(MaxInt<uint32_t> in_sat, MaxInt<uint32_t> in_dsat) : sat(in_sat), dsat(in_dsat) {}; +}; + +} // namespace internal + +//! A node in a miniscript expression. +template<typename Key> +struct Node { + //! What node type this node is. + const Fragment fragment; + //! The k parameter (time for OLDER/AFTER, threshold for THRESH(_M)) + const uint32_t k = 0; + //! The keys used by this expression (only for PK_K/PK_H/MULTI) + const std::vector<Key> keys; + //! The data bytes in this expression (only for HASH160/HASH256/SHA256/RIPEMD10). + const std::vector<unsigned char> data; + //! Subexpressions (for WRAP_*/AND_*/OR_*/ANDOR/THRESH) + const std::vector<NodeRef<Key>> subs; + +private: + //! Cached ops counts. + const internal::Ops ops; + //! Cached stack size bounds. + const internal::StackSize ss; + //! Cached expression type (computed by CalcType and fed through SanitizeType). + 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; + + //! Compute the length of the script for this miniscript (including children). + size_t CalcScriptLen() const { + size_t subsize = 0; + for (const auto& sub : subs) { + subsize += sub->ScriptSize(); + } + Type sub0type = subs.size() > 0 ? subs[0]->GetType() : ""_mst; + return internal::ComputeScriptLen(fragment, sub0type, subsize, k, subs.size(), keys.size()); + } + + /* Apply a recursive algorithm to a Miniscript tree, without actual recursive calls. + * + * The algorithm is defined by two functions: downfn and upfn. Conceptually, the + * result can be thought of as first using downfn to compute a "state" for each node, + * from the root down to the leaves. Then upfn is used to compute a "result" for each + * node, from the leaves back up to the root, which is then returned. In the actual + * implementation, both functions are invoked in an interleaved fashion, performing a + * depth-first traversal of the tree. + * + * In more detail, it is invoked as node.TreeEvalMaybe<Result>(root, downfn, upfn): + * - root is the state of the root node, of type State. + * - downfn is a callable (State&, const Node&, size_t) -> State, which given a + * node, its state, and an index of one of its children, computes the state of that + * child. It can modify the state. Children of a given node will have downfn() + * called in order. + * - upfn is a callable (State&&, const Node&, Span<Result>) -> std::optional<Result>, + * which given a node, its state, and a Span of the results of its children, + * computes the result of the node. If std::nullopt is returned by upfn, + * TreeEvalMaybe() immediately returns std::nullopt. + * The return value of TreeEvalMaybe is the result of the root node. + * + * Result type cannot be bool due to the std::vector<bool> specialization. + */ + template<typename Result, typename State, typename DownFn, typename UpFn> + std::optional<Result> TreeEvalMaybe(State root_state, DownFn downfn, UpFn upfn) const + { + /** Entries of the explicit stack tracked in this algorithm. */ + struct StackElem + { + const Node& node; //!< The node being evaluated. + size_t expanded; //!< How many children of this node have been expanded. + State state; //!< The state for that node. + + StackElem(const Node& node_, size_t exp_, State&& state_) : + node(node_), expanded(exp_), state(std::move(state_)) {} + }; + /* Stack of tree nodes being explored. */ + std::vector<StackElem> stack; + /* Results of subtrees so far. Their order and mapping to tree nodes + * is implicitly defined by stack. */ + std::vector<Result> results; + stack.emplace_back(*this, 0, std::move(root_state)); + + /* Here is a demonstration of the algorithm, for an example tree A(B,C(D,E),F). + * State variables are omitted for simplicity. + * + * First: stack=[(A,0)] results=[] + * stack=[(A,1),(B,0)] results=[] + * stack=[(A,1)] results=[B] + * stack=[(A,2),(C,0)] results=[B] + * stack=[(A,2),(C,1),(D,0)] results=[B] + * stack=[(A,2),(C,1)] results=[B,D] + * stack=[(A,2),(C,2),(E,0)] results=[B,D] + * stack=[(A,2),(C,2)] results=[B,D,E] + * stack=[(A,2)] results=[B,C] + * stack=[(A,3),(F,0)] results=[B,C] + * stack=[(A,3)] results=[B,C,F] + * Final: stack=[] results=[A] + */ + while (stack.size()) { + const Node& node = stack.back().node; + if (stack.back().expanded < node.subs.size()) { + /* We encounter a tree node with at least one unexpanded child. + * Expand it. By the time we hit this node again, the result of + * that child (and all earlier children) will be at the end of `results`. */ + size_t child_index = stack.back().expanded++; + State child_state = downfn(stack.back().state, node, child_index); + stack.emplace_back(*node.subs[child_index], 0, std::move(child_state)); + continue; + } + // Invoke upfn with the last node.subs.size() elements of results as input. + assert(results.size() >= node.subs.size()); + std::optional<Result> result{upfn(std::move(stack.back().state), node, + Span<Result>{results}.last(node.subs.size()))}; + // If evaluation returns std::nullopt, abort immediately. + if (!result) return {}; + // Replace the last node.subs.size() elements of results with the new result. + results.erase(results.end() - node.subs.size(), results.end()); + results.push_back(std::move(*result)); + stack.pop_back(); + } + // The final remaining results element is the root result, return it. + assert(results.size() == 1); + return std::move(results[0]); + } + + /** Like TreeEvalMaybe, but without downfn or State type. + * upfn takes (const Node&, Span<Result>) and returns std::optional<Result>. */ + template<typename Result, typename UpFn> + std::optional<Result> TreeEvalMaybe(UpFn upfn) const + { + struct DummyState {}; + return TreeEvalMaybe<Result>(DummyState{}, + [](DummyState, const Node&, size_t) { return DummyState{}; }, + [&upfn](DummyState, const Node& node, Span<Result> subs) { + return upfn(node, subs); + } + ); + } + + /** Like TreeEvalMaybe, but always produces a result. upfn must return Result. */ + template<typename Result, typename State, typename DownFn, typename UpFn> + Result TreeEval(State root_state, DownFn&& downfn, UpFn upfn) const + { + // Invoke TreeEvalMaybe with upfn wrapped to return std::optional<Result>, and then + // unconditionally dereference the result (it cannot be std::nullopt). + return std::move(*TreeEvalMaybe<Result>(std::move(root_state), + std::forward<DownFn>(downfn), + [&upfn](State&& state, const Node& node, Span<Result> subs) { + Result res{upfn(std::move(state), node, subs)}; + return std::optional<Result>(std::move(res)); + } + )); + } + + /** 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) + { + std::vector<std::pair<const Node<Key>&, const Node<Key>&>> queue; + queue.emplace_back(node1, node2); + while (!queue.empty()) { + const auto& [a, b] = queue.back(); + queue.pop_back(); + if (std::tie(a.fragment, a.k, a.keys, a.data) < std::tie(b.fragment, b.k, b.keys, b.data)) return -1; + if (std::tie(b.fragment, b.k, b.keys, b.data) < std::tie(a.fragment, a.k, a.keys, a.data)) return 1; + if (a.subs.size() < b.subs.size()) return -1; + if (b.subs.size() < a.subs.size()) return 1; + size_t n = a.subs.size(); + for (size_t i = 0; i < n; ++i) { + queue.emplace_back(*a.subs[n - 1 - i], *b.subs[n - 1 - i]); + } + } + return 0; + } + + //! Compute the type for this miniscript. + Type CalcType() const { + using namespace internal; + + // THRESH has a variable number of subexpressions + std::vector<Type> sub_types; + if (fragment == Fragment::THRESH) { + for (const auto& sub : subs) sub_types.push_back(sub->GetType()); + } + // All other nodes than THRESH can be computed just from the types of the 0-3 subexpressions. + Type x = subs.size() > 0 ? subs[0]->GetType() : ""_mst; + Type y = subs.size() > 1 ? subs[1]->GetType() : ""_mst; + Type z = subs.size() > 2 ? subs[2]->GetType() : ""_mst; + + return SanitizeType(ComputeType(fragment, x, y, z, sub_types, k, data.size(), subs.size(), keys.size())); + } + +public: + template<typename Ctx> + CScript ToScript(const Ctx& ctx) const + { + // To construct the CScript for a Miniscript object, we use the TreeEval algorithm. + // The State is a boolean: whether or not the node's script expansion is followed + // by an OP_VERIFY (which may need to be combined with the last script opcode). + auto downfn = [](bool verify, const Node& node, size_t index) { + // For WRAP_V, the subexpression is certainly followed by OP_VERIFY. + if (node.fragment == Fragment::WRAP_V) return true; + // The subexpression of WRAP_S, and the last subexpression of AND_V + // inherit the followed-by-OP_VERIFY property from the parent. + if (node.fragment == Fragment::WRAP_S || + (node.fragment == Fragment::AND_V && index == 1)) return verify; + return false; + }; + // The upward function computes for a node, given its followed-by-OP_VERIFY status + // and the CScripts of its child nodes, the CScript of the node. + auto upfn = [&ctx](bool verify, const Node& node, Span<CScript> subs) -> CScript { + switch (node.fragment) { + case Fragment::PK_K: return BuildScript(ctx.ToPKBytes(node.keys[0])); + case Fragment::PK_H: return BuildScript(OP_DUP, OP_HASH160, ctx.ToPKHBytes(node.keys[0]), OP_EQUALVERIFY); + case Fragment::OLDER: return BuildScript(node.k, OP_CHECKSEQUENCEVERIFY); + case Fragment::AFTER: return BuildScript(node.k, OP_CHECKLOCKTIMEVERIFY); + case Fragment::SHA256: return BuildScript(OP_SIZE, 32, OP_EQUALVERIFY, OP_SHA256, node.data, verify ? OP_EQUALVERIFY : OP_EQUAL); + case Fragment::RIPEMD160: return BuildScript(OP_SIZE, 32, OP_EQUALVERIFY, OP_RIPEMD160, node.data, verify ? OP_EQUALVERIFY : OP_EQUAL); + case Fragment::HASH256: return BuildScript(OP_SIZE, 32, OP_EQUALVERIFY, OP_HASH256, node.data, verify ? OP_EQUALVERIFY : OP_EQUAL); + case Fragment::HASH160: return BuildScript(OP_SIZE, 32, OP_EQUALVERIFY, OP_HASH160, node.data, verify ? OP_EQUALVERIFY : OP_EQUAL); + case Fragment::WRAP_A: return BuildScript(OP_TOALTSTACK, subs[0], OP_FROMALTSTACK); + case Fragment::WRAP_S: return BuildScript(OP_SWAP, subs[0]); + case Fragment::WRAP_C: return BuildScript(std::move(subs[0]), verify ? OP_CHECKSIGVERIFY : OP_CHECKSIG); + case Fragment::WRAP_D: return BuildScript(OP_DUP, OP_IF, subs[0], OP_ENDIF); + case Fragment::WRAP_V: { + if (node.subs[0]->GetType() << "x"_mst) { + return BuildScript(std::move(subs[0]), OP_VERIFY); + } else { + return std::move(subs[0]); + } + } + case Fragment::WRAP_J: return BuildScript(OP_SIZE, OP_0NOTEQUAL, OP_IF, subs[0], OP_ENDIF); + case Fragment::WRAP_N: return BuildScript(std::move(subs[0]), OP_0NOTEQUAL); + case Fragment::JUST_1: return BuildScript(OP_1); + case Fragment::JUST_0: return BuildScript(OP_0); + case Fragment::AND_V: return BuildScript(std::move(subs[0]), subs[1]); + case Fragment::AND_B: return BuildScript(std::move(subs[0]), subs[1], OP_BOOLAND); + case Fragment::OR_B: return BuildScript(std::move(subs[0]), subs[1], OP_BOOLOR); + case Fragment::OR_D: return BuildScript(std::move(subs[0]), OP_IFDUP, OP_NOTIF, subs[1], OP_ENDIF); + case Fragment::OR_C: return BuildScript(std::move(subs[0]), OP_NOTIF, subs[1], OP_ENDIF); + case Fragment::OR_I: return BuildScript(OP_IF, subs[0], OP_ELSE, subs[1], OP_ENDIF); + case Fragment::ANDOR: return BuildScript(std::move(subs[0]), OP_NOTIF, subs[2], OP_ELSE, subs[1], OP_ENDIF); + case Fragment::MULTI: { + CScript script = BuildScript(node.k); + for (const auto& key : node.keys) { + script = BuildScript(std::move(script), ctx.ToPKBytes(key)); + } + return BuildScript(std::move(script), node.keys.size(), verify ? OP_CHECKMULTISIGVERIFY : OP_CHECKMULTISIG); + } + case Fragment::THRESH: { + CScript script = std::move(subs[0]); + for (size_t i = 1; i < subs.size(); ++i) { + script = BuildScript(std::move(script), subs[i], OP_ADD); + } + return BuildScript(std::move(script), node.k, verify ? OP_EQUALVERIFY : OP_EQUAL); + } + } + assert(false); + }; + return TreeEval<CScript>(false, downfn, upfn); + } + + template<typename CTx> + std::optional<std::string> ToString(const CTx& ctx) const { + // To construct the std::string representation for a Miniscript object, we use + // the TreeEvalMaybe algorithm. The State is a boolean: whether the parent node is a + // wrapper. If so, non-wrapper expressions must be prefixed with a ":". + auto downfn = [](bool, const Node& node, size_t) { + return (node.fragment == Fragment::WRAP_A || node.fragment == Fragment::WRAP_S || + node.fragment == Fragment::WRAP_D || node.fragment == Fragment::WRAP_V || + node.fragment == Fragment::WRAP_J || node.fragment == Fragment::WRAP_N || + node.fragment == Fragment::WRAP_C || + (node.fragment == Fragment::AND_V && node.subs[1]->fragment == Fragment::JUST_1) || + (node.fragment == Fragment::OR_I && node.subs[0]->fragment == Fragment::JUST_0) || + (node.fragment == Fragment::OR_I && node.subs[1]->fragment == Fragment::JUST_0)); + }; + // The upward function computes for a node, given whether its parent is a wrapper, + // and the string representations of its child nodes, the string representation of the node. + auto upfn = [&ctx](bool wrapped, const Node& node, Span<std::string> subs) -> std::optional<std::string> { + std::string ret = wrapped ? ":" : ""; + + switch (node.fragment) { + case Fragment::WRAP_A: return "a" + std::move(subs[0]); + case Fragment::WRAP_S: return "s" + std::move(subs[0]); + case Fragment::WRAP_C: + if (node.subs[0]->fragment == Fragment::PK_K) { + // pk(K) is syntactic sugar for c:pk_k(K) + auto key_str = ctx.ToString(node.subs[0]->keys[0]); + if (!key_str) return {}; + return std::move(ret) + "pk(" + std::move(*key_str) + ")"; + } + if (node.subs[0]->fragment == Fragment::PK_H) { + // pkh(K) is syntactic sugar for c:pk_h(K) + auto key_str = ctx.ToString(node.subs[0]->keys[0]); + if (!key_str) return {}; + return std::move(ret) + "pkh(" + std::move(*key_str) + ")"; + } + return "c" + std::move(subs[0]); + case Fragment::WRAP_D: return "d" + std::move(subs[0]); + case Fragment::WRAP_V: return "v" + std::move(subs[0]); + case Fragment::WRAP_J: return "j" + std::move(subs[0]); + case Fragment::WRAP_N: return "n" + std::move(subs[0]); + case Fragment::AND_V: + // t:X is syntactic sugar for and_v(X,1). + if (node.subs[1]->fragment == Fragment::JUST_1) return "t" + std::move(subs[0]); + break; + case Fragment::OR_I: + if (node.subs[0]->fragment == Fragment::JUST_0) return "l" + std::move(subs[1]); + if (node.subs[1]->fragment == Fragment::JUST_0) return "u" + std::move(subs[0]); + break; + default: break; + } + switch (node.fragment) { + case Fragment::PK_K: { + auto key_str = ctx.ToString(node.keys[0]); + if (!key_str) return {}; + return std::move(ret) + "pk_k(" + std::move(*key_str) + ")"; + } + case Fragment::PK_H: { + auto key_str = ctx.ToString(node.keys[0]); + if (!key_str) return {}; + return std::move(ret) + "pk_h(" + std::move(*key_str) + ")"; + } + case Fragment::AFTER: return std::move(ret) + "after(" + ::ToString(node.k) + ")"; + case Fragment::OLDER: return std::move(ret) + "older(" + ::ToString(node.k) + ")"; + case Fragment::HASH256: return std::move(ret) + "hash256(" + HexStr(node.data) + ")"; + case Fragment::HASH160: return std::move(ret) + "hash160(" + HexStr(node.data) + ")"; + case Fragment::SHA256: return std::move(ret) + "sha256(" + HexStr(node.data) + ")"; + case Fragment::RIPEMD160: return std::move(ret) + "ripemd160(" + HexStr(node.data) + ")"; + case Fragment::JUST_1: return std::move(ret) + "1"; + case Fragment::JUST_0: return std::move(ret) + "0"; + case Fragment::AND_V: return std::move(ret) + "and_v(" + std::move(subs[0]) + "," + std::move(subs[1]) + ")"; + case Fragment::AND_B: return std::move(ret) + "and_b(" + std::move(subs[0]) + "," + std::move(subs[1]) + ")"; + case Fragment::OR_B: return std::move(ret) + "or_b(" + std::move(subs[0]) + "," + std::move(subs[1]) + ")"; + case Fragment::OR_D: return std::move(ret) + "or_d(" + std::move(subs[0]) + "," + std::move(subs[1]) + ")"; + case Fragment::OR_C: return std::move(ret) + "or_c(" + std::move(subs[0]) + "," + std::move(subs[1]) + ")"; + case Fragment::OR_I: return std::move(ret) + "or_i(" + std::move(subs[0]) + "," + std::move(subs[1]) + ")"; + case Fragment::ANDOR: + // and_n(X,Y) is syntactic sugar for andor(X,Y,0). + if (node.subs[2]->fragment == Fragment::JUST_0) return std::move(ret) + "and_n(" + std::move(subs[0]) + "," + std::move(subs[1]) + ")"; + return std::move(ret) + "andor(" + std::move(subs[0]) + "," + std::move(subs[1]) + "," + std::move(subs[2]) + ")"; + case Fragment::MULTI: { + auto str = std::move(ret) + "multi(" + ::ToString(node.k); + for (const auto& key : node.keys) { + auto key_str = ctx.ToString(key); + if (!key_str) return {}; + str += "," + std::move(*key_str); + } + return std::move(str) + ")"; + } + case Fragment::THRESH: { + auto str = std::move(ret) + "thresh(" + ::ToString(node.k); + for (auto& sub : subs) { + str += "," + std::move(sub); + } + return std::move(str) + ")"; + } + default: break; + } + assert(false); + }; + + return TreeEvalMaybe<std::string>(false, downfn, upfn); + } + + internal::Ops CalcOps() const { + switch (fragment) { + case Fragment::JUST_1: return {0, 0, {}}; + case Fragment::JUST_0: return {0, {}, 0}; + case Fragment::PK_K: return {0, 0, 0}; + case Fragment::PK_H: return {3, 0, 0}; + case Fragment::OLDER: + case Fragment::AFTER: return {1, 0, {}}; + case Fragment::SHA256: + case Fragment::RIPEMD160: + case Fragment::HASH256: + case Fragment::HASH160: return {4, 0, {}}; + case Fragment::AND_V: return {subs[0]->ops.count + subs[1]->ops.count, subs[0]->ops.sat + subs[1]->ops.sat, {}}; + case Fragment::AND_B: { + const auto count{1 + subs[0]->ops.count + subs[1]->ops.count}; + const auto sat{subs[0]->ops.sat + subs[1]->ops.sat}; + const auto dsat{subs[0]->ops.dsat + subs[1]->ops.dsat}; + return {count, sat, dsat}; + } + case Fragment::OR_B: { + const auto count{1 + subs[0]->ops.count + subs[1]->ops.count}; + const auto sat{(subs[0]->ops.sat + subs[1]->ops.dsat) | (subs[1]->ops.sat + subs[0]->ops.dsat)}; + const auto dsat{subs[0]->ops.dsat + subs[1]->ops.dsat}; + return {count, sat, dsat}; + } + case Fragment::OR_D: { + const auto count{3 + subs[0]->ops.count + subs[1]->ops.count}; + const auto sat{subs[0]->ops.sat | (subs[1]->ops.sat + subs[0]->ops.dsat)}; + const auto dsat{subs[0]->ops.dsat + subs[1]->ops.dsat}; + return {count, sat, dsat}; + } + case Fragment::OR_C: { + const auto count{2 + subs[0]->ops.count + subs[1]->ops.count}; + const auto sat{subs[0]->ops.sat | (subs[1]->ops.sat + subs[0]->ops.dsat)}; + return {count, sat, {}}; + } + case Fragment::OR_I: { + const auto count{3 + subs[0]->ops.count + subs[1]->ops.count}; + const auto sat{subs[0]->ops.sat | subs[1]->ops.sat}; + const auto dsat{subs[0]->ops.dsat | subs[1]->ops.dsat}; + return {count, sat, dsat}; + } + case Fragment::ANDOR: { + const auto count{3 + subs[0]->ops.count + subs[1]->ops.count + subs[2]->ops.count}; + const auto sat{(subs[1]->ops.sat + subs[0]->ops.sat) | (subs[0]->ops.dsat + subs[2]->ops.sat)}; + const auto dsat{subs[0]->ops.dsat + subs[2]->ops.dsat}; + return {count, sat, dsat}; + } + case Fragment::MULTI: return {1, (uint32_t)keys.size(), (uint32_t)keys.size()}; + case Fragment::WRAP_S: + case Fragment::WRAP_C: + case Fragment::WRAP_N: return {1 + subs[0]->ops.count, subs[0]->ops.sat, subs[0]->ops.dsat}; + case Fragment::WRAP_A: return {2 + subs[0]->ops.count, subs[0]->ops.sat, subs[0]->ops.dsat}; + case Fragment::WRAP_D: return {3 + subs[0]->ops.count, subs[0]->ops.sat, 0}; + case Fragment::WRAP_J: return {4 + subs[0]->ops.count, subs[0]->ops.sat, 0}; + case Fragment::WRAP_V: return {subs[0]->ops.count + (subs[0]->GetType() << "x"_mst), subs[0]->ops.sat, {}}; + case Fragment::THRESH: { + uint32_t count = 0; + auto sats = Vector(internal::MaxInt<uint32_t>(0)); + for (const auto& sub : subs) { + count += sub->ops.count + 1; + auto next_sats = Vector(sats[0] + sub->ops.dsat); + for (size_t j = 1; j < sats.size(); ++j) next_sats.push_back((sats[j] + sub->ops.dsat) | (sats[j - 1] + sub->ops.sat)); + next_sats.push_back(sats[sats.size() - 1] + sub->ops.sat); + sats = std::move(next_sats); + } + assert(k <= sats.size()); + return {count, sats[k], sats[0]}; + } + } + assert(false); + } + + internal::StackSize CalcStackSize() const { + switch (fragment) { + case Fragment::JUST_0: return {{}, 0}; + case Fragment::JUST_1: + case Fragment::OLDER: + case Fragment::AFTER: return {0, {}}; + case Fragment::PK_K: return {1, 1}; + case Fragment::PK_H: return {2, 2}; + case Fragment::SHA256: + case Fragment::RIPEMD160: + case Fragment::HASH256: + case Fragment::HASH160: return {1, {}}; + case Fragment::ANDOR: { + const auto sat{(subs[0]->ss.sat + subs[1]->ss.sat) | (subs[0]->ss.dsat + subs[2]->ss.sat)}; + const auto dsat{subs[0]->ss.dsat + subs[2]->ss.dsat}; + return {sat, dsat}; + } + case Fragment::AND_V: return {subs[0]->ss.sat + subs[1]->ss.sat, {}}; + case Fragment::AND_B: return {subs[0]->ss.sat + subs[1]->ss.sat, subs[0]->ss.dsat + subs[1]->ss.dsat}; + case Fragment::OR_B: { + const auto sat{(subs[0]->ss.dsat + subs[1]->ss.sat) | (subs[0]->ss.sat + subs[1]->ss.dsat)}; + const auto dsat{subs[0]->ss.dsat + subs[1]->ss.dsat}; + return {sat, dsat}; + } + case Fragment::OR_C: return {subs[0]->ss.sat | (subs[0]->ss.dsat + subs[1]->ss.sat), {}}; + case Fragment::OR_D: return {subs[0]->ss.sat | (subs[0]->ss.dsat + subs[1]->ss.sat), subs[0]->ss.dsat + subs[1]->ss.dsat}; + case Fragment::OR_I: return {(subs[0]->ss.sat + 1) | (subs[1]->ss.sat + 1), (subs[0]->ss.dsat + 1) | (subs[1]->ss.dsat + 1)}; + case Fragment::MULTI: return {k + 1, k + 1}; + case Fragment::WRAP_A: + case Fragment::WRAP_N: + case Fragment::WRAP_S: + case Fragment::WRAP_C: return subs[0]->ss; + case Fragment::WRAP_D: return {1 + subs[0]->ss.sat, 1}; + case Fragment::WRAP_V: return {subs[0]->ss.sat, {}}; + case Fragment::WRAP_J: return {subs[0]->ss.sat, 1}; + case Fragment::THRESH: { + auto sats = Vector(internal::MaxInt<uint32_t>(0)); + for (const auto& sub : subs) { + auto next_sats = Vector(sats[0] + sub->ss.dsat); + for (size_t j = 1; j < sats.size(); ++j) next_sats.push_back((sats[j] + sub->ss.dsat) | (sats[j - 1] + sub->ss.sat)); + next_sats.push_back(sats[sats.size() - 1] + sub->ss.sat); + sats = std::move(next_sats); + } + assert(k <= sats.size()); + return {sats[k], sats[0]}; + } + } + assert(false); + } + + /** Check whether any key is repeated. + * 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 { + // We cannot use a lambda here, as lambdas are non assignable, and the set operations + // below require moving the comparators around. + struct Comp { + const Ctx* ctx_ptr; + 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 {}; + + 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 {}; + + 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 {}; + } + + return key_set; + }; + + return !TreeEvalMaybe<set>(upfn); + } + +public: + //! Return the size of the script for this expression (faster than ToScript().size()). + size_t ScriptSize() const { return scriptlen; } + + //! Return the maximum number of ops needed to satisfy this script non-malleably. + uint32_t GetOps() const { return ops.count + ops.sat.value; } + + //! Check the ops limit of this script against the consensus limit. + bool CheckOpsLimit() const { return GetOps() <= MAX_OPS_PER_SCRIPT; } + + /** Return the maximum number of stack elements needed to satisfy this script non-malleably, including + * the script push. */ + uint32_t GetStackSize() const { return ss.sat.value + 1; } + + //! Check the maximum stack size for this script against the policy limit. + bool CheckStackSize() const { return GetStackSize() - 1 <= MAX_STANDARD_P2WSH_STACK_ITEMS; } + + //! 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; } + + //! Check whether this node is valid as a script on its own. + bool IsValidTopLevel() const { return IsValid() && GetType() << "B"_mst; } + + //! Check whether this script can always be satisfied in a non-malleable way. + bool IsNonMalleable() const { return GetType() << "m"_mst; } + + //! Check whether this script always needs a signature. + bool NeedsSignature() const { return GetType() << "s"_mst; } + + //! Check whether there is no satisfaction path that contains both timelocks and heightlocks + 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; } + + //! Whether successful non-malleable satisfactions are guaranteed to be valid. + bool ValidSatisfactions() const { return IsValid() && CheckOpsLimit() && CheckStackSize(); } + + //! Whether the apparent policy of this node matches its script semantics. Doesn't guarantee it is a safe script on its own. + bool IsSaneSubexpression() const { return ValidSatisfactions() && IsNonMalleable() && CheckTimeLocksMix() && CheckDuplicateKey(); } + + //! Check whether this node is safe as a script on its own. + bool IsSane() const { return IsValidTopLevel() && IsSaneSubexpression() && NeedsSignature(); } + + //! 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)) {} +}; + +namespace internal { + +enum class ParseContext { + /** An expression which may be begin with wrappers followed by a colon. */ + WRAPPED_EXPR, + /** A miniscript expression which does not begin with wrappers. */ + EXPR, + + /** SWAP wraps the top constructed node with s: */ + SWAP, + /** ALT wraps the top constructed node with a: */ + ALT, + /** CHECK wraps the top constructed node with c: */ + CHECK, + /** DUP_IF wraps the top constructed node with d: */ + DUP_IF, + /** VERIFY wraps the top constructed node with v: */ + VERIFY, + /** NON_ZERO wraps the top constructed node with j: */ + NON_ZERO, + /** ZERO_NOTEQUAL wraps the top constructed node with n: */ + ZERO_NOTEQUAL, + /** WRAP_U will construct an or_i(X,0) node from the top constructed node. */ + WRAP_U, + /** WRAP_T will construct an and_v(X,1) node from the top constructed node. */ + WRAP_T, + + /** AND_N will construct an andor(X,Y,0) node from the last two constructed nodes. */ + AND_N, + /** AND_V will construct an and_v node from the last two constructed nodes. */ + AND_V, + /** AND_B will construct an and_b node from the last two constructed nodes. */ + AND_B, + /** ANDOR will construct an andor node from the last three constructed nodes. */ + ANDOR, + /** OR_B will construct an or_b node from the last two constructed nodes. */ + OR_B, + /** OR_C will construct an or_c node from the last two constructed nodes. */ + OR_C, + /** OR_D will construct an or_d node from the last two constructed nodes. */ + OR_D, + /** OR_I will construct an or_i node from the last two constructed nodes. */ + OR_I, + + /** THRESH will read a wrapped expression, and then look for a COMMA. If + * no comma follows, it will construct a thresh node from the appropriate + * number of constructed children. Otherwise, it will recurse with another + * THRESH. */ + THRESH, + + /** COMMA expects the next element to be ',' and fails if not. */ + COMMA, + /** CLOSE_BRACKET expects the next element to be ')' and fails if not. */ + CLOSE_BRACKET, +}; + +int FindNextChar(Span<const char> in, const char m); + +/** Parse a key string ending at the end of the fragment's text representation. */ +template<typename Key, typename Ctx> +std::optional<std::pair<Key, int>> ParseKeyEnd(Span<const char> in, const Ctx& ctx) +{ + int key_size = FindNextChar(in, ')'); + if (key_size < 1) return {}; + auto key = ctx.FromString(in.begin(), in.begin() + key_size); + if (!key) return {}; + return {{std::move(*key), key_size}}; +} + +/** Parse a hex string ending at the end of the fragment's text representation. */ +template<typename Ctx> +std::optional<std::pair<std::vector<unsigned char>, int>> ParseHexStrEnd(Span<const char> in, const size_t expected_size, + const Ctx& ctx) +{ + int hash_size = FindNextChar(in, ')'); + if (hash_size < 1) return {}; + std::string val = std::string(in.begin(), in.begin() + hash_size); + if (!IsHex(val)) return {}; + auto hash = ParseHex(val); + if (hash.size() != expected_size) return {}; + return {{std::move(hash), hash_size}}; +} + +/** 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) +{ + 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()))); + } else { + constructed.back() = MakeNodeRef<Key>(ctx, nt, Vector(std::move(constructed.back()), std::move(child))); + } +} + +/** + * 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; + + // 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; + + to_parse.emplace_back(ParseContext::WRAPPED_EXPR, -1, -1); + + while (!to_parse.empty()) { + // 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) { + if (in[i] == ':') { + colon_index = i; + break; + } + 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) { + if (in[j] == 'a') { + to_parse.emplace_back(ParseContext::ALT, -1, -1); + } else if (in[j] == 's') { + to_parse.emplace_back(ParseContext::SWAP, -1, -1); + } else if (in[j] == 'c') { + to_parse.emplace_back(ParseContext::CHECK, -1, -1); + } else if (in[j] == 'd') { + to_parse.emplace_back(ParseContext::DUP_IF, -1, -1); + } else if (in[j] == 'j') { + to_parse.emplace_back(ParseContext::NON_ZERO, -1, -1); + } else if (in[j] == 'n') { + to_parse.emplace_back(ParseContext::ZERO_NOTEQUAL, -1, -1); + } else if (in[j] == 'v') { + to_parse.emplace_back(ParseContext::VERIFY, -1, -1); + } else if (in[j] == 'u') { + to_parse.emplace_back(ParseContext::WRAP_U, -1, -1); + } else if (in[j] == 't') { + 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)); + to_parse.emplace_back(ParseContext::OR_I, -1, -1); + } else { + return {}; + } + } + to_parse.emplace_back(ParseContext::EXPR, -1, -1); + in = in.subspan(colon_index + 1); + break; + } + case ParseContext::EXPR: { + if (Const("0", in)) { + constructed.push_back(MakeNodeRef<Key>(ctx, Fragment::JUST_0)); + } else if (Const("1", in)) { + constructed.push_back(MakeNodeRef<Key>(ctx, 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)))))); + in = in.subspan(key_size + 1); + } 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)))))); + in = in.subspan(key_size + 1); + } 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)))); + in = in.subspan(key_size + 1); + } 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)))); + in = in.subspan(key_size + 1); + } 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))); + in = in.subspan(hash_size + 1); + } 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))); + in = in.subspan(hash_size + 1); + } 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))); + in = in.subspan(hash_size + 1); + } 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))); + in = in.subspan(hash_size + 1); + } 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)); + in = in.subspan(arg_size + 1); + } 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)); + in = in.subspan(arg_size + 1); + } else if (Const("multi(", in)) { + // Get threshold + int next_comma = FindNextChar(in, ','); + if (next_comma < 1) return {}; + if (!ParseInt64(std::string(in.begin(), in.begin() + next_comma), &k)) return {}; + in = in.subspan(next_comma + 1); + // Get keys + std::vector<Key> keys; + while (next_comma != -1) { + next_comma = FindNextChar(in, ','); + int key_length = (next_comma == -1) ? FindNextChar(in, ')') : next_comma; + if (key_length < 1) return {}; + auto key = ctx.FromString(in.begin(), in.begin() + key_length); + if (!key) return {}; + keys.push_back(std::move(*key)); + in = in.subspan(key_length + 1); + } + 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)); + } else if (Const("thresh(", in)) { + int next_comma = FindNextChar(in, ','); + if (next_comma < 1) return {}; + if (!ParseInt64(std::string(in.begin(), in.begin() + next_comma), &k)) return {}; + if (k < 1) return {}; + in = in.subspan(next_comma + 1); + // 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); + } else if (Const("andor(", in)) { + to_parse.emplace_back(ParseContext::ANDOR, -1, -1); + to_parse.emplace_back(ParseContext::CLOSE_BRACKET, -1, -1); + 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); + to_parse.emplace_back(ParseContext::COMMA, -1, -1); + to_parse.emplace_back(ParseContext::WRAPPED_EXPR, -1, -1); + } else { + if (Const("and_n(", in)) { + to_parse.emplace_back(ParseContext::AND_N, -1, -1); + } else if (Const("and_b(", in)) { + to_parse.emplace_back(ParseContext::AND_B, -1, -1); + } else if (Const("and_v(", in)) { + to_parse.emplace_back(ParseContext::AND_V, -1, -1); + } else if (Const("or_b(", in)) { + to_parse.emplace_back(ParseContext::OR_B, -1, -1); + } else if (Const("or_c(", in)) { + to_parse.emplace_back(ParseContext::OR_C, -1, -1); + } else if (Const("or_d(", in)) { + to_parse.emplace_back(ParseContext::OR_D, -1, -1); + } else if (Const("or_i(", in)) { + to_parse.emplace_back(ParseContext::OR_I, -1, -1); + } else { + return {}; + } + to_parse.emplace_back(ParseContext::CLOSE_BRACKET, -1, -1); + 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); + } + break; + } + case ParseContext::ALT: { + constructed.back() = MakeNodeRef<Key>(ctx, 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()))); + break; + } + case ParseContext::CHECK: { + constructed.back() = MakeNodeRef<Key>(ctx, 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()))); + break; + } + case ParseContext::NON_ZERO: { + constructed.back() = MakeNodeRef<Key>(ctx, 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()))); + break; + } + case ParseContext::VERIFY: { + constructed.back() = MakeNodeRef<Key>(ctx, 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))); + break; + } + case ParseContext::WRAP_T: { + constructed.back() = MakeNodeRef<Key>(ctx, Fragment::AND_V, Vector(std::move(constructed.back()), MakeNodeRef<Key>(ctx, Fragment::JUST_1))); + break; + } + case ParseContext::AND_B: { + BuildBack(ctx, 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))); + break; + } + case ParseContext::AND_V: { + BuildBack(ctx, Fragment::AND_V, constructed); + break; + } + case ParseContext::OR_B: { + BuildBack(ctx, Fragment::OR_B, constructed); + break; + } + case ParseContext::OR_C: { + BuildBack(ctx, Fragment::OR_C, constructed); + break; + } + case ParseContext::OR_D: { + BuildBack(ctx, Fragment::OR_D, constructed); + break; + } + case ParseContext::OR_I: { + BuildBack(ctx, Fragment::OR_I, constructed); + break; + } + case ParseContext::ANDOR: { + auto right = std::move(constructed.back()); + 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))); + break; + } + case ParseContext::THRESH: { + if (in.size() < 1) return {}; + if (in[0] == ',') { + in = in.subspan(1); + to_parse.emplace_back(ParseContext::THRESH, n+1, k); + to_parse.emplace_back(ParseContext::WRAPPED_EXPR, -1, -1); + } else if (in[0] == ')') { + if (k > n) return {}; + in = in.subspan(1); + // Children are constructed in reverse order, so iterate from end to beginning + std::vector<NodeRef<Key>> subs; + for (int i = 0; i < n; ++i) { + subs.push_back(std::move(constructed.back())); + constructed.pop_back(); + } + std::reverse(subs.begin(), subs.end()); + constructed.push_back(MakeNodeRef<Key>(ctx, Fragment::THRESH, std::move(subs), k)); + } else { + return {}; + } + break; + } + case ParseContext::COMMA: { + if (in.size() < 1 || in[0] != ',') return {}; + in = in.subspan(1); + break; + } + case ParseContext::CLOSE_BRACKET: { + if (in.size() < 1 || in[0] != ')') return {}; + in = in.subspan(1); + break; + } + } + } + + // Sanity checks on the produced miniscript + assert(constructed.size() == 1); + if (in.size() > 0) return {}; + return std::move(constructed.front()); +} + +/** Decode a script into opcode/push pairs. + * + * Construct a vector with one element per opcode in the script, in reverse order. + * Each element is a pair consisting of the opcode, as well as the data pushed by + * the opcode (including OP_n), if any. OP_CHECKSIGVERIFY, OP_CHECKMULTISIGVERIFY, + * and OP_EQUALVERIFY are decomposed into OP_CHECKSIG, OP_CHECKMULTISIG, OP_EQUAL + * respectively, plus OP_VERIFY. + */ +std::optional<std::vector<Opcode>> DecomposeScript(const CScript& script); + +/** Determine whether the passed pair (created by DecomposeScript) is pushing a number. */ +std::optional<int64_t> ParseScriptNumber(const Opcode& in); + +enum class DecodeContext { + /** A single expression of type B, K, or V. Specifically, this can't be an + * and_v or an expression of type W (a: and s: wrappers). */ + SINGLE_BKV_EXPR, + /** Potentially multiple SINGLE_BKV_EXPRs as children of (potentially multiple) + * and_v expressions. Syntactic sugar for MAYBE_AND_V + SINGLE_BKV_EXPR. */ + BKV_EXPR, + /** An expression of type W (a: or s: wrappers). */ + W_EXPR, + + /** SWAP expects the next element to be OP_SWAP (inside a W-type expression that + * didn't end with FROMALTSTACK), and wraps the top of the constructed stack + * with s: */ + SWAP, + /** ALT expects the next element to be TOALTSTACK (we must have already read a + * FROMALTSTACK earlier), and wraps the top of the constructed stack with a: */ + ALT, + /** CHECK wraps the top constructed node with c: */ + CHECK, + /** DUP_IF wraps the top constructed node with d: */ + DUP_IF, + /** VERIFY wraps the top constructed node with v: */ + VERIFY, + /** NON_ZERO wraps the top constructed node with j: */ + NON_ZERO, + /** ZERO_NOTEQUAL wraps the top constructed node with n: */ + ZERO_NOTEQUAL, + + /** MAYBE_AND_V will check if the next part of the script could be a valid + * miniscript sub-expression, and if so it will push AND_V and SINGLE_BKV_EXPR + * to decode it and construct the and_v node. This is recursive, to deal with + * multiple and_v nodes inside each other. */ + MAYBE_AND_V, + /** AND_V will construct an and_v node from the last two constructed nodes. */ + AND_V, + /** AND_B will construct an and_b node from the last two constructed nodes. */ + AND_B, + /** ANDOR will construct an andor node from the last three constructed nodes. */ + ANDOR, + /** OR_B will construct an or_b node from the last two constructed nodes. */ + OR_B, + /** OR_C will construct an or_c node from the last two constructed nodes. */ + OR_C, + /** OR_D will construct an or_d node from the last two constructed nodes. */ + OR_D, + + /** In a thresh expression, all sub-expressions other than the first are W-type, + * and end in OP_ADD. THRESH_W will check for this OP_ADD and either push a W_EXPR + * or a SINGLE_BKV_EXPR and jump to THRESH_E accordingly. */ + THRESH_W, + /** THRESH_E constructs a thresh node from the appropriate number of constructed + * children. */ + THRESH_E, + + /** ENDIF signals that we are inside some sort of OP_IF structure, which could be + * or_d, or_c, or_i, andor, d:, or j: wrapper, depending on what follows. We read + * a BKV_EXPR and then deal with the next opcode case-by-case. */ + ENDIF, + /** If, inside an ENDIF context, we find an OP_NOTIF before finding an OP_ELSE, + * we could either be in an or_d or an or_c node. We then check for IFDUP to + * distinguish these cases. */ + ENDIF_NOTIF, + /** If, inside an ENDIF context, we find an OP_ELSE, then we could be in either an + * or_i or an andor node. Read the next BKV_EXPR and find either an OP_IF or an + * OP_NOTIF. */ + ENDIF_ELSE, +}; + +//! Parse a miniscript from a bitcoin script +template<typename Key, typename Ctx, typename I> +inline NodeRef<Key> DecodeScript(I& in, I last, const Ctx& ctx) +{ + // The two integers are used to hold state for thresh() + std::vector<std::tuple<DecodeContext, int64_t, int64_t>> to_parse; + std::vector<NodeRef<Key>> constructed; + + // This is the top level, so we assume the type is B + // (in particular, disallowing top level W expressions) + to_parse.emplace_back(DecodeContext::BKV_EXPR, -1, -1); + + while (!to_parse.empty()) { + // Exit early if the Miniscript is not going to be valid. + if (!constructed.empty() && !constructed.back()->IsValid()) 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 DecodeContext::SINGLE_BKV_EXPR: { + if (in >= last) return {}; + + // Constants + if (in[0].first == OP_1) { + ++in; + constructed.push_back(MakeNodeRef<Key>(ctx, Fragment::JUST_1)); + break; + } + if (in[0].first == OP_0) { + ++in; + constructed.push_back(MakeNodeRef<Key>(ctx, Fragment::JUST_0)); + break; + } + // Public keys + if (in[0].second.size() == 33) { + 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)))); + 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)))); + break; + } + // Time locks + std::optional<int64_t> num; + 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)); + 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)); + 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)); + 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)); + 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)); + 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)); + in += 7; + break; + } + } + // Multi + if (last - in >= 3 && in[0].first == OP_CHECKMULTISIG) { + std::vector<Key> keys; + const auto n = ParseScriptNumber(in[1]); + if (!n || last - in < 3 + *n) return {}; + if (*n < 1 || *n > 20) return {}; + for (int i = 0; i < *n; ++i) { + if (in[2 + i].second.size() != 33) return {}; + auto key = ctx.FromPKBytes(in[2 + i].second.begin(), in[2 + i].second.end()); + if (!key) return {}; + keys.push_back(std::move(*key)); + } + const auto k = ParseScriptNumber(in[2 + *n]); + 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)); + break; + } + /** In the following wrappers, we only need to push SINGLE_BKV_EXPR rather + * than BKV_EXPR, because and_v commutes with these wrappers. For example, + * c:and_v(X,Y) produces the same script as and_v(X,c:Y). */ + // c: wrapper + if (in[0].first == OP_CHECKSIG) { + ++in; + to_parse.emplace_back(DecodeContext::CHECK, -1, -1); + to_parse.emplace_back(DecodeContext::SINGLE_BKV_EXPR, -1, -1); + break; + } + // v: wrapper + if (in[0].first == OP_VERIFY) { + ++in; + to_parse.emplace_back(DecodeContext::VERIFY, -1, -1); + to_parse.emplace_back(DecodeContext::SINGLE_BKV_EXPR, -1, -1); + break; + } + // n: wrapper + if (in[0].first == OP_0NOTEQUAL) { + ++in; + to_parse.emplace_back(DecodeContext::ZERO_NOTEQUAL, -1, -1); + to_parse.emplace_back(DecodeContext::SINGLE_BKV_EXPR, -1, -1); + break; + } + // Thresh + if (last - in >= 3 && in[0].first == OP_EQUAL && (num = ParseScriptNumber(in[1]))) { + if (*num < 1) return {}; + in += 2; + to_parse.emplace_back(DecodeContext::THRESH_W, 0, *num); + break; + } + // OP_ENDIF can be WRAP_J, WRAP_D, ANDOR, OR_C, OR_D, or OR_I + if (in[0].first == OP_ENDIF) { + ++in; + to_parse.emplace_back(DecodeContext::ENDIF, -1, -1); + to_parse.emplace_back(DecodeContext::BKV_EXPR, -1, -1); + break; + } + /** In and_b and or_b nodes, we only look for SINGLE_BKV_EXPR, because + * or_b(and_v(X,Y),Z) has script [X] [Y] [Z] OP_BOOLOR, the same as + * and_v(X,or_b(Y,Z)). In this example, the former of these is invalid as + * miniscript, while the latter is valid. So we leave the and_v "outside" + * while decoding. */ + // and_b + if (in[0].first == OP_BOOLAND) { + ++in; + to_parse.emplace_back(DecodeContext::AND_B, -1, -1); + to_parse.emplace_back(DecodeContext::SINGLE_BKV_EXPR, -1, -1); + to_parse.emplace_back(DecodeContext::W_EXPR, -1, -1); + break; + } + // or_b + if (in[0].first == OP_BOOLOR) { + ++in; + to_parse.emplace_back(DecodeContext::OR_B, -1, -1); + to_parse.emplace_back(DecodeContext::SINGLE_BKV_EXPR, -1, -1); + to_parse.emplace_back(DecodeContext::W_EXPR, -1, -1); + break; + } + // Unrecognised expression + return {}; + } + case DecodeContext::BKV_EXPR: { + to_parse.emplace_back(DecodeContext::MAYBE_AND_V, -1, -1); + to_parse.emplace_back(DecodeContext::SINGLE_BKV_EXPR, -1, -1); + break; + } + case DecodeContext::W_EXPR: { + // a: wrapper + if (in >= last) return {}; + if (in[0].first == OP_FROMALTSTACK) { + ++in; + to_parse.emplace_back(DecodeContext::ALT, -1, -1); + } else { + to_parse.emplace_back(DecodeContext::SWAP, -1, -1); + } + to_parse.emplace_back(DecodeContext::BKV_EXPR, -1, -1); + break; + } + case DecodeContext::MAYBE_AND_V: { + // If we reach a potential AND_V top-level, check if the next part of the script could be another AND_V child + // These op-codes cannot end any well-formed miniscript so cannot be used in an and_v node. + if (in < last && in[0].first != OP_IF && in[0].first != OP_ELSE && in[0].first != OP_NOTIF && in[0].first != OP_TOALTSTACK && in[0].first != OP_SWAP) { + to_parse.emplace_back(DecodeContext::AND_V, -1, -1); + // BKV_EXPR can contain more AND_V nodes + to_parse.emplace_back(DecodeContext::BKV_EXPR, -1, -1); + } + break; + } + 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()))); + 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()))); + break; + } + case DecodeContext::CHECK: { + if (constructed.empty()) return {}; + constructed.back() = MakeNodeRef<Key>(ctx, 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()))); + break; + } + case DecodeContext::VERIFY: { + if (constructed.empty()) return {}; + constructed.back() = MakeNodeRef<Key>(ctx, 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()))); + break; + } + case DecodeContext::ZERO_NOTEQUAL: { + if (constructed.empty()) return {}; + constructed.back() = MakeNodeRef<Key>(ctx, 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); + break; + } + case DecodeContext::AND_B: { + if (constructed.size() < 2) return {}; + BuildBack(ctx, Fragment::AND_B, constructed, /*reverse=*/true); + break; + } + case DecodeContext::OR_B: { + if (constructed.size() < 2) return {}; + BuildBack(ctx, Fragment::OR_B, constructed, /*reverse=*/true); + break; + } + case DecodeContext::OR_C: { + if (constructed.size() < 2) return {}; + BuildBack(ctx, Fragment::OR_C, constructed, /*reverse=*/true); + break; + } + case DecodeContext::OR_D: { + if (constructed.size() < 2) return {}; + BuildBack(ctx, Fragment::OR_D, constructed, /*reverse=*/true); + break; + } + case DecodeContext::ANDOR: { + if (constructed.size() < 3) return {}; + NodeRef<Key> left = std::move(constructed.back()); + constructed.pop_back(); + 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))); + break; + } + case DecodeContext::THRESH_W: { + if (in >= last) return {}; + if (in[0].first == OP_ADD) { + ++in; + to_parse.emplace_back(DecodeContext::THRESH_W, n+1, k); + to_parse.emplace_back(DecodeContext::W_EXPR, -1, -1); + } else { + to_parse.emplace_back(DecodeContext::THRESH_E, n+1, k); + // All children of thresh have type modifier d, so cannot be and_v + to_parse.emplace_back(DecodeContext::SINGLE_BKV_EXPR, -1, -1); + } + break; + } + case DecodeContext::THRESH_E: { + if (k < 1 || k > n || constructed.size() < static_cast<size_t>(n)) return {}; + std::vector<NodeRef<Key>> subs; + for (int i = 0; i < n; ++i) { + NodeRef<Key> sub = std::move(constructed.back()); + constructed.pop_back(); + subs.push_back(std::move(sub)); + } + constructed.push_back(MakeNodeRef<Key>(ctx, Fragment::THRESH, std::move(subs), k)); + break; + } + case DecodeContext::ENDIF: { + if (in >= last) return {}; + + // could be andor or or_i + if (in[0].first == OP_ELSE) { + ++in; + to_parse.emplace_back(DecodeContext::ENDIF_ELSE, -1, -1); + to_parse.emplace_back(DecodeContext::BKV_EXPR, -1, -1); + } + // could be j: or d: wrapper + else if (in[0].first == OP_IF) { + if (last - in >= 2 && in[1].first == OP_DUP) { + in += 2; + to_parse.emplace_back(DecodeContext::DUP_IF, -1, -1); + } else if (last - in >= 3 && in[1].first == OP_0NOTEQUAL && in[2].first == OP_SIZE) { + in += 3; + to_parse.emplace_back(DecodeContext::NON_ZERO, -1, -1); + } + else { + return {}; + } + // could be or_c or or_d + } else if (in[0].first == OP_NOTIF) { + ++in; + to_parse.emplace_back(DecodeContext::ENDIF_NOTIF, -1, -1); + } + else { + return {}; + } + break; + } + case DecodeContext::ENDIF_NOTIF: { + if (in >= last) return {}; + if (in[0].first == OP_IFDUP) { + ++in; + to_parse.emplace_back(DecodeContext::OR_D, -1, -1); + } else { + to_parse.emplace_back(DecodeContext::OR_C, -1, -1); + } + // or_c and or_d both require X to have type modifier d so, can't contain and_v + to_parse.emplace_back(DecodeContext::SINGLE_BKV_EXPR, -1, -1); + break; + } + case DecodeContext::ENDIF_ELSE: { + if (in >= last) return {}; + if (in[0].first == OP_IF) { + ++in; + BuildBack(ctx, Fragment::OR_I, constructed, /*reverse=*/true); + } else if (in[0].first == OP_NOTIF) { + ++in; + to_parse.emplace_back(DecodeContext::ANDOR, -1, -1); + // andor requires X to have type modifier d, so it can't be and_v + to_parse.emplace_back(DecodeContext::SINGLE_BKV_EXPR, -1, -1); + } else { + return {}; + } + break; + } + } + } + if (constructed.size() != 1) return {}; + const NodeRef<Key> tl_node = std::move(constructed.front()); + // 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 {}; + return tl_node; +} + +} // namespace internal + +template<typename Ctx> +inline NodeRef<typename Ctx::Key> FromString(const std::string& str, const Ctx& ctx) { + return internal::Parse<typename Ctx::Key>(str, ctx); +} + +template<typename Ctx> +inline NodeRef<typename Ctx::Key> FromScript(const CScript& script, const Ctx& ctx) { + using namespace internal; + auto decomposed = DecomposeScript(script); + if (!decomposed) return {}; + auto it = decomposed->begin(); + auto ret = DecodeScript<typename Ctx::Key>(it, decomposed->end(), ctx); + if (!ret) return {}; + if (it != decomposed->end()) return {}; + return ret; +} + +} // namespace miniscript + +#endif // BITCOIN_SCRIPT_MINISCRIPT_H diff --git a/src/script/script.cpp b/src/script/script.cpp index 9a6419088b..88b4bc2f44 100644 --- a/src/script/script.cpp +++ b/src/script/script.cpp @@ -339,3 +339,28 @@ bool IsOpSuccess(const opcodetype& opcode) (opcode >= 141 && opcode <= 142) || (opcode >= 149 && opcode <= 153) || (opcode >= 187 && opcode <= 254); } + +bool CheckMinimalPush(const std::vector<unsigned char>& data, opcodetype opcode) { + // Excludes OP_1NEGATE, OP_1-16 since they are by definition minimal + assert(0 <= opcode && opcode <= OP_PUSHDATA4); + if (data.size() == 0) { + // Should have used OP_0. + return opcode == OP_0; + } else if (data.size() == 1 && data[0] >= 1 && data[0] <= 16) { + // Should have used OP_1 .. OP_16. + return false; + } else if (data.size() == 1 && data[0] == 0x81) { + // Should have used OP_1NEGATE. + return false; + } else if (data.size() <= 75) { + // Must have used a direct push (opcode indicating number of bytes pushed + those bytes). + return opcode == data.size(); + } else if (data.size() <= 255) { + // Must have used OP_PUSHDATA. + return opcode == OP_PUSHDATA1; + } else if (data.size() <= 65535) { + // Must have used OP_PUSHDATA2. + return opcode == OP_PUSHDATA2; + } + return true; +} diff --git a/src/script/script.h b/src/script/script.h index 8b7a7bb7b3..1e5f694d52 100644 --- a/src/script/script.h +++ b/src/script/script.h @@ -29,6 +29,9 @@ static const int MAX_OPS_PER_SCRIPT = 201; // Maximum number of public keys per multisig static const int MAX_PUBKEYS_PER_MULTISIG = 20; +/** The limit of keys in OP_CHECKSIGADD-based scripts. It is due to the stack limit in BIP342. */ +static constexpr unsigned int MAX_PUBKEYS_PER_MULTI_A = 999; + // Maximum script length in bytes static const int MAX_SCRIPT_SIZE = 10000; @@ -332,6 +335,8 @@ public: return m_value; } + int64_t GetInt64() const { return m_value; } + std::vector<unsigned char> getvch() const { return serialize(m_value); @@ -573,4 +578,31 @@ struct CScriptWitness /** Test for OP_SUCCESSx opcodes as defined by BIP342. */ bool IsOpSuccess(const opcodetype& opcode); +bool CheckMinimalPush(const std::vector<unsigned char>& data, opcodetype opcode); + +/** Build a script by concatenating other scripts, or any argument accepted by CScript::operator<<. */ +template<typename... Ts> +CScript BuildScript(Ts&&... inputs) +{ + CScript ret; + int cnt{0}; + + ([&ret, &cnt] (Ts&& input) { + 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) { + ret = std::forward<Ts>(input); + } else { + ret.insert(ret.end(), input.begin(), input.end()); + } + } else { + // Otherwise invoke CScript::operator<<. + ret << input; + } + cnt++; + } (std::forward<Ts>(inputs)), ...); + + return ret; +} + #endif // BITCOIN_SCRIPT_SCRIPT_H 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 371a937bc8..4014ebadbc 100644 --- a/src/script/sign.cpp +++ b/src/script/sign.cpp @@ -18,16 +18,16 @@ typedef std::vector<unsigned char> valtype; -MutableTransactionSignatureCreator::MutableTransactionSignatureCreator(const CMutableTransaction* tx, unsigned int input_idx, const CAmount& amount, int hash_type) - : txTo{tx}, nIn{input_idx}, nHashType{hash_type}, amount{amount}, checker{txTo, nIn, amount, MissingDataBehavior::FAIL}, +MutableTransactionSignatureCreator::MutableTransactionSignatureCreator(const CMutableTransaction& tx, unsigned int input_idx, const CAmount& amount, int hash_type) + : m_txto{tx}, nIn{input_idx}, nHashType{hash_type}, amount{amount}, checker{&m_txto, nIn, amount, MissingDataBehavior::FAIL}, m_txdata(nullptr) { } -MutableTransactionSignatureCreator::MutableTransactionSignatureCreator(const CMutableTransaction* tx, unsigned int input_idx, const CAmount& amount, const PrecomputedTransactionData* txdata, int hash_type) - : txTo{tx}, nIn{input_idx}, nHashType{hash_type}, amount{amount}, - checker{txdata ? MutableTransactionSignatureChecker{txTo, nIn, amount, *txdata, MissingDataBehavior::FAIL} : - MutableTransactionSignatureChecker{txTo, nIn, amount, MissingDataBehavior::FAIL}}, +MutableTransactionSignatureCreator::MutableTransactionSignatureCreator(const CMutableTransaction& tx, unsigned int input_idx, const CAmount& amount, const PrecomputedTransactionData* txdata, int hash_type) + : m_txto{tx}, nIn{input_idx}, nHashType{hash_type}, amount{amount}, + checker{txdata ? MutableTransactionSignatureChecker{&m_txto, nIn, amount, *txdata, MissingDataBehavior::FAIL} : + MutableTransactionSignatureChecker{&m_txto, nIn, amount, MissingDataBehavior::FAIL}}, m_txdata(txdata) { } @@ -50,7 +50,7 @@ bool MutableTransactionSignatureCreator::CreateSig(const SigningProvider& provid // BASE/WITNESS_V0 signatures don't support explicit SIGHASH_DEFAULT, use SIGHASH_ALL instead. const int hashtype = nHashType == SIGHASH_DEFAULT ? SIGHASH_ALL : nHashType; - uint256 hash = SignatureHash(scriptCode, *txTo, nIn, hashtype, amount, sigversion, m_txdata); + uint256 hash = SignatureHash(scriptCode, m_txto, nIn, hashtype, amount, sigversion, m_txdata); if (!key.Sign(hash, vchSig)) return false; vchSig.push_back((unsigned char)hashtype); @@ -80,7 +80,7 @@ bool MutableTransactionSignatureCreator::CreateSchnorrSig(const SigningProvider& execdata.m_tapleaf_hash = *leaf_hash; } uint256 hash; - if (!SignatureHashSchnorr(hash, execdata, *txTo, nIn, nHashType, sigversion, *m_txdata, MissingDataBehavior::FAIL)) return false; + if (!SignatureHashSchnorr(hash, execdata, m_txto, nIn, nHashType, sigversion, *m_txdata, MissingDataBehavior::FAIL)) return false; sig.resize(64); // Use uint256{} as aux_rnd for now. if (!key.SignSchnorr(hash, sig, merkle_root, {})) return false; @@ -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,16 +165,50 @@ 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)); return true; } + return false; + } + + // multi_a scripts (<key> OP_CHECKSIG <key> OP_CHECKSIGADD <key> OP_CHECKSIGADD <k> OP_NUMEQUAL) + if (auto match = MatchMultiA(script)) { + std::vector<std::vector<unsigned char>> sigs; + int good_sigs = 0; + for (size_t i = 0; i < match->second.size(); ++i) { + XOnlyPubKey pubkey{*(match->second.rbegin() + i)}; + std::vector<unsigned char> sig; + bool good_sig = CreateTaprootScriptSig(creator, sigdata, provider, sig, pubkey, leaf_hash, sigversion); + if (good_sig && good_sigs < match->first) { + ++good_sigs; + sigs.push_back(std::move(sig)); + } else { + sigs.emplace_back(); + } + } + if (good_sigs == match->first) { + result = std::move(sigs); + return true; + } + return false; } return false; @@ -182,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; } } @@ -517,7 +569,7 @@ bool SignSignature(const SigningProvider &provider, const CScript& fromPubKey, C { assert(nIn < txTo.vin.size()); - MutableTransactionSignatureCreator creator(&txTo, nIn, amount, nHashType); + MutableTransactionSignatureCreator creator(txTo, nIn, amount, nHashType); SignatureData sigdata; bool ret = ProduceSignature(provider, creator, fromPubKey, sigdata); @@ -540,7 +592,7 @@ namespace { class DummySignatureChecker final : public BaseSignatureChecker { public: - DummySignatureChecker() {} + DummySignatureChecker() = default; 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; } }; @@ -580,25 +632,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; @@ -632,7 +665,7 @@ bool SignTransaction(CMutableTransaction& mtx, const SigningProvider* keystore, CTxIn& txin = mtx.vin[i]; auto coin = coins.find(txin.prevout); if (coin == coins.end() || coin->second.IsSpent()) { - txdata.Init(txConst, /* spent_outputs */ {}, /* force */ true); + txdata.Init(txConst, /*spent_outputs=*/{}, /*force=*/true); break; } else { spent_outputs.emplace_back(coin->second.out.nValue, coin->second.out.scriptPubKey); @@ -656,7 +689,7 @@ bool SignTransaction(CMutableTransaction& mtx, const SigningProvider* keystore, SignatureData sigdata = DataFromTransaction(mtx, i, coin->second.out); // Only sign SIGHASH_SINGLE if there's a corresponding output: if (!fHashSingle || (i < mtx.vout.size())) { - ProduceSignature(*keystore, MutableTransactionSignatureCreator(&mtx, i, amount, &txdata, nHashType), prevPubKey, sigdata); + ProduceSignature(*keystore, MutableTransactionSignatureCreator(mtx, i, amount, &txdata, nHashType), prevPubKey, sigdata); } UpdateInput(txin, sigdata); diff --git a/src/script/sign.h b/src/script/sign.h index 7301826ba5..958d673b9f 100644 --- a/src/script/sign.h +++ b/src/script/sign.h @@ -6,6 +6,7 @@ #ifndef BITCOIN_SCRIPT_SIGN_H #define BITCOIN_SCRIPT_SIGN_H +#include <attributes.h> #include <coins.h> #include <hash.h> #include <pubkey.h> @@ -34,8 +35,9 @@ public: }; /** A signature creator for transactions. */ -class MutableTransactionSignatureCreator : public BaseSignatureCreator { - const CMutableTransaction* txTo; +class MutableTransactionSignatureCreator : public BaseSignatureCreator +{ + const CMutableTransaction& m_txto; unsigned int nIn; int nHashType; CAmount amount; @@ -43,8 +45,8 @@ class MutableTransactionSignatureCreator : public BaseSignatureCreator { const PrecomputedTransactionData* m_txdata; public: - MutableTransactionSignatureCreator(const CMutableTransaction* tx, unsigned int input_idx, const CAmount& amount, int hash_type); - MutableTransactionSignatureCreator(const CMutableTransaction* tx, unsigned int input_idx, const CAmount& amount, const PrecomputedTransactionData* txdata, int hash_type); + MutableTransactionSignatureCreator(const CMutableTransaction& tx LIFETIMEBOUND, unsigned int input_idx, const CAmount& amount, int hash_type); + MutableTransactionSignatureCreator(const CMutableTransaction& tx LIFETIMEBOUND, unsigned int input_idx, const CAmount& amount, const PrecomputedTransactionData* txdata, int hash_type); const BaseSignatureChecker& Checker() const override { return checker; } bool CreateSig(const SigningProvider& provider, std::vector<unsigned char>& vchSig, const CKeyID& keyid, const CScript& scriptCode, SigVersion sigversion) const override; 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; @@ -68,10 +70,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) @@ -93,12 +97,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 5fb98cc307..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)) {} @@ -91,56 +88,83 @@ static constexpr bool IsSmallInteger(opcodetype opcode) return opcode >= OP_1 && opcode <= OP_16; } -static constexpr bool IsPushdataOp(opcodetype opcode) -{ - return opcode > OP_FALSE && opcode <= OP_PUSHDATA4; -} - -static constexpr bool IsValidMultisigKeyCount(int n_keys) -{ - return n_keys > 0 && n_keys <= MAX_PUBKEYS_PER_MULTISIG; -} - -static bool GetMultisigKeyCount(opcodetype opcode, valtype data, int& count) +/** Retrieve a minimally-encoded number in range [min,max] from an (opcode, data) pair, + * whether it's OP_n or through a push. */ +static std::optional<int> GetScriptNumber(opcodetype opcode, valtype data, int min, int max) { + int count; if (IsSmallInteger(opcode)) { count = CScript::DecodeOP_N(opcode); - return IsValidMultisigKeyCount(count); - } - - if (IsPushdataOp(opcode)) { - if (!CheckMinimalPush(data, opcode)) return false; + } else if (IsPushdataOp(opcode)) { + if (!CheckMinimalPush(data, opcode)) return {}; try { count = CScriptNum(data, /* fRequireMinimal = */ true).getint(); - return IsValidMultisigKeyCount(count); } catch (const scriptnum_error&) { - return false; + return {}; } + } else { + return {}; } - - return false; + if (count < min || count > max) return {}; + return count; } static bool MatchMultisig(const CScript& script, int& required_sigs, std::vector<valtype>& pubkeys) { opcodetype opcode; valtype data; - int num_keys; CScript::const_iterator it = script.begin(); if (script.size() < 1 || script.back() != OP_CHECKMULTISIG) return false; - if (!script.GetOp(it, opcode, data) || !GetMultisigKeyCount(opcode, data, required_sigs)) return false; + if (!script.GetOp(it, opcode, data)) return false; + auto req_sigs = GetScriptNumber(opcode, data, 1, MAX_PUBKEYS_PER_MULTISIG); + if (!req_sigs) return false; + required_sigs = *req_sigs; while (script.GetOp(it, opcode, data) && CPubKey::ValidSize(data)) { pubkeys.emplace_back(std::move(data)); } - if (!GetMultisigKeyCount(opcode, data, num_keys)) return false; - - if (pubkeys.size() != static_cast<unsigned long>(num_keys) || num_keys < required_sigs) return false; + auto num_keys = GetScriptNumber(opcode, data, required_sigs, MAX_PUBKEYS_PER_MULTISIG); + if (!num_keys) return false; + if (pubkeys.size() != static_cast<unsigned long>(*num_keys)) return false; return (it + 1 == script.end()); } +std::optional<std::pair<int, std::vector<Span<const unsigned char>>>> MatchMultiA(const CScript& script) +{ + std::vector<Span<const unsigned char>> keyspans; + + // Redundant, but very fast and selective test. + if (script.size() == 0 || script[0] != 32 || script.back() != OP_NUMEQUAL) return {}; + + // Parse keys + auto it = script.begin(); + while (script.end() - it >= 34) { + if (*it != 32) return {}; + ++it; + keyspans.emplace_back(&*it, 32); + it += 32; + if (*it != (keyspans.size() == 1 ? OP_CHECKSIG : OP_CHECKSIGADD)) return {}; + ++it; + } + if (keyspans.size() == 0 || keyspans.size() > MAX_PUBKEYS_PER_MULTI_A) return {}; + + // Parse threshold. + opcodetype opcode; + std::vector<unsigned char> data; + if (!script.GetOp(it, opcode, data)) return {}; + if (it == script.end()) return {}; + if (*it != OP_NUMEQUAL) return {}; + ++it; + if (it != script.end()) return {}; + auto threshold = GetScriptNumber(opcode, data, 1, (int)keyspans.size()); + if (!threshold) return {}; + + // Construct result. + return std::pair{*threshold, std::move(keyspans)}; +} + TxoutType Solver(const CScript& scriptPubKey, std::vector<std::vector<unsigned char>>& vSolutionsRet) { vSolutionsRet.clear(); @@ -348,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; } @@ -366,13 +390,7 @@ void TaprootSpendData::Merge(TaprootSpendData other) merkle_root = other.merkle_root; } for (auto& [key, control_blocks] : other.scripts) { - // Once P0083R3 is supported by all our targeted platforms, - // this loop body can be replaced with: - // scripts[key].merge(std::move(control_blocks)); - auto& target = scripts[key]; - for (auto& control_block: control_blocks) { - target.insert(std::move(control_block)); - } + scripts[key].merge(std::move(control_blocks)); } } @@ -431,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); @@ -464,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; @@ -588,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 @@ -621,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 eb50421768..1e6769782a 100644 --- a/src/script/standard.h +++ b/src/script/standard.h @@ -6,6 +6,7 @@ #ifndef BITCOIN_SCRIPT_STANDARD_H #define BITCOIN_SCRIPT_STANDARD_H +#include <attributes.h> #include <pubkey.h> #include <script/interpreter.h> #include <uint256.h> @@ -32,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. @@ -162,6 +154,11 @@ bool IsValidDestination(const CTxDestination& dest); /** Get the name of a TxoutType as a string */ std::string GetTxnOutputType(TxoutType t); +constexpr bool IsPushdataOp(opcodetype opcode) +{ + return opcode > OP_FALSE && opcode <= OP_PUSHDATA4; +} + /** * Parse a scriptPubKey and identify script type for standard scripts. If * successful, returns script type and parsed pubkeys or hashes, depending on @@ -191,6 +188,10 @@ CScript GetScriptForDestination(const CTxDestination& dest); /** Generate a P2PK script for the given pubkey. */ CScript GetScriptForRawPubKey(const CPubKey& pubkey); +/** Determine if script is a "multi_a" script. Returns (threshold, keyspans) if so, and nullopt otherwise. + * The keyspans refer to bytes in the passed script. */ +std::optional<std::pair<int, std::vector<Span<const unsigned char>>>> MatchMultiA(const CScript& script LIFETIMEBOUND); + /** Generate a multisig script. */ CScript GetScriptForMultisig(int nRequired, const std::vector<CPubKey>& keys); @@ -312,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/.cirrus.yml b/src/secp256k1/.cirrus.yml index 35a9a45367..a2e7f36d1f 100644 --- a/src/secp256k1/.cirrus.yml +++ b/src/secp256k1/.cirrus.yml @@ -4,10 +4,10 @@ env: # Specific warnings can be disabled with -Wno-error=foo. # -pedantic-errors is not equivalent to -Werror=pedantic and thus not implied by -Werror according to the GCC manual. WERROR_CFLAGS: -Werror -pedantic-errors - MAKEFLAGS: -j2 + MAKEFLAGS: -j4 BUILD: check ### secp256k1 config - STATICPRECOMPUTATION: yes + ECMULTWINDOW: auto ECMULTGENPRECISION: auto ASM: no WIDEMUL: auto @@ -23,6 +23,8 @@ env: BENCH: yes SECP256K1_BENCH_ITERS: 2 CTIMETEST: yes + # Compile and run the tests + EXAMPLES: yes cat_logs_snippet: &CAT_LOGS always: @@ -50,28 +52,32 @@ merge_base_script_snippet: &MERGE_BASE - git config --global user.name "ci" - git merge FETCH_HEAD # Merge base to detect silent merge conflicts -task: - name: "x86_64: Linux (Debian stable)" +linux_container_snippet: &LINUX_CONTAINER container: dockerfile: ci/linux-debian.Dockerfile # Reduce number of CPUs to be able to do more builds in parallel. cpu: 1 + # Gives us more CPUs for free if they're available. + greedy: true # More than enough for our scripts. memory: 1G + +task: + name: "x86_64: Linux (Debian stable)" + << : *LINUX_CONTAINER matrix: &ENV_MATRIX - env: {WIDEMUL: int64, RECOVERY: yes} - - env: {WIDEMUL: int64, ECDH: yes, EXPERIMENTAL: yes, SCHNORRSIG: yes} + - env: {WIDEMUL: int64, ECDH: yes, SCHNORRSIG: yes} - env: {WIDEMUL: int128} - - env: {WIDEMUL: int128, RECOVERY: yes, EXPERIMENTAL: yes, SCHNORRSIG: yes} - - env: {WIDEMUL: int128, ECDH: yes, EXPERIMENTAL: yes, SCHNORRSIG: yes} + - env: {WIDEMUL: int128, RECOVERY: yes, SCHNORRSIG: yes} + - env: {WIDEMUL: int128, ECDH: yes, SCHNORRSIG: yes} - env: {WIDEMUL: int128, ASM: x86_64} - - env: { RECOVERY: yes, EXPERIMENTAL: yes, SCHNORRSIG: yes} - - env: { STATICPRECOMPUTATION: no} + - env: { RECOVERY: yes, SCHNORRSIG: yes} - env: {BUILD: distcheck, WITH_VALGRIND: no, CTIMETEST: no, BENCH: no} - env: {CPPFLAGS: -DDETERMINISTIC} - env: {CFLAGS: -O0, CTIMETEST: no} - - env: { ECMULTGENPRECISION: 2 } - - env: { ECMULTGENPRECISION: 8 } + - env: { ECMULTGENPRECISION: 2, ECMULTWINDOW: 2 } + - env: { ECMULTGENPRECISION: 8, ECMULTWINDOW: 4 } matrix: - env: CC: gcc @@ -84,15 +90,11 @@ task: task: name: "i686: Linux (Debian stable)" - container: - dockerfile: ci/linux-debian.Dockerfile - cpu: 1 - memory: 1G + << : *LINUX_CONTAINER env: HOST: i686-linux-gnu ECDH: yes RECOVERY: yes - EXPERIMENTAL: yes SCHNORRSIG: yes matrix: - env: @@ -134,8 +136,10 @@ task: ## - rm /tmp/.com.apple.dt.CommandLineTools.installondemand.in-progress ## brew_valgrind_pre_script: + # Retry a few times because this tends to fail randomly. + - for i in {1..5}; do brew update && break || sleep 15; done - brew config - - brew tap --shallow LouisBrunner/valgrind + - brew tap LouisBrunner/valgrind # Fetch valgrind source but don't build it yet. - brew fetch --HEAD LouisBrunner/valgrind/valgrind brew_valgrind_cache: @@ -165,10 +169,7 @@ task: task: name: "s390x (big-endian): Linux (Debian stable, QEMU)" - container: - dockerfile: ci/linux-debian.Dockerfile - cpu: 1 - memory: 1G + << : *LINUX_CONTAINER env: WRAPPER_CMD: qemu-s390x SECP256K1_TEST_ITERS: 16 @@ -176,7 +177,6 @@ task: WITH_VALGRIND: no ECDH: yes RECOVERY: yes - EXPERIMENTAL: yes SCHNORRSIG: yes CTIMETEST: no << : *MERGE_BASE @@ -188,10 +188,7 @@ task: task: name: "ARM32: Linux (Debian stable, QEMU)" - container: - dockerfile: ci/linux-debian.Dockerfile - cpu: 1 - memory: 1G + << : *LINUX_CONTAINER env: WRAPPER_CMD: qemu-arm SECP256K1_TEST_ITERS: 16 @@ -199,12 +196,11 @@ task: WITH_VALGRIND: no ECDH: yes RECOVERY: yes - EXPERIMENTAL: yes SCHNORRSIG: yes CTIMETEST: no matrix: - env: {} - - env: {ASM: arm} + - env: {EXPERIMENTAL: yes, ASM: arm} << : *MERGE_BASE test_script: - ./ci/cirrus.sh @@ -212,10 +208,7 @@ task: task: name: "ARM64: Linux (Debian stable, QEMU)" - container: - dockerfile: ci/linux-debian.Dockerfile - cpu: 1 - memory: 1G + << : *LINUX_CONTAINER env: WRAPPER_CMD: qemu-aarch64 SECP256K1_TEST_ITERS: 16 @@ -223,7 +216,6 @@ task: WITH_VALGRIND: no ECDH: yes RECOVERY: yes - EXPERIMENTAL: yes SCHNORRSIG: yes CTIMETEST: no << : *MERGE_BASE @@ -233,10 +225,7 @@ task: task: name: "ppc64le: Linux (Debian stable, QEMU)" - container: - dockerfile: ci/linux-debian.Dockerfile - cpu: 1 - memory: 1G + << : *LINUX_CONTAINER env: WRAPPER_CMD: qemu-ppc64le SECP256K1_TEST_ITERS: 16 @@ -244,7 +233,6 @@ task: WITH_VALGRIND: no ECDH: yes RECOVERY: yes - EXPERIMENTAL: yes SCHNORRSIG: yes CTIMETEST: no << : *MERGE_BASE @@ -254,10 +242,7 @@ task: task: name: "x86_64 (mingw32-w64): Windows (Debian stable, Wine)" - container: - dockerfile: ci/linux-debian.Dockerfile - cpu: 1 - memory: 1G + << : *LINUX_CONTAINER env: WRAPPER_CMD: wine64-stable SECP256K1_TEST_ITERS: 16 @@ -265,7 +250,6 @@ task: WITH_VALGRIND: no ECDH: yes RECOVERY: yes - EXPERIMENTAL: yes SCHNORRSIG: yes CTIMETEST: no << : *MERGE_BASE @@ -275,23 +259,23 @@ task: # Sanitizers task: - container: - dockerfile: ci/linux-debian.Dockerfile - cpu: 1 - memory: 2G + << : *LINUX_CONTAINER env: ECDH: yes RECOVERY: yes - EXPERIMENTAL: yes SCHNORRSIG: yes CTIMETEST: no matrix: - name: "Valgrind (memcheck)" + container: + cpu: 2 env: # The `--error-exitcode` is required to make the test fail if valgrind found errors, otherwise it'll return 0 (https://www.valgrind.org/docs/manual/manual-core.html) WRAPPER_CMD: "valgrind --error-exitcode=42" SECP256K1_TEST_ITERS: 2 - name: "UBSan, ASan, LSan" + container: + memory: 2G env: CFLAGS: "-fsanitize=undefined,address -g" UBSAN_OPTIONS: "print_stacktrace=1:halt_on_error=1" @@ -302,11 +286,10 @@ task: matrix: - env: ASM: auto - STATICPRECOMPUTATION: yes - env: ASM: no - STATICPRECOMPUTATION: no ECMULTGENPRECISION: 2 + ECMULTWINDOW: 2 matrix: - env: CC: clang @@ -320,17 +303,13 @@ task: task: name: "C++ -fpermissive" - container: - dockerfile: ci/linux-debian.Dockerfile - cpu: 1 - memory: 1G + << : *LINUX_CONTAINER env: # ./configure correctly errors out when given CC=g++. # We hack around this by passing CC=g++ only to make. CC: gcc - MAKEFLAGS: -j2 CC=g++ CFLAGS=-fpermissive\ -g + MAKEFLAGS: -j4 CC=g++ CFLAGS=-fpermissive\ -g WERROR_CFLAGS: - EXPERIMENTAL: yes ECDH: yes RECOVERY: yes SCHNORRSIG: yes @@ -338,3 +317,10 @@ task: test_script: - ./ci/cirrus.sh << : *CAT_LOGS + +task: + name: "sage prover" + << : *LINUX_CONTAINER + test_script: + - cd sage + - sage prove_group_implementations.sage diff --git a/src/secp256k1/.gitattributes b/src/secp256k1/.gitattributes index a0fa567da8..30efb2244f 100644 --- a/src/secp256k1/.gitattributes +++ b/src/secp256k1/.gitattributes @@ -1,2 +1,2 @@ -src/ecmult_static_pre_g.h linguist-generated -src/ecmult_gen_static_prec_table.h linguist-generated +src/precomputed_ecmult.c linguist-generated +src/precomputed_ecmult_gen.c linguist-generated diff --git a/src/secp256k1/.gitignore b/src/secp256k1/.gitignore index 22cd500501..d88627d72e 100644 --- a/src/secp256k1/.gitignore +++ b/src/secp256k1/.gitignore @@ -3,14 +3,19 @@ bench_ecmult bench_internal tests exhaustive_tests -gen_ecmult_gen_static_prec_table -gen_ecmult_static_pre_g +precompute_ecmult_gen +precompute_ecmult valgrind_ctime_test +ecdh_example +ecdsa_example +schnorr_example *.exe *.so *.a *.csv !.gitignore +*.log +*.trs Makefile configure @@ -41,6 +46,7 @@ coverage.*.html src/libsecp256k1-config.h src/libsecp256k1-config.h.in +build-aux/ar-lib build-aux/config.guess build-aux/config.sub build-aux/depcomp diff --git a/src/secp256k1/Makefile.am b/src/secp256k1/Makefile.am index 7ea29bc6e3..51c5960301 100644 --- a/src/secp256k1/Makefile.am +++ b/src/secp256k1/Makefile.am @@ -26,12 +26,14 @@ noinst_HEADERS += src/eckey.h noinst_HEADERS += src/eckey_impl.h noinst_HEADERS += src/ecmult.h noinst_HEADERS += src/ecmult_impl.h +noinst_HEADERS += src/ecmult_compute_table.h +noinst_HEADERS += src/ecmult_compute_table_impl.h noinst_HEADERS += src/ecmult_const.h noinst_HEADERS += src/ecmult_const_impl.h noinst_HEADERS += src/ecmult_gen.h noinst_HEADERS += src/ecmult_gen_impl.h -noinst_HEADERS += src/ecmult_gen_prec.h -noinst_HEADERS += src/ecmult_gen_prec_impl.h +noinst_HEADERS += src/ecmult_gen_compute_table.h +noinst_HEADERS += src/ecmult_gen_compute_table_impl.h noinst_HEADERS += src/field_10x26.h noinst_HEADERS += src/field_10x26_impl.h noinst_HEADERS += src/field_5x52.h @@ -42,6 +44,8 @@ noinst_HEADERS += src/modinv32.h noinst_HEADERS += src/modinv32_impl.h noinst_HEADERS += src/modinv64.h noinst_HEADERS += src/modinv64_impl.h +noinst_HEADERS += src/precomputed_ecmult.h +noinst_HEADERS += src/precomputed_ecmult_gen.h noinst_HEADERS += src/assumptions.h noinst_HEADERS += src/util.h noinst_HEADERS += src/scratch.h @@ -59,13 +63,19 @@ noinst_HEADERS += contrib/lax_der_parsing.h noinst_HEADERS += contrib/lax_der_parsing.c noinst_HEADERS += contrib/lax_der_privatekey_parsing.h noinst_HEADERS += contrib/lax_der_privatekey_parsing.c +noinst_HEADERS += examples/random.h + +PRECOMPUTED_LIB = libsecp256k1_precomputed.la +noinst_LTLIBRARIES = $(PRECOMPUTED_LIB) +libsecp256k1_precomputed_la_SOURCES = src/precomputed_ecmult.c src/precomputed_ecmult_gen.c +libsecp256k1_precomputed_la_CPPFLAGS = $(SECP_INCLUDES) if USE_EXTERNAL_ASM COMMON_LIB = libsecp256k1_common.la -noinst_LTLIBRARIES = $(COMMON_LIB) else COMMON_LIB = endif +noinst_LTLIBRARIES += $(COMMON_LIB) pkgconfigdir = $(libdir)/pkgconfig pkgconfig_DATA = libsecp256k1.pc @@ -78,8 +88,8 @@ endif libsecp256k1_la_SOURCES = src/secp256k1.c libsecp256k1_la_CPPFLAGS = -I$(top_srcdir)/include -I$(top_srcdir)/src $(SECP_INCLUDES) -libsecp256k1_la_LIBADD = $(SECP_LIBS) $(COMMON_LIB) -libsecp256k1_la_LDFLAGS = -no-undefined +libsecp256k1_la_LIBADD = $(SECP_LIBS) $(COMMON_LIB) $(PRECOMPUTED_LIB) +libsecp256k1_la_LDFLAGS = -no-undefined -version-info $(LIB_VERSION_CURRENT):$(LIB_VERSION_REVISION):$(LIB_VERSION_AGE) if VALGRIND_ENABLED libsecp256k1_la_CPPFLAGS += -DVALGRIND @@ -91,10 +101,10 @@ noinst_PROGRAMS += bench bench_internal bench_ecmult bench_SOURCES = src/bench.c bench_LDADD = libsecp256k1.la $(SECP_LIBS) $(SECP_TEST_LIBS) $(COMMON_LIB) bench_internal_SOURCES = src/bench_internal.c -bench_internal_LDADD = $(SECP_LIBS) $(COMMON_LIB) +bench_internal_LDADD = $(SECP_LIBS) $(COMMON_LIB) $(PRECOMPUTED_LIB) bench_internal_CPPFLAGS = $(SECP_INCLUDES) bench_ecmult_SOURCES = src/bench_ecmult.c -bench_ecmult_LDADD = $(SECP_LIBS) $(COMMON_LIB) +bench_ecmult_LDADD = $(SECP_LIBS) $(COMMON_LIB) $(PRECOMPUTED_LIB) bench_ecmult_CPPFLAGS = $(SECP_INCLUDES) endif @@ -112,7 +122,7 @@ endif if !ENABLE_COVERAGE tests_CPPFLAGS += -DVERIFY endif -tests_LDADD = $(SECP_LIBS) $(SECP_TEST_LIBS) $(COMMON_LIB) +tests_LDADD = $(SECP_LIBS) $(SECP_TEST_LIBS) $(COMMON_LIB) $(PRECOMPUTED_LIB) tests_LDFLAGS = -static TESTS += tests endif @@ -124,22 +134,57 @@ exhaustive_tests_CPPFLAGS = $(SECP_INCLUDES) if !ENABLE_COVERAGE exhaustive_tests_CPPFLAGS += -DVERIFY endif +# Note: do not include $(PRECOMPUTED_LIB) in exhaustive_tests (it uses runtime-generated tables). exhaustive_tests_LDADD = $(SECP_LIBS) $(COMMON_LIB) exhaustive_tests_LDFLAGS = -static TESTS += exhaustive_tests endif +if USE_EXAMPLES +noinst_PROGRAMS += ecdsa_example +ecdsa_example_SOURCES = examples/ecdsa.c +ecdsa_example_CPPFLAGS = -I$(top_srcdir)/include +ecdsa_example_LDADD = libsecp256k1.la +ecdsa_example_LDFLAGS = -static +if BUILD_WINDOWS +ecdsa_example_LDFLAGS += -lbcrypt +endif +TESTS += ecdsa_example +if ENABLE_MODULE_ECDH +noinst_PROGRAMS += ecdh_example +ecdh_example_SOURCES = examples/ecdh.c +ecdh_example_CPPFLAGS = -I$(top_srcdir)/include +ecdh_example_LDADD = libsecp256k1.la +ecdh_example_LDFLAGS = -static +if BUILD_WINDOWS +ecdh_example_LDFLAGS += -lbcrypt +endif +TESTS += ecdh_example +endif +if ENABLE_MODULE_SCHNORRSIG +noinst_PROGRAMS += schnorr_example +schnorr_example_SOURCES = examples/schnorr.c +schnorr_example_CPPFLAGS = -I$(top_srcdir)/include +schnorr_example_LDADD = libsecp256k1.la +schnorr_example_LDFLAGS = -static +if BUILD_WINDOWS +schnorr_example_LDFLAGS += -lbcrypt +endif +TESTS += schnorr_example +endif +endif + ### Precomputed tables -EXTRA_PROGRAMS = gen_ecmult_static_pre_g gen_ecmult_gen_static_prec_table +EXTRA_PROGRAMS = precompute_ecmult precompute_ecmult_gen CLEANFILES = $(EXTRA_PROGRAMS) -gen_ecmult_static_pre_g_SOURCES = src/gen_ecmult_static_pre_g.c -gen_ecmult_static_pre_g_CPPFLAGS = $(SECP_INCLUDES) -gen_ecmult_static_pre_g_LDADD = $(SECP_LIBS) $(COMMON_LIB) +precompute_ecmult_SOURCES = src/precompute_ecmult.c +precompute_ecmult_CPPFLAGS = $(SECP_INCLUDES) +precompute_ecmult_LDADD = $(SECP_LIBS) $(COMMON_LIB) -gen_ecmult_gen_static_prec_table_SOURCES = src/gen_ecmult_gen_static_prec_table.c -gen_ecmult_gen_static_prec_table_CPPFLAGS = $(SECP_INCLUDES) -gen_ecmult_gen_static_prec_table_LDADD = $(SECP_LIBS) $(COMMON_LIB) +precompute_ecmult_gen_SOURCES = src/precompute_ecmult_gen.c +precompute_ecmult_gen_CPPFLAGS = $(SECP_INCLUDES) +precompute_ecmult_gen_LDADD = $(SECP_LIBS) $(COMMON_LIB) # See Automake manual, Section "Errors with distclean". # We don't list any dependencies for the prebuilt files here because @@ -147,15 +192,14 @@ gen_ecmult_gen_static_prec_table_LDADD = $(SECP_LIBS) $(COMMON_LIB) # build by a normal user) depends on mtimes, and thus is very fragile. # This means that rebuilds of the prebuilt files always need to be # forced by deleting them, e.g., by invoking `make clean-precomp`. -src/ecmult_static_pre_g.h: - $(MAKE) $(AM_MAKEFLAGS) gen_ecmult_static_pre_g$(EXEEXT) - ./gen_ecmult_static_pre_g$(EXEEXT) -src/ecmult_gen_static_prec_table.h: - $(MAKE) $(AM_MAKEFLAGS) gen_ecmult_gen_static_prec_table$(EXEEXT) - ./gen_ecmult_gen_static_prec_table$(EXEEXT) - -PRECOMP = src/ecmult_gen_static_prec_table.h src/ecmult_static_pre_g.h -noinst_HEADERS += $(PRECOMP) +src/precomputed_ecmult.c: + $(MAKE) $(AM_MAKEFLAGS) precompute_ecmult$(EXEEXT) + ./precompute_ecmult$(EXEEXT) +src/precomputed_ecmult_gen.c: + $(MAKE) $(AM_MAKEFLAGS) precompute_ecmult_gen$(EXEEXT) + ./precompute_ecmult_gen$(EXEEXT) + +PRECOMP = src/precomputed_ecmult_gen.c src/precomputed_ecmult.c precomp: $(PRECOMP) # Ensure the prebuilt files will be build first (only if they don't exist, diff --git a/src/secp256k1/README.md b/src/secp256k1/README.md index 5fc07dd4fa..f5db915e83 100644 --- a/src/secp256k1/README.md +++ b/src/secp256k1/README.md @@ -17,9 +17,7 @@ Features: * Suitable for embedded systems. * Optional module for public key recovery. * Optional module for ECDH key exchange. -* Optional module for Schnorr signatures according to [BIP-340](https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki) (experimental). - -Experimental features have not received enough scrutiny to satisfy the standard of quality of this library but are made available for testing and review by the community. The APIs of these features should not be considered stable. +* Optional module for Schnorr signatures according to [BIP-340](https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki). Implementation details ---------------------- @@ -35,6 +33,7 @@ Implementation details * Optimized implementation of arithmetic modulo the curve's field size (2^256 - 0x1000003D1). * Using 5 52-bit limbs (including hand-optimized assembly for x86_64, by Diederik Huys). * Using 10 26-bit limbs (including hand-optimized assembly for 32-bit ARM, by Wladimir J. van der Laan). + * This is an experimental feature that has not received enough scrutiny to satisfy the standard of quality of this library but is made available for testing and review by the community. * Scalar operations * Optimized implementation without data-dependent branches of arithmetic modulo the curve's order. * Using 4 64-bit limbs (relying on __int128 support in the compiler). @@ -69,6 +68,16 @@ libsecp256k1 is built using autotools: $ make check # run the test suite $ sudo make install # optional +To compile optional modules (such as Schnorr signatures), you need to run `./configure` with additional flags (such as `--enable-module-schnorrsig`). Run `./configure --help` to see the full list of available flags. + +Usage examples +----------- + Usage examples can be found in the [examples](examples) directory. To compile them you need to configure with `--enable-examples`. + * [ECDSA example](examples/ecdsa.c) + * [Schnorr signatures example](examples/schnorr.c) + * [Deriving a shared secret (ECDH) example](examples/ecdh.c) + To compile the Schnorr signature and ECDH examples, you also need to configure with `--enable-module-schnorrsig` and `--enable-module-ecdh`. + Test coverage ----------- diff --git a/src/secp256k1/build-aux/m4/bitcoin_secp.m4 b/src/secp256k1/build-aux/m4/bitcoin_secp.m4 index c14d09fa1b..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"); @@ -38,3 +38,16 @@ AC_DEFUN([SECP_TRY_APPEND_CFLAGS], [ unset flag_works AC_SUBST($2) ]) + +dnl SECP_SET_DEFAULT(VAR, default, default-dev-mode) +dnl Set VAR to default or default-dev-mode, depending on whether dev mode is enabled +AC_DEFUN([SECP_SET_DEFAULT], [ + if test "${enable_dev_mode+set}" != set; then + AC_MSG_ERROR([[Set enable_dev_mode before calling SECP_SET_DEFAULT]]) + fi + if test x"$enable_dev_mode" = x"yes"; then + $1="$3" + else + $1="$2" + fi +]) diff --git a/src/secp256k1/ci/cirrus.sh b/src/secp256k1/ci/cirrus.sh index e27b34782e..b85f012d3f 100755 --- a/src/secp256k1/ci/cirrus.sh +++ b/src/secp256k1/ci/cirrus.sh @@ -15,9 +15,11 @@ valgrind --version || true ./configure \ --enable-experimental="$EXPERIMENTAL" \ --with-test-override-wide-multiply="$WIDEMUL" --with-asm="$ASM" \ - --enable-ecmult-static-precomputation="$STATICPRECOMPUTATION" --with-ecmult-gen-precision="$ECMULTGENPRECISION" \ + --with-ecmult-window="$ECMULTWINDOW" \ + --with-ecmult-gen-precision="$ECMULTGENPRECISION" \ --enable-module-ecdh="$ECDH" --enable-module-recovery="$RECOVERY" \ --enable-module-schnorrsig="$SCHNORRSIG" \ + --enable-examples="$EXAMPLES" \ --with-valgrind="$WITH_VALGRIND" \ --host="$HOST" $EXTRAFLAGS diff --git a/src/secp256k1/ci/linux-debian.Dockerfile b/src/secp256k1/ci/linux-debian.Dockerfile index fdba12aa00..5cccbb5565 100644 --- a/src/secp256k1/ci/linux-debian.Dockerfile +++ b/src/secp256k1/ci/linux-debian.Dockerfile @@ -19,7 +19,8 @@ RUN apt-get install --no-install-recommends --no-upgrade -y \ gcc-arm-linux-gnueabihf libc6-dev-armhf-cross libc6-dbg:armhf \ gcc-aarch64-linux-gnu libc6-dev-arm64-cross libc6-dbg:arm64 \ gcc-powerpc64le-linux-gnu libc6-dev-ppc64el-cross libc6-dbg:ppc64el \ - wine gcc-mingw-w64-x86-64 + wine gcc-mingw-w64-x86-64 \ + sagemath # Run a dummy command in wine to make it set up configuration RUN wine64-stable xcopy || true diff --git a/src/secp256k1/configure.ac b/src/secp256k1/configure.ac index 94feea7bb7..2db59a8ff3 100644 --- a/src/secp256k1/configure.ac +++ b/src/secp256k1/configure.ac @@ -1,30 +1,47 @@ AC_PREREQ([2.60]) -AC_INIT([libsecp256k1],[0.1]) + +# The package (a.k.a. release) version is based on semantic versioning 2.0.0 of +# the API. All changes in experimental modules are treated as +# backwards-compatible and therefore at most increase the minor version. +define(_PKG_VERSION_MAJOR, 0) +define(_PKG_VERSION_MINOR, 1) +define(_PKG_VERSION_BUILD, 0) +define(_PKG_VERSION_IS_RELEASE, false) + +# The library version is based on libtool versioning of the ABI. The set of +# rules for updating the version can be found here: +# https://www.gnu.org/software/libtool/manual/html_node/Updating-version-info.html +# All changes in experimental modules are treated as if they don't affect the +# interface and therefore only increase the revision. +define(_LIB_VERSION_CURRENT, 0) +define(_LIB_VERSION_REVISION, 0) +define(_LIB_VERSION_AGE, 0) + +AC_INIT([libsecp256k1],m4_join([.], _PKG_VERSION_MAJOR, _PKG_VERSION_MINOR, _PKG_VERSION_BUILD)m4_if(_PKG_VERSION_IS_RELEASE, [true], [], [-pre]),[https://github.com/bitcoin-core/secp256k1/issues],[libsecp256k1],[https://github.com/bitcoin-core/secp256k1]) + AC_CONFIG_AUX_DIR([build-aux]) AC_CONFIG_MACRO_DIR([build-aux/m4]) AC_CANONICAL_HOST AH_TOP([#ifndef LIBSECP256K1_CONFIG_H]) AH_TOP([#define LIBSECP256K1_CONFIG_H]) AH_BOTTOM([#endif /*LIBSECP256K1_CONFIG_H*/]) -AM_INIT_AUTOMAKE([foreign subdir-objects]) -LT_INIT([win32-dll]) +# Require Automake 1.11.2 for AM_PROG_AR +AM_INIT_AUTOMAKE([1.11.2 foreign subdir-objects]) # Make the compilation flags quiet unless V=1 is used. m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])]) -PKG_PROG_PKG_CONFIG - -AC_PATH_TOOL(AR, ar) -AC_PATH_TOOL(RANLIB, ranlib) -AC_PATH_TOOL(STRIP, strip) - -AM_PROG_CC_C_O -AC_PROG_CC_C89 +AC_PROG_CC if test x"$ac_cv_prog_cc_c89" = x"no"; then AC_MSG_ERROR([c89 compiler support required]) fi AM_PROG_AS +AM_PROG_AR + +LT_INIT([win32-dll]) + +build_windows=no case $host_os in *darwin*) @@ -49,6 +66,9 @@ case $host_os in fi fi ;; + cygwin*|mingw*) + build_windows=yes + ;; esac # Try if some desirable compiler flags are supported and append them to SECP_CFLAGS. @@ -91,55 +111,54 @@ SECP_TRY_APPEND_DEFAULT_CFLAGS(SECP_CFLAGS) ### Define config arguments ### +# In dev mode, we enable all binaries and modules by default but individual options can still be overridden explicitly. +# Check for dev mode first because SECP_SET_DEFAULT needs enable_dev_mode set. +AC_ARG_ENABLE(dev_mode, [], [], + [enable_dev_mode=no]) + AC_ARG_ENABLE(benchmark, - AS_HELP_STRING([--enable-benchmark],[compile benchmark [default=yes]]), - [use_benchmark=$enableval], - [use_benchmark=yes]) + AS_HELP_STRING([--enable-benchmark],[compile benchmark [default=yes]]), [], + [SECP_SET_DEFAULT([enable_benchmark], [yes], [yes])]) AC_ARG_ENABLE(coverage, - AS_HELP_STRING([--enable-coverage],[enable compiler flags to support kcov coverage analysis [default=no]]), - [enable_coverage=$enableval], - [enable_coverage=no]) + AS_HELP_STRING([--enable-coverage],[enable compiler flags to support kcov coverage analysis [default=no]]), [], + [SECP_SET_DEFAULT([enable_coverage], [no], [no])]) AC_ARG_ENABLE(tests, - AS_HELP_STRING([--enable-tests],[compile tests [default=yes]]), - [use_tests=$enableval], - [use_tests=yes]) + AS_HELP_STRING([--enable-tests],[compile tests [default=yes]]), [], + [SECP_SET_DEFAULT([enable_tests], [yes], [yes])]) AC_ARG_ENABLE(experimental, - AS_HELP_STRING([--enable-experimental],[allow experimental configure options [default=no]]), - [use_experimental=$enableval], - [use_experimental=no]) + AS_HELP_STRING([--enable-experimental],[allow experimental configure options [default=no]]), [], + [SECP_SET_DEFAULT([enable_experimental], [no], [yes])]) AC_ARG_ENABLE(exhaustive_tests, - AS_HELP_STRING([--enable-exhaustive-tests],[compile exhaustive tests [default=yes]]), - [use_exhaustive_tests=$enableval], - [use_exhaustive_tests=yes]) + AS_HELP_STRING([--enable-exhaustive-tests],[compile exhaustive tests [default=yes]]), [], + [SECP_SET_DEFAULT([enable_exhaustive_tests], [yes], [yes])]) + +AC_ARG_ENABLE(examples, + AS_HELP_STRING([--enable-examples],[compile the examples [default=no]]), [], + [SECP_SET_DEFAULT([enable_examples], [no], [yes])]) AC_ARG_ENABLE(module_ecdh, - AS_HELP_STRING([--enable-module-ecdh],[enable ECDH shared secret computation]), - [enable_module_ecdh=$enableval], - [enable_module_ecdh=no]) + AS_HELP_STRING([--enable-module-ecdh],[enable ECDH module [default=no]]), [], + [SECP_SET_DEFAULT([enable_module_ecdh], [no], [yes])]) AC_ARG_ENABLE(module_recovery, - AS_HELP_STRING([--enable-module-recovery],[enable ECDSA pubkey recovery module [default=no]]), - [enable_module_recovery=$enableval], - [enable_module_recovery=no]) + AS_HELP_STRING([--enable-module-recovery],[enable ECDSA pubkey recovery module [default=no]]), [], + [SECP_SET_DEFAULT([enable_module_recovery], [no], [yes])]) AC_ARG_ENABLE(module_extrakeys, - AS_HELP_STRING([--enable-module-extrakeys],[enable extrakeys module (experimental)]), - [enable_module_extrakeys=$enableval], - [enable_module_extrakeys=no]) + AS_HELP_STRING([--enable-module-extrakeys],[enable extrakeys module [default=no]]), [], + [SECP_SET_DEFAULT([enable_module_extrakeys], [no], [yes])]) AC_ARG_ENABLE(module_schnorrsig, - AS_HELP_STRING([--enable-module-schnorrsig],[enable schnorrsig module (experimental)]), - [enable_module_schnorrsig=$enableval], - [enable_module_schnorrsig=no]) + AS_HELP_STRING([--enable-module-schnorrsig],[enable schnorrsig module [default=no]]), [], + [SECP_SET_DEFAULT([enable_module_schnorrsig], [no], [yes])]) AC_ARG_ENABLE(external_default_callbacks, - AS_HELP_STRING([--enable-external-default-callbacks],[enable external default callback functions [default=no]]), - [use_external_default_callbacks=$enableval], - [use_external_default_callbacks=no]) + AS_HELP_STRING([--enable-external-default-callbacks],[enable external default callback functions [default=no]]), [], + [SECP_SET_DEFAULT([enable_external_default_callbacks], [no], [no])]) # Test-only override of the (autodetected by the C code) "widemul" setting. # Legal values are int64 (for [u]int64_t), int128 (for [unsigned] __int128), and auto (the default). @@ -152,7 +171,7 @@ AC_ARG_WITH([ecmult-window], [AS_HELP_STRING([--with-ecmult-window=SIZE|auto], [window size for ecmult precomputation for verification, specified as integer in range [2..24].] [Larger values result in possibly better performance at the cost of an exponentially larger precomputed table.] [The table will store 2^(SIZE-1) * 64 bytes of data but can be larger in memory due to platform-specific padding and alignment.] -[A window size larger than 15 will require you delete the prebuilt ecmult_static_pre_g.h file so that it can be rebuilt.] +[A window size larger than 15 will require you delete the prebuilt precomputed_ecmult.c file so that it can be rebuilt.] [For very large window sizes, use "make -j 1" to reduce memory use during compilation.] ["auto" is a reasonable setting for desktop machines (currently 15). [default=auto]] )], @@ -229,14 +248,14 @@ else fi # Select assembly optimization -use_external_asm=no +enable_external_asm=no case $set_asm in x86_64) AC_DEFINE(USE_ASM_X86_64, 1, [Define this symbol to enable x86_64 assembly optimizations]) ;; arm) - use_external_asm=yes + enable_external_asm=yes ;; no) ;; @@ -245,7 +264,7 @@ no) ;; esac -if test x"$use_external_asm" = x"yes"; then +if test x"$enable_external_asm" = x"yes"; then AC_DEFINE(USE_EXTERNAL_ASM, 1, [Define this symbol if an external (non-inline) assembly implementation is used]) fi @@ -333,7 +352,7 @@ if test x"$enable_module_extrakeys" = x"yes"; then AC_DEFINE(ENABLE_MODULE_EXTRAKEYS, 1, [Define this symbol to enable the extrakeys module]) fi -if test x"$use_external_default_callbacks" = x"yes"; then +if test x"$enable_external_default_callbacks" = x"yes"; then AC_DEFINE(USE_EXTERNAL_DEFAULT_CALLBACKS, 1, [Define this symbol if an external implementation of the default callbacks is used]) fi @@ -345,16 +364,8 @@ if test x"$enable_experimental" = x"yes"; then AC_MSG_NOTICE([******]) AC_MSG_NOTICE([WARNING: experimental build]) AC_MSG_NOTICE([Experimental features do not have stable APIs or properties, and may not be safe for production use.]) - AC_MSG_NOTICE([Building extrakeys module: $enable_module_extrakeys]) - AC_MSG_NOTICE([Building schnorrsig module: $enable_module_schnorrsig]) AC_MSG_NOTICE([******]) else - if test x"$enable_module_extrakeys" = x"yes"; then - AC_MSG_ERROR([extrakeys module is experimental. Use --enable-experimental to allow.]) - fi - if test x"$enable_module_schnorrsig" = x"yes"; then - AC_MSG_ERROR([schnorrsig module is experimental. Use --enable-experimental to allow.]) - fi if test x"$set_asm" = x"arm"; then AC_MSG_ERROR([ARM assembly optimization is experimental. Use --enable-experimental to allow.]) fi @@ -372,29 +383,30 @@ AC_SUBST(SECP_TEST_LIBS) AC_SUBST(SECP_TEST_INCLUDES) AC_SUBST(SECP_CFLAGS) AM_CONDITIONAL([ENABLE_COVERAGE], [test x"$enable_coverage" = x"yes"]) -AM_CONDITIONAL([USE_TESTS], [test x"$use_tests" != x"no"]) -AM_CONDITIONAL([USE_EXHAUSTIVE_TESTS], [test x"$use_exhaustive_tests" != x"no"]) -AM_CONDITIONAL([USE_BENCHMARK], [test x"$use_benchmark" = x"yes"]) +AM_CONDITIONAL([USE_TESTS], [test x"$enable_tests" != x"no"]) +AM_CONDITIONAL([USE_EXHAUSTIVE_TESTS], [test x"$enable_exhaustive_tests" != x"no"]) +AM_CONDITIONAL([USE_EXAMPLES], [test x"$enable_examples" != x"no"]) +AM_CONDITIONAL([USE_BENCHMARK], [test x"$enable_benchmark" = x"yes"]) AM_CONDITIONAL([ENABLE_MODULE_ECDH], [test x"$enable_module_ecdh" = x"yes"]) AM_CONDITIONAL([ENABLE_MODULE_RECOVERY], [test x"$enable_module_recovery" = x"yes"]) AM_CONDITIONAL([ENABLE_MODULE_EXTRAKEYS], [test x"$enable_module_extrakeys" = x"yes"]) AM_CONDITIONAL([ENABLE_MODULE_SCHNORRSIG], [test x"$enable_module_schnorrsig" = x"yes"]) -AM_CONDITIONAL([USE_EXTERNAL_ASM], [test x"$use_external_asm" = x"yes"]) +AM_CONDITIONAL([USE_EXTERNAL_ASM], [test x"$enable_external_asm" = x"yes"]) AM_CONDITIONAL([USE_ASM_ARM], [test x"$set_asm" = x"arm"]) - -# Make sure nothing new is exported so that we don't break the cache. -PKGCONFIG_PATH_TEMP="$PKG_CONFIG_PATH" -unset PKG_CONFIG_PATH -PKG_CONFIG_PATH="$PKGCONFIG_PATH_TEMP" +AM_CONDITIONAL([BUILD_WINDOWS], [test "$build_windows" = "yes"]) +AC_SUBST(LIB_VERSION_CURRENT, _LIB_VERSION_CURRENT) +AC_SUBST(LIB_VERSION_REVISION, _LIB_VERSION_REVISION) +AC_SUBST(LIB_VERSION_AGE, _LIB_VERSION_AGE) AC_OUTPUT echo echo "Build Options:" -echo " with external callbacks = $use_external_default_callbacks" -echo " with benchmarks = $use_benchmark" -echo " with tests = $use_tests" +echo " with external callbacks = $enable_external_default_callbacks" +echo " with benchmarks = $enable_benchmark" +echo " with tests = $enable_tests" echo " with coverage = $enable_coverage" +echo " with examples = $enable_examples" echo " module ecdh = $enable_module_ecdh" echo " module recovery = $enable_module_recovery" echo " module extrakeys = $enable_module_extrakeys" diff --git a/src/secp256k1/doc/CHANGELOG.md b/src/secp256k1/doc/CHANGELOG.md new file mode 100644 index 0000000000..3c4c2e4583 --- /dev/null +++ b/src/secp256k1/doc/CHANGELOG.md @@ -0,0 +1,12 @@ +# Changelog + +This file is currently only a template for future use. + +Each change falls into one of the following categories: Added, Changed, Deprecated, Removed, Fixed or Security. + +## [Unreleased] + +## [MAJOR.MINOR.PATCH] - YYYY-MM-DD + +### Added/Changed/Deprecated/Removed/Fixed/Security +- [Title with link to Pull Request](https://link-to-pr) diff --git a/src/secp256k1/doc/release-process.md b/src/secp256k1/doc/release-process.md new file mode 100644 index 0000000000..a35b8a9db3 --- /dev/null +++ b/src/secp256k1/doc/release-process.md @@ -0,0 +1,14 @@ +# Release Process + +1. Open PR to master that + 1. adds release notes to `doc/CHANGELOG.md` and + 2. if this is **not** a patch release, updates `_PKG_VERSION_{MAJOR,MINOR}` and `_LIB_VERSIONS_*` in `configure.ac` +2. After the PR is merged, + * if this is **not** a patch release, create a release branch with name `MAJOR.MINOR`. + Make sure that the branch contains the right commits. + Create commit on the release branch that sets `_PKG_VERSION_IS_RELEASE` in `configure.ac` to `true`. + * if this **is** a patch release, open a pull request with the bugfixes to the `MAJOR.MINOR` branch. + Also include the release note commit bump `_PKG_VERSION_BUILD` and `_LIB_VERSIONS_*` in `configure.ac`. +4. Tag the commit with `git tag -s vMAJOR.MINOR.PATCH`. +5. Push branch and tag with `git push origin --tags`. +6. Create a new GitHub release with a link to the corresponding entry in `doc/CHANGELOG.md`. diff --git a/src/secp256k1/examples/EXAMPLES_COPYING b/src/secp256k1/examples/EXAMPLES_COPYING new file mode 100644 index 0000000000..0e259d42c9 --- /dev/null +++ b/src/secp256k1/examples/EXAMPLES_COPYING @@ -0,0 +1,121 @@ +Creative Commons Legal Code + +CC0 1.0 Universal + + CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE + LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN + ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS + INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES + REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS + PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM + THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED + HEREUNDER. + +Statement of Purpose + +The laws of most jurisdictions throughout the world automatically confer +exclusive Copyright and Related Rights (defined below) upon the creator +and subsequent owner(s) (each and all, an "owner") of an original work of +authorship and/or a database (each, a "Work"). + +Certain owners wish to permanently relinquish those rights to a Work for +the purpose of contributing to a commons of creative, cultural and +scientific works ("Commons") that the public can reliably and without fear +of later claims of infringement build upon, modify, incorporate in other +works, reuse and redistribute as freely as possible in any form whatsoever +and for any purposes, including without limitation commercial purposes. +These owners may contribute to the Commons to promote the ideal of a free +culture and the further production of creative, cultural and scientific +works, or to gain reputation or greater distribution for their Work in +part through the use and efforts of others. + +For these and/or other purposes and motivations, and without any +expectation of additional consideration or compensation, the person +associating CC0 with a Work (the "Affirmer"), to the extent that he or she +is an owner of Copyright and Related Rights in the Work, voluntarily +elects to apply CC0 to the Work and publicly distribute the Work under its +terms, with knowledge of his or her Copyright and Related Rights in the +Work and the meaning and intended legal effect of CC0 on those rights. + +1. Copyright and Related Rights. A Work made available under CC0 may be +protected by copyright and related or neighboring rights ("Copyright and +Related Rights"). Copyright and Related Rights include, but are not +limited to, the following: + + i. the right to reproduce, adapt, distribute, perform, display, + communicate, and translate a Work; + ii. moral rights retained by the original author(s) and/or performer(s); +iii. publicity and privacy rights pertaining to a person's image or + likeness depicted in a Work; + iv. rights protecting against unfair competition in regards to a Work, + subject to the limitations in paragraph 4(a), below; + v. rights protecting the extraction, dissemination, use and reuse of data + in a Work; + vi. database rights (such as those arising under Directive 96/9/EC of the + European Parliament and of the Council of 11 March 1996 on the legal + protection of databases, and under any national implementation + thereof, including any amended or successor version of such + directive); and +vii. other similar, equivalent or corresponding rights throughout the + world based on applicable law or treaty, and any national + implementations thereof. + +2. Waiver. To the greatest extent permitted by, but not in contravention +of, applicable law, Affirmer hereby overtly, fully, permanently, +irrevocably and unconditionally waives, abandons, and surrenders all of +Affirmer's Copyright and Related Rights and associated claims and causes +of action, whether now known or unknown (including existing as well as +future claims and causes of action), in the Work (i) in all territories +worldwide, (ii) for the maximum duration provided by applicable law or +treaty (including future time extensions), (iii) in any current or future +medium and for any number of copies, and (iv) for any purpose whatsoever, +including without limitation commercial, advertising or promotional +purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each +member of the public at large and to the detriment of Affirmer's heirs and +successors, fully intending that such Waiver shall not be subject to +revocation, rescission, cancellation, termination, or any other legal or +equitable action to disrupt the quiet enjoyment of the Work by the public +as contemplated by Affirmer's express Statement of Purpose. + +3. Public License Fallback. Should any part of the Waiver for any reason +be judged legally invalid or ineffective under applicable law, then the +Waiver shall be preserved to the maximum extent permitted taking into +account Affirmer's express Statement of Purpose. In addition, to the +extent the Waiver is so judged Affirmer hereby grants to each affected +person a royalty-free, non transferable, non sublicensable, non exclusive, +irrevocable and unconditional license to exercise Affirmer's Copyright and +Related Rights in the Work (i) in all territories worldwide, (ii) for the +maximum duration provided by applicable law or treaty (including future +time extensions), (iii) in any current or future medium and for any number +of copies, and (iv) for any purpose whatsoever, including without +limitation commercial, advertising or promotional purposes (the +"License"). The License shall be deemed effective as of the date CC0 was +applied by Affirmer to the Work. Should any part of the License for any +reason be judged legally invalid or ineffective under applicable law, such +partial invalidity or ineffectiveness shall not invalidate the remainder +of the License, and in such case Affirmer hereby affirms that he or she +will not (i) exercise any of his or her remaining Copyright and Related +Rights in the Work or (ii) assert any associated claims and causes of +action with respect to the Work, in either case contrary to Affirmer's +express Statement of Purpose. + +4. Limitations and Disclaimers. + + a. No trademark or patent rights held by Affirmer are waived, abandoned, + surrendered, licensed or otherwise affected by this document. + b. Affirmer offers the Work as-is and makes no representations or + warranties of any kind concerning the Work, express, implied, + statutory or otherwise, including without limitation warranties of + title, merchantability, fitness for a particular purpose, non + infringement, or the absence of latent or other defects, accuracy, or + the present or absence of errors, whether or not discoverable, all to + the greatest extent permissible under applicable law. + c. Affirmer disclaims responsibility for clearing rights of other persons + that may apply to the Work or any use thereof, including without + limitation any person's Copyright and Related Rights in the Work. + Further, Affirmer disclaims responsibility for obtaining any necessary + consents, permissions or other rights required for any use of the + Work. + d. Affirmer understands and acknowledges that Creative Commons is not a + party to this document and has no duty or obligation with respect to + this CC0 or use of the Work. diff --git a/src/secp256k1/examples/ecdh.c b/src/secp256k1/examples/ecdh.c new file mode 100644 index 0000000000..d7e8add361 --- /dev/null +++ b/src/secp256k1/examples/ecdh.c @@ -0,0 +1,127 @@ +/************************************************************************* + * Written in 2020-2022 by Elichai Turkel * + * To the extent possible under law, the author(s) have dedicated all * + * copyright and related and neighboring rights to the software in this * + * file to the public domain worldwide. This software is distributed * + * without any warranty. For the CC0 Public Domain Dedication, see * + * EXAMPLES_COPYING or https://creativecommons.org/publicdomain/zero/1.0 * + *************************************************************************/ + +#include <stdio.h> +#include <assert.h> +#include <string.h> + +#include <secp256k1.h> +#include <secp256k1_ecdh.h> + +#include "random.h" + + +int main(void) { + unsigned char seckey1[32]; + unsigned char seckey2[32]; + unsigned char compressed_pubkey1[33]; + unsigned char compressed_pubkey2[33]; + unsigned char shared_secret1[32]; + unsigned char shared_secret2[32]; + unsigned char randomize[32]; + int return_val; + size_t len; + secp256k1_pubkey pubkey1; + secp256k1_pubkey pubkey2; + + /* The specification in secp256k1.h states that `secp256k1_ec_pubkey_create` + * needs a context object initialized for signing, which is why we create + * a context with the SECP256K1_CONTEXT_SIGN flag. + * (The docs for `secp256k1_ecdh` don't require any special context, just + * some initialized context) */ + secp256k1_context* ctx = secp256k1_context_create(SECP256K1_CONTEXT_SIGN); + if (!fill_random(randomize, sizeof(randomize))) { + printf("Failed to generate randomness\n"); + return 1; + } + /* Randomizing the context is recommended to protect against side-channel + * leakage See `secp256k1_context_randomize` in secp256k1.h for more + * information about it. This should never fail. */ + return_val = secp256k1_context_randomize(ctx, randomize); + assert(return_val); + + /*** Key Generation ***/ + + /* If the secret key is zero or out of range (bigger than secp256k1's + * order), we try to sample a new key. Note that the probability of this + * happening is negligible. */ + while (1) { + if (!fill_random(seckey1, sizeof(seckey1)) || !fill_random(seckey2, sizeof(seckey2))) { + printf("Failed to generate randomness\n"); + return 1; + } + if (secp256k1_ec_seckey_verify(ctx, seckey1) && secp256k1_ec_seckey_verify(ctx, seckey2)) { + break; + } + } + + /* Public key creation using a valid context with a verified secret key should never fail */ + return_val = secp256k1_ec_pubkey_create(ctx, &pubkey1, seckey1); + assert(return_val); + return_val = secp256k1_ec_pubkey_create(ctx, &pubkey2, seckey2); + assert(return_val); + + /* Serialize pubkey1 in a compressed form (33 bytes), should always return 1 */ + len = sizeof(compressed_pubkey1); + return_val = secp256k1_ec_pubkey_serialize(ctx, compressed_pubkey1, &len, &pubkey1, SECP256K1_EC_COMPRESSED); + assert(return_val); + /* Should be the same size as the size of the output, because we passed a 33 byte array. */ + assert(len == sizeof(compressed_pubkey1)); + + /* Serialize pubkey2 in a compressed form (33 bytes) */ + len = sizeof(compressed_pubkey2); + return_val = secp256k1_ec_pubkey_serialize(ctx, compressed_pubkey2, &len, &pubkey2, SECP256K1_EC_COMPRESSED); + assert(return_val); + /* Should be the same size as the size of the output, because we passed a 33 byte array. */ + assert(len == sizeof(compressed_pubkey2)); + + /*** Creating the shared secret ***/ + + /* Perform ECDH with seckey1 and pubkey2. Should never fail with a verified + * seckey and valid pubkey */ + return_val = secp256k1_ecdh(ctx, shared_secret1, &pubkey2, seckey1, NULL, NULL); + assert(return_val); + + /* Perform ECDH with seckey2 and pubkey1. Should never fail with a verified + * seckey and valid pubkey */ + return_val = secp256k1_ecdh(ctx, shared_secret2, &pubkey1, seckey2, NULL, NULL); + assert(return_val); + + /* Both parties should end up with the same shared secret */ + return_val = memcmp(shared_secret1, shared_secret2, sizeof(shared_secret1)); + assert(return_val == 0); + + printf("Secret Key1: "); + print_hex(seckey1, sizeof(seckey1)); + printf("Compressed Pubkey1: "); + print_hex(compressed_pubkey1, sizeof(compressed_pubkey1)); + printf("\nSecret Key2: "); + print_hex(seckey2, sizeof(seckey2)); + printf("Compressed Pubkey2: "); + print_hex(compressed_pubkey2, sizeof(compressed_pubkey2)); + printf("\nShared Secret: "); + print_hex(shared_secret1, sizeof(shared_secret1)); + + /* This will clear everything from the context and free the memory */ + secp256k1_context_destroy(ctx); + + /* It's best practice to try to clear secrets from memory after using them. + * This is done because some bugs can allow an attacker to leak memory, for + * example through "out of bounds" array access (see Heartbleed), Or the OS + * swapping them to disk. Hence, we overwrite the secret key buffer with zeros. + * + * TODO: Prevent these writes from being optimized out, as any good compiler + * will remove any writes that aren't used. */ + memset(seckey1, 0, sizeof(seckey1)); + memset(seckey2, 0, sizeof(seckey2)); + memset(shared_secret1, 0, sizeof(shared_secret1)); + memset(shared_secret2, 0, sizeof(shared_secret2)); + + return 0; +} diff --git a/src/secp256k1/examples/ecdsa.c b/src/secp256k1/examples/ecdsa.c new file mode 100644 index 0000000000..434c856ba0 --- /dev/null +++ b/src/secp256k1/examples/ecdsa.c @@ -0,0 +1,137 @@ +/************************************************************************* + * Written in 2020-2022 by Elichai Turkel * + * To the extent possible under law, the author(s) have dedicated all * + * copyright and related and neighboring rights to the software in this * + * file to the public domain worldwide. This software is distributed * + * without any warranty. For the CC0 Public Domain Dedication, see * + * EXAMPLES_COPYING or https://creativecommons.org/publicdomain/zero/1.0 * + *************************************************************************/ + +#include <stdio.h> +#include <assert.h> +#include <string.h> + +#include <secp256k1.h> + +#include "random.h" + + + +int main(void) { + /* Instead of signing the message directly, we must sign a 32-byte hash. + * Here the message is "Hello, world!" and the hash function was SHA-256. + * An actual implementation should just call SHA-256, but this example + * hardcodes the output to avoid depending on an additional library. + * See https://bitcoin.stackexchange.com/questions/81115/if-someone-wanted-to-pretend-to-be-satoshi-by-posting-a-fake-signature-to-defrau/81116#81116 */ + unsigned char msg_hash[32] = { + 0x31, 0x5F, 0x5B, 0xDB, 0x76, 0xD0, 0x78, 0xC4, + 0x3B, 0x8A, 0xC0, 0x06, 0x4E, 0x4A, 0x01, 0x64, + 0x61, 0x2B, 0x1F, 0xCE, 0x77, 0xC8, 0x69, 0x34, + 0x5B, 0xFC, 0x94, 0xC7, 0x58, 0x94, 0xED, 0xD3, + }; + unsigned char seckey[32]; + unsigned char randomize[32]; + unsigned char compressed_pubkey[33]; + unsigned char serialized_signature[64]; + size_t len; + int is_signature_valid; + int return_val; + secp256k1_pubkey pubkey; + secp256k1_ecdsa_signature sig; + /* The specification in secp256k1.h states that `secp256k1_ec_pubkey_create` needs + * a context object initialized for signing and `secp256k1_ecdsa_verify` needs + * a context initialized for verification, which is why we create a context + * for both signing and verification with the SECP256K1_CONTEXT_SIGN and + * SECP256K1_CONTEXT_VERIFY flags. */ + secp256k1_context* ctx = secp256k1_context_create(SECP256K1_CONTEXT_SIGN | SECP256K1_CONTEXT_VERIFY); + if (!fill_random(randomize, sizeof(randomize))) { + printf("Failed to generate randomness\n"); + return 1; + } + /* Randomizing the context is recommended to protect against side-channel + * leakage See `secp256k1_context_randomize` in secp256k1.h for more + * information about it. This should never fail. */ + return_val = secp256k1_context_randomize(ctx, randomize); + assert(return_val); + + /*** Key Generation ***/ + + /* If the secret key is zero or out of range (bigger than secp256k1's + * order), we try to sample a new key. Note that the probability of this + * happening is negligible. */ + while (1) { + if (!fill_random(seckey, sizeof(seckey))) { + printf("Failed to generate randomness\n"); + return 1; + } + if (secp256k1_ec_seckey_verify(ctx, seckey)) { + break; + } + } + + /* Public key creation using a valid context with a verified secret key should never fail */ + return_val = secp256k1_ec_pubkey_create(ctx, &pubkey, seckey); + assert(return_val); + + /* Serialize the pubkey in a compressed form(33 bytes). Should always return 1. */ + len = sizeof(compressed_pubkey); + return_val = secp256k1_ec_pubkey_serialize(ctx, compressed_pubkey, &len, &pubkey, SECP256K1_EC_COMPRESSED); + assert(return_val); + /* Should be the same size as the size of the output, because we passed a 33 byte array. */ + assert(len == sizeof(compressed_pubkey)); + + /*** Signing ***/ + + /* Generate an ECDSA signature `noncefp` and `ndata` allows you to pass a + * custom nonce function, passing `NULL` will use the RFC-6979 safe default. + * Signing with a valid context, verified secret key + * and the default nonce function should never fail. */ + return_val = secp256k1_ecdsa_sign(ctx, &sig, msg_hash, seckey, NULL, NULL); + assert(return_val); + + /* Serialize the signature in a compact form. Should always return 1 + * according to the documentation in secp256k1.h. */ + return_val = secp256k1_ecdsa_signature_serialize_compact(ctx, serialized_signature, &sig); + assert(return_val); + + + /*** Verification ***/ + + /* Deserialize the signature. This will return 0 if the signature can't be parsed correctly. */ + if (!secp256k1_ecdsa_signature_parse_compact(ctx, &sig, serialized_signature)) { + printf("Failed parsing the signature\n"); + return 1; + } + + /* Deserialize the public key. This will return 0 if the public key can't be parsed correctly. */ + if (!secp256k1_ec_pubkey_parse(ctx, &pubkey, compressed_pubkey, sizeof(compressed_pubkey))) { + printf("Failed parsing the public key\n"); + return 1; + } + + /* Verify a signature. This will return 1 if it's valid and 0 if it's not. */ + is_signature_valid = secp256k1_ecdsa_verify(ctx, &sig, msg_hash, &pubkey); + + printf("Is the signature valid? %s\n", is_signature_valid ? "true" : "false"); + printf("Secret Key: "); + print_hex(seckey, sizeof(seckey)); + printf("Public Key: "); + print_hex(compressed_pubkey, sizeof(compressed_pubkey)); + printf("Signature: "); + print_hex(serialized_signature, sizeof(serialized_signature)); + + + /* This will clear everything from the context and free the memory */ + secp256k1_context_destroy(ctx); + + /* It's best practice to try to clear secrets from memory after using them. + * This is done because some bugs can allow an attacker to leak memory, for + * example through "out of bounds" array access (see Heartbleed), Or the OS + * swapping them to disk. Hence, we overwrite the secret key buffer with zeros. + * + * TODO: Prevent these writes from being optimized out, as any good compiler + * will remove any writes that aren't used. */ + memset(seckey, 0, sizeof(seckey)); + + return 0; +} diff --git a/src/secp256k1/examples/random.h b/src/secp256k1/examples/random.h new file mode 100644 index 0000000000..439226f09f --- /dev/null +++ b/src/secp256k1/examples/random.h @@ -0,0 +1,73 @@ +/************************************************************************* + * Copyright (c) 2020-2021 Elichai Turkel * + * Distributed under the CC0 software license, see the accompanying file * + * EXAMPLES_COPYING or https://creativecommons.org/publicdomain/zero/1.0 * + *************************************************************************/ + +/* + * This file is an attempt at collecting best practice methods for obtaining randomness with different operating systems. + * It may be out-of-date. Consult the documentation of the operating system before considering to use the methods below. + * + * Platform randomness sources: + * Linux -> `getrandom(2)`(`sys/random.h`), if not available `/dev/urandom` should be used. http://man7.org/linux/man-pages/man2/getrandom.2.html, https://linux.die.net/man/4/urandom + * macOS -> `getentropy(2)`(`sys/random.h`), if not available `/dev/urandom` should be used. https://www.unix.com/man-page/mojave/2/getentropy, https://opensource.apple.com/source/xnu/xnu-517.12.7/bsd/man/man4/random.4.auto.html + * FreeBSD -> `getrandom(2)`(`sys/random.h`), if not available `kern.arandom` should be used. https://www.freebsd.org/cgi/man.cgi?query=getrandom, https://www.freebsd.org/cgi/man.cgi?query=random&sektion=4 + * OpenBSD -> `getentropy(2)`(`unistd.h`), if not available `/dev/urandom` should be used. https://man.openbsd.org/getentropy, https://man.openbsd.org/urandom + * Windows -> `BCryptGenRandom`(`bcrypt.h`). https://docs.microsoft.com/en-us/windows/win32/api/bcrypt/nf-bcrypt-bcryptgenrandom + */ + +#if defined(_WIN32) +#include <windows.h> +#include <ntstatus.h> +#include <bcrypt.h> +#elif defined(__linux__) || defined(__APPLE__) || defined(__FreeBSD__) +#include <sys/random.h> +#elif defined(__OpenBSD__) +#include <unistd.h> +#else +#error "Couldn't identify the OS" +#endif + +#include <stddef.h> +#include <limits.h> +#include <stdio.h> + + +/* Returns 1 on success, and 0 on failure. */ +static int fill_random(unsigned char* data, size_t size) { +#if defined(_WIN32) + NTSTATUS res = BCryptGenRandom(NULL, data, size, BCRYPT_USE_SYSTEM_PREFERRED_RNG); + if (res != STATUS_SUCCESS || size > ULONG_MAX) { + return 0; + } else { + return 1; + } +#elif defined(__linux__) || defined(__FreeBSD__) + /* If `getrandom(2)` is not available you should fallback to /dev/urandom */ + ssize_t res = getrandom(data, size, 0); + if (res < 0 || (size_t)res != size ) { + return 0; + } else { + return 1; + } +#elif defined(__APPLE__) || defined(__OpenBSD__) + /* If `getentropy(2)` is not available you should fallback to either + * `SecRandomCopyBytes` or /dev/urandom */ + int res = getentropy(data, size); + if (res == 0) { + return 1; + } else { + return 0; + } +#endif + return 0; +} + +static void print_hex(unsigned char* data, size_t size) { + size_t i; + printf("0x"); + for (i = 0; i < size; i++) { + printf("%02x", data[i]); + } + printf("\n"); +} diff --git a/src/secp256k1/examples/schnorr.c b/src/secp256k1/examples/schnorr.c new file mode 100644 index 0000000000..82eb07d5d7 --- /dev/null +++ b/src/secp256k1/examples/schnorr.c @@ -0,0 +1,152 @@ +/************************************************************************* + * Written in 2020-2022 by Elichai Turkel * + * To the extent possible under law, the author(s) have dedicated all * + * copyright and related and neighboring rights to the software in this * + * file to the public domain worldwide. This software is distributed * + * without any warranty. For the CC0 Public Domain Dedication, see * + * EXAMPLES_COPYING or https://creativecommons.org/publicdomain/zero/1.0 * + *************************************************************************/ + +#include <stdio.h> +#include <assert.h> +#include <string.h> + +#include <secp256k1.h> +#include <secp256k1_extrakeys.h> +#include <secp256k1_schnorrsig.h> + +#include "random.h" + +int main(void) { + unsigned char msg[12] = "Hello World!"; + unsigned char msg_hash[32]; + unsigned char tag[17] = "my_fancy_protocol"; + unsigned char seckey[32]; + unsigned char randomize[32]; + unsigned char auxiliary_rand[32]; + unsigned char serialized_pubkey[32]; + unsigned char signature[64]; + int is_signature_valid; + int return_val; + secp256k1_xonly_pubkey pubkey; + secp256k1_keypair keypair; + /* The specification in secp256k1_extrakeys.h states that `secp256k1_keypair_create` + * needs a context object initialized for signing. And in secp256k1_schnorrsig.h + * they state that `secp256k1_schnorrsig_verify` needs a context initialized for + * verification, which is why we create a context for both signing and verification + * with the SECP256K1_CONTEXT_SIGN and SECP256K1_CONTEXT_VERIFY flags. */ + secp256k1_context* ctx = secp256k1_context_create(SECP256K1_CONTEXT_SIGN | SECP256K1_CONTEXT_VERIFY); + if (!fill_random(randomize, sizeof(randomize))) { + printf("Failed to generate randomness\n"); + return 1; + } + /* Randomizing the context is recommended to protect against side-channel + * leakage See `secp256k1_context_randomize` in secp256k1.h for more + * information about it. This should never fail. */ + return_val = secp256k1_context_randomize(ctx, randomize); + assert(return_val); + + /*** Key Generation ***/ + + /* If the secret key is zero or out of range (bigger than secp256k1's + * order), we try to sample a new key. Note that the probability of this + * happening is negligible. */ + while (1) { + if (!fill_random(seckey, sizeof(seckey))) { + printf("Failed to generate randomness\n"); + return 1; + } + /* Try to create a keypair with a valid context, it should only fail if + * the secret key is zero or out of range. */ + if (secp256k1_keypair_create(ctx, &keypair, seckey)) { + break; + } + } + + /* Extract the X-only public key from the keypair. We pass NULL for + * `pk_parity` as the parity isn't needed for signing or verification. + * `secp256k1_keypair_xonly_pub` supports returning the parity for + * other use cases such as tests or verifying Taproot tweaks. + * This should never fail with a valid context and public key. */ + return_val = secp256k1_keypair_xonly_pub(ctx, &pubkey, NULL, &keypair); + assert(return_val); + + /* Serialize the public key. Should always return 1 for a valid public key. */ + return_val = secp256k1_xonly_pubkey_serialize(ctx, serialized_pubkey, &pubkey); + assert(return_val); + + /*** Signing ***/ + + /* Instead of signing (possibly very long) messages directly, we sign a + * 32-byte hash of the message in this example. + * + * We use secp256k1_tagged_sha256 to create this hash. This function expects + * a context-specific "tag", which restricts the context in which the signed + * messages should be considered valid. For example, if protocol A mandates + * to use the tag "my_fancy_protocol" and protocol B mandates to use the tag + * "my_boring_protocol", then signed messages from protocol A will never be + * valid in protocol B (and vice versa), even if keys are reused across + * protocols. This implements "domain separation", which is considered good + * practice. It avoids attacks in which users are tricked into signing a + * message that has intended consequences in the intended context (e.g., + * protocol A) but would have unintended consequences if it were valid in + * some other context (e.g., protocol B). */ + return_val = secp256k1_tagged_sha256(ctx, msg_hash, tag, sizeof(tag), msg, sizeof(msg)); + assert(return_val); + + /* Generate 32 bytes of randomness to use with BIP-340 schnorr signing. */ + if (!fill_random(auxiliary_rand, sizeof(auxiliary_rand))) { + printf("Failed to generate randomness\n"); + return 1; + } + + /* Generate a Schnorr signature. + * + * We use the secp256k1_schnorrsig_sign32 function that provides a simple + * interface for signing 32-byte messages (which in our case is a hash of + * the actual message). BIP-340 recommends passing 32 bytes of randomness + * to the signing function to improve security against side-channel attacks. + * Signing with a valid context, a 32-byte message, a verified keypair, and + * any 32 bytes of auxiliary random data should never fail. */ + return_val = secp256k1_schnorrsig_sign32(ctx, signature, msg_hash, &keypair, auxiliary_rand); + assert(return_val); + + /*** Verification ***/ + + /* Deserialize the public key. This will return 0 if the public key can't + * be parsed correctly */ + if (!secp256k1_xonly_pubkey_parse(ctx, &pubkey, serialized_pubkey)) { + printf("Failed parsing the public key\n"); + return 1; + } + + /* Compute the tagged hash on the received messages using the same tag as the signer. */ + return_val = secp256k1_tagged_sha256(ctx, msg_hash, tag, sizeof(tag), msg, sizeof(msg)); + assert(return_val); + + /* Verify a signature. This will return 1 if it's valid and 0 if it's not. */ + is_signature_valid = secp256k1_schnorrsig_verify(ctx, signature, msg_hash, 32, &pubkey); + + + printf("Is the signature valid? %s\n", is_signature_valid ? "true" : "false"); + printf("Secret Key: "); + print_hex(seckey, sizeof(seckey)); + printf("Public Key: "); + print_hex(serialized_pubkey, sizeof(serialized_pubkey)); + printf("Signature: "); + print_hex(signature, sizeof(signature)); + + /* This will clear everything from the context and free the memory */ + secp256k1_context_destroy(ctx); + + /* It's best practice to try to clear secrets from memory after using them. + * This is done because some bugs can allow an attacker to leak memory, for + * example through "out of bounds" array access (see Heartbleed), Or the OS + * swapping them to disk. Hence, we overwrite the secret key buffer with zeros. + * + * TODO: Prevent these writes from being optimized out, as any good compiler + * will remove any writes that aren't used. */ + memset(seckey, 0, sizeof(seckey)); + + return 0; +} diff --git a/src/secp256k1/include/secp256k1.h b/src/secp256k1/include/secp256k1.h index 57114b8f26..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 @@ -169,6 +173,17 @@ typedef int (*secp256k1_nonce_function)( # define SECP256K1_ARG_NONNULL(_x) # endif +/** Attribute for marking functions, types, and variables as deprecated */ +#if !defined(SECP256K1_BUILD) && defined(__has_attribute) +# if __has_attribute(__deprecated__) +# define SECP256K1_DEPRECATED(_msg) __attribute__ ((__deprecated__(_msg))) +# else +# define SECP256K1_DEPRECATED(_msg) +# endif +#else +# define SECP256K1_DEPRECATED(_msg) +#endif + /** All flags' lower 8 bits indicate what they're for. Do not use directly. */ #define SECP256K1_FLAGS_TYPE_MASK ((1 << 8) - 1) #define SECP256K1_FLAGS_TYPE_CONTEXT (1 << 0) @@ -641,7 +656,8 @@ SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_ec_seckey_negate( SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_ec_privkey_negate( const secp256k1_context* ctx, unsigned char *seckey -) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2); +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) + SECP256K1_DEPRECATED("Use secp256k1_ec_seckey_negate instead"); /** Negates a public key in place. * @@ -681,7 +697,8 @@ SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_ec_privkey_tweak_add( const secp256k1_context* ctx, unsigned char *seckey, const unsigned char *tweak32 -) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3); +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) + SECP256K1_DEPRECATED("Use secp256k1_ec_seckey_tweak_add instead"); /** Tweak a public key by adding tweak times the generator to it. * @@ -727,7 +744,8 @@ SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_ec_privkey_tweak_mul( const secp256k1_context* ctx, unsigned char *seckey, const unsigned char *tweak32 -) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3); +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) + SECP256K1_DEPRECATED("Use secp256k1_ec_seckey_tweak_mul instead"); /** Tweak a public key by multiplying it by a tweak value. * @@ -800,7 +818,7 @@ SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_ec_pubkey_combine( * implementations optimized for a specific tag can precompute the SHA256 state * after hashing the tag hashes. * - * Returns 0 if the arguments are invalid and 1 otherwise. + * Returns: 1 always. * Args: ctx: pointer to a context object * Out: hash32: pointer to a 32-byte array to store the resulting hash * In: tag: pointer to an array containing the tag diff --git a/src/secp256k1/include/secp256k1_extrakeys.h b/src/secp256k1/include/secp256k1_extrakeys.h index a64d561b60..09cbeaaa80 100644 --- a/src/secp256k1/include/secp256k1_extrakeys.h +++ b/src/secp256k1/include/secp256k1_extrakeys.h @@ -81,8 +81,7 @@ SECP256K1_API int secp256k1_xonly_pubkey_cmp( /** Converts a secp256k1_pubkey into a secp256k1_xonly_pubkey. * - * Returns: 1 if the public key was successfully converted - * 0 otherwise + * Returns: 1 always. * * Args: ctx: pointer to a context object. * Out: xonly_pubkey: pointer to an x-only public key object for placing the converted public key. @@ -172,7 +171,7 @@ SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_keypair_create( /** Get the secret key from a keypair. * - * Returns: 0 if the arguments are invalid. 1 otherwise. + * Returns: 1 always. * Args: ctx: pointer to a context object. * Out: seckey: pointer to a 32-byte buffer for the secret key. * In: keypair: pointer to a keypair. @@ -185,7 +184,7 @@ SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_keypair_sec( /** Get the public key from a keypair. * - * Returns: 0 if the arguments are invalid. 1 otherwise. + * Returns: 1 always. * Args: ctx: pointer to a context object. * Out: pubkey: pointer to a pubkey object. If 1 is returned, it is set to * the keypair public key. If not, it's set to an invalid value. @@ -202,7 +201,7 @@ SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_keypair_pub( * This is the same as calling secp256k1_keypair_pub and then * secp256k1_xonly_pubkey_from_pubkey. * - * Returns: 0 if the arguments are invalid. 1 otherwise. + * Returns: 1 always. * Args: ctx: pointer to a context object. * Out: pubkey: pointer to an xonly_pubkey object. If 1 is returned, it is set * to the keypair public key after converting it to an diff --git a/src/secp256k1/include/secp256k1_schnorrsig.h b/src/secp256k1/include/secp256k1_schnorrsig.h index e971ddc2aa..5fedcb07b0 100644 --- a/src/secp256k1/include/secp256k1_schnorrsig.h +++ b/src/secp256k1/include/secp256k1_schnorrsig.h @@ -116,7 +116,7 @@ typedef struct { * BIP-340 "Default Signing" for a full explanation of this * argument and for guidance if randomness is expensive. */ -SECP256K1_API int secp256k1_schnorrsig_sign( +SECP256K1_API int secp256k1_schnorrsig_sign32( const secp256k1_context* ctx, unsigned char *sig64, const unsigned char *msg32, @@ -124,6 +124,17 @@ SECP256K1_API int secp256k1_schnorrsig_sign( const unsigned char *aux_rand32 ) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4); +/** Same as secp256k1_schnorrsig_sign32, but DEPRECATED. Will be removed in + * future versions. */ +SECP256K1_API int secp256k1_schnorrsig_sign( + const secp256k1_context* ctx, + unsigned char *sig64, + const unsigned char *msg32, + const secp256k1_keypair *keypair, + const unsigned char *aux_rand32 +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4) + SECP256K1_DEPRECATED("Use secp256k1_schnorrsig_sign32 instead"); + /** Create a Schnorr signature with a more flexible API. * * Same arguments as secp256k1_schnorrsig_sign except that it allows signing diff --git a/src/secp256k1/sage/group_prover.sage b/src/secp256k1/sage/group_prover.sage index b200bfeae3..9305c215d5 100644 --- a/src/secp256k1/sage/group_prover.sage +++ b/src/secp256k1/sage/group_prover.sage @@ -164,6 +164,9 @@ class constraints: def negate(self): return constraints(zero=self.nonzero, nonzero=self.zero) + def map(self, fun): + return constraints(zero={fun(k): v for k, v in self.zero.items()}, nonzero={fun(k): v for k, v in self.nonzero.items()}) + def __add__(self, other): zero = self.zero.copy() zero.update(other.zero) @@ -177,6 +180,30 @@ class constraints: def __repr__(self): return "%s" % self +def normalize_factor(p): + """Normalizes the sign of primitive polynomials (as returned by factor()) + + This function ensures that the polynomial has a positive leading coefficient. + + This is necessary because recent sage versions (starting with v9.3 or v9.4, + we don't know) are inconsistent about the placement of the minus sign in + polynomial factorizations: + ``` + sage: R.<ax,bx,ay,by,Az,Bz,Ai,Bi> = PolynomialRing(QQ,8,order='invlex') + sage: R((-2 * (bx - ax)) ^ 1).factor() + (-2) * (bx - ax) + sage: R((-2 * (bx - ax)) ^ 2).factor() + (4) * (-bx + ax)^2 + sage: R((-2 * (bx - ax)) ^ 3).factor() + (8) * (-bx + ax)^3 + ``` + """ + # Assert p is not 0 and that its non-zero coeffients are coprime. + # (We could just work with the primitive part p/p.content() but we want to be + # aware if factor() does not return a primitive part in future sage versions.) + assert p.content() == 1 + # Ensure that the first non-zero coefficient is positive. + return p if p.lc() > 0 else -p def conflicts(R, con): """Check whether any of the passed non-zero assumptions is implied by the zero assumptions""" @@ -204,10 +231,10 @@ def get_nonzero_set(R, assume): nonzero = set() for nz in map(numerator, assume.nonzero): for (f,n) in nz.factor(): - nonzero.add(f) + nonzero.add(normalize_factor(f)) rnz = zero.reduce(nz) for (f,n) in rnz.factor(): - nonzero.add(f) + nonzero.add(normalize_factor(f)) return nonzero @@ -222,27 +249,27 @@ def prove_nonzero(R, exprs, assume): return (False, [exprs[expr]]) allexprs = reduce(lambda a,b: numerator(a)*numerator(b), exprs, 1) for (f, n) in allexprs.factor(): - if f not in nonzero: + if normalize_factor(f) not in nonzero: ok = False if ok: return (True, None) ok = True - for (f, n) in zero.reduce(numerator(allexprs)).factor(): - if f not in nonzero: + for (f, n) in zero.reduce(allexprs).factor(): + if normalize_factor(f) not in nonzero: ok = False if ok: return (True, None) ok = True for expr in exprs: for (f,n) in numerator(expr).factor(): - if f not in nonzero: + if normalize_factor(f) not in nonzero: ok = False if ok: return (True, None) ok = True for expr in exprs: for (f,n) in zero.reduce(numerator(expr)).factor(): - if f not in nonzero: + if normalize_factor(f) not in nonzero: expl.add(exprs[expr]) if expl: return (False, list(expl)) @@ -254,7 +281,7 @@ def prove_zero(R, exprs, assume): """Check whether all of the passed expressions are provably zero, given assumptions""" r, e = prove_nonzero(R, dict(map(lambda x: (fastfrac(R, x.bot, 1), exprs[x]), exprs)), assume) if not r: - return (False, map(lambda x: "Possibly zero denominator: %s" % x, e)) + return (False, list(map(lambda x: "Possibly zero denominator: %s" % x, e))) zero = R.ideal(list(map(numerator, assume.zero))) nonzero = prod(x for x in assume.nonzero) expl = [] @@ -279,8 +306,8 @@ def describe_extra(R, assume, assumeExtra): if base not in zero: add = [] for (f, n) in numerator(base).factor(): - if f not in nonzero: - add += ["%s" % f] + if normalize_factor(f) not in nonzero: + add += ["%s" % normalize_factor(f)] if add: ret.add((" * ".join(add)) + " = 0 [%s]" % assumeExtra.zero[base]) # Iterate over the extra nonzero expressions @@ -288,8 +315,8 @@ def describe_extra(R, assume, assumeExtra): nzr = zeroextra.reduce(numerator(nz)) if nzr not in zeroextra: for (f,n) in nzr.factor(): - if zeroextra.reduce(f) not in nonzero: - ret.add("%s != 0" % zeroextra.reduce(f)) + if normalize_factor(zeroextra.reduce(f)) not in nonzero: + ret.add("%s != 0" % normalize_factor(zeroextra.reduce(f))) return ", ".join(x for x in ret) @@ -299,22 +326,21 @@ def check_symbolic(R, assumeLaw, assumeAssert, assumeBranch, require): if conflicts(R, assume): # This formula does not apply - return None + return (True, None) describe = describe_extra(R, assumeLaw + assumeBranch, assumeAssert) + if describe != "": + describe = " (assuming " + describe + ")" ok, msg = prove_zero(R, require.zero, assume) if not ok: - return "FAIL, %s fails (assuming %s)" % (str(msg), describe) + return (False, "FAIL, %s fails%s" % (str(msg), describe)) res, expl = prove_nonzero(R, require.nonzero, assume) if not res: - return "FAIL, %s fails (assuming %s)" % (str(expl), describe) + return (False, "FAIL, %s fails%s" % (str(expl), describe)) - if describe != "": - return "OK (assuming %s)" % describe - else: - return "OK" + return (True, "OK%s" % describe) def concrete_verify(c): diff --git a/src/secp256k1/sage/prove_group_implementations.sage b/src/secp256k1/sage/prove_group_implementations.sage index a97e732f7f..652bd87f11 100644 --- a/src/secp256k1/sage/prove_group_implementations.sage +++ b/src/secp256k1/sage/prove_group_implementations.sage @@ -8,25 +8,20 @@ load("weierstrass_prover.sage") def formula_secp256k1_gej_double_var(a): """libsecp256k1's secp256k1_gej_double_var, used by various addition functions""" rz = a.Z * a.Y - rz = rz * 2 - t1 = a.X^2 - t1 = t1 * 3 - t2 = t1^2 - t3 = a.Y^2 - t3 = t3 * 2 - t4 = t3^2 - t4 = t4 * 2 - t3 = t3 * a.X - rx = t3 - rx = rx * 4 - rx = -rx - rx = rx + t2 - t2 = -t2 - t3 = t3 * 6 - t3 = t3 + t2 - ry = t1 * t3 - t2 = -t4 - ry = ry + t2 + s = a.Y^2 + l = a.X^2 + l = l * 3 + l = l / 2 + t = -s + t = t * a.X + rx = l^2 + rx = rx + t + rx = rx + t + s = s^2 + t = t + rx + ry = t * l + ry = ry + s + ry = -ry return jacobianpoint(rx, ry, rz) def formula_secp256k1_gej_add_var(branch, a, b): @@ -45,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)) @@ -85,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)) @@ -114,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 @@ -131,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)) @@ -197,7 +183,8 @@ def formula_secp256k1_gej_add_ge(branch, a, b): rr_alt = rr m_alt = m n = m_alt^2 - q = n * t + q = -t + q = q * n n = n^2 if degenerate: n = m @@ -210,8 +197,6 @@ def formula_secp256k1_gej_add_ge(branch, a, b): zeroes.update({rz : 'r.z=0'}) else: nonzeroes.update({rz : 'r.z!=0'}) - rz = rz * 2 - q = -q t = t + q rx = t t = t * 2 @@ -219,8 +204,7 @@ def formula_secp256k1_gej_add_ge(branch, a, b): t = t * rr_alt t = t + n ry = -t - rx = rx * 4 - ry = ry * 4 + ry = ry / 2 if a_infinity: rx = b.X ry = b.Y @@ -292,15 +276,18 @@ def formula_secp256k1_gej_add_ge_old(branch, a, b): return (constraints(zero={b.Z - 1 : 'b.z=1', b.Infinity : 'b_finite'}), constraints(zero=zero, nonzero=nonzero), jacobianpoint(rx, ry, rz)) if __name__ == "__main__": - check_symbolic_jacobian_weierstrass("secp256k1_gej_add_var", 0, 7, 5, formula_secp256k1_gej_add_var) - check_symbolic_jacobian_weierstrass("secp256k1_gej_add_ge_var", 0, 7, 5, formula_secp256k1_gej_add_ge_var) - check_symbolic_jacobian_weierstrass("secp256k1_gej_add_zinv_var", 0, 7, 5, formula_secp256k1_gej_add_zinv_var) - check_symbolic_jacobian_weierstrass("secp256k1_gej_add_ge", 0, 7, 16, formula_secp256k1_gej_add_ge) - check_symbolic_jacobian_weierstrass("secp256k1_gej_add_ge_old [should fail]", 0, 7, 4, formula_secp256k1_gej_add_ge_old) + success = True + success = success & check_symbolic_jacobian_weierstrass("secp256k1_gej_add_var", 0, 7, 5, formula_secp256k1_gej_add_var) + success = success & check_symbolic_jacobian_weierstrass("secp256k1_gej_add_ge_var", 0, 7, 5, formula_secp256k1_gej_add_ge_var) + success = success & check_symbolic_jacobian_weierstrass("secp256k1_gej_add_zinv_var", 0, 7, 5, formula_secp256k1_gej_add_zinv_var) + success = success & check_symbolic_jacobian_weierstrass("secp256k1_gej_add_ge", 0, 7, 16, formula_secp256k1_gej_add_ge) + success = success & (not check_symbolic_jacobian_weierstrass("secp256k1_gej_add_ge_old [should fail]", 0, 7, 4, formula_secp256k1_gej_add_ge_old)) if len(sys.argv) >= 2 and sys.argv[1] == "--exhaustive": - check_exhaustive_jacobian_weierstrass("secp256k1_gej_add_var", 0, 7, 5, formula_secp256k1_gej_add_var, 43) - check_exhaustive_jacobian_weierstrass("secp256k1_gej_add_ge_var", 0, 7, 5, formula_secp256k1_gej_add_ge_var, 43) - check_exhaustive_jacobian_weierstrass("secp256k1_gej_add_zinv_var", 0, 7, 5, formula_secp256k1_gej_add_zinv_var, 43) - check_exhaustive_jacobian_weierstrass("secp256k1_gej_add_ge", 0, 7, 16, formula_secp256k1_gej_add_ge, 43) - check_exhaustive_jacobian_weierstrass("secp256k1_gej_add_ge_old [should fail]", 0, 7, 4, formula_secp256k1_gej_add_ge_old, 43) + success = success & check_exhaustive_jacobian_weierstrass("secp256k1_gej_add_var", 0, 7, 5, formula_secp256k1_gej_add_var, 43) + success = success & check_exhaustive_jacobian_weierstrass("secp256k1_gej_add_ge_var", 0, 7, 5, formula_secp256k1_gej_add_ge_var, 43) + success = success & check_exhaustive_jacobian_weierstrass("secp256k1_gej_add_zinv_var", 0, 7, 5, formula_secp256k1_gej_add_zinv_var, 43) + success = success & check_exhaustive_jacobian_weierstrass("secp256k1_gej_add_ge", 0, 7, 16, formula_secp256k1_gej_add_ge, 43) + success = success & (not check_exhaustive_jacobian_weierstrass("secp256k1_gej_add_ge_old [should fail]", 0, 7, 4, formula_secp256k1_gej_add_ge_old, 43)) + + sys.exit(int(not success)) diff --git a/src/secp256k1/sage/weierstrass_prover.sage b/src/secp256k1/sage/weierstrass_prover.sage index b770c6dafe..be9cfd4c76 100644 --- a/src/secp256k1/sage/weierstrass_prover.sage +++ b/src/secp256k1/sage/weierstrass_prover.sage @@ -184,6 +184,7 @@ def check_exhaustive_jacobian_weierstrass(name, A, B, branches, formula, p): if r: points.append(point) + ret = True for za in range(1, p): for zb in range(1, p): for pa in points: @@ -211,8 +212,11 @@ def check_exhaustive_jacobian_weierstrass(name, A, B, branches, formula, p): match = True r, e = concrete_verify(require) if not r: + ret = False print(" failure in branch %i for (%s,%s,%s,%s) + (%s,%s,%s,%s) = (%s,%s,%s,%s): %s" % (branch, pA.X, pA.Y, pA.Z, pA.Infinity, pB.X, pB.Y, pB.Z, pB.Infinity, pC.X, pC.Y, pC.Z, pC.Infinity, e)) + print() + return ret def check_symbolic_function(R, assumeAssert, assumeBranch, f, A, B, pa, pb, pA, pB, pC): @@ -244,15 +248,21 @@ def check_symbolic_jacobian_weierstrass(name, A, B, branches, formula): print("Formula " + name + ":") count = 0 + ret = True for branch in range(branches): assumeFormula, assumeBranch, pC = formula(branch, pA, pB) + assumeBranch = assumeBranch.map(lift) + assumeFormula = assumeFormula.map(lift) pC.X = lift(pC.X) pC.Y = lift(pC.Y) pC.Z = lift(pC.Z) pC.Infinity = lift(pC.Infinity) for key in laws_jacobian_weierstrass: - res[key].append((check_symbolic_function(R, assumeFormula, assumeBranch, laws_jacobian_weierstrass[key], A, B, pa, pb, pA, pB, pC), branch)) + success, msg = check_symbolic_function(R, assumeFormula, assumeBranch, laws_jacobian_weierstrass[key], A, B, pa, pb, pA, pB, pC) + if not success: + ret = False + res[key].append((msg, branch)) for key in res: print(" %s:" % key) @@ -262,3 +272,4 @@ def check_symbolic_jacobian_weierstrass(name, A, B, branches, formula): print(" branch %i: %s" % (x[1], x[0])) print() + return ret diff --git a/src/secp256k1/src/bench_internal.c b/src/secp256k1/src/bench_internal.c index aed8216127..7eb3af28d7 100644 --- a/src/secp256k1/src/bench_internal.c +++ b/src/secp256k1/src/bench_internal.c @@ -140,6 +140,15 @@ void bench_scalar_inverse_var(void* arg, int iters) { CHECK(j <= iters); } +void bench_field_half(void* arg, int iters) { + int i; + bench_inv *data = (bench_inv*)arg; + + for (i = 0; i < iters; i++) { + secp256k1_fe_half(&data->fe[0]); + } +} + void bench_field_normalize(void* arg, int iters) { int i; bench_inv *data = (bench_inv*)arg; @@ -245,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; @@ -354,6 +372,7 @@ int main(int argc, char **argv) { if (d || have_flag(argc, argv, "scalar") || have_flag(argc, argv, "inverse")) run_benchmark("scalar_inverse", bench_scalar_inverse, bench_setup, NULL, &data, 10, iters); if (d || have_flag(argc, argv, "scalar") || have_flag(argc, argv, "inverse")) run_benchmark("scalar_inverse_var", bench_scalar_inverse_var, bench_setup, NULL, &data, 10, iters); + if (d || have_flag(argc, argv, "field") || have_flag(argc, argv, "half")) run_benchmark("field_half", bench_field_half, bench_setup, NULL, &data, 10, iters*100); if (d || have_flag(argc, argv, "field") || have_flag(argc, argv, "normalize")) run_benchmark("field_normalize", bench_field_normalize, bench_setup, NULL, &data, 10, iters*100); if (d || have_flag(argc, argv, "field") || have_flag(argc, argv, "normalize")) run_benchmark("field_normalize_weak", bench_field_normalize_weak, bench_setup, NULL, &data, 10, iters*100); if (d || have_flag(argc, argv, "field") || have_flag(argc, argv, "sqr")) run_benchmark("field_sqr", bench_field_sqr, bench_setup, NULL, &data, 10, iters*10); @@ -366,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/ecmult_compute_table.h b/src/secp256k1/src/ecmult_compute_table.h new file mode 100644 index 0000000000..665f87ff3d --- /dev/null +++ b/src/secp256k1/src/ecmult_compute_table.h @@ -0,0 +1,16 @@ +/***************************************************************************************************** + * Copyright (c) 2013, 2014, 2017, 2021 Pieter Wuille, Andrew Poelstra, Jonas Nick, Russell O'Connor * + * Distributed under the MIT software license, see the accompanying * + * file COPYING or https://www.opensource.org/licenses/mit-license.php. * + *****************************************************************************************************/ + +#ifndef SECP256K1_ECMULT_COMPUTE_TABLE_H +#define SECP256K1_ECMULT_COMPUTE_TABLE_H + +/* Construct table of all odd multiples of gen in range 1..(2**(window_g-1)-1). */ +static void secp256k1_ecmult_compute_table(secp256k1_ge_storage* table, int window_g, const secp256k1_gej* gen); + +/* Like secp256k1_ecmult_compute_table, but one for both gen and gen*2^128. */ +static void secp256k1_ecmult_compute_two_tables(secp256k1_ge_storage* table, secp256k1_ge_storage* table_128, int window_g, const secp256k1_ge* gen); + +#endif /* SECP256K1_ECMULT_COMPUTE_TABLE_H */ diff --git a/src/secp256k1/src/ecmult_compute_table_impl.h b/src/secp256k1/src/ecmult_compute_table_impl.h new file mode 100644 index 0000000000..69d59ce595 --- /dev/null +++ b/src/secp256k1/src/ecmult_compute_table_impl.h @@ -0,0 +1,49 @@ +/***************************************************************************************************** + * Copyright (c) 2013, 2014, 2017, 2021 Pieter Wuille, Andrew Poelstra, Jonas Nick, Russell O'Connor * + * Distributed under the MIT software license, see the accompanying * + * file COPYING or https://www.opensource.org/licenses/mit-license.php. * + *****************************************************************************************************/ + +#ifndef SECP256K1_ECMULT_COMPUTE_TABLE_IMPL_H +#define SECP256K1_ECMULT_COMPUTE_TABLE_IMPL_H + +#include "ecmult_compute_table.h" +#include "group_impl.h" +#include "field_impl.h" +#include "ecmult.h" +#include "util.h" + +static void secp256k1_ecmult_compute_table(secp256k1_ge_storage* table, int window_g, const secp256k1_gej* gen) { + secp256k1_gej gj; + secp256k1_ge ge, dgen; + int j; + + gj = *gen; + secp256k1_ge_set_gej_var(&ge, &gj); + secp256k1_ge_to_storage(&table[0], &ge); + + secp256k1_gej_double_var(&gj, gen, NULL); + secp256k1_ge_set_gej_var(&dgen, &gj); + + for (j = 1; j < ECMULT_TABLE_SIZE(window_g); ++j) { + secp256k1_gej_set_ge(&gj, &ge); + secp256k1_gej_add_ge_var(&gj, &gj, &dgen, NULL); + secp256k1_ge_set_gej_var(&ge, &gj); + secp256k1_ge_to_storage(&table[j], &ge); + } +} + +/* Like secp256k1_ecmult_compute_table, but one for both gen and gen*2^128. */ +static void secp256k1_ecmult_compute_two_tables(secp256k1_ge_storage* table, secp256k1_ge_storage* table_128, int window_g, const secp256k1_ge* gen) { + secp256k1_gej gj; + int i; + + secp256k1_gej_set_ge(&gj, gen); + secp256k1_ecmult_compute_table(table, window_g, &gj); + for (i = 0; i < 128; ++i) { + secp256k1_gej_double_var(&gj, &gj, NULL); + } + secp256k1_ecmult_compute_table(table_128, window_g, &gj); +} + +#endif /* SECP256K1_ECMULT_COMPUTE_TABLE_IMPL_H */ diff --git a/src/secp256k1/src/ecmult_const_impl.h b/src/secp256k1/src/ecmult_const_impl.h index 30b151ff9a..12dbcc6c5b 100644 --- a/src/secp256k1/src/ecmult_const_impl.h +++ b/src/secp256k1/src/ecmult_const_impl.h @@ -12,6 +12,19 @@ #include "ecmult_const.h" #include "ecmult_impl.h" +/** Fill a table 'pre' with precomputed odd multiples of a. + * + * The resulting point set is brought to a single constant Z denominator, stores the X and Y + * coordinates as ge_storage points in pre, and stores the global Z in globalz. + * It only operates on tables sized for WINDOW_A wnaf multiples. + */ +static void secp256k1_ecmult_odd_multiples_table_globalz_windowa(secp256k1_ge *pre, secp256k1_fe *globalz, const secp256k1_gej *a) { + secp256k1_fe zr[ECMULT_TABLE_SIZE(WINDOW_A)]; + + secp256k1_ecmult_odd_multiples_table(ECMULT_TABLE_SIZE(WINDOW_A), pre, zr, globalz, a); + secp256k1_ge_table_set_globalz(ECMULT_TABLE_SIZE(WINDOW_A), pre, zr); +} + /* This is like `ECMULT_TABLE_GET_GE` but is constant time */ #define ECMULT_CONST_TABLE_GET_GE(r,pre,n,w) do { \ int m = 0; \ @@ -40,7 +53,6 @@ secp256k1_fe_cmov(&(r)->y, &neg_y, (n) != abs_n); \ } while(0) - /** Convert a number to WNAF notation. * The number becomes represented by sum(2^{wi} * wnaf[i], i=0..WNAF_SIZE(w)+1) - return_val. * It has the following guarantees: @@ -56,7 +68,7 @@ */ static int secp256k1_wnaf_const(int *wnaf, const secp256k1_scalar *scalar, int w, int size) { int global_sign; - int skew = 0; + int skew; int word = 0; /* 1 2 3 */ @@ -64,9 +76,7 @@ static int secp256k1_wnaf_const(int *wnaf, const secp256k1_scalar *scalar, int w int u; int flip; - int bit; - secp256k1_scalar s; - int not_neg_one; + secp256k1_scalar s = *scalar; VERIFY_CHECK(w > 0); VERIFY_CHECK(size > 0); @@ -74,33 +84,19 @@ static int secp256k1_wnaf_const(int *wnaf, const secp256k1_scalar *scalar, int w /* Note that we cannot handle even numbers by negating them to be odd, as is * done in other implementations, since if our scalars were specified to have * width < 256 for performance reasons, their negations would have width 256 - * and we'd lose any performance benefit. Instead, we use a technique from - * Section 4.2 of the Okeya/Tagaki paper, which is to add either 1 (for even) - * or 2 (for odd) to the number we are encoding, returning a skew value indicating + * and we'd lose any performance benefit. Instead, we use a variation of a + * technique from Section 4.2 of the Okeya/Tagaki paper, which is to add 1 to the + * number we are encoding when it is even, returning a skew value indicating * this, and having the caller compensate after doing the multiplication. * * In fact, we _do_ want to negate numbers to minimize their bit-lengths (and in * particular, to ensure that the outputs from the endomorphism-split fit into - * 128 bits). If we negate, the parity of our number flips, inverting which of - * {1, 2} we want to add to the scalar when ensuring that it's odd. Further - * complicating things, -1 interacts badly with `secp256k1_scalar_cadd_bit` and - * we need to special-case it in this logic. */ - flip = secp256k1_scalar_is_high(scalar); - /* We add 1 to even numbers, 2 to odd ones, noting that negation flips parity */ - bit = flip ^ !secp256k1_scalar_is_even(scalar); - /* We check for negative one, since adding 2 to it will cause an overflow */ - secp256k1_scalar_negate(&s, scalar); - not_neg_one = !secp256k1_scalar_is_one(&s); - s = *scalar; - secp256k1_scalar_cadd_bit(&s, bit, not_neg_one); - /* If we had negative one, flip == 1, s.d[0] == 0, bit == 1, so caller expects - * that we added two to it and flipped it. In fact for -1 these operations are - * identical. We only flipped, but since skewing is required (in the sense that - * the skew must be 1 or 2, never zero) and flipping is not, we need to change - * our flags to claim that we only skewed. */ + * 128 bits). If we negate, the parity of our number flips, affecting whether + * we want to add to the scalar to ensure that it's odd. */ + flip = secp256k1_scalar_is_high(&s); + skew = flip ^ secp256k1_scalar_is_even(&s); + secp256k1_scalar_cadd_bit(&s, 0, skew); global_sign = secp256k1_scalar_cond_negate(&s, flip); - global_sign *= not_neg_one * 2 - 1; - skew = 1 << bit; /* 4 */ u_last = secp256k1_scalar_shr_int(&s, w); @@ -214,42 +210,22 @@ static void secp256k1_ecmult_const(secp256k1_gej *r, const secp256k1_ge *a, cons } } - secp256k1_fe_mul(&r->z, &r->z, &Z); - { /* Correct for wNAF skew */ - secp256k1_ge correction = *a; - secp256k1_ge_storage correction_1_stor; - secp256k1_ge_storage correction_lam_stor; - secp256k1_ge_storage a2_stor; secp256k1_gej tmpj; - secp256k1_gej_set_ge(&tmpj, &correction); - secp256k1_gej_double_var(&tmpj, &tmpj, NULL); - secp256k1_ge_set_gej(&correction, &tmpj); - secp256k1_ge_to_storage(&correction_1_stor, a); - if (size > 128) { - secp256k1_ge_to_storage(&correction_lam_stor, a); - } - secp256k1_ge_to_storage(&a2_stor, &correction); - /* For odd numbers this is 2a (so replace it), for even ones a (so no-op) */ - secp256k1_ge_storage_cmov(&correction_1_stor, &a2_stor, skew_1 == 2); - if (size > 128) { - secp256k1_ge_storage_cmov(&correction_lam_stor, &a2_stor, skew_lam == 2); - } - - /* Apply the correction */ - secp256k1_ge_from_storage(&correction, &correction_1_stor); - secp256k1_ge_neg(&correction, &correction); - secp256k1_gej_add_ge(r, r, &correction); + secp256k1_ge_neg(&tmpa, &pre_a[0]); + secp256k1_gej_add_ge(&tmpj, r, &tmpa); + secp256k1_gej_cmov(r, &tmpj, skew_1); if (size > 128) { - secp256k1_ge_from_storage(&correction, &correction_lam_stor); - secp256k1_ge_neg(&correction, &correction); - secp256k1_ge_mul_lambda(&correction, &correction); - secp256k1_gej_add_ge(r, r, &correction); + secp256k1_ge_neg(&tmpa, &pre_a_lam[0]); + secp256k1_gej_add_ge(&tmpj, r, &tmpa); + secp256k1_gej_cmov(r, &tmpj, skew_lam); } } + + secp256k1_fe_mul(&r->z, &r->z, &Z); } #endif /* SECP256K1_ECMULT_CONST_IMPL_H */ diff --git a/src/secp256k1/src/ecmult_gen_prec.h b/src/secp256k1/src/ecmult_gen_compute_table.h index 0cfcde9b79..e577158d92 100644 --- a/src/secp256k1/src/ecmult_gen_prec.h +++ b/src/secp256k1/src/ecmult_gen_compute_table.h @@ -4,11 +4,11 @@ * file COPYING or https://www.opensource.org/licenses/mit-license.php.* ***********************************************************************/ -#ifndef SECP256K1_ECMULT_GEN_PREC_H -#define SECP256K1_ECMULT_GEN_PREC_H +#ifndef SECP256K1_ECMULT_GEN_COMPUTE_TABLE_H +#define SECP256K1_ECMULT_GEN_COMPUTE_TABLE_H #include "ecmult_gen.h" -static void secp256k1_ecmult_gen_create_prec_table(secp256k1_ge_storage* table, const secp256k1_ge* gen, int bits); +static void secp256k1_ecmult_gen_compute_table(secp256k1_ge_storage* table, const secp256k1_ge* gen, int bits); -#endif /* SECP256K1_ECMULT_GEN_PREC_H */ +#endif /* SECP256K1_ECMULT_GEN_COMPUTE_TABLE_H */ diff --git a/src/secp256k1/src/ecmult_gen_prec_impl.h b/src/secp256k1/src/ecmult_gen_compute_table_impl.h index bac76c8b13..ff6a2992dc 100644 --- a/src/secp256k1/src/ecmult_gen_prec_impl.h +++ b/src/secp256k1/src/ecmult_gen_compute_table_impl.h @@ -4,16 +4,16 @@ * file COPYING or https://www.opensource.org/licenses/mit-license.php.* ***********************************************************************/ -#ifndef SECP256K1_ECMULT_GEN_PREC_IMPL_H -#define SECP256K1_ECMULT_GEN_PREC_IMPL_H +#ifndef SECP256K1_ECMULT_GEN_COMPUTE_TABLE_IMPL_H +#define SECP256K1_ECMULT_GEN_COMPUTE_TABLE_IMPL_H -#include "ecmult_gen_prec.h" +#include "ecmult_gen_compute_table.h" #include "group_impl.h" #include "field_impl.h" #include "ecmult_gen.h" #include "util.h" -static void secp256k1_ecmult_gen_create_prec_table(secp256k1_ge_storage* table, const secp256k1_ge* gen, int bits) { +static void secp256k1_ecmult_gen_compute_table(secp256k1_ge_storage* table, const secp256k1_ge* gen, int bits) { int g = ECMULT_GEN_PREC_G(bits); int n = ECMULT_GEN_PREC_N(bits); @@ -78,4 +78,4 @@ static void secp256k1_ecmult_gen_create_prec_table(secp256k1_ge_storage* table, free(prec); } -#endif /* SECP256K1_ECMULT_GEN_PREC_IMPL_H */ +#endif /* SECP256K1_ECMULT_GEN_COMPUTE_TABLE_IMPL_H */ diff --git a/src/secp256k1/src/ecmult_gen_impl.h b/src/secp256k1/src/ecmult_gen_impl.h index 6a6ab9a4b5..2c8a503acc 100644 --- a/src/secp256k1/src/ecmult_gen_impl.h +++ b/src/secp256k1/src/ecmult_gen_impl.h @@ -12,7 +12,7 @@ #include "group.h" #include "ecmult_gen.h" #include "hash_impl.h" -#include "ecmult_gen_static_prec_table.h" +#include "precomputed_ecmult_gen.h" static void secp256k1_ecmult_gen_context_build(secp256k1_ecmult_gen_context *ctx) { secp256k1_ecmult_gen_blind(ctx, NULL); diff --git a/src/secp256k1/src/ecmult_impl.h b/src/secp256k1/src/ecmult_impl.h index 5bd4d4d23d..bbc820c77c 100644 --- a/src/secp256k1/src/ecmult_impl.h +++ b/src/secp256k1/src/ecmult_impl.h @@ -14,7 +14,7 @@ #include "group.h" #include "scalar.h" #include "ecmult.h" -#include "ecmult_static_pre_g.h" +#include "precomputed_ecmult.h" #if defined(EXHAUSTIVE_TEST_ORDER) /* We need to lower these values for exhaustive tests because @@ -47,7 +47,7 @@ /* The number of objects allocated on the scratch space for ecmult_multi algorithms */ #define PIPPENGER_SCRATCH_OBJECTS 6 -#define STRAUSS_SCRATCH_OBJECTS 7 +#define STRAUSS_SCRATCH_OBJECTS 5 #define PIPPENGER_MAX_BUCKET_WINDOW 12 @@ -56,14 +56,23 @@ #define ECMULT_MAX_POINTS_PER_BATCH 5000000 -/** Fill a table 'prej' with precomputed odd multiples of a. Prej will contain - * the values [1*a,3*a,...,(2*n-1)*a], so it space for n values. zr[0] will - * contain prej[0].z / a.z. The other zr[i] values = prej[i].z / prej[i-1].z. - * Prej's Z values are undefined, except for the last value. +/** Fill a table 'pre_a' with precomputed odd multiples of a. + * pre_a will contain [1*a,3*a,...,(2*n-1)*a], so it needs space for n group elements. + * zr needs space for n field elements. + * + * Although pre_a is an array of _ge rather than _gej, it actually represents elements + * in Jacobian coordinates with their z coordinates omitted. The omitted z-coordinates + * can be recovered using z and zr. Using the notation z(b) to represent the omitted + * z coordinate of b: + * - z(pre_a[n-1]) = 'z' + * - z(pre_a[i-1]) = z(pre_a[i]) / zr[i] for n > i > 0 + * + * Lastly the zr[0] value, which isn't used above, is set so that: + * - a.z = z(pre_a[0]) / zr[0] */ -static void secp256k1_ecmult_odd_multiples_table(int n, secp256k1_gej *prej, secp256k1_fe *zr, const secp256k1_gej *a) { - secp256k1_gej d; - secp256k1_ge a_ge, d_ge; +static void secp256k1_ecmult_odd_multiples_table(int n, secp256k1_ge *pre_a, secp256k1_fe *zr, secp256k1_fe *z, const secp256k1_gej *a) { + secp256k1_gej d, ai; + secp256k1_ge d_ge; int i; VERIFY_CHECK(!a->infinity); @@ -71,75 +80,74 @@ static void secp256k1_ecmult_odd_multiples_table(int n, secp256k1_gej *prej, sec secp256k1_gej_double_var(&d, a, NULL); /* - * Perform the additions on an isomorphism where 'd' is affine: drop the z coordinate - * of 'd', and scale the 1P starting value's x/y coordinates without changing its z. + * Perform the additions using an isomorphic curve Y^2 = X^3 + 7*C^6 where C := d.z. + * The isomorphism, phi, maps a secp256k1 point (x, y) to the point (x*C^2, y*C^3) on the other curve. + * In Jacobian coordinates phi maps (x, y, z) to (x*C^2, y*C^3, z) or, equivalently to (x, y, z/C). + * + * phi(x, y, z) = (x*C^2, y*C^3, z) = (x, y, z/C) + * d_ge := phi(d) = (d.x, d.y, 1) + * ai := phi(a) = (a.x*C^2, a.y*C^3, a.z) + * + * The group addition functions work correctly on these isomorphic curves. + * In particular phi(d) is easy to represent in affine coordinates under this isomorphism. + * This lets us use the faster secp256k1_gej_add_ge_var group addition function that we wouldn't be able to use otherwise. */ - d_ge.x = d.x; - d_ge.y = d.y; - d_ge.infinity = 0; - - secp256k1_ge_set_gej_zinv(&a_ge, a, &d.z); - prej[0].x = a_ge.x; - prej[0].y = a_ge.y; - prej[0].z = a->z; - prej[0].infinity = 0; + secp256k1_ge_set_xy(&d_ge, &d.x, &d.y); + secp256k1_ge_set_gej_zinv(&pre_a[0], a, &d.z); + secp256k1_gej_set_ge(&ai, &pre_a[0]); + ai.z = a->z; + /* pre_a[0] is the point (a.x*C^2, a.y*C^3, a.z*C) which is equvalent to a. + * Set zr[0] to C, which is the ratio between the omitted z(pre_a[0]) value and a.z. + */ zr[0] = d.z; + for (i = 1; i < n; i++) { - secp256k1_gej_add_ge_var(&prej[i], &prej[i-1], &d_ge, &zr[i]); + secp256k1_gej_add_ge_var(&ai, &ai, &d_ge, &zr[i]); + secp256k1_ge_set_xy(&pre_a[i], &ai.x, &ai.y); } - /* - * Each point in 'prej' has a z coordinate too small by a factor of 'd.z'. Only - * the final point's z coordinate is actually used though, so just update that. + /* Multiply the last z-coordinate by C to undo the isomorphism. + * Since the z-coordinates of the pre_a values are implied by the zr array of z-coordinate ratios, + * undoing the isomorphism here undoes the isomorphism for all pre_a values. */ - secp256k1_fe_mul(&prej[n-1].z, &prej[n-1].z, &d.z); + secp256k1_fe_mul(z, &ai.z, &d.z); } -/** Fill a table 'pre' with precomputed odd multiples of a. - * - * The resulting point set is brought to a single constant Z denominator, stores the X and Y - * coordinates as ge_storage points in pre, and stores the global Z in rz. - * It only operates on tables sized for WINDOW_A wnaf multiples. - * - * To compute a*P + b*G, we compute a table for P using this function, - * and use the precomputed table in <ecmult_static_pre_g.h> for G. - */ -static void secp256k1_ecmult_odd_multiples_table_globalz_windowa(secp256k1_ge *pre, secp256k1_fe *globalz, const secp256k1_gej *a) { - secp256k1_gej prej[ECMULT_TABLE_SIZE(WINDOW_A)]; - secp256k1_fe zr[ECMULT_TABLE_SIZE(WINDOW_A)]; +#define SECP256K1_ECMULT_TABLE_VERIFY(n,w) \ + VERIFY_CHECK(((n) & 1) == 1); \ + VERIFY_CHECK((n) >= -((1 << ((w)-1)) - 1)); \ + VERIFY_CHECK((n) <= ((1 << ((w)-1)) - 1)); - /* Compute the odd multiples in Jacobian form. */ - secp256k1_ecmult_odd_multiples_table(ECMULT_TABLE_SIZE(WINDOW_A), prej, zr, a); - /* Bring them to the same Z denominator. */ - secp256k1_ge_globalz_set_table_gej(ECMULT_TABLE_SIZE(WINDOW_A), pre, globalz, prej, zr); +SECP256K1_INLINE static void secp256k1_ecmult_table_get_ge(secp256k1_ge *r, const secp256k1_ge *pre, int n, int w) { + SECP256K1_ECMULT_TABLE_VERIFY(n,w) + if (n > 0) { + *r = pre[(n-1)/2]; + } else { + *r = pre[(-n-1)/2]; + secp256k1_fe_negate(&(r->y), &(r->y), 1); + } } -/** The following two macro retrieves a particular odd multiple from a table - * of precomputed multiples. */ -#define ECMULT_TABLE_GET_GE(r,pre,n,w) do { \ - VERIFY_CHECK(((n) & 1) == 1); \ - VERIFY_CHECK((n) >= -((1 << ((w)-1)) - 1)); \ - VERIFY_CHECK((n) <= ((1 << ((w)-1)) - 1)); \ - if ((n) > 0) { \ - *(r) = (pre)[((n)-1)/2]; \ - } else { \ - *(r) = (pre)[(-(n)-1)/2]; \ - secp256k1_fe_negate(&((r)->y), &((r)->y), 1); \ - } \ -} while(0) - -#define ECMULT_TABLE_GET_GE_STORAGE(r,pre,n,w) do { \ - VERIFY_CHECK(((n) & 1) == 1); \ - VERIFY_CHECK((n) >= -((1 << ((w)-1)) - 1)); \ - VERIFY_CHECK((n) <= ((1 << ((w)-1)) - 1)); \ - if ((n) > 0) { \ - secp256k1_ge_from_storage((r), &(pre)[((n)-1)/2]); \ - } else { \ - secp256k1_ge_from_storage((r), &(pre)[(-(n)-1)/2]); \ - secp256k1_fe_negate(&((r)->y), &((r)->y), 1); \ - } \ -} while(0) +SECP256K1_INLINE static void secp256k1_ecmult_table_get_ge_lambda(secp256k1_ge *r, const secp256k1_ge *pre, const secp256k1_fe *x, int n, int w) { + SECP256K1_ECMULT_TABLE_VERIFY(n,w) + if (n > 0) { + secp256k1_ge_set_xy(r, &x[(n-1)/2], &pre[(n-1)/2].y); + } else { + secp256k1_ge_set_xy(r, &x[(-n-1)/2], &pre[(-n-1)/2].y); + secp256k1_fe_negate(&(r->y), &(r->y), 1); + } +} + +SECP256K1_INLINE static void secp256k1_ecmult_table_get_ge_storage(secp256k1_ge *r, const secp256k1_ge_storage *pre, int n, int w) { + SECP256K1_ECMULT_TABLE_VERIFY(n,w) + if (n > 0) { + secp256k1_ge_from_storage(r, &pre[(n-1)/2]); + } else { + secp256k1_ge_from_storage(r, &pre[(-n-1)/2]); + secp256k1_fe_negate(&(r->y), &(r->y), 1); + } +} /** Convert a number to WNAF notation. The number becomes represented by sum(2^i * wnaf[i], i=0..bits), * with the following guarantees: @@ -201,19 +209,16 @@ static int secp256k1_ecmult_wnaf(int *wnaf, int len, const secp256k1_scalar *a, } struct secp256k1_strauss_point_state { - secp256k1_scalar na_1, na_lam; int wnaf_na_1[129]; int wnaf_na_lam[129]; int bits_na_1; int bits_na_lam; - size_t input_pos; }; struct secp256k1_strauss_state { - secp256k1_gej* prej; - secp256k1_fe* zr; + /* aux is used to hold z-ratios, and then used to hold pre_a[i].x * BETA values. */ + secp256k1_fe* aux; secp256k1_ge* pre_a; - secp256k1_ge* pre_a_lam; struct secp256k1_strauss_point_state* ps; }; @@ -231,17 +236,19 @@ static void secp256k1_ecmult_strauss_wnaf(const struct secp256k1_strauss_state * size_t np; size_t no = 0; + secp256k1_fe_set_int(&Z, 1); for (np = 0; np < num; ++np) { + secp256k1_gej tmp; + secp256k1_scalar na_1, na_lam; if (secp256k1_scalar_is_zero(&na[np]) || secp256k1_gej_is_infinity(&a[np])) { continue; } - state->ps[no].input_pos = np; /* split na into na_1 and na_lam (where na = na_1 + na_lam*lambda, and na_1 and na_lam are ~128 bit) */ - secp256k1_scalar_split_lambda(&state->ps[no].na_1, &state->ps[no].na_lam, &na[np]); + secp256k1_scalar_split_lambda(&na_1, &na_lam, &na[np]); /* build wnaf representation for na_1 and na_lam. */ - state->ps[no].bits_na_1 = secp256k1_ecmult_wnaf(state->ps[no].wnaf_na_1, 129, &state->ps[no].na_1, WINDOW_A); - state->ps[no].bits_na_lam = secp256k1_ecmult_wnaf(state->ps[no].wnaf_na_lam, 129, &state->ps[no].na_lam, WINDOW_A); + state->ps[no].bits_na_1 = secp256k1_ecmult_wnaf(state->ps[no].wnaf_na_1, 129, &na_1, WINDOW_A); + state->ps[no].bits_na_lam = secp256k1_ecmult_wnaf(state->ps[no].wnaf_na_lam, 129, &na_lam, WINDOW_A); VERIFY_CHECK(state->ps[no].bits_na_1 <= 129); VERIFY_CHECK(state->ps[no].bits_na_lam <= 129); if (state->ps[no].bits_na_1 > bits) { @@ -250,40 +257,36 @@ static void secp256k1_ecmult_strauss_wnaf(const struct secp256k1_strauss_state * if (state->ps[no].bits_na_lam > bits) { bits = state->ps[no].bits_na_lam; } - ++no; - } - /* Calculate odd multiples of a. - * All multiples are brought to the same Z 'denominator', which is stored - * in Z. Due to secp256k1' isomorphism we can do all operations pretending - * that the Z coordinate was 1, use affine addition formulae, and correct - * the Z coordinate of the result once at the end. - * The exception is the precomputed G table points, which are actually - * affine. Compared to the base used for other points, they have a Z ratio - * of 1/Z, so we can use secp256k1_gej_add_zinv_var, which uses the same - * isomorphism to efficiently add with a known Z inverse. - */ - if (no > 0) { - /* Compute the odd multiples in Jacobian form. */ - secp256k1_ecmult_odd_multiples_table(ECMULT_TABLE_SIZE(WINDOW_A), state->prej, state->zr, &a[state->ps[0].input_pos]); - for (np = 1; np < no; ++np) { - secp256k1_gej tmp = a[state->ps[np].input_pos]; + /* Calculate odd multiples of a. + * All multiples are brought to the same Z 'denominator', which is stored + * in Z. Due to secp256k1' isomorphism we can do all operations pretending + * that the Z coordinate was 1, use affine addition formulae, and correct + * the Z coordinate of the result once at the end. + * The exception is the precomputed G table points, which are actually + * affine. Compared to the base used for other points, they have a Z ratio + * of 1/Z, so we can use secp256k1_gej_add_zinv_var, which uses the same + * isomorphism to efficiently add with a known Z inverse. + */ + tmp = a[np]; + if (no) { #ifdef VERIFY - secp256k1_fe_normalize_var(&(state->prej[(np - 1) * ECMULT_TABLE_SIZE(WINDOW_A) + ECMULT_TABLE_SIZE(WINDOW_A) - 1].z)); + secp256k1_fe_normalize_var(&Z); #endif - secp256k1_gej_rescale(&tmp, &(state->prej[(np - 1) * ECMULT_TABLE_SIZE(WINDOW_A) + ECMULT_TABLE_SIZE(WINDOW_A) - 1].z)); - secp256k1_ecmult_odd_multiples_table(ECMULT_TABLE_SIZE(WINDOW_A), state->prej + np * ECMULT_TABLE_SIZE(WINDOW_A), state->zr + np * ECMULT_TABLE_SIZE(WINDOW_A), &tmp); - secp256k1_fe_mul(state->zr + np * ECMULT_TABLE_SIZE(WINDOW_A), state->zr + np * ECMULT_TABLE_SIZE(WINDOW_A), &(a[state->ps[np].input_pos].z)); + secp256k1_gej_rescale(&tmp, &Z); } - /* Bring them to the same Z denominator. */ - secp256k1_ge_globalz_set_table_gej(ECMULT_TABLE_SIZE(WINDOW_A) * no, state->pre_a, &Z, state->prej, state->zr); - } else { - secp256k1_fe_set_int(&Z, 1); + secp256k1_ecmult_odd_multiples_table(ECMULT_TABLE_SIZE(WINDOW_A), state->pre_a + no * ECMULT_TABLE_SIZE(WINDOW_A), state->aux + no * ECMULT_TABLE_SIZE(WINDOW_A), &Z, &tmp); + if (no) secp256k1_fe_mul(state->aux + no * ECMULT_TABLE_SIZE(WINDOW_A), state->aux + no * ECMULT_TABLE_SIZE(WINDOW_A), &(a[np].z)); + + ++no; } + /* Bring them to the same Z denominator. */ + secp256k1_ge_table_set_globalz(ECMULT_TABLE_SIZE(WINDOW_A) * no, state->pre_a, state->aux); + for (np = 0; np < no; ++np) { for (i = 0; i < ECMULT_TABLE_SIZE(WINDOW_A); i++) { - secp256k1_ge_mul_lambda(&state->pre_a_lam[np * ECMULT_TABLE_SIZE(WINDOW_A) + i], &state->pre_a[np * ECMULT_TABLE_SIZE(WINDOW_A) + i]); + secp256k1_fe_mul(&state->aux[np * ECMULT_TABLE_SIZE(WINDOW_A) + i], &state->pre_a[np * ECMULT_TABLE_SIZE(WINDOW_A) + i].x, &secp256k1_const_beta); } } @@ -309,20 +312,20 @@ static void secp256k1_ecmult_strauss_wnaf(const struct secp256k1_strauss_state * secp256k1_gej_double_var(r, r, NULL); for (np = 0; np < no; ++np) { if (i < state->ps[np].bits_na_1 && (n = state->ps[np].wnaf_na_1[i])) { - ECMULT_TABLE_GET_GE(&tmpa, state->pre_a + np * ECMULT_TABLE_SIZE(WINDOW_A), n, WINDOW_A); + secp256k1_ecmult_table_get_ge(&tmpa, state->pre_a + np * ECMULT_TABLE_SIZE(WINDOW_A), n, WINDOW_A); secp256k1_gej_add_ge_var(r, r, &tmpa, NULL); } if (i < state->ps[np].bits_na_lam && (n = state->ps[np].wnaf_na_lam[i])) { - ECMULT_TABLE_GET_GE(&tmpa, state->pre_a_lam + np * ECMULT_TABLE_SIZE(WINDOW_A), n, WINDOW_A); + secp256k1_ecmult_table_get_ge_lambda(&tmpa, state->pre_a + np * ECMULT_TABLE_SIZE(WINDOW_A), state->aux + np * ECMULT_TABLE_SIZE(WINDOW_A), n, WINDOW_A); secp256k1_gej_add_ge_var(r, r, &tmpa, NULL); } } if (i < bits_ng_1 && (n = wnaf_ng_1[i])) { - ECMULT_TABLE_GET_GE_STORAGE(&tmpa, secp256k1_pre_g, n, WINDOW_G); + secp256k1_ecmult_table_get_ge_storage(&tmpa, secp256k1_pre_g, n, WINDOW_G); secp256k1_gej_add_zinv_var(r, r, &tmpa, &Z); } if (i < bits_ng_128 && (n = wnaf_ng_128[i])) { - ECMULT_TABLE_GET_GE_STORAGE(&tmpa, secp256k1_pre_g_128, n, WINDOW_G); + secp256k1_ecmult_table_get_ge_storage(&tmpa, secp256k1_pre_g_128, n, WINDOW_G); secp256k1_gej_add_zinv_var(r, r, &tmpa, &Z); } } @@ -333,23 +336,19 @@ static void secp256k1_ecmult_strauss_wnaf(const struct secp256k1_strauss_state * } static void secp256k1_ecmult(secp256k1_gej *r, const secp256k1_gej *a, const secp256k1_scalar *na, const secp256k1_scalar *ng) { - secp256k1_gej prej[ECMULT_TABLE_SIZE(WINDOW_A)]; - secp256k1_fe zr[ECMULT_TABLE_SIZE(WINDOW_A)]; + secp256k1_fe aux[ECMULT_TABLE_SIZE(WINDOW_A)]; secp256k1_ge pre_a[ECMULT_TABLE_SIZE(WINDOW_A)]; struct secp256k1_strauss_point_state ps[1]; - secp256k1_ge pre_a_lam[ECMULT_TABLE_SIZE(WINDOW_A)]; struct secp256k1_strauss_state state; - state.prej = prej; - state.zr = zr; + state.aux = aux; state.pre_a = pre_a; - state.pre_a_lam = pre_a_lam; state.ps = ps; secp256k1_ecmult_strauss_wnaf(&state, r, 1, a, na, ng); } static size_t secp256k1_strauss_scratch_size(size_t n_points) { - static const size_t point_size = (2 * sizeof(secp256k1_ge) + sizeof(secp256k1_gej) + sizeof(secp256k1_fe)) * ECMULT_TABLE_SIZE(WINDOW_A) + sizeof(struct secp256k1_strauss_point_state) + sizeof(secp256k1_gej) + sizeof(secp256k1_scalar); + static const size_t point_size = (sizeof(secp256k1_ge) + sizeof(secp256k1_fe)) * ECMULT_TABLE_SIZE(WINDOW_A) + sizeof(struct secp256k1_strauss_point_state) + sizeof(secp256k1_gej) + sizeof(secp256k1_scalar); return n_points*point_size; } @@ -370,13 +369,11 @@ static int secp256k1_ecmult_strauss_batch(const secp256k1_callback* error_callba * constant and strauss_scratch_size accordingly. */ points = (secp256k1_gej*)secp256k1_scratch_alloc(error_callback, scratch, n_points * sizeof(secp256k1_gej)); scalars = (secp256k1_scalar*)secp256k1_scratch_alloc(error_callback, scratch, n_points * sizeof(secp256k1_scalar)); - state.prej = (secp256k1_gej*)secp256k1_scratch_alloc(error_callback, scratch, n_points * ECMULT_TABLE_SIZE(WINDOW_A) * sizeof(secp256k1_gej)); - state.zr = (secp256k1_fe*)secp256k1_scratch_alloc(error_callback, scratch, n_points * ECMULT_TABLE_SIZE(WINDOW_A) * sizeof(secp256k1_fe)); + state.aux = (secp256k1_fe*)secp256k1_scratch_alloc(error_callback, scratch, n_points * ECMULT_TABLE_SIZE(WINDOW_A) * sizeof(secp256k1_fe)); state.pre_a = (secp256k1_ge*)secp256k1_scratch_alloc(error_callback, scratch, n_points * ECMULT_TABLE_SIZE(WINDOW_A) * sizeof(secp256k1_ge)); - state.pre_a_lam = (secp256k1_ge*)secp256k1_scratch_alloc(error_callback, scratch, n_points * ECMULT_TABLE_SIZE(WINDOW_A) * sizeof(secp256k1_ge)); state.ps = (struct secp256k1_strauss_point_state*)secp256k1_scratch_alloc(error_callback, scratch, n_points * sizeof(struct secp256k1_strauss_point_state)); - if (points == NULL || scalars == NULL || state.prej == NULL || state.zr == NULL || state.pre_a == NULL || state.pre_a_lam == NULL || state.ps == NULL) { + if (points == NULL || scalars == NULL || state.aux == NULL || state.pre_a == NULL || state.ps == NULL) { secp256k1_scratch_apply_checkpoint(error_callback, scratch, scratch_checkpoint); return 0; } diff --git a/src/secp256k1/src/field.h b/src/secp256k1/src/field.h index 55679a2fc1..2584a494ee 100644 --- a/src/secp256k1/src/field.h +++ b/src/secp256k1/src/field.h @@ -32,6 +32,12 @@ #error "Please select wide multiplication implementation" #endif +static const secp256k1_fe secp256k1_fe_one = SECP256K1_FE_CONST(0, 0, 0, 0, 0, 0, 0, 1); +static const secp256k1_fe secp256k1_const_beta = SECP256K1_FE_CONST( + 0x7ae96a2bul, 0x657c0710ul, 0x6e64479eul, 0xac3434e9ul, + 0x9cf04975ul, 0x12f58995ul, 0xc1396c28ul, 0x719501eeul +); + /** Normalize a field element. This brings the field element to a canonical representation, reduces * its magnitude to 1, and reduces it modulo field size `p`. */ @@ -124,4 +130,13 @@ static void secp256k1_fe_storage_cmov(secp256k1_fe_storage *r, const secp256k1_f /** If flag is true, set *r equal to *a; otherwise leave it. Constant-time. Both *r and *a must be initialized.*/ static void secp256k1_fe_cmov(secp256k1_fe *r, const secp256k1_fe *a, int flag); +/** Halves the value of a field element modulo the field prime. Constant-time. + * For an input magnitude 'm', the output magnitude is set to 'floor(m/2) + 1'. + * The output is not guaranteed to be normalized, regardless of the input. */ +static void secp256k1_fe_half(secp256k1_fe *r); + +/** Sets each limb of 'r' to its upper bound at magnitude 'm'. The output will also have its + * magnitude set to 'm' and is normalized if (and only if) 'm' is zero. */ +static void secp256k1_fe_get_bounds(secp256k1_fe *r, int m); + #endif /* SECP256K1_FIELD_H */ diff --git a/src/secp256k1/src/field_10x26_impl.h b/src/secp256k1/src/field_10x26_impl.h index 4363e727e7..21742bf6eb 100644 --- a/src/secp256k1/src/field_10x26_impl.h +++ b/src/secp256k1/src/field_10x26_impl.h @@ -11,6 +11,15 @@ #include "field.h" #include "modinv32_impl.h" +/** See the comment at the top of field_5x52_impl.h for more details. + * + * Here, we represent field elements as 10 uint32_t's in base 2^26, least significant first, + * where limbs can contain >26 bits. + * A magnitude M means: + * - 2*M*(2^22-1) is the max (inclusive) of the most significant limb + * - 2*M*(2^26-1) is the max (inclusive) of the remaining limbs + */ + #ifdef VERIFY static void secp256k1_fe_verify(const secp256k1_fe *a) { const uint32_t *d = a->n; @@ -40,6 +49,26 @@ static void secp256k1_fe_verify(const secp256k1_fe *a) { } #endif +static void secp256k1_fe_get_bounds(secp256k1_fe *r, int m) { + VERIFY_CHECK(m >= 0); + VERIFY_CHECK(m <= 2048); + r->n[0] = 0x3FFFFFFUL * 2 * m; + r->n[1] = 0x3FFFFFFUL * 2 * m; + r->n[2] = 0x3FFFFFFUL * 2 * m; + r->n[3] = 0x3FFFFFFUL * 2 * m; + r->n[4] = 0x3FFFFFFUL * 2 * m; + r->n[5] = 0x3FFFFFFUL * 2 * m; + r->n[6] = 0x3FFFFFFUL * 2 * m; + r->n[7] = 0x3FFFFFFUL * 2 * m; + r->n[8] = 0x3FFFFFFUL * 2 * m; + r->n[9] = 0x03FFFFFUL * 2 * m; +#ifdef VERIFY + r->magnitude = m; + r->normalized = (m == 0); + secp256k1_fe_verify(r); +#endif +} + static void secp256k1_fe_normalize(secp256k1_fe *r) { uint32_t t0 = r->n[0], t1 = r->n[1], t2 = r->n[2], t3 = r->n[3], t4 = r->n[4], t5 = r->n[5], t6 = r->n[6], t7 = r->n[7], t8 = r->n[8], t9 = r->n[9]; @@ -391,6 +420,10 @@ SECP256K1_INLINE static void secp256k1_fe_negate(secp256k1_fe *r, const secp256k #ifdef VERIFY VERIFY_CHECK(a->magnitude <= m); secp256k1_fe_verify(a); + VERIFY_CHECK(0x3FFFC2FUL * 2 * (m + 1) >= 0x3FFFFFFUL * 2 * m); + VERIFY_CHECK(0x3FFFFBFUL * 2 * (m + 1) >= 0x3FFFFFFUL * 2 * m); + VERIFY_CHECK(0x3FFFFFFUL * 2 * (m + 1) >= 0x3FFFFFFUL * 2 * m); + VERIFY_CHECK(0x03FFFFFUL * 2 * (m + 1) >= 0x03FFFFFUL * 2 * m); #endif r->n[0] = 0x3FFFC2FUL * 2 * (m + 1) - a->n[0]; r->n[1] = 0x3FFFFBFUL * 2 * (m + 1) - a->n[1]; @@ -1120,6 +1153,82 @@ static SECP256K1_INLINE void secp256k1_fe_cmov(secp256k1_fe *r, const secp256k1_ #endif } +static SECP256K1_INLINE void secp256k1_fe_half(secp256k1_fe *r) { + uint32_t t0 = r->n[0], t1 = r->n[1], t2 = r->n[2], t3 = r->n[3], t4 = r->n[4], + t5 = r->n[5], t6 = r->n[6], t7 = r->n[7], t8 = r->n[8], t9 = r->n[9]; + uint32_t one = (uint32_t)1; + uint32_t mask = -(t0 & one) >> 6; + +#ifdef VERIFY + secp256k1_fe_verify(r); + VERIFY_CHECK(r->magnitude < 32); +#endif + + /* Bounds analysis (over the rationals). + * + * Let m = r->magnitude + * C = 0x3FFFFFFUL * 2 + * D = 0x03FFFFFUL * 2 + * + * Initial bounds: t0..t8 <= C * m + * t9 <= D * m + */ + + t0 += 0x3FFFC2FUL & mask; + t1 += 0x3FFFFBFUL & mask; + t2 += mask; + t3 += mask; + t4 += mask; + t5 += mask; + t6 += mask; + t7 += mask; + t8 += mask; + t9 += mask >> 4; + + VERIFY_CHECK((t0 & one) == 0); + + /* t0..t8: added <= C/2 + * t9: added <= D/2 + * + * Current bounds: t0..t8 <= C * (m + 1/2) + * t9 <= D * (m + 1/2) + */ + + r->n[0] = (t0 >> 1) + ((t1 & one) << 25); + r->n[1] = (t1 >> 1) + ((t2 & one) << 25); + r->n[2] = (t2 >> 1) + ((t3 & one) << 25); + r->n[3] = (t3 >> 1) + ((t4 & one) << 25); + r->n[4] = (t4 >> 1) + ((t5 & one) << 25); + r->n[5] = (t5 >> 1) + ((t6 & one) << 25); + r->n[6] = (t6 >> 1) + ((t7 & one) << 25); + r->n[7] = (t7 >> 1) + ((t8 & one) << 25); + r->n[8] = (t8 >> 1) + ((t9 & one) << 25); + r->n[9] = (t9 >> 1); + + /* t0..t8: shifted right and added <= C/4 + 1/2 + * t9: shifted right + * + * Current bounds: t0..t8 <= C * (m/2 + 1/2) + * t9 <= D * (m/2 + 1/4) + */ + +#ifdef VERIFY + /* Therefore the output magnitude (M) has to be set such that: + * t0..t8: C * M >= C * (m/2 + 1/2) + * t9: D * M >= D * (m/2 + 1/4) + * + * It suffices for all limbs that, for any input magnitude m: + * M >= m/2 + 1/2 + * + * and since we want the smallest such integer value for M: + * M == floor(m/2) + 1 + */ + r->magnitude = (r->magnitude >> 1) + 1; + r->normalized = 0; + secp256k1_fe_verify(r); +#endif +} + static SECP256K1_INLINE void secp256k1_fe_storage_cmov(secp256k1_fe_storage *r, const secp256k1_fe_storage *a, int flag) { uint32_t mask0, mask1; VG_CHECK_VERIFY(r->n, sizeof(r->n)); diff --git a/src/secp256k1/src/field_5x52_impl.h b/src/secp256k1/src/field_5x52_impl.h index b56bdd1353..6bd202f587 100644 --- a/src/secp256k1/src/field_5x52_impl.h +++ b/src/secp256k1/src/field_5x52_impl.h @@ -22,11 +22,18 @@ #endif /** Implements arithmetic modulo FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE FFFFFC2F, - * represented as 5 uint64_t's in base 2^52. The values are allowed to contain >52 each. In particular, - * each FieldElem has a 'magnitude' associated with it. Internally, a magnitude M means each element - * is at most M*(2^53-1), except the most significant one, which is limited to M*(2^49-1). All operations - * accept any input with magnitude at most M, and have different rules for propagating magnitude to their - * output. + * represented as 5 uint64_t's in base 2^52, least significant first. Note that the limbs are allowed to + * contain >52 bits each. + * + * Each field element has a 'magnitude' associated with it. Internally, a magnitude M means: + * - 2*M*(2^48-1) is the max (inclusive) of the most significant limb + * - 2*M*(2^52-1) is the max (inclusive) of the remaining limbs + * + * Operations have different rules for propagating magnitude to their outputs. If an operation takes a + * magnitude M as a parameter, that means the magnitude of input field elements can be at most M (inclusive). + * + * Each field element also has a 'normalized' flag. A field element is normalized if its magnitude is either + * 0 or 1, and its value is already reduced modulo the order of the field. */ #ifdef VERIFY @@ -51,6 +58,21 @@ static void secp256k1_fe_verify(const secp256k1_fe *a) { } #endif +static void secp256k1_fe_get_bounds(secp256k1_fe *r, int m) { + VERIFY_CHECK(m >= 0); + VERIFY_CHECK(m <= 2048); + r->n[0] = 0xFFFFFFFFFFFFFULL * 2 * m; + r->n[1] = 0xFFFFFFFFFFFFFULL * 2 * m; + r->n[2] = 0xFFFFFFFFFFFFFULL * 2 * m; + r->n[3] = 0xFFFFFFFFFFFFFULL * 2 * m; + r->n[4] = 0x0FFFFFFFFFFFFULL * 2 * m; +#ifdef VERIFY + r->magnitude = m; + r->normalized = (m == 0); + secp256k1_fe_verify(r); +#endif +} + static void secp256k1_fe_normalize(secp256k1_fe *r) { uint64_t t0 = r->n[0], t1 = r->n[1], t2 = r->n[2], t3 = r->n[3], t4 = r->n[4]; @@ -377,6 +399,9 @@ SECP256K1_INLINE static void secp256k1_fe_negate(secp256k1_fe *r, const secp256k #ifdef VERIFY VERIFY_CHECK(a->magnitude <= m); secp256k1_fe_verify(a); + VERIFY_CHECK(0xFFFFEFFFFFC2FULL * 2 * (m + 1) >= 0xFFFFFFFFFFFFFULL * 2 * m); + VERIFY_CHECK(0xFFFFFFFFFFFFFULL * 2 * (m + 1) >= 0xFFFFFFFFFFFFFULL * 2 * m); + VERIFY_CHECK(0x0FFFFFFFFFFFFULL * 2 * (m + 1) >= 0x0FFFFFFFFFFFFULL * 2 * m); #endif r->n[0] = 0xFFFFEFFFFFC2FULL * 2 * (m + 1) - a->n[0]; r->n[1] = 0xFFFFFFFFFFFFFULL * 2 * (m + 1) - a->n[1]; @@ -467,6 +492,71 @@ static SECP256K1_INLINE void secp256k1_fe_cmov(secp256k1_fe *r, const secp256k1_ #endif } +static SECP256K1_INLINE void secp256k1_fe_half(secp256k1_fe *r) { + uint64_t t0 = r->n[0], t1 = r->n[1], t2 = r->n[2], t3 = r->n[3], t4 = r->n[4]; + uint64_t one = (uint64_t)1; + uint64_t mask = -(t0 & one) >> 12; + +#ifdef VERIFY + secp256k1_fe_verify(r); + VERIFY_CHECK(r->magnitude < 32); +#endif + + /* Bounds analysis (over the rationals). + * + * Let m = r->magnitude + * C = 0xFFFFFFFFFFFFFULL * 2 + * D = 0x0FFFFFFFFFFFFULL * 2 + * + * Initial bounds: t0..t3 <= C * m + * t4 <= D * m + */ + + t0 += 0xFFFFEFFFFFC2FULL & mask; + t1 += mask; + t2 += mask; + t3 += mask; + t4 += mask >> 4; + + VERIFY_CHECK((t0 & one) == 0); + + /* t0..t3: added <= C/2 + * t4: added <= D/2 + * + * Current bounds: t0..t3 <= C * (m + 1/2) + * t4 <= D * (m + 1/2) + */ + + r->n[0] = (t0 >> 1) + ((t1 & one) << 51); + r->n[1] = (t1 >> 1) + ((t2 & one) << 51); + r->n[2] = (t2 >> 1) + ((t3 & one) << 51); + r->n[3] = (t3 >> 1) + ((t4 & one) << 51); + r->n[4] = (t4 >> 1); + + /* t0..t3: shifted right and added <= C/4 + 1/2 + * t4: shifted right + * + * Current bounds: t0..t3 <= C * (m/2 + 1/2) + * t4 <= D * (m/2 + 1/4) + */ + +#ifdef VERIFY + /* Therefore the output magnitude (M) has to be set such that: + * t0..t3: C * M >= C * (m/2 + 1/2) + * t4: D * M >= D * (m/2 + 1/4) + * + * It suffices for all limbs that, for any input magnitude m: + * M >= m/2 + 1/2 + * + * and since we want the smallest such integer value for M: + * M == floor(m/2) + 1 + */ + r->magnitude = (r->magnitude >> 1) + 1; + r->normalized = 0; + secp256k1_fe_verify(r); +#endif +} + static SECP256K1_INLINE void secp256k1_fe_storage_cmov(secp256k1_fe_storage *r, const secp256k1_fe_storage *a, int flag) { uint64_t mask0, mask1; VG_CHECK_VERIFY(r->n, sizeof(r->n)); diff --git a/src/secp256k1/src/field_impl.h b/src/secp256k1/src/field_impl.h index 374284a1f4..0a4a04d9ac 100644 --- a/src/secp256k1/src/field_impl.h +++ b/src/secp256k1/src/field_impl.h @@ -135,6 +135,4 @@ static int secp256k1_fe_sqrt(secp256k1_fe *r, const secp256k1_fe *a) { return secp256k1_fe_equal(&t1, a); } -static const secp256k1_fe secp256k1_fe_one = SECP256K1_FE_CONST(0, 0, 0, 0, 0, 0, 0, 1); - #endif /* SECP256K1_FIELD_IMPL_H */ diff --git a/src/secp256k1/src/gen_ecmult_static_pre_g.c b/src/secp256k1/src/gen_ecmult_static_pre_g.c deleted file mode 100644 index ba1d1f17d7..0000000000 --- a/src/secp256k1/src/gen_ecmult_static_pre_g.c +++ /dev/null @@ -1,131 +0,0 @@ -/***************************************************************************************************** - * Copyright (c) 2013, 2014, 2017, 2021 Pieter Wuille, Andrew Poelstra, Jonas Nick, Russell O'Connor * - * Distributed under the MIT software license, see the accompanying * - * file COPYING or https://www.opensource.org/licenses/mit-license.php. * - *****************************************************************************************************/ - -#include <inttypes.h> -#include <stdio.h> - -/* Autotools creates libsecp256k1-config.h, of which ECMULT_WINDOW_SIZE is needed. - ifndef guard so downstream users can define their own if they do not use autotools. */ -#if !defined(ECMULT_WINDOW_SIZE) -#include "libsecp256k1-config.h" -#endif - -#include "../include/secp256k1.h" -#include "assumptions.h" -#include "util.h" -#include "field_impl.h" -#include "group_impl.h" -#include "ecmult.h" - -void print_table(FILE *fp, const char *name, int window_g, const secp256k1_gej *gen, int with_conditionals) { - static secp256k1_gej gj; - static secp256k1_ge ge, dgen; - static secp256k1_ge_storage ges; - int j; - int i; - - gj = *gen; - secp256k1_ge_set_gej_var(&ge, &gj); - secp256k1_ge_to_storage(&ges, &ge); - - fprintf(fp, "static const secp256k1_ge_storage %s[ECMULT_TABLE_SIZE(WINDOW_G)] = {\n", name); - fprintf(fp, " S(%"PRIx32",%"PRIx32",%"PRIx32",%"PRIx32",%"PRIx32",%"PRIx32",%"PRIx32",%"PRIx32 - ",%"PRIx32",%"PRIx32",%"PRIx32",%"PRIx32",%"PRIx32",%"PRIx32",%"PRIx32",%"PRIx32")\n", - SECP256K1_GE_STORAGE_CONST_GET(ges)); - - secp256k1_gej_double_var(&gj, gen, NULL); - secp256k1_ge_set_gej_var(&dgen, &gj); - - j = 1; - for(i = 3; i <= window_g; ++i) { - if (with_conditionals) { - fprintf(fp, "#if ECMULT_TABLE_SIZE(WINDOW_G) > %ld\n", ECMULT_TABLE_SIZE(i-1)); - } - for(;j < ECMULT_TABLE_SIZE(i); ++j) { - secp256k1_gej_set_ge(&gj, &ge); - secp256k1_gej_add_ge_var(&gj, &gj, &dgen, NULL); - secp256k1_ge_set_gej_var(&ge, &gj); - secp256k1_ge_to_storage(&ges, &ge); - - fprintf(fp, ",S(%"PRIx32",%"PRIx32",%"PRIx32",%"PRIx32",%"PRIx32",%"PRIx32",%"PRIx32",%"PRIx32 - ",%"PRIx32",%"PRIx32",%"PRIx32",%"PRIx32",%"PRIx32",%"PRIx32",%"PRIx32",%"PRIx32")\n", - SECP256K1_GE_STORAGE_CONST_GET(ges)); - } - if (with_conditionals) { - fprintf(fp, "#endif\n"); - } - } - fprintf(fp, "};\n"); -} - -void print_two_tables(FILE *fp, int window_g, const secp256k1_ge *g, int with_conditionals) { - secp256k1_gej gj; - int i; - - secp256k1_gej_set_ge(&gj, g); - print_table(fp, "secp256k1_pre_g", window_g, &gj, with_conditionals); - for (i = 0; i < 128; ++i) { - secp256k1_gej_double_var(&gj, &gj, NULL); - } - print_table(fp, "secp256k1_pre_g_128", window_g, &gj, with_conditionals); -} - -int main(void) { - const secp256k1_ge g = SECP256K1_G; - const secp256k1_ge g_13 = SECP256K1_G_ORDER_13; - const secp256k1_ge g_199 = SECP256K1_G_ORDER_199; - const int window_g_13 = 4; - const int window_g_199 = 8; - FILE* fp; - - fp = fopen("src/ecmult_static_pre_g.h","w"); - if (fp == NULL) { - fprintf(stderr, "Could not open src/ecmult_static_pre_g.h for writing!\n"); - return -1; - } - - fprintf(fp, "/* This file was automatically generated by gen_ecmult_static_pre_g. */\n"); - fprintf(fp, "/* This file contains an array secp256k1_pre_g with odd multiples of the base point G and\n"); - fprintf(fp, " * an array secp256k1_pre_g_128 with odd multiples of 2^128*G for accelerating the computation of a*P + b*G.\n"); - fprintf(fp, " */\n"); - fprintf(fp, "#ifndef SECP256K1_ECMULT_STATIC_PRE_G_H\n"); - fprintf(fp, "#define SECP256K1_ECMULT_STATIC_PRE_G_H\n"); - fprintf(fp, "#include \"group.h\"\n"); - fprintf(fp, "#ifdef S\n"); - fprintf(fp, " #error macro identifier S already in use.\n"); - fprintf(fp, "#endif\n"); - fprintf(fp, "#define S(a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p) " - "SECP256K1_GE_STORAGE_CONST(0x##a##u,0x##b##u,0x##c##u,0x##d##u,0x##e##u,0x##f##u,0x##g##u," - "0x##h##u,0x##i##u,0x##j##u,0x##k##u,0x##l##u,0x##m##u,0x##n##u,0x##o##u,0x##p##u)\n"); - fprintf(fp, "#if ECMULT_TABLE_SIZE(ECMULT_WINDOW_SIZE) > %ld\n", ECMULT_TABLE_SIZE(ECMULT_WINDOW_SIZE)); - fprintf(fp, " #error configuration mismatch, invalid ECMULT_WINDOW_SIZE. Try deleting ecmult_static_pre_g.h before the build.\n"); - fprintf(fp, "#endif\n"); - fprintf(fp, "#if defined(EXHAUSTIVE_TEST_ORDER)\n"); - fprintf(fp, "#if EXHAUSTIVE_TEST_ORDER == 13\n"); - fprintf(fp, "#define WINDOW_G %d\n", window_g_13); - - print_two_tables(fp, window_g_13, &g_13, 0); - - fprintf(fp, "#elif EXHAUSTIVE_TEST_ORDER == 199\n"); - fprintf(fp, "#define WINDOW_G %d\n", window_g_199); - - print_two_tables(fp, window_g_199, &g_199, 0); - - fprintf(fp, "#else\n"); - fprintf(fp, " #error No known generator for the specified exhaustive test group order.\n"); - fprintf(fp, "#endif\n"); - fprintf(fp, "#else /* !defined(EXHAUSTIVE_TEST_ORDER) */\n"); - fprintf(fp, "#define WINDOW_G ECMULT_WINDOW_SIZE\n"); - - print_two_tables(fp, ECMULT_WINDOW_SIZE, &g, 1); - - fprintf(fp, "#endif\n"); - fprintf(fp, "#undef S\n"); - fprintf(fp, "#endif\n"); - fclose(fp); - - return 0; -} diff --git a/src/secp256k1/src/group.h b/src/secp256k1/src/group.h index b9cd334dae..bb7dae1cf7 100644 --- a/src/secp256k1/src/group.h +++ b/src/secp256k1/src/group.h @@ -9,7 +9,10 @@ #include "field.h" -/** A group element of the secp256k1 curve, in affine coordinates. */ +/** A group element in affine coordinates on the secp256k1 curve, + * or occasionally on an isomorphic curve of the form y^2 = x^3 + 7*t^6. + * Note: For exhaustive test mode, secp256k1 is replaced by a small subgroup of a different curve. + */ typedef struct { secp256k1_fe x; secp256k1_fe y; @@ -19,7 +22,9 @@ typedef struct { #define SECP256K1_GE_CONST(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p) {SECP256K1_FE_CONST((a),(b),(c),(d),(e),(f),(g),(h)), SECP256K1_FE_CONST((i),(j),(k),(l),(m),(n),(o),(p)), 0} #define SECP256K1_GE_CONST_INFINITY {SECP256K1_FE_CONST(0, 0, 0, 0, 0, 0, 0, 0), SECP256K1_FE_CONST(0, 0, 0, 0, 0, 0, 0, 0), 1} -/** A group element of the secp256k1 curve, in jacobian coordinates. */ +/** A group element of the secp256k1 curve, in jacobian coordinates. + * Note: For exhastive test mode, sepc256k1 is replaced by a small subgroup of a different curve. + */ typedef struct { secp256k1_fe x; /* actual X: x/z^2 */ secp256k1_fe y; /* actual Y: y/z^3 */ @@ -64,12 +69,24 @@ static void secp256k1_ge_set_gej_var(secp256k1_ge *r, secp256k1_gej *a); /** Set a batch of group elements equal to the inputs given in jacobian coordinates */ static void secp256k1_ge_set_all_gej_var(secp256k1_ge *r, const secp256k1_gej *a, size_t len); -/** Bring a batch inputs given in jacobian coordinates (with known z-ratios) to - * the same global z "denominator". zr must contain the known z-ratios such - * that mul(a[i].z, zr[i+1]) == a[i+1].z. zr[0] is ignored. The x and y - * coordinates of the result are stored in r, the common z coordinate is - * stored in globalz. */ -static void secp256k1_ge_globalz_set_table_gej(size_t len, secp256k1_ge *r, secp256k1_fe *globalz, const secp256k1_gej *a, const secp256k1_fe *zr); +/** Bring a batch of inputs to the same global z "denominator", based on ratios between + * (omitted) z coordinates of adjacent elements. + * + * Although the elements a[i] are _ge rather than _gej, they actually represent elements + * in Jacobian coordinates with their z coordinates omitted. + * + * Using the notation z(b) to represent the omitted z coordinate of b, the array zr of + * z coordinate ratios must satisfy zr[i] == z(a[i]) / z(a[i-1]) for 0 < 'i' < len. + * The zr[0] value is unused. + * + * This function adjusts the coordinates of 'a' in place so that for all 'i', z(a[i]) == z(a[len-1]). + * In other words, the initial value of z(a[len-1]) becomes the global z "denominator". Only the + * a[i].x and a[i].y coordinates are explicitly modified; the adjustment of the omitted z coordinate is + * implicit. + * + * The coordinates of the final element a[len-1] are not changed. + */ +static void secp256k1_ge_table_set_globalz(size_t len, secp256k1_ge *a, const secp256k1_fe *zr); /** Set a group element (affine) equal to the point at infinity. */ static void secp256k1_ge_set_infinity(secp256k1_ge *r); @@ -125,6 +142,9 @@ static void secp256k1_ge_to_storage(secp256k1_ge_storage *r, const secp256k1_ge static void secp256k1_ge_from_storage(secp256k1_ge *r, const secp256k1_ge_storage *a); /** If flag is true, set *r equal to *a; otherwise leave it. Constant-time. Both *r and *a must be initialized.*/ +static void secp256k1_gej_cmov(secp256k1_gej *r, const secp256k1_gej *a, int flag); + +/** If flag is true, set *r equal to *a; otherwise leave it. Constant-time. Both *r and *a must be initialized.*/ static void secp256k1_ge_storage_cmov(secp256k1_ge_storage *r, const secp256k1_ge_storage *a, int flag); /** Rescale a jacobian point by b which must be non-zero. Constant-time. */ diff --git a/src/secp256k1/src/group_impl.h b/src/secp256k1/src/group_impl.h index bce9fbdad5..63735ab682 100644 --- a/src/secp256k1/src/group_impl.h +++ b/src/secp256k1/src/group_impl.h @@ -161,27 +161,26 @@ static void secp256k1_ge_set_all_gej_var(secp256k1_ge *r, const secp256k1_gej *a } } -static void secp256k1_ge_globalz_set_table_gej(size_t len, secp256k1_ge *r, secp256k1_fe *globalz, const secp256k1_gej *a, const secp256k1_fe *zr) { +static void secp256k1_ge_table_set_globalz(size_t len, secp256k1_ge *a, const secp256k1_fe *zr) { size_t i = len - 1; secp256k1_fe zs; if (len > 0) { - /* The z of the final point gives us the "global Z" for the table. */ - r[i].x = a[i].x; - r[i].y = a[i].y; /* Ensure all y values are in weak normal form for fast negation of points */ - secp256k1_fe_normalize_weak(&r[i].y); - *globalz = a[i].z; - r[i].infinity = 0; + secp256k1_fe_normalize_weak(&a[i].y); zs = zr[i]; /* Work our way backwards, using the z-ratios to scale the x/y values. */ while (i > 0) { + secp256k1_gej tmpa; if (i != len - 1) { secp256k1_fe_mul(&zs, &zs, &zr[i]); } i--; - secp256k1_ge_set_gej_zinv(&r[i], &a[i], &zs); + tmpa.x = a[i].x; + tmpa.y = a[i].y; + tmpa.infinity = 0; + secp256k1_ge_set_gej_zinv(&a[i], &tmpa, &zs); } } } @@ -272,37 +271,35 @@ static int secp256k1_ge_is_valid_var(const secp256k1_ge *a) { } static SECP256K1_INLINE void secp256k1_gej_double(secp256k1_gej *r, const secp256k1_gej *a) { - /* Operations: 3 mul, 4 sqr, 0 normalize, 12 mul_int/add/negate. - * - * Note that there is an implementation described at - * https://hyperelliptic.org/EFD/g1p/auto-shortw-jacobian-0.html#doubling-dbl-2009-l - * which trades a multiply for a square, but in practice this is actually slower, - * mainly because it requires more normalizations. - */ - secp256k1_fe t1,t2,t3,t4; + /* Operations: 3 mul, 4 sqr, 8 add/half/mul_int/negate */ + secp256k1_fe l, s, t; r->infinity = a->infinity; - secp256k1_fe_mul(&r->z, &a->z, &a->y); - secp256k1_fe_mul_int(&r->z, 2); /* Z' = 2*Y*Z (2) */ - secp256k1_fe_sqr(&t1, &a->x); - secp256k1_fe_mul_int(&t1, 3); /* T1 = 3*X^2 (3) */ - secp256k1_fe_sqr(&t2, &t1); /* T2 = 9*X^4 (1) */ - secp256k1_fe_sqr(&t3, &a->y); - secp256k1_fe_mul_int(&t3, 2); /* T3 = 2*Y^2 (2) */ - secp256k1_fe_sqr(&t4, &t3); - secp256k1_fe_mul_int(&t4, 2); /* T4 = 8*Y^4 (2) */ - secp256k1_fe_mul(&t3, &t3, &a->x); /* T3 = 2*X*Y^2 (1) */ - r->x = t3; - secp256k1_fe_mul_int(&r->x, 4); /* X' = 8*X*Y^2 (4) */ - secp256k1_fe_negate(&r->x, &r->x, 4); /* X' = -8*X*Y^2 (5) */ - secp256k1_fe_add(&r->x, &t2); /* X' = 9*X^4 - 8*X*Y^2 (6) */ - secp256k1_fe_negate(&t2, &t2, 1); /* T2 = -9*X^4 (2) */ - secp256k1_fe_mul_int(&t3, 6); /* T3 = 12*X*Y^2 (6) */ - secp256k1_fe_add(&t3, &t2); /* T3 = 12*X*Y^2 - 9*X^4 (8) */ - secp256k1_fe_mul(&r->y, &t1, &t3); /* Y' = 36*X^3*Y^2 - 27*X^6 (1) */ - secp256k1_fe_negate(&t2, &t4, 2); /* T2 = -8*Y^4 (3) */ - secp256k1_fe_add(&r->y, &t2); /* Y' = 36*X^3*Y^2 - 27*X^6 - 8*Y^4 (4) */ + /* Formula used: + * L = (3/2) * X1^2 + * S = Y1^2 + * T = -X1*S + * X3 = L^2 + 2*T + * Y3 = -(L*(X3 + T) + S^2) + * Z3 = Y1*Z1 + */ + + secp256k1_fe_mul(&r->z, &a->z, &a->y); /* Z3 = Y1*Z1 (1) */ + secp256k1_fe_sqr(&s, &a->y); /* S = Y1^2 (1) */ + secp256k1_fe_sqr(&l, &a->x); /* L = X1^2 (1) */ + secp256k1_fe_mul_int(&l, 3); /* L = 3*X1^2 (3) */ + secp256k1_fe_half(&l); /* L = 3/2*X1^2 (2) */ + secp256k1_fe_negate(&t, &s, 1); /* T = -S (2) */ + secp256k1_fe_mul(&t, &t, &a->x); /* T = -X1*S (1) */ + secp256k1_fe_sqr(&r->x, &l); /* X3 = L^2 (1) */ + secp256k1_fe_add(&r->x, &t); /* X3 = L^2 + T (2) */ + secp256k1_fe_add(&r->x, &t); /* X3 = L^2 + 2*T (3) */ + secp256k1_fe_sqr(&s, &s); /* S' = S^2 (1) */ + secp256k1_fe_add(&t, &r->x); /* T' = X3 + T (4) */ + secp256k1_fe_mul(&r->y, &t, &l); /* Y3 = L*(X3 + T) (1) */ + secp256k1_fe_add(&r->y, &s); /* Y3 = L*(X3 + T) + S^2 (2) */ + secp256k1_fe_negate(&r->y, &r->y, 2); /* Y3 = -(L*(X3 + T) + S^2) (3) */ } static void secp256k1_gej_double_var(secp256k1_gej *r, const secp256k1_gej *a, secp256k1_fe *rzr) { @@ -327,22 +324,20 @@ static void secp256k1_gej_double_var(secp256k1_gej *r, const secp256k1_gej *a, s if (rzr != NULL) { *rzr = a->y; secp256k1_fe_normalize_weak(rzr); - secp256k1_fe_mul_int(rzr, 2); } secp256k1_gej_double(r, a); } 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); @@ -351,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); @@ -359,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); @@ -371,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); @@ -401,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); @@ -409,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); @@ -421,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; @@ -453,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 @@ -471,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); @@ -480,21 +490,29 @@ 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); } static void secp256k1_gej_add_ge(secp256k1_gej *r, const secp256k1_gej *a, const secp256k1_ge *b) { - /* Operations: 7 mul, 5 sqr, 4 normalize, 21 mul_int/add/negate/cmov */ - static const secp256k1_fe fe_1 = SECP256K1_FE_CONST(0, 0, 0, 0, 0, 0, 0, 1); + /* Operations: 7 mul, 5 sqr, 24 add/cmov/half/mul_int/negate/normalize_weak/normalizes_to_zero */ secp256k1_fe zz, u1, u2, s1, s2, t, tt, m, n, q, rr; secp256k1_fe m_alt, rr_alt; int infinity, degenerate; @@ -515,11 +533,11 @@ static void secp256k1_gej_add_ge(secp256k1_gej *r, const secp256k1_gej *a, const * Z = Z1*Z2 * T = U1+U2 * M = S1+S2 - * Q = T*M^2 + * Q = -T*M^2 * R = T^2-U1*U2 - * X3 = 4*(R^2-Q) - * Y3 = 4*(R*(3*Q-2*R^2)-M^4) - * Z3 = 2*M*Z + * X3 = R^2+Q + * Y3 = -(R*(2*X3+Q)+M^4)/2 + * Z3 = M*Z * (Note that the paper uses xi = Xi / Zi and yi = Yi / Zi instead.) * * This formula has the benefit of being the same for both addition @@ -583,7 +601,8 @@ static void secp256k1_gej_add_ge(secp256k1_gej *r, const secp256k1_gej *a, const * and denominator of lambda; R and M represent the explicit * expressions x1^2 + x2^2 + x1x2 and y1 + y2. */ secp256k1_fe_sqr(&n, &m_alt); /* n = Malt^2 (1) */ - secp256k1_fe_mul(&q, &n, &t); /* q = Q = T*Malt^2 (1) */ + secp256k1_fe_negate(&q, &t, 2); /* q = -T (3) */ + secp256k1_fe_mul(&q, &q, &n); /* q = Q = -T*Malt^2 (1) */ /* These two lines use the observation that either M == Malt or M == 0, * so M^3 * Malt is either Malt^4 (which is computed by squaring), or * zero (which is "computed" by cmov). So the cost is one squaring @@ -591,26 +610,21 @@ static void secp256k1_gej_add_ge(secp256k1_gej *r, const secp256k1_gej *a, const secp256k1_fe_sqr(&n, &n); secp256k1_fe_cmov(&n, &m, degenerate); /* n = M^3 * Malt (2) */ secp256k1_fe_sqr(&t, &rr_alt); /* t = Ralt^2 (1) */ - secp256k1_fe_mul(&r->z, &a->z, &m_alt); /* r->z = Malt*Z (1) */ + secp256k1_fe_mul(&r->z, &a->z, &m_alt); /* r->z = Z3 = Malt*Z (1) */ infinity = secp256k1_fe_normalizes_to_zero(&r->z) & ~a->infinity; - secp256k1_fe_mul_int(&r->z, 2); /* r->z = Z3 = 2*Malt*Z (2) */ - secp256k1_fe_negate(&q, &q, 1); /* q = -Q (2) */ - secp256k1_fe_add(&t, &q); /* t = Ralt^2-Q (3) */ - secp256k1_fe_normalize_weak(&t); - r->x = t; /* r->x = Ralt^2-Q (1) */ - secp256k1_fe_mul_int(&t, 2); /* t = 2*x3 (2) */ - secp256k1_fe_add(&t, &q); /* t = 2*x3 - Q: (4) */ - secp256k1_fe_mul(&t, &t, &rr_alt); /* t = Ralt*(2*x3 - Q) (1) */ - secp256k1_fe_add(&t, &n); /* t = Ralt*(2*x3 - Q) + M^3*Malt (3) */ - secp256k1_fe_negate(&r->y, &t, 3); /* r->y = Ralt*(Q - 2x3) - M^3*Malt (4) */ - secp256k1_fe_normalize_weak(&r->y); - secp256k1_fe_mul_int(&r->x, 4); /* r->x = X3 = 4*(Ralt^2-Q) */ - secp256k1_fe_mul_int(&r->y, 4); /* r->y = Y3 = 4*Ralt*(Q - 2x3) - 4*M^3*Malt (4) */ + secp256k1_fe_add(&t, &q); /* t = Ralt^2 + Q (2) */ + r->x = t; /* r->x = X3 = Ralt^2 + Q (2) */ + secp256k1_fe_mul_int(&t, 2); /* t = 2*X3 (4) */ + secp256k1_fe_add(&t, &q); /* t = 2*X3 + Q (5) */ + secp256k1_fe_mul(&t, &t, &rr_alt); /* t = Ralt*(2*X3 + Q) (1) */ + secp256k1_fe_add(&t, &n); /* t = Ralt*(2*X3 + Q) + M^3*Malt (3) */ + secp256k1_fe_negate(&r->y, &t, 3); /* r->y = -(Ralt*(2*X3 + Q) + M^3*Malt) (4) */ + secp256k1_fe_half(&r->y); /* r->y = Y3 = -(Ralt*(2*X3 + Q) + M^3*Malt)/2 (3) */ /** In case a->infinity == 1, replace r with (b->x, b->y, 1). */ secp256k1_fe_cmov(&r->x, &b->x, a->infinity); secp256k1_fe_cmov(&r->y, &b->y, a->infinity); - secp256k1_fe_cmov(&r->z, &fe_1, a->infinity); + secp256k1_fe_cmov(&r->z, &secp256k1_fe_one, a->infinity); r->infinity = infinity; } @@ -642,18 +656,22 @@ static void secp256k1_ge_from_storage(secp256k1_ge *r, const secp256k1_ge_storag r->infinity = 0; } +static SECP256K1_INLINE void secp256k1_gej_cmov(secp256k1_gej *r, const secp256k1_gej *a, int flag) { + secp256k1_fe_cmov(&r->x, &a->x, flag); + secp256k1_fe_cmov(&r->y, &a->y, flag); + secp256k1_fe_cmov(&r->z, &a->z, flag); + + r->infinity ^= (r->infinity ^ a->infinity) & flag; +} + static SECP256K1_INLINE void secp256k1_ge_storage_cmov(secp256k1_ge_storage *r, const secp256k1_ge_storage *a, int flag) { secp256k1_fe_storage_cmov(&r->x, &a->x, flag); secp256k1_fe_storage_cmov(&r->y, &a->y, flag); } static void secp256k1_ge_mul_lambda(secp256k1_ge *r, const secp256k1_ge *a) { - static const secp256k1_fe beta = SECP256K1_FE_CONST( - 0x7ae96a2bul, 0x657c0710ul, 0x6e64479eul, 0xac3434e9ul, - 0x9cf04975ul, 0x12f58995ul, 0xc1396c28ul, 0x719501eeul - ); *r = *a; - secp256k1_fe_mul(&r->x, &r->x, &beta); + secp256k1_fe_mul(&r->x, &r->x, &secp256k1_const_beta); } static int secp256k1_ge_is_in_correct_subgroup(const secp256k1_ge* ge) { diff --git a/src/secp256k1/src/hash.h b/src/secp256k1/src/hash.h index 0947a09694..4e0384cfbf 100644 --- a/src/secp256k1/src/hash.h +++ b/src/secp256k1/src/hash.h @@ -12,8 +12,8 @@ typedef struct { uint32_t s[8]; - uint32_t buf[16]; /* In big endian */ - size_t bytes; + unsigned char buf[64]; + uint64_t bytes; } secp256k1_sha256; static void secp256k1_sha256_initialize(secp256k1_sha256 *hash); diff --git a/src/secp256k1/src/hash_impl.h b/src/secp256k1/src/hash_impl.h index f8cd3a1634..0991fe7838 100644 --- a/src/secp256k1/src/hash_impl.h +++ b/src/secp256k1/src/hash_impl.h @@ -28,12 +28,6 @@ (h) = t1 + t2; \ } while(0) -#if defined(SECP256K1_BIG_ENDIAN) -#define BE32(x) (x) -#elif defined(SECP256K1_LITTLE_ENDIAN) -#define BE32(p) ((((p) & 0xFF) << 24) | (((p) & 0xFF00) << 8) | (((p) & 0xFF0000) >> 8) | (((p) & 0xFF000000) >> 24)) -#endif - static void secp256k1_sha256_initialize(secp256k1_sha256 *hash) { hash->s[0] = 0x6a09e667ul; hash->s[1] = 0xbb67ae85ul; @@ -47,26 +41,26 @@ static void secp256k1_sha256_initialize(secp256k1_sha256 *hash) { } /** Perform one SHA-256 transformation, processing 16 big endian 32-bit words. */ -static void secp256k1_sha256_transform(uint32_t* s, const uint32_t* chunk) { +static void secp256k1_sha256_transform(uint32_t* s, const unsigned char* buf) { uint32_t a = s[0], b = s[1], c = s[2], d = s[3], e = s[4], f = s[5], g = s[6], h = s[7]; uint32_t w0, w1, w2, w3, w4, w5, w6, w7, w8, w9, w10, w11, w12, w13, w14, w15; - Round(a, b, c, d, e, f, g, h, 0x428a2f98, w0 = BE32(chunk[0])); - Round(h, a, b, c, d, e, f, g, 0x71374491, w1 = BE32(chunk[1])); - Round(g, h, a, b, c, d, e, f, 0xb5c0fbcf, w2 = BE32(chunk[2])); - Round(f, g, h, a, b, c, d, e, 0xe9b5dba5, w3 = BE32(chunk[3])); - Round(e, f, g, h, a, b, c, d, 0x3956c25b, w4 = BE32(chunk[4])); - Round(d, e, f, g, h, a, b, c, 0x59f111f1, w5 = BE32(chunk[5])); - Round(c, d, e, f, g, h, a, b, 0x923f82a4, w6 = BE32(chunk[6])); - Round(b, c, d, e, f, g, h, a, 0xab1c5ed5, w7 = BE32(chunk[7])); - Round(a, b, c, d, e, f, g, h, 0xd807aa98, w8 = BE32(chunk[8])); - Round(h, a, b, c, d, e, f, g, 0x12835b01, w9 = BE32(chunk[9])); - Round(g, h, a, b, c, d, e, f, 0x243185be, w10 = BE32(chunk[10])); - Round(f, g, h, a, b, c, d, e, 0x550c7dc3, w11 = BE32(chunk[11])); - Round(e, f, g, h, a, b, c, d, 0x72be5d74, w12 = BE32(chunk[12])); - Round(d, e, f, g, h, a, b, c, 0x80deb1fe, w13 = BE32(chunk[13])); - Round(c, d, e, f, g, h, a, b, 0x9bdc06a7, w14 = BE32(chunk[14])); - Round(b, c, d, e, f, g, h, a, 0xc19bf174, w15 = BE32(chunk[15])); + Round(a, b, c, d, e, f, g, h, 0x428a2f98, w0 = secp256k1_read_be32(&buf[0])); + Round(h, a, b, c, d, e, f, g, 0x71374491, w1 = secp256k1_read_be32(&buf[4])); + Round(g, h, a, b, c, d, e, f, 0xb5c0fbcf, w2 = secp256k1_read_be32(&buf[8])); + Round(f, g, h, a, b, c, d, e, 0xe9b5dba5, w3 = secp256k1_read_be32(&buf[12])); + Round(e, f, g, h, a, b, c, d, 0x3956c25b, w4 = secp256k1_read_be32(&buf[16])); + Round(d, e, f, g, h, a, b, c, 0x59f111f1, w5 = secp256k1_read_be32(&buf[20])); + Round(c, d, e, f, g, h, a, b, 0x923f82a4, w6 = secp256k1_read_be32(&buf[24])); + Round(b, c, d, e, f, g, h, a, 0xab1c5ed5, w7 = secp256k1_read_be32(&buf[28])); + Round(a, b, c, d, e, f, g, h, 0xd807aa98, w8 = secp256k1_read_be32(&buf[32])); + Round(h, a, b, c, d, e, f, g, 0x12835b01, w9 = secp256k1_read_be32(&buf[36])); + Round(g, h, a, b, c, d, e, f, 0x243185be, w10 = secp256k1_read_be32(&buf[40])); + Round(f, g, h, a, b, c, d, e, 0x550c7dc3, w11 = secp256k1_read_be32(&buf[44])); + Round(e, f, g, h, a, b, c, d, 0x72be5d74, w12 = secp256k1_read_be32(&buf[48])); + Round(d, e, f, g, h, a, b, c, 0x80deb1fe, w13 = secp256k1_read_be32(&buf[52])); + Round(c, d, e, f, g, h, a, b, 0x9bdc06a7, w14 = secp256k1_read_be32(&buf[56])); + Round(b, c, d, e, f, g, h, a, 0xc19bf174, w15 = secp256k1_read_be32(&buf[60])); Round(a, b, c, d, e, f, g, h, 0xe49b69c1, w0 += sigma1(w14) + w9 + sigma0(w1)); Round(h, a, b, c, d, e, f, g, 0xefbe4786, w1 += sigma1(w15) + w10 + sigma0(w2)); @@ -136,7 +130,7 @@ static void secp256k1_sha256_write(secp256k1_sha256 *hash, const unsigned char * while (len >= 64 - bufsize) { /* Fill the buffer, and process it. */ size_t chunk_len = 64 - bufsize; - memcpy(((unsigned char*)hash->buf) + bufsize, data, chunk_len); + memcpy(hash->buf + bufsize, data, chunk_len); data += chunk_len; len -= chunk_len; secp256k1_sha256_transform(hash->s, hash->buf); @@ -149,19 +143,19 @@ static void secp256k1_sha256_write(secp256k1_sha256 *hash, const unsigned char * } static void secp256k1_sha256_finalize(secp256k1_sha256 *hash, unsigned char *out32) { - static const unsigned char pad[64] = {0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; - uint32_t sizedesc[2]; - uint32_t out[8]; - int i = 0; - sizedesc[0] = BE32(hash->bytes >> 29); - sizedesc[1] = BE32(hash->bytes << 3); + static const unsigned char pad[64] = {0x80}; + unsigned char sizedesc[8]; + int i; + /* The maximum message size of SHA256 is 2^64-1 bits. */ + VERIFY_CHECK(hash->bytes < ((uint64_t)1 << 61)); + secp256k1_write_be32(&sizedesc[0], hash->bytes >> 29); + secp256k1_write_be32(&sizedesc[4], hash->bytes << 3); secp256k1_sha256_write(hash, pad, 1 + ((119 - (hash->bytes % 64)) % 64)); - secp256k1_sha256_write(hash, (const unsigned char*)sizedesc, 8); + secp256k1_sha256_write(hash, sizedesc, 8); for (i = 0; i < 8; i++) { - out[i] = BE32(hash->s[i]); + secp256k1_write_be32(&out32[4*i], hash->s[i]); hash->s[i] = 0; } - memcpy(out32, (const unsigned char*)out, 32); } /* Initializes a sha256 struct and writes the 64 byte string @@ -285,7 +279,6 @@ static void secp256k1_rfc6979_hmac_sha256_finalize(secp256k1_rfc6979_hmac_sha256 rng->retry = 0; } -#undef BE32 #undef Round #undef sigma1 #undef sigma0 diff --git a/src/secp256k1/src/modules/ecdh/tests_impl.h b/src/secp256k1/src/modules/ecdh/tests_impl.h index be07447a4b..10b7075c38 100644 --- a/src/secp256k1/src/modules/ecdh/tests_impl.h +++ b/src/secp256k1/src/modules/ecdh/tests_impl.h @@ -60,7 +60,7 @@ void test_ecdh_generator_basepoint(void) { s_one[31] = 1; /* Check against pubkey creation when the basepoint is the generator */ - for (i = 0; i < 100; ++i) { + for (i = 0; i < 2 * count; ++i) { secp256k1_sha256 sha; unsigned char s_b32[32]; unsigned char output_ecdh[65]; @@ -123,10 +123,43 @@ void test_bad_scalar(void) { CHECK(secp256k1_ecdh(ctx, output, &point, s_overflow, ecdh_hash_function_test_fail, NULL) == 0); } +/** Test that ECDH(sG, 1/s) == ECDH((1/s)G, s) == ECDH(G, 1) for a few random s. */ +void test_result_basepoint(void) { + secp256k1_pubkey point; + secp256k1_scalar rand; + unsigned char s[32]; + unsigned char s_inv[32]; + unsigned char out[32]; + unsigned char out_inv[32]; + unsigned char out_base[32]; + int i; + + unsigned char s_one[32] = { 0 }; + s_one[31] = 1; + CHECK(secp256k1_ec_pubkey_create(ctx, &point, s_one) == 1); + CHECK(secp256k1_ecdh(ctx, out_base, &point, s_one, NULL, NULL) == 1); + + for (i = 0; i < 2 * count; i++) { + random_scalar_order(&rand); + secp256k1_scalar_get_b32(s, &rand); + secp256k1_scalar_inverse(&rand, &rand); + secp256k1_scalar_get_b32(s_inv, &rand); + + CHECK(secp256k1_ec_pubkey_create(ctx, &point, s) == 1); + CHECK(secp256k1_ecdh(ctx, out, &point, s_inv, NULL, NULL) == 1); + CHECK(secp256k1_memcmp_var(out, out_base, 32) == 0); + + CHECK(secp256k1_ec_pubkey_create(ctx, &point, s_inv) == 1); + CHECK(secp256k1_ecdh(ctx, out_inv, &point, s, NULL, NULL) == 1); + CHECK(secp256k1_memcmp_var(out_inv, out_base, 32) == 0); + } +} + void run_ecdh_tests(void) { test_ecdh_api(); test_ecdh_generator_basepoint(); test_bad_scalar(); + test_result_basepoint(); } #endif /* SECP256K1_MODULE_ECDH_TESTS_H */ diff --git a/src/secp256k1/src/modules/schnorrsig/main_impl.h b/src/secp256k1/src/modules/schnorrsig/main_impl.h index 94e3ee414d..cd651591c4 100644 --- a/src/secp256k1/src/modules/schnorrsig/main_impl.h +++ b/src/secp256k1/src/modules/schnorrsig/main_impl.h @@ -192,11 +192,15 @@ static int secp256k1_schnorrsig_sign_internal(const secp256k1_context* ctx, unsi return ret; } -int secp256k1_schnorrsig_sign(const secp256k1_context* ctx, unsigned char *sig64, const unsigned char *msg32, const secp256k1_keypair *keypair, const unsigned char *aux_rand32) { +int secp256k1_schnorrsig_sign32(const secp256k1_context* ctx, unsigned char *sig64, const unsigned char *msg32, const secp256k1_keypair *keypair, const unsigned char *aux_rand32) { /* We cast away const from the passed aux_rand32 argument since we know the default nonce function does not modify it. */ return secp256k1_schnorrsig_sign_internal(ctx, sig64, msg32, 32, keypair, secp256k1_nonce_function_bip340, (unsigned char*)aux_rand32); } +int secp256k1_schnorrsig_sign(const secp256k1_context* ctx, unsigned char *sig64, const unsigned char *msg32, const secp256k1_keypair *keypair, const unsigned char *aux_rand32) { + return secp256k1_schnorrsig_sign32(ctx, sig64, msg32, keypair, aux_rand32); +} + int secp256k1_schnorrsig_sign_custom(const secp256k1_context* ctx, unsigned char *sig64, const unsigned char *msg, size_t msglen, const secp256k1_keypair *keypair, secp256k1_schnorrsig_extraparams *extraparams) { secp256k1_nonce_function_hardened noncefp = NULL; void *ndata = NULL; diff --git a/src/secp256k1/src/modules/schnorrsig/tests_impl.h b/src/secp256k1/src/modules/schnorrsig/tests_impl.h index 2efec8a2b9..25840b8fa7 100644 --- a/src/secp256k1/src/modules/schnorrsig/tests_impl.h +++ b/src/secp256k1/src/modules/schnorrsig/tests_impl.h @@ -87,7 +87,7 @@ void run_nonce_function_bip340_tests(void) { CHECK(nonce_function_bip340(nonce, msg, msglen, key, pk, NULL, 0, NULL) == 0); CHECK(nonce_function_bip340(nonce, msg, msglen, key, pk, algo, algolen, NULL) == 1); /* Other algo is fine */ - secp256k1_rfc6979_hmac_sha256_generate(&secp256k1_test_rng, algo, algolen); + secp256k1_testrand_bytes_test(algo, algolen); CHECK(nonce_function_bip340(nonce, msg, msglen, key, pk, algo, algolen, NULL) == 1); for (i = 0; i < count; i++) { @@ -160,21 +160,21 @@ void test_schnorrsig_api(void) { /** main test body **/ ecount = 0; - CHECK(secp256k1_schnorrsig_sign(none, sig, msg, &keypairs[0], NULL) == 1); + CHECK(secp256k1_schnorrsig_sign32(none, sig, msg, &keypairs[0], NULL) == 1); CHECK(ecount == 0); - CHECK(secp256k1_schnorrsig_sign(vrfy, sig, msg, &keypairs[0], NULL) == 1); + CHECK(secp256k1_schnorrsig_sign32(vrfy, sig, msg, &keypairs[0], NULL) == 1); CHECK(ecount == 0); - CHECK(secp256k1_schnorrsig_sign(sign, sig, msg, &keypairs[0], NULL) == 1); + CHECK(secp256k1_schnorrsig_sign32(sign, sig, msg, &keypairs[0], NULL) == 1); CHECK(ecount == 0); - CHECK(secp256k1_schnorrsig_sign(sign, NULL, msg, &keypairs[0], NULL) == 0); + CHECK(secp256k1_schnorrsig_sign32(sign, NULL, msg, &keypairs[0], NULL) == 0); CHECK(ecount == 1); - CHECK(secp256k1_schnorrsig_sign(sign, sig, NULL, &keypairs[0], NULL) == 0); + CHECK(secp256k1_schnorrsig_sign32(sign, sig, NULL, &keypairs[0], NULL) == 0); CHECK(ecount == 2); - CHECK(secp256k1_schnorrsig_sign(sign, sig, msg, NULL, NULL) == 0); + CHECK(secp256k1_schnorrsig_sign32(sign, sig, msg, NULL, NULL) == 0); CHECK(ecount == 3); - CHECK(secp256k1_schnorrsig_sign(sign, sig, msg, &invalid_keypair, NULL) == 0); + CHECK(secp256k1_schnorrsig_sign32(sign, sig, msg, &invalid_keypair, NULL) == 0); CHECK(ecount == 4); - CHECK(secp256k1_schnorrsig_sign(sttc, sig, msg, &keypairs[0], NULL) == 0); + CHECK(secp256k1_schnorrsig_sign32(sttc, sig, msg, &keypairs[0], NULL) == 0); CHECK(ecount == 5); ecount = 0; @@ -202,7 +202,7 @@ void test_schnorrsig_api(void) { CHECK(ecount == 6); ecount = 0; - CHECK(secp256k1_schnorrsig_sign(sign, sig, msg, &keypairs[0], NULL) == 1); + CHECK(secp256k1_schnorrsig_sign32(sign, sig, msg, &keypairs[0], NULL) == 1); CHECK(secp256k1_schnorrsig_verify(none, sig, msg, sizeof(msg), &pk[0]) == 1); CHECK(ecount == 0); CHECK(secp256k1_schnorrsig_verify(sign, sig, msg, sizeof(msg), &pk[0]) == 1); @@ -247,7 +247,7 @@ void test_schnorrsig_bip_vectors_check_signing(const unsigned char *sk, const un secp256k1_xonly_pubkey pk, pk_expected; CHECK(secp256k1_keypair_create(ctx, &keypair, sk)); - CHECK(secp256k1_schnorrsig_sign(ctx, sig, msg32, &keypair, aux_rand)); + CHECK(secp256k1_schnorrsig_sign32(ctx, sig, msg32, &keypair, aux_rand)); CHECK(secp256k1_memcmp_var(sig, expected_sig, 64) == 0); CHECK(secp256k1_xonly_pubkey_parse(ctx, &pk_expected, pk_serialized)); @@ -740,8 +740,11 @@ void test_schnorrsig_sign(void) { secp256k1_testrand256(aux_rand); CHECK(secp256k1_keypair_create(ctx, &keypair, sk)); CHECK(secp256k1_keypair_xonly_pub(ctx, &pk, NULL, &keypair)); - CHECK(secp256k1_schnorrsig_sign(ctx, sig, msg, &keypair, NULL) == 1); + CHECK(secp256k1_schnorrsig_sign32(ctx, sig, msg, &keypair, NULL) == 1); CHECK(secp256k1_schnorrsig_verify(ctx, sig, msg, sizeof(msg), &pk)); + /* Check that deprecated alias gives the same result */ + CHECK(secp256k1_schnorrsig_sign(ctx, sig2, msg, &keypair, NULL) == 1); + CHECK(secp256k1_memcmp_var(sig, sig2, sizeof(sig)) == 0); /* Test different nonce functions */ CHECK(secp256k1_schnorrsig_sign_custom(ctx, sig, msg, sizeof(msg), &keypair, &extraparams) == 1); @@ -764,7 +767,7 @@ void test_schnorrsig_sign(void) { extraparams.noncefp = NULL; extraparams.ndata = aux_rand; CHECK(secp256k1_schnorrsig_sign_custom(ctx, sig, msg, sizeof(msg), &keypair, &extraparams) == 1); - CHECK(secp256k1_schnorrsig_sign(ctx, sig2, msg, &keypair, extraparams.ndata) == 1); + CHECK(secp256k1_schnorrsig_sign32(ctx, sig2, msg, &keypair, extraparams.ndata) == 1); CHECK(secp256k1_memcmp_var(sig, sig2, sizeof(sig)) == 0); } @@ -787,7 +790,7 @@ void test_schnorrsig_sign_verify(void) { for (i = 0; i < N_SIGS; i++) { secp256k1_testrand256(msg[i]); - CHECK(secp256k1_schnorrsig_sign(ctx, sig[i], msg[i], &keypair, NULL)); + CHECK(secp256k1_schnorrsig_sign32(ctx, sig[i], msg[i], &keypair, NULL)); CHECK(secp256k1_schnorrsig_verify(ctx, sig[i], msg[i], sizeof(msg[i]), &pk)); } @@ -795,18 +798,18 @@ void test_schnorrsig_sign_verify(void) { /* Flip a few bits in the signature and in the message and check that * verify and verify_batch (TODO) fail */ size_t sig_idx = secp256k1_testrand_int(N_SIGS); - size_t byte_idx = secp256k1_testrand_int(32); + size_t byte_idx = secp256k1_testrand_bits(5); unsigned char xorbyte = secp256k1_testrand_int(254)+1; sig[sig_idx][byte_idx] ^= xorbyte; CHECK(!secp256k1_schnorrsig_verify(ctx, sig[sig_idx], msg[sig_idx], sizeof(msg[sig_idx]), &pk)); sig[sig_idx][byte_idx] ^= xorbyte; - byte_idx = secp256k1_testrand_int(32); + byte_idx = secp256k1_testrand_bits(5); sig[sig_idx][32+byte_idx] ^= xorbyte; CHECK(!secp256k1_schnorrsig_verify(ctx, sig[sig_idx], msg[sig_idx], sizeof(msg[sig_idx]), &pk)); sig[sig_idx][32+byte_idx] ^= xorbyte; - byte_idx = secp256k1_testrand_int(32); + byte_idx = secp256k1_testrand_bits(5); msg[sig_idx][byte_idx] ^= xorbyte; CHECK(!secp256k1_schnorrsig_verify(ctx, sig[sig_idx], msg[sig_idx], sizeof(msg[sig_idx]), &pk)); msg[sig_idx][byte_idx] ^= xorbyte; @@ -816,13 +819,13 @@ void test_schnorrsig_sign_verify(void) { } /* Test overflowing s */ - CHECK(secp256k1_schnorrsig_sign(ctx, sig[0], msg[0], &keypair, NULL)); + CHECK(secp256k1_schnorrsig_sign32(ctx, sig[0], msg[0], &keypair, NULL)); CHECK(secp256k1_schnorrsig_verify(ctx, sig[0], msg[0], sizeof(msg[0]), &pk)); memset(&sig[0][32], 0xFF, 32); CHECK(!secp256k1_schnorrsig_verify(ctx, sig[0], msg[0], sizeof(msg[0]), &pk)); /* Test negative s */ - CHECK(secp256k1_schnorrsig_sign(ctx, sig[0], msg[0], &keypair, NULL)); + CHECK(secp256k1_schnorrsig_sign32(ctx, sig[0], msg[0], &keypair, NULL)); CHECK(secp256k1_schnorrsig_verify(ctx, sig[0], msg[0], sizeof(msg[0]), &pk)); secp256k1_scalar_set_b32(&s, &sig[0][32], NULL); secp256k1_scalar_negate(&s, &s); @@ -873,7 +876,7 @@ void test_schnorrsig_taproot(void) { /* Key spend */ secp256k1_testrand256(msg); - CHECK(secp256k1_schnorrsig_sign(ctx, sig, msg, &keypair, NULL) == 1); + CHECK(secp256k1_schnorrsig_sign32(ctx, sig, msg, &keypair, NULL) == 1); /* Verify key spend */ CHECK(secp256k1_xonly_pubkey_parse(ctx, &output_pk, output_pk_bytes) == 1); CHECK(secp256k1_schnorrsig_verify(ctx, sig, msg, sizeof(msg), &output_pk) == 1); diff --git a/src/secp256k1/src/precompute_ecmult.c b/src/secp256k1/src/precompute_ecmult.c new file mode 100644 index 0000000000..5ccbcb3c57 --- /dev/null +++ b/src/secp256k1/src/precompute_ecmult.c @@ -0,0 +1,96 @@ +/***************************************************************************************************** + * Copyright (c) 2013, 2014, 2017, 2021 Pieter Wuille, Andrew Poelstra, Jonas Nick, Russell O'Connor * + * Distributed under the MIT software license, see the accompanying * + * file COPYING or https://www.opensource.org/licenses/mit-license.php. * + *****************************************************************************************************/ + +#include <inttypes.h> +#include <stdio.h> + +/* Autotools creates libsecp256k1-config.h, of which ECMULT_WINDOW_SIZE is needed. + ifndef guard so downstream users can define their own if they do not use autotools. */ +#if !defined(ECMULT_WINDOW_SIZE) +#include "libsecp256k1-config.h" +#endif + +#include "../include/secp256k1.h" +#include "assumptions.h" +#include "util.h" +#include "field_impl.h" +#include "group_impl.h" +#include "ecmult.h" +#include "ecmult_compute_table_impl.h" + +static void print_table(FILE *fp, const char *name, int window_g, const secp256k1_ge_storage* table) { + int j; + int i; + + fprintf(fp, "const secp256k1_ge_storage %s[ECMULT_TABLE_SIZE(WINDOW_G)] = {\n", name); + fprintf(fp, " S(%"PRIx32",%"PRIx32",%"PRIx32",%"PRIx32",%"PRIx32",%"PRIx32",%"PRIx32",%"PRIx32 + ",%"PRIx32",%"PRIx32",%"PRIx32",%"PRIx32",%"PRIx32",%"PRIx32",%"PRIx32",%"PRIx32")\n", + SECP256K1_GE_STORAGE_CONST_GET(table[0])); + + j = 1; + for(i = 3; i <= window_g; ++i) { + fprintf(fp, "#if WINDOW_G > %d\n", i-1); + for(;j < ECMULT_TABLE_SIZE(i); ++j) { + fprintf(fp, ",S(%"PRIx32",%"PRIx32",%"PRIx32",%"PRIx32",%"PRIx32",%"PRIx32",%"PRIx32",%"PRIx32 + ",%"PRIx32",%"PRIx32",%"PRIx32",%"PRIx32",%"PRIx32",%"PRIx32",%"PRIx32",%"PRIx32")\n", + SECP256K1_GE_STORAGE_CONST_GET(table[j])); + } + fprintf(fp, "#endif\n"); + } + fprintf(fp, "};\n"); +} + +static void print_two_tables(FILE *fp, int window_g) { + secp256k1_ge_storage* table = malloc(ECMULT_TABLE_SIZE(window_g) * sizeof(secp256k1_ge_storage)); + secp256k1_ge_storage* table_128 = malloc(ECMULT_TABLE_SIZE(window_g) * sizeof(secp256k1_ge_storage)); + + secp256k1_ecmult_compute_two_tables(table, table_128, window_g, &secp256k1_ge_const_g); + + print_table(fp, "secp256k1_pre_g", window_g, table); + print_table(fp, "secp256k1_pre_g_128", window_g, table_128); + + free(table); + free(table_128); +} + +int main(void) { + /* Always compute all tables for window sizes up to 15. */ + int window_g = (ECMULT_WINDOW_SIZE < 15) ? 15 : ECMULT_WINDOW_SIZE; + FILE* fp; + + fp = fopen("src/precomputed_ecmult.c","w"); + if (fp == NULL) { + fprintf(stderr, "Could not open src/precomputed_ecmult.h for writing!\n"); + return -1; + } + + fprintf(fp, "/* This file was automatically generated by precompute_ecmult. */\n"); + fprintf(fp, "/* This file contains an array secp256k1_pre_g with odd multiples of the base point G and\n"); + fprintf(fp, " * an array secp256k1_pre_g_128 with odd multiples of 2^128*G for accelerating the computation of a*P + b*G.\n"); + fprintf(fp, " */\n"); + fprintf(fp, "#if defined HAVE_CONFIG_H\n"); + fprintf(fp, "# include \"libsecp256k1-config.h\"\n"); + fprintf(fp, "#endif\n"); + fprintf(fp, "#include \"../include/secp256k1.h\"\n"); + fprintf(fp, "#include \"group.h\"\n"); + fprintf(fp, "#include \"ecmult.h\"\n"); + fprintf(fp, "#include \"precomputed_ecmult.h\"\n"); + fprintf(fp, "#define S(a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p) SECP256K1_GE_STORAGE_CONST(0x##a##u,0x##b##u,0x##c##u,0x##d##u,0x##e##u,0x##f##u,0x##g##u,0x##h##u,0x##i##u,0x##j##u,0x##k##u,0x##l##u,0x##m##u,0x##n##u,0x##o##u,0x##p##u)\n"); + fprintf(fp, "#if ECMULT_WINDOW_SIZE > %d\n", window_g); + fprintf(fp, " #error configuration mismatch, invalid ECMULT_WINDOW_SIZE. Try deleting precomputed_ecmult.c before the build.\n"); + fprintf(fp, "#endif\n"); + fprintf(fp, "#ifdef EXHAUSTIVE_TEST_ORDER\n"); + fprintf(fp, "# error Cannot compile precomputed_ecmult.c in exhaustive test mode\n"); + fprintf(fp, "#endif /* EXHAUSTIVE_TEST_ORDER */\n"); + fprintf(fp, "#define WINDOW_G ECMULT_WINDOW_SIZE\n"); + + print_two_tables(fp, window_g); + + fprintf(fp, "#undef S\n"); + fclose(fp); + + return 0; +} diff --git a/src/secp256k1/src/gen_ecmult_gen_static_prec_table.c b/src/secp256k1/src/precompute_ecmult_gen.c index 22923df313..7c6359c402 100644 --- a/src/secp256k1/src/gen_ecmult_gen_static_prec_table.c +++ b/src/secp256k1/src/precompute_ecmult_gen.c @@ -1,8 +1,8 @@ -/*********************************************************************** - * Copyright (c) 2013, 2014, 2015 Thomas Daede, Cory Fields * - * Distributed under the MIT software license, see the accompanying * - * file COPYING or https://www.opensource.org/licenses/mit-license.php.* - ***********************************************************************/ +/********************************************************************************* + * Copyright (c) 2013, 2014, 2015, 2021 Thomas Daede, Cory Fields, Pieter Wuille * + * Distributed under the MIT software license, see the accompanying * + * file COPYING or https://www.opensource.org/licenses/mit-license.php. * + *********************************************************************************/ #include <inttypes.h> #include <stdio.h> @@ -12,10 +12,10 @@ #include "util.h" #include "group.h" #include "ecmult_gen.h" -#include "ecmult_gen_prec_impl.h" +#include "ecmult_gen_compute_table_impl.h" int main(int argc, char **argv) { - const char outfile[] = "src/ecmult_gen_static_prec_table.h"; + const char outfile[] = "src/precomputed_ecmult_gen.c"; FILE* fp; int bits; @@ -28,21 +28,20 @@ int main(int argc, char **argv) { return -1; } - fprintf(fp, "/* This file was automatically generated by gen_ecmult_gen_static_prec_table. */\n"); + fprintf(fp, "/* This file was automatically generated by precompute_ecmult_gen. */\n"); fprintf(fp, "/* See ecmult_gen_impl.h for details about the contents of this file. */\n"); - fprintf(fp, "#ifndef SECP256K1_ECMULT_GEN_STATIC_PREC_TABLE_H\n"); - fprintf(fp, "#define SECP256K1_ECMULT_GEN_STATIC_PREC_TABLE_H\n"); - + fprintf(fp, "#if defined HAVE_CONFIG_H\n"); + fprintf(fp, "# include \"libsecp256k1-config.h\"\n"); + fprintf(fp, "#endif\n"); + fprintf(fp, "#include \"../include/secp256k1.h\"\n"); fprintf(fp, "#include \"group.h\"\n"); - - fprintf(fp, "#define S(a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p) " - "SECP256K1_GE_STORAGE_CONST(0x##a##u,0x##b##u,0x##c##u,0x##d##u,0x##e##u,0x##f##u,0x##g##u," - "0x##h##u,0x##i##u,0x##j##u,0x##k##u,0x##l##u,0x##m##u,0x##n##u,0x##o##u,0x##p##u)\n"); - + fprintf(fp, "#include \"ecmult_gen.h\"\n"); + fprintf(fp, "#include \"precomputed_ecmult_gen.h\"\n"); fprintf(fp, "#ifdef EXHAUSTIVE_TEST_ORDER\n"); - fprintf(fp, "static secp256k1_ge_storage secp256k1_ecmult_gen_prec_table[ECMULT_GEN_PREC_N(ECMULT_GEN_PREC_BITS)][ECMULT_GEN_PREC_G(ECMULT_GEN_PREC_BITS)];\n"); - fprintf(fp, "#else\n"); - fprintf(fp, "static const secp256k1_ge_storage secp256k1_ecmult_gen_prec_table[ECMULT_GEN_PREC_N(ECMULT_GEN_PREC_BITS)][ECMULT_GEN_PREC_G(ECMULT_GEN_PREC_BITS)] = {\n"); + fprintf(fp, "# error Cannot compile precomputed_ecmult_gen.c in exhaustive test mode\n"); + fprintf(fp, "#endif /* EXHAUSTIVE_TEST_ORDER */\n"); + fprintf(fp, "#define S(a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p) SECP256K1_GE_STORAGE_CONST(0x##a##u,0x##b##u,0x##c##u,0x##d##u,0x##e##u,0x##f##u,0x##g##u,0x##h##u,0x##i##u,0x##j##u,0x##k##u,0x##l##u,0x##m##u,0x##n##u,0x##o##u,0x##p##u)\n"); + fprintf(fp, "const secp256k1_ge_storage secp256k1_ecmult_gen_prec_table[ECMULT_GEN_PREC_N(ECMULT_GEN_PREC_BITS)][ECMULT_GEN_PREC_G(ECMULT_GEN_PREC_BITS)] = {\n"); for (bits = 2; bits <= 8; bits *= 2) { int g = ECMULT_GEN_PREC_G(bits); @@ -50,7 +49,7 @@ int main(int argc, char **argv) { int inner, outer; secp256k1_ge_storage* table = checked_malloc(&default_error_callback, n * g * sizeof(secp256k1_ge_storage)); - secp256k1_ecmult_gen_create_prec_table(table, &secp256k1_ge_const_g, bits); + secp256k1_ecmult_gen_compute_table(table, &secp256k1_ge_const_g, bits); fprintf(fp, "#if ECMULT_GEN_PREC_BITS == %d\n", bits); for(outer = 0; outer != n; outer++) { @@ -74,9 +73,7 @@ int main(int argc, char **argv) { } fprintf(fp, "};\n"); - fprintf(fp, "#endif /* EXHAUSTIVE_TEST_ORDER */\n"); - fprintf(fp, "#undef SC\n"); - fprintf(fp, "#endif /* SECP256K1_ECMULT_GEN_STATIC_PREC_TABLE_H */\n"); + fprintf(fp, "#undef S\n"); fclose(fp); return 0; diff --git a/src/secp256k1/src/ecmult_static_pre_g.h b/src/secp256k1/src/precomputed_ecmult.c index 9072fb2688..3e67f37b74 100644 --- a/src/secp256k1/src/ecmult_static_pre_g.h +++ b/src/secp256k1/src/precomputed_ecmult.c @@ -1,187 +1,38 @@ -/* This file was automatically generated by gen_ecmult_static_pre_g. */ +/* This file was automatically generated by precompute_ecmult. */ /* This file contains an array secp256k1_pre_g with odd multiples of the base point G and * an array secp256k1_pre_g_128 with odd multiples of 2^128*G for accelerating the computation of a*P + b*G. */ -#ifndef SECP256K1_ECMULT_STATIC_PRE_G_H -#define SECP256K1_ECMULT_STATIC_PRE_G_H -#include "group.h" -#ifdef S - #error macro identifier S already in use. +#if defined HAVE_CONFIG_H +# include "libsecp256k1-config.h" #endif +#include "../include/secp256k1.h" +#include "group.h" +#include "ecmult.h" +#include "precomputed_ecmult.h" #define S(a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p) SECP256K1_GE_STORAGE_CONST(0x##a##u,0x##b##u,0x##c##u,0x##d##u,0x##e##u,0x##f##u,0x##g##u,0x##h##u,0x##i##u,0x##j##u,0x##k##u,0x##l##u,0x##m##u,0x##n##u,0x##o##u,0x##p##u) -#if ECMULT_TABLE_SIZE(ECMULT_WINDOW_SIZE) > 8192 - #error configuration mismatch, invalid ECMULT_WINDOW_SIZE. Try deleting ecmult_static_pre_g.h before the build. +#if ECMULT_WINDOW_SIZE > 15 + #error configuration mismatch, invalid ECMULT_WINDOW_SIZE. Try deleting precomputed_ecmult.c before the build. #endif -#if defined(EXHAUSTIVE_TEST_ORDER) -#if EXHAUSTIVE_TEST_ORDER == 13 -#define WINDOW_G 4 -static const secp256k1_ge_storage secp256k1_pre_g[ECMULT_TABLE_SIZE(WINDOW_G)] = { - S(c3459c3d,35326167,cd86cce8,7a2417f,5b8bd567,de8538ee,d507b0c,d128f5bb,8e467fec,cd30000a,6cc1184e,25d382c2,a2f4494e,2fbe9abc,8b64abac,d005fb24) -,S(ae64a1bd,38872f22,f637b457,125cc859,e4c7a31b,cf553cf5,b96e7096,cc61cc10,8e467fec,cd30000a,6cc1184e,25d382c2,a2f4494e,2fbe9abc,8b64abac,d005fb24) -,S(851695d4,9a83f8ef,919bb861,53cbcb16,630fb68a,ed0a766a,3ec693d6,8e6afa40,3c915051,a5fb60b4,fec49de6,e4385101,59f30035,b6502bc0,f5edba86,9753a96c) -,S(0,0,0,0,0,0,0,1,c36eafae,5a049f4b,13b6219,1bc7aefe,a60cffca,49afd43f,a124578,68ac52c3) -}; -static const secp256k1_ge_storage secp256k1_pre_g_128[ECMULT_TABLE_SIZE(WINDOW_G)] = { - S(8e55c205,92466f75,3c417ec0,e600f626,bfac877c,52258a1c,3941145a,62753693,8e467fec,cd30000a,6cc1184e,25d382c2,a2f4494e,2fbe9abc,8b64abac,d005fb24) -,S(c3459c3d,35326167,cd86cce8,7a2417f,5b8bd567,de8538ee,d507b0c,d128f5bb,8e467fec,cd30000a,6cc1184e,25d382c2,a2f4494e,2fbe9abc,8b64abac,d005fb24) -,S(0,0,0,0,0,0,0,1,3c915051,a5fb60b4,fec49de6,e4385101,59f30035,b6502bc0,f5edba86,9753a96c) -,S(7ae96a2b,657c0710,6e64479e,ac3434e9,9cf04975,12f58995,c1396c28,719501ee,c36eafae,5a049f4b,13b6219,1bc7aefe,a60cffca,49afd43f,a124578,68ac52c3) -}; -#elif EXHAUSTIVE_TEST_ORDER == 199 -#define WINDOW_G 8 -static const secp256k1_ge_storage secp256k1_pre_g[ECMULT_TABLE_SIZE(WINDOW_G)] = { - S(226e653f,c8df7744,9bacbf12,7d1dcbf9,87f05b2a,e7edbd28,1f564575,c48dcf18,a13872c2,e933bb17,5d9ffd5b,b5b6e10c,57fe3c00,baaaa15a,e003ec3e,9c269bae) -,S(ff1755e,623c8369,f55edda4,2a5deef0,b32c57f4,80c5884f,d2a2dde1,b1c078c4,640db9f3,9dda2f51,ee3ef3db,775315aa,c06346f1,e31ff76f,83a24bb0,8fc93242) -,S(64dd1439,5d19a544,a7a1e81b,b9d079b3,593e7022,6bcd444e,6dc8197a,1a6dc3e6,2c7f2dce,e421d852,d3bff68e,993c8bb4,c189d3be,bd4fa667,6a599f9f,8c639c50) -,S(199d1eb1,5a28f0aa,7258651b,ff07ff13,1c988fa,dc41dc67,390c3172,54b98016,d670e3f1,6fc2382,46bf323f,127e7c14,576e3a64,5d5c41da,40f22b29,4fca876c) -,S(3d303d12,f8eb67d0,ecaee514,bdd90e57,b58b6d6a,c896a26f,78c06103,52225b04,d282f481,b04bece2,60de6995,b58c4f0d,9c6121e0,d94f45da,f5da7f13,cfefef99) -,S(76bb4d25,c7a005dc,49a5295f,bb92bf1e,5d0dd5f6,609c2008,54f37361,23a6f9f3,e2295c71,21478f52,d4d18aa,72cd82ae,1995d37e,6ef2e05a,4dea6ee2,6371fbe3) -,S(8735fb32,6950ae71,31cd03f7,cc1511d8,65c87e84,748c9824,8ccc576e,5480ed8,39cfeb15,665f371f,535d56c1,317a8d62,29ea2827,943a98f1,cee7f685,86e47eec) -,S(8521a020,a8e7cc5e,d9534d74,d4656581,33869bc,1940548d,6cc35ba4,e3c4323a,c63014ea,99a0c8e0,aca2a93e,ce85729d,d615d7d8,6bc5670e,31180979,791b7d43) -,S(2f63a396,4d14ca73,2167e033,306863d8,f75605c7,4ea6c917,890dd50d,fc20f53b,e2295c71,21478f52,d4d18aa,72cd82ae,1995d37e,6ef2e05a,4dea6ee2,6371fbe3) -,S(c479fee8,e2202b42,1c9346fb,4e59f720,95a70dea,2ade9641,58bad01c,2c37f,6897db9d,5a3d8948,ea97069e,de75661,b1ad74e4,2b1daa4f,533d88ba,67137731) -,S(4d756b2,e61d9806,e02ec830,f1fd44f8,46438689,c528346,15a1addf,ac864bcd,c90704c9,8f72d8be,638a36dc,e8768909,fac7921c,dc54d314,89fbb3e,6da9018a) -,S(baeab1a8,bca6e88,740deeb5,93887afe,295155ba,fa50f307,baf2de95,2841dca6,a88e0162,de086e20,587faa5c,bca8f572,318c17e9,5668819c,13c4d5d9,2d1c75dc) -,S(47bbafcd,f612ca3e,703f7721,623bde14,926fc00d,a6bf9e9,41ddb2d5,587626e3,d160b834,5807531a,327a7269,3627dab6,f9770a12,14eb182d,fa73ad15,41b57ff0) -,S(f9d5cda0,a41aea0e,f0afc533,e1b50808,3acc6043,e04e55bb,77421ab,1cebb8ec,f8a4cc8e,538e1b7f,627c6713,7c896eb1,c5548159,f434695a,ea394c63,592d41cf) -,S(26e2e75b,aeb04a72,8b893f67,d27ebfc6,b8984a88,6fbd8eee,5719107d,5a413f0f,61b51308,ac8643f0,7c18eda7,ef9a4196,aa66221,1b809acc,2b2cb538,23010ef8) -,S(318ef624,4d411c97,913a7bb3,8f07b960,e6c57afa,f1edc5bf,e708da8a,954f68ac,37d97203,d25d9c14,53827e27,9474afb2,92943df0,8ba1108a,43644c86,ee6c87e4) -,S(eac612b0,84cb452a,35d49c60,c0d0c0bd,359eb909,68a80186,92a028f,ece4c331,23575727,9da3d12b,c0004d12,e06a9cda,8405c91e,b8118260,2793e62f,16c2a29e) -,S(9f5ed380,fa5188a1,cb1c84df,cb7e2aad,939cdfde,581d765b,95c1c25,43016afb,423923ad,75aa7eb1,259434a7,1d58c469,f6268a38,5cd8fb7c,4be64d63,fad9bcea) -,S(ee534645,42ff07e2,8cf783ee,1da96e00,b81f531,9b9a4885,27f92302,acd12cac,c90704c9,8f72d8be,638a36dc,e8768909,fac7921c,dc54d314,89fbb3e,6da9018a) -,S(1130e02f,29480660,b2cb4f68,81da3f2c,1bdaf64,8df3b6ce,1a333738,29e4165e,ef2b477b,a9c921df,5ff65b6e,7c6f96a8,627743ff,a53e3858,cd9d7673,f7435ac4) -,S(e8d3f45a,b5be455f,dc3c2270,34100ca2,b8d203e1,35a9ed3d,5c8b703c,ef4d9b48,2d7d0b7e,4fb4131d,9f21966a,4a73b0f2,639ede1f,26b0ba25,a2580eb,30100c96) -,S(eb5c4120,4917d176,6fea883b,e680dfd9,233d9654,97fac48b,76f5d0d4,7f6dbf73,97682462,a5c276b7,1568f961,f218a99e,4e528b1b,d4e255b0,acc27744,98ec84fe) -,S(4097892c,5478baea,62e8da20,6d17fcd8,b8eed45f,24fb7648,fe742508,8da60a4e,10d4b884,5636de20,a009a491,83906957,9d88bc00,5ac1c7a7,3262898b,8bca16b) -,S(9963e528,754cbba8,29622664,1aec11b0,3fba2739,40f61cbd,5d4c0478,5429f40,d670e3f1,6fc2382,46bf323f,127e7c14,576e3a64,5d5c41da,40f22b29,4fca876c) -,S(853cffd4,4cb0a29e,390ad886,455566cc,2776ab74,55210e57,9bf1fcc0,c8d767ed,621fdd8d,dd929f3f,681a6f83,bf8061f8,2b4396a6,5df86be6,ec656a86,b1b730b0) -,S(7fe309bb,e3e8edd3,3f5f94b,4247256e,41e62e69,fd9f2063,d878c6d6,3d85675f,23575727,9da3d12b,c0004d12,e06a9cda,8405c91e,b8118260,2793e62f,16c2a29e) -,S(851695d4,9a83f8ef,919bb861,53cbcb16,630fb68a,ed0a766a,3ec693d6,8e6afa40,cb47a6a4,91313828,f036dbd7,64173a40,473d3969,5cc47758,511d0a81,badb02f1) -,S(f31a7dfe,4d2e4a98,aaf3dd70,25441c7a,2da056b0,db6795e8,3a39d76f,b316bd92,17e98bbd,6e144db,959b7aea,cd766958,7be26db5,445ea19d,dab8bc90,1f527cb4) -,S(2266b5e,d5cef55,81e36315,c5fc5c53,a1c73b12,c24c8de7,9afef2a0,7a50212c,7a60fc27,e4ab0ba6,ea71c036,52b83c5c,5ab5dac8,ea43dec2,84719f8c,6d7c3c81) -,S(e0089ec9,4b25ac6,ff44b5da,2dfedb37,9fdaedf1,6bc8cecd,b1a95fa0,32431c73,17e98bbd,6e144db,959b7aea,cd766958,7be26db5,445ea19d,dab8bc90,1f527cb4) -,S(896c7bc,5cfeaecc,37b111ea,2deee270,c7cb24eb,6eb1cb80,a84e87d3,89d997c1,66446d0d,b01a679b,59afb34a,2bbf8cfe,f0a44870,f0e074ce,78a51a36,9bbbf33c) -,S(709c8403,ed2fd0f9,5fac44f0,dfa96ec2,e7b357e4,4ebb3dcf,6f688f20,39d3d67d,d160b834,5807531a,327a7269,3627dab6,f9770a12,14eb182d,fa73ad15,41b57ff0) -,S(142f854d,ba0b87a2,9826f018,86ab51ff,b9f19214,c23c07c0,f008a375,a1effdb6,74bafd32,bdf2bdfd,c65a52c3,5ef4f4d,9332f7b2,a0512a9f,f821f858,484bb9c) -,S(66abbf18,f58dd1c9,4b6072c,eb182abe,c31d3af9,6d91060b,d233f462,5716f289,d380d231,1bde27ad,2c400971,66c3744b,3e762c41,42b05998,95a6605f,739c5fdf) -,S(7a1b727f,c05c6de2,79b2e027,108fae3e,87ca9249,eb44207f,286e1cbe,4728027,9de02272,226d60c0,97e5907c,407f9e07,d4bc6959,a2079419,139a9578,4e48cb7f) -,S(3d98fe85,5d42f661,e35d8ce3,cfee5f17,57bc3de9,ecec02fa,11a2d90,378ac0e8,bdc6dc52,8a55814e,da6bcb58,e2a73b96,9d975c7,a3270483,b419b29b,5263f45) -,S(34f8450b,18bffd3f,cbadcd08,5da5411b,ba2ef195,55753768,bf777307,3a2d4cb4,5771fe9d,21f791df,a78055a3,43570a8d,ce73e816,a9977e63,ec3b2a25,d2e38653) -,S(76dac07e,bc240289,791e6a9a,56abdc7,bdf2fac1,e7a0100b,5a0ee09e,3455cc33,8b4502cd,420d4202,39a5ad3c,fa10b0b2,6ccd084d,5faed560,7de07a6,fb7b4093) -,S(929effed,d3b91ccb,a985ab23,bef45c16,6d5ffaa3,22562529,b84d051c,f94044a1,640db9f3,9dda2f51,ee3ef3db,775315aa,c06346f1,e31ff76f,83a24bb0,8fc93242) -,S(1ea4ff3b,5a0a78a9,fda9d73a,ea5f0629,886b083a,6d3beea4,d6bc96e1,b50da944,9edf1300,7f2b7fb2,92301e8c,77ddb63e,5f2d23c4,315118c1,8b1b6273,86bba11a) -,S(86fef7a7,b4d3b079,2c83a223,d1c929d6,6ca15202,f4b956a1,cf7ed2cb,83dba7c4,61b51308,ac8643f0,7c18eda7,ef9a4196,aa66221,1b809acc,2b2cb538,23010ef8) -,S(c979de1c,1318f60f,a9929ff3,a6621b9,d15226e8,2883556e,36a5f77b,28e3b585,d7bd974a,b40a1bd3,a35a1ee9,16d32768,48e7bcfc,be70851d,8ba758b9,996d2099) -,S(2cdce338,ae1f5aa0,55c76cb5,acbd084e,3284bb5d,b8cf9b4a,141cc8ee,1aa61e59,17e98bbd,6e144db,959b7aea,cd766958,7be26db5,445ea19d,dab8bc90,1f527cb4) -,S(80c5672b,3f773975,b5829101,6fa9de9b,33863263,8a978db,934f04c1,3a909b85,55ebf47c,6835f232,5ea232b3,8af64478,65cbdce1,2f2c5a79,abc445fd,518ab677) -,S(521e20fc,9c7c0514,47f31e74,5bb81662,dac66374,9b891a6f,d9681cb6,21e3155c,61b51308,ac8643f0,7c18eda7,ef9a4196,aa66221,1b809acc,2b2cb538,23010ef8) -,S(b084fee5,5da62ee0,ebdebf3b,245e0d4e,def46aac,bfdc9f43,261d3e2b,eed946a0,80f3b79c,ba5f8457,3f2aa473,1d10c1f,9b6543e1,b8cec5e0,e89cd6df,868cc2aa) -,S(d8a3fd95,b7ed8e8a,674da307,94911393,303917c2,60944d1f,76261274,fba45757,5ec78d3d,16cc44e8,a26002a4,4a491ef3,a801c3ff,45555ea5,1ffc13c0,63d96081) -,S(c2d4b350,75ee5eb,3e2c8d3c,f33649dc,dd4f4c29,728bbdae,3f3ff02d,f3dc7415,6120ecff,80d4804d,6dcfe173,882249c1,a0d2dc3b,ceaee73e,74e49d8b,79445b15) -,S(9fa3c7bd,6b8544f2,df48309,596ea267,8f8537e1,dc406ec7,cf2c67d1,ae5f1e8a,c8268dfc,2da263eb,ac7d81d8,6b8b504d,6d6bc20f,745eef75,bc9bb378,1193744b) -,S(0,0,0,0,0,0,0,1,34b8595b,6ecec7d7,fc92428,9be8c5bf,b8c2c696,a33b88a7,aee2f57d,4524f93e) -,S(969360d2,54e2dba9,e79d0789,c339250d,c438330a,a66addf9,f27e2ee8,fc9036ac,99bb92f2,4fe59864,a6504cb5,d4407301,f5bb78f,f1f8b31,875ae5c8,644408f3) -,S(318f2c1f,3dc109ad,ed9d8958,e2e6eb8c,e26eec32,f2725ad1,68e116c7,497e7044,75b3371,ac71e480,9d8398ec,8376914e,3aab7ea6,bcb96a5,15c6b39b,a6d2ba60) -,S(bcea96e9,4ff1b11e,6e8a09d6,ec594b00,597e2b0a,37b0e581,50a58d59,33220419,7f0c4863,45a07ba8,c0d55b8c,fe2ef3e0,649abc1e,47313a1f,1763291f,79733985) -,S(4ed9d2a,7f32fa30,fd059de5,ee512073,47d68d12,b77df5b8,6a83a814,3fcdd5c0,5ec78d3d,16cc44e8,a26002a4,4a491ef3,a801c3ff,45555ea5,1ffc13c0,63d96081) -,S(875c93a1,8ac5a86e,9e85e4aa,3c5ad0ac,3d697192,6384b05b,568604b3,8fee8fb6,3738dc95,97add4e5,c0a39af1,b98ccba2,7bb43333,e35ab481,54c52779,9191144b) -,S(47a7cc2e,1cbd64c8,301443ed,be1ab328,85dce80e,a6d8c847,4eb9be09,6db5fecf,d160b834,5807531a,327a7269,3627dab6,f9770a12,14eb182d,fa73ad15,41b57ff0) -,S(3e8a6608,17f28243,f363d67e,b874856f,1fc9810a,9da6c3f3,a72dd23f,ec45513e,8a8d4c8f,37d2572,6ca66369,59c9eca3,82bd076e,867092a0,b428ad99,2c54a63b) -,S(9556e393,974bcd02,c6356a53,fce819d4,887b188c,99b8de16,1e5d3697,d595cdce,23575727,9da3d12b,c0004d12,e06a9cda,8405c91e,b8118260,2793e62f,16c2a29e) -,S(5245e215,52c6f785,2cccef32,e9f54774,cfb6bb9b,c363ae72,71174df4,6d77fdcc,ca78f1f0,894a6670,19609169,78c251dc,64f969e,c25be4b4,202508d3,22d07f3b) -,S(5d6f8ab3,ca0a5fca,611b7738,16adb4f8,df73ad68,5ce45286,75101d00,54ff3eca,640db9f3,9dda2f51,ee3ef3db,775315aa,c06346f1,e31ff76f,83a24bb0,8fc93242) -,S(a4465b18,b7f702e2,a8165184,834ce490,68fb0a51,6658734b,6229eda0,605911a2,c8c7236a,68522b1a,3f5c650e,4673345d,844bcccc,1ca54b7e,ab3ad885,6e6ee7e4) -,S(d49b0640,1e240c43,21b2b173,3b640c6a,e2c4b389,2d3f4f73,8faac78b,9995cf2e,75b3371,ac71e480,9d8398ec,8376914e,3aab7ea6,bcb96a5,15c6b39b,a6d2ba60) -,S(599db82f,8229ad47,7a662726,d2d351a6,f76cefd7,8fc376b1,ef6e344e,4e4dae3a,284268b5,4bf5e42c,5ca5e116,e92cd897,b7184303,418f7ae2,7458a745,6692db96) -,S(a0627644,ebc6a423,1f3e113b,eedbf9d8,21d9374a,af2e7c55,14ee7395,aa9992b7,859f03d8,1b54f459,158e3fc9,ad47c3a3,a54a2537,15bc213d,7b8e6072,9283bfae) -}; -static const secp256k1_ge_storage secp256k1_pre_g_128[ECMULT_TABLE_SIZE(WINDOW_G)] = { - S(ec823227,1d0e875a,8e1cf7e6,f9a35a15,749be650,1cc885c4,f383060b,46bd3d33,aa140b83,97ca0dcd,a15dcd4c,7509bb87,9a34231e,d0d3a586,543bba01,ae7545b8) -,S(66abbf18,f58dd1c9,4b6072c,eb182abe,c31d3af9,6d91060b,d233f462,5716f289,d380d231,1bde27ad,2c400971,66c3744b,3e762c41,42b05998,95a6605f,739c5fdf) -,S(4d756b2,e61d9806,e02ec830,f1fd44f8,46438689,c528346,15a1addf,ac864bcd,36f8fb36,708d2741,9c75c923,178976f6,5386de3,23ab2ceb,f76044c0,9256faa5) -,S(875c93a1,8ac5a86e,9e85e4aa,3c5ad0ac,3d697192,6384b05b,568604b3,8fee8fb6,c8c7236a,68522b1a,3f5c650e,4673345d,844bcccc,1ca54b7e,ab3ad885,6e6ee7e4) -,S(f3a864ac,edc7852f,f4dfae93,5f8588a6,96ff17bf,7233134e,6704ceb,16f3b74c,39cfeb15,665f371f,535d56c1,317a8d62,29ea2827,943a98f1,cee7f685,86e47eec) -,S(3e8a6608,17f28243,f363d67e,b874856f,1fc9810a,9da6c3f3,a72dd23f,ec45513e,8a8d4c8f,37d2572,6ca66369,59c9eca3,82bd076e,867092a0,b428ad99,2c54a63b) -,S(47bbafcd,f612ca3e,703f7721,623bde14,926fc00d,a6bf9e9,41ddb2d5,587626e3,d160b834,5807531a,327a7269,3627dab6,f9770a12,14eb182d,fa73ad15,41b57ff0) -,S(709c8403,ed2fd0f9,5fac44f0,dfa96ec2,e7b357e4,4ebb3dcf,6f688f20,39d3d67d,2e9f47cb,a7f8ace5,cd858d96,c9d82549,688f5ed,eb14e7d2,58c52e9,be4a7c3f) -,S(66fd0ada,7159c2df,fdd925e7,2eb70772,743e39e3,4c10d5cf,f58eac4e,a30e4157,8a8d4c8f,37d2572,6ca66369,59c9eca3,82bd076e,867092a0,b428ad99,2c54a63b) -,S(60d5d771,4e1e7589,e0b1e68c,ed7f881,73fca809,eae35685,65334942,79962dc2,99bb92f2,4fe59864,a6504cb5,d4407301,f5bb78f,f1f8b31,875ae5c8,644408f3) -,S(3d98fe85,5d42f661,e35d8ce3,cfee5f17,57bc3de9,ecec02fa,11a2d90,378ac0e8,bdc6dc52,8a55814e,da6bcb58,e2a73b96,9d975c7,a3270483,b419b29b,5263f45) -,S(2f63a396,4d14ca73,2167e033,306863d8,f75605c7,4ea6c917,890dd50d,fc20f53b,1dd6a38e,deb870ad,f2b2e755,8d327d51,e66a2c81,910d1fa5,b215911c,9c8e004c) -,S(bcea96e9,4ff1b11e,6e8a09d6,ec594b00,597e2b0a,37b0e581,50a58d59,33220419,80f3b79c,ba5f8457,3f2aa473,1d10c1f,9b6543e1,b8cec5e0,e89cd6df,868cc2aa) -,S(ae3796a4,823f3eb4,ea4bd677,110dc3fb,45537c3c,4d10d2e8,e758a3be,4875db83,ef2b477b,a9c921df,5ff65b6e,7c6f96a8,627743ff,a53e3858,cd9d7673,f7435ac4) -,S(5245e215,52c6f785,2cccef32,e9f54774,cfb6bb9b,c363ae72,71174df4,6d77fdcc,ca78f1f0,894a6670,19609169,78c251dc,64f969e,c25be4b4,202508d3,22d07f3b) -,S(26e2e75b,aeb04a72,8b893f67,d27ebfc6,b8984a88,6fbd8eee,5719107d,5a413f0f,61b51308,ac8643f0,7c18eda7,ef9a4196,aa66221,1b809acc,2b2cb538,23010ef8) -,S(e0089ec9,4b25ac6,ff44b5da,2dfedb37,9fdaedf1,6bc8cecd,b1a95fa0,32431c73,e8167442,f91ebb24,6a648515,328996a7,841d924a,bba15e62,2547436e,e0ad7f7b) -,S(f8f16642,266d8dc6,abfdbbc6,f6c47711,a192c051,7f00a633,b78076a7,a7884e72,4f6d0e5,148ce7e1,772f84c,b08903b8,71d2beb5,d0934488,8ab1c75d,42232790) -,S(23082df9,a86b80fc,5185ee3c,6493763b,14a6e237,baf686aa,f589b649,8573d04c,bdc6dc52,8a55814e,da6bcb58,e2a73b96,9d975c7,a3270483,b419b29b,5263f45) -,S(76dac07e,bc240289,791e6a9a,56abdc7,bdf2fac1,e7a0100b,5a0ee09e,3455cc33,8b4502cd,420d4202,39a5ad3c,fa10b0b2,6ccd084d,5faed560,7de07a6,fb7b4093) -,S(8735fb32,6950ae71,31cd03f7,cc1511d8,65c87e84,748c9824,8ccc576e,5480ed8,c63014ea,99a0c8e0,aca2a93e,ce85729d,d615d7d8,6bc5670e,31180979,791b7d43) -,S(969360d2,54e2dba9,e79d0789,c339250d,c438330a,a66addf9,f27e2ee8,fc9036ac,66446d0d,b01a679b,59afb34a,2bbf8cfe,f0a44870,f0e074ce,78a51a36,9bbbf33c) -,S(74f5ba33,89d075d3,eebaa54d,73e9f038,881b7329,5623e833,b5e87beb,29ba3246,74bafd32,bdf2bdfd,c65a52c3,5ef4f4d,9332f7b2,a0512a9f,f821f858,484bb9c) -,S(a4465b18,b7f702e2,a8165184,834ce490,68fb0a51,6658734b,6229eda0,605911a2,c8c7236a,68522b1a,3f5c650e,4673345d,844bcccc,1ca54b7e,ab3ad885,6e6ee7e4) -,S(eac612b0,84cb452a,35d49c60,c0d0c0bd,359eb909,68a80186,92a028f,ece4c331,23575727,9da3d12b,c0004d12,e06a9cda,8405c91e,b8118260,2793e62f,16c2a29e) -,S(f31a7dfe,4d2e4a98,aaf3dd70,25441c7a,2da056b0,db6795e8,3a39d76f,b316bd92,e8167442,f91ebb24,6a648515,328996a7,841d924a,bba15e62,2547436e,e0ad7f7b) -,S(470ce05d,53f77ff0,c5ab8770,a4c11a3e,23f7d4fe,f5c0ab36,1b940a8a,6b8cf7b9,57d6adb1,5f0b1daa,1fce91d7,ccf75d45,9480b056,6f81ff8a,85612173,1cd8f2b8) -,S(d9fbce92,515652cf,3714f87b,e16e505,91a28eb4,1bf7053,2ab42ebd,be900212,2d7d0b7e,4fb4131d,9f21966a,4a73b0f2,639ede1f,26b0ba25,a2580eb,30100c96) -,S(1ea4ff3b,5a0a78a9,fda9d73a,ea5f0629,886b083a,6d3beea4,d6bc96e1,b50da944,9edf1300,7f2b7fb2,92301e8c,77ddb63e,5f2d23c4,315118c1,8b1b6273,86bba11a) -,S(3d303d12,f8eb67d0,ecaee514,bdd90e57,b58b6d6a,c896a26f,78c06103,52225b04,2d7d0b7e,4fb4131d,9f21966a,4a73b0f2,639ede1f,26b0ba25,a2580eb,30100c96) -,S(9fa3c7bd,6b8544f2,df48309,596ea267,8f8537e1,dc406ec7,cf2c67d1,ae5f1e8a,37d97203,d25d9c14,53827e27,9474afb2,92943df0,8ba1108a,43644c86,ee6c87e4) -,S(92906a31,52682000,a59736ed,ef48a7b0,c78d6a49,8727b3b,893d3478,de04ada5,80f3b79c,ba5f8457,3f2aa473,1d10c1f,9b6543e1,b8cec5e0,e89cd6df,868cc2aa) -,S(599db82f,8229ad47,7a662726,d2d351a6,f76cefd7,8fc376b1,ef6e344e,4e4dae3a,284268b5,4bf5e42c,5ca5e116,e92cd897,b7184303,418f7ae2,7458a745,6692db96) -,S(ee534645,42ff07e2,8cf783ee,1da96e00,b81f531,9b9a4885,27f92302,acd12cac,c90704c9,8f72d8be,638a36dc,e8768909,fac7921c,dc54d314,89fbb3e,6da9018a) -,S(7fe309bb,e3e8edd3,3f5f94b,4247256e,41e62e69,fd9f2063,d878c6d6,3d85675f,dca8a8d8,625c2ed4,3fffb2ed,1f956325,7bfa36e1,47ee7d9f,d86c19cf,e93d5991) -,S(64eaaad1,9c7e9d01,5cc997ff,c2f23022,c59805d,48a48e1,970b8847,10211659,fb092f1a,eb73181e,f88d07b3,4f76fc47,8e2d414a,2f6cbb77,754e38a1,bddcd49f) -,S(59e10f43,eb4b2fb0,94f2f66d,1404dd08,ab9c2442,50bd16e0,21feb78f,e0380d01,e2295c71,21478f52,d4d18aa,72cd82ae,1995d37e,6ef2e05a,4dea6ee2,6371fbe3) -,S(c979de1c,1318f60f,a9929ff3,a6621b9,d15226e8,2883556e,36a5f77b,28e3b585,d7bd974a,b40a1bd3,a35a1ee9,16d32768,48e7bcfc,be70851d,8ba758b9,996d2099) -,S(64dd1439,5d19a544,a7a1e81b,b9d079b3,593e7022,6bcd444e,6dc8197a,1a6dc3e6,d380d231,1bde27ad,2c400971,66c3744b,3e762c41,42b05998,95a6605f,739c5fdf) -,S(d8a3fd95,b7ed8e8a,674da307,94911393,303917c2,60944d1f,76261274,fba45757,a13872c2,e933bb17,5d9ffd5b,b5b6e10c,57fe3c00,baaaa15a,e003ec3e,9c269bae) -,S(acee4285,57fc32a1,d3d14199,30cadaf0,613a630a,7a7e8c14,d08d05df,19143c3a,ca78f1f0,894a6670,19609169,78c251dc,64f969e,c25be4b4,202508d3,22d07f3b) -,S(5d771e5d,6dc6c87,5ede8bae,4b27a9d4,3c5f8da2,8e84f5c3,501299c8,db16484c,859f03d8,1b54f459,158e3fc9,ad47c3a3,a54a2537,15bc213d,7b8e6072,9283bfae) -,S(e8d3f45a,b5be455f,dc3c2270,34100ca2,b8d203e1,35a9ed3d,5c8b703c,ef4d9b48,2d7d0b7e,4fb4131d,9f21966a,4a73b0f2,639ede1f,26b0ba25,a2580eb,30100c96) -,S(9963e528,754cbba8,29622664,1aec11b0,3fba2739,40f61cbd,5d4c0478,5429f40,298f1c0e,f903dc7d,b940cdc0,ed8183eb,a891c59b,a2a3be25,bf0dd4d5,b03574c3) -,S(cbdb65,553cd5d8,ff61cf33,e53fdd9a,cf0ee159,c21dc578,be5bac2b,7973c229,ca78f1f0,894a6670,19609169,78c251dc,64f969e,c25be4b4,202508d3,22d07f3b) -,S(5029bff6,d4c80347,738230c8,cb252906,471b5bc1,3d26a533,304f5f0d,808f756c,97682462,a5c276b7,1568f961,f218a99e,4e528b1b,d4e255b0,acc27744,98ec84fe) -,S(80c5672b,3f773975,b5829101,6fa9de9b,33863263,8a978db,934f04c1,3a909b85,55ebf47c,6835f232,5ea232b3,8af64478,65cbdce1,2f2c5a79,abc445fd,518ab677) -,S(226e653f,c8df7744,9bacbf12,7d1dcbf9,87f05b2a,e7edbd28,1f564575,c48dcf18,5ec78d3d,16cc44e8,a26002a4,4a491ef3,a801c3ff,45555ea5,1ffc13c0,63d96081) -,S(521e20fc,9c7c0514,47f31e74,5bb81662,dac66374,9b891a6f,d9681cb6,21e3155c,9e4aecf7,5379bc0f,83e71258,1065be69,f5599dde,e47f6533,d4d34ac6,dcfeed37) -,S(5472b5d0,4ba1ca80,236183b1,46e596e5,4b20b83b,248988c8,bbe8460e,3666f262,a829524e,a0f4e255,e0316e28,3308a2ba,6b7f4fa9,907e0075,7a9ede8b,e3270977) -,S(2ecd421e,47399e76,60d10143,1789a437,89b54d23,31d1cb78,49cabda2,bc5174f9,c8268dfc,2da263eb,ac7d81d8,6b8b504d,6d6bc20f,745eef75,bc9bb378,1193744b) -,S(4097892c,5478baea,62e8da20,6d17fcd8,b8eed45f,24fb7648,fe742508,8da60a4e,10d4b884,5636de20,a009a491,83906957,9d88bc00,5ac1c7a7,3262898b,8bca16b) -,S(eb5c4120,4917d176,6fea883b,e680dfd9,233d9654,97fac48b,76f5d0d4,7f6dbf73,6897db9d,5a3d8948,ea97069e,de75661,b1ad74e4,2b1daa4f,533d88ba,67137731) -,S(92b866ad,a37a3f2f,bc607717,96b2c74f,57dde74c,da8e015f,792df531,7eb21fa6,55ebf47c,6835f232,5ea232b3,8af64478,65cbdce1,2f2c5a79,abc445fd,518ab677) -,S(a78dab,f2f2ef7f,4d424752,aa1aeaf5,50bec241,bf9ad129,3b9fe680,32b6141b,9de02272,226d60c0,97e5907c,407f9e07,d4bc6959,a2079419,139a9578,4e48cb7f) -,S(b084fee5,5da62ee0,ebdebf3b,245e0d4e,def46aac,bfdc9f43,261d3e2b,eed946a0,80f3b79c,ba5f8457,3f2aa473,1d10c1f,9b6543e1,b8cec5e0,e89cd6df,868cc2aa) -,S(ff1755e,623c8369,f55edda4,2a5deef0,b32c57f4,80c5884f,d2a2dde1,b1c078c4,640db9f3,9dda2f51,ee3ef3db,775315aa,c06346f1,e31ff76f,83a24bb0,8fc93242) -,S(2cdce338,ae1f5aa0,55c76cb5,acbd084e,3284bb5d,b8cf9b4a,141cc8ee,1aa61e59,e8167442,f91ebb24,6a648515,328996a7,841d924a,bba15e62,2547436e,e0ad7f7b) -,S(3faf6744,2aebb675,c6b75911,e3d34f4c,a8f5b669,a4c438f2,2c163659,3e36c1ea,fc0b579e,cbaf2c74,7b5c792d,7b66abf2,dfb1a44d,2ed6a417,7e1fc6bd,a989b623) -,S(34772cad,ad5888f2,53a810b7,5b175b8d,e3a454e4,26a1b5a5,c003f222,8e7b45c0,d380d231,1bde27ad,2c400971,66c3744b,3e762c41,42b05998,95a6605f,739c5fdf) -,S(853cffd4,4cb0a29e,390ad886,455566cc,2776ab74,55210e57,9bf1fcc0,c8d767ed,621fdd8d,dd929f3f,681a6f83,bf8061f8,2b4396a6,5df86be6,ec656a86,b1b730b0) -,S(1130e02f,29480660,b2cb4f68,81da3f2c,1bdaf64,8df3b6ce,1a333738,29e4165e,10d4b884,5636de20,a009a491,83906957,9d88bc00,5ac1c7a7,3262898b,8bca16b) -,S(a0627644,ebc6a423,1f3e113b,eedbf9d8,21d9374a,af2e7c55,14ee7395,aa9992b7,7a60fc27,e4ab0ba6,ea71c036,52b83c5c,5ab5dac8,ea43dec2,84719f8c,6d7c3c81) -,S(1e864d74,9e96a16a,c4299b88,226aaff9,9a45ab9c,203853ac,ea0378ef,5715ded6,6120ecff,80d4804d,6dcfe173,882249c1,a0d2dc3b,ceaee73e,74e49d8b,79445b15) -}; -#else - #error No known generator for the specified exhaustive test group order. -#endif -#else /* !defined(EXHAUSTIVE_TEST_ORDER) */ +#ifdef EXHAUSTIVE_TEST_ORDER +# error Cannot compile precomputed_ecmult.c in exhaustive test mode +#endif /* EXHAUSTIVE_TEST_ORDER */ #define WINDOW_G ECMULT_WINDOW_SIZE -static const secp256k1_ge_storage secp256k1_pre_g[ECMULT_TABLE_SIZE(WINDOW_G)] = { +const secp256k1_ge_storage secp256k1_pre_g[ECMULT_TABLE_SIZE(WINDOW_G)] = { S(79be667e,f9dcbbac,55a06295,ce870b07,29bfcdb,2dce28d9,59f2815b,16f81798,483ada77,26a3c465,5da4fbfc,e1108a8,fd17b448,a6855419,9c47d08f,fb10d4b8) -#if ECMULT_TABLE_SIZE(WINDOW_G) > 1 +#if WINDOW_G > 2 ,S(f9308a01,9258c310,49344f85,f89d5229,b531c845,836f99b0,8601f113,bce036f9,388f7b0f,632de814,fe337e6,2a37f356,6500a999,34c2231b,6cb9fd75,84b8e672) #endif -#if ECMULT_TABLE_SIZE(WINDOW_G) > 2 +#if WINDOW_G > 3 ,S(2f8bde4d,1a072093,55b4a725,a5c5128,e88b84bd,dc619ab7,cba8d569,b240efe4,d8ac2226,36e5e3d6,d4dba9dd,a6c9c426,f788271b,ab0d6840,dca87d3a,a6ac62d6) ,S(5cbdf064,6e5db4ea,a398f365,f2ea7a0e,3d419b7e,330e39c,e92bdded,cac4f9bc,6aebca40,ba255960,a3178d6d,861a54db,a813d0b8,13fde7b5,a5082628,87264da) #endif -#if ECMULT_TABLE_SIZE(WINDOW_G) > 4 +#if WINDOW_G > 4 ,S(acd484e2,f0c7f653,9ad178a,9f559abd,e0979697,4c57e714,c35f110d,fc27ccbe,cc338921,b0a7d9fd,64380971,763b61e9,add888a4,375f8e0f,5cc262a,c64f9c37) ,S(774ae7f8,58a9411e,5ef4246b,70c65aac,5649980b,e5c17891,bbec1789,5da008cb,d984a032,eb6b5e19,243dd56,d7b7b365,372db1e2,dff9d6a8,301d74c9,c953c61b) ,S(f28773c2,d975288b,c7d1d205,c3748651,b075fbc6,610e58cd,deeddf8f,19405aa8,ab0902e,8d880a89,758212eb,65cdaf47,3a1a06da,521fa91f,29b5cb52,db03ed81) ,S(d7924d4f,7d43ea96,5a465ae3,95ff411,31e5946f,3c85f79e,44adbcf8,e27e080e,581e2872,a86c72a6,83842ec2,28cc6def,ea40af2b,d896d3a5,c504dc9f,f6a26b58) #endif -#if ECMULT_TABLE_SIZE(WINDOW_G) > 8 +#if WINDOW_G > 5 ,S(defdea4c,db677750,a420fee8,7eacf21,eb9898ae,79b97687,66e4faa0,4a2d4a34,4211ab06,94635168,e997b0ea,d2a93dae,ced1f4a0,4a95c0f6,cfb199f6,9e56eb77) ,S(2b4ea0a7,97a443d2,93ef5cff,444f4979,f06acfeb,d7e86d27,74756561,38385b6c,85e89bc0,37945d93,b343083b,5a1c8613,1a01f60c,50269763,b570c854,e5c09b7a) ,S(352bbf4a,4cdd1256,4f93fa33,2ce33330,1d9ad402,71f81071,81340aef,25be59d5,321eb407,5348f534,d59c1825,9dda3e1f,4a1b3b2e,71b1039c,67bd3d8b,cf81998c) @@ -191,7 +42,7 @@ static const secp256k1_ge_storage secp256k1_pre_g[ECMULT_TABLE_SIZE(WINDOW_G)] = ,S(c44d12c7,65d812e,8acf28d7,cbb19f90,11ecd9e9,fdf281b0,e6a3b5e8,7d22e7db,2119a460,ce326cdc,76c45926,c982fdac,e106e86,1edf61c5,a039063f,e0e6482) ,S(6a245bf6,dc698504,c89a20cf,ded60853,152b6953,36c28063,b61c65cb,d269e6b4,e022cf42,c2bd4a70,8b3f5126,f16a24ad,8b33ba48,d0423b6e,fd5e6348,100d8a82) #endif -#if ECMULT_TABLE_SIZE(WINDOW_G) > 16 +#if WINDOW_G > 6 ,S(1697ffa6,fd9de627,c077e3d2,fe541084,ce13300b,bec1146,f95ae57f,d0bd6a5,b9c398f1,86806f5d,27561506,e4557433,a2cf1500,9e498ae7,adee9d63,d01b2396) ,S(605bdb01,9981718b,986d0f07,e834cb0d,9deb8360,ffb7f61d,f982345e,f27a7479,2972d2d,e4f8d206,81a78d93,ec96fe23,c26bfae8,4fb14db4,3b01e1e9,56b8c49) ,S(62d14dab,4150bf49,7402fdc4,5a215e10,dcb01c35,4959b10c,fe31c7e9,d87ff33d,80fc06bd,8cc5b010,98088a19,50eed0db,1aa1329,67ab4722,35f56424,83b25eaf) @@ -209,7 +60,7 @@ static const secp256k1_ge_storage secp256k1_pre_g[ECMULT_TABLE_SIZE(WINDOW_G)] = ,S(754e3239,f325570c,dbbf4a87,deee8a66,b7f2b334,79d468fb,c1a50743,bf56cc18,673fb86,e5bda30f,b3cd0ed3,4ea49a0,23ee33d0,197a695d,c5d9809,3c536683) ,S(e3e6bd10,71a1e96a,ff57859c,82d570f0,33080066,1d1c952f,9fe26946,91d9b9e8,59c9e0bb,a394e76f,40c0aa58,379a3cb6,a5a22839,93e90c41,67002af4,920e37f5) #endif -#if ECMULT_TABLE_SIZE(WINDOW_G) > 32 +#if WINDOW_G > 7 ,S(186b483d,56a0338,26ae73d8,8f732985,c4ccb1f3,2ba35f4b,4cc47fdc,f04aa6eb,3b952d32,c67cf77e,2e17446e,204180ab,21fb8090,895138b4,a4a797f8,6e80888b) ,S(df9d70a6,b9876ce5,44c98561,f4be4f72,5442e6d2,b737d9c9,1a832172,4ce0963f,55eb2daf,d84d6ccd,5f862b78,5dc39d4a,b1572227,20ef9da2,17b8c45c,f2ba2417) ,S(5edd5cc2,3c51e87a,497ca815,d5dce0f8,ab52554f,849ed899,5de64c5f,34ce7143,efae9c8d,bc141306,61e8cec0,30c89ad0,c13c66c0,d17a2905,cdc706ab,7399a868) @@ -243,7 +94,7 @@ static const secp256k1_ge_storage secp256k1_pre_g[ECMULT_TABLE_SIZE(WINDOW_G)] = ,S(c4191636,5abb2b5d,9192f5f,2dbeafec,208f020f,12570a18,4dbadc3e,58595997,4f14351,d0087efa,49d245b3,28984989,d5caf945,f34bfc0,ed16e96b,58fa9913) ,S(841d6063,a586fa47,5a724604,da03bc5b,92a2e0d2,e0a36acf,e4c73a55,14742881,73867f5,9c0659e8,1904f9a1,c7543698,e62562d6,744c169c,e7a36de0,1a8d6154) #endif -#if ECMULT_TABLE_SIZE(WINDOW_G) > 64 +#if WINDOW_G > 8 ,S(5e95bb39,9a6971d3,76026947,f89bde2f,282b3381,928be4d,ed112ac4,d70e20d5,39f23f36,6809085b,eebfc711,81313775,a99c9aed,7d8ba38b,161384c7,46012865) ,S(36e4641a,53948fd4,76c39f8a,99fd974e,5ec07564,b5315d8b,f99471bc,a0ef2f66,d2424b1b,1abe4eb8,164227b0,85c9aa94,56ea1349,3fd563e0,6fd51cf5,694c78fc) ,S(336581e,a7bfbbb2,90c191a2,f507a41c,f5643842,170e914f,aeab27c2,c579f726,ead12168,595fe1be,99252129,b6e56b33,91f7ab14,10cd1e0e,f3dcdcab,d2fda224) @@ -309,7 +160,7 @@ static const secp256k1_ge_storage secp256k1_pre_g[ECMULT_TABLE_SIZE(WINDOW_G)] = ,S(809a20c6,7d64900f,fb698c4c,825f6d5f,2310fb04,51c86934,5b7319f6,45605721,9e994980,d9917e22,b76b0619,27fa0414,3d096ccc,54963e6a,5ebfa5f3,f8e286c1) ,S(1b38903a,43f7f114,ed4500b4,eac7083f,defece1c,f29c6352,8d563446,f972c180,4036edc9,31a60ae8,89353f77,fd53de4a,2708b26b,6f5da72a,d3394119,daf408f9) #endif -#if ECMULT_TABLE_SIZE(WINDOW_G) > 128 +#if WINDOW_G > 9 ,S(90a80db6,eb294b9e,ab0b4e8d,dfa3efe7,263458ce,2d07566d,f4e6c588,68feef23,753c8b9f,9754f18d,87f21145,d9e2936b,5ee050b2,7bbd9681,442c76e9,2fcf91e6) ,S(c2c80f84,4b705998,12d62546,f60340e,3e6f3605,4a14546e,6dc25d47,376bea9b,86ca160d,68f4d4e7,18b495b8,91d3b1b5,73b871a7,2b4cf61,23abd448,3aa79c64) ,S(9cf60674,4cf4b5f3,fdf989d3,f19fb265,2d00cfe1,d5fcd692,a323ce11,a28e7553,8147cbf7,b973fcc1,5b57b6a3,cfad6863,edd0f30e,3c45b85d,c300c513,c247759d) @@ -439,7 +290,7 @@ static const secp256k1_ge_storage secp256k1_pre_g[ECMULT_TABLE_SIZE(WINDOW_G)] = ,S(367807c9,a3606b4e,1b8c2616,ad528030,1dfcf686,40eddf02,fc59317c,230e9a86,1f023f2f,a2bbece7,3dba14c,124095cb,fdc4f92f,281a14,8304a412,c16ecae6) ,S(8ec4fdc3,9891f6af,1374e06f,c44b815,1b82541,75fc4909,acba5941,201af62b,2dc6cae5,cac2d887,83dca0e5,3c798f8,fe067bcf,5fc29751,13756cf7,ef4e5f1b) #endif -#if ECMULT_TABLE_SIZE(WINDOW_G) > 256 +#if WINDOW_G > 10 ,S(5cc24a6d,4c5b24f9,14542f91,e5fa937f,ffa08551,51b8b842,8729b06a,9178a263,b1da8635,81531a1f,bfb38a4e,419fa1fe,ca8d55a8,3ddbcb98,da19d5cf,fb7da472) ,S(83905926,c03905c3,a9644a6c,da810dd2,92602a50,50c52a21,9134fc4d,e3599e9f,4293260f,e8af6792,a20b115e,aa837638,9094298b,21d9de16,cf20e0c5,7a46089a) ,S(944b097e,4721e9dd,f8204ac3,d3878fa,e8fa6c14,34ae4822,481b2985,6589b6c7,5fc47565,30e9b095,f8b79643,1e745b99,1525bd4c,4764e8e,e8af4b96,9bd6ddf6) @@ -697,7 +548,7 @@ static const secp256k1_ge_storage secp256k1_pre_g[ECMULT_TABLE_SIZE(WINDOW_G)] = ,S(d9a0c689,95283291,972d6a72,897181b2,6b4ae317,4e98a676,f75bbfbf,81be876e,d36ae886,a0acbc9a,d1564d97,4d3f0309,e9039b93,bb350d92,2b7f8c7a,c6bfa042) ,S(c7a36324,6aeb7c8c,991b2aa7,10abdf5c,fff29912,30b3a69f,be2dd481,7c7c3e0a,1298fdd7,e448d2d,79986302,6b4b2d49,2b32148,d6d1e5f8,15f5b0c0,5c9e21f) #endif -#if ECMULT_TABLE_SIZE(WINDOW_G) > 512 +#if WINDOW_G > 11 ,S(635cd7a0,5064d3bc,66535a0,4dbf563a,640d2464,c7fe0ac4,8304214f,e4985a86,e40265,913e77f6,46735cfc,af9e2f30,e8d5f047,f3281a4c,e0453e27,e9e3ae1f) ,S(5da624f,38edea25,37f5b632,695b481a,eca54bb7,2169cadc,c5b91e10,989ee5f5,d3ea6dba,a1080300,fffaeeb2,43a5256e,48c28822,37b16505,a220d771,ca721779) ,S(aa4f64a2,b19775ec,92c1f687,1fe4b6d5,ce05278c,2976c80e,fe19284f,e87d4d5e,f39f117e,95fce0fb,6976e949,9583a66c,224ea028,8396518e,293793dc,3ed4f240) @@ -1211,7 +1062,7 @@ static const secp256k1_ge_storage secp256k1_pre_g[ECMULT_TABLE_SIZE(WINDOW_G)] = ,S(dd879eba,f870ee3d,f6e3f03,60833e12,fbdf844c,d6a5b76c,19802485,6649bbc1,e7bb04c3,a99a5c11,dd7a324c,1d5696c8,b041a12a,c6a538f7,3094716c,9943c55a) ,S(7aa4afbe,29b06a1e,da9319d5,72572b13,1df68f74,1b5f8d8e,d00ccc04,80f8016e,15f79a9f,b77b8587,7f02e3c1,3202b972,423fd00d,937c0d2d,c9d94b17,53fe7130) #endif -#if ECMULT_TABLE_SIZE(WINDOW_G) > 1024 +#if WINDOW_G > 12 ,S(9145f3f5,876a3265,5d7fee64,f2d6e660,c34354e9,1571d68b,a19e4cc5,8c39f890,45e0a95d,cf369fd0,5bc9ba7f,da108d6f,37650c1d,5766b6c9,98ecda28,b285d8ff) ,S(5450b752,36fb010d,1ef37afa,2077f3dc,a5a7f6c8,91a21317,13df740f,4511ac9e,a7c7ed57,62bef86d,4de1084d,3ded2d7b,13ff563a,67109ef0,8b6f0180,fe6ba175) ,S(ba6c0d72,5e48de2c,cb8256d6,7417d075,bbf2766f,d13501b8,4c32cf88,c0666a0f,cd2a9132,7137299f,50eda669,3a599032,bdecd64b,91bcc640,5ed186f3,e525e442) @@ -2237,7 +2088,7 @@ static const secp256k1_ge_storage secp256k1_pre_g[ECMULT_TABLE_SIZE(WINDOW_G)] = ,S(6d109d8c,2505808f,7b4052d6,b170ec80,c68373bb,e02f2b62,1cd29773,a96acb3e,c8dea0de,511f6e40,5141088e,cb023bff,200c870b,9cb952ad,c5038b28,eadc9a57) ,S(9c219898,6d202467,c055c734,73557505,6105b3e,2874dbf2,8f7f0c39,66e1af88,8637496c,8eddff12,f9dd4146,36565e0f,efddf5cd,52d48455,ec9fb2d,8dd0914a) #endif -#if ECMULT_TABLE_SIZE(WINDOW_G) > 2048 +#if WINDOW_G > 13 ,S(22ca5039,e660f60,1036dd75,2bb973b,dcd104b5,dcb8f2e7,4de8edc6,c292b03a,86fd4471,1ef6b81e,4416449a,708fa0c9,cb6731ba,c403e58e,da5e915f,646b11e8) ,S(cfc18f02,cc004640,f2116fdd,1f6ca202,2e39be25,df75e27c,80bde842,e6b6f938,d62b58df,4f79d0e5,97ba2923,f708cbe5,c09236a4,d9d01398,6451d684,6290df7d) ,S(3388bcc2,3425458e,991f46ca,6235c50a,57490556,becbaf25,9d3c14f9,9a80a087,7d4aa2f8,6f65783,1c22316,6958fbf3,6c3dca5,d4ac413b,3102e13e,a645c016) @@ -4287,7 +4138,7 @@ static const secp256k1_ge_storage secp256k1_pre_g[ECMULT_TABLE_SIZE(WINDOW_G)] = ,S(d5acff71,e82a455c,3a1ce292,689e8686,f441ce1b,e644e79a,bd6d0efe,29270865,aac6d48f,1b46e970,a044971a,ad13f033,ca8cde96,958d870e,dc7d80,1d26d5e8) ,S(5cf51c1b,2210d85,9d765e17,32109514,8f03fc57,51004b6a,91f098e2,e2711596,1eeb19e0,610df459,2c31e58e,aa2a2148,17fe9ee,a3995838,f395bdcf,26d5c3b3) #endif -#if ECMULT_TABLE_SIZE(WINDOW_G) > 4096 +#if WINDOW_G > 14 ,S(21456873,b58e3687,52c75800,8d3bcae,9efaf1f5,c5727842,25e3d854,8fd421cb,a2f2d10,c85e8a0f,4a136ad0,5df991ce,7d3c5585,a263d5cf,da50a4f4,3db76cb0) ,S(c10de5c0,51ae2d73,28ac06ca,cca840b4,ed7ab204,21c6122f,1d68fe7f,7893d38d,ee30e086,a891484,2ad4041,f9ab9c57,cff1a315,f0642d31,b31c2914,faac99e0) ,S(be778032,f12c1b77,9bba3d9e,d290ca90,30ac7050,bdd77a2,7eac09be,eea65c0b,8657348b,a1e27a63,1dd2a54b,e2d5270f,4cca817a,219c5378,4d4f73ba,2c932e63) @@ -8386,22 +8237,22 @@ static const secp256k1_ge_storage secp256k1_pre_g[ECMULT_TABLE_SIZE(WINDOW_G)] = ,S(1e70619c,381a6adc,e5d925e0,c9c74f97,3c02ff64,ff2662d7,34efc485,d2bce895,c923f771,f543ffed,42935c28,8474aaaf,80a46ad4,3c579ce0,bb5e663d,668b24b3) #endif }; -static const secp256k1_ge_storage secp256k1_pre_g_128[ECMULT_TABLE_SIZE(WINDOW_G)] = { +const secp256k1_ge_storage secp256k1_pre_g_128[ECMULT_TABLE_SIZE(WINDOW_G)] = { S(8f68b9d2,f63b5f33,9239c1ad,981f162e,e88c5678,723ea335,1b7b444c,9ec4c0da,662a9f2d,ba063986,de1d90c2,b6be215d,bbea2cfe,95510bfd,f23cbf79,501fff82) -#if ECMULT_TABLE_SIZE(WINDOW_G) > 1 +#if WINDOW_G > 2 ,S(38381dbe,2e509f22,8ba93363,f2451f08,fd845cb3,51d954be,18e2b8ed,d23809fa,e4a32d0a,fb917dc,b09405a5,520eb1cc,3681fccb,32d8f24d,bd707518,331fed52) #endif -#if ECMULT_TABLE_SIZE(WINDOW_G) > 2 +#if WINDOW_G > 3 ,S(49262724,e4372ae6,f6921b82,aa4699a1,f186aea5,40122630,3ea42648,97c2a310,1337e773,bca7abf9,5a2cfa56,9714303b,6d163612,a75ff8ce,c41b681,5e27ded0) ,S(e306568c,1a240c90,d5e253b3,e477e2f8,4dcc1a56,ff06db8d,1384b079,cebd2d31,eac6fe3,78934260,888f2b10,7f7d0db6,ffbc8042,be373826,692b4083,92546e44) #endif -#if ECMULT_TABLE_SIZE(WINDOW_G) > 4 +#if WINDOW_G > 4 ,S(3b9e100e,2428cefc,271b0e76,23fbd633,74ebf8d9,aab41dd9,c530c39e,363136b0,fafb9815,2d16bb71,df1533eb,8f475b26,a2ae28a3,3ad31f81,953ec16f,6cdbbc8a) ,S(bb0aad49,712ac9a9,2b76ca80,f5dedef7,17ca0768,8107beee,9608f047,2f485d3f,ea699c53,c5835479,8ecd201f,7297da34,895a5afa,31670bff,e7939250,3ca2f975) ,S(79090ac8,e4eefcc0,d4e8eb19,7afe0113,e1e58b4d,b01123de,4aeed33a,36718dc9,eaab722b,91905b8f,13d816cb,cd9aaa56,dd36afb7,ba9008b,963322b1,1cfae7c5) ,S(e77c81ad,e9f97b55,1c03dbbc,e549ba66,8dd71de7,cd775ad2,a269694c,7f60c7d1,3acf1478,eef81321,c5fc3b32,3ea81543,631470f7,1c2986d3,4ec581f2,82d72449) #endif -#if ECMULT_TABLE_SIZE(WINDOW_G) > 8 +#if WINDOW_G > 5 ,S(de2b5ce9,dbce511c,f2d8878e,3ded87cc,3d633dae,a2d45341,501fb3a4,55ccf6b0,f10576f3,d3c3e0e1,bbf717e9,8b1a3744,65b8c45a,c66318bb,34829eb7,11100666) ,S(d07bddff,d491a2fe,1ea59fbd,7c121217,29659ca5,de46658b,26b1460b,13c03c56,b2ad4708,cd3c97dd,f9c40e2,a1de04d5,61d963ff,8cc2eea7,6be3f60c,2b405ce7) ,S(82403e7c,5d3016af,3765ec4c,396ce8e1,f8da45c,434b8257,10edab41,bb6a4d51,d09661b,e27cb767,4456badd,b3e84051,99ab6ccc,4ec67c1b,11e92ead,7b463b19) @@ -8411,7 +8262,7 @@ static const secp256k1_ge_storage secp256k1_pre_g_128[ECMULT_TABLE_SIZE(WINDOW_G ,S(1f56f096,b18a7499,a153a5ae,acf8be05,8496dd23,da8e6c19,215628fb,c0567ed0,fef22b8a,3b52f490,83004436,b65cd69,c94189f4,1a93c0d5,1fc13cb4,379dff58) ,S(1d9f69b1,a4a47432,e386f525,234aa30,79e947cf,cf203297,4e0fc05b,638e213b,d898ec17,949c0761,b38500c3,a2b1da24,5438d5b3,d3f6f720,41f15d6e,e4d4ccbb) #endif -#if ECMULT_TABLE_SIZE(WINDOW_G) > 16 +#if WINDOW_G > 6 ,S(128c913,4d9dcb78,12fc4361,5c67ad0,55213354,dc8008b1,aeb5a9dc,fb629efd,fee3e54a,dd152610,d9725936,99d662,c160c8e4,ec6f76e4,5ff41818,be67c96) ,S(21ec012f,5a95b94d,244b8d51,9756075c,301f2854,8e2c51fc,49c0e3d9,d1a9685,2def2105,77af497f,4c7fef71,6949f28e,7418eda6,fd5fc162,d128de19,3cde08ae) ,S(688f5202,fb9d8bc0,9e480e89,4c7cfc74,761c3be7,7dafb11c,58422836,3e331cc5,96ba7d59,63b541a7,2ec7cabd,92403434,1a393eaf,89eebd94,62d9c218,c7302cd3) @@ -8429,7 +8280,7 @@ static const secp256k1_ge_storage secp256k1_pre_g_128[ECMULT_TABLE_SIZE(WINDOW_G ,S(144e88f6,3e73abff,72cac11e,7ddccf79,19e744e6,278941ae,18d1b797,e098e4e5,63cdbf3,5df3c655,c58197f,ea54633d,158705cf,7dc2eb3b,4e09f83c,3021837c) ,S(9436e3dc,489ecd8e,2d16a739,c9c73e3d,60e5bc93,68157039,75b8efbd,5c3a9081,1460531f,50cb6ebf,d1aa7806,ea84e7f7,8e8d76b2,b3a66d5e,3a0bf60,39a7e59c) #endif -#if ECMULT_TABLE_SIZE(WINDOW_G) > 32 +#if WINDOW_G > 7 ,S(9d3c2561,7a56d10b,46d9b01a,1710d193,e840e005,df669e76,1936c275,20890db9,6bbdc0bc,4c4ae9bc,c2dfee9b,82da9b94,1f89ffcd,e8af2aca,4467ce3,78521ea3) ,S(29f98e50,f51b7f8b,e18c6ae0,b453c4f2,d0aca5a8,b0e61d2d,dda8506,3fdb76c8,daf3bcdc,ae8e031c,73eb8b21,14058063,58a6ec30,ad379186,df80e3c7,f0e5d28f) ,S(d67d30c2,c71daa36,1805e31,1dd6046e,17a89752,94d76e1a,538af074,4dc22c94,48b9b0e7,12c807b0,b92e690a,a2e068cc,e87ebbbe,aaf4bd96,9c1114bb,a54f670c) @@ -8463,7 +8314,7 @@ static const secp256k1_ge_storage secp256k1_pre_g_128[ECMULT_TABLE_SIZE(WINDOW_G ,S(6f97ac0f,27b87905,6b442d13,e566978e,91f0cc1d,d6ac1e64,e9764a35,325dd1b7,83c6e70c,fac6c707,226ce1cc,691b38a0,7e937f5a,5f2d9c81,4dd0d3ff,9f433d32) ,S(72c5d60f,eb014e2d,ba8265ca,d454f261,2d6abcd4,b2236bad,c94c4801,561dce1f,e3119a19,7ef91963,b3b28216,3c5d3acb,97b281b6,d246cbf0,690b40da,63978fe2) #endif -#if ECMULT_TABLE_SIZE(WINDOW_G) > 64 +#if WINDOW_G > 8 ,S(a96d2da0,1b10186,6998659d,f441a1b0,2af32b94,aae8c6ea,707d9ed0,d5f33825,660d7d83,5da5235,9f7cfd41,28c370aa,5659ea71,16a91690,6c0e8108,a513f9f) ,S(2cce6f63,4d815ecc,1981d200,87616677,d906aa27,990c4875,17314dc5,5be3c4fa,615210dd,bd599e91,1b6f997a,fd05475,b33cb274,c9ecb6e5,d3c23323,beba4b50) ,S(992b0084,525dd399,d98602d9,8b8d53b2,4558fafc,758a2f46,60e89bd6,a645f0c4,83ca98d2,26545a29,8c45f40b,11420602,f5a5c70e,595eea57,2da64d61,a4e2f98d) @@ -8529,7 +8380,7 @@ static const secp256k1_ge_storage secp256k1_pre_g_128[ECMULT_TABLE_SIZE(WINDOW_G ,S(7590a4a,627d5e90,e42a4b0f,6be6497a,f906182d,d2dddc48,a8b6bbfd,f56c0504,d611e21a,b5498760,5c97e878,baa464bc,cc5bd875,353b69f8,1f93ce2a,a6c38587) ,S(94b1da0f,b310e056,db0dff72,81db3362,1fcc555d,bf3c973b,76097908,7fe19d6a,8318893d,d5b41a56,33a2ab4,ae4b953c,45c42e9c,8f2fd159,85286de3,fd4fc217) #endif -#if ECMULT_TABLE_SIZE(WINDOW_G) > 128 +#if WINDOW_G > 9 ,S(b5af4299,bcdacef1,e07d081,daec2cfa,d6f8f821,38e151a5,f20e6d52,84c9a6cb,c0984407,8a7db82d,f572987e,b137dc09,c8cf65fd,aedcb20,43b2479b,ab95448d) ,S(5f49ad43,70744587,3980a153,c851829b,f8ef6141,9889bb0c,3c476847,6939c3e3,5c40d385,20f56c3d,ba08ae1,b40fc24a,2ae25c94,45cae0f7,d01d1800,747e04eb) ,S(f6bb067f,88ccc11c,64e30d1a,e6893942,16bea3bf,26ec9c64,5cde1b9e,487da385,315a476d,7268978c,d89d4ec8,adde4a83,28dbfdd9,f2bf44fe,ad4ed721,78288f55) @@ -8659,7 +8510,7 @@ static const secp256k1_ge_storage secp256k1_pre_g_128[ECMULT_TABLE_SIZE(WINDOW_G ,S(8ac79852,75673818,69fe93c9,6141493e,72ca344,790487b,d5425ed6,9f5c5c18,bb314248,fcbfc867,d1972e04,b9ef1f90,775375f9,9d25bcec,684c72c9,bdd1c08e) ,S(560e9711,88cbc7ce,c61f3bf4,45f0ade3,6f3f174d,219a160,5f9c8692,3f848b9d,9e92dace,6775cc67,bfbbf21f,c64e6e9d,4d133f8a,c18ee277,bc80ef6d,d1b9cd2f) #endif -#if ECMULT_TABLE_SIZE(WINDOW_G) > 256 +#if WINDOW_G > 10 ,S(8c464204,4b95eb66,cc95ca08,8469a1c0,28eea52f,7f709fe,e6d90700,f5fb943a,bf10fc5d,7bc25181,301b3a61,5ebea597,a3492025,953c3aa5,1a11271e,689d0223) ,S(d293f74c,f3578b71,35377247,cd8b0367,7f2245da,f87453cb,4d8d1cc2,871eb5aa,86c2013a,61dfea14,63e45931,eb09050f,f080e3d4,ae357423,ba7afbed,a64a6f26) ,S(35997241,ae757b1f,c767611e,c76eb935,fefbf7a7,33666aff,4c6bd744,d7687d35,bcbea61f,246bcd4c,c3c7fd35,7bd393da,2f36e0ef,cca0df9c,5994f96f,c44b2aa0) @@ -8917,7 +8768,7 @@ static const secp256k1_ge_storage secp256k1_pre_g_128[ECMULT_TABLE_SIZE(WINDOW_G ,S(6ca7e032,8bf5d67a,b804d431,e5f709e,bd3156e0,1d4511da,1dc67394,24d2e659,c428b133,f3683909,6551c2ff,2f870d80,d80aaaf4,6d2b1a69,7722057,5eca2647) ,S(60df19a9,83e9086c,4cbd20ba,fbafa8b2,346ae4ee,9c1ad4dd,99959e91,2df530d3,dae7b854,29f817a0,421c2f1e,e4e8e4d,a09d60f8,84701e31,d7a8b7b0,3b79cc48) #endif -#if ECMULT_TABLE_SIZE(WINDOW_G) > 512 +#if WINDOW_G > 11 ,S(6f18d50e,5ef5f2ad,bad80ea1,c59a3847,5c22ff05,6ba52c7,e1d26d6d,d3686bce,d1d7ea0f,a166efd0,facf5c83,bc3786e0,d3f3405d,5b4578f5,13a336ad,f0d7d7a9) ,S(b634dc9d,5ed51336,4ac9569a,8ddee9d9,fcdfe00e,65e59233,b3be8a07,2fd949e0,d72e46e2,c61b2575,f25a505c,bdc3456e,fdf18976,6562a1a7,e86d036,eb31db69) ,S(5a37fcf0,2dba24f3,e6646171,43dbc5ab,40d91983,69f5cf0e,4fc566fd,97445910,a960d1d8,13f8746c,662a9582,614c7847,33e6153d,2975cb59,c3342463,75c69d1e) @@ -9431,7 +9282,7 @@ static const secp256k1_ge_storage secp256k1_pre_g_128[ECMULT_TABLE_SIZE(WINDOW_G ,S(edc9e8b1,3c61a1e0,8f93be12,495084ed,b5a90eca,4f11dc03,cd217b1a,56285a2c,66c2fd21,7e8fac7,4fd43b1b,58c80661,b0e8df85,a2fddfda,5e9f99fe,621e1f3d) ,S(dd94295f,8388a498,2aaa4164,927b81ba,cae9d002,b3ddb6b6,97082c70,f1e2c66e,838c060c,d409c1ea,9bcd9e8c,b1fac83f,3b08c2b2,482d8f4b,fd3ebc18,8d6706b) #endif -#if ECMULT_TABLE_SIZE(WINDOW_G) > 1024 +#if WINDOW_G > 12 ,S(7e068e04,dfc0be48,e5c0d3b3,5bfc734e,96e96ddd,d0ac4876,92f74535,685ab7e,df2cd146,90d225c8,d04052e6,93f14bf,69351e08,79883646,2c88401e,4ec70d0a) ,S(73a9bfcf,d10aac9b,46e659f,76c439a0,b7c2a073,7dec217a,21f43f39,949a5052,73b91529,3ea7b052,682062c,8f86cbf,bf379df6,91a9cec2,4a1424a9,b3be10dd) ,S(efddff84,c8cc754,e6e34678,d85809bc,55cc224b,f69be05a,daa847ec,d408c55b,65dd8f41,8264aab2,efe6ed7e,c45b4ac,8aac218a,3b6713fc,1736dd6d,c2f188d5) @@ -10457,7 +10308,7 @@ static const secp256k1_ge_storage secp256k1_pre_g_128[ECMULT_TABLE_SIZE(WINDOW_G ,S(8a663e7,40cad237,12dad6f9,1bb5d266,30542933,853e02eb,665e198d,a5c6e53b,e206b399,51175d15,daccf711,63b0918c,786a8fe,2077812,c0c1eee6,bb5c1e99) ,S(2b0956cb,e0ca91ee,f44fac0a,76cc0b00,5f4a5ae8,27e63696,fe1aa8b4,a92941a1,a31e6fa8,dd454eaf,90ee1e11,62627e3,3f84b8fe,da29f423,ddf2a962,386cffd2) #endif -#if ECMULT_TABLE_SIZE(WINDOW_G) > 2048 +#if WINDOW_G > 13 ,S(e0144151,7a2ac970,eb6381df,7e11fb4c,6b009980,6a0d330,d5429126,e662c3bd,8238ce9b,a691c80c,21563934,18d18dbf,aeb3fd34,48863a1e,7f4ba360,408dfcee) ,S(be3213e,8b062f33,caf16c53,5a0b666,f344e0d7,1e28aeba,8b215a3,7ec86c37,552523ba,eaca38a4,cd795683,852a2643,550fb83d,d1db0adc,3f1b29c9,7d51cd1f) ,S(623393b6,bfbfbd64,fc7aa1db,9e58e274,1c18eb6d,b5eb30ab,c4fe167e,a9e8ff2b,7c0e4174,f0fd5bf2,a025e316,fa3b7b97,1339a197,b52b0b50,bad4dfe,34e42cd3) @@ -12507,7 +12358,7 @@ static const secp256k1_ge_storage secp256k1_pre_g_128[ECMULT_TABLE_SIZE(WINDOW_G ,S(4beaa213,3c6161d0,c7da6d7c,8eb09cba,2e8ca505,a790a7d3,9d0c16b,8053bb52,da4c083c,9cbdda2e,c6626d8f,e4b2ee13,703496c,c298bffa,db1acdca,99d4f6d5) ,S(a8d31ec6,53ce1834,a78de363,c5f9abd1,8594b34,4d34f5fc,8a10e81f,5804831,b4d6a9cd,7cbbe370,9edb5601,cb7c8ba4,8c462c18,594a4bce,64f4c286,dac5cff9) #endif -#if ECMULT_TABLE_SIZE(WINDOW_G) > 4096 +#if WINDOW_G > 14 ,S(a8f11586,f3df4945,a753c485,ee0fd4d,e410771d,bddc1b26,c9ff10e0,77b915b7,a4ad6f16,dbd741bc,71b2dfd2,dd2d340,3816bf73,4e73cc10,abcfa6bb,d0f161a4) ,S(13e697c3,812d4772,254082da,372892d4,e1a66e1a,eb16bbd4,f7a0d531,c979cb2,87fa7baa,f6def12d,31e42c14,f672c0d6,a9d0e2e1,ceee2546,d65bd01,c18df57f) ,S(3cae4590,821a9697,a5963269,b44f0222,98f60021,b6048b3f,49c6fd4d,8650c7a,e03f8745,60418449,ad97f28,41664745,349329d,268c1c43,86e25147,6e44b234) @@ -16606,6 +16457,4 @@ static const secp256k1_ge_storage secp256k1_pre_g_128[ECMULT_TABLE_SIZE(WINDOW_G ,S(1b9a142f,fc4d03ea,4b079f2d,b05fad98,8ddb2d32,b359967f,c173801f,63320825,59bda7ed,5b691c20,4fc8f8ac,f53be298,ae628954,a8134d0f,dd097e67,be9ff9b6) #endif }; -#endif #undef S -#endif diff --git a/src/secp256k1/src/precomputed_ecmult.h b/src/secp256k1/src/precomputed_ecmult.h new file mode 100644 index 0000000000..949b62c874 --- /dev/null +++ b/src/secp256k1/src/precomputed_ecmult.h @@ -0,0 +1,35 @@ +/***************************************************************************************************** + * Copyright (c) 2013, 2014, 2017, 2021 Pieter Wuille, Andrew Poelstra, Jonas Nick, Russell O'Connor * + * Distributed under the MIT software license, see the accompanying * + * file COPYING or https://www.opensource.org/licenses/mit-license.php. * + *****************************************************************************************************/ + +#ifndef SECP256K1_PRECOMPUTED_ECMULT_H +#define SECP256K1_PRECOMPUTED_ECMULT_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include "group.h" +#if defined(EXHAUSTIVE_TEST_ORDER) +#if EXHAUSTIVE_TEST_ORDER == 13 +# define WINDOW_G 4 +# elif EXHAUSTIVE_TEST_ORDER == 199 +# define WINDOW_G 8 +# else +# error No known generator for the specified exhaustive test group order. +# endif +static secp256k1_ge_storage secp256k1_pre_g[ECMULT_TABLE_SIZE(WINDOW_G)]; +static secp256k1_ge_storage secp256k1_pre_g_128[ECMULT_TABLE_SIZE(WINDOW_G)]; +#else /* !defined(EXHAUSTIVE_TEST_ORDER) */ +# define WINDOW_G ECMULT_WINDOW_SIZE +extern const secp256k1_ge_storage secp256k1_pre_g[ECMULT_TABLE_SIZE(WINDOW_G)]; +extern const secp256k1_ge_storage secp256k1_pre_g_128[ECMULT_TABLE_SIZE(WINDOW_G)]; +#endif /* defined(EXHAUSTIVE_TEST_ORDER) */ + +#ifdef __cplusplus +} +#endif + +#endif /* SECP256K1_PRECOMPUTED_ECMULT_H */ diff --git a/src/secp256k1/src/ecmult_gen_static_prec_table.h b/src/secp256k1/src/precomputed_ecmult_gen.c index bf4e5ea248..d67291fcf5 100644 --- a/src/secp256k1/src/ecmult_gen_static_prec_table.h +++ b/src/secp256k1/src/precomputed_ecmult_gen.c @@ -1,13 +1,17 @@ -/* This file was automatically generated by gen_ecmult_gen_static_prec_table. */ +/* This file was automatically generated by precompute_ecmult_gen. */ /* See ecmult_gen_impl.h for details about the contents of this file. */ -#ifndef SECP256K1_ECMULT_GEN_STATIC_PREC_TABLE_H -#define SECP256K1_ECMULT_GEN_STATIC_PREC_TABLE_H +#if defined HAVE_CONFIG_H +# include "libsecp256k1-config.h" +#endif +#include "../include/secp256k1.h" #include "group.h" -#define S(a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p) SECP256K1_GE_STORAGE_CONST(0x##a##u,0x##b##u,0x##c##u,0x##d##u,0x##e##u,0x##f##u,0x##g##u,0x##h##u,0x##i##u,0x##j##u,0x##k##u,0x##l##u,0x##m##u,0x##n##u,0x##o##u,0x##p##u) +#include "ecmult_gen.h" +#include "precomputed_ecmult_gen.h" #ifdef EXHAUSTIVE_TEST_ORDER -static secp256k1_ge_storage secp256k1_ecmult_gen_prec_table[ECMULT_GEN_PREC_N(ECMULT_GEN_PREC_BITS)][ECMULT_GEN_PREC_G(ECMULT_GEN_PREC_BITS)]; -#else -static const secp256k1_ge_storage secp256k1_ecmult_gen_prec_table[ECMULT_GEN_PREC_N(ECMULT_GEN_PREC_BITS)][ECMULT_GEN_PREC_G(ECMULT_GEN_PREC_BITS)] = { +# error Cannot compile precomputed_ecmult_gen.c in exhaustive test mode +#endif /* EXHAUSTIVE_TEST_ORDER */ +#define S(a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p) SECP256K1_GE_STORAGE_CONST(0x##a##u,0x##b##u,0x##c##u,0x##d##u,0x##e##u,0x##f##u,0x##g##u,0x##h##u,0x##i##u,0x##j##u,0x##k##u,0x##l##u,0x##m##u,0x##n##u,0x##o##u,0x##p##u) +const secp256k1_ge_storage secp256k1_ecmult_gen_prec_table[ECMULT_GEN_PREC_N(ECMULT_GEN_PREC_BITS)][ECMULT_GEN_PREC_G(ECMULT_GEN_PREC_BITS)] = { #if ECMULT_GEN_PREC_BITS == 2 {S(3a9ed373,6eed3eec,9aeb5ac0,21b54652,56817b1f,8de6cd0,fbcee548,ba044bb5,7bcc5928,bdc9c023,dfc663b8,9e4f6969,ab751798,8e600ec1,d242010c,45c7974a), S(e44d7675,c3cb2857,4e133c01,a74f4afc,5ce684f8,4a789711,603f7c4f,50abef58,25bcb62f,fe2e2ce2,196ad86c,a006e20,8c64d21b,b25320a3,b5574b9c,1e1bfb4b), @@ -9743,6 +9747,4 @@ S(244b87a4,fcecef37,76c16c5c,24c7785,be3b3c13,46595363,b8c066ec,45bfe561,9642f5f S(9de52b81,157165cc,aef44485,4c2b3535,a599a79,80d024de,5334b385,ecbb2e91,74fca165,26fe2f87,a41ce510,4dd5634,5cf98c11,803c0392,3eb4b8b7,60240c02)} #endif }; -#endif /* EXHAUSTIVE_TEST_ORDER */ -#undef SC -#endif /* SECP256K1_ECMULT_GEN_STATIC_PREC_TABLE_H */ +#undef S diff --git a/src/secp256k1/src/precomputed_ecmult_gen.h b/src/secp256k1/src/precomputed_ecmult_gen.h new file mode 100644 index 0000000000..7256ad2e30 --- /dev/null +++ b/src/secp256k1/src/precomputed_ecmult_gen.h @@ -0,0 +1,26 @@ +/********************************************************************************* + * Copyright (c) 2013, 2014, 2015, 2021 Thomas Daede, Cory Fields, Pieter Wuille * + * Distributed under the MIT software license, see the accompanying * + * file COPYING or https://www.opensource.org/licenses/mit-license.php. * + *********************************************************************************/ + +#ifndef SECP256K1_PRECOMPUTED_ECMULT_GEN_H +#define SECP256K1_PRECOMPUTED_ECMULT_GEN_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include "group.h" +#include "ecmult_gen.h" +#ifdef EXHAUSTIVE_TEST_ORDER +static secp256k1_ge_storage secp256k1_ecmult_gen_prec_table[ECMULT_GEN_PREC_N(ECMULT_GEN_PREC_BITS)][ECMULT_GEN_PREC_G(ECMULT_GEN_PREC_BITS)]; +#else +extern const secp256k1_ge_storage secp256k1_ecmult_gen_prec_table[ECMULT_GEN_PREC_N(ECMULT_GEN_PREC_BITS)][ECMULT_GEN_PREC_G(ECMULT_GEN_PREC_BITS)]; +#endif /* defined(EXHAUSTIVE_TEST_ORDER) */ + +#ifdef __cplusplus +} +#endif + +#endif /* SECP256K1_PRECOMPUTED_ECMULT_GEN_H */ diff --git a/src/secp256k1/src/secp256k1.c b/src/secp256k1/src/secp256k1.c index 36fde24c3d..8f34c35283 100644 --- a/src/secp256k1/src/secp256k1.c +++ b/src/secp256k1/src/secp256k1.c @@ -423,8 +423,12 @@ static int nonce_function_rfc6979(unsigned char *nonce32, const unsigned char *m unsigned int offset = 0; secp256k1_rfc6979_hmac_sha256 rng; unsigned int i; + secp256k1_scalar msg; + unsigned char msgmod32[32]; + secp256k1_scalar_set_b32(&msg, msg32, NULL); + secp256k1_scalar_get_b32(msgmod32, &msg); /* We feed a byte array to the PRNG as input, consisting of: - * - the private key (32 bytes) and message (32 bytes), see RFC 6979 3.2d. + * - the private key (32 bytes) and reduced message (32 bytes), see RFC 6979 3.2d. * - optionally 32 extra bytes of data, see RFC 6979 3.6 Additional Data. * - optionally 16 extra bytes with the algorithm name. * Because the arguments have distinct fixed lengths it is not possible for @@ -432,7 +436,7 @@ static int nonce_function_rfc6979(unsigned char *nonce32, const unsigned char *m * nonces. */ buffer_append(keydata, &offset, key32, 32); - buffer_append(keydata, &offset, msg32, 32); + buffer_append(keydata, &offset, msgmod32, 32); if (data != NULL) { buffer_append(keydata, &offset, data, 32); } diff --git a/src/secp256k1/src/testrand.h b/src/secp256k1/src/testrand.h index 667d1867bd..bd149bb1b4 100644 --- a/src/secp256k1/src/testrand.h +++ b/src/secp256k1/src/testrand.h @@ -17,11 +17,14 @@ SECP256K1_INLINE static void secp256k1_testrand_seed(const unsigned char *seed16); /** Generate a pseudorandom number in the range [0..2**32-1]. */ -static uint32_t secp256k1_testrand32(void); +SECP256K1_INLINE static uint32_t secp256k1_testrand32(void); + +/** Generate a pseudorandom number in the range [0..2**64-1]. */ +SECP256K1_INLINE static uint64_t secp256k1_testrand64(void); /** Generate a pseudorandom number in the range [0..2**bits-1]. Bits must be 1 or * more. */ -static uint32_t secp256k1_testrand_bits(int bits); +SECP256K1_INLINE static uint64_t secp256k1_testrand_bits(int bits); /** Generate a pseudorandom number in the range [0..range-1]. */ static uint32_t secp256k1_testrand_int(uint32_t range); diff --git a/src/secp256k1/src/testrand_impl.h b/src/secp256k1/src/testrand_impl.h index c8d30ef6a8..e9b9d7ded4 100644 --- a/src/secp256k1/src/testrand_impl.h +++ b/src/secp256k1/src/testrand_impl.h @@ -14,37 +14,64 @@ #include "testrand.h" #include "hash.h" -static secp256k1_rfc6979_hmac_sha256 secp256k1_test_rng; -static uint32_t secp256k1_test_rng_precomputed[8]; -static int secp256k1_test_rng_precomputed_used = 8; +static uint64_t secp256k1_test_state[4]; static uint64_t secp256k1_test_rng_integer; static int secp256k1_test_rng_integer_bits_left = 0; SECP256K1_INLINE static void secp256k1_testrand_seed(const unsigned char *seed16) { - secp256k1_rfc6979_hmac_sha256_initialize(&secp256k1_test_rng, seed16, 16); + static const unsigned char PREFIX[19] = "secp256k1 test init"; + unsigned char out32[32]; + secp256k1_sha256 hash; + int i; + + /* Use SHA256(PREFIX || seed16) as initial state. */ + secp256k1_sha256_initialize(&hash); + secp256k1_sha256_write(&hash, PREFIX, sizeof(PREFIX)); + secp256k1_sha256_write(&hash, seed16, 16); + secp256k1_sha256_finalize(&hash, out32); + for (i = 0; i < 4; ++i) { + uint64_t s = 0; + int j; + for (j = 0; j < 8; ++j) s = (s << 8) | out32[8*i + j]; + secp256k1_test_state[i] = s; + } + secp256k1_test_rng_integer_bits_left = 0; } -SECP256K1_INLINE static uint32_t secp256k1_testrand32(void) { - if (secp256k1_test_rng_precomputed_used == 8) { - secp256k1_rfc6979_hmac_sha256_generate(&secp256k1_test_rng, (unsigned char*)(&secp256k1_test_rng_precomputed[0]), sizeof(secp256k1_test_rng_precomputed)); - secp256k1_test_rng_precomputed_used = 0; - } - return secp256k1_test_rng_precomputed[secp256k1_test_rng_precomputed_used++]; +SECP256K1_INLINE static uint64_t rotl(const uint64_t x, int k) { + return (x << k) | (x >> (64 - k)); +} + +SECP256K1_INLINE static uint64_t secp256k1_testrand64(void) { + /* Test-only Xoshiro256++ RNG. See https://prng.di.unimi.it/ */ + const uint64_t result = rotl(secp256k1_test_state[0] + secp256k1_test_state[3], 23) + secp256k1_test_state[0]; + const uint64_t t = secp256k1_test_state[1] << 17; + secp256k1_test_state[2] ^= secp256k1_test_state[0]; + secp256k1_test_state[3] ^= secp256k1_test_state[1]; + secp256k1_test_state[1] ^= secp256k1_test_state[2]; + secp256k1_test_state[0] ^= secp256k1_test_state[3]; + secp256k1_test_state[2] ^= t; + secp256k1_test_state[3] = rotl(secp256k1_test_state[3], 45); + return result; } -static uint32_t secp256k1_testrand_bits(int bits) { - uint32_t ret; +SECP256K1_INLINE static uint64_t secp256k1_testrand_bits(int bits) { + uint64_t ret; if (secp256k1_test_rng_integer_bits_left < bits) { - secp256k1_test_rng_integer |= (((uint64_t)secp256k1_testrand32()) << secp256k1_test_rng_integer_bits_left); - secp256k1_test_rng_integer_bits_left += 32; + secp256k1_test_rng_integer = secp256k1_testrand64(); + secp256k1_test_rng_integer_bits_left = 64; } ret = secp256k1_test_rng_integer; secp256k1_test_rng_integer >>= bits; secp256k1_test_rng_integer_bits_left -= bits; - ret &= ((~((uint32_t)0)) >> (32 - bits)); + ret &= ((~((uint64_t)0)) >> (64 - bits)); return ret; } +SECP256K1_INLINE static uint32_t secp256k1_testrand32(void) { + return secp256k1_testrand_bits(32); +} + static uint32_t secp256k1_testrand_int(uint32_t range) { /* We want a uniform integer between 0 and range-1, inclusive. * B is the smallest number such that range <= 2**B. @@ -85,7 +112,19 @@ static uint32_t secp256k1_testrand_int(uint32_t range) { } static void secp256k1_testrand256(unsigned char *b32) { - secp256k1_rfc6979_hmac_sha256_generate(&secp256k1_test_rng, b32, 32); + int i; + for (i = 0; i < 4; ++i) { + uint64_t val = secp256k1_testrand64(); + b32[0] = val; + b32[1] = val >> 8; + b32[2] = val >> 16; + b32[3] = val >> 24; + b32[4] = val >> 32; + b32[5] = val >> 40; + b32[6] = val >> 48; + b32[7] = val >> 56; + b32 += 8; + } } static void secp256k1_testrand_bytes_test(unsigned char *bytes, size_t len) { @@ -109,7 +148,7 @@ static void secp256k1_testrand256_test(unsigned char *b32) { } static void secp256k1_testrand_flip(unsigned char *b, size_t len) { - b[secp256k1_testrand_int(len)] ^= (1 << secp256k1_testrand_int(8)); + b[secp256k1_testrand_int(len)] ^= (1 << secp256k1_testrand_bits(3)); } static void secp256k1_testrand_init(const char* hexseed) { diff --git a/src/secp256k1/src/tests.c b/src/secp256k1/src/tests.c index 712fc655fa..dd53173930 100644 --- a/src/secp256k1/src/tests.c +++ b/src/secp256k1/src/tests.c @@ -28,6 +28,8 @@ #include "modinv64_impl.h" #endif +#define CONDITIONAL_TEST(cnt, nam) if (count < (cnt)) { printf("Skipping %s (iteration count too low)\n", nam); } else + static int count = 64; static secp256k1_context *ctx = NULL; @@ -100,6 +102,12 @@ void random_group_element_jacobian_test(secp256k1_gej *gej, const secp256k1_ge * gej->infinity = ge->infinity; } +void random_gej_test(secp256k1_gej *gej) { + secp256k1_ge ge; + random_group_element_test(&ge); + random_group_element_jacobian_test(gej, &ge); +} + void random_scalar_order_test(secp256k1_scalar *num) { do { unsigned char b32[32]; @@ -443,14 +451,18 @@ void run_ctz_tests(void) { /***** HASH TESTS *****/ -void run_sha256_tests(void) { - static const char *inputs[8] = { +void run_sha256_known_output_tests(void) { + static const char *inputs[] = { "", "abc", "message digest", "secure hash algorithm", "SHA256 is considered to be safe", "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", "For this sample, this 63-byte string will be used as input data", - "This is exactly 64 bytes long, not counting the terminating byte" + "This is exactly 64 bytes long, not counting the terminating byte", + "aaaaa", }; - static const unsigned char outputs[8][32] = { + static const unsigned int repeat[] = { + 1, 1, 1, 1, 1, 1, 1, 1, 1000000/5 + }; + static const unsigned char outputs[][32] = { {0xe3, 0xb0, 0xc4, 0x42, 0x98, 0xfc, 0x1c, 0x14, 0x9a, 0xfb, 0xf4, 0xc8, 0x99, 0x6f, 0xb9, 0x24, 0x27, 0xae, 0x41, 0xe4, 0x64, 0x9b, 0x93, 0x4c, 0xa4, 0x95, 0x99, 0x1b, 0x78, 0x52, 0xb8, 0x55}, {0xba, 0x78, 0x16, 0xbf, 0x8f, 0x01, 0xcf, 0xea, 0x41, 0x41, 0x40, 0xde, 0x5d, 0xae, 0x22, 0x23, 0xb0, 0x03, 0x61, 0xa3, 0x96, 0x17, 0x7a, 0x9c, 0xb4, 0x10, 0xff, 0x61, 0xf2, 0x00, 0x15, 0xad}, {0xf7, 0x84, 0x6f, 0x55, 0xcf, 0x23, 0xe1, 0x4e, 0xeb, 0xea, 0xb5, 0xb4, 0xe1, 0x55, 0x0c, 0xad, 0x5b, 0x50, 0x9e, 0x33, 0x48, 0xfb, 0xc4, 0xef, 0xa3, 0xa1, 0x41, 0x3d, 0x39, 0x3c, 0xb6, 0x50}, @@ -458,27 +470,146 @@ void run_sha256_tests(void) { {0x68, 0x19, 0xd9, 0x15, 0xc7, 0x3f, 0x4d, 0x1e, 0x77, 0xe4, 0xe1, 0xb5, 0x2d, 0x1f, 0xa0, 0xf9, 0xcf, 0x9b, 0xea, 0xea, 0xd3, 0x93, 0x9f, 0x15, 0x87, 0x4b, 0xd9, 0x88, 0xe2, 0xa2, 0x36, 0x30}, {0x24, 0x8d, 0x6a, 0x61, 0xd2, 0x06, 0x38, 0xb8, 0xe5, 0xc0, 0x26, 0x93, 0x0c, 0x3e, 0x60, 0x39, 0xa3, 0x3c, 0xe4, 0x59, 0x64, 0xff, 0x21, 0x67, 0xf6, 0xec, 0xed, 0xd4, 0x19, 0xdb, 0x06, 0xc1}, {0xf0, 0x8a, 0x78, 0xcb, 0xba, 0xee, 0x08, 0x2b, 0x05, 0x2a, 0xe0, 0x70, 0x8f, 0x32, 0xfa, 0x1e, 0x50, 0xc5, 0xc4, 0x21, 0xaa, 0x77, 0x2b, 0xa5, 0xdb, 0xb4, 0x06, 0xa2, 0xea, 0x6b, 0xe3, 0x42}, - {0xab, 0x64, 0xef, 0xf7, 0xe8, 0x8e, 0x2e, 0x46, 0x16, 0x5e, 0x29, 0xf2, 0xbc, 0xe4, 0x18, 0x26, 0xbd, 0x4c, 0x7b, 0x35, 0x52, 0xf6, 0xb3, 0x82, 0xa9, 0xe7, 0xd3, 0xaf, 0x47, 0xc2, 0x45, 0xf8} + {0xab, 0x64, 0xef, 0xf7, 0xe8, 0x8e, 0x2e, 0x46, 0x16, 0x5e, 0x29, 0xf2, 0xbc, 0xe4, 0x18, 0x26, 0xbd, 0x4c, 0x7b, 0x35, 0x52, 0xf6, 0xb3, 0x82, 0xa9, 0xe7, 0xd3, 0xaf, 0x47, 0xc2, 0x45, 0xf8}, + {0xcd, 0xc7, 0x6e, 0x5c, 0x99, 0x14, 0xfb, 0x92, 0x81, 0xa1, 0xc7, 0xe2, 0x84, 0xd7, 0x3e, 0x67, 0xf1, 0x80, 0x9a, 0x48, 0xa4, 0x97, 0x20, 0x0e, 0x04, 0x6d, 0x39, 0xcc, 0xc7, 0x11, 0x2c, 0xd0}, }; - int i; - for (i = 0; i < 8; i++) { + unsigned int i, ninputs; + + /* Skip last input vector for low iteration counts */ + ninputs = sizeof(inputs)/sizeof(inputs[0]) - 1; + CONDITIONAL_TEST(16, "run_sha256_known_output_tests 1000000") ninputs++; + + for (i = 0; i < ninputs; i++) { unsigned char out[32]; secp256k1_sha256 hasher; + unsigned int j; + /* 1. Run: simply write the input bytestrings */ + j = repeat[i]; secp256k1_sha256_initialize(&hasher); - secp256k1_sha256_write(&hasher, (const unsigned char*)(inputs[i]), strlen(inputs[i])); + while (j > 0) { + secp256k1_sha256_write(&hasher, (const unsigned char*)(inputs[i]), strlen(inputs[i])); + j--; + } secp256k1_sha256_finalize(&hasher, out); CHECK(secp256k1_memcmp_var(out, outputs[i], 32) == 0); + /* 2. Run: split the input bytestrings randomly before writing */ if (strlen(inputs[i]) > 0) { int split = secp256k1_testrand_int(strlen(inputs[i])); secp256k1_sha256_initialize(&hasher); - secp256k1_sha256_write(&hasher, (const unsigned char*)(inputs[i]), split); - secp256k1_sha256_write(&hasher, (const unsigned char*)(inputs[i] + split), strlen(inputs[i]) - split); + j = repeat[i]; + while (j > 0) { + secp256k1_sha256_write(&hasher, (const unsigned char*)(inputs[i]), split); + secp256k1_sha256_write(&hasher, (const unsigned char*)(inputs[i] + split), strlen(inputs[i]) - split); + j--; + } secp256k1_sha256_finalize(&hasher, out); CHECK(secp256k1_memcmp_var(out, outputs[i], 32) == 0); } } } +/** SHA256 counter tests + +The tests verify that the SHA256 counter doesn't wrap around at message length +2^i bytes for i = 20, ..., 33. This wide range aims at being independent of the +implementation of the counter and it catches multiple natural 32-bit overflows +(e.g., counting bits, counting bytes, counting blocks, ...). + +The test vectors have been generated using following Python script which relies +on https://github.com/cloudtools/sha256/ (v0.3 on Python v3.10.2). + +``` +from sha256 import sha256 +from copy import copy + +def midstate_c_definition(hasher): + ret = ' {{0x' + hasher.state[0].hex('_', 4).replace('_', ', 0x') + '},\n' + ret += ' {0x00}, ' + str(hex(hasher.state[1])) + '}' + return ret + +def output_c_literal(hasher): + return '{0x' + hasher.digest().hex('_').replace('_', ', 0x') + '}' + +MESSAGE = b'abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmno' +assert(len(MESSAGE) == 64) +BYTE_BOUNDARIES = [(2**b)//len(MESSAGE) - 1 for b in range(20, 34)] + +midstates = [] +digests = [] +hasher = sha256() +for i in range(BYTE_BOUNDARIES[-1] + 1): + if i in BYTE_BOUNDARIES: + midstates.append(midstate_c_definition(hasher)) + hasher_copy = copy(hasher) + hasher_copy.update(MESSAGE) + digests.append(output_c_literal(hasher_copy)) + hasher.update(MESSAGE) + +for x in midstates: + print(x + ',') + +for x in digests: + print(x + ',') +``` +*/ +void run_sha256_counter_tests(void) { + static const char *input = "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmno"; + static const secp256k1_sha256 midstates[] = { + {{0xa2b5c8bb, 0x26c88bb3, 0x2abdc3d2, 0x9def99a3, 0xdfd21a6e, 0x41fe585b, 0x7ef2c440, 0x2b79adda}, + {0x00}, 0xfffc0}, + {{0xa0d29445, 0x9287de66, 0x76aabd71, 0x41acd765, 0x0c7528b4, 0x84e14906, 0x942faec6, 0xcc5a7b26}, + {0x00}, 0x1fffc0}, + {{0x50449526, 0xb9f1d657, 0xa0fc13e9, 0x50860f10, 0xa550c431, 0x3fbc97c1, 0x7bbb2d89, 0xdb67bac1}, + {0x00}, 0x3fffc0}, + {{0x54a6efdc, 0x46762e7b, 0x88bfe73f, 0xbbd149c7, 0x41620c43, 0x1168da7b, 0x2c5960f9, 0xeccffda6}, + {0x00}, 0x7fffc0}, + {{0x2515a8f5, 0x5faa2977, 0x3a850486, 0xac858cad, 0x7b7276ee, 0x235c0385, 0xc53a157c, 0x7cb3e69c}, + {0x00}, 0xffffc0}, + {{0x34f39828, 0x409fedb7, 0x4bbdd0fb, 0x3b643634, 0x7806bf2e, 0xe0d1b713, 0xca3f2e1e, 0xe38722c2}, + {0x00}, 0x1ffffc0}, + {{0x389ef5c5, 0x38c54167, 0x8f5d56ab, 0x582a75cc, 0x8217caef, 0xf10947dd, 0x6a1998a8, 0x048f0b8c}, + {0x00}, 0x3ffffc0}, + {{0xd6c3f394, 0x0bee43b9, 0x6783f497, 0x29fa9e21, 0x6ce491c1, 0xa81fe45e, 0x2fc3859a, 0x269012d0}, + {0x00}, 0x7ffffc0}, + {{0x6dd3c526, 0x44d88aa0, 0x806a1bae, 0xfbcc0d32, 0x9d6144f3, 0x9d2bd757, 0x9851a957, 0xb50430ad}, + {0x00}, 0xfffffc0}, + {{0x2add4021, 0xdfe8a9e6, 0xa56317c6, 0x7a15f5bb, 0x4a48aacd, 0x5d368414, 0x4f00e6f0, 0xd9355023}, + {0x00}, 0x1fffffc0}, + {{0xb66666b4, 0xdbeac32b, 0x0ea351ae, 0xcba9da46, 0x6278b874, 0x8c508e23, 0xe16ca776, 0x8465bac1}, + {0x00}, 0x3fffffc0}, + {{0xb6744789, 0x9cce87aa, 0xc4c478b7, 0xf38404d8, 0x2e38ba62, 0xa3f7019b, 0x50458fe7, 0x3047dbec}, + {0x00}, 0x7fffffc0}, + {{0x8b1297ba, 0xba261a80, 0x2ba1b0dd, 0xfbc67d6d, 0x61072c4e, 0x4b5a2a0f, 0x52872760, 0x2dfeb162}, + {0x00}, 0xffffffc0}, + {{0x24f33cf7, 0x41ad6583, 0x41c8ff5d, 0xca7ef35f, 0x50395756, 0x021b743e, 0xd7126cd7, 0xd037473a}, + {0x00}, 0x1ffffffc0}, + }; + static const unsigned char outputs[][32] = { + {0x0e, 0x83, 0xe2, 0xc9, 0x4f, 0xb2, 0xb8, 0x2b, 0x89, 0x06, 0x92, 0x78, 0x04, 0x03, 0x48, 0x5c, 0x48, 0x44, 0x67, 0x61, 0x77, 0xa4, 0xc7, 0x90, 0x9e, 0x92, 0x55, 0x10, 0x05, 0xfe, 0x39, 0x15}, + {0x1d, 0x1e, 0xd7, 0xb8, 0xa3, 0xa7, 0x8a, 0x79, 0xfd, 0xa0, 0x05, 0x08, 0x9c, 0xeb, 0xf0, 0xec, 0x67, 0x07, 0x9f, 0x8e, 0x3c, 0x0d, 0x8e, 0xf9, 0x75, 0x55, 0x13, 0xc1, 0xe8, 0x77, 0xf8, 0xbb}, + {0x66, 0x95, 0x6c, 0xc9, 0xe0, 0x39, 0x65, 0xb6, 0xb0, 0x05, 0xd1, 0xaf, 0xaf, 0xf3, 0x1d, 0xb9, 0xa4, 0xda, 0x6f, 0x20, 0xcd, 0x3a, 0xae, 0x64, 0xc2, 0xdb, 0xee, 0xf5, 0xb8, 0x8d, 0x57, 0x0e}, + {0x3c, 0xbb, 0x1c, 0x12, 0x5e, 0x17, 0xfd, 0x54, 0x90, 0x45, 0xa7, 0x7b, 0x61, 0x6c, 0x1d, 0xfe, 0xe6, 0xcc, 0x7f, 0xee, 0xcf, 0xef, 0x33, 0x35, 0x50, 0x62, 0x16, 0x70, 0x2f, 0x87, 0xc3, 0xc9}, + {0x53, 0x4d, 0xa8, 0xe7, 0x1e, 0x98, 0x73, 0x8d, 0xd9, 0xa3, 0x54, 0xa5, 0x0e, 0x59, 0x2c, 0x25, 0x43, 0x6f, 0xaa, 0xa2, 0xf5, 0x21, 0x06, 0x3e, 0xc9, 0x82, 0x06, 0x94, 0x98, 0x72, 0x9d, 0xa7}, + {0xef, 0x7e, 0xe9, 0x6b, 0xd3, 0xe5, 0xb7, 0x41, 0x4c, 0xc8, 0xd3, 0x07, 0x52, 0x9a, 0x5a, 0x8b, 0x4e, 0x1e, 0x75, 0xa4, 0x17, 0x78, 0xc8, 0x36, 0xcd, 0xf8, 0x2e, 0xd9, 0x57, 0xe3, 0xd7, 0x07}, + {0x87, 0x16, 0xfb, 0xf9, 0xa5, 0xf8, 0xc4, 0x56, 0x2b, 0x48, 0x52, 0x8e, 0x2d, 0x30, 0x85, 0xb6, 0x4c, 0x56, 0xb5, 0xd1, 0x16, 0x9c, 0xcf, 0x32, 0x95, 0xad, 0x03, 0xe8, 0x05, 0x58, 0x06, 0x76}, + {0x75, 0x03, 0x80, 0x28, 0xf2, 0xa7, 0x63, 0x22, 0x1a, 0x26, 0x9c, 0x68, 0xe0, 0x58, 0xfc, 0x73, 0xeb, 0x42, 0xf6, 0x86, 0x16, 0x24, 0x4b, 0xbc, 0x24, 0xf7, 0x02, 0xc8, 0x3d, 0x90, 0xe2, 0xb0}, + {0xdf, 0x49, 0x0f, 0x15, 0x7b, 0x7d, 0xbf, 0xe0, 0xd4, 0xcf, 0x47, 0xc0, 0x80, 0x93, 0x4a, 0x61, 0xaa, 0x03, 0x07, 0x66, 0xb3, 0x38, 0x5d, 0xc8, 0xc9, 0x07, 0x61, 0xfb, 0x97, 0x10, 0x2f, 0xd8}, + {0x77, 0x19, 0x40, 0x56, 0x41, 0xad, 0xbc, 0x59, 0xda, 0x1e, 0xc5, 0x37, 0x14, 0x63, 0x7b, 0xfb, 0x79, 0xe2, 0x7a, 0xb1, 0x55, 0x42, 0x99, 0x42, 0x56, 0xfe, 0x26, 0x9d, 0x0f, 0x7e, 0x80, 0xc6}, + {0x50, 0xe7, 0x2a, 0x0e, 0x26, 0x44, 0x2f, 0xe2, 0x55, 0x2d, 0xc3, 0x93, 0x8a, 0xc5, 0x86, 0x58, 0x22, 0x8c, 0x0c, 0xbf, 0xb1, 0xd2, 0xca, 0x87, 0x2a, 0xe4, 0x35, 0x26, 0x6f, 0xcd, 0x05, 0x5e}, + {0xe4, 0x80, 0x6f, 0xdb, 0x3d, 0x7d, 0xba, 0xde, 0x50, 0x3f, 0xea, 0x00, 0x3d, 0x46, 0x59, 0x64, 0xfd, 0x58, 0x1c, 0xa1, 0xb8, 0x7d, 0x5f, 0xac, 0x94, 0x37, 0x9e, 0xa0, 0xc0, 0x9c, 0x93, 0x8b}, + {0x2c, 0xf3, 0xa9, 0xf6, 0x15, 0x25, 0x80, 0x70, 0x76, 0x99, 0x7d, 0xf1, 0xc3, 0x2f, 0xa3, 0x31, 0xff, 0x92, 0x35, 0x2e, 0x8d, 0x04, 0x13, 0x33, 0xd8, 0x0d, 0xdb, 0x4a, 0xf6, 0x8c, 0x03, 0x34}, + {0xec, 0x12, 0x24, 0x9f, 0x35, 0xa4, 0x29, 0x8b, 0x9e, 0x4a, 0x95, 0xf8, 0x61, 0xaf, 0x61, 0xc5, 0x66, 0x55, 0x3e, 0x3f, 0x2a, 0x98, 0xea, 0x71, 0x16, 0x6b, 0x1c, 0xd9, 0xe4, 0x09, 0xd2, 0x8e}, + }; + unsigned int i; + for (i = 0; i < sizeof(midstates)/sizeof(midstates[0]); i++) { + unsigned char out[32]; + secp256k1_sha256 hasher = midstates[i]; + secp256k1_sha256_write(&hasher, (const unsigned char*)input, strlen(input)); + secp256k1_sha256_finalize(&hasher, out); + CHECK(secp256k1_memcmp_var(out, outputs[i], 32) == 0); + } +} + void run_hmac_sha256_tests(void) { static const char *keys[6] = { "\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b", @@ -790,7 +921,7 @@ void signed30_to_uint16(uint16_t* out, const secp256k1_modinv32_signed30* in) { void mutate_sign_signed30(secp256k1_modinv32_signed30* x) { int i; for (i = 0; i < 16; ++i) { - int pos = secp256k1_testrand_int(8); + int pos = secp256k1_testrand_bits(3); if (x->v[pos] > 0 && x->v[pos + 1] <= 0x3fffffff) { x->v[pos] -= 0x40000000; x->v[pos + 1] += 1; @@ -862,7 +993,7 @@ void mutate_sign_signed62(secp256k1_modinv64_signed62* x) { static const int64_t M62 = (int64_t)(UINT64_MAX >> 2); int i; for (i = 0; i < 8; ++i) { - int pos = secp256k1_testrand_int(4); + int pos = secp256k1_testrand_bits(2); if (x->v[pos] > 0 && x->v[pos + 1] <= M62) { x->v[pos] -= (M62 + 1); x->v[pos + 1] += 1; @@ -2451,13 +2582,65 @@ void run_field_convert(void) { CHECK(secp256k1_memcmp_var(&fes2, &fes, sizeof(fes)) == 0); } -int fe_secp256k1_memcmp_var(const secp256k1_fe *a, const secp256k1_fe *b) { - secp256k1_fe t = *b; +/* Returns true if two field elements have the same representation. */ +int fe_identical(const secp256k1_fe *a, const secp256k1_fe *b) { + int ret = 1; #ifdef VERIFY - t.magnitude = a->magnitude; - t.normalized = a->normalized; + ret &= (a->magnitude == b->magnitude); + ret &= (a->normalized == b->normalized); #endif - return secp256k1_memcmp_var(a, &t, sizeof(secp256k1_fe)); + /* Compare the struct member that holds the limbs. */ + ret &= (secp256k1_memcmp_var(a->n, b->n, sizeof(a->n)) == 0); + return ret; +} + +void run_field_half(void) { + secp256k1_fe t, u; + int m; + + /* Check magnitude 0 input */ + secp256k1_fe_get_bounds(&t, 0); + secp256k1_fe_half(&t); +#ifdef VERIFY + CHECK(t.magnitude == 1); + CHECK(t.normalized == 0); +#endif + CHECK(secp256k1_fe_normalizes_to_zero(&t)); + + /* Check non-zero magnitudes in the supported range */ + for (m = 1; m < 32; m++) { + /* Check max-value input */ + secp256k1_fe_get_bounds(&t, m); + + u = t; + secp256k1_fe_half(&u); +#ifdef VERIFY + CHECK(u.magnitude == (m >> 1) + 1); + CHECK(u.normalized == 0); +#endif + secp256k1_fe_normalize_weak(&u); + secp256k1_fe_add(&u, &u); + CHECK(check_fe_equal(&t, &u)); + + /* Check worst-case input: ensure the LSB is 1 so that P will be added, + * which will also cause all carries to be 1, since all limbs that can + * generate a carry are initially even and all limbs of P are odd in + * every existing field implementation. */ + secp256k1_fe_get_bounds(&t, m); + CHECK(t.n[0] > 0); + CHECK((t.n[0] & 1) == 0); + --t.n[0]; + + u = t; + secp256k1_fe_half(&u); +#ifdef VERIFY + CHECK(u.magnitude == (m >> 1) + 1); + CHECK(u.normalized == 0); +#endif + secp256k1_fe_normalize_weak(&u); + secp256k1_fe_add(&u, &u); + CHECK(check_fe_equal(&t, &u)); + } } void run_field_misc(void) { @@ -2467,9 +2650,13 @@ void run_field_misc(void) { secp256k1_fe q; secp256k1_fe fe5 = SECP256K1_FE_CONST(0, 0, 0, 0, 0, 0, 0, 5); int i, j; - for (i = 0; i < 5*count; i++) { + for (i = 0; i < 1000 * count; i++) { secp256k1_fe_storage xs, ys, zs; - random_fe(&x); + if (i & 1) { + random_fe(&x); + } else { + random_fe_test(&x); + } random_fe_non_zero(&y); /* Test the fe equality and comparison operations. */ CHECK(secp256k1_fe_cmp_var(&x, &x) == 0); @@ -2483,13 +2670,13 @@ void run_field_misc(void) { CHECK(x.normalized && x.magnitude == 1); #endif secp256k1_fe_cmov(&x, &x, 1); - CHECK(fe_secp256k1_memcmp_var(&x, &z) != 0); - CHECK(fe_secp256k1_memcmp_var(&x, &q) == 0); + CHECK(!fe_identical(&x, &z)); + CHECK(fe_identical(&x, &q)); secp256k1_fe_cmov(&q, &z, 1); #ifdef VERIFY CHECK(!q.normalized && q.magnitude == z.magnitude); #endif - CHECK(fe_secp256k1_memcmp_var(&q, &z) == 0); + CHECK(fe_identical(&q, &z)); secp256k1_fe_normalize_var(&x); secp256k1_fe_normalize_var(&z); CHECK(!secp256k1_fe_equal_var(&x, &z)); @@ -2537,6 +2724,14 @@ void run_field_misc(void) { secp256k1_fe_add(&q, &x); CHECK(check_fe_equal(&y, &z)); CHECK(check_fe_equal(&q, &y)); + /* Check secp256k1_fe_half. */ + z = x; + secp256k1_fe_half(&z); + secp256k1_fe_add(&z, &z); + CHECK(check_fe_equal(&x, &z)); + secp256k1_fe_add(&z, &z); + secp256k1_fe_half(&z); + CHECK(check_fe_equal(&x, &z)); } } @@ -3338,6 +3533,37 @@ void run_ge(void) { test_intialized_inf(); } +void test_gej_cmov(const secp256k1_gej *a, const secp256k1_gej *b) { + secp256k1_gej t = *a; + secp256k1_gej_cmov(&t, b, 0); + CHECK(gej_xyz_equals_gej(&t, a)); + secp256k1_gej_cmov(&t, b, 1); + CHECK(gej_xyz_equals_gej(&t, b)); +} + +void run_gej(void) { + int i; + secp256k1_gej a, b; + + /* Tests for secp256k1_gej_cmov */ + for (i = 0; i < count; i++) { + secp256k1_gej_set_infinity(&a); + secp256k1_gej_set_infinity(&b); + test_gej_cmov(&a, &b); + + random_gej_test(&a); + test_gej_cmov(&a, &b); + test_gej_cmov(&b, &a); + + b = a; + test_gej_cmov(&a, &b); + + random_gej_test(&b); + test_gej_cmov(&a, &b); + test_gej_cmov(&b, &a); + } +} + void test_ec_combine(void) { secp256k1_scalar sum = SECP256K1_SCALAR_CONST(0, 0, 0, 0, 0, 0, 0, 0); secp256k1_pubkey data[6]; @@ -4052,6 +4278,174 @@ void test_ecmult_multi(secp256k1_scratch *scratch, secp256k1_ecmult_multi_func e } } +int test_ecmult_multi_random(secp256k1_scratch *scratch) { + /* Large random test for ecmult_multi_* functions which exercises: + * - Few or many inputs (0 up to 128, roughly exponentially distributed). + * - Few or many 0*P or a*INF inputs (roughly uniformly distributed). + * - Including or excluding an nonzero a*G term (or such a term at all). + * - Final expected result equal to infinity or not (roughly 50%). + * - ecmult_multi_var, ecmult_strauss_single_batch, ecmult_pippenger_single_batch + */ + + /* These 4 variables define the eventual input to the ecmult_multi function. + * g_scalar is the G scalar fed to it (or NULL, possibly, if g_scalar=0), and + * scalars[0..filled-1] and gejs[0..filled-1] are the scalars and points + * which form its normal inputs. */ + int filled = 0; + secp256k1_scalar g_scalar = SECP256K1_SCALAR_CONST(0, 0, 0, 0, 0, 0, 0, 0); + secp256k1_scalar scalars[128]; + secp256k1_gej gejs[128]; + /* The expected result, and the computed result. */ + secp256k1_gej expected, computed; + /* Temporaries. */ + secp256k1_scalar sc_tmp; + secp256k1_ge ge_tmp; + /* Variables needed for the actual input to ecmult_multi. */ + secp256k1_ge ges[128]; + ecmult_multi_data data; + + int i; + /* Which multiplication function to use */ + int fn = secp256k1_testrand_int(3); + secp256k1_ecmult_multi_func ecmult_multi = fn == 0 ? secp256k1_ecmult_multi_var : + fn == 1 ? secp256k1_ecmult_strauss_batch_single : + secp256k1_ecmult_pippenger_batch_single; + /* Simulate exponentially distributed num. */ + int num_bits = 2 + secp256k1_testrand_int(6); + /* Number of (scalar, point) inputs (excluding g). */ + int num = secp256k1_testrand_int((1 << num_bits) + 1); + /* Number of those which are nonzero. */ + int num_nonzero = secp256k1_testrand_int(num + 1); + /* Whether we're aiming to create an input with nonzero expected result. */ + int nonzero_result = secp256k1_testrand_bits(1); + /* Whether we will provide nonzero g multiplicand. In some cases our hand + * is forced here based on num_nonzero and nonzero_result. */ + int g_nonzero = num_nonzero == 0 ? nonzero_result : + num_nonzero == 1 && !nonzero_result ? 1 : + (int)secp256k1_testrand_bits(1); + /* Which g_scalar pointer to pass into ecmult_multi(). */ + const secp256k1_scalar* g_scalar_ptr = (g_nonzero || secp256k1_testrand_bits(1)) ? &g_scalar : NULL; + /* How many EC multiplications were performed in this function. */ + int mults = 0; + /* How many randomization steps to apply to the input list. */ + int rands = (int)secp256k1_testrand_bits(3); + if (rands > num_nonzero) rands = num_nonzero; + + secp256k1_gej_set_infinity(&expected); + secp256k1_gej_set_infinity(&gejs[0]); + secp256k1_scalar_set_int(&scalars[0], 0); + + if (g_nonzero) { + /* If g_nonzero, set g_scalar to nonzero value r. */ + random_scalar_order_test(&g_scalar); + if (!nonzero_result) { + /* If expected=0 is desired, add a (a*r, -(1/a)*g) term to compensate. */ + CHECK(num_nonzero > filled); + random_scalar_order_test(&sc_tmp); + secp256k1_scalar_mul(&scalars[filled], &sc_tmp, &g_scalar); + secp256k1_scalar_inverse_var(&sc_tmp, &sc_tmp); + secp256k1_scalar_negate(&sc_tmp, &sc_tmp); + secp256k1_ecmult_gen(&ctx->ecmult_gen_ctx, &gejs[filled], &sc_tmp); + ++filled; + ++mults; + } + } + + if (nonzero_result && filled < num_nonzero) { + /* If a nonzero result is desired, and there is space, add a random nonzero term. */ + random_scalar_order_test(&scalars[filled]); + random_group_element_test(&ge_tmp); + secp256k1_gej_set_ge(&gejs[filled], &ge_tmp); + ++filled; + } + + if (nonzero_result) { + /* Compute the expected result using normal ecmult. */ + CHECK(filled <= 1); + secp256k1_ecmult(&expected, &gejs[0], &scalars[0], &g_scalar); + mults += filled + g_nonzero; + } + + /* At this point we have expected = scalar_g*G + sum(scalars[i]*gejs[i] for i=0..filled-1). */ + CHECK(filled <= 1 + !nonzero_result); + CHECK(filled <= num_nonzero); + + /* Add entries to scalars,gejs so that there are num of them. All the added entries + * either have scalar=0 or point=infinity, so these do not change the expected result. */ + while (filled < num) { + if (secp256k1_testrand_bits(1)) { + secp256k1_gej_set_infinity(&gejs[filled]); + random_scalar_order_test(&scalars[filled]); + } else { + secp256k1_scalar_set_int(&scalars[filled], 0); + random_group_element_test(&ge_tmp); + secp256k1_gej_set_ge(&gejs[filled], &ge_tmp); + } + ++filled; + } + + /* Now perform cheapish transformations on gejs and scalars, for indices + * 0..num_nonzero-1, which do not change the expected result, but may + * convert some of them to be both non-0-scalar and non-infinity-point. */ + for (i = 0; i < rands; ++i) { + int j; + secp256k1_scalar v, iv; + /* Shuffle the entries. */ + for (j = 0; j < num_nonzero; ++j) { + int k = secp256k1_testrand_int(num_nonzero - j); + if (k != 0) { + secp256k1_gej gej = gejs[j]; + secp256k1_scalar sc = scalars[j]; + gejs[j] = gejs[j + k]; + scalars[j] = scalars[j + k]; + gejs[j + k] = gej; + scalars[j + k] = sc; + } + } + /* Perturb all consecutive pairs of inputs: + * a*P + b*Q -> (a+b)*P + b*(Q-P). */ + for (j = 0; j + 1 < num_nonzero; j += 2) { + secp256k1_gej gej; + secp256k1_scalar_add(&scalars[j], &scalars[j], &scalars[j+1]); + secp256k1_gej_neg(&gej, &gejs[j]); + secp256k1_gej_add_var(&gejs[j+1], &gejs[j+1], &gej, NULL); + } + /* Transform the last input: a*P -> (v*a) * ((1/v)*P). */ + CHECK(num_nonzero >= 1); + random_scalar_order_test(&v); + secp256k1_scalar_inverse(&iv, &v); + secp256k1_scalar_mul(&scalars[num_nonzero - 1], &scalars[num_nonzero - 1], &v); + secp256k1_ecmult(&gejs[num_nonzero - 1], &gejs[num_nonzero - 1], &iv, NULL); + ++mults; + } + + /* Shuffle all entries (0..num-1). */ + for (i = 0; i < num; ++i) { + int j = secp256k1_testrand_int(num - i); + if (j != 0) { + secp256k1_gej gej = gejs[i]; + secp256k1_scalar sc = scalars[i]; + gejs[i] = gejs[i + j]; + scalars[i] = scalars[i + j]; + gejs[i + j] = gej; + scalars[i + j] = sc; + } + } + + /* Compute affine versions of all inputs. */ + secp256k1_ge_set_all_gej_var(ges, gejs, filled); + /* Invoke ecmult_multi code. */ + data.sc = scalars; + data.pt = ges; + CHECK(ecmult_multi(&ctx->error_callback, scratch, &computed, g_scalar_ptr, ecmult_multi_callback, &data, filled)); + mults += num_nonzero + g_nonzero; + /* Compare with expected result. */ + secp256k1_gej_neg(&computed, &computed); + secp256k1_gej_add_var(&computed, &computed, &expected, NULL); + CHECK(secp256k1_gej_is_infinity(&computed)); + return mults; +} + void test_ecmult_multi_batch_single(secp256k1_ecmult_multi_func ecmult_multi) { secp256k1_scalar szero; secp256k1_scalar sc; @@ -4093,7 +4487,7 @@ void test_secp256k1_pippenger_bucket_window_inv(void) { * for a given scratch space. */ void test_ecmult_multi_pippenger_max_points(void) { - size_t scratch_size = secp256k1_testrand_int(256); + size_t scratch_size = secp256k1_testrand_bits(8); size_t max_size = secp256k1_pippenger_scratch_size(secp256k1_pippenger_bucket_window_inv(PIPPENGER_MAX_BUCKET_WINDOW-1)+512, 12); secp256k1_scratch *scratch; size_t n_points_supported; @@ -4242,6 +4636,7 @@ void test_ecmult_multi_batching(void) { void run_ecmult_multi_tests(void) { secp256k1_scratch *scratch; + int64_t todo = (int64_t)320 * count; test_secp256k1_pippenger_bucket_window_inv(); test_ecmult_multi_pippenger_max_points(); @@ -4252,6 +4647,9 @@ void run_ecmult_multi_tests(void) { test_ecmult_multi_batch_single(secp256k1_ecmult_pippenger_batch_single); test_ecmult_multi(scratch, secp256k1_ecmult_strauss_batch_single); test_ecmult_multi_batch_single(secp256k1_ecmult_strauss_batch_single); + while (todo > 0) { + todo -= test_ecmult_multi_random(scratch); + } secp256k1_scratch_destroy(&ctx->error_callback, scratch); /* Run test_ecmult_multi with space for exactly one point */ @@ -4347,7 +4745,7 @@ void test_constant_wnaf(const secp256k1_scalar *number, int w) { secp256k1_scalar_add(&x, &x, &t); } /* Skew num because when encoding numbers as odd we use an offset */ - secp256k1_scalar_set_int(&scalar_skew, 1 << (skew == 2)); + secp256k1_scalar_set_int(&scalar_skew, skew); secp256k1_scalar_add(&num, &num, &scalar_skew); CHECK(secp256k1_scalar_eq(&x, &num)); } @@ -4540,8 +4938,8 @@ void test_ecmult_accumulate(secp256k1_sha256* acc, const secp256k1_scalar* x, se } } -void test_ecmult_constants(void) { - /* Test ecmult_gen for: +void test_ecmult_constants_2bit(void) { + /* Using test_ecmult_accumulate, test ecmult for: * - For i in 0..36: * - Key i * - Key -i @@ -4584,8 +4982,81 @@ void test_ecmult_constants(void) { secp256k1_scratch_space_destroy(ctx, scratch); } +void test_ecmult_constants_sha(uint32_t prefix, size_t iter, const unsigned char* expected32) { + /* Using test_ecmult_accumulate, test ecmult for: + * - Key 0 + * - Key 1 + * - Key -1 + * - For i in range(iter): + * - Key SHA256(LE32(prefix) || LE16(i)) + */ + secp256k1_scalar x; + secp256k1_sha256 acc; + unsigned char b32[32]; + unsigned char inp[6]; + size_t i; + secp256k1_scratch_space *scratch = secp256k1_scratch_space_create(ctx, 65536); + + inp[0] = prefix & 0xFF; + inp[1] = (prefix >> 8) & 0xFF; + inp[2] = (prefix >> 16) & 0xFF; + inp[3] = (prefix >> 24) & 0xFF; + secp256k1_sha256_initialize(&acc); + secp256k1_scalar_set_int(&x, 0); + test_ecmult_accumulate(&acc, &x, scratch); + secp256k1_scalar_set_int(&x, 1); + test_ecmult_accumulate(&acc, &x, scratch); + secp256k1_scalar_negate(&x, &x); + test_ecmult_accumulate(&acc, &x, scratch); + + for (i = 0; i < iter; ++i) { + secp256k1_sha256 gen; + inp[4] = i & 0xff; + inp[5] = (i >> 8) & 0xff; + secp256k1_sha256_initialize(&gen); + secp256k1_sha256_write(&gen, inp, sizeof(inp)); + secp256k1_sha256_finalize(&gen, b32); + secp256k1_scalar_set_b32(&x, b32, NULL); + test_ecmult_accumulate(&acc, &x, scratch); + } + secp256k1_sha256_finalize(&acc, b32); + CHECK(secp256k1_memcmp_var(b32, expected32, 32) == 0); + + secp256k1_scratch_space_destroy(ctx, scratch); +} + void run_ecmult_constants(void) { - test_ecmult_constants(); + /* Expected hashes of all points in the tests below. Computed using an + * independent implementation. */ + static const unsigned char expected32_6bit20[32] = { + 0x68, 0xb6, 0xed, 0x6f, 0x28, 0xca, 0xc9, 0x7f, + 0x8e, 0x8b, 0xd6, 0xc0, 0x61, 0x79, 0x34, 0x6e, + 0x5a, 0x8f, 0x2b, 0xbc, 0x3e, 0x1f, 0xc5, 0x2e, + 0x2a, 0xd0, 0x45, 0x67, 0x7f, 0x95, 0x95, 0x8e + }; + static const unsigned char expected32_8bit8[32] = { + 0x8b, 0x65, 0x8e, 0xea, 0x86, 0xae, 0x3c, 0x95, + 0x90, 0xb6, 0x77, 0xa4, 0x8c, 0x76, 0xd9, 0xec, + 0xf5, 0xab, 0x8a, 0x2f, 0xfd, 0xdb, 0x19, 0x12, + 0x1a, 0xee, 0xe6, 0xb7, 0x6e, 0x05, 0x3f, 0xc6 + }; + /* For every combination of 6 bit positions out of 256, restricted to + * 20-bit windows (i.e., the first and last bit position are no more than + * 19 bits apart), all 64 bit patterns occur in the input scalars used in + * this test. */ + CONDITIONAL_TEST(1, "test_ecmult_constants_sha 1024") { + test_ecmult_constants_sha(4808378u, 1024, expected32_6bit20); + } + + /* For every combination of 8 consecutive bit positions, all 256 bit + * patterns occur in the input scalars used in this test. */ + CONDITIONAL_TEST(3, "test_ecmult_constants_sha 2048") { + test_ecmult_constants_sha(1607366309u, 2048, expected32_8bit8); + } + + CONDITIONAL_TEST(35, "test_ecmult_constants_2bit") { + test_ecmult_constants_2bit(); + } } void test_ecmult_gen_blind(void) { @@ -5851,14 +6322,14 @@ static void random_ber_signature(unsigned char *sig, size_t *len, int* certainly /* We generate two classes of numbers: nlow==1 "low" ones (up to 32 bytes), nlow==0 "high" ones (32 bytes with 129 top bits set, or larger than 32 bytes) */ nlow[n] = der ? 1 : (secp256k1_testrand_bits(3) != 0); /* The length of the number in bytes (the first byte of which will always be nonzero) */ - nlen[n] = nlow[n] ? secp256k1_testrand_int(33) : 32 + secp256k1_testrand_int(200) * secp256k1_testrand_int(8) / 8; + nlen[n] = nlow[n] ? secp256k1_testrand_int(33) : 32 + secp256k1_testrand_int(200) * secp256k1_testrand_bits(3) / 8; CHECK(nlen[n] <= 232); /* The top bit of the number. */ nhbit[n] = (nlow[n] == 0 && nlen[n] == 32) ? 1 : (nlen[n] == 0 ? 0 : secp256k1_testrand_bits(1)); /* The top byte of the number (after the potential hardcoded 16 0xFF characters for "high" 32 bytes numbers) */ nhbyte[n] = nlen[n] == 0 ? 0 : (nhbit[n] ? 128 + secp256k1_testrand_bits(7) : 1 + secp256k1_testrand_int(127)); /* The number of zero bytes in front of the number (which is 0 or 1 in case of DER, otherwise we extend up to 300 bytes) */ - nzlen[n] = der ? ((nlen[n] == 0 || nhbit[n]) ? 1 : 0) : (nlow[n] ? secp256k1_testrand_int(3) : secp256k1_testrand_int(300 - nlen[n]) * secp256k1_testrand_int(8) / 8); + nzlen[n] = der ? ((nlen[n] == 0 || nhbit[n]) ? 1 : 0) : (nlow[n] ? secp256k1_testrand_int(3) : secp256k1_testrand_int(300 - nlen[n]) * secp256k1_testrand_bits(3) / 8); if (nzlen[n] > ((nlen[n] == 0 || nhbit[n]) ? 1 : 0)) { *certainly_not_der = 1; } @@ -5867,7 +6338,7 @@ static void random_ber_signature(unsigned char *sig, size_t *len, int* certainly nlenlen[n] = nlen[n] + nzlen[n] < 128 ? 0 : (nlen[n] + nzlen[n] < 256 ? 1 : 2); if (!der) { /* nlenlen[n] max 127 bytes */ - int add = secp256k1_testrand_int(127 - nlenlen[n]) * secp256k1_testrand_int(16) * secp256k1_testrand_int(16) / 256; + int add = secp256k1_testrand_int(127 - nlenlen[n]) * secp256k1_testrand_bits(4) * secp256k1_testrand_bits(4) / 256; nlenlen[n] += add; if (add != 0) { *certainly_not_der = 1; @@ -5881,7 +6352,7 @@ static void random_ber_signature(unsigned char *sig, size_t *len, int* certainly CHECK(tlen <= 856); /* The length of the garbage inside the tuple. */ - elen = (der || indet) ? 0 : secp256k1_testrand_int(980 - tlen) * secp256k1_testrand_int(8) / 8; + elen = (der || indet) ? 0 : secp256k1_testrand_int(980 - tlen) * secp256k1_testrand_bits(3) / 8; if (elen != 0) { *certainly_not_der = 1; } @@ -5889,7 +6360,7 @@ static void random_ber_signature(unsigned char *sig, size_t *len, int* certainly CHECK(tlen <= 980); /* The length of the garbage after the end of the tuple. */ - glen = der ? 0 : secp256k1_testrand_int(990 - tlen) * secp256k1_testrand_int(8) / 8; + glen = der ? 0 : secp256k1_testrand_int(990 - tlen) * secp256k1_testrand_bits(3) / 8; if (glen != 0) { *certainly_not_der = 1; } @@ -5904,7 +6375,7 @@ static void random_ber_signature(unsigned char *sig, size_t *len, int* certainly } else { int tlenlen = tlen < 128 ? 0 : (tlen < 256 ? 1 : 2); if (!der) { - int add = secp256k1_testrand_int(127 - tlenlen) * secp256k1_testrand_int(16) * secp256k1_testrand_int(16) / 256; + int add = secp256k1_testrand_int(127 - tlenlen) * secp256k1_testrand_bits(4) * secp256k1_testrand_bits(4) / 256; tlenlen += add; if (add != 0) { *certainly_not_der = 1; @@ -6416,6 +6887,19 @@ void run_secp256k1_memczero_test(void) { CHECK(secp256k1_memcmp_var(buf1, buf2, sizeof(buf1)) == 0); } +void run_secp256k1_byteorder_tests(void) { + const uint32_t x = 0xFF03AB45; + const unsigned char x_be[4] = {0xFF, 0x03, 0xAB, 0x45}; + unsigned char buf[4]; + uint32_t x_; + + secp256k1_write_be32(buf, x); + CHECK(secp256k1_memcmp_var(buf, x_be, sizeof(buf)) == 0); + + x_ = secp256k1_read_be32(buf); + CHECK(x == x_); +} + void int_cmov_test(void) { int r = INT_MAX; int a = 0; @@ -6616,7 +7100,8 @@ int main(int argc, char **argv) { run_modinv_tests(); run_inverse_tests(); - run_sha256_tests(); + run_sha256_known_output_tests(); + run_sha256_counter_tests(); run_hmac_sha256_tests(); run_rfc6979_hmac_sha256_tests(); run_tagged_sha256_tests(); @@ -6625,6 +7110,7 @@ int main(int argc, char **argv) { run_scalar_tests(); /* field tests */ + run_field_half(); run_field_misc(); run_field_convert(); run_fe_mul(); @@ -6633,6 +7119,7 @@ int main(int argc, char **argv) { /* group tests */ run_ge(); + run_gej(); run_group_decompress(); /* ecmult tests */ @@ -6687,6 +7174,7 @@ int main(int argc, char **argv) { /* util tests */ run_secp256k1_memczero_test(); + run_secp256k1_byteorder_tests(); run_cmov_tests(); diff --git a/src/secp256k1/src/tests_exhaustive.c b/src/secp256k1/src/tests_exhaustive.c index 6bae7a4778..6a4e2340f2 100644 --- a/src/secp256k1/src/tests_exhaustive.c +++ b/src/secp256k1/src/tests_exhaustive.c @@ -22,7 +22,8 @@ #include "assumptions.h" #include "group.h" #include "testrand_impl.h" -#include "ecmult_gen_prec_impl.h" +#include "ecmult_compute_table_impl.h" +#include "ecmult_gen_compute_table_impl.h" static int count = 2; @@ -389,8 +390,9 @@ int main(int argc, char** argv) { printf("running tests for core %lu (out of [0..%lu])\n", (unsigned long)this_core, (unsigned long)num_cores - 1); } - /* Recreate the ecmult_gen table using the right generator (as selected via EXHAUSTIVE_TEST_ORDER) */ - secp256k1_ecmult_gen_create_prec_table(&secp256k1_ecmult_gen_prec_table[0][0], &secp256k1_ge_const_g, ECMULT_GEN_PREC_BITS); + /* Recreate the ecmult{,_gen} tables using the right generator (as selected via EXHAUSTIVE_TEST_ORDER) */ + secp256k1_ecmult_gen_compute_table(&secp256k1_ecmult_gen_prec_table[0][0], &secp256k1_ge_const_g, ECMULT_GEN_PREC_BITS); + secp256k1_ecmult_compute_two_tables(secp256k1_pre_g, secp256k1_pre_g_128, WINDOW_G, &secp256k1_ge_const_g); while (count--) { /* Build context */ diff --git a/src/secp256k1/src/util.h b/src/secp256k1/src/util.h index 04227a7c9b..dac86bd77f 100644 --- a/src/secp256k1/src/util.h +++ b/src/secp256k1/src/util.h @@ -173,31 +173,6 @@ static SECP256K1_INLINE void *checked_realloc(const secp256k1_callback* cb, void # define SECP256K1_GNUC_EXT #endif -/* If SECP256K1_{LITTLE,BIG}_ENDIAN is not explicitly provided, infer from various other system macros. */ -#if !defined(SECP256K1_LITTLE_ENDIAN) && !defined(SECP256K1_BIG_ENDIAN) -/* Inspired by https://github.com/rofl0r/endianness.h/blob/9853923246b065a3b52d2c43835f3819a62c7199/endianness.h#L52L73 */ -# if (defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) || \ - defined(_X86_) || defined(__x86_64__) || defined(__i386__) || \ - defined(__i486__) || defined(__i586__) || defined(__i686__) || \ - defined(__MIPSEL) || defined(_MIPSEL) || defined(MIPSEL) || \ - defined(__ARMEL__) || defined(__AARCH64EL__) || \ - (defined(__LITTLE_ENDIAN__) && __LITTLE_ENDIAN__ == 1) || \ - (defined(_LITTLE_ENDIAN) && _LITTLE_ENDIAN == 1) || \ - defined(_M_IX86) || defined(_M_AMD64) || defined(_M_ARM) /* MSVC */ -# define SECP256K1_LITTLE_ENDIAN -# endif -# if (defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) || \ - defined(__MIPSEB) || defined(_MIPSEB) || defined(MIPSEB) || \ - defined(__MICROBLAZEEB__) || defined(__ARMEB__) || defined(__AARCH64EB__) || \ - (defined(__BIG_ENDIAN__) && __BIG_ENDIAN__ == 1) || \ - (defined(_BIG_ENDIAN) && _BIG_ENDIAN == 1) -# define SECP256K1_BIG_ENDIAN -# endif -#endif -#if defined(SECP256K1_LITTLE_ENDIAN) == defined(SECP256K1_BIG_ENDIAN) -# error Please make sure that either SECP256K1_LITTLE_ENDIAN or SECP256K1_BIG_ENDIAN is set, see src/util.h. -#endif - /* Zero memory if flag == 1. Flag must be 0 or 1. Constant time. */ static SECP256K1_INLINE void secp256k1_memczero(void *s, size_t len, int flag) { unsigned char *p = (unsigned char *)s; @@ -338,4 +313,20 @@ static SECP256K1_INLINE int secp256k1_ctz64_var(uint64_t x) { #endif } +/* Read a uint32_t in big endian */ +SECP256K1_INLINE static uint32_t secp256k1_read_be32(const unsigned char* p) { + return (uint32_t)p[0] << 24 | + (uint32_t)p[1] << 16 | + (uint32_t)p[2] << 8 | + (uint32_t)p[3]; +} + +/* Write a uint32_t in big endian */ +SECP256K1_INLINE static void secp256k1_write_be32(unsigned char* p, uint32_t x) { + p[3] = x; + p[2] = x >> 8; + p[1] = x >> 16; + p[0] = x >> 24; +} + #endif /* SECP256K1_UTIL_H */ diff --git a/src/secp256k1/src/valgrind_ctime_test.c b/src/secp256k1/src/valgrind_ctime_test.c index ea6d4b3deb..6ff0085d34 100644 --- a/src/secp256k1/src/valgrind_ctime_test.c +++ b/src/secp256k1/src/valgrind_ctime_test.c @@ -166,7 +166,7 @@ void run_tests(secp256k1_context *ctx, unsigned char *key) { ret = secp256k1_keypair_create(ctx, &keypair, key); VALGRIND_MAKE_MEM_DEFINED(&ret, sizeof(ret)); CHECK(ret == 1); - ret = secp256k1_schnorrsig_sign(ctx, sig, msg, &keypair, NULL); + ret = secp256k1_schnorrsig_sign32(ctx, sig, msg, &keypair, NULL); VALGRIND_MAKE_MEM_DEFINED(&ret, sizeof(ret)); CHECK(ret == 1); #endif diff --git a/src/serialize.h b/src/serialize.h index 44bb471f25..89a9f32240 100644 --- a/src/serialize.h +++ b/src/serialize.h @@ -472,10 +472,10 @@ struct CustomUintFormatter if (v < 0 || v > MAX) throw std::ios_base::failure("CustomUintFormatter value out of range"); if (BigEndian) { uint64_t raw = htobe64(v); - s.write({BytePtr(&raw) + 8 - Bytes, Bytes}); + s.write({AsBytePtr(&raw) + 8 - Bytes, Bytes}); } else { uint64_t raw = htole64(v); - s.write({BytePtr(&raw), Bytes}); + s.write({AsBytePtr(&raw), Bytes}); } } @@ -485,10 +485,10 @@ struct CustomUintFormatter static_assert(std::numeric_limits<U>::max() >= MAX && std::numeric_limits<U>::min() <= 0, "Assigned type too small"); uint64_t raw = 0; if (BigEndian) { - s.read({BytePtr(&raw) + 8 - Bytes, Bytes}); + s.read({AsBytePtr(&raw) + 8 - Bytes, Bytes}); v = static_cast<I>(be64toh(raw)); } else { - s.read({BytePtr(&raw), Bytes}); + s.read({AsBytePtr(&raw), Bytes}); v = static_cast<I>(le64toh(raw)); } } @@ -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 93f04e9021..1dbc55aeb5 100644 --- a/src/shutdown.cpp +++ b/src/shutdown.cpp @@ -5,13 +5,15 @@ #include <shutdown.h> +#if defined(HAVE_CONFIG_H) +#include <config/bitcoin-config.h> +#endif + #include <logging.h> -#include <node/ui_interface.h> +#include <node/interface_ui.h> #include <util/tokenpipe.h> #include <warnings.h> -#include <config/bitcoin-config.h> - #include <assert.h> #include <atomic> #ifdef WIN32 diff --git a/src/span.h b/src/span.h index b627b993c2..444d63001f 100644 --- a/src/span.h +++ b/src/span.h @@ -245,19 +245,19 @@ T& SpanPopBack(Span<T>& span) //! Convert a data pointer to a std::byte data pointer. //! Where possible, please use the safer AsBytes helpers. -inline const std::byte* BytePtr(const void* data) { return reinterpret_cast<const std::byte*>(data); } -inline std::byte* BytePtr(void* data) { return reinterpret_cast<std::byte*>(data); } +inline const std::byte* AsBytePtr(const void* data) { return reinterpret_cast<const std::byte*>(data); } +inline std::byte* AsBytePtr(void* data) { return reinterpret_cast<std::byte*>(data); } // From C++20 as_bytes and as_writeable_bytes template <typename T> Span<const std::byte> AsBytes(Span<T> s) noexcept { - return {BytePtr(s.data()), s.size_bytes()}; + return {AsBytePtr(s.data()), s.size_bytes()}; } template <typename T> Span<std::byte> AsWritableBytes(Span<T> s) noexcept { - return {BytePtr(s.data()), s.size_bytes()}; + return {AsBytePtr(s.data()), s.size_bytes()}; } template <typename V> diff --git a/src/streams.h b/src/streams.h index 96b7696f72..f14d347380 100644 --- a/src/streams.h +++ b/src/streams.h @@ -465,35 +465,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 +497,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 +515,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/allocators/secure.h b/src/support/allocators/secure.h index a9aa928082..c6bd685189 100644 --- a/src/support/allocators/secure.h +++ b/src/support/allocators/secure.h @@ -37,7 +37,7 @@ struct secure_allocator : public std::allocator<T> { typedef secure_allocator<_Other> other; }; - T* allocate(std::size_t n, const void* hint = 0) + T* allocate(std::size_t n, const void* hint = nullptr) { T* allocation = static_cast<T*>(LockedPoolManager::Instance().alloc(sizeof(T) * n)); if (!allocation) { diff --git a/src/support/lockedpool.cpp b/src/support/lockedpool.cpp index 6965f40253..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 @@ -235,12 +235,6 @@ PosixLockedPageAllocator::PosixLockedPageAllocator() #endif } -// Some systems (at least OS X) do not define MAP_ANONYMOUS yet and define -// MAP_ANON which is deprecated -#ifndef MAP_ANONYMOUS -#define MAP_ANONYMOUS MAP_ANON -#endif - void *PosixLockedPageAllocator::AllocateLocked(size_t len, bool *lockingSuccess) { void *addr; @@ -288,9 +282,8 @@ LockedPool::LockedPool(std::unique_ptr<LockedPageAllocator> allocator_in, Lockin { } -LockedPool::~LockedPool() -{ -} +LockedPool::~LockedPool() = default; + void* LockedPool::alloc(size_t size) { std::lock_guard<std::mutex> lock(mutex); diff --git a/src/sync.h b/src/sync.h index 85000a9151..7ec4b668ac 100644 --- a/src/sync.h +++ b/src/sync.h @@ -6,8 +6,11 @@ #ifndef BITCOIN_SYNC_H #define BITCOIN_SYNC_H +#ifdef DEBUG_LOCKCONTENTION #include <logging.h> #include <logging/timer.h> +#endif + #include <threadsafety.h> #include <util/macros.h> @@ -80,8 +83,6 @@ void AssertLockNotHeldInternal(const char* pszName, const char* pszFile, int nLi inline void DeleteLock(void* cs) {} inline bool LockStackEmpty() { return true; } #endif -#define AssertLockHeld(cs) AssertLockHeldInternal(#cs, __FILE__, __LINE__, &cs) -#define AssertLockNotHeld(cs) AssertLockNotHeldInternal(#cs, __FILE__, __LINE__, &cs) /** * Template mixin that adds -Wthread-safety locking annotations and lock order @@ -126,7 +127,25 @@ public: using RecursiveMutex = AnnotatedMixin<std::recursive_mutex>; /** Wrapped mutex: supports waiting but not recursive locking */ -typedef AnnotatedMixin<std::mutex> Mutex; +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. */ template <typename Mutex, typename Base = typename Mutex::UniqueLock> @@ -136,8 +155,10 @@ private: void Enter(const char* pszName, const char* pszFile, int nLine) { EnterCritical(pszName, pszFile, nLine, Base::mutex()); +#ifdef DEBUG_LOCKCONTENTION if (Base::try_lock()) return; LOG_TIME_MICROS_WITH_CATEGORY(strprintf("lock contention %s, %s:%d", pszName, pszFile, nLine), BCLog::LOCK); +#endif Base::lock(); } @@ -218,17 +239,31 @@ public: friend class reverse_lock; }; -#define REVERSE_LOCK(g) typename std::decay<decltype(g)>::type::reverse_lock PASTE2(revlock, __COUNTER__)(g, #g, __FILE__, __LINE__) +#define REVERSE_LOCK(g) typename std::decay<decltype(g)>::type::reverse_lock UNIQUE_NAME(revlock)(g, #g, __FILE__, __LINE__) template<typename MutexArg> using DebugLock = UniqueLock<typename std::remove_reference<typename std::remove_pointer<MutexArg>::type>::type>; -#define LOCK(cs) DebugLock<decltype(cs)> PASTE2(criticalblock, __COUNTER__)(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) \ { \ @@ -267,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 efc30b6822..b10d32ccec 100644 --- a/src/test/addrman_tests.cpp +++ b/src/test/addrman_tests.cpp @@ -23,7 +23,7 @@ using namespace std::literals; using node::NodeContext; -static const std::vector<bool> EMPTY_ASMAP; +static NetGroupManager EMPTY_NETGROUPMAN{std::vector<bool>()}; static const bool DETERMINISTIC{true}; static int32_t GetCheckRatio(const NodeContext& node_ctx) @@ -62,7 +62,7 @@ BOOST_FIXTURE_TEST_SUITE(addrman_tests, BasicTestingSetup) BOOST_AUTO_TEST_CASE(addrman_simple) { - auto addrman = std::make_unique<AddrMan>(EMPTY_ASMAP, DETERMINISTIC, GetCheckRatio(m_node)); + auto addrman = std::make_unique<AddrMan>(EMPTY_NETGROUPMAN, DETERMINISTIC, GetCheckRatio(m_node)); CNetAddr source = ResolveIP("252.2.2.2"); @@ -96,7 +96,7 @@ BOOST_AUTO_TEST_CASE(addrman_simple) BOOST_CHECK(addrman->size() >= 1); // Test: reset addrman and test AddrMan::Add multiple addresses works as expected - addrman = std::make_unique<AddrMan>(EMPTY_ASMAP, DETERMINISTIC, GetCheckRatio(m_node)); + addrman = std::make_unique<AddrMan>(EMPTY_NETGROUPMAN, DETERMINISTIC, GetCheckRatio(m_node)); std::vector<CAddress> vAddr; vAddr.push_back(CAddress(ResolveService("250.1.1.3", 8333), NODE_NONE)); vAddr.push_back(CAddress(ResolveService("250.1.1.4", 8333), NODE_NONE)); @@ -106,7 +106,7 @@ BOOST_AUTO_TEST_CASE(addrman_simple) BOOST_AUTO_TEST_CASE(addrman_ports) { - auto addrman = std::make_unique<AddrMan>(EMPTY_ASMAP, DETERMINISTIC, GetCheckRatio(m_node)); + auto addrman = std::make_unique<AddrMan>(EMPTY_NETGROUPMAN, DETERMINISTIC, GetCheckRatio(m_node)); CNetAddr source = ResolveIP("252.2.2.2"); @@ -135,7 +135,7 @@ BOOST_AUTO_TEST_CASE(addrman_ports) BOOST_AUTO_TEST_CASE(addrman_select) { - auto addrman = std::make_unique<AddrMan>(EMPTY_ASMAP, DETERMINISTIC, GetCheckRatio(m_node)); + auto addrman = std::make_unique<AddrMan>(EMPTY_NETGROUPMAN, DETERMINISTIC, GetCheckRatio(m_node)); CNetAddr source = ResolveIP("252.2.2.2"); @@ -194,7 +194,7 @@ BOOST_AUTO_TEST_CASE(addrman_select) BOOST_AUTO_TEST_CASE(addrman_new_collisions) { - auto addrman = std::make_unique<AddrMan>(EMPTY_ASMAP, DETERMINISTIC, GetCheckRatio(m_node)); + auto addrman = std::make_unique<AddrMan>(EMPTY_NETGROUPMAN, DETERMINISTIC, GetCheckRatio(m_node)); CNetAddr source = ResolveIP("252.2.2.2"); @@ -223,9 +223,9 @@ BOOST_AUTO_TEST_CASE(addrman_new_collisions) BOOST_AUTO_TEST_CASE(addrman_new_multiplicity) { - auto addrman = std::make_unique<AddrMan>(EMPTY_ASMAP, DETERMINISTIC, GetCheckRatio(m_node)); + 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(); @@ -255,7 +255,7 @@ BOOST_AUTO_TEST_CASE(addrman_new_multiplicity) BOOST_AUTO_TEST_CASE(addrman_tried_collisions) { - auto addrman = std::make_unique<AddrMan>(EMPTY_ASMAP, DETERMINISTIC, GetCheckRatio(m_node)); + auto addrman = std::make_unique<AddrMan>(EMPTY_NETGROUPMAN, DETERMINISTIC, GetCheckRatio(m_node)); CNetAddr source = ResolveIP("252.2.2.2"); @@ -286,7 +286,7 @@ BOOST_AUTO_TEST_CASE(addrman_tried_collisions) BOOST_AUTO_TEST_CASE(addrman_getaddr) { - auto addrman = std::make_unique<AddrMan>(EMPTY_ASMAP, DETERMINISTIC, GetCheckRatio(m_node)); + auto addrman = std::make_unique<AddrMan>(EMPTY_NETGROUPMAN, DETERMINISTIC, GetCheckRatio(m_node)); // Test: Sanity check, GetAddr should never return anything if addrman // is empty. @@ -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); @@ -357,27 +357,25 @@ BOOST_AUTO_TEST_CASE(caddrinfo_get_tried_bucket_legacy) uint256 nKey1 = (uint256)(CHashWriter(SER_GETHASH, 0) << 1).GetHash(); uint256 nKey2 = (uint256)(CHashWriter(SER_GETHASH, 0) << 2).GetHash(); - std::vector<bool> asmap; // use /16 - - BOOST_CHECK_EQUAL(info1.GetTriedBucket(nKey1, asmap), 40); + BOOST_CHECK_EQUAL(info1.GetTriedBucket(nKey1, EMPTY_NETGROUPMAN), 40); // Test: Make sure key actually randomizes bucket placement. A fail on // this test could be a security issue. - BOOST_CHECK(info1.GetTriedBucket(nKey1, asmap) != info1.GetTriedBucket(nKey2, asmap)); + BOOST_CHECK(info1.GetTriedBucket(nKey1, EMPTY_NETGROUPMAN) != info1.GetTriedBucket(nKey2, EMPTY_NETGROUPMAN)); // Test: Two addresses with same IP but different ports can map to // different buckets because they have different keys. AddrInfo info2 = AddrInfo(addr2, source1); BOOST_CHECK(info1.GetKey() != info2.GetKey()); - BOOST_CHECK(info1.GetTriedBucket(nKey1, asmap) != info2.GetTriedBucket(nKey1, asmap)); + BOOST_CHECK(info1.GetTriedBucket(nKey1, EMPTY_NETGROUPMAN) != info2.GetTriedBucket(nKey1, EMPTY_NETGROUPMAN)); std::set<int> buckets; for (int i = 0; i < 255; i++) { AddrInfo infoi = AddrInfo( CAddress(ResolveService("250.1.1." + ToString(i)), NODE_NONE), ResolveIP("250.1.1." + ToString(i))); - int bucket = infoi.GetTriedBucket(nKey1, asmap); + int bucket = infoi.GetTriedBucket(nKey1, EMPTY_NETGROUPMAN); buckets.insert(bucket); } // Test: IP addresses in the same /16 prefix should @@ -389,7 +387,7 @@ BOOST_AUTO_TEST_CASE(caddrinfo_get_tried_bucket_legacy) AddrInfo infoj = AddrInfo( CAddress(ResolveService("250." + ToString(j) + ".1.1"), NODE_NONE), ResolveIP("250." + ToString(j) + ".1.1")); - int bucket = infoj.GetTriedBucket(nKey1, asmap); + int bucket = infoj.GetTriedBucket(nKey1, EMPTY_NETGROUPMAN); buckets.insert(bucket); } // Test: IP addresses in the different /16 prefix should map to more than @@ -409,27 +407,25 @@ BOOST_AUTO_TEST_CASE(caddrinfo_get_new_bucket_legacy) uint256 nKey1 = (uint256)(CHashWriter(SER_GETHASH, 0) << 1).GetHash(); uint256 nKey2 = (uint256)(CHashWriter(SER_GETHASH, 0) << 2).GetHash(); - std::vector<bool> asmap; // use /16 - // Test: Make sure the buckets are what we expect - BOOST_CHECK_EQUAL(info1.GetNewBucket(nKey1, asmap), 786); - BOOST_CHECK_EQUAL(info1.GetNewBucket(nKey1, source1, asmap), 786); + BOOST_CHECK_EQUAL(info1.GetNewBucket(nKey1, EMPTY_NETGROUPMAN), 786); + BOOST_CHECK_EQUAL(info1.GetNewBucket(nKey1, source1, EMPTY_NETGROUPMAN), 786); // Test: Make sure key actually randomizes bucket placement. A fail on // this test could be a security issue. - BOOST_CHECK(info1.GetNewBucket(nKey1, asmap) != info1.GetNewBucket(nKey2, asmap)); + BOOST_CHECK(info1.GetNewBucket(nKey1, EMPTY_NETGROUPMAN) != info1.GetNewBucket(nKey2, EMPTY_NETGROUPMAN)); // Test: Ports should not affect bucket placement in the addr AddrInfo info2 = AddrInfo(addr2, source1); BOOST_CHECK(info1.GetKey() != info2.GetKey()); - BOOST_CHECK_EQUAL(info1.GetNewBucket(nKey1, asmap), info2.GetNewBucket(nKey1, asmap)); + BOOST_CHECK_EQUAL(info1.GetNewBucket(nKey1, EMPTY_NETGROUPMAN), info2.GetNewBucket(nKey1, EMPTY_NETGROUPMAN)); std::set<int> buckets; for (int i = 0; i < 255; i++) { AddrInfo infoi = AddrInfo( CAddress(ResolveService("250.1.1." + ToString(i)), NODE_NONE), ResolveIP("250.1.1." + ToString(i))); - int bucket = infoi.GetNewBucket(nKey1, asmap); + int bucket = infoi.GetNewBucket(nKey1, EMPTY_NETGROUPMAN); buckets.insert(bucket); } // Test: IP addresses in the same group (\16 prefix for IPv4) should @@ -442,7 +438,7 @@ BOOST_AUTO_TEST_CASE(caddrinfo_get_new_bucket_legacy) ResolveService( ToString(250 + (j / 255)) + "." + ToString(j % 256) + ".1.1"), NODE_NONE), ResolveIP("251.4.1.1")); - int bucket = infoj.GetNewBucket(nKey1, asmap); + int bucket = infoj.GetNewBucket(nKey1, EMPTY_NETGROUPMAN); buckets.insert(bucket); } // Test: IP addresses in the same source groups should map to NO MORE @@ -454,7 +450,7 @@ BOOST_AUTO_TEST_CASE(caddrinfo_get_new_bucket_legacy) AddrInfo infoj = AddrInfo( CAddress(ResolveService("250.1.1.1"), NODE_NONE), ResolveIP("250." + ToString(p) + ".1.1")); - int bucket = infoj.GetNewBucket(nKey1, asmap); + int bucket = infoj.GetNewBucket(nKey1, EMPTY_NETGROUPMAN); buckets.insert(bucket); } // Test: IP addresses in the different source groups should map to MORE @@ -475,6 +471,9 @@ BOOST_AUTO_TEST_CASE(caddrinfo_get_new_bucket_legacy) // 101.8.0.0/16 AS8 BOOST_AUTO_TEST_CASE(caddrinfo_get_tried_bucket) { + std::vector<bool> asmap = FromBytes(asmap_raw, sizeof(asmap_raw) * 8); + NetGroupManager ngm_asmap{asmap}; + CAddress addr1 = CAddress(ResolveService("250.1.1.1", 8333), NODE_NONE); CAddress addr2 = CAddress(ResolveService("250.1.1.1", 9999), NODE_NONE); @@ -486,27 +485,25 @@ BOOST_AUTO_TEST_CASE(caddrinfo_get_tried_bucket) uint256 nKey1 = (uint256)(CHashWriter(SER_GETHASH, 0) << 1).GetHash(); uint256 nKey2 = (uint256)(CHashWriter(SER_GETHASH, 0) << 2).GetHash(); - std::vector<bool> asmap = FromBytes(asmap_raw, sizeof(asmap_raw) * 8); - - BOOST_CHECK_EQUAL(info1.GetTriedBucket(nKey1, asmap), 236); + BOOST_CHECK_EQUAL(info1.GetTriedBucket(nKey1, ngm_asmap), 236); // Test: Make sure key actually randomizes bucket placement. A fail on // this test could be a security issue. - BOOST_CHECK(info1.GetTriedBucket(nKey1, asmap) != info1.GetTriedBucket(nKey2, asmap)); + BOOST_CHECK(info1.GetTriedBucket(nKey1, ngm_asmap) != info1.GetTriedBucket(nKey2, ngm_asmap)); // Test: Two addresses with same IP but different ports can map to // different buckets because they have different keys. AddrInfo info2 = AddrInfo(addr2, source1); BOOST_CHECK(info1.GetKey() != info2.GetKey()); - BOOST_CHECK(info1.GetTriedBucket(nKey1, asmap) != info2.GetTriedBucket(nKey1, asmap)); + BOOST_CHECK(info1.GetTriedBucket(nKey1, ngm_asmap) != info2.GetTriedBucket(nKey1, ngm_asmap)); std::set<int> buckets; for (int j = 0; j < 255; j++) { AddrInfo infoj = AddrInfo( CAddress(ResolveService("101." + ToString(j) + ".1.1"), NODE_NONE), ResolveIP("101." + ToString(j) + ".1.1")); - int bucket = infoj.GetTriedBucket(nKey1, asmap); + int bucket = infoj.GetTriedBucket(nKey1, ngm_asmap); buckets.insert(bucket); } // Test: IP addresses in the different /16 prefix MAY map to more than @@ -518,7 +515,7 @@ BOOST_AUTO_TEST_CASE(caddrinfo_get_tried_bucket) AddrInfo infoj = AddrInfo( CAddress(ResolveService("250." + ToString(j) + ".1.1"), NODE_NONE), ResolveIP("250." + ToString(j) + ".1.1")); - int bucket = infoj.GetTriedBucket(nKey1, asmap); + int bucket = infoj.GetTriedBucket(nKey1, ngm_asmap); buckets.insert(bucket); } // Test: IP addresses in the different /16 prefix MAY NOT map to more than @@ -528,6 +525,9 @@ BOOST_AUTO_TEST_CASE(caddrinfo_get_tried_bucket) BOOST_AUTO_TEST_CASE(caddrinfo_get_new_bucket) { + std::vector<bool> asmap = FromBytes(asmap_raw, sizeof(asmap_raw) * 8); + NetGroupManager ngm_asmap{asmap}; + CAddress addr1 = CAddress(ResolveService("250.1.2.1", 8333), NODE_NONE); CAddress addr2 = CAddress(ResolveService("250.1.2.1", 9999), NODE_NONE); @@ -538,27 +538,25 @@ BOOST_AUTO_TEST_CASE(caddrinfo_get_new_bucket) uint256 nKey1 = (uint256)(CHashWriter(SER_GETHASH, 0) << 1).GetHash(); uint256 nKey2 = (uint256)(CHashWriter(SER_GETHASH, 0) << 2).GetHash(); - std::vector<bool> asmap = FromBytes(asmap_raw, sizeof(asmap_raw) * 8); - // Test: Make sure the buckets are what we expect - BOOST_CHECK_EQUAL(info1.GetNewBucket(nKey1, asmap), 795); - BOOST_CHECK_EQUAL(info1.GetNewBucket(nKey1, source1, asmap), 795); + BOOST_CHECK_EQUAL(info1.GetNewBucket(nKey1, ngm_asmap), 795); + BOOST_CHECK_EQUAL(info1.GetNewBucket(nKey1, source1, ngm_asmap), 795); // Test: Make sure key actually randomizes bucket placement. A fail on // this test could be a security issue. - BOOST_CHECK(info1.GetNewBucket(nKey1, asmap) != info1.GetNewBucket(nKey2, asmap)); + BOOST_CHECK(info1.GetNewBucket(nKey1, ngm_asmap) != info1.GetNewBucket(nKey2, ngm_asmap)); // Test: Ports should not affect bucket placement in the addr AddrInfo info2 = AddrInfo(addr2, source1); BOOST_CHECK(info1.GetKey() != info2.GetKey()); - BOOST_CHECK_EQUAL(info1.GetNewBucket(nKey1, asmap), info2.GetNewBucket(nKey1, asmap)); + BOOST_CHECK_EQUAL(info1.GetNewBucket(nKey1, ngm_asmap), info2.GetNewBucket(nKey1, ngm_asmap)); std::set<int> buckets; for (int i = 0; i < 255; i++) { AddrInfo infoi = AddrInfo( CAddress(ResolveService("250.1.1." + ToString(i)), NODE_NONE), ResolveIP("250.1.1." + ToString(i))); - int bucket = infoi.GetNewBucket(nKey1, asmap); + int bucket = infoi.GetNewBucket(nKey1, ngm_asmap); buckets.insert(bucket); } // Test: IP addresses in the same /16 prefix @@ -571,7 +569,7 @@ BOOST_AUTO_TEST_CASE(caddrinfo_get_new_bucket) ResolveService( ToString(250 + (j / 255)) + "." + ToString(j % 256) + ".1.1"), NODE_NONE), ResolveIP("251.4.1.1")); - int bucket = infoj.GetNewBucket(nKey1, asmap); + int bucket = infoj.GetNewBucket(nKey1, ngm_asmap); buckets.insert(bucket); } // Test: IP addresses in the same source /16 prefix should not map to more @@ -583,7 +581,7 @@ BOOST_AUTO_TEST_CASE(caddrinfo_get_new_bucket) AddrInfo infoj = AddrInfo( CAddress(ResolveService("250.1.1.1"), NODE_NONE), ResolveIP("101." + ToString(p) + ".1.1")); - int bucket = infoj.GetNewBucket(nKey1, asmap); + int bucket = infoj.GetNewBucket(nKey1, ngm_asmap); buckets.insert(bucket); } // Test: IP addresses in the different source /16 prefixes usually map to MORE @@ -595,7 +593,7 @@ BOOST_AUTO_TEST_CASE(caddrinfo_get_new_bucket) AddrInfo infoj = AddrInfo( CAddress(ResolveService("250.1.1.1"), NODE_NONE), ResolveIP("250." + ToString(p) + ".1.1")); - int bucket = infoj.GetNewBucket(nKey1, asmap); + int bucket = infoj.GetNewBucket(nKey1, ngm_asmap); buckets.insert(bucket); } // Test: IP addresses in the different source /16 prefixes sometimes map to NO MORE @@ -606,11 +604,12 @@ BOOST_AUTO_TEST_CASE(caddrinfo_get_new_bucket) BOOST_AUTO_TEST_CASE(addrman_serialization) { std::vector<bool> asmap1 = FromBytes(asmap_raw, sizeof(asmap_raw) * 8); + NetGroupManager netgroupman{asmap1}; const auto ratio = GetCheckRatio(m_node); - auto addrman_asmap1 = std::make_unique<AddrMan>(asmap1, DETERMINISTIC, ratio); - auto addrman_asmap1_dup = std::make_unique<AddrMan>(asmap1, DETERMINISTIC, ratio); - auto addrman_noasmap = std::make_unique<AddrMan>(EMPTY_ASMAP, DETERMINISTIC, ratio); + auto addrman_asmap1 = std::make_unique<AddrMan>(netgroupman, DETERMINISTIC, ratio); + auto addrman_asmap1_dup = std::make_unique<AddrMan>(netgroupman, DETERMINISTIC, ratio); + auto addrman_noasmap = std::make_unique<AddrMan>(EMPTY_NETGROUPMAN, DETERMINISTIC, ratio); CDataStream stream(SER_NETWORK, PROTOCOL_VERSION); @@ -639,8 +638,8 @@ BOOST_AUTO_TEST_CASE(addrman_serialization) BOOST_CHECK(addr_pos1.position != addr_pos3.position); // deserializing non-asmaped peers.dat to asmaped addrman - addrman_asmap1 = std::make_unique<AddrMan>(asmap1, DETERMINISTIC, ratio); - addrman_noasmap = std::make_unique<AddrMan>(EMPTY_ASMAP, DETERMINISTIC, ratio); + addrman_asmap1 = std::make_unique<AddrMan>(netgroupman, DETERMINISTIC, ratio); + addrman_noasmap = std::make_unique<AddrMan>(EMPTY_NETGROUPMAN, DETERMINISTIC, ratio); addrman_noasmap->Add({addr}, default_source); stream << *addrman_noasmap; stream >> *addrman_asmap1; @@ -651,8 +650,8 @@ BOOST_AUTO_TEST_CASE(addrman_serialization) BOOST_CHECK(addr_pos4 == addr_pos2); // used to map to different buckets, now maps to the same bucket. - addrman_asmap1 = std::make_unique<AddrMan>(asmap1, DETERMINISTIC, ratio); - addrman_noasmap = std::make_unique<AddrMan>(EMPTY_ASMAP, DETERMINISTIC, ratio); + addrman_asmap1 = std::make_unique<AddrMan>(netgroupman, DETERMINISTIC, ratio); + addrman_noasmap = std::make_unique<AddrMan>(EMPTY_NETGROUPMAN, DETERMINISTIC, ratio); CAddress addr1 = CAddress(ResolveService("250.1.1.1"), NODE_NONE); CAddress addr2 = CAddress(ResolveService("250.2.1.1"), NODE_NONE); addrman_noasmap->Add({addr, addr2}, default_source); @@ -671,7 +670,7 @@ BOOST_AUTO_TEST_CASE(remove_invalid) { // Confirm that invalid addresses are ignored in unserialization. - auto addrman = std::make_unique<AddrMan>(EMPTY_ASMAP, DETERMINISTIC, GetCheckRatio(m_node)); + auto addrman = std::make_unique<AddrMan>(EMPTY_NETGROUPMAN, DETERMINISTIC, GetCheckRatio(m_node)); CDataStream stream(SER_NETWORK, PROTOCOL_VERSION); const CAddress new1{ResolveService("5.5.5.5"), NODE_NONE}; @@ -703,14 +702,14 @@ BOOST_AUTO_TEST_CASE(remove_invalid) BOOST_REQUIRE(pos + sizeof(tried2_raw_replacement) <= stream.size()); memcpy(stream.data() + pos, tried2_raw_replacement, sizeof(tried2_raw_replacement)); - addrman = std::make_unique<AddrMan>(EMPTY_ASMAP, DETERMINISTIC, GetCheckRatio(m_node)); + addrman = std::make_unique<AddrMan>(EMPTY_NETGROUPMAN, DETERMINISTIC, GetCheckRatio(m_node)); stream >> *addrman; BOOST_CHECK_EQUAL(addrman->size(), 2); } BOOST_AUTO_TEST_CASE(addrman_selecttriedcollision) { - auto addrman = std::make_unique<AddrMan>(EMPTY_ASMAP, DETERMINISTIC, GetCheckRatio(m_node)); + auto addrman = std::make_unique<AddrMan>(EMPTY_NETGROUPMAN, DETERMINISTIC, GetCheckRatio(m_node)); BOOST_CHECK(addrman->size() == 0); @@ -743,7 +742,7 @@ BOOST_AUTO_TEST_CASE(addrman_selecttriedcollision) BOOST_AUTO_TEST_CASE(addrman_noevict) { - auto addrman = std::make_unique<AddrMan>(EMPTY_ASMAP, DETERMINISTIC, GetCheckRatio(m_node)); + auto addrman = std::make_unique<AddrMan>(EMPTY_NETGROUPMAN, DETERMINISTIC, GetCheckRatio(m_node)); // Add 35 addresses. CNetAddr source = ResolveIP("252.2.2.2"); @@ -795,7 +794,7 @@ BOOST_AUTO_TEST_CASE(addrman_noevict) BOOST_AUTO_TEST_CASE(addrman_evictionworks) { - auto addrman = std::make_unique<AddrMan>(EMPTY_ASMAP, DETERMINISTIC, GetCheckRatio(m_node)); + auto addrman = std::make_unique<AddrMan>(EMPTY_NETGROUPMAN, DETERMINISTIC, GetCheckRatio(m_node)); BOOST_CHECK(addrman->size() == 0); @@ -822,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(); @@ -865,7 +864,7 @@ static CDataStream AddrmanToStream(const AddrMan& addrman) BOOST_AUTO_TEST_CASE(load_addrman) { - AddrMan addrman{EMPTY_ASMAP, DETERMINISTIC, GetCheckRatio(m_node)}; + AddrMan addrman{EMPTY_NETGROUPMAN, DETERMINISTIC, GetCheckRatio(m_node)}; CService addr1, addr2, addr3; BOOST_CHECK(Lookup("250.7.1.1", addr1, 8333, false)); @@ -884,7 +883,7 @@ BOOST_AUTO_TEST_CASE(load_addrman) // Test that the de-serialization does not throw an exception. CDataStream ssPeers1 = AddrmanToStream(addrman); bool exceptionThrown = false; - AddrMan addrman1{EMPTY_ASMAP, !DETERMINISTIC, GetCheckRatio(m_node)}; + AddrMan addrman1{EMPTY_NETGROUPMAN, !DETERMINISTIC, GetCheckRatio(m_node)}; BOOST_CHECK(addrman1.size() == 0); try { @@ -901,7 +900,7 @@ BOOST_AUTO_TEST_CASE(load_addrman) // Test that ReadFromStream creates an addrman with the correct number of addrs. CDataStream ssPeers2 = AddrmanToStream(addrman); - AddrMan addrman2{EMPTY_ASMAP, !DETERMINISTIC, GetCheckRatio(m_node)}; + AddrMan addrman2{EMPTY_NETGROUPMAN, !DETERMINISTIC, GetCheckRatio(m_node)}; BOOST_CHECK(addrman2.size() == 0); ReadFromStream(addrman2, ssPeers2); BOOST_CHECK(addrman2.size() == 3); @@ -939,7 +938,7 @@ BOOST_AUTO_TEST_CASE(load_addrman_corrupted) // Test that the de-serialization of corrupted peers.dat throws an exception. CDataStream ssPeers1 = MakeCorruptPeersDat(); bool exceptionThrown = false; - AddrMan addrman1{EMPTY_ASMAP, !DETERMINISTIC, GetCheckRatio(m_node)}; + AddrMan addrman1{EMPTY_NETGROUPMAN, !DETERMINISTIC, GetCheckRatio(m_node)}; BOOST_CHECK(addrman1.size() == 0); try { unsigned char pchMsgTmp[4]; @@ -955,7 +954,7 @@ BOOST_AUTO_TEST_CASE(load_addrman_corrupted) // Test that ReadFromStream fails if peers.dat is corrupt CDataStream ssPeers2 = MakeCorruptPeersDat(); - AddrMan addrman2{EMPTY_ASMAP, !DETERMINISTIC, GetCheckRatio(m_node)}; + AddrMan addrman2{EMPTY_NETGROUPMAN, !DETERMINISTIC, GetCheckRatio(m_node)}; BOOST_CHECK(addrman2.size() == 0); BOOST_CHECK_THROW(ReadFromStream(addrman2, ssPeers2), std::ios_base::failure); } @@ -963,11 +962,11 @@ BOOST_AUTO_TEST_CASE(load_addrman_corrupted) BOOST_AUTO_TEST_CASE(addrman_update_address) { // Tests updating nTime via Connected() and nServices via SetServices() - auto addrman = std::make_unique<AddrMan>(EMPTY_ASMAP, DETERMINISTIC, GetCheckRatio(m_node)); + auto addrman = std::make_unique<AddrMan>(EMPTY_NETGROUPMAN, DETERMINISTIC, GetCheckRatio(m_node)); 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); @@ -979,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 @@ -987,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/base32_tests.cpp b/src/test/base32_tests.cpp index 5fab7f0d1e..c6109dfeb0 100644 --- a/src/test/base32_tests.cpp +++ b/src/test/base32_tests.cpp @@ -22,20 +22,16 @@ BOOST_AUTO_TEST_CASE(base32_testvectors) BOOST_CHECK_EQUAL(strEnc, vstrOut[i]); strEnc = EncodeBase32(vstrIn[i], false); BOOST_CHECK_EQUAL(strEnc, vstrOutNoPadding[i]); - std::string strDec = DecodeBase32(vstrOut[i]); - BOOST_CHECK_EQUAL(strDec, vstrIn[i]); + auto dec = DecodeBase32(vstrOut[i]); + BOOST_REQUIRE(dec); + BOOST_CHECK_MESSAGE(MakeByteSpan(*dec) == MakeByteSpan(vstrIn[i]), vstrOut[i]); } // Decoding strings with embedded NUL characters should fail - bool failure; - (void)DecodeBase32("invalid\0"s, &failure); // correct size, invalid due to \0 - BOOST_CHECK(failure); - (void)DecodeBase32("AWSX3VPP"s, &failure); // valid - BOOST_CHECK(!failure); - (void)DecodeBase32("AWSX3VPP\0invalid"s, &failure); // correct size, invalid due to \0 - BOOST_CHECK(failure); - (void)DecodeBase32("AWSX3VPPinvalid"s, &failure); // invalid size - BOOST_CHECK(failure); + BOOST_CHECK(!DecodeBase32("invalid\0"s)); // correct size, invalid due to \0 + BOOST_CHECK(DecodeBase32("AWSX3VPP"s)); // valid + BOOST_CHECK(!DecodeBase32("AWSX3VPP\0invalid"s)); // correct size, invalid due to \0 + BOOST_CHECK(!DecodeBase32("AWSX3VPPinvalid"s)); // invalid size } BOOST_AUTO_TEST_SUITE_END() 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/base64_tests.cpp b/src/test/base64_tests.cpp index 6ee1b83691..54a02c6bf8 100644 --- a/src/test/base64_tests.cpp +++ b/src/test/base64_tests.cpp @@ -19,8 +19,9 @@ BOOST_AUTO_TEST_CASE(base64_testvectors) { std::string strEnc = EncodeBase64(vstrIn[i]); BOOST_CHECK_EQUAL(strEnc, vstrOut[i]); - std::string strDec = DecodeBase64(strEnc); - BOOST_CHECK_EQUAL(strDec, vstrIn[i]); + auto dec = DecodeBase64(strEnc); + BOOST_REQUIRE(dec); + BOOST_CHECK_MESSAGE(MakeByteSpan(*dec) == MakeByteSpan(vstrIn[i]), vstrOut[i]); } { @@ -34,15 +35,10 @@ BOOST_AUTO_TEST_CASE(base64_testvectors) } // Decoding strings with embedded NUL characters should fail - bool failure; - (void)DecodeBase64("invalid\0"s, &failure); - BOOST_CHECK(failure); - (void)DecodeBase64("nQB/pZw="s, &failure); - BOOST_CHECK(!failure); - (void)DecodeBase64("nQB/pZw=\0invalid"s, &failure); - BOOST_CHECK(failure); - (void)DecodeBase64("nQB/pZw=invalid\0"s, &failure); - BOOST_CHECK(failure); + BOOST_CHECK(!DecodeBase64("invalid\0"s)); + BOOST_CHECK(DecodeBase64("nQB/pZw="s)); + BOOST_CHECK(!DecodeBase64("nQB/pZw=\0invalid"s)); + BOOST_CHECK(!DecodeBase64("nQB/pZw=invalid\0"s)); } BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/bip32_tests.cpp b/src/test/bip32_tests.cpp index 0fa6b7784f..75b29ae0aa 100644 --- a/src/test/bip32_tests.cpp +++ b/src/test/bip32_tests.cpp @@ -120,8 +120,9 @@ const std::vector<std::string> TEST5 = { "xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHL" }; -void RunTest(const TestVector &test) { - std::vector<unsigned char> seed = ParseHex(test.strHexMaster); +void RunTest(const TestVector& test) +{ + std::vector<std::byte> seed{ParseHex<std::byte>(test.strHexMaster)}; CExtKey key; CExtPubKey pubkey; key.SetSeed(seed); @@ -183,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/blockencodings_tests.cpp b/src/test/blockencodings_tests.cpp index 9dfbd7ba7c..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()); @@ -64,7 +64,7 @@ BOOST_AUTO_TEST_CASE(SimpleRoundTripTest) // Do a simple ShortTxIDs RT { - CBlockHeaderAndShortTxIDs shortIDs(block, true); + CBlockHeaderAndShortTxIDs shortIDs{block}; CDataStream stream(SER_NETWORK, PROTOCOL_VERSION); stream << shortIDs; @@ -122,7 +122,7 @@ public: stream >> *this; } explicit TestHeaderAndShortIDs(const CBlock& block) : - TestHeaderAndShortIDs(CBlockHeaderAndShortTxIDs(block, true)) {} + TestHeaderAndShortIDs(CBlockHeaderAndShortTxIDs{block}) {} uint64_t GetShortID(const uint256& txhash) const { CDataStream stream(SER_NETWORK, PROTOCOL_VERSION); @@ -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); @@ -279,7 +279,7 @@ BOOST_AUTO_TEST_CASE(EmptyBlockRoundTripTest) // Test simple header round-trip with only coinbase { - CBlockHeaderAndShortTxIDs shortIDs(block, false); + CBlockHeaderAndShortTxIDs shortIDs{block}; CDataStream stream(SER_NETWORK, PROTOCOL_VERSION); stream << shortIDs; diff --git a/src/test/blockfilter_index_tests.cpp b/src/test/blockfilter_index_tests.cpp index 7c502349b3..1a182209b8 100644 --- a/src/test/blockfilter_index_tests.cpp +++ b/src/test/blockfilter_index_tests.cpp @@ -4,8 +4,10 @@ #include <blockfilter.h> #include <chainparams.h> +#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> @@ -18,7 +20,6 @@ using node::BlockAssembler; using node::CBlockTemplate; -using node::IncrementExtraNonce; BOOST_AUTO_TEST_SUITE(blockfilter_index_tests) @@ -65,8 +66,7 @@ CBlock BuildChainTestingSetup::CreateBlock(const CBlockIndex* prev, const std::vector<CMutableTransaction>& txns, const CScript& scriptPubKey) { - const CChainParams& chainparams = Params(); - std::unique_ptr<CBlockTemplate> pblocktemplate = BlockAssembler(m_node.chainman->ActiveChainstate(), *m_node.mempool, chainparams).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; @@ -76,11 +76,14 @@ CBlock BuildChainTestingSetup::CreateBlock(const CBlockIndex* prev, for (const CMutableTransaction& tx : txns) { block.vtx.push_back(MakeTransactionRef(tx)); } - // IncrementExtraNonce creates a valid coinbase and merkleRoot - unsigned int extraNonce = 0; - IncrementExtraNonce(&block, prev, extraNonce); + { + CMutableTransaction tx_coinbase{*block.vtx.at(0)}; + tx_coinbase.vin.at(0).scriptSig = CScript{} << prev->nHeight + 1; + block.vtx.at(0) = MakeTransactionRef(std::move(tx_coinbase)); + block.hashMerkleRoot = BlockMerkleRoot(block); + } - while (!CheckProofOfWork(block.GetHash(), block.nBits, chainparams.GetConsensus())) ++block.nNonce; + while (!CheckProofOfWork(block.GetHash(), block.nBits, m_node.chainman->GetConsensus())) ++block.nNonce; return block; } @@ -98,7 +101,7 @@ bool BuildChainTestingSetup::BuildChain(const CBlockIndex* pindex, CBlockHeader header = block->GetBlockHeader(); BlockValidationState state; - if (!Assert(m_node.chainman)->ProcessNewBlockHeaders({header}, state, Params(), &pindex)) { + if (!Assert(m_node.chainman)->ProcessNewBlockHeaders({header}, state, &pindex)) { return false; } } @@ -108,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; @@ -135,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; @@ -175,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(Params(), block, true, nullptr)); + BOOST_REQUIRE(Assert(m_node.chainman)->ProcessNewBlock(block, true, nullptr)); } for (size_t i = 0; i < 2; i++) { const auto& block = chainA[i]; @@ -193,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(Params(), block, true, nullptr)); + BOOST_REQUIRE(Assert(m_node.chainman)->ProcessNewBlock(block, true, nullptr)); } for (size_t i = 0; i < 3; i++) { const auto& block = chainB[i]; @@ -224,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(Params(), block, true, nullptr)); + BOOST_REQUIRE(Assert(m_node.chainman)->ProcessNewBlock(block, true, nullptr)); } // Check that chain A and B blocks can be retrieved. @@ -277,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++; }); @@ -299,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 8eb4dbc592..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) { @@ -148,7 +148,7 @@ BOOST_AUTO_TEST_CASE(blockfilters_json_test) } unsigned int pos = 0; - /*int block_height =*/ test[pos++].get_int(); + /*int block_height =*/ test[pos++].getInt<int>(); uint256 block_hash; BOOST_CHECK(ParseHashStr(test[pos++].get_str(), block_hash)); diff --git a/src/test/checkqueue_tests.cpp b/src/test/checkqueue_tests.cpp index 153ccd984b..875522d744 100644 --- a/src/test/checkqueue_tests.cpp +++ b/src/test/checkqueue_tests.cpp @@ -19,13 +19,17 @@ #include <vector> /** - * Identical to TestingSetup but excludes lock contention logging, as some of - * these tests are designed to be heavily contested to trigger race conditions - * or other issues. + * Identical to TestingSetup but excludes lock contention logging if + * `DEBUG_LOCKCONTENTION` is defined, as some of these tests are designed to be + * heavily contested to trigger race conditions or other issues. */ struct NoLockLoggingTestingSetup : public TestingSetup { NoLockLoggingTestingSetup() +#ifdef DEBUG_LOCKCONTENTION : TestingSetup{CBaseChainParams::MAIN, /*extra_args=*/{"-debugexclude=lock"}} {} +#else + : TestingSetup{CBaseChainParams::MAIN} {} +#endif }; BOOST_FIXTURE_TEST_SUITE(checkqueue_tests, NoLockLoggingTestingSetup) @@ -38,7 +42,7 @@ struct FakeCheck { { return true; } - void swap(FakeCheck& x){}; + void swap(FakeCheck& x) noexcept {}; }; struct FakeCheckCheckCompletion { @@ -48,7 +52,7 @@ struct FakeCheckCheckCompletion { n_calls.fetch_add(1, std::memory_order_relaxed); return true; } - void swap(FakeCheckCheckCompletion& x){}; + void swap(FakeCheckCheckCompletion& x) noexcept {}; }; struct FailingCheck { @@ -59,7 +63,7 @@ struct FailingCheck { { return !fails; } - void swap(FailingCheck& x) + void swap(FailingCheck& x) noexcept { std::swap(fails, x.fails); }; @@ -77,7 +81,10 @@ struct UniqueCheck { results.insert(check_id); return true; } - void swap(UniqueCheck& x) { std::swap(x.check_id, check_id); }; + void swap(UniqueCheck& x) noexcept + { + std::swap(x.check_id, check_id); + }; }; @@ -88,7 +95,7 @@ struct MemoryCheck { { return true; } - MemoryCheck(){}; + MemoryCheck() = default; MemoryCheck(const MemoryCheck& x) { // We have to do this to make sure that destructor calls are paired @@ -105,7 +112,10 @@ struct MemoryCheck { { fake_allocated_memory.fetch_sub(b, std::memory_order_relaxed); }; - void swap(MemoryCheck& x) { std::swap(b, x.b); }; + void swap(MemoryCheck& x) noexcept + { + std::swap(b, x.b); + }; }; struct FrozenCleanupCheck { @@ -119,7 +129,7 @@ struct FrozenCleanupCheck { { return true; } - FrozenCleanupCheck() {} + FrozenCleanupCheck() = default; ~FrozenCleanupCheck() { if (should_freeze) { @@ -129,7 +139,10 @@ struct FrozenCleanupCheck { cv.wait(l, []{ return nFrozen.load(std::memory_order_relaxed) == 0;}); } } - void swap(FrozenCleanupCheck& x){std::swap(should_freeze, x.should_freeze);}; + void swap(FrozenCleanupCheck& x) noexcept + { + std::swap(should_freeze, x.should_freeze); + }; }; // Static Allocations diff --git a/src/test/coins_tests.cpp b/src/test/coins_tests.cpp index 82e4e1c90f..b333a9f72d 100644 --- a/src/test/coins_tests.cpp +++ b/src/test/coins_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 <attributes.h> #include <clientversion.h> #include <coins.h> #include <script/standard.h> diff --git a/src/test/coinstatsindex_tests.cpp b/src/test/coinstatsindex_tests.cpp index 92de4ec7ba..c93d05a93b 100644 --- a/src/test/coinstatsindex_tests.cpp +++ b/src/test/coinstatsindex_tests.cpp @@ -2,8 +2,11 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. +#include <chainparams.h> #include <index/coinstatsindex.h> +#include <interfaces/chain.h> #include <test/util/setup_common.h> +#include <test/util/validation.h> #include <util/time.h> #include <validation.h> @@ -11,16 +14,23 @@ #include <chrono> -using node::CCoinsStats; -using node::CoinStatsHashType; - BOOST_AUTO_TEST_SUITE(coinstatsindex_tests) +static void IndexWaitSynced(BaseIndex& index) +{ + // Allow the CoinStatsIndex to catch up with the block index that is syncing + // in a background thread. + const auto timeout = GetTime<std::chrono::seconds>() + 120s; + while (!index.BlockUntilSyncedToCurrentChain()) { + BOOST_REQUIRE(timeout > GetTime<std::chrono::milliseconds>()); + UninterruptibleSleep(100ms); + } +} + 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}; - CCoinsStats coin_stats{CoinStatsHashType::MUHASH}; const CBlockIndex* block_index; { LOCK(cs_main); @@ -28,21 +38,15 @@ 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, coin_stats)); + 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()); - // Allow the CoinStatsIndex to catch up with the block index that is syncing - // in a background thread. - const auto timeout = GetTime<std::chrono::seconds>() + 120s; - while (!coin_stats_index.BlockUntilSyncedToCurrentChain()) { - BOOST_REQUIRE(timeout > GetTime<std::chrono::milliseconds>()); - UninterruptibleSleep(100ms); - } + IndexWaitSynced(coin_stats_index); // Check that CoinStatsIndex works for genesis block. const CBlockIndex* genesis_block_index; @@ -50,10 +54,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, coin_stats)); + BOOST_CHECK(coin_stats_index.LookUpStats(genesis_block_index)); // Check that CoinStatsIndex updates with new blocks. - coin_stats_index.LookUpStats(block_index, coin_stats); + BOOST_CHECK(coin_stats_index.LookUpStats(block_index)); const CScript script_pub_key{CScript() << ToByteVector(coinbaseKey.GetPubKey()) << OP_CHECKSIG}; std::vector<CMutableTransaction> noTxns; @@ -62,13 +66,12 @@ BOOST_FIXTURE_TEST_CASE(coinstatsindex_initial_sync, TestChain100Setup) // Let the CoinStatsIndex to catch up again. BOOST_CHECK(coin_stats_index.BlockUntilSyncedToCurrentChain()); - CCoinsStats new_coin_stats{CoinStatsHashType::MUHASH}; const CBlockIndex* new_block_index; { LOCK(cs_main); new_block_index = m_node.chainman->ActiveChain().Tip(); } - coin_stats_index.LookUpStats(new_block_index, new_coin_stats); + BOOST_CHECK(coin_stats_index.LookUpStats(new_block_index)); BOOST_CHECK(block_index != new_block_index); @@ -78,4 +81,44 @@ BOOST_FIXTURE_TEST_CASE(coinstatsindex_initial_sync, TestChain100Setup) // Rest of shutdown sequence and destructors happen in ~TestingSetup() } +// Test shutdown between BlockConnected and ChainStateFlushed notifications, +// 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(); + const CChainParams& params = Params(); + { + 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; + { + const CScript script_pub_key{CScript() << ToByteVector(coinbaseKey.GetPubKey()) << OP_CHECKSIG}; + const CBlock block = this->CreateBlock({}, script_pub_key, chainstate); + + new_block = std::make_shared<CBlock>(block); + + 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)); + CCoinsViewCache view(&chainstate.CoinsTip()); + BOOST_CHECK(chainstate.ConnectBlock(block, state, new_block_index, view)); + } + // Send block connected notification, then stop the index without + // sending a chainstate flushed notification. Prior to #24138, this + // would cause the index to be corrupted and fail to reload. + ValidationInterfaceTest::BlockConnected(index, new_block, new_block_index); + index.Stop(); + } + + { + CoinStatsIndex index{interfaces::MakeChain(m_node), 1 << 20}; + // Make sure the index can be loaded. + BOOST_REQUIRE(index.Start()); + index.Stop(); + } +} + BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/data/key_io_invalid.json b/src/test/data/key_io_invalid.json index abe07dad24..8f55abfec7 100644 --- a/src/test/data/key_io_invalid.json +++ b/src/test/data/key_io_invalid.json @@ -6,207 +6,207 @@ "x" ], [ - "2v7k5Bb8Lr1MMgTgW6HAf5YHXi6BzpPjHpQ4srD4RSwHYpzXKiXmLAgiLhkXvp3JF5v7nq45EWr" + "1GAdfviErV2Ew95FPtZyikz2qGP3gyCB6Hyu94sedAkPpA523m3fQwps9YKUZkKgQckGPKhRsFR" ], [ - "RAZzCGtMbiUgMiiyrZySrSpdfnQReFXA3r" + "37G2kMDLpmWVhimxRdzwNfE8JFvWXnJYnVcXeeGrek2qumdJuK7XArcVVpRtLLjRra3t64BEPF2" ], [ - "NYamy7tcPQTzoU5iyQojD3sqhiz7zxkvn8" + "giymtio7u7oqWtmC9YnvAEKkLF3JQpAdkEFkVJKYrVDfaLbhaDpX1ihfF2vZmya1i61fwLPC3YQ" ], [ - "geaFG555Ex5nyRf7JjW6Pj2GwZA8KYxtJJLbr1eZhVW75STbYBZeRszy3wg4pkKdF4ez9J4wQiz" + "8iVk9nLM3nYwRuwypjy9NK5rsuZH7BbrQRZ1pgcQmvMnjAgRXD" ], [ - "2Cxmid3c2XQ2zvQ8SA1ha2TKqvqbJS9XFmXRsCneBS3Po7Qqb65z5zNdsoF9AfieXFcpoVPmkmfa" + "cPTVQ1hbo4qdoysf6Jx5GthqucNmdfqt6J2pZRFeXv8Ep7Kmjqud" ], [ - "gaJ7UVge2njVg9tFTetJrtHgruMm7aQDiSAxfHrVEgzK8N2ooagDVmDkdph434xzc4K96Gjyxcs" + "cQbR2Ny85XFBzUMx3Ed6HsTLw2pVruSgPvt5AofnBUnhiv86gYeW" ], [ - "5JN5BEVQPZ3tAiatz1RGXkrJuE3EC6bervMaPb38wTNgEuZCeqp" + "2UB3iG3VJbX2TRrMwm6ssWskgvU9VjFBYSqCzwqkrihCwo7mg4mtS4WuGZgxTKuxf5A3EcotYEymz" ], [ - "3TnFbyUtBRS5rE1KTW81qLVspjJNaB3uu6uuvLjxhZo2DB6PCGh" + "cQe12pqwPR6ExtZKfrKf1q4b3CTh1Qi7MwuvMvzs79nWXDvESfBJ" ], [ - "7UgSZGaMaTc4d2mdEgcGBFiMeS6eMsithGUqvBsKTQdGzD7XQDbMEYo3gojdbXEPbUdFF3CQoK72f" + "tc1qeul5g2xfkvdkrhcfmdursv73ad64jnkjl9c40f" ], [ - "9261wfqQqruNDnBDhbbb4tN9oKA1KpRFHeoYeufyJApVGixyAG4V" + "bt1pq65rzej5glw3ra79gav6fqnx4haa0z257qr3mc8cggkefahmgvyseufhc0" ], [ - "cS824CTUh18scFmYuqt6BgxuRhdR4dEEnCHs3fzBbcyQgbfasHbw" + "tb13hty4qmumlwpp6chxjvcyzza4duqgtmxw3xhm3u9ahj4nyhtwz8eq7ynrj4" ], [ - "tc1q0ywf7wkz6t580n3yemd3ucfw8jxn93tpc6wskt" + "bcrt1r2qxpwuge" ], [ - "bt1pxeeuh96wpm5c6u3kavts2qgwlv6y8um7u7ga6ltlwrhrv7w9vers8lgt3k" + "bc10uexgzna2dpfk0vjt35srz6a27ps6m0l89jweznt83n2sqn2fx4hvn9ym5af8wut34sfrqhk3" ], [ - "tb130lvl2lyugsk2tf3zhwcjjv39dmwt2tt7ytqaexy8edwcuwks6p5scll5kz" + "tb1qum6uh0pt4q253qaf520929737v63w5gf" ], [ - "bcrt1rhsveeudk" + "bcrt1q888ryfgxpvl0k7vum8zpyar2u2sexvdhkf38ue37yknmqq0ycrwpl3w48y" ], [ - "bc10rmfwl8nxdweeyc4sf89t0tn9fv9w6qpyzsnl2r4k48vjqh03qas9asdje0rlr0phru0wqw0p" + "bc1qdsuzmn04k2z8vryw8l4dj8m5ygqgnne5n" ], [ - "tb1qjqnfsuatr54e957xzg9sqk7yqcry9lns" + "tb1qlj8es50nc8j8r8xshrjgzmw5azx89efghmw8ju6zcqla0g6xcnrstsjz7k" ], [ - "bcrt1q8p08mv8echkf3es027u4cdswxlylm3th76ls8v6y4zy4vwsavngpr4e4td" + "bcrt1qzwmyj0z924g7fzs5yvnrkc43y76RVyr2lh5t4r" ], [ - "BC1QNC2H66VLWTWTW52DP0FYUSNU3QQG5VT4V" + "bc1qpu6d26mrulzetu4jqhd7rsunv9aqru26f5c4j8" ], [ - "tb1qgk665m2auw09rc7pqyf7aulcuhmatz9xqtr5mxew7zuysacaascqs9v0vn" + "tb1qun6d26ufh77ghny6u5u8cwz9da7qwc6k4wkuceae9tth06eqlw0syupl4w" ], [ - "bcrt17CAPP7" + "bcrt1qj7g2jps453kj9htk9cxyyc2nxe69x4kzzmth7v" ], [ - "bc1qxmf2d6aerjzam3rur0zufqxqnyqfts5u302s7x" + "bc1p702xksx4z3uqf0u2phllxkfe5cgu0adxptqs0uelx0tqt8e885sqryes2l" ], [ - "tb1qn8x5dnzpexq7nnvrvnhwr9c3wkakpcyu9wwsjzq9pstkwg0t6qhs4l3rv6" + "tb1z7gmh0v6pc30z4xum76lmw8w86yswrlmw" ], [ - "BCRT1Q397G2RNVYRL5LK07CE8NCKHVKP8Z4SC9U0MVH9" + "bcrt1sjsrw6nun4h502cr97xmnyyuhkr22q0s6efrgtu" ], [ - "bc1pgxwyajq0gdn389f69uwn2fw9q0z5c9s063j5dgkdd23ajaud4hpsercr9h" + "2UVPFpGYnLHJezFzjUo42our6PMEoozzRdM" ], [ - "tb1z6mnmp5k542l6yk4ul0mp4rq3yvz44lfm" + "2MygHQjE1U33q3LSC53p69YqFjP8PihumJAF" ], [ - "bcrt17capp7" + "KzNbAQ4mexfAxa6RKBzHQqfoTycaeWpv2p" ], [ - "2D2bqvKseKHdoKjCNvjVULUgmxHu9hjKGwDbPRjTRH59tsHNLeyKwq3vyVBbo9LByY9wiapqjwFY" + "2jDPrDfAKihCGPbPD9ztY8TswAia4V8Bc6vx" ], [ - "2SSjAim4wZpeQRe5zTj1qqS6Li9ttJDaZ3ze" + "4VQUNG1hG64QFtaNyQZQWDdwpxB275Pwb3tvyPt2HDxB8Mi2MgH8Tz3AC83YYiz9LydsLNXEZJLHY" ], [ - "mi9H6MjLwXxy9kxe1x4ToxyLRBsmcZxgVi" + "39TKsUQ5QpEL1wowc6GMUqak94ijirPuP69ooV3xsFmiKQX2dau" ], [ - "VciXoxEitcn88jy197J9n9cpJ1pZahzU3SyWUiHqLgcfjttLEEJz" + "2UEJjT3dSdwc8dAo7oedPzznXceXCEsBbDfAvSymqpqDrkZMv7JBEUpLyhkghioYAWC9W4sKysry" ], [ - "KppmwADGoExPT9Eq5hjRWpWFDbzJyfzHFgsfxBiDHNpVBgWPRNuy" + "7VmMEkphxCFSV1y659Th4dkk6x6bJS5eQvbt8rzUYKQyd6ACgwQ4vXHtXKFUwP2kW3XULipnHJdZ7" ], [ - "TN7EQXMxKffzvHo54yHHu9R4ks9f5gWBW3MMVf5k72zAqrgVK9ys" + "tc1qdlapns4zkn03juf2k9xwwpct209suj6mgcd9gh" ], [ - "92dbrMEYzP5dD5UhQ6maNkCQ4GLG42BM4Gc6XKZzSSMSfosfkkcB" + "bt1psa5eptk29c4jc9yumeseat3a0l5e2fpmw635za2p4gpwdnthueysxga9je" ], [ - "J7VQxPxyzuWEkRstQWpCz2AgysEz1APgnWCEQrFvkN3umAnCrhQF" + "tb13w8c43lykfj3lvm9sgp6dsnfjla3d57cm83seykunf0ltxjc9lt2q4efm4d" ], [ - "tc1qymllj6c96v5qj2504y27ldtner6eh8ldx38t83" + "bcrt1rjqr2tdkm" ], [ - "bt1flep4g" + "bc10lyxwnxa70l270e6fcmxr4x7dtgu2yvy7gzkurwxy4zhdvgaqrrn6pfg2flyhqzy5t5se8yu3" ], [ - "tb13c553hwygcgj48qwmr9f8q0hgdcfklyaye5sxzcpcjnmxv4z506xs90tchn" + "TB1QFDFM763VXVSUNZHQLPWC0Q8FG5LJX6ZN" ], [ - "bcrt1tyddyu" + "bcrt1q60chha7wfwlau4kdr4mlvyeyc8mnnh9dhxk05e0hmrxcuhghefj36uwyha" ], [ - "bc10qssq2mknjqf0glwe2f3587wc4jysvs3f8s6chysae6hcl6fxzdm4wxyyscrl5k9f5qmnf05a" + "bc1gmk9yu" ], [ - "tb1q425lmgvxdgtyl2m6xuu2pc354y4fvgg8" + "tb1ly0q7p" ], [ - "bcrt1q9wp8e5d2u3u4g0pll0cy7smeeuqezdun9xl439n3p2gg4fvgfvk3hu52hj" + "bcrt1qdwttaw38uf42wxw40kwk3u8nguyTQH3hx6jmqp" ], [ - "bc1qrz5acazpue8vl4zsaxn8fxtmeuqmyjkq3" + "bc1qtsvlht6730n04f2mpaj5vv8hrledn5n5ug8c79" ], [ - "tb1qkeuglpgmnex9tv3fr7htzfrh3rwrk23r52rx9halxzmv9fr85lwq0fwhmp" + "tb1dclvmr" ], [ - "bcrt1qd0t2wrhl7s57z99rsyaekpq0dyjcQRSSmz80r4" + "bcrt1q3fqvctqu48wsvggrt09vj0yk2gzzcscdp4h98u" ], [ - "BC1QXLFDUCGX90T3E53PQCNKJ2PK25MSF3VLPMVY6T" + "bc1prklpq7tjcawg89cmwwqr3u5apwav36xa4zz56ady7crsllm6mpnqts7p86" ], [ - "tb1qmycg4zszgnk34vaurx3cu8wpvteg9h40yq6cp52gt26gjel03t3su3x3xu" + "tb1zkm58zyhxz3ffkfgsyprflg543slsl4c4" ], [ - "bcrt1q9hy58r4fnuxqzdqndpmq9pptc9nt2dw3rczf5e" + "bcrt1snzr5kaypnfhpnjanrhd20fhqcjxm3hfh7dw9fu" ], [ - "BC1PA7682NAY6JQSLUWAJYTC0ERWTMW7A4RPWLNTUS32LCXWLHVKKKTQ2UL8CG" + "2GgnYKqBGuA2Mm5GnrPsMTZR81xPhNtgMYoFUZngZGiobhCuUpCaTriUHRcgFreEekNdPAR17q8d" ], [ - "tb1z850dpxnwz2fzae5h2myatj4yvu6rq5xq" + "AZEah8d1EK362okRBS66e8SvdtYkrE8tsX" ], [ - "bcrt1sp525pzjsmpqvcrawjreww36e9keg876skjvpwt" + "gep8xr77FyPW6zYP15RiV9W8nL6w2HyHB16cUDakfyDceMA6ZzUdhJjk2LPuLYHnLkBqkRTTi6z" ], [ - "xcAvW5jurCpzSpLxBKEhCewCgwwuGhqJnC" + "2NDNP7GY59tTJPZTpbkprhM9SR99Nn5rUs7" ], [ - "2Cvv8yp9kXbQt8EKh6Yma95yJ1uwYF9YKXuVhGJyu3dHGVsb2AVpTC62TFACZZ3KDNrALxR2CVNs" + "2Csgzy2T287YAjeU5tFtt1nPshBZAUFQi4WtgaWyZGKSBNnKXHy2Tmxo8QK4Mfdds977ShcDWC5o" ], [ - "niUuL46hCuEVvkAzZKHvD746qbmLmzip9Pv3F6UZV14JxzEXBnTkVxCT4URapChJG6qAEgsZs6G" + "Kwjk3Vy6sdXMQDGWJzaWmqFxUNtWZCX1q4F4Kpt8jNNUoWJUUaTY" ], [ - "2UHHgGfiipzvB8Eumnmvq6SowvrMJimjT3NwwG1839XEiUfwtpSdkUrseNsQuagXv21ce7aZu6yo" + "Svj8kk98bAS9V4L2crmxakbhmnPm3cJ1tJ4Je4yVzDreU8eSTFURS1SPYv5oWEQD8Q9VBDvx5uF" ], [ - "8u9djKu4u6o3bsgeR4BKNnLK3akpo64FYzDAmA9239wKeshgF97" + "KNYsv6v9GtkGeD4WdQnBEJCrPKQm91PTxAbCfXr66LEd4JDmhPWC" ], [ - "TC1QPAARXSLVMXHVRR0474LZXQYZWLGFZYPSFVL9E4" + "2UJ2H2xvAeXmFKfQwMyDoSdQTTPFMNCT3SsoUafBWKzoGP3NsUK1buEgQZG38viyD53jgMdpqfT7" ], [ - "bt1pakek0n2267t9yaksxaczgr2syhv9y3xkx0wnsdwchfa6xkmjtvuqg3kgyr" + "6aLMfayKF4TW4ecn5SEc8FExpyJA2peKxYRGZhes6tQ4NTTzuGy" ], [ - "tb13h83rtwq62udrhwpn87uely7cyxcjrj0azz6a4r3n9s87x5uj98ys6ufp83" + "7VP4FmcebU2thJns9MnXde7LWfuqR5vMizrAuUoq2GcJjzTyA4RHFcPVdZL8PLg1SbpSFdJrvLXoY5" ], [ - "bcrt1rk5vw5qf2" + "tc1q5qdvt99uc92jyz663dtdpfpv6nr67ahmgwcpq2" ], [ - "bc10d3rmtg62h747en5j6fju5g5qyvsransrkty6ghh96pu647wumctejlsngh9pf26cysrys2x2" + "bt1peu3ppd7x796sjjenp09r8cs22rhylqm9lhggk72qp8q22vzft0wq2a0x6j" ], [ - "tb1qajuy2cdwqgmrzc7la85al5cwcq374tsp" + "tb1323z3lnz7dl3kd0nsuh6xy4he9almzl67anxgg3xdzkaxc9rwntlqdhdzd7" ], [ - "bcrt1q3udxvj6x20chqh723mn064mzz65yr56ef00xk8czvu3jnx04ydapzk02s5" + "bcrt1r2gc42sky" ], [ - "bc1qule2szwzyaq4qy0s3aa4mauucyqt6fewe" + "bc10fd889x4hd54tqu2ewg9t4hhft2wl7m6x50av4uswzw46xe6as0xmltfg7vrjfkvm459vld7w" ], [ - "tb1ql0qny5vg9gh5tyzke6dw36px5ulkrp24x53x0pl2t5lpwrtejw3s2seej2" + "TB1QZY7V0F2AT3308YGGNGN66ULJTCN3RY6F" ], [ - "bcrt17CAPP7" + "bcrt1qjg3cwht92znyw0l4r5rtctmls337nrc7g0ry9drjxmlecjd3atl3fake7c" ], [ - "bc1qtvm6davyf725wfedc2d5mrgfewqgcrce8gjrpl" + "bc1qmgf8xt8xkecl79k04mma3lz34gqep7hg4" ], [ - "tb1q5acjgtqrrw3an0dzavxxxzlex8k7aukjzjk9v2u4rmfdqxjphcyq7ge97e" + "TB1Q3F9WGNXE9ZMTTMDN5VKVKHYZ8Y0LCV72YV7V5LSXTJXEYHNHEHASLYL0TZ" ] ] diff --git a/src/test/data/key_io_valid.json b/src/test/data/key_io_valid.json index 5dee44c04b..c051f8b76b 100644 --- a/src/test/data/key_io_valid.json +++ b/src/test/data/key_io_valid.json @@ -1,71 +1,71 @@ [ [ - "1BShJZ8A5q53oJJfMJoEF1gfZCWdZqZwwD", - "76a914728d4cc27d19707b0197cfcd7c412d43287864b588ac", + "1FsSia9rv4NeEwvJ2GvXrX7LyxYspbN2mo", + "76a914a31c06bd463e3923bc1aadbde48b16976c08071788ac", { "chain": "main", "isPrivkey": false } ], [ - "3L1YkZjdeNSqaZcNKZFXQfyokx3zVYm7r6", - "a914c8f37c3cc21561296ad81f4bec6b5de10ebc185187", + "36j4NfKv6Akva9amjWrLG6MuSQym1GuEmm", + "a914373b819a068f32b7a6b38b6b38729647cfde01c287", { "chain": "main", "isPrivkey": false } ], [ - "mhJuoGLgnJC8gdBgBzEigsoyG4omQXejPT", - "76a91413a92d1998e081354d36c13ce0c9dc04b865d40a88ac", + "mzK2FFDEhxqHcmrJw1ysqFkVyhUULo45hZ", + "76a914ce28b26c57472737f5c3561a1761185bd8589a4388ac", { "chain": "test", "isPrivkey": false } ], [ - "2N5VpzKEuYvZJbmg6eUNGnfrrD1ir92FWGu", - "a91486648cc2faaf05660e72c04c7a837bcc3e986f1787", + "2NC2hEhe28ULKAJkW5MjZ3jtTMJdvXmByvK", + "a914ce0bba75891ff9ec60148d4bd4a09ee2dc5c933187", { "chain": "test", "isPrivkey": false } ], [ - "mtQueCtmAnP3E4aBHXCiFNEQAuPaLMuQNy", - "76a9148d74ecd86c845baf9c6d4484d2d00e731b79e34788ac", + "mww4LvqtTMKvmeQvizPz2EQv26xTneWrbg", + "76a914b4110ba93ac54afc14da3bdd19614774a2d55d2988ac", { "chain": "signet", "isPrivkey": false } ], [ - "2NEvWRTHjh89gV52fkperFtwzoFWQiQmiCh", - "a914edc895152c67ccff0ba620bcc373b789ec68266f87", + "2N1r7aC69VHeE7yQJPDLi9T1PYq4wnwvjuT", + "a9145e5a35ab44b3efaea5129ba22b88ba3e2976614587", { "chain": "signet", "isPrivkey": false } ], [ - "mngdx94qJFhSf7A7SAEgQSC9fQJuapujJp", - "76a9144e9dba545455a80ce94c343d1cac9dec62cbf22288ac", + "n4fajahJrAuKbN7uNsKjLjQkz9Qn5ewJXQ", + "76a914fdeca3b08e38af53d7c4c60e3ad208ce5066441088ac", { "chain": "regtest", "isPrivkey": false } ], [ - "2NBzRN3pV56k3JUvSHifaHyzjGHv7ZS9FZZ", - "a914cd9da5642451273e5b6d088854cc1fad4a8d442187", + "2MxFajLApXpYk4VodBSZSt7rw8y4ryABkfA", + "a91436e9f191e0b75036a77f65e2eaa4752443233fbe87", { "chain": "regtest", "isPrivkey": false } ], [ - "5KcrFZvJ2p4dM6QVUPu53cKXcCfozA1PJLHm1mNAxkDYhgThLu4", - "ed6c796e2f62377410766214f55aa81ac9a6590ad7ed57c509c983bf648409ac", + "5JuW2AMDYu4xVwRG9DZW18VbzQrGcd5RCgb99sS6ehJsNQXu5b9", + "8f8943bf956de595665c38ffff23827e17c10cdc1c27a028caae6c9810626198", { "chain": "main", "isCompressed": false, @@ -73,8 +73,8 @@ } ], [ - "L195WBrf2G3nCnun4CLxrb8XKk9LbCqH43THh4n4QrL5SzRzpq9j", - "74f76c106e38d20514a99a86e4fe3bb28319e7dd2ad21dbc170cbb516a5358fa", + "L5nJeqKmpHp4P7F8ZYyjwc5a7P4d8EabuGAzfGJk7yC1BJyzNaEd", + "ff778740f88ddcf102aeb81daee289c044c4a4571c4b6f287400f4b8e0b843f8", { "chain": "main", "isCompressed": true, @@ -82,8 +82,8 @@ } ], [ - "92z6HnMQR4tWqjfVA3UaUN5EuUMgoVMdCa5rZFYZfmgyD7wxYCw", - "b8511e1d74549e305517d48a1d394d1be2cfa5d0f3c0d83f9f450316ffa01276", + "92ZdE5HoLafywnTBbzPxbvRmp75pSfzvdU3XaZGh1cToipgdHVh", + "80c32d81e91bdea04cd7a3819b32275fc3298af4c7ec87eb0099527d041ced5c", { "chain": "test", "isCompressed": false, @@ -91,8 +91,8 @@ } ], [ - "cTPnaF52x4w4Tq6afPxRHux3wbYb86thS7S45A7r3oZc1AHTQ6Qm", - "ad68c48d337181da125de9061933ececcdf7d917631af7d34f7e38082bff9a11", + "cV83kKisF3RQSvXbUCm9ox3kaz5JjEUBWcx8tNydfGJcyeUxuH47", + "e0fcd4ce4e3d0e3de091f21415bb7cd011fac288c42020a879f28c2a4387df9b", { "chain": "test", "isCompressed": true, @@ -100,8 +100,8 @@ } ], [ - "924U35yFcYkxe2JXGmuhSRVaShGyhRDZx1ysPmw1sAHuszGMoxq", - "3e8dfaf78d4f02b11d0b645648a4f3080d71d0d068979c47f7255c9a29eee01d", + "92QuSnywrhsV7WPZChTgSQA23uSmj9MCEEno1eRBDG9sg8M29cX", + "6cf636ed8ac1bab033b64f66feaba65f70e684731e3f39105605968d3a963801", { "chain": "signet", "isCompressed": false, @@ -109,8 +109,8 @@ } ], [ - "cRy1qCf2LUesGPQagTkYwk2V3PyN2KCPKgxeg6k6KoJPzH7nrVjw", - "82d4187690d6b59bcffda27dae52f2ecb87313cfc0904e0b674a27d906a65fde", + "cND53Dhp8eCZqG2ghe8YhSCGesXZ8fE5PGD1khrqNvEi4RBoXhEK", + "12b5a10f3a11e708dc5412833c47ab7c368a21b9efe19293793ec879ce683018", { "chain": "signet", "isCompressed": true, @@ -118,8 +118,8 @@ } ], [ - "932NTcHK35Apf2C3K9Zv1ZdeZEmB1x7ZT2Ju3SjoEY6pUgUpT7H", - "bd7dba24df9e003e145ae9b4862776413a0bb6fa5b4e42753397f2d9536e58a9", + "91mn1wYKEB1zyof1VFm8tMtocZx1oBrKKRCu9GCpgZvPmBLEJjp", + "18a86e5a6c6977ddba0daca7fba5190f67ba56ccdc1b3f31308972236c2e4776", { "chain": "regtest", "isCompressed": false, @@ -127,8 +127,8 @@ } ], [ - "cNa75orYQ2oos52zCnMaS5PG6XbNZKc5LpGxTHacrxwWeX4WAK3E", - "1d87e3c58b08766fea03598380ec8d59f8c88d5392bf683ab1088bd4caf073ee", + "cPisAUdLvqqAr6MYtXnrWvgvyUAwuNyuTvZkDGw6miPhZdaiSDNH", + "3fdfec1371cedcdb8c190ca6ff8ad603f817edc0d93c2a687c7b36dd66e70f2a", { "chain": "regtest", "isCompressed": true, @@ -136,8 +136,8 @@ } ], [ - "bc1q5cuatynjmk4szh40mmunszfzh7zrc5xm9w8ccy", - "0014a639d59272ddab015eafdef9380922bf843c50db", + "bc1qvyq0cc6rahyvsazfdje0twl7ez82ndmuac2lhv", + "00146100fc6343edc8c874496cb2f5bbfec88ea9b77c", { "chain": "main", "isPrivkey": false, @@ -145,8 +145,8 @@ } ], [ - "bc1qkw7lz3ahms6e0ajv27mzh7g62tchjpmve4afc29u7w49tddydy2syv0087", - "0020b3bdf147b7dc3597f64c57b62bf91a52f179076ccd7a9c28bcf3aa55b5a46915", + "bc1qyucykdlhp62tezs0hagqury402qwhk589q80tqs5myh3rxq34nwqhkdhv7", + "002027304b37f70e94bc8a0fbf500e0c957a80ebda87280ef58214d92f119811acdc", { "chain": "main", "isPrivkey": false, @@ -154,8 +154,8 @@ } ], [ - "bc1p5rgvqejqh9dh37t9g94dd9cm8vtqns7dndgj423egwggsggcdzmsspvr7j", - "5120a0d0c06640b95b78f965416ad6971b3b1609c3cd9b512aaa39439088211868b7", + "bc1p83n3au0rjylefxq2nc2xh2y4jzz4pm6zxj4mw5pagdjjr2a9f36s6jjnnu", + "51203c671ef1e3913f94980a9e146ba895908550ef4234abb7503d436521aba54c75", { "chain": "main", "isPrivkey": false, @@ -163,8 +163,8 @@ } ], [ - "bc1zr4pq63udck", - "52021d42", + "bc1z2rksukkjr8", + "520250ed", { "chain": "main", "isPrivkey": false, @@ -172,8 +172,8 @@ } ], [ - "tb1q74fxwnvhsue0l8wremgq66xzvn48jlc5zthsvz", - "0014f552674d978732ff9dc3ced00d68c264ea797f14", + "tb1qcrh3yqn4nlleplcez2yndq2ry8h9ncg3qh7n54", + "0014c0ef1202759fff90ff19128936814321ee59e111", { "chain": "test", "isPrivkey": false, @@ -181,8 +181,8 @@ } ], [ - "tb1qpt7cqgq8ukv92dcraun9c3n0s3aswrt62vtv8nqmkfpa2tjfghesv9ln74", - "00200afd802007e598553703ef265c466f847b070d7a5316c3cc1bb243d52e4945f3", + "tb1quyl9ujpgwr2chdzdnnalen48sup245vdfnh2jxhsuq3yx80rrwlq5hqfe4", + "0020e13e5e482870d58bb44d9cfbfccea78702aad18d4ceea91af0e022431de31bbe", { "chain": "test", "isPrivkey": false, @@ -190,8 +190,8 @@ } ], [ - "tb1ph9v3e8nxct57hknlkhkz75p5pnxnkn05cw8ewpxu6tek56g29xgqydzfu7", - "5120b9591c9e66c2e9ebda7fb5ec2f50340ccd3b4df4c38f9704dcd2f36a690a2990", + "tb1p35n52jy6xkm4wd905tdy8qtagrn73kqdz73xe4zxpvq9t3fp50aqk3s6gz", + "51208d2745489a35b75734afa2da43817d40e7e8d80d17a26cd4460b0055c521a3fa", { "chain": "test", "isPrivkey": false, @@ -199,8 +199,8 @@ } ], [ - "tb1ray6e8gxfx49ers6c4c70l3c8lsxtcmlx", - "5310e93593a0c9354b91c358ae3cffc707fc", + "tb1rgv5m6uvdk3kc7qsuz0c79v88ycr5w4wa", + "53104329bd718db46d8f021c13f1e2b0e726", { "chain": "test", "isPrivkey": false, @@ -208,8 +208,8 @@ } ], [ - "tb1q0sqzfp3zj42u0perxr6jahhu4y03uw4dypk6sc", - "00147c002486229555c7872330f52edefca91f1e3aad", + "tb1q3vya2h5435jkugq2few7dmktlrwq4ejmfaw7kr", + "00148b09d55e958d256e200a4e5de6eecbf8dc0ae65b", { "chain": "signet", "isPrivkey": false, @@ -217,8 +217,8 @@ } ], [ - "tb1q9jv4qnawnuevqaeadn47gkq05ev78m4qg3zqejykdr9u0cm7yutq6gu5dj", - "00202c99504fae9f32c0773d6cebe4580fa659e3eea044440cc89668cbc7e37e2716", + "tb1qxkhrl2s6ttrclckldruea0e8anhrehffl8xv7t0pdyrzm08v2hyqy408nf", + "002035ae3faa1a5ac78fe2df68f99ebf27ecee3cdd29f9cccf2de169062dbcec55c8", { "chain": "signet", "isPrivkey": false, @@ -226,8 +226,8 @@ } ], [ - "tb1pxqf7d825wjtcftj7uep8w24jq3tz8vudfaqj20rns8ahqya56gcs92eqtu", - "51203013e69d54749784ae5ee642772ab2045623b38d4f41253c7381fb7013b4d231", + "tb1pae5um27ahn8n73pgexe3kcwlp8dhswpn684h2k2w6t9a7w3eq65qephd5y", + "5120ee69cdabddbccf3f4428c9b31b61df09db783833d1eb75594ed2cbdf3a3906a8", { "chain": "signet", "isPrivkey": false, @@ -235,8 +235,8 @@ } ], [ - "tb1rsrzkyvu2rt0dcgexajtazlw5nft4j7494ay396q6auw9375wxsrsgag884", - "532080c562338a1adedc2326ec97d17dd49a57597aa5af4912e81aef1c58fa8e3407", + "tb1rx9n9g37az8mu236e5jpxdt0m67y4fuq8rhs0ss3djnm0kscfrwvq0ntlyg", + "532031665447dd11f7c54759a48266adfbd78954f0071de0f8422d94f6fb43091b98", { "chain": "signet", "isPrivkey": false, @@ -244,8 +244,8 @@ } ], [ - "bcrt1qwf52dt9y2sv0f7fwkcpmtfjf74d4np2saeljt6", - "00147268a6aca45418f4f92eb603b5a649f55b598550", + "bcrt1qdavt4j2sd7dlhqsavtnfxvzppw6k7qy97tmnu9", + "00146f58bac9506f9bfb821d62e69330410bb56f0085", { "chain": "regtest", "isPrivkey": false, @@ -253,8 +253,8 @@ } ], [ - "bcrt1q0lma84unycxl4n96etffthqlf7y5axyp4fxf64kmhymvw8l6pwfs39futd", - "00207ff7d3d793260dfaccbacad295dc1f4f894e9881aa4c9d56dbb936c71ffa0b93", + "bcrt1qan8gntac7z7me2ejt4hpru42ad2f759fmy0m3ejvs98656znv7eqga4uhv", + "0020ecce89afb8f0bdbcab325d6e11f2aaeb549f50a9d91fb8e64c814faa685367b2", { "chain": "regtest", "isPrivkey": false, @@ -262,8 +262,8 @@ } ], [ - "bcrt1p3xat2ryucc2v0adrktqnavfzttvezrr27ngltsa2726p2ehvxz4se722v2", - "512089bab50c9cc614c7f5a3b2c13eb1225ad9910c6af4d1f5c3aaf2b41566ec30ab", + "bcrt1pfwxjqvtt4tcxrtdluukfmy2dv7xd2qzdfy6kajv5nwn4yam3wxkq3553uh", + "51204b8d20316baaf061adbfe72c9d914d678cd5004d49356ec9949ba752777171ac", { "chain": "regtest", "isPrivkey": false, @@ -271,8 +271,8 @@ } ], [ - "bcrt1saflydw6e26xhp29euhy5jke5jjqyywk3wvtc9ulgw9dvxyuqy9hdnxthyw755c7ldavy7u", - "6028ea7e46bb59568d70a8b9e5c9495b349480423ad1731782f3e8715ac31380216ed9997723bd4a63df", + "bcrt1sx6p8njlx7h9mc2agz4yg82dzne23050ncq72cneeecez2pst8mahn8xecsf8g6hzx94420", + "6028368279cbe6f5cbbc2ba8154883a9a29e5517d1f3c03cac4f39ce3225060b3efb799cd9c412746ae2", { "chain": "regtest", "isPrivkey": false, @@ -280,72 +280,72 @@ } ], [ - "16y3Q1XVRZqMR9T1XL1FkvNtD2E1bXBuYa", - "76a9144171ec673aeb9fcf42af6094a3c82207e3b9a78188ac", + "1FjL87pn8ky6Vbavd1ZHeChRXtoxwRGCRd", + "76a914a19331b7b2627e663e25a7b001e4c0dcc5e21bc788ac", { "chain": "main", "isPrivkey": false } ], [ - "3CmZZnAiHVQgiAKSakf864oJMxN2BP1eLC", - "a914798575fc1041b9440c4e63c28e57e597d00b7e4387", + "3BZECeAH8gSKkjrTx8PwMrNQBLG18yHpvf", + "a9146c382dcdf5b284760c8e3fead91f7422cd76aa8787", { "chain": "main", "isPrivkey": false } ], [ - "mtCB3SoBo7EYUv8j54kUubGY4x3aJPY8nk", - "76a9148b0c5f9ee714e0d1d24642ad63d9d5f398d9b56588ac", + "n4YNbYuFdPwFrxSP8sjHFbAhUbLMUiY9jE", + "76a914fc8f9851f3c1e4719cd0b8e4816dd4e88c72e52888ac", { "chain": "test", "isPrivkey": false } ], [ - "2N5ymzzKpx6EdUR4UdMZ7t9hcuwqtpHwgw5", - "a9148badb3c3b5c0d39f906f7618e0018b7eae4baf7387", + "2NAeQVZayzVFAtgeC3iYJsjpjWDmsDph71A", + "a914bedc797342c03fd7a346c4c7857ca03d467013b687", { "chain": "test", "isPrivkey": false } ], [ - "myXnpYbub28zgiJupDdZSWZtDbjcyfJVby", - "76a914c59ac57661b57daadd7c0caf7318c14f54c6c0fa88ac", + "mnCBpkNMJEJLehgdEkzSo2eioniyJMxLpZ", + "76a914493c455551e48a1423263b62b127b436106a685488ac", { "chain": "signet", "isPrivkey": false } ], [ - "2MtLg8jS5jSXm9evMzTtvpLjy26dBmjFEoT", - "a9140c0007e89cea625d3bf9543baa5a470bb7e5b67287", + "2N5sNHomeNJDZv67AcFx9ES7FBZY4jx9KDA", + "a9148a776a0f34d56b63e7c595f2b205dbe1c393617a87", { "chain": "signet", "isPrivkey": false } ], [ - "mzCyqdf2UNGdpgkD9NBgLcxdwXRg1i9buY", - "76a914cd04311bdd1ef9c5c24e41930e032aade82a863a88ac", + "mfhE6jAUwjUDNZhaX1PAsDTKfneQF2Nshc", + "76a91401f15a4cc063dae4f4d56b89bfbc8bcc9ae5387c88ac", { "chain": "regtest", "isPrivkey": false } ], [ - "2N3zGiwFku2vQjYnAqXv5Qu2ztfYRhh7tbF", - "a91475d56d75c88e704d6c72fbe84ac1505abf736b4087", + "2MxNm1VHyVU4RuP3u1c1v5aQLk2dQjwy1Qk", + "a91438456f7c076356abadcc67b92ad777eb20fb9f8887", { "chain": "regtest", "isPrivkey": false } ], [ - "5JUHCgyxNSHg64wwju72eNsG6ajqo4Z2fHHw9iLDLfh69rSiL7w", - "5644d06d88855dacf3192a31df8f4acd8e4c155c52a86d2c1fa48303f5cff053", + "5HsL2nZuEebU5nM3RxNVQD9GcAnvNMahqQskf4fkqHe54zwd14e", + "06e8649790a90615a46d22dd762e0c42615336745356c2e16147c0f3d46b40d5", { "chain": "main", "isCompressed": false, @@ -353,8 +353,8 @@ } ], [ - "L2kZaexG69VSriMe9T2m1jkS86iPe3xNbjcdfakRC1PHe7ay78Ji", - "a50ee94aefcabf5a5d7c85be5b3844dee03c5604861dbfc77fe388c91e5a30f8", + "KwuVvu6hsuEMHrfFWJQV64tRrWX3QzqHH18JuAHYqYV6dqBvNKxd", + "147804bf8a0dfff35939a611c7f5a60ac107f33f33d6059f273d2079ab1d90f2", { "chain": "main", "isCompressed": true, @@ -362,8 +362,8 @@ } ], [ - "927JwT1ViCr5TD2ZX8CsMNhg17dXmou5xu4y2KiH54zD7i34UJq", - "4502a54c0026b0150281d41f40860d1e23870c63cdc32645bbed688f2ee41f64", + "921M1RNxghFcsVGqAJksQVbSgx36Yz4u6vebfz1wDujNvgNt93B", + "3777b341c45e2a9b9bf6bfb71dc7d129f64f1b9406ed4f93ade8f56065f1b732", { "chain": "test", "isCompressed": false, @@ -371,8 +371,8 @@ } ], [ - "cTpGGNPVy2Eagawohbr4aGtRJzpLnjxGsGYh9DUcBM45f3KdKGF6", - "ba005a0cb39587aab00bd54c848b59e8adaed47403228567ddc739c2a344ff59", + "cNEnbfF2fcxmmCLWqMAaq6fxJvVkwMbyU3kCbpQznz4Z1j6TZDGb", + "1397b0d4a03e1ab2c54dd9af99ce1ecbfb90c80a58886da95e1181a55703d96b", { "chain": "test", "isCompressed": true, @@ -380,8 +380,8 @@ } ], [ - "932PLCLA19yPNqV67qwHBSGjxi82LVzWBF7josL9ab4Q1kxgPGF", - "bd8677e076eb39770bf7e9f9e8d3f2cf257effab9b4c220fd3439ccfc208c984", + "93BcpCMKPmFCuY8bqS4k3HFrhJ1Afxi4uSsEeJFvX86GYW7PC7W", + "d27d1b6ef55ca2e4d475b5276f2dbb85f7a6459dceeb89c67b776fd3bb974452", { "chain": "signet", "isCompressed": false, @@ -389,8 +389,8 @@ } ], [ - "cViUpEy8URSsLjUvxwL7cEuNgCVqM7oKBzd1ZPbA4khcQsQJuj1j", - "f2b36ade8393e29dc71e52cb75ba1109ba210203cd7d0a5ae881ad6846516203", + "cUtwbyxoL1owPxUafgH2meEpydeywjhnTYv2mJaFHHchz39AaEgy", + "da3ed4ef1647e1733ec076919cab6156077ed9532e7c365acc425747e198b3e1", { "chain": "signet", "isCompressed": true, @@ -398,8 +398,8 @@ } ], [ - "92jddDjJCVDmJtgvBHQ9i58PMash8kwsYhRdNo22ea2MYPXdCBE", - "977bf8686f1bcad28f86c4c14afbd33215746bd19203647bf7ff9c6fddc9cc87", + "927zPWny2SiNaUmHF5NnGQXQWDwbByfFzXGgu88j91ZoutSosvE", + "468e0284f230153db8687d8ec23db079a5b67d72ca04174b3867b13e4ea9945e", { "chain": "regtest", "isCompressed": false, @@ -407,8 +407,8 @@ } ], [ - "cVwAuMoUqRo399X7vXzuzQyPEvZJMXM8c82zHzRkFCxPCSGx8A6y", - "f93acbbce02b8cb9ddca3fad495441e324cc01eb640b0a7b4c9f0e31644c822a", + "cRez45VGSp5EXNqm89K3NJJPSKKapJg5Kbw3atxr2337x2gtgYed", + "798d87586cffbe8c545ab374454e403b1eb831501ebe89f3c3b02f3137bd7b46", { "chain": "regtest", "isCompressed": true, @@ -416,8 +416,8 @@ } ], [ - "bc1qz377zwe5awr68dnggengqx9vrjt05k98q3sw2n", - "0014147de13b34eb87a3b66846668018ac1c96fa58a7", + "bc1qhxt04s5xnpy0kxw4x99n5hpdf5pmtzpqs52es2", + "0014b996fac2869848fb19d5314b3a5c2d4d03b58820", { "chain": "main", "isPrivkey": false, @@ -425,8 +425,8 @@ } ], [ - "bc1qkmhskpdzg8kdkfywhu09kswwn9qan9vnkrf6mk40jvnr06s6sz5ssf82ya", - "0020b6ef0b05a241ecdb248ebf1e5b41ce9941d99593b0d3addaaf932637ea1a80a9", + "bc1qgc9ljrvdf2e0zg9rmmq86xklqwfys7r6wptjlacdgrcdc7sa6ggqu4rrxf", + "0020460bf90d8d4ab2f120a3dec07d1adf039248787a70572ff70d40f0dc7a1dd210", { "chain": "main", "isPrivkey": false, @@ -434,8 +434,8 @@ } ], [ - "bc1ps8cndas60cntk8x79sg9f5e5jz7x050z8agyugln2ukkks23rryqpejzkc", - "512081f136f61a7e26bb1cde2c1054d33490bc67d1e23f504e23f3572d6b415118c8", + "bc1pve739yap4uxjvfk0jrey69078u0gasm2nwvv483ec6zkzulgw9xqu4w9fd", + "5120667d1293a1af0d2626cf90f24d15fe3f1e8ec36a9b98ca9e39c6856173e8714c", { "chain": "main", "isPrivkey": false, @@ -443,8 +443,8 @@ } ], [ - "bc1zn4tsczge9l", - "52029d57", + "bc1zmjtqxkzs89", + "5202dc96", { "chain": "main", "isPrivkey": false, @@ -452,8 +452,8 @@ } ], [ - "tb1q6xw0wwd9n9d7ge87dryz4vm5vtahzhvz6yett3", - "0014d19cf739a5995be464fe68c82ab37462fb715d82", + "tb1ql4k5ayv7p7w0t0ge7tpntgpkgw53g2payxkszr", + "0014fd6d4e919e0f9cf5bd19f2c335a03643a914283d", { "chain": "test", "isPrivkey": false, @@ -461,8 +461,8 @@ } ], [ - "tb1qwn9zq9fu5uk35ykpgsc7rz4uawy4yh0r5m5er26768h5ur50su3qj6evun", - "002074ca20153ca72d1a12c14431e18abceb89525de3a6e991ab5ed1ef4e0e8f8722", + "tb1q9jx3x2qqdpempxrcfgyrkjd5fzeacaqj4ua7cs7fe2sfd2wdaueq5wn26y", + "00202c8d1328006873b098784a083b49b448b3dc7412af3bec43c9caa096a9cdef32", { "chain": "test", "isPrivkey": false, @@ -470,8 +470,8 @@ } ], [ - "tb1pmcdc5d8gr92rtemfsnhpeqanvs0nr82upn5dktxluz9n0qcv34lqxke0wq", - "5120de1b8a34e8195435e76984ee1c83b3641f319d5c0ce8db2cdfe08b37830c8d7e", + "tb1pdswckwd9ym5yf5eyzg8j4jjwnzla8y0tf9cp7aasfkek0u29sz9qfr00yf", + "51206c1d8b39a526e844d324120f2aca4e98bfd391eb49701f77b04db367f145808a", { "chain": "test", "isPrivkey": false, @@ -479,8 +479,8 @@ } ], [ - "tb1rgxjvtfzp0xczz6dlzqv8d5cmuykk4qkk", - "531041a4c5a44179b02169bf101876d31be1", + "tb1r0ecpfxg2udhtc556gqrpwwhk4sw3f0kc", + "53107e7014990ae36ebc529a4006173af6ac", { "chain": "test", "isPrivkey": false, @@ -488,8 +488,8 @@ } ], [ - "tb1qa9dlyt6fydestul4y4wh72yshh044w32np8etk", - "0014e95bf22f49237305f3f5255d7f2890bddf5aba2a", + "tb1q6mwf89hnqhlu8txjgjfs4s7p93ugffn3k062ll", + "0014d6dc9396f305ffc3acd244930ac3c12c7884a671", { "chain": "signet", "isPrivkey": false, @@ -497,8 +497,8 @@ } ], [ - "tb1qu4p26n0033720xm0rjgkds5ehdwf039k2fgv75um5krrvfhrrj7qckl9r2", - "0020e542ad4def8c7ca79b6f1c9166c299bb5c97c4b65250cf539ba5863626e31cbc", + "tb1qafrjalu4d73dql0czau9j6z422434kef235mzljf48ckd5xz3sys09jm97", + "0020ea472eff956fa2d07df8177859685552ab1adb295469b17e49a9f166d0c28c09", { "chain": "signet", "isPrivkey": false, @@ -506,8 +506,8 @@ } ], [ - "tb1pjyukm4n4flwd0ey3lrl06c9kalr60ggmlkcxq2rhhxmy4lvkmkpqexdzqy", - "512091396dd6754fdcd7e491f8fefd60b6efc7a7a11bfdb0602877b9b64afd96dd82", + "tb1pwst9qszjrhuv2e7as0flcq9gm698v6gdxzz9e87p07s8rssdx3zqklm3vf", + "512074165040521df8c567dd83d3fc00a8de8a76690d30845c9fc17fa071c20d3444", { "chain": "signet", "isPrivkey": false, @@ -515,8 +515,8 @@ } ], [ - "tb1r4k75s5syvewsvxufdc3xfhf4tw4u30alw39xny3dnxrl6hau7systymfdv", - "5320adbd485204665d061b896e2264dd355babc8bfbf744a69922d9987fd5fbcf409", + "tb1r3ss76jtsuxe8c8c8lxsehnpak55ylrgr345pww076l536ahjr6jsydamx3", + "53208c21ed4970e1b27c1f07f9a19bcc3db5284f8d038d681739fed7e91d76f21ea5", { "chain": "signet", "isPrivkey": false, @@ -524,8 +524,8 @@ } ], [ - "bcrt1qnk3tdwwj47ppc4pqmxkjdusegedn9ru5gvccwa", - "00149da2b6b9d2af821c5420d9ad26f219465b328f94", + "bcrt1q65nhlm4hf2ptg3t264al57p7wjxj2c3s6kyt83", + "0014d5277feeb74a82b4456ad57bfa783e748d256230", { "chain": "regtest", "isPrivkey": false, @@ -533,8 +533,8 @@ } ], [ - "bcrt1qz7prfshfkwsxuk72pt6mzr6uumq4qllxe4vmwqt89tat48d362yqlykk6a", - "0020178234c2e9b3a06e5bca0af5b10f5ce6c1507fe6cd59b701672afaba9db1d288", + "bcrt1qawvc90lpytw3z3k9etdx54l0exq5f5sqfzu5e45kjnl6slwayeeqx2dyac", + "0020eb9982bfe122dd1146c5cada6a57efc98144d20048b94cd69694ffa87ddd2672", { "chain": "regtest", "isPrivkey": false, @@ -542,8 +542,8 @@ } ], [ - "bcrt1pumee3wj80xvyr7wjmj7zsk26x5pn095aegy862yhx6f2j9sgc9hq6cj4cm", - "5120e6f398ba47799841f9d2dcbc28595a350337969dca087d28973692a91608c16e", + "bcrt1p39a4s4vdcw9kqa8w2t0rp7aj8kfxyw7mce5sk5d70x6wnnmpvt7skf2kxy", + "5120897b58558dc38b6074ee52de30fbb23d92623bdbc6690b51be79b4e9cf6162fd", { "chain": "regtest", "isPrivkey": false, @@ -551,8 +551,8 @@ } ], [ - "bcrt1szqz8hj64d2hhc6nt65v09jxal66pgff2xpcp9kj648qkk8kjzxelsts4dktd799g47uase", - "602810047bcb556aaf7c6a6bd518f2c8ddfeb414252a307012da5aa9c16b1ed211b3f82e156d96df14a8", + "bcrt1s489d9fhmyel0vzfqsrmew4x7r80asuqesm5hgqacy35daflcyufh3j8cgdtflvt99ph05m", + "6028a9cad2a6fb267ef6092080f79754de19dfd8701986e97403b82468dea7f8271378c8f843569fb165", { "chain": "regtest", "isPrivkey": false, @@ -560,48 +560,48 @@ } ], [ - "12agZTajtRE3STSchwWNWnrm467zzTQ916", - "76a9141156e00f70061e5faba8b71593a8c7554b47090c88ac", + "1G9A9j6W8TLuh6dEeVwWeyibK1Uc5MfVFV", + "76a914a614da54daacdb8861f451a0b7e3c27cdf8a099e88ac", { "chain": "main", "isPrivkey": false } ], [ - "3NXqB6iZiPYbKruNT3d9xNBTmtb73xMvvf", - "a914e49decc9e5d97e0547d3642f3a4795b13ae62bca87", + "33GA3ZXbw5o5HeUrBEaqkWXFYYZmdxGRRP", + "a914113ca1afeb49ff3abf176ffa19c2a2b4df19712a87", { "chain": "main", "isPrivkey": false } ], [ - "mjgt4BoCYxjzWvJFoh68x7cj5GeaKDYhyx", - "76a9142dc11fc7b8072f733f690ffb0591c00f4062295c88ac", + "mwgS2HRbjyfYxFnR1nF9VKLvmdgMfFBmGq", + "76a914b14ce7070b53cb0e4b5b5f6e253e876990aeca2e88ac", { "chain": "test", "isPrivkey": false } ], [ - "2NCT6FdQ5MxorHgnFxLeHyGwTGRdkHcrJDH", - "a914d2a8ec992b0894a0d9391ca5d9c45c388c41be7e87", + "2MwBVrJQ76BdaGD76CTmou8cZzQYLpe4NqU", + "a9142b2c149cde619eae3d7fe995243b76a3417541aa87", { "chain": "test", "isPrivkey": false } ], [ - "mpomiA7wqDnMcxaNLC23eBuXAb4U6H4ZqW", - "76a91465e75e340415ed297c58d6a14d3c17ceeaa17bbd88ac", + "mfnJ8tEkqKNFE5YaHTXFxyHk2mnDK2fvDh", + "76a91402e6cd77e649ad8b281271f158fc964ca3f66cb088ac", { "chain": "signet", "isPrivkey": false } ], [ - "2N1pGAA5uatbU2PKvMA9BnJmHcK6yHfMiZa", - "a9145e008b6cc232164570befc23d216060bf4ea793b87", + "2My83D67ir7K8PPzeT6mE2oth3ZwNTVRS9F", + "a9144074d84d32ff62da7b1b3c61925b934bfeb34b0587", { "chain": "signet", "isPrivkey": false diff --git a/src/test/dbwrapper_tests.cpp b/src/test/dbwrapper_tests.cpp index fc89fe1450..ab4c587c46 100644 --- a/src/test/dbwrapper_tests.cpp +++ b/src/test/dbwrapper_tests.cpp @@ -321,7 +321,7 @@ struct StringContentsSerializer { // Used to make two serialized objects the same while letting them have different lengths // This is a terrible idea std::string str; - StringContentsSerializer() {} + StringContentsSerializer() = default; explicit StringContentsSerializer(const std::string& inp) : str(inp) {} StringContentsSerializer& operator+=(const std::string& s) { diff --git a/src/test/denialofservice_tests.cpp b/src/test/denialofservice_tests.cpp index a17cc87730..66c4605afa 100644 --- a/src/test/denialofservice_tests.cpp +++ b/src/test/denialofservice_tests.cpp @@ -4,7 +4,6 @@ // Unit tests for denial-of-service detection/prevention code -#include <arith_uint256.h> #include <banman.h> #include <chainparams.h> #include <net.h> @@ -16,7 +15,7 @@ #include <serialize.h> #include <test/util/net.h> #include <test/util/setup_common.h> -#include <txorphanage.h> +#include <timedata.h> #include <util/string.h> #include <util/system.h> #include <util/time.h> @@ -34,10 +33,6 @@ static CService ip(uint32_t i) return CService(CNetAddr(s), Params().GetDefaultPort()); } -static NodeId id = 0; - -void UpdateLastBlockAnnounceTime(NodeId node, int64_t time_in_seconds); - BOOST_FIXTURE_TEST_SUITE(denialofservice_tests, TestingSetup) // Test eviction of an outbound peer whose chain never advances @@ -50,17 +45,15 @@ BOOST_FIXTURE_TEST_SUITE(denialofservice_tests, TestingSetup) // work. BOOST_AUTO_TEST_CASE(outbound_slow_chain_eviction) { - const CChainParams& chainparams = Params(); - auto connman = std::make_unique<CConnman>(0x1337, 0x1337, *m_node.addrman); + 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(chainparams, *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, @@ -69,10 +62,16 @@ 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), + /*permission_flags=*/NetPermissionFlags::None, + /*version=*/PROTOCOL_VERSION, + /*relay_txs=*/true); + TestOnlyResetTimeData(); // This test requires that we have a chain with non-zero work. { @@ -84,7 +83,7 @@ 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); @@ -97,7 +96,7 @@ BOOST_AUTO_TEST_CASE(outbound_slow_chain_eviction) 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); @@ -107,18 +106,17 @@ BOOST_AUTO_TEST_CASE(outbound_slow_chain_eviction) 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(std::vector<CNode*>& vNodes, PeerManager& peerLogic, ConnmanTestMsg& connman, ConnectionType connType) +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, @@ -130,7 +128,7 @@ static void AddRandomOutboundPeer(std::vector<CNode*>& vNodes, PeerManager& peer 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); @@ -138,9 +136,9 @@ static void AddRandomOutboundPeer(std::vector<CNode*>& vNodes, PeerManager& peer BOOST_AUTO_TEST_CASE(stale_tip_peer_management) { - const CChainParams& chainparams = Params(); - auto connman = std::make_unique<ConnmanTestMsg>(0x1337, 0x1337, *m_node.addrman); - auto peerLogic = PeerManager::make(chainparams, *connman, *m_node.addrman, nullptr, + NodeId id{0}; + auto connman = std::make_unique<ConnmanTestMsg>(0x1337, 0x1337, *m_node.addrman, *m_node.netgroupman); + auto peerLogic = PeerManager::make(*connman, *m_node.addrman, nullptr, *m_node.chainman, *m_node.mempool, false); constexpr int max_outbound_full_relay = MAX_OUTBOUND_FULL_RELAY_CONNECTIONS; @@ -151,13 +149,13 @@ BOOST_AUTO_TEST_CASE(stale_tip_peer_management) const auto time_init{GetTime<std::chrono::seconds>()}; SetMockTime(time_init); - const auto time_later{time_init + 3 * std::chrono::seconds{chainparams.GetConsensus().nPowTargetSpacing} + 1s}; + const auto time_later{time_init + 3 * std::chrono::seconds{m_node.chainman->GetConsensus().nPowTargetSpacing} + 1s}; connman->Init(options); std::vector<CNode *> vNodes; // Mock some outbound peers for (int i = 0; i < max_outbound_full_relay; ++i) { - AddRandomOutboundPeer(vNodes, *peerLogic, *connman, ConnectionType::OUTBOUND_FULL_RELAY); + AddRandomOutboundPeer(id, vNodes, *peerLogic, *connman, ConnectionType::OUTBOUND_FULL_RELAY); } peerLogic->CheckForStaleTipAndEvictPeers(); @@ -183,7 +181,7 @@ BOOST_AUTO_TEST_CASE(stale_tip_peer_management) // on the next check (since we're mocking the time to be in the future, the // required time connected check should be satisfied). SetMockTime(time_init); - AddRandomOutboundPeer(vNodes, *peerLogic, *connman, ConnectionType::OUTBOUND_FULL_RELAY); + AddRandomOutboundPeer(id, vNodes, *peerLogic, *connman, ConnectionType::OUTBOUND_FULL_RELAY); SetMockTime(time_later); peerLogic->CheckForStaleTipAndEvictPeers(); @@ -197,7 +195,7 @@ BOOST_AUTO_TEST_CASE(stale_tip_peer_management) // Update the last announced block time for the last // peer, and check that the next newest node gets evicted. - UpdateLastBlockAnnounceTime(vNodes.back()->GetId(), GetTime()); + peerLogic->UpdateLastBlockAnnounceTime(vNodes.back()->GetId(), GetTime()); peerLogic->CheckForStaleTipAndEvictPeers(); for (int i = 0; i < max_outbound_full_relay - 1; ++i) { @@ -215,9 +213,9 @@ BOOST_AUTO_TEST_CASE(stale_tip_peer_management) BOOST_AUTO_TEST_CASE(block_relay_only_eviction) { - const CChainParams& chainparams = Params(); - auto connman = std::make_unique<ConnmanTestMsg>(0x1337, 0x1337, *m_node.addrman); - auto peerLogic = PeerManager::make(chainparams, *connman, *m_node.addrman, nullptr, + NodeId id{0}; + auto connman = std::make_unique<ConnmanTestMsg>(0x1337, 0x1337, *m_node.addrman, *m_node.netgroupman); + auto peerLogic = PeerManager::make(*connman, *m_node.addrman, nullptr, *m_node.chainman, *m_node.mempool, false); constexpr int max_outbound_block_relay{MAX_BLOCK_RELAY_ONLY_CONNECTIONS}; @@ -232,7 +230,7 @@ BOOST_AUTO_TEST_CASE(block_relay_only_eviction) // Add block-relay-only peers up to the limit for (int i = 0; i < max_outbound_block_relay; ++i) { - AddRandomOutboundPeer(vNodes, *peerLogic, *connman, ConnectionType::BLOCK_RELAY); + AddRandomOutboundPeer(id, vNodes, *peerLogic, *connman, ConnectionType::BLOCK_RELAY); } peerLogic->CheckForStaleTipAndEvictPeers(); @@ -241,7 +239,7 @@ BOOST_AUTO_TEST_CASE(block_relay_only_eviction) } // Add an extra block-relay-only peer breaking the limit (mocks logic in ThreadOpenConnections) - AddRandomOutboundPeer(vNodes, *peerLogic, *connman, ConnectionType::BLOCK_RELAY); + AddRandomOutboundPeer(id, vNodes, *peerLogic, *connman, ConnectionType::BLOCK_RELAY); peerLogic->CheckForStaleTipAndEvictPeers(); // The extra peer should only get marked for eviction after MINIMUM_CONNECT_TIME @@ -277,10 +275,9 @@ BOOST_AUTO_TEST_CASE(block_relay_only_eviction) BOOST_AUTO_TEST_CASE(peer_discouragement) { - const CChainParams& chainparams = Params(); 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); - auto peerLogic = PeerManager::make(chainparams, *connman, *m_node.addrman, banman.get(), + auto connman = std::make_unique<ConnmanTestMsg>(0x1337, 0x1337, *m_node.addrman, *m_node.netgroupman); + auto peerLogic = PeerManager::make(*connman, *m_node.addrman, banman.get(), *m_node.chainman, *m_node.mempool, false); CNetAddr tor_netaddr; @@ -297,8 +294,8 @@ BOOST_AUTO_TEST_CASE(peer_discouragement) std::array<CNode*, 3> nodes; banman->ClearBanned(); + NodeId id{0}; nodes[0] = new CNode{id++, - NODE_NETWORK, /*sock=*/nullptr, addr[0], /*nKeyedNetGroupIn=*/0, @@ -308,10 +305,10 @@ 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 + peerLogic->UnitTestMisbehaving(nodes[0]->GetId(), DISCOURAGEMENT_THRESHOLD); // Should be discouraged { LOCK(nodes[0]->cs_sendProcessing); BOOST_CHECK(peerLogic->SendMessages(nodes[0])); @@ -321,7 +318,6 @@ BOOST_AUTO_TEST_CASE(peer_discouragement) BOOST_CHECK(!banman->IsDiscouraged(other_addr)); // Different address, not discouraged nodes[1] = new CNode{id++, - NODE_NETWORK, /*sock=*/nullptr, addr[1], /*nKeyedNetGroupIn=*/1, @@ -331,10 +327,10 @@ 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=*/""); + peerLogic->UnitTestMisbehaving(nodes[1]->GetId(), DISCOURAGEMENT_THRESHOLD - 1); { LOCK(nodes[1]->cs_sendProcessing); BOOST_CHECK(peerLogic->SendMessages(nodes[1])); @@ -345,7 +341,7 @@ BOOST_AUTO_TEST_CASE(peer_discouragement) // [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 + peerLogic->UnitTestMisbehaving(nodes[1]->GetId(), 1); // [1] reaches discouragement threshold { LOCK(nodes[1]->cs_sendProcessing); BOOST_CHECK(peerLogic->SendMessages(nodes[1])); @@ -359,7 +355,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, @@ -369,10 +364,10 @@ 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=*/""); + peerLogic->UnitTestMisbehaving(nodes[2]->GetId(), DISCOURAGEMENT_THRESHOLD); { LOCK(nodes[2]->cs_sendProcessing); BOOST_CHECK(peerLogic->SendMessages(nodes[2])); @@ -392,10 +387,9 @@ BOOST_AUTO_TEST_CASE(peer_discouragement) BOOST_AUTO_TEST_CASE(DoS_bantime) { - const CChainParams& chainparams = Params(); 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); - auto peerLogic = PeerManager::make(chainparams, *connman, *m_node.addrman, banman.get(), + auto connman = std::make_unique<CConnman>(0x1337, 0x1337, *m_node.addrman, *m_node.netgroupman); + auto peerLogic = PeerManager::make(*connman, *m_node.addrman, banman.get(), *m_node.chainman, *m_node.mempool, false); banman->ClearBanned(); @@ -403,8 +397,8 @@ BOOST_AUTO_TEST_CASE(DoS_bantime) SetMockTime(nStartTime); // Overrides future calls to GetTime() CAddress addr(ip(0xa0b0c001), NODE_NONE); + NodeId id{0}; CNode dummyNode{id++, - NODE_NETWORK, /*sock=*/nullptr, addr, /*nKeyedNetGroupIn=*/4, @@ -414,10 +408,10 @@ 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=*/""); + peerLogic->UnitTestMisbehaving(dummyNode.GetId(), DISCOURAGEMENT_THRESHOLD); { LOCK(dummyNode.cs_sendProcessing); BOOST_CHECK(peerLogic->SendMessages(&dummyNode)); @@ -427,121 +421,4 @@ BOOST_AUTO_TEST_CASE(DoS_bantime) peerLogic->FinalizeNode(dummyNode); } -class TxOrphanageTest : public TxOrphanage -{ -public: - inline size_t CountOrphans() const EXCLUSIVE_LOCKS_REQUIRED(g_cs_orphans) - { - return m_orphans.size(); - } - - CTransactionRef RandomOrphan() EXCLUSIVE_LOCKS_REQUIRED(g_cs_orphans) - { - std::map<uint256, OrphanTx>::iterator it; - it = m_orphans.lower_bound(InsecureRand256()); - if (it == m_orphans.end()) - it = m_orphans.begin(); - return it->second.tx; - } -}; - -static void MakeNewKeyWithFastRandomContext(CKey& key) -{ - std::vector<unsigned char> keydata; - keydata = g_insecure_rand_ctx.randbytes(32); - key.Set(keydata.data(), keydata.data() + keydata.size(), /*fCompressedIn=*/true); - assert(key.IsValid()); -} - -BOOST_AUTO_TEST_CASE(DoS_mapOrphans) -{ - // This test had non-deterministic coverage due to - // randomly selected seeds. - // This seed is chosen so that all branches of the function - // ecdsa_signature_parse_der_lax are executed during this test. - // Specifically branches that run only when an ECDSA - // signature's R and S values have leading zeros. - g_insecure_rand_ctx = FastRandomContext(ArithToUint256(arith_uint256(33))); - - TxOrphanageTest orphanage; - CKey key; - MakeNewKeyWithFastRandomContext(key); - FillableSigningProvider keystore; - BOOST_CHECK(keystore.AddKey(key)); - - LOCK(g_cs_orphans); - - // 50 orphan transactions: - for (int i = 0; i < 50; i++) - { - CMutableTransaction tx; - tx.vin.resize(1); - tx.vin[0].prevout.n = 0; - tx.vin[0].prevout.hash = InsecureRand256(); - tx.vin[0].scriptSig << OP_1; - tx.vout.resize(1); - tx.vout[0].nValue = 1*CENT; - tx.vout[0].scriptPubKey = GetScriptForDestination(PKHash(key.GetPubKey())); - - orphanage.AddTx(MakeTransactionRef(tx), i); - } - - // ... and 50 that depend on other orphans: - for (int i = 0; i < 50; i++) - { - CTransactionRef txPrev = orphanage.RandomOrphan(); - - CMutableTransaction tx; - tx.vin.resize(1); - tx.vin[0].prevout.n = 0; - tx.vin[0].prevout.hash = txPrev->GetHash(); - tx.vout.resize(1); - tx.vout[0].nValue = 1*CENT; - tx.vout[0].scriptPubKey = GetScriptForDestination(PKHash(key.GetPubKey())); - BOOST_CHECK(SignSignature(keystore, *txPrev, tx, 0, SIGHASH_ALL)); - - orphanage.AddTx(MakeTransactionRef(tx), i); - } - - // This really-big orphan should be ignored: - for (int i = 0; i < 10; i++) - { - CTransactionRef txPrev = orphanage.RandomOrphan(); - - CMutableTransaction tx; - tx.vout.resize(1); - tx.vout[0].nValue = 1*CENT; - tx.vout[0].scriptPubKey = GetScriptForDestination(PKHash(key.GetPubKey())); - tx.vin.resize(2777); - for (unsigned int j = 0; j < tx.vin.size(); j++) - { - tx.vin[j].prevout.n = j; - tx.vin[j].prevout.hash = txPrev->GetHash(); - } - BOOST_CHECK(SignSignature(keystore, *txPrev, tx, 0, SIGHASH_ALL)); - // Re-use same signature for other inputs - // (they don't have to be valid for this test) - for (unsigned int j = 1; j < tx.vin.size(); j++) - tx.vin[j].scriptSig = tx.vin[0].scriptSig; - - BOOST_CHECK(!orphanage.AddTx(MakeTransactionRef(tx), i)); - } - - // Test EraseOrphansFor: - for (NodeId i = 0; i < 3; i++) - { - size_t sizeBefore = orphanage.CountOrphans(); - orphanage.EraseForPeer(i); - BOOST_CHECK(orphanage.CountOrphans() < sizeBefore); - } - - // Test LimitOrphanTxSize() function: - orphanage.LimitOrphans(40); - BOOST_CHECK(orphanage.CountOrphans() <= 40); - orphanage.LimitOrphans(10); - BOOST_CHECK(orphanage.CountOrphans() <= 10); - orphanage.LimitOrphans(0); - BOOST_CHECK(orphanage.CountOrphans() == 0); -} - BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/descriptor_tests.cpp b/src/test/descriptor_tests.cpp index 55a9a78159..d0ad037228 100644 --- a/src/test/descriptor_tests.cpp +++ b/src/test/descriptor_tests.cpp @@ -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; @@ -311,10 +309,10 @@ void DoCheck(const std::string& prv, const std::string& pub, const std::string& spend.vout.resize(1); std::vector<CTxOut> utxos(1); PrecomputedTransactionData txdata; - txdata.Init(spend, std::move(utxos), /* force */ true); - MutableTransactionSignatureCreator creator(&spend, 0, CAmount{0}, &txdata, SIGHASH_DEFAULT); + 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)); } @@ -381,17 +379,17 @@ BOOST_AUTO_TEST_CASE(descriptor_test) 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); - CheckUnparsable("sh(wpkh(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY2))", "sh(wpkh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5))", "Pubkey '03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5' is invalid"); // Invalid pubkey - CheckUnparsable("pkh(deadbeef/1/2'/3/4']L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)", "pkh(deadbeef/1/2'/3/4']03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", "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)", "Multiple ']' characters found for a single pubkey"); // Multiple end brackets in key origin + 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); - CheckUnparsable("wpkh(5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss)", "wpkh(04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235)", "Uncompressed keys are not allowed"); // No uncompressed keys in witness - CheckUnparsable("wsh(pk(5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss))", "wsh(pk(04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235))", "Uncompressed keys are not allowed"); // No uncompressed keys in witness - CheckUnparsable("sh(wpkh(5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss))", "sh(wpkh(04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235))", "Uncompressed keys are not allowed"); // No uncompressed keys in witness + 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); @@ -415,9 +413,9 @@ BOOST_AUTO_TEST_CASE(descriptor_test) // 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},{}}); - CheckUnparsable("combo([012345678]xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc)", "combo([012345678]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL)", "Fingerprint is not 4 bytes (9 characters instead of 8 characters)"); // Too long key fingerprint - CheckUnparsable("pkh(xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U/2147483648)", "pkh(xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/2147483648)", "Key path value 2147483648 is out of range"); // BIP 32 path element overflow - CheckUnparsable("pkh(xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U/1aa)", "pkh(xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/1aa)", "Key path value '1aa' is not a valid uint32"); // Path is not valid uint + 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}}); // Multisig constructions @@ -430,11 +428,11 @@ BOOST_AUTO_TEST_CASE(descriptor_test) 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); 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/*'))", "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/*'))", "Fingerprint 'aaagaaaa' is not hex"); // Non hex fingerprint - CheckUnparsable("wsh(multi(2,[aaaaaaaa],xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt/1/2/*,xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi/10/20/30/40/*'))", "wsh(multi(2,[aaaaaaaa],xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/1/2/*,xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8/10/20/30/40/*'))", "No key provided"); // No public key with origin - CheckUnparsable("wsh(multi(2,[aaaaaaa]xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U/2147483647'/0,xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt/1/2/*,xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi/10/20/30/40/*'))", "wsh(multi(2,[aaaaaaa]xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/2147483647'/0,xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/1/2/*,xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8/10/20/30/40/*'))", "Fingerprint is not 4 bytes (7 characters instead of 8 characters)"); // Too short fingerprint - CheckUnparsable("wsh(multi(2,[aaaaaaaaa]xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U/2147483647'/0,xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt/1/2/*,xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi/10/20/30/40/*'))", "wsh(multi(2,[aaaaaaaaa]xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/2147483647'/0,xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/1/2/*,xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8/10/20/30/40/*'))", "Fingerprint is not 4 bytes (9 characters instead of 8 characters)"); // Too long fingerprint + 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 + CheckUnparsable("wsh(multi(2,[aaaaaaaa],xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt/1/2/*,xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi/10/20/30/40/*'))", "wsh(multi(2,[aaaaaaaa],xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/1/2/*,xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8/10/20/30/40/*'))", "Multi: No key provided"); // No public key with origin + CheckUnparsable("wsh(multi(2,[aaaaaaa]xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U/2147483647'/0,xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt/1/2/*,xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi/10/20/30/40/*'))", "wsh(multi(2,[aaaaaaa]xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/2147483647'/0,xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/1/2/*,xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8/10/20/30/40/*'))", "Multi: Fingerprint is not 4 bytes (7 characters instead of 8 characters)"); // Too short fingerprint + CheckUnparsable("wsh(multi(2,[aaaaaaaaa]xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U/2147483647'/0,xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt/1/2/*,xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi/10/20/30/40/*'))", "wsh(multi(2,[aaaaaaaaa]xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/2147483647'/0,xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/1/2/*,xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8/10/20/30/40/*'))", "Multi: Fingerprint is not 4 bytes (9 characters instead of 8 characters)"); // Too long fingerprint CheckUnparsable("multi(a,L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1,5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss)", "multi(a,03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd,04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235)", "Multi threshold 'a' is not valid"); // Invalid threshold CheckUnparsable("multi(0,L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1,5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss)", "multi(0,03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd,04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235)", "Multisig threshold cannot be 0, must be at least 1"); // Threshold of 0 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 @@ -466,6 +464,31 @@ 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(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)", + "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 +509,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,xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/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(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)))))", 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/fs_tests.cpp b/src/test/fs_tests.cpp index 5875f0218f..d2554483d4 100644 --- a/src/test/fs_tests.cpp +++ b/src/test/fs_tests.cpp @@ -46,8 +46,8 @@ BOOST_AUTO_TEST_CASE(fsbridge_fstream) { fs::path tmpfolder = m_args.GetDataDirBase(); // tmpfile1 should be the same as tmpfile2 - fs::path tmpfile1 = tmpfolder / "fs_tests_₿_🏃"; - fs::path tmpfile2 = tmpfolder / "fs_tests_₿_🏃"; + fs::path tmpfile1 = tmpfolder / fs::u8path("fs_tests_₿_🏃"); + fs::path tmpfile2 = tmpfolder / fs::u8path("fs_tests_₿_🏃"); { std::ofstream file{tmpfile1}; file << "bitcoin"; @@ -86,7 +86,7 @@ BOOST_AUTO_TEST_CASE(fsbridge_fstream) } { // Join an absolute path and a relative path. - fs::path p = fsbridge::AbsPathJoin(tmpfolder, "fs_tests_₿_🏃"); + fs::path p = fsbridge::AbsPathJoin(tmpfolder, fs::u8path("fs_tests_₿_🏃")); BOOST_CHECK(p.is_absolute()); BOOST_CHECK_EQUAL(tmpfile1, p); } diff --git a/src/test/fuzz/addrman.cpp b/src/test/fuzz/addrman.cpp index ba917dec2a..7668940cbc 100644 --- a/src/test/fuzz/addrman.cpp +++ b/src/test/fuzz/addrman.cpp @@ -37,11 +37,19 @@ void initialize_addrman() g_setup = testing_setup.get(); } +[[nodiscard]] inline NetGroupManager ConsumeNetGroupManager(FuzzedDataProvider& fuzzed_data_provider) noexcept +{ + std::vector<bool> asmap = ConsumeRandomLengthBitVector(fuzzed_data_provider); + if (!SanityCheckASMap(asmap, 128)) asmap.clear(); + return NetGroupManager(asmap); +} + FUZZ_TARGET_INIT(data_stream_addr_man, initialize_addrman) { FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()}; CDataStream data_stream = ConsumeDataStream(fuzzed_data_provider); - AddrMan addr_man{/*asmap=*/std::vector<bool>(), /*deterministic=*/false, GetCheckRatio()}; + NetGroupManager netgroupman{ConsumeNetGroupManager(fuzzed_data_provider)}; + AddrMan addr_man(netgroupman, /*deterministic=*/false, GetCheckRatio()); try { ReadFromStream(addr_man, data_stream); } catch (const std::exception&) { @@ -105,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. @@ -124,8 +132,8 @@ void FillAddrman(AddrMan& addrman, FuzzedDataProvider& fuzzed_data_provider) class AddrManDeterministic : public AddrMan { public: - explicit AddrManDeterministic(std::vector<bool> asmap, FuzzedDataProvider& fuzzed_data_provider) - : AddrMan{std::move(asmap), /*deterministic=*/true, GetCheckRatio()} + explicit AddrManDeterministic(const NetGroupManager& netgroupman, FuzzedDataProvider& fuzzed_data_provider) + : AddrMan(netgroupman, /*deterministic=*/true, GetCheckRatio()) { WITH_LOCK(m_impl->cs, m_impl->insecure_rand = FastRandomContext{ConsumeUInt256(fuzzed_data_provider)}); } @@ -153,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); @@ -167,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)>; @@ -223,19 +231,12 @@ public: } }; -[[nodiscard]] inline std::vector<bool> ConsumeAsmap(FuzzedDataProvider& fuzzed_data_provider) noexcept -{ - std::vector<bool> asmap = ConsumeRandomLengthBitVector(fuzzed_data_provider); - if (!SanityCheckASMap(asmap, 128)) asmap.clear(); - return asmap; -} - FUZZ_TARGET_INIT(addrman, initialize_addrman) { FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size()); SetMockTime(ConsumeTime(fuzzed_data_provider)); - std::vector<bool> asmap = ConsumeAsmap(fuzzed_data_provider); - auto addr_man_ptr = std::make_unique<AddrManDeterministic>(asmap, fuzzed_data_provider); + NetGroupManager netgroupman{ConsumeNetGroupManager(fuzzed_data_provider)}; + auto addr_man_ptr = std::make_unique<AddrManDeterministic>(netgroupman, fuzzed_data_provider); if (fuzzed_data_provider.ConsumeBool()) { const std::vector<uint8_t> serialized_data{ConsumeRandomLengthByteVector(fuzzed_data_provider)}; CDataStream ds(serialized_data, SER_DISK, INIT_PROTO_VERSION); @@ -244,7 +245,7 @@ FUZZ_TARGET_INIT(addrman, initialize_addrman) try { ds >> *addr_man_ptr; } catch (const std::ios_base::failure&) { - addr_man_ptr = std::make_unique<AddrManDeterministic>(asmap, fuzzed_data_provider); + addr_man_ptr = std::make_unique<AddrManDeterministic>(netgroupman, fuzzed_data_provider); } } AddrManDeterministic& addr_man = *addr_man_ptr; @@ -268,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)}}); } }, [&] { @@ -313,9 +314,9 @@ FUZZ_TARGET_INIT(addrman_serdeser, initialize_addrman) FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size()); SetMockTime(ConsumeTime(fuzzed_data_provider)); - std::vector<bool> asmap = ConsumeAsmap(fuzzed_data_provider); - AddrManDeterministic addr_man1{asmap, fuzzed_data_provider}; - AddrManDeterministic addr_man2{asmap, fuzzed_data_provider}; + NetGroupManager netgroupman{ConsumeNetGroupManager(fuzzed_data_provider)}; + AddrManDeterministic addr_man1{netgroupman, fuzzed_data_provider}; + AddrManDeterministic addr_man2{netgroupman, fuzzed_data_provider}; CDataStream data_stream(SER_NETWORK, PROTOCOL_VERSION); diff --git a/src/test/fuzz/asmap.cpp b/src/test/fuzz/asmap.cpp index 95be963dc8..1720f8e0ab 100644 --- a/src/test/fuzz/asmap.cpp +++ b/src/test/fuzz/asmap.cpp @@ -3,6 +3,7 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include <netaddress.h> +#include <netgroup.h> #include <test/fuzz/fuzz.h> #include <util/asmap.h> @@ -56,5 +57,6 @@ FUZZ_TARGET(asmap) memcpy(&ipv4, addr_data, addr_size); net_addr.SetIP(CNetAddr{ipv4}); } - (void)net_addr.GetMappedAS(asmap); + NetGroupManager netgroupman{asmap}; + (void)netgroupman.GetMappedAS(net_addr); } 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/base_encode_decode.cpp b/src/test/fuzz/base_encode_decode.cpp index 196410e29c..48356065b0 100644 --- a/src/test/fuzz/base_encode_decode.cpp +++ b/src/test/fuzz/base_encode_decode.cpp @@ -26,7 +26,7 @@ FUZZ_TARGET_INIT(base_encode_decode, initialize_base_encode_decode) std::vector<unsigned char> decoded; if (DecodeBase58(random_encoded_string, decoded, 100)) { const std::string encoded_string = EncodeBase58(decoded); - assert(encoded_string == TrimString(encoded_string)); + assert(encoded_string == TrimStringView(encoded_string)); assert(ToLower(encoded_string) == ToLower(TrimString(random_encoded_string))); } @@ -36,17 +36,16 @@ FUZZ_TARGET_INIT(base_encode_decode, initialize_base_encode_decode) assert(ToLower(encoded_string) == ToLower(TrimString(random_encoded_string))); } - bool pf_invalid; - std::string decoded_string = DecodeBase32(random_encoded_string, &pf_invalid); - if (!pf_invalid) { - const std::string encoded_string = EncodeBase32(decoded_string); - assert(encoded_string == TrimString(encoded_string)); + auto result = DecodeBase32(random_encoded_string); + if (result) { + const std::string encoded_string = EncodeBase32(*result); + assert(encoded_string == TrimStringView(encoded_string)); assert(ToLower(encoded_string) == ToLower(TrimString(random_encoded_string))); } - decoded_string = DecodeBase64(random_encoded_string, &pf_invalid); - if (!pf_invalid) { - const std::string encoded_string = EncodeBase64(decoded_string); + result = DecodeBase64(random_encoded_string); + if (result) { + const std::string encoded_string = EncodeBase64(*result); assert(encoded_string == TrimString(encoded_string)); assert(ToLower(encoded_string) == ToLower(TrimString(random_encoded_string))); } 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/checkqueue.cpp b/src/test/fuzz/checkqueue.cpp index 0b16f0f0d5..7d107995aa 100644 --- a/src/test/fuzz/checkqueue.cpp +++ b/src/test/fuzz/checkqueue.cpp @@ -26,7 +26,7 @@ struct DumbCheck { return result; } - void swap(DumbCheck& x) + void swap(DumbCheck& x) noexcept { } }; diff --git a/src/test/fuzz/coins_view.cpp b/src/test/fuzz/coins_view.cpp index 360dc00307..6c96702f1e 100644 --- a/src/test/fuzz/coins_view.cpp +++ b/src/test/fuzz/coins_view.cpp @@ -10,7 +10,6 @@ #include <consensus/tx_verify.h> #include <consensus/validation.h> #include <key.h> -#include <node/coinstats.h> #include <policy/policy.h> #include <primitives/transaction.h> #include <pubkey.h> @@ -26,10 +25,6 @@ #include <string> #include <vector> -using node::CCoinsStats; -using node::CoinStatsHashType; -using node::GetUTXOStats; - namespace { const TestingSetup* g_setup; const Coin EMPTY_COIN{}; @@ -270,16 +265,6 @@ FUZZ_TARGET_INIT(coins_view, initialize_coins_view) (void)GetTransactionSigOpCost(transaction, coins_view_cache, flags); }, [&] { - CCoinsStats stats{CoinStatsHashType::HASH_SERIALIZED}; - bool expected_code_path = false; - try { - (void)GetUTXOStats(&coins_view_cache, g_setup->m_node.chainman->m_blockman, stats); - } catch (const std::logic_error&) { - expected_code_path = true; - } - assert(expected_code_path); - }, - [&] { (void)IsWitnessStandard(CTransaction{random_mutable_transaction}, coins_view_cache); }); } diff --git a/src/test/fuzz/connman.cpp b/src/test/fuzz/connman.cpp index a14d28f4ef..4406779015 100644 --- a/src/test/fuzz/connman.cpp +++ b/src/test/fuzz/connman.cpp @@ -19,12 +19,12 @@ #include <vector> namespace { -const BasicTestingSetup* g_setup; +const TestingSetup* g_setup; } // namespace void initialize_connman() { - static const auto testing_setup = MakeNoLogFileContext<>(); + static const auto testing_setup = MakeNoLogFileContext<const TestingSetup>(); g_setup = testing_setup.get(); } @@ -32,10 +32,11 @@ FUZZ_TARGET_INIT(connman, initialize_connman) { FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()}; SetMockTime(ConsumeTime(fuzzed_data_provider)); - AddrMan addrman(/*asmap=*/std::vector<bool>(), - /*deterministic=*/false, - g_setup->m_node.args->GetIntArg("-checkaddrman", 0)); - CConnman connman{fuzzed_data_provider.ConsumeIntegral<uint64_t>(), fuzzed_data_provider.ConsumeIntegral<uint64_t>(), addrman, fuzzed_data_provider.ConsumeBool()}; + CConnman connman{fuzzed_data_provider.ConsumeIntegral<uint64_t>(), + fuzzed_data_provider.ConsumeIntegral<uint64_t>(), + *g_setup->m_node.addrman, + *g_setup->m_node.netgroupman, + fuzzed_data_provider.ConsumeBool()}; CNetAddr random_netaddr; CNode random_node = ConsumeNode(fuzzed_data_provider); CSubNet random_subnet; diff --git a/src/test/fuzz/crypto_diff_fuzz_chacha20.cpp b/src/test/fuzz/crypto_diff_fuzz_chacha20.cpp index fcc96c6418..1b89d55773 100644 --- a/src/test/fuzz/crypto_diff_fuzz_chacha20.cpp +++ b/src/test/fuzz/crypto_diff_fuzz_chacha20.cpp @@ -128,7 +128,7 @@ void ECRYPT_encrypt_bytes(ECRYPT_ctx* x, const u8* m, u8* c, u32 bytes) { u32 x0, x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15; u32 j0, j1, j2, j3, j4, j5, j6, j7, j8, j9, j10, j11, j12, j13, j14, j15; - u8* ctarget = NULL; + u8* ctarget = nullptr; u8 tmp[64]; uint32_t i; diff --git a/src/test/fuzz/deserialize.cpp b/src/test/fuzz/deserialize.cpp index ed6f172a2a..0a7d0c55bd 100644 --- a/src/test/fuzz/deserialize.cpp +++ b/src/test/fuzz/deserialize.cpp @@ -15,6 +15,7 @@ #include <merkleblock.h> #include <net.h> #include <netbase.h> +#include <netgroup.h> #include <node/utxo_snapshot.h> #include <primitives/block.h> #include <protocol.h> @@ -200,7 +201,8 @@ FUZZ_TARGET_DESERIALIZE(blockmerkleroot, { BlockMerkleRoot(block, &mutated); }) FUZZ_TARGET_DESERIALIZE(addrman_deserialize, { - AddrMan am(/*asmap=*/std::vector<bool>(), + NetGroupManager netgroupman{std::vector<bool>()}; + AddrMan am(netgroupman, /*deterministic=*/false, g_setup->m_node.args->GetIntArg("-checkaddrman", 0)); DeserializeFromFuzzingInput(buffer, am); diff --git a/src/test/fuzz/fuzz.cpp b/src/test/fuzz/fuzz.cpp index a490bbfa1d..24ae34bd9e 100644 --- a/src/test/fuzz/fuzz.cpp +++ b/src/test/fuzz/fuzz.cpp @@ -10,7 +10,9 @@ #include <test/util/setup_common.h> #include <util/check.h> #include <util/sock.h> +#include <util/time.h> +#include <csignal> #include <cstdint> #include <exception> #include <fstream> @@ -59,6 +61,7 @@ void FuzzFrameworkRegisterTarget(std::string_view name, TypeTestOneInput target, Assert(it_ins.second); } +static std::string_view g_fuzz_target; static TypeTestOneInput* g_test_one_input{nullptr}; void initialize() @@ -92,9 +95,12 @@ void initialize() should_abort = true; } Assert(!should_abort); - std::string_view fuzz_target{Assert(std::getenv("FUZZ"))}; - const auto it = FuzzTargets().find(fuzz_target); - Assert(it != FuzzTargets().end()); + g_fuzz_target = Assert(std::getenv("FUZZ")); + const auto it = FuzzTargets().find(g_fuzz_target); + if (it == FuzzTargets().end()) { + std::cerr << "No fuzzer for " << g_fuzz_target << "." << std::endl; + std::exit(EXIT_FAILURE); + } Assert(!g_test_one_input); g_test_one_input = &std::get<0>(it->second); std::get<1>(it->second)(); @@ -112,6 +118,35 @@ static bool read_stdin(std::vector<uint8_t>& data) } #endif +#if defined(PROVIDE_FUZZ_MAIN_FUNCTION) && !defined(__AFL_LOOP) +static bool read_file(fs::path p, std::vector<uint8_t>& data) +{ + uint8_t buffer[1024]; + FILE* f = fsbridge::fopen(p, "rb"); + if (f == nullptr) return false; + do { + const size_t length = fread(buffer, sizeof(uint8_t), sizeof(buffer), f); + if (ferror(f)) return false; + data.insert(data.end(), buffer, buffer + length); + } while (!feof(f)); + fclose(f); + return true; +} +#endif + +#if defined(PROVIDE_FUZZ_MAIN_FUNCTION) && !defined(__AFL_LOOP) +static fs::path g_input_path; +void signal_handler(int signal) +{ + if (signal == SIGABRT) { + std::cerr << "Error processing input " << g_input_path << std::endl; + } else { + std::cerr << "Unexpected signal " << signal << " received\n"; + } + std::_Exit(EXIT_FAILURE); +} +#endif + // This function is used by libFuzzer extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { @@ -151,10 +186,37 @@ int main(int argc, char** argv) } #else std::vector<uint8_t> buffer; - if (!read_stdin(buffer)) { + if (argc <= 1) { + if (!read_stdin(buffer)) { + return 0; + } + test_one_input(buffer); return 0; } - test_one_input(buffer); + std::signal(SIGABRT, signal_handler); + const auto start_time{Now<SteadySeconds>()}; + int tested = 0; + for (int i = 1; i < argc; ++i) { + fs::path input_path(*(argv + i)); + if (fs::is_directory(input_path)) { + for (fs::directory_iterator it(input_path); it != fs::directory_iterator(); ++it) { + if (!fs::is_regular_file(it->path())) continue; + g_input_path = it->path(); + Assert(read_file(it->path(), buffer)); + test_one_input(buffer); + ++tested; + buffer.clear(); + } + } else { + g_input_path = input_path; + Assert(read_file(input_path, buffer)); + test_one_input(buffer); + ++tested; + buffer.clear(); + } + } + const auto end_time{Now<SteadySeconds>()}; + std::cout << g_fuzz_target << ": succeeded against " << tested << " files in " << count_seconds(end_time - start_time) << "s." << std::endl; #endif return 0; } diff --git a/src/test/fuzz/hex.cpp b/src/test/fuzz/hex.cpp index cc1bc1c8cf..e637975b48 100644 --- a/src/test/fuzz/hex.cpp +++ b/src/test/fuzz/hex.cpp @@ -25,6 +25,8 @@ FUZZ_TARGET_INIT(hex, initialize_hex) { const std::string random_hex_string(buffer.begin(), buffer.end()); const std::vector<unsigned char> data = ParseHex(random_hex_string); + const std::vector<std::byte> bytes{ParseHex<std::byte>(random_hex_string)}; + assert(AsBytes(Span{data}) == Span{bytes}); const std::string hex_data = HexStr(data); if (IsHex(random_hex_string)) { assert(ToLower(random_hex_string) == hex_data); diff --git a/src/test/fuzz/http_request.cpp b/src/test/fuzz/http_request.cpp index e3b62032bc..0fe18abaa9 100644 --- a/src/test/fuzz/http_request.cpp +++ b/src/test/fuzz/http_request.cpp @@ -19,23 +19,8 @@ #include <string> #include <vector> -// workaround for libevent versions before 2.1.1, -// when internal functions didn't have underscores at the end -#if LIBEVENT_VERSION_NUMBER < 0x02010100 -extern "C" int evhttp_parse_firstline(struct evhttp_request*, struct evbuffer*); -extern "C" int evhttp_parse_headers(struct evhttp_request*, struct evbuffer*); -inline int evhttp_parse_firstline_(struct evhttp_request* r, struct evbuffer* b) -{ - return evhttp_parse_firstline(r, b); -} -inline int evhttp_parse_headers_(struct evhttp_request* r, struct evbuffer* b) -{ - return evhttp_parse_headers(r, b); -} -#else extern "C" int evhttp_parse_firstline_(struct evhttp_request*, struct evbuffer*); extern "C" int evhttp_parse_headers_(struct evhttp_request*, struct evbuffer*); -#endif std::string RequestMethodString(HTTPRequest::RequestMethod m); @@ -54,7 +39,7 @@ FUZZ_TARGET(http_request) // and is a consequence of our hacky but necessary use of the internal function evhttp_parse_firstline_ in // this fuzzing harness. The workaround is not aesthetically pleasing, but it successfully avoids the troublesome // code path. " http:// HTTP/1.1\n" was a crashing input prior to this workaround. - const std::string http_buffer_str = ToLower({http_buffer.begin(), http_buffer.end()}); + const std::string http_buffer_str = ToLower(std::string{http_buffer.begin(), http_buffer.end()}); if (http_buffer_str.find(" http://") != std::string::npos || http_buffer_str.find(" https://") != std::string::npos || evhttp_parse_firstline_(evreq, evbuf) != 1 || evhttp_parse_headers_(evreq, evbuf) != 1) { evbuffer_free(evbuf); diff --git a/src/test/fuzz/integer.cpp b/src/test/fuzz/integer.cpp index 72574612a2..c52fca5fe8 100644 --- a/src/test/fuzz/integer.cpp +++ b/src/test/fuzz/integer.cpp @@ -87,9 +87,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/mempool_utils.h b/src/test/fuzz/mempool_utils.h new file mode 100644 index 0000000000..bfe12e30ba --- /dev/null +++ b/src/test/fuzz/mempool_utils.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_TEST_FUZZ_MEMPOOL_UTILS_H +#define BITCOIN_TEST_FUZZ_MEMPOOL_UTILS_H + +#include <validation.h> + +class DummyChainState final : public CChainState +{ +public: + void SetMempool(CTxMemPool* mempool) + { + m_mempool = mempool; + } +}; + +#endif // BITCOIN_TEST_FUZZ_MEMPOOL_UTILS_H diff --git a/src/test/fuzz/miniscript.cpp b/src/test/fuzz/miniscript.cpp new file mode 100644 index 0000000000..6be75322b4 --- /dev/null +++ b/src/test/fuzz/miniscript.cpp @@ -0,0 +1,167 @@ +// 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 <core_io.h> +#include <hash.h> +#include <key.h> +#include <script/miniscript.h> +#include <script/script.h> +#include <test/fuzz/FuzzedDataProvider.h> +#include <test/fuzz/fuzz.h> +#include <test/fuzz/util.h> +#include <util/strencodings.h> + +namespace { + +//! Some pre-computed data for more efficient string roundtrips. +struct TestData { + typedef CPubKey Key; + + // Precomputed public keys. + std::vector<Key> dummy_keys; + std::map<Key, int> dummy_key_idx_map; + std::map<CKeyID, Key> dummy_keys_map; + + //! Set the precomputed data. + void Init() { + unsigned char keydata[32] = {1}; + for (size_t i = 0; i < 256; i++) { + keydata[31] = i; + CKey privkey; + privkey.Set(keydata, keydata + 32, true); + const Key pubkey = privkey.GetPubKey(); + + dummy_keys.push_back(pubkey); + dummy_key_idx_map.emplace(pubkey, i); + dummy_keys_map.insert({pubkey.GetID(), pubkey}); + } + } +} TEST_DATA; + +/** + * Context to parse a Miniscript node to and from Script or text representation. + * Uses an integer (an index in the dummy keys array from the test data) as keys in order + * to focus on fuzzing the Miniscript nodes' test representation, not the key representation. + */ +struct ParserContext { + typedef CPubKey Key; + + bool KeyCompare(const Key& a, const Key& b) const { + return a < b; + } + + std::optional<std::string> ToString(const Key& key) const + { + auto it = TEST_DATA.dummy_key_idx_map.find(key); + if (it == TEST_DATA.dummy_key_idx_map.end()) return {}; + uint8_t idx = it->second; + return HexStr(Span{&idx, 1}); + } + + template<typename I> + std::optional<Key> FromString(I first, I last) const { + if (last - first != 2) return {}; + auto idx = ParseHex(std::string(first, last)); + if (idx.size() != 1) return {}; + return TEST_DATA.dummy_keys[idx[0]]; + } + + template<typename I> + std::optional<Key> FromPKBytes(I first, I last) const { + Key key; + key.Set(first, last); + if (!key.IsValid()) return {}; + return key; + } + + template<typename I> + std::optional<Key> FromPKHBytes(I first, I last) const { + assert(last - first == 20); + CKeyID keyid; + std::copy(first, last, keyid.begin()); + const auto it = TEST_DATA.dummy_keys_map.find(keyid); + if (it == TEST_DATA.dummy_keys_map.end()) return {}; + return it->second; + } +} PARSER_CTX; + +//! Context that implements naive conversion from/to script only, for roundtrip testing. +struct ScriptParserContext { + //! For Script roundtrip we never need the key from a key hash. + struct Key { + bool is_hash; + std::vector<unsigned char> data; + }; + + bool KeyCompare(const Key& a, const Key& b) const { + return a.data < b.data; + } + + const std::vector<unsigned char>& ToPKBytes(const Key& key) const + { + assert(!key.is_hash); + return key.data; + } + + const std::vector<unsigned char> ToPKHBytes(const Key& key) const + { + if (key.is_hash) return key.data; + const auto h = Hash160(key.data); + return {h.begin(), h.end()}; + } + + template<typename I> + std::optional<Key> FromPKBytes(I first, I last) const + { + Key key; + key.data.assign(first, last); + key.is_hash = false; + return key; + } + + template<typename I> + std::optional<Key> FromPKHBytes(I first, I last) const + { + Key key; + key.data.assign(first, last); + key.is_hash = true; + return key; + } +} SCRIPT_PARSER_CONTEXT; + +} // namespace + +void FuzzInit() +{ + ECC_Start(); + TEST_DATA.Init(); +} + +/* Fuzz tests that test parsing from a string, and roundtripping via string. */ +FUZZ_TARGET_INIT(miniscript_string, FuzzInit) +{ + FuzzedDataProvider provider(buffer.data(), buffer.size()); + auto str = provider.ConsumeRemainingBytesAsString(); + auto parsed = miniscript::FromString(str, PARSER_CTX); + if (!parsed) return; + + const auto str2 = parsed->ToString(PARSER_CTX); + assert(str2); + auto parsed2 = miniscript::FromString(*str2, PARSER_CTX); + assert(parsed2); + assert(*parsed == *parsed2); +} + +/* Fuzz tests that test parsing from a script, and roundtripping via script. */ +FUZZ_TARGET(miniscript_script) +{ + FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size()); + const std::optional<CScript> script = ConsumeDeserializable<CScript>(fuzzed_data_provider); + if (!script) return; + + const auto ms = miniscript::FromScript(*script, SCRIPT_PARSER_CONTEXT); + if (!ms) return; + + assert(ms->ToScript(SCRIPT_PARSER_CONTEXT) == *script); +} diff --git a/src/test/fuzz/net.cpp b/src/test/fuzz/net.cpp index fb11ea36ce..741810f6a2 100644 --- a/src/test/fuzz/net.cpp +++ b/src/test/fuzz/net.cpp @@ -52,16 +52,6 @@ FUZZ_TARGET_INIT(net, initialize_net) } }, [&] { - const std::optional<CInv> inv_opt = ConsumeDeserializable<CInv>(fuzzed_data_provider); - if (!inv_opt) { - return; - } - node.AddKnownTx(inv_opt->hash); - }, - [&] { - node.PushTxInventory(ConsumeUInt256(fuzzed_data_provider)); - }, - [&] { const std::optional<CService> service_opt = ConsumeDeserializable<CService>(fuzzed_data_provider); if (!service_opt) { return; @@ -78,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 2e90085744..e27b254580 100644 --- a/src/test/fuzz/node_eviction.cpp +++ b/src/test/fuzz/node_eviction.cpp @@ -26,12 +26,14 @@ FUZZ_TARGET(node_eviction) /*m_last_block_time=*/std::chrono::seconds{fuzzed_data_provider.ConsumeIntegral<int64_t>()}, /*m_last_tx_time=*/std::chrono::seconds{fuzzed_data_provider.ConsumeIntegral<int64_t>()}, /*fRelevantServices=*/fuzzed_data_provider.ConsumeBool(), - /*fRelayTxes=*/fuzzed_data_provider.ConsumeBool(), + /*m_relay_txs=*/fuzzed_data_provider.ConsumeBool(), /*fBloomFilter=*/fuzzed_data_provider.ConsumeBool(), /*nKeyedNetGroup=*/fuzzed_data_provider.ConsumeIntegral<uint64_t>(), /*prefer_evict=*/fuzzed_data_provider.ConsumeBool(), /*m_is_local=*/fuzzed_data_provider.ConsumeBool(), /*m_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..637ba503c6 100644 --- a/src/test/fuzz/policy_estimator.cpp +++ b/src/test/fuzz/policy_estimator.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 <primitives/transaction.h> #include <test/fuzz/FuzzedDataProvider.h> #include <test/fuzz/fuzz.h> @@ -15,15 +16,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 +76,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/prevector.cpp b/src/test/fuzz/prevector.cpp index a48bab1ee2..e2d65a4796 100644 --- a/src/test/fuzz/prevector.cpp +++ b/src/test/fuzz/prevector.cpp @@ -161,7 +161,7 @@ public: pre_vector.shrink_to_fit(); } - void swap() + void swap() noexcept { real_vector.swap(real_vector_alt); pre_vector.swap(pre_vector_alt); diff --git a/src/test/fuzz/process_message.cpp b/src/test/fuzz/process_message.cpp index 1763cd8af3..272c9e6cdc 100644 --- a/src/test/fuzz/process_message.cpp +++ b/src/test/fuzz/process_message.cpp @@ -80,8 +80,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); diff --git a/src/test/fuzz/process_messages.cpp b/src/test/fuzz/process_messages.cpp index e1c11e1afd..12e682416c 100644 --- a/src/test/fuzz/process_messages.cpp +++ b/src/test/fuzz/process_messages.cpp @@ -46,8 +46,7 @@ FUZZ_TARGET_INIT(process_messages, initialize_process_messages) 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); } diff --git a/src/test/fuzz/psbt.cpp b/src/test/fuzz/psbt.cpp index 669688a80d..baa64bba0f 100644 --- a/src/test/fuzz/psbt.cpp +++ b/src/test/fuzz/psbt.cpp @@ -32,7 +32,8 @@ FUZZ_TARGET_INIT(psbt, initialize_psbt) FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()}; PartiallySignedTransaction psbt_mut; std::string error; - if (!DecodeRawPSBT(psbt_mut, fuzzed_data_provider.ConsumeRandomLengthString(), error)) { + auto str = fuzzed_data_provider.ConsumeRandomLengthString(); + if (!DecodeRawPSBT(psbt_mut, MakeByteSpan(str), error)) { return; } const PartiallySignedTransaction psbt = psbt_mut; @@ -79,7 +80,8 @@ FUZZ_TARGET_INIT(psbt, initialize_psbt) } PartiallySignedTransaction psbt_merge; - if (!DecodeRawPSBT(psbt_merge, fuzzed_data_provider.ConsumeRandomLengthString(), error)) { + str = fuzzed_data_provider.ConsumeRandomLengthString(); + if (!DecodeRawPSBT(psbt_merge, MakeByteSpan(str), error)) { psbt_merge = psbt; } psbt_mut = psbt; diff --git a/src/test/fuzz/rbf.cpp b/src/test/fuzz/rbf.cpp index 8dcaa609b5..1a06ae886e 100644 --- a/src/test/fuzz/rbf.cpp +++ b/src/test/fuzz/rbf.cpp @@ -2,12 +2,14 @@ // 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/util/setup_common.h> #include <txmempool.h> #include <cstdint> @@ -15,7 +17,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 +35,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 03a84b697d..26913a41d2 100644 --- a/src/test/fuzz/rpc.cpp +++ b/src/test/fuzz/rpc.cpp @@ -128,6 +128,7 @@ const std::vector<std::string> RPC_COMMANDS_SAFE_FOR_FUZZING{ "getmempoolancestors", "getmempooldescendants", "getmempoolentry", + "gettxspendingprevout", "getmempoolinfo", "getmininginfo", "getnettotals", @@ -158,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_assets_test_minimizer.cpp b/src/test/fuzz/script_assets_test_minimizer.cpp index 00a3bed12f..35d7246ed8 100644 --- a/src/test/fuzz/script_assets_test_minimizer.cpp +++ b/src/test/fuzz/script_assets_test_minimizer.cpp @@ -11,8 +11,8 @@ #include <streams.h> #include <univalue.h> #include <util/strencodings.h> +#include <util/string.h> -#include <boost/algorithm/string.hpp> #include <cstdint> #include <string> #include <vector> @@ -130,8 +130,7 @@ unsigned int ParseScriptFlags(const std::string& str) if (str.empty()) return 0; unsigned int flags = 0; - std::vector<std::string> words; - boost::algorithm::split(words, str, boost::algorithm::is_any_of(",")); + std::vector<std::string> words = SplitString(str, ','); for (const std::string& word : words) { auto it = FLAG_NAMES.find(word); @@ -150,7 +149,7 @@ void Test(const std::string& str) CMutableTransaction tx = TxFromHex(test["tx"].get_str()); const std::vector<CTxOut> prevouts = TxOutsFromJSON(test["prevouts"]); if (prevouts.size() != tx.vin.size()) throw std::runtime_error("Incorrect number of prevouts"); - size_t idx = test["index"].get_int64(); + size_t idx = test["index"].getInt<int64_t>(); if (idx >= tx.vin.size()) throw std::runtime_error("Invalid index"); unsigned int test_flags = ParseScriptFlags(test["flags"].get_str()); bool final = test.exists("final") && test["final"].get_bool(); diff --git a/src/test/fuzz/script_format.cpp b/src/test/fuzz/script_format.cpp index 2fa893f812..9186746bcf 100644 --- a/src/test/fuzz/script_format.cpp +++ b/src/test/fuzz/script_format.cpp @@ -3,7 +3,9 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include <chainparams.h> +#include <consensus/consensus.h> #include <core_io.h> +#include <policy/policy.h> #include <script/script.h> #include <test/fuzz/FuzzedDataProvider.h> #include <test/fuzz/fuzz.h> @@ -19,12 +21,13 @@ FUZZ_TARGET_INIT(script_format, initialize_script_format) { FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size()); const CScript script{ConsumeScript(fuzzed_data_provider)}; + if (script.size() > MAX_STANDARD_TX_WEIGHT / WITNESS_SCALE_FACTOR) { + return; + } (void)FormatScript(script); (void)ScriptToAsmStr(script, /*fAttemptSighashDecode=*/fuzzed_data_provider.ConsumeBool()); UniValue o1(UniValue::VOBJ); - ScriptPubKeyToUniv(script, o1, /*include_hex=*/fuzzed_data_provider.ConsumeBool()); - UniValue o3(UniValue::VOBJ); - ScriptToUniv(script, o3); + ScriptToUniv(script, /*out=*/o1, /*include_hex=*/fuzzed_data_provider.ConsumeBool(), /*include_address=*/fuzzed_data_provider.ConsumeBool()); } 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/script_sign.cpp b/src/test/fuzz/script_sign.cpp index 1446eafe92..3ddb30d870 100644 --- a/src/test/fuzz/script_sign.cpp +++ b/src/test/fuzz/script_sign.cpp @@ -113,7 +113,7 @@ FUZZ_TARGET_INIT(script_sign, initialize_script_sign) } if (n_in < script_tx_to.vin.size()) { (void)SignSignature(provider, ConsumeScript(fuzzed_data_provider), script_tx_to, n_in, ConsumeMoney(fuzzed_data_provider), fuzzed_data_provider.ConsumeIntegral<int>()); - MutableTransactionSignatureCreator signature_creator{&tx_to, n_in, ConsumeMoney(fuzzed_data_provider), fuzzed_data_provider.ConsumeIntegral<int>()}; + MutableTransactionSignatureCreator signature_creator{tx_to, n_in, ConsumeMoney(fuzzed_data_provider), fuzzed_data_provider.ConsumeIntegral<int>()}; std::vector<unsigned char> vch_sig; CKeyID address; if (fuzzed_data_provider.ConsumeBool()) { diff --git a/src/test/fuzz/signature_checker.cpp b/src/test/fuzz/signature_checker.cpp index f6c591aca4..a585680de1 100644 --- a/src/test/fuzz/signature_checker.cpp +++ b/src/test/fuzz/signature_checker.cpp @@ -49,7 +49,7 @@ public: return m_fuzzed_data_provider.ConsumeBool(); } - virtual ~FuzzedSignatureChecker() {} + virtual ~FuzzedSignatureChecker() = default; }; } // namespace diff --git a/src/test/fuzz/string.cpp b/src/test/fuzz/string.cpp index ca57af25c4..94399faf04 100644 --- a/src/test/fuzz/string.cpp +++ b/src/test/fuzz/string.cpp @@ -42,7 +42,7 @@ bool LegacyParsePrechecks(const std::string& str) return false; if (str.size() >= 1 && (IsSpace(str[0]) || IsSpace(str[str.size() - 1]))) // No padding allowed return false; - if (!ValidAsCString(str)) // No embedded NUL characters allowed + if (!ContainsNoNUL(str)) // No embedded NUL characters allowed return false; return true; } @@ -188,7 +188,7 @@ FUZZ_TARGET(string) (void)TrimString(random_string_1); (void)TrimString(random_string_1, random_string_2); (void)urlDecode(random_string_1); - (void)ValidAsCString(random_string_1); + (void)ContainsNoNUL(random_string_1); (void)_(random_string_1.c_str()); try { throw scriptnum_error{random_string_1}; @@ -225,6 +225,12 @@ FUZZ_TARGET(string) (void)ParseFixedPoint(random_string_1, fuzzed_data_provider.ConsumeIntegralInRange<int>(0, 1024), &amount_out); } { + const auto single_split{SplitString(random_string_1, fuzzed_data_provider.ConsumeIntegral<char>())}; + assert(single_split.size() >= 1); + const auto any_split{SplitString(random_string_1, random_string_2)}; + assert(any_split.size() >= 1); + } + { (void)Untranslated(random_string_1); const bilingual_str bs1{random_string_1, random_string_2}; const bilingual_str bs2{random_string_2, random_string_1}; diff --git a/src/test/fuzz/transaction.cpp b/src/test/fuzz/transaction.cpp index 6dd8a36692..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); @@ -102,6 +101,6 @@ FUZZ_TARGET_INIT(transaction, initialize_transaction) (void)IsWitnessStandard(tx, coins_view_cache); UniValue u(UniValue::VOBJ); - TxToUniv(tx, /*hashBlock=*/uint256::ZERO, u); - TxToUniv(tx, /*hashBlock=*/uint256::ONE, u); + TxToUniv(tx, /*block_hash=*/uint256::ZERO, /*entry=*/u); + TxToUniv(tx, /*block_hash=*/uint256::ONE, /*entry=*/u); } diff --git a/src/test/fuzz/tx_out.cpp b/src/test/fuzz/tx_out.cpp index 39a50b6c80..a2421ff582 100644 --- a/src/test/fuzz/tx_out.cpp +++ b/src/test/fuzz/tx_out.cpp @@ -4,6 +4,7 @@ #include <consensus/validation.h> #include <core_memusage.h> +#include <policy/feerate.h> #include <policy/policy.h> #include <primitives/transaction.h> #include <streams.h> diff --git a/src/test/fuzz/tx_pool.cpp b/src/test/fuzz/tx_pool.cpp index df5b271d06..3191367870 100644 --- a/src/test/fuzz/tx_pool.cpp +++ b/src/test/fuzz/tx_pool.cpp @@ -3,9 +3,12 @@ // 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/mempool_utils.h> #include <test/fuzz/util.h> #include <test/util/mining.h> #include <test/util/script.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>(); @@ -97,7 +92,7 @@ void Finish(FuzzedDataProvider& fuzzed_data_provider, MockedTxPool& tx_pool, CCh 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), chainstate.m_params, options}; + auto assembler = BlockAssembler{chainstate, &tx_pool, options}; auto block_template = assembler.CreateNewBlock(CScript{} << OP_TRUE); Assert(block_template->block.vtx.size() >= 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,16 +238,19 @@ 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 and always fully validates the transaction. + // Make sure ProcessNewPackage on one transaction works. // The result is not guaranteed to be the same as what is returned by ATMP. const auto result_package = WITH_LOCK(::cs_main, return ProcessNewPackage(chainstate, tx_pool, {tx}, true)); - auto it = result_package.m_tx_results.find(tx->GetWitnessHash()); - Assert(it != result_package.m_tx_results.end()); - Assert(it->second.m_result_type == MempoolAcceptResult::ResultType::VALID || - it->second.m_result_type == MempoolAcceptResult::ResultType::INVALID); + // If something went wrong due to a package-specific policy, it might not return a + // validation result for the transaction. + if (result_package.m_state.GetResult() != PackageValidationResult::PCKG_POLICY) { + auto it = result_package.m_tx_results.find(tx->GetWitnessHash()); + Assert(it != result_package.m_tx_results.end()); + Assert(it->second.m_result_type == MempoolAcceptResult::ResultType::VALID || + it->second.m_result_type == MempoolAcceptResult::ResultType::INVALID); + } 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; @@ -304,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) { @@ -316,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) @@ -327,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()) { @@ -342,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..7580651371 --- /dev/null +++ b/src/test/fuzz/txorphan.cpp @@ -0,0 +1,147 @@ +// 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 <algorithm> +#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(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 f0c1b0d147..ba1a634e41 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{ @@ -193,6 +227,33 @@ int FuzzedSock::GetSockOpt(int level, int opt_name, void* opt_val, socklen_t* op return 0; } +int FuzzedSock::SetSockOpt(int, int, const void*, socklen_t) const +{ + constexpr std::array setsockopt_errnos{ + ENOMEM, + ENOBUFS, + }; + if (m_fuzzed_data_provider.ConsumeBool()) { + SetFuzzedErrNo(m_fuzzed_data_provider, setsockopt_errnos); + return -1; + } + 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{ @@ -210,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()) { @@ -219,58 +289,15 @@ bool FuzzedSock::IsConnected(std::string& errmsg) const return false; } -void FillNode(FuzzedDataProvider& fuzzed_data_provider, ConnmanTestMsg& connman, PeerManager& peerman, 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 filter_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 - filter_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); - if (node.m_tx_relay != nullptr) { - LOCK(node.m_tx_relay->cs_filter); - assert(node.m_tx_relay->fRelayTxes == filter_txs); - } - 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); - } +void FillNode(FuzzedDataProvider& fuzzed_data_provider, ConnmanTestMsg& connman, CNode& node) noexcept +{ + 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), + /*permission_flags=*/ConsumeWeakEnum(fuzzed_data_provider, ALL_NET_PERMISSION_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 @@ -500,6 +527,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 6c91844633..33d9ab3cc3 100644 --- a/src/test/fuzz/util.h +++ b/src/test/fuzz/util.h @@ -6,10 +6,9 @@ #define BITCOIN_TEST_FUZZ_UTIL_H #include <arith_uint256.h> -#include <attributes.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> @@ -56,20 +55,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; }; @@ -280,16 +287,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>(); @@ -300,7 +303,6 @@ auto ConsumeNode(FuzzedDataProvider& fuzzed_data_provider, const std::optional<N const bool inbound_onion{conn_type == ConnectionType::INBOUND ? fuzzed_data_provider.ConsumeBool() : false}; if constexpr (ReturnUniquePtr) { return std::make_unique<CNode>(node_id, - local_services, sock, address, keyed_net_group, @@ -311,7 +313,6 @@ auto ConsumeNode(FuzzedDataProvider& fuzzed_data_provider, const std::optional<N inbound_onion); } else { return CNode{node_id, - local_services, sock, address, keyed_net_group, @@ -324,7 +325,7 @@ auto ConsumeNode(FuzzedDataProvider& fuzzed_data_provider, const std::optional<N } 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; class FuzzedFileProvider { @@ -354,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/utxo_snapshot.cpp b/src/test/fuzz/utxo_snapshot.cpp index e513f1883c..0b596492be 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, ::Params())}; + bool processed{chainman.ProcessNewBlockHeaders({*block}, 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..8241dff189 100644 --- a/src/test/fuzz/validation_load_mempool.cpp +++ b/src/test/fuzz/validation_load_mempool.cpp @@ -2,9 +2,14 @@ // 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/mempool_utils.h> #include <test/fuzz/util.h> #include <test/util/setup_common.h> #include <txmempool.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/getarg_tests.cpp b/src/test/getarg_tests.cpp index ef9d72dcde..70dd137e22 100644 --- a/src/test/getarg_tests.cpp +++ b/src/test/getarg_tests.cpp @@ -3,6 +3,8 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include <test/util/setup_common.h> +#include <univalue.h> +#include <util/settings.h> #include <util/strencodings.h> #include <util/system.h> @@ -11,7 +13,6 @@ #include <utility> #include <vector> -#include <boost/algorithm/string.hpp> #include <boost/test/unit_test.hpp> BOOST_FIXTURE_TEST_SUITE(getarg_tests, BasicTestingSetup) @@ -19,8 +20,9 @@ BOOST_FIXTURE_TEST_SUITE(getarg_tests, BasicTestingSetup) void ResetArgs(ArgsManager& local_args, const std::string& strArg) { std::vector<std::string> vecArg; - if (strArg.size()) - boost::split(vecArg, strArg, IsSpace, boost::token_compress_on); + if (strArg.size()) { + vecArg = SplitString(strArg, ' '); + } // Insert dummy executable name: vecArg.insert(vecArg.begin(), "testbitcoin"); @@ -41,6 +43,116 @@ void SetupArgs(ArgsManager& local_args, const std::vector<std::pair<std::string, } } +// Test behavior of GetArg functions when string, integer, and boolean types +// are specified in the settings.json file. GetArg functions are convenience +// functions. The GetSetting method can always be used instead of GetArg +// methods to retrieve original values, and there's not always an objective +// answer to what GetArg behavior is best in every case. This test makes sure +// there's test coverage for whatever the current behavior is, so it's not +// broken or changed unintentionally. +BOOST_AUTO_TEST_CASE(setting_args) +{ + ArgsManager args; + SetupArgs(args, {{"-foo", ArgsManager::ALLOW_ANY}}); + + auto set_foo = [&](const util::SettingsValue& value) { + args.LockSettings([&](util::Settings& settings) { + settings.rw_settings["foo"] = value; + }); + }; + + set_foo("str"); + BOOST_CHECK_EQUAL(args.GetSetting("foo").write(), "\"str\""); + BOOST_CHECK_EQUAL(args.GetArg("foo", "default"), "str"); + BOOST_CHECK_EQUAL(args.GetIntArg("foo", 100), 0); + BOOST_CHECK_EQUAL(args.GetBoolArg("foo", true), false); + BOOST_CHECK_EQUAL(args.GetBoolArg("foo", false), false); + + set_foo("99"); + BOOST_CHECK_EQUAL(args.GetSetting("foo").write(), "\"99\""); + BOOST_CHECK_EQUAL(args.GetArg("foo", "default"), "99"); + BOOST_CHECK_EQUAL(args.GetIntArg("foo", 100), 99); + BOOST_CHECK_EQUAL(args.GetBoolArg("foo", true), true); + BOOST_CHECK_EQUAL(args.GetBoolArg("foo", false), true); + + set_foo("3.25"); + BOOST_CHECK_EQUAL(args.GetSetting("foo").write(), "\"3.25\""); + BOOST_CHECK_EQUAL(args.GetArg("foo", "default"), "3.25"); + BOOST_CHECK_EQUAL(args.GetIntArg("foo", 100), 3); + BOOST_CHECK_EQUAL(args.GetBoolArg("foo", true), true); + BOOST_CHECK_EQUAL(args.GetBoolArg("foo", false), true); + + set_foo("0"); + BOOST_CHECK_EQUAL(args.GetSetting("foo").write(), "\"0\""); + BOOST_CHECK_EQUAL(args.GetArg("foo", "default"), "0"); + BOOST_CHECK_EQUAL(args.GetIntArg("foo", 100), 0); + BOOST_CHECK_EQUAL(args.GetBoolArg("foo", true), false); + BOOST_CHECK_EQUAL(args.GetBoolArg("foo", false), false); + + set_foo(""); + BOOST_CHECK_EQUAL(args.GetSetting("foo").write(), "\"\""); + BOOST_CHECK_EQUAL(args.GetArg("foo", "default"), ""); + BOOST_CHECK_EQUAL(args.GetIntArg("foo", 100), 0); + BOOST_CHECK_EQUAL(args.GetBoolArg("foo", true), true); + BOOST_CHECK_EQUAL(args.GetBoolArg("foo", false), true); + + set_foo(99); + BOOST_CHECK_EQUAL(args.GetSetting("foo").write(), "99"); + BOOST_CHECK_EQUAL(args.GetArg("foo", "default"), "99"); + BOOST_CHECK_EQUAL(args.GetIntArg("foo", 100), 99); + BOOST_CHECK_THROW(args.GetBoolArg("foo", true), std::runtime_error); + BOOST_CHECK_THROW(args.GetBoolArg("foo", false), std::runtime_error); + + set_foo(3.25); + BOOST_CHECK_EQUAL(args.GetSetting("foo").write(), "3.25"); + BOOST_CHECK_EQUAL(args.GetArg("foo", "default"), "3.25"); + BOOST_CHECK_THROW(args.GetIntArg("foo", 100), std::runtime_error); + BOOST_CHECK_THROW(args.GetBoolArg("foo", true), std::runtime_error); + BOOST_CHECK_THROW(args.GetBoolArg("foo", false), std::runtime_error); + + set_foo(0); + BOOST_CHECK_EQUAL(args.GetSetting("foo").write(), "0"); + BOOST_CHECK_EQUAL(args.GetArg("foo", "default"), "0"); + BOOST_CHECK_EQUAL(args.GetIntArg("foo", 100), 0); + BOOST_CHECK_THROW(args.GetBoolArg("foo", true), std::runtime_error); + BOOST_CHECK_THROW(args.GetBoolArg("foo", false), std::runtime_error); + + set_foo(true); + BOOST_CHECK_EQUAL(args.GetSetting("foo").write(), "true"); + BOOST_CHECK_EQUAL(args.GetArg("foo", "default"), "1"); + BOOST_CHECK_EQUAL(args.GetIntArg("foo", 100), 1); + BOOST_CHECK_EQUAL(args.GetBoolArg("foo", true), true); + BOOST_CHECK_EQUAL(args.GetBoolArg("foo", false), true); + + set_foo(false); + BOOST_CHECK_EQUAL(args.GetSetting("foo").write(), "false"); + BOOST_CHECK_EQUAL(args.GetArg("foo", "default"), "0"); + BOOST_CHECK_EQUAL(args.GetIntArg("foo", 100), 0); + BOOST_CHECK_EQUAL(args.GetBoolArg("foo", true), false); + BOOST_CHECK_EQUAL(args.GetBoolArg("foo", false), false); + + set_foo(UniValue::VOBJ); + BOOST_CHECK_EQUAL(args.GetSetting("foo").write(), "{}"); + BOOST_CHECK_THROW(args.GetArg("foo", "default"), std::runtime_error); + BOOST_CHECK_THROW(args.GetIntArg("foo", 100), std::runtime_error); + BOOST_CHECK_THROW(args.GetBoolArg("foo", true), std::runtime_error); + BOOST_CHECK_THROW(args.GetBoolArg("foo", false), std::runtime_error); + + set_foo(UniValue::VARR); + BOOST_CHECK_EQUAL(args.GetSetting("foo").write(), "[]"); + BOOST_CHECK_THROW(args.GetArg("foo", "default"), std::runtime_error); + BOOST_CHECK_THROW(args.GetIntArg("foo", 100), std::runtime_error); + BOOST_CHECK_THROW(args.GetBoolArg("foo", true), std::runtime_error); + BOOST_CHECK_THROW(args.GetBoolArg("foo", false), std::runtime_error); + + set_foo(UniValue::VNULL); + BOOST_CHECK_EQUAL(args.GetSetting("foo").write(), "null"); + BOOST_CHECK_EQUAL(args.GetArg("foo", "default"), "default"); + BOOST_CHECK_EQUAL(args.GetIntArg("foo", 100), 100); + BOOST_CHECK_EQUAL(args.GetBoolArg("foo", true), true); + BOOST_CHECK_EQUAL(args.GetBoolArg("foo", false), false); +} + BOOST_AUTO_TEST_CASE(boolarg) { ArgsManager local_args; @@ -245,6 +357,24 @@ BOOST_AUTO_TEST_CASE(patharg) ResetArgs(local_args, "-dir=user/.bitcoin/.//"); BOOST_CHECK_EQUAL(local_args.GetPathArg("-dir"), relative_path); + + // Check negated and default argument handling. Specifying an empty argument + // is the same as not specifying the argument. This is convenient for + // scripting so later command line arguments can override earlier command + // line arguments or bitcoin.conf values. Currently the -dir= case cannot be + // distinguished from -dir case with no assignment, but #16545 would add the + // ability to distinguish these in the future (and treat the no-assign case + // like an imperative command or an error). + ResetArgs(local_args, ""); + BOOST_CHECK_EQUAL(local_args.GetPathArg("-dir", "default"), fs::path{"default"}); + ResetArgs(local_args, "-dir=override"); + BOOST_CHECK_EQUAL(local_args.GetPathArg("-dir", "default"), fs::path{"override"}); + ResetArgs(local_args, "-dir="); + BOOST_CHECK_EQUAL(local_args.GetPathArg("-dir", "default"), fs::path{"default"}); + ResetArgs(local_args, "-dir"); + BOOST_CHECK_EQUAL(local_args.GetPathArg("-dir", "default"), fs::path{"default"}); + ResetArgs(local_args, "-nodir"); + BOOST_CHECK_EQUAL(local_args.GetPathArg("-dir", "default"), fs::path{""}); } BOOST_AUTO_TEST_CASE(doubledash) diff --git a/src/test/httpserver_tests.cpp b/src/test/httpserver_tests.cpp new file mode 100644 index 0000000000..ee59ec6967 --- /dev/null +++ b/src/test/httpserver_tests.cpp @@ -0,0 +1,38 @@ +// Copyright (c) 2012-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 <httpserver.h> +#include <test/util/setup_common.h> + +#include <boost/test/unit_test.hpp> + +BOOST_FIXTURE_TEST_SUITE(httpserver_tests, BasicTestingSetup) + +BOOST_AUTO_TEST_CASE(test_query_parameters) +{ + std::string uri {}; + + // No parameters + uri = "localhost:8080/rest/headers/someresource.json"; + BOOST_CHECK(!GetQueryParameterFromUri(uri.c_str(), "p1").has_value()); + + // Single parameter + uri = "localhost:8080/rest/endpoint/someresource.json?p1=v1"; + BOOST_CHECK_EQUAL(GetQueryParameterFromUri(uri.c_str(), "p1").value(), "v1"); + BOOST_CHECK(!GetQueryParameterFromUri(uri.c_str(), "p2").has_value()); + + // Multiple parameters + uri = "/rest/endpoint/someresource.json?p1=v1&p2=v2"; + BOOST_CHECK_EQUAL(GetQueryParameterFromUri(uri.c_str(), "p1").value(), "v1"); + BOOST_CHECK_EQUAL(GetQueryParameterFromUri(uri.c_str(), "p2").value(), "v2"); + + // If the query string contains duplicate keys, the first value is returned + uri = "/rest/endpoint/someresource.json?p1=v1&p1=v2"; + BOOST_CHECK_EQUAL(GetQueryParameterFromUri(uri.c_str(), "p1").value(), "v1"); + + // Invalid query string syntax is the same as not having parameters + uri = "/rest/endpoint/someresource.json&p1=v1&p2=v2"; + BOOST_CHECK(!GetQueryParameterFromUri(uri.c_str(), "p1").has_value()); +} +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 b06157e99f..1eac68de14 100644 --- a/src/test/key_io_tests.cpp +++ b/src/test/key_io_tests.cpp @@ -28,14 +28,14 @@ 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); continue; } std::string exp_base58string = test[0].get_str(); - std::vector<unsigned char> exp_payload = ParseHex(test[1].get_str()); + const std::vector<std::byte> exp_payload{ParseHex<std::byte>(test[1].get_str())}; const UniValue &metadata = test[2].get_obj(); bool isPrivkey = find_value(metadata, "isPrivkey").get_bool(); SelectParams(find_value(metadata, "chain").get_str()); @@ -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/key_tests.cpp b/src/test/key_tests.cpp index 61d334ab18..8cb0515a8a 100644 --- a/src/test/key_tests.cpp +++ b/src/test/key_tests.cpp @@ -204,7 +204,7 @@ BOOST_AUTO_TEST_CASE(key_key_negation) // create a dummy hash for signature comparison unsigned char rnd[8]; std::string str = "Bitcoin key verification\n"; - GetRandBytes(rnd, sizeof(rnd)); + GetRandBytes(rnd); uint256 hash; CHash256().Write(MakeUCharSpan(str)).Write(rnd).Finalize(hash); diff --git a/src/test/logging_tests.cpp b/src/test/logging_tests.cpp index cbfb6c67c3..5a5e3b3f1f 100644 --- a/src/test/logging_tests.cpp +++ b/src/test/logging_tests.cpp @@ -5,13 +5,55 @@ #include <logging.h> #include <logging/timer.h> #include <test/util/setup_common.h> +#include <util/string.h> #include <chrono> +#include <fstream> +#include <iostream> +#include <utility> +#include <vector> #include <boost/test/unit_test.hpp> BOOST_FIXTURE_TEST_SUITE(logging_tests, BasicTestingSetup) +struct LogSetup : public BasicTestingSetup { + fs::path prev_log_path; + fs::path tmp_log_path; + bool prev_reopen_file; + bool prev_print_to_file; + bool prev_log_timestamps; + bool prev_log_threadnames; + bool prev_log_sourcelocations; + + LogSetup() : prev_log_path{LogInstance().m_file_path}, + tmp_log_path{m_args.GetDataDirBase() / "tmp_debug.log"}, + prev_reopen_file{LogInstance().m_reopen_file}, + 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} + { + 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; + } + + ~LogSetup() + { + LogInstance().m_file_path = prev_log_path; + LogPrintf("Sentinel log to reopen log file\n"); + LogInstance().m_print_to_file = prev_print_to_file; + LogInstance().m_reopen_file = prev_reopen_file; + LogInstance().m_log_timestamps = prev_log_timestamps; + LogInstance().m_log_threadnames = prev_log_threadnames; + LogInstance().m_log_sourcelocations = prev_log_sourcelocations; + } +}; + BOOST_AUTO_TEST_CASE(logging_timer) { SetMockTime(1); @@ -30,4 +72,85 @@ BOOST_AUTO_TEST_CASE(logging_timer) BOOST_CHECK_EQUAL(sec_timer.LogMsg("test secs"), "tests: test secs (1.00s)"); } +BOOST_FIXTURE_TEST_CASE(logging_LogPrintf_, LogSetup) +{ + 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"); + LogPrintf_("fn4", "src4", 4, BCLog::LogFlags::NONE, BCLog::Level::None, "foo4: %s", "bar4\n"); + 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); + } + std::vector<std::string> expected = { + "[src1:1] [fn1] [net:debug] foo1: bar1", + "[src2:2] [fn2] [net] foo2: bar2", + "[src3:3] [fn3] [debug] foo3: bar3", + "[src4:4] [fn4] foo4: bar4", + }; + BOOST_CHECK_EQUAL_COLLECTIONS(log_lines.begin(), log_lines.end(), expected.begin(), expected.end()); +} + +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);) { + log_lines.push_back(log); + } + std::vector<std::string> expected = { + "foo5: bar5", + "[net] foo6: bar6", + "[net:debug] foo7: bar7", + "[net:info] foo8: bar8", + "[net:warning] foo9: bar9", + "[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(); + std::vector<std::pair<BCLog::LogFlags, std::string>> expected_category_names; + const auto category_names = SplitString(concated_categery_names, ','); + for (const auto& category_name : category_names) { + BCLog::LogFlags category = BCLog::NONE; + const auto trimmed_category_name = TrimString(category_name); + BOOST_TEST(GetLogCategory(category, trimmed_category_name)); + expected_category_names.emplace_back(category, trimmed_category_name); + } + + std::vector<std::string> expected; + for (const auto& [category, name] : expected_category_names) { + LogPrint(category, "foo: %s\n", "bar"); + std::string expected_log = "["; + expected_log += name; + expected_log += "] foo: bar"; + expected.push_back(expected_log); + } + + 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_AUTO_TEST_SUITE_END() diff --git a/src/test/mempool_tests.cpp b/src/test/mempool_tests.cpp index d6f24210eb..8c745b07b9 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; @@ -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; @@ -747,6 +753,15 @@ BOOST_AUTO_TEST_CASE(MempoolAncestryTests) pool.GetTransactionAncestry(ty6->GetHash(), ancestors, descendants); BOOST_CHECK_EQUAL(ancestors, 9ULL); BOOST_CHECK_EQUAL(descendants, 6ULL); +} + +BOOST_AUTO_TEST_CASE(MempoolAncestryTestsDiamond) +{ + size_t ancestors, descendants; + + CTxMemPool& pool = *Assert(m_node.mempool); + LOCK2(::cs_main, pool.cs); + TestMemPoolEntryHelper entry; /* Ancestors represented more than once ("diamond") */ // @@ -759,7 +774,6 @@ BOOST_AUTO_TEST_CASE(MempoolAncestryTests) tb = make_tx(/*output_values=*/{5 * COIN, 3 * COIN}, /*inputs=*/ {ta}); tc = make_tx(/*output_values=*/{2 * COIN}, /*inputs=*/{tb}, /*input_indices=*/{1}); td = make_tx(/*output_values=*/{6 * COIN}, /*inputs=*/{tb, tc}, /*input_indices=*/{0, 0}); - pool.clear(); pool.addUnchecked(entry.Fee(10000LL).FromTx(ta)); pool.addUnchecked(entry.Fee(10000LL).FromTx(tb)); pool.addUnchecked(entry.Fee(10000LL).FromTx(tc)); diff --git a/src/test/miner_tests.cpp b/src/test/miner_tests.cpp index c453dae701..d88aed7d4a 100644 --- a/src/test/miner_tests.cpp +++ b/src/test/miner_tests.cpp @@ -10,6 +10,7 @@ #include <node/miner.h> #include <policy/policy.h> #include <script/standard.h> +#include <timedata.h> #include <txmempool.h> #include <uint256.h> #include <util/strencodings.h> @@ -30,10 +31,12 @@ 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); - bool TestSequenceLocks(const CTransaction& tx, int flags) 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) { CCoinsViewMemPool view_mempool(&m_node.chainman->ActiveChainstate().CoinsTip(), *m_node.mempool); - return CheckSequenceLocks(m_node.chainman->ActiveChain().Tip(), view_mempool, tx, flags); + return CheckSequenceLocksAtTip(m_node.chainman->ActiveChain().Tip(), view_mempool, tx); } BlockAssembler AssemblerForTest(const CChainParams& params); }; @@ -49,7 +52,7 @@ BlockAssembler MinerTestingSetup::AssemblerForTest(const CChainParams& params) options.nBlockMaxWeight = MAX_BLOCK_WEIGHT; options.blockMinFeeRate = blockMinFeeRate; - return BlockAssembler(m_node.chainman->ActiveChainstate(), *m_node.mempool, params, options); + return BlockAssembler{m_node.chainman->ActiveChainstate(), m_node.mempool.get(), options}; } constexpr static struct { @@ -191,60 +194,17 @@ void MinerTestingSetup::TestPackageSelection(const CChainParams& chainparams, co BOOST_CHECK(pblocktemplate->block.vtx[8]->GetHash() == hashLowFeeTx2); } -// NOTE: These tests rely on CreateNewBlock doing its own self-validation! -BOOST_AUTO_TEST_CASE(CreateNewBlock_validity) +void MinerTestingSetup::TestBasicMining(const CChainParams& chainparams, const CScript& scriptPubKey, const std::vector<CTransactionRef>& txFirst, int baseheight) { - // 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; - CMutableTransaction tx; - CScript script; uint256 hash; + CMutableTransaction tx; TestMemPoolEntryHelper entry; entry.nFee = 11; entry.nHeight = 11; - fCheckpointsEnabled = false; - - // Simple block creation, nothing special yet: - BOOST_CHECK(pblocktemplate = AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey)); - - // We can't make transactions until we have inputs - // Therefore, load 110 blocks :) - static_assert(std::size(BLOCKINFO) == 110, "Should have 110 blocks to import"); - int baseheight = 0; - std::vector<CTransactionRef> txFirst; - for (const auto& bi : BLOCKINFO) { - CBlock *pblock = &pblocktemplate->block; // pointer for convenience - { - LOCK(cs_main); - pblock->nVersion = VERSIONBITS_TOP_BITS; - pblock->nTime = m_node.chainman->ActiveChain().Tip()->GetMedianTimePast()+1; - CMutableTransaction txCoinbase(*pblock->vtx[0]); - txCoinbase.nVersion = 1; - txCoinbase.vin[0].scriptSig = CScript{} << (m_node.chainman->ActiveChain().Height() + 1) << bi.extranonce; - txCoinbase.vout.resize(1); // Ignore the (optional) segwit commitment added by CreateNewBlock (as the hardcoded nonces don't account for this) - txCoinbase.vout[0].scriptPubKey = CScript(); - pblock->vtx[0] = MakeTransactionRef(std::move(txCoinbase)); - if (txFirst.size() == 0) - baseheight = m_node.chainman->ActiveChain().Height(); - if (txFirst.size() < 4) - txFirst.push_back(pblock->vtx[0]); - pblock->hashMerkleRoot = BlockMerkleRoot(*pblock); - pblock->nNonce = bi.nonce; - } - std::shared_ptr<const CBlock> shared_pblock = std::make_shared<const CBlock>(*pblock); - BOOST_CHECK(Assert(m_node.chainman)->ProcessNewBlock(chainparams, shared_pblock, true, nullptr)); - pblock->hashPrevBlock = pblock->GetHash(); - } - - LOCK(cs_main); - LOCK(m_node.mempool->cs); - // Just to make sure we can still make simple blocks - BOOST_CHECK(pblocktemplate = AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey)); + auto pblocktemplate = AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey); + BOOST_CHECK(pblocktemplate); const CAmount BLOCKSUBSIDY = 50*COIN; const CAmount LOWFEE = CENT; @@ -365,7 +325,7 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity) next->pprev = prev; next->nHeight = prev->nHeight + 1; next->BuildSkip(); - m_node.chainman->ActiveChain().SetTip(next); + m_node.chainman->ActiveChain().SetTip(*next); } BOOST_CHECK(pblocktemplate = AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey)); // Extend to a 210000-long block chain. @@ -377,7 +337,7 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity) next->pprev = prev; next->nHeight = prev->nHeight + 1; next->BuildSkip(); - m_node.chainman->ActiveChain().SetTip(next); + m_node.chainman->ActiveChain().SetTip(*next); } BOOST_CHECK(pblocktemplate = AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey)); @@ -386,7 +346,7 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity) tx.vin[0].prevout.n = 0; tx.vin[0].scriptSig = CScript() << OP_1; tx.vout[0].nValue = BLOCKSUBSIDY-LOWFEE; - script = CScript() << OP_0; + 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)); @@ -402,15 +362,15 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity) // 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->ActiveChain().SetTip(*Assert(del->pprev)); m_node.chainman->ActiveChainstate().CoinsTip().SetBestBlock(del->pprev->GetBlockHash()); delete del->phashBlock; delete del; } // non-final txs in mempool - SetMockTime(m_node.chainman->ActiveChain().Tip()->GetMedianTimePast()+1); - 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; @@ -429,8 +389,8 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity) tx.nLockTime = 0; hash = tx.GetHash(); m_node.mempool->addUnchecked(entry.Fee(HIGHFEE).Time(GetTime()).SpendsCoinbase(true).FromTx(tx)); - BOOST_CHECK(CheckFinalTx(m_node.chainman->ActiveChain().Tip(), CTransaction(tx), flags)); // Locktime passes - BOOST_CHECK(!TestSequenceLocks(CTransaction(tx), flags)); // Sequence locks fail + BOOST_CHECK(CheckFinalTxAtTip(*Assert(m_node.chainman->ActiveChain().Tip()), CTransaction{tx})); // Locktime passes + BOOST_CHECK(!TestSequenceLocks(CTransaction{tx})); // Sequence locks fail { CBlockIndex* active_chain_tip = m_node.chainman->ActiveChain().Tip(); @@ -443,19 +403,21 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity) prevheights[0] = baseheight + 2; hash = tx.GetHash(); m_node.mempool->addUnchecked(entry.Time(GetTime()).FromTx(tx)); - BOOST_CHECK(CheckFinalTx(m_node.chainman->ActiveChain().Tip(), CTransaction(tx), flags)); // Locktime passes - BOOST_CHECK(!TestSequenceLocks(CTransaction(tx), flags)); // Sequence locks fail - - for (int i = 0; i < CBlockIndex::nMedianTimeSpan; i++) - m_node.chainman->ActiveChain().Tip()->GetAncestor(m_node.chainman->ActiveChain().Tip()->nHeight - i)->nTime += 512; //Trick the MedianTimePast + BOOST_CHECK(CheckFinalTxAtTip(*Assert(m_node.chainman->ActiveChain().Tip()), CTransaction{tx})); // Locktime passes + BOOST_CHECK(!TestSequenceLocks(CTransaction{tx})); // Sequence locks fail + const int SEQUENCE_LOCK_TIME = 512; // Sequence locks pass 512 seconds later + for (int i = 0; i < CBlockIndex::nMedianTimeSpan; ++i) + m_node.chainman->ActiveChain().Tip()->GetAncestor(m_node.chainman->ActiveChain().Tip()->nHeight - i)->nTime += SEQUENCE_LOCK_TIME; // Trick the MedianTimePast { CBlockIndex* active_chain_tip = m_node.chainman->ActiveChain().Tip(); - BOOST_CHECK(SequenceLocks(CTransaction(tx), flags, prevheights, CreateBlockIndex(active_chain_tip->nHeight + 1, active_chain_tip))); // Sequence locks pass 512 seconds later + BOOST_CHECK(SequenceLocks(CTransaction(tx), flags, prevheights, CreateBlockIndex(active_chain_tip->nHeight + 1, active_chain_tip))); } - for (int i = 0; i < CBlockIndex::nMedianTimeSpan; i++) - m_node.chainman->ActiveChain().Tip()->GetAncestor(m_node.chainman->ActiveChain().Tip()->nHeight - i)->nTime -= 512; //undo tricked MTP + for (int i = 0; i < CBlockIndex::nMedianTimeSpan; ++i) { + CBlockIndex* ancestor{Assert(m_node.chainman->ActiveChain().Tip()->GetAncestor(m_node.chainman->ActiveChain().Tip()->nHeight - i))}; + ancestor->nTime -= SEQUENCE_LOCK_TIME; // undo tricked MTP + } // absolute height locked tx.vin[0].prevout.hash = txFirst[2]->GetHash(); @@ -464,8 +426,8 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity) tx.nLockTime = m_node.chainman->ActiveChain().Tip()->nHeight + 1; hash = tx.GetHash(); m_node.mempool->addUnchecked(entry.Time(GetTime()).FromTx(tx)); - BOOST_CHECK(!CheckFinalTx(m_node.chainman->ActiveChain().Tip(), CTransaction(tx), flags)); // Locktime fails - BOOST_CHECK(TestSequenceLocks(CTransaction(tx), flags)); // Sequence locks pass + BOOST_CHECK(!CheckFinalTxAtTip(*Assert(m_node.chainman->ActiveChain().Tip()), CTransaction{tx})); // Locktime fails + BOOST_CHECK(TestSequenceLocks(CTransaction{tx})); // 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 @@ -475,8 +437,8 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity) prevheights[0] = baseheight + 4; hash = tx.GetHash(); m_node.mempool->addUnchecked(entry.Time(GetTime()).FromTx(tx)); - BOOST_CHECK(!CheckFinalTx(m_node.chainman->ActiveChain().Tip(), CTransaction(tx), flags)); // Locktime fails - BOOST_CHECK(TestSequenceLocks(CTransaction(tx), flags)); // Sequence locks pass + BOOST_CHECK(!CheckFinalTxAtTip(*Assert(m_node.chainman->ActiveChain().Tip()), CTransaction{tx})); // Locktime fails + BOOST_CHECK(TestSequenceLocks(CTransaction{tx})); // 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) @@ -484,14 +446,14 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity) prevheights[0] = m_node.chainman->ActiveChain().Tip()->nHeight + 1; tx.nLockTime = 0; tx.vin[0].nSequence = 0; - BOOST_CHECK(CheckFinalTx(m_node.chainman->ActiveChain().Tip(), CTransaction(tx), flags)); // Locktime passes - BOOST_CHECK(TestSequenceLocks(CTransaction(tx), flags)); // Sequence locks pass + BOOST_CHECK(CheckFinalTxAtTip(*Assert(m_node.chainman->ActiveChain().Tip()), CTransaction{tx})); // Locktime passes + BOOST_CHECK(TestSequenceLocks(CTransaction{tx})); // Sequence locks pass tx.vin[0].nSequence = 1; - BOOST_CHECK(!TestSequenceLocks(CTransaction(tx), flags)); // Sequence locks fail + BOOST_CHECK(!TestSequenceLocks(CTransaction{tx})); // Sequence locks fail tx.vin[0].nSequence = CTxIn::SEQUENCE_LOCKTIME_TYPE_FLAG; - BOOST_CHECK(TestSequenceLocks(CTransaction(tx), flags)); // Sequence locks pass + BOOST_CHECK(TestSequenceLocks(CTransaction{tx})); // Sequence locks pass tx.vin[0].nSequence = CTxIn::SEQUENCE_LOCKTIME_TYPE_FLAG | 1; - BOOST_CHECK(!TestSequenceLocks(CTransaction(tx), flags)); // Sequence locks fail + BOOST_CHECK(!TestSequenceLocks(CTransaction{tx})); // Sequence locks fail BOOST_CHECK(pblocktemplate = AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey)); @@ -500,14 +462,140 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity) // but relative locked txs will if inconsistently added to mempool. // For now these will still generate a valid template until BIP68 soft fork BOOST_CHECK_EQUAL(pblocktemplate->block.vtx.size(), 3U); - // However if we advance height by 1 and time by 512, all of them should be mined - for (int i = 0; i < CBlockIndex::nMedianTimeSpan; i++) - m_node.chainman->ActiveChain().Tip()->GetAncestor(m_node.chainman->ActiveChain().Tip()->nHeight - i)->nTime += 512; //Trick the MedianTimePast + // However if we advance height by 1 and time by SEQUENCE_LOCK_TIME, all of them should be mined + for (int i = 0; i < CBlockIndex::nMedianTimeSpan; ++i) { + CBlockIndex* ancestor{Assert(m_node.chainman->ActiveChain().Tip()->GetAncestor(m_node.chainman->ActiveChain().Tip()->nHeight - i))}; + ancestor->nTime += SEQUENCE_LOCK_TIME; // Trick the MedianTimePast + } m_node.chainman->ActiveChain().Tip()->nHeight++; SetMockTime(m_node.chainman->ActiveChain().Tip()->GetMedianTimePast() + 1); BOOST_CHECK(pblocktemplate = AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey)); BOOST_CHECK_EQUAL(pblocktemplate->block.vtx.size(), 5U); +} + +void MinerTestingSetup::TestPrioritisedMining(const CChainParams& chainparams, const CScript& scriptPubKey, const std::vector<CTransactionRef>& txFirst) +{ + TestMemPoolEntryHelper entry; + + // Test that a tx below min fee but prioritised is included + CMutableTransaction tx; + tx.vin.resize(1); + tx.vin[0].prevout.hash = txFirst[0]->GetHash(); + tx.vin[0].prevout.n = 0; + tx.vin[0].scriptSig = CScript() << OP_1; + 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.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)); + + // 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); + + // 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); + + // 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 + // included by itself: FreeParent <- FreeChild <- FreeGrandchild. + // When FreeParent is added, a modified entry will be created for FreeChild + FreeGrandchild + // FreeParent's prioritisation should not be included in that entry. + // When FreeChild is included, FreeChild's prioritisation should also not be included. + 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.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.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)); + + auto pblocktemplate = AssemblerForTest(chainparams).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); + BOOST_CHECK(pblocktemplate->block.vtx[3]->GetHash() == hashParentTx); + BOOST_CHECK(pblocktemplate->block.vtx[4]->GetHash() == hashPrioritsedChild); + BOOST_CHECK(pblocktemplate->block.vtx[5]->GetHash() == hashFreeChild); + for (size_t i=0; i<pblocktemplate->block.vtx.size(); ++i) { + // The FreeParent and FreeChild's prioritisations should not impact the child. + BOOST_CHECK(pblocktemplate->block.vtx[i]->GetHash() != hashFreeGrandchild); + // De-prioritised transaction should not be included. + BOOST_CHECK(pblocktemplate->block.vtx[i]->GetHash() != hashMediumFeeTx); + } +} + +// NOTE: These tests rely on CreateNewBlock doing its own self-validation! +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; + + // Simple block creation, nothing special yet: + BOOST_CHECK(pblocktemplate = AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey)); + + // We can't make transactions until we have inputs + // Therefore, load 110 blocks :) + static_assert(std::size(BLOCKINFO) == 110, "Should have 110 blocks to import"); + int baseheight = 0; + std::vector<CTransactionRef> txFirst; + for (const auto& bi : BLOCKINFO) { + CBlock *pblock = &pblocktemplate->block; // pointer for convenience + { + LOCK(cs_main); + pblock->nVersion = VERSIONBITS_TOP_BITS; + pblock->nTime = m_node.chainman->ActiveChain().Tip()->GetMedianTimePast()+1; + CMutableTransaction txCoinbase(*pblock->vtx[0]); + txCoinbase.nVersion = 1; + txCoinbase.vin[0].scriptSig = CScript{} << (m_node.chainman->ActiveChain().Height() + 1) << bi.extranonce; + txCoinbase.vout.resize(1); // Ignore the (optional) segwit commitment added by CreateNewBlock (as the hardcoded nonces don't account for this) + txCoinbase.vout[0].scriptPubKey = CScript(); + pblock->vtx[0] = MakeTransactionRef(std::move(txCoinbase)); + if (txFirst.size() == 0) + baseheight = m_node.chainman->ActiveChain().Height(); + if (txFirst.size() < 4) + txFirst.push_back(pblock->vtx[0]); + pblock->hashMerkleRoot = BlockMerkleRoot(*pblock); + 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)); + pblock->hashPrevBlock = pblock->GetHash(); + } + + LOCK(cs_main); + LOCK(m_node.mempool->cs); + + TestBasicMining(chainparams, scriptPubKey, txFirst, baseheight); m_node.chainman->ActiveChain().Tip()->nHeight--; SetMockTime(0); @@ -515,6 +603,12 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity) TestPackageSelection(chainparams, scriptPubKey, txFirst); + m_node.chainman->ActiveChain().Tip()->nHeight--; + SetMockTime(0); + m_node.mempool->clear(); + + TestPrioritisedMining(chainparams, scriptPubKey, txFirst); + fCheckpointsEnabled = true; } diff --git a/src/test/miniscript_tests.cpp b/src/test/miniscript_tests.cpp new file mode 100644 index 0000000000..95e8476b77 --- /dev/null +++ b/src/test/miniscript_tests.cpp @@ -0,0 +1,326 @@ +// 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 <string> + +#include <test/util/setup_common.h> +#include <boost/test/unit_test.hpp> + +#include <hash.h> +#include <pubkey.h> +#include <uint256.h> +#include <crypto/ripemd160.h> +#include <crypto/sha256.h> +#include <script/miniscript.h> + +namespace { + +/** TestData groups various kinds of precomputed data necessary in this test. */ +struct TestData { + //! The only public keys used in this test. + std::vector<CPubKey> pubkeys; + //! A map from the public keys to their CKeyIDs (faster than hashing every time). + std::map<CPubKey, CKeyID> pkhashes; + std::map<CKeyID, CPubKey> pkmap; + + // Various precomputed hashes + std::vector<std::vector<unsigned char>> sha256; + std::vector<std::vector<unsigned char>> ripemd160; + std::vector<std::vector<unsigned char>> hash256; + std::vector<std::vector<unsigned char>> hash160; + + TestData() + { + // We generate 255 public keys and 255 hashes of each type. + for (int i = 1; i <= 255; ++i) { + // This 32-byte array functions as both private key data and hash preimage (31 zero bytes plus any nonzero byte). + unsigned char keydata[32] = {0}; + keydata[31] = i; + + // Compute CPubkey and CKeyID + CKey key; + key.Set(keydata, keydata + 32, true); + CPubKey pubkey = key.GetPubKey(); + CKeyID keyid = pubkey.GetID(); + pubkeys.push_back(pubkey); + pkhashes.emplace(pubkey, keyid); + pkmap.emplace(keyid, pubkey); + + // Compute various hashes + std::vector<unsigned char> hash; + hash.resize(32); + CSHA256().Write(keydata, 32).Finalize(hash.data()); + sha256.push_back(hash); + CHash256().Write(keydata).Finalize(hash); + hash256.push_back(hash); + hash.resize(20); + CRIPEMD160().Write(keydata, 32).Finalize(hash.data()); + ripemd160.push_back(hash); + CHash160().Write(keydata).Finalize(hash); + hash160.push_back(hash); + } + } +}; + +//! Global TestData object +std::unique_ptr<const TestData> g_testdata; + +/** A class encapsulating conversion routing for CPubKey. */ +struct KeyConverter { + typedef CPubKey Key; + + bool KeyCompare(const Key& a, const Key& b) const { + return a < b; + } + + //! Convert a public key to bytes. + std::vector<unsigned char> ToPKBytes(const CPubKey& key) const { return {key.begin(), key.end()}; } + + //! Convert a public key to its Hash160 bytes (precomputed). + std::vector<unsigned char> ToPKHBytes(const CPubKey& key) const + { + auto it = g_testdata->pkhashes.find(key); + assert(it != g_testdata->pkhashes.end()); + return {it->second.begin(), it->second.end()}; + } + + //! Parse a public key from a range of hex characters. + template<typename I> + std::optional<Key> FromString(I first, I last) const { + auto bytes = ParseHex(std::string(first, last)); + Key key{bytes.begin(), bytes.end()}; + if (key.IsValid()) return key; + return {}; + } + + template<typename I> + std::optional<Key> FromPKBytes(I first, I last) const { + Key key{first, last}; + if (key.IsValid()) return key; + return {}; + } + + template<typename I> + std::optional<Key> FromPKHBytes(I first, I last) const { + assert(last - first == 20); + CKeyID keyid; + std::copy(first, last, keyid.begin()); + auto it = g_testdata->pkmap.find(keyid); + 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 { + TESTMODE_INVALID = 0, + TESTMODE_VALID = 1, + TESTMODE_NONMAL = 2, + TESTMODE_NEEDSIG = 4, + TESTMODE_TIMELOCKMIX = 8 +}; + +void Test(const std::string& ms, const std::string& hexscript, int mode, int opslimit = -1, int stacklimit = -1) +{ + auto node = miniscript::FromString(ms, CONVERTER); + if (mode == TESTMODE_INVALID) { + BOOST_CHECK_MESSAGE(!node || !node->IsValid(), "Unexpectedly valid: " + ms); + } else { + BOOST_CHECK_MESSAGE(node, "Unparseable: " + ms); + BOOST_CHECK_MESSAGE(node->IsValid(), "Invalid: " + ms); + BOOST_CHECK_MESSAGE(node->IsValidTopLevel(), "Invalid top level: " + ms); + auto computed_script = node->ToScript(CONVERTER); + BOOST_CHECK_MESSAGE(node->ScriptSize() == computed_script.size(), "Script size mismatch: " + ms); + if (hexscript != "?") BOOST_CHECK_MESSAGE(HexStr(computed_script) == hexscript, "Script mismatch: " + ms + " (" + HexStr(computed_script) + " vs " + hexscript + ")"); + BOOST_CHECK_MESSAGE(node->IsNonMalleable() == !!(mode & TESTMODE_NONMAL), "Malleability mismatch: " + ms); + BOOST_CHECK_MESSAGE(node->NeedsSignature() == !!(mode & TESTMODE_NEEDSIG), "Signature necessity mismatch: " + ms); + BOOST_CHECK_MESSAGE((node->GetType() << "k"_mst) == !(mode & TESTMODE_TIMELOCKMIX), "Timelock mix mismatch: " + ms); + auto inferred_miniscript = miniscript::FromScript(computed_script, CONVERTER); + BOOST_CHECK_MESSAGE(inferred_miniscript, "Cannot infer miniscript from script: " + ms); + BOOST_CHECK_MESSAGE(inferred_miniscript->ToScript(CONVERTER) == computed_script, "Roundtrip failure: miniscript->script != miniscript->script->miniscript->script: " + ms); + if (opslimit != -1) BOOST_CHECK_MESSAGE((int)node->GetOps() == opslimit, "Ops limit mismatch: " << ms << " (" << node->GetOps() << " vs " << opslimit << ")"); + if (stacklimit != -1) BOOST_CHECK_MESSAGE((int)node->GetStackSize() == stacklimit, "Stack limit mismatch: " << ms << " (" << node->GetStackSize() << " vs " << stacklimit << ")"); + } +} +} // namespace + +BOOST_FIXTURE_TEST_SUITE(miniscript_tests, BasicTestingSetup) + +BOOST_AUTO_TEST_CASE(fixed_tests) +{ + g_testdata.reset(new TestData()); + + // Validity rules + Test("l:older(1)", "?", TESTMODE_VALID | TESTMODE_NONMAL); // older(1): valid + Test("l:older(0)", "?", TESTMODE_INVALID); // older(0): k must be at least 1 + Test("l:older(2147483647)", "?", TESTMODE_VALID | TESTMODE_NONMAL); // older(2147483647): valid + Test("l:older(2147483648)", "?", TESTMODE_INVALID); // older(2147483648): k must be below 2^31 + Test("u:after(1)", "?", TESTMODE_VALID | TESTMODE_NONMAL); // after(1): valid + Test("u:after(0)", "?", TESTMODE_INVALID); // after(0): k must be at least 1 + Test("u:after(2147483647)", "?", TESTMODE_VALID | TESTMODE_NONMAL); // after(2147483647): valid + Test("u:after(2147483648)", "?", TESTMODE_INVALID); // after(2147483648): k must be below 2^31 + Test("andor(0,1,1)", "?", TESTMODE_VALID | TESTMODE_NONMAL); // andor(Bdu,B,B): valid + Test("andor(a:0,1,1)", "?", TESTMODE_INVALID); // andor(Wdu,B,B): X must be B + Test("andor(0,a:1,a:1)", "?", TESTMODE_INVALID); // andor(Bdu,W,W): Y and Z must be B/V/K + Test("andor(1,1,1)", "?", TESTMODE_INVALID); // andor(Bu,B,B): X must be d + Test("andor(n:or_i(0,after(1)),1,1)", "?", TESTMODE_VALID); // andor(Bdu,B,B): valid + Test("andor(or_i(0,after(1)),1,1)", "?", TESTMODE_INVALID); // andor(Bd,B,B): X must be u + Test("c:andor(0,pk_k(03a0434d9e47f3c86235477c7b1ae6ae5d3442d49b1943c2b752a68e2a47e247c7),pk_k(036d2b085e9e382ed10b69fc311a03f8641ccfff21574de0927513a49d9a688a00))", "?", TESTMODE_VALID | TESTMODE_NONMAL | TESTMODE_NEEDSIG); // andor(Bdu,K,K): valid + Test("t:andor(0,v:1,v:1)", "?", TESTMODE_VALID | TESTMODE_NONMAL); // andor(Bdu,V,V): valid + Test("and_v(v:1,1)", "?", TESTMODE_VALID | TESTMODE_NONMAL); // and_v(V,B): valid + Test("t:and_v(v:1,v:1)", "?", TESTMODE_VALID | TESTMODE_NONMAL); // and_v(V,V): valid + Test("c:and_v(v:1,pk_k(036d2b085e9e382ed10b69fc311a03f8641ccfff21574de0927513a49d9a688a00))", "?", TESTMODE_VALID | TESTMODE_NONMAL | TESTMODE_NEEDSIG); // and_v(V,K): valid + Test("and_v(1,1)", "?", TESTMODE_INVALID); // and_v(B,B): X must be V + Test("and_v(pk_k(02352bbf4a4cdd12564f93fa332ce333301d9ad40271f8107181340aef25be59d5),1)", "?", TESTMODE_INVALID); // and_v(K,B): X must be V + Test("and_v(v:1,a:1)", "?", TESTMODE_INVALID); // and_v(K,W): Y must be B/V/K + Test("and_b(1,a:1)", "?", TESTMODE_VALID | TESTMODE_NONMAL); // and_b(B,W): valid + Test("and_b(1,1)", "?", TESTMODE_INVALID); // and_b(B,B): Y must W + Test("and_b(v:1,a:1)", "?", TESTMODE_INVALID); // and_b(V,W): X must be B + Test("and_b(a:1,a:1)", "?", TESTMODE_INVALID); // and_b(W,W): X must be B + Test("and_b(pk_k(025601570cb47f238d2b0286db4a990fa0f3ba28d1a319f5e7cf55c2a2444da7cc),a:1)", "?", TESTMODE_INVALID); // and_b(K,W): X must be B + Test("or_b(0,a:0)", "?", TESTMODE_VALID | TESTMODE_NONMAL | TESTMODE_NEEDSIG); // or_b(Bd,Wd): valid + Test("or_b(1,a:0)", "?", TESTMODE_INVALID); // or_b(B,Wd): X must be d + Test("or_b(0,a:1)", "?", TESTMODE_INVALID); // or_b(Bd,W): Y must be d + Test("or_b(0,0)", "?", TESTMODE_INVALID); // or_b(Bd,Bd): Y must W + Test("or_b(v:0,a:0)", "?", TESTMODE_INVALID); // or_b(V,Wd): X must be B + Test("or_b(a:0,a:0)", "?", TESTMODE_INVALID); // or_b(Wd,Wd): X must be B + Test("or_b(pk_k(025601570cb47f238d2b0286db4a990fa0f3ba28d1a319f5e7cf55c2a2444da7cc),a:0)", "?", TESTMODE_INVALID); // or_b(Kd,Wd): X must be B + Test("t:or_c(0,v:1)", "?", TESTMODE_VALID | TESTMODE_NONMAL); // or_c(Bdu,V): valid + Test("t:or_c(a:0,v:1)", "?", TESTMODE_INVALID); // or_c(Wdu,V): X must be B + Test("t:or_c(1,v:1)", "?", TESTMODE_INVALID); // or_c(Bu,V): X must be d + Test("t:or_c(n:or_i(0,after(1)),v:1)", "?", TESTMODE_VALID); // or_c(Bdu,V): valid + Test("t:or_c(or_i(0,after(1)),v:1)", "?", TESTMODE_INVALID); // or_c(Bd,V): X must be u + Test("t:or_c(0,1)", "?", TESTMODE_INVALID); // or_c(Bdu,B): Y must be V + Test("or_d(0,1)", "?", TESTMODE_VALID | TESTMODE_NONMAL); // or_d(Bdu,B): valid + Test("or_d(a:0,1)", "?", TESTMODE_INVALID); // or_d(Wdu,B): X must be B + Test("or_d(1,1)", "?", TESTMODE_INVALID); // or_d(Bu,B): X must be d + Test("or_d(n:or_i(0,after(1)),1)", "?", TESTMODE_VALID); // or_d(Bdu,B): valid + Test("or_d(or_i(0,after(1)),1)", "?", TESTMODE_INVALID); // or_d(Bd,B): X must be u + Test("or_d(0,v:1)", "?", TESTMODE_INVALID); // or_d(Bdu,V): Y must be B + Test("or_i(1,1)", "?", TESTMODE_VALID); // or_i(B,B): valid + Test("t:or_i(v:1,v:1)", "?", TESTMODE_VALID); // or_i(V,V): valid + Test("c:or_i(pk_k(03a0434d9e47f3c86235477c7b1ae6ae5d3442d49b1943c2b752a68e2a47e247c7),pk_k(036d2b085e9e382ed10b69fc311a03f8641ccfff21574de0927513a49d9a688a00))", "?", TESTMODE_VALID | TESTMODE_NONMAL | TESTMODE_NEEDSIG); // or_i(K,K): valid + Test("or_i(a:1,a:1)", "?", TESTMODE_INVALID); // or_i(W,W): X and Y must be B/V/K + Test("or_b(l:after(100),al:after(1000000000))", "?", TESTMODE_VALID); // or_b(timelock, heighlock) valid + Test("and_b(after(100),a:after(1000000000))", "?", TESTMODE_VALID | TESTMODE_NONMAL | TESTMODE_TIMELOCKMIX); // and_b(timelock, heighlock) invalid + Test("pk(03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65)", "2103d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65ac", TESTMODE_VALID | TESTMODE_NONMAL | TESTMODE_NEEDSIG); // alias to c:pk_k + Test("pkh(03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65)", "76a914fcd35ddacad9f2d5be5e464639441c6065e6955d88ac", TESTMODE_VALID | TESTMODE_NONMAL | TESTMODE_NEEDSIG); // alias to c:pk_h + + + // Randomly generated test set that covers the majority of type and node type combinations + Test("lltvln:after(1231488000)", "6300676300676300670400046749b1926869516868", TESTMODE_VALID | TESTMODE_NONMAL, 12, 4); + Test("uuj:and_v(v:multi(2,03d01115d548e7561b15c38f004d734633687cf4419620095bc5b0f47070afe85a,025601570cb47f238d2b0286db4a990fa0f3ba28d1a319f5e7cf55c2a2444da7cc),after(1231488000))", "6363829263522103d01115d548e7561b15c38f004d734633687cf4419620095bc5b0f47070afe85a21025601570cb47f238d2b0286db4a990fa0f3ba28d1a319f5e7cf55c2a2444da7cc52af0400046749b168670068670068", TESTMODE_VALID | TESTMODE_NONMAL | TESTMODE_NEEDSIG, 14, 6); + Test("or_b(un:multi(2,03daed4f2be3a8bf278e70132fb0beb7522f570e144bf615c07e996d443dee8729,024ce119c96e2fa357200b559b2f7dd5a5f02d5290aff74b03f3e471b273211c97),al:older(16))", "63522103daed4f2be3a8bf278e70132fb0beb7522f570e144bf615c07e996d443dee872921024ce119c96e2fa357200b559b2f7dd5a5f02d5290aff74b03f3e471b273211c9752ae926700686b63006760b2686c9b", TESTMODE_VALID, 14, 6); + Test("j:and_v(vdv:after(1567547623),older(2016))", "829263766304e7e06e5db169686902e007b268", TESTMODE_VALID | TESTMODE_NONMAL, 11, 2); + Test("t:and_v(vu:hash256(131772552c01444cd81360818376a040b7c3b2b7b0a53550ee3edde216cec61b),v:sha256(ec4916dd28fc4c10d78e287ca5d9cc51ee1ae73cbfde08c6b37324cbfaac8bc5))", "6382012088aa20131772552c01444cd81360818376a040b7c3b2b7b0a53550ee3edde216cec61b876700686982012088a820ec4916dd28fc4c10d78e287ca5d9cc51ee1ae73cbfde08c6b37324cbfaac8bc58851", TESTMODE_VALID | TESTMODE_NONMAL, 12, 4); + Test("t:andor(multi(3,02d7924d4f7d43ea965a465ae3095ff41131e5946f3c85f79e44adbcf8e27e080e,03fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556,02e493dbf1c10d80f3581e4904930b1404cc6c13900ee0758474fa94abe8c4cd13),v:older(4194305),v:sha256(9267d3dbed802941483f1afa2a6bc68de5f653128aca9bf1461c5d0a3ad36ed2))", "532102d7924d4f7d43ea965a465ae3095ff41131e5946f3c85f79e44adbcf8e27e080e2103fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a14602975562102e493dbf1c10d80f3581e4904930b1404cc6c13900ee0758474fa94abe8c4cd1353ae6482012088a8209267d3dbed802941483f1afa2a6bc68de5f653128aca9bf1461c5d0a3ad36ed2886703010040b2696851", TESTMODE_VALID | TESTMODE_NONMAL, 13, 6); + Test("or_d(multi(1,02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9),or_b(multi(3,022f01e5e15cca351daff3843fb70f3c2f0a1bdd05e5af888a67784ef3e10a2a01,032fa2104d6b38d11b0230010559879124e42ab8dfeff5ff29dc9cdadd4ecacc3f,03d01115d548e7561b15c38f004d734633687cf4419620095bc5b0f47070afe85a),su:after(500000)))", "512102f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f951ae73645321022f01e5e15cca351daff3843fb70f3c2f0a1bdd05e5af888a67784ef3e10a2a0121032fa2104d6b38d11b0230010559879124e42ab8dfeff5ff29dc9cdadd4ecacc3f2103d01115d548e7561b15c38f004d734633687cf4419620095bc5b0f47070afe85a53ae7c630320a107b16700689b68", TESTMODE_VALID | TESTMODE_NONMAL, 15, 8); + Test("or_d(sha256(38df1c1f64a24a77b23393bca50dff872e31edc4f3b5aa3b90ad0b82f4f089b6),and_n(un:after(499999999),older(4194305)))", "82012088a82038df1c1f64a24a77b23393bca50dff872e31edc4f3b5aa3b90ad0b82f4f089b68773646304ff64cd1db19267006864006703010040b26868", TESTMODE_VALID, 16, 2); + Test("and_v(or_i(v:multi(2,02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5,03774ae7f858a9411e5ef4246b70c65aac5649980be5c17891bbec17895da008cb),v:multi(2,03e60fce93b59e9ec53011aabc21c23e97b2a31369b87a5ae9c44ee89e2a6dec0a,025cbdf0646e5db4eaa398f365f2ea7a0e3d419b7e0330e39ce92bddedcac4f9bc)),sha256(d1ec675902ef1633427ca360b290b0b3045a0d9058ddb5e648b4c3c3224c5c68))", "63522102c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee52103774ae7f858a9411e5ef4246b70c65aac5649980be5c17891bbec17895da008cb52af67522103e60fce93b59e9ec53011aabc21c23e97b2a31369b87a5ae9c44ee89e2a6dec0a21025cbdf0646e5db4eaa398f365f2ea7a0e3d419b7e0330e39ce92bddedcac4f9bc52af6882012088a820d1ec675902ef1633427ca360b290b0b3045a0d9058ddb5e648b4c3c3224c5c6887", TESTMODE_VALID | TESTMODE_NONMAL | TESTMODE_NEEDSIG, 11, 6); + Test("j:and_b(multi(2,0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798,024ce119c96e2fa357200b559b2f7dd5a5f02d5290aff74b03f3e471b273211c97),s:or_i(older(1),older(4252898)))", "82926352210279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f8179821024ce119c96e2fa357200b559b2f7dd5a5f02d5290aff74b03f3e471b273211c9752ae7c6351b26703e2e440b2689a68", TESTMODE_VALID | TESTMODE_NEEDSIG, 14, 5); + Test("and_b(older(16),s:or_d(sha256(e38990d0c7fc009880a9c07c23842e886c6bbdc964ce6bdd5817ad357335ee6f),n:after(1567547623)))", "60b27c82012088a820e38990d0c7fc009880a9c07c23842e886c6bbdc964ce6bdd5817ad357335ee6f87736404e7e06e5db192689a", TESTMODE_VALID, 12, 2); + Test("j:and_v(v:hash160(20195b5a3d650c17f0f29f91c33f8f6335193d07),or_d(sha256(96de8fc8c256fa1e1556d41af431cace7dca68707c78dd88c3acab8b17164c47),older(16)))", "82926382012088a91420195b5a3d650c17f0f29f91c33f8f6335193d078882012088a82096de8fc8c256fa1e1556d41af431cace7dca68707c78dd88c3acab8b17164c4787736460b26868", TESTMODE_VALID, 16, 3); + Test("and_b(hash256(32ba476771d01e37807990ead8719f08af494723de1d228f2c2c07cc0aa40bac),a:and_b(hash256(131772552c01444cd81360818376a040b7c3b2b7b0a53550ee3edde216cec61b),a:older(1)))", "82012088aa2032ba476771d01e37807990ead8719f08af494723de1d228f2c2c07cc0aa40bac876b82012088aa20131772552c01444cd81360818376a040b7c3b2b7b0a53550ee3edde216cec61b876b51b26c9a6c9a", TESTMODE_VALID | TESTMODE_NONMAL, 15, 3); + Test("thresh(2,multi(2,03a0434d9e47f3c86235477c7b1ae6ae5d3442d49b1943c2b752a68e2a47e247c7,036d2b085e9e382ed10b69fc311a03f8641ccfff21574de0927513a49d9a688a00),a:multi(1,036d2b085e9e382ed10b69fc311a03f8641ccfff21574de0927513a49d9a688a00),ac:pk_k(022f01e5e15cca351daff3843fb70f3c2f0a1bdd05e5af888a67784ef3e10a2a01))", "522103a0434d9e47f3c86235477c7b1ae6ae5d3442d49b1943c2b752a68e2a47e247c721036d2b085e9e382ed10b69fc311a03f8641ccfff21574de0927513a49d9a688a0052ae6b5121036d2b085e9e382ed10b69fc311a03f8641ccfff21574de0927513a49d9a688a0051ae6c936b21022f01e5e15cca351daff3843fb70f3c2f0a1bdd05e5af888a67784ef3e10a2a01ac6c935287", TESTMODE_VALID | TESTMODE_NONMAL | TESTMODE_NEEDSIG, 13, 7); + Test("and_n(sha256(d1ec675902ef1633427ca360b290b0b3045a0d9058ddb5e648b4c3c3224c5c68),t:or_i(v:older(4252898),v:older(144)))", "82012088a820d1ec675902ef1633427ca360b290b0b3045a0d9058ddb5e648b4c3c3224c5c68876400676303e2e440b26967029000b269685168", TESTMODE_VALID, 14, 3); + Test("or_d(nd:and_v(v:older(4252898),v:older(4252898)),sha256(38df1c1f64a24a77b23393bca50dff872e31edc4f3b5aa3b90ad0b82f4f089b6))", "766303e2e440b26903e2e440b2696892736482012088a82038df1c1f64a24a77b23393bca50dff872e31edc4f3b5aa3b90ad0b82f4f089b68768", TESTMODE_VALID, 15, 3); + Test("c:and_v(or_c(sha256(9267d3dbed802941483f1afa2a6bc68de5f653128aca9bf1461c5d0a3ad36ed2),v:multi(1,02c44d12c7065d812e8acf28d7cbb19f9011ecd9e9fdf281b0e6a3b5e87d22e7db)),pk_k(03acd484e2f0c7f65309ad178a9f559abde09796974c57e714c35f110dfc27ccbe))", "82012088a8209267d3dbed802941483f1afa2a6bc68de5f653128aca9bf1461c5d0a3ad36ed28764512102c44d12c7065d812e8acf28d7cbb19f9011ecd9e9fdf281b0e6a3b5e87d22e7db51af682103acd484e2f0c7f65309ad178a9f559abde09796974c57e714c35f110dfc27ccbeac", TESTMODE_VALID | TESTMODE_NEEDSIG, 8, 3); + Test("c:and_v(or_c(multi(2,036d2b085e9e382ed10b69fc311a03f8641ccfff21574de0927513a49d9a688a00,02352bbf4a4cdd12564f93fa332ce333301d9ad40271f8107181340aef25be59d5),v:ripemd160(1b0f3c404d12075c68c938f9f60ebea4f74941a0)),pk_k(03fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556))", "5221036d2b085e9e382ed10b69fc311a03f8641ccfff21574de0927513a49d9a688a002102352bbf4a4cdd12564f93fa332ce333301d9ad40271f8107181340aef25be59d552ae6482012088a6141b0f3c404d12075c68c938f9f60ebea4f74941a088682103fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556ac", TESTMODE_VALID | TESTMODE_NONMAL | TESTMODE_NEEDSIG, 10, 6); + Test("and_v(andor(hash256(8a35d9ca92a48eaade6f53a64985e9e2afeb74dcf8acb4c3721e0dc7e4294b25),v:hash256(939894f70e6c3a25da75da0cc2071b4076d9b006563cf635986ada2e93c0d735),v:older(50000)),after(499999999))", "82012088aa208a35d9ca92a48eaade6f53a64985e9e2afeb74dcf8acb4c3721e0dc7e4294b2587640350c300b2696782012088aa20939894f70e6c3a25da75da0cc2071b4076d9b006563cf635986ada2e93c0d735886804ff64cd1db1", TESTMODE_VALID, 14, 3); + Test("andor(hash256(5f8d30e655a7ba0d7596bb3ddfb1d2d20390d23b1845000e1e118b3be1b3f040),j:and_v(v:hash160(3a2bff0da9d96868e66abc4427bea4691cf61ccd),older(4194305)),ripemd160(44d90e2d3714c8663b632fcf0f9d5f22192cc4c8))", "82012088aa205f8d30e655a7ba0d7596bb3ddfb1d2d20390d23b1845000e1e118b3be1b3f040876482012088a61444d90e2d3714c8663b632fcf0f9d5f22192cc4c8876782926382012088a9143a2bff0da9d96868e66abc4427bea4691cf61ccd8803010040b26868", TESTMODE_VALID, 20, 3); + Test("or_i(c:and_v(v:after(500000),pk_k(02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5)),sha256(d9147961436944f43cd99d28b2bbddbf452ef872b30c8279e255e7daafc7f946))", "630320a107b1692102c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5ac6782012088a820d9147961436944f43cd99d28b2bbddbf452ef872b30c8279e255e7daafc7f9468768", TESTMODE_VALID | TESTMODE_NONMAL, 10, 3); + Test("thresh(2,c:pk_h(025cbdf0646e5db4eaa398f365f2ea7a0e3d419b7e0330e39ce92bddedcac4f9bc),s:sha256(e38990d0c7fc009880a9c07c23842e886c6bbdc964ce6bdd5817ad357335ee6f),a:hash160(dd69735817e0e3f6f826a9238dc2e291184f0131))", "76a9145dedfbf9ea599dd4e3ca6a80b333c472fd0b3f6988ac7c82012088a820e38990d0c7fc009880a9c07c23842e886c6bbdc964ce6bdd5817ad357335ee6f87936b82012088a914dd69735817e0e3f6f826a9238dc2e291184f0131876c935287", TESTMODE_VALID, 18, 5); + Test("and_n(sha256(9267d3dbed802941483f1afa2a6bc68de5f653128aca9bf1461c5d0a3ad36ed2),uc:and_v(v:older(144),pk_k(03fe72c435413d33d48ac09c9161ba8b09683215439d62b7940502bda8b202e6ce)))", "82012088a8209267d3dbed802941483f1afa2a6bc68de5f653128aca9bf1461c5d0a3ad36ed28764006763029000b2692103fe72c435413d33d48ac09c9161ba8b09683215439d62b7940502bda8b202e6ceac67006868", TESTMODE_VALID | TESTMODE_NEEDSIG, 13, 4); + Test("and_n(c:pk_k(03daed4f2be3a8bf278e70132fb0beb7522f570e144bf615c07e996d443dee8729),and_b(l:older(4252898),a:older(16)))", "2103daed4f2be3a8bf278e70132fb0beb7522f570e144bf615c07e996d443dee8729ac64006763006703e2e440b2686b60b26c9a68", TESTMODE_VALID | TESTMODE_NONMAL | TESTMODE_NEEDSIG | TESTMODE_TIMELOCKMIX, 12, 3); + Test("c:or_i(and_v(v:older(16),pk_h(02d7924d4f7d43ea965a465ae3095ff41131e5946f3c85f79e44adbcf8e27e080e)),pk_h(026a245bf6dc698504c89a20cfded60853152b695336c28063b61c65cbd269e6b4))", "6360b26976a9149fc5dbe5efdce10374a4dd4053c93af540211718886776a9142fbd32c8dd59ee7c17e66cb6ebea7e9846c3040f8868ac", TESTMODE_VALID | TESTMODE_NONMAL | TESTMODE_NEEDSIG, 12, 4); + Test("or_d(c:pk_h(02e493dbf1c10d80f3581e4904930b1404cc6c13900ee0758474fa94abe8c4cd13),andor(c:pk_k(024ce119c96e2fa357200b559b2f7dd5a5f02d5290aff74b03f3e471b273211c97),older(2016),after(1567547623)))", "76a914c42e7ef92fdb603af844d064faad95db9bcdfd3d88ac736421024ce119c96e2fa357200b559b2f7dd5a5f02d5290aff74b03f3e471b273211c97ac6404e7e06e5db16702e007b26868", TESTMODE_VALID | TESTMODE_NONMAL, 13, 4); + Test("c:andor(ripemd160(6ad07d21fd5dfc646f0b30577045ce201616b9ba),pk_h(02d7924d4f7d43ea965a465ae3095ff41131e5946f3c85f79e44adbcf8e27e080e),and_v(v:hash256(8a35d9ca92a48eaade6f53a64985e9e2afeb74dcf8acb4c3721e0dc7e4294b25),pk_h(03d01115d548e7561b15c38f004d734633687cf4419620095bc5b0f47070afe85a)))", "82012088a6146ad07d21fd5dfc646f0b30577045ce201616b9ba876482012088aa208a35d9ca92a48eaade6f53a64985e9e2afeb74dcf8acb4c3721e0dc7e4294b258876a914dd100be7d9aea5721158ebde6d6a1fd8fff93bb1886776a9149fc5dbe5efdce10374a4dd4053c93af5402117188868ac", TESTMODE_VALID | TESTMODE_NEEDSIG, 18, 4); + Test("c:andor(u:ripemd160(6ad07d21fd5dfc646f0b30577045ce201616b9ba),pk_h(03daed4f2be3a8bf278e70132fb0beb7522f570e144bf615c07e996d443dee8729),or_i(pk_h(022f01e5e15cca351daff3843fb70f3c2f0a1bdd05e5af888a67784ef3e10a2a01),pk_h(0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798)))", "6382012088a6146ad07d21fd5dfc646f0b30577045ce201616b9ba87670068646376a9149652d86bedf43ad264362e6e6eba6eb764508127886776a914751e76e8199196d454941c45d1b3a323f1433bd688686776a91420d637c1a6404d2227f3561fdbaff5a680dba6488868ac", TESTMODE_VALID | TESTMODE_NEEDSIG, 23, 5); + Test("c:or_i(andor(c:pk_h(03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65),pk_h(022f01e5e15cca351daff3843fb70f3c2f0a1bdd05e5af888a67784ef3e10a2a01),pk_h(02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5)),pk_k(02d7924d4f7d43ea965a465ae3095ff41131e5946f3c85f79e44adbcf8e27e080e))", "6376a914fcd35ddacad9f2d5be5e464639441c6065e6955d88ac6476a91406afd46bcdfd22ef94ac122aa11f241244a37ecc886776a9149652d86bedf43ad264362e6e6eba6eb7645081278868672102d7924d4f7d43ea965a465ae3095ff41131e5946f3c85f79e44adbcf8e27e080e68ac", TESTMODE_VALID | TESTMODE_NONMAL | TESTMODE_NEEDSIG, 17, 6); + Test("thresh(1,c:pk_k(03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65),altv:after(1000000000),altv:after(100))", "2103d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65ac6b6300670400ca9a3bb16951686c936b6300670164b16951686c935187", TESTMODE_VALID, 18, 4); + Test("thresh(2,c:pk_k(03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65),ac:pk_k(03fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556),altv:after(1000000000),altv:after(100))", "2103d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65ac6b2103fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556ac6c936b6300670400ca9a3bb16951686c936b6300670164b16951686c935287", TESTMODE_VALID | TESTMODE_NONMAL | TESTMODE_TIMELOCKMIX, 22, 5); + + // Misc unit tests + // A Script with a non minimal push is invalid + std::vector<unsigned char> nonminpush = ParseHex("0000210232780000feff00ffffffffffff21ff005f00ae21ae00000000060602060406564c2102320000060900fe00005f00ae21ae00100000060606060606000000000000000000000000000000000000000000000000000000000000000000"); + const CScript nonminpush_script(nonminpush.begin(), nonminpush.end()); + BOOST_CHECK(miniscript::FromScript(nonminpush_script, CONVERTER) == nullptr); + // A non-minimal VERIFY (<key> CHECKSIG VERIFY 1) + std::vector<unsigned char> nonminverify = ParseHex("2103a0434d9e47f3c86235477c7b1ae6ae5d3442d49b1943c2b752a68e2a47e247c7ac6951"); + const CScript nonminverify_script(nonminverify.begin(), nonminverify.end()); + BOOST_CHECK(miniscript::FromScript(nonminverify_script, CONVERTER) == nullptr); + // A threshold as large as the number of subs is valid. + Test("thresh(2,c:pk_k(03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65),altv:after(100))", "2103d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65ac6b6300670164b16951686c935287", TESTMODE_VALID | TESTMODE_NEEDSIG | TESTMODE_NONMAL); + // A threshold of 1 is valid. + Test("thresh(1,c:pk_k(03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65),sc:pk_k(03fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556))", "2103d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65ac7c2103fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556ac935187", TESTMODE_VALID | TESTMODE_NEEDSIG | TESTMODE_NONMAL); + // A threshold with a k larger than the number of subs is invalid + Test("thresh(3,c:pk_k(03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65),sc:pk_k(03fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556))", "2103d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65ac7c2103fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556ac935187", TESTMODE_INVALID); + // A threshold with a k null is invalid + Test("thresh(0,c:pk_k(03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65),sc:pk_k(03fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556))", "2103d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65ac7c2103fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556ac935187", TESTMODE_INVALID); + // For CHECKMULTISIG the OP cost is the number of keys, but the stack size is the number of sigs (+1) + const auto ms_multi = miniscript::FromString("multi(1,03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65,03fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556,0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798)", CONVERTER); + BOOST_CHECK(ms_multi); + BOOST_CHECK_EQUAL(ms_multi->GetOps(), 4); // 3 pubkeys + CMS + BOOST_CHECK_EQUAL(ms_multi->GetStackSize(), 3); // 1 sig + dummy elem + script push + // The 'd:' wrapper leaves on the stack what was DUP'ed at the beginning of its execution. + // Since it contains an OP_IF just after on the same element, we can make sure that the element + // in question must be OP_1 if OP_IF enforces that its argument must only be OP_1 or the empty + // vector (since otherwise the execution would immediately fail). This is the MINIMALIF rule. + // Unfortunately, this rule is consensus for Taproot but only policy for P2WSH. Therefore we can't + // (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 && !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); + BOOST_CHECK(!ms_dup1->IsSane() && !ms_dup1->CheckDuplicateKey()); + // Same with a disjunction, and different key nodes (pk and pkh) + const auto ms_dup2 = miniscript::FromString("or_b(c:pk_k(03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65),ac:pk_h(03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65))", CONVERTER); + BOOST_CHECK(ms_dup2 && !ms_dup2->IsSane() && !ms_dup2->CheckDuplicateKey()); + // Same when the duplicates are leaves or a larger tree + const auto ms_dup3 = miniscript::FromString("or_i(and_b(pk(03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65),s:pk(03fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556)),and_b(older(1),s:pk(03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65)))", CONVERTER); + BOOST_CHECK(ms_dup3 && !ms_dup3->IsSane() && !ms_dup3->CheckDuplicateKey()); + // 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()); + // 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 + Test("after(1000000000)", "?", TESTMODE_VALID | TESTMODE_NONMAL); // only timelock + Test("or_b(l:after(100),al:after(1000000000))", "?", TESTMODE_VALID); // or_b(timelock, heighlock) valid + Test("and_b(after(100),a:after(1000000000))", "?", TESTMODE_VALID | TESTMODE_NONMAL | TESTMODE_TIMELOCKMIX); // and_b(timelock, heighlock) invalid + /* This is correctly detected as non-malleable but for the wrong reason. The type system assumes that branches 1 and 2 + can be spent together to create a non-malleble witness, but because of mixing of timelocks they cannot be spent together. + But since exactly one of the two after's can be satisfied, the witness involving the key cannot be malleated. + */ + Test("thresh(2,ltv:after(1000000000),altv:after(100),a:pk(03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65))", "?", TESTMODE_VALID | TESTMODE_TIMELOCKMIX | TESTMODE_NONMAL); // thresh with k = 2 + // This is actually non-malleable in practice, but we cannot detect it in type system. See above rationale + Test("thresh(1,c:pk_k(03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65),altv:after(1000000000),altv:after(100))", "?", TESTMODE_VALID); // thresh with k = 1 + + + g_testdata.reset(); +} + +BOOST_AUTO_TEST_SUITE_END() 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_peer_eviction_tests.cpp b/src/test/net_peer_eviction_tests.cpp index 6ec3fb0c6b..d519a4442f 100644 --- a/src/test/net_peer_eviction_tests.cpp +++ b/src/test/net_peer_eviction_tests.cpp @@ -478,8 +478,8 @@ BOOST_AUTO_TEST_CASE(peer_protection_test) c.m_network = NET_IPV6; } }, - /* protected_peer_ids */ {0, 4}, - /* unprotected_peer_ids */ {1, 2, 3}, + /*protected_peer_ids=*/{0, 4}, + /*unprotected_peer_ids=*/{1, 2, 3}, random_context)); // Combined test: expect having 1 CJDNS, 1 I2P, 1 localhost and 1 onion peer @@ -627,7 +627,7 @@ BOOST_AUTO_TEST_CASE(peer_eviction_test) number_of_nodes, [number_of_nodes](NodeEvictionCandidate& candidate) { candidate.m_last_block_time = std::chrono::seconds{number_of_nodes - candidate.id}; if (candidate.id <= 7) { - candidate.fRelayTxes = false; + candidate.m_relay_txs = false; candidate.fRelevantServices = true; } }, @@ -646,7 +646,7 @@ BOOST_AUTO_TEST_CASE(peer_eviction_test) number_of_nodes, [number_of_nodes](NodeEvictionCandidate& candidate) { candidate.m_last_block_time = std::chrono::seconds{number_of_nodes - candidate.id}; if (candidate.id <= 7) { - candidate.fRelayTxes = false; + candidate.m_relay_txs = false; candidate.fRelevantServices = true; } }, diff --git a/src/test/net_tests.cpp b/src/test/net_tests.cpp index fcb1a80765..f6642d3218 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); @@ -732,26 +725,31 @@ BOOST_AUTO_TEST_CASE(LimitedAndReachable_Network) BOOST_CHECK(IsReachable(NET_IPV6)); BOOST_CHECK(IsReachable(NET_ONION)); BOOST_CHECK(IsReachable(NET_I2P)); + BOOST_CHECK(IsReachable(NET_CJDNS)); SetReachable(NET_IPV4, false); SetReachable(NET_IPV6, false); SetReachable(NET_ONION, false); SetReachable(NET_I2P, false); + SetReachable(NET_CJDNS, false); BOOST_CHECK(!IsReachable(NET_IPV4)); BOOST_CHECK(!IsReachable(NET_IPV6)); BOOST_CHECK(!IsReachable(NET_ONION)); BOOST_CHECK(!IsReachable(NET_I2P)); + BOOST_CHECK(!IsReachable(NET_CJDNS)); SetReachable(NET_IPV4, true); SetReachable(NET_IPV6, true); SetReachable(NET_ONION, true); SetReachable(NET_I2P, true); + SetReachable(NET_CJDNS, true); BOOST_CHECK(IsReachable(NET_IPV4)); BOOST_CHECK(IsReachable(NET_IPV6)); BOOST_CHECK(IsReachable(NET_ONION)); BOOST_CHECK(IsReachable(NET_I2P)); + BOOST_CHECK(IsReachable(NET_CJDNS)); } BOOST_AUTO_TEST_CASE(LimitedAndReachable_NetworkCaseUnroutableAndInternal) @@ -829,7 +827,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, @@ -849,7 +846,7 @@ BOOST_AUTO_TEST_CASE(initial_advertise_from_version_message) *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}; diff --git a/src/test/netbase_tests.cpp b/src/test/netbase_tests.cpp index 8e6e911ec2..c2d2fa37b4 100644 --- a/src/test/netbase_tests.cpp +++ b/src/test/netbase_tests.cpp @@ -5,6 +5,7 @@ #include <net_permissions.h> #include <netaddress.h> #include <netbase.h> +#include <netgroup.h> #include <protocol.h> #include <serialize.h> #include <streams.h> @@ -315,22 +316,22 @@ BOOST_AUTO_TEST_CASE(subnet_test) BOOST_AUTO_TEST_CASE(netbase_getgroup) { - std::vector<bool> asmap; // use /16 - BOOST_CHECK(ResolveIP("127.0.0.1").GetGroup(asmap) == std::vector<unsigned char>({0})); // Local -> !Routable() - BOOST_CHECK(ResolveIP("257.0.0.1").GetGroup(asmap) == std::vector<unsigned char>({0})); // !Valid -> !Routable() - BOOST_CHECK(ResolveIP("10.0.0.1").GetGroup(asmap) == std::vector<unsigned char>({0})); // RFC1918 -> !Routable() - BOOST_CHECK(ResolveIP("169.254.1.1").GetGroup(asmap) == std::vector<unsigned char>({0})); // RFC3927 -> !Routable() - BOOST_CHECK(ResolveIP("1.2.3.4").GetGroup(asmap) == std::vector<unsigned char>({(unsigned char)NET_IPV4, 1, 2})); // IPv4 - BOOST_CHECK(ResolveIP("::FFFF:0:102:304").GetGroup(asmap) == std::vector<unsigned char>({(unsigned char)NET_IPV4, 1, 2})); // RFC6145 - BOOST_CHECK(ResolveIP("64:FF9B::102:304").GetGroup(asmap) == std::vector<unsigned char>({(unsigned char)NET_IPV4, 1, 2})); // RFC6052 - BOOST_CHECK(ResolveIP("2002:102:304:9999:9999:9999:9999:9999").GetGroup(asmap) == std::vector<unsigned char>({(unsigned char)NET_IPV4, 1, 2})); // RFC3964 - BOOST_CHECK(ResolveIP("2001:0:9999:9999:9999:9999:FEFD:FCFB").GetGroup(asmap) == std::vector<unsigned char>({(unsigned char)NET_IPV4, 1, 2})); // RFC4380 - BOOST_CHECK(ResolveIP("2001:470:abcd:9999:9999:9999:9999:9999").GetGroup(asmap) == std::vector<unsigned char>({(unsigned char)NET_IPV6, 32, 1, 4, 112, 175})); //he.net - BOOST_CHECK(ResolveIP("2001:2001:9999:9999:9999:9999:9999:9999").GetGroup(asmap) == std::vector<unsigned char>({(unsigned char)NET_IPV6, 32, 1, 32, 1})); //IPv6 + NetGroupManager netgroupman{std::vector<bool>()}; // use /16 + BOOST_CHECK(netgroupman.GetGroup(ResolveIP("127.0.0.1")) == std::vector<unsigned char>({0})); // Local -> !Routable() + BOOST_CHECK(netgroupman.GetGroup(ResolveIP("257.0.0.1")) == std::vector<unsigned char>({0})); // !Valid -> !Routable() + BOOST_CHECK(netgroupman.GetGroup(ResolveIP("10.0.0.1")) == std::vector<unsigned char>({0})); // RFC1918 -> !Routable() + BOOST_CHECK(netgroupman.GetGroup(ResolveIP("169.254.1.1")) == std::vector<unsigned char>({0})); // RFC3927 -> !Routable() + BOOST_CHECK(netgroupman.GetGroup(ResolveIP("1.2.3.4")) == std::vector<unsigned char>({(unsigned char)NET_IPV4, 1, 2})); // IPv4 + BOOST_CHECK(netgroupman.GetGroup(ResolveIP("::FFFF:0:102:304")) == std::vector<unsigned char>({(unsigned char)NET_IPV4, 1, 2})); // RFC6145 + BOOST_CHECK(netgroupman.GetGroup(ResolveIP("64:FF9B::102:304")) == std::vector<unsigned char>({(unsigned char)NET_IPV4, 1, 2})); // RFC6052 + BOOST_CHECK(netgroupman.GetGroup(ResolveIP("2002:102:304:9999:9999:9999:9999:9999")) == std::vector<unsigned char>({(unsigned char)NET_IPV4, 1, 2})); // RFC3964 + BOOST_CHECK(netgroupman.GetGroup(ResolveIP("2001:0:9999:9999:9999:9999:FEFD:FCFB")) == std::vector<unsigned char>({(unsigned char)NET_IPV4, 1, 2})); // RFC4380 + BOOST_CHECK(netgroupman.GetGroup(ResolveIP("2001:470:abcd:9999:9999:9999:9999:9999")) == std::vector<unsigned char>({(unsigned char)NET_IPV6, 32, 1, 4, 112, 175})); //he.net + BOOST_CHECK(netgroupman.GetGroup(ResolveIP("2001:2001:9999:9999:9999:9999:9999:9999")) == std::vector<unsigned char>({(unsigned char)NET_IPV6, 32, 1, 32, 1})); //IPv6 // baz.net sha256 hash: 12929400eb4607c4ac075f087167e75286b179c693eb059a01774b864e8fe505 std::vector<unsigned char> internal_group = {NET_INTERNAL, 0x12, 0x92, 0x94, 0x00, 0xeb, 0x46, 0x07, 0xc4, 0xac, 0x07}; - BOOST_CHECK(CreateInternal("baz.net").GetGroup(asmap) == internal_group); + BOOST_CHECK(netgroupman.GetGroup(CreateInternal("baz.net")) == internal_group); } BOOST_AUTO_TEST_CASE(netbase_parsenetwork) @@ -479,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/orphanage_tests.cpp b/src/test/orphanage_tests.cpp new file mode 100644 index 0000000000..842daa8bd4 --- /dev/null +++ b/src/test/orphanage_tests.cpp @@ -0,0 +1,137 @@ +// Copyright (c) 2011-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 <arith_uint256.h> +#include <pubkey.h> +#include <script/sign.h> +#include <script/signingprovider.h> +#include <script/standard.h> +#include <test/util/setup_common.h> +#include <txorphanage.h> + +#include <array> +#include <cstdint> + +#include <boost/test/unit_test.hpp> + +BOOST_FIXTURE_TEST_SUITE(orphanage_tests, TestingSetup) + +class TxOrphanageTest : public TxOrphanage +{ +public: + inline size_t CountOrphans() const EXCLUSIVE_LOCKS_REQUIRED(g_cs_orphans) + { + return m_orphans.size(); + } + + CTransactionRef RandomOrphan() EXCLUSIVE_LOCKS_REQUIRED(g_cs_orphans) + { + std::map<uint256, OrphanTx>::iterator it; + it = m_orphans.lower_bound(InsecureRand256()); + if (it == m_orphans.end()) + it = m_orphans.begin(); + return it->second.tx; + } +}; + +static void MakeNewKeyWithFastRandomContext(CKey& key) +{ + std::vector<unsigned char> keydata; + keydata = g_insecure_rand_ctx.randbytes(32); + key.Set(keydata.data(), keydata.data() + keydata.size(), /*fCompressedIn=*/true); + assert(key.IsValid()); +} + +BOOST_AUTO_TEST_CASE(DoS_mapOrphans) +{ + // This test had non-deterministic coverage due to + // randomly selected seeds. + // This seed is chosen so that all branches of the function + // ecdsa_signature_parse_der_lax are executed during this test. + // Specifically branches that run only when an ECDSA + // signature's R and S values have leading zeros. + g_insecure_rand_ctx = FastRandomContext{uint256{33}}; + + TxOrphanageTest orphanage; + CKey key; + MakeNewKeyWithFastRandomContext(key); + FillableSigningProvider keystore; + BOOST_CHECK(keystore.AddKey(key)); + + LOCK(g_cs_orphans); + + // 50 orphan transactions: + for (int i = 0; i < 50; i++) + { + CMutableTransaction tx; + tx.vin.resize(1); + tx.vin[0].prevout.n = 0; + tx.vin[0].prevout.hash = InsecureRand256(); + tx.vin[0].scriptSig << OP_1; + tx.vout.resize(1); + tx.vout[0].nValue = 1*CENT; + tx.vout[0].scriptPubKey = GetScriptForDestination(PKHash(key.GetPubKey())); + + orphanage.AddTx(MakeTransactionRef(tx), i); + } + + // ... and 50 that depend on other orphans: + for (int i = 0; i < 50; i++) + { + CTransactionRef txPrev = orphanage.RandomOrphan(); + + CMutableTransaction tx; + tx.vin.resize(1); + tx.vin[0].prevout.n = 0; + tx.vin[0].prevout.hash = txPrev->GetHash(); + tx.vout.resize(1); + tx.vout[0].nValue = 1*CENT; + tx.vout[0].scriptPubKey = GetScriptForDestination(PKHash(key.GetPubKey())); + BOOST_CHECK(SignSignature(keystore, *txPrev, tx, 0, SIGHASH_ALL)); + + orphanage.AddTx(MakeTransactionRef(tx), i); + } + + // This really-big orphan should be ignored: + for (int i = 0; i < 10; i++) + { + CTransactionRef txPrev = orphanage.RandomOrphan(); + + CMutableTransaction tx; + tx.vout.resize(1); + tx.vout[0].nValue = 1*CENT; + tx.vout[0].scriptPubKey = GetScriptForDestination(PKHash(key.GetPubKey())); + tx.vin.resize(2777); + for (unsigned int j = 0; j < tx.vin.size(); j++) + { + tx.vin[j].prevout.n = j; + tx.vin[j].prevout.hash = txPrev->GetHash(); + } + BOOST_CHECK(SignSignature(keystore, *txPrev, tx, 0, SIGHASH_ALL)); + // Re-use same signature for other inputs + // (they don't have to be valid for this test) + for (unsigned int j = 1; j < tx.vin.size(); j++) + tx.vin[j].scriptSig = tx.vin[0].scriptSig; + + BOOST_CHECK(!orphanage.AddTx(MakeTransactionRef(tx), i)); + } + + // Test EraseOrphansFor: + for (NodeId i = 0; i < 3; i++) + { + size_t sizeBefore = orphanage.CountOrphans(); + orphanage.EraseForPeer(i); + BOOST_CHECK(orphanage.CountOrphans() < sizeBefore); + } + + // Test LimitOrphanTxSize() function: + orphanage.LimitOrphans(40); + BOOST_CHECK(orphanage.CountOrphans() <= 40); + orphanage.LimitOrphans(10); + BOOST_CHECK(orphanage.CountOrphans() <= 10); + orphanage.LimitOrphans(0); + BOOST_CHECK(orphanage.CountOrphans() == 0); +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/pmt_tests.cpp b/src/test/pmt_tests.cpp index a9d661438c..3cbbec92d6 100644 --- a/src/test/pmt_tests.cpp +++ b/src/test/pmt_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 <arith_uint256.h> #include <consensus/merkle.h> #include <merkleblock.h> #include <serialize.h> @@ -107,13 +106,13 @@ BOOST_AUTO_TEST_CASE(pmt_test1) BOOST_AUTO_TEST_CASE(pmt_malleability) { - std::vector<uint256> vTxid = { - ArithToUint256(1), ArithToUint256(2), - ArithToUint256(3), ArithToUint256(4), - ArithToUint256(5), ArithToUint256(6), - ArithToUint256(7), ArithToUint256(8), - ArithToUint256(9), ArithToUint256(10), - ArithToUint256(9), ArithToUint256(10), + std::vector<uint256> vTxid{ + uint256{1}, uint256{2}, + uint256{3}, uint256{4}, + uint256{5}, uint256{6}, + uint256{7}, uint256{8}, + uint256{9}, uint256{10}, + uint256{9}, uint256{10}, }; std::vector<bool> vMatch = {false, false, false, false, false, false, false, false, false, true, true, false}; 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/prevector_tests.cpp b/src/test/prevector_tests.cpp index 89814748fe..3977a3d548 100644 --- a/src/test/prevector_tests.cpp +++ b/src/test/prevector_tests.cpp @@ -165,7 +165,8 @@ public: test(); } - void swap() { + void swap() noexcept + { real_vector.swap(real_vector_alt); pre_vector.swap(pre_vector_alt); test(); diff --git a/src/test/random_tests.cpp b/src/test/random_tests.cpp index 978a7bee4d..96fb28dc9f 100644 --- a/src/test/random_tests.cpp +++ b/src/test/random_tests.cpp @@ -26,11 +26,21 @@ BOOST_AUTO_TEST_CASE(fastrandom_tests) FastRandomContext ctx2(true); for (int i = 10; i > 0; --i) { - BOOST_CHECK_EQUAL(GetRand(std::numeric_limits<uint64_t>::max()), uint64_t{10393729187455219830U}); - BOOST_CHECK_EQUAL(GetRandInt(std::numeric_limits<int>::max()), int{769702006}); + BOOST_CHECK_EQUAL(GetRand<uint64_t>(), uint64_t{10393729187455219830U}); + BOOST_CHECK_EQUAL(GetRand<int>(), int{769702006}); BOOST_CHECK_EQUAL(GetRandMicros(std::chrono::hours{1}).count(), 2917185654); BOOST_CHECK_EQUAL(GetRandMillis(std::chrono::hours{1}).count(), 2144374); } + { + constexpr SteadySeconds time_point{1s}; + FastRandomContext ctx{true}; + BOOST_CHECK_EQUAL(7, ctx.rand_uniform_delay(time_point, 9s).time_since_epoch().count()); + BOOST_CHECK_EQUAL(-6, ctx.rand_uniform_delay(time_point, -9s).time_since_epoch().count()); + BOOST_CHECK_EQUAL(1, ctx.rand_uniform_delay(time_point, 0s).time_since_epoch().count()); + BOOST_CHECK_EQUAL(1467825113502396065, ctx.rand_uniform_delay(time_point, 9223372036854775807s).time_since_epoch().count()); + BOOST_CHECK_EQUAL(-970181367944767837, ctx.rand_uniform_delay(time_point, -9223372036854775807s).time_since_epoch().count()); + BOOST_CHECK_EQUAL(24761, ctx.rand_uniform_delay(time_point, 9h).time_since_epoch().count()); + } BOOST_CHECK_EQUAL(ctx1.rand32(), ctx2.rand32()); BOOST_CHECK_EQUAL(ctx1.rand32(), ctx2.rand32()); BOOST_CHECK_EQUAL(ctx1.rand64(), ctx2.rand64()); @@ -43,12 +53,22 @@ 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; for (int i = 10; i > 0; --i) { - BOOST_CHECK(GetRand(std::numeric_limits<uint64_t>::max()) != uint64_t{10393729187455219830U}); - BOOST_CHECK(GetRandInt(std::numeric_limits<int>::max()) != int{769702006}); + BOOST_CHECK(GetRand<uint64_t>() != uint64_t{10393729187455219830U}); + BOOST_CHECK(GetRand<int>() != int{769702006}); BOOST_CHECK(GetRandMicros(std::chrono::hours{1}) != std::chrono::microseconds{2917185654}); BOOST_CHECK(GetRandMillis(std::chrono::hours{1}) != std::chrono::milliseconds{2144374}); } 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/rest_tests.cpp b/src/test/rest_tests.cpp new file mode 100644 index 0000000000..20dfe4b41a --- /dev/null +++ b/src/test/rest_tests.cpp @@ -0,0 +1,48 @@ +// Copyright (c) 2012-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 <rest.h> +#include <test/util/setup_common.h> + +#include <boost/test/unit_test.hpp> + +#include <string> + +BOOST_FIXTURE_TEST_SUITE(rest_tests, BasicTestingSetup) + +BOOST_AUTO_TEST_CASE(test_query_string) +{ + std::string param; + RESTResponseFormat rf; + // No query string + rf = ParseDataFormat(param, "/rest/endpoint/someresource.json"); + BOOST_CHECK_EQUAL(param, "/rest/endpoint/someresource"); + BOOST_CHECK_EQUAL(rf, RESTResponseFormat::JSON); + + // Query string with single parameter + rf = ParseDataFormat(param, "/rest/endpoint/someresource.bin?p1=v1"); + BOOST_CHECK_EQUAL(param, "/rest/endpoint/someresource"); + BOOST_CHECK_EQUAL(rf, RESTResponseFormat::BINARY); + + // Query string with multiple parameters + rf = ParseDataFormat(param, "/rest/endpoint/someresource.hex?p1=v1&p2=v2"); + BOOST_CHECK_EQUAL(param, "/rest/endpoint/someresource"); + BOOST_CHECK_EQUAL(rf, RESTResponseFormat::HEX); + + // Incorrectly formed query string will not be handled + rf = ParseDataFormat(param, "/rest/endpoint/someresource.json&p1=v1"); + BOOST_CHECK_EQUAL(param, "/rest/endpoint/someresource.json&p1=v1"); + BOOST_CHECK_EQUAL(rf, RESTResponseFormat::UNDEF); + + // Omitted data format with query string should return UNDEF and hide query string + rf = ParseDataFormat(param, "/rest/endpoint/someresource?p1=v1"); + BOOST_CHECK_EQUAL(param, "/rest/endpoint/someresource"); + BOOST_CHECK_EQUAL(rf, RESTResponseFormat::UNDEF); + + // Data format specified after query string + rf = ParseDataFormat(param, "/rest/endpoint/someresource?p1=v1.json"); + BOOST_CHECK_EQUAL(param, "/rest/endpoint/someresource"); + BOOST_CHECK_EQUAL(rf, RESTResponseFormat::UNDEF); +} +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 50b5078110..a52530e179 100644 --- a/src/test/rpc_tests.cpp +++ b/src/test/rpc_tests.cpp @@ -2,25 +2,21 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#include <rpc/client.h> -#include <rpc/server.h> -#include <rpc/util.h> - #include <core_io.h> #include <interfaces/chain.h> #include <node/context.h> +#include <rpc/blockchain.h> +#include <rpc/client.h> +#include <rpc/server.h> +#include <rpc/util.h> #include <test/util/setup_common.h> +#include <univalue.h> #include <util/time.h> #include <any> -#include <boost/algorithm/string.hpp> #include <boost/test/unit_test.hpp> -#include <univalue.h> - -#include <rpc/blockchain.h> - class RPCTestingSetup : public TestingSetup { public: @@ -29,8 +25,7 @@ public: UniValue RPCTestingSetup::CallRPC(std::string args) { - std::vector<std::string> vArgs; - boost::split(vArgs, args, boost::is_any_of(" \t")); + std::vector<std::string> vArgs{SplitString(args, ' ')}; std::string strMethod = vArgs[0]; vArgs.erase(vArgs.begin()); JSONRPCRequest request; @@ -71,9 +66,9 @@ BOOST_AUTO_TEST_CASE(rpc_rawparams) BOOST_CHECK_THROW(CallRPC("decoderawtransaction DEADBEEF"), std::runtime_error); std::string rawtx = "0100000001a15d57094aa7a21a28cb20b59aab8fc7d1149a3bdbcddba9c622e4f5f6a99ece010000006c493046022100f93bb0e7d8db7bd46e40132d1f8242026e045f03a0efe71bbb8e3f475e970d790221009337cd7f1f929f00cc6ff01f03729b069a7c21b59b1736ddfee5db5946c5da8c0121033b9b137ee87d5a812d6f506efdd37f0affa7ffc310711c06c7f3e097c9447c52ffffffff0100e1f505000000001976a9140389035a9225b3839e2bbf32d826a1e222031fd888ac00000000"; BOOST_CHECK_NO_THROW(r = CallRPC(std::string("decoderawtransaction ")+rawtx)); - BOOST_CHECK_EQUAL(find_value(r.get_obj(), "size").get_int(), 193); - BOOST_CHECK_EQUAL(find_value(r.get_obj(), "version").get_int(), 1); - BOOST_CHECK_EQUAL(find_value(r.get_obj(), "locktime").get_int(), 0); + BOOST_CHECK_EQUAL(find_value(r.get_obj(), "size").getInt<int>(), 193); + BOOST_CHECK_EQUAL(find_value(r.get_obj(), "version").getInt<int>(), 1); + BOOST_CHECK_EQUAL(find_value(r.get_obj(), "locktime").getInt<int>(), 0); BOOST_CHECK_THROW(CallRPC(std::string("decoderawtransaction ")+rawtx+" extra"), std::runtime_error); BOOST_CHECK_NO_THROW(r = CallRPC(std::string("decoderawtransaction ")+rawtx+" false")); BOOST_CHECK_THROW(r = CallRPC(std::string("decoderawtransaction ")+rawtx+" false extra"), std::runtime_error); @@ -95,7 +90,7 @@ BOOST_AUTO_TEST_CASE(rpc_togglenetwork) BOOST_CHECK_NO_THROW(CallRPC("setnetworkactive false")); r = CallRPC("getnetworkinfo"); - int numConnection = find_value(r.get_obj(), "connections").get_int(); + int numConnection = find_value(r.get_obj(), "connections").getInt<int>(); BOOST_CHECK_EQUAL(numConnection, 0); netState = find_value(r.get_obj(), "networkactive").get_bool(); @@ -186,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; } @@ -269,7 +264,7 @@ BOOST_AUTO_TEST_CASE(rpc_ban) ar = r.get_array(); o1 = ar[0].get_obj(); adr = find_value(o1, "address"); - int64_t banned_until{find_value(o1, "banned_until").get_int64()}; + int64_t banned_until{find_value(o1, "banned_until").getInt<int64_t>()}; BOOST_CHECK_EQUAL(adr.get_str(), "127.0.0.0/24"); BOOST_CHECK_EQUAL(banned_until, 9907731200); // absolute time check @@ -284,10 +279,10 @@ BOOST_AUTO_TEST_CASE(rpc_ban) ar = r.get_array(); o1 = ar[0].get_obj(); adr = find_value(o1, "address"); - banned_until = find_value(o1, "banned_until").get_int64(); - const int64_t ban_created{find_value(o1, "ban_created").get_int64()}; - const int64_t ban_duration{find_value(o1, "ban_duration").get_int64()}; - const int64_t time_remaining{find_value(o1, "time_remaining").get_int64()}; + banned_until = find_value(o1, "banned_until").getInt<int64_t>(); + const int64_t ban_created{find_value(o1, "ban_created").getInt<int64_t>()}; + const int64_t ban_duration{find_value(o1, "ban_duration").getInt<int64_t>()}; + const int64_t time_remaining{find_value(o1, "time_remaining").getInt<int64_t>()}; BOOST_CHECK_EQUAL(adr.get_str(), "127.0.0.0/24"); BOOST_CHECK_EQUAL(banned_until, time_remaining_expected + now.count()); BOOST_CHECK_EQUAL(ban_duration, banned_until - ban_created); @@ -342,22 +337,22 @@ BOOST_AUTO_TEST_CASE(rpc_convert_values_generatetoaddress) UniValue result; BOOST_CHECK_NO_THROW(result = RPCConvertValues("generatetoaddress", {"101", "mkESjLZW66TmHhiFX8MCaBjrhZ543PPh9a"})); - BOOST_CHECK_EQUAL(result[0].get_int(), 101); + BOOST_CHECK_EQUAL(result[0].getInt<int>(), 101); BOOST_CHECK_EQUAL(result[1].get_str(), "mkESjLZW66TmHhiFX8MCaBjrhZ543PPh9a"); BOOST_CHECK_NO_THROW(result = RPCConvertValues("generatetoaddress", {"101", "mhMbmE2tE9xzJYCV9aNC8jKWN31vtGrguU"})); - BOOST_CHECK_EQUAL(result[0].get_int(), 101); + BOOST_CHECK_EQUAL(result[0].getInt<int>(), 101); BOOST_CHECK_EQUAL(result[1].get_str(), "mhMbmE2tE9xzJYCV9aNC8jKWN31vtGrguU"); BOOST_CHECK_NO_THROW(result = RPCConvertValues("generatetoaddress", {"1", "mkESjLZW66TmHhiFX8MCaBjrhZ543PPh9a", "9"})); - BOOST_CHECK_EQUAL(result[0].get_int(), 1); + BOOST_CHECK_EQUAL(result[0].getInt<int>(), 1); BOOST_CHECK_EQUAL(result[1].get_str(), "mkESjLZW66TmHhiFX8MCaBjrhZ543PPh9a"); - BOOST_CHECK_EQUAL(result[2].get_int(), 9); + BOOST_CHECK_EQUAL(result[2].getInt<int>(), 9); BOOST_CHECK_NO_THROW(result = RPCConvertValues("generatetoaddress", {"1", "mhMbmE2tE9xzJYCV9aNC8jKWN31vtGrguU", "9"})); - BOOST_CHECK_EQUAL(result[0].get_int(), 1); + BOOST_CHECK_EQUAL(result[0].getInt<int>(), 1); BOOST_CHECK_EQUAL(result[1].get_str(), "mhMbmE2tE9xzJYCV9aNC8jKWN31vtGrguU"); - BOOST_CHECK_EQUAL(result[2].get_int(), 9); + BOOST_CHECK_EQUAL(result[2].getInt<int>(), 9); } BOOST_AUTO_TEST_CASE(rpc_getblockstats_calculate_percentiles_by_weight) diff --git a/src/test/sanity_tests.cpp b/src/test/sanity_tests.cpp index a7057f8361..907a3fd15b 100644 --- a/src/test/sanity_tests.cpp +++ b/src/test/sanity_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 <compat/sanity.h> #include <key.h> #include <test/util/setup_common.h> #include <util/time.h> @@ -13,7 +12,6 @@ BOOST_FIXTURE_TEST_SUITE(sanity_tests, BasicTestingSetup) BOOST_AUTO_TEST_CASE(basic_sanity) { - BOOST_CHECK_MESSAGE(glibcxx_sanity_test() == true, "stdlib sanity test"); BOOST_CHECK_MESSAGE(ECC_InitSanityCheck() == true, "secp256k1 sanity test"); BOOST_CHECK_MESSAGE(ChronoSanityCheck() == true, "chrono epoch test"); } diff --git a/src/test/scheduler_tests.cpp b/src/test/scheduler_tests.cpp index 4195d413fc..7b5dda8114 100644 --- a/src/test/scheduler_tests.cpp +++ b/src/test/scheduler_tests.cpp @@ -15,13 +15,13 @@ BOOST_AUTO_TEST_SUITE(scheduler_tests) -static void microTask(CScheduler& s, std::mutex& mutex, int& counter, int delta, std::chrono::system_clock::time_point rescheduleTime) +static void microTask(CScheduler& s, std::mutex& mutex, int& counter, int delta, std::chrono::steady_clock::time_point rescheduleTime) { { std::lock_guard<std::mutex> lock(mutex); counter += delta; } - std::chrono::system_clock::time_point noTime = std::chrono::system_clock::time_point::min(); + auto noTime = std::chrono::steady_clock::time_point::min(); if (rescheduleTime != noTime) { CScheduler::Function f = std::bind(µTask, std::ref(s), std::ref(mutex), std::ref(counter), -delta + 1, noTime); s.schedule(f, rescheduleTime); @@ -49,15 +49,15 @@ BOOST_AUTO_TEST_CASE(manythreads) auto randomMsec = [](FastRandomContext& rc) -> int { return -11 + (int)rc.randrange(1012); }; // [-11, 1000] auto randomDelta = [](FastRandomContext& rc) -> int { return -1000 + (int)rc.randrange(2001); }; // [-1000, 1000] - std::chrono::system_clock::time_point start = std::chrono::system_clock::now(); - std::chrono::system_clock::time_point now = start; - std::chrono::system_clock::time_point first, last; + auto start = std::chrono::steady_clock::now(); + auto now = start; + std::chrono::steady_clock::time_point first, last; size_t nTasks = microTasks.getQueueInfo(first, last); BOOST_CHECK(nTasks == 0); for (int i = 0; i < 100; ++i) { - std::chrono::system_clock::time_point t = now + std::chrono::microseconds(randomMsec(rng)); - std::chrono::system_clock::time_point tReschedule = now + std::chrono::microseconds(500 + randomMsec(rng)); + auto t = now + std::chrono::microseconds(randomMsec(rng)); + auto tReschedule = now + std::chrono::microseconds(500 + randomMsec(rng)); int whichCounter = zeroToNine(rng); CScheduler::Function f = std::bind(µTask, std::ref(microTasks), std::ref(counterMutex[whichCounter]), std::ref(counter[whichCounter]), @@ -75,14 +75,14 @@ BOOST_AUTO_TEST_CASE(manythreads) microThreads.emplace_back(std::bind(&CScheduler::serviceQueue, µTasks)); UninterruptibleSleep(std::chrono::microseconds{600}); - now = std::chrono::system_clock::now(); + now = std::chrono::steady_clock::now(); // More threads and more tasks: for (int i = 0; i < 5; i++) microThreads.emplace_back(std::bind(&CScheduler::serviceQueue, µTasks)); for (int i = 0; i < 100; i++) { - std::chrono::system_clock::time_point t = now + std::chrono::microseconds(randomMsec(rng)); - std::chrono::system_clock::time_point tReschedule = now + std::chrono::microseconds(500 + randomMsec(rng)); + auto t = now + std::chrono::microseconds(randomMsec(rng)); + auto tReschedule = now + std::chrono::microseconds(500 + randomMsec(rng)); int whichCounter = zeroToNine(rng); CScheduler::Function f = std::bind(µTask, std::ref(microTasks), std::ref(counterMutex[whichCounter]), std::ref(counter[whichCounter]), @@ -111,8 +111,8 @@ BOOST_AUTO_TEST_CASE(wait_until_past) Mutex mtx; WAIT_LOCK(mtx, lock); - const auto no_wait= [&](const std::chrono::seconds& d) { - return condvar.wait_until(lock, std::chrono::system_clock::now() - d); + const auto no_wait = [&](const std::chrono::seconds& d) { + return condvar.wait_until(lock, std::chrono::steady_clock::now() - d); }; BOOST_CHECK(std::cv_status::timeout == no_wait(std::chrono::seconds{1})); @@ -128,8 +128,8 @@ BOOST_AUTO_TEST_CASE(singlethreadedscheduler_ordered) CScheduler scheduler; // each queue should be well ordered with respect to itself but not other queues - SingleThreadedSchedulerClient queue1(&scheduler); - SingleThreadedSchedulerClient queue2(&scheduler); + SingleThreadedSchedulerClient queue1(scheduler); + SingleThreadedSchedulerClient queue2(scheduler); // create more threads than queues // if the queues only permit execution of one task at once then @@ -137,7 +137,7 @@ BOOST_AUTO_TEST_CASE(singlethreadedscheduler_ordered) // if they don't we'll get out of order behaviour std::vector<std::thread> threads; for (int i = 0; i < 5; ++i) { - threads.emplace_back(std::bind(&CScheduler::serviceQueue, &scheduler)); + threads.emplace_back([&] { scheduler.serviceQueue(); }); } // these are not atomic, if SinglethreadedSchedulerClient prevents @@ -183,7 +183,7 @@ BOOST_AUTO_TEST_CASE(mockforward) scheduler.scheduleFromNow(dummy, std::chrono::minutes{8}); // check taskQueue - std::chrono::system_clock::time_point first, last; + std::chrono::steady_clock::time_point first, last; size_t num_tasks = scheduler.getQueueInfo(first, last); BOOST_CHECK_EQUAL(num_tasks, 3ul); @@ -204,7 +204,7 @@ BOOST_AUTO_TEST_CASE(mockforward) BOOST_CHECK_EQUAL(counter, 2); // check that the time of the remaining job has been updated - std::chrono::system_clock::time_point now = std::chrono::system_clock::now(); + auto now = std::chrono::steady_clock::now(); int delta = std::chrono::duration_cast<std::chrono::seconds>(first - now).count(); // should be between 2 & 3 minutes from now BOOST_CHECK(delta > 2*60 && delta < 3*60); 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_segwit_tests.cpp b/src/test/script_segwit_tests.cpp new file mode 100644 index 0000000000..2bad59805f --- /dev/null +++ b/src/test/script_segwit_tests.cpp @@ -0,0 +1,164 @@ +// Copyright (c) 2012-2021 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include <script/script.h> +#include <test/util/setup_common.h> + +#include <boost/test/unit_test.hpp> + +BOOST_FIXTURE_TEST_SUITE(script_segwit_tests, BasicTestingSetup) + +BOOST_AUTO_TEST_CASE(IsPayToWitnessScriptHash_Valid) +{ + uint256 dummy; + CScript p2wsh; + p2wsh << OP_0 << ToByteVector(dummy); + BOOST_CHECK(p2wsh.IsPayToWitnessScriptHash()); + + std::vector<unsigned char> bytes = {OP_0, 32}; + bytes.insert(bytes.end(), 32, 0); + BOOST_CHECK(CScript(bytes.begin(), bytes.end()).IsPayToWitnessScriptHash()); +} + +BOOST_AUTO_TEST_CASE(IsPayToWitnessScriptHash_Invalid_NotOp0) +{ + uint256 dummy; + CScript notp2wsh; + notp2wsh << OP_1 << ToByteVector(dummy); + BOOST_CHECK(!notp2wsh.IsPayToWitnessScriptHash()); +} + +BOOST_AUTO_TEST_CASE(IsPayToWitnessScriptHash_Invalid_Size) +{ + uint160 dummy; + CScript notp2wsh; + notp2wsh << OP_0 << ToByteVector(dummy); + BOOST_CHECK(!notp2wsh.IsPayToWitnessScriptHash()); +} + +BOOST_AUTO_TEST_CASE(IsPayToWitnessScriptHash_Invalid_Nop) +{ + uint256 dummy; + CScript notp2wsh; + notp2wsh << OP_0 << OP_NOP << ToByteVector(dummy); + BOOST_CHECK(!notp2wsh.IsPayToWitnessScriptHash()); +} + +BOOST_AUTO_TEST_CASE(IsPayToWitnessScriptHash_Invalid_EmptyScript) +{ + CScript notp2wsh; + BOOST_CHECK(!notp2wsh.IsPayToWitnessScriptHash()); +} + +BOOST_AUTO_TEST_CASE(IsPayToWitnessScriptHash_Invalid_Pushdata) +{ + // A script is not P2WSH if OP_PUSHDATA is used to push the hash. + std::vector<unsigned char> bytes = {OP_0, OP_PUSHDATA1, 32}; + bytes.insert(bytes.end(), 32, 0); + BOOST_CHECK(!CScript(bytes.begin(), bytes.end()).IsPayToWitnessScriptHash()); + + bytes = {OP_0, OP_PUSHDATA2, 32, 0}; + bytes.insert(bytes.end(), 32, 0); + BOOST_CHECK(!CScript(bytes.begin(), bytes.end()).IsPayToWitnessScriptHash()); + + bytes = {OP_0, OP_PUSHDATA4, 32, 0, 0, 0}; + bytes.insert(bytes.end(), 32, 0); + BOOST_CHECK(!CScript(bytes.begin(), bytes.end()).IsPayToWitnessScriptHash()); +} + +namespace { + +bool IsExpectedWitnessProgram(const CScript& script, const int expectedVersion, const std::vector<unsigned char>& expectedProgram) +{ + int actualVersion; + std::vector<unsigned char> actualProgram; + if (!script.IsWitnessProgram(actualVersion, actualProgram)) { + return false; + } + BOOST_CHECK_EQUAL(actualVersion, expectedVersion); + BOOST_CHECK(actualProgram == expectedProgram); + return true; +} + +bool IsNoWitnessProgram(const CScript& script) +{ + int dummyVersion; + std::vector<unsigned char> dummyProgram; + return !script.IsWitnessProgram(dummyVersion, dummyProgram); +} + +} // anonymous namespace + +BOOST_AUTO_TEST_CASE(IsWitnessProgram_Valid) +{ + // Witness programs have a minimum data push of 2 bytes. + std::vector<unsigned char> program = {42, 18}; + CScript wit; + wit << OP_0 << program; + BOOST_CHECK(IsExpectedWitnessProgram(wit, 0, program)); + + wit.clear(); + // Witness programs have a maximum data push of 40 bytes. + program.resize(40); + wit << OP_16 << program; + BOOST_CHECK(IsExpectedWitnessProgram(wit, 16, program)); + + program.resize(32); + std::vector<unsigned char> bytes = {OP_5, static_cast<unsigned char>(program.size())}; + bytes.insert(bytes.end(), program.begin(), program.end()); + BOOST_CHECK(IsExpectedWitnessProgram(CScript(bytes.begin(), bytes.end()), 5, program)); +} + +BOOST_AUTO_TEST_CASE(IsWitnessProgram_Invalid_Version) +{ + std::vector<unsigned char> program(10); + CScript nowit; + nowit << OP_1NEGATE << program; + BOOST_CHECK(IsNoWitnessProgram(nowit)); +} + +BOOST_AUTO_TEST_CASE(IsWitnessProgram_Invalid_Size) +{ + std::vector<unsigned char> program(1); + CScript nowit; + nowit << OP_0 << program; + BOOST_CHECK(IsNoWitnessProgram(nowit)); + + nowit.clear(); + program.resize(41); + nowit << OP_0 << program; + BOOST_CHECK(IsNoWitnessProgram(nowit)); +} + +BOOST_AUTO_TEST_CASE(IsWitnessProgram_Invalid_Nop) +{ + std::vector<unsigned char> program(10); + CScript nowit; + nowit << OP_0 << OP_NOP << program; + BOOST_CHECK(IsNoWitnessProgram(nowit)); +} + +BOOST_AUTO_TEST_CASE(IsWitnessProgram_Invalid_EmptyScript) +{ + CScript nowit; + BOOST_CHECK(IsNoWitnessProgram(nowit)); +} + +BOOST_AUTO_TEST_CASE(IsWitnessProgram_Invalid_Pushdata) +{ + // A script is no witness program if OP_PUSHDATA is used to push the hash. + std::vector<unsigned char> bytes = {OP_0, OP_PUSHDATA1, 32}; + bytes.insert(bytes.end(), 32, 0); + BOOST_CHECK(IsNoWitnessProgram(CScript(bytes.begin(), bytes.end()))); + + bytes = {OP_0, OP_PUSHDATA2, 32, 0}; + bytes.insert(bytes.end(), 32, 0); + BOOST_CHECK(IsNoWitnessProgram(CScript(bytes.begin(), bytes.end()))); + + bytes = {OP_0, OP_PUSHDATA4, 32, 0, 0, 0}; + bytes.insert(bytes.end(), 32, 0); + BOOST_CHECK(IsNoWitnessProgram(CScript(bytes.begin(), bytes.end()))); +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/script_standard_tests.cpp b/src/test/script_standard_tests.cpp index 75bc616cf9..25e47c0fb0 100644 --- a/src/test/script_standard_tests.cpp +++ b/src/test/script_standard_tests.cpp @@ -406,8 +406,8 @@ BOOST_AUTO_TEST_CASE(bip341_spk_test_vectors) if (node.isObject()) { auto script_bytes = ParseHex(node["script"].get_str()); CScript script(script_bytes.begin(), script_bytes.end()); - int idx = node["id"].get_int(); - int leaf_version = node["leafVersion"].get_int(); + int idx = node["id"].getInt<int>(); + int leaf_version = node["leafVersion"].getInt<int>(); scriptposes[{script, leaf_version}] = idx; spktest.Add(depth, script, leaf_version); } else { diff --git a/src/test/script_tests.cpp b/src/test/script_tests.cpp index c453f22594..935194057c 100644 --- a/src/test/script_tests.cpp +++ b/src/test/script_tests.cpp @@ -257,11 +257,11 @@ private: CScriptWitness scriptWitness; CTransactionRef creditTx; CMutableTransaction spendTx; - bool havePush; + bool havePush{false}; std::vector<unsigned char> push; std::string comment; uint32_t flags; - int scriptError; + int scriptError{SCRIPT_ERR_OK}; CAmount nValue; void DoPush() @@ -280,7 +280,7 @@ private: } public: - TestBuilder(const CScript& script_, const std::string& comment_, uint32_t flags_, bool P2SH = false, WitnessMode wm = WitnessMode::NONE, int witnessversion = 0, CAmount nValue_ = 0) : script(script_), havePush(false), comment(comment_), flags(flags_), scriptError(SCRIPT_ERR_OK), nValue(nValue_) + TestBuilder(const CScript& script_, const std::string& comment_, uint32_t flags_, bool P2SH = false, WitnessMode wm = WitnessMode::NONE, int witnessversion = 0, CAmount nValue_ = 0) : script(script_), comment(comment_), flags(flags_), nValue(nValue_) { CScript scriptPubKey = script; if (wm == WitnessMode::PKH) { @@ -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; @@ -1162,7 +1162,7 @@ SignatureData CombineSignatures(const CTxOut& txout, const CMutableTransaction& SignatureData data; data.MergeSignatureData(scriptSig1); data.MergeSignatureData(scriptSig2); - ProduceSignature(DUMMY_SIGNING_PROVIDER, MutableTransactionSignatureCreator(&tx, 0, txout.nValue, SIGHASH_DEFAULT), txout.scriptPubKey, data); + ProduceSignature(DUMMY_SIGNING_PROVIDER, MutableTransactionSignatureCreator(tx, 0, txout.nValue, SIGHASH_DEFAULT), txout.scriptPubKey, data); return data; } @@ -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; @@ -1678,7 +1678,7 @@ static void AssetTest(const UniValue& test) CMutableTransaction mtx = TxFromHex(test["tx"].get_str()); const std::vector<CTxOut> prevouts = TxOutsFromJSON(test["prevouts"]); BOOST_CHECK(prevouts.size() == mtx.vin.size()); - size_t idx = test["index"].get_int64(); + size_t idx = test["index"].getInt<int64_t>(); uint32_t test_flags{ParseScriptFlags(test["flags"].get_str())}; bool fin = test.exists("final") && test["final"].get_bool(); @@ -1760,7 +1760,7 @@ BOOST_AUTO_TEST_CASE(bip341_keypath_test_vectors) for (const auto& utxo_spent : vec["given"]["utxosSpent"].getValues()) { auto script_bytes = ParseHex(utxo_spent["scriptPubKey"].get_str()); CScript script{script_bytes.begin(), script_bytes.end()}; - CAmount amount{utxo_spent["amountSats"].get_int()}; + CAmount amount{utxo_spent["amountSats"].getInt<int>()}; utxos.emplace_back(amount, script); } @@ -1775,8 +1775,8 @@ BOOST_AUTO_TEST_CASE(bip341_keypath_test_vectors) BOOST_CHECK_EQUAL(HexStr(txdata.m_sequences_single_hash), vec["intermediary"]["hashSequences"].get_str()); for (const auto& input : vec["inputSpending"].getValues()) { - int txinpos = input["given"]["txinIndex"].get_int(); - int hashtype = input["given"]["hashType"].get_int(); + int txinpos = input["given"]["txinIndex"].getInt<int>(); + int hashtype = input["given"]["hashType"].getInt<int>(); // Load key. auto privkey = ParseHex(input["given"]["internalPrivkey"].get_str()); @@ -1796,7 +1796,7 @@ BOOST_AUTO_TEST_CASE(bip341_keypath_test_vectors) // Sign and verify signature. FlatSigningProvider provider; provider.keys[key.GetPubKey().GetID()] = key; - MutableTransactionSignatureCreator creator(&tx, txinpos, utxos[txinpos].nValue, &txdata, hashtype); + MutableTransactionSignatureCreator creator(tx, txinpos, utxos[txinpos].nValue, &txdata, hashtype); std::vector<unsigned char> signature; BOOST_CHECK(creator.CreateSchnorrSig(provider, signature, pubkey, nullptr, &merkle_root, SigVersion::TAPROOT)); BOOST_CHECK_EQUAL(HexStr(signature), input["expected"]["witness"][0].get_str()); @@ -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/settings_tests.cpp b/src/test/settings_tests.cpp index 15ffd068c7..0feb68b9b1 100644 --- a/src/test/settings_tests.cpp +++ b/src/test/settings_tests.cpp @@ -105,7 +105,7 @@ BOOST_AUTO_TEST_CASE(ReadWrite) //! Check settings struct contents against expected json strings. static void CheckValues(const util::Settings& settings, const std::string& single_val, const std::string& list_val) { - util::SettingsValue single_value = GetSetting(settings, "section", "name", false, false); + util::SettingsValue single_value = GetSetting(settings, "section", "name", false, false, false); util::SettingsValue list_value(util::SettingsValue::VARR); for (const auto& item : GetSettingsList(settings, "section", "name", false)) { list_value.push_back(item); @@ -141,9 +141,9 @@ BOOST_AUTO_TEST_CASE(NullOverride) { util::Settings settings; settings.command_line_options["name"].push_back("value"); - BOOST_CHECK_EQUAL(R"("value")", GetSetting(settings, "section", "name", false, false).write().c_str()); + BOOST_CHECK_EQUAL(R"("value")", GetSetting(settings, "section", "name", false, false, false).write().c_str()); settings.forced_settings["name"] = {}; - BOOST_CHECK_EQUAL(R"(null)", GetSetting(settings, "section", "name", false, false).write().c_str()); + BOOST_CHECK_EQUAL(R"(null)", GetSetting(settings, "section", "name", false, false, false).write().c_str()); } // Test different ways settings can be merged, and verify results. This test can @@ -224,7 +224,7 @@ BOOST_FIXTURE_TEST_CASE(Merge, MergeTestingSetup) } desc += " || "; - desc += GetSetting(settings, network, name, ignore_default_section_config, /* get_chain_name= */ false).write(); + desc += GetSetting(settings, network, name, ignore_default_section_config, /*ignore_nonpersistent=*/false, /*get_chain_name=*/false).write(); desc += " |"; for (const auto& s : GetSettingsList(settings, network, name, ignore_default_section_config)) { desc += " "; diff --git a/src/test/sighash_tests.cpp b/src/test/sighash_tests.cpp index 1601b02356..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) { @@ -184,8 +184,8 @@ BOOST_AUTO_TEST_CASE(sighash_from_data) // deserialize test data raw_tx = test[0].get_str(); raw_script = test[1].get_str(); - nIn = test[2].get_int(); - nHashType = test[3].get_int(); + nIn = test[2].getInt<int>(); + nHashType = test[3].getInt<int>(); sigHashHex = test[4].get_str(); CDataStream stream(ParseHex(raw_tx), SER_NETWORK, PROTOCOL_VERSION); diff --git a/src/test/skiplist_tests.cpp b/src/test/skiplist_tests.cpp index 6dadf09176..3d3fd5d93d 100644 --- a/src/test/skiplist_tests.cpp +++ b/src/test/skiplist_tests.cpp @@ -72,7 +72,7 @@ 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++) { @@ -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 9c6950f11f..f160bb08a5 100644 --- a/src/test/system_tests.cpp +++ b/src/test/system_tests.cpp @@ -7,12 +7,16 @@ #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 +#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 <boost/test/unit_test.hpp> diff --git a/src/test/transaction_tests.cpp b/src/test/transaction_tests.cpp index 4fb7f9c405..952598f745 100644 --- a/src/test/transaction_tests.cpp +++ b/src/test/transaction_tests.cpp @@ -31,8 +31,6 @@ #include <map> #include <string> -#include <boost/algorithm/string/classification.hpp> -#include <boost/algorithm/string/split.hpp> #include <boost/test/unit_test.hpp> #include <univalue.h> @@ -42,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}, @@ -70,8 +71,7 @@ unsigned int ParseScriptFlags(std::string strFlags) { if (strFlags.empty() || strFlags == "NONE") return 0; unsigned int flags = 0; - std::vector<std::string> words; - boost::algorithm::split(words, strFlags, boost::algorithm::is_any_of(",")); + std::vector<std::string> words = SplitString(strFlags, ','); for (const std::string& word : words) { @@ -194,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()) { @@ -214,17 +214,17 @@ 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; break; } - COutPoint outpoint{uint256S(vinput[0].get_str()), uint32_t(vinput[1].get_int())}; + COutPoint outpoint{uint256S(vinput[0].get_str()), uint32_t(vinput[1].getInt<int>())}; mapprevOutScriptPubKeys[outpoint] = ParseScript(vinput[2].get_str()); if (vinput.size() >= 4) { - mapprevOutValues[outpoint] = vinput[3].get_int64(); + mapprevOutValues[outpoint] = vinput[3].getInt<int64_t>(); } } if (!fValid) @@ -282,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()) { @@ -302,17 +302,17 @@ 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; break; } - COutPoint outpoint{uint256S(vinput[0].get_str()), uint32_t(vinput[1].get_int())}; + COutPoint outpoint{uint256S(vinput[0].get_str()), uint32_t(vinput[1].getInt<int>())}; mapprevOutScriptPubKeys[outpoint] = ParseScript(vinput[2].get_str()); if (vinput.size() >= 4) { - mapprevOutValues[outpoint] = vinput[3].get_int64(); + mapprevOutValues[outpoint] = vinput[3].getInt<int64_t>(); } } if (!fValid) @@ -562,7 +562,7 @@ SignatureData CombineSignatures(const CMutableTransaction& input1, const CMutabl SignatureData sigdata; sigdata = DataFromTransaction(input1, 0, tx->vout[0]); sigdata.MergeSignatureData(DataFromTransaction(input2, 0, tx->vout[0])); - ProduceSignature(DUMMY_SIGNING_PROVIDER, MutableTransactionSignatureCreator(&input1, 0, tx->vout[0].nValue, SIGHASH_ALL), tx->vout[0].scriptPubKey, sigdata); + ProduceSignature(DUMMY_SIGNING_PROVIDER, MutableTransactionSignatureCreator(input1, 0, tx->vout[0].nValue, SIGHASH_ALL), tx->vout[0].scriptPubKey, sigdata); return sigdata; } @@ -748,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); @@ -768,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; @@ -808,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"); @@ -927,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/txpackage_tests.cpp b/src/test/txpackage_tests.cpp index 6013080dc6..079b753304 100644 --- a/src/test/txpackage_tests.cpp +++ b/src/test/txpackage_tests.cpp @@ -73,19 +73,19 @@ BOOST_FIXTURE_TEST_CASE(package_validation_tests, TestChain100Setup) CKey parent_key; parent_key.MakeNewKey(true); CScript parent_locking_script = GetScriptForDestination(PKHash(parent_key.GetPubKey())); - auto mtx_parent = CreateValidMempoolTransaction(/*input_transaction=*/ m_coinbase_txns[0], /*input_vout=*/0, - /*input_height=*/ 0, /*input_signing_key=*/coinbaseKey, - /*output_destination=*/ parent_locking_script, - /*output_amount=*/ CAmount(49 * COIN), /*submit=*/false); + auto mtx_parent = CreateValidMempoolTransaction(/*input_transaction=*/m_coinbase_txns[0], /*input_vout=*/0, + /*input_height=*/0, /*input_signing_key=*/coinbaseKey, + /*output_destination=*/parent_locking_script, + /*output_amount=*/CAmount(49 * COIN), /*submit=*/false); CTransactionRef tx_parent = MakeTransactionRef(mtx_parent); CKey child_key; child_key.MakeNewKey(true); CScript child_locking_script = GetScriptForDestination(PKHash(child_key.GetPubKey())); - auto mtx_child = CreateValidMempoolTransaction(/*input_transaction=*/ tx_parent, /*input_vout=*/0, - /*input_height=*/ 101, /*input_signing_key=*/parent_key, + auto mtx_child = CreateValidMempoolTransaction(/*input_transaction=*/tx_parent, /*input_vout=*/0, + /*input_height=*/101, /*input_signing_key=*/parent_key, /*output_destination=*/child_locking_script, - /*output_amount=*/ CAmount(48 * COIN), /*submit=*/false); + /*output_amount=*/CAmount(48 * COIN), /*submit=*/false); CTransactionRef tx_child = MakeTransactionRef(mtx_child); const auto result_parent_child = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool, {tx_parent, tx_child}, /*test_accept=*/true); BOOST_CHECK_MESSAGE(result_parent_child.m_state.IsValid(), @@ -98,7 +98,9 @@ BOOST_FIXTURE_TEST_CASE(package_validation_tests, TestChain100Setup) BOOST_CHECK(it_child != result_parent_child.m_tx_results.end()); BOOST_CHECK_MESSAGE(it_child->second.m_state.IsValid(), "Package validation unexpectedly failed: " << it_child->second.m_state.GetRejectReason()); - + BOOST_CHECK(result_parent_child.m_package_feerate.has_value()); + BOOST_CHECK(result_parent_child.m_package_feerate.value() == + CFeeRate(2 * COIN, GetVirtualTransactionSize(*tx_parent) + GetVirtualTransactionSize(*tx_child))); // A single, giant transaction submitted through ProcessNewPackage fails on single tx policy. CTransactionRef giant_ptx = create_placeholder_tx(999, 999); @@ -110,6 +112,7 @@ BOOST_FIXTURE_TEST_CASE(package_validation_tests, TestChain100Setup) auto it_giant_tx = result_single_large.m_tx_results.find(giant_ptx->GetWitnessHash()); BOOST_CHECK(it_giant_tx != result_single_large.m_tx_results.end()); BOOST_CHECK_EQUAL(it_giant_tx->second.m_state.GetRejectReason(), "tx-size"); + BOOST_CHECK(result_single_large.m_package_feerate == std::nullopt); // Check that mempool size hasn't changed. BOOST_CHECK_EQUAL(m_node.mempool->size(), initialPoolSize); @@ -128,11 +131,11 @@ BOOST_FIXTURE_TEST_CASE(noncontextual_package_tests, TestChain100Setup) // Parent and Child Package { auto mtx_parent = CreateValidMempoolTransaction(m_coinbase_txns[0], 0, 0, coinbaseKey, spk, - CAmount(49 * COIN), /* submit */ false); + CAmount(49 * COIN), /*submit=*/false); CTransactionRef tx_parent = MakeTransactionRef(mtx_parent); auto mtx_child = CreateValidMempoolTransaction(tx_parent, 0, 101, placeholder_key, spk2, - CAmount(48 * COIN), /* submit */ false); + CAmount(48 * COIN), /*submit=*/false); CTransactionRef tx_child = MakeTransactionRef(mtx_child); PackageValidationState state; @@ -218,26 +221,27 @@ BOOST_FIXTURE_TEST_CASE(package_submission_tests, TestChain100Setup) // Unrelated transactions are not allowed in package submission. Package package_unrelated; for (size_t i{0}; i < 10; ++i) { - auto mtx = CreateValidMempoolTransaction(/* input_transaction */ m_coinbase_txns[i + 25], /* vout */ 0, - /* input_height */ 0, /* input_signing_key */ coinbaseKey, - /* output_destination */ parent_locking_script, - /* output_amount */ CAmount(49 * COIN), /* submit */ false); + auto mtx = CreateValidMempoolTransaction(/*input_transaction=*/m_coinbase_txns[i + 25], /*input_vout=*/0, + /*input_height=*/0, /*input_signing_key=*/coinbaseKey, + /*output_destination=*/parent_locking_script, + /*output_amount=*/CAmount(49 * COIN), /*submit=*/false); package_unrelated.emplace_back(MakeTransactionRef(mtx)); } auto result_unrelated_submit = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool, - package_unrelated, /* test_accept */ false); + package_unrelated, /*test_accept=*/false); BOOST_CHECK(result_unrelated_submit.m_state.IsInvalid()); BOOST_CHECK_EQUAL(result_unrelated_submit.m_state.GetResult(), PackageValidationResult::PCKG_POLICY); BOOST_CHECK_EQUAL(result_unrelated_submit.m_state.GetRejectReason(), "package-not-child-with-parents"); BOOST_CHECK_EQUAL(m_node.mempool->size(), expected_pool_size); + BOOST_CHECK(result_unrelated_submit.m_package_feerate == std::nullopt); // Parent and Child (and Grandchild) Package Package package_parent_child; Package package_3gen; - auto mtx_parent = CreateValidMempoolTransaction(/* input_transaction */ m_coinbase_txns[0], /* vout */ 0, - /* input_height */ 0, /* input_signing_key */ coinbaseKey, - /* output_destination */ parent_locking_script, - /* output_amount */ CAmount(49 * COIN), /* submit */ false); + auto mtx_parent = CreateValidMempoolTransaction(/*input_transaction=*/m_coinbase_txns[0], /*input_vout=*/0, + /*input_height=*/0, /*input_signing_key=*/coinbaseKey, + /*output_destination=*/parent_locking_script, + /*output_amount=*/CAmount(49 * COIN), /*submit=*/false); CTransactionRef tx_parent = MakeTransactionRef(mtx_parent); package_parent_child.push_back(tx_parent); package_3gen.push_back(tx_parent); @@ -245,10 +249,10 @@ BOOST_FIXTURE_TEST_CASE(package_submission_tests, TestChain100Setup) CKey child_key; child_key.MakeNewKey(true); CScript child_locking_script = GetScriptForDestination(PKHash(child_key.GetPubKey())); - auto mtx_child = CreateValidMempoolTransaction(/* input_transaction */ tx_parent, /* vout */ 0, - /* input_height */ 101, /* input_signing_key */ parent_key, - /* output_destination */ child_locking_script, - /* output_amount */ CAmount(48 * COIN), /* submit */ false); + auto mtx_child = CreateValidMempoolTransaction(/*input_transaction=*/tx_parent, /*input_vout=*/0, + /*input_height=*/101, /*input_signing_key=*/parent_key, + /*output_destination=*/child_locking_script, + /*output_amount=*/CAmount(48 * COIN), /*submit=*/false); CTransactionRef tx_child = MakeTransactionRef(mtx_child); package_parent_child.push_back(tx_child); package_3gen.push_back(tx_child); @@ -256,21 +260,22 @@ BOOST_FIXTURE_TEST_CASE(package_submission_tests, TestChain100Setup) CKey grandchild_key; grandchild_key.MakeNewKey(true); CScript grandchild_locking_script = GetScriptForDestination(PKHash(grandchild_key.GetPubKey())); - auto mtx_grandchild = CreateValidMempoolTransaction(/* input_transaction */ tx_child, /* vout */ 0, - /* input_height */ 101, /* input_signing_key */ child_key, - /* output_destination */ grandchild_locking_script, - /* output_amount */ CAmount(47 * COIN), /* submit */ false); + auto mtx_grandchild = CreateValidMempoolTransaction(/*input_transaction=*/tx_child, /*input_vout=*/0, + /*input_height=*/101, /*input_signing_key=*/child_key, + /*output_destination=*/grandchild_locking_script, + /*output_amount=*/CAmount(47 * COIN), /*submit=*/false); CTransactionRef tx_grandchild = MakeTransactionRef(mtx_grandchild); package_3gen.push_back(tx_grandchild); // 3 Generations is not allowed. { auto result_3gen_submit = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool, - package_3gen, /* test_accept */ false); + package_3gen, /*test_accept=*/false); BOOST_CHECK(result_3gen_submit.m_state.IsInvalid()); BOOST_CHECK_EQUAL(result_3gen_submit.m_state.GetResult(), PackageValidationResult::PCKG_POLICY); BOOST_CHECK_EQUAL(result_3gen_submit.m_state.GetRejectReason(), "package-not-child-with-parents"); BOOST_CHECK_EQUAL(m_node.mempool->size(), expected_pool_size); + BOOST_CHECK(result_3gen_submit.m_package_feerate == std::nullopt); } // Child with missing parent. @@ -280,18 +285,19 @@ BOOST_FIXTURE_TEST_CASE(package_submission_tests, TestChain100Setup) package_missing_parent.push_back(MakeTransactionRef(mtx_child)); { const auto result_missing_parent = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool, - package_missing_parent, /* test_accept */ false); + package_missing_parent, /*test_accept=*/false); BOOST_CHECK(result_missing_parent.m_state.IsInvalid()); BOOST_CHECK_EQUAL(result_missing_parent.m_state.GetResult(), PackageValidationResult::PCKG_POLICY); BOOST_CHECK_EQUAL(result_missing_parent.m_state.GetRejectReason(), "package-not-child-with-unconfirmed-parents"); BOOST_CHECK_EQUAL(m_node.mempool->size(), expected_pool_size); + BOOST_CHECK(result_missing_parent.m_package_feerate == std::nullopt); } // Submit package with parent + child. { const auto submit_parent_child = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool, - package_parent_child, /* test_accept */ false); + package_parent_child, /*test_accept=*/false); expected_pool_size += 2; BOOST_CHECK_MESSAGE(submit_parent_child.m_state.IsValid(), "Package validation unexpectedly failed: " << submit_parent_child.m_state.GetRejectReason()); @@ -305,12 +311,16 @@ BOOST_FIXTURE_TEST_CASE(package_submission_tests, TestChain100Setup) BOOST_CHECK_EQUAL(m_node.mempool->size(), expected_pool_size); BOOST_CHECK(m_node.mempool->exists(GenTxid::Txid(tx_parent->GetHash()))); BOOST_CHECK(m_node.mempool->exists(GenTxid::Txid(tx_child->GetHash()))); + + // Since both transactions have high feerates, they each passed validation individually. + // Package validation was unnecessary, so there is no package feerate. + BOOST_CHECK(submit_parent_child.m_package_feerate == std::nullopt); } // Already-in-mempool transactions should be detected and de-duplicated. { const auto submit_deduped = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool, - package_parent_child, /* test_accept */ false); + package_parent_child, /*test_accept=*/false); BOOST_CHECK_MESSAGE(submit_deduped.m_state.IsValid(), "Package validation unexpectedly failed: " << submit_deduped.m_state.GetRejectReason()); auto it_parent_deduped = submit_deduped.m_tx_results.find(tx_parent->GetWitnessHash()); @@ -325,6 +335,8 @@ BOOST_FIXTURE_TEST_CASE(package_submission_tests, TestChain100Setup) BOOST_CHECK_EQUAL(m_node.mempool->size(), expected_pool_size); BOOST_CHECK(m_node.mempool->exists(GenTxid::Txid(tx_parent->GetHash()))); BOOST_CHECK(m_node.mempool->exists(GenTxid::Txid(tx_child->GetHash()))); + + BOOST_CHECK(submit_deduped.m_package_feerate == std::nullopt); } } @@ -340,10 +352,10 @@ BOOST_FIXTURE_TEST_CASE(package_witness_swap_tests, TestChain100Setup) // and the mempool entry's wtxid returned. CScript witnessScript = CScript() << OP_DROP << OP_TRUE; CScript scriptPubKey = GetScriptForDestination(WitnessV0ScriptHash(witnessScript)); - auto mtx_parent = CreateValidMempoolTransaction(/*input_transaction=*/ m_coinbase_txns[0], /*vout=*/ 0, - /*input_height=*/ 0, /*input_signing_key=*/ coinbaseKey, - /*output_destination=*/ scriptPubKey, - /*output_amount=*/ CAmount(49 * COIN), /*submit=*/ false); + auto mtx_parent = CreateValidMempoolTransaction(/*input_transaction=*/m_coinbase_txns[0], /*input_vout=*/0, + /*input_height=*/0, /*input_signing_key=*/coinbaseKey, + /*output_destination=*/scriptPubKey, + /*output_amount=*/CAmount(49 * COIN), /*submit=*/false); CTransactionRef ptx_parent = MakeTransactionRef(mtx_parent); // Make two children with the same txid but different witnesses. @@ -384,7 +396,7 @@ BOOST_FIXTURE_TEST_CASE(package_witness_swap_tests, TestChain100Setup) // same-txid-different-witness. { const auto submit_witness1 = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool, - {ptx_parent, ptx_child1}, /*test_accept=*/ false); + {ptx_parent, ptx_child1}, /*test_accept=*/false); BOOST_CHECK_MESSAGE(submit_witness1.m_state.IsValid(), "Package validation unexpectedly failed: " << submit_witness1.m_state.GetRejectReason()); auto it_parent1 = submit_witness1.m_tx_results.find(ptx_parent->GetWitnessHash()); @@ -399,8 +411,12 @@ BOOST_FIXTURE_TEST_CASE(package_witness_swap_tests, TestChain100Setup) BOOST_CHECK(m_node.mempool->exists(GenTxid::Txid(ptx_parent->GetHash()))); BOOST_CHECK(m_node.mempool->exists(GenTxid::Txid(ptx_child1->GetHash()))); + // Child2 would have been validated individually. + BOOST_CHECK(submit_witness1.m_package_feerate == std::nullopt); + const auto submit_witness2 = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool, - {ptx_parent, ptx_child2}, /*test_accept=*/ false); + {ptx_parent, ptx_child2}, /*test_accept=*/false); + BOOST_CHECK(submit_witness2.m_package_feerate == std::nullopt); BOOST_CHECK_MESSAGE(submit_witness2.m_state.IsValid(), "Package validation unexpectedly failed: " << submit_witness2.m_state.GetRejectReason()); auto it_parent2_deduped = submit_witness2.m_tx_results.find(ptx_parent->GetWitnessHash()); @@ -417,13 +433,14 @@ BOOST_FIXTURE_TEST_CASE(package_witness_swap_tests, TestChain100Setup) // Deduplication should work when wtxid != txid. Submit package with the already-in-mempool // transactions again, which should not fail. const auto submit_segwit_dedup = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool, - {ptx_parent, ptx_child1}, /*test_accept=*/ false); + {ptx_parent, ptx_child1}, /*test_accept=*/false); BOOST_CHECK_MESSAGE(submit_segwit_dedup.m_state.IsValid(), "Package validation unexpectedly failed: " << submit_segwit_dedup.m_state.GetRejectReason()); auto it_parent_dup = submit_segwit_dedup.m_tx_results.find(ptx_parent->GetWitnessHash()); auto it_child_dup = submit_segwit_dedup.m_tx_results.find(ptx_child1->GetWitnessHash()); BOOST_CHECK(it_parent_dup->second.m_result_type == MempoolAcceptResult::ResultType::MEMPOOL_ENTRY); BOOST_CHECK(it_child_dup->second.m_result_type == MempoolAcceptResult::ResultType::MEMPOOL_ENTRY); + BOOST_CHECK(submit_witness2.m_package_feerate == std::nullopt); } // Try submitting Package1{child2, grandchild} where child2 is same-txid-different-witness as @@ -436,16 +453,16 @@ BOOST_FIXTURE_TEST_CASE(package_witness_swap_tests, TestChain100Setup) CKey grandchild_key; grandchild_key.MakeNewKey(true); CScript grandchild_locking_script = GetScriptForDestination(WitnessV0KeyHash(grandchild_key.GetPubKey())); - auto mtx_grandchild = CreateValidMempoolTransaction(/*input_transaction=*/ ptx_child2, /* vout=*/ 0, - /*input_height=*/ 0, /*input_signing_key=*/ child_key, - /*output_destination=*/ grandchild_locking_script, - /*output_amount=*/ CAmount(47 * COIN), /*submit=*/ false); + auto mtx_grandchild = CreateValidMempoolTransaction(/*input_transaction=*/ptx_child2, /*input_vout=*/0, + /*input_height=*/0, /*input_signing_key=*/child_key, + /*output_destination=*/grandchild_locking_script, + /*output_amount=*/CAmount(47 * COIN), /*submit=*/false); CTransactionRef ptx_grandchild = MakeTransactionRef(mtx_grandchild); // We already submitted child1 above. { const auto submit_spend_ignored = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool, - {ptx_child2, ptx_grandchild}, /*test_accept=*/ false); + {ptx_child2, ptx_grandchild}, /*test_accept=*/false); BOOST_CHECK_MESSAGE(submit_spend_ignored.m_state.IsValid(), "Package validation unexpectedly failed: " << submit_spend_ignored.m_state.GetRejectReason()); auto it_child2_ignored = submit_spend_ignored.m_tx_results.find(ptx_child2->GetWitnessHash()); @@ -458,6 +475,9 @@ BOOST_FIXTURE_TEST_CASE(package_witness_swap_tests, TestChain100Setup) BOOST_CHECK(m_node.mempool->exists(GenTxid::Txid(ptx_child2->GetHash()))); BOOST_CHECK(!m_node.mempool->exists(GenTxid::Wtxid(ptx_child2->GetWitnessHash()))); BOOST_CHECK(m_node.mempool->exists(GenTxid::Wtxid(ptx_grandchild->GetWitnessHash()))); + + // Since child2 is ignored, grandchild would be validated individually. + BOOST_CHECK(submit_spend_ignored.m_package_feerate == std::nullopt); } // A package Package{parent1, parent2, parent3, child} where the parents are a mixture of @@ -471,10 +491,10 @@ BOOST_FIXTURE_TEST_CASE(package_witness_swap_tests, TestChain100Setup) acs_witness.stack.push_back(std::vector<unsigned char>(acs_script.begin(), acs_script.end())); // parent1 will already be in the mempool - auto mtx_parent1 = CreateValidMempoolTransaction(/*input_transaction=*/ m_coinbase_txns[1], /*vout=*/ 0, - /*input_height=*/ 0, /*input_signing_key=*/ coinbaseKey, - /*output_destination=*/ acs_spk, - /*output_amount=*/ CAmount(49 * COIN), /*submit=*/ true); + auto mtx_parent1 = CreateValidMempoolTransaction(/*input_transaction=*/m_coinbase_txns[1], /*input_vout=*/0, + /*input_height=*/0, /*input_signing_key=*/coinbaseKey, + /*output_destination=*/acs_spk, + /*output_amount=*/CAmount(49 * COIN), /*submit=*/true); CTransactionRef ptx_parent1 = MakeTransactionRef(mtx_parent1); package_mixed.push_back(ptx_parent1); @@ -489,10 +509,10 @@ BOOST_FIXTURE_TEST_CASE(package_witness_swap_tests, TestChain100Setup) parent2_witness2.stack.push_back(std::vector<unsigned char>(grandparent2_script.begin(), grandparent2_script.end())); // Create grandparent2 creating an output with multiple spending paths. Submit to mempool. - auto mtx_grandparent2 = CreateValidMempoolTransaction(/*input_transaction=*/ m_coinbase_txns[2], /* vout=*/ 0, - /*input_height=*/ 0, /*input_signing_key=*/ coinbaseKey, - /*output_destination=*/ grandparent2_spk, - /*output_amount=*/ CAmount(49 * COIN), /*submit=*/ true); + auto mtx_grandparent2 = CreateValidMempoolTransaction(/*input_transaction=*/m_coinbase_txns[2], /*input_vout=*/0, + /*input_height=*/0, /*input_signing_key=*/coinbaseKey, + /*output_destination=*/grandparent2_spk, + /*output_amount=*/CAmount(49 * COIN), /*submit=*/true); CTransactionRef ptx_grandparent2 = MakeTransactionRef(mtx_grandparent2); CMutableTransaction mtx_parent2_v1; @@ -516,11 +536,11 @@ BOOST_FIXTURE_TEST_CASE(package_witness_swap_tests, TestChain100Setup) BOOST_CHECK(parent2_v2_result.m_result_type == MempoolAcceptResult::ResultType::VALID); package_mixed.push_back(ptx_parent2_v1); - // parent3 will be a new transaction - auto mtx_parent3 = CreateValidMempoolTransaction(/*input_transaction=*/ m_coinbase_txns[3], /*vout=*/ 0, - /*input_height=*/ 0, /*input_signing_key=*/ coinbaseKey, - /*output_destination=*/ acs_spk, - /*output_amount=*/ CAmount(49 * COIN), /*submit=*/ false); + // parent3 will be a new transaction. Put 0 fees on it to make it invalid on its own. + auto mtx_parent3 = CreateValidMempoolTransaction(/*input_transaction=*/m_coinbase_txns[3], /*input_vout=*/0, + /*input_height=*/0, /*input_signing_key=*/coinbaseKey, + /*output_destination=*/acs_spk, + /*output_amount=*/CAmount(50 * COIN), /*submit=*/false); CTransactionRef ptx_parent3 = MakeTransactionRef(mtx_parent3); package_mixed.push_back(ptx_parent3); @@ -536,7 +556,7 @@ BOOST_FIXTURE_TEST_CASE(package_witness_swap_tests, TestChain100Setup) mtx_mixed_child.vin[0].scriptWitness = acs_witness; mtx_mixed_child.vin[1].scriptWitness = acs_witness; mtx_mixed_child.vin[2].scriptWitness = acs_witness; - mtx_mixed_child.vout.push_back(CTxOut(145 * COIN, mixed_child_spk)); + mtx_mixed_child.vout.push_back(CTxOut((48 + 49 + 50 - 1) * COIN, mixed_child_spk)); CTransactionRef ptx_mixed_child = MakeTransactionRef(mtx_mixed_child); package_mixed.push_back(ptx_mixed_child); @@ -568,6 +588,205 @@ BOOST_FIXTURE_TEST_CASE(package_witness_swap_tests, TestChain100Setup) BOOST_CHECK(!m_node.mempool->exists(GenTxid::Wtxid(ptx_parent2_v1->GetWitnessHash()))); BOOST_CHECK(m_node.mempool->exists(GenTxid::Txid(ptx_parent3->GetHash()))); BOOST_CHECK(m_node.mempool->exists(GenTxid::Txid(ptx_mixed_child->GetHash()))); + + // package feerate should include parent3 and child. It should not include parent1 or parent2_v1. + BOOST_CHECK(mixed_result.m_package_feerate.has_value()); + const CFeeRate expected_feerate(1 * COIN, GetVirtualTransactionSize(*ptx_parent3) + GetVirtualTransactionSize(*ptx_mixed_child)); + BOOST_CHECK_MESSAGE(mixed_result.m_package_feerate.value() == expected_feerate, + strprintf("Expected package feerate %s, got %s", expected_feerate.ToString(), + mixed_result.m_package_feerate.value().ToString())); + } +} + +BOOST_FIXTURE_TEST_CASE(package_cpfp_tests, TestChain100Setup) +{ + mineBlocks(5); + LOCK(::cs_main); + size_t expected_pool_size = m_node.mempool->size(); + CKey child_key; + child_key.MakeNewKey(true); + CScript parent_spk = GetScriptForDestination(WitnessV0KeyHash(child_key.GetPubKey())); + CKey grandchild_key; + grandchild_key.MakeNewKey(true); + CScript child_spk = GetScriptForDestination(WitnessV0KeyHash(grandchild_key.GetPubKey())); + + // zero-fee parent and high-fee child package + const CAmount coinbase_value{50 * COIN}; + const CAmount parent_value{coinbase_value - 0}; + const CAmount child_value{parent_value - COIN}; + + Package package_cpfp; + auto mtx_parent = CreateValidMempoolTransaction(/*input_transaction=*/m_coinbase_txns[0], /*input_vout=*/0, + /*input_height=*/0, /*input_signing_key=*/coinbaseKey, + /*output_destination=*/parent_spk, + /*output_amount=*/parent_value, /*submit=*/false); + CTransactionRef tx_parent = MakeTransactionRef(mtx_parent); + package_cpfp.push_back(tx_parent); + + auto mtx_child = CreateValidMempoolTransaction(/*input_transaction=*/tx_parent, /*input_vout=*/0, + /*input_height=*/101, /*input_signing_key=*/child_key, + /*output_destination=*/child_spk, + /*output_amount=*/child_value, /*submit=*/false); + CTransactionRef tx_child = MakeTransactionRef(mtx_child); + package_cpfp.push_back(tx_child); + + // Package feerate is calculated using modified fees, and prioritisetransaction accepts negative + // fee deltas. This should be taken into account. De-prioritise the parent transaction by -1BTC, + // bringing the package feerate to 0. + m_node.mempool->PrioritiseTransaction(tx_parent->GetHash(), -1 * COIN); + { + BOOST_CHECK_EQUAL(m_node.mempool->size(), expected_pool_size); + const auto submit_cpfp_deprio = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool, + package_cpfp, /*test_accept=*/ false); + BOOST_CHECK_MESSAGE(submit_cpfp_deprio.m_state.IsInvalid(), + "Package validation unexpectedly succeeded: " << submit_cpfp_deprio.m_state.GetRejectReason()); + BOOST_CHECK(submit_cpfp_deprio.m_tx_results.empty()); + BOOST_CHECK_EQUAL(m_node.mempool->size(), expected_pool_size); + const CFeeRate expected_feerate(0, GetVirtualTransactionSize(*tx_parent) + GetVirtualTransactionSize(*tx_child)); + BOOST_CHECK(submit_cpfp_deprio.m_package_feerate.has_value()); + BOOST_CHECK(submit_cpfp_deprio.m_package_feerate.value() == CFeeRate{0}); + BOOST_CHECK_MESSAGE(submit_cpfp_deprio.m_package_feerate.value() == expected_feerate, + strprintf("Expected package feerate %s, got %s", expected_feerate.ToString(), + submit_cpfp_deprio.m_package_feerate.value().ToString())); + } + + // Clear the prioritisation of the parent transaction. + WITH_LOCK(m_node.mempool->cs, m_node.mempool->ClearPrioritisation(tx_parent->GetHash())); + + // Package CPFP: Even though the parent pays 0 absolute fees, the child pays 1 BTC which is + // enough for the package feerate to meet the threshold. + { + BOOST_CHECK_EQUAL(m_node.mempool->size(), expected_pool_size); + const auto submit_cpfp = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool, + package_cpfp, /*test_accept=*/ false); + expected_pool_size += 2; + BOOST_CHECK_MESSAGE(submit_cpfp.m_state.IsValid(), + "Package validation unexpectedly failed: " << submit_cpfp.m_state.GetRejectReason()); + auto it_parent = submit_cpfp.m_tx_results.find(tx_parent->GetWitnessHash()); + auto it_child = submit_cpfp.m_tx_results.find(tx_child->GetWitnessHash()); + BOOST_CHECK(it_parent != submit_cpfp.m_tx_results.end()); + BOOST_CHECK(it_parent->second.m_result_type == MempoolAcceptResult::ResultType::VALID); + BOOST_CHECK(it_parent->second.m_base_fees.value() == 0); + BOOST_CHECK(it_child != submit_cpfp.m_tx_results.end()); + BOOST_CHECK(it_child->second.m_result_type == MempoolAcceptResult::ResultType::VALID); + BOOST_CHECK(it_child->second.m_base_fees.value() == COIN); + + BOOST_CHECK_EQUAL(m_node.mempool->size(), expected_pool_size); + BOOST_CHECK(m_node.mempool->exists(GenTxid::Txid(tx_parent->GetHash()))); + BOOST_CHECK(m_node.mempool->exists(GenTxid::Txid(tx_child->GetHash()))); + + const CFeeRate expected_feerate(coinbase_value - child_value, + GetVirtualTransactionSize(*tx_parent) + GetVirtualTransactionSize(*tx_child)); + BOOST_CHECK(expected_feerate.GetFeePerK() > 1000); + BOOST_CHECK(submit_cpfp.m_package_feerate.has_value()); + BOOST_CHECK_MESSAGE(submit_cpfp.m_package_feerate.value() == expected_feerate, + strprintf("Expected package feerate %s, got %s", expected_feerate.ToString(), + submit_cpfp.m_package_feerate.value().ToString())); + } + + // Just because we allow low-fee parents doesn't mean we allow low-feerate packages. + // This package just pays 200 satoshis total. This would be enough to pay for the child alone, + // but isn't enough for the entire package to meet the 1sat/vbyte minimum. + Package package_still_too_low; + auto mtx_parent_cheap = CreateValidMempoolTransaction(/*input_transaction=*/m_coinbase_txns[1], /*input_vout=*/0, + /*input_height=*/0, /*input_signing_key=*/coinbaseKey, + /*output_destination=*/parent_spk, + /*output_amount=*/coinbase_value, /*submit=*/false); + CTransactionRef tx_parent_cheap = MakeTransactionRef(mtx_parent_cheap); + package_still_too_low.push_back(tx_parent_cheap); + + auto mtx_child_cheap = CreateValidMempoolTransaction(/*input_transaction=*/tx_parent_cheap, /*input_vout=*/0, + /*input_height=*/101, /*input_signing_key=*/child_key, + /*output_destination=*/child_spk, + /*output_amount=*/coinbase_value - 200, /*submit=*/false); + CTransactionRef tx_child_cheap = MakeTransactionRef(mtx_child_cheap); + package_still_too_low.push_back(tx_child_cheap); + + // Cheap package should fail with package-fee-too-low. + { + BOOST_CHECK_EQUAL(m_node.mempool->size(), expected_pool_size); + const auto submit_package_too_low = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool, + package_still_too_low, /*test_accept=*/false); + BOOST_CHECK_MESSAGE(submit_package_too_low.m_state.IsInvalid(), "Package validation unexpectedly succeeded"); + BOOST_CHECK_EQUAL(submit_package_too_low.m_state.GetResult(), PackageValidationResult::PCKG_POLICY); + BOOST_CHECK_EQUAL(submit_package_too_low.m_state.GetRejectReason(), "package-fee-too-low"); + BOOST_CHECK_EQUAL(m_node.mempool->size(), expected_pool_size); + const CFeeRate child_feerate(200, GetVirtualTransactionSize(*tx_child_cheap)); + BOOST_CHECK(child_feerate.GetFeePerK() > 1000); + const CFeeRate expected_feerate(200, + GetVirtualTransactionSize(*tx_parent_cheap) + GetVirtualTransactionSize(*tx_child_cheap)); + BOOST_CHECK(expected_feerate.GetFeePerK() < 1000); + BOOST_CHECK(submit_package_too_low.m_package_feerate.has_value()); + BOOST_CHECK_MESSAGE(submit_package_too_low.m_package_feerate.value() == expected_feerate, + strprintf("Expected package feerate %s, got %s", expected_feerate.ToString(), + submit_package_too_low.m_package_feerate.value().ToString())); + } + + // Package feerate includes the modified fees of the transactions. + // This means a child with its fee delta from prioritisetransaction can pay for a parent. + m_node.mempool->PrioritiseTransaction(tx_child_cheap->GetHash(), 1 * COIN); + // Now that the child's fees have "increased" by 1 BTC, the cheap package should succeed. + { + const auto submit_prioritised_package = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool, + package_still_too_low, /*test_accept=*/false); + expected_pool_size += 2; + BOOST_CHECK_MESSAGE(submit_prioritised_package.m_state.IsValid(), + "Package validation unexpectedly failed" << submit_prioritised_package.m_state.GetRejectReason()); + const CFeeRate expected_feerate(1 * COIN + 200, + GetVirtualTransactionSize(*tx_parent_cheap) + GetVirtualTransactionSize(*tx_child_cheap)); + BOOST_CHECK(submit_prioritised_package.m_package_feerate.has_value()); + BOOST_CHECK_MESSAGE(submit_prioritised_package.m_package_feerate.value() == expected_feerate, + strprintf("Expected package feerate %s, got %s", expected_feerate.ToString(), + submit_prioritised_package.m_package_feerate.value().ToString())); + } + + // Package feerate is calculated without topology in mind; it's just aggregating fees and sizes. + // However, this should not allow parents to pay for children. Each transaction should be + // validated individually first, eliminating sufficient-feerate parents before they are unfairly + // included in the package feerate. It's also important that the low-fee child doesn't prevent + // the parent from being accepted. + Package package_rich_parent; + const CAmount high_parent_fee{1 * COIN}; + auto mtx_parent_rich = CreateValidMempoolTransaction(/*input_transaction=*/m_coinbase_txns[2], /*input_vout=*/0, + /*input_height=*/0, /*input_signing_key=*/coinbaseKey, + /*output_destination=*/parent_spk, + /*output_amount=*/coinbase_value - high_parent_fee, /*submit=*/false); + CTransactionRef tx_parent_rich = MakeTransactionRef(mtx_parent_rich); + package_rich_parent.push_back(tx_parent_rich); + + auto mtx_child_poor = CreateValidMempoolTransaction(/*input_transaction=*/tx_parent_rich, /*input_vout=*/0, + /*input_height=*/101, /*input_signing_key=*/child_key, + /*output_destination=*/child_spk, + /*output_amount=*/coinbase_value - high_parent_fee, /*submit=*/false); + CTransactionRef tx_child_poor = MakeTransactionRef(mtx_child_poor); + package_rich_parent.push_back(tx_child_poor); + + // Parent pays 1 BTC and child pays none. The parent should be accepted without the child. + { + BOOST_CHECK_EQUAL(m_node.mempool->size(), expected_pool_size); + const auto submit_rich_parent = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool, + package_rich_parent, /*test_accept=*/false); + expected_pool_size += 1; + BOOST_CHECK_MESSAGE(submit_rich_parent.m_state.IsInvalid(), "Package validation unexpectedly succeeded"); + + // The child would have been validated on its own and failed, then submitted as a "package" of 1. + // The package feerate is just the child's feerate, which is 0sat/vb. + BOOST_CHECK(submit_rich_parent.m_package_feerate.has_value()); + BOOST_CHECK_MESSAGE(submit_rich_parent.m_package_feerate.value() == CFeeRate(), + "expected 0, got " << submit_rich_parent.m_package_feerate.value().ToString()); + BOOST_CHECK_EQUAL(submit_rich_parent.m_state.GetResult(), PackageValidationResult::PCKG_POLICY); + BOOST_CHECK_EQUAL(submit_rich_parent.m_state.GetRejectReason(), "package-fee-too-low"); + + auto it_parent = submit_rich_parent.m_tx_results.find(tx_parent_rich->GetWitnessHash()); + BOOST_CHECK(it_parent != submit_rich_parent.m_tx_results.end()); + BOOST_CHECK(it_parent->second.m_result_type == MempoolAcceptResult::ResultType::VALID); + BOOST_CHECK(it_parent->second.m_state.GetRejectReason() == ""); + BOOST_CHECK_MESSAGE(it_parent->second.m_base_fees.value() == high_parent_fee, + strprintf("rich parent: expected fee %s, got %s", high_parent_fee, it_parent->second.m_base_fees.value())); + + BOOST_CHECK_EQUAL(m_node.mempool->size(), expected_pool_size); + BOOST_CHECK(m_node.mempool->exists(GenTxid::Txid(tx_parent_rich->GetHash()))); + BOOST_CHECK(!m_node.mempool->exists(GenTxid::Txid(tx_child_poor->GetHash()))); } } BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/txvalidationcache_tests.cpp b/src/test/txvalidationcache_tests.cpp index b1e2119c64..633f75ff4f 100644 --- a/src/test/txvalidationcache_tests.cpp +++ b/src/test/txvalidationcache_tests.cpp @@ -15,7 +15,7 @@ struct Dersig100Setup : public TestChain100Setup { Dersig100Setup() - : TestChain100Setup{{"-testactivationheight=dersig@102"}} {} + : TestChain100Setup{CBaseChainParams::REGTEST, {"-testactivationheight=dersig@102"}} {} }; bool CheckInputScripts(const CTransaction& tx, TxValidationState& state, @@ -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())); @@ -329,7 +324,7 @@ BOOST_FIXTURE_TEST_CASE(checkinputs_test, Dersig100Setup) // Sign SignatureData sigdata; - BOOST_CHECK(ProduceSignature(keystore, MutableTransactionSignatureCreator(&valid_with_witness_tx, 0, 11*CENT, SIGHASH_ALL), spend_tx.vout[1].scriptPubKey, sigdata)); + BOOST_CHECK(ProduceSignature(keystore, MutableTransactionSignatureCreator(valid_with_witness_tx, 0, 11 * CENT, SIGHASH_ALL), spend_tx.vout[1].scriptPubKey, sigdata)); UpdateInput(valid_with_witness_tx.vin[0], sigdata); // This should be valid under all script flags. @@ -355,9 +350,9 @@ BOOST_FIXTURE_TEST_CASE(checkinputs_test, Dersig100Setup) tx.vout[0].scriptPubKey = p2pk_scriptPubKey; // Sign - for (int i=0; i<2; ++i) { + for (int i = 0; i < 2; ++i) { SignatureData sigdata; - BOOST_CHECK(ProduceSignature(keystore, MutableTransactionSignatureCreator(&tx, i, 11*CENT, SIGHASH_ALL), spend_tx.vout[i].scriptPubKey, sigdata)); + BOOST_CHECK(ProduceSignature(keystore, MutableTransactionSignatureCreator(tx, i, 11 * CENT, SIGHASH_ALL), spend_tx.vout[i].scriptPubKey, sigdata)); UpdateInput(tx.vin[i], sigdata); } diff --git a/src/test/util/chainstate.h b/src/test/util/chainstate.h index 09f96a033c..13e0e684b8 100644 --- a/src/test/util/chainstate.h +++ b/src/test/util/chainstate.h @@ -16,7 +16,7 @@ #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 @@ -30,9 +30,9 @@ CreateAndActivateUTXOSnapshot(node::NodeContext& node, const fs::path root, F ma // int height; WITH_LOCK(::cs_main, height = node.chainman->ActiveHeight()); - fs::path snapshot_path = root / tfm::format("test_snapshot.%d.dat", height); + 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); @@ -42,7 +42,7 @@ CreateAndActivateUTXOSnapshot(node::NodeContext& node, const fs::path root, F ma // 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/logging.h b/src/test/util/logging.h index ebe0ecf623..f477088392 100644 --- a/src/test/util/logging.h +++ b/src/test/util/logging.h @@ -36,6 +36,6 @@ public: ~DebugLogHelper() { check_found(); } }; -#define ASSERT_DEBUG_LOG(message) DebugLogHelper PASTE2(debugloghelper, __COUNTER__)(message) +#define ASSERT_DEBUG_LOG(message) DebugLogHelper UNIQUE_NAME(debugloghelper)(message) #endif // BITCOIN_TEST_UTIL_LOGGING_H diff --git a/src/test/util/mining.cpp b/src/test/util/mining.cpp index 5ed8598e8e..88cf9647e7 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(Params(), block, true, nullptr)}; + bool processed{Assert(node.chainman)->ProcessNewBlock(block, 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), Params()} + 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 fe3cf52974..223db16c6c 100644 --- a/src/test/util/net.cpp +++ b/src/test/util/net.cpp @@ -5,11 +5,71 @@ #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, + NetPermissionFlags permission_flags, + 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); + { + LOCK(node.cs_sendProcessing); + 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); + 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); + } +} + void ConnmanTestMsg::NodeReceiveMsgBytes(CNode& node, Span<const uint8_t> msg_bytes, bool& complete) const { assert(node.ReceiveMsgBytes(msg_bytes, complete)); @@ -52,12 +112,14 @@ std::vector<NodeEvictionCandidate> GetRandomNodeEvictionCandidates(int n_candida /*m_last_block_time=*/std::chrono::seconds{random_context.randrange(100)}, /*m_last_tx_time=*/std::chrono::seconds{random_context.randrange(100)}, /*fRelevantServices=*/random_context.randbool(), - /*fRelayTxes=*/random_context.randbool(), + /*m_relay_txs=*/random_context.randbool(), /*fBloomFilter=*/random_context.randbool(), /*nKeyedNetGroup=*/random_context.randrange(100), /*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 20c45058a1..ec6b4e6e88 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,6 +39,14 @@ struct ConnmanTestMsg : public CConnman { m_nodes.clear(); } + void Handshake(CNode& node, + bool successfully_connected, + ServiceFlags remote_services, + ServiceFlags local_services, + NetPermissionFlags permission_flags, + int32_t version, + bool relay_txs); + void ProcessMessagesOnce(CNode& node) { 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) { @@ -150,6 +158,14 @@ public: return 0; } + 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 @@ -160,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 c968e4d124..30d26ecf79 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> @@ -12,14 +14,19 @@ #include <consensus/validation.h> #include <crypto/sha256.h> #include <init.h> +#include <init/common.h> #include <interfaces/chain.h> #include <net.h> #include <net_processing.h> -#include <node/miner.h> -#include <noui.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> @@ -28,7 +35,10 @@ #include <script/sigcache.h> #include <shutdown.h> #include <streams.h> +#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> @@ -41,16 +51,18 @@ #include <validationinterface.h> #include <walletinitinterface.h> +#include <algorithm> #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; @@ -123,12 +135,15 @@ BasicTestingSetup::BasicTestingSetup(const std::string& chainName, const std::ve InitLogging(*m_node.args); AppInitParameterInteraction(*m_node.args); LogInstance().StartLogging(); - SHA256AutoDetect(); - ECC_Start(); + 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; @@ -144,24 +159,42 @@ BasicTestingSetup::~BasicTestingSetup() LogInstance().DisconnectTestLogger(); fs::remove_all(m_path_root); gArgs.ClearArgs(); - ECC_Stop(); +} + +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) { + const CChainParams& chainparams = Params(); + // We have to run a scheduler thread to prevent ActivateBestChain // from blocking due to queue overrun. m_node.scheduler = std::make_unique<CScheduler>(); 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(), 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); - m_node.chainman = std::make_unique<ChainstateManager>(); + const ChainstateManager::Options chainman_opts{ + .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); // Start script-checking threads. Set g_parallel_script_checks to true so they are used. @@ -179,56 +212,47 @@ ChainTestingSetup::~ChainTestingSetup() m_node.connman.reset(); m_node.banman.reset(); m_node.addrman.reset(); + m_node.netgroupman.reset(); m_node.args = nullptr; - UnloadBlockIndex(m_node.mempool.get(), *m_node.chainman); m_node.mempool.reset(); m_node.scheduler.reset(); - m_node.chainman->Reset(); m_node.chainman.reset(); } TestingSetup::TestingSetup(const std::string& chainName, const std::vector<const char*>& extra_args) : ChainTestingSetup(chainName, extra_args) { - const CChainParams& chainparams = Params(); // Ideally we'd move all the RPC tests to the functional testing framework // 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, - chainparams.GetConsensus(), - 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), - chainparams.GetConsensus(), - m_args.GetIntArg("-checkblocks", DEFAULT_CHECKBLOCKS), - m_args.GetIntArg("-checklevel", DEFAULT_CHECKLEVEL), - /*get_unix_time_seconds=*/static_cast<int64_t(*)()>(GetTime)); - 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)) { throw std::runtime_error(strprintf("ActivateBestChain failed. (%s)", state.ToString())); } - m_node.addrman = std::make_unique<AddrMan>(/*asmap=*/std::vector<bool>(), + m_node.netgroupman = std::make_unique<NetGroupManager>(/*asmap=*/std::vector<bool>()); + m_node.addrman = std::make_unique<AddrMan>(*m_node.netgroupman, /*deterministic=*/false, m_node.args->GetIntArg("-checkaddrman", 0)); m_node.banman = std::make_unique<BanMan>(m_args.GetDataDirBase() / "banlist", nullptr, DEFAULT_MISBEHAVING_BANTIME); - m_node.connman = std::make_unique<CConnman>(0x1337, 0x1337, *m_node.addrman); // Deterministic randomness for tests. - m_node.peerman = PeerManager::make(chainparams, *m_node.connman, *m_node.addrman, + m_node.connman = std::make_unique<ConnmanTestMsg>(0x1337, 0x1337, *m_node.addrman, *m_node.netgroupman); // Deterministic randomness for tests. + m_node.peerman = PeerManager::make(*m_node.connman, *m_node.addrman, m_node.banman.get(), *m_node.chainman, *m_node.mempool, false); { @@ -238,8 +262,8 @@ TestingSetup::TestingSetup(const std::string& chainName, const std::vector<const } } -TestChain100Setup::TestChain100Setup(const std::vector<const char*>& extra_args) - : TestingSetup{CBaseChainParams::REGTEST, extra_args} +TestChain100Setup::TestChain100Setup(const std::string& chain_name, const std::vector<const char*>& extra_args) + : TestingSetup{chain_name, extra_args} { SetMockTime(1598887952); constexpr std::array<unsigned char, 32> vchKey = { @@ -273,9 +297,7 @@ CBlock TestChain100Setup::CreateBlock( const CScript& scriptPubKey, CChainState& chainstate) { - const CChainParams& chainparams = Params(); - CTxMemPool empty_pool; - CBlock block = BlockAssembler(chainstate, empty_pool, chainparams).CreateNewBlock(scriptPubKey)->block; + CBlock block = BlockAssembler{chainstate, nullptr}.CreateNewBlock(scriptPubKey)->block; Assert(block.vtx.size() == 1); for (const CMutableTransaction& tx : txns) { @@ -283,7 +305,7 @@ CBlock TestChain100Setup::CreateBlock( } RegenerateCommitments(block, *Assert(m_node.chainman)); - while (!CheckProofOfWork(block.GetHash(), block.nBits, chainparams.GetConsensus())) ++block.nNonce; + while (!CheckProofOfWork(block.GetHash(), block.nBits, m_node.chainman->GetConsensus())) ++block.nNonce; return block; } @@ -297,10 +319,9 @@ CBlock TestChain100Setup::CreateAndProcessBlock( chainstate = &Assert(m_node.chainman)->ActiveChainstate(); } - const CChainParams& chainparams = Params(); 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(chainparams, shared_pblock, true, nullptr); + Assert(m_node.chainman)->ProcessNewBlock(shared_pblock, true, nullptr); return block; } @@ -355,6 +376,52 @@ CMutableTransaction TestChain100Setup::CreateValidMempoolTransaction(CTransactio return mempool_txn; } +std::vector<CTransactionRef> TestChain100Setup::PopulateMempool(FastRandomContext& det_rand, size_t num_transactions, bool submit) +{ + std::vector<CTransactionRef> mempool_transactions; + std::deque<std::pair<COutPoint, CAmount>> unspent_prevouts; + std::transform(m_coinbase_txns.begin(), m_coinbase_txns.end(), std::back_inserter(unspent_prevouts), + [](const auto& tx){ return std::make_pair(COutPoint(tx->GetHash(), 0), tx->vout[0].nValue); }); + while (num_transactions > 0 && !unspent_prevouts.empty()) { + // The number of inputs and outputs are random, between 1 and 24. + CMutableTransaction mtx = CMutableTransaction(); + const size_t num_inputs = det_rand.randrange(24) + 1; + CAmount total_in{0}; + for (size_t n{0}; n < num_inputs; ++n) { + if (unspent_prevouts.empty()) break; + const auto& [prevout, amount] = unspent_prevouts.front(); + mtx.vin.push_back(CTxIn(prevout, CScript())); + total_in += amount; + unspent_prevouts.pop_front(); + } + const size_t num_outputs = det_rand.randrange(24) + 1; + // Approximately 1000sat "fee," equal output amounts. + const CAmount amount_per_output = (total_in - 1000) / num_outputs; + for (size_t n{0}; n < num_outputs; ++n) { + CScript spk = CScript() << CScriptNum(num_transactions + n); + mtx.vout.push_back(CTxOut(amount_per_output, spk)); + } + CTransactionRef ptx = MakeTransactionRef(mtx); + mempool_transactions.push_back(ptx); + if (amount_per_output > 2000) { + // If the value is high enough to fund another transaction + fees, keep track of it so + // it can be used to build a more complex transaction graph. Insert randomly into + // unspent_prevouts for extra randomness in the resulting structures. + for (size_t n{0}; n < num_outputs; ++n) { + unspent_prevouts.push_back(std::make_pair(COutPoint(ptx->GetHash(), n), amount_per_output)); + std::swap(unspent_prevouts.back(), unspent_prevouts[det_rand.randrange(unspent_prevouts.size())]); + } + } + if (submit) { + LOCK2(m_node.mempool->cs, cs_main); + LockPoints lp; + m_node.mempool->addUnchecked(CTxMemPoolEntry(ptx, 1000, 0, 1, false, 4, lp)); + } + --num_transactions; + } + return mempool_transactions; +} + CTxMemPoolEntry TestMemPoolEntryHelper::FromTx(const CMutableTransaction& tx) const { return FromTx(MakeTransactionRef(tx)); diff --git a/src/test/util/setup_common.h b/src/test/util/setup_common.h index a1b7525cf4..ed2c5db7e6 100644 --- a/src/test/util/setup_common.h +++ b/src/test/util/setup_common.h @@ -81,8 +81,7 @@ static constexpr CAmount CENT{1000000}; * This just configures logging, data dir and chain parameters. */ struct BasicTestingSetup { - ECCVerifyHandle globalVerifyHandle; - node::NodeContext m_node; + node::NodeContext m_node; // keep as first member to be destructed last explicit BasicTestingSetup(const std::string& chainName = CBaseChainParams::MAIN, const std::vector<const char*>& extra_args = {}); ~BasicTestingSetup(); @@ -91,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. @@ -122,7 +124,8 @@ class CScript; * Testing fixture that pre-creates a 100-block REGTEST-mode block chain */ struct TestChain100Setup : public TestingSetup { - TestChain100Setup(const std::vector<const char*>& extra_args = {}); + TestChain100Setup(const std::string& chain_name = CBaseChainParams::REGTEST, + const std::vector<const char*>& extra_args = {}); /** * Create a new block with just given transactions, coinbase paying to @@ -164,6 +167,19 @@ struct TestChain100Setup : public TestingSetup { CAmount output_amount = CAmount(1 * COIN), bool submit = true); + /** Create transactions spending from m_coinbase_txns. These transactions will only spend coins + * that exist in the current chain, but may be premature coinbase spends, have missing + * signatures, or violate some other consensus rules. They should only be used for testing + * mempool consistency. All transactions will have some random number of inputs and outputs + * (between 1 and 24). Transactions may or may not be dependent upon each other; if dependencies + * exit, every parent will always be somewhere in the list before the child so each transaction + * can be submitted in the same order they appear in the list. + * @param[in] submit When true, submit transactions to the mempool. + * When false, return them but don't submit them. + * @returns A vector of transactions that can be submitted to the mempool. + */ + std::vector<CTransactionRef> PopulateMempool(FastRandomContext& det_rand, size_t num_transactions, bool submit); + std::vector<CTransactionRef> m_coinbase_txns; // For convenience, coinbase transactions CKey coinbaseKey; // private/public key needed to spend coinbase transactions }; diff --git a/src/test/util/validation.cpp b/src/test/util/validation.cpp index 1aed492c3c..49535855f9 100644 --- a/src/test/util/validation.cpp +++ b/src/test/util/validation.cpp @@ -7,6 +7,7 @@ #include <util/check.h> #include <util/time.h> #include <validation.h> +#include <validationinterface.h> void TestChainState::ResetIbd() { @@ -20,3 +21,8 @@ void TestChainState::JumpOutOfIbd() m_cached_finished_ibd = true; Assert(!IsInitialBlockDownload()); } + +void ValidationInterfaceTest::BlockConnected(CValidationInterface& obj, const std::shared_ptr<const CBlock>& block, const CBlockIndex* pindex) +{ + obj.BlockConnected(block, pindex); +} diff --git a/src/test/util/validation.h b/src/test/util/validation.h index b13aa0be60..b0bc717b6c 100644 --- a/src/test/util/validation.h +++ b/src/test/util/validation.h @@ -7,6 +7,8 @@ #include <validation.h> +class CValidationInterface; + struct TestChainState : public CChainState { /** Reset the ibd cache to its initial state */ void ResetIbd(); @@ -14,4 +16,10 @@ struct TestChainState : public CChainState { void JumpOutOfIbd(); }; +class ValidationInterfaceTest +{ +public: + static void BlockConnected(CValidationInterface& obj, const std::shared_ptr<const CBlock>& block, const CBlockIndex* pindex); +}; + #endif // BITCOIN_TEST_UTIL_VALIDATION_H 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 9f78215de2..70e2f89e0b 100644 --- a/src/test/util_tests.cpp +++ b/src/test/util_tests.cpp @@ -17,6 +17,7 @@ #include <util/message.h> // For MessageSign(), MessageVerify(), MESSAGE_MAGIC #include <util/moneystr.h> #include <util/overflow.h> +#include <util/readwritefile.h> #include <util/spanparsing.h> #include <util/strencodings.h> #include <util/string.h> @@ -24,9 +25,10 @@ #include <util/vector.h> #include <array> -#include <optional> +#include <fstream> #include <limits> #include <map> +#include <optional> #include <stdint.h> #include <string.h> #include <thread> @@ -76,6 +78,31 @@ BOOST_AUTO_TEST_CASE(util_datadir) BOOST_CHECK_EQUAL(dd_norm, args.GetDataDirBase()); } +namespace { +class NoCopyOrMove +{ +public: + int i; + explicit NoCopyOrMove(int i) : i{i} { } + + NoCopyOrMove() = delete; + NoCopyOrMove(const NoCopyOrMove&) = delete; + NoCopyOrMove(NoCopyOrMove&&) = delete; + NoCopyOrMove& operator=(const NoCopyOrMove&) = delete; + NoCopyOrMove& operator=(NoCopyOrMove&&) = delete; + + operator bool() const { return i != 0; } + + int get_ip1() { return i + 1; } + bool test() + { + // Check that Assume can be used within a lambda and still call methods + [&]() { Assume(get_ip1()); }(); + return Assume(get_ip1() != 5); + } +}; +} // namespace + BOOST_AUTO_TEST_CASE(util_check) { // Check that Assert can forward @@ -87,6 +114,14 @@ BOOST_AUTO_TEST_CASE(util_check) // Check that Assume can be used as unary expression const bool result{Assume(two == 2)}; Assert(result); + + // Check that Assert doesn't require copy/move + NoCopyOrMove x{9}; + Assert(x).i += 3; + Assert(x).test(); + + // Check nested Asserts + BOOST_CHECK_EQUAL(Assert((Assert(x).test() ? 3 : 0)), 3); } BOOST_AUTO_TEST_CASE(util_criticalsection) @@ -118,7 +153,7 @@ static const unsigned char ParseHex_expected[65] = { 0xde, 0x5c, 0x38, 0x4d, 0xf7, 0xba, 0x0b, 0x8d, 0x57, 0x8a, 0x4c, 0x70, 0x2b, 0x6b, 0xf1, 0x1d, 0x5f }; -BOOST_AUTO_TEST_CASE(util_ParseHex) +BOOST_AUTO_TEST_CASE(parse_hex) { std::vector<unsigned char> result; std::vector<unsigned char> expected(ParseHex_expected, ParseHex_expected + sizeof(ParseHex_expected)); @@ -134,6 +169,14 @@ BOOST_AUTO_TEST_CASE(util_ParseHex) result = ParseHex(" 89 34 56 78"); BOOST_CHECK(result.size() == 4 && result[0] == 0x89 && result[1] == 0x34 && result[2] == 0x56 && result[3] == 0x78); + // Embedded null is treated as end + const std::string with_embedded_null{" 11 "s + " \0 " + " 22 "s}; + BOOST_CHECK_EQUAL(with_embedded_null.size(), 11); + result = ParseHex(with_embedded_null); + BOOST_CHECK(result.size() == 1 && result[0] == 0x11); + // Stop parsing at invalid value result = ParseHex("1234 invalid 1234"); BOOST_CHECK(result.size() == 2 && result[0] == 0x12 && result[1] == 0x34); @@ -163,6 +206,24 @@ BOOST_AUTO_TEST_CASE(util_HexStr) BOOST_CHECK_EQUAL(HexStr(in_s), out_exp); BOOST_CHECK_EQUAL(HexStr(in_b), out_exp); } + + { + auto input = std::string(); + for (size_t i=0; i<256; ++i) { + input.push_back(static_cast<char>(i)); + } + + auto hex = HexStr(input); + BOOST_TEST_REQUIRE(hex.size() == 512); + static constexpr auto hexmap = std::string_view("0123456789abcdef"); + for (size_t i = 0; i < 256; ++i) { + auto upper = hexmap.find(hex[i * 2]); + auto lower = hexmap.find(hex[i * 2 + 1]); + BOOST_TEST_REQUIRE(upper != std::string_view::npos); + BOOST_TEST_REQUIRE(lower != std::string_view::npos); + BOOST_TEST_REQUIRE(i == upper*16 + lower); + } + } } BOOST_AUTO_TEST_CASE(span_write_bytes) @@ -188,20 +249,36 @@ BOOST_AUTO_TEST_CASE(util_Join) BOOST_CHECK_EQUAL(Join<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) { BOOST_CHECK_EQUAL(TrimString(" foo bar "), "foo bar"); - BOOST_CHECK_EQUAL(TrimString("\t \n \n \f\n\r\t\v\tfoo \n \f\n\r\t\v\tbar\t \n \f\n\r\t\v\t\n "), "foo \n \f\n\r\t\v\tbar"); + BOOST_CHECK_EQUAL(TrimStringView("\t \n \n \f\n\r\t\v\tfoo \n \f\n\r\t\v\tbar\t \n \f\n\r\t\v\t\n "), "foo \n \f\n\r\t\v\tbar"); BOOST_CHECK_EQUAL(TrimString("\t \n foo \n\tbar\t \n "), "foo \n\tbar"); - BOOST_CHECK_EQUAL(TrimString("\t \n foo \n\tbar\t \n ", "fobar"), "\t \n foo \n\tbar\t \n "); + BOOST_CHECK_EQUAL(TrimStringView("\t \n foo \n\tbar\t \n ", "fobar"), "\t \n foo \n\tbar\t \n "); BOOST_CHECK_EQUAL(TrimString("foo bar"), "foo bar"); - BOOST_CHECK_EQUAL(TrimString("foo bar", "fobar"), " "); + BOOST_CHECK_EQUAL(TrimStringView("foo bar", "fobar"), " "); BOOST_CHECK_EQUAL(TrimString(std::string("\0 foo \0 ", 8)), std::string("\0 foo \0", 7)); - BOOST_CHECK_EQUAL(TrimString(std::string(" foo ", 5)), std::string("foo", 3)); + BOOST_CHECK_EQUAL(TrimStringView(std::string(" foo ", 5)), std::string("foo", 3)); BOOST_CHECK_EQUAL(TrimString(std::string("\t\t\0\0\n\n", 6)), std::string("\0\0", 2)); - BOOST_CHECK_EQUAL(TrimString(std::string("\x05\x04\x03\x02\x01\x00", 6)), std::string("\x05\x04\x03\x02\x01\x00", 6)); + BOOST_CHECK_EQUAL(TrimStringView(std::string("\x05\x04\x03\x02\x01\x00", 6)), std::string("\x05\x04\x03\x02\x01\x00", 6)); BOOST_CHECK_EQUAL(TrimString(std::string("\x05\x04\x03\x02\x01\x00", 6), std::string("\x05\x04\x03\x02\x01", 5)), std::string("\0", 1)); - BOOST_CHECK_EQUAL(TrimString(std::string("\x05\x04\x03\x02\x01\x00", 6), std::string("\x05\x04\x03\x02\x01\x00", 6)), ""); + 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) @@ -212,9 +289,6 @@ BOOST_AUTO_TEST_CASE(util_FormatParseISO8601DateTime) 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); - - auto time = GetTimeSeconds(); - BOOST_CHECK_EQUAL(ParseISO8601DateTime(FormatISO8601DateTime(time)), time); } BOOST_AUTO_TEST_CASE(util_FormatISO8601Date) @@ -1435,19 +1509,27 @@ BOOST_AUTO_TEST_CASE(util_time_GetTime) { SetMockTime(111); // Check that mock time does not change after a sleep - for (const auto& num_sleep : {0, 1}) { - UninterruptibleSleep(std::chrono::milliseconds{num_sleep}); + for (const auto& num_sleep : {0ms, 1ms}) { + UninterruptibleSleep(num_sleep); BOOST_CHECK_EQUAL(111, GetTime()); // Deprecated time getter + BOOST_CHECK_EQUAL(111, Now<NodeSeconds>().time_since_epoch().count()); + BOOST_CHECK_EQUAL(111, TicksSinceEpoch<std::chrono::seconds>(NodeClock::now())); + BOOST_CHECK_EQUAL(111, TicksSinceEpoch<SecondsDouble>(Now<NodeSeconds>())); BOOST_CHECK_EQUAL(111, GetTime<std::chrono::seconds>().count()); BOOST_CHECK_EQUAL(111000, GetTime<std::chrono::milliseconds>().count()); + BOOST_CHECK_EQUAL(111000, TicksSinceEpoch<std::chrono::milliseconds>(NodeClock::now())); BOOST_CHECK_EQUAL(111000000, GetTime<std::chrono::microseconds>().count()); } SetMockTime(0); - // Check that system time changes after a sleep + // Check that steady time and system time changes after a sleep + const auto steady_ms_0 = Now<SteadyMilliseconds>(); + const auto steady_0 = std::chrono::steady_clock::now(); const auto ms_0 = GetTime<std::chrono::milliseconds>(); const auto us_0 = GetTime<std::chrono::microseconds>(); - UninterruptibleSleep(std::chrono::milliseconds{1}); + UninterruptibleSleep(1ms); + BOOST_CHECK(steady_ms_0 < Now<SteadyMilliseconds>()); + BOOST_CHECK(steady_0 + 1ms <= std::chrono::steady_clock::now()); BOOST_CHECK(ms_0 < GetTime<std::chrono::milliseconds>()); BOOST_CHECK(us_0 < GetTime<std::chrono::microseconds>()); } @@ -2014,7 +2096,7 @@ BOOST_AUTO_TEST_CASE(test_ParseFixedPoint) BOOST_CHECK(!ParseFixedPoint("31.999999999999999999999", 3, &amount)); } -static void TestOtherThread(fs::path dirname, std::string lockname, bool *result) +static void TestOtherThread(fs::path dirname, fs::path lockname, bool *result) { *result = LockDirectory(dirname, lockname); } @@ -2024,7 +2106,7 @@ static constexpr char LockCommand = 'L'; static constexpr char UnlockCommand = 'U'; static constexpr char ExitCommand = 'X'; -[[noreturn]] static void TestOtherProcess(fs::path dirname, std::string lockname, int fd) +[[noreturn]] static void TestOtherProcess(fs::path dirname, fs::path lockname, int fd) { char ch; while (true) { @@ -2055,7 +2137,7 @@ static constexpr char ExitCommand = 'X'; BOOST_AUTO_TEST_CASE(test_LockDirectory) { fs::path dirname = m_args.GetDataDirBase() / "lock_dir"; - const std::string lockname = ".lock"; + const fs::path lockname = ".lock"; #ifndef WIN32 // Revert SIGCHLD to default, otherwise boost.test will catch and fail on // it: there is BOOST_TEST_IGNORE_SIGCHLD but that only works when defined @@ -2314,6 +2396,68 @@ BOOST_AUTO_TEST_CASE(test_spanparsing) BOOST_CHECK_EQUAL(SpanToStr(results[3]), ""); } +BOOST_AUTO_TEST_CASE(test_SplitString) +{ + // Empty string. + { + std::vector<std::string> result = SplitString("", '-'); + BOOST_CHECK_EQUAL(result.size(), 1); + BOOST_CHECK_EQUAL(result[0], ""); + } + + // Empty items. + { + std::vector<std::string> result = SplitString("-", '-'); + BOOST_CHECK_EQUAL(result.size(), 2); + BOOST_CHECK_EQUAL(result[0], ""); + BOOST_CHECK_EQUAL(result[1], ""); + } + + // More empty items. + { + std::vector<std::string> result = SplitString("--", '-'); + BOOST_CHECK_EQUAL(result.size(), 3); + BOOST_CHECK_EQUAL(result[0], ""); + BOOST_CHECK_EQUAL(result[1], ""); + BOOST_CHECK_EQUAL(result[2], ""); + } + + // Separator is not present. + { + std::vector<std::string> result = SplitString("abc", '-'); + BOOST_CHECK_EQUAL(result.size(), 1); + BOOST_CHECK_EQUAL(result[0], "abc"); + } + + // Basic behavior. + { + std::vector<std::string> result = SplitString("a-b", '-'); + BOOST_CHECK_EQUAL(result.size(), 2); + BOOST_CHECK_EQUAL(result[0], "a"); + BOOST_CHECK_EQUAL(result[1], "b"); + } + + // Case-sensitivity of the separator. + { + std::vector<std::string> result = SplitString("AAA", 'a'); + BOOST_CHECK_EQUAL(result.size(), 1); + BOOST_CHECK_EQUAL(result[0], "AAA"); + } + + // multiple split characters + { + using V = std::vector<std::string>; + BOOST_TEST(SplitString("a,b.c:d;e", ",;") == V({"a", "b.c:d", "e"})); + BOOST_TEST(SplitString("a,b.c:d;e", ",;:.") == V({"a", "b", "c", "d", "e"})); + BOOST_TEST(SplitString("a,b.c:d;e", "") == V({"a,b.c:d;e"})); + BOOST_TEST(SplitString("aaa", "bcdefg") == V({"aaa"})); + BOOST_TEST(SplitString("x\0a,b"s, "\0"s) == V({"x", "a,b"})); + BOOST_TEST(SplitString("x\0a,b"s, '\0') == V({"x", "a,b"})); + BOOST_TEST(SplitString("x\0a,b"s, "\0,"s) == V({"x", "a", "b"})); + BOOST_TEST(SplitString("abcdefg", "bcd") == V({"a", "", "", "efg"})); + } +} + BOOST_AUTO_TEST_CASE(test_LogEscapeMessage) { // ASCII and UTF-8 must pass through unaltered. @@ -2334,9 +2478,9 @@ struct Tracker //! Points to the original object (possibly itself) we moved/copied from const Tracker* origin; //! How many copies where involved between the original object and this one (moves are not counted) - int copies; + int copies{0}; - Tracker() noexcept : origin(this), copies(0) {} + Tracker() noexcept : origin(this) {} Tracker(const Tracker& t) noexcept : origin(t.origin), copies(t.copies + 1) {} Tracker(Tracker&& t) noexcept : origin(t.origin), copies(t.copies) {} Tracker& operator=(const Tracker& t) noexcept @@ -2534,13 +2678,13 @@ BOOST_AUTO_TEST_CASE(message_hash) BOOST_AUTO_TEST_CASE(remove_prefix) { BOOST_CHECK_EQUAL(RemovePrefix("./util/system.h", "./"), "util/system.h"); - BOOST_CHECK_EQUAL(RemovePrefix("foo", "foo"), ""); + BOOST_CHECK_EQUAL(RemovePrefixView("foo", "foo"), ""); BOOST_CHECK_EQUAL(RemovePrefix("foo", "fo"), "o"); - BOOST_CHECK_EQUAL(RemovePrefix("foo", "f"), "oo"); + BOOST_CHECK_EQUAL(RemovePrefixView("foo", "f"), "oo"); BOOST_CHECK_EQUAL(RemovePrefix("foo", ""), "foo"); - BOOST_CHECK_EQUAL(RemovePrefix("fo", "foo"), "fo"); + BOOST_CHECK_EQUAL(RemovePrefixView("fo", "foo"), "fo"); BOOST_CHECK_EQUAL(RemovePrefix("f", "foo"), "f"); - BOOST_CHECK_EQUAL(RemovePrefix("", "foo"), ""); + BOOST_CHECK_EQUAL(RemovePrefixView("", "foo"), ""); BOOST_CHECK_EQUAL(RemovePrefix("", ""), ""); } @@ -2592,4 +2736,49 @@ BOOST_AUTO_TEST_CASE(util_ParseByteUnits) BOOST_CHECK(!ParseByteUnits("1x", noop)); } +BOOST_AUTO_TEST_CASE(util_ReadBinaryFile) +{ + fs::path tmpfolder = m_args.GetDataDirBase(); + fs::path tmpfile = tmpfolder / "read_binary.dat"; + std::string expected_text; + for (int i = 0; i < 30; i++) { + expected_text += "0123456789"; + } + { + std::ofstream file{tmpfile}; + file << expected_text; + } + { + // read all contents in file + auto [valid, text] = ReadBinaryFile(tmpfile); + BOOST_CHECK(valid); + BOOST_CHECK_EQUAL(text, expected_text); + } + { + // read half contents in file + auto [valid, text] = ReadBinaryFile(tmpfile, expected_text.size() / 2); + BOOST_CHECK(valid); + BOOST_CHECK_EQUAL(text, expected_text.substr(0, expected_text.size() / 2)); + } + { + // read from non-existent file + fs::path invalid_file = tmpfolder / "invalid_binary.dat"; + auto [valid, text] = ReadBinaryFile(invalid_file); + BOOST_CHECK(!valid); + BOOST_CHECK(text.empty()); + } +} + +BOOST_AUTO_TEST_CASE(util_WriteBinaryFile) +{ + fs::path tmpfolder = m_args.GetDataDirBase(); + fs::path tmpfile = tmpfolder / "write_binary.dat"; + std::string expected_text = "bitcoin"; + auto valid = WriteBinaryFile(tmpfile, expected_text); + std::string actual_text; + std::ifstream file{tmpfile}; + file >> actual_text; + BOOST_CHECK(valid); + BOOST_CHECK_EQUAL(actual_text, expected_text); +} BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/validation_block_tests.cpp b/src/test/validation_block_tests.cpp index c5b1dabcb7..1c5ca18759 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, Params()).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; @@ -89,7 +89,7 @@ std::shared_ptr<CBlock> MinerTestingSetup::Block(const uint256& prev_hash) std::shared_ptr<CBlock> MinerTestingSetup::FinalizeBlock(std::shared_ptr<CBlock> pblock) { const CBlockIndex* prev_block{WITH_LOCK(::cs_main, return m_node.chainman->m_blockman.LookupBlockIndex(pblock->hashPrevBlock))}; - GenerateCoinbaseCommitment(*pblock, prev_block, Params().GetConsensus()); + m_node.chainman->GenerateCoinbaseCommitment(*pblock, prev_block); pblock->hashMerkleRoot = BlockMerkleRoot(*pblock); @@ -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, Params())); + BOOST_CHECK(Assert(m_node.chainman)->ProcessNewBlockHeaders({pblock->GetBlockHeader()}, 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(Params(), std::make_shared<CBlock>(Params().GenesisBlock()), true, &ignored)); + BOOST_CHECK(Assert(m_node.chainman)->ProcessNewBlock(std::make_shared<CBlock>(Params().GenesisBlock()), 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(Params(), block, true, &ignored); + Assert(m_node.chainman)->ProcessNewBlock(block, 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(Params(), block, true, &ignored); + bool processed = Assert(m_node.chainman)->ProcessNewBlock(block, 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(Params(), block, /*force_processing=*/true, /*new_block=*/&ignored); + return Assert(m_node.chainman)->ProcessNewBlock(block, /*force_processing=*/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, Params()).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 1beef5cf04..ee60f9aa4d 100644 --- a/src/test/validation_chainstate_tests.cpp +++ b/src/test/validation_chainstate_tests.cpp @@ -3,28 +3,27 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. // #include <chainparams.h> -#include <random.h> -#include <uint256.h> #include <consensus/validation.h> -#include <sync.h> +#include <random.h> #include <rpc/blockchain.h> +#include <sync.h> #include <test/util/chainstate.h> #include <test/util/setup_common.h> +#include <uint256.h> #include <validation.h> #include <vector> #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. //! BOOST_AUTO_TEST_CASE(validation_chainstate_resize_caches) { - ChainstateManager manager; - 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 { @@ -72,9 +71,6 @@ BOOST_AUTO_TEST_CASE(validation_chainstate_resize_caches) // The view cache should be empty since we had to destruct to downsize. BOOST_CHECK(!c1.CoinsTip().HaveCoinInCache(outpoint)); } - - // Avoid triggering the address sanitizer. - WITH_LOCK(::cs_main, manager.Unload()); } //! Test UpdateTip behavior for both active and background chainstates. @@ -96,7 +92,7 @@ BOOST_FIXTURE_TEST_CASE(chainstate_update_tip, TestChain100Setup) BOOST_REQUIRE(CreateAndActivateUTXOSnapshot(m_node, m_path_root)); // Ensure our active chain is the snapshot chainstate. - BOOST_CHECK(chainman.IsSnapshotActive()); + BOOST_CHECK(WITH_LOCK(::cs_main, return chainman.IsSnapshotActive())); curr_tip = ::g_best_block; diff --git a/src/test/validation_chainstatemanager_tests.cpp b/src/test/validation_chainstatemanager_tests.cpp index 26392e690d..7b7bd05b5c 100644 --- a/src/test/validation_chainstatemanager_tests.cpp +++ b/src/test/validation_chainstatemanager_tests.cpp @@ -45,16 +45,16 @@ BOOST_AUTO_TEST_CASE(chainstatemanager) WITH_LOCK(::cs_main, c1.InitCoinsCache(1 << 23)); BOOST_CHECK(!manager.IsSnapshotActive()); - BOOST_CHECK(!manager.IsSnapshotValidated()); + BOOST_CHECK(WITH_LOCK(::cs_main, return !manager.IsSnapshotValidated())); 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); @@ -78,18 +78,18 @@ BOOST_AUTO_TEST_CASE(chainstatemanager) BOOST_CHECK(c2.ActivateBestChain(_, nullptr)); BOOST_CHECK(manager.IsSnapshotActive()); - BOOST_CHECK(!manager.IsSnapshotValidated()); + BOOST_CHECK(WITH_LOCK(::cs_main, return !manager.IsSnapshotValidated())); BOOST_CHECK_EQUAL(&c2, &manager.ActiveChainstate()); BOOST_CHECK(&c1 != &manager.ActiveChainstate()); 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); @@ -99,8 +99,6 @@ BOOST_AUTO_TEST_CASE(chainstatemanager) // Let scheduler events finish running to avoid accessing memory that is going to be unloaded SyncWithValidationInterfaceQueue(); - - WITH_LOCK(::cs_main, manager.Unload()); } //! Test rebalancing the caches associated with each chainstate. @@ -195,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; @@ -206,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; })); @@ -238,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); @@ -337,7 +335,7 @@ 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()) { diff --git a/src/test/validation_flush_tests.cpp b/src/test/validation_flush_tests.cpp index a34895d4ae..74b2af6858 100644 --- a/src/test/validation_flush_tests.cpp +++ b/src/test/validation_flush_tests.cpp @@ -4,14 +4,11 @@ // #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. @@ -20,11 +17,7 @@ BOOST_FIXTURE_TEST_SUITE(validation_flush_tests, ChainTestingSetup) //! 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)); + CChainState& chainstate{m_node.chainman->ActiveChainstate()}; constexpr bool is_64_bit = sizeof(void*) == 8; diff --git a/src/test/versionbits_tests.cpp b/src/test/versionbits_tests.cpp index bf87812a8a..e6203af4b4 100644 --- a/src/test/versionbits_tests.cpp +++ b/src/test/versionbits_tests.cpp @@ -5,9 +5,7 @@ #include <chain.h> #include <chainparams.h> #include <consensus/params.h> -#include <deploymentstatus.h> #include <test/util/setup_common.h> -#include <validation.h> #include <versionbits.h> #include <boost/test/unit_test.hpp> @@ -184,7 +182,7 @@ public: CBlockIndex* Tip() { return vpblock.empty() ? nullptr : vpblock.back(); } }; -BOOST_FIXTURE_TEST_SUITE(versionbits_tests, TestingSetup) +BOOST_FIXTURE_TEST_SUITE(versionbits_tests, BasicTestingSetup) BOOST_AUTO_TEST_CASE(versionbits_test) { @@ -257,10 +255,10 @@ BOOST_AUTO_TEST_CASE(versionbits_test) } /** Check that ComputeBlockVersion will set the appropriate bit correctly */ -static void check_computeblockversion(const Consensus::Params& params, Consensus::DeploymentPos dep) +static void check_computeblockversion(VersionBitsCache& versionbitscache, const Consensus::Params& params, Consensus::DeploymentPos dep) { - // This implicitly uses g_versionbitscache, so clear it every time - g_versionbitscache.Clear(); + // Clear the cache every time + versionbitscache.Clear(); int64_t bit = params.vDeployments[dep].bit; int64_t nStartTime = params.vDeployments[dep].nStartTime; @@ -268,13 +266,14 @@ static void check_computeblockversion(const Consensus::Params& params, Consensus int min_activation_height = params.vDeployments[dep].min_activation_height; // should not be any signalling for first block - BOOST_CHECK_EQUAL(g_versionbitscache.ComputeBlockVersion(nullptr, params), VERSIONBITS_TOP_BITS); + BOOST_CHECK_EQUAL(versionbitscache.ComputeBlockVersion(nullptr, params), VERSIONBITS_TOP_BITS); // always/never active deployments shouldn't need to be tested further if (nStartTime == Consensus::BIP9Deployment::ALWAYS_ACTIVE || nStartTime == Consensus::BIP9Deployment::NEVER_ACTIVE) { BOOST_CHECK_EQUAL(min_activation_height, 0); + BOOST_CHECK_EQUAL(nTimeout, Consensus::BIP9Deployment::NO_TIMEOUT); return; } @@ -288,7 +287,7 @@ static void check_computeblockversion(const Consensus::Params& params, Consensus // Check min_activation_height is on a retarget boundary BOOST_REQUIRE_EQUAL(min_activation_height % params.nMinerConfirmationWindow, 0U); - const uint32_t bitmask{g_versionbitscache.Mask(params, dep)}; + const uint32_t bitmask{versionbitscache.Mask(params, dep)}; BOOST_CHECK_EQUAL(bitmask, uint32_t{1} << bit); // In the first chain, test that the bit is set by CBV until it has failed. @@ -307,9 +306,9 @@ static void check_computeblockversion(const Consensus::Params& params, Consensus // earlier time, so will transition from DEFINED to STARTED at the // end of the first period by mining blocks at nTime == 0 lastBlock = firstChain.Mine(params.nMinerConfirmationWindow - 1, nTime, VERSIONBITS_LAST_OLD_BLOCK_VERSION).Tip(); - BOOST_CHECK_EQUAL(g_versionbitscache.ComputeBlockVersion(lastBlock, params) & (1 << bit), 0); + BOOST_CHECK_EQUAL(versionbitscache.ComputeBlockVersion(lastBlock, params) & (1 << bit), 0); lastBlock = firstChain.Mine(params.nMinerConfirmationWindow, nTime, VERSIONBITS_LAST_OLD_BLOCK_VERSION).Tip(); - BOOST_CHECK((g_versionbitscache.ComputeBlockVersion(lastBlock, params) & (1 << bit)) != 0); + BOOST_CHECK((versionbitscache.ComputeBlockVersion(lastBlock, params) & (1 << bit)) != 0); // then we'll keep mining at nStartTime... } else { // use a time 1s earlier than start time to check we stay DEFINED @@ -317,28 +316,28 @@ static void check_computeblockversion(const Consensus::Params& params, Consensus // Start generating blocks before nStartTime lastBlock = firstChain.Mine(params.nMinerConfirmationWindow, nTime, VERSIONBITS_LAST_OLD_BLOCK_VERSION).Tip(); - BOOST_CHECK_EQUAL(g_versionbitscache.ComputeBlockVersion(lastBlock, params) & (1 << bit), 0); + BOOST_CHECK_EQUAL(versionbitscache.ComputeBlockVersion(lastBlock, params) & (1 << bit), 0); // Mine more blocks (4 less than the adjustment period) at the old time, and check that CBV isn't setting the bit yet. for (uint32_t i = 1; i < params.nMinerConfirmationWindow - 4; i++) { lastBlock = firstChain.Mine(params.nMinerConfirmationWindow + i, nTime, VERSIONBITS_LAST_OLD_BLOCK_VERSION).Tip(); - BOOST_CHECK_EQUAL(g_versionbitscache.ComputeBlockVersion(lastBlock, params) & (1 << bit), 0); + BOOST_CHECK_EQUAL(versionbitscache.ComputeBlockVersion(lastBlock, params) & (1 << bit), 0); } // Now mine 5 more blocks at the start time -- MTP should not have passed yet, so // CBV should still not yet set the bit. nTime = nStartTime; for (uint32_t i = params.nMinerConfirmationWindow - 4; i <= params.nMinerConfirmationWindow; i++) { lastBlock = firstChain.Mine(params.nMinerConfirmationWindow + i, nTime, VERSIONBITS_LAST_OLD_BLOCK_VERSION).Tip(); - BOOST_CHECK_EQUAL(g_versionbitscache.ComputeBlockVersion(lastBlock, params) & (1 << bit), 0); + BOOST_CHECK_EQUAL(versionbitscache.ComputeBlockVersion(lastBlock, params) & (1 << bit), 0); } // Next we will advance to the next period and transition to STARTED, } lastBlock = firstChain.Mine(params.nMinerConfirmationWindow * 3, nTime, VERSIONBITS_LAST_OLD_BLOCK_VERSION).Tip(); // so ComputeBlockVersion should now set the bit, - BOOST_CHECK((g_versionbitscache.ComputeBlockVersion(lastBlock, params) & (1 << bit)) != 0); + BOOST_CHECK((versionbitscache.ComputeBlockVersion(lastBlock, params) & (1 << bit)) != 0); // and should also be using the VERSIONBITS_TOP_BITS. - BOOST_CHECK_EQUAL(g_versionbitscache.ComputeBlockVersion(lastBlock, params) & VERSIONBITS_TOP_MASK, VERSIONBITS_TOP_BITS); + BOOST_CHECK_EQUAL(versionbitscache.ComputeBlockVersion(lastBlock, params) & VERSIONBITS_TOP_MASK, VERSIONBITS_TOP_BITS); // Check that ComputeBlockVersion will set the bit until nTimeout nTime += 600; @@ -347,8 +346,8 @@ static void check_computeblockversion(const Consensus::Params& params, Consensus // These blocks are all before nTimeout is reached. while (nTime < nTimeout && blocksToMine > 0) { lastBlock = firstChain.Mine(nHeight+1, nTime, VERSIONBITS_LAST_OLD_BLOCK_VERSION).Tip(); - BOOST_CHECK((g_versionbitscache.ComputeBlockVersion(lastBlock, params) & (1 << bit)) != 0); - BOOST_CHECK_EQUAL(g_versionbitscache.ComputeBlockVersion(lastBlock, params) & VERSIONBITS_TOP_MASK, VERSIONBITS_TOP_BITS); + BOOST_CHECK((versionbitscache.ComputeBlockVersion(lastBlock, params) & (1 << bit)) != 0); + BOOST_CHECK_EQUAL(versionbitscache.ComputeBlockVersion(lastBlock, params) & VERSIONBITS_TOP_MASK, VERSIONBITS_TOP_BITS); blocksToMine--; nTime += 600; nHeight += 1; @@ -362,7 +361,7 @@ static void check_computeblockversion(const Consensus::Params& params, Consensus // finish the last period before we start timing out while (nHeight % params.nMinerConfirmationWindow != 0) { lastBlock = firstChain.Mine(nHeight+1, nTime - 1, VERSIONBITS_LAST_OLD_BLOCK_VERSION).Tip(); - BOOST_CHECK((g_versionbitscache.ComputeBlockVersion(lastBlock, params) & (1 << bit)) != 0); + BOOST_CHECK((versionbitscache.ComputeBlockVersion(lastBlock, params) & (1 << bit)) != 0); nHeight += 1; } @@ -370,12 +369,12 @@ static void check_computeblockversion(const Consensus::Params& params, Consensus // the bit until the period transition. for (uint32_t i = 0; i < params.nMinerConfirmationWindow - 1; i++) { lastBlock = firstChain.Mine(nHeight+1, nTime, VERSIONBITS_LAST_OLD_BLOCK_VERSION).Tip(); - BOOST_CHECK((g_versionbitscache.ComputeBlockVersion(lastBlock, params) & (1 << bit)) != 0); + BOOST_CHECK((versionbitscache.ComputeBlockVersion(lastBlock, params) & (1 << bit)) != 0); nHeight += 1; } // The next block should trigger no longer setting the bit. lastBlock = firstChain.Mine(nHeight+1, nTime, VERSIONBITS_LAST_OLD_BLOCK_VERSION).Tip(); - BOOST_CHECK_EQUAL(g_versionbitscache.ComputeBlockVersion(lastBlock, params) & (1 << bit), 0); + BOOST_CHECK_EQUAL(versionbitscache.ComputeBlockVersion(lastBlock, params) & (1 << bit), 0); } // On a new chain: @@ -386,34 +385,36 @@ static void check_computeblockversion(const Consensus::Params& params, Consensus // Mine one period worth of blocks, and check that the bit will be on for the // next period. lastBlock = secondChain.Mine(params.nMinerConfirmationWindow, nTime, VERSIONBITS_LAST_OLD_BLOCK_VERSION).Tip(); - BOOST_CHECK((g_versionbitscache.ComputeBlockVersion(lastBlock, params) & (1 << bit)) != 0); + BOOST_CHECK((versionbitscache.ComputeBlockVersion(lastBlock, params) & (1 << bit)) != 0); // Mine another period worth of blocks, signaling the new bit. lastBlock = secondChain.Mine(params.nMinerConfirmationWindow * 2, nTime, VERSIONBITS_TOP_BITS | (1<<bit)).Tip(); // After one period of setting the bit on each block, it should have locked in. // We keep setting the bit for one more period though, until activation. - BOOST_CHECK((g_versionbitscache.ComputeBlockVersion(lastBlock, params) & (1 << bit)) != 0); + BOOST_CHECK((versionbitscache.ComputeBlockVersion(lastBlock, params) & (1 << bit)) != 0); // Now check that we keep mining the block until the end of this period, and // then stop at the beginning of the next period. lastBlock = secondChain.Mine((params.nMinerConfirmationWindow * 3) - 1, nTime, VERSIONBITS_LAST_OLD_BLOCK_VERSION).Tip(); - BOOST_CHECK((g_versionbitscache.ComputeBlockVersion(lastBlock, params) & (1 << bit)) != 0); + BOOST_CHECK((versionbitscache.ComputeBlockVersion(lastBlock, params) & (1 << bit)) != 0); lastBlock = secondChain.Mine(params.nMinerConfirmationWindow * 3, nTime, VERSIONBITS_LAST_OLD_BLOCK_VERSION).Tip(); if (lastBlock->nHeight + 1 < min_activation_height) { // check signalling continues while min_activation_height is not reached lastBlock = secondChain.Mine(min_activation_height - 1, nTime, VERSIONBITS_LAST_OLD_BLOCK_VERSION).Tip(); - BOOST_CHECK((g_versionbitscache.ComputeBlockVersion(lastBlock, params) & (1 << bit)) != 0); + BOOST_CHECK((versionbitscache.ComputeBlockVersion(lastBlock, params) & (1 << bit)) != 0); // then reach min_activation_height, which was already REQUIRE'd to start a new period lastBlock = secondChain.Mine(min_activation_height, nTime, VERSIONBITS_LAST_OLD_BLOCK_VERSION).Tip(); } // Check that we don't signal after activation - BOOST_CHECK_EQUAL(g_versionbitscache.ComputeBlockVersion(lastBlock, params) & (1 << bit), 0); + BOOST_CHECK_EQUAL(versionbitscache.ComputeBlockVersion(lastBlock, params) & (1 << bit), 0); } BOOST_AUTO_TEST_CASE(versionbits_computeblockversion) { + VersionBitsCache vbcache; + // check that any deployment on any chain can conceivably reach both // ACTIVE and FAILED states in roughly the way we expect for (const auto& chain_name : {CBaseChainParams::MAIN, CBaseChainParams::TESTNET, CBaseChainParams::SIGNET, CBaseChainParams::REGTEST}) { @@ -426,10 +427,10 @@ BOOST_AUTO_TEST_CASE(versionbits_computeblockversion) // not take precedence over STARTED/LOCKED_IN. So all softforks on // the same bit might overlap, even when non-overlapping start-end // times are picked. - const uint32_t dep_mask{g_versionbitscache.Mask(chainParams->GetConsensus(), dep)}; + const uint32_t dep_mask{vbcache.Mask(chainParams->GetConsensus(), dep)}; BOOST_CHECK(!(chain_all_vbits & dep_mask)); chain_all_vbits |= dep_mask; - check_computeblockversion(chainParams->GetConsensus(), dep); + check_computeblockversion(vbcache, chainParams->GetConsensus(), dep); } } @@ -439,7 +440,7 @@ BOOST_AUTO_TEST_CASE(versionbits_computeblockversion) ArgsManager args; args.ForceSetArg("-vbparams", "testdummy:1199145601:1230767999"); // January 1, 2008 - December 31, 2008 const auto chainParams = CreateChainParams(args, CBaseChainParams::REGTEST); - check_computeblockversion(chainParams->GetConsensus(), Consensus::DEPLOYMENT_TESTDUMMY); + check_computeblockversion(vbcache, chainParams->GetConsensus(), Consensus::DEPLOYMENT_TESTDUMMY); } { @@ -449,7 +450,7 @@ BOOST_AUTO_TEST_CASE(versionbits_computeblockversion) ArgsManager args; args.ForceSetArg("-vbparams", "testdummy:1199145601:1230767999:403200"); // January 1, 2008 - December 31, 2008, min act height 403200 const auto chainParams = CreateChainParams(args, CBaseChainParams::REGTEST); - check_computeblockversion(chainParams->GetConsensus(), Consensus::DEPLOYMENT_TESTDUMMY); + check_computeblockversion(vbcache, chainParams->GetConsensus(), Consensus::DEPLOYMENT_TESTDUMMY); } } 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 cb9a5fbf8b..363aab39ce 100644 --- a/src/threadinterrupt.h +++ b/src/threadinterrupt.h @@ -19,13 +19,12 @@ class CThreadInterrupt { public: + using Clock = std::chrono::steady_clock; CThreadInterrupt(); explicit operator bool() const; - void operator()(); + void operator()() EXCLUSIVE_LOCKS_REQUIRED(!mut); void reset(); - bool sleep_for(std::chrono::milliseconds rel_time); - bool sleep_for(std::chrono::seconds rel_time); - bool sleep_for(std::chrono::minutes rel_time); + 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 06659871e5..ceee08e68c 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; /** @@ -99,7 +99,7 @@ void AddTimeData(const CNetAddr& ip, int64_t nOffsetSample) } } - if (LogAcceptCategory(BCLog::NET)) { + if (LogAcceptCategory(BCLog::NET, BCLog::Level::Debug)) { std::string log_message{"time data samples: "}; for (const int64_t n : vSorted) { log_message += strprintf("%+d ", n); diff --git a/src/timedata.h b/src/timedata.h index 2f039d5465..cfe5111644 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; diff --git a/src/torcontrol.cpp b/src/torcontrol.cpp index 7ae384ceb3..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> @@ -24,10 +24,6 @@ #include <set> #include <vector> -#include <boost/algorithm/string/classification.hpp> -#include <boost/algorithm/string/replace.hpp> -#include <boost/algorithm/string/split.hpp> - #include <event2/buffer.h> #include <event2/bufferevent.h> #include <event2/event.h> @@ -53,6 +49,7 @@ static const float RECONNECT_TIMEOUT_EXP = 1.5; * this is belt-and-suspenders sanity limit to prevent memory exhaustion. */ static const int MAX_LINE_LENGTH = 100000; +static const uint16_t DEFAULT_TOR_SOCKS_PORT = 9050; /****** Low-level TorControlConnection ********/ @@ -97,7 +94,7 @@ void TorControlConnection::readcb(struct bufferevent *bev, void *ctx) self->reply_handlers.front()(*self, self->message); self->reply_handlers.pop_front(); } else { - LogPrint(BCLog::TOR, "tor: Received unexpected sync reply %i\n", self->message.code); + LogPrint(BCLog::TOR, "Received unexpected sync reply %i\n", self->message.code); } } self->message.Clear(); @@ -116,13 +113,13 @@ void TorControlConnection::eventcb(struct bufferevent *bev, short what, void *ct { TorControlConnection *self = static_cast<TorControlConnection*>(ctx); if (what & BEV_EVENT_CONNECTED) { - LogPrint(BCLog::TOR, "tor: Successfully connected!\n"); + LogPrint(BCLog::TOR, "Successfully connected!\n"); self->connected(*self); } else if (what & (BEV_EVENT_EOF|BEV_EVENT_ERROR)) { if (what & BEV_EVENT_ERROR) { - LogPrint(BCLog::TOR, "tor: Error connecting to Tor control socket\n"); + LogPrint(BCLog::TOR, "Error connecting to Tor control socket\n"); } else { - LogPrint(BCLog::TOR, "tor: End of stream\n"); + LogPrint(BCLog::TOR, "End of stream\n"); } self->Disconnect(); self->disconnected(*self); @@ -307,8 +304,7 @@ std::map<std::string,std::string> ParseTorReplyMapping(const std::string &s) TorController::TorController(struct event_base* _base, const std::string& tor_control_center, const CService& target): base(_base), - m_tor_control_center(tor_control_center), conn(base), reconnect(true), reconnect_ev(0), - reconnect_timeout(RECONNECT_TIMEOUT_START), + m_tor_control_center(tor_control_center), conn(base), reconnect(true), reconnect_timeout(RECONNECT_TIMEOUT_START), m_target(target) { reconnect_ev = event_new(base, -1, 0, reconnect_cb, this); @@ -322,7 +318,7 @@ TorController::TorController(struct event_base* _base, const std::string& tor_co // Read service private key if cached std::pair<bool,std::string> pkf = ReadBinaryFile(GetPrivateKeyFile()); if (pkf.first) { - LogPrint(BCLog::TOR, "tor: Reading cached private key from %s\n", fs::PathToString(GetPrivateKeyFile())); + LogPrint(BCLog::TOR, "Reading cached private key from %s\n", fs::PathToString(GetPrivateKeyFile())); private_key = pkf.second; } } @@ -338,10 +334,77 @@ TorController::~TorController() } } +void TorController::get_socks_cb(TorControlConnection& _conn, const TorControlReply& reply) +{ + // NOTE: We can only get here if -onion is unset + std::string socks_location; + if (reply.code == 250) { + for (const auto& line : reply.lines) { + if (0 == line.compare(0, 20, "net/listeners/socks=")) { + const std::string port_list_str = line.substr(20); + std::vector<std::string> port_list = SplitString(port_list_str, ' '); + + for (auto& portstr : port_list) { + if (portstr.empty()) continue; + if ((portstr[0] == '"' || portstr[0] == '\'') && portstr.size() >= 2 && (*portstr.rbegin() == portstr[0])) { + portstr = portstr.substr(1, portstr.size() - 2); + if (portstr.empty()) continue; + } + socks_location = portstr; + if (0 == portstr.compare(0, 10, "127.0.0.1:")) { + // Prefer localhost - ignore other ports + break; + } + } + } + } + if (!socks_location.empty()) { + LogPrint(BCLog::TOR, "Get SOCKS port command yielded %s\n", socks_location); + } else { + LogPrintf("tor: Get SOCKS port command returned nothing\n"); + } + } else if (reply.code == 510) { // 510 Unrecognized command + LogPrintf("tor: Get SOCKS port command failed with unrecognized command (You probably should upgrade Tor)\n"); + } else { + LogPrintf("tor: Get SOCKS port command failed; error code %d\n", reply.code); + } + + CService resolved; + Assume(!resolved.IsValid()); + if (!socks_location.empty()) { + resolved = LookupNumeric(socks_location, DEFAULT_TOR_SOCKS_PORT); + } + if (!resolved.IsValid()) { + // Fallback to old behaviour + resolved = LookupNumeric("127.0.0.1", DEFAULT_TOR_SOCKS_PORT); + } + + Assume(resolved.IsValid()); + LogPrint(BCLog::TOR, "Configuring onion proxy for %s\n", resolved.ToStringIPPort()); + Proxy addrOnion = Proxy(resolved, true); + SetProxy(NET_ONION, addrOnion); + + const auto onlynets = gArgs.GetArgs("-onlynet"); + + const bool onion_allowed_by_onlynet{ + !gArgs.IsArgSet("-onlynet") || + std::any_of(onlynets.begin(), onlynets.end(), [](const auto& n) { + return ParseNetwork(n) == NET_ONION; + })}; + + if (onion_allowed_by_onlynet) { + // If NET_ONION is reachable, then the below is a noop. + // + // If NET_ONION is not reachable, then none of -proxy or -onion was given. + // Since we are here, then -torcontrol and -torpassword were given. + SetReachable(NET_ONION, true); + } +} + void TorController::add_onion_cb(TorControlConnection& _conn, const TorControlReply& reply) { if (reply.code == 250) { - LogPrint(BCLog::TOR, "tor: ADD_ONION successful\n"); + LogPrint(BCLog::TOR, "ADD_ONION successful\n"); for (const std::string &s : reply.lines) { std::map<std::string,std::string> m = ParseTorReplyMapping(s); std::map<std::string,std::string>::iterator i; @@ -358,9 +421,9 @@ 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, "tor: Cached service private key to %s\n", fs::PathToString(GetPrivateKeyFile())); + LogPrint(BCLog::TOR, "Cached service private key to %s\n", fs::PathToString(GetPrivateKeyFile())); } else { LogPrintf("tor: Error writing service private key to %s\n", fs::PathToString(GetPrivateKeyFile())); } @@ -376,30 +439,12 @@ void TorController::add_onion_cb(TorControlConnection& _conn, const TorControlRe void TorController::auth_cb(TorControlConnection& _conn, const TorControlReply& reply) { if (reply.code == 250) { - LogPrint(BCLog::TOR, "tor: Authentication successful\n"); + LogPrint(BCLog::TOR, "Authentication successful\n"); // Now that we know Tor is running setup the proxy for onion addresses // if -onion isn't set to something else. if (gArgs.GetArg("-onion", "") == "") { - CService resolved(LookupNumeric("127.0.0.1", 9050)); - Proxy addrOnion = Proxy(resolved, true); - SetProxy(NET_ONION, addrOnion); - - const auto onlynets = gArgs.GetArgs("-onlynet"); - - const bool onion_allowed_by_onlynet{ - !gArgs.IsArgSet("-onlynet") || - std::any_of(onlynets.begin(), onlynets.end(), [](const auto& n) { - return ParseNetwork(n) == NET_ONION; - })}; - - if (onion_allowed_by_onlynet) { - // If NET_ONION is reachable, then the below is a noop. - // - // If NET_ONION is not reachable, then none of -proxy or -onion was given. - // Since we are here, then -torcontrol and -torpassword were given. - SetReachable(NET_ONION, true); - } + _conn.Command("GETINFO net/listeners/socks", std::bind(&TorController::get_socks_cb, this, std::placeholders::_1, std::placeholders::_2)); } // Finally - now create the service @@ -445,7 +490,7 @@ static std::vector<uint8_t> ComputeResponse(const std::string &key, const std::v void TorController::authchallenge_cb(TorControlConnection& _conn, const TorControlReply& reply) { if (reply.code == 250) { - LogPrint(BCLog::TOR, "tor: SAFECOOKIE authentication challenge successful\n"); + LogPrint(BCLog::TOR, "SAFECOOKIE authentication challenge successful\n"); std::pair<std::string,std::string> l = SplitTorReplyLine(reply.lines[0]); if (l.first == "AUTHCHALLENGE") { std::map<std::string,std::string> m = ParseTorReplyMapping(l.second); @@ -455,7 +500,7 @@ void TorController::authchallenge_cb(TorControlConnection& _conn, const TorContr } std::vector<uint8_t> serverHash = ParseHex(m["SERVERHASH"]); std::vector<uint8_t> serverNonce = ParseHex(m["SERVERNONCE"]); - LogPrint(BCLog::TOR, "tor: AUTHCHALLENGE ServerHash %s ServerNonce %s\n", HexStr(serverHash), HexStr(serverNonce)); + LogPrint(BCLog::TOR, "AUTHCHALLENGE ServerHash %s ServerNonce %s\n", HexStr(serverHash), HexStr(serverNonce)); if (serverNonce.size() != 32) { LogPrintf("tor: ServerNonce is not 32 bytes, as required by spec\n"); return; @@ -492,20 +537,22 @@ void TorController::protocolinfo_cb(TorControlConnection& _conn, const TorContro if (l.first == "AUTH") { std::map<std::string,std::string> m = ParseTorReplyMapping(l.second); std::map<std::string,std::string>::iterator i; - if ((i = m.find("METHODS")) != m.end()) - boost::split(methods, i->second, boost::is_any_of(",")); + if ((i = m.find("METHODS")) != m.end()) { + std::vector<std::string> m_vec = SplitString(i->second, ','); + methods = std::set<std::string>(m_vec.begin(), m_vec.end()); + } if ((i = m.find("COOKIEFILE")) != m.end()) cookiefile = i->second; } else if (l.first == "VERSION") { std::map<std::string,std::string> m = ParseTorReplyMapping(l.second); std::map<std::string,std::string>::iterator i; if ((i = m.find("Tor")) != m.end()) { - LogPrint(BCLog::TOR, "tor: Connected to Tor version %s\n", i->second); + LogPrint(BCLog::TOR, "Connected to Tor version %s\n", i->second); } } } for (const std::string &s : methods) { - LogPrint(BCLog::TOR, "tor: Supported authentication method: %s\n", s); + LogPrint(BCLog::TOR, "Supported authentication method: %s\n", s); } // Prefer NULL, otherwise SAFECOOKIE. If a password is provided, use HASHEDPASSWORD /* Authentication: @@ -515,24 +562,24 @@ void TorController::protocolinfo_cb(TorControlConnection& _conn, const TorContro std::string torpassword = gArgs.GetArg("-torpassword", ""); if (!torpassword.empty()) { if (methods.count("HASHEDPASSWORD")) { - LogPrint(BCLog::TOR, "tor: Using HASHEDPASSWORD authentication\n"); - boost::replace_all(torpassword, "\"", "\\\""); + LogPrint(BCLog::TOR, "Using HASHEDPASSWORD authentication\n"); + ReplaceAll(torpassword, "\"", "\\\""); _conn.Command("AUTHENTICATE \"" + torpassword + "\"", std::bind(&TorController::auth_cb, this, std::placeholders::_1, std::placeholders::_2)); } else { LogPrintf("tor: Password provided with -torpassword, but HASHEDPASSWORD authentication is not available\n"); } } else if (methods.count("NULL")) { - LogPrint(BCLog::TOR, "tor: Using NULL authentication\n"); + LogPrint(BCLog::TOR, "Using NULL authentication\n"); _conn.Command("AUTHENTICATE", std::bind(&TorController::auth_cb, this, std::placeholders::_1, std::placeholders::_2)); } else if (methods.count("SAFECOOKIE")) { // Cookie: hexdump -e '32/1 "%02x""\n"' ~/.tor/control_auth_cookie - LogPrint(BCLog::TOR, "tor: Using SAFECOOKIE authentication, reading cookie authentication from %s\n", cookiefile); + LogPrint(BCLog::TOR, "Using SAFECOOKIE authentication, reading cookie authentication from %s\n", cookiefile); std::pair<bool,std::string> status_cookie = ReadBinaryFile(fs::PathFromString(cookiefile), TOR_COOKIE_SIZE); if (status_cookie.first && status_cookie.second.size() == TOR_COOKIE_SIZE) { // _conn.Command("AUTHENTICATE " + HexStr(status_cookie.second), std::bind(&TorController::auth_cb, this, std::placeholders::_1, std::placeholders::_2)); cookie = std::vector<uint8_t>(status_cookie.second.begin(), status_cookie.second.end()); clientNonce = std::vector<uint8_t>(TOR_NONCE_SIZE, 0); - GetRandBytes(clientNonce.data(), TOR_NONCE_SIZE); + GetRandBytes(clientNonce); _conn.Command("AUTHCHALLENGE SAFECOOKIE " + HexStr(clientNonce), std::bind(&TorController::authchallenge_cb, this, std::placeholders::_1, std::placeholders::_2)); } else { if (status_cookie.first) { @@ -568,7 +615,7 @@ void TorController::disconnected_cb(TorControlConnection& _conn) if (!reconnect) return; - LogPrint(BCLog::TOR, "tor: Not connected to Tor control port %s, trying to reconnect\n", m_tor_control_center); + LogPrint(BCLog::TOR, "Not connected to Tor control port %s, trying to reconnect\n", m_tor_control_center); // Single-shot timer for reconnect. Use exponential backoff. struct timeval time = MillisToTimeval(int64_t(reconnect_timeout * 1000.0)); diff --git a/src/torcontrol.h b/src/torcontrol.h index 4ace3edcb1..81475aee74 100644 --- a/src/torcontrol.h +++ b/src/torcontrol.h @@ -140,6 +140,8 @@ private: std::vector<uint8_t> clientNonce; public: + /** Callback for GETINFO net/listeners/socks result */ + void get_socks_cb(TorControlConnection& conn, const TorControlReply& reply); /** Callback for ADD_ONION result */ void add_onion_cb(TorControlConnection& conn, const TorControlReply& reply); /** Callback for AUTHENTICATE result */ diff --git a/src/txdb.cpp b/src/txdb.cpp index 9a39e90ccd..bad3bb80a9 100644 --- a/src/txdb.cpp +++ b/src/txdb.cpp @@ -6,7 +6,6 @@ #include <txdb.h> #include <chain.h> -#include <node/ui_interface.h> #include <pow.h> #include <random.h> #include <shutdown.h> @@ -18,7 +17,6 @@ #include <stdint.h> static constexpr uint8_t DB_COIN{'C'}; -static constexpr uint8_t DB_COINS{'c'}; static constexpr uint8_t DB_BLOCK_FILES{'f'}; static constexpr uint8_t DB_BLOCK_INDEX{'b'}; @@ -29,6 +27,7 @@ static constexpr uint8_t DB_REINDEX_FLAG{'R'}; static constexpr uint8_t DB_LAST_BLOCK{'l'}; // Keys used in previous version that might still be found in the DB: +static constexpr uint8_t DB_COINS{'c'}; static constexpr uint8_t DB_TXINDEX_BLOCK{'T'}; // uint8_t DB_TXINDEX{'t'} @@ -50,6 +49,15 @@ std::optional<bilingual_str> CheckLegacyTxindex(CBlockTreeDB& block_tree_db) return std::nullopt; } +bool CCoinsViewDB::NeedsUpgrade() +{ + std::unique_ptr<CDBIterator> cursor{m_db->NewIterator()}; + // DB_COINS was deprecated in v0.15.0, commit + // 1088b02f0ccd7358d2b7076bb9e122d59d502d02 + cursor->Seek(std::make_pair(DB_COINS, uint256{})); + return cursor->Valid(); +} + namespace { struct CoinEntry { @@ -60,7 +68,7 @@ struct CoinEntry { SERIALIZE_METHODS(CoinEntry, obj) { READWRITE(obj.key, obj.outpoint->hash, VARINT(obj.outpoint->n)); } }; -} +} // namespace CCoinsViewDB::CCoinsViewDB(fs::path ldb_path, size_t nCacheSize, bool fMemory, bool fWipe) : m_db(std::make_unique<CDBWrapper>(ldb_path, nCacheSize, fMemory, fWipe, true)), @@ -76,7 +84,7 @@ void CCoinsViewDB::ResizeCache(size_t new_cache_size) // filesystem lock. m_db.reset(); m_db = std::make_unique<CDBWrapper>( - m_ldb_path, new_cache_size, m_is_memory, /*fWipe*/ false, /*obfuscate*/ true); + m_ldb_path, new_cache_size, m_is_memory, /*fWipe=*/false, /*obfuscate=*/true); } } @@ -199,11 +207,10 @@ public: // cache warmup on instantiation. CCoinsViewDBCursor(CDBIterator* pcursorIn, const uint256&hashBlockIn): CCoinsViewCursor(hashBlockIn), pcursor(pcursorIn) {} - ~CCoinsViewDBCursor() {} + ~CCoinsViewDBCursor() = default; bool GetKey(COutPoint &key) const override; bool GetValue(Coin &coin) const override; - unsigned int GetValueSize() const override; bool Valid() const override; void Next() override; @@ -249,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; @@ -308,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; @@ -337,125 +339,3 @@ bool CBlockTreeDB::LoadBlockIndexGuts(const Consensus::Params& consensusParams, return true; } - -namespace { - -//! Legacy class to deserialize pre-pertxout database entries without reindex. -class CCoins -{ -public: - //! whether transaction is a coinbase - bool fCoinBase; - - //! unspent transaction outputs; spent outputs are .IsNull(); spent outputs at the end of the array are dropped - std::vector<CTxOut> vout; - - //! at which height this transaction was included in the active block chain - int nHeight; - - //! empty constructor - CCoins() : fCoinBase(false), vout(0), nHeight(0) { } - - template<typename Stream> - void Unserialize(Stream &s) { - unsigned int nCode = 0; - // version - unsigned int nVersionDummy; - ::Unserialize(s, VARINT(nVersionDummy)); - // header code - ::Unserialize(s, VARINT(nCode)); - fCoinBase = nCode & 1; - std::vector<bool> vAvail(2, false); - vAvail[0] = (nCode & 2) != 0; - vAvail[1] = (nCode & 4) != 0; - unsigned int nMaskCode = (nCode / 8) + ((nCode & 6) != 0 ? 0 : 1); - // spentness bitmask - while (nMaskCode > 0) { - unsigned char chAvail = 0; - ::Unserialize(s, chAvail); - for (unsigned int p = 0; p < 8; p++) { - bool f = (chAvail & (1 << p)) != 0; - vAvail.push_back(f); - } - if (chAvail != 0) - nMaskCode--; - } - // txouts themself - vout.assign(vAvail.size(), CTxOut()); - for (unsigned int i = 0; i < vAvail.size(); i++) { - if (vAvail[i]) - ::Unserialize(s, Using<TxOutCompression>(vout[i])); - } - // coinbase height - ::Unserialize(s, VARINT_MODE(nHeight, VarIntMode::NONNEGATIVE_SIGNED)); - } -}; - -} - -/** Upgrade the database from older formats. - * - * Currently implemented: from the per-tx utxo model (0.8..0.14.x) to per-txout. - */ -bool CCoinsViewDB::Upgrade() { - std::unique_ptr<CDBIterator> pcursor(m_db->NewIterator()); - pcursor->Seek(std::make_pair(DB_COINS, uint256())); - if (!pcursor->Valid()) { - return true; - } - - int64_t count = 0; - LogPrintf("Upgrading utxo-set database...\n"); - LogPrintf("[0%%]..."); /* Continued */ - uiInterface.ShowProgress(_("Upgrading UTXO database").translated, 0, true); - size_t batch_size = 1 << 24; - CDBBatch batch(*m_db); - int reportDone = 0; - std::pair<unsigned char, uint256> key; - std::pair<unsigned char, uint256> prev_key = {DB_COINS, uint256()}; - while (pcursor->Valid()) { - if (ShutdownRequested()) { - break; - } - if (pcursor->GetKey(key) && key.first == DB_COINS) { - if (count++ % 256 == 0) { - uint32_t high = 0x100 * *key.second.begin() + *(key.second.begin() + 1); - int percentageDone = (int)(high * 100.0 / 65536.0 + 0.5); - uiInterface.ShowProgress(_("Upgrading UTXO database").translated, percentageDone, true); - if (reportDone < percentageDone/10) { - // report max. every 10% step - LogPrintf("[%d%%]...", percentageDone); /* Continued */ - reportDone = percentageDone/10; - } - } - CCoins old_coins; - if (!pcursor->GetValue(old_coins)) { - return error("%s: cannot parse CCoins record", __func__); - } - COutPoint outpoint(key.second, 0); - for (size_t i = 0; i < old_coins.vout.size(); ++i) { - if (!old_coins.vout[i].IsNull() && !old_coins.vout[i].scriptPubKey.IsUnspendable()) { - Coin newcoin(std::move(old_coins.vout[i]), old_coins.nHeight, old_coins.fCoinBase); - outpoint.n = i; - CoinEntry entry(&outpoint); - batch.Write(entry, newcoin); - } - } - batch.Erase(key); - if (batch.SizeEstimate() > batch_size) { - m_db->WriteBatch(batch); - batch.Clear(); - m_db->CompactRange(prev_key, key); - prev_key = key; - } - pcursor->Next(); - } else { - break; - } - } - m_db->WriteBatch(batch); - m_db->CompactRange({DB_COINS, uint256()}, key); - uiInterface.ShowProgress("", 100, false); - LogPrintf("[%s].\n", ShutdownRequested() ? "CANCELLED" : "DONE"); - return !ShutdownRequested(); -} diff --git a/src/txdb.h b/src/txdb.h index e70f3cd1f2..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> @@ -65,8 +66,8 @@ public: bool BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock) override; std::unique_ptr<CCoinsViewCursor> Cursor() const override; - //! Attempt to update from an older database format. Returns whether an error occurred. - bool Upgrade(); + //! Whether an unsupported database format is used. + bool NeedsUpgrade(); size_t EstimateSize() const override; //! Dynamically alter the underlying leveldb cache size. diff --git a/src/txmempool.cpp b/src/txmempool.cpp index fb5652d0a0..b151953d0d 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> @@ -54,16 +56,6 @@ struct update_ancestor_state int64_t modifySigOpsCost; }; -struct update_fee_delta -{ - explicit update_fee_delta(int64_t _feeDelta) : feeDelta(_feeDelta) { } - - void operator() (CTxMemPoolEntry &e) { e.UpdateFeeDelta(feeDelta); } - -private: - int64_t feeDelta; -}; - bool TestLockPointValidity(CChain& active_chain, const LockPoints& lp) { AssertLockHeld(cs_main); @@ -92,6 +84,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}, @@ -99,11 +92,11 @@ CTxMemPoolEntry::CTxMemPoolEntry(const CTransactionRef& tx, CAmount fee, nModFeesWithAncestors{nFee}, nSigOpCostWithAncestors{sigOpCost} {} -void CTxMemPoolEntry::UpdateFeeDelta(int64_t 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) @@ -113,12 +106,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(); @@ -158,7 +150,7 @@ void CTxMemPool::UpdateForDescendants(txiter updateIt, cacheMap& cachedDescendan // 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()); } } @@ -166,7 +158,7 @@ void CTxMemPool::UpdateForDescendants(txiter updateIt, cacheMap& cachedDescendan mapTx.modify(updateIt, update_descendant_state(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 @@ -209,7 +201,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) { @@ -338,7 +330,7 @@ bool CTxMemPool::CalculateMemPoolAncestors(const CTxMemPoolEntry &entry, staged_ancestors = it->GetMemPoolParentsConst(); } - return CalculateAncestorsAndCheckLimits(entry.GetTxSize(), /* entry_count */ 1, + return CalculateAncestorsAndCheckLimits(entry.GetTxSize(), /*entry_count=*/1, setAncestors, staged_ancestors, limitAncestorCount, limitAncestorSize, limitDescendantCount, limitDescendantSize, errString); @@ -445,7 +437,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); } @@ -454,15 +446,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 } @@ -491,12 +494,12 @@ void CTxMemPool::addUnchecked(const CTxMemPoolEntry &entry, setEntries &setAnces indexed_transaction_set::iterator newit = mapTx.insert(entry).first; // Update transaction for any feeDelta created by PrioritiseTransaction - // TODO: refactor so that the fee delta is calculated before inserting - // into mapTx. 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, update_fee_delta(delta)); + mapTx.modify(newit, [&delta](CTxMemPoolEntry& e) { e.UpdateModifiedFee(delta); }); } // Update cachedInnerUsage to include contained transaction's usage. @@ -704,6 +707,7 @@ void CTxMemPool::removeForBlock(const std::vector<CTransactionRef>& vtx, unsigne void CTxMemPool::_clear() { + vTxHashes.clear(); mapTx.clear(); mapNextTx.clear(); totalTxSize = 0; @@ -928,10 +932,10 @@ 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, update_fee_delta(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(); @@ -1119,12 +1123,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) { @@ -1148,7 +1152,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); @@ -1212,14 +1216,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 e7e5a3c402..73904cc370 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> @@ -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 - int64_t 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; } - int64_t 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. - void UpdateFeeDelta(int64_t feeDelta); + // Updates the modified fees with descendants/ancestors. + void UpdateModifiedFee(CAmount fee_diff); // Update the LockPoints after a reorg void UpdateLockPoints(const LockPoints& lp); @@ -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: @@ -560,15 +564,28 @@ 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; + + using Limits = kernel::MemPoolLimits; + + 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,13 +665,8 @@ 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); + 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) @@ -696,12 +708,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 +734,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 { @@ -827,14 +847,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/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 7c62c33970..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 may break 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/TODO b/src/univalue/TODO deleted file mode 100644 index 5530048e92..0000000000 --- a/src/univalue/TODO +++ /dev/null @@ -1,10 +0,0 @@ - -Rearrange tree for easier 'git subtree' style use - -Move towards C++11 etc. - -Namespace support - must come up with useful shorthand, avoiding -long Univalue::Univalue::Univalue usages forced upon library users. - -Improve test suite - 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 495b25a53d..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++11 compiler (no GNU extensions) -AX_CXX_COMPILE_STDCXX([11], [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 b8a6c73f4e..0000000000 --- a/src/univalue/gen/gen.cpp +++ /dev/null @@ -1,82 +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 <stdio.h> -#include <string.h> -#include "univalue.h" - -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 fc5cf402be..8ba6fd5425 100644 --- a/src/univalue/include/univalue.h +++ b/src/univalue/include/univalue.h @@ -3,16 +3,17 @@ // 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__ - -#include <stdint.h> -#include <string.h> +#ifndef BITCOIN_UNIVALUE_INCLUDE_UNIVALUE_H +#define BITCOIN_UNIVALUE_INCLUDE_UNIVALUE_H +#include <charconv> +#include <cstdint> +#include <cstring> +#include <map> +#include <stdexcept> #include <string> +#include <type_traits> #include <vector> -#include <map> -#include <cassert> class UniValue { public: @@ -23,41 +24,39 @@ public: 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; } @@ -81,68 +80,14 @@ public: bool isArray() const { return (typ == VARR); } bool isObject() const { return (typ == VOBJ); } - bool push_back(const UniValue& val); - bool push_back(const std::string& val_) { - UniValue tmpVal(VSTR, val_); - return push_back(tmpVal); - } - bool push_back(const char *val_) { - std::string s(val_); - return push_back(s); - } - bool push_back(uint64_t val_) { - UniValue tmpVal(val_); - return push_back(tmpVal); - } - bool push_back(int64_t val_) { - UniValue tmpVal(val_); - return push_back(tmpVal); - } - bool push_back(bool val_) { - UniValue tmpVal(val_); - return push_back(tmpVal); - } - bool push_back(int val_) { - UniValue tmpVal(val_); - return push_back(tmpVal); - } - bool push_back(double val_) { - UniValue tmpVal(val_); - return push_back(tmpVal); - } - bool push_backV(const std::vector<UniValue>& vec); + void push_back(const 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 pushKV(const std::string& key, const std::string& val_) { - UniValue tmpVal(VSTR, val_); - return pushKV(key, tmpVal); - } - bool pushKV(const std::string& key, const char *val_) { - std::string _val(val_); - return pushKV(key, _val); - } - bool pushKV(const std::string& key, int64_t val_) { - UniValue tmpVal(val_); - return pushKV(key, tmpVal); - } - bool pushKV(const std::string& key, uint64_t val_) { - UniValue tmpVal(val_); - return pushKV(key, tmpVal); - } - bool pushKV(const std::string& key, bool val_) { - UniValue tmpVal(val_); - return pushKV(key, tmpVal); - } - bool pushKV(const std::string& key, int val_) { - UniValue tmpVal((int64_t)val_); - return pushKV(key, tmpVal); - } - bool pushKV(const std::string& key, double val_) { - UniValue tmpVal(val_); - return pushKV(key, tmpVal); - } - bool pushKVs(const UniValue& obj); + void pushKV(const std::string& key, const UniValue& val); + void pushKVs(const UniValue& obj); std::string write(unsigned int prettyIndent = 0, unsigned int indentLevel = 0) const; @@ -159,6 +104,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; @@ -168,10 +114,10 @@ public: // value is of unexpected type const std::vector<std::string>& getKeys() const; const std::vector<UniValue>& getValues() const; + template <typename Int> + Int getInt() const; bool get_bool() const; const std::string& get_str() const; - int get_int() const; - int64_t get_int64() const; double get_real() const; const UniValue& get_obj() const; const UniValue& get_array() const; @@ -180,6 +126,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 @@ -237,4 +203,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 c4e59fae74..55e777b8ae 100644 --- a/src/univalue/lib/univalue.cpp +++ b/src/univalue/lib/univalue.cpp @@ -3,12 +3,15 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or https://opensource.org/licenses/mit-license.php. -#include <stdint.h> +#include <univalue.h> + #include <iomanip> +#include <map> +#include <memory> #include <sstream> -#include <stdlib.h> - -#include "univalue.h" +#include <string> +#include <utility> +#include <vector> const UniValue NullUniValue; @@ -20,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) @@ -43,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; @@ -63,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; @@ -72,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(const UniValue& val_) { - if (typ != VARR) - return false; + checkType(VARR); values.push_back(val_); - return true; } -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_) { + checkType(VOBJ); + keys.push_back(key); values.push_back(val_); } -bool UniValue::pushKV(const std::string& key, const UniValue& val_) +void UniValue::pushKV(const std::string& key, const UniValue& val_) { - if (typ != VOBJ) - return false; + checkType(VOBJ); size_t idx; if (findKey(key, idx)) values[idx] = val_; else __pushKV(key, val_); - return true; } -bool UniValue::pushKVs(const UniValue& obj) +void UniValue::pushKVs(const 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; } void UniValue::getObjMap(std::map<std::string,UniValue>& kv) const @@ -218,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 std::runtime_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 5af89a3561..5c58f388dd 100644 --- a/src/univalue/lib/univalue_get.cpp +++ b/src/univalue/lib/univalue_get.cpp @@ -3,17 +3,18 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or https://opensource.org/licenses/mit-license.php. -#include <stdint.h> -#include <errno.h> -#include <string.h> -#include <stdlib.h> -#include <stdexcept> -#include <vector> +#include <univalue.h> + +#include <cerrno> +#include <cstdint> +#include <cstdlib> +#include <cstring> #include <limits> -#include <string> +#include <locale> #include <sstream> - -#include "univalue.h" +#include <stdexcept> +#include <string> +#include <vector> namespace { @@ -28,37 +29,6 @@ static bool ParsePrechecks(const std::string& str) return true; } -bool ParseInt32(const std::string& str, int32_t *out) -{ - if (!ParsePrechecks(str)) - return false; - char *endp = nullptr; - errno = 0; // strtol will not set errno if valid - long int n = strtol(str.c_str(), &endp, 10); - if(out) *out = (int32_t)n; - // Note that strtol returns a *long int*, so even if strtol doesn't report an over/underflow - // we still have to check that the returned value is within the range of an *int32_t*. On 64-bit - // platforms the size of these types may be different. - return endp && *endp == 0 && !errno && - n >= std::numeric_limits<int32_t>::min() && - n <= std::numeric_limits<int32_t>::max(); -} - -bool ParseInt64(const std::string& str, int64_t *out) -{ - if (!ParsePrechecks(str)) - return false; - char *endp = nullptr; - errno = 0; // strtoll will not set errno if valid - long long int n = strtoll(str.c_str(), &endp, 10); - if(out) *out = (int64_t)n; - // Note that strtoll returns a *long long int*, so even if strtol doesn't report a over/underflow - // we still have to check that the returned value is within the range of an *int64_t*. - return endp && *endp == 0 && !errno && - n >= std::numeric_limits<int64_t>::min() && - n <= std::numeric_limits<int64_t>::max(); -} - bool ParseDouble(const std::string& str, double *out) { if (!ParsePrechecks(str)) @@ -76,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; } @@ -90,42 +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(); } -int UniValue::get_int() const -{ - if (typ != VNUM) - throw std::runtime_error("JSON value is not an integer as expected"); - int32_t retval; - if (!ParseInt32(getValStr(), &retval)) - throw std::runtime_error("JSON integer out of range"); - return retval; -} - -int64_t UniValue::get_int64() const -{ - if (typ != VNUM) - throw std::runtime_error("JSON value is not an integer as expected"); - int64_t retval; - if (!ParseInt64(getValStr(), &retval)) - throw std::runtime_error("JSON integer out of range"); - return retval; -} - 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"); @@ -134,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 be39bfe57a..2f2385383c 100644 --- a/src/univalue/lib/univalue_read.cpp +++ b/src/univalue/lib/univalue_read.cpp @@ -2,11 +2,14 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or https://opensource.org/licenses/mit-license.php. -#include <string.h> +#include <univalue.h> +#include <univalue_utffilter.h> + +#include <cstdio> +#include <cstdint> +#include <cstring> +#include <string> #include <vector> -#include <stdio.h> -#include "univalue.h" -#include "univalue_utffilter.h" /* * According to stackexchange, the original json test suite wanted @@ -14,7 +17,7 @@ * so we will follow PHP's lead, which should be more than sufficient * (further stackexchange comments indicate depth > 32 rarely occurs). */ -static const size_t MAX_JSON_DEPTH = 512; +static constexpr size_t MAX_JSON_DEPTH = 512; static bool json_isdigit(int ch) { diff --git a/src/univalue/lib/univalue_write.cpp b/src/univalue/lib/univalue_write.cpp index 3a2c580c7f..4a3cbba20f 100644 --- a/src/univalue/lib/univalue_write.cpp +++ b/src/univalue/lib/univalue_write.cpp @@ -2,10 +2,12 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or https://opensource.org/licenses/mit-license.php. -#include <iomanip> -#include <stdio.h> -#include "univalue.h" -#include "univalue_escapes.h" +#include <univalue.h> +#include <univalue_escapes.h> + +#include <memory> +#include <string> +#include <vector> static std::string json_escape(const std::string& inS) { 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 83d292200b..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 c2f52f83ac..94d7343ff3 100644 --- a/src/univalue/test/object.cpp +++ b/src/univalue/test/object.cpp @@ -3,17 +3,16 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or https://opensource.org/licenses/mit-license.php. -#include <stdint.h> -#include <vector> -#include <string> -#include <map> +#include <univalue.h> + #include <cassert> +#include <cstdint> +#include <map> +#include <memory> #include <stdexcept> -#include <univalue.h> +#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) { \ @@ -33,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()); @@ -48,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"); @@ -83,30 +80,47 @@ 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; + 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.get_int(), std::runtime_error); + BOOST_CHECK_THROW(v2.getInt<int>(), std::runtime_error); UniValue v3; - BOOST_CHECK(v3.setNumStr("32482348723847471234")); - BOOST_CHECK_THROW(v3.get_int64(), std::runtime_error); - BOOST_CHECK(v3.setNumStr("1000")); - BOOST_CHECK_EQUAL(v3.get_int64(), 1000); + v3.setNumStr("32482348723847471234"); + BOOST_CHECK_THROW(v3.getInt<int64_t>(), std::runtime_error); + v3.setNumStr("1000"); + BOOST_CHECK_EQUAL(v3.getInt<int64_t>(), 1000); UniValue v4; - BOOST_CHECK(v4.setNumStr("2147483648")); - BOOST_CHECK_EQUAL(v4.get_int64(), 2147483648); - BOOST_CHECK_THROW(v4.get_int(), std::runtime_error); - BOOST_CHECK(v4.setNumStr("1000")); - BOOST_CHECK_EQUAL(v4.get_int(), 1000); + v4.setNumStr("2147483648"); + BOOST_CHECK_EQUAL(v4.getInt<int64_t>(), 2147483648); + BOOST_CHECK_THROW(v4.getInt<int>(), std::runtime_error); + 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); BOOST_CHECK_THROW(v4.get_array(), std::runtime_error); @@ -118,84 +132,84 @@ BOOST_AUTO_TEST_CASE(univalue_typecheck) BOOST_CHECK(v5.read("[true, 10]")); BOOST_CHECK_NO_THROW(v5.get_array()); std::vector<UniValue> vals = v5.getValues(); - BOOST_CHECK_THROW(vals[0].get_int(), std::runtime_error); + BOOST_CHECK_THROW(vals[0].getInt<int>(), std::runtime_error); BOOST_CHECK_EQUAL(vals[0].get_bool(), true); - BOOST_CHECK_EQUAL(vals[1].get_int(), 10); + BOOST_CHECK_EQUAL(vals[1].getInt<int>(), 10); 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"); @@ -204,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); @@ -243,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; @@ -251,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); @@ -338,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); @@ -362,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)); @@ -405,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(); @@ -417,4 +430,3 @@ int main (int argc, char *argv[]) univalue_readwrite(); return 0; } - diff --git a/src/univalue/test/test_json.cpp b/src/univalue/test/test_json.cpp index 2943bae2b1..f8c10238d4 100644 --- a/src/univalue/test/test_json.cpp +++ b/src/univalue/test/test_json.cpp @@ -4,9 +4,11 @@ // It reads JSON input from stdin and exits with code 0 if it can be parsed // successfully. It also pretty prints the parsed JSON value to stdout. +#include <univalue.h> + #include <iostream> +#include <iterator> #include <string> -#include "univalue.h" using namespace std; diff --git a/src/univalue/test/unitester.cpp b/src/univalue/test/unitester.cpp index 02e1a83c6d..6344a5a0ab 100644 --- a/src/univalue/test/unitester.cpp +++ b/src/univalue/test/unitester.cpp @@ -2,26 +2,17 @@ // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or https://opensource.org/licenses/mit-license.php. -#include <stdlib.h> -#include <stdio.h> -#include <string.h> +#include <univalue.h> + #include <cassert> +#include <cstdio> #include <string> -#include "univalue.h" #ifndef JSON_TEST_SRC #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) { @@ -42,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) { @@ -142,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..39e43eeb31 100644 --- a/src/util/bip32.cpp +++ b/src/util/bip32.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 <sstream> -#include <stdio.h> #include <tinyformat.h> #include <util/bip32.h> #include <util/strencodings.h> +#include <algorithm> +#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 8f86f2aaa6..0872bc88de 100644 --- a/src/util/bip32.h +++ b/src/util/bip32.h @@ -5,7 +5,7 @@ #ifndef BITCOIN_UTIL_BIP32_H #define BITCOIN_UTIL_BIP32_H -#include <attributes.h> +#include <cstdint> #include <string> #include <vector> diff --git a/src/util/bytevectorhash.cpp b/src/util/bytevectorhash.cpp index f87d0e04b3..6d777613e6 100644 --- a/src/util/bytevectorhash.cpp +++ b/src/util/bytevectorhash.cpp @@ -6,10 +6,12 @@ #include <random.h> #include <util/bytevectorhash.h> -ByteVectorHash::ByteVectorHash() +#include <vector> + +ByteVectorHash::ByteVectorHash() : + m_k0(GetRand<uint64_t>()), + m_k1(GetRand<uint64_t>()) { - GetRandBytes(reinterpret_cast<unsigned char*>(&m_k0), sizeof(m_k0)); - GetRandBytes(reinterpret_cast<unsigned char*>(&m_k1), sizeof(m_k1)); } size_t ByteVectorHash::operator()(const std::vector<unsigned char>& input) const 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/check.cpp b/src/util/check.cpp new file mode 100644 index 0000000000..2a9f885560 --- /dev/null +++ b/src/util/check.cpp @@ -0,0 +1,14 @@ +// 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/check.h> + +#include <tinyformat.h> + +void assertion_fail(const char* file, int line, const char* func, const char* assertion) +{ + auto str = strprintf("%s:%s %s: Assertion `%s' failed.\n", file, line, func, assertion); + fwrite(str.data(), 1, str.size(), stderr); + std::abort(); +} diff --git a/src/util/check.h b/src/util/check.h index a443c13cf2..aca957925a 100644 --- a/src/util/check.h +++ b/src/util/check.h @@ -18,8 +18,23 @@ class NonFatalCheckError : public std::runtime_error using std::runtime_error::runtime_error; }; +#define format_internal_error(msg, file, line, func, report) \ + strprintf("Internal bug detected: \"%s\"\n%s:%d (%s)\nPlease report this issue here: %s\n", \ + msg, file, line, func, report) + +/** Helper for CHECK_NONFATAL() */ +template <typename T> +T&& inline_check_non_fatal(T&& val, const char* file, int line, const char* func, const char* assertion) +{ + if (!(val)) { + throw NonFatalCheckError( + format_internal_error(assertion, file, line, func, PACKAGE_BUGREPORT)); + } + return std::forward<T>(val); +} + /** - * Throw a NonFatalCheckError when the condition evaluates to false + * Identity function. Throw a NonFatalCheckError when the condition evaluates to false * * This should only be used * - where the condition is assumed to be true, not for error handling or validating user input @@ -29,32 +44,34 @@ class NonFatalCheckError : public std::runtime_error * asserts or recoverable logic errors. A NonFatalCheckError in RPC code is caught and passed as a string to the RPC * caller, which can then report the issue to the developers. */ -#define CHECK_NONFATAL(condition) \ - do { \ - if (!(condition)) { \ - throw NonFatalCheckError( \ - strprintf("Internal bug detected: '%s'\n" \ - "%s:%d (%s)\n" \ - "You may report this issue here: %s\n", \ - (#condition), \ - __FILE__, __LINE__, __func__, \ - PACKAGE_BUGREPORT)); \ - } \ - } while (false) +#define CHECK_NONFATAL(condition) \ + inline_check_non_fatal(condition, __FILE__, __LINE__, __func__, #condition) #if defined(NDEBUG) #error "Cannot compile without assertions!" #endif /** Helper for Assert() */ -template <typename T> -T get_pure_r_value(T&& val) +void assertion_fail(const char* file, int line, const char* func, const char* assertion); + +/** Helper for Assert()/Assume() */ +template <bool IS_ASSERT, typename T> +T&& inline_assertion_check(T&& val, [[maybe_unused]] const char* file, [[maybe_unused]] int line, [[maybe_unused]] const char* func, [[maybe_unused]] const char* assertion) { + if constexpr (IS_ASSERT +#ifdef ABORT_ON_FAILED_ASSUME + || true +#endif + ) { + if (!val) { + assertion_fail(file, line, func, assertion); + } + } return std::forward<T>(val); } /** Identity function. Abort if the value compares equal to zero */ -#define Assert(val) ([&]() -> decltype(get_pure_r_value(val)) { auto&& check = (val); assert(#val && check); return std::forward<decltype(get_pure_r_value(val))>(check); }()) +#define Assert(val) inline_assertion_check<true>(val, __FILE__, __LINE__, __func__, #val) /** * Assume is the identity function. @@ -66,10 +83,15 @@ T get_pure_r_value(T&& val) * - For non-fatal errors in interactive sessions (e.g. RPC or command line * interfaces), CHECK_NONFATAL() might be more appropriate. */ -#ifdef ABORT_ON_FAILED_ASSUME -#define Assume(val) Assert(val) -#else -#define Assume(val) ([&]() -> decltype(get_pure_r_value(val)) { auto&& check = (val); return std::forward<decltype(get_pure_r_value(val))>(check); }()) -#endif +#define Assume(val) inline_assertion_check<false>(val, __FILE__, __LINE__, __func__, #val) + +/** + * NONFATAL_UNREACHABLE() is a macro that is used to mark unreachable code. It throws a NonFatalCheckError. + * This is used to mark code that is not yet implemented or is not yet reachable. + */ +#define NONFATAL_UNREACHABLE() \ + throw NonFatalCheckError( \ + format_internal_error("Unreachable code reached (non-fatal)", \ + __FILE__, __LINE__, __func__, PACKAGE_BUGREPORT)) #endif // BITCOIN_UTIL_CHECK_H diff --git a/src/util/epochguard.h b/src/util/epochguard.h index 0fec7d2624..7f6477fb3b 100644 --- a/src/util/epochguard.h +++ b/src/util/epochguard.h @@ -7,6 +7,7 @@ #define BITCOIN_UTIL_EPOCHGUARD_H #include <threadsafety.h> +#include <util/macros.h> #include <cassert> @@ -96,6 +97,6 @@ public: } }; -#define WITH_FRESH_EPOCH(epoch) const Epoch::Guard PASTE2(epoch_guard_, __COUNTER__)(epoch) +#define WITH_FRESH_EPOCH(epoch) const Epoch::Guard UNIQUE_NAME(epoch_guard_)(epoch) #endif // BITCOIN_UTIL_EPOCHGUARD_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/getuniquepath.cpp b/src/util/getuniquepath.cpp index 6776e7785b..1d8e511c83 100644 --- a/src/util/getuniquepath.cpp +++ b/src/util/getuniquepath.cpp @@ -9,6 +9,6 @@ fs::path GetUniquePath(const fs::path& base) { FastRandomContext rnd; - fs::path tmpFile = base / HexStr(rnd.randbytes(8)); + fs::path tmpFile = base / fs::u8path(HexStr(rnd.randbytes(8))); return tmpFile; }
\ No newline at end of file diff --git a/src/util/hasher.cpp b/src/util/hasher.cpp index 5900daf050..a80f20c894 100644 --- a/src/util/hasher.cpp +++ b/src/util/hasher.cpp @@ -2,16 +2,16 @@ // 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>()) {} -SaltedTxidHasher::SaltedTxidHasher() : k0(GetRand(std::numeric_limits<uint64_t>::max())), k1(GetRand(std::numeric_limits<uint64_t>::max())) {} +SaltedOutpointHasher::SaltedOutpointHasher() : k0(GetRand<uint64_t>()), k1(GetRand<uint64_t>()) {} -SaltedOutpointHasher::SaltedOutpointHasher() : k0(GetRand(std::numeric_limits<uint64_t>::max())), k1(GetRand(std::numeric_limits<uint64_t>::max())) {} - -SaltedSipHasher::SaltedSipHasher() : m_k0(GetRand(std::numeric_limits<uint64_t>::max())), m_k1(GetRand(std::numeric_limits<uint64_t>::max())) {} +SaltedSipHasher::SaltedSipHasher() : m_k0(GetRand<uint64_t>()), m_k1(GetRand<uint64_t>()) {} size_t SaltedSipHasher::operator()(const Span<const unsigned char>& script) const { 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/macros.h b/src/util/macros.h index c9740c8e82..bf6ba665dc 100644 --- a/src/util/macros.h +++ b/src/util/macros.h @@ -8,6 +8,8 @@ #define PASTE(x, y) x ## y #define PASTE2(x, y) PASTE(x, y) +#define UNIQUE_NAME(name) PASTE2(name, __COUNTER__) + /** * Converts the parameter X to a string after macro replacement on X has been performed. * Don't merge these into one macro! diff --git a/src/util/message.cpp b/src/util/message.cpp index 2c7f0406f0..028251a5a8 100644 --- a/src/util/message.cpp +++ b/src/util/message.cpp @@ -3,16 +3,20 @@ // 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 <serialize.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> /** @@ -35,14 +39,13 @@ MessageVerificationResult MessageVerify( return MessageVerificationResult::ERR_ADDRESS_NO_KEY; } - bool invalid = false; - std::vector<unsigned char> signature_bytes = DecodeBase64(signature.c_str(), &invalid); - if (invalid) { + auto signature_bytes = DecodeBase64(signature); + if (!signature_bytes) { return MessageVerificationResult::ERR_MALFORMED_SIGNATURE; } CPubKey pubkey; - if (!pubkey.RecoverCompact(MessageHash(message), signature_bytes)) { + if (!pubkey.RecoverCompact(MessageHash(message), *signature_bytes)) { return MessageVerificationResult::ERR_PUBKEY_NOT_RECOVERED; } @@ -71,7 +74,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 2cd7a426f8..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) @@ -40,7 +41,7 @@ std::string FormatMoney(const CAmount n) std::optional<CAmount> ParseMoney(const std::string& money_string) { - if (!ValidAsCString(money_string)) { + if (!ContainsNoNUL(money_string)) { return std::nullopt; } const std::string str = TrimString(money_string); diff --git a/src/util/moneystr.h b/src/util/moneystr.h index 8180604342..3d33bd7f99 100644 --- a/src/util/moneystr.h +++ b/src/util/moneystr.h @@ -9,7 +9,6 @@ #ifndef BITCOIN_UTIL_MONEYSTR_H #define BITCOIN_UTIL_MONEYSTR_H -#include <attributes.h> #include <consensus/amount.h> #include <optional> diff --git a/src/util/readwritefile.cpp b/src/util/readwritefile.cpp index a45c41d367..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> @@ -18,7 +19,7 @@ std::pair<bool,std::string> ReadBinaryFile(const fs::path &filename, size_t maxs std::string retval; char buffer[128]; do { - const size_t n = fread(buffer, 1, sizeof(buffer), f); + const size_t n = fread(buffer, 1, std::min(sizeof(buffer), maxsize - retval.size()), f); // Check for reading errors so we don't return any data if we couldn't // read the entire file (or up to maxsize) if (ferror(f)) { @@ -26,7 +27,7 @@ std::pair<bool,std::string> ReadBinaryFile(const fs::path &filename, size_t maxs return std::make_pair(false,""); } retval.append(buffer, buffer+n); - } while (!feof(f) && retval.size() <= maxsize); + } while (!feof(f) && retval.size() < maxsize); fclose(f); return std::make_pair(true,retval); } 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/settings.cpp b/src/util/settings.cpp index 26439b010b..924a9cfab2 100644 --- a/src/util/settings.cpp +++ b/src/util/settings.cpp @@ -127,6 +127,7 @@ SettingsValue GetSetting(const Settings& settings, const std::string& section, const std::string& name, bool ignore_default_section_config, + bool ignore_nonpersistent, bool get_chain_name) { SettingsValue result; @@ -162,6 +163,9 @@ SettingsValue GetSetting(const Settings& settings, return; } + // Ignore nonpersistent settings if requested. + if (ignore_nonpersistent && (source == Source::COMMAND_LINE || source == Source::FORCED)) return; + // Skip negated command line settings. if (skip_negated_command_line && span.last_negated()) return; diff --git a/src/util/settings.h b/src/util/settings.h index ed36349232..e97158dc09 100644 --- a/src/util/settings.h +++ b/src/util/settings.h @@ -20,7 +20,7 @@ namespace util { //! @note UniValue is used here for convenience and because it can be easily //! serialized in a readable format. But any other variant type that can //! be assigned strings, int64_t, and bool values and has get_str(), -//! get_int64(), get_bool(), isNum(), isBool(), isFalse(), isTrue() and +//! getInt<int64_t>(), get_bool(), isNum(), isBool(), isFalse(), isTrue() and //! isNull() methods can be substituted if there's a need to move away //! from UniValue. (An implementation with boost::variant was posted at //! https://github.com/bitcoin/bitcoin/pull/15934/files#r337691812) @@ -55,12 +55,18 @@ bool WriteSettings(const fs::path& path, //! @param ignore_default_section_config - ignore values in the default section //! of the config file (part before any //! [section] keywords) +//! @param ignore_nonpersistent - ignore non-persistent settings values (forced +//! settings values and values specified on the +//! command line). Only return settings in the +//! read-only config and read-write settings +//! files. //! @param get_chain_name - enable special backwards compatible behavior //! for GetChainName SettingsValue GetSetting(const Settings& settings, const std::string& section, const std::string& name, bool ignore_default_section_config, + bool ignore_nonpersistent, bool get_chain_name); //! Get combined setting value similar to GetSetting(), except if setting was diff --git a/src/util/sock.cpp b/src/util/sock.cpp index 2029d70a37..125dbc7f18 100644 --- a/src/util/sock.cpp +++ b/src/util/sock.cpp @@ -2,11 +2,12 @@ // 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> #include <util/sock.h> +#include <util/syserror.h> #include <util/system.h> #include <util/time.h> @@ -38,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; @@ -50,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); @@ -74,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 @@ -105,65 +107,115 @@ int Sock::GetSockOpt(int level, int opt_name, void* opt_val, socklen_t* opt_len) return getsockopt(m_socket, level, opt_name, static_cast<char*>(opt_val), opt_len); } +int Sock::SetSockOpt(int level, int opt_name, const void* opt_val, socklen_t opt_len) const +{ + 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(s, &send)) { + events.occurred |= SEND; } - if (FD_ISSET(m_socket, &fdset_send)) { - *occurred |= SEND; + if (FD_ISSET(s, &err)) { + events.occurred |= ERR; } } @@ -320,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) { @@ -339,34 +407,7 @@ std::string NetworkErrorString(int err) #else std::string NetworkErrorString(int err) { - char buf[256]; - buf[0] = 0; - /* Too bad there are two incompatible implementations of the - * thread-safe strerror. */ - const char *s; -#ifdef STRERROR_R_CHAR_P /* GNU variant can return a pointer outside the passed buffer */ - s = strerror_r(err, buf, sizeof(buf)); -#else /* POSIX variant always returns message in buffer */ - s = buf; - if (strerror_r(err, buf, sizeof(buf))) - buf[0] = 0; -#endif - return strprintf("%s (%d)", s, err); + // On BSD sockets implementations, NetworkErrorString is the same as SysErrorString. + 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 7510482857..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. @@ -115,31 +116,106 @@ public: void* opt_val, socklen_t* opt_len) const; + /** + * setsockopt(2) wrapper. Equivalent to + * `setsockopt(this->Get(), level, opt_name, opt_val, opt_len)`. Code that uses this + * wrapper can be unit tested if this method is overridden by a mock Sock implementation. + */ + [[nodiscard]] virtual int SetSockOpt(int level, + int opt_name, + 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. */ /** @@ -183,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 50f6aee419..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 { @@ -48,20 +49,4 @@ Span<const char> Expr(Span<const char>& sp) return ret; } -std::vector<Span<const char>> Split(const Span<const char>& sp, char sep) -{ - std::vector<Span<const char>> ret; - auto it = sp.begin(); - auto start = it; - while (it != sp.end()) { - if (*it == sep) { - ret.emplace_back(start, it); - start = it + 1; - } - ++it; - } - ret.emplace_back(start, it); - return ret; -} - } // namespace spanparsing diff --git a/src/util/spanparsing.h b/src/util/spanparsing.h index fa2e698e6d..51795271de 100644 --- a/src/util/spanparsing.h +++ b/src/util/spanparsing.h @@ -8,6 +8,7 @@ #include <span.h> #include <string> +#include <string_view> #include <vector> namespace spanparsing { @@ -36,6 +37,30 @@ bool Func(const std::string& str, Span<const char>& sp); */ Span<const char> Expr(Span<const char>& sp); +/** Split a string on any char found in separators, returning a vector. + * + * If sep does not occur in sp, a singleton with the entirety of sp is returned. + * + * Note that this function does not care about braces, so splitting + * "foo(bar(1),2),3) on ',' will return {"foo(bar(1)", "2)", "3)"}. + */ +template <typename T = Span<const char>> +std::vector<T> Split(const Span<const char>& sp, std::string_view separators) +{ + std::vector<T> ret; + auto it = sp.begin(); + auto start = it; + while (it != sp.end()) { + if (separators.find(*it) != std::string::npos) { + ret.emplace_back(start, it); + start = it + 1; + } + ++it; + } + ret.emplace_back(start, it); + return ret; +} + /** Split a string on every instance of sep, returning a vector. * * If sep does not occur in sp, a singleton with the entirety of sp is returned. @@ -43,7 +68,11 @@ Span<const char> Expr(Span<const char>& sp); * Note that this function does not care about braces, so splitting * "foo(bar(1),2),3) on ',' will return {"foo(bar(1)", "2)", "3)"}. */ -std::vector<Span<const char>> Split(const Span<const char>& sp, char sep); +template <typename T = Span<const char>> +std::vector<T> Split(const Span<const char>& sp, char sep) +{ + return Split<T>(sp, std::string_view{&sep, 1}); +} } // namespace spanparsing diff --git a/src/util/strencodings.cpp b/src/util/strencodings.cpp index 940fa90da2..303e19beec 100644 --- a/src/util/strencodings.cpp +++ b/src/util/strencodings.cpp @@ -3,16 +3,18 @@ // 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 <cstdlib> +#include <array> +#include <cassert> #include <cstring> #include <limits> #include <optional> +#include <ostream> +#include <string> +#include <vector> static const std::string CHARS_ALPHA_NUM = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; @@ -24,15 +26,15 @@ static const std::string SAFE_CHARS[] = CHARS_ALPHA_NUM + "!*'();:@&=+$,/?#[]-_.~%", // SAFE_CHARS_URI }; -std::string SanitizeString(const std::string& str, int rule) +std::string SanitizeString(std::string_view str, int rule) { - std::string strResult; - for (std::string::size_type i = 0; i < str.size(); i++) - { - if (SAFE_CHARS[rule].find(str[i]) != std::string::npos) - strResult.push_back(str[i]); + std::string result; + for (char c : str) { + if (SAFE_CHARS[rule].find(c) != std::string::npos) { + result.push_back(c); + } } - return strResult; + return result; } const signed char p_util_hexdigit[256] = @@ -58,56 +60,45 @@ signed char HexDigit(char c) return p_util_hexdigit[(unsigned char)c]; } -bool IsHex(const std::string& str) +bool IsHex(std::string_view str) { - for(std::string::const_iterator it(str.begin()); it != str.end(); ++it) - { - if (HexDigit(*it) < 0) - return false; + for (char c : str) { + if (HexDigit(c) < 0) return false; } return (str.size() > 0) && (str.size()%2 == 0); } -bool IsHexNumber(const std::string& str) +bool IsHexNumber(std::string_view str) { - size_t starting_location = 0; - if (str.size() > 2 && *str.begin() == '0' && *(str.begin()+1) == 'x') { - starting_location = 2; - } - for (const char c : str.substr(starting_location)) { + if (str.substr(0, 2) == "0x") str.remove_prefix(2); + for (char c : str) { if (HexDigit(c) < 0) return false; } // Return false for empty string or "0x". - return (str.size() > starting_location); + return str.size() > 0; } -std::vector<unsigned char> ParseHex(const char* psz) +template <typename Byte> +std::vector<Byte> ParseHex(std::string_view str) { - // convert hex dump to vector - std::vector<unsigned char> vch; - while (true) - { - while (IsSpace(*psz)) - psz++; - signed char c = HexDigit(*psz++); - if (c == (signed char)-1) - break; - auto n{uint8_t(c << 4)}; - c = HexDigit(*psz++); - if (c == (signed char)-1) - break; - n |= c; - vch.push_back(n); + std::vector<Byte> vch; + auto it = str.begin(); + while (it != str.end() && it + 1 != str.end()) { + if (IsSpace(*it)) { + ++it; + continue; + } + auto c1 = HexDigit(*(it++)); + auto c2 = HexDigit(*(it++)); + if (c1 < 0 || c2 < 0) break; + vch.push_back(Byte(c1 << 4) | Byte(c2)); } return vch; } +template std::vector<std::byte> ParseHex(std::string_view); +template std::vector<uint8_t> ParseHex(std::string_view); -std::vector<unsigned char> ParseHex(const std::string& str) -{ - return ParseHex(str.c_str()); -} - -void SplitHostPort(std::string in, uint16_t& portOut, std::string& hostOut) +void SplitHostPort(std::string_view in, uint16_t& portOut, std::string& hostOut) { size_t colon = in.find_last_of(':'); // if a : is found, and it either follows a [...], or no other : is in the string, treat it as port separator @@ -139,7 +130,7 @@ std::string EncodeBase64(Span<const unsigned char> input) return str; } -std::vector<unsigned char> DecodeBase64(const char* p, bool* pf_invalid) +std::optional<std::vector<unsigned char>> DecodeBase64(std::string_view str) { static const int8_t decode64_table[256]{ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, @@ -157,46 +148,23 @@ std::vector<unsigned char> DecodeBase64(const char* p, bool* pf_invalid) -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }; - const char* e = p; - std::vector<uint8_t> val; - val.reserve(strlen(p)); - while (*p != 0) { - int x = decode64_table[(unsigned char)*p]; - if (x == -1) break; - val.push_back(uint8_t(x)); - ++p; - } + if (str.size() % 4 != 0) return {}; + /* One or two = characters at the end are permitted. */ + if (str.size() >= 1 && str.back() == '=') str.remove_suffix(1); + if (str.size() >= 1 && str.back() == '=') str.remove_suffix(1); std::vector<unsigned char> ret; - ret.reserve((val.size() * 3) / 4); - bool valid = ConvertBits<6, 8, false>([&](unsigned char c) { ret.push_back(c); }, val.begin(), val.end()); - - const char* q = p; - while (valid && *p != 0) { - if (*p != '=') { - valid = false; - break; - } - ++p; - } - valid = valid && (p - e) % 4 == 0 && p - q < 4; - if (pf_invalid) *pf_invalid = !valid; + ret.reserve((str.size() * 3) / 4); + bool valid = ConvertBits<6, 8, false>( + [&](unsigned char c) { ret.push_back(c); }, + str.begin(), str.end(), + [](char c) { return decode64_table[uint8_t(c)]; } + ); + if (!valid) return {}; return ret; } -std::string DecodeBase64(const std::string& str, bool* pf_invalid) -{ - if (!ValidAsCString(str)) { - if (pf_invalid) { - *pf_invalid = true; - } - return {}; - } - std::vector<unsigned char> vchRet = DecodeBase64(str.c_str(), pf_invalid); - return std::string((const char*)vchRet.data(), vchRet.size()); -} - std::string EncodeBase32(Span<const unsigned char> input, bool pad) { static const char *pbase32 = "abcdefghijklmnopqrstuvwxyz234567"; @@ -212,12 +180,12 @@ std::string EncodeBase32(Span<const unsigned char> input, bool pad) return str; } -std::string EncodeBase32(const std::string& str, bool pad) +std::string EncodeBase32(std::string_view str, bool pad) { return EncodeBase32(MakeUCharSpan(str), pad); } -std::vector<unsigned char> DecodeBase32(const char* p, bool* pf_invalid) +std::optional<std::vector<unsigned char>> DecodeBase32(std::string_view str) { static const int8_t decode32_table[256]{ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, @@ -235,49 +203,29 @@ std::vector<unsigned char> DecodeBase32(const char* p, bool* pf_invalid) -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }; - const char* e = p; - std::vector<uint8_t> val; - val.reserve(strlen(p)); - while (*p != 0) { - int x = decode32_table[(unsigned char)*p]; - if (x == -1) break; - val.push_back(uint8_t(x)); - ++p; - } + if (str.size() % 8 != 0) return {}; + /* 1, 3, 4, or 6 padding '=' suffix characters are permitted. */ + if (str.size() >= 1 && str.back() == '=') str.remove_suffix(1); + if (str.size() >= 2 && str.substr(str.size() - 2) == "==") str.remove_suffix(2); + if (str.size() >= 1 && str.back() == '=') str.remove_suffix(1); + if (str.size() >= 2 && str.substr(str.size() - 2) == "==") str.remove_suffix(2); std::vector<unsigned char> ret; - ret.reserve((val.size() * 5) / 8); - bool valid = ConvertBits<5, 8, false>([&](unsigned char c) { ret.push_back(c); }, val.begin(), val.end()); - - const char* q = p; - while (valid && *p != 0) { - if (*p != '=') { - valid = false; - break; - } - ++p; - } - valid = valid && (p - e) % 8 == 0 && p - q < 8; - if (pf_invalid) *pf_invalid = !valid; + ret.reserve((str.size() * 5) / 8); + bool valid = ConvertBits<5, 8, false>( + [&](unsigned char c) { ret.push_back(c); }, + str.begin(), str.end(), + [](char c) { return decode32_table[uint8_t(c)]; } + ); - return ret; -} + if (!valid) return {}; -std::string DecodeBase32(const std::string& str, bool* pf_invalid) -{ - if (!ValidAsCString(str)) { - if (pf_invalid) { - *pf_invalid = true; - } - return {}; - } - std::vector<unsigned char> vchRet = DecodeBase32(str.c_str(), pf_invalid); - return std::string((const char*)vchRet.data(), vchRet.size()); + return ret; } namespace { template <typename T> -bool ParseIntegral(const std::string& str, T* out) +bool ParseIntegral(std::string_view str, T* out) { static_assert(std::is_integral<T>::value); // Replicate the exact behavior of strtol/strtoll/strtoul/strtoull when @@ -296,37 +244,37 @@ bool ParseIntegral(const std::string& str, T* out) } }; // namespace -bool ParseInt32(const std::string& str, int32_t* out) +bool ParseInt32(std::string_view str, int32_t* out) { return ParseIntegral<int32_t>(str, out); } -bool ParseInt64(const std::string& str, int64_t* out) +bool ParseInt64(std::string_view str, int64_t* out) { return ParseIntegral<int64_t>(str, out); } -bool ParseUInt8(const std::string& str, uint8_t* out) +bool ParseUInt8(std::string_view str, uint8_t* out) { return ParseIntegral<uint8_t>(str, out); } -bool ParseUInt16(const std::string& str, uint16_t* out) +bool ParseUInt16(std::string_view str, uint16_t* out) { return ParseIntegral<uint16_t>(str, out); } -bool ParseUInt32(const std::string& str, uint32_t* out) +bool ParseUInt32(std::string_view str, uint32_t* out) { return ParseIntegral<uint32_t>(str, out); } -bool ParseUInt64(const std::string& str, uint64_t* out) +bool ParseUInt64(std::string_view str, uint64_t* out) { return ParseIntegral<uint64_t>(str, out); } -std::string FormatParagraph(const std::string& in, size_t width, size_t indent) +std::string FormatParagraph(std::string_view in, size_t width, size_t indent) { assert(width >= indent); std::stringstream out; @@ -395,7 +343,7 @@ static inline bool ProcessMantissaDigit(char ch, int64_t &mantissa, int &mantiss return true; } -bool ParseFixedPoint(const std::string &val, int decimals, int64_t *amount_out) +bool ParseFixedPoint(std::string_view val, int decimals, int64_t *amount_out) { int64_t mantissa = 0; int64_t exponent = 0; @@ -487,14 +435,14 @@ bool ParseFixedPoint(const std::string &val, int decimals, int64_t *amount_out) return true; } -std::string ToLower(const std::string& str) +std::string ToLower(std::string_view str) { std::string r; for (auto ch : str) r += ToLower(ch); return r; } -std::string ToUpper(const std::string& str) +std::string ToUpper(std::string_view str) { std::string r; for (auto ch : str) r += ToUpper(ch); @@ -508,21 +456,41 @@ std::string Capitalize(std::string str) return str; } +namespace { + +using ByteAsHex = std::array<char, 2>; + +constexpr std::array<ByteAsHex, 256> CreateByteToHexMap() +{ + constexpr char hexmap[16] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; + + std::array<ByteAsHex, 256> byte_to_hex{}; + for (size_t i = 0; i < byte_to_hex.size(); ++i) { + byte_to_hex[i][0] = hexmap[i >> 4]; + byte_to_hex[i][1] = hexmap[i & 15]; + } + return byte_to_hex; +} + +} // namespace + std::string HexStr(const Span<const uint8_t> s) { std::string rv(s.size() * 2, '\0'); - static constexpr char hexmap[16] = { '0', '1', '2', '3', '4', '5', '6', '7', - '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; - auto it = rv.begin(); + static constexpr auto byte_to_hex = CreateByteToHexMap(); + static_assert(sizeof(byte_to_hex) == 512); + + char* it = rv.data(); for (uint8_t v : s) { - *it++ = hexmap[v >> 4]; - *it++ = hexmap[v & 15]; + std::memcpy(it, byte_to_hex[v].data(), 2); + it += 2; } - assert(it == rv.end()); + + assert(it == rv.data() + rv.size()); return rv; } -std::optional<uint64_t> ParseByteUnits(const std::string& str, ByteUnit default_multiplier) +std::optional<uint64_t> ParseByteUnits(std::string_view str, ByteUnit default_multiplier) { if (str.empty()) { return std::nullopt; diff --git a/src/util/strencodings.h b/src/util/strencodings.h index 1f83fa3ffa..14867b21b2 100644 --- a/src/util/strencodings.h +++ b/src/util/strencodings.h @@ -9,16 +9,18 @@ #ifndef BITCOIN_UTIL_STRENCODINGS_H #define BITCOIN_UTIL_STRENCODINGS_H -#include <attributes.h> #include <span.h> #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() */ @@ -54,24 +56,23 @@ enum class ByteUnit : uint64_t { * @param[in] rule The set of safe chars to choose (default: least restrictive) * @return A new string without unsafe chars */ -std::string SanitizeString(const std::string& str, int rule = SAFE_CHARS_DEFAULT); -std::vector<unsigned char> ParseHex(const char* psz); -std::vector<unsigned char> ParseHex(const std::string& str); +std::string SanitizeString(std::string_view str, int rule = SAFE_CHARS_DEFAULT); +/** Parse the hex string into bytes (uint8_t or std::byte). Ignores whitespace. */ +template <typename Byte = uint8_t> +std::vector<Byte> ParseHex(std::string_view str); signed char HexDigit(char c); /* Returns true if each character in str is a hex character, and has an even * number of hex digits.*/ -bool IsHex(const std::string& str); +bool IsHex(std::string_view str); /** * Return true if the string is a hex number, optionally prefixed with "0x" */ -bool IsHexNumber(const std::string& str); -std::vector<unsigned char> DecodeBase64(const char* p, bool* pf_invalid = nullptr); -std::string DecodeBase64(const std::string& str, bool* pf_invalid = nullptr); +bool IsHexNumber(std::string_view str); +std::optional<std::vector<unsigned char>> DecodeBase64(std::string_view str); std::string EncodeBase64(Span<const unsigned char> input); inline std::string EncodeBase64(Span<const std::byte> input) { return EncodeBase64(MakeUCharSpan(input)); } -inline std::string EncodeBase64(const std::string& str) { return EncodeBase64(MakeUCharSpan(str)); } -std::vector<unsigned char> DecodeBase32(const char* p, bool* pf_invalid = nullptr); -std::string DecodeBase32(const std::string& str, bool* pf_invalid = nullptr); +inline std::string EncodeBase64(std::string_view str) { return EncodeBase64(MakeUCharSpan(str)); } +std::optional<std::vector<unsigned char>> DecodeBase32(std::string_view str); /** * Base32 encode. @@ -85,9 +86,9 @@ std::string EncodeBase32(Span<const unsigned char> input, bool pad = true); * If `pad` is true, then the output will be padded with '=' so that its length * is a multiple of 8. */ -std::string EncodeBase32(const std::string& str, bool pad = true); +std::string EncodeBase32(std::string_view str, bool pad = true); -void SplitHostPort(std::string in, uint16_t& portOut, std::string& hostOut); +void SplitHostPort(std::string_view in, uint16_t& portOut, std::string& hostOut); // LocaleIndependentAtoi is provided for backwards compatibility reasons. // @@ -101,12 +102,12 @@ void SplitHostPort(std::string in, uint16_t& portOut, std::string& hostOut); // undefined behavior, while this function returns the maximum or minimum // values, respectively. template <typename T> -T LocaleIndependentAtoi(const std::string& str) +T LocaleIndependentAtoi(std::string_view str) { static_assert(std::is_integral<T>::value); T result; // Emulate atoi(...) handling of white space and leading +/-. - std::string s = TrimString(str); + std::string_view s = TrimStringView(str); if (!s.empty() && s[0] == '+') { if (s.length() >= 2 && s[1] == '-') { return 0; @@ -162,7 +163,7 @@ constexpr inline bool IsSpace(char c) noexcept { * parsed value is not in the range representable by the type T. */ template <typename T> -std::optional<T> ToIntegral(const std::string& str) +std::optional<T> ToIntegral(std::string_view str) { static_assert(std::is_integral<T>::value); T result; @@ -178,42 +179,42 @@ std::optional<T> ToIntegral(const std::string& str) * @returns true if the entire string could be parsed as valid integer, * false if not the entire string could be parsed or when overflow or underflow occurred. */ -[[nodiscard]] bool ParseInt32(const std::string& str, int32_t *out); +[[nodiscard]] bool ParseInt32(std::string_view str, int32_t *out); /** * Convert string to signed 64-bit integer with strict parse error feedback. * @returns true if the entire string could be parsed as valid integer, * false if not the entire string could be parsed or when overflow or underflow occurred. */ -[[nodiscard]] bool ParseInt64(const std::string& str, int64_t *out); +[[nodiscard]] bool ParseInt64(std::string_view str, int64_t *out); /** * Convert decimal string to unsigned 8-bit integer with strict parse error feedback. * @returns true if the entire string could be parsed as valid integer, * false if not the entire string could be parsed or when overflow or underflow occurred. */ -[[nodiscard]] bool ParseUInt8(const std::string& str, uint8_t *out); +[[nodiscard]] bool ParseUInt8(std::string_view str, uint8_t *out); /** * Convert decimal string to unsigned 16-bit integer with strict parse error feedback. * @returns true if the entire string could be parsed as valid integer, * false if the entire string could not be parsed or if overflow or underflow occurred. */ -[[nodiscard]] bool ParseUInt16(const std::string& str, uint16_t* out); +[[nodiscard]] bool ParseUInt16(std::string_view str, uint16_t* out); /** * Convert decimal string to unsigned 32-bit integer with strict parse error feedback. * @returns true if the entire string could be parsed as valid integer, * false if not the entire string could be parsed or when overflow or underflow occurred. */ -[[nodiscard]] bool ParseUInt32(const std::string& str, uint32_t *out); +[[nodiscard]] bool ParseUInt32(std::string_view str, uint32_t *out); /** * Convert decimal string to unsigned 64-bit integer with strict parse error feedback. * @returns true if the entire string could be parsed as valid integer, * false if not the entire string could be parsed or when overflow or underflow occurred. */ -[[nodiscard]] bool ParseUInt64(const std::string& str, uint64_t *out); +[[nodiscard]] bool ParseUInt64(std::string_view str, uint64_t *out); /** * Convert a span of bytes to a lower-case hexadecimal string. @@ -226,7 +227,7 @@ inline std::string HexStr(const Span<const std::byte> s) { return HexStr(MakeUCh * Format a paragraph of text to a fixed width, adding spaces for * indentation to any added line. */ -std::string FormatParagraph(const std::string& in, size_t width = 79, size_t indent = 0); +std::string FormatParagraph(std::string_view in, size_t width = 79, size_t indent = 0); /** * Timing-attack-resistant comparison. @@ -248,17 +249,28 @@ bool TimingResistantEqual(const T& a, const T& b) * @returns true on success, false on error. * @note The result must be in the range (-10^18,10^18), otherwise an overflow error will trigger. */ -[[nodiscard]] bool ParseFixedPoint(const std::string &val, int decimals, int64_t *amount_out); +[[nodiscard]] bool ParseFixedPoint(std::string_view, int decimals, int64_t *amount_out); + +namespace { +/** Helper class for the default infn argument to ConvertBits (just returns the input). */ +struct IntIdentity +{ + [[maybe_unused]] int operator()(int x) const { return x; } +}; + +} // namespace /** Convert from one power-of-2 number base to another. */ -template<int frombits, int tobits, bool pad, typename O, typename I> -bool ConvertBits(const O& outfn, I it, I end) { +template<int frombits, int tobits, bool pad, typename O, typename It, typename I = IntIdentity> +bool ConvertBits(O outfn, It it, It end, I infn = {}) { size_t acc = 0; size_t bits = 0; constexpr size_t maxv = (1 << tobits) - 1; constexpr size_t max_acc = (1 << (frombits + tobits - 1)) - 1; while (it != end) { - acc = ((acc << frombits) | *it) & max_acc; + int v = infn(*it); + if (v < 0) return false; + acc = ((acc << frombits) | v) & max_acc; bits += frombits; while (bits >= tobits) { bits -= tobits; @@ -298,7 +310,7 @@ constexpr char ToLower(char c) * @param[in] str the string to convert to lowercase. * @returns lowercased equivalent of str */ -std::string ToLower(const std::string& str); +std::string ToLower(std::string_view str); /** * Converts the given character to its uppercase equivalent. @@ -324,7 +336,7 @@ constexpr char ToUpper(char c) * @param[in] str the string to convert to uppercase. * @returns UPPERCASED EQUIVALENT OF str */ -std::string ToUpper(const std::string& str); +std::string ToUpper(std::string_view str); /** * Capitalizes the first character of the given string. @@ -348,6 +360,6 @@ std::string Capitalize(std::string str); * @returns optional uint64_t bytes from str or nullopt * if ToIntegral is false, str is empty, trailing whitespace or overflow */ -std::optional<uint64_t> ParseByteUnits(const std::string& str, ByteUnit default_multiplier); +std::optional<uint64_t> ParseByteUnits(std::string_view str, ByteUnit default_multiplier); #endif // BITCOIN_UTIL_STRENCODINGS_H diff --git a/src/util/string.cpp b/src/util/string.cpp index 8ea3a1afc6..db6dbe4135 100644 --- a/src/util/string.cpp +++ b/src/util/string.cpp @@ -3,3 +3,13 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include <util/string.h> + +#include <regex> +#include <string> +#include <utility> + +void ReplaceAll(std::string& in_out, const std::string& search, const std::string& substitute) +{ + if (search.empty()) return; + in_out = std::regex_replace(in_out, std::regex(std::move(search)), substitute); +} diff --git a/src/util/string.h b/src/util/string.h index a3b8df8d78..dd4de888bb 100644 --- a/src/util/string.h +++ b/src/util/string.h @@ -5,27 +5,46 @@ #ifndef BITCOIN_UTIL_STRING_H #define BITCOIN_UTIL_STRING_H -#include <attributes.h> +#include <util/spanparsing.h> #include <algorithm> #include <array> +#include <cstdint> #include <cstring> #include <locale> #include <sstream> #include <string> +#include <string_view> #include <vector> -[[nodiscard]] inline std::string TrimString(const std::string& str, const std::string& pattern = " \f\n\r\t\v") +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) +{ + return spanparsing::Split<std::string>(str, sep); +} + +[[nodiscard]] inline std::vector<std::string> SplitString(std::string_view str, std::string_view separators) +{ + return spanparsing::Split<std::string>(str, separators); +} + +[[nodiscard]] inline std::string_view TrimStringView(std::string_view str, std::string_view pattern = " \f\n\r\t\v") { std::string::size_type front = str.find_first_not_of(pattern); if (front == std::string::npos) { - return std::string(); + return {}; } std::string::size_type end = str.find_last_not_of(pattern); return str.substr(front, end - front + 1); } -[[nodiscard]] inline std::string RemovePrefix(const std::string& str, const std::string& prefix) +[[nodiscard]] inline std::string TrimString(std::string_view str, std::string_view pattern = " \f\n\r\t\v") +{ + return std::string(TrimStringView(str, pattern)); +} + +[[nodiscard]] inline std::string_view RemovePrefixView(std::string_view str, std::string_view prefix) { if (str.substr(0, prefix.size()) == prefix) { return str.substr(prefix.size()); @@ -33,6 +52,11 @@ return str; } +[[nodiscard]] inline std::string RemovePrefix(std::string_view str, std::string_view prefix) +{ + return std::string(RemovePrefixView(str, prefix)); +} + /** * Join a list of items * @@ -52,14 +76,14 @@ auto Join(const std::vector<T>& list, const BaseType& separator, UnaryOp unary_o return ret; } -template <typename T> -T Join(const std::vector<T>& list, const T& separator) +template <typename T, typename T2> +T Join(const std::vector<T>& list, const T2& 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, const std::string& separator) +inline std::string Join(const std::vector<std::string>& list, std::string_view separator) { return Join<std::string>(list, separator); } @@ -75,9 +99,12 @@ inline std::string MakeUnorderedList(const std::vector<std::string>& items) /** * Check if a string does not contain any embedded NUL (\0) characters */ -[[nodiscard]] inline bool ValidAsCString(const std::string& str) noexcept +[[nodiscard]] inline bool ContainsNoNUL(std::string_view str) noexcept { - return str.size() == strlen(str.c_str()); + for (auto c : str) { + if (c == 0) return false; + } + return true; } /** diff --git a/src/util/syscall_sandbox.cpp b/src/util/syscall_sandbox.cpp index f2a9cf664d..a69f815ce4 100644 --- a/src/util/syscall_sandbox.cpp +++ b/src/util/syscall_sandbox.cpp @@ -821,7 +821,6 @@ bool SetupSyscallSandbox(bool log_syscall_violation_before_terminating) return false; } } - SetSyscallSandboxPolicy(SyscallSandboxPolicy::INITIALIZATION); return true; } diff --git a/src/util/syscall_sandbox.h b/src/util/syscall_sandbox.h index f7a1cbdb55..dc02ce29e9 100644 --- a/src/util/syscall_sandbox.h +++ b/src/util/syscall_sandbox.h @@ -45,9 +45,6 @@ void SetSyscallSandboxPolicy(SyscallSandboxPolicy syscall_policy); #if defined(USE_SYSCALL_SANDBOX) //! Setup and enable the experimental syscall sandbox for the running process. -//! -//! SetSyscallSandboxPolicy(SyscallSandboxPolicy::INITIALIZATION) is called as part of -//! SetupSyscallSandbox(...). [[nodiscard]] bool SetupSyscallSandbox(bool log_syscall_violation_before_terminating); //! Invoke a disallowed syscall. Use for testing purposes. diff --git a/src/util/syserror.cpp b/src/util/syserror.cpp new file mode 100644 index 0000000000..5270f55366 --- /dev/null +++ b/src/util/syserror.cpp @@ -0,0 +1,35 @@ +// Copyright (c) 2020-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 <tinyformat.h> +#include <util/syserror.h> + +#include <cstring> +#include <string> + +std::string SysErrorString(int err) +{ + char buf[1024]; + /* Too bad there are three incompatible implementations of the + * thread-safe strerror. */ + const char *s = nullptr; +#ifdef WIN32 + if (strerror_s(buf, sizeof(buf), err) == 0) s = buf; +#else +#ifdef STRERROR_R_CHAR_P /* GNU variant can return a pointer outside the passed buffer */ + s = strerror_r(err, buf, sizeof(buf)); +#else /* POSIX variant always returns message in buffer */ + if (strerror_r(err, buf, sizeof(buf)) == 0) s = buf; +#endif +#endif + if (s != nullptr) { + return strprintf("%s (%d)", s, err); + } else { + return strprintf("Unknown error (%d)", err); + } +} diff --git a/src/util/syserror.h b/src/util/syserror.h new file mode 100644 index 0000000000..a54ba553ee --- /dev/null +++ b/src/util/syserror.h @@ -0,0 +1,16 @@ +// Copyright (c) 2010-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_SYSERROR_H +#define BITCOIN_UTIL_SYSERROR_H + +#include <string> + +/** Return system error string from errno value. Use this instead of + * std::strerror, which is not thread-safe. For network errors use + * NetworkErrorString from sock.h instead. + */ +std::string SysErrorString(int err); + +#endif // BITCOIN_UTIL_SYSERROR_H diff --git a/src/util/system.cpp b/src/util/system.cpp index aa9122106b..1953a9f836 100644 --- a/src/util/system.cpp +++ b/src/util/system.cpp @@ -6,7 +6,16 @@ #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> @@ -16,6 +25,7 @@ #include <util/getuniquepath.h> #include <util/strencodings.h> #include <util/string.h> +#include <util/syserror.h> #include <util/translation.h> @@ -45,16 +55,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 */ @@ -66,7 +66,6 @@ #include <malloc.h> #endif -#include <boost/algorithm/string/replace.hpp> #include <univalue.h> #include <fstream> @@ -87,7 +86,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 @@ -95,7 +94,7 @@ static Mutex cs_dir_locks; */ static std::map<std::string, std::unique_ptr<fsbridge::FileLock>> dir_locks GUARDED_BY(cs_dir_locks); -bool LockDirectory(const fs::path& directory, const std::string lockfile_name, bool probe_only) +bool LockDirectory(const fs::path& directory, const fs::path& lockfile_name, bool probe_only) { LOCK(cs_dir_locks); fs::path pathLockFile = directory / lockfile_name; @@ -119,7 +118,7 @@ bool LockDirectory(const fs::path& directory, const std::string lockfile_name, b return true; } -void UnlockDirectory(const fs::path& directory, const std::string& lockfile_name) +void UnlockDirectory(const fs::path& directory, const fs::path& lockfile_name) { LOCK(cs_dir_locks); dir_locks.erase(fs::PathToString(directory / lockfile_name)); @@ -227,7 +226,7 @@ KeyInfo InterpretKey(std::string key) * @return parsed settings value if it is valid, otherwise nullopt accompanied * by a descriptive error string */ -static std::optional<util::SettingsValue> InterpretValue(const KeyInfo& key, const std::string& value, +static std::optional<util::SettingsValue> InterpretValue(const KeyInfo& key, const std::string* value, unsigned int flags, std::string& error) { // Return negated settings as false values. @@ -237,20 +236,24 @@ static std::optional<util::SettingsValue> InterpretValue(const KeyInfo& key, con return std::nullopt; } // Double negatives like -nofoo=0 are supported (but discouraged) - if (!InterpretBool(value)) { - LogPrintf("Warning: parsed potentially confusing double-negative -%s=%s\n", key.name, value); + if (value && !InterpretBool(*value)) { + LogPrintf("Warning: parsed potentially confusing double-negative -%s=%s\n", key.name, *value); return true; } return false; } - return value; + if (!value && (flags & ArgsManager::DISALLOW_ELISION)) { + error = strprintf("Can not set -%s with no value. Please specify value with -%s=value.", key.name, key.name); + return std::nullopt; + } + return value ? *value : ""; } // Define default constructor and destructor that are not inline, so code instantiating this class doesn't need to // #include class definitions for all members. // For example, m_settings has an internal dependency on univalue. -ArgsManager::ArgsManager() {} -ArgsManager::~ArgsManager() {} +ArgsManager::ArgsManager() = default; +ArgsManager::~ArgsManager() = default; const std::set<std::string> ArgsManager::GetUnsuitableSectionOnlyArgs() const { @@ -311,7 +314,7 @@ bool ArgsManager::ParseParameters(int argc, const char* const argv[], std::strin #endif if (key == "-") break; //bitcoin-tx using stdin - std::string val; + std::optional<std::string> val; size_t is_index = key.find('='); if (is_index != std::string::npos) { val = key.substr(is_index + 1); @@ -357,7 +360,7 @@ bool ArgsManager::ParseParameters(int argc, const char* const argv[], std::strin return false; } - std::optional<util::SettingsValue> value = InterpretValue(keyinfo, val, *flags, error); + std::optional<util::SettingsValue> value = InterpretValue(keyinfo, val ? &*val : nullptr, *flags, error); if (!value) return false; m_settings.command_line_options[keyinfo.name].push_back(*value); @@ -387,9 +390,12 @@ std::optional<unsigned int> ArgsManager::GetArgFlags(const std::string& name) co return std::nullopt; } -fs::path ArgsManager::GetPathArg(std::string pathlike_arg) const +fs::path ArgsManager::GetPathArg(std::string arg, const fs::path& default_value) const { - auto result = fs::PathFromString(GetArg(pathlike_arg, "")).lexically_normal(); + if (IsArgNegated(arg)) return fs::path{}; + std::string path_str = GetArg(arg, ""); + if (path_str.empty()) return default_value; + fs::path result = fs::PathFromString(path_str).lexically_normal(); // Remove trailing slash, if present. return result.has_filename() ? result : result.parent_path(); } @@ -514,14 +520,17 @@ bool ArgsManager::InitSettings(std::string& error) return true; } -bool ArgsManager::GetSettingsPath(fs::path* filepath, bool temp) const +bool ArgsManager::GetSettingsPath(fs::path* filepath, bool temp, bool backup) const { - if (IsArgNegated("-settings")) { + fs::path settings = GetPathArg("-settings", BITCOIN_SETTINGS_FILENAME); + if (settings.empty()) { return false; } + if (backup) { + settings += ".bak"; + } if (filepath) { - std::string settings = GetArg("-settings", BITCOIN_SETTINGS_FILENAME); - *filepath = fsbridge::AbsPathJoin(GetDataDirNet(), fs::PathFromString(temp ? settings + ".tmp" : settings)); + *filepath = fsbridge::AbsPathJoin(GetDataDirNet(), temp ? settings + ".tmp" : settings); } return true; } @@ -560,10 +569,10 @@ bool ArgsManager::ReadSettingsFile(std::vector<std::string>* errors) return true; } -bool ArgsManager::WriteSettingsFile(std::vector<std::string>* errors) const +bool ArgsManager::WriteSettingsFile(std::vector<std::string>* errors, bool backup) const { fs::path path, path_tmp; - if (!GetSettingsPath(&path, /* temp= */ false) || !GetSettingsPath(&path_tmp, /* temp= */ true)) { + if (!GetSettingsPath(&path, /*temp=*/false, backup) || !GetSettingsPath(&path_tmp, /*temp=*/true, backup)) { throw std::logic_error("Attempt to write settings file when dynamic settings are disabled."); } @@ -580,6 +589,13 @@ bool ArgsManager::WriteSettingsFile(std::vector<std::string>* errors) const return true; } +util::SettingsValue ArgsManager::GetPersistentSetting(const std::string& name) const +{ + LOCK(cs_args); + return util::GetSetting(m_settings, m_network, name, !UseDefaultSection("-" + name), + /*ignore_nonpersistent=*/true, /*get_chain_name=*/false); +} + bool ArgsManager::IsArgNegated(const std::string& strArg) const { return GetSetting(strArg).isFalse(); @@ -587,20 +603,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 value.isNull() ? strDefault : value.isFalse() ? "0" : value.isTrue() ? "1" : value.get_str(); + 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 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 value.isNull() ? nDefault : value.isFalse() ? 0 : value.isTrue() ? 1 : value.isNum() ? value.get_int64() : LocaleIndependentAtoi<int64_t>(value.get_str()); + 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 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 value.isNull() ? fDefault : value.isBool() ? value.get_bool() : InterpretBool(value.get_str()); + 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 SettingToBool(value).value_or(fDefault); } bool ArgsManager::SoftSetArg(const std::string& strArg, const std::string& strValue) @@ -669,7 +740,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) { @@ -814,9 +885,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) @@ -841,8 +912,8 @@ static bool GetConfigOptions(std::istream& stream, const std::string& filepath, error = strprintf("parse error on line %i: %s, options in configuration file must be specified without leading -", linenr, str); return false; } else if ((pos = str.find('=')) != std::string::npos) { - std::string name = prefix + TrimString(str.substr(0, pos), pattern); - std::string value = TrimString(str.substr(pos + 1), pattern); + std::string name = prefix + TrimString(std::string_view{str}.substr(0, pos), pattern); + std::string_view value = TrimStringView(std::string_view{str}.substr(pos + 1), pattern); if (used_hash && name.find("rpcpassword") != std::string::npos) { error = strprintf("parse error on line %i, using # in rpcpassword can be ambiguous and should be avoided", linenr); return false; @@ -875,7 +946,7 @@ bool ArgsManager::ReadConfigStream(std::istream& stream, const std::string& file KeyInfo key = InterpretKey(option.first); std::optional<unsigned int> flags = GetArgFlags('-' + key.name); if (flags) { - std::optional<util::SettingsValue> value = InterpretValue(key, option.second, *flags, error); + std::optional<util::SettingsValue> value = InterpretValue(key, &option.second, *flags, error); if (!value) { return false; } @@ -900,17 +971,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 @@ -948,7 +1019,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; @@ -990,6 +1061,7 @@ std::string ArgsManager::GetChainName() const LOCK(cs_args); util::SettingsValue value = util::GetSetting(m_settings, /* section= */ "", SettingName(arg), /* ignore_default_section_config= */ false, + /*ignore_nonpersistent=*/false, /* get_chain_name= */ true); return value.isNull() ? false : value.isBool() ? value.get_bool() : InterpretBool(value.get_str()); }; @@ -1022,7 +1094,8 @@ util::SettingsValue ArgsManager::GetSetting(const std::string& arg) const { LOCK(cs_args); return util::GetSetting( - m_settings, m_network, SettingName(arg), !UseDefaultSection(arg), /* get_chain_name= */ false); + m_settings, m_network, SettingName(arg), !UseDefaultSection(arg), + /*ignore_nonpersistent=*/false, /*get_chain_name=*/false); } std::vector<util::SettingsValue> ArgsManager::GetSettingsList(const std::string& arg) const @@ -1240,7 +1313,7 @@ fs::path GetSpecialFolderPath(int nFolder, bool fCreate) std::string ShellEscape(const std::string& arg) { std::string escaped = arg; - boost::replace_all(escaped, "'", "'\"'\"'"); + ReplaceAll(escaped, "'", "'\"'\"'"); return "'" + escaped + "'"; } #endif @@ -1362,7 +1435,7 @@ void ScheduleBatchPriority() const static sched_param param{}; const int rc = pthread_setschedparam(pthread_self(), SCHED_BATCH, ¶m); if (rc != 0) { - LogPrintf("Failed to pthread_setschedparam: %s\n", strerror(rc)); + LogPrintf("Failed to pthread_setschedparam: %s\n", SysErrorString(rc)); } #endif } diff --git a/src/util/system.h b/src/util/system.h index f193c8ac0b..756e6642f2 100644 --- a/src/util/system.h +++ b/src/util/system.h @@ -14,8 +14,7 @@ #include <config/bitcoin-config.h> #endif -#include <attributes.h> -#include <compat.h> +#include <compat/compat.h> #include <compat/assumptions.h> #include <fs.h> #include <logging.h> @@ -76,8 +75,8 @@ void AllocateFileRange(FILE *file, unsigned int offset, unsigned int length); */ [[nodiscard]] bool RenameOver(fs::path src, fs::path dest); -bool LockDirectory(const fs::path& directory, const std::string lockfile_name, bool probe_only=false); -void UnlockDirectory(const fs::path& directory, const std::string& lockfile_name); +bool LockDirectory(const fs::path& directory, const fs::path& lockfile_name, bool probe_only=false); +void UnlockDirectory(const fs::path& directory, const fs::path& lockfile_name); bool DirIsWritable(const fs::path& directory); bool CheckDiskSpace(const fs::path& dir, uint64_t additional_bytes = 0); @@ -98,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 @@ -161,6 +160,15 @@ struct SectionInfo int m_line; }; +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 { public: @@ -175,6 +183,7 @@ public: // ALLOW_STRING = 0x08, //!< unimplemented, draft implementation in #16545 // ALLOW_LIST = 0x10, //!< unimplemented, draft implementation in #16545 DISALLOW_NEGATION = 0x20, //!< disallow -nofoo syntax + DISALLOW_ELISION = 0x40, //!< disallow -foo syntax that doesn't assign any value DEBUG_ONLY = 0x100, /* Some options would cause cross-contamination if values for @@ -271,16 +280,6 @@ protected: std::optional<const Command> GetCommand() const; /** - * Get a normalized path from a specified pathlike argument - * - * It is guaranteed that the returned path has no trailing slashes. - * - * @param pathlike_arg Pathlike argument to get a path from (e.g., "-datadir", "-blocksdir" or "-walletdir") - * @return Normalized path which is get from a specified pathlike argument - */ - fs::path GetPathArg(std::string pathlike_arg) const; - - /** * Get blocks directory path * * @return Blocks path which is network specific @@ -341,6 +340,19 @@ 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 + * + * @param arg Argument to get a path from (e.g., "-datadir", "-blocksdir" or "-walletdir") + * @param default_value Optional default value to return instead of the empty path. + * @return normalized path if argument is set, with redundant "." and ".." + * path components and trailing separators removed (see patharg unit test + * for examples or implementation for details). If argument is empty or not + * set, default_value is returned unchanged. + */ + fs::path GetPathArg(std::string arg, const fs::path& default_value = {}) const; /** * Return integer argument or default value @@ -350,6 +362,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 @@ -359,6 +372,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 @@ -434,7 +448,7 @@ protected: * Get settings file path, or return false if read-write settings were * disabled with -nosettings. */ - bool GetSettingsPath(fs::path* filepath = nullptr, bool temp = false) const; + bool GetSettingsPath(fs::path* filepath = nullptr, bool temp = false, bool backup = false) const; /** * Read settings file. Push errors to vector, or log them if null. @@ -442,9 +456,16 @@ protected: bool ReadSettingsFile(std::vector<std::string>* errors = nullptr); /** - * Write settings file. Push errors to vector, or log them if null. + * Write settings file or backup settings file. Push errors to vector, or + * log them if null. + */ + bool WriteSettingsFile(std::vector<std::string>* errors = nullptr, bool backup = false) const; + + /** + * Get current setting from config file or read/write settings file, + * ignoring nonpersistent command line or forced settings values. */ - bool WriteSettingsFile(std::vector<std::string>* errors = nullptr) const; + util::SettingsValue GetPersistentSetting(const std::string& name) const; /** * Access settings with lock held. diff --git a/src/util/thread.cpp b/src/util/thread.cpp index 14be668685..f9f427ba20 100644 --- a/src/util/thread.cpp +++ b/src/util/thread.cpp @@ -9,6 +9,7 @@ #include <util/threadnames.h> #include <exception> +#include <functional> void util::TraceThread(const char* thread_name, std::function<void()> thread_func) { diff --git a/src/util/threadnames.cpp b/src/util/threadnames.cpp index 764fffabd7..a5a86d2598 100644 --- a/src/util/threadnames.cpp +++ b/src/util/threadnames.cpp @@ -6,7 +6,9 @@ #include <config/bitcoin-config.h> #endif +#include <string> #include <thread> +#include <utility> #if (defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__DragonFly__)) #include <pthread.h> @@ -16,7 +18,7 @@ #include <util/threadnames.h> #ifdef HAVE_SYS_PRCTL_H -#include <sys/prctl.h> // For prctl, PR_SET_NAME, PR_GET_NAME +#include <sys/prctl.h> #endif //! Set the thread's name at the process level. Does not affect the diff --git a/src/util/time.cpp b/src/util/time.cpp index f7712f0dc8..f6d37347f8 100644 --- a/src/util/time.cpp +++ b/src/util/time.cpp @@ -7,32 +7,25 @@ #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 <atomic> +#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); } static std::atomic<int64_t> nMockTime(0); //!< For testing -int64_t GetTime() -{ - int64_t mocktime = nMockTime.load(std::memory_order_relaxed); - if (mocktime) return mocktime; - - time_t now = time(nullptr); - assert(now > 0); - return now; -} - bool ChronoSanityCheck() { // std::chrono::system_clock.time_since_epoch and time_t(0) are not guaranteed @@ -76,19 +69,16 @@ bool ChronoSanityCheck() return true; } -template <typename T> -T GetTime() +NodeClock::time_point NodeClock::now() noexcept { const std::chrono::seconds mocktime{nMockTime.load(std::memory_order_relaxed)}; - - return std::chrono::duration_cast<T>( + const auto ret{ mocktime.count() ? mocktime : - std::chrono::microseconds{GetTimeMicros()}); -} -template std::chrono::seconds GetTime(); -template std::chrono::milliseconds GetTime(); -template std::chrono::microseconds GetTime(); + std::chrono::system_clock::now().time_since_epoch()}; + assert(ret > 0s); + return time_point{ret}; +}; template <typename T> static T GetSystemTime() @@ -124,10 +114,7 @@ int64_t GetTimeMicros() return int64_t{GetSystemTime<std::chrono::microseconds>().count()}; } -int64_t GetTimeSeconds() -{ - return int64_t{GetSystemTime<std::chrono::seconds>().count()}; -} +int64_t GetTime() { return GetTime<std::chrono::seconds>().count(); } std::string FormatISO8601DateTime(int64_t nTime) { struct tm ts; diff --git a/src/util/time.h b/src/util/time.h index 9d92b23725..4f9bde5d56 100644 --- a/src/util/time.h +++ b/src/util/time.h @@ -6,40 +6,65 @@ #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; +/** Mockable clock in the context of tests, otherwise the system clock */ +struct NodeClock : public std::chrono::system_clock { + using time_point = std::chrono::time_point<NodeClock>; + /** Return current system time or mocked time, if set */ + static time_point now() noexcept; + static std::time_t to_time_t(const time_point&) = delete; // unused + static time_point from_time_t(std::time_t) = delete; // unused +}; +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>; + void UninterruptibleSleep(const std::chrono::microseconds& n); /** - * Helper to count the seconds of a duration. + * Helper to count the seconds of a duration/time_point. * - * All durations should be using std::chrono and calling this should generally + * All durations/time_points should be using std::chrono and calling this should generally * be avoided in code. Though, it is still preferred to an inline t.count() to * protect against a reliance on the exact type of t. * - * This helper is used to convert durations before passing them over an + * 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 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(); } - -/** * DEPRECATED - * Use either GetTimeSeconds (not mockable) or GetTime<T> (mockable) + * Use either ClockType::now() or Now<TimePointType>() if a cast is needed. + * ClockType is + * - std::chrono::steady_clock for steady time + * - std::chrono::system_clock for system time + * - NodeClock for mockable system time */ int64_t GetTime(); @@ -47,8 +72,6 @@ int64_t GetTime(); int64_t GetTimeMillis(); /** Returns the system time (not mockable) */ int64_t GetTimeMicros(); -/** Returns the system time (not mockable) */ -int64_t GetTimeSeconds(); // Like GetTime(), but not mockable /** * DEPRECATED @@ -64,9 +87,21 @@ void SetMockTime(std::chrono::seconds mock_time_in); /** For testing */ std::chrono::seconds GetMockTime(); -/** Return system time (or mocked time, if set) */ +/** + * Return the current time point cast to the given precision. Only use this + * when an exact precision is needed, otherwise use T::clock::now() directly. + */ +template <typename T> +T Now() +{ + return std::chrono::time_point_cast<typename T::duration>(T::clock::now()); +} +/** DEPRECATED, see GetTime */ template <typename T> -T GetTime(); +T GetTime() +{ + return Now<std::chrono::time_point<NodeClock, T>>().time_since_epoch(); +} /** * ISO 8601 formatting is preferred. Use the FormatISO8601{DateTime,Date} diff --git a/src/util/tokenpipe.cpp b/src/util/tokenpipe.cpp index 4c091cd2e6..49456814e2 100644 --- a/src/util/tokenpipe.cpp +++ b/src/util/tokenpipe.cpp @@ -3,7 +3,9 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include <util/tokenpipe.h> +#if defined(HAVE_CONFIG_H) #include <config/bitcoin-config.h> +#endif #ifndef WIN32 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 214112e2bd..d276cea2f7 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> @@ -16,15 +19,13 @@ #include <consensus/tx_verify.h> #include <consensus/validation.h> #include <cuckoocache.h> -#include <deploymentstatus.h> #include <flatfile.h> +#include <fs.h> #include <hash.h> -#include <index/blockfilterindex.h> #include <logging.h> #include <logging/timer.h> #include <node/blockstorage.h> -#include <node/coinstats.h> -#include <node/ui_interface.h> +#include <node/interface_ui.h> #include <node/utxo_snapshot.h> #include <policy/policy.h> #include <policy/rbf.h> @@ -38,7 +39,6 @@ #include <script/sigcache.h> #include <shutdown.h> #include <signet.h> -#include <timedata.h> #include <tinyformat.h> #include <txdb.h> #include <txmempool.h> @@ -50,46 +50,41 @@ #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> #include <string> -#include <boost/algorithm/string/replace.hpp> +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; using node::CBlockIndexWorkComparator; -using node::CCoinsStats; -using node::CoinStatsHashType; -using node::GetUTXOStats; -using node::OpenBlockFile; +using node::fImporting; +using node::fPruneMode; +using node::fReindex; using node::ReadBlockFromDisk; using node::SnapshotMetadata; -using node::UNDOFILE_CHUNK_SIZE; using node::UndoReadFromDisk; using node::UnlinkPrunedFiles; -using node::fHavePruned; -using node::fImporting; -using node::fPruneMode; -using node::fReindex; -using node::nPruneTarget; #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. */ @@ -106,24 +101,12 @@ const std::vector<std::string> CHECKLEVEL_DOC { "level 4 tries to reconnect the blocks", "each level includes the checks of the previous levels", }; - -bool CBlockIndexWorkComparator::operator()(const CBlockIndex *pa, const CBlockIndex *pb) const { - // First sort by most total work, ... - if (pa->nChainWork > pb->nChainWork) return false; - if (pa->nChainWork < pb->nChainWork) return true; - - // ... then by earliest time received, ... - if (pa->nSequenceId < pb->nSequenceId) return false; - if (pa->nSequenceId > pb->nSequenceId) return true; - - // Use pointer address as tie breaker (should only happen with blocks - // loaded from disk, as those all have id 0). - if (pa < pb) return false; - if (pa > pb) return true; - - // Identical blocks. - return false; -} +/** The number of blocks to keep below the deepest prune lock. + * There is nothing special about this number. It is higher than what we + * expect to see in regular mainnet reorgs, but not so high that it would + * noticeably interfere with the pruning mechanism. + * */ +static constexpr int PRUNE_LOCK_BUFFER{10}; /** * Mutex to guard access to validation specific variables, such as reading @@ -137,12 +120,10 @@ bool CBlockIndexWorkComparator::operator()(const CBlockIndex *pa, const CBlockIn */ RecursiveMutex cs_main; -CBlockIndex *pindexBestHeader = nullptr; -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; @@ -150,16 +131,14 @@ int64_t nMaxTipAge = DEFAULT_MAX_TIP_AGE; uint256 hashAssumeValid; arith_uint256 nMinimumChainWork; -CFeeRate minRelayTxFee = CFeeRate(DEFAULT_MIN_RELAY_TX_FEE); - -CBlockIndex* CChainState::FindForkInGlobalIndex(const CBlockLocator& locator) const +const CBlockIndex* CChainState::FindForkInGlobalIndex(const CBlockLocator& locator) const { AssertLockHeld(cs_main); // Find the latest block common to locator and chain - we expect that // locator.vHave is sorted descending by height. for (const uint256& hash : locator.vHave) { - CBlockIndex* pindex{m_blockman.LookupBlockIndex(hash)}; + const CBlockIndex* pindex{m_blockman.LookupBlockIndex(hash)}; if (pindex) { if (m_chain.Contains(pindex)) { return pindex; @@ -178,43 +157,31 @@ bool CheckInputScripts(const CTransaction& tx, TxValidationState& state, std::vector<CScriptCheck>* pvChecks = nullptr) EXCLUSIVE_LOCKS_REQUIRED(cs_main); -bool CheckFinalTx(const CBlockIndex* active_chain_tip, const CTransaction &tx, int flags) +bool CheckFinalTxAtTip(const CBlockIndex& active_chain_tip, const CTransaction& tx) { AssertLockHeld(cs_main); - assert(active_chain_tip); // TODO: Make active_chain_tip a reference - // By convention a negative value for flags indicates that the - // current network-enforced consensus rules should be used. In - // a future soft-fork scenario that would mean checking which - // rules would be enforced for the next block and setting the - // appropriate flags. At the present time no soft-forks are - // scheduled, so no flags are set. - flags = std::max(flags, 0); - - // CheckFinalTx() uses active_chain_tip.Height()+1 to evaluate + // CheckFinalTxAtTip() uses active_chain_tip.Height()+1 to evaluate // nLockTime because when IsFinalTx() is called within // AcceptBlock(), the height of the block *being* // evaluated is what is used. Thus if we want to know if a // transaction can be part of the *next* block, we need to call // IsFinalTx() with one more than 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() if LOCKTIME_MEDIAN_TIME_PAST is set. - const int64_t nBlockTime = (flags & LOCKTIME_MEDIAN_TIME_PAST) - ? active_chain_tip->GetMedianTimePast() - : GetAdjustedTime(); + // IsFinalTx(). + const int64_t nBlockTime{active_chain_tip.GetMedianTimePast()}; return IsFinalTx(tx, nBlockHeight, nBlockTime); } -bool CheckSequenceLocks(CBlockIndex* tip, +bool CheckSequenceLocksAtTip(CBlockIndex* tip, const CCoinsView& coins_view, const CTransaction& tx, - int flags, LockPoints* lp, bool useExistingLockPoints) { @@ -222,7 +189,7 @@ bool CheckSequenceLocks(CBlockIndex* tip, CBlockIndex index; index.pprev = tip; - // CheckSequenceLocks() uses active_chainstate.m_chain.Height()+1 to evaluate + // CheckSequenceLocksAtTip() uses active_chainstate.m_chain.Height()+1 to evaluate // height based locks because when SequenceLocks() is called within // ConnectBlock(), the height of the block *being* // evaluated is what is used. @@ -252,7 +219,7 @@ bool CheckSequenceLocks(CBlockIndex* tip, prevheights[txinIndex] = coin.nHeight; } } - lockPair = CalculateSequenceLocks(tx, flags, prevheights, index); + lockPair = CalculateSequenceLocks(tx, STANDARD_LOCKTIME_VERIFY_FLAGS, prevheights, index); if (lp) { lp->height = lockPair.first; lp->time = lockPair.second; @@ -268,7 +235,7 @@ bool CheckSequenceLocks(CBlockIndex* tip, // lockPair from CalculateSequenceLocks against tip+1. We know // EvaluateSequenceLocks will fail if there was a non-zero sequence // lock on a mempool input, so we can use the return value of - // CheckSequenceLocks to indicate the LockPoints validity + // CheckSequenceLocksAtTip to indicate the LockPoints validity int maxInputHeight = 0; for (const int height : prevheights) { // Can ignore mempool inputs since we'll fail if they had non-zero locks @@ -276,27 +243,32 @@ bool CheckSequenceLocks(CBlockIndex* tip, maxInputHeight = std::max(maxInputHeight, height); } } - lp->maxInputBlock = tip->GetAncestor(maxInputHeight); + // tip->GetAncestor(maxInputHeight) should never return a nullptr + // because maxInputHeight is always less than the tip height. + // It would, however, be a bad bug to continue execution, since a + // LockPoints object with the maxInputBlock member set to nullptr + // signifies no relative lock time. + lp->maxInputBlock = Assert(tip->GetAncestor(maxInputHeight)); } } return EvaluateSequenceLocks(index, lockPair); } // Returns the script flags which should be checked for a given block -static unsigned int GetBlockScriptFlags(const CBlockIndex* pindex, const Consensus::Params& chainparams); +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); } @@ -308,8 +280,9 @@ static bool IsCurrentForFeeEstimation(CChainState& active_chainstate) EXCLUSIVE_ return false; if (active_chainstate.m_chain.Tip()->GetBlockTime() < count_seconds(GetTime<std::chrono::seconds>() - MAX_FEE_ESTIMATION_TIP_AGE)) return false; - if (active_chainstate.m_chain.Height() < pindexBestHeader->nHeight - 1) + if (active_chainstate.m_chain.Height() < active_chainstate.m_chainman.m_best_header->nHeight - 1) { return false; + } return true; } @@ -349,35 +322,33 @@ 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. // Also updates valid entries' cached LockPoints if needed. // If false, the tx is still valid and its lockpoints are updated. // If true, the tx would be invalid in the next block; remove this entry and all of its descendants. - const auto filter_final_and_mature = [this, flags=STANDARD_LOCKTIME_VERIFY_FLAGS](CTxMemPool::txiter it) + const auto filter_final_and_mature = [this](CTxMemPool::txiter it) EXCLUSIVE_LOCKS_REQUIRED(m_mempool->cs, ::cs_main) { AssertLockHeld(m_mempool->cs); AssertLockHeld(::cs_main); const CTransaction& tx = it->GetTx(); // The transaction must be final. - if (!CheckFinalTx(m_chain.Tip(), tx, flags)) 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); - // CheckSequenceLocks checks if the transaction will be final in the next block to be + // CheckSequenceLocksAtTip checks if the transaction will be final in the next block to be // created on top of the new chain. We use useExistingLockPoints=false so that, instead of // using the information in lp (which might now refer to a block that no longer exists in // the chain), it will update lp to contain LockPoints relevant to the new chain. - if (!CheckSequenceLocks(m_chain.Tip(), view_mempool, tx, flags, &lp, validLP)) { - // If CheckSequenceLocks fails, remove the tx and don't depend on the LockPoints. + if (!CheckSequenceLocksAtTip(m_chain.Tip(), view_mempool, tx, &lp, validLP)) { + // If CheckSequenceLocksAtTip fails, remove the tx and don't depend on the LockPoints. return true; } else if (!validLP) { - // If CheckSequenceLocks succeeded, it also updated the LockPoints. + // If CheckSequenceLocksAtTip succeeded, it also updated the LockPoints. // Now update the mempool entry lockpoints as well. m_mempool->mapTx.modify(it, [&lp](CTxMemPoolEntry& e) { e.UpdateLockPoints(lp); }); } @@ -403,11 +374,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()); } /** @@ -458,10 +425,10 @@ 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) { + m_limit_ancestors(m_pool.m_limits.ancestor_count), + m_limit_ancestor_size(m_pool.m_limits.ancestor_size_vbytes), + m_limit_descendants(m_pool.m_limits.descendant_count), + m_limit_descendant_size(m_pool.m_limits.descendant_size_vbytes) { } // We put the arguments we're handed into a struct, so we can pass them @@ -488,6 +455,10 @@ public: * partially submitted. */ const bool m_package_submission; + /** When true, use package feerates instead of individual transaction feerates for fee-based + * policies such as mempool min fee and min relay fee. + */ + const bool m_package_feerates; /** Parameters for single transaction mempool validation. */ static ATMPArgs SingleAccept(const CChainParams& chainparams, int64_t accept_time, @@ -500,6 +471,7 @@ public: /* m_test_accept */ test_accept, /* m_allow_bip125_replacement */ true, /* m_package_submission */ false, + /* m_package_feerates */ false, }; } @@ -513,6 +485,7 @@ public: /* m_test_accept */ true, /* m_allow_bip125_replacement */ false, /* m_package_submission */ false, // not submitting to mempool + /* m_package_feerates */ false, }; } @@ -526,11 +499,44 @@ public: /* m_test_accept */ false, /* m_allow_bip125_replacement */ false, /* m_package_submission */ true, + /* m_package_feerates */ true, }; } - // No default ctor to avoid exposing details to clients and allowing the possibility of + + /** Parameters for a single transaction within a package. */ + static ATMPArgs SingleInPackageAccept(const ATMPArgs& package_args) { + return ATMPArgs{/* m_chainparams */ package_args.m_chainparams, + /* m_accept_time */ package_args.m_accept_time, + /* 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_package_submission */ false, + /* m_package_feerates */ false, // only 1 transaction + }; + } + + private: + // Private ctor to avoid exposing details to clients and allowing the possibility of // mixing up the order of the arguments. Use static functions above instead. - ATMPArgs() = delete; + ATMPArgs(const CChainParams& chainparams, + int64_t accept_time, + bool bypass_limits, + std::vector<COutPoint>& coins_to_uncache, + bool test_accept, + bool allow_bip125_replacement, + bool package_submission, + bool package_feerates) + : m_chainparams{chainparams}, + m_accept_time{accept_time}, + 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_package_submission{package_submission}, + m_package_feerates{package_feerates} + { + } }; // Single transaction acceptance @@ -634,13 +640,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; } @@ -692,8 +699,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. @@ -705,8 +713,9 @@ 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 (!CheckFinalTx(m_active_chainstate.m_chain.Tip(), tx, STANDARD_LOCKTIME_VERIFY_FLAGS)) + if (!CheckFinalTxAtTip(*Assert(m_active_chainstate.m_chain.Tip()), tx)) { return state.Invalid(TxValidationResult::TX_PREMATURE_SPEND, "non-final"); + } if (m_pool.exists(GenTxid::Wtxid(tx.GetWitnessHash()))) { // Exact transaction already exists in the mempool. @@ -735,7 +744,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"); } @@ -786,22 +798,23 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws) // be mined yet. // Pass in m_view which has all of the relevant inputs cached. Note that, since m_view's // backend was removed, it no longer pulls coins from the mempool. - if (!CheckSequenceLocks(m_active_chainstate.m_chain.Tip(), m_view, tx, STANDARD_LOCKTIME_VERIFY_FLAGS, &lp)) + if (!CheckSequenceLocksAtTip(m_active_chainstate.m_chain.Tip(), m_view, tx, &lp)) { return state.Invalid(TxValidationResult::TX_PREMATURE_SPEND, "non-BIP68-final"); + } // The mempool holds txs for the next block, so pass height+1 to CheckTxInputs if (!Consensus::CheckTxInputs(tx, state, m_view, m_active_chainstate.m_chain.Height() + 1, ws.m_base_fees)) { return false; // state filled in by CheckTxInputs } - // Check for non-standard pay-to-script-hash in inputs - 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); @@ -828,9 +841,10 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws) return state.Invalid(TxValidationResult::TX_NOT_STANDARD, "bad-txns-too-many-sigops", strprintf("%d", nSigOpsCost)); - // No transactions are allowed below minRelayTxFee except from disconnected - // blocks - if (!bypass_limits && !CheckFeeRate(ws.m_vsize, ws.m_modified_fees, state)) return false; + // 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; ws.m_iters_conflicting = m_pool.GetIterSet(ws.m_conflicts); // Calculate in-mempool ancestors, up to a limit. @@ -945,7 +959,7 @@ bool MemPoolAccept::ReplacementChecks(Workspace& ws) 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; @@ -1005,7 +1019,6 @@ bool MemPoolAccept::ConsensusScriptChecks(const ATMPArgs& args, Workspace& ws) const CTransaction& tx = *ws.m_ptx; const uint256& hash = ws.m_hash; TxValidationState& state = ws.m_state; - const CChainParams& chainparams = args.m_chainparams; // Check again against the current block tip's script verification // flags to cache our script execution flags. This is, of course, @@ -1022,7 +1035,7 @@ bool MemPoolAccept::ConsensusScriptChecks(const ATMPArgs& args, Workspace& ws) // There is a similar check in CreateNewBlock() to prevent creating // invalid blocks (using TestBlockValidity), however allowing such // transactions into the mempool can be exploited as a DoS attack. - unsigned int currentBlockScriptVerifyFlags = GetBlockScriptFlags(m_active_chainstate.m_chain.Tip(), chainparams.GetConsensus()); + unsigned int currentBlockScriptVerifyFlags{GetBlockScriptFlags(*m_active_chainstate.m_chain.Tip(), m_active_chainstate.m_chainman)}; if (!CheckInputsFromMempoolAndCache(tx, state, m_view, m_pool, currentBlockScriptVerifyFlags, ws.m_precomputed_txdata, m_active_chainstate.CoinsTip())) { LogPrintf("BUG! PLEASE REPORT THIS! CheckInputScripts failed against latest-block but not STANDARD flags %s, %s\n", hash.ToString(), state.ToString()); @@ -1071,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"); } @@ -1098,6 +1111,7 @@ bool MemPoolAccept::SubmitPackage(const ATMPArgs& args, std::vector<Workspace>& if (!ConsensusScriptChecks(args, ws)) { results.emplace(ws.m_ptx->GetWitnessHash(), MempoolAcceptResult::Failure(ws.m_state)); // Since PolicyScriptChecks() passed, this should never fail. + Assume(false); all_submitted = false; package_state.Invalid(PackageValidationResult::PCKG_MEMPOOL_ERROR, strprintf("BUG! PolicyScriptChecks succeeded but ConsensusScriptChecks failed: %s", @@ -1112,6 +1126,7 @@ bool MemPoolAccept::SubmitPackage(const ATMPArgs& args, std::vector<Workspace>& m_limit_descendant_size, 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); all_submitted = false; package_state.Invalid(PackageValidationResult::PCKG_MEMPOOL_ERROR, strprintf("BUG! Mempool ancestors or descendants were underestimated: %s", @@ -1125,6 +1140,7 @@ bool MemPoolAccept::SubmitPackage(const ATMPArgs& args, std::vector<Workspace>& if (!Finalize(args, ws)) { results.emplace(ws.m_ptx->GetWitnessHash(), MempoolAcceptResult::Failure(ws.m_state)); // Since LimitMempoolSize() won't be called, this should never fail. + Assume(false); all_submitted = false; package_state.Invalid(PackageValidationResult::PCKG_MEMPOOL_ERROR, strprintf("BUG! Adding to mempool failed: %s", ws.m_ptx->GetHash().ToString())); @@ -1133,9 +1149,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. @@ -1214,12 +1228,27 @@ PackageMempoolAcceptResult MemPoolAccept::AcceptMultipleTransactions(const std:: m_viewmempool.PackageAddTransaction(ws.m_ptx); } + // Transactions must meet two minimum feerates: the mempool minimum fee and min relay fee. + // For transactions consisting of exactly one child and its parents, it suffices to use the + // package feerate (total modified fees / total virtual size) to check this requirement. + const auto m_total_vsize = std::accumulate(workspaces.cbegin(), workspaces.cend(), int64_t{0}, + [](int64_t sum, auto& ws) { return sum + ws.m_vsize; }); + const auto m_total_modified_fees = std::accumulate(workspaces.cbegin(), workspaces.cend(), CAmount{0}, + [](CAmount sum, auto& ws) { return sum + ws.m_modified_fees; }); + const CFeeRate package_feerate(m_total_modified_fees, m_total_vsize); + TxValidationState placeholder_state; + if (args.m_package_feerates && + !CheckFeeRate(m_total_vsize, m_total_modified_fees, placeholder_state)) { + package_state.Invalid(PackageValidationResult::PCKG_POLICY, "package-fee-too-low"); + return PackageMempoolAcceptResult(package_state, package_feerate, {}); + } + // Apply package mempool ancestor/descendant limits. Skip if there is only one transaction, // because it's unnecessary. Also, CPFP carve out can increase the limit for individual // transactions, but this exemption is not extended to packages in CheckPackageLimits(). std::string err_string; if (txns.size() > 1 && !PackageMempoolChecks(txns, package_state)) { - return PackageMempoolAcceptResult(package_state, std::move(results)); + return PackageMempoolAcceptResult(package_state, package_feerate, std::move(results)); } for (Workspace& ws : workspaces) { @@ -1227,7 +1256,7 @@ PackageMempoolAcceptResult MemPoolAccept::AcceptMultipleTransactions(const std:: // Exit early to avoid doing pointless work. Update the failed tx result; the rest are unfinished. package_state.Invalid(PackageValidationResult::PCKG_TX, "transaction failed"); results.emplace(ws.m_ptx->GetWitnessHash(), MempoolAcceptResult::Failure(ws.m_state)); - return PackageMempoolAcceptResult(package_state, std::move(results)); + return PackageMempoolAcceptResult(package_state, package_feerate, std::move(results)); } if (args.m_test_accept) { // When test_accept=true, transactions that pass PolicyScriptChecks are valid because there are @@ -1238,14 +1267,14 @@ PackageMempoolAcceptResult MemPoolAccept::AcceptMultipleTransactions(const std:: } } - if (args.m_test_accept) return PackageMempoolAcceptResult(package_state, std::move(results)); + if (args.m_test_accept) return PackageMempoolAcceptResult(package_state, package_feerate, std::move(results)); if (!SubmitPackage(args, workspaces, package_state, results)) { // PackageValidationState filled in by SubmitPackage(). - return PackageMempoolAcceptResult(package_state, std::move(results)); + return PackageMempoolAcceptResult(package_state, package_feerate, std::move(results)); } - return PackageMempoolAcceptResult(package_state, std::move(results)); + return PackageMempoolAcceptResult(package_state, package_feerate, std::move(results)); } PackageMempoolAcceptResult MemPoolAccept::AcceptPackage(const Package& package, ATMPArgs& args) @@ -1312,6 +1341,8 @@ PackageMempoolAcceptResult MemPoolAccept::AcceptPackage(const Package& package, // transactions that are already in the mempool, and only call AcceptMultipleTransactions() with // the new transactions. This ensures we don't double-count transaction counts and sizes when // checking ancestor/descendant limits, or double-count transaction fees for fee-related policy. + ATMPArgs single_args = ATMPArgs::SingleInPackageAccept(args); + bool quit_early{false}; std::vector<CTransactionRef> txns_new; for (const auto& tx : package) { const auto& wtxid = tx->GetWitnessHash(); @@ -1338,18 +1369,43 @@ PackageMempoolAcceptResult MemPoolAccept::AcceptPackage(const Package& package, results.emplace(wtxid, MempoolAcceptResult::MempoolTxDifferentWitness(iter.value()->GetTx().GetWitnessHash())); } else { // Transaction does not already exist in the mempool. - txns_new.push_back(tx); + // Try submitting the transaction on its own. + const auto single_res = AcceptSingleTransaction(tx, single_args); + if (single_res.m_result_type == MempoolAcceptResult::ResultType::VALID) { + // The transaction succeeded on its own and is now in the mempool. Don't include it + // in package validation, because its fees should only be "used" once. + assert(m_pool.exists(GenTxid::Wtxid(wtxid))); + results.emplace(wtxid, single_res); + } else if (single_res.m_state.GetResult() != TxValidationResult::TX_MEMPOOL_POLICY && + single_res.m_state.GetResult() != TxValidationResult::TX_MISSING_INPUTS) { + // Package validation policy only differs from individual policy in its evaluation + // of feerate. For example, if a transaction fails here due to violation of a + // consensus rule, the result will not change when it is submitted as part of a + // package. To minimize the amount of repeated work, unless the transaction fails + // due to feerate or missing inputs (its parent is a previous transaction in the + // package that failed due to feerate), don't run package validation. Note that this + // decision might not make sense if different types of packages are allowed in the + // future. Continue individually validating the rest of the transactions, because + // some of them may still be valid. + quit_early = true; + } else { + txns_new.push_back(tx); + } } } // Nothing to do if the entire package has already been submitted. - if (txns_new.empty()) return PackageMempoolAcceptResult(package_state, std::move(results)); + if (quit_early || txns_new.empty()) { + // No package feerate when no package validation was done. + return PackageMempoolAcceptResult(package_state, std::move(results)); + } // Validate the (deduplicated) transactions as a package. auto submission_result = AcceptMultipleTransactions(txns_new, args); // Include already-in-mempool transaction results in the final result. for (const auto& [wtxid, mempoolaccept_res] : results) { submission_result.m_tx_results.emplace(wtxid, mempoolaccept_res); } + if (submission_result.m_state.IsValid()) assert(submission_result.m_package_feerate.has_value()); return submission_result; } @@ -1390,7 +1446,7 @@ PackageMempoolAcceptResult ProcessNewPackage(CChainState& active_chainstate, CTx assert(std::all_of(package.cbegin(), package.cend(), [](const auto& tx){return tx != nullptr;})); std::vector<COutPoint> coins_to_uncache; - const CChainParams& chainparams = Params(); + const CChainParams& chainparams = active_chainstate.m_params; const auto result = [&]() EXCLUSIVE_LOCKS_REQUIRED(cs_main) { AssertLockHeld(cs_main); if (test_accept) { @@ -1428,7 +1484,7 @@ CAmount GetBlockSubsidy(int nHeight, const Consensus::Params& consensusParams) } CoinsViews::CoinsViews( - std::string ldb_name, + fs::path ldb_name, size_t cache_size_bytes, bool in_memory, bool should_wipe) : m_dbview( @@ -1448,7 +1504,7 @@ CChainState::CChainState( std::optional<uint256> from_snapshot_blockhash) : m_mempool(mempool), m_blockman(blockman), - m_params(::Params()), + m_params(chainman.GetParams()), m_chainman(chainman), m_from_snapshot_blockhash(from_snapshot_blockhash) {} @@ -1456,7 +1512,7 @@ void CChainState::InitCoinsDB( size_t cache_size_bytes, bool in_memory, bool should_wipe, - std::string leveldb_name) + fs::path leveldb_name) { if (m_from_snapshot_blockhash) { leveldb_name += "_" + m_from_snapshot_blockhash->ToString(); @@ -1514,7 +1570,7 @@ static void AlertNotify(const std::string& strMessage) std::string singleQuote("'"); std::string safeStatus = SanitizeString(strMessage); safeStatus = singleQuote+safeStatus+singleQuote; - boost::replace_all(strCmd, "%s", safeStatus); + ReplaceAll(strCmd, "%s", safeStatus); std::thread t(runCommand, strCmd); t.detach(); // thread runs free @@ -1546,8 +1602,8 @@ void CChainState::InvalidChainFound(CBlockIndex* pindexNew) if (!m_chainman.m_best_invalid || pindexNew->nChainWork > m_chainman.m_best_invalid->nChainWork) { m_chainman.m_best_invalid = pindexNew; } - if (pindexBestHeader != nullptr && pindexBestHeader->GetAncestor(pindexNew->nHeight) == pindexNew) { - pindexBestHeader = m_chain.Tip(); + if (m_chainman.m_best_header != nullptr && m_chainman.m_best_header->GetAncestor(pindexNew->nHeight) == pindexNew) { + m_chainman.m_best_header = m_chain.Tip(); } LogPrintf("%s: invalid block=%s height=%d log2_work=%f date=%s\n", __func__, @@ -1599,7 +1655,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 @@ -1607,12 +1664,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; } /** @@ -1842,10 +1901,11 @@ void StopScriptCheckWorkerThreads() class WarningBitsConditionChecker : public AbstractThresholdConditionChecker { private: - int bit; + const ChainstateManager& m_chainman; + int m_bit; public: - explicit WarningBitsConditionChecker(int bitIn) : bit(bitIn) {} + explicit WarningBitsConditionChecker(const ChainstateManager& chainman, int bit) : m_chainman{chainman}, m_bit(bit) {} int64_t BeginTime(const Consensus::Params& params) const override { return 0; } int64_t EndTime(const Consensus::Params& params) const override { return std::numeric_limits<int64_t>::max(); } @@ -1856,52 +1916,48 @@ public: { return pindex->nHeight >= params.MinBIP9WarningHeight && ((pindex->nVersion & VERSIONBITS_TOP_MASK) == VERSIONBITS_TOP_BITS) && - ((pindex->nVersion >> bit) & 1) != 0 && - ((g_versionbitscache.ComputeBlockVersion(pindex->pprev, params) >> bit) & 1) == 0; + ((pindex->nVersion >> m_bit) & 1) != 0 && + ((m_chainman.m_versionbitscache.ComputeBlockVersion(pindex->pprev, params) >> m_bit) & 1) == 0; } }; -static ThresholdConditionCache warningcache[VERSIONBITS_NUM_BITS] GUARDED_BY(cs_main); +static std::array<ThresholdConditionCache, VERSIONBITS_NUM_BITS> warningcache GUARDED_BY(cs_main); -static unsigned int GetBlockScriptFlags(const CBlockIndex* pindex, const Consensus::Params& consensusparams) +static unsigned int GetBlockScriptFlags(const CBlockIndex& block_index, const ChainstateManager& chainman) { - unsigned int flags = SCRIPT_VERIFY_NONE; + const Consensus::Params& consensusparams = chainman.GetConsensus(); // BIP16 didn't become active until Apr 1 2012 (on mainnet, and // retroactively applied to testnet) // However, only one historical block violated the P2SH rules (on both - // mainnet and testnet), so for simplicity, always leave P2SH - // on except for the one violating block. - if (consensusparams.BIP16Exception.IsNull() || // no bip16 exception on this chain - pindex->phashBlock == nullptr || // this is a new candidate block, eg from TestBlockValidity() - *pindex->phashBlock != consensusparams.BIP16Exception) // this block isn't the historical exception - { - // Enforce WITNESS rules whenever P2SH is in effect - flags |= SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_WITNESS; + // mainnet and testnet). + // Similarly, only one historical block violated the TAPROOT rules on + // mainnet. + // For simplicity, always leave P2SH+WITNESS+TAPROOT on except for the two + // violating blocks. + uint32_t flags{SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_WITNESS | SCRIPT_VERIFY_TAPROOT}; + const auto it{consensusparams.script_flag_exceptions.find(*Assert(block_index.phashBlock))}; + if (it != consensusparams.script_flag_exceptions.end()) { + flags = it->second; } // Enforce the DERSIG (BIP66) rule - if (DeploymentActiveAt(*pindex, consensusparams, Consensus::DEPLOYMENT_DERSIG)) { + if (DeploymentActiveAt(block_index, chainman, Consensus::DEPLOYMENT_DERSIG)) { flags |= SCRIPT_VERIFY_DERSIG; } // Enforce CHECKLOCKTIMEVERIFY (BIP65) - if (DeploymentActiveAt(*pindex, consensusparams, Consensus::DEPLOYMENT_CLTV)) { + if (DeploymentActiveAt(block_index, chainman, Consensus::DEPLOYMENT_CLTV)) { flags |= SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY; } // Enforce CHECKSEQUENCEVERIFY (BIP112) - if (DeploymentActiveAt(*pindex, consensusparams, Consensus::DEPLOYMENT_CSV)) { + if (DeploymentActiveAt(block_index, chainman, Consensus::DEPLOYMENT_CSV)) { flags |= SCRIPT_VERIFY_CHECKSEQUENCEVERIFY; } - // Enforce Taproot (BIP340-BIP342) - if (DeploymentActiveAt(*pindex, consensusparams, Consensus::DEPLOYMENT_TAPROOT)) { - flags |= SCRIPT_VERIFY_TAPROOT; - } - // Enforce BIP147 NULLDUMMY (activated simultaneously with segwit) - if (DeploymentActiveAt(*pindex, consensusparams, Consensus::DEPLOYMENT_SEGWIT)) { + if (DeploymentActiveAt(block_index, chainman, Consensus::DEPLOYMENT_SEGWIT)) { flags |= SCRIPT_VERIFY_NULLDUMMY; } @@ -1909,11 +1965,11 @@ static unsigned int GetBlockScriptFlags(const CBlockIndex* pindex, const Consens } - static int64_t nTimeCheck = 0; static int64_t nTimeForks = 0; -static int64_t nTimeVerify = 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; @@ -1944,7 +2000,7 @@ bool CChainState::ConnectBlock(const CBlock& block, BlockValidationState& state, // Also, currently the rule against blocks more than 2 hours in the future // is enforced in ContextualCheckBlockHeader(); we wouldn't want to // re-enforce that rule here (at least until we make it impossible for - // GetAdjustedTime() to go backward). + // m_adjusted_time_callback() to go backward). if (!CheckBlock(block, state, m_params.GetConsensus(), !fJustCheck, !fJustCheck)) { if (state.GetResult() == BlockValidationResult::BLOCK_MUTATED) { // We don't write down blocks to disk if they may have been @@ -1978,9 +2034,9 @@ bool CChainState::ConnectBlock(const CBlock& block, BlockValidationState& state, // effectively caching the result of part of the verification. BlockMap::const_iterator it = m_blockman.m_block_index.find(hashAssumeValid); if (it != m_blockman.m_block_index.end()) { - if (it->second->GetAncestor(pindex->nHeight) == pindex && - pindexBestHeader->GetAncestor(pindex->nHeight) == pindex && - pindexBestHeader->nChainWork >= nMinimumChainWork) { + if (it->second.GetAncestor(pindex->nHeight) == pindex && + m_chainman.m_best_header->GetAncestor(pindex->nHeight) == pindex && + m_chainman.m_best_header->nChainWork >= nMinimumChainWork) { // This block is a member of the assumed verified chain and an ancestor of the best header. // Script verification is skipped when connecting blocks under the // assumevalid block. Assuming the assumevalid block is valid this @@ -1995,7 +2051,7 @@ bool CChainState::ConnectBlock(const CBlock& block, BlockValidationState& state, // artificially set the default assumed verified block further back. // The test against nMinimumChainWork prevents the skipping when denied access to any chain at // least as good as the expected chain. - fScriptChecks = (GetBlockProofEquivalentTime(*pindexBestHeader, *pindex, *pindexBestHeader, m_params.GetConsensus()) <= 60 * 60 * 24 * 7 * 2); + fScriptChecks = (GetBlockProofEquivalentTime(*m_chainman.m_best_header, *pindex, *m_chainman.m_best_header, m_params.GetConsensus()) <= 60 * 60 * 24 * 7 * 2); } } } @@ -2092,12 +2148,12 @@ bool CChainState::ConnectBlock(const CBlock& block, BlockValidationState& state, // Enforce BIP68 (sequence locks) int nLockTimeFlags = 0; - if (DeploymentActiveAt(*pindex, m_params.GetConsensus(), Consensus::DEPLOYMENT_CSV)) { + if (DeploymentActiveAt(*pindex, m_chainman, Consensus::DEPLOYMENT_CSV)) { nLockTimeFlags |= LOCKTIME_VERIFY_SEQUENCE; } // Get the script flags for this block - unsigned int flags = GetBlockScriptFlags(pindex, m_params.GetConsensus()); + 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); @@ -2207,17 +2263,19 @@ 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); + 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 nTime5 = GetTimeMicros(); nTimeIndex += nTime5 - nTime4; - LogPrint(BCLog::BENCH, " - Index writing: %.2fms [%.2fs (%.2fms/blk)]\n", MILLI * (nTime5 - nTime4), nTimeIndex * MICRO, nTimeIndex * MILLI / nBlocksTotal); + 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); TRACE6(validation, block_connected, block_hash.data(), @@ -2236,7 +2294,7 @@ CoinsCacheSizeState CChainState::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( @@ -2286,12 +2344,24 @@ bool CChainState::FlushStateToDisk( CoinsCacheSizeState cache_state = GetCoinsCacheSizeState(); LOCK(m_blockman.cs_LastBlockFile); if (fPruneMode && (m_blockman.m_check_for_pruning || nManualPruneHeight > 0) && !fReindex) { - // make sure we don't prune above the blockfilterindexes bestblocks + // make sure we don't prune above any of the prune locks bestblocks // pruning is height-based - int last_prune = m_chain.Height(); // last height we can prune - ForEachBlockFilterIndex([&](BlockFilterIndex& index) { - last_prune = std::max(1, std::min(last_prune, index.GetSummary().best_block_height)); - }); + int last_prune{m_chain.Height()}; // last height we can prune + std::optional<std::string> limiting_lock; // prune lock that actually was the limiting factor, only used for logging + + for (const auto& prune_lock : m_blockman.m_prune_locks) { + if (prune_lock.second.height_first == std::numeric_limits<int>::max()) continue; + // Remove the buffer and one additional block here to get actual height that is outside of the buffer + const int lock_height{prune_lock.second.height_first - PRUNE_LOCK_BUFFER - 1}; + last_prune = std::max(1, std::min(last_prune, lock_height)); + if (last_prune == lock_height) { + limiting_lock = prune_lock.first; + } + } + + if (limiting_lock) { + LogPrint(BCLog::PRUNE, "%s limited pruning to height %d\n", limiting_lock.value(), last_prune); + } if (nManualPruneHeight > 0) { LOG_TIME_MILLIS_WITH_CATEGORY("find files to prune (manual)", BCLog::BENCH); @@ -2305,9 +2375,9 @@ bool CChainState::FlushStateToDisk( } if (!setFilesToPrune.empty()) { fFlushForPrune = true; - if (!fHavePruned) { + if (!m_blockman.m_have_pruned) { m_blockman.m_block_tree_db->WriteFlag("prunedblockfiles", true); - fHavePruned = true; + m_blockman.m_have_pruned = true; } } } @@ -2378,9 +2448,9 @@ bool CChainState::FlushStateToDisk( full_flush_completed = true; TRACE5(utxocache, flush, (int64_t)(GetTimeMicros() - nNow.count()), // in microseconds (µs) - (u_int32_t)mode, - (u_int64_t)coins_count, - (u_int64_t)coins_mem_usage, + (uint32_t)mode, + (uint64_t)coins_count, + (uint64_t)coins_mem_usage, (bool)fFlushForPrune); } } @@ -2480,8 +2550,8 @@ void CChainState::UpdateTip(const CBlockIndex* pindexNew) if (!this->IsInitialBlockDownload()) { const CBlockIndex* pindex = pindexNew; for (int bit = 0; bit < VERSIONBITS_NUM_BITS; bit++) { - WarningBitsConditionChecker checker(bit); - ThresholdState state = checker.GetStateFor(pindex, m_params.GetConsensus(), warningcache[bit]); + WarningBitsConditionChecker checker(m_chainman, bit); + ThresholdState state = checker.GetStateFor(pindex, m_params.GetConsensus(), warningcache.at(bit)); if (state == ThresholdState::ACTIVE || state == ThresholdState::LOCKED_IN) { const bilingual_str warning = strprintf(_("Unknown new rules activated (versionbit %i)"), bit); if (state == ThresholdState::ACTIVE) { @@ -2512,6 +2582,7 @@ bool CChainState::DisconnectTip(BlockValidationState& state, DisconnectedBlockTr 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; @@ -2529,6 +2600,18 @@ bool CChainState::DisconnectTip(BlockValidationState& state, DisconnectedBlockTr assert(flushed); } LogPrint(BCLog::BENCH, "- Disconnect block: %.2fms\n", (GetTimeMicros() - nStart) * MILLI); + + { + // Prune locks that began at or after the tip should be moved backward so they get a chance to reorg + const int max_height_first{pindexDelete->nHeight - 1}; + for (auto& prune_lock : m_blockman.m_prune_locks) { + if (prune_lock.second.height_first <= max_height_first) continue; + + prune_lock.second.height_first = max_height_first; + LogPrint(BCLog::PRUNE, "%s prune lock moved back to %d\n", prune_lock.first, max_height_first); + } + } + // Write the chain state to disk, if necessary. if (!FlushStateToDisk(state, FlushStateMode::IF_NEEDED)) { return false; @@ -2547,7 +2630,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 @@ -2556,7 +2639,7 @@ bool CChainState::DisconnectTip(BlockValidationState& state, DisconnectedBlockTr return true; } -static int64_t nTimeReadFromDisk = 0; +static int64_t nTimeReadFromDiskTotal = 0; static int64_t nTimeConnectTotal = 0; static int64_t nTimeFlush = 0; static int64_t nTimeChainState = 0; @@ -2565,7 +2648,7 @@ static int64_t nTimePostConnect = 0; struct PerBlockConnectTrace { CBlockIndex* pindex = nullptr; std::shared_ptr<const CBlock> pblock; - PerBlockConnectTrace() {} + PerBlockConnectTrace() = default; }; /** * Used to track blocks whose transactions were applied to the UTXO state as a @@ -2624,13 +2707,14 @@ bool CChainState::ConnectTip(BlockValidationState& state, CBlockIndex* pindexNew } pthisBlock = pblockNew; } else { + LogPrint(BCLog::BENCH, " - Using cached block\n"); pthisBlock = pblock; } const CBlock& blockConnecting = *pthisBlock; // Apply the block atomically to the chain state. - int64_t nTime2 = GetTimeMicros(); nTimeReadFromDisk += nTime2 - nTime1; + int64_t nTime2 = GetTimeMicros(); nTimeReadFromDiskTotal += nTime2 - nTime1; int64_t nTime3; - LogPrint(BCLog::BENCH, " - Load block from disk: %.2fms [%.2fs]\n", (nTime2 - nTime1) * MILLI, nTimeReadFromDisk * MICRO); + LogPrint(BCLog::BENCH, " - Load block from disk: %.2fms [%.2fs (%.2fms/blk)]\n", (nTime2 - nTime1) * MILLI, nTimeReadFromDiskTotal * MICRO, nTimeReadFromDiskTotal * MILLI / nBlocksTotal); { CCoinsViewCache view(&CoinsTip()); bool rv = ConnectBlock(blockConnecting, state, pindexNew, view); @@ -2660,7 +2744,7 @@ bool CChainState::ConnectTip(BlockValidationState& state, CBlockIndex* pindexNew 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; @@ -2850,7 +2934,7 @@ static bool NotifyHeaderTip(CChainState& chainstate) LOCKS_EXCLUDED(cs_main) { CBlockIndex* pindexHeader = nullptr; { LOCK(cs_main); - pindexHeader = pindexBestHeader; + pindexHeader = chainstate.m_chainman.m_best_header; if (pindexHeader != pindexHeaderOld) { fNotify = true; @@ -3035,8 +3119,8 @@ bool CChainState::InvalidateBlock(BlockValidationState& state, CBlockIndex* pind { LOCK(cs_main); - for (const auto& entry : m_blockman.m_block_index) { - CBlockIndex *candidate = entry.second; + for (auto& entry : m_blockman.m_block_index) { + CBlockIndex* candidate = &entry.second; // We don't need to put anything in our active chain into the // multimap, because those candidates will be found and considered // as we disconnect. @@ -3133,12 +3217,10 @@ bool CChainState::InvalidateBlock(BlockValidationState& state, CBlockIndex* pind // it up here, this should be an essentially unobservable error. // Loop back over all block index entries and add any missing entries // to setBlockIndexCandidates. - BlockMap::iterator it = m_blockman.m_block_index.begin(); - while (it != m_blockman.m_block_index.end()) { - if (it->second->IsValid(BLOCK_VALID_TRANSACTIONS) && it->second->HaveTxsDownloaded() && !setBlockIndexCandidates.value_comp()(it->second, m_chain.Tip())) { - setBlockIndexCandidates.insert(it->second); + for (auto& [_, block_index] : m_blockman.m_block_index) { + if (block_index.IsValid(BLOCK_VALID_TRANSACTIONS) && block_index.HaveTxsDownloaded() && !setBlockIndexCandidates.value_comp()(&block_index, m_chain.Tip())) { + setBlockIndexCandidates.insert(&block_index); } - it++; } InvalidChainFound(to_mark_failed); @@ -3157,21 +3239,19 @@ void CChainState::ResetBlockFailureFlags(CBlockIndex *pindex) { int nHeight = pindex->nHeight; // Remove the invalidity flag from this block and all its descendants. - BlockMap::iterator it = m_blockman.m_block_index.begin(); - while (it != m_blockman.m_block_index.end()) { - if (!it->second->IsValid() && it->second->GetAncestor(nHeight) == pindex) { - it->second->nStatus &= ~BLOCK_FAILED_MASK; - m_blockman.m_dirty_blockindex.insert(it->second); - if (it->second->IsValid(BLOCK_VALID_TRANSACTIONS) && it->second->HaveTxsDownloaded() && setBlockIndexCandidates.value_comp()(m_chain.Tip(), it->second)) { - setBlockIndexCandidates.insert(it->second); + for (auto& [_, block_index] : m_blockman.m_block_index) { + if (!block_index.IsValid() && block_index.GetAncestor(nHeight) == pindex) { + block_index.nStatus &= ~BLOCK_FAILED_MASK; + m_blockman.m_dirty_blockindex.insert(&block_index); + if (block_index.IsValid(BLOCK_VALID_TRANSACTIONS) && block_index.HaveTxsDownloaded() && setBlockIndexCandidates.value_comp()(m_chain.Tip(), &block_index)) { + setBlockIndexCandidates.insert(&block_index); } - if (it->second == m_chainman.m_best_invalid) { + if (&block_index == m_chainman.m_best_invalid) { // Reset invalid block marker if it was pointing to one of those. m_chainman.m_best_invalid = nullptr; } - m_chainman.m_failed_blocks.erase(it->second); + m_chainman.m_failed_blocks.erase(&block_index); } - it++; } // Remove the invalidity flag from all ancestors too. @@ -3195,7 +3275,7 @@ void CChainState::ReceivedBlockTransactions(const CBlock& block, CBlockIndex* pi pindexNew->nDataPos = pos.nPos; pindexNew->nUndoPos = 0; pindexNew->nStatus |= BLOCK_HAVE_DATA; - if (DeploymentActiveAt(*pindexNew, m_params.GetConsensus(), Consensus::DEPLOYMENT_SEGWIT)) { + if (DeploymentActiveAt(*pindexNew, m_chainman, Consensus::DEPLOYMENT_SEGWIT)) { pindexNew->nStatus |= BLOCK_OPT_WITNESS; } pindexNew->RaiseValidity(BLOCK_VALID_TRANSACTIONS); @@ -3313,11 +3393,11 @@ bool CheckBlock(const CBlock& block, BlockValidationState& state, const Consensu return true; } -void UpdateUncommittedBlockStructures(CBlock& block, const CBlockIndex* pindexPrev, const Consensus::Params& consensusParams) +void ChainstateManager::UpdateUncommittedBlockStructures(CBlock& block, const CBlockIndex* pindexPrev) const { int commitpos = GetWitnessCommitmentIndex(block); static const std::vector<unsigned char> nonce(32, 0x00); - if (commitpos != NO_WITNESS_COMMITMENT && DeploymentActiveAfter(pindexPrev, consensusParams, Consensus::DEPLOYMENT_SEGWIT) && !block.vtx[0]->HasWitness()) { + if (commitpos != NO_WITNESS_COMMITMENT && DeploymentActiveAfter(pindexPrev, *this, Consensus::DEPLOYMENT_SEGWIT) && !block.vtx[0]->HasWitness()) { CMutableTransaction tx(*block.vtx[0]); tx.vin[0].scriptWitness.stack.resize(1); tx.vin[0].scriptWitness.stack[0] = nonce; @@ -3325,7 +3405,7 @@ void UpdateUncommittedBlockStructures(CBlock& block, const CBlockIndex* pindexPr } } -std::vector<unsigned char> GenerateCoinbaseCommitment(CBlock& block, const CBlockIndex* pindexPrev, const Consensus::Params& consensusParams) +std::vector<unsigned char> ChainstateManager::GenerateCoinbaseCommitment(CBlock& block, const CBlockIndex* pindexPrev) const { std::vector<unsigned char> commitment; int commitpos = GetWitnessCommitmentIndex(block); @@ -3348,7 +3428,7 @@ std::vector<unsigned char> GenerateCoinbaseCommitment(CBlock& block, const CBloc tx.vout.push_back(out); block.vtx[0] = MakeTransactionRef(std::move(tx)); } - UpdateUncommittedBlockStructures(block, pindexPrev, consensusParams); + UpdateUncommittedBlockStructures(block, pindexPrev); return commitment; } @@ -3361,14 +3441,14 @@ std::vector<unsigned char> GenerateCoinbaseCommitment(CBlock& block, const CBloc * in ConnectBlock(). * Note that -reindex-chainstate skips the validation that happens here! */ -static bool ContextualCheckBlockHeader(const CBlockHeader& block, BlockValidationState& state, BlockManager& blockman, const CChainParams& params, 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, int64_t nAdjustedTime) EXCLUSIVE_LOCKS_REQUIRED(::cs_main) { AssertLockHeld(::cs_main); assert(pindexPrev != nullptr); const int nHeight = pindexPrev->nHeight + 1; // Check proof of work - const Consensus::Params& consensusParams = params.GetConsensus(); + const Consensus::Params& consensusParams = chainman.GetConsensus(); if (block.nBits != GetNextWorkRequired(pindexPrev, &block, consensusParams)) return state.Invalid(BlockValidationResult::BLOCK_INVALID_HEADER, "bad-diffbits", "incorrect proof of work"); @@ -3377,7 +3457,7 @@ static bool ContextualCheckBlockHeader(const CBlockHeader& block, BlockValidatio // Don't accept any forks from the main chain prior to last checkpoint. // GetLastCheckpoint finds the last checkpoint in MapCheckpoints that's in our // BlockIndex(). - CBlockIndex* pcheckpoint = blockman.GetLastCheckpoint(params.Checkpoints()); + const CBlockIndex* pcheckpoint = blockman.GetLastCheckpoint(chainman.GetParams().Checkpoints()); if (pcheckpoint && nHeight < pcheckpoint->nHeight) { LogPrintf("ERROR: %s: forked chain older than last checkpoint (height %d)\n", __func__, nHeight); return state.Invalid(BlockValidationResult::BLOCK_CHECKPOINT, "bad-fork-prior-to-checkpoint"); @@ -3393,9 +3473,9 @@ static bool ContextualCheckBlockHeader(const CBlockHeader& block, BlockValidatio 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, consensusParams, Consensus::DEPLOYMENT_HEIGHTINCB)) || - (block.nVersion < 3 && DeploymentActiveAfter(pindexPrev, consensusParams, Consensus::DEPLOYMENT_DERSIG)) || - (block.nVersion < 4 && DeploymentActiveAfter(pindexPrev, consensusParams, Consensus::DEPLOYMENT_CLTV))) { + if ((block.nVersion < 2 && DeploymentActiveAfter(pindexPrev, chainman, Consensus::DEPLOYMENT_HEIGHTINCB)) || + (block.nVersion < 3 && DeploymentActiveAfter(pindexPrev, chainman, Consensus::DEPLOYMENT_DERSIG)) || + (block.nVersion < 4 && DeploymentActiveAfter(pindexPrev, chainman, Consensus::DEPLOYMENT_CLTV))) { return state.Invalid(BlockValidationResult::BLOCK_INVALID_HEADER, strprintf("bad-version(0x%08x)", block.nVersion), strprintf("rejected nVersion=0x%08x block", block.nVersion)); } @@ -3409,20 +3489,20 @@ static bool ContextualCheckBlockHeader(const CBlockHeader& block, BlockValidatio * in ConnectBlock(). * Note that -reindex-chainstate skips the validation that happens here! */ -static bool ContextualCheckBlock(const CBlock& block, BlockValidationState& state, const Consensus::Params& consensusParams, const CBlockIndex* pindexPrev) +static bool ContextualCheckBlock(const CBlock& block, BlockValidationState& state, const ChainstateManager& chainman, const CBlockIndex* pindexPrev) { const int nHeight = pindexPrev == nullptr ? 0 : pindexPrev->nHeight + 1; // Enforce BIP113 (Median Time Past). - int nLockTimeFlags = 0; - if (DeploymentActiveAfter(pindexPrev, consensusParams, Consensus::DEPLOYMENT_CSV)) { + 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) { @@ -3432,7 +3512,7 @@ static bool ContextualCheckBlock(const CBlock& block, BlockValidationState& stat } // Enforce rule that the coinbase starts with serialized block height - if (DeploymentActiveAfter(pindexPrev, consensusParams, Consensus::DEPLOYMENT_HEIGHTINCB)) + if (DeploymentActiveAfter(pindexPrev, chainman, Consensus::DEPLOYMENT_HEIGHTINCB)) { CScript expect = CScript() << nHeight; if (block.vtx[0]->vin[0].scriptSig.size() < expect.size() || @@ -3450,7 +3530,7 @@ static bool ContextualCheckBlock(const CBlock& block, BlockValidationState& stat // {0xaa, 0x21, 0xa9, 0xed}, and the following 32 bytes are SHA256^2(witness root, witness reserved value). In case there are // multiple, the last one is used. bool fHaveWitness = false; - if (DeploymentActiveAfter(pindexPrev, consensusParams, Consensus::DEPLOYMENT_SEGWIT)) { + if (DeploymentActiveAfter(pindexPrev, chainman, Consensus::DEPLOYMENT_SEGWIT)) { int commitpos = GetWitnessCommitmentIndex(block); if (commitpos != NO_WITNESS_COMMITMENT) { bool malleated = false; @@ -3491,16 +3571,16 @@ static bool ContextualCheckBlock(const CBlock& block, BlockValidationState& stat return true; } -bool ChainstateManager::AcceptBlockHeader(const CBlockHeader& block, BlockValidationState& state, const CChainParams& chainparams, CBlockIndex** ppindex) +bool ChainstateManager::AcceptBlockHeader(const CBlockHeader& block, BlockValidationState& state, CBlockIndex** ppindex) { AssertLockHeld(cs_main); // Check for duplicate uint256 hash = block.GetHash(); BlockMap::iterator miSelf{m_blockman.m_block_index.find(hash)}; - if (hash != chainparams.GetConsensus().hashGenesisBlock) { + if (hash != GetConsensus().hashGenesisBlock) { if (miSelf != m_blockman.m_block_index.end()) { // Block header is already known. - CBlockIndex* pindex = miSelf->second; + CBlockIndex* pindex = &(miSelf->second); if (ppindex) *ppindex = pindex; if (pindex->nStatus & BLOCK_FAILED_MASK) { @@ -3510,7 +3590,7 @@ bool ChainstateManager::AcceptBlockHeader(const CBlockHeader& block, BlockValida return true; } - if (!CheckBlockHeader(block, state, chainparams.GetConsensus())) { + if (!CheckBlockHeader(block, state, GetConsensus())) { LogPrint(BCLog::VALIDATION, "%s: Consensus::CheckBlockHeader: %s, %s\n", __func__, hash.ToString(), state.ToString()); return false; } @@ -3522,12 +3602,12 @@ bool ChainstateManager::AcceptBlockHeader(const CBlockHeader& block, BlockValida LogPrint(BCLog::VALIDATION, "%s: %s prev block not found\n", __func__, hash.ToString()); return state.Invalid(BlockValidationResult::BLOCK_MISSING_PREV, "prev-blk-not-found"); } - pindexPrev = (*mi).second; + pindexPrev = &((*mi).second); if (pindexPrev->nStatus & BLOCK_FAILED_MASK) { 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, chainparams, pindexPrev, GetAdjustedTime())) { + if (!ContextualCheckBlockHeader(block, state, m_blockman, *this, pindexPrev, m_adjusted_time_callback())) { LogPrint(BCLog::VALIDATION, "%s: Consensus::ContextualCheckBlockHeader: %s, %s\n", __func__, hash.ToString(), state.ToString()); return false; } @@ -3571,7 +3651,7 @@ bool ChainstateManager::AcceptBlockHeader(const CBlockHeader& block, BlockValida } } } - CBlockIndex* pindex{m_blockman.AddToBlockIndex(block)}; + CBlockIndex* pindex{m_blockman.AddToBlockIndex(block, m_best_header)}; if (ppindex) *ppindex = pindex; @@ -3580,14 +3660,14 @@ bool ChainstateManager::AcceptBlockHeader(const CBlockHeader& block, BlockValida } // Exposed wrapper for AcceptBlockHeader -bool ChainstateManager::ProcessNewBlockHeaders(const std::vector<CBlockHeader>& headers, BlockValidationState& state, const CChainParams& chainparams, const CBlockIndex** ppindex) +bool ChainstateManager::ProcessNewBlockHeaders(const std::vector<CBlockHeader>& headers, 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, chainparams, &pindex)}; + bool accepted{AcceptBlockHeader(header, state, &pindex)}; ActiveChainstate().CheckBlockIndex(); if (!accepted) { @@ -3601,7 +3681,7 @@ bool ChainstateManager::ProcessNewBlockHeaders(const std::vector<CBlockHeader>& if (NotifyHeaderTip(ActiveChainstate())) { if (ActiveChainstate().IsInitialBlockDownload() && ppindex && *ppindex) { const CBlockIndex& last_accepted{**ppindex}; - const int64_t blocks_left{(GetTime() - last_accepted.GetBlockTime()) / chainparams.GetConsensus().nPowTargetSpacing}; + const int64_t blocks_left{(GetTime() - last_accepted.GetBlockTime()) / GetConsensus().nPowTargetSpacing}; const double progress{100.0 * last_accepted.nHeight / (last_accepted.nHeight + blocks_left)}; LogPrintf("Synchronizing blockheaders, height: %d (~%.2f%%)\n", last_accepted.nHeight, progress); } @@ -3620,7 +3700,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, m_params, &pindex)}; + bool accepted_header{m_chainman.AcceptBlockHeader(block, state, &pindex)}; CheckBlockIndex(); if (!accepted_header) @@ -3660,7 +3740,7 @@ bool CChainState::AcceptBlock(const std::shared_ptr<const CBlock>& pblock, Block } if (!CheckBlock(block, state, m_params.GetConsensus()) || - !ContextualCheckBlock(block, state, m_params.GetConsensus(), pindex->pprev)) { + !ContextualCheckBlock(block, state, m_chainman, pindex->pprev)) { if (state.IsInvalid() && state.GetResult() != BlockValidationResult::BLOCK_MUTATED) { pindex->nStatus |= BLOCK_FAILED_VALID; m_blockman.m_dirty_blockindex.insert(pindex); @@ -3693,7 +3773,7 @@ bool CChainState::AcceptBlock(const std::shared_ptr<const CBlock>& pblock, Block return true; } -bool ChainstateManager::ProcessNewBlock(const CChainParams& chainparams, 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* new_block) { AssertLockNotHeld(cs_main); @@ -3711,7 +3791,7 @@ bool ChainstateManager::ProcessNewBlock(const CChainParams& chainparams, const s // malleability that cause CheckBlock() to fail; see e.g. CVE-2012-2459 and // https://lists.linuxfoundation.org/pipermail/bitcoin-dev/2019-February/016697.html. Because CheckBlock() is // not very expensive, the anti-DoS benefits of caching failure (of a definitely-invalid block) are not substantial. - bool ret = CheckBlock(*block, state, chainparams.GetConsensus()); + bool ret = CheckBlock(*block, state, GetConsensus()); if (ret) { // Store to disk ret = ActiveChainstate().AcceptBlock(block, state, &pindex, force_processing, nullptr, new_block); @@ -3751,6 +3831,7 @@ bool TestBlockValidity(BlockValidationState& state, CChainState& chainstate, const CBlock& block, CBlockIndex* pindexPrev, + const std::function<int64_t()>& adjusted_time_callback, bool fCheckPOW, bool fCheckMerkleRoot) { @@ -3764,11 +3845,11 @@ bool TestBlockValidity(BlockValidationState& state, indexDummy.phashBlock = &block_hash; // NOTE: CheckBlockHeader is called by CheckBlock - if (!ContextualCheckBlockHeader(block, state, chainstate.m_blockman, chainparams, pindexPrev, GetAdjustedTime())) + if (!ContextualCheckBlockHeader(block, state, chainstate.m_blockman, chainstate.m_chainman, pindexPrev, adjusted_time_callback())) return error("%s: Consensus::ContextualCheckBlockHeader: %s", __func__, state.ToString()); if (!CheckBlock(block, state, chainparams.GetConsensus(), fCheckPOW, fCheckMerkleRoot)) return error("%s: Consensus::CheckBlock: %s", __func__, state.ToString()); - if (!ContextualCheckBlock(block, state, chainparams.GetConsensus(), pindexPrev)) + if (!ContextualCheckBlock(block, state, chainstate.m_chainman, pindexPrev)) return error("%s: Consensus::ContextualCheckBlock: %s", __func__, state.ToString()); if (!chainstate.ConnectBlock(block, state, &indexDummy, viewNew, true)) { return false; @@ -3788,13 +3869,11 @@ void PruneBlockFilesManual(CChainState& active_chainstate, int nManualPruneHeigh } } -void CChainState::LoadMempool(const ArgsManager& args) +void CChainState::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() @@ -3813,7 +3892,7 @@ bool CChainState::LoadChainTip() if (!pindex) { return false; } - m_chain.SetTip(pindex); + m_chain.SetTip(*pindex); PruneBlockIndexCandidates(); tip = m_chain.Tip(); @@ -3994,13 +4073,13 @@ bool CChainState::ReplayBlocks() if (m_blockman.m_block_index.count(hashHeads[0]) == 0) { return error("ReplayBlocks(): reorganization to unknown block requested"); } - pindexNew = m_blockman.m_block_index[hashHeads[0]]; + pindexNew = &(m_blockman.m_block_index[hashHeads[0]]); if (!hashHeads[1].IsNull()) { // The old tip is allowed to be 0, indicating it's the first flush. if (m_blockman.m_block_index.count(hashHeads[1]) == 0) { return error("ReplayBlocks(): reorganization from unknown block requested"); } - pindexOld = m_blockman.m_block_index[hashHeads[1]]; + pindexOld = &(m_blockman.m_block_index[hashHeads[1]]); pindexFork = LastCommonAncestor(pindexOld, pindexNew); assert(pindexFork != nullptr); } @@ -4028,10 +4107,11 @@ bool CChainState::ReplayBlocks() // Roll forward from the forking point to the new tip. int nForkHeight = pindexFork ? pindexFork->nHeight : 0; for (int nHeight = nForkHeight + 1; nHeight <= pindexNew->nHeight; ++nHeight) { - const CBlockIndex* pindex = pindexNew->GetAncestor(nHeight); - LogPrintf("Rolling forward %s (%i)\n", pindex->GetBlockHash().ToString(), nHeight); + const CBlockIndex& pindex{*Assert(pindexNew->GetAncestor(nHeight))}; + + LogPrintf("Rolling forward %s (%i)\n", pindex.GetBlockHash().ToString(), nHeight); uiInterface.ShowProgress(_("Replaying blocks…").translated, (int) ((nHeight - nForkHeight) * 100.0 / (pindexNew->nHeight - nForkHeight)) , false); - if (!RollforwardBlock(pindex, cache)) return false; + if (!RollforwardBlock(&pindex, cache)) return false; } cache.SetBestBlock(pindexNew->GetBlockHash()); @@ -4047,7 +4127,7 @@ bool CChainState::NeedsRedownload() const // At and above m_params.SegwitHeight, segwit consensus rules must be validated CBlockIndex* block{m_chain.Tip()}; - while (block != nullptr && DeploymentActiveAt(*block, m_params.GetConsensus(), Consensus::DEPLOYMENT_SEGWIT)) { + while (block != nullptr && DeploymentActiveAt(*block, m_chainman, Consensus::DEPLOYMENT_SEGWIT)) { if (!(block->nStatus & BLOCK_OPT_WITNESS)) { // block is insufficiently validated for a segwit client return true; @@ -4065,30 +4145,82 @@ void CChainState::UnloadBlockIndex() setBlockIndexCandidates.clear(); } -// May NOT be used after any connections are up as much -// of the peer-processing logic assumes a consistent -// block index state -void UnloadBlockIndex(CTxMemPool* mempool, ChainstateManager& chainman) -{ - LOCK(cs_main); - chainman.Unload(); - pindexBestHeader = nullptr; - if (mempool) mempool->clear(); - g_versionbitscache.Clear(); - for (int b = 0; b < VERSIONBITS_NUM_BITS; b++) { - warningcache[b].clear(); - } - fHavePruned = false; -} - bool ChainstateManager::LoadBlockIndex() { AssertLockHeld(cs_main); // Load block index from databases bool needs_init = fReindex; if (!fReindex) { - bool ret = m_blockman.LoadBlockIndexDB(*this); + bool ret = m_blockman.LoadBlockIndexDB(GetConsensus()); if (!ret) return false; + + std::vector<CBlockIndex*> vSortedByHeight{m_blockman.GetAllBlockIndices()}; + std::sort(vSortedByHeight.begin(), vSortedByHeight.end(), + CBlockIndexHeightOnlyComparator()); + + // Find start of assumed-valid region. + int first_assumed_valid_height = std::numeric_limits<int>::max(); + + for (const CBlockIndex* block : vSortedByHeight) { + if (block->IsAssumedValid()) { + auto chainstates = GetAll(); + + // If we encounter an assumed-valid block index entry, ensure that we have + // one chainstate that tolerates assumed-valid entries and another that does + // not (i.e. the background validation chainstate), since assumed-valid + // entries should always be pending validation by a fully-validated chainstate. + auto any_chain = [&](auto fnc) { return std::any_of(chainstates.cbegin(), chainstates.cend(), fnc); }; + assert(any_chain([](auto chainstate) { return chainstate->reliesOnAssumedValid(); })); + assert(any_chain([](auto chainstate) { return !chainstate->reliesOnAssumedValid(); })); + + first_assumed_valid_height = block->nHeight; + break; + } + } + + for (CBlockIndex* pindex : vSortedByHeight) { + if (ShutdownRequested()) return false; + if (pindex->IsAssumedValid() || + (pindex->IsValid(BLOCK_VALID_TRANSACTIONS) && + (pindex->HaveTxsDownloaded() || pindex->pprev == nullptr))) { + + // Fill each chainstate's block candidate set. Only add assumed-valid + // blocks to the tip candidate set if the chainstate is allowed to rely on + // assumed-valid blocks. + // + // If all setBlockIndexCandidates contained the assumed-valid blocks, the + // background chainstate's ActivateBestChain() call would add assumed-valid + // blocks to the chain (based on how FindMostWorkChain() works). Obviously + // we don't want this since the purpose of the background validation chain + // is to validate assued-valid blocks. + // + // Note: This is considering all blocks whose height is greater or equal to + // the first assumed-valid block to be assumed-valid blocks, and excluding + // them from the background chainstate's setBlockIndexCandidates set. This + // does mean that some blocks which are not technically assumed-valid + // (later blocks on a fork beginning before the first assumed-valid block) + // might not get added to the background chainstate, but this is ok, + // because they will still be attached to the active chainstate if they + // actually contain more work. + // + // Instead of this height-based approach, an earlier attempt was made at + // 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()) { + if (chainstate->reliesOnAssumedValid() || + pindex->nHeight < first_assumed_valid_height) { + chainstate->setBlockIndexCandidates.insert(pindex); + } + } + } + if (pindex->nStatus & BLOCK_FAILED_MASK && (!m_best_invalid || pindex->nChainWork > m_best_invalid->nChainWork)) { + m_best_invalid = pindex; + } + if (pindex->IsValid(BLOCK_VALID_TREE) && (m_best_header == nullptr || CBlockIndexWorkComparator()(m_best_header, pindex))) + m_best_header = pindex; + } + needs_init = m_blockman.m_block_index.empty(); } @@ -4121,7 +4253,7 @@ bool CChainState::LoadGenesisBlock() if (blockPos.IsNull()) { return error("%s: writing genesis block to disk failed", __func__); } - CBlockIndex *pindex = m_blockman.AddToBlockIndex(block); + CBlockIndex* pindex = m_blockman.AddToBlockIndex(block, m_chainman.m_best_header); ReceivedBlockTransactions(block, pindex, blockPos); } catch (const std::runtime_error& e) { return error("%s: failed to write genesis block: %s", __func__, e.what()); @@ -4130,11 +4262,16 @@ bool CChainState::LoadGenesisBlock() return true; } -void CChainState::LoadExternalBlockFile(FILE* fileIn, FlatFilePos* dbp) +void CChainState::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; + + // Either both should be specified (-reindex), or neither (-loadblock). + assert(!dbp == !blocks_with_unknown_parent); + int64_t nStart = GetTimeMillis(); int nLoaded = 0; @@ -4184,13 +4321,14 @@ 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; } // process in case the block isn't known yet - CBlockIndex* pindex = m_blockman.LookupBlockIndex(hash); + 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)) { @@ -4214,13 +4352,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>(); @@ -4235,7 +4375,7 @@ void CChainState::LoadExternalBlockFile(FILE* fileIn, FlatFilePos* dbp) } } range.first++; - mapBlocksUnknownParent.erase(it); + blocks_with_unknown_parent->erase(it); NotifyHeaderTip(*this); } } @@ -4267,8 +4407,8 @@ void CChainState::CheckBlockIndex() // Build forward-pointing map of the entire block tree. std::multimap<CBlockIndex*,CBlockIndex*> forward; - for (const std::pair<const uint256, CBlockIndex*>& entry : m_blockman.m_block_index) { - forward.insert(std::make_pair(entry.second->pprev, entry.second)); + for (auto& [_, block_index] : m_blockman.m_block_index) { + forward.emplace(block_index.pprev, &block_index); } assert(forward.size() == m_blockman.m_block_index.size()); @@ -4332,7 +4472,7 @@ void CChainState::CheckBlockIndex() // HAVE_DATA is only equivalent to nTx > 0 (or VALID_TRANSACTIONS) if no pruning has occurred. // Unless these indexes are assumed valid and pending block download on a // background chainstate. - if (!fHavePruned && !pindex->IsAssumedValid()) { + if (!m_blockman.m_have_pruned && !pindex->IsAssumedValid()) { // If we've never pruned, then HAVE_DATA should be equivalent to nTx > 0 assert(!(pindex->nStatus & BLOCK_HAVE_DATA) == (pindex->nTx == 0)); assert(pindexFirstMissing == pindexFirstNeverProcessed); @@ -4406,7 +4546,7 @@ void CChainState::CheckBlockIndex() if (pindexFirstMissing == nullptr) assert(!foundInUnlinked); // We aren't missing data for any parent -- cannot be in m_blocks_unlinked. if (pindex->pprev && (pindex->nStatus & BLOCK_HAVE_DATA) && pindexFirstNeverProcessed == nullptr && pindexFirstMissing != nullptr) { // We HAVE_DATA for this block, have received data for all parents at some point, but we're currently missing data for some parent. - assert(fHavePruned); // We must have pruned. + assert(m_blockman.m_have_pruned); // We must have pruned. // This block may have entered m_blocks_unlinked if: // - it has a descendant that at some point had more work than the // tip, and @@ -4512,153 +4652,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) { @@ -4741,7 +4734,7 @@ const AssumeutxoData* ExpectedAssumeutxo( } bool ChainstateManager::ActivateSnapshot( - CAutoFile& coins_file, + AutoFile& coins_file, const SnapshotMetadata& metadata, bool in_memory) { @@ -4786,7 +4779,7 @@ bool ChainstateManager::ActivateSnapshot( auto snapshot_chainstate = WITH_LOCK(::cs_main, return std::make_unique<CChainState>( - /* mempool */ nullptr, m_blockman, *this, base_blockhash)); + /*mempool=*/nullptr, m_blockman, *this, base_blockhash)); { LOCK(::cs_main); @@ -4836,7 +4829,7 @@ static void FlushSnapshotToDisk(CCoinsViewCache& coins_cache, bool snapshot_load bool ChainstateManager::PopulateAndValidateSnapshot( CChainState& snapshot_chainstate, - CAutoFile& coins_file, + AutoFile& coins_file, const SnapshotMetadata& metadata) { // It's okay to release cs_main before we're done using `coins_cache` because we know @@ -4848,14 +4841,15 @@ bool ChainstateManager::PopulateAndValidateSnapshot( CBlockIndex* snapshot_start_block = WITH_LOCK(::cs_main, return m_blockman.LookupBlockIndex(base_blockhash)); if (!snapshot_start_block) { - // Needed for GetUTXOStats and ExpectedAssumeutxo to determine the height and to avoid a crash when base_blockhash.IsNull() + // Needed for ComputeUTXOStats and ExpectedAssumeutxo to determine the + // height and to avoid a crash when base_blockhash.IsNull() LogPrintf("[snapshot] Did not find snapshot start blockheader %s\n", base_blockhash.ToString()); return false; } int base_height = snapshot_start_block->nHeight; - auto maybe_au_data = ExpectedAssumeutxo(base_height, ::Params()); + auto maybe_au_data = ExpectedAssumeutxo(base_height, GetParams()); if (!maybe_au_data) { LogPrintf("[snapshot] assumeutxo height in snapshot metadata not recognized " /* Continued */ @@ -4956,26 +4950,26 @@ bool ChainstateManager::PopulateAndValidateSnapshot( assert(coins_cache.GetBestBlock() == base_blockhash); - CCoinsStats stats{CoinStatsHashType::HASH_SERIALIZED}; auto breakpoint_fnc = [] { /* TODO insert breakpoint here? */ }; // As above, okay to immediately release cs_main here since no other context knows // about the snapshot_chainstate. CCoinsViewDB* snapshot_coinsdb = WITH_LOCK(::cs_main, return &snapshot_chainstate.CoinsDB()); - if (!GetUTXOStats(snapshot_coinsdb, m_blockman, stats, breakpoint_fnc)) { + const std::optional<CCoinsStats> maybe_stats = ComputeUTXOStats(CoinStatsHashType::HASH_SERIALIZED, snapshot_coinsdb, m_blockman, breakpoint_fnc); + if (!maybe_stats.has_value()) { LogPrintf("[snapshot] failed to generate coins stats\n"); return false; } // Assert that the deserialized chainstate contents match the expected assumeutxo value. - if (AssumeutxoHash{stats.hashSerialized} != au_data.hash_serialized) { + if (AssumeutxoHash{maybe_stats->hashSerialized} != au_data.hash_serialized) { LogPrintf("[snapshot] bad snapshot content hash: expected %s, got %s\n", - au_data.hash_serialized.ToString(), stats.hashSerialized.ToString()); + au_data.hash_serialized.ToString(), maybe_stats->hashSerialized.ToString()); 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); @@ -5009,7 +5003,7 @@ bool ChainstateManager::PopulateAndValidateSnapshot( // Fake BLOCK_OPT_WITNESS so that CChainState::NeedsRedownload() // won't ask to rewind the entire assumed-valid chain on startup. - if (DeploymentActiveAt(*index, ::Params().GetConsensus(), Consensus::DEPLOYMENT_SEGWIT)) { + if (DeploymentActiveAt(*index, *this, Consensus::DEPLOYMENT_SEGWIT)) { index->nStatus |= BLOCK_OPT_WITNESS; } @@ -5043,28 +5037,6 @@ bool ChainstateManager::IsSnapshotActive() const return m_snapshot_chainstate && m_active_chainstate == m_snapshot_chainstate.get(); } -void ChainstateManager::Unload() -{ - AssertLockHeld(::cs_main); - for (CChainState* chainstate : this->GetAll()) { - chainstate->m_chain.SetTip(nullptr); - chainstate->UnloadBlockIndex(); - } - - m_failed_blocks.clear(); - m_blockman.Unload(); - m_best_invalid = nullptr; -} - -void ChainstateManager::Reset() -{ - LOCK(::cs_main); - m_ibd_chainstate.reset(); - m_snapshot_chainstate.reset(); - m_active_chainstate = nullptr; - m_snapshot_validated = false; -} - void ChainstateManager::MaybeRebalanceCaches() { AssertLockHeld(::cs_main); @@ -5095,3 +5067,15 @@ void ChainstateManager::MaybeRebalanceCaches() } } } + +ChainstateManager::~ChainstateManager() +{ + LOCK(::cs_main); + + m_versionbitscache.Clear(); + + // TODO: The warning cache should probably become non-global + for (auto& i : warningcache) { + i.clear(); + } +} diff --git a/src/validation.h b/src/validation.h index 7766d77a88..324074aee9 100644 --- a/src/validation.h +++ b/src/validation.h @@ -13,11 +13,15 @@ #include <arith_uint256.h> #include <attributes.h> #include <chain.h> +#include <chainparams.h> +#include <kernel/chainstatemanager_opts.h> #include <consensus/amount.h> +#include <deploymentstatus.h> #include <fs.h> #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> @@ -26,6 +30,7 @@ #include <util/check.h> #include <util/hasher.h> #include <util/translation.h> +#include <versionbits.h> #include <atomic> #include <map> @@ -40,7 +45,6 @@ class CChainState; class CBlockTreeDB; -class CChainParams; class CTxMemPool; class ChainstateManager; struct ChainTxData; @@ -51,29 +55,10 @@ struct AssumeutxoData; namespace node { class SnapshotMetadata; } // namespace node +namespace Consensus { +struct Params; +} // namespace Consensus -/** Default for -minrelaytxfee, minimum relay fee for transactions */ -static const unsigned int DEFAULT_MIN_RELAY_TX_FEE = 1000; -/** 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) */ @@ -83,8 +68,6 @@ 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. */ @@ -109,7 +92,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; @@ -117,11 +100,8 @@ 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; -/** A fee rate smaller than this is considered zero fee (for relaying, mining and transaction creation) */ -extern CFeeRate minRelayTxFee; /** If the tip is older than this (in seconds), the node is considered to be in initial block download. */ extern int64_t nMaxTipAge; @@ -131,14 +111,9 @@ extern uint256 hashAssumeValid; /** Minimum work we will assume exists on some valid chain. */ extern arith_uint256 nMinimumChainWork; -/** Best header we've seen so far (used for getheaders queries' starting points). */ -extern CBlockIndex *pindexBestHeader; - /** Documentation for argument 'checklevel'. */ extern const std::vector<std::string> CHECKLEVEL_DOC; -/** Unload database information */ -void UnloadBlockIndex(CTxMemPool* mempool, ChainstateManager& chainman); /** Run instances of script checking worker threads */ void StartScriptCheckWorkerThreads(int threads_num); /** Stop all of the script checking worker threads */ @@ -234,11 +209,19 @@ struct PackageMempoolAcceptResult * was a package-wide error (see result in m_state), m_tx_results will be empty. */ std::map<const uint256, const MempoolAcceptResult> m_tx_results; + /** Package feerate, defined as the aggregated modified fees divided by the total virtual size + * of all transactions in the package. May be unavailable if some inputs were not available or + * a transaction failure caused validation to terminate early. */ + std::optional<CFeeRate> m_package_feerate; explicit PackageMempoolAcceptResult(PackageValidationState state, std::map<const uint256, const MempoolAcceptResult>&& results) : m_state{state}, m_tx_results(std::move(results)) {} + explicit PackageMempoolAcceptResult(PackageValidationState state, CFeeRate feerate, + std::map<const uint256, const MempoolAcceptResult>&& results) + : m_state{state}, m_tx_results(std::move(results)), m_package_feerate{feerate} {} + /** Constructor to create a PackageMempoolAcceptResult from a single MempoolAcceptResult */ explicit PackageMempoolAcceptResult(const uint256& wtxid, const MempoolAcceptResult& result) : m_tx_results{ {wtxid, result} } {} @@ -273,16 +256,12 @@ PackageMempoolAcceptResult ProcessNewPackage(CChainState& active_chainstate, CTx const Package& txns, bool test_accept) EXCLUSIVE_LOCKS_REQUIRED(cs_main); -/** Transaction validation functions */ +/* Mempool validation helper functions */ /** * Check if transaction will be final in the next block to be created. - * - * Calls IsFinalTx() with current block height and appropriate block time. - * - * See consensus/consensus.h for flag definitions. */ -bool CheckFinalTx(const CBlockIndex* active_chain_tip, const CTransaction &tx, int flags = -1) 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. @@ -299,14 +278,11 @@ bool CheckFinalTx(const CBlockIndex* active_chain_tip, const CTransaction &tx, i * Optionally stores in LockPoints the resulting height and time calculated and the hash * of the block needed for calculation or skips the calculation and uses the LockPoints * passed in for evaluation. - * The LockPoints should not be considered valid if CheckSequenceLocks returns false. - * - * See consensus/consensus.h for flag definitions. + * The LockPoints should not be considered valid if CheckSequenceLocksAtTip returns false. */ -bool CheckSequenceLocks(CBlockIndex* tip, +bool CheckSequenceLocksAtTip(CBlockIndex* tip, const CCoinsView& coins_view, const CTransaction& tx, - int flags, LockPoints* lp = nullptr, bool useExistingLockPoints = false); @@ -332,7 +308,8 @@ public: bool operator()(); - void swap(CScriptCheck &check) { + void swap(CScriptCheck& check) noexcept + { std::swap(ptxTo, check.ptxTo); std::swap(m_tx_out, check.m_tx_out); std::swap(nIn, check.nIn); @@ -346,7 +323,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 */ @@ -359,15 +336,10 @@ bool TestBlockValidity(BlockValidationState& state, CChainState& chainstate, const CBlock& block, CBlockIndex* pindexPrev, + const std::function<int64_t()>& adjusted_time_callback, bool fCheckPOW = true, bool fCheckMerkleRoot = true) EXCLUSIVE_LOCKS_REQUIRED(cs_main); -/** Update uncommitted block structures (currently: only the witness reserved value). This is safe for submitted blocks. */ -void UpdateUncommittedBlockStructures(CBlock& block, const CBlockIndex* pindexPrev, const Consensus::Params& consensusParams); - -/** 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 Consensus::Params& consensusParams); - /** RAII wrapper for VerifyDB: Verify consistency of the block and coin databases */ class CVerifyDB { public: @@ -427,7 +399,7 @@ public: //! state to disk, which should not be done until the health of the database is verified. //! //! All arguments forwarded onto CCoinsViewDB. - CoinsViews(std::string ldb_name, size_t cache_size_bytes, bool in_memory, bool should_wipe); + CoinsViews(fs::path ldb_name, size_t cache_size_bytes, bool in_memory, bool should_wipe); //! Initialize the CCoinsViewCache member. void InitCache() EXCLUSIVE_LOCKS_REQUIRED(::cs_main); @@ -498,6 +470,7 @@ public: node::BlockManager& m_blockman; /** Chain parameters for this chainstate */ + /* TODO: replace with m_chainman.GetParams() */ const CChainParams& m_params; //! The chainstate manager that owns this chainstate. The reference is @@ -521,7 +494,7 @@ public: size_t cache_size_bytes, bool in_memory, bool should_wipe, - std::string leveldb_name = "chainstate"); + fs::path leveldb_name = "chainstate"); //! Initialize the in-memory coins cache (to be done after the health of the on-disk database //! is verified). @@ -601,8 +574,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); /** @@ -693,7 +694,7 @@ public: bool IsInitialBlockDownload() const; /** Find the last common block of this chain and a locator. */ - CBlockIndex* FindForkInGlobalIndex(const CBlockLocator& locator) const EXCLUSIVE_LOCKS_REQUIRED(cs_main); + const CBlockIndex* FindForkInGlobalIndex(const CBlockLocator& locator) const EXCLUSIVE_LOCKS_REQUIRED(cs_main); /** * Make various assertions about the state of the block index. @@ -703,7 +704,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); @@ -833,15 +834,18 @@ private: //! If true, the assumed-valid chainstate has been fully validated //! by the background validation chainstate. - bool m_snapshot_validated{false}; + bool m_snapshot_validated GUARDED_BY(::cs_main){false}; + + CBlockIndex* m_best_invalid GUARDED_BY(::cs_main){nullptr}; - CBlockIndex* m_best_invalid; - friend bool node::BlockManager::LoadBlockIndex(const Consensus::Params&, ChainstateManager&); + 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, + AutoFile& coins_file, const node::SnapshotMetadata& metadata); /** @@ -851,11 +855,32 @@ private: bool AcceptBlockHeader( const CBlockHeader& block, BlockValidationState& state, - const CChainParams& chainparams, CBlockIndex** ppindex) EXCLUSIVE_LOCKS_REQUIRED(cs_main); friend CChainState; public: + using Options = kernel::ChainstateManagerOpts; + + explicit ChainstateManager(const Options& opts) + : m_chainparams{opts.chainparams}, + m_adjusted_time_callback{Assert(opts.adjusted_time_callback)} {}; + + const CChainParams& GetParams() const { return m_chainparams; } + const Consensus::Params& GetConsensus() const { return m_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; } + std::thread m_load_block; //! A single BlockManager instance is shared across each constructed //! chainstate to avoid duplicating block metadata. @@ -882,6 +907,9 @@ public: */ std::set<CBlockIndex*> m_failed_blocks; + /** Best header we've seen so far (used for getheaders queries' starting points). */ + CBlockIndex* m_best_header = nullptr; + //! The total number of bytes available for us to use across all in-memory //! coins caches. This will be split somehow across chainstates. int64_t m_total_coinstip_cache{0}; @@ -919,13 +947,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(); } + 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) { @@ -933,6 +961,11 @@ public: return m_blockman.m_block_index; } + /** + * Track versionbit status + */ + mutable VersionBitsCache m_versionbitscache; + //! @returns true if a snapshot-based chainstate is in use. Also implies //! that a background validation chainstate is also in use. bool IsSnapshotActive() const; @@ -940,7 +973,7 @@ public: std::optional<uint256> SnapshotBlockhash() const; //! Is there a snapshot in use and has it been fully validated? - bool IsSnapshotValidated() const { return m_snapshot_validated; } + bool IsSnapshotValidated() const EXCLUSIVE_LOCKS_REQUIRED(::cs_main) { return m_snapshot_validated; } /** * Process an incoming block. This only returns after the best known valid @@ -961,7 +994,7 @@ public: * @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 CChainParams& chainparams, 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* new_block) LOCKS_EXCLUDED(cs_main); /** * Process incoming block headers. @@ -971,10 +1004,9 @@ public: * * @param[in] block The block headers themselves * @param[out] state This may be set to an Error state if any error occurred processing them - * @param[in] chainparams The params for the chain we want to connect to * @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 CChainParams& chainparams, const CBlockIndex** ppindex = nullptr) LOCKS_EXCLUDED(cs_main); + bool ProcessNewBlockHeaders(const std::vector<CBlockHeader>& block, BlockValidationState& state, const CBlockIndex** ppindex = nullptr) LOCKS_EXCLUDED(cs_main); /** * Try to add a transaction to the memory pool. @@ -988,30 +1020,37 @@ public: //! Load the block tree and coins database from disk, initializing state if we're running with -reindex bool LoadBlockIndex() EXCLUSIVE_LOCKS_REQUIRED(cs_main); - //! Unload block index and chain data before shutdown. - void Unload() EXCLUSIVE_LOCKS_REQUIRED(::cs_main); - - //! Clear (deconstruct) chainstate data. - void Reset(); - //! Check to see if caches are out of balance and if so, call //! ResizeCoinsCaches() as needed. void MaybeRebalanceCaches() EXCLUSIVE_LOCKS_REQUIRED(::cs_main); - ~ChainstateManager() { - LOCK(::cs_main); - UnloadBlockIndex(/* mempool */ nullptr, *this); - Reset(); - } + /** Update uncommitted block structures (currently: only the witness reserved value). This is safe for submitted blocks. */ + void UpdateUncommittedBlockStructures(CBlock& block, const CBlockIndex* pindexPrev) const; + + /** 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; + + ~ChainstateManager(); }; -using FopenFn = std::function<FILE*(const fs::path&, const char*)>; +/** Deployment* info via ChainstateManager */ +template<typename DEP> +bool DeploymentActiveAfter(const CBlockIndex* pindexPrev, const ChainstateManager& chainman, DEP dep) +{ + return DeploymentActiveAfter(pindexPrev, chainman.GetConsensus(), dep, chainman.m_versionbitscache); +} -/** Dump the mempool to disk. */ -bool DumpMempool(const CTxMemPool& pool, FopenFn mockable_fopen_function = fsbridge::fopen, bool skip_file_commit = false); +template<typename DEP> +bool DeploymentActiveAt(const CBlockIndex& index, const ChainstateManager& chainman, DEP dep) +{ + return DeploymentActiveAt(index, chainman.GetConsensus(), dep, chainman.m_versionbitscache); +} -/** Load the mempool from disk. */ -bool LoadMempool(CTxMemPool& pool, CChainState& active_chainstate, FopenFn mockable_fopen_function = fsbridge::fopen); +template<typename DEP> +bool DeploymentEnabled(const ChainstateManager& chainman, DEP dep) +{ + return DeploymentEnabled(chainman.GetConsensus(), dep); +} /** * Return the expected assumeutxo value for a given height, if one exists. diff --git a/src/validationinterface.cpp b/src/validationinterface.cpp index 1e07ff23ae..613c5b65ef 100644 --- a/src/validationinterface.cpp +++ b/src/validationinterface.cpp @@ -5,6 +5,7 @@ #include <validationinterface.h> +#include <attributes.h> #include <chain.h> #include <consensus/validation.h> #include <logging.h> @@ -16,14 +17,16 @@ #include <unordered_map> #include <utility> -//! The MainSignalsInstance manages a list of shared_ptr<CValidationInterface> -//! callbacks. -//! -//! A std::unordered_map is used to track what callbacks are currently -//! registered, and a std::list is to used to store the callbacks that are -//! currently registered as well as any callbacks that are just unregistered -//! and about to be deleted when they are done executing. -struct MainSignalsInstance { +/** + * MainSignalsImpl manages a list of shared_ptr<CValidationInterface> callbacks. + * + * A std::unordered_map is used to track what callbacks are currently + * registered, and a std::list is used to store the callbacks that are + * currently registered as well as any callbacks that are just unregistered + * and about to be deleted when they are done executing. + */ +class MainSignalsImpl +{ private: Mutex m_mutex; //! List entries consist of a callback pointer and reference count. The @@ -40,9 +43,9 @@ public: // our own queue here :( SingleThreadedSchedulerClient m_schedulerClient; - explicit MainSignalsInstance(CScheduler *pscheduler) : m_schedulerClient(pscheduler) {} + explicit MainSignalsImpl(CScheduler& scheduler LIFETIMEBOUND) : m_schedulerClient(scheduler) {} - void Register(std::shared_ptr<CValidationInterface> callbacks) + void Register(std::shared_ptr<CValidationInterface> callbacks) EXCLUSIVE_LOCKS_REQUIRED(!m_mutex) { LOCK(m_mutex); auto inserted = m_map.emplace(callbacks.get(), m_list.end()); @@ -50,7 +53,7 @@ public: inserted.first->second->callbacks = std::move(callbacks); } - void Unregister(CValidationInterface* callbacks) + void Unregister(CValidationInterface* callbacks) EXCLUSIVE_LOCKS_REQUIRED(!m_mutex) { LOCK(m_mutex); auto it = m_map.find(callbacks); @@ -64,7 +67,7 @@ public: //! map entry. After this call, the list may still contain callbacks that //! are currently executing, but it will be cleared when they are done //! executing. - void Clear() + void Clear() EXCLUSIVE_LOCKS_REQUIRED(!m_mutex) { LOCK(m_mutex); for (const auto& entry : m_map) { @@ -73,7 +76,7 @@ public: m_map.clear(); } - template<typename F> void Iterate(F&& f) + template<typename F> void Iterate(F&& f) EXCLUSIVE_LOCKS_REQUIRED(!m_mutex) { WAIT_LOCK(m_mutex, lock); for (auto it = m_list.begin(); it != m_list.end();) { @@ -92,7 +95,7 @@ static CMainSignals g_signals; void CMainSignals::RegisterBackgroundSignalScheduler(CScheduler& scheduler) { assert(!m_internals); - m_internals.reset(new MainSignalsInstance(&scheduler)); + m_internals = std::make_unique<MainSignalsImpl>(scheduler); } void CMainSignals::UnregisterBackgroundSignalScheduler() diff --git a/src/validationinterface.h b/src/validationinterface.h index 7c3ce00fbc..a929a3d56b 100644 --- a/src/validationinterface.h +++ b/src/validationinterface.h @@ -17,9 +17,7 @@ class BlockValidationState; class CBlock; class CBlockIndex; struct CBlockLocator; -class CConnman; class CValidationInterface; -class uint256; class CScheduler; enum class MemPoolRemovalReason; @@ -174,12 +172,13 @@ protected: * has been received and connected to the headers tree, though not validated yet */ virtual void NewPoWValidBlock(const CBlockIndex *pindex, const std::shared_ptr<const CBlock>& block) {}; friend class CMainSignals; + friend class ValidationInterfaceTest; }; -struct MainSignalsInstance; +class MainSignalsImpl; class CMainSignals { private: - std::unique_ptr<MainSignalsInstance> m_internals; + std::unique_ptr<MainSignalsImpl> m_internals; friend void ::RegisterSharedValidationInterface(std::shared_ptr<CValidationInterface>); friend void ::UnregisterValidationInterface(CValidationInterface*); diff --git a/src/versionbits.cpp b/src/versionbits.cpp index 7a297c2bbb..dc85655028 100644 --- a/src/versionbits.cpp +++ b/src/versionbits.cpp @@ -2,8 +2,9 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#include <versionbits.h> #include <consensus/params.h> +#include <util/check.h> +#include <versionbits.h> ThresholdState AbstractThresholdConditionChecker::GetStateFor(const CBlockIndex* pindexPrev, const Consensus::Params& params, ThresholdConditionCache& cache) const { @@ -158,7 +159,7 @@ int AbstractThresholdConditionChecker::GetStateSinceHeightFor(const CBlockIndex* // if we are computing for the last block of a period, then pindexPrev points to the second to last block of the period, and // if we are computing for the first block of a period, then pindexPrev points to the last block of the previous period. // The parent of the genesis block is represented by nullptr. - pindexPrev = pindexPrev->GetAncestor(pindexPrev->nHeight - ((pindexPrev->nHeight + 1) % nPeriod)); + pindexPrev = Assert(pindexPrev->GetAncestor(pindexPrev->nHeight - ((pindexPrev->nHeight + 1) % nPeriod))); const CBlockIndex* previousPeriodParent = pindexPrev->GetAncestor(pindexPrev->nHeight - nPeriod); diff --git a/src/versionbits.h b/src/versionbits.h index 1b3fa11e61..9f7ee1b48e 100644 --- a/src/versionbits.h +++ b/src/versionbits.h @@ -92,16 +92,16 @@ public: static uint32_t Mask(const Consensus::Params& params, Consensus::DeploymentPos pos); /** Get the BIP9 state for a given deployment for the block after pindexPrev. */ - ThresholdState State(const CBlockIndex* pindexPrev, const Consensus::Params& params, Consensus::DeploymentPos pos); + ThresholdState State(const CBlockIndex* pindexPrev, const Consensus::Params& params, Consensus::DeploymentPos pos) EXCLUSIVE_LOCKS_REQUIRED(!m_mutex); /** Get the block height at which the BIP9 deployment switched into the state for the block after pindexPrev. */ - int StateSinceHeight(const CBlockIndex* pindexPrev, const Consensus::Params& params, Consensus::DeploymentPos pos); + int StateSinceHeight(const CBlockIndex* pindexPrev, const Consensus::Params& params, Consensus::DeploymentPos pos) EXCLUSIVE_LOCKS_REQUIRED(!m_mutex); /** Determine what nVersion a new block should use */ - int32_t ComputeBlockVersion(const CBlockIndex* pindexPrev, const Consensus::Params& params); + int32_t ComputeBlockVersion(const CBlockIndex* pindexPrev, const Consensus::Params& params) EXCLUSIVE_LOCKS_REQUIRED(!m_mutex); - void Clear(); + void Clear() EXCLUSIVE_LOCKS_REQUIRED(!m_mutex); }; #endif // BITCOIN_VERSIONBITS_H diff --git a/src/wallet/bdb.cpp b/src/wallet/bdb.cpp index 49f0abf9e7..deb1293cd4 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> @@ -60,12 +61,12 @@ bool WalletDatabaseFileId::operator==(const WalletDatabaseFileId& rhs) const * erases the weak pointer from the g_dbenvs map. * @post A new BerkeleyEnvironment weak pointer is inserted into g_dbenvs if the directory path key was not already in the map. */ -std::shared_ptr<BerkeleyEnvironment> GetBerkeleyEnv(const fs::path& env_directory) +std::shared_ptr<BerkeleyEnvironment> GetBerkeleyEnv(const fs::path& env_directory, bool use_shared_memory) { LOCK(cs_db); auto inserted = g_dbenvs.emplace(fs::PathToString(env_directory), std::weak_ptr<BerkeleyEnvironment>()); if (inserted.second) { - auto env = std::make_shared<BerkeleyEnvironment>(env_directory); + auto env = std::make_shared<BerkeleyEnvironment>(env_directory, use_shared_memory); inserted.first->second = env; return env; } @@ -99,7 +100,7 @@ void BerkeleyEnvironment::Close() if (ret != 0) LogPrintf("BerkeleyEnvironment::Close: Error %d closing database environment: %s\n", ret, DbEnv::strerror(ret)); if (!fMockDb) - DbEnv((u_int32_t)0).remove(strPath.c_str(), 0); + DbEnv((uint32_t)0).remove(strPath.c_str(), 0); if (error_file) fclose(error_file); @@ -113,7 +114,7 @@ void BerkeleyEnvironment::Reset() fMockDb = false; } -BerkeleyEnvironment::BerkeleyEnvironment(const fs::path& dir_path) : strPath(fs::PathToString(dir_path)) +BerkeleyEnvironment::BerkeleyEnvironment(const fs::path& dir_path, bool use_shared_memory) : strPath(fs::PathToString(dir_path)), m_use_shared_memory(use_shared_memory) { Reset(); } @@ -145,8 +146,9 @@ bool BerkeleyEnvironment::Open(bilingual_str& err) LogPrintf("BerkeleyEnvironment::Open: LogDir=%s ErrorFile=%s\n", fs::PathToString(pathLogDir), fs::PathToString(pathErrorFile)); unsigned int nEnvFlags = 0; - if (gArgs.GetBoolArg("-privdb", DEFAULT_WALLET_PRIVDB)) + if (!m_use_shared_memory) { nEnvFlags |= DB_PRIVATE; + } dbenv->set_lg_dir(fs::PathToString(pathLogDir).c_str()); dbenv->set_cachesize(0, 0x100000, 1); // 1 MiB should be enough for just the wallet @@ -188,7 +190,7 @@ bool BerkeleyEnvironment::Open(bilingual_str& err) } //! Construct an in-memory mock Berkeley environment for testing -BerkeleyEnvironment::BerkeleyEnvironment() +BerkeleyEnvironment::BerkeleyEnvironment() : m_use_shared_memory(false) { Reset(); @@ -247,7 +249,7 @@ const void* BerkeleyBatch::SafeDbt::get_data() const return m_dbt.get_data(); } -u_int32_t BerkeleyBatch::SafeDbt::get_size() const +uint32_t BerkeleyBatch::SafeDbt::get_size() const { return m_dbt.get_size(); } @@ -260,7 +262,7 @@ BerkeleyBatch::SafeDbt::operator Dbt*() bool BerkeleyDatabase::Verify(bilingual_str& errorStr) { fs::path walletDir = env->Directory(); - fs::path file_path = walletDir / strFile; + fs::path file_path = walletDir / m_filename; LogPrintf("Using BerkeleyDB version %s\n", BerkeleyDatabaseVersion()); LogPrintf("Using wallet %s\n", fs::PathToString(file_path)); @@ -274,6 +276,7 @@ bool BerkeleyDatabase::Verify(bilingual_str& errorStr) assert(m_refcount == 0); Db db(env->dbenv.get(), 0); + const std::string strFile = fs::PathToString(m_filename); int result = db.verify(strFile.c_str(), nullptr, nullptr, 0); if (result != 0) { errorStr = strprintf(_("%s corrupt. Try using the wallet tool bitcoin-wallet to salvage or restoring a backup."), fs::quoted(fs::PathToString(file_path))); @@ -296,11 +299,11 @@ BerkeleyDatabase::~BerkeleyDatabase() { if (env) { LOCK(cs_db); - env->CloseDb(strFile); + env->CloseDb(m_filename); assert(!m_db); - size_t erased = env->m_databases.erase(strFile); + size_t erased = env->m_databases.erase(m_filename); assert(erased == 1); - env->m_fileids.erase(strFile); + env->m_fileids.erase(fs::PathToString(m_filename)); } } @@ -312,13 +315,7 @@ BerkeleyBatch::BerkeleyBatch(BerkeleyDatabase& database, const bool read_only, b fFlushOnClose = fFlushOnCloseIn; env = database.env.get(); pdb = database.m_db.get(); - strFile = database.strFile; - if (!Exists(std::string("version"))) { - bool fTmp = fReadOnly; - fReadOnly = false; - Write(std::string("version"), CLIENT_VERSION); - fReadOnly = fTmp; - } + strFile = fs::PathToString(database.m_filename); } void BerkeleyDatabase::Open() @@ -334,6 +331,7 @@ void BerkeleyDatabase::Open() if (m_db == nullptr) { int ret; std::unique_ptr<Db> pdb_temp = std::make_unique<Db>(env->dbenv.get(), 0); + const std::string strFile = fs::PathToString(m_filename); bool fMockDb = env->IsMock(); if (fMockDb) { @@ -377,7 +375,7 @@ void BerkeleyBatch::Flush() nMinutes = 1; if (env) { // env is nullptr for dummy databases (i.e. in tests). Don't actually flush if env is nullptr so we don't segfault - env->dbenv->txn_checkpoint(nMinutes ? gArgs.GetIntArg("-dblogsize", DEFAULT_WALLET_DBLOGSIZE) * 1024 : 0, nMinutes, 0); + env->dbenv->txn_checkpoint(nMinutes ? m_database.m_max_log_mb * 1024 : 0, nMinutes, 0); } } @@ -406,11 +404,11 @@ void BerkeleyBatch::Close() Flush(); } -void BerkeleyEnvironment::CloseDb(const std::string& strFile) +void BerkeleyEnvironment::CloseDb(const fs::path& filename) { { LOCK(cs_db); - auto it = m_databases.find(strFile); + auto it = m_databases.find(filename); assert(it != m_databases.end()); BerkeleyDatabase& database = it->second.get(); if (database.m_db) { @@ -433,12 +431,12 @@ void BerkeleyEnvironment::ReloadDbEnv() return true; }); - std::vector<std::string> filenames; - for (auto it : m_databases) { + std::vector<fs::path> filenames; + for (const auto& it : m_databases) { filenames.push_back(it.first); } // Close the individual Db's - for (const std::string& filename : filenames) { + for (const fs::path& filename : filenames) { CloseDb(filename); } // Reset the environment @@ -453,9 +451,10 @@ bool BerkeleyDatabase::Rewrite(const char* pszSkip) while (true) { { LOCK(cs_db); + const std::string strFile = fs::PathToString(m_filename); if (m_refcount <= 0) { // Flush log data to the dat file - env->CloseDb(strFile); + env->CloseDb(m_filename); env->CheckpointLSN(strFile); m_refcount = -1; @@ -507,7 +506,7 @@ bool BerkeleyDatabase::Rewrite(const char* pszSkip) } if (fSuccess) { db.Close(); - env->CloseDb(strFile); + env->CloseDb(m_filename); if (pdbCopy->close(0)) fSuccess = false; } else { @@ -543,13 +542,14 @@ void BerkeleyEnvironment::Flush(bool fShutdown) LOCK(cs_db); bool no_dbs_accessed = true; for (auto& db_it : m_databases) { - std::string strFile = db_it.first; + const fs::path& filename = db_it.first; int nRefCount = db_it.second.get().m_refcount; if (nRefCount < 0) continue; + const std::string strFile = fs::PathToString(filename); LogPrint(BCLog::WALLETDB, "BerkeleyEnvironment::Flush: Flushing %s (refcount = %d)...\n", strFile, nRefCount); if (nRefCount == 0) { // Move log data to the dat file - CloseDb(strFile); + CloseDb(filename); LogPrint(BCLog::WALLETDB, "BerkeleyEnvironment::Flush: %s checkpoint\n", strFile); dbenv->txn_checkpoint(0, 0, 0); LogPrint(BCLog::WALLETDB, "BerkeleyEnvironment::Flush: %s detach\n", strFile); @@ -589,11 +589,12 @@ bool BerkeleyDatabase::PeriodicFlush() // Don't flush if there haven't been any batch writes for this database. if (m_refcount < 0) return false; + const std::string strFile = fs::PathToString(m_filename); LogPrint(BCLog::WALLETDB, "Flushing %s\n", strFile); int64_t nStart = GetTimeMillis(); // Flush wallet file so it's self contained - env->CloseDb(strFile); + env->CloseDb(m_filename); env->CheckpointLSN(strFile); m_refcount = -1; @@ -604,6 +605,7 @@ bool BerkeleyDatabase::PeriodicFlush() bool BerkeleyDatabase::Backup(const std::string& strDest) const { + const std::string strFile = fs::PathToString(m_filename); while (true) { { @@ -611,14 +613,14 @@ bool BerkeleyDatabase::Backup(const std::string& strDest) const if (m_refcount <= 0) { // Flush log data to the dat file - env->CloseDb(strFile); + env->CloseDb(m_filename); env->CheckpointLSN(strFile); // Copy wallet file - fs::path pathSrc = env->Directory() / strFile; + fs::path pathSrc = env->Directory() / m_filename; fs::path pathDest(fs::PathFromString(strDest)); if (fs::is_directory(pathDest)) - pathDest /= fs::PathFromString(strFile); + pathDest /= m_filename; try { if (fs::exists(pathDest) && fs::equivalent(pathSrc, pathDest)) { @@ -682,10 +684,10 @@ bool BerkeleyBatch::ReadAtCursor(CDataStream& ssKey, CDataStream& ssValue, bool& // Convert to streams ssKey.SetType(SER_DISK); ssKey.clear(); - ssKey.write({BytePtr(datKey.get_data()), datKey.get_size()}); + ssKey.write({AsBytePtr(datKey.get_data()), datKey.get_size()}); ssValue.SetType(SER_DISK); ssValue.clear(); - ssValue.write({BytePtr(datValue.get_data()), datValue.get_size()}); + ssValue.write({AsBytePtr(datValue.get_data()), datValue.get_size()}); return true; } @@ -757,7 +759,7 @@ bool BerkeleyBatch::ReadKey(CDataStream&& key, CDataStream& value) SafeDbt datValue; int ret = pdb->get(activeTxn, datKey, datValue, 0); if (ret == 0 && datValue.get_data() != nullptr) { - value.write({BytePtr(datValue.get_data()), datValue.get_size()}); + value.write({AsBytePtr(datValue.get_data()), datValue.get_size()}); return true; } return false; @@ -830,14 +832,14 @@ std::unique_ptr<BerkeleyDatabase> MakeBerkeleyDatabase(const fs::path& path, con std::unique_ptr<BerkeleyDatabase> db; { LOCK(cs_db); // Lock env.m_databases until insert in BerkeleyDatabase constructor - std::string data_filename = fs::PathToString(data_file.filename()); - std::shared_ptr<BerkeleyEnvironment> env = GetBerkeleyEnv(data_file.parent_path()); + fs::path data_filename = data_file.filename(); + std::shared_ptr<BerkeleyEnvironment> env = GetBerkeleyEnv(data_file.parent_path(), options.use_shared_memory); if (env->m_databases.count(data_filename)) { error = Untranslated(strprintf("Refusing to load database. Data file '%s' is already loaded.", fs::PathToString(env->Directory() / data_filename))); status = DatabaseStatus::FAILED_ALREADY_LOADED; return nullptr; } - db = std::make_unique<BerkeleyDatabase>(std::move(env), std::move(data_filename)); + db = std::make_unique<BerkeleyDatabase>(std::move(env), std::move(data_filename), options); } if (options.verify && !db->Verify(error)) { diff --git a/src/wallet/bdb.h b/src/wallet/bdb.h index b924890d81..ddab85521b 100644 --- a/src/wallet/bdb.h +++ b/src/wallet/bdb.h @@ -32,11 +32,9 @@ struct bilingual_str; namespace wallet { -static const unsigned int DEFAULT_WALLET_DBLOGSIZE = 100; -static const bool DEFAULT_WALLET_PRIVDB = true; struct WalletDatabaseFileId { - u_int8_t value[DB_FILE_ID_LEN]; + uint8_t value[DB_FILE_ID_LEN]; bool operator==(const WalletDatabaseFileId& rhs) const; }; @@ -53,11 +51,12 @@ private: public: std::unique_ptr<DbEnv> dbenv; - std::map<std::string, std::reference_wrapper<BerkeleyDatabase>> m_databases; + std::map<fs::path, std::reference_wrapper<BerkeleyDatabase>> m_databases; std::unordered_map<std::string, WalletDatabaseFileId> m_fileids; std::condition_variable_any m_db_in_use; + bool m_use_shared_memory; - explicit BerkeleyEnvironment(const fs::path& env_directory); + explicit BerkeleyEnvironment(const fs::path& env_directory, bool use_shared_memory); BerkeleyEnvironment(); ~BerkeleyEnvironment(); void Reset(); @@ -71,7 +70,7 @@ public: void Flush(bool fShutdown); void CheckpointLSN(const std::string& strFile); - void CloseDb(const std::string& strFile); + void CloseDb(const fs::path& filename); void ReloadDbEnv(); DbTxn* TxnBegin(int flags = DB_TXN_WRITE_NOSYNC) @@ -85,7 +84,7 @@ public: }; /** Get BerkeleyEnvironment given a directory path. */ -std::shared_ptr<BerkeleyEnvironment> GetBerkeleyEnv(const fs::path& env_directory); +std::shared_ptr<BerkeleyEnvironment> GetBerkeleyEnv(const fs::path& env_directory, bool use_shared_memory); class BerkeleyBatch; @@ -98,10 +97,10 @@ public: BerkeleyDatabase() = delete; /** Create DB handle to real database */ - BerkeleyDatabase(std::shared_ptr<BerkeleyEnvironment> env, std::string filename) : - WalletDatabase(), env(std::move(env)), strFile(std::move(filename)) + BerkeleyDatabase(std::shared_ptr<BerkeleyEnvironment> env, fs::path filename, const DatabaseOptions& options) : + WalletDatabase(), env(std::move(env)), m_filename(std::move(filename)), m_max_log_mb(options.max_log_mb) { - auto inserted = this->env->m_databases.emplace(strFile, std::ref(*this)); + auto inserted = this->env->m_databases.emplace(m_filename, std::ref(*this)); assert(inserted.second); } @@ -142,7 +141,7 @@ public: bool Verify(bilingual_str& error); /** Return path to main database filename */ - std::string Filename() override { return fs::PathToString(env->Directory() / strFile); } + std::string Filename() override { return fs::PathToString(env->Directory() / m_filename); } std::string Format() override { return "bdb"; } /** @@ -159,7 +158,8 @@ public: /** Database pointer. This is initialized lazily and reset during flushes, so it can be null. */ std::unique_ptr<Db> m_db; - std::string strFile; + fs::path m_filename; + int64_t m_max_log_mb; /** Make a BerkeleyBatch connected to this database */ std::unique_ptr<DatabaseBatch> MakeBatch(bool flush_on_close = true) override; @@ -182,7 +182,7 @@ class BerkeleyBatch : public DatabaseBatch // delegate to Dbt const void* get_data() const; - u_int32_t get_size() const; + uint32_t get_size() const; // conversion operator to access the underlying Dbt operator Dbt*(); 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 23faad027f..49e6bac462 100644 --- a/src/wallet/coinselection.cpp +++ b/src/wallet/coinselection.cpp @@ -50,8 +50,8 @@ struct { * The Branch and Bound algorithm is described in detail in Murch's Master Thesis: * https://murch.one/wp-content/uploads/2016/11/erhardt2016coinselection.pdf * - * @param const std::vector<CInputCoin>& utxo_pool The set of UTXOs that we are choosing from. - * These UTXOs will be sorted in descending order by effective value and the CInputCoins' + * @param const std::vector<OutputGroup>& utxo_pool The set of UTXO groups that we are choosing from. + * These UTXO groups will be sorted in descending order by effective value and the OutputGroups' * values are their effective values. * @param const CAmount& selection_target This is the value that we want to select. It is the lower * bound of the range. @@ -64,16 +64,15 @@ static const size_t TOTAL_TRIES = 100000; std::optional<SelectionResult> SelectCoinsBnB(std::vector<OutputGroup>& utxo_pool, const CAmount& selection_target, const CAmount& cost_of_change) { - SelectionResult result(selection_target); + SelectionResult result(selection_target, SelectionAlgorithm::BNB); CAmount curr_value = 0; - - std::vector<bool> curr_selection; // select the utxo at this index - curr_selection.reserve(utxo_pool.size()); + std::vector<size_t> curr_selection; // selected utxo indexes // Calculate curr_available_value CAmount curr_available_value = 0; for (const OutputGroup& utxo : utxo_pool) { - // Assert that this utxo is not negative. It should never be negative, effective value calculation should have removed it + // Assert that this utxo is not negative. It should never be negative, + // effective value calculation should have removed it assert(utxo.GetSelectionAmount() > 0); curr_available_value += utxo.GetSelectionAmount(); } @@ -85,15 +84,15 @@ std::optional<SelectionResult> SelectCoinsBnB(std::vector<OutputGroup>& utxo_poo std::sort(utxo_pool.begin(), utxo_pool.end(), descending); CAmount curr_waste = 0; - std::vector<bool> best_selection; + std::vector<size_t> best_selection; CAmount best_waste = MAX_MONEY; // Depth First search loop for choosing the UTXOs - for (size_t i = 0; i < TOTAL_TRIES; ++i) { + for (size_t curr_try = 0, utxo_pool_index = 0; curr_try < TOTAL_TRIES; ++curr_try, ++utxo_pool_index) { // Conditions for starting a backtrack bool backtrack = false; - if (curr_value + curr_available_value < selection_target || // Cannot possibly reach target with the amount remaining in the curr_available_value. - curr_value > selection_target + cost_of_change || // Selected value is out of range, go back and try other branch + if (curr_value + curr_available_value < selection_target || // Cannot possibly reach target with the amount remaining in the curr_available_value. + curr_value > selection_target + cost_of_change || // Selected value is out of range, go back and try other branch (curr_waste > best_waste && (utxo_pool.at(0).fee - utxo_pool.at(0).long_term_fee) > 0)) { // Don't select things which we know will be more wasteful if the waste is increasing backtrack = true; } else if (curr_value >= selection_target) { // Selected value is within range @@ -104,48 +103,44 @@ std::optional<SelectionResult> SelectCoinsBnB(std::vector<OutputGroup>& utxo_poo // explore any more UTXOs to avoid burning money like that. if (curr_waste <= best_waste) { best_selection = curr_selection; - best_selection.resize(utxo_pool.size()); 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; } - // Backtracking, moving backwards - if (backtrack) { - // Walk backwards to find the last included UTXO that still needs to have its omission branch traversed. - while (!curr_selection.empty() && !curr_selection.back()) { - curr_selection.pop_back(); - curr_available_value += utxo_pool.at(curr_selection.size()).GetSelectionAmount(); - } - + if (backtrack) { // Backtracking, moving backwards if (curr_selection.empty()) { // We have walked back to the first utxo and no branch is untraversed. All solutions searched break; } + // Add omitted UTXOs back to lookahead before traversing the omission branch of last included UTXO. + for (--utxo_pool_index; utxo_pool_index > curr_selection.back(); --utxo_pool_index) { + curr_available_value += utxo_pool.at(utxo_pool_index).GetSelectionAmount(); + } + // Output was included on previous iterations, try excluding now. - curr_selection.back() = false; - OutputGroup& utxo = utxo_pool.at(curr_selection.size() - 1); + assert(utxo_pool_index == curr_selection.back()); + OutputGroup& utxo = utxo_pool.at(utxo_pool_index); curr_value -= utxo.GetSelectionAmount(); curr_waste -= utxo.fee - utxo.long_term_fee; + curr_selection.pop_back(); } else { // Moving forwards, continuing down this branch - OutputGroup& utxo = utxo_pool.at(curr_selection.size()); + OutputGroup& utxo = utxo_pool.at(utxo_pool_index); // Remove this utxo from the curr_available_value utxo amount curr_available_value -= utxo.GetSelectionAmount(); - // Avoid searching a branch if the previous UTXO has the same value and same waste and was excluded. Since the ratio of fee to - // long term fee is the same, we only need to check if one of those values match in order to know that the waste is the same. - if (!curr_selection.empty() && !curr_selection.back() && - utxo.GetSelectionAmount() == utxo_pool.at(curr_selection.size() - 1).GetSelectionAmount() && - utxo.fee == utxo_pool.at(curr_selection.size() - 1).fee) { - curr_selection.push_back(false); - } else { + if (curr_selection.empty() || + // The previous index is included and therefore not relevant for exclusion shortcut + (utxo_pool_index - 1) == curr_selection.back() || + // Avoid searching a branch if the previous UTXO has the same value and same waste and was excluded. + // Since the ratio of fee to long term fee is the same, we only need to check if one of those values match in order to know that the waste is the same. + utxo.GetSelectionAmount() != utxo_pool.at(utxo_pool_index - 1).GetSelectionAmount() || + utxo.fee != utxo_pool.at(utxo_pool_index - 1).fee) + { // Inclusion branch first (Largest First Exploration) - curr_selection.push_back(true); + curr_selection.push_back(utxo_pool_index); curr_value += utxo.GetSelectionAmount(); curr_waste += utxo.fee - utxo.long_term_fee; } @@ -158,23 +153,23 @@ std::optional<SelectionResult> SelectCoinsBnB(std::vector<OutputGroup>& utxo_poo } // Set output set - for (size_t i = 0; i < best_selection.size(); ++i) { - if (best_selection.at(i)) { - result.AddInput(utxo_pool.at(i)); - } + for (const size_t& i : best_selection) { + result.AddInput(utxo_pool.at(i)); } + result.ComputeAndSetWaste(CAmount{0}); + assert(best_waste == result.GetWaste()); return result; } -std::optional<SelectionResult> SelectCoinsSRD(const std::vector<OutputGroup>& utxo_pool, CAmount target_value) +std::optional<SelectionResult> SelectCoinsSRD(const std::vector<OutputGroup>& utxo_pool, CAmount target_value, FastRandomContext& rng) { - SelectionResult result(target_value); + SelectionResult result(target_value, SelectionAlgorithm::SRD); std::vector<size_t> indexes; indexes.resize(utxo_pool.size()); std::iota(indexes.begin(), indexes.end(), 0); - Shuffle(indexes.begin(), indexes.end(), FastRandomContext()); + Shuffle(indexes.begin(), indexes.end(), rng); CAmount selected_eff_value = 0; for (const size_t i : indexes) { @@ -189,16 +184,27 @@ std::optional<SelectionResult> SelectCoinsSRD(const std::vector<OutputGroup>& ut return std::nullopt; } -static void ApproximateBestSubset(const std::vector<OutputGroup>& groups, const CAmount& nTotalLower, const CAmount& nTargetValue, +/** Find a subset of the OutputGroups that is at least as large as, but as close as possible to, the + * target amount; solve subset sum. + * param@[in] groups OutputGroups to choose from, sorted by value in descending order. + * param@[in] nTotalLower Total (effective) value of the UTXOs in groups. + * param@[in] nTargetValue Subset sum target, not including change. + * param@[out] vfBest Boolean vector representing the subset chosen that is closest to + * nTargetValue, with indices corresponding to groups. If the ith + * entry is true, that means the ith group in groups was selected. + * param@[out] nBest Total amount of subset chosen that is closest to nTargetValue. + * param@[in] iterations Maximum number of tries. + */ +static void ApproximateBestSubset(FastRandomContext& insecure_rand, const std::vector<OutputGroup>& groups, + const CAmount& nTotalLower, const CAmount& nTargetValue, std::vector<char>& vfBest, CAmount& nBest, int iterations = 1000) { std::vector<char> vfIncluded; + // Worst case "best" approximation is just all of the groups. vfBest.assign(groups.size(), true); nBest = nTotalLower; - FastRandomContext insecure_rand; - for (int nRep = 0; nRep < iterations && nBest != nTargetValue; nRep++) { vfIncluded.assign(groups.size(), false); @@ -221,6 +227,8 @@ static void ApproximateBestSubset(const std::vector<OutputGroup>& groups, const if (nTotal >= nTargetValue) { fReachedTarget = true; + // If the total is between nTargetValue and nBest, it's our new best + // approximation. if (nTotal < nBest) { nBest = nTotal; @@ -235,22 +243,25 @@ static void ApproximateBestSubset(const std::vector<OutputGroup>& groups, const } } -std::optional<SelectionResult> KnapsackSolver(std::vector<OutputGroup>& groups, const CAmount& nTargetValue) +std::optional<SelectionResult> KnapsackSolver(std::vector<OutputGroup>& groups, const CAmount& nTargetValue, + CAmount change_target, FastRandomContext& rng) { - SelectionResult result(nTargetValue); + SelectionResult result(nTargetValue, SelectionAlgorithm::KNAPSACK); // List of values less than target std::optional<OutputGroup> lowest_larger; + // Groups with selection amount smaller than the target and any change we might produce. + // Don't include groups larger than this, because they will only cause us to overshoot. std::vector<OutputGroup> applicable_groups; CAmount nTotalLower = 0; - Shuffle(groups.begin(), groups.end(), FastRandomContext()); + Shuffle(groups.begin(), groups.end(), rng); for (const OutputGroup& group : groups) { if (group.GetSelectionAmount() == nTargetValue) { result.AddInput(group); return result; - } else if (group.GetSelectionAmount() < nTargetValue + MIN_CHANGE) { + } else if (group.GetSelectionAmount() < nTargetValue + change_target) { applicable_groups.push_back(group); nTotalLower += group.GetSelectionAmount(); } else if (!lowest_larger || group.GetSelectionAmount() < lowest_larger->GetSelectionAmount()) { @@ -276,15 +287,15 @@ std::optional<SelectionResult> KnapsackSolver(std::vector<OutputGroup>& groups, std::vector<char> vfBest; CAmount nBest; - ApproximateBestSubset(applicable_groups, nTotalLower, nTargetValue, vfBest, nBest); - if (nBest != nTargetValue && nTotalLower >= nTargetValue + MIN_CHANGE) { - ApproximateBestSubset(applicable_groups, nTotalLower, nTargetValue + MIN_CHANGE, vfBest, nBest); + ApproximateBestSubset(rng, applicable_groups, nTotalLower, nTargetValue, vfBest, nBest); + if (nBest != nTargetValue && nTotalLower >= nTargetValue + change_target) { + ApproximateBestSubset(rng, applicable_groups, nTotalLower, nTargetValue + change_target, vfBest, nBest); } // If we have a bigger coin and (either the stochastic approximation didn't find a good solution, // or the next bigger coin is closer), return the bigger coin if (lowest_larger && - ((nBest != nTargetValue && nBest < nTargetValue + MIN_CHANGE) || lowest_larger->GetSelectionAmount() <= nBest)) { + ((nBest != nTargetValue && nBest < nTargetValue + change_target) || lowest_larger->GetSelectionAmount() <= nBest)) { result.AddInput(*lowest_larger); } else { for (unsigned int i = 0; i < applicable_groups.size(); i++) { @@ -293,7 +304,7 @@ std::optional<SelectionResult> KnapsackSolver(std::vector<OutputGroup>& groups, } } - if (LogAcceptCategory(BCLog::SELECTCOINS)) { + if (LogAcceptCategory(BCLog::SELECTCOINS, BCLog::Level::Debug)) { std::string log_message{"Coin selection best subset: "}; for (unsigned int i = 0; i < applicable_groups.size(); i++) { if (vfBest[i]) { @@ -313,29 +324,23 @@ std::optional<SelectionResult> KnapsackSolver(std::vector<OutputGroup>& groups, ******************************************************************************/ -void OutputGroup::Insert(const CInputCoin& output, int depth, bool from_me, size_t ancestors, size_t descendants, bool positive_only) { - // Compute the effective value first - const CAmount coin_fee = output.m_input_bytes < 0 ? 0 : m_effective_feerate.GetFee(output.m_input_bytes); - const CAmount ev = output.txout.nValue - coin_fee; - +void OutputGroup::Insert(const COutput& output, size_t ancestors, size_t descendants, bool positive_only) { // Filter for positive only here before adding the coin - if (positive_only && ev <= 0) return; + if (positive_only && output.GetEffectiveValue() <= 0) return; m_outputs.push_back(output); - CInputCoin& coin = m_outputs.back(); + COutput& coin = m_outputs.back(); - coin.m_fee = coin_fee; - fee += coin.m_fee; + fee += coin.GetFee(); - coin.m_long_term_fee = coin.m_input_bytes < 0 ? 0 : m_long_term_feerate.GetFee(coin.m_input_bytes); - long_term_fee += coin.m_long_term_fee; + coin.long_term_fee = coin.input_bytes < 0 ? 0 : m_long_term_feerate.GetFee(coin.input_bytes); + long_term_fee += coin.long_term_fee; - coin.effective_value = ev; - effective_value += coin.effective_value; + effective_value += coin.GetEffectiveValue(); - m_from_me &= from_me; - m_value += output.txout.nValue; - m_depth = std::min(m_depth, depth); + m_from_me &= coin.from_me; + m_value += coin.txout.nValue; + m_depth = std::min(m_depth, coin.depth); // ancestors here express the number of ancestors the new coin will end up having, which is // the sum, rather than the max; this will overestimate in the cases where multiple inputs // have common ancestors @@ -357,7 +362,7 @@ CAmount OutputGroup::GetSelectionAmount() const return m_subtract_fee_outputs ? m_value : effective_value; } -CAmount GetSelectionWaste(const std::set<CInputCoin>& inputs, CAmount change_cost, CAmount target, bool use_effective_value) +CAmount GetSelectionWaste(const std::set<COutput>& inputs, CAmount change_cost, CAmount target, bool use_effective_value) { // This function should not be called with empty inputs as that would mean the selection failed assert(!inputs.empty()); @@ -365,9 +370,9 @@ CAmount GetSelectionWaste(const std::set<CInputCoin>& inputs, CAmount change_cos // Always consider the cost of spending an input now vs in the future. CAmount waste = 0; CAmount selected_effective_value = 0; - for (const CInputCoin& coin : inputs) { - waste += coin.m_fee - coin.m_long_term_fee; - selected_effective_value += use_effective_value ? coin.effective_value : coin.txout.nValue; + for (const COutput& coin : inputs) { + waste += coin.GetFee() - coin.long_term_fee; + selected_effective_value += use_effective_value ? coin.GetEffectiveValue() : coin.txout.nValue; } if (change_cost) { @@ -384,6 +389,17 @@ CAmount GetSelectionWaste(const std::set<CInputCoin>& inputs, CAmount change_cos return waste; } +CAmount GenerateChangeTarget(CAmount payment_value, FastRandomContext& rng) +{ + if (payment_value <= CHANGE_LOWER / 2) { + return 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; + } +} + void SelectionResult::ComputeAndSetWaste(CAmount change_cost) { m_waste = GetSelectionWaste(m_selected_inputs, change_cost, m_target, m_use_effective); @@ -411,14 +427,14 @@ void SelectionResult::AddInput(const OutputGroup& group) m_use_effective = !group.m_subtract_fee_outputs; } -const std::set<CInputCoin>& SelectionResult::GetInputSet() const +const std::set<COutput>& SelectionResult::GetInputSet() const { return m_selected_inputs; } -std::vector<CInputCoin> SelectionResult::GetShuffledInputVector() const +std::vector<COutput> SelectionResult::GetShuffledInputVector() const { - std::vector<CInputCoin> coins(m_selected_inputs.begin(), m_selected_inputs.end()); + std::vector<COutput> coins(m_selected_inputs.begin(), m_selected_inputs.end()); Shuffle(coins.begin(), coins.end(), FastRandomContext()); return coins; } @@ -430,4 +446,22 @@ bool SelectionResult::operator<(SelectionResult other) const // As this operator is only used in std::min_element, we want the result that has more inputs when waste are equal. return *m_waste < *other.m_waste || (*m_waste == *other.m_waste && m_selected_inputs.size() > other.m_selected_inputs.size()); } + +std::string COutput::ToString() const +{ + return strprintf("COutput(%s, %d, %d) [%s]", outpoint.hash.ToString(), outpoint.n, depth, FormatMoney(txout.nValue)); +} + +std::string GetAlgorithmName(const SelectionAlgorithm algo) +{ + switch (algo) + { + case SelectionAlgorithm::BNB: return "bnb"; + case SelectionAlgorithm::KNAPSACK: return "knapsack"; + case SelectionAlgorithm::SRD: return "srd"; + case SelectionAlgorithm::MANUAL: return "manual"; + // No default case to allow for compiler to warn + } + assert(false); +} } // namespace wallet diff --git a/src/wallet/coinselection.h b/src/wallet/coinselection.h index 496a026999..e257d3f7c7 100644 --- a/src/wallet/coinselection.h +++ b/src/wallet/coinselection.h @@ -13,72 +13,116 @@ #include <optional> namespace wallet { -//! target minimum change amount -static constexpr CAmount MIN_CHANGE{COIN / 100}; -//! final minimum change amount after paying for fees -static const CAmount MIN_FINAL_CHANGE = MIN_CHANGE/2; +//! lower bound for randomly-chosen target change amount +static constexpr CAmount CHANGE_LOWER{50000}; +//! upper bound for randomly-chosen target change amount +static constexpr CAmount CHANGE_UPPER{1000000}; /** A UTXO under consideration for use in funding a new transaction. */ -class CInputCoin { +struct COutput { +private: + /** The output's value minus fees required to spend it.*/ + std::optional<CAmount> effective_value; + + /** The fee required to spend this output at the transaction's target feerate. */ + std::optional<CAmount> fee; + public: - CInputCoin(const CTransactionRef& tx, unsigned int i) - { - if (!tx) - throw std::invalid_argument("tx should not be null"); - if (i >= tx->vout.size()) - throw std::out_of_range("The output index is out of range"); - - outpoint = COutPoint(tx->GetHash(), i); - txout = tx->vout[i]; - effective_value = txout.nValue; - } + /** The outpoint identifying this UTXO */ + COutPoint outpoint; - CInputCoin(const CTransactionRef& tx, unsigned int i, int input_bytes) : CInputCoin(tx, i) - { - m_input_bytes = input_bytes; - } + /** The output itself */ + CTxOut txout; + + /** + * Depth in block chain. + * If > 0: the tx is on chain and has this many confirmations. + * If = 0: the tx is waiting confirmation. + * If < 0: a conflicting tx is on chain and has this many confirmations. */ + int depth; + + /** Pre-computed estimated size of this output as a fully-signed input in a transaction. Can be -1 if it could not be calculated */ + int input_bytes; + + /** Whether we have the private keys to spend this output */ + bool spendable; + + /** Whether we know how to spend this output, ignoring the lack of keys */ + bool solvable; + + /** + * Whether this output is considered safe to spend. Unconfirmed transactions + * from outside keys and unconfirmed replacement transactions are considered + * unsafe and will not be used to fund new spending transactions. + */ + bool safe; + + /** The time of the transaction containing this output as determined by CWalletTx::nTimeSmart */ + int64_t time; + + /** Whether the transaction containing this output is sent from the owning wallet */ + bool from_me; - CInputCoin(const COutPoint& outpoint_in, const CTxOut& txout_in) + /** The fee required to spend this output at the consolidation feerate. */ + CAmount long_term_fee{0}; + + COutput(const COutPoint& outpoint, const CTxOut& txout, int depth, int input_bytes, bool spendable, bool solvable, bool safe, int64_t time, bool from_me, const std::optional<CFeeRate> feerate = std::nullopt) + : outpoint{outpoint}, + txout{txout}, + depth{depth}, + input_bytes{input_bytes}, + spendable{spendable}, + solvable{solvable}, + safe{safe}, + time{time}, + from_me{from_me} { - outpoint = outpoint_in; - txout = txout_in; - effective_value = txout.nValue; + if (feerate) { + fee = input_bytes < 0 ? 0 : feerate.value().GetFee(input_bytes); + effective_value = txout.nValue - fee.value(); + } } - CInputCoin(const COutPoint& outpoint_in, const CTxOut& txout_in, int input_bytes) : CInputCoin(outpoint_in, txout_in) + COutput(const COutPoint& outpoint, const CTxOut& txout, int depth, int input_bytes, bool spendable, bool solvable, bool safe, int64_t time, bool from_me, const CAmount fees) + : COutput(outpoint, txout, depth, input_bytes, spendable, solvable, safe, time, from_me) { - m_input_bytes = input_bytes; + // if input_bytes is unknown, then fees should be 0, if input_bytes is known, then the fees should be a positive integer or 0 (input_bytes known and fees = 0 only happens in the tests) + assert((input_bytes < 0 && fees == 0) || (input_bytes > 0 && fees >= 0)); + fee = fees; + effective_value = txout.nValue - fee.value(); } - COutPoint outpoint; - CTxOut txout; - CAmount effective_value; - CAmount m_fee{0}; - CAmount m_long_term_fee{0}; - - /** Pre-computed estimated size of this output as a fully-signed input in a transaction. Can be -1 if it could not be calculated */ - int m_input_bytes{-1}; + std::string ToString() const; - bool operator<(const CInputCoin& rhs) const { + bool operator<(const COutput& rhs) const + { return outpoint < rhs.outpoint; } - bool operator!=(const CInputCoin& rhs) const { - return outpoint != rhs.outpoint; + CAmount GetFee() const + { + assert(fee.has_value()); + return fee.value(); } - bool operator==(const CInputCoin& rhs) const { - return outpoint == rhs.outpoint; + CAmount GetEffectiveValue() const + { + assert(effective_value.has_value()); + return effective_value.value(); } }; /** Parameters for one iteration of Coin Selection. */ -struct CoinSelectionParams -{ +struct CoinSelectionParams { + /** Randomness to use in the context of coin selection. */ + FastRandomContext& rng_fast; /** Size of a change output in bytes, determined by the output type. */ size_t change_output_size = 0; /** Size of the input to spend a change output in virtual bytes. */ size_t change_spend_size = 0; + /** 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}; /** Cost of creating the change output. */ CAmount m_change_fee{0}; /** Cost of creating the change output + cost of spending the change output in the future. */ @@ -100,17 +144,22 @@ struct CoinSelectionParams * reuse. Dust outputs are not eligible to be added to output groups and thus not considered. */ bool m_avoid_partial_spends = false; - CoinSelectionParams(size_t change_output_size, size_t change_spend_size, CFeeRate effective_feerate, - CFeeRate long_term_feerate, CFeeRate discard_feerate, size_t tx_noinputs_size, bool avoid_partial) : - change_output_size(change_output_size), - change_spend_size(change_spend_size), - m_effective_feerate(effective_feerate), - m_long_term_feerate(long_term_feerate), - m_discard_feerate(discard_feerate), - tx_noinputs_size(tx_noinputs_size), - m_avoid_partial_spends(avoid_partial) - {} - CoinSelectionParams() {} + CoinSelectionParams(FastRandomContext& rng_fast, size_t change_output_size, size_t change_spend_size, + CAmount min_change_target, CFeeRate effective_feerate, + CFeeRate long_term_feerate, CFeeRate discard_feerate, size_t tx_noinputs_size, bool avoid_partial) + : rng_fast{rng_fast}, + change_output_size(change_output_size), + change_spend_size(change_spend_size), + m_min_change_target(min_change_target), + m_effective_feerate(effective_feerate), + m_long_term_feerate(long_term_feerate), + m_discard_feerate(discard_feerate), + tx_noinputs_size(tx_noinputs_size), + m_avoid_partial_spends(avoid_partial) + { + } + CoinSelectionParams(FastRandomContext& rng_fast) + : rng_fast{rng_fast} {} }; /** Parameters for filtering which OutputGroups we may use in coin selection. @@ -139,7 +188,7 @@ struct CoinEligibilityFilter struct OutputGroup { /** The list of UTXOs contained in this output group. */ - std::vector<CInputCoin> m_outputs; + std::vector<COutput> m_outputs; /** Whether the UTXOs were sent by the wallet to itself. This is relevant because we may want at * least a certain number of confirmations on UTXOs received from outside wallets while trusting * our own UTXOs more. */ @@ -176,7 +225,7 @@ struct OutputGroup m_subtract_fee_outputs(params.m_subtract_fee_outputs) {} - void Insert(const CInputCoin& output, int depth, bool from_me, size_t ancestors, size_t descendants, bool positive_only); + void Insert(const COutput& output, size_t ancestors, size_t descendants, bool positive_only); bool EligibleForSpending(const CoinEligibilityFilter& eligibility_filter) const; CAmount GetSelectionAmount() const; }; @@ -198,23 +247,51 @@ struct OutputGroup * @param[in] use_effective_value Whether to use the input's effective value (when true) or the real value (when false). * @return The waste */ -[[nodiscard]] CAmount GetSelectionWaste(const std::set<CInputCoin>& inputs, CAmount change_cost, CAmount target, bool use_effective_value = true); +[[nodiscard]] CAmount GetSelectionWaste(const std::set<COutput>& inputs, CAmount change_cost, CAmount target, bool use_effective_value = true); + + +/** 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. + * The random value is between 50ksat and min(2 * payment_value, 1milsat) + * When payment_value <= 25ksat, the value is just 50ksat. + * + * Making change amounts similar to the payment value may help disguise which output(s) are payments + * are which ones are change. Using double the payment value may increase the number of inputs + * needed (and thus be more expensive in fees), but breaks analysis techniques which assume the + * 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). + */ +[[nodiscard]] CAmount GenerateChangeTarget(CAmount payment_value, FastRandomContext& rng); + +enum class SelectionAlgorithm : uint8_t +{ + BNB = 0, + KNAPSACK = 1, + SRD = 2, + MANUAL = 3, +}; + +std::string GetAlgorithmName(const SelectionAlgorithm algo); struct SelectionResult { private: /** Set of inputs selected by the algorithm to use in the transaction */ - std::set<CInputCoin> m_selected_inputs; - /** 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; + std::set<COutput> m_selected_inputs; /** 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: - explicit SelectionResult(const CAmount target) - : m_target(target) {} + /** 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) {} SelectionResult() = delete; @@ -230,9 +307,9 @@ public: [[nodiscard]] CAmount GetWaste() const; /** Get m_selected_inputs */ - const std::set<CInputCoin>& GetInputSet() const; - /** Get the vector of CInputCoins that will be used to fill in a CTransaction's vin */ - std::vector<CInputCoin> GetShuffledInputVector() const; + 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; }; @@ -246,10 +323,11 @@ std::optional<SelectionResult> SelectCoinsBnB(std::vector<OutputGroup>& utxo_poo * @param[in] target_value The target value to select for * @returns If successful, a SelectionResult, otherwise, std::nullopt */ -std::optional<SelectionResult> SelectCoinsSRD(const std::vector<OutputGroup>& utxo_pool, CAmount target_value); +std::optional<SelectionResult> SelectCoinsSRD(const std::vector<OutputGroup>& utxo_pool, CAmount target_value, FastRandomContext& rng); // Original coin selection algorithm as a fallback -std::optional<SelectionResult> KnapsackSolver(std::vector<OutputGroup>& groups, const CAmount& nTargetValue); +std::optional<SelectionResult> KnapsackSolver(std::vector<OutputGroup>& groups, const CAmount& nTargetValue, + CAmount change_target, FastRandomContext& rng); } // namespace wallet #endif // BITCOIN_WALLET_COINSELECTION_H diff --git a/src/wallet/context.cpp b/src/wallet/context.cpp index 800aa5bf9c..3d4bf9d703 100644 --- a/src/wallet/context.cpp +++ b/src/wallet/context.cpp @@ -5,6 +5,6 @@ #include <wallet/context.h> namespace wallet { -WalletContext::WalletContext() {} -WalletContext::~WalletContext() {} +WalletContext::WalletContext() = default; +WalletContext::~WalletContext() = default; } // namespace wallet diff --git a/src/wallet/db.cpp b/src/wallet/db.cpp index 0ed2658129..8e79ee678e 100644 --- a/src/wallet/db.cpp +++ b/src/wallet/db.cpp @@ -6,6 +6,7 @@ #include <chainparams.h> #include <fs.h> #include <logging.h> +#include <util/system.h> #include <wallet/db.h> #include <exception> @@ -137,4 +138,13 @@ bool IsSQLiteFile(const fs::path& path) // Check the application id matches our network magic return memcmp(Params().MessageStart(), app_id, 4) == 0; } + +void ReadDatabaseArgs(const ArgsManager& args, DatabaseOptions& options) +{ + // Override current options with args values, if any were specified + options.use_unsafe_sync = args.GetBoolArg("-unsafesqlitesync", options.use_unsafe_sync); + options.use_shared_memory = !args.GetBoolArg("-privdb", !options.use_shared_memory); + options.max_log_mb = args.GetIntArg("-dblogsize", options.max_log_mb); +} + } // namespace wallet diff --git a/src/wallet/db.h b/src/wallet/db.h index 5825b00e3a..f09844c37e 100644 --- a/src/wallet/db.h +++ b/src/wallet/db.h @@ -16,6 +16,7 @@ #include <optional> #include <string> +class ArgsManager; struct bilingual_str; namespace wallet { @@ -207,7 +208,12 @@ struct DatabaseOptions { std::optional<DatabaseFormat> require_format; uint64_t create_flags = 0; SecureString create_passphrase; - bool verify = true; + + // Specialized options. Not every option is supported by every backend. + bool verify = true; //!< Check data integrity on load. + bool use_unsafe_sync = false; //!< Disable file sync for faster performance. + bool use_shared_memory = false; //!< Let other processes access the database. + int64_t max_log_mb = 100; //!< Max log size to allow before consolidating. }; enum class DatabaseStatus { @@ -227,6 +233,7 @@ enum class DatabaseStatus { /** Recursively list database paths in directory. */ std::vector<fs::path> ListDatabases(const fs::path& path); +void ReadDatabaseArgs(const ArgsManager& args, DatabaseOptions& options); std::unique_ptr<WalletDatabase> MakeDatabase(const fs::path& path, const DatabaseOptions& options, DatabaseStatus& status, bilingual_str& error); fs::path BDBDataFile(const fs::path& path); diff --git a/src/wallet/dump.cpp b/src/wallet/dump.cpp index 6d8508fc72..f7fee443d0 100644 --- a/src/wallet/dump.cpp +++ b/src/wallet/dump.cpp @@ -19,10 +19,10 @@ namespace wallet { static const std::string DUMP_MAGIC = "BITCOIN_CORE_WALLET_DUMP"; uint32_t DUMP_VERSION = 1; -bool DumpWallet(CWallet& wallet, bilingual_str& error) +bool DumpWallet(const ArgsManager& args, CWallet& wallet, bilingual_str& error) { // Get the dumpfile - std::string dump_filename = gArgs.GetArg("-dumpfile", ""); + std::string dump_filename = args.GetArg("-dumpfile", ""); if (dump_filename.empty()) { error = _("No dump file provided. To use dump, -dumpfile=<filename> must be provided."); return false; @@ -41,7 +41,7 @@ bool DumpWallet(CWallet& wallet, bilingual_str& error) return false; } - CHashWriter hasher(0, 0); + HashWriter hasher{}; WalletDatabase& db = wallet.GetDatabase(); std::unique_ptr<DatabaseBatch> batch = db.MakeBatch(); @@ -114,10 +114,10 @@ static void WalletToolReleaseWallet(CWallet* wallet) delete wallet; } -bool CreateFromDump(const std::string& name, const fs::path& wallet_path, bilingual_str& error, std::vector<bilingual_str>& warnings) +bool CreateFromDump(const ArgsManager& args, const std::string& name, const fs::path& wallet_path, bilingual_str& error, std::vector<bilingual_str>& warnings) { // Get the dumpfile - std::string dump_filename = gArgs.GetArg("-dumpfile", ""); + std::string dump_filename = args.GetArg("-dumpfile", ""); if (dump_filename.empty()) { error = _("No dump file provided. To use createfromdump, -dumpfile=<filename> must be provided."); return false; @@ -132,7 +132,7 @@ bool CreateFromDump(const std::string& name, const fs::path& wallet_path, biling std::ifstream dump_file{dump_path}; // Compute the checksum - CHashWriter hasher(0, 0); + HashWriter hasher{}; uint256 checksum; // Check the magic and version @@ -171,7 +171,7 @@ bool CreateFromDump(const std::string& name, const fs::path& wallet_path, biling return false; } // Get the data file format with format_value as the default - std::string file_format = gArgs.GetArg("-format", format_value); + std::string file_format = args.GetArg("-format", format_value); if (file_format.empty()) { error = _("No wallet file format provided. To use createfromdump, -format=<format> must be provided."); return false; @@ -193,6 +193,7 @@ bool CreateFromDump(const std::string& name, const fs::path& wallet_path, biling DatabaseOptions options; DatabaseStatus status; + ReadDatabaseArgs(args, options); options.require_create = true; options.require_format = data_format; std::unique_ptr<WalletDatabase> database = MakeDatabase(wallet_path, options, status, error); diff --git a/src/wallet/dump.h b/src/wallet/dump.h index a879c4db35..bf683e9843 100644 --- a/src/wallet/dump.h +++ b/src/wallet/dump.h @@ -11,11 +11,12 @@ #include <vector> struct bilingual_str; +class ArgsManager; namespace wallet { class CWallet; -bool DumpWallet(CWallet& wallet, bilingual_str& error); -bool CreateFromDump(const std::string& name, const fs::path& wallet_path, bilingual_str& error, std::vector<bilingual_str>& warnings); +bool DumpWallet(const ArgsManager& args, CWallet& wallet, bilingual_str& error); +bool CreateFromDump(const ArgsManager& args, const std::string& name, const fs::path& wallet_path, bilingual_str& error, std::vector<bilingual_str>& warnings); } // namespace wallet #endif // BITCOIN_WALLET_DUMP_H 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 3552c14160..c2b8082eae 100644 --- a/src/wallet/feebumper.cpp +++ b/src/wallet/feebumper.cpp @@ -21,7 +21,7 @@ namespace wallet { //! 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) { - 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; } @@ -135,7 +135,7 @@ static CFeeRate EstimateFeeRate(const CWallet& wallet, const CWalletTx& wtx, con feerate += std::max(node_incremental_relay_fee, wallet_incremental_relay_fee); // Fee rate must also be at least the wallet's GetMinimumFeeRate - CFeeRate min_feerate(GetMinimumFeeRate(wallet, coin_control, /* feeCalc */ nullptr)); + CFeeRate min_feerate(GetMinimumFeeRate(wallet, coin_control, /*feeCalc=*/nullptr)); // Set the required fee rate for the replacement transaction in coin control. return std::max(feerate, min_feerate); @@ -213,32 +213,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; - CTransactionRef tx_new; - CAmount fee_ret; - int change_pos_in_out = -1; // No requested location for change - bilingual_str fail_reason; - FeeCalculation fee_calc_out; - if (!CreateTransaction(wallet, recipients, tx_new, fee_ret, change_pos_in_out, fail_reason, new_coin_control, fee_calc_out, false)) { - errors.push_back(Untranslated("Unable to create transaction.") + Untranslated(" ") + fail_reason); + constexpr int RANDOM_CHANGE_POSITION = -1; + 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 = fee_ret; + new_fee = txr.fee; // Write back transaction - mtx = CMutableTransaction(*tx_new); - // Mark new tx not replaceable, if requested. - if (!coin_control.m_signal_bip125_rbf.value_or(wallet.m_signal_rbf)) { - for (auto& input : mtx.vin) { - if (input.nSequence < 0xfffffffe) input.nSequence = 0xfffffffe; - } - } + mtx = CMutableTransaction(*txr.tx); return Result::OK; } 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 7a83dbc35d..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> @@ -80,9 +80,9 @@ void WalletInit::AddWalletOptions(ArgsManager& argsman) const argsman.AddArg("-walletrbf", strprintf("Send transactions with full-RBF opt-in enabled (RPC only, default: %u)", DEFAULT_WALLET_RBF), ArgsManager::ALLOW_ANY, OptionsCategory::WALLET); #ifdef USE_BDB - argsman.AddArg("-dblogsize=<n>", strprintf("Flush wallet database activity from memory to disk log every <n> megabytes (default: %u)", DEFAULT_WALLET_DBLOGSIZE), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::WALLET_DEBUG_TEST); + argsman.AddArg("-dblogsize=<n>", strprintf("Flush wallet database activity from memory to disk log every <n> megabytes (default: %u)", DatabaseOptions().max_log_mb), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::WALLET_DEBUG_TEST); argsman.AddArg("-flushwallet", strprintf("Run a thread to flush wallet periodically (default: %u)", DEFAULT_FLUSHWALLET), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::WALLET_DEBUG_TEST); - argsman.AddArg("-privdb", strprintf("Sets the DB_PRIVATE flag in the wallet db environment (default: %u)", DEFAULT_WALLET_PRIVDB), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::WALLET_DEBUG_TEST); + argsman.AddArg("-privdb", strprintf("Sets the DB_PRIVATE flag in the wallet db environment (default: %u)", !DatabaseOptions().use_shared_memory), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::WALLET_DEBUG_TEST); #else argsman.AddHiddenArgs({"-dblogsize", "-flushwallet", "-privdb"}); #endif @@ -94,6 +94,7 @@ void WalletInit::AddWalletOptions(ArgsManager& argsman) const #endif argsman.AddArg("-walletrejectlongchains", strprintf("Wallet will not create transactions that violate mempool chain limits (default: %u)", DEFAULT_WALLET_REJECT_LONG_CHAINS), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::WALLET_DEBUG_TEST); + argsman.AddArg("-walletcrosschain", strprintf("Allow reusing wallet files across chains (default: %u)", DEFAULT_WALLETCROSSCHAIN), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::WALLET_DEBUG_TEST); argsman.AddHiddenArgs({"-zapwallettxes"}); } diff --git a/src/wallet/interfaces.cpp b/src/wallet/interfaces.cpp index 9083c304b2..4fbc519e39 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) @@ -80,7 +82,10 @@ WalletTx MakeWalletTx(CWallet& wallet, const CWalletTx& wtx) //! Construct wallet tx status struct. WalletTxStatus MakeWalletTxStatus(const CWallet& wallet, const CWalletTx& wtx) + EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet) { + AssertLockHeld(wallet.cs_wallet); + WalletTxStatus result; result.block_height = wtx.state<TxStateConfirmed>() ? wtx.state<TxStateConfirmed>()->confirmed_block_height : @@ -107,7 +112,18 @@ 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; +} + +WalletTxOut MakeWalletTxOut(const CWallet& wallet, + const COutput& output) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet) +{ + WalletTxOut result; + result.txout = output.txout; + result.time = output.time; + result.depth_in_main_chain = output.depth; + result.is_spent = wallet.IsSpent(output.outpoint); return result; } @@ -132,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 { @@ -177,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 { @@ -231,28 +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); - CTransactionRef tx; - FeeCalculation fee_calc_out; - if (!CreateTransaction(*m_wallet, recipients, tx, fee, change_pos, - fail_reason, coin_control, fee_calc_out, sign)) { - return {}; - } - return tx; + 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; } void commitTransaction(CTransactionRef tx, WalletValueMap value_map, @@ -307,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; } @@ -419,8 +431,8 @@ public: for (const auto& entry : ListCoins(*m_wallet)) { auto& group = result[entry.first]; for (const auto& coin : entry.second) { - group.emplace_back(COutPoint(coin.tx->GetHash(), coin.i), - MakeWalletTxOut(*m_wallet, *coin.tx, coin.i, coin.nDepth)); + group.emplace_back(coin.outpoint, + MakeWalletTxOut(*m_wallet, coin)); } } return result; @@ -539,28 +551,34 @@ 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; + util::Result<std::unique_ptr<Wallet>> wallet{MakeWallet(m_context, CreateWallet(m_context, name, /*load_on_start=*/true, options, status, error, warnings))}; + return wallet ? std::move(wallet) : 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; + util::Result<std::unique_ptr<Wallet>> wallet{MakeWallet(m_context, LoadWallet(m_context, name, /*load_on_start=*/true, options, status, error, warnings))}; + return wallet ? std::move(wallet) : 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; + util::Result<std::unique_ptr<Wallet>> wallet{MakeWallet(m_context, RestoreWallet(m_context, backup_file, wallet_name, /*load_on_start=*/true, status, error, warnings))}; + return wallet ? std::move(wallet) : util::Error{error}; } std::string getWalletDir() override { diff --git a/src/wallet/load.cpp b/src/wallet/load.cpp index 633d8c5450..c06513588b 100644 --- a/src/wallet/load.cpp +++ b/src/wallet/load.cpp @@ -57,6 +57,7 @@ bool VerifyWallets(WalletContext& context) if (!args.IsArgSet("wallet")) { DatabaseOptions options; DatabaseStatus status; + ReadDatabaseArgs(args, options); bilingual_str error_string; options.require_existing = true; options.verify = false; @@ -84,6 +85,7 @@ bool VerifyWallets(WalletContext& context) DatabaseOptions options; DatabaseStatus status; + ReadDatabaseArgs(args, options); options.require_existing = true; options.verify = true; bilingual_str error_string; @@ -112,6 +114,7 @@ bool LoadWallets(WalletContext& context) } DatabaseOptions options; DatabaseStatus status; + ReadDatabaseArgs(*context.args, options); options.require_existing = true; options.verify = false; // No need to verify, assuming verified earlier in VerifyWallets() bilingual_str error; @@ -127,6 +130,8 @@ bool LoadWallets(WalletContext& context) chain.initError(error); return false; } + + NotifyWalletLoaded(context, pwallet); AddWallet(context, pwallet); } return true; diff --git a/src/wallet/receive.cpp b/src/wallet/receive.cpp index 1a6f06213c..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; } @@ -123,17 +108,17 @@ static CAmount GetCachableAmount(const CWallet& wallet, const CWalletTx& wtx, CW CAmount CachedTxGetCredit(const CWallet& wallet, const CWalletTx& wtx, const isminefilter& filter) { + AssertLockHeld(wallet.cs_wallet); + // Must wait until coinbase is safely deep enough in the chain before valuing it if (wallet.IsTxImmatureCoinBase(wtx)) 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; } @@ -144,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; } @@ -162,26 +145,21 @@ CAmount CachedTxGetChange(const CWallet& wallet, const CWalletTx& wtx) return wtx.nChangeCached; } -CAmount CachedTxGetImmatureCredit(const CWallet& wallet, const CWalletTx& wtx, bool fUseCache) +CAmount CachedTxGetImmatureCredit(const CWallet& wallet, const CWalletTx& wtx, const isminefilter& filter) { - if (wallet.IsTxImmatureCoinBase(wtx) && wallet.IsTxInMainChain(wtx)) { - return GetCachableAmount(wallet, wtx, CWalletTx::IMMATURE_CREDIT, ISMINE_SPENDABLE, !fUseCache); - } - - return 0; -} + AssertLockHeld(wallet.cs_wallet); -CAmount CachedTxGetImmatureWatchOnlyCredit(const CWallet& wallet, const CWalletTx& wtx, const bool fUseCache) -{ 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); + // Avoid caching ismine for NO or ALL cases (could remove this check and simplify in the future). bool allow_cache = (filter & ISMINE_ALL) && (filter & ISMINE_ALL) != ISMINE_ALL; @@ -189,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"); @@ -216,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(); @@ -241,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)) @@ -325,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; @@ -335,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; @@ -363,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; } } @@ -439,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 d7705b5262..9125b1e9aa 100644 --- a/src/wallet/receive.h +++ b/src/wallet/receive.h @@ -24,17 +24,15 @@ bool OutputIsChange(const CWallet& wallet, const CTxOut& txout) EXCLUSIVE_LOCKS_ CAmount OutputGetChange(const CWallet& wallet, const CTxOut& txout) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet); CAmount TxGetChange(const CWallet& wallet, const CTransaction& tx); -CAmount CachedTxGetCredit(const CWallet& wallet, const CWalletTx& wtx, const isminefilter& filter); +CAmount CachedTxGetCredit(const CWallet& wallet, const CWalletTx& wtx, const isminefilter& filter) + EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet); //! 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 CachedTxGetImmatureWatchOnlyCredit(const CWallet& wallet, const CWalletTx& wtx, const bool fUseCache = true); -// TODO: Remove "NO_THREAD_SAFETY_ANALYSIS" and replace it with the correct -// annotation "EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet)". The -// annotation "NO_THREAD_SAFETY_ANALYSIS" was temporarily added to avoid -// having to resolve the issue of member access into incomplete type CWallet. -CAmount CachedTxGetAvailableCredit(const CWallet& wallet, const CWalletTx& wtx, bool fUseCache = true, const isminefilter& filter = ISMINE_SPENDABLE) NO_THREAD_SAFETY_ANALYSIS; +CAmount CachedTxGetImmatureCredit(const CWallet& wallet, const CWalletTx& wtx, const isminefilter& filter) + EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet); +CAmount CachedTxGetAvailableCredit(const CWallet& wallet, const CWalletTx& wtx, const isminefilter& filter = ISMINE_SPENDABLE) + EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet); struct COutputEntry { CTxDestination destination; @@ -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 51587a64a3..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 @@ -239,7 +237,7 @@ RPCHelpMan addmultisigaddress() {RPCResult::Type::STR, "address", "The value of the new multisig address"}, {RPCResult::Type::STR_HEX, "redeemScript", "The string value of the hex-encoded redemption script"}, {RPCResult::Type::STR, "descriptor", "The descriptor for this multisig"}, - {RPCResult::Type::ARR, "warnings", /* optional */ true, "Any warnings resulting from the creation of this multisig", + {RPCResult::Type::ARR, "warnings", /*optional=*/true, "Any warnings resulting from the creation of this multisig", { {RPCResult::Type::STR, "", ""}, }}, @@ -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); @@ -264,7 +262,7 @@ RPCHelpMan addmultisigaddress() if (!request.params[2].isNull()) label = LabelFromValue(request.params[2]); - int required = request.params[0].get_int(); + int required = request.params[0].getInt<int>(); // Get the public keys const UniValue& keys_or_addrs = request.params[1].get_array(); @@ -302,11 +300,11 @@ RPCHelpMan addmultisigaddress() result.pushKV("descriptor", descriptor->ToString()); UniValue warnings(UniValue::VARR); - if (!request.params[3].isNull() && OutputTypeFromDestination(dest) != output_type) { + if (descriptor->GetOutputType() != output_type) { // Only warns if the user has explicitly chosen an address type we cannot generate warnings.push_back("Unable to make chosen address type, please ensure no uncompressed public keys are present."); } - if (warnings.size()) result.pushKV("warnings", warnings); + if (!warnings.empty()) result.pushKV("warnings", warnings); return result; }, @@ -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"); @@ -340,9 +338,9 @@ RPCHelpMan keypoolrefill() // 0 is interpreted by TopUpKeyPool() as the default keypool size given by -keypool unsigned int kpSize = 0; if (!request.params[0].isNull()) { - if (request.params[0].get_int() < 0) + if (request.params[0].getInt<int>() < 0) throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, expected valid size."); - kpSize = (unsigned int)request.params[0].get_int(); + kpSize = (unsigned int)request.params[0].getInt<int>(); } EnsureWalletIsUnlocked(*pwallet); @@ -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()); @@ -597,7 +595,7 @@ RPCHelpMan getaddressinfo() DescriptorScriptPubKeyMan* desc_spk_man = dynamic_cast<DescriptorScriptPubKeyMan*>(spk_man); if (desc_spk_man) { std::string desc_str; - if (desc_spk_man->GetDescriptorString(desc_str, /* priv */ false)) { + if (desc_spk_man->GetDescriptorString(desc_str, /*priv=*/false)) { ret.pushKV("parent_desc", desc_str); } } @@ -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 228564fae4..306053fd0c 100644 --- a/src/wallet/rpc/backup.cpp +++ b/src/wallet/rpc/backup.cpp @@ -26,8 +26,6 @@ #include <tuple> #include <string> -#include <boost/algorithm/string.hpp> - #include <univalue.h> @@ -102,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, rescanwallet 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{ @@ -124,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"); @@ -140,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(); @@ -192,7 +192,7 @@ RPCHelpMan importprivkey() RescanWallet(*pwallet, reserver); } - return NullUniValue; + return UniValue::VNULL; }, }; } @@ -200,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, rescanwallet 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, "", ""}, @@ -226,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); @@ -296,7 +299,7 @@ RPCHelpMan importaddress() } } - return NullUniValue; + return UniValue::VNULL; }, }; } @@ -314,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())) { @@ -349,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"); @@ -373,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); @@ -390,7 +393,7 @@ RPCHelpMan removeprunedfunds() throw JSONRPCError(RPC_INVALID_PARAMETER, "Transaction does not exist in wallet."); } - return NullUniValue; + return UniValue::VNULL; }, }; } @@ -402,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, rescanwallet 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{ @@ -420,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); @@ -475,7 +480,7 @@ RPCHelpMan importpubkey() } } - return NullUniValue; + return UniValue::VNULL; }, }; } @@ -485,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"}, }, @@ -501,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); @@ -546,8 +551,7 @@ RPCHelpMan importwallet() if (line.empty() || line[0] == '#') continue; - std::vector<std::string> vstr; - boost::split(vstr, line, boost::is_any_of(" ")); + std::vector<std::string> vstr = SplitString(line, ' '); if (vstr.size() < 2) continue; CKey key = DecodeSecret(vstr[0]); @@ -633,7 +637,7 @@ RPCHelpMan importwallet() if (!fGood) throw JSONRPCError(RPC_WALLET_ERROR, "Error adding some keys/scripts to wallet"); - return NullUniValue; + return UniValue::VNULL; }, }; } @@ -657,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); @@ -707,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); @@ -915,7 +919,7 @@ static std::string RecurseImportData(const CScript& script, ImportData& import_d case TxoutType::WITNESS_V1_TAPROOT: return "unrecognized script"; } // no default case, so the compiler can warn about missing cases - CHECK_NONFATAL(false); + NONFATAL_UNREACHABLE(); } static UniValue ProcessImportLegacy(ImportData& import_data, std::map<CKeyID, CPubKey>& pubkey_map, std::map<CKeyID, CKey>& privkey_map, std::set<CScript>& script_pub_keys, bool& have_solving_data, const UniValue& data, std::vector<CKeyID>& ordered_pubkeys) @@ -1235,7 +1239,7 @@ static int64_t GetImportTimestamp(const UniValue& data, int64_t now) if (data.exists("timestamp")) { const UniValue& timestamp = data["timestamp"]; if (timestamp.isNum()) { - return timestamp.get_int64(); + return timestamp.getInt<int64_t>(); } else if (timestamp.isStr() && timestamp.get_str() == "now") { return now; } @@ -1252,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, rescanwallet 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", @@ -1260,7 +1266,7 @@ 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"} + /*oneline_description=*/"", {"\"<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" @@ -1268,7 +1274,7 @@ RPCHelpMan importmulti() " \"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"} + /*oneline_description=*/"", {"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"}, @@ -1293,7 +1299,7 @@ RPCHelpMan importmulti() "\"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\""}, }, @@ -1322,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 @@ -1476,7 +1482,7 @@ static UniValue ProcessDescriptorImport(CWallet& wallet, const UniValue& data, c next_index = range_start; if (data.exists("next_index")) { - next_index = data["next_index"].get_int64(); + next_index = data["next_index"].getInt<int64_t>(); // bound checks if (next_index < range_start || next_index >= range_end) { throw JSONRPCError(RPC_INVALID_PARAMETER, "next_index is out of range"); @@ -1595,8 +1601,8 @@ RPCHelpMan importdescriptors() " 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"} + "of all descriptors being imported will be scanned as well as the mempool.", + /*oneline_description=*/"", {"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"}, @@ -1624,13 +1630,13 @@ RPCHelpMan importdescriptors() }, RPCExamples{ HelpExampleCli("importdescriptors", "'[{ \"desc\": \"<my descriptor>\", \"timestamp\":1455191478, \"internal\": true }, " - "{ \"desc\": \"<my desccriptor 2>\", \"label\": \"example 2\", \"timestamp\": 1455191480 }]'") + + "{ \"desc\": \"<my descriptor 2>\", \"label\": \"example 2\", \"timestamp\": 1455191480 }]'") + HelpExampleCli("importdescriptors", "'[{ \"desc\": \"<my descriptor>\", \"timestamp\":1455191478, \"active\": true, \"range\": [0,100], \"label\": \"<my bech32 wallet>\" }]'") }, [&](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 @@ -1748,13 +1754,13 @@ RPCHelpMan listdescriptors() {RPCResult::Type::OBJ, "", "", { {RPCResult::Type::STR, "desc", "Descriptor string representation"}, {RPCResult::Type::NUM, "timestamp", "The creation time of the descriptor"}, - {RPCResult::Type::BOOL, "active", "Activeness flag"}, - {RPCResult::Type::BOOL, "internal", true, "Whether this is an internal or external descriptor; defined only for active descriptors"}, - {RPCResult::Type::ARR_FIXED, "range", true, "Defined only for ranged descriptors", { + {RPCResult::Type::BOOL, "active", "Whether this descriptor is currently used to generate new addresses"}, + {RPCResult::Type::BOOL, "internal", /*optional=*/true, "True if this descriptor is used to generate change addresses. False if this descriptor is used to generate receiving addresses; defined only for active descriptors"}, + {RPCResult::Type::ARR_FIXED, "range", /*optional=*/true, "Defined only for ranged descriptors", { {RPCResult::Type::NUM, "", "Range start inclusive"}, {RPCResult::Type::NUM, "", "Range end inclusive"}, }}, - {RPCResult::Type::NUM, "next", true, "The next index to generate addresses from; defined only for ranged descriptors"}, + {RPCResult::Type::NUM, "next", /*optional=*/true, "The next index to generate addresses from; defined only for ranged descriptors"}, }}, }} }}, @@ -1765,7 +1771,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"); @@ -1834,7 +1840,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 @@ -1847,7 +1853,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 035541babd..50766f20eb 100644 --- a/src/wallet/rpc/coins.cpp +++ b/src/wallet/rpc/coins.cpp @@ -18,40 +18,40 @@ namespace wallet { static CAmount GetReceived(const CWallet& wallet, const UniValue& params, bool by_label) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet) { - std::set<CTxDestination> address_set; - + std::vector<CTxDestination> addresses; if (by_label) { // Get the set of addresses assigned to label - std::string label = LabelFromValue(params[0]); - address_set = wallet.GetLabelAddresses(label); + 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 CTxDestination dest = DecodeDestination(params[0].get_str()); if (!IsValidDestination(dest)) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Bitcoin address"); } - CScript script_pub_key = GetScriptForDestination(dest); - if (!wallet.IsMine(script_pub_key)) { - throw JSONRPCError(RPC_WALLET_ERROR, "Address not found in wallet"); + addresses.emplace_back(dest); + } + + // Filter by own scripts only + std::set<CScript> output_scripts; + for (const auto& address : addresses) { + auto output_script{GetScriptForDestination(address)}; + if (wallet.IsMine(output_script)) { + output_scripts.insert(output_script); } - address_set.insert(dest); + } + + if (output_scripts.empty()) { + throw JSONRPCError(RPC_WALLET_ERROR, "Address not found in wallet"); } // Minimum confirmations int min_depth = 1; if (!params[1].isNull()) - min_depth = params[1].get_int(); + min_depth = params[1].getInt<int>(); const bool include_immature_coinbase{params[2].isNull() ? false : params[2].get_bool()}; - // Excluding coinbase outputs is deprecated - // It can be enabled by setting deprecatedrpc=exclude_coinbase - const bool include_coinbase{!wallet.chain().rpcEnableDeprecated("exclude_coinbase")}; - - if (include_immature_coinbase && !include_coinbase) { - throw JSONRPCError(RPC_INVALID_PARAMETER, "include_immature_coinbase is incompatible with deprecated exclude_coinbase"); - } - // Tally CAmount amount = 0; for (const std::pair<const uint256, CWalletTx>& wtx_pair : wallet.mapWallet) { @@ -59,15 +59,14 @@ static CAmount GetReceived(const CWallet& wallet, const UniValue& params, bool b int depth{wallet.GetTxDepthInMainChain(wtx)}; if (depth < min_depth // Coinbase with less than 1 confirmation is no longer in the main chain - || (wtx.IsCoinBase() && (depth < 1 || !include_coinbase)) + || (wtx.IsCoinBase() && (depth < 1)) || (wallet.IsTxImmatureCoinBase(wtx) && !include_immature_coinbase)) { continue; } for (const CTxOut& txout : wtx.tx->vout) { - CTxDestination address; - if (ExtractDestination(txout.scriptPubKey, address) && wallet.IsMine(address) && address_set.count(address)) { + if (output_scripts.count(txout.scriptPubKey) > 0) { amount += txout.nValue; } } @@ -104,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 @@ -112,7 +111,7 @@ RPCHelpMan getreceivedbyaddress() LOCK(pwallet->cs_wallet); - return ValueFromAmount(GetReceived(*pwallet, request.params, /* by_label */ false)); + return ValueFromAmount(GetReceived(*pwallet, request.params, /*by_label=*/false)); }, }; } @@ -145,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 @@ -153,7 +152,7 @@ RPCHelpMan getreceivedbylabel() LOCK(pwallet->cs_wallet); - return ValueFromAmount(GetReceived(*pwallet, request.params, /* by_label */ true)); + return ValueFromAmount(GetReceived(*pwallet, request.params, /*by_label=*/true)); }, }; } @@ -185,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 @@ -200,7 +199,7 @@ RPCHelpMan getbalance() int min_depth = 0; if (!request.params[1].isNull()) { - min_depth = request.params[1].get_int(); + min_depth = request.params[1].getInt<int>(); } bool include_watchonly = ParseIncludeWatchonly(request.params[2], *pwallet); @@ -224,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 @@ -283,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 @@ -324,7 +323,7 @@ RPCHelpMan lockunspent() }); const uint256 txid(ParseHashO(o, "txid")); - const int nOutput = find_value(o, "vout").get_int(); + const int nOutput = find_value(o, "vout").getInt<int>(); if (nOutput < 0) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, vout cannot be negative"); } @@ -342,11 +341,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"); @@ -408,7 +407,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); @@ -460,7 +459,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 @@ -544,6 +543,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."}, @@ -560,18 +562,18 @@ 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].get_int(); + nMinDepth = request.params[0].getInt<int>(); } int nMaxDepth = 9999999; if (!request.params[1].isNull()) { RPCTypeCheckArgument(request.params[1], UniValue::VNUM); - nMaxDepth = request.params[1].get_int(); + nMaxDepth = request.params[1].getInt<int>(); } std::set<CTxDestination> destinations; @@ -623,7 +625,7 @@ RPCHelpMan listunspent() nMinimumSumAmount = AmountFromValue(options["minimumSumAmount"]); if (options.exists("maximumCount")) - nMaximumCount = options["maximumCount"].get_int64(); + nMaximumCount = options["maximumCount"].getInt<int64_t>(); } // Make sure the results are valid at least up to the most recent block @@ -639,7 +641,7 @@ RPCHelpMan listunspent() cctl.m_max_depth = nMaxDepth; cctl.m_include_unsafe_inputs = include_unsafe; LOCK(pwallet->cs_wallet); - AvailableCoins(*pwallet, vecOutputs, &cctl, nMinimumAmount, nMaximumAmount, nMinimumSumAmount, nMaximumCount); + vecOutputs = AvailableCoinsListUnspent(*pwallet, &cctl, nMinimumAmount, nMaximumAmount, nMinimumSumAmount, nMaximumCount).All(); } LOCK(pwallet->cs_wallet); @@ -648,16 +650,16 @@ RPCHelpMan listunspent() for (const COutput& out : vecOutputs) { CTxDestination address; - const CScript& scriptPubKey = out.tx->tx->vout[out.i].scriptPubKey; + const CScript& scriptPubKey = out.txout.scriptPubKey; bool fValidAddress = ExtractDestination(scriptPubKey, address); - bool reused = avoid_reuse && pwallet->IsSpentKey(out.tx->GetHash(), out.i); + bool reused = avoid_reuse && pwallet->IsSpentKey(scriptPubKey); if (destinations.size() && (!fValidAddress || !destinations.count(address))) continue; UniValue entry(UniValue::VOBJ); - entry.pushKV("txid", out.tx->GetHash().GetHex()); - entry.pushKV("vout", out.i); + entry.pushKV("txid", out.outpoint.hash.GetHex()); + entry.pushKV("vout", (int)out.outpoint.n); if (fValidAddress) { entry.pushKV("address", EncodeDestination(address)); @@ -702,29 +704,30 @@ RPCHelpMan listunspent() } entry.pushKV("scriptPubKey", HexStr(scriptPubKey)); - entry.pushKV("amount", ValueFromAmount(out.tx->tx->vout[out.i].nValue)); - entry.pushKV("confirmations", out.nDepth); - if (!out.nDepth) { + entry.pushKV("amount", ValueFromAmount(out.txout.nValue)); + entry.pushKV("confirmations", out.depth); + if (!out.depth) { size_t ancestor_count, descendant_count, ancestor_size; CAmount ancestor_fees; - pwallet->chain().getTransactionAncestry(out.tx->GetHash(), ancestor_count, descendant_count, &ancestor_size, &ancestor_fees); + pwallet->chain().getTransactionAncestry(out.outpoint.hash, ancestor_count, descendant_count, &ancestor_size, &ancestor_fees); if (ancestor_count) { entry.pushKV("ancestorcount", uint64_t(ancestor_count)); entry.pushKV("ancestorsize", uint64_t(ancestor_size)); entry.pushKV("ancestorfees", uint64_t(ancestor_fees)); } } - entry.pushKV("spendable", out.fSpendable); - entry.pushKV("solvable", out.fSolvable); - if (out.fSolvable) { + entry.pushKV("spendable", out.spendable); + entry.pushKV("solvable", out.solvable); + if (out.solvable) { std::unique_ptr<SigningProvider> provider = pwallet->GetSolvingProvider(scriptPubKey); if (provider) { auto descriptor = InferDescriptor(scriptPubKey, *provider); entry.pushKV("desc", descriptor->ToString()); } } + PushParentDescriptors(*pwallet, scriptPubKey, entry); if (avoid_reuse) entry.pushKV("reused", reused); - entry.pushKV("safe", out.fSafe); + entry.pushKV("safe", out.safe); results.push_back(entry); } diff --git a/src/wallet/rpc/encrypt.cpp b/src/wallet/rpc/encrypt.cpp index 802cc63d6d..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; @@ -54,7 +54,7 @@ RPCHelpMan walletpassphrase() strWalletPass = request.params[0].get_str().c_str(); // Get the timeout - nSleepTime = request.params[1].get_int64(); + nSleepTime = request.params[1].getInt<int64_t>(); // Timeout cannot be negative, otherwise it will relock immediately if (nSleepTime < 0) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Timeout cannot be negative."); @@ -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 433b5a1815..c6e7588fb4 100644 --- a/src/wallet/rpc/spend.cpp +++ b/src/wallet/rpc/spend.cpp @@ -9,10 +9,12 @@ #include <rpc/rawtransaction_util.h> #include <rpc/util.h> #include <util/fees.h> +#include <util/rbf.h> #include <util/translation.h> #include <util/vector.h> #include <wallet/coincontrol.h> #include <wallet/feebumper.h> +#include <wallet/fees.h> #include <wallet/rpc/util.h> #include <wallet/spend.h> #include <wallet/wallet.h> @@ -21,7 +23,8 @@ namespace wallet { -static void ParseRecipients(const UniValue& address_amounts, const UniValue& subtract_fee_outputs, std::vector<CRecipient> &recipients) { +static void ParseRecipients(const UniValue& address_amounts, const UniValue& subtract_fee_outputs, std::vector<CRecipient>& recipients) +{ std::set<CTxDestination> destinations; int i = 0; for (const std::string& address: address_amounts.getKeys()) { @@ -51,6 +54,93 @@ static void ParseRecipients(const UniValue& address_amounts, const UniValue& sub } } +static void InterpretFeeEstimationInstructions(const UniValue& conf_target, const UniValue& estimate_mode, const UniValue& fee_rate, UniValue& options) +{ + if (options.exists("conf_target") || options.exists("estimate_mode")) { + if (!conf_target.isNull() || !estimate_mode.isNull()) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Pass conf_target and estimate_mode either as arguments or in the options object, but not both"); + } + } else { + options.pushKV("conf_target", conf_target); + options.pushKV("estimate_mode", estimate_mode); + } + if (options.exists("fee_rate")) { + if (!fee_rate.isNull()) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Pass the fee_rate either as an argument, or in the options object, but not both"); + } + } else { + options.pushKV("fee_rate", fee_rate); + } + if (!options["conf_target"].isNull() && (options["estimate_mode"].isNull() || (options["estimate_mode"].get_str() == "unset"))) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Specify estimate_mode"); + } +} + +static UniValue FinishTransaction(const std::shared_ptr<CWallet> pwallet, const UniValue& options, const CMutableTransaction& rawTx) +{ + // Make a blank psbt + PartiallySignedTransaction psbtx(rawTx); + + // First fill transaction with our data without signing, + // so external signers are not asked sign more than once. + bool complete; + pwallet->FillPSBT(psbtx, complete, SIGHASH_DEFAULT, false, true); + const TransactionError err{pwallet->FillPSBT(psbtx, complete, SIGHASH_DEFAULT, true, false)}; + if (err != TransactionError::OK) { + throw JSONRPCTransactionError(err); + } + + CMutableTransaction mtx; + complete = FinalizeAndExtractPSBT(psbtx, mtx); + + UniValue result(UniValue::VOBJ); + + const bool psbt_opt_in{options.exists("psbt") && options["psbt"].get_bool()}; + bool add_to_wallet{options.exists("add_to_wallet") ? options["add_to_wallet"].get_bool() : true}; + if (psbt_opt_in || !complete || !add_to_wallet) { + // Serialize the PSBT + CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION); + ssTx << psbtx; + result.pushKV("psbt", EncodeBase64(ssTx.str())); + } + + if (complete) { + std::string hex{EncodeHexTx(CTransaction(mtx))}; + CTransactionRef tx(MakeTransactionRef(std::move(mtx))); + result.pushKV("txid", tx->GetHash().GetHex()); + if (add_to_wallet && !psbt_opt_in) { + pwallet->CommitTransaction(tx, {}, /*orderForm=*/{}); + } else { + result.pushKV("hex", hex); + } + } + result.pushKV("complete", complete); + + return result; +} + +static void PreventOutdatedOptions(const UniValue& options) +{ + if (options.exists("feeRate")) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Use fee_rate (" + CURRENCY_ATOM + "/vB) instead of feeRate"); + } + if (options.exists("changeAddress")) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Use change_address instead of changeAddress"); + } + if (options.exists("changePosition")) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Use change_position instead of changePosition"); + } + if (options.exists("includeWatching")) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Use include_watching instead of includeWatching"); + } + if (options.exists("lockUnspents")) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Use lock_unspents instead of lockUnspents"); + } + if (options.exists("subtractFeeFromOutputs")) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Use subtract_fee_from_outputs instead of subtractFeeFromOutputs"); + } +} + UniValue SendMoney(CWallet& wallet, const CCoinControl &coin_control, std::vector<CRecipient> &recipients, mapValue_t map_value, bool verbose) { EnsureWalletIsUnlocked(wallet); @@ -65,20 +155,17 @@ UniValue SendMoney(CWallet& wallet, const CCoinControl &coin_control, std::vecto std::shuffle(recipients.begin(), recipients.end(), FastRandomContext()); // Send - CAmount nFeeRequired = 0; - int nChangePosRet = -1; - bilingual_str error; - CTransactionRef tx; - FeeCalculation fee_calc_out; - const bool fCreated = CreateTransaction(wallet, recipients, tx, nFeeRequired, nChangePosRet, error, coin_control, fee_calc_out, true); - if (!fCreated) { - throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, error.original); + constexpr int RANDOM_CHANGE_POSITION = -1; + auto res = CreateTransaction(wallet, recipients, RANDOM_CHANGE_POSITION, coin_control, true); + if (!res) { + throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, util::ErrorString(res).original); } + 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(); @@ -108,7 +195,7 @@ static void SetFeeEstimateMode(const CWallet& wallet, CCoinControl& cc, const Un throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot specify both estimate_mode and fee_rate"); } // Fee rates in sat/vB cannot represent more than 3 significant digits. - cc.m_feerate = CFeeRate{AmountFromValue(fee_rate, /* decimals */ 3)}; + cc.m_feerate = CFeeRate{AmountFromValue(fee_rate, /*decimals=*/3)}; if (override_min_fee) cc.fOverrideFeeRate = true; // Default RBF to true for explicit fee_rate, if unset. if (!cc.m_signal_bip125_rbf) cc.m_signal_bip125_rbf = true; @@ -139,8 +226,8 @@ RPCHelpMan sendtoaddress() "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"}, {"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."}, @@ -174,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 @@ -203,7 +290,7 @@ RPCHelpMan sendtoaddress() // We also enable partial spend avoidance if reuse avoidance is set. coin_control.m_avoid_partial_spends |= coin_control.m_avoid_address_reuse; - SetFeeEstimateMode(*pwallet, coin_control, /* conf_target */ request.params[6], /* estimate_mode */ request.params[7], /* fee_rate */ request.params[9], /* override_min_fee */ false); + SetFeeEstimateMode(*pwallet, coin_control, /*conf_target=*/request.params[6], /*estimate_mode=*/request.params[7], /*fee_rate=*/request.params[9], /*override_min_fee=*/false); EnsureWalletIsUnlocked(*pwallet); @@ -248,8 +335,8 @@ RPCHelpMan sendmany() }, {"replaceable", RPCArg::Type::BOOL, RPCArg::DefaultHint{"wallet default"}, "Allow this transaction to be replaced by a transaction with higher fees via 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."}, }, @@ -280,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 @@ -306,7 +393,7 @@ RPCHelpMan sendmany() coin_control.m_signal_bip125_rbf = request.params[5].get_bool(); } - SetFeeEstimateMode(*pwallet, coin_control, /* conf_target */ request.params[6], /* estimate_mode */ request.params[7], /* fee_rate */ request.params[8], /* override_min_fee */ false); + SetFeeEstimateMode(*pwallet, coin_control, /*conf_target=*/request.params[6], /*estimate_mode=*/request.params[7], /*fee_rate=*/request.params[8], /*override_min_fee=*/false); std::vector<CRecipient> recipients; ParseRecipients(sendTo, subtractFeeFromAmount, recipients); @@ -335,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); @@ -360,31 +447,43 @@ RPCHelpMan settxfee() // Only includes key documentation where the key is snake_case in all RPC methods. MixedCase keys can be added later. -static std::vector<RPCArg> FundTxDoc() +static std::vector<RPCArg> FundTxDoc(bool solving_data = true) { - return { + 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\"") + "\""}, - {"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"}, - {"solving_data", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED_NAMED_ARG, "Keys and scripts needed for producing a final transaction with a dummy signature.\n" - "Used for fee estimation during coin selection.", - { - {"pubkeys", RPCArg::Type::ARR, RPCArg::Default{UniValue::VARR}, "Public keys involved in this transaction.", - { - {"pubkey", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, "A public key"}, - }}, - {"scripts", RPCArg::Type::ARR, RPCArg::Default{UniValue::VARR}, "Scripts involved in this transaction.", - { - {"script", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, "A script"}, - }}, - {"descriptors", RPCArg::Type::ARR, RPCArg::Default{UniValue::VARR}, "Descriptors that provide solving data for this transaction.", - { - {"descriptor", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "A descriptor"}, - }}, - }}, + {"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" + }, }; + if (solving_data) { + args.push_back({"solving_data", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED_NAMED_ARG, "Keys and scripts needed for producing a final transaction with a dummy signature.\n" + "Used for fee estimation during coin selection.", + { + { + "pubkeys", RPCArg::Type::ARR, RPCArg::Default{UniValue::VARR}, "Public keys involved in this transaction.", + { + {"pubkey", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, "A public key"}, + } + }, + { + "scripts", RPCArg::Type::ARR, RPCArg::Default{UniValue::VARR}, "Scripts involved in this transaction.", + { + {"script", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, "A script"}, + } + }, + { + "descriptors", RPCArg::Type::ARR, RPCArg::Default{UniValue::VARR}, "Descriptors that provide solving data for this transaction.", + { + {"descriptor", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "A descriptor"}, + } + }, + } + }); + } + return args; } void FundTransaction(CWallet& wallet, CMutableTransaction& tx, CAmount& fee_out, int& change_position, const UniValue& options, CCoinControl& coinControl, bool override_min_fee) @@ -434,8 +533,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")) { @@ -450,7 +549,7 @@ void FundTransaction(CWallet& wallet, CMutableTransaction& tx, CAmount& fee_out, } if (options.exists("changePosition") || options.exists("change_position")) { - change_position = (options.exists("change_position") ? options["change_position"] : options["changePosition"]).get_int(); + change_position = (options.exists("change_position") ? options["change_position"] : options["changePosition"]).getInt<int>(); } if (options.exists("change_type")) { @@ -545,7 +644,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)); } } } @@ -558,7 +657,7 @@ void FundTransaction(CWallet& wallet, CMutableTransaction& tx, CAmount& fee_out, if (!vout_v.isNum()) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, missing vout key"); } - int vout = vout_v.get_int(); + int vout = vout_v.getInt<int>(); if (vout < 0) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, vout cannot be negative"); } @@ -567,7 +666,7 @@ void FundTransaction(CWallet& wallet, CMutableTransaction& tx, CAmount& fee_out, if (!weight_v.isNum()) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, missing weight key"); } - int64_t weight = weight_v.get_int64(); + int64_t weight = weight_v.getInt<int64_t>(); const int64_t min_input_weight = GetTransactionInputWeight(CTxIn()); CHECK_NONFATAL(min_input_weight == 165); if (weight < min_input_weight) { @@ -588,7 +687,7 @@ void FundTransaction(CWallet& wallet, CMutableTransaction& tx, CAmount& fee_out, throw JSONRPCError(RPC_INVALID_PARAMETER, "changePosition out of bounds"); for (unsigned int idx = 0; idx < subtractFeeFromOutputs.size(); idx++) { - int pos = subtractFeeFromOutputs[idx].get_int(); + int pos = subtractFeeFromOutputs[idx].getInt<int>(); if (setSubtractFeeFromOutputs.count(pos)) throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Invalid parameter, duplicated position: %d", pos)); if (pos < 0) @@ -598,19 +697,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)) { @@ -659,7 +745,7 @@ RPCHelpMan fundrawtransaction() {"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."}, - {"changeAddress", RPCArg::Type::STR, RPCArg::DefaultHint{"pool address"}, "The bitcoin address to receive the change"}, + {"changeAddress", RPCArg::Type::STR, RPCArg::DefaultHint{"automatic"}, "The bitcoin address to receive the change"}, {"changePosition", RPCArg::Type::NUM, RPCArg::DefaultHint{"random"}, "The index of the change output"}, {"change_type", RPCArg::Type::STR, RPCArg::DefaultHint{"set by -changetype"}, "The output type to use. Only valid if changeAddress is not specified. Options are \"legacy\", \"p2sh-segwit\", and \"bech32\"."}, {"includeWatching", RPCArg::Type::BOOL, RPCArg::DefaultHint{"true for watch-only wallets, otherwise false"}, "Also select inputs which are watch only.\n" @@ -719,7 +805,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}); @@ -735,8 +821,8 @@ 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; - FundTransaction(*pwallet, tx, fee, change_position, request.params[1], coin_control, /* override_min_fee */ 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); result.pushKV("hex", EncodeHexTx(CTransaction(tx))); @@ -809,7 +895,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); @@ -881,7 +967,7 @@ 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"}, }, @@ -906,7 +992,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."); @@ -941,7 +1027,7 @@ static RPCHelpMan bumpfee_helper(std::string method_name) if (options.exists("replaceable")) { coin_control.m_signal_bip125_rbf = options["replaceable"].get_bool(); } - SetFeeEstimateMode(*pwallet, coin_control, conf_target, options["estimate_mode"], options["fee_rate"], /* override_min_fee */ false); + SetFeeEstimateMode(*pwallet, coin_control, conf_target, options["estimate_mode"], options["fee_rate"], /*override_min_fee=*/false); } // Make sure the results are valid at least up to the most recent block @@ -1045,8 +1131,8 @@ 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>>( @@ -1056,7 +1142,7 @@ RPCHelpMan send() "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."}, {"add_to_wallet", RPCArg::Type::BOOL, RPCArg::Default{true}, "When false, returns a serialized transaction which will not be added to the wallet or broadcast"}, - {"change_address", RPCArg::Type::STR_HEX, RPCArg::DefaultHint{"pool address"}, "The bitcoin address to receive the change"}, + {"change_address", RPCArg::Type::STR, RPCArg::DefaultHint{"automatic"}, "The bitcoin address to receive the change"}, {"change_position", RPCArg::Type::NUM, RPCArg::DefaultHint{"random"}, "The index of the change output"}, {"change_type", RPCArg::Type::STR, RPCArg::DefaultHint{"set by -changetype"}, "The output type to use. Only valid if change_address is not specified. Options are \"legacy\", \"p2sh-segwit\", and \"bech32\"."}, {"fee_rate", RPCArg::Type::AMOUNT, RPCArg::DefaultHint{"not set, fall back to wallet fee estimation"}, "Specify a fee rate in " + CURRENCY_ATOM + "/vB."}, @@ -1123,105 +1209,250 @@ 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]}; - if (options.exists("conf_target") || options.exists("estimate_mode")) { - if (!request.params[1].isNull() || !request.params[2].isNull()) { - throw JSONRPCError(RPC_INVALID_PARAMETER, "Pass conf_target and estimate_mode either as arguments or in the options object, but not both"); - } - } else { - options.pushKV("conf_target", request.params[1]); - options.pushKV("estimate_mode", request.params[2]); - } - if (options.exists("fee_rate")) { - if (!request.params[3].isNull()) { - throw JSONRPCError(RPC_INVALID_PARAMETER, "Pass the fee_rate either as an argument, or in the options object, but not both"); - } - } else { - options.pushKV("fee_rate", request.params[3]); - } - if (!options["conf_target"].isNull() && (options["estimate_mode"].isNull() || (options["estimate_mode"].get_str() == "unset"))) { - throw JSONRPCError(RPC_INVALID_PARAMETER, "Specify estimate_mode"); - } - if (options.exists("feeRate")) { - throw JSONRPCError(RPC_INVALID_PARAMETER, "Use fee_rate (" + CURRENCY_ATOM + "/vB) instead of feeRate"); - } - if (options.exists("changeAddress")) { - throw JSONRPCError(RPC_INVALID_PARAMETER, "Use change_address"); - } - if (options.exists("changePosition")) { - throw JSONRPCError(RPC_INVALID_PARAMETER, "Use change_position"); - } - if (options.exists("includeWatching")) { - throw JSONRPCError(RPC_INVALID_PARAMETER, "Use include_watching"); - } - if (options.exists("lockUnspents")) { - throw JSONRPCError(RPC_INVALID_PARAMETER, "Use lock_unspents"); - } - if (options.exists("subtractFeeFromOutputs")) { - throw JSONRPCError(RPC_INVALID_PARAMETER, "Use subtract_fee_from_outputs"); - } + InterpretFeeEstimationInstructions(/*conf_target=*/request.params[1], /*estimate_mode=*/request.params[2], /*fee_rate=*/request.params[3], options); + PreventOutdatedOptions(options); - const bool psbt_opt_in = options.exists("psbt") && options["psbt"].get_bool(); CAmount fee; int change_position; - bool rbf = pwallet->m_signal_rbf; - if (options.exists("replaceable")) { - rbf = options["replaceable"].get_bool(); - } + bool rbf{options.exists("replaceable") ? options["replaceable"].get_bool() : pwallet->m_signal_rbf}; CMutableTransaction rawTx = ConstructTransaction(options["inputs"], request.params[0], options["locktime"], 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(options["inputs"], options); - FundTransaction(*pwallet, rawTx, fee, change_position, options, coin_control, /* override_min_fee */ false); + FundTransaction(*pwallet, rawTx, fee, change_position, options, coin_control, /*override_min_fee=*/false); - bool add_to_wallet = true; - if (options.exists("add_to_wallet")) { - add_to_wallet = options["add_to_wallet"].get_bool(); + return FinishTransaction(pwallet, options, rawTx); + } + }; +} + +RPCHelpMan sendall() +{ + return RPCHelpMan{"sendall", + "EXPERIMENTAL warning: this call may be changed in future releases.\n" + "\nSpend the value of all (or specific) confirmed UTXOs in the wallet to one or more recipients.\n" + "Unconfirmed inbound UTXOs and locked UTXOs will not be spent. Sendall will respect the avoid_reuse wallet flag.\n" + "If your wallet contains many small inputs, either because it received tiny payments or as a result of accumulating change, consider using `send_max` to exclude inputs that are worth less than the fees needed to spend them.\n", + { + {"recipients", RPCArg::Type::ARR, RPCArg::Optional::NO, "The sendall destinations. Each address may only appear once.\n" + "Optionally some recipients can be specified with an amount to perform payments, but at least one address must appear without a specified amount.\n", + { + {"address", RPCArg::Type::STR, RPCArg::Optional::NO, "A bitcoin address which receives an equal share of the unspecified amount."}, + {"", RPCArg::Type::OBJ_USER_KEYS, RPCArg::Optional::OMITTED, "", + { + {"address", RPCArg::Type::AMOUNT, RPCArg::Optional::NO, "A key-value pair. The key (string) is the bitcoin address, the value (float or string) is the amount in " + CURRENCY_UNIT + ""}, + }, + }, + }, + }, + {"conf_target", RPCArg::Type::NUM, RPCArg::DefaultHint{"wallet -txconfirmtarget"}, "Confirmation target in blocks"}, + {"estimate_mode", RPCArg::Type::STR, RPCArg::Default{"unset"}, "The fee estimate mode, must be one of (case insensitive):\n" + "\"" + 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_to_wallet", RPCArg::Type::BOOL, RPCArg::Default{true}, "When false, returns the serialized transaction without broadcasting or adding it to the wallet"}, + {"fee_rate", RPCArg::Type::AMOUNT, RPCArg::DefaultHint{"not set, fall back to wallet fee estimation"}, "Specify a fee rate in " + CURRENCY_ATOM + "/vB."}, + {"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", + { + {"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"}, + }, + }, + {"locktime", RPCArg::Type::NUM, RPCArg::Default{0}, "Raw locktime. Non-0 value also locktime-activates inputs"}, + {"lock_unspents", RPCArg::Type::BOOL, RPCArg::Default{false}, "Lock selected unspent outputs"}, + {"psbt", RPCArg::Type::BOOL, RPCArg::DefaultHint{"automatic"}, "Always return a PSBT, implies add_to_wallet=false."}, + {"send_max", RPCArg::Type::BOOL, RPCArg::Default{false}, "When true, only use UTXOs that can pay for their own fees to maximize the output amount. When 'false' (default), no UTXO is left behind. send_max is incompatible with providing specific inputs."}, + }, + FundTxDoc() + ), + "options" + }, + }, + RPCResult{ + RPCResult::Type::OBJ, "", "", + { + {RPCResult::Type::BOOL, "complete", "If the transaction has a complete set of signatures"}, + {RPCResult::Type::STR_HEX, "txid", /*optional=*/true, "The transaction id for the send. Only 1 transaction is created regardless of the number of addresses."}, + {RPCResult::Type::STR_HEX, "hex", /*optional=*/true, "If add_to_wallet is false, the hex-encoded raw transaction with signature(s)"}, + {RPCResult::Type::STR, "psbt", /*optional=*/true, "If more signatures are needed, or if add_to_wallet is false, the base64-encoded (partially) signed transaction"} + } + }, + RPCExamples{"" + "\nSpend all UTXOs from the wallet with a fee rate of 1 " + CURRENCY_ATOM + "/vB using named arguments\n" + + HelpExampleCli("-named sendall", "recipients='[\"" + EXAMPLE_ADDRESS[0] + "\"]' fee_rate=1\n") + + "Spend all UTXOs with a fee rate of 1.1 " + CURRENCY_ATOM + "/vB using positional arguments\n" + + HelpExampleCli("sendall", "'[\"" + EXAMPLE_ADDRESS[0] + "\"]' null \"unset\" 1.1\n") + + "Spend all UTXOs split into equal amounts to two addresses with a fee rate of 1.5 " + CURRENCY_ATOM + "/vB using the options argument\n" + + HelpExampleCli("sendall", "'[\"" + EXAMPLE_ADDRESS[0] + "\", \"" + EXAMPLE_ADDRESS[1] + "\"]' null \"unset\" null '{\"fee_rate\": 1.5}'\n") + + "Leave dust UTXOs in wallet, spend only UTXOs with positive effective value with a fee rate of 10 " + CURRENCY_ATOM + "/vB using the options argument\n" + + HelpExampleCli("sendall", "'[\"" + EXAMPLE_ADDRESS[0] + "\"]' null \"unset\" null '{\"fee_rate\": 10, \"send_max\": true}'\n") + + "Spend all UTXOs with a fee rate of 1.3 " + CURRENCY_ATOM + "/vB using named arguments and sending a 0.25 " + CURRENCY_UNIT + " to another recipient\n" + + HelpExampleCli("-named sendall", "recipients='[{\"" + EXAMPLE_ADDRESS[1] + "\": 0.25}, \""+ EXAMPLE_ADDRESS[0] + "\"]' fee_rate=1.3\n") + }, + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue + { + RPCTypeCheck(request.params, { + UniValue::VARR, // recipients + UniValue::VNUM, // conf_target + UniValue::VSTR, // estimate_mode + UniValueType(), // fee_rate, will be checked by AmountFromValue() in SetFeeEstimateMode() + UniValue::VOBJ, // options + }, true + ); + + std::shared_ptr<CWallet> const pwallet{GetWalletForJSONRPCRequest(request)}; + 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(); + + 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); + PreventOutdatedOptions(options); + + + std::set<std::string> addresses_without_amount; + UniValue recipient_key_value_pairs(UniValue::VARR); + const UniValue& recipients{request.params[0]}; + for (unsigned int i = 0; i < recipients.size(); ++i) { + const UniValue& recipient{recipients[i]}; + if (recipient.isStr()) { + UniValue rkvp(UniValue::VOBJ); + rkvp.pushKV(recipient.get_str(), 0); + recipient_key_value_pairs.push_back(rkvp); + addresses_without_amount.insert(recipient.get_str()); + } else { + recipient_key_value_pairs.push_back(recipient); + } + } + + if (addresses_without_amount.size() == 0) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Must provide at least one address without a specified amount"); + } + + CCoinControl coin_control; + + SetFeeEstimateMode(*pwallet, coin_control, options["conf_target"], options["estimate_mode"], options["fee_rate"], /*override_min_fee=*/false); + + coin_control.fAllowWatchOnly = ParseIncludeWatchonly(options["include_watching"], *pwallet); + + const bool rbf{options.exists("replaceable") ? options["replaceable"].get_bool() : pwallet->m_signal_rbf}; + + FeeCalculation fee_calc_out; + CFeeRate fee_rate{GetMinimumFeeRate(*pwallet, coin_control, &fee_calc_out)}; + // 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 && fee_rate > *coin_control.m_feerate) { + throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Fee rate (%s) is lower than the minimum fee rate setting (%s)", coin_control.m_feerate->ToString(FeeEstimateMode::SAT_VB), fee_rate.ToString(FeeEstimateMode::SAT_VB))); + } + if (fee_calc_out.reason == FeeReason::FALLBACK && !pwallet->m_allow_fallback_fee) { + // eventually allow a fallback fee + throw JSONRPCError(RPC_WALLET_ERROR, "Fee estimation failed. Fallbackfee is disabled. Wait a few blocks or enable -fallbackfee."); + } + + CMutableTransaction rawTx{ConstructTransaction(options["inputs"], recipient_key_value_pairs, options["locktime"], rbf)}; + LOCK(pwallet->cs_wallet); + + CAmount total_input_value(0); + bool send_max{options.exists("send_max") ? options["send_max"].get_bool() : false}; + if (options.exists("inputs") && options.exists("send_max")) { + 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)) { + 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)}; + if (!tx || pwallet->IsMine(tx->tx->vout[input.prevout.n]) != (coin_control.fAllowWatchOnly ? ISMINE_ALL : ISMINE_SPENDABLE)) { + throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Input not found. UTXO (%s:%d) is not part of wallet.", input.prevout.hash.ToString(), input.prevout.n)); + } + total_input_value += tx->tx->vout[input.prevout.n].nValue; + } + } else { + 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; + } + CTxIn input(output.outpoint.hash, output.outpoint.n, CScript(), rbf ? MAX_BIP125_RBF_SEQUENCE : CTxIn::SEQUENCE_FINAL); + rawTx.vin.push_back(input); + total_input_value += output.txout.nValue; + } } - // Make a blank psbt - PartiallySignedTransaction psbtx(rawTx); + // estimate final size of tx + const TxSize tx_size{CalculateMaximumSignedTxSize(CTransaction(rawTx), pwallet.get())}; + const CAmount fee_from_size{fee_rate.GetFee(tx_size.vsize)}; + const CAmount effective_value{total_input_value - fee_from_size}; - // First fill transaction with our data without signing, - // so external signers are not asked sign more than once. - bool complete; - pwallet->FillPSBT(psbtx, complete, SIGHASH_DEFAULT, false, true); - const TransactionError err = pwallet->FillPSBT(psbtx, complete, SIGHASH_DEFAULT, true, false); - if (err != TransactionError::OK) { - throw JSONRPCTransactionError(err); + 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."); + } else { + throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, "Total value of UTXO pool too low to pay for transaction. Try using lower feerate or excluding uneconomic UTXOs with 'send_max' option."); + } } - CMutableTransaction mtx; - complete = FinalizeAndExtractPSBT(psbtx, mtx); + CAmount output_amounts_claimed{0}; + for (const CTxOut& out : rawTx.vout) { + output_amounts_claimed += out.nValue; + } - UniValue result(UniValue::VOBJ); + if (output_amounts_claimed > total_input_value) { + throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, "Assigned more value to outputs than available funds."); + } - if (psbt_opt_in || !complete || !add_to_wallet) { - // Serialize the PSBT - CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION); - ssTx << psbtx; - result.pushKV("psbt", EncodeBase64(ssTx.str())); + const CAmount remainder{effective_value - output_amounts_claimed}; + if (remainder < 0) { + throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, "Insufficient funds for fees after creating specified outputs."); } - if (complete) { - std::string err_string; - std::string hex = EncodeHexTx(CTransaction(mtx)); - CTransactionRef tx(MakeTransactionRef(std::move(mtx))); - result.pushKV("txid", tx->GetHash().GetHex()); - if (add_to_wallet && !psbt_opt_in) { - pwallet->CommitTransaction(tx, {}, {} /* orderForm */); + const CAmount per_output_without_amount{remainder / (long)addresses_without_amount.size()}; + + bool gave_remaining_to_first{false}; + for (CTxOut& out : rawTx.vout) { + CTxDestination dest; + ExtractDestination(out.scriptPubKey, dest); + std::string addr{EncodeDestination(dest)}; + if (addresses_without_amount.count(addr) > 0) { + out.nValue = per_output_without_amount; + if (!gave_remaining_to_first) { + out.nValue += remainder % addresses_without_amount.size(); + gave_remaining_to_first = true; + } + if (IsDust(out, pwallet->chain().relayDustFee())) { + // Dynamically generated output amount is dust + throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, "Dynamically assigned remainder results in dust output."); + } } else { - result.pushKV("hex", hex); + if (IsDust(out, pwallet->chain().relayDustFee())) { + // Specified output amount is dust + throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Specified output amount to %s is below dust threshold.", addr)); + } + } + } + + const bool lock_unspents{options.exists("lock_unspents") ? options["lock_unspents"].get_bool() : false}; + if (lock_unspents) { + for (const CTxIn& txin : rawTx.vin) { + pwallet->LockCoin(txin.prevout); } } - result.pushKV("complete", complete); - return result; + return FinishTransaction(pwallet, options, rawTx); } }; } @@ -1259,7 +1490,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 @@ -1351,7 +1582,7 @@ RPCHelpMan walletcreatefundedpsbt() {"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."}, - {"changeAddress", RPCArg::Type::STR_HEX, RPCArg::DefaultHint{"pool address"}, "The bitcoin address to receive the change"}, + {"changeAddress", RPCArg::Type::STR, RPCArg::DefaultHint{"automatic"}, "The bitcoin address to receive the change"}, {"changePosition", RPCArg::Type::NUM, RPCArg::DefaultHint{"random"}, "The index of the change output"}, {"change_type", RPCArg::Type::STR, RPCArg::DefaultHint{"set by -changetype"}, "The output type to use. Only valid if changeAddress is not specified. Options are \"legacy\", \"p2sh-segwit\", and \"bech32\"."}, {"includeWatching", RPCArg::Type::BOOL, RPCArg::DefaultHint{"true for watch-only wallets, otherwise false"}, "Also select inputs which are watch only"}, @@ -1386,7 +1617,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 @@ -1402,7 +1633,7 @@ RPCHelpMan walletcreatefundedpsbt() }, true ); - UniValue options = request.params[3]; + UniValue options{request.params[3].isNull() ? UniValue::VOBJ : request.params[3]}; CAmount fee; int change_position; @@ -1416,9 +1647,9 @@ RPCHelpMan walletcreatefundedpsbt() 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); + FundTransaction(wallet, rawTx, fee, change_position, options, coin_control, /*override_min_fee=*/true); // Make a blank psbt PartiallySignedTransaction psbtx(rawTx); diff --git a/src/wallet/rpc/transactions.cpp b/src/wallet/rpc/transactions.cpp index eef2c13ee1..a0769d3ecb 100644 --- a/src/wallet/rpc/transactions.cpp +++ b/src/wallet/rpc/transactions.cpp @@ -15,6 +15,7 @@ using interfaces::FoundBlock; namespace wallet { static void WalletTxToJSON(const CWallet& wallet, const CWalletTx& wtx, UniValue& entry) + EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet) { interfaces::Chain& chain = wallet.chain(); int confirms = wallet.GetTxDepthInMainChain(wtx); @@ -34,6 +35,7 @@ static void WalletTxToJSON(const CWallet& wallet, const CWalletTx& wtx, UniValue } uint256 hash = wtx.GetHash(); entry.pushKV("txid", hash.GetHex()); + entry.pushKV("wtxid", wtx.GetWitnessHash().GetHex()); UniValue conflicts(UniValue::VARR); for (const uint256& conflict : wallet.GetTxConflicts(wtx)) conflicts.push_back(conflict.GetHex()); @@ -62,9 +64,7 @@ struct tallyitem int nConf{std::numeric_limits<int>::max()}; std::vector<uint256> txids; bool fIsWatchonly{false}; - tallyitem() - { - } + tallyitem() = default; }; static UniValue ListReceived(const CWallet& wallet, const UniValue& params, const bool by_label, const bool include_immature_coinbase) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet) @@ -72,7 +72,7 @@ static UniValue ListReceived(const CWallet& wallet, const UniValue& params, cons // Minimum confirmations int nMinDepth = 1; if (!params[0].isNull()) - nMinDepth = params[0].get_int(); + nMinDepth = params[0].getInt<int>(); // Whether to include empty labels bool fIncludeEmpty = false; @@ -85,22 +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; - } - - // Excluding coinbase outputs is deprecated - // It can be enabled by setting deprecatedrpc=exclude_coinbase - const bool include_coinbase{!wallet.chain().rpcEnableDeprecated("exclude_coinbase")}; - - if (include_immature_coinbase && !include_coinbase) { - throw JSONRPCError(RPC_INVALID_PARAMETER, "include_immature_coinbase is incompatible with deprecated exclude_coinbase"); } // Tally @@ -113,24 +103,22 @@ static UniValue ListReceived(const CWallet& wallet, const UniValue& params, cons continue; // Coinbase with less than 1 confirmation is no longer in the main chain - if ((wtx.IsCoinBase() && (nDepth < 1 || !include_coinbase)) - || (wallet.IsTxImmatureCoinBase(wtx) && !include_immature_coinbase)) - { + if ((wtx.IsCoinBase() && (nDepth < 1)) + || (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]; @@ -146,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); @@ -263,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 @@ -308,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 @@ -337,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); @@ -393,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) @@ -431,6 +409,7 @@ static const std::vector<RPCResult> TransactionDescriptionString() {RPCResult::Type::NUM, "blockindex", /*optional=*/true, "The index of the transaction in the block that includes it."}, {RPCResult::Type::NUM_TIME, "blocktime", /*optional=*/true, "The block time expressed in " + UNIX_EPOCH_TIME + "."}, {RPCResult::Type::STR_HEX, "txid", "The transaction id."}, + {RPCResult::Type::STR_HEX, "wtxid", "The hash of serialized transaction, including witness data."}, {RPCResult::Type::ARR, "walletconflicts", "Conflicting transaction ids.", { {RPCResult::Type::STR_HEX, "txid", "The transaction id."}, @@ -443,7 +422,11 @@ static const std::vector<RPCResult> TransactionDescriptionString() {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."}}; + "may be unknown for unconfirmed transactions not in the mempool."}, + {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() @@ -496,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 @@ -511,10 +494,10 @@ RPCHelpMan listtransactions() } int nCount = 10; if (!request.params[1].isNull()) - nCount = request.params[1].get_int(); + nCount = request.params[1].getInt<int>(); int nFrom = 0; if (!request.params[2].isNull()) - nFrom = request.params[2].get_int(); + nFrom = request.params[2].getInt<int>(); isminefilter filter = ISMINE_SPENDABLE; if (ParseIncludeWatchonly(request.params[3], *pwallet)) { @@ -526,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); @@ -549,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; }, }; @@ -569,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, "", "", @@ -612,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 @@ -637,7 +620,7 @@ RPCHelpMan listsinceblock() } if (!request.params[1].isNull()) { - target_confirms = request.params[1].get_int(); + target_confirms = request.params[1].getInt<int>(); if (target_confirms < 1) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter"); @@ -649,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; @@ -658,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); } } @@ -675,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; @@ -735,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"}, @@ -753,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 @@ -798,7 +785,7 @@ RPCHelpMan gettransaction() if (verbose) { UniValue decoded(UniValue::VOBJ); - TxToUniv(*wtx.tx, uint256(), decoded, false); + TxToUniv(*wtx.tx, /*block_hash=*/uint256(), /*entry=*/decoded, /*include_hex=*/false); entry.pushKV("decoded", decoded); } @@ -826,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 @@ -843,7 +830,7 @@ RPCHelpMan abandontransaction() throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Transaction not eligible for abandonment"); } - return NullUniValue; + return UniValue::VNULL; }, }; } @@ -871,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 @@ -891,14 +878,14 @@ RPCHelpMan rescanblockchain() int tip_height = pwallet->GetLastBlockHeight(); if (!request.params[0].isNull()) { - start_height = request.params[0].get_int(); + start_height = request.params[0].getInt<int>(); if (start_height < 0 || start_height > tip_height) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid start_height"); } } if (!request.params[1].isNull()) { - stop_height = request.params[1].get_int(); + stop_height = request.params[1].getInt<int>(); if (*stop_height < 0 || *stop_height > tip_height) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid stop_height"); } else if (*stop_height < start_height) { @@ -915,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; @@ -951,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..3c1634324e 100644 --- a/src/wallet/rpc/util.cpp +++ b/src/wallet/rpc/util.cpp @@ -64,12 +64,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 +100,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 +109,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 +122,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..2ca28914e7 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,6 +41,8 @@ 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); } // namespace wallet diff --git a/src/wallet/rpc/wallet.cpp b/src/wallet/rpc/wallet.cpp index 883a3c102b..eb275f9951 100644 --- a/src/wallet/rpc/wallet.cpp +++ b/src/wallet/rpc/wallet.cpp @@ -8,6 +8,7 @@ #include <rpc/server.h> #include <rpc/util.h> #include <util/translation.h> +#include <wallet/context.h> #include <wallet/receive.h> #include <wallet/rpc/wallet.h> #include <wallet/rpc/util.h> @@ -55,7 +56,7 @@ static RPCHelpMan getwalletinfo() { {RPCResult::Type::NUM, "duration", "elapsed seconds since scan start"}, {RPCResult::Type::NUM, "progress", "scanning progress percentage [0.0, 1.0]"}, - }}, + }, /*skip_type_check=*/true}, {RPCResult::Type::BOOL, "descriptors", "whether this wallet uses descriptors for scriptPubKey management"}, {RPCResult::Type::BOOL, "external_signer", "whether this wallet is configured to use an external signer such as a hardware wallet"}, }}, @@ -67,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 @@ -220,6 +221,7 @@ static RPCHelpMan loadwallet() DatabaseOptions options; DatabaseStatus status; + ReadDatabaseArgs(*context.args, options); options.require_existing = true; bilingual_str error; std::vector<bilingual_str> warnings; @@ -239,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; @@ -255,7 +257,7 @@ static RPCHelpMan setwalletflag() { {RPCResult::Type::STR, "flag_name", "The name of the flag that was modified"}, {RPCResult::Type::BOOL, "flag_state", "The new state of the flag"}, - {RPCResult::Type::STR, "warnings", "Any warnings associated with the change"}, + {RPCResult::Type::STR, "warnings", /*optional=*/true, "Any warnings associated with the change"}, } }, RPCExamples{ @@ -265,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(); @@ -315,7 +317,9 @@ static RPCHelpMan createwallet() {"blank", RPCArg::Type::BOOL, RPCArg::Default{false}, "Create a blank wallet. A blank wallet has no keys or HD seed. One can be set using sethdseed."}, {"passphrase", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, "Encrypt the wallet with this passphrase."}, {"avoid_reuse", RPCArg::Type::BOOL, RPCArg::Default{false}, "Keep track of coin reuse, and treat dirty and clean coins differently with privacy considerations in mind."}, - {"descriptors", RPCArg::Type::BOOL, RPCArg::Default{true}, "Create a native descriptor wallet. The wallet will use descriptors internally to handle address creation"}, + {"descriptors", RPCArg::Type::BOOL, RPCArg::Default{true}, "Create a native descriptor wallet. The wallet will use descriptors internally to handle address creation." + " Setting to \"false\" will create a legacy wallet; however, the legacy wallet type is being deprecated and" + " support for creating and opening legacy wallets will be removed in the future."}, {"load_on_startup", RPCArg::Type::BOOL, RPCArg::Optional::OMITTED_NAMED_ARG, "Save wallet name to persistent settings and load on startup. True to add wallet to startup list, false to remove, null to leave unchanged."}, {"external_signer", RPCArg::Type::BOOL, RPCArg::Default{false}, "Use an external signer such as a hardware wallet. Requires -signer to be configured. Wallet creation will fail if keys cannot be fetched. Requires disable_private_keys and descriptors set to true."}, }, @@ -379,6 +383,7 @@ static RPCHelpMan createwallet() DatabaseOptions options; DatabaseStatus status; + ReadDatabaseArgs(*context.args, options); options.require_create = true; options.create_flags = flags; options.create_passphrase = passphrase; @@ -475,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); @@ -516,7 +521,7 @@ static RPCHelpMan sethdseed() spk_man.SetHDSeed(master_pub_key); if (flush_key_pool) spk_man.NewKeyPool(); - return NullUniValue; + return UniValue::VNULL; }, }; } @@ -527,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, "", "", @@ -546,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); @@ -554,7 +559,7 @@ static RPCHelpMan upgradewallet() int version = 0; if (!request.params[0].isNull()) { - version = request.params[0].get_int(); + version = request.params[0].getInt<int>(); } bilingual_str error; const int previous_version{pwallet->GetVersion()}; @@ -585,6 +590,117 @@ 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; +} + }; +} + // addresses RPCHelpMan getaddressinfo(); RPCHelpMan getnewaddress(); @@ -639,6 +755,7 @@ RPCHelpMan fundrawtransaction(); RPCHelpMan bumpfee(); RPCHelpMan psbtbumpfee(); RPCHelpMan send(); +RPCHelpMan sendall(); RPCHelpMan walletprocesspsbt(); RPCHelpMan walletcreatefundedpsbt(); RPCHelpMan signrawtransactionwithwallet(); @@ -658,78 +775,76 @@ RPCHelpMan abortrescan(); Span<const CRPCCommand> GetWalletRPCCommands() { -// clang-format off -static const CRPCCommand commands[] = -{ // category actor (function) - // ------------------ ------------------------ - { "rawtransactions", &fundrawtransaction, }, - { "wallet", &abandontransaction, }, - { "wallet", &abortrescan, }, - { "wallet", &addmultisigaddress, }, - { "wallet", &backupwallet, }, - { "wallet", &bumpfee, }, - { "wallet", &psbtbumpfee, }, - { "wallet", &createwallet, }, - { "wallet", &restorewallet, }, - { "wallet", &dumpprivkey, }, - { "wallet", &dumpwallet, }, - { "wallet", &encryptwallet, }, - { "wallet", &getaddressesbylabel, }, - { "wallet", &getaddressinfo, }, - { "wallet", &getbalance, }, - { "wallet", &getnewaddress, }, - { "wallet", &getrawchangeaddress, }, - { "wallet", &getreceivedbyaddress, }, - { "wallet", &getreceivedbylabel, }, - { "wallet", &gettransaction, }, - { "wallet", &getunconfirmedbalance, }, - { "wallet", &getbalances, }, - { "wallet", &getwalletinfo, }, - { "wallet", &importaddress, }, - { "wallet", &importdescriptors, }, - { "wallet", &importmulti, }, - { "wallet", &importprivkey, }, - { "wallet", &importprunedfunds, }, - { "wallet", &importpubkey, }, - { "wallet", &importwallet, }, - { "wallet", &keypoolrefill, }, - { "wallet", &listaddressgroupings, }, - { "wallet", &listdescriptors, }, - { "wallet", &listlabels, }, - { "wallet", &listlockunspent, }, - { "wallet", &listreceivedbyaddress, }, - { "wallet", &listreceivedbylabel, }, - { "wallet", &listsinceblock, }, - { "wallet", &listtransactions, }, - { "wallet", &listunspent, }, - { "wallet", &listwalletdir, }, - { "wallet", &listwallets, }, - { "wallet", &loadwallet, }, - { "wallet", &lockunspent, }, - { "wallet", &newkeypool, }, - { "wallet", &removeprunedfunds, }, - { "wallet", &rescanblockchain, }, - { "wallet", &send, }, - { "wallet", &sendmany, }, - { "wallet", &sendtoaddress, }, - { "wallet", &sethdseed, }, - { "wallet", &setlabel, }, - { "wallet", &settxfee, }, - { "wallet", &setwalletflag, }, - { "wallet", &signmessage, }, - { "wallet", &signrawtransactionwithwallet, }, - { "wallet", &unloadwallet, }, - { "wallet", &upgradewallet, }, - { "wallet", &walletcreatefundedpsbt, }, + static const CRPCCommand commands[]{ + {"rawtransactions", &fundrawtransaction}, + {"wallet", &abandontransaction}, + {"wallet", &abortrescan}, + {"wallet", &addmultisigaddress}, + {"wallet", &backupwallet}, + {"wallet", &bumpfee}, + {"wallet", &psbtbumpfee}, + {"wallet", &createwallet}, + {"wallet", &restorewallet}, + {"wallet", &dumpprivkey}, + {"wallet", &dumpwallet}, + {"wallet", &encryptwallet}, + {"wallet", &getaddressesbylabel}, + {"wallet", &getaddressinfo}, + {"wallet", &getbalance}, + {"wallet", &getnewaddress}, + {"wallet", &getrawchangeaddress}, + {"wallet", &getreceivedbyaddress}, + {"wallet", &getreceivedbylabel}, + {"wallet", &gettransaction}, + {"wallet", &getunconfirmedbalance}, + {"wallet", &getbalances}, + {"wallet", &getwalletinfo}, + {"wallet", &importaddress}, + {"wallet", &importdescriptors}, + {"wallet", &importmulti}, + {"wallet", &importprivkey}, + {"wallet", &importprunedfunds}, + {"wallet", &importpubkey}, + {"wallet", &importwallet}, + {"wallet", &keypoolrefill}, + {"wallet", &listaddressgroupings}, + {"wallet", &listdescriptors}, + {"wallet", &listlabels}, + {"wallet", &listlockunspent}, + {"wallet", &listreceivedbyaddress}, + {"wallet", &listreceivedbylabel}, + {"wallet", &listsinceblock}, + {"wallet", &listtransactions}, + {"wallet", &listunspent}, + {"wallet", &listwalletdir}, + {"wallet", &listwallets}, + {"wallet", &loadwallet}, + {"wallet", &lockunspent}, + {"wallet", &newkeypool}, + {"wallet", &removeprunedfunds}, + {"wallet", &rescanblockchain}, + {"wallet", &send}, + {"wallet", &sendmany}, + {"wallet", &sendtoaddress}, + {"wallet", &sethdseed}, + {"wallet", &setlabel}, + {"wallet", &settxfee}, + {"wallet", &setwalletflag}, + {"wallet", &signmessage}, + {"wallet", &signrawtransactionwithwallet}, + {"wallet", &simulaterawtransaction}, + {"wallet", &sendall}, + {"wallet", &unloadwallet}, + {"wallet", &upgradewallet}, + {"wallet", &walletcreatefundedpsbt}, #ifdef ENABLE_EXTERNAL_SIGNER - { "wallet", &walletdisplayaddress, }, + {"wallet", &walletdisplayaddress}, #endif // ENABLE_EXTERNAL_SIGNER - { "wallet", &walletlock, }, - { "wallet", &walletpassphrase, }, - { "wallet", &walletpassphrasechange, }, - { "wallet", &walletprocesspsbt, }, -}; -// clang-format on + {"wallet", &walletlock}, + {"wallet", &walletpassphrase}, + {"wallet", &walletpassphrasechange}, + {"wallet", &walletprocesspsbt}, + }; return commands; } } // namespace wallet diff --git a/src/wallet/salvage.cpp b/src/wallet/salvage.cpp index 1ecc96fe0e..9ba3c7fd2c 100644 --- a/src/wallet/salvage.cpp +++ b/src/wallet/salvage.cpp @@ -23,10 +23,11 @@ static bool KeyFilter(const std::string& type) return WalletBatch::IsKeyType(type) || type == DBKeys::HDCHAIN; } -bool RecoverDatabaseFile(const fs::path& file_path, bilingual_str& error, std::vector<bilingual_str>& warnings) +bool RecoverDatabaseFile(const ArgsManager& args, const fs::path& file_path, bilingual_str& error, std::vector<bilingual_str>& warnings) { DatabaseOptions options; DatabaseStatus status; + ReadDatabaseArgs(args, options); options.require_existing = true; options.verify = false; options.require_format = DatabaseFormat::BERKELEY; diff --git a/src/wallet/salvage.h b/src/wallet/salvage.h index 332aceb262..e4822c3c75 100644 --- a/src/wallet/salvage.h +++ b/src/wallet/salvage.h @@ -9,10 +9,11 @@ #include <fs.h> #include <streams.h> +class ArgsManager; struct bilingual_str; namespace wallet { -bool RecoverDatabaseFile(const fs::path& file_path, bilingual_str& error, std::vector<bilingual_str>& warnings); +bool RecoverDatabaseFile(const ArgsManager& args, const fs::path& file_path, bilingual_str& error, std::vector<bilingual_str>& warnings); } // namespace wallet #endif // BITCOIN_WALLET_SALVAGE_H diff --git a/src/wallet/scriptpubkeyman.cpp b/src/wallet/scriptpubkeyman.cpp index 8633e7c62c..40d5ecd755 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) @@ -1088,6 +1080,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 +1104,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 +1116,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 +1124,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 +1456,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 +1658,11 @@ std::set<CKeyID> LegacyScriptPubKeyMan::GetKeys() const return set_address; } -bool DescriptorScriptPubKeyMan::GetNewDestination(const OutputType type, CTxDestination& dest, bilingual_str& error) +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 +1680,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 +1696,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 +1764,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 +1788,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 +1965,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 +2079,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 +2122,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 +2183,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 +2191,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)); + } } } } diff --git a/src/wallet/scriptpubkeyman.h b/src/wallet/scriptpubkeyman.h index ad924791af..408d162eba 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) {} @@ -359,13 +360,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; @@ -546,6 +547,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 +570,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 diff --git a/src/wallet/spend.cpp b/src/wallet/spend.cpp index 83eaececc1..001acd04e2 100644 --- a/src/wallet/spend.cpp +++ b/src/wallet/spend.cpp @@ -11,6 +11,7 @@ #include <util/fees.h> #include <util/moneystr.h> #include <util/rbf.h> +#include <util/trace.h> #include <util/translation.h> #include <wallet/coincontrol.h> #include <wallet/fees.h> @@ -19,35 +20,27 @@ #include <wallet/transaction.h> #include <wallet/wallet.h> +#include <cmath> + 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); -} - -std::string COutput::ToString() const -{ - return strprintf("COutput(%s, %d, %d) [%s]", tx->GetHash().ToString(), i, nDepth, FormatMoney(tx->tx->vout[i].nValue)); -} - -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 @@ -86,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, 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); @@ -158,71 +221,106 @@ void AvailableCoins(const CWallet& wallet, std::vector<COutput>& vCoins, const C continue; } + 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)); - vCoins.push_back(COutput(wallet, wtx, i, nDepth, spendable, solvable, safeTx, (coinControl && coinControl->fAllowWatchOnly))); + // Filter by spendable outputs only + if (!spendable && only_spendable) continue; + + // If the Output is P2SH and spendable, 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 spendable, it will be classified + // as a P2SH (legacy), since we have no way of knowing otherwise without the redeemScript + CScript script; + bool is_from_p2sh{false}; + if (output.scriptPubKey.IsPayToScriptHash() && solvable) { + CTxDestination destination; + if (!ExtractDestination(output.scriptPubKey, destination)) + continue; + const CScriptID& hash = CScriptID(std::get<ScriptHash>(destination)); + if (!provider->GetCScript(hash, script)) + continue; + is_from_p2sh = true; + } else { + script = output.scriptPubKey; + } + COutput coin(outpoint, output, nDepth, input_bytes, spendable, solvable, safeTx, wtx.GetTxTime(), tx_from_me, feerate); + + // When parsing a scriptPubKey, Solver returns the parsed pubkeys or hashes (depending on the script) + // We don't need those here, so we are leaving them in return_values_unused + std::vector<std::vector<uint8_t>> return_values_unused; + TxoutType type; + type = Solver(script, return_values_unused); + result.Add(GetOutputType(type, is_from_p2sh), coin); + + // 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; +} + +CoinsResult AvailableCoinsListUnspent(const CWallet& wallet, const CCoinControl* coinControl, const CAmount& nMinimumAmount, const CAmount& nMaximumAmount, const CAmount& nMinimumSumAmount, const uint64_t 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; - AvailableCoins(wallet, vCoins, coinControl); - for (const COutput& out : vCoins) { - if (out.fSpendable) { - balance += out.tx->tx->vout[out.i].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) @@ -243,19 +341,22 @@ const CTxOut& FindNonChangeParentOutput(const CWallet& wallet, const CTransactio return ptx->vout[n]; } +const CTxOut& FindNonChangeParentOutput(const CWallet& wallet, const COutPoint& outpoint) +{ + AssertLockHeld(wallet.cs_wallet); + return FindNonChangeParentOutput(wallet, *wallet.GetWalletTx(outpoint.hash)->tx, outpoint.n); +} + 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; - - AvailableCoins(wallet, availableCoins); - for (const COutput& coin : availableCoins) { + for (const COutput& coin : AvailableCoinsListUnspent(wallet).All()) { CTxDestination address; - if ((coin.fSpendable || (wallet.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) && coin.fSolvable)) && - ExtractDestination(FindNonChangeParentOutput(wallet, *coin.tx->tx, coin.i).scriptPubKey, address)) { + if ((coin.spendable || (wallet.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) && coin.solvable)) && + ExtractDestination(FindNonChangeParentOutput(wallet, coin.outpoint).scriptPubKey, address)) { result[address].emplace_back(std::move(coin)); } } @@ -268,14 +369,16 @@ std::map<CTxDestination, std::vector<COutput>> ListCoins(const CWallet& wallet) for (const COutPoint& output : lockedCoins) { auto it = wallet.mapWallet.find(output.hash); if (it != wallet.mapWallet.end()) { - int depth = wallet.GetTxDepthInMainChain(it->second); - if (depth >= 0 && output.n < it->second.tx->vout.size() && - wallet.IsMine(it->second.tx->vout[output.n]) == is_mine_filter + const auto& wtx = it->second; + int depth = wallet.GetTxDepthInMainChain(wtx); + if (depth >= 0 && output.n < wtx.tx->vout.size() && + wallet.IsMine(wtx.tx->vout[output.n]) == is_mine_filter ) { CTxDestination address; - if (ExtractDestination(FindNonChangeParentOutput(wallet, *it->second.tx, output.n).scriptPubKey, address)) { + if (ExtractDestination(FindNonChangeParentOutput(wallet, *wtx.tx, output.n).scriptPubKey, address)) { + const auto out = wtx.tx->vout.at(output.n); result[address].emplace_back( - wallet, it->second, output.n, depth, true /* spendable */, true /* solvable */, false /* safe */); + 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)); } } } @@ -292,15 +395,14 @@ std::vector<OutputGroup> GroupOutputs(const CWallet& wallet, const std::vector<C // Allowing partial spends means no grouping. Each COutput gets its own OutputGroup. for (const COutput& output : outputs) { // Skip outputs we cannot spend - if (!output.fSpendable) continue; + if (!output.spendable) continue; size_t ancestors, descendants; - wallet.chain().getTransactionAncestry(output.tx->GetHash(), ancestors, descendants); - CInputCoin input_coin = output.GetInputCoin(); + wallet.chain().getTransactionAncestry(output.outpoint.hash, ancestors, descendants); // Make an OutputGroup containing just this output OutputGroup group{coin_sel_params}; - group.Insert(input_coin, output.nDepth, CachedTxIsFromMe(wallet, *output.tx, ISMINE_ALL), ancestors, descendants, positive_only); + group.Insert(output, ancestors, descendants, positive_only); // Check the OutputGroup's eligibility. Only add the eligible ones. if (positive_only && group.GetSelectionAmount() <= 0) continue; @@ -312,18 +414,17 @@ std::vector<OutputGroup> GroupOutputs(const CWallet& wallet, const std::vector<C // We want to combine COutputs that have the same scriptPubKey into single OutputGroups // except when there are more than OUTPUT_GROUP_MAX_ENTRIES COutputs grouped in an OutputGroup. // To do this, we maintain a map where the key is the scriptPubKey and the value is a vector of OutputGroups. - // For each COutput, we check if the scriptPubKey is in the map, and if it is, the COutput's CInputCoin is added + // For each COutput, we check if the scriptPubKey is in the map, and if it is, the COutput is added // to the last OutputGroup in the vector for the scriptPubKey. When the last OutputGroup has - // OUTPUT_GROUP_MAX_ENTRIES CInputCoins, a new OutputGroup is added to the end of the vector. + // OUTPUT_GROUP_MAX_ENTRIES COutputs, a new OutputGroup is added to the end of the vector. std::map<CScript, std::vector<OutputGroup>> spk_to_groups_map; for (const auto& output : outputs) { // Skip outputs we cannot spend - if (!output.fSpendable) continue; + if (!output.spendable) continue; size_t ancestors, descendants; - wallet.chain().getTransactionAncestry(output.tx->GetHash(), ancestors, descendants); - CInputCoin input_coin = output.GetInputCoin(); - CScript spk = input_coin.txout.scriptPubKey; + wallet.chain().getTransactionAncestry(output.outpoint.hash, ancestors, descendants); + CScript spk = output.txout.scriptPubKey; std::vector<OutputGroup>& groups = spk_to_groups_map[spk]; @@ -332,7 +433,7 @@ std::vector<OutputGroup> GroupOutputs(const CWallet& wallet, const std::vector<C groups.emplace_back(coin_sel_params); } - // Get the last OutputGroup in the vector so that we can add the CInputCoin to it + // Get the last OutputGroup in the vector so that we can add the COutput to it // A pointer is used here so that group can be reassigned later if it is full. OutputGroup* group = &groups.back(); @@ -344,8 +445,8 @@ std::vector<OutputGroup> GroupOutputs(const CWallet& wallet, const std::vector<C group = &groups.back(); } - // Add the input_coin to group - group->Insert(input_coin, output.nDepth, CachedTxIsFromMe(wallet, *output.tx, ISMINE_ALL), ancestors, descendants, positive_only); + // Add the output to group + group->Insert(output, ancestors, descendants, positive_only); } // Now we go through the entire map and pull out the OutputGroups @@ -370,32 +471,62 @@ 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, true /* positive_only */); if (auto bnb_result{SelectCoinsBnB(positive_groups, nTargetValue, coin_selection_params.m_cost_of_change)}) { - bnb_result->ComputeAndSetWaste(CAmount(0)); 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 */); + std::vector<OutputGroup> all_groups = GroupOutputs(wallet, available_coins, coin_selection_params, eligibility_filter, false /* positive_only */); + CAmount target_with_change = nTargetValue; // 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)}) { + // So we need to include that for KnapsackSolver and SRD as well, as we are expecting to create a change output. + if (!coin_selection_params.m_subtract_fee_outputs) { + target_with_change += coin_selection_params.m_change_fee; + } + if (auto knapsack_result{KnapsackSolver(all_groups, target_with_change, coin_selection_params.m_min_change_target, coin_selection_params.rng_fast)}) { knapsack_result->ComputeAndSetWaste(coin_selection_params.m_cost_of_change); results.push_back(*knapsack_result); } - // We include the minimum final change for SRD as we do want to avoid making really small change. - // KnapsackSolver does not need this because it includes MIN_CHANGE internally. - const CAmount srd_target = nTargetValue + coin_selection_params.m_change_fee + MIN_FINAL_CHANGE; - if (auto srd_result{SelectCoinsSRD(positive_groups, srd_target)}) { + // 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 = target_with_change + 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); results.push_back(*srd_result); } @@ -411,82 +542,74 @@ 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.fSpendable) continue; - /* Set depth, from_me, ancestors, and descendants to 0 or false 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.GetInputCoin(), 0, false, 0, 0, false); - } - SelectionResult result(nTargetValue); - result.AddInput(preset_inputs); - if (result.GetSelectedValue() < nTargetValue) return std::nullopt; - return result; - } - // calculate value from preset inputs and store them - std::set<CInputCoin> setPresetCoins; + std::set<COutPoint> preset_coins; std::vector<COutPoint> vPresetInputs; coin_control.ListSelected(vPresetInputs); 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); } - CInputCoin coin(outpoint, txout, input_bytes); - if (coin.m_input_bytes == -1) { + if (input_bytes == -1) { return std::nullopt; // Not solvable, can't estimate size for fee } - coin.effective_value = coin.txout.nValue - coin_selection_params.m_effective_feerate.GetFee(coin.m_input_bytes); + + /* Set some defaults for depth, spendable, solvable, safe, time, and from_me as these don't matter for preset inputs since no selection is being done. */ + COutput output(outpoint, txout, /*depth=*/ 0, input_bytes, /*spendable=*/ true, /*solvable=*/ true, /*safe=*/ true, /*time=*/ 0, /*from_me=*/ false, coin_selection_params.m_effective_feerate); if (coin_selection_params.m_subtract_fee_outputs) { - value_to_select -= coin.txout.nValue; + value_to_select -= output.txout.nValue; } else { - value_to_select -= coin.effective_value; + value_to_select -= output.GetEffectiveValue(); } - setPresetCoins.insert(coin); - /* Set depth, from_me, ancestors, and descendants to 0 or false as don't matter for preset inputs as no actual selection is being done. + preset_coins.insert(outpoint); + /* Set ancestors and descendants to 0 as they don't matter for preset inputs since 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(coin, 0, false, 0, 0, false); + 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 (setPresetCoins.count(it->GetInputCoin())) - 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 (result.GetSelectedValue() < nTargetValue) return std::nullopt; + result.ComputeAndSetWaste(coin_selection_params.m_cost_of_change); + 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; @@ -498,11 +621,11 @@ 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(), FastRandomContext()); + available_coins.Shuffle(coin_selection_params.rng_fast); } // Coin Selection attempts to select inputs from a pool of eligible UTXOs to fund the @@ -510,30 +633,31 @@ std::optional<SelectionResult> SelectCoins(const CWallet& wallet, const std::vec // 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)); + if (value_to_select <= 0) return std::make_optional(SelectionResult(nTargetValue, 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 @@ -541,7 +665,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; } } @@ -551,7 +675,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; } } @@ -564,6 +688,9 @@ std::optional<SelectionResult> SelectCoins(const CWallet& wallet, const std::vec // Add preset inputs to result res->AddInput(preset_inputs); + if (res->m_algo == SelectionAlgorithm::MANUAL) { + res->ComputeAndSetWaste(coin_selection_params.m_cost_of_change); + } return res; } @@ -583,12 +710,14 @@ static bool IsCurrentForAntiFeeSniping(interfaces::Chain& chain, const uint256& } /** - * Return a height-based locktime for new transactions (uses the height of the + * Set a height-based locktime for new transactions (uses the height of the * current chain tip unless we are not synced with the current chain */ -static uint32_t GetLocktimeForNewTransaction(interfaces::Chain& chain, const uint256& block_hash, int block_height) +static void DiscourageFeeSniping(CMutableTransaction& tx, FastRandomContext& rng_fast, + interfaces::Chain& chain, const uint256& block_hash, int block_height) { - uint32_t locktime; + // All inputs must be added by now + assert(!tx.vin.empty()); // Discourage fee sniping. // // For a large miner the value of the transactions in the best block and @@ -610,41 +739,53 @@ static uint32_t GetLocktimeForNewTransaction(interfaces::Chain& chain, const uin // now we ensure code won't be written that makes assumptions about // nLockTime that preclude a fix later. if (IsCurrentForAntiFeeSniping(chain, block_hash)) { - locktime = block_height; + tx.nLockTime = block_height; // Secondly occasionally randomly pick a nLockTime even further back, so // that transactions that are delayed after signing for whatever reason, // e.g. high-latency mix networks and some CoinJoin implementations, have // better privacy. - if (GetRandInt(10) == 0) - locktime = std::max(0, (int)locktime - GetRandInt(100)); + if (rng_fast.randrange(10) == 0) { + tx.nLockTime = std::max(0, int(tx.nLockTime) - int(rng_fast.randrange(100))); + } } else { // If our chain is lagging behind, we can't discourage fee sniping nor help // the privacy of high-latency transactions. To avoid leaking a potentially // unique "nLockTime fingerprint", set nLockTime to a constant. - locktime = 0; + tx.nLockTime = 0; + } + // Sanity check all values + assert(tx.nLockTime < LOCKTIME_THRESHOLD); // Type must be block height + assert(tx.nLockTime <= uint64_t(block_height)); + for (const auto& in : tx.vin) { + // Can not be FINAL for locktime to work + assert(in.nSequence != CTxIn::SEQUENCE_FINAL); + // May be MAX NONFINAL to disable both BIP68 and BIP125 + if (in.nSequence == CTxIn::MAX_SEQUENCE_NONFINAL) continue; + // May be MAX BIP125 to disable BIP68 and enable BIP125 + if (in.nSequence == MAX_BIP125_RBF_SEQUENCE) continue; + // The wallet does not support any other sequence-use right now. + assert(false); } - assert(locktime < LOCKTIME_THRESHOLD); - return locktime; } -static bool CreateTransactionInternal( +static util::Result<CreatedTransactionResult> CreateTransactionInternal( CWallet& wallet, const std::vector<CRecipient>& vecSend, - CTransactionRef& tx, - CAmount& nFeeRet, - int& nChangePosInOut, - bilingual_str& error, + int change_pos, 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 + CAmount nFeeRet; + int nChangePosInOut = change_pos; + + FastRandomContext rng_fast; CMutableTransaction txNew; // The resulting transaction that we make - txNew.nLockTime = GetLocktimeForNewTransaction(wallet.chain(), wallet.GetLastBlockHash(), wallet.GetLastBlockHeight()); - CoinSelectionParams coin_selection_params; // Parameters for coin selection, init with dummy + CoinSelectionParams coin_selection_params{rng_fast}; // Parameters for coin selection, init with dummy coin_selection_params.m_avoid_partial_spends = coin_control.m_avoid_partial_spends; // Set the long term feerate estimate to the wallet's consolidate feerate @@ -662,9 +803,11 @@ static bool CreateTransactionInternal( coin_selection_params.m_subtract_fee_outputs = true; } } + coin_selection_params.m_min_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)) { @@ -680,11 +823,13 @@ static bool 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. @@ -712,13 +857,11 @@ static bool 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 false; + 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 false; + return util::Error{_("Fee estimation failed. Fallbackfee is disabled. Wait a few blocks or enable -fallbackfee.")}; } // Calculate the cost of change @@ -731,7 +874,8 @@ static bool CreateTransactionInternal( // 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) { @@ -742,10 +886,8 @@ static bool CreateTransactionInternal( coin_selection_params.tx_noinputs_size += ::GetSerializeSize(txout, PROTOCOL_VERSION); } - if (IsDust(txout, wallet.chain().relayDustFee())) - { - error = _("Transaction amount too small"); - return false; + if (IsDust(txout, wallet.chain().relayDustFee())) { + return util::Error{_("Transaction amount too small")}; } txNew.vout.push_back(txout); } @@ -755,15 +897,20 @@ static bool CreateTransactionInternal( CAmount selection_target = recipients_sum + not_input_fees; // Get available coins - std::vector<COutput> vAvailableCoins; - AvailableCoins(wallet, vAvailableCoins, &coin_control, 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 false; + 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. @@ -771,25 +918,22 @@ static bool CreateTransactionInternal( assert(change_and_fee >= 0); CTxOut newTxOut(change_and_fee, scriptChange); - if (nChangePosInOut == -1) - { + if (nChangePosInOut == -1) { // Insert change txn at random position: - nChangePosInOut = GetRandInt(txNew.vout.size()+1); + 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 false; + else if ((unsigned int)nChangePosInOut > txNew.vout.size()) { + return util::Error{_("Transaction change output index out of range")}; } assert(nChangePosInOut != -1); auto change_position = txNew.vout.insert(txNew.vout.begin() + nChangePosInOut, newTxOut); // Shuffle selected coins and fill in final vin - std::vector<CInputCoin> selected_coins = result->GetShuffledInputVector(); + std::vector<COutput> selected_coins = result->GetShuffledInputVector(); - // Note how the sequence number is set to non-maxint so that - // the nLockTime set above actually works. + // The sequence number is set to non-maxint so that DiscourageFeeSniping + // works. // // BIP125 defines opt-in RBF as any nSequence < maxint-1, so // we use the highest possible value in that range (maxint-2) @@ -800,13 +944,13 @@ static bool CreateTransactionInternal( for (const auto& coin : selected_coins) { txNew.vin.push_back(CTxIn(coin.outpoint, CScript(), nSequence)); } + DiscourageFeeSniping(txNew, rng_fast, wallet.chain(), wallet.GetLastBlockHash(), wallet.GetLastBlockHeight()); // Calculate the transaction fee 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 false; + return util::Error{_("Missing solving data for estimating transaction size")}; } nFeeRet = coin_selection_params.m_effective_feerate.GetFee(nBytes); @@ -866,11 +1010,10 @@ static bool 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 false; } } ++i; @@ -880,42 +1023,37 @@ static bool CreateTransactionInternal( // Give up if change keypool ran out and change is required if (scriptChange.empty() && nChangePosInOut != -1) { - return false; + return util::Error{error}; } if (sign && !wallet.SignTransaction(txNew)) { - error = _("Signing transaction failed"); - return false; + 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 false; + return util::Error{_("Transaction too large")}; } if (nFeeRet > wallet.m_default_max_tx_fee) { - error = TransactionErrorString(TransactionError::MAX_FEE_EXCEEDED); - return false; + 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 false; + 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, @@ -925,52 +1063,45 @@ static bool 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 true; + return CreatedTransactionResult(tx, nFeeRet, nChangePosInOut, feeCalc); } -bool CreateTransaction( +util::Result<CreatedTransactionResult> CreateTransaction( CWallet& wallet, const std::vector<CRecipient>& vecSend, - CTransactionRef& tx, - CAmount& nFeeRet, - int& nChangePosInOut, - bilingual_str& error, + int change_pos, const CCoinControl& coin_control, - FeeCalculation& fee_calc_out, bool sign) { if (vecSend.empty()) { - error = _("Transaction must have at least one recipient"); - return false; + 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 false; + return util::Error{_("Transaction amounts must not be negative")}; } LOCK(wallet.cs_wallet); - int nChangePosIn = nChangePosInOut; - Assert(!tx); // tx is an out-param. TODO change the return type from bool to tx (or nullptr) - bool res = CreateTransactionInternal(wallet, vecSend, tx, nFeeRet, nChangePosInOut, error, coin_control, fee_calc_out, sign); + 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 (res && nFeeRet > 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; - CAmount nFeeRet2; - CTransactionRef tx2; - int nChangePosInOut2 = nChangePosIn; - bilingual_str error2; // fired and forgotten; if an error occurs, we discard the results - if (CreateTransactionInternal(wallet, vecSend, tx2, nFeeRet2, nChangePosInOut2, error2, tmp_cc, fee_calc_out, sign)) { - // if fee of this alternative one is within the range of the max fee, we use this one - const bool use_aps = nFeeRet2 <= nFeeRet + wallet.m_max_aps_fee; - wallet.WalletLogPrintf("Fee non-grouped = %lld, grouped = %lld, using %s\n", nFeeRet, nFeeRet2, use_aps ? "grouped" : "non-grouped"); - if (use_aps) { - tx = tx2; - nFeeRet = nFeeRet2; - nChangePosInOut = nChangePosInOut2; - } + 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}; + 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"); + if (use_aps) return txr_grouped; } } return res; @@ -987,21 +1118,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); + } + } - CTransactionRef tx_new; - FeeCalculation fee_calc_out; - if (!CreateTransaction(wallet, vecSend, tx_new, nFeeRet, nChangePosInOut, error, coinControl, fee_calc_out, false)) { + 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 4453fb2762..c29e5be5c7 100644 --- a/src/wallet/spend.h +++ b/src/wallet/spend.h @@ -6,86 +6,72 @@ #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> -namespace wallet { -/** Get the marginal bytes if spending the specified output from this transaction */ -int GetTxSpendSize(const CWallet& wallet, const CWalletTx& wtx, unsigned int out, bool use_max_sig = false); - -class COutput -{ -public: - const CWalletTx *tx; - - /** Index in tx->vout. */ - int i; - - /** - * Depth in block chain. - * If > 0: the tx is on chain and has this many confirmations. - * If = 0: the tx is waiting confirmation. - * If < 0: a conflicting tx is on chain and has this many confirmations. */ - int nDepth; - - /** Pre-computed estimated size of this output as a fully-signed input in a transaction. Can be -1 if it could not be calculated */ - int nInputBytes; - - /** Whether we have the private keys to spend this output */ - bool fSpendable; - - /** Whether we know how to spend this output, ignoring the lack of keys */ - bool fSolvable; - - /** 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 */ - bool use_max_sig; - - /** - * Whether this output is considered safe to spend. Unconfirmed transactions - * from outside keys and unconfirmed replacement transactions are considered - * unsafe and will not be used to fund new spending transactions. - */ - bool fSafe; - - COutput(const CWallet& wallet, const CWalletTx& wtx, int iIn, int nDepthIn, bool fSpendableIn, bool fSolvableIn, bool fSafeIn, bool use_max_sig_in = false) - { - tx = &wtx; i = iIn; nDepth = nDepthIn; fSpendable = fSpendableIn; fSolvable = fSolvableIn; fSafe = fSafeIn; nInputBytes = -1; use_max_sig = use_max_sig_in; - // If known and signable by the given wallet, compute nInputBytes - // Failure will keep this value -1 - if (fSpendable) { - nInputBytes = GetTxSpendSize(wallet, wtx, i, use_max_sig); - } - } - - std::string ToString() const; - - inline CInputCoin GetInputCoin() const - { - return CInputCoin(tx->tx, i, nInputBytes); - } -}; - -//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); +#include <optional> +namespace wallet { +/** Get the marginal bytes if spending the specified output from this transaction. + * 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, 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. + */ +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); @@ -93,6 +79,7 @@ CAmount GetAvailableBalance(const CWallet& wallet, const CCoinControl* coinContr * Find non-change parent output. */ const CTxOut& FindNonChangeParentOutput(const CWallet& wallet, const CTransaction& tx, int output) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet); +const CTxOut& FindNonChangeParentOutput(const CWallet& wallet, const COutPoint& outpoint) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet); /** * Return list of available coins and locked coins grouped by non-change output address. @@ -100,43 +87,71 @@ const CTxOut& FindNonChangeParentOutput(const CWallet& wallet, const CTransactio 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, const FeeCalculation& _fee_calc) + : tx(_tx), fee(_fee), fee_calc(_fee_calc), change_pos(_change_pos) {} +}; + /** * Create a new transaction paying the recipients with a set of coins * selected by SelectCoins(); Also create the change output, when needed - * @note passing nChangePosInOut as -1 will result in setting a random position + * @note passing change_pos as -1 will result in setting a random position */ -bool CreateTransaction(CWallet& wallet, const std::vector<CRecipient>& vecSend, CTransactionRef& tx, CAmount& nFeeRet, int& nChangePosInOut, 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 2b2181e70b..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) @@ -37,6 +37,22 @@ static void ErrorLogCallback(void* arg, int code, const char* msg) LogPrintf("SQLite Error. Code: %d. Message: %s\n", code, msg); } +static bool BindBlobToStatement(sqlite3_stmt* stmt, + int index, + Span<const std::byte> blob, + const std::string& description) +{ + int res = sqlite3_bind_blob(stmt, index, blob.data(), blob.size(), SQLITE_STATIC); + if (res != SQLITE_OK) { + LogPrintf("Unable to bind %s to statement: %s\n", description, sqlite3_errstr(res)); + sqlite3_clear_bindings(stmt); + sqlite3_reset(stmt); + return false; + } + + return true; +} + static std::optional<int> ReadPragmaInteger(sqlite3* db, const std::string& key, const std::string& description, bilingual_str& error) { std::string stmt_text = strprintf("PRAGMA %s", key); @@ -67,8 +83,8 @@ static void SetPragma(sqlite3* db, const std::string& key, const std::string& va } } -SQLiteDatabase::SQLiteDatabase(const fs::path& dir_path, const fs::path& file_path, bool mock) - : WalletDatabase(), m_mock(mock), m_dir_path(fs::PathToString(dir_path)), m_file_path(fs::PathToString(file_path)) +SQLiteDatabase::SQLiteDatabase(const fs::path& dir_path, const fs::path& file_path, const DatabaseOptions& options, bool mock) + : WalletDatabase(), m_mock(mock), m_dir_path(fs::PathToString(dir_path)), m_file_path(fs::PathToString(file_path)), m_use_unsafe_sync(options.use_unsafe_sync) { { LOCK(g_sqlite_mutex); @@ -239,7 +255,7 @@ void SQLiteDatabase::Open() // Enable fullfsync for the platforms that use it SetPragma(m_db, "fullfsync", "true", "Failed to enable fullfsync"); - if (gArgs.GetBoolArg("-unsafesqlitesync", false)) { + if (m_use_unsafe_sync) { // Use normal synchronous mode for the journal LogPrintf("WARNING SQLite is configured to not wait for data to be flushed to disk. Data loss and corruption may occur.\n"); SetPragma(m_db, "synchronous", "OFF", "Failed to set synchronous mode to OFF"); @@ -377,14 +393,8 @@ bool SQLiteBatch::ReadKey(CDataStream&& key, CDataStream& value) assert(m_read_stmt); // Bind: leftmost parameter in statement is index 1 - int res = sqlite3_bind_blob(m_read_stmt, 1, key.data(), key.size(), SQLITE_STATIC); - if (res != SQLITE_OK) { - LogPrintf("%s: Unable to bind statement: %s\n", __func__, sqlite3_errstr(res)); - sqlite3_clear_bindings(m_read_stmt); - sqlite3_reset(m_read_stmt); - return false; - } - res = sqlite3_step(m_read_stmt); + if (!BindBlobToStatement(m_read_stmt, 1, key, "key")) return false; + int res = sqlite3_step(m_read_stmt); if (res != SQLITE_ROW) { if (res != SQLITE_DONE) { // SQLITE_DONE means "not found", don't log an error in that case. @@ -395,7 +405,7 @@ bool SQLiteBatch::ReadKey(CDataStream&& key, CDataStream& value) return false; } // Leftmost column in result is index 0 - const std::byte* data{BytePtr(sqlite3_column_blob(m_read_stmt, 0))}; + const std::byte* data{AsBytePtr(sqlite3_column_blob(m_read_stmt, 0))}; size_t data_size(sqlite3_column_bytes(m_read_stmt, 0)); value.write({data, data_size}); @@ -418,23 +428,11 @@ bool SQLiteBatch::WriteKey(CDataStream&& key, CDataStream&& value, bool overwrit // Bind: leftmost parameter in statement is index 1 // Insert index 1 is key, 2 is value - int res = sqlite3_bind_blob(stmt, 1, key.data(), key.size(), SQLITE_STATIC); - if (res != SQLITE_OK) { - LogPrintf("%s: Unable to bind key to statement: %s\n", __func__, sqlite3_errstr(res)); - sqlite3_clear_bindings(stmt); - sqlite3_reset(stmt); - return false; - } - res = sqlite3_bind_blob(stmt, 2, value.data(), value.size(), SQLITE_STATIC); - if (res != SQLITE_OK) { - LogPrintf("%s: Unable to bind value to statement: %s\n", __func__, sqlite3_errstr(res)); - sqlite3_clear_bindings(stmt); - sqlite3_reset(stmt); - return false; - } + if (!BindBlobToStatement(stmt, 1, key, "key")) return false; + if (!BindBlobToStatement(stmt, 2, value, "value")) return false; // Execute - res = sqlite3_step(stmt); + int res = sqlite3_step(stmt); sqlite3_clear_bindings(stmt); sqlite3_reset(stmt); if (res != SQLITE_DONE) { @@ -449,16 +447,10 @@ bool SQLiteBatch::EraseKey(CDataStream&& key) assert(m_delete_stmt); // Bind: leftmost parameter in statement is index 1 - int res = sqlite3_bind_blob(m_delete_stmt, 1, key.data(), key.size(), SQLITE_STATIC); - if (res != SQLITE_OK) { - LogPrintf("%s: Unable to bind statement: %s\n", __func__, sqlite3_errstr(res)); - sqlite3_clear_bindings(m_delete_stmt); - sqlite3_reset(m_delete_stmt); - return false; - } + if (!BindBlobToStatement(m_delete_stmt, 1, key, "key")) return false; // Execute - res = sqlite3_step(m_delete_stmt); + int res = sqlite3_step(m_delete_stmt); sqlite3_clear_bindings(m_delete_stmt); sqlite3_reset(m_delete_stmt); if (res != SQLITE_DONE) { @@ -473,18 +465,11 @@ bool SQLiteBatch::HasKey(CDataStream&& key) assert(m_read_stmt); // Bind: leftmost parameter in statement is index 1 - bool ret = false; - int res = sqlite3_bind_blob(m_read_stmt, 1, key.data(), key.size(), SQLITE_STATIC); - if (res == SQLITE_OK) { - res = sqlite3_step(m_read_stmt); - if (res == SQLITE_ROW) { - ret = true; - } - } - + if (!BindBlobToStatement(m_read_stmt, 1, key, "key")) return false; + int res = sqlite3_step(m_read_stmt); sqlite3_clear_bindings(m_read_stmt); sqlite3_reset(m_read_stmt); - return ret; + return res == SQLITE_ROW; } bool SQLiteBatch::StartCursor() @@ -512,10 +497,10 @@ bool SQLiteBatch::ReadAtCursor(CDataStream& key, CDataStream& value, bool& compl } // Leftmost column in result is index 0 - const std::byte* key_data{BytePtr(sqlite3_column_blob(m_cursor_stmt, 0))}; + const std::byte* key_data{AsBytePtr(sqlite3_column_blob(m_cursor_stmt, 0))}; size_t key_data_size(sqlite3_column_bytes(m_cursor_stmt, 0)); key.write({key_data, key_data_size}); - const std::byte* value_data{BytePtr(sqlite3_column_blob(m_cursor_stmt, 1))}; + const std::byte* value_data{AsBytePtr(sqlite3_column_blob(m_cursor_stmt, 1))}; size_t value_data_size(sqlite3_column_bytes(m_cursor_stmt, 1)); value.write({value_data, value_data_size}); return true; @@ -561,7 +546,7 @@ std::unique_ptr<SQLiteDatabase> MakeSQLiteDatabase(const fs::path& path, const D { try { fs::path data_file = SQLiteDataFile(path); - auto db = std::make_unique<SQLiteDatabase>(data_file.parent_path(), data_file); + auto db = std::make_unique<SQLiteDatabase>(data_file.parent_path(), data_file, options); if (options.verify && !db->Verify(error)) { status = DatabaseStatus::FAILED_VERIFY; return nullptr; diff --git a/src/wallet/sqlite.h b/src/wallet/sqlite.h index 3ed598d0d2..47b7ebb0ec 100644 --- a/src/wallet/sqlite.h +++ b/src/wallet/sqlite.h @@ -69,7 +69,7 @@ public: SQLiteDatabase() = delete; /** Create DB handle to real database */ - SQLiteDatabase(const fs::path& dir_path, const fs::path& file_path, bool mock = false); + SQLiteDatabase(const fs::path& dir_path, const fs::path& file_path, const DatabaseOptions& options, bool mock = false); ~SQLiteDatabase(); @@ -113,6 +113,7 @@ public: std::unique_ptr<DatabaseBatch> MakeBatch(bool flush_on_close = true) override; sqlite3* m_db{nullptr}; + bool m_use_unsafe_sync; }; std::unique_ptr<SQLiteDatabase> MakeSQLiteDatabase(const fs::path& path, const DatabaseOptions& options, DatabaseStatus& status, bilingual_str& error); 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 b9f12158ca..4dfdc87a38 100644 --- a/src/wallet/test/coinselector_tests.cpp +++ b/src/wallet/test/coinselector_tests.cpp @@ -28,20 +28,20 @@ BOOST_FIXTURE_TEST_SUITE(coinselector_tests, WalletTestingSetup) // we repeat those tests this many times and only complain if all iterations of the test fail #define RANDOM_REPEATS 5 -typedef std::set<CInputCoin> CoinSet; +typedef std::set<COutput> CoinSet; static const CoinEligibilityFilter filter_standard(1, 6, 0); static const CoinEligibilityFilter filter_confirmed(1, 1, 0); static const CoinEligibilityFilter filter_standard_extra(6, 6, 0); static int nextLockTime = 0; -static void add_coin(const CAmount& nValue, int nInput, std::vector<CInputCoin>& set) +static void add_coin(const CAmount& nValue, int nInput, std::vector<COutput>& set) { CMutableTransaction tx; tx.vout.resize(nInput + 1); tx.vout[nInput].nValue = nValue; tx.nLockTime = nextLockTime++; // so all transactions get different hashes - set.emplace_back(MakeTransactionRef(tx), nInput); + set.emplace_back(COutPoint(tx.GetHash(), nInput), tx.vout.at(nInput), /*depth=*/ 1, /*input_bytes=*/ -1, /*spendable=*/ true, /*solvable=*/ true, /*safe=*/ true, /*time=*/ 0, /*from_me=*/ false, /*fees=*/ 0); } static void add_coin(const CAmount& nValue, int nInput, SelectionResult& result) @@ -50,9 +50,9 @@ static void add_coin(const CAmount& nValue, int nInput, SelectionResult& result) tx.vout.resize(nInput + 1); tx.vout[nInput].nValue = nValue; tx.nLockTime = nextLockTime++; // so all transactions get different hashes - CInputCoin coin(MakeTransactionRef(tx), nInput); + COutput output(COutPoint(tx.GetHash(), nInput), tx.vout.at(nInput), /*depth=*/ 1, /*input_bytes=*/ -1, /*spendable=*/ true, /*solvable=*/ true, /*safe=*/ true, /*time=*/ 0, /*from_me=*/ false, /*fees=*/ 0); OutputGroup group; - group.Insert(coin, 1, false, 0, 0, true); + group.Insert(output, /*ancestors=*/ 0, /*descendants=*/ 0, /*positive_only=*/ true); result.AddInput(group); } @@ -62,30 +62,19 @@ static void add_coin(const CAmount& nValue, int nInput, CoinSet& set, CAmount fe tx.vout.resize(nInput + 1); tx.vout[nInput].nValue = nValue; tx.nLockTime = nextLockTime++; // so all transactions get different hashes - CInputCoin coin(MakeTransactionRef(tx), nInput); - coin.effective_value = nValue - fee; - coin.m_fee = fee; - coin.m_long_term_fee = long_term_fee; + COutput coin(COutPoint(tx.GetHash(), nInput), tx.vout.at(nInput), /*depth=*/ 1, /*input_bytes=*/ 148, /*spendable=*/ true, /*solvable=*/ true, /*safe=*/ true, /*time=*/ 0, /*from_me=*/ false, fee); + coin.long_term_fee = long_term_fee; set.insert(coin); } -static void add_coin(std::vector<COutput>& coins, CWallet& wallet, const CAmount& nValue, 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); - } - if (fIsFromMe) { - // IsFromMe() returns (GetDebit() > 0), and GetDebit() is 0 if vin.empty(), - // so stop vin being empty, and cache a non-zero Debit to fake out IsFromMe() - tx.vin.resize(1); + tx.vout[nInput].scriptPubKey = GetScriptForDestination(*Assert(wallet.GetNewDestination(OutputType::BECH32, ""))); } uint256 txid = tx.GetHash(); @@ -93,13 +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; - if (fIsFromMe) - { - wtx.m_amounts[CWalletTx::DEBIT].Set(ISMINE_SPENDABLE, 1); - wtx.m_is_cache_empty = false; - } - COutput output(wallet, wtx, nInput, nAge, true /* spendable */, true /* solvable */, true /* safe */); - coins.push_back(output); + 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. @@ -124,11 +108,14 @@ static bool EquivalentResult(const SelectionResult& a, const SelectionResult& b) /** Check if this selection is equal to another one. Equal means same inputs (i.e same value and prevout) */ static bool EqualResult(const SelectionResult& a, const SelectionResult& b) { - std::pair<CoinSet::iterator, CoinSet::iterator> ret = std::mismatch(a.GetInputSet().begin(), a.GetInputSet().end(), b.GetInputSet().begin()); + std::pair<CoinSet::iterator, CoinSet::iterator> ret = std::mismatch(a.GetInputSet().begin(), a.GetInputSet().end(), b.GetInputSet().begin(), + [](const COutput& a, const COutput& b) { + return a.outpoint == b.outpoint; + }); return ret.first == a.GetInputSet().end() && ret.second == b.GetInputSet().end(); } -static CAmount make_hard_case(int utxos, std::vector<CInputCoin>& utxo_pool) +static CAmount make_hard_case(int utxos, std::vector<COutput>& utxo_pool) { utxo_pool.clear(); CAmount target = 0; @@ -140,45 +127,43 @@ static CAmount make_hard_case(int utxos, std::vector<CInputCoin>& utxo_pool) return target; } -inline std::vector<OutputGroup>& GroupCoins(const std::vector<CInputCoin>& coins) -{ - static std::vector<OutputGroup> static_groups; - static_groups.clear(); - for (auto& coin : coins) { - static_groups.emplace_back(); - static_groups.back().Insert(coin, 0, true, 0, 0, false); - } - return static_groups; -} - -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.GetInputCoin(), coin.nDepth, coin.tx->m_amounts[CWalletTx::DEBIT].m_cached[ISMINE_SPENDABLE] && coin.tx->m_amounts[CWalletTx::DEBIT].m_value[ISMINE_SPENDABLE] == 1 /* HACK: we can't figure out the is_me flag so we use the conditions defined above; perhaps set safe to false for !fIsFromMe in add_coin() */, 0, 0, false); + 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) { - CoinSelectionParams coin_selection_params(/* change_output_size= */ 0, - /* change_spend_size= */ 0, /* effective_feerate= */ CFeeRate(0), - /* long_term_feerate= */ CFeeRate(0), /* discard_feerate= */ CFeeRate(0), - /* tx_noinputs_size= */ 0, /* avoid_partial= */ false); + FastRandomContext rand{}; + CoinSelectionParams coin_selection_params{ + rand, + /*change_output_size=*/ 0, + /*change_spend_size=*/ 0, + /*min_change_target=*/ CENT, + /*effective_feerate=*/ CFeeRate(0), + /*long_term_feerate=*/ CFeeRate(0), + /*discard_feerate=*/ CFeeRate(0), + /*tx_noinputs_size=*/ 0, + /*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; } // Branch and bound coin selection tests BOOST_AUTO_TEST_CASE(bnb_search_test) { + FastRandomContext rand{}; // Setup - std::vector<CInputCoin> utxo_pool; - SelectionResult expected_result(CAmount(0)); + std::vector<COutput> utxo_pool; + SelectionResult expected_result(CAmount(0), SelectionAlgorithm::BNB); ///////////////////////// // Known Outcome tests // @@ -210,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)); @@ -236,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); @@ -301,10 +287,17 @@ 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(/* change_output_size= */ 0, - /* change_spend_size= */ 0, /* effective_feerate= */ CFeeRate(3000), - /* long_term_feerate= */ CFeeRate(1000), /* discard_feerate= */ CFeeRate(1000), - /* tx_noinputs_size= */ 0, /* avoid_partial= */ false); + CoinSelectionParams coin_selection_params_bnb{ + rand, + /*change_output_size=*/ 0, + /*change_spend_size=*/ 0, + /*min_change_target=*/ 0, + /*effective_feerate=*/ CFeeRate(3000), + /*long_term_feerate=*/ CFeeRate(1000), + /*discard_feerate=*/ CFeeRate(1000), + /*tx_noinputs_size=*/ 0, + /*avoid_partial=*/ false, + }; { std::unique_ptr<CWallet> wallet = std::make_unique<CWallet>(m_node.chain.get(), "", m_args, CreateMockWalletDatabase()); wallet->LoadWallet(); @@ -312,18 +305,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); - coins.at(0).nInputBytes = 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); - coins.at(0).nInputBytes = 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); } @@ -335,260 +328,318 @@ 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, 6 * 24, false, 0, true); - add_coin(coins, *wallet, 3 * CENT, 6 * 24, false, 0, true); - add_coin(coins, *wallet, 2 * CENT, 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(COutPoint(coins.at(0).tx->GetHash(), coins.at(0).i)); + 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); } + { + 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; + + // 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(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, available_coins, 10 * CENT, coin_control, coin_selection_params_bnb); + BOOST_CHECK(EquivalentResult(expected_result, *result11)); + 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(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, available_coins, 10 * CENT, coin_control, coin_selection_params_bnb); + BOOST_CHECK(EquivalentResult(expected_result, *result12)); + 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(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.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)); + } } BOOST_AUTO_TEST_CASE(knapsack_solver_test) { + FastRandomContext rand{}; + const auto temp1{[&rand](std::vector<OutputGroup>& g, const CAmount& v, CAmount c) { return KnapsackSolver(g, v, c, rand); }}; + const auto KnapsackSolver{temp1}; std::unique_ptr<CWallet> wallet = std::make_unique<CWallet>(m_node.chain.get(), "", m_args, CreateMockWalletDatabase()); wallet->LoadWallet(); LOCK(wallet->cs_wallet); 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)); + BOOST_CHECK(!KnapsackSolver(KnapsackGroupOutputs(available_coins.All(), *wallet, filter_standard), 1 * CENT, CENT)); - add_coin(coins, *wallet, 1*CENT, 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)); + 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); + 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)); + 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); + 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, 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)); + 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)); + 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); + 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); + 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); + 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); + 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); + 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); + 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); + 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)); + 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); + 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); + 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); + 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); + 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); + 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); + 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, MIN_CHANGE * 1 / 10); - add_coin(coins, *wallet, MIN_CHANGE * 2 / 10); - add_coin(coins, *wallet, MIN_CHANGE * 3 / 10); - add_coin(coins, *wallet, MIN_CHANGE * 4 / 10); - add_coin(coins, *wallet, MIN_CHANGE * 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 * MIN_CHANGE from the 1.5 * MIN_CHANGE - // we'll get change smaller than MIN_CHANGE whatever happens, so can expect MIN_CHANGE exactly - const auto result16 = KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_confirmed), MIN_CHANGE); + // 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(available_coins.All(), *wallet, filter_confirmed), CENT, CENT); BOOST_CHECK(result16); - BOOST_CHECK_EQUAL(result16->GetSelectedValue(), MIN_CHANGE); + BOOST_CHECK_EQUAL(result16->GetSelectedValue(), CENT); // but if we add a bigger coin, small change is avoided - add_coin(coins, *wallet, 1111*MIN_CHANGE); + 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 * MIN_CHANGE); + const auto result17 = KnapsackSolver(KnapsackGroupOutputs(available_coins.All(), *wallet, filter_confirmed), 1 * CENT, CENT); BOOST_CHECK(result17); - BOOST_CHECK_EQUAL(result17->GetSelectedValue(), 1 * MIN_CHANGE); // we should get the exact amount + BOOST_CHECK_EQUAL(result17->GetSelectedValue(), 1 * CENT); // we should get the exact amount // if we add more small coins: - add_coin(coins, *wallet, MIN_CHANGE * 6 / 10); - add_coin(coins, *wallet, MIN_CHANGE * 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 * MIN_CHANGE - const auto result18 = KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_confirmed), 1 * MIN_CHANGE); + // and try again to make 1.0 * CENT + const auto result18 = KnapsackSolver(KnapsackGroupOutputs(available_coins.All(), *wallet, filter_confirmed), 1 * CENT, CENT); BOOST_CHECK(result18); - BOOST_CHECK_EQUAL(result18->GetSelectedValue(), 1 * MIN_CHANGE); // we should get the exact amount + 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); + 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 - // if there's not enough in the smaller coins to make at least 1 * MIN_CHANGE change (0.5+0.6+0.7 < 1.0+1.0), + // if there's not enough in the smaller coins to make at least 1 * CENT change (0.5+0.6+0.7 < 1.0+1.0), // 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, MIN_CHANGE * 5 / 10); - add_coin(coins, *wallet, MIN_CHANGE * 6 / 10); - add_coin(coins, *wallet, MIN_CHANGE * 7 / 10); - add_coin(coins, *wallet, 1111 * MIN_CHANGE); - const auto result20 = KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_confirmed), 1 * MIN_CHANGE); + 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 * MIN_CHANGE); // we get the bigger coin + 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, MIN_CHANGE * 4 / 10); - add_coin(coins, *wallet, MIN_CHANGE * 6 / 10); - add_coin(coins, *wallet, MIN_CHANGE * 8 / 10); - add_coin(coins, *wallet, 1111 * MIN_CHANGE); - const auto result21 = KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_confirmed), MIN_CHANGE); + 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(), MIN_CHANGE); // we should get the exact amount + 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, MIN_CHANGE * 5 / 100); - add_coin(coins, *wallet, MIN_CHANGE * 1); - add_coin(coins, *wallet, MIN_CHANGE * 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), MIN_CHANGE * 10001 / 100); + const auto result22 = KnapsackSolver(KnapsackGroupOutputs(available_coins.All(), *wallet, filter_confirmed), CENT * 10001 / 100, CENT); BOOST_CHECK(result22); - BOOST_CHECK_EQUAL(result22->GetSelectedValue(), MIN_CHANGE * 10105 / 100); // we should get all coins + 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), MIN_CHANGE * 9990 / 100); + const auto result23 = KnapsackSolver(KnapsackGroupOutputs(available_coins.All(), *wallet, filter_confirmed), CENT * 9990 / 100, CENT); BOOST_CHECK(result23); - BOOST_CHECK_EQUAL(result23->GetSelectedValue(), 101 * MIN_CHANGE); + BOOST_CHECK_EQUAL(result23->GetSelectedValue(), 101 * CENT); BOOST_CHECK_EQUAL(result23->GetInputSet().size(), 2U); } // 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); + const auto result24 = KnapsackSolver(KnapsackGroupOutputs(available_coins.All(), *wallet, filter_confirmed), 2000, CENT); BOOST_CHECK(result24); - if (amt - 2000 < MIN_CHANGE) { + if (amt - 2000 < CENT) { // needs more than one input: - uint16_t returnSize = std::ceil((2000.0 + MIN_CHANGE)/amt); + uint16_t returnSize = std::ceil((2000.0 + CENT)/amt); CAmount returnValue = amt * returnSize; BOOST_CHECK_EQUAL(result24->GetSelectedValue(), returnValue); BOOST_CHECK_EQUAL(result24->GetInputSet().size(), returnSize); @@ -602,17 +653,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); + const auto result25 = KnapsackSolver(GroupCoins(available_coins.All()), 50 * COIN, CENT); BOOST_CHECK(result25); - const auto result26 = KnapsackSolver(GroupCoins(coins), 50 * COIN); + const auto result26 = KnapsackSolver(GroupCoins(available_coins.All()), 50 * COIN, CENT); BOOST_CHECK(result26); BOOST_CHECK(!EqualResult(*result25, *result26)); @@ -623,9 +674,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); + const auto result27 = KnapsackSolver(GroupCoins(available_coins.All()), COIN, CENT); BOOST_CHECK(result27); - const auto result28 = KnapsackSolver(GroupCoins(coins), COIN); + const auto result28 = KnapsackSolver(GroupCoins(available_coins.All()), COIN, CENT); BOOST_CHECK(result28); if (EqualResult(*result27, *result28)) fails++; @@ -636,19 +687,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); + const auto result29 = KnapsackSolver(GroupCoins(available_coins.All()), 90 * CENT, CENT); BOOST_CHECK(result29); - const auto result30 = KnapsackSolver(GroupCoins(coins), 90 * CENT); + const auto result30 = KnapsackSolver(GroupCoins(available_coins.All()), 90 * CENT, CENT); BOOST_CHECK(result30); if (EqualResult(*result29, *result30)) fails++; @@ -660,20 +711,21 @@ BOOST_AUTO_TEST_CASE(knapsack_solver_test) BOOST_AUTO_TEST_CASE(ApproximateBestSubset) { + FastRandomContext rand{}; std::unique_ptr<CWallet> wallet = std::make_unique<CWallet>(m_node.chain.get(), "", m_args, CreateMockWalletDatabase()); wallet->LoadWallet(); LOCK(wallet->cs_wallet); 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); + 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); @@ -696,14 +748,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; } @@ -714,12 +766,19 @@ BOOST_AUTO_TEST_CASE(SelectCoins_test) CAmount target = rand.randrange(balance - 1000) + 1000; // Perform selection - CoinSelectionParams cs_params(/* change_output_size= */ 34, - /* change_spend_size= */ 148, /* effective_feerate= */ CFeeRate(0), - /* long_term_feerate= */ CFeeRate(0), /* discard_feerate= */ CFeeRate(0), - /* tx_noinputs_size= */ 0, /* avoid_partial= */ false); + CoinSelectionParams cs_params{ + rand, + /*change_output_size=*/ 34, + /*change_spend_size=*/ 148, + /*min_change_target=*/ CENT, + /*effective_feerate=*/ CFeeRate(0), + /*long_term_feerate=*/ CFeeRate(0), + /*discard_feerate=*/ CFeeRate(0), + /*tx_noinputs_size=*/ 0, + /*avoid_partial=*/ false, + }; 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); } @@ -804,7 +863,58 @@ 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) +{ + const int input_bytes = 148; + const CFeeRate feerate(1000); + const CAmount nValue = 10000; + const int nInput = 0; + + CMutableTransaction tx; + tx.vout.resize(1); + tx.vout[nInput].nValue = nValue; + + // standard case, pass feerate in constructor + COutput output1(COutPoint(tx.GetHash(), nInput), tx.vout.at(nInput), /*depth=*/ 1, input_bytes, /*spendable=*/ true, /*solvable=*/ true, /*safe=*/ true, /*time=*/ 0, /*from_me=*/ false, feerate); + const CAmount expected_ev1 = 9852; // 10000 - 148 + BOOST_CHECK_EQUAL(output1.GetEffectiveValue(), expected_ev1); + + // input bytes unknown (input_bytes = -1), pass feerate in constructor + COutput output2(COutPoint(tx.GetHash(), nInput), tx.vout.at(nInput), /*depth=*/ 1, /*input_bytes=*/ -1, /*spendable=*/ true, /*solvable=*/ true, /*safe=*/ true, /*time=*/ 0, /*from_me=*/ false, feerate); + BOOST_CHECK_EQUAL(output2.GetEffectiveValue(), nValue); // The effective value should be equal to the absolute value if input_bytes is -1 + + // negative effective value, pass feerate in constructor + COutput output3(COutPoint(tx.GetHash(), nInput), tx.vout.at(nInput), /*depth=*/ 1, input_bytes, /*spendable=*/ true, /*solvable=*/ true, /*safe=*/ true, /*time=*/ 0, /*from_me=*/ false, CFeeRate(100000)); + const CAmount expected_ev3 = -4800; // 10000 - 14800 + BOOST_CHECK_EQUAL(output3.GetEffectiveValue(), expected_ev3); + + // standard case, pass fees in constructor + const CAmount fees = 148; + COutput output4(COutPoint(tx.GetHash(), nInput), tx.vout.at(nInput), /*depth=*/ 1, input_bytes, /*spendable=*/ true, /*solvable=*/ true, /*safe=*/ true, /*time=*/ 0, /*from_me=*/ false, fees); + BOOST_CHECK_EQUAL(output4.GetEffectiveValue(), expected_ev1); + + // input bytes unknown (input_bytes = -1), pass fees in constructor + COutput output5(COutPoint(tx.GetHash(), nInput), tx.vout.at(nInput), /*depth=*/ 1, /*input_bytes=*/ -1, /*spendable=*/ true, /*solvable=*/ true, /*safe=*/ true, /*time=*/ 0, /*from_me=*/ false, /*fees=*/ 0); + BOOST_CHECK_EQUAL(output5.GetEffectiveValue(), nValue); // The effective value should be equal to the absolute value if input_bytes is -1 } BOOST_AUTO_TEST_SUITE_END() diff --git a/src/wallet/test/db_tests.cpp b/src/wallet/test/db_tests.cpp index 35ae3707f8..f61808c549 100644 --- a/src/wallet/test/db_tests.cpp +++ b/src/wallet/test/db_tests.cpp @@ -15,22 +15,22 @@ namespace wallet { BOOST_FIXTURE_TEST_SUITE(db_tests, BasicTestingSetup) -static std::shared_ptr<BerkeleyEnvironment> GetWalletEnv(const fs::path& path, std::string& database_filename) +static std::shared_ptr<BerkeleyEnvironment> GetWalletEnv(const fs::path& path, fs::path& database_filename) { fs::path data_file = BDBDataFile(path); - database_filename = fs::PathToString(data_file.filename()); - return GetBerkeleyEnv(data_file.parent_path()); + database_filename = data_file.filename(); + return GetBerkeleyEnv(data_file.parent_path(), false); } BOOST_AUTO_TEST_CASE(getwalletenv_file) { - std::string test_name = "test_name.dat"; - const fs::path datadir = gArgs.GetDataDirNet(); + fs::path test_name = "test_name.dat"; + const fs::path datadir = m_args.GetDataDirNet(); fs::path file_path = datadir / test_name; std::ofstream f{file_path}; f.close(); - std::string filename; + fs::path filename; std::shared_ptr<BerkeleyEnvironment> env = GetWalletEnv(file_path, filename); BOOST_CHECK_EQUAL(filename, test_name); BOOST_CHECK_EQUAL(env->Directory(), datadir); @@ -38,10 +38,10 @@ BOOST_AUTO_TEST_CASE(getwalletenv_file) BOOST_AUTO_TEST_CASE(getwalletenv_directory) { - std::string expected_name = "wallet.dat"; - const fs::path datadir = gArgs.GetDataDirNet(); + fs::path expected_name = "wallet.dat"; + const fs::path datadir = m_args.GetDataDirNet(); - std::string filename; + fs::path filename; std::shared_ptr<BerkeleyEnvironment> env = GetWalletEnv(datadir, filename); BOOST_CHECK_EQUAL(filename, expected_name); BOOST_CHECK_EQUAL(env->Directory(), datadir); @@ -49,9 +49,9 @@ BOOST_AUTO_TEST_CASE(getwalletenv_directory) BOOST_AUTO_TEST_CASE(getwalletenv_g_dbenvs_multiple) { - fs::path datadir = gArgs.GetDataDirNet() / "1"; - fs::path datadir_2 = gArgs.GetDataDirNet() / "2"; - std::string filename; + fs::path datadir = m_args.GetDataDirNet() / "1"; + fs::path datadir_2 = m_args.GetDataDirNet() / "2"; + fs::path filename; std::shared_ptr<BerkeleyEnvironment> env_1 = GetWalletEnv(datadir, filename); std::shared_ptr<BerkeleyEnvironment> env_2 = GetWalletEnv(datadir, filename); @@ -65,7 +65,7 @@ BOOST_AUTO_TEST_CASE(getwalletenv_g_dbenvs_free_instance) { fs::path datadir = gArgs.GetDataDirNet() / "1"; fs::path datadir_2 = gArgs.GetDataDirNet() / "2"; - std::string filename; + fs::path filename; std::shared_ptr <BerkeleyEnvironment> env_1_a = GetWalletEnv(datadir, filename); std::shared_ptr <BerkeleyEnvironment> env_2_a = GetWalletEnv(datadir_2, filename); diff --git a/src/wallet/test/fuzz/coinselection.cpp b/src/wallet/test/fuzz/coinselection.cpp new file mode 100644 index 0000000000..3465f2f331 --- /dev/null +++ b/src/wallet/test/fuzz/coinselection.cpp @@ -0,0 +1,99 @@ +// 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 <policy/feerate.h> +#include <primitives/transaction.h> +#include <test/fuzz/FuzzedDataProvider.h> +#include <test/fuzz/fuzz.h> +#include <test/fuzz/util.h> +#include <test/util/setup_common.h> +#include <wallet/coinselection.h> + +#include <vector> + +namespace wallet { + +static void AddCoin(const CAmount& value, int n_input, int n_input_bytes, int locktime, std::vector<COutput>& coins, CFeeRate fee_rate) +{ + CMutableTransaction tx; + tx.vout.resize(n_input + 1); + tx.vout[n_input].nValue = value; + tx.nLockTime = locktime; // all transactions get different hashes + coins.emplace_back(COutPoint(tx.GetHash(), n_input), tx.vout.at(n_input), /*depth=*/0, n_input_bytes, /*spendable=*/true, /*solvable=*/true, /*safe=*/true, /*time=*/0, /*from_me=*/true, fee_rate); +} + +// Randomly distribute coins to instances of OutputGroup +static void GroupCoins(FuzzedDataProvider& fuzzed_data_provider, const std::vector<COutput>& coins, const CoinSelectionParams& coin_params, bool positive_only, std::vector<OutputGroup>& output_groups) +{ + auto output_group = OutputGroup(coin_params); + 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 + // that would be invalid for the BnB algorithm + valid_outputgroup = !positive_only || output_group.GetSelectionAmount() > 0; + if (valid_outputgroup && fuzzed_data_provider.ConsumeBool()) { + output_groups.push_back(output_group); + output_group = OutputGroup(coin_params); + valid_outputgroup = false; + } + } + if (valid_outputgroup) output_groups.push_back(output_group); +} + +FUZZ_TARGET(coinselection) +{ + FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()}; + std::vector<COutput> utxo_pool; + + const CFeeRate long_term_fee_rate{ConsumeMoney(fuzzed_data_provider, /*max=*/COIN)}; + const CFeeRate effective_fee_rate{ConsumeMoney(fuzzed_data_provider, /*max=*/COIN)}; + const CAmount cost_of_change{ConsumeMoney(fuzzed_data_provider, /*max=*/COIN)}; + const CAmount target{fuzzed_data_provider.ConsumeIntegralInRange<CAmount>(1, MAX_MONEY)}; + const bool subtract_fee_outputs{fuzzed_data_provider.ConsumeBool()}; + + FastRandomContext fast_random_context{ConsumeUInt256(fuzzed_data_provider)}; + CoinSelectionParams coin_params{fast_random_context}; + 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; + + // Create some coins + CAmount total_balance{0}; + int next_locktime{0}; + LIMITED_WHILE(fuzzed_data_provider.ConsumeBool(), 10000) + { + const int n_input{fuzzed_data_provider.ConsumeIntegralInRange<int>(0, 10)}; + const int n_input_bytes{fuzzed_data_provider.ConsumeIntegralInRange<int>(100, 10000)}; + const CAmount amount{fuzzed_data_provider.ConsumeIntegralInRange<CAmount>(1, MAX_MONEY)}; + if (total_balance + amount >= MAX_MONEY) { + break; + } + AddCoin(amount, n_input, n_input_bytes, ++next_locktime, utxo_pool, coin_params.m_effective_feerate); + total_balance += amount; + } + + std::vector<OutputGroup> group_pos; + GroupCoins(fuzzed_data_provider, utxo_pool, coin_params, /*positive_only=*/true, group_pos); + std::vector<OutputGroup> group_all; + GroupCoins(fuzzed_data_provider, utxo_pool, coin_params, /*positive_only=*/false, group_all); + + // Run coinselection algorithms + 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); + + CAmount change_target{GenerateChangeTarget(target, 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 the total balance is sufficient for the target and we are not using + // effective values, Knapsack should always find a solution. + if (total_balance >= target && subtract_fee_outputs) { + assert(result_knapsack); + } +} + +} // namespace wallet diff --git a/src/wallet/test/fuzz/notifications.cpp b/src/wallet/test/fuzz/notifications.cpp index 1c16da25bd..5e9cd4001b 100644 --- a/src/wallet/test/fuzz/notifications.cpp +++ b/src/wallet/test/fuzz/notifications.cpp @@ -64,20 +64,18 @@ struct FuzzedWallet { assert(RemoveWallet(context, wallet, load_on_start, warnings)); assert(warnings.empty()); UnloadWallet(std::move(wallet)); - fs::remove_all(GetWalletDir() / name); + fs::remove_all(GetWalletDir() / fs::PathFromString(name)); } 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/wallet/test/init_test_fixture.cpp b/src/wallet/test/init_test_fixture.cpp index be38cebafd..8eb7689c94 100644 --- a/src/wallet/test/init_test_fixture.cpp +++ b/src/wallet/test/init_test_fixture.cpp @@ -15,20 +15,19 @@ namespace wallet { InitWalletDirTestingSetup::InitWalletDirTestingSetup(const std::string& chainName) : BasicTestingSetup(chainName) { - m_wallet_loader = MakeWalletLoader(*m_node.chain, *Assert(m_node.args)); + m_wallet_loader = MakeWalletLoader(*m_node.chain, m_args); - std::string sep; - sep += fs::path::preferred_separator; + const auto sep = fs::path::preferred_separator; - m_datadir = gArgs.GetDataDirNet(); + m_datadir = m_args.GetDataDirNet(); m_cwd = fs::current_path(); m_walletdir_path_cases["default"] = m_datadir / "wallets"; m_walletdir_path_cases["custom"] = m_datadir / "my_wallets"; m_walletdir_path_cases["nonexistent"] = m_datadir / "path_does_not_exist"; m_walletdir_path_cases["file"] = m_datadir / "not_a_directory.dat"; - m_walletdir_path_cases["trailing"] = m_datadir / ("wallets" + sep); - m_walletdir_path_cases["trailing2"] = m_datadir / ("wallets" + sep + sep); + m_walletdir_path_cases["trailing"] = (m_datadir / "wallets") + sep; + m_walletdir_path_cases["trailing2"] = (m_datadir / "wallets") + sep + sep; fs::current_path(m_datadir); m_walletdir_path_cases["relative"] = "wallets"; @@ -42,14 +41,11 @@ InitWalletDirTestingSetup::InitWalletDirTestingSetup(const std::string& chainNam InitWalletDirTestingSetup::~InitWalletDirTestingSetup() { - gArgs.LockSettings([&](util::Settings& settings) { - settings.forced_settings.erase("walletdir"); - }); fs::current_path(m_cwd); } void InitWalletDirTestingSetup::SetWalletDir(const fs::path& walletdir_path) { - gArgs.ForceSetArg("-walletdir", fs::PathToString(walletdir_path)); + m_args.ForceSetArg("-walletdir", fs::PathToString(walletdir_path)); } } // namespace wallet diff --git a/src/wallet/test/init_tests.cpp b/src/wallet/test/init_tests.cpp index 7fdecc5642..fb0a07e181 100644 --- a/src/wallet/test/init_tests.cpp +++ b/src/wallet/test/init_tests.cpp @@ -18,7 +18,7 @@ BOOST_AUTO_TEST_CASE(walletinit_verify_walletdir_default) SetWalletDir(m_walletdir_path_cases["default"]); bool result = m_wallet_loader->verify(); BOOST_CHECK(result == true); - fs::path walletdir = gArgs.GetPathArg("-walletdir"); + fs::path walletdir = m_args.GetPathArg("-walletdir"); fs::path expected_path = fs::canonical(m_walletdir_path_cases["default"]); BOOST_CHECK_EQUAL(walletdir, expected_path); } @@ -28,7 +28,7 @@ BOOST_AUTO_TEST_CASE(walletinit_verify_walletdir_custom) SetWalletDir(m_walletdir_path_cases["custom"]); bool result = m_wallet_loader->verify(); BOOST_CHECK(result == true); - fs::path walletdir = gArgs.GetPathArg("-walletdir"); + fs::path walletdir = m_args.GetPathArg("-walletdir"); fs::path expected_path = fs::canonical(m_walletdir_path_cases["custom"]); BOOST_CHECK_EQUAL(walletdir, expected_path); } @@ -68,7 +68,7 @@ BOOST_AUTO_TEST_CASE(walletinit_verify_walletdir_no_trailing) SetWalletDir(m_walletdir_path_cases["trailing"]); bool result = m_wallet_loader->verify(); BOOST_CHECK(result == true); - fs::path walletdir = gArgs.GetPathArg("-walletdir"); + fs::path walletdir = m_args.GetPathArg("-walletdir"); fs::path expected_path = fs::canonical(m_walletdir_path_cases["default"]); BOOST_CHECK_EQUAL(walletdir, expected_path); } @@ -78,7 +78,7 @@ BOOST_AUTO_TEST_CASE(walletinit_verify_walletdir_no_trailing2) SetWalletDir(m_walletdir_path_cases["trailing2"]); bool result = m_wallet_loader->verify(); BOOST_CHECK(result == true); - fs::path walletdir = gArgs.GetPathArg("-walletdir"); + fs::path walletdir = m_args.GetPathArg("-walletdir"); fs::path expected_path = fs::canonical(m_walletdir_path_cases["default"]); BOOST_CHECK_EQUAL(walletdir, expected_path); } diff --git a/src/wallet/test/psbt_wallet_tests.cpp b/src/wallet/test/psbt_wallet_tests.cpp index b953f402a2..62053ae8d2 100644 --- a/src/wallet/test/psbt_wallet_tests.cpp +++ b/src/wallet/test/psbt_wallet_tests.cpp @@ -68,7 +68,6 @@ BOOST_AUTO_TEST_CASE(psbt_updater_test) // Try to sign the mutated input SignatureData sigdata; BOOST_CHECK(m_wallet.FillPSBT(psbtx, complete, SIGHASH_ALL, true, true) != TransactionError::OK); - //BOOST_CHECK(spk_man->FillPSBT(psbtx, PrecomputePSBTData(psbtx), SIGHASH_ALL, true, true) != TransactionError::OK); } BOOST_AUTO_TEST_CASE(parse_hd_keypath) diff --git a/src/wallet/test/spend_tests.cpp b/src/wallet/test/spend_tests.cpp index 334bd5b8bc..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 @@ -27,21 +27,19 @@ BOOST_FIXTURE_TEST_CASE(SubtractFee, TestChain100Setup) // instead of the miner. auto check_tx = [&wallet](CAmount leftover_input_amount) { CRecipient recipient{GetScriptForRawPubKey({}), 50 * COIN - leftover_input_amount, true /* subtract fee */}; - CTransactionRef tx; - CAmount fee; - int change_pos = -1; - bilingual_str error; + constexpr int RANDOM_CHANGE_POSITION = -1; 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; - BOOST_CHECK(CreateTransaction(*wallet, {recipient}, tx, fee, change_pos, error, coin_control, fee_calc)); - BOOST_CHECK_EQUAL(tx->vout.size(), 1); - BOOST_CHECK_EQUAL(tx->vout[0].nValue, recipient.nAmount + leftover_input_amount - fee); - BOOST_CHECK_GT(fee, 0); - return 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_crypto_tests.cpp b/src/wallet/test/wallet_crypto_tests.cpp index 166e27bab9..327c28412a 100644 --- a/src/wallet/test/wallet_crypto_tests.cpp +++ b/src/wallet/test/wallet_crypto_tests.cpp @@ -81,7 +81,7 @@ BOOST_AUTO_TEST_CASE(passphrase) { std::string hash(GetRandHash().ToString()); std::vector<unsigned char> vchSalt(8); - GetRandBytes(vchSalt.data(), vchSalt.size()); + GetRandBytes(vchSalt); uint32_t rounds = InsecureRand32(); if (rounds > 30000) rounds = 30000; diff --git a/src/wallet/test/wallet_test_fixture.cpp b/src/wallet/test/wallet_test_fixture.cpp index cb006dea3a..38d23b6f91 100644 --- a/src/wallet/test/wallet_test_fixture.cpp +++ b/src/wallet/test/wallet_test_fixture.cpp @@ -9,6 +9,7 @@ namespace wallet { WalletTestingSetup::WalletTestingSetup(const std::string& chainName) : TestingSetup(chainName), + m_wallet_loader{interfaces::MakeWalletLoader(*m_node.chain, *Assert(m_node.args))}, m_wallet(m_node.chain.get(), "", m_args, CreateMockWalletDatabase()) { m_wallet.LoadWallet(); diff --git a/src/wallet/test/wallet_test_fixture.h b/src/wallet/test/wallet_test_fixture.h index d4b855b145..e0b38a40e7 100644 --- a/src/wallet/test/wallet_test_fixture.h +++ b/src/wallet/test/wallet_test_fixture.h @@ -22,7 +22,7 @@ struct WalletTestingSetup : public TestingSetup { explicit WalletTestingSetup(const std::string& chainName = CBaseChainParams::MAIN); ~WalletTestingSetup(); - std::unique_ptr<interfaces::WalletLoader> m_wallet_loader = interfaces::MakeWalletLoader(*m_node.chain, *Assert(m_node.args)); + std::unique_ptr<interfaces::WalletLoader> m_wallet_loader; CWallet m_wallet; std::unique_ptr<interfaces::Handler> m_chain_notifications_handler; }; diff --git a/src/wallet/test/wallet_tests.cpp b/src/wallet/test/wallet_tests.cpp index 7693c9c0e8..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> @@ -54,9 +52,7 @@ static const std::shared_ptr<CWallet> TestLoadWallet(WalletContext& context) std::vector<bilingual_str> warnings; auto database = MakeWalletDatabase("", options, status, error); auto wallet = CWallet::Create(context, "", std::move(database), options.create_flags, error, warnings); - if (context.chain) { - wallet->postInitProcess(); - } + NotifyWalletLoaded(context, wallet); return wallet; } @@ -95,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()); @@ -122,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. @@ -154,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()); @@ -181,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()); @@ -199,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; @@ -221,7 +236,7 @@ BOOST_FIXTURE_TEST_CASE(importmulti_rescan, TestChain100Setup) wallet->SetupLegacyScriptPubKeyMan(); WITH_LOCK(wallet->cs_wallet, wallet->SetLastBlockProcessed(newTip->nHeight, newTip->GetBlockHash())); WalletContext context; - context.args = &gArgs; + context.args = &m_args; AddWallet(context, wallet); UniValue keys; keys.setArray(); @@ -266,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]); @@ -277,12 +292,12 @@ BOOST_FIXTURE_TEST_CASE(importwallet_rescan, TestChain100Setup) SetMockTime(KEY_TIME); m_coinbase_txns.emplace_back(CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey())).vtx[0]); - std::string backup_file = fs::PathToString(gArgs.GetDataDirNet() / "wallet.backup"); + std::string backup_file = fs::PathToString(m_args.GetDataDirNet() / "wallet.backup"); // Import key into wallet and call dumpwallet to create backup file. { WalletContext context; - context.args = &gArgs; + context.args = &m_args; const std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(m_node.chain.get(), "", m_args, CreateDummyWalletDatabase()); { auto spk_man = wallet->GetOrCreateLegacyScriptPubKeyMan(); @@ -291,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; @@ -310,12 +326,13 @@ BOOST_FIXTURE_TEST_CASE(importwallet_rescan, TestChain100Setup) wallet->SetupLegacyScriptPubKeyMan(); WalletContext context; - context.args = &gArgs; + context.args = &m_args; JSONRPCRequest request; request.context = &context; 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); @@ -339,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(), /*position_in_block=*/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(); @@ -349,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) @@ -367,13 +385,13 @@ static int64_t AddTx(ChainstateManager& chainman, CWallet& wallet, uint32_t lock CBlockIndex* block = nullptr; if (blockTime > 0) { LOCK(cs_main); - auto inserted = chainman.BlockIndex().emplace(GetRandHash(), new CBlockIndex); + auto inserted = chainman.BlockIndex().emplace(std::piecewise_construct, std::make_tuple(GetRandHash()), std::make_tuple()); assert(inserted.second); const uint256& hash = inserted.first->first; - block = inserted.first->second; + block = &inserted.first->second; block->nTime = blockTime; block->phashBlock = &hash; - state = TxStateConfirmed{hash, block->nHeight, /*position_in_block=*/0}; + state = TxStateConfirmed{hash, block->nHeight, /*index=*/0}; } return wallet.AddToWallet(MakeTransactionRef(tx), state, [&](CWalletTx& wtx, bool /* new_tx */) { // Assign wtx.m_state to simplify test and avoid the need to simulate @@ -509,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() @@ -520,13 +538,12 @@ public: CWalletTx& AddTx(CRecipient recipient) { CTransactionRef tx; - CAmount fee; - int changePos = -1; - bilingual_str error; CCoinControl dummy; - FeeCalculation fee_calc_out; { - BOOST_CHECK(CreateTransaction(*wallet, {recipient}, tx, fee, changePos, error, dummy, fee_calc_out)); + 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; @@ -537,10 +554,11 @@ 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()); - it->second.m_state = TxStateConfirmed{m_node.chainman->ActiveChain().Tip()->GetBlockHash(), m_node.chainman->ActiveChain().Height(), /*position_in_block=*/1}; + it->second.m_state = TxStateConfirmed{m_node.chainman->ActiveChain().Tip()->GetBlockHash(), m_node.chainman->ActiveChain().Height(), /*index=*/1}; return it->second; } @@ -581,21 +599,17 @@ 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; - AvailableCoins(*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) { LOCK(wallet->cs_wallet); - wallet->LockCoin(COutPoint(coin.tx->GetHash(), coin.i)); + wallet->LockCoin(coin.outpoint); } } { LOCK(wallet->cs_wallet); - std::vector<COutput> available; - AvailableCoins(*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. @@ -616,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()); @@ -626,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, "")); } } @@ -716,10 +726,10 @@ BOOST_FIXTURE_TEST_CASE(wallet_descriptor_test, BasicTestingSetup) //! rescanning where new transactions in new blocks could be lost. BOOST_FIXTURE_TEST_CASE(CreateWallet, TestChain100Setup) { - gArgs.ForceSetArg("-unsafesqlitesync", "1"); + m_args.ForceSetArg("-unsafesqlitesync", "1"); // Create new wallet with known key and unload it. WalletContext context; - context.args = &gArgs; + context.args = &m_args; context.chain = m_node.chain.get(); auto wallet = TestLoadWallet(context); CKey key; @@ -763,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); @@ -775,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); @@ -798,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); @@ -812,7 +825,7 @@ BOOST_FIXTURE_TEST_CASE(CreateWallet, TestChain100Setup) BOOST_FIXTURE_TEST_CASE(CreateWalletWithoutChain, BasicTestingSetup) { WalletContext context; - context.args = &gArgs; + context.args = &m_args; auto wallet = TestLoadWallet(context); BOOST_CHECK(wallet); UnloadWallet(std::move(wallet)); @@ -820,9 +833,9 @@ BOOST_FIXTURE_TEST_CASE(CreateWalletWithoutChain, BasicTestingSetup) BOOST_FIXTURE_TEST_CASE(ZapSelectTx, TestChain100Setup) { - gArgs.ForceSetArg("-unsafesqlitesync", "1"); + m_args.ForceSetArg("-unsafesqlitesync", "1"); WalletContext context; - context.args = &gArgs; + context.args = &m_args; context.chain = m_node.chain.get(); auto wallet = TestLoadWallet(context); CKey key; @@ -838,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/transaction.h b/src/wallet/transaction.h index 00f9c9f154..271d698e56 100644 --- a/src/wallet/transaction.h +++ b/src/wallet/transaction.h @@ -296,6 +296,7 @@ public: bool isUnconfirmed() const { return !isAbandoned() && !isConflicted() && !isConfirmed(); } bool isConfirmed() const { return state<TxStateConfirmed>(); } const uint256& GetHash() const { return tx->GetHash(); } + const uint256& GetWitnessHash() const { return tx->GetWitnessHash(); } bool IsCoinBase() const { return tx->IsCoinBase(); } // Disable copying of CWalletTx objects to prevent bugs where instances get diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index ed8aff54cb..3d989d90f8 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -44,8 +44,6 @@ #include <assert.h> #include <optional> -#include <boost/algorithm/string/replace.hpp> - using interfaces::FoundBlock; namespace wallet { @@ -151,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); @@ -167,8 +172,16 @@ std::unique_ptr<interfaces::Handler> HandleLoadWallet(WalletContext& context, Lo return interfaces::MakeHandler([&context, it] { LOCK(context.wallets_mutex); context.wallet_load_fns.erase(it); }); } -static Mutex g_loading_wallet_mutex; -static Mutex g_wallet_release_mutex; +void NotifyWalletLoaded(WalletContext& context, const std::shared_ptr<CWallet>& wallet) +{ + LOCK(context.wallets_mutex); + for (auto& load_wallet : context.wallet_load_fns) { + load_wallet(interfaces::MakeWallet(context, wallet)); + } +} + +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); @@ -232,6 +245,8 @@ std::shared_ptr<CWallet> LoadWalletInternal(WalletContext& context, const std::s status = DatabaseStatus::FAILED_LOAD; return nullptr; } + + NotifyWalletLoaded(context, wallet); AddWallet(context, wallet); wallet->postInitProcess(); @@ -289,6 +304,13 @@ std::shared_ptr<CWallet> CreateWallet(WalletContext& context, const std::string& return nullptr; } + // Do not allow a passphrase when private keys are disabled + if (!passphrase.empty() && (wallet_creation_flags & WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { + error = Untranslated("Passphrase provided but private keys are disabled. A passphrase is only used to encrypt private keys, so cannot be used for wallets with private keys disabled."); + status = DatabaseStatus::FAILED_CREATE; + return nullptr; + } + // Wallet::Verify will check if we're trying to create a wallet with a duplicate name. std::unique_ptr<WalletDatabase> database = MakeWalletDatabase(name, options, status, error); if (!database) { @@ -297,13 +319,6 @@ std::shared_ptr<CWallet> CreateWallet(WalletContext& context, const std::string& return nullptr; } - // Do not allow a passphrase when private keys are disabled - if (!passphrase.empty() && (wallet_creation_flags & WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { - error = Untranslated("Passphrase provided but private keys are disabled. A passphrase is only used to encrypt private keys, so cannot be used for wallets with private keys disabled."); - status = DatabaseStatus::FAILED_CREATE; - return nullptr; - } - // Make the wallet context.chain->initMessage(_("Loading wallet…").translated); const std::shared_ptr<CWallet> wallet = CWallet::Create(context, name, std::move(database), wallet_creation_flags, error, warnings); @@ -348,12 +363,19 @@ std::shared_ptr<CWallet> CreateWallet(WalletContext& context, const std::string& wallet->Lock(); } } + + NotifyWalletLoaded(context, wallet); AddWallet(context, wallet); wallet->postInitProcess(); // Write the wallet settings UpdateWalletSetting(*context.chain, name, load_on_start, warnings); + // Legacy wallets are being deprecated, warn if a newly created wallet is legacy + if (!(wallet_creation_flags & WALLET_FLAG_DESCRIPTORS)) { + warnings.push_back(_("Wallet created successfully. The legacy wallet type is being deprecated and support for creating and opening legacy wallets will be removed in the future.")); + } + status = DatabaseStatus::SUCCESS; return wallet; } @@ -361,6 +383,7 @@ std::shared_ptr<CWallet> CreateWallet(WalletContext& context, const std::string& std::shared_ptr<CWallet> RestoreWallet(WalletContext& context, const fs::path& backup_file, const std::string& wallet_name, std::optional<bool> load_on_start, DatabaseStatus& status, bilingual_str& error, std::vector<bilingual_str>& warnings) { DatabaseOptions options; + ReadDatabaseArgs(*context.args, options); options.require_existing = true; if (!fs::exists(backup_file)) { @@ -398,7 +421,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); @@ -505,6 +528,11 @@ bool CWallet::ChangeWalletPassphrase(const SecureString& strOldWalletPassphrase, void CWallet::chainStateFlushed(const CBlockLocator& loc) { + // Don't update the best block until the chain is attached so that in case of a shutdown, + // the rescan will be restarted at next startup. + if (m_attaching_chain) { + return; + } WalletBatch batch(GetDatabase()); batch.WriteBestBlock(loc); } @@ -530,7 +558,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; @@ -548,11 +576,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() @@ -608,16 +642,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())) @@ -644,16 +676,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) @@ -664,12 +693,12 @@ bool CWallet::EncryptWallet(const SecureString& strWalletPassphrase) CKeyingMaterial _vMasterKey; _vMasterKey.resize(WALLET_CRYPTO_KEY_SIZE); - GetStrongRandBytes(_vMasterKey.data(), WALLET_CRYPTO_KEY_SIZE); + GetStrongRandBytes(_vMasterKey); CMasterKey kMasterKey; kMasterKey.vchSalt.resize(WALLET_CRYPTO_SALT_SIZE); - GetStrongRandBytes(kMasterKey.vchSalt.data(), WALLET_CRYPTO_SALT_SIZE); + GetStrongRandBytes(kMasterKey.vchSalt); CCrypter crypter; int64_t nStartTime = GetTimeMillis(); @@ -887,35 +916,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; } } } @@ -952,7 +977,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) @@ -995,14 +1020,14 @@ CWalletTx* CWallet::AddToWallet(CTransactionRef tx, const TxState& state, const if (!strCmd.empty()) { - boost::replace_all(strCmd, "%s", hash.GetHex()); + ReplaceAll(strCmd, "%s", hash.GetHex()); if (auto* conf = wtx.state<TxStateConfirmed>()) { - boost::replace_all(strCmd, "%b", conf->confirmed_block_hash.GetHex()); - boost::replace_all(strCmd, "%h", ToString(conf->confirmed_block_height)); + ReplaceAll(strCmd, "%b", conf->confirmed_block_hash.GetHex()); + ReplaceAll(strCmd, "%h", ToString(conf->confirmed_block_height)); } else { - boost::replace_all(strCmd, "%b", "unconfirmed"); - boost::replace_all(strCmd, "%h", "-1"); + ReplaceAll(strCmd, "%b", "unconfirmed"); + ReplaceAll(strCmd, "%h", "-1"); } #ifndef WIN32 // Substituting the wallet name isn't currently supported on windows @@ -1010,7 +1035,7 @@ CWalletTx* CWallet::AddToWallet(CTransactionRef tx, const TxState& state, const // https://github.com/bitcoin/bitcoin/pull/13339#issuecomment-537384875 // A few ways it could be implemented in the future are described in: // https://github.com/bitcoin/bitcoin/pull/13339#issuecomment-461288094 - boost::replace_all(strCmd, "%w", ShellEscape(GetName())); + ReplaceAll(strCmd, "%w", ShellEscape(GetName())); #endif std::thread t(runCommand, strCmd); t.detach(); // thread runs free @@ -1051,7 +1076,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()) { @@ -1118,7 +1143,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; @@ -1179,12 +1210,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 @@ -1230,12 +1262,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 @@ -1302,30 +1335,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{}); } } @@ -1351,7 +1385,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; @@ -1394,6 +1428,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); @@ -1496,13 +1543,16 @@ void CWallet::InitWalletFlags(uint64_t flags) } // 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; } @@ -1569,12 +1619,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; } } @@ -1657,7 +1704,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))); @@ -1670,7 +1717,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. @@ -1688,10 +1736,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) { - int64_t nNow = GetTime(); - int64_t start_time = GetTimeMillis(); + constexpr auto INTERVAL_TIME{60s}; + auto current_time{reserver.now()}; + auto start_time{reserver.now()}; assert(reserver.isReserved()); @@ -1718,8 +1767,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 (GetTime() >= nNow + 60) { - nNow = GetTime(); + + 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); } @@ -1749,6 +1800,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; @@ -1778,6 +1839,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); @@ -1786,7 +1851,7 @@ 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 { - WalletLogPrintf("Rescan completed in %15dms\n", GetTimeMillis() - start_time); + WalletLogPrintf("Rescan completed in %15dms\n", Ticks<std::chrono::milliseconds>(reserver.now() - start_time)); } return result; } @@ -1821,6 +1886,8 @@ void CWallet::ReacceptWalletTransactions() bool CWallet::SubmitTxMemoryPoolAndRelay(CWalletTx& wtx, std::string& err_string, bool relay) const { + AssertLockHeld(cs_wallet); + // Can't relay if wallet is not broadcasting if (!GetBroadcastTransactions()) return false; // Don't relay abandoned transactions @@ -1849,12 +1916,11 @@ bool CWallet::SubmitTxMemoryPoolAndRelay(CWalletTx& wtx, std::string& err_string std::set<uint256> CWallet::GetTxConflicts(const CWalletTx& wtx) const { - std::set<uint256> result; - { - uint256 myHash = wtx.GetHash(); - result = GetConflicts(myHash); - result.erase(myHash); - } + AssertLockHeld(cs_wallet); + + const uint256 myHash{wtx.GetHash()}; + std::set<uint256> result{GetConflicts(myHash)}; + result.erase(myHash); return result; } @@ -1925,7 +1991,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; } @@ -1957,7 +2023,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) { @@ -1981,6 +2046,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; @@ -1994,6 +2061,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) { @@ -2085,7 +2181,7 @@ void CWallet::CommitTransaction(CTransactionRef tx, mapValue_t mapValue, std::ve // Add tx to wallet, because if it has change it's also ours, // otherwise just for transaction history. - AddToWallet(tx, TxStateInactive{}, [&](CWalletTx& wtx, bool new_tx) { + CWalletTx* wtx = AddToWallet(tx, TxStateInactive{}, [&](CWalletTx& wtx, bool new_tx) { CHECK_NONFATAL(wtx.mapValue.empty()); CHECK_NONFATAL(wtx.vOrderForm.empty()); wtx.mapValue = std::move(mapValue); @@ -2095,6 +2191,11 @@ void CWallet::CommitTransaction(CTransactionRef tx, mapValue_t mapValue, std::ve return true; }); + // wtx can only be null if the db write failed. + if (!wtx) { + throw std::runtime_error(std::string(__func__) + ": Wallet db error, transaction commit failed"); + } + // Notify that old coins are spent for (const CTxIn& txin : tx->vin) { CWalletTx &coin = mapWallet.at(txin.prevout.hash); @@ -2102,17 +2203,13 @@ void CWallet::CommitTransaction(CTransactionRef tx, mapValue_t mapValue, std::ve NotifyTransactionChanged(coin.GetHash(), CT_UPDATED); } - // Get the inserted-CWalletTx from mapWallet so that the - // wtx cached mempool state is updated correctly - CWalletTx& wtx = mapWallet.at(tx->GetHash()); - if (!fBroadcastTransactions) { // Don't submit tx to the mempool return; } std::string err_string; - if (!SubmitTxMemoryPoolAndRelay(wtx, err_string, true)) { + if (!SubmitTxMemoryPoolAndRelay(*wtx, err_string, true)) { WalletLogPrintf("CommitTransaction(): Transaction cannot be broadcast immediately, %s\n", err_string); // TODO: if we expect the failure to be long term or permanent, instead delete wtx from the wallet and return failure. } @@ -2265,37 +2362,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 @@ -2326,42 +2418,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() @@ -2428,12 +2541,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 @@ -2754,7 +2865,7 @@ std::shared_ptr<CWallet> CWallet::Create(WalletContext& context, const std::stri } else if (wallet_creation_flags & WALLET_FLAG_DISABLE_PRIVATE_KEYS) { // Make it impossible to disable private keys after creation error = strprintf(_("Error loading %s: Private keys can only be disabled during creation"), walletFile); - return NULL; + return nullptr; } else if (walletInstance->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { for (auto spk_man : walletInstance->GetActiveScriptPubKeyMans()) { if (spk_man->HavePrivateKeys()) { @@ -2903,13 +3014,6 @@ std::shared_ptr<CWallet> CWallet::Create(WalletContext& context, const std::stri } { - LOCK(context.wallets_mutex); - for (auto& load_wallet : context.wallet_load_fns) { - load_wallet(interfaces::MakeWallet(context, walletInstance)); - } - } - - { LOCK(walletInstance->cs_wallet); walletInstance->SetBroadcastTransactions(args.GetBoolArg("-walletbroadcast", DEFAULT_WALLETBROADCAST)); walletInstance->WalletLogPrintf("setKeyPool.size() = %u\n", walletInstance->GetKeyPoolSize()); @@ -2927,14 +3031,31 @@ bool CWallet::AttachChain(const std::shared_ptr<CWallet>& walletInstance, interf assert(!walletInstance->m_chain || walletInstance->m_chain == &chain); walletInstance->m_chain = &chain; + // Unless allowed, ensure wallet files are not reused across chains: + if (!gArgs.GetBoolArg("-walletcrosschain", DEFAULT_WALLETCROSSCHAIN)) { + WalletBatch batch(walletInstance->GetDatabase()); + CBlockLocator locator; + if (batch.ReadBestBlock(locator) && locator.vHave.size() > 0 && chain.getHeight()) { + // Wallet is assumed to be from another chain, if genesis block in the active + // chain differs from the genesis block known to the wallet. + if (chain.getBlockHash(0) != locator.vHave.back()) { + error = Untranslated("Wallet files should not be reused across chains. Restart bitcoind with -walletcrosschain to override."); + return false; + } + } + } + // Register wallet with validationinterface. It's done before rescan to avoid // missing block connections between end of rescan and validation subscribing. // Because of wallet lock being hold, block connection notifications are going to // be pending on the validation-side until lock release. It's likely to have // block processing duplicata (if rescan block range overlaps with notification one) // but we guarantee at least than wallet state is correct after notifications delivery. + // However, chainStateFlushed notifications are ignored until the rescan is finished + // so that in case of a shutdown event, the rescan will be repeated at the next start. // This is temporary until rescan and notifications delivery are unified under same // interface. + walletInstance->m_attaching_chain = true; //ignores chainStateFlushed notifications walletInstance->m_chain_notifications_handler = walletInstance->chain().handleNotifications(walletInstance); // If rescan_required = true, rescan_height remains equal to 0 @@ -2961,20 +3082,31 @@ 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.hasAssumedValidChain() ? + _( + "Assumed-valid: last wallet synchronisation goes beyond " + "available block data. You need to wait for the background " + "validation chain to download more blocks.") : + _("Prune: last wallet synchronisation goes beyond pruned data. You need to -reindex (download the whole blockchain again in case of pruned node)"); return false; } } @@ -2995,14 +3127,16 @@ 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; } } + walletInstance->m_attaching_chain = false; walletInstance->chainStateFlushed(chain.getTipLocator()); walletInstance->GetDatabase().IncrementUpdateCounter(); } + walletInstance->m_attaching_chain = false; return true; } @@ -3096,8 +3230,11 @@ int CWallet::GetTxDepthInMainChain(const CWalletTx& wtx) const int CWallet::GetTxBlocksToMaturity(const CWalletTx& wtx) const { - if (!wtx.IsCoinBase()) + AssertLockHeld(cs_wallet); + + if (!wtx.IsCoinBase()) { return 0; + } int chain_depth = GetTxDepthInMainChain(wtx); assert(chain_depth >= 0); // coinbase tx should not be conflicted return std::max(0, (COINBASE_MATURITY+1) - chain_depth); @@ -3105,6 +3242,8 @@ int CWallet::GetTxBlocksToMaturity(const CWalletTx& wtx) const bool CWallet::IsTxImmatureCoinBase(const CWalletTx& wtx) const { + AssertLockHeld(cs_wallet); + // note GetBlocksToMaturity is 0 for non-coinbase tx return GetTxBlocksToMaturity(wtx) > 0; } @@ -3221,6 +3360,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)) { @@ -3326,7 +3477,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); @@ -3362,7 +3513,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(); @@ -3380,7 +3531,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"); diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index e2b985d9d8..934811dee1 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -14,13 +14,14 @@ #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> #include <util/ui_change_type.h> #include <validationinterface.h> -#include <wallet/coinselection.h> #include <wallet/crypter.h> #include <wallet/scriptpubkeyman.h> #include <wallet/transaction.h> @@ -37,6 +38,7 @@ #include <stdint.h> #include <string> #include <utility> +#include <unordered_map> #include <vector> #include <boost/signals2/signal.hpp> @@ -46,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 { @@ -63,11 +64,13 @@ 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); std::shared_ptr<CWallet> RestoreWallet(WalletContext& context, const fs::path& backup_file, const std::string& wallet_name, std::optional<bool> load_on_start, DatabaseStatus& status, bilingual_str& error, std::vector<bilingual_str>& warnings); std::unique_ptr<interfaces::Handler> HandleLoadWallet(WalletContext& context, LoadWalletFn load_wallet); +void NotifyWalletLoaded(WalletContext& context, const std::shared_ptr<CWallet>& wallet); std::unique_ptr<WalletDatabase> MakeWalletDatabase(const std::string& name, const DatabaseOptions& options, DatabaseStatus& status, bilingual_str& error); //! -paytxfee default @@ -95,13 +98,14 @@ static const CAmount WALLET_INCREMENTAL_RELAY_FEE = 5000; //! Default for -spendzeroconfchange static const bool DEFAULT_SPEND_ZEROCONF_CHANGE = true; //! Default for -walletrejectlongchains -static const bool DEFAULT_WALLET_REJECT_LONG_CHAINS = false; +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; //! -maxtxfee default constexpr CAmount DEFAULT_TRANSACTION_MAXFEE{COIN / 10}; //! Discourage users to set fees higher than this amount (in satoshis) per kB @@ -112,7 +116,6 @@ constexpr CAmount HIGH_MAX_TX_FEE{100 * HIGH_TX_FEE_PER_KB}; static constexpr size_t DUMMY_NESTED_P2WPKH_INPUT_SIZE = 91; class CCoinControl; -class COutput; class CWalletTx; class ReserveDestination; @@ -189,7 +192,7 @@ 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 @@ -238,6 +241,7 @@ private: std::atomic<bool> fAbortRescan{false}; std::atomic<bool> fScanningWallet{false}; // controlled by WalletRescanReserver + std::atomic<bool> m_attaching_chain{false}; std::atomic<int64_t> m_scanning_start{0}; std::atomic<double> m_scanning_progress{0}; friend class WalletRescanReserver; @@ -258,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 @@ -389,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; @@ -414,13 +418,7 @@ public: const CWalletTx* GetWalletTx(const uint256& hash) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); - // TODO: Remove "NO_THREAD_SAFETY_ANALYSIS" and replace it with the correct - // annotation "EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet)". The annotation - // "NO_THREAD_SAFETY_ANALYSIS" was temporarily added to avoid having to - // resolve the issue of member access into incomplete type CWallet. Note - // that we still have the runtime check "AssertLockHeld(pwallet->cs_wallet)" - // in place. - std::set<uint256> GetTxConflicts(const CWalletTx& wtx) const NO_THREAD_SAFETY_ANALYSIS; + std::set<uint256> GetTxConflicts(const CWalletTx& wtx) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); /** * Return depth of transaction in blockchain: @@ -428,36 +426,34 @@ public: * 0 : in memory pool, waiting to be included in a block * >=1 : this many blocks deep in the main chain */ - // TODO: Remove "NO_THREAD_SAFETY_ANALYSIS" and replace it with the correct - // annotation "EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet)". The annotation - // "NO_THREAD_SAFETY_ANALYSIS" was temporarily added to avoid having to - // resolve the issue of member access into incomplete type CWallet. Note - // that we still have the runtime check "AssertLockHeld(pwallet->cs_wallet)" - // in place. - int GetTxDepthInMainChain(const CWalletTx& wtx) const NO_THREAD_SAFETY_ANALYSIS; - bool IsTxInMainChain(const CWalletTx& wtx) const { return GetTxDepthInMainChain(wtx) > 0; } + int GetTxDepthInMainChain(const CWalletTx& wtx) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + bool IsTxInMainChain(const CWalletTx& wtx) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet) + { + AssertLockHeld(cs_wallet); + return GetTxDepthInMainChain(wtx) > 0; + } /** * @return number of blocks to maturity for this transaction: * 0 : is not a coinbase transaction, or is a mature coinbase transaction * >0 : is a coinbase transaction which matures in this many blocks */ - int GetTxBlocksToMaturity(const CWalletTx& wtx) const; - bool IsTxImmatureCoinBase(const CWalletTx& wtx) const; + int GetTxBlocksToMaturity(const CWalletTx& wtx) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + bool IsTxImmatureCoinBase(const CWalletTx& wtx) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); //! 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); @@ -512,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); @@ -535,7 +535,7 @@ 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(); @@ -583,7 +583,8 @@ public: void CommitTransaction(CTransactionRef tx, mapValue_t mapValue, std::vector<std::pair<std::string, std::string>> orderForm); /** Pass this transaction to node for mempool insertion and relay to peers if flag set to true */ - bool SubmitTxMemoryPoolAndRelay(CWalletTx& wtx, std::string& err_string, bool relay) const; + bool SubmitTxMemoryPoolAndRelay(CWalletTx& wtx, std::string& err_string, bool relay) const + EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); bool DummySignTx(CMutableTransaction &txNew, const std::set<CTxOut> &txouts, const CCoinControl* coin_control = nullptr) const { @@ -641,7 +642,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 @@ -649,8 +673,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); @@ -661,6 +685,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; @@ -691,7 +716,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(); @@ -823,6 +848,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(); @@ -903,8 +931,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) {} @@ -925,6 +956,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) { @@ -939,7 +974,7 @@ 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); } // namespace wallet diff --git a/src/wallet/walletdb.cpp b/src/wallet/walletdb.cpp index 6e100524a4..0d85652c0c 100644 --- a/src/wallet/walletdb.cpp +++ b/src/wallet/walletdb.cpp @@ -314,8 +314,7 @@ public: std::map<uint160, CHDChain> m_hd_chains; bool tx_corrupt{false}; - CWalletScanState() { - } + CWalletScanState() = default; }; static bool @@ -850,25 +849,25 @@ DBErrors WalletBatch::LoadWallet(CWallet* pwallet) // Set the active ScriptPubKeyMans for (auto spk_man_pair : wss.m_active_external_spks) { - pwallet->LoadActiveScriptPubKeyMan(spk_man_pair.second, spk_man_pair.first, /* internal */ false); + pwallet->LoadActiveScriptPubKeyMan(spk_man_pair.second, spk_man_pair.first, /*internal=*/false); } for (auto spk_man_pair : wss.m_active_internal_spks) { - pwallet->LoadActiveScriptPubKeyMan(spk_man_pair.second, spk_man_pair.first, /* internal */ true); + pwallet->LoadActiveScriptPubKeyMan(spk_man_pair.second, spk_man_pair.first, /*internal=*/true); } // 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); } @@ -884,12 +883,10 @@ 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 + // Last client version to open this wallet 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); + 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); 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); @@ -910,7 +907,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) @@ -1187,12 +1184,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) { + + std::optional<DatabaseFormat> format; + if (options.require_format) format = options.require_format; + if (!format) { +#ifdef USE_BDB + format = DatabaseFormat::BERKELEY; +#endif #ifdef USE_SQLITE - return std::make_unique<SQLiteDatabase>("", "", true); -#elif USE_BDB - return std::make_unique<BerkeleyDatabase>(std::make_shared<BerkeleyEnvironment>(), ""); + format = DatabaseFormat::SQLITE; #endif + } + + if (format == DatabaseFormat::SQLITE) { +#ifdef USE_SQLITE + 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..a04ea598b6 100644 --- a/src/wallet/walletdb.h +++ b/src/wallet/walletdb.h @@ -301,6 +301,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 65c42ec161..e991bc0814 100644 --- a/src/wallet/wallettool.cpp +++ b/src/wallet/wallettool.cpp @@ -140,6 +140,7 @@ bool ExecuteWalletToolFunc(const ArgsManager& args, const std::string& command) if (command == "create") { DatabaseOptions options; + ReadDatabaseArgs(args, options); options.require_create = true; // If -legacy is set, use it. Otherwise default to false. bool make_legacy = args.GetBoolArg("-legacy", false); @@ -165,6 +166,7 @@ bool ExecuteWalletToolFunc(const ArgsManager& args, const std::string& command) } } else if (command == "info") { DatabaseOptions options; + ReadDatabaseArgs(args, options); options.require_existing = true; const std::shared_ptr<CWallet> wallet_instance = MakeWallet(name, path, args, options); if (!wallet_instance) return false; @@ -174,7 +176,7 @@ bool ExecuteWalletToolFunc(const ArgsManager& args, const std::string& command) #ifdef USE_BDB bilingual_str error; std::vector<bilingual_str> warnings; - bool ret = RecoverDatabaseFile(path, error, warnings); + bool ret = RecoverDatabaseFile(args, path, error, warnings); if (!ret) { for (const auto& warning : warnings) { tfm::format(std::cerr, "%s\n", warning.original); @@ -190,11 +192,12 @@ bool ExecuteWalletToolFunc(const ArgsManager& args, const std::string& command) #endif } else if (command == "dump") { DatabaseOptions options; + ReadDatabaseArgs(args, options); options.require_existing = true; const std::shared_ptr<CWallet> wallet_instance = MakeWallet(name, path, args, options); if (!wallet_instance) return false; bilingual_str error; - bool ret = DumpWallet(*wallet_instance, error); + bool ret = DumpWallet(args, *wallet_instance, error); if (!ret && !error.empty()) { tfm::format(std::cerr, "%s\n", error.original); return ret; @@ -204,7 +207,7 @@ bool ExecuteWalletToolFunc(const ArgsManager& args, const std::string& command) } else if (command == "createfromdump") { bilingual_str error; std::vector<bilingual_str> warnings; - bool ret = CreateFromDump(name, path, error, warnings); + bool ret = CreateFromDump(args, name, path, error, warnings); for (const auto& warning : warnings) { tfm::format(std::cout, "%s\n", warning.original); } 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 a53de34db4..26618735f6 100644 --- a/src/zmq/zmqnotificationinterface.cpp +++ b/src/zmq/zmqnotificationinterface.cpp @@ -70,9 +70,9 @@ bool CZMQNotificationInterface::Initialize() { int major = 0, minor = 0, patch = 0; zmq_version(&major, &minor, &patch); - LogPrint(BCLog::ZMQ, "zmq: version %d.%d.%d\n", major, minor, patch); + LogPrint(BCLog::ZMQ, "version %d.%d.%d\n", major, minor, patch); - LogPrint(BCLog::ZMQ, "zmq: Initialize notification interface\n"); + LogPrint(BCLog::ZMQ, "Initialize notification interface\n"); assert(!pcontext); pcontext = zmq_ctx_new(); @@ -85,9 +85,9 @@ bool CZMQNotificationInterface::Initialize() for (auto& notifier : notifiers) { if (notifier->Initialize(pcontext)) { - LogPrint(BCLog::ZMQ, "zmq: Notifier %s ready (address = %s)\n", notifier->GetType(), notifier->GetAddress()); + LogPrint(BCLog::ZMQ, "Notifier %s ready (address = %s)\n", notifier->GetType(), notifier->GetAddress()); } else { - LogPrint(BCLog::ZMQ, "zmq: Notifier %s failed (address = %s)\n", notifier->GetType(), notifier->GetAddress()); + LogPrint(BCLog::ZMQ, "Notifier %s failed (address = %s)\n", notifier->GetType(), notifier->GetAddress()); return false; } } @@ -98,11 +98,11 @@ bool CZMQNotificationInterface::Initialize() // Called during shutdown sequence void CZMQNotificationInterface::Shutdown() { - LogPrint(BCLog::ZMQ, "zmq: Shutdown notification interface\n"); + LogPrint(BCLog::ZMQ, "Shutdown notification interface\n"); if (pcontext) { for (auto& notifier : notifiers) { - LogPrint(BCLog::ZMQ, "zmq: Shutdown notifier %s at %s\n", notifier->GetType(), notifier->GetAddress()); + LogPrint(BCLog::ZMQ, "Shutdown notifier %s at %s\n", notifier->GetType(), notifier->GetAddress()); notifier->Shutdown(); } zmq_ctx_term(pcontext); diff --git a/src/zmq/zmqpublishnotifier.cpp b/src/zmq/zmqpublishnotifier.cpp index 2c6f24a239..011336bf72 100644 --- a/src/zmq/zmqpublishnotifier.cpp +++ b/src/zmq/zmqpublishnotifier.cpp @@ -106,7 +106,7 @@ bool CZMQAbstractPublishNotifier::Initialize(void *pcontext) return false; } - LogPrint(BCLog::ZMQ, "zmq: Outbound message high water mark for %s at %s is %d\n", type, address, outbound_message_high_water_mark); + LogPrint(BCLog::ZMQ, "Outbound message high water mark for %s at %s is %d\n", type, address, outbound_message_high_water_mark); int rc = zmq_setsockopt(psocket, ZMQ_SNDHWM, &outbound_message_high_water_mark, sizeof(outbound_message_high_water_mark)); if (rc != 0) @@ -147,8 +147,8 @@ bool CZMQAbstractPublishNotifier::Initialize(void *pcontext) } else { - LogPrint(BCLog::ZMQ, "zmq: Reusing socket for address %s\n", address); - LogPrint(BCLog::ZMQ, "zmq: Outbound message high water mark for %s at %s is %d\n", type, address, outbound_message_high_water_mark); + LogPrint(BCLog::ZMQ, "Reusing socket for address %s\n", address); + LogPrint(BCLog::ZMQ, "Outbound message high water mark for %s at %s is %d\n", type, address, outbound_message_high_water_mark); psocket = i->second->psocket; mapPublishNotifiers.insert(std::make_pair(address, this)); @@ -179,7 +179,7 @@ void CZMQAbstractPublishNotifier::Shutdown() if (count == 1) { - LogPrint(BCLog::ZMQ, "zmq: Close socket at address %s\n", address); + LogPrint(BCLog::ZMQ, "Close socket at address %s\n", address); int linger = 0; zmq_setsockopt(psocket, ZMQ_LINGER, &linger, sizeof(linger)); zmq_close(psocket); @@ -208,7 +208,7 @@ bool CZMQAbstractPublishNotifier::SendZmqMessage(const char *command, const void bool CZMQPublishHashBlockNotifier::NotifyBlock(const CBlockIndex *pindex) { uint256 hash = pindex->GetBlockHash(); - LogPrint(BCLog::ZMQ, "zmq: Publish hashblock %s to %s\n", hash.GetHex(), this->address); + LogPrint(BCLog::ZMQ, "Publish hashblock %s to %s\n", hash.GetHex(), this->address); uint8_t data[32]; for (unsigned int i = 0; i < 32; i++) { data[31 - i] = hash.begin()[i]; @@ -219,7 +219,7 @@ bool CZMQPublishHashBlockNotifier::NotifyBlock(const CBlockIndex *pindex) bool CZMQPublishHashTransactionNotifier::NotifyTransaction(const CTransaction &transaction) { uint256 hash = transaction.GetHash(); - LogPrint(BCLog::ZMQ, "zmq: Publish hashtx %s to %s\n", hash.GetHex(), this->address); + LogPrint(BCLog::ZMQ, "Publish hashtx %s to %s\n", hash.GetHex(), this->address); uint8_t data[32]; for (unsigned int i = 0; i < 32; i++) { data[31 - i] = hash.begin()[i]; @@ -229,7 +229,7 @@ bool CZMQPublishHashTransactionNotifier::NotifyTransaction(const CTransaction &t bool CZMQPublishRawBlockNotifier::NotifyBlock(const CBlockIndex *pindex) { - LogPrint(BCLog::ZMQ, "zmq: Publish rawblock %s to %s\n", pindex->GetBlockHash().GetHex(), this->address); + LogPrint(BCLog::ZMQ, "Publish rawblock %s to %s\n", pindex->GetBlockHash().GetHex(), this->address); const Consensus::Params& consensusParams = Params().GetConsensus(); CDataStream ss(SER_NETWORK, PROTOCOL_VERSION | RPCSerializationFlags()); @@ -251,7 +251,7 @@ bool CZMQPublishRawBlockNotifier::NotifyBlock(const CBlockIndex *pindex) bool CZMQPublishRawTransactionNotifier::NotifyTransaction(const CTransaction &transaction) { uint256 hash = transaction.GetHash(); - LogPrint(BCLog::ZMQ, "zmq: Publish rawtx %s to %s\n", hash.GetHex(), this->address); + LogPrint(BCLog::ZMQ, "Publish rawtx %s to %s\n", hash.GetHex(), this->address); CDataStream ss(SER_NETWORK, PROTOCOL_VERSION | RPCSerializationFlags()); ss << transaction; return SendZmqMessage(MSG_RAWTX, &(*ss.begin()), ss.size()); @@ -273,27 +273,27 @@ static bool SendSequenceMsg(CZMQAbstractPublishNotifier& notifier, uint256 hash, bool CZMQPublishSequenceNotifier::NotifyBlockConnect(const CBlockIndex *pindex) { uint256 hash = pindex->GetBlockHash(); - LogPrint(BCLog::ZMQ, "zmq: Publish sequence block connect %s to %s\n", hash.GetHex(), this->address); + LogPrint(BCLog::ZMQ, "Publish sequence block connect %s to %s\n", hash.GetHex(), this->address); return SendSequenceMsg(*this, hash, /* Block (C)onnect */ 'C'); } bool CZMQPublishSequenceNotifier::NotifyBlockDisconnect(const CBlockIndex *pindex) { uint256 hash = pindex->GetBlockHash(); - LogPrint(BCLog::ZMQ, "zmq: Publish sequence block disconnect %s to %s\n", hash.GetHex(), this->address); + LogPrint(BCLog::ZMQ, "Publish sequence block disconnect %s to %s\n", hash.GetHex(), this->address); return SendSequenceMsg(*this, hash, /* Block (D)isconnect */ 'D'); } bool CZMQPublishSequenceNotifier::NotifyTransactionAcceptance(const CTransaction &transaction, uint64_t mempool_sequence) { uint256 hash = transaction.GetHash(); - LogPrint(BCLog::ZMQ, "zmq: Publish hashtx mempool acceptance %s to %s\n", hash.GetHex(), this->address); + LogPrint(BCLog::ZMQ, "Publish hashtx mempool acceptance %s to %s\n", hash.GetHex(), this->address); return SendSequenceMsg(*this, hash, /* Mempool (A)cceptance */ 'A', mempool_sequence); } bool CZMQPublishSequenceNotifier::NotifyTransactionRemoval(const CTransaction &transaction, uint64_t mempool_sequence) { uint256 hash = transaction.GetHash(); - LogPrint(BCLog::ZMQ, "zmq: Publish hashtx mempool removal %s to %s\n", hash.GetHex(), this->address); + LogPrint(BCLog::ZMQ, "Publish hashtx mempool removal %s to %s\n", hash.GetHex(), this->address); return SendSequenceMsg(*this, hash, /* Mempool (R)emoval */ 'R', mempool_sequence); } diff --git a/src/zmq/zmqrpc.cpp b/src/zmq/zmqrpc.cpp index f9f8b5a9dc..ec6d1cbba3 100644 --- a/src/zmq/zmqrpc.cpp +++ b/src/zmq/zmqrpc.cpp @@ -51,10 +51,8 @@ static RPCHelpMan getzmqnotifications() }; } -const CRPCCommand commands[] = -{ // category actor (function) - // ----------------- ----------------------- - { "zmq", &getzmqnotifications, }, +const CRPCCommand commands[]{ + {"zmq", &getzmqnotifications}, }; } // anonymous namespace diff --git a/src/zmq/zmqutil.cpp b/src/zmq/zmqutil.cpp index f0568634d4..cf3a0b2d71 100644 --- a/src/zmq/zmqutil.cpp +++ b/src/zmq/zmqutil.cpp @@ -12,5 +12,5 @@ void zmqError(const std::string& str) { - LogPrint(BCLog::ZMQ, "zmq: Error: %s, msg: %s\n", str, zmq_strerror(errno)); + LogPrint(BCLog::ZMQ, "Error: %s, msg: %s\n", str, zmq_strerror(errno)); } diff --git a/test/README.md b/test/README.md index 8fffde888d..7d4a26fb4e 100644 --- a/test/README.md +++ b/test/README.md @@ -95,18 +95,48 @@ 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 v22.0 v0.21.0 v0.20.1 v0.19.1 v0.18.1 v0.17.2 v0.16.3 v0.15.2 +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` The individual tests and the test_runner harness have many command-line options. Run `test/functional/test_runner.py -h` to see them all. +#### Speed up test runs with a ramdisk + +If you have available RAM on your system you can create a ramdisk to use as the `cache` and `tmp` directories for the functional tests in order to speed them up. +Speed-up amount varies on each system (and according to your ram speed and other variables), but a 2-3x speed-up is not uncommon. + +To create a 4GB ramdisk on Linux at `/mnt/tmp/`: + +```bash +sudo mkdir -p /mnt/tmp +sudo mount -t tmpfs -o size=4g tmpfs /mnt/tmp/ +``` + +Configure the size of the ramdisk using the `size=` option. +The size of the ramdisk needed is relative to the number of concurrent jobs the test suite runs. +For example running the test suite with `--jobs=100` might need a 4GB ramdisk, but running with `--jobs=32` will only need a 2.5GB ramdisk. + +To use, run the test suite specifying the ramdisk as the `cachedir` and `tmpdir`: + +```bash +test/functional/test_runner.py --cachedir=/mnt/tmp/cache --tmpdir=/mnt/tmp +``` + +Once finished with the tests and the disk, and to free the ram, simply unmount the disk: + +```bash +sudo umount /mnt/tmp +``` + #### Troubleshooting and debugging test failures ##### Resource contention @@ -277,11 +307,12 @@ Use the `-v` option for verbose output. | Lint test | Dependency | |-----------|:----------:| -| [`lint-python.sh`](lint/lint-python.sh) | [flake8](https://gitlab.com/pycqa/flake8) -| [`lint-python.sh`](lint/lint-python.sh) | [mypy](https://github.com/python/mypy) -| [`lint-python.sh`](lint/lint-python.sh) | [pyzmq](https://github.com/zeromq/pyzmq) -| [`lint-shell.sh`](lint/lint-shell.sh) | [ShellCheck](https://github.com/koalaman/shellcheck) -| [`lint-spelling.sh`](lint/lint-spelling.sh) | [codespell](https://github.com/codespell-project/codespell) +| [`lint-python.py`](lint/lint-python.py) | [flake8](https://gitlab.com/pycqa/flake8) +| [`lint-python.py`](lint/lint-python.py) | [mypy](https://github.com/python/mypy) +| [`lint-python.py`](lint/lint-python.py) | [pyzmq](https://github.com/zeromq/pyzmq) +| [`lint-python-dead-code.py`](lint/lint-python-dead-code.py) | [vulture](https://github.com/jendrikseipp/vulture) +| [`lint-shell.py`](lint/lint-shell.py) | [ShellCheck](https://github.com/koalaman/shellcheck) +| [`lint-spelling.py`](lint/lint-spelling.py) | [codespell](https://github.com/codespell-project/codespell) In use versions and install instructions are available in the [CI setup](../ci/lint/04_install.sh). @@ -292,13 +323,13 @@ Please be aware that on Linux distributions all dependencies are usually availab Individual tests can be run by directly calling the test script, e.g.: ``` -test/lint/lint-files.sh +test/lint/lint-files.py ``` You can run all the shell-based lint tests by running: ``` -test/lint/lint-all.sh +test/lint/all-lint.py ``` # Writing functional tests diff --git a/test/config.ini.in b/test/config.ini.in index 8bcba1b39c..5888ef443b 100644 --- a/test/config.ini.in +++ b/test/config.ini.in @@ -19,9 +19,11 @@ RPCAUTH=@abs_top_srcdir@/share/rpcauth/rpcauth.py @USE_SQLITE_TRUE@USE_SQLITE=true @USE_BDB_TRUE@USE_BDB=true @BUILD_BITCOIN_CLI_TRUE@ENABLE_CLI=true +@BUILD_BITCOIN_UTIL_TRUE@ENABLE_BITCOIN_UTIL=true @BUILD_BITCOIN_WALLET_TRUE@ENABLE_WALLET_TOOL=true @BUILD_BITCOIND_TRUE@ENABLE_BITCOIND=true @ENABLE_FUZZ_TRUE@ENABLE_FUZZ=true @ENABLE_ZMQ_TRUE@ENABLE_ZMQ=true @ENABLE_EXTERNAL_SIGNER_TRUE@ENABLE_EXTERNAL_SIGNER=true @ENABLE_SYSCALL_SANDBOX_TRUE@ENABLE_SYSCALL_SANDBOX=true +@ENABLE_USDT_TRACEPOINTS_TRUE@ENABLE_USDT_TRACEPOINTS=true diff --git a/test/functional/README.md b/test/functional/README.md index 926810cf03..1bd618a0c3 100644 --- a/test/functional/README.md +++ b/test/functional/README.md @@ -24,11 +24,13 @@ don't have test cases for. Consider using [pyenv](https://github.com/pyenv/pyenv), which checks [.python-version](/.python-version), to prevent accidentally introducing modern syntax from an unsupported Python version. The CI linter job also checks this, but [possibly not in all cases](https://github.com/bitcoin/bitcoin/pull/14884#discussion_r239585126). -- See [the python lint script](/test/lint/lint-python.sh) that checks for violations that +- See [the python lint script](/test/lint/lint-python.py) that checks for violations that 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 0fdefaa9c3..63abf0d9f8 100755 --- a/test/functional/feature_addrman.py +++ b/test/functional/feature_addrman.py @@ -68,6 +68,17 @@ class AddrmanTest(BitcoinTestFramework): self.start_node(0, extra_args=["-checkaddrman=1"]) assert_equal(self.nodes[0].getnodeaddresses(), []) + self.log.info("Check that addrman with negative lowest_compatible cannot be read") + self.stop_node(0) + write_addrman(peers_dat, lowest_compatible=-32) + self.nodes[0].assert_start_raises_init_error( + expected_msg=init_error( + "Corrupted addrman database: The compat value \\(0\\) is lower " + "than the expected minimum value 32.: (.+)" + ), + match=ErrorMatch.FULL_REGEX, + ) + self.log.info("Check that addrman from future is overwritten with new addrman") self.stop_node(0) write_addrman(peers_dat, lowest_compatible=111) @@ -84,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_backwards_compatibility.py b/test/functional/feature_backwards_compatibility.py index a7fb3184a6..59a12193fd 100755 --- a/test/functional/feature_backwards_compatibility.py +++ b/test/functional/feature_backwards_compatibility.py @@ -34,11 +34,12 @@ from test_framework.util import ( class BackwardsCompatibilityTest(BitcoinTestFramework): def set_test_params(self): self.setup_clean_chain = True - self.num_nodes = 9 + self.num_nodes = 10 # Add new version after each release: self.extra_args = [ ["-addresstype=bech32", "-whitelist=noban@127.0.0.1"], # Pre-release: use to mine blocks. noban for immediate tx relay ["-nowallet", "-walletrbf=1", "-addresstype=bech32", "-whitelist=noban@127.0.0.1"], # Pre-release: use to receive coins, swap wallets, etc + ["-nowallet", "-walletrbf=1", "-addresstype=bech32", "-whitelist=noban@127.0.0.1"], # v23.0 ["-nowallet", "-walletrbf=1", "-addresstype=bech32", "-whitelist=noban@127.0.0.1"], # v22.0 ["-nowallet", "-walletrbf=1", "-addresstype=bech32", "-whitelist=noban@127.0.0.1"], # v0.21.0 ["-nowallet", "-walletrbf=1", "-addresstype=bech32", "-whitelist=noban@127.0.0.1"], # v0.20.1 @@ -57,6 +58,7 @@ class BackwardsCompatibilityTest(BitcoinTestFramework): self.add_nodes(self.num_nodes, extra_args=self.extra_args, versions=[ None, None, + 230000, 220000, 210000, 200100, 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_blockfilterindex_prune.py b/test/functional/feature_blockfilterindex_prune.py deleted file mode 100755 index 2451988135..0000000000 --- a/test/functional/feature_blockfilterindex_prune.py +++ /dev/null @@ -1,78 +0,0 @@ -#!/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 blockfilterindex in conjunction with prune.""" -from test_framework.test_framework import BitcoinTestFramework -from test_framework.util import ( - assert_equal, - assert_greater_than, - assert_raises_rpc_error, -) - - -class FeatureBlockfilterindexPruneTest(BitcoinTestFramework): - def set_test_params(self): - self.num_nodes = 1 - self.extra_args = [["-fastprune", "-prune=1", "-blockfilterindex=1"]] - - def sync_index(self, height): - expected = {'basic block filter index': {'synced': True, 'best_block_height': height}} - self.wait_until(lambda: self.nodes[0].getindexinfo() == expected) - - def run_test(self): - self.log.info("check if we can access a blockfilter when pruning is enabled but no blocks are actually pruned") - self.sync_index(height=200) - assert_greater_than(len(self.nodes[0].getblockfilter(self.nodes[0].getbestblockhash())['filter']), 0) - self.generate(self.nodes[0], 500) - self.sync_index(height=700) - - self.log.info("prune some blocks") - pruneheight = self.nodes[0].pruneblockchain(400) - # the prune heights used here and below are magic numbers that are determined by the - # thresholds at which block files wrap, so they depend on disk serialization and default block file size. - assert_equal(pruneheight, 248) - - self.log.info("check if we can access the tips blockfilter when we have pruned some blocks") - assert_greater_than(len(self.nodes[0].getblockfilter(self.nodes[0].getbestblockhash())['filter']), 0) - - self.log.info("check if we can access the blockfilter of a pruned block") - assert_greater_than(len(self.nodes[0].getblockfilter(self.nodes[0].getblockhash(2))['filter']), 0) - - # mine and sync index up to a height that will later be the pruneheight - self.generate(self.nodes[0], 298) - self.sync_index(height=998) - - self.log.info("start node without blockfilterindex") - self.restart_node(0, extra_args=["-fastprune", "-prune=1"]) - - self.log.info("make sure accessing the blockfilters throws an error") - assert_raises_rpc_error(-1, "Index is not enabled for filtertype basic", self.nodes[0].getblockfilter, self.nodes[0].getblockhash(2)) - self.generate(self.nodes[0], 502) - - self.log.info("prune exactly up to the blockfilterindexes best block while blockfilters are disabled") - pruneheight_2 = self.nodes[0].pruneblockchain(1000) - assert_equal(pruneheight_2, 998) - self.restart_node(0, extra_args=["-fastprune", "-prune=1", "-blockfilterindex=1"]) - self.log.info("make sure that we can continue with the partially synced index after having pruned up to the index height") - self.sync_index(height=1500) - - self.log.info("prune below the blockfilterindexes best block while blockfilters are disabled") - self.restart_node(0, extra_args=["-fastprune", "-prune=1"]) - self.generate(self.nodes[0], 1000) - pruneheight_3 = self.nodes[0].pruneblockchain(2000) - assert_greater_than(pruneheight_3, pruneheight_2) - self.stop_node(0) - - self.log.info("make sure we get an init error when starting the node again with block filters") - self.nodes[0].assert_start_raises_init_error( - extra_args=["-fastprune", "-prune=1", "-blockfilterindex=1"], - expected_msg="Error: basic block filter index best block of the index goes beyond pruned data. Please disable the index or reindex (which will download the whole blockchain again)", - ) - - self.log.info("make sure the node starts again with the -reindex arg") - self.start_node(0, extra_args=["-fastprune", "-prune=1", "-blockfilterindex", "-reindex"]) - - -if __name__ == '__main__': - FeatureBlockfilterindexPruneTest().main() diff --git a/test/functional/feature_coinstatsindex.py b/test/functional/feature_coinstatsindex.py index c70f8a83db..2e21638f80 100755 --- a/test/functional/feature_coinstatsindex.py +++ b/test/functional/feature_coinstatsindex.py @@ -18,9 +18,6 @@ from test_framework.blocktools import ( ) from test_framework.messages import ( COIN, - COutPoint, - CTransaction, - CTxIn, CTxOut, ) from test_framework.script import ( @@ -33,6 +30,11 @@ from test_framework.util import ( assert_equal, assert_raises_rpc_error, ) +from test_framework.wallet import ( + MiniWallet, + getnewdestination, +) + class CoinStatsIndexTest(BitcoinTestFramework): def set_test_params(self): @@ -40,16 +42,12 @@ class CoinStatsIndexTest(BitcoinTestFramework): self.num_nodes = 2 self.supports_cli = False self.extra_args = [ - # Explicitly set the output type in order to have consistent tx vsize / fees - # for both legacy and descriptor wallets (disables the change address type detection algorithm) - ["-addresstype=bech32", "-changetype=bech32"], + [], ["-coinstatsindex"] ] - def skip_test_if_missing_module(self): - self.skip_if_no_wallet() - def run_test(self): + self.wallet = MiniWallet(self.nodes[0]) self._test_coin_stats_index() self._test_use_index_option() self._test_reorg_index() @@ -69,9 +67,8 @@ class CoinStatsIndexTest(BitcoinTestFramework): index_hash_options = ['none', 'muhash'] # Generate a normal transaction and mine it - self.generate(node, COINBASE_MATURITY + 1) - address = self.nodes[0].get_deterministic_priv_key().address - node.sendtoaddress(address=address, amount=10, subtractfeefromamount=True) + self.generate(self.wallet, COINBASE_MATURITY + 1) + self.wallet.send_self_transfer(from_node=node) self.generate(node, 1) self.log.info("Test that gettxoutsetinfo() output is consistent with or without coinstatsindex option") @@ -136,36 +133,31 @@ class CoinStatsIndexTest(BitcoinTestFramework): assert_equal(res5['block_info'], { 'unspendable': 0, 'prevout_spent': 50, - 'new_outputs_ex_coinbase': Decimal('49.99995560'), - 'coinbase': Decimal('50.00004440'), + 'new_outputs_ex_coinbase': Decimal('49.99968800'), + 'coinbase': Decimal('50.00031200'), 'unspendables': { 'genesis_block': 0, 'bip30': 0, 'scripts': 0, - 'unclaimed_rewards': 0 + 'unclaimed_rewards': 0, } }) self.block_sanity_check(res5['block_info']) # Generate and send a normal tx with two outputs - tx1_inputs = [] - tx1_outputs = {self.nodes[0].getnewaddress(): 21, self.nodes[0].getnewaddress(): 42} - raw_tx1 = self.nodes[0].createrawtransaction(tx1_inputs, tx1_outputs) - funded_tx1 = self.nodes[0].fundrawtransaction(raw_tx1) - signed_tx1 = self.nodes[0].signrawtransactionwithwallet(funded_tx1['hex']) - tx1_txid = self.nodes[0].sendrawtransaction(signed_tx1['hex']) + tx1_txid, tx1_vout = self.wallet.send_to( + from_node=node, + scriptPubKey=self.wallet.get_scriptPubKey(), + amount=21 * COIN, + ) # Find the right position of the 21 BTC output - tx1_final = self.nodes[0].gettransaction(tx1_txid) - for output in tx1_final['details']: - if output['amount'] == Decimal('21.00000000') and output['category'] == 'receive': - n = output['vout'] + tx1_out_21 = self.wallet.get_utxo(txid=tx1_txid, vout=tx1_vout) # Generate and send another tx with an OP_RETURN output (which is unspendable) - tx2 = CTransaction() - tx2.vin.append(CTxIn(COutPoint(int(tx1_txid, 16), n), b'')) - tx2.vout.append(CTxOut(int(Decimal('20.99') * COIN), CScript([OP_RETURN] + [OP_FALSE]*30))) - tx2_hex = self.nodes[0].signrawtransactionwithwallet(tx2.serialize().hex())['hex'] + tx2 = self.wallet.create_self_transfer(utxo_to_spend=tx1_out_21)['tx'] + tx2.vout = [CTxOut(int(Decimal('20.99') * COIN), CScript([OP_RETURN] + [OP_FALSE] * 30))] + tx2_hex = tx2.serialize().hex() self.nodes[0].sendrawtransaction(tx2_hex) # Include both txs in a block @@ -177,14 +169,14 @@ class CoinStatsIndexTest(BitcoinTestFramework): assert_equal(res6['total_unspendable_amount'], Decimal('70.99000000')) assert_equal(res6['block_info'], { 'unspendable': Decimal('20.99000000'), - 'prevout_spent': 111, - 'new_outputs_ex_coinbase': Decimal('89.99993620'), - 'coinbase': Decimal('50.01006380'), + 'prevout_spent': 71, + 'new_outputs_ex_coinbase': Decimal('49.99999000'), + 'coinbase': Decimal('50.01001000'), 'unspendables': { 'genesis_block': 0, 'bip30': 0, 'scripts': Decimal('20.99000000'), - 'unclaimed_rewards': 0 + 'unclaimed_rewards': 0, } }) self.block_sanity_check(res6['block_info']) @@ -231,6 +223,22 @@ class CoinStatsIndexTest(BitcoinTestFramework): res10 = index_node.gettxoutsetinfo('muhash') assert(res8['txouts'] < res10['txouts']) + self.log.info("Test that the index works with -reindex") + + self.restart_node(1, extra_args=["-coinstatsindex", "-reindex"]) + res11 = index_node.gettxoutsetinfo('muhash') + assert_equal(res11, res10) + + self.log.info("Test that -reindex-chainstate is disallowed with coinstatsindex") + + self.stop_node(1) + self.nodes[1].assert_start_raises_init_error( + expected_msg='Error: -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.', + extra_args=['-coinstatsindex', '-reindex-chainstate'], + ) + self.restart_node(1, extra_args=["-coinstatsindex"]) + def _test_use_index_option(self): self.log.info("Test use_index option for nodes running the index") @@ -246,7 +254,7 @@ class CoinStatsIndexTest(BitcoinTestFramework): # Generate two block, let the index catch up, then invalidate the blocks index_node = self.nodes[1] - reorg_blocks = self.generatetoaddress(index_node, 2, index_node.getnewaddress()) + reorg_blocks = self.generatetoaddress(index_node, 2, getnewdestination()[2]) reorg_block = reorg_blocks[1] res_invalid = index_node.gettxoutsetinfo('muhash') index_node.invalidateblock(reorg_blocks[0]) diff --git a/test/functional/feature_config_args.py b/test/functional/feature_config_args.py index eea5fa24ee..6c51a5ac31 100755 --- a/test/functional/feature_config_args.py +++ b/test/functional/feature_config_args.py @@ -85,7 +85,7 @@ class ConfArgsTest(BitcoinTestFramework): def test_invalid_command_line_options(self): self.nodes[0].assert_start_raises_init_error( - expected_msg='Error: No proxy server specified. Use -proxy=<ip> or -proxy=<ip:port>.', + expected_msg='Error: Error parsing command line arguments: Can not set -proxy with no value. Please specify value with -proxy=value.', extra_args=['-proxy'], ) @@ -247,7 +247,8 @@ class ConfArgsTest(BitcoinTestFramework): conf_file = os.path.join(default_data_dir, "bitcoin.conf") # datadir needs to be set before [chain] section - conf_file_contents = open(conf_file, encoding='utf8').read() + with open(conf_file, encoding='utf8') as f: + conf_file_contents = f.read() with open(conf_file, 'w', encoding='utf8') as f: f.write(f"datadir={new_data_dir}\n") f.write(conf_file_contents) diff --git a/test/functional/feature_csv_activation.py b/test/functional/feature_csv_activation.py index 6470c1c5eb..bff95c3b94 100755 --- a/test/functional/feature_csv_activation.py +++ b/test/functional/feature_csv_activation.py @@ -112,6 +112,7 @@ class BIP68_112_113Test(BitcoinTestFramework): tx.nVersion = txversion self.miniwallet.sign_tx(tx) tx.vin[0].scriptSig = CScript([-1, OP_CHECKSEQUENCEVERIFY, OP_DROP] + list(CScript(tx.vin[0].scriptSig))) + tx.rehash() return tx def create_bip112emptystack(self, input, txversion): @@ -119,6 +120,7 @@ class BIP68_112_113Test(BitcoinTestFramework): tx.nVersion = txversion self.miniwallet.sign_tx(tx) tx.vin[0].scriptSig = CScript([OP_CHECKSEQUENCEVERIFY] + list(CScript(tx.vin[0].scriptSig))) + tx.rehash() return tx def send_generic_input_tx(self, coinbases): @@ -136,7 +138,6 @@ class BIP68_112_113Test(BitcoinTestFramework): tx.nVersion = txversion tx.vin[0].nSequence = locktime + locktime_delta self.miniwallet.sign_tx(tx) - tx.rehash() txs.append({'tx': tx, 'sdf': sdf, 'stf': stf}) return txs @@ -339,20 +340,16 @@ class BIP68_112_113Test(BitcoinTestFramework): # BIP 113 tests should now fail regardless of version number if nLockTime isn't satisfied by new rules bip113tx_v1.nLockTime = self.last_block_time - 600 * 5 # = MTP of prior block (not <) but < time put on current block self.miniwallet.sign_tx(bip113tx_v1) - bip113tx_v1.rehash() bip113tx_v2.nLockTime = self.last_block_time - 600 * 5 # = MTP of prior block (not <) but < time put on current block self.miniwallet.sign_tx(bip113tx_v2) - bip113tx_v2.rehash() for bip113tx in [bip113tx_v1, bip113tx_v2]: self.send_blocks([self.create_test_block([bip113tx])], success=False, reject_reason='bad-txns-nonfinal') # BIP 113 tests should now pass if the locktime is < MTP bip113tx_v1.nLockTime = self.last_block_time - 600 * 5 - 1 # < MTP of prior block self.miniwallet.sign_tx(bip113tx_v1) - bip113tx_v1.rehash() bip113tx_v2.nLockTime = self.last_block_time - 600 * 5 - 1 # < MTP of prior block self.miniwallet.sign_tx(bip113tx_v2) - bip113tx_v2.rehash() for bip113tx in [bip113tx_v1, bip113tx_v2]: self.send_blocks([self.create_test_block([bip113tx])]) self.nodes[0].invalidateblock(self.nodes[0].getbestblockhash()) @@ -477,7 +474,6 @@ class BIP68_112_113Test(BitcoinTestFramework): for tx in [tx['tx'] for tx in bip112txs_vary_OP_CSV_v2 if not tx['sdf'] and tx['stf']]: tx.vin[0].nSequence = BASE_RELATIVE_LOCKTIME | SEQ_TYPE_FLAG self.miniwallet.sign_tx(tx) - tx.rehash() time_txs.append(tx) self.send_blocks([self.create_test_block(time_txs)]) diff --git a/test/functional/feature_dbcrash.py b/test/functional/feature_dbcrash.py index 3e60efbb3c..f606f26e70 100755 --- a/test/functional/feature_dbcrash.py +++ b/test/functional/feature_dbcrash.py @@ -30,17 +30,17 @@ import http.client import random import time +from test_framework.blocktools import COINBASE_MATURITY from test_framework.messages import ( COIN, - COutPoint, - CTransaction, - CTxIn, - CTxOut, ) from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_equal, - create_confirmed_utxos, +) +from test_framework.wallet import ( + MiniWallet, + getnewdestination, ) @@ -62,17 +62,13 @@ 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 skip_test_if_missing_module(self): - self.skip_if_no_wallet() - def setup_network(self): self.add_nodes(self.num_nodes, extra_args=self.extra_args) self.start_nodes() - self.import_deterministic_coinbase_privkeys() # Leave them unconnected, we'll use submitblock directly in this test def restart_node(self, node_index, expected_tip): @@ -190,34 +186,36 @@ class ChainstateWriteCrashTest(BitcoinTestFramework): num_transactions = 0 random.shuffle(utxo_list) while len(utxo_list) >= 2 and num_transactions < count: - tx = CTransaction() - input_amount = 0 - for _ in range(2): - utxo = utxo_list.pop() - tx.vin.append(CTxIn(COutPoint(int(utxo['txid'], 16), utxo['vout']))) - input_amount += int(utxo['amount'] * COIN) - output_amount = (input_amount - FEE) // 3 - - if output_amount <= 0: + utxos_to_spend = [utxo_list.pop() for _ in range(2)] + input_amount = int(sum([utxo['value'] for utxo in utxos_to_spend]) * COIN) + if input_amount < FEE: # Sanity check -- if we chose inputs that are too small, skip continue - for _ in range(3): - tx.vout.append(CTxOut(output_amount, bytes.fromhex(utxo['scriptPubKey']))) - - # Sign and send the transaction to get into the mempool - tx_signed_hex = node.signrawtransactionwithwallet(tx.serialize().hex())['hex'] - node.sendrawtransaction(tx_signed_hex) + self.wallet.send_self_transfer_multi( + from_node=node, + utxos_to_spend=utxos_to_spend, + num_outputs=3, + fee_per_output=FEE // 3, + ) num_transactions += 1 def run_test(self): + self.wallet = MiniWallet(self.nodes[3]) + self.wallet.rescan_utxos() + initial_height = self.nodes[3].getblockcount() + self.generate(self.nodes[3], COINBASE_MATURITY, sync_fun=self.no_op) + # Track test coverage statistics self.restart_counts = [0, 0, 0] # Track the restarts for nodes 0-2 self.crashed_on_restart = 0 # Track count of crashes during recovery # Start by creating a lot of utxos on node3 - initial_height = self.nodes[3].getblockcount() - utxo_list = create_confirmed_utxos(self, self.nodes[3].getnetworkinfo()['relayfee'], self.nodes[3], 5000, sync_fun=self.no_op) + 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") # Sync these blocks with the other nodes @@ -257,13 +255,14 @@ class ChainstateWriteCrashTest(BitcoinTestFramework): self.nodes[3], nblocks=min(10, current_height + 1 - self.nodes[3].getblockcount()), # new address to avoid mining a block that has just been invalidated - address=self.nodes[3].getnewaddress(), + address=getnewdestination()[2], sync_fun=self.no_op, )) self.log.debug(f"Syncing {len(block_hashes)} new blocks...") self.sync_node3blocks(block_hashes) - utxo_list = self.nodes[3].listunspent() - self.log.debug(f"Node3 utxo count: {len(utxo_list)}") + self.wallet.rescan_utxos() + utxo_list = self.wallet.get_utxos() + self.log.debug(f"MiniWallet utxo count: {len(utxo_list)}") # Check that the utxo hashes agree with node3 # Useful side effect: each utxo cache gets flushed here, so that we diff --git a/test/functional/feature_fee_estimation.py b/test/functional/feature_fee_estimation.py index 233ffd60da..b0cbcf4edf 100755 --- a/test/functional/feature_fee_estimation.py +++ b/test/functional/feature_fee_estimation.py @@ -3,25 +3,13 @@ # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test fee estimation code.""" +from copy import deepcopy from decimal import Decimal import os import random from test_framework.messages import ( COIN, - COutPoint, - CTransaction, - CTxIn, - CTxOut, -) -from test_framework.script import ( - CScript, - OP_1, - OP_DROP, - OP_TRUE, -) -from test_framework.script_util import ( - script_to_p2sh_script, ) from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( @@ -31,22 +19,14 @@ from test_framework.util import ( assert_raises_rpc_error, satoshi_round, ) - -# Construct 2 trivial P2SH's and the ScriptSigs that spend them -# So we can create many transactions without needing to spend -# time signing. -SCRIPT = CScript([OP_1, OP_DROP]) -P2SH = script_to_p2sh_script(SCRIPT) -REDEEM_SCRIPT = CScript([OP_TRUE, SCRIPT]) +from test_framework.wallet import MiniWallet def small_txpuzzle_randfee( - from_node, conflist, unconflist, amount, min_fee, fee_increment + wallet, from_node, conflist, unconflist, amount, min_fee, fee_increment ): - """Create and send a transaction with a random fee. + """Create and send a transaction with a random fee using MiniWallet. - The transaction pays to a trivial P2SH script, and assumes that its inputs - are of the same form. The function takes a list of confirmed outputs and unconfirmed outputs and attempts to use the confirmed list first for its inputs. It adds the newly created outputs to the unconfirmed list. @@ -58,23 +38,29 @@ def small_txpuzzle_randfee( rand_fee = float(fee_increment) * (1.1892 ** random.randint(0, 28)) # Total fee ranges from min_fee to min_fee + 127*fee_increment fee = min_fee - fee_increment + satoshi_round(rand_fee) - tx = CTransaction() + utxos_to_spend = [] total_in = Decimal("0.00000000") while total_in <= (amount + fee) and len(conflist) > 0: t = conflist.pop(0) - total_in += t["amount"] - tx.vin.append(CTxIn(COutPoint(int(t["txid"], 16), t["vout"]), REDEEM_SCRIPT)) + total_in += t["value"] + utxos_to_spend.append(t) while total_in <= (amount + fee) and len(unconflist) > 0: t = unconflist.pop(0) - total_in += t["amount"] - tx.vin.append(CTxIn(COutPoint(int(t["txid"], 16), t["vout"]), REDEEM_SCRIPT)) + total_in += t["value"] + utxos_to_spend.append(t) if total_in <= amount + fee: raise RuntimeError(f"Insufficient funds: need {amount + fee}, have {total_in}") - tx.vout.append(CTxOut(int((total_in - amount - fee) * COIN), P2SH)) - tx.vout.append(CTxOut(int(amount * COIN), P2SH)) + tx = wallet.create_self_transfer_multi( + utxos_to_spend=utxos_to_spend, + 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) + txid = from_node.sendrawtransaction(hexstring=tx.serialize().hex(), maxfeerate=0) - unconflist.append({"txid": txid, "vout": 0, "amount": total_in - amount - fee}) - unconflist.append({"txid": txid, "vout": 1, "amount": amount}) + unconflist.append({"txid": txid, "vout": 0, "value": total_in - amount - fee}) + unconflist.append({"txid": txid, "vout": 1, "value": amount}) return (tx.serialize().hex(), fee) @@ -129,17 +115,13 @@ def check_estimates(node, fees_seen): check_smart_estimates(node, fees_seen) -def send_tx(node, utxo, feerate): +def send_tx(wallet, node, utxo, feerate): """Broadcast a 1in-1out transaction with a specific input and feerate (sat/vb).""" - tx = CTransaction() - tx.vin = [CTxIn(COutPoint(int(utxo["txid"], 16), utxo["vout"]), REDEEM_SCRIPT)] - tx.vout = [CTxOut(int(utxo["amount"] * COIN), P2SH)] - - # vbytes == bytes as we are using legacy transactions - fee = tx.get_vsize() * feerate - tx.vout[0].nValue -= fee - - return node.sendrawtransaction(tx.serialize().hex()) + return wallet.send_self_transfer( + from_node=node, + utxo_to_spend=utxo, + fee_rate=Decimal(feerate * 1000) / COIN, + )['txid'] class EstimateFeeTest(BitcoinTestFramework): @@ -152,9 +134,6 @@ class EstimateFeeTest(BitcoinTestFramework): ["-whitelist=noban@127.0.0.1", "-blockmaxweight=32000"], ] - def skip_test_if_missing_module(self): - self.skip_if_no_wallet() - def setup_network(self): """ We'll setup the network to have 3 nodes that all mine with different parameters. @@ -168,9 +147,6 @@ class EstimateFeeTest(BitcoinTestFramework): # (68k weight is room enough for 120 or so transactions) # Node2 is a stingy miner, that # produces too small blocks (room for only 55 or so transactions) - self.start_nodes() - self.import_deterministic_coinbase_privkeys() - self.stop_nodes() def transact_and_mine(self, numblocks, mining_node): min_fee = Decimal("0.00001") @@ -183,6 +159,7 @@ class EstimateFeeTest(BitcoinTestFramework): for _ in range(random.randrange(100 - 50, 100 + 50)): from_index = random.randint(1, 2) (txhex, fee) = small_txpuzzle_randfee( + self.wallet, self.nodes[from_index], self.confutxo, self.memutxo, @@ -205,24 +182,10 @@ class EstimateFeeTest(BitcoinTestFramework): def initial_split(self, node): """Split two coinbase UTxOs into many small coins""" - utxo_count = 2048 - self.confutxo = [] - splitted_amount = Decimal("0.04") - fee = Decimal("0.1") - change = Decimal("100") - splitted_amount * utxo_count - fee - tx = CTransaction() - tx.vin = [ - CTxIn(COutPoint(int(cb["txid"], 16), cb["vout"])) - for cb in node.listunspent()[:2] - ] - tx.vout = [CTxOut(int(splitted_amount * COIN), P2SH) for _ in range(utxo_count)] - tx.vout.append(CTxOut(int(change * COIN), P2SH)) - txhex = node.signrawtransactionwithwallet(tx.serialize().hex())["hex"] - txid = node.sendrawtransaction(txhex) - self.confutxo = [ - {"txid": txid, "vout": i, "amount": splitted_amount} - for i in range(utxo_count) - ] + self.confutxo = self.wallet.send_self_transfer_multi( + from_node=node, + utxos_to_spend=[self.wallet.get_utxo() for _ in range(2)], + num_outputs=2048)['new_utxos'] while len(node.getrawmempool()) > 0: self.generate(node, 1, sync_fun=self.no_op) @@ -284,12 +247,12 @@ class EstimateFeeTest(BitcoinTestFramework): # Broadcast 45 low fee transactions that will need to be RBF'd for _ in range(45): u = utxos.pop(0) - txid = send_tx(node, u, low_feerate) + txid = send_tx(self.wallet, node, u, low_feerate) utxos_to_respend.append(u) txids_to_replace.append(txid) # Broadcast 5 low fee transaction which don't need to for _ in range(5): - send_tx(node, utxos.pop(0), low_feerate) + send_tx(self.wallet, node, utxos.pop(0), low_feerate) # Mine the transactions on another node self.sync_mempools(wait=0.1, nodes=[node, miner]) for txid in txids_to_replace: @@ -298,7 +261,7 @@ class EstimateFeeTest(BitcoinTestFramework): # RBF the low-fee transactions while len(utxos_to_respend) > 0: u = utxos_to_respend.pop(0) - send_tx(node, u, high_feerate) + send_tx(self.wallet, node, u, high_feerate) # Mine the last replacement txs self.sync_mempools(wait=0.1, nodes=[node, miner]) @@ -316,6 +279,8 @@ class EstimateFeeTest(BitcoinTestFramework): # Split two coinbases into many small utxos self.start_node(0) + self.wallet = MiniWallet(self.nodes[0]) + self.wallet.rescan_utxos() self.initial_split(self.nodes[0]) self.log.info("Finished splitting") diff --git a/test/functional/feature_index_prune.py b/test/functional/feature_index_prune.py new file mode 100755 index 0000000000..bc85e43a57 --- /dev/null +++ b/test/functional/feature_index_prune.py @@ -0,0 +1,155 @@ +#!/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 indices in conjunction with prune.""" +from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import ( + assert_equal, + assert_greater_than, + assert_raises_rpc_error, +) + + +class FeatureIndexPruneTest(BitcoinTestFramework): + def set_test_params(self): + self.num_nodes = 4 + self.extra_args = [ + ["-fastprune", "-prune=1", "-blockfilterindex=1"], + ["-fastprune", "-prune=1", "-coinstatsindex=1"], + ["-fastprune", "-prune=1", "-blockfilterindex=1", "-coinstatsindex=1"], + [] + ] + + def sync_index(self, height): + expected_filter = { + 'basic block filter index': {'synced': True, 'best_block_height': height}, + } + self.wait_until(lambda: self.nodes[0].getindexinfo() == expected_filter) + + expected_stats = { + 'coinstatsindex': {'synced': True, 'best_block_height': height} + } + self.wait_until(lambda: self.nodes[1].getindexinfo() == expected_stats) + + expected = {**expected_filter, **expected_stats} + self.wait_until(lambda: self.nodes[2].getindexinfo() == expected) + + def reconnect_nodes(self): + self.connect_nodes(0,1) + self.connect_nodes(0,2) + self.connect_nodes(0,3) + + def mine_batches(self, blocks): + n = blocks // 250 + for _ in range(n): + self.generate(self.nodes[0], 250) + self.generate(self.nodes[0], blocks % 250) + self.sync_blocks() + + def restart_without_indices(self): + for i in range(3): + self.restart_node(i, extra_args=["-fastprune", "-prune=1"]) + self.reconnect_nodes() + + def run_test(self): + filter_nodes = [self.nodes[0], self.nodes[2]] + stats_nodes = [self.nodes[1], self.nodes[2]] + + self.log.info("check if we can access blockfilters and coinstats when pruning is enabled but no blocks are actually pruned") + self.sync_index(height=200) + tip = self.nodes[0].getbestblockhash() + for node in filter_nodes: + assert_greater_than(len(node.getblockfilter(tip)['filter']), 0) + for node in stats_nodes: + assert(node.gettxoutsetinfo(hash_type="muhash", hash_or_height=tip)['muhash']) + + self.mine_batches(500) + self.sync_index(height=700) + + self.log.info("prune some blocks") + for node in self.nodes[:2]: + with node.assert_debug_log(['limited pruning to height 689']): + pruneheight_new = node.pruneblockchain(400) + # the prune heights used here and below are magic numbers that are determined by the + # thresholds at which block files wrap, so they depend on disk serialization and default block file size. + assert_equal(pruneheight_new, 248) + + self.log.info("check if we can access the tips blockfilter and coinstats when we have pruned some blocks") + tip = self.nodes[0].getbestblockhash() + for node in filter_nodes: + assert_greater_than(len(node.getblockfilter(tip)['filter']), 0) + for node in stats_nodes: + assert(node.gettxoutsetinfo(hash_type="muhash", hash_or_height=tip)['muhash']) + + self.log.info("check if we can access the blockfilter and coinstats of a pruned block") + height_hash = self.nodes[0].getblockhash(2) + for node in filter_nodes: + assert_greater_than(len(node.getblockfilter(height_hash)['filter']), 0) + for node in stats_nodes: + assert(node.gettxoutsetinfo(hash_type="muhash", hash_or_height=height_hash)['muhash']) + + # mine and sync index up to a height that will later be the pruneheight + self.generate(self.nodes[0], 51) + self.sync_index(height=751) + + self.restart_without_indices() + + self.log.info("make sure trying to access the indices throws errors") + for node in filter_nodes: + msg = "Index is not enabled for filtertype basic" + assert_raises_rpc_error(-1, msg, node.getblockfilter, height_hash) + for node in stats_nodes: + msg = "Querying specific block heights requires coinstatsindex" + assert_raises_rpc_error(-8, msg, node.gettxoutsetinfo, "muhash", height_hash) + + self.mine_batches(749) + + self.log.info("prune exactly up to the indices best blocks while the indices are disabled") + for i in range(3): + pruneheight_2 = self.nodes[i].pruneblockchain(1000) + assert_equal(pruneheight_2, 750) + # Restart the nodes again with the indices activated + self.restart_node(i, extra_args=self.extra_args[i]) + + self.log.info("make sure that we can continue with the partially synced indices after having pruned up to the index height") + self.sync_index(height=1500) + + self.log.info("prune further than the indices best blocks while the indices are disabled") + self.restart_without_indices() + self.mine_batches(1000) + + for i in range(3): + pruneheight_3 = self.nodes[i].pruneblockchain(2000) + assert_greater_than(pruneheight_3, pruneheight_2) + self.stop_node(i) + + self.log.info("make sure we get an init error when starting the nodes again with the indices") + filter_msg = "Error: basic block filter index best block of the index goes beyond pruned data. Please disable the index or reindex (which will download the whole blockchain again)" + stats_msg = "Error: coinstatsindex best block of the index goes beyond pruned data. Please disable the index or reindex (which will download the whole blockchain again)" + for i, msg in enumerate([filter_msg, stats_msg, filter_msg]): + self.nodes[i].assert_start_raises_init_error(extra_args=self.extra_args[i], expected_msg=msg) + + self.log.info("make sure the nodes start again with the indices and an additional -reindex arg") + for i in range(3): + restart_args = self.extra_args[i]+["-reindex"] + self.restart_node(i, extra_args=restart_args) + # The nodes need to be reconnected to the non-pruning node upon restart, otherwise they will be stuck + self.connect_nodes(i, 3) + + self.sync_blocks(timeout=300) + + for node in self.nodes[:2]: + with node.assert_debug_log(['limited pruning to height 2489']): + pruneheight_new = node.pruneblockchain(2500) + assert_equal(pruneheight_new, 2005) + + self.log.info("ensure that prune locks don't prevent indices from failing in a reorg scenario") + with self.nodes[0].assert_debug_log(['basic block filter index prune lock moved back to 2480']): + self.nodes[3].invalidateblock(self.nodes[0].getblockhash(2480)) + self.generate(self.nodes[3], 30) + self.sync_blocks() + + +if __name__ == '__main__': + FeatureIndexPruneTest().main() diff --git a/test/functional/feature_init.py b/test/functional/feature_init.py index d0cb1e10e2..13c7326519 100755 --- a/test/functional/feature_init.py +++ b/test/functional/feature_init.py @@ -49,33 +49,33 @@ class InitStressTest(BitcoinTestFramework): assert_equal(200, node.getblockcount()) lines_to_terminate_after = [ - 'Validating signatures for all blocks', - 'scheduler thread start', - 'Starting HTTP server', - 'Loading P2P addresses', - 'Loading banlist', - 'Loading block index', - 'Switching active chainstate', - 'Checking all blk files are present', - 'Loaded best chain:', - 'init message: Verifying blocks', - 'init message: Starting network threads', - 'net thread start', - 'addcon thread start', - 'loadblk thread start', - 'txindex thread start', - 'block filter index thread start', - 'coinstatsindex thread start', - 'msghand thread start', - 'net thread start', - 'addcon thread start', + b'Validating signatures for all blocks', + b'scheduler thread start', + b'Starting HTTP server', + b'Loading P2P addresses', + b'Loading banlist', + b'Loading block index', + b'Switching active chainstate', + b'Checking all blk files are present', + b'Loaded best chain:', + b'init message: Verifying blocks', + b'init message: Starting network threads', + b'net thread start', + b'addcon thread start', + b'loadblk thread start', + b'txindex thread start', + b'block filter index thread start', + b'coinstatsindex thread start', + b'msghand thread start', + b'net thread start', + b'addcon thread start', ] if self.is_wallet_compiled(): - lines_to_terminate_after.append('Verifying wallet') + lines_to_terminate_after.append(b'Verifying wallet') for terminate_line in lines_to_terminate_after: - self.log.info(f"Starting node and will exit after line '{terminate_line}'") - with node.wait_for_debug_log([terminate_line], ignore_case=True): + self.log.info(f"Starting node and will exit after line {terminate_line}") + with node.wait_for_debug_log([terminate_line]): node.start(extra_args=['-txindex=1', '-blockfilterindex=1', '-coinstatsindex=1']) self.log.debug("Terminating node after terminate line was found") sigterm_node() diff --git a/test/functional/feature_maxuploadtarget.py b/test/functional/feature_maxuploadtarget.py index 24f79dda67..3ea412002a 100755 --- a/test/functional/feature_maxuploadtarget.py +++ b/test/functional/feature_maxuploadtarget.py @@ -13,10 +13,19 @@ if uploadtarget has been reached. from collections import defaultdict import time -from test_framework.messages import CInv, MSG_BLOCK, msg_getdata +from test_framework.messages import ( + CInv, + MSG_BLOCK, + msg_getdata, +) from test_framework.p2p import P2PInterface from test_framework.test_framework import BitcoinTestFramework -from test_framework.util import assert_equal, mine_large_block +from test_framework.util import ( + assert_equal, + mine_large_block, +) +from test_framework.wallet import MiniWallet + class TestP2PConn(P2PInterface): def __init__(self): @@ -37,16 +46,10 @@ class MaxUploadTest(BitcoinTestFramework): self.num_nodes = 1 self.extra_args = [[ "-maxuploadtarget=800M", - "-acceptnonstdtxn=1", + "-datacarriersize=100000", ]] self.supports_cli = False - # Cache for utxos, as the listunspent may take a long time later in the test - self.utxo_cache = [] - - def skip_test_if_missing_module(self): - self.skip_if_no_wallet() - def run_test(self): # Before we connect anything, we first set the time on the node # to be in the past, otherwise things break because the CNode @@ -55,7 +58,8 @@ class MaxUploadTest(BitcoinTestFramework): self.nodes[0].setmocktime(old_time) # Generate some old blocks - self.generate(self.nodes[0], 130) + self.wallet = MiniWallet(self.nodes[0]) + self.generate(self.wallet, 130) # p2p_conns[0] will only request old blocks # p2p_conns[1] will only request new blocks @@ -66,7 +70,7 @@ class MaxUploadTest(BitcoinTestFramework): p2p_conns.append(self.nodes[0].add_p2p_connection(TestP2PConn())) # Now mine a big block - mine_large_block(self, self.nodes[0], self.utxo_cache) + mine_large_block(self, self.wallet, self.nodes[0]) # Store the hash; we'll request this later big_old_block = self.nodes[0].getbestblockhash() @@ -77,7 +81,7 @@ class MaxUploadTest(BitcoinTestFramework): self.nodes[0].setmocktime(int(time.time()) - 2*60*60*24) # Mine one more block, so that the prior block looks old - mine_large_block(self, self.nodes[0], self.utxo_cache) + mine_large_block(self, self.wallet, self.nodes[0]) # We'll be requesting this new block too big_new_block = self.nodes[0].getbestblockhash() diff --git a/test/functional/feature_minchainwork.py b/test/functional/feature_minchainwork.py index 489a729cfc..fb4024b1b0 100755 --- a/test/functional/feature_minchainwork.py +++ b/test/functional/feature_minchainwork.py @@ -17,6 +17,7 @@ only succeeds past a given node once its nMinimumChainWork has been exceeded. import time +from test_framework.p2p import P2PInterface, msg_getheaders from test_framework.test_framework import BitcoinTestFramework from test_framework.util import assert_equal @@ -41,6 +42,9 @@ class MinimumChainWorkTest(BitcoinTestFramework): for i in range(self.num_nodes-1): self.connect_nodes(i+1, i) + # Set clock of node2 2 days ahead, to keep it in IBD during this test. + self.nodes[2].setmocktime(int(time.time()) + 48*60*60) + def run_test(self): # Start building a chain on node0. node2 shouldn't be able to sync until node1's # minchainwork is exceeded @@ -71,6 +75,15 @@ class MinimumChainWorkTest(BitcoinTestFramework): assert self.nodes[1].getbestblockhash() != self.nodes[0].getbestblockhash() assert_equal(self.nodes[2].getblockcount(), starting_blockcount) + self.log.info("Check that getheaders requests to node2 are ignored") + peer = self.nodes[2].add_p2p_connection(P2PInterface()) + msg = msg_getheaders() + msg.locator.vHave = [int(self.nodes[2].getbestblockhash(), 16)] + msg.hashstop = 0 + peer.send_and_ping(msg) + time.sleep(5) + 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) @@ -85,5 +98,21 @@ class MinimumChainWorkTest(BitcoinTestFramework): self.sync_all() self.log.info(f"Blockcounts: {[n.getblockcount() for n in self.nodes]}") + self.log.info("Test that getheaders requests to node2 are not ignored") + peer.send_and_ping(msg) + assert "headers" in peer.last_message + + # Verify that node2 is in fact still in IBD (otherwise this test may + # not be exercising the logic we want!) + assert_equal(self.nodes[2].getblockchaininfo()['initialblockdownload'], True) + + self.log.info("Test -minimumchainwork with a non-hex value") + self.stop_node(0) + self.nodes[0].assert_start_raises_init_error( + ["-minimumchainwork=test"], + expected_msg='Error: Invalid non-hex (test) minimum chain work value specified', + ) + + if __name__ == '__main__': MinimumChainWorkTest().main() 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 fb0f6d7cb7..dd3cdc96ca 100755 --- a/test/functional/feature_proxy.py +++ b/test/functional/feature_proxy.py @@ -30,22 +30,25 @@ addnode connect to generic DNS name addnode connect to a CJDNS address - Test getnetworkinfo for each node + +- Test passing invalid -proxy +- Test passing invalid -onion +- Test passing invalid -i2psam +- Test passing -onlynet=onion without -proxy or -onion +- Test passing -onlynet=onion with -onion=0 and with -noonion +- Test passing unknown -onlynet """ 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" @@ -68,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: @@ -234,7 +237,15 @@ class ProxyTest(BitcoinTestFramework): return r self.log.info("Test RPC getnetworkinfo") - n0 = networks_dict(self.nodes[0].getnetworkinfo()) + nodes_network_info = [] + + self.log.debug("Test that setting -proxy disables local address discovery, i.e. -discover=0") + for node in self.nodes: + network_info = node.getnetworkinfo() + assert_equal(network_info["localaddresses"], []) + nodes_network_info.append(network_info) + + n0 = networks_dict(nodes_network_info[0]) assert_equal(NETWORKS, n0.keys()) for net in NETWORKS: if net == NET_I2P: @@ -249,7 +260,7 @@ class ProxyTest(BitcoinTestFramework): assert_equal(n0['i2p']['reachable'], False) assert_equal(n0['cjdns']['reachable'], False) - n1 = networks_dict(self.nodes[1].getnetworkinfo()) + n1 = networks_dict(nodes_network_info[1]) assert_equal(NETWORKS, n1.keys()) for net in ['ipv4', 'ipv6']: assert_equal(n1[net]['proxy'], f'{self.conf1.addr[0]}:{self.conf1.addr[1]}') @@ -261,14 +272,15 @@ class ProxyTest(BitcoinTestFramework): assert_equal(n1['i2p']['proxy_randomize_credentials'], False) assert_equal(n1['i2p']['reachable'], True) - n2 = networks_dict(self.nodes[2].getnetworkinfo()) + n2 = networks_dict(nodes_network_info[2]) assert_equal(NETWORKS, n2.keys()) + proxy = f'{self.conf2.addr[0]}:{self.conf2.addr[1]}' for net in NETWORKS: if net == NET_I2P: expected_proxy = '' expected_randomize = False else: - expected_proxy = f'{self.conf2.addr[0]}:{self.conf2.addr[1]}' + expected_proxy = proxy expected_randomize = True assert_equal(n2[net]['proxy'], expected_proxy) assert_equal(n2[net]['proxy_randomize_credentials'], expected_randomize) @@ -277,20 +289,18 @@ class ProxyTest(BitcoinTestFramework): assert_equal(n2['cjdns']['reachable'], False) if self.have_ipv6: - n3 = networks_dict(self.nodes[3].getnetworkinfo()) + n3 = networks_dict(nodes_network_info[3]) assert_equal(NETWORKS, n3.keys()) + proxy = f'[{self.conf3.addr[0]}]:{self.conf3.addr[1]}' for net in NETWORKS: - if net == NET_I2P or net == NET_ONION: - expected_proxy = '' - else: - expected_proxy = f'[{self.conf3.addr[0]}]:{self.conf3.addr[1]}' + expected_proxy = '' if net == NET_I2P or net == NET_ONION else proxy assert_equal(n3[net]['proxy'], expected_proxy) assert_equal(n3[net]['proxy_randomize_credentials'], False) assert_equal(n3['onion']['reachable'], False) assert_equal(n3['i2p']['reachable'], False) assert_equal(n3['cjdns']['reachable'], False) - n4 = networks_dict(self.nodes[4].getnetworkinfo()) + n4 = networks_dict(nodes_network_info[4]) assert_equal(NETWORKS, n4.keys()) for net in NETWORKS: if net == NET_I2P: @@ -305,6 +315,42 @@ class ProxyTest(BitcoinTestFramework): assert_equal(n4['i2p']['reachable'], False) assert_equal(n4['cjdns']['reachable'], True) + self.stop_node(1) + + self.log.info("Test passing invalid -proxy raises expected init error") + self.nodes[1].extra_args = ["-proxy=abc:def"] + msg = "Error: Invalid -proxy address or hostname: 'abc:def'" + self.nodes[1].assert_start_raises_init_error(expected_msg=msg) + + self.log.info("Test passing invalid -onion raises expected init error") + self.nodes[1].extra_args = ["-onion=xyz:abc"] + msg = "Error: Invalid -onion address or hostname: 'xyz:abc'" + self.nodes[1].assert_start_raises_init_error(expected_msg=msg) + + self.log.info("Test passing invalid -i2psam raises expected init error") + self.nodes[1].extra_args = ["-i2psam=def:xyz"] + 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.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") + 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 unknown network to -onlynet raises expected init error") + self.nodes[1].extra_args = ["-onlynet=abc"] + msg = "Error: Unknown network specified in -onlynet: 'abc'" + self.nodes[1].assert_start_raises_init_error(expected_msg=msg) + if __name__ == '__main__': ProxyTest().main() diff --git a/test/functional/feature_pruning.py b/test/functional/feature_pruning.py index ba3c5053cb..7dbeccbc09 100755 --- a/test/functional/feature_pruning.py +++ b/test/functional/feature_pruning.py @@ -125,6 +125,7 @@ class PruneTest(BitcoinTestFramework): self.sync_blocks(self.nodes[0:5]) def test_invalid_command_line_options(self): + self.stop_node(0) self.nodes[0].assert_start_raises_init_error( expected_msg='Error: Prune cannot be configured with a negative value.', extra_args=['-prune=-1'], @@ -138,8 +139,8 @@ class PruneTest(BitcoinTestFramework): extra_args=['-prune=550', '-txindex'], ) self.nodes[0].assert_start_raises_init_error( - expected_msg='Error: Prune mode is incompatible with -coinstatsindex.', - extra_args=['-prune=550', '-coinstatsindex'], + expected_msg='Error: Prune mode is incompatible with -reindex-chainstate. Use full -reindex instead.', + extra_args=['-prune=550', '-reindex-chainstate'], ) def test_height_min(self): @@ -290,7 +291,7 @@ class PruneTest(BitcoinTestFramework): def prune(index): ret = node.pruneblockchain(height=height(index)) - assert_equal(ret, node.getblockchaininfo()['pruneheight']) + assert_equal(ret + 1, node.getblockchaininfo()['pruneheight']) def has_block(index): return os.path.isfile(os.path.join(self.nodes[node_number].datadir, self.chain, "blocks", f"blk{index:05}.dat")) diff --git a/test/functional/feature_rbf.py b/test/functional/feature_rbf.py index f0ed914461..2237a4171e 100755 --- a/test/functional/feature_rbf.py +++ b/test/functional/feature_rbf.py @@ -4,44 +4,36 @@ # 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 MAX_REPLACEMENT_LIMIT = 100 class ReplaceByFeeTest(BitcoinTestFramework): def set_test_params(self): - self.num_nodes = 1 + self.num_nodes = 2 self.extra_args = [ [ - "-acceptnonstdtxn=1", "-maxorphantx=1000", "-limitancestorcount=50", "-limitancestorsize=101", "-limitdescendantcount=200", "-limitdescendantsize=101", ], + # second node has default mempool parameters + [ + ], ] self.supports_cli = False @@ -73,6 +65,9 @@ class ReplaceByFeeTest(BitcoinTestFramework): self.log.info("Running test too many replacements...") self.test_too_many_replacements() + self.log.info("Running test too many replacements using default mempool params...") + self.test_too_many_replacements_with_default_mempool_params() + self.log.info("Running test opt-in...") self.test_opt_in() @@ -88,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: @@ -109,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) @@ -154,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) @@ -199,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): @@ -226,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. @@ -254,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) @@ -290,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) @@ -328,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) @@ -355,67 +348,149 @@ 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 + evictions) without having to rely on non-default mempool parameters. + + In order to do this, create a number of "root" UTXOs, and then hang + enough transactions off of each root UTXO to exceed the MAX_REPLACEMENT_LIMIT. + Then create a conflicting RBF replacement transaction. + """ + normal_node = self.nodes[1] + wallet = MiniWallet(normal_node) + wallet.rescan_utxos() + # Clear mempools to avoid cross-node sync failure. + for node in self.nodes: + self.generate(node, 1) + + # This has to be chosen so that the total number of transactions can exceed + # MAX_REPLACEMENT_LIMIT without having any one tx graph run into the descendant + # limit; 10 works. + num_tx_graphs = 10 + + # (Number of transactions per graph, BIP125 rule 5 failure expected) + cases = [ + # Test the base case of evicting fewer than MAX_REPLACEMENT_LIMIT + # transactions. + ((MAX_REPLACEMENT_LIMIT // num_tx_graphs) - 1, False), + + # Test hitting the rule 5 eviction limit. + (MAX_REPLACEMENT_LIMIT // num_tx_graphs, True), + ] + + for (txs_per_graph, failure_expected) in cases: + self.log.debug(f"txs_per_graph: {txs_per_graph}, failure: {failure_expected}") + # "Root" utxos of each txn graph that we will attempt to double-spend with + # an RBF replacement. + root_utxos = [] + + # For each root UTXO, create a package that contains the spend of that + # UTXO and `txs_per_graph` children tx. + for graph_num in range(num_tx_graphs): + root_utxos.append(wallet.get_utxo()) + + optin_parent_tx = wallet.send_self_transfer_multi( + from_node=normal_node, + sequence=MAX_BIP125_RBF_SEQUENCE, + utxos_to_spend=[root_utxos[graph_num]], + num_outputs=txs_per_graph, + ) + assert_equal(True, normal_node.getmempoolentry(optin_parent_tx['txid'])['bip125-replaceable']) + new_utxos = optin_parent_tx['new_utxos'] + + for utxo in new_utxos: + # Create spends for each output from the "root" of this graph. + child_tx = wallet.send_self_transfer( + from_node=normal_node, + utxo_to_spend=utxo, + ) + + assert normal_node.getmempoolentry(child_tx['txid']) + + num_txs_invalidated = len(root_utxos) + (num_tx_graphs * txs_per_graph) + + if failure_expected: + assert num_txs_invalidated > MAX_REPLACEMENT_LIMIT + else: + assert num_txs_invalidated <= MAX_REPLACEMENT_LIMIT + + # Now attempt to submit a tx that double-spends all the root tx inputs, which + # would invalidate `num_txs_invalidated` transactions. + tx_hex = wallet.create_self_transfer_multi( + utxos_to_spend=root_utxos, + fee_per_output=10_000_000, # absurdly high feerate + )["hex"] + + if failure_expected: + assert_raises_rpc_error( + -26, "too many potential replacements", normal_node.sendrawtransaction, tx_hex, 0) + else: + txid = normal_node.sendrawtransaction(tx_hex, 0) + assert normal_node.getmempoolentry(txid) + + # Clear the mempool once finished, and rescan the other nodes' wallet + # to account for the spends we've made on `normal_node`. + self.generate(normal_node, 1) + self.wallet.rescan_utxos() + def test_opt_in(self): """Replacing should only work if orig tx opted in""" 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) @@ -423,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) @@ -442,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 @@ -478,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) @@ -504,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() @@ -557,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'), ) @@ -590,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 @@ -619,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 6d7f1def88..7f2a615be1 100755 --- a/test/functional/feature_segwit.py +++ b/test/functional/feature_segwit.py @@ -86,18 +86,18 @@ class SegWitTest(BitcoinTestFramework): [ "-acceptnonstdtxn=1", "-rpcserialversion=0", - "-testactivationheight=segwit@432", + "-testactivationheight=segwit@165", "-addresstype=legacy", ], [ "-acceptnonstdtxn=1", "-rpcserialversion=1", - "-testactivationheight=segwit@432", + "-testactivationheight=segwit@165", "-addresstype=legacy", ], [ "-acceptnonstdtxn=1", - "-testactivationheight=segwit@432", + "-testactivationheight=segwit@165", "-addresstype=legacy", ], ] @@ -117,12 +117,6 @@ class SegWitTest(BitcoinTestFramework): assert_equal(len(node.getblock(block[0])["tx"]), 2) self.sync_blocks() - def skip_mine(self, node, txid, sign, redeem_script=""): - send_to_witness(1, node, getutxo(txid), self.pubkey[0], False, Decimal("49.998"), sign, redeem_script) - block = self.generate(node, 1) - assert_equal(len(node.getblock(block[0])["tx"]), 1) - self.sync_blocks() - def fail_accept(self, node, error_msg, txid, sign, redeem_script=""): assert_raises_rpc_error(-26, error_msg, send_to_witness, use_p2wsh=1, node=node, utxo=getutxo(txid), pubkey=self.pubkey[0], encode_p2sh=False, amount=Decimal("49.998"), sign=sign, insert_redeem_script=redeem_script) @@ -197,23 +191,21 @@ class SegWitTest(BitcoinTestFramework): assert_equal(self.nodes[1].getbalance(), 20 * Decimal("49.999")) assert_equal(self.nodes[2].getbalance(), 20 * Decimal("49.999")) - self.generate(self.nodes[0], 260) # block 423 - - self.log.info("Verify witness txs are skipped for mining before the fork") - self.skip_mine(self.nodes[2], wit_ids[NODE_2][P2WPKH][0], True) # block 424 - self.skip_mine(self.nodes[2], wit_ids[NODE_2][P2WSH][0], True) # block 425 - self.skip_mine(self.nodes[2], p2sh_ids[NODE_2][P2WPKH][0], True) # block 426 - self.skip_mine(self.nodes[2], p2sh_ids[NODE_2][P2WSH][0], True) # block 427 - self.log.info("Verify unsigned p2sh witness txs without a redeem script are invalid") self.fail_accept(self.nodes[2], "mandatory-script-verify-flag-failed (Operation not valid with the current stack size)", p2sh_ids[NODE_2][P2WPKH][1], sign=False) self.fail_accept(self.nodes[2], "mandatory-script-verify-flag-failed (Operation not valid with the current stack size)", p2sh_ids[NODE_2][P2WSH][1], sign=False) - self.generate(self.nodes[2], 4) # blocks 428-431 + self.generate(self.nodes[0], 1) # block 164 + + self.log.info("Verify witness txs are mined as soon as segwit activates") + + send_to_witness(1, self.nodes[2], getutxo(wit_ids[NODE_2][P2WPKH][0]), self.pubkey[0], encode_p2sh=False, amount=Decimal("49.998"), sign=True) + send_to_witness(1, self.nodes[2], getutxo(wit_ids[NODE_2][P2WSH][0]), self.pubkey[0], encode_p2sh=False, amount=Decimal("49.998"), sign=True) + send_to_witness(1, self.nodes[2], getutxo(p2sh_ids[NODE_2][P2WPKH][0]), self.pubkey[0], encode_p2sh=False, amount=Decimal("49.998"), sign=True) + send_to_witness(1, self.nodes[2], getutxo(p2sh_ids[NODE_2][P2WSH][0]), self.pubkey[0], encode_p2sh=False, amount=Decimal("49.998"), sign=True) - self.log.info("Verify previous witness txs skipped for mining can now be mined") assert_equal(len(self.nodes[2].getrawmempool()), 4) - blockhash = self.generate(self.nodes[2], 1)[0] # block 432 (first block with new rules; 432 = 144 * 3) + blockhash = self.generate(self.nodes[2], 1)[0] # block 165 (first block with new rules) assert_equal(len(self.nodes[2].getrawmempool()), 0) segwit_tx_list = self.nodes[2].getblock(blockhash)["tx"] assert_equal(len(segwit_tx_list), 5) @@ -255,10 +247,10 @@ class SegWitTest(BitcoinTestFramework): self.fail_accept(self.nodes[2], 'non-mandatory-script-verify-flag (Witness program was passed an empty witness)', p2sh_ids[NODE_2][P2WSH][2], sign=False, redeem_script=witness_script(True, self.pubkey[2])) self.log.info("Verify default node can now use witness txs") - self.success_mine(self.nodes[0], wit_ids[NODE_0][P2WPKH][0], True) # block 432 - self.success_mine(self.nodes[0], wit_ids[NODE_0][P2WSH][0], True) # block 433 - self.success_mine(self.nodes[0], p2sh_ids[NODE_0][P2WPKH][0], True) # block 434 - self.success_mine(self.nodes[0], p2sh_ids[NODE_0][P2WSH][0], True) # block 435 + self.success_mine(self.nodes[0], wit_ids[NODE_0][P2WPKH][0], True) + self.success_mine(self.nodes[0], wit_ids[NODE_0][P2WSH][0], True) + self.success_mine(self.nodes[0], p2sh_ids[NODE_0][P2WPKH][0], True) + self.success_mine(self.nodes[0], p2sh_ids[NODE_0][P2WSH][0], True) self.log.info("Verify sigops are counted in GBT with BIP141 rules after the fork") txid = self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), 1) @@ -617,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 3e3d4b3c77..67cdc5ca32 100755 --- a/test/functional/feature_taproot.py +++ b/test/functional/feature_taproot.py @@ -10,7 +10,6 @@ from test_framework.blocktools import ( create_block, add_witness_commitment, MAX_BLOCK_SIGOPS_WEIGHT, - NORMAL_GBT_REQUEST_PARAMS, WITNESS_SCALE_FACTOR, ) from test_framework.messages import ( @@ -92,14 +91,17 @@ 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, - program_to_witness + program_to_witness, ) from collections import OrderedDict, namedtuple -from enum import Enum from io import BytesIO import json import hashlib @@ -458,7 +460,7 @@ def spend(tx, idx, utxos, **kwargs): # Each spender is a tuple of: # - A scriptPubKey which is to be spent from (CScript) # - A comment describing the test (string) -# - Whether the spending (on itself) is expected to be standard (Enum.Standard) +# - Whether the spending (on itself) is expected to be standard (bool) # - A tx-signing lambda returning (scriptsig, witness_stack), taking as inputs: # - A transaction to sign (CTransaction) # - An input position (int) @@ -470,14 +472,9 @@ def spend(tx, idx, utxos, **kwargs): # - Whether this test demands being placed in a txin with no corresponding txout (for testing SIGHASH_SINGLE behavior) Spender = namedtuple("Spender", "script,comment,is_standard,sat_function,err_msg,sigops_weight,no_fail,need_vin_vout_mismatch") -# The full node versions that treat the tx standard. -# ALL means any version -# V23 means the major version 23.0 and any later version -# NONE means no version -Standard = Enum('Standard', 'ALL V23 NONE') -def make_spender(comment, *, tap=None, witv0=False, script=None, pkh=None, p2sh=False, spk_mutate_pre_p2sh=None, failure=None, standard=Standard.ALL, err_msg=None, sigops_weight=0, need_vin_vout_mismatch=False, **kwargs): +def make_spender(comment, *, tap=None, witv0=False, script=None, pkh=None, p2sh=False, spk_mutate_pre_p2sh=None, failure=None, standard=True, err_msg=None, sigops_weight=0, need_vin_vout_mismatch=False, **kwargs): """Helper for constructing Spender objects using the context signing framework. * tap: a TaprootInfo object (see taproot_construct), for Taproot spends (cannot be combined with pkh, witv0, or script) @@ -487,18 +484,13 @@ def make_spender(comment, *, tap=None, witv0=False, script=None, pkh=None, p2sh= * p2sh: whether the output is P2SH wrapper (this is supported even for Taproot, where it makes the output unencumbered) * spk_mutate_pre_psh: a callable to be applied to the script (before potentially P2SH-wrapping it) * failure: a dict of entries to override in the context when intentionally failing to spend (if None, no_fail will be set) - * standard: whether the (valid version of) spending is expected to be standard (True is mapped to Standard.ALL, False is mapped to Standard.NONE) + * standard: whether the (valid version of) spending is expected to be standard * err_msg: a string with an expected error message for failure (or None, if not cared about) * sigops_weight: the pre-taproot sigops weight consumed by a successful spend * need_vin_vout_mismatch: whether this test requires being tested in a transaction input that has no corresponding transaction output. """ - if standard == True: - standard = Standard.ALL - elif standard == False: - standard = Standard.NONE - conf = dict() # Compute scriptPubKey and set useful defaults based on the inputs. @@ -578,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): @@ -1143,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. @@ -1168,29 +1162,21 @@ def spenders_taproot_active(): return spenders -def spenders_taproot_inactive(): - """Spenders for testing that pre-activation Taproot rules don't apply.""" + +def spenders_taproot_nonstandard(): + """Spenders for testing that post-activation Taproot rules may be nonstandard.""" spenders = [] sec = generate_privkey() pub, _ = compute_xonly_pubkey(sec) scripts = [ - ("pk", CScript([pub, OP_CHECKSIG])), ("future_leaf", CScript([pub, OP_CHECKSIG]), 0xc2), ("op_success", CScript([pub, OP_CHECKSIG, OP_0, OP_IF, CScriptOp(0x50), OP_ENDIF])), ] tap = taproot_construct(pub, scripts) - # Test that keypath spending is valid & non-standard, regardless of validity. - add_spender(spenders, "inactive/keypath_valid", key=sec, tap=tap, standard=Standard.V23) - add_spender(spenders, "inactive/keypath_invalidsig", key=sec, tap=tap, standard=False, sighash=bitflipper(default_sighash)) - add_spender(spenders, "inactive/keypath_empty", key=sec, tap=tap, standard=False, witness=[]) - - # Same for scriptpath spending (and features like annex, leaf versions, or OP_SUCCESS don't change this) - add_spender(spenders, "inactive/scriptpath_valid", key=sec, tap=tap, leaf="pk", standard=Standard.V23, inputs=[getter("sign")]) - add_spender(spenders, "inactive/scriptpath_invalidsig", key=sec, tap=tap, leaf="pk", standard=False, inputs=[getter("sign")], sighash=bitflipper(default_sighash)) - add_spender(spenders, "inactive/scriptpath_invalidcb", key=sec, tap=tap, leaf="pk", standard=False, inputs=[getter("sign")], controlblock=bitflipper(default_controlblock)) + # Test that features like annex, leaf versions, or OP_SUCCESS are valid but non-standard add_spender(spenders, "inactive/scriptpath_valid_unkleaf", key=sec, tap=tap, leaf="future_leaf", standard=False, inputs=[getter("sign")]) add_spender(spenders, "inactive/scriptpath_invalid_unkleaf", key=sec, tap=tap, leaf="future_leaf", standard=False, inputs=[getter("sign")], sighash=bitflipper(default_sighash)) add_spender(spenders, "inactive/scriptpath_valid_opsuccess", key=sec, tap=tap, leaf="op_success", standard=False, inputs=[getter("sign")]) @@ -1218,7 +1204,7 @@ def dump_json_test(tx, input_utxos, idx, success, failure): # The "final" field indicates that a spend should be always valid, even with more validation flags enabled # than the listed ones. Use standardness as a proxy for this (which gives a conservative underestimate). - if spender.is_standard == Standard.ALL: + if spender.is_standard: fields.append(("final", True)) def dump_witness(wit): @@ -1245,31 +1231,14 @@ class TaprootTest(BitcoinTestFramework): def add_options(self, parser): parser.add_argument("--dumptests", dest="dump_tests", default=False, action="store_true", help="Dump generated test cases to directory set by TEST_DUMP_DIR environment variable") - parser.add_argument("--previous_release", dest="previous_release", default=False, action="store_true", - help="Use a previous release as taproot-inactive node") def skip_test_if_missing_module(self): self.skip_if_no_wallet() - if self.options.previous_release: - self.skip_if_no_previous_releases() def set_test_params(self): - self.num_nodes = 2 + self.num_nodes = 1 self.setup_clean_chain = True - # Node 0 has Taproot inactive, Node 1 active. - self.extra_args = [["-par=1"], ["-par=1"]] - if self.options.previous_release: - self.wallet_names = [None, self.default_wallet_name] - else: - self.extra_args[0].append("-vbparams=taproot:1:1") - - def setup_nodes(self): - self.add_nodes(self.num_nodes, self.extra_args, versions=[ - 200100 if self.options.previous_release else None, - None, - ]) - self.start_nodes() - self.import_deterministic_coinbase_privkeys() + self.extra_args = [["-par=1"]] def block_submit(self, node, txs, msg, err_msg, cb_pubkey=None, fees=0, sigops_weight=0, witness=False, accept=False): @@ -1483,11 +1452,10 @@ class TaprootTest(BitcoinTestFramework): for i in range(len(input_utxos)): tx.vin[i].scriptSig = input_data[i][i != fail_input][0] tx.wit.vtxinwit[i].scriptWitness.stack = input_data[i][i != fail_input][1] - taproot_spend_policy = Standard.V23 if node.version is None else Standard.ALL # Submit to mempool to check standardness is_standard_tx = ( fail_input is None # Must be valid to be standard - and (all(utxo.spender.is_standard == Standard.ALL or utxo.spender.is_standard == taproot_spend_policy for utxo in input_utxos)) # All inputs must be standard + and (all(utxo.spender.is_standard for utxo in input_utxos)) # All inputs must be standard and tx.nVersion >= 1 # The tx version must be standard and tx.nVersion <= 2) tx.rehash() @@ -1514,7 +1482,7 @@ class TaprootTest(BitcoinTestFramework): self.log.info("Unit test scenario...") # Deterministically mine coins to OP_TRUE in block 1 - assert self.nodes[1].getblockcount() == 0 + assert_equal(self.nodes[0].getblockcount(), 0) coinbase = CTransaction() coinbase.nVersion = 1 coinbase.vin = [CTxIn(COutPoint(0, 0xffffffff), CScript([OP_1, OP_1]), SEQUENCE_FINAL)] @@ -1523,12 +1491,12 @@ class TaprootTest(BitcoinTestFramework): coinbase.rehash() assert coinbase.hash == "f60c73405d499a956d3162e3483c395526ef78286458a4cb17b125aa92e49b20" # Mine it - block = create_block(hashprev=int(self.nodes[1].getbestblockhash(), 16), coinbase=coinbase) + block = create_block(hashprev=int(self.nodes[0].getbestblockhash(), 16), coinbase=coinbase) block.rehash() block.solve() - self.nodes[1].submitblock(block.serialize().hex()) - assert self.nodes[1].getblockcount() == 1 - self.generate(self.nodes[1], COINBASE_MATURITY) + self.nodes[0].submitblock(block.serialize().hex()) + assert_equal(self.nodes[0].getblockcount(), 1) + self.generate(self.nodes[0], COINBASE_MATURITY) SEED = 317 VALID_LEAF_VERS = list(range(0xc0, 0x100, 2)) + [0x66, 0x7e, 0x80, 0x84, 0x96, 0x98, 0xba, 0xbc, 0xbe] @@ -1617,8 +1585,8 @@ class TaprootTest(BitcoinTestFramework): spend_info[spk]['prevout'] = COutPoint(tx.sha256, i & 1) spend_info[spk]['utxo'] = CTxOut(val, spk) # Mine those transactions - self.init_blockinfo(self.nodes[1]) - self.block_submit(self.nodes[1], txn, "Crediting txn", None, sigops_weight=10, accept=True) + self.init_blockinfo(self.nodes[0]) + self.block_submit(self.nodes[0], txn, "Crediting txn", None, sigops_weight=10, accept=True) # scriptPubKey computation tests = {"version": 1} @@ -1730,53 +1698,21 @@ class TaprootTest(BitcoinTestFramework): keypath_tests.append(tx_test) assert_equal(hashlib.sha256(tx.serialize()).hexdigest(), "24bab662cb55a7f3bae29b559f651674c62bcc1cd442d44715c0133939107b38") # Mine the spending transaction - self.block_submit(self.nodes[1], [tx], "Spending txn", None, sigops_weight=10000, accept=True, witness=True) + self.block_submit(self.nodes[0], [tx], "Spending txn", None, sigops_weight=10000, accept=True, witness=True) if GEN_TEST_VECTORS: print(json.dumps(tests, indent=4, sort_keys=False)) - def run_test(self): self.gen_test_vectors() - # Post-taproot activation tests go first (pre-taproot tests' blocks are invalid post-taproot). self.log.info("Post-activation tests...") - self.test_spenders(self.nodes[1], spenders_taproot_active(), input_counts=[1, 2, 2, 2, 2, 3]) - - # Re-connect nodes in case they have been disconnected - self.disconnect_nodes(0, 1) - self.connect_nodes(0, 1) - - # Transfer value of the largest 500 coins to pre-taproot node. - addr = self.nodes[0].getnewaddress() - - unsp = self.nodes[1].listunspent() - unsp = sorted(unsp, key=lambda i: i['amount'], reverse=True) - unsp = unsp[:500] - - rawtx = self.nodes[1].createrawtransaction( - inputs=[{ - 'txid': i['txid'], - 'vout': i['vout'] - } for i in unsp], - outputs={addr: sum(i['amount'] for i in unsp)} - ) - rawtx = self.nodes[1].signrawtransactionwithwallet(rawtx)['hex'] - - # Mine a block with the transaction - block = create_block(tmpl=self.nodes[1].getblocktemplate(NORMAL_GBT_REQUEST_PARAMS), txlist=[rawtx]) - add_witness_commitment(block) - block.solve() - assert_equal(None, self.nodes[1].submitblock(block.serialize().hex())) - self.sync_blocks() - - # Pre-taproot activation tests. - self.log.info("Pre-activation tests...") + self.test_spenders(self.nodes[0], spenders_taproot_active(), input_counts=[1, 2, 2, 2, 2, 3]) # Run each test twice; once in isolation, and once combined with others. Testing in isolation # means that the standardness is verified in every test (as combined transactions are only standard # when all their inputs are standard). - self.test_spenders(self.nodes[0], spenders_taproot_inactive(), input_counts=[1]) - self.test_spenders(self.nodes[0], spenders_taproot_inactive(), input_counts=[2, 3]) + self.test_spenders(self.nodes[0], spenders_taproot_nonstandard(), input_counts=[1]) + self.test_spenders(self.nodes[0], spenders_taproot_nonstandard(), input_counts=[2, 3]) if __name__ == '__main__': diff --git a/test/functional/feature_unsupported_utxo_db.py b/test/functional/feature_unsupported_utxo_db.py new file mode 100755 index 0000000000..1c8c08d1d8 --- /dev/null +++ b/test/functional/feature_unsupported_utxo_db.py @@ -0,0 +1,61 @@ +#!/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 that unsupported utxo db causes an init error. + +Previous releases are required by this test, see test/README.md. +""" + +import shutil + +from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import assert_equal + + +class UnsupportedUtxoDbTest(BitcoinTestFramework): + def set_test_params(self): + self.setup_clean_chain = True + self.num_nodes = 2 + + def skip_test_if_missing_module(self): + self.skip_if_no_previous_releases() + + def setup_network(self): + self.add_nodes( + self.num_nodes, + versions=[ + 140300, # Last release with previous utxo db format + None, # For MiniWallet, without migration code + ], + ) + + def run_test(self): + self.log.info("Create previous version (v0.14.3) utxo db") + self.start_node(0) + block = self.generate(self.nodes[0], 1, sync_fun=self.no_op)[-1] + assert_equal(self.nodes[0].getbestblockhash(), block) + assert_equal(self.nodes[0].gettxoutsetinfo()["total_amount"], 50) + self.stop_nodes() + + self.log.info("Check init error") + legacy_utxos_dir = self.nodes[0].chain_path / "chainstate" + legacy_blocks_dir = self.nodes[0].chain_path / "blocks" + recent_utxos_dir = self.nodes[1].chain_path / "chainstate" + recent_blocks_dir = self.nodes[1].chain_path / "blocks" + shutil.copytree(legacy_utxos_dir, recent_utxos_dir) + shutil.copytree(legacy_blocks_dir, recent_blocks_dir) + self.nodes[1].assert_start_raises_init_error( + expected_msg="Error: Unsupported chainstate database format found. " + "Please restart with -reindex-chainstate. " + "This will rebuild the chainstate database.", + ) + + self.log.info("Drop legacy utxo db") + self.start_node(1, extra_args=["-reindex-chainstate"]) + assert_equal(self.nodes[1].getbestblockhash(), block) + assert_equal(self.nodes[1].gettxoutsetinfo()["total_amount"], 50) + + +if __name__ == "__main__": + UnsupportedUtxoDbTest().main() diff --git a/test/functional/feature_utxo_set_hash.py b/test/functional/feature_utxo_set_hash.py index 75180e62a2..4d486bc6f4 100755 --- a/test/functional/feature_utxo_set_hash.py +++ b/test/functional/feature_utxo_set_hash.py @@ -69,8 +69,8 @@ class UTXOSetHashTest(BitcoinTestFramework): assert_equal(finalized[::-1].hex(), node_muhash) self.log.info("Test deterministic UTXO set hash results") - assert_equal(node.gettxoutsetinfo()['hash_serialized_2'], "3a570529b4c32e77268de1f81b903c75cc2da53c48df0d125c1e697ba7c8c7b7") - assert_equal(node.gettxoutsetinfo("muhash")['muhash'], "a13e0e70eb8acc786549596e3bc154623f1a5a622ba2f70715f6773ec745f435") + assert_equal(node.gettxoutsetinfo()['hash_serialized_2'], "f9aa4fb5ffd10489b9a6994e70ccf1de8a8bfa2d5f201d9857332e9954b0855d") + assert_equal(node.gettxoutsetinfo("muhash")['muhash'], "d1725b2fe3ef43e55aa4907480aea98d406fc9e0bf8f60169e2305f1fbf5961b") def run_test(self): self.test_muhash_implementation() diff --git a/test/functional/feature_versionbits_warning.py b/test/functional/feature_versionbits_warning.py index e83dd7f446..1572463308 100755 --- a/test/functional/feature_versionbits_warning.py +++ b/test/functional/feature_versionbits_warning.py @@ -58,7 +58,8 @@ class VersionBitsWarningTest(BitcoinTestFramework): def versionbits_in_alert_file(self): """Test that the versionbits warning has been written to the alert file.""" - alert_text = open(self.alert_filename, 'r', encoding='utf8').read() + with open(self.alert_filename, 'r', encoding='utf8') as f: + alert_text = f.read() return VB_PATTERN.search(alert_text) is not None def run_test(self): diff --git a/test/functional/interface_rest.py b/test/functional/interface_rest.py index a3d949c6a8..f36bbda3af 100755 --- a/test/functional/interface_rest.py +++ b/test/functional/interface_rest.py @@ -10,6 +10,7 @@ import http.client from io import BytesIO import json from struct import pack, unpack +import typing import urllib.parse @@ -57,14 +58,21 @@ class RESTTest (BitcoinTestFramework): args.append("-whitelist=noban@127.0.0.1") self.supports_cli = False - def test_rest_request(self, uri, http_method='GET', req_type=ReqType.JSON, body='', status=200, ret_type=RetType.JSON): + def test_rest_request( + self, + uri: str, + http_method: str = 'GET', + req_type: ReqType = ReqType.JSON, + body: str = '', + status: int = 200, + ret_type: RetType = RetType.JSON, + query_params: typing.Dict[str, typing.Any] = None, + ) -> typing.Union[http.client.HTTPResponse, bytes, str, None]: rest_uri = '/rest' + uri - if req_type == ReqType.JSON: - rest_uri += '.json' - elif req_type == ReqType.BIN: - rest_uri += '.bin' - elif req_type == ReqType.HEX: - rest_uri += '.hex' + if req_type in ReqType: + rest_uri += f'.{req_type.name.lower()}' + if query_params: + rest_uri += f'?{urllib.parse.urlencode(query_params)}' conn = http.client.HTTPConnection(self.url.hostname, self.url.port) self.log.debug(f'{http_method} {rest_uri} {body}') @@ -83,6 +91,8 @@ class RESTTest (BitcoinTestFramework): elif ret_type == RetType.JSON: return json.loads(resp.read().decode('utf-8'), parse_float=Decimal) + return None + def run_test(self): self.url = urllib.parse.urlparse(self.nodes[0].url) self.wallet = MiniWallet(self.nodes[0]) @@ -209,16 +219,16 @@ class RESTTest (BitcoinTestFramework): self.generate(self.nodes[0], 1) # generate block to not affect upcoming tests - self.log.info("Test the /block, /blockhashbyheight and /headers URIs") + self.log.info("Test the /block, /blockhashbyheight, /headers, and /blockfilterheaders URIs") bb_hash = self.nodes[0].getbestblockhash() # Check result if block does not exists - assert_equal(self.test_rest_request(f"/headers/1/{UNKNOWN_PARAM}"), []) + assert_equal(self.test_rest_request(f"/headers/{UNKNOWN_PARAM}", query_params={"count": 1}), []) self.test_rest_request(f"/block/{UNKNOWN_PARAM}", status=404, ret_type=RetType.OBJ) # Check result if block is not in the active chain self.nodes[0].invalidateblock(bb_hash) - assert_equal(self.test_rest_request(f'/headers/1/{bb_hash}'), []) + assert_equal(self.test_rest_request(f'/headers/{bb_hash}', query_params={'count': 1}), []) self.test_rest_request(f'/block/{bb_hash}') self.nodes[0].reconsiderblock(bb_hash) @@ -228,7 +238,7 @@ class RESTTest (BitcoinTestFramework): response_bytes = response.read() # Compare with block header - response_header = self.test_rest_request(f"/headers/1/{bb_hash}", req_type=ReqType.BIN, ret_type=RetType.OBJ) + response_header = self.test_rest_request(f"/headers/{bb_hash}", req_type=ReqType.BIN, ret_type=RetType.OBJ, query_params={"count": 1}) assert_equal(int(response_header.getheader('content-length')), BLOCK_HEADER_SIZE) response_header_bytes = response_header.read() assert_equal(response_bytes[:BLOCK_HEADER_SIZE], response_header_bytes) @@ -240,7 +250,7 @@ class RESTTest (BitcoinTestFramework): assert_equal(response_bytes.hex().encode(), response_hex_bytes) # Compare with hex block header - response_header_hex = self.test_rest_request(f"/headers/1/{bb_hash}", req_type=ReqType.HEX, ret_type=RetType.OBJ) + response_header_hex = self.test_rest_request(f"/headers/{bb_hash}", req_type=ReqType.HEX, ret_type=RetType.OBJ, query_params={"count": 1}) assert_greater_than(int(response_header_hex.getheader('content-length')), BLOCK_HEADER_SIZE*2) response_header_hex_bytes = response_header_hex.read(BLOCK_HEADER_SIZE*2) assert_equal(response_bytes[:BLOCK_HEADER_SIZE].hex().encode(), response_header_hex_bytes) @@ -267,7 +277,7 @@ class RESTTest (BitcoinTestFramework): self.test_rest_request("/blockhashbyheight/", ret_type=RetType.OBJ, status=400) # Compare with json block header - json_obj = self.test_rest_request(f"/headers/1/{bb_hash}") + json_obj = self.test_rest_request(f"/headers/{bb_hash}", query_params={"count": 1}) assert_equal(len(json_obj), 1) # ensure that there is one header in the json response assert_equal(json_obj[0]['hash'], bb_hash) # request/response hash should be the same @@ -278,9 +288,9 @@ class RESTTest (BitcoinTestFramework): # See if we can get 5 headers in one response self.generate(self.nodes[1], 5) - json_obj = self.test_rest_request(f"/headers/5/{bb_hash}") + 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/5/{bb_hash}") + json_obj = self.test_rest_request(f"/blockfilterheaders/basic/{bb_hash}", query_params={"count": 5}) first_filter_header = json_obj[0] assert_equal(len(json_obj), 5) # now we should have 5 filter header objects json_obj = self.test_rest_request(f"/blockfilter/basic/{bb_hash}") @@ -290,11 +300,17 @@ class RESTTest (BitcoinTestFramework): assert_equal(first_filter_header, rpc_blockfilter['header']) assert_equal(json_obj['filter'], rpc_blockfilter['filter']) + # Test blockfilterheaders with an invalid hash and filtertype + resp = self.test_rest_request(f"/blockfilterheaders/{INVALID_PARAM}/{bb_hash}", ret_type=RetType.OBJ, status=400) + assert_equal(resp.read().decode('utf-8').rstrip(), f"Unknown filtertype {INVALID_PARAM}") + resp = self.test_rest_request(f"/blockfilterheaders/basic/{INVALID_PARAM}", ret_type=RetType.OBJ, status=400) + assert_equal(resp.read().decode('utf-8').rstrip(), f"Invalid hash: {INVALID_PARAM}") + # Test number parsing for num in ['5a', '-5', '0', '2001', '99999999999999999999999999999999999']: assert_equal( bytes(f'Header count is invalid or out of acceptable range (1-2000): {num}\r\n', 'ascii'), - self.test_rest_request(f"/headers/{num}/{bb_hash}", ret_type=RetType.BYTES, status=400), + self.test_rest_request(f"/headers/{bb_hash}", ret_type=RetType.BYTES, status=400, query_params={"count": num}), ) self.log.info("Test tx inclusion in the /mempool and /block URIs") @@ -314,8 +330,15 @@ class RESTTest (BitcoinTestFramework): # the size of the memory pool should be greater than 3x ~100 bytes assert_greater_than(json_obj['bytes'], 300) + mempool_info = self.nodes[0].getmempoolinfo() + assert_equal(json_obj, mempool_info) + # Check that there are our submitted transactions in the TX memory pool json_obj = self.test_rest_request("/mempool/contents") + raw_mempool_verbose = self.nodes[0].getrawmempool(verbose=True) + + assert_equal(json_obj, raw_mempool_verbose) + for i, tx in enumerate(txs): assert tx in json_obj assert_equal(json_obj[tx]['spentby'], txs[i + 1:i + 2]) @@ -351,6 +374,15 @@ class RESTTest (BitcoinTestFramework): json_obj = self.test_rest_request("/chaininfo") assert_equal(json_obj['bestblockhash'], bb_hash) + # Compare with normal RPC getblockchaininfo response + blockchain_info = self.nodes[0].getblockchaininfo() + assert_equal(blockchain_info, json_obj) + + # Test compatibility of deprecated and newer endpoints + self.log.info("Test compatibility of deprecated and newer endpoints") + assert_equal(self.test_rest_request(f"/headers/{bb_hash}", query_params={"count": 1}), self.test_rest_request(f"/headers/1/{bb_hash}")) + assert_equal(self.test_rest_request(f"/blockfilterheaders/basic/{bb_hash}", query_params={"count": 1}), self.test_rest_request(f"/blockfilterheaders/basic/5/{bb_hash}")) + if __name__ == '__main__': RESTTest().main() diff --git a/test/functional/interface_usdt_coinselection.py b/test/functional/interface_usdt_coinselection.py new file mode 100755 index 0000000000..ef32feda99 --- /dev/null +++ b/test/functional/interface_usdt_coinselection.py @@ -0,0 +1,208 @@ +#!/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. + +""" Tests the coin_selection:* tracepoint API interface. + See https://github.com/bitcoin/bitcoin/blob/master/doc/tracing.md#context-coin_selection +""" + +# Test will be skipped if we don't have bcc installed +try: + from bcc import BPF, USDT # type: ignore[import] +except ImportError: + pass +from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import ( + assert_equal, + assert_greater_than, + assert_raises_rpc_error, +) + +coinselection_tracepoints_program = """ +#include <uapi/linux/ptrace.h> + +#define WALLET_NAME_LENGTH 16 +#define ALGO_NAME_LENGTH 16 + +struct event_data +{ + u8 type; + char wallet_name[WALLET_NAME_LENGTH]; + + // selected coins event + char algo[ALGO_NAME_LENGTH]; + s64 target; + s64 waste; + s64 selected_value; + + // create tx event + bool success; + s64 fee; + s32 change_pos; + + // aps create tx event + bool use_aps; +}; + +BPF_QUEUE(coin_selection_events, struct event_data, 1024); + +int trace_selected_coins(struct pt_regs *ctx) { + struct event_data data; + __builtin_memset(&data, 0, sizeof(data)); + data.type = 1; + bpf_usdt_readarg_p(1, ctx, &data.wallet_name, WALLET_NAME_LENGTH); + bpf_usdt_readarg_p(2, ctx, &data.algo, ALGO_NAME_LENGTH); + bpf_usdt_readarg(3, ctx, &data.target); + bpf_usdt_readarg(4, ctx, &data.waste); + bpf_usdt_readarg(5, ctx, &data.selected_value); + coin_selection_events.push(&data, 0); + return 0; +} + +int trace_normal_create_tx(struct pt_regs *ctx) { + struct event_data data; + __builtin_memset(&data, 0, sizeof(data)); + data.type = 2; + bpf_usdt_readarg_p(1, ctx, &data.wallet_name, WALLET_NAME_LENGTH); + bpf_usdt_readarg(2, ctx, &data.success); + bpf_usdt_readarg(3, ctx, &data.fee); + bpf_usdt_readarg(4, ctx, &data.change_pos); + coin_selection_events.push(&data, 0); + return 0; +} + +int trace_attempt_aps(struct pt_regs *ctx) { + struct event_data data; + __builtin_memset(&data, 0, sizeof(data)); + data.type = 3; + bpf_usdt_readarg_p(1, ctx, &data.wallet_name, WALLET_NAME_LENGTH); + coin_selection_events.push(&data, 0); + return 0; +} + +int trace_aps_create_tx(struct pt_regs *ctx) { + struct event_data data; + __builtin_memset(&data, 0, sizeof(data)); + data.type = 4; + bpf_usdt_readarg_p(1, ctx, &data.wallet_name, WALLET_NAME_LENGTH); + bpf_usdt_readarg(2, ctx, &data.use_aps); + bpf_usdt_readarg(3, ctx, &data.success); + bpf_usdt_readarg(4, ctx, &data.fee); + bpf_usdt_readarg(5, ctx, &data.change_pos); + coin_selection_events.push(&data, 0); + return 0; +} +""" + + +class CoinSelectionTracepointTest(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_platform_not_linux() + self.skip_if_no_bitcoind_tracepoints() + self.skip_if_no_python_bcc() + self.skip_if_no_bpf_permissions() + self.skip_if_no_wallet() + + def get_tracepoints(self, expected_types): + events = [] + try: + for i in range(0, len(expected_types) + 1): + event = self.bpf["coin_selection_events"].pop() + assert_equal(event.wallet_name.decode(), self.default_wallet_name) + assert_equal(event.type, expected_types[i]) + events.append(event) + else: + # If the loop exits successfully instead of throwing a KeyError, then we have had + # more events than expected. There should be no more than len(expected_types) events. + assert False + except KeyError: + assert_equal(len(events), len(expected_types)) + return events + + + def determine_selection_from_usdt(self, events): + success = None + use_aps = None + algo = None + waste = None + change_pos = None + + is_aps = False + sc_events = [] + for event in events: + if event.type == 1: + if not is_aps: + algo = event.algo.decode() + waste = event.waste + sc_events.append(event) + elif event.type == 2: + success = event.success + if not is_aps: + change_pos = event.change_pos + elif event.type == 3: + is_aps = True + elif event.type == 4: + assert is_aps + if event.use_aps: + use_aps = True + assert_equal(len(sc_events), 2) + algo = sc_events[1].algo.decode() + waste = sc_events[1].waste + change_pos = event.change_pos + return success, use_aps, algo, waste, change_pos + + def run_test(self): + self.log.info("hook into the coin_selection tracepoints") + ctx = USDT(pid=self.nodes[0].process.pid) + ctx.enable_probe(probe="coin_selection:selected_coins", fn_name="trace_selected_coins") + ctx.enable_probe(probe="coin_selection:normal_create_tx_internal", fn_name="trace_normal_create_tx") + ctx.enable_probe(probe="coin_selection:attempting_aps_create_tx", fn_name="trace_attempt_aps") + ctx.enable_probe(probe="coin_selection:aps_create_tx_internal", fn_name="trace_aps_create_tx") + self.bpf = BPF(text=coinselection_tracepoints_program, usdt_contexts=[ctx], debug=0) + + self.log.info("Prepare wallets") + self.generate(self.nodes[0], 101) + wallet = self.nodes[0].get_wallet_rpc(self.default_wallet_name) + + self.log.info("Sending a transaction should result in all tracepoints") + # We should have 5 tracepoints in the order: + # 1. selected_coins (type 1) + # 2. normal_create_tx_internal (type 2) + # 3. attempting_aps_create_tx (type 3) + # 4. selected_coins (type 1) + # 5. aps_create_tx_internal (type 4) + wallet.sendtoaddress(wallet.getnewaddress(), 10) + events = self.get_tracepoints([1, 2, 3, 1, 4]) + success, use_aps, algo, waste, change_pos = self.determine_selection_from_usdt(events) + assert_equal(success, True) + assert_greater_than(change_pos, -1) + + self.log.info("Failing to fund results in 1 tracepoint") + # We should have 1 tracepoints in the order + # 1. normal_create_tx_internal (type 2) + assert_raises_rpc_error(-6, "Insufficient funds", wallet.sendtoaddress, wallet.getnewaddress(), 102 * 50) + events = self.get_tracepoints([2]) + success, use_aps, algo, waste, change_pos = self.determine_selection_from_usdt(events) + assert_equal(success, False) + + self.log.info("Explicitly enabling APS results in 2 tracepoints") + # We should have 2 tracepoints in the order + # 1. selected_coins (type 1) + # 2. normal_create_tx_internal (type 2) + wallet.setwalletflag("avoid_reuse") + wallet.sendtoaddress(address=wallet.getnewaddress(), amount=10, avoid_reuse=True) + events = self.get_tracepoints([1, 2]) + success, use_aps, algo, waste, change_pos = self.determine_selection_from_usdt(events) + assert_equal(success, True) + assert_equal(use_aps, None) + + self.bpf.cleanup() + + +if __name__ == '__main__': + CoinSelectionTracepointTest().main() diff --git a/test/functional/interface_usdt_net.py b/test/functional/interface_usdt_net.py new file mode 100755 index 0000000000..2235da702b --- /dev/null +++ b/test/functional/interface_usdt_net.py @@ -0,0 +1,171 @@ +#!/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. + +""" Tests the net:* tracepoint API interface. + See https://github.com/bitcoin/bitcoin/blob/master/doc/tracing.md#context-net +""" + +import ctypes +from io import BytesIO +# Test will be skipped if we don't have bcc installed +try: + from bcc import BPF, USDT # type: ignore[import] +except ImportError: + pass +from test_framework.messages import msg_version +from test_framework.p2p import P2PInterface +from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import assert_equal + +# Tor v3 addresses are 62 chars + 6 chars for the port (':12345'). +MAX_PEER_ADDR_LENGTH = 68 +MAX_PEER_CONN_TYPE_LENGTH = 20 +MAX_MSG_TYPE_LENGTH = 20 +# We won't process messages larger than 150 byte in this test. For reading +# larger messanges see contrib/tracing/log_raw_p2p_msgs.py +MAX_MSG_DATA_LENGTH = 150 + +net_tracepoints_program = """ +#include <uapi/linux/ptrace.h> + +#define MAX_PEER_ADDR_LENGTH {} +#define MAX_PEER_CONN_TYPE_LENGTH {} +#define MAX_MSG_TYPE_LENGTH {} +#define MAX_MSG_DATA_LENGTH {} +""".format( + MAX_PEER_ADDR_LENGTH, + MAX_PEER_CONN_TYPE_LENGTH, + MAX_MSG_TYPE_LENGTH, + MAX_MSG_DATA_LENGTH +) + """ +#define MIN(a,b) ({ __typeof__ (a) _a = (a); __typeof__ (b) _b = (b); _a < _b ? _a : _b; }) + +struct p2p_message +{ + u64 peer_id; + char peer_addr[MAX_PEER_ADDR_LENGTH]; + char peer_conn_type[MAX_PEER_CONN_TYPE_LENGTH]; + char msg_type[MAX_MSG_TYPE_LENGTH]; + u64 msg_size; + u8 msg[MAX_MSG_DATA_LENGTH]; +}; + +BPF_PERF_OUTPUT(inbound_messages); +int trace_inbound_message(struct pt_regs *ctx) { + struct p2p_message msg = {}; + bpf_usdt_readarg(1, ctx, &msg.peer_id); + bpf_usdt_readarg_p(2, ctx, &msg.peer_addr, MAX_PEER_ADDR_LENGTH); + bpf_usdt_readarg_p(3, ctx, &msg.peer_conn_type, MAX_PEER_CONN_TYPE_LENGTH); + bpf_usdt_readarg_p(4, ctx, &msg.msg_type, MAX_MSG_TYPE_LENGTH); + bpf_usdt_readarg(5, ctx, &msg.msg_size); + bpf_usdt_readarg_p(6, ctx, &msg.msg, MIN(msg.msg_size, MAX_MSG_DATA_LENGTH)); + inbound_messages.perf_submit(ctx, &msg, sizeof(msg)); + return 0; +} + +BPF_PERF_OUTPUT(outbound_messages); +int trace_outbound_message(struct pt_regs *ctx) { + struct p2p_message msg = {}; + bpf_usdt_readarg(1, ctx, &msg.peer_id); + bpf_usdt_readarg_p(2, ctx, &msg.peer_addr, MAX_PEER_ADDR_LENGTH); + bpf_usdt_readarg_p(3, ctx, &msg.peer_conn_type, MAX_PEER_CONN_TYPE_LENGTH); + bpf_usdt_readarg_p(4, ctx, &msg.msg_type, MAX_MSG_TYPE_LENGTH); + bpf_usdt_readarg(5, ctx, &msg.msg_size); + bpf_usdt_readarg_p(6, ctx, &msg.msg, MIN(msg.msg_size, MAX_MSG_DATA_LENGTH)); + outbound_messages.perf_submit(ctx, &msg, sizeof(msg)); + return 0; +}; +""" + + +class NetTracepointTest(BitcoinTestFramework): + def set_test_params(self): + self.num_nodes = 1 + + def skip_test_if_missing_module(self): + self.skip_if_platform_not_linux() + self.skip_if_no_bitcoind_tracepoints() + self.skip_if_no_python_bcc() + self.skip_if_no_bpf_permissions() + + def run_test(self): + # Tests the net:inbound_message and net:outbound_message tracepoints + # See https://github.com/bitcoin/bitcoin/blob/master/doc/tracing.md#context-net + + class P2PMessage(ctypes.Structure): + _fields_ = [ + ("peer_id", ctypes.c_uint64), + ("peer_addr", ctypes.c_char * MAX_PEER_ADDR_LENGTH), + ("peer_conn_type", ctypes.c_char * MAX_PEER_CONN_TYPE_LENGTH), + ("msg_type", ctypes.c_char * MAX_MSG_TYPE_LENGTH), + ("msg_size", ctypes.c_uint64), + ("msg", ctypes.c_ubyte * MAX_MSG_DATA_LENGTH), + ] + + def __repr__(self): + return f"P2PMessage(peer={self.peer_id}, addr={self.peer_addr.decode('utf-8')}, conn_type={self.peer_conn_type.decode('utf-8')}, msg_type={self.msg_type.decode('utf-8')}, msg_size={self.msg_size})" + + self.log.info( + "hook into the net:inbound_message and net:outbound_message tracepoints") + 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", + fn_name="trace_outbound_message") + bpf = BPF(text=net_tracepoints_program, usdt_contexts=[ctx], debug=0) + + # The handle_* function is a ctypes callback function called from C. When + # we assert in the handle_* function, the AssertError doesn't propagate + # back to Python. The exception is ignored. We manually count and assert + # that the handle_* functions succeeded. + EXPECTED_INOUTBOUND_VERSION_MSG = 1 + checked_inbound_version_msg = 0 + checked_outbound_version_msg = 0 + + def check_p2p_message(event, inbound): + nonlocal checked_inbound_version_msg, checked_outbound_version_msg + if event.msg_type.decode("utf-8") == "version": + self.log.info( + f"check_p2p_message(): {'inbound' if inbound else 'outbound'} {event}") + peer = self.nodes[0].getpeerinfo()[0] + msg = msg_version() + msg.deserialize(BytesIO(bytes(event.msg[:event.msg_size]))) + assert_equal(peer["id"], event.peer_id, peer["id"]) + assert_equal(peer["addr"], event.peer_addr.decode("utf-8")) + assert_equal(peer["connection_type"], + event.peer_conn_type.decode("utf-8")) + if inbound: + checked_inbound_version_msg += 1 + else: + checked_outbound_version_msg += 1 + + def handle_inbound(_, data, __): + event = ctypes.cast(data, ctypes.POINTER(P2PMessage)).contents + check_p2p_message(event, True) + + def handle_outbound(_, data, __): + event = ctypes.cast(data, ctypes.POINTER(P2PMessage)).contents + check_p2p_message(event, False) + + bpf["inbound_messages"].open_perf_buffer(handle_inbound) + bpf["outbound_messages"].open_perf_buffer(handle_outbound) + + self.log.info("connect a P2P test node to our bitcoind node") + test_node = P2PInterface() + self.nodes[0].add_p2p_connection(test_node) + bpf.perf_buffer_poll(timeout=200) + + self.log.info( + "check that we got both an inbound and outbound version message") + assert_equal(EXPECTED_INOUTBOUND_VERSION_MSG, + checked_inbound_version_msg) + assert_equal(EXPECTED_INOUTBOUND_VERSION_MSG, + checked_outbound_version_msg) + + bpf.cleanup() + + +if __name__ == '__main__': + NetTracepointTest().main() diff --git a/test/functional/interface_usdt_utxocache.py b/test/functional/interface_usdt_utxocache.py new file mode 100755 index 0000000000..2280de1479 --- /dev/null +++ b/test/functional/interface_usdt_utxocache.py @@ -0,0 +1,410 @@ +#!/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. + +""" Tests the utxocache:* tracepoint API interface. + See https://github.com/bitcoin/bitcoin/blob/master/doc/tracing.md#context-utxocache +""" + +import ctypes +# Test will be skipped if we don't have bcc installed +try: + from bcc import BPF, USDT # type: ignore[import] +except ImportError: + pass +from test_framework.messages import COIN +from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import assert_equal +from test_framework.wallet import MiniWallet + +utxocache_changes_program = """ +#include <uapi/linux/ptrace.h> + +typedef signed long long i64; + +struct utxocache_change +{ + char txid[32]; + u32 index; + u32 height; + i64 value; + bool is_coinbase; +}; + +BPF_PERF_OUTPUT(utxocache_add); +int trace_utxocache_add(struct pt_regs *ctx) { + struct utxocache_change add = {}; + bpf_usdt_readarg_p(1, ctx, &add.txid, 32); + bpf_usdt_readarg(2, ctx, &add.index); + bpf_usdt_readarg(3, ctx, &add.height); + bpf_usdt_readarg(4, ctx, &add.value); + bpf_usdt_readarg(5, ctx, &add.is_coinbase); + utxocache_add.perf_submit(ctx, &add, sizeof(add)); + return 0; +} + +BPF_PERF_OUTPUT(utxocache_spent); +int trace_utxocache_spent(struct pt_regs *ctx) { + struct utxocache_change spent = {}; + bpf_usdt_readarg_p(1, ctx, &spent.txid, 32); + bpf_usdt_readarg(2, ctx, &spent.index); + bpf_usdt_readarg(3, ctx, &spent.height); + bpf_usdt_readarg(4, ctx, &spent.value); + bpf_usdt_readarg(5, ctx, &spent.is_coinbase); + utxocache_spent.perf_submit(ctx, &spent, sizeof(spent)); + return 0; +} + +BPF_PERF_OUTPUT(utxocache_uncache); +int trace_utxocache_uncache(struct pt_regs *ctx) { + struct utxocache_change uncache = {}; + bpf_usdt_readarg_p(1, ctx, &uncache.txid, 32); + bpf_usdt_readarg(2, ctx, &uncache.index); + bpf_usdt_readarg(3, ctx, &uncache.height); + bpf_usdt_readarg(4, ctx, &uncache.value); + bpf_usdt_readarg(5, ctx, &uncache.is_coinbase); + utxocache_uncache.perf_submit(ctx, &uncache, sizeof(uncache)); + return 0; +} +""" + +utxocache_flushes_program = """ +#include <uapi/linux/ptrace.h> + +typedef signed long long i64; + +struct utxocache_flush +{ + i64 duration; + u32 mode; + u64 size; + u64 memory; + bool for_prune; +}; + +BPF_PERF_OUTPUT(utxocache_flush); +int trace_utxocache_flush(struct pt_regs *ctx) { + struct utxocache_flush flush = {}; + bpf_usdt_readarg(1, ctx, &flush.duration); + bpf_usdt_readarg(2, ctx, &flush.mode); + bpf_usdt_readarg(3, ctx, &flush.size); + bpf_usdt_readarg(4, ctx, &flush.memory); + bpf_usdt_readarg(5, ctx, &flush.for_prune); + utxocache_flush.perf_submit(ctx, &flush, sizeof(flush)); + return 0; +} +""" + +FLUSHMODE_NAME = { + 0: "NONE", + 1: "IF_NEEDED", + 2: "PERIODIC", + 3: "ALWAYS", +} + + +class UTXOCacheChange(ctypes.Structure): + _fields_ = [ + ("txid", ctypes.c_ubyte * 32), + ("index", ctypes.c_uint32), + ("height", ctypes.c_uint32), + ("value", ctypes.c_uint64), + ("is_coinbase", ctypes.c_bool), + ] + + def __repr__(self): + return f"UTXOCacheChange(outpoint={bytes(self.txid[::-1]).hex()}:{self.index}, height={self.height}, value={self.value}sat, is_coinbase={self.is_coinbase})" + + +class UTXOCacheFlush(ctypes.Structure): + _fields_ = [ + ("duration", ctypes.c_int64), + ("mode", ctypes.c_uint32), + ("size", ctypes.c_uint64), + ("memory", ctypes.c_uint64), + ("for_prune", ctypes.c_bool), + ] + + def __repr__(self): + return f"UTXOCacheFlush(duration={self.duration}, mode={FLUSHMODE_NAME[self.mode]}, size={self.size}, memory={self.memory}, for_prune={self.for_prune})" + + +class UTXOCacheTracepointTest(BitcoinTestFramework): + def set_test_params(self): + self.setup_clean_chain = False + self.num_nodes = 1 + self.extra_args = [["-txindex"]] + + def skip_test_if_missing_module(self): + self.skip_if_platform_not_linux() + self.skip_if_no_bitcoind_tracepoints() + self.skip_if_no_python_bcc() + self.skip_if_no_bpf_permissions() + + def run_test(self): + self.wallet = MiniWallet(self.nodes[0]) + self.generate(self.wallet, 101) + + self.test_uncache() + self.test_add_spent() + self.test_flush() + + def test_uncache(self): + """ Tests the utxocache:uncache tracepoint API. + https://github.com/bitcoin/bitcoin/blob/master/doc/tracing.md#tracepoint-utxocacheuncache + """ + # To trigger an UTXO uncache from the cache, we create an invalid transaction + # spending a not-cached, but existing UTXO. During transaction validation, this + # the UTXO is added to the utxo cache, but as the transaction is invalid, it's + # uncached again. + self.log.info("testing the utxocache:uncache tracepoint API") + + # Retrieve the txid for the UTXO created in the first block. This UTXO is not + # in our UTXO cache. + EARLY_BLOCK_HEIGHT = 1 + block_1_hash = self.nodes[0].getblockhash(EARLY_BLOCK_HEIGHT) + block_1 = self.nodes[0].getblock(block_1_hash) + block_1_coinbase_txid = block_1["tx"][0] + + # 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()["tx"] + invalid_tx.vin[0].prevout.hash = int(block_1_coinbase_txid, 16) + + self.log.info("hooking into the utxocache:uncache tracepoint") + 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) + + # The handle_* function is a ctypes callback function called from C. When + # we assert in the handle_* function, the AssertError doesn't propagate + # back to Python. The exception is ignored. We manually count and assert + # that the handle_* functions succeeded. + EXPECTED_HANDLE_UNCACHE_SUCCESS = 1 + handle_uncache_succeeds = 0 + + def handle_utxocache_uncache(_, data, __): + nonlocal handle_uncache_succeeds + event = ctypes.cast(data, ctypes.POINTER(UTXOCacheChange)).contents + self.log.info(f"handle_utxocache_uncache(): {event}") + assert_equal(block_1_coinbase_txid, bytes(event.txid[::-1]).hex()) + assert_equal(0, event.index) # prevout index + assert_equal(EARLY_BLOCK_HEIGHT, event.height) + assert_equal(50 * COIN, event.value) + assert_equal(True, event.is_coinbase) + + handle_uncache_succeeds += 1 + + bpf["utxocache_uncache"].open_perf_buffer(handle_utxocache_uncache) + + self.log.info( + "testmempoolaccept the invalid transaction to trigger an UTXO-cache uncache") + result = self.nodes[0].testmempoolaccept( + [invalid_tx.serialize().hex()])[0] + assert_equal(result["allowed"], False) + + bpf.perf_buffer_poll(timeout=100) + bpf.cleanup() + + self.log.info( + f"check that we successfully traced {EXPECTED_HANDLE_UNCACHE_SUCCESS} uncaches") + assert_equal(EXPECTED_HANDLE_UNCACHE_SUCCESS, handle_uncache_succeeds) + + def test_add_spent(self): + """ Tests the utxocache:add utxocache:spent tracepoint API + See https://github.com/bitcoin/bitcoin/blob/master/doc/tracing.md#tracepoint-utxocacheadd + and https://github.com/bitcoin/bitcoin/blob/master/doc/tracing.md#tracepoint-utxocachespent + """ + + self.log.info( + "test the utxocache:add and utxocache:spent tracepoint API") + + self.log.info("create an unconfirmed transaction") + self.wallet.send_self_transfer(from_node=self.nodes[0]) + + # We mine a block to trace changes (add/spent) to the active in-memory cache + # of the UTXO set (see CoinsTip() of CCoinsViewCache). However, in some cases + # temporary clones of the active cache are made. For example, during mining with + # the generate RPC call, the block is first tested in TestBlockValidity(). There, + # a clone of the active cache is modified during a test ConnectBlock() call. + # These are implementation details we don't want to test here. Thus, after + # mining, we invalidate the block, start the tracing, and then trace the cache + # changes to the active utxo cache. + self.log.info("mine and invalidate a block that is later reconsidered") + block_hash = self.generate(self.wallet, 1)[0] + self.nodes[0].invalidateblock(block_hash) + + self.log.info( + "hook into the utxocache:add and utxocache:spent tracepoints") + 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") + bpf = BPF(text=utxocache_changes_program, usdt_contexts=[ctx], debug=0) + + # The handle_* function is a ctypes callback function called from C. When + # we assert in the handle_* function, the AssertError doesn't propagate + # back to Python. The exception is ignored. We manually count and assert + # that the handle_* functions succeeded. + EXPECTED_HANDLE_ADD_SUCCESS = 2 + EXPECTED_HANDLE_SPENT_SUCCESS = 1 + handle_add_succeeds = 0 + handle_spent_succeeds = 0 + + expected_utxocache_spents = [] + expected_utxocache_adds = [] + + def handle_utxocache_add(_, data, __): + nonlocal handle_add_succeeds + event = ctypes.cast(data, ctypes.POINTER(UTXOCacheChange)).contents + self.log.info(f"handle_utxocache_add(): {event}") + add = expected_utxocache_adds.pop(0) + assert_equal(add["txid"], bytes(event.txid[::-1]).hex()) + assert_equal(add["index"], event.index) + assert_equal(add["height"], event.height) + assert_equal(add["value"], event.value) + assert_equal(add["is_coinbase"], event.is_coinbase) + handle_add_succeeds += 1 + + def handle_utxocache_spent(_, data, __): + nonlocal handle_spent_succeeds + event = ctypes.cast(data, ctypes.POINTER(UTXOCacheChange)).contents + self.log.info(f"handle_utxocache_spent(): {event}") + spent = expected_utxocache_spents.pop(0) + assert_equal(spent["txid"], bytes(event.txid[::-1]).hex()) + assert_equal(spent["index"], event.index) + assert_equal(spent["height"], event.height) + assert_equal(spent["value"], event.value) + assert_equal(spent["is_coinbase"], event.is_coinbase) + handle_spent_succeeds += 1 + + bpf["utxocache_add"].open_perf_buffer(handle_utxocache_add) + bpf["utxocache_spent"].open_perf_buffer(handle_utxocache_spent) + + # We trigger a block re-connection. This causes changes (add/spent) + # to the UTXO-cache which in turn triggers the tracepoints. + self.log.info("reconsider the previously invalidated block") + self.nodes[0].reconsiderblock(block_hash) + + block = self.nodes[0].getblock(block_hash, 2) + for (block_index, tx) in enumerate(block["tx"]): + for vin in tx["vin"]: + if "coinbase" not in vin: + prevout_tx = self.nodes[0].getrawtransaction( + vin["txid"], True) + prevout_tx_block = self.nodes[0].getblockheader( + prevout_tx["blockhash"]) + spends_coinbase = "coinbase" in prevout_tx["vin"][0] + expected_utxocache_spents.append({ + "txid": vin["txid"], + "index": vin["vout"], + "height": prevout_tx_block["height"], + "value": int(prevout_tx["vout"][vin["vout"]]["value"] * COIN), + "is_coinbase": spends_coinbase, + }) + for (i, vout) in enumerate(tx["vout"]): + if vout["scriptPubKey"]["type"] != "nulldata": + expected_utxocache_adds.append({ + "txid": tx["txid"], + "index": i, + "height": block["height"], + "value": int(vout["value"] * COIN), + "is_coinbase": block_index == 0, + }) + + assert_equal(EXPECTED_HANDLE_ADD_SUCCESS, len(expected_utxocache_adds)) + assert_equal(EXPECTED_HANDLE_SPENT_SUCCESS, + len(expected_utxocache_spents)) + + bpf.perf_buffer_poll(timeout=200) + bpf.cleanup() + + self.log.info( + f"check that we successfully traced {EXPECTED_HANDLE_ADD_SUCCESS} adds and {EXPECTED_HANDLE_SPENT_SUCCESS} spent") + assert_equal(0, len(expected_utxocache_adds)) + assert_equal(0, len(expected_utxocache_spents)) + assert_equal(EXPECTED_HANDLE_ADD_SUCCESS, handle_add_succeeds) + assert_equal(EXPECTED_HANDLE_SPENT_SUCCESS, handle_spent_succeeds) + + def test_flush(self): + """ Tests the utxocache:flush tracepoint API. + See https://github.com/bitcoin/bitcoin/blob/master/doc/tracing.md#tracepoint-utxocacheflush""" + + self.log.info("test the utxocache:flush tracepoint API") + 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) + + # The handle_* function is a ctypes callback function called from C. When + # we assert in the handle_* function, the AssertError doesn't propagate + # back to Python. The exception is ignored. We manually count and assert + # that the handle_* functions succeeded. + EXPECTED_HANDLE_FLUSH_SUCCESS = 3 + handle_flush_succeeds = 0 + 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_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) + handle_flush_succeeds += 1 + + bpf["utxocache_flush"].open_perf_buffer(handle_utxocache_flush) + + self.log.info("stop the node to flush the UTXO cache") + 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. + 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)) + + self.log.info("restart the node with -prune") + self.start_node(0, ["-fastprune=1", "-prune=1"]) + + 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) + + 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) + bpf.cleanup() + + 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(EXPECTED_HANDLE_FLUSH_SUCCESS, handle_flush_succeeds) + + +if __name__ == '__main__': + UTXOCacheTracepointTest().main() diff --git a/test/functional/interface_usdt_validation.py b/test/functional/interface_usdt_validation.py new file mode 100755 index 0000000000..8953dd023b --- /dev/null +++ b/test/functional/interface_usdt_validation.py @@ -0,0 +1,137 @@ +#!/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. + +""" Tests the validation:* tracepoint API interface. + See https://github.com/bitcoin/bitcoin/blob/master/doc/tracing.md#context-validation +""" + +import ctypes + +# Test will be skipped if we don't have bcc installed +try: + from bcc import BPF, USDT # type: ignore[import] +except ImportError: + pass + +from test_framework.address import ADDRESS_BCRT1_UNSPENDABLE +from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import assert_equal + + +validation_blockconnected_program = """ +#include <uapi/linux/ptrace.h> + +typedef signed long long i64; + +struct connected_block +{ + char hash[32]; + int height; + i64 transactions; + int inputs; + i64 sigops; + u64 duration; +}; + +BPF_PERF_OUTPUT(block_connected); +int trace_block_connected(struct pt_regs *ctx) { + struct connected_block block = {}; + bpf_usdt_readarg_p(1, ctx, &block.hash, 32); + bpf_usdt_readarg(2, ctx, &block.height); + bpf_usdt_readarg(3, ctx, &block.transactions); + bpf_usdt_readarg(4, ctx, &block.inputs); + bpf_usdt_readarg(5, ctx, &block.sigops); + bpf_usdt_readarg(6, ctx, &block.duration); + block_connected.perf_submit(ctx, &block, sizeof(block)); + return 0; +} +""" + + +class ValidationTracepointTest(BitcoinTestFramework): + def set_test_params(self): + self.num_nodes = 1 + + def skip_test_if_missing_module(self): + self.skip_if_platform_not_linux() + self.skip_if_no_bitcoind_tracepoints() + self.skip_if_no_python_bcc() + self.skip_if_no_bpf_permissions() + + def run_test(self): + # Tests the validation:block_connected tracepoint by generating blocks + # and comparing the values passed in the tracepoint arguments with the + # blocks. + # See https://github.com/bitcoin/bitcoin/blob/master/doc/tracing.md#tracepoint-validationblock_connected + + class Block(ctypes.Structure): + _fields_ = [ + ("hash", ctypes.c_ubyte * 32), + ("height", ctypes.c_int), + ("transactions", ctypes.c_int64), + ("inputs", ctypes.c_int), + ("sigops", ctypes.c_int64), + ("duration", ctypes.c_uint64), + ] + + def __repr__(self): + return "ConnectedBlock(hash=%s height=%d, transactions=%d, inputs=%d, sigops=%d, duration=%d)" % ( + bytes(self.hash[::-1]).hex(), + self.height, + self.transactions, + self.inputs, + self.sigops, + self.duration) + + # The handle_* function is a ctypes callback function called from C. When + # we assert in the handle_* function, the AssertError doesn't propagate + # back to Python. The exception is ignored. We manually count and assert + # that the handle_* functions succeeded. + BLOCKS_EXPECTED = 2 + blocks_checked = 0 + expected_blocks = dict() + + self.log.info("hook into the validation:block_connected tracepoint") + 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, + usdt_contexts=[ctx], debug=0) + + def handle_blockconnected(_, data, __): + nonlocal expected_blocks, blocks_checked + event = ctypes.cast(data, ctypes.POINTER(Block)).contents + self.log.info(f"handle_blockconnected(): {event}") + 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( + handle_blockconnected) + + self.log.info(f"mine {BLOCKS_EXPECTED} blocks") + block_hashes = self.generatetoaddress( + self.nodes[0], BLOCKS_EXPECTED, ADDRESS_BCRT1_UNSPENDABLE) + for block_hash in block_hashes: + expected_blocks[block_hash] = self.nodes[0].getblock(block_hash, 2) + + bpf.perf_buffer_poll(timeout=200) + bpf.cleanup() + + self.log.info(f"check that we traced {BLOCKS_EXPECTED} blocks") + assert_equal(BLOCKS_EXPECTED, blocks_checked) + assert_equal(0, len(expected_blocks)) + + +if __name__ == '__main__': + ValidationTracepointTest().main() diff --git a/test/functional/interface_zmq.py b/test/functional/interface_zmq.py index 1ee12c0040..7d8d10589b 100755 --- a/test/functional/interface_zmq.py +++ b/test/functional/interface_zmq.py @@ -23,6 +23,9 @@ from test_framework.util import ( assert_equal, assert_raises_rpc_error, ) +from test_framework.wallet import ( + MiniWallet, +) from test_framework.netutil import test_ipv6_local from io import BytesIO from time import sleep @@ -100,8 +103,6 @@ class ZMQTestSetupBlock: class ZMQTest (BitcoinTestFramework): def set_test_params(self): self.num_nodes = 2 - if self.is_wallet_compiled(): - self.requires_wallet = True # 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 @@ -111,6 +112,7 @@ class ZMQTest (BitcoinTestFramework): self.skip_if_no_bitcoind_zmq() def run_test(self): + self.wallet = MiniWallet(self.nodes[0]) self.ctx = zmq.Context() try: self.test_basic() @@ -211,25 +213,25 @@ class ZMQTest (BitcoinTestFramework): assert_equal([txid.hex()], self.nodes[1].getblock(hash)["tx"]) - if self.is_wallet_compiled(): - self.log.info("Wait for tx from second node") - payment_txid = self.nodes[1].sendtoaddress(self.nodes[0].getnewaddress(), 1.0) - self.sync_all() - - # Should receive the broadcasted txid. - txid = hashtx.receive() - assert_equal(payment_txid, txid.hex()) + self.wallet.rescan_utxos() + self.log.info("Wait for tx from second node") + payment_tx = self.wallet.send_self_transfer(from_node=self.nodes[1]) + payment_txid = payment_tx['txid'] + self.sync_all() + # Should receive the broadcasted txid. + txid = hashtx.receive() + assert_equal(payment_txid, txid.hex()) - # Should receive the broadcasted raw transaction. - hex = rawtx.receive() - assert_equal(payment_txid, hash256_reversed(hex).hex()) + # Should receive the broadcasted raw transaction. + hex = rawtx.receive() + assert_equal(payment_tx['wtxid'], hash256_reversed(hex).hex()) - # Mining the block with this tx should result in second notification - # after coinbase tx notification - self.generatetoaddress(self.nodes[0], 1, ADDRESS_BCRT1_UNSPENDABLE) - hashtx.receive() - txid = hashtx.receive() - assert_equal(payment_txid, txid.hex()) + # Mining the block with this tx should result in second notification + # after coinbase tx notification + self.generatetoaddress(self.nodes[0], 1, ADDRESS_BCRT1_UNSPENDABLE) + hashtx.receive() + txid = hashtx.receive() + assert_equal(payment_txid, txid.hex()) self.log.info("Test the getzmqnotifications RPC") @@ -243,9 +245,6 @@ class ZMQTest (BitcoinTestFramework): assert_equal(self.nodes[1].getzmqnotifications(), []) def test_reorg(self): - if not self.is_wallet_compiled(): - self.log.info("Skipping reorg test because wallet is disabled") - return address = 'tcp://127.0.0.1:28333' @@ -256,7 +255,7 @@ class ZMQTest (BitcoinTestFramework): self.disconnect_nodes(0, 1) # Generate 1 block in nodes[0] with 1 mempool tx and receive all notifications - payment_txid = self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), 1.0) + payment_txid = self.wallet.send_self_transfer(from_node=self.nodes[0])['txid'] disconnect_block = self.generatetoaddress(self.nodes[0], 1, ADDRESS_BCRT1_UNSPENDABLE, sync_fun=self.no_op)[0] disconnect_cb = self.nodes[0].getblock(disconnect_block)["tx"][0] assert_equal(self.nodes[0].getbestblockhash(), hashblock.receive().hex()) @@ -325,126 +324,124 @@ class ZMQTest (BitcoinTestFramework): assert_equal((self.nodes[1].getblockhash(block_count-1), "C", None), seq.receive_sequence()) assert_equal((self.nodes[1].getblockhash(block_count), "C", None), seq.receive_sequence()) - # Rest of test requires wallet functionality - if self.is_wallet_compiled(): - self.log.info("Wait for tx from second node") - payment_txid = self.nodes[1].sendtoaddress(address=self.nodes[0].getnewaddress(), amount=5.0, replaceable=True) - self.sync_all() - self.log.info("Testing sequence notifications with mempool sequence values") - - # Should receive the broadcasted txid. - assert_equal((payment_txid, "A", seq_num), seq.receive_sequence()) - seq_num += 1 - - self.log.info("Testing RBF notification") - # Replace it to test eviction/addition notification - rbf_info = self.nodes[1].bumpfee(payment_txid) - self.sync_all() - assert_equal((payment_txid, "R", seq_num), seq.receive_sequence()) - seq_num += 1 - assert_equal((rbf_info["txid"], "A", seq_num), seq.receive_sequence()) - seq_num += 1 - - # Doesn't get published when mined, make a block and tx to "flush" the possibility - # though the mempool sequence number does go up by the number of transactions - # removed from the mempool by the block mining it. - mempool_size = len(self.nodes[0].getrawmempool()) - c_block = self.generatetoaddress(self.nodes[0], 1, ADDRESS_BCRT1_UNSPENDABLE)[0] - # Make sure the number of mined transactions matches the number of txs out of mempool - mempool_size_delta = mempool_size - len(self.nodes[0].getrawmempool()) - assert_equal(len(self.nodes[0].getblock(c_block)["tx"])-1, mempool_size_delta) - seq_num += mempool_size_delta - payment_txid_2 = self.nodes[1].sendtoaddress(self.nodes[0].getnewaddress(), 1.0) - self.sync_all() - assert_equal((c_block, "C", None), seq.receive_sequence()) - assert_equal((payment_txid_2, "A", seq_num), seq.receive_sequence()) - seq_num += 1 - - # Spot check getrawmempool results that they only show up when asked for - assert type(self.nodes[0].getrawmempool()) is list - assert type(self.nodes[0].getrawmempool(mempool_sequence=False)) is list - assert "mempool_sequence" not in self.nodes[0].getrawmempool(verbose=True) - assert_raises_rpc_error(-8, "Verbose results cannot contain mempool sequence values.", self.nodes[0].getrawmempool, True, True) - assert_equal(self.nodes[0].getrawmempool(mempool_sequence=True)["mempool_sequence"], seq_num) - - self.log.info("Testing reorg notifications") - # Manually invalidate the last block to test mempool re-entry - # N.B. This part could be made more lenient in exact ordering - # since it greatly depends on inner-workings of blocks/mempool - # during "deep" re-orgs. Probably should "re-construct" - # blockchain/mempool state from notifications instead. - block_count = self.nodes[0].getblockcount() - best_hash = self.nodes[0].getbestblockhash() - self.nodes[0].invalidateblock(best_hash) - sleep(2) # Bit of room to make sure transaction things happened - - # Make sure getrawmempool mempool_sequence results aren't "queued" but immediately reflective - # of the time they were gathered. - assert self.nodes[0].getrawmempool(mempool_sequence=True)["mempool_sequence"] > seq_num - - assert_equal((best_hash, "D", None), seq.receive_sequence()) - assert_equal((rbf_info["txid"], "A", seq_num), seq.receive_sequence()) - seq_num += 1 - - # Other things may happen but aren't wallet-deterministic so we don't test for them currently - self.nodes[0].reconsiderblock(best_hash) - self.generatetoaddress(self.nodes[1], 1, ADDRESS_BCRT1_UNSPENDABLE) - - self.log.info("Evict mempool transaction by block conflict") - orig_txid = self.nodes[0].sendtoaddress(address=self.nodes[0].getnewaddress(), amount=1.0, replaceable=True) - - # More to be simply mined - more_tx = [] - for _ in range(5): - more_tx.append(self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), 0.1)) - - raw_tx = self.nodes[0].getrawtransaction(orig_txid) - bump_info = self.nodes[0].bumpfee(orig_txid) - # Mine the pre-bump tx - txs_to_add = [raw_tx] + [self.nodes[0].getrawtransaction(txid) for txid in more_tx] - block = create_block(int(self.nodes[0].getbestblockhash(), 16), create_coinbase(self.nodes[0].getblockcount()+1), txlist=txs_to_add) - add_witness_commitment(block) - block.solve() - assert_equal(self.nodes[0].submitblock(block.serialize().hex()), None) - tip = self.nodes[0].getbestblockhash() - assert_equal(int(tip, 16), block.sha256) - orig_txid_2 = self.nodes[0].sendtoaddress(address=self.nodes[0].getnewaddress(), amount=1.0, replaceable=True) - - # Flush old notifications until evicted tx original entry + self.log.info("Wait for tx from second node") + payment_tx = self.wallet.send_self_transfer(from_node=self.nodes[1]) + payment_txid = payment_tx['txid'] + self.sync_all() + self.log.info("Testing sequence notifications with mempool sequence values") + + # Should receive the broadcasted txid. + assert_equal((payment_txid, "A", seq_num), seq.receive_sequence()) + seq_num += 1 + + self.log.info("Testing RBF notification") + # Replace it to test eviction/addition notification + payment_tx['tx'].vout[0].nValue -= 1000 + rbf_txid = self.nodes[1].sendrawtransaction(payment_tx['tx'].serialize().hex()) + self.sync_all() + assert_equal((payment_txid, "R", seq_num), seq.receive_sequence()) + seq_num += 1 + assert_equal((rbf_txid, "A", seq_num), seq.receive_sequence()) + seq_num += 1 + + # Doesn't get published when mined, make a block and tx to "flush" the possibility + # though the mempool sequence number does go up by the number of transactions + # removed from the mempool by the block mining it. + mempool_size = len(self.nodes[0].getrawmempool()) + c_block = self.generatetoaddress(self.nodes[0], 1, ADDRESS_BCRT1_UNSPENDABLE)[0] + # Make sure the number of mined transactions matches the number of txs out of mempool + mempool_size_delta = mempool_size - len(self.nodes[0].getrawmempool()) + assert_equal(len(self.nodes[0].getblock(c_block)["tx"])-1, mempool_size_delta) + seq_num += mempool_size_delta + payment_txid_2 = self.wallet.send_self_transfer(from_node=self.nodes[1])['txid'] + self.sync_all() + assert_equal((c_block, "C", None), seq.receive_sequence()) + assert_equal((payment_txid_2, "A", seq_num), seq.receive_sequence()) + seq_num += 1 + + # Spot check getrawmempool results that they only show up when asked for + assert type(self.nodes[0].getrawmempool()) is list + assert type(self.nodes[0].getrawmempool(mempool_sequence=False)) is list + assert "mempool_sequence" not in self.nodes[0].getrawmempool(verbose=True) + assert_raises_rpc_error(-8, "Verbose results cannot contain mempool sequence values.", self.nodes[0].getrawmempool, True, True) + assert_equal(self.nodes[0].getrawmempool(mempool_sequence=True)["mempool_sequence"], seq_num) + + self.log.info("Testing reorg notifications") + # Manually invalidate the last block to test mempool re-entry + # N.B. This part could be made more lenient in exact ordering + # since it greatly depends on inner-workings of blocks/mempool + # during "deep" re-orgs. Probably should "re-construct" + # blockchain/mempool state from notifications instead. + block_count = self.nodes[0].getblockcount() + best_hash = self.nodes[0].getbestblockhash() + self.nodes[0].invalidateblock(best_hash) + sleep(2) # Bit of room to make sure transaction things happened + + # Make sure getrawmempool mempool_sequence results aren't "queued" but immediately reflective + # of the time they were gathered. + assert self.nodes[0].getrawmempool(mempool_sequence=True)["mempool_sequence"] > seq_num + + assert_equal((best_hash, "D", None), seq.receive_sequence()) + assert_equal((rbf_txid, "A", seq_num), seq.receive_sequence()) + seq_num += 1 + + # Other things may happen but aren't wallet-deterministic so we don't test for them currently + self.nodes[0].reconsiderblock(best_hash) + self.generatetoaddress(self.nodes[1], 1, ADDRESS_BCRT1_UNSPENDABLE) + + self.log.info("Evict mempool transaction by block conflict") + orig_tx = self.wallet.send_self_transfer(from_node=self.nodes[0]) + orig_txid = orig_tx['txid'] + + # More to be simply mined + more_tx = [] + for _ in range(5): + more_tx.append(self.wallet.send_self_transfer(from_node=self.nodes[0])) + + orig_tx['tx'].vout[0].nValue -= 1000 + bump_txid = self.nodes[0].sendrawtransaction(orig_tx['tx'].serialize().hex()) + # Mine the pre-bump tx + txs_to_add = [orig_tx['hex']] + [tx['hex'] for tx in more_tx] + block = create_block(int(self.nodes[0].getbestblockhash(), 16), create_coinbase(self.nodes[0].getblockcount()+1), txlist=txs_to_add) + add_witness_commitment(block) + block.solve() + assert_equal(self.nodes[0].submitblock(block.serialize().hex()), None) + tip = self.nodes[0].getbestblockhash() + assert_equal(int(tip, 16), block.sha256) + orig_txid_2 = self.wallet.send_self_transfer(from_node=self.nodes[0])['txid'] + + # Flush old notifications until evicted tx original entry + (hash_str, label, mempool_seq) = seq.receive_sequence() + while hash_str != orig_txid: (hash_str, label, mempool_seq) = seq.receive_sequence() - while hash_str != orig_txid: - (hash_str, label, mempool_seq) = seq.receive_sequence() - mempool_seq += 1 + mempool_seq += 1 - # Added original tx - assert_equal(label, "A") - # More transactions to be simply mined - for i in range(len(more_tx)): - assert_equal((more_tx[i], "A", mempool_seq), seq.receive_sequence()) - mempool_seq += 1 - # Bumped by rbf - assert_equal((orig_txid, "R", mempool_seq), seq.receive_sequence()) - mempool_seq += 1 - assert_equal((bump_info["txid"], "A", mempool_seq), seq.receive_sequence()) + # Added original tx + assert_equal(label, "A") + # More transactions to be simply mined + for i in range(len(more_tx)): + assert_equal((more_tx[i]['txid'], "A", mempool_seq), seq.receive_sequence()) mempool_seq += 1 - # Conflict announced first, then block - assert_equal((bump_info["txid"], "R", mempool_seq), seq.receive_sequence()) - mempool_seq += 1 - assert_equal((tip, "C", None), seq.receive_sequence()) - mempool_seq += len(more_tx) - # Last tx - assert_equal((orig_txid_2, "A", mempool_seq), seq.receive_sequence()) - mempool_seq += 1 - self.generatetoaddress(self.nodes[0], 1, ADDRESS_BCRT1_UNSPENDABLE) - self.sync_all() # want to make sure we didn't break "consensus" for other tests + # Bumped by rbf + assert_equal((orig_txid, "R", mempool_seq), seq.receive_sequence()) + mempool_seq += 1 + assert_equal((bump_txid, "A", mempool_seq), seq.receive_sequence()) + mempool_seq += 1 + # Conflict announced first, then block + assert_equal((bump_txid, "R", mempool_seq), seq.receive_sequence()) + mempool_seq += 1 + assert_equal((tip, "C", None), seq.receive_sequence()) + mempool_seq += len(more_tx) + # Last tx + assert_equal((orig_txid_2, "A", mempool_seq), seq.receive_sequence()) + mempool_seq += 1 + self.generatetoaddress(self.nodes[0], 1, ADDRESS_BCRT1_UNSPENDABLE) + self.sync_all() # want to make sure we didn't break "consensus" for other tests def test_mempool_sync(self): """ Use sequence notification plus getrawmempool sequence results to "sync mempool" """ - if not self.is_wallet_compiled(): - self.log.info("Skipping mempool sync test") - return self.log.info("Testing 'mempool sync' usage of sequence notifier") [seq] = self.setup_zmq_test([("sequence", "tcp://127.0.0.1:28333")]) @@ -455,10 +452,10 @@ class ZMQTest (BitcoinTestFramework): # Some transactions have been happening but we aren't consuming zmq notifications yet # or we lost a ZMQ message somehow and want to start over - txids = [] + txs = [] num_txs = 5 for _ in range(num_txs): - txids.append(self.nodes[1].sendtoaddress(address=self.nodes[0].getnewaddress(), amount=1.0, replaceable=True)) + txs.append(self.wallet.send_self_transfer(from_node=self.nodes[1])) self.sync_all() # 1) Consume backlog until we get a mempool sequence number @@ -484,11 +481,12 @@ class ZMQTest (BitcoinTestFramework): # Things continue to happen in the "interim" while waiting for snapshot results # We have node 0 do all these to avoid p2p races with RBF announcements for _ in range(num_txs): - txids.append(self.nodes[0].sendtoaddress(address=self.nodes[0].getnewaddress(), amount=0.1, replaceable=True)) - self.nodes[0].bumpfee(txids[-1]) + txs.append(self.wallet.send_self_transfer(from_node=self.nodes[0])) + txs[-1]['tx'].vout[0].nValue -= 1000 + self.nodes[0].sendrawtransaction(txs[-1]['tx'].serialize().hex()) self.sync_all() self.generatetoaddress(self.nodes[0], 1, ADDRESS_BCRT1_UNSPENDABLE) - final_txid = self.nodes[0].sendtoaddress(address=self.nodes[0].getnewaddress(), amount=0.1, replaceable=True) + final_txid = self.wallet.send_self_transfer(from_node=self.nodes[0])['txid'] # 3) Consume ZMQ backlog until we get to "now" for the mempool snapshot while True: diff --git a/test/functional/mempool_accept.py b/test/functional/mempool_accept.py index d3961e753d..65b37a4975 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, @@ -76,7 +76,7 @@ class MempoolAcceptanceTest(BitcoinTestFramework): tx.vout[0].nValue = int(0.3 * COIN) tx.vout[1].nValue = int(49 * COIN) raw_tx_in_block = tx.serialize().hex() - txid_in_block = self.wallet.sendrawtransaction(from_node=node, tx_hex=raw_tx_in_block, maxfeerate=0) + txid_in_block = self.wallet.sendrawtransaction(from_node=node, tx_hex=raw_tx_in_block) self.generate(node, 1) self.mempool_size = 0 self.check_mempool_result( @@ -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( @@ -166,7 +166,7 @@ class MempoolAcceptanceTest(BitcoinTestFramework): tx.vin[1].prevout = COutPoint(hash=int(txid_1, 16), n=0) tx.vout[0].nValue = int(0.1 * COIN) raw_tx_spend_both = tx.serialize().hex() - txid_spend_both = self.wallet.sendrawtransaction(from_node=node, tx_hex=raw_tx_spend_both, maxfeerate=0) + txid_spend_both = self.wallet.sendrawtransaction(from_node=node, tx_hex=raw_tx_spend_both) self.generate(node, 1) self.mempool_size = 0 # Now see if we can add the coins back to the utxo set by sending the exact txs again 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..47ae0c762b 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. """ @@ -20,7 +20,7 @@ from test_framework.util import ( ) from test_framework.wallet import MiniWallet -DEFAULT_MEMPOOL_EXPIRY = 336 # hours +DEFAULT_MEMPOOL_EXPIRY_HOURS = 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 a6fb1dcf35..9a981bd5a5 100755 --- a/test/functional/mempool_package_onemore.py +++ b/test/functional/mempool_package_onemore.py @@ -7,74 +7,67 @@ size. """ -from decimal import Decimal - -from test_framework.blocktools import COINBASE_MATURITY +from test_framework.messages import ( + DEFAULT_ANCESTOR_LIMIT, +) from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_equal, assert_raises_rpc_error, - chain_transaction, ) +from test_framework.wallet import MiniWallet -MAX_ANCESTORS = 25 -MAX_DESCENDANTS = 25 class MempoolPackagesTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 1 self.extra_args = [["-maxorphantx=1000"]] - def skip_test_if_missing_module(self): - self.skip_if_no_wallet() + def chain_tx(self, utxos_to_spend, *, num_outputs=1): + return self.wallet.send_self_transfer_multi( + from_node=self.nodes[0], + utxos_to_spend=utxos_to_spend, + num_outputs=num_outputs)['new_utxos'] def run_test(self): - # Mine some blocks and have them mature. - self.generate(self.nodes[0], COINBASE_MATURITY + 1) - utxo = self.nodes[0].listunspent(10) - txid = utxo[0]['txid'] - vout = utxo[0]['vout'] - value = utxo[0]['amount'] + self.wallet = MiniWallet(self.nodes[0]) + self.wallet.rescan_utxos() - fee = Decimal("0.0002") - # 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): - (txid, sent_value) = chain_transaction(self.nodes[0], [txid], [vout], value, fee, 2) - vout = 0 - value = sent_value - chain.append([txid, value]) - for _ in range(MAX_ANCESTORS - 4): - (txid, sent_value) = chain_transaction(self.nodes[0], [txid], [0], value, fee, 1) - value = sent_value - chain.append([txid, value]) - (second_chain, second_chain_value) = chain_transaction(self.nodes[0], [utxo[1]['txid']], [utxo[1]['vout']], utxo[1]['amount'], fee, 1) + utxo, utxo2 = self.chain_tx([utxo], num_outputs=2) + chain.append(utxo2) + 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]", chain_transaction, self.nodes[0], [txid], [0], value, fee, 1) + assert_raises_rpc_error(-26, "too-long-mempool-chain, too many unconfirmed ancestors [limit: 25]", self.chain_tx, [utxo]) # ...even if it chains on from some point in the middle of the chain. - assert_raises_rpc_error(-26, "too-long-mempool-chain, too many descendants", chain_transaction, self.nodes[0], [chain[2][0]], [1], chain[2][1], fee, 1) - assert_raises_rpc_error(-26, "too-long-mempool-chain, too many descendants", chain_transaction, self.nodes[0], [chain[1][0]], [1], chain[1][1], fee, 1) + assert_raises_rpc_error(-26, "too-long-mempool-chain, too many descendants", self.chain_tx, [chain[2]]) + assert_raises_rpc_error(-26, "too-long-mempool-chain, too many descendants", self.chain_tx, [chain[1]]) # ...even if it chains on to two parent transactions with one in the chain. - assert_raises_rpc_error(-26, "too-long-mempool-chain, too many descendants", chain_transaction, self.nodes[0], [chain[0][0], second_chain], [1, 0], chain[0][1] + second_chain_value, fee, 1) + assert_raises_rpc_error(-26, "too-long-mempool-chain, too many descendants", self.chain_tx, [chain[0], second_chain]) # ...especially if its > 40k weight - assert_raises_rpc_error(-26, "too-long-mempool-chain, too many descendants", chain_transaction, self.nodes[0], [chain[0][0]], [1], chain[0][1], fee, 350) + assert_raises_rpc_error(-26, "too-long-mempool-chain, too many descendants", self.chain_tx, [chain[0]], num_outputs=350) # But not if it chains directly off the first transaction - (replacable_txid, replacable_orig_value) = chain_transaction(self.nodes[0], [chain[0][0]], [1], chain[0][1], fee, 1) + replacable_tx = self.wallet.send_self_transfer_multi(from_node=self.nodes[0], utxos_to_spend=[chain[0]])['tx'] # and the second chain should work just fine - chain_transaction(self.nodes[0], [second_chain], [0], second_chain_value, fee, 1) + self.chain_tx([second_chain]) # Make sure we can RBF the chain which used our carve-out rule - second_tx_outputs = {self.nodes[0].getrawtransaction(replacable_txid, True)["vout"][0]['scriptPubKey']['address']: replacable_orig_value - (Decimal(1) / Decimal(100))} - second_tx = self.nodes[0].createrawtransaction([{'txid': chain[0][0], 'vout': 1}], second_tx_outputs) - signed_second_tx = self.nodes[0].signrawtransactionwithwallet(second_tx) - self.nodes[0].sendrawtransaction(signed_second_tx['hex']) + replacable_tx.vout[0].nValue -= 1000000 + 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__': MempoolPackagesTest().main() diff --git a/test/functional/mempool_packages.py b/test/functional/mempool_packages.py index 068fdc0b65..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 = [] @@ -100,6 +103,12 @@ class MempoolPackagesTest(BitcoinTestFramework): entry = self.nodes[0].getmempoolentry(x) assert_equal(entry, mempool[x]) + # Check that gettxspendingprevout is consistent with getrawmempool + witnesstx = self.nodes[0].gettransaction(txid=x, verbose=True)['decoded'] + for tx_in in witnesstx["vin"]: + spending_result = self.nodes[0].gettxspendingprevout([ {'txid' : tx_in["txid"], 'vout' : tx_in["vout"]} ]) + assert_equal(spending_result, [ {'txid' : tx_in["txid"], 'vout' : tx_in["vout"], 'spendingtxid' : x} ]) + # Check that the descendant calculations are correct assert_equal(entry['descendantcount'], descendant_count) descendant_fees += entry['fees']['base'] @@ -207,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) @@ -234,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) @@ -244,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: @@ -259,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 adf7326dac..7587adc257 100755 --- a/test/functional/mempool_unbroadcast.py +++ b/test/functional/mempool_unbroadcast.py @@ -9,21 +9,20 @@ import time from test_framework.p2p import P2PTxInvStore from test_framework.test_framework import BitcoinTestFramework -from test_framework.util import ( - assert_equal, - create_confirmed_utxos, -) +from test_framework.util import assert_equal +from test_framework.wallet import MiniWallet MAX_INITIAL_BROADCAST_DELAY = 15 * 60 # 15 minutes in seconds class MempoolUnbroadcastTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 2 - - def skip_test_if_missing_module(self): - self.skip_if_no_wallet() + if self.is_wallet_compiled(): + self.requires_wallet = True def run_test(self): + self.wallet = MiniWallet(self.nodes[0]) + self.wallet.rescan_utxos() self.test_broadcast() self.test_txn_removal() @@ -31,30 +30,25 @@ class MempoolUnbroadcastTest(BitcoinTestFramework): self.log.info("Test that mempool reattempts delivery of locally submitted transaction") node = self.nodes[0] - min_relay_fee = node.getnetworkinfo()["relayfee"] - utxos = create_confirmed_utxos(self, min_relay_fee, node, 10) - self.disconnect_nodes(0, 1) self.log.info("Generate transactions that only node 0 knows about") - # generate a wallet txn - addr = node.getnewaddress() - wallet_tx_hsh = node.sendtoaddress(addr, 0.0001) + if self.is_wallet_compiled(): + # generate a wallet txn + addr = node.getnewaddress() + wallet_tx_hsh = node.sendtoaddress(addr, 0.0001) # generate a txn using sendrawtransaction - us0 = utxos.pop() - inputs = [{"txid": us0["txid"], "vout": us0["vout"]}] - outputs = {addr: 0.0001} - tx = node.createrawtransaction(inputs, outputs) - node.settxfee(min_relay_fee) - txF = node.fundrawtransaction(tx) - txFS = node.signrawtransactionwithwallet(txF["hex"]) + txFS = self.wallet.create_self_transfer() rpc_tx_hsh = node.sendrawtransaction(txFS["hex"]) # check transactions are in unbroadcast using rpc mempoolinfo = self.nodes[0].getmempoolinfo() - assert_equal(mempoolinfo['unbroadcastcount'], 2) + unbroadcast_count = 1 + if self.is_wallet_compiled(): + unbroadcast_count += 1 + assert_equal(mempoolinfo['unbroadcastcount'], unbroadcast_count) mempool = self.nodes[0].getrawmempool(True) for tx in mempool: assert_equal(mempool[tx]['unbroadcast'], True) @@ -62,7 +56,8 @@ class MempoolUnbroadcastTest(BitcoinTestFramework): # check that second node doesn't have these two txns mempool = self.nodes[1].getrawmempool() assert rpc_tx_hsh not in mempool - assert wallet_tx_hsh not in mempool + if self.is_wallet_compiled(): + assert wallet_tx_hsh not in mempool # ensure that unbroadcast txs are persisted to mempool.dat self.restart_node(0) @@ -75,7 +70,8 @@ class MempoolUnbroadcastTest(BitcoinTestFramework): self.sync_mempools(timeout=30) mempool = self.nodes[1].getrawmempool() assert rpc_tx_hsh in mempool - assert wallet_tx_hsh in mempool + if self.is_wallet_compiled(): + assert wallet_tx_hsh in mempool # check that transactions are no longer in first node's unbroadcast set mempool = self.nodes[0].getrawmempool(True) @@ -102,8 +98,7 @@ class MempoolUnbroadcastTest(BitcoinTestFramework): # since the node doesn't have any connections, it will not receive # any GETDATAs & thus the transaction will remain in the unbroadcast set. - addr = node.getnewaddress() - txhsh = node.sendtoaddress(addr, 0.0001) + txhsh = self.wallet.send_self_transfer(from_node=node)["txid"] # check transaction was removed from unbroadcast set due to presence in # a block 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 6f2ac805a0..3b75b2bc2d 100755 --- a/test/functional/mining_prioritisetransaction.py +++ b/test/functional/mining_prioritisetransaction.py @@ -4,26 +4,110 @@ # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test the prioritisetransaction mining RPC.""" +from decimal import Decimal import time -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") + mock_time = int(time.time()) + self.nodes[0].setmocktime(mock_time) + + # tx_a + # / \ + # / \ + # tx_b tx_c + # \ / + # \ / + # tx_d + + tx_o_a = self.wallet.send_self_transfer_multi( + from_node=self.nodes[0], + num_outputs=2, + ) + txid_a = tx_o_a["txid"] + + tx_o_b, tx_o_c = [self.wallet.send_self_transfer( + from_node=self.nodes[0], + utxo_to_spend=u, + ) for u in tx_o_a["new_utxos"]] + txid_b = tx_o_b["txid"] + txid_c = tx_o_c["txid"] + + tx_o_d = self.wallet.send_self_transfer_multi( + from_node=self.nodes[0], + utxos_to_spend=[ + self.wallet.get_utxo(txid=txid_b), + self.wallet.get_utxo(txid=txid_c), + ], + ) + txid_d = tx_o_d["txid"] + + self.log.info("Test priority while txs are in mempool") + raw_before = self.nodes[0].getrawmempool(verbose=True) + fee_delta_b = Decimal(9999) / COIN + fee_delta_c_1 = Decimal(-1234) / COIN + fee_delta_c_2 = Decimal(8888) / COIN + self.nodes[0].prioritisetransaction(txid=txid_b, fee_delta=int(fee_delta_b * COIN)) + self.nodes[0].prioritisetransaction(txid=txid_c, fee_delta=int(fee_delta_c_1 * COIN)) + self.nodes[0].prioritisetransaction(txid=txid_c, fee_delta=int(fee_delta_c_2 * COIN)) + raw_before[txid_a]["fees"]["descendant"] += fee_delta_b + fee_delta_c_1 + fee_delta_c_2 + raw_before[txid_b]["fees"]["modified"] += fee_delta_b + raw_before[txid_b]["fees"]["ancestor"] += fee_delta_b + raw_before[txid_b]["fees"]["descendant"] += fee_delta_b + raw_before[txid_c]["fees"]["modified"] += fee_delta_c_1 + fee_delta_c_2 + raw_before[txid_c]["fees"]["ancestor"] += fee_delta_c_1 + fee_delta_c_2 + raw_before[txid_c]["fees"]["descendant"] += fee_delta_c_1 + fee_delta_c_2 + raw_before[txid_d]["fees"]["ancestor"] += fee_delta_b + fee_delta_c_1 + fee_delta_c_2 + raw_after = self.nodes[0].getrawmempool(verbose=True) + assert_equal(raw_before[txid_a], raw_after[txid_a]) + assert_equal(raw_before, raw_after) + + self.log.info("Test priority while txs are not in mempool") + self.restart_node(0, extra_args=["-nopersistmempool"]) + self.nodes[0].setmocktime(mock_time) + assert_equal(self.nodes[0].getmempoolinfo()["size"], 0) + self.nodes[0].prioritisetransaction(txid=txid_b, fee_delta=int(fee_delta_b * COIN)) + self.nodes[0].prioritisetransaction(txid=txid_c, fee_delta=int(fee_delta_c_1 * COIN)) + self.nodes[0].prioritisetransaction(txid=txid_c, fee_delta=int(fee_delta_c_2 * COIN)) + for t in [tx_o_a["hex"], tx_o_b["hex"], tx_o_c["hex"], tx_o_d["hex"]]: + self.nodes[0].sendrawtransaction(t) + raw_after = self.nodes[0].getrawmempool(verbose=True) + assert_equal(raw_before[txid_a], raw_after[txid_a]) + assert_equal(raw_before, raw_after) + + # Clear mempool + self.generate(self.nodes[0], 1) + + # Use default extra_args + self.restart_node(0) 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) assert_raises_rpc_error(-1, "prioritisetransaction", self.nodes[0].prioritisetransaction, '') @@ -38,17 +122,22 @@ 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(-1, "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(-1, "JSON value of type string is not of expected type number", self.nodes[0].prioritisetransaction, txid=txid, fee_delta='foo') + + self.test_diamond() self.txouts = gen_return_txouts() 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 = [] @@ -58,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 @@ -117,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_addr_relay.py b/test/functional/p2p_addr_relay.py index 3218a9b14a..e2e9b6dcb2 100755 --- a/test/functional/p2p_addr_relay.py +++ b/test/functional/p2p_addr_relay.py @@ -21,8 +21,19 @@ from test_framework.p2p import ( P2P_SERVICES, ) from test_framework.test_framework import BitcoinTestFramework -from test_framework.util import assert_equal, assert_greater_than +from test_framework.util import ( + assert_equal, + assert_greater_than, + assert_greater_than_or_equal +) + +ONE_MINUTE = 60 +TEN_MINUTES = 10 * ONE_MINUTE +ONE_HOUR = 60 * ONE_MINUTE +TWO_HOURS = 2 * ONE_HOUR +ONE_DAY = 24 * ONE_HOUR +ADDR_DESTINATIONS_THRESHOLD = 4 class AddrReceiver(P2PInterface): num_ipv4_received = 0 @@ -85,6 +96,9 @@ class AddrTest(BitcoinTestFramework): self.relay_tests() self.inbound_blackhole_tests() + self.destination_rotates_once_in_24_hours_test() + self.destination_rotates_more_than_once_over_several_days_test() + # This test populates the addrman, which can impact the node's behavior # in subsequent tests self.getaddr_tests() @@ -362,6 +376,56 @@ class AddrTest(BitcoinTestFramework): self.nodes[0].disconnect_p2ps() + def get_nodes_that_received_addr(self, peer, receiver_peer, addr_receivers, + time_interval_1, time_interval_2): + + # Clean addr response related to the initial getaddr. There is no way to avoid initial + # getaddr because the peer won't self-announce then. + for addr_receiver in addr_receivers: + addr_receiver.num_ipv4_received = 0 + + for _ in range(10): + self.mocktime += time_interval_1 + self.msg.addrs[0].time = self.mocktime + TEN_MINUTES + self.nodes[0].setmocktime(self.mocktime) + with self.nodes[0].assert_debug_log(['received: addr (31 bytes) peer=0']): + peer.send_and_ping(self.msg) + self.mocktime += time_interval_2 + self.nodes[0].setmocktime(self.mocktime) + receiver_peer.sync_with_ping() + return [node for node in addr_receivers if node.addr_received()] + + def destination_rotates_once_in_24_hours_test(self): + self.restart_node(0, []) + + self.log.info('Test within 24 hours an addr relay destination is rotated at most once') + self.mocktime = int(time.time()) + self.msg = self.setup_addr_msg(1) + self.addr_receivers = [] + peer = self.nodes[0].add_p2p_connection(P2PInterface()) + receiver_peer = self.nodes[0].add_p2p_connection(AddrReceiver()) + addr_receivers = [self.nodes[0].add_p2p_connection(AddrReceiver()) for _ in range(20)] + nodes_received_addr = self.get_nodes_that_received_addr(peer, receiver_peer, addr_receivers, 0, TWO_HOURS) # 10 intervals of 2 hours + # Per RelayAddress, we would announce these addrs to 2 destinations per day. + # Since it's at most one rotation, at most 4 nodes can receive ADDR. + assert_greater_than_or_equal(ADDR_DESTINATIONS_THRESHOLD, len(nodes_received_addr)) + self.nodes[0].disconnect_p2ps() + + def destination_rotates_more_than_once_over_several_days_test(self): + self.restart_node(0, []) + + self.log.info('Test after several days an addr relay destination is rotated more than once') + self.msg = self.setup_addr_msg(1) + peer = self.nodes[0].add_p2p_connection(P2PInterface()) + receiver_peer = self.nodes[0].add_p2p_connection(AddrReceiver()) + addr_receivers = [self.nodes[0].add_p2p_connection(AddrReceiver()) for _ in range(20)] + # 10 intervals of 1 day (+ 1 hour, which should be enough to cover 30-min Poisson in most cases) + nodes_received_addr = self.get_nodes_that_received_addr(peer, receiver_peer, addr_receivers, ONE_DAY, ONE_HOUR) + # Now that there should have been more than one rotation, more than + # ADDR_DESTINATIONS_THRESHOLD nodes should have received ADDR. + assert_greater_than(len(nodes_received_addr), ADDR_DESTINATIONS_THRESHOLD) + self.nodes[0].disconnect_p2ps() + if __name__ == '__main__': AddrTest().main() diff --git a/test/functional/p2p_block_sync.py b/test/functional/p2p_block_sync.py new file mode 100755 index 0000000000..d821edc1b1 --- /dev/null +++ b/test/functional/p2p_block_sync.py @@ -0,0 +1,37 @@ +#!/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 block download + +Ensure that even in IBD, we'll eventually sync chain from inbound peers +(whether we have only inbound peers or both inbound and outbound peers). +""" + +from test_framework.test_framework import BitcoinTestFramework + +class BlockSyncTest(BitcoinTestFramework): + + def set_test_params(self): + self.setup_clean_chain = True + self.num_nodes = 3 + + def setup_network(self): + self.setup_nodes() + # Construct a network: + # node0 -> node1 -> node2 + # So node1 has both an inbound and outbound peer. + # In our test, we will mine a block on node0, and ensure that it makes + # to to both node1 and node2. + self.connect_nodes(0, 1) + self.connect_nodes(1, 2) + + def run_test(self): + self.log.info("Setup network: node0->node1->node2") + self.log.info("Mining one block on node0 and verify all nodes sync") + self.generate(self.nodes[0], 1) + self.log.info("Success!") + + +if __name__ == '__main__': + BlockSyncTest().main() diff --git a/test/functional/p2p_blockfilters.py b/test/functional/p2p_blockfilters.py index e73fad439f..ef12b5f6b7 100755 --- a/test/functional/p2p_blockfilters.py +++ b/test/functional/p2p_blockfilters.py @@ -244,6 +244,23 @@ class CompactFiltersTest(BitcoinTestFramework): peer_0.send_message(request) peer_0.wait_for_disconnect() + self.log.info("Test -peerblockfilters without -blockfilterindex raises an error") + self.stop_node(0) + self.nodes[0].extra_args = ["-peerblockfilters"] + msg = "Error: Cannot set -peerblockfilters without -blockfilterindex." + self.nodes[0].assert_start_raises_init_error(expected_msg=msg) + + self.log.info("Test unknown value to -blockfilterindex raises an error") + self.nodes[0].extra_args = ["-blockfilterindex=abc"] + msg = "Error: Unknown -blockfilterindex value abc." + self.nodes[0].assert_start_raises_init_error(expected_msg=msg) + + self.log.info("Test -blockfilterindex with -reindex-chainstate raises an error") + self.nodes[0].assert_start_raises_init_error( + expected_msg='Error: -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.', + extra_args=['-blockfilterindex', '-reindex-chainstate'], + ) def compute_last_header(prev_header, hashes): """Compute the last filter header from a starting header and a sequence of filter hashes.""" diff --git a/test/functional/p2p_blocksonly.py b/test/functional/p2p_blocksonly.py index 6f142f23f2..8ac38bff3a 100755 --- a/test/functional/p2p_blocksonly.py +++ b/test/functional/p2p_blocksonly.py @@ -89,17 +89,29 @@ 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") self.nodes[0].sendrawtransaction(tx_hex) - # Bump time forward to ensure nNextInvSend timer pops + # Bump time forward to ensure m_next_inv_send_time timer pops self.nodes[0].setmocktime(int(time.time()) + 60) 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 364e806e18..5e50e1ebce 100755 --- a/test/functional/p2p_compactblocks.py +++ b/test/functional/p2p_compactblocks.py @@ -2,11 +2,7 @@ # Copyright (c) 2016-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 compact blocks (BIP 152). - -Version 1 compact blocks are pre-segwit (txids) -Version 2 compact blocks are post-segwit (wtxids) -""" +"""Test compact blocks (BIP 152).""" import random from test_framework.blocktools import ( @@ -31,7 +27,6 @@ from test_framework.messages import ( MSG_BLOCK, MSG_CMPCT_BLOCK, MSG_WITNESS_FLAG, - NODE_NETWORK, P2PHeaderAndShortIDs, PrefilledTransaction, calculate_shortid, @@ -70,7 +65,7 @@ from test_framework.wallet import MiniWallet # TestP2PConn: A peer we use to send messages to bitcoind, and store responses. class TestP2PConn(P2PInterface): - def __init__(self, cmpct_version): + def __init__(self): super().__init__() self.last_sendcmpct = [] self.block_announced = False @@ -78,7 +73,6 @@ class TestP2PConn(P2PInterface): # This is for synchronizing the p2p message traffic, # so we can eg wait until a particular block is announced. self.announced_blockhashes = set() - self.cmpct_version = cmpct_version def on_sendcmpct(self, message): self.last_sendcmpct.append(message) @@ -152,10 +146,8 @@ class CompactBlocksTest(BitcoinTestFramework): ]] self.utxos = [] - def build_block_on_tip(self, node, segwit=False): + def build_block_on_tip(self, node): block = create_block(tmpl=node.getblocktemplate(NORMAL_GBT_REQUEST_PARAMS)) - if segwit: - add_witness_commitment(block) block.solve() return block @@ -185,15 +177,13 @@ class CompactBlocksTest(BitcoinTestFramework): # Test "sendcmpct" (between peers preferring the same version): # - No compact block announcements unless sendcmpct is sent. - # - If sendcmpct is sent with version > preferred_version, the message is ignored. + # - If sendcmpct is sent with version = 1, the message is ignored. + # - If sendcmpct is sent with version > 2, the message is ignored. # - If sendcmpct is sent with boolean 0, then block announcements are not # made with compact blocks. # - If sendcmpct is then sent with boolean 1, then new block announcements # are made with compact blocks. - # If old_node is passed in, request compact blocks with version=preferred-1 - # and verify that it receives block announcements via compact block. - def test_sendcmpct(self, test_node, old_node=None): - preferred_version = test_node.cmpct_version + def test_sendcmpct(self, test_node): node = self.nodes[0] # Make sure we get a SENDCMPCT message from our peer @@ -201,10 +191,8 @@ class CompactBlocksTest(BitcoinTestFramework): return (len(test_node.last_sendcmpct) > 0) test_node.wait_until(received_sendcmpct, timeout=30) with p2p_lock: - # Check that the first version received is the preferred one - assert_equal(test_node.last_sendcmpct[0].version, preferred_version) - # And that we receive versions down to 1. - assert_equal(test_node.last_sendcmpct[-1].version, 1) + # Check that version 2 is received. + assert_equal(test_node.last_sendcmpct[0].version, 2) test_node.last_sendcmpct = [] tip = int(node.getbestblockhash(), 16) @@ -232,22 +220,29 @@ class CompactBlocksTest(BitcoinTestFramework): # Before each test, sync the headers chain. test_node.request_headers_and_sync(locator=[tip]) + # Now try a SENDCMPCT message with too-low version + test_node.send_and_ping(msg_sendcmpct(announce=True, version=1)) + check_announcement_of_new_block(node, test_node, lambda p: "cmpctblock" not in p.last_message) + + # Headers sync before next test. + test_node.request_headers_and_sync(locator=[tip]) + # Now try a SENDCMPCT message with too-high version - test_node.send_and_ping(msg_sendcmpct(announce=True, version=preferred_version+1)) + test_node.send_and_ping(msg_sendcmpct(announce=True, version=3)) check_announcement_of_new_block(node, test_node, lambda p: "cmpctblock" not in p.last_message) # Headers sync before next test. test_node.request_headers_and_sync(locator=[tip]) # Now try a SENDCMPCT message with valid version, but announce=False - test_node.send_and_ping(msg_sendcmpct(announce=False, version=preferred_version)) + test_node.send_and_ping(msg_sendcmpct(announce=False, version=2)) check_announcement_of_new_block(node, test_node, lambda p: "cmpctblock" not in p.last_message) # Headers sync before next test. test_node.request_headers_and_sync(locator=[tip]) # Finally, try a SENDCMPCT message with announce=True - test_node.send_and_ping(msg_sendcmpct(announce=True, version=preferred_version)) + test_node.send_and_ping(msg_sendcmpct(announce=True, version=2)) check_announcement_of_new_block(node, test_node, lambda p: "cmpctblock" in p.last_message) # Try one more time (no headers sync should be needed!) @@ -257,22 +252,14 @@ class CompactBlocksTest(BitcoinTestFramework): test_node.send_and_ping(msg_sendheaders()) check_announcement_of_new_block(node, test_node, lambda p: "cmpctblock" in p.last_message) - # Try one more time, after sending a version-1, announce=false message. - test_node.send_and_ping(msg_sendcmpct(announce=False, version=preferred_version-1)) + # Try one more time, after sending a version=1, announce=false message. + test_node.send_and_ping(msg_sendcmpct(announce=False, version=1)) check_announcement_of_new_block(node, test_node, lambda p: "cmpctblock" in p.last_message) # Now turn off announcements - test_node.send_and_ping(msg_sendcmpct(announce=False, version=preferred_version)) + test_node.send_and_ping(msg_sendcmpct(announce=False, version=2)) check_announcement_of_new_block(node, test_node, lambda p: "cmpctblock" not in p.last_message and "headers" in p.last_message) - if old_node is not None: - # Verify that a peer using an older protocol version can receive - # announcements from this node. - old_node.send_and_ping(msg_sendcmpct(announce=True, version=preferred_version-1)) - # Header sync - old_node.request_headers_and_sync(locator=[tip]) - check_announcement_of_new_block(node, old_node, lambda p: "cmpctblock" in p.last_message) - # This test actually causes bitcoind to (reasonably!) disconnect us, so do this last. def test_invalid_cmpctblock_message(self): self.generate(self.nodes[0], COINBASE_MATURITY + 1) @@ -289,8 +276,7 @@ class CompactBlocksTest(BitcoinTestFramework): # Compare the generated shortids to what we expect based on BIP 152, given # bitcoind's choice of nonce. - def test_compactblock_construction(self, test_node, use_witness_address=True): - version = test_node.cmpct_version + def test_compactblock_construction(self, test_node): node = self.nodes[0] # Generate a bunch of transactions. self.generate(node, COINBASE_MATURITY + 1) @@ -303,8 +289,7 @@ class CompactBlocksTest(BitcoinTestFramework): if not tx.wit.is_null(): segwit_tx_generated = True - if use_witness_address: - assert segwit_tx_generated # check that our test is not broken + assert segwit_tx_generated # check that our test is not broken # Wait until we've seen the block announcement for the resulting tip tip = int(node.getbestblockhash(), 16) @@ -331,7 +316,7 @@ class CompactBlocksTest(BitcoinTestFramework): with p2p_lock: # Convert the on-the-wire representation to absolute indexes header_and_shortids = HeaderAndShortIDs(test_node.last_message["cmpctblock"].header_and_shortids) - self.check_compactblock_construction_from_block(version, header_and_shortids, block_hash, block) + self.check_compactblock_construction_from_block(header_and_shortids, block_hash, block) # Now fetch the compact block using a normal non-announce getdata test_node.clear_block_announcement() @@ -345,9 +330,9 @@ class CompactBlocksTest(BitcoinTestFramework): with p2p_lock: # Convert the on-the-wire representation to absolute indexes header_and_shortids = HeaderAndShortIDs(test_node.last_message["cmpctblock"].header_and_shortids) - self.check_compactblock_construction_from_block(version, header_and_shortids, block_hash, block) + self.check_compactblock_construction_from_block(header_and_shortids, block_hash, block) - def check_compactblock_construction_from_block(self, version, header_and_shortids, block_hash, block): + def check_compactblock_construction_from_block(self, header_and_shortids, block_hash, block): # Check that we got the right block! header_and_shortids.header.calc_sha256() assert_equal(header_and_shortids.header.sha256, block_hash) @@ -364,11 +349,7 @@ class CompactBlocksTest(BitcoinTestFramework): # And this checks the witness wtxid = entry.tx.calc_sha256(True) - if version == 2: - assert_equal(wtxid, block.vtx[entry.index].calc_sha256(True)) - else: - # Shouldn't have received a witness - assert entry.tx.wit.is_null() + assert_equal(wtxid, block.vtx[entry.index].calc_sha256(True)) # Check that the cmpctblock message announced all the transactions. assert_equal(len(header_and_shortids.prefilled_txn) + len(header_and_shortids.shortids), len(block.vtx)) @@ -384,9 +365,7 @@ class CompactBlocksTest(BitcoinTestFramework): # Already checked prefilled transactions above header_and_shortids.prefilled_txn.pop(0) else: - tx_hash = block.vtx[index].sha256 - if version == 2: - tx_hash = block.vtx[index].calc_sha256(True) + tx_hash = block.vtx[index].calc_sha256(True) shortid = calculate_shortid(k0, k1, tx_hash) assert_equal(shortid, header_and_shortids.shortids[0]) header_and_shortids.shortids.pop(0) @@ -395,16 +374,12 @@ class CompactBlocksTest(BitcoinTestFramework): # Test that bitcoind requests compact blocks when we announce new blocks # via header or inv, and that responding to getblocktxn causes the block # to be successfully reconstructed. - # Post-segwit: upgraded nodes would only make this request of cb-version-2, - # NODE_WITNESS peers. Unupgraded nodes would still make this request of - # any cb-version-1-supporting peer. - def test_compactblock_requests(self, test_node, segwit=True): - version = test_node.cmpct_version + def test_compactblock_requests(self, test_node): node = self.nodes[0] # Try announcing a block with an inv or header, expect a compactblock # request for announce in ["inv", "header"]: - block = self.build_block_on_tip(node, segwit=segwit) + block = self.build_block_on_tip(node) if announce == "inv": test_node.send_message(msg_inv([CInv(MSG_BLOCK, block.sha256)])) @@ -420,9 +395,7 @@ class CompactBlocksTest(BitcoinTestFramework): comp_block.header = CBlockHeader(block) comp_block.nonce = 0 [k0, k1] = comp_block.get_siphash_keys() - coinbase_hash = block.vtx[0].sha256 - if version == 2: - coinbase_hash = block.vtx[0].calc_sha256(True) + coinbase_hash = block.vtx[0].calc_sha256(True) comp_block.shortids = [calculate_shortid(k0, k1, coinbase_hash)] test_node.send_and_ping(msg_cmpctblock(comp_block.to_p2p())) assert_equal(int(node.getbestblockhash(), 16), block.hashPrevBlock) @@ -433,10 +406,7 @@ class CompactBlocksTest(BitcoinTestFramework): assert_equal(absolute_indexes, [0]) # should be a coinbase request # Send the coinbase, and verify that the tip advances. - if version == 2: - msg = msg_blocktxn() - else: - msg = msg_no_witness_blocktxn() + msg = msg_blocktxn() msg.block_transactions.blockhash = block.sha256 msg.block_transactions.transactions = [block.vtx[0]] test_node.send_and_ping(msg) @@ -462,9 +432,7 @@ class CompactBlocksTest(BitcoinTestFramework): # node needs, and that responding to them causes the block to be # reconstructed. def test_getblocktxn_requests(self, test_node): - version = test_node.cmpct_version node = self.nodes[0] - with_witness = (version == 2) def test_getblocktxn_response(compact_block, peer, expected_result): msg = msg_cmpctblock(compact_block.to_p2p()) @@ -485,13 +453,12 @@ class CompactBlocksTest(BitcoinTestFramework): block = self.build_block_with_transactions(node, utxo, 5) self.utxos.append([block.vtx[-1].sha256, 0, block.vtx[-1].vout[0].nValue]) comp_block = HeaderAndShortIDs() - comp_block.initialize_from_block(block, use_witness=with_witness) + comp_block.initialize_from_block(block, use_witness=True) test_getblocktxn_response(comp_block, test_node, [1, 2, 3, 4, 5]) msg_bt = msg_no_witness_blocktxn() - if with_witness: - msg_bt = msg_blocktxn() # serialize with witnesses + msg_bt = msg_blocktxn() # serialize with witnesses msg_bt.block_transactions = BlockTransactions(block.sha256, block.vtx[1:]) test_tip_after_message(node, test_node, msg_bt, block.sha256) @@ -500,7 +467,7 @@ class CompactBlocksTest(BitcoinTestFramework): self.utxos.append([block.vtx[-1].sha256, 0, block.vtx[-1].vout[0].nValue]) # Now try interspersing the prefilled transactions - comp_block.initialize_from_block(block, prefill_list=[0, 1, 5], use_witness=with_witness) + comp_block.initialize_from_block(block, prefill_list=[0, 1, 5], use_witness=True) test_getblocktxn_response(comp_block, test_node, [2, 3, 4]) msg_bt.block_transactions = BlockTransactions(block.sha256, block.vtx[2:5]) test_tip_after_message(node, test_node, msg_bt, block.sha256) @@ -514,7 +481,7 @@ class CompactBlocksTest(BitcoinTestFramework): # Prefill 4 out of the 6 transactions, and verify that only the one # that was not in the mempool is requested. - comp_block.initialize_from_block(block, prefill_list=[0, 2, 3, 4], use_witness=with_witness) + comp_block.initialize_from_block(block, prefill_list=[0, 2, 3, 4], use_witness=True) test_getblocktxn_response(comp_block, test_node, [5]) msg_bt.block_transactions = BlockTransactions(block.sha256, [block.vtx[5]]) @@ -538,7 +505,7 @@ class CompactBlocksTest(BitcoinTestFramework): test_node.last_message.pop("getblocktxn", None) # Send compact block - comp_block.initialize_from_block(block, prefill_list=[0], use_witness=with_witness) + comp_block.initialize_from_block(block, prefill_list=[0], use_witness=True) test_tip_after_message(node, test_node, msg_cmpctblock(comp_block.to_p2p()), block.sha256) with p2p_lock: # Shouldn't have gotten a request for any transaction @@ -547,7 +514,6 @@ class CompactBlocksTest(BitcoinTestFramework): # Incorrectly responding to a getblocktxn shouldn't cause the block to be # permanently failed. def test_incorrect_blocktxn_response(self, test_node): - version = test_node.cmpct_version node = self.nodes[0] utxo = self.utxos.pop(0) @@ -564,7 +530,7 @@ class CompactBlocksTest(BitcoinTestFramework): # Send compact block comp_block = HeaderAndShortIDs() - comp_block.initialize_from_block(block, prefill_list=[0], use_witness=(version == 2)) + comp_block.initialize_from_block(block, prefill_list=[0], use_witness=True) test_node.send_and_ping(msg_cmpctblock(comp_block.to_p2p())) absolute_indexes = [] with p2p_lock: @@ -580,9 +546,7 @@ class CompactBlocksTest(BitcoinTestFramework): # different peer provide the block further down, so that we're still # verifying that the block isn't marked bad permanently. This is good # enough for now. - msg = msg_no_witness_blocktxn() - if version == 2: - msg = msg_blocktxn() + msg = msg_blocktxn() msg.block_transactions = BlockTransactions(block.sha256, [block.vtx[5]] + block.vtx[7:]) test_node.send_and_ping(msg) @@ -595,14 +559,10 @@ class CompactBlocksTest(BitcoinTestFramework): test_node.last_message["getdata"].inv[0].type == MSG_BLOCK | MSG_WITNESS_FLAG # Deliver the block - if version == 2: - test_node.send_and_ping(msg_block(block)) - else: - test_node.send_and_ping(msg_no_witness_block(block)) + test_node.send_and_ping(msg_block(block)) assert_equal(int(node.getbestblockhash(), 16), block.sha256) def test_getblocktxn_handler(self, test_node): - version = test_node.cmpct_version node = self.nodes[0] # bitcoind will not send blocktxn responses for blocks whose height is # more than 10 blocks deep. @@ -628,12 +588,8 @@ class CompactBlocksTest(BitcoinTestFramework): tx = test_node.last_message["blocktxn"].block_transactions.transactions.pop(0) tx.calc_sha256() assert_equal(tx.sha256, block.vtx[index].sha256) - if version == 1: - # Witnesses should have been stripped - assert tx.wit.is_null() - else: - # Check that the witness matches - assert_equal(tx.calc_sha256(True), block.vtx[index].calc_sha256(True)) + # Check that the witness matches + assert_equal(tx.calc_sha256(True), block.vtx[index].calc_sha256(True)) test_node.last_message.pop("blocktxn", None) current_height -= 1 @@ -650,6 +606,15 @@ 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_compactblocks_not_at_tip(self, test_node): node = self.nodes[0] # Test that requesting old compactblocks doesn't work. @@ -727,7 +692,7 @@ class CompactBlocksTest(BitcoinTestFramework): # Test that we don't get disconnected if we relay a compact block with valid header, # but invalid transactions. - def test_invalid_tx_in_compactblock(self, test_node, use_segwit=True): + def test_invalid_tx_in_compactblock(self, test_node): node = self.nodes[0] assert len(self.utxos) utxo = self.utxos[0] @@ -735,17 +700,15 @@ class CompactBlocksTest(BitcoinTestFramework): block = self.build_block_with_transactions(node, utxo, 5) del block.vtx[3] block.hashMerkleRoot = block.calc_merkle_root() - if use_segwit: - # If we're testing with segwit, also drop the coinbase witness, - # but include the witness commitment. - add_witness_commitment(block) - block.vtx[0].wit.vtxinwit = [] + # Drop the coinbase witness but include the witness commitment. + add_witness_commitment(block) + block.vtx[0].wit.vtxinwit = [] block.solve() # Now send the compact block with all transactions prefilled, and # verify that we don't get disconnected. comp_block = HeaderAndShortIDs() - comp_block.initialize_from_block(block, prefill_list=[0, 1, 2, 3, 4], use_witness=use_segwit) + comp_block.initialize_from_block(block, prefill_list=[0, 1, 2, 3, 4], use_witness=True) msg = msg_cmpctblock(comp_block.to_p2p()) test_node.send_and_ping(msg) @@ -759,7 +722,7 @@ class CompactBlocksTest(BitcoinTestFramework): node = self.nodes[0] tip = node.getbestblockhash() peer.get_headers(locator=[int(tip, 16)], hashstop=0) - peer.send_and_ping(msg_sendcmpct(announce=True, version=peer.cmpct_version)) + peer.send_and_ping(msg_sendcmpct(announce=True, version=2)) def test_compactblock_reconstruction_multiple_peers(self, stalling_peer, delivery_peer): node = self.nodes[0] @@ -813,7 +776,7 @@ class CompactBlocksTest(BitcoinTestFramework): def test_highbandwidth_mode_states_via_getpeerinfo(self): # create new p2p connection for a fresh state w/o any prior sendcmpct messages sent - hb_test_node = self.nodes[0].add_p2p_connection(TestP2PConn(cmpct_version=2)) + hb_test_node = self.nodes[0].add_p2p_connection(TestP2PConn()) # assert the RPC getpeerinfo boolean fields `bip152_hb_{to, from}` # match the given parameters for the last peer of a given node @@ -843,9 +806,8 @@ class CompactBlocksTest(BitcoinTestFramework): self.wallet = MiniWallet(self.nodes[0]) # Setup the p2p connections - self.segwit_node = self.nodes[0].add_p2p_connection(TestP2PConn(cmpct_version=2)) - self.old_node = self.nodes[0].add_p2p_connection(TestP2PConn(cmpct_version=1), services=NODE_NETWORK) - self.additional_segwit_node = self.nodes[0].add_p2p_connection(TestP2PConn(cmpct_version=2)) + self.segwit_node = self.nodes[0].add_p2p_connection(TestP2PConn()) + self.additional_segwit_node = self.nodes[0].add_p2p_connection(TestP2PConn()) # We will need UTXOs to construct transactions in later tests. self.make_utxos() @@ -853,11 +815,10 @@ class CompactBlocksTest(BitcoinTestFramework): assert softfork_active(self.nodes[0], "segwit") self.log.info("Testing SENDCMPCT p2p message... ") - self.test_sendcmpct(self.segwit_node, old_node=self.old_node) + self.test_sendcmpct(self.segwit_node) self.test_sendcmpct(self.additional_segwit_node) self.log.info("Testing compactblock construction...") - self.test_compactblock_construction(self.old_node) self.test_compactblock_construction(self.segwit_node) self.log.info("Testing compactblock requests (segwit node)... ") @@ -868,11 +829,9 @@ class CompactBlocksTest(BitcoinTestFramework): self.log.info("Testing getblocktxn handler (segwit node should return witnesses)...") self.test_getblocktxn_handler(self.segwit_node) - self.test_getblocktxn_handler(self.old_node) self.log.info("Testing compactblock requests/announcements not at chain tip...") self.test_compactblocks_not_at_tip(self.segwit_node) - self.test_compactblocks_not_at_tip(self.old_node) self.log.info("Testing handling of incorrect blocktxn responses...") self.test_incorrect_blocktxn_response(self.segwit_node) @@ -885,13 +844,12 @@ class CompactBlocksTest(BitcoinTestFramework): # (Post-segwit activation, blocks won't propagate from node0 to node1 # automatically, so don't bother testing a block announced to node0.) self.log.info("Testing end-to-end block relay...") - self.request_cb_announcements(self.old_node) self.request_cb_announcements(self.segwit_node) - self.test_end_to_end_block_relay([self.segwit_node, self.old_node]) + self.request_cb_announcements(self.additional_segwit_node) + self.test_end_to_end_block_relay([self.segwit_node, self.additional_segwit_node]) self.log.info("Testing handling of invalid compact blocks...") self.test_invalid_tx_in_compactblock(self.segwit_node) - self.test_invalid_tx_in_compactblock(self.old_node) self.log.info("Testing invalid index in cmpctblock message...") self.test_invalid_cmpctblock_message() diff --git a/test/functional/p2p_compactblocks_blocksonly.py b/test/functional/p2p_compactblocks_blocksonly.py index 6367eb26a3..3d0c421a93 100755 --- a/test/functional/p2p_compactblocks_blocksonly.py +++ b/test/functional/p2p_compactblocks_blocksonly.py @@ -48,7 +48,7 @@ class P2PCompactBlocksBlocksOnly(BitcoinTestFramework): p2p_conn_high_bw = self.nodes[1].add_p2p_connection(P2PInterface()) p2p_conn_low_bw = self.nodes[3].add_p2p_connection(P2PInterface()) for conn in [p2p_conn_blocksonly, p2p_conn_high_bw, p2p_conn_low_bw]: - assert_equal(conn.message_count['sendcmpct'], 2) + assert_equal(conn.message_count['sendcmpct'], 1) conn.send_and_ping(msg_sendcmpct(announce=False, version=2)) # Nodes: @@ -74,14 +74,14 @@ class P2PCompactBlocksBlocksOnly(BitcoinTestFramework): # receiving a new valid block at the tip. p2p_conn_blocksonly.send_and_ping(msg_block(block0)) assert_equal(int(self.nodes[0].getbestblockhash(), 16), block0.sha256) - assert_equal(p2p_conn_blocksonly.message_count['sendcmpct'], 2) + assert_equal(p2p_conn_blocksonly.message_count['sendcmpct'], 1) assert_equal(p2p_conn_blocksonly.last_message['sendcmpct'].announce, False) # A normal node participating in transaction relay should request BIP152 # high bandwidth mode upon receiving a new valid block at the tip. p2p_conn_high_bw.send_and_ping(msg_block(block0)) assert_equal(int(self.nodes[1].getbestblockhash(), 16), block0.sha256) - p2p_conn_high_bw.wait_until(lambda: p2p_conn_high_bw.message_count['sendcmpct'] == 3) + p2p_conn_high_bw.wait_until(lambda: p2p_conn_high_bw.message_count['sendcmpct'] == 2) assert_equal(p2p_conn_high_bw.last_message['sendcmpct'].announce, True) # Don't send a block from the p2p_conn_low_bw so the low bandwidth node diff --git a/test/functional/p2p_getaddr_caching.py b/test/functional/p2p_getaddr_caching.py index d375af6fe1..8907c34a89 100755 --- a/test/functional/p2p_getaddr_caching.py +++ b/test/functional/p2p_getaddr_caching.py @@ -14,6 +14,7 @@ from test_framework.p2p import ( from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_equal, + p2p_port, ) # As defined in net_processing. @@ -42,6 +43,12 @@ class AddrReceiver(P2PInterface): class AddrTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 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"], + ] def run_test(self): self.log.info('Fill peer AddrMan with a lot of records') @@ -55,35 +62,66 @@ class AddrTest(BitcoinTestFramework): # only a fraction of all known addresses can be cached and returned. assert(len(self.nodes[0].getnodeaddresses(0)) > int(MAX_ADDR_TO_SEND / (MAX_PCT_ADDR_TO_SEND / 100))) - responses = [] + last_response_on_local_bind = None + last_response_on_onion_bind1 = None + last_response_on_onion_bind2 = None self.log.info('Send many addr requests within short time to receive same response') N = 5 cur_mock_time = int(time.time()) for i in range(N): - addr_receiver = self.nodes[0].add_p2p_connection(AddrReceiver()) - addr_receiver.send_and_ping(msg_getaddr()) + addr_receiver_local = self.nodes[0].add_p2p_connection(AddrReceiver()) + addr_receiver_local.send_and_ping(msg_getaddr()) + addr_receiver_onion1 = self.nodes[0].add_p2p_connection(AddrReceiver(), dstport=self.onion_port1) + addr_receiver_onion1.send_and_ping(msg_getaddr()) + addr_receiver_onion2 = self.nodes[0].add_p2p_connection(AddrReceiver(), dstport=self.onion_port2) + addr_receiver_onion2.send_and_ping(msg_getaddr()) + # Trigger response cur_mock_time += 5 * 60 self.nodes[0].setmocktime(cur_mock_time) - addr_receiver.wait_until(addr_receiver.addr_received) - responses.append(addr_receiver.get_received_addrs()) - for response in responses[1:]: - assert_equal(response, responses[0]) - assert(len(response) == MAX_ADDR_TO_SEND) + addr_receiver_local.wait_until(addr_receiver_local.addr_received) + addr_receiver_onion1.wait_until(addr_receiver_onion1.addr_received) + addr_receiver_onion2.wait_until(addr_receiver_onion2.addr_received) + + if i > 0: + # Responses from different binds should be unique + assert(last_response_on_local_bind != addr_receiver_onion1.get_received_addrs()) + assert(last_response_on_local_bind != addr_receiver_onion2.get_received_addrs()) + assert(last_response_on_onion_bind1 != addr_receiver_onion2.get_received_addrs()) + # Responses on from the same bind should be the same + assert_equal(last_response_on_local_bind, addr_receiver_local.get_received_addrs()) + assert_equal(last_response_on_onion_bind1, addr_receiver_onion1.get_received_addrs()) + assert_equal(last_response_on_onion_bind2, addr_receiver_onion2.get_received_addrs()) + + last_response_on_local_bind = addr_receiver_local.get_received_addrs() + last_response_on_onion_bind1 = addr_receiver_onion1.get_received_addrs() + last_response_on_onion_bind2 = addr_receiver_onion2.get_received_addrs() + + for response in [last_response_on_local_bind, last_response_on_onion_bind1, last_response_on_onion_bind2]: + assert_equal(len(response), MAX_ADDR_TO_SEND) cur_mock_time += 3 * 24 * 60 * 60 self.nodes[0].setmocktime(cur_mock_time) self.log.info('After time passed, see a new response to addr request') - last_addr_receiver = self.nodes[0].add_p2p_connection(AddrReceiver()) - last_addr_receiver.send_and_ping(msg_getaddr()) + addr_receiver_local = self.nodes[0].add_p2p_connection(AddrReceiver()) + addr_receiver_local.send_and_ping(msg_getaddr()) + addr_receiver_onion1 = self.nodes[0].add_p2p_connection(AddrReceiver(), dstport=self.onion_port1) + addr_receiver_onion1.send_and_ping(msg_getaddr()) + addr_receiver_onion2 = self.nodes[0].add_p2p_connection(AddrReceiver(), dstport=self.onion_port2) + addr_receiver_onion2.send_and_ping(msg_getaddr()) + # Trigger response cur_mock_time += 5 * 60 self.nodes[0].setmocktime(cur_mock_time) - last_addr_receiver.wait_until(last_addr_receiver.addr_received) - # new response is different - assert(set(responses[0]) != set(last_addr_receiver.get_received_addrs())) + addr_receiver_local.wait_until(addr_receiver_local.addr_received) + addr_receiver_onion1.wait_until(addr_receiver_onion1.addr_received) + addr_receiver_onion2.wait_until(addr_receiver_onion2.addr_received) + # new response is different + assert(set(last_response_on_local_bind) != set(addr_receiver_local.get_received_addrs())) + assert(set(last_response_on_onion_bind1) != set(addr_receiver_onion1.get_received_addrs())) + assert(set(last_response_on_onion_bind2) != set(addr_receiver_onion2.get_received_addrs())) if __name__ == '__main__': AddrTest().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_message_capture.py b/test/functional/p2p_message_capture.py index 0a7ae44de4..87c77f4540 100755 --- a/test/functional/p2p_message_capture.py +++ b/test/functional/p2p_message_capture.py @@ -43,12 +43,8 @@ def mini_parser(dat_file): break tmp_header = BytesIO(tmp_header_raw) tmp_header.read(TIME_SIZE) # skip the timestamp field - raw_msgtype = tmp_header.read(MSGTYPE_SIZE) - msgtype: bytes = raw_msgtype.split(b'\x00', 1)[0] - remainder = raw_msgtype.split(b'\x00', 1)[1] - assert(len(msgtype) > 0) + msgtype = tmp_header.read(MSGTYPE_SIZE).rstrip(b'\x00') assert(msgtype in MESSAGEMAP) - assert(len(remainder) == 0 or not remainder.decode().isprintable()) length: int = int.from_bytes(tmp_header.read(LENGTH_SIZE), "little") data = f_in.read(length) assert_equal(len(data), length) 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 f377fbaaa6..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, @@ -43,7 +43,6 @@ from test_framework.messages import ( ser_uint256, ser_vector, sha256, - tx_from_hex, ) from test_framework.p2p import ( P2PInterface, @@ -89,6 +88,8 @@ from test_framework.util import ( softfork_active, assert_raises_rpc_error, ) +from test_framework.wallet import MiniWallet + MAX_SIGOP_COST = 80000 @@ -221,9 +222,6 @@ class SegWitTest(BitcoinTestFramework): ] self.supports_cli = False - def skip_test_if_missing_module(self): - self.skip_if_no_wallet() - # Helper functions def build_next_block(self): @@ -247,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) @@ -259,6 +257,7 @@ class SegWitTest(BitcoinTestFramework): self.log.info("Starting tests before segwit activation") self.segwit_active = False + self.wallet = MiniWallet(self.nodes[0]) self.test_non_witness_transaction() self.test_v0_outputs_arent_spendable() @@ -307,7 +306,7 @@ class SegWitTest(BitcoinTestFramework): self.test_node.send_and_ping(msg_no_witness_block(block)) # make sure the block was processed txid = block.vtx[0].sha256 - self.generate(self.nodes[0], 99) # let the block mature + self.generate(self.wallet, 99) # let the block mature # Create a transaction that spends the coinbase tx = CTransaction() @@ -372,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) @@ -586,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 @@ -1379,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") @@ -1387,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. @@ -1999,21 +2002,13 @@ class SegWitTest(BitcoinTestFramework): def serialize(self): return serialize_with_bogus_witness(self.tx) - self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(address_type='bech32'), 5) - self.generate(self.nodes[0], 1) - unspent = next(u for u in self.nodes[0].listunspent() if u['spendable'] and u['address'].startswith('bcrt')) - - raw = self.nodes[0].createrawtransaction([{"txid": unspent['txid'], "vout": unspent['vout']}], {self.nodes[0].getnewaddress(): 1}) - tx = tx_from_hex(raw) + 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(['Superfluous witness record']): + with self.nodes[0].assert_debug_log(['Unknown transaction optional data']): self.test_node.send_and_ping(msg_bogus_tx(tx)) - raw = self.nodes[0].signrawtransactionwithwallet(raw) - assert raw['complete'] - raw = raw['hex'] - tx = tx_from_hex(raw) + tx.wit.vtxinwit = [] # drop witness 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']): + with self.nodes[0].assert_debug_log(['Superfluous witness record']): self.test_node.send_and_ping(msg_bogus_tx(tx)) @subtest 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 9c4e1dd1b1..76d9b045ce 100755 --- a/test/functional/p2p_unrequested_blocks.py +++ b/test/functional/p2p_unrequested_blocks.py @@ -257,16 +257,11 @@ class AcceptBlockTest(BitcoinTestFramework): test_node.send_message(msg_block(block_291)) # At this point we've sent an obviously-bogus block, wait for full processing - # without assuming whether we will be disconnected or not - try: - # Only wait a short while so the test doesn't take forever if we do get - # disconnected - test_node.sync_with_ping(timeout=1) - except AssertionError: - test_node.wait_for_disconnect() - - self.nodes[0].disconnect_p2ps() - test_node = self.nodes[0].add_p2p_connection(P2PInterface()) + # and assume disconnection + test_node.wait_for_disconnect() + + self.nodes[0].disconnect_p2ps() + test_node = self.nodes[0].add_p2p_connection(P2PInterface()) # We should have failed reorg and switched back to 290 (but have block 291) assert_equal(self.nodes[0].getblockcount(), 290) diff --git a/test/functional/rpc_blockchain.py b/test/functional/rpc_blockchain.py index b264f23fb5..d07b144905 100755 --- a/test/functional/rpc_blockchain.py +++ b/test/functional/rpc_blockchain.py @@ -200,7 +200,7 @@ class BlockchainTest(BitcoinTestFramework): 'timeout': 0x7fffffffffffffff, # testdummy does not have a timeout so is set to the max int64 value 'min_activation_height': 0, 'status': 'started', - 'status-next': status_next, + 'status_next': status_next, 'since': 144, 'statistics': { 'period': 144, @@ -220,7 +220,7 @@ class BlockchainTest(BitcoinTestFramework): 'timeout': 9223372036854775807, 'min_activation_height': 0, 'status': 'active', - 'status-next': 'active', + 'status_next': 'active', 'since': 0, }, 'height': 0, @@ -261,12 +261,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(-1, "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(-1, "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') @@ -536,7 +536,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(-1, "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 1a3d14100f..1ad3fc24c9 100755 --- a/test/functional/rpc_createmultisig.py +++ b/test/functional/rpc_createmultisig.py @@ -18,15 +18,18 @@ from test_framework.util import ( assert_equal, ) from test_framework.wallet_util import bytes_to_wif +from test_framework.wallet import ( + MiniWallet, + getnewdestination, +) class RpcCreateMultiSigTest(BitcoinTestFramework): def set_test_params(self): self.setup_clean_chain = True self.num_nodes = 3 self.supports_cli = False - - def skip_test_if_missing_module(self): - self.skip_if_no_wallet() + if self.is_bdb_compiled(): + self.requires_wallet = True def get_keys(self): self.pub = [] @@ -37,15 +40,20 @@ class RpcCreateMultiSigTest(BitcoinTestFramework): k.generate() self.pub.append(k.get_pubkey().get_bytes().hex()) self.priv.append(bytes_to_wif(k.get_bytes(), k.is_compressed)) - self.final = node2.getnewaddress() + if self.is_bdb_compiled(): + self.final = node2.getnewaddress() + else: + self.final = getnewdestination('bech32')[2] def run_test(self): node0, node1, node2 = self.nodes + self.wallet = MiniWallet(test_node=node0) - self.check_addmultisigaddress_errors() + if self.is_bdb_compiled(): + self.check_addmultisigaddress_errors() self.log.info('Generating blocks ...') - self.generate(node0, 149) + self.generate(self.wallet, 149) self.moved = 0 for self.nkeys in [3, 5]: @@ -53,14 +61,12 @@ class RpcCreateMultiSigTest(BitcoinTestFramework): for self.output_type in ["bech32", "p2sh-segwit", "legacy"]: self.get_keys() self.do_multisig() - - self.checkbalances() + if self.is_bdb_compiled(): + self.checkbalances() # Test mixed compressed and uncompressed pubkeys self.log.info('Mixed compressed and uncompressed multisigs are not allowed') - pk0 = node0.getaddressinfo(node0.getnewaddress())['pubkey'] - pk1 = node1.getaddressinfo(node1.getnewaddress())['pubkey'] - pk2 = node2.getaddressinfo(node2.getnewaddress())['pubkey'] + pk0, pk1, pk2 = [getnewdestination('bech32')[0].hex() for _ in range(3)] # decompress pk2 pk_obj = ECPubKey() @@ -68,26 +74,32 @@ class RpcCreateMultiSigTest(BitcoinTestFramework): pk_obj.compressed = False pk2 = pk_obj.get_bytes().hex() - node0.createwallet(wallet_name='wmulti0', disable_private_keys=True) - wmulti0 = node0.get_wallet_rpc('wmulti0') + if self.is_bdb_compiled(): + node0.createwallet(wallet_name='wmulti0', disable_private_keys=True) + wmulti0 = node0.get_wallet_rpc('wmulti0') # Check all permutations of keys because order matters apparently for keys in itertools.permutations([pk0, pk1, pk2]): # Results should be the same as this legacy one legacy_addr = node0.createmultisig(2, keys, 'legacy')['address'] - result = wmulti0.addmultisigaddress(2, keys, '', 'legacy') - assert_equal(legacy_addr, result['address']) - assert 'warnings' not in result + + if self.is_bdb_compiled(): + result = wmulti0.addmultisigaddress(2, keys, '', 'legacy') + assert_equal(legacy_addr, result['address']) + assert 'warnings' not in result # Generate addresses with the segwit types. These should all make legacy addresses + err_msg = ["Unable to make chosen address type, please ensure no uncompressed public keys are present."] + for addr_type in ['bech32', 'p2sh-segwit']: - result = wmulti0.createmultisig(2, keys, addr_type) + result = self.nodes[0].createmultisig(nrequired=2, keys=keys, address_type=addr_type) assert_equal(legacy_addr, result['address']) - assert_equal(result['warnings'], ["Unable to make chosen address type, please ensure no uncompressed public keys are present."]) + assert_equal(result['warnings'], err_msg) - result = wmulti0.addmultisigaddress(2, keys, '', addr_type) - assert_equal(legacy_addr, result['address']) - assert_equal(result['warnings'], ["Unable to make chosen address type, please ensure no uncompressed public keys are present."]) + if self.is_bdb_compiled(): + result = wmulti0.addmultisigaddress(nrequired=2, keys=keys, address_type=addr_type) + assert_equal(legacy_addr, result['address']) + assert_equal(result['warnings'], err_msg) self.log.info('Testing sortedmulti descriptors with BIP 67 test vectors') with open(os.path.join(os.path.dirname(os.path.realpath(__file__)), 'data/rpc_bip67.json'), encoding='utf-8') as f: @@ -126,26 +138,29 @@ class RpcCreateMultiSigTest(BitcoinTestFramework): bal0 = node0.getbalance() bal1 = node1.getbalance() bal2 = node2.getbalance() + balw = self.wallet.get_balance() height = node0.getblockchaininfo()["blocks"] assert 150 < height < 350 total = 149 * 50 + (height - 149 - 100) * 25 assert bal1 == 0 assert bal2 == self.moved - assert bal0 + bal1 + bal2 == total + assert_equal(bal0 + bal1 + bal2 + balw, total) def do_multisig(self): node0, node1, node2 = self.nodes - if 'wmulti' not in node1.listwallets(): - try: - node1.loadwallet('wmulti') - except JSONRPCException as e: - path = os.path.join(self.options.tmpdir, "node1", "regtest", "wallets", "wmulti") - if e.error['code'] == -18 and "Wallet file verification failed. Failed to load database path '{}'. Path does not exist.".format(path) in e.error['message']: - node1.createwallet(wallet_name='wmulti', disable_private_keys=True) - else: - raise - wmulti = node1.get_wallet_rpc('wmulti') + + if self.is_bdb_compiled(): + if 'wmulti' not in node1.listwallets(): + try: + node1.loadwallet('wmulti') + except JSONRPCException as e: + path = os.path.join(self.options.tmpdir, "node1", "regtest", "wallets", "wmulti") + if e.error['code'] == -18 and "Wallet file verification failed. Failed to load database path '{}'. Path does not exist.".format(path) in e.error['message']: + node1.createwallet(wallet_name='wmulti', disable_private_keys=True) + else: + raise + wmulti = node1.get_wallet_rpc('wmulti') # Construct the expected descriptor desc = 'multi({},{})'.format(self.nsigs, ','.join(self.pub)) @@ -158,23 +173,26 @@ class RpcCreateMultiSigTest(BitcoinTestFramework): desc = descsum_create(desc) msig = node2.createmultisig(self.nsigs, self.pub, self.output_type) + assert 'warnings' not in msig madd = msig["address"] mredeem = msig["redeemScript"] assert_equal(desc, msig['descriptor']) if self.output_type == 'bech32': assert madd[0:4] == "bcrt" # actually a bech32 address - # compare against addmultisigaddress - msigw = wmulti.addmultisigaddress(self.nsigs, self.pub, None, self.output_type) - maddw = msigw["address"] - mredeemw = msigw["redeemScript"] - assert_equal(desc, drop_origins(msigw['descriptor'])) - # addmultisigiaddress and createmultisig work the same - assert maddw == madd - assert mredeemw == mredeem - - txid = node0.sendtoaddress(madd, 40) - + if self.is_bdb_compiled(): + # compare against addmultisigaddress + msigw = wmulti.addmultisigaddress(self.nsigs, self.pub, None, self.output_type) + maddw = msigw["address"] + mredeemw = msigw["redeemScript"] + assert_equal(desc, drop_origins(msigw['descriptor'])) + # addmultisigiaddress and createmultisig work the same + assert maddw == madd + assert mredeemw == mredeem + wmulti.unloadwallet() + + spk = bytes.fromhex(node0.validateaddress(madd)["scriptPubKey"]) + txid, _ = self.wallet.send_to(from_node=self.nodes[0], scriptPubKey=spk, amount=1300) tx = node0.getrawtransaction(txid, True) vout = [v["n"] for v in tx["vout"] if madd == v["scriptPubKey"]["address"]] assert len(vout) == 1 @@ -225,8 +243,6 @@ class RpcCreateMultiSigTest(BitcoinTestFramework): txinfo = node0.getrawtransaction(tx, True, blk) self.log.info("n/m=%d/%d %s size=%d vsize=%d weight=%d" % (self.nsigs, self.nkeys, self.output_type, txinfo["size"], txinfo["vsize"], txinfo["weight"])) - wmulti.unloadwallet() - if __name__ == '__main__': RpcCreateMultiSigTest().main() diff --git a/test/functional/rpc_dumptxoutset.py b/test/functional/rpc_dumptxoutset.py index 1721b6ffe8..672c9a53dc 100755 --- a/test/functional/rpc_dumptxoutset.py +++ b/test/functional/rpc_dumptxoutset.py @@ -37,21 +37,25 @@ class DumptxoutsetTest(BitcoinTestFramework): # Blockhash should be deterministic based on mocked time. assert_equal( out['base_hash'], - '6fd417acba2a8738b06fee43330c50d58e6a725046c3d843c8dd7e51d46d1ed6') + '09abf0e7b510f61ca6cf33bab104e9ee99b3528b371d27a2d4b39abb800fba7e') with open(str(expected_path), 'rb') as f: digest = hashlib.sha256(f.read()).hexdigest() # UTXO snapshot hash should be deterministic based on mocked time. assert_equal( - digest, '7ae82c986fa5445678d2a21453bb1c86d39e47af13da137640c2b1cf8093691c') + digest, 'b1bacb602eacf5fbc9a7c2ef6eeb0d229c04e98bdf0c2ea5929012cd0eae3830') assert_equal( - out['txoutset_hash'], 'd4b614f476b99a6e569973bf1c0120d88b1a168076f8ce25691fb41dd1cef149') + out['txoutset_hash'], '1f7e3befd45dc13ae198dfbb22869a9c5c4196f8e9ef9735831af1288033f890') assert_equal(out['nchaintx'], 101) - # Specifying a path to an existing file will fail. + # Specifying a path to an existing or invalid file will fail. assert_raises_rpc_error( -8, '{} already exists'.format(FILENAME), node.dumptxoutset, FILENAME) + invalid_path = str(Path(node.datadir) / "invalid" / "path") + assert_raises_rpc_error( + -8, "Couldn't open file {}.incomplete for writing".format(invalid_path), node.dumptxoutset, invalid_path) + if __name__ == '__main__': DumptxoutsetTest().main() diff --git a/test/functional/rpc_fundrawtransaction.py b/test/functional/rpc_fundrawtransaction.py index 759e43194b..5a870de6c7 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,7 @@ class RawTransactionsTest(BitcoinTestFramework): self.generate(self.nodes[2], 1) self.generate(self.nodes[0], 121) + self.test_weight_calculation() self.test_change_position() self.test_simple() self.test_simple_two_coins() @@ -297,7 +301,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(-1, "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 +408,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.""" @@ -1069,6 +1073,38 @@ class RawTransactionsTest(BitcoinTestFramework): self.nodes[2].unloadwallet("extfund") + 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_generate.py b/test/functional/rpc_generate.py index 47d7814da3..2b1dd20ea1 100755 --- a/test/functional/rpc_generate.py +++ b/test/functional/rpc_generate.py @@ -2,9 +2,10 @@ # 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 generate RPC.""" +"""Test generate* RPCs.""" from test_framework.test_framework import BitcoinTestFramework +from test_framework.wallet import MiniWallet from test_framework.util import ( assert_equal, assert_raises_rpc_error, @@ -16,6 +17,94 @@ class RPCGenerateTest(BitcoinTestFramework): self.num_nodes = 1 def run_test(self): + self.test_generatetoaddress() + self.test_generate() + self.test_generateblock() + + def test_generatetoaddress(self): + self.generatetoaddress(self.nodes[0], 1, 'mneYUmWYsuk7kySiURxCi3AGxrAqZxLgPZ') + assert_raises_rpc_error(-5, "Invalid address", self.generatetoaddress, self.nodes[0], 1, '3J98t1WpEZ73CNmQviecrnyiWrnqRhWNLy') + + def test_generateblock(self): + node = self.nodes[0] + miniwallet = MiniWallet(node) + miniwallet.rescan_utxos() + + self.log.info('Generate an empty block to address') + address = miniwallet.get_address() + hash = self.generateblock(node, output=address, transactions=[])['hash'] + block = node.getblock(blockhash=hash, verbose=2) + assert_equal(len(block['tx']), 1) + assert_equal(block['tx'][0]['vout'][0]['scriptPubKey']['address'], address) + + self.log.info('Generate an empty block to a descriptor') + hash = self.generateblock(node, 'addr(' + address + ')', [])['hash'] + block = node.getblock(blockhash=hash, verbosity=2) + assert_equal(len(block['tx']), 1) + assert_equal(block['tx'][0]['vout'][0]['scriptPubKey']['address'], address) + + self.log.info('Generate an empty block to a combo descriptor with compressed pubkey') + combo_key = '0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798' + combo_address = 'bcrt1qw508d6qejxtdg4y5r3zarvary0c5xw7kygt080' + hash = self.generateblock(node, 'combo(' + combo_key + ')', [])['hash'] + block = node.getblock(hash, 2) + assert_equal(len(block['tx']), 1) + assert_equal(block['tx'][0]['vout'][0]['scriptPubKey']['address'], combo_address) + + self.log.info('Generate an empty block to a combo descriptor with uncompressed pubkey') + combo_key = '0408ef68c46d20596cc3f6ddf7c8794f71913add807f1dc55949fa805d764d191c0b7ce6894c126fce0babc6663042f3dde9b0cf76467ea315514e5a6731149c67' + combo_address = 'mkc9STceoCcjoXEXe6cm66iJbmjM6zR9B2' + hash = self.generateblock(node, 'combo(' + combo_key + ')', [])['hash'] + block = node.getblock(hash, 2) + assert_equal(len(block['tx']), 1) + assert_equal(block['tx'][0]['vout'][0]['scriptPubKey']['address'], combo_address) + + # Generate some extra mempool transactions to verify they don't get mined + for _ in range(10): + miniwallet.send_self_transfer(from_node=node) + + self.log.info('Generate block with txid') + txid = miniwallet.send_self_transfer(from_node=node)['txid'] + hash = self.generateblock(node, address, [txid])['hash'] + block = node.getblock(hash, 1) + assert_equal(len(block['tx']), 2) + assert_equal(block['tx'][1], txid) + + self.log.info('Generate block with raw tx') + rawtx = miniwallet.create_self_transfer()['hex'] + hash = self.generateblock(node, address, [rawtx])['hash'] + + block = node.getblock(hash, 1) + assert_equal(len(block['tx']), 2) + txid = block['tx'][1] + assert_equal(node.getrawtransaction(txid=txid, verbose=False, blockhash=hash), rawtx) + + self.log.info('Fail to generate block with out of order txs') + txid1 = miniwallet.send_self_transfer(from_node=node)['txid'] + utxo1 = miniwallet.get_utxo(txid=txid1) + rawtx2 = miniwallet.create_self_transfer(utxo_to_spend=utxo1)['hex'] + assert_raises_rpc_error(-25, 'TestBlockValidity failed: bad-txns-inputs-missingorspent', self.generateblock, node, address, [rawtx2, txid1]) + + self.log.info('Fail to generate block with txid not in mempool') + missing_txid = '0000000000000000000000000000000000000000000000000000000000000000' + assert_raises_rpc_error(-5, 'Transaction ' + missing_txid + ' not in mempool.', self.generateblock, node, address, [missing_txid]) + + self.log.info('Fail to generate block with invalid raw tx') + invalid_raw_tx = '0000' + assert_raises_rpc_error(-22, 'Transaction decode failed for ' + invalid_raw_tx, self.generateblock, node, address, [invalid_raw_tx]) + + self.log.info('Fail to generate block with invalid address/descriptor') + assert_raises_rpc_error(-5, 'Invalid address or descriptor', self.generateblock, node, '1234', []) + + self.log.info('Fail to generate block with a ranged descriptor') + ranged_descriptor = 'pkh(tpubD6NzVbkrYhZ4XgiXtGrdW5XDAPFCL9h7we1vwNCpn8tGbBcgfVYjXyhWo4E1xkh56hjod1RhGjxbaTLV3X4FyWuejifB9jusQ46QzG87VKp/0/*)' + assert_raises_rpc_error(-8, 'Ranged descriptor not accepted. Maybe pass through deriveaddresses first?', self.generateblock, node, ranged_descriptor, []) + + self.log.info('Fail to generate block with a descriptor missing a private key') + child_descriptor = 'pkh(tpubD6NzVbkrYhZ4XgiXtGrdW5XDAPFCL9h7we1vwNCpn8tGbBcgfVYjXyhWo4E1xkh56hjod1RhGjxbaTLV3X4FyWuejifB9jusQ46QzG87VKp/0\'/0)' + assert_raises_rpc_error(-5, 'Cannot derive script without private keys', self.generateblock, node, child_descriptor, []) + + def test_generate(self): message = ( "generate\n\n" "has been replaced by the -generate " diff --git a/test/functional/rpc_generateblock.py b/test/functional/rpc_generateblock.py deleted file mode 100755 index 7eeb745817..0000000000 --- a/test/functional/rpc_generateblock.py +++ /dev/null @@ -1,100 +0,0 @@ -#!/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 generateblock rpc. -''' - -from test_framework.test_framework import BitcoinTestFramework -from test_framework.wallet import MiniWallet -from test_framework.util import ( - assert_equal, - assert_raises_rpc_error, -) - - -class GenerateBlockTest(BitcoinTestFramework): - def set_test_params(self): - self.num_nodes = 1 - - def run_test(self): - node = self.nodes[0] - miniwallet = MiniWallet(node) - miniwallet.rescan_utxos() - - self.log.info('Generate an empty block to address') - address = miniwallet.get_address() - hash = self.generateblock(node, output=address, transactions=[])['hash'] - block = node.getblock(blockhash=hash, verbose=2) - assert_equal(len(block['tx']), 1) - assert_equal(block['tx'][0]['vout'][0]['scriptPubKey']['address'], address) - - self.log.info('Generate an empty block to a descriptor') - hash = self.generateblock(node, 'addr(' + address + ')', [])['hash'] - block = node.getblock(blockhash=hash, verbosity=2) - assert_equal(len(block['tx']), 1) - assert_equal(block['tx'][0]['vout'][0]['scriptPubKey']['address'], address) - - self.log.info('Generate an empty block to a combo descriptor with compressed pubkey') - combo_key = '0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798' - combo_address = 'bcrt1qw508d6qejxtdg4y5r3zarvary0c5xw7kygt080' - hash = self.generateblock(node, 'combo(' + combo_key + ')', [])['hash'] - block = node.getblock(hash, 2) - assert_equal(len(block['tx']), 1) - assert_equal(block['tx'][0]['vout'][0]['scriptPubKey']['address'], combo_address) - - self.log.info('Generate an empty block to a combo descriptor with uncompressed pubkey') - combo_key = '0408ef68c46d20596cc3f6ddf7c8794f71913add807f1dc55949fa805d764d191c0b7ce6894c126fce0babc6663042f3dde9b0cf76467ea315514e5a6731149c67' - combo_address = 'mkc9STceoCcjoXEXe6cm66iJbmjM6zR9B2' - hash = self.generateblock(node, 'combo(' + combo_key + ')', [])['hash'] - block = node.getblock(hash, 2) - assert_equal(len(block['tx']), 1) - assert_equal(block['tx'][0]['vout'][0]['scriptPubKey']['address'], combo_address) - - # Generate some extra mempool transactions to verify they don't get mined - for _ in range(10): - miniwallet.send_self_transfer(from_node=node) - - self.log.info('Generate block with txid') - txid = miniwallet.send_self_transfer(from_node=node)['txid'] - hash = self.generateblock(node, address, [txid])['hash'] - block = node.getblock(hash, 1) - assert_equal(len(block['tx']), 2) - assert_equal(block['tx'][1], txid) - - self.log.info('Generate block with raw tx') - rawtx = miniwallet.create_self_transfer()['hex'] - hash = self.generateblock(node, address, [rawtx])['hash'] - - block = node.getblock(hash, 1) - assert_equal(len(block['tx']), 2) - txid = block['tx'][1] - assert_equal(node.getrawtransaction(txid=txid, verbose=False, blockhash=hash), rawtx) - - self.log.info('Fail to generate block with out of order txs') - txid1 = miniwallet.send_self_transfer(from_node=node)['txid'] - utxo1 = miniwallet.get_utxo(txid=txid1) - rawtx2 = miniwallet.create_self_transfer(utxo_to_spend=utxo1)['hex'] - assert_raises_rpc_error(-25, 'TestBlockValidity failed: bad-txns-inputs-missingorspent', self.generateblock, node, address, [rawtx2, txid1]) - - self.log.info('Fail to generate block with txid not in mempool') - missing_txid = '0000000000000000000000000000000000000000000000000000000000000000' - assert_raises_rpc_error(-5, 'Transaction ' + missing_txid + ' not in mempool.', self.generateblock, node, address, [missing_txid]) - - self.log.info('Fail to generate block with invalid raw tx') - invalid_raw_tx = '0000' - assert_raises_rpc_error(-22, 'Transaction decode failed for ' + invalid_raw_tx, self.generateblock, node, address, [invalid_raw_tx]) - - self.log.info('Fail to generate block with invalid address/descriptor') - assert_raises_rpc_error(-5, 'Invalid address or descriptor', self.generateblock, node, '1234', []) - - self.log.info('Fail to generate block with a ranged descriptor') - ranged_descriptor = 'pkh(tpubD6NzVbkrYhZ4XgiXtGrdW5XDAPFCL9h7we1vwNCpn8tGbBcgfVYjXyhWo4E1xkh56hjod1RhGjxbaTLV3X4FyWuejifB9jusQ46QzG87VKp/0/*)' - assert_raises_rpc_error(-8, 'Ranged descriptor not accepted. Maybe pass through deriveaddresses first?', self.generateblock, node, ranged_descriptor, []) - - self.log.info('Fail to generate block with a descriptor missing a private key') - child_descriptor = 'pkh(tpubD6NzVbkrYhZ4XgiXtGrdW5XDAPFCL9h7we1vwNCpn8tGbBcgfVYjXyhWo4E1xkh56hjod1RhGjxbaTLV3X4FyWuejifB9jusQ46QzG87VKp/0\'/0)' - assert_raises_rpc_error(-5, 'Cannot derive script without private keys', self.generateblock, node, child_descriptor, []) - -if __name__ == '__main__': - GenerateBlockTest().main() diff --git a/test/functional/rpc_getblockfrompeer.py b/test/functional/rpc_getblockfrompeer.py index b65322d920..41e430d87e 100755 --- a/test/functional/rpc_getblockfrompeer.py +++ b/test/functional/rpc_getblockfrompeer.py @@ -5,6 +5,11 @@ """Test the getblockfrompeer RPC.""" from test_framework.authproxy import JSONRPCException +from test_framework.messages import NODE_WITNESS +from test_framework.p2p import ( + P2P_SERVICES, + P2PInterface, +) from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_equal, @@ -49,14 +54,24 @@ 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, "Expected type string, got number", self.nodes[0].getblockfrompeer, 1234, peer_0_peer_1_id) + assert_raises_rpc_error(-3, "Expected type number, got string", 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) + peers = self.nodes[0].getpeerinfo() + assert_equal(len(peers), 2) + presegwit_peer_id = peers[1]["id"] + assert_raises_rpc_error(-1, "Pre-SegWit peer", self.nodes[0].getblockfrompeer, short_tip, presegwit_peer_id) self.log.info("Successful fetch") result = self.nodes[0].getblockfrompeer(short_tip, peer_0_peer_1_id) @@ -66,5 +81,6 @@ class GetBlockFromPeerTest(BitcoinTestFramework): self.log.info("Don't fetch blocks we already have") assert_raises_rpc_error(-1, "Block already downloaded", self.nodes[0].getblockfrompeer, short_tip, peer_0_peer_1_id) + if __name__ == '__main__': GetBlockFromPeerTest().main() diff --git a/test/functional/rpc_help.py b/test/functional/rpc_help.py index 3b6413d4a6..5b7e724728 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(-1, "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_mempool_entry_fee_fields_deprecation.py b/test/functional/rpc_mempool_entry_fee_fields_deprecation.py deleted file mode 100755 index 82761ff7c8..0000000000 --- a/test/functional/rpc_mempool_entry_fee_fields_deprecation.py +++ /dev/null @@ -1,67 +0,0 @@ -#!/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 deprecation of fee fields from top level mempool entry object""" - -from test_framework.blocktools import COIN -from test_framework.test_framework import BitcoinTestFramework -from test_framework.util import assert_equal -from test_framework.wallet import MiniWallet - - -def assertions_helper(new_object, deprecated_object, deprecated_fields): - for field in deprecated_fields: - assert field in deprecated_object - assert field not in new_object - - -class MempoolFeeFieldsDeprecationTest(BitcoinTestFramework): - def set_test_params(self): - self.num_nodes = 2 - self.extra_args = [[], ["-deprecatedrpc=fees"]] - - def run_test(self): - # we get spendable outputs from the premined chain starting - # at block 76. see BitcoinTestFramework._initialize_chain() for details - self.wallet = MiniWallet(self.nodes[0]) - self.wallet.rescan_utxos() - - # we create the tx on the first node and wait until it syncs to node_deprecated - # thus, any differences must be coming from getmempoolentry or getrawmempool - tx = self.wallet.send_self_transfer(from_node=self.nodes[0]) - self.nodes[1].sendrawtransaction(tx["hex"]) - - deprecated_fields = ["ancestorfees", "descendantfees", "modifiedfee", "fee"] - self.test_getmempoolentry(tx["txid"], deprecated_fields) - self.test_getrawmempool(tx["txid"], deprecated_fields) - self.test_deprecated_fields_match(tx["txid"]) - - def test_getmempoolentry(self, txid, deprecated_fields): - - self.log.info("Test getmempoolentry rpc") - entry = self.nodes[0].getmempoolentry(txid) - deprecated_entry = self.nodes[1].getmempoolentry(txid) - assertions_helper(entry, deprecated_entry, deprecated_fields) - - def test_getrawmempool(self, txid, deprecated_fields): - - self.log.info("Test getrawmempool rpc") - entry = self.nodes[0].getrawmempool(verbose=True)[txid] - deprecated_entry = self.nodes[1].getrawmempool(verbose=True)[txid] - assertions_helper(entry, deprecated_entry, deprecated_fields) - - def test_deprecated_fields_match(self, txid): - - self.log.info("Test deprecated fee fields match new fees object") - entry = self.nodes[0].getmempoolentry(txid) - deprecated_entry = self.nodes[1].getmempoolentry(txid) - - assert_equal(deprecated_entry["fee"], entry["fees"]["base"]) - assert_equal(deprecated_entry["modifiedfee"], entry["fees"]["modified"]) - assert_equal(deprecated_entry["descendantfees"], entry["fees"]["descendant"] * COIN) - assert_equal(deprecated_entry["ancestorfees"], entry["fees"]["ancestor"] * COIN) - - -if __name__ == "__main__": - MempoolFeeFieldsDeprecationTest().main() diff --git a/test/functional/rpc_mempool_info.py b/test/functional/rpc_mempool_info.py new file mode 100755 index 0000000000..9b5a3b9112 --- /dev/null +++ b/test/functional/rpc_mempool_info.py @@ -0,0 +1,100 @@ +#!/usr/bin/env python3 +# Copyright (c) 2014-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 RPCs that retrieve information from the mempool.""" + +from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import ( + assert_equal, + assert_raises_rpc_error, +) +from test_framework.wallet import MiniWallet + + +class RPCMempoolInfoTest(BitcoinTestFramework): + def set_test_params(self): + self.num_nodes = 1 + + def run_test(self): + self.wallet = MiniWallet(self.nodes[0]) + self.wallet.rescan_utxos() + confirmed_utxo = self.wallet.get_utxo() + + # Create a tree of unconfirmed transactions in the mempool: + # txA + # / \ + # / \ + # / \ + # / \ + # / \ + # txB txC + # / \ / \ + # / \ / \ + # txD txE txF txG + # \ / + # \ / + # txH + + def create_tx(**kwargs): + return self.wallet.send_self_transfer_multi( + from_node=self.nodes[0], + **kwargs, + ) + + txA = create_tx(utxos_to_spend=[confirmed_utxo], num_outputs=2) + txB = create_tx(utxos_to_spend=[txA["new_utxos"][0]], num_outputs=2) + txC = create_tx(utxos_to_spend=[txA["new_utxos"][1]], num_outputs=2) + txD = create_tx(utxos_to_spend=[txB["new_utxos"][0]], num_outputs=1) + txE = create_tx(utxos_to_spend=[txB["new_utxos"][1]], num_outputs=1) + txF = create_tx(utxos_to_spend=[txC["new_utxos"][0]], num_outputs=2) + txG = create_tx(utxos_to_spend=[txC["new_utxos"][1]], num_outputs=1) + txH = create_tx(utxos_to_spend=[txE["new_utxos"][0],txF["new_utxos"][0]], num_outputs=1) + txidA, txidB, txidC, txidD, txidE, txidF, txidG, txidH = [ + tx["txid"] for tx in [txA, txB, txC, txD, txE, txF, txG, txH] + ] + + mempool = self.nodes[0].getrawmempool() + assert_equal(len(mempool), 8) + for txid in [txidA, txidB, txidC, txidD, txidE, txidF, txidG, txidH]: + assert_equal(txid in mempool, True) + + self.log.info("Find transactions spending outputs") + result = self.nodes[0].gettxspendingprevout([ {'txid' : confirmed_utxo['txid'], 'vout' : 0}, {'txid' : txidA, 'vout' : 1} ]) + assert_equal(result, [ {'txid' : confirmed_utxo['txid'], 'vout' : 0, 'spendingtxid' : txidA}, {'txid' : txidA, 'vout' : 1, 'spendingtxid' : txidC} ]) + + self.log.info("Find transaction spending multiple outputs") + result = self.nodes[0].gettxspendingprevout([ {'txid' : txidE, 'vout' : 0}, {'txid' : txidF, 'vout' : 0} ]) + assert_equal(result, [ {'txid' : txidE, 'vout' : 0, 'spendingtxid' : txidH}, {'txid' : txidF, 'vout' : 0, 'spendingtxid' : txidH} ]) + + self.log.info("Find no transaction when output is unspent") + result = self.nodes[0].gettxspendingprevout([ {'txid' : txidH, 'vout' : 0} ]) + assert_equal(result, [ {'txid' : txidH, 'vout' : 0} ]) + result = self.nodes[0].gettxspendingprevout([ {'txid' : txidA, 'vout' : 5} ]) + assert_equal(result, [ {'txid' : txidA, 'vout' : 5} ]) + + self.log.info("Mixed spent and unspent outputs") + result = self.nodes[0].gettxspendingprevout([ {'txid' : txidB, 'vout' : 0}, {'txid' : txidG, 'vout' : 3} ]) + assert_equal(result, [ {'txid' : txidB, 'vout' : 0, 'spendingtxid' : txidD}, {'txid' : txidG, 'vout' : 3} ]) + + self.log.info("Unknown input fields") + assert_raises_rpc_error(-3, "Unexpected key unknown", self.nodes[0].gettxspendingprevout, [{'txid' : txidC, 'vout' : 1, 'unknown' : 42}]) + + self.log.info("Invalid vout provided") + assert_raises_rpc_error(-8, "Invalid parameter, vout cannot be negative", self.nodes[0].gettxspendingprevout, [{'txid' : txidA, 'vout' : -1}]) + + self.log.info("Invalid txid provided") + assert_raises_rpc_error(-3, "Expected type string for txid, got number", self.nodes[0].gettxspendingprevout, [{'txid' : 42, 'vout' : 0}]) + + self.log.info("Missing outputs") + assert_raises_rpc_error(-8, "Invalid parameter, outputs are missing", self.nodes[0].gettxspendingprevout, []) + + self.log.info("Missing vout") + assert_raises_rpc_error(-3, "Missing vout", self.nodes[0].gettxspendingprevout, [{'txid' : txidA}]) + + self.log.info("Missing txid") + assert_raises_rpc_error(-3, "Missing txid", self.nodes[0].gettxspendingprevout, [{'vout' : 3}]) + + +if __name__ == '__main__': + RPCMempoolInfoTest().main() diff --git a/test/functional/rpc_misc.py b/test/functional/rpc_misc.py index 2f1796d7cc..f6ee6a5215 100755 --- a/test/functional/rpc_misc.py +++ b/test/functional/rpc_misc.py @@ -27,7 +27,7 @@ class RpcMiscTest(BitcoinTestFramework): self.log.info("test CHECK_NONFATAL") assert_raises_rpc_error( -1, - 'Internal bug detected: \'request.params[9].get_str() != "trigger_internal_bug"\'', + 'Internal bug detected: "request.params[9].get_str() != "trigger_internal_bug""', lambda: node.echo(arg9='trigger_internal_bug'), ) @@ -56,9 +56,6 @@ class RpcMiscTest(BitcoinTestFramework): self.log.info("test logging rpc and help") - # Test logging RPC returns the expected number of logging categories. - assert_equal(len(node.logging()), 27) - # Test toggling a logging category on/off/on with the logging RPC. assert_equal(node.logging()['qt'], True) node.logging(exclude=['qt']) diff --git a/test/functional/rpc_net.py b/test/functional/rpc_net.py index 81a3cfee97..ad8ba06824 100755 --- a/test/functional/rpc_net.py +++ b/test/functional/rpc_net.py @@ -257,6 +257,10 @@ class NetTest(BitcoinTestFramework): assert_equal(node.addpeeraddress(address="", port=8333), {"success": False}) assert_equal(node.getnodeaddresses(count=0), []) + self.log.debug("Test that adding an address with invalid port fails") + assert_raises_rpc_error(-1, "JSON integer out of range", self.nodes[0].addpeeraddress, address="1.2.3.4", port=-1) + assert_raises_rpc_error(-1, "JSON integer out of range", self.nodes[0].addpeeraddress,address="1.2.3.4", port=65536) + self.log.debug("Test that adding a valid address to the tried table succeeds") assert_equal(node.addpeeraddress(address="1.2.3.4", tried=True, port=8333), {"success": True}) with node.assert_debug_log(expected_msgs=["CheckAddrman: new 0, tried 1, total 1 started"]): 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 b037807b53..4583ca25cf 100755 --- a/test/functional/rpc_psbt.py +++ b/test/functional/rpc_psbt.py @@ -9,7 +9,25 @@ 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 ( + 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 ( assert_approx, @@ -17,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): @@ -431,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'] @@ -441,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: @@ -448,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 @@ -615,8 +638,8 @@ class PSBTTest(BitcoinTestFramework): self.nodes[1].createwallet("extfund") wallet = self.nodes[1].get_wallet_rpc("extfund") - # Make a weird but signable script. sh(pkh()) descriptor accomplishes this - desc = descsum_create("sh(pkh({}))".format(privkey)) + # Make a weird but signable script. sh(wsh(pkh())) descriptor accomplishes this + desc = descsum_create("sh(wsh(pkh({})))".format(privkey)) if self.options.descriptors: res = self.nodes[0].importdescriptors([{"desc": desc, "timestamp": "now"}]) else: @@ -634,7 +657,7 @@ class PSBTTest(BitcoinTestFramework): assert_raises_rpc_error(-4, "Insufficient funds", wallet.walletcreatefundedpsbt, [ext_utxo], {self.nodes[0].getnewaddress(): 15}) # But funding should work when the solving data is provided - psbt = wallet.walletcreatefundedpsbt([ext_utxo], {self.nodes[0].getnewaddress(): 15}, 0, {"add_inputs": True, "solving_data": {"pubkeys": [addr_info['pubkey']], "scripts": [addr_info["embedded"]["scriptPubKey"]]}}) + psbt = wallet.walletcreatefundedpsbt([ext_utxo], {self.nodes[0].getnewaddress(): 15}, 0, {"add_inputs": True, "solving_data": {"pubkeys": [addr_info['pubkey']], "scripts": [addr_info["embedded"]["scriptPubKey"], addr_info["embedded"]["embedded"]["scriptPubKey"]]}}) signed = wallet.walletprocesspsbt(psbt['psbt']) assert not signed['complete'] signed = self.nodes[0].walletprocesspsbt(signed['psbt']) @@ -655,10 +678,11 @@ class PSBTTest(BitcoinTestFramework): break psbt_in = dec["inputs"][input_idx] # Calculate the input weight - # (prevout + sequence + length of scriptSig + 2 bytes buffer) * 4 + len of scriptwitness + # (prevout + sequence + length of scriptSig + scriptsig + 1 byte buffer) * WITNESS_SCALE_FACTOR + num scriptWitness stack items + (length of stack item + stack item) * N stack items + 1 byte buffer len_scriptsig = len(psbt_in["final_scriptSig"]["hex"]) // 2 if "final_scriptSig" in psbt_in else 0 - len_scriptwitness = len(psbt_in["final_scriptwitness"]["hex"]) // 2 if "final_scriptwitness" in psbt_in else 0 - input_weight = ((41 + len_scriptsig + 2) * 4) + len_scriptwitness + len_scriptsig += len(ser_compact_size(len_scriptsig)) + 1 + len_scriptwitness = (sum([(len(x) // 2) + len(ser_compact_size(len(x) // 2)) for x in psbt_in["final_scriptwitness"]]) + len(psbt_in["final_scriptwitness"]) + 1) if "final_scriptwitness" in psbt_in else 0 + input_weight = ((40 + len_scriptsig) * WITNESS_SCALE_FACTOR) + len_scriptwitness low_input_weight = input_weight // 2 high_input_weight = input_weight * 2 @@ -718,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 a839af0288..a858292dd4 100755 --- a/test/functional/rpc_rawtransaction.py +++ b/test/functional/rpc_rawtransaction.py @@ -17,6 +17,7 @@ from decimal import Decimal from test_framework.blocktools import COINBASE_MATURITY from test_framework.messages import ( + MAX_BIP125_RBF_SEQUENCE, CTransaction, tx_from_hex, ) @@ -24,7 +25,10 @@ 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 import ( + getnewdestination, + MiniWallet, ) @@ -52,79 +56,67 @@ class multidict(dict): class RawTransactionsTest(BitcoinTestFramework): def set_test_params(self): self.setup_clean_chain = True - self.num_nodes = 4 + self.num_nodes = 3 self.extra_args = [ ["-txindex"], ["-txindex"], - ["-txindex"], [], ] # whitelist all peers to speed up tx relay / mempool sync for args in self.extra_args: args.append("-whitelist=noban@127.0.0.1") + self.requires_wallet = self.is_specified_wallet_compiled() self.supports_cli = False - def skip_test_if_missing_module(self): - self.skip_if_no_wallet() - def setup_network(self): super().setup_network() self.connect_nodes(0, 2) def run_test(self): + self.wallet = MiniWallet(self.nodes[0]) self.log.info("Prepare some coins for multiple *rawtransaction commands") - self.generate(self.nodes[2], 1) + self.generate(self.wallet, 10) self.generate(self.nodes[0], COINBASE_MATURITY + 1) - for amount in [1.5, 1.0, 5.0]: - self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), amount) - self.sync_all() - self.generate(self.nodes[0], 5) self.getrawtransaction_tests() self.createrawtransaction_tests() - self.signrawtransactionwithwallet_tests() self.sendrawtransaction_tests() self.sendrawtransaction_testmempoolaccept_tests() self.decoderawtransaction_tests() self.transaction_version_number_tests() - if not self.options.descriptors: + if self.requires_wallet and not self.options.descriptors: self.raw_multisig_transaction_legacy_tests() def getrawtransaction_tests(self): - addr = self.nodes[1].getnewaddress() - txid = self.nodes[0].sendtoaddress(addr, 10) + tx = self.wallet.send_self_transfer(from_node=self.nodes[0]) self.generate(self.nodes[0], 1) - vout = find_vout_for_address(self.nodes[1], txid, addr) - rawTx = self.nodes[1].createrawtransaction([{'txid': txid, 'vout': vout}], {self.nodes[1].getnewaddress(): 9.999}) - rawTxSigned = self.nodes[1].signrawtransactionwithwallet(rawTx) - txId = self.nodes[1].sendrawtransaction(rawTxSigned['hex']) - self.generateblock(self.nodes[0], output=self.nodes[0].getnewaddress(), transactions=[rawTxSigned['hex']]) + txId = tx['txid'] err_msg = ( "No such mempool transaction. Use -txindex or provide a block hash to enable" " blockchain transaction queries. Use gettransaction for wallet transactions." ) - for n in [0, 3]: + for n in [0, 2]: self.log.info(f"Test getrawtransaction {'with' if n == 0 else 'without'} -txindex") if n == 0: # With -txindex. # 1. valid parameters - only supply txid - assert_equal(self.nodes[n].getrawtransaction(txId), rawTxSigned['hex']) + assert_equal(self.nodes[n].getrawtransaction(txId), tx['hex']) # 2. valid parameters - supply txid and 0 for non-verbose - assert_equal(self.nodes[n].getrawtransaction(txId, 0), rawTxSigned['hex']) + assert_equal(self.nodes[n].getrawtransaction(txId, 0), tx['hex']) # 3. valid parameters - supply txid and False for non-verbose - assert_equal(self.nodes[n].getrawtransaction(txId, False), rawTxSigned['hex']) + assert_equal(self.nodes[n].getrawtransaction(txId, False), tx['hex']) # 4. valid parameters - supply txid and 1 for verbose. # We only check the "hex" field of the output so we don't need to update this test every time the output format changes. - assert_equal(self.nodes[n].getrawtransaction(txId, 1)["hex"], rawTxSigned['hex']) + assert_equal(self.nodes[n].getrawtransaction(txId, 1)["hex"], tx['hex']) # 5. valid parameters - supply txid and True for non-verbose - assert_equal(self.nodes[n].getrawtransaction(txId, True)["hex"], rawTxSigned['hex']) + assert_equal(self.nodes[n].getrawtransaction(txId, True)["hex"], tx['hex']) else: # Without -txindex, expect to raise. for verbose in [None, 0, False, 1, True]: @@ -132,18 +124,18 @@ 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(-1, "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(-1, "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(-1, "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.nodes[2].sendtoaddress(self.nodes[1].getnewaddress(), 1) + tx = self.wallet.send_self_transfer(from_node=self.nodes[2])['txid'] block1, block2 = self.generate(self.nodes[2], 2) - for n in [0, 3]: + for n in [0, 2]: self.log.info(f"Test getrawtransaction {'with' if n == 0 else 'without'} -txindex, with blockhash") # We should be able to get the raw transaction by providing the correct block gottx = self.nodes[n].getrawtransaction(txid=tx, verbose=True, blockhash=block1) @@ -160,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(-1, "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" @@ -189,8 +181,8 @@ class RawTransactionsTest(BitcoinTestFramework): # 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(-1, "JSON value of type string is not of expected type object", self.nodes[0].createrawtransaction, ['foo'], {}) + assert_raises_rpc_error(-1, "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}], {}) @@ -200,21 +192,22 @@ class RawTransactionsTest(BitcoinTestFramework): # sequence number out of range for invalid_seq in [-1, 4294967296]: inputs = [{'txid': TXID, 'vout': 1, 'sequence': invalid_seq}] - outputs = {self.nodes[0].getnewaddress(): 1} + address = getnewdestination()[2] + outputs = {address: 1} assert_raises_rpc_error(-8, 'Invalid parameter, sequence number is out of range', self.nodes[0].createrawtransaction, inputs, outputs) # with valid sequence number for valid_seq in [1000, 4294967294]: inputs = [{'txid': TXID, 'vout': 1, 'sequence': valid_seq}] - outputs = {self.nodes[0].getnewaddress(): 1} + address = getnewdestination()[2] + outputs = {address: 1} rawtx = self.nodes[0].createrawtransaction(inputs, outputs) decrawtx = self.nodes[0].decoderawtransaction(rawtx) assert_equal(decrawtx['vin'][0]['sequence'], valid_seq) # Test `createrawtransaction` invalid `outputs` - address = self.nodes[0].getnewaddress() - address2 = self.nodes[0].getnewaddress() - assert_raises_rpc_error(-1, "JSON value is not an array as expected", self.nodes[0].createrawtransaction, [], 'foo') + address = getnewdestination()[2] + assert_raises_rpc_error(-1, "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'}) @@ -228,6 +221,10 @@ class RawTransactionsTest(BitcoinTestFramework): assert_raises_rpc_error(-8, "Invalid parameter, key-value pair must contain exactly one key", self.nodes[0].createrawtransaction, [], [{'a': 1, 'b': 2}]) assert_raises_rpc_error(-8, "Invalid parameter, key-value pair not an object as expected", self.nodes[0].createrawtransaction, [], [['key-value pair1'], ['2']]) + # 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': 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(-8, "Invalid parameter, locktime out of range", self.nodes[0].createrawtransaction, [], {}, -1) @@ -245,6 +242,7 @@ class RawTransactionsTest(BitcoinTestFramework): self.nodes[2].createrawtransaction(inputs=[{'txid': TXID, 'vout': 9}], outputs=[{address: 99}]), ) # Two outputs + address2 = getnewdestination()[2] tx = tx_from_hex(self.nodes[2].createrawtransaction(inputs=[{'txid': TXID, 'vout': 9}], outputs=OrderedDict([(address, 99), (address2, 99)]))) assert_equal(len(tx.vout), 2) assert_equal( @@ -259,122 +257,53 @@ class RawTransactionsTest(BitcoinTestFramework): self.nodes[2].createrawtransaction(inputs=[{'txid': TXID, 'vout': 9}], outputs=[{address: 99}, {address2: 99}, {'data': '99'}]), ) - def signrawtransactionwithwallet_tests(self): - for type in ["bech32", "p2sh-segwit", "legacy"]: - self.log.info(f"Test signrawtransactionwithwallet with missing prevtx info ({type})") - addr = self.nodes[0].getnewaddress("", type) - addrinfo = self.nodes[0].getaddressinfo(addr) - pubkey = addrinfo["scriptPubKey"] - inputs = [{'txid': TXID, 'vout': 3, 'sequence': 1000}] - outputs = {self.nodes[0].getnewaddress(): 1} - rawtx = self.nodes[0].createrawtransaction(inputs, outputs) - - prevtx = dict(txid=TXID, scriptPubKey=pubkey, vout=3, amount=1) - succ = self.nodes[0].signrawtransactionwithwallet(rawtx, [prevtx]) - assert succ["complete"] - - if type == "legacy": - del prevtx["amount"] - succ = self.nodes[0].signrawtransactionwithwallet(rawtx, [prevtx]) - assert succ["complete"] - else: - assert_raises_rpc_error(-3, "Missing amount", self.nodes[0].signrawtransactionwithwallet, rawtx, [ - { - "txid": TXID, - "scriptPubKey": pubkey, - "vout": 3, - } - ]) - - assert_raises_rpc_error(-3, "Missing vout", self.nodes[0].signrawtransactionwithwallet, rawtx, [ - { - "txid": TXID, - "scriptPubKey": pubkey, - "amount": 1, - } - ]) - assert_raises_rpc_error(-3, "Missing txid", self.nodes[0].signrawtransactionwithwallet, rawtx, [ - { - "scriptPubKey": pubkey, - "vout": 3, - "amount": 1, - } - ]) - assert_raises_rpc_error(-3, "Missing scriptPubKey", self.nodes[0].signrawtransactionwithwallet, rawtx, [ - { - "txid": TXID, - "vout": 3, - "amount": 1 - } - ]) - def sendrawtransaction_tests(self): self.log.info("Test sendrawtransaction with missing input") inputs = [{'txid': TXID, 'vout': 1}] # won't exist - outputs = {self.nodes[0].getnewaddress(): 4.998} + address = getnewdestination()[2] + outputs = {address: 4.998} rawtx = self.nodes[2].createrawtransaction(inputs, outputs) - rawtx = self.nodes[2].signrawtransactionwithwallet(rawtx) - assert_raises_rpc_error(-25, "bad-txns-inputs-missingorspent", self.nodes[2].sendrawtransaction, rawtx['hex']) + assert_raises_rpc_error(-25, "bad-txns-inputs-missingorspent", self.nodes[2].sendrawtransaction, rawtx) def sendrawtransaction_testmempoolaccept_tests(self): self.log.info("Test sendrawtransaction/testmempoolaccept with maxfeerate") fee_exceeds_max = "Fee exceeds maximum configured by user (e.g. -maxtxfee, maxfeerate)" # Test a transaction with a small fee. - txId = self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), 1.0) - rawTx = self.nodes[0].getrawtransaction(txId, True) - vout = next(o for o in rawTx['vout'] if o['value'] == Decimal('1.00000000')) - - self.sync_all() - inputs = [{"txid": txId, "vout": vout['n']}] - # Fee 10,000 satoshis, (1 - (10000 sat * 0.00000001 BTC/sat)) = 0.9999 - outputs = {self.nodes[0].getnewaddress(): Decimal("0.99990000")} - rawTx = self.nodes[2].createrawtransaction(inputs, outputs) - rawTxSigned = self.nodes[2].signrawtransactionwithwallet(rawTx) - assert_equal(rawTxSigned['complete'], True) - # Fee 10,000 satoshis, ~100 b transaction, fee rate should land around 100 sat/byte = 0.00100000 BTC/kB + # Fee rate is 0.00100000 BTC/kvB + tx = self.wallet.create_self_transfer(fee_rate=Decimal('0.00100000')) # Thus, testmempoolaccept should reject - testres = self.nodes[2].testmempoolaccept([rawTxSigned['hex']], 0.00001000)[0] + testres = self.nodes[2].testmempoolaccept([tx['hex']], 0.00001000)[0] assert_equal(testres['allowed'], False) assert_equal(testres['reject-reason'], 'max-fee-exceeded') # and sendrawtransaction should throw - assert_raises_rpc_error(-25, fee_exceeds_max, self.nodes[2].sendrawtransaction, rawTxSigned['hex'], 0.00001000) + assert_raises_rpc_error(-25, fee_exceeds_max, self.nodes[2].sendrawtransaction, tx['hex'], 0.00001000) # and the following calls should both succeed - testres = self.nodes[2].testmempoolaccept(rawtxs=[rawTxSigned['hex']])[0] + testres = self.nodes[2].testmempoolaccept(rawtxs=[tx['hex']])[0] assert_equal(testres['allowed'], True) - self.nodes[2].sendrawtransaction(hexstring=rawTxSigned['hex']) + self.nodes[2].sendrawtransaction(hexstring=tx['hex']) # Test a transaction with a large fee. - txId = self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), 1.0) - rawTx = self.nodes[0].getrawtransaction(txId, True) - vout = next(o for o in rawTx['vout'] if o['value'] == Decimal('1.00000000')) - - self.sync_all() - inputs = [{"txid": txId, "vout": vout['n']}] - # Fee 2,000,000 satoshis, (1 - (2000000 sat * 0.00000001 BTC/sat)) = 0.98 - outputs = {self.nodes[0].getnewaddress() : Decimal("0.98000000")} - rawTx = self.nodes[2].createrawtransaction(inputs, outputs) - rawTxSigned = self.nodes[2].signrawtransactionwithwallet(rawTx) - assert_equal(rawTxSigned['complete'], True) - # Fee 2,000,000 satoshis, ~100 b transaction, fee rate should land around 20,000 sat/byte = 0.20000000 BTC/kB + # Fee rate is 0.20000000 BTC/kvB + tx = self.wallet.create_self_transfer(fee_rate=Decimal("0.20000000")) # Thus, testmempoolaccept should reject - testres = self.nodes[2].testmempoolaccept([rawTxSigned['hex']])[0] + testres = self.nodes[2].testmempoolaccept([tx['hex']])[0] assert_equal(testres['allowed'], False) assert_equal(testres['reject-reason'], 'max-fee-exceeded') # and sendrawtransaction should throw - assert_raises_rpc_error(-25, fee_exceeds_max, self.nodes[2].sendrawtransaction, rawTxSigned['hex']) + assert_raises_rpc_error(-25, fee_exceeds_max, self.nodes[2].sendrawtransaction, tx['hex']) # and the following calls should both succeed - testres = self.nodes[2].testmempoolaccept(rawtxs=[rawTxSigned['hex']], maxfeerate='0.20000000')[0] + testres = self.nodes[2].testmempoolaccept(rawtxs=[tx['hex']], maxfeerate='0.20000000')[0] assert_equal(testres['allowed'], True) - self.nodes[2].sendrawtransaction(hexstring=rawTxSigned['hex'], maxfeerate='0.20000000') + self.nodes[2].sendrawtransaction(hexstring=tx['hex'], maxfeerate='0.20000000') self.log.info("Test sendrawtransaction/testmempoolaccept with tx already in the chain") self.generate(self.nodes[2], 1) for node in self.nodes: - testres = node.testmempoolaccept([rawTxSigned['hex']])[0] + testres = node.testmempoolaccept([tx['hex']])[0] assert_equal(testres['allowed'], False) assert_equal(testres['reject-reason'], 'txn-already-known') - assert_raises_rpc_error(-27, 'Transaction already in block chain', node.sendrawtransaction, rawTxSigned['hex']) + assert_raises_rpc_error(-27, 'Transaction already in block chain', node.sendrawtransaction, tx['hex']) def decoderawtransaction_tests(self): self.log.info("Test decoderawtransaction") 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/rpc_users.py b/test/functional/rpc_users.py index 7cedb4336b..1a35a57802 100755 --- a/test/functional/rpc_users.py +++ b/test/functional/rpc_users.py @@ -107,6 +107,9 @@ class HTTPBasicsTest(BitcoinTestFramework): self.stop_node(0) self.nodes[0].assert_start_raises_init_error(expected_msg=init_error, extra_args=['-rpcauth=foo']) self.nodes[0].assert_start_raises_init_error(expected_msg=init_error, extra_args=['-rpcauth=foo:bar']) + self.nodes[0].assert_start_raises_init_error(expected_msg=init_error, extra_args=['-rpcauth=foo:bar:baz']) + self.nodes[0].assert_start_raises_init_error(expected_msg=init_error, extra_args=['-rpcauth=foo$bar:baz']) + self.nodes[0].assert_start_raises_init_error(expected_msg=init_error, extra_args=['-rpcauth=foo$bar$baz']) self.log.info('Check that failure to write cookie file will abort the node gracefully') cookie_file = os.path.join(get_datadir_path(self.options.tmpdir, 0), self.chain, '.cookie.tmp') diff --git a/test/functional/test_framework/address.py b/test/functional/test_framework/address.py index c7fbf679b6..92244b5ed8 100644 --- a/test/functional/test_framework/address.py +++ b/test/functional/test_framework/address.py @@ -35,7 +35,7 @@ class AddressType(enum.Enum): legacy = 'legacy' # P2PKH -chars = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz' +b58chars = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz' def create_deterministic_address_bcrt1_p2tr_op_true(): @@ -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) @@ -59,10 +58,10 @@ def byte_to_base58(b, version): b += hash256(b)[:4] # append checksum value = int.from_bytes(b, 'big') while value > 0: - result = chars[value % 58] + result + result = b58chars[value % 58] + result value //= 58 while b[0] == 0: - result = chars[0] + result + result = b58chars[0] + result b = b[1:] return result @@ -76,8 +75,8 @@ def base58_to_byte(s): n = 0 for c in s: n *= 58 - assert c in chars - digit = chars.index(c) + assert c in b58chars + digit = b58chars.index(c) n += digit h = '%x' % n if len(h) % 2: @@ -85,14 +84,14 @@ def base58_to_byte(s): res = n.to_bytes((n.bit_length() + 7) // 8, 'big') pad = 0 for c in s: - if c == chars[0]: + if c == b58chars[0]: pad += 1 else: break res = b'\x00' * pad + res - # Assert if the checksum is invalid - assert_equal(hash256(res[:-4])[:4], res[-4:]) + if hash256(res[:-4])[:4] != res[-4:]: + raise ValueError('Invalid Base58Check checksum') return res[1:-4], int(res[0]) @@ -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 f57b6e7494..4e757b64ca 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,12 @@ 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 + def sha256(s): return hashlib.sha256(s).digest() @@ -208,6 +214,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 @@ -1672,7 +1692,7 @@ class msg_getcfilters: __slots__ = ("filter_type", "start_height", "stop_hash") msgtype = b"getcfilters" - def __init__(self, filter_type, start_height, stop_hash): + def __init__(self, filter_type=None, start_height=None, stop_hash=None): self.filter_type = filter_type self.start_height = start_height self.stop_hash = stop_hash @@ -1722,7 +1742,7 @@ class msg_getcfheaders: __slots__ = ("filter_type", "start_height", "stop_hash") msgtype = b"getcfheaders" - def __init__(self, filter_type, start_height, stop_hash): + def __init__(self, filter_type=None, start_height=None, stop_hash=None): self.filter_type = filter_type self.start_height = start_height self.stop_hash = stop_hash @@ -1775,7 +1795,7 @@ class msg_getcfcheckpt: __slots__ = ("filter_type", "stop_hash") msgtype = b"getcfcheckpt" - def __init__(self, filter_type, stop_hash): + def __init__(self, filter_type=None, stop_hash=None): self.filter_type = filter_type self.stop_hash = stop_hash diff --git a/test/functional/test_framework/p2p.py b/test/functional/test_framework/p2p.py index 251d3d5eae..fc72a9ab73 100755 --- a/test/functional/test_framework/p2p.py +++ b/test/functional/test_framework/p2p.py @@ -47,6 +47,9 @@ from test_framework.messages import ( msg_getaddr, msg_getblocks, msg_getblocktxn, + msg_getcfcheckpt, + msg_getcfheaders, + msg_getcfilters, msg_getdata, msg_getheaders, msg_headers, @@ -108,6 +111,9 @@ MESSAGEMAP = { b"getaddr": msg_getaddr, b"getblocks": msg_getblocks, b"getblocktxn": msg_getblocktxn, + b"getcfcheckpt": msg_getcfcheckpt, + b"getcfheaders": msg_getcfheaders, + b"getcfilters": msg_getcfilters, b"getdata": msg_getdata, b"getheaders": msg_getheaders, b"headers": msg_headers, 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.py b/test/functional/test_framework/script.py index 7791ae5392..2b70eab4e4 100644 --- a/test/functional/test_framework/script.py +++ b/test/functional/test_framework/script.py @@ -27,6 +27,7 @@ from .messages import ( from .ripemd160 import ripemd160 MAX_SCRIPT_ELEMENT_SIZE = 520 +MAX_PUBKEYS_PER_MULTI_A = 999 LOCKTIME_THRESHOLD = 500000000 ANNEX_TAG = 0x50 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 ecdc3bf424..c880aabd21 100755 --- a/test/functional/test_framework/test_framework.py +++ b/test/functional/test_framework/test_framework.py @@ -9,6 +9,7 @@ from enum import Enum import argparse import logging import os +import platform import pdb import random import re @@ -222,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) @@ -243,8 +244,14 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass): "src", "bitcoin-cli" + config["environment"]["EXEEXT"], ) + fname_bitcoinutil = os.path.join( + config["environment"]["BUILDDIR"], + "src", + "bitcoin-util" + config["environment"]["EXEEXT"], + ) self.options.bitcoind = os.getenv("BITCOIND", default=fname_bitcoind) self.options.bitcoincli = os.getenv("BITCOINCLI", default=fname_bitcoincli) + self.options.bitcoinutil = os.getenv("BITCOINUTIL", default=fname_bitcoinutil) os.environ['PATH'] = os.pathsep.join([ os.path.join(config['environment']['BUILDDIR'], 'src'), @@ -574,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 @@ -581,10 +590,10 @@ 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): @@ -613,7 +622,7 @@ 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(), timeout=5) disconnect_nodes_helper(self.nodes[a], b) @@ -826,6 +835,29 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass): except ImportError: raise SkipTest("python3-zmq module not available.") + def skip_if_no_python_bcc(self): + """Attempt to import the bcc package and skip the tests if the import fails.""" + try: + import bcc # type: ignore[import] # noqa: F401 + except ImportError: + raise SkipTest("bcc python module not available") + + def skip_if_no_bitcoind_tracepoints(self): + """Skip the running test if bitcoind has not been compiled with USDT tracepoint support.""" + if not self.is_usdt_compiled(): + raise SkipTest("bitcoind has not been built with USDT tracepoints enabled.") + + def skip_if_no_bpf_permissions(self): + """Skip the running test if we don't have permissions to do BPF syscalls and load BPF maps.""" + # check for 'root' permissions + if os.geteuid() != 0: + raise SkipTest("no permissions to use BPF (please review the tests carefully before running them with higher privileges)") + + def skip_if_platform_not_linux(self): + """Skip the running test if we are not on a Linux platform""" + if platform.system() != "Linux": + raise SkipTest("not on a Linux system") + def skip_if_no_bitcoind_zmq(self): """Skip the running test if bitcoind has not been compiled with zmq support.""" if not self.is_zmq_compiled(): @@ -856,6 +888,11 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass): if not self.is_wallet_tool_compiled(): raise SkipTest("bitcoin-wallet has not been compiled") + def skip_if_no_bitcoin_util(self): + """Skip the running test if bitcoin-util has not been compiled.""" + if not self.is_bitcoin_util_compiled(): + raise SkipTest("bitcoin-util has not been compiled") + def skip_if_no_cli(self): """Skip the running test if bitcoin-cli has not been compiled.""" if not self.is_cli_compiled(): @@ -903,10 +940,18 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass): """Checks whether bitcoin-wallet was compiled.""" return self.config["components"].getboolean("ENABLE_WALLET_TOOL") + def is_bitcoin_util_compiled(self): + """Checks whether bitcoin-util was compiled.""" + return self.config["components"].getboolean("ENABLE_BITCOIN_UTIL") + def is_zmq_compiled(self): """Checks whether the zmq module was compiled.""" return self.config["components"].getboolean("ENABLE_ZMQ") + def is_usdt_compiled(self): + """Checks whether the USDT tracepoints were compiled.""" + return self.config["components"].getboolean("ENABLE_USDT_TRACEPOINTS") + def is_sqlite_compiled(self): """Checks whether the wallet module was compiled with Sqlite support.""" return self.config["components"].getboolean("USE_SQLITE") diff --git a/test/functional/test_framework/test_node.py b/test/functional/test_framework/test_node.py index e56d4aa492..03f6c8adea 100755 --- a/test/functional/test_framework/test_node.py +++ b/test/functional/test_framework/test_node.py @@ -423,7 +423,7 @@ class TestNode(): self._raise_assertion_error('Expected messages "{}" does not partially match log:\n\n{}\n\n'.format(str(expected_msgs), print_log)) @contextlib.contextmanager - def wait_for_debug_log(self, expected_msgs, timeout=60, ignore_case=False): + def wait_for_debug_log(self, expected_msgs, timeout=60): """ Block until we see a particular debug log message fragment or until we exceed the timeout. Return: @@ -431,18 +431,17 @@ class TestNode(): """ time_end = time.time() + timeout * self.timeout_factor prev_size = self.debug_log_bytes() - re_flags = re.MULTILINE | (re.IGNORECASE if ignore_case else 0) yield while True: found = True - with open(self.debug_log_path, encoding='utf-8') as dl: + with open(self.debug_log_path, "rb") as dl: dl.seek(prev_size) log = dl.read() for expected_msg in expected_msgs: - if re.search(re.escape(expected_msg), log, flags=re_flags) is None: + if expected_msg not in log: found = False if found: @@ -545,6 +544,7 @@ class TestNode(): Will throw if bitcoind starts without an error. Will throw if an expected_msg is provided and it does not match bitcoind's stdout.""" + assert not self.running with tempfile.NamedTemporaryFile(dir=self.stderr_dir, delete=False) as log_stderr, \ tempfile.NamedTemporaryFile(dir=self.stdout_dir, delete=False) as log_stdout: try: diff --git a/test/functional/test_framework/util.py b/test/functional/test_framework/util.py index 210025104e..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 ############################################ @@ -378,6 +390,7 @@ def write_config(config_path, *, n, chain, extra_config="", disable_autoconnect= f.write("[{}]\n".format(chain_name_conf_section)) f.write("port=" + str(p2p_port(n)) + "\n") f.write("rpcport=" + str(rpc_port(n)) + "\n") + f.write("rpcdoccheck=1\n") f.write("fallbackfee=0.0002\n") f.write("server=1\n") f.write("keypool=1\n") @@ -475,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, @@ -531,59 +511,39 @@ 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 -def mine_large_block(test_framework, node, utxos=None): +def mine_large_block(test_framework, mini_wallet, node): # generate a 66k transaction, # and 14 of them is close to the 1MB block limit - num = 14 txouts = gen_return_txouts() - utxos = utxos if utxos is not None else [] - if len(utxos) < num: - utxos.clear() - utxos.extend(node.listunspent()) fee = 100 * node.getnetworkinfo()["relayfee"] - create_lots_of_big_transactions(node, txouts, utxos, num, fee=fee) + 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 dd41a740ae..374fda5c23 100644 --- a/test/functional/test_framework/wallet.py +++ b/test/functional/test_framework/wallet.py @@ -7,17 +7,24 @@ from copy import deepcopy from decimal import Decimal from enum import Enum -from random import choice -from typing import Optional +from typing import ( + Any, + List, + Optional, +) from test_framework.address import ( base58_to_byte, create_deterministic_address_bcrt1_p2tr_op_true, 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, @@ -32,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, @@ -77,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: @@ -93,23 +101,50 @@ 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) + def rescan_utxos(self): """Drop all utxos and rescan the utxo set""" self._utxos = [] 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): @@ -121,43 +156,62 @@ class MiniWallet: if not fixed_length: break tx.vin[0].scriptSig = CScript([der_sig + bytes(bytearray([SIGHASH_ALL]))]) + 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): + return self._scriptPubKey + def get_descriptor(self): 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: Optional[str]='', mark_as_spent=True): + def get_utxo(self, *, txid: str = '', vout: Optional[int] = None, mark_as_spent=True) -> dict: """ Returns a utxo and marks it as spent (pops it from the internal list) Args: txid: get the first utxo we find from a specific transaction """ - index = -1 # by default the last utxo self._utxos = sorted(self._utxos, key=lambda k: (k['value'], -k['height'])) # Put the largest utxo last if txid: - utxo = next(filter(lambda utxo: txid == utxo['txid'], self._utxos)) - index = self._utxos.index(utxo) + utxo_filter: Any = filter(lambda utxo: txid == utxo['txid'], self._utxos) + else: + utxo_filter = reversed(self._utxos) # By default the largest utxo + if vout is not None: + utxo_filter = filter(lambda utxo: vout == utxo['vout'], utxo_filter) + index = self._utxos.index(next(utxo_filter)) if mark_as_spent: return self._utxos.pop(index) else: return self._utxos[index] - 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 get_utxos(self, *, mark_as_spent=True): + """Returns the list of all utxos and optionally mark them as spent""" + utxos = deepcopy(self._utxos) + if mark_as_spent: + self._utxos = [] + return utxos + + 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): @@ -172,58 +226,134 @@ 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 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.""" - from_node = from_node or self._test_node + 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) + self.sendrawtransaction(from_node=from_node, tx_hex=tx["hex"]) + return tx + + def create_self_transfer_multi( + 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. 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, + 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)] + + # adapt input prevouts + for i, utxo in enumerate(utxos_to_spend): + tx.vin[i] = CTxIn(COutPoint(int(utxo['txid'], 16), utxo['vout'])) + + # adapt output amounts (use fixed fee per output) + 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 = amount_per_output or (outputs_value_total // num_outputs) + + if target_weight: + self._bulk_tx(tx, target_weight) + + 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] + else: + assert False + + 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) - tx_info = from_node.testmempoolaccept([tx_hex])[0] - assert_equal(mempool_valid, tx_info['allowed']) - if mempool_valid: - assert_equal(tx_info['vsize'], vsize) - assert_equal(tx_info['fees']['base'], utxo_to_spend['value'] - Decimal(send_value) / COIN) - return {'txid': tx_info['txid'], 'wtxid': tx_info['wtxid'], '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, **kwargs): - txid = from_node.sendrawtransaction(hexstring=tx_hex, **kwargs) + 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. + + 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='bech32'): + +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() @@ -238,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 @@ -307,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 b0f24e3b97..37d0c4f87e 100755 --- a/test/functional/test_runner.py +++ b/test/functional/test_runner.py @@ -28,7 +28,7 @@ import logging import unittest # Formatting. Default colors to empty strings. -BOLD, GREEN, RED, GREY = ("", ""), ("", ""), ("", ""), ("", "") +DEFAULT, BOLD, GREEN, RED = ("", ""), ("", ""), ("", ""), ("", "") try: # Make sure python thinks it can write unicode to its stdout "\u2713".encode("utf_8").decode(sys.stdout.encoding) @@ -59,10 +59,10 @@ if os.name != 'nt' or sys.getwindowsversion() >= (10, 0, 14393): #type:ignore kernel32.SetConsoleMode(stderr, stderr_mode.value | ENABLE_VIRTUAL_TERMINAL_PROCESSING) # primitive formatting on supported # terminal via ANSI escape sequences: + DEFAULT = ('\033[0m', '\033[0m') BOLD = ('\033[0m', '\033[1m') GREEN = ('\033[0m', '\033[0;32m') RED = ('\033[0m', '\033[0;31m') - GREY = ('\033[0m', '\033[1;30m') TEST_EXIT_PASSED = 0 TEST_EXIT_SKIPPED = 77 @@ -82,6 +82,7 @@ EXTENDED_SCRIPTS = [ # Longest test should go first, to favor running tests in parallel 'feature_pruning.py', 'feature_dbcrash.py', + 'feature_index_prune.py', ] BASE_SCRIPTS = [ @@ -111,7 +112,6 @@ BASE_SCRIPTS = [ 'p2p_tx_download.py', 'mempool_updatefromblock.py', 'wallet_dump.py --legacy-wallet', - 'feature_taproot.py --previous_release', 'feature_taproot.py', 'rpc_signer.py', 'wallet_signer.py --descriptors', @@ -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 @@ -145,6 +146,8 @@ BASE_SCRIPTS = [ 'wallet_txn_doublespend.py --mineblock', 'tool_wallet.py --legacy-wallet', 'tool_wallet.py --descriptors', + 'tool_signet_miner.py --legacy-wallet', + 'tool_signet_miner.py --descriptors', 'wallet_txn_clone.py', 'wallet_txn_clone.py --segwit', 'rpc_getchaintips.py', @@ -153,8 +156,10 @@ 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', 'wallet_multiwallet.py --legacy-wallet', 'wallet_multiwallet.py --descriptors', 'wallet_multiwallet.py --usecli', @@ -168,16 +173,20 @@ BASE_SCRIPTS = [ 'wallet_reorgsrestore.py', 'interface_http.py', 'interface_rpc.py', + 'interface_usdt_coinselection.py', + 'interface_usdt_net.py', + 'interface_usdt_utxocache.py', + 'interface_usdt_validation.py', 'rpc_psbt.py --legacy-wallet', 'rpc_psbt.py --descriptors', 'rpc_users.py', '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', 'rpc_rawtransaction.py --legacy-wallet', - 'rpc_rawtransaction.py --descriptors', 'wallet_groups.py --legacy-wallet', 'wallet_transactiontime_rescan.py --descriptors', 'wallet_transactiontime_rescan.py --legacy-wallet', @@ -188,8 +197,7 @@ BASE_SCRIPTS = [ 'rpc_decodescript.py', 'rpc_blockchain.py', 'rpc_deprecated.py', - 'wallet_disable.py --legacy-wallet', - 'wallet_disable.py --descriptors', + 'wallet_disable.py', 'p2p_addr_relay.py', 'p2p_getaddr_caching.py', 'p2p_getdata.py', @@ -198,6 +206,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', @@ -225,9 +234,7 @@ BASE_SCRIPTS = [ 'feature_rbf.py --descriptors', 'mempool_packages.py', 'mempool_package_onemore.py', - 'rpc_createmultisig.py --legacy-wallet', - 'rpc_createmultisig.py --descriptors', - 'rpc_packages.py', + 'rpc_createmultisig.py', 'mempool_package_limits.py', 'feature_versionbits_warning.py', 'rpc_preciousblock.py', @@ -237,12 +244,11 @@ BASE_SCRIPTS = [ 'p2p_eviction.py', 'wallet_signmessagewithaddress.py', 'rpc_signmessagewithprivkey.py', - 'rpc_generateblock.py', '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', @@ -252,6 +258,7 @@ BASE_SCRIPTS = [ 'rpc_bind.py --ipv4', 'rpc_bind.py --ipv6', 'rpc_bind.py --nonloopback', + 'wallet_crosschain.py', 'mining_basic.py', 'feature_signet.py', 'wallet_bumpfee.py --legacy-wallet', @@ -259,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', @@ -280,6 +289,8 @@ BASE_SCRIPTS = [ 'wallet_create_tx.py --legacy-wallet', 'wallet_send.py --legacy-wallet', 'wallet_send.py --descriptors', + 'wallet_sendall.py --legacy-wallet', + 'wallet_sendall.py --descriptors', 'wallet_create_tx.py --descriptors', 'wallet_taproot.py', 'wallet_inactive_hdchains.py', @@ -307,10 +318,11 @@ BASE_SCRIPTS = [ 'p2p_ping.py', 'rpc_scantxoutset.py', 'feature_txindex_compatibility.py', + 'feature_unsupported_utxo_db.py', 'feature_logging.py', 'feature_anchors.py', - 'feature_coinstatsindex.py --legacy-wallet', - 'feature_coinstatsindex.py --descriptors', + 'mempool_datacarrier.py', + 'feature_coinstatsindex.py', 'wallet_orphanedreward.py', 'wallet_timelock.py', 'p2p_node_network_limited.py', @@ -322,13 +334,12 @@ BASE_SCRIPTS = [ 'feature_presegwit_node_upgrade.py', 'feature_settings.py', 'rpc_getdescriptorinfo.py', - 'rpc_mempool_entry_fee_fields_deprecation.py', + 'rpc_mempool_info.py', 'rpc_help.py', 'feature_dirsymlinks.py', 'feature_help.py', 'feature_shutdown.py', 'p2p_ibd_txrelay.py', - 'feature_blockfilterindex_prune.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 ] @@ -367,11 +378,11 @@ def main(): args, unknown_args = parser.parse_known_args() if not args.ansi: - global BOLD, GREEN, RED, GREY + global DEFAULT, BOLD, GREEN, RED + DEFAULT = ("", "") BOLD = ("", "") GREEN = ("", "") RED = ("", "") - GREY = ("", "") # args to be passed on always start with two dashes; tests are the remaining unknown args tests = [arg for arg in unknown_args if arg[:2] != "--"] @@ -589,11 +600,12 @@ def run_tests(*, test_list, src_dir, build_dir, tmpdir, jobs=1, enable_coverage= # Clean up dangling processes if any. This may only happen with --failfast option. # Killing the process group will also terminate the current process but that is # not an issue - if len(job_queue.jobs): + if not os.getenv("CI_FAILFAST_TEST_LEAVE_DANGLING") and len(job_queue.jobs): os.killpg(os.getpgid(0), signal.SIGKILL) sys.exit(not all_passed) + def print_results(test_results, max_len_name, runtime): results = "\n" + BOLD[1] + "%s | %s | %s\n\n" % ("TEST".ljust(max_len_name), "STATUS ", "DURATION") + BOLD[0] @@ -714,7 +726,7 @@ class TestResult(): color = RED glyph = CROSS elif self.status == "Skipped": - color = GREY + color = DEFAULT glyph = CIRCLE return color[1] + "%s | %s%s | %s s\n" % (self.name.ljust(self.padding), glyph, self.status.ljust(7), self.time) + color[0] diff --git a/test/functional/tool_signet_miner.py b/test/functional/tool_signet_miner.py new file mode 100755 index 0000000000..e6fc9072ab --- /dev/null +++ b/test/functional/tool_signet_miner.py @@ -0,0 +1,62 @@ +#!/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 signet miner tool""" + +import os.path +import subprocess +import sys +import time + +from test_framework.key import ECKey +from test_framework.script_util import key_to_p2wpkh_script +from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import assert_equal +from test_framework.wallet_util import bytes_to_wif + + +CHALLENGE_PRIVATE_KEY = (42).to_bytes(32, 'big') + + +class SignetMinerTest(BitcoinTestFramework): + def set_test_params(self): + self.chain = "signet" + self.setup_clean_chain = True + self.num_nodes = 1 + + # generate and specify signet challenge (simple p2wpkh script) + privkey = ECKey() + privkey.set(CHALLENGE_PRIVATE_KEY, True) + pubkey = privkey.get_pubkey().get_bytes() + challenge = key_to_p2wpkh_script(pubkey) + self.extra_args = [[f'-signetchallenge={challenge.hex()}']] + + def skip_test_if_missing_module(self): + self.skip_if_no_cli() + self.skip_if_no_wallet() + self.skip_if_no_bitcoin_util() + + def run_test(self): + node = self.nodes[0] + # import private key needed for signing block + node.importprivkey(bytes_to_wif(CHALLENGE_PRIVATE_KEY)) + + # generate block with signet miner tool + base_dir = self.config["environment"]["SRCDIR"] + signet_miner_path = os.path.join(base_dir, "contrib", "signet", "miner") + subprocess.run([ + sys.executable, + signet_miner_path, + f'--cli={node.cli.binary} -datadir={node.cli.datadir}', + 'generate', + f'--address={node.getnewaddress()}', + f'--grind-cmd={self.options.bitcoinutil} grind', + '--nbits=1d00ffff', + f'--set-block-time={int(time.time())}', + ], check=True, stderr=subprocess.STDOUT) + assert_equal(node.getblockcount(), 1) + + +if __name__ == "__main__": + SignetMinerTest().main() 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_avoidreuse.py b/test/functional/wallet_avoidreuse.py index dc823c2c60..f663666f57 100755 --- a/test/functional/wallet_avoidreuse.py +++ b/test/functional/wallet_avoidreuse.py @@ -118,6 +118,17 @@ class AvoidReuseTest(BitcoinTestFramework): assert_raises_rpc_error(-8, "Wallet flag is already set to false", self.nodes[0].setwalletflag, 'avoid_reuse', False) assert_raises_rpc_error(-8, "Wallet flag is already set to true", self.nodes[1].setwalletflag, 'avoid_reuse', True) + # Create a wallet with avoid reuse, and test that disabling it afterwards persists + self.nodes[1].createwallet(wallet_name="avoid_reuse_persist", avoid_reuse=True) + w = self.nodes[1].get_wallet_rpc("avoid_reuse_persist") + assert_equal(w.getwalletinfo()["avoid_reuse"], True) + w.setwalletflag("avoid_reuse", False) + assert_equal(w.getwalletinfo()["avoid_reuse"], False) + w.unloadwallet() + self.nodes[1].loadwallet("avoid_reuse_persist") + assert_equal(w.getwalletinfo()["avoid_reuse"], False) + w.unloadwallet() + def test_immutable(self): '''Test immutable wallet flags''' self.log.info("Test immutable wallet flags") diff --git a/test/functional/wallet_balance.py b/test/functional/wallet_balance.py index 0cfbefb719..d49bca6855 100755 --- a/test/functional/wallet_balance.py +++ b/test/functional/wallet_balance.py @@ -50,7 +50,9 @@ class WalletTest(BitcoinTestFramework): self.num_nodes = 2 self.setup_clean_chain = True self.extra_args = [ - ['-limitdescendantcount=3'], # Limit mempool descendants as a hack to have wallet txs rejected from the mempool + # Limit mempool descendants as a hack to have wallet txs rejected from the mempool. + # Set walletrejectlongchains=0 so the wallet still creates the transactions. + ['-limitdescendantcount=3', '-walletrejectlongchains=0'], [], ] @@ -271,6 +273,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 69f9df57d8..adeb551895 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", + "-dustrelayfee=0", "-walletrejectlongchains=0" ]] * self.num_nodes self.setup_clean_chain = True self.supports_cli = False @@ -142,7 +143,7 @@ class WalletTest(BitcoinTestFramework): self.nodes[2].lockunspent(False, [unspent_0], True) # Restarting the node with the lock written to the wallet should keep the lock - self.restart_node(2) + self.restart_node(2, ["-walletrejectlongchains=0"]) assert_raises_rpc_error(-8, "Invalid parameter, output already locked", self.nodes[2].lockunspent, False, [unspent_0]) # Unloading and reloading the wallet with a persistent lock should keep the lock @@ -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(-1, "not of expected type number", self.generate, self.nodes[0], "2") if not self.options.descriptors: @@ -568,7 +569,7 @@ class WalletTest(BitcoinTestFramework): self.log.info("Test -reindex") self.stop_nodes() # set lower ancestor limit for later - self.start_node(0, ['-reindex', "-limitancestorcount=" + str(chainlimit)]) + self.start_node(0, ['-reindex', "-walletrejectlongchains=0", "-limitancestorcount=" + str(chainlimit)]) self.start_node(1, ['-reindex', "-limitancestorcount=" + str(chainlimit)]) self.start_node(2, ['-reindex', "-limitancestorcount=" + str(chainlimit)]) # reindex will leave rpc warm up "early"; Wait for it to finish @@ -668,7 +669,7 @@ class WalletTest(BitcoinTestFramework): "category": baz["category"], "vout": baz["vout"]} expected_fields = frozenset({'amount', 'bip125-replaceable', 'confirmations', 'details', 'fee', - 'hex', 'time', 'timereceived', 'trusted', 'txid', 'walletconflicts'}) + 'hex', 'time', 'timereceived', 'trusted', 'txid', 'wtxid', 'walletconflicts'}) verbose_field = "decoded" expected_verbose_fields = expected_fields | {verbose_field} @@ -700,6 +701,38 @@ 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) + 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 f6843d597d..9a481b290b 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 ( @@ -212,7 +212,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) @@ -243,7 +243,7 @@ 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}) @@ -442,7 +442,9 @@ def test_watchonly_psbt(self, peer_node, rbf_node, dest_address): self.generate(peer_node, 1) # Create single-input PSBT for transaction to be bumped - psbt = watcher.walletcreatefundedpsbt([], {dest_address: 0.0005}, 0, {"fee_rate": 1}, True)['psbt'] + # Ensure the payment amount + change can be fully funded using one of the 0.001BTC inputs. + psbt = watcher.walletcreatefundedpsbt([watcher.listunspent()[0]], {dest_address: 0.0005}, 0, + {"fee_rate": 1, "add_inputs": False}, True)['psbt'] psbt_signed = signer.walletprocesspsbt(psbt=psbt, sign=True, sighashtype="ALL", bip32derivs=True) psbt_final = watcher.finalizepsbt(psbt_signed["psbt"]) original_txid = watcher.sendrawtransaction(psbt_final["hex"]) @@ -576,7 +578,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 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_createwallet.py b/test/functional/wallet_createwallet.py index 4416a9655f..12480d4d1e 100755 --- a/test/functional/wallet_createwallet.py +++ b/test/functional/wallet_createwallet.py @@ -26,6 +26,11 @@ class CreateWalletTest(BitcoinTestFramework): node = self.nodes[0] self.generate(node, 1) # Leave IBD for sethdseed + self.log.info("Run createwallet with invalid parameters.") + # Run createwallet with invalid parameters. This must not prevent a new wallet with the same name from being created with the correct parameters. + assert_raises_rpc_error(-4, "Passphrase provided but private keys are disabled. A passphrase is only used to encrypt private keys, so cannot be used for wallets with private keys disabled.", + self.nodes[0].createwallet, wallet_name='w0', disable_private_keys=True, passphrase="passphrase") + self.nodes[0].createwallet(wallet_name='w0') w0 = node.get_wallet_rpc('w0') address1 = w0.getnewaddress() @@ -164,5 +169,10 @@ class CreateWalletTest(BitcoinTestFramework): self.log.info('Using a passphrase with private keys disabled returns error') assert_raises_rpc_error(-4, 'Passphrase provided but private keys are disabled. A passphrase is only used to encrypt private keys, so cannot be used for wallets with private keys disabled.', self.nodes[0].createwallet, wallet_name='w9', disable_private_keys=True, passphrase='thisisapassphrase') + if self.is_bdb_compiled(): + self.log.info("Test legacy wallet deprecation") + res = self.nodes[0].createwallet(wallet_name="legacy_w0", descriptors=False, passphrase=None) + assert_equal(res["warning"], "Wallet created successfully. The legacy wallet type is being deprecated and support for creating and opening legacy wallets will be removed in the future.") + if __name__ == '__main__': CreateWalletTest().main() diff --git a/test/functional/wallet_crosschain.py b/test/functional/wallet_crosschain.py new file mode 100755 index 0000000000..b6d0c87985 --- /dev/null +++ b/test/functional/wallet_crosschain.py @@ -0,0 +1,60 @@ +#!/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. + +import os + +from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import assert_raises_rpc_error + +class WalletCrossChain(BitcoinTestFramework): + def set_test_params(self): + self.num_nodes = 2 + self.setup_clean_chain = True + + def skip_test_if_missing_module(self): + self.skip_if_no_wallet() + + def setup_network(self): + self.add_nodes(self.num_nodes) + + # Switch node 1 to testnet before starting it. + self.nodes[1].chain = 'testnet3' + self.nodes[1].extra_args = ['-maxconnections=0'] # disable testnet sync + with open(self.nodes[1].bitcoinconf, 'r', encoding='utf8') as conf: + conf_data = conf.read() + with open (self.nodes[1].bitcoinconf, 'w', encoding='utf8') as conf: + conf.write(conf_data.replace('regtest=', 'testnet=').replace('[regtest]', '[test]')) + + self.start_nodes() + + def run_test(self): + self.log.info("Creating wallets") + + node0_wallet = os.path.join(self.nodes[0].datadir, 'node0_wallet') + self.nodes[0].createwallet(node0_wallet) + self.nodes[0].unloadwallet(node0_wallet) + node1_wallet = os.path.join(self.nodes[1].datadir, 'node1_wallet') + self.nodes[1].createwallet(node1_wallet) + self.nodes[1].unloadwallet(node1_wallet) + + self.log.info("Loading wallets into nodes with a different genesis blocks") + + if self.options.descriptors: + assert_raises_rpc_error(-18, 'Wallet file verification failed.', self.nodes[0].loadwallet, node1_wallet) + assert_raises_rpc_error(-18, 'Wallet file verification failed.', self.nodes[1].loadwallet, node0_wallet) + else: + assert_raises_rpc_error(-4, 'Wallet files should not be reused across chains.', self.nodes[0].loadwallet, node1_wallet) + assert_raises_rpc_error(-4, 'Wallet files should not be reused across chains.', self.nodes[1].loadwallet, node0_wallet) + + if not self.options.descriptors: + self.log.info("Override cross-chain wallet load protection") + self.stop_nodes() + self.start_nodes([['-walletcrosschain']] * self.num_nodes) + self.nodes[0].loadwallet(node1_wallet) + self.nodes[1].loadwallet(node0_wallet) + + +if __name__ == '__main__': + WalletCrossChain().main() 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_disable.py b/test/functional/wallet_disable.py index 2c7996ca6b..74cddf2738 100755 --- a/test/functional/wallet_disable.py +++ b/test/functional/wallet_disable.py @@ -26,10 +26,6 @@ class DisableWalletTest (BitcoinTestFramework): x = self.nodes[0].validateaddress('mneYUmWYsuk7kySiURxCi3AGxrAqZxLgPZ') assert x['isvalid'] == True - # Checking mining to an address without a wallet. Generating to a valid address should succeed - # but generating to an invalid address will fail. - self.generatetoaddress(self.nodes[0], 1, 'mneYUmWYsuk7kySiURxCi3AGxrAqZxLgPZ') - assert_raises_rpc_error(-5, "Invalid address", self.generatetoaddress, self.nodes[0], 1, '3J98t1WpEZ73CNmQviecrnyiWrnqRhWNLy') if __name__ == '__main__': - DisableWalletTest ().main () + DisableWalletTest().main() diff --git a/test/functional/wallet_hd.py b/test/functional/wallet_hd.py index ac878ea0aa..686a365584 100755 --- a/test/functional/wallet_hd.py +++ b/test/functional/wallet_hd.py @@ -173,8 +173,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(-1, "JSON value of type string is not of expected type bool", self.nodes[1].sethdseed, "Not_bool") + assert_raises_rpc_error(-1, "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..525b91a6e0 100755 --- a/test/functional/wallet_importdescriptors.py +++ b/test/functional/wallet_importdescriptors.py @@ -454,7 +454,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 +480,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_importprunedfunds.py b/test/functional/wallet_importprunedfunds.py index cdb5823109..2a4d0981c7 100755 --- a/test/functional/wallet_importprunedfunds.py +++ b/test/functional/wallet_importprunedfunds.py @@ -5,9 +5,13 @@ """Test the importprunedfunds and removeprunedfunds RPCs.""" from decimal import Decimal -from test_framework.blocktools import COINBASE_MATURITY from test_framework.address import key_to_p2wpkh +from test_framework.blocktools import COINBASE_MATURITY from test_framework.key import ECKey +from test_framework.messages import ( + CMerkleBlock, + from_hex, +) from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_equal, @@ -15,6 +19,7 @@ from test_framework.util import ( ) from test_framework.wallet_util import bytes_to_wif + class ImportPrunedFundsTest(BitcoinTestFramework): def set_test_params(self): self.setup_clean_chain = True @@ -124,5 +129,18 @@ class ImportPrunedFundsTest(BitcoinTestFramework): w1.removeprunedfunds(txnid3) assert not [tx for tx in w1.listtransactions(include_watchonly=True) if tx['txid'] == txnid3] + # Check various RPC parameter validation errors + assert_raises_rpc_error(-22, "TX decode failed", w1.importprunedfunds, b'invalid tx'.hex(), proof1) + assert_raises_rpc_error(-5, "Transaction given doesn't exist in proof", w1.importprunedfunds, rawtxn2, proof1) + + mb = from_hex(CMerkleBlock(), proof1) + mb.header.hashMerkleRoot = 0xdeadbeef # cause mismatch between merkle root and merkle block + assert_raises_rpc_error(-5, "Something wrong with merkleblock", w1.importprunedfunds, rawtxn1, mb.serialize().hex()) + + mb = from_hex(CMerkleBlock(), proof1) + mb.header.nTime += 1 # modify arbitrary block header field to change block hash + assert_raises_rpc_error(-5, "Block not found in chain", w1.importprunedfunds, rawtxn1, mb.serialize().hex()) + + if __name__ == '__main__': ImportPrunedFundsTest().main() diff --git a/test/functional/wallet_listreceivedby.py b/test/functional/wallet_listreceivedby.py index 48b92796fc..7ae3a40eaf 100755 --- a/test/functional/wallet_listreceivedby.py +++ b/test/functional/wallet_listreceivedby.py @@ -18,17 +18,12 @@ from test_framework.wallet_util import test_address class ReceivedByTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 2 - # Test deprecated exclude coinbase on second node - self.extra_args = [[], ["-deprecatedrpc=exclude_coinbase"]] def skip_test_if_missing_module(self): self.skip_if_no_wallet() self.skip_if_no_cli() def run_test(self): - # Generate block to get out of IBD - self.generate(self.nodes[0], 1) - # save the number of coinbase reward addresses so far num_cb_reward_addresses = len(self.nodes[1].listreceivedbyaddress(minconf=0, include_empty=True, include_watchonly=True)) @@ -62,6 +57,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, ]} @@ -134,6 +134,9 @@ class ReceivedByTest(BitcoinTestFramework): txid = self.nodes[0].sendtoaddress(addr, 0.1) self.sync_all() + # getreceivedbylabel returns an error if the wallet doesn't own the label + assert_raises_rpc_error(-4, "Label not found in wallet", self.nodes[0].getreceivedbylabel, "dummy") + # listreceivedbylabel should return received_by_label_json because of 0 confirmations assert_array_result(self.nodes[1].listreceivedbylabel(), {"label": label}, @@ -172,7 +175,7 @@ class ReceivedByTest(BitcoinTestFramework): address = self.nodes[0].getnewaddress(label) reward = Decimal("25") - self.generatetoaddress(self.nodes[0], 1, address, sync_fun=self.no_op) + self.generatetoaddress(self.nodes[0], 1, address) hash = self.nodes[0].getbestblockhash() self.log.info("getreceivedbyaddress returns nothing with defaults") @@ -212,7 +215,7 @@ class ReceivedByTest(BitcoinTestFramework): {"label": label, "amount": reward}) self.log.info("Generate 100 more blocks") - self.generate(self.nodes[0], COINBASE_MATURITY, sync_fun=self.no_op) + self.generate(self.nodes[0], COINBASE_MATURITY) self.log.info("getreceivedbyaddress returns reward with defaults") balance = self.nodes[0].getreceivedbyaddress(address) @@ -253,35 +256,6 @@ class ReceivedByTest(BitcoinTestFramework): {"label": label}, {}, True) - # Test exclude_coinbase - address2 = self.nodes[1].getnewaddress(label) - self.generatetoaddress(self.nodes[1], COINBASE_MATURITY + 1, address2, sync_fun=self.no_op) - - self.log.info("getreceivedbyaddress returns nothing when excluding coinbase") - balance = self.nodes[1].getreceivedbyaddress(address2) - assert_equal(balance, 0) - - self.log.info("getreceivedbylabel returns nothing when excluding coinbase") - balance = self.nodes[1].getreceivedbylabel("label") - assert_equal(balance, 0) - - self.log.info("listreceivedbyaddress does not include address when excluding coinbase") - assert_array_result(self.nodes[1].listreceivedbyaddress(), - {"address": address2}, - {}, True) - - self.log.info("listreceivedbylabel does not include label when excluding coinbase") - assert_array_result(self.nodes[1].listreceivedbylabel(), - {"label": label}, - {}, True) - - self.log.info("getreceivedbyaddress throws when setting include_immature_coinbase with deprecated exclude_coinbase") - assert_raises_rpc_error(-8, 'include_immature_coinbase is incompatible with deprecated exclude_coinbase', self.nodes[1].getreceivedbyaddress, address2, 1, True) - - - self.log.info("listreceivedbyaddress throws when setting include_immature_coinbase with deprecated exclude_coinbase") - assert_raises_rpc_error(-8, 'include_immature_coinbase is incompatible with deprecated exclude_coinbase', self.nodes[1].listreceivedbyaddress, 1, False, False, "", True) - if __name__ == '__main__': ReceivedByTest().main() diff --git a/test/functional/wallet_listsinceblock.py b/test/functional/wallet_listsinceblock.py index fc06565983..36dbec467b 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, @@ -39,6 +40,8 @@ class ListSinceBlockTest(BitcoinTestFramework): self.test_double_send() self.double_spends_filtered() self.test_targetconfirmations() + self.test_desc() + self.test_send_to_self() def test_no_blockhash(self): self.log.info("Test no blockhash") @@ -346,7 +349,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 +386,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_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..99e472a7b4 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(-1, "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 5aae2c813a..6552bfe60c 100755 --- a/test/functional/wallet_resendwallettransactions.py +++ b/test/functional/wallet_resendwallettransactions.py @@ -38,7 +38,7 @@ class ResendWalletTransactionsTest(BitcoinTestFramework): # Can take a few seconds due to transaction trickling peer_first.wait_for_broadcast([txid]) - # Add a second peer since txs aren't rebroadcast to the same peer (see filterInventoryKnown) + # Add a second peer since txs aren't rebroadcast to the same peer (see m_tx_inventory_known_filter) peer_second = node.add_p2p_connection(P2PTxInvStore()) self.log.info("Create a block") diff --git a/test/functional/wallet_send.py b/test/functional/wallet_send.py index 86e36be8f7..07baa0595e 100755 --- a/test/functional/wallet_send.py +++ b/test/functional/wallet_send.py @@ -10,6 +10,10 @@ from itertools import product from test_framework.authproxy import JSONRPCException from test_framework.descriptors import descsum_create from test_framework.key import ECKey +from test_framework.messages import ( + ser_compact_size, + WITNESS_SCALE_FACTOR, +) from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_equal, @@ -488,8 +492,8 @@ class WalletSendTest(BitcoinTestFramework): self.nodes[1].createwallet("extfund") ext_fund = self.nodes[1].get_wallet_rpc("extfund") - # Make a weird but signable script. sh(pkh()) descriptor accomplishes this - desc = descsum_create("sh(pkh({}))".format(privkey)) + # Make a weird but signable script. sh(wsh(pkh())) descriptor accomplishes this + desc = descsum_create("sh(wsh(pkh({})))".format(privkey)) if self.options.descriptors: res = ext_fund.importdescriptors([{"desc": desc, "timestamp": "now"}]) else: @@ -507,7 +511,7 @@ class WalletSendTest(BitcoinTestFramework): self.test_send(from_wallet=ext_wallet, to_wallet=self.nodes[0], amount=15, inputs=[ext_utxo], add_inputs=True, psbt=True, include_watching=True, expect_error=(-4, "Insufficient funds")) # But funding should work when the solving data is provided - res = self.test_send(from_wallet=ext_wallet, to_wallet=self.nodes[0], amount=15, inputs=[ext_utxo], add_inputs=True, psbt=True, include_watching=True, solving_data={"pubkeys": [addr_info['pubkey']], "scripts": [addr_info["embedded"]["scriptPubKey"]]}) + res = self.test_send(from_wallet=ext_wallet, to_wallet=self.nodes[0], amount=15, inputs=[ext_utxo], add_inputs=True, psbt=True, include_watching=True, solving_data={"pubkeys": [addr_info['pubkey']], "scripts": [addr_info["embedded"]["scriptPubKey"], addr_info["embedded"]["embedded"]["scriptPubKey"]]}) signed = ext_wallet.walletprocesspsbt(res["psbt"]) signed = ext_fund.walletprocesspsbt(res["psbt"]) assert signed["complete"] @@ -526,10 +530,11 @@ class WalletSendTest(BitcoinTestFramework): break psbt_in = dec["inputs"][input_idx] # Calculate the input weight - # (prevout + sequence + length of scriptSig + 2 bytes buffer) * 4 + len of scriptwitness + # (prevout + sequence + length of scriptSig + scriptsig + 1 byte buffer) * WITNESS_SCALE_FACTOR + num scriptWitness stack items + (length of stack item + stack item) * N stack items + 1 byte buffer len_scriptsig = len(psbt_in["final_scriptSig"]["hex"]) // 2 if "final_scriptSig" in psbt_in else 0 - len_scriptwitness = len(psbt_in["final_scriptwitness"]["hex"]) // 2 if "final_scriptwitness" in psbt_in else 0 - input_weight = ((41 + len_scriptsig + 2) * 4) + len_scriptwitness + len_scriptsig += len(ser_compact_size(len_scriptsig)) + 1 + len_scriptwitness = (sum([(len(x) // 2) + len(ser_compact_size(len(x) // 2)) for x in psbt_in["final_scriptwitness"]]) + len(psbt_in["final_scriptwitness"]) + 1) if "final_scriptwitness" in psbt_in else 0 + input_weight = ((40 + len_scriptsig) * WITNESS_SCALE_FACTOR) + len_scriptwitness # Input weight error conditions assert_raises_rpc_error( diff --git a/test/functional/wallet_sendall.py b/test/functional/wallet_sendall.py new file mode 100755 index 0000000000..aa8d2a9d2c --- /dev/null +++ b/test/functional/wallet_sendall.py @@ -0,0 +1,316 @@ +#!/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 the sendall RPC command.""" + +from decimal import Decimal, getcontext + +from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import ( + assert_equal, + assert_greater_than, + assert_raises_rpc_error, +) + +# Decorator to reset activewallet to zero utxos +def cleanup(func): + def wrapper(self): + try: + func(self) + finally: + if 0 < self.wallet.getbalances()["mine"]["trusted"]: + self.wallet.sendall([self.remainder_target]) + assert_equal(0, self.wallet.getbalances()["mine"]["trusted"]) # wallet is empty + return wrapper + +class SendallTest(BitcoinTestFramework): + # Setup and helpers + def skip_test_if_missing_module(self): + self.skip_if_no_wallet() + + def set_test_params(self): + getcontext().prec=10 + self.num_nodes = 1 + self.setup_clean_chain = True + + def assert_balance_swept_completely(self, tx, balance): + output_sum = sum([o["value"] for o in tx["decoded"]["vout"]]) + assert_equal(output_sum, balance + tx["fee"]) + assert_equal(0, self.wallet.getbalances()["mine"]["trusted"]) # wallet is empty + + def assert_tx_has_output(self, tx, addr, value=None): + for output in tx["decoded"]["vout"]: + if addr == output["scriptPubKey"]["address"] and value is None or value == output["value"]: + return + raise AssertionError("Output to {} not present or wrong amount".format(addr)) + + def assert_tx_has_outputs(self, tx, expected_outputs): + assert_equal(len(expected_outputs), len(tx["decoded"]["vout"])) + for eo in expected_outputs: + self.assert_tx_has_output(tx, eo["address"], eo["value"]) + + def add_utxos(self, amounts): + for a in amounts: + self.def_wallet.sendtoaddress(self.wallet.getnewaddress(), a) + self.generate(self.nodes[0], 1) + assert_greater_than(self.wallet.getbalances()["mine"]["trusted"], 0) + return self.wallet.getbalances()["mine"]["trusted"] + + # Helper schema for success cases + def test_sendall_success(self, sendall_args, remaining_balance = 0): + sendall_tx_receipt = self.wallet.sendall(sendall_args) + self.generate(self.nodes[0], 1) + # wallet has remaining balance (usually empty) + assert_equal(remaining_balance, self.wallet.getbalances()["mine"]["trusted"]) + + assert_equal(sendall_tx_receipt["complete"], True) + return self.wallet.gettransaction(txid = sendall_tx_receipt["txid"], verbose = True) + + @cleanup + def gen_and_clean(self): + self.add_utxos([15, 2, 4]) + + def test_cleanup(self): + self.log.info("Test that cleanup wrapper empties wallet") + self.gen_and_clean() + assert_equal(0, self.wallet.getbalances()["mine"]["trusted"]) # wallet is empty + + # Actual tests + @cleanup + def sendall_two_utxos(self): + self.log.info("Testing basic sendall case without specific amounts") + pre_sendall_balance = self.add_utxos([10,11]) + tx_from_wallet = self.test_sendall_success(sendall_args = [self.remainder_target]) + + self.assert_tx_has_outputs(tx = tx_from_wallet, + expected_outputs = [ + { "address": self.remainder_target, "value": pre_sendall_balance + tx_from_wallet["fee"] } # fee is neg + ] + ) + self.assert_balance_swept_completely(tx_from_wallet, pre_sendall_balance) + + @cleanup + def sendall_split(self): + self.log.info("Testing sendall where two recipients have unspecified amount") + pre_sendall_balance = self.add_utxos([1, 2, 3, 15]) + tx_from_wallet = self.test_sendall_success([self.remainder_target, self.split_target]) + + half = (pre_sendall_balance + tx_from_wallet["fee"]) / 2 + self.assert_tx_has_outputs(tx_from_wallet, + expected_outputs = [ + { "address": self.split_target, "value": half }, + { "address": self.remainder_target, "value": half } + ] + ) + self.assert_balance_swept_completely(tx_from_wallet, pre_sendall_balance) + + @cleanup + def sendall_and_spend(self): + self.log.info("Testing sendall in combination with paying specified amount to recipient") + pre_sendall_balance = self.add_utxos([8, 13]) + tx_from_wallet = self.test_sendall_success([{self.recipient: 5}, self.remainder_target]) + + self.assert_tx_has_outputs(tx_from_wallet, + expected_outputs = [ + { "address": self.recipient, "value": 5 }, + { "address": self.remainder_target, "value": pre_sendall_balance - 5 + tx_from_wallet["fee"] } + ] + ) + self.assert_balance_swept_completely(tx_from_wallet, pre_sendall_balance) + + @cleanup + def sendall_invalid_recipient_addresses(self): + self.log.info("Test having only recipient with specified amount, missing recipient with unspecified amount") + self.add_utxos([12, 9]) + + assert_raises_rpc_error( + -8, + "Must provide at least one address without a specified amount" , + self.wallet.sendall, + [{self.recipient: 5}] + ) + + @cleanup + def sendall_duplicate_recipient(self): + self.log.info("Test duplicate destination") + self.add_utxos([1, 8, 3, 9]) + + assert_raises_rpc_error( + -8, + "Invalid parameter, duplicated address: {}".format(self.remainder_target), + self.wallet.sendall, + [self.remainder_target, self.remainder_target] + ) + + @cleanup + def sendall_invalid_amounts(self): + self.log.info("Test sending more than balance") + pre_sendall_balance = self.add_utxos([7, 14]) + + expected_tx = self.wallet.sendall(recipients=[{self.recipient: 5}, self.remainder_target], options={"add_to_wallet": False}) + tx = self.wallet.decoderawtransaction(expected_tx['hex']) + fee = 21 - sum([o["value"] for o in tx["vout"]]) + + assert_raises_rpc_error(-6, "Assigned more value to outputs than available funds.", self.wallet.sendall, + [{self.recipient: pre_sendall_balance + 1}, self.remainder_target]) + assert_raises_rpc_error(-6, "Insufficient funds for fees after creating specified outputs.", self.wallet.sendall, + [{self.recipient: pre_sendall_balance}, self.remainder_target]) + assert_raises_rpc_error(-8, "Specified output amount to {} is below dust threshold".format(self.recipient), + self.wallet.sendall, [{self.recipient: 0.00000001}, self.remainder_target]) + assert_raises_rpc_error(-6, "Dynamically assigned remainder results in dust output.", self.wallet.sendall, + [{self.recipient: pre_sendall_balance - fee}, self.remainder_target]) + assert_raises_rpc_error(-6, "Dynamically assigned remainder results in dust output.", self.wallet.sendall, + [{self.recipient: pre_sendall_balance - fee - Decimal(0.00000010)}, self.remainder_target]) + + # @cleanup not needed because different wallet used + def sendall_negative_effective_value(self): + self.log.info("Test that sendall fails if all UTXOs have negative effective value") + # Use dedicated wallet for dust amounts and unload wallet at end + self.nodes[0].createwallet("dustwallet") + dust_wallet = self.nodes[0].get_wallet_rpc("dustwallet") + + self.def_wallet.sendtoaddress(dust_wallet.getnewaddress(), 0.00000400) + self.def_wallet.sendtoaddress(dust_wallet.getnewaddress(), 0.00000300) + self.generate(self.nodes[0], 1) + assert_greater_than(dust_wallet.getbalances()["mine"]["trusted"], 0) + + assert_raises_rpc_error(-6, "Total value of UTXO pool too low to pay for transaction." + + " Try using lower feerate or excluding uneconomic UTXOs with 'send_max' option.", + dust_wallet.sendall, recipients=[self.remainder_target], fee_rate=300) + + dust_wallet.unloadwallet() + + @cleanup + def sendall_with_send_max(self): + self.log.info("Check that `send_max` option causes negative value UTXOs to be left behind") + self.add_utxos([0.00000400, 0.00000300, 1]) + + # sendall with send_max + sendall_tx_receipt = self.wallet.sendall(recipients=[self.remainder_target], fee_rate=300, options={"send_max": True}) + tx_from_wallet = self.wallet.gettransaction(txid = sendall_tx_receipt["txid"], verbose = True) + + assert_equal(len(tx_from_wallet["decoded"]["vin"]), 1) + self.assert_tx_has_outputs(tx_from_wallet, [{"address": self.remainder_target, "value": 1 + tx_from_wallet["fee"]}]) + assert_equal(self.wallet.getbalances()["mine"]["trusted"], Decimal("0.00000700")) + + self.def_wallet.sendtoaddress(self.wallet.getnewaddress(), 1) + self.generate(self.nodes[0], 1) + + @cleanup + def sendall_specific_inputs(self): + self.log.info("Test sendall with a subset of UTXO pool") + self.add_utxos([17, 4]) + utxo = self.wallet.listunspent()[0] + + sendall_tx_receipt = self.wallet.sendall(recipients=[self.remainder_target], options={"inputs": [utxo]}) + tx_from_wallet = self.wallet.gettransaction(txid = sendall_tx_receipt["txid"], verbose = True) + assert_equal(len(tx_from_wallet["decoded"]["vin"]), 1) + assert_equal(len(tx_from_wallet["decoded"]["vout"]), 1) + assert_equal(tx_from_wallet["decoded"]["vin"][0]["txid"], utxo["txid"]) + assert_equal(tx_from_wallet["decoded"]["vin"][0]["vout"], utxo["vout"]) + self.assert_tx_has_output(tx_from_wallet, self.remainder_target) + + self.generate(self.nodes[0], 1) + assert_greater_than(self.wallet.getbalances()["mine"]["trusted"], 0) + + @cleanup + def sendall_fails_on_missing_input(self): + # fails because UTXO was previously spent, and wallet is empty + self.log.info("Test sendall fails because specified UTXO is not available") + self.add_utxos([16, 5]) + spent_utxo = self.wallet.listunspent()[0] + + # fails on unconfirmed spent UTXO + self.wallet.sendall(recipients=[self.remainder_target]) + assert_raises_rpc_error(-8, + "Input not available. UTXO ({}:{}) was already spent.".format(spent_utxo["txid"], spent_utxo["vout"]), + self.wallet.sendall, recipients=[self.remainder_target], options={"inputs": [spent_utxo]}) + + # fails on specific previously spent UTXO, while other UTXOs exist + self.generate(self.nodes[0], 1) + self.add_utxos([19, 2]) + assert_raises_rpc_error(-8, + "Input not available. UTXO ({}:{}) was already spent.".format(spent_utxo["txid"], spent_utxo["vout"]), + self.wallet.sendall, recipients=[self.remainder_target], options={"inputs": [spent_utxo]}) + + # fails because UTXO is unknown, while other UTXOs exist + foreign_utxo = self.def_wallet.listunspent()[0] + assert_raises_rpc_error(-8, "Input not found. UTXO ({}:{}) is not part of wallet.".format(foreign_utxo["txid"], + foreign_utxo["vout"]), self.wallet.sendall, recipients=[self.remainder_target], + options={"inputs": [foreign_utxo]}) + + @cleanup + def sendall_fails_on_no_address(self): + self.log.info("Test sendall fails because no address is provided") + self.add_utxos([19, 2]) + + assert_raises_rpc_error( + -8, + "Must provide at least one address without a specified amount" , + self.wallet.sendall, + [] + ) + + @cleanup + def sendall_fails_on_specific_inputs_with_send_max(self): + self.log.info("Test sendall fails because send_max is used while specific inputs are provided") + self.add_utxos([15, 6]) + utxo = self.wallet.listunspent()[0] + + assert_raises_rpc_error(-8, + "Cannot combine send_max with specific inputs.", + self.wallet.sendall, + recipients=[self.remainder_target], + options={"inputs": [utxo], "send_max": True}) + + def run_test(self): + self.nodes[0].createwallet("activewallet") + self.wallet = self.nodes[0].get_wallet_rpc("activewallet") + self.def_wallet = self.nodes[0].get_wallet_rpc(self.default_wallet_name) + self.generate(self.nodes[0], 101) + self.recipient = self.def_wallet.getnewaddress() # payee for a specific amount + self.remainder_target = self.def_wallet.getnewaddress() # address that receives everything left after payments and fees + self.split_target = self.def_wallet.getnewaddress() # 2nd target when splitting rest + + # Test cleanup + self.test_cleanup() + + # Basic sweep: everything to one address + self.sendall_two_utxos() + + # Split remainder to two addresses with equal amounts + self.sendall_split() + + # Pay recipient and sweep remainder + self.sendall_and_spend() + + # sendall fails if no recipient has unspecified amount + self.sendall_invalid_recipient_addresses() + + # Sendall fails if same destination is provided twice + self.sendall_duplicate_recipient() + + # Sendall fails when trying to spend more than the balance + self.sendall_invalid_amounts() + + # Sendall fails when wallet has no economically spendable UTXOs + self.sendall_negative_effective_value() + + # Leave dust behind if using send_max + self.sendall_with_send_max() + + # Sendall succeeds with specific inputs + self.sendall_specific_inputs() + + # Fails for the right reasons on missing or previously spent UTXOs + self.sendall_fails_on_missing_input() + + # Sendall fails when no address is provided + self.sendall_fails_on_no_address() + + # Sendall fails when using send_max while specifying inputs + self.sendall_fails_on_specific_inputs_with_send_max() + +if __name__ == '__main__': + SendallTest().main() diff --git a/test/functional/wallet_signer.py b/test/functional/wallet_signer.py index 423cfecdc0..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()}") @@ -194,6 +203,12 @@ class WalletSignerTest(BitcoinTestFramework): assert(res["complete"]) assert_equal(res["hex"], mock_tx) + self.log.info('Test sendall using hww1') + + res = hww.sendall(recipients=[{dest:0.5}, hww.getrawchangeaddress()],options={"add_to_wallet": False}) + assert(res["complete"]) + assert_equal(res["hex"], mock_tx) + # # Handle error thrown by script # self.set_mock_result(self.nodes[4], "2") # assert_raises_rpc_error(-1, 'Unable to parse JSON', @@ -206,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 a2091b4ece..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 = ( @@ -334,16 +242,65 @@ class SignRawTransactionsTest(BitcoinTestFramework): assert_equal(signed["complete"], True) self.nodes[0].sendrawtransaction(signed["hex"]) + def test_signing_with_missing_prevtx_info(self): + txid = "1d1d4e24ed99057e84c3f80fd8fbec79ed9e1acee37da269356ecea000000000" + for type in ["bech32", "p2sh-segwit", "legacy"]: + self.log.info(f"Test signing with missing prevtx info ({type})") + addr = self.nodes[0].getnewaddress("", type) + addrinfo = self.nodes[0].getaddressinfo(addr) + pubkey = addrinfo["scriptPubKey"] + inputs = [{'txid': txid, 'vout': 3, 'sequence': 1000}] + outputs = {self.nodes[0].getnewaddress(): 1} + rawtx = self.nodes[0].createrawtransaction(inputs, outputs) + + prevtx = dict(txid=txid, scriptPubKey=pubkey, vout=3, amount=1) + succ = self.nodes[0].signrawtransactionwithwallet(rawtx, [prevtx]) + assert succ["complete"] + + if type == "legacy": + del prevtx["amount"] + succ = self.nodes[0].signrawtransactionwithwallet(rawtx, [prevtx]) + assert succ["complete"] + else: + assert_raises_rpc_error(-3, "Missing amount", self.nodes[0].signrawtransactionwithwallet, rawtx, [ + { + "txid": txid, + "scriptPubKey": pubkey, + "vout": 3, + } + ]) + + assert_raises_rpc_error(-3, "Missing vout", self.nodes[0].signrawtransactionwithwallet, rawtx, [ + { + "txid": txid, + "scriptPubKey": pubkey, + "amount": 1, + } + ]) + assert_raises_rpc_error(-3, "Missing txid", self.nodes[0].signrawtransactionwithwallet, rawtx, [ + { + "scriptPubKey": pubkey, + "vout": 3, + "amount": 1, + } + ]) + assert_raises_rpc_error(-3, "Missing scriptPubKey", self.nodes[0].signrawtransactionwithwallet, rawtx, [ + { + "txid": txid, + "vout": 3, + "amount": 1 + } + ]) + 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() self.test_signing_with_csv() self.test_signing_with_cltv() + self.test_signing_with_missing_prevtx_info() 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 17eab25457..3c630ba433 100755 --- a/test/functional/wallet_taproot.py +++ b/test/functional/wallet_taproot.py @@ -7,13 +7,17 @@ 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, - OP_1, + MAX_PUBKEYS_PER_MULTI_A, OP_CHECKSIG, + OP_CHECKSIGADD, + OP_NUMEQUAL, taproot_construct, ) from test_framework.segwit_addr import encode_segwit_address @@ -155,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.""" @@ -167,20 +168,31 @@ def pk(hex_key): """Construct a script expression for taproot_construct for pk(hex_key).""" return (None, CScript([bytes.fromhex(hex_key), OP_CHECKSIG])) +def multi_a(k, hex_keys, sort=False): + """Construct a script expression for taproot_construct for a multi_a script.""" + xkeys = [bytes.fromhex(hex_key) for hex_key in hex_keys] + if sort: + xkeys.sort() + ops = [xkeys[0], OP_CHECKSIG] + for i in range(1, len(hex_keys)): + ops += [xkeys[i], OP_CHECKSIGADD] + ops += [k, OP_NUMEQUAL] + return (None, CScript(ops)) + 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.""" def set_test_params(self): - self.num_nodes = 3 + self.num_nodes = 2 self.setup_clean_chain = True - self.extra_args = [['-keypool=100'], ['-keypool=100'], ["-vbparams=taproot:1:1"]] + self.extra_args = [['-keypool=100'], ['-keypool=100']] self.supports_cli = False def skip_test_if_missing_module(self): @@ -194,19 +206,6 @@ class WalletTaprootTest(BitcoinTestFramework): pass @staticmethod - def rand_keys(n): - ret = [] - idxes = set() - for _ in range(n): - while True: - i = random.randrange(len(KEYS)) - if not i in idxes: - break - idxes.add(i) - ret.append(KEYS[i]) - return ret - - @staticmethod def make_desc(pattern, privmap, keys, pub_only = False): pat = pattern.replace("$H", H_POINT) for i in range(len(privmap)): @@ -221,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) @@ -242,15 +246,11 @@ class WalletTaprootTest(BitcoinTestFramework): assert_equal(len(rederive), 1) assert_equal(rederive[0], addr_g) - # tr descriptors can be imported regardless of Taproot status + # tr descriptors can be imported result = self.privs_tr_enabled.importdescriptors([{"desc": desc, "timestamp": "now"}]) assert(result[0]["success"]) result = self.pubs_tr_enabled.importdescriptors([{"desc": desc_pub, "timestamp": "now"}]) assert(result[0]["success"]) - result = self.privs_tr_disabled.importdescriptors([{"desc": desc, "timestamp": "now"}]) - assert result[0]["success"] - result = self.pubs_tr_disabled.importdescriptors([{"desc": desc_pub, "timestamp": "now"}]) - assert result[0]["success"] def do_test_sendtoaddress(self, comment, pattern, privmap, treefn, keys_pay, keys_change): self.log.info("Testing %s through sendtoaddress" % comment) @@ -275,7 +275,8 @@ class WalletTaprootTest(BitcoinTestFramework): self.generatetoaddress(self.nodes[0], 1, self.boring.getnewaddress(), sync_fun=self.no_op) test_balance = int(self.rpc_online.getbalance() * 100000000) ret_amnt = random.randrange(100000, test_balance) - res = self.rpc_online.sendtoaddress(address=self.boring.getnewaddress(), amount=Decimal(ret_amnt) / 100000000, subtractfeefromamount=True) + # Increase fee_rate to compensate for the wallet's inability to estimate fees for script path spends. + res = self.rpc_online.sendtoaddress(address=self.boring.getnewaddress(), amount=Decimal(ret_amnt) / 100000000, subtractfeefromamount=True, fee_rate=200) self.generatetoaddress(self.nodes[0], 1, self.boring.getnewaddress(), sync_fun=self.no_op) assert(self.rpc_online.gettransaction(res)["confirmations"] > 0) @@ -306,16 +307,30 @@ class WalletTaprootTest(BitcoinTestFramework): self.generatetoaddress(self.nodes[0], 1, self.boring.getnewaddress(), sync_fun=self.no_op) test_balance = int(self.psbt_online.getbalance() * 100000000) ret_amnt = random.randrange(100000, test_balance) - psbt = self.psbt_online.walletcreatefundedpsbt([], [{self.boring.getnewaddress(): Decimal(ret_amnt) / 100000000}], None, {"subtractFeeFromOutputs":[0]})['psbt'] - res = self.psbt_offline.walletprocesspsbt(psbt) - assert(res['complete']) + # 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, "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) assert(self.psbt_online.gettransaction(txid)['confirmations'] > 0) - def do_test(self, comment, pattern, privmap, treefn, nkeys): - keys = self.rand_keys(nkeys * 4) + def do_test(self, comment, pattern, privmap, treefn): + nkeys = len(privmap) + keys = random.sample(KEYS, nkeys * 4) self.do_test_addr(comment, pattern, privmap, treefn, keys[0:nkeys]) self.do_test_sendtoaddress(comment, pattern, privmap, treefn, keys[0:nkeys], keys[nkeys:2*nkeys]) self.do_test_psbt(comment, pattern, privmap, treefn, keys[2*nkeys:3*nkeys], keys[3*nkeys:4*nkeys]) @@ -324,12 +339,8 @@ class WalletTaprootTest(BitcoinTestFramework): self.log.info("Creating wallets...") self.nodes[0].createwallet(wallet_name="privs_tr_enabled", descriptors=True, blank=True) self.privs_tr_enabled = self.nodes[0].get_wallet_rpc("privs_tr_enabled") - self.nodes[2].createwallet(wallet_name="privs_tr_disabled", descriptors=True, blank=True) - self.privs_tr_disabled=self.nodes[2].get_wallet_rpc("privs_tr_disabled") self.nodes[0].createwallet(wallet_name="pubs_tr_enabled", descriptors=True, blank=True, disable_private_keys=True) self.pubs_tr_enabled = self.nodes[0].get_wallet_rpc("pubs_tr_enabled") - self.nodes[2].createwallet(wallet_name="pubs_tr_disabled", descriptors=True, blank=True, disable_private_keys=True) - self.pubs_tr_disabled=self.nodes[2].get_wallet_rpc("pubs_tr_disabled") self.nodes[0].createwallet(wallet_name="boring") self.nodes[0].createwallet(wallet_name="addr_gen", descriptors=True, disable_private_keys=True, blank=True) self.nodes[0].createwallet(wallet_name="rpc_online", descriptors=True, blank=True) @@ -349,75 +360,114 @@ class WalletTaprootTest(BitcoinTestFramework): "tr(XPRV)", "tr($1/*)", [True], - lambda k1: (key(k1), []), - 1 + lambda k1: (key(k1), []) ) self.do_test( "tr(H,XPRV)", "tr($H,pk($1/*))", [True], - lambda k1: (key(H_POINT), [pk(k1)]), - 1 + lambda k1: (key(H_POINT), [pk(k1)]) ) self.do_test( "wpkh(XPRV)", "wpkh($1/*)", [True], - None, - 1 + None ) self.do_test( "tr(XPRV,{H,{H,XPUB}})", "tr($1/*,{pk($H),{pk($H),pk($2/*)}})", [True, False], - lambda k1, k2: (key(k1), [pk(H_POINT), [pk(H_POINT), pk(k2)]]), - 2 + lambda k1, k2: (key(k1), [pk(H_POINT), [pk(H_POINT), pk(k2)]]) ) self.do_test( "wsh(multi(1,XPRV,XPUB))", "wsh(multi(1,$1/*,$2/*))", [True, False], - None, - 2 + None ) self.do_test( "tr(XPRV,{XPUB,XPUB})", "tr($1/*,{pk($2/*),pk($2/*)})", [True, False], - lambda k1, k2: (key(k1), [pk(k2), pk(k2)]), - 2 + lambda k1, k2: (key(k1), [pk(k2), pk(k2)]) ) self.do_test( "tr(XPRV,{{XPUB,H},{H,XPUB}})", "tr($1/*,{{pk($2/*),pk($H)},{pk($H),pk($2/*)}})", [True, False], - lambda k1, k2: (key(k1), [[pk(k2), pk(H_POINT)], [pk(H_POINT), pk(k2)]]), - 2 + lambda k1, k2: (key(k1), [[pk(k2), pk(H_POINT)], [pk(H_POINT), pk(k2)]]) ) self.do_test( "tr(XPUB,{{H,{H,XPUB}},{H,{H,{H,XPRV}}}})", "tr($1/*,{{pk($H),{pk($H),pk($2/*)}},{pk($H),{pk($H),{pk($H),pk($3/*)}}}})", [False, False, True], - lambda k1, k2, k3: (key(k1), [[pk(H_POINT), [pk(H_POINT), pk(k2)]], [pk(H_POINT), [pk(H_POINT), [pk(H_POINT), pk(k3)]]]]), - 3 + lambda k1, k2, k3: (key(k1), [[pk(H_POINT), [pk(H_POINT), pk(k2)]], [pk(H_POINT), [pk(H_POINT), [pk(H_POINT), pk(k3)]]]]) ) self.do_test( "tr(XPRV,{XPUB,{{XPUB,{H,H}},{{H,H},XPUB}}})", "tr($1/*,{pk($2/*),{{pk($2/*),{pk($H),pk($H)}},{{pk($H),pk($H)},pk($2/*)}}})", [True, False], - lambda k1, k2: (key(k1), [pk(k2), [[pk(k2), [pk(H_POINT), pk(H_POINT)]], [[pk(H_POINT), pk(H_POINT)], pk(k2)]]]), - 2 + lambda k1, k2: (key(k1), [pk(k2), [[pk(k2), [pk(H_POINT), pk(H_POINT)]], [[pk(H_POINT), pk(H_POINT)], pk(k2)]]]) + ) + self.do_test( + "tr(H,multi_a(1,XPRV))", + "tr($H,multi_a(1,$1/*))", + [True], + lambda k1: (key(H_POINT), [multi_a(1, [k1])]) + ) + self.do_test( + "tr(H,sortedmulti_a(1,XPRV,XPUB))", + "tr($H,sortedmulti_a(1,$1/*,$2/*))", + [True, False], + lambda k1, k2: (key(H_POINT), [multi_a(1, [k1, k2], True)]) + ) + self.do_test( + "tr(H,{H,multi_a(1,XPUB,XPRV)})", + "tr($H,{pk($H),multi_a(1,$1/*,$2/*)})", + [False, True], + lambda k1, k2: (key(H_POINT), [pk(H_POINT), [multi_a(1, [k1, k2])]]) + ) + self.do_test( + "tr(H,sortedmulti_a(1,XPUB,XPRV,XPRV))", + "tr($H,sortedmulti_a(1,$1/*,$2/*,$3/*))", + [False, True, True], + lambda k1, k2, k3: (key(H_POINT), [multi_a(1, [k1, k2, k3], True)]) + ) + self.do_test( + "tr(H,multi_a(2,XPRV,XPUB,XPRV))", + "tr($H,multi_a(2,$1/*,$2/*,$3/*))", + [True, False, True], + lambda k1, k2, k3: (key(H_POINT), [multi_a(2, [k1, k2, k3])]) + ) + self.do_test( + "tr(XPUB,{{XPUB,{XPUB,sortedmulti_a(2,XPRV,XPUB,XPRV)}})", + "tr($2/*,{pk($2/*),{pk($2/*),sortedmulti_a(2,$1/*,$2/*,$3/*)}})", + [True, False, True], + lambda k1, k2, k3: (key(k2), [pk(k2), [pk(k2), multi_a(2, [k1, k2, k3], True)]]) + ) + rnd_pos = random.randrange(MAX_PUBKEYS_PER_MULTI_A) + self.do_test( + "tr(XPUB,multi_a(1,H...,XPRV,H...))", + "tr($2/*,multi_a(1" + (",$H" * rnd_pos) + ",$1/*" + (",$H" * (MAX_PUBKEYS_PER_MULTI_A - 1 - rnd_pos)) + "))", + [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...") - txid = self.rpc_online.sendtoaddress(address=self.boring.getnewaddress(), amount=self.rpc_online.getbalance(), subtractfeefromamount=True) + txid = self.rpc_online.sendall(recipients=[self.boring.getnewaddress()])["txid"] self.generatetoaddress(self.nodes[0], 1, self.boring.getnewaddress(), sync_fun=self.no_op) assert(self.rpc_online.gettransaction(txid)["confirmations"] > 0) - psbt = self.psbt_online.walletcreatefundedpsbt([], [{self.boring.getnewaddress(): self.psbt_online.getbalance()}], None, {"subtractFeeFromOutputs": [0]})['psbt'] - res = self.psbt_offline.walletprocesspsbt(psbt) - assert(res['complete']) + psbt = self.psbt_online.sendall(recipients=[self.boring.getnewaddress()], options={"psbt": True})["psbt"] + 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/get_previous_releases.py b/test/get_previous_releases.py index e0d48f8047..c983dce619 100755 --- a/test/get_previous_releases.py +++ b/test/get_previous_releases.py @@ -19,54 +19,67 @@ import subprocess import sys import hashlib - SHA256_SUMS = { - "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", + "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": {"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", - - "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", - - "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", + "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": {"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": {"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": {"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"}, } @@ -92,8 +105,11 @@ def download_binary(tag, args) -> int: if match: 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", "arm64-apple-darwin"]: + platform = "osx64" tarball = 'bitcoin-{tag}-{platform}.tar.gz'.format( - tag=tag[1:], platform=args.platform) + tag=tag[1:], platform=platform) tarballUrl = 'https://bitcoincore.org/{bin_path}/{tarball}'.format( bin_path=bin_path, tarball=tarball) @@ -119,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 @@ -197,8 +213,8 @@ def check_host(args) -> int: platforms = { 'aarch64-*-linux*': 'aarch64-linux-gnu', 'x86_64-*-linux*': 'x86_64-linux-gnu', - 'x86_64-apple-darwin*': 'osx64', - 'aarch64-apple-darwin*': 'osx64', + 'x86_64-apple-darwin*': 'x86_64-apple-darwin', + 'aarch64-apple-darwin*': 'arm64-apple-darwin', } args.platform = '' for pattern, target in platforms.items(): @@ -244,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 f4165f908e..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) @@ -39,6 +38,6 @@ To do so, add the upstream repository as remote: git remote add --fetch secp256k1 https://github.com/bitcoin-core/secp256k1.git ``` -lint-all.sh +all-lint.py =========== Calls other scripts with the `lint-` prefix. diff --git a/test/lint/all-lint.py b/test/lint/all-lint.py new file mode 100755 index 0000000000..34a7b9742a --- /dev/null +++ b/test/lint/all-lint.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python3 +# +# Copyright (c) 2017-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. +# +# This script runs all test/lint/lint-* files, and fails if any exit +# with a non-zero status code. + +from glob import glob +from pathlib import Path +from subprocess import run + +exit_code = 0 +mod_path = Path(__file__).parent +for lint in glob(f"{mod_path}/lint-*.py"): + result = run([lint]) + if result.returncode != 0: + print(f"^---- failure generated from {lint.split('/')[-1]}") + exit_code |= result.returncode + +exit(exit_code) diff --git a/test/lint/extended-lint-all.sh b/test/lint/extended-lint-all.sh deleted file mode 100755 index be5d9db4a9..0000000000 --- a/test/lint/extended-lint-all.sh +++ /dev/null @@ -1,26 +0,0 @@ -#!/usr/bin/env bash -# -# Copyright (c) 2019-2020 The Bitcoin Core developers -# Distributed under the MIT software license, see the accompanying -# file COPYING or http://www.opensource.org/licenses/mit-license.php. -# -# This script runs all contrib/devtools/extended-lint-*.sh files, and fails if -# any exit with a non-zero status code. - -# This script is intentionally locale dependent by not setting "export LC_ALL=C" -# in order to allow for the executed lint scripts to opt in or opt out of locale -# dependence themselves. - -set -u - -SCRIPTDIR=$(dirname "${BASH_SOURCE[0]}") -LINTALL=$(basename "${BASH_SOURCE[0]}") - -for f in "${SCRIPTDIR}"/extended-lint-*.sh; do - if [ "$(basename "$f")" != "$LINTALL" ]; then - if ! "$f"; then - echo "^---- failure generated from $f" - exit 1 - fi - fi -done diff --git a/test/lint/extended-lint-cppcheck.sh b/test/lint/extended-lint-cppcheck.sh deleted file mode 100755 index 2af39ed60a..0000000000 --- a/test/lint/extended-lint-cppcheck.sh +++ /dev/null @@ -1,88 +0,0 @@ -#!/usr/bin/env bash -# -# 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. -# - -export LC_ALL=C - -ENABLED_CHECKS=( - "Class '.*' has a constructor with 1 argument that is not explicit." - "Struct '.*' has a constructor with 1 argument that is not explicit." -) - -IGNORED_WARNINGS=( - "src/arith_uint256.h:.* Class 'arith_uint256' has a constructor with 1 argument that is not explicit." - "src/arith_uint256.h:.* Class 'base_uint < 256 >' has a constructor with 1 argument that is not explicit." - "src/arith_uint256.h:.* Class 'base_uint' has a constructor with 1 argument that is not explicit." - "src/coins.h:.* Class 'CCoinsViewBacked' has a constructor with 1 argument that is not explicit." - "src/coins.h:.* Class 'CCoinsViewCache' has a constructor with 1 argument that is not explicit." - "src/coins.h:.* Class 'CCoinsViewCursor' has a constructor with 1 argument that is not explicit." - "src/net.h:.* Class 'CNetMessage' has a constructor with 1 argument that is not explicit." - "src/policy/feerate.h:.* Class 'CFeeRate' has a constructor with 1 argument that is not explicit." - "src/prevector.h:.* Class 'const_iterator' has a constructor with 1 argument that is not explicit." - "src/prevector.h:.* Class 'const_reverse_iterator' has a constructor with 1 argument that is not explicit." - "src/prevector.h:.* Class 'iterator' has a constructor with 1 argument that is not explicit." - "src/prevector.h:.* Class 'reverse_iterator' has a constructor with 1 argument that is not explicit." - "src/primitives/block.h:.* Class 'CBlock' has a constructor with 1 argument that is not explicit." - "src/primitives/transaction.h:.* Class 'CTransaction' has a constructor with 1 argument that is not explicit." - "src/protocol.h:.* Class 'CMessageHeader' has a constructor with 1 argument that is not explicit." - "src/qt/guiutil.h:.* Class 'ItemDelegate' has a constructor with 1 argument that is not explicit." - "src/rpc/util.h:.* Struct 'RPCResults' has a constructor with 1 argument that is not explicit." - "src/rpc/util.h:.* Struct 'UniValueType' has a constructor with 1 argument that is not explicit." - "src/rpc/util.h:.* style: Struct 'UniValueType' has a constructor with 1 argument that is not explicit." - "src/script/descriptor.cpp:.* Class 'AddressDescriptor' has a constructor with 1 argument that is not explicit." - "src/script/descriptor.cpp:.* Class 'ComboDescriptor' has a constructor with 1 argument that is not explicit." - "src/script/descriptor.cpp:.* Class 'ConstPubkeyProvider' has a constructor with 1 argument that is not explicit." - "src/script/descriptor.cpp:.* Class 'PKDescriptor' has a constructor with 1 argument that is not explicit." - "src/script/descriptor.cpp:.* Class 'PKHDescriptor' has a constructor with 1 argument that is not explicit." - "src/script/descriptor.cpp:.* Class 'RawDescriptor' has a constructor with 1 argument that is not explicit." - "src/script/descriptor.cpp:.* Class 'SHDescriptor' has a constructor with 1 argument that is not explicit." - "src/script/descriptor.cpp:.* Class 'WPKHDescriptor' has a constructor with 1 argument that is not explicit." - "src/script/descriptor.cpp:.* Class 'WSHDescriptor' has a constructor with 1 argument that is not explicit." - "src/script/script.h:.* Class 'CScript' has a constructor with 1 argument that is not explicit." - "src/script/standard.h:.* Class 'CScriptID' has a constructor with 1 argument that is not explicit." - "src/span.h:.* Class 'Span < const CRPCCommand >' has a constructor with 1 argument that is not explicit." - "src/span.h:.* Class 'Span < const char >' has a constructor with 1 argument that is not explicit." - "src/span.h:.* Class 'Span < const std :: vector <unsigned char > >' has a constructor with 1 argument that is not explicit." - "src/span.h:.* Class 'Span < const uint8_t >' has a constructor with 1 argument that is not explicit." - "src/span.h:.* Class 'Span' has a constructor with 1 argument that is not explicit." - "src/support/allocators/secure.h:.* Struct 'secure_allocator < char >' has a constructor with 1 argument that is not explicit." - "src/support/allocators/secure.h:.* Struct 'secure_allocator < RNGState >' has a constructor with 1 argument that is not explicit." - "src/support/allocators/secure.h:.* Struct 'secure_allocator < unsigned char >' has a constructor with 1 argument that is not explicit." - "src/support/allocators/zeroafterfree.h:.* Struct 'zero_after_free_allocator < char >' has a constructor with 1 argument that is not explicit." - "src/test/checkqueue_tests.cpp:.* Struct 'FailingCheck' has a constructor with 1 argument that is not explicit." - "src/test/checkqueue_tests.cpp:.* Struct 'MemoryCheck' has a constructor with 1 argument that is not explicit." - "src/test/checkqueue_tests.cpp:.* Struct 'UniqueCheck' has a constructor with 1 argument that is not explicit." - "src/test/fuzz/util.h:.* Class 'FuzzedFileProvider' has a constructor with 1 argument that is not explicit." - "src/test/fuzz/util.h:.* Class 'FuzzedAutoFileProvider' has a constructor with 1 argument that is not explicit." - "src/wallet/db.h:.* Class 'BerkeleyEnvironment' has a constructor with 1 argument that is not explicit." -) - -if ! command -v cppcheck > /dev/null; then - echo "Skipping cppcheck linting since cppcheck is not installed. Install by running \"apt install cppcheck\"" - exit 0 -fi - -function join_array { - local IFS="$1" - shift - echo "$*" -} - -ENABLED_CHECKS_REGEXP=$(join_array "|" "${ENABLED_CHECKS[@]}") -IGNORED_WARNINGS_REGEXP=$(join_array "|" "${IGNORED_WARNINGS[@]}") -WARNINGS=$(git ls-files -- "*.cpp" "*.h" ":(exclude)src/leveldb/" ":(exclude)src/crc32c/" ":(exclude)src/secp256k1/" ":(exclude)src/minisketch/" ":(exclude)src/univalue/" | \ - xargs cppcheck --enable=all -j "$(getconf _NPROCESSORS_ONLN)" --language=c++ --std=c++17 --template=gcc -D__cplusplus -DCLIENT_VERSION_BUILD -DCLIENT_VERSION_IS_RELEASE -DCLIENT_VERSION_MAJOR -DCLIENT_VERSION_MINOR -DCOPYRIGHT_YEAR -DDEBUG -I src/ -q 2>&1 | sort -u | \ - grep -E "${ENABLED_CHECKS_REGEXP}" | \ - grep -vE "${IGNORED_WARNINGS_REGEXP}") -if [[ ${WARNINGS} != "" ]]; then - echo "${WARNINGS}" - echo - echo "Advice not applicable in this specific case? Add an exception by updating" - echo "IGNORED_WARNINGS in $0" - # Uncomment to enforce the developer note policy "By default, declare single-argument constructors `explicit`" - # exit 1 -fi -exit 0 diff --git a/test/lint/lint-all.sh b/test/lint/lint-all.sh deleted file mode 100755 index fabc24c91b..0000000000 --- a/test/lint/lint-all.sh +++ /dev/null @@ -1,30 +0,0 @@ -#!/usr/bin/env bash -# -# Copyright (c) 2017-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. -# -# This script runs all contrib/devtools/lint-*.sh files, and fails if any exit -# with a non-zero status code. - -# This script is intentionally locale dependent by not setting "export LC_ALL=C" -# in order to allow for the executed lint scripts to opt in or opt out of locale -# dependence themselves. - -set -u - -SCRIPTDIR=$(dirname "${BASH_SOURCE[0]}") -LINTALL=$(basename "${BASH_SOURCE[0]}") - -EXIT_CODE=0 - -for f in "${SCRIPTDIR}"/lint-*.sh; do - if [ "$(basename "$f")" != "$LINTALL" ]; then - if ! "$f"; then - echo "^---- failure generated from $f" - EXIT_CODE=1 - fi - fi -done - -exit ${EXIT_CODE} diff --git a/test/lint/lint-assertions.py b/test/lint/lint-assertions.py new file mode 100755 index 0000000000..195ff33d11 --- /dev/null +++ b/test/lint/lint-assertions.py @@ -0,0 +1,52 @@ +#!/usr/bin/env python3 +# +# Copyright (c) 2018-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. +# +# Check for assertions with obvious side effects. + +import sys +import subprocess + + +def git_grep(params: [], error_msg: ""): + try: + output = subprocess.check_output(["git", "grep", *params], universal_newlines=True, encoding="utf8") + print(error_msg) + print(output) + return 1 + except subprocess.CalledProcessError as ex1: + if ex1.returncode > 1: + raise ex1 + return 0 + + +def main(): + # PRE31-C (SEI CERT C Coding Standard): + # "Assertions should not contain assignments, increment, or decrement operators." + exit_code = git_grep([ + "-E", + r"[^_]assert\(.*(\+\+|\-\-|[^=!<>]=[^=!<>]).*\);", + "--", + "*.cpp", + "*.h", + ], "Assertions should not have side effects:") + + # Aborting the whole process is undesirable for RPC code. So nonfatal + # checks should be used over assert. See: src/util/check.h + # src/rpc/server.cpp is excluded from this check since it's mostly meta-code. + exit_code |= git_grep([ + "-nE", + r"\<(A|a)ss(ume|ert) *\(.*\);", + "--", + "src/rpc/", + "src/wallet/rpc*", + ":(exclude)src/rpc/server.cpp", + ], "CHECK_NONFATAL(condition) or NONFATAL_UNREACHABLE should be used instead of assert for RPC code.") + + sys.exit(exit_code) + + +if __name__ == "__main__": + main() diff --git a/test/lint/lint-assertions.sh b/test/lint/lint-assertions.sh deleted file mode 100755 index 2860f5621b..0000000000 --- a/test/lint/lint-assertions.sh +++ /dev/null @@ -1,34 +0,0 @@ -#!/usr/bin/env bash -# -# Copyright (c) 2018-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. -# -# Check for assertions with obvious side effects. - -export LC_ALL=C - -EXIT_CODE=0 - -# PRE31-C (SEI CERT C Coding Standard): -# "Assertions should not contain assignments, increment, or decrement operators." -OUTPUT=$(git grep -E '[^_]assert\(.*(\+\+|\-\-|[^=!<>]=[^=!<>]).*\);' -- "*.cpp" "*.h") -if [[ ${OUTPUT} != "" ]]; then - echo "Assertions should not have side effects:" - echo - echo "${OUTPUT}" - EXIT_CODE=1 -fi - -# Macro CHECK_NONFATAL(condition) should be used instead of assert for RPC code, where it -# is undesirable to crash the whole program. See: src/util/check.h -# src/rpc/server.cpp is excluded from this check since it's mostly meta-code. -OUTPUT=$(git grep -nE '\<(A|a)ssert *\(.*\);' -- "src/rpc/" "src/wallet/rpc*" ":(exclude)src/rpc/server.cpp") -if [[ ${OUTPUT} != "" ]]; then - echo "CHECK_NONFATAL(condition) should be used instead of assert for RPC code." - echo - echo "${OUTPUT}" - EXIT_CODE=1 -fi - -exit ${EXIT_CODE} diff --git a/test/lint/lint-circular-dependencies.py b/test/lint/lint-circular-dependencies.py new file mode 100755 index 0000000000..a0f17ac119 --- /dev/null +++ b/test/lint/lint-circular-dependencies.py @@ -0,0 +1,83 @@ +#!/usr/bin/env python3 +# +# Copyright (c) 2020-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. +# +# Check for circular dependencies + +import os +import re +import subprocess +import sys + +EXPECTED_CIRCULAR_DEPENDENCIES = ( + "chainparamsbase -> util/system -> chainparamsbase", + "node/blockstorage -> validation -> node/blockstorage", + "policy/fees -> txmempool -> policy/fees", + "qt/addresstablemodel -> qt/walletmodel -> qt/addresstablemodel", + "qt/recentrequeststablemodel -> qt/walletmodel -> qt/recentrequeststablemodel", + "qt/sendcoinsdialog -> qt/walletmodel -> qt/sendcoinsdialog", + "qt/transactiontablemodel -> qt/walletmodel -> qt/transactiontablemodel", + "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" + + +def main(): + circular_dependencies = [] + exit_code = 0 + + os.chdir(CODE_DIR) + files = subprocess.check_output( + ['git', 'ls-files', '--', '*.h', '*.cpp'], + universal_newlines=True, + ).splitlines() + + command = [sys.executable, "../contrib/devtools/circular-dependencies.py", *files] + dependencies_output = subprocess.run( + command, + stdout=subprocess.PIPE, + universal_newlines=True, + ) + + for dependency_str in dependencies_output.stdout.rstrip().split("\n"): + circular_dependencies.append( + re.sub("^Circular dependency: ", "", dependency_str) + ) + + # Check for an unexpected dependencies + for dependency in circular_dependencies: + if dependency not in EXPECTED_CIRCULAR_DEPENDENCIES: + exit_code = 1 + print( + f'A new circular dependency in the form of "{dependency}" appears to have been introduced.\n', + file=sys.stderr, + ) + + # Check for missing expected dependencies + for expected_dependency in EXPECTED_CIRCULAR_DEPENDENCIES: + if expected_dependency not in circular_dependencies: + exit_code = 1 + print( + f'Good job! The circular dependency "{expected_dependency}" is no longer present.', + ) + print( + f"Please remove it from EXPECTED_CIRCULAR_DEPENDENCIES in {__file__}", + ) + print( + "to make sure this circular dependency is not accidentally reintroduced.\n", + ) + + sys.exit(exit_code) + + +if __name__ == "__main__": + main() diff --git a/test/lint/lint-circular-dependencies.sh b/test/lint/lint-circular-dependencies.sh deleted file mode 100755 index 69185090d1..0000000000 --- a/test/lint/lint-circular-dependencies.sh +++ /dev/null @@ -1,65 +0,0 @@ -#!/usr/bin/env bash -# -# Copyright (c) 2018-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. -# -# Check for circular dependencies - -export LC_ALL=C - -EXPECTED_CIRCULAR_DEPENDENCIES=( - "chainparamsbase -> util/system -> chainparamsbase" - "node/blockstorage -> validation -> node/blockstorage" - "index/blockfilterindex -> node/blockstorage -> validation -> index/blockfilterindex" - "index/base -> validation -> index/blockfilterindex -> index/base" - "index/coinstatsindex -> node/coinstats -> index/coinstatsindex" - "policy/fees -> txmempool -> policy/fees" - "qt/addresstablemodel -> qt/walletmodel -> qt/addresstablemodel" - "qt/recentrequeststablemodel -> qt/walletmodel -> qt/recentrequeststablemodel" - "qt/sendcoinsdialog -> qt/walletmodel -> qt/sendcoinsdialog" - "qt/transactiontablemodel -> qt/walletmodel -> qt/transactiontablemodel" - "wallet/fees -> wallet/wallet -> wallet/fees" - "wallet/wallet -> wallet/walletdb -> wallet/wallet" - "node/coinstats -> validation -> node/coinstats" -) - -EXIT_CODE=0 - -CIRCULAR_DEPENDENCIES=() - -IFS=$'\n' -for CIRC in $(cd src && ../contrib/devtools/circular-dependencies.py {*,*/*,*/*/*}.{h,cpp} | sed -e 's/^Circular dependency: //'); do - CIRCULAR_DEPENDENCIES+=( "$CIRC" ) - IS_EXPECTED_CIRC=0 - for EXPECTED_CIRC in "${EXPECTED_CIRCULAR_DEPENDENCIES[@]}"; do - if [[ "${CIRC}" == "${EXPECTED_CIRC}" ]]; then - IS_EXPECTED_CIRC=1 - break - fi - done - if [[ ${IS_EXPECTED_CIRC} == 0 ]]; then - echo "A new circular dependency in the form of \"${CIRC}\" appears to have been introduced." - echo - EXIT_CODE=1 - fi -done - -for EXPECTED_CIRC in "${EXPECTED_CIRCULAR_DEPENDENCIES[@]}"; do - IS_PRESENT_EXPECTED_CIRC=0 - for CIRC in "${CIRCULAR_DEPENDENCIES[@]}"; do - if [[ "${CIRC}" == "${EXPECTED_CIRC}" ]]; then - IS_PRESENT_EXPECTED_CIRC=1 - break - fi - done - if [[ ${IS_PRESENT_EXPECTED_CIRC} == 0 ]]; then - echo "Good job! The circular dependency \"${EXPECTED_CIRC}\" is no longer present." - echo "Please remove it from EXPECTED_CIRCULAR_DEPENDENCIES in $0" - echo "to make sure this circular dependency is not accidentally reintroduced." - echo - EXIT_CODE=1 - fi -done - -exit ${EXIT_CODE} diff --git a/test/lint/lint-cpp.sh b/test/lint/lint-cpp.sh deleted file mode 100755 index cac57b968d..0000000000 --- a/test/lint/lint-cpp.sh +++ /dev/null @@ -1,21 +0,0 @@ -#!/usr/bin/env bash -# -# 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. -# -# Check for various C++ code patterns we want to avoid. - -export LC_ALL=C - -EXIT_CODE=0 - -OUTPUT=$(git grep -E "boost::bind\(" -- "*.cpp" "*.h") -if [[ ${OUTPUT} != "" ]]; then - echo "Use of boost::bind detected. Use std::bind instead." - echo - echo "${OUTPUT}" - EXIT_CODE=1 -fi - -exit ${EXIT_CODE}
\ No newline at end of file diff --git a/test/lint/lint-files.py b/test/lint/lint-files.py index 68b795eef7..123bee2cbc 100755 --- a/test/lint/lint-files.py +++ b/test/lint/lint-files.py @@ -11,18 +11,20 @@ import os import re import sys from subprocess import check_output -from typing import Optional, NoReturn +from typing import Dict, Optional, NoReturn -CMD_ALL_FILES = "git ls-files -z --full-name" -CMD_SOURCE_FILES = 'git ls-files -z --full-name -- "*.[cC][pP][pP]" "*.[hH]" "*.[pP][yY]" "*.[sS][hH]"' -CMD_SHEBANG_FILES = "git grep --full-name --line-number -I '^#!'" +CMD_TOP_LEVEL = ["git", "rev-parse", "--show-toplevel"] +CMD_ALL_FILES = ["git", "ls-files", "-z", "--full-name", "--stage"] +CMD_SHEBANG_FILES = ["git", "grep", "--full-name", "--line-number", "-I", "^#!"] + +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 = 644 -ALLOWED_PERMISSION_EXECUTABLES = 755 +ALLOWED_PERMISSION_NON_EXECUTABLES = 0o644 +ALLOWED_PERMISSION_EXECUTABLES = 0o755 ALLOWED_EXECUTABLE_SHEBANG = { "py": [b"#!/usr/bin/env python3"], "sh": [b"#!/usr/bin/env bash", b"#!/bin/sh"], @@ -30,8 +32,15 @@ ALLOWED_EXECUTABLE_SHEBANG = { class FileMeta(object): - def __init__(self, file_path: str): - self.file_path = file_path + def __init__(self, file_spec: str): + '''Parse a `git ls files --stage` output line.''' + # 100755 5a150d5f8031fcd75e80a4dd9843afa33655f579 0 ci/test/00_setup_env.sh + meta, self.file_path = file_spec.split('\t', 2) + meta = meta.split() + # The octal file permission of the file. Internally, git only + # keeps an 'executable' bit, so this will always be 0o644 or 0o755. + self.permissions = int(meta[0], 8) & 0o7777 + # We don't currently care about the other fields @property def extension(self) -> Optional[str]: @@ -59,20 +68,24 @@ class FileMeta(object): except IndexError: return None - @property - def permissions(self) -> int: - """ - Returns the octal file permission of the file - """ - return int(oct(os.stat(self.file_path).st_mode)[-3:]) +def get_git_file_metadata() -> Dict[str, FileMeta]: + ''' + Return a dictionary mapping the name of all files in the repository to git tree metadata. + ''' + files_raw = check_output(CMD_ALL_FILES).decode("utf8").rstrip("\0").split("\0") + files = {} + for file_spec in files_raw: + meta = FileMeta(file_spec) + files[meta.file_path] = meta + return files -def check_all_filenames() -> int: +def check_all_filenames(files) -> int: """ Checks every file in the repository against an allowed regexp to make sure only lowercase or uppercase alphanumerics (a-zA-Z0-9), underscores (_), hyphens (-), at (@) and dots (.) are used in repository filenames. """ - filenames = check_output(CMD_ALL_FILES, shell=True).decode("utf8").rstrip("\0").split("\0") + filenames = files.keys() filename_regex = re.compile(ALLOWED_FILENAME_REGEXP) failed_tests = 0 for filename in filenames: @@ -84,14 +97,14 @@ def check_all_filenames() -> int: return failed_tests -def check_source_filenames() -> int: +def check_source_filenames(files) -> int: """ Checks only source files (*.cpp, *.h, *.py, *.sh) against a stricter allowed regexp to make sure only lowercase alphanumerics (a-z0-9), underscores (_), hyphens (-) and dots (.) are used in source code filenames. Additionally there is an exception regexp for directories or files which are excepted from matching this regexp. """ - filenames = check_output(CMD_SOURCE_FILES, shell=True).decode("utf8").rstrip("\0").split("\0") + filenames = [filename for filename in files.keys() if re.match(ALL_SOURCE_FILENAMES_REGEXP, filename, re.IGNORECASE)] filename_regex = re.compile(ALLOWED_SOURCE_FILENAME_REGEXP) filename_exception_regex = re.compile(ALLOWED_SOURCE_FILENAME_EXCEPTION_REGEXP) failed_tests = 0 @@ -104,16 +117,14 @@ def check_source_filenames() -> int: return failed_tests -def check_all_file_permissions() -> int: +def check_all_file_permissions(files) -> int: """ Checks all files in the repository match an allowed executable or non-executable file permission octal. Additionally checks that for executable files, the file contains a shebang line """ - filenames = check_output(CMD_ALL_FILES, shell=True).decode("utf8").rstrip("\0").split("\0") failed_tests = 0 - for filename in filenames: - file_meta = FileMeta(filename) + for filename, file_meta in files.items(): if file_meta.permissions == ALLOWED_PERMISSION_EXECUTABLES: with open(filename, "rb") as f: shebang = f.readline().rstrip(b"\n") @@ -121,7 +132,7 @@ def check_all_file_permissions() -> int: # For any file with executable permissions the first line must contain a shebang if not shebang.startswith(b"#!"): print( - f"""File "{filename}" has permission {ALLOWED_PERMISSION_EXECUTABLES} (executable) and is thus expected to contain a shebang '#!'. Add shebang or do "chmod {ALLOWED_PERMISSION_NON_EXECUTABLES} {filename}" to make it non-executable.""" + f"""File "{filename}" has permission {ALLOWED_PERMISSION_EXECUTABLES:03o} (executable) and is thus expected to contain a shebang '#!'. Add shebang or do "chmod {ALLOWED_PERMISSION_NON_EXECUTABLES:03o} {filename}" to make it non-executable.""" ) failed_tests += 1 @@ -144,18 +155,18 @@ def check_all_file_permissions() -> int: continue else: print( - f"""File "{filename}" has unexpected permission {file_meta.permissions}. Do "chmod {ALLOWED_PERMISSION_NON_EXECUTABLES} {filename}" (if non-executable) or "chmod {ALLOWED_PERMISSION_EXECUTABLES} {filename}" (if executable).""" + f"""File "{filename}" has unexpected permission {file_meta.permissions:03o}. Do "chmod {ALLOWED_PERMISSION_NON_EXECUTABLES:03o} {filename}" (if non-executable) or "chmod {ALLOWED_PERMISSION_EXECUTABLES:03o} {filename}" (if executable).""" ) failed_tests += 1 return failed_tests -def check_shebang_file_permissions() -> int: +def check_shebang_file_permissions(files_meta) -> int: """ Checks every file that contains a shebang line to ensure it has an executable permission """ - filenames = check_output(CMD_SHEBANG_FILES, shell=True).decode("utf8").strip().split("\n") + filenames = check_output(CMD_SHEBANG_FILES).decode("utf8").strip().split("\n") # The git grep command we use returns files which contain a shebang on any line within the file # so we need to filter the list to only files with the shebang on the first line @@ -163,7 +174,7 @@ def check_shebang_file_permissions() -> int: failed_tests = 0 for filename in filenames: - file_meta = FileMeta(filename) + file_meta = files_meta[filename] if file_meta.permissions != ALLOWED_PERMISSION_EXECUTABLES: # These file types are typically expected to be sourced and not executed directly if file_meta.full_extension in ["bash", "init", "openrc", "sh.in"]: @@ -177,18 +188,23 @@ def check_shebang_file_permissions() -> int: continue print( - f"""File "{filename}" contains a shebang line, but has the file permission {file_meta.permissions} instead of the expected executable permission {ALLOWED_PERMISSION_EXECUTABLES}. Do "chmod {ALLOWED_PERMISSION_EXECUTABLES} {filename}" (or remove the shebang line).""" + f"""File "{filename}" contains a shebang line, but has the file permission {file_meta.permissions:03o} instead of the expected executable permission {ALLOWED_PERMISSION_EXECUTABLES:03o}. Do "chmod {ALLOWED_PERMISSION_EXECUTABLES:03o} {filename}" (or remove the shebang line).""" ) failed_tests += 1 return failed_tests def main() -> NoReturn: + root_dir = check_output(CMD_TOP_LEVEL).decode("utf8").strip() + os.chdir(root_dir) + + files = get_git_file_metadata() + failed_tests = 0 - failed_tests += check_all_filenames() - failed_tests += check_source_filenames() - failed_tests += check_all_file_permissions() - failed_tests += check_shebang_file_permissions() + failed_tests += check_all_filenames(files) + failed_tests += check_source_filenames(files) + failed_tests += check_all_file_permissions(files) + failed_tests += check_shebang_file_permissions(files) if failed_tests: print( diff --git a/test/lint/lint-files.sh b/test/lint/lint-files.sh deleted file mode 100755 index 86d7fc724a..0000000000 --- a/test/lint/lint-files.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/usr/bin/env bash -# 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. - -export LC_ALL=C - -set -e -cd "$(dirname "$0")/../.." -test/lint/lint-files.py diff --git a/test/lint/lint-format-strings.py b/test/lint/lint-format-strings.py index b814446125..f54126e023 100755 --- a/test/lint/lint-format-strings.py +++ b/test/lint/lint-format-strings.py @@ -1,291 +1,100 @@ #!/usr/bin/env python3 # -# Copyright (c) 2018-2019 The Bitcoin Core developers +# Copyright (c) 2018-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. # -# Lint format strings: This program checks that the number of arguments passed -# to a variadic format string function matches the number of format specifiers -# in the format string. -import argparse +""" +Lint format strings: This program checks that the number of arguments passed +to a variadic format string function matches the number of format specifiers +in the format string. +""" + +import subprocess import re import sys -FALSE_POSITIVES = [ - ("src/dbwrapper.cpp", "vsnprintf(p, limit - p, format, backup_ap)"), - ("src/index/base.cpp", "FatalError(const char* fmt, const Args&... args)"), - ("src/netbase.cpp", "LogConnectFailure(bool manual_connection, const char* fmt, const Args&... args)"), - ("src/clientversion.cpp", "strprintf(_(COPYRIGHT_HOLDERS).translated, COPYRIGHT_HOLDERS_SUBSTITUTION)"), - ("src/validationinterface.cpp", "LogPrint(BCLog::VALIDATION, fmt \"\\n\", __VA_ARGS__)"), - ("src/wallet/wallet.h", "WalletLogPrintf(std::string fmt, Params... parameters)"), - ("src/wallet/wallet.h", "LogPrintf((\"%s \" + fmt).c_str(), GetDisplayName(), parameters...)"), - ("src/wallet/scriptpubkeyman.h", "WalletLogPrintf(std::string fmt, Params... parameters)"), - ("src/wallet/scriptpubkeyman.h", "LogPrintf((\"%s \" + fmt).c_str(), m_storage.GetDisplayName(), parameters...)"), +FUNCTION_NAMES_AND_NUMBER_OF_LEADING_ARGUMENTS = [ + 'FatalError,0', + 'fprintf,1', + 'tfm::format,1', # Assuming tfm::::format(std::ostream&, ... + 'LogConnectFailure,1', + 'LogPrint,1', + 'LogPrintf,0', + 'LogPrintfCategory,1', + 'LogPrintLevel,2', + 'printf,0', + 'snprintf,2', + 'sprintf,1', + 'strprintf,0', + 'vfprintf,1', + 'vprintf,1', + 'vsnprintf,1', + 'vsprintf,1', + 'WalletLogPrintf,0', ] - - -def parse_function_calls(function_name, source_code): - """Return an array with all calls to function function_name in string source_code. - Preprocessor directives and C++ style comments ("//") in source_code are removed. - - >>> len(parse_function_calls("foo", "foo();bar();foo();bar();")) - 2 - >>> parse_function_calls("foo", "foo(1);bar(1);foo(2);bar(2);")[0].startswith("foo(1);") - True - >>> parse_function_calls("foo", "foo(1);bar(1);foo(2);bar(2);")[1].startswith("foo(2);") - True - >>> len(parse_function_calls("foo", "foo();bar();// foo();bar();")) - 1 - >>> len(parse_function_calls("foo", "#define FOO foo();")) - 0 - """ - assert type(function_name) is str and type(source_code) is str and function_name - lines = [re.sub("// .*", " ", line).strip() - for line in source_code.split("\n") - if not line.strip().startswith("#")] - return re.findall(r"[^a-zA-Z_](?=({}\(.*).*)".format(function_name), " " + " ".join(lines)) - - -def normalize(s): - """Return a normalized version of string s with newlines, tabs and C style comments ("/* ... */") - replaced with spaces. Multiple spaces are replaced with a single space. - - >>> normalize(" /* nothing */ foo\tfoo /* bar */ foo ") - 'foo foo foo' - """ - assert type(s) is str - s = s.replace("\n", " ") - s = s.replace("\t", " ") - s = re.sub(r"/\*.*?\*/", " ", s) - s = re.sub(" {2,}", " ", s) - return s.strip() - - -ESCAPE_MAP = { - r"\n": "[escaped-newline]", - r"\t": "[escaped-tab]", - r'\"': "[escaped-quote]", -} - - -def escape(s): - """Return the escaped version of string s with "\\\"", "\\n" and "\\t" escaped as - "[escaped-backslash]", "[escaped-newline]" and "[escaped-tab]". - - >>> unescape(escape("foo")) == "foo" - True - >>> escape(r'foo \\t foo \\n foo \\\\ foo \\ foo \\"bar\\"') - 'foo [escaped-tab] foo [escaped-newline] foo \\\\\\\\ foo \\\\ foo [escaped-quote]bar[escaped-quote]' - """ - assert type(s) is str - for raw_value, escaped_value in ESCAPE_MAP.items(): - s = s.replace(raw_value, escaped_value) - return s - - -def unescape(s): - """Return the unescaped version of escaped string s. - Reverses the replacements made in function escape(s). - - >>> unescape(escape("bar")) - 'bar' - >>> unescape("foo [escaped-tab] foo [escaped-newline] foo \\\\\\\\ foo \\\\ foo [escaped-quote]bar[escaped-quote]") - 'foo \\\\t foo \\\\n foo \\\\\\\\ foo \\\\ foo \\\\"bar\\\\"' - """ - assert type(s) is str - for raw_value, escaped_value in ESCAPE_MAP.items(): - s = s.replace(escaped_value, raw_value) - return s - - -def parse_function_call_and_arguments(function_name, function_call): - """Split string function_call into an array of strings consisting of: - * the string function_call followed by "(" - * the function call argument #1 - * ... - * the function call argument #n - * a trailing ");" - - The strings returned are in escaped form. See escape(...). - - >>> parse_function_call_and_arguments("foo", 'foo("%s", "foo");') - ['foo(', '"%s",', ' "foo"', ')'] - >>> parse_function_call_and_arguments("foo", 'foo("%s", "foo");') - ['foo(', '"%s",', ' "foo"', ')'] - >>> parse_function_call_and_arguments("foo", 'foo("%s %s", "foo", "bar");') - ['foo(', '"%s %s",', ' "foo",', ' "bar"', ')'] - >>> parse_function_call_and_arguments("fooprintf", 'fooprintf("%050d", i);') - ['fooprintf(', '"%050d",', ' i', ')'] - >>> parse_function_call_and_arguments("foo", 'foo(bar(foobar(barfoo("foo"))), foobar); barfoo') - ['foo(', 'bar(foobar(barfoo("foo"))),', ' foobar', ')'] - >>> parse_function_call_and_arguments("foo", "foo()") - ['foo(', '', ')'] - >>> parse_function_call_and_arguments("foo", "foo(123)") - ['foo(', '123', ')'] - >>> parse_function_call_and_arguments("foo", 'foo("foo")') - ['foo(', '"foo"', ')'] - >>> parse_function_call_and_arguments("strprintf", 'strprintf("%s (%d)", std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>,wchar_t>().to_bytes(buf), err);') - ['strprintf(', '"%s (%d)",', ' std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>,wchar_t>().to_bytes(buf),', ' err', ')'] - >>> parse_function_call_and_arguments("strprintf", 'strprintf("%s (%d)", foo<wchar_t>().to_bytes(buf), err);') - ['strprintf(', '"%s (%d)",', ' foo<wchar_t>().to_bytes(buf),', ' err', ')'] - >>> parse_function_call_and_arguments("strprintf", 'strprintf("%s (%d)", foo().to_bytes(buf), err);') - ['strprintf(', '"%s (%d)",', ' foo().to_bytes(buf),', ' err', ')'] - >>> parse_function_call_and_arguments("strprintf", 'strprintf("%s (%d)", foo << 1, err);') - ['strprintf(', '"%s (%d)",', ' foo << 1,', ' err', ')'] - >>> parse_function_call_and_arguments("strprintf", 'strprintf("%s (%d)", foo<bar>() >> 1, err);') - ['strprintf(', '"%s (%d)",', ' foo<bar>() >> 1,', ' err', ')'] - >>> parse_function_call_and_arguments("strprintf", 'strprintf("%s (%d)", foo < 1 ? bar : foobar, err);') - ['strprintf(', '"%s (%d)",', ' foo < 1 ? bar : foobar,', ' err', ')'] - >>> parse_function_call_and_arguments("strprintf", 'strprintf("%s (%d)", foo < 1, err);') - ['strprintf(', '"%s (%d)",', ' foo < 1,', ' err', ')'] - >>> parse_function_call_and_arguments("strprintf", 'strprintf("%s (%d)", foo > 1 ? bar : foobar, err);') - ['strprintf(', '"%s (%d)",', ' foo > 1 ? bar : foobar,', ' err', ')'] - >>> parse_function_call_and_arguments("strprintf", 'strprintf("%s (%d)", foo > 1, err);') - ['strprintf(', '"%s (%d)",', ' foo > 1,', ' err', ')'] - >>> parse_function_call_and_arguments("strprintf", 'strprintf("%s (%d)", foo <= 1, err);') - ['strprintf(', '"%s (%d)",', ' foo <= 1,', ' err', ')'] - >>> parse_function_call_and_arguments("strprintf", 'strprintf("%s (%d)", foo <= bar<1, 2>(1, 2), err);') - ['strprintf(', '"%s (%d)",', ' foo <= bar<1, 2>(1, 2),', ' err', ')'] - >>> parse_function_call_and_arguments("strprintf", 'strprintf("%s (%d)", foo>foo<1,2>(1,2)?bar:foobar,err)'); - ['strprintf(', '"%s (%d)",', ' foo>foo<1,2>(1,2)?bar:foobar,', 'err', ')'] - >>> parse_function_call_and_arguments("strprintf", 'strprintf("%s (%d)", foo>foo<1,2>(1,2),err)'); - ['strprintf(', '"%s (%d)",', ' foo>foo<1,2>(1,2),', 'err', ')'] - """ - assert type(function_name) is str and type(function_call) is str and function_name - remaining = normalize(escape(function_call)) - expected_function_call = "{}(".format(function_name) - assert remaining.startswith(expected_function_call) - parts = [expected_function_call] - remaining = remaining[len(expected_function_call):] - open_parentheses = 1 - open_template_arguments = 0 - in_string = False - parts.append("") - for i, char in enumerate(remaining): - parts.append(parts.pop() + char) - if char == "\"": - in_string = not in_string - continue - if in_string: - continue - if char == "(": - open_parentheses += 1 - continue - if char == ")": - open_parentheses -= 1 - if open_parentheses > 1: - continue - if open_parentheses == 0: - parts.append(parts.pop()[:-1]) - parts.append(char) - break - prev_char = remaining[i - 1] if i - 1 >= 0 else None - next_char = remaining[i + 1] if i + 1 <= len(remaining) - 1 else None - if char == "<" and next_char not in [" ", "<", "="] and prev_char not in [" ", "<"]: - open_template_arguments += 1 - continue - if char == ">" and next_char not in [" ", ">", "="] and prev_char not in [" ", ">"] and open_template_arguments > 0: - open_template_arguments -= 1 - if open_template_arguments > 0: - continue - if char == ",": - parts.append("") - return parts - - -def parse_string_content(argument): - """Return the text within quotes in string argument. - - >>> parse_string_content('1 "foo %d bar" 2') - 'foo %d bar' - >>> parse_string_content('1 foobar 2') - '' - >>> parse_string_content('1 "bar" 2') - 'bar' - >>> parse_string_content('1 "foo" 2 "bar" 3') - 'foobar' - >>> parse_string_content('1 "foo" 2 " " "bar" 3') - 'foo bar' - >>> parse_string_content('""') - '' - >>> parse_string_content('') - '' - >>> parse_string_content('1 2 3') - '' - """ - assert type(argument) is str - string_content = "" - in_string = False - for char in normalize(escape(argument)): - if char == "\"": - in_string = not in_string - elif in_string: - string_content += char - return string_content - - -def count_format_specifiers(format_string): - """Return the number of format specifiers in string format_string. - - >>> count_format_specifiers("foo bar foo") - 0 - >>> count_format_specifiers("foo %d bar foo") - 1 - >>> count_format_specifiers("foo %d bar %i foo") - 2 - >>> count_format_specifiers("foo %d bar %i foo %% foo") - 2 - >>> count_format_specifiers("foo %d bar %i foo %% foo %d foo") - 3 - >>> count_format_specifiers("foo %d bar %i foo %% foo %*d foo") - 4 - """ - assert type(format_string) is str - format_string = format_string.replace('%%', 'X') - n = 0 - in_specifier = False - for i, char in enumerate(format_string): - if char == "%": - in_specifier = True - n += 1 - elif char in "aAcdeEfFgGinopsuxX": - in_specifier = False - elif in_specifier and char == "*": - n += 1 - return n - +RUN_LINT_FILE = 'test/lint/run-lint-format-strings.py' + +def check_doctest(): + command = [ + sys.executable, + '-m', + 'doctest', + RUN_LINT_FILE, + ] + try: + subprocess.run(command, check = True) + except subprocess.CalledProcessError: + sys.exit(1) + +def get_matching_files(function_name): + command = [ + 'git', + 'grep', + '--full-name', + '-l', + function_name, + '--', + '*.c', + '*.cpp', + '*.h', + ] + try: + return subprocess.check_output(command, stderr = subprocess.STDOUT).decode('utf-8').splitlines() + except subprocess.CalledProcessError as e: + if e.returncode > 1: # return code is 1 when match is empty + print(e.output.decode('utf-8'), end='') + sys.exit(1) + return [] def main(): - parser = argparse.ArgumentParser(description="This program checks that the number of arguments passed " - "to a variadic format string function matches the number of format " - "specifiers in the format string.") - parser.add_argument("--skip-arguments", type=int, help="number of arguments before the format string " - "argument (e.g. 1 in the case of fprintf)", default=0) - parser.add_argument("function_name", help="function name (e.g. fprintf)", default=None) - parser.add_argument("file", nargs="*", help="C++ source code file (e.g. foo.cpp)") - args = parser.parse_args() exit_code = 0 - for filename in args.file: - with open(filename, "r", encoding="utf-8") as f: - for function_call_str in parse_function_calls(args.function_name, f.read()): - parts = parse_function_call_and_arguments(args.function_name, function_call_str) - relevant_function_call_str = unescape("".join(parts))[:512] - if (f.name, relevant_function_call_str) in FALSE_POSITIVES: - continue - if len(parts) < 3 + args.skip_arguments: - exit_code = 1 - print("{}: Could not parse function call string \"{}(...)\": {}".format(f.name, args.function_name, relevant_function_call_str)) - continue - argument_count = len(parts) - 3 - args.skip_arguments - format_str = parse_string_content(parts[1 + args.skip_arguments]) - format_specifier_count = count_format_specifiers(format_str) - if format_specifier_count != argument_count: - exit_code = 1 - print("{}: Expected {} argument(s) after format string but found {} argument(s): {}".format(f.name, format_specifier_count, argument_count, relevant_function_call_str)) - continue - sys.exit(exit_code) + check_doctest() + for s in FUNCTION_NAMES_AND_NUMBER_OF_LEADING_ARGUMENTS: + function_name, skip_arguments = s.split(',') + matching_files = get_matching_files(function_name) + + matching_files_filtered = [] + for matching_file in matching_files: + 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() + + run_lint_args = [ + RUN_LINT_FILE, + '--skip-arguments', + skip_arguments, + function_name, + ] + run_lint_args.extend(matching_files_filtered) + + try: + subprocess.run(run_lint_args, check = True) + except subprocess.CalledProcessError: + exit_code = 1 + sys.exit(exit_code) -if __name__ == "__main__": +if __name__ == '__main__': main() diff --git a/test/lint/lint-format-strings.sh b/test/lint/lint-format-strings.sh deleted file mode 100755 index d98f12b1a1..0000000000 --- a/test/lint/lint-format-strings.sh +++ /dev/null @@ -1,44 +0,0 @@ -#!/usr/bin/env bash -# -# Copyright (c) 2018-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. -# -# Lint format strings: This program checks that the number of arguments passed -# to a variadic format string function matches the number of format specifiers -# in the format string. - -export LC_ALL=C - -FUNCTION_NAMES_AND_NUMBER_OF_LEADING_ARGUMENTS=( - "FatalError,0" - "fprintf,1" - "tfm::format,1" # Assuming tfm::::format(std::ostream&, ... - "LogConnectFailure,1" - "LogPrint,1" - "LogPrintf,0" - "printf,0" - "snprintf,2" - "sprintf,1" - "strprintf,0" - "vfprintf,1" - "vprintf,1" - "vsnprintf,1" - "vsprintf,1" - "WalletLogPrintf,0" -) - -EXIT_CODE=0 -if ! python3 -m doctest test/lint/lint-format-strings.py; then - EXIT_CODE=1 -fi -for S in "${FUNCTION_NAMES_AND_NUMBER_OF_LEADING_ARGUMENTS[@]}"; do - IFS="," read -r FUNCTION_NAME SKIP_ARGUMENTS <<< "${S}" - for MATCHING_FILE in $(git grep --full-name -l "${FUNCTION_NAME}" -- "*.c" "*.cpp" "*.h" | sort | grep -vE "^src/(leveldb|secp256k1|minisketch|tinyformat|univalue|test/fuzz/strprintf.cpp)"); do - MATCHING_FILES+=("${MATCHING_FILE}") - done - if ! test/lint/lint-format-strings.py --skip-arguments "${SKIP_ARGUMENTS}" "${FUNCTION_NAME}" "${MATCHING_FILES[@]}"; then - EXIT_CODE=1 - fi -done -exit ${EXIT_CODE} diff --git a/test/lint/lint-git-commit-check.py b/test/lint/lint-git-commit-check.py new file mode 100755 index 0000000000..a1d03370e8 --- /dev/null +++ b/test/lint/lint-git-commit-check.py @@ -0,0 +1,63 @@ +#!/usr/bin/env python3 +# +# Copyright (c) 2020-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. +# +# Linter to check that commit messages have a new line before the body +# or no body at all + +import argparse +import os +import sys + +from subprocess import check_output + + +def parse_args(): + """Parse command line arguments.""" + parser = argparse.ArgumentParser( + description=""" + Linter to check that commit messages have a new line before + the body or no body at all. + """, + epilog=f""" + You can manually set the commit-range with the COMMIT_RANGE + environment variable (e.g. "COMMIT_RANGE='47ba2c3...ee50c9e' + {sys.argv[0]}"). Defaults to current merge base when neither + prev-commits nor the environment variable is set. + """) + + parser.add_argument("--prev-commits", "-p", required=False, help="The previous n commits to check") + + return parser.parse_args() + + +def main(): + args = parse_args() + exit_code = 0 + + if not os.getenv("COMMIT_RANGE"): + if args.prev_commits: + commit_range = "HEAD~" + args.prev_commits + "...HEAD" + else: + # This assumes that the target branch of the pull request will be master. + merge_base = check_output(["git", "merge-base", "HEAD", "master"], universal_newlines=True, encoding="utf8").rstrip("\n") + commit_range = merge_base + "..HEAD" + else: + commit_range = os.getenv("COMMIT_RANGE") + + commit_hashes = check_output(["git", "log", commit_range, "--format=%H"], universal_newlines=True, encoding="utf8").splitlines() + + for hash in commit_hashes: + commit_info = check_output(["git", "log", "--format=%B", "-n", "1", hash], universal_newlines=True, encoding="utf8").splitlines() + if len(commit_info) >= 2: + if commit_info[1]: + print(f"The subject line of commit hash {hash} is followed by a non-empty line. Subject lines should always be followed by a blank line.") + exit_code = 1 + + sys.exit(exit_code) + + +if __name__ == "__main__": + main() diff --git a/test/lint/lint-git-commit-check.sh b/test/lint/lint-git-commit-check.sh deleted file mode 100755 index f77373ed00..0000000000 --- a/test/lint/lint-git-commit-check.sh +++ /dev/null @@ -1,48 +0,0 @@ -#!/usr/bin/env bash -# 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. -# -# Linter to check that commit messages have a new line before the body -# or no body at all - -export LC_ALL=C - -EXIT_CODE=0 - -while getopts "?" opt; do - case $opt in - ?) - echo "Usage: $0 [N]" - echo " COMMIT_RANGE='<commit range>' $0" - echo " $0 -?" - echo "Checks unmerged commits, the previous N commits, or a commit range." - echo "COMMIT_RANGE='47ba2c3...ee50c9e' $0" - exit ${EXIT_CODE} - ;; - esac -done - -if [ -z "${COMMIT_RANGE}" ]; then - if [ -n "$1" ]; then - COMMIT_RANGE="HEAD~$1...HEAD" - else - # This assumes that the target branch of the pull request will be master. - MERGE_BASE=$(git merge-base HEAD master) - COMMIT_RANGE="$MERGE_BASE..HEAD" - fi -fi - -while IFS= read -r commit_hash || [[ -n "$commit_hash" ]]; do - n_line=0 - while IFS= read -r line || [[ -n "$line" ]]; do - n_line=$((n_line+1)) - length=${#line} - if [ $n_line -eq 2 ] && [ "$length" -ne 0 ]; then - echo "The subject line of commit hash ${commit_hash} is followed by a non-empty line. Subject lines should always be followed by a blank line." - EXIT_CODE=1 - fi - done < <(git log --format=%B -n 1 "$commit_hash") -done < <(git log "${COMMIT_RANGE}" --format=%H) - -exit ${EXIT_CODE} diff --git a/test/lint/lint-include-guards.py b/test/lint/lint-include-guards.py new file mode 100755 index 0000000000..5867aae028 --- /dev/null +++ b/test/lint/lint-include-guards.py @@ -0,0 +1,99 @@ +#!/usr/bin/env python3 +# +# Copyright (c) 2018-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. + +""" +Check include guards. +""" + +import re +import sys +from subprocess import check_output +from typing import List + + +HEADER_ID_PREFIX = 'BITCOIN_' +HEADER_ID_SUFFIX = '_H' + +EXCLUDE_FILES_WITH_PREFIX = ['src/crypto/ctaes', + 'src/leveldb', + 'src/crc32c', + 'src/secp256k1', + 'src/minisketch', + 'src/tinyformat.h', + 'src/bench/nanobench.h', + 'src/test/fuzz/FuzzedDataProvider.h'] + + +def _get_header_file_lst() -> List[str]: + """ Helper function to get a list of header filepaths to be + checked for include guards. + """ + git_cmd_lst = ['git', 'ls-files', '--', '*.h'] + header_file_lst = check_output( + git_cmd_lst).decode('utf-8').splitlines() + + header_file_lst = [hf for hf in header_file_lst + if not any(ef in hf for ef + in EXCLUDE_FILES_WITH_PREFIX)] + + return header_file_lst + + +def _get_header_id(header_file: str) -> str: + """ Helper function to get the header id from a header file + string. + + eg: 'src/wallet/walletdb.h' -> 'BITCOIN_WALLET_WALLETDB_H' + + Args: + header_file: Filepath to header file. + + Returns: + The header id. + """ + header_id_base = header_file.split('/')[1:] + header_id_base = '_'.join(header_id_base) + header_id_base = header_id_base.replace('.h', '').replace('-', '_') + header_id_base = header_id_base.upper() + + header_id = f'{HEADER_ID_PREFIX}{header_id_base}{HEADER_ID_SUFFIX}' + + return header_id + + +def main(): + exit_code = 0 + + header_file_lst = _get_header_file_lst() + for header_file in header_file_lst: + header_id = _get_header_id(header_file) + + regex_pattern = f'^#(ifndef|define|endif //) {header_id}' + + with open(header_file, 'r', encoding='utf-8') as f: + header_file_contents = f.readlines() + + count = 0 + for header_file_contents_string in header_file_contents: + include_guard_lst = re.findall( + regex_pattern, header_file_contents_string) + + count += len(include_guard_lst) + + if count != 3: + print(f'{header_file} seems to be missing the expected ' + 'include guard:') + print(f' #ifndef {header_id}') + print(f' #define {header_id}') + print(' ...') + print(f' #endif // {header_id}\n') + exit_code = 1 + + sys.exit(exit_code) + + +if __name__ == '__main__': + main() diff --git a/test/lint/lint-include-guards.sh b/test/lint/lint-include-guards.sh deleted file mode 100755 index f14218aa74..0000000000 --- a/test/lint/lint-include-guards.sh +++ /dev/null @@ -1,30 +0,0 @@ -#!/usr/bin/env bash -# -# Copyright (c) 2018-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. -# -# Check include guards. - -export LC_ALL=C -HEADER_ID_PREFIX="BITCOIN_" -HEADER_ID_SUFFIX="_H" - -REGEXP_EXCLUDE_FILES_WITH_PREFIX="src/(crypto/ctaes/|leveldb/|crc32c/|secp256k1/|minisketch/|test/fuzz/FuzzedDataProvider.h|tinyformat.h|bench/nanobench.h|univalue/)" - -EXIT_CODE=0 -for HEADER_FILE in $(git ls-files -- "*.h" | grep -vE "^${REGEXP_EXCLUDE_FILES_WITH_PREFIX}") -do - HEADER_ID_BASE=$(cut -f2- -d/ <<< "${HEADER_FILE}" | sed "s/\.h$//g" | tr / _ | tr - _ | tr "[:lower:]" "[:upper:]") - HEADER_ID="${HEADER_ID_PREFIX}${HEADER_ID_BASE}${HEADER_ID_SUFFIX}" - if [[ $(grep --count --extended-regexp "^#(ifndef|define|endif //) ${HEADER_ID}" "${HEADER_FILE}") != 3 ]]; then - echo "${HEADER_FILE} seems to be missing the expected include guard:" - echo " #ifndef ${HEADER_ID}" - echo " #define ${HEADER_ID}" - echo " ..." - echo " #endif // ${HEADER_ID}" - echo - EXIT_CODE=1 - fi -done -exit ${EXIT_CODE} diff --git a/test/lint/lint-includes.py b/test/lint/lint-includes.py new file mode 100755 index 0000000000..b3fa4b9303 --- /dev/null +++ b/test/lint/lint-includes.py @@ -0,0 +1,175 @@ +#!/usr/bin/env python3 +# +# Copyright (c) 2018-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. +# +# Check for duplicate includes. +# Guard against accidental introduction of new Boost dependencies. +# Check includes: Check for duplicate includes. Enforce bracket syntax includes. + +import os +import re +import sys + +from subprocess import check_output, CalledProcessError + + +EXCLUDED_DIRS = ["src/leveldb/", + "src/crc32c/", + "src/secp256k1/", + "src/minisketch/", + ] + +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", + "boost/multi_index_container.hpp", + "boost/process.hpp", + "boost/signals2/connection.hpp", + "boost/signals2/optional_last_value.hpp", + "boost/signals2/signal.hpp", + "boost/test/included/unit_test.hpp", + "boost/test/unit_test.hpp"] + + +def get_toplevel(): + return check_output(["git", "rev-parse", "--show-toplevel"], universal_newlines=True, encoding="utf8").rstrip("\n") + + +def list_files_by_suffix(suffixes): + exclude_args = [":(exclude)" + dir for dir in EXCLUDED_DIRS] + + files_list = check_output(["git", "ls-files", "src"] + exclude_args, universal_newlines=True, encoding="utf8").splitlines() + + return [file for file in files_list if file.endswith(suffixes)] + + +def find_duplicate_includes(include_list): + tempset = set() + duplicates = set() + + for inclusion in include_list: + if inclusion in tempset: + duplicates.add(inclusion) + else: + tempset.add(inclusion) + + return duplicates + + +def find_included_cpps(): + included_cpps = list() + + try: + included_cpps = check_output(["git", "grep", "-E", r"^#include [<\"][^>\"]+\.cpp[>\"]", "--", "*.cpp", "*.h"], universal_newlines=True, encoding="utf8").splitlines() + except CalledProcessError as e: + if e.returncode > 1: + raise e + + return included_cpps + + +def find_extra_boosts(): + included_boosts = list() + filtered_included_boost_set = set() + exclusion_set = set() + + try: + included_boosts = check_output(["git", "grep", "-E", r"^#include <boost/", "--", "*.cpp", "*.h"], universal_newlines=True, encoding="utf8").splitlines() + except CalledProcessError as e: + if e.returncode > 1: + raise e + + for boost in included_boosts: + filtered_included_boost_set.add(re.findall(r'(?<=\<).+?(?=\>)', boost)[0]) + + for expected_boost in EXPECTED_BOOST_INCLUDES: + for boost in filtered_included_boost_set: + if expected_boost in boost: + exclusion_set.add(boost) + + extra_boosts = set(filtered_included_boost_set.difference(exclusion_set)) + + return extra_boosts + + +def find_quote_syntax_inclusions(): + exclude_args = [":(exclude)" + dir for dir in EXCLUDED_DIRS] + quote_syntax_inclusions = list() + + try: + quote_syntax_inclusions = check_output(["git", "grep", r"^#include \"", "--", "*.cpp", "*.h"] + exclude_args, universal_newlines=True, encoding="utf8").splitlines() + except CalledProcessError as e: + if e.returncode > 1: + raise e + + return quote_syntax_inclusions + + +def main(): + exit_code = 0 + + os.chdir(get_toplevel()) + + # Check for duplicate includes + for filename in list_files_by_suffix((".cpp", ".h")): + with open(filename, "r", encoding="utf8") as file: + include_list = [line.rstrip("\n") for line in file if re.match(r"^#include", line)] + + duplicates = find_duplicate_includes(include_list) + + if duplicates: + print(f"Duplicate include(s) in {filename}:") + for duplicate in duplicates: + print(duplicate) + print("") + exit_code = 1 + + # Check if code includes .cpp-files + included_cpps = find_included_cpps() + + if included_cpps: + print("The following files #include .cpp files:") + for included_cpp in included_cpps: + print(included_cpp) + print("") + exit_code = 1 + + # Guard against accidental introduction of new Boost dependencies + extra_boosts = find_extra_boosts() + + if extra_boosts: + for boost in extra_boosts: + print(f"A new Boost dependency in the form of \"{boost}\" appears to have been introduced:") + print(check_output(["git", "grep", boost, "--", "*.cpp", "*.h"], universal_newlines=True, encoding="utf8")) + exit_code = 1 + + # Check if Boost dependencies are no longer used + for expected_boost in EXPECTED_BOOST_INCLUDES: + try: + check_output(["git", "grep", "-q", r"^#include <%s>" % expected_boost, "--", "*.cpp", "*.h"], universal_newlines=True, encoding="utf8") + except CalledProcessError as e: + if e.returncode > 1: + raise e + else: + print(f"Good job! The Boost dependency \"{expected_boost}\" is no longer used. " + "Please remove it from EXPECTED_BOOST_INCLUDES in test/lint/lint-includes.py " + "to make sure this dependency is not accidentally reintroduced.\n") + exit_code = 1 + + # Enforce bracket syntax includes + quote_syntax_inclusions = find_quote_syntax_inclusions() + + if quote_syntax_inclusions: + print("Please use bracket syntax includes (\"#include <foo.h>\") instead of quote syntax includes:") + for quote_syntax_inclusion in quote_syntax_inclusions: + print(quote_syntax_inclusion) + exit_code = 1 + + sys.exit(exit_code) + + +if __name__ == "__main__": + main() diff --git a/test/lint/lint-includes.sh b/test/lint/lint-includes.sh deleted file mode 100755 index 9e72831ee9..0000000000 --- a/test/lint/lint-includes.sh +++ /dev/null @@ -1,103 +0,0 @@ -#!/usr/bin/env bash -# -# Copyright (c) 2018-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. -# -# Check for duplicate includes. -# Guard against accidental introduction of new Boost dependencies. -# Check includes: Check for duplicate includes. Enforce bracket syntax includes. - -export LC_ALL=C -IGNORE_REGEXP="/(leveldb|secp256k1|minisketch|univalue|crc32c)/" - -# cd to root folder of git repo for git ls-files to work properly -cd "$(dirname "$0")/../.." || exit 1 - -filter_suffix() { - git ls-files | grep -E "^src/.*\.${1}"'$' | grep -Ev "${IGNORE_REGEXP}" -} - -EXIT_CODE=0 - -for HEADER_FILE in $(filter_suffix h); do - DUPLICATE_INCLUDES_IN_HEADER_FILE=$(grep -E "^#include " < "${HEADER_FILE}" | sort | uniq -d) - if [[ ${DUPLICATE_INCLUDES_IN_HEADER_FILE} != "" ]]; then - echo "Duplicate include(s) in ${HEADER_FILE}:" - echo "${DUPLICATE_INCLUDES_IN_HEADER_FILE}" - echo - EXIT_CODE=1 - fi -done - -for CPP_FILE in $(filter_suffix cpp); do - DUPLICATE_INCLUDES_IN_CPP_FILE=$(grep -E "^#include " < "${CPP_FILE}" | sort | uniq -d) - if [[ ${DUPLICATE_INCLUDES_IN_CPP_FILE} != "" ]]; then - echo "Duplicate include(s) in ${CPP_FILE}:" - echo "${DUPLICATE_INCLUDES_IN_CPP_FILE}" - echo - EXIT_CODE=1 - fi -done - -INCLUDED_CPP_FILES=$(git grep -E "^#include [<\"][^>\"]+\.cpp[>\"]" -- "*.cpp" "*.h") -if [[ ${INCLUDED_CPP_FILES} != "" ]]; then - echo "The following files #include .cpp files:" - echo "${INCLUDED_CPP_FILES}" - echo - EXIT_CODE=1 -fi - -EXPECTED_BOOST_INCLUDES=( - boost/algorithm/string.hpp - boost/algorithm/string/classification.hpp - boost/algorithm/string/replace.hpp - boost/algorithm/string/split.hpp - 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 - boost/multi_index_container.hpp - boost/process.hpp - boost/signals2/connection.hpp - boost/signals2/optional_last_value.hpp - boost/signals2/signal.hpp - boost/test/included/unit_test.hpp - boost/test/unit_test.hpp -) - -for BOOST_INCLUDE in $(git grep '^#include <boost/' -- "*.cpp" "*.h" | cut -f2 -d: | cut -f2 -d'<' | cut -f1 -d'>' | sort -u); do - IS_EXPECTED_INCLUDE=0 - for EXPECTED_BOOST_INCLUDE in "${EXPECTED_BOOST_INCLUDES[@]}"; do - if [[ "${BOOST_INCLUDE}" == "${EXPECTED_BOOST_INCLUDE}" ]]; then - IS_EXPECTED_INCLUDE=1 - break - fi - done - if [[ ${IS_EXPECTED_INCLUDE} == 0 ]]; then - EXIT_CODE=1 - echo "A new Boost dependency in the form of \"${BOOST_INCLUDE}\" appears to have been introduced:" - git grep "${BOOST_INCLUDE}" -- "*.cpp" "*.h" - echo - fi -done - -for EXPECTED_BOOST_INCLUDE in "${EXPECTED_BOOST_INCLUDES[@]}"; do - if ! git grep -q "^#include <${EXPECTED_BOOST_INCLUDE}>" -- "*.cpp" "*.h"; then - echo "Good job! The Boost dependency \"${EXPECTED_BOOST_INCLUDE}\" is no longer used." - echo "Please remove it from EXPECTED_BOOST_INCLUDES in $0" - echo "to make sure this dependency is not accidentally reintroduced." - echo - EXIT_CODE=1 - fi -done - -QUOTE_SYNTAX_INCLUDES=$(git grep '^#include "' -- "*.cpp" "*.h" | grep -Ev "${IGNORE_REGEXP}") -if [[ ${QUOTE_SYNTAX_INCLUDES} != "" ]]; then - echo "Please use bracket syntax includes (\"#include <foo.h>\") instead of quote syntax includes:" - echo "${QUOTE_SYNTAX_INCLUDES}" - echo - EXIT_CODE=1 -fi - -exit ${EXIT_CODE} diff --git a/test/lint/lint-locale-dependence.py b/test/lint/lint-locale-dependence.py new file mode 100755 index 0000000000..4876ac2e2d --- /dev/null +++ b/test/lint/lint-locale-dependence.py @@ -0,0 +1,260 @@ +#!/usr/bin/env python3 +# Copyright (c) 2018-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. +# +# Be aware that bitcoind and bitcoin-qt differ in terms of localization: Qt +# opts in to POSIX localization by running setlocale(LC_ALL, "") on startup, +# whereas no such call is made in bitcoind. +# +# Qt runs setlocale(LC_ALL, "") on initialization. This installs the locale +# specified by the user's LC_ALL (or LC_*) environment variable as the new +# C locale. +# +# In contrast, bitcoind does not opt in to localization -- no call to +# setlocale(LC_ALL, "") is made and the environment variables LC_* are +# thus ignored. +# +# This results in situations where bitcoind is guaranteed to be running +# with the classic locale ("C") whereas the locale of bitcoin-qt will vary +# depending on the user's environment variables. +# +# An example: Assuming the environment variable LC_ALL=de_DE then the +# call std::to_string(1.23) will return "1.230000" in bitcoind but +# "1,230000" in bitcoin-qt. +# +# From the Qt documentation: +# "On Unix/Linux Qt is configured to use the system locale settings by default. +# This can cause a conflict when using POSIX functions, for instance, when +# converting between data types such as floats and strings, since the notation +# may differ between locales. To get around this problem, call the POSIX function +# setlocale(LC_NUMERIC,"C") right after initializing QApplication, QGuiApplication +# or QCoreApplication to reset the locale that is used for number formatting to +# "C"-locale." +# +# See https://doc.qt.io/qt-5/qcoreapplication.html#locale-settings and +# https://stackoverflow.com/a/34878283 for more details. +# +# TODO: Reduce KNOWN_VIOLATIONS by replacing uses of locale dependent snprintf with strprintf. + +import re +import sys + +from subprocess import check_output, CalledProcessError + + +KNOWN_VIOLATIONS = [ + "src/dbwrapper.cpp:.*vsnprintf", + "src/test/dbwrapper_tests.cpp:.*snprintf", + "src/test/fuzz/locale.cpp:.*setlocale", + "src/test/fuzz/string.cpp:.*strtol", + "src/test/fuzz/string.cpp:.*strtoul", + "src/test/util_tests.cpp:.*strtoll", + "src/wallet/bdb.cpp:.*DbEnv::strerror", # False positive + "src/util/syserror.cpp:.*strerror", # Outside this function use `SysErrorString` +] + +REGEXP_EXTERNAL_DEPENDENCIES_EXCLUSIONS = [ + "src/crypto/ctaes/", + "src/leveldb/", + "src/secp256k1/", + "src/minisketch/", + "src/tinyformat.h", +] + +LOCALE_DEPENDENT_FUNCTIONS = [ + "alphasort", # LC_COLLATE (via strcoll) + "asctime", # LC_TIME (directly) + "asprintf", # (via vasprintf) + "atof", # LC_NUMERIC (via strtod) + "atoi", # LC_NUMERIC (via strtol) + "atol", # LC_NUMERIC (via strtol) + "atoll", # (via strtoll) + "atoq", + "btowc", # LC_CTYPE (directly) + "ctime", # (via asctime or localtime) + "dprintf", # (via vdprintf) + "fgetwc", + "fgetws", + "fold_case", # boost::locale::fold_case + "fprintf", # (via vfprintf) + "fputwc", + "fputws", + "fscanf", # (via __vfscanf) + "fwprintf", # (via __vfwprintf) + "getdate", # via __getdate_r => isspace // __localtime_r + "getwc", + "getwchar", + "is_digit", # boost::algorithm::is_digit + "is_space", # boost::algorithm::is_space + "isalnum", # LC_CTYPE + "isalpha", # LC_CTYPE + "isblank", # LC_CTYPE + "iscntrl", # LC_CTYPE + "isctype", # LC_CTYPE + "isdigit", # LC_CTYPE + "isgraph", # LC_CTYPE + "islower", # LC_CTYPE + "isprint", # LC_CTYPE + "ispunct", # LC_CTYPE + "isspace", # LC_CTYPE + "isupper", # LC_CTYPE + "iswalnum", # LC_CTYPE + "iswalpha", # LC_CTYPE + "iswblank", # LC_CTYPE + "iswcntrl", # LC_CTYPE + "iswctype", # LC_CTYPE + "iswdigit", # LC_CTYPE + "iswgraph", # LC_CTYPE + "iswlower", # LC_CTYPE + "iswprint", # LC_CTYPE + "iswpunct", # LC_CTYPE + "iswspace", # LC_CTYPE + "iswupper", # LC_CTYPE + "iswxdigit", # LC_CTYPE + "isxdigit", # LC_CTYPE + "localeconv", # LC_NUMERIC + LC_MONETARY + "mblen", # LC_CTYPE + "mbrlen", + "mbrtowc", + "mbsinit", + "mbsnrtowcs", + "mbsrtowcs", + "mbstowcs", # LC_CTYPE + "mbtowc", # LC_CTYPE + "mktime", + "normalize", # boost::locale::normalize + "printf", # LC_NUMERIC + "putwc", + "putwchar", + "scanf", # LC_NUMERIC + "setlocale", + "snprintf", + "sprintf", + "sscanf", + "std::locale::global", + "std::to_string", + "stod", + "stof", + "stoi", + "stol", + "stold", + "stoll", + "stoul", + "stoull", + "strcasecmp", + "strcasestr", + "strcoll", # LC_COLLATE + "strerror", + "strfmon", + "strftime", # LC_TIME + "strncasecmp", + "strptime", + "strtod", # LC_NUMERIC + "strtof", + "strtoimax", + "strtol", # LC_NUMERIC + "strtold", + "strtoll", + "strtoq", + "strtoul", # LC_NUMERIC + "strtoull", + "strtoumax", + "strtouq", + "strxfrm", # LC_COLLATE + "swprintf", + "to_lower", # boost::locale::to_lower + "to_title", # boost::locale::to_title + "to_upper", # boost::locale::to_upper + "tolower", # LC_CTYPE + "toupper", # LC_CTYPE + "towctrans", + "towlower", # LC_CTYPE + "towupper", # LC_CTYPE + "trim", # boost::algorithm::trim + "trim_left", # boost::algorithm::trim_left + "trim_right", # boost::algorithm::trim_right + "ungetwc", + "vasprintf", + "vdprintf", + "versionsort", + "vfprintf", + "vfscanf", + "vfwprintf", + "vprintf", + "vscanf", + "vsnprintf", + "vsprintf", + "vsscanf", + "vswprintf", + "vwprintf", + "wcrtomb", + "wcscasecmp", + "wcscoll", # LC_COLLATE + "wcsftime", # LC_TIME + "wcsncasecmp", + "wcsnrtombs", + "wcsrtombs", + "wcstod", # LC_NUMERIC + "wcstof", + "wcstoimax", + "wcstol", # LC_NUMERIC + "wcstold", + "wcstoll", + "wcstombs", # LC_CTYPE + "wcstoul", # LC_NUMERIC + "wcstoull", + "wcstoumax", + "wcswidth", + "wcsxfrm", # LC_COLLATE + "wctob", + "wctomb", # LC_CTYPE + "wctrans", + "wctype", + "wcwidth", + "wprintf" +] + + +def find_locale_dependent_function_uses(): + regexp_locale_dependent_functions = "|".join(LOCALE_DEPENDENT_FUNCTIONS) + exclude_args = [":(exclude)" + excl for excl in REGEXP_EXTERNAL_DEPENDENCIES_EXCLUSIONS] + git_grep_command = ["git", "grep", "-E", "[^a-zA-Z0-9_\\`'\"<>](" + regexp_locale_dependent_functions + ")(_r|_s)?[^a-zA-Z0-9_\\`'\"<>]", "--", "*.cpp", "*.h"] + exclude_args + git_grep_output = list() + + try: + git_grep_output = check_output(git_grep_command, universal_newlines=True, encoding="utf8").splitlines() + except CalledProcessError as e: + if e.returncode > 1: + raise e + + return git_grep_output + + +def main(): + exit_code = 0 + + regexp_ignore_known_violations = "|".join(KNOWN_VIOLATIONS) + git_grep_output = find_locale_dependent_function_uses() + + for locale_dependent_function in LOCALE_DEPENDENT_FUNCTIONS: + matches = [line for line in git_grep_output + if re.search("[^a-zA-Z0-9_\\`'\"<>]" + locale_dependent_function + "(_r|_s)?[^a-zA-Z0-9_\\`'\"<>]", line) + and not re.search("\\.(c|cpp|h):\\s*(//|\\*|/\\*|\").*" + locale_dependent_function, line) + and not re.search(regexp_ignore_known_violations, line)] + if matches: + print(f"The locale dependent function {locale_dependent_function}(...) appears to be used:") + for match in matches: + print(match) + print("") + 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(f"Advice not applicable in this specific case? Add an exception by updating the ignore list in {sys.argv[0]}") + + sys.exit(exit_code) + + +if __name__ == "__main__": + main() diff --git a/test/lint/lint-locale-dependence.sh b/test/lint/lint-locale-dependence.sh deleted file mode 100755 index 7d608eed6a..0000000000 --- a/test/lint/lint-locale-dependence.sh +++ /dev/null @@ -1,241 +0,0 @@ -#!/usr/bin/env bash -# Copyright (c) 2018-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. - -export LC_ALL=C - -# Be aware that bitcoind and bitcoin-qt differ in terms of localization: Qt -# opts in to POSIX localization by running setlocale(LC_ALL, "") on startup, -# whereas no such call is made in bitcoind. -# -# Qt runs setlocale(LC_ALL, "") on initialization. This installs the locale -# specified by the user's LC_ALL (or LC_*) environment variable as the new -# C locale. -# -# In contrast, bitcoind does not opt in to localization -- no call to -# setlocale(LC_ALL, "") is made and the environment variables LC_* are -# thus ignored. -# -# This results in situations where bitcoind is guaranteed to be running -# with the classic locale ("C") whereas the locale of bitcoin-qt will vary -# depending on the user's environment variables. -# -# An example: Assuming the environment variable LC_ALL=de_DE then the -# call std::to_string(1.23) will return "1.230000" in bitcoind but -# "1,230000" in bitcoin-qt. -# -# From the Qt documentation: -# "On Unix/Linux Qt is configured to use the system locale settings by default. -# This can cause a conflict when using POSIX functions, for instance, when -# converting between data types such as floats and strings, since the notation -# may differ between locales. To get around this problem, call the POSIX function -# setlocale(LC_NUMERIC,"C") right after initializing QApplication, QGuiApplication -# or QCoreApplication to reset the locale that is used for number formatting to -# "C"-locale." -# -# See https://doc.qt.io/qt-5/qcoreapplication.html#locale-settings and -# https://stackoverflow.com/a/34878283 for more details. - -# TODO: Reduce KNOWN_VIOLATIONS by replacing uses of locale dependent snprintf with strprintf. -KNOWN_VIOLATIONS=( - "src/dbwrapper.cpp:.*vsnprintf" - "src/test/dbwrapper_tests.cpp:.*snprintf" - "src/test/fuzz/locale.cpp" - "src/test/fuzz/string.cpp" - "src/test/util_tests.cpp" -) - -REGEXP_IGNORE_EXTERNAL_DEPENDENCIES="^src/(crypto/ctaes/|leveldb/|secp256k1/|minisketch/|tinyformat.h|univalue/)" - -LOCALE_DEPENDENT_FUNCTIONS=( - alphasort # LC_COLLATE (via strcoll) - asctime # LC_TIME (directly) - asprintf # (via vasprintf) - atof # LC_NUMERIC (via strtod) - atoi # LC_NUMERIC (via strtol) - atol # LC_NUMERIC (via strtol) - atoll # (via strtoll) - atoq - btowc # LC_CTYPE (directly) - ctime # (via asctime or localtime) - dprintf # (via vdprintf) - fgetwc - fgetws - fold_case # boost::locale::fold_case - fprintf # (via vfprintf) - fputwc - fputws - fscanf # (via __vfscanf) - fwprintf # (via __vfwprintf) - getdate # via __getdate_r => isspace // __localtime_r - getwc - getwchar - is_digit # boost::algorithm::is_digit - is_space # boost::algorithm::is_space - isalnum # LC_CTYPE - isalpha # LC_CTYPE - isblank # LC_CTYPE - iscntrl # LC_CTYPE - isctype # LC_CTYPE - isdigit # LC_CTYPE - isgraph # LC_CTYPE - islower # LC_CTYPE - isprint # LC_CTYPE - ispunct # LC_CTYPE - isspace # LC_CTYPE - isupper # LC_CTYPE - iswalnum # LC_CTYPE - iswalpha # LC_CTYPE - iswblank # LC_CTYPE - iswcntrl # LC_CTYPE - iswctype # LC_CTYPE - iswdigit # LC_CTYPE - iswgraph # LC_CTYPE - iswlower # LC_CTYPE - iswprint # LC_CTYPE - iswpunct # LC_CTYPE - iswspace # LC_CTYPE - iswupper # LC_CTYPE - iswxdigit # LC_CTYPE - isxdigit # LC_CTYPE - localeconv # LC_NUMERIC + LC_MONETARY - mblen # LC_CTYPE - mbrlen - mbrtowc - mbsinit - mbsnrtowcs - mbsrtowcs - mbstowcs # LC_CTYPE - mbtowc # LC_CTYPE - mktime - normalize # boost::locale::normalize - printf # LC_NUMERIC - putwc - putwchar - scanf # LC_NUMERIC - setlocale - snprintf - sprintf - sscanf - std::locale::global - std::to_string - stod - stof - stoi - stol - stold - stoll - stoul - stoull - strcasecmp - strcasestr - strcoll # LC_COLLATE -# strerror - strfmon - strftime # LC_TIME - strncasecmp - strptime - strtod # LC_NUMERIC - strtof - strtoimax - strtol # LC_NUMERIC - strtold - strtoll - strtoq - strtoul # LC_NUMERIC - strtoull - strtoumax - strtouq - strxfrm # LC_COLLATE - swprintf - to_lower # boost::locale::to_lower - to_title # boost::locale::to_title - to_upper # boost::locale::to_upper - tolower # LC_CTYPE - toupper # LC_CTYPE - towctrans - towlower # LC_CTYPE - towupper # LC_CTYPE - trim # boost::algorithm::trim - trim_left # boost::algorithm::trim_left - trim_right # boost::algorithm::trim_right - ungetwc - vasprintf - vdprintf - versionsort - vfprintf - vfscanf - vfwprintf - vprintf - vscanf - vsnprintf - vsprintf - vsscanf - vswprintf - vwprintf - wcrtomb - wcscasecmp - wcscoll # LC_COLLATE - wcsftime # LC_TIME - wcsncasecmp - wcsnrtombs - wcsrtombs - wcstod # LC_NUMERIC - wcstof - wcstoimax - wcstol # LC_NUMERIC - wcstold - wcstoll - wcstombs # LC_CTYPE - wcstoul # LC_NUMERIC - wcstoull - wcstoumax - wcswidth - wcsxfrm # LC_COLLATE - wctob - wctomb # LC_CTYPE - wctrans - wctype - wcwidth - wprintf -) - -function join_array { - local IFS="$1" - shift - echo "$*" -} - -REGEXP_IGNORE_KNOWN_VIOLATIONS=$(join_array "|" "${KNOWN_VIOLATIONS[@]}") - -# Invoke "git grep" only once in order to minimize run-time -REGEXP_LOCALE_DEPENDENT_FUNCTIONS=$(join_array "|" "${LOCALE_DEPENDENT_FUNCTIONS[@]}") -GIT_GREP_OUTPUT=$(git grep -E "[^a-zA-Z0-9_\`'\"<>](${REGEXP_LOCALE_DEPENDENT_FUNCTIONS}(_r|_s)?)[^a-zA-Z0-9_\`'\"<>]" -- "*.cpp" "*.h") - -EXIT_CODE=0 -for LOCALE_DEPENDENT_FUNCTION in "${LOCALE_DEPENDENT_FUNCTIONS[@]}"; do - MATCHES=$(grep -E "[^a-zA-Z0-9_\`'\"<>]${LOCALE_DEPENDENT_FUNCTION}(_r|_s)?[^a-zA-Z0-9_\`'\"<>]" <<< "${GIT_GREP_OUTPUT}" | \ - grep -vE "\.(c|cpp|h):\s*(//|\*|/\*|\").*${LOCALE_DEPENDENT_FUNCTION}") - if [[ ${REGEXP_IGNORE_EXTERNAL_DEPENDENCIES} != "" ]]; then - MATCHES=$(grep -vE "${REGEXP_IGNORE_EXTERNAL_DEPENDENCIES}" <<< "${MATCHES}") - fi - if [[ ${REGEXP_IGNORE_KNOWN_VIOLATIONS} != "" ]]; then - MATCHES=$(grep -vE "${REGEXP_IGNORE_KNOWN_VIOLATIONS}" <<< "${MATCHES}") - fi - if [[ ${MATCHES} != "" ]]; then - echo "The locale dependent function ${LOCALE_DEPENDENT_FUNCTION}(...) appears to be used:" - echo "${MATCHES}" - echo - EXIT_CODE=1 - fi -done -if [[ ${EXIT_CODE} != 0 ]]; then - echo "Unnecessary locale dependence can cause bugs that are very" - echo "tricky to isolate and fix. Please avoid using locale dependent" - echo "functions if possible." - echo - echo "Advice not applicable in this specific case? Add an exception" - echo "by updating the ignore list in $0" -fi -exit ${EXIT_CODE} diff --git a/test/lint/lint-logs.py b/test/lint/lint-logs.py new file mode 100755 index 0000000000..aaf697467d --- /dev/null +++ b/test/lint/lint-logs.py @@ -0,0 +1,34 @@ +#!/usr/bin/env python3 +# +# Copyright (c) 2018-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. +# +# Check that all logs are terminated with '\n' +# +# Some logs are continued over multiple lines. They should be explicitly +# commented with /* Continued */ + +import re +import sys + +from subprocess import check_output + + +def main(): + 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(), LogPrintfCategory(), LogPrint(), LogPrintLevel(), and WalletLogPrintf() should be terminated with \"\\n\".") + print("") + + for line in unterminated_logs: + print(line) + + sys.exit(1) + + +if __name__ == "__main__": + main() diff --git a/test/lint/lint-logs.sh b/test/lint/lint-logs.sh deleted file mode 100755 index 6d5165f649..0000000000 --- a/test/lint/lint-logs.sh +++ /dev/null @@ -1,28 +0,0 @@ -#!/usr/bin/env bash -# -# Copyright (c) 2018-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. -# -# Check that all logs are terminated with '\n' -# -# Some logs are continued over multiple lines. They should be explicitly -# commented with /* Continued */ -# -# There are some instances of LogPrintf() in comments. Those can be -# ignored - -export LC_ALL=C -UNTERMINATED_LOGS=$(git grep --extended-regexp "LogPrintf?\(" -- "*.cpp" | \ - grep -v '\\n"' | \ - grep -v '\.\.\.' | \ - grep -v "/\* Continued \*/" | \ - grep -v "LogPrint()" | \ - grep -v "LogPrintf()") -if [[ ${UNTERMINATED_LOGS} != "" ]]; then - # shellcheck disable=SC2028 - echo "All calls to LogPrintf() and LogPrint() should be terminated with \\n" - echo - echo "${UNTERMINATED_LOGS}" - exit 1 -fi diff --git a/test/lint/lint-python-dead-code.py b/test/lint/lint-python-dead-code.py new file mode 100755 index 0000000000..b3f9394788 --- /dev/null +++ b/test/lint/lint-python-dead-code.py @@ -0,0 +1,41 @@ +#!/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. + +""" +Find dead Python code. +""" + +from subprocess import check_output, STDOUT, CalledProcessError + +FILES_ARGS = ['git', 'ls-files', '--', '*.py'] + + +def check_vulture_install(): + try: + check_output(["vulture", "--version"]) + except FileNotFoundError: + print("Skipping Python dead code linting since vulture is not installed. Install by running \"pip3 install vulture\"") + exit(0) + + +def main(): + check_vulture_install() + + files = check_output(FILES_ARGS).decode("utf-8").splitlines() + # --min-confidence 100 will only report code that is guaranteed to be unused within the analyzed files. + # Any value below 100 introduces the risk of false positives, which would create an unacceptable maintenance burden. + vulture_args = ['vulture', '--min-confidence=100'] + files + + try: + check_output(vulture_args, stderr=STDOUT) + except CalledProcessError as e: + print(e.output.decode("utf-8"), end="") + print("Python dead code detection found some issues") + exit(1) + + +if __name__ == "__main__": + main() diff --git a/test/lint/lint-python-dead-code.sh b/test/lint/lint-python-dead-code.sh deleted file mode 100755 index 247bfb310a..0000000000 --- a/test/lint/lint-python-dead-code.sh +++ /dev/null @@ -1,22 +0,0 @@ -#!/usr/bin/env bash -# -# 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. -# -# Find dead Python code. - -export LC_ALL=C - -if ! command -v vulture > /dev/null; then - echo "Skipping Python dead code linting since vulture is not installed. Install by running \"pip3 install vulture\"" - exit 0 -fi - -# --min-confidence 100 will only report code that is guaranteed to be unused within the analyzed files. -# Any value below 100 introduces the risk of false positives, which would create an unacceptable maintenance burden. -mapfile -t FILES < <(git ls-files -- "*.py") -if ! vulture --min-confidence 100 "${FILES[@]}"; then - echo "Python dead code detection found some issues" - exit 1 -fi diff --git a/test/lint/lint-python-mutable-default-parameters.py b/test/lint/lint-python-mutable-default-parameters.py new file mode 100755 index 0000000000..7991e3630b --- /dev/null +++ b/test/lint/lint-python-mutable-default-parameters.py @@ -0,0 +1,72 @@ +#!/usr/bin/env python3 +# +# 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. + +""" +Detect when a mutable list or dict is used as a default parameter value in a Python function. +""" + +import subprocess +import sys + + +def main(): + command = [ + "git", + "grep", + "-E", + r"^\s*def [a-zA-Z0-9_]+\(.*=\s*(\[|\{)", + "--", + "*.py", + ] + output = subprocess.run(command, stdout=subprocess.PIPE, universal_newlines=True) + if len(output.stdout) > 0: + error_msg = ( + "A mutable list or dict seems to be used as default parameter value:\n\n" + f"{output.stdout}\n" + f"{example()}" + ) + print(error_msg) + sys.exit(1) + else: + sys.exit(0) + + +def example(): + return """This is how mutable list and dict default parameter values behave: + +>>> def f(i, j=[], k={}): +... j.append(i) +... k[i] = True +... return j, k +... +>>> f(1) +([1], {1: True}) +>>> f(1) +([1, 1], {1: True}) +>>> f(2) +([1, 1, 2], {1: True, 2: True}) + +The intended behaviour was likely: + +>>> def f(i, j=None, k=None): +... if j is None: +... j = [] +... if k is None: +... k = {} +... j.append(i) +... k[i] = True +... return j, k +... +>>> f(1) +([1], {1: True}) +>>> f(1) +([1], {1: True}) +>>> f(2) +([2], {2: True})""" + + +if __name__ == "__main__": + main() diff --git a/test/lint/lint-python-mutable-default-parameters.sh b/test/lint/lint-python-mutable-default-parameters.sh deleted file mode 100755 index 1f9f035d30..0000000000 --- a/test/lint/lint-python-mutable-default-parameters.sh +++ /dev/null @@ -1,52 +0,0 @@ -#!/usr/bin/env bash -# -# 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. -# -# Detect when a mutable list or dict is used as a default parameter value in a Python function. - -export LC_ALL=C -EXIT_CODE=0 -OUTPUT=$(git grep -E '^\s*def [a-zA-Z0-9_]+\(.*=\s*(\[|\{)' -- "*.py") -if [[ ${OUTPUT} != "" ]]; then - echo "A mutable list or dict seems to be used as default parameter value:" - echo - echo "${OUTPUT}" - echo - cat << EXAMPLE -This is how mutable list and dict default parameter values behave: - ->>> def f(i, j=[], k={}): -... j.append(i) -... k[i] = True -... return j, k -... ->>> f(1) -([1], {1: True}) ->>> f(1) -([1, 1], {1: True}) ->>> f(2) -([1, 1, 2], {1: True, 2: True}) - -The intended behaviour was likely: - ->>> def f(i, j=None, k=None): -... if j is None: -... j = [] -... if k is None: -... k = {} -... j.append(i) -... k[i] = True -... return j, k -... ->>> f(1) -([1], {1: True}) ->>> f(1) -([1], {1: True}) ->>> f(2) -([2], {2: True}) -EXAMPLE - EXIT_CODE=1 -fi -exit ${EXIT_CODE} diff --git a/test/lint/lint-python-utf8-encoding.py b/test/lint/lint-python-utf8-encoding.py new file mode 100755 index 0000000000..62fdc34d50 --- /dev/null +++ b/test/lint/lint-python-utf8-encoding.py @@ -0,0 +1,73 @@ +#!/usr/bin/env python3 +# +# Copyright (c) 2018-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. +# +# Make sure we explicitly open all text files using UTF-8 (or ASCII) encoding to +# avoid potential issues on the BSDs where the locale is not always set. + +import sys +import re + +from subprocess import check_output, CalledProcessError + +EXCLUDED_DIRS = ["src/crc32c/"] + + +def get_exclude_args(): + return [":(exclude)" + dir for dir in EXCLUDED_DIRS] + + +def check_fileopens(): + fileopens = list() + + try: + fileopens = check_output(["git", "grep", r" open(", "--", "*.py"] + get_exclude_args(), universal_newlines=True, encoding="utf8").splitlines() + except CalledProcessError as e: + if e.returncode > 1: + raise e + + filtered_fileopens = [fileopen for fileopen in fileopens if not re.search(r"encoding=.(ascii|utf8|utf-8).|open\([^,]*, ['\"][^'\"]*b[^'\"]*['\"]", fileopen)] + + return filtered_fileopens + + +def check_checked_outputs(): + checked_outputs = list() + + try: + checked_outputs = check_output(["git", "grep", "check_output(", "--", "*.py"] + get_exclude_args(), universal_newlines=True, encoding="utf8").splitlines() + except CalledProcessError as e: + if e.returncode > 1: + raise e + + filtered_checked_outputs = [checked_output for checked_output in checked_outputs if re.search(r"universal_newlines=True", checked_output) and not re.search(r"encoding=.(ascii|utf8|utf-8).", checked_output)] + + return filtered_checked_outputs + + +def main(): + exit_code = 0 + + nonexplicit_utf8_fileopens = check_fileopens() + if nonexplicit_utf8_fileopens: + print("Python's open(...) seems to be used to open text files without explicitly specifying encoding='utf8':\n") + for fileopen in nonexplicit_utf8_fileopens: + print(fileopen) + exit_code = 1 + + nonexplicit_utf8_checked_outputs = check_checked_outputs() + if nonexplicit_utf8_checked_outputs: + if nonexplicit_utf8_fileopens: + print("\n") + print("Python's check_output(...) seems to be used to get program outputs without explicitly specifying encoding='utf8':\n") + for checked_output in nonexplicit_utf8_checked_outputs: + print(checked_output) + exit_code = 1 + + sys.exit(exit_code) + + +if __name__ == "__main__": + main() diff --git a/test/lint/lint-python-utf8-encoding.sh b/test/lint/lint-python-utf8-encoding.sh deleted file mode 100755 index 6e5b18fc23..0000000000 --- a/test/lint/lint-python-utf8-encoding.sh +++ /dev/null @@ -1,28 +0,0 @@ -#!/usr/bin/env bash -# -# Copyright (c) 2018-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. -# -# Make sure we explicitly open all text files using UTF-8 (or ASCII) encoding to -# avoid potential issues on the BSDs where the locale is not always set. - -export LC_ALL=C -EXIT_CODE=0 -OUTPUT=$(git grep " open(" -- "*.py" ":(exclude)src/crc32c/" | grep -vE "encoding=.(ascii|utf8|utf-8)." | grep -vE "open\([^,]*, ['\"][^'\"]*b[^'\"]*['\"]") -if [[ ${OUTPUT} != "" ]]; then - echo "Python's open(...) seems to be used to open text files without explicitly" - echo "specifying encoding=\"utf8\":" - echo - echo "${OUTPUT}" - EXIT_CODE=1 -fi -OUTPUT=$(git grep "check_output(" -- "*.py" ":(exclude)src/crc32c/"| grep "universal_newlines=True" | grep -vE "encoding=.(ascii|utf8|utf-8).") -if [[ ${OUTPUT} != "" ]]; then - echo "Python's check_output(...) seems to be used to get program outputs without explicitly" - echo "specifying encoding=\"utf8\":" - echo - echo "${OUTPUT}" - EXIT_CODE=1 -fi -exit ${EXIT_CODE} diff --git a/test/lint/lint-python.py b/test/lint/lint-python.py new file mode 100755 index 0000000000..4d16facfea --- /dev/null +++ b/test/lint/lint-python.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. + +""" +Check for specified flake8 and mypy warnings in python files. +""" + +import os +import pkg_resources +import subprocess +import sys + +DEPS = ['flake8', 'mypy', 'pyzmq'] +MYPY_CACHE_DIR = f"{os.getenv('BASE_ROOT_DIR', '')}/test/.mypy_cache" +FILES_ARGS = ['git', 'ls-files', 'test/functional/*.py', 'contrib/devtools/*.py'] + +ENABLED = ( + 'E101,' # indentation contains mixed spaces and tabs + 'E112,' # expected an indented block + 'E113,' # unexpected indentation + 'E115,' # expected an indented block (comment) + 'E116,' # unexpected indentation (comment) + 'E125,' # continuation line with same indent as next logical line + 'E129,' # visually indented line with same indent as next logical line + 'E131,' # continuation line unaligned for hanging indent + 'E133,' # closing bracket is missing indentation + 'E223,' # tab before operator + 'E224,' # tab after operator + 'E242,' # tab after ',' + 'E266,' # too many leading '#' for block comment + 'E271,' # multiple spaces after keyword + 'E272,' # multiple spaces before keyword + 'E273,' # tab after keyword + 'E274,' # tab before keyword + 'E275,' # missing whitespace after keyword + 'E304,' # blank lines found after function decorator + 'E306,' # expected 1 blank line before a nested definition + 'E401,' # multiple imports on one line + 'E402,' # module level import not at top of file + 'E502,' # the backslash is redundant between brackets + 'E701,' # multiple statements on one line (colon) + 'E702,' # multiple statements on one line (semicolon) + 'E703,' # statement ends with a semicolon + 'E711,' # comparison to None should be 'if cond is None:' + 'E714,' # test for object identity should be "is not" + 'E721,' # do not compare types, use "isinstance()" + 'E742,' # do not define classes named "l", "O", or "I" + 'E743,' # do not define functions named "l", "O", or "I" + 'E901,' # SyntaxError: invalid syntax + 'E902,' # TokenError: EOF in multi-line string + 'F401,' # module imported but unused + 'F402,' # import module from line N shadowed by loop variable + 'F403,' # 'from foo_module import *' used; unable to detect undefined names + 'F404,' # future import(s) name after other statements + 'F405,' # foo_function may be undefined, or defined from star imports: bar_module + 'F406,' # "from module import *" only allowed at module level + 'F407,' # an undefined __future__ feature name was imported + 'F601,' # dictionary key name repeated with different values + 'F602,' # dictionary key variable name repeated with different values + 'F621,' # too many expressions in an assignment with star-unpacking + 'F622,' # two or more starred expressions in an assignment (a, *b, *c = d) + 'F631,' # assertion test is a tuple, which are always True + 'F632,' # use ==/!= to compare str, bytes, and int literals + 'F701,' # a break statement outside of a while or for loop + 'F702,' # a continue statement outside of a while or for loop + 'F703,' # a continue statement in a finally block in a loop + 'F704,' # a yield or yield from statement outside of a function + 'F705,' # a return statement with arguments inside a generator + 'F706,' # a return statement outside of a function/method + 'F707,' # an except: block as not the last exception handler + 'F811,' # redefinition of unused name from line N + 'F812,' # list comprehension redefines 'foo' from line N + 'F821,' # undefined name 'Foo' + 'F822,' # undefined name name in __all__ + 'F823,' # local variable name … referenced before assignment + 'F831,' # duplicate argument name in function definition + 'F841,' # local variable 'foo' is assigned to but never used + 'W191,' # indentation contains tabs + 'W291,' # trailing whitespace + 'W292,' # no newline at end of file + 'W293,' # blank line contains whitespace + 'W601,' # .has_key() is deprecated, use "in" + 'W602,' # deprecated form of raising exception + 'W603,' # "<>" is deprecated, use "!=" + 'W604,' # backticks are deprecated, use "repr()" + 'W605,' # invalid escape sequence "x" + 'W606,' # 'async' and 'await' are reserved keywords starting with Python 3.7 +) + + +def check_dependencies(): + working_set = {pkg.key for pkg in pkg_resources.working_set} + + for dep in DEPS: + if dep not in working_set: + print(f"Skipping Python linting since {dep} is not installed.") + exit(0) + + +def main(): + check_dependencies() + + if len(sys.argv) > 1: + flake8_files = sys.argv[1:] + else: + files_args = ['git', 'ls-files', '*.py'] + flake8_files = subprocess.check_output(files_args).decode("utf-8").splitlines() + + flake8_args = ['flake8', '--ignore=B,C,E,F,I,N,W', f'--select={ENABLED}'] + flake8_files + flake8_env = os.environ.copy() + flake8_env["PYTHONWARNINGS"] = "ignore" + + try: + subprocess.check_call(flake8_args, env=flake8_env) + except subprocess.CalledProcessError: + exit(1) + + mypy_files = subprocess.check_output(FILES_ARGS).decode("utf-8").splitlines() + mypy_args = ['mypy', '--show-error-codes'] + mypy_files + + try: + subprocess.check_call(mypy_args) + except subprocess.CalledProcessError: + exit(1) + + +if __name__ == "__main__": + main() diff --git a/test/lint/lint-python.sh b/test/lint/lint-python.sh deleted file mode 100755 index 7d7857d325..0000000000 --- a/test/lint/lint-python.sh +++ /dev/null @@ -1,111 +0,0 @@ -#!/usr/bin/env bash -# -# Copyright (c) 2017-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. -# -# Check for specified flake8 warnings in python files. - -export LC_ALL=C -export MYPY_CACHE_DIR="${BASE_ROOT_DIR}/test/.mypy_cache" - -enabled=( - E101 # indentation contains mixed spaces and tabs - E112 # expected an indented block - E113 # unexpected indentation - E115 # expected an indented block (comment) - E116 # unexpected indentation (comment) - E125 # continuation line with same indent as next logical line - E129 # visually indented line with same indent as next logical line - E131 # continuation line unaligned for hanging indent - E133 # closing bracket is missing indentation - E223 # tab before operator - E224 # tab after operator - E242 # tab after ',' - E266 # too many leading '#' for block comment - E271 # multiple spaces after keyword - E272 # multiple spaces before keyword - E273 # tab after keyword - E274 # tab before keyword - E275 # missing whitespace after keyword - E304 # blank lines found after function decorator - E306 # expected 1 blank line before a nested definition - E401 # multiple imports on one line - E402 # module level import not at top of file - E502 # the backslash is redundant between brackets - E701 # multiple statements on one line (colon) - E702 # multiple statements on one line (semicolon) - E703 # statement ends with a semicolon - E711 # comparison to None should be 'if cond is None:' - E714 # test for object identity should be "is not" - E721 # do not compare types, use "isinstance()" - E742 # do not define classes named "l", "O", or "I" - E743 # do not define functions named "l", "O", or "I" - E901 # SyntaxError: invalid syntax - E902 # TokenError: EOF in multi-line string - F401 # module imported but unused - F402 # import module from line N shadowed by loop variable - F403 # 'from foo_module import *' used; unable to detect undefined names - F404 # future import(s) name after other statements - F405 # foo_function may be undefined, or defined from star imports: bar_module - F406 # "from module import *" only allowed at module level - F407 # an undefined __future__ feature name was imported - F601 # dictionary key name repeated with different values - F602 # dictionary key variable name repeated with different values - F621 # too many expressions in an assignment with star-unpacking - F622 # two or more starred expressions in an assignment (a, *b, *c = d) - F631 # assertion test is a tuple, which are always True - F632 # use ==/!= to compare str, bytes, and int literals - F701 # a break statement outside of a while or for loop - F702 # a continue statement outside of a while or for loop - F703 # a continue statement in a finally block in a loop - F704 # a yield or yield from statement outside of a function - F705 # a return statement with arguments inside a generator - F706 # a return statement outside of a function/method - F707 # an except: block as not the last exception handler - F811 # redefinition of unused name from line N - F812 # list comprehension redefines 'foo' from line N - F821 # undefined name 'Foo' - F822 # undefined name name in __all__ - F823 # local variable name … referenced before assignment - F831 # duplicate argument name in function definition - F841 # local variable 'foo' is assigned to but never used - W191 # indentation contains tabs - W291 # trailing whitespace - W292 # no newline at end of file - W293 # blank line contains whitespace - W601 # .has_key() is deprecated, use "in" - W602 # deprecated form of raising exception - W603 # "<>" is deprecated, use "!=" - W604 # backticks are deprecated, use "repr()" - W605 # invalid escape sequence "x" - W606 # 'async' and 'await' are reserved keywords starting with Python 3.7 -) - -if ! command -v flake8 > /dev/null; then - echo "Skipping Python linting since flake8 is not installed." - exit 0 -elif PYTHONWARNINGS="ignore" flake8 --version | grep -q "Python 2"; then - echo "Skipping Python linting since flake8 is running under Python 2. Install the Python 3 version of flake8." - exit 0 -fi - -EXIT_CODE=0 - -# shellcheck disable=SC2046 -if ! PYTHONWARNINGS="ignore" flake8 --ignore=B,C,E,F,I,N,W --select=$(IFS=","; echo "${enabled[*]}") $( - if [[ $# == 0 ]]; then - git ls-files "*.py" - else - echo "$@" - fi -); then - EXIT_CODE=1 -fi - -mapfile -t FILES < <(git ls-files "test/functional/*.py" "contrib/devtools/*.py") -if ! mypy --show-error-codes "${FILES[@]}"; then - EXIT_CODE=1 -fi - -exit $EXIT_CODE diff --git a/test/lint/lint-qt.sh b/test/lint/lint-qt.sh deleted file mode 100755 index 2e77682aa2..0000000000 --- a/test/lint/lint-qt.sh +++ /dev/null @@ -1,20 +0,0 @@ -#!/usr/bin/env bash -# -# Copyright (c) 2018 The Bitcoin Core developers -# Distributed under the MIT software license, see the accompanying -# file COPYING or http://www.opensource.org/licenses/mit-license.php. -# -# Check for SIGNAL/SLOT connect style, removed since Qt4 support drop. - -export LC_ALL=C - -EXIT_CODE=0 - -OUTPUT=$(git grep -E '(SIGNAL|, ?SLOT)\(' -- src/qt) -if [[ ${OUTPUT} != "" ]]; then - echo "Use Qt5 connect style in:" - echo "$OUTPUT" - EXIT_CODE=1 -fi - -exit ${EXIT_CODE} diff --git a/test/lint/lint-shell-locale.py b/test/lint/lint-shell-locale.py new file mode 100755 index 0000000000..309c426374 --- /dev/null +++ b/test/lint/lint-shell-locale.py @@ -0,0 +1,67 @@ +#!/usr/bin/env python3 +# +# Copyright (c) 2018-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. + +""" +Make sure all shell scripts are: +a.) explicitly opt out of locale dependence using + "export LC_ALL=C" or "export LC_ALL=C.UTF-8", or +b.) explicitly opt in to locale dependence using the annotation below. +""" + +import subprocess +import sys +import re + +OPT_IN_LINE = '# This script is intentionally locale dependent by not setting \"export LC_ALL=C\"' + +OPT_OUT_LINES = [ + 'export LC_ALL=C', + 'export LC_ALL=C.UTF-8', +] + +def get_shell_files_list(): + command = [ + 'git', + 'ls-files', + '--', + '*.sh', + ] + try: + return subprocess.check_output(command, stderr = subprocess.STDOUT).decode('utf-8').splitlines() + except subprocess.CalledProcessError as e: + if e.returncode > 1: # return code is 1 when match is empty + print(e.output.decode('utf-8'), end='') + sys.exit(1) + return [] + +def main(): + exit_code = 0 + shell_files = get_shell_files_list() + for file_path in shell_files: + if re.search('src/(secp256k1|minisketch)/', file_path): + continue + + with open(file_path, 'r', encoding='utf-8') as file_obj: + contents = file_obj.read() + + if OPT_IN_LINE in contents: + continue + + non_comment_pattern = re.compile(r'^\s*((?!#).+)$', re.MULTILINE) + non_comment_lines = re.findall(non_comment_pattern, contents) + if not non_comment_lines: + continue + + first_non_comment_line = non_comment_lines[0] + if first_non_comment_line not in OPT_OUT_LINES: + print(f'Missing "export LC_ALL=C" (to avoid locale dependence) as first non-comment non-empty line in {file_path}') + exit_code = 1 + + return sys.exit(exit_code) + +if __name__ == '__main__': + main() + diff --git a/test/lint/lint-shell-locale.sh b/test/lint/lint-shell-locale.sh deleted file mode 100755 index 4c6b8a57e6..0000000000 --- a/test/lint/lint-shell-locale.sh +++ /dev/null @@ -1,25 +0,0 @@ -#!/usr/bin/env bash -# -# Copyright (c) 2018-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. -# -# Make sure all shell scripts: -# a.) explicitly opt out of locale dependence using -# "export LC_ALL=C" or "export LC_ALL=C.UTF-8", or -# b.) explicitly opt in to locale dependence using the annotation below. - -export LC_ALL=C - -EXIT_CODE=0 -for SHELL_SCRIPT in $(git ls-files -- "*.sh" | grep -vE "src/(secp256k1|minisketch|univalue)/"); do - if grep -q "# This script is intentionally locale dependent by not setting \"export LC_ALL=C\"" "${SHELL_SCRIPT}"; then - continue - fi - FIRST_NON_COMMENT_LINE=$(grep -vE '^(#.*)?$' "${SHELL_SCRIPT}" | head -1) - if [[ ${FIRST_NON_COMMENT_LINE} != "export LC_ALL=C" && ${FIRST_NON_COMMENT_LINE} != "export LC_ALL=C.UTF-8" ]]; then - echo "Missing \"export LC_ALL=C\" (to avoid locale dependence) as first non-comment non-empty line in ${SHELL_SCRIPT}" - EXIT_CODE=1 - fi -done -exit ${EXIT_CODE} diff --git a/test/lint/lint-shell.py b/test/lint/lint-shell.py new file mode 100755 index 0000000000..ed95024ef5 --- /dev/null +++ b/test/lint/lint-shell.py @@ -0,0 +1,93 @@ +#!/usr/bin/env python3 +# +# Copyright (c) 2018-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. + +""" +Check for shellcheck warnings in shell scripts. +""" + +import subprocess +import re +import sys + +# Disabled warnings: +DISABLED = [ + 'SC2162', # read without -r will mangle backslashes. +] + +def check_shellcheck_install(): + try: + subprocess.run(['shellcheck', '--version'], stdout=subprocess.DEVNULL, check=True) + except FileNotFoundError: + print('Skipping shell linting since shellcheck is not installed.') + sys.exit(0) + +def get_files(command): + output = subprocess.run(command, stdout=subprocess.PIPE, universal_newlines=True) + files = output.stdout.split('\n') + + # remove whitespace element + files = list(filter(None, files)) + return files + +def main(): + check_shellcheck_install() + + # build the `exclude` flag + exclude = '--exclude=' + ','.join(DISABLED) + + # build the `sourced files` list + sourced_files_cmd = [ + 'git', + 'grep', + '-El', + r'^# shellcheck shell=', + ] + sourced_files = get_files(sourced_files_cmd) + + # build the `guix files` list + guix_files_cmd = [ + 'git', + 'grep', + '-El', + r'^#!\/usr\/bin\/env bash', + '--', + 'contrib/guix', + 'contrib/shell', + ] + guix_files = get_files(guix_files_cmd) + + # build the other script files list + files_cmd = [ + 'git', + 'ls-files', + '--', + '*.sh', + ] + files = get_files(files_cmd) + # remove everything that doesn't match this regex + reg = re.compile(r'src/[leveldb,secp256k1,minisketch]') + files[:] = [file for file in files if not reg.match(file)] + + # build the `shellcheck` command + shellcheck_cmd = [ + 'shellcheck', + '--external-sources', + '--check-sourced', + '--source-path=SCRIPTDIR', + ] + shellcheck_cmd.append(exclude) + shellcheck_cmd.extend(sourced_files) + shellcheck_cmd.extend(guix_files) + shellcheck_cmd.extend(files) + + # run the `shellcheck` command + try: + subprocess.check_call(shellcheck_cmd) + except subprocess.CalledProcessError: + sys.exit(1) + +if __name__ == '__main__': + main() diff --git a/test/lint/lint-shell.sh b/test/lint/lint-shell.sh deleted file mode 100755 index 5fa104fce6..0000000000 --- a/test/lint/lint-shell.sh +++ /dev/null @@ -1,33 +0,0 @@ -#!/usr/bin/env bash -# -# Copyright (c) 2018-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. -# -# Check for shellcheck warnings in shell scripts. - -export LC_ALL=C - -# Disabled warnings: -disabled=( - SC2162 # read without -r will mangle backslashes. -) - -EXIT_CODE=0 - -if ! command -v shellcheck > /dev/null; then - echo "Skipping shell linting since shellcheck is not installed." - exit $EXIT_CODE -fi - -SHELLCHECK_CMD=(shellcheck --external-sources --check-sourced --source-path=SCRIPTDIR) -EXCLUDE="--exclude=$(IFS=','; echo "${disabled[*]}")" -# Check shellcheck directive used for sourced files -mapfile -t SOURCED_FILES < <(git ls-files | xargs gawk '/^# shellcheck shell=/ {print FILENAME} {nextfile}') -mapfile -t GUIX_FILES < <(git ls-files contrib/guix contrib/shell | xargs gawk '/^#!\/usr\/bin\/env bash/ {print FILENAME} {nextfile}') -mapfile -t FILES < <(git ls-files -- '*.sh' | grep -vE 'src/(leveldb|secp256k1|minisketch|univalue)/') -if ! "${SHELLCHECK_CMD[@]}" "$EXCLUDE" "${SOURCED_FILES[@]}" "${GUIX_FILES[@]}" "${FILES[@]}"; then - EXIT_CODE=1 -fi - -exit $EXIT_CODE diff --git a/test/lint/lint-spelling.py b/test/lint/lint-spelling.py new file mode 100755 index 0000000000..14d7d13a75 --- /dev/null +++ b/test/lint/lint-spelling.py @@ -0,0 +1,40 @@ +#!/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. + +""" +Warn in case of spelling errors. +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)contrib/builder-keys/keys.txt", ":(exclude)contrib/guix/patches"] + + +def check_codespell_install(): + try: + check_output(["codespell", "--version"]) + except FileNotFoundError: + print("Skipping spell check linting since codespell is not installed.") + exit(0) + + +def main(): + check_codespell_install() + + files = check_output(FILES_ARGS).decode("utf-8").splitlines() + codespell_args = ['codespell', '--check-filenames', '--disable-colors', '--quiet-level=7', '--ignore-words={}'.format(IGNORE_WORDS_FILE)] + files + + try: + check_output(codespell_args, stderr=STDOUT) + except CalledProcessError as e: + print(e.output.decode("utf-8"), end="") + print('^ Warning: codespell identified likely spelling errors. Any false positives? Add them to the list of ignored words in {}'.format(IGNORE_WORDS_FILE)) + + +if __name__ == "__main__": + main() diff --git a/test/lint/lint-spelling.sh b/test/lint/lint-spelling.sh deleted file mode 100755 index b3e558b02a..0000000000 --- a/test/lint/lint-spelling.sh +++ /dev/null @@ -1,21 +0,0 @@ -#!/usr/bin/env bash -# -# Copyright (c) 2018-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. -# -# Warn in case of spelling errors. -# Note: Will exit successfully regardless of spelling errors. - -export LC_ALL=C - -if ! command -v codespell > /dev/null; then - echo "Skipping spell check linting since codespell is not installed." - exit 0 -fi - -IGNORE_WORDS_FILE=test/lint/lint-spelling.ignore-words.txt -mapfile -t FILES < <(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") -if ! codespell --check-filenames --disable-colors --quiet-level=7 --ignore-words=${IGNORE_WORDS_FILE} "${FILES[@]}"; then - echo "^ Warning: codespell identified likely spelling errors. Any false positives? Add them to the list of ignored words in ${IGNORE_WORDS_FILE}" -fi diff --git a/test/lint/lint-submodule.py b/test/lint/lint-submodule.py new file mode 100755 index 0000000000..89d4c80f55 --- /dev/null +++ b/test/lint/lint-submodule.py @@ -0,0 +1,23 @@ +#!/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. + +""" +This script checks for git modules +""" + +import subprocess +import sys + +def main(): + submodules_list = subprocess.check_output(['git', 'submodule', 'status', '--recursive'], + universal_newlines = True, encoding = 'utf8').rstrip('\n') + if submodules_list: + print("These submodules were found, delete them:\n", submodules_list) + sys.exit(1) + sys.exit(0) + +if __name__ == '__main__': + main() diff --git a/test/lint/lint-submodule.sh b/test/lint/lint-submodule.sh deleted file mode 100755 index d9aa021df7..0000000000 --- a/test/lint/lint-submodule.sh +++ /dev/null @@ -1,20 +0,0 @@ -#!/usr/bin/env bash -# -# 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. -# -# This script checks for git modules -export LC_ALL=C -EXIT_CODE=0 - -CMD=$(git submodule status --recursive) -if test -n "$CMD"; -then - echo These submodules were found, delete them: - echo "$CMD" - EXIT_CODE=1 -fi - -exit $EXIT_CODE - diff --git a/test/lint/lint-tests.py b/test/lint/lint-tests.py new file mode 100755 index 0000000000..849ddcb961 --- /dev/null +++ b/test/lint/lint-tests.py @@ -0,0 +1,87 @@ +#!/usr/bin/env python3 +# +# Copyright (c) 2018-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. + +""" +Check the test suite naming conventions +""" + +import re +import subprocess +import sys + + +def grep_boost_fixture_test_suite(): + command = [ + "git", + "grep", + "-E", + r"^BOOST_FIXTURE_TEST_SUITE\(", + "--", + "src/test/**.cpp", + "src/wallet/test/**.cpp", + ] + return subprocess.check_output(command, universal_newlines=True, encoding="utf8") + + +def check_matching_test_names(test_suite_list): + not_matching = [ + x + for x in test_suite_list + if re.search(r"/(.*?)\.cpp:BOOST_FIXTURE_TEST_SUITE\(\1, .*\)", x) is None + ] + if len(not_matching) > 0: + not_matching = "\n".join(not_matching) + error_msg = ( + "The test suite in file src/test/foo_tests.cpp should be named\n" + '"foo_tests". Please make sure the following test suites follow\n' + "that convention:\n\n" + f"{not_matching}\n" + ) + print(error_msg) + return 1 + return 0 + + +def get_duplicates(input_list): + """ + From https://stackoverflow.com/a/9835819 + """ + seen = set() + dupes = set() + for x in input_list: + if x in seen: + dupes.add(x) + else: + seen.add(x) + return dupes + + +def check_unique_test_names(test_suite_list): + output = [re.search(r"\((.*?),", x) for x in test_suite_list] + output = [x.group(1) for x in output if x is not None] + output = get_duplicates(output) + output = sorted(list(output)) + + if len(output) > 0: + output = "\n".join(output) + error_msg = ( + "Test suite names must be unique. The following test suite names\n" + f"appear to be used more than once:\n\n{output}" + ) + print(error_msg) + return 1 + return 0 + + +def main(): + test_suite_list = grep_boost_fixture_test_suite().splitlines() + exit_code = check_matching_test_names(test_suite_list) + exit_code |= check_unique_test_names(test_suite_list) + sys.exit(exit_code) + + +if __name__ == "__main__": + main() diff --git a/test/lint/lint-tests.sh b/test/lint/lint-tests.sh deleted file mode 100755 index 35d11023eb..0000000000 --- a/test/lint/lint-tests.sh +++ /dev/null @@ -1,35 +0,0 @@ -#!/usr/bin/env bash -# -# Copyright (c) 2018 The Bitcoin Core developers -# Distributed under the MIT software license, see the accompanying -# file COPYING or http://www.opensource.org/licenses/mit-license.php. -# -# Check the test suite naming conventions - -export LC_ALL=C -EXIT_CODE=0 - -NAMING_INCONSISTENCIES=$(git grep -E '^BOOST_FIXTURE_TEST_SUITE\(' -- \ - "src/test/**.cpp" "src/wallet/test/**.cpp" | \ - grep -vE '/(.*?)\.cpp:BOOST_FIXTURE_TEST_SUITE\(\1, .*\)$') -if [[ ${NAMING_INCONSISTENCIES} != "" ]]; then - echo "The test suite in file src/test/foo_tests.cpp should be named" - echo "\"foo_tests\". Please make sure the following test suites follow" - echo "that convention:" - echo - echo "${NAMING_INCONSISTENCIES}" - EXIT_CODE=1 -fi - -TEST_SUITE_NAME_COLLISSIONS=$(git grep -E '^BOOST_FIXTURE_TEST_SUITE\(' -- \ - "src/test/**.cpp" "src/wallet/test/**.cpp" | cut -f2 -d'(' | cut -f1 -d, | \ - sort | uniq -d) -if [[ ${TEST_SUITE_NAME_COLLISSIONS} != "" ]]; then - echo "Test suite names must be unique. The following test suite names" - echo "appear to be used more than once:" - echo - echo "${TEST_SUITE_NAME_COLLISSIONS}" - EXIT_CODE=1 -fi - -exit ${EXIT_CODE} diff --git a/test/lint/lint-whitespace.py b/test/lint/lint-whitespace.py new file mode 100755 index 0000000000..3fb5b80013 --- /dev/null +++ b/test/lint/lint-whitespace.py @@ -0,0 +1,134 @@ +#!/usr/bin/env python3 +# +# Copyright (c) 2017-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. +# +# Check for new lines in diff that introduce trailing whitespace or +# tab characters instead of spaces. + +# We can't run this check unless we know the commit range for the PR. + +import argparse +import os +import re +import sys + +from subprocess import check_output + +EXCLUDED_DIRS = ["depends/patches/", + "contrib/guix/patches/", + "src/leveldb/", + "src/crc32c/", + "src/secp256k1/", + "src/minisketch/", + "doc/release-notes/", + "src/qt/locale"] + +def parse_args(): + """Parse command line arguments.""" + parser = argparse.ArgumentParser( + description=""" + Check for new lines in diff that introduce trailing whitespace + or tab characters instead of spaces in unstaged changes, the + previous n commits, or a commit-range. + """, + epilog=f""" + You can manually set the commit-range with the COMMIT_RANGE + environment variable (e.g. "COMMIT_RANGE='47ba2c3...ee50c9e' + {sys.argv[0]}"). Defaults to current merge base when neither + prev-commits nor the environment variable is set. + """) + + parser.add_argument("--prev-commits", "-p", required=False, help="The previous n commits to check") + + return parser.parse_args() + + +def report_diff(selection): + filename = "" + seen = False + seenln = False + + print("The following changes were suspected:") + + for line in selection: + if re.match(r"^diff", line): + filename = line + seen = False + elif re.match(r"^@@", line): + linenumber = line + seenln = False + else: + if not seen: + # The first time a file is seen with trailing whitespace or a tab character, we print the + # filename (preceded by a newline). + print("") + print(filename) + seen = True + if not seenln: + print(linenumber) + seenln = True + print(line) + + +def get_diff(commit_range, check_only_code): + exclude_args = [":(exclude)" + dir for dir in EXCLUDED_DIRS] + + if check_only_code: + what_files = ["*.cpp", "*.h", "*.md", "*.py", "*.sh"] + else: + what_files = ["."] + + diff = check_output(["git", "diff", "-U0", commit_range, "--"] + what_files + exclude_args, universal_newlines=True, encoding="utf8") + + return diff + + +def main(): + args = parse_args() + + if not os.getenv("COMMIT_RANGE"): + if args.prev_commits: + commit_range = "HEAD~" + args.prev_commits + "...HEAD" + else: + # This assumes that the target branch of the pull request will be master. + merge_base = check_output(["git", "merge-base", "HEAD", "master"], universal_newlines=True, encoding="utf8").rstrip("\n") + commit_range = merge_base + "..HEAD" + else: + commit_range = os.getenv("COMMIT_RANGE") + + whitespace_selection = [] + tab_selection = [] + + # Check if trailing whitespace was found in the diff. + for line in get_diff(commit_range, check_only_code=False).splitlines(): + if re.match(r"^(diff --git|\@@|^\+.*\s+$)", line): + whitespace_selection.append(line) + + whitespace_additions = [i for i in whitespace_selection if i.startswith("+")] + + # Check if tab characters were found in the diff. + for line in get_diff(commit_range, check_only_code=True).splitlines(): + if re.match(r"^(diff --git|\@@|^\+.*\t)", line): + tab_selection.append(line) + + tab_additions = [i for i in tab_selection if i.startswith("+")] + + ret = 0 + + if len(whitespace_additions) > 0: + print("This diff appears to have added new lines with trailing whitespace.") + report_diff(whitespace_selection) + ret = 1 + + if len(tab_additions) > 0: + print("This diff appears to have added new lines with tab characters instead of spaces.") + report_diff(tab_selection) + ret = 1 + + sys.exit(ret) + + +if __name__ == "__main__": + main() diff --git a/test/lint/lint-whitespace.sh b/test/lint/lint-whitespace.sh deleted file mode 100755 index 9d55c71eb5..0000000000 --- a/test/lint/lint-whitespace.sh +++ /dev/null @@ -1,115 +0,0 @@ -#!/usr/bin/env bash -# -# Copyright (c) 2017-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. -# -# Check for new lines in diff that introduce trailing whitespace. - -# We can't run this check unless we know the commit range for the PR. - -export LC_ALL=C -while getopts "?" opt; do - case $opt in - ?) - echo "Usage: $0 [N]" - echo " COMMIT_RANGE='<commit range>' $0" - echo " $0 -?" - echo "Checks unstaged changes, the previous N commits, or a commit range." - echo "COMMIT_RANGE='47ba2c3...ee50c9e' $0" - exit 0 - ;; - esac -done - -if [ -z "${COMMIT_RANGE}" ]; then - if [ -n "$1" ]; then - COMMIT_RANGE="HEAD~$1...HEAD" - else - # This assumes that the target branch of the pull request will be master. - MERGE_BASE=$(git merge-base HEAD master) - COMMIT_RANGE="$MERGE_BASE..HEAD" - fi -fi - -showdiff() { - if ! git diff -U0 "${COMMIT_RANGE}" -- "." ":(exclude)depends/patches/" ":(exclude)contrib/guix/patches/" ":(exclude)src/leveldb/" ":(exclude)src/crc32c/" ":(exclude)src/secp256k1/" ":(exclude)src/minisketch/" ":(exclude)src/univalue/" ":(exclude)doc/release-notes/" ":(exclude)src/qt/locale/"; then - echo "Failed to get a diff" - exit 1 - fi -} - -showcodediff() { - if ! git diff -U0 "${COMMIT_RANGE}" -- *.cpp *.h *.md *.py *.sh ":(exclude)src/leveldb/" ":(exclude)src/crc32c/" ":(exclude)src/secp256k1/" ":(exclude)src/minisketch/" ":(exclude)src/univalue/" ":(exclude)doc/release-notes/" ":(exclude)src/qt/locale/"; then - echo "Failed to get a diff" - exit 1 - fi -} - -RET=0 - -# Check if trailing whitespace was found in the diff. -if showdiff | grep -E -q '^\+.*\s+$'; then - echo "This diff appears to have added new lines with trailing whitespace." - echo "The following changes were suspected:" - FILENAME="" - SEEN=0 - SEENLN=0 - while read -r line; do - if [[ "$line" =~ ^diff ]]; then - FILENAME="$line" - SEEN=0 - elif [[ "$line" =~ ^@@ ]]; then - LINENUMBER="$line" - SEENLN=0 - else - if [ "$SEEN" -eq 0 ]; then - # The first time a file is seen with trailing whitespace, we print the - # filename (preceded by a newline). - echo - echo "$FILENAME" - SEEN=1 - fi - if [ "$SEENLN" -eq 0 ]; then - echo "$LINENUMBER" - SEENLN=1 - fi - echo "$line" - fi - done < <(showdiff | grep -E '^(diff --git |@@|\+.*\s+$)') - RET=1 -fi - -# Check if tab characters were found in the diff. -if showcodediff | perl -nle '$MATCH++ if m{^\+.*\t}; END{exit 1 unless $MATCH>0}' > /dev/null; then - echo "This diff appears to have added new lines with tab characters instead of spaces." - echo "The following changes were suspected:" - FILENAME="" - SEEN=0 - SEENLN=0 - while read -r line; do - if [[ "$line" =~ ^diff ]]; then - FILENAME="$line" - SEEN=0 - elif [[ "$line" =~ ^@@ ]]; then - LINENUMBER="$line" - SEENLN=0 - else - if [ "$SEEN" -eq 0 ]; then - # The first time a file is seen with a tab character, we print the - # filename (preceded by a newline). - echo - echo "$FILENAME" - SEEN=1 - fi - if [ "$SEENLN" -eq 0 ]; then - echo "$LINENUMBER" - SEENLN=1 - fi - echo "$line" - fi - done < <(showcodediff | perl -nle 'print if m{^(diff --git |@@|\+.*\t)}') - RET=1 -fi - -exit $RET diff --git a/test/lint/run-lint-format-strings.py b/test/lint/run-lint-format-strings.py new file mode 100755 index 0000000000..b814446125 --- /dev/null +++ b/test/lint/run-lint-format-strings.py @@ -0,0 +1,291 @@ +#!/usr/bin/env python3 +# +# Copyright (c) 2018-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. +# +# Lint format strings: This program checks that the number of arguments passed +# to a variadic format string function matches the number of format specifiers +# in the format string. + +import argparse +import re +import sys + +FALSE_POSITIVES = [ + ("src/dbwrapper.cpp", "vsnprintf(p, limit - p, format, backup_ap)"), + ("src/index/base.cpp", "FatalError(const char* fmt, const Args&... args)"), + ("src/netbase.cpp", "LogConnectFailure(bool manual_connection, const char* fmt, const Args&... args)"), + ("src/clientversion.cpp", "strprintf(_(COPYRIGHT_HOLDERS).translated, COPYRIGHT_HOLDERS_SUBSTITUTION)"), + ("src/validationinterface.cpp", "LogPrint(BCLog::VALIDATION, fmt \"\\n\", __VA_ARGS__)"), + ("src/wallet/wallet.h", "WalletLogPrintf(std::string fmt, Params... parameters)"), + ("src/wallet/wallet.h", "LogPrintf((\"%s \" + fmt).c_str(), GetDisplayName(), parameters...)"), + ("src/wallet/scriptpubkeyman.h", "WalletLogPrintf(std::string fmt, Params... parameters)"), + ("src/wallet/scriptpubkeyman.h", "LogPrintf((\"%s \" + fmt).c_str(), m_storage.GetDisplayName(), parameters...)"), +] + + +def parse_function_calls(function_name, source_code): + """Return an array with all calls to function function_name in string source_code. + Preprocessor directives and C++ style comments ("//") in source_code are removed. + + >>> len(parse_function_calls("foo", "foo();bar();foo();bar();")) + 2 + >>> parse_function_calls("foo", "foo(1);bar(1);foo(2);bar(2);")[0].startswith("foo(1);") + True + >>> parse_function_calls("foo", "foo(1);bar(1);foo(2);bar(2);")[1].startswith("foo(2);") + True + >>> len(parse_function_calls("foo", "foo();bar();// foo();bar();")) + 1 + >>> len(parse_function_calls("foo", "#define FOO foo();")) + 0 + """ + assert type(function_name) is str and type(source_code) is str and function_name + lines = [re.sub("// .*", " ", line).strip() + for line in source_code.split("\n") + if not line.strip().startswith("#")] + return re.findall(r"[^a-zA-Z_](?=({}\(.*).*)".format(function_name), " " + " ".join(lines)) + + +def normalize(s): + """Return a normalized version of string s with newlines, tabs and C style comments ("/* ... */") + replaced with spaces. Multiple spaces are replaced with a single space. + + >>> normalize(" /* nothing */ foo\tfoo /* bar */ foo ") + 'foo foo foo' + """ + assert type(s) is str + s = s.replace("\n", " ") + s = s.replace("\t", " ") + s = re.sub(r"/\*.*?\*/", " ", s) + s = re.sub(" {2,}", " ", s) + return s.strip() + + +ESCAPE_MAP = { + r"\n": "[escaped-newline]", + r"\t": "[escaped-tab]", + r'\"': "[escaped-quote]", +} + + +def escape(s): + """Return the escaped version of string s with "\\\"", "\\n" and "\\t" escaped as + "[escaped-backslash]", "[escaped-newline]" and "[escaped-tab]". + + >>> unescape(escape("foo")) == "foo" + True + >>> escape(r'foo \\t foo \\n foo \\\\ foo \\ foo \\"bar\\"') + 'foo [escaped-tab] foo [escaped-newline] foo \\\\\\\\ foo \\\\ foo [escaped-quote]bar[escaped-quote]' + """ + assert type(s) is str + for raw_value, escaped_value in ESCAPE_MAP.items(): + s = s.replace(raw_value, escaped_value) + return s + + +def unescape(s): + """Return the unescaped version of escaped string s. + Reverses the replacements made in function escape(s). + + >>> unescape(escape("bar")) + 'bar' + >>> unescape("foo [escaped-tab] foo [escaped-newline] foo \\\\\\\\ foo \\\\ foo [escaped-quote]bar[escaped-quote]") + 'foo \\\\t foo \\\\n foo \\\\\\\\ foo \\\\ foo \\\\"bar\\\\"' + """ + assert type(s) is str + for raw_value, escaped_value in ESCAPE_MAP.items(): + s = s.replace(escaped_value, raw_value) + return s + + +def parse_function_call_and_arguments(function_name, function_call): + """Split string function_call into an array of strings consisting of: + * the string function_call followed by "(" + * the function call argument #1 + * ... + * the function call argument #n + * a trailing ");" + + The strings returned are in escaped form. See escape(...). + + >>> parse_function_call_and_arguments("foo", 'foo("%s", "foo");') + ['foo(', '"%s",', ' "foo"', ')'] + >>> parse_function_call_and_arguments("foo", 'foo("%s", "foo");') + ['foo(', '"%s",', ' "foo"', ')'] + >>> parse_function_call_and_arguments("foo", 'foo("%s %s", "foo", "bar");') + ['foo(', '"%s %s",', ' "foo",', ' "bar"', ')'] + >>> parse_function_call_and_arguments("fooprintf", 'fooprintf("%050d", i);') + ['fooprintf(', '"%050d",', ' i', ')'] + >>> parse_function_call_and_arguments("foo", 'foo(bar(foobar(barfoo("foo"))), foobar); barfoo') + ['foo(', 'bar(foobar(barfoo("foo"))),', ' foobar', ')'] + >>> parse_function_call_and_arguments("foo", "foo()") + ['foo(', '', ')'] + >>> parse_function_call_and_arguments("foo", "foo(123)") + ['foo(', '123', ')'] + >>> parse_function_call_and_arguments("foo", 'foo("foo")') + ['foo(', '"foo"', ')'] + >>> parse_function_call_and_arguments("strprintf", 'strprintf("%s (%d)", std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>,wchar_t>().to_bytes(buf), err);') + ['strprintf(', '"%s (%d)",', ' std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>,wchar_t>().to_bytes(buf),', ' err', ')'] + >>> parse_function_call_and_arguments("strprintf", 'strprintf("%s (%d)", foo<wchar_t>().to_bytes(buf), err);') + ['strprintf(', '"%s (%d)",', ' foo<wchar_t>().to_bytes(buf),', ' err', ')'] + >>> parse_function_call_and_arguments("strprintf", 'strprintf("%s (%d)", foo().to_bytes(buf), err);') + ['strprintf(', '"%s (%d)",', ' foo().to_bytes(buf),', ' err', ')'] + >>> parse_function_call_and_arguments("strprintf", 'strprintf("%s (%d)", foo << 1, err);') + ['strprintf(', '"%s (%d)",', ' foo << 1,', ' err', ')'] + >>> parse_function_call_and_arguments("strprintf", 'strprintf("%s (%d)", foo<bar>() >> 1, err);') + ['strprintf(', '"%s (%d)",', ' foo<bar>() >> 1,', ' err', ')'] + >>> parse_function_call_and_arguments("strprintf", 'strprintf("%s (%d)", foo < 1 ? bar : foobar, err);') + ['strprintf(', '"%s (%d)",', ' foo < 1 ? bar : foobar,', ' err', ')'] + >>> parse_function_call_and_arguments("strprintf", 'strprintf("%s (%d)", foo < 1, err);') + ['strprintf(', '"%s (%d)",', ' foo < 1,', ' err', ')'] + >>> parse_function_call_and_arguments("strprintf", 'strprintf("%s (%d)", foo > 1 ? bar : foobar, err);') + ['strprintf(', '"%s (%d)",', ' foo > 1 ? bar : foobar,', ' err', ')'] + >>> parse_function_call_and_arguments("strprintf", 'strprintf("%s (%d)", foo > 1, err);') + ['strprintf(', '"%s (%d)",', ' foo > 1,', ' err', ')'] + >>> parse_function_call_and_arguments("strprintf", 'strprintf("%s (%d)", foo <= 1, err);') + ['strprintf(', '"%s (%d)",', ' foo <= 1,', ' err', ')'] + >>> parse_function_call_and_arguments("strprintf", 'strprintf("%s (%d)", foo <= bar<1, 2>(1, 2), err);') + ['strprintf(', '"%s (%d)",', ' foo <= bar<1, 2>(1, 2),', ' err', ')'] + >>> parse_function_call_and_arguments("strprintf", 'strprintf("%s (%d)", foo>foo<1,2>(1,2)?bar:foobar,err)'); + ['strprintf(', '"%s (%d)",', ' foo>foo<1,2>(1,2)?bar:foobar,', 'err', ')'] + >>> parse_function_call_and_arguments("strprintf", 'strprintf("%s (%d)", foo>foo<1,2>(1,2),err)'); + ['strprintf(', '"%s (%d)",', ' foo>foo<1,2>(1,2),', 'err', ')'] + """ + assert type(function_name) is str and type(function_call) is str and function_name + remaining = normalize(escape(function_call)) + expected_function_call = "{}(".format(function_name) + assert remaining.startswith(expected_function_call) + parts = [expected_function_call] + remaining = remaining[len(expected_function_call):] + open_parentheses = 1 + open_template_arguments = 0 + in_string = False + parts.append("") + for i, char in enumerate(remaining): + parts.append(parts.pop() + char) + if char == "\"": + in_string = not in_string + continue + if in_string: + continue + if char == "(": + open_parentheses += 1 + continue + if char == ")": + open_parentheses -= 1 + if open_parentheses > 1: + continue + if open_parentheses == 0: + parts.append(parts.pop()[:-1]) + parts.append(char) + break + prev_char = remaining[i - 1] if i - 1 >= 0 else None + next_char = remaining[i + 1] if i + 1 <= len(remaining) - 1 else None + if char == "<" and next_char not in [" ", "<", "="] and prev_char not in [" ", "<"]: + open_template_arguments += 1 + continue + if char == ">" and next_char not in [" ", ">", "="] and prev_char not in [" ", ">"] and open_template_arguments > 0: + open_template_arguments -= 1 + if open_template_arguments > 0: + continue + if char == ",": + parts.append("") + return parts + + +def parse_string_content(argument): + """Return the text within quotes in string argument. + + >>> parse_string_content('1 "foo %d bar" 2') + 'foo %d bar' + >>> parse_string_content('1 foobar 2') + '' + >>> parse_string_content('1 "bar" 2') + 'bar' + >>> parse_string_content('1 "foo" 2 "bar" 3') + 'foobar' + >>> parse_string_content('1 "foo" 2 " " "bar" 3') + 'foo bar' + >>> parse_string_content('""') + '' + >>> parse_string_content('') + '' + >>> parse_string_content('1 2 3') + '' + """ + assert type(argument) is str + string_content = "" + in_string = False + for char in normalize(escape(argument)): + if char == "\"": + in_string = not in_string + elif in_string: + string_content += char + return string_content + + +def count_format_specifiers(format_string): + """Return the number of format specifiers in string format_string. + + >>> count_format_specifiers("foo bar foo") + 0 + >>> count_format_specifiers("foo %d bar foo") + 1 + >>> count_format_specifiers("foo %d bar %i foo") + 2 + >>> count_format_specifiers("foo %d bar %i foo %% foo") + 2 + >>> count_format_specifiers("foo %d bar %i foo %% foo %d foo") + 3 + >>> count_format_specifiers("foo %d bar %i foo %% foo %*d foo") + 4 + """ + assert type(format_string) is str + format_string = format_string.replace('%%', 'X') + n = 0 + in_specifier = False + for i, char in enumerate(format_string): + if char == "%": + in_specifier = True + n += 1 + elif char in "aAcdeEfFgGinopsuxX": + in_specifier = False + elif in_specifier and char == "*": + n += 1 + return n + + +def main(): + parser = argparse.ArgumentParser(description="This program checks that the number of arguments passed " + "to a variadic format string function matches the number of format " + "specifiers in the format string.") + parser.add_argument("--skip-arguments", type=int, help="number of arguments before the format string " + "argument (e.g. 1 in the case of fprintf)", default=0) + parser.add_argument("function_name", help="function name (e.g. fprintf)", default=None) + parser.add_argument("file", nargs="*", help="C++ source code file (e.g. foo.cpp)") + args = parser.parse_args() + exit_code = 0 + for filename in args.file: + with open(filename, "r", encoding="utf-8") as f: + for function_call_str in parse_function_calls(args.function_name, f.read()): + parts = parse_function_call_and_arguments(args.function_name, function_call_str) + relevant_function_call_str = unescape("".join(parts))[:512] + if (f.name, relevant_function_call_str) in FALSE_POSITIVES: + continue + if len(parts) < 3 + args.skip_arguments: + exit_code = 1 + print("{}: Could not parse function call string \"{}(...)\": {}".format(f.name, args.function_name, relevant_function_call_str)) + continue + argument_count = len(parts) - 3 - args.skip_arguments + format_str = parse_string_content(parts[1 + args.skip_arguments]) + format_specifier_count = count_format_specifiers(format_str) + if format_specifier_count != argument_count: + exit_code = 1 + print("{}: Expected {} argument(s) after format string but found {} argument(s): {}".format(f.name, format_specifier_count, argument_count, relevant_function_call_str)) + continue + sys.exit(exit_code) + + +if __name__ == "__main__": + main() diff --git a/test/lint/lint-spelling.ignore-words.txt b/test/lint/spelling.ignore-words.txt index afdb0692d8..0efd298408 100644 --- a/test/lint/lint-spelling.ignore-words.txt +++ b/test/lint/spelling.ignore-words.txt @@ -3,6 +3,7 @@ ba blockin cachable creat +desig fo fpr hights @@ -11,6 +12,7 @@ inout invokable keypair mor +nd nin ser unparseable diff --git a/test/sanitizer_suppressions/ubsan b/test/sanitizer_suppressions/ubsan index b06dd253be..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 @@ -62,7 +62,6 @@ implicit-integer-sign-change:script/bitcoinconsensus.cpp implicit-integer-sign-change:script/interpreter.cpp implicit-integer-sign-change:serialize.h implicit-integer-sign-change:txmempool.cpp -implicit-signed-integer-truncation:addrman.cpp implicit-signed-integer-truncation:crypto/ implicit-unsigned-integer-truncation:crypto/ shift-base:arith_uint256.cpp diff --git a/test/util/test_runner.py b/test/util/test_runner.py index a7fc3b1dc1..03db05c563 100755 --- a/test/util/test_runner.py +++ b/test/util/test_runner.py @@ -22,7 +22,8 @@ import sys def main(): config = configparser.ConfigParser() config.optionxform = str - config.read_file(open(os.path.join(os.path.dirname(__file__), "../config.ini"), encoding="utf8")) + with open(os.path.join(os.path.dirname(__file__), "../config.ini"), encoding="utf8") as f: + config.read_file(f) env_conf = dict(config.items('environment')) parser = argparse.ArgumentParser(description=__doc__) @@ -43,7 +44,8 @@ def main(): def bctester(testDir, input_basename, buildenv): """ Loads and parses the input file, runs all tests and reports results""" input_filename = os.path.join(testDir, input_basename) - raw_data = open(input_filename, encoding="utf8").read() + with open(input_filename, encoding="utf8") as f: + raw_data = f.read() input_data = json.loads(raw_data) failed_testcases = [] @@ -80,7 +82,8 @@ def bctest(testDir, testObj, buildenv): inputData = None if "input" in testObj: filename = os.path.join(testDir, testObj["input"]) - inputData = open(filename, encoding="utf8").read() + with open(filename, encoding="utf8") as f: + inputData = f.read() stdinCfg = subprocess.PIPE # Read the expected output data (if there is any) @@ -91,7 +94,8 @@ def bctest(testDir, testObj, buildenv): outputFn = testObj['output_cmp'] outputType = os.path.splitext(outputFn)[1][1:] # output type from file extension (determines how to compare) try: - outputData = open(os.path.join(testDir, outputFn), encoding="utf8").read() + with open(os.path.join(testDir, outputFn), encoding="utf8") as f: + outputData = f.read() except: logging.error("Output file " + outputFn + " cannot be opened") raise |