diff options
140 files changed, 3408 insertions, 1969 deletions
diff --git a/.gitignore b/.gitignore index 394c76b51a..5cef80f92f 100644 --- a/.gitignore +++ b/.gitignore @@ -77,7 +77,7 @@ src/qt/bitcoin-qt.includes # Only ignore unexpected patches *.patch -!depends/patches/*.patch +!depends/patches/**/*.patch #libtool object files *.lo diff --git a/.travis.yml b/.travis.yml index adf2140642..b893f35699 100644 --- a/.travis.yml +++ b/.travis.yml @@ -37,6 +37,7 @@ cache: stages: - lint - test + - extended-lint env: global: - MAKEJOBS=-j3 @@ -85,6 +86,19 @@ jobs: script: - set -o errexit; source .travis/lint_06_script.sh + - stage: extended-lint + name: 'extended lint [runtime >= 60 seconds]' + env: + cache: false + language: python + python: '3.5' + install: + - set -o errexit; source .travis/extended_lint_04_install.sh + before_script: + - set -o errexit; source .travis/lint_05_before_script.sh + script: + - set -o errexit; source .travis/extended_lint_06_script.sh + - stage: test name: 'ARM [GOAL: install] [no unit or functional tests]' env: >- @@ -165,7 +179,7 @@ jobs: RUN_FUNCTIONAL_TESTS=false RUN_FUZZ_TESTS=true GOAL="install" - BITCOIN_CONFIG="--disable-wallet --disable-bench --with-utils=no --with-daemon=no --with-libs=no --with-gui=no --enable-fuzz --with-sanitizers=fuzzer,address CC=clang CXX=clang++" + BITCOIN_CONFIG="--enable-fuzz --with-sanitizers=fuzzer,address CC=clang CXX=clang++" - stage: test name: 'x86_64 Linux [GOAL: install] [bionic] [no wallet]' diff --git a/.travis/extended_lint_04_install.sh b/.travis/extended_lint_04_install.sh new file mode 100755 index 0000000000..123d874a84 --- /dev/null +++ b/.travis/extended_lint_04_install.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env bash +# +# Copyright (c) 2019 The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. + +export LC_ALL=C + +CPPCHECK_VERSION=1.86 +curl -s https://codeload.github.com/danmar/cppcheck/tar.gz/${CPPCHECK_VERSION} | tar -zxf - --directory /tmp/ +(cd /tmp/cppcheck-${CPPCHECK_VERSION}/ && make CFGDIR=/tmp/cppcheck-${CPPCHECK_VERSION}/cfg/ > /dev/null) +export PATH="$PATH:/tmp/cppcheck-${CPPCHECK_VERSION}/" diff --git a/.travis/extended_lint_06_script.sh b/.travis/extended_lint_06_script.sh new file mode 100755 index 0000000000..e8228c9c4d --- /dev/null +++ b/.travis/extended_lint_06_script.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash +# +# Copyright (c) 2019 The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. + +export LC_ALL=C + +test/lint/extended-lint-all.sh diff --git a/.travis/test_04_install.sh b/.travis/test_04_install.sh index b589ee7a16..319f2c5b21 100755 --- a/.travis/test_04_install.sh +++ b/.travis/test_04_install.sh @@ -6,6 +6,9 @@ export LC_ALL=C.UTF-8 +free -m -h +echo "Number of CPUs (nproc): $(nproc)" + travis_retry docker pull "$DOCKER_NAME_TAG" export DIR_FUZZ_IN=${TRAVIS_BUILD_DIR}/qa-assets diff --git a/build-aux/m4/bitcoin_qt.m4 b/build-aux/m4/bitcoin_qt.m4 index 1a7c5d5f7d..8ccbb19300 100644 --- a/build-aux/m4/bitcoin_qt.m4 +++ b/build-aux/m4/bitcoin_qt.m4 @@ -355,7 +355,6 @@ AC_DEFUN([_BITCOIN_QT_FIND_STATIC_PLUGINS],[ PKG_CHECK_MODULES([QTFB], [Qt5FbSupport], [QT_LIBS="-lQt5FbSupport $QT_LIBS"]) fi if test "x$TARGET_OS" = xlinux; then - PKG_CHECK_MODULES([X11XCB], [x11-xcb], [QT_LIBS="$X11XCB_LIBS $QT_LIBS"]) PKG_CHECK_MODULES([QTXCBQPA], [Qt5XcbQpa], [QT_LIBS="$QTXCBQPA_LIBS $QT_LIBS"]) elif test "x$TARGET_OS" = xdarwin; then PKG_CHECK_MODULES([QTCLIPBOARD], [Qt5ClipboardSupport], [QT_LIBS="-lQt5ClipboardSupport $QT_LIBS"]) diff --git a/build_msvc/bench_bitcoin/bench_bitcoin.vcxproj b/build_msvc/bench_bitcoin/bench_bitcoin.vcxproj index 9b6a298859..e64614c09d 100644 --- a/build_msvc/bench_bitcoin/bench_bitcoin.vcxproj +++ b/build_msvc/bench_bitcoin/bench_bitcoin.vcxproj @@ -24,6 +24,7 @@ <ClCompile Include="..\..\src\bench\examples.cpp" /> <ClCompile Include="..\..\src\bench\lockedpool.cpp" /> <ClCompile Include="..\..\src\bench\mempool_eviction.cpp" /> + <ClCompile Include="..\..\src\bench\rpc_blockchain.cpp" /> <ClCompile Include="..\..\src\bench\rpc_mempool.cpp" /> <ClCompile Include="..\..\src\bench\merkle_root.cpp" /> <ClCompile Include="..\..\src\bench\rollingbloom.cpp" /> diff --git a/configure.ac b/configure.ac index 1d5a41ab9a..08a850cfa2 100644 --- a/configure.ac +++ b/configure.ac @@ -148,7 +148,8 @@ AC_ARG_ENABLE([extended-functional-tests], [use_extended_functional_tests=no]) AC_ARG_ENABLE([fuzz], - AS_HELP_STRING([--enable-fuzz],[enable building of fuzz targets (default no)]), + AS_HELP_STRING([--enable-fuzz], + [enable building of fuzz targets (default no). enabling this will disable all other targets]), [enable_fuzz=$enableval], [enable_fuzz=no]) @@ -956,6 +957,29 @@ AC_SUBST(LEVELDB_CPPFLAGS) AC_SUBST(LIBLEVELDB) AC_SUBST(LIBMEMENV) +dnl enable-fuzz should disable all other targets +if test "x$enable_fuzz" = "xyes"; then + AC_MSG_WARN(enable-fuzz will disable all other targets) + build_bitcoin_utils=no + build_bitcoin_cli=no + build_bitcoin_tx=no + build_bitcoin_wallet=no + build_bitcoind=no + build_bitcoin_libs=no + bitcoin_enable_qt=no + bitcoin_enable_qt_test=no + bitcoin_enable_qt_dbus=no + enable_wallet=no + use_bench=no + use_upnp=no + use_zmq=no +else + BITCOIN_QT_INIT + + dnl sets $bitcoin_enable_qt, $bitcoin_enable_qt_test, $bitcoin_enable_qt_dbus + BITCOIN_QT_CONFIGURE([$use_pkgconfig]) +fi + if test x$enable_wallet != xno; then dnl Check for libdb_cxx only if wallet enabled BITCOIN_FIND_BDB48 @@ -970,11 +994,6 @@ if test x$use_upnp != xno; then ) fi -BITCOIN_QT_INIT - -dnl sets $bitcoin_enable_qt, $bitcoin_enable_qt_test, $bitcoin_enable_qt_dbus -BITCOIN_QT_CONFIGURE([$use_pkgconfig]) - if test x$build_bitcoin_wallet$build_bitcoin_cli$build_bitcoin_tx$build_bitcoind$bitcoin_enable_qt$use_tests$use_bench = xnonononononono; then use_boost=no else diff --git a/contrib/devtools/symbol-check.py b/contrib/devtools/symbol-check.py index 7729dd7257..dd35d862c9 100755 --- a/contrib/devtools/symbol-check.py +++ b/contrib/devtools/symbol-check.py @@ -67,8 +67,6 @@ ALLOWED_LIBRARIES = { 'ld-linux-armhf.so.3', # 32-bit ARM dynamic linker 'ld-linux-riscv64-lp64d.so.1', # 64-bit RISC-V dynamic linker # bitcoin-qt only -'libX11-xcb.so.1', # part of X11 -'libX11.so.6', # part of X11 'libxcb.so.1', # part of X11 'libfontconfig.so.1', # font support 'libfreetype.so.6', # font parsing diff --git a/contrib/guix/README.md b/contrib/guix/README.md new file mode 100644 index 0000000000..4dfa1729a5 --- /dev/null +++ b/contrib/guix/README.md @@ -0,0 +1,229 @@ +# Bootstrappable Bitcoin Core Builds + +This directory contains the files necessary to perform bootstrappable Bitcoin +Core builds. + +[Bootstrappability][b17e] furthers our binary security guarantees by allowing us +to _audit and reproduce_ our toolchain instead of blindly _trusting_ binary +downloads. + +We achieve bootstrappability by using Guix as a functional package manager. + +## Requirements + +Conservatively, a x86_64 machine with: + +- 2 or more logical cores +- 4GB of free disk space on the partition that /gnu/store will reside in +- 24GB of free disk space on the partition that the Bitcoin Core git repository + resides in + +> Note: these requirements are slightly less onerous than those of Gitian builds + +## Setup + +### Installing Guix + +If you're just testing this out, you can use the +[Dockerfile][fanquake/guix-docker] for convenience. It automatically speeds up +your builds by [using substitutes](#speeding-up-builds-with-substitute-servers). +If you don't want this behaviour, refer to the [next +section](#choosing-your-security-model). + +Otherwise, follow the [Guix installation guide][guix/bin-install]. + +> Note: For those who like to keep their filesystems clean, Guix is designed to +> be very standalone and _will not_ conflict with your system's package +> manager/existing setup. It _only_ touches `/var/guix`, `/gnu`, and +> `~/.config/guix`. + +### Choosing your security model + +Guix allows us to achieve better binary security by using our CPU time to build +everything from scratch. However, it doesn't sacrifice user choice in pursuit of +this: users can decide whether or not to bootstrap and to use substitutes. + +After installation, you may want to consider [adding substitute +servers](#speeding-up-builds-with-substitute-servers) to speed up your build if +that fits your security model (say, if you're just testing that this works). +This is skippable if you're using the [Dockerfile][fanquake/guix-docker]. + +If you prefer not to use any substitutes, make sure to set +`ADDITIONAL_GUIX_ENVIRONMENT_FLAGS` like the following snippet. The first build +will take a while, but the resulting packages will be cached for future builds. + +```sh +export ADDITIONAL_GUIX_ENVIRONMENT_FLAGS='--no-substitutes' +``` + +Likewise, to perform a bootstrapped build (takes even longer): + +```sh +export ADDITIONAL_GUIX_ENVIRONMENT_FLAGS='--bootstrap --no-substitutes' +``` + +### Using the right Guix + +Once Guix is installed, deploy our patched version into your current Guix +profile. The changes there are slowly being upstreamed. + +```sh +guix pull --url=https://github.com/dongcarl/guix.git \ + --commit=82c77e52b8b46e0a3aad2cb12307c2e30547deec \ + --max-jobs=4 # change accordingly +``` + +Make sure that you are using your current profile. (You are prompted to do this +at the end of the `guix pull`) + +```bash +export PATH="${HOME}/.config/guix/current/bin${PATH:+:}$PATH" +``` + +> Note: There is ongoing work to eliminate this entire section using Guix +> [inferiors][guix/inferiors] and [channels][guix/channels]. + +## Usage + +### As a Development Environment + +For a Bitcoin Core depends development environment, simply invoke + +```sh +guix environment --manifest=contrib/guix/manifest.scm +``` + +And you'll land back in your shell with all the build dependencies required for +a `depends` build injected into your environment. + +### As a Tool for Deterministic Builds + +From the top of a clean Bitcoin Core repository: + +```sh +./contrib/guix/guix-build.sh +``` + +After the build finishes successfully (check the status code please), compare +hashes: + +```sh +find output/ -type f -print0 | sort -z | xargs -r0 sha256sum +``` + +#### Recognized environment variables + +* _**HOSTS**_ + + Override the space-separated list of platform triples for which to perform a + bootstrappable build. _(defaults to "i686-linux-gnu x86\_64-linux-gnu + arm-linux-gnueabihf aarch64-linux-gnu riscv64-linux-gnu")_ + + > Windows and OS X platform triplet support are WIP. + +* _**SOURCES_PATH**_ + + Set the depends tree download cache for sources. This is passed through to the + depends tree. Setting this to the same directory across multiple builds of the + depends tree can eliminate unnecessary redownloading of package sources. + +* _**MAX_JOBS**_ + + Override the maximum number of jobs to run simultaneously, you might want to + do so on a memory-limited machine. This may be passed to `make` as in `make + --jobs="$MAX_JOBS"` or `xargs` as in `xargs -P"$MAX_JOBS"`. _(defaults to the + value of `nproc` outside the container)_ + +* _**SOURCE_DATE_EPOCH**_ + + Override the reference UNIX timestamp used for bit-for-bit reproducibility, + the variable name conforms to [standard][r12e/source-date-epoch]. _(defaults + to the output of `$(git log --format=%at -1)`)_ + +* _**V**_ + + If non-empty, will pass `V=1` to all `make` invocations, making `make` output + verbose. + +* _**ADDITIONAL_GUIX_ENVIRONMENT_FLAGS**_ + + Additional flags to be passed to `guix environment`. For a fully-bootstrapped + build, set this to `--bootstrap --no-substitutes` (refer to the [security + model section](#choosing-your-security-model) for more details). Note that a + fully-bootstrapped build will take quite a long time on the first run. + +## Tips and Tricks + +### Speeding up builds with substitute servers + +_This whole section is automatically done in the convenience +[Dockerfiles][fanquake/guix-docker]_ + +For those who are used to life in the fast _(and trustful)_ lane, you can use +[substitute servers][guix/substitutes] to enable binary downloads of packages. + +> For those who only want to use substitutes from the official Guix build farm +> and have authorized the build farm's signing key during Guix's installation, +> you don't need to do anything. + +#### Authorize the signing keys + +For the official Guix build farm at https://ci.guix.gnu.org, run as root: + +``` +guix archive --authorize < ~root/.config/guix/current/share/guix/ci.guix.gnu.org.pub +``` + +For dongcarl's substitute server at https://guix.carldong.io, run as root: + +```sh +wget -qO- 'https://guix.carldong.io/signing-key.pub' | guix archive --authorize +``` + +#### Use the substitute servers + +The official Guix build farm at https://ci.guix.gnu.org is automatically used +unless the `--no-substitutes` flag is supplied. + +This can be overridden for all `guix` invocations by passing the +`--substitute-urls` option to your invocation of `guix-daemon`. This can also be +overridden on a call-by-call basis by passing the same `--substitute-urls` +option to client tools such at `guix environment`. + +To use dongcarl's substitute server for Bitcoin Core builds after having +[authorized his signing key](#authorize-the-signing-keys): + +``` +export ADDITIONAL_GUIX_ENVIRONMENT_FLAGS='--substitute-urls="https://guix.carldong.io https://ci.guix.gnu.org"' +``` + +## FAQ + +### How can I trust the binary installation? + +As mentioned at the bottom of [this manual page][guix/bin-install]: + +> The binary installation tarballs can be (re)produced and verified simply by +> running the following command in the Guix source tree: +> +> make guix-binary.x86_64-linux.tar.xz + +### When will Guix be packaged in debian? + +Vagrant Cascadian has been making good progress on this +[here][debian/guix-package]. We have all the pieces needed to put up an APT +repository and will likely put one up soon. + +[b17e]: http://bootstrappable.org/ +[r12e/source-date-epoch]: https://reproducible-builds.org/docs/source-date-epoch/ + +[guix/install.sh]: https://git.savannah.gnu.org/cgit/guix.git/plain/etc/guix-install.sh +[guix/bin-install]: https://www.gnu.org/software/guix/manual/en/html_node/Binary-Installation.html +[guix/env-setup]: https://www.gnu.org/software/guix/manual/en/html_node/Build-Environment-Setup.html +[guix/substitutes]: https://www.gnu.org/software/guix/manual/en/html_node/Substitutes.html +[guix/substitute-server-auth]: https://www.gnu.org/software/guix/manual/en/html_node/Substitute-Server-Authorization.html +[guix/inferiors]: https://www.gnu.org/software/guix/manual/en/html_node/Inferiors.html +[guix/channels]: https://www.gnu.org/software/guix/manual/en/html_node/Channels.html + +[debian/guix-package]: https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=850644 +[fanquake/guix-docker]: https://github.com/fanquake/core-review/tree/master/guix diff --git a/contrib/guix/guix-build.sh b/contrib/guix/guix-build.sh new file mode 100755 index 0000000000..f8ba8c7ed2 --- /dev/null +++ b/contrib/guix/guix-build.sh @@ -0,0 +1,39 @@ +#!/usr/bin/env bash +export LC_ALL=C +set -e -o pipefail + +# Determine the maximum number of jobs to run simultaneously (overridable by +# environment) +MAX_JOBS="${MAX_JOBS:-$(nproc)}" + +# Download the depends sources now as we won't have internet access in the build +# container +make -C "${PWD}/depends" -j"$MAX_JOBS" download ${V:+V=1} ${SOURCES_PATH:+SOURCES_PATH="$SOURCES_PATH"} + +# Determine the reference time used for determinism (overridable by environment) +SOURCE_DATE_EPOCH="${SOURCE_DATE_EPOCH:-$(git log --format=%at -1)}" + +# Deterministically build Bitcoin Core for HOSTs (overriable by environment) +for host in ${HOSTS=i686-linux-gnu x86_64-linux-gnu arm-linux-gnueabihf aarch64-linux-gnu riscv64-linux-gnu}; do + + # Display proper warning when the user interrupts the build + trap 'echo "** INT received while building ${host}, you may want to clean up the relevant output and distsrc-* directories before rebuilding"' INT + + # Run the build script 'contrib/guix/libexec/build.sh' in the build + # container specified by 'contrib/guix/manifest.scm' + # shellcheck disable=SC2086 + guix environment --manifest="${PWD}/contrib/guix/manifest.scm" \ + --container \ + --pure \ + --no-cwd \ + --share="$PWD"=/bitcoin \ + ${SOURCES_PATH:+--share="$SOURCES_PATH"} \ + ${ADDITIONAL_GUIX_ENVIRONMENT_FLAGS} \ + -- env HOST="$host" \ + MAX_JOBS="$MAX_JOBS" \ + SOURCE_DATE_EPOCH="${SOURCE_DATE_EPOCH:?unable to determine value}" \ + ${V:+V=1} \ + ${SOURCES_PATH:+SOURCES_PATH="$SOURCES_PATH"} \ + bash -c "cd /bitcoin && bash contrib/guix/libexec/build.sh" + +done diff --git a/contrib/guix/libexec/build.sh b/contrib/guix/libexec/build.sh new file mode 100644 index 0000000000..56b972a5cb --- /dev/null +++ b/contrib/guix/libexec/build.sh @@ -0,0 +1,206 @@ +#!/usr/bin/env bash +export LC_ALL=C +set -e -o pipefail + +# Check that environment variables assumed to be set by the environment are set +echo "Building for platform triple ${HOST:?not set} with reference timestamp ${SOURCE_DATE_EPOCH:?not set}..." +echo "At most ${MAX_JOBS:?not set} jobs will run at once..." + +##################### +# Environment Setup # +##################### + +# The depends folder also serves as a base-prefix for depends packages for +# $HOSTs after successfully building. +BASEPREFIX="${PWD}/depends" + +# Setup an output directory for our build +OUTDIR="${OUTDIR:-${PWD}/output}" +[ -e "$OUTDIR" ] || mkdir -p "$OUTDIR" + +# Setup the directory where our Bitcoin Core build for HOST will occur +DISTSRC="${DISTSRC:-${PWD}/distsrc-${HOST}}" +if [ -e "$DISTSRC" ]; then + echo "DISTSRC directory '${DISTSRC}' exists, probably because of previous builds... Aborting..." + exit 1 +else + mkdir -p "$DISTSRC" +fi + +# Given a package name and an output name, return the path of that output in our +# current guix environment +store_path() { + grep --extended-regexp "/[^-]{32}-${1}-cross-${HOST}-[^-]+${2:+-${2}}" "${GUIX_ENVIRONMENT}/manifest" \ + | head --lines=1 \ + | sed --expression='s|^[[:space:]]*"||' \ + --expression='s|"[[:space:]]*$||' +} + +# Determine output paths to use in CROSS_* environment variables +CROSS_GLIBC="$(store_path glibc)" +CROSS_GLIBC_STATIC="$(store_path glibc static)" +CROSS_KERNEL="$(store_path linux-libre-headers)" +CROSS_GCC="$(store_path gcc)" + +# Set environment variables to point Guix's cross-toolchain to the right +# includes/libs for $HOST +export CROSS_C_INCLUDE_PATH="${CROSS_GCC}/include:${CROSS_GLIBC}/include:${CROSS_KERNEL}/include" +export CROSS_CPLUS_INCLUDE_PATH="${CROSS_GCC}/include/c++:${CROSS_GLIBC}/include:${CROSS_KERNEL}/include" +export CROSS_LIBRARY_PATH="${CROSS_GLIBC}/lib:${CROSS_GLIBC_STATIC}/lib:${CROSS_GCC}/lib:${CROSS_GCC}/${HOST}/lib:${CROSS_KERNEL}/lib" + +# Disable Guix ld auto-rpath behavior +export GUIX_LD_WRAPPER_DISABLE_RPATH=yes + +# Make /usr/bin if it doesn't exist +[ -e /usr/bin ] || mkdir -p /usr/bin + +# Symlink file and env to a conventional path +[ -e /usr/bin/file ] || ln -s --no-dereference "$(command -v file)" /usr/bin/file +[ -e /usr/bin/env ] || ln -s --no-dereference "$(command -v env)" /usr/bin/env + +# Determine the correct value for -Wl,--dynamic-linker for the current $HOST +glibc_dynamic_linker=$( + case "$HOST" in + i686-linux-gnu) echo /lib/ld-linux.so.2 ;; + x86_64-linux-gnu) echo /lib64/ld-linux-x86-64.so.2 ;; + arm-linux-gnueabihf) echo /lib/ld-linux-armhf.so.3 ;; + aarch64-linux-gnu) echo /lib/ld-linux-aarch64.so.1 ;; + riscv64-linux-gnu) echo /lib/ld-linux-riscv64-lp64d.so.1 ;; + *) exit 1 ;; + esac +) + +# Environment variables for determinism +export QT_RCC_TEST=1 +export QT_RCC_SOURCE_DATE_OVERRIDE=1 +export TAR_OPTIONS="--owner=0 --group=0 --numeric-owner --mtime='@${SOURCE_DATE_EPOCH}' --sort=name" +export TZ="UTC" + +#################### +# Depends Building # +#################### + +# Build the depends tree, overriding variables that assume multilib gcc +make -C depends --jobs="$MAX_JOBS" HOST="$HOST" \ + ${V:+V=1} \ + ${SOURCES_PATH+SOURCES_PATH="$SOURCES_PATH"} \ + i686_linux_CC=i686-linux-gnu-gcc \ + i686_linux_CXX=i686-linux-gnu-g++ \ + i686_linux_AR=i686-linux-gnu-ar \ + i686_linux_RANLIB=i686-linux-gnu-ranlib \ + i686_linux_NM=i686-linux-gnu-nm \ + i686_linux_STRIP=i686-linux-gnu-strip \ + x86_64_linux_CC=x86_64-linux-gnu-gcc \ + x86_64_linux_CXX=x86_64-linux-gnu-g++ \ + x86_64_linux_AR=x86_64-linux-gnu-ar \ + x86_64_linux_RANLIB=x86_64-linux-gnu-ranlib \ + x86_64_linux_NM=x86_64-linux-gnu-nm \ + x86_64_linux_STRIP=x86_64-linux-gnu-strip \ + qt_config_opts_i686_linux='-platform linux-g++ -xplatform bitcoin-linux-g++' + + +########################### +# Source Tarball Building # +########################### + +# Create the source tarball and move it to "${OUTDIR}/src" if not already there +if [ -z "$(find "${OUTDIR}/src" -name 'bitcoin-*.tar.gz')" ]; then + ./autogen.sh + env CONFIG_SITE="${BASEPREFIX}/${HOST}/share/config.site" ./configure --prefix=/ + make dist GZIP_ENV='-9n' ${V:+V=1} + mkdir -p "${OUTDIR}/src" + mv "$(find "${PWD}" -name 'bitcoin-*.tar.gz')" "${OUTDIR}/src/" +fi + +# Determine the full path to our source tarball +SOURCEDIST="$(find "${OUTDIR}/src" -name 'bitcoin-*.tar.gz')" +# Determine our distribution name (e.g. bitcoin-0.18.0) +DISTNAME="$(basename "$SOURCEDIST" '.tar.gz')" + +########################### +# Binary Tarball Building # +########################### + +# Create a spec file to normalize ssp linking behaviour +spec_file="$(mktemp)" +cat << EOF > "$spec_file" +*link_ssp: +%{fstack-protector|fstack-protector-all|fstack-protector-strong|fstack-protector-explicit:} +EOF + +# Similar flags to Gitian +CONFIGFLAGS="--enable-glibc-back-compat --enable-reduce-exports --disable-bench --disable-gui-tests" +HOST_CFLAGS="-O2 -g -specs=${spec_file} -ffile-prefix-map=${PWD}=." +HOST_CXXFLAGS="-O2 -g -specs=${spec_file} -ffile-prefix-map=${PWD}=." +HOST_LDFLAGS="-Wl,--as-needed -Wl,--dynamic-linker=$glibc_dynamic_linker -static-libstdc++" + +# Make $HOST-specific native binaries from depends available in $PATH +export PATH="${BASEPREFIX}/${HOST}/native/bin:${PATH}" +( + cd "$DISTSRC" + + # Extract the source tarball + tar --strip-components=1 -xf "${SOURCEDIST}" + + # Configure this DISTSRC for $HOST + # shellcheck disable=SC2086 + env CONFIG_SITE="${BASEPREFIX}/${HOST}/share/config.site" \ + ./configure --prefix=/ \ + --disable-ccache \ + --disable-maintainer-mode \ + --disable-dependency-tracking \ + ${CONFIGFLAGS} \ + CFLAGS="${HOST_CFLAGS}" \ + CXXFLAGS="${HOST_CXXFLAGS}" \ + LDFLAGS="${HOST_LDFLAGS}" + + sed -i.old 's/-lstdc++ //g' config.status libtool src/univalue/config.status src/univalue/libtool + + # Build Bitcoin Core + make --jobs="$MAX_JOBS" ${V:+V=1} + + # Perform basic ELF security checks on a series of executables. + make -C src --jobs=1 check-security ${V:+V=1} + # Check that executables only contain allowed gcc, glibc and libstdc++ + # version symbols for Linux distro back-compatibility. + make -C src --jobs=1 check-symbols ${V:+V=1} + + # Setup the directory where our Bitcoin Core build for HOST will be + # installed. This directory will also later serve as the input for our + # binary tarballs. + INSTALLPATH="${PWD}/installed/${DISTNAME}" + mkdir -p "${INSTALLPATH}" + # Install built Bitcoin Core to $INSTALLPATH + make install DESTDIR="${INSTALLPATH}" ${V:+V=1} + ( + cd installed + + # Prune libtool and object archives + find . -name "lib*.la" -delete + find . -name "lib*.a" -delete + + # Prune pkg-config files + rm -r "${DISTNAME}/lib/pkgconfig" + + # Split binaries and libraries from their debug symbols + { + find "${DISTNAME}/bin" -type f -executable -print0 + find "${DISTNAME}/lib" -type f -print0 + } | xargs -0 -n1 -P"$MAX_JOBS" -I{} "${DISTSRC}/contrib/devtools/split-debug.sh" {} {} {}.dbg + + cp "${DISTSRC}/doc/README.md" "${DISTNAME}/" + + # Finally, deterministically produce {non-,}debug binary tarballs ready + # for release + find "${DISTNAME}" -not -name "*.dbg" -print0 \ + | sort --zero-terminated \ + | tar --create --no-recursion --mode='u+rw,go+r-w,a+X' --null --files-from=- \ + | gzip -9n > "${OUTDIR}/${DISTNAME}-${HOST}.tar.gz" \ + || ( rm -f "${OUTDIR}/${DISTNAME}-${HOST}.tar.gz" && exit 1 ) + find "${DISTNAME}" -name "*.dbg" -print0 \ + | sort --zero-terminated \ + | tar --create --no-recursion --mode='u+rw,go+r-w,a+X' --null --files-from=- \ + | gzip -9n > "${OUTDIR}/${DISTNAME}-${HOST}-debug.tar.gz" \ + || ( rm -f "${OUTDIR}/${DISTNAME}-${HOST}-debug.tar.gz" && exit 1 ) + ) +) diff --git a/contrib/guix/manifest.scm b/contrib/guix/manifest.scm new file mode 100644 index 0000000000..ca11d7a0f0 --- /dev/null +++ b/contrib/guix/manifest.scm @@ -0,0 +1,158 @@ +(use-modules (gnu) + (gnu packages) + (gnu packages autotools) + (gnu packages base) + (gnu packages bash) + (gnu packages check) + (gnu packages commencement) + (gnu packages compression) + (gnu packages cross-base) + (gnu packages file) + (gnu packages gawk) + (gnu packages gcc) + (gnu packages linux) + (gnu packages perl) + (gnu packages pkg-config) + (gnu packages python) + (gnu packages shells) + (guix build-system trivial) + (guix gexp) + (guix packages) + (guix profiles) + (guix utils)) + +(define (make-ssp-fixed-gcc xgcc) + "Given a XGCC package, return a modified package that uses the SSP function +from glibc instead of from libssp.so. Taken from: +http://www.linuxfromscratch.org/hlfs/view/development/chapter05/gcc-pass1.html" + (package + (inherit xgcc) + (arguments + (substitute-keyword-arguments (package-arguments xgcc) + ((#:make-flags flags) + `(cons "gcc_cv_libc_provides_ssp=yes" ,flags)))))) + +(define (make-gcc-rpath-link xgcc) + "Given a XGCC package, return a modified package that replace each instance of +-rpath in the default system spec that's inserted by Guix with -rpath-link" + (package + (inherit xgcc) + (arguments + (substitute-keyword-arguments (package-arguments xgcc) + ((#:phases phases) + `(modify-phases ,phases + (add-after 'pre-configure 'replace-rpath-with-rpath-link + (lambda _ + (substitute* (cons "gcc/config/rs6000/sysv4.h" + (find-files "gcc/config" + "^gnu-user.*\\.h$")) + (("-rpath=") "-rpath-link=")) + #t)))))))) + +(define (make-cross-toolchain target + base-gcc-for-libc + base-kernel-headers + base-libc + base-gcc) + "Create a cross-compilation toolchain package for TARGET" + (let* ((xbinutils (cross-binutils target)) + ;; 1. Build a cross-compiling gcc without targeting any libc, derived + ;; from BASE-GCC-FOR-LIBC + (xgcc-sans-libc (cross-gcc target + #:xgcc base-gcc-for-libc + #:xbinutils xbinutils)) + ;; 2. Build cross-compiled kernel headers with XGCC-SANS-LIBC, derived + ;; from BASE-KERNEL-HEADERS + (xkernel (cross-kernel-headers target + base-kernel-headers + xgcc-sans-libc + xbinutils)) + ;; 3. Build a cross-compiled libc with XGCC-SANS-LIBC and XKERNEL, + ;; derived from BASE-LIBC + (xlibc (cross-libc target + base-libc + xgcc-sans-libc + xbinutils + xkernel)) + ;; 4. Build a cross-compiling gcc targeting XLIBC, derived from + ;; BASE-GCC + (xgcc (cross-gcc target + #:xgcc base-gcc + #:xbinutils xbinutils + #:libc xlibc))) + ;; Define a meta-package that propagates the resulting XBINUTILS, XLIBC, and + ;; XGCC + (package + (name (string-append target "-toolchain")) + (version (package-version xgcc)) + (source #f) + (build-system trivial-build-system) + (arguments '(#:builder (begin (mkdir %output) #t))) + (propagated-inputs + `(("binutils" ,xbinutils) + ("libc" ,xlibc) + ("libc:static" ,xlibc "static") + ("gcc" ,xgcc))) + (synopsis (string-append "Complete GCC tool chain for " target)) + (description (string-append "This package provides a complete GCC tool +chain for " target " development.")) + (home-page (package-home-page xgcc)) + (license (package-license xgcc))))) + +(define* (make-bitcoin-cross-toolchain target + #:key + (base-gcc-for-libc gcc-5) + (base-kernel-headers linux-libre-headers-4.19) + (base-libc glibc-2.27) + (base-gcc (make-gcc-rpath-link + (make-ssp-fixed-gcc gcc-9)))) + "Convienience wrapper around MAKE-CROSS-TOOLCHAIN with default values +desirable for building Bitcoin Core release binaries." + (make-cross-toolchain target + base-gcc-for-libc + base-kernel-headers + base-libc + base-gcc)) + +(packages->manifest + (list ;; The Basics + bash-minimal + which + coreutils + util-linux + ;; File(system) inspection + file + grep + diffutils + findutils + ;; File transformation + patch + gawk + sed + ;; Compression and archiving + tar + bzip2 + gzip + xz + zlib + ;; Build tools + gnu-make + libtool + autoconf + automake + pkg-config + ;; Scripting + perl + python-3.7 + ;; Native gcc 9 toolchain targeting glibc 2.27 + (make-gcc-toolchain gcc-9 glibc-2.27) + ;; Cross gcc 9 toolchains targeting glibc 2.27 + (make-bitcoin-cross-toolchain "i686-linux-gnu") + (make-bitcoin-cross-toolchain "x86_64-linux-gnu") + (make-bitcoin-cross-toolchain "aarch64-linux-gnu") + (make-bitcoin-cross-toolchain "arm-linux-gnueabihf") + ;; The glibc 2.27 for riscv64 needs gcc 7 to successfully build (see: + ;; https://www.gnu.org/software/gcc/gcc-7/changes.html#riscv). The final + ;; toolchain is still a gcc 9 toolchain targeting glibc 2.27. + (make-bitcoin-cross-toolchain "riscv64-linux-gnu" + #:base-gcc-for-libc gcc-7))) diff --git a/depends/packages/expat.mk b/depends/packages/expat.mk index b811f84a38..4784381915 100644 --- a/depends/packages/expat.mk +++ b/depends/packages/expat.mk @@ -1,11 +1,11 @@ package=expat -$(package)_version=2.2.6 -$(package)_download_path=https://github.com/libexpat/libexpat/releases/download/R_2_2_6/ +$(package)_version=2.2.7 +$(package)_download_path=https://github.com/libexpat/libexpat/releases/download/R_2_2_7/ $(package)_file_name=$(package)-$($(package)_version).tar.bz2 -$(package)_sha256_hash=17b43c2716d521369f82fc2dc70f359860e90fa440bea65b3b85f0b246ea81f2 +$(package)_sha256_hash=cbc9102f4a31a8dafd42d642e9a3aa31e79a0aedaa1f6efd2795ebc83174ec18 define $(package)_set_vars - $(package)_config_opts=--disable-shared --without-docbook + $(package)_config_opts=--disable-shared --without-docbook --without-tests --without-examples $(package)_config_opts_linux=--with-pic endef diff --git a/depends/packages/libX11.mk b/depends/packages/libX11.mk deleted file mode 100644 index f46bd9219e..0000000000 --- a/depends/packages/libX11.mk +++ /dev/null @@ -1,32 +0,0 @@ -package=libX11 -$(package)_version=1.6.2 -$(package)_download_path=https://xorg.freedesktop.org/releases/individual/lib/ -$(package)_file_name=$(package)-$($(package)_version).tar.bz2 -$(package)_sha256_hash=2aa027e837231d2eeea90f3a4afe19948a6eb4c8b2bec0241eba7dbc8106bd16 -$(package)_dependencies=libxcb xtrans xextproto xproto - -define $(package)_set_vars - # See libXext for --disable-malloc0returnsnull rationale. - $(package)_config_opts=--disable-xkb --disable-static --disable-malloc0returnsnull - $(package)_config_opts_linux=--with-pic -endef - -define $(package)_preprocess_cmds - cp -f $(BASEDIR)/config.guess $(BASEDIR)/config.sub . -endef - -define $(package)_config_cmds - $($(package)_autoconf) -endef - -define $(package)_build_cmds - $(MAKE) -endef - -define $(package)_stage_cmds - $(MAKE) DESTDIR=$($(package)_staging_dir) install -endef - -define $(package)_postprocess_cmds - rm lib/*.la -endef diff --git a/depends/packages/libXext.mk b/depends/packages/libXext.mk deleted file mode 100644 index 77f32a340e..0000000000 --- a/depends/packages/libXext.mk +++ /dev/null @@ -1,53 +0,0 @@ -package=libXext -$(package)_version=1.3.3 -$(package)_download_path=https://xorg.freedesktop.org/releases/individual/lib/ -$(package)_file_name=$(package)-$($(package)_version).tar.bz2 -$(package)_sha256_hash=b518d4d332231f313371fdefac59e3776f4f0823bcb23cf7c7305bfb57b16e35 -$(package)_dependencies=xproto xextproto libX11 libXau - -define $(package)_set_vars - # A number of steps in the autoconfig process implicitly assume that the build - # system and the host system are the same. For example, library components - # want to build and run test programs to determine the behavior of certain - # host system elements. This is clearly impossible when crosscompiling. To - # work around these issues, the --enable-malloc0returnsnull (or - # --disable-malloc0returnsnull, depending on the host system) must be passed - # to configure. - # -- https://www.x.org/wiki/CrossCompilingXorg/ - # - # Concretely, between the releases of libXext 1.3.2 and 1.3.3, - # XORG_CHECK_MALLOC_ZERO from xorg-macros was changed to use the autoconf - # cache, expecting cross-compilation environments to seed this cache as there - # is no single correct value when cross compiling (think uclibc, musl, etc.). - # You can see the actual change in commit 72fdc868b56fe2b7bdc9a69872651baeca72 - # in the freedesktop/xorg-macros repo. - # - # As a result of this change, if we don't seed the cache and we don't use - # either --{en,dis}able-malloc0returnsnull, the AC_RUN_IFELSE block has no - # optional action-if-cross-compiling argument and configure prints an error - # message and exits as documented in the autoconf manual. Prior to this - # commit, the AC_RUN_IFELSE block had an action-if-cross-compiling argument - # which set the more pessimistic default value MALLOC_ZERO_RETURNS_NULL=yes. - # This is why the flag was not required prior to libXext 1.3.3. - $(package)_config_opts=--disable-static --disable-malloc0returnsnull -endef - -define $(package)_preprocess_cmds - cp -f $(BASEDIR)/config.guess $(BASEDIR)/config.sub . -endef - -define $(package)_config_cmds - $($(package)_autoconf) -endef - -define $(package)_build_cmds - $(MAKE) -endef - -define $(package)_stage_cmds - $(MAKE) DESTDIR=$($(package)_staging_dir) install -endef - -define $(package)_postprocess_cmds - rm lib/*.la -endef diff --git a/depends/packages/libxcb.mk b/depends/packages/libxcb.mk index 9402951826..c497c5ac20 100644 --- a/depends/packages/libxcb.mk +++ b/depends/packages/libxcb.mk @@ -3,7 +3,7 @@ $(package)_version=1.10 $(package)_download_path=https://xcb.freedesktop.org/dist $(package)_file_name=$(package)-$($(package)_version).tar.bz2 $(package)_sha256_hash=98d9ab05b636dd088603b64229dd1ab2d2cc02ab807892e107d674f9c3f2d5b5 -$(package)_dependencies=xcb_proto libXau xproto +$(package)_dependencies=xcb_proto libXau define $(package)_set_vars $(package)_config_opts=--disable-static diff --git a/depends/packages/packages.mk b/depends/packages/packages.mk index 93f0918fe9..9edcd1eb38 100644 --- a/depends/packages/packages.mk +++ b/depends/packages/packages.mk @@ -3,7 +3,7 @@ packages:=boost openssl libevent qt_native_packages = native_protobuf qt_packages = qrencode protobuf zlib -qt_linux_packages:=qt expat libxcb xcb_proto libXau xproto freetype fontconfig libX11 xextproto libXext xtrans +qt_linux_packages:=qt expat libxcb xcb_proto libXau xproto freetype fontconfig rapidcheck_packages = rapidcheck diff --git a/depends/packages/qt.mk b/depends/packages/qt.mk index 23cde9ee6d..6f369e2c52 100644 --- a/depends/packages/qt.mk +++ b/depends/packages/qt.mk @@ -5,10 +5,10 @@ $(package)_suffix=opensource-src-$($(package)_version).tar.xz $(package)_file_name=qtbase-$($(package)_suffix) $(package)_sha256_hash=36dd9574f006eaa1e5af780e4b33d11fe39d09fd7c12f3b9d83294174bd28f00 $(package)_dependencies=openssl zlib -$(package)_linux_dependencies=freetype fontconfig libxcb libX11 xproto libXext +$(package)_linux_dependencies=freetype fontconfig libxcb $(package)_build_subdir=qtbase $(package)_qt_libs=corelib network widgets gui plugins testlib -$(package)_patches=fix_qt_pkgconfig.patch mac-qmake.conf fix_configure_mac.patch fix_no_printer.patch fix_rcc_determinism.patch fix_riscv64_arch.patch xkb-default.patch +$(package)_patches=fix_qt_pkgconfig.patch mac-qmake.conf fix_configure_mac.patch fix_no_printer.patch fix_rcc_determinism.patch fix_riscv64_arch.patch xkb-default.patch no-xlib.patch $(package)_qttranslations_file_name=qttranslations-$($(package)_suffix) $(package)_qttranslations_sha256_hash=b36da7d93c3ab6fca56b32053bb73bc619c8b192bb89b74e3bcde2705f1c2a14 @@ -98,6 +98,8 @@ endif $(package)_config_opts_linux = -qt-xkbcommon-x11 $(package)_config_opts_linux += -qt-xcb +$(package)_config_opts_linux += -no-xcb-xlib +$(package)_config_opts_linux += -no-feature-xlib $(package)_config_opts_linux += -system-freetype $(package)_config_opts_linux += -no-feature-sessionmanager $(package)_config_opts_linux += -fontconfig @@ -156,11 +158,13 @@ define $(package)_preprocess_cmds echo "!host_build: QMAKE_CXXFLAGS += $($(package)_cxxflags) $($(package)_cppflags)" >> qtbase/mkspecs/common/gcc-base.conf && \ echo "!host_build: QMAKE_LFLAGS += $($(package)_ldflags)" >> qtbase/mkspecs/common/gcc-base.conf && \ patch -p1 -i $($(package)_patch_dir)/fix_riscv64_arch.patch &&\ + patch -p1 -i $($(package)_patch_dir)/no-xlib.patch &&\ echo "QMAKE_LINK_OBJECT_MAX = 10" >> qtbase/mkspecs/win32-g++/qmake.conf &&\ echo "QMAKE_LINK_OBJECT_SCRIPT = object_script" >> qtbase/mkspecs/win32-g++/qmake.conf &&\ sed -i.old "s|QMAKE_CFLAGS = |!host_build: QMAKE_CFLAGS = $($(package)_cflags) $($(package)_cppflags) |" qtbase/mkspecs/win32-g++/qmake.conf && \ sed -i.old "s|QMAKE_LFLAGS = |!host_build: QMAKE_LFLAGS = $($(package)_ldflags) |" qtbase/mkspecs/win32-g++/qmake.conf && \ - sed -i.old "s|QMAKE_CXXFLAGS = |!host_build: QMAKE_CXXFLAGS = $($(package)_cxxflags) $($(package)_cppflags) |" qtbase/mkspecs/win32-g++/qmake.conf + sed -i.old "s|QMAKE_CXXFLAGS = |!host_build: QMAKE_CXXFLAGS = $($(package)_cxxflags) $($(package)_cppflags) |" qtbase/mkspecs/win32-g++/qmake.conf && \ + sed -i.old "s/LIBRARY_PATH/(CROSS_)?\0/g" qtbase/mkspecs/features/toolchain.prf endef define $(package)_config_cmds diff --git a/depends/packages/xextproto.mk b/depends/packages/xextproto.mk deleted file mode 100644 index 157b76edf6..0000000000 --- a/depends/packages/xextproto.mk +++ /dev/null @@ -1,25 +0,0 @@ -package=xextproto -$(package)_version=7.3.0 -$(package)_download_path=https://xorg.freedesktop.org/releases/individual/proto -$(package)_file_name=$(package)-$($(package)_version).tar.bz2 -$(package)_sha256_hash=f3f4b23ac8db9c3a9e0d8edb591713f3d70ef9c3b175970dd8823dfc92aa5bb0 - -define $(package)_preprocess_cmds - cp -f $(BASEDIR)/config.guess $(BASEDIR)/config.sub . -endef - -define $(package)_set_vars -$(package)_config_opts=--disable-shared -endef - -define $(package)_config_cmds - $($(package)_autoconf) -endef - -define $(package)_build_cmds - $(MAKE) -endef - -define $(package)_stage_cmds - $(MAKE) DESTDIR=$($(package)_staging_dir) install -endef diff --git a/depends/packages/xtrans.mk b/depends/packages/xtrans.mk deleted file mode 100644 index 6201d1d270..0000000000 --- a/depends/packages/xtrans.mk +++ /dev/null @@ -1,26 +0,0 @@ -package=xtrans -$(package)_version=1.3.4 -$(package)_download_path=https://xorg.freedesktop.org/releases/individual/lib/ -$(package)_file_name=$(package)-$($(package)_version).tar.bz2 -$(package)_sha256_hash=054d4ee3efd52508c753e9f7bc655ef185a29bd2850dd9e2fc2ccc33544f583a -$(package)_dependencies= - -define $(package)_set_vars - $(package)_config_opts_linux=--disable-docs --without-xmlto --without-fop --without-xsltproc -endef - -define $(package)_preprocess_cmds - cp -f $(BASEDIR)/config.guess $(BASEDIR)/config.sub . -endef - -define $(package)_config_cmds - $($(package)_autoconf) -endef - -define $(package)_build_cmds - $(MAKE) -endef - -define $(package)_stage_cmds - $(MAKE) DESTDIR=$($(package)_staging_dir) install -endef diff --git a/depends/patches/qt/no-xlib.patch b/depends/patches/qt/no-xlib.patch new file mode 100644 index 0000000000..fe82c2c73c --- /dev/null +++ b/depends/patches/qt/no-xlib.patch @@ -0,0 +1,69 @@ +From 9563cef873ae82e06f60708d706d054717e801ce Mon Sep 17 00:00:00 2001 +From: Carl Dong <contact@carldong.me> +Date: Thu, 18 Jul 2019 17:22:05 -0400 +Subject: [PATCH] Wrap xlib related code blocks in #if's + +They are not necessary to compile QT. +--- + qtbase/src/plugins/platforms/xcb/qxcbcursor.cpp | 8 ++++++++ + 1 file changed, 8 insertions(+) + +diff --git a/qtbase/src/plugins/platforms/xcb/qxcbcursor.cpp b/qtbase/src/plugins/platforms/xcb/qxcbcursor.cpp +index 7c62c2e2b3..c05c6c0a07 100644 +--- a/qtbase/src/plugins/platforms/xcb/qxcbcursor.cpp ++++ b/qtbase/src/plugins/platforms/xcb/qxcbcursor.cpp +@@ -49,7 +49,9 @@ + #include <QtGui/QWindow> + #include <QtGui/QBitmap> + #include <QtGui/private/qguiapplication_p.h> ++#if QT_CONFIG(xcb_xlib) && QT_CONFIG(library) + #include <X11/cursorfont.h> ++#endif + #include <xcb/xfixes.h> + #include <xcb/xcb_image.h> + +@@ -384,6 +386,7 @@ void QXcbCursor::changeCursor(QCursor *cursor, QWindow *widget) + w->setCursor(c, isBitmapCursor); + } + ++#if QT_CONFIG(xcb_xlib) && QT_CONFIG(library) + static int cursorIdForShape(int cshape) + { + int cursorId = 0; +@@ -437,6 +440,7 @@ static int cursorIdForShape(int cshape) + } + return cursorId; + } ++#endif + + xcb_cursor_t QXcbCursor::createNonStandardCursor(int cshape) + { +@@ -558,7 +562,9 @@ static xcb_cursor_t loadCursor(void *dpy, int cshape) + xcb_cursor_t QXcbCursor::createFontCursor(int cshape) + { + xcb_connection_t *conn = xcb_connection(); ++#if QT_CONFIG(xcb_xlib) && QT_CONFIG(library) + int cursorId = cursorIdForShape(cshape); ++#endif + xcb_cursor_t cursor = XCB_NONE; + + // Try Xcursor first +@@ -589,6 +595,7 @@ xcb_cursor_t QXcbCursor::createFontCursor(int cshape) + // Non-standard X11 cursors are created from bitmaps + cursor = createNonStandardCursor(cshape); + ++#if QT_CONFIG(xcb_xlib) && QT_CONFIG(library) + // Create a glpyh cursor if everything else failed + if (!cursor && cursorId) { + cursor = xcb_generate_id(conn); +@@ -596,6 +603,7 @@ xcb_cursor_t QXcbCursor::createFontCursor(int cshape) + cursorId, cursorId + 1, + 0xFFFF, 0xFFFF, 0xFFFF, 0, 0, 0); + } ++#endif + + if (cursor && cshape >= 0 && cshape < Qt::LastCursor && connection()->hasXFixes()) { + const char *name = cursorNames[cshape]; +-- +2.22.0 + diff --git a/doc/bips.md b/doc/bips.md index eb24ce6f66..3085fa424b 100644 --- a/doc/bips.md +++ b/doc/bips.md @@ -12,8 +12,8 @@ BIPs that are implemented by Bitcoin Core (up-to-date up to **v0.18.0**): * [`BIP 31`](https://github.com/bitcoin/bips/blob/master/bip-0031.mediawiki): The 'pong' protocol message (and the protocol version bump to 60001) has been implemented since **v0.6.1** ([PR #1081](https://github.com/bitcoin/bitcoin/pull/1081)). * [`BIP 32`](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki): Hierarchical Deterministic Wallets has been implemented since **v0.13.0** ([PR #8035](https://github.com/bitcoin/bitcoin/pull/8035)). * [`BIP 34`](https://github.com/bitcoin/bips/blob/master/bip-0034.mediawiki): The rule that requires blocks to contain their height (number) in the coinbase input, and the introduction of version 2 blocks has been implemented since **v0.7.0**. The rule took effect for version 2 blocks as of *block 224413* (March 5th 2013), and version 1 blocks are no longer allowed since *block 227931* (March 25th 2013) ([PR #1526](https://github.com/bitcoin/bitcoin/pull/1526)). -* [`BIP 35`](https://github.com/bitcoin/bips/blob/master/bip-0035.mediawiki): The 'mempool' protocol message (and the protocol version bump to 60002) has been implemented since **v0.7.0** ([PR #1641](https://github.com/bitcoin/bitcoin/pull/1641)). -* [`BIP 37`](https://github.com/bitcoin/bips/blob/master/bip-0037.mediawiki): The bloom filtering for transaction relaying, partial Merkle trees for blocks, and the protocol version bump to 70001 (enabling low-bandwidth SPV clients) has been implemented since **v0.8.0** ([PR #1795](https://github.com/bitcoin/bitcoin/pull/1795)). +* [`BIP 35`](https://github.com/bitcoin/bips/blob/master/bip-0035.mediawiki): The 'mempool' protocol message (and the protocol version bump to 60002) has been implemented since **v0.7.0** ([PR #1641](https://github.com/bitcoin/bitcoin/pull/1641)). As of **v0.13.0**, this is only available for `NODE_BLOOM` (BIP 111) peers. +* [`BIP 37`](https://github.com/bitcoin/bips/blob/master/bip-0037.mediawiki): The bloom filtering for transaction relaying, partial Merkle trees for blocks, and the protocol version bump to 70001 (enabling low-bandwidth SPV clients) has been implemented since **v0.8.0** ([PR #1795](https://github.com/bitcoin/bitcoin/pull/1795)). Disabled by default since **v0.19.0**, can be enabled by the `-peerbloomfilters` option. * [`BIP 42`](https://github.com/bitcoin/bips/blob/master/bip-0042.mediawiki): The bug that would have caused the subsidy schedule to resume after block 13440000 was fixed in **v0.9.2** ([PR #3842](https://github.com/bitcoin/bitcoin/pull/3842)). * [`BIP 61`](https://github.com/bitcoin/bips/blob/master/bip-0061.mediawiki): The 'reject' protocol message (and the protocol version bump to 70002) was added in **v0.9.0** ([PR #3185](https://github.com/bitcoin/bitcoin/pull/3185)). Starting **v0.17.0**, whether to send reject messages can be configured with the `-enablebip61` option, and support is deprecated as of **v0.18.0**. * [`BIP 65`](https://github.com/bitcoin/bips/blob/master/bip-0065.mediawiki): The CHECKLOCKTIMEVERIFY softfork was merged in **v0.12.0** ([PR #6351](https://github.com/bitcoin/bitcoin/pull/6351)), and backported to **v0.11.2** and **v0.10.4**. Mempool-only CLTV was added in [PR #6124](https://github.com/bitcoin/bitcoin/pull/6124). diff --git a/doc/dependencies.md b/doc/dependencies.md index 54eca143c5..c9c6a93c01 100644 --- a/doc/dependencies.md +++ b/doc/dependencies.md @@ -8,7 +8,7 @@ These are the dependencies currently used by Bitcoin Core. You can find instruct | Berkeley DB | [4.8.30](https://www.oracle.com/technetwork/database/database-technologies/berkeleydb/downloads/index.html) | 4.8.x | No | | | | Boost | [1.70.0](https://www.boost.org/users/download/) | [1.47.0](https://github.com/bitcoin/bitcoin/pull/8920) | No | | | | Clang | | [3.3+](https://llvm.org/releases/download.html) (C++11 support) | | | | -| Expat | [2.2.6](https://libexpat.github.io/) | | No | Yes | | +| Expat | [2.2.7](https://libexpat.github.io/) | | No | Yes | | | fontconfig | [2.12.1](https://www.freedesktop.org/software/fontconfig/release/) | | No | Yes | | | FreeType | [2.7.1](https://download.savannah.gnu.org/releases/freetype) | | No | | | | GCC | | [4.8+](https://gcc.gnu.org/) (C++11 support) | | | | diff --git a/doc/developer-notes.md b/doc/developer-notes.md index ecd720539e..39463dc6f8 100644 --- a/doc/developer-notes.md +++ b/doc/developer-notes.md @@ -375,7 +375,7 @@ reported in the debug.log file. Re-architecting the core code so there are better-defined interfaces between the various components is a goal, with any necessary locking -done by the components (e.g. see the self-contained `CBasicKeyStore` class +done by the components (e.g. see the self-contained `FillableSigningProvider` class and its `cs_KeyStore` lock for example). Threads diff --git a/doc/fuzzing.md b/doc/fuzzing.md index f9221dde5b..3dc6be8b86 100644 --- a/doc/fuzzing.md +++ b/doc/fuzzing.md @@ -46,7 +46,7 @@ export AFLPATH=$PWD To build Bitcoin Core using AFL instrumentation (this assumes that the `AFLPATH` was set as above): ``` -./configure --disable-ccache --disable-shared --enable-tests --enable-fuzz --disable-wallet --disable-bench --with-utils=no --with-daemon=no --with-libs=no --with-gui=no CC=${AFLPATH}/afl-gcc CXX=${AFLPATH}/afl-g++ +./configure --disable-ccache --disable-shared --enable-tests --enable-fuzz CC=${AFLPATH}/afl-gcc CXX=${AFLPATH}/afl-g++ export AFL_HARDEN=1 cd src/ make @@ -83,7 +83,7 @@ found in the `compiler-rt` runtime libraries package). To build all fuzz targets with libFuzzer, run ``` -./configure --disable-ccache --disable-wallet --disable-bench --with-utils=no --with-daemon=no --with-libs=no --with-gui=no --enable-fuzz --with-sanitizers=fuzzer,address CC=clang CXX=clang++ +./configure --disable-ccache --enable-fuzz --with-sanitizers=fuzzer,address CC=clang CXX=clang++ make ``` diff --git a/doc/release-notes.md b/doc/release-notes.md index 9efb6cbabb..83c84d34c9 100644 --- a/doc/release-notes.md +++ b/doc/release-notes.md @@ -103,6 +103,20 @@ Low-level Changes section below. Low-level changes ================= +RPC +--- + + +Tests +----- + +- The regression test chain, that can be enabled by the `-regtest` command line + flag, now requires transactions to not violate standard policy by default. + Making the default the same as for mainnet, makes it easier to test mainnet + behavior on regtest. Be reminded that the testnet still allows non-standard + txs by default and that the policy can be locally adjusted with the + `-acceptnonstdtxn` command line flag for both test chains. + Configuration ------------ diff --git a/doc/release-notes/release-notes-16152.md b/doc/release-notes/release-notes-16152.md new file mode 100644 index 0000000000..9c77cb9ae5 --- /dev/null +++ b/doc/release-notes/release-notes-16152.md @@ -0,0 +1,7 @@ +P2P Changes +----------- +- The default value for the -peerbloomfilters configuration option (and, thus, NODE_BLOOM support) has been changed to false. + This resolves well-known DoS vectors in Bitcoin Core, especially for nodes with spinning disks. It is not anticipated that + this will result in a significant lack of availability of NODE_BLOOM-enabled nodes in the coming years, however, clients + which rely on the availability of NODE_BLOOM-supporting nodes on the P2P network should consider the process of migrating + to a more modern (and less trustful and privacy-violating) alternative over the coming years. diff --git a/src/Makefile.am b/src/Makefile.am index 39e8d3d689..0ed412295f 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -143,7 +143,6 @@ BITCOIN_CORE_H = \ interfaces/wallet.h \ key.h \ key_io.h \ - keystore.h \ dbwrapper.h \ limitedmap.h \ logging.h \ @@ -175,14 +174,17 @@ BITCOIN_CORE_H = \ rpc/blockchain.h \ rpc/client.h \ rpc/protocol.h \ - rpc/server.h \ rpc/rawtransaction_util.h \ rpc/register.h \ + rpc/request.h \ + rpc/server.h \ rpc/util.h \ scheduler.h \ script/descriptor.h \ + script/keyorigin.h \ script/sigcache.h \ script/sign.h \ + script/signingprovider.h \ script/standard.h \ shutdown.h \ streams.h \ @@ -351,6 +353,8 @@ crypto_libbitcoin_crypto_base_a_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) crypto_libbitcoin_crypto_base_a_SOURCES = \ crypto/aes.cpp \ crypto/aes.h \ + crypto/chacha_poly_aead.h \ + crypto/chacha_poly_aead.cpp \ crypto/chacha20.h \ crypto/chacha20.cpp \ crypto/common.h \ @@ -446,7 +450,6 @@ libbitcoin_common_a_SOURCES = \ core_write.cpp \ key.cpp \ key_io.cpp \ - keystore.cpp \ merkleblock.cpp \ netaddress.cpp \ netbase.cpp \ @@ -460,6 +463,7 @@ libbitcoin_common_a_SOURCES = \ scheduler.cpp \ script/descriptor.cpp \ script/sign.cpp \ + script/signingprovider.cpp \ script/standard.cpp \ versionbitsinfo.cpp \ warnings.cpp \ @@ -481,7 +485,7 @@ libbitcoin_util_a_SOURCES = \ interfaces/handler.cpp \ logging.cpp \ random.cpp \ - rpc/protocol.cpp \ + rpc/request.cpp \ support/cleanse.cpp \ sync.cpp \ threadinterrupt.cpp \ @@ -613,7 +617,7 @@ bitcoin_wallet_LDADD += $(BOOST_LIBS) $(BDB_LIBS) $(CRYPTO_LIBS) $(EVENT_PTHREAD # bitcoinconsensus library # if BUILD_BITCOIN_LIBS include_HEADERS = script/bitcoinconsensus.h -libbitcoinconsensus_la_SOURCES = $(crypto_libbitcoin_crypto_base_a_SOURCES) $(libbitcoin_consensus_a_SOURCES) +libbitcoinconsensus_la_SOURCES = support/cleanse.cpp $(crypto_libbitcoin_crypto_base_a_SOURCES) $(libbitcoin_consensus_a_SOURCES) if GLIBC_BACK_COMPAT libbitcoinconsensus_la_SOURCES += compat/glibc_compat.cpp diff --git a/src/Makefile.bench.include b/src/Makefile.bench.include index 1b01b50b07..e421b377a0 100644 --- a/src/Makefile.bench.include +++ b/src/Makefile.bench.include @@ -24,11 +24,13 @@ bench_bench_bitcoin_SOURCES = \ bench/examples.cpp \ bench/rollingbloom.cpp \ bench/chacha20.cpp \ + bench/chacha_poly_aead.cpp \ bench/crypto_hash.cpp \ bench/ccoins_caching.cpp \ bench/gcs_filter.cpp \ bench/merkle_root.cpp \ bench/mempool_eviction.cpp \ + bench/rpc_blockchain.cpp \ bench/rpc_mempool.cpp \ bench/util_time.cpp \ bench/verify_script.cpp \ diff --git a/src/bench/ccoins_caching.cpp b/src/bench/ccoins_caching.cpp index 1041a22303..39cab092cf 100644 --- a/src/bench/ccoins_caching.cpp +++ b/src/bench/ccoins_caching.cpp @@ -5,7 +5,7 @@ #include <bench/bench.h> #include <coins.h> #include <policy/policy.h> -#include <wallet/crypter.h> +#include <script/signingprovider.h> #include <vector> @@ -17,7 +17,7 @@ // paid to a TX_PUBKEYHASH. // static std::vector<CMutableTransaction> -SetupDummyInputs(CBasicKeyStore& keystoreRet, CCoinsViewCache& coinsRet) +SetupDummyInputs(FillableSigningProvider& keystoreRet, CCoinsViewCache& coinsRet) { std::vector<CMutableTransaction> dummyTransactions; dummyTransactions.resize(2); @@ -55,7 +55,7 @@ SetupDummyInputs(CBasicKeyStore& keystoreRet, CCoinsViewCache& coinsRet) // (https://github.com/bitcoin/bitcoin/issues/7883#issuecomment-224807484) static void CCoinsCaching(benchmark::State& state) { - CBasicKeyStore keystore; + FillableSigningProvider keystore; CCoinsView coinsDummy; CCoinsViewCache coins(&coinsDummy); std::vector<CMutableTransaction> dummyTransactions = SetupDummyInputs(keystore, coins); diff --git a/src/bench/chacha_poly_aead.cpp b/src/bench/chacha_poly_aead.cpp new file mode 100644 index 0000000000..f5f7297490 --- /dev/null +++ b/src/bench/chacha_poly_aead.cpp @@ -0,0 +1,123 @@ +// Copyright (c) 2019 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include <iostream> + +#include <bench/bench.h> +#include <crypto/chacha_poly_aead.h> +#include <crypto/poly1305.h> // for the POLY1305_TAGLEN constant +#include <hash.h> + +#include <limits> +#include <assert.h> + +/* Number of bytes to process per iteration */ +static constexpr uint64_t BUFFER_SIZE_TINY = 64; +static constexpr uint64_t BUFFER_SIZE_SMALL = 256; +static constexpr uint64_t BUFFER_SIZE_LARGE = 1024 * 1024; + +static const unsigned char k1[32] = {0}; +static const unsigned char k2[32] = {0}; + +static ChaCha20Poly1305AEAD aead(k1, 32, k2, 32); + +static void CHACHA20_POLY1305_AEAD(benchmark::State& state, size_t buffersize, bool include_decryption) +{ + std::vector<unsigned char> in(buffersize + CHACHA20_POLY1305_AEAD_AAD_LEN + POLY1305_TAGLEN, 0); + std::vector<unsigned char> out(buffersize + CHACHA20_POLY1305_AEAD_AAD_LEN + POLY1305_TAGLEN, 0); + uint64_t seqnr_payload = 0; + uint64_t seqnr_aad = 0; + int aad_pos = 0; + uint32_t len = 0; + while (state.KeepRunning()) { + // encrypt or decrypt the buffer with a static key + assert(aead.Crypt(seqnr_payload, seqnr_aad, aad_pos, out.data(), out.size(), in.data(), buffersize, true)); + + if (include_decryption) { + // if we decrypt, include the GetLength + assert(aead.GetLength(&len, seqnr_aad, aad_pos, in.data())); + assert(aead.Crypt(seqnr_payload, seqnr_aad, aad_pos, out.data(), out.size(), in.data(), buffersize, true)); + } + + // increase main sequence number + seqnr_payload++; + // increase aad position (position in AAD keystream) + aad_pos += CHACHA20_POLY1305_AEAD_AAD_LEN; + if (aad_pos + CHACHA20_POLY1305_AEAD_AAD_LEN > CHACHA20_ROUND_OUTPUT) { + aad_pos = 0; + seqnr_aad++; + } + if (seqnr_payload + 1 == std::numeric_limits<uint64_t>::max()) { + // reuse of nonce+key is okay while benchmarking. + seqnr_payload = 0; + seqnr_aad = 0; + aad_pos = 0; + } + } +} + +static void CHACHA20_POLY1305_AEAD_64BYTES_ONLY_ENCRYPT(benchmark::State& state) +{ + CHACHA20_POLY1305_AEAD(state, BUFFER_SIZE_TINY, false); +} + +static void CHACHA20_POLY1305_AEAD_256BYTES_ONLY_ENCRYPT(benchmark::State& state) +{ + CHACHA20_POLY1305_AEAD(state, BUFFER_SIZE_SMALL, false); +} + +static void CHACHA20_POLY1305_AEAD_1MB_ONLY_ENCRYPT(benchmark::State& state) +{ + CHACHA20_POLY1305_AEAD(state, BUFFER_SIZE_LARGE, false); +} + +static void CHACHA20_POLY1305_AEAD_64BYTES_ENCRYPT_DECRYPT(benchmark::State& state) +{ + CHACHA20_POLY1305_AEAD(state, BUFFER_SIZE_TINY, true); +} + +static void CHACHA20_POLY1305_AEAD_256BYTES_ENCRYPT_DECRYPT(benchmark::State& state) +{ + CHACHA20_POLY1305_AEAD(state, BUFFER_SIZE_SMALL, true); +} + +static void CHACHA20_POLY1305_AEAD_1MB_ENCRYPT_DECRYPT(benchmark::State& state) +{ + CHACHA20_POLY1305_AEAD(state, BUFFER_SIZE_LARGE, true); +} + +// Add Hash() (dbl-sha256) bench for comparison + +static void HASH(benchmark::State& state, size_t buffersize) +{ + uint8_t hash[CHash256::OUTPUT_SIZE]; + std::vector<uint8_t> in(buffersize,0); + while (state.KeepRunning()) + CHash256().Write(in.data(), in.size()).Finalize(hash); +} + +static void HASH_64BYTES(benchmark::State& state) +{ + HASH(state, BUFFER_SIZE_TINY); +} + +static void HASH_256BYTES(benchmark::State& state) +{ + HASH(state, BUFFER_SIZE_SMALL); +} + +static void HASH_1MB(benchmark::State& state) +{ + HASH(state, BUFFER_SIZE_LARGE); +} + +BENCHMARK(CHACHA20_POLY1305_AEAD_64BYTES_ONLY_ENCRYPT, 500000); +BENCHMARK(CHACHA20_POLY1305_AEAD_256BYTES_ONLY_ENCRYPT, 250000); +BENCHMARK(CHACHA20_POLY1305_AEAD_1MB_ONLY_ENCRYPT, 340); +BENCHMARK(CHACHA20_POLY1305_AEAD_64BYTES_ENCRYPT_DECRYPT, 500000); +BENCHMARK(CHACHA20_POLY1305_AEAD_256BYTES_ENCRYPT_DECRYPT, 250000); +BENCHMARK(CHACHA20_POLY1305_AEAD_1MB_ENCRYPT_DECRYPT, 340); +BENCHMARK(HASH_64BYTES, 500000); +BENCHMARK(HASH_256BYTES, 250000); +BENCHMARK(HASH_1MB, 340); diff --git a/src/bench/rpc_blockchain.cpp b/src/bench/rpc_blockchain.cpp new file mode 100644 index 0000000000..29e448fc43 --- /dev/null +++ b/src/bench/rpc_blockchain.cpp @@ -0,0 +1,33 @@ +// Copyright (c) 2016-2019 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include <bench/bench.h> +#include <bench/data.h> + +#include <validation.h> +#include <streams.h> +#include <consensus/validation.h> +#include <rpc/blockchain.h> + +#include <univalue.h> + +static void BlockToJsonVerbose(benchmark::State& state) { + CDataStream stream(benchmark::data::block413567, SER_NETWORK, PROTOCOL_VERSION); + char a = '\0'; + stream.write(&a, 1); // Prevent compaction + + CBlock block; + stream >> block; + + CBlockIndex blockindex; + const uint256 blockHash = block.GetHash(); + blockindex.phashBlock = &blockHash; + blockindex.nBits = 403014710; + + while (state.KeepRunning()) { + (void)blockToJSON(block, &blockindex, &blockindex, /*verbose*/ true); + } +} + +BENCHMARK(BlockToJsonVerbose, 10); diff --git a/src/bitcoin-cli.cpp b/src/bitcoin-cli.cpp index 38010c461e..d3419520a7 100644 --- a/src/bitcoin-cli.cpp +++ b/src/bitcoin-cli.cpp @@ -12,6 +12,7 @@ #include <fs.h> #include <rpc/client.h> #include <rpc/protocol.h> +#include <rpc/request.h> #include <util/system.h> #include <util/strencodings.h> diff --git a/src/bitcoin-tx.cpp b/src/bitcoin-tx.cpp index 933b34744d..4d513deed8 100644 --- a/src/bitcoin-tx.cpp +++ b/src/bitcoin-tx.cpp @@ -11,12 +11,12 @@ #include <consensus/consensus.h> #include <core_io.h> #include <key_io.h> -#include <keystore.h> #include <policy/policy.h> #include <policy/rbf.h> #include <primitives/transaction.h> #include <script/script.h> #include <script/sign.h> +#include <script/signingprovider.h> #include <univalue.h> #include <util/rbf.h> #include <util/system.h> @@ -557,7 +557,7 @@ static void MutateTxSign(CMutableTransaction& tx, const std::string& flagStr) if (!registers.count("privatekeys")) throw std::runtime_error("privatekeys register variable must be set."); - CBasicKeyStore tempKeystore; + FillableSigningProvider tempKeystore; UniValue keysObj = registers["privatekeys"]; for (unsigned int kidx = 0; kidx < keysObj.size(); kidx++) { @@ -631,7 +631,7 @@ static void MutateTxSign(CMutableTransaction& tx, const std::string& flagStr) } } - const CKeyStore& keystore = tempKeystore; + const FillableSigningProvider& keystore = tempKeystore; bool fHashSingle = ((nHashType & ~SIGHASH_ANYONECANPAY) == SIGHASH_SINGLE); diff --git a/src/chainparams.cpp b/src/chainparams.cpp index b8e0ea23dd..f937e2754b 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -141,7 +141,7 @@ public: fDefaultConsistencyChecks = false; fRequireStandard = true; - fMineBlocksOnDemand = false; + m_is_test_chain = false; checkpointData = { { @@ -247,7 +247,7 @@ public: fDefaultConsistencyChecks = false; fRequireStandard = false; - fMineBlocksOnDemand = false; + m_is_test_chain = true; checkpointData = { @@ -324,8 +324,8 @@ public: vSeeds.clear(); //!< Regtest mode doesn't have any DNS seeds. fDefaultConsistencyChecks = true; - fRequireStandard = false; - fMineBlocksOnDemand = true; + fRequireStandard = true; + m_is_test_chain = true; checkpointData = { { diff --git a/src/chainparams.h b/src/chainparams.h index 6ff3dbb7e5..b3fcd77cea 100644 --- a/src/chainparams.h +++ b/src/chainparams.h @@ -66,13 +66,15 @@ public: bool DefaultConsistencyChecks() const { return fDefaultConsistencyChecks; } /** Policy: Filter transactions that do not match well-defined patterns */ bool RequireStandard() const { return fRequireStandard; } + /** If this is a test chain */ + bool IsTestChain() const { return m_is_test_chain; } uint64_t PruneAfterHeight() const { return nPruneAfterHeight; } /** Minimum free space (in GB) needed for data directory */ uint64_t AssumedBlockchainSize() const { return m_assumed_blockchain_size; } /** Minimum free space (in GB) needed for data directory when pruned; Does not include prune target*/ uint64_t AssumedChainStateSize() const { return m_assumed_chain_state_size; } - /** Make miner stop after a block is found. In RPC, don't return until nGenProcLimit blocks are generated */ - bool MineBlocksOnDemand() const { return fMineBlocksOnDemand; } + /** Whether it is possible to mine blocks on demand (no retargeting) */ + bool MineBlocksOnDemand() const { return consensus.fPowNoRetargeting; } /** Return the BIP70 network string (main, test or regtest) */ std::string NetworkIDString() const { return strNetworkID; } /** Return true if the fallback fee is by default enabled for this network */ @@ -101,7 +103,7 @@ protected: std::vector<SeedSpec6> vFixedSeeds; bool fDefaultConsistencyChecks; bool fRequireStandard; - bool fMineBlocksOnDemand; + bool m_is_test_chain; CCheckpointData checkpointData; ChainTxData chainTxData; bool m_fallback_fee_enabled; diff --git a/src/coins.cpp b/src/coins.cpp index 3ef9e0463c..6b85edd01a 100644 --- a/src/coins.cpp +++ b/src/coins.cpp @@ -5,6 +5,7 @@ #include <coins.h> #include <consensus/consensus.h> +#include <logging.h> #include <random.h> #include <version.h> @@ -258,3 +259,19 @@ const Coin& AccessByTxid(const CCoinsViewCache& view, const uint256& txid) } return coinEmpty; } + +bool CCoinsViewErrorCatcher::GetCoin(const COutPoint &outpoint, Coin &coin) const { + try { + return CCoinsViewBacked::GetCoin(outpoint, coin); + } catch(const std::runtime_error& e) { + for (auto f : m_err_callbacks) { + f(); + } + LogPrintf("Error reading from database: %s\n", e.what()); + // Starting the shutdown sequence and returning false to the caller would be + // interpreted as 'entry not found' (as opposed to unable to read data), and + // could lead to invalid interpretation. Just exit immediately, as we can't + // continue anyway, and all writes should be atomic. + std::abort(); + } +} diff --git a/src/coins.h b/src/coins.h index 482e233e8c..dca1beabb6 100644 --- a/src/coins.h +++ b/src/coins.h @@ -17,6 +17,7 @@ #include <assert.h> #include <stdint.h> +#include <functional> #include <unordered_map> /** @@ -315,4 +316,28 @@ void AddCoins(CCoinsViewCache& cache, const CTransaction& tx, int nHeight, bool //! lookups to database, so it should be used with care. const Coin& AccessByTxid(const CCoinsViewCache& cache, const uint256& txid); +/** + * This is a minimally invasive approach to shutdown on LevelDB read errors from the + * chainstate, while keeping user interface out of the common library, which is shared + * between bitcoind, and bitcoin-qt and non-server tools. + * + * Writes do not need similar protection, as failure to write is handled by the caller. +*/ +class CCoinsViewErrorCatcher final : public CCoinsViewBacked +{ +public: + explicit CCoinsViewErrorCatcher(CCoinsView* view) : CCoinsViewBacked(view) {} + + void AddReadErrCallback(std::function<void()> f) { + m_err_callbacks.emplace_back(std::move(f)); + } + + bool GetCoin(const COutPoint &outpoint, Coin &coin) const override; + +private: + /** A list of callbacks to execute upon leveldb read error. */ + std::vector<std::function<void()>> m_err_callbacks; + +}; + #endif // BITCOIN_COINS_H diff --git a/src/crypto/chacha_poly_aead.cpp b/src/crypto/chacha_poly_aead.cpp new file mode 100644 index 0000000000..6a3d43deb1 --- /dev/null +++ b/src/crypto/chacha_poly_aead.cpp @@ -0,0 +1,126 @@ +// Copyright (c) 2019 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include <crypto/chacha_poly_aead.h> + +#include <crypto/common.h> +#include <crypto/poly1305.h> +#include <support/cleanse.h> + +#include <assert.h> +#include <string.h> + +#include <cstdio> +#include <limits> + +#ifndef HAVE_TIMINGSAFE_BCMP + +int timingsafe_bcmp(const unsigned char* b1, const unsigned char* b2, size_t n) +{ + const unsigned char *p1 = b1, *p2 = b2; + int ret = 0; + + for (; n > 0; n--) + ret |= *p1++ ^ *p2++; + return (ret != 0); +} + +#endif // TIMINGSAFE_BCMP + +ChaCha20Poly1305AEAD::ChaCha20Poly1305AEAD(const unsigned char* K_1, size_t K_1_len, const unsigned char* K_2, size_t K_2_len) +{ + assert(K_1_len == CHACHA20_POLY1305_AEAD_KEY_LEN); + assert(K_2_len == CHACHA20_POLY1305_AEAD_KEY_LEN); + m_chacha_main.SetKey(K_1, CHACHA20_POLY1305_AEAD_KEY_LEN); + m_chacha_header.SetKey(K_2, CHACHA20_POLY1305_AEAD_KEY_LEN); + + // set the cached sequence number to uint64 max which hints for an unset cache. + // we can't hit uint64 max since the rekey rule (which resets the sequence number) is 1GB + m_cached_aad_seqnr = std::numeric_limits<uint64_t>::max(); +} + +bool ChaCha20Poly1305AEAD::Crypt(uint64_t seqnr_payload, uint64_t seqnr_aad, int aad_pos, unsigned char* dest, size_t dest_len /* length of the output buffer for sanity checks */, const unsigned char* src, size_t src_len, bool is_encrypt) +{ + // check buffer boundaries + if ( + // if we encrypt, make sure the source contains at least the expected AAD and the destination has at least space for the source + MAC + (is_encrypt && (src_len < CHACHA20_POLY1305_AEAD_AAD_LEN || dest_len < src_len + POLY1305_TAGLEN)) || + // if we decrypt, make sure the source contains at least the expected AAD+MAC and the destination has at least space for the source - MAC + (!is_encrypt && (src_len < CHACHA20_POLY1305_AEAD_AAD_LEN + POLY1305_TAGLEN || dest_len < src_len - POLY1305_TAGLEN))) { + return false; + } + + unsigned char expected_tag[POLY1305_TAGLEN], poly_key[POLY1305_KEYLEN]; + memset(poly_key, 0, sizeof(poly_key)); + m_chacha_main.SetIV(seqnr_payload); + + // block counter 0 for the poly1305 key + // use lower 32bytes for the poly1305 key + // (throws away 32 unused bytes (upper 32) from this ChaCha20 round) + m_chacha_main.Seek(0); + m_chacha_main.Crypt(poly_key, poly_key, sizeof(poly_key)); + + // if decrypting, verify the tag prior to decryption + if (!is_encrypt) { + const unsigned char* tag = src + src_len - POLY1305_TAGLEN; + poly1305_auth(expected_tag, src, src_len - POLY1305_TAGLEN, poly_key); + + // constant time compare the calculated MAC with the provided MAC + if (timingsafe_bcmp(expected_tag, tag, POLY1305_TAGLEN) != 0) { + memory_cleanse(expected_tag, sizeof(expected_tag)); + memory_cleanse(poly_key, sizeof(poly_key)); + return false; + } + memory_cleanse(expected_tag, sizeof(expected_tag)); + // MAC has been successfully verified, make sure we don't covert it in decryption + src_len -= POLY1305_TAGLEN; + } + + // calculate and cache the next 64byte keystream block if requested sequence number is not yet the cache + if (m_cached_aad_seqnr != seqnr_aad) { + m_cached_aad_seqnr = seqnr_aad; + m_chacha_header.SetIV(seqnr_aad); + m_chacha_header.Seek(0); + m_chacha_header.Keystream(m_aad_keystream_buffer, CHACHA20_ROUND_OUTPUT); + } + // crypt the AAD (3 bytes message length) with given position in AAD cipher instance keystream + dest[0] = src[0] ^ m_aad_keystream_buffer[aad_pos]; + dest[1] = src[1] ^ m_aad_keystream_buffer[aad_pos + 1]; + dest[2] = src[2] ^ m_aad_keystream_buffer[aad_pos + 2]; + + // Set the playload ChaCha instance block counter to 1 and crypt the payload + m_chacha_main.Seek(1); + m_chacha_main.Crypt(src + CHACHA20_POLY1305_AEAD_AAD_LEN, dest + CHACHA20_POLY1305_AEAD_AAD_LEN, src_len - CHACHA20_POLY1305_AEAD_AAD_LEN); + + // If encrypting, calculate and append tag + if (is_encrypt) { + // the poly1305 tag expands over the AAD (3 bytes length) & encrypted payload + poly1305_auth(dest + src_len, dest, src_len, poly_key); + } + + // cleanse no longer required MAC and polykey + memory_cleanse(poly_key, sizeof(poly_key)); + return true; +} + +bool ChaCha20Poly1305AEAD::GetLength(uint32_t* len24_out, uint64_t seqnr_aad, int aad_pos, const uint8_t* ciphertext) +{ + // enforce valid aad position to avoid accessing outside of the 64byte keystream cache + // (there is space for 21 times 3 bytes) + assert(aad_pos >= 0 && aad_pos < CHACHA20_ROUND_OUTPUT - CHACHA20_POLY1305_AEAD_AAD_LEN); + if (m_cached_aad_seqnr != seqnr_aad) { + // we need to calculate the 64 keystream bytes since we reached a new aad sequence number + m_cached_aad_seqnr = seqnr_aad; + m_chacha_header.SetIV(seqnr_aad); // use LE for the nonce + m_chacha_header.Seek(0); // block counter 0 + m_chacha_header.Keystream(m_aad_keystream_buffer, CHACHA20_ROUND_OUTPUT); // write keystream to the cache + } + + // decrypt the ciphertext length by XORing the right position of the 64byte keystream cache with the ciphertext + *len24_out = (ciphertext[0] ^ m_aad_keystream_buffer[aad_pos + 0]) | + (ciphertext[1] ^ m_aad_keystream_buffer[aad_pos + 1]) << 8 | + (ciphertext[2] ^ m_aad_keystream_buffer[aad_pos + 2]) << 16; + + return true; +} diff --git a/src/crypto/chacha_poly_aead.h b/src/crypto/chacha_poly_aead.h new file mode 100644 index 0000000000..b3ba781cdd --- /dev/null +++ b/src/crypto/chacha_poly_aead.h @@ -0,0 +1,146 @@ +// Copyright (c) 2019 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_CRYPTO_CHACHA_POLY_AEAD_H +#define BITCOIN_CRYPTO_CHACHA_POLY_AEAD_H + +#include <crypto/chacha20.h> + +#include <cmath> + +static constexpr int CHACHA20_POLY1305_AEAD_KEY_LEN = 32; +static constexpr int CHACHA20_POLY1305_AEAD_AAD_LEN = 3; /* 3 bytes length */ +static constexpr int CHACHA20_ROUND_OUTPUT = 64; /* 64 bytes per round */ +static constexpr int AAD_PACKAGES_PER_ROUND = 21; /* 64 / 3 round down*/ + +/* A AEAD class for ChaCha20-Poly1305@bitcoin. + * + * ChaCha20 is a stream cipher designed by Daniel Bernstein and described in + * <ref>[http://cr.yp.to/chacha/chacha-20080128.pdf ChaCha20]</ref>. It operates + * by permuting 128 fixed bits, 128 or 256 bits of key, a 64 bit nonce and a 64 + * bit counter into 64 bytes of output. This output is used as a keystream, with + * any unused bytes simply discarded. + * + * Poly1305 <ref>[http://cr.yp.to/mac/poly1305-20050329.pdf Poly1305]</ref>, also + * by Daniel Bernstein, is a one-time Carter-Wegman MAC that computes a 128 bit + * integrity tag given a message and a single-use 256 bit secret key. + * + * The chacha20-poly1305@bitcoin combines these two primitives into an + * authenticated encryption mode. The construction used is based on that proposed + * for TLS by Adam Langley in + * <ref>[http://tools.ietf.org/html/draft-agl-tls-chacha20poly1305-03 "ChaCha20 + * and Poly1305 based Cipher Suites for TLS", Adam Langley]</ref>, but differs in + * the layout of data passed to the MAC and in the addition of encryption of the + * packet lengths. + * + * ==== Detailed Construction ==== + * + * The chacha20-poly1305@bitcoin cipher requires two 256 bits of key material as + * output from the key exchange. Each key (K_1 and K_2) are used by two separate + * instances of chacha20. + * + * The instance keyed by K_1 is a stream cipher that is used only to encrypt the 3 + * byte packet length field and has its own sequence number. The second instance, + * keyed by K_2, is used in conjunction with poly1305 to build an AEAD + * (Authenticated Encryption with Associated Data) that is used to encrypt and + * authenticate the entire packet. + * + * Two separate cipher instances are used here so as to keep the packet lengths + * confidential but not create an oracle for the packet payload cipher by + * decrypting and using the packet length prior to checking the MAC. By using an + * independently-keyed cipher instance to encrypt the length, an active attacker + * seeking to exploit the packet input handling as a decryption oracle can learn + * nothing about the payload contents or its MAC (assuming key derivation, + * ChaCha20 and Poly1305 are secure). + * + * The AEAD is constructed as follows: for each packet, generate a Poly1305 key by + * taking the first 256 bits of ChaCha20 stream output generated using K_2, an IV + * consisting of the packet sequence number encoded as an LE uint64 and a ChaCha20 + * block counter of zero. The K_2 ChaCha20 block counter is then set to the + * little-endian encoding of 1 (i.e. {1, 0, 0, 0, 0, 0, 0, 0}) and this instance + * is used for encryption of the packet payload. + * + * ==== Packet Handling ==== + * + * When receiving a packet, the length must be decrypted first. When 3 bytes of + * ciphertext length have been received, they may be decrypted. + * + * A ChaCha20 round always calculates 64bytes which is sufficient to crypt 21 + * times a 3 bytes length field (21*3 = 63). The length field sequence number can + * thus be used 21 times (keystream caching). + * + * The length field must be enc-/decrypted with the ChaCha20 keystream keyed with + * K_1 defined by block counter 0, the length field sequence number in little + * endian and a keystream position from 0 to 60. + * + * Once the entire packet has been received, the MAC MUST be checked before + * decryption. A per-packet Poly1305 key is generated as described above and the + * MAC tag calculated using Poly1305 with this key over the ciphertext of the + * packet length and the payload together. The calculated MAC is then compared in + * constant time with the one appended to the packet and the packet decrypted + * using ChaCha20 as described above (with K_2, the packet sequence number as + * nonce and a starting block counter of 1). + * + * Detection of an invalid MAC MUST lead to immediate connection termination. + * + * To send a packet, first encode the 3 byte length and encrypt it using K_1 as + * described above. Encrypt the packet payload (using K_2) and append it to the + * encrypted length. Finally, calculate a MAC tag and append it. + * + * The initiating peer MUST use <code>K_1_A, K_2_A</code> to encrypt messages on + * the send channel, <code>K_1_B, K_2_B</code> MUST be used to decrypt messages on + * the receive channel. + * + * The responding peer MUST use <code>K_1_A, K_2_A</code> to decrypt messages on + * the receive channel, <code>K_1_B, K_2_B</code> MUST be used to encrypt messages + * on the send channel. + * + * Optimized implementations of ChaCha20-Poly1305@bitcoin are relatively fast in + * general, therefore it is very likely that encrypted messages require not more + * CPU cycles per bytes then the current unencrypted p2p message format + * (ChaCha20/Poly1305 versus double SHA256). + * + * The initial packet sequence numbers are 0. + * + * K_2 ChaCha20 cipher instance (payload) must never reuse a {key, nonce} for + * encryption nor may it be used to encrypt more than 2^70 bytes under the same + * {key, nonce}. + * + * K_1 ChaCha20 cipher instance (length field/AAD) must never reuse a {key, nonce, + * position-in-keystream} for encryption nor may it be used to encrypt more than + * 2^70 bytes under the same {key, nonce}. + * + * We use message sequence numbers for both communication directions. + */ + +class ChaCha20Poly1305AEAD +{ +private: + ChaCha20 m_chacha_main; // payload and poly1305 key-derivation cipher instance + ChaCha20 m_chacha_header; // AAD cipher instance (encrypted length) + unsigned char m_aad_keystream_buffer[CHACHA20_ROUND_OUTPUT]; // aad keystream cache + uint64_t m_cached_aad_seqnr; // aad keystream cache hint + +public: + ChaCha20Poly1305AEAD(const unsigned char* K_1, size_t K_1_len, const unsigned char* K_2, size_t K_2_len); + + explicit ChaCha20Poly1305AEAD(const ChaCha20Poly1305AEAD&) = delete; + + /** Encrypts/decrypts a packet + seqnr_payload, the message sequence number + seqnr_aad, the messages AAD sequence number which allows reuse of the AAD keystream + aad_pos, position to use in the AAD keystream to encrypt the AAD + dest, output buffer, must be of a size equal or larger then CHACHA20_POLY1305_AEAD_AAD_LEN + payload (+ POLY1305_TAG_LEN in encryption) bytes + destlen, length of the destination buffer + src, the AAD+payload to encrypt or the AAD+payload+MAC to decrypt + src_len, the length of the source buffer + is_encrypt, set to true if we encrypt (creates and appends the MAC instead of verifying it) + */ + bool Crypt(uint64_t seqnr_payload, uint64_t seqnr_aad, int aad_pos, unsigned char* dest, size_t dest_len, const unsigned char* src, size_t src_len, bool is_encrypt); + + /** decrypts the 3 bytes AAD data and decodes it into a uint32_t field */ + bool GetLength(uint32_t* len24_out, uint64_t seqnr_aad, int aad_pos, const uint8_t* ciphertext); +}; + +#endif // BITCOIN_CRYPTO_CHACHA_POLY_AEAD_H diff --git a/src/index/blockfilterindex.cpp b/src/index/blockfilterindex.cpp index 20f33baf2c..c3ce8d7af0 100644 --- a/src/index/blockfilterindex.cpp +++ b/src/index/blockfilterindex.cpp @@ -53,7 +53,7 @@ struct DBHeightKey { int height; DBHeightKey() : height(0) {} - DBHeightKey(int height_in) : height(height_in) {} + explicit DBHeightKey(int height_in) : height(height_in) {} template<typename Stream> void Serialize(Stream& s) const @@ -76,7 +76,7 @@ struct DBHeightKey { struct DBHashKey { uint256 hash; - DBHashKey(const uint256& hash_in) : hash(hash_in) {} + explicit DBHashKey(const uint256& hash_in) : hash(hash_in) {} ADD_SERIALIZE_METHODS; diff --git a/src/init.cpp b/src/init.cpp index 5d7c3b9af7..068b05286c 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -15,6 +15,7 @@ #include <blockfilter.h> #include <chain.h> #include <chainparams.h> +#include <coins.h> #include <compat/sanity.h> #include <consensus/validation.h> #include <fs.h> @@ -146,31 +147,6 @@ NODISCARD static bool CreatePidFile() // shutdown thing. // -/** - * This is a minimally invasive approach to shutdown on LevelDB read errors from the - * chainstate, while keeping user interface out of the common library, which is shared - * between bitcoind, and bitcoin-qt and non-server tools. -*/ -class CCoinsViewErrorCatcher final : public CCoinsViewBacked -{ -public: - explicit CCoinsViewErrorCatcher(CCoinsView* view) : CCoinsViewBacked(view) {} - bool GetCoin(const COutPoint &outpoint, Coin &coin) const override { - try { - return CCoinsViewBacked::GetCoin(outpoint, coin); - } catch(const std::runtime_error& e) { - uiInterface.ThreadSafeMessageBox(_("Error reading from database, shutting down."), "", CClientUIInterface::MSG_ERROR); - LogPrintf("Error reading from database: %s\n", e.what()); - // Starting the shutdown sequence and returning false to the caller would be - // interpreted as 'entry not found' (as opposed to unable to read data), and - // could lead to invalid interpretation. Just exit immediately, as we can't - // continue anyway, and all writes should be atomic. - abort(); - } - } - // Writes do not need similar protection, as failure to write is handled by the caller. -}; - static std::unique_ptr<CCoinsViewErrorCatcher> pcoinscatcher; static std::unique_ptr<ECCVerifyHandle> globalVerifyHandle; @@ -492,7 +468,7 @@ void SetupServerArgs() "and level 4 tries to reconnect the blocks, " "each level includes the checks of the previous levels " "(0-4, default: %u)", DEFAULT_CHECKLEVEL), true, OptionsCategory::DEBUG_TEST); - gArgs.AddArg("-checkblockindex", strprintf("Do a full consistency check for mapBlockIndex, setBlockIndexCandidates, ::ChainActive() and mapBlocksUnlinked occasionally. (default: %u, regtest: %u)", defaultChainParams->DefaultConsistencyChecks(), regtestChainParams->DefaultConsistencyChecks()), true, OptionsCategory::DEBUG_TEST); + gArgs.AddArg("-checkblockindex", strprintf("Do a full consistency check for the block tree, setBlockIndexCandidates, ::ChainActive() and mapBlocksUnlinked occasionally. (default: %u, regtest: %u)", defaultChainParams->DefaultConsistencyChecks(), regtestChainParams->DefaultConsistencyChecks()), true, OptionsCategory::DEBUG_TEST); gArgs.AddArg("-checkmempool=<n>", strprintf("Run checks every <n> transactions (default: %u, regtest: %u)", defaultChainParams->DefaultConsistencyChecks(), regtestChainParams->DefaultConsistencyChecks()), true, OptionsCategory::DEBUG_TEST); gArgs.AddArg("-checkpoints", strprintf("Disable expensive verification for known chain history (default: %u)", DEFAULT_CHECKPOINTS_ENABLED), true, OptionsCategory::DEBUG_TEST); gArgs.AddArg("-deprecatedrpc=<method>", "Allows deprecated RPC method(s) to be used", true, OptionsCategory::DEBUG_TEST); @@ -1150,8 +1126,9 @@ bool AppInitParameterInteraction() } fRequireStandard = !gArgs.GetBoolArg("-acceptnonstdtxn", !chainparams.RequireStandard()); - if (chainparams.RequireStandard() && !fRequireStandard) + if (!chainparams.IsTestChain() && !fRequireStandard) { return InitError(strprintf("acceptnonstdtxn is not currently supported for %s chain", chainparams.NetworkIDString())); + } nBytesPerSigOp = gArgs.GetArg("-bytespersigop", nBytesPerSigOp); if (!g_wallet_init_interface.ParameterInteraction()) return false; @@ -1517,7 +1494,8 @@ bool AppInitMain(InitInterfaces& interfaces) // If the loaded chain has a wrong genesis, bail out immediately // (we're likely using a testnet datadir, or the other way around). - if (!mapBlockIndex.empty() && !LookupBlockIndex(chainparams.GetConsensus().hashGenesisBlock)) { + if (!::BlockIndex().empty() && + !LookupBlockIndex(chainparams.GetConsensus().hashGenesisBlock)) { return InitError(_("Incorrect or no genesis block found. Wrong datadir for network?")); } @@ -1538,10 +1516,15 @@ bool AppInitMain(InitInterfaces& interfaces) } // At this point we're either in reindex or we've loaded a useful - // block tree into mapBlockIndex! + // block tree into BlockIndex()! pcoinsdbview.reset(new CCoinsViewDB(nCoinDBCache, false, fReset || fReindexChainState)); pcoinscatcher.reset(new CCoinsViewErrorCatcher(pcoinsdbview.get())); + pcoinscatcher->AddReadErrCallback([]() { + uiInterface.ThreadSafeMessageBox( + _("Error reading from database, shutting down."), + "", CClientUIInterface::MSG_ERROR); + }); // If necessary, upgrade from older database format. // This is a no-op if we cleared the coinsviewdb with -reindex or -reindex-chainstate @@ -1577,7 +1560,7 @@ bool AppInitMain(InitInterfaces& interfaces) if (!fReset) { // Note that RewindBlockIndex MUST run even if we're about to -reindex-chainstate. // It both disconnects blocks based on ::ChainActive(), and drops block data in - // mapBlockIndex based on lack of available witness data. + // BlockIndex() based on lack of available witness data. uiInterface.InitMessage(_("Rewinding blocks...")); if (!RewindBlockIndex(chainparams)) { strLoadError = _("Unable to rewind the database to a pre-fork state. You will need to redownload the blockchain"); @@ -1749,7 +1732,7 @@ bool AppInitMain(InitInterfaces& interfaces) //// debug print { LOCK(cs_main); - LogPrintf("mapBlockIndex.size() = %u\n", mapBlockIndex.size()); + LogPrintf("block tree size = %u\n", ::BlockIndex().size()); chain_active_height = ::ChainActive().Height(); } LogPrintf("nBestHeight = %d\n", chain_active_height); diff --git a/src/interfaces/chain.cpp b/src/interfaces/chain.cpp index 161dd01ffe..02f39cef8e 100644 --- a/src/interfaces/chain.cpp +++ b/src/interfaces/chain.cpp @@ -205,7 +205,7 @@ public: class RpcHandlerImpl : public Handler { public: - RpcHandlerImpl(const CRPCCommand& command) : m_command(command), m_wrapped_command(&command) + explicit RpcHandlerImpl(const CRPCCommand& command) : m_command(command), m_wrapped_command(&command) { m_command.actor = [this](const JSONRPCRequest& request, UniValue& result, bool last_handler) { if (!m_wrapped_command) return false; diff --git a/src/interfaces/wallet.cpp b/src/interfaces/wallet.cpp index 34c982e1e6..deb1618ceb 100644 --- a/src/interfaces/wallet.cpp +++ b/src/interfaces/wallet.cpp @@ -36,7 +36,7 @@ namespace { class PendingWalletTxImpl : public PendingWalletTx { public: - explicit PendingWalletTxImpl(CWallet& wallet) : m_wallet(wallet), m_key(&wallet) {} + explicit PendingWalletTxImpl(CWallet& wallet) : m_wallet(wallet) {} const CTransaction& get() override { return *m_tx; } @@ -47,7 +47,7 @@ public: auto locked_chain = m_wallet.chain().lock(); LOCK(m_wallet.cs_wallet); CValidationState state; - if (!m_wallet.CommitTransaction(m_tx, std::move(value_map), std::move(order_form), m_key, state)) { + if (!m_wallet.CommitTransaction(m_tx, std::move(value_map), std::move(order_form), state)) { reject_reason = state.GetRejectReason(); return false; } @@ -56,7 +56,6 @@ public: CTransactionRef m_tx; CWallet& m_wallet; - CReserveKey m_key; }; //! Construct wallet tx struct. @@ -140,9 +139,11 @@ public: void abortRescan() override { m_wallet->AbortRescan(); } bool backupWallet(const std::string& filename) override { return m_wallet->BackupWallet(filename); } std::string getWalletName() override { return m_wallet->GetName(); } - bool getKeyFromPool(bool internal, CPubKey& pub_key) override + bool getNewDestination(const OutputType type, const std::string label, CTxDestination& dest) override { - return m_wallet->GetKeyFromPool(pub_key, internal); + LOCK(m_wallet->cs_wallet); + std::string error; + return m_wallet->GetNewDestination(type, label, dest, error); } bool getPubKey(const CKeyID& address, CPubKey& pub_key) override { return m_wallet->GetPubKey(address, pub_key); } bool getPrivKey(const CKeyID& address, CKey& key) override { return m_wallet->GetKey(address, key); } @@ -236,7 +237,7 @@ public: auto locked_chain = m_wallet->chain().lock(); LOCK(m_wallet->cs_wallet); auto pending = MakeUnique<PendingWalletTxImpl>(*m_wallet); - if (!m_wallet->CreateTransaction(*locked_chain, recipients, pending->m_tx, pending->m_key, fee, change_pos, + if (!m_wallet->CreateTransaction(*locked_chain, recipients, pending->m_tx, fee, change_pos, fail_reason, coin_control, sign)) { return {}; } @@ -476,7 +477,7 @@ public: } std::unique_ptr<Handler> handleStatusChanged(StatusChangedFn fn) override { - return MakeHandler(m_wallet->NotifyStatusChanged.connect([fn](CCryptoKeyStore*) { fn(); })); + return MakeHandler(m_wallet->NotifyStatusChanged.connect([fn](CWallet*) { fn(); })); } std::unique_ptr<Handler> handleAddressBookChanged(AddressBookChangedFn fn) override { diff --git a/src/interfaces/wallet.h b/src/interfaces/wallet.h index 9c9b29a813..db47dbafaf 100644 --- a/src/interfaces/wallet.h +++ b/src/interfaces/wallet.h @@ -78,8 +78,8 @@ public: //! Get wallet name. virtual std::string getWalletName() = 0; - // Get key from pool. - virtual bool getKeyFromPool(bool internal, CPubKey& pub_key) = 0; + // Get a new address. + virtual bool getNewDestination(const OutputType type, const std::string label, CTxDestination& dest) = 0; //! Get public key. virtual bool getPubKey(const CKeyID& address, CPubKey& pub_key) = 0; diff --git a/src/keystore.h b/src/keystore.h deleted file mode 100644 index 4bd99e255d..0000000000 --- a/src/keystore.h +++ /dev/null @@ -1,83 +0,0 @@ -// Copyright (c) 2009-2010 Satoshi Nakamoto -// Copyright (c) 2009-2018 The Bitcoin Core developers -// Distributed under the MIT software license, see the accompanying -// file COPYING or http://www.opensource.org/licenses/mit-license.php. - -#ifndef BITCOIN_KEYSTORE_H -#define BITCOIN_KEYSTORE_H - -#include <key.h> -#include <pubkey.h> -#include <script/script.h> -#include <script/sign.h> -#include <script/standard.h> -#include <sync.h> - -#include <boost/signals2/signal.hpp> - -/** A virtual base class for key stores */ -class CKeyStore : public SigningProvider -{ -public: - //! Add a key to the store. - virtual bool AddKeyPubKey(const CKey &key, const CPubKey &pubkey) =0; - - //! Check whether a key corresponding to a given address is present in the store. - virtual bool HaveKey(const CKeyID &address) const =0; - virtual std::set<CKeyID> GetKeys() const =0; - - //! Support for BIP 0013 : see https://github.com/bitcoin/bips/blob/master/bip-0013.mediawiki - virtual bool AddCScript(const CScript& redeemScript) =0; - virtual bool HaveCScript(const CScriptID &hash) const =0; - virtual std::set<CScriptID> GetCScripts() const =0; - - //! Support for Watch-only addresses - virtual bool AddWatchOnly(const CScript &dest) =0; - virtual bool RemoveWatchOnly(const CScript &dest) =0; - virtual bool HaveWatchOnly(const CScript &dest) const =0; - virtual bool HaveWatchOnly() const =0; -}; - -/** Basic key store, that keeps keys in an address->secret map */ -class CBasicKeyStore : public CKeyStore -{ -protected: - mutable CCriticalSection cs_KeyStore; - - using KeyMap = std::map<CKeyID, CKey>; - using WatchKeyMap = std::map<CKeyID, CPubKey>; - using ScriptMap = std::map<CScriptID, CScript>; - using WatchOnlySet = std::set<CScript>; - - KeyMap mapKeys GUARDED_BY(cs_KeyStore); - WatchKeyMap mapWatchKeys GUARDED_BY(cs_KeyStore); - ScriptMap mapScripts GUARDED_BY(cs_KeyStore); - WatchOnlySet setWatchOnly GUARDED_BY(cs_KeyStore); - - void ImplicitlyLearnRelatedKeyScripts(const CPubKey& pubkey) EXCLUSIVE_LOCKS_REQUIRED(cs_KeyStore); - -public: - bool AddKeyPubKey(const CKey& key, const CPubKey &pubkey) override; - bool AddKey(const CKey &key) { return AddKeyPubKey(key, key.GetPubKey()); } - bool GetPubKey(const CKeyID &address, CPubKey& vchPubKeyOut) const override; - bool HaveKey(const CKeyID &address) const override; - std::set<CKeyID> GetKeys() const override; - bool GetKey(const CKeyID &address, CKey &keyOut) const override; - bool AddCScript(const CScript& redeemScript) override; - bool HaveCScript(const CScriptID &hash) const override; - std::set<CScriptID> GetCScripts() const override; - bool GetCScript(const CScriptID &hash, CScript& redeemScriptOut) const override; - - bool AddWatchOnly(const CScript &dest) override; - bool RemoveWatchOnly(const CScript &dest) override; - bool HaveWatchOnly(const CScript &dest) const override; - bool HaveWatchOnly() const override; -}; - -/** Return the CKeyID of the key involved in a script (if there is a unique one). */ -CKeyID GetKeyForDestination(const CKeyStore& store, const CTxDestination& dest); - -/** Checks if a CKey is in the given CKeyStore compressed or otherwise*/ -bool HaveKey(const CKeyStore& store, const CKey& key); - -#endif // BITCOIN_KEYSTORE_H diff --git a/src/net_processing.h b/src/net_processing.h index 39c22d7118..dffc3f273f 100644 --- a/src/net_processing.h +++ b/src/net_processing.h @@ -19,6 +19,7 @@ static const unsigned int DEFAULT_MAX_ORPHAN_TRANSACTIONS = 100; static const unsigned int DEFAULT_BLOCK_RECONSTRUCTION_EXTRA_TXN = 100; /** Default for BIP61 (sending reject messages) */ static constexpr bool DEFAULT_ENABLE_BIP61{false}; +static const bool DEFAULT_PEERBLOOMFILTERS = false; class PeerLogicValidation final : public CValidationInterface, public NetEventsInterface { private: diff --git a/src/netbase.cpp b/src/netbase.cpp index 78b3b6ae3a..6d4738c835 100644 --- a/src/netbase.cpp +++ b/src/netbase.cpp @@ -65,6 +65,12 @@ bool static LookupIntern(const char *pszName, std::vector<CNetAddr>& vIP, unsign { CNetAddr addr; + // From our perspective, onion addresses are not hostnames but rather + // direct encodings of CNetAddr much like IPv4 dotted-decimal notation + // or IPv6 colon-separated hextet notation. Since we can't use + // getaddrinfo to decode them and it wouldn't make sense to resolve + // them, we return a network address representing it instead. See + // CNetAddr::SetSpecial(const std::string&) for more details. if (addr.SetSpecial(std::string(pszName))) { vIP.push_back(addr); return true; @@ -74,15 +80,25 @@ bool static LookupIntern(const char *pszName, std::vector<CNetAddr>& vIP, unsign struct addrinfo aiHint; memset(&aiHint, 0, sizeof(struct addrinfo)); + // We want a TCP port, which is a streaming socket type aiHint.ai_socktype = SOCK_STREAM; aiHint.ai_protocol = IPPROTO_TCP; + // We don't care which address family (IPv4 or IPv6) is returned aiHint.ai_family = AF_UNSPEC; + // If we allow lookups of hostnames, use the AI_ADDRCONFIG flag to only + // return addresses whose family we have an address configured for. + // + // If we don't allow lookups, then use the AI_NUMERICHOST flag for + // getaddrinfo to only decode numerical network addresses and suppress + // hostname lookups. aiHint.ai_flags = fAllowLookup ? AI_ADDRCONFIG : AI_NUMERICHOST; struct addrinfo *aiRes = nullptr; int nErr = getaddrinfo(pszName, nullptr, &aiHint, &aiRes); if (nErr) return false; + // Traverse the linked list starting with aiTrav, add all non-internal + // IPv4,v6 addresses to vIP while respecting nMaxSolutions. struct addrinfo *aiTrav = aiRes; while (aiTrav != nullptr && (nMaxSolutions == 0 || vIP.size() < nMaxSolutions)) { @@ -112,6 +128,21 @@ bool static LookupIntern(const char *pszName, std::vector<CNetAddr>& vIP, unsign return (vIP.size() > 0); } +/** + * Resolve a host string to its corresponding network addresses. + * + * @param pszName The string representing a host. Could be a name or a numerical + * IP address (IPv6 addresses in their bracketed form are + * allowed). + * @param[out] vIP The resulting network addresses to which the specified host + * string resolved. + * + * @returns Whether or not the specified host string successfully resolved to + * any resulting network addresses. + * + * @see Lookup(const char *, std::vector<CService>&, int, bool, unsigned int) + * for additional parameter descriptions. + */ bool LookupHost(const char *pszName, std::vector<CNetAddr>& vIP, unsigned int nMaxSolutions, bool fAllowLookup) { std::string strHost(pszName); @@ -124,6 +155,12 @@ bool LookupHost(const char *pszName, std::vector<CNetAddr>& vIP, unsigned int nM return LookupIntern(strHost.c_str(), vIP, nMaxSolutions, fAllowLookup); } + /** + * Resolve a host string to its first corresponding network address. + * + * @see LookupHost(const char *, std::vector<CNetAddr>&, unsigned int, bool) for + * additional parameter descriptions. + */ bool LookupHost(const char *pszName, CNetAddr& addr, bool fAllowLookup) { std::vector<CNetAddr> vIP; @@ -134,6 +171,26 @@ bool LookupHost(const char *pszName, CNetAddr& addr, bool fAllowLookup) return true; } +/** + * Resolve a service string to its corresponding service. + * + * @param pszName The string representing a service. Could be a name or a + * numerical IP address (IPv6 addresses should be in their + * disambiguated bracketed form), optionally followed by a port + * number. (e.g. example.com:8333 or + * [2001:db8:85a3:8d3:1319:8a2e:370:7348]:420) + * @param[out] vAddr The resulting services to which the specified service string + * resolved. + * @param portDefault The default port for resulting services if not specified + * by the service string. + * @param fAllowLookup Whether or not hostname lookups are permitted. If yes, + * external queries may be performed. + * @param nMaxSolutions The maximum number of results we want, specifying 0 + * means "as many solutions as we get." + * + * @returns Whether or not the service string successfully resolved to any + * resulting services. + */ bool Lookup(const char *pszName, std::vector<CService>& vAddr, int portDefault, bool fAllowLookup, unsigned int nMaxSolutions) { if (pszName[0] == 0) @@ -152,6 +209,12 @@ bool Lookup(const char *pszName, std::vector<CService>& vAddr, int portDefault, return true; } +/** + * Resolve a service string to its first corresponding service. + * + * @see Lookup(const char *, std::vector<CService>&, int, bool, unsigned int) + * for additional parameter descriptions. + */ bool Lookup(const char *pszName, CService& addr, int portDefault, bool fAllowLookup) { std::vector<CService> vService; @@ -162,6 +225,16 @@ bool Lookup(const char *pszName, CService& addr, int portDefault, bool fAllowLoo return true; } +/** + * Resolve a service string with a numeric IP to its first corresponding + * service. + * + * @returns The resulting CService if the resolution was successful, [::]:0 + * otherwise. + * + * @see Lookup(const char *, CService&, int, bool) for additional parameter + * descriptions. + */ CService LookupNumeric(const char *pszName, int portDefault) { CService addr; @@ -231,22 +304,29 @@ enum class IntrRecvError { }; /** - * Read bytes from socket. This will either read the full number of bytes requested - * or return False on error or timeout. - * This function can be interrupted by calling InterruptSocks5() + * Try to read a specified number of bytes from a socket. Please read the "see + * also" section for more detail. * - * @param data Buffer to receive into - * @param len Length of data to receive - * @param timeout Timeout in milliseconds for receive operation + * @param data The buffer where the read bytes should be stored. + * @param len The number of bytes to read into the specified buffer. + * @param timeout The total timeout in milliseconds for this read. + * @param hSocket The socket (has to be in non-blocking mode) from which to read + * bytes. * - * @note This function requires that hSocket is in non-blocking mode. + * @returns An IntrRecvError indicating the resulting status of this read. + * IntrRecvError::OK only if all of the specified number of bytes were + * read. + * + * @see This function can be interrupted by calling InterruptSocks5(bool). + * Sockets can be made non-blocking with SetSocketNonBlocking(const + * SOCKET&, bool). */ static IntrRecvError InterruptibleRecv(uint8_t* data, size_t len, int timeout, const SOCKET& hSocket) { int64_t curTime = GetTimeMillis(); int64_t endTime = curTime + timeout; - // Maximum time to wait in one select call. It will take up until this time (in millis) - // to break off in case of an interruption. + // Maximum time to wait for I/O readiness. It will take up until this time + // (in millis) to break off in case of an interruption. const int64_t maxWait = 1000; while (len > 0 && curTime < endTime) { ssize_t ret = recv(hSocket, (char*)data, len, 0); // Optimistically try the recv first @@ -261,11 +341,13 @@ static IntrRecvError InterruptibleRecv(uint8_t* data, size_t len, int timeout, c if (!IsSelectableSocket(hSocket)) { return IntrRecvError::NetworkError; } + // Only wait at most maxWait milliseconds at a time, unless + // we're approaching the end of the specified total timeout int timeout_ms = std::min(endTime - curTime, maxWait); #ifdef USE_POLL struct pollfd pollfd = {}; pollfd.fd = hSocket; - pollfd.events = POLLIN | POLLOUT; + pollfd.events = POLLIN; int nRet = poll(&pollfd, 1, timeout_ms); #else struct timeval tval = MillisToTimeval(timeout_ms); @@ -320,7 +402,24 @@ static std::string Socks5ErrorString(uint8_t err) } } -/** Connect using SOCKS5 (as described in RFC1928) */ +/** + * Connect to a specified destination service through an already connected + * SOCKS5 proxy. + * + * @param strDest The destination fully-qualified domain name. + * @param port The destination port. + * @param auth The credentials with which to authenticate with the specified + * SOCKS5 proxy. + * @param hSocket The SOCKS5 proxy socket. + * + * @returns Whether or not the operation succeeded. + * + * @note The specified SOCKS5 proxy socket must already be connected to the + * SOCKS5 proxy. + * + * @see <a href="https://www.ietf.org/rfc/rfc1928.txt">RFC1928: SOCKS Protocol + * Version 5</a> + */ static bool Socks5(const std::string& strDest, int port, const ProxyCredentials *auth, const SOCKET& hSocket) { IntrRecvError recvr; @@ -328,15 +427,15 @@ static bool Socks5(const std::string& strDest, int port, const ProxyCredentials if (strDest.size() > 255) { return error("Hostname too long"); } - // Accepted authentication methods + // Construct the version identifier/method selection message std::vector<uint8_t> vSocks5Init; - vSocks5Init.push_back(SOCKSVersion::SOCKS5); + vSocks5Init.push_back(SOCKSVersion::SOCKS5); // We want the SOCK5 protocol if (auth) { - vSocks5Init.push_back(0x02); // Number of methods + vSocks5Init.push_back(0x02); // 2 method identifiers follow... vSocks5Init.push_back(SOCKS5Method::NOAUTH); vSocks5Init.push_back(SOCKS5Method::USER_PASS); } else { - vSocks5Init.push_back(0x01); // Number of methods + vSocks5Init.push_back(0x01); // 1 method identifier follows... vSocks5Init.push_back(SOCKS5Method::NOAUTH); } ssize_t ret = send(hSocket, (const char*)vSocks5Init.data(), vSocks5Init.size(), MSG_NOSIGNAL); @@ -440,8 +539,16 @@ static bool Socks5(const std::string& strDest, int port, const ProxyCredentials return true; } +/** + * Try to create a socket file descriptor with specific properties in the + * communications domain (address family) of the specified service. + * + * For details on the desired properties, see the inline comments in the source + * code. + */ SOCKET CreateSocket(const CService &addrConnect) { + // Create a sockaddr from the specified service. struct sockaddr_storage sockaddr; socklen_t len = sizeof(sockaddr); if (!addrConnect.GetSockAddr((struct sockaddr*)&sockaddr, &len)) { @@ -449,10 +556,13 @@ SOCKET CreateSocket(const CService &addrConnect) return INVALID_SOCKET; } + // Create a TCP socket in the address family of the specified service. SOCKET hSocket = socket(((struct sockaddr*)&sockaddr)->sa_family, SOCK_STREAM, IPPROTO_TCP); if (hSocket == INVALID_SOCKET) return INVALID_SOCKET; + // Ensure that waiting for I/O on this socket won't result in undefined + // behavior. if (!IsSelectableSocket(hSocket)) { CloseSocket(hSocket); LogPrintf("Cannot create connection: non-selectable socket created (fd >= FD_SETSIZE ?)\n"); @@ -461,17 +571,18 @@ SOCKET CreateSocket(const CService &addrConnect) #ifdef SO_NOSIGPIPE int set = 1; - // Different way of disabling SIGPIPE on BSD + // Set the no-sigpipe option on the socket for BSD systems, other UNIXes + // should use the MSG_NOSIGNAL flag for every send. setsockopt(hSocket, SOL_SOCKET, SO_NOSIGPIPE, (void*)&set, sizeof(int)); #endif - //Disable Nagle's algorithm + // Set the no-delay option (disable Nagle's algorithm) on the TCP socket. SetSocketNoDelay(hSocket); - // Set to non-blocking + // Set the non-blocking option on the socket. if (!SetSocketNonBlocking(hSocket, true)) { CloseSocket(hSocket); - LogPrintf("ConnectSocketDirectly: Setting socket to non-blocking failed, error %s\n", NetworkErrorString(WSAGetLastError())); + LogPrintf("CreateSocket: Setting socket to non-blocking failed, error %s\n", NetworkErrorString(WSAGetLastError())); } return hSocket; } @@ -486,8 +597,21 @@ static void LogConnectFailure(bool manual_connection, const char* fmt, const Arg } } +/** + * Try to connect to the specified service on the specified socket. + * + * @param addrConnect The service to which to connect. + * @param hSocket The socket on which to connect. + * @param nTimeout Wait this many milliseconds for the connection to be + * established. + * @param manual_connection Whether or not the connection was manually requested + * (e.g. thru the addnode RPC) + * + * @returns Whether or not a connection was successfully made. + */ bool ConnectSocketDirectly(const CService &addrConnect, const SOCKET& hSocket, int nTimeout, bool manual_connection) { + // Create a sockaddr from the specified service. struct sockaddr_storage sockaddr; socklen_t len = sizeof(sockaddr); if (hSocket == INVALID_SOCKET) { @@ -498,12 +622,17 @@ bool ConnectSocketDirectly(const CService &addrConnect, const SOCKET& hSocket, i LogPrintf("Cannot connect to %s: unsupported network\n", addrConnect.ToString()); return false; } + + // Connect to the addrConnect service on the hSocket socket. if (connect(hSocket, (struct sockaddr*)&sockaddr, len) == SOCKET_ERROR) { int nErr = WSAGetLastError(); // WSAEINVAL is here because some legacy version of winsock uses it if (nErr == WSAEINPROGRESS || nErr == WSAEWOULDBLOCK || nErr == WSAEINVAL) { + // Connection didn't actually fail, but is being established + // asynchronously. Thus, use async I/O api (select/poll) + // synchronously to check for successful connection with a timeout. #ifdef USE_POLL struct pollfd pollfd = {}; pollfd.fd = hSocket; @@ -516,6 +645,10 @@ bool ConnectSocketDirectly(const CService &addrConnect, const SOCKET& hSocket, i FD_SET(hSocket, &fdset); int nRet = select(hSocket + 1, nullptr, &fdset, nullptr, &timeout); #endif + // Upon successful completion, both select and poll return the total + // number of file descriptors that have been selected. A value of 0 + // indicates that the call timed out and no file descriptors have + // been selected. if (nRet == 0) { LogPrint(BCLog::NET, "connection to %s timeout\n", addrConnect.ToString()); @@ -526,6 +659,11 @@ bool ConnectSocketDirectly(const CService &addrConnect, const SOCKET& hSocket, i LogPrintf("select() for %s failed: %s\n", addrConnect.ToString(), NetworkErrorString(WSAGetLastError())); return false; } + + // Even if the select/poll was successful, the connect might not + // have been successful. The reason for this failure is hidden away + // in the SO_ERROR for the socket in modern systems. We read it into + // nRet here. socklen_t nRetSize = sizeof(nRet); if (getsockopt(hSocket, SOL_SOCKET, SO_ERROR, (sockopt_arg_type)&nRet, &nRetSize) == SOCKET_ERROR) { @@ -569,6 +707,22 @@ bool GetProxy(enum Network net, proxyType &proxyInfoOut) { return true; } +/** + * Set the name proxy to use for all connections to nodes specified by a + * hostname. After setting this proxy, connecting to a node sepcified by a + * hostname won't result in a local lookup of said hostname, rather, connect to + * the node by asking the name proxy for a proxy connection to the hostname, + * effectively delegating the hostname lookup to the specified proxy. + * + * This delegation increases privacy for those who set the name proxy as they no + * longer leak their external hostname queries to their DNS servers. + * + * @returns Whether or not the operation succeeded. + * + * @note SOCKS5's support for UDP-over-SOCKS5 has been considered, but no SOCK5 + * server in common use (most notably Tor) actually implements UDP + * support, and a DNS resolver is beyond the scope of this project. + */ bool SetNameProxy(const proxyType &addrProxy) { if (!addrProxy.IsValid()) return false; @@ -599,6 +753,21 @@ bool IsProxy(const CNetAddr &addr) { return false; } +/** + * Connect to a specified destination service through a SOCKS5 proxy by first + * connecting to the SOCKS5 proxy. + * + * @param proxy The SOCKS5 proxy. + * @param strDest The destination service to which to connect. + * @param port The destination port. + * @param hSocket The socket on which to connect to the SOCKS5 proxy. + * @param nTimeout Wait this many milliseconds for the connection to the SOCKS5 + * proxy to be established. + * @param outProxyConnectionFailed[out] Whether or not the connection to the + * SOCKS5 proxy failed. + * + * @returns Whether or not the operation succeeded. + */ bool ConnectThroughProxy(const proxyType &proxy, const std::string& strDest, int port, const SOCKET& hSocket, int nTimeout, bool *outProxyConnectionFailed) { // first connect to proxy server @@ -623,6 +792,17 @@ bool ConnectThroughProxy(const proxyType &proxy, const std::string& strDest, int return true; } +/** + * Parse and resolve a specified subnet string into the appropriate internal + * representation. + * + * @param pszName A string representation of a subnet of the form `network + * address [ "/", ( CIDR-style suffix | netmask ) ]`(e.g. + * `2001:db8::/32`, `192.0.2.0/255.255.255.0`, or `8.8.8.8`). + * @param ret The resulting internal representation of a subnet. + * + * @returns Whether the operation succeeded or not. + */ bool LookupSubNet(const char* pszName, CSubNet& ret) { std::string strSubnet(pszName); @@ -630,6 +810,8 @@ bool LookupSubNet(const char* pszName, CSubNet& ret) std::vector<CNetAddr> vIP; std::string strAddress = strSubnet.substr(0, slash); + // TODO: Use LookupHost(const char *, CNetAddr&, bool) instead to just get + // one CNetAddr. if (LookupHost(strAddress.c_str(), vIP, 1, false)) { CNetAddr network = vIP[0]; @@ -637,8 +819,8 @@ bool LookupSubNet(const char* pszName, CSubNet& ret) { std::string strNetmask = strSubnet.substr(slash + 1); int32_t n; - // IPv4 addresses start at offset 12, and first 12 bytes must match, so just offset n - if (ParseInt32(strNetmask, &n)) { // If valid number, assume /24 syntax + if (ParseInt32(strNetmask, &n)) { + // If valid number, assume CIDR variable-length subnet masking ret = CSubNet(network, n); return ret.IsValid(); } diff --git a/src/outputtype.cpp b/src/outputtype.cpp index 73ffb801f2..bcaa05f4b6 100644 --- a/src/outputtype.cpp +++ b/src/outputtype.cpp @@ -5,9 +5,10 @@ #include <outputtype.h> -#include <keystore.h> #include <pubkey.h> #include <script/script.h> +#include <script/sign.h> +#include <script/signingprovider.h> #include <script/standard.h> #include <assert.h> @@ -73,7 +74,7 @@ std::vector<CTxDestination> GetAllDestinationsForKey(const CPubKey& key) } } -CTxDestination AddAndGetDestinationForScript(CKeyStore& keystore, const CScript& script, OutputType type) +CTxDestination AddAndGetDestinationForScript(FillableSigningProvider& keystore, const CScript& script, OutputType type) { // Add script to keystore keystore.AddCScript(script); @@ -98,4 +99,3 @@ CTxDestination AddAndGetDestinationForScript(CKeyStore& keystore, const CScript& default: assert(false); } } - diff --git a/src/outputtype.h b/src/outputtype.h index 6c30fd1950..6acbaa2f3e 100644 --- a/src/outputtype.h +++ b/src/outputtype.h @@ -7,7 +7,7 @@ #define BITCOIN_OUTPUTTYPE_H #include <attributes.h> -#include <keystore.h> +#include <script/signingprovider.h> #include <script/standard.h> #include <string> @@ -44,7 +44,7 @@ std::vector<CTxDestination> GetAllDestinationsForKey(const CPubKey& key); * This function will automatically add the script (and any other * necessary scripts) to the keystore. */ -CTxDestination AddAndGetDestinationForScript(CKeyStore& keystore, const CScript& script, OutputType); +CTxDestination AddAndGetDestinationForScript(FillableSigningProvider& keystore, const CScript& script, OutputType); #endif // BITCOIN_OUTPUTTYPE_H diff --git a/src/protocol.h b/src/protocol.h index a790a06906..91d043947b 100644 --- a/src/protocol.h +++ b/src/protocol.h @@ -1,5 +1,5 @@ // Copyright (c) 2009-2010 Satoshi Nakamoto -// Copyright (c) 2009-2018 The Bitcoin Core developers +// Copyright (c) 2009-2019 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. @@ -261,9 +261,6 @@ enum ServiceFlags : uint64_t { // NODE_WITNESS indicates that a node can be asked for blocks and transactions including // witness data. NODE_WITNESS = (1 << 3), - // NODE_XTHIN means the node supports Xtreme Thinblocks - // If this is turned off then the node will not service nor make xthin requests - NODE_XTHIN = (1 << 4), // NODE_NETWORK_LIMITED means the same as NODE_NETWORK with the limitation of only // serving the last 288 (2 day) blocks // See BIP159 for details on how this is implemented. diff --git a/src/psbt.h b/src/psbt.h index f3840b9ed3..6d77db0c6f 100644 --- a/src/psbt.h +++ b/src/psbt.h @@ -12,6 +12,7 @@ #include <primitives/transaction.h> #include <pubkey.h> #include <script/sign.h> +#include <script/signingprovider.h> // Magic bytes static constexpr uint8_t PSBT_MAGIC_BYTES[5] = {'p', 's', 'b', 't', 0xff}; diff --git a/src/qt/addresstablemodel.cpp b/src/qt/addresstablemodel.cpp index fa6c9c9f7a..29423db3d0 100644 --- a/src/qt/addresstablemodel.cpp +++ b/src/qt/addresstablemodel.cpp @@ -358,12 +358,15 @@ QString AddressTableModel::addRow(const QString &type, const QString &label, con return QString(); } } + + // Add entry + walletModel->wallet().setAddressBook(DecodeDestination(strAddress), strLabel, "send"); } else if(type == Receive) { // Generate a new address to associate with given label - CPubKey newKey; - if(!walletModel->wallet().getKeyFromPool(false /* internal */, newKey)) + CTxDestination dest; + if(!walletModel->wallet().getNewDestination(address_type, strLabel, dest)) { WalletModel::UnlockContext ctx(walletModel->requestUnlock()); if(!ctx.isValid()) @@ -372,23 +375,18 @@ QString AddressTableModel::addRow(const QString &type, const QString &label, con editStatus = WALLET_UNLOCK_FAILURE; return QString(); } - if(!walletModel->wallet().getKeyFromPool(false /* internal */, newKey)) + if(!walletModel->wallet().getNewDestination(address_type, strLabel, dest)) { editStatus = KEY_GENERATION_FAILURE; return QString(); } } - walletModel->wallet().learnRelatedScripts(newKey, address_type); - strAddress = EncodeDestination(GetDestinationForKey(newKey, address_type)); + strAddress = EncodeDestination(dest); } else { return QString(); } - - // Add entry - walletModel->wallet().setAddressBook(DecodeDestination(strAddress), strLabel, - (type == Send ? "send" : "receive")); return QString::fromStdString(strAddress); } diff --git a/src/qt/bitcoin.cpp b/src/qt/bitcoin.cpp index 0ab3fd05ee..ed5d47cad7 100644 --- a/src/qt/bitcoin.cpp +++ b/src/qt/bitcoin.cpp @@ -445,6 +445,9 @@ int GuiMain(int argc, char* argv[]) // Register meta types used for QMetaObject::invokeMethod qRegisterMetaType< bool* >(); +#ifdef ENABLE_WALLET + qRegisterMetaType<WalletModel*>(); +#endif // Need to pass name here as CAmount is a typedef (see http://qt-project.org/doc/qt-5/qmetatype.html#qRegisterMetaType) // IMPORTANT if it is no longer a typedef use the normal variant above qRegisterMetaType< CAmount >("CAmount"); diff --git a/src/qt/bitcoingui.cpp b/src/qt/bitcoingui.cpp index 6b2ce8c61d..3533227483 100644 --- a/src/qt/bitcoingui.cpp +++ b/src/qt/bitcoingui.cpp @@ -1372,12 +1372,13 @@ static bool ThreadSafeMessageBox(BitcoinGUI* gui, const std::string& message, co style &= ~CClientUIInterface::SECURE; bool ret = false; // In case of modal message, use blocking connection to wait for user to click a button - QMetaObject::invokeMethod(gui, "message", + bool invoked = QMetaObject::invokeMethod(gui, "message", modal ? GUIUtil::blockingGUIThreadConnection() : Qt::QueuedConnection, Q_ARG(QString, QString::fromStdString(caption)), Q_ARG(QString, QString::fromStdString(message)), Q_ARG(unsigned int, style), Q_ARG(bool*, &ret)); + assert(invoked); return ret; } diff --git a/src/qt/clientmodel.cpp b/src/qt/clientmodel.cpp index ce950150df..238be08480 100644 --- a/src/qt/clientmodel.cpp +++ b/src/qt/clientmodel.cpp @@ -184,34 +184,39 @@ void ClientModel::updateBanlist() static void ShowProgress(ClientModel *clientmodel, const std::string &title, int nProgress) { // emits signal "showProgress" - QMetaObject::invokeMethod(clientmodel, "showProgress", Qt::QueuedConnection, + bool invoked = QMetaObject::invokeMethod(clientmodel, "showProgress", Qt::QueuedConnection, Q_ARG(QString, QString::fromStdString(title)), Q_ARG(int, nProgress)); + assert(invoked); } static void NotifyNumConnectionsChanged(ClientModel *clientmodel, int newNumConnections) { // Too noisy: qDebug() << "NotifyNumConnectionsChanged: " + QString::number(newNumConnections); - QMetaObject::invokeMethod(clientmodel, "updateNumConnections", Qt::QueuedConnection, + bool invoked = QMetaObject::invokeMethod(clientmodel, "updateNumConnections", Qt::QueuedConnection, Q_ARG(int, newNumConnections)); + assert(invoked); } static void NotifyNetworkActiveChanged(ClientModel *clientmodel, bool networkActive) { - QMetaObject::invokeMethod(clientmodel, "updateNetworkActive", Qt::QueuedConnection, + bool invoked = QMetaObject::invokeMethod(clientmodel, "updateNetworkActive", Qt::QueuedConnection, Q_ARG(bool, networkActive)); + assert(invoked); } static void NotifyAlertChanged(ClientModel *clientmodel) { qDebug() << "NotifyAlertChanged"; - QMetaObject::invokeMethod(clientmodel, "updateAlert", Qt::QueuedConnection); + bool invoked = QMetaObject::invokeMethod(clientmodel, "updateAlert", Qt::QueuedConnection); + assert(invoked); } static void BannedListChanged(ClientModel *clientmodel) { qDebug() << QString("%1: Requesting update for peer banlist").arg(__func__); - QMetaObject::invokeMethod(clientmodel, "updateBanlist", Qt::QueuedConnection); + bool invoked = QMetaObject::invokeMethod(clientmodel, "updateBanlist", Qt::QueuedConnection); + assert(invoked); } static void BlockTipChanged(ClientModel *clientmodel, bool initialSync, int height, int64_t blockTime, double verificationProgress, bool fHeader) @@ -233,11 +238,12 @@ static void BlockTipChanged(ClientModel *clientmodel, bool initialSync, int heig // if we are in-sync or if we notify a header update, update the UI regardless of last update time if (fHeader || !initialSync || now - nLastUpdateNotification > MODEL_UPDATE_DELAY) { //pass an async signal to the UI thread - QMetaObject::invokeMethod(clientmodel, "numBlocksChanged", Qt::QueuedConnection, + bool invoked = QMetaObject::invokeMethod(clientmodel, "numBlocksChanged", Qt::QueuedConnection, Q_ARG(int, height), Q_ARG(QDateTime, QDateTime::fromTime_t(blockTime)), Q_ARG(double, verificationProgress), Q_ARG(bool, fHeader)); + assert(invoked); nLastUpdateNotification = now; } } diff --git a/src/qt/coincontroldialog.cpp b/src/qt/coincontroldialog.cpp index 6b9f79aaf8..03d18d2845 100644 --- a/src/qt/coincontroldialog.cpp +++ b/src/qt/coincontroldialog.cpp @@ -418,7 +418,8 @@ void CoinControlDialog::updateLabels(WalletModel *model, QDialog* dialog) if (amount > 0) { - CTxOut txout(amount, static_cast<CScript>(std::vector<unsigned char>(24, 0))); + // Assumes a p2pkh script size + CTxOut txout(amount, CScript() << std::vector<unsigned char>(24, 0)); txDummy.vout.push_back(txout); fDust |= IsDust(txout, model->node().getDustRelayFee()); } @@ -509,7 +510,8 @@ void CoinControlDialog::updateLabels(WalletModel *model, QDialog* dialog) // Never create dust outputs; if we would, just add the dust to the fee. if (nChange > 0 && nChange < MIN_CHANGE) { - CTxOut txout(nChange, static_cast<CScript>(std::vector<unsigned char>(24, 0))); + // Assumes a p2pkh script size + CTxOut txout(nChange, CScript() << std::vector<unsigned char>(24, 0)); if (IsDust(txout, model->node().getDustRelayFee())) { nPayFee += nChange; diff --git a/src/qt/guiutil.cpp b/src/qt/guiutil.cpp index 70e52c9f1d..dc1da7f8a9 100644 --- a/src/qt/guiutil.cpp +++ b/src/qt/guiutil.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2011-2018 The Bitcoin Core developers +// Copyright (c) 2011-2019 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. @@ -11,10 +11,10 @@ #include <base58.h> #include <chainparams.h> -#include <primitives/transaction.h> -#include <key_io.h> #include <interfaces/node.h> +#include <key_io.h> #include <policy/policy.h> +#include <primitives/transaction.h> #include <protocol.h> #include <script/script.h> #include <script/standard.h> @@ -639,7 +639,7 @@ fs::path static GetAutostartFilePath() std::string chain = gArgs.GetChainName(); if (chain == CBaseChainParams::MAIN) return GetAutostartDir() / "bitcoin.desktop"; - return GetAutostartDir() / strprintf("bitcoin-%s.lnk", chain); + return GetAutostartDir() / strprintf("bitcoin-%s.desktop", chain); } bool GetStartOnSystemStartup() @@ -841,9 +841,6 @@ QString formatServicesStr(quint64 mask) case NODE_WITNESS: strList.append("WITNESS"); break; - case NODE_XTHIN: - strList.append("XTHIN"); - break; default: strList.append(QString("%1[%2]").arg("UNKNOWN").arg(check)); } diff --git a/src/qt/paymentserver.cpp b/src/qt/paymentserver.cpp index c99515fe1c..f3f5d28af9 100644 --- a/src/qt/paymentserver.cpp +++ b/src/qt/paymentserver.cpp @@ -666,16 +666,14 @@ void PaymentServer::fetchPaymentACK(WalletModel* walletModel, const SendCoinsRec payment.add_transactions(transaction.data(), transaction.size()); // Create a new refund address, or re-use: - CPubKey newKey; - if (walletModel->wallet().getKeyFromPool(false /* internal */, newKey)) { + CTxDestination dest; + const OutputType change_type = walletModel->wallet().getDefaultChangeType() != OutputType::CHANGE_AUTO ? walletModel->wallet().getDefaultChangeType() : walletModel->wallet().getDefaultAddressType(); + if (walletModel->wallet().getNewDestination(change_type, "", dest)) { // BIP70 requests encode the scriptPubKey directly, so we are not restricted to address // types supported by the receiver. As a result, we choose the address format we also // use for change. Despite an actual payment and not change, this is a close match: // it's the output type we use subject to privacy issues, but not restricted by what // other software supports. - const OutputType change_type = walletModel->wallet().getDefaultChangeType() != OutputType::CHANGE_AUTO ? walletModel->wallet().getDefaultChangeType() : walletModel->wallet().getDefaultAddressType(); - walletModel->wallet().learnRelatedScripts(newKey, change_type); - CTxDestination dest = GetDestinationForKey(newKey, change_type); std::string label = tr("Refund from %1").arg(recipient.authenticatedMerchant).toStdString(); walletModel->wallet().setAddressBook(dest, label, "refund"); diff --git a/src/qt/splashscreen.cpp b/src/qt/splashscreen.cpp index dcb9d1923f..98eeee1a47 100644 --- a/src/qt/splashscreen.cpp +++ b/src/qt/splashscreen.cpp @@ -156,11 +156,12 @@ void SplashScreen::finish() static void InitMessage(SplashScreen *splash, const std::string &message) { - QMetaObject::invokeMethod(splash, "showMessage", + bool invoked = QMetaObject::invokeMethod(splash, "showMessage", Qt::QueuedConnection, Q_ARG(QString, QString::fromStdString(message)), Q_ARG(int, Qt::AlignBottom|Qt::AlignHCenter), Q_ARG(QColor, QColor(55,55,55))); + assert(invoked); } static void ShowProgress(SplashScreen *splash, const std::string &title, int nProgress, bool resume_possible) diff --git a/src/qt/test/wallettests.cpp b/src/qt/test/wallettests.cpp index e54915ec75..120dff95c0 100644 --- a/src/qt/test/wallettests.cpp +++ b/src/qt/test/wallettests.cpp @@ -68,7 +68,8 @@ uint256 SendCoins(CWallet& wallet, SendCoinsDialog& sendCoinsDialog, const CTxDe if (status == CT_NEW) txid = hash; })); ConfirmSend(); - QMetaObject::invokeMethod(&sendCoinsDialog, "on_sendButton_clicked"); + bool invoked = QMetaObject::invokeMethod(&sendCoinsDialog, "on_sendButton_clicked"); + assert(invoked); return txid; } diff --git a/src/qt/transactiontablemodel.cpp b/src/qt/transactiontablemodel.cpp index 6fe35b13cf..1064c60dfd 100644 --- a/src/qt/transactiontablemodel.cpp +++ b/src/qt/transactiontablemodel.cpp @@ -687,10 +687,11 @@ public: { QString strHash = QString::fromStdString(hash.GetHex()); qDebug() << "NotifyTransactionChanged: " + strHash + " status= " + QString::number(status); - QMetaObject::invokeMethod(ttm, "updateTransaction", Qt::QueuedConnection, + bool invoked = QMetaObject::invokeMethod(ttm, "updateTransaction", Qt::QueuedConnection, Q_ARG(QString, strHash), Q_ARG(int, status), Q_ARG(bool, showTransaction)); + assert(invoked); } private: uint256 hash; @@ -725,12 +726,16 @@ static void ShowProgress(TransactionTableModel *ttm, const std::string &title, i if (nProgress == 100) { fQueueNotifications = false; - if (vQueueNotifications.size() > 10) // prevent balloon spam, show maximum 10 balloons - QMetaObject::invokeMethod(ttm, "setProcessingQueuedTransactions", Qt::QueuedConnection, Q_ARG(bool, true)); + if (vQueueNotifications.size() > 10) { // prevent balloon spam, show maximum 10 balloons + bool invoked = QMetaObject::invokeMethod(ttm, "setProcessingQueuedTransactions", Qt::QueuedConnection, Q_ARG(bool, true)); + assert(invoked); + } for (unsigned int i = 0; i < vQueueNotifications.size(); ++i) { - if (vQueueNotifications.size() - i <= 10) - QMetaObject::invokeMethod(ttm, "setProcessingQueuedTransactions", Qt::QueuedConnection, Q_ARG(bool, false)); + if (vQueueNotifications.size() - i <= 10) { + bool invoked = QMetaObject::invokeMethod(ttm, "setProcessingQueuedTransactions", Qt::QueuedConnection, Q_ARG(bool, false)); + assert(invoked); + } vQueueNotifications[i].invoke(ttm); } diff --git a/src/qt/walletcontroller.cpp b/src/qt/walletcontroller.cpp index 4d489d7f44..2aedb77798 100644 --- a/src/qt/walletcontroller.cpp +++ b/src/qt/walletcontroller.cpp @@ -124,7 +124,8 @@ WalletModel* WalletController::getOrCreateWallet(std::unique_ptr<interfaces::Wal } else { // Handler callback runs in a different thread so fix wallet model thread affinity. wallet_model->moveToThread(thread()); - QMetaObject::invokeMethod(this, "addWallet", Qt::QueuedConnection, Q_ARG(WalletModel*, wallet_model)); + bool invoked = QMetaObject::invokeMethod(this, "addWallet", Qt::QueuedConnection, Q_ARG(WalletModel*, wallet_model)); + assert(invoked); } return wallet_model; diff --git a/src/qt/walletmodel.cpp b/src/qt/walletmodel.cpp index c1eba61749..57406179f7 100644 --- a/src/qt/walletmodel.cpp +++ b/src/qt/walletmodel.cpp @@ -221,9 +221,12 @@ WalletModel::SendCoinsReturn WalletModel::prepareTransaction(WalletModelTransact return TransactionCreationFailed; } - // Reject absurdly high fee - if (nFeeRequired > m_wallet->getDefaultMaxTxFee()) + // Reject absurdly high fee. (This can never happen because the + // wallet never creates transactions with fee greater than + // m_default_max_tx_fee. This merely a belt-and-suspenders check). + if (nFeeRequired > m_wallet->getDefaultMaxTxFee()) { return AbsurdFee; + } } return SendCoinsReturn(OK); @@ -374,13 +377,15 @@ bool WalletModel::changePassphrase(const SecureString &oldPass, const SecureStri static void NotifyUnload(WalletModel* walletModel) { qDebug() << "NotifyUnload"; - QMetaObject::invokeMethod(walletModel, "unload"); + bool invoked = QMetaObject::invokeMethod(walletModel, "unload"); + assert(invoked); } static void NotifyKeyStoreStatusChanged(WalletModel *walletmodel) { qDebug() << "NotifyKeyStoreStatusChanged"; - QMetaObject::invokeMethod(walletmodel, "updateStatus", Qt::QueuedConnection); + bool invoked = QMetaObject::invokeMethod(walletmodel, "updateStatus", Qt::QueuedConnection); + assert(invoked); } static void NotifyAddressBookChanged(WalletModel *walletmodel, @@ -392,38 +397,43 @@ static void NotifyAddressBookChanged(WalletModel *walletmodel, QString strPurpose = QString::fromStdString(purpose); qDebug() << "NotifyAddressBookChanged: " + strAddress + " " + strLabel + " isMine=" + QString::number(isMine) + " purpose=" + strPurpose + " status=" + QString::number(status); - QMetaObject::invokeMethod(walletmodel, "updateAddressBook", Qt::QueuedConnection, + bool invoked = QMetaObject::invokeMethod(walletmodel, "updateAddressBook", Qt::QueuedConnection, Q_ARG(QString, strAddress), Q_ARG(QString, strLabel), Q_ARG(bool, isMine), Q_ARG(QString, strPurpose), Q_ARG(int, status)); + assert(invoked); } static void NotifyTransactionChanged(WalletModel *walletmodel, const uint256 &hash, ChangeType status) { Q_UNUSED(hash); Q_UNUSED(status); - QMetaObject::invokeMethod(walletmodel, "updateTransaction", Qt::QueuedConnection); + bool invoked = QMetaObject::invokeMethod(walletmodel, "updateTransaction", Qt::QueuedConnection); + assert(invoked); } static void ShowProgress(WalletModel *walletmodel, const std::string &title, int nProgress) { // emits signal "showProgress" - QMetaObject::invokeMethod(walletmodel, "showProgress", Qt::QueuedConnection, + bool invoked = QMetaObject::invokeMethod(walletmodel, "showProgress", Qt::QueuedConnection, Q_ARG(QString, QString::fromStdString(title)), Q_ARG(int, nProgress)); + assert(invoked); } static void NotifyWatchonlyChanged(WalletModel *walletmodel, bool fHaveWatchonly) { - QMetaObject::invokeMethod(walletmodel, "updateWatchOnlyFlag", Qt::QueuedConnection, + bool invoked = QMetaObject::invokeMethod(walletmodel, "updateWatchOnlyFlag", Qt::QueuedConnection, Q_ARG(bool, fHaveWatchonly)); + assert(invoked); } static void NotifyCanGetAddressesChanged(WalletModel* walletmodel) { - QMetaObject::invokeMethod(walletmodel, "canGetAddressesChanged"); + bool invoked = QMetaObject::invokeMethod(walletmodel, "canGetAddressesChanged"); + assert(invoked); } void WalletModel::subscribeToCoreSignals() diff --git a/src/rest.cpp b/src/rest.cpp index ab409947d3..eba7aae50f 100644 --- a/src/rest.cpp +++ b/src/rest.cpp @@ -12,6 +12,7 @@ #include <primitives/block.h> #include <primitives/transaction.h> #include <rpc/blockchain.h> +#include <rpc/protocol.h> #include <rpc/server.h> #include <streams.h> #include <sync.h> diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp index 743efc1e65..b7dcd59c6d 100644 --- a/src/rpc/blockchain.cpp +++ b/src/rpc/blockchain.cpp @@ -165,8 +165,6 @@ UniValue blockToJSON(const CBlock& block, const CBlockIndex* tip, const CBlockIn static UniValue getblockcount(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() != 0) - throw std::runtime_error( RPCHelpMan{"getblockcount", "\nReturns the height of the most-work fully-validated chain.\n" "The genesis block has height 0.\n", @@ -178,7 +176,7 @@ static UniValue getblockcount(const JSONRPCRequest& request) HelpExampleCli("getblockcount", "") + HelpExampleRpc("getblockcount", "") }, - }.ToString()); + }.Check(request); LOCK(cs_main); return ::ChainActive().Height(); @@ -186,8 +184,6 @@ static UniValue getblockcount(const JSONRPCRequest& request) static UniValue getbestblockhash(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() != 0) - throw std::runtime_error( RPCHelpMan{"getbestblockhash", "\nReturns the hash of the best (tip) block in the most-work fully-validated chain.\n", {}, @@ -198,7 +194,7 @@ static UniValue getbestblockhash(const JSONRPCRequest& request) HelpExampleCli("getbestblockhash", "") + HelpExampleRpc("getbestblockhash", "") }, - }.ToString()); + }.Check(request); LOCK(cs_main); return ::ChainActive().Tip()->GetBlockHash().GetHex(); @@ -216,8 +212,6 @@ void RPCNotifyBlockChange(bool ibd, const CBlockIndex * pindex) static UniValue waitfornewblock(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() > 1) - throw std::runtime_error( RPCHelpMan{"waitfornewblock", "\nWaits for a specific new block and returns useful info about it.\n" "\nReturns the current block on timeout or exit.\n", @@ -234,7 +228,7 @@ static UniValue waitfornewblock(const JSONRPCRequest& request) HelpExampleCli("waitfornewblock", "1000") + HelpExampleRpc("waitfornewblock", "1000") }, - }.ToString()); + }.Check(request); int timeout = 0; if (!request.params[0].isNull()) timeout = request.params[0].get_int(); @@ -257,8 +251,6 @@ static UniValue waitfornewblock(const JSONRPCRequest& request) static UniValue waitforblock(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) - throw std::runtime_error( RPCHelpMan{"waitforblock", "\nWaits for a specific new block and returns useful info about it.\n" "\nReturns the current block on timeout or exit.\n", @@ -276,7 +268,7 @@ static UniValue waitforblock(const JSONRPCRequest& request) HelpExampleCli("waitforblock", "\"0000000000079f8ef3d2c688c244eb7a4570b24c9ed7b4a8c619eb02596f8862\", 1000") + HelpExampleRpc("waitforblock", "\"0000000000079f8ef3d2c688c244eb7a4570b24c9ed7b4a8c619eb02596f8862\", 1000") }, - }.ToString()); + }.Check(request); int timeout = 0; uint256 hash(ParseHashV(request.params[0], "blockhash")); @@ -302,8 +294,6 @@ static UniValue waitforblock(const JSONRPCRequest& request) static UniValue waitforblockheight(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) - throw std::runtime_error( RPCHelpMan{"waitforblockheight", "\nWaits for (at least) block height and returns the height and hash\n" "of the current tip.\n" @@ -322,7 +312,7 @@ static UniValue waitforblockheight(const JSONRPCRequest& request) HelpExampleCli("waitforblockheight", "\"100\", 1000") + HelpExampleRpc("waitforblockheight", "\"100\", 1000") }, - }.ToString()); + }.Check(request); int timeout = 0; int height = request.params[0].get_int(); @@ -347,8 +337,6 @@ static UniValue waitforblockheight(const JSONRPCRequest& request) static UniValue syncwithvalidationinterfacequeue(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() > 0) { - throw std::runtime_error( RPCHelpMan{"syncwithvalidationinterfacequeue", "\nWaits for the validation interface queue to catch up on everything that was there when we entered this function.\n", {}, @@ -357,16 +345,14 @@ static UniValue syncwithvalidationinterfacequeue(const JSONRPCRequest& request) HelpExampleCli("syncwithvalidationinterfacequeue","") + HelpExampleRpc("syncwithvalidationinterfacequeue","") }, - }.ToString()); - } + }.Check(request); + SyncWithValidationInterfaceQueue(); return NullUniValue; } static UniValue getdifficulty(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() != 0) - throw std::runtime_error( RPCHelpMan{"getdifficulty", "\nReturns the proof-of-work difficulty as a multiple of the minimum difficulty.\n", {}, @@ -377,7 +363,7 @@ static UniValue getdifficulty(const JSONRPCRequest& request) HelpExampleCli("getdifficulty", "") + HelpExampleRpc("getdifficulty", "") }, - }.ToString()); + }.Check(request); LOCK(cs_main); return GetDifficulty(::ChainActive().Tip()); @@ -504,8 +490,6 @@ UniValue MempoolToJSON(const CTxMemPool& pool, bool verbose) static UniValue getrawmempool(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() > 1) - throw std::runtime_error( RPCHelpMan{"getrawmempool", "\nReturns all transaction ids in memory pool as a json array of string transaction ids.\n" "\nHint: use getmempoolentry to fetch a specific transaction from the mempool.\n", @@ -528,7 +512,7 @@ static UniValue getrawmempool(const JSONRPCRequest& request) HelpExampleCli("getrawmempool", "true") + HelpExampleRpc("getrawmempool", "true") }, - }.ToString()); + }.Check(request); bool fVerbose = false; if (!request.params[0].isNull()) @@ -539,8 +523,6 @@ static UniValue getrawmempool(const JSONRPCRequest& request) static UniValue getmempoolancestors(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) { - throw std::runtime_error( RPCHelpMan{"getmempoolancestors", "\nIf txid is in the mempool, returns all in-mempool ancestors.\n", { @@ -566,8 +548,7 @@ static UniValue getmempoolancestors(const JSONRPCRequest& request) HelpExampleCli("getmempoolancestors", "\"mytxid\"") + HelpExampleRpc("getmempoolancestors", "\"mytxid\"") }, - }.ToString()); - } + }.Check(request); bool fVerbose = false; if (!request.params[1].isNull()) @@ -609,8 +590,6 @@ static UniValue getmempoolancestors(const JSONRPCRequest& request) static UniValue getmempooldescendants(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) { - throw std::runtime_error( RPCHelpMan{"getmempooldescendants", "\nIf txid is in the mempool, returns all in-mempool descendants.\n", { @@ -636,8 +615,7 @@ static UniValue getmempooldescendants(const JSONRPCRequest& request) HelpExampleCli("getmempooldescendants", "\"mytxid\"") + HelpExampleRpc("getmempooldescendants", "\"mytxid\"") }, - }.ToString()); - } + }.Check(request); bool fVerbose = false; if (!request.params[1].isNull()) @@ -679,8 +657,6 @@ static UniValue getmempooldescendants(const JSONRPCRequest& request) static UniValue getmempoolentry(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() != 1) { - throw std::runtime_error( RPCHelpMan{"getmempoolentry", "\nReturns mempool data for given transaction\n", { @@ -695,8 +671,7 @@ static UniValue getmempoolentry(const JSONRPCRequest& request) HelpExampleCli("getmempoolentry", "\"mytxid\"") + HelpExampleRpc("getmempoolentry", "\"mytxid\"") }, - }.ToString()); - } + }.Check(request); uint256 hash = ParseHashV(request.params[0], "parameter 1"); @@ -715,8 +690,6 @@ static UniValue getmempoolentry(const JSONRPCRequest& request) static UniValue getblockhash(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() != 1) - throw std::runtime_error( RPCHelpMan{"getblockhash", "\nReturns hash of block in best-block-chain at height provided.\n", { @@ -729,7 +702,7 @@ static UniValue getblockhash(const JSONRPCRequest& request) HelpExampleCli("getblockhash", "1000") + HelpExampleRpc("getblockhash", "1000") }, - }.ToString()); + }.Check(request); LOCK(cs_main); @@ -743,8 +716,6 @@ static UniValue getblockhash(const JSONRPCRequest& request) static UniValue getblockheader(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) - throw std::runtime_error( RPCHelpMan{"getblockheader", "\nIf verbose is false, returns a string that is serialized, hex-encoded data for blockheader 'hash'.\n" "If verbose is true, returns an Object with information about blockheader <hash>.\n", @@ -780,7 +751,7 @@ static UniValue getblockheader(const JSONRPCRequest& request) HelpExampleCli("getblockheader", "\"00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09\"") + HelpExampleRpc("getblockheader", "\"00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09\"") }, - }.ToString()); + }.Check(request); uint256 hash(ParseHashV(request.params[0], "hash")); @@ -846,7 +817,7 @@ static CBlockUndo GetUndoChecked(const CBlockIndex* pblockindex) static UniValue getblock(const JSONRPCRequest& request) { - const RPCHelpMan help{"getblock", + RPCHelpMan{"getblock", "\nIf verbosity is 0, returns a string that is serialized, hex-encoded data for block 'hash'.\n" "If verbosity is 1, returns an Object with information about block <hash>.\n" "If verbosity is 2, returns an Object with information about block <hash> and information about each transaction. \n", @@ -898,11 +869,7 @@ static UniValue getblock(const JSONRPCRequest& request) HelpExampleCli("getblock", "\"00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09\"") + HelpExampleRpc("getblock", "\"00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09\"") }, - }; - - if (request.fHelp || !help.IsValidNumArgs(request.params.size())) { - throw std::runtime_error(help.ToString()); - } + }.Check(request); uint256 hash(ParseHashV(request.params[0], "blockhash")); @@ -1013,8 +980,6 @@ static bool GetUTXOStats(CCoinsView *view, CCoinsStats &stats) static UniValue pruneblockchain(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() != 1) - throw std::runtime_error( RPCHelpMan{"pruneblockchain", "", { {"height", RPCArg::Type::NUM, RPCArg::Optional::NO, "The block height to prune up to. May be set to a discrete height, or a unix timestamp\n" @@ -1027,7 +992,7 @@ static UniValue pruneblockchain(const JSONRPCRequest& request) HelpExampleCli("pruneblockchain", "1000") + HelpExampleRpc("pruneblockchain", "1000") }, - }.ToString()); + }.Check(request); if (!fPruneMode) throw JSONRPCError(RPC_MISC_ERROR, "Cannot prune blocks because node is not in prune mode."); @@ -1071,8 +1036,6 @@ static UniValue pruneblockchain(const JSONRPCRequest& request) static UniValue gettxoutsetinfo(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() != 0) - throw std::runtime_error( RPCHelpMan{"gettxoutsetinfo", "\nReturns statistics about the unspent transaction output set.\n" "Note this call may take some time.\n", @@ -1093,7 +1056,7 @@ static UniValue gettxoutsetinfo(const JSONRPCRequest& request) HelpExampleCli("gettxoutsetinfo", "") + HelpExampleRpc("gettxoutsetinfo", "") }, - }.ToString()); + }.Check(request); UniValue ret(UniValue::VOBJ); @@ -1116,8 +1079,6 @@ static UniValue gettxoutsetinfo(const JSONRPCRequest& request) UniValue gettxout(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() < 2 || request.params.size() > 3) - throw std::runtime_error( RPCHelpMan{"gettxout", "\nReturns details about an unspent transaction output.\n", { @@ -1151,7 +1112,7 @@ UniValue gettxout(const JSONRPCRequest& request) "\nAs a JSON-RPC call\n" + HelpExampleRpc("gettxout", "\"txid\", 1") }, - }.ToString()); + }.Check(request); LOCK(cs_main); @@ -1197,8 +1158,6 @@ static UniValue verifychain(const JSONRPCRequest& request) { int nCheckLevel = gArgs.GetArg("-checklevel", DEFAULT_CHECKLEVEL); int nCheckDepth = gArgs.GetArg("-checkblocks", DEFAULT_CHECKBLOCKS); - if (request.fHelp || request.params.size() > 2) - throw std::runtime_error( RPCHelpMan{"verifychain", "\nVerifies blockchain database.\n", { @@ -1212,7 +1171,7 @@ static UniValue verifychain(const JSONRPCRequest& request) HelpExampleCli("verifychain", "") + HelpExampleRpc("verifychain", "") }, - }.ToString()); + }.Check(request); LOCK(cs_main); @@ -1297,8 +1256,6 @@ static void BIP9SoftForkDescPushBack(UniValue& bip9_softforks, const Consensus:: UniValue getblockchaininfo(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() != 0) - throw std::runtime_error( RPCHelpMan{"getblockchaininfo", "Returns an object containing various state info regarding blockchain processing.\n", {}, @@ -1350,7 +1307,7 @@ UniValue getblockchaininfo(const JSONRPCRequest& request) HelpExampleCli("getblockchaininfo", "") + HelpExampleRpc("getblockchaininfo", "") }, - }.ToString()); + }.Check(request); LOCK(cs_main); @@ -1417,8 +1374,6 @@ struct CompareBlocksByHeight static UniValue getchaintips(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() != 0) - throw std::runtime_error( RPCHelpMan{"getchaintips", "Return information about all known tips in the block tree," " including the main chain as well as orphaned branches.\n", @@ -1449,14 +1404,14 @@ static UniValue getchaintips(const JSONRPCRequest& request) HelpExampleCli("getchaintips", "") + HelpExampleRpc("getchaintips", "") }, - }.ToString()); + }.Check(request); LOCK(cs_main); /* * Idea: the set of chain tips is ::ChainActive().tip, plus orphan blocks which do not have another orphan building off of them. * Algorithm: - * - Make one pass through mapBlockIndex, picking out the orphan blocks, and also storing a set of the orphan block's pprev pointers. + * - Make one pass through g_blockman.m_block_index, picking out the orphan blocks, and also storing a set of the orphan block's pprev pointers. * - Iterate through the orphan blocks. If the block isn't pointed to by another orphan, it is a chain tip. * - add ::ChainActive().Tip() */ @@ -1464,7 +1419,7 @@ static UniValue getchaintips(const JSONRPCRequest& request) std::set<const CBlockIndex*> setOrphans; std::set<const CBlockIndex*> setPrevs; - for (const std::pair<const uint256, CBlockIndex*>& item : mapBlockIndex) + for (const std::pair<const uint256, CBlockIndex*>& item : ::BlockIndex()) { if (!::ChainActive().Contains(item.second)) { setOrphans.insert(item.second); @@ -1540,8 +1495,6 @@ UniValue MempoolInfoToJSON(const CTxMemPool& pool) static UniValue getmempoolinfo(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() != 0) - throw std::runtime_error( RPCHelpMan{"getmempoolinfo", "\nReturns details on the active state of the TX memory pool.\n", {}, @@ -1560,15 +1513,13 @@ static UniValue getmempoolinfo(const JSONRPCRequest& request) HelpExampleCli("getmempoolinfo", "") + HelpExampleRpc("getmempoolinfo", "") }, - }.ToString()); + }.Check(request); return MempoolInfoToJSON(::mempool); } static UniValue preciousblock(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() != 1) - throw std::runtime_error( RPCHelpMan{"preciousblock", "\nTreats a block as if it were received before others with the same work.\n" "\nA later preciousblock call can override the effect of an earlier one.\n" @@ -1581,7 +1532,7 @@ static UniValue preciousblock(const JSONRPCRequest& request) HelpExampleCli("preciousblock", "\"blockhash\"") + HelpExampleRpc("preciousblock", "\"blockhash\"") }, - }.ToString()); + }.Check(request); uint256 hash(ParseHashV(request.params[0], "blockhash")); CBlockIndex* pblockindex; @@ -1606,8 +1557,6 @@ static UniValue preciousblock(const JSONRPCRequest& request) static UniValue invalidateblock(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() != 1) - throw std::runtime_error( RPCHelpMan{"invalidateblock", "\nPermanently marks a block as invalid, as if it violated a consensus rule.\n", { @@ -1618,7 +1567,7 @@ static UniValue invalidateblock(const JSONRPCRequest& request) HelpExampleCli("invalidateblock", "\"blockhash\"") + HelpExampleRpc("invalidateblock", "\"blockhash\"") }, - }.ToString()); + }.Check(request); uint256 hash(ParseHashV(request.params[0], "blockhash")); CValidationState state; @@ -1646,8 +1595,6 @@ static UniValue invalidateblock(const JSONRPCRequest& request) static UniValue reconsiderblock(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() != 1) - throw std::runtime_error( RPCHelpMan{"reconsiderblock", "\nRemoves invalidity status of a block, its ancestors and its descendants, reconsider them for activation.\n" "This can be used to undo the effects of invalidateblock.\n", @@ -1659,7 +1606,7 @@ static UniValue reconsiderblock(const JSONRPCRequest& request) HelpExampleCli("reconsiderblock", "\"blockhash\"") + HelpExampleRpc("reconsiderblock", "\"blockhash\"") }, - }.ToString()); + }.Check(request); uint256 hash(ParseHashV(request.params[0], "blockhash")); @@ -1685,8 +1632,6 @@ static UniValue reconsiderblock(const JSONRPCRequest& request) static UniValue getchaintxstats(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() > 2) - throw std::runtime_error( RPCHelpMan{"getchaintxstats", "\nCompute statistics about the total number and rate of transactions in the chain.\n", { @@ -1708,7 +1653,7 @@ static UniValue getchaintxstats(const JSONRPCRequest& request) HelpExampleCli("getchaintxstats", "") + HelpExampleRpc("getchaintxstats", "2016") }, - }.ToString()); + }.Check(request); const CBlockIndex* pindex; int blockcount = 30 * 24 * 60 * 60 / Params().GetConsensus().nPowTargetSpacing; // By default: 1 month @@ -1818,7 +1763,7 @@ static constexpr size_t PER_UTXO_OVERHEAD = sizeof(COutPoint) + sizeof(uint32_t) static UniValue getblockstats(const JSONRPCRequest& request) { - const RPCHelpMan help{"getblockstats", + 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", { @@ -1873,10 +1818,7 @@ static UniValue getblockstats(const JSONRPCRequest& request) HelpExampleCli("getblockstats", "1000 '[\"minfeerate\",\"avgfeerate\"]'") + HelpExampleRpc("getblockstats", "1000 '[\"minfeerate\",\"avgfeerate\"]'") }, - }; - if (request.fHelp || !help.IsValidNumArgs(request.params.size())) { - throw std::runtime_error(help.ToString()); - } + }.Check(request); LOCK(cs_main); @@ -2077,8 +2019,6 @@ static UniValue getblockstats(const JSONRPCRequest& request) static UniValue savemempool(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() != 0) { - throw std::runtime_error( RPCHelpMan{"savemempool", "\nDumps the mempool to disk. It will fail until the previous dump is fully loaded.\n", {}, @@ -2087,8 +2027,7 @@ static UniValue savemempool(const JSONRPCRequest& request) HelpExampleCli("savemempool", "") + HelpExampleRpc("savemempool", "") }, - }.ToString()); - } + }.Check(request); if (!::mempool.IsLoaded()) { throw JSONRPCError(RPC_MISC_ERROR, "The mempool was not loaded yet"); @@ -2163,8 +2102,6 @@ public: UniValue scantxoutset(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) - throw std::runtime_error( RPCHelpMan{"scantxoutset", "\nEXPERIMENTAL warning: this call may be removed or changed in future releases.\n" "\nScans the unspent transaction output set for entries that match certain output descriptors.\n" @@ -2213,8 +2150,7 @@ UniValue scantxoutset(const JSONRPCRequest& request) "]\n" }, RPCExamples{""}, - }.ToString() - ); + }.Check(request); RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VARR}); @@ -2301,8 +2237,6 @@ UniValue scantxoutset(const JSONRPCRequest& request) static UniValue getblockfilter(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) { - throw std::runtime_error( RPCHelpMan{"getblockfilter", "\nRetrieve a BIP 157 content filter for a particular block.\n", { @@ -2318,9 +2252,7 @@ static UniValue getblockfilter(const JSONRPCRequest& request) RPCExamples{ HelpExampleCli("getblockfilter", "\"00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09\" \"basic\"") } - }.ToString() - ); - } + }.Check(request); uint256 block_hash = ParseHashV(request.params[0], "blockhash"); std::string filtertype_name = "basic"; diff --git a/src/rpc/mining.cpp b/src/rpc/mining.cpp index 6636a8a29a..48bc88823a 100644 --- a/src/rpc/mining.cpp +++ b/src/rpc/mining.cpp @@ -78,8 +78,6 @@ static UniValue GetNetworkHashPS(int lookup, int height) { static UniValue getnetworkhashps(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() > 2) - throw std::runtime_error( RPCHelpMan{"getnetworkhashps", "\nReturns the estimated network hashes per second based on the last n blocks.\n" "Pass in [blocks] to override # of blocks, -1 specifies since last difficulty change.\n" @@ -95,7 +93,7 @@ static UniValue getnetworkhashps(const JSONRPCRequest& request) HelpExampleCli("getnetworkhashps", "") + HelpExampleRpc("getnetworkhashps", "") }, - }.ToString()); + }.Check(request); LOCK(cs_main); return GetNetworkHashPS(!request.params[0].isNull() ? request.params[0].get_int() : 120, !request.params[1].isNull() ? request.params[1].get_int() : -1); @@ -144,8 +142,6 @@ static UniValue generateBlocks(const CScript& coinbase_script, int nGenerate, ui static UniValue generatetoaddress(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() < 2 || request.params.size() > 3) - throw std::runtime_error( RPCHelpMan{"generatetoaddress", "\nMine blocks immediately to a specified address (before the RPC call returns)\n", { @@ -162,7 +158,7 @@ static UniValue generatetoaddress(const JSONRPCRequest& request) + "If you are running the bitcoin core wallet, you can get a new address to send the newly generated bitcoin to with:\n" + HelpExampleCli("getnewaddress", "") }, - }.ToString()); + }.Check(request); int nGenerate = request.params[0].get_int(); uint64_t nMaxTries = 1000000; @@ -182,8 +178,6 @@ static UniValue generatetoaddress(const JSONRPCRequest& request) static UniValue getmininginfo(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() != 0) { - throw std::runtime_error( RPCHelpMan{"getmininginfo", "\nReturns a json object containing mining-related information.", {}, @@ -203,8 +197,7 @@ static UniValue getmininginfo(const JSONRPCRequest& request) HelpExampleCli("getmininginfo", "") + HelpExampleRpc("getmininginfo", "") }, - }.ToString()); - } + }.Check(request); LOCK(cs_main); @@ -224,8 +217,6 @@ static UniValue getmininginfo(const JSONRPCRequest& request) // NOTE: Unlike wallet RPC (which use BTC values), mining RPCs follow GBT (BIP 22) in using satoshi amounts static UniValue prioritisetransaction(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() != 3) - throw std::runtime_error( RPCHelpMan{"prioritisetransaction", "Accepts the transaction into mined blocks at a higher (or lower) priority\n", { @@ -244,7 +235,7 @@ static UniValue prioritisetransaction(const JSONRPCRequest& request) HelpExampleCli("prioritisetransaction", "\"txid\" 0.0 10000") + HelpExampleRpc("prioritisetransaction", "\"txid\", 0.0, 10000") }, - }.ToString()); + }.Check(request); LOCK(cs_main); @@ -290,8 +281,6 @@ static std::string gbt_vb_name(const Consensus::DeploymentPos pos) { static UniValue getblocktemplate(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() > 1) - throw std::runtime_error( RPCHelpMan{"getblocktemplate", "\nIf the request parameters include a 'mode' key, that is used to explicitly select between the default 'template' request or a 'proposal'.\n" "It returns data needed to construct a block to work on.\n" @@ -301,7 +290,7 @@ static UniValue getblocktemplate(const JSONRPCRequest& request) " https://github.com/bitcoin/bips/blob/master/bip-0009.mediawiki#getblocktemplate_changes\n" " https://github.com/bitcoin/bips/blob/master/bip-0145.mediawiki\n", { - {"template_request", RPCArg::Type::OBJ, RPCArg::Optional::NO, "A json object in the following spec", + {"template_request", RPCArg::Type::OBJ, "{}", "A json object in the following spec", { {"mode", RPCArg::Type::STR, /* treat as named arg */ RPCArg::Optional::OMITTED_NAMED_ARG, "This must be set to \"template\", \"proposal\" (see BIP 23), or omitted"}, {"capabilities", RPCArg::Type::ARR, /* treat as named arg */ RPCArg::Optional::OMITTED_NAMED_ARG, "A list of strings", @@ -366,7 +355,7 @@ static UniValue getblocktemplate(const JSONRPCRequest& request) HelpExampleCli("getblocktemplate", "{\"rules\": [\"segwit\"]}") + HelpExampleRpc("getblocktemplate", "{\"rules\": [\"segwit\"]}") }, - }.ToString()); + }.Check(request); LOCK(cs_main); @@ -696,8 +685,6 @@ protected: static UniValue submitblock(const JSONRPCRequest& request) { // We allow 2 arguments for compliance with BIP22. Argument 2 is ignored. - if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) { - throw std::runtime_error( RPCHelpMan{"submitblock", "\nAttempts to submit new block to network.\n" "See https://en.bitcoin.it/wiki/BIP_0022 for full specification.\n", @@ -710,8 +697,7 @@ static UniValue submitblock(const JSONRPCRequest& request) HelpExampleCli("submitblock", "\"mydata\"") + HelpExampleRpc("submitblock", "\"mydata\"") }, - }.ToString()); - } + }.Check(request); std::shared_ptr<CBlock> blockptr = std::make_shared<CBlock>(); CBlock& block = *blockptr; @@ -761,8 +747,6 @@ static UniValue submitblock(const JSONRPCRequest& request) static UniValue submitheader(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() != 1) { - throw std::runtime_error( RPCHelpMan{"submitheader", "\nDecode the given hexdata as a header and submit it as a candidate chain tip if valid." "\nThrows when the header is invalid.\n", @@ -776,8 +760,7 @@ static UniValue submitheader(const JSONRPCRequest& request) HelpExampleCli("submitheader", "\"aabbcc\"") + HelpExampleRpc("submitheader", "\"aabbcc\"") }, - }.ToString()); - } + }.Check(request); CBlockHeader h; if (!DecodeHexBlockHeader(h, request.params[0].get_str())) { @@ -801,8 +784,6 @@ static UniValue submitheader(const JSONRPCRequest& request) static UniValue estimatesmartfee(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) - throw std::runtime_error( RPCHelpMan{"estimatesmartfee", "\nEstimates the approximate fee per kilobyte needed for a transaction to begin\n" "confirmation within conf_target blocks if possible and return the number of blocks\n" @@ -835,7 +816,7 @@ static UniValue estimatesmartfee(const JSONRPCRequest& request) RPCExamples{ HelpExampleCli("estimatesmartfee", "6") }, - }.ToString()); + }.Check(request); RPCTypeCheck(request.params, {UniValue::VNUM, UniValue::VSTR}); RPCTypeCheckArgument(request.params[0], UniValue::VNUM); @@ -866,8 +847,6 @@ static UniValue estimatesmartfee(const JSONRPCRequest& request) static UniValue estimaterawfee(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) - throw std::runtime_error( RPCHelpMan{"estimaterawfee", "\nWARNING: This interface is unstable and may disappear or change!\n" "\nWARNING: This is an advanced API call that is tightly coupled to the specific\n" @@ -908,7 +887,7 @@ static UniValue estimaterawfee(const JSONRPCRequest& request) RPCExamples{ HelpExampleCli("estimaterawfee", "6 0.9") }, - }.ToString()); + }.Check(request); RPCTypeCheck(request.params, {UniValue::VNUM, UniValue::VNUM}, true); RPCTypeCheckArgument(request.params[0], UniValue::VNUM); diff --git a/src/rpc/misc.cpp b/src/rpc/misc.cpp index 7a1bdec7b9..6be4057366 100644 --- a/src/rpc/misc.cpp +++ b/src/rpc/misc.cpp @@ -25,8 +25,6 @@ static UniValue validateaddress(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() != 1) - throw std::runtime_error( RPCHelpMan{"validateaddress", "\nReturn information about the given bitcoin address.\n", { @@ -47,7 +45,7 @@ static UniValue validateaddress(const JSONRPCRequest& request) HelpExampleCli("validateaddress", "\"1PSSGeFHDnKNxiEyFrD1wcEaHr9hrQDDWc\"") + HelpExampleRpc("validateaddress", "\"1PSSGeFHDnKNxiEyFrD1wcEaHr9hrQDDWc\"") }, - }.ToString()); + }.Check(request); CTxDestination dest = DecodeDestination(request.params[0].get_str()); bool isValid = IsValidDestination(dest); @@ -70,9 +68,6 @@ static UniValue validateaddress(const JSONRPCRequest& request) static UniValue createmultisig(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() < 2 || request.params.size() > 3) - { - std::string msg = RPCHelpMan{"createmultisig", "\nCreates a multi-signature address with n signature of m keys required.\n" "It returns a json object with the address and redeemScript.\n", @@ -96,9 +91,7 @@ static UniValue createmultisig(const JSONRPCRequest& request) "\nAs a JSON-RPC call\n" + HelpExampleRpc("createmultisig", "2, \"[\\\"03789ed0bb717d88f7d321a368d905e7430207ebbd82bd342cf11ae157a7ace5fd\\\",\\\"03dbc6764b8884a92e871274b87583e6d5c2a58819473e17e107ef3f6aa5a61626\\\"]\"") }, - }.ToString(); - throw std::runtime_error(msg); - } + }.Check(request); int required = request.params[0].get_int(); @@ -122,7 +115,7 @@ static UniValue createmultisig(const JSONRPCRequest& request) } // Construct using pay-to-script-hash: - CBasicKeyStore keystore; + FillableSigningProvider keystore; CScript inner; const CTxDestination dest = AddAndGetMultisigDestination(required, pubkeys, output_type, keystore, inner); @@ -135,8 +128,6 @@ static UniValue createmultisig(const JSONRPCRequest& request) UniValue getdescriptorinfo(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() != 1) { - throw std::runtime_error( RPCHelpMan{"getdescriptorinfo", {"\nAnalyses a descriptor.\n"}, { @@ -153,9 +144,7 @@ UniValue getdescriptorinfo(const JSONRPCRequest& request) RPCExamples{ "Analyse a descriptor\n" + HelpExampleCli("getdescriptorinfo", "\"wpkh([d34db33f/84h/0h/0h]0279be667ef9dcbbac55a06295Ce870b07029Bfcdb2dce28d959f2815b16f81798)\"") - }}.ToString() - ); - } + }}.Check(request); RPCTypeCheck(request.params, {UniValue::VSTR}); @@ -175,8 +164,6 @@ UniValue getdescriptorinfo(const JSONRPCRequest& request) UniValue deriveaddresses(const JSONRPCRequest& request) { - if (request.fHelp || request.params.empty() || request.params.size() > 2) { - throw std::runtime_error( RPCHelpMan{"deriveaddresses", {"\nDerives one or more addresses corresponding to an output descriptor.\n" "Examples of output descriptors are:\n" @@ -197,9 +184,7 @@ UniValue deriveaddresses(const JSONRPCRequest& request) RPCExamples{ "First three native segwit receive addresses\n" + HelpExampleCli("deriveaddresses", "\"wpkh([d34db33f/84h/0h/0h]xpub6DJ2dNUysrn5Vt36jH2KLBT2i1auw1tTSSomg8PhqNiUtx8QX2SvC9nrHu81fT41fvDUnhMjEzQgXnQjKEu3oaqMSzhSrHMxyyoEAmUHQbY/0/*)#cjjspncu\" \"[0,2]\"") - }}.ToString() - ); - } + }}.Check(request); RPCTypeCheck(request.params, {UniValue::VSTR, UniValueType()}); // Range argument is checked later const std::string desc_str = request.params[0].get_str(); @@ -254,8 +239,6 @@ UniValue deriveaddresses(const JSONRPCRequest& request) static UniValue verifymessage(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() != 3) - throw std::runtime_error( RPCHelpMan{"verifymessage", "\nVerify a signed message\n", { @@ -276,7 +259,7 @@ static UniValue verifymessage(const JSONRPCRequest& request) "\nAs a JSON-RPC call\n" + HelpExampleRpc("verifymessage", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\", \"signature\", \"my message\"") }, - }.ToString()); + }.Check(request); LOCK(cs_main); @@ -313,8 +296,6 @@ static UniValue verifymessage(const JSONRPCRequest& request) static UniValue signmessagewithprivkey(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() != 2) - throw std::runtime_error( RPCHelpMan{"signmessagewithprivkey", "\nSign a message with the private key of an address\n", { @@ -332,7 +313,7 @@ static UniValue signmessagewithprivkey(const JSONRPCRequest& request) "\nAs a JSON-RPC call\n" + HelpExampleRpc("signmessagewithprivkey", "\"privkey\", \"my message\"") }, - }.ToString()); + }.Check(request); std::string strPrivkey = request.params[0].get_str(); std::string strMessage = request.params[1].get_str(); @@ -355,8 +336,6 @@ static UniValue signmessagewithprivkey(const JSONRPCRequest& request) static UniValue setmocktime(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() != 1) - throw std::runtime_error( RPCHelpMan{"setmocktime", "\nSet the local time to given timestamp (-regtest only)\n", { @@ -365,8 +344,7 @@ static UniValue setmocktime(const JSONRPCRequest& request) }, RPCResults{}, RPCExamples{""}, - }.ToString() - ); + }.Check(request); if (!Params().MineBlocksOnDemand()) throw std::runtime_error("setmocktime for regression testing (-regtest mode) only"); @@ -421,8 +399,6 @@ static UniValue getmemoryinfo(const JSONRPCRequest& request) /* Please, avoid using the word "pool" here in the RPC interface or help, * as users will undoubtedly confuse it with the other "memory pool" */ - if (request.fHelp || request.params.size() > 1) - throw std::runtime_error( RPCHelpMan{"getmemoryinfo", "Returns an object containing information about memory usage.\n", { @@ -451,7 +427,7 @@ static UniValue getmemoryinfo(const JSONRPCRequest& request) HelpExampleCli("getmemoryinfo", "") + HelpExampleRpc("getmemoryinfo", "") }, - }.ToString()); + }.Check(request); std::string mode = request.params[0].isNull() ? "stats" : request.params[0].get_str(); if (mode == "stats") { @@ -489,8 +465,6 @@ static void EnableOrDisableLogCategories(UniValue cats, bool enable) { UniValue logging(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() > 2) { - throw std::runtime_error( RPCHelpMan{"logging", "Gets and sets the logging configuration.\n" "When called without an argument, returns the list of categories with status that are currently being debug logged or not.\n" @@ -522,8 +496,7 @@ UniValue logging(const JSONRPCRequest& request) HelpExampleCli("logging", "\"[\\\"all\\\"]\" \"[\\\"http\\\"]\"") + HelpExampleRpc("logging", "[\"all\"], [\"libevent\"]") }, - }.ToString()); - } + }.Check(request); uint32_t original_log_categories = LogInstance().GetCategoryMask(); if (request.params[0].isArray()) { diff --git a/src/rpc/net.cpp b/src/rpc/net.cpp index d993a88458..16b59e3d58 100644 --- a/src/rpc/net.cpp +++ b/src/rpc/net.cpp @@ -26,8 +26,6 @@ static UniValue getconnectioncount(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() != 0) - throw std::runtime_error( RPCHelpMan{"getconnectioncount", "\nReturns the number of connections to other nodes.\n", {}, @@ -38,7 +36,7 @@ static UniValue getconnectioncount(const JSONRPCRequest& request) HelpExampleCli("getconnectioncount", "") + HelpExampleRpc("getconnectioncount", "") }, - }.ToString()); + }.Check(request); if(!g_connman) throw JSONRPCError(RPC_CLIENT_P2P_DISABLED, "Error: Peer-to-peer functionality missing or disabled"); @@ -48,8 +46,6 @@ static UniValue getconnectioncount(const JSONRPCRequest& request) static UniValue ping(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() != 0) - throw std::runtime_error( RPCHelpMan{"ping", "\nRequests that a ping be sent to all other nodes, to measure ping time.\n" "Results provided in getpeerinfo, pingtime and pingwait fields are decimal seconds.\n" @@ -60,7 +56,7 @@ static UniValue ping(const JSONRPCRequest& request) HelpExampleCli("ping", "") + HelpExampleRpc("ping", "") }, - }.ToString()); + }.Check(request); if(!g_connman) throw JSONRPCError(RPC_CLIENT_P2P_DISABLED, "Error: Peer-to-peer functionality missing or disabled"); @@ -74,8 +70,6 @@ static UniValue ping(const JSONRPCRequest& request) static UniValue getpeerinfo(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() != 0) - throw std::runtime_error( RPCHelpMan{"getpeerinfo", "\nReturns data about each connected network node as a json array of objects.\n", {}, @@ -131,7 +125,7 @@ static UniValue getpeerinfo(const JSONRPCRequest& request) HelpExampleCli("getpeerinfo", "") + HelpExampleRpc("getpeerinfo", "") }, - }.ToString()); + }.Check(request); if(!g_connman) throw JSONRPCError(RPC_CLIENT_P2P_DISABLED, "Error: Peer-to-peer functionality missing or disabled"); @@ -258,8 +252,6 @@ static UniValue addnode(const JSONRPCRequest& request) static UniValue disconnectnode(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() == 0 || request.params.size() >= 3) - throw std::runtime_error( RPCHelpMan{"disconnectnode", "\nImmediately disconnects from the specified peer node.\n" "\nStrictly one out of 'address' and 'nodeid' can be provided to identify the node.\n" @@ -275,7 +267,7 @@ static UniValue disconnectnode(const JSONRPCRequest& request) + HelpExampleRpc("disconnectnode", "\"192.168.0.6:8333\"") + HelpExampleRpc("disconnectnode", "\"\", 1") }, - }.ToString()); + }.Check(request); if(!g_connman) throw JSONRPCError(RPC_CLIENT_P2P_DISABLED, "Error: Peer-to-peer functionality missing or disabled"); @@ -304,8 +296,6 @@ static UniValue disconnectnode(const JSONRPCRequest& request) static UniValue getaddednodeinfo(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() > 1) - throw std::runtime_error( RPCHelpMan{"getaddednodeinfo", "\nReturns information about the given added node, or all added nodes\n" "(note that onetry addnodes are not listed here)\n", @@ -331,7 +321,7 @@ static UniValue getaddednodeinfo(const JSONRPCRequest& request) HelpExampleCli("getaddednodeinfo", "\"192.168.0.201\"") + HelpExampleRpc("getaddednodeinfo", "\"192.168.0.201\"") }, - }.ToString()); + }.Check(request); if(!g_connman) throw JSONRPCError(RPC_CLIENT_P2P_DISABLED, "Error: Peer-to-peer functionality missing or disabled"); @@ -374,8 +364,6 @@ static UniValue getaddednodeinfo(const JSONRPCRequest& request) static UniValue getnettotals(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() > 0) - throw std::runtime_error( RPCHelpMan{"getnettotals", "\nReturns information about network traffic, including bytes in, bytes out,\n" "and current time.\n", @@ -400,7 +388,7 @@ static UniValue getnettotals(const JSONRPCRequest& request) HelpExampleCli("getnettotals", "") + HelpExampleRpc("getnettotals", "") }, - }.ToString()); + }.Check(request); if(!g_connman) throw JSONRPCError(RPC_CLIENT_P2P_DISABLED, "Error: Peer-to-peer functionality missing or disabled"); @@ -443,8 +431,6 @@ static UniValue GetNetworksInfo() static UniValue getnetworkinfo(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() != 0) - throw std::runtime_error( RPCHelpMan{"getnetworkinfo", "Returns an object containing various state info regarding P2P networking.\n", {}, @@ -485,7 +471,7 @@ static UniValue getnetworkinfo(const JSONRPCRequest& request) HelpExampleCli("getnetworkinfo", "") + HelpExampleRpc("getnetworkinfo", "") }, - }.ToString()); + }.Check(request); LOCK(cs_main); UniValue obj(UniValue::VOBJ); @@ -602,8 +588,6 @@ static UniValue setban(const JSONRPCRequest& request) static UniValue listbanned(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() != 0) - throw std::runtime_error( RPCHelpMan{"listbanned", "\nList all banned IPs/Subnets.\n", {}, @@ -612,7 +596,7 @@ static UniValue listbanned(const JSONRPCRequest& request) HelpExampleCli("listbanned", "") + HelpExampleRpc("listbanned", "") }, - }.ToString()); + }.Check(request); if(!g_banman) { throw JSONRPCError(RPC_DATABASE_ERROR, "Error: Ban database not loaded"); @@ -639,8 +623,6 @@ static UniValue listbanned(const JSONRPCRequest& request) static UniValue clearbanned(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() != 0) - throw std::runtime_error( RPCHelpMan{"clearbanned", "\nClear all banned IPs.\n", {}, @@ -649,7 +631,7 @@ static UniValue clearbanned(const JSONRPCRequest& request) HelpExampleCli("clearbanned", "") + HelpExampleRpc("clearbanned", "") }, - }.ToString()); + }.Check(request); if (!g_banman) { throw JSONRPCError(RPC_DATABASE_ERROR, "Error: Ban database not loaded"); } @@ -661,8 +643,6 @@ static UniValue clearbanned(const JSONRPCRequest& request) static UniValue setnetworkactive(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() != 1) { - throw std::runtime_error( RPCHelpMan{"setnetworkactive", "\nDisable/enable all p2p network activity.\n", { @@ -670,9 +650,7 @@ static UniValue setnetworkactive(const JSONRPCRequest& request) }, RPCResults{}, RPCExamples{""}, - }.ToString() - ); - } + }.Check(request); if (!g_connman) { throw JSONRPCError(RPC_CLIENT_P2P_DISABLED, "Error: Peer-to-peer functionality missing or disabled"); @@ -685,8 +663,6 @@ static UniValue setnetworkactive(const JSONRPCRequest& request) static UniValue getnodeaddresses(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() > 1) { - throw std::runtime_error( RPCHelpMan{"getnodeaddresses", "\nReturn known addresses which can potentially be used to find new nodes in the network\n", { @@ -707,8 +683,7 @@ static UniValue getnodeaddresses(const JSONRPCRequest& request) HelpExampleCli("getnodeaddresses", "8") + HelpExampleRpc("getnodeaddresses", "8") }, - }.ToString()); - } + }.Check(request); if (!g_connman) { throw JSONRPCError(RPC_CLIENT_P2P_DISABLED, "Error: Peer-to-peer functionality missing or disabled"); } diff --git a/src/rpc/protocol.h b/src/rpc/protocol.h index 6bcbccbd4f..ef6537e4ec 100644 --- a/src/rpc/protocol.h +++ b/src/rpc/protocol.h @@ -6,15 +6,6 @@ #ifndef BITCOIN_RPC_PROTOCOL_H #define BITCOIN_RPC_PROTOCOL_H -#include <fs.h> - -#include <list> -#include <map> -#include <stdint.h> -#include <string> - -#include <univalue.h> - //! HTTP status codes enum HTTPStatusCode { @@ -92,18 +83,4 @@ enum RPCErrorCode RPC_FORBIDDEN_BY_SAFE_MODE = -2, //!< Server is in safe mode, and command is not allowed in safe mode }; -UniValue JSONRPCRequestObj(const std::string& strMethod, const UniValue& params, const UniValue& id); -UniValue JSONRPCReplyObj(const UniValue& result, const UniValue& error, const UniValue& id); -std::string JSONRPCReply(const UniValue& result, const UniValue& error, const UniValue& id); -UniValue JSONRPCError(int code, const std::string& message); - -/** Generate a new RPC authentication cookie and write it to disk */ -bool GenerateAuthCookie(std::string *cookie_out); -/** Read the RPC authentication cookie from disk */ -bool GetAuthCookie(std::string *cookie_out); -/** Delete RPC authentication cookie from disk */ -void DeleteAuthCookie(); -/** Parse JSON-RPC batch reply into a vector */ -std::vector<UniValue> JSONRPCProcessBatchReply(const UniValue &in, size_t num); - #endif // BITCOIN_RPC_PROTOCOL_H diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp index 6ac16622c2..532765b3d8 100644 --- a/src/rpc/rawtransaction.cpp +++ b/src/rpc/rawtransaction.cpp @@ -10,7 +10,6 @@ #include <core_io.h> #include <index/txindex.h> #include <key_io.h> -#include <keystore.h> #include <merkleblock.h> #include <node/coin.h> #include <node/psbt.h> @@ -24,6 +23,7 @@ #include <script/script.h> #include <script/script_error.h> #include <script/sign.h> +#include <script/signingprovider.h> #include <script/standard.h> #include <uint256.h> #include <util/moneystr.h> @@ -71,7 +71,7 @@ static void TxToJSON(const CTransaction& tx, const uint256 hashBlock, UniValue& static UniValue getrawtransaction(const JSONRPCRequest& request) { - const RPCHelpMan help{ + RPCHelpMan{ "getrawtransaction", "\nReturn the raw transaction data.\n" @@ -149,11 +149,7 @@ static UniValue getrawtransaction(const JSONRPCRequest& request) + HelpExampleCli("getrawtransaction", "\"mytxid\" false \"myblockhash\"") + HelpExampleCli("getrawtransaction", "\"mytxid\" true \"myblockhash\"") }, - }; - - if (request.fHelp || !help.IsValidNumArgs(request.params.size())) { - throw std::runtime_error(help.ToString()); - } + }.Check(request); bool in_active_chain = true; uint256 hash = ParseHashV(request.params[0], "parameter 1"); @@ -217,8 +213,6 @@ static UniValue getrawtransaction(const JSONRPCRequest& request) static UniValue gettxoutproof(const JSONRPCRequest& request) { - if (request.fHelp || (request.params.size() != 1 && request.params.size() != 2)) - throw std::runtime_error( RPCHelpMan{"gettxoutproof", "\nReturns a hex-encoded proof that \"txid\" was included in a block.\n" "\nNOTE: By default this function only works sometimes. This is when there is an\n" @@ -237,8 +231,7 @@ static UniValue gettxoutproof(const JSONRPCRequest& request) "\"data\" (string) A string that is a serialized, hex-encoded data for the proof.\n" }, RPCExamples{""}, - }.ToString() - ); + }.Check(request); std::set<uint256> setTxids; uint256 oneTxid; @@ -313,8 +306,6 @@ static UniValue gettxoutproof(const JSONRPCRequest& request) static UniValue verifytxoutproof(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() != 1) - throw std::runtime_error( RPCHelpMan{"verifytxoutproof", "\nVerifies that a proof points to a transaction in a block, returning the transaction it commits to\n" "and throwing an RPC error if the block is not in our best chain\n", @@ -325,8 +316,7 @@ static UniValue verifytxoutproof(const JSONRPCRequest& request) "[\"txid\"] (array, strings) The txid(s) which the proof commits to, or empty array if the proof can not be validated.\n" }, RPCExamples{""}, - }.ToString() - ); + }.Check(request); CDataStream ssMB(ParseHexV(request.params[0], "proof"), SER_NETWORK, PROTOCOL_VERSION | SERIALIZE_TRANSACTION_NO_WITNESS); CMerkleBlock merkleBlock; @@ -358,8 +348,6 @@ static UniValue verifytxoutproof(const JSONRPCRequest& request) static UniValue createrawtransaction(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() < 2 || request.params.size() > 4) { - throw std::runtime_error( RPCHelpMan{"createrawtransaction", "\nCreate a transaction spending the given inputs and creating new outputs.\n" "Outputs can be addresses or data.\n" @@ -408,8 +396,7 @@ static UniValue createrawtransaction(const JSONRPCRequest& request) + HelpExampleRpc("createrawtransaction", "\"[{\\\"txid\\\":\\\"myid\\\",\\\"vout\\\":0}]\", \"[{\\\"address\\\":0.01}]\"") + HelpExampleRpc("createrawtransaction", "\"[{\\\"txid\\\":\\\"myid\\\",\\\"vout\\\":0}]\", \"[{\\\"data\\\":\\\"00010203\\\"}]\"") }, - }.ToString()); - } + }.Check(request); RPCTypeCheck(request.params, { UniValue::VARR, @@ -426,7 +413,7 @@ static UniValue createrawtransaction(const JSONRPCRequest& request) static UniValue decoderawtransaction(const JSONRPCRequest& request) { - const RPCHelpMan help{"decoderawtransaction", + RPCHelpMan{"decoderawtransaction", "\nReturn a JSON object representing the serialized, hex-encoded transaction.\n", { {"hexstring", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The transaction hex string"}, @@ -483,11 +470,7 @@ static UniValue decoderawtransaction(const JSONRPCRequest& request) HelpExampleCli("decoderawtransaction", "\"hexstring\"") + HelpExampleRpc("decoderawtransaction", "\"hexstring\"") }, - }; - - if (request.fHelp || !help.IsValidNumArgs(request.params.size())) { - throw std::runtime_error(help.ToString()); - } + }.Check(request); RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VBOOL}); @@ -518,7 +501,7 @@ static std::string GetAllOutputTypes() static UniValue decodescript(const JSONRPCRequest& request) { - const RPCHelpMan help{"decodescript", + RPCHelpMan{"decodescript", "\nDecode a hex-encoded script.\n", { {"hexstring", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "the hex-encoded script"}, @@ -549,11 +532,7 @@ static UniValue decodescript(const JSONRPCRequest& request) HelpExampleCli("decodescript", "\"hexstring\"") + HelpExampleRpc("decodescript", "\"hexstring\"") }, - }; - - if (request.fHelp || !help.IsValidNumArgs(request.params.size())) { - throw std::runtime_error(help.ToString()); - } + }.Check(request); RPCTypeCheck(request.params, {UniValue::VSTR}); @@ -610,8 +589,6 @@ static UniValue decodescript(const JSONRPCRequest& request) static UniValue combinerawtransaction(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() != 1) - throw std::runtime_error( RPCHelpMan{"combinerawtransaction", "\nCombine multiple partially signed transactions into one transaction.\n" "The combined transaction may be another partially signed transaction or a \n" @@ -629,7 +606,7 @@ static UniValue combinerawtransaction(const JSONRPCRequest& request) RPCExamples{ HelpExampleCli("combinerawtransaction", "[\"myhex1\", \"myhex2\", \"myhex3\"]") }, - }.ToString()); + }.Check(request); UniValue txs = request.params[0].get_array(); @@ -694,8 +671,6 @@ static UniValue combinerawtransaction(const JSONRPCRequest& request) static UniValue signrawtransactionwithkey(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() < 2 || request.params.size() > 4) - throw std::runtime_error( RPCHelpMan{"signrawtransactionwithkey", "\nSign inputs for raw transaction (serialized, hex-encoded).\n" "The second argument is an array of base58-encoded private\n" @@ -752,7 +727,7 @@ static UniValue signrawtransactionwithkey(const JSONRPCRequest& request) HelpExampleCli("signrawtransactionwithkey", "\"myhex\" \"[\\\"key1\\\",\\\"key2\\\"]\"") + HelpExampleRpc("signrawtransactionwithkey", "\"myhex\", \"[\\\"key1\\\",\\\"key2\\\"]\"") }, - }.ToString()); + }.Check(request); RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VARR, UniValue::VARR, UniValue::VSTR}, true); @@ -761,7 +736,7 @@ static UniValue signrawtransactionwithkey(const JSONRPCRequest& request) throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed"); } - CBasicKeyStore keystore; + FillableSigningProvider keystore; const UniValue& keys = request.params[1].get_array(); for (unsigned int idx = 0; idx < keys.size(); ++idx) { UniValue k = keys[idx]; @@ -784,7 +759,7 @@ static UniValue signrawtransactionwithkey(const JSONRPCRequest& request) static UniValue sendrawtransaction(const JSONRPCRequest& request) { - const RPCHelpMan help{"sendrawtransaction", + RPCHelpMan{"sendrawtransaction", "\nSubmits raw transaction (serialized, hex-encoded) to local node and network.\n" "\nAlso see createrawtransaction and signrawtransactionwithkey calls.\n", { @@ -806,11 +781,7 @@ static UniValue sendrawtransaction(const JSONRPCRequest& request) "\nAs a JSON-RPC call\n" + HelpExampleRpc("sendrawtransaction", "\"signedhex\"") }, - }; - - if (request.fHelp || !help.IsValidNumArgs(request.params.size())) { - throw std::runtime_error(help.ToString()); - } + }.Check(request); RPCTypeCheck(request.params, { UniValue::VSTR, @@ -848,7 +819,7 @@ static UniValue sendrawtransaction(const JSONRPCRequest& request) static UniValue testmempoolaccept(const JSONRPCRequest& request) { - const RPCHelpMan help{"testmempoolaccept", + RPCHelpMan{"testmempoolaccept", "\nReturns result of mempool acceptance tests indicating if raw transaction (serialized, hex-encoded) would be accepted by mempool.\n" "\nThis checks if the transaction violates the consensus or policy rules.\n" "\nSee sendrawtransaction call.\n", @@ -881,11 +852,7 @@ static UniValue testmempoolaccept(const JSONRPCRequest& request) "\nAs a JSON-RPC call\n" + HelpExampleRpc("testmempoolaccept", "[\"signedhex\"]") }, - }; - - if (request.fHelp || !help.IsValidNumArgs(request.params.size())) { - throw std::runtime_error(help.ToString()); - } + }.Check(request); RPCTypeCheck(request.params, { UniValue::VARR, @@ -964,8 +931,6 @@ static std::string WriteHDKeypath(std::vector<uint32_t>& keypath) UniValue decodepsbt(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() != 1) - throw std::runtime_error( RPCHelpMan{"decodepsbt", "\nReturn a JSON object representing the serialized, base64-encoded partially signed Bitcoin transaction.\n", { @@ -1062,7 +1027,7 @@ UniValue decodepsbt(const JSONRPCRequest& request) RPCExamples{ HelpExampleCli("decodepsbt", "\"psbt\"") }, - }.ToString()); + }.Check(request); RPCTypeCheck(request.params, {UniValue::VSTR}); @@ -1239,8 +1204,6 @@ UniValue decodepsbt(const JSONRPCRequest& request) UniValue combinepsbt(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() != 1) - throw std::runtime_error( RPCHelpMan{"combinepsbt", "\nCombine multiple partially signed Bitcoin transactions into one transaction.\n" "Implements the Combiner role.\n", @@ -1257,7 +1220,7 @@ UniValue combinepsbt(const JSONRPCRequest& request) RPCExamples{ HelpExampleCli("combinepsbt", "[\"mybase64_1\", \"mybase64_2\", \"mybase64_3\"]") }, - }.ToString()); + }.Check(request); RPCTypeCheck(request.params, {UniValue::VARR}, true); @@ -1289,8 +1252,6 @@ UniValue combinepsbt(const JSONRPCRequest& request) UniValue finalizepsbt(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) - throw std::runtime_error( RPCHelpMan{"finalizepsbt", "Finalize the inputs of a PSBT. If the transaction is fully signed, it will produce a\n" "network serialized transaction which can be broadcast with sendrawtransaction. Otherwise a PSBT will be\n" @@ -1312,7 +1273,7 @@ UniValue finalizepsbt(const JSONRPCRequest& request) RPCExamples{ HelpExampleCli("finalizepsbt", "\"psbt\"") }, - }.ToString()); + }.Check(request); RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VBOOL}, true); @@ -1348,8 +1309,6 @@ UniValue finalizepsbt(const JSONRPCRequest& request) UniValue createpsbt(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() < 2 || request.params.size() > 4) - throw std::runtime_error( RPCHelpMan{"createpsbt", "\nCreates a transaction in the Partially Signed Transaction format.\n" "Implements the Creator role.\n", @@ -1392,7 +1351,7 @@ UniValue createpsbt(const JSONRPCRequest& request) RPCExamples{ HelpExampleCli("createpsbt", "\"[{\\\"txid\\\":\\\"myid\\\",\\\"vout\\\":0}]\" \"[{\\\"data\\\":\\\"00010203\\\"}]\"") }, - }.ToString()); + }.Check(request); RPCTypeCheck(request.params, { @@ -1424,7 +1383,7 @@ UniValue createpsbt(const JSONRPCRequest& request) UniValue converttopsbt(const JSONRPCRequest& request) { - const RPCHelpMan help{"converttopsbt", + RPCHelpMan{"converttopsbt", "\nConverts a network serialized transaction to a PSBT. This should be used only with createrawtransaction and fundrawtransaction\n" "createpsbt and walletcreatefundedpsbt should be used for new applications.\n", { @@ -1448,11 +1407,7 @@ UniValue converttopsbt(const JSONRPCRequest& request) "\nConvert the transaction to a PSBT\n" + HelpExampleCli("converttopsbt", "\"rawtransaction\"") }, - }; - - if (request.fHelp || !help.IsValidNumArgs(request.params.size())) { - throw std::runtime_error(help.ToString()); - } + }.Check(request); RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VBOOL, UniValue::VBOOL}, true); @@ -1495,8 +1450,6 @@ UniValue converttopsbt(const JSONRPCRequest& request) UniValue utxoupdatepsbt(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) { - throw std::runtime_error( RPCHelpMan{"utxoupdatepsbt", "\nUpdates all segwit inputs and outputs in a PSBT with data from output descriptors, the UTXO set or the mempool.\n", { @@ -1514,8 +1467,7 @@ UniValue utxoupdatepsbt(const JSONRPCRequest& request) }, RPCExamples { HelpExampleCli("utxoupdatepsbt", "\"psbt\"") - }}.ToString()); - } + }}.Check(request); RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VARR}, true); @@ -1585,8 +1537,6 @@ UniValue utxoupdatepsbt(const JSONRPCRequest& request) UniValue joinpsbts(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() != 1) { - throw std::runtime_error( RPCHelpMan{"joinpsbts", "\nJoins multiple distinct PSBTs with different inputs and outputs into one PSBT with inputs and outputs from all of the PSBTs\n" "No input in any of the PSBTs can be in more than one of the PSBTs.\n", @@ -1601,8 +1551,7 @@ UniValue joinpsbts(const JSONRPCRequest& request) }, RPCExamples { HelpExampleCli("joinpsbts", "\"psbt\"") - }}.ToString()); - } + }}.Check(request); RPCTypeCheck(request.params, {UniValue::VARR}, true); @@ -1659,8 +1608,6 @@ UniValue joinpsbts(const JSONRPCRequest& request) UniValue analyzepsbt(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() != 1) { - throw std::runtime_error( RPCHelpMan{"analyzepsbt", "\nAnalyzes and provides information about the current status of a PSBT and its inputs\n", { @@ -1694,8 +1641,7 @@ UniValue analyzepsbt(const JSONRPCRequest& request) }, RPCExamples { HelpExampleCli("analyzepsbt", "\"psbt\"") - }}.ToString()); - } + }}.Check(request); RPCTypeCheck(request.params, {UniValue::VSTR}); diff --git a/src/rpc/rawtransaction_util.cpp b/src/rpc/rawtransaction_util.cpp index 14fcb628eb..1c96d01232 100644 --- a/src/rpc/rawtransaction_util.cpp +++ b/src/rpc/rawtransaction_util.cpp @@ -8,11 +8,12 @@ #include <coins.h> #include <core_io.h> #include <key_io.h> -#include <keystore.h> #include <policy/policy.h> #include <primitives/transaction.h> -#include <rpc/protocol.h> +#include <rpc/request.h> #include <rpc/util.h> +#include <script/sign.h> +#include <script/signingprovider.h> #include <tinyformat.h> #include <univalue.h> #include <util/rbf.h> @@ -148,7 +149,7 @@ static void TxInErrorToJSON(const CTxIn& txin, UniValue& vErrorsRet, const std:: vErrorsRet.push_back(entry); } -UniValue SignTransaction(CMutableTransaction& mtx, const UniValue& prevTxsUnival, CBasicKeyStore* keystore, std::map<COutPoint, Coin>& coins, bool is_temp_keystore, const UniValue& hashType) +UniValue SignTransaction(CMutableTransaction& mtx, const UniValue& prevTxsUnival, FillableSigningProvider* keystore, std::map<COutPoint, Coin>& coins, bool is_temp_keystore, const UniValue& hashType) { // Add previous txouts given in the RPC call: if (!prevTxsUnival.isNull()) { diff --git a/src/rpc/rawtransaction_util.h b/src/rpc/rawtransaction_util.h index c115d33a77..d198887b93 100644 --- a/src/rpc/rawtransaction_util.h +++ b/src/rpc/rawtransaction_util.h @@ -7,7 +7,7 @@ #include <map> -class CBasicKeyStore; +class FillableSigningProvider; class UniValue; struct CMutableTransaction; class Coin; @@ -24,7 +24,7 @@ class COutPoint; * @param hashType The signature hash type * @returns JSON object with details of signed transaction */ -UniValue SignTransaction(CMutableTransaction& mtx, const UniValue& prevTxs, CBasicKeyStore* keystore, std::map<COutPoint, Coin>& coins, bool tempKeystore, const UniValue& hashType); +UniValue SignTransaction(CMutableTransaction& mtx, const UniValue& prevTxs, FillableSigningProvider* keystore, std::map<COutPoint, Coin>& coins, bool tempKeystore, const UniValue& hashType); /** Create a transaction from univalue parameters */ CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniValue& outputs_in, const UniValue& locktime, const UniValue& rbf); diff --git a/src/rpc/protocol.cpp b/src/rpc/request.cpp index 33b0130a94..56cac6661e 100644 --- a/src/rpc/protocol.cpp +++ b/src/rpc/request.cpp @@ -1,15 +1,16 @@ // Copyright (c) 2010 Satoshi Nakamoto -// Copyright (c) 2009-2018 The Bitcoin Core developers +// Copyright (c) 2009-2019 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#include <rpc/protocol.h> +#include <rpc/request.h> + +#include <fs.h> #include <random.h> -#include <tinyformat.h> +#include <rpc/protocol.h> #include <util/system.h> #include <util/strencodings.h> -#include <util/time.h> /** * JSON-RPC protocol. Bitcoin speaks version 1.0 for maximum compatibility, @@ -148,3 +149,36 @@ std::vector<UniValue> JSONRPCProcessBatchReply(const UniValue &in, size_t num) } return batch; } + +void JSONRPCRequest::parse(const UniValue& valRequest) +{ + // Parse request + if (!valRequest.isObject()) + throw JSONRPCError(RPC_INVALID_REQUEST, "Invalid Request object"); + const UniValue& request = valRequest.get_obj(); + + // Parse id now so errors from here on will have the id + id = find_value(request, "id"); + + // Parse method + UniValue valMethod = find_value(request, "method"); + if (valMethod.isNull()) + throw JSONRPCError(RPC_INVALID_REQUEST, "Missing method"); + if (!valMethod.isStr()) + throw JSONRPCError(RPC_INVALID_REQUEST, "Method must be a string"); + strMethod = valMethod.get_str(); + if (fLogIPs) + LogPrint(BCLog::RPC, "ThreadRPCServer method=%s user=%s peeraddr=%s\n", SanitizeString(strMethod), + this->authUser, this->peerAddr); + else + LogPrint(BCLog::RPC, "ThreadRPCServer method=%s user=%s\n", SanitizeString(strMethod), this->authUser); + + // Parse params + UniValue valParams = find_value(request, "params"); + if (valParams.isArray() || valParams.isObject()) + params = valParams; + else if (valParams.isNull()) + params = UniValue(UniValue::VARR); + else + throw JSONRPCError(RPC_INVALID_REQUEST, "Params must be an array or object"); +} diff --git a/src/rpc/request.h b/src/rpc/request.h new file mode 100644 index 0000000000..99eb4f9354 --- /dev/null +++ b/src/rpc/request.h @@ -0,0 +1,42 @@ +// Copyright (c) 2010 Satoshi Nakamoto +// Copyright (c) 2009-2019 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_RPC_REQUEST_H +#define BITCOIN_RPC_REQUEST_H + +#include <string> + +#include <univalue.h> + +UniValue JSONRPCRequestObj(const std::string& strMethod, const UniValue& params, const UniValue& id); +UniValue JSONRPCReplyObj(const UniValue& result, const UniValue& error, const UniValue& id); +std::string JSONRPCReply(const UniValue& result, const UniValue& error, const UniValue& id); +UniValue JSONRPCError(int code, const std::string& message); + +/** Generate a new RPC authentication cookie and write it to disk */ +bool GenerateAuthCookie(std::string *cookie_out); +/** Read the RPC authentication cookie from disk */ +bool GetAuthCookie(std::string *cookie_out); +/** Delete RPC authentication cookie from disk */ +void DeleteAuthCookie(); +/** Parse JSON-RPC batch reply into a vector */ +std::vector<UniValue> JSONRPCProcessBatchReply(const UniValue &in, size_t num); + +class JSONRPCRequest +{ +public: + UniValue id; + std::string strMethod; + UniValue params; + bool fHelp; + std::string URI; + std::string authUser; + std::string peerAddr; + + JSONRPCRequest() : id(NullUniValue), params(NullUniValue), fHelp(false) {} + void parse(const UniValue& valRequest); +}; + +#endif // BITCOIN_RPC_REQUEST_H diff --git a/src/rpc/server.cpp b/src/rpc/server.cpp index 145b99b567..18f7426bcf 100644 --- a/src/rpc/server.cpp +++ b/src/rpc/server.cpp @@ -178,8 +178,6 @@ UniValue stop(const JSONRPCRequest& jsonRequest) static UniValue uptime(const JSONRPCRequest& jsonRequest) { - if (jsonRequest.fHelp || jsonRequest.params.size() > 0) - throw std::runtime_error( RPCHelpMan{"uptime", "\nReturns the total uptime of the server.\n", {}, @@ -190,15 +188,13 @@ static UniValue uptime(const JSONRPCRequest& jsonRequest) HelpExampleCli("uptime", "") + HelpExampleRpc("uptime", "") }, - }.ToString()); + }.Check(jsonRequest); return GetTime() - GetStartupTime(); } static UniValue getrpcinfo(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() > 0) { - throw std::runtime_error( RPCHelpMan{"getrpcinfo", "\nReturns details of the RPC server.\n", {}, @@ -217,9 +213,7 @@ static UniValue getrpcinfo(const JSONRPCRequest& request) RPCExamples{ HelpExampleCli("getrpcinfo", "") + HelpExampleRpc("getrpcinfo", "")}, - }.ToString() - ); - } + }.Check(request); LOCK(g_rpc_server_info.mutex); UniValue active_commands(UniValue::VARR); @@ -334,39 +328,6 @@ bool RPCIsInWarmup(std::string *outStatus) return fRPCInWarmup; } -void JSONRPCRequest::parse(const UniValue& valRequest) -{ - // Parse request - if (!valRequest.isObject()) - throw JSONRPCError(RPC_INVALID_REQUEST, "Invalid Request object"); - const UniValue& request = valRequest.get_obj(); - - // Parse id now so errors from here on will have the id - id = find_value(request, "id"); - - // Parse method - UniValue valMethod = find_value(request, "method"); - if (valMethod.isNull()) - throw JSONRPCError(RPC_INVALID_REQUEST, "Missing method"); - if (!valMethod.isStr()) - throw JSONRPCError(RPC_INVALID_REQUEST, "Method must be a string"); - strMethod = valMethod.get_str(); - if (fLogIPs) - LogPrint(BCLog::RPC, "ThreadRPCServer method=%s user=%s peeraddr=%s\n", SanitizeString(strMethod), - this->authUser, this->peerAddr); - else - LogPrint(BCLog::RPC, "ThreadRPCServer method=%s user=%s\n", SanitizeString(strMethod), this->authUser); - - // Parse params - UniValue valParams = find_value(request, "params"); - if (valParams.isArray() || valParams.isObject()) - params = valParams; - else if (valParams.isNull()) - params = UniValue(UniValue::VARR); - else - throw JSONRPCError(RPC_INVALID_REQUEST, "Params must be an array or object"); -} - bool IsDeprecatedRPCEnabled(const std::string& method) { const std::vector<std::string> enabled_methods = gArgs.GetArgs("-deprecatedrpc"); diff --git a/src/rpc/server.h b/src/rpc/server.h index 431ff0bb7c..b060db5bf9 100644 --- a/src/rpc/server.h +++ b/src/rpc/server.h @@ -7,13 +7,14 @@ #define BITCOIN_RPC_SERVER_H #include <amount.h> -#include <rpc/protocol.h> +#include <rpc/request.h> #include <uint256.h> #include <list> #include <map> #include <stdint.h> #include <string> +#include <functional> #include <univalue.h> @@ -27,21 +28,6 @@ namespace RPCServer void OnStopped(std::function<void ()> slot); } -class JSONRPCRequest -{ -public: - UniValue id; - std::string strMethod; - UniValue params; - bool fHelp; - std::string URI; - std::string authUser; - std::string peerAddr; - - JSONRPCRequest() : id(NullUniValue), params(NullUniValue), fHelp(false) {} - void parse(const UniValue& valRequest); -}; - /** Query whether RPC is running */ bool IsRPCRunning(); diff --git a/src/rpc/util.cpp b/src/rpc/util.cpp index 67ccb225b5..de90276677 100644 --- a/src/rpc/util.cpp +++ b/src/rpc/util.cpp @@ -3,8 +3,8 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include <key_io.h> -#include <keystore.h> #include <outputtype.h> +#include <script/signingprovider.h> #include <rpc/util.h> #include <script/descriptor.h> #include <tinyformat.h> @@ -131,8 +131,8 @@ CPubKey HexToPubKey(const std::string& hex_in) return vchPubKey; } -// Retrieves a public key for an address from the given CKeyStore -CPubKey AddrToPubKey(CKeyStore* const keystore, const std::string& addr_in) +// Retrieves a public key for an address from the given FillableSigningProvider +CPubKey AddrToPubKey(FillableSigningProvider* const keystore, const std::string& addr_in) { CTxDestination dest = DecodeDestination(addr_in); if (!IsValidDestination(dest)) { @@ -153,7 +153,7 @@ CPubKey AddrToPubKey(CKeyStore* const keystore, const std::string& addr_in) } // Creates a multisig address from a given list of public keys, number of signatures required, and the address type -CTxDestination AddAndGetMultisigDestination(const int required, const std::vector<CPubKey>& pubkeys, OutputType type, CKeyStore& keystore, CScript& script_out) +CTxDestination AddAndGetMultisigDestination(const int required, const std::vector<CPubKey>& pubkeys, OutputType type, FillableSigningProvider& keystore, CScript& script_out) { // Gather public keys if (required < 1) { diff --git a/src/rpc/util.h b/src/rpc/util.h index 8762281d73..4c3322b879 100644 --- a/src/rpc/util.h +++ b/src/rpc/util.h @@ -9,6 +9,7 @@ #include <outputtype.h> #include <pubkey.h> #include <rpc/protocol.h> +#include <rpc/request.h> #include <script/script.h> #include <script/sign.h> #include <script/standard.h> @@ -19,7 +20,7 @@ #include <boost/variant.hpp> -class CKeyStore; +class FillableSigningProvider; class CPubKey; class CScript; struct InitInterfaces; @@ -72,8 +73,8 @@ extern std::string HelpExampleCli(const std::string& methodname, const std::stri extern std::string HelpExampleRpc(const std::string& methodname, const std::string& args); CPubKey HexToPubKey(const std::string& hex_in); -CPubKey AddrToPubKey(CKeyStore* const keystore, const std::string& addr_in); -CTxDestination AddAndGetMultisigDestination(const int required, const std::vector<CPubKey>& pubkeys, OutputType type, CKeyStore& keystore, CScript& script_out); +CPubKey AddrToPubKey(FillableSigningProvider* const keystore, const std::string& addr_in); +CTxDestination AddAndGetMultisigDestination(const int required, const std::vector<CPubKey>& pubkeys, OutputType type, FillableSigningProvider& keystore, CScript& script_out); UniValue DescribeAddress(const CTxDestination& dest); @@ -226,7 +227,7 @@ struct RPCResults { struct RPCExamples { const std::string m_examples; - RPCExamples( + explicit RPCExamples( std::string examples) : m_examples(std::move(examples)) { @@ -242,6 +243,15 @@ public: std::string ToString() const; /** If the supplied number of args is neither too small nor too high */ bool IsValidNumArgs(size_t num_args) const; + /** + * Check if the given request is valid according to this command or if + * the user is asking for help information, and throw help when appropriate. + */ + inline void Check(const JSONRPCRequest& request) const { + if (request.fHelp || !IsValidNumArgs(request.params.size())) { + throw std::runtime_error(ToString()); + } + } private: const std::string m_name; diff --git a/src/script/descriptor.h b/src/script/descriptor.h index af7ae229ca..29915c6c92 100644 --- a/src/script/descriptor.h +++ b/src/script/descriptor.h @@ -7,6 +7,7 @@ #include <script/script.h> #include <script/sign.h> +#include <script/signingprovider.h> #include <vector> diff --git a/src/script/interpreter.cpp b/src/script/interpreter.cpp index 95b25b4911..f8701b6d01 100644 --- a/src/script/interpreter.cpp +++ b/src/script/interpreter.cpp @@ -926,7 +926,7 @@ bool EvalScript(std::vector<std::vector<unsigned char> >& stack, const CScript& // Drop the signature in pre-segwit scripts but not segwit scripts if (sigversion == SigVersion::BASE) { - int found = FindAndDelete(scriptCode, CScript(vchSig)); + int found = FindAndDelete(scriptCode, CScript() << vchSig); if (found > 0 && (flags & SCRIPT_VERIFY_CONST_SCRIPTCODE)) return set_error(serror, SCRIPT_ERR_SIG_FINDANDDELETE); } @@ -992,7 +992,7 @@ bool EvalScript(std::vector<std::vector<unsigned char> >& stack, const CScript& { valtype& vchSig = stacktop(-isig-k); if (sigversion == SigVersion::BASE) { - int found = FindAndDelete(scriptCode, CScript(vchSig)); + int found = FindAndDelete(scriptCode, CScript() << vchSig); if (found > 0 && (flags & SCRIPT_VERIFY_CONST_SCRIPTCODE)) return set_error(serror, SCRIPT_ERR_SIG_FINDANDDELETE); } diff --git a/src/script/keyorigin.h b/src/script/keyorigin.h new file mode 100644 index 0000000000..610f233500 --- /dev/null +++ b/src/script/keyorigin.h @@ -0,0 +1,37 @@ +// Copyright (c) 2019 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_SCRIPT_KEYORIGIN_H +#define BITCOIN_SCRIPT_KEYORIGIN_H + +#include <serialize.h> +#include <streams.h> +#include <vector> + +struct KeyOriginInfo +{ + unsigned char fingerprint[4]; //!< First 32 bits of the Hash160 of the public key at the root of the path + std::vector<uint32_t> path; + + friend bool operator==(const KeyOriginInfo& a, const KeyOriginInfo& b) + { + return std::equal(std::begin(a.fingerprint), std::end(a.fingerprint), std::begin(b.fingerprint)) && a.path == b.path; + } + + ADD_SERIALIZE_METHODS; + template <typename Stream, typename Operation> + inline void SerializationOp(Stream& s, Operation ser_action) + { + READWRITE(fingerprint); + READWRITE(path); + } + + void clear() + { + memset(fingerprint, 0, 4); + path.clear(); + } +}; + +#endif // BITCOIN_SCRIPT_KEYORIGIN_H diff --git a/src/script/script.h b/src/script/script.h index 11e8661a5b..6355b8a704 100644 --- a/src/script/script.h +++ b/src/script/script.h @@ -437,7 +437,9 @@ public: explicit CScript(opcodetype b) { operator<<(b); } explicit CScript(const CScriptNum& b) { operator<<(b); } - explicit CScript(const std::vector<unsigned char>& b) { operator<<(b); } + // delete non-existent constructor to defend against future introduction + // e.g. via prevector + explicit CScript(const std::vector<unsigned char>& b) = delete; CScript& operator<<(int64_t b) { return push_int64(b); } diff --git a/src/script/sign.cpp b/src/script/sign.cpp index 5320dc0876..13481af9c5 100644 --- a/src/script/sign.cpp +++ b/src/script/sign.cpp @@ -8,6 +8,7 @@ #include <key.h> #include <policy/policy.h> #include <primitives/transaction.h> +#include <script/signingprovider.h> #include <script/standard.h> #include <uint256.h> @@ -423,22 +424,10 @@ public: } }; -template<typename M, typename K, typename V> -bool LookupHelper(const M& map, const K& key, V& value) -{ - auto it = map.find(key); - if (it != map.end()) { - value = it->second; - return true; - } - return false; -} - } const BaseSignatureCreator& DUMMY_SIGNATURE_CREATOR = DummySignatureCreator(32, 32); const BaseSignatureCreator& DUMMY_MAXIMUM_SIGNATURE_CREATOR = DummySignatureCreator(33, 32); -const SigningProvider& DUMMY_SIGNING_PROVIDER = SigningProvider(); bool IsSolvable(const SigningProvider& provider, const CScript& script) { @@ -459,53 +448,6 @@ bool IsSolvable(const SigningProvider& provider, const CScript& script) return false; } -bool HidingSigningProvider::GetCScript(const CScriptID& scriptid, CScript& script) const -{ - return m_provider->GetCScript(scriptid, script); -} - -bool HidingSigningProvider::GetPubKey(const CKeyID& keyid, CPubKey& pubkey) const -{ - return m_provider->GetPubKey(keyid, pubkey); -} - -bool HidingSigningProvider::GetKey(const CKeyID& keyid, CKey& key) const -{ - if (m_hide_secret) return false; - return m_provider->GetKey(keyid, key); -} - -bool HidingSigningProvider::GetKeyOrigin(const CKeyID& keyid, KeyOriginInfo& info) const -{ - if (m_hide_origin) return false; - return m_provider->GetKeyOrigin(keyid, info); -} - -bool FlatSigningProvider::GetCScript(const CScriptID& scriptid, CScript& script) const { return LookupHelper(scripts, scriptid, script); } -bool FlatSigningProvider::GetPubKey(const CKeyID& keyid, CPubKey& pubkey) const { return LookupHelper(pubkeys, keyid, pubkey); } -bool FlatSigningProvider::GetKeyOrigin(const CKeyID& keyid, KeyOriginInfo& info) const -{ - std::pair<CPubKey, KeyOriginInfo> out; - bool ret = LookupHelper(origins, keyid, out); - if (ret) info = std::move(out.second); - return ret; -} -bool FlatSigningProvider::GetKey(const CKeyID& keyid, CKey& key) const { return LookupHelper(keys, keyid, key); } - -FlatSigningProvider Merge(const FlatSigningProvider& a, const FlatSigningProvider& b) -{ - FlatSigningProvider ret; - ret.scripts = a.scripts; - ret.scripts.insert(b.scripts.begin(), b.scripts.end()); - ret.pubkeys = a.pubkeys; - ret.pubkeys.insert(b.pubkeys.begin(), b.pubkeys.end()); - ret.keys = a.keys; - ret.keys.insert(b.keys.begin(), b.keys.end()); - ret.origins = a.origins; - ret.origins.insert(b.origins.begin(), b.origins.end()); - return ret; -} - bool IsSegWitOutput(const SigningProvider& provider, const CScript& script) { std::vector<valtype> solutions; diff --git a/src/script/sign.h b/src/script/sign.h index e5c0329a61..0e751afd3b 100644 --- a/src/script/sign.h +++ b/src/script/sign.h @@ -10,6 +10,7 @@ #include <hash.h> #include <pubkey.h> #include <script/interpreter.h> +#include <script/keyorigin.h> #include <streams.h> class CKey; @@ -17,77 +18,10 @@ class CKeyID; class CScript; class CScriptID; class CTransaction; +class SigningProvider; struct CMutableTransaction; -struct KeyOriginInfo -{ - unsigned char fingerprint[4]; //!< First 32 bits of the Hash160 of the public key at the root of the path - std::vector<uint32_t> path; - - friend bool operator==(const KeyOriginInfo& a, const KeyOriginInfo& b) - { - return std::equal(std::begin(a.fingerprint), std::end(a.fingerprint), std::begin(b.fingerprint)) && a.path == b.path; - } - - ADD_SERIALIZE_METHODS; - template <typename Stream, typename Operation> - inline void SerializationOp(Stream& s, Operation ser_action) - { - READWRITE(fingerprint); - READWRITE(path); - } - - void clear() - { - memset(fingerprint, 0, 4); - path.clear(); - } -}; - -/** An interface to be implemented by keystores that support signing. */ -class SigningProvider -{ -public: - virtual ~SigningProvider() {} - virtual bool GetCScript(const CScriptID &scriptid, CScript& script) const { return false; } - virtual bool GetPubKey(const CKeyID &address, CPubKey& pubkey) const { return false; } - virtual bool GetKey(const CKeyID &address, CKey& key) const { return false; } - virtual bool GetKeyOrigin(const CKeyID& keyid, KeyOriginInfo& info) const { return false; } -}; - -extern const SigningProvider& DUMMY_SIGNING_PROVIDER; - -class HidingSigningProvider : public SigningProvider -{ -private: - const bool m_hide_secret; - const bool m_hide_origin; - const SigningProvider* m_provider; - -public: - HidingSigningProvider(const SigningProvider* provider, bool hide_secret, bool hide_origin) : m_hide_secret(hide_secret), m_hide_origin(hide_origin), m_provider(provider) {} - bool GetCScript(const CScriptID& scriptid, CScript& script) const override; - bool GetPubKey(const CKeyID& keyid, CPubKey& pubkey) const override; - bool GetKey(const CKeyID& keyid, CKey& key) const override; - bool GetKeyOrigin(const CKeyID& keyid, KeyOriginInfo& info) const override; -}; - -struct FlatSigningProvider final : public SigningProvider -{ - std::map<CScriptID, CScript> scripts; - std::map<CKeyID, CPubKey> pubkeys; - std::map<CKeyID, std::pair<CPubKey, KeyOriginInfo>> origins; - std::map<CKeyID, CKey> keys; - - bool GetCScript(const CScriptID& scriptid, CScript& script) const override; - bool GetPubKey(const CKeyID& keyid, CPubKey& pubkey) const override; - bool GetKeyOrigin(const CKeyID& keyid, KeyOriginInfo& info) const override; - bool GetKey(const CKeyID& keyid, CKey& key) const override; -}; - -FlatSigningProvider Merge(const FlatSigningProvider& a, const FlatSigningProvider& b); - /** Interface for signature creators. */ class BaseSignatureCreator { public: diff --git a/src/keystore.cpp b/src/script/signingprovider.cpp index f6d19416ce..01757e2f65 100644 --- a/src/keystore.cpp +++ b/src/script/signingprovider.cpp @@ -1,18 +1,78 @@ // Copyright (c) 2009-2010 Satoshi Nakamoto -// Copyright (c) 2009-2018 The Bitcoin Core developers +// Copyright (c) 2009-2019 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#include <keystore.h> +#include <script/keyorigin.h> +#include <script/signingprovider.h> +#include <script/standard.h> #include <util/system.h> -void CBasicKeyStore::ImplicitlyLearnRelatedKeyScripts(const CPubKey& pubkey) +const SigningProvider& DUMMY_SIGNING_PROVIDER = SigningProvider(); + +template<typename M, typename K, typename V> +bool LookupHelper(const M& map, const K& key, V& value) +{ + auto it = map.find(key); + if (it != map.end()) { + value = it->second; + return true; + } + return false; +} + +bool HidingSigningProvider::GetCScript(const CScriptID& scriptid, CScript& script) const +{ + return m_provider->GetCScript(scriptid, script); +} + +bool HidingSigningProvider::GetPubKey(const CKeyID& keyid, CPubKey& pubkey) const +{ + return m_provider->GetPubKey(keyid, pubkey); +} + +bool HidingSigningProvider::GetKey(const CKeyID& keyid, CKey& key) const +{ + if (m_hide_secret) return false; + return m_provider->GetKey(keyid, key); +} + +bool HidingSigningProvider::GetKeyOrigin(const CKeyID& keyid, KeyOriginInfo& info) const +{ + if (m_hide_origin) return false; + return m_provider->GetKeyOrigin(keyid, info); +} + +bool FlatSigningProvider::GetCScript(const CScriptID& scriptid, CScript& script) const { return LookupHelper(scripts, scriptid, script); } +bool FlatSigningProvider::GetPubKey(const CKeyID& keyid, CPubKey& pubkey) const { return LookupHelper(pubkeys, keyid, pubkey); } +bool FlatSigningProvider::GetKeyOrigin(const CKeyID& keyid, KeyOriginInfo& info) const +{ + std::pair<CPubKey, KeyOriginInfo> out; + bool ret = LookupHelper(origins, keyid, out); + if (ret) info = std::move(out.second); + return ret; +} +bool FlatSigningProvider::GetKey(const CKeyID& keyid, CKey& key) const { return LookupHelper(keys, keyid, key); } + +FlatSigningProvider Merge(const FlatSigningProvider& a, const FlatSigningProvider& b) +{ + FlatSigningProvider ret; + ret.scripts = a.scripts; + ret.scripts.insert(b.scripts.begin(), b.scripts.end()); + ret.pubkeys = a.pubkeys; + ret.pubkeys.insert(b.pubkeys.begin(), b.pubkeys.end()); + ret.keys = a.keys; + ret.keys.insert(b.keys.begin(), b.keys.end()); + ret.origins = a.origins; + ret.origins.insert(b.origins.begin(), b.origins.end()); + return ret; +} + +void FillableSigningProvider::ImplicitlyLearnRelatedKeyScripts(const CPubKey& pubkey) { AssertLockHeld(cs_KeyStore); CKeyID key_id = pubkey.GetID(); - // We must actually know about this key already. - assert(HaveKey(key_id) || mapWatchKeys.count(key_id)); // This adds the redeemscripts necessary to detect P2WPKH and P2SH-P2WPKH // outputs. Technically P2WPKH outputs don't have a redeemscript to be // spent. However, our current IsMine logic requires the corresponding @@ -32,23 +92,17 @@ void CBasicKeyStore::ImplicitlyLearnRelatedKeyScripts(const CPubKey& pubkey) } } -bool CBasicKeyStore::GetPubKey(const CKeyID &address, CPubKey &vchPubKeyOut) const +bool FillableSigningProvider::GetPubKey(const CKeyID &address, CPubKey &vchPubKeyOut) const { CKey key; if (!GetKey(address, key)) { - LOCK(cs_KeyStore); - WatchKeyMap::const_iterator it = mapWatchKeys.find(address); - if (it != mapWatchKeys.end()) { - vchPubKeyOut = it->second; - return true; - } return false; } vchPubKeyOut = key.GetPubKey(); return true; } -bool CBasicKeyStore::AddKeyPubKey(const CKey& key, const CPubKey &pubkey) +bool FillableSigningProvider::AddKeyPubKey(const CKey& key, const CPubKey &pubkey) { LOCK(cs_KeyStore); mapKeys[pubkey.GetID()] = key; @@ -56,13 +110,13 @@ bool CBasicKeyStore::AddKeyPubKey(const CKey& key, const CPubKey &pubkey) return true; } -bool CBasicKeyStore::HaveKey(const CKeyID &address) const +bool FillableSigningProvider::HaveKey(const CKeyID &address) const { LOCK(cs_KeyStore); return mapKeys.count(address) > 0; } -std::set<CKeyID> CBasicKeyStore::GetKeys() const +std::set<CKeyID> FillableSigningProvider::GetKeys() const { LOCK(cs_KeyStore); std::set<CKeyID> set_address; @@ -72,7 +126,7 @@ std::set<CKeyID> CBasicKeyStore::GetKeys() const return set_address; } -bool CBasicKeyStore::GetKey(const CKeyID &address, CKey &keyOut) const +bool FillableSigningProvider::GetKey(const CKeyID &address, CKey &keyOut) const { LOCK(cs_KeyStore); KeyMap::const_iterator mi = mapKeys.find(address); @@ -83,23 +137,23 @@ bool CBasicKeyStore::GetKey(const CKeyID &address, CKey &keyOut) const return false; } -bool CBasicKeyStore::AddCScript(const CScript& redeemScript) +bool FillableSigningProvider::AddCScript(const CScript& redeemScript) { if (redeemScript.size() > MAX_SCRIPT_ELEMENT_SIZE) - return error("CBasicKeyStore::AddCScript(): redeemScripts > %i bytes are invalid", MAX_SCRIPT_ELEMENT_SIZE); + return error("FillableSigningProvider::AddCScript(): redeemScripts > %i bytes are invalid", MAX_SCRIPT_ELEMENT_SIZE); LOCK(cs_KeyStore); mapScripts[CScriptID(redeemScript)] = redeemScript; return true; } -bool CBasicKeyStore::HaveCScript(const CScriptID& hash) const +bool FillableSigningProvider::HaveCScript(const CScriptID& hash) const { LOCK(cs_KeyStore); return mapScripts.count(hash) > 0; } -std::set<CScriptID> CBasicKeyStore::GetCScripts() const +std::set<CScriptID> FillableSigningProvider::GetCScripts() const { LOCK(cs_KeyStore); std::set<CScriptID> set_script; @@ -109,7 +163,7 @@ std::set<CScriptID> CBasicKeyStore::GetCScripts() const return set_script; } -bool CBasicKeyStore::GetCScript(const CScriptID &hash, CScript& redeemScriptOut) const +bool FillableSigningProvider::GetCScript(const CScriptID &hash, CScript& redeemScriptOut) const { LOCK(cs_KeyStore); ScriptMap::const_iterator mi = mapScripts.find(hash); @@ -121,60 +175,7 @@ bool CBasicKeyStore::GetCScript(const CScriptID &hash, CScript& redeemScriptOut) return false; } -static bool ExtractPubKey(const CScript &dest, CPubKey& pubKeyOut) -{ - //TODO: Use Solver to extract this? - CScript::const_iterator pc = dest.begin(); - opcodetype opcode; - std::vector<unsigned char> vch; - if (!dest.GetOp(pc, opcode, vch) || !CPubKey::ValidSize(vch)) - return false; - pubKeyOut = CPubKey(vch); - if (!pubKeyOut.IsFullyValid()) - return false; - if (!dest.GetOp(pc, opcode, vch) || opcode != OP_CHECKSIG || dest.GetOp(pc, opcode, vch)) - return false; - return true; -} - -bool CBasicKeyStore::AddWatchOnly(const CScript &dest) -{ - LOCK(cs_KeyStore); - setWatchOnly.insert(dest); - CPubKey pubKey; - if (ExtractPubKey(dest, pubKey)) { - mapWatchKeys[pubKey.GetID()] = pubKey; - ImplicitlyLearnRelatedKeyScripts(pubKey); - } - return true; -} - -bool CBasicKeyStore::RemoveWatchOnly(const CScript &dest) -{ - LOCK(cs_KeyStore); - setWatchOnly.erase(dest); - CPubKey pubKey; - if (ExtractPubKey(dest, pubKey)) { - mapWatchKeys.erase(pubKey.GetID()); - } - // Related CScripts are not removed; having superfluous scripts around is - // harmless (see comment in ImplicitlyLearnRelatedKeyScripts). - return true; -} - -bool CBasicKeyStore::HaveWatchOnly(const CScript &dest) const -{ - LOCK(cs_KeyStore); - return setWatchOnly.count(dest) > 0; -} - -bool CBasicKeyStore::HaveWatchOnly() const -{ - LOCK(cs_KeyStore); - return (!setWatchOnly.empty()); -} - -CKeyID GetKeyForDestination(const CKeyStore& store, const CTxDestination& dest) +CKeyID GetKeyForDestination(const SigningProvider& store, const CTxDestination& dest) { // Only supports destinations which map to single public keys, i.e. P2PKH, // P2WPKH, and P2SH-P2WPKH. @@ -196,10 +197,3 @@ CKeyID GetKeyForDestination(const CKeyStore& store, const CTxDestination& dest) } return CKeyID(); } - -bool HaveKey(const CKeyStore& store, const CKey& key) -{ - CKey key2; - key2.Set(key.begin(), key.end(), !key.IsCompressed()); - return store.HaveKey(key.GetPubKey().GetID()) || store.HaveKey(key2.GetPubKey().GetID()); -} diff --git a/src/script/signingprovider.h b/src/script/signingprovider.h new file mode 100644 index 0000000000..4eec2311d4 --- /dev/null +++ b/src/script/signingprovider.h @@ -0,0 +1,92 @@ +// Copyright (c) 2009-2010 Satoshi Nakamoto +// Copyright (c) 2009-2019 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_SCRIPT_SIGNINGPROVIDER_H +#define BITCOIN_SCRIPT_SIGNINGPROVIDER_H + +#include <key.h> +#include <pubkey.h> +#include <script/script.h> +#include <script/standard.h> +#include <sync.h> + +struct KeyOriginInfo; + +/** An interface to be implemented by keystores that support signing. */ +class SigningProvider +{ +public: + virtual ~SigningProvider() {} + virtual bool GetCScript(const CScriptID &scriptid, CScript& script) const { return false; } + virtual bool HaveCScript(const CScriptID &scriptid) const { return false; } + virtual bool GetPubKey(const CKeyID &address, CPubKey& pubkey) const { return false; } + virtual bool GetKey(const CKeyID &address, CKey& key) const { return false; } + virtual bool HaveKey(const CKeyID &address) const { return false; } + virtual bool GetKeyOrigin(const CKeyID& keyid, KeyOriginInfo& info) const { return false; } +}; + +extern const SigningProvider& DUMMY_SIGNING_PROVIDER; + +class HidingSigningProvider : public SigningProvider +{ +private: + const bool m_hide_secret; + const bool m_hide_origin; + const SigningProvider* m_provider; + +public: + HidingSigningProvider(const SigningProvider* provider, bool hide_secret, bool hide_origin) : m_hide_secret(hide_secret), m_hide_origin(hide_origin), m_provider(provider) {} + bool GetCScript(const CScriptID& scriptid, CScript& script) const override; + bool GetPubKey(const CKeyID& keyid, CPubKey& pubkey) const override; + bool GetKey(const CKeyID& keyid, CKey& key) const override; + bool GetKeyOrigin(const CKeyID& keyid, KeyOriginInfo& info) const override; +}; + +struct FlatSigningProvider final : public SigningProvider +{ + std::map<CScriptID, CScript> scripts; + std::map<CKeyID, CPubKey> pubkeys; + std::map<CKeyID, std::pair<CPubKey, KeyOriginInfo>> origins; + std::map<CKeyID, CKey> keys; + + bool GetCScript(const CScriptID& scriptid, CScript& script) const override; + bool GetPubKey(const CKeyID& keyid, CPubKey& pubkey) const override; + bool GetKeyOrigin(const CKeyID& keyid, KeyOriginInfo& info) const override; + bool GetKey(const CKeyID& keyid, CKey& key) const override; +}; + +FlatSigningProvider Merge(const FlatSigningProvider& a, const FlatSigningProvider& b); + +/** Fillable signing provider that keeps keys in an address->secret map */ +class FillableSigningProvider : public SigningProvider +{ +protected: + mutable CCriticalSection cs_KeyStore; + + using KeyMap = std::map<CKeyID, CKey>; + using ScriptMap = std::map<CScriptID, CScript>; + + KeyMap mapKeys GUARDED_BY(cs_KeyStore); + ScriptMap mapScripts GUARDED_BY(cs_KeyStore); + + void ImplicitlyLearnRelatedKeyScripts(const CPubKey& pubkey) EXCLUSIVE_LOCKS_REQUIRED(cs_KeyStore); + +public: + virtual bool AddKeyPubKey(const CKey& key, const CPubKey &pubkey); + virtual bool AddKey(const CKey &key) { return AddKeyPubKey(key, key.GetPubKey()); } + virtual bool GetPubKey(const CKeyID &address, CPubKey& vchPubKeyOut) const override; + virtual bool HaveKey(const CKeyID &address) const override; + virtual std::set<CKeyID> GetKeys() const; + virtual bool GetKey(const CKeyID &address, CKey &keyOut) const override; + virtual bool AddCScript(const CScript& redeemScript); + virtual bool HaveCScript(const CScriptID &hash) const override; + virtual std::set<CScriptID> GetCScripts() const; + virtual bool GetCScript(const CScriptID &hash, CScript& redeemScriptOut) const override; +}; + +/** Return the CKeyID of the key involved in a script (if there is a unique one). */ +CKeyID GetKeyForDestination(const SigningProvider& store, const CTxDestination& dest); + +#endif // BITCOIN_SCRIPT_SIGNINGPROVIDER_H diff --git a/src/script/standard.cpp b/src/script/standard.cpp index b7d6cd925c..fc6898f444 100644 --- a/src/script/standard.cpp +++ b/src/script/standard.cpp @@ -9,7 +9,6 @@ #include <pubkey.h> #include <script/script.h> - typedef std::vector<unsigned char> valtype; bool fAcceptDatacarrier = DEFAULT_ACCEPT_DATACARRIER; diff --git a/src/test/addrman_tests.cpp b/src/test/addrman_tests.cpp index eeb54b4cf0..da0abd495a 100644 --- a/src/test/addrman_tests.cpp +++ b/src/test/addrman_tests.cpp @@ -533,9 +533,6 @@ BOOST_AUTO_TEST_CASE(addrman_selecttriedcollision) { CAddrManTest addrman; - // Set addrman addr placement to be deterministic. - addrman.MakeDeterministic(); - BOOST_CHECK(addrman.size() == 0); // Empty addrman should return blank addrman info. @@ -568,9 +565,6 @@ BOOST_AUTO_TEST_CASE(addrman_noevict) { CAddrManTest addrman; - // Set addrman addr placement to be deterministic. - addrman.MakeDeterministic(); - // Add twenty two addresses. CNetAddr source = ResolveIP("252.2.2.2"); for (unsigned int i = 1; i < 23; i++) { @@ -627,9 +621,6 @@ BOOST_AUTO_TEST_CASE(addrman_evictionworks) { CAddrManTest addrman; - // Set addrman addr placement to be deterministic. - addrman.MakeDeterministic(); - BOOST_CHECK(addrman.size() == 0); // Empty addrman should return blank addrman info. diff --git a/src/test/crypto_tests.cpp b/src/test/crypto_tests.cpp index 4e2acca4c3..4ac12bf969 100644 --- a/src/test/crypto_tests.cpp +++ b/src/test/crypto_tests.cpp @@ -4,6 +4,7 @@ #include <crypto/aes.h> #include <crypto/chacha20.h> +#include <crypto/chacha_poly_aead.h> #include <crypto/poly1305.h> #include <crypto/hkdf_sha256_32.h> #include <crypto/hmac_sha256.h> @@ -585,6 +586,131 @@ BOOST_AUTO_TEST_CASE(hkdf_hmac_sha256_l32_tests) "8da4e775a563c18f715f802a063c5a31b8a11f5c5ee1879ec3454e5f3c738d2d"); } +static void TestChaCha20Poly1305AEAD(bool must_succeed, unsigned int expected_aad_length, const std::string& hex_m, const std::string& hex_k1, const std::string& hex_k2, const std::string& hex_aad_keystream, const std::string& hex_encrypted_message, const std::string& hex_encrypted_message_seq_999) +{ + // we need two sequence numbers, one for the payload cipher instance... + uint32_t seqnr_payload = 0; + // ... and one for the AAD (length) cipher instance + uint32_t seqnr_aad = 0; + // we need to keep track of the position in the AAD cipher instance + // keystream since we use the same 64byte output 21 times + // (21 times 3 bytes length < 64) + int aad_pos = 0; + + std::vector<unsigned char> aead_K_1 = ParseHex(hex_k1); + std::vector<unsigned char> aead_K_2 = ParseHex(hex_k2); + std::vector<unsigned char> plaintext_buf = ParseHex(hex_m); + std::vector<unsigned char> expected_aad_keystream = ParseHex(hex_aad_keystream); + std::vector<unsigned char> expected_ciphertext_and_mac = ParseHex(hex_encrypted_message); + std::vector<unsigned char> expected_ciphertext_and_mac_sequence999 = ParseHex(hex_encrypted_message_seq_999); + + std::vector<unsigned char> ciphertext_buf(plaintext_buf.size() + POLY1305_TAGLEN, 0); + std::vector<unsigned char> plaintext_buf_new(plaintext_buf.size(), 0); + std::vector<unsigned char> cmp_ctx_buffer(64); + uint32_t out_len = 0; + + // create the AEAD instance + ChaCha20Poly1305AEAD aead(aead_K_1.data(), aead_K_1.size(), aead_K_2.data(), aead_K_2.size()); + + // create a chacha20 instance to compare against + ChaCha20 cmp_ctx(aead_K_2.data(), 32); + + // encipher + bool res = aead.Crypt(seqnr_payload, seqnr_aad, aad_pos, ciphertext_buf.data(), ciphertext_buf.size(), plaintext_buf.data(), plaintext_buf.size(), true); + // make sure the operation succeeded if expected to succeed + BOOST_CHECK_EQUAL(res, must_succeed); + if (!res) return; + + // verify ciphertext & mac against the test vector + BOOST_CHECK_EQUAL(expected_ciphertext_and_mac.size(), ciphertext_buf.size()); + BOOST_CHECK(memcmp(ciphertext_buf.data(), expected_ciphertext_and_mac.data(), ciphertext_buf.size()) == 0); + + // manually construct the AAD keystream + cmp_ctx.SetIV(seqnr_aad); + cmp_ctx.Seek(0); + cmp_ctx.Keystream(cmp_ctx_buffer.data(), 64); + BOOST_CHECK(memcmp(expected_aad_keystream.data(), cmp_ctx_buffer.data(), expected_aad_keystream.size()) == 0); + // crypt the 3 length bytes and compare the length + uint32_t len_cmp = 0; + len_cmp = (ciphertext_buf[0] ^ cmp_ctx_buffer[aad_pos + 0]) | + (ciphertext_buf[1] ^ cmp_ctx_buffer[aad_pos + 1]) << 8 | + (ciphertext_buf[2] ^ cmp_ctx_buffer[aad_pos + 2]) << 16; + BOOST_CHECK_EQUAL(len_cmp, expected_aad_length); + + // encrypt / decrypt 1000 packets + for (size_t i = 0; i < 1000; ++i) { + res = aead.Crypt(seqnr_payload, seqnr_aad, aad_pos, ciphertext_buf.data(), ciphertext_buf.size(), plaintext_buf.data(), plaintext_buf.size(), true); + BOOST_CHECK(res); + BOOST_CHECK(aead.GetLength(&out_len, seqnr_aad, aad_pos, ciphertext_buf.data())); + BOOST_CHECK_EQUAL(out_len, expected_aad_length); + res = aead.Crypt(seqnr_payload, seqnr_aad, aad_pos, plaintext_buf_new.data(), plaintext_buf_new.size(), ciphertext_buf.data(), ciphertext_buf.size(), false); + BOOST_CHECK(res); + + // make sure we repetitive get the same plaintext + BOOST_CHECK(memcmp(plaintext_buf.data(), plaintext_buf_new.data(), plaintext_buf.size()) == 0); + + // compare sequence number 999 against the test vector + if (seqnr_payload == 999) { + BOOST_CHECK(memcmp(ciphertext_buf.data(), expected_ciphertext_and_mac_sequence999.data(), expected_ciphertext_and_mac_sequence999.size()) == 0); + } + // set nonce and block counter, output the keystream + cmp_ctx.SetIV(seqnr_aad); + cmp_ctx.Seek(0); + cmp_ctx.Keystream(cmp_ctx_buffer.data(), 64); + + // crypt the 3 length bytes and compare the length + len_cmp = 0; + len_cmp = (ciphertext_buf[0] ^ cmp_ctx_buffer[aad_pos + 0]) | + (ciphertext_buf[1] ^ cmp_ctx_buffer[aad_pos + 1]) << 8 | + (ciphertext_buf[2] ^ cmp_ctx_buffer[aad_pos + 2]) << 16; + BOOST_CHECK_EQUAL(len_cmp, expected_aad_length); + + // increment the sequence number(s) + // always increment the payload sequence number + // increment the AAD keystream position by its size (3) + // increment the AAD sequence number if we would hit the 64 byte limit + seqnr_payload++; + aad_pos += CHACHA20_POLY1305_AEAD_AAD_LEN; + if (aad_pos + CHACHA20_POLY1305_AEAD_AAD_LEN > CHACHA20_ROUND_OUTPUT) { + aad_pos = 0; + seqnr_aad++; + } + } +} + +BOOST_AUTO_TEST_CASE(chacha20_poly1305_aead_testvector) +{ + /* test chacha20poly1305@bitcoin AEAD */ + + // must fail with no message + TestChaCha20Poly1305AEAD(false, 0, + "", + "0000000000000000000000000000000000000000000000000000000000000000", + "0000000000000000000000000000000000000000000000000000000000000000", "", "", ""); + + TestChaCha20Poly1305AEAD(true, 0, + /* m */ "0000000000000000000000000000000000000000000000000000000000000000", + /* k1 (payload) */ "0000000000000000000000000000000000000000000000000000000000000000", + /* k2 (AAD) */ "0000000000000000000000000000000000000000000000000000000000000000", + /* AAD keystream */ "76b8e0ada0f13d90405d6ae55386bd28bdd219b8a08ded1aa836efcc8b770dc7da41597c5157488d7724e03fb8d84a376a43b8f41518a11cc387b669b2ee6586", + /* encrypted message & MAC */ "76b8e09f07e7be5551387a98ba977c732d080dcb0f29a048e3656912c6533e32d2fc11829c1b6c1df1f551cd6131ff08", + /* encrypted message & MAC at sequence 999 */ "b0a03d5bd2855d60699e7d3a3133fa47be740fe4e4c1f967555e2d9271f31c3aaa7aa16ec62c5e24f040c08bb20c3598"); + TestChaCha20Poly1305AEAD(true, 1, + "0100000000000000000000000000000000000000000000000000000000000000", + "0000000000000000000000000000000000000000000000000000000000000000", + "0000000000000000000000000000000000000000000000000000000000000000", + "76b8e0ada0f13d90405d6ae55386bd28bdd219b8a08ded1aa836efcc8b770dc7da41597c5157488d7724e03fb8d84a376a43b8f41518a11cc387b669b2ee6586", + "77b8e09f07e7be5551387a98ba977c732d080dcb0f29a048e3656912c6533e32baf0c85b6dff8602b06cf52a6aefc62e", + "b1a03d5bd2855d60699e7d3a3133fa47be740fe4e4c1f967555e2d9271f31c3a8bd94d54b5ecabbc41ffbb0c90924080"); + TestChaCha20Poly1305AEAD(true, 255, + "ff0000f195e66982105ffb640bb7757f579da31602fc93ec01ac56f85ac3c134a4547b733b46413042c9440049176905d3be59ea1c53f15916155c2be8241a38008b9a26bc35941e2444177c8ade6689de95264986d95889fb60e84629c9bd9a5acb1cc118be563eb9b3a4a472f82e09a7e778492b562ef7130e88dfe031c79db9d4f7c7a899151b9a475032b63fc385245fe054e3dd5a97a5f576fe064025d3ce042c566ab2c507b138db853e3d6959660996546cc9c4a6eafdc777c040d70eaf46f76dad3979e5c5360c3317166a1c894c94a371876a94df7628fe4eaaf2ccb27d5aaae0ad7ad0f9d4b6ad3b54098746d4524d38407a6deb3ab78fab78c9", + "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", + "ff0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", + "c640c1711e3ee904ac35c57ab9791c8a1c408603a90b77a83b54f6c844cb4b06d94e7fc6c800e165acd66147e80ec45a567f6ce66d05ec0cae679dceeb890017", + "3940c1e92da4582ff6f92a776aeb14d014d384eeb30f660dacf70a14a23fd31e91212701334e2ce1acf5199dc84f4d61ddbe6571bca5af874b4c9226c26e650995d157644e1848b96ed6c2102d5489a050e71d29a5a66ece11de5fb5c9558d54da28fe45b0bc4db4e5b88030bfc4a352b4b7068eccf656bae7ad6a35615315fc7c49d4200388d5eca67c2e822e069336c69b40db67e0f3c81209c50f3216a4b89fb3ae1b984b7851a2ec6f68ab12b101ab120e1ea7313bb93b5a0f71185c7fea017ddb92769861c29dba4fbc432280d5dff21b36d1c4c790128b22699950bb18bf74c448cdfe547d8ed4f657d8005fdc0cd7a050c2d46050a44c4376355858981fbe8b184288276e7a93eabc899c4a", + "f039c6689eaeef0456685200feaab9d54bbd9acde4410a3b6f4321296f4a8ca2604b49727d8892c57e005d799b2a38e85e809f20146e08eec75169691c8d4f54a0d51a1e1c7b381e0474eb02f994be9415ef3ffcbd2343f0601e1f3b172a1d494f838824e4df570f8e3b0c04e27966e36c82abd352d07054ef7bd36b84c63f9369afe7ed79b94f953873006b920c3fa251a771de1b63da927058ade119aa898b8c97e42a606b2f6df1e2d957c22f7593c1e2002f4252f4c9ae4bf773499e5cfcfe14dfc1ede26508953f88553bf4a76a802f6a0068d59295b01503fd9a600067624203e880fdf53933b96e1f4d9eb3f4e363dd8165a278ff667a41ee42b9892b077cefff92b93441f7be74cf10e6cd"); +} + BOOST_AUTO_TEST_CASE(countbits_tests) { FastRandomContext ctx; diff --git a/src/test/denialofservice_tests.cpp b/src/test/denialofservice_tests.cpp index 93883d1d98..a50d6854f8 100644 --- a/src/test/denialofservice_tests.cpp +++ b/src/test/denialofservice_tests.cpp @@ -6,10 +6,11 @@ #include <banman.h> #include <chainparams.h> -#include <keystore.h> #include <net.h> #include <net_processing.h> #include <script/sign.h> +#include <script/signingprovider.h> +#include <script/standard.h> #include <serialize.h> #include <util/memory.h> #include <util/system.h> @@ -369,7 +370,7 @@ BOOST_AUTO_TEST_CASE(DoS_mapOrphans) { CKey key; key.MakeNewKey(true); - CBasicKeyStore keystore; + FillableSigningProvider keystore; BOOST_CHECK(keystore.AddKey(key)); // 50 orphan transactions: diff --git a/src/test/multisig_tests.cpp b/src/test/multisig_tests.cpp index 11e79937be..7c60abb93f 100644 --- a/src/test/multisig_tests.cpp +++ b/src/test/multisig_tests.cpp @@ -3,12 +3,12 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include <key.h> -#include <keystore.h> #include <policy/policy.h> #include <script/script.h> #include <script/script_error.h> #include <script/interpreter.h> #include <script/sign.h> +#include <script/signingprovider.h> #include <tinyformat.h> #include <uint256.h> #include <test/setup_common.h> @@ -174,7 +174,7 @@ BOOST_AUTO_TEST_CASE(multisig_IsStandard) BOOST_AUTO_TEST_CASE(multisig_Sign) { // Test SignSignature() (and therefore the version of Solver() that signs transactions) - CBasicKeyStore keystore; + FillableSigningProvider keystore; CKey key[4]; for (int i = 0; i < 4; i++) { diff --git a/src/test/script_p2sh_tests.cpp b/src/test/script_p2sh_tests.cpp index 735b67c06e..f451d80984 100644 --- a/src/test/script_p2sh_tests.cpp +++ b/src/test/script_p2sh_tests.cpp @@ -4,13 +4,13 @@ #include <consensus/tx_verify.h> #include <key.h> -#include <keystore.h> #include <validation.h> #include <policy/policy.h> #include <script/script.h> #include <script/script_error.h> #include <policy/settings.h> #include <script/sign.h> +#include <script/signingprovider.h> #include <test/setup_common.h> #include <vector> @@ -55,7 +55,7 @@ BOOST_AUTO_TEST_CASE(sign) // scriptPubKey: HASH160 <hash> EQUAL // Test SignSignature() (and therefore the version of Solver() that signs transactions) - CBasicKeyStore keystore; + FillableSigningProvider keystore; CKey key[4]; for (int i = 0; i < 4; i++) { @@ -151,7 +151,7 @@ BOOST_AUTO_TEST_CASE(set) { LOCK(cs_main); // Test the CScript::Set* methods - CBasicKeyStore keystore; + FillableSigningProvider keystore; CKey key[4]; std::vector<CPubKey> keys; for (int i = 0; i < 4; i++) @@ -265,7 +265,7 @@ BOOST_AUTO_TEST_CASE(AreInputsStandard) LOCK(cs_main); CCoinsView coinsDummy; CCoinsViewCache coins(&coinsDummy); - CBasicKeyStore keystore; + FillableSigningProvider keystore; CKey key[6]; std::vector<CPubKey> keys; for (int i = 0; i < 6; i++) diff --git a/src/test/script_standard_tests.cpp b/src/test/script_standard_tests.cpp index 046b220e3f..412a57dd9d 100644 --- a/src/test/script_standard_tests.cpp +++ b/src/test/script_standard_tests.cpp @@ -3,8 +3,8 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include <key.h> -#include <keystore.h> #include <script/script.h> +#include <script/signingprovider.h> #include <script/standard.h> #include <test/setup_common.h> diff --git a/src/test/script_tests.cpp b/src/test/script_tests.cpp index ae903df0ad..84a70fe78b 100644 --- a/src/test/script_tests.cpp +++ b/src/test/script_tests.cpp @@ -6,10 +6,10 @@ #include <core_io.h> #include <key.h> -#include <keystore.h> #include <script/script.h> #include <script/script_error.h> #include <script/sign.h> +#include <script/signingprovider.h> #include <util/system.h> #include <util/strencodings.h> #include <test/setup_common.h> @@ -1199,7 +1199,7 @@ SignatureData CombineSignatures(const CTxOut& txout, const CMutableTransaction& BOOST_AUTO_TEST_CASE(script_combineSigs) { // Test the ProduceSignature's ability to combine signatures function - CBasicKeyStore keystore; + FillableSigningProvider keystore; std::vector<CKey> keys; std::vector<CPubKey> pubkeys; for (int i = 0; i < 3; i++) diff --git a/src/test/transaction_tests.cpp b/src/test/transaction_tests.cpp index f77b77a972..34192c6b6a 100644 --- a/src/test/transaction_tests.cpp +++ b/src/test/transaction_tests.cpp @@ -12,12 +12,12 @@ #include <consensus/validation.h> #include <core_io.h> #include <key.h> -#include <keystore.h> #include <validation.h> #include <policy/policy.h> #include <policy/settings.h> #include <script/script.h> #include <script/sign.h> +#include <script/signingprovider.h> #include <script/script_error.h> #include <script/standard.h> #include <streams.h> @@ -289,7 +289,7 @@ BOOST_AUTO_TEST_CASE(basic_transaction_tests) // paid to a TX_PUBKEYHASH. // static std::vector<CMutableTransaction> -SetupDummyInputs(CBasicKeyStore& keystoreRet, CCoinsViewCache& coinsRet) +SetupDummyInputs(FillableSigningProvider& keystoreRet, CCoinsViewCache& coinsRet) { std::vector<CMutableTransaction> dummyTransactions; dummyTransactions.resize(2); @@ -322,7 +322,7 @@ SetupDummyInputs(CBasicKeyStore& keystoreRet, CCoinsViewCache& coinsRet) BOOST_AUTO_TEST_CASE(test_Get) { - CBasicKeyStore keystore; + FillableSigningProvider keystore; CCoinsView coinsDummy; CCoinsViewCache coins(&coinsDummy); std::vector<CMutableTransaction> dummyTransactions = SetupDummyInputs(keystore, coins); @@ -346,7 +346,7 @@ BOOST_AUTO_TEST_CASE(test_Get) BOOST_CHECK_EQUAL(coins.GetValueIn(CTransaction(t1)), (50+21+22)*CENT); } -static void CreateCreditAndSpend(const CKeyStore& keystore, const CScript& outscript, CTransactionRef& output, CMutableTransaction& input, bool success = true) +static void CreateCreditAndSpend(const FillableSigningProvider& keystore, const CScript& outscript, CTransactionRef& output, CMutableTransaction& input, bool success = true) { CMutableTransaction outputm; outputm.nVersion = 1; @@ -423,7 +423,7 @@ BOOST_AUTO_TEST_CASE(test_big_witness_transaction) CKey key; key.MakeNewKey(true); // Need to use compressed keys in segwit or the signing will fail - CBasicKeyStore keystore; + FillableSigningProvider keystore; BOOST_CHECK(keystore.AddKeyPubKey(key, key.GetPubKey())); CKeyID hash = key.GetPubKey().GetID(); CScript scriptPubKey = CScript() << OP_0 << std::vector<unsigned char>(hash.begin(), hash.end()); @@ -507,7 +507,7 @@ SignatureData CombineSignatures(const CMutableTransaction& input1, const CMutabl BOOST_AUTO_TEST_CASE(test_witness) { - CBasicKeyStore keystore, keystore2; + FillableSigningProvider keystore, keystore2; CKey key1, key2, key3, key1L, key2L; CPubKey pubkey1, pubkey2, pubkey3, pubkey1L, pubkey2L; key1.MakeNewKey(true); @@ -682,7 +682,7 @@ BOOST_AUTO_TEST_CASE(test_witness) BOOST_AUTO_TEST_CASE(test_IsStandard) { LOCK(cs_main); - CBasicKeyStore keystore; + FillableSigningProvider keystore; CCoinsView coinsDummy; CCoinsViewCache coins(&coinsDummy); std::vector<CMutableTransaction> dummyTransactions = SetupDummyInputs(keystore, coins); diff --git a/src/test/txvalidationcache_tests.cpp b/src/test/txvalidationcache_tests.cpp index 45c97fa2aa..f99a3748c9 100644 --- a/src/test/txvalidationcache_tests.cpp +++ b/src/test/txvalidationcache_tests.cpp @@ -8,8 +8,8 @@ #include <txmempool.h> #include <script/standard.h> #include <script/sign.h> +#include <script/signingprovider.h> #include <test/setup_common.h> -#include <keystore.h> #include <boost/test/unit_test.hpp> @@ -161,7 +161,7 @@ BOOST_FIXTURE_TEST_CASE(checkinputs_test, TestChain100Setup) CScript p2pkh_scriptPubKey = GetScriptForDestination(PKHash(coinbaseKey.GetPubKey())); CScript p2wpkh_scriptPubKey = GetScriptForWitness(p2pkh_scriptPubKey); - CBasicKeyStore keystore; + FillableSigningProvider keystore; BOOST_CHECK(keystore.AddKey(coinbaseKey)); BOOST_CHECK(keystore.AddCScript(p2pk_scriptPubKey)); diff --git a/src/test/util.cpp b/src/test/util.cpp index a2ea648324..b7bb6deeaa 100644 --- a/src/test/util.cpp +++ b/src/test/util.cpp @@ -23,14 +23,9 @@ const std::string ADDRESS_BCRT1_UNSPENDABLE = "bcrt1qqqqqqqqqqqqqqqqqqqqqqqqqqqq std::string getnewaddress(CWallet& w) { constexpr auto output_type = OutputType::BECH32; - - CPubKey new_key; - if (!w.GetKeyFromPool(new_key)) assert(false); - - w.LearnRelatedScripts(new_key, output_type); - const auto dest = GetDestinationForKey(new_key, output_type); - - w.SetAddressBook(dest, /* label */ "", "receive"); + CTxDestination dest; + std::string error; + if (!w.GetNewDestination(output_type, "", dest, error)) assert(false); return EncodeDestination(dest); } diff --git a/src/torcontrol.cpp b/src/torcontrol.cpp index a1c730ba08..3f40785c21 100644 --- a/src/torcontrol.cpp +++ b/src/torcontrol.cpp @@ -759,7 +759,9 @@ void InterruptTorControl() { if (gBase) { LogPrintf("tor: Thread interrupt\n"); - event_base_loopbreak(gBase); + event_base_once(gBase, -1, EV_TIMEOUT, [](evutil_socket_t, short, void*) { + event_base_loopbreak(gBase); + }, nullptr, nullptr); } } diff --git a/src/txdb.cpp b/src/txdb.cpp index 73fe2a8ee4..90b92969b9 100644 --- a/src/txdb.cpp +++ b/src/txdb.cpp @@ -250,7 +250,7 @@ bool CBlockTreeDB::LoadBlockIndexGuts(const Consensus::Params& consensusParams, pcursor->Seek(std::make_pair(DB_BLOCK_INDEX, uint256())); - // Load mapBlockIndex + // Load m_block_index while (pcursor->Valid()) { boost::this_thread::interruption_point(); if (ShutdownRequested()) return false; diff --git a/src/validation.cpp b/src/validation.cpp index 262b6856a4..f585bc7d2d 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -77,7 +77,11 @@ bool CBlockIndexWorkComparator::operator()(const CBlockIndex *pa, const CBlockIn return false; } -static CChainState g_chainstate; +namespace { +BlockManager g_blockman; +} // anon namespace + +static CChainState g_chainstate(g_blockman); CChainState& ChainstateActive() { return g_chainstate; } @@ -95,7 +99,6 @@ CChain& ChainActive() { return g_chainstate.m_chain; } */ RecursiveMutex cs_main; -BlockMap& mapBlockIndex = ::ChainstateActive().mapBlockIndex; CBlockIndex *pindexBestHeader = nullptr; Mutex g_best_block_mutex; std::condition_variable g_best_block_cv; @@ -125,12 +128,7 @@ CScript COINBASE_FLAGS; // Internal stuff namespace { - CBlockIndex *&pindexBestInvalid = ::ChainstateActive().pindexBestInvalid; - - /** All pairs A->B, where A (or one of its ancestors) misses transactions, but B has transactions. - * Pruned nodes may have entries where B is missing data. - */ - std::multimap<CBlockIndex*, CBlockIndex*>& mapBlocksUnlinked = ::ChainstateActive().mapBlocksUnlinked; + CBlockIndex* pindexBestInvalid = nullptr; CCriticalSection cs_LastBlockFile; std::vector<CBlockFileInfo> vinfoBlockFile; @@ -148,6 +146,13 @@ namespace { std::set<int> setDirtyFileInfo; } // anon namespace +CBlockIndex* LookupBlockIndex(const uint256& hash) +{ + AssertLockHeld(cs_main); + BlockMap::const_iterator it = g_blockman.m_block_index.find(hash); + return it == g_blockman.m_block_index.end() ? nullptr : it->second; +} + CBlockIndex* FindForkInGlobalIndex(const CChain& chain, const CBlockLocator& locator) { AssertLockHeld(cs_main); @@ -611,7 +616,21 @@ static bool AcceptToMemoryPoolWorker(const CChainParams& chainparams, CTxMemPool size_t nLimitDescendantSize = gArgs.GetArg("-limitdescendantsize", DEFAULT_DESCENDANT_SIZE_LIMIT)*1000; std::string errString; if (!pool.CalculateMemPoolAncestors(entry, setAncestors, nLimitAncestors, nLimitAncestorSize, nLimitDescendants, nLimitDescendantSize, errString)) { - return state.Invalid(ValidationInvalidReason::TX_MEMPOOL_POLICY, false, REJECT_NONSTANDARD, "too-long-mempool-chain", errString); + setAncestors.clear(); + // If the new transaction is relatively small (up to 40k weight) + // and has at most one ancestor (ie ancestor limit of 2, including + // the new transaction), allow it if its parent has exactly the + // descendant limit descendants. + // + // This allows protocols which rely on distrusting counterparties + // being able to broadcast descendants of an unconfirmed transaction + // to be secure by simply only having two immediately-spendable + // outputs - one for each counterparty. For more info on the uses for + // this, see https://lists.linuxfoundation.org/pipermail/bitcoin-dev/2018-November/016518.html + if (nSize > EXTRA_DESCENDANT_TX_SIZE_LIMIT || + !pool.CalculateMemPoolAncestors(entry, setAncestors, 2, nLimitAncestorSize, nLimitDescendants + 1, nLimitDescendantSize + EXTRA_DESCENDANT_TX_SIZE_LIMIT, errString)) { + return state.Invalid(ValidationInvalidReason::TX_MEMPOOL_POLICY, false, REJECT_NONSTANDARD, "too-long-mempool-chain", errString); + } } // A transaction that spends outputs that would be replaced by it is invalid. Now @@ -1047,6 +1066,11 @@ bool CChainState::IsInitialBlockDownload() const static CBlockIndex *pindexBestForkTip = nullptr, *pindexBestForkBase = nullptr; +BlockMap& BlockIndex() +{ + return g_blockman.m_block_index; +} + static void AlertNotify(const std::string& strMessage) { uiInterface.NotifyAlertChanged(); @@ -1160,7 +1184,7 @@ void static InvalidChainFound(CBlockIndex* pindexNew) EXCLUSIVE_LOCKS_REQUIRED(c void CChainState::InvalidBlockFound(CBlockIndex *pindex, const CValidationState &state) { if (state.GetReason() != ValidationInvalidReason::BLOCK_MUTATED) { pindex->nStatus |= BLOCK_FAILED_VALID; - m_failed_blocks.insert(pindex); + m_blockman.m_failed_blocks.insert(pindex); setDirtyBlockIndex.insert(pindex); setBlockIndexCandidates.erase(pindex); InvalidChainFound(pindex); @@ -1695,8 +1719,8 @@ bool CChainState::ConnectBlock(const CBlock& block, CValidationState& state, CBl // relative to a piece of software is an objective fact these defaults can be easily reviewed. // This setting doesn't force the selection of any particular chain but makes validating some faster by // effectively caching the result of part of the verification. - BlockMap::const_iterator it = mapBlockIndex.find(hashAssumeValid); - if (it != mapBlockIndex.end()) { + BlockMap::const_iterator it = m_blockman.m_block_index.find(hashAssumeValid); + if (it != m_blockman.m_block_index.end()) { if (it->second->GetAncestor(pindex->nHeight) == pindex && pindexBestHeader->GetAncestor(pindex->nHeight) == pindex && pindexBestHeader->nChainWork >= nMinimumChainWork) { @@ -2366,10 +2390,11 @@ CBlockIndex* CChainState::FindMostWorkChain() { if (fFailedChain) { pindexFailed->nStatus |= BLOCK_FAILED_CHILD; } else if (fMissingData) { - // If we're missing data, then add back to mapBlocksUnlinked, + // If we're missing data, then add back to m_blocks_unlinked, // so that if the block arrives in the future we can try adding // to setBlockIndexCandidates again. - mapBlocksUnlinked.insert(std::make_pair(pindexFailed->pprev, pindexFailed)); + m_blockman.m_blocks_unlinked.insert( + std::make_pair(pindexFailed->pprev, pindexFailed)); } setBlockIndexCandidates.erase(pindexFailed); pindexFailed = pindexFailed->pprev; @@ -2720,12 +2745,12 @@ bool CChainState::InvalidateBlock(CValidationState& state, const CChainParams& c to_mark_failed->nStatus |= BLOCK_FAILED_VALID; setDirtyBlockIndex.insert(to_mark_failed); setBlockIndexCandidates.erase(to_mark_failed); - m_failed_blocks.insert(to_mark_failed); + m_blockman.m_failed_blocks.insert(to_mark_failed); // The resulting new best tip may not be in setBlockIndexCandidates anymore, so // add it again. - BlockMap::iterator it = mapBlockIndex.begin(); - while (it != mapBlockIndex.end()) { + BlockMap::iterator it = m_blockman.m_block_index.begin(); + while (it != m_blockman.m_block_index.end()) { if (it->second->IsValid(BLOCK_VALID_TRANSACTIONS) && it->second->HaveTxsDownloaded() && !setBlockIndexCandidates.value_comp()(it->second, m_chain.Tip())) { setBlockIndexCandidates.insert(it->second); } @@ -2752,8 +2777,8 @@ void CChainState::ResetBlockFailureFlags(CBlockIndex *pindex) { int nHeight = pindex->nHeight; // Remove the invalidity flag from this block and all its descendants. - BlockMap::iterator it = mapBlockIndex.begin(); - while (it != mapBlockIndex.end()) { + BlockMap::iterator it = m_blockman.m_block_index.begin(); + while (it != m_blockman.m_block_index.end()) { if (!it->second->IsValid() && it->second->GetAncestor(nHeight) == pindex) { it->second->nStatus &= ~BLOCK_FAILED_MASK; setDirtyBlockIndex.insert(it->second); @@ -2764,7 +2789,7 @@ void CChainState::ResetBlockFailureFlags(CBlockIndex *pindex) { // Reset invalid block marker if it was pointing to one of those. pindexBestInvalid = nullptr; } - m_failed_blocks.erase(it->second); + m_blockman.m_failed_blocks.erase(it->second); } it++; } @@ -2774,7 +2799,7 @@ void CChainState::ResetBlockFailureFlags(CBlockIndex *pindex) { if (pindex->nStatus & BLOCK_FAILED_MASK) { pindex->nStatus &= ~BLOCK_FAILED_MASK; setDirtyBlockIndex.insert(pindex); - m_failed_blocks.erase(pindex); + m_blockman.m_failed_blocks.erase(pindex); } pindex = pindex->pprev; } @@ -2784,14 +2809,14 @@ void ResetBlockFailureFlags(CBlockIndex *pindex) { return ::ChainstateActive().ResetBlockFailureFlags(pindex); } -CBlockIndex* CChainState::AddToBlockIndex(const CBlockHeader& block) +CBlockIndex* BlockManager::AddToBlockIndex(const CBlockHeader& block) { AssertLockHeld(cs_main); // Check for duplicate uint256 hash = block.GetHash(); - BlockMap::iterator it = mapBlockIndex.find(hash); - if (it != mapBlockIndex.end()) + BlockMap::iterator it = m_block_index.find(hash); + if (it != m_block_index.end()) return it->second; // Construct new block index object @@ -2800,10 +2825,10 @@ CBlockIndex* CChainState::AddToBlockIndex(const CBlockHeader& block) // to avoid miners withholding blocks but broadcasting headers, to get a // competitive advantage. pindexNew->nSequenceId = 0; - BlockMap::iterator mi = mapBlockIndex.insert(std::make_pair(hash, pindexNew)).first; + BlockMap::iterator mi = m_block_index.insert(std::make_pair(hash, pindexNew)).first; pindexNew->phashBlock = &((*mi).first); - BlockMap::iterator miPrev = mapBlockIndex.find(block.hashPrevBlock); - if (miPrev != mapBlockIndex.end()) + BlockMap::iterator miPrev = m_block_index.find(block.hashPrevBlock); + if (miPrev != m_block_index.end()) { pindexNew->pprev = (*miPrev).second; pindexNew->nHeight = pindexNew->pprev->nHeight + 1; @@ -2852,17 +2877,17 @@ void CChainState::ReceivedBlockTransactions(const CBlock& block, CBlockIndex* pi if (m_chain.Tip() == nullptr || !setBlockIndexCandidates.value_comp()(pindex, m_chain.Tip())) { setBlockIndexCandidates.insert(pindex); } - std::pair<std::multimap<CBlockIndex*, CBlockIndex*>::iterator, std::multimap<CBlockIndex*, CBlockIndex*>::iterator> range = mapBlocksUnlinked.equal_range(pindex); + std::pair<std::multimap<CBlockIndex*, CBlockIndex*>::iterator, std::multimap<CBlockIndex*, CBlockIndex*>::iterator> range = m_blockman.m_blocks_unlinked.equal_range(pindex); while (range.first != range.second) { std::multimap<CBlockIndex*, CBlockIndex*>::iterator it = range.first; queue.push_back(it->second); range.first++; - mapBlocksUnlinked.erase(it); + m_blockman.m_blocks_unlinked.erase(it); } } } else { if (pindexNew->pprev && pindexNew->pprev->IsValid(BLOCK_VALID_TREE)) { - mapBlocksUnlinked.insert(std::make_pair(pindexNew->pprev, pindexNew)); + m_blockman.m_blocks_unlinked.insert(std::make_pair(pindexNew->pprev, pindexNew)); } } } @@ -3117,7 +3142,7 @@ static bool ContextualCheckBlockHeader(const CBlockHeader& block, CValidationSta if (fCheckpointsEnabled) { // Don't accept any forks from the main chain prior to last checkpoint. // GetLastCheckpoint finds the last checkpoint in MapCheckpoints that's in our - // MapBlockIndex. + // g_blockman.m_block_index. CBlockIndex* pcheckpoint = GetLastCheckpoint(params.Checkpoints()); if (pcheckpoint && nHeight < pcheckpoint->nHeight) return state.Invalid(ValidationInvalidReason::BLOCK_CHECKPOINT, error("%s: forked chain older than last checkpoint (height %d)", __func__, nHeight), REJECT_CHECKPOINT, "bad-fork-prior-to-checkpoint"); @@ -3230,15 +3255,15 @@ static bool ContextualCheckBlock(const CBlock& block, CValidationState& state, c return true; } -bool CChainState::AcceptBlockHeader(const CBlockHeader& block, CValidationState& state, const CChainParams& chainparams, CBlockIndex** ppindex) +bool BlockManager::AcceptBlockHeader(const CBlockHeader& block, CValidationState& state, const CChainParams& chainparams, CBlockIndex** ppindex) { AssertLockHeld(cs_main); // Check for duplicate uint256 hash = block.GetHash(); - BlockMap::iterator miSelf = mapBlockIndex.find(hash); + BlockMap::iterator miSelf = m_block_index.find(hash); CBlockIndex *pindex = nullptr; if (hash != chainparams.GetConsensus().hashGenesisBlock) { - if (miSelf != mapBlockIndex.end()) { + if (miSelf != m_block_index.end()) { // Block header is already known. pindex = miSelf->second; if (ppindex) @@ -3253,8 +3278,8 @@ bool CChainState::AcceptBlockHeader(const CBlockHeader& block, CValidationState& // Get prev block index CBlockIndex* pindexPrev = nullptr; - BlockMap::iterator mi = mapBlockIndex.find(block.hashPrevBlock); - if (mi == mapBlockIndex.end()) + BlockMap::iterator mi = m_block_index.find(block.hashPrevBlock); + if (mi == m_block_index.end()) return state.Invalid(ValidationInvalidReason::BLOCK_MISSING_PREV, error("%s: prev block not found", __func__), 0, "prev-blk-not-found"); pindexPrev = (*mi).second; if (pindexPrev->nStatus & BLOCK_FAILED_MASK) @@ -3306,8 +3331,6 @@ bool CChainState::AcceptBlockHeader(const CBlockHeader& block, CValidationState& if (ppindex) *ppindex = pindex; - CheckBlockIndex(chainparams.GetConsensus()); - return true; } @@ -3319,7 +3342,10 @@ bool ProcessNewBlockHeaders(const std::vector<CBlockHeader>& headers, CValidatio LOCK(cs_main); for (const CBlockHeader& header : headers) { CBlockIndex *pindex = nullptr; // Use a temp pindex instead of ppindex to avoid a const_cast - if (!::ChainstateActive().AcceptBlockHeader(header, state, chainparams, &pindex)) { + bool accepted = g_blockman.AcceptBlockHeader(header, state, chainparams, &pindex); + ::ChainstateActive().CheckBlockIndex(chainparams.GetConsensus()); + + if (!accepted) { if (first_invalid) *first_invalid = header; return false; } @@ -3362,7 +3388,10 @@ bool CChainState::AcceptBlock(const std::shared_ptr<const CBlock>& pblock, CVali CBlockIndex *pindexDummy = nullptr; CBlockIndex *&pindex = ppindex ? *ppindex : pindexDummy; - if (!AcceptBlockHeader(block, state, chainparams, &pindex)) + bool accepted_header = m_blockman.AcceptBlockHeader(block, state, chainparams, &pindex); + CheckBlockIndex(chainparams.GetConsensus()); + + if (!accepted_header) return false; // Try to process all requested blocks that we don't have, but only @@ -3513,7 +3542,7 @@ void PruneOneBlockFile(const int fileNumber) { LOCK(cs_LastBlockFile); - for (const auto& entry : mapBlockIndex) { + for (const auto& entry : g_blockman.m_block_index) { CBlockIndex* pindex = entry.second; if (pindex->nFile == fileNumber) { pindex->nStatus &= ~BLOCK_HAVE_DATA; @@ -3523,16 +3552,16 @@ void PruneOneBlockFile(const int fileNumber) pindex->nUndoPos = 0; setDirtyBlockIndex.insert(pindex); - // Prune from mapBlocksUnlinked -- any block we prune would have + // Prune from m_blocks_unlinked -- any block we prune would have // to be downloaded again in order to consider its chain, at which // point it would be considered as a candidate for - // mapBlocksUnlinked or setBlockIndexCandidates. - std::pair<std::multimap<CBlockIndex*, CBlockIndex*>::iterator, std::multimap<CBlockIndex*, CBlockIndex*>::iterator> range = mapBlocksUnlinked.equal_range(pindex->pprev); + // m_blocks_unlinked or setBlockIndexCandidates. + auto range = g_blockman.m_blocks_unlinked.equal_range(pindex->pprev); while (range.first != range.second) { std::multimap<CBlockIndex *, CBlockIndex *>::iterator _it = range.first; range.first++; if (_it->second == pindex) { - mapBlocksUnlinked.erase(_it); + g_blockman.m_blocks_unlinked.erase(_it); } } } @@ -3681,7 +3710,7 @@ fs::path GetBlockPosFilename(const FlatFilePos &pos) return BlockFileSeq().FileName(pos); } -CBlockIndex * CChainState::InsertBlockIndex(const uint256& hash) +CBlockIndex * BlockManager::InsertBlockIndex(const uint256& hash) { AssertLockHeld(cs_main); @@ -3689,27 +3718,30 @@ CBlockIndex * CChainState::InsertBlockIndex(const uint256& hash) return nullptr; // Return existing - BlockMap::iterator mi = mapBlockIndex.find(hash); - if (mi != mapBlockIndex.end()) + BlockMap::iterator mi = m_block_index.find(hash); + if (mi != m_block_index.end()) return (*mi).second; // Create new CBlockIndex* pindexNew = new CBlockIndex(); - mi = mapBlockIndex.insert(std::make_pair(hash, pindexNew)).first; + mi = m_block_index.insert(std::make_pair(hash, pindexNew)).first; pindexNew->phashBlock = &((*mi).first); return pindexNew; } -bool CChainState::LoadBlockIndex(const Consensus::Params& consensus_params, CBlockTreeDB& blocktree) +bool BlockManager::LoadBlockIndex( + const Consensus::Params& consensus_params, + CBlockTreeDB& blocktree, + std::set<CBlockIndex*, CBlockIndexWorkComparator>& block_index_candidates) { if (!blocktree.LoadBlockIndexGuts(consensus_params, [this](const uint256& hash) EXCLUSIVE_LOCKS_REQUIRED(cs_main) { return this->InsertBlockIndex(hash); })) return false; // Calculate nChainWork std::vector<std::pair<int, CBlockIndex*> > vSortedByHeight; - vSortedByHeight.reserve(mapBlockIndex.size()); - for (const std::pair<const uint256, CBlockIndex*>& item : mapBlockIndex) + vSortedByHeight.reserve(m_block_index.size()); + for (const std::pair<const uint256, CBlockIndex*>& item : m_block_index) { CBlockIndex* pindex = item.second; vSortedByHeight.push_back(std::make_pair(pindex->nHeight, pindex)); @@ -3729,7 +3761,7 @@ bool CChainState::LoadBlockIndex(const Consensus::Params& consensus_params, CBlo pindex->nChainTx = pindex->pprev->nChainTx + pindex->nTx; } else { pindex->nChainTx = 0; - mapBlocksUnlinked.insert(std::make_pair(pindex->pprev, pindex)); + m_blocks_unlinked.insert(std::make_pair(pindex->pprev, pindex)); } } else { pindex->nChainTx = pindex->nTx; @@ -3739,8 +3771,9 @@ bool CChainState::LoadBlockIndex(const Consensus::Params& consensus_params, CBlo pindex->nStatus |= BLOCK_FAILED_CHILD; setDirtyBlockIndex.insert(pindex); } - if (pindex->IsValid(BLOCK_VALID_TRANSACTIONS) && (pindex->HaveTxsDownloaded() || pindex->pprev == nullptr)) - setBlockIndexCandidates.insert(pindex); + if (pindex->IsValid(BLOCK_VALID_TRANSACTIONS) && (pindex->HaveTxsDownloaded() || pindex->pprev == nullptr)) { + block_index_candidates.insert(pindex); + } if (pindex->nStatus & BLOCK_FAILED_MASK && (!pindexBestInvalid || pindex->nChainWork > pindexBestInvalid->nChainWork)) pindexBestInvalid = pindex; if (pindex->pprev) @@ -3752,9 +3785,21 @@ bool CChainState::LoadBlockIndex(const Consensus::Params& consensus_params, CBlo return true; } +void BlockManager::Unload() { + m_failed_blocks.clear(); + m_blocks_unlinked.clear(); + + for (const BlockMap::value_type& entry : m_block_index) { + delete entry.second; + } + + m_block_index.clear(); +} + bool static LoadBlockIndexDB(const CChainParams& chainparams) EXCLUSIVE_LOCKS_REQUIRED(cs_main) { - if (!::ChainstateActive().LoadBlockIndex(chainparams.GetConsensus(), *pblocktree)) + if (!g_blockman.LoadBlockIndex( + chainparams.GetConsensus(), *pblocktree, ::ChainstateActive().setBlockIndexCandidates)) return false; // Load block file info @@ -3777,7 +3822,7 @@ bool static LoadBlockIndexDB(const CChainParams& chainparams) EXCLUSIVE_LOCKS_RE // Check presence of blk files LogPrintf("Checking all blk files are present...\n"); std::set<int> setBlkDataFiles; - for (const std::pair<const uint256, CBlockIndex*>& item : mapBlockIndex) + for (const std::pair<const uint256, CBlockIndex*>& item : g_blockman.m_block_index) { CBlockIndex* pindex = item.second; if (pindex->nStatus & BLOCK_HAVE_DATA) { @@ -3976,16 +4021,16 @@ bool CChainState::ReplayBlocks(const CChainParams& params, CCoinsView* view) const CBlockIndex* pindexNew; // New tip during the interrupted flush. const CBlockIndex* pindexFork = nullptr; // Latest block common to both the old and the new tip. - if (mapBlockIndex.count(hashHeads[0]) == 0) { + if (m_blockman.m_block_index.count(hashHeads[0]) == 0) { return error("ReplayBlocks(): reorganization to unknown block requested"); } - pindexNew = mapBlockIndex[hashHeads[0]]; + pindexNew = m_blockman.m_block_index[hashHeads[0]]; if (!hashHeads[1].IsNull()) { // The old tip is allowed to be 0, indicating it's the first flush. - if (mapBlockIndex.count(hashHeads[1]) == 0) { + if (m_blockman.m_block_index.count(hashHeads[1]) == 0) { return error("ReplayBlocks(): reorganization from unknown block requested"); } - pindexOld = mapBlockIndex[hashHeads[1]]; + pindexOld = m_blockman.m_block_index[hashHeads[1]]; pindexFork = LastCommonAncestor(pindexOld, pindexNew); assert(pindexFork != nullptr); } @@ -4051,10 +4096,10 @@ void CChainState::EraseBlockData(CBlockIndex* index) setDirtyBlockIndex.insert(index); // Update indexes setBlockIndexCandidates.erase(index); - std::pair<std::multimap<CBlockIndex*, CBlockIndex*>::iterator, std::multimap<CBlockIndex*, CBlockIndex*>::iterator> ret = mapBlocksUnlinked.equal_range(index->pprev); + auto ret = m_blockman.m_blocks_unlinked.equal_range(index->pprev); while (ret.first != ret.second) { if (ret.first->second == index) { - mapBlocksUnlinked.erase(ret.first++); + m_blockman.m_blocks_unlinked.erase(ret.first++); } else { ++ret.first; } @@ -4074,7 +4119,7 @@ bool CChainState::RewindBlockIndex(const CChainParams& params) // blocks will be dealt with below (releasing cs_main in between). { LOCK(cs_main); - for (const auto& entry : mapBlockIndex) { + for (const auto& entry : m_blockman.m_block_index) { if (IsWitnessEnabled(entry.second->pprev, params.GetConsensus()) && !(entry.second->nStatus & BLOCK_OPT_WITNESS) && !m_chain.Contains(entry.second)) { EraseBlockData(entry.second); } @@ -4180,7 +4225,6 @@ bool RewindBlockIndex(const CChainParams& params) { void CChainState::UnloadBlockIndex() { nBlockSequenceId = 1; - m_failed_blocks.clear(); setBlockIndexCandidates.clear(); } @@ -4191,10 +4235,10 @@ void UnloadBlockIndex() { LOCK(cs_main); ::ChainActive().SetTip(nullptr); + g_blockman.Unload(); pindexBestInvalid = nullptr; pindexBestHeader = nullptr; mempool.clear(); - mapBlocksUnlinked.clear(); vinfoBlockFile.clear(); nLastBlockFile = 0; setDirtyBlockIndex.clear(); @@ -4203,11 +4247,6 @@ void UnloadBlockIndex() for (int b = 0; b < VERSIONBITS_NUM_BITS; b++) { warningcache[b].clear(); } - - for (const BlockMap::value_type& entry : mapBlockIndex) { - delete entry.second; - } - mapBlockIndex.clear(); fHavePruned = false; ::ChainstateActive().UnloadBlockIndex(); @@ -4220,7 +4259,7 @@ bool LoadBlockIndex(const CChainParams& chainparams) if (!fReindex) { bool ret = LoadBlockIndexDB(chainparams); if (!ret) return false; - needs_init = mapBlockIndex.empty(); + needs_init = g_blockman.m_block_index.empty(); } if (needs_init) { @@ -4240,10 +4279,10 @@ bool CChainState::LoadGenesisBlock(const CChainParams& chainparams) LOCK(cs_main); // Check whether we're already initialized by checking for genesis in - // mapBlockIndex. Note that we can't use m_chain here, since it is + // m_blockman.m_block_index. Note that we can't use m_chain here, since it is // set based on the coins db, not the block index db, which is the only // thing loaded at this point. - if (mapBlockIndex.count(chainparams.GenesisBlock().GetHash())) + if (m_blockman.m_block_index.count(chainparams.GenesisBlock().GetHash())) return true; try { @@ -4251,7 +4290,7 @@ bool CChainState::LoadGenesisBlock(const CChainParams& chainparams) FlatFilePos blockPos = SaveBlockToDisk(block, 0, chainparams, nullptr); if (blockPos.IsNull()) return error("%s: writing genesis block to disk failed", __func__); - CBlockIndex *pindex = AddToBlockIndex(block); + CBlockIndex *pindex = m_blockman.AddToBlockIndex(block); ReceivedBlockTransactions(block, pindex, blockPos, chainparams.GetConsensus()); } catch (const std::runtime_error& e) { return error("%s: failed to write genesis block: %s", __func__, e.what()); @@ -4396,20 +4435,20 @@ void CChainState::CheckBlockIndex(const Consensus::Params& consensusParams) LOCK(cs_main); // During a reindex, we read the genesis block and call CheckBlockIndex before ActivateBestChain, - // so we have the genesis block in mapBlockIndex but no active chain. (A few of the tests when - // iterating the block tree require that m_chain has been initialized.) + // so we have the genesis block in m_blockman.m_block_index but no active chain. (A few of the + // tests when iterating the block tree require that m_chain has been initialized.) if (m_chain.Height() < 0) { - assert(mapBlockIndex.size() <= 1); + assert(m_blockman.m_block_index.size() <= 1); return; } // Build forward-pointing map of the entire block tree. std::multimap<CBlockIndex*,CBlockIndex*> forward; - for (const std::pair<const uint256, CBlockIndex*>& entry : mapBlockIndex) { + for (const std::pair<const uint256, CBlockIndex*>& entry : m_blockman.m_block_index) { forward.insert(std::make_pair(entry.second->pprev, entry.second)); } - assert(forward.size() == mapBlockIndex.size()); + assert(forward.size() == m_blockman.m_block_index.size()); std::pair<std::multimap<CBlockIndex*,CBlockIndex*>::iterator,std::multimap<CBlockIndex*,CBlockIndex*>::iterator> rangeGenesis = forward.equal_range(nullptr); CBlockIndex *pindex = rangeGenesis.first->second; @@ -4463,7 +4502,7 @@ void CChainState::CheckBlockIndex(const Consensus::Params& consensusParams) assert(pindex->nHeight == nHeight); // nHeight must be consistent. assert(pindex->pprev == nullptr || pindex->nChainWork >= pindex->pprev->nChainWork); // For every block except the genesis block, the chainwork must be larger than the parent's. assert(nHeight < 2 || (pindex->pskip && (pindex->pskip->nHeight < nHeight))); // The pskip pointer must point back for all but the first 2 blocks. - assert(pindexFirstNotTreeValid == nullptr); // All mapBlockIndex entries must at least be TREE valid + assert(pindexFirstNotTreeValid == nullptr); // All m_blockman.m_block_index entries must at least be TREE valid if ((pindex->nStatus & BLOCK_VALID_MASK) >= BLOCK_VALID_TREE) assert(pindexFirstNotTreeValid == nullptr); // TREE valid implies all parents are TREE valid if ((pindex->nStatus & BLOCK_VALID_MASK) >= BLOCK_VALID_CHAIN) assert(pindexFirstNotChainValid == nullptr); // CHAIN valid implies all parents are CHAIN valid if ((pindex->nStatus & BLOCK_VALID_MASK) >= BLOCK_VALID_SCRIPTS) assert(pindexFirstNotScriptsValid == nullptr); // SCRIPTS valid implies all parents are SCRIPTS valid @@ -4482,13 +4521,13 @@ void CChainState::CheckBlockIndex(const Consensus::Params& consensusParams) } // If some parent is missing, then it could be that this block was in // setBlockIndexCandidates but had to be removed because of the missing data. - // In this case it must be in mapBlocksUnlinked -- see test below. + // In this case it must be in m_blocks_unlinked -- see test below. } } else { // If this block sorts worse than the current tip or some ancestor's block has never been seen, it cannot be in setBlockIndexCandidates. assert(setBlockIndexCandidates.count(pindex) == 0); } - // Check whether this block is in mapBlocksUnlinked. - std::pair<std::multimap<CBlockIndex*,CBlockIndex*>::iterator,std::multimap<CBlockIndex*,CBlockIndex*>::iterator> rangeUnlinked = mapBlocksUnlinked.equal_range(pindex->pprev); + // Check whether this block is in m_blocks_unlinked. + std::pair<std::multimap<CBlockIndex*,CBlockIndex*>::iterator,std::multimap<CBlockIndex*,CBlockIndex*>::iterator> rangeUnlinked = m_blockman.m_blocks_unlinked.equal_range(pindex->pprev); bool foundInUnlinked = false; while (rangeUnlinked.first != rangeUnlinked.second) { assert(rangeUnlinked.first->first == pindex->pprev); @@ -4499,22 +4538,22 @@ void CChainState::CheckBlockIndex(const Consensus::Params& consensusParams) rangeUnlinked.first++; } if (pindex->pprev && (pindex->nStatus & BLOCK_HAVE_DATA) && pindexFirstNeverProcessed != nullptr && pindexFirstInvalid == nullptr) { - // If this block has block data available, some parent was never received, and has no invalid parents, it must be in mapBlocksUnlinked. + // If this block has block data available, some parent was never received, and has no invalid parents, it must be in m_blocks_unlinked. assert(foundInUnlinked); } - if (!(pindex->nStatus & BLOCK_HAVE_DATA)) assert(!foundInUnlinked); // Can't be in mapBlocksUnlinked if we don't HAVE_DATA - if (pindexFirstMissing == nullptr) assert(!foundInUnlinked); // We aren't missing data for any parent -- cannot be in mapBlocksUnlinked. + if (!(pindex->nStatus & BLOCK_HAVE_DATA)) assert(!foundInUnlinked); // Can't be in m_blocks_unlinked if we don't HAVE_DATA + if (pindexFirstMissing == nullptr) assert(!foundInUnlinked); // We aren't missing data for any parent -- cannot be in m_blocks_unlinked. if (pindex->pprev && (pindex->nStatus & BLOCK_HAVE_DATA) && pindexFirstNeverProcessed == nullptr && pindexFirstMissing != nullptr) { // We HAVE_DATA for this block, have received data for all parents at some point, but we're currently missing data for some parent. assert(fHavePruned); // We must have pruned. - // This block may have entered mapBlocksUnlinked if: + // This block may have entered m_blocks_unlinked if: // - it has a descendant that at some point had more work than the // tip, and // - we tried switching to that descendant but were missing // data for some intermediate block between m_chain and the // tip. // So if this block is itself better than m_chain.Tip() and it wasn't in - // setBlockIndexCandidates, then it must be in mapBlocksUnlinked. + // setBlockIndexCandidates, then it must be in m_blocks_unlinked. if (!CBlockIndexWorkComparator()(pindex, m_chain.Tip()) && setBlockIndexCandidates.count(pindex) == 0) { if (pindexFirstInvalid == nullptr) { assert(foundInUnlinked); @@ -4758,10 +4797,10 @@ public: CMainCleanup() {} ~CMainCleanup() { // block headers - BlockMap::iterator it1 = mapBlockIndex.begin(); - for (; it1 != mapBlockIndex.end(); it1++) + BlockMap::iterator it1 = g_blockman.m_block_index.begin(); + for (; it1 != g_blockman.m_block_index.end(); it1++) delete (*it1).second; - mapBlockIndex.clear(); + g_blockman.m_block_index.clear(); } }; static CMainCleanup instance_of_cmaincleanup; diff --git a/src/validation.h b/src/validation.h index 9573d62048..d747fdbf27 100644 --- a/src/validation.h +++ b/src/validation.h @@ -64,6 +64,12 @@ static const unsigned int DEFAULT_ANCESTOR_SIZE_LIMIT = 101; static const unsigned int DEFAULT_DESCENDANT_LIMIT = 25; /** Default for -limitdescendantsize, maximum kilobytes of in-mempool descendants */ static const unsigned int DEFAULT_DESCENDANT_SIZE_LIMIT = 101; +/** + * An extra transaction can be added to a package, as long as it only has one + * ancestor and is no larger than this. Not really any reason to make this + * configurable as it doesn't materially change DoS parameters. + */ +static const unsigned int EXTRA_DESCENDANT_TX_SIZE_LIMIT = 10000; /** Default for -mempoolexpiry, expiration time for mempool transactions in hours */ static const unsigned int DEFAULT_MEMPOOL_EXPIRY = 336; /** Maximum kilobytes for transactions to store for processing during reorg */ @@ -126,8 +132,6 @@ static const unsigned int MAX_BLOCKS_TO_ANNOUNCE = 8; /** Maximum number of unconnecting headers announcements before DoS score */ static const int MAX_UNCONNECTING_HEADERS = 10; -static const bool DEFAULT_PEERBLOOMFILTERS = true; - /** Default for -stopatheight */ static const int DEFAULT_STOPATHEIGHT = 0; @@ -144,7 +148,6 @@ extern CCriticalSection cs_main; extern CBlockPolicyEstimator feeEstimator; extern CTxMemPool mempool; typedef std::unordered_map<uint256, CBlockIndex*, BlockHasher> BlockMap; -extern BlockMap& mapBlockIndex GUARDED_BY(cs_main); extern Mutex g_best_block_mutex; extern std::condition_variable g_best_block_cv; extern uint256 g_best_block; @@ -406,12 +409,7 @@ public: /** Replay blocks that aren't fully applied to the database. */ bool ReplayBlocks(const CChainParams& params, CCoinsView* view); -inline CBlockIndex* LookupBlockIndex(const uint256& hash) EXCLUSIVE_LOCKS_REQUIRED(cs_main) -{ - AssertLockHeld(cs_main); - BlockMap::const_iterator it = mapBlockIndex.find(hash); - return it == mapBlockIndex.end() ? nullptr : it->second; -} +CBlockIndex* LookupBlockIndex(const uint256& hash) EXCLUSIVE_LOCKS_REQUIRED(cs_main); /** Find the last common block between the parameter chain and a locator. */ CBlockIndex* FindForkInGlobalIndex(const CChain& chain, const CBlockLocator& locator) EXCLUSIVE_LOCKS_REQUIRED(cs_main); @@ -439,27 +437,90 @@ struct CBlockIndexWorkComparator }; /** - * CChainState stores and provides an API to update our local knowledge of the - * current best chain and header tree. + * Maintains a tree of blocks (stored in `m_block_index`) which is consulted + * to determine where the most-work tip is. * - * It generally provides access to the current block tree, as well as functions - * to provide new data, which it will appropriately validate and incorporate in - * its state as necessary. + * This data is used mostly in `CChainState` - information about, e.g., + * candidate tips is not maintained here. + */ +class BlockManager { +public: + BlockMap m_block_index GUARDED_BY(cs_main); + + /** In order to efficiently track invalidity of headers, we keep the set of + * blocks which we tried to connect and found to be invalid here (ie which + * were set to BLOCK_FAILED_VALID since the last restart). We can then + * walk this set and check if a new header is a descendant of something in + * this set, preventing us from having to walk m_block_index when we try + * to connect a bad block and fail. + * + * While this is more complicated than marking everything which descends + * from an invalid block as invalid at the time we discover it to be + * invalid, doing so would require walking all of m_block_index to find all + * descendants. Since this case should be very rare, keeping track of all + * BLOCK_FAILED_VALID blocks in a set should be just fine and work just as + * well. + * + * Because we already walk m_block_index in height-order at startup, we go + * ahead and mark descendants of invalid blocks as FAILED_CHILD at that time, + * instead of putting things in this set. + */ + std::set<CBlockIndex*> m_failed_blocks; + + /** + * All pairs A->B, where A (or one of its ancestors) misses transactions, but B has transactions. + * Pruned nodes may have entries where B is missing data. + */ + std::multimap<CBlockIndex*, CBlockIndex*> m_blocks_unlinked; + + /** + * Load the blocktree off disk and into memory. Populate certain metadata + * per index entry (nStatus, nChainWork, nTimeMax, etc.) as well as peripheral + * collections like setDirtyBlockIndex. + * + * @param[out] block_index_candidates Fill this set with any valid blocks for + * which we've downloaded all transactions. + */ + bool LoadBlockIndex( + const Consensus::Params& consensus_params, + CBlockTreeDB& blocktree, + std::set<CBlockIndex*, CBlockIndexWorkComparator>& block_index_candidates) + EXCLUSIVE_LOCKS_REQUIRED(cs_main); + + /** Clear all data members. */ + void Unload() EXCLUSIVE_LOCKS_REQUIRED(cs_main); + + CBlockIndex* AddToBlockIndex(const CBlockHeader& block) EXCLUSIVE_LOCKS_REQUIRED(cs_main); + /** Create a new block index entry for a given block hash */ + CBlockIndex* InsertBlockIndex(const uint256& hash) EXCLUSIVE_LOCKS_REQUIRED(cs_main); + + /** + * If a block header hasn't already been seen, call CheckBlockHeader on it, ensure + * that it doesn't descend from an invalid block, and then add it to m_block_index. + */ + bool AcceptBlockHeader( + const CBlockHeader& block, + CValidationState& state, + const CChainParams& chainparams, + CBlockIndex** ppindex) EXCLUSIVE_LOCKS_REQUIRED(cs_main); +}; + +/** + * CChainState stores and provides an API to update our local knowledge of the + * current best chain. * * Eventually, the API here is targeted at being exposed externally as a * consumable libconsensus library, so any functions added must only call * other class member functions, pure functions in other parts of the consensus * library, callbacks via the validation interface, or read/write-to-disk * functions (eventually this will also be via callbacks). + * + * Anything that is contingent on the current tip of the chain is stored here, + * whereas block information and metadata independent of the current tip is + * kept in `BlockMetadataManager`. */ class CChainState { private: - /** - * The set of all CBlockIndex entries with BLOCK_VALID_TRANSACTIONS (for itself and all ancestors) and - * as good as our current tip or better. Entries may be failed, though, and pruning nodes may be - * missing the data for the block. - */ - std::set<CBlockIndex*, CBlockIndexWorkComparator> setBlockIndexCandidates; /** * Every received block is assigned a unique and increasing identifier, so we @@ -473,26 +534,6 @@ private: /** chainwork for the last block that preciousblock has been applied to. */ arith_uint256 nLastPreciousChainwork = 0; - /** In order to efficiently track invalidity of headers, we keep the set of - * blocks which we tried to connect and found to be invalid here (ie which - * were set to BLOCK_FAILED_VALID since the last restart). We can then - * walk this set and check if a new header is a descendant of something in - * this set, preventing us from having to walk mapBlockIndex when we try - * to connect a bad block and fail. - * - * While this is more complicated than marking everything which descends - * from an invalid block as invalid at the time we discover it to be - * invalid, doing so would require walking all of mapBlockIndex to find all - * descendants. Since this case should be very rare, keeping track of all - * BLOCK_FAILED_VALID blocks in a set should be just fine and work just as - * well. - * - * Because we already walk mapBlockIndex in height-order at startup, we go - * ahead and mark descendants of invalid blocks as FAILED_CHILD at that time, - * instead of putting things in this set. - */ - std::set<CBlockIndex*> m_failed_blocks; - /** * the ChainState CriticalSection * A lock that must be held when modifying this ChainState - held in ActivateBestChain() @@ -507,15 +548,23 @@ private: */ mutable std::atomic<bool> m_cached_finished_ibd{false}; + //! Reference to a BlockManager instance which itself is shared across all + //! CChainState instances. Keeping a local reference allows us to test more + //! easily as opposed to referencing a global. + BlockManager& m_blockman; + public: + CChainState(BlockManager& blockman) : m_blockman(blockman) { } + //! The current chain of blockheaders we consult and build on. //! @see CChain, CBlockIndex. CChain m_chain; - BlockMap mapBlockIndex GUARDED_BY(cs_main); - std::multimap<CBlockIndex*, CBlockIndex*> mapBlocksUnlinked; - CBlockIndex *pindexBestInvalid = nullptr; - - bool LoadBlockIndex(const Consensus::Params& consensus_params, CBlockTreeDB& blocktree) EXCLUSIVE_LOCKS_REQUIRED(cs_main); + /** + * The set of all CBlockIndex entries with BLOCK_VALID_TRANSACTIONS (for itself and all ancestors) and + * as good as our current tip or better. Entries may be failed, though, and pruning nodes may be + * missing the data for the block. + */ + std::set<CBlockIndex*, CBlockIndexWorkComparator> setBlockIndexCandidates; /** * Update the on-disk chain state. @@ -541,11 +590,6 @@ public: bool ActivateBestChain(CValidationState &state, const CChainParams& chainparams, std::shared_ptr<const CBlock> pblock) LOCKS_EXCLUDED(cs_main); - /** - * If a block header hasn't already been seen, call CheckBlockHeader on it, ensure - * that it doesn't descend from an invalid block, and then add it to mapBlockIndex. - */ - bool AcceptBlockHeader(const CBlockHeader& block, CValidationState& state, const CChainParams& chainparams, CBlockIndex** ppindex) EXCLUSIVE_LOCKS_REQUIRED(cs_main); bool AcceptBlock(const std::shared_ptr<const CBlock>& pblock, CValidationState& state, const CChainParams& chainparams, CBlockIndex** ppindex, bool fRequested, const FlatFilePos* dbp, bool* fNewBlock) EXCLUSIVE_LOCKS_REQUIRED(cs_main); // Block (dis)connection on a given view: @@ -572,13 +616,6 @@ public: /** Check whether we are doing an initial block download (synchronizing from disk or network) */ bool IsInitialBlockDownload() const; -private: - bool ActivateBestChainStep(CValidationState& state, const CChainParams& chainparams, CBlockIndex* pindexMostWork, const std::shared_ptr<const CBlock>& pblock, bool& fInvalidFound, ConnectTrace& connectTrace) EXCLUSIVE_LOCKS_REQUIRED(cs_main, ::mempool.cs); - bool ConnectTip(CValidationState& state, const CChainParams& chainparams, CBlockIndex* pindexNew, const std::shared_ptr<const CBlock>& pblock, ConnectTrace& connectTrace, DisconnectedBlockTransactions& disconnectpool) EXCLUSIVE_LOCKS_REQUIRED(cs_main, ::mempool.cs); - - CBlockIndex* AddToBlockIndex(const CBlockHeader& block) EXCLUSIVE_LOCKS_REQUIRED(cs_main); - /** Create a new block index entry for a given block hash */ - CBlockIndex* InsertBlockIndex(const uint256& hash) EXCLUSIVE_LOCKS_REQUIRED(cs_main); /** * Make various assertions about the state of the block index. * @@ -586,6 +623,10 @@ private: */ void CheckBlockIndex(const Consensus::Params& consensusParams); +private: + bool ActivateBestChainStep(CValidationState& state, const CChainParams& chainparams, CBlockIndex* pindexMostWork, const std::shared_ptr<const CBlock>& pblock, bool& fInvalidFound, ConnectTrace& connectTrace) EXCLUSIVE_LOCKS_REQUIRED(cs_main, ::mempool.cs); + bool ConnectTip(CValidationState& state, const CChainParams& chainparams, CBlockIndex* pindexNew, const std::shared_ptr<const CBlock>& pblock, ConnectTrace& connectTrace, DisconnectedBlockTransactions& disconnectpool) EXCLUSIVE_LOCKS_REQUIRED(cs_main, ::mempool.cs); + void InvalidBlockFound(CBlockIndex *pindex, const CValidationState &state) EXCLUSIVE_LOCKS_REQUIRED(cs_main); CBlockIndex* FindMostWorkChain() EXCLUSIVE_LOCKS_REQUIRED(cs_main); void ReceivedBlockTransactions(const CBlock& block, CBlockIndex* pindexNew, const FlatFilePos& pos, const Consensus::Params& consensusParams) EXCLUSIVE_LOCKS_REQUIRED(cs_main); @@ -615,6 +656,9 @@ CChainState& ChainstateActive(); /** @returns the most-work chain. */ CChain& ChainActive(); +/** @returns the global block index map. */ +BlockMap& BlockIndex(); + /** Global variable that points to the coins database (protected by cs_main) */ extern std::unique_ptr<CCoinsViewDB> pcoinsdbview; diff --git a/src/wallet/crypter.cpp b/src/wallet/crypter.cpp index dd56ea10ab..0b76c1a0eb 100644 --- a/src/wallet/crypter.cpp +++ b/src/wallet/crypter.cpp @@ -107,8 +107,7 @@ bool CCrypter::Decrypt(const std::vector<unsigned char>& vchCiphertext, CKeyingM return true; } - -static bool EncryptSecret(const CKeyingMaterial& vMasterKey, const CKeyingMaterial &vchPlaintext, const uint256& nIV, std::vector<unsigned char> &vchCiphertext) +bool EncryptSecret(const CKeyingMaterial& vMasterKey, const CKeyingMaterial &vchPlaintext, const uint256& nIV, std::vector<unsigned char> &vchCiphertext) { CCrypter cKeyCrypter; std::vector<unsigned char> chIV(WALLET_CRYPTO_IV_SIZE); @@ -118,7 +117,7 @@ static bool EncryptSecret(const CKeyingMaterial& vMasterKey, const CKeyingMateri return cKeyCrypter.Encrypt(*((const CKeyingMaterial*)&vchPlaintext), vchCiphertext); } -static bool DecryptSecret(const CKeyingMaterial& vMasterKey, const std::vector<unsigned char>& vchCiphertext, const uint256& nIV, CKeyingMaterial& vchPlaintext) +bool DecryptSecret(const CKeyingMaterial& vMasterKey, const std::vector<unsigned char>& vchCiphertext, const uint256& nIV, CKeyingMaterial& vchPlaintext) { CCrypter cKeyCrypter; std::vector<unsigned char> chIV(WALLET_CRYPTO_IV_SIZE); @@ -128,7 +127,7 @@ static bool DecryptSecret(const CKeyingMaterial& vMasterKey, const std::vector<u return cKeyCrypter.Decrypt(vchCiphertext, *((CKeyingMaterial*)&vchPlaintext)); } -static bool DecryptKey(const CKeyingMaterial& vMasterKey, const std::vector<unsigned char>& vchCryptedSecret, const CPubKey& vchPubKey, CKey& key) +bool DecryptKey(const CKeyingMaterial& vMasterKey, const std::vector<unsigned char>& vchCryptedSecret, const CPubKey& vchPubKey, CKey& key) { CKeyingMaterial vchSecret; if(!DecryptSecret(vMasterKey, vchCryptedSecret, vchPubKey.GetHash(), vchSecret)) @@ -140,188 +139,3 @@ static bool DecryptKey(const CKeyingMaterial& vMasterKey, const std::vector<unsi key.Set(vchSecret.begin(), vchSecret.end(), vchPubKey.IsCompressed()); return key.VerifyPubKey(vchPubKey); } - -bool CCryptoKeyStore::SetCrypted() -{ - LOCK(cs_KeyStore); - if (fUseCrypto) - return true; - if (!mapKeys.empty()) - return false; - fUseCrypto = true; - return true; -} - -bool CCryptoKeyStore::IsLocked() const -{ - if (!IsCrypted()) { - return false; - } - LOCK(cs_KeyStore); - return vMasterKey.empty(); -} - -bool CCryptoKeyStore::Lock() -{ - if (!SetCrypted()) - return false; - - { - LOCK(cs_KeyStore); - vMasterKey.clear(); - } - - NotifyStatusChanged(this); - return true; -} - -bool CCryptoKeyStore::Unlock(const CKeyingMaterial& vMasterKeyIn, bool accept_no_keys) -{ - { - LOCK(cs_KeyStore); - if (!SetCrypted()) - return false; - - bool keyPass = mapCryptedKeys.empty(); // Always pass when there are no encrypted keys - bool keyFail = false; - CryptedKeyMap::const_iterator mi = mapCryptedKeys.begin(); - for (; mi != mapCryptedKeys.end(); ++mi) - { - const CPubKey &vchPubKey = (*mi).second.first; - const std::vector<unsigned char> &vchCryptedSecret = (*mi).second.second; - CKey key; - if (!DecryptKey(vMasterKeyIn, vchCryptedSecret, vchPubKey, key)) - { - keyFail = true; - break; - } - keyPass = true; - if (fDecryptionThoroughlyChecked) - break; - } - if (keyPass && keyFail) - { - LogPrintf("The wallet is probably corrupted: Some keys decrypt but not all.\n"); - throw std::runtime_error("Error unlocking wallet: some keys decrypt but not all. Your wallet file may be corrupt."); - } - if (keyFail || (!keyPass && !accept_no_keys)) - return false; - vMasterKey = vMasterKeyIn; - fDecryptionThoroughlyChecked = true; - } - NotifyStatusChanged(this); - return true; -} - -bool CCryptoKeyStore::AddKeyPubKey(const CKey& key, const CPubKey &pubkey) -{ - LOCK(cs_KeyStore); - if (!IsCrypted()) { - return CBasicKeyStore::AddKeyPubKey(key, pubkey); - } - - if (IsLocked()) { - return false; - } - - std::vector<unsigned char> vchCryptedSecret; - CKeyingMaterial vchSecret(key.begin(), key.end()); - if (!EncryptSecret(vMasterKey, vchSecret, pubkey.GetHash(), vchCryptedSecret)) { - return false; - } - - if (!AddCryptedKey(pubkey, vchCryptedSecret)) { - return false; - } - return true; -} - - -bool CCryptoKeyStore::AddCryptedKey(const CPubKey &vchPubKey, const std::vector<unsigned char> &vchCryptedSecret) -{ - LOCK(cs_KeyStore); - if (!SetCrypted()) { - return false; - } - - mapCryptedKeys[vchPubKey.GetID()] = make_pair(vchPubKey, vchCryptedSecret); - ImplicitlyLearnRelatedKeyScripts(vchPubKey); - return true; -} - -bool CCryptoKeyStore::HaveKey(const CKeyID &address) const -{ - LOCK(cs_KeyStore); - if (!IsCrypted()) { - return CBasicKeyStore::HaveKey(address); - } - return mapCryptedKeys.count(address) > 0; -} - -bool CCryptoKeyStore::GetKey(const CKeyID &address, CKey& keyOut) const -{ - LOCK(cs_KeyStore); - if (!IsCrypted()) { - return CBasicKeyStore::GetKey(address, keyOut); - } - - CryptedKeyMap::const_iterator mi = mapCryptedKeys.find(address); - if (mi != mapCryptedKeys.end()) - { - const CPubKey &vchPubKey = (*mi).second.first; - const std::vector<unsigned char> &vchCryptedSecret = (*mi).second.second; - return DecryptKey(vMasterKey, vchCryptedSecret, vchPubKey, keyOut); - } - return false; -} - -bool CCryptoKeyStore::GetPubKey(const CKeyID &address, CPubKey& vchPubKeyOut) const -{ - LOCK(cs_KeyStore); - if (!IsCrypted()) - return CBasicKeyStore::GetPubKey(address, vchPubKeyOut); - - CryptedKeyMap::const_iterator mi = mapCryptedKeys.find(address); - if (mi != mapCryptedKeys.end()) - { - vchPubKeyOut = (*mi).second.first; - return true; - } - // Check for watch-only pubkeys - return CBasicKeyStore::GetPubKey(address, vchPubKeyOut); -} - -std::set<CKeyID> CCryptoKeyStore::GetKeys() const -{ - LOCK(cs_KeyStore); - if (!IsCrypted()) { - return CBasicKeyStore::GetKeys(); - } - std::set<CKeyID> set_address; - for (const auto& mi : mapCryptedKeys) { - set_address.insert(mi.first); - } - return set_address; -} - -bool CCryptoKeyStore::EncryptKeys(CKeyingMaterial& vMasterKeyIn) -{ - LOCK(cs_KeyStore); - if (!mapCryptedKeys.empty() || IsCrypted()) - return false; - - fUseCrypto = true; - for (const KeyMap::value_type& mKey : mapKeys) - { - const CKey &key = mKey.second; - CPubKey vchPubKey = key.GetPubKey(); - CKeyingMaterial vchSecret(key.begin(), key.end()); - std::vector<unsigned char> vchCryptedSecret; - if (!EncryptSecret(vMasterKeyIn, vchSecret, vchPubKey.GetHash(), vchCryptedSecret)) - return false; - if (!AddCryptedKey(vchPubKey, vchCryptedSecret)) - return false; - } - mapKeys.clear(); - return true; -} diff --git a/src/wallet/crypter.h b/src/wallet/crypter.h index 8e195ca8fa..17a4e9820c 100644 --- a/src/wallet/crypter.h +++ b/src/wallet/crypter.h @@ -5,9 +5,9 @@ #ifndef BITCOIN_WALLET_CRYPTER_H #define BITCOIN_WALLET_CRYPTER_H -#include <keystore.h> #include <serialize.h> #include <support/allocators/secure.h> +#include <script/signingprovider.h> #include <atomic> @@ -109,54 +109,8 @@ public: } }; -/** Keystore which keeps the private keys encrypted. - * It derives from the basic key store, which is used if no encryption is active. - */ -class CCryptoKeyStore : public CBasicKeyStore -{ -private: - - CKeyingMaterial vMasterKey GUARDED_BY(cs_KeyStore); - - //! if fUseCrypto is true, mapKeys must be empty - //! if fUseCrypto is false, vMasterKey must be empty - std::atomic<bool> fUseCrypto; - - //! keeps track of whether Unlock has run a thorough check before - bool fDecryptionThoroughlyChecked; - -protected: - using CryptedKeyMap = std::map<CKeyID, std::pair<CPubKey, std::vector<unsigned char>>>; - - bool SetCrypted(); - - //! will encrypt previously unencrypted keys - bool EncryptKeys(CKeyingMaterial& vMasterKeyIn); - - bool Unlock(const CKeyingMaterial& vMasterKeyIn, bool accept_no_keys = false); - CryptedKeyMap mapCryptedKeys GUARDED_BY(cs_KeyStore); - -public: - CCryptoKeyStore() : fUseCrypto(false), fDecryptionThoroughlyChecked(false) - { - } - - bool IsCrypted() const { return fUseCrypto; } - bool IsLocked() const; - bool Lock(); - - virtual bool AddCryptedKey(const CPubKey &vchPubKey, const std::vector<unsigned char> &vchCryptedSecret); - bool AddKeyPubKey(const CKey& key, const CPubKey &pubkey) override; - bool HaveKey(const CKeyID &address) const override; - bool GetKey(const CKeyID &address, CKey& keyOut) const override; - bool GetPubKey(const CKeyID &address, CPubKey& vchPubKeyOut) const override; - std::set<CKeyID> GetKeys() const override; - - /** - * Wallet status (encrypted, locked) changed. - * Note: Called without locks held. - */ - boost::signals2::signal<void (CCryptoKeyStore* wallet)> NotifyStatusChanged; -}; +bool EncryptSecret(const CKeyingMaterial& vMasterKey, const CKeyingMaterial &vchPlaintext, const uint256& nIV, std::vector<unsigned char> &vchCiphertext); +bool DecryptSecret(const CKeyingMaterial& vMasterKey, const std::vector<unsigned char>& vchCiphertext, const uint256& nIV, CKeyingMaterial& vchPlaintext); +bool DecryptKey(const CKeyingMaterial& vMasterKey, const std::vector<unsigned char>& vchCryptedSecret, const CPubKey& vchPubKey, CKey& key); #endif // BITCOIN_WALLET_CRYPTER_H diff --git a/src/wallet/feebumper.cpp b/src/wallet/feebumper.cpp index 46cf6b7616..619197a57a 100644 --- a/src/wallet/feebumper.cpp +++ b/src/wallet/feebumper.cpp @@ -272,18 +272,14 @@ Result CreateRateBumpTransaction(CWallet* wallet, const uint256& txid, const CCo new_coin_control.m_min_depth = 1; CTransactionRef tx_new = MakeTransactionRef(); - CReserveKey reservekey(wallet); CAmount fee_ret; int change_pos_in_out = -1; // No requested location for change std::string fail_reason; - if (!wallet->CreateTransaction(*locked_chain, recipients, tx_new, reservekey, fee_ret, change_pos_in_out, fail_reason, new_coin_control, false)) { + if (!wallet->CreateTransaction(*locked_chain, recipients, tx_new, fee_ret, change_pos_in_out, fail_reason, new_coin_control, false)) { errors.push_back("Unable to create transaction: " + fail_reason); return Result::WALLET_ERROR; } - // If change key hasn't been ReturnKey'ed by this point, we take it out of keypool - reservekey.KeepKey(); - // Write back new fee if successful new_fee = fee_ret; @@ -330,9 +326,8 @@ Result CommitTransaction(CWallet* wallet, const uint256& txid, CMutableTransacti mapValue_t mapValue = oldWtx.mapValue; mapValue["replaces_txid"] = oldWtx.GetHash().ToString(); - CReserveKey reservekey(wallet); CValidationState state; - if (!wallet->CommitTransaction(tx, std::move(mapValue), oldWtx.vOrderForm, reservekey, state)) { + if (!wallet->CommitTransaction(tx, std::move(mapValue), oldWtx.vOrderForm, state)) { // NOTE: CommitTransaction never returns false, so this should never happen. errors.push_back(strprintf("The transaction was rejected: %s", FormatStateMessage(state))); return Result::WALLET_ERROR; diff --git a/src/wallet/ismine.cpp b/src/wallet/ismine.cpp index 6138d4ae44..b7ef2d4490 100644 --- a/src/wallet/ismine.cpp +++ b/src/wallet/ismine.cpp @@ -8,6 +8,7 @@ #include <key.h> #include <script/script.h> #include <script/sign.h> +#include <script/signingprovider.h> #include <wallet/wallet.h> typedef std::vector<unsigned char> valtype; diff --git a/src/wallet/rpcdump.cpp b/src/wallet/rpcdump.cpp index 3112dca9f5..7e973194d9 100644 --- a/src/wallet/rpcdump.cpp +++ b/src/wallet/rpcdump.cpp @@ -109,8 +109,6 @@ UniValue importprivkey(const JSONRPCRequest& request) return NullUniValue; } - if (request.fHelp || request.params.size() < 1 || request.params.size() > 3) - throw std::runtime_error( RPCHelpMan{"importprivkey", "\nAdds a private key (as returned by dumpprivkey) to your wallet. Requires a new wallet backup.\n" "Hint: use importmulti to import more than one private key.\n" @@ -135,7 +133,7 @@ UniValue importprivkey(const JSONRPCRequest& request) "\nAs a JSON-RPC call\n" + HelpExampleRpc("importprivkey", "\"mykey\", \"testing\", false") }, - }.ToString()); + }.Check(request); if (pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { throw JSONRPCError(RPC_WALLET_ERROR, "Cannot import private keys to a wallet with private keys disabled"); @@ -217,8 +215,6 @@ UniValue abortrescan(const JSONRPCRequest& request) return NullUniValue; } - if (request.fHelp || request.params.size() > 0) - throw std::runtime_error( RPCHelpMan{"abortrescan", "\nStops current wallet rescan triggered by an RPC call, e.g. by an importprivkey call.\n" "Note: Use \"getwalletinfo\" to query the scanning progress.\n", @@ -232,7 +228,7 @@ UniValue abortrescan(const JSONRPCRequest& request) "\nAs a JSON-RPC call\n" + HelpExampleRpc("abortrescan", "") }, - }.ToString()); + }.Check(request); if (!pwallet->IsScanning() || pwallet->IsAbortingRescan()) return false; pwallet->AbortRescan(); @@ -283,8 +279,6 @@ UniValue importaddress(const JSONRPCRequest& request) return NullUniValue; } - if (request.fHelp || request.params.size() < 1 || request.params.size() > 4) - throw std::runtime_error( RPCHelpMan{"importaddress", "\nAdds an address or script (in hex) that can be watched as if it were in your wallet but cannot be used to spend. Requires a new wallet backup.\n" "\nNote: This call can take over an hour to complete if rescan is true, during that time, other rpc calls\n" @@ -309,7 +303,7 @@ UniValue importaddress(const JSONRPCRequest& request) "\nAs a JSON-RPC call\n" + HelpExampleRpc("importaddress", "\"myaddress\", \"testing\", false") }, - }.ToString()); + }.Check(request); std::string strLabel; @@ -376,8 +370,6 @@ UniValue importprunedfunds(const JSONRPCRequest& request) return NullUniValue; } - if (request.fHelp || request.params.size() != 2) - throw std::runtime_error( RPCHelpMan{"importprunedfunds", "\nImports funds without rescan. Corresponding address or script must previously be included in wallet. Aimed towards pruned wallets. The end-user is responsible to import additional transactions that subsequently spend the imported outputs or rescan after the point in the blockchain the transaction is included.\n", { @@ -386,8 +378,7 @@ UniValue importprunedfunds(const JSONRPCRequest& request) }, RPCResults{}, RPCExamples{""}, - }.ToString() - ); + }.Check(request); CMutableTransaction tx; if (!DecodeHexTx(tx, request.params[0].get_str())) @@ -443,8 +434,6 @@ UniValue removeprunedfunds(const JSONRPCRequest& request) return NullUniValue; } - if (request.fHelp || request.params.size() != 1) - throw std::runtime_error( RPCHelpMan{"removeprunedfunds", "\nDeletes the specified transaction from the wallet. Meant for use with pruned wallets and as a companion to importprunedfunds. This will affect wallet balances.\n", { @@ -456,7 +445,7 @@ UniValue removeprunedfunds(const JSONRPCRequest& request) "\nAs a JSON-RPC call\n" + HelpExampleRpc("removeprunedfunds", "\"a8d0c0184dde994a09ec054286f1ce581bebf46446a512166eae7628734ea0a5\"") }, - }.ToString()); + }.Check(request); auto locked_chain = pwallet->chain().lock(); LOCK(pwallet->cs_wallet); @@ -485,8 +474,6 @@ UniValue importpubkey(const JSONRPCRequest& request) return NullUniValue; } - if (request.fHelp || request.params.size() < 1 || request.params.size() > 3) - throw std::runtime_error( RPCHelpMan{"importpubkey", "\nAdds a public key (in hex) that can be watched as if it were in your wallet but cannot be used to spend. Requires a new wallet backup.\n" "Hint: use importmulti to import more than one public key.\n" @@ -507,7 +494,7 @@ UniValue importpubkey(const JSONRPCRequest& request) "\nAs a JSON-RPC call\n" + HelpExampleRpc("importpubkey", "\"mypubkey\", \"testing\", false") }, - }.ToString()); + }.Check(request); std::string strLabel; @@ -570,8 +557,6 @@ UniValue importwallet(const JSONRPCRequest& request) return NullUniValue; } - if (request.fHelp || request.params.size() != 1) - throw std::runtime_error( RPCHelpMan{"importwallet", "\nImports keys from a wallet dump file (see dumpwallet). Requires a new wallet backup to include imported keys.\n" "Note: Use \"getwalletinfo\" to query the scanning progress.\n", @@ -587,7 +572,7 @@ UniValue importwallet(const JSONRPCRequest& request) "\nImport using the json rpc call\n" + HelpExampleRpc("importwallet", "\"test\"") }, - }.ToString()); + }.Check(request); if (pwallet->chain().havePruned()) { // Exit early and print an error. @@ -735,8 +720,6 @@ UniValue dumpprivkey(const JSONRPCRequest& request) return NullUniValue; } - if (request.fHelp || request.params.size() != 1) - throw std::runtime_error( RPCHelpMan{"dumpprivkey", "\nReveals the private key corresponding to 'address'.\n" "Then the importprivkey can be used with this output\n", @@ -751,7 +734,7 @@ UniValue dumpprivkey(const JSONRPCRequest& request) + HelpExampleCli("importprivkey", "\"mykey\"") + HelpExampleRpc("dumpprivkey", "\"myaddress\"") }, - }.ToString()); + }.Check(request); auto locked_chain = pwallet->chain().lock(); LOCK(pwallet->cs_wallet); @@ -783,8 +766,6 @@ UniValue dumpwallet(const JSONRPCRequest& request) return NullUniValue; } - if (request.fHelp || request.params.size() != 1) - throw std::runtime_error( RPCHelpMan{"dumpwallet", "\nDumps all wallet keys in a human-readable format to a server-side file. This does not allow overwriting existing files.\n" "Imported scripts are included in the dumpfile, but corresponding BIP173 addresses, etc. may not be added automatically by importwallet.\n" @@ -802,7 +783,7 @@ UniValue dumpwallet(const JSONRPCRequest& request) HelpExampleCli("dumpwallet", "\"test\"") + HelpExampleRpc("dumpwallet", "\"test\"") }, - }.ToString()); + }.Check(request); auto locked_chain = pwallet->chain().lock(); LOCK(pwallet->cs_wallet); @@ -1322,8 +1303,6 @@ UniValue importmulti(const JSONRPCRequest& mainRequest) return NullUniValue; } - if (mainRequest.fHelp || mainRequest.params.size() < 1 || mainRequest.params.size() > 2) - throw std::runtime_error( RPCHelpMan{"importmulti", "\nImport addresses/scripts (with private or public keys, redeem script (P2SH)), optionally rescanning the blockchain from the earliest creation time of the imported scripts. Requires a new wallet backup.\n" "If an address/script is imported without all of the private keys required to spend from that address, it will be watchonly. The 'watchonly' option must be set to true in this case or a warning will be returned.\n" @@ -1384,8 +1363,7 @@ UniValue importmulti(const JSONRPCRequest& mainRequest) "{ \"scriptPubKey\": { \"address\": \"<my 2nd address>\" }, \"label\": \"example 2\", \"timestamp\": 1455191480 }]'") + HelpExampleCli("importmulti", "'[{ \"scriptPubKey\": { \"address\": \"<my address>\" }, \"timestamp\":1455191478 }]' '{ \"rescan\": false}'") }, - }.ToString() - ); + }.Check(mainRequest); RPCTypeCheck(mainRequest.params, {UniValue::VARR, UniValue::VOBJ}); diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index eae5f876ea..ab732dc0d8 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -52,6 +52,14 @@ static inline bool GetAvoidReuseFlag(CWallet * const pwallet, const UniValue& pa return avoid_reuse; } +/** Checks if a CKey is in the given CWallet compressed or otherwise*/ +bool HaveKey(const CWallet& wallet, const CKey& key) +{ + CKey key2; + key2.Set(key.begin(), key.end(), !key.IsCompressed()); + return wallet.HaveKey(key.GetPubKey().GetID()) || wallet.HaveKey(key2.GetPubKey().GetID()); +} + bool GetWalletNameFromJSONRPCRequest(const JSONRPCRequest& request, std::string& wallet_name) { if (request.URI.substr(0, WALLET_ENDPOINT_BASE.size()) == WALLET_ENDPOINT_BASE) { @@ -159,8 +167,6 @@ static UniValue getnewaddress(const JSONRPCRequest& request) return NullUniValue; } - if (request.fHelp || request.params.size() > 2) - throw std::runtime_error( RPCHelpMan{"getnewaddress", "\nReturns a new Bitcoin address for receiving payments.\n" "If 'label' is specified, it is added to the address book \n" @@ -176,7 +182,7 @@ static UniValue getnewaddress(const JSONRPCRequest& request) HelpExampleCli("getnewaddress", "") + HelpExampleRpc("getnewaddress", "") }, - }.ToString()); + }.Check(request); LOCK(pwallet->cs_wallet); @@ -196,19 +202,11 @@ static UniValue getnewaddress(const JSONRPCRequest& request) } } - if (!pwallet->IsLocked()) { - pwallet->TopUpKeyPool(); - } - - // Generate a new key that is added to wallet - CPubKey newKey; - if (!pwallet->GetKeyFromPool(newKey)) { - throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, "Error: Keypool ran out, please call keypoolrefill first"); + CTxDestination dest; + std::string error; + if (!pwallet->GetNewDestination(output_type, label, dest, error)) { + throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, error); } - pwallet->LearnRelatedScripts(newKey, output_type); - CTxDestination dest = GetDestinationForKey(newKey, output_type); - - pwallet->SetAddressBook(dest, label, "receive"); return EncodeDestination(dest); } @@ -222,8 +220,6 @@ static UniValue getrawchangeaddress(const JSONRPCRequest& request) return NullUniValue; } - if (request.fHelp || request.params.size() > 1) - throw std::runtime_error( RPCHelpMan{"getrawchangeaddress", "\nReturns a new Bitcoin address, for receiving change.\n" "This is for use with raw transactions, NOT normal use.\n", @@ -237,7 +233,7 @@ static UniValue getrawchangeaddress(const JSONRPCRequest& request) HelpExampleCli("getrawchangeaddress", "") + HelpExampleRpc("getrawchangeaddress", "") }, - }.ToString()); + }.Check(request); LOCK(pwallet->cs_wallet); @@ -245,10 +241,6 @@ static UniValue getrawchangeaddress(const JSONRPCRequest& request) throw JSONRPCError(RPC_WALLET_ERROR, "Error: This wallet has no available keys"); } - if (!pwallet->IsLocked()) { - pwallet->TopUpKeyPool(); - } - OutputType output_type = pwallet->m_default_change_type != OutputType::CHANGE_AUTO ? pwallet->m_default_change_type : pwallet->m_default_address_type; if (!request.params[0].isNull()) { if (!ParseOutputType(request.params[0].get_str(), output_type)) { @@ -256,16 +248,11 @@ static UniValue getrawchangeaddress(const JSONRPCRequest& request) } } - CReserveKey reservekey(pwallet); - CPubKey vchPubKey; - if (!reservekey.GetReservedKey(vchPubKey, true)) - throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, "Error: Keypool ran out, please call keypoolrefill first"); - - reservekey.KeepKey(); - - pwallet->LearnRelatedScripts(vchPubKey, output_type); - CTxDestination dest = GetDestinationForKey(vchPubKey, output_type); - + CTxDestination dest; + std::string error; + if (!pwallet->GetNewChangeDestination(output_type, dest, error)) { + throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, error); + } return EncodeDestination(dest); } @@ -279,8 +266,6 @@ static UniValue setlabel(const JSONRPCRequest& request) return NullUniValue; } - if (request.fHelp || request.params.size() != 2) - throw std::runtime_error( RPCHelpMan{"setlabel", "\nSets the label associated with the given address.\n", { @@ -292,7 +277,7 @@ static UniValue setlabel(const JSONRPCRequest& request) HelpExampleCli("setlabel", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\" \"tabby\"") + HelpExampleRpc("setlabel", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\", \"tabby\"") }, - }.ToString()); + }.Check(request); LOCK(pwallet->cs_wallet); @@ -332,7 +317,6 @@ static CTransactionRef SendMoney(interfaces::Chain::Lock& locked_chain, CWallet CScript scriptPubKey = GetScriptForDestination(address); // Create and send the transaction - CReserveKey reservekey(pwallet); CAmount nFeeRequired; std::string strError; std::vector<CRecipient> vecSend; @@ -340,13 +324,13 @@ static CTransactionRef SendMoney(interfaces::Chain::Lock& locked_chain, CWallet CRecipient recipient = {scriptPubKey, nValue, fSubtractFeeFromAmount}; vecSend.push_back(recipient); CTransactionRef tx; - if (!pwallet->CreateTransaction(locked_chain, vecSend, tx, reservekey, nFeeRequired, nChangePosRet, strError, coin_control)) { + if (!pwallet->CreateTransaction(locked_chain, vecSend, tx, nFeeRequired, nChangePosRet, strError, coin_control)) { if (!fSubtractFeeFromAmount && nValue + nFeeRequired > curBalance) strError = strprintf("Error: This transaction requires a transaction fee of at least %s", FormatMoney(nFeeRequired)); throw JSONRPCError(RPC_WALLET_ERROR, strError); } CValidationState state; - if (!pwallet->CommitTransaction(tx, std::move(mapValue), {} /* orderForm */, reservekey, state)) { + if (!pwallet->CommitTransaction(tx, std::move(mapValue), {} /* orderForm */, state)) { strError = strprintf("Error: The transaction was rejected! Reason given: %s", FormatStateMessage(state)); throw JSONRPCError(RPC_WALLET_ERROR, strError); } @@ -362,8 +346,6 @@ static UniValue sendtoaddress(const JSONRPCRequest& request) return NullUniValue; } - if (request.fHelp || request.params.size() < 2 || request.params.size() > 9) - throw std::runtime_error( RPCHelpMan{"sendtoaddress", "\nSend an amount to a given address." + HelpRequiringPassphrase(pwallet) + "\n", @@ -395,7 +377,7 @@ static UniValue sendtoaddress(const JSONRPCRequest& request) + HelpExampleCli("sendtoaddress", "\"1M72Sfpbz1BPpXFHz9m3CdqATR44Jvaydd\" 0.1 \"\" \"\" true") + HelpExampleRpc("sendtoaddress", "\"1M72Sfpbz1BPpXFHz9m3CdqATR44Jvaydd\", 0.1, \"donation\", \"seans outpost\"") }, - }.ToString()); + }.Check(request); // Make sure the results are valid at least up to the most recent block // the user could have gotten from another RPC command prior to now @@ -460,8 +442,6 @@ static UniValue listaddressgroupings(const JSONRPCRequest& request) return NullUniValue; } - if (request.fHelp || request.params.size() != 0) - throw std::runtime_error( RPCHelpMan{"listaddressgroupings", "\nLists groups of addresses which have had their common ownership\n" "made public by common use as inputs or as the resulting change\n" @@ -484,7 +464,7 @@ static UniValue listaddressgroupings(const JSONRPCRequest& request) HelpExampleCli("listaddressgroupings", "") + HelpExampleRpc("listaddressgroupings", "") }, - }.ToString()); + }.Check(request); // Make sure the results are valid at least up to the most recent block // the user could have gotten from another RPC command prior to now @@ -523,8 +503,6 @@ static UniValue signmessage(const JSONRPCRequest& request) return NullUniValue; } - if (request.fHelp || request.params.size() != 2) - throw std::runtime_error( RPCHelpMan{"signmessage", "\nSign a message with the private key of an address" + HelpRequiringPassphrase(pwallet) + "\n", @@ -545,7 +523,7 @@ static UniValue signmessage(const JSONRPCRequest& request) "\nAs a JSON-RPC call\n" + HelpExampleRpc("signmessage", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\", \"my message\"") }, - }.ToString()); + }.Check(request); auto locked_chain = pwallet->chain().lock(); LOCK(pwallet->cs_wallet); @@ -591,8 +569,6 @@ static UniValue getreceivedbyaddress(const JSONRPCRequest& request) return NullUniValue; } - if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) - throw std::runtime_error( RPCHelpMan{"getreceivedbyaddress", "\nReturns the total amount received by the given address in transactions with at least minconf confirmations.\n", { @@ -612,7 +588,7 @@ static UniValue getreceivedbyaddress(const JSONRPCRequest& request) "\nAs a JSON-RPC call\n" + HelpExampleRpc("getreceivedbyaddress", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\", 6") }, - }.ToString()); + }.Check(request); // Make sure the results are valid at least up to the most recent block // the user could have gotten from another RPC command prior to now @@ -663,8 +639,6 @@ static UniValue getreceivedbylabel(const JSONRPCRequest& request) return NullUniValue; } - if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) - throw std::runtime_error( RPCHelpMan{"getreceivedbylabel", "\nReturns the total amount received by addresses with <label> in transactions with at least [minconf] confirmations.\n", { @@ -684,7 +658,7 @@ static UniValue getreceivedbylabel(const JSONRPCRequest& request) "\nAs a JSON-RPC call\n" + HelpExampleRpc("getreceivedbylabel", "\"tabby\", 6") }, - }.ToString()); + }.Check(request); // Make sure the results are valid at least up to the most recent block // the user could have gotten from another RPC command prior to now @@ -733,8 +707,6 @@ static UniValue getbalance(const JSONRPCRequest& request) return NullUniValue; } - if (request.fHelp || request.params.size() > 4) - throw std::runtime_error( RPCHelpMan{"getbalance", "\nReturns the total available balance.\n" "The available balance is what the wallet considers currently spendable, and is\n" @@ -756,7 +728,7 @@ static UniValue getbalance(const JSONRPCRequest& request) "\nAs a JSON-RPC call\n" + HelpExampleRpc("getbalance", "\"*\", 6") }, - }.ToString()); + }.Check(request); // Make sure the results are valid at least up to the most recent block // the user could have gotten from another RPC command prior to now @@ -796,14 +768,12 @@ static UniValue getunconfirmedbalance(const JSONRPCRequest &request) return NullUniValue; } - if (request.fHelp || request.params.size() > 0) - throw std::runtime_error( RPCHelpMan{"getunconfirmedbalance", "DEPRECATED\nIdentical to getbalances().mine.untrusted_pending\n", {}, RPCResults{}, RPCExamples{""}, - }.ToString()); + }.Check(request); // Make sure the results are valid at least up to the most recent block // the user could have gotten from another RPC command prior to now @@ -825,7 +795,7 @@ static UniValue sendmany(const JSONRPCRequest& request) return NullUniValue; } - const RPCHelpMan help{"sendmany", + RPCHelpMan{"sendmany", "\nSend multiple times. Amounts are double-precision floating point numbers." + HelpRequiringPassphrase(pwallet) + "\n", { @@ -866,11 +836,7 @@ static UniValue sendmany(const JSONRPCRequest& request) "\nAs a JSON-RPC call\n" + HelpExampleRpc("sendmany", "\"\", {\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\":0.01,\"1353tsE8YMTA4EuV7dgUXGjNFf9KpVvKHz\":0.02}, 6, \"testing\"") }, - }; - - if (request.fHelp || !help.IsValidNumArgs(request.params.size())) { - throw std::runtime_error(help.ToString()); - } + }.Check(request); // Make sure the results are valid at least up to the most recent block // the user could have gotten from another RPC command prior to now @@ -948,16 +914,15 @@ static UniValue sendmany(const JSONRPCRequest& request) std::shuffle(vecSend.begin(), vecSend.end(), FastRandomContext()); // Send - CReserveKey keyChange(pwallet); CAmount nFeeRequired = 0; int nChangePosRet = -1; std::string strFailReason; CTransactionRef tx; - bool fCreated = pwallet->CreateTransaction(*locked_chain, vecSend, tx, keyChange, nFeeRequired, nChangePosRet, strFailReason, coin_control); + bool fCreated = pwallet->CreateTransaction(*locked_chain, vecSend, tx, nFeeRequired, nChangePosRet, strFailReason, coin_control); if (!fCreated) throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, strFailReason); CValidationState state; - if (!pwallet->CommitTransaction(tx, std::move(mapValue), {} /* orderForm */, keyChange, state)) { + if (!pwallet->CommitTransaction(tx, std::move(mapValue), {} /* orderForm */, state)) { strFailReason = strprintf("Transaction commit failed:: %s", FormatStateMessage(state)); throw JSONRPCError(RPC_WALLET_ERROR, strFailReason); } @@ -974,8 +939,6 @@ static UniValue addmultisigaddress(const JSONRPCRequest& request) return NullUniValue; } - if (request.fHelp || request.params.size() < 2 || request.params.size() > 4) { - std::string msg = RPCHelpMan{"addmultisigaddress", "\nAdd a nrequired-to-sign multisignature address to the wallet. Requires a new wallet backup.\n" "Each key is a Bitcoin address or hex-encoded public key.\n" @@ -1004,9 +967,7 @@ static UniValue addmultisigaddress(const JSONRPCRequest& request) "\nAs a JSON-RPC call\n" + HelpExampleRpc("addmultisigaddress", "2, \"[\\\"16sSauSf5pF2UkUwvKGq4qjNRzBZYqgEL5\\\",\\\"171sgjn4YtPu27adkKGrdDwzRTxnRkBfKV\\\"]\"") }, - }.ToString(); - throw std::runtime_error(msg); - } + }.Check(request); auto locked_chain = pwallet->chain().lock(); LOCK(pwallet->cs_wallet); @@ -1211,8 +1172,6 @@ static UniValue listreceivedbyaddress(const JSONRPCRequest& request) return NullUniValue; } - if (request.fHelp || request.params.size() > 4) - throw std::runtime_error( RPCHelpMan{"listreceivedbyaddress", "\nList balances by receiving address.\n", { @@ -1243,7 +1202,7 @@ static UniValue listreceivedbyaddress(const JSONRPCRequest& request) + HelpExampleRpc("listreceivedbyaddress", "6, true, true") + HelpExampleRpc("listreceivedbyaddress", "6, true, true, \"1M72Sfpbz1BPpXFHz9m3CdqATR44Jvaydd\"") }, - }.ToString()); + }.Check(request); // Make sure the results are valid at least up to the most recent block // the user could have gotten from another RPC command prior to now @@ -1264,8 +1223,6 @@ static UniValue listreceivedbylabel(const JSONRPCRequest& request) return NullUniValue; } - if (request.fHelp || request.params.size() > 3) - throw std::runtime_error( RPCHelpMan{"listreceivedbylabel", "\nList received transactions by label.\n", { @@ -1289,7 +1246,7 @@ static UniValue listreceivedbylabel(const JSONRPCRequest& request) + HelpExampleCli("listreceivedbylabel", "6 true") + HelpExampleRpc("listreceivedbylabel", "6, true, true") }, - }.ToString()); + }.Check(request); // Make sure the results are valid at least up to the most recent block // the user could have gotten from another RPC command prior to now @@ -1404,8 +1361,6 @@ UniValue listtransactions(const JSONRPCRequest& request) return NullUniValue; } - if (request.fHelp || request.params.size() > 4) - throw std::runtime_error( RPCHelpMan{"listtransactions", "\nIf a label name is provided, this will return only incoming transactions paying to addresses with the specified label.\n" "\nReturns up to 'count' most recent transactions skipping the first 'from' transactions.\n", @@ -1457,7 +1412,7 @@ UniValue listtransactions(const JSONRPCRequest& request) "\nAs a JSON-RPC call\n" + HelpExampleRpc("listtransactions", "\"*\", 20, 100") }, - }.ToString()); + }.Check(request); // Make sure the results are valid at least up to the most recent block // the user could have gotten from another RPC command prior to now @@ -1538,8 +1493,6 @@ static UniValue listsinceblock(const JSONRPCRequest& request) return NullUniValue; } - if (request.fHelp || request.params.size() > 4) - throw std::runtime_error( RPCHelpMan{"listsinceblock", "\nGet all transactions in blocks since block [blockhash], or all transactions if omitted.\n" "If \"blockhash\" is no longer a part of the main chain, transactions from the fork point onward are included.\n" @@ -1592,7 +1545,7 @@ static UniValue listsinceblock(const JSONRPCRequest& request) + HelpExampleCli("listsinceblock", "\"000000000000000bacf66f7497b7dc45ef753ee9a7d38571037cdb1a57f663ad\" 6") + HelpExampleRpc("listsinceblock", "\"000000000000000bacf66f7497b7dc45ef753ee9a7d38571037cdb1a57f663ad\", 6") }, - }.ToString()); + }.Check(request); // Make sure the results are valid at least up to the most recent block // the user could have gotten from another RPC command prior to now @@ -1683,8 +1636,6 @@ static UniValue gettransaction(const JSONRPCRequest& request) return NullUniValue; } - if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) - throw std::runtime_error( RPCHelpMan{"gettransaction", "\nGet detailed information about in-wallet transaction <txid>\n", { @@ -1732,7 +1683,7 @@ static UniValue gettransaction(const JSONRPCRequest& request) + HelpExampleCli("gettransaction", "\"1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d\" true") + HelpExampleRpc("gettransaction", "\"1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d\"") }, - }.ToString()); + }.Check(request); // Make sure the results are valid at least up to the most recent block // the user could have gotten from another RPC command prior to now @@ -1785,8 +1736,6 @@ static UniValue abandontransaction(const JSONRPCRequest& request) return NullUniValue; } - if (request.fHelp || request.params.size() != 1) { - throw std::runtime_error( RPCHelpMan{"abandontransaction", "\nMark in-wallet transaction <txid> as abandoned\n" "This will mark this transaction and all its in-wallet descendants as abandoned which will allow\n" @@ -1801,8 +1750,7 @@ static UniValue abandontransaction(const JSONRPCRequest& request) HelpExampleCli("abandontransaction", "\"1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d\"") + HelpExampleRpc("abandontransaction", "\"1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d\"") }, - }.ToString()); - } + }.Check(request); // Make sure the results are valid at least up to the most recent block // the user could have gotten from another RPC command prior to now @@ -1833,8 +1781,6 @@ static UniValue backupwallet(const JSONRPCRequest& request) return NullUniValue; } - if (request.fHelp || request.params.size() != 1) - throw std::runtime_error( RPCHelpMan{"backupwallet", "\nSafely copies current wallet file to destination, which can be a directory or a path with filename.\n", { @@ -1845,7 +1791,7 @@ static UniValue backupwallet(const JSONRPCRequest& request) HelpExampleCli("backupwallet", "\"backup.dat\"") + HelpExampleRpc("backupwallet", "\"backup.dat\"") }, - }.ToString()); + }.Check(request); // Make sure the results are valid at least up to the most recent block // the user could have gotten from another RPC command prior to now @@ -1872,8 +1818,6 @@ static UniValue keypoolrefill(const JSONRPCRequest& request) return NullUniValue; } - if (request.fHelp || request.params.size() > 1) - throw std::runtime_error( RPCHelpMan{"keypoolrefill", "\nFills the keypool."+ HelpRequiringPassphrase(pwallet) + "\n", @@ -1885,7 +1829,7 @@ static UniValue keypoolrefill(const JSONRPCRequest& request) HelpExampleCli("keypoolrefill", "") + HelpExampleRpc("keypoolrefill", "") }, - }.ToString()); + }.Check(request); if (pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { throw JSONRPCError(RPC_WALLET_ERROR, "Error: Private keys are disabled for this wallet"); @@ -1922,8 +1866,6 @@ static UniValue walletpassphrase(const JSONRPCRequest& request) return NullUniValue; } - if (request.fHelp || request.params.size() != 2) { - throw std::runtime_error( RPCHelpMan{"walletpassphrase", "\nStores the wallet decryption key in memory for 'timeout' seconds.\n" "This is needed prior to performing transactions related to private keys such as sending bitcoins\n" @@ -1943,8 +1885,7 @@ static UniValue walletpassphrase(const JSONRPCRequest& request) "\nAs a JSON-RPC call\n" + HelpExampleRpc("walletpassphrase", "\"my pass phrase\", 60") }, - }.ToString()); - } + }.Check(request); auto locked_chain = pwallet->chain().lock(); LOCK(pwallet->cs_wallet); @@ -2009,8 +1950,6 @@ static UniValue walletpassphrasechange(const JSONRPCRequest& request) return NullUniValue; } - if (request.fHelp || request.params.size() != 2) { - throw std::runtime_error( RPCHelpMan{"walletpassphrasechange", "\nChanges the wallet passphrase from 'oldpassphrase' to 'newpassphrase'.\n", { @@ -2022,8 +1961,7 @@ static UniValue walletpassphrasechange(const JSONRPCRequest& request) HelpExampleCli("walletpassphrasechange", "\"old one\" \"new one\"") + HelpExampleRpc("walletpassphrasechange", "\"old one\", \"new one\"") }, - }.ToString()); - } + }.Check(request); auto locked_chain = pwallet->chain().lock(); LOCK(pwallet->cs_wallet); @@ -2063,8 +2001,6 @@ static UniValue walletlock(const JSONRPCRequest& request) return NullUniValue; } - if (request.fHelp || request.params.size() != 0) { - throw std::runtime_error( RPCHelpMan{"walletlock", "\nRemoves the wallet encryption key from memory, locking the wallet.\n" "After calling this method, you will need to call walletpassphrase again\n" @@ -2081,8 +2017,7 @@ static UniValue walletlock(const JSONRPCRequest& request) "\nAs a JSON-RPC call\n" + HelpExampleRpc("walletlock", "") }, - }.ToString()); - } + }.Check(request); auto locked_chain = pwallet->chain().lock(); LOCK(pwallet->cs_wallet); @@ -2107,8 +2042,6 @@ static UniValue encryptwallet(const JSONRPCRequest& request) return NullUniValue; } - if (request.fHelp || request.params.size() != 1) { - throw std::runtime_error( RPCHelpMan{"encryptwallet", "\nEncrypts the wallet with 'passphrase'. This is for first time encryption.\n" "After this, any calls that interact with private keys such as sending or signing \n" @@ -2131,8 +2064,7 @@ static UniValue encryptwallet(const JSONRPCRequest& request) "\nAs a JSON-RPC call\n" + HelpExampleRpc("encryptwallet", "\"my pass phrase\"") }, - }.ToString()); - } + }.Check(request); auto locked_chain = pwallet->chain().lock(); LOCK(pwallet->cs_wallet); @@ -2171,8 +2103,6 @@ static UniValue lockunspent(const JSONRPCRequest& request) return NullUniValue; } - if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) - throw std::runtime_error( RPCHelpMan{"lockunspent", "\nUpdates list of temporarily unspendable outputs.\n" "Temporarily lock (unlock=false) or unlock (unlock=true) specified transaction outputs.\n" @@ -2209,7 +2139,7 @@ static UniValue lockunspent(const JSONRPCRequest& request) "\nAs a JSON-RPC call\n" + HelpExampleRpc("lockunspent", "false, \"[{\\\"txid\\\":\\\"a08e6907dbbd3d809776dbfc5d82e371b764ed838b5655e72f463568df1aadf0\\\",\\\"vout\\\":1}]\"") }, - }.ToString()); + }.Check(request); // Make sure the results are valid at least up to the most recent block // the user could have gotten from another RPC command prior to now @@ -2300,8 +2230,6 @@ static UniValue listlockunspent(const JSONRPCRequest& request) return NullUniValue; } - if (request.fHelp || request.params.size() > 0) - throw std::runtime_error( RPCHelpMan{"listlockunspent", "\nReturns list of temporarily unspendable outputs.\n" "See the lockunspent call to lock and unlock transactions for spending.\n", @@ -2327,7 +2255,7 @@ static UniValue listlockunspent(const JSONRPCRequest& request) "\nAs a JSON-RPC call\n" + HelpExampleRpc("listlockunspent", "") }, - }.ToString()); + }.Check(request); auto locked_chain = pwallet->chain().lock(); LOCK(pwallet->cs_wallet); @@ -2357,8 +2285,6 @@ static UniValue settxfee(const JSONRPCRequest& request) return NullUniValue; } - if (request.fHelp || request.params.size() < 1 || request.params.size() > 1) { - throw std::runtime_error( RPCHelpMan{"settxfee", "\nSet the transaction fee per kB for this wallet. Overrides the global -paytxfee command line parameter.\n", { @@ -2371,8 +2297,7 @@ static UniValue settxfee(const JSONRPCRequest& request) HelpExampleCli("settxfee", "0.00001") + HelpExampleRpc("settxfee", "0.00001") }, - }.ToString()); - } + }.Check(request); auto locked_chain = pwallet->chain().lock(); LOCK(pwallet->cs_wallet); @@ -2399,7 +2324,7 @@ static UniValue getbalances(const JSONRPCRequest& request) } CWallet& wallet = *rpc_wallet; - const RPCHelpMan help{ + RPCHelpMan{ "getbalances", "Returns an object with all balances in " + CURRENCY_UNIT + ".\n", {}, @@ -2420,11 +2345,7 @@ static UniValue getbalances(const JSONRPCRequest& request) RPCExamples{ HelpExampleCli("getbalances", "") + HelpExampleRpc("getbalances", "")}, - }; - - if (request.fHelp || !help.IsValidNumArgs(request.params.size())) { - throw std::runtime_error(help.ToString()); - } + }.Check(request); // Make sure the results are valid at least up to the most recent block // the user could have gotten from another RPC command prior to now @@ -2469,7 +2390,7 @@ static UniValue getwalletinfo(const JSONRPCRequest& request) return NullUniValue; } - const RPCHelpMan help{"getwalletinfo", + RPCHelpMan{"getwalletinfo", "Returns an object containing various wallet state info.\n", {}, RPCResult{ @@ -2499,11 +2420,7 @@ static UniValue getwalletinfo(const JSONRPCRequest& request) HelpExampleCli("getwalletinfo", "") + HelpExampleRpc("getwalletinfo", "") }, - }; - - if (request.fHelp || !help.IsValidNumArgs(request.params.size())) { - throw std::runtime_error(help.ToString()); - } + }.Check(request); // Make sure the results are valid at least up to the most recent block // the user could have gotten from another RPC command prior to now @@ -2550,8 +2467,6 @@ static UniValue getwalletinfo(const JSONRPCRequest& request) static UniValue listwalletdir(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() != 0) { - throw std::runtime_error( RPCHelpMan{"listwalletdir", "Returns a list of wallets in the wallet directory.\n", {}, @@ -2569,8 +2484,7 @@ static UniValue listwalletdir(const JSONRPCRequest& request) HelpExampleCli("listwalletdir", "") + HelpExampleRpc("listwalletdir", "") }, - }.ToString()); - } + }.Check(request); UniValue wallets(UniValue::VARR); for (const auto& path : ListWalletDir()) { @@ -2586,8 +2500,6 @@ static UniValue listwalletdir(const JSONRPCRequest& request) static UniValue listwallets(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() != 0) - throw std::runtime_error( RPCHelpMan{"listwallets", "Returns a list of currently loaded wallets.\n" "For full information on the wallet, use \"getwalletinfo\"\n", @@ -2602,7 +2514,7 @@ static UniValue listwallets(const JSONRPCRequest& request) HelpExampleCli("listwallets", "") + HelpExampleRpc("listwallets", "") }, - }.ToString()); + }.Check(request); UniValue obj(UniValue::VARR); @@ -2621,8 +2533,6 @@ static UniValue listwallets(const JSONRPCRequest& request) static UniValue loadwallet(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() != 1) - throw std::runtime_error( RPCHelpMan{"loadwallet", "\nLoads a wallet from a wallet file or directory." "\nNote that all wallet command-line options used when starting bitcoind will be" @@ -2640,7 +2550,7 @@ static UniValue loadwallet(const JSONRPCRequest& request) HelpExampleCli("loadwallet", "\"test.dat\"") + HelpExampleRpc("loadwallet", "\"test.dat\"") }, - }.ToString()); + }.Check(request); WalletLocation location(request.params[0].get_str()); @@ -2674,12 +2584,10 @@ static UniValue setwalletflag(const JSONRPCRequest& request) return NullUniValue; } - if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) { - std::string flags = ""; - for (auto& it : WALLET_FLAG_MAP) - if (it.second & MUTABLE_WALLET_FLAGS) - flags += (flags == "" ? "" : ", ") + it.first; - throw std::runtime_error( + std::string flags = ""; + for (auto& it : WALLET_FLAG_MAP) + if (it.second & MUTABLE_WALLET_FLAGS) + flags += (flags == "" ? "" : ", ") + it.first; RPCHelpMan{"setwalletflag", "\nChange the state of the given wallet flag for a wallet.\n", { @@ -2697,8 +2605,7 @@ static UniValue setwalletflag(const JSONRPCRequest& request) HelpExampleCli("setwalletflag", "avoid_reuse") + HelpExampleRpc("setwalletflag", "\"avoid_reuse\"") }, - }.ToString()); - } + }.Check(request); std::string flag_str = request.params[0].get_str(); bool value = request.params[1].isNull() || request.params[1].get_bool(); @@ -2737,7 +2644,7 @@ static UniValue setwalletflag(const JSONRPCRequest& request) static UniValue createwallet(const JSONRPCRequest& request) { - const RPCHelpMan help{ + RPCHelpMan{ "createwallet", "\nCreates and loads a new wallet.\n", { @@ -2757,22 +2664,14 @@ static UniValue createwallet(const JSONRPCRequest& request) HelpExampleCli("createwallet", "\"testwallet\"") + HelpExampleRpc("createwallet", "\"testwallet\"") }, - }; - - if (request.fHelp || !help.IsValidNumArgs(request.params.size())) { - throw std::runtime_error(help.ToString()); - } - std::string error; - std::string warning; + }.Check(request); uint64_t flags = 0; if (!request.params[1].isNull() && request.params[1].get_bool()) { flags |= WALLET_FLAG_DISABLE_PRIVATE_KEYS; } - bool create_blank = false; // Indicate that the wallet is actually supposed to be blank and not just blank to make it encrypted if (!request.params[2].isNull() && request.params[2].get_bool()) { - create_blank = true; flags |= WALLET_FLAG_BLANK_WALLET; } SecureString passphrase; @@ -2783,55 +2682,24 @@ static UniValue createwallet(const JSONRPCRequest& request) // Empty string is invalid throw JSONRPCError(RPC_WALLET_ENCRYPTION_FAILED, "Cannot encrypt a wallet with a blank password"); } - // Born encrypted wallets need to be blank first so that wallet creation doesn't make any unencrypted keys - flags |= WALLET_FLAG_BLANK_WALLET; } if (!request.params[4].isNull() && request.params[4].get_bool()) { flags |= WALLET_FLAG_AVOID_REUSE; } - WalletLocation location(request.params[0].get_str()); - if (location.Exists()) { - throw JSONRPCError(RPC_WALLET_ERROR, "Wallet " + location.GetName() + " already exists."); - } - - // Wallet::Verify will check if we're trying to create a wallet with a duplication name. - if (!CWallet::Verify(*g_rpc_interfaces->chain, location, false, error, warning)) { - throw JSONRPCError(RPC_WALLET_ERROR, "Wallet file verification failed: " + error); - } - - std::shared_ptr<CWallet> const wallet = CWallet::CreateWalletFromFile(*g_rpc_interfaces->chain, location, flags); - if (!wallet) { - throw JSONRPCError(RPC_WALLET_ERROR, "Wallet creation failed."); - } - - // Encrypt the wallet if there's a passphrase - if (!passphrase.empty() && !(flags & WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { - if (!wallet->EncryptWallet(passphrase)) { - throw JSONRPCError(RPC_WALLET_ENCRYPTION_FAILED, "Error: Wallet created but failed to encrypt."); - } - - if (!create_blank) { - // Unlock the wallet - if (!wallet->Unlock(passphrase)) { - throw JSONRPCError(RPC_WALLET_ENCRYPTION_FAILED, "Error: Wallet was encrypted but could not be unlocked"); - } - - // Set a seed for the wallet - CPubKey master_pub_key = wallet->GenerateNewSeed(); - wallet->SetHDSeed(master_pub_key); - wallet->NewKeyPool(); - - // Relock the wallet - wallet->Lock(); - } + std::string error; + std::string warning; + WalletCreationStatus status; + std::shared_ptr<CWallet> wallet = CreateWallet(*g_rpc_interfaces->chain, request.params[0].get_str(), error, warning, status, passphrase, flags); + if (status == WalletCreationStatus::CREATION_FAILED) { + throw JSONRPCError(RPC_WALLET_ERROR, error); + } else if (status == WalletCreationStatus::ENCRYPTION_FAILED) { + throw JSONRPCError(RPC_WALLET_ENCRYPTION_FAILED, error); + } else if (status != WalletCreationStatus::SUCCESS) { + throw JSONRPCError(RPC_WALLET_ERROR, "Wallet creation failed"); } - AddWallet(wallet); - - wallet->postInitProcess(); - UniValue obj(UniValue::VOBJ); obj.pushKV("name", wallet->GetName()); obj.pushKV("warning", warning); @@ -2841,8 +2709,6 @@ static UniValue createwallet(const JSONRPCRequest& request) static UniValue unloadwallet(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() > 1) { - throw std::runtime_error( RPCHelpMan{"unloadwallet", "Unloads the wallet referenced by the request endpoint otherwise unloads the wallet specified in the argument.\n" "Specifying the wallet name on a wallet endpoint is invalid.", @@ -2854,8 +2720,7 @@ static UniValue unloadwallet(const JSONRPCRequest& request) HelpExampleCli("unloadwallet", "wallet_name") + HelpExampleRpc("unloadwallet", "wallet_name") }, - }.ToString()); - } + }.Check(request); std::string wallet_name; if (GetWalletNameFromJSONRPCRequest(request, wallet_name)) { @@ -2892,7 +2757,7 @@ static UniValue listunspent(const JSONRPCRequest& request) return NullUniValue; } - const RPCHelpMan help{ + RPCHelpMan{ "listunspent", "\nReturns array of unspent transaction outputs\n" "with between minconf and maxconf (inclusive) confirmations.\n" @@ -2946,11 +2811,7 @@ static UniValue listunspent(const JSONRPCRequest& request) + HelpExampleCli("listunspent", "6 9999999 '[]' true '{ \"minimumAmount\": 0.005 }'") + HelpExampleRpc("listunspent", "6, 9999999, [] , true, { \"minimumAmount\": 0.005 } ") }, - }; - - if (request.fHelp || !help.IsValidNumArgs(request.params.size())) { - throw std::runtime_error(help.ToString()); - } + }.Check(request); int nMinDepth = 1; if (!request.params[0].isNull()) { @@ -3219,7 +3080,7 @@ static UniValue fundrawtransaction(const JSONRPCRequest& request) return NullUniValue; } - const RPCHelpMan help{"fundrawtransaction", + RPCHelpMan{"fundrawtransaction", "\nAdd inputs to a transaction until it has enough in value to meet its out value.\n" "This will not modify existing inputs, and will add at most one change output to the outputs.\n" "No existing outputs will be modified unless \"subtractFeeFromOutputs\" is specified.\n" @@ -3283,11 +3144,7 @@ static UniValue fundrawtransaction(const JSONRPCRequest& request) "\nSend the transaction\n" + HelpExampleCli("sendrawtransaction", "\"signedtransactionhex\"") }, - }; - - if (request.fHelp || !help.IsValidNumArgs(request.params.size())) { - throw std::runtime_error(help.ToString()); - } + }.Check(request); RPCTypeCheck(request.params, {UniValue::VSTR, UniValueType(), UniValue::VBOOL}); @@ -3320,8 +3177,6 @@ UniValue signrawtransactionwithwallet(const JSONRPCRequest& request) return NullUniValue; } - if (request.fHelp || request.params.size() < 1 || request.params.size() > 3) - throw std::runtime_error( RPCHelpMan{"signrawtransactionwithwallet", "\nSign inputs for raw transaction (serialized, hex-encoded).\n" "The second optional argument (may be null) is an array of previous transaction outputs that\n" @@ -3371,7 +3226,7 @@ UniValue signrawtransactionwithwallet(const JSONRPCRequest& request) HelpExampleCli("signrawtransactionwithwallet", "\"myhex\"") + HelpExampleRpc("signrawtransactionwithwallet", "\"myhex\"") }, - }.ToString()); + }.Check(request); RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VARR, UniValue::VSTR}, true); @@ -3404,8 +3259,6 @@ static UniValue bumpfee(const JSONRPCRequest& request) if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) return NullUniValue; - if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) { - throw std::runtime_error( RPCHelpMan{"bumpfee", "\nBumps the fee of an opt-in-RBF transaction T, replacing it with a new transaction B.\n" "An opt-in RBF transaction with the given txid must be in the wallet.\n" @@ -3453,8 +3306,7 @@ static UniValue bumpfee(const JSONRPCRequest& request) "\nBump the fee, get the new transaction\'s txid\n" + HelpExampleCli("bumpfee", "<txid>") }, - }.ToString()); - } + }.Check(request); RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VOBJ}); uint256 hash(ParseHashV(request.params[0], "txid")); @@ -3567,8 +3419,6 @@ UniValue rescanblockchain(const JSONRPCRequest& request) return NullUniValue; } - if (request.fHelp || request.params.size() > 2) { - throw std::runtime_error( RPCHelpMan{"rescanblockchain", "\nRescan the local blockchain for wallet related transactions.\n" "Note: Use \"getwalletinfo\" to query the scanning progress.\n", @@ -3586,8 +3436,7 @@ UniValue rescanblockchain(const JSONRPCRequest& request) HelpExampleCli("rescanblockchain", "100000 120000") + HelpExampleRpc("rescanblockchain", "100000, 120000") }, - }.ToString()); - } + }.Check(request); WalletRescanReserver reserver(pwallet); if (!reserver.reserve()) { @@ -3774,8 +3623,6 @@ UniValue getaddressinfo(const JSONRPCRequest& request) return NullUniValue; } - if (request.fHelp || request.params.size() != 1) { - throw std::runtime_error( RPCHelpMan{"getaddressinfo", "\nReturn information about the given bitcoin address. Some information requires the address\n" "to be in the wallet.\n", @@ -3824,8 +3671,7 @@ UniValue getaddressinfo(const JSONRPCRequest& request) HelpExampleCli("getaddressinfo", "\"1PSSGeFHDnKNxiEyFrD1wcEaHr9hrQDDWc\"") + HelpExampleRpc("getaddressinfo", "\"1PSSGeFHDnKNxiEyFrD1wcEaHr9hrQDDWc\"") }, - }.ToString()); - } + }.Check(request); LOCK(pwallet->cs_wallet); @@ -3902,8 +3748,6 @@ static UniValue getaddressesbylabel(const JSONRPCRequest& request) return NullUniValue; } - if (request.fHelp || request.params.size() != 1) - throw std::runtime_error( RPCHelpMan{"getaddressesbylabel", "\nReturns the list of addresses assigned the specified label.\n", { @@ -3920,7 +3764,7 @@ static UniValue getaddressesbylabel(const JSONRPCRequest& request) HelpExampleCli("getaddressesbylabel", "\"tabby\"") + HelpExampleRpc("getaddressesbylabel", "\"tabby\"") }, - }.ToString()); + }.Check(request); LOCK(pwallet->cs_wallet); @@ -3961,8 +3805,6 @@ static UniValue listlabels(const JSONRPCRequest& request) return NullUniValue; } - if (request.fHelp || request.params.size() > 1) - throw std::runtime_error( RPCHelpMan{"listlabels", "\nReturns the list of all labels, or labels that are assigned to addresses with a specific purpose.\n", { @@ -3984,7 +3826,7 @@ static UniValue listlabels(const JSONRPCRequest& request) "\nAs a JSON-RPC call\n" + HelpExampleRpc("listlabels", "receive") }, - }.ToString()); + }.Check(request); LOCK(pwallet->cs_wallet); @@ -4018,8 +3860,6 @@ UniValue sethdseed(const JSONRPCRequest& request) return NullUniValue; } - if (request.fHelp || request.params.size() > 2) { - throw std::runtime_error( RPCHelpMan{"sethdseed", "\nSet or generate a new HD wallet seed. Non-HD wallets will not be upgraded to being a HD wallet. Wallets that are already\n" "HD will have a new HD seed set so that new keys added to the keypool will be derived from this new seed.\n" @@ -4040,8 +3880,7 @@ UniValue sethdseed(const JSONRPCRequest& request) + HelpExampleCli("sethdseed", "true \"wifkey\"") + HelpExampleRpc("sethdseed", "true, \"wifkey\"") }, - }.ToString()); - } + }.Check(request); if (pwallet->chain().isInitialBlockDownload()) { throw JSONRPCError(RPC_CLIENT_IN_INITIAL_DOWNLOAD, "Cannot set a new HD seed while still in Initial Block Download"); @@ -4097,8 +3936,6 @@ UniValue walletprocesspsbt(const JSONRPCRequest& request) return NullUniValue; } - if (request.fHelp || request.params.size() < 1 || request.params.size() > 4) - throw std::runtime_error( RPCHelpMan{"walletprocesspsbt", "\nUpdate a PSBT with input information from our wallet and then sign inputs\n" "that we can sign for." + @@ -4125,7 +3962,7 @@ UniValue walletprocesspsbt(const JSONRPCRequest& request) RPCExamples{ HelpExampleCli("walletprocesspsbt", "\"psbt\"") }, - }.ToString()); + }.Check(request); RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VBOOL, UniValue::VSTR}); @@ -4166,8 +4003,6 @@ UniValue walletcreatefundedpsbt(const JSONRPCRequest& request) return NullUniValue; } - if (request.fHelp || request.params.size() < 2 || request.params.size() > 5) - throw std::runtime_error( RPCHelpMan{"walletcreatefundedpsbt", "\nCreates and funds a transaction in the Partially Signed Transaction format. Inputs will be added if supplied inputs are not enough\n" "Implements the Creator and Updater roles.\n", @@ -4239,7 +4074,7 @@ UniValue walletcreatefundedpsbt(const JSONRPCRequest& request) "\nCreate a transaction with no inputs\n" + HelpExampleCli("walletcreatefundedpsbt", "\"[{\\\"txid\\\":\\\"myid\\\",\\\"vout\\\":0}]\" \"[{\\\"data\\\":\\\"00010203\\\"}]\"") }, - }.ToString()); + }.Check(request); RPCTypeCheck(request.params, { UniValue::VARR, diff --git a/src/wallet/test/wallet_tests.cpp b/src/wallet/test/wallet_tests.cpp index ef95d0544f..8af05dea45 100644 --- a/src/wallet/test/wallet_tests.cpp +++ b/src/wallet/test/wallet_tests.cpp @@ -272,7 +272,7 @@ static int64_t AddTx(CWallet& wallet, uint32_t lockTime, int64_t mockTime, int64 if (blockTime > 0) { auto locked_chain = wallet.chain().lock(); LockAssertion lock(::cs_main); - auto inserted = mapBlockIndex.emplace(GetRandHash(), new CBlockIndex); + auto inserted = ::BlockIndex().emplace(GetRandHash(), new CBlockIndex); assert(inserted.second); const uint256& hash = inserted.first->first; block = inserted.first->second; @@ -361,17 +361,16 @@ public: CWalletTx& AddTx(CRecipient recipient) { CTransactionRef tx; - CReserveKey reservekey(wallet.get()); CAmount fee; int changePos = -1; std::string error; CCoinControl dummy; { auto locked_chain = m_chain->lock(); - BOOST_CHECK(wallet->CreateTransaction(*locked_chain, {recipient}, tx, reservekey, fee, changePos, error, dummy)); + BOOST_CHECK(wallet->CreateTransaction(*locked_chain, {recipient}, tx, fee, changePos, error, dummy)); } CValidationState state; - BOOST_CHECK(wallet->CommitTransaction(tx, {}, {}, reservekey, state)); + BOOST_CHECK(wallet->CommitTransaction(tx, {}, {}, state)); CMutableTransaction blocktx; { LOCK(wallet->cs_wallet); @@ -464,8 +463,9 @@ BOOST_FIXTURE_TEST_CASE(wallet_disableprivkeys, TestChain100Setup) wallet->SetMinVersion(FEATURE_LATEST); wallet->SetWalletFlag(WALLET_FLAG_DISABLE_PRIVATE_KEYS); BOOST_CHECK(!wallet->TopUpKeyPool(1000)); - CPubKey pubkey; - BOOST_CHECK(!wallet->GetKeyFromPool(pubkey, false)); + CTxDestination dest; + std::string error; + BOOST_CHECK(!wallet->GetNewDestination(OutputType::BECH32, "", dest, error)); } // Explicit calculation which is used to test the wallet constant @@ -488,7 +488,7 @@ static size_t CalculateNestedKeyhashInputSize(bool use_max_sig) CScript script_pubkey = CScript() << OP_HASH160 << std::vector<unsigned char>(script_id.begin(), script_id.end()) << OP_EQUAL; // Add inner-script to key store and key to watchonly - CBasicKeyStore keystore; + FillableSigningProvider keystore; keystore.AddCScript(inner_script); keystore.AddKeyPubKey(key, pubkey); diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 7f14a4a7c3..452d4f7a6a 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -13,13 +13,13 @@ #include <interfaces/wallet.h> #include <key.h> #include <key_io.h> -#include <keystore.h> #include <policy/fees.h> #include <policy/policy.h> #include <primitives/block.h> #include <primitives/transaction.h> #include <script/descriptor.h> #include <script/script.h> +#include <script/signingprovider.h> #include <util/bip32.h> #include <util/error.h> #include <util/fees.h> @@ -160,6 +160,70 @@ std::shared_ptr<CWallet> LoadWallet(interfaces::Chain& chain, const std::string& return LoadWallet(chain, WalletLocation(name), error, warning); } +std::shared_ptr<CWallet> CreateWallet(interfaces::Chain& chain, const std::string& name, std::string& error, std::string& warning, WalletCreationStatus& status, const SecureString& passphrase, uint64_t wallet_creation_flags) +{ + // Indicate that the wallet is actually supposed to be blank and not just blank to make it encrypted + bool create_blank = (wallet_creation_flags & WALLET_FLAG_BLANK_WALLET); + + // Born encrypted wallets need to be created blank first. + if (!passphrase.empty()) { + wallet_creation_flags |= WALLET_FLAG_BLANK_WALLET; + } + + // Check the wallet file location + WalletLocation location(name); + if (location.Exists()) { + error = "Wallet " + location.GetName() + " already exists."; + status = WalletCreationStatus::CREATION_FAILED; + return nullptr; + } + + // Wallet::Verify will check if we're trying to create a wallet with a duplicate name. + std::string wallet_error; + if (!CWallet::Verify(chain, location, false, wallet_error, warning)) { + error = "Wallet file verification failed: " + wallet_error; + status = WalletCreationStatus::CREATION_FAILED; + return nullptr; + } + + // Make the wallet + std::shared_ptr<CWallet> wallet = CWallet::CreateWalletFromFile(chain, location, wallet_creation_flags); + if (!wallet) { + error = "Wallet creation failed"; + status = WalletCreationStatus::CREATION_FAILED; + return nullptr; + } + + // Encrypt the wallet + if (!passphrase.empty() && !(wallet_creation_flags & WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { + if (!wallet->EncryptWallet(passphrase)) { + error = "Error: Wallet created but failed to encrypt."; + status = WalletCreationStatus::ENCRYPTION_FAILED; + return nullptr; + } + if (!create_blank) { + // Unlock the wallet + if (!wallet->Unlock(passphrase)) { + error = "Error: Wallet was encrypted but could not be unlocked"; + status = WalletCreationStatus::ENCRYPTION_FAILED; + return nullptr; + } + + // Set a seed for the wallet + CPubKey master_pub_key = wallet->GenerateNewSeed(); + wallet->SetHDSeed(master_pub_key); + wallet->NewKeyPool(); + + // Relock the wallet + wallet->Lock(); + } + } + AddWallet(wallet); + wallet->postInitProcess(); + status = WalletCreationStatus::SUCCESS; + return wallet; +} + const uint32_t BIP32_HARDENED_KEY_LIMIT = 0x80000000; const uint256 CMerkleTx::ABANDON_HASH(uint256S("0000000000000000000000000000000000000000000000000000000000000001")); @@ -294,14 +358,14 @@ bool CWallet::AddKeyPubKeyWithDB(WalletBatch& batch, const CKey& secret, const C // Make sure we aren't adding private keys to private key disabled wallets assert(!IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)); - // CCryptoKeyStore has no concept of wallet databases, but calls AddCryptedKey + // FillableSigningProvider has no concept of wallet databases, but calls AddCryptedKey // which is overridden below. To avoid flushes, the database handle is // tunneled through to it. bool needsDB = !encrypted_batch; if (needsDB) { encrypted_batch = &batch; } - if (!CCryptoKeyStore::AddKeyPubKey(secret, pubkey)) { + if (!AddKeyPubKeyInner(secret, pubkey)) { if (needsDB) encrypted_batch = nullptr; return false; } @@ -336,7 +400,7 @@ bool CWallet::AddKeyPubKey(const CKey& secret, const CPubKey &pubkey) bool CWallet::AddCryptedKey(const CPubKey &vchPubKey, const std::vector<unsigned char> &vchCryptedSecret) { - if (!CCryptoKeyStore::AddCryptedKey(vchPubKey, vchCryptedSecret)) + if (!AddCryptedKeyInner(vchPubKey, vchCryptedSecret)) return false; { LOCK(cs_wallet); @@ -404,7 +468,7 @@ void CWallet::UpgradeKeyMetadata() bool CWallet::LoadCryptedKey(const CPubKey &vchPubKey, const std::vector<unsigned char> &vchCryptedSecret) { - return CCryptoKeyStore::AddCryptedKey(vchPubKey, vchCryptedSecret); + return AddCryptedKeyInner(vchPubKey, vchCryptedSecret); } /** @@ -431,7 +495,7 @@ bool CWallet::AddCScript(const CScript& redeemScript) bool CWallet::AddCScriptWithDB(WalletBatch& batch, const CScript& redeemScript) { - if (!CCryptoKeyStore::AddCScript(redeemScript)) + if (!FillableSigningProvider::AddCScript(redeemScript)) return false; if (batch.WriteCScript(Hash160(redeemScript), redeemScript)) { UnsetWalletFlagWithDB(batch, WALLET_FLAG_BLANK_WALLET); @@ -452,12 +516,40 @@ bool CWallet::LoadCScript(const CScript& redeemScript) return true; } - return CCryptoKeyStore::AddCScript(redeemScript); + return FillableSigningProvider::AddCScript(redeemScript); +} + +static bool ExtractPubKey(const CScript &dest, CPubKey& pubKeyOut) +{ + //TODO: Use Solver to extract this? + CScript::const_iterator pc = dest.begin(); + opcodetype opcode; + std::vector<unsigned char> vch; + if (!dest.GetOp(pc, opcode, vch) || !CPubKey::ValidSize(vch)) + return false; + pubKeyOut = CPubKey(vch); + if (!pubKeyOut.IsFullyValid()) + return false; + if (!dest.GetOp(pc, opcode, vch) || opcode != OP_CHECKSIG || dest.GetOp(pc, opcode, vch)) + return false; + return true; +} + +bool CWallet::AddWatchOnlyInMem(const CScript &dest) +{ + LOCK(cs_KeyStore); + setWatchOnly.insert(dest); + CPubKey pubKey; + if (ExtractPubKey(dest, pubKey)) { + mapWatchKeys[pubKey.GetID()] = pubKey; + ImplicitlyLearnRelatedKeyScripts(pubKey); + } + return true; } bool CWallet::AddWatchOnlyWithDB(WalletBatch &batch, const CScript& dest) { - if (!CCryptoKeyStore::AddWatchOnly(dest)) + if (!AddWatchOnlyInMem(dest)) return false; const CKeyMetadata& meta = m_script_metadata[CScriptID(dest)]; UpdateTimeFirstKey(meta.nCreateTime); @@ -490,8 +582,17 @@ bool CWallet::AddWatchOnly(const CScript& dest, int64_t nCreateTime) bool CWallet::RemoveWatchOnly(const CScript &dest) { AssertLockHeld(cs_wallet); - if (!CCryptoKeyStore::RemoveWatchOnly(dest)) - return false; + { + LOCK(cs_KeyStore); + setWatchOnly.erase(dest); + CPubKey pubKey; + if (ExtractPubKey(dest, pubKey)) { + mapWatchKeys.erase(pubKey.GetID()); + } + // Related CScripts are not removed; having superfluous scripts around is + // harmless (see comment in ImplicitlyLearnRelatedKeyScripts). + } + if (!HaveWatchOnly()) NotifyWatchonlyChanged(false); if (!WalletBatch(*database).EraseWatchOnly(dest)) @@ -502,7 +603,19 @@ bool CWallet::RemoveWatchOnly(const CScript &dest) bool CWallet::LoadWatchOnly(const CScript &dest) { - return CCryptoKeyStore::AddWatchOnly(dest); + return AddWatchOnlyInMem(dest); +} + +bool CWallet::HaveWatchOnly(const CScript &dest) const +{ + LOCK(cs_KeyStore); + return setWatchOnly.count(dest) > 0; +} + +bool CWallet::HaveWatchOnly() const +{ + LOCK(cs_KeyStore); + return (!setWatchOnly.empty()); } bool CWallet::Unlock(const SecureString& strWalletPassphrase, bool accept_no_keys) @@ -518,7 +631,7 @@ bool CWallet::Unlock(const SecureString& strWalletPassphrase, bool accept_no_key return false; if (!crypter.Decrypt(pMasterKey.second.vchCryptedKey, _vMasterKey)) continue; // try another master key - if (CCryptoKeyStore::Unlock(_vMasterKey, accept_no_keys)) { + if (Unlock(_vMasterKey, accept_no_keys)) { // Now that we've unlocked, upgrade the key metadata UpgradeKeyMetadata(); return true; @@ -544,7 +657,7 @@ bool CWallet::ChangeWalletPassphrase(const SecureString& strOldWalletPassphrase, return false; if (!crypter.Decrypt(pMasterKey.second.vchCryptedKey, _vMasterKey)) return false; - if (CCryptoKeyStore::Unlock(_vMasterKey)) + if (Unlock(_vMasterKey)) { int64_t nStartTime = GetTimeMillis(); crypter.SetKeyFromPassphrase(strNewWalletPassphrase, pMasterKey.second.vchSalt, pMasterKey.second.nDeriveIterations, pMasterKey.second.nDerivationMethod); @@ -2666,17 +2779,13 @@ bool CWallet::FundTransaction(CMutableTransaction& tx, CAmount& nFeeRet, int& nC auto locked_chain = chain().lock(); LOCK(cs_wallet); - CReserveKey reservekey(this); CTransactionRef tx_new; - if (!CreateTransaction(*locked_chain, vecSend, tx_new, reservekey, nFeeRet, nChangePosInOut, strFailReason, coinControl, false)) { + if (!CreateTransaction(*locked_chain, vecSend, tx_new, nFeeRet, nChangePosInOut, strFailReason, coinControl, false)) { return false; } if (nChangePosInOut != -1) { tx.vout.insert(tx.vout.begin() + nChangePosInOut, tx_new->vout[nChangePosInOut]); - // We don't have the normal Create/Commit cycle, and don't want to risk - // reusing change, so just remove the key from the keypool here. - reservekey.KeepKey(); } // Copy output sizes from new transaction; they may have had the fee @@ -2696,11 +2805,6 @@ bool CWallet::FundTransaction(CMutableTransaction& tx, CAmount& nFeeRet, int& nC } } - if (nFeeRet > this->m_default_max_tx_fee) { - strFailReason = TransactionErrorString(TransactionError::MAX_FEE_EXCEEDED); - return false; - } - return true; } @@ -2792,10 +2896,11 @@ OutputType CWallet::TransactionChangeType(OutputType change_type, const std::vec return m_default_address_type; } -bool CWallet::CreateTransaction(interfaces::Chain::Lock& locked_chain, const std::vector<CRecipient>& vecSend, CTransactionRef& tx, CReserveKey& reservekey, CAmount& nFeeRet, +bool CWallet::CreateTransaction(interfaces::Chain::Lock& locked_chain, const std::vector<CRecipient>& vecSend, CTransactionRef& tx, CAmount& nFeeRet, int& nChangePosInOut, std::string& strFailReason, const CCoinControl& coin_control, bool sign) { CAmount nValue = 0; + ReserveDestination reservedest(this); int nChangePosRequest = nChangePosInOut; unsigned int nSubtractFeeFromAmount = 0; for (const auto& recipient : vecSend) @@ -2833,7 +2938,7 @@ bool CWallet::CreateTransaction(interfaces::Chain::Lock& locked_chain, const std CoinSelectionParams coin_selection_params; // Parameters for coin selection, init with dummy // Create change script that will be used if we need change - // TODO: pass in scriptChange instead of reservekey so + // TODO: pass in scriptChange instead of reservedest so // change transaction isn't always pay-to-bitcoin-address CScript scriptChange; @@ -2853,19 +2958,16 @@ bool CWallet::CreateTransaction(interfaces::Chain::Lock& locked_chain, const std strFailReason = _("Can't generate a change-address key. No keys in the internal keypool and can't generate any keys."); return false; } - CPubKey vchPubKey; - bool ret; - ret = reservekey.GetReservedKey(vchPubKey, true); + CTxDestination dest; + const OutputType change_type = TransactionChangeType(coin_control.m_change_type ? *coin_control.m_change_type : m_default_change_type, vecSend); + bool ret = reservedest.GetReservedDestination(change_type, dest, true); if (!ret) { - strFailReason = _("Keypool ran out, please call keypoolrefill first"); + strFailReason = "Keypool ran out, please call keypoolrefill first"; return false; } - const OutputType change_type = TransactionChangeType(coin_control.m_change_type ? *coin_control.m_change_type : m_default_change_type, vecSend); - - LearnRelatedScripts(vchPubKey, change_type); - scriptChange = GetScriptForDestination(GetDestinationForKey(vchPubKey, change_type)); + scriptChange = GetScriptForDestination(dest); } CTxOut change_prototype_txout(0, scriptChange); coin_selection_params.change_output_size = GetSerializeSize(change_prototype_txout); @@ -3013,14 +3115,6 @@ bool CWallet::CreateTransaction(interfaces::Chain::Lock& locked_chain, const std return false; } - // If we made it here and we aren't even able to meet the relay fee on the next pass, give up - // because we must be at the maximum allowed fee. - if (nFeeNeeded < chain().relayMinFee().GetFee(nBytes)) - { - strFailReason = _("Transaction too large for fee policy"); - return false; - } - if (nFeeRet >= nFeeNeeded) { // Reduce fee to only the needed amount if possible. This // prevents potential overpayment in fees if the coins @@ -3086,8 +3180,6 @@ bool CWallet::CreateTransaction(interfaces::Chain::Lock& locked_chain, const std } } - if (nChangePosInOut == -1) reservekey.ReturnKey(); // Return any reserved key if we don't have change - // Shuffle selected coins and fill in final vin txNew.vin.clear(); std::vector<CInputCoin> selected_coins(setCoins.begin(), setCoins.end()); @@ -3137,6 +3229,11 @@ bool CWallet::CreateTransaction(interfaces::Chain::Lock& locked_chain, const std } } + if (nFeeRet > m_default_max_tx_fee) { + strFailReason = TransactionErrorString(TransactionError::MAX_FEE_EXCEEDED); + return false; + } + if (gArgs.GetBoolArg("-walletrejectlongchains", DEFAULT_WALLET_REJECT_LONG_CHAINS)) { // Lastly, ensure this tx will pass the mempool's chain limits if (!chain().checkChainLimits(tx)) { @@ -3145,6 +3242,10 @@ bool CWallet::CreateTransaction(interfaces::Chain::Lock& locked_chain, const std } } + // Before we return success, we assume any change key will be used to prevent + // accidental re-use. + reservedest.KeepDestination(); + WalletLogPrintf("Fee Calculation: Fee:%d Bytes:%u Needed:%d Tgt:%d (requested %d) Reason:\"%s\" Decay %.5f: Estimation: (%g - %g) %.2f%% %.1f/(%.1f %d mem %.1f out) Fail: (%g - %g) %.2f%% %.1f/(%.1f %d mem %.1f out)\n", nFeeRet, nBytes, nFeeNeeded, feeCalc.returnedTarget, feeCalc.desiredTarget, StringForFeeReason(feeCalc.reason), feeCalc.est.decay, feeCalc.est.pass.start, feeCalc.est.pass.end, @@ -3159,7 +3260,7 @@ bool CWallet::CreateTransaction(interfaces::Chain::Lock& locked_chain, const std /** * Call after CreateTransaction unless you want to abort */ -bool CWallet::CommitTransaction(CTransactionRef tx, mapValue_t mapValue, std::vector<std::pair<std::string, std::string>> orderForm, CReserveKey& reservekey, CValidationState& state) +bool CWallet::CommitTransaction(CTransactionRef tx, mapValue_t mapValue, std::vector<std::pair<std::string, std::string>> orderForm, CValidationState& state) { { auto locked_chain = chain().lock(); @@ -3173,8 +3274,6 @@ bool CWallet::CommitTransaction(CTransactionRef tx, mapValue_t mapValue, std::ve WalletLogPrintf("CommitTransaction:\n%s", wtxNew.tx->ToString()); /* Continued */ { - // Take key pair from key pool so it won't be used again - reservekey.KeepKey(); // Add tx to wallet, because if it has change it's also ours, // otherwise just for transaction history. @@ -3425,8 +3524,7 @@ bool CWallet::TopUpKeyPool(unsigned int kpSize) { LOCK(cs_wallet); - if (IsLocked()) - return false; + if (IsLocked()) return false; // Top up key pool unsigned int nTargetSize; @@ -3487,8 +3585,7 @@ bool CWallet::ReserveKeyFromKeyPool(int64_t& nIndex, CKeyPool& keypool, bool fRe { LOCK(cs_wallet); - if (!IsLocked()) - TopUpKeyPool(); + TopUpKeyPool(); bool fReturningInternal = fRequestedInternal; fReturningInternal &= (IsHDEnabled() && CanSupportFeature(FEATURE_HD_SPLIT)) || IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS); @@ -3575,6 +3672,42 @@ bool CWallet::GetKeyFromPool(CPubKey& result, bool internal) return true; } +bool CWallet::GetNewDestination(const OutputType type, const std::string label, CTxDestination& dest, std::string& error) +{ + LOCK(cs_wallet); + error.clear(); + + TopUpKeyPool(); + + // Generate a new key that is added to wallet + CPubKey new_key; + if (!GetKeyFromPool(new_key)) { + error = "Error: Keypool ran out, please call keypoolrefill first"; + return false; + } + LearnRelatedScripts(new_key, type); + dest = GetDestinationForKey(new_key, type); + + SetAddressBook(dest, label, "receive"); + return true; +} + +bool CWallet::GetNewChangeDestination(const OutputType type, CTxDestination& dest, std::string& error) +{ + error.clear(); + + TopUpKeyPool(); + + ReserveDestination reservedest(this); + if (!reservedest.GetReservedDestination(type, dest, true)) { + error = "Error: Keypool ran out, please call keypoolrefill first"; + return false; + } + + reservedest.KeepDestination(); + return true; +} + static int64_t GetOldestKeyTimeInPool(const std::set<int64_t>& setKeyPool, WalletBatch& batch) { if (setKeyPool.empty()) { return GetTime(); @@ -3754,7 +3887,7 @@ std::set<CTxDestination> CWallet::GetLabelAddresses(const std::string& label) co return result; } -bool CReserveKey::GetReservedKey(CPubKey& pubkey, bool internal) +bool ReserveDestination::GetReservedDestination(const OutputType type, CTxDestination& dest, bool internal) { if (!pwallet->CanGetAddresses(internal)) { return false; @@ -3770,25 +3903,29 @@ bool CReserveKey::GetReservedKey(CPubKey& pubkey, bool internal) fInternal = keypool.fInternal; } assert(vchPubKey.IsValid()); - pubkey = vchPubKey; + pwallet->LearnRelatedScripts(vchPubKey, type); + address = GetDestinationForKey(vchPubKey, type); + dest = address; return true; } -void CReserveKey::KeepKey() +void ReserveDestination::KeepDestination() { if (nIndex != -1) pwallet->KeepKey(nIndex); nIndex = -1; vchPubKey = CPubKey(); + address = CNoDestination(); } -void CReserveKey::ReturnKey() +void ReserveDestination::ReturnDestination() { if (nIndex != -1) { pwallet->ReturnKey(nIndex, fInternal, vchPubKey); } nIndex = -1; vchPubKey = CPubKey(); + address = CNoDestination(); } void CWallet::MarkReserveKeysAsUsed(int64_t keypool_id) @@ -4590,3 +4727,203 @@ bool CWallet::AddKeyOriginWithDB(WalletBatch& batch, const CPubKey& pubkey, cons mapKeyMetadata[pubkey.GetID()].hdKeypath = WriteHDKeypath(info.path); return batch.WriteKeyMetadata(mapKeyMetadata[pubkey.GetID()], pubkey, true); } + +bool CWallet::SetCrypted() +{ + LOCK(cs_KeyStore); + if (fUseCrypto) + return true; + if (!mapKeys.empty()) + return false; + fUseCrypto = true; + return true; +} + +bool CWallet::IsLocked() const +{ + if (!IsCrypted()) { + return false; + } + LOCK(cs_KeyStore); + return vMasterKey.empty(); +} + +bool CWallet::Lock() +{ + if (!SetCrypted()) + return false; + + { + LOCK(cs_KeyStore); + vMasterKey.clear(); + } + + NotifyStatusChanged(this); + return true; +} + +bool CWallet::Unlock(const CKeyingMaterial& vMasterKeyIn, bool accept_no_keys) +{ + { + LOCK(cs_KeyStore); + if (!SetCrypted()) + return false; + + bool keyPass = mapCryptedKeys.empty(); // Always pass when there are no encrypted keys + bool keyFail = false; + CryptedKeyMap::const_iterator mi = mapCryptedKeys.begin(); + for (; mi != mapCryptedKeys.end(); ++mi) + { + const CPubKey &vchPubKey = (*mi).second.first; + const std::vector<unsigned char> &vchCryptedSecret = (*mi).second.second; + CKey key; + if (!DecryptKey(vMasterKeyIn, vchCryptedSecret, vchPubKey, key)) + { + keyFail = true; + break; + } + keyPass = true; + if (fDecryptionThoroughlyChecked) + break; + } + if (keyPass && keyFail) + { + LogPrintf("The wallet is probably corrupted: Some keys decrypt but not all.\n"); + throw std::runtime_error("Error unlocking wallet: some keys decrypt but not all. Your wallet file may be corrupt."); + } + if (keyFail || (!keyPass && !accept_no_keys)) + return false; + vMasterKey = vMasterKeyIn; + fDecryptionThoroughlyChecked = true; + } + NotifyStatusChanged(this); + return true; +} + +bool CWallet::HaveKey(const CKeyID &address) const +{ + LOCK(cs_KeyStore); + if (!IsCrypted()) { + return FillableSigningProvider::HaveKey(address); + } + return mapCryptedKeys.count(address) > 0; +} + +bool CWallet::GetKey(const CKeyID &address, CKey& keyOut) const +{ + LOCK(cs_KeyStore); + if (!IsCrypted()) { + return FillableSigningProvider::GetKey(address, keyOut); + } + + CryptedKeyMap::const_iterator mi = mapCryptedKeys.find(address); + if (mi != mapCryptedKeys.end()) + { + const CPubKey &vchPubKey = (*mi).second.first; + const std::vector<unsigned char> &vchCryptedSecret = (*mi).second.second; + return DecryptKey(vMasterKey, vchCryptedSecret, vchPubKey, keyOut); + } + return false; +} + +bool CWallet::GetWatchPubKey(const CKeyID &address, CPubKey &pubkey_out) const +{ + LOCK(cs_KeyStore); + WatchKeyMap::const_iterator it = mapWatchKeys.find(address); + if (it != mapWatchKeys.end()) { + pubkey_out = it->second; + return true; + } + return false; +} + +bool CWallet::GetPubKey(const CKeyID &address, CPubKey& vchPubKeyOut) const +{ + LOCK(cs_KeyStore); + if (!IsCrypted()) { + if (!FillableSigningProvider::GetPubKey(address, vchPubKeyOut)) { + return GetWatchPubKey(address, vchPubKeyOut); + } + return true; + } + + CryptedKeyMap::const_iterator mi = mapCryptedKeys.find(address); + if (mi != mapCryptedKeys.end()) + { + vchPubKeyOut = (*mi).second.first; + return true; + } + // Check for watch-only pubkeys + return GetWatchPubKey(address, vchPubKeyOut); +} + +std::set<CKeyID> CWallet::GetKeys() const +{ + LOCK(cs_KeyStore); + if (!IsCrypted()) { + return FillableSigningProvider::GetKeys(); + } + std::set<CKeyID> set_address; + for (const auto& mi : mapCryptedKeys) { + set_address.insert(mi.first); + } + return set_address; +} + +bool CWallet::EncryptKeys(CKeyingMaterial& vMasterKeyIn) +{ + LOCK(cs_KeyStore); + if (!mapCryptedKeys.empty() || IsCrypted()) + return false; + + fUseCrypto = true; + for (const KeyMap::value_type& mKey : mapKeys) + { + const CKey &key = mKey.second; + CPubKey vchPubKey = key.GetPubKey(); + CKeyingMaterial vchSecret(key.begin(), key.end()); + std::vector<unsigned char> vchCryptedSecret; + if (!EncryptSecret(vMasterKeyIn, vchSecret, vchPubKey.GetHash(), vchCryptedSecret)) + return false; + if (!AddCryptedKey(vchPubKey, vchCryptedSecret)) + return false; + } + mapKeys.clear(); + return true; +} + +bool CWallet::AddKeyPubKeyInner(const CKey& key, const CPubKey &pubkey) +{ + LOCK(cs_KeyStore); + if (!IsCrypted()) { + return FillableSigningProvider::AddKeyPubKey(key, pubkey); + } + + if (IsLocked()) { + return false; + } + + std::vector<unsigned char> vchCryptedSecret; + CKeyingMaterial vchSecret(key.begin(), key.end()); + if (!EncryptSecret(vMasterKey, vchSecret, pubkey.GetHash(), vchCryptedSecret)) { + return false; + } + + if (!AddCryptedKey(pubkey, vchCryptedSecret)) { + return false; + } + return true; +} + + +bool CWallet::AddCryptedKeyInner(const CPubKey &vchPubKey, const std::vector<unsigned char> &vchCryptedSecret) +{ + LOCK(cs_KeyStore); + if (!SetCrypted()) { + return false; + } + + mapCryptedKeys[vchPubKey.GetID()] = make_pair(vchPubKey, vchCryptedSecret); + ImplicitlyLearnRelatedKeyScripts(vchPubKey); + return true; +} diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 7b5465c219..6a7097bf44 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -35,6 +35,8 @@ #include <utility> #include <vector> +#include <boost/signals2/signal.hpp> + //! Explicitly unload and delete the wallet. //! Blocks the current thread after signaling the unload intent so that all //! wallet clients release the wallet. @@ -49,6 +51,14 @@ std::vector<std::shared_ptr<CWallet>> GetWallets(); std::shared_ptr<CWallet> GetWallet(const std::string& name); std::shared_ptr<CWallet> LoadWallet(interfaces::Chain& chain, const WalletLocation& location, std::string& error, std::string& warning); +enum WalletCreationStatus { + SUCCESS, + CREATION_FAILED, + ENCRYPTION_FAILED +}; + +std::shared_ptr<CWallet> CreateWallet(interfaces::Chain& chain, const std::string& name, std::string& error, std::string& warning, WalletCreationStatus& status, const SecureString& passphrase, uint64_t wallet_creation_flags); + //! Default for -keypool static const unsigned int DEFAULT_KEYPOOL_SIZE = 1000; //! -paytxfee default @@ -85,11 +95,11 @@ static constexpr size_t DUMMY_NESTED_P2WPKH_INPUT_SIZE = 91; class CCoinControl; class COutput; -class CReserveKey; class CScript; class CWalletTx; struct FeeCalculation; enum class FeeEstimateMode; +class ReserveDestination; /** (client) version numbers for particular wallet features */ enum WalletFeature @@ -254,55 +264,57 @@ public: } }; -/** A wrapper to reserve a key from a wallet keypool +/** A wrapper to reserve an address from a wallet * - * CReserveKey is used to reserve a key from the keypool. It is passed around - * during the CreateTransaction/CommitTransaction procedure. + * ReserveDestination is used to reserve an address. + * It is currently only used inside of CreateTransaction. * - * Instantiating a CReserveKey does not reserve a keypool key. To do so, - * GetReservedKey() needs to be called on the object. Once a key has been - * reserved, call KeepKey() on the CReserveKey object to make sure it is not - * returned to the keypool. Call ReturnKey() to return the key to the keypool - * so it can be re-used (for example, if the key was used in a new transaction + * Instantiating a ReserveDestination does not reserve an address. To do so, + * GetReservedDestination() needs to be called on the object. Once an address has been + * reserved, call KeepDestination() on the ReserveDestination object to make sure it is not + * returned. Call ReturnDestination() to return the address so it can be re-used (for + * example, if the address was used in a new transaction * and that transaction was not completed and needed to be aborted). * - * If a key is reserved and KeepKey() is not called, then the key will be - * returned to the keypool when the CReserveObject goes out of scope. + * If an address is reserved and KeepDestination() is not called, then the address will be + * returned when the ReserveDestination goes out of scope. */ -class CReserveKey +class ReserveDestination { protected: - //! The wallet to reserve the keypool key from + //! The wallet to reserve from CWallet* pwallet; - //! The index of the key in the keypool + //! The index of the address's key in the keypool int64_t nIndex{-1}; - //! The public key + //! The public key for the address CPubKey vchPubKey; + //! The destination + CTxDestination address; //! Whether this is from the internal (change output) keypool bool fInternal{false}; public: - //! Construct a CReserveKey object. This does NOT reserve a key from the keypool yet - explicit CReserveKey(CWallet* pwalletIn) + //! Construct a ReserveDestination object. This does NOT reserve an address yet + explicit ReserveDestination(CWallet* pwalletIn) { pwallet = pwalletIn; } - CReserveKey(const CReserveKey&) = delete; - CReserveKey& operator=(const CReserveKey&) = delete; + ReserveDestination(const ReserveDestination&) = delete; + ReserveDestination& operator=(const ReserveDestination&) = delete; //! Destructor. If a key has been reserved and not KeepKey'ed, it will be returned to the keypool - ~CReserveKey() + ~ReserveDestination() { - ReturnKey(); + ReturnDestination(); } - //! Reserve a key from the keypool - bool GetReservedKey(CPubKey &pubkey, bool internal = false); - //! Return a key to the keypool - void ReturnKey(); - //! Keep the key. Do not return it to the keypool when this object goes out of scope - void KeepKey(); + //! Reserve an address + bool GetReservedDestination(const OutputType type, CTxDestination& pubkey, bool internal); + //! Return reserved address + void ReturnDestination(); + //! Keep the address. Do not return it's key to the keypool when this object goes out of scope + void KeepDestination(); }; /** Address book data */ @@ -709,9 +721,35 @@ class WalletRescanReserver; //forward declarations for ScanForWalletTransactions * A CWallet is an extension of a keystore, which also maintains a set of transactions and balances, * and provides the ability to create new transactions. */ -class CWallet final : public CCryptoKeyStore, private interfaces::Chain::Notifications +class CWallet final : public FillableSigningProvider, private interfaces::Chain::Notifications { private: + CKeyingMaterial vMasterKey GUARDED_BY(cs_KeyStore); + + //! if fUseCrypto is true, mapKeys must be empty + //! if fUseCrypto is false, vMasterKey must be empty + std::atomic<bool> fUseCrypto; + + //! keeps track of whether Unlock has run a thorough check before + bool fDecryptionThoroughlyChecked; + + using CryptedKeyMap = std::map<CKeyID, std::pair<CPubKey, std::vector<unsigned char>>>; + using WatchOnlySet = std::set<CScript>; + using WatchKeyMap = std::map<CKeyID, CPubKey>; + + bool SetCrypted(); + + //! will encrypt previously unencrypted keys + bool EncryptKeys(CKeyingMaterial& vMasterKeyIn); + + bool Unlock(const CKeyingMaterial& vMasterKeyIn, bool accept_no_keys = false); + CryptedKeyMap mapCryptedKeys GUARDED_BY(cs_KeyStore); + WatchOnlySet setWatchOnly GUARDED_BY(cs_KeyStore); + WatchKeyMap mapWatchKeys GUARDED_BY(cs_KeyStore); + + bool AddCryptedKeyInner(const CPubKey &vchPubKey, const std::vector<unsigned char> &vchCryptedSecret); + bool AddKeyPubKeyInner(const CKey& key, const CPubKey &pubkey); + std::atomic<bool> fAbortRescan{false}; std::atomic<bool> fScanningWallet{false}; // controlled by WalletRescanReserver std::atomic<int64_t> m_scanning_start{0}; @@ -794,8 +832,9 @@ private: * of the other AddWatchOnly which accepts a timestamp and sets * nTimeFirstKey more intelligently for more efficient rescans. */ - bool AddWatchOnly(const CScript& dest) override EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + bool AddWatchOnly(const CScript& dest) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); bool AddWatchOnlyWithDB(WalletBatch &batch, const CScript& dest) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + bool AddWatchOnlyInMem(const CScript &dest); /** Add a KeyOriginInfo to the wallet */ bool AddKeyOriginWithDB(WalletBatch& batch, const CPubKey& pubkey, const KeyOriginInfo& info); @@ -835,6 +874,9 @@ private: */ uint256 m_last_block_processed GUARDED_BY(cs_wallet); + //! Fetches a key from the keypool + bool GetKeyFromPool(CPubKey &key, bool internal = false); + public: /* * Main wallet lock. @@ -879,7 +921,9 @@ public: /** Construct wallet with specified name and database implementation. */ CWallet(interfaces::Chain* chain, const WalletLocation& location, std::unique_ptr<WalletDatabase> database) - : m_chain(chain), + : fUseCrypto(false), + fDecryptionThoroughlyChecked(false), + m_chain(chain), m_location(location), database(std::move(database)) { @@ -893,6 +937,10 @@ public: encrypted_batch = nullptr; } + bool IsCrypted() const { return fUseCrypto; } + bool IsLocked() const; + bool Lock(); + std::map<uint256, CWalletTx> mapWallet GUARDED_BY(cs_wallet); typedef std::multimap<int64_t, CWalletTx*> TxItems; @@ -975,7 +1023,7 @@ public: //! Adds a key to the store, and saves it to disk. bool AddKeyPubKey(const CKey& key, const CPubKey &pubkey) override EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); //! Adds a key to the store, without saving it to disk (used by LoadWallet) - bool LoadKey(const CKey& key, const CPubKey &pubkey) { return CCryptoKeyStore::AddKeyPubKey(key, pubkey); } + bool LoadKey(const CKey& key, const CPubKey &pubkey) { return AddKeyPubKeyInner(key, pubkey); } //! Load metadata (used by LoadWallet) void LoadKeyMetadata(const CKeyID& keyID, const CKeyMetadata &metadata) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); void LoadScriptMetadata(const CScriptID& script_id, const CKeyMetadata &metadata) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); @@ -986,9 +1034,13 @@ public: void UpdateTimeFirstKey(int64_t nCreateTime) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); //! Adds an encrypted key to the store, and saves it to disk. - bool AddCryptedKey(const CPubKey &vchPubKey, const std::vector<unsigned char> &vchCryptedSecret) override; + bool AddCryptedKey(const CPubKey &vchPubKey, const std::vector<unsigned char> &vchCryptedSecret); //! Adds an encrypted key to the store, without saving it to disk (used by LoadWallet) bool LoadCryptedKey(const CPubKey &vchPubKey, const std::vector<unsigned char> &vchCryptedSecret); + bool GetKey(const CKeyID &address, CKey& keyOut) const override; + bool GetPubKey(const CKeyID &address, CPubKey& vchPubKeyOut) const override; + bool HaveKey(const CKeyID &address) const override; + std::set<CKeyID> GetKeys() const override; bool AddCScript(const CScript& redeemScript) override; bool LoadCScript(const CScript& redeemScript); @@ -1005,9 +1057,15 @@ public: //! Adds a watch-only address to the store, and saves it to disk. bool AddWatchOnly(const CScript& dest, int64_t nCreateTime) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); - bool RemoveWatchOnly(const CScript &dest) override EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + bool RemoveWatchOnly(const CScript &dest) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); //! Adds a watch-only address to the store, without saving it to disk (used by LoadWallet) bool LoadWatchOnly(const CScript &dest); + //! Returns whether the watch-only script is in the wallet + bool HaveWatchOnly(const CScript &dest) const; + //! Returns whether there are any watch-only things in the wallet + bool HaveWatchOnly() const; + //! Fetches a pubkey from mapWatchKeys if it exists there + bool GetWatchPubKey(const CKeyID &address, CPubKey &pubkey_out) const; //! Holds a timestamp at which point the wallet is scheduled (externally) to be relocked. Caller must arrange for actual relocking to occur via Lock(). int64_t nRelockTime = 0; @@ -1079,9 +1137,9 @@ public: * selected by SelectCoins(); Also create the change output, when needed * @note passing nChangePosInOut as -1 will result in setting a random position */ - bool CreateTransaction(interfaces::Chain::Lock& locked_chain, const std::vector<CRecipient>& vecSend, CTransactionRef& tx, CReserveKey& reservekey, CAmount& nFeeRet, int& nChangePosInOut, + bool CreateTransaction(interfaces::Chain::Lock& locked_chain, const std::vector<CRecipient>& vecSend, CTransactionRef& tx, CAmount& nFeeRet, int& nChangePosInOut, std::string& strFailReason, const CCoinControl& coin_control, bool sign = true); - bool CommitTransaction(CTransactionRef tx, mapValue_t mapValue, std::vector<std::pair<std::string, std::string>> orderForm, CReserveKey& reservekey, CValidationState& state); + bool CommitTransaction(CTransactionRef tx, mapValue_t mapValue, std::vector<std::pair<std::string, std::string>> orderForm, CValidationState& state); bool DummySignTx(CMutableTransaction &txNew, const std::set<CTxOut> &txouts, bool use_max_sig = false) const { @@ -1136,7 +1194,6 @@ public: bool ReserveKeyFromKeyPool(int64_t& nIndex, CKeyPool& keypool, bool fRequestedInternal); void KeepKey(int64_t nIndex); void ReturnKey(int64_t nIndex, bool fInternal, const CPubKey& pubkey); - bool GetKeyFromPool(CPubKey &key, bool internal = false); int64_t GetOldestKeyPoolTime(); /** * Marks all keys in the keypool up to and including reserve_key as used. @@ -1149,6 +1206,9 @@ public: std::set<CTxDestination> GetLabelAddresses(const std::string& label) const; + bool GetNewDestination(const OutputType type, const std::string label, CTxDestination& dest, std::string& error); + bool GetNewChangeDestination(const OutputType type, CTxDestination& dest, std::string& error); + isminetype IsMine(const CTxIn& txin) const; /** * Returns amount of debit if the input matches the @@ -1232,6 +1292,12 @@ public: /** Keypool has new keys */ boost::signals2::signal<void ()> NotifyCanGetAddressesChanged; + /** + * Wallet status (encrypted, locked) changed. + * Note: Called without locks held. + */ + boost::signals2::signal<void (CWallet* wallet)> NotifyStatusChanged; + /** Inquire whether this wallet broadcasts transactions. */ bool GetBroadcastTransactions() const { return fBroadcastTransactions; } /** Set whether this wallet broadcasts transactions. */ @@ -1296,7 +1362,7 @@ public: /** * Explicitly make the wallet learn the related scripts for outputs to the * given key. This is purely to make the wallet file compatible with older - * software, as CBasicKeyStore automatically does this implicitly for all + * software, as FillableSigningProvider automatically does this implicitly for all * keys now. */ void LearnRelatedScripts(const CPubKey& key, OutputType); diff --git a/src/zmq/zmqrpc.cpp b/src/zmq/zmqrpc.cpp index a34968ef7d..cf97b7ecce 100644 --- a/src/zmq/zmqrpc.cpp +++ b/src/zmq/zmqrpc.cpp @@ -15,8 +15,6 @@ namespace { UniValue getzmqnotifications(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() != 0) { - throw std::runtime_error( RPCHelpMan{"getzmqnotifications", "\nReturns information about the active ZeroMQ notifications.\n", {}, @@ -34,8 +32,7 @@ UniValue getzmqnotifications(const JSONRPCRequest& request) HelpExampleCli("getzmqnotifications", "") + HelpExampleRpc("getzmqnotifications", "") }, - }.ToString()); - } + }.Check(request); UniValue result(UniValue::VARR); if (g_zmq_notification_interface != nullptr) { diff --git a/test/README.md b/test/README.md index ecea3213ab..8f08b7afe4 100644 --- a/test/README.md +++ b/test/README.md @@ -49,6 +49,29 @@ You can run any combination (incl. duplicates) of tests by calling: test/functional/test_runner.py <testname1> <testname2> <testname3> ... ``` +Wildcard test names can be passed, if the paths are coherent and the test runner +is called from a `bash` shell or similar that does the globbing. For example, +to run all the wallet tests: + +``` +test/functional/test_runner.py test/functional/wallet* +functional/test_runner.py functional/wallet* (called from the test/ directory) +test_runner.py wallet* (called from the test/functional/ directory) +``` + +but not + +``` +test/functional/test_runner.py wallet* +``` + +Combinations of wildcards can be passed: + +``` +test/functional/test_runner.py ./test/functional/tool* test/functional/mempool* +test_runner.py tool* mempool* +``` + Run the regression test suite with: ``` diff --git a/test/functional/feature_bip68_sequence.py b/test/functional/feature_bip68_sequence.py index d38eca6cbe..f0bf09e172 100755 --- a/test/functional/feature_bip68_sequence.py +++ b/test/functional/feature_bip68_sequence.py @@ -29,7 +29,10 @@ NOT_FINAL_ERROR = "non-BIP68-final (code 64)" class BIP68Test(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 2 - self.extra_args = [[], ["-acceptnonstdtxn=0"]] + self.extra_args = [ + ["-acceptnonstdtxn=1"], + ["-acceptnonstdtxn=0"], + ] def skip_test_if_missing_module(self): self.skip_if_no_wallet() diff --git a/test/functional/feature_block.py b/test/functional/feature_block.py index 3ad83cd2b3..b5eac88ba7 100755 --- a/test/functional/feature_block.py +++ b/test/functional/feature_block.py @@ -78,7 +78,7 @@ class FullBlockTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 1 self.setup_clean_chain = True - self.extra_args = [[]] + self.extra_args = [['-acceptnonstdtxn=1']] # This is a consensus block test, we don't care about tx policy def run_test(self): node = self.nodes[0] # convenience reference to the node diff --git a/test/functional/feature_cltv.py b/test/functional/feature_cltv.py index 7712e8bdf6..af34f9f0db 100755 --- a/test/functional/feature_cltv.py +++ b/test/functional/feature_cltv.py @@ -57,7 +57,11 @@ def cltv_validate(node, tx, height): class BIP65Test(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 1 - self.extra_args = [['-whitelist=127.0.0.1', '-par=1']] # Use only one script thread to get the exact reject reason for testing + self.extra_args = [[ + '-whitelist=127.0.0.1', + '-par=1', # Use only one script thread to get the exact reject reason for testing + '-acceptnonstdtxn=1', # cltv_invalidate is nonstandard + ]] self.setup_clean_chain = True self.rpc_timeout = 120 diff --git a/test/functional/feature_config_args.py b/test/functional/feature_config_args.py index 131f52a2e5..aae262344d 100755 --- a/test/functional/feature_config_args.py +++ b/test/functional/feature_config_args.py @@ -40,6 +40,11 @@ class ConfArgsTest(BitcoinTestFramework): self.nodes[0].assert_start_raises_init_error(expected_msg='Error: Config setting for -wallet only applied on regtest network when in [regtest] section.') with open(inc_conf_file_path, 'w', encoding='utf-8') as conf: + conf.write('regtest=0\n') # mainnet + conf.write('acceptnonstdtxn=1\n') + self.nodes[0].assert_start_raises_init_error(expected_msg='Error: acceptnonstdtxn is not currently supported for main chain') + + with open(inc_conf_file_path, 'w', encoding='utf-8') as conf: conf.write('nono\n') self.nodes[0].assert_start_raises_init_error(expected_msg='Error: Error reading configuration file: parse error on line 1: nono, if you intended to specify a negated option, use nono=1 instead') diff --git a/test/functional/feature_maxuploadtarget.py b/test/functional/feature_maxuploadtarget.py index 87c318de9a..180ea0e51d 100755 --- a/test/functional/feature_maxuploadtarget.py +++ b/test/functional/feature_maxuploadtarget.py @@ -35,7 +35,7 @@ class MaxUploadTest(BitcoinTestFramework): def set_test_params(self): self.setup_clean_chain = True self.num_nodes = 1 - self.extra_args = [["-maxuploadtarget=800"]] + self.extra_args = [["-maxuploadtarget=800", "-acceptnonstdtxn=1"]] # Cache for utxos, as the listunspent may take a long time later in the test self.utxo_cache = [] diff --git a/test/functional/feature_rbf.py b/test/functional/feature_rbf.py index 1496c5d958..fd79df0b07 100755 --- a/test/functional/feature_rbf.py +++ b/test/functional/feature_rbf.py @@ -67,8 +67,8 @@ class ReplaceByFeeTest(BitcoinTestFramework): self.num_nodes = 1 self.extra_args = [ [ + "-acceptnonstdtxn=1", "-maxorphantx=1000", - "-whitelist=127.0.0.1", "-limitancestorcount=50", "-limitancestorsize=101", "-limitdescendantcount=200", diff --git a/test/functional/feature_segwit.py b/test/functional/feature_segwit.py index 2d4dd96a1d..a71d4071d5 100755 --- a/test/functional/feature_segwit.py +++ b/test/functional/feature_segwit.py @@ -53,17 +53,20 @@ class SegWitTest(BitcoinTestFramework): # This test tests SegWit both pre and post-activation, so use the normal BIP9 activation. self.extra_args = [ [ + "-acceptnonstdtxn=1", "-rpcserialversion=0", "-vbparams=segwit:0:999999999999", "-addresstype=legacy", ], [ + "-acceptnonstdtxn=1", "-blockversion=4", "-rpcserialversion=1", "-vbparams=segwit:0:999999999999", "-addresstype=legacy", ], [ + "-acceptnonstdtxn=1", "-blockversion=536870915", "-vbparams=segwit:0:999999999999", "-addresstype=legacy", diff --git a/test/functional/mempool_accept.py b/test/functional/mempool_accept.py index 2bb5d8ab7d..209a222004 100755 --- a/test/functional/mempool_accept.py +++ b/test/functional/mempool_accept.py @@ -36,7 +36,6 @@ class MempoolAcceptanceTest(BitcoinTestFramework): self.num_nodes = 1 self.extra_args = [[ '-txindex', - '-acceptnonstdtxn=0', # Try to mimic main-net ]] * self.num_nodes def skip_test_if_missing_module(self): diff --git a/test/functional/mempool_limit.py b/test/functional/mempool_limit.py index 351b27e94a..edf2069933 100755 --- a/test/functional/mempool_limit.py +++ b/test/functional/mempool_limit.py @@ -13,7 +13,11 @@ class MempoolLimitTest(BitcoinTestFramework): def set_test_params(self): self.setup_clean_chain = True self.num_nodes = 1 - self.extra_args = [["-maxmempool=5", "-spendzeroconfchange=0"]] + self.extra_args = [[ + "-acceptnonstdtxn=1", + "-maxmempool=5", + "-spendzeroconfchange=0", + ]] def skip_test_if_missing_module(self): self.skip_if_no_wallet() diff --git a/test/functional/mempool_package_onemore.py b/test/functional/mempool_package_onemore.py new file mode 100755 index 0000000000..f955c1a77f --- /dev/null +++ b/test/functional/mempool_package_onemore.py @@ -0,0 +1,86 @@ +#!/usr/bin/env python3 +# Copyright (c) 2014-2019 The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. +"""Test descendant package tracking carve-out allowing one final transaction in + an otherwise-full package as long as it has only one parent and is <= 10k in + size. +""" + +from decimal import Decimal + +from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import assert_equal, assert_raises_rpc_error, satoshi_round + +MAX_ANCESTORS = 25 +MAX_DESCENDANTS = 25 + +class MempoolPackagesTest(BitcoinTestFramework): + def set_test_params(self): + self.num_nodes = 1 + self.extra_args = [["-maxorphantx=1000"]] + + def skip_test_if_missing_module(self): + self.skip_if_no_wallet() + + # Build a transaction that spends parent_txid:vout + # Return amount sent + def chain_transaction(self, node, parent_txids, vouts, value, fee, num_outputs): + 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 i in range(num_outputs): + outputs[node.getnewaddress()] = send_value + rawtx = node.createrawtransaction(inputs, outputs) + 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) + + def run_test(self): + # Mine some blocks and have them mature. + self.nodes[0].generate(101) + utxo = self.nodes[0].listunspent(10) + txid = utxo[0]['txid'] + vout = utxo[0]['vout'] + value = utxo[0]['amount'] + + fee = Decimal("0.0002") + # MAX_ANCESTORS transactions off a confirmed tx should be fine + chain = [] + for _ in range(4): + (txid, sent_value) = self.chain_transaction(self.nodes[0], [txid], [vout], value, fee, 2) + vout = 0 + value = sent_value + chain.append([txid, value]) + for _ in range(MAX_ANCESTORS - 4): + (txid, sent_value) = self.chain_transaction(self.nodes[0], [txid], [0], value, fee, 1) + value = sent_value + chain.append([txid, value]) + (second_chain, second_chain_value) = self.chain_transaction(self.nodes[0], [utxo[1]['txid']], [utxo[1]['vout']], utxo[1]['amount'], fee, 1) + + # Check mempool has MAX_ANCESTORS + 1 transactions in it + assert_equal(len(self.nodes[0].getrawmempool(True)), MAX_ANCESTORS + 1) + + # Adding one more transaction on to the chain should fail. + assert_raises_rpc_error(-26, "too-long-mempool-chain", self.chain_transaction, self.nodes[0], [txid], [0], value, fee, 1) + # ...even if it chains on from some point in the middle of the chain. + assert_raises_rpc_error(-26, "too-long-mempool-chain", self.chain_transaction, self.nodes[0], [chain[2][0]], [1], chain[2][1], fee, 1) + assert_raises_rpc_error(-26, "too-long-mempool-chain", self.chain_transaction, self.nodes[0], [chain[1][0]], [1], chain[1][1], fee, 1) + # ...even if it chains on to two parent transactions with one in the chain. + assert_raises_rpc_error(-26, "too-long-mempool-chain", self.chain_transaction, self.nodes[0], [chain[0][0], second_chain], [1, 0], chain[0][1] + second_chain_value, fee, 1) + # ...especially if its > 40k weight + assert_raises_rpc_error(-26, "too-long-mempool-chain", self.chain_transaction, self.nodes[0], [chain[0][0]], [1], chain[0][1], fee, 350) + # But not if it chains directly off the first transaction + self.chain_transaction(self.nodes[0], [chain[0][0]], [1], chain[0][1], fee, 1) + # and the second chain should work just fine + self.chain_transaction(self.nodes[0], [second_chain], [0], second_chain_value, fee, 1) + + # Finally, check that we added two transactions + assert_equal(len(self.nodes[0].getrawmempool(True)), MAX_ANCESTORS + 3) + +if __name__ == '__main__': + MempoolPackagesTest().main() diff --git a/test/functional/mining_prioritisetransaction.py b/test/functional/mining_prioritisetransaction.py index b0a069be81..7e05a8e6c8 100755 --- a/test/functional/mining_prioritisetransaction.py +++ b/test/functional/mining_prioritisetransaction.py @@ -14,7 +14,10 @@ class PrioritiseTransactionTest(BitcoinTestFramework): def set_test_params(self): self.setup_clean_chain = True self.num_nodes = 2 - self.extra_args = [["-printpriority=1"], ["-printpriority=1"]] + self.extra_args = [[ + "-printpriority=1", + "-acceptnonstdtxn=1", + ]] * self.num_nodes def skip_test_if_missing_module(self): self.skip_if_no_wallet() diff --git a/test/functional/p2p_compactblocks.py b/test/functional/p2p_compactblocks.py index 0994857912..eb3336bd3b 100755 --- a/test/functional/p2p_compactblocks.py +++ b/test/functional/p2p_compactblocks.py @@ -95,6 +95,9 @@ class CompactBlocksTest(BitcoinTestFramework): def set_test_params(self): self.setup_clean_chain = True self.num_nodes = 1 + self.extra_args = [[ + "-acceptnonstdtxn=1", + ]] self.utxos = [] def skip_test_if_missing_module(self): diff --git a/test/functional/p2p_invalid_tx.py b/test/functional/p2p_invalid_tx.py index 1b18dd3e58..3cca2d78db 100755 --- a/test/functional/p2p_invalid_tx.py +++ b/test/functional/p2p_invalid_tx.py @@ -25,6 +25,9 @@ from data import invalid_txs class InvalidTxRequestTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 1 + self.extra_args = [[ + "-acceptnonstdtxn=1", + ]] self.setup_clean_chain = True def bootstrap_p2p(self, *, num_connections=1): diff --git a/test/functional/p2p_node_network_limited.py b/test/functional/p2p_node_network_limited.py index 573d5f5a5f..a4650df8ee 100755 --- a/test/functional/p2p_node_network_limited.py +++ b/test/functional/p2p_node_network_limited.py @@ -8,7 +8,7 @@ Tests that a node configured with -prune=550 signals NODE_NETWORK_LIMITED correc and that it responds to getdata requests for blocks correctly: - send a block within 288 + 2 of the tip - disconnect peers who request blocks older than that.""" -from test_framework.messages import CInv, msg_getdata, msg_verack, NODE_BLOOM, NODE_NETWORK_LIMITED, NODE_WITNESS +from test_framework.messages import CInv, msg_getdata, msg_verack, NODE_NETWORK_LIMITED, NODE_WITNESS from test_framework.mininode import P2PInterface, mininode_lock from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( @@ -55,7 +55,7 @@ class NodeNetworkLimitedTest(BitcoinTestFramework): def run_test(self): node = self.nodes[0].add_p2p_connection(P2PIgnoreInv()) - expected_services = NODE_BLOOM | NODE_WITNESS | NODE_NETWORK_LIMITED + expected_services = NODE_WITNESS | NODE_NETWORK_LIMITED self.log.info("Check that node has signalled expected services.") assert_equal(node.nServices, expected_services) @@ -83,7 +83,7 @@ class NodeNetworkLimitedTest(BitcoinTestFramework): node1.wait_for_addr() #must relay address with NODE_NETWORK_LIMITED - assert_equal(node1.firstAddrnServices, 1036) + assert_equal(node1.firstAddrnServices, expected_services) self.nodes[0].disconnect_p2ps() node1.wait_for_disconnect() diff --git a/test/functional/p2p_segwit.py b/test/functional/p2p_segwit.py index b7fa42f593..dca71aec43 100755 --- a/test/functional/p2p_segwit.py +++ b/test/functional/p2p_segwit.py @@ -184,7 +184,11 @@ class SegWitTest(BitcoinTestFramework): self.setup_clean_chain = True self.num_nodes = 3 # This test tests SegWit both pre and post-activation, so use the normal BIP9 activation. - self.extra_args = [["-whitelist=127.0.0.1", "-vbparams=segwit:0:999999999999"], ["-whitelist=127.0.0.1", "-acceptnonstdtxn=0", "-vbparams=segwit:0:999999999999"], ["-whitelist=127.0.0.1", "-vbparams=segwit:0:0"]] + self.extra_args = [ + ["-whitelist=127.0.0.1", "-acceptnonstdtxn=1", "-vbparams=segwit:0:999999999999"], + ["-whitelist=127.0.0.1", "-acceptnonstdtxn=0", "-vbparams=segwit:0:999999999999"], + ["-whitelist=127.0.0.1", "-acceptnonstdtxn=1", "-vbparams=segwit:0:0"], + ] def skip_test_if_missing_module(self): self.skip_if_no_wallet() diff --git a/test/functional/rpc_psbt.py b/test/functional/rpc_psbt.py index dc113da530..b3d8696208 100755 --- a/test/functional/rpc_psbt.py +++ b/test/functional/rpc_psbt.py @@ -136,7 +136,7 @@ class PSBTTest(BitcoinTestFramework): assert_greater_than(0.06, res["fee"]) # feeRate of 10 BTC / KB produces a total fee well above -maxtxfee - # previously this was silenty capped at -maxtxfee + # previously this was silently capped at -maxtxfee assert_raises_rpc_error(-4, "Fee exceeds maximum configured by -maxtxfee", self.nodes[1].walletcreatefundedpsbt, [{"txid":txid,"vout":p2wpkh_pos},{"txid":txid,"vout":p2sh_p2wpkh_pos},{"txid":txid,"vout":p2pkh_pos}], {self.nodes[1].getnewaddress():29.99}, 0, {"feeRate": 10}) # partially sign multisig things with node 1 diff --git a/test/functional/rpc_users.py b/test/functional/rpc_users.py index 102dd22594..8bbb3c04fa 100755 --- a/test/functional/rpc_users.py +++ b/test/functional/rpc_users.py @@ -20,6 +20,17 @@ import string import configparser import sys +def call_with_auth(node, user, password): + url = urllib.parse.urlparse(node.url) + headers = {"Authorization": "Basic " + str_to_b64str('{}:{}'.format(user, password))} + + conn = http.client.HTTPConnection(url.hostname, url.port) + conn.connect() + conn.request('POST', '/', '{"method": "getbestblockhash"}', headers) + resp = conn.getresponse() + conn.close() + return resp + class HTTPBasicsTest(BitcoinTestFramework): def set_test_params(self): @@ -28,15 +39,24 @@ class HTTPBasicsTest(BitcoinTestFramework): def setup_chain(self): super().setup_chain() #Append rpcauth to bitcoin.conf before initialization + self.rtpassword = "cA773lm788buwYe4g4WT+05pKyNruVKjQ25x3n0DQcM=" rpcauth = "rpcauth=rt:93648e835a54c573682c2eb19f882535$7681e9c5b74bdd85e78166031d2058e1069b3ed7ed967c93fc63abba06f31144" - rpcauth2 = "rpcauth=rt2:f8607b1a88861fac29dfccf9b52ff9f$ff36a0c23c8c62b4846112e50fa888416e94c17bfd4c42f88fd8f55ec6a3137e" - rpcuser = "rpcuser=rpcuser💻" - rpcpassword = "rpcpassword=rpcpassword🔑" - self.user = ''.join(SystemRandom().choice(string.ascii_letters + string.digits) for _ in range(10)) + self.rpcuser = "rpcuser💻" + self.rpcpassword = "rpcpassword🔑" + config = configparser.ConfigParser() config.read_file(open(self.options.configfile)) gen_rpcauth = config['environment']['RPCAUTH'] + + # 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) + 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) lines = p.stdout.read().splitlines() rpcauth3 = lines[1] @@ -47,160 +67,40 @@ class HTTPBasicsTest(BitcoinTestFramework): f.write(rpcauth2+"\n") f.write(rpcauth3+"\n") with open(os.path.join(get_datadir_path(self.options.tmpdir, 1), "bitcoin.conf"), 'a', encoding='utf8') as f: - f.write(rpcuser+"\n") - f.write(rpcpassword+"\n") - - def run_test(self): - - ################################################## - # Check correctness of the rpcauth config option # - ################################################## - url = urllib.parse.urlparse(self.nodes[0].url) - - #Old authpair - authpair = url.username + ':' + url.password - - #New authpair generated via share/rpcauth tool - password = "cA773lm788buwYe4g4WT+05pKyNruVKjQ25x3n0DQcM=" - - #Second authpair with different username - password2 = "8/F3uMDw4KSEbw96U3CA1C4X05dkHDN2BPFjTgZW4KI=" - authpairnew = "rt:"+password + f.write("rpcuser={}\n".format(self.rpcuser)) + f.write("rpcpassword={}\n".format(self.rpcpassword)) + def test_auth(self, node, user, password): self.log.info('Correct...') - headers = {"Authorization": "Basic " + str_to_b64str(authpair)} + assert_equal(200, call_with_auth(node, user, password).status) - conn = http.client.HTTPConnection(url.hostname, url.port) - conn.connect() - conn.request('POST', '/', '{"method": "getbestblockhash"}', headers) - resp = conn.getresponse() - assert_equal(resp.status, 200) - conn.close() - - #Use new authpair to confirm both work - self.log.info('Correct...') - headers = {"Authorization": "Basic " + str_to_b64str(authpairnew)} - - conn = http.client.HTTPConnection(url.hostname, url.port) - conn.connect() - conn.request('POST', '/', '{"method": "getbestblockhash"}', headers) - resp = conn.getresponse() - assert_equal(resp.status, 200) - conn.close() - - #Wrong login name with rt's password self.log.info('Wrong...') - authpairnew = "rtwrong:"+password - headers = {"Authorization": "Basic " + str_to_b64str(authpairnew)} - - conn = http.client.HTTPConnection(url.hostname, url.port) - conn.connect() - conn.request('POST', '/', '{"method": "getbestblockhash"}', headers) - resp = conn.getresponse() - assert_equal(resp.status, 401) - conn.close() + assert_equal(401, call_with_auth(node, user, password+'wrong').status) - #Wrong password for rt self.log.info('Wrong...') - authpairnew = "rt:"+password+"wrong" - headers = {"Authorization": "Basic " + str_to_b64str(authpairnew)} + assert_equal(401, call_with_auth(node, user+'wrong', password).status) - conn = http.client.HTTPConnection(url.hostname, url.port) - conn.connect() - conn.request('POST', '/', '{"method": "getbestblockhash"}', headers) - resp = conn.getresponse() - assert_equal(resp.status, 401) - conn.close() - - #Correct for rt2 - self.log.info('Correct...') - authpairnew = "rt2:"+password2 - headers = {"Authorization": "Basic " + str_to_b64str(authpairnew)} - - conn = http.client.HTTPConnection(url.hostname, url.port) - conn.connect() - conn.request('POST', '/', '{"method": "getbestblockhash"}', headers) - resp = conn.getresponse() - assert_equal(resp.status, 200) - conn.close() - - #Wrong password for rt2 self.log.info('Wrong...') - authpairnew = "rt2:"+password2+"wrong" - headers = {"Authorization": "Basic " + str_to_b64str(authpairnew)} - - conn = http.client.HTTPConnection(url.hostname, url.port) - conn.connect() - conn.request('POST', '/', '{"method": "getbestblockhash"}', headers) - resp = conn.getresponse() - assert_equal(resp.status, 401) - conn.close() + assert_equal(401, call_with_auth(node, user+'wrong', password+'wrong').status) - #Correct for randomly generated user - self.log.info('Correct...') - authpairnew = self.user+":"+self.password - headers = {"Authorization": "Basic " + str_to_b64str(authpairnew)} - - conn = http.client.HTTPConnection(url.hostname, url.port) - conn.connect() - conn.request('POST', '/', '{"method": "getbestblockhash"}', headers) - resp = conn.getresponse() - assert_equal(resp.status, 200) - conn.close() + def run_test(self): - #Wrong password for randomly generated user - self.log.info('Wrong...') - authpairnew = self.user+":"+self.password+"Wrong" - headers = {"Authorization": "Basic " + str_to_b64str(authpairnew)} + ################################################## + # Check correctness of the rpcauth config option # + ################################################## + url = urllib.parse.urlparse(self.nodes[0].url) - conn = http.client.HTTPConnection(url.hostname, url.port) - conn.connect() - conn.request('POST', '/', '{"method": "getbestblockhash"}', headers) - resp = conn.getresponse() - assert_equal(resp.status, 401) - conn.close() + self.test_auth(self.nodes[0], url.username, url.password) + self.test_auth(self.nodes[0], 'rt', self.rtpassword) + self.test_auth(self.nodes[0], 'rt2', self.rt2password) + self.test_auth(self.nodes[0], self.user, self.password) ############################################################### # Check correctness of the rpcuser/rpcpassword config options # ############################################################### url = urllib.parse.urlparse(self.nodes[1].url) - # rpcuser and rpcpassword authpair - self.log.info('Correct...') - rpcuserauthpair = "rpcuser💻:rpcpassword🔑" - - headers = {"Authorization": "Basic " + str_to_b64str(rpcuserauthpair)} - - conn = http.client.HTTPConnection(url.hostname, url.port) - conn.connect() - conn.request('POST', '/', '{"method": "getbestblockhash"}', headers) - resp = conn.getresponse() - assert_equal(resp.status, 200) - conn.close() - - #Wrong login name with rpcuser's password - rpcuserauthpair = "rpcuserwrong:rpcpassword" - headers = {"Authorization": "Basic " + str_to_b64str(rpcuserauthpair)} - - conn = http.client.HTTPConnection(url.hostname, url.port) - conn.connect() - conn.request('POST', '/', '{"method": "getbestblockhash"}', headers) - resp = conn.getresponse() - assert_equal(resp.status, 401) - conn.close() - - #Wrong password for rpcuser - self.log.info('Wrong...') - rpcuserauthpair = "rpcuser:rpcpasswordwrong" - headers = {"Authorization": "Basic " + str_to_b64str(rpcuserauthpair)} - - conn = http.client.HTTPConnection(url.hostname, url.port) - conn.connect() - conn.request('POST', '/', '{"method": "getbestblockhash"}', headers) - resp = conn.getresponse() - assert_equal(resp.status, 401) - conn.close() - + self.test_auth(self.nodes[1], self.rpcuser, self.rpcpassword) if __name__ == '__main__': HTTPBasicsTest ().main () diff --git a/test/functional/test_framework/messages.py b/test/functional/test_framework/messages.py index e454ed5987..89a5a65e64 100755 --- a/test/functional/test_framework/messages.py +++ b/test/functional/test_framework/messages.py @@ -44,7 +44,7 @@ BIP125_SEQUENCE_NUMBER = 0xfffffffd # Sequence number that is BIP 125 opt-in an NODE_NETWORK = (1 << 0) # NODE_GETUTXO = (1 << 1) -NODE_BLOOM = (1 << 2) +# NODE_BLOOM = (1 << 2) NODE_WITNESS = (1 << 3) NODE_NETWORK_LIMITED = (1 << 10) diff --git a/test/functional/test_framework/util.py b/test/functional/test_framework/util.py index 26215083fb..efd962ea93 100644 --- a/test/functional/test_framework/util.py +++ b/test/functional/test_framework/util.py @@ -19,6 +19,7 @@ import time from . import coverage from .authproxy import AuthServiceProxy, JSONRPCException +from io import BytesIO logger = logging.getLogger("TestFramework.utils") @@ -515,14 +516,13 @@ def gen_return_txouts(): for i in range(512): script_pubkey = script_pubkey + "01" # concatenate 128 txouts of above script_pubkey which we'll insert before the txout for change - txouts = "81" + txouts = [] + from .messages import CTxOut + txout = CTxOut() + txout.nValue = 0 + txout.scriptPubKey = hex_str_to_bytes(script_pubkey) for k in range(128): - # add txout value - txouts = txouts + "0000000000000000" - # add length of script_pubkey - txouts = txouts + "fd0402" - # add script_pubkey - txouts = txouts + script_pubkey + txouts.append(txout) return txouts # Create a spend of each passed-in utxo, splicing in "txouts" to each raw @@ -530,6 +530,7 @@ def gen_return_txouts(): def create_lots_of_big_transactions(node, txouts, utxos, num, fee): addr = node.getnewaddress() txids = [] + from .messages import CTransaction for _ in range(num): t = utxos.pop() inputs = [{"txid": t["txid"], "vout": t["vout"]}] @@ -537,9 +538,11 @@ def create_lots_of_big_transactions(node, txouts, utxos, num, fee): change = t['amount'] - fee outputs[addr] = satoshi_round(change) rawtx = node.createrawtransaction(inputs, outputs) - newtx = rawtx[0:92] - newtx = newtx + txouts - newtx = newtx + rawtx[94:] + tx = CTransaction() + tx.deserialize(BytesIO(hex_str_to_bytes(rawtx))) + for txout in txouts: + tx.vout.append(txout) + newtx = tx.serialize().hex() signresult = node.signrawtransactionwithwallet(newtx, None, "NONE") txid = node.sendrawtransaction(signresult["hex"], 0) txids.append(txid) diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py index 462547f44f..6fc48f2649 100755 --- a/test/functional/test_runner.py +++ b/test/functional/test_runner.py @@ -157,6 +157,7 @@ BASE_SCRIPTS = [ 'rpc_invalidateblock.py', 'feature_rbf.py', 'mempool_packages.py', + 'mempool_package_onemore.py', 'rpc_createmultisig.py', 'feature_versionbits_warning.py', 'rpc_preciousblock.py', @@ -234,6 +235,7 @@ def main(): parser.add_argument('--quiet', '-q', action='store_true', help='only print dots, results summary and failure logs') parser.add_argument('--tmpdirprefix', '-t', default=tempfile.gettempdir(), help="Root directory for datadirs") parser.add_argument('--failfast', action='store_true', help='stop execution after the first test failure') + parser.add_argument('--filter', help='filter scripts to run by regular expression') args, unknown_args = parser.parse_known_args() # args to be passed on always start with two dashes; tests are the remaining unknown args @@ -269,11 +271,22 @@ def main(): test_list = [] if tests: # Individual tests have been specified. Run specified tests that exist - # in the ALL_SCRIPTS list. Accept the name with or without .py extension. - tests = [test + ".py" if ".py" not in test else test for test in tests] + # in the ALL_SCRIPTS list. Accept names with or without a .py extension. + # Specified tests can contain wildcards, but in that case the supplied + # paths should be coherent, e.g. the same path as that provided to call + # test_runner.py. Examples: + # `test/functional/test_runner.py test/functional/wallet*` + # `test/functional/test_runner.py ./test/functional/wallet*` + # `test_runner.py wallet*` + # but not: + # `test/functional/test_runner.py wallet*` + # Multiple wildcards can be passed: + # `test_runner.py tool* mempool*` for test in tests: - if test in ALL_SCRIPTS: - test_list.append(test) + script = test.split("/")[-1] + script = script + ".py" if ".py" not in script else script + if script in ALL_SCRIPTS: + test_list.append(script) else: print("{}WARNING!{} Test '{}' not found in full test list.".format(BOLD[1], BOLD[0], test)) elif args.extended: @@ -294,6 +307,9 @@ def main(): if not exclude_list: print("{}WARNING!{} Test '{}' not found in current test list.".format(BOLD[1], BOLD[0], exclude_test)) + if args.filter: + test_list = list(filter(re.compile(args.filter).search, test_list)) + if not test_list: print("No valid test scripts specified. Check that your test is in one " "of the test lists in test_runner.py, or run test_runner.py with no arguments to run all tests") diff --git a/test/functional/wallet_basic.py b/test/functional/wallet_basic.py index daa834b5b8..34e84fcf55 100755 --- a/test/functional/wallet_basic.py +++ b/test/functional/wallet_basic.py @@ -20,6 +20,9 @@ from test_framework.util import ( class WalletTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 4 + self.extra_args = [[ + "-acceptnonstdtxn=1", + ]] * self.num_nodes self.setup_clean_chain = True def skip_test_if_missing_module(self): diff --git a/test/functional/wallet_bumpfee.py b/test/functional/wallet_bumpfee.py index 1fe029a6fb..030eb50791 100755 --- a/test/functional/wallet_bumpfee.py +++ b/test/functional/wallet_bumpfee.py @@ -80,6 +80,7 @@ class BumpFeeTest(BitcoinTestFramework): test_bumpfee_metadata(rbf_node, dest_address) test_locked_wallet_fails(rbf_node, dest_address) test_change_script_match(rbf_node, dest_address) + test_maxtxfee_fails(self, rbf_node, dest_address) # These tests wipe out a number of utxos that are expected in other tests test_small_output_with_feerate_succeeds(rbf_node, dest_address) test_no_more_inputs_fails(rbf_node, dest_address) @@ -248,6 +249,15 @@ def test_settxfee(rbf_node, dest_address): rbf_node.settxfee(Decimal("0.00000000")) # unset paytxfee +def test_maxtxfee_fails(test, rbf_node, dest_address): + test.restart_node(1, ['-maxtxfee=0.00003'] + test.extra_args[1]) + rbf_node.walletpassphrase(WALLET_PASSPHRASE, WALLET_PASSPHRASE_TIMEOUT) + rbfid = spend_one_input(rbf_node, dest_address) + assert_raises_rpc_error(-4, "Unable to create transaction: Fee exceeds maximum configured by -maxtxfee", rbf_node.bumpfee, rbfid) + test.restart_node(1, test.extra_args[1]) + rbf_node.walletpassphrase(WALLET_PASSPHRASE, WALLET_PASSPHRASE_TIMEOUT) + + def test_rebumping(rbf_node, dest_address): # check that re-bumping the original tx fails, but bumping the bumper succeeds rbfid = spend_one_input(rbf_node, dest_address) diff --git a/test/functional/wallet_create_tx.py b/test/functional/wallet_create_tx.py index 0b584a0bb2..330de8b0fc 100755 --- a/test/functional/wallet_create_tx.py +++ b/test/functional/wallet_create_tx.py @@ -6,6 +6,7 @@ from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_equal, + assert_raises_rpc_error, ) from test_framework.blocktools import ( TIME_GENESIS_BLOCK, @@ -26,6 +27,10 @@ class CreateTxWalletTest(BitcoinTestFramework): self.nodes[0].generate(200) self.nodes[0].setmocktime(0) + self.test_anti_fee_sniping() + self.test_tx_size_too_large() + + def test_anti_fee_sniping(self): self.log.info('Check that we have some (old) blocks and that anti-fee-sniping is disabled') assert_equal(self.nodes[0].getblockchaininfo()['blocks'], 200) txid = self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), 1) @@ -38,6 +43,40 @@ class CreateTxWalletTest(BitcoinTestFramework): tx = self.nodes[0].decoderawtransaction(self.nodes[0].gettransaction(txid)['hex']) assert 0 < tx['locktime'] <= 201 + def test_tx_size_too_large(self): + # More than 10kB of outputs, so that we hit -maxtxfee with a high feerate + outputs = {self.nodes[0].getnewaddress(address_type='bech32'): 0.000025 for i in range(400)} + raw_tx = self.nodes[0].createrawtransaction(inputs=[], outputs=outputs) + + for fee_setting in ['-minrelaytxfee=0.01', '-mintxfee=0.01', '-paytxfee=0.01']: + self.log.info('Check maxtxfee in combination with {}'.format(fee_setting)) + self.restart_node(0, extra_args=[fee_setting]) + assert_raises_rpc_error( + -6, + "Fee exceeds maximum configured by -maxtxfee", + lambda: self.nodes[0].sendmany(dummy="", amounts=outputs), + ) + assert_raises_rpc_error( + -4, + "Fee exceeds maximum configured by -maxtxfee", + lambda: self.nodes[0].fundrawtransaction(hexstring=raw_tx), + ) + + self.log.info('Check maxtxfee in combination with settxfee') + self.restart_node(0) + self.nodes[0].settxfee(0.01) + assert_raises_rpc_error( + -6, + "Fee exceeds maximum configured by -maxtxfee", + lambda: self.nodes[0].sendmany(dummy="", amounts=outputs), + ) + assert_raises_rpc_error( + -4, + "Fee exceeds maximum configured by -maxtxfee", + lambda: self.nodes[0].fundrawtransaction(hexstring=raw_tx), + ) + self.nodes[0].settxfee(0) + if __name__ == '__main__': CreateTxWalletTest().main() diff --git a/test/functional/wallet_encryption.py b/test/functional/wallet_encryption.py index c514b7e0b4..fbcb4e75ba 100755 --- a/test/functional/wallet_encryption.py +++ b/test/functional/wallet_encryption.py @@ -49,7 +49,7 @@ class WalletEncryptionTest(BitcoinTestFramework): assert_equal(privkey, self.nodes[0].dumpprivkey(address)) # Check that the timeout is right - time.sleep(2) + time.sleep(3) assert_raises_rpc_error(-13, "Please enter the wallet passphrase with walletpassphrase first", self.nodes[0].dumpprivkey, address) # Test wrong passphrase diff --git a/test/lint/extended-lint-all.sh b/test/lint/extended-lint-all.sh new file mode 100755 index 0000000000..65c51e02f5 --- /dev/null +++ b/test/lint/extended-lint-all.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash +# +# Copyright (c) 2019 The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. +# +# This script runs all contrib/devtools/extended-lint-*.sh files, and fails if +# any exit with a non-zero status code. + +# This script is intentionally locale dependent by not setting "export LC_ALL=C" +# in order to allow for the executed lint scripts to opt in or opt out of locale +# dependence themselves. + +set -u + +SCRIPTDIR=$(dirname "${BASH_SOURCE[0]}") +LINTALL=$(basename "${BASH_SOURCE[0]}") + +for f in "${SCRIPTDIR}"/extended-lint-*.sh; do + if [ "$(basename "$f")" != "$LINTALL" ]; then + if ! "$f"; then + echo "^---- failure generated from $f" + exit 1 + fi + fi +done diff --git a/test/lint/extended-lint-cppcheck.sh b/test/lint/extended-lint-cppcheck.sh new file mode 100755 index 0000000000..47df25ba6b --- /dev/null +++ b/test/lint/extended-lint-cppcheck.sh @@ -0,0 +1,80 @@ +#!/usr/bin/env bash +# +# Copyright (c) 2019 The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. +# + +export LC_ALL=C + +ENABLED_CHECKS=( + "Class '.*' has a constructor with 1 argument that is not explicit." + "Struct '.*' has a constructor with 1 argument that is not explicit." +) + +IGNORED_WARNINGS=( + "src/arith_uint256.h:.* Class 'arith_uint256' has a constructor with 1 argument that is not explicit." + "src/arith_uint256.h:.* Class 'base_uint < 256 >' has a constructor with 1 argument that is not explicit." + "src/arith_uint256.h:.* Class 'base_uint' has a constructor with 1 argument that is not explicit." + "src/coins.h:.* Class 'CCoinsViewBacked' has a constructor with 1 argument that is not explicit." + "src/coins.h:.* Class 'CCoinsViewCache' has a constructor with 1 argument that is not explicit." + "src/coins.h:.* Class 'CCoinsViewCursor' has a constructor with 1 argument that is not explicit." + "src/net.h:.* Class 'CNetMessage' has a constructor with 1 argument that is not explicit." + "src/policy/feerate.h:.* Class 'CFeeRate' has a constructor with 1 argument that is not explicit." + "src/prevector.h:.* Class 'const_iterator' has a constructor with 1 argument that is not explicit." + "src/prevector.h:.* Class 'const_reverse_iterator' has a constructor with 1 argument that is not explicit." + "src/prevector.h:.* Class 'iterator' has a constructor with 1 argument that is not explicit." + "src/prevector.h:.* Class 'reverse_iterator' has a constructor with 1 argument that is not explicit." + "src/primitives/block.h:.* Class 'CBlock' has a constructor with 1 argument that is not explicit." + "src/primitives/transaction.h:.* Class 'CTransaction' has a constructor with 1 argument that is not explicit." + "src/protocol.h:.* Class 'CMessageHeader' has a constructor with 1 argument that is not explicit." + "src/qt/guiutil.h:.* Class 'ItemDelegate' has a constructor with 1 argument that is not explicit." + "src/rpc/util.h:.* Struct 'RPCResults' has a constructor with 1 argument that is not explicit." + "src/rpc/util.h:.* style: Struct 'UniValueType' has a constructor with 1 argument that is not explicit." + "src/script/descriptor.cpp:.* Class 'AddressDescriptor' has a constructor with 1 argument that is not explicit." + "src/script/descriptor.cpp:.* Class 'ComboDescriptor' has a constructor with 1 argument that is not explicit." + "src/script/descriptor.cpp:.* Class 'ConstPubkeyProvider' has a constructor with 1 argument that is not explicit." + "src/script/descriptor.cpp:.* Class 'PKDescriptor' has a constructor with 1 argument that is not explicit." + "src/script/descriptor.cpp:.* Class 'PKHDescriptor' has a constructor with 1 argument that is not explicit." + "src/script/descriptor.cpp:.* Class 'RawDescriptor' has a constructor with 1 argument that is not explicit." + "src/script/descriptor.cpp:.* Class 'SHDescriptor' has a constructor with 1 argument that is not explicit." + "src/script/descriptor.cpp:.* Class 'WPKHDescriptor' has a constructor with 1 argument that is not explicit." + "src/script/descriptor.cpp:.* Class 'WSHDescriptor' has a constructor with 1 argument that is not explicit." + "src/script/script.h:.* Class 'CScript' has a constructor with 1 argument that is not explicit." + "src/script/standard.h:.* Class 'CScriptID' has a constructor with 1 argument that is not explicit." + "src/support/allocators/secure.h:.* Struct 'secure_allocator < char >' has a constructor with 1 argument that is not explicit." + "src/support/allocators/secure.h:.* Struct 'secure_allocator < RNGState >' has a constructor with 1 argument that is not explicit." + "src/support/allocators/secure.h:.* Struct 'secure_allocator < unsigned char >' has a constructor with 1 argument that is not explicit." + "src/support/allocators/zeroafterfree.h:.* Struct 'zero_after_free_allocator < char >' has a constructor with 1 argument that is not explicit." + "src/test/checkqueue_tests.cpp:.* Struct 'FailingCheck' has a constructor with 1 argument that is not explicit." + "src/test/checkqueue_tests.cpp:.* Struct 'MemoryCheck' has a constructor with 1 argument that is not explicit." + "src/test/checkqueue_tests.cpp:.* Struct 'UniqueCheck' has a constructor with 1 argument that is not explicit." + "src/wallet/db.h:.* Class 'BerkeleyEnvironment' has a constructor with 1 argument that is not explicit." +) + +if ! command -v cppcheck > /dev/null; then + echo "Skipping cppcheck linting since cppcheck is not installed. Install by running \"apt install cppcheck\"" + exit 0 +fi + +function join_array { + local IFS="$1" + shift + echo "$*" +} + +ENABLED_CHECKS_REGEXP=$(join_array "|" "${ENABLED_CHECKS[@]}") +IGNORED_WARNINGS_REGEXP=$(join_array "|" "${IGNORED_WARNINGS[@]}") +WARNINGS=$(git ls-files -- "*.cpp" "*.h" ":(exclude)src/leveldb/" ":(exclude)src/secp256k1/" ":(exclude)src/univalue/" | \ + xargs cppcheck --enable=all -j "$(getconf _NPROCESSORS_ONLN)" --language=c++ --std=c++11 --template=gcc -D__cplusplus -DCLIENT_VERSION_BUILD -DCLIENT_VERSION_IS_RELEASE -DCLIENT_VERSION_MAJOR -DCLIENT_VERSION_MINOR -DCLIENT_VERSION_REVISION -DCOPYRIGHT_YEAR -DDEBUG -DHAVE_WORKING_BOOST_SLEEP_FOR -I src/ -q 2>&1 | sort -u | \ + grep -E "${ENABLED_CHECKS_REGEXP}" | \ + grep -vE "${IGNORED_WARNINGS_REGEXP}") +if [[ ${WARNINGS} != "" ]]; then + echo "${WARNINGS}" + echo + echo "Advice not applicable in this specific case? Add an exception by updating" + echo "IGNORED_WARNINGS in $0" + # Uncomment to enforce the developer note policy "By default, declare single-argument constructors `explicit`" + # exit 1 +fi +exit 0 |