diff options
152 files changed, 837 insertions, 793 deletions
diff --git a/.cirrus.yml b/.cirrus.yml index 83cb72e4e0..97fe9866ae 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -65,14 +65,17 @@ compute_credits_template: &CREDITS_TEMPLATE use_compute_credits: $CIRRUS_REPO_FULL_NAME == 'bitcoin/bitcoin' && $CIRRUS_PR != "" task: - name: 'lint [bionic]' + name: 'lint [jammy]' << : *BASE_TEMPLATE container: - image: ubuntu:bionic # For python 3.6, oldest supported version according to doc/dependencies.md + image: ubuntu:jammy cpu: 1 memory: 1G # For faster CI feedback, immediately schedule the linters << : *CREDITS_TEMPLATE + python_cache: + folder: "/tmp/python" + fingerprint_script: cat .python-version /etc/os-release lint_script: - ./ci/lint_run_all.sh env: @@ -185,7 +188,7 @@ task: - netsh int ipv4 set dynamicport tcp start=1025 num=64511 - netsh int ipv6 set dynamicport tcp start=1025 num=64511 # Exclude feature_dbcrash for now due to timeout - - python test\functional\test_runner.py --nocleanup --ci --quiet --combinedlogslen=4000 --jobs=6 --timeout-factor=8 --extended --exclude feature_dbcrash + - python test\functional\test_runner.py --nocleanup --ci --quiet --combinedlogslen=99999999 --jobs=6 --timeout-factor=8 --extended --exclude feature_dbcrash task: name: 'ARM [unit tests, no functional tests] [bullseye]' @@ -292,10 +295,10 @@ task: FILE_ENV: "./ci/test/00_setup_env_i686_multiprocess.sh" task: - name: '[no wallet, libbitcoinkernel] [bionic]' + name: '[no wallet, libbitcoinkernel] [buster]' << : *GLOBAL_TASK_TEMPLATE container: - image: ubuntu:bionic + image: debian:buster env: << : *CIRRUS_EPHEMERAL_WORKER_TEMPLATE_ENV FILE_ENV: "./ci/test/00_setup_env_native_nowallet_libbitcoinkernel.sh" diff --git a/.python-version b/.python-version index cd337510be..36f601f10e 100644 --- a/.python-version +++ b/.python-version @@ -1 +1 @@ -3.6.15 +3.7.16 diff --git a/ci/lint/04_install.sh b/ci/lint/04_install.sh index b459b321f1..f7147582dc 100755 --- a/ci/lint/04_install.sh +++ b/ci/lint/04_install.sh @@ -7,14 +7,31 @@ export LC_ALL=C ${CI_RETRY_EXE} apt-get update -${CI_RETRY_EXE} apt-get install -y python3-pip curl git gawk jq -( - # Temporary workaround for https://github.com/bitcoin/bitcoin/pull/26130#issuecomment-1260499544 - # Can be removed once the underlying image is bumped to something that includes git2.34 or later - sed -i -e 's/bionic/jammy/g' /etc/apt/sources.list - ${CI_RETRY_EXE} apt-get update - ${CI_RETRY_EXE} apt-get install -y --reinstall git -) +# Lint dependencies: +# - curl/xz-utils (to install shellcheck) +# - git (used in many lint scripts) +# - gpg (used by verify-commits) +${CI_RETRY_EXE} apt-get install -y curl xz-utils git gpg + +if [ -z "${SKIP_PYTHON_INSTALL}" ]; then + PYTHON_PATH=/tmp/python + if [ ! -d "${PYTHON_PATH}/bin" ]; then + ( + git clone https://github.com/pyenv/pyenv.git + cd pyenv/plugins/python-build || exit 1 + ./install.sh + ) + # For dependencies see https://github.com/pyenv/pyenv/wiki#suggested-build-environment + ${CI_RETRY_EXE} apt-get install -y build-essential libssl-dev zlib1g-dev \ + libbz2-dev libreadline-dev libsqlite3-dev curl llvm \ + libncursesw5-dev xz-utils tk-dev libxml2-dev libxmlsec1-dev libffi-dev liblzma-dev \ + clang + env CC=clang python-build "$(cat "${BASE_ROOT_DIR}/.python-version")" "${PYTHON_PATH}" + fi + export PATH="${PYTHON_PATH}/bin:${PATH}" + command -v python3 + python3 --version +fi ${CI_RETRY_EXE} pip3 install codespell==2.2.1 ${CI_RETRY_EXE} pip3 install flake8==5.0.4 @@ -23,5 +40,6 @@ ${CI_RETRY_EXE} pip3 install pyzmq==24.0.1 ${CI_RETRY_EXE} pip3 install vulture==2.6 SHELLCHECK_VERSION=v0.8.0 -curl -sL "https://github.com/koalaman/shellcheck/releases/download/${SHELLCHECK_VERSION}/shellcheck-${SHELLCHECK_VERSION}.linux.x86_64.tar.xz" | tar --xz -xf - --directory /tmp/ -export PATH="/tmp/shellcheck-${SHELLCHECK_VERSION}:${PATH}" +curl -sL "https://github.com/koalaman/shellcheck/releases/download/${SHELLCHECK_VERSION}/shellcheck-${SHELLCHECK_VERSION}.linux.x86_64.tar.xz" | \ + tar --xz -xf - --directory /tmp/ +mv "/tmp/shellcheck-${SHELLCHECK_VERSION}/shellcheck" /usr/bin/ diff --git a/ci/lint/06_script.sh b/ci/lint/06_script.sh index c14d7473d3..fa28f6126c 100755 --- a/ci/lint/06_script.sh +++ b/ci/lint/06_script.sh @@ -6,7 +6,11 @@ export LC_ALL=C -if [ -n "$CIRRUS_PR" ]; then +if [ -n "$LOCAL_BRANCH" ]; then + # To faithfully recreate CI linting locally, specify all commits on the current + # branch. + COMMIT_RANGE="$(git merge-base HEAD master)..HEAD" +elif [ -n "$CIRRUS_PR" ]; then COMMIT_RANGE="HEAD~..HEAD" echo git log --no-merges --oneline "$COMMIT_RANGE" diff --git a/ci/lint/Dockerfile b/ci/lint/Dockerfile new file mode 100644 index 0000000000..03c20c7286 --- /dev/null +++ b/ci/lint/Dockerfile @@ -0,0 +1,29 @@ +# See test/lint/README.md for usage. +# +# This container basically has to live in this directory in order to pull in the CI +# install scripts. If it lived in the root directory, it would have to pull in the +# entire repo as docker context during build; if it lived elsewhere, it wouldn't be +# able to make back-references to pull in the install scripts. So here it lives. + +FROM python:3.7-buster + +ENV DEBIAN_FRONTEND=noninteractive +ENV LC_ALL=C.UTF-8 + +# This is used by the 04_install.sh script; we can't read the Python version from +# .python-version for the same reasons as above, and it's more efficient to pull a +# preexisting Python image than it is to build from source. +ENV SKIP_PYTHON_INSTALL=1 + +# Must be built from ./ci/lint/ for these paths to work. +COPY ./docker-entrypoint.sh /entrypoint.sh +COPY ./04_install.sh /install.sh + +RUN /install.sh && \ + echo 'alias lint="./ci/lint/06_script.sh"' >> ~/.bashrc && \ + chmod 755 /entrypoint.sh && \ + rm -rf /var/lib/apt/lists/* + + +WORKDIR /bitcoin +ENTRYPOINT ["/entrypoint.sh"] diff --git a/ci/lint/docker-entrypoint.sh b/ci/lint/docker-entrypoint.sh new file mode 100755 index 0000000000..3fdbbb0761 --- /dev/null +++ b/ci/lint/docker-entrypoint.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env bash +export LC_ALL=C + +# Fixes permission issues when there is a container UID/GID mismatch with the owner +# of the mounted bitcoin src dir. +git config --global --add safe.directory /bitcoin + +if [ -z "$1" ]; then + LOCAL_BRANCH=1 bash -ic "./ci/lint/06_script.sh" +else + exec "$@" +fi diff --git a/ci/test/00_setup_env_i686_centos.sh b/ci/test/00_setup_env_i686_centos.sh index 156be81ec4..e8f963c8a9 100755 --- a/ci/test/00_setup_env_i686_centos.sh +++ b/ci/test/00_setup_env_i686_centos.sh @@ -9,7 +9,7 @@ export LC_ALL=C.UTF-8 export HOST=i686-pc-linux-gnu export CONTAINER_NAME=ci_i686_centos export CI_IMAGE_NAME_TAG=quay.io/centos/centos:stream8 -export CI_BASE_PACKAGES="gcc-c++ glibc-devel.x86_64 libstdc++-devel.x86_64 glibc-devel.i686 libstdc++-devel.i686 ccache libtool make git python3 python3-pip which patch lbzip2 xz procps-ng dash rsync coreutils bison" +export CI_BASE_PACKAGES="gcc-c++ glibc-devel.x86_64 libstdc++-devel.x86_64 glibc-devel.i686 libstdc++-devel.i686 ccache libtool make git python38 python38-pip which patch lbzip2 xz procps-ng dash rsync coreutils bison" export PIP_PACKAGES="pyzmq" export GOAL="install" export BITCOIN_CONFIG="--enable-zmq --with-gui=qt5 --enable-reduce-exports" diff --git a/ci/test/00_setup_env_native_nowallet_libbitcoinkernel.sh b/ci/test/00_setup_env_native_nowallet_libbitcoinkernel.sh index 96cea1be3e..08bb5d1eab 100755 --- a/ci/test/00_setup_env_native_nowallet_libbitcoinkernel.sh +++ b/ci/test/00_setup_env_native_nowallet_libbitcoinkernel.sh @@ -7,8 +7,10 @@ export LC_ALL=C.UTF-8 export CONTAINER_NAME=ci_native_nowallet_libbitcoinkernel -export CI_IMAGE_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 CI_IMAGE_NAME_TAG=debian:buster +# Use minimum supported python3.7 and clang-8, see doc/dependencies.md +export PACKAGES="-t buster-backports python3-zmq clang-8 llvm-8 libc++abi-8-dev libc++-8-dev" +export APPEND_APT_SOURCES_LIST="deb http://deb.debian.org/debian buster-backports main" 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 4b64a1d2e8..3f39185ae8 100755 --- a/ci/test/00_setup_env_native_qt5.sh +++ b/ci/test/00_setup_env_native_qt5.sh @@ -7,7 +7,8 @@ export LC_ALL=C.UTF-8 export CONTAINER_NAME=ci_native_qt5 -export CI_IMAGE_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 CI_IMAGE_NAME_TAG=debian:buster +# Use minimum supported python3.7 and gcc-8, 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 diff --git a/ci/test/04_install.sh b/ci/test/04_install.sh index ec604c9476..9bfe555243 100755 --- a/ci/test/04_install.sh +++ b/ci/test/04_install.sh @@ -92,6 +92,9 @@ elif [ "$CI_USE_APT_INSTALL" != "no" ]; then # TODO: drop this once we can use newer images in GCE CI_EXEC_ROOT add-apt-repository ppa:hadret/bpfcc fi + if [[ -n "${APPEND_APT_SOURCES_LIST}" ]]; then + CI_EXEC_ROOT echo "${APPEND_APT_SOURCES_LIST}" >> /etc/apt/sources.list + fi ${CI_RETRY_EXE} CI_EXEC_ROOT apt-get update ${CI_RETRY_EXE} CI_EXEC_ROOT apt-get install --no-install-recommends --no-upgrade -y "$PACKAGES" "$CI_BASE_PACKAGES" fi diff --git a/ci/test/06_script_b.sh b/ci/test/06_script_b.sh index 02c91cfa69..f792a9f192 100755 --- a/ci/test/06_script_b.sh +++ b/ci/test/06_script_b.sh @@ -31,7 +31,7 @@ if [ "$RUN_UNIT_TESTS_SEQUENTIAL" = "true" ]; then 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 + 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=99999999 --timeout-factor="${TEST_RUNNER_TIMEOUT_FACTOR}" "${TEST_RUNNER_EXTRA}" --quiet --failfast fi if [ "${RUN_TIDY}" = "true" ]; then diff --git a/configure.ac b/configure.ac index a5d9086603..c982ee8b8c 100644 --- a/configure.ac +++ b/configure.ac @@ -120,8 +120,8 @@ AC_PATH_TOOL([AR], [ar]) AC_PATH_TOOL([GCOV], [gcov]) AC_PATH_TOOL([LLVM_COV], [llvm-cov]) AC_PATH_PROG([LCOV], [lcov]) -dnl Python 3.6 is specified in .python-version and should be used if available, see doc/dependencies.md -AC_PATH_PROGS([PYTHON], [python3.6 python3.7 python3.8 python3.9 python3.10 python3.11 python3 python]) +dnl Python 3.7 is specified in .python-version and should be used if available, see doc/dependencies.md +AC_PATH_PROGS([PYTHON], [python3.7 python3.8 python3.9 python3.10 python3.11 python3.12 python3 python]) AC_PATH_PROG([GENHTML], [genhtml]) AC_PATH_PROG([GIT], [git]) AC_PATH_PROG([CCACHE], [ccache]) diff --git a/contrib/debian/copyright b/contrib/debian/copyright index 60b3bcb424..ca430170a1 100644 --- a/contrib/debian/copyright +++ b/contrib/debian/copyright @@ -7,22 +7,14 @@ Source: https://github.com/bitcoin/bitcoin Files: * Copyright: 2009-2023, Bitcoin Core Developers License: Expat -Comment: The Bitcoin Core Developers encompasses the current developers listed on bitcoin.org, - as well as the numerous contributors to the project. +Comment: The Bitcoin Core Developers encompasses all contributors to the + project, listed in the release notes or the git log. Files: debian/* Copyright: 2010-2011, Jonas Smedegaard <dr@jones.dk> 2011, Matt Corallo <matt@bluematt.me> License: GPL-2+ -Files: src/secp256k1/build-aux/m4/ax_jni_include_dir.m4 -Copyright: 2008 Don Anderson <dda@sleepycat.com> -License: GNU-All-permissive-License - -Files: src/secp256k1/build-aux/m4/ax_prog_cc_for_build.m4 -Copyright: 2008 Paolo Bonzini <bonzini@gnu.org> -License: GNU-All-permissive-License - Files: src/qt/res/icons/add.png src/qt/res/icons/address-book.png src/qt/res/icons/chevron.png @@ -112,12 +104,6 @@ License: Expat TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -License: GNU-All-permissive-License - 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. - License: GPL-2+ This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the diff --git a/contrib/devtools/clang-format-diff.py b/contrib/devtools/clang-format-diff.py index 98eee67f43..420bf7ff33 100755 --- a/contrib/devtools/clang-format-diff.py +++ b/contrib/devtools/clang-format-diff.py @@ -146,7 +146,7 @@ def main(): stdout=subprocess.PIPE, stderr=None, stdin=subprocess.PIPE, - universal_newlines=True) + text=True) stdout, stderr = p.communicate() if p.returncode != 0: sys.exit(p.returncode) diff --git a/contrib/devtools/gen-manpages.py b/contrib/devtools/gen-manpages.py index 38cdb3acb8..2860e7db99 100755 --- a/contrib/devtools/gen-manpages.py +++ b/contrib/devtools/gen-manpages.py @@ -23,7 +23,7 @@ help2man = os.getenv('HELP2MAN', 'help2man') # If not otherwise specified, get top directory from git. topdir = os.getenv('TOPDIR') if not topdir: - r = subprocess.run([git, 'rev-parse', '--show-toplevel'], stdout=subprocess.PIPE, check=True, universal_newlines=True) + r = subprocess.run([git, 'rev-parse', '--show-toplevel'], stdout=subprocess.PIPE, check=True, text=True) topdir = r.stdout.rstrip() # Get input and output directories. @@ -36,7 +36,7 @@ versions = [] for relpath in BINARIES: abspath = os.path.join(builddir, relpath) try: - r = subprocess.run([abspath, "--version"], stdout=subprocess.PIPE, check=True, universal_newlines=True) + r = subprocess.run([abspath, "--version"], stdout=subprocess.PIPE, check=True, text=True) except IOError: print(f'{abspath} not found or not an executable', file=sys.stderr) sys.exit(1) diff --git a/contrib/devtools/test-security-check.py b/contrib/devtools/test-security-check.py index fe61ab275f..54718fd7a1 100755 --- a/contrib/devtools/test-security-check.py +++ b/contrib/devtools/test-security-check.py @@ -39,7 +39,7 @@ def call_security_check(cc, source, executable, options): env_flags += filter(None, os.environ.get(var, '').split(' ')) subprocess.run([*cc,source,'-o',executable] + env_flags + options, check=True) - p = subprocess.run([os.path.join(os.path.dirname(__file__), 'security-check.py'), executable], stdout=subprocess.PIPE, universal_newlines=True) + p = subprocess.run([os.path.join(os.path.dirname(__file__), 'security-check.py'), executable], stdout=subprocess.PIPE, text=True) return (p.returncode, p.stdout.rstrip()) def get_arch(cc, source, executable): diff --git a/contrib/devtools/test-symbol-check.py b/contrib/devtools/test-symbol-check.py index 624870ebcb..de73b02090 100755 --- a/contrib/devtools/test-symbol-check.py +++ b/contrib/devtools/test-symbol-check.py @@ -23,13 +23,13 @@ def call_symbol_check(cc: List[str], source, executable, options): env_flags += filter(None, os.environ.get(var, '').split(' ')) subprocess.run([*cc,source,'-o',executable] + env_flags + options, check=True) - p = subprocess.run([os.path.join(os.path.dirname(__file__), 'symbol-check.py'), executable], stdout=subprocess.PIPE, universal_newlines=True) + p = subprocess.run([os.path.join(os.path.dirname(__file__), 'symbol-check.py'), executable], stdout=subprocess.PIPE, text=True) os.remove(source) os.remove(executable) return (p.returncode, p.stdout.rstrip()) def get_machine(cc: List[str]): - p = subprocess.run([*cc,'-dumpmachine'], stdout=subprocess.PIPE, universal_newlines=True) + p = subprocess.run([*cc,'-dumpmachine'], stdout=subprocess.PIPE, text=True) return p.stdout.rstrip() class TestSymbolChecks(unittest.TestCase): diff --git a/contrib/macdeploy/macdeployqtplus b/contrib/macdeploy/macdeployqtplus index 2420539b7c..f8677ba7b8 100755 --- a/contrib/macdeploy/macdeployqtplus +++ b/contrib/macdeploy/macdeployqtplus @@ -187,7 +187,7 @@ def getFrameworks(binaryPath: str, verbose: int) -> List[FrameworkInfo]: if verbose: print(f"Inspecting with otool: {binaryPath}") otoolbin=os.getenv("OTOOL", "otool") - otool = run([otoolbin, "-L", binaryPath], stdout=PIPE, stderr=PIPE, universal_newlines=True) + otool = run([otoolbin, "-L", binaryPath], stdout=PIPE, stderr=PIPE, text=True) if otool.returncode != 0: sys.stderr.write(otool.stderr) sys.stderr.flush() @@ -577,17 +577,17 @@ if config.dmg is not None: tempname: str = appname + ".temp.dmg" - run(["hdiutil", "create", tempname, "-srcfolder", "dist", "-format", "UDRW", "-size", str(size), "-volname", appname], check=True, universal_newlines=True) + run(["hdiutil", "create", tempname, "-srcfolder", "dist", "-format", "UDRW", "-size", str(size), "-volname", appname], check=True, text=True) if verbose: print("Attaching temp image...") - output = run(["hdiutil", "attach", tempname, "-readwrite"], check=True, universal_newlines=True, stdout=PIPE).stdout + output = run(["hdiutil", "attach", tempname, "-readwrite"], check=True, text=True, stdout=PIPE).stdout print("+ Finalizing .dmg disk image +") - run(["hdiutil", "detach", f"/Volumes/{appname}"], universal_newlines=True) + run(["hdiutil", "detach", f"/Volumes/{appname}"], text=True) - run(["hdiutil", "convert", tempname, "-format", "UDZO", "-o", appname, "-imagekey", "zlib-level=9"], check=True, universal_newlines=True) + run(["hdiutil", "convert", tempname, "-format", "UDZO", "-o", appname, "-imagekey", "zlib-level=9"], check=True, text=True) os.unlink(tempname) diff --git a/contrib/signet/getcoins.py b/contrib/signet/getcoins.py index ff99d60679..d4e436626f 100755 --- a/contrib/signet/getcoins.py +++ b/contrib/signet/getcoins.py @@ -129,7 +129,7 @@ if args.captcha != '': # Retrieve a captcha # Convert SVG image to PPM, and load it try: - rv = subprocess.run([args.imagemagick, 'svg:-', '-depth', '8', 'ppm:-'], input=res.content, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + rv = subprocess.run([args.imagemagick, 'svg:-', '-depth', '8', 'ppm:-'], input=res.content, check=True, capture_output=True) except FileNotFoundError: raise SystemExit(f"The binary {args.imagemagick} could not be found. Please make sure ImageMagick (or a compatible fork) is installed and that the correct path is specified.") diff --git a/depends/Makefile b/depends/Makefile index 11fdd6dd53..27bf804c6b 100644 --- a/depends/Makefile +++ b/depends/Makefile @@ -32,6 +32,8 @@ SOURCES_PATH ?= $(BASEDIR)/sources WORK_PATH = $(BASEDIR)/work BASE_CACHE ?= $(BASEDIR)/built SDK_PATH ?= $(BASEDIR)/SDKs +NO_BOOST ?= +NO_LIBEVENT ?= NO_QT ?= NO_QR ?= NO_BDB ?= @@ -147,6 +149,10 @@ include packages/packages.mk 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))') +boost_packages_$(NO_BOOST) = $(boost_packages) + +libevent_packages_$(NO_LIBEVENT) = $(libevent_packages) + qrencode_packages_$(NO_QR) = $(qrencode_$(host_os)_packages) qt_packages_$(NO_QT) = $(qt_packages) $(qt_$(host_os)_packages) $(qt_$(host_arch)_$(host_os)_packages) $(qrencode_packages_) @@ -162,7 +168,7 @@ zmq_packages_$(NO_ZMQ) = $(zmq_packages) multiprocess_packages_$(MULTIPROCESS) = $(multiprocess_packages) usdt_packages_$(NO_USDT) = $(usdt_$(host_os)_packages) -packages += $($(host_arch)_$(host_os)_packages) $($(host_os)_packages) $(qt_packages_) $(wallet_packages_) $(upnp_packages_) $(natpmp_packages_) $(usdt_packages_) +packages += $($(host_arch)_$(host_os)_packages) $($(host_os)_packages) $(boost_packages_) $(libevent_packages_) $(qt_packages_) $(wallet_packages_) $(upnp_packages_) $(natpmp_packages_) $(usdt_packages_) native_packages += $($(host_arch)_$(host_os)_native_packages) $($(host_os)_native_packages) ifneq ($(zmq_packages_),) diff --git a/depends/packages/packages.mk b/depends/packages/packages.mk index 998cc0221c..b3600b72d0 100644 --- a/depends/packages/packages.mk +++ b/depends/packages/packages.mk @@ -1,4 +1,8 @@ -packages:=boost libevent +packages:= + +boost_packages = boost + +libevent_packages = libevent qrencode_linux_packages = qrencode qrencode_android_packages = qrencode diff --git a/doc/build-freebsd.md b/doc/build-freebsd.md index a8e643a2ab..d45e9c4d0d 100644 --- a/doc/build-freebsd.md +++ b/doc/build-freebsd.md @@ -72,7 +72,7 @@ There is an included test suite that is useful for testing code changes when dev To run the test suite (recommended), you will need to have Python 3 installed: ```bash -pkg install python3 +pkg install python3 databases/py-sqlite3 ``` --- diff --git a/doc/dependencies.md b/doc/dependencies.md index c1f641c162..3349c81c46 100644 --- a/doc/dependencies.md +++ b/doc/dependencies.md @@ -10,7 +10,7 @@ You can find installation instructions in the `build-*.md` file for your platfor | [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) | +| [Python](https://www.python.org) (tests) | [3.7](https://github.com/bitcoin/bitcoin/pull/26226) | | [systemtap](https://sourceware.org/systemtap/) ([tracing](tracing.md))| N/A | ## Required diff --git a/doc/release-notes-23395.md b/doc/release-notes-23395.md new file mode 100644 index 0000000000..b9d7d9409c --- /dev/null +++ b/doc/release-notes-23395.md @@ -0,0 +1,8 @@ +Notable changes +=============== + +New settings +------------ + +- The `shutdownnotify` option is used to specify a command to execute synchronously +before Bitcoin Core has begun its shutdown sequence. (#23395) diff --git a/doc/release-notes-25957.md b/doc/release-notes-25957.md new file mode 100644 index 0000000000..c71afa2c2e --- /dev/null +++ b/doc/release-notes-25957.md @@ -0,0 +1,9 @@ +Wallet +------ + +- Rescans for descriptor wallets are now significantly faster if compact + block filters (BIP158) are available. Since those are not constructed + by default, the configuration option "-blockfilterindex=1" has to be + provided to take advantage of the optimization. This improves the + performance of the RPC calls `rescanblockchain`, `importdescriptors` + and `restorewallet`. (#25957) diff --git a/src/.clang-tidy b/src/.clang-tidy index bea5b53668..b2e6914548 100644 --- a/src/.clang-tidy +++ b/src/.clang-tidy @@ -12,19 +12,8 @@ performance-unnecessary-copy-initialization, readability-redundant-declaration, readability-redundant-string-init, ' -WarningsAsErrors: ' -bugprone-argument-comment, -bugprone-use-after-move, -misc-unused-using-decls, -modernize-use-default-member-init, -modernize-use-nullptr, -performance-for-range-copy, -performance-move-const-arg, -performance-no-automatic-move, -performance-unnecessary-copy-initialization, -readability-redundant-declaration, -readability-redundant-string-init, -' +WarningsAsErrors: '*' CheckOptions: - key: performance-move-const-arg.CheckTriviallyCopyableMove value: false +HeaderFilterRegex: './qt' diff --git a/src/Makefile.am b/src/Makefile.am index 62c94f59fb..35b0ad24c3 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -655,8 +655,9 @@ libbitcoin_common_a_SOURCES = \ policy/policy.cpp \ protocol.cpp \ psbt.cpp \ - rpc/rawtransaction_util.cpp \ rpc/external_signer.cpp \ + rpc/rawtransaction_util.cpp \ + rpc/request.cpp \ rpc/util.cpp \ scheduler.cpp \ script/descriptor.cpp \ @@ -684,7 +685,6 @@ libbitcoin_util_a_SOURCES = \ logging.cpp \ random.cpp \ randomenv.cpp \ - rpc/request.cpp \ support/cleanse.cpp \ sync.cpp \ util/asmap.cpp \ diff --git a/src/addrdb.cpp b/src/addrdb.cpp index d95c07d6a8..7c954dbf3a 100644 --- a/src/addrdb.cpp +++ b/src/addrdb.cpp @@ -34,10 +34,9 @@ bool SerializeDB(Stream& stream, const Data& data) { // Write and commit header, data try { - CHashWriter hasher(stream.GetType(), stream.GetVersion()); - stream << Params().MessageStart() << data; - hasher << Params().MessageStart() << data; - stream << hasher.GetHash(); + HashedSourceWriter hashwriter{stream}; + hashwriter << Params().MessageStart() << data; + stream << hashwriter.GetHash(); } catch (const std::exception& e) { return error("%s: Serialize or I/O error - %s", __func__, e.what()); } diff --git a/src/addrman.cpp b/src/addrman.cpp index 91eedeebe1..f86bdfa3df 100644 --- a/src/addrman.cpp +++ b/src/addrman.cpp @@ -1178,8 +1178,7 @@ void AddrMan::Unserialize(Stream& s_) } // explicit instantiation -template void AddrMan::Serialize(CHashWriter& s) const; -template void AddrMan::Serialize(CAutoFile& s) const; +template void AddrMan::Serialize(HashedSourceWriter<CAutoFile>& s) const; template void AddrMan::Serialize(CDataStream& s) const; template void AddrMan::Unserialize(CAutoFile& s); template void AddrMan::Unserialize(CHashVerifier<CAutoFile>& s); diff --git a/src/hash.h b/src/hash.h index b18a031268..6500f1c709 100644 --- a/src/hash.h +++ b/src/hash.h @@ -6,6 +6,7 @@ #ifndef BITCOIN_HASH_H #define BITCOIN_HASH_H +#include <attributes.h> #include <crypto/common.h> #include <crypto/ripemd160.h> #include <crypto/sha256.h> @@ -199,6 +200,30 @@ public: } }; +/** Writes data to an underlying source stream, while hashing the written data. */ +template <typename Source> +class HashedSourceWriter : public CHashWriter +{ +private: + Source& m_source; + +public: + explicit HashedSourceWriter(Source& source LIFETIMEBOUND) : CHashWriter{source.GetType(), source.GetVersion()}, m_source{source} {} + + void write(Span<const std::byte> src) + { + m_source.write(src); + CHashWriter::write(src); + } + + template <typename T> + HashedSourceWriter& operator<<(const T& obj) + { + ::Serialize(*this, obj); + return *this; + } +}; + /** Compute the 256-bit hash of an object's serialization. */ template<typename T> uint256 SerializeHash(const T& obj, int nType=SER_GETHASH, int nVersion=PROTOCOL_VERSION) diff --git a/src/init.cpp b/src/init.cpp index b2b0308353..d8d8a66d34 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -191,8 +191,24 @@ static fs::path GetPidFile(const ArgsManager& args) // shutdown thing. // +#if HAVE_SYSTEM +static void ShutdownNotify(const ArgsManager& args) +{ + std::vector<std::thread> threads; + for (const auto& cmd : args.GetArgs("-shutdownnotify")) { + threads.emplace_back(runCommand, cmd); + } + for (auto& t : threads) { + t.join(); + } +} +#endif + void Interrupt(NodeContext& node) { +#if HAVE_SYSTEM + ShutdownNotify(*node.args); +#endif InterruptHTTPServer(); InterruptHTTPRPC(); InterruptRPC(); @@ -441,6 +457,7 @@ void SetupServerArgs(ArgsManager& argsman) 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); + argsman.AddArg("-shutdownnotify=<cmd>", "Execute command immediately before beginning shutdown. The need for shutdown may be urgent, so be careful not to delay it long (if the command doesn't require interaction with the server, consider having it fork into the background).", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); #endif #ifndef WIN32 argsman.AddArg("-sysperms", "Create new files with system default permissions, instead of umask 077 (only effective with disabled wallet functionality)", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); diff --git a/src/psbt.h b/src/psbt.h index 40d69cd454..8fafadcea9 100644 --- a/src/psbt.h +++ b/src/psbt.h @@ -206,7 +206,7 @@ struct PSBTInput // 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<std::pair<std::vector<unsigned char>, 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; @@ -621,7 +621,7 @@ struct PSBTInput } 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); + const auto leaf_script = std::make_pair(script_v, (int)leaf_ver); m_tap_scripts[leaf_script].insert(std::vector<unsigned char>(key.begin() + 1, key.end())); break; } @@ -713,7 +713,7 @@ struct PSBTOutput CScript witness_script; std::map<CPubKey, KeyOriginInfo> hd_keypaths; XOnlyPubKey m_tap_internal_key; - std::vector<std::tuple<uint8_t, uint8_t, CScript>> m_tap_tree; + std::vector<std::tuple<uint8_t, uint8_t, std::vector<unsigned char>>> 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; @@ -864,7 +864,7 @@ struct PSBTOutput while (!s_tree.empty()) { uint8_t depth; uint8_t leaf_ver; - CScript script; + std::vector<unsigned char> script; s_tree >> depth; s_tree >> leaf_ver; s_tree >> script; diff --git a/src/qt/addressbookpage.cpp b/src/qt/addressbookpage.cpp index cc24b7cfcb..b888fc43e2 100644 --- a/src/qt/addressbookpage.cpp +++ b/src/qt/addressbookpage.cpp @@ -64,7 +64,6 @@ protected: AddressBookPage::AddressBookPage(const PlatformStyle *platformStyle, Mode _mode, Tabs _tab, QWidget *parent) : QDialog(parent, GUIUtil::dialog_flags), ui(new Ui::AddressBookPage), - model(nullptr), mode(_mode), tab(_tab) { diff --git a/src/qt/addressbookpage.h b/src/qt/addressbookpage.h index 144990f419..283209d00c 100644 --- a/src/qt/addressbookpage.h +++ b/src/qt/addressbookpage.h @@ -49,7 +49,7 @@ public Q_SLOTS: private: Ui::AddressBookPage *ui; - AddressTableModel *model; + AddressTableModel* model{nullptr}; Mode mode; Tabs tab; QString returnValue; diff --git a/src/qt/askpassphrasedialog.cpp b/src/qt/askpassphrasedialog.cpp index f16700bfd9..d15aba5cdd 100644 --- a/src/qt/askpassphrasedialog.cpp +++ b/src/qt/askpassphrasedialog.cpp @@ -23,8 +23,6 @@ AskPassphraseDialog::AskPassphraseDialog(Mode _mode, QWidget *parent, SecureStri QDialog(parent, GUIUtil::dialog_flags), ui(new Ui::AskPassphraseDialog), mode(_mode), - model(nullptr), - fCapsLock(false), m_passphrase_out(passphrase_out) { ui->setupUi(this); diff --git a/src/qt/askpassphrasedialog.h b/src/qt/askpassphrasedialog.h index 66031b08d3..370ea1de7e 100644 --- a/src/qt/askpassphrasedialog.h +++ b/src/qt/askpassphrasedialog.h @@ -38,8 +38,8 @@ public: private: Ui::AskPassphraseDialog *ui; Mode mode; - WalletModel *model; - bool fCapsLock; + WalletModel* model{nullptr}; + bool fCapsLock{false}; SecureString* m_passphrase_out; private Q_SLOTS: diff --git a/src/qt/bitcoin.cpp b/src/qt/bitcoin.cpp index e8b07a6749..59f433749d 100644 --- a/src/qt/bitcoin.cpp +++ b/src/qt/bitcoin.cpp @@ -228,14 +228,8 @@ void DebugMessageHandler(QtMsgType type, const QMessageLogContext& context, cons static int qt_argc = 1; static const char* qt_argv = "bitcoin-qt"; -BitcoinApplication::BitcoinApplication(): - QApplication(qt_argc, const_cast<char **>(&qt_argv)), - optionsModel(nullptr), - clientModel(nullptr), - window(nullptr), - pollShutdownTimer(nullptr), - returnValue(0), - platformStyle(nullptr) +BitcoinApplication::BitcoinApplication() + : QApplication(qt_argc, const_cast<char**>(&qt_argv)) { // Qt runs setlocale(LC_ALL, "") on initialization. RegisterMetaTypes(); diff --git a/src/qt/bitcoin.h b/src/qt/bitcoin.h index 61a18ba6a1..9174e23de6 100644 --- a/src/qt/bitcoin.h +++ b/src/qt/bitcoin.h @@ -96,16 +96,16 @@ protected: private: std::optional<InitExecutor> m_executor; - OptionsModel *optionsModel; - ClientModel *clientModel; - BitcoinGUI *window; - QTimer *pollShutdownTimer; + OptionsModel* optionsModel{nullptr}; + ClientModel* clientModel{nullptr}; + BitcoinGUI* window{nullptr}; + QTimer* pollShutdownTimer{nullptr}; #ifdef ENABLE_WALLET PaymentServer* paymentServer{nullptr}; WalletController* m_wallet_controller{nullptr}; #endif - int returnValue; - const PlatformStyle *platformStyle; + int returnValue{0}; + const PlatformStyle* platformStyle{nullptr}; std::unique_ptr<QWidget> shutdownWindow; SplashScreen* m_splash = nullptr; std::unique_ptr<interfaces::Node> m_node; diff --git a/src/qt/bitcoinamountfield.cpp b/src/qt/bitcoinamountfield.cpp index 765a2a5ffc..a7e2d22488 100644 --- a/src/qt/bitcoinamountfield.cpp +++ b/src/qt/bitcoinamountfield.cpp @@ -217,9 +217,8 @@ Q_SIGNALS: #include <qt/bitcoinamountfield.moc> -BitcoinAmountField::BitcoinAmountField(QWidget *parent) : - QWidget(parent), - amount(nullptr) +BitcoinAmountField::BitcoinAmountField(QWidget* parent) + : QWidget(parent) { amount = new AmountSpinBox(this); amount->setLocale(QLocale::c()); diff --git a/src/qt/bitcoinamountfield.h b/src/qt/bitcoinamountfield.h index a40cd38332..f710915a49 100644 --- a/src/qt/bitcoinamountfield.h +++ b/src/qt/bitcoinamountfield.h @@ -74,7 +74,7 @@ protected: bool eventFilter(QObject *object, QEvent *event) override; private: - AmountSpinBox *amount; + AmountSpinBox* amount{nullptr}; QValueComboBox *unit; private Q_SLOTS: diff --git a/src/qt/bitcoingui.cpp b/src/qt/bitcoingui.cpp index a0731b337a..a7ffd367d7 100644 --- a/src/qt/bitcoingui.cpp +++ b/src/qt/bitcoingui.cpp @@ -1541,10 +1541,8 @@ bool BitcoinGUI::isPrivacyModeActivated() const return m_mask_values_action->isChecked(); } -UnitDisplayStatusBarControl::UnitDisplayStatusBarControl(const PlatformStyle *platformStyle) - : optionsModel(nullptr), - menu(nullptr), - m_platform_style{platformStyle} +UnitDisplayStatusBarControl::UnitDisplayStatusBarControl(const PlatformStyle* platformStyle) + : m_platform_style{platformStyle} { createContextMenu(); setToolTip(tr("Unit to show amounts in. Click to select another unit.")); diff --git a/src/qt/bitcoingui.h b/src/qt/bitcoingui.h index 9970e87833..1a83057146 100644 --- a/src/qt/bitcoingui.h +++ b/src/qt/bitcoingui.h @@ -333,8 +333,8 @@ protected: void changeEvent(QEvent* e) override; private: - OptionsModel *optionsModel; - QMenu* menu; + OptionsModel* optionsModel{nullptr}; + QMenu* menu{nullptr}; const PlatformStyle* m_platform_style; /** Shows context menu with Display Unit options by the mouse coordinates */ diff --git a/src/qt/clientmodel.cpp b/src/qt/clientmodel.cpp index 6f53626fd1..c0d1a0e226 100644 --- a/src/qt/clientmodel.cpp +++ b/src/qt/clientmodel.cpp @@ -34,8 +34,6 @@ ClientModel::ClientModel(interfaces::Node& node, OptionsModel *_optionsModel, QO QObject(parent), m_node(node), optionsModel(_optionsModel), - peerTableModel(nullptr), - banTableModel(nullptr), m_thread(new QThread(this)) { cachedBestHeaderHeight = -1; diff --git a/src/qt/clientmodel.h b/src/qt/clientmodel.h index 49ac75452f..9ff64fe772 100644 --- a/src/qt/clientmodel.h +++ b/src/qt/clientmodel.h @@ -104,9 +104,9 @@ private: std::unique_ptr<interfaces::Handler> m_handler_notify_block_tip; std::unique_ptr<interfaces::Handler> m_handler_notify_header_tip; OptionsModel *optionsModel; - PeerTableModel *peerTableModel; + PeerTableModel* peerTableModel{nullptr}; PeerTableSortProxy* m_peer_table_sort_proxy{nullptr}; - BanTableModel *banTableModel; + BanTableModel* banTableModel{nullptr}; //! A thread to interact with m_node asynchronously QThread* const m_thread; diff --git a/src/qt/csvmodelwriter.cpp b/src/qt/csvmodelwriter.cpp index 656afb6e87..445ea41475 100644 --- a/src/qt/csvmodelwriter.cpp +++ b/src/qt/csvmodelwriter.cpp @@ -8,9 +8,9 @@ #include <QFile> #include <QTextStream> -CSVModelWriter::CSVModelWriter(const QString &_filename, QObject *parent) : - QObject(parent), - filename(_filename), model(nullptr) +CSVModelWriter::CSVModelWriter(const QString& _filename, QObject* parent) + : QObject(parent), + filename(_filename) { } diff --git a/src/qt/csvmodelwriter.h b/src/qt/csvmodelwriter.h index ad247b6859..a1e77826a8 100644 --- a/src/qt/csvmodelwriter.h +++ b/src/qt/csvmodelwriter.h @@ -32,7 +32,7 @@ public: private: QString filename; - const QAbstractItemModel *model; + const QAbstractItemModel* model{nullptr}; struct Column { diff --git a/src/qt/editaddressdialog.cpp b/src/qt/editaddressdialog.cpp index fa27635981..9b3319415d 100644 --- a/src/qt/editaddressdialog.cpp +++ b/src/qt/editaddressdialog.cpp @@ -12,12 +12,10 @@ #include <QMessageBox> -EditAddressDialog::EditAddressDialog(Mode _mode, QWidget *parent) : - QDialog(parent, GUIUtil::dialog_flags), - ui(new Ui::EditAddressDialog), - mapper(nullptr), - mode(_mode), - model(nullptr) +EditAddressDialog::EditAddressDialog(Mode _mode, QWidget* parent) + : QDialog(parent, GUIUtil::dialog_flags), + ui(new Ui::EditAddressDialog), + mode(_mode) { ui->setupUi(this); diff --git a/src/qt/editaddressdialog.h b/src/qt/editaddressdialog.h index f7ad80bb2d..7bfadcfbcc 100644 --- a/src/qt/editaddressdialog.h +++ b/src/qt/editaddressdialog.h @@ -49,9 +49,9 @@ private: QString getDuplicateAddressWarning() const; Ui::EditAddressDialog *ui; - QDataWidgetMapper *mapper; + QDataWidgetMapper* mapper{nullptr}; Mode mode; - AddressTableModel *model; + AddressTableModel* model{nullptr}; QString address; }; diff --git a/src/qt/intro.cpp b/src/qt/intro.cpp index ea5b1a7242..12aa02340a 100644 --- a/src/qt/intro.cpp +++ b/src/qt/intro.cpp @@ -122,8 +122,6 @@ int GetPruneTargetGB() Intro::Intro(QWidget *parent, int64_t blockchain_size_gb, int64_t chain_state_size_gb) : QDialog(parent, GUIUtil::dialog_flags), ui(new Ui::Intro), - thread(nullptr), - signalled(false), m_blockchain_size_gb(blockchain_size_gb), m_chain_state_size_gb(chain_state_size_gb), m_prune_target_gb{GetPruneTargetGB()} diff --git a/src/qt/intro.h b/src/qt/intro.h index d9c45007e1..900c657b27 100644 --- a/src/qt/intro.h +++ b/src/qt/intro.h @@ -64,9 +64,9 @@ private Q_SLOTS: private: Ui::Intro *ui; - QThread *thread; + QThread* thread{nullptr}; QMutex mutex; - bool signalled; + bool signalled{false}; QString pathToCheck; const int64_t m_blockchain_size_gb; const int64_t m_chain_state_size_gb; diff --git a/src/qt/modaloverlay.cpp b/src/qt/modaloverlay.cpp index 585599141a..b09e230bce 100644 --- a/src/qt/modaloverlay.cpp +++ b/src/qt/modaloverlay.cpp @@ -12,13 +12,10 @@ #include <QPropertyAnimation> #include <QResizeEvent> -ModalOverlay::ModalOverlay(bool enable_wallet, QWidget *parent) : -QWidget(parent), -ui(new Ui::ModalOverlay), -bestHeaderHeight(0), -bestHeaderDate(QDateTime()), -layerIsVisible(false), -userClosed(false) +ModalOverlay::ModalOverlay(bool enable_wallet, QWidget* parent) + : QWidget(parent), + ui(new Ui::ModalOverlay), + bestHeaderDate(QDateTime()) { ui->setupUi(this); connect(ui->closeButton, &QPushButton::clicked, this, &ModalOverlay::closeClicked); diff --git a/src/qt/modaloverlay.h b/src/qt/modaloverlay.h index 8d0c8fbf9a..40e487b249 100644 --- a/src/qt/modaloverlay.h +++ b/src/qt/modaloverlay.h @@ -45,11 +45,11 @@ protected: private: Ui::ModalOverlay *ui; - int bestHeaderHeight; //best known height (based on the headers) + int bestHeaderHeight{0}; // best known height (based on the headers) QDateTime bestHeaderDate; QVector<QPair<qint64, double> > blockProcessTime; - bool layerIsVisible; - bool userClosed; + bool layerIsVisible{false}; + bool userClosed{false}; QPropertyAnimation m_animation; void UpdateHeaderSyncLabel(); void UpdateHeaderPresyncLabel(int height, const QDateTime& blockDate); diff --git a/src/qt/notificator.cpp b/src/qt/notificator.cpp index c97644ad7d..88bc33098a 100644 --- a/src/qt/notificator.cpp +++ b/src/qt/notificator.cpp @@ -32,11 +32,7 @@ Notificator::Notificator(const QString &_programName, QSystemTrayIcon *_trayIcon QObject(_parent), parent(_parent), programName(_programName), - mode(None), trayIcon(_trayIcon) -#ifdef USE_DBUS - ,interface(nullptr) -#endif { if(_trayIcon && _trayIcon->supportsMessages()) { diff --git a/src/qt/notificator.h b/src/qt/notificator.h index ac4bbe5a0c..1fd8181a22 100644 --- a/src/qt/notificator.h +++ b/src/qt/notificator.h @@ -61,10 +61,10 @@ private: UserNotificationCenter /**< Use the 10.8+ User Notification Center (Mac only) */ }; QString programName; - Mode mode; + Mode mode{None}; QSystemTrayIcon *trayIcon; #ifdef USE_DBUS - QDBusInterface *interface; + QDBusInterface* interface{nullptr}; void notifyDBus(Class cls, const QString &title, const QString &text, const QIcon &icon, int millisTimeout); #endif diff --git a/src/qt/optionsdialog.cpp b/src/qt/optionsdialog.cpp index 9f53fbf429..53b0c3832b 100644 --- a/src/qt/optionsdialog.cpp +++ b/src/qt/optionsdialog.cpp @@ -31,11 +31,9 @@ #include <QSystemTrayIcon> #include <QTimer> -OptionsDialog::OptionsDialog(QWidget *parent, bool enableWallet) : - QDialog(parent, GUIUtil::dialog_flags), - ui(new Ui::OptionsDialog), - model(nullptr), - mapper(nullptr) +OptionsDialog::OptionsDialog(QWidget* parent, bool enableWallet) + : QDialog(parent, GUIUtil::dialog_flags), + ui(new Ui::OptionsDialog) { ui->setupUi(this); diff --git a/src/qt/optionsdialog.h b/src/qt/optionsdialog.h index 6cf0d1064f..031e4d3163 100644 --- a/src/qt/optionsdialog.h +++ b/src/qt/optionsdialog.h @@ -75,8 +75,8 @@ Q_SIGNALS: private: Ui::OptionsDialog *ui; ClientModel* m_client_model{nullptr}; - OptionsModel *model; - QDataWidgetMapper *mapper; + OptionsModel* model{nullptr}; + QDataWidgetMapper* mapper{nullptr}; }; #endif // BITCOIN_QT_OPTIONSDIALOG_H diff --git a/src/qt/overviewpage.cpp b/src/qt/overviewpage.cpp index 3a4861afd4..c9caec39cf 100644 --- a/src/qt/overviewpage.cpp +++ b/src/qt/overviewpage.cpp @@ -140,8 +140,6 @@ private: OverviewPage::OverviewPage(const PlatformStyle *platformStyle, QWidget *parent) : QWidget(parent), ui(new Ui::OverviewPage), - clientModel(nullptr), - walletModel(nullptr), m_platform_style{platformStyle}, txdelegate(new TxViewDelegate(platformStyle, this)) { diff --git a/src/qt/overviewpage.h b/src/qt/overviewpage.h index c03b87c616..2ca38b78dd 100644 --- a/src/qt/overviewpage.h +++ b/src/qt/overviewpage.h @@ -50,8 +50,8 @@ protected: private: Ui::OverviewPage *ui; - ClientModel *clientModel; - WalletModel *walletModel; + ClientModel* clientModel{nullptr}; + WalletModel* walletModel{nullptr}; bool m_privacy{false}; const PlatformStyle* m_platform_style; diff --git a/src/qt/paymentserver.cpp b/src/qt/paymentserver.cpp index c6aeea065b..3f9d1b040b 100644 --- a/src/qt/paymentserver.cpp +++ b/src/qt/paymentserver.cpp @@ -126,11 +126,8 @@ bool PaymentServer::ipcSendCommandLine() return fResult; } -PaymentServer::PaymentServer(QObject* parent, bool startLocalServer) : - QObject(parent), - saveURIs(true), - uriServer(nullptr), - optionsModel(nullptr) +PaymentServer::PaymentServer(QObject* parent, bool startLocalServer) + : QObject(parent) { // Install global event filter to catch QFileOpenEvents // on Mac: sent when you click bitcoin: links diff --git a/src/qt/paymentserver.h b/src/qt/paymentserver.h index 08b83244ab..63f4faa772 100644 --- a/src/qt/paymentserver.h +++ b/src/qt/paymentserver.h @@ -101,9 +101,9 @@ protected: bool eventFilter(QObject *object, QEvent *event) override; private: - bool saveURIs; // true during startup - QLocalServer* uriServer; - OptionsModel *optionsModel; + bool saveURIs{true}; // true during startup + QLocalServer* uriServer{nullptr}; + OptionsModel* optionsModel{nullptr}; }; #endif // BITCOIN_QT_PAYMENTSERVER_H diff --git a/src/qt/peertablemodel.cpp b/src/qt/peertablemodel.cpp index b4be1b031a..9f47213ed9 100644 --- a/src/qt/peertablemodel.cpp +++ b/src/qt/peertablemodel.cpp @@ -14,10 +14,9 @@ #include <QList> #include <QTimer> -PeerTableModel::PeerTableModel(interfaces::Node& node, QObject* parent) : - QAbstractTableModel(parent), - m_node(node), - timer(nullptr) +PeerTableModel::PeerTableModel(interfaces::Node& node, QObject* parent) + : QAbstractTableModel(parent), + m_node(node) { // set up timer for auto refresh timer = new QTimer(this); diff --git a/src/qt/peertablemodel.h b/src/qt/peertablemodel.h index e51b152538..a0174c3af4 100644 --- a/src/qt/peertablemodel.h +++ b/src/qt/peertablemodel.h @@ -110,7 +110,7 @@ private: /*: Title of Peers Table column which contains the peer's User Agent string. */ tr("User Agent")}; - QTimer *timer; + QTimer* timer{nullptr}; }; #endif // BITCOIN_QT_PEERTABLEMODEL_H diff --git a/src/qt/qrimagewidget.cpp b/src/qt/qrimagewidget.cpp index 05a67117b6..00f928b355 100644 --- a/src/qt/qrimagewidget.cpp +++ b/src/qt/qrimagewidget.cpp @@ -23,8 +23,8 @@ #include <qrencode.h> #endif -QRImageWidget::QRImageWidget(QWidget *parent): - QLabel(parent), contextMenu(nullptr) +QRImageWidget::QRImageWidget(QWidget* parent) + : QLabel(parent) { contextMenu = new QMenu(this); contextMenu->addAction(tr("&Save Image…"), this, &QRImageWidget::saveImage); diff --git a/src/qt/qrimagewidget.h b/src/qt/qrimagewidget.h index 5dbba074a1..d9ca2b899f 100644 --- a/src/qt/qrimagewidget.h +++ b/src/qt/qrimagewidget.h @@ -41,7 +41,7 @@ protected: virtual void contextMenuEvent(QContextMenuEvent *event) override; private: - QMenu *contextMenu; + QMenu* contextMenu{nullptr}; }; #endif // BITCOIN_QT_QRIMAGEWIDGET_H diff --git a/src/qt/qvalidatedlineedit.cpp b/src/qt/qvalidatedlineedit.cpp index bdb302715b..b646332001 100644 --- a/src/qt/qvalidatedlineedit.cpp +++ b/src/qt/qvalidatedlineedit.cpp @@ -7,10 +7,8 @@ #include <qt/bitcoinaddressvalidator.h> #include <qt/guiconstants.h> -QValidatedLineEdit::QValidatedLineEdit(QWidget *parent) : - QLineEdit(parent), - valid(true), - checkValidator(nullptr) +QValidatedLineEdit::QValidatedLineEdit(QWidget* parent) + : QLineEdit(parent) { connect(this, &QValidatedLineEdit::textChanged, this, &QValidatedLineEdit::markValid); } diff --git a/src/qt/qvalidatedlineedit.h b/src/qt/qvalidatedlineedit.h index 4b1139d8f0..b1ae013957 100644 --- a/src/qt/qvalidatedlineedit.h +++ b/src/qt/qvalidatedlineedit.h @@ -25,8 +25,8 @@ protected: void focusOutEvent(QFocusEvent *evt) override; private: - bool valid; - const QValidator *checkValidator; + bool valid{true}; + const QValidator* checkValidator{nullptr}; public Q_SLOTS: void setText(const QString&); diff --git a/src/qt/qvaluecombobox.cpp b/src/qt/qvaluecombobox.cpp index f94486a2f3..c163ba56dc 100644 --- a/src/qt/qvaluecombobox.cpp +++ b/src/qt/qvaluecombobox.cpp @@ -4,8 +4,8 @@ #include <qt/qvaluecombobox.h> -QValueComboBox::QValueComboBox(QWidget *parent) : - QComboBox(parent), role(Qt::UserRole) +QValueComboBox::QValueComboBox(QWidget* parent) + : QComboBox(parent) { connect(this, qOverload<int>(&QComboBox::currentIndexChanged), this, &QValueComboBox::handleSelectionChanged); } diff --git a/src/qt/qvaluecombobox.h b/src/qt/qvaluecombobox.h index bde9c0d1a6..14379dd612 100644 --- a/src/qt/qvaluecombobox.h +++ b/src/qt/qvaluecombobox.h @@ -28,7 +28,7 @@ Q_SIGNALS: void valueChanged(); private: - int role; + int role{Qt::UserRole}; private Q_SLOTS: void handleSelectionChanged(int idx); diff --git a/src/qt/receivecoinsdialog.cpp b/src/qt/receivecoinsdialog.cpp index acbe179463..22eb642ecd 100644 --- a/src/qt/receivecoinsdialog.cpp +++ b/src/qt/receivecoinsdialog.cpp @@ -25,7 +25,6 @@ ReceiveCoinsDialog::ReceiveCoinsDialog(const PlatformStyle *_platformStyle, QWidget *parent) : QDialog(parent, GUIUtil::dialog_flags), ui(new Ui::ReceiveCoinsDialog), - model(nullptr), platformStyle(_platformStyle) { ui->setupUi(this); diff --git a/src/qt/receivecoinsdialog.h b/src/qt/receivecoinsdialog.h index a089b8aa6a..0bb02ebcf2 100644 --- a/src/qt/receivecoinsdialog.h +++ b/src/qt/receivecoinsdialog.h @@ -51,7 +51,7 @@ public Q_SLOTS: private: Ui::ReceiveCoinsDialog *ui; - WalletModel *model; + WalletModel* model{nullptr}; QMenu *contextMenu; QAction* copyLabelAction; QAction* copyMessageAction; diff --git a/src/qt/receiverequestdialog.cpp b/src/qt/receiverequestdialog.cpp index c0135283b1..3453857f98 100644 --- a/src/qt/receiverequestdialog.cpp +++ b/src/qt/receiverequestdialog.cpp @@ -18,10 +18,9 @@ #include <config/bitcoin-config.h> /* for USE_QRCODE */ #endif -ReceiveRequestDialog::ReceiveRequestDialog(QWidget *parent) : - QDialog(parent, GUIUtil::dialog_flags), - ui(new Ui::ReceiveRequestDialog), - model(nullptr) +ReceiveRequestDialog::ReceiveRequestDialog(QWidget* parent) + : QDialog(parent, GUIUtil::dialog_flags), + ui(new Ui::ReceiveRequestDialog) { ui->setupUi(this); GUIUtil::handleCloseWindowShortcut(this); diff --git a/src/qt/receiverequestdialog.h b/src/qt/receiverequestdialog.h index c861680761..d64fee3663 100644 --- a/src/qt/receiverequestdialog.h +++ b/src/qt/receiverequestdialog.h @@ -33,7 +33,7 @@ private Q_SLOTS: private: Ui::ReceiveRequestDialog *ui; - WalletModel *model; + WalletModel* model{nullptr}; SendCoinsRecipient info; }; diff --git a/src/qt/recentrequeststablemodel.h b/src/qt/recentrequeststablemodel.h index cf7cd24ce2..151f8322a8 100644 --- a/src/qt/recentrequeststablemodel.h +++ b/src/qt/recentrequeststablemodel.h @@ -18,11 +18,11 @@ class WalletModel; class RecentRequestEntry { public: - RecentRequestEntry() : nVersion(RecentRequestEntry::CURRENT_VERSION), id(0) { } + RecentRequestEntry() : nVersion(RecentRequestEntry::CURRENT_VERSION) {} static const int CURRENT_VERSION = 1; int nVersion; - int64_t id; + int64_t id{0}; QDateTime date; SendCoinsRecipient recipient; diff --git a/src/qt/sendcoinsdialog.cpp b/src/qt/sendcoinsdialog.cpp index 249e3f2101..1604cad503 100644 --- a/src/qt/sendcoinsdialog.cpp +++ b/src/qt/sendcoinsdialog.cpp @@ -64,11 +64,7 @@ int getIndexForConfTarget(int target) { SendCoinsDialog::SendCoinsDialog(const PlatformStyle *_platformStyle, QWidget *parent) : QDialog(parent, GUIUtil::dialog_flags), ui(new Ui::SendCoinsDialog), - clientModel(nullptr), - model(nullptr), m_coin_control(new CCoinControl), - fNewRecipientAllowed(true), - fFeeMinimized(true), platformStyle(_platformStyle) { ui->setupUi(this); diff --git a/src/qt/sendcoinsdialog.h b/src/qt/sendcoinsdialog.h index 918e3fd19f..2fcdf5b32a 100644 --- a/src/qt/sendcoinsdialog.h +++ b/src/qt/sendcoinsdialog.h @@ -62,12 +62,12 @@ Q_SIGNALS: private: Ui::SendCoinsDialog *ui; - ClientModel *clientModel; - WalletModel *model; + ClientModel* clientModel{nullptr}; + WalletModel* model{nullptr}; std::unique_ptr<wallet::CCoinControl> m_coin_control; std::unique_ptr<WalletModelTransaction> m_current_transaction; - bool fNewRecipientAllowed; - bool fFeeMinimized; + bool fNewRecipientAllowed{true}; + bool fFeeMinimized{true}; const PlatformStyle *platformStyle; // Copy PSBT to clipboard and offer to save it. diff --git a/src/qt/sendcoinsentry.cpp b/src/qt/sendcoinsentry.cpp index 58d8fb69e3..0536635c84 100644 --- a/src/qt/sendcoinsentry.cpp +++ b/src/qt/sendcoinsentry.cpp @@ -22,7 +22,6 @@ SendCoinsEntry::SendCoinsEntry(const PlatformStyle *_platformStyle, QWidget *parent) : QWidget(parent), ui(new Ui::SendCoinsEntry), - model(nullptr), platformStyle(_platformStyle) { ui->setupUi(this); diff --git a/src/qt/sendcoinsentry.h b/src/qt/sendcoinsentry.h index 424809b01e..0edc0d1203 100644 --- a/src/qt/sendcoinsentry.h +++ b/src/qt/sendcoinsentry.h @@ -73,7 +73,7 @@ protected: private: SendCoinsRecipient recipient; Ui::SendCoinsEntry *ui; - WalletModel *model; + WalletModel* model{nullptr}; const PlatformStyle *platformStyle; bool updateLabel(const QString &address); diff --git a/src/qt/signverifymessagedialog.cpp b/src/qt/signverifymessagedialog.cpp index 52213a428a..0e725acb33 100644 --- a/src/qt/signverifymessagedialog.cpp +++ b/src/qt/signverifymessagedialog.cpp @@ -21,7 +21,6 @@ SignVerifyMessageDialog::SignVerifyMessageDialog(const PlatformStyle *_platformStyle, QWidget *parent) : QDialog(parent, GUIUtil::dialog_flags), ui(new Ui::SignVerifyMessageDialog), - model(nullptr), platformStyle(_platformStyle) { ui->setupUi(this); diff --git a/src/qt/signverifymessagedialog.h b/src/qt/signverifymessagedialog.h index 1a3708bf8f..4072707b61 100644 --- a/src/qt/signverifymessagedialog.h +++ b/src/qt/signverifymessagedialog.h @@ -35,7 +35,7 @@ protected: private: Ui::SignVerifyMessageDialog *ui; - WalletModel *model; + WalletModel* model{nullptr}; const PlatformStyle *platformStyle; private Q_SLOTS: diff --git a/src/qt/splashscreen.cpp b/src/qt/splashscreen.cpp index 451a0183c0..096f8a0ded 100644 --- a/src/qt/splashscreen.cpp +++ b/src/qt/splashscreen.cpp @@ -28,7 +28,7 @@ SplashScreen::SplashScreen(const NetworkStyle* networkStyle) - : QWidget(), curAlignment(0) + : QWidget() { // set reference point, paddings int paddingRight = 50; diff --git a/src/qt/splashscreen.h b/src/qt/splashscreen.h index df53f625f5..2356bbacd3 100644 --- a/src/qt/splashscreen.h +++ b/src/qt/splashscreen.h @@ -57,7 +57,7 @@ private: QPixmap pixmap; QString curMessage; QColor curColor; - int curAlignment; + int curAlignment{0}; interfaces::Node* m_node = nullptr; bool m_shutdown = false; diff --git a/src/qt/trafficgraphwidget.cpp b/src/qt/trafficgraphwidget.cpp index 3f27eed3f8..08789c1048 100644 --- a/src/qt/trafficgraphwidget.cpp +++ b/src/qt/trafficgraphwidget.cpp @@ -19,15 +19,10 @@ #define XMARGIN 10 #define YMARGIN 10 -TrafficGraphWidget::TrafficGraphWidget(QWidget *parent) : - QWidget(parent), - timer(nullptr), - fMax(0.0f), - vSamplesIn(), - vSamplesOut(), - nLastBytesIn(0), - nLastBytesOut(0), - clientModel(nullptr) +TrafficGraphWidget::TrafficGraphWidget(QWidget* parent) + : QWidget(parent), + vSamplesIn(), + vSamplesOut() { timer = new QTimer(this); connect(timer, &QTimer::timeout, this, &TrafficGraphWidget::updateRates); diff --git a/src/qt/trafficgraphwidget.h b/src/qt/trafficgraphwidget.h index 1cc8fd4adb..5e5557ec82 100644 --- a/src/qt/trafficgraphwidget.h +++ b/src/qt/trafficgraphwidget.h @@ -37,14 +37,14 @@ public Q_SLOTS: private: void paintPath(QPainterPath &path, QQueue<float> &samples); - QTimer *timer; - float fMax; + QTimer* timer{nullptr}; + float fMax{0.0f}; std::chrono::minutes m_range{0}; QQueue<float> vSamplesIn; QQueue<float> vSamplesOut; - quint64 nLastBytesIn; - quint64 nLastBytesOut; - ClientModel *clientModel; + quint64 nLastBytesIn{0}; + quint64 nLastBytesOut{0}; + ClientModel* clientModel{nullptr}; }; #endif // BITCOIN_QT_TRAFFICGRAPHWIDGET_H diff --git a/src/qt/transactionfilterproxy.cpp b/src/qt/transactionfilterproxy.cpp index 3be7e1a969..3cc0cc839d 100644 --- a/src/qt/transactionfilterproxy.cpp +++ b/src/qt/transactionfilterproxy.cpp @@ -11,14 +11,10 @@ #include <cstdlib> #include <optional> -TransactionFilterProxy::TransactionFilterProxy(QObject *parent) : - QSortFilterProxyModel(parent), - m_search_string(), - typeFilter(ALL_TYPES), - watchOnlyFilter(WatchOnlyFilter_All), - minAmount(0), - limitRows(-1), - showInactive(true) +TransactionFilterProxy::TransactionFilterProxy(QObject* parent) + : QSortFilterProxyModel(parent), + m_search_string(), + typeFilter(ALL_TYPES) { } diff --git a/src/qt/transactionfilterproxy.h b/src/qt/transactionfilterproxy.h index fd9be52842..8e5f72d764 100644 --- a/src/qt/transactionfilterproxy.h +++ b/src/qt/transactionfilterproxy.h @@ -58,10 +58,10 @@ private: std::optional<QDateTime> dateTo; QString m_search_string; quint32 typeFilter; - WatchOnlyFilter watchOnlyFilter; - CAmount minAmount; - int limitRows; - bool showInactive; + WatchOnlyFilter watchOnlyFilter{WatchOnlyFilter_All}; + CAmount minAmount{0}; + int limitRows{-1}; + bool showInactive{true}; }; #endif // BITCOIN_QT_TRANSACTIONFILTERPROXY_H diff --git a/src/qt/transactiontablemodel.cpp b/src/qt/transactiontablemodel.cpp index 339b3058f4..3b32137bd4 100644 --- a/src/qt/transactiontablemodel.cpp +++ b/src/qt/transactiontablemodel.cpp @@ -253,7 +253,6 @@ TransactionTableModel::TransactionTableModel(const PlatformStyle *_platformStyle QAbstractTableModel(parent), walletModel(parent), priv(new TransactionTablePriv(this)), - fProcessingQueuedTransactions(false), platformStyle(_platformStyle) { subscribeToCoreSignals(); diff --git a/src/qt/transactiontablemodel.h b/src/qt/transactiontablemodel.h index f8576edd59..92ba9bf894 100644 --- a/src/qt/transactiontablemodel.h +++ b/src/qt/transactiontablemodel.h @@ -89,7 +89,7 @@ private: std::unique_ptr<interfaces::Handler> m_handler_show_progress; QStringList columns; TransactionTablePriv *priv; - bool fProcessingQueuedTransactions; + bool fProcessingQueuedTransactions{false}; const PlatformStyle *platformStyle; void subscribeToCoreSignals(); diff --git a/src/qt/walletmodel.cpp b/src/qt/walletmodel.cpp index 097b2b0364..cb8491e27a 100644 --- a/src/qt/walletmodel.cpp +++ b/src/qt/walletmodel.cpp @@ -46,10 +46,6 @@ WalletModel::WalletModel(std::unique_ptr<interfaces::Wallet> wallet, ClientModel m_client_model(&client_model), m_node(client_model.node()), optionsModel(client_model.getOptionsModel()), - addressTableModel(nullptr), - transactionTableModel(nullptr), - recentRequestsTableModel(nullptr), - cachedEncryptionStatus(Unencrypted), timer(new QTimer(this)) { fHaveWatchOnly = m_wallet->haveWatchOnly(); diff --git a/src/qt/walletmodel.h b/src/qt/walletmodel.h index 88a6f3a598..604a9e03c8 100644 --- a/src/qt/walletmodel.h +++ b/src/qt/walletmodel.h @@ -181,13 +181,13 @@ private: // (transaction fee, for example) OptionsModel *optionsModel; - AddressTableModel *addressTableModel; - TransactionTableModel *transactionTableModel; - RecentRequestsTableModel *recentRequestsTableModel; + AddressTableModel* addressTableModel{nullptr}; + TransactionTableModel* transactionTableModel{nullptr}; + RecentRequestsTableModel* recentRequestsTableModel{nullptr}; // Cache some values to be able to detect changes interfaces::WalletBalances m_cached_balances; - EncryptionStatus cachedEncryptionStatus; + EncryptionStatus cachedEncryptionStatus{Unencrypted}; QTimer* timer; // Block hash denoting when the last balance update was done. diff --git a/src/qt/walletmodeltransaction.cpp b/src/qt/walletmodeltransaction.cpp index b579dcc0f0..61ccd9dd82 100644 --- a/src/qt/walletmodeltransaction.cpp +++ b/src/qt/walletmodeltransaction.cpp @@ -10,9 +10,8 @@ #include <policy/policy.h> -WalletModelTransaction::WalletModelTransaction(const QList<SendCoinsRecipient> &_recipients) : - recipients(_recipients), - fee(0) +WalletModelTransaction::WalletModelTransaction(const QList<SendCoinsRecipient>& _recipients) + : recipients(_recipients) { } diff --git a/src/qt/walletmodeltransaction.h b/src/qt/walletmodeltransaction.h index 28fb551364..0e6ed8be49 100644 --- a/src/qt/walletmodeltransaction.h +++ b/src/qt/walletmodeltransaction.h @@ -41,7 +41,7 @@ public: private: QList<SendCoinsRecipient> recipients; CTransactionRef wtx; - CAmount fee; + CAmount fee{0}; }; #endif // BITCOIN_QT_WALLETMODELTRANSACTION_H diff --git a/src/qt/walletview.cpp b/src/qt/walletview.cpp index a4cd1a6caf..e62821d5bd 100644 --- a/src/qt/walletview.cpp +++ b/src/qt/walletview.cpp @@ -31,7 +31,6 @@ WalletView::WalletView(WalletModel* wallet_model, const PlatformStyle* _platformStyle, QWidget* parent) : QStackedWidget(parent), - clientModel(nullptr), walletModel(wallet_model), platformStyle(_platformStyle) { diff --git a/src/qt/walletview.h b/src/qt/walletview.h index 301084ffa9..ebceef9cf9 100644 --- a/src/qt/walletview.h +++ b/src/qt/walletview.h @@ -50,7 +50,7 @@ public: void showOutOfSyncWarning(bool fShow); private: - ClientModel *clientModel; + ClientModel* clientModel{nullptr}; //! //! The wallet model represents a bitcoin wallet, and offers access to diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp index fade2ad504..2b39580043 100644 --- a/src/rpc/blockchain.cpp +++ b/src/rpc/blockchain.cpp @@ -445,11 +445,6 @@ 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); @@ -655,7 +650,8 @@ static RPCHelpMan getblock() "If verbosity is 3, returns an Object with information about block <hash> and information about each transaction, including prevout information for inputs (only for unpruned blocks in the current best chain).\n", { {"blockhash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The block hash"}, - {"verbosity|verbose", RPCArg::Type::NUM, RPCArg::Default{1}, "0 for hex-encoded data, 1 for a JSON object, 2 for JSON object with transaction data, and 3 for JSON object with transaction data including prevout information for inputs"}, + {"verbosity|verbose", RPCArg::Type::NUM, RPCArg::Default{1}, "0 for hex-encoded data, 1 for a JSON object, 2 for JSON object with transaction data, and 3 for JSON object with transaction data including prevout information for inputs", + RPCArgOptions{.skip_type_check = true}}, }, { RPCResult{"for verbosity = 0", @@ -873,7 +869,11 @@ 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::DefaultHint{"the current best block"}, "The block hash or height of the target height (only available with coinstatsindex).", RPCArgOptions{.type_str={"", "string or numeric"}}}, + {"hash_or_height", RPCArg::Type::NUM, RPCArg::DefaultHint{"the current best block"}, "The block hash or height of the target height (only available with coinstatsindex).", + RPCArgOptions{ + .skip_type_check = true, + .type_str = {"", "string or numeric"}, + }}, {"use_index", RPCArg::Type::BOOL, RPCArg::Default{true}, "Use coinstatsindex, if available."}, }, RPCResult{ @@ -1743,7 +1743,11 @@ static RPCHelpMan getblockstats() "\nCompute per block statistics for a given window. All amounts are in satoshis.\n" "It won't work for some heights with pruning.\n", { - {"hash_or_height", RPCArg::Type::NUM, RPCArg::Optional::NO, "The block hash or height of the target block", RPCArgOptions{.type_str={"", "string or numeric"}}}, + {"hash_or_height", RPCArg::Type::NUM, RPCArg::Optional::NO, "The block hash or height of the target block", + RPCArgOptions{ + .skip_type_check = true, + .type_str = {"", "string or numeric"}, + }}, {"stats", RPCArg::Type::ARR, RPCArg::DefaultHint{"all values"}, "Values to plot (see result below)", { {"height", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "Selected statistic"}, @@ -2145,8 +2149,6 @@ static RPCHelpMan scantxoutset() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VARR}); - UniValue result(UniValue::VOBJ); if (request.params[0].get_str() == "status") { CoinsViewScanReserver reserver; diff --git a/src/rpc/client.cpp b/src/rpc/client.cpp index b046e1a17b..5fe914f0a1 100644 --- a/src/rpc/client.cpp +++ b/src/rpc/client.cpp @@ -227,11 +227,16 @@ private: public: CRPCConvertTable(); - bool convert(const std::string& method, int idx) { - return (members.count(std::make_pair(method, idx)) > 0); + /** Return arg_value as UniValue, and first parse it if it is a non-string parameter */ + UniValue ArgToUniValue(const std::string& arg_value, const std::string& method, int param_idx) + { + return members.count(std::make_pair(method, param_idx)) > 0 ? ParseNonRFCJSONValue(arg_value) : arg_value; } - bool convert(const std::string& method, const std::string& name) { - return (membersByName.count(std::make_pair(method, name)) > 0); + + /** Return arg_value as UniValue, and first parse it if it is a non-string parameter */ + UniValue ArgToUniValue(const std::string& arg_value, const std::string& method, const std::string& param_name) + { + return membersByName.count(std::make_pair(method, param_name)) > 0 ? ParseNonRFCJSONValue(arg_value) : arg_value; } }; @@ -263,14 +268,7 @@ UniValue RPCConvertValues(const std::string &strMethod, const std::vector<std::s for (unsigned int idx = 0; idx < strParams.size(); idx++) { const std::string& strVal = strParams[idx]; - - if (!rpcCvtTable.convert(strMethod, idx)) { - // insert string value directly - params.push_back(strVal); - } else { - // parse string as JSON, insert bool/number/object/etc. value - params.push_back(ParseNonRFCJSONValue(strVal)); - } + params.push_back(rpcCvtTable.ArgToUniValue(strVal, strMethod, idx)); } return params; @@ -284,7 +282,7 @@ UniValue RPCConvertNamedValues(const std::string &strMethod, const std::vector<s for (const std::string &s: strParams) { size_t pos = s.find('='); if (pos == std::string::npos) { - positional_args.push_back(rpcCvtTable.convert(strMethod, positional_args.size()) ? ParseNonRFCJSONValue(s) : s); + positional_args.push_back(rpcCvtTable.ArgToUniValue(s, strMethod, positional_args.size())); continue; } @@ -294,13 +292,7 @@ UniValue RPCConvertNamedValues(const std::string &strMethod, const std::vector<s // Intentionally overwrite earlier named values with later ones as a // convenience for scripts and command line users that want to merge // options. - if (!rpcCvtTable.convert(strMethod, name)) { - // insert string value directly - params.pushKV(name, value); - } else { - // parse string as JSON, insert bool/number/object/etc. value - params.pushKV(name, ParseNonRFCJSONValue(value)); - } + params.pushKV(name, rpcCvtTable.ArgToUniValue(value, strMethod, name)); } if (!positional_args.empty()) { diff --git a/src/rpc/fees.cpp b/src/rpc/fees.cpp index cd2f6390e5..62396d4c58 100644 --- a/src/rpc/fees.cpp +++ b/src/rpc/fees.cpp @@ -63,8 +63,6 @@ static RPCHelpMan estimatesmartfee() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - RPCTypeCheck(request.params, {UniValue::VNUM, UniValue::VSTR}); - CBlockPolicyEstimator& fee_estimator = EnsureAnyFeeEstimator(request.context); const NodeContext& node = EnsureAnyNodeContext(request.context); const CTxMemPool& mempool = EnsureMemPool(node); @@ -155,8 +153,6 @@ static RPCHelpMan estimaterawfee() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - RPCTypeCheck(request.params, {UniValue::VNUM, UniValue::VNUM}, true); - CBlockPolicyEstimator& fee_estimator = EnsureAnyFeeEstimator(request.context); unsigned int max_target = fee_estimator.HighestTargetTracked(FeeEstimateHorizon::LONG_HALFLIFE); diff --git a/src/rpc/mempool.cpp b/src/rpc/mempool.cpp index 44bff55f96..44f7435a26 100644 --- a/src/rpc/mempool.cpp +++ b/src/rpc/mempool.cpp @@ -61,11 +61,6 @@ static RPCHelpMan sendrawtransaction() }, [&](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."); @@ -147,10 +142,6 @@ static RPCHelpMan testmempoolaccept() }, [&](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, @@ -800,9 +791,6 @@ static RPCHelpMan submitpackage() 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, diff --git a/src/rpc/net.cpp b/src/rpc/net.cpp index d430087358..f0e5b90509 100644 --- a/src/rpc/net.cpp +++ b/src/rpc/net.cpp @@ -114,7 +114,7 @@ static RPCHelpMan getpeerinfo() { {RPCResult::Type::STR, "SERVICE_NAME", "the service name if it is recognised"} }}, - {RPCResult::Type::BOOL, "relaytxes", /*optional=*/true, "Whether we relay transactions to this peer"}, + {RPCResult::Type::BOOL, "relaytxes", "Whether we relay transactions to this peer"}, {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"}, @@ -131,17 +131,17 @@ static RPCHelpMan getpeerinfo() {RPCResult::Type::BOOL, "inbound", "Inbound (true) or Outbound (false)"}, {RPCResult::Type::BOOL, "bip152_hb_to", "Whether we selected peer as (compact blocks) high-bandwidth peer"}, {RPCResult::Type::BOOL, "bip152_hb_from", "Whether peer selected us as (compact blocks) high-bandwidth peer"}, - {RPCResult::Type::NUM, "startingheight", /*optional=*/true, "The starting height (block) of the peer"}, - {RPCResult::Type::NUM, "presynced_headers", /*optional=*/true, "The current height of header pre-synchronization with this peer, or -1 if no low-work sync is in progress"}, - {RPCResult::Type::NUM, "synced_headers", /*optional=*/true, "The last header we have in common with this peer"}, - {RPCResult::Type::NUM, "synced_blocks", /*optional=*/true, "The last block we have in common with this peer"}, - {RPCResult::Type::ARR, "inflight", /*optional=*/true, "", + {RPCResult::Type::NUM, "startingheight", "The starting height (block) of the peer"}, + {RPCResult::Type::NUM, "presynced_headers", "The current height of header pre-synchronization with this peer, or -1 if no low-work sync is in progress"}, + {RPCResult::Type::NUM, "synced_headers", "The last header we have in common with this peer"}, + {RPCResult::Type::NUM, "synced_blocks", "The last block we have in common with this peer"}, + {RPCResult::Type::ARR, "inflight", "", { {RPCResult::Type::NUM, "n", "The heights of blocks we're currently asking from this peer"}, }}, - {RPCResult::Type::BOOL, "addr_relay_enabled", /*optional=*/true, "Whether we participate in address relay with this peer"}, - {RPCResult::Type::NUM, "addr_processed", /*optional=*/true, "The total number of addresses processed, excluding those dropped due to rate limiting"}, - {RPCResult::Type::NUM, "addr_rate_limited", /*optional=*/true, "The total number of addresses dropped due to rate limiting"}, + {RPCResult::Type::BOOL, "addr_relay_enabled", "Whether we participate in address relay with this peer"}, + {RPCResult::Type::NUM, "addr_processed", "The total number of addresses processed, excluding those dropped due to rate limiting"}, + {RPCResult::Type::NUM, "addr_rate_limited", "The total number of addresses dropped due to rate limiting"}, {RPCResult::Type::ARR, "permissions", "Any special permissions that have been granted to this peer", { {RPCResult::Type::STR, "permission_type", Join(NET_PERMISSIONS_DOC, ",\n") + ".\n"}, @@ -205,12 +205,10 @@ static RPCHelpMan getpeerinfo() if (stats.m_mapped_as != 0) { obj.pushKV("mapped_as", uint64_t(stats.m_mapped_as)); } - ServiceFlags services{fStateStats ? statestats.their_services : ServiceFlags::NODE_NONE}; + ServiceFlags services{statestats.their_services}; obj.pushKV("services", strprintf("%016x", services)); obj.pushKV("servicesnames", GetServicesNames(services)); - if (fStateStats) { - obj.pushKV("relaytxes", statestats.m_relay_txs); - } + obj.pushKV("relaytxes", statestats.m_relay_txs); 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)); @@ -225,7 +223,7 @@ static RPCHelpMan getpeerinfo() if (stats.m_min_ping_time < std::chrono::microseconds::max()) { obj.pushKV("minping", Ticks<SecondsDouble>(stats.m_min_ping_time)); } - if (fStateStats && statestats.m_ping_wait > 0s) { + if (statestats.m_ping_wait > 0s) { obj.pushKV("pingwait", Ticks<SecondsDouble>(statestats.m_ping_wait)); } obj.pushKV("version", stats.nVersion); @@ -236,26 +234,24 @@ static RPCHelpMan getpeerinfo() obj.pushKV("inbound", stats.fInbound); obj.pushKV("bip152_hb_to", stats.m_bip152_highbandwidth_to); obj.pushKV("bip152_hb_from", stats.m_bip152_highbandwidth_from); - if (fStateStats) { - obj.pushKV("startingheight", statestats.m_starting_height); - obj.pushKV("presynced_headers", statestats.presync_height); - obj.pushKV("synced_headers", statestats.nSyncHeight); - obj.pushKV("synced_blocks", statestats.nCommonHeight); - UniValue heights(UniValue::VARR); - for (const int height : statestats.vHeightInFlight) { - heights.push_back(height); - } - obj.pushKV("inflight", heights); - 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); + obj.pushKV("startingheight", statestats.m_starting_height); + obj.pushKV("presynced_headers", statestats.presync_height); + obj.pushKV("synced_headers", statestats.nSyncHeight); + obj.pushKV("synced_blocks", statestats.nCommonHeight); + UniValue heights(UniValue::VARR); + for (const int height : statestats.vHeightInFlight) { + heights.push_back(height); } + obj.pushKV("inflight", heights); + 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); UniValue permissions(UniValue::VARR); for (const auto& permission : NetPermissions::ToStrings(stats.m_permission_flags)) { permissions.push_back(permission); } obj.pushKV("permissions", permissions); - obj.pushKV("minfeefilter", fStateStats ? ValueFromAmount(statestats.m_fee_filter_received) : 0); + obj.pushKV("minfeefilter", ValueFromAmount(statestats.m_fee_filter_received)); UniValue sendPerMsgType(UniValue::VOBJ); for (const auto& i : stats.mapSendBytesPerMsgType) { @@ -362,7 +358,6 @@ static RPCHelpMan addconnection() throw std::runtime_error("addconnection is for regression testing (-regtest mode) only."); } - RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VSTR}); const std::string address = request.params[0].get_str(); const std::string conn_type_in{TrimString(request.params[1].get_str())}; ConnectionType conn_type{}; diff --git a/src/rpc/node.cpp b/src/rpc/node.cpp index ab1bc6615f..79b8277968 100644 --- a/src/rpc/node.cpp +++ b/src/rpc/node.cpp @@ -53,7 +53,6 @@ static RPCHelpMan setmocktime() // 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)); @@ -107,8 +106,6 @@ static RPCHelpMan mockscheduler() 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)"); @@ -296,18 +293,18 @@ static RPCHelpMan echo(const std::string& name) "\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, ""}, - }, + { + {"arg0", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, "", RPCArgOptions{.skip_type_check = true}}, + {"arg1", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, "", RPCArgOptions{.skip_type_check = true}}, + {"arg2", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, "", RPCArgOptions{.skip_type_check = true}}, + {"arg3", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, "", RPCArgOptions{.skip_type_check = true}}, + {"arg4", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, "", RPCArgOptions{.skip_type_check = true}}, + {"arg5", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, "", RPCArgOptions{.skip_type_check = true}}, + {"arg6", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, "", RPCArgOptions{.skip_type_check = true}}, + {"arg7", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, "", RPCArgOptions{.skip_type_check = true}}, + {"arg8", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, "", RPCArgOptions{.skip_type_check = true}}, + {"arg9", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, "", RPCArgOptions{.skip_type_check = true}}, + }, RPCResult{RPCResult::Type::ANY, "", "Returns whatever was passed in"}, RPCExamples{""}, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue diff --git a/src/rpc/output_script.cpp b/src/rpc/output_script.cpp index 2ac6d6d76f..911c769e61 100644 --- a/src/rpc/output_script.cpp +++ b/src/rpc/output_script.cpp @@ -195,8 +195,6 @@ static RPCHelpMan getdescriptorinfo() }, [&](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); @@ -247,7 +245,6 @@ static RPCHelpMan deriveaddresses() }, [&](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; diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp index c712536b91..981dead3b8 100644 --- a/src/rpc/rawtransaction.cpp +++ b/src/rpc/rawtransaction.cpp @@ -162,7 +162,7 @@ static std::vector<RPCArg> CreateTxDoc() }, }, }, - }, + RPCArgOptions{.skip_type_check = true}}, {"locktime", RPCArg::Type::NUM, RPCArg::Default{0}, "Raw locktime. Non-0 value also locktime-activates inputs"}, {"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."}, @@ -185,7 +185,8 @@ static RPCHelpMan getrawtransaction() "If verbosity is 2, returns a JSON Object with information about the transaction, including fee and prevout information.", { {"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The transaction id"}, - {"verbosity|verbose", RPCArg::Type::NUM, RPCArg::Default{0}, "0 for hex-encoded data, 1 for a JSON object, and 2 for JSON object with fee and prevout"}, + {"verbosity|verbose", RPCArg::Type::NUM, RPCArg::Default{0}, "0 for hex-encoded data, 1 for a JSON object, and 2 for JSON object with fee and prevout", + RPCArgOptions{.skip_type_check = true}}, {"blockhash", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED_NAMED_ARG, "The block in which to look for the transaction"}, }, { @@ -354,14 +355,6 @@ static RPCHelpMan createrawtransaction() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - RPCTypeCheck(request.params, { - UniValue::VARR, - UniValueType(), // ARR or OBJ, checked later - UniValue::VNUM, - UniValue::VBOOL - }, true - ); - std::optional<bool> rbf; if (!request.params[3].isNull()) { rbf = request.params[3].get_bool(); @@ -397,8 +390,6 @@ static RPCHelpMan decoderawtransaction() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VBOOL}); - CMutableTransaction mtx; bool try_witness = request.params[1].isNull() ? true : request.params[1].get_bool(); @@ -451,8 +442,6 @@ static RPCHelpMan decodescript() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - RPCTypeCheck(request.params, {UniValue::VSTR}); - UniValue r(UniValue::VOBJ); CScript script; if (request.params[0].get_str().size() > 0){ @@ -702,8 +691,6 @@ static RPCHelpMan signrawtransactionwithkey() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VARR, UniValue::VARR, UniValue::VSTR}, true); - 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."); @@ -981,8 +968,6 @@ static RPCHelpMan decodepsbt() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - RPCTypeCheck(request.params, {UniValue::VSTR}); - // Unserialize the transactions PartiallySignedTransaction psbtx; std::string error; @@ -1395,8 +1380,6 @@ static RPCHelpMan combinepsbt() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - RPCTypeCheck(request.params, {UniValue::VARR}, true); - // Unserialize the transactions std::vector<PartiallySignedTransaction> psbtxs; UniValue txs = request.params[0].get_array(); @@ -1450,8 +1433,6 @@ static RPCHelpMan finalizepsbt() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VBOOL}, true); - // Unserialize the transactions PartiallySignedTransaction psbtx; std::string error; @@ -1499,14 +1480,6 @@ static RPCHelpMan createpsbt() [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - RPCTypeCheck(request.params, { - UniValue::VARR, - UniValueType(), // ARR or OBJ, checked later - UniValue::VNUM, - UniValue::VBOOL, - }, true - ); - std::optional<bool> rbf; if (!request.params[3].isNull()) { rbf = request.params[3].get_bool(); @@ -1560,8 +1533,6 @@ static RPCHelpMan converttopsbt() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VBOOL, UniValue::VBOOL}, true); - // parse hex string from parameter CMutableTransaction tx; bool permitsigdata = request.params[1].isNull() ? false : request.params[1].get_bool(); @@ -1623,8 +1594,6 @@ static RPCHelpMan utxoupdatepsbt() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VARR}, true); - // Unserialize the transactions PartiallySignedTransaction psbtx; std::string error; @@ -1714,8 +1683,6 @@ static RPCHelpMan joinpsbts() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - RPCTypeCheck(request.params, {UniValue::VARR}, true); - // Unserialize the transactions std::vector<PartiallySignedTransaction> psbtxs; UniValue txs = request.params[0].get_array(); @@ -1842,8 +1809,6 @@ static RPCHelpMan analyzepsbt() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - RPCTypeCheck(request.params, {UniValue::VSTR}); - // Unserialize the transaction PartiallySignedTransaction psbtx; std::string error; diff --git a/src/rpc/util.cpp b/src/rpc/util.cpp index 53785b1941..9c8b0b5642 100644 --- a/src/rpc/util.cpp +++ b/src/rpc/util.cpp @@ -2,6 +2,7 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. +#include <clientversion.h> #include <consensus/amount.h> #include <key_io.h> #include <outputtype.h> @@ -30,23 +31,6 @@ std::string GetAllOutputTypes() return Join(ret, ", "); } -void RPCTypeCheck(const UniValue& params, - const std::list<UniValueType>& typesExpected, - bool fAllowNull) -{ - unsigned int i = 0; - for (const UniValueType& t : typesExpected) { - if (params.size() <= i) - break; - - const UniValue& v = params[i]; - if (!(fAllowNull && v.isNull())) { - RPCTypeCheckArgument(v, t); - } - i++; - } -} - void RPCTypeCheckArgument(const UniValue& value, const UniValueType& typeExpected) { if (!typeExpected.typeAny && value.type() != typeExpected.type) { @@ -405,6 +389,7 @@ struct Sections { const auto indent = std::string(current_indent, ' '); const auto indent_next = std::string(current_indent + 2, ' '); const bool push_name{outer_type == OuterType::OBJ}; // Dictionary keys must have a name + const bool is_top_level_arg{outer_type == OuterType::NONE}; // True on the first recursion switch (arg.m_type) { case RPCArg::Type::STR_HEX: @@ -413,7 +398,7 @@ struct Sections { case RPCArg::Type::AMOUNT: case RPCArg::Type::RANGE: case RPCArg::Type::BOOL: { - if (outer_type == OuterType::NONE) return; // Nothing more to do for non-recursive types on first recursion + if (is_top_level_arg) return; // Nothing more to do for non-recursive types on first recursion auto left = indent; if (arg.m_opts.type_str.size() != 0 && push_name) { left += "\"" + arg.GetName() + "\": " + arg.m_opts.type_str.at(0); @@ -421,12 +406,12 @@ struct Sections { left += push_name ? arg.ToStringObj(/*oneline=*/false) : arg.ToString(/*oneline=*/false); } left += ","; - PushSection({left, arg.ToDescriptionString()}); + PushSection({left, arg.ToDescriptionString(/*is_named_arg=*/push_name)}); break; } case RPCArg::Type::OBJ: case RPCArg::Type::OBJ_USER_KEYS: { - const auto right = outer_type == OuterType::NONE ? "" : arg.ToDescriptionString(); + const auto right = is_top_level_arg ? "" : arg.ToDescriptionString(/*is_named_arg=*/push_name); PushSection({indent + (push_name ? "\"" + arg.GetName() + "\": " : "") + "{", right}); for (const auto& arg_inner : arg.m_inner) { Push(arg_inner, current_indent + 2, OuterType::OBJ); @@ -434,20 +419,20 @@ struct Sections { if (arg.m_type != RPCArg::Type::OBJ) { PushSection({indent_next + "...", ""}); } - PushSection({indent + "}" + (outer_type != OuterType::NONE ? "," : ""), ""}); + PushSection({indent + "}" + (is_top_level_arg ? "" : ","), ""}); break; } case RPCArg::Type::ARR: { auto left = indent; left += push_name ? "\"" + arg.GetName() + "\": " : ""; left += "["; - const auto right = outer_type == OuterType::NONE ? "" : arg.ToDescriptionString(); + const auto right = is_top_level_arg ? "" : arg.ToDescriptionString(/*is_named_arg=*/push_name); PushSection({left, right}); for (const auto& arg_inner : arg.m_inner) { Push(arg_inner, current_indent + 2, OuterType::ARR); } PushSection({indent_next + "...", ""}); - PushSection({indent + "]" + (outer_type != OuterType::NONE ? "," : ""), ""}); + PushSection({indent + "]" + (is_top_level_arg ? "" : ","), ""}); break; } } // no default case, so the compiler can warn about missing cases @@ -579,9 +564,31 @@ UniValue RPCHelpMan::HandleRequest(const JSONRPCRequest& request) const if (request.mode == JSONRPCRequest::GET_HELP || !IsValidNumArgs(request.params.size())) { throw std::runtime_error(ToString()); } + for (size_t i{0}; i < m_args.size(); ++i) { + m_args.at(i).MatchesType(request.params[i]); + } UniValue ret = m_fun(*this, request); 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); })); + UniValue mismatch{UniValue::VARR}; + for (const auto& res : m_results.m_results) { + UniValue match{res.MatchesType(ret)}; + if (match.isTrue()) { + mismatch.setNull(); + break; + } + mismatch.push_back(match); + } + if (!mismatch.isNull()) { + std::string explain{ + mismatch.empty() ? "no possible results defined" : + mismatch.size() == 1 ? mismatch[0].write(4) : + mismatch.write(4)}; + throw std::runtime_error{ + strprintf("Internal bug detected: RPC call \"%s\" returned incorrect type:\n%s\n%s %s\nPlease report this issue here: %s\n", + m_name, explain, + PACKAGE_NAME, FormatFullVersion(), + PACKAGE_BUGREPORT)}; + } } return ret; } @@ -641,7 +648,7 @@ std::string RPCHelpMan::ToString() const if (i == 0) ret += "\nArguments:\n"; // Push named argument name and description - sections.m_sections.emplace_back(::ToString(i + 1) + ". " + arg.GetFirstName(), arg.ToDescriptionString()); + sections.m_sections.emplace_back(::ToString(i + 1) + ". " + arg.GetFirstName(), arg.ToDescriptionString(/*is_named_arg=*/true)); sections.m_max_pad = std::max(sections.m_max_pad, sections.m_sections.back().m_left.size()); // Recursively push nested args @@ -677,6 +684,44 @@ UniValue RPCHelpMan::GetArgMap() const return arr; } +void RPCArg::MatchesType(const UniValue& request) const +{ + if (m_opts.skip_type_check) return; + if (IsOptional() && request.isNull()) return; + switch (m_type) { + case Type::STR_HEX: + case Type::STR: { + RPCTypeCheckArgument(request, UniValue::VSTR); + return; + } + case Type::NUM: { + RPCTypeCheckArgument(request, UniValue::VNUM); + return; + } + case Type::AMOUNT: { + // VNUM or VSTR, checked inside AmountFromValue() + return; + } + case Type::RANGE: { + // VNUM or VARR, checked inside ParseRange() + return; + } + case Type::BOOL: { + RPCTypeCheckArgument(request, UniValue::VBOOL); + return; + } + case Type::OBJ: + case Type::OBJ_USER_KEYS: { + RPCTypeCheckArgument(request, UniValue::VOBJ); + return; + } + case Type::ARR: { + RPCTypeCheckArgument(request, UniValue::VARR); + return; + } + } // no default case, so the compiler can warn about missing cases +} + std::string RPCArg::GetFirstName() const { return m_names.substr(0, m_names.find("|")); @@ -697,7 +742,7 @@ bool RPCArg::IsOptional() const } } -std::string RPCArg::ToDescriptionString() const +std::string RPCArg::ToDescriptionString(bool is_named_arg) const { std::string ret; ret += "("; @@ -743,14 +788,12 @@ std::string RPCArg::ToDescriptionString() const ret += ", optional, default=" + std::get<RPCArg::Default>(m_fallback).write(); } else { switch (std::get<RPCArg::Optional>(m_fallback)) { + case RPCArg::Optional::OMITTED_NAMED_ARG: // Deprecated alias for OMITTED, can be removed case RPCArg::Optional::OMITTED: { + if (is_named_arg) ret += ", optional"; // Default value is "null" in dicts. Otherwise, // nothing to do. Element is treated as if not present and has no default value break; } - case RPCArg::Optional::OMITTED_NAMED_ARG: { - ret += ", optional"; // Default value is "null" - break; - } case RPCArg::Optional::NO: { ret += ", required"; break; @@ -860,53 +903,77 @@ void RPCResult::ToSections(Sections& sections, const OuterType outer_type, const NONFATAL_UNREACHABLE(); } -bool RPCResult::MatchesType(const UniValue& result) const +static const std::optional<UniValue::VType> ExpectedType(RPCResult::Type type) { - if (m_skip_type_check) { - return true; - } - switch (m_type) { + using Type = RPCResult::Type; + switch (type) { case Type::ELISION: case Type::ANY: { - return true; + return std::nullopt; } case Type::NONE: { - return UniValue::VNULL == result.getType(); + return UniValue::VNULL; } case Type::STR: case Type::STR_HEX: { - return UniValue::VSTR == result.getType(); + return UniValue::VSTR; } case Type::NUM: case Type::STR_AMOUNT: case Type::NUM_TIME: { - return UniValue::VNUM == result.getType(); + return UniValue::VNUM; } case Type::BOOL: { - return UniValue::VBOOL == result.getType(); + return UniValue::VBOOL; } case Type::ARR_FIXED: case Type::ARR: { - if (UniValue::VARR != result.getType()) return false; + return UniValue::VARR; + } + case Type::OBJ_DYN: + case Type::OBJ: { + return UniValue::VOBJ; + } + } // no default case, so the compiler can warn about missing cases + NONFATAL_UNREACHABLE(); +} + +UniValue RPCResult::MatchesType(const UniValue& result) const +{ + if (m_skip_type_check) { + return true; + } + + const auto exp_type = ExpectedType(m_type); + if (!exp_type) return true; // can be any type, so nothing to check + + if (*exp_type != result.getType()) { + return strprintf("returned type is %s, but declared as %s in doc", uvTypeName(result.getType()), uvTypeName(*exp_type)); + } + + if (UniValue::VARR == result.getType()) { + UniValue errors(UniValue::VOBJ); 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; + UniValue match{doc_inner.MatchesType(result.get_array()[i])}; + if (!match.isTrue()) errors.pushKV(strprintf("%d", i), match); } - return true; // empty result array is valid + if (errors.empty()) return true; // empty result array is valid + return errors; } - case Type::OBJ_DYN: - case Type::OBJ: { - if (UniValue::VOBJ != result.getType()) return false; + + if (UniValue::VOBJ == result.getType()) { if (!m_inner.empty() && m_inner.at(0).m_type == Type::ELISION) return true; + UniValue errors(UniValue::VOBJ); 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; - } + UniValue match{doc_inner.MatchesType(result.get_obj()[i])}; + if (!match.isTrue()) errors.pushKV(result.getKeys()[i], match); } - return true; // empty result obj is valid + if (errors.empty()) return true; // empty result obj is valid + return errors; } std::set<std::string> doc_keys; for (const auto& doc_entry : m_inner) { @@ -916,7 +983,7 @@ bool RPCResult::MatchesType(const UniValue& result) const 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 + errors.pushKV(result_entry.first, "key returned that was not in doc"); } } @@ -924,18 +991,18 @@ bool RPCResult::MatchesType(const UniValue& result) const 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 + errors.pushKV(doc_entry.m_key_name, "key missing, despite not being optional in doc"); } continue; } - if (!doc_entry.MatchesType(result_it->second)) { - return false; // wrong type - } + UniValue match{doc_entry.MatchesType(result_it->second)}; + if (!match.isTrue()) errors.pushKV(doc_entry.m_key_name, match); } - return true; + if (errors.empty()) return true; + return errors; } - } // no default case, so the compiler can warn about missing cases - NONFATAL_UNREACHABLE(); + + return true; } void RPCResult::CheckInnerDoc() const diff --git a/src/rpc/util.h b/src/rpc/util.h index 387fee34d3..27363eeb50 100644 --- a/src/rpc/util.h +++ b/src/rpc/util.h @@ -63,13 +63,6 @@ struct UniValueType { }; /** - * Type-check arguments; throws JSONRPCError if wrong type given. Does not check that - * the right number of arguments are passed, just that any passed are the correct type. - */ -void RPCTypeCheck(const UniValue& params, - const std::list<UniValueType>& typesExpected, bool fAllowNull=false); - -/** * Type-check one argument; throws JSONRPCError if wrong type given. */ void RPCTypeCheckArgument(const UniValue& value, const UniValueType& typeExpected); @@ -138,6 +131,7 @@ enum class OuterType { }; struct RPCArgOptions { + bool skip_type_check{false}; std::string oneline_description{}; //!< Should be empty unless it is supposed to override the auto-generated summary line std::vector<std::string> type_str{}; //!< Should be empty unless it is supposed to override the auto-generated type strings. Vector length is either 0 or 2, m_opts.type_str.at(0) will override the type of the value in a key-value pair, m_opts.type_str.at(1) will override the type in the argument description. bool hidden{false}; //!< For testing only @@ -160,21 +154,24 @@ struct RPCArg { /** Required arg */ NO, /** + * The arg is optional for one of two reasons: + * * Optional arg that is a named argument and has a default value of - * `null`. When possible, the default value should be specified. - */ - OMITTED_NAMED_ARG, - /** + * `null`. + * * Optional argument with default value omitted because they are - * implicitly clear. That is, elements in an array or object may not + * implicitly clear. That is, elements in an array may not * exist by default. * When possible, the default value should be specified. */ OMITTED, + OMITTED_NAMED_ARG, // Deprecated alias for OMITTED, can be removed }; + /** Hint for default value */ using DefaultHint = std::string; + /** Default constant value */ using Default = UniValue; - using Fallback = std::variant<Optional, /* hint for default value */ DefaultHint, /* default constant value */ Default>; + using Fallback = std::variant<Optional, DefaultHint, Default>; const std::string m_names; //!< The name of the arg (can be empty for inner args, can contain multiple aliases separated by | for named request arguments) const Type m_type; @@ -184,10 +181,10 @@ struct RPCArg { const RPCArgOptions m_opts; RPCArg( - const std::string name, - const Type type, - const Fallback fallback, - const std::string description, + std::string name, + Type type, + Fallback fallback, + std::string description, RPCArgOptions opts = {}) : m_names{std::move(name)}, m_type{std::move(type)}, @@ -199,11 +196,11 @@ struct RPCArg { } RPCArg( - const std::string name, - const Type type, - const Fallback fallback, - const std::string description, - const std::vector<RPCArg> inner, + std::string name, + Type type, + Fallback fallback, + std::string description, + std::vector<RPCArg> inner, RPCArgOptions opts = {}) : m_names{std::move(name)}, m_type{std::move(type)}, @@ -217,6 +214,9 @@ struct RPCArg { bool IsOptional() const; + /** Check whether the request JSON type matches. */ + void MatchesType(const UniValue& request) const; + /** Return the first of all aliases */ std::string GetFirstName() const; @@ -237,7 +237,7 @@ struct RPCArg { * Return the description string, including the argument type and whether * the argument is required. */ - std::string ToDescriptionString() const; + std::string ToDescriptionString(bool is_named_arg) const; }; struct RPCResult { @@ -266,12 +266,12 @@ struct RPCResult { const std::string m_cond; RPCResult( - const std::string cond, - const Type type, - const std::string m_key_name, - const bool optional, - const std::string description, - const std::vector<RPCResult> inner = {}) + std::string cond, + Type type, + std::string m_key_name, + bool optional, + std::string description, + std::vector<RPCResult> inner = {}) : m_type{std::move(type)}, m_key_name{std::move(m_key_name)}, m_inner{std::move(inner)}, @@ -285,19 +285,19 @@ struct RPCResult { } RPCResult( - const std::string cond, - const Type type, - const std::string m_key_name, - const std::string description, - const std::vector<RPCResult> inner = {}) - : RPCResult{cond, type, m_key_name, false, description, inner} {} + std::string cond, + Type type, + std::string m_key_name, + std::string description, + std::vector<RPCResult> inner = {}) + : RPCResult{std::move(cond), type, std::move(m_key_name), /*optional=*/false, std::move(description), std::move(inner)} {} RPCResult( - const Type type, - const std::string m_key_name, - const bool optional, - const std::string description, - const std::vector<RPCResult> inner = {}, + Type type, + std::string m_key_name, + bool optional, + std::string description, + std::vector<RPCResult> inner = {}, bool skip_type_check = false) : m_type{std::move(type)}, m_key_name{std::move(m_key_name)}, @@ -311,12 +311,12 @@ struct RPCResult { } RPCResult( - const Type type, - const std::string m_key_name, - const std::string description, - const std::vector<RPCResult> inner = {}, + Type type, + std::string m_key_name, + std::string description, + std::vector<RPCResult> inner = {}, bool skip_type_check = false) - : RPCResult{type, m_key_name, false, description, inner, skip_type_check} {} + : RPCResult{type, std::move(m_key_name), /*optional=*/false, std::move(description), std::move(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; @@ -324,8 +324,10 @@ struct RPCResult { std::string ToStringObj() const; /** Return the description string, including the result type. */ std::string ToDescriptionString() const; - /** Check whether the result JSON type matches. */ - bool MatchesType(const UniValue& result) const; + /** Check whether the result JSON type matches. + * Returns true if type matches, or object describing error(s) if not. + */ + UniValue MatchesType(const UniValue& result) const; private: void CheckInnerDoc() const; diff --git a/src/script/descriptor.cpp b/src/script/descriptor.cpp index 864eb8864f..5815a059ae 100644 --- a/src/script/descriptor.cpp +++ b/src/script/descriptor.cpp @@ -1643,7 +1643,7 @@ std::unique_ptr<DescriptorImpl> InferScript(const CScript& script, ParseScriptCo for (const auto& [depth, script, leaf_ver] : *tree) { std::unique_ptr<DescriptorImpl> subdesc; if (leaf_ver == TAPROOT_LEAF_TAPSCRIPT) { - subdesc = InferScript(script, ParseScriptContext::P2TR, provider); + subdesc = InferScript(CScript(script.begin(), script.end()), ParseScriptContext::P2TR, provider); } if (!subdesc) { ok = false; diff --git a/src/script/interpreter.cpp b/src/script/interpreter.cpp index a942ff349b..03b157a847 100644 --- a/src/script/interpreter.cpp +++ b/src/script/interpreter.cpp @@ -1825,9 +1825,20 @@ static bool ExecuteWitnessScript(const Span<const valtype>& stack_span, const CS return true; } -uint256 ComputeTapleafHash(uint8_t leaf_version, const CScript& script) +uint256 ComputeTapleafHash(uint8_t leaf_version, Span<const unsigned char> script) { - return (HashWriter{HASHER_TAPLEAF} << leaf_version << script).GetSHA256(); + return (HashWriter{HASHER_TAPLEAF} << leaf_version << CompactSizeWriter(script.size()) << script).GetSHA256(); +} + +uint256 ComputeTapbranchHash(Span<const unsigned char> a, Span<const unsigned char> b) +{ + HashWriter ss_branch{HASHER_TAPBRANCH}; + if (std::lexicographical_compare(a.begin(), a.end(), b.begin(), b.end())) { + ss_branch << a << b; + } else { + ss_branch << b << a; + } + return ss_branch.GetSHA256(); } uint256 ComputeTaprootMerkleRoot(Span<const unsigned char> control, const uint256& tapleaf_hash) @@ -1839,14 +1850,8 @@ uint256 ComputeTaprootMerkleRoot(Span<const unsigned char> control, const uint25 const int path_len = (control.size() - TAPROOT_CONTROL_BASE_SIZE) / TAPROOT_CONTROL_NODE_SIZE; uint256 k = tapleaf_hash; for (int i = 0; i < path_len; ++i) { - 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; - } else { - ss_branch << node << k; - } - k = ss_branch.GetSHA256(); + k = ComputeTapbranchHash(k, node); } return k; } @@ -1917,18 +1922,18 @@ static bool VerifyWitnessProgram(const CScriptWitness& witness, int witversion, } else { // Script path spending (stack size is >1 after removing optional annex) const valtype& control = SpanPopBack(stack); - const valtype& script_bytes = SpanPopBack(stack); - exec_script = CScript(script_bytes.begin(), script_bytes.end()); + const valtype& script = SpanPopBack(stack); if (control.size() < TAPROOT_CONTROL_BASE_SIZE || control.size() > TAPROOT_CONTROL_MAX_SIZE || ((control.size() - TAPROOT_CONTROL_BASE_SIZE) % TAPROOT_CONTROL_NODE_SIZE) != 0) { return set_error(serror, SCRIPT_ERR_TAPROOT_WRONG_CONTROL_SIZE); } - execdata.m_tapleaf_hash = ComputeTapleafHash(control[0] & TAPROOT_LEAF_MASK, exec_script); + execdata.m_tapleaf_hash = ComputeTapleafHash(control[0] & TAPROOT_LEAF_MASK, script); if (!VerifyTaprootCommitment(control, program, execdata.m_tapleaf_hash)) { return set_error(serror, SCRIPT_ERR_WITNESS_PROGRAM_MISMATCH); } execdata.m_tapleaf_hash_init = true; if ((control[0] & TAPROOT_LEAF_MASK) == TAPROOT_LEAF_TAPSCRIPT) { // Tapscript (leaf version 0xc0) + exec_script = CScript(script.begin(), script.end()); execdata.m_validation_weight_left = ::GetSerializeSize(witness.stack, PROTOCOL_VERSION) + VALIDATION_WEIGHT_OFFSET; execdata.m_validation_weight_left_init = true; return ExecuteWitnessScript(stack, exec_script, flags, SigVersion::TAPSCRIPT, checker, execdata, serror); diff --git a/src/script/interpreter.h b/src/script/interpreter.h index 42282e6e5c..ac1013302d 100644 --- a/src/script/interpreter.h +++ b/src/script/interpreter.h @@ -333,7 +333,10 @@ public: }; /** Compute the BIP341 tapleaf hash from leaf version & script. */ -uint256 ComputeTapleafHash(uint8_t leaf_version, const CScript& script); +uint256 ComputeTapleafHash(uint8_t leaf_version, Span<const unsigned char> script); +/** Compute the BIP341 tapbranch hash from two branches. + * Spans must be 32 bytes each. */ +uint256 ComputeTapbranchHash(Span<const unsigned char> a, Span<const unsigned char> b); /** Compute the BIP341 taproot script tree Merkle root from control block and leaf hash. * Requires control block to have valid length (33 + k*32, with k in {0,1,..,128}). */ uint256 ComputeTaprootMerkleRoot(Span<const unsigned char> control, const uint256& tapleaf_hash); diff --git a/src/script/sign.cpp b/src/script/sign.cpp index 1a8558cd9f..d369d4960d 100644 --- a/src/script/sign.cpp +++ b/src/script/sign.cpp @@ -169,13 +169,14 @@ static bool CreateTaprootScriptSig(const BaseSignatureCreator& creator, Signatur return false; } -static bool SignTaprootScript(const SigningProvider& provider, const BaseSignatureCreator& creator, SignatureData& sigdata, int leaf_version, const CScript& script, std::vector<valtype>& result) +static bool SignTaprootScript(const SigningProvider& provider, const BaseSignatureCreator& creator, SignatureData& sigdata, int leaf_version, Span<const unsigned char> script_bytes, std::vector<valtype>& result) { // Only BIP342 tapscript signing is supported for now. if (leaf_version != TAPROOT_LEAF_TAPSCRIPT) return false; SigVersion sigversion = SigVersion::TAPSCRIPT; - uint256 leaf_hash = (HashWriter{HASHER_TAPLEAF} << uint8_t(leaf_version) << script).GetSHA256(); + uint256 leaf_hash = ComputeTapleafHash(leaf_version, script_bytes); + CScript script = CScript(script_bytes.begin(), script_bytes.end()); // <xonly pubkey> OP_CHECKSIG if (script.size() == 34 && script[33] == OP_CHECKSIG && script[0] == 0x20) { diff --git a/src/script/standard.cpp b/src/script/standard.cpp index 27b9a5c741..7c4a05b6e6 100644 --- a/src/script/standard.cpp +++ b/src/script/standard.cpp @@ -370,12 +370,7 @@ bool IsValidDestination(const CTxDestination& dest) { leaf.merkle_branch.push_back(a.hash); ret.leaves.emplace_back(std::move(leaf)); } - /* Lexicographically sort a and b's hash, and compute parent hash. */ - if (a.hash < b.hash) { - ret.hash = (HashWriter{HASHER_TAPBRANCH} << a.hash << b.hash).GetSHA256(); - } else { - ret.hash = (HashWriter{HASHER_TAPBRANCH} << b.hash << a.hash).GetSHA256(); - } + ret.hash = ComputeTapbranchHash(a.hash, b.hash); return ret; } @@ -443,14 +438,14 @@ void TaprootBuilder::Insert(TaprootBuilder::NodeInfo&& node, int depth) return branch.size() == 0 || (branch.size() == 1 && branch[0]); } -TaprootBuilder& TaprootBuilder::Add(int depth, const CScript& script, int leaf_version, bool track) +TaprootBuilder& TaprootBuilder::Add(int depth, Span<const unsigned char> script, int leaf_version, bool track) { assert((leaf_version & ~TAPROOT_LEAF_MASK) == 0); if (!IsValid()) return *this; /* Construct NodeInfo object with leaf hash and (if track is true) also leaf information. */ NodeInfo node; - node.hash = (HashWriter{HASHER_TAPLEAF} << uint8_t(leaf_version) << script).GetSHA256(); - if (track) node.leaves.emplace_back(LeafInfo{script, leaf_version, {}}); + node.hash = ComputeTapleafHash(leaf_version, script); + if (track) node.leaves.emplace_back(LeafInfo{std::vector<unsigned char>(script.begin(), script.end()), leaf_version, {}}); /* Insert into the branch. */ Insert(std::move(node), depth); return *this; @@ -506,13 +501,13 @@ TaprootSpendData TaprootBuilder::GetSpendData() const return spd; } -std::optional<std::vector<std::tuple<int, CScript, int>>> InferTaprootTree(const TaprootSpendData& spenddata, const XOnlyPubKey& output) +std::optional<std::vector<std::tuple<int, std::vector<unsigned char>, int>>> InferTaprootTree(const TaprootSpendData& spenddata, const XOnlyPubKey& output) { // Verify that the output matches the assumed Merkle root and internal key. auto tweak = spenddata.internal_key.CreateTapTweak(spenddata.merkle_root.IsNull() ? nullptr : &spenddata.merkle_root); if (!tweak || tweak->first != output) return std::nullopt; // If the Merkle root is 0, the tree is empty, and we're done. - std::vector<std::tuple<int, CScript, int>> ret; + std::vector<std::tuple<int, std::vector<unsigned char>, int>> ret; if (spenddata.merkle_root.IsNull()) return ret; /** Data structure to represent the nodes of the tree we're going to build. */ @@ -523,7 +518,7 @@ std::optional<std::vector<std::tuple<int, CScript, int>>> InferTaprootTree(const std::unique_ptr<TreeNode> sub[2]; /** If this is known to be a leaf node, a pointer to the (script, leaf_ver) pair. * nullptr otherwise. */ - const std::pair<CScript, int>* leaf = nullptr; + const std::pair<std::vector<unsigned char>, int>* leaf = nullptr; /** Whether or not this node has been explored (is known to be a leaf, or known to have children). */ bool explored = false; /** Whether or not this node is an inner node (unknown until explored = true). */ @@ -607,7 +602,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() && - (HashWriter{HASHER_TAPBRANCH} << node.sub[1]->hash << node.sub[1]->hash).GetSHA256() == node.hash) { + ComputeTapbranchHash(node.sub[1]->hash, node.sub[1]->hash) == 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 @@ -641,10 +636,10 @@ 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 +std::vector<std::tuple<uint8_t, uint8_t, std::vector<unsigned char>>> TaprootBuilder::GetTreeTuples() const { assert(IsComplete()); - std::vector<std::tuple<uint8_t, uint8_t, CScript>> tuples; + std::vector<std::tuple<uint8_t, uint8_t, std::vector<unsigned char>>> tuples; if (m_branch.size()) { const auto& leaves = m_branch[0]->leaves; for (const auto& leaf : leaves) { diff --git a/src/script/standard.h b/src/script/standard.h index f08258af4f..18cf5c8c88 100644 --- a/src/script/standard.h +++ b/src/script/standard.h @@ -217,7 +217,7 @@ struct TaprootSpendData * inference can reconstruct the full tree. Within each set, the control * blocks are sorted by size, so that the signing logic can easily * prefer the cheapest one. */ - std::map<std::pair<CScript, int>, std::set<std::vector<unsigned char>, ShortestVectorFirstComparator>> scripts; + std::map<std::pair<std::vector<unsigned char>, int>, std::set<std::vector<unsigned char>, ShortestVectorFirstComparator>> scripts; /** Merge other TaprootSpendData (for the same scriptPubKey) into this. */ void Merge(TaprootSpendData other); }; @@ -229,7 +229,7 @@ private: /** Information about a tracked leaf in the Merkle tree. */ struct LeafInfo { - CScript script; //!< The script. + std::vector<unsigned char> script; //!< The script. int leaf_version; //!< The leaf version for that script. std::vector<uint256> merkle_branch; //!< The hashing partners above this leaf. }; @@ -296,7 +296,7 @@ public: /** Add a new script at a certain depth in the tree. Add() operations must be called * in depth-first traversal order of binary tree. If track is true, it will be included in * the GetSpendData() output. */ - TaprootBuilder& Add(int depth, const CScript& script, int leaf_version, bool track = true); + TaprootBuilder& Add(int depth, Span<const unsigned char> script, int leaf_version, bool track = true); /** Like Add(), but for a Merkle node with a given hash to the tree. */ TaprootBuilder& AddOmitted(int depth, const uint256& hash); /** Finalize the construction. Can only be called when IsComplete() is true. @@ -314,7 +314,7 @@ public: /** 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; + std::vector<std::tuple<uint8_t, uint8_t, std::vector<unsigned char>>> GetTreeTuples() const; /** Returns true if there are any tapscripts */ bool HasScripts() const { return !m_branch.empty(); } }; @@ -325,6 +325,6 @@ public: * std::nullopt is returned. Otherwise, a vector of (depth, script, leaf_ver) tuples is * returned, corresponding to a depth-first traversal of the script tree. */ -std::optional<std::vector<std::tuple<int, CScript, int>>> InferTaprootTree(const TaprootSpendData& spenddata, const XOnlyPubKey& output); +std::optional<std::vector<std::tuple<int, std::vector<unsigned char>, int>>> InferTaprootTree(const TaprootSpendData& spenddata, const XOnlyPubKey& output); #endif // BITCOIN_SCRIPT_STANDARD_H diff --git a/src/support/lockedpool.cpp b/src/support/lockedpool.cpp index fb59324f7a..8b05743330 100644 --- a/src/support/lockedpool.cpp +++ b/src/support/lockedpool.cpp @@ -19,6 +19,9 @@ #endif #include <algorithm> +#include <limits> +#include <stdexcept> +#include <utility> #ifdef ARENA_DEBUG #include <iomanip> #include <iostream> diff --git a/src/support/lockedpool.h b/src/support/lockedpool.h index 03e4e371a3..66fbc218ab 100644 --- a/src/support/lockedpool.h +++ b/src/support/lockedpool.h @@ -5,11 +5,11 @@ #ifndef BITCOIN_SUPPORT_LOCKEDPOOL_H #define BITCOIN_SUPPORT_LOCKEDPOOL_H -#include <stdint.h> +#include <cstddef> #include <list> #include <map> -#include <mutex> #include <memory> +#include <mutex> #include <unordered_map> /** diff --git a/src/test/fuzz/process_message.cpp b/src/test/fuzz/process_message.cpp index 731e0d22e0..0a7924f226 100644 --- a/src/test/fuzz/process_message.cpp +++ b/src/test/fuzz/process_message.cpp @@ -56,7 +56,9 @@ void initialize_process_message() { Assert(GetNumMsgTypes() == getAllNetMessageTypes().size()); // If this fails, add or remove the message type below - static const auto testing_setup = MakeNoLogFileContext<const TestingSetup>(); + static const auto testing_setup = MakeNoLogFileContext<const TestingSetup>( + /*chain_name=*/CBaseChainParams::REGTEST, + /*extra_args=*/{"-txreconciliation"}); g_setup = testing_setup.get(); for (int i = 0; i < 2 * COINBASE_MATURITY; i++) { MineBlock(g_setup->m_node, CScript() << OP_TRUE); diff --git a/src/test/fuzz/process_messages.cpp b/src/test/fuzz/process_messages.cpp index 465184d57d..96339743ba 100644 --- a/src/test/fuzz/process_messages.cpp +++ b/src/test/fuzz/process_messages.cpp @@ -23,7 +23,9 @@ const TestingSetup* g_setup; void initialize_process_messages() { - static const auto testing_setup = MakeNoLogFileContext<const TestingSetup>(); + static const auto testing_setup = MakeNoLogFileContext<const TestingSetup>( + /*chain_name=*/CBaseChainParams::REGTEST, + /*extra_args=*/{"-txreconciliation"}); g_setup = testing_setup.get(); for (int i = 0; i < 2 * COINBASE_MATURITY; i++) { MineBlock(g_setup->m_node, CScript() << OP_TRUE); diff --git a/src/test/script_standard_tests.cpp b/src/test/script_standard_tests.cpp index 88df34ffe6..7bebadf224 100644 --- a/src/test/script_standard_tests.cpp +++ b/src/test/script_standard_tests.cpp @@ -400,12 +400,11 @@ BOOST_AUTO_TEST_CASE(bip341_spk_test_vectors) for (const auto& vec : vectors.getValues()) { TaprootBuilder spktest; - std::map<std::pair<CScript, int>, int> scriptposes; + std::map<std::pair<std::vector<unsigned char>, int>, int> scriptposes; std::function<void (const UniValue&, int)> parse_tree = [&](const UniValue& node, int depth) { if (node.isNull()) return; if (node.isObject()) { - auto script_bytes = ParseHex(node["script"].get_str()); - CScript script(script_bytes.begin(), script_bytes.end()); + auto script = ParseHex(node["script"].get_str()); int idx = node["id"].getInt<int>(); int leaf_version = node["leafVersion"].getInt<int>(); scriptposes[{script, leaf_version}] = idx; diff --git a/src/test/script_tests.cpp b/src/test/script_tests.cpp index 472cba2aac..d4cede8f7c 100644 --- a/src/test/script_tests.cpp +++ b/src/test/script_tests.cpp @@ -1817,7 +1817,24 @@ BOOST_AUTO_TEST_CASE(bip341_keypath_test_vectors) } } +} + +BOOST_AUTO_TEST_CASE(compute_tapbranch) +{ + uint256 hash1 = uint256S("8ad69ec7cf41c2a4001fd1f738bf1e505ce2277acdcaa63fe4765192497f47a7"); + uint256 hash2 = uint256S("f224a923cd0021ab202ab139cc56802ddb92dcfc172b9212261a539df79a112a"); + uint256 result = uint256S("a64c5b7b943315f9b805d7a7296bedfcfd08919270a1f7a1466e98f8693d8cd9"); + BOOST_CHECK_EQUAL(ComputeTapbranchHash(hash1, hash2), result); +} + +BOOST_AUTO_TEST_CASE(compute_tapleaf) +{ + const uint8_t script[6] = {'f','o','o','b','a','r'}; + uint256 tlc0 = uint256S("edbc10c272a1215dcdcc11d605b9027b5ad6ed97cd45521203f136767b5b9c06"); + uint256 tlc2 = uint256S("8b5c4f90ae6bf76e259dbef5d8a59df06359c391b59263741b25eca76451b27a"); + BOOST_CHECK_EQUAL(ComputeTapleafHash(0xc0, Span(script)), tlc0); + BOOST_CHECK_EQUAL(ComputeTapleafHash(0xc2, Span(script)), tlc2); } BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/streams_tests.cpp b/src/test/streams_tests.cpp index dce230ac10..03117f76ae 100644 --- a/src/test/streams_tests.cpp +++ b/src/test/streams_tests.cpp @@ -500,4 +500,18 @@ BOOST_AUTO_TEST_CASE(streams_buffered_file_rand) fs::remove(streams_test_filename); } +BOOST_AUTO_TEST_CASE(streams_hashed) +{ + CDataStream stream(SER_NETWORK, INIT_PROTO_VERSION); + HashedSourceWriter hash_writer{stream}; + const std::string data{"bitcoin"}; + hash_writer << data; + + CHashVerifier hash_verifier{&stream}; + std::string result; + hash_verifier >> result; + BOOST_CHECK_EQUAL(data, result); + BOOST_CHECK_EQUAL(hash_writer.GetHash(), hash_verifier.GetHash()); +} + BOOST_AUTO_TEST_SUITE_END() diff --git a/src/wallet/rpc/backup.cpp b/src/wallet/rpc/backup.cpp index 64253789ec..ab46706084 100644 --- a/src/wallet/rpc/backup.cpp +++ b/src/wallet/rpc/backup.cpp @@ -1330,8 +1330,6 @@ RPCHelpMan importmulti() // the user could have gotten from another RPC command prior to now wallet.BlockUntilSyncedToCurrentChain(); - RPCTypeCheck(mainRequest.params, {UniValue::VARR, UniValue::VOBJ}); - EnsureLegacyScriptPubKeyMan(*pwallet, true); const UniValue& requests = mainRequest.params[0]; @@ -1652,8 +1650,6 @@ RPCHelpMan importdescriptors() throw JSONRPCError(RPC_WALLET_ERROR, "importdescriptors is not available for non-descriptor wallets"); } - RPCTypeCheck(main_request.params, {UniValue::VARR, UniValue::VOBJ}); - WalletRescanReserver reserver(*pwallet); if (!reserver.reserve()) { throw JSONRPCError(RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort existing rescan or wait."); diff --git a/src/wallet/rpc/spend.cpp b/src/wallet/rpc/spend.cpp index 0d25d4fe2b..f7fd6e479f 100644 --- a/src/wallet/rpc/spend.cpp +++ b/src/wallet/rpc/spend.cpp @@ -314,10 +314,13 @@ RPCHelpMan sendtoaddress() RPCHelpMan sendmany() { return RPCHelpMan{"sendmany", - "\nSend multiple times. Amounts are double-precision floating point numbers." + + "Send multiple times. Amounts are double-precision floating point numbers." + HELP_REQUIRING_PASSPHRASE, { - {"dummy", RPCArg::Type::STR, RPCArg::Optional::NO, "Must be set to \"\" for backwards compatibility.", RPCArgOptions{.oneline_description="\"\""}}, + {"dummy", RPCArg::Type::STR, RPCArg::Default{"\"\""}, "Must be set to \"\" for backwards compatibility.", + RPCArgOptions{ + .oneline_description = "\"\"", + }}, {"amounts", RPCArg::Type::OBJ_USER_KEYS, RPCArg::Optional::NO, "The addresses and amounts", { {"address", RPCArg::Type::AMOUNT, RPCArg::Optional::NO, "The bitcoin address is the key, the numeric amount (can be string) in " + CURRENCY_UNIT + " is the value"}, @@ -798,7 +801,10 @@ RPCHelpMan fundrawtransaction() }, }, FundTxDoc()), - RPCArgOptions{.oneline_description="options"}}, + RPCArgOptions{ + .skip_type_check = true, + .oneline_description = "options", + }}, {"iswitness", RPCArg::Type::BOOL, RPCArg::DefaultHint{"depends on heuristic tests"}, "Whether the transaction hex is a serialized witness transaction.\n" "If iswitness is not present, heuristic tests will be used in decoding.\n" "If true, only witness deserialization will be tried.\n" @@ -830,8 +836,6 @@ RPCHelpMan fundrawtransaction() std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); if (!pwallet) return UniValue::VNULL; - RPCTypeCheck(request.params, {UniValue::VSTR, UniValueType(), UniValue::VBOOL}); - // parse hex string from parameter CMutableTransaction tx; bool try_witness = request.params[2].isNull() ? true : request.params[2].get_bool(); @@ -920,8 +924,6 @@ RPCHelpMan signrawtransactionwithwallet() const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request); if (!pwallet) return UniValue::VNULL; - RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VARR, UniValue::VSTR}, true); - 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."); @@ -1021,7 +1023,6 @@ static RPCHelpMan bumpfee_helper(std::string method_name) throw JSONRPCError(RPC_WALLET_ERROR, "bumpfee is not available with wallets that have private keys disabled. Use psbtbumpfee instead."); } - RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VOBJ}); uint256 hash(ParseHashV(request.params[0], "txid")); CCoinControl coin_control; @@ -1155,7 +1156,7 @@ RPCHelpMan send() }, }, }, - }, + RPCArgOptions{.skip_type_check = true}}, {"conf_target", RPCArg::Type::NUM, RPCArg::DefaultHint{"wallet -txconfirmtarget"}, "Confirmation target in blocks"}, {"estimate_mode", RPCArg::Type::STR, RPCArg::Default{"unset"}, "The fee estimate mode, must be one of (case insensitive):\n" "\"" + FeeModes("\"\n\"") + "\""}, @@ -1227,15 +1228,6 @@ RPCHelpMan send() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - RPCTypeCheck(request.params, { - UniValueType(), // outputs (ARR or OBJ, checked later) - 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; @@ -1338,15 +1330,6 @@ RPCHelpMan sendall() }, [&](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 @@ -1561,8 +1544,6 @@ RPCHelpMan walletprocesspsbt() // the user could have gotten from another RPC command prior to now wallet.BlockUntilSyncedToCurrentChain(); - RPCTypeCheck(request.params, {UniValue::VSTR}); - // Unserialize the transaction PartiallySignedTransaction psbtx; std::string error; @@ -1637,7 +1618,7 @@ RPCHelpMan walletcreatefundedpsbt() }, }, }, - }, + RPCArgOptions{.skip_type_check = true}}, {"locktime", RPCArg::Type::NUM, RPCArg::Default{0}, "Raw locktime. Non-0 value also locktime-activates inputs"}, {"options", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED_NAMED_ARG, "", Cat<std::vector<RPCArg>>( @@ -1690,15 +1671,6 @@ RPCHelpMan walletcreatefundedpsbt() // the user could have gotten from another RPC command prior to now wallet.BlockUntilSyncedToCurrentChain(); - RPCTypeCheck(request.params, { - UniValue::VARR, - UniValueType(), // ARR or OBJ, checked later - UniValue::VNUM, - UniValue::VOBJ, - UniValue::VBOOL - }, true - ); - UniValue options{request.params[3].isNull() ? UniValue::VOBJ : request.params[3]}; CAmount fee; diff --git a/src/wallet/rpc/wallet.cpp b/src/wallet/rpc/wallet.cpp index aeab9b7634..63be95fdd3 100644 --- a/src/wallet/rpc/wallet.cpp +++ b/src/wallet/rpc/wallet.cpp @@ -568,8 +568,6 @@ static RPCHelpMan upgradewallet() std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); if (!pwallet) return UniValue::VNULL; - RPCTypeCheck(request.params, {UniValue::VNUM}, true); - EnsureWalletIsUnlocked(*pwallet); int version = 0; @@ -637,8 +635,6 @@ RPCHelpMan simulaterawtransaction() 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); diff --git a/src/wallet/spend.cpp b/src/wallet/spend.cpp index 8bb970936b..e66ff8c97c 100644 --- a/src/wallet/spend.cpp +++ b/src/wallet/spend.cpp @@ -362,67 +362,45 @@ CAmount GetAvailableBalance(const CWallet& wallet, const CCoinControl* coinContr return AvailableCoins(wallet, coinControl).GetTotalAmount(); } -const CTxOut& FindNonChangeParentOutput(const CWallet& wallet, const CTransaction& tx, int output) +const CTxOut& FindNonChangeParentOutput(const CWallet& wallet, const COutPoint& outpoint) { AssertLockHeld(wallet.cs_wallet); - const CTransaction* ptx = &tx; - int n = output; + const CWalletTx* wtx{Assert(wallet.GetWalletTx(outpoint.hash))}; + + const CTransaction* ptx = wtx->tx.get(); + int n = outpoint.n; while (OutputIsChange(wallet, ptx->vout[n]) && ptx->vin.size() > 0) { const COutPoint& prevout = ptx->vin[0].prevout; - auto it = wallet.mapWallet.find(prevout.hash); - if (it == wallet.mapWallet.end() || it->second.tx->vout.size() <= prevout.n || - !wallet.IsMine(it->second.tx->vout[prevout.n])) { + const CWalletTx* it = wallet.GetWalletTx(prevout.hash); + if (!it || it->tx->vout.size() <= prevout.n || + !wallet.IsMine(it->tx->vout[prevout.n])) { break; } - ptx = it->second.tx.get(); + ptx = it->tx.get(); n = prevout.n; } 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; - for (COutput& coin : AvailableCoinsListUnspent(wallet).All()) { + CCoinControl coin_control; + // Include watch-only for LegacyScriptPubKeyMan wallets without private keys + coin_control.fAllowWatchOnly = wallet.GetLegacyScriptPubKeyMan() && wallet.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS); + CoinFilterParams coins_params; + coins_params.only_spendable = false; + coins_params.skip_locked = false; + for (const COutput& coin : AvailableCoins(wallet, &coin_control, /*feerate=*/std::nullopt, coins_params).All()) { CTxDestination 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)); + result[address].emplace_back(coin); } } - - std::vector<COutPoint> lockedCoins; - wallet.ListLockedCoins(lockedCoins); - // Include watch-only for LegacyScriptPubKeyMan wallets without private keys - const bool include_watch_only = wallet.GetLegacyScriptPubKeyMan() && wallet.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS); - const isminetype is_mine_filter = include_watch_only ? ISMINE_WATCH_ONLY : ISMINE_SPENDABLE; - for (const COutPoint& output : lockedCoins) { - auto it = wallet.mapWallet.find(output.hash); - if (it != wallet.mapWallet.end()) { - 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, *wtx.tx, output.n).scriptPubKey, address)) { - const auto out = wtx.tx->vout.at(output.n); - result[address].emplace_back( - 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)); - } - } - } - } - return result; } diff --git a/src/wallet/spend.h b/src/wallet/spend.h index 5ffdd11813..d8da556d29 100644 --- a/src/wallet/spend.h +++ b/src/wallet/spend.h @@ -99,7 +99,6 @@ 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); /** diff --git a/test/functional/feature_notifications.py b/test/functional/feature_notifications.py index 32fea18f37..8cb633d454 100755 --- a/test/functional/feature_notifications.py +++ b/test/functional/feature_notifications.py @@ -31,7 +31,7 @@ class NotificationsTest(BitcoinTestFramework): self.num_nodes = 2 self.setup_clean_chain = True # The experimental syscall sandbox feature (-sandbox) is not compatible with -alertnotify, - # -blocknotify or -walletnotify (which all invoke execve). + # -blocknotify, -walletnotify or -shutdownnotify (which all invoke execve). self.disable_syscall_sandbox = True def setup_network(self): @@ -39,14 +39,18 @@ class NotificationsTest(BitcoinTestFramework): self.alertnotify_dir = os.path.join(self.options.tmpdir, "alertnotify") self.blocknotify_dir = os.path.join(self.options.tmpdir, "blocknotify") self.walletnotify_dir = os.path.join(self.options.tmpdir, "walletnotify") + self.shutdownnotify_dir = os.path.join(self.options.tmpdir, "shutdownnotify") + self.shutdownnotify_file = os.path.join(self.shutdownnotify_dir, "shutdownnotify.txt") os.mkdir(self.alertnotify_dir) os.mkdir(self.blocknotify_dir) os.mkdir(self.walletnotify_dir) + os.mkdir(self.shutdownnotify_dir) # -alertnotify and -blocknotify on node0, walletnotify on node1 self.extra_args = [[ f"-alertnotify=echo > {os.path.join(self.alertnotify_dir, '%s')}", f"-blocknotify=echo > {os.path.join(self.blocknotify_dir, '%s')}", + f"-shutdownnotify=echo > {self.shutdownnotify_file}", ], [ f"-walletnotify=echo %h_%b > {os.path.join(self.walletnotify_dir, notify_outputname('%w', '%s'))}", ]] @@ -162,6 +166,10 @@ class NotificationsTest(BitcoinTestFramework): # TODO: add test for `-alertnotify` large fork notifications + self.log.info("test -shutdownnotify") + self.stop_nodes() + self.wait_until(lambda: os.path.isfile(self.shutdownnotify_file), timeout=10) + def expect_wallet_notify(self, tx_details): self.wait_until(lambda: len(os.listdir(self.walletnotify_dir)) >= len(tx_details), timeout=10) # Should have no more and no less files than expected diff --git a/test/functional/feature_rbf.py b/test/functional/feature_rbf.py index 0a84a66a8f..947d2e8273 100755 --- a/test/functional/feature_rbf.py +++ b/test/functional/feature_rbf.py @@ -392,11 +392,11 @@ class ReplaceByFeeTest(BitcoinTestFramework): 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) # Clear mempools to avoid cross-node sync failure. for node in self.nodes: self.generate(node, 1) + normal_node = self.nodes[1] + wallet = MiniWallet(normal_node) # 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 diff --git a/test/functional/mempool_limit.py b/test/functional/mempool_limit.py index 07636439d0..d38a37f952 100755 --- a/test/functional/mempool_limit.py +++ b/test/functional/mempool_limit.py @@ -25,7 +25,6 @@ class MempoolLimitTest(BitcoinTestFramework): self.extra_args = [[ "-datacarriersize=100000", "-maxmempool=5", - "-spendzeroconfchange=0", ]] self.supports_cli = False diff --git a/test/functional/mempool_package_limits.py b/test/functional/mempool_package_limits.py index 47b7be7d88..63f5a3e3d3 100755 --- a/test/functional/mempool_package_limits.py +++ b/test/functional/mempool_package_limits.py @@ -45,7 +45,7 @@ class MempoolPackageLimitsTest(BitcoinTestFramework): assert_equal(0, node.getmempoolinfo()["size"]) chain_hex = [] - chaintip_utxo = self.wallet.send_self_transfer_chain(from_node=node, chain_length=mempool_count) + chaintip_utxo = self.wallet.send_self_transfer_chain(from_node=node, chain_length=mempool_count)[-1]["new_utxo"] # in-package transactions for _ in range(package_count): tx = self.wallet.create_self_transfer(utxo_to_spend=chaintip_utxo) @@ -100,13 +100,13 @@ class MempoolPackageLimitsTest(BitcoinTestFramework): package_hex = [] # 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]) + chain_a_tip_utxo = self.wallet.send_self_transfer_chain(from_node=node, chain_length=11, utxo_to_spend=m1_utxos[0])[-1]["new_utxo"] # 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]) + chain_b_tip_utxo = self.wallet.send_self_transfer_chain(from_node=node, chain_length=12, utxo_to_spend=m1_utxos[1])[-1]["new_utxo"] # Pb pb_hex = self.wallet.create_self_transfer(utxo_to_spend=chain_b_tip_utxo)["hex"] package_hex.append(pb_hex) @@ -145,7 +145,7 @@ class MempoolPackageLimitsTest(BitcoinTestFramework): m1_utxos = self.wallet.send_self_transfer_multi(from_node=node, num_outputs=2)['new_utxos'] # Chain M2...M24 - self.wallet.send_self_transfer_chain(from_node=node, chain_length=23, utxo_to_spend=m1_utxos[0]) + self.wallet.send_self_transfer_chain(from_node=node, chain_length=23, utxo_to_spend=m1_utxos[0])[-1]["new_utxo"] # P1 p1_tx = self.wallet.create_self_transfer(utxo_to_spend=m1_utxos[1]) @@ -191,7 +191,7 @@ class MempoolPackageLimitsTest(BitcoinTestFramework): # Two chains of 13 transactions each for _ in range(2): - chain_tip_utxo = self.wallet.send_self_transfer_chain(from_node=node, chain_length=12) + chain_tip_utxo = self.wallet.send_self_transfer_chain(from_node=node, chain_length=12)[-1]["new_utxo"] # Save the 13th transaction for the package tx = self.wallet.create_self_transfer(utxo_to_spend=chain_tip_utxo) package_hex.append(tx["hex"]) @@ -234,7 +234,7 @@ class MempoolPackageLimitsTest(BitcoinTestFramework): 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): - chaintip_utxo = self.wallet.send_self_transfer_chain(from_node=node, chain_length=12) + chaintip_utxo = self.wallet.send_self_transfer_chain(from_node=node, chain_length=12)[-1]["new_utxo"] # last 2 transactions will be the parents of Pc pc_parent_utxos.append(chaintip_utxo) diff --git a/test/functional/mempool_packages.py b/test/functional/mempool_packages.py index c89528101e..0387282862 100755 --- a/test/functional/mempool_packages.py +++ b/test/functional/mempool_packages.py @@ -6,7 +6,6 @@ from decimal import Decimal -from test_framework.blocktools import COINBASE_MATURITY from test_framework.messages import ( COIN, DEFAULT_ANCESTOR_LIMIT, @@ -17,9 +16,8 @@ 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 # custom limits for node1 CUSTOM_ANCESTOR_LIMIT = 5 @@ -45,43 +43,33 @@ class MempoolPackagesTest(BitcoinTestFramework): ], ] - def skip_test_if_missing_module(self): - self.skip_if_no_wallet() - def run_test(self): - # Mine some blocks and have them mature. + self.wallet = MiniWallet(self.nodes[0]) + self.wallet.rescan_utxos() + + if self.is_specified_wallet_compiled(): + self.nodes[0].createwallet("watch_wallet", disable_private_keys=True) + self.nodes[0].importaddress(self.wallet.get_address()) + peer_inv_store = self.nodes[0].add_p2p_connection(P2PTxInvStore()) # keep track of invs - 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'] - assert 'ancestorcount' not in utxo[0] - assert 'ancestorsize' not in utxo[0] - assert 'ancestorfees' not in utxo[0] - - fee = Decimal("0.0001") + # DEFAULT_ANCESTOR_LIMIT transactions off a confirmed tx should be fine - chain = [] - witness_chain = [] + chain = self.wallet.create_self_transfer_chain(chain_length=DEFAULT_ANCESTOR_LIMIT) + witness_chain = [t["wtxid"] for t in chain] ancestor_vsize = 0 ancestor_fees = Decimal(0) - 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) - # We need the wtxids to check P2P announcements - witnesstx = self.nodes[0].gettransaction(txid=txid, verbose=True)['decoded'] - witness_chain.append(witnesstx['hash']) + for i, t in enumerate(chain): + ancestor_vsize += t["tx"].get_vsize() + ancestor_fees += t["fee"] + self.wallet.sendrawtransaction(from_node=self.nodes[0], tx_hex=t["hex"]) # Check that listunspent ancestor{count, size, fees} yield the correct results - wallet_unspent = self.nodes[0].listunspent(minconf=0) - this_unspent = next(utxo_info for utxo_info in wallet_unspent if utxo_info['txid'] == txid) - assert_equal(this_unspent['ancestorcount'], i + 1) - ancestor_vsize += self.nodes[0].getrawtransaction(txid=txid, verbose=True)['vsize'] - assert_equal(this_unspent['ancestorsize'], ancestor_vsize) - ancestor_fees -= self.nodes[0].gettransaction(txid=txid)['fee'] - assert_equal(this_unspent['ancestorfees'], ancestor_fees * COIN) + if self.is_specified_wallet_compiled(): + wallet_unspent = self.nodes[0].listunspent(minconf=0) + this_unspent = next(utxo_info for utxo_info in wallet_unspent if utxo_info["txid"] == t["txid"]) + assert_equal(this_unspent['ancestorcount'], i + 1) + assert_equal(this_unspent['ancestorsize'], ancestor_vsize) + assert_equal(this_unspent['ancestorfees'], ancestor_fees * COIN) # Wait until mempool transactions have passed initial broadcast (sent inv and received getdata) # Otherwise, getrawmempool may be inconsistent with getmempoolentry if unbroadcast changes in between @@ -99,15 +87,20 @@ class MempoolPackagesTest(BitcoinTestFramework): ancestor_count = DEFAULT_ANCESTOR_LIMIT assert_equal(ancestor_fees, sum([mempool[tx]['fees']['base'] for tx in mempool])) + # Adding one more transaction on to the chain should fail. + next_hop = self.wallet.create_self_transfer(utxo_to_spend=chain[-1]["new_utxo"])["hex"] + assert_raises_rpc_error(-26, "too-long-mempool-chain", lambda: self.nodes[0].sendrawtransaction(next_hop)) + descendants = [] - ancestors = list(chain) + ancestors = [t["txid"] for t in chain] + chain = [t["txid"] for t in chain] for x in reversed(chain): # Check that getmempoolentry is consistent with getrawmempool 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'] + witnesstx = self.nodes[0].getrawtransaction(txid=x, verbose=True) 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} ]) @@ -193,9 +186,6 @@ class MempoolPackagesTest(BitcoinTestFramework): descendant_fees += entry['fees']['base'] assert_equal(entry['fees']['descendant'], descendant_fees + Decimal('0.00001')) - # Adding one more transaction on to the chain should fail. - assert_raises_rpc_error(-26, "too-long-mempool-chain", chain_transaction, self.nodes[0], [txid], [vout], value, fee, 1) - # Check that prioritising a tx before it's added to the mempool works # First clear the mempool by mining a block. self.generate(self.nodes[0], 1) @@ -232,28 +222,23 @@ class MempoolPackagesTest(BitcoinTestFramework): # TODO: test ancestor size limits # Now test descendant chain limits - txid = utxo[1]['txid'] - value = utxo[1]['amount'] - vout = utxo[1]['vout'] - transaction_package = [] tx_children = [] # First create one parent tx with 10 children - (txid, sent_value) = chain_transaction(self.nodes[0], [txid], [vout], value, fee, 10) - parent_transaction = txid - for i in range(10): - transaction_package.append({'txid': txid, 'vout': i, 'amount': sent_value}) + tx_with_children = self.wallet.send_self_transfer_multi(from_node=self.nodes[0], num_outputs=10) + parent_transaction = tx_with_children["txid"] + transaction_package = tx_with_children["new_utxos"] # 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(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) + new_tx = self.wallet.send_self_transfer_multi(from_node=self.nodes[0], num_outputs=10, utxos_to_spend=[utxo]) + txid = new_tx["txid"] chain.append(txid) if utxo['txid'] is parent_transaction: tx_children.append(txid) - for j in range(10): - transaction_package.append({'txid': txid, 'vout': j, 'amount': sent_value}) + transaction_package.extend(new_tx["new_utxos"]) mempool = self.nodes[0].getrawmempool(True) assert_equal(mempool[parent_transaction]['descendantcount'], DEFAULT_DESCENDANT_LIMIT) @@ -263,8 +248,8 @@ class MempoolPackagesTest(BitcoinTestFramework): assert_equal(mempool[child]['depends'], [parent_transaction]) # Sending one more chained transaction will fail - utxo = transaction_package.pop(0) - assert_raises_rpc_error(-26, "too-long-mempool-chain", chain_transaction, self.nodes[0], [utxo['txid']], [utxo['vout']], utxo['amount'], fee, 10) + next_hop = self.wallet.create_self_transfer(utxo_to_spend=transaction_package.pop(0))["hex"] + assert_raises_rpc_error(-26, "too-long-mempool-chain", lambda: self.nodes[0].sendrawtransaction(next_hop)) # Check that node1's mempool is as expected, containing: # - txs from previous ancestor test (-> custom ancestor limit) @@ -304,42 +289,19 @@ class MempoolPackagesTest(BitcoinTestFramework): # last block. # Create tx0 with 2 outputs - utxo = self.nodes[0].listunspent() - txid = utxo[0]['txid'] - value = utxo[0]['amount'] - vout = utxo[0]['vout'] - - send_value = (value - fee) / 2 - inputs = [ {'txid' : txid, 'vout' : vout} ] - outputs = {} - for _ in range(2): - outputs[self.nodes[0].getnewaddress()] = send_value - rawtx = self.nodes[0].createrawtransaction(inputs, outputs) - signedtx = self.nodes[0].signrawtransactionwithwallet(rawtx) - txid = self.nodes[0].sendrawtransaction(signedtx['hex']) - tx0_id = txid - value = send_value + tx0 = self.wallet.send_self_transfer_multi(from_node=self.nodes[0], num_outputs=2) # Create tx1 - tx1_id, _ = chain_transaction(self.nodes[0], [tx0_id], [0], value, fee, 1) + tx1 = self.wallet.send_self_transfer(from_node=self.nodes[0], utxo_to_spend=tx0["new_utxos"][0]) # Create tx2-7 - vout = 1 - txid = tx0_id - for _ in range(6): - (txid, sent_value) = chain_transaction(self.nodes[0], [txid], [vout], value, fee, 1) - vout = 0 - value = sent_value + tx7 = self.wallet.send_self_transfer_chain(from_node=self.nodes[0], utxo_to_spend=tx0["new_utxos"][1], chain_length=6)[-1] # Mine these in a block self.generate(self.nodes[0], 1) # Now generate tx8, with a big fee - inputs = [ {'txid' : tx1_id, 'vout': 0}, {'txid' : txid, 'vout': 0} ] - outputs = { self.nodes[0].getnewaddress() : send_value + value - 4*fee } - rawtx = self.nodes[0].createrawtransaction(inputs, outputs) - signedtx = self.nodes[0].signrawtransactionwithwallet(rawtx) - txid = self.nodes[0].sendrawtransaction(signedtx['hex']) + self.wallet.send_self_transfer_multi(from_node=self.nodes[0], utxos_to_spend=[tx1["new_utxo"], tx7["new_utxo"]], fee_per_output=40000) self.sync_mempools() # Now try to disconnect the tip on each node... diff --git a/test/functional/p2p_disconnect_ban.py b/test/functional/p2p_disconnect_ban.py index 7169cc8937..b2f0659eda 100755 --- a/test/functional/p2p_disconnect_ban.py +++ b/test/functional/p2p_disconnect_ban.py @@ -107,7 +107,7 @@ class DisconnectBanTest(BitcoinTestFramework): self.log.info("disconnectnode: fail to disconnect when calling with address and nodeid") address1 = self.nodes[0].getpeerinfo()[0]['addr'] - node1 = self.nodes[0].getpeerinfo()[0]['addr'] + node1 = self.nodes[0].getpeerinfo()[0]["id"] assert_raises_rpc_error(-32602, "Only one of address and nodeid should be provided.", self.nodes[0].disconnectnode, address=address1, nodeid=node1) self.log.info("disconnectnode: fail to disconnect when calling with junk address") diff --git a/test/functional/rpc_invalid_address_message.py b/test/functional/rpc_invalid_address_message.py index 452f857a44..0c29efb85a 100755 --- a/test/functional/rpc_invalid_address_message.py +++ b/test/functional/rpc_invalid_address_message.py @@ -95,15 +95,19 @@ class InvalidAddressErrorMessageTest(BitcoinTestFramework): self.check_invalid(INVALID_ADDRESS, 'Not a valid Bech32 or Base58 encoding') self.check_invalid(INVALID_ADDRESS_2, 'Not a valid Bech32 or Base58 encoding') + node = self.nodes[0] + + # Missing arg returns the help text + assert_raises_rpc_error(-1, "Return information about the given bitcoin address.", node.validateaddress) + # Explicit None is not allowed for required parameters + assert_raises_rpc_error(-3, "JSON value of type null is not of expected type string", node.validateaddress, None) + def test_getaddressinfo(self): node = self.nodes[0] assert_raises_rpc_error(-5, "Invalid Bech32 address data size", node.getaddressinfo, BECH32_INVALID_SIZE) - assert_raises_rpc_error(-5, "Not a valid Bech32 or Base58 encoding", node.getaddressinfo, BECH32_INVALID_PREFIX) - assert_raises_rpc_error(-5, "Invalid prefix for Base58-encoded address", node.getaddressinfo, BASE58_INVALID_PREFIX) - assert_raises_rpc_error(-5, "Not a valid Bech32 or Base58 encoding", node.getaddressinfo, INVALID_ADDRESS) def run_test(self): diff --git a/test/functional/rpc_packages.py b/test/functional/rpc_packages.py index 10388399ad..6cb9760b3d 100755 --- a/test/functional/rpc_packages.py +++ b/test/functional/rpc_packages.py @@ -128,8 +128,8 @@ class RPCPackagesTest(BitcoinTestFramework): node = self.nodes[0] chain = self.wallet.create_self_transfer_chain(chain_length=25) - chain_hex = chain["chain_hex"] - chain_txns = chain["chain_txns"] + chain_hex = [t["hex"] for t in chain] + chain_txns = [t["tx"] for t in chain] self.log.info("Check that testmempoolaccept requires packages to be sorted by dependency") assert_equal(node.testmempoolaccept(rawtxs=chain_hex[::-1]), @@ -374,7 +374,7 @@ class RPCPackagesTest(BitcoinTestFramework): self.log.info("Submitpackage only allows packages of 1 child with its parents") # Chain of 3 transactions has too many generations - chain_hex = self.wallet.create_self_transfer_chain(chain_length=25)["chain_hex"] + chain_hex = [t["hex"] for t in self.wallet.create_self_transfer_chain(chain_length=25)] assert_raises_rpc_error(-25, "not-child-with-parents", node.submitpackage, chain_hex) diff --git a/test/functional/rpc_users.py b/test/functional/rpc_users.py index 560f226469..8cc3ec401e 100755 --- a/test/functional/rpc_users.py +++ b/test/functional/rpc_users.py @@ -53,13 +53,13 @@ class HTTPBasicsTest(BitcoinTestFramework): # Generate RPCAUTH with specified password self.rt2password = "8/F3uMDw4KSEbw96U3CA1C4X05dkHDN2BPFjTgZW4KI=" - p = subprocess.Popen([sys.executable, gen_rpcauth, 'rt2', self.rt2password], stdout=subprocess.PIPE, universal_newlines=True) + p = subprocess.Popen([sys.executable, gen_rpcauth, 'rt2', self.rt2password], stdout=subprocess.PIPE, text=True) lines = p.stdout.read().splitlines() rpcauth2 = lines[1] # Generate RPCAUTH without specifying password self.user = ''.join(SystemRandom().choice(string.ascii_letters + string.digits) for _ in range(10)) - p = subprocess.Popen([sys.executable, gen_rpcauth, self.user], stdout=subprocess.PIPE, universal_newlines=True) + p = subprocess.Popen([sys.executable, gen_rpcauth, self.user], stdout=subprocess.PIPE, text=True) lines = p.stdout.read().splitlines() rpcauth3 = lines[1] self.password = lines[3] diff --git a/test/functional/test_framework/test_node.py b/test/functional/test_framework/test_node.py index 8585972cb3..f3d81ed7da 100755 --- a/test/functional/test_framework/test_node.py +++ b/test/functional/test_framework/test_node.py @@ -730,7 +730,7 @@ class TestNodeCLI(): p_args += [command] p_args += pos_args + named_args self.log.debug("Running bitcoin-cli {}".format(p_args[2:])) - process = subprocess.Popen(p_args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True) + process = subprocess.Popen(p_args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) cli_stdout, cli_stderr = process.communicate(input=self.input) returncode = process.poll() if returncode: diff --git a/test/functional/test_framework/util.py b/test/functional/test_framework/util.py index 1d5108c31a..9048a915b2 100644 --- a/test/functional/test_framework/util.py +++ b/test/functional/test_framework/util.py @@ -488,28 +488,6 @@ def find_output(node, txid, amount, *, blockhash=None): raise RuntimeError("find_output txid %s : %s not found" % (txid, str(amount))) -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, - equally divided up to the desired number of outputs. - - Returns a tuple with the txid and the amount sent per output. - """ - send_value = satoshi_round((value - fee)/num_outputs) - inputs = [] - for (txid, vout) in zip(parent_txids, vouts): - inputs.append({'txid' : txid, 'vout' : vout}) - outputs = {} - for _ in range(num_outputs): - outputs[node.getnewaddress()] = send_value - rawtx = node.createrawtransaction(inputs, outputs, 0, True) - signedtx = node.signrawtransactionwithwallet(rawtx) - txid = node.sendrawtransaction(signedtx['hex']) - fulltx = node.getrawtransaction(txid, 1) - assert len(fulltx['vout']) == num_outputs # make sure we didn't generate a change output - return (txid, send_value) - - # Create large OP_RETURN txouts that can be appended to a transaction # to make it large (helper for constructing large transactions). The # total serialized size of the txouts is about 66k vbytes. diff --git a/test/functional/test_framework/wallet.py b/test/functional/test_framework/wallet.py index b5de318d25..f3253630c4 100644 --- a/test/functional/test_framework/wallet.py +++ b/test/functional/test_framework/wallet.py @@ -180,12 +180,12 @@ class MiniWallet: tx.vin[0].scriptSig = CScript([der_sig + bytes(bytearray([SIGHASH_ALL]))]) tx.rehash() elif self._mode == MiniWalletMode.RAW_OP_TRUE: - for i in range(len(tx.vin)): - tx.vin[i].scriptSig = CScript([OP_NOP] * 43) # pad to identical size + for i in tx.vin: + i.scriptSig = CScript([OP_NOP] * 43) # pad to identical size elif self._mode == MiniWalletMode.ADDRESS_OP_TRUE: tx.wit.vtxinwit = [CTxInWitness()] * len(tx.vin) - for i in range(len(tx.vin)): - tx.wit.vtxinwit[i].scriptWitness.stack = [CScript([OP_TRUE]), bytes([LEAF_VERSION_TAPSCRIPT]) + self._internal_key] + for i in tx.wit.vtxinwit: + i.scriptWitness.stack = [CScript([OP_TRUE]), bytes([LEAF_VERSION_TAPSCRIPT]) + self._internal_key] else: assert False @@ -298,10 +298,13 @@ class MiniWallet: inputs_value_total = sum([int(COIN * utxo['value']) for utxo in utxos_to_spend]) outputs_value_total = inputs_value_total - fee_per_output * num_outputs amount_per_output = amount_per_output or (outputs_value_total // num_outputs) + assert amount_per_output > 0 + outputs_value_total = amount_per_output * num_outputs + fee = Decimal(inputs_value_total - outputs_value_total) / COIN # create tx tx = CTransaction() - tx.vin = [CTxIn(COutPoint(int(utxo_to_spend['txid'], 16), utxo_to_spend['vout']), nSequence=seq) for utxo_to_spend,seq in zip(utxos_to_spend, sequence)] + tx.vin = [CTxIn(COutPoint(int(utxo_to_spend['txid'], 16), utxo_to_spend['vout']), nSequence=seq) for utxo_to_spend, seq in zip(utxos_to_spend, sequence)] tx.vout = [CTxOut(amount_per_output, bytearray(self._scriptPubKey)) for _ in range(num_outputs)] tx.nLockTime = locktime @@ -320,7 +323,9 @@ class MiniWallet: coinbase=False, confirmations=0, ) for i in range(len(tx.vout))], + "fee": fee, "txid": txid, + "wtxid": tx.getwtxid(), "hex": tx.serialize().hex(), "tx": tx, } @@ -338,52 +343,45 @@ class MiniWallet: else: assert False send_value = utxo_to_spend["value"] - (fee or (fee_rate * vsize / 1000)) - assert send_value > 0 # create tx tx = self.create_self_transfer_multi(utxos_to_spend=[utxo_to_spend], locktime=locktime, sequence=sequence, amount_per_output=int(COIN * send_value), target_weight=target_weight) if not target_weight: assert_equal(tx["tx"].get_vsize(), vsize) + tx["new_utxo"] = tx.pop("new_utxos")[0] - return {"txid": tx["txid"], "wtxid": tx["tx"].getwtxid(), "hex": tx["hex"], "tx": tx["tx"], "new_utxo": tx["new_utxos"][0]} + return tx 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 create_self_transfer_chain(self, *, chain_length): + def create_self_transfer_chain(self, *, chain_length, utxo_to_spend=None): """ Create 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 a dic {"chain_hex": chain_hex, "chain_txns" : chain_txns} - - "chain_hex" is a list representing the chain's transactions in hexadecimal. - "chain_txns" is a list representing the chain's transactions in the CTransaction object. """ - chaintip_utxo = self.get_utxo() - chain_hex = [] - chain_txns = [] + chaintip_utxo = utxo_to_spend or self.get_utxo() + chain = [] for _ in range(chain_length): tx = self.create_self_transfer(utxo_to_spend=chaintip_utxo) chaintip_utxo = tx["new_utxo"] - chain_hex.append(tx["hex"]) - chain_txns.append(tx["tx"]) + chain.append(tx) - return {"chain_hex": chain_hex, "chain_txns" : chain_txns} + return chain - def send_self_transfer_chain(self, *, from_node, chain_length, utxo_to_spend=None): + def send_self_transfer_chain(self, *, from_node, **kwargs): """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 + Returns a list of objects for each tx (see create_self_transfer_multi). """ - 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 + chain = self.create_self_transfer_chain(**kwargs) + for t in chain: + self.sendrawtransaction(from_node=from_node, tx_hex=t["hex"]) + return chain def getnewdestination(address_type='bech32m'): diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py index 4b9bf11d0e..569af0ee9b 100755 --- a/test/functional/test_runner.py +++ b/test/functional/test_runner.py @@ -585,7 +585,7 @@ def run_tests(*, test_list, src_dir, build_dir, tmpdir, jobs=1, enable_coverage= combined_logs_args = [sys.executable, os.path.join(tests_dir, 'combine_logs.py'), testdir] if BOLD[0]: combined_logs_args += ['--color'] - combined_logs, _ = subprocess.Popen(combined_logs_args, universal_newlines=True, stdout=subprocess.PIPE).communicate() + combined_logs, _ = subprocess.Popen(combined_logs_args, text=True, stdout=subprocess.PIPE).communicate() print("\n".join(deque(combined_logs.splitlines(), combined_logs_len))) if failfast: @@ -670,7 +670,7 @@ class TestHandler: self.jobs.append((test, time.time(), subprocess.Popen([sys.executable, self.tests_dir + test_argv[0]] + test_argv[1:] + self.flags + portseed_arg + tmpdir_arg, - universal_newlines=True, + text=True, stdout=log_stdout, stderr=log_stderr), testdir, diff --git a/test/functional/tool_wallet.py b/test/functional/tool_wallet.py index 4a321c2fc4..a888f93b03 100755 --- a/test/functional/tool_wallet.py +++ b/test/functional/tool_wallet.py @@ -37,7 +37,7 @@ class ToolWalletTest(BitcoinTestFramework): if not self.options.descriptors and 'create' in args: default_args.append('-legacy') - return subprocess.Popen([binary] + default_args + list(args), stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True) + return subprocess.Popen([binary] + default_args + list(args), stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) def assert_raises_tool_error(self, error, *args): p = self.bitcoin_wallet_process(*args) diff --git a/test/functional/wallet_basic.py b/test/functional/wallet_basic.py index 86cfd4a230..52022a2eee 100755 --- a/test/functional/wallet_basic.py +++ b/test/functional/wallet_basic.py @@ -731,6 +731,45 @@ class WalletTest(BitcoinTestFramework): assert_equal(coin_b["parent_descs"][0], multi_b) self.nodes[0].unloadwallet("wo") + self.log.info("Test -spendzeroconfchange") + self.restart_node(0, ["-spendzeroconfchange=0"]) + + # create new wallet and fund it with a confirmed UTXO + self.nodes[0].createwallet(wallet_name="zeroconf", load_on_startup=True) + zeroconf_wallet = self.nodes[0].get_wallet_rpc("zeroconf") + default_wallet = self.nodes[0].get_wallet_rpc(self.default_wallet_name) + default_wallet.sendtoaddress(zeroconf_wallet.getnewaddress(), Decimal('1.0')) + self.generate(self.nodes[0], 1, sync_fun=self.no_op) + utxos = zeroconf_wallet.listunspent(minconf=0) + assert_equal(len(utxos), 1) + assert_equal(utxos[0]['confirmations'], 1) + + # spend confirmed UTXO to ourselves + zeroconf_wallet.sendall(recipients=[zeroconf_wallet.getnewaddress()]) + utxos = zeroconf_wallet.listunspent(minconf=0) + assert_equal(len(utxos), 1) + assert_equal(utxos[0]['confirmations'], 0) + # accounts for untrusted pending balance + bal = zeroconf_wallet.getbalances() + assert_equal(bal['mine']['trusted'], 0) + assert_equal(bal['mine']['untrusted_pending'], utxos[0]['amount']) + + # spending an unconfirmed UTXO sent to ourselves should fail + assert_raises_rpc_error(-6, "Insufficient funds", zeroconf_wallet.sendtoaddress, zeroconf_wallet.getnewaddress(), Decimal('0.5')) + + # check that it works again with -spendzeroconfchange set (=default) + self.restart_node(0, ["-spendzeroconfchange=1"]) + zeroconf_wallet = self.nodes[0].get_wallet_rpc("zeroconf") + utxos = zeroconf_wallet.listunspent(minconf=0) + assert_equal(len(utxos), 1) + assert_equal(utxos[0]['confirmations'], 0) + # accounts for trusted balance + bal = zeroconf_wallet.getbalances() + assert_equal(bal['mine']['trusted'], utxos[0]['amount']) + assert_equal(bal['mine']['untrusted_pending'], 0) + + zeroconf_wallet.sendtoaddress(zeroconf_wallet.getnewaddress(), Decimal('0.5')) + if __name__ == '__main__': WalletTest().main() diff --git a/test/functional/wallet_descriptor.py b/test/functional/wallet_descriptor.py index f7fb38482a..b0e93df36a 100755 --- a/test/functional/wallet_descriptor.py +++ b/test/functional/wallet_descriptor.py @@ -114,7 +114,7 @@ class WalletDescriptorTest(BitcoinTestFramework): # Make sure things are disabled self.log.info("Test disabled RPCs") 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.importpubkey, send_wrpc.getaddressinfo(send_wrpc.getnewaddress())["pubkey"]) 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()]) diff --git a/test/fuzz/test_runner.py b/test/fuzz/test_runner.py index e2eab2a0fe..af21e7b956 100755 --- a/test/fuzz/test_runner.py +++ b/test/fuzz/test_runner.py @@ -143,7 +143,7 @@ def main(): timeout=20, check=True, stderr=subprocess.PIPE, - universal_newlines=True, + text=True, ).stderr if "libFuzzer" not in help_output: logging.error("Must be built with libFuzzer") @@ -200,7 +200,7 @@ def generate_corpus(*, fuzz_pool, src_dir, build_dir, corpus_dir, targets): env=get_fuzz_env(target=t, source_dir=src_dir), check=True, stderr=subprocess.PIPE, - universal_newlines=True, + text=True, ).stderr)) futures = [] @@ -241,7 +241,7 @@ def merge_inputs(*, fuzz_pool, corpus, test_list, src_dir, build_dir, merge_dir) env=get_fuzz_env(target=t, source_dir=src_dir), check=True, stderr=subprocess.PIPE, - universal_newlines=True, + text=True, ).stderr logging.debug(output) @@ -270,7 +270,7 @@ def run_once(*, fuzz_pool, corpus, test_list, src_dir, build_dir, use_valgrind): args, env=get_fuzz_env(target=t, source_dir=src_dir), stderr=subprocess.PIPE, - universal_newlines=True, + text=True, ) output += result.stderr return output, result @@ -299,7 +299,7 @@ def parse_test_list(*, fuzz_bin): }, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, - universal_newlines=True, + text=True, ).stdout.splitlines() return test_list_all diff --git a/test/lint/README.md b/test/lint/README.md index 8d592c3282..704922d7ab 100644 --- a/test/lint/README.md +++ b/test/lint/README.md @@ -1,5 +1,23 @@ This folder contains lint scripts. +Running locally +=============== + +To run linters locally with the same versions as the CI environment, use the included +Dockerfile: + +```sh +cd ./ci/lint +docker build -t bitcoin-linter . + +cd /root/of/bitcoin/repo +docker run --rm -v $(pwd):/bitcoin -it bitcoin-linter +``` + +After building the container once, you can simply run the last command any time you +want to lint. + + check-doc.py ============ Check for missing documentation of command line options. diff --git a/test/lint/lint-assertions.py b/test/lint/lint-assertions.py index 195ff33d11..e7eecebce5 100755 --- a/test/lint/lint-assertions.py +++ b/test/lint/lint-assertions.py @@ -12,7 +12,7 @@ import subprocess def git_grep(params: [], error_msg: ""): try: - output = subprocess.check_output(["git", "grep", *params], universal_newlines=True, encoding="utf8") + output = subprocess.check_output(["git", "grep", *params], text=True, encoding="utf8") print(error_msg) print(output) return 1 diff --git a/test/lint/lint-circular-dependencies.py b/test/lint/lint-circular-dependencies.py index b69bbe7cd0..cf6a5f81f1 100755 --- a/test/lint/lint-circular-dependencies.py +++ b/test/lint/lint-circular-dependencies.py @@ -38,14 +38,14 @@ def main(): os.chdir(CODE_DIR) files = subprocess.check_output( ['git', 'ls-files', '--', '*.h', '*.cpp'], - universal_newlines=True, + text=True, ).splitlines() command = [sys.executable, "../contrib/devtools/circular-dependencies.py", *files] dependencies_output = subprocess.run( command, stdout=subprocess.PIPE, - universal_newlines=True, + text=True, ) for dependency_str in dependencies_output.stdout.rstrip().split("\n"): diff --git a/test/lint/lint-git-commit-check.py b/test/lint/lint-git-commit-check.py index 049104398a..5897a17e70 100755 --- a/test/lint/lint-git-commit-check.py +++ b/test/lint/lint-git-commit-check.py @@ -42,17 +42,17 @@ def main(): 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") + merge_base = check_output(["git", "merge-base", "HEAD", "master"], text=True, encoding="utf8").rstrip("\n") commit_range = merge_base + "..HEAD" else: commit_range = os.getenv("COMMIT_RANGE") if commit_range == "SKIP_EMPTY_NOT_A_PR": sys.exit(0) - commit_hashes = check_output(["git", "log", commit_range, "--format=%H"], universal_newlines=True, encoding="utf8").splitlines() + commit_hashes = check_output(["git", "log", commit_range, "--format=%H"], text=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() + commit_info = check_output(["git", "log", "--format=%B", "-n", "1", hash], text=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.") diff --git a/test/lint/lint-includes.py b/test/lint/lint-includes.py index b3fa4b9303..459030bb0b 100755 --- a/test/lint/lint-includes.py +++ b/test/lint/lint-includes.py @@ -35,13 +35,13 @@ EXPECTED_BOOST_INCLUDES = ["boost/date_time/posix_time/posix_time.hpp", def get_toplevel(): - return check_output(["git", "rev-parse", "--show-toplevel"], universal_newlines=True, encoding="utf8").rstrip("\n") + return check_output(["git", "rev-parse", "--show-toplevel"], text=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() + files_list = check_output(["git", "ls-files", "src"] + exclude_args, text=True, encoding="utf8").splitlines() return [file for file in files_list if file.endswith(suffixes)] @@ -63,7 +63,7 @@ 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() + included_cpps = check_output(["git", "grep", "-E", r"^#include [<\"][^>\"]+\.cpp[>\"]", "--", "*.cpp", "*.h"], text=True, encoding="utf8").splitlines() except CalledProcessError as e: if e.returncode > 1: raise e @@ -77,7 +77,7 @@ def find_extra_boosts(): exclusion_set = set() try: - included_boosts = check_output(["git", "grep", "-E", r"^#include <boost/", "--", "*.cpp", "*.h"], universal_newlines=True, encoding="utf8").splitlines() + included_boosts = check_output(["git", "grep", "-E", r"^#include <boost/", "--", "*.cpp", "*.h"], text=True, encoding="utf8").splitlines() except CalledProcessError as e: if e.returncode > 1: raise e @@ -100,7 +100,7 @@ def find_quote_syntax_inclusions(): quote_syntax_inclusions = list() try: - quote_syntax_inclusions = check_output(["git", "grep", r"^#include \"", "--", "*.cpp", "*.h"] + exclude_args, universal_newlines=True, encoding="utf8").splitlines() + quote_syntax_inclusions = check_output(["git", "grep", r"^#include \"", "--", "*.cpp", "*.h"] + exclude_args, text=True, encoding="utf8").splitlines() except CalledProcessError as e: if e.returncode > 1: raise e @@ -143,13 +143,13 @@ def main(): 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")) + print(check_output(["git", "grep", boost, "--", "*.cpp", "*.h"], text=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") + check_output(["git", "grep", "-q", r"^#include <%s>" % expected_boost, "--", "*.cpp", "*.h"], text=True, encoding="utf8") except CalledProcessError as e: if e.returncode > 1: raise e diff --git a/test/lint/lint-locale-dependence.py b/test/lint/lint-locale-dependence.py index ce7444cd1a..c5cb34b20a 100755 --- a/test/lint/lint-locale-dependence.py +++ b/test/lint/lint-locale-dependence.py @@ -223,7 +223,7 @@ def find_locale_dependent_function_uses(): git_grep_output = list() try: - git_grep_output = check_output(git_grep_command, universal_newlines=True, encoding="utf8").splitlines() + git_grep_output = check_output(git_grep_command, text=True, encoding="utf8").splitlines() except CalledProcessError as e: if e.returncode > 1: raise e diff --git a/test/lint/lint-logs.py b/test/lint/lint-logs.py index aaf697467d..de04a1aeca 100755 --- a/test/lint/lint-logs.py +++ b/test/lint/lint-logs.py @@ -16,7 +16,7 @@ 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() + logs_list = check_output(["git", "grep", "--extended-regexp", r"(LogPrintLevel|LogPrintfCategory|LogPrintf?)\(", "--", "*.cpp"], text=True, encoding="utf8").splitlines() unterminated_logs = [line for line in logs_list if not re.search(r'(\\n"|/\* Continued \*/)', line)] diff --git a/test/lint/lint-python-mutable-default-parameters.py b/test/lint/lint-python-mutable-default-parameters.py index 3dfc5940f7..820595ea34 100755 --- a/test/lint/lint-python-mutable-default-parameters.py +++ b/test/lint/lint-python-mutable-default-parameters.py @@ -21,7 +21,7 @@ def main(): "--", "*.py", ] - output = subprocess.run(command, stdout=subprocess.PIPE, universal_newlines=True) + output = subprocess.run(command, stdout=subprocess.PIPE, text=True) if len(output.stdout) > 0: error_msg = ( "A mutable list or dict seems to be used as default parameter value:\n\n" diff --git a/test/lint/lint-python-utf8-encoding.py b/test/lint/lint-python-utf8-encoding.py index 62fdc34d50..364da63468 100755 --- a/test/lint/lint-python-utf8-encoding.py +++ b/test/lint/lint-python-utf8-encoding.py @@ -23,7 +23,7 @@ def check_fileopens(): fileopens = list() try: - fileopens = check_output(["git", "grep", r" open(", "--", "*.py"] + get_exclude_args(), universal_newlines=True, encoding="utf8").splitlines() + fileopens = check_output(["git", "grep", r" open(", "--", "*.py"] + get_exclude_args(), text=True, encoding="utf8").splitlines() except CalledProcessError as e: if e.returncode > 1: raise e @@ -37,12 +37,12 @@ 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() + checked_outputs = check_output(["git", "grep", "check_output(", "--", "*.py"] + get_exclude_args(), text=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)] + filtered_checked_outputs = [checked_output for checked_output in checked_outputs if re.search(r"text=True", checked_output) and not re.search(r"encoding=.(ascii|utf8|utf-8).", checked_output)] return filtered_checked_outputs diff --git a/test/lint/lint-shell.py b/test/lint/lint-shell.py index ed95024ef5..1646bf0d3e 100755 --- a/test/lint/lint-shell.py +++ b/test/lint/lint-shell.py @@ -25,7 +25,7 @@ def check_shellcheck_install(): sys.exit(0) def get_files(command): - output = subprocess.run(command, stdout=subprocess.PIPE, universal_newlines=True) + output = subprocess.run(command, stdout=subprocess.PIPE, text=True) files = output.stdout.split('\n') # remove whitespace element diff --git a/test/lint/lint-submodule.py b/test/lint/lint-submodule.py index 89d4c80f55..4d2fbf088f 100755 --- a/test/lint/lint-submodule.py +++ b/test/lint/lint-submodule.py @@ -13,7 +13,7 @@ import sys def main(): submodules_list = subprocess.check_output(['git', 'submodule', 'status', '--recursive'], - universal_newlines = True, encoding = 'utf8').rstrip('\n') + text = True, encoding = 'utf8').rstrip('\n') if submodules_list: print("These submodules were found, delete them:\n", submodules_list) sys.exit(1) diff --git a/test/lint/lint-tests.py b/test/lint/lint-tests.py index 849ddcb961..1eeb7bb014 100755 --- a/test/lint/lint-tests.py +++ b/test/lint/lint-tests.py @@ -23,7 +23,7 @@ def grep_boost_fixture_test_suite(): "src/test/**.cpp", "src/wallet/test/**.cpp", ] - return subprocess.check_output(command, universal_newlines=True, encoding="utf8") + return subprocess.check_output(command, text=True, encoding="utf8") def check_matching_test_names(test_suite_list): diff --git a/test/lint/lint-whitespace.py b/test/lint/lint-whitespace.py index 72b7ebc394..f5e4a776d0 100755 --- a/test/lint/lint-whitespace.py +++ b/test/lint/lint-whitespace.py @@ -80,7 +80,7 @@ def get_diff(commit_range, check_only_code): else: what_files = ["."] - diff = check_output(["git", "diff", "-U0", commit_range, "--"] + what_files + exclude_args, universal_newlines=True, encoding="utf8") + diff = check_output(["git", "diff", "-U0", commit_range, "--"] + what_files + exclude_args, text=True, encoding="utf8") return diff @@ -93,7 +93,7 @@ def main(): 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") + merge_base = check_output(["git", "merge-base", "HEAD", "master"], text=True, encoding="utf8").rstrip("\n") commit_range = merge_base + "..HEAD" else: commit_range = os.getenv("COMMIT_RANGE") diff --git a/test/util/test_runner.py b/test/util/test_runner.py index 03db05c563..ea3626fa65 100755 --- a/test/util/test_runner.py +++ b/test/util/test_runner.py @@ -107,7 +107,7 @@ def bctest(testDir, testObj, buildenv): raise Exception # Run the test - proc = subprocess.Popen(execrun, stdin=stdinCfg, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True) + proc = subprocess.Popen(execrun, stdin=stdinCfg, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) try: outs = proc.communicate(input=inputData) except OSError: |