diff options
540 files changed, 15922 insertions, 8344 deletions
diff --git a/.appveyor.yml b/.appveyor.yml index d7ce2224e9..249d0b8df9 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -3,57 +3,56 @@ skip_tags: true image: Visual Studio 2017 configuration: Release platform: x64 +clone_depth: 5 environment: APPVEYOR_SAVE_CACHE_ON_ERROR: true CLCACHE_SERVER: 1 - PACKAGES: boost-filesystem boost-signals2 boost-interprocess boost-test libevent openssl zeromq berkeleydb secp256k1 leveldb - PYTHONIOENCODING: utf-8 + PACKAGES: boost-filesystem boost-signals2 boost-test libevent openssl zeromq berkeleydb secp256k1 leveldb + PATH: 'C:\Python37-x64;C:\Python37-x64\Scripts;%PATH%' + PYTHONUTF8: 1 cache: - C:\tools\vcpkg\installed - C:\Users\appveyor\clcache -- build_msvc\cache -init: -- cmd: set PATH=C:\Python36-x64;C:\Python36-x64\Scripts;%PATH% install: -- cmd: pip install git+https://github.com/frerich/clcache.git +- cmd: pip install --quiet git+https://github.com/frerich/clcache.git@v4.2.0 # Disable zmq test for now since python zmq library on Windows would cause Access violation sometimes. # - cmd: pip install zmq -- ps: $packages = $env:PACKAGES -Split ' ' -- ps: for ($i=0; $i -lt $packages.length; $i++) { - $env:ALL_PACKAGES += $packages[$i] + ":" + $env:PLATFORM + "-windows-static " - } -- cmd: git -C C:\Tools\vcpkg pull # This is a temporary fix, can be removed after appveyor update its image to include Microsoft/vcpkg#4046 -- cmd: vcpkg install %ALL_PACKAGES% -- cmd: vcpkg upgrade --no-dry-run +- cmd: vcpkg install --triplet %PLATFORM%-windows-static %PACKAGES% > NUL - cmd: del /s /q C:\Tools\vcpkg\installed\%PLATFORM%-windows-static\debug # Remove unused debug library before_build: -- cmd: if not exist build_msvc\cache\ (del build_msvc\cache & mkdir build_msvc\cache) -- cmd: if not exist build_msvc\%PLATFORM%\%CONFIGURATION%\ (mkdir build_msvc\%PLATFORM%\%CONFIGURATION%) -- cmd: if exist build_msvc\cache\*.iobj (move build_msvc\cache\* build_msvc\%PLATFORM%\%CONFIGURATION%\) -- cmd: clcache -M 2147483648 +- ps: clcache -M 536870912 - cmd: python build_msvc\msvc-autogen.py - ps: $files = (Get-ChildItem -Recurse | where {$_.extension -eq ".vcxproj"}).FullName -- ps: for ($i = 0; $i -lt $files.length; $i++) { - (Get-Content $files[$i]).Replace("</RuntimeLibrary>", "</RuntimeLibrary><DebugInformationFormat>None</DebugInformationFormat>").Replace("NDEBUG;", "") | Set-Content $files[$i] +- ps: for (${i} = 0; ${i} -lt ${files}.length; ${i}++) { + ${content} = (Get-Content ${files}[${i}]); + ${content} = ${content}.Replace("</RuntimeLibrary>", "</RuntimeLibrary><DebugInformationFormat>None</DebugInformationFormat>"); + ${content} = ${content}.Replace("<WholeProgramOptimization>true", "<WholeProgramOptimization>false"); + ${content} = ${content}.Replace("NDEBUG;", ""); + Set-Content ${files}[${i}] ${content}; } - ps: Start-Process clcache-server +- ps: fsutil behavior set disablelastaccess 0 # Enable Access time feature on Windows (for clcache) build_script: -- cmd: msbuild /p:TrackFileAccess=false /p:CLToolExe=clcache.exe build_msvc\bitcoin.sln /m /v:q /nowarn:C4244;C4267;C4715 /nologo +- cmd: msbuild /p:TrackFileAccess=false /p:CLToolExe=clcache.exe build_msvc\bitcoin.sln /m /v:q /nologo after_build: -- cmd: move build_msvc\%PLATFORM%\%CONFIGURATION%\*.iobj build_msvc\cache\ -- cmd: move build_msvc\%PLATFORM%\%CONFIGURATION%\*.ipdb build_msvc\cache\ -- cmd: del C:\Users\appveyor\clcache\stats.txt +- ps: fsutil behavior set disablelastaccess 1 # Disable Access time feature on Windows (better performance) +- ps: clcache -z before_test: - ps: ${conf_ini} = (Get-Content([IO.Path]::Combine(${env:APPVEYOR_BUILD_FOLDER}, "test", "config.ini.in"))) -- ps: ${conf_ini} = $conf_ini.Replace("@abs_top_srcdir@", ${env:APPVEYOR_BUILD_FOLDER}).Replace("@abs_top_builddir@", ${env:APPVEYOR_BUILD_FOLDER}).Replace("@EXEEXT@", ".exe") -- ps: ${conf_ini} = $conf_ini.Replace("@ENABLE_WALLET_TRUE@", "").Replace("@BUILD_BITCOIN_UTILS_TRUE@", "").Replace("@BUILD_BITCOIND_TRUE@", "").Replace("@ENABLE_ZMQ_TRUE@", "") +- ps: ${conf_ini} = ${conf_ini}.Replace("@abs_top_srcdir@", ${env:APPVEYOR_BUILD_FOLDER}) +- ps: ${conf_ini} = ${conf_ini}.Replace("@abs_top_builddir@", ${env:APPVEYOR_BUILD_FOLDER}) +- ps: ${conf_ini} = ${conf_ini}.Replace("@EXEEXT@", ".exe") +- ps: ${conf_ini} = ${conf_ini}.Replace("@ENABLE_WALLET_TRUE@", "") +- ps: ${conf_ini} = ${conf_ini}.Replace("@BUILD_BITCOIN_CLI_TRUE@", "") +- ps: ${conf_ini} = ${conf_ini}.Replace("@BUILD_BITCOIND_TRUE@", "") +- ps: ${conf_ini} = ${conf_ini}.Replace("@ENABLE_ZMQ_TRUE@", "") - ps: ${utf8} = New-Object System.Text.UTF8Encoding ${false} -- ps: '[IO.File]::WriteAllLines([IO.Path]::Combine(${env:APPVEYOR_BUILD_FOLDER}, "test", "config.ini"), $conf_ini, ${utf8})' +- ps: '[IO.File]::WriteAllLines([IO.Path]::Combine(${env:APPVEYOR_BUILD_FOLDER}, "test", "config.ini"), ${conf_ini}, ${utf8})' - ps: move "build_msvc\${env:PLATFORM}\${env:CONFIGURATION}\*.exe" src test_script: -- cmd: src\test_bitcoin.exe -- ps: src\bench_bitcoin.exe -evals=1 -scaling=0 +- cmd: src\test_bitcoin.exe -k stdout -e stdout 2> NUL +- cmd: src\bench_bitcoin.exe -evals=1 -scaling=0 > NUL - ps: python test\util\bitcoin-util-test.py - cmd: python test\util\rpcauth-test.py -- cmd: python test\functional\test_runner.py --force --quiet --combinedlogslen=4000 --exclude wallet_multiwallet +- cmd: python test\functional\test_runner.py --ci --force --quiet --combinedlogslen=4000 --failfast deploy: off diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index 55cebc008f..8768a8ca6b 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -17,5 +17,7 @@ If the node is "stuck" during sync or giving "block checksum mismatch" errors, p <!-- What type of machine are you observing the error on (OS/CPU and disk type)? --> +<!-- For the GUI-related issue on Linux provide names and versions of a distro, a desktop environment and a graphical shell (if relevant). --> + <!-- Any extra information that might be useful in the debugging process. --> <!--- This is normally the contents of a `debug.log` or `config.log` file. Raw text or a link to a pastebin type site are preferred. --> diff --git a/.gitignore b/.gitignore index d083c63ea0..380d2eab98 100644 --- a/.gitignore +++ b/.gitignore @@ -116,3 +116,6 @@ test/cache/* libbitcoinconsensus.pc contrib/devtools/split-debug.sh + +# Output from running db4 installation +db4/ diff --git a/.python-version b/.python-version new file mode 100644 index 0000000000..7bcbb3808b --- /dev/null +++ b/.python-version @@ -0,0 +1 @@ +3.4.9 diff --git a/.travis.yml b/.travis.yml index 8819d38914..6181726fb6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,4 @@ -sudo: required -dist: trusty +dist: xenial os: linux language: minimal cache: @@ -16,7 +15,6 @@ env: - MAKEJOBS=-j3 - RUN_UNIT_TESTS=true - RUN_FUNCTIONAL_TESTS=true - - RUN_BENCH=false # Set to true for any one job that has debug enabled, to quickly check bench is not crashing or hitting assertions - DOCKER_NAME_TAG=ubuntu:18.04 - BOOST_TEST_RANDOM=1$TRAVIS_BUILD_ID - CCACHE_SIZE=100M @@ -34,104 +32,109 @@ install: before_script: - set -o errexit; source .travis/test_05_before_script.sh script: - - set -o errexit; source .travis/test_06_script.sh + - if [ $SECONDS -gt 1200 ]; then set +o errexit; echo "Travis early exit to cache current state"; false; else set -o errexit; source .travis/test_06_script.sh; fi after_script: - echo $TRAVIS_COMMIT_RANGE - echo $TRAVIS_COMMIT_LOG jobs: include: -# lint stage + - stage: lint + name: 'lint' env: - sudo: false cache: false language: python - python: '3.6' + python: '3.4' # Oldest supported version according to doc/dependencies.md install: - set -o errexit; source .travis/lint_04_install.sh before_script: - set -o errexit; source .travis/lint_05_before_script.sh script: - set -o errexit; source .travis/lint_06_script.sh -# ARM + - stage: test + name: 'ARM [GOAL: install] [no unit or functional tests]' env: >- HOST=arm-linux-gnueabihf - PACKAGES="g++-arm-linux-gnueabihf" - DEP_OPTS="NO_QT=1" + PACKAGES="python3 g++-arm-linux-gnueabihf" RUN_UNIT_TESTS=false RUN_FUNCTIONAL_TESTS=false GOAL="install" # -Wno-psabi is to disable ABI warnings: "note: parameter passing for argument of type ... changed in GCC 7.1" # This could be removed once the ABI change warning does not show up by default BITCOIN_CONFIG="--enable-glibc-back-compat --enable-reduce-exports CXXFLAGS=-Wno-psabi" -# Win32 + - stage: test + name: 'Win32 [GOAL: deploy] [no gui tests]' env: >- HOST=i686-w64-mingw32 DPKG_ADD_ARCH="i386" - DEP_OPTS="NO_QT=1" PACKAGES="python3 nsis g++-mingw-w64-i686 wine-binfmt wine32" - GOAL="install" - BITCOIN_CONFIG="--enable-reduce-exports" -# Win64 + GOAL="deploy" + BITCOIN_CONFIG="--enable-reduce-exports --disable-gui-tests" + - stage: test + name: 'Win64 [GOAL: deploy] [no gui tests]' env: >- HOST=x86_64-w64-mingw32 - DEP_OPTS="NO_QT=1" PACKAGES="python3 nsis g++-mingw-w64-x86-64 wine-binfmt wine64" - GOAL="install" - BITCOIN_CONFIG="--enable-reduce-exports" -# 32-bit + dash + GOAL="deploy" + BITCOIN_CONFIG="--enable-reduce-exports --disable-gui-tests" + - stage: test + name: '32-bit + dash [GOAL: install]' env: >- HOST=i686-pc-linux-gnu PACKAGES="g++-multilib python3-zmq" - DEP_OPTS="NO_QT=1" GOAL="install" BITCOIN_CONFIG="--enable-zmq --enable-glibc-back-compat --enable-reduce-exports LDFLAGS=-static-libstdc++" CONFIG_SHELL="/bin/dash" -# x86_64 Linux (uses qt5 dev package instead of depends Qt to speed up build and avoid timeout) + - stage: test + name: 'x86_64 Linux [GOAL: install] [bionic] [uses qt5 dev package instead of depends Qt to speed up build and avoid timeout]' env: >- HOST=x86_64-unknown-linux-gnu PACKAGES="python3-zmq qtbase5-dev qttools5-dev-tools protobuf-compiler libdbus-1-dev libharfbuzz-dev libprotobuf-dev" DEP_OPTS="NO_QT=1 NO_UPNP=1 DEBUG=1 ALLOW_HOST_PACKAGES=1" GOAL="install" BITCOIN_CONFIG="--enable-zmq --with-gui=qt5 --enable-glibc-back-compat --enable-reduce-exports --enable-debug CXXFLAGS=\"-g0 -O2\"" -# x86_64 Linux (no depends, only system libs) + - stage: test + name: 'x86_64 Linux [GOAL: install] [xenial] [no depends, only system libs, sanitizers: thread (TSan), no wallet]' env: >- HOST=x86_64-unknown-linux-gnu - PACKAGES="python3-zmq qtbase5-dev qttools5-dev-tools libssl1.0-dev libevent-dev bsdmainutils libboost-system-dev libboost-filesystem-dev libboost-chrono-dev libboost-test-dev libboost-thread-dev libdb5.3++-dev libminiupnpc-dev libzmq3-dev libprotobuf-dev protobuf-compiler libqrencode-dev" + DOCKER_NAME_TAG=ubuntu:16.04 + PACKAGES="clang llvm python3-zmq qtbase5-dev qttools5-dev-tools libssl-dev libevent-dev bsdmainutils libboost-system-dev libboost-filesystem-dev libboost-chrono-dev libboost-test-dev libboost-thread-dev libdb5.3++-dev libminiupnpc-dev libzmq3-dev libprotobuf-dev protobuf-compiler libqrencode-dev" NO_DEPENDS=1 GOAL="install" - BITCOIN_CONFIG="--enable-zmq --with-incompatible-bdb --enable-glibc-back-compat --enable-reduce-exports --with-gui=qt5 CPPFLAGS=-DDEBUG_LOCKORDER" -# x86_64 Linux (sanitizers) + BITCOIN_CONFIG="--enable-zmq --disable-wallet --with-gui=qt5 CPPFLAGS=-DDEBUG_LOCKORDER --with-sanitizers=thread --disable-hardening --disable-asm CC=clang CXX=clang++" + - stage: test + name: 'x86_64 Linux [GOAL: install] [bionic] [no depends, only system libs, sanitizers: address/leak (ASan + LSan) + undefined (UBSan) + integer]' env: >- HOST=x86_64-unknown-linux-gnu - PACKAGES="clang python3-zmq qtbase5-dev qttools5-dev-tools libssl1.0-dev libevent-dev bsdmainutils libboost-system-dev libboost-filesystem-dev libboost-chrono-dev libboost-test-dev libboost-thread-dev libdb5.3++-dev libminiupnpc-dev libzmq3-dev libprotobuf-dev protobuf-compiler libqrencode-dev" + PACKAGES="clang llvm python3-zmq qtbase5-dev qttools5-dev-tools libssl1.0-dev libevent-dev bsdmainutils libboost-system-dev libboost-filesystem-dev libboost-chrono-dev libboost-test-dev libboost-thread-dev libdb5.3++-dev libminiupnpc-dev libzmq3-dev libprotobuf-dev protobuf-compiler libqrencode-dev" NO_DEPENDS=1 - RUN_BENCH=true - RUN_FUNCTIONAL_TESTS=false # Disabled for now, can be combined with the other x86_64 linux NO_DEPENDS job when functional tests pass the sanitizers + FUNCTIONAL_TESTS_CONFIG="--exclude wallet_multiwallet.py" # Temporarily suppress ASan heap-use-after-free (see issue #14163) GOAL="install" - BITCOIN_CONFIG="--enable-zmq --with-incompatible-bdb --enable-glibc-back-compat --enable-reduce-exports --with-gui=qt5 CPPFLAGS=-DDEBUG_LOCKORDER --with-sanitizers=undefined CC=clang CXX=clang++" -# x86_64 Linux, No wallet + BITCOIN_CONFIG="--enable-zmq --with-incompatible-bdb --with-gui=qt5 CPPFLAGS=-DDEBUG_LOCKORDER --with-sanitizers=address,integer,undefined CC=clang CXX=clang++" + - stage: test + name: 'x86_64 Linux [GOAL: install] [bionic] [no wallet]' env: >- HOST=x86_64-unknown-linux-gnu - PACKAGES="python3" + PACKAGES="python3-zmq" DEP_OPTS="NO_WALLET=1" GOAL="install" BITCOIN_CONFIG="--enable-glibc-back-compat --enable-reduce-exports" -# Cross-Mac + - stage: test + name: 'macOS 10.10 [GOAL: deploy]' env: >- HOST=x86_64-apple-darwin14 PACKAGES="cmake imagemagick libcap-dev librsvg2-bin libz-dev libbz2-dev libtiff-tools python-dev python3-setuptools-git" OSX_SDK=10.11 RUN_UNIT_TESTS=false RUN_FUNCTIONAL_TESTS=false - GOAL="all deploy" + GOAL="deploy" BITCOIN_CONFIG="--enable-gui --enable-reduce-exports --enable-werror" diff --git a/.travis/lint_04_install.sh b/.travis/lint_04_install.sh index 3a6ff73d79..9a22773e57 100755 --- a/.travis/lint_04_install.sh +++ b/.travis/lint_04_install.sh @@ -6,5 +6,10 @@ export LC_ALL=C -travis_retry pip install codespell -travis_retry pip install flake8 +travis_retry pip install codespell==1.13.0 +travis_retry pip install flake8==3.5.0 +travis_retry pip install vulture==0.29 + +SHELLCHECK_VERSION=v0.6.0 +curl -s "https://storage.googleapis.com/shellcheck/shellcheck-${SHELLCHECK_VERSION}.linux.x86_64.tar.xz" | tar --xz -xf - --directory /tmp/ +export PATH="/tmp/shellcheck-${SHELLCHECK_VERSION}:${PATH}" diff --git a/.travis/lint_06_script.sh b/.travis/lint_06_script.sh index 6191d82571..701e6d8005 100755 --- a/.travis/lint_06_script.sh +++ b/.travis/lint_06_script.sh @@ -19,6 +19,7 @@ test/lint/check-rpc-mappings.py . test/lint/lint-all.sh if [ "$TRAVIS_REPO_SLUG" = "bitcoin/bitcoin" -a "$TRAVIS_EVENT_TYPE" = "cron" ]; then + git log --merges --before="2 days ago" -1 --format='%H' > ./contrib/verify-commits/trusted-sha512-root-commit while read -r LINE; do travis_retry gpg --keyserver hkp://subset.pool.sks-keyservers.net --recv-keys $LINE; done < contrib/verify-commits/trusted-keys && - travis_wait 50 contrib/verify-commits/verify-commits.py; + travis_wait 50 contrib/verify-commits/verify-commits.py --clean-merge=2; fi diff --git a/.travis/test_03_before_install.sh b/.travis/test_03_before_install.sh index d091a67ca9..3c9fcf3f98 100755 --- a/.travis/test_03_before_install.sh +++ b/.travis/test_03_before_install.sh @@ -7,6 +7,8 @@ export LC_ALL=C.UTF-8 PATH=$(echo $PATH | tr ':' "\n" | sed '/\/opt\/python/d' | tr "\n" ":" | sed "s|::|:|g") +# Add llvm-symbolizer directory to PATH. Needed to get symbolized stack traces from the sanitizers. +PATH=$PATH:/usr/lib/llvm-6.0/bin/ export PATH BEGIN_FOLD () { diff --git a/.travis/test_04_install.sh b/.travis/test_04_install.sh index ef595287b7..a111387f10 100755 --- a/.travis/test_04_install.sh +++ b/.travis/test_04_install.sh @@ -7,9 +7,16 @@ export LC_ALL=C.UTF-8 travis_retry docker pull "$DOCKER_NAME_TAG" -env | grep -E '^(CCACHE_|WINEDEBUG|LC_ALL|BOOST_TEST_RANDOM|CONFIG_SHELL)' | tee /tmp/env +mkdir -p "${TRAVIS_BUILD_DIR}/sanitizer-output/" +export ASAN_OPTIONS="" +export LSAN_OPTIONS="suppressions=${TRAVIS_BUILD_DIR}/test/sanitizer_suppressions/lsan" +export TSAN_OPTIONS="suppressions=${TRAVIS_BUILD_DIR}/test/sanitizer_suppressions/tsan:log_path=${TRAVIS_BUILD_DIR}/sanitizer-output/tsan" +export UBSAN_OPTIONS="suppressions=${TRAVIS_BUILD_DIR}/test/sanitizer_suppressions/ubsan:print_stacktrace=1:halt_on_error=1" +env | grep -E '^(BITCOIN_CONFIG|CCACHE_|WINEDEBUG|LC_ALL|BOOST_TEST_RANDOM|CONFIG_SHELL|(ASAN|LSAN|TSAN|UBSAN)_OPTIONS)' | tee /tmp/env if [[ $HOST = *-mingw32 ]]; then DOCKER_ADMIN="--cap-add SYS_ADMIN" +elif [[ $BITCOIN_CONFIG = *--with-sanitizers=*address* ]]; then # If ran with (ASan + LSan), Docker needs access to ptrace (https://github.com/google/sanitizers/issues/764) + DOCKER_ADMIN="--cap-add SYS_PTRACE" fi DOCKER_ID=$(docker run $DOCKER_ADMIN -idt --mount type=bind,src=$TRAVIS_BUILD_DIR,dst=$TRAVIS_BUILD_DIR --mount type=bind,src=$CCACHE_DIR,dst=$CCACHE_DIR -w $TRAVIS_BUILD_DIR --env-file /tmp/env $DOCKER_NAME_TAG) diff --git a/.travis/test_06_script.sh b/.travis/test_06_script.sh index 59cc110db5..618aa2c3b6 100755 --- a/.travis/test_06_script.sh +++ b/.travis/test_06_script.sh @@ -40,6 +40,9 @@ BEGIN_FOLD configure DOCKER_EXEC ./configure --cache-file=../config.cache $BITCOIN_CONFIG_ALL $BITCOIN_CONFIG || ( cat config.log && false) END_FOLD +set -o errtrace +trap 'DOCKER_EXEC "cat ${TRAVIS_BUILD_DIR}/sanitizer-output/* 2> /dev/null"' ERR + BEGIN_FOLD build DOCKER_EXEC make $MAKEJOBS $GOAL || ( echo "Build failure. Verbose build follows." && DOCKER_EXEC make $GOAL V=1 ; false ) END_FOLD @@ -50,18 +53,12 @@ if [ "$RUN_UNIT_TESTS" = "true" ]; then END_FOLD fi -if [ "$RUN_BENCH" = "true" ]; then - BEGIN_FOLD bench - DOCKER_EXEC LD_LIBRARY_PATH=$TRAVIS_BUILD_DIR/depends/$HOST/lib $OUTDIR/bin/bench_bitcoin -scaling=0.001 - END_FOLD -fi - if [ "$TRAVIS_EVENT_TYPE" = "cron" ]; then extended="--extended --exclude feature_pruning" fi if [ "$RUN_FUNCTIONAL_TESTS" = "true" ]; then BEGIN_FOLD functional-tests - DOCKER_EXEC test/functional/test_runner.py --combinedlogslen=4000 --coverage --quiet --failfast ${extended} + DOCKER_EXEC test/functional/test_runner.py --ci --combinedlogslen=4000 --coverage --quiet --failfast ${extended} ${FUNCTIONAL_TESTS_CONFIG} END_FOLD fi diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3d5dc3221b..139fe7dc52 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -25,7 +25,8 @@ Most communication about Bitcoin Core development happens on IRC, in the #bitcoin-core-dev channel on Freenode. The easiest way to participate on IRC is with the web client, [webchat.freenode.net](https://webchat.freenode.net/). Chat history logs can be found -on [botbot.me](https://botbot.me/freenode/bitcoin-core-dev/). +on [http://www.erisian.com.au/bitcoin-core-dev/](http://www.erisian.com.au/bitcoin-core-dev/) +and [http://gnusha.org/bitcoin-core-dev/](http://gnusha.org/bitcoin-core-dev/). Discussion about code base improvements happens in GitHub issues and on pull requests. @@ -67,6 +68,8 @@ If a particular commit references another issue, please add the reference. For example: `refs #1234` or `fixes #4321`. Using the `fixes` or `closes` keywords will cause the corresponding issue to be closed when the pull request is merged. +Commit messages should never contain any `@` mentions. + Please refer to the [Git manual](https://git-scm.com/doc) for more information about Git. @@ -135,6 +138,10 @@ before it will be merged. The basic squashing workflow is shown below. # Save and quit. git push -f # (force push to GitHub) +Please update the resulting commit message if needed, it should read as a +coherent message. In most cases this means that you should not just list the +interim commits. + If you have problems with squashing (or other workflows with `git`), you can alternatively enable "Allow edits from maintainers" in the right GitHub sidebar and ask for help in the pull request. @@ -1,7 +1,7 @@ The MIT License (MIT) -Copyright (c) 2009-2018 The Bitcoin Core developers -Copyright (c) 2009-2018 Bitcoin Developers +Copyright (c) 2009-2019 The Bitcoin Core developers +Copyright (c) 2009-2019 Bitcoin Developers Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/Makefile.am b/Makefile.am index eec9f72215..9a6e15f0dd 100644 --- a/Makefile.am +++ b/Makefile.am @@ -19,6 +19,7 @@ endif BITCOIND_BIN=$(top_builddir)/src/$(BITCOIN_DAEMON_NAME)$(EXEEXT) BITCOIN_QT_BIN=$(top_builddir)/src/qt/$(BITCOIN_GUI_NAME)$(EXEEXT) BITCOIN_CLI_BIN=$(top_builddir)/src/$(BITCOIN_CLI_NAME)$(EXEEXT) +BITCOIN_TX_BIN=$(top_builddir)/src/$(BITCOIN_TX_NAME)$(EXEEXT) BITCOIN_WIN_INSTALLER=$(PACKAGE)-$(PACKAGE_VERSION)-win$(WINDOWS_BITS)-setup$(EXEEXT) empty := @@ -74,6 +75,7 @@ $(BITCOIN_WIN_INSTALLER): all-recursive STRIPPROG="$(STRIP)" $(INSTALL_STRIP_PROGRAM) $(BITCOIND_BIN) $(top_builddir)/release STRIPPROG="$(STRIP)" $(INSTALL_STRIP_PROGRAM) $(BITCOIN_QT_BIN) $(top_builddir)/release STRIPPROG="$(STRIP)" $(INSTALL_STRIP_PROGRAM) $(BITCOIN_CLI_BIN) $(top_builddir)/release + STRIPPROG="$(STRIP)" $(INSTALL_STRIP_PROGRAM) $(BITCOIN_TX_BIN) $(top_builddir)/release @test -f $(MAKENSIS) && $(MAKENSIS) -V2 $(top_builddir)/share/setup.nsi || \ echo error: could not build $@ @echo built $@ @@ -167,6 +169,9 @@ $(BITCOIND_BIN): FORCE $(BITCOIN_CLI_BIN): FORCE $(MAKE) -C src $(@F) +$(BITCOIN_TX_BIN): FORCE + $(MAKE) -C src $(@F) + if USE_LCOV LCOV_FILTER_PATTERN=-p "/usr/include/" -p "/usr/lib/" -p "src/leveldb/" -p "src/bench/" -p "src/univalue" -p "src/crypto/ctaes" -p "src/secp256k1" @@ -294,5 +299,5 @@ clean-docs: clean-local: clean-docs rm -rf coverage_percent.txt test_bitcoin.coverage/ total.coverage/ test/tmp/ cache/ $(OSX_APP) - rm -rf test/functional/__pycache__ test/functional/test_framework/__pycache__ test/cache + rm -rf test/functional/__pycache__ test/functional/test_framework/__pycache__ test/cache share/rpcauth/__pycache__ @@ -31,7 +31,8 @@ The `master` branch is regularly built and tested, but is not guaranteed to be completely stable. [Tags](https://github.com/bitcoin/bitcoin/tags) are created regularly to indicate new official, stable release versions of Bitcoin Core. -The contribution workflow is described in [CONTRIBUTING.md](CONTRIBUTING.md). +The contribution workflow is described in [CONTRIBUTING.md](CONTRIBUTING.md) +and useful hints for developers can be found in [doc/developer-notes.md](doc/developer-notes.md). Testing ------- diff --git a/build-aux/m4/bitcoin_qt.m4 b/build-aux/m4/bitcoin_qt.m4 index 05df8621d2..90a2cd9875 100644 --- a/build-aux/m4/bitcoin_qt.m4 +++ b/build-aux/m4/bitcoin_qt.m4 @@ -276,7 +276,7 @@ AC_DEFUN([_BITCOIN_QT_CHECK_QT5],[ #endif ]], [[ - #if QT_VERSION < 0x050000 || QT_VERSION_MAJOR < 5 + #if QT_VERSION < 0x050200 || QT_VERSION_MAJOR < 5 choke #endif ]])], diff --git a/build_msvc/README.md b/build_msvc/README.md index 5fb08df8d7..63c5babf35 100644 --- a/build_msvc/README.md +++ b/build_msvc/README.md @@ -31,20 +31,13 @@ Additional dependencies required from the [bitcoin-core](https://github.com/bitc Building --------------------- -The instructions below use vcpkg to install the dependencies. +The instructions below use `vcpkg` to install the dependencies. -- Clone and vcpkg from the [github repository](https://github.com/Microsoft/vcpkg) and install as per the instructions in the main README.md. +- Clone `vcpkg` from the [github repository](https://github.com/Microsoft/vcpkg) and install as per the instructions in the main README.md. - Install the required packages (replace x64 with x86 as required): -- Install the required dependencies with vcpkg: ``` - PS >.\vcpkg install boost:x64-windows-static ` - libevent:x64-windows-static ` - openssl:x64-windows-static ` - zeromq:x64-windows-static ` - berkeleydb:x64-windows-static ` - secp256k1:x64-windows-static ` - leveldb:x64-windows-static + PS >.\vcpkg install --triplet x64-windows-static boost-filesystem boost-signals2 boost-test libevent openssl zeromq berkeleydb secp256k1 leveldb ``` - Use Python to generate *.vcxproj from Makefile @@ -53,4 +46,4 @@ The instructions below use vcpkg to install the dependencies. PS >python msvc-autogen.py ``` -- Build in Visual Studio.
\ No newline at end of file +- Build in Visual Studio. diff --git a/build_msvc/common.vcxproj b/build_msvc/common.vcxproj index 3a53f3bad3..c7c20622e4 100644 --- a/build_msvc/common.vcxproj +++ b/build_msvc/common.vcxproj @@ -12,4 +12,11 @@ Outputs="$(MSBuildThisFileDirectory)..\src\config\bitcoin-config.h"> <Copy SourceFiles="$(MSBuildThisFileDirectory)bitcoin_config.h" DestinationFiles="$(MSBuildThisFileDirectory)..\src\config\bitcoin-config.h" /> </Target> -</Project>
\ No newline at end of file + <ItemDefinitionGroup> + <ClCompile> + <AdditionalOptions>/utf-8 %(AdditionalOptions)</AdditionalOptions> + <DisableSpecificWarnings>4018;4244;4267;4715;4805;</DisableSpecificWarnings> + <TreatWarningAsError>true</TreatWarningAsError> + </ClCompile> + </ItemDefinitionGroup> +</Project> diff --git a/build_msvc/libbitcoin_server/libbitcoin_server.vcxproj.in b/build_msvc/libbitcoin_server/libbitcoin_server.vcxproj.in index 0a165d0b75..e66277123a 100644 --- a/build_msvc/libbitcoin_server/libbitcoin_server.vcxproj.in +++ b/build_msvc/libbitcoin_server/libbitcoin_server.vcxproj.in @@ -154,12 +154,6 @@ </Link> </ItemDefinitionGroup> <ItemGroup> - <ClCompile Include="..\..\src\rpc\net.cpp"> - <ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">$(IntDir)\netrpc.obj</ObjectFileName> - <ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">$(IntDir)\netrpc.obj</ObjectFileName> - <ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(IntDir)\netrpc.obj</ObjectFileName> - <ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(IntDir)\netrpc.obj</ObjectFileName> - </ClCompile> @SOURCE_FILES@ </ItemGroup> <ItemGroup> diff --git a/build_msvc/libbitcoinconsensus/libbitcoinconsensus.vcxproj b/build_msvc/libbitcoinconsensus/libbitcoinconsensus.vcxproj index 0be7e7e430..2c6c0a8b7c 100644 --- a/build_msvc/libbitcoinconsensus/libbitcoinconsensus.vcxproj +++ b/build_msvc/libbitcoinconsensus/libbitcoinconsensus.vcxproj @@ -40,7 +40,7 @@ <ClCompile Include="..\..\src\script\script.cpp" /> <ClCompile Include="..\..\src\script\script_error.cpp" /> <ClCompile Include="..\..\src\uint256.cpp" /> - <ClCompile Include="..\..\src\utilstrencodings.cpp" /> + <ClCompile Include="..\..\src\util\strencodings.cpp" /> </ItemGroup> <ItemGroup> <None Include="packages.config" /> diff --git a/build_msvc/msvc-autogen.py b/build_msvc/msvc-autogen.py index 8888487e75..f351532f9d 100644 --- a/build_msvc/msvc-autogen.py +++ b/build_msvc/msvc-autogen.py @@ -16,10 +16,6 @@ libs = [ ] ignore_list = [ - 'rpc/net.cpp', - 'interfaces/handler.cpp', - 'interfaces/node.cpp', - 'interfaces/wallet.cpp', ] lib_sources = {} @@ -32,7 +28,9 @@ def parse_makefile(makefile): if current_lib: source = line.split()[0] if source.endswith('.cpp') and not source.startswith('$') and source not in ignore_list: - lib_sources[current_lib].append(source.replace('/', '\\')) + source_filename = source.replace('/', '\\') + object_filename = source.replace('/', '_')[:-4] + ".obj" + lib_sources[current_lib].append((source_filename, object_filename)) if not line.endswith('\\'): current_lib = '' continue @@ -51,8 +49,10 @@ def main(): for key, value in lib_sources.items(): vcxproj_filename = os.path.abspath(os.path.join(os.path.dirname(__file__), key, key + '.vcxproj')) content = '' - for source_filename in value: - content += ' <ClCompile Include="..\\..\\src\\' + source_filename + '" />\n' + for source_filename, object_filename in value: + content += ' <ClCompile Include="..\\..\\src\\' + source_filename + '">\n' + content += ' <ObjectFileName>$(IntDir)' + object_filename + '</ObjectFileName>\n' + content += ' </ClCompile>\n' with open(vcxproj_filename + '.in', 'r', encoding='utf-8') as vcxproj_in_file: with open(vcxproj_filename, 'w', encoding='utf-8') as vcxproj_file: vcxproj_file.write(vcxproj_in_file.read().replace( diff --git a/build_msvc/test_bitcoin/test_bitcoin.vcxproj b/build_msvc/test_bitcoin/test_bitcoin.vcxproj index 444a2ed725..2316e473aa 100644 --- a/build_msvc/test_bitcoin/test_bitcoin.vcxproj +++ b/build_msvc/test_bitcoin/test_bitcoin.vcxproj @@ -24,7 +24,7 @@ <ClCompile Include="..\..\src\wallet\test\*_tests.cpp" /> <ClCompile Include="..\..\src\test\test_bitcoin.cpp" /> <ClCompile Include="..\..\src\test\test_bitcoin_main.cpp" /> - <ClCompile Include="..\..\src\wallet\test\wallet_test_fixture.cpp" /> + <ClCompile Include="..\..\src\wallet\test\*_fixture.cpp" /> </ItemGroup> <ItemGroup> <ProjectReference Include="..\libbitcoinconsensus\libbitcoinconsensus.vcxproj"> diff --git a/configure.ac b/configure.ac index 72bd785e2e..c4b1d63bfe 100644 --- a/configure.ac +++ b/configure.ac @@ -4,11 +4,12 @@ define(_CLIENT_VERSION_MAJOR, 0) define(_CLIENT_VERSION_MINOR, 17) define(_CLIENT_VERSION_REVISION, 99) define(_CLIENT_VERSION_BUILD, 0) +define(_CLIENT_VERSION_RC, 0) define(_CLIENT_VERSION_IS_RELEASE, false) -define(_COPYRIGHT_YEAR, 2018) +define(_COPYRIGHT_YEAR, 2019) define(_COPYRIGHT_HOLDERS,[The %s developers]) define(_COPYRIGHT_HOLDERS_SUBSTITUTION,[[Bitcoin Core]]) -AC_INIT([Bitcoin Core],[_CLIENT_VERSION_MAJOR._CLIENT_VERSION_MINOR._CLIENT_VERSION_REVISION],[https://github.com/bitcoin/bitcoin/issues],[bitcoin],[https://bitcoincore.org/]) +AC_INIT([Bitcoin Core],m4_join([.], _CLIENT_VERSION_MAJOR, _CLIENT_VERSION_MINOR, _CLIENT_VERSION_REVISION, m4_if(_CLIENT_VERSION_BUILD, [0], [], _CLIENT_VERSION_BUILD))m4_if(_CLIENT_VERSION_RC, [0], [], [rc]_CLIENT_VERSION_RC),[https://github.com/bitcoin/bitcoin/issues],[bitcoin],[https://bitcoincore.org/]) AC_CONFIG_SRCDIR([src/validation.cpp]) AC_CONFIG_HEADERS([src/config/bitcoin-config.h]) AC_CONFIG_AUX_DIR([build-aux]) @@ -209,6 +210,11 @@ AC_ARG_ENABLE([zmq], [disable ZMQ notifications])], [use_zmq=$enableval], [use_zmq=yes]) +AC_ARG_ENABLE([bip70], + [AS_HELP_STRING([--disable-bip70], + [disable BIP70 (payment protocol) support in GUI (enabled by default)])], + [enable_bip70=$enableval], + [enable_bip70=auto]) AC_ARG_WITH([protoc-bindir],[AS_HELP_STRING([--with-protoc-bindir=BIN_DIR],[specify protoc bin path])], [protoc_bin_path=$withval], []) @@ -289,7 +295,14 @@ if test x$use_sanitizers != x; then AX_CHECK_LINK_FLAG( [[-fsanitize=$use_sanitizers]], [[SANITIZER_LDFLAGS=-fsanitize=$use_sanitizers]], - [AC_MSG_ERROR([linker did not accept requested flags, you are missing required libraries])]) + [AC_MSG_ERROR([linker did not accept requested flags, you are missing required libraries])], + [], + [AC_LANG_PROGRAM([[ + #include <cstdint> + #include <cstddef> + extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { return 0; } + __attribute__((weak)) // allow for libFuzzer linking + ]],[[]])]) fi ERROR_CXXFLAGS= @@ -498,17 +511,6 @@ case $host in LEVELDB_TARGET_FLAGS="-DOS_MACOSX" if test x$cross_compiling != xyes; then BUILD_OS=darwin - AC_CHECK_PROG([PORT],port, port) - if test x$PORT = xport; then - dnl add default macports paths - CPPFLAGS="$CPPFLAGS -isystem /opt/local/include" - LIBS="$LIBS -L/opt/local/lib" - if test -d /opt/local/include/db48; then - CPPFLAGS="$CPPFLAGS -I/opt/local/include/db48" - LIBS="$LIBS -L/opt/local/lib/db48" - fi - fi - AC_PATH_PROGS([RSVG_CONVERT], [rsvg-convert rsvg],rsvg-convert) AC_CHECK_PROG([BREW],brew, brew) if test x$BREW = xbrew; then @@ -1082,11 +1084,13 @@ if test x$use_pkgconfig = xyes; then [ PKG_CHECK_MODULES([SSL], [libssl],, [AC_MSG_ERROR(openssl not found.)]) PKG_CHECK_MODULES([CRYPTO], [libcrypto],,[AC_MSG_ERROR(libcrypto not found.)]) - BITCOIN_QT_CHECK([PKG_CHECK_MODULES([PROTOBUF], [protobuf], [have_protobuf=yes], [BITCOIN_QT_FAIL(libprotobuf not found)])]) + if test x$enable_bip70 != xno; then + BITCOIN_QT_CHECK([PKG_CHECK_MODULES([PROTOBUF], [protobuf], [have_protobuf=yes], [have_protobuf=no])]) + fi if test x$use_qr != xno; then BITCOIN_QT_CHECK([PKG_CHECK_MODULES([QR], [libqrencode], [have_qrencode=yes], [have_qrencode=no])]) fi - if test x$build_bitcoin_cli$build_bitcoin_tx$build_bitcoind$bitcoin_enable_qt$use_tests != xnonononono; then + if test x$build_bitcoin_cli$build_bitcoind$bitcoin_enable_qt$use_tests != xnononono; then PKG_CHECK_MODULES([EVENT], [libevent],, [AC_MSG_ERROR(libevent not found.)]) if test x$TARGET_OS != xwindows; then PKG_CHECK_MODULES([EVENT_PTHREADS], [libevent_pthreads],, [AC_MSG_ERROR(libevent_pthreads not found.)]) @@ -1111,7 +1115,7 @@ else AC_CHECK_HEADER([openssl/ssl.h],, AC_MSG_ERROR(libssl headers missing),) AC_CHECK_LIB([ssl], [main],SSL_LIBS=-lssl, AC_MSG_ERROR(libssl missing)) - if test x$build_bitcoin_cli$build_bitcoin_tx$build_bitcoind$bitcoin_enable_qt$use_tests != xnonononono; then + if test x$build_bitcoin_cli$build_bitcoind$bitcoin_enable_qt$use_tests != xnononono; then AC_CHECK_HEADER([event2/event.h],, AC_MSG_ERROR(libevent headers missing),) AC_CHECK_LIB([event],[main],EVENT_LIBS=-levent,AC_MSG_ERROR(libevent missing)) if test x$TARGET_OS != xwindows; then @@ -1142,7 +1146,9 @@ else esac fi - BITCOIN_QT_CHECK(AC_CHECK_LIB([protobuf] ,[main],[PROTOBUF_LIBS=-lprotobuf], BITCOIN_QT_FAIL(libprotobuf not found))) + if test x$enable_bip70 != xno; then + BITCOIN_QT_CHECK(AC_CHECK_LIB([protobuf] ,[main],[PROTOBUF_LIBS=-lprotobuf], [have_protobuf=no])) + fi if test x$use_qr != xno; then BITCOIN_QT_CHECK([AC_CHECK_LIB([qrencode], [main],[QR_LIBS=-lqrencode], [have_qrencode=no])]) BITCOIN_QT_CHECK([AC_CHECK_HEADER([qrencode.h],, have_qrencode=no)]) @@ -1220,7 +1226,11 @@ AM_CONDITIONAL([EMBEDDED_UNIVALUE],[test x$need_bundled_univalue = xyes]) AC_SUBST(UNIVALUE_CFLAGS) AC_SUBST(UNIVALUE_LIBS) -BITCOIN_QT_PATH_PROGS([PROTOC], [protoc],$protoc_bin_path) + +if test x$have_protobuf != xno && + test x$enable_bip70 != xno; then + BITCOIN_QT_PATH_PROGS([PROTOC], [protoc],$protoc_bin_path) +fi AC_MSG_CHECKING([whether to build bitcoind]) AM_CONDITIONAL([BUILD_BITCOIND], [test x$build_bitcoind = xyes]) @@ -1338,6 +1348,23 @@ if test x$bitcoin_enable_qt != xno; then else AC_MSG_RESULT([no]) fi + + AC_MSG_CHECKING([whether to build BIP70 support]) + if test x$have_protobuf = xno; then + if test x$enable_bip70 = xyes; then + AC_MSG_ERROR(protobuf missing) + fi + enable_bip70=no + AC_MSG_RESULT(no) + else + if test x$enable_bip70 != xno; then + AC_DEFINE([ENABLE_BIP70],[1],[Define if BIP70 support should be compiled in]) + enable_bip70=yes + AC_MSG_RESULT([yes]) + else + AC_MSG_RESULT([no]) + fi + fi fi AM_CONDITIONAL([ENABLE_ZMQ], [test "x$use_zmq" = "xyes"]) @@ -1369,6 +1396,7 @@ AM_CONDITIONAL([ENABLE_WALLET],[test x$enable_wallet = xyes]) AM_CONDITIONAL([ENABLE_TESTS],[test x$BUILD_TEST = xyes]) AM_CONDITIONAL([ENABLE_QT],[test x$bitcoin_enable_qt = xyes]) AM_CONDITIONAL([ENABLE_QT_TESTS],[test x$BUILD_TEST_QT = xyes]) +AM_CONDITIONAL([ENABLE_BIP70],[test x$enable_bip70 = xyes]) AM_CONDITIONAL([ENABLE_BENCH],[test x$use_bench = xyes]) AM_CONDITIONAL([USE_QRCODE], [test x$use_qr = xyes]) AM_CONDITIONAL([USE_LCOV],[test x$use_lcov = xyes]) @@ -1503,6 +1531,7 @@ echo "Options used to compile and link:" echo " with wallet = $enable_wallet" echo " with gui / qt = $bitcoin_enable_qt" if test x$bitcoin_enable_qt != xno; then + echo " with bip70 = $enable_bip70" echo " with qr = $use_qr" fi echo " with zmq = $use_zmq" diff --git a/contrib/debian/copyright b/contrib/debian/copyright index e5b9cbaa40..2d5b0188d2 100644 --- a/contrib/debian/copyright +++ b/contrib/debian/copyright @@ -5,7 +5,7 @@ Upstream-Contact: Satoshi Nakamoto <satoshin@gmx.com> Source: https://github.com/bitcoin/bitcoin Files: * -Copyright: 2009-2018, Bitcoin Core Developers +Copyright: 2009-2019, Bitcoin Core Developers License: Expat Comment: The Bitcoin Core Developers encompasses the current developers listed on bitcoin.org, as well as the numerous contributors to the project. diff --git a/contrib/devtools/README.md b/contrib/devtools/README.md index a0b6225345..6ee65f40be 100644 --- a/contrib/devtools/README.md +++ b/contrib/devtools/README.md @@ -119,7 +119,25 @@ Configuring the github-merge tool for the bitcoin repository is done in the foll git config githubmerge.repository bitcoin/bitcoin git config githubmerge.testcmd "make -j4 check" (adapt to whatever you want to use for testing) - git config --global user.signingkey mykeyid (if you want to GPG sign) + git config --global user.signingkey mykeyid + +Authentication (optional) +-------------------------- + +The API request limit for unauthenticated requests is quite low, but the +limit for authenticated requests is much higher. If you start running +into rate limiting errors it can be useful to set an authentication token +so that the script can authenticate requests. + +- First, go to [Personal access tokens](https://github.com/settings/tokens). +- Click 'Generate new token'. +- Fill in an arbitrary token description. No further privileges are needed. +- Click the `Generate token` button at the bottom of the form. +- Copy the generated token (should be a hexadecimal string) + +Then do: + + git config --global user.ghtoken "pasted token" Create and verify timestamps of merge commits --------------------------------------------- diff --git a/contrib/devtools/clang-format-diff.py b/contrib/devtools/clang-format-diff.py index 77e845a9b4..f322b3a880 100755 --- a/contrib/devtools/clang-format-diff.py +++ b/contrib/devtools/clang-format-diff.py @@ -109,7 +109,7 @@ def main(): match = re.search('^\+\+\+\ (.*?/){%s}(\S*)' % args.p, line) if match: filename = match.group(2) - if filename == None: + if filename is None: continue if args.regex is not None: diff --git a/contrib/devtools/copyright_header.py b/contrib/devtools/copyright_header.py index c5fcddea04..6d7a592f01 100755 --- a/contrib/devtools/copyright_header.py +++ b/contrib/devtools/copyright_header.py @@ -15,35 +15,32 @@ import os ################################################################################ EXCLUDE = [ - # libsecp256k1: - 'src/secp256k1/include/secp256k1.h', - 'src/secp256k1/include/secp256k1_ecdh.h', - 'src/secp256k1/include/secp256k1_recovery.h', - 'src/secp256k1/include/secp256k1_schnorr.h', - 'src/secp256k1/src/java/org_bitcoin_NativeSecp256k1.c', - 'src/secp256k1/src/java/org_bitcoin_NativeSecp256k1.h', - 'src/secp256k1/src/java/org_bitcoin_Secp256k1Context.c', - 'src/secp256k1/src/java/org_bitcoin_Secp256k1Context.h', - # univalue: - 'src/univalue/test/object.cpp', - 'src/univalue/lib/univalue_escapes.h', # auto generated: 'src/qt/bitcoinstrings.cpp', 'src/chainparamsseeds.h', # other external copyrights: 'src/tinyformat.h', - 'src/leveldb/util/env_win.cc', - 'src/crypto/ctaes/bench.c', 'test/functional/test_framework/bignum.py', # python init: '*__init__.py', ] EXCLUDE_COMPILED = re.compile('|'.join([fnmatch.translate(m) for m in EXCLUDE])) +EXCLUDE_DIRS = [ + # git subtrees + "src/crypto/ctaes/", + "src/leveldb/", + "src/secp256k1/", + "src/univalue/", +] + INCLUDE = ['*.h', '*.cpp', '*.cc', '*.c', '*.py'] INCLUDE_COMPILED = re.compile('|'.join([fnmatch.translate(m) for m in INCLUDE])) def applies_to_file(filename): + for excluded_dir in EXCLUDE_DIRS: + if filename.startswith(excluded_dir): + return False return ((EXCLUDE_COMPILED.match(filename) is None) and (INCLUDE_COMPILED.match(filename) is not None)) @@ -81,7 +78,7 @@ ANY_COPYRIGHT_STYLE_OR_YEAR_STYLE = ("%s %s" % (ANY_COPYRIGHT_STYLE, ANY_COPYRIGHT_COMPILED = re.compile(ANY_COPYRIGHT_STYLE_OR_YEAR_STYLE) def compile_copyright_regex(copyright_style, year_style, name): - return re.compile('%s %s %s' % (copyright_style, year_style, name)) + return re.compile('%s %s,? %s' % (copyright_style, year_style, name)) EXPECTED_HOLDER_NAMES = [ "Satoshi Nakamoto\n", @@ -107,6 +104,9 @@ EXPECTED_HOLDER_NAMES = [ "Jan-Klaas Kollhof\n", "Sam Rushing\n", "ArtForz -- public domain half-a-node\n", + "Intel Corporation", + "The Zcash developers", + "Jeremy Rubin", ] DOMINANT_STYLE_COMPILED = {} @@ -491,7 +491,7 @@ def get_git_change_year_range(filename): def file_already_has_core_copyright(file_lines): index, _ = get_updatable_copyright_line(file_lines) - return index != None + return index is not None ################################################################################ # insert header execution diff --git a/contrib/devtools/github-merge.py b/contrib/devtools/github-merge.py index 4e90f85f50..ab834b7623 100755 --- a/contrib/devtools/github-merge.py +++ b/contrib/devtools/github-merge.py @@ -14,7 +14,6 @@ # In case of a clean merge that is accepted by the user, the local branch with # name $BRANCH is overwritten with the merged result, and optionally pushed. -from __future__ import division,print_function,unicode_literals import os from sys import stdin,stdout,stderr import argparse @@ -23,10 +22,8 @@ import subprocess import sys import json import codecs -try: - from urllib.request import Request,urlopen -except: - from urllib2 import Request,urlopen +from urllib.request import Request, urlopen +from urllib.error import HTTPError # External tools (can be overridden using environment) GIT = os.getenv('GIT','git') @@ -50,17 +47,24 @@ def git_config_get(option, default=None): except subprocess.CalledProcessError: return default -def retrieve_pr_info(repo,pull): +def retrieve_pr_info(repo,pull,ghtoken): ''' Retrieve pull request information from github. Return None if no title can be found, or an error happens. ''' try: req = Request("https://api.github.com/repos/"+repo+"/pulls/"+pull) + if ghtoken is not None: + req.add_header('Authorization', 'token ' + ghtoken) result = urlopen(req) reader = codecs.getreader('utf-8') obj = json.load(reader(result)) return obj + except HTTPError as e: + error_message = e.read() + print('Warning: unable to retrieve pull information from github: %s' % e) + print('Detailed error: %s' % error_message) + return None except Exception as e: print('Warning: unable to retrieve pull information from github: %s' % e) return None @@ -138,6 +142,7 @@ def parse_arguments(): In addition, you can set the following git configuration variables: githubmerge.repository (mandatory), user.signingkey (mandatory), + user.ghtoken (default: none). githubmerge.host (default: git@github.com), githubmerge.branch (no default), githubmerge.testcmd (default: none). @@ -156,6 +161,7 @@ def main(): host = git_config_get('githubmerge.host','git@github.com') opt_branch = git_config_get('githubmerge.branch',None) testcmd = git_config_get('githubmerge.testcmd') + ghtoken = git_config_get('user.ghtoken') signingkey = git_config_get('user.signingkey') if repo is None: print("ERROR: No repository configured. Use this command to set:", file=stderr) @@ -166,14 +172,17 @@ def main(): print("git config --global user.signingkey <key>",file=stderr) sys.exit(1) - host_repo = host+":"+repo # shortcut for push/pull target + if host.startswith(('https:','http:')): + host_repo = host+"/"+repo+".git" + else: + host_repo = host+":"+repo # Extract settings from command line args = parse_arguments() pull = str(args.pull[0]) # Receive pull information from github - info = retrieve_pr_info(repo,pull) + info = retrieve_pr_info(repo,pull,ghtoken) if info is None: sys.exit(1) title = info['title'].strip() diff --git a/contrib/devtools/security-check.py b/contrib/devtools/security-check.py index 57af1c1066..44b7f6c7cc 100755 --- a/contrib/devtools/security-check.py +++ b/contrib/devtools/security-check.py @@ -86,7 +86,7 @@ def check_ELF_RELRO(executable): # This does not affect security: the permission flags of the GNU_RELRO program header are ignored, the PT_LOAD header determines the effective permissions. # However, the dynamic linker need to write to this area so these are RW. # Glibc itself takes care of mprotecting this area R after relocations are finished. - # See also http://permalink.gmane.org/gmane.comp.gnu.binutils/71347 + # See also https://marc.info/?l=binutils&m=1498883354122353 if typ == 'GNU_RELRO': have_gnu_relro = True diff --git a/contrib/devtools/update-translations.py b/contrib/devtools/update-translations.py index f0098cfcdf..1b9d3a4c27 100755 --- a/contrib/devtools/update-translations.py +++ b/contrib/devtools/update-translations.py @@ -125,7 +125,7 @@ def escape_cdata(text): return text def contains_bitcoin_addr(text, errors): - if text != None and ADDRESS_REGEXP.search(text) != None: + if text is not None and ADDRESS_REGEXP.search(text) is not None: errors.append('Translation "%s" contains a bitcoin address. This will be removed.' % (text)) return True return False diff --git a/contrib/gitian-build.py b/contrib/gitian-build.py index 2e8c99247d..fc7fbb764d 100755 --- a/contrib/gitian-build.py +++ b/contrib/gitian-build.py @@ -51,8 +51,10 @@ def build(): os.chdir('gitian-builder') os.makedirs('inputs', exist_ok=True) - subprocess.check_call(['wget', '-N', '-P', 'inputs', 'http://downloads.sourceforge.net/project/osslsigncode/osslsigncode/osslsigncode-1.7.1.tar.gz']) + subprocess.check_call(['wget', '-N', '-P', 'inputs', 'https://downloads.sourceforge.net/project/osslsigncode/osslsigncode/osslsigncode-1.7.1.tar.gz']) subprocess.check_call(['wget', '-N', '-P', 'inputs', 'https://bitcoincore.org/cfields/osslsigncode-Backports-to-1.7.1.patch']) + subprocess.check_call(["echo 'a8c4e9cafba922f89de0df1f2152e7be286aba73f78505169bc351a7938dd911 inputs/osslsigncode-Backports-to-1.7.1.patch' | sha256sum -c"], shell=True) + subprocess.check_call(["echo 'f9a8cdb38b9c309326764ebc937cba1523a3a751a7ab05df3ecc99d18ae466c9 inputs/osslsigncode-1.7.1.tar.gz' | sha256sum -c"], shell=True) subprocess.check_call(['make', '-C', '../bitcoin/depends', 'download', 'SOURCES_PATH=' + os.getcwd() + '/cache/common']) if args.linux: @@ -65,14 +67,14 @@ def build(): print('\nCompiling ' + args.version + ' Windows') subprocess.check_call(['bin/gbuild', '-j', args.jobs, '-m', args.memory, '--commit', 'bitcoin='+args.commit, '--url', 'bitcoin='+args.url, '../bitcoin/contrib/gitian-descriptors/gitian-win.yml']) subprocess.check_call(['bin/gsign', '-p', args.sign_prog, '--signer', args.signer, '--release', args.version+'-win-unsigned', '--destination', '../gitian.sigs/', '../bitcoin/contrib/gitian-descriptors/gitian-win.yml']) - subprocess.check_call('mv build/out/bitcoin-*-win-unsigned.tar.gz inputs/bitcoin-win-unsigned.tar.gz', shell=True) + subprocess.check_call('mv build/out/bitcoin-*-win-unsigned.tar.gz inputs/', shell=True) subprocess.check_call('mv build/out/bitcoin-*.zip build/out/bitcoin-*.exe ../bitcoin-binaries/'+args.version, shell=True) if args.macos: print('\nCompiling ' + args.version + ' MacOS') subprocess.check_call(['bin/gbuild', '-j', args.jobs, '-m', args.memory, '--commit', 'bitcoin='+args.commit, '--url', 'bitcoin='+args.url, '../bitcoin/contrib/gitian-descriptors/gitian-osx.yml']) subprocess.check_call(['bin/gsign', '-p', args.sign_prog, '--signer', args.signer, '--release', args.version+'-osx-unsigned', '--destination', '../gitian.sigs/', '../bitcoin/contrib/gitian-descriptors/gitian-osx.yml']) - subprocess.check_call('mv build/out/bitcoin-*-osx-unsigned.tar.gz inputs/bitcoin-osx-unsigned.tar.gz', shell=True) + subprocess.check_call('mv build/out/bitcoin-*-osx-unsigned.tar.gz inputs/', shell=True) subprocess.check_call('mv build/out/bitcoin-*.tar.gz build/out/bitcoin-*.dmg ../bitcoin-binaries/'+args.version, shell=True) os.chdir(workdir) @@ -92,6 +94,7 @@ def sign(): if args.windows: print('\nSigning ' + args.version + ' Windows') + subprocess.check_call('cp inputs/bitcoin-' + args.version + '-win-unsigned.tar.gz inputs/bitcoin-win-unsigned.tar.gz', shell=True) subprocess.check_call(['bin/gbuild', '-i', '--commit', 'signature='+args.commit, '../bitcoin/contrib/gitian-descriptors/gitian-win-signer.yml']) subprocess.check_call(['bin/gsign', '-p', args.sign_prog, '--signer', args.signer, '--release', args.version+'-win-signed', '--destination', '../gitian.sigs/', '../bitcoin/contrib/gitian-descriptors/gitian-win-signer.yml']) subprocess.check_call('mv build/out/bitcoin-*win64-setup.exe ../bitcoin-binaries/'+args.version, shell=True) @@ -99,6 +102,7 @@ def sign(): if args.macos: print('\nSigning ' + args.version + ' MacOS') + subprocess.check_call('cp inputs/bitcoin-' + args.version + '-osx-unsigned.tar.gz inputs/bitcoin-osx-unsigned.tar.gz', shell=True) subprocess.check_call(['bin/gbuild', '-i', '--commit', 'signature='+args.commit, '../bitcoin/contrib/gitian-descriptors/gitian-osx-signer.yml']) subprocess.check_call(['bin/gsign', '-p', args.sign_prog, '--signer', args.signer, '--release', args.version+'-osx-signed', '--destination', '../gitian.sigs/', '../bitcoin/contrib/gitian-descriptors/gitian-osx-signer.yml']) subprocess.check_call('mv build/out/bitcoin-osx-signed.dmg ../bitcoin-binaries/'+args.version+'/bitcoin-'+args.version+'-osx.dmg', shell=True) diff --git a/contrib/init/README.md b/contrib/init/README.md index 8d3e57c526..306a37f75a 100644 --- a/contrib/init/README.md +++ b/contrib/init/README.md @@ -5,7 +5,7 @@ Upstart: bitcoind.conf OpenRC: bitcoind.openrc bitcoind.openrcconf CentOS: bitcoind.init -macOS: org.bitcoin.bitcoind.plist +macOS: org.bitcoin.bitcoind.plist ``` have been made available to assist packagers in creating node packages here. diff --git a/contrib/install_db4.sh b/contrib/install_db4.sh index 4f74e67f2f..e90b8af7e1 100755 --- a/contrib/install_db4.sh +++ b/contrib/install_db4.sh @@ -51,7 +51,7 @@ http_get() { if [ -f "${2}" ]; then echo "File ${2} already exists; not downloading again" elif check_exists curl; then - curl --insecure "${1}" -o "${2}" + curl --insecure --retry 5 "${1}" -o "${2}" else wget --no-check-certificate "${1}" -O "${2}" fi diff --git a/contrib/linearize/linearize-data.py b/contrib/linearize/linearize-data.py index b6ead4a166..56c1fbfc92 100755 --- a/contrib/linearize/linearize-data.py +++ b/contrib/linearize/linearize-data.py @@ -7,7 +7,6 @@ # file COPYING or http://www.opensource.org/licenses/mit-license.php. # -from __future__ import print_function, division import struct import re import os diff --git a/contrib/linearize/linearize-hashes.py b/contrib/linearize/linearize-hashes.py index 911c3c959d..e10b46d831 100755 --- a/contrib/linearize/linearize-hashes.py +++ b/contrib/linearize/linearize-hashes.py @@ -7,11 +7,7 @@ # file COPYING or http://www.opensource.org/licenses/mit-license.php. # -from __future__ import print_function -try: # Python 3 - import http.client as httplib -except ImportError: # Python 2 - import httplib +from http.client import HttpConnection import json import re import base64 @@ -31,7 +27,7 @@ class BitcoinRPC: authpair = "%s:%s" % (username, password) authpair = authpair.encode('utf-8') self.authhdr = b"Basic " + base64.b64encode(authpair) - self.conn = httplib.HTTPConnection(host, port=port, timeout=30) + self.conn = HttpConnection(host, port=port, timeout=30) def execute(self, obj): try: diff --git a/contrib/macdeploy/custom_dsstore.py b/contrib/macdeploy/custom_dsstore.py index 6fa134972a..dc1c1882dd 100755 --- a/contrib/macdeploy/custom_dsstore.py +++ b/contrib/macdeploy/custom_dsstore.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -# Copyright (c) 2013-2017 The Bitcoin Core developers +# Copyright (c) 2013-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. import biplist @@ -13,7 +13,7 @@ package_name_ns = sys.argv[2] ds = DSStore.open(output_file, 'w+') ds['.']['bwsp'] = { 'ShowStatusBar': False, - 'WindowBounds': b'{{300, 280}, {500, 343}}', + 'WindowBounds': '{{300, 280}, {500, 343}}', 'ContainerShowSidebar': False, 'SidebarWidth': 0, 'ShowTabView': False, diff --git a/contrib/macdeploy/macdeployqtplus b/contrib/macdeploy/macdeployqtplus index 17ce6c44f9..9da03e5b02 100755 --- a/contrib/macdeploy/macdeployqtplus +++ b/contrib/macdeploy/macdeployqtplus @@ -172,12 +172,6 @@ class DeploymentInfo(object): if os.path.exists(os.path.join(parentDir, "translations")): # Classic layout, e.g. "/usr/local/Trolltech/Qt-4.x.x" self.qtPath = parentDir - elif os.path.exists(os.path.join(parentDir, "share", "qt4", "translations")): - # MacPorts layout, e.g. "/opt/local/share/qt4" - self.qtPath = os.path.join(parentDir, "share", "qt4") - elif os.path.exists(os.path.join(os.path.dirname(parentDir), "share", "qt4", "translations")): - # Newer Macports layout - self.qtPath = os.path.join(os.path.dirname(parentDir), "share", "qt4") else: self.qtPath = os.getenv("QTDIR", None) diff --git a/contrib/qos/tc.sh b/contrib/qos/tc.sh index 738ea70dbe..5f9b87d9b2 100644 --- a/contrib/qos/tc.sh +++ b/contrib/qos/tc.sh @@ -1,3 +1,5 @@ +#!/usr/bin/env bash +# # Copyright (c) 2017 The Bitcoin Core developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. diff --git a/contrib/verify-commits/README.md b/contrib/verify-commits/README.md index aa805ad1b9..27ca15acb4 100644 --- a/contrib/verify-commits/README.md +++ b/contrib/verify-commits/README.md @@ -3,7 +3,7 @@ Tooling for verification of PGP signed commits This is an incomplete work in progress, but currently includes a pre-push hook script (`pre-push-hook.sh`) for maintainers to ensure that their own commits -are PGP signed (nearly always merge commits), as well as a script to verify +are PGP signed (nearly always merge commits), as well as a Python 3 script to verify commits against a trusted keys list. @@ -17,9 +17,11 @@ be backdoored. Instead, you need to use a trusted version of verify-commits prior to checkout to make sure you're checking out only code signed by trusted keys: - git fetch origin && \ - ./contrib/verify-commits/verify-commits.py origin/master && \ - git checkout origin/master + ```sh + git fetch origin && \ + ./contrib/verify-commits/verify-commits.py origin/master && \ + git checkout origin/master + ``` Note that the above isn't a good UI/UX yet, and needs significant improvements to make it more convenient and reduce the chance of errors; pull-reqs @@ -33,6 +35,13 @@ Configuration files * `trusted-keys`: This file should contain a \n-delimited list of all PGP fingerprints of authorized commit signers (primary, not subkeys). * `allow-revsig-commits`: This file should contain a \n-delimited list of git commit hashes. See next section for more info. +Import trusted keys +------------------- +In order to check the commit signatures you must add the trusted PGP keys to your machine. This can be done in Linux by running +```sh +gpg --recv-keys $(<contrib/verify-commits/trusted-keys) +``` + Key expiry/revocation --------------------- diff --git a/contrib/verify-commits/trusted-keys b/contrib/verify-commits/trusted-keys index 5610692616..a10da9d822 100644 --- a/contrib/verify-commits/trusted-keys +++ b/contrib/verify-commits/trusted-keys @@ -2,3 +2,4 @@ 133EAC179436F14A5CF1B794860FEB804E669320 32EE5C4C3FA15CCADB46ABE529D4BCB6416F53EC B8B3F1C0E58C15DB6A81D30C3648A882F4316B9B +CA03882CB1FC067B5D3ACFE4D300116E1C875A3D diff --git a/contrib/verify-commits/verify-commits.py b/contrib/verify-commits/verify-commits.py index 544f4dc48d..b3c8064ec2 100755 --- a/contrib/verify-commits/verify-commits.py +++ b/contrib/verify-commits/verify-commits.py @@ -139,7 +139,7 @@ def main(): if len(parents) == 2 and check_merge and not allow_unclean: current_tree = subprocess.check_output([GIT, 'show', '--format=%T', current_commit], universal_newlines=True, encoding='utf8').splitlines()[0] subprocess.call([GIT, 'checkout', '--force', '--quiet', parents[0]]) - subprocess.call([GIT, 'merge', '--no-ff', '--quiet', parents[1]], stdout=subprocess.DEVNULL) + subprocess.call([GIT, 'merge', '--no-ff', '--quiet', '--no-gpg-sign', parents[1]], stdout=subprocess.DEVNULL) recreated_tree = subprocess.check_output([GIT, 'show', '--format=format:%T', 'HEAD'], universal_newlines=True, encoding='utf8').splitlines()[0] if current_tree != recreated_tree: print("Merge commit {} is not clean".format(current_commit), file=sys.stderr) diff --git a/contrib/zmq/zmq_sub.py b/contrib/zmq/zmq_sub.py index 20763e935d..06893407f5 100644 --- a/contrib/zmq/zmq_sub.py +++ b/contrib/zmq/zmq_sub.py @@ -42,6 +42,7 @@ class ZMQHandler(): self.zmqContext = zmq.asyncio.Context() self.zmqSubSocket = self.zmqContext.socket(zmq.SUB) + self.zmqSubSocket.setsockopt(zmq.RCVHWM, 0) self.zmqSubSocket.setsockopt_string(zmq.SUBSCRIBE, "hashblock") self.zmqSubSocket.setsockopt_string(zmq.SUBSCRIBE, "hashtx") self.zmqSubSocket.setsockopt_string(zmq.SUBSCRIBE, "rawblock") diff --git a/contrib/zmq/zmq_sub3.4.py b/contrib/zmq/zmq_sub3.4.py index 7e608c1a50..66fdf7887f 100644 --- a/contrib/zmq/zmq_sub3.4.py +++ b/contrib/zmq/zmq_sub3.4.py @@ -46,6 +46,7 @@ class ZMQHandler(): self.zmqContext = zmq.asyncio.Context() self.zmqSubSocket = self.zmqContext.socket(zmq.SUB) + self.zmqSubSocket.setsockopt(zmq.RCVHWM, 0) self.zmqSubSocket.setsockopt_string(zmq.SUBSCRIBE, "hashblock") self.zmqSubSocket.setsockopt_string(zmq.SUBSCRIBE, "hashtx") self.zmqSubSocket.setsockopt_string(zmq.SUBSCRIBE, "rawblock") diff --git a/depends/README.md b/depends/README.md index 73bfc8b726..693bc36197 100644 --- a/depends/README.md +++ b/depends/README.md @@ -30,29 +30,38 @@ Common `host-platform-triplets` for cross compilation are: No other options are needed, the paths are automatically configured. -Install the required dependencies: Ubuntu & Debian --------------------------------------------------- +### Install the required dependencies: Ubuntu & Debian -For macOS cross compilation: +#### For macOS cross compilation sudo apt-get install curl librsvg2-bin libtiff-tools bsdmainutils cmake imagemagick libcap-dev libz-dev libbz2-dev python-setuptools -For Win32/Win64 cross compilation: +#### For Win32/Win64 cross compilation - see [build-windows.md](../doc/build-windows.md#cross-compilation-for-ubuntu-and-windows-subsystem-for-linux) -For linux (including i386, ARM) cross compilation: +#### For linux (including i386, ARM) cross compilation - sudo apt-get install curl g++-aarch64-linux-gnu g++-4.8-aarch64-linux-gnu gcc-4.8-aarch64-linux-gnu binutils-aarch64-linux-gnu g++-arm-linux-gnueabihf g++-4.8-arm-linux-gnueabihf gcc-4.8-arm-linux-gnueabihf binutils-arm-linux-gnueabihf g++-4.8-multilib gcc-4.8-multilib binutils-gold bsdmainutils +Common linux dependencies: + + sudo apt-get install make automake cmake curl g++-multilib libtool binutils-gold bsdmainutils pkg-config python3 + +For linux ARM cross compilation: + + sudo apt-get install g++-arm-linux-gnueabihf binutils-arm-linux-gnueabihf + +For linux AARCH64 cross compilation: + + sudo apt-get install g++-aarch64-linux-gnu binutils-aarch64-linux-gnu For linux RISC-V 64-bit cross compilation (there are no packages for 32-bit): - sudo apt-get install curl g++-riscv64-linux-gnu binutils-riscv64-linux-gnu + sudo apt-get install g++-riscv64-linux-gnu binutils-riscv64-linux-gnu RISC-V known issue: gcc-7.3.0 and gcc-7.3.1 result in a broken `test_bitcoin` executable (see https://github.com/bitcoin/bitcoin/pull/13543), this is apparently fixed in gcc-8.1.0. -Dependency Options: +### Dependency Options The following can be set when running make: make FOO=bar SOURCES_PATH: downloaded sources will be placed here @@ -70,7 +79,7 @@ The following can be set when running make: make FOO=bar If some packages are not built, for example `make NO_WALLET=1`, the appropriate options will be passed to bitcoin's configure. In this case, `--disable-wallet`. -Additional targets: +### Additional targets download: run 'make download' to fetch all sources without building them download-osx: run 'make download-osx' to fetch all sources needed for macOS builds diff --git a/depends/builders/darwin.mk b/depends/builders/darwin.mk index 27f550ab03..c7671c1548 100644 --- a/depends/builders/darwin.mk +++ b/depends/builders/darwin.mk @@ -1,13 +1,13 @@ -build_darwin_CC: = $(shell xcrun -f clang) -build_darwin_CXX: = $(shell xcrun -f clang++) -build_darwin_AR: = $(shell xcrun -f ar) -build_darwin_RANLIB: = $(shell xcrun -f ranlib) -build_darwin_STRIP: = $(shell xcrun -f strip) -build_darwin_OTOOL: = $(shell xcrun -f otool) -build_darwin_NM: = $(shell xcrun -f nm) +build_darwin_CC:=$(shell xcrun -f clang) +build_darwin_CXX:=$(shell xcrun -f clang++) +build_darwin_AR:=$(shell xcrun -f ar) +build_darwin_RANLIB:=$(shell xcrun -f ranlib) +build_darwin_STRIP:=$(shell xcrun -f strip) +build_darwin_OTOOL:=$(shell xcrun -f otool) +build_darwin_NM:=$(shell xcrun -f nm) build_darwin_INSTALL_NAME_TOOL:=$(shell xcrun -f install_name_tool) -build_darwin_SHA256SUM = shasum -a 256 -build_darwin_DOWNLOAD = curl --location --fail --connect-timeout $(DOWNLOAD_CONNECT_TIMEOUT) --retry $(DOWNLOAD_RETRIES) -o +build_darwin_SHA256SUM=shasum -a 256 +build_darwin_DOWNLOAD=curl --location --fail --connect-timeout $(DOWNLOAD_CONNECT_TIMEOUT) --retry $(DOWNLOAD_RETRIES) -o #darwin host on darwin builder. overrides darwin host preferences. darwin_CC=$(shell xcrun -f clang) -mmacosx-version-min=$(OSX_MIN_VERSION) diff --git a/depends/packages/bdb.mk b/depends/packages/bdb.mk index 6c9876c2c7..3cd2e28858 100644 --- a/depends/packages/bdb.mk +++ b/depends/packages/bdb.mk @@ -10,6 +10,7 @@ $(package)_config_opts=--disable-shared --enable-cxx --disable-replication $(package)_config_opts_mingw32=--enable-mingw $(package)_config_opts_linux=--with-pic $(package)_cxxflags=-std=c++11 +$(package)_cppflags_mingw32=-DUNICODE -D_UNICODE endef define $(package)_preprocess_cmds diff --git a/depends/packages/expat.mk b/depends/packages/expat.mk index acbc60eea3..8d06882cdb 100644 --- a/depends/packages/expat.mk +++ b/depends/packages/expat.mk @@ -1,11 +1,11 @@ package=expat -$(package)_version=2.2.5 -$(package)_download_path=https://github.com/libexpat/libexpat/releases/download/R_2_2_5/ +$(package)_version=2.2.6 +$(package)_download_path=https://github.com/libexpat/libexpat/releases/download/R_2_2_6/ $(package)_file_name=$(package)-$($(package)_version).tar.bz2 -$(package)_sha256_hash=d9dc32efba7e74f788fcc4f212a43216fc37cf5f23f4c2339664d473353aedf6 +$(package)_sha256_hash=17b43c2716d521369f82fc2dc70f359860e90fa440bea65b3b85f0b246ea81f2 define $(package)_set_vars -$(package)_config_opts=--disable-static +$(package)_config_opts=--disable-static --without-docbook endef define $(package)_config_cmds diff --git a/depends/packages/freetype.mk b/depends/packages/freetype.mk index 76b025c463..41e02e2030 100644 --- a/depends/packages/freetype.mk +++ b/depends/packages/freetype.mk @@ -5,7 +5,7 @@ $(package)_file_name=$(package)-$($(package)_version).tar.bz2 $(package)_sha256_hash=3a3bb2c4e15ffb433f2032f50a5b5a92558206822e22bfe8cbe339af4aa82f88 define $(package)_set_vars - $(package)_config_opts=--without-zlib --without-png --disable-static + $(package)_config_opts=--without-zlib --without-png --without-harfbuzz --without-bzip2 --disable-static $(package)_config_opts_linux=--with-pic endef diff --git a/depends/packages/qt.mk b/depends/packages/qt.mk index d15f147cd7..deebf13e98 100644 --- a/depends/packages/qt.mk +++ b/depends/packages/qt.mk @@ -1,9 +1,9 @@ PACKAGE=qt -$(package)_version=5.9.6 +$(package)_version=5.9.7 $(package)_download_path=https://download.qt.io/official_releases/qt/5.9/$($(package)_version)/submodules $(package)_suffix=opensource-src-$($(package)_version).tar.xz $(package)_file_name=qtbase-$($(package)_suffix) -$(package)_sha256_hash=eed620cb268b199bd83b3fc6a471c51d51e1dc2dbb5374fc97a0cc75facbe36f +$(package)_sha256_hash=36dd9574f006eaa1e5af780e4b33d11fe39d09fd7c12f3b9d83294174bd28f00 $(package)_dependencies=openssl zlib $(package)_linux_dependencies=freetype fontconfig libxcb libX11 xproto libXext $(package)_build_subdir=qtbase @@ -11,10 +11,10 @@ $(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)_qttranslations_file_name=qttranslations-$($(package)_suffix) -$(package)_qttranslations_sha256_hash=9822084f8e2d2939ba39f4af4c0c2320e45d5996762a9423f833055607604ed8 +$(package)_qttranslations_sha256_hash=b36da7d93c3ab6fca56b32053bb73bc619c8b192bb89b74e3bcde2705f1c2a14 $(package)_qttools_file_name=qttools-$($(package)_suffix) -$(package)_qttools_sha256_hash=50e75417ec0c74bb8b1989d1d8e981ee83690dce7dfc0c2169f7c00f397e5117 +$(package)_qttools_sha256_hash=d62e0f70d99645d6704dbb8976fb2222443061743689943d40970c52c49367a1 $(package)_extra_sources = $($(package)_qttranslations_file_name) $(package)_extra_sources += $($(package)_qttools_file_name) @@ -27,6 +27,7 @@ $(package)_config_opts += -c++std c++11 $(package)_config_opts += -confirm-license $(package)_config_opts += -dbus-runtime $(package)_config_opts += -hostprefix $(build_prefix) +$(package)_config_opts += -no-compile-examples $(package)_config_opts += -no-cups $(package)_config_opts += -no-egl $(package)_config_opts += -no-eglfs @@ -64,13 +65,25 @@ $(package)_config_opts += -prefix $(host_prefix) $(package)_config_opts += -qt-libpng $(package)_config_opts += -qt-libjpeg $(package)_config_opts += -qt-pcre +$(package)_config_opts += -qt-harfbuzz $(package)_config_opts += -system-zlib $(package)_config_opts += -static $(package)_config_opts += -silent $(package)_config_opts += -v +$(package)_config_opts += -no-feature-dial +$(package)_config_opts += -no-feature-ftp +$(package)_config_opts += -no-feature-lcdnumber +$(package)_config_opts += -no-feature-pdf $(package)_config_opts += -no-feature-printer $(package)_config_opts += -no-feature-printdialog $(package)_config_opts += -no-feature-concurrent +$(package)_config_opts += -no-feature-sql +$(package)_config_opts += -no-feature-statemachine +$(package)_config_opts += -no-feature-syntaxhighlighter +$(package)_config_opts += -no-feature-textbrowser +$(package)_config_opts += -no-feature-textodfwriter +$(package)_config_opts += -no-feature-udpsocket +$(package)_config_opts += -no-feature-wizard $(package)_config_opts += -no-feature-xml ifneq ($(build_os),darwin) diff --git a/depends/packages/zeromq.mk b/depends/packages/zeromq.mk index c9068e83a5..dfbc50580c 100644 --- a/depends/packages/zeromq.mk +++ b/depends/packages/zeromq.mk @@ -1,8 +1,8 @@ package=zeromq -$(package)_version=4.2.5 +$(package)_version=4.3.1 $(package)_download_path=https://github.com/zeromq/libzmq/releases/download/v$($(package)_version)/ $(package)_file_name=$(package)-$($(package)_version).tar.gz -$(package)_sha256_hash=cc9090ba35713d59bb2f7d7965f877036c49c5558ea0c290b0dcc6f2a17e489f +$(package)_sha256_hash=bcbabe1e2c7d0eec4ed612e10b94b112dd5f06fcefa994a0c79a45d835cd21eb $(package)_patches=0001-fix-build-with-older-mingw64.patch 0002-disable-pthread_set_name_np.patch define $(package)_set_vars diff --git a/doc/JSON-RPC-interface.md b/doc/JSON-RPC-interface.md new file mode 100644 index 0000000000..59df541567 --- /dev/null +++ b/doc/JSON-RPC-interface.md @@ -0,0 +1,38 @@ +# JSON-RPC Interface + +The headless daemon `bitcoind` has the JSON-RPC API enabled by default, the GUI +`bitcoin-qt` has it disabled by default. This can be changed with the `-server` +option. In the GUI it is possible to execute RPC methods in the Debug Console +Dialog. + +## RPC consistency guarantees + +State that can be queried via RPCs is guaranteed to be at least up-to-date with +the chain state immediately prior to the call's execution. However, the state +returned by RPCs that reflect the mempool may not be up-to-date with the +current mempool state. + +### Transaction Pool + +The mempool state returned via an RPC is consistent with itself and with the +chain state at the time of the call. Thus, the mempool state only encompasses +transactions that are considered mine-able by the node at the time of the RPC. + +The mempool state returned via an RPC reflects all effects of mempool and chain +state related RPCs that returned prior to this call. + +### Wallet + +The wallet state returned via an RPC is consistent with itself and with the +chain state at the time of the call. + +Wallet RPCs will return the latest chain state consistent with prior non-wallet +RPCs. The effects of all blocks (and transactions in blocks) at the time of the +call is reflected in the state of all wallet transactions. For example, if a +block contains transactions that conflicted with mempool transactions, the +wallet would reflect the removal of these mempool transactions in the state. + +However, the wallet may not be up-to-date with the current state of the mempool +or the state of the mempool by an RPC that returned before this RPC. For +example, a wallet transaction that was BIP-125-replaced in the mempool prior to +this RPC may not yet be reflected as such in this RPC response. diff --git a/doc/README.md b/doc/README.md index b3f875c4a4..51950d4a13 100644 --- a/doc/README.md +++ b/doc/README.md @@ -3,9 +3,9 @@ Bitcoin Core Setup --------------------- -Bitcoin Core is the original Bitcoin client and it builds the backbone of the network. It downloads and, by default, stores the entire history of Bitcoin transactions (which is currently more than 100 GBs); depending on the speed of your computer and network connection, the synchronization process can take anywhere from a few hours to a day or more. +Bitcoin Core is the original Bitcoin client and it builds the backbone of the network. It downloads and, by default, stores the entire history of Bitcoin transactions, which requires a few hundred gigabytes of disk space. Depending on the speed of your computer and network connection, the synchronization process can take anywhere from a few hours to a day or more. -To download Bitcoin Core, visit [bitcoincore.org](https://bitcoincore.org/en/releases/). +To download Bitcoin Core, visit [bitcoincore.org](https://bitcoincore.org/en/download/). Running --------------------- @@ -30,7 +30,7 @@ Drag Bitcoin Core to your applications folder, and then run Bitcoin Core. * See the documentation at the [Bitcoin Wiki](https://en.bitcoin.it/wiki/Main_Page) for help and more information. -* Ask for help on [#bitcoin](http://webchat.freenode.net?channels=bitcoin) on Freenode. If you don't have an IRC client use [webchat here](http://webchat.freenode.net?channels=bitcoin). +* Ask for help on [#bitcoin](http://webchat.freenode.net?channels=bitcoin) on Freenode. If you don't have an IRC client, use [webchat here](http://webchat.freenode.net?channels=bitcoin). * Ask for help on the [BitcoinTalk](https://bitcointalk.org/) forums, in the [Technical Support board](https://bitcointalk.org/index.php?board=4.0). Building @@ -41,9 +41,10 @@ The following are developer notes on how to build Bitcoin Core on your native pl - [macOS Build Notes](build-osx.md) - [Unix Build Notes](build-unix.md) - [Windows Build Notes](build-windows.md) +- [FreeBSD Build Notes](build-freebsd.md) - [OpenBSD Build Notes](build-openbsd.md) - [NetBSD Build Notes](build-netbsd.md) -- [Gitian Building Guide](gitian-building.md) +- [Gitian Building Guide (External Link)](https://github.com/bitcoin-core/docs/blob/master/gitian-building.md) Development --------------------- @@ -56,6 +57,7 @@ The Bitcoin repo's [root README](/README.md) contains relevant information on th - [Translation Process](translation_process.md) - [Translation Strings Policy](translation_strings_policy.md) - [Travis CI](travis-ci.md) +- [JSON-RPC Interface](JSON-RPC-interface.md) - [Unauthenticated REST Interface](REST-interface.md) - [Shared Libraries](shared-libraries.md) - [BIPS](bips.md) @@ -64,11 +66,12 @@ The Bitcoin repo's [root README](/README.md) contains relevant information on th ### Resources * Discuss on the [BitcoinTalk](https://bitcointalk.org/) forums, in the [Development & Technical Discussion board](https://bitcointalk.org/index.php?board=6.0). -* Discuss project-specific development on #bitcoin-core-dev on Freenode. If you don't have an IRC client use [webchat here](http://webchat.freenode.net/?channels=bitcoin-core-dev). -* Discuss general Bitcoin development on #bitcoin-dev on Freenode. If you don't have an IRC client use [webchat here](http://webchat.freenode.net/?channels=bitcoin-dev). +* Discuss project-specific development on #bitcoin-core-dev on Freenode. If you don't have an IRC client, use [webchat here](http://webchat.freenode.net/?channels=bitcoin-core-dev). +* Discuss general Bitcoin development on #bitcoin-dev on Freenode. If you don't have an IRC client, use [webchat here](http://webchat.freenode.net/?channels=bitcoin-dev). ### Miscellaneous - [Assets Attribution](assets-attribution.md) +- [bitcoin.conf Configuration File](bitcoin-conf.md) - [Files](files.md) - [Fuzz-testing](fuzzing.md) - [Reduce Traffic](reduce-traffic.md) diff --git a/doc/REST-interface.md b/doc/REST-interface.md index d33165cb82..d21df36130 100644 --- a/doc/REST-interface.md +++ b/doc/REST-interface.md @@ -6,6 +6,12 @@ The REST API can be enabled with the `-rest` option. The interface runs on the same port as the JSON-RPC interface, by default port 8332 for mainnet, port 18332 for testnet, and port 18443 for regtest. +REST Interface consistency guarantees +------------------------------------- + +The [same guarantees as for the RPC Interface](/doc/JSON-RPC-interface.md#rpc-consistency-guarantees) +apply. + Supported API ------------- @@ -21,6 +27,7 @@ For full TX query capability, one must enable the transaction index via "txindex `GET /rest/block/notxdetails/<BLOCK-HASH>.<bin|hex|json>` Given a block hash: returns a block, in binary, hex-encoded binary or JSON formats. +Responds with 404 if the block doesn't exist. The HTTP request and response are both handled entirely in-memory, thus making maximum memory usage at least 2.66MB (1 MB max block, plus hex encoding) per request. @@ -30,6 +37,7 @@ With the /notxdetails/ option JSON response will only contain the transaction ha `GET /rest/headers/<COUNT>/<BLOCK-HASH>.<bin|hex|json>` Given a block hash: returns <COUNT> amount of blockheaders in upward direction. +Returns empty if the block doesn't exist or it isn't in the active chain. #### Blockhash by height `GET /rest/blockhashbyheight/<HEIGHT>.<bin|hex|json>` diff --git a/doc/bitcoin-conf.md b/doc/bitcoin-conf.md new file mode 100644 index 0000000000..88ecb8fe65 --- /dev/null +++ b/doc/bitcoin-conf.md @@ -0,0 +1,37 @@ +# `bitcoin.conf` Configuration File + +The configuration file is used by `bitcoind`, `bitcoin-qt` and `bitcoin-cli`. + +All command-line options (except for `-?`, `-help`, `-version` and `-conf`) may be specified in a configuration file, and all configuration file options (except for `includeconf`) may also be specified on the command line. Command-line options override values set in the configuration file and configuration file options override values set in the GUI. + +## Configuration File Format + +The configuration file is a plain text file and consists of `option=value` entries, one per line. Leading and trailing whitespaces are removed. + +In contrast to the command-line usage: +- an option must be specified without leading `-`; +- a value of the given option is mandatory; e.g., `testnet=1` (for chain selection options), `noconnect=1` (for negated options). + +### Blank lines + +Blank lines are allowed and ignored by the parser. + +### Comments + +A comment starts with a number sign (`#`) and extends to the end of the line. All comments are ignored by the parser. + +Comments may appear in two ways: +- on their own on an otherwise empty line (_preferable_); +- after an `option=value` entry. + +### Network specific options + +Network specific options can be: +- placed into sections with headers `[main]` (not `[mainnet]`), `[test]` (not `[testnet]`) or `[regtest]`; +- prefixed with a chain name; e.g., `regtest.maxmempool=100`. + +## Configuration File Path + +The configuration file is not automatically created; you can create it using your favorite text editor. By default, the configuration file name is `bitcoin.conf` and it is located in the Bitcoin data directory, but both the Bitcoin data directory and the configuration file path may be changed using the `-datadir` and `-conf` command-line options. + +The `includeconf=<file>` option in the `bitcoin.conf` file can be used to include additional configuration files. diff --git a/doc/build-freebsd.md b/doc/build-freebsd.md index 48746ce0c2..70f5dfc882 100644 --- a/doc/build-freebsd.md +++ b/doc/build-freebsd.md @@ -14,6 +14,12 @@ You will need the following dependencies, which can be installed as root via pkg pkg install autoconf automake boost-libs git gmake libevent libtool openssl pkgconf ``` +In order to run the test suite (recommended), you will need to have Python 3 installed: + +``` +pkg install python3 +``` + For the wallet (optional): ``` ./contrib/install_db4.sh `pwd` @@ -29,17 +35,29 @@ git clone https://github.com/bitcoin/bitcoin ## Building Bitcoin Core -**Important**: Use `gmake` (the non-GNU `make` will exit with an error). +**Important**: Use `gmake` (the non-GNU `make` will exit with an error): ``` ./autogen.sh ./configure # to build with wallet OR ./configure --disable-wallet # to build without wallet +``` + +followed by either: +``` gmake ``` +to build without testing, or + +``` +gmake check +``` + +to also run the test suite (recommended, if Python 3 is installed). + *Note on debugging*: The version of `gdb` installed by default is [ancient and considered harmful](https://wiki.freebsd.org/GdbRetirement). It is not suitable for debugging a multi-threaded C++ program, not even for getting backtraces. Please install the package `gdb` and use the versioned gdb command (e.g. `gdb7111`). diff --git a/doc/build-netbsd.md b/doc/build-netbsd.md index 5bf2d6b59b..ab422f6aa7 100644 --- a/doc/build-netbsd.md +++ b/doc/build-netbsd.md @@ -1,6 +1,6 @@ NetBSD build guide ====================== -(updated for NetBSD 7.0) +(updated for NetBSD 8.0) This guide describes how to build bitcoind and command-line utilities on NetBSD. @@ -15,21 +15,38 @@ You will need the following modules, which can be installed via pkgsrc or pkgin: autoconf automake boost -db4 git gmake libevent libtool -python27 -``` +pkg-config +python37 -Download the source code: -``` -git clone https://github.com/bitcoin/bitcoin +git clone https://github.com/bitcoin/bitcoin.git ``` See [dependencies.md](dependencies.md) for a complete overview. +### Building BerkeleyDB + +BerkeleyDB is only necessary for the wallet functionality. To skip this, pass +`--disable-wallet` to `./configure` and skip to the next section. + +It is recommended to use Berkeley DB 4.8. You cannot use the BerkeleyDB library +from ports, for the same reason as boost above (g++/libstd++ incompatibility). +If you have to build it yourself, you can use [the installation script included +in contrib/](/contrib/install_db4.sh) like so: + +```shell +./contrib/install_db4.sh `pwd` +``` + +from the root of the repository. Then set `BDB_PREFIX` for the next section: + +```shell +export BDB_PREFIX="$PWD/db4" +``` + ### Building Bitcoin Core **Important**: Use `gmake` (the non-GNU `make` will exit with an error). @@ -37,13 +54,26 @@ See [dependencies.md](dependencies.md) for a complete overview. With wallet: ``` ./autogen.sh -./configure CPPFLAGS="-I/usr/pkg/include" LDFLAGS="-L/usr/pkg/lib" BOOST_CPPFLAGS="-I/usr/pkg/include" BOOST_LDFLAGS="-L/usr/pkg/lib" -gmake +./configure --with-gui=no CPPFLAGS="-I/usr/pkg/include" \ + LDFLAGS="-L/usr/pkg/lib" \ + BOOST_CPPFLAGS="-I/usr/pkg/include" \ + BOOST_LDFLAGS="-L/usr/pkg/lib" \ + BDB_LIBS="-L${BDB_PREFIX}/lib -ldb_cxx-4.8" \ + BDB_CFLAGS="-I${BDB_PREFIX}/include" ``` Without wallet: ``` ./autogen.sh -./configure --disable-wallet CPPFLAGS="-I/usr/pkg/include" LDFLAGS="-L/usr/pkg/lib" BOOST_CPPFLAGS="-I/usr/pkg/include" BOOST_LDFLAGS="-L/usr/pkg/lib" -gmake +./configure --with-gui=no --disable-wallet \ + CPPFLAGS="-I/usr/pkg/include" \ + LDFLAGS="-L/usr/pkg/lib" \ + BOOST_CPPFLAGS="-I/usr/pkg/include" \ + BOOST_LDFLAGS="-L/usr/pkg/lib" +``` + +Build and run the tests: +```bash +gmake # use -jX here for parallelism +gmake check ``` diff --git a/doc/build-openbsd.md b/doc/build-openbsd.md index 63288acf16..dad2566a6c 100644 --- a/doc/build-openbsd.md +++ b/doc/build-openbsd.md @@ -1,6 +1,6 @@ OpenBSD build guide ====================== -(updated for OpenBSD 6.3) +(updated for OpenBSD 6.4) This guide describes how to build bitcoind and command-line utilities on OpenBSD. @@ -14,7 +14,7 @@ Run the following as root to install the base dependencies for building: ```bash pkg_add git gmake libevent libtool boost pkg_add autoconf # (select highest version, e.g. 2.69) -pkg_add automake # (select highest version, e.g. 1.15) +pkg_add automake # (select highest version, e.g. 1.16) pkg_add python # (select highest version, e.g. 3.6) git clone https://github.com/bitcoin/bitcoin.git @@ -36,7 +36,7 @@ BerkeleyDB is only necessary for the wallet functionality. To skip this, pass It is recommended to use Berkeley DB 4.8. You cannot use the BerkeleyDB library from ports, for the same reason as boost above (g++/libstd++ incompatibility). If you have to build it yourself, you can use [the installation script included -in contrib/](/contrib/install_db4.sh) like so +in contrib/](/contrib/install_db4.sh) like so: ```shell ./contrib/install_db4.sh `pwd` CC=cc CXX=c++ @@ -60,8 +60,8 @@ Preparation: export AUTOCONF_VERSION=2.69 # Replace this with the automake version that you installed. Include only -# the major and minor parts of the version: use "1.15" for "automake-1.15.1". -export AUTOMAKE_VERSION=1.15 +# the major and minor parts of the version: use "1.16" for "automake-1.16.1". +export AUTOMAKE_VERSION=1.16 ./autogen.sh ``` @@ -94,7 +94,7 @@ The standard ulimit restrictions in OpenBSD are very strict: data(kbytes) 1572864 -This, unfortunately, in some cases not enough to compile some `.cpp` files in the project, +This is, unfortunately, in some cases not enough to compile some `.cpp` files in the project, (see issue [#6658](https://github.com/bitcoin/bitcoin/issues/6658)). If your user is in the `staff` group the limit can be raised with: diff --git a/doc/build-osx.md b/doc/build-osx.md index 1fa01d0d6e..c9a59bab83 100644 --- a/doc/build-osx.md +++ b/doc/build-osx.md @@ -20,7 +20,7 @@ Dependencies See [dependencies.md](dependencies.md) for a complete overview. -If you want to build the disk image with `make deploy` (.dmg / optional), you need RSVG +If you want to build the disk image with `make deploy` (.dmg / optional), you need RSVG: brew install librsvg @@ -28,7 +28,7 @@ Berkeley DB ----------- It is recommended to use Berkeley DB 4.8. If you have to build it yourself, you can use [the installation script included in contrib/](/contrib/install_db4.sh) -like so +like so: ```shell ./contrib/install_db4.sh . @@ -36,12 +36,12 @@ like so from the root of the repository. -**Note**: You only need Berkeley DB if the wallet is enabled (see the section *Disable-Wallet mode* below). +**Note**: You only need Berkeley DB if the wallet is enabled (see [*Disable-wallet mode*](/doc/build-osx.md#disable-wallet-mode)). Build Bitcoin Core ------------------------ -1. Clone the Bitcoin Core source code and cd into `bitcoin` +1. Clone the Bitcoin Core source code: git clone https://github.com/bitcoin/bitcoin cd bitcoin @@ -80,13 +80,13 @@ Running Bitcoin Core is now available at `./src/bitcoind` -Before running, it's recommended that you create an RPC configuration file. +Before running, you may create an empty configuration file: - echo -e "rpcuser=bitcoinrpc\nrpcpassword=$(xxd -l 16 -p /dev/urandom)" > "/Users/${USER}/Library/Application Support/Bitcoin/bitcoin.conf" + touch "/Users/${USER}/Library/Application Support/Bitcoin/bitcoin.conf" chmod 600 "/Users/${USER}/Library/Application Support/Bitcoin/bitcoin.conf" -The first time you run bitcoind, it will start downloading the blockchain. This process could take several hours. +The first time you run bitcoind, it will start downloading the blockchain. This process could take many hours, or even days on slower than average systems. You can monitor the download process by looking at the debug.log file: diff --git a/doc/build-unix.md b/doc/build-unix.md index 3f8848abf2..522e3069ce 100644 --- a/doc/build-unix.md +++ b/doc/build-unix.md @@ -6,8 +6,8 @@ Some notes on how to build Bitcoin Core in Unix. Note --------------------- -Always use absolute paths to configure and compile Bitcoin Core and the dependencies, -for example, when specifying the path of the dependency: +Always use absolute paths to configure and compile Bitcoin Core and the dependencies. +For example, when specifying the path of the dependency: ../dist/configure --enable-cxx --disable-shared --with-pic --prefix=$BDB_PREFIX @@ -24,7 +24,7 @@ make make install # optional ``` -This will build bitcoin-qt as well if the dependencies are met. +This will build bitcoin-qt as well, if the dependencies are met. Dependencies --------------------- @@ -70,7 +70,11 @@ tuned to conserve memory with additional CXXFLAGS: Build requirements: - sudo apt-get install build-essential libtool autotools-dev automake pkg-config libssl-dev libevent-dev bsdmainutils python3 libboost-system-dev libboost-filesystem-dev libboost-chrono-dev libboost-test-dev libboost-thread-dev + sudo apt-get install build-essential libtool autotools-dev automake pkg-config bsdmainutils python3 + +Now, you can either build from self-compiled [depends](/depends/README.md) or install the required dependencies: + + sudo apt-get install libssl-dev libevent-dev libboost-system-dev libboost-filesystem-dev libboost-chrono-dev libboost-test-dev libboost-thread-dev BerkeleyDB is required for the wallet. @@ -83,11 +87,12 @@ You can add the repository and install using the following commands: sudo apt-get install libdb4.8-dev libdb4.8++-dev Ubuntu and Debian have their own libdb-dev and libdb++-dev packages, but these will install -BerkeleyDB 5.1 or later, which break binary wallet compatibility with the distributed executables which +BerkeleyDB 5.1 or later. This will break binary wallet compatibility with the distributed executables, which are based on BerkeleyDB 4.8. If you do not care about wallet compatibility, pass `--with-incompatible-bdb` to configure. -See the section "Disable-wallet mode" to build Bitcoin Core without wallet. +To build Bitcoin Core without wallet, see [*Disable-wallet mode*](/doc/build-unix.md#disable-wallet-mode) + Optional (see --with-miniupnpc and --enable-upnp-default): @@ -97,7 +102,7 @@ ZMQ dependencies (provides ZMQ API): sudo apt-get install libzmq3-dev -#### Dependencies for the GUI +GUI dependencies: If you want to build bitcoin-qt, make sure that the required packages for Qt development are installed. Qt 5 is necessary to build the GUI. @@ -157,7 +162,7 @@ Berkeley DB ----------- It is recommended to use Berkeley DB 4.8. If you have to build it yourself, you can use [the installation script included in contrib/](/contrib/install_db4.sh) -like so +like so: ```shell ./contrib/install_db4.sh `pwd` @@ -165,7 +170,7 @@ like so from the root of the repository. -**Note**: You only need Berkeley DB if the wallet is enabled (see the section *Disable-Wallet mode* below). +**Note**: You only need Berkeley DB if the wallet is enabled (see [*Disable-wallet mode*](/doc/build-unix.md#disable-wallet-mode)). Boost ----- @@ -189,9 +194,7 @@ Hardening Flags: Hardening enables the following features: - -* Position Independent Executable - Build position independent code to take advantage of Address Space Layout Randomization +* _Position Independent Executable_: Build position independent code to take advantage of Address Space Layout Randomization offered by some kernels. Attackers who can cause execution of code at an arbitrary memory location are thwarted if they don't know where anything useful is located. The stack and heap are randomly located by default, but this allows the code section to be @@ -209,8 +212,7 @@ Hardening enables the following features: TYPE ET_DYN -* Non-executable Stack - If the stack is executable then trivial stack-based buffer overflow exploits are possible if +* _Non-executable Stack_: If the stack is executable then trivial stack-based buffer overflow exploits are possible if vulnerable buffers are found. By default, Bitcoin Core should be built with a non-executable stack, but if one of the libraries it uses asks for an executable stack or someone makes a mistake and uses a compiler extension which requires an executable stack, it will silently build an diff --git a/doc/build-windows.md b/doc/build-windows.md index 12adadacdc..9641e0d3fd 100644 --- a/doc/build-windows.md +++ b/doc/build-windows.md @@ -5,15 +5,15 @@ Below are some notes on how to build Bitcoin Core for Windows. The options known to work for building Bitcoin Core on Windows are: -* On Linux using the [Mingw-w64](https://mingw-w64.org/doku.php) cross compiler tool chain. Ubuntu Bionic 18.04 is required +* On Linux, using the [Mingw-w64](https://mingw-w64.org/doku.php) cross compiler tool chain. Ubuntu Bionic 18.04 is required and is the platform used to build the Bitcoin Core Windows release binaries. -* On Windows using [Windows +* On Windows, using [Windows Subsystem for Linux (WSL)](https://msdn.microsoft.com/commandline/wsl/about) and the Mingw-w64 cross compiler tool chain. Other options which may work, but which have not been extensively tested are (please contribute instructions): -* On Windows using a POSIX compatibility layer application such as [cygwin](http://www.cygwin.com/) or [msys2](http://www.msys2.org/). -* On Windows using a native compiler tool chain such as [Visual Studio](https://www.visualstudio.com). +* On Windows, using a POSIX compatibility layer application such as [cygwin](http://www.cygwin.com/) or [msys2](http://www.msys2.org/). +* On Windows, using a native compiler tool chain such as [Visual Studio](https://www.visualstudio.com). Installing Windows Subsystem for Linux --------------------------------------- @@ -65,11 +65,15 @@ A host toolchain (`build-essential`) is necessary because some dependency packages (such as `protobuf`) need to build host utilities that are used in the build process. -See also: [dependencies.md](dependencies.md). +See [dependencies.md](dependencies.md) for a complete overview. + +If you want to build the windows installer with `make deploy` you need [NSIS](https://nsis.sourceforge.io/Main_Page): + + sudo apt install nsis ## Building for 64-bit Windows -The first step is to install the mingw-w64 cross-compilation tool chain. +The first step is to install the mingw-w64 cross-compilation tool chain: sudo apt install g++-mingw-w64-x86-64 @@ -81,13 +85,13 @@ Once the toolchain is installed the build steps are common: Note that for WSL the Bitcoin Core source path MUST be somewhere in the default mount file system, for example /usr/src/bitcoin, AND not under /mnt/d/. If this is not the case the dependency autoconf scripts will fail. -This means you cannot use a directory that located directly on the host Windows file system to perform the build. +This means you cannot use a directory that is located directly on the host Windows file system to perform the build. Acquire the source in the usual way: git clone https://github.com/bitcoin/bitcoin.git -Once the source code is ready the build steps are below. +Once the source code is ready the build steps are below: PATH=$(echo "$PATH" | sed -e 's/:\/mnt.*//g') # strip out problematic Windows %PATH% imported var cd depends @@ -133,18 +137,22 @@ Installation ------------- After building using the Windows subsystem it can be useful to copy the compiled -executables to a directory on the windows drive in the same directory structure +executables to a directory on the Windows drive in the same directory structure as they appear in the release `.zip` archive. This can be done in the following way. This will install to `c:\workspace\bitcoin`, for example: make install DESTDIR=/mnt/c/workspace/bitcoin +You can also create an installer using: + + make deploy + Footnotes --------- -<a name="footnote1">1</a>: Starting from Ubuntu Xenial 16.04 both the 32 and 64 bit Mingw-w64 packages install two different +<a name="footnote1">1</a>: Starting from Ubuntu Xenial 16.04, both the 32 and 64 bit Mingw-w64 packages install two different compiler options to allow a choice between either posix or win32 threads. The default option is win32 threads which is the more efficient since it will result in binary code that links directly with the Windows kernel32.lib. Unfortunately, the headers -required to support win32 threads conflict with some of the classes in the C++11 standard library in particular std::mutex. +required to support win32 threads conflict with some of the classes in the C++11 standard library, in particular std::mutex. It's not possible to build the Bitcoin Core code using the win32 version of the Mingw-w64 cross compilers (at least not without modifying headers in the Bitcoin Core source code). diff --git a/doc/dependencies.md b/doc/dependencies.md index d777aaf66f..b833e9151f 100644 --- a/doc/dependencies.md +++ b/doc/dependencies.md @@ -3,15 +3,15 @@ Dependencies These are the dependencies currently used by Bitcoin Core. You can find instructions for installing them in the `build-*.md` file for your platform. -| Dependency | Version used | Minimum required | CVEs | Shared | [Bundled Qt library](https://doc.qt.io/qt-5/configure-options.html) | +| Dependency | Version used | Minimum required | CVEs | Shared | [Bundled Qt library](https://doc.qt.io/qt-5/configure-options.html#third-party-libraries) | | --- | --- | --- | --- | --- | --- | -| Berkeley DB | [4.8.30](http://www.oracle.com/technetwork/database/database-technologies/berkeleydb/downloads/index.html) | 4.8.x | No | | | +| Berkeley DB | [4.8.30](https://www.oracle.com/technetwork/database/database-technologies/berkeleydb/downloads/index.html) | 4.8.x | No | | | | Boost | [1.64.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) | | | | | D-Bus | [1.10.18](https://cgit.freedesktop.org/dbus/dbus/tree/NEWS?h=dbus-1.10) | | No | Yes | | -| Expat | [2.2.5](https://libexpat.github.io/) | | No | Yes | | +| Expat | [2.2.6](https://libexpat.github.io/) | | No | Yes | | | fontconfig | [2.12.1](https://www.freedesktop.org/software/fontconfig/release/) | | No | Yes | | -| FreeType | [2.7.1](http://download.savannah.gnu.org/releases/freetype) | | No | | | +| FreeType | [2.7.1](https://download.savannah.gnu.org/releases/freetype) | | No | | | | GCC | | [4.8+](https://gcc.gnu.org/) (C++11 support) | | | | | HarfBuzz-NG | | | | | | | libevent | [2.1.8-stable](https://github.com/libevent/libevent/releases) | 2.0.22 | No | | | @@ -23,8 +23,8 @@ These are the dependencies currently used by Bitcoin Core. You can find instruct | protobuf | [2.6.1](https://github.com/google/protobuf/releases) | | No | | | | Python (tests) | | [3.4](https://www.python.org/downloads) | | | | | qrencode | [3.4.4](https://fukuchi.org/works/qrencode) | | No | | | -| Qt | [5.9.6](https://download.qt.io/official_releases/qt/) | 5.x | No | | | +| Qt | [5.9.7](https://download.qt.io/official_releases/qt/) | [5.2](https://github.com/bitcoin/bitcoin/pull/14725) | No | | | | XCB | | | | | [Yes](https://github.com/bitcoin/bitcoin/blob/master/depends/packages/qt.mk#L87) (Linux only) | | xkbcommon | | | | | [Yes](https://github.com/bitcoin/bitcoin/blob/master/depends/packages/qt.mk#L86) (Linux only) | -| ZeroMQ | [4.2.5](https://github.com/zeromq/libzmq/releases) | 4.0.0 | No | | | +| ZeroMQ | [4.3.1](https://github.com/zeromq/libzmq/releases) | 4.0.0 | No | | | | zlib | [1.2.11](https://zlib.net/) | | | | No | diff --git a/doc/descriptors.md b/doc/descriptors.md index c23ac06e8f..de4d4e574f 100644 --- a/doc/descriptors.md +++ b/doc/descriptors.md @@ -22,19 +22,20 @@ Output descriptors currently support: ## Examples -- `pk(0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798)` represents a P2PK output. -- `pkh(02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5)` represents a P2PKH output. -- `wpkh(02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9)` represents a P2WPKH output. -- `sh(wpkh(03fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556))` represents a P2SH-P2WPKH output. -- `combo(0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798)` represents a P2PK, P2PKH, P2WPKH, and P2SH-P2WPKH output. -- `sh(wsh(pkh(02e493dbf1c10d80f3581e4904930b1404cc6c13900ee0758474fa94abe8c4cd13)))` represents a (overly complicated) P2SH-P2WSH-P2PKH output. -- `multi(1,022f8bde4d1a07209355b4a7250a5c5128e88b84bddc619ab7cba8d569b240efe4,025cbdf0646e5db4eaa398f365f2ea7a0e3d419b7e0330e39ce92bddedcac4f9bc)` represents a bare *1-of-2* multisig. -- `sh(multi(2,022f01e5e15cca351daff3843fb70f3c2f0a1bdd05e5af888a67784ef3e10a2a01,03acd484e2f0c7f65309ad178a9f559abde09796974c57e714c35f110dfc27ccbe))` represents a P2SH *2-of-2* multisig. -- `wsh(multi(2,03a0434d9e47f3c86235477c7b1ae6ae5d3442d49b1943c2b752a68e2a47e247c7,03774ae7f858a9411e5ef4246b70c65aac5649980be5c17891bbec17895da008cb,03d01115d548e7561b15c38f004d734633687cf4419620095bc5b0f47070afe85a))` represents a P2WSH *2-of-3* multisig. -- `sh(wsh(multi(1,03f28773c2d975288bc7d1d205c3748651b075fbc6610e58cddeeddf8f19405aa8,03499fdf9e895e719cfd64e67f07d38e3226aa7b63678949e6e49b241a60e823e4,02d7924d4f7d43ea965a465ae3095ff41131e5946f3c85f79e44adbcf8e27e080e)))` represents a P2SH-P2WSH *1-of-3* multisig. -- `pk(xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8)` refers to a single P2PK output, using the public key part from the specified xpub. -- `pkh(xpub68Gmy5EdvgibQVfPdqkBBCHxA5htiqg55crXYuXoQRKfDBFA1WEjWgP6LHhwBZeNK1VTsfTFUHCdrfp1bgwQ9xv5ski8PX9rL2dZXvgGDnw/1'/2)` refers to a single P2PKH output, using child key *1'/2* of the specified xpub. -- `wsh(multi(1,xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/1/0/*,xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/1/0/*))` refers to a chain of *1-of-2* P2WSH multisig outputs, using public keys taken from two HD chains with corresponding derivation paths. +- `pk(0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798)` describes a P2PK output with the specified public key. +- `pkh(02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5)` describes a P2PKH output with the specified public key. +- `wpkh(02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9)` describes a P2WPKH output with the specified public key. +- `sh(wpkh(03fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556))` describes a P2SH-P2WPKH output with the specified public key. +- `combo(0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798)` describes any P2PK, P2PKH, P2WPKH, or P2SH-P2WPKH output with the specified public key. +- `sh(wsh(pkh(02e493dbf1c10d80f3581e4904930b1404cc6c13900ee0758474fa94abe8c4cd13)))` describes an (overly complicated) P2SH-P2WSH-P2PKH output with the specified public key. +- `multi(1,022f8bde4d1a07209355b4a7250a5c5128e88b84bddc619ab7cba8d569b240efe4,025cbdf0646e5db4eaa398f365f2ea7a0e3d419b7e0330e39ce92bddedcac4f9bc)` describes a bare *1-of-2* multisig output with keys in the specified order. +- `sh(multi(2,022f01e5e15cca351daff3843fb70f3c2f0a1bdd05e5af888a67784ef3e10a2a01,03acd484e2f0c7f65309ad178a9f559abde09796974c57e714c35f110dfc27ccbe))` describes a P2SH *2-of-2* multisig output with keys in the specified order. +- `wsh(multi(2,03a0434d9e47f3c86235477c7b1ae6ae5d3442d49b1943c2b752a68e2a47e247c7,03774ae7f858a9411e5ef4246b70c65aac5649980be5c17891bbec17895da008cb,03d01115d548e7561b15c38f004d734633687cf4419620095bc5b0f47070afe85a))` describes a P2WSH *2-of-3* multisig output with keys in the specified order. +- `sh(wsh(multi(1,03f28773c2d975288bc7d1d205c3748651b075fbc6610e58cddeeddf8f19405aa8,03499fdf9e895e719cfd64e67f07d38e3226aa7b63678949e6e49b241a60e823e4,02d7924d4f7d43ea965a465ae3095ff41131e5946f3c85f79e44adbcf8e27e080e)))` describes a P2SH-P2WSH *1-of-3* multisig output with keys in the specified order. +- `pk(xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8)` describes a P2PK output with the public key of the specified xpub. +- `pkh(xpub68Gmy5EdvgibQVfPdqkBBCHxA5htiqg55crXYuXoQRKfDBFA1WEjWgP6LHhwBZeNK1VTsfTFUHCdrfp1bgwQ9xv5ski8PX9rL2dZXvgGDnw/1'/2)` describes a P2PKH output with child key *1'/2* of the specified xpub. +- `pkh([d34db33f/44'/0'/0']xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/1/*)` describes a set of P2PKH outputs, but additionally specifies that the specified xpub is a child of a master with fingerprint `d34db33f`, and derived using path `44'/0'/0'`. +- `wsh(multi(1,xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/1/0/*,xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/0/0/*))` describes a set of *1-of-2* P2WSH multisig outputs where the first multisig key is the *1/0/`i`* child of the first specified xpub and the second multisig key is the *0/0/`i`* child of the second specified xpub, and `i` is any number in a configurable range (`0-1000` by default). ## Reference @@ -52,19 +53,26 @@ Descriptors consist of several types of expressions. The top level expression is - `raw(HEX)` (top level only): the script whose hex encoding is HEX. `KEY` expressions: -- Hex encoded public keys (66 characters starting with `02` or `03`, or 130 characters starting with `04`). - - Inside `wpkh` and `wsh`, only compressed public keys are permitted. -- [WIF](https://en.bitcoin.it/wiki/Wallet_import_format) encoded private keys may be specified instead of the corresponding public key, with the same meaning. --`xpub` encoded extended public key or `xprv` encoded private key (as defined in [BIP 32](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki)). - - Followed by zero or more `/NUM` unhardened and `/NUM'` hardened BIP32 derivation steps. - - Optionally followed by a single `/*` or `/*'` final step to denote all (direct) unhardened or hardened children. - - The usage of hardened derivation steps requires providing the private key. - - Instead of a `'`, the suffix `h` can be used to denote hardened derivation. +- Optionally, key origin information, consisting of: + - An open bracket `[` + - Exactly 8 hex characters for the fingerprint of the key where the derivation starts (see BIP32 for details) + - Followed by zero or more `/NUM` or `/NUM'` path elements to indicate unhardened or hardened derivation steps between the fingerprint and the key or xpub/xprv root that follows + - A closing bracket `]` +- Followed by the actual key, which is either: + - Hex encoded public keys (either 66 characters starting with `02` or `03` for a compressed pubkey, or 130 characters starting with `04` for an uncompressed pubkey). + - Inside `wpkh` and `wsh`, only compressed public keys are permitted. + - [WIF](https://en.bitcoin.it/wiki/Wallet_import_format) encoded private keys may be specified instead of the corresponding public key, with the same meaning. + - `xpub` encoded extended public key or `xprv` encoded extended private key (as defined in [BIP 32](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki)). + - Followed by zero or more `/NUM` unhardened and `/NUM'` hardened BIP32 derivation steps. + - Optionally followed by a single `/*` or `/*'` final step to denote all (direct) unhardened or hardened children. + - The usage of hardened derivation steps requires providing the private key. + +(Anywhere a `'` suffix is permitted to denote hardened derivation, the suffix `h` can be used instead.) `ADDR` expressions are any type of supported address: -- P2PKH addresses (base58, of the form `1...`). Note that P2PKH addresses in descriptors cannot be used for P2PK outputs (use the `pk` function instead). -- P2SH addresses (base58, of the form `3...`, defined in [BIP 13](https://github.com/bitcoin/bips/blob/master/bip-0013.mediawiki)). -- Segwit addresses (bech32, of the form `bc1...`, defined in [BIP 173](https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki)). +- P2PKH addresses (base58, of the form `1...` for mainnet or `[nm]...` for testnet). Note that P2PKH addresses in descriptors cannot be used for P2PK outputs (use the `pk` function instead). +- P2SH addresses (base58, of the form `3...` for mainnet or `2...` for testnet, defined in [BIP 13](https://github.com/bitcoin/bips/blob/master/bip-0013.mediawiki)). +- Segwit addresses (bech32, of the form `bc1...` for mainnet or `tb1...` for testnet, defined in [BIP 173](https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki)). ## Explanation @@ -76,10 +84,9 @@ imaginable, though they may not be optimal: P2SH-P2PK, P2SH-P2PKH, P2WSH-P2PK, P2WSH-P2PKH, P2SH-P2WSH-P2PK, P2SH-P2WSH-P2PKH. To describe these, we model these as functions. The functions `pk` -(P2PK), `pkh` (P2PKH) and `wpkh` (P2WPKH) take as input a public key in -hexadecimal notation (which will be extended later), and return the +(P2PK), `pkh` (P2PKH) and `wpkh` (P2WPKH) take as input a `KEY` expression, and return the corresponding *scriptPubKey*. The functions `sh` (P2SH) and `wsh` (P2WSH) -take as input a script, and return the script describing P2SH and P2WSH +take as input a `SCRIPT` expression, and return the script describing P2SH and P2WSH outputs with the input as embedded script. The names of the functions do not contain "p2" for brevity. @@ -88,9 +95,18 @@ not contain "p2" for brevity. Several pieces of software use multi-signature (multisig) scripts based on Bitcoin's OP_CHECKMULTISIG opcode. To support these, we introduce the `multi(k,key_1,key_2,...,key_n)` function. It represents a *k-of-n* -multisig policy, where any *k* out of the *n* provided public keys must +multisig policy, where any *k* out of the *n* provided `KEY` expressions must sign. +Key order is significant. A `multi()` expression describes a multisig script +with keys in the specified order, and in a search for TXOs, it will not match +outputs with multisig scriptPubKeys that have the same keys in a different +order. Also, to prevent a combinatorial explosion of the search space, if more +than one of the `multi()` key arguments is a BIP32 wildcard path ending in `/*` +or `*'`, the `multi()` expression only matches multisig scripts with the `i`th +child key from each wildcard path in lockstep, rather than scripts with any +combination of child keys from each wildcard path. + ### BIP32 derived keys and chains Most modern wallet software and hardware uses keys that are derived using @@ -101,12 +117,43 @@ path consists of a sequence of 0 or more integers (in the range *0..2<sup>31</sup>-1*) each optionally followed by `'` or `h`, and separated by `/` characters. The string may optionally end with the literal `/*` or `/*'` (or `/*h`) to refer to all unhardened or hardened -child keys instead. +child keys in a configurable range (by default `0-1000`, inclusive). Whenever a public key is described using a hardened derivation step, the script cannot be computed without access to the corresponding private key. +### Key origin identification + +In order to describe scripts whose signing keys reside on another device, +it may be necessary to identify the master key and derivation path an +xpub was derived with. + +For example, when following BIP44, it would be useful to describe a +change chain directly as `xpub.../44'/0'/0'/1/*` where `xpub...` +corresponds with the master key `m`. Unfortunately, since there are +hardened derivation steps that follow the xpub, this descriptor does not +let you compute scripts without access to the corresponding private keys. +Instead, it should be written as `xpub.../1/*`, where xpub corresponds to +`m/44'/0'/0'`. + +When interacting with a hardware device, it may be necessary to include +the entire path from the master down. [BIP174](https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki) standardizes this by +providing the master key *fingerprint* (first 32 bit of the Hash160 of +the master pubkey), plus all derivation steps. To support constructing +these, we permit providing this key origin information inside the +descriptor language, even though it does not affect the actual +scriptPubKeys it refers to. + +Every public key can be prefixed by an 8-character hexadecimal +fingerprint plus optional derivation steps (hardened and unhardened) +surrounded by brackets, identifying the master and derivation path the key or xpub +that follows was derived with. + +Note that the fingerprint of the parent only serves as a fast way to detect +parent and child nodes in software, and software must be willing to deal with +collisions. + ### Including private keys Often it is useful to communicate a description of scripts along with the @@ -119,6 +166,6 @@ steps, or for dumping wallet descriptors including private key material. In order to easily represent the sets of scripts currently supported by existing Bitcoin Core wallets, a convenience function `combo` is -provided, which takes as input a public key, and constructs the P2PK, +provided, which takes as input a public key, and describes a set of P2PK, P2PKH, P2WPKH, and P2SH-P2WPH scripts for that key. In case the key is -uncompressed, it only constructs P2PK and P2PKH. +uncompressed, the set only includes P2PK and P2PKH scripts. diff --git a/doc/developer-notes.md b/doc/developer-notes.md index 1d103d481b..e0def4ea27 100644 --- a/doc/developer-notes.md +++ b/doc/developer-notes.md @@ -7,8 +7,8 @@ Developer Notes - [Developer Notes](#developer-notes) - [Coding Style (General)](#coding-style-general) - [Coding Style (C++)](#coding-style-c) - - [Doxygen comments](#doxygen-comments) - [Coding Style (Python)](#coding-style-python) + - [Coding Style (Doxygen-compatible comments)](#coding-style-doxygen-compatible-comments) - [Development tips and tricks](#development-tips-and-tricks) - [Compiling for debugging](#compiling-for-debugging) - [Compiling for gprof profiling](#compiling-for-gprof-profiling) @@ -28,6 +28,8 @@ Developer Notes - [Strings and formatting](#strings-and-formatting) - [Variable names](#variable-names) - [Threads and synchronization](#threads-and-synchronization) + - [Scripts](#scripts) + - [Shebang](#shebang) - [Source code organization](#source-code-organization) - [GUI](#gui) - [Subtrees](#subtrees) @@ -69,7 +71,7 @@ tool to clean up patches automatically before submission. - **Symbol naming conventions**. These are preferred in new code, but are not required when doing so would need changes to significant pieces of existing code. - - Variable and namespace names are all lowercase, and may use `_` to + - Variable (including function arguments) and namespace names are all lowercase, and may use `_` to separate words (snake_case). - Class member variables have a `m_` prefix. - Global variables have a `g_` prefix. @@ -118,10 +120,17 @@ public: } // namespace foo ``` -Doxygen comments ------------------ +Coding Style (Python) +--------------------- + +Refer to [/test/functional/README.md#style-guidelines](/test/functional/README.md#style-guidelines). + +Coding Style (Doxygen-compatible comments) +------------------------------------------ -To facilitate the generation of documentation, use doxygen-compatible comment blocks for functions, methods and fields. +Bitcoin Core uses [Doxygen](http://www.doxygen.nl/) to generate its official documentation. + +Use Doxygen-compatible comment blocks for functions, methods, and fields. For example, to describe a function use: ```c++ @@ -154,7 +163,7 @@ int var; //!< Detailed description after the member ``` or -```cpp +```c++ //! Description before the member int var; ``` @@ -174,15 +183,15 @@ Not OK (used plenty in the current source, but not picked up): // ``` -A full list of comment syntaxes picked up by doxygen can be found at http://www.stack.nl/~dimitri/doxygen/manual/docblocks.html, -but if possible use one of the above styles. - -Documentation can be generated with `make docs` and cleaned up with `make clean-docs`. +A full list of comment syntaxes picked up by Doxygen can be found at https://www.stack.nl/~dimitri/doxygen/manual/docblocks.html, +but the above styles are favored. -Coding Style (Python) ---------------------- +Documentation can be generated with `make docs` and cleaned up with `make clean-docs`. The resulting files are located in `doc/doxygen/html`; open `index.html` to view the homepage. -Refer to [/test/functional/README.md#style-guidelines](/test/functional/README.md#style-guidelines). +Before running `make docs`, you will need to install dependencies `doxygen` and `dot`. For example, on MacOS via Homebrew: +``` +brew install doxygen --with-graphviz +``` Development tips and tricks --------------------------- @@ -602,6 +611,31 @@ TRY_LOCK(cs_vNodes, lockNodes); } ``` +Scripts +-------------------------- + +### Shebang + +- Use `#!/usr/bin/env bash` instead of obsolete `#!/bin/bash`. + + - [*Rationale*](https://github.com/dylanaraps/pure-bash-bible#shebang): + + `#!/bin/bash` assumes it is always installed to /bin/ which can cause issues; + + `#!/usr/bin/env bash` searches the user's PATH to find the bash binary. + + OK: + +```bash +#!/usr/bin/env bash +``` + + Wrong: + +```bash +#!/bin/bash +``` + Source code organization -------------------------- @@ -705,16 +739,16 @@ Current subtrees include: - Upstream at https://github.com/google/leveldb ; Maintained by Google, but open important PRs to Core to avoid delay. - **Note**: Follow the instructions in [Upgrading LevelDB](#upgrading-leveldb) when - merging upstream changes to the leveldb subtree. + merging upstream changes to the LevelDB subtree. - src/libsecp256k1 - - Upstream at https://github.com/bitcoin-core/secp256k1/ ; actively maintaned by Core contributors. + - Upstream at https://github.com/bitcoin-core/secp256k1/ ; actively maintained by Core contributors. - src/crypto/ctaes - Upstream at https://github.com/bitcoin-core/ctaes ; actively maintained by Core contributors. - src/univalue - - Upstream at https://github.com/jgarzik/univalue ; report important PRs to Core to avoid delay. + - Upstream at https://github.com/bitcoin-core/univalue ; actively maintained by Core contributors, deviates from upstream https://github.com/jgarzik/univalue Upgrading LevelDB --------------------- @@ -827,7 +861,16 @@ To create a scripted-diff: - `-BEGIN VERIFY SCRIPT-` - `-END VERIFY SCRIPT-` -The scripted-diff is verified by the tool `test/lint/commit-script-check.sh` +The scripted-diff is verified by the tool `test/lint/commit-script-check.sh`. The tool's default behavior when supplied +with a commit is to verify all scripted-diffs from the beginning of time up to said commit. Internally, the tool passes +the first supplied argument to `git rev-list --reverse` to determine which commits to verify script-diffs for, ignoring +commits that don't conform to the commit message format described above. + +For development, it might be more convenient to verify all scripted-diffs in a range `A..B`, for example: + +```bash +test/lint/commit-script-check.sh origin/master..HEAD +``` Commit [`bb81e173`](https://github.com/bitcoin/bitcoin/commit/bb81e173) is an example of a scripted-diff. diff --git a/doc/files.md b/doc/files.md index 5657b1e6cb..85c27f3fd0 100644 --- a/doc/files.md +++ b/doc/files.md @@ -1,25 +1,26 @@ - -* banlist.dat: stores the IPs/Subnets of banned nodes -* bitcoin.conf: contains configuration settings for bitcoind or bitcoin-qt -* bitcoind.pid: stores the process id of bitcoind while running -* blocks/blk000??.dat: block data (custom, 128 MiB per file); since 0.8.0 -* blocks/rev000??.dat; block undo data (custom); since 0.8.0 (format changed since pre-0.8) -* blocks/index/*; block index (LevelDB); since 0.8.0 -* chainstate/*; block chain state database (LevelDB); since 0.8.0 -* database/*: BDB database environment; only used for wallet since 0.8.0; moved to wallets/ directory on new installs since 0.16.0 -* db.log: wallet database log file; moved to wallets/ directory on new installs since 0.16.0 -* debug.log: contains debug information and general logging generated by bitcoind or bitcoin-qt -* fee_estimates.dat: stores statistics used to estimate minimum transaction fees and priorities required for confirmation; since 0.10.0 -* indexes/txindex/*: optional transaction index database (LevelDB); since 0.17.0 -* mempool.dat: dump of the mempool's transactions; since 0.14.0. -* peers.dat: peer IP address database (custom format); since 0.7.0 -* wallet.dat: personal wallet (BDB) with keys and transactions; moved to wallets/ directory on new installs since 0.16.0 -* wallets/database/*: BDB database environment; used for wallets since 0.16.0 -* wallets/db.log: wallet database log file; since 0.16.0 -* wallets/wallet.dat: personal wallet (BDB) with keys and transactions; since 0.16.0 -* .cookie: session RPC authentication cookie (written at start when cookie authentication is used, deleted on shutdown): since 0.12.0 -* onion_private_key: cached Tor hidden service private key for `-listenonion`: since 0.12.0 -* guisettings.ini.bak: backup of former GUI settings after `-resetguisettings` is used +Filename | Description +--------------------|---------------------------------------------------------------------------------------------------------------------------- +banlist.dat | stores the IPs/Subnets of banned nodes +bitcoin.conf | contains configuration settings for bitcoind or bitcoin-qt +bitcoind.pid | stores the process id of bitcoind while running +blocks/blk000??.dat | block data (custom, 128 MiB per file); since 0.8.0 +blocks/rev000??.dat | block undo data (custom); since 0.8.0 (format changed since pre-0.8) +blocks/index/* | block index (LevelDB); since 0.8.0 +chainstate/* | blockchain state database (LevelDB); since 0.8.0 +database/* | BDB database environment; only used for wallet since 0.8.0; moved to wallets/ directory on new installs since 0.16.0 +db.log | wallet database log file; moved to wallets/ directory on new installs since 0.16.0 +debug.log | contains debug information and general logging generated by bitcoind or bitcoin-qt +fee_estimates.dat | stores statistics used to estimate minimum transaction fees and priorities required for confirmation; since 0.10.0 +indexes/txindex/* | optional transaction index database (LevelDB); since 0.17.0 +mempool.dat | dump of the mempool's transactions; since 0.14.0 +peers.dat | peer IP address database (custom format); since 0.7.0 +wallet.dat | personal wallet (BDB) with keys and transactions; moved to wallets/ directory on new installs since 0.16.0 +wallets/database/* | BDB database environment; used for wallets since 0.16.0 +wallets/db.log | wallet database log file; since 0.16.0 +wallets/wallet.dat | personal wallet (BDB) with keys and transactions; since 0.16.0 +.cookie | session RPC authentication cookie (written at start when cookie authentication is used, deleted on shutdown): since 0.12.0 +onion_private_key | cached Tor hidden service private key for `-listenonion`: since 0.12.0 +guisettings.ini.bak | backup of former GUI settings after `-resetguisettings` is used Only used in pre-0.8.0 --------------------- diff --git a/doc/fuzzing.md b/doc/fuzzing.md index 5dedcb51c8..23317e938e 100644 --- a/doc/fuzzing.md +++ b/doc/fuzzing.md @@ -3,10 +3,11 @@ Fuzz-testing Bitcoin Core A special test harness `test_bitcoin_fuzzy` is provided to provide an easy entry point for fuzzers and the like. In this document we'll describe how to -use it with AFL. +use it with AFL and libFuzzer. -Building AFL -------------- +## AFL + +### Building AFL It is recommended to always use the latest version of afl: ``` @@ -17,8 +18,7 @@ make export AFLPATH=$PWD ``` -Instrumentation ----------------- +### Instrumentation To build Bitcoin Core using AFL instrumentation (this assumes that the `AFLPATH` was set as above): @@ -39,8 +39,7 @@ compiling using `afl-clang-fast`/`afl-clang-fast++` the resulting features "persistent mode" and "deferred forkserver" can be used. See https://github.com/mcarpenter/afl/tree/master/llvm_mode for details. -Preparing fuzzing ------------------- +### Preparing fuzzing AFL needs an input directory with examples, and an output directory where it will place examples that it found. These can be anywhere in the file system, @@ -60,8 +59,7 @@ Example inputs are available from: Extract these (or other starting inputs) into the `inputs` directory before starting fuzzing. -Fuzzing --------- +### Fuzzing To start the actual fuzzing use: ``` @@ -70,3 +68,21 @@ $AFLPATH/afl-fuzz -i ${AFLIN} -o ${AFLOUT} -m52 -- test/test_bitcoin_fuzzy You may have to change a few kernel parameters to test optimally - `afl-fuzz` will print an error and suggestion if so. + +## libFuzzer + +A recent version of `clang`, the address sanitizer and libFuzzer is needed (all +found in the `compiler-rt` runtime libraries package). + +To build the `test/test_bitcoin_fuzzy` executable run + +``` +./configure --disable-ccache --with-sanitizers=fuzzer,address CC=clang CXX=clang++ +make +``` + +The fuzzer needs some inputs to work on, but the inputs or seeds can be used +interchangeably between libFuzzer and AFL. + +See https://llvm.org/docs/LibFuzzer.html#running on how to run the libFuzzer +instrumented executable. diff --git a/doc/init.md b/doc/init.md index 239b74e4e1..5778b09d05 100644 --- a/doc/init.md +++ b/doc/init.md @@ -22,7 +22,7 @@ Configuration At a bare minimum, bitcoind requires that the rpcpassword setting be set when running as a daemon. If the configuration file does not exist or this -setting is not set, bitcoind will shutdown promptly after startup. +setting is not set, bitcoind will shut down promptly after startup. This password does not have to be remembered or typed as it is mostly used as a fixed token that bitcoind and client programs read from the configuration diff --git a/doc/man/bitcoin-cli.1 b/doc/man/bitcoin-cli.1 index bf24d929bc..553addfa84 100644 --- a/doc/man/bitcoin-cli.1 +++ b/doc/man/bitcoin-cli.1 @@ -1,17 +1,21 @@ .\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.6. -.TH BITCOIN-CLI "1" "July 2018" "bitcoin-cli v0.16.99.0" "User Commands" +.TH BITCOIN-CLI "1" "December 2018" "bitcoin-cli v0.17.99.0" "User Commands" .SH NAME -bitcoin-cli \- manual page for bitcoin-cli v0.16.99.0 +bitcoin-cli \- manual page for bitcoin-cli v0.17.99.0 +.SH SYNOPSIS +.B bitcoin-cli +[\fI\,options\/\fR] \fI\,<command> \/\fR[\fI\,params\/\fR] \fI\,Send command to Bitcoin Core\/\fR +.br +.B bitcoin-cli +[\fI\,options\/\fR] \fI\,-named <command> \/\fR[\fI\,name=value\/\fR]... \fI\,Send command to Bitcoin Core (with named arguments)\/\fR +.br +.B bitcoin-cli +[\fI\,options\/\fR] \fI\,help List commands\/\fR +.br +.B bitcoin-cli +[\fI\,options\/\fR] \fI\,help <command> Get help for a command\/\fR .SH DESCRIPTION -Bitcoin Core RPC client version v0.16.99.0 -.SS "Usage:" -.TP -bitcoin\-cli [options] <command> [params] -Send command to Bitcoin Core -.IP -bitcoin\-cli [options] \fB\-named\fR <command> [name=value] ... Send command to Bitcoin Core (with named arguments) -bitcoin\-cli [options] help List commands -bitcoin\-cli [options] help <command> Get help for a command +Bitcoin Core RPC client version v0.17.99.0 .SH OPTIONS .HP \-? @@ -59,7 +63,8 @@ Password for JSON\-RPC connections .HP \fB\-rpcport=\fR<port> .IP -Connect to JSON\-RPC on <port> (default: 8332 or testnet: 18332) +Connect to JSON\-RPC on <port> (default: 8332, testnet: 18332, regtest: +18443) .HP \fB\-rpcuser=\fR<user> .IP @@ -72,20 +77,20 @@ Wait for RPC server to start \fB\-rpcwallet=\fR<walletname> .IP Send RPC for non\-default wallet on RPC server (needs to exactly match -corresponding \fB\-wallet\fR option passed to bitcoind) +corresponding \fB\-wallet\fR option passed to bitcoind). This changes +the RPC endpoint used, e.g. +http://127.0.0.1:8332/wallet/<walletname> .HP \fB\-stdin\fR .IP Read extra arguments from standard input, one per line until EOF/Ctrl\-D -(recommended for sensitive information such as passphrases). -When combined with \fB\-stdinrpcpass\fR, the first line from standard -input is used for the RPC password. +(recommended for sensitive information such as passphrases). When +combined with \fB\-stdinrpcpass\fR, the first line from standard input +is used for the RPC password. .HP \fB\-stdinrpcpass\fR -.TP -Read RPC password from standard input as a single line. -When combined .IP +Read RPC password from standard input as a single line. When combined with \fB\-stdin\fR, the first line from standard input is used for the RPC password. .HP diff --git a/doc/man/bitcoin-qt.1 b/doc/man/bitcoin-qt.1 index 3a18c9f49f..1d87acd3de 100644 --- a/doc/man/bitcoin-qt.1 +++ b/doc/man/bitcoin-qt.1 @@ -1,12 +1,12 @@ .\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.6. -.TH BITCOIN-QT "1" "July 2018" "bitcoin-qt v0.16.99.0" "User Commands" +.TH BITCOIN-QT "1" "December 2018" "bitcoin-qt v0.17.99.0" "User Commands" .SH NAME -bitcoin-qt \- manual page for bitcoin-qt v0.16.99.0 +bitcoin-qt \- manual page for bitcoin-qt v0.17.99.0 +.SH SYNOPSIS +.B bitcoin-qt +[\fI\,command-line options\/\fR] .SH DESCRIPTION -Bitcoin Core version v0.16.99.0 (64\-bit) -Usage: -.IP -bitcoin\-qt [command\-line options] +Bitcoin Core version v0.17.99.0 (64\-bit) .SH OPTIONS .HP \-? @@ -23,9 +23,9 @@ long fork (%s in cmd is replaced by message) If this block is in the chain assume that it and its ancestors are valid and potentially skip their script verification (0 to verify all, default: -0000000000000000005214481d2d96f898e3d5416e43359c145944a909d242e0, +0000000000000000002e63058c023a9a1de233554f28c7b21380b6c9003f36a8, testnet: -0000000002e9e7b00e1f6dc5123a04aad68dd0f0968d8c7aa45f6640795c37b1) +0000000000000037a8cd3e06cd5edbfe9dd1dbcc5dacab279376ef7cfc2b4c75) .HP \fB\-blocknotify=\fR<cmd> .IP @@ -61,7 +61,8 @@ Set database cache size in megabytes (4 to 16384, default: 450) \fB\-debuglogfile=\fR<file> .IP Specify location of debug log file. Relative paths will be prefixed by a -net\-specific datadir location. (0 to disable; default: debug.log) +net\-specific datadir location. (\fB\-nodebuglogfile\fR to disable; +default: debug.log) .HP \fB\-includeconf=\fR<file> .IP @@ -108,7 +109,7 @@ blocks if a target size in MiB is provided. This mode is incompatible with \fB\-txindex\fR and \fB\-rescan\fR. Warning: Reverting this setting requires re\-downloading the entire blockchain. (default: 0 = disable pruning blocks, 1 = allow manual pruning via RPC, ->550 = automatically prune block files to stay under the +>=550 = automatically prune block files to stay under the specified target size in MiB) .HP \fB\-reindex\fR @@ -117,7 +118,9 @@ Rebuild chain state and block index from the blk*.dat files on disk .HP \fB\-reindex\-chainstate\fR .IP -Rebuild chain state from the currently indexed blocks +Rebuild chain state from the currently indexed blocks. When in pruning +mode or if blocks on disk might be corrupted, use full \fB\-reindex\fR +instead. .HP \fB\-sysperms\fR .IP @@ -157,7 +160,7 @@ for IPv6 .HP \fB\-connect=\fR<ip> .IP -Connect only to the specified node; \fB\-connect\fR=\fI\,0\/\fR disables automatic +Connect only to the specified node; \fB\-noconnect\fR disables automatic connections (the rules for this peer are the same as for \fB\-addnode\fR). This option can be specified multiple times to connect to multiple nodes. @@ -178,7 +181,7 @@ unless \fB\-connect\fR used) .HP \fB\-enablebip61\fR .IP -Send reject messages per BIP61 (default: 1) +Send reject messages per BIP61 (default: 0) .HP \fB\-externalip=\fR<ip> .IP @@ -221,8 +224,8 @@ Tries to keep outbound traffic under the given target (in MiB per 24h), .HP \fB\-onion=\fR<ip:port> .IP -Use separate SOCKS5 proxy to reach peers via Tor hidden services -(default: \fB\-proxy\fR) +Use separate SOCKS5 proxy to reach peers via Tor hidden services, set +\fB\-noonion\fR to disable (default: \fB\-proxy\fR) .HP \fB\-onlynet=\fR<net> .IP @@ -242,11 +245,13 @@ Relay non\-P2SH multisig (default: 1) .HP \fB\-port=\fR<port> .IP -Listen for connections on <port> (default: 8333 or testnet: 18333) +Listen for connections on <port> (default: 8333, testnet: 18333, +regtest: 18444) .HP \fB\-proxy=\fR<ip:port> .IP -Connect through SOCKS5 proxy +Connect through SOCKS5 proxy, set \fB\-noproxy\fR to disable (default: +disabled) .HP \fB\-proxyrandomize\fR .IP @@ -393,8 +398,7 @@ Send transactions with full\-RBF opt\-in enabled (RPC only, default: 0) .IP Delete all wallet transactions and only recover those parts of the blockchain through \fB\-rescan\fR on startup (1 = keep tx meta data e.g. -account owner and payment request information, 2 = drop tx meta -data) +payment request information, 2 = drop tx meta data) .PP ZeroMQ notification options: .HP @@ -418,7 +422,7 @@ Debugging/Testing options: .HP \fB\-debug=\fR<category> .IP -Output debugging information (default: 0, supplying <category> is +Output debugging information (default: \fB\-nodebug\fR, supplying <category> is optional). If <category> is not supplied or if <category> = 1, output all debugging information. <category> can be: net, tor, mempool, http, bench, zmq, db, rpc, estimatefee, addrman, @@ -433,7 +437,7 @@ or more specified categories. .HP \fB\-help\-debug\fR .IP -Show all debugging options (usage: \fB\-\-help\fR \fB\-help\-debug\fR) +Print help message with debugging options and exit .HP \fB\-logips\fR .IP @@ -452,7 +456,7 @@ transaction; setting this too low may abort large transactions \fB\-printtoconsole\fR .IP Send trace/debug info to console (default: 1 when no \fB\-daemon\fR. To disable -logging to file, set debuglogfile=0) +logging to file, set \fB\-nodebuglogfile\fR) .HP \fB\-shrinkdebugfile\fR .IP @@ -529,8 +533,8 @@ option can be specified multiple times .HP \fB\-rpcauth=\fR<userpw> .IP -Username and hashed password for JSON\-RPC connections. The field -<userpw> comes in the format: <USERNAME>:<SALT>$<HASH>. A +Username and HMAC\-SHA\-256 hashed password for JSON\-RPC connections. The +field <userpw> comes in the format: <USERNAME>:<SALT>$<HASH>. A canonical python script is included in share/rpcauth. The client then connects normally using the rpcuser=<USERNAME>/rpcpassword=<PASSWORD> pair of arguments. This @@ -538,12 +542,12 @@ option can be specified multiple times .HP \fB\-rpcbind=\fR<addr>[:port] .IP -Bind to given address to listen for JSON\-RPC connections. This option is -ignored unless \fB\-rpcallowip\fR is also passed. Port is optional and -overrides \fB\-rpcport\fR. Use [host]:port notation for IPv6. This -option can be specified multiple times (default: 127.0.0.1 and -::1 i.e., localhost, or if \fB\-rpcallowip\fR has been specified, -0.0.0.0 and :: i.e., all addresses) +Bind to given address to listen for JSON\-RPC connections. Do not expose +the RPC server to untrusted networks such as the public internet! +This option is ignored unless \fB\-rpcallowip\fR is also passed. Port is +optional and overrides \fB\-rpcport\fR. Use [host]:port notation for +IPv6. This option can be specified multiple times (default: +127.0.0.1 and ::1 i.e., localhost) .HP \fB\-rpccookiefile=\fR<loc> .IP @@ -556,8 +560,8 @@ Password for JSON\-RPC connections .HP \fB\-rpcport=\fR<port> .IP -Listen for JSON\-RPC connections on <port> (default: 8332 or testnet: -18332) +Listen for JSON\-RPC connections on <port> (default: 8332, testnet: +18332, regtest: 18443) .HP \fB\-rpcserialversion\fR .IP diff --git a/doc/man/bitcoin-tx.1 b/doc/man/bitcoin-tx.1 index e1b81bad6c..f16c68ca14 100644 --- a/doc/man/bitcoin-tx.1 +++ b/doc/man/bitcoin-tx.1 @@ -1,16 +1,15 @@ .\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.6. -.TH BITCOIN-TX "1" "July 2018" "bitcoin-tx v0.16.99.0" "User Commands" +.TH BITCOIN-TX "1" "December 2018" "bitcoin-tx v0.17.99.0" "User Commands" .SH NAME -bitcoin-tx \- manual page for bitcoin-tx v0.16.99.0 +bitcoin-tx \- manual page for bitcoin-tx v0.17.99.0 +.SH SYNOPSIS +.B bitcoin-tx +[\fI\,options\/\fR] \fI\,<hex-tx> \/\fR[\fI\,commands\/\fR] \fI\,Update hex-encoded bitcoin transaction\/\fR +.br +.B bitcoin-tx +[\fI\,options\/\fR] \fI\,-create \/\fR[\fI\,commands\/\fR] \fI\,Create hex-encoded bitcoin transaction\/\fR .SH DESCRIPTION -Bitcoin Core bitcoin\-tx utility version v0.16.99.0 -.SS "Usage:" -.TP -bitcoin\-tx [options] <hex\-tx> [commands] -Update hex\-encoded bitcoin transaction -.TP -bitcoin\-tx [options] \fB\-create\fR [commands] -Create hex\-encoded bitcoin transaction +Bitcoin Core bitcoin\-tx utility version v0.17.99.0 .SH OPTIONS .HP \-? diff --git a/doc/man/bitcoind.1 b/doc/man/bitcoind.1 index d0ba131cde..5c4b1cd03b 100644 --- a/doc/man/bitcoind.1 +++ b/doc/man/bitcoind.1 @@ -1,13 +1,12 @@ .\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.6. -.TH BITCOIND "1" "July 2018" "bitcoind v0.16.99.0" "User Commands" +.TH BITCOIND "1" "December 2018" "bitcoind v0.17.99.0" "User Commands" .SH NAME -bitcoind \- manual page for bitcoind v0.16.99.0 +bitcoind \- manual page for bitcoind v0.17.99.0 +.SH SYNOPSIS +.B bitcoind +[\fI\,options\/\fR] \fI\,Start Bitcoin Core Daemon\/\fR .SH DESCRIPTION -Bitcoin Core Daemon version v0.16.99.0 -.SS "Usage:" -.TP -bitcoind [options] -Start Bitcoin Core Daemon +Bitcoin Core Daemon version v0.17.99.0 .SH OPTIONS .HP \-? @@ -24,9 +23,9 @@ long fork (%s in cmd is replaced by message) If this block is in the chain assume that it and its ancestors are valid and potentially skip their script verification (0 to verify all, default: -0000000000000000005214481d2d96f898e3d5416e43359c145944a909d242e0, +0000000000000000002e63058c023a9a1de233554f28c7b21380b6c9003f36a8, testnet: -0000000002e9e7b00e1f6dc5123a04aad68dd0f0968d8c7aa45f6640795c37b1) +0000000000000037a8cd3e06cd5edbfe9dd1dbcc5dacab279376ef7cfc2b4c75) .HP \fB\-blocknotify=\fR<cmd> .IP @@ -62,7 +61,8 @@ Set database cache size in megabytes (4 to 16384, default: 450) \fB\-debuglogfile=\fR<file> .IP Specify location of debug log file. Relative paths will be prefixed by a -net\-specific datadir location. (0 to disable; default: debug.log) +net\-specific datadir location. (\fB\-nodebuglogfile\fR to disable; +default: debug.log) .HP \fB\-includeconf=\fR<file> .IP @@ -109,7 +109,7 @@ blocks if a target size in MiB is provided. This mode is incompatible with \fB\-txindex\fR and \fB\-rescan\fR. Warning: Reverting this setting requires re\-downloading the entire blockchain. (default: 0 = disable pruning blocks, 1 = allow manual pruning via RPC, ->550 = automatically prune block files to stay under the +>=550 = automatically prune block files to stay under the specified target size in MiB) .HP \fB\-reindex\fR @@ -118,7 +118,9 @@ Rebuild chain state and block index from the blk*.dat files on disk .HP \fB\-reindex\-chainstate\fR .IP -Rebuild chain state from the currently indexed blocks +Rebuild chain state from the currently indexed blocks. When in pruning +mode or if blocks on disk might be corrupted, use full \fB\-reindex\fR +instead. .HP \fB\-sysperms\fR .IP @@ -158,7 +160,7 @@ for IPv6 .HP \fB\-connect=\fR<ip> .IP -Connect only to the specified node; \fB\-connect\fR=\fI\,0\/\fR disables automatic +Connect only to the specified node; \fB\-noconnect\fR disables automatic connections (the rules for this peer are the same as for \fB\-addnode\fR). This option can be specified multiple times to connect to multiple nodes. @@ -179,7 +181,7 @@ unless \fB\-connect\fR used) .HP \fB\-enablebip61\fR .IP -Send reject messages per BIP61 (default: 1) +Send reject messages per BIP61 (default: 0) .HP \fB\-externalip=\fR<ip> .IP @@ -222,8 +224,8 @@ Tries to keep outbound traffic under the given target (in MiB per 24h), .HP \fB\-onion=\fR<ip:port> .IP -Use separate SOCKS5 proxy to reach peers via Tor hidden services -(default: \fB\-proxy\fR) +Use separate SOCKS5 proxy to reach peers via Tor hidden services, set +\fB\-noonion\fR to disable (default: \fB\-proxy\fR) .HP \fB\-onlynet=\fR<net> .IP @@ -243,11 +245,13 @@ Relay non\-P2SH multisig (default: 1) .HP \fB\-port=\fR<port> .IP -Listen for connections on <port> (default: 8333 or testnet: 18333) +Listen for connections on <port> (default: 8333, testnet: 18333, +regtest: 18444) .HP \fB\-proxy=\fR<ip:port> .IP -Connect through SOCKS5 proxy +Connect through SOCKS5 proxy, set \fB\-noproxy\fR to disable (default: +disabled) .HP \fB\-proxyrandomize\fR .IP @@ -394,8 +398,7 @@ Send transactions with full\-RBF opt\-in enabled (RPC only, default: 0) .IP Delete all wallet transactions and only recover those parts of the blockchain through \fB\-rescan\fR on startup (1 = keep tx meta data e.g. -account owner and payment request information, 2 = drop tx meta -data) +payment request information, 2 = drop tx meta data) .PP ZeroMQ notification options: .HP @@ -419,7 +422,7 @@ Debugging/Testing options: .HP \fB\-debug=\fR<category> .IP -Output debugging information (default: 0, supplying <category> is +Output debugging information (default: \fB\-nodebug\fR, supplying <category> is optional). If <category> is not supplied or if <category> = 1, output all debugging information. <category> can be: net, tor, mempool, http, bench, zmq, db, rpc, estimatefee, addrman, @@ -434,7 +437,7 @@ or more specified categories. .HP \fB\-help\-debug\fR .IP -Show all debugging options (usage: \fB\-\-help\fR \fB\-help\-debug\fR) +Print help message with debugging options and exit .HP \fB\-logips\fR .IP @@ -453,7 +456,7 @@ transaction; setting this too low may abort large transactions \fB\-printtoconsole\fR .IP Send trace/debug info to console (default: 1 when no \fB\-daemon\fR. To disable -logging to file, set debuglogfile=0) +logging to file, set \fB\-nodebuglogfile\fR) .HP \fB\-shrinkdebugfile\fR .IP @@ -530,8 +533,8 @@ option can be specified multiple times .HP \fB\-rpcauth=\fR<userpw> .IP -Username and hashed password for JSON\-RPC connections. The field -<userpw> comes in the format: <USERNAME>:<SALT>$<HASH>. A +Username and HMAC\-SHA\-256 hashed password for JSON\-RPC connections. The +field <userpw> comes in the format: <USERNAME>:<SALT>$<HASH>. A canonical python script is included in share/rpcauth. The client then connects normally using the rpcuser=<USERNAME>/rpcpassword=<PASSWORD> pair of arguments. This @@ -539,12 +542,12 @@ option can be specified multiple times .HP \fB\-rpcbind=\fR<addr>[:port] .IP -Bind to given address to listen for JSON\-RPC connections. This option is -ignored unless \fB\-rpcallowip\fR is also passed. Port is optional and -overrides \fB\-rpcport\fR. Use [host]:port notation for IPv6. This -option can be specified multiple times (default: 127.0.0.1 and -::1 i.e., localhost, or if \fB\-rpcallowip\fR has been specified, -0.0.0.0 and :: i.e., all addresses) +Bind to given address to listen for JSON\-RPC connections. Do not expose +the RPC server to untrusted networks such as the public internet! +This option is ignored unless \fB\-rpcallowip\fR is also passed. Port is +optional and overrides \fB\-rpcport\fR. Use [host]:port notation for +IPv6. This option can be specified multiple times (default: +127.0.0.1 and ::1 i.e., localhost) .HP \fB\-rpccookiefile=\fR<loc> .IP @@ -557,8 +560,8 @@ Password for JSON\-RPC connections .HP \fB\-rpcport=\fR<port> .IP -Listen for JSON\-RPC connections on <port> (default: 8332 or testnet: -18332) +Listen for JSON\-RPC connections on <port> (default: 8332, testnet: +18332, regtest: 18443) .HP \fB\-rpcserialversion\fR .IP diff --git a/doc/psbt.md b/doc/psbt.md index 95e2f7fa01..560b45ef31 100644 --- a/doc/psbt.md +++ b/doc/psbt.md @@ -90,7 +90,7 @@ the command line in case `bitcoin-cli` is used. Setup: - All three call `getnewaddress` to create a new address; call these addresses *Aalice*, *Abob*, and *Acarol*. -- All three call `getaddressinfo X`, with *X* their respective address, and +- All three call `getaddressinfo "X"`, with *X* their respective address, and remember the corresponding public keys. Call these public keys *Kalice*, *Kbob*, and *Kcarol*. - All three now run `addmultisigaddress 2 ["Kalice","Kbob","Kcarol"]` to teach @@ -105,28 +105,28 @@ Setup: output. Again, it may be necessary to explicitly specify the addresstype in order to get a result that matches. This command won't enable them to initiate transactions later, however. -- They can now give out *D* as address others can pay to. +- They can now give out *Amulti* as address others can pay to. Later, when *V* BTC has been received on *Amulti*, and Bob and Carol want to move the coins in their entirety to address *Asend*, with no change. Alice does not need to be involved. - One of them - let's assume Carol here - initiates the creation. She runs - `walletcreatefundedpsbt [] {"Asend":V} 0 false {"subtractFeeFromOutputs":[0], "includeWatching":true}`. - We call the resulting PSBT *P*. P does not contain any signatures. + `walletcreatefundedpsbt [] {"Asend":V} 0 {"subtractFeeFromOutputs":[0], "includeWatching":true}`. + We call the resulting PSBT *P*. *P* does not contain any signatures. - Carol needs to sign the transaction herself. In order to do so, she runs - `walletprocesspsbt P`, and gives the resulting PSBT *P2* to Bob. + `walletprocesspsbt "P"`, and gives the resulting PSBT *P2* to Bob. - Bob inspects the PSBT using `decodepsbt "P2"` to determine if the transaction has indeed just the expected input, and an output to *Asend*, and the fee is reasonable. If he agrees, he calls `walletprocesspsbt "P2"` to sign. The resulting PSBT *P3* contains both Carol's and Bob's signature. -- Now anyone can call `finalizepsbt "P2"` to extract a fully signed transaction +- Now anyone can call `finalizepsbt "P3"` to extract a fully signed transaction *T*. - Finally anyone can broadcast the transaction using `sendrawtransaction "T"`. In case there are more signers, it may be advantageous to let them all sign in -parallel, rather passing the PSBT from one signer to the next one. In the +parallel, rather than passing the PSBT from one signer to the next one. In the above example this would translate to Carol handing a copy of *P* to each signer -separately. They can then all invoke `walletprocesspsbt P`, and end up with +separately. They can then all invoke `walletprocesspsbt "P"`, and end up with their individually-signed PSBT structures. They then all send those back to Carol (or anyone) who can combine them using `combinepsbt`. The last two steps (`finalizepsbt` and `sendrawtransaction`) remain unchanged. diff --git a/doc/release-notes-13152.md b/doc/release-notes-13152.md deleted file mode 100644 index 72526f5355..0000000000 --- a/doc/release-notes-13152.md +++ /dev/null @@ -1,4 +0,0 @@ -New RPC methods ------------- - -- `getnodeaddresses` returns peer addresses known to this node. It may be used to connect to nodes over TCP without using the DNS seeds.
\ No newline at end of file diff --git a/doc/release-notes-14023.md b/doc/release-notes-14023.md deleted file mode 100644 index 18ea6f26d0..0000000000 --- a/doc/release-notes-14023.md +++ /dev/null @@ -1,8 +0,0 @@ -Account API removed -------------------- - -The 'account' API was deprecated in v0.17 and has been fully removed in v0.18. -The 'label' API was introduced in v0.17 as a replacement for accounts. - -See the release notes from v0.17 for a full description of the changes from the -'account' API to the 'label' API. diff --git a/doc/release-notes-14282.md b/doc/release-notes-14282.md deleted file mode 100644 index e6d8e0b70c..0000000000 --- a/doc/release-notes-14282.md +++ /dev/null @@ -1,6 +0,0 @@ -Low-level RPC changes ----------------------- - -`-usehd` was removed in version 0.16. From that version onwards, all new -wallets created are hierarchical deterministic wallets. Version 0.18 makes -specifying `-usehd` invalid config. diff --git a/doc/release-notes-14941.md b/doc/release-notes-14941.md new file mode 100644 index 0000000000..c3820d0368 --- /dev/null +++ b/doc/release-notes-14941.md @@ -0,0 +1,5 @@ +Miscellaneous RPC changes +------------ + +- The `unloadwallet` RPC is now synchronous, meaning that it blocks until the + wallet is fully unloaded. diff --git a/doc/release-notes-14982.md b/doc/release-notes-14982.md new file mode 100644 index 0000000000..3f0bf8aacd --- /dev/null +++ b/doc/release-notes-14982.md @@ -0,0 +1,5 @@ +New RPCs +-------- + +- The RPC `getrpcinfo` returns runtime details of the RPC server. At the moment + it returns the active commands and the corresponding execution time. diff --git a/doc/release-notes.md b/doc/release-notes.md index 2044a50098..c21a153a25 100644 --- a/doc/release-notes.md +++ b/doc/release-notes.md @@ -47,31 +47,261 @@ processing the entire blockchain. Compatibility ============== -Bitcoin Core is extensively tested on multiple operating systems using -the Linux kernel, macOS 10.10+, and Windows 7 and newer (Windows XP is not supported). +Bitcoin Core is supported and extensively tested on operating systems using +the Linux kernel, macOS 10.10+, and Windows 7 and newer. It is not recommended +to use Bitcoin Core on unsupported systems. Bitcoin Core should also work on most other Unix-like systems but is not frequently tested on them. -From 0.17.0 onwards macOS <10.10 is no longer supported. 0.17.0 is built using Qt 5.9.x, which doesn't -support versions of macOS older than 10.10. +From 0.17.0 onwards, macOS <10.10 is no longer supported. 0.17.0 is +built using Qt 5.9.x, which doesn't support versions of macOS older than +10.10. Additionally, Bitcoin Core does not yet change appearance when +macOS "dark mode" is activated. + +In addition to previously-supported CPU platforms, this release's +pre-compiled distribution also provides binaries for the RISC-V +platform. Notable changes =============== -Command line option changes ---------------------------- +Mining +------ + +- Calls to `getblocktemplate` will fail if the segwit rule is not specified. + Calling `getblocktemplate` without segwit specified is almost certainly + a misconfiguration since doing so results in lower rewards for the miner. + Failed calls will produce an error message describing how to enable the + segwit rule. + +Configuration option changes +---------------------------- + +- A warning is printed if an unrecognized section name is used in the + configuration file. Recognized sections are `[test]`, `[main]`, and + `[regtest]`. + +- Four new options are available for configuring the maximum number of + messages that ZMQ will queue in memory (the "high water mark") before + dropping additional messages. The default value is 1,000, the same as + was used for previous releases. See the [ZMQ + documentation](https://github.com/bitcoin/bitcoin/blob/master/doc/zmq.md#usage) + for details. + +- The `enablebip61` option (introduced in Bitcoin Core 0.17.0) is + used to toggle sending of BIP 61 reject messages. Reject messages have no use + case on the P2P network and are only logged for debugging by most network + nodes. The option will now by default be off for improved privacy and security + as well as reduced upload usage. The option can explicitly be turned on for + local-network debugging purposes. + +- The `rpcallowip` option can no longer be used to automatically listen + on all network interfaces. Instead, the `rpcbind` parameter must also + be used to specify the IP addresses to listen on. Listening for RPC + commands over a public network connection is insecure and should be + disabled, so a warning is now printed if a user selects such a + configuration. If you need to expose RPC in order to use a tool + like Docker, ensure you only bind RPC to your localhost, e.g. `docker + run [...] -p 127.0.0.1:8332:8332` (this is an extra `:8332` over the + normal Docker port specification). + +- The `rpcpassword` option now causes a startup error if the password + set in the configuration file contains a hash character (#), as it's + ambiguous whether the hash character is meant for the password or as a + comment. + +Documentation +------------- + +- A new short + [document](https://github.com/bitcoin/bitcoin/blob/master/doc/JSON-RPC-interface.md) + about the JSON-RPC interface describes cases where the results of an + RPC might contain inconsistencies between data sourced from different + subsystems, such as wallet state and mempool state. A note is added + to the [REST interface documentation](https://github.com/bitcoin/bitcoin/blob/master/doc/REST-interface.md) + indicating that the same rules apply. + +- A new [document](https://github.com/bitcoin/bitcoin/blob/master/doc/bitcoin-conf.md) + about the `bitcoin.conf` file describes how to use it to configure + Bitcoin Core. + +- A new document introduces Bitcoin Core's BIP174 + [Partially-Signed Bitcoin Transactions (PSBT)](https://github.com/bitcoin/bitcoin/blob/master/doc/psbt.md) + interface, which is used to allow multiple programs to collaboratively + work to create, sign, and broadcast new transactions. This is useful + for offline (cold storage) wallets, multisig wallets, coinjoin + implementations, and many other cases where two or more programs need + to interact to generate a complete transaction. -The `-enablebip61` command line option (introduced in Bitcoin Core 0.17.0) is -used to toggle sending of BIP 61 reject messages. Reject messages have no use -case on the P2P network and are only logged for debugging by most network -nodes. The option will now by default be off for improved privacy and security -as well as reduced upload usage. The option can explicitly be turned on for -local-network debugging purposes. +- The [output script descriptor](https://github.com/bitcoin/bitcoin/blob/master/doc/descriptors.md) + documentation has been updated with information about new features in + this still-developing language for describing the output scripts that + a wallet or other program wants to receive notifications for, such as + which addresses it wants to know received payments. The language is + currently used in the `scantxoutset` RPC and is expected to be adapted + to other RPCs and to the underlying wallet structure. -Example item +Build system changes +-------------------- + +- A new `--disable-bip70` option may be passed to `./configure` to + prevent Bitcoin-Qt from being built with support for the BIP70 payment + protocol or from linking libssl. As the payment protocol has exposed + Bitcoin Core to libssl vulnerabilities in the past, builders who don't + need BIP70 support are encouraged to use this option to reduce their + exposure to future vulnerabilities. + +Deprecated or removed RPCs +-------------------------- + +- The `signrawtransaction` RPC is removed after being deprecated and + hidden behind a special configuration option in version 0.17.0. + +- The 'account' API is removed after being deprecated in v0.17. The + 'label' API was introduced in v0.17 as a replacement for accounts. + See the [release notes from v0.17](https://github.com/bitcoin/bitcoin/blob/master/doc/release-notes/release-notes-0.17.0.md#label-and-account-apis-for-wallet) + for a full description of the changes from the 'account' API to the + 'label' API. + +- The `addwitnessaddress` RPC is removed after being deprecated in + version 0.13.0. + +- The wallet's `generate` RPC method is deprecated and will be fully + removed in a subsequent major version. This RPC is only used for + testing, but its implementation reached across multiple subsystems + (wallet and mining), so it is being deprecated to simplify the + wallet-node interface. Projects that are using `generate` for testing + purposes should transition to using the `generatetoaddress` RPC, which + does not require or use the wallet component. Calling + `generatetoaddress` with an address returned by the `getnewaddress` + RPC gives the same functionality as the old `generate` RPC. To + continue using `generate` in this version, restart bitcoind with the + `-deprecatedrpc=generate` configuration option. + +New RPCs +-------- + +- A new `getnodeaddresses` RPC returns peer addresses known to this + node. It may be used to find nodes to connect to without using a DNS + seeder. + +- A new `listwalletdir` RPC returns a list of wallets in the wallet + directory (either the default wallet directory or the directory + configured by the `-walletdir` parameter). + +Updated RPCs ------------ +Note: some low-level RPC changes mainly useful for testing are described +in the Low-level Changes section below. + +- The `getpeerinfo` RPC now returns an additional `minfeefilter` field + set to the peer's BIP133 fee filter. You can use this to detect that + you have peers that are willing to accept transactions below the + default minimum relay fee. + +- The mempool RPCs, such as `getrawmempool` with `verbose=true`, now + return an additional "bip125-replaceable" value indicating whether the + transaction (or its unconfirmed ancestors) opts-in to asking nodes and + miners to replace it with a higher-feerate transaction spending any of + the same inputs. + +- The `settxfee` RPC previously silently ignored attempts to set the fee + below the allowed minimums. It now prints a warning. The special + value of "0" may still be used to request the minimum value. + +- The `getaddressinfo` RPC now provides an `ischange` field indicating + whether the wallet used the address in a change output. + +- The `importmulti` RPC has been updated to support P2WSH, P2WPKH, + P2SH-P2WPKH, and P2SH-P2WSH. Requests for P2WSH and P2SH-P2WSH accept + an additional `witnessscript` parameter. + +- The `importmulti` RPC now returns an additional `warnings` field for + each request with an array of strings explaining when fields are being + ignored or are inconsistent, if there are any. + +- The `getaddressinfo` RPC now returns an additional `solvable` boolean + field when Bitcoin Core knows enough about the address's scriptPubKey, + optional redeemScript, and optional witnessScript in order for the + wallet to be able to generate an unsigned input spending funds sent to + that address. + +- The `getaddressinfo`, `listunspent`, and `scantxoutset` RPCs now + return an additional `desc` field that contains an output descriptor + containing all key paths and signing information for the address + (except for the private key). The `desc` field is only returned for + `getaddressinfo` and `listunspent` when the address is solvable. + +- The `importprivkey` RPC will preserve previously-set labels for + addresses or public keys corresponding to the private key being + imported. For example, if you imported a watch-only address with the + label "cold wallet" in earlier releases of Bitcoin Core, subsequently + importing the private key would default to resetting the address's + label to the default empty-string label (""). In this release, the + previous label of "cold wallet" will be retained. If you optionally + specify any label besides the default when calling `importprivkey`, + the new label will be applied to the address. + +- See the [Mining](#mining) section for changes to `getblocktemplate`. + +Graphical User Interface (GUI) +------------------------------ + +- A new Window menu is added alongside the existing File, Settings, and + Help menus. Several items from the other menus that opened new + windows have been moved to this new Window menu. + +- In the Send tab, the checkbox for "pay only the required fee" + has been removed. Instead, the user can simply decrease the value in + the Custom Feerate field all the way down to the node's configured + minimum relay fee. + +- In the Overview tab, the watch-only balance will be the only + balance shown if the wallet was created using the `createwallet` RPC + and the `disable_private_keys` parameter was set to true. + +- The launch-on-startup option is no longer available on macOS if + compiled with macosx min version greater than 10.11 (use + CXXFLAGS="-mmacosx-version-min=10.11" + CFLAGS="-mmacosx-version-min=10.11" for setting the deployment + sdk version) + +Low-level changes +================= + +RPC +--- + +- The `submitblock` RPC previously returned the reason a rejected block + was invalid the first time it processed that block but returned a + generic "duplicate" rejection message on subsequent occasions it + processed the same block. It now always returns the fundamental + reason for rejecting an invalid block and only returns "duplicate" for + valid blocks it has already accepted. + +- A new `submitheader` RPC allows submitting block headers independently + from their block. This is likely only useful for testing. + +Configuration +------------- + +- The `-usehd` configuration option was removed in version 0.16. From + that version onwards, all new wallets created are hierarchical + deterministic wallets. This release makes specifying `-usehd` an + invalid configuration option. + +Changes for particular platforms +-------------------------------- + +- On macOS, Bitcoin Core now opts out of application CPU throttling + ("app nap") during initial blockchain download, when catching up from + over 100 blocks behind the current chain tip, or when reindexing chain + data. This helps prevent these operations from taking an excessively + long time because the operating system is attempting to conserve + power. + Credits ======= diff --git a/doc/release-notes/release-notes-0.17.0.1.md b/doc/release-notes/release-notes-0.17.0.1.md new file mode 100644 index 0000000000..92db7dac7d --- /dev/null +++ b/doc/release-notes/release-notes-0.17.0.1.md @@ -0,0 +1,41 @@ +Bitcoin Core version 0.17.0.1 is now available from: + + <https://bitcoincore.org/bin/bitcoin-core-0.17.0.1/> + +This release provides a minor bug fix for 0.17.0. + +Please report bugs using the issue tracker at GitHub: + + <https://github.com/bitcoin/bitcoin/issues> + +To receive security and update notifications, please subscribe to: + + <https://bitcoincore.org/en/list/announcements/join/> + +Notable changes +=============== + +An issue was solved with OSX dmg generation, affecting macOS 10.12 to 10.14, +which could cause Finder to crash on install. + +There are no significant changes for other operating systems. + +0.17.0.1 change log +=================== + +### Build system +- #14416 `eb2cc84` Fix OSX dmg issue (10.12 to 10.14) (jonasschnelli) + +### Documentation +- #14509 `1b5af2c` [0.17] doc: use SegWit in getblocktemplate example (Sjors) + +Credits +======= + +Thanks to everyone who directly contributed to this release: + +- Jonas Schnelli +- Pieter Wuille +- Sjors Provoost +- Wladimir J. van der Laan + diff --git a/doc/release-notes/release-notes-0.17.0.md b/doc/release-notes/release-notes-0.17.0.md new file mode 100644 index 0000000000..418d7ba5f9 --- /dev/null +++ b/doc/release-notes/release-notes-0.17.0.md @@ -0,0 +1,1105 @@ +Bitcoin Core version 0.17.0 is now available from: + + <https://bitcoincore.org/bin/bitcoin-core-0.17.0/> + +This is a new major version release, including new features, various bugfixes +and performance improvements, as well as updated translations. + +Please report bugs using the issue tracker at GitHub: + + <https://github.com/bitcoin/bitcoin/issues> + +To receive security and update notifications, please subscribe to: + + <https://bitcoincore.org/en/list/announcements/join/> + +How to Upgrade +============== + +If you are running an older version, shut it down. Wait until it has completely +shut down (which might take a few minutes for older versions), then run the +installer (on Windows) or just copy over `/Applications/Bitcoin-Qt` (on Mac) +or `bitcoind`/`bitcoin-qt` (on Linux). + +If your node has a txindex, the txindex db will be migrated the first time you run 0.17.0 or newer, which may take up to a few hours. Your node will not be functional until this migration completes. + +The first time you run version 0.15.0 or newer, your chainstate database will be converted to a +new format, which will take anywhere from a few minutes to half an hour, +depending on the speed of your machine. + +Note that the block database format also changed in version 0.8.0 and there is no +automatic upgrade code from before version 0.8 to version 0.15.0. Upgrading +directly from 0.7.x and earlier without redownloading the blockchain is not supported. +However, as usual, old wallet versions are still supported. + +Downgrading warning +------------------- + +The chainstate database for this release is not compatible with previous +releases, so if you run 0.15 and then decide to switch back to any +older version, you will need to run the old release with the `-reindex-chainstate` +option to rebuild the chainstate data structures in the old format. + +If your node has pruning enabled, this will entail re-downloading and +processing the entire blockchain. + +Compatibility +============== + +Bitcoin Core is extensively tested on multiple operating systems using +the Linux kernel, macOS 10.10+, and Windows 7 and newer (Windows XP is not supported). + +Bitcoin Core should also work on most other Unix-like systems but is not +frequently tested on them. + +From 0.17.0 onwards macOS <10.10 is no longer supported. 0.17.0 is built using Qt 5.9.x, which doesn't +support versions of macOS older than 10.10. + +Known issues +============ + +- Upgrading from 0.13.0 or older currently results in memory blow-up during the roll-back of blocks to the SegWit activation point. In these cases, a full `-reindex` is necessary. + +- The GUI suffers from visual glitches in the new MacOS dark mode. This has to do with our Qt theme handling and is not a new problem in 0.17.0, but is expected to be resolved in 0.17.1. + +Notable changes +=============== + +Changed configuration options +----------------------------- + +- `-includeconf=<file>` can be used to include additional configuration files. + Only works inside the `bitcoin.conf` file, not inside included files or from + command-line. Multiple files may be included. Can be disabled from command- + line via `-noincludeconf`. Note that multi-argument commands like + `-includeconf` will override preceding `-noincludeconf`, i.e. + ``` + noincludeconf=1 + includeconf=relative.conf + ``` + + as bitcoin.conf will still include `relative.conf`. + +GUI changes +----------- + +- Block storage can be limited under Preferences, in the Main tab. Undoing this setting requires downloading the full blockchain again. This mode is incompatible with -txindex and -rescan. + +External wallet files +--------------------- + +The `-wallet=<path>` option now accepts full paths instead of requiring wallets +to be located in the -walletdir directory. + +Newly created wallet format +--------------------------- + +If `-wallet=<path>` is specified with a path that does not exist, it will now +create a wallet directory at the specified location (containing a wallet.dat +data file, a db.log file, and database/log.?????????? files) instead of just +creating a data file at the path and storing log files in the parent +directory. This should make backing up wallets more straightforward than +before because the specified wallet path can just be directly archived without +having to look in the parent directory for transaction log files. + +For backwards compatibility, wallet paths that are names of existing data files +in the `-walletdir` directory will continue to be accepted and interpreted the +same as before. + +Dynamic loading and creation of wallets +--------------------------------------- + +Previously, wallets could only be loaded or created at startup, by specifying `-wallet` parameters on the command line or in the bitcoin.conf file. It is now possible to load, create and unload wallets dynamically at runtime: + +- Existing wallets can be loaded by calling the `loadwallet` RPC. The wallet can be specified as file/directory basename (which must be located in the `walletdir` directory), or as an absolute path to a file/directory. +- New wallets can be created (and loaded) by calling the `createwallet` RPC. The provided name must not match a wallet file in the `walletdir` directory or the name of a wallet that is currently loaded. +- Loaded wallets can be unloaded by calling the `unloadwallet` RPC. + +This feature is currently only available through the RPC interface. + +Coin selection +-------------- + +### Partial spend avoidance + +When an address is paid multiple times the coins from those separate payments can be spent separately which hurts privacy due to linking otherwise separate addresses. A new `-avoidpartialspends` flag has been added (default=false). If enabled, the wallet will always spend existing UTXO to the same address together even if it results in higher fees. If someone were to send coins to an address after it was used, those coins will still be included in future coin selections. + +Configuration sections for testnet and regtest +---------------------------------------------- + +It is now possible for a single configuration file to set different +options for different networks. This is done by using sections or by +prefixing the option with the network, such as: + + main.uacomment=bitcoin + test.uacomment=bitcoin-testnet + regtest.uacomment=regtest + [main] + mempoolsize=300 + [test] + mempoolsize=100 + [regtest] + mempoolsize=20 + +If the following options are not in a section, they will only apply to mainnet: +`addnode=`, `connect=`, `port=`, `bind=`, `rpcport=`, `rpcbind=` and `wallet=`. +The options to choose a network (`regtest=` and `testnet=`) must be specified +outside of sections. + +'label' and 'account' APIs for wallet +------------------------------------- + +A new 'label' API has been introduced for the wallet. This is intended as a +replacement for the deprecated 'account' API. The 'account' can continue to +be used in V0.17 by starting bitcoind with the '-deprecatedrpc=accounts' +argument, and will be fully removed in V0.18. + +The label RPC methods mirror the account functionality, with the following functional differences: + +- Labels can be set on any address, not just receiving addresses. This functionality was previously only available through the GUI. +- Labels can be deleted by reassigning all addresses using the `setlabel` RPC method. +- There isn't support for sending transactions _from_ a label, or for determining which label a transaction was sent from. +- Labels do not have a balance. + +Here are the changes to RPC methods: + +| Deprecated Method | New Method | Notes | +| :---------------------- | :-------------------- | :-----------| +| `getaccount` | `getaddressinfo` | `getaddressinfo` returns a json object with address information instead of just the name of the account as a string. | +| `getaccountaddress` | n/a | There is no replacement for `getaccountaddress` since labels do not have an associated receive address. | +| `getaddressesbyaccount` | `getaddressesbylabel` | `getaddressesbylabel` returns a json object with the addresses as keys, instead of a list of strings. | +| `getreceivedbyaccount` | `getreceivedbylabel` | _no change in behavior_ | +| `listaccounts` | `listlabels` | `listlabels` does not return a balance or accept `minconf` and `watchonly` arguments. | +| `listreceivedbyaccount` | `listreceivedbylabel` | Both methods return new `label` fields, along with `account` fields for backward compatibility. | +| `move` | n/a | _no replacement_ | +| `sendfrom` | n/a | _no replacement_ | +| `setaccount` | `setlabel` | Both methods now: <ul><li>allow assigning labels to any address, instead of raising an error if the address is not receiving address.<li>delete the previous label associated with an address when the final address using that label is reassigned to a different label, instead of making an implicit `getaccountaddress` call to ensure the previous label still has a receiving address. | + +| Changed Method | Notes | +| :--------------------- | :------ | +| `addmultisigaddress` | Renamed `account` named parameter to `label`. Still accepts `account` for backward compatibility if running with '-deprecatedrpc=accounts'. | +| `getnewaddress` | Renamed `account` named parameter to `label`. Still accepts `account` for backward compatibility. if running with '-deprecatedrpc=accounts' | +| `listunspent` | Returns new `label` fields. `account` field will be returned for backward compatibility if running with '-deprecatedrpc=accounts' | +| `sendmany` | The `account` named parameter has been renamed to `dummy`. If provided, the `dummy` parameter must be set to the empty string, unless running with the `-deprecatedrpc=accounts` argument (in which case functionality is unchanged). | +| `listtransactions` | The `account` named parameter has been renamed to `dummy`. If provided, the `dummy` parameter must be set to the string `*`, unless running with the `-deprecatedrpc=accounts` argument (in which case functionality is unchanged). | +| `getbalance` | `account`, `minconf` and `include_watchonly` parameters are deprecated, and can only be used if running with '-deprecatedrpc=accounts' | + +BIP 174 Partially Signed Bitcoin Transactions support +----------------------------------------------------- + +[BIP 174 PSBT](https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki) is an interchange format for Bitcoin transactions that are not fully signed +yet, together with relevant metadata to help entities work towards signing it. +It is intended to simplify workflows where multiple parties need to cooperate to +produce a transaction. Examples include hardware wallets, multisig setups, and +[CoinJoin](https://bitcointalk.org/?topic=279249) transactions. + +### Overall workflow + +Overall, the construction of a fully signed Bitcoin transaction goes through the +following steps: + +- A **Creator** proposes a particular transaction to be created. He constructs + a PSBT that contains certain inputs and outputs, but no additional metadata. +- For each input, an **Updater** adds information about the UTXOs being spent by + the transaction to the PSBT. +- A potentially other Updater adds information about the scripts and public keys + involved in each of the inputs (and possibly outputs) of the PSBT. +- **Signers** inspect the transaction and its metadata to decide whether they + agree with the transaction. They can use amount information from the UTXOs + to assess the values and fees involved. If they agree, they produce a + partial signature for the inputs for which they have relevant key(s). +- A **Finalizer** is run for each input to convert the partial signatures and + possibly script information into a final `scriptSig` and/or `scriptWitness`. +- An **Extractor** produces a valid Bitcoin transaction (in network format) + from a PSBT for which all inputs are finalized. + +Generally, each of the above (excluding Creator and Extractor) will simply +add more and more data to a particular PSBT. In a naive workflow, they all have +to operate sequentially, passing the PSBT from one to the next, until the +Extractor can convert it to a real transaction. In order to permit parallel +operation, **Combiners** can be employed which merge metadata from different +PSBTs for the same unsigned transaction. + +The names above in bold are the names of the roles defined in BIP174. They're +useful in understanding the underlying steps, but in practice, software and +hardware implementations will typically implement multiple roles simultaneously. + +### RPCs + +- **`converttopsbt` (Creator)** is a utility RPC that converts an + unsigned raw transaction to PSBT format. It ignores existing signatures. +- **`createpsbt` (Creator)** is a utility RPC that takes a list of inputs and + outputs and converts them to a PSBT with no additional information. It is + equivalent to calling `createrawtransaction` followed by `converttopsbt`. +- **`walletcreatefundedpsbt` (Creator, Updater)** is a wallet RPC that creates a + PSBT with the specified inputs and outputs, adds additional inputs and change + to it to balance it out, and adds relevant metadata. In particular, for inputs + that the wallet knows about (counting towards its normal or watch-only + balance), UTXO information will be added. For outputs and inputs with UTXO + information present, key and script information will be added which the wallet + knows about. It is equivalent to running `createrawtransaction`, followed by + `fundrawtransaction`, and `converttopsbt`. +- **`walletprocesspsbt` (Updater, Signer, Finalizer)** is a wallet RPC that takes as + input a PSBT, adds UTXO, key, and script data to inputs and outputs that miss + it, and optionally signs inputs. Where possible it also finalizes the partial + signatures. +- **`finalizepsbt` (Finalizer, Extractor)** is a utility RPC that finalizes any + partial signatures, and if all inputs are finalized, converts the result to a + fully signed transaction which can be broadcast with `sendrawtransaction`. +- **`combinepsbt` (Combiner)** is a utility RPC that implements a Combiner. It + can be used at any point in the workflow to merge information added to + different versions of the same PSBT. In particular it is useful to combine the + output of multiple Updaters or Signers. +- **`decodepsbt`** is a diagnostic utility RPC which will show all information in + a PSBT in human-readable form, as well as compute its eventual fee if known. + +Upgrading non-HD wallets to HD wallets +-------------------------------------- + +Since Bitcoin Core 0.13.0, creating new BIP 32 Hierarchical Deterministic wallets has been supported by Bitcoin Core but old non-HD wallets could not be upgraded to HD. Now non-HD wallets can be upgraded to HD using the `-upgradewallet` command line option. This upgrade will result in the all keys in the keypool being marked as used and a new keypool generated. **A new backup must be made when this upgrade is performed.** + +Additionally, `-upgradewallet` can be used to upgraded from a non-split HD chain (all keys generated with `m/0'/0'/i'`) to a split HD chain (receiving keys generated with `'m/0'/0'/i'` and change keys generated with `m'/0'/1'/i'`). When this upgrade occurs, all keys already in the keypool will remain in the keypool to be used until all keys from before the upgrade are exhausted. This is to avoid issues with backups and downgrades when some keys may come from the change key keypool. Users can begin using the new split HD chain keypools by using the `newkeypool` RPC to mark all keys in the keypool as used and begin using a new keypool generated from the split HD chain. + +HD Master key rotation +---------------------- + +A new RPC, `sethdseed`, has been introduced which allows users to set a new HD seed or set their own HD seed. This allows for a new HD seed to be used. **A new backup must be made when a new HD seed is set.** + +Low-level RPC changes +--------------------- + +- The new RPC `scantxoutset` can be used to scan the UTXO set for entries + that match certain output descriptors. Refer to the [output descriptors + reference documentation](/doc/descriptors.md) for more details. This call + is similar to `listunspent` but does not use a wallet, meaning that the + wallet can be disabled at compile or run time. This call is experimental, + as such, is subject to changes or removal in future releases. + +- The `createrawtransaction` RPC will now accept an array or dictionary (kept for compatibility) for the `outputs` parameter. This means the order of transaction outputs can be specified by the client. +- The `fundrawtransaction` RPC will reject the previously deprecated `reserveChangeKey` option. +- `sendmany` now shuffles outputs to improve privacy, so any previously expected behavior with regards to output ordering can no longer be relied upon. +- The new RPC `testmempoolaccept` can be used to test acceptance of a transaction to the mempool without adding it. +- JSON transaction decomposition now includes a `weight` field which provides + the transaction's exact weight. This is included in REST /rest/tx/ and + /rest/block/ endpoints when in json mode. This is also included in `getblock` + (with verbosity=2), `listsinceblock`, `listtransactions`, and + `getrawtransaction` RPC commands. +- New `fees` field introduced in `getrawmempool`, `getmempoolancestors`, `getmempooldescendants` and + `getmempoolentry` when verbosity is set to `true` with sub-fields `ancestor`, `base`, `modified` + and `descendant` denominated in BTC. This new field deprecates previous fee fields, such as + `fee`, `modifiedfee`, `ancestorfee` and `descendantfee`. +- The new RPC `getzmqnotifications` returns information about active ZMQ + notifications. +- When bitcoin is not started with any `-wallet=<path>` options, the name of + the default wallet returned by `getwalletinfo` and `listwallets` RPCs is + now the empty string `""` instead of `"wallet.dat"`. If bitcoin is started + with any `-wallet=<path>` options, there is no change in behavior, and the + name of any wallet is just its `<path>` string. +- Passing an empty string (`""`) as the `address_type` parameter to + `getnewaddress`, `getrawchangeaddress`, `addmultisigaddress`, + `fundrawtransaction` RPCs is now an error. Previously, this would fall back + to using the default address type. It is still possible to pass null or leave + the parameter unset to use the default address type. + +- Bare multisig outputs to our keys are no longer automatically treated as + incoming payments. As this feature was only available for multisig outputs for + which you had all private keys in your wallet, there was generally no use for + them compared to single-key schemes. Furthermore, no address format for such + outputs is defined, and wallet software can't easily send to it. These outputs + will no longer show up in `listtransactions`, `listunspent`, or contribute to + your balance, unless they are explicitly watched (using `importaddress` or + `importmulti` with hex script argument). `signrawtransaction*` also still + works for them. + +- The `getwalletinfo` RPC method now returns an `hdseedid` value, which is always the same as the incorrectly-named `hdmasterkeyid` value. `hdmasterkeyid` will be removed in V0.18. +- The `getaddressinfo` RPC method now returns an `hdseedid` value, which is always the same as the incorrectly-named `hdmasterkeyid` value. `hdmasterkeyid` will be removed in V0.18. + +- Parts of the `validateaddress` RPC method have been deprecated and moved to + `getaddressinfo`. Clients must transition to using `getaddressinfo` to access + this information before upgrading to v0.18. The following deprecated fields + have moved to `getaddressinfo` and will only be shown with + `-deprecatedrpc=validateaddress`: `ismine`, `iswatchonly`, `script`, `hex`, + `pubkeys`, `sigsrequired`, `pubkey`, `addresses`, `embedded`, `iscompressed`, + `account`, `timestamp`, `hdkeypath`, `hdmasterkeyid`. +- `signrawtransaction` is deprecated and will be fully removed in v0.18. To use + `signrawtransaction` in v0.17, restart bitcoind with + `-deprecatedrpc=signrawtransaction`. Projects should transition to using + `signrawtransactionwithkey` and `signrawtransactionwithwallet` before + upgrading to v0.18. + +Other API changes +----------------- + +- The `inactivehdmaster` property in the `dumpwallet` output has been corrected to `inactivehdseed` + +### Logging + +- The log timestamp format is now ISO 8601 (e.g. "2018-02-28T12:34:56Z"). + +- When running bitcoind with `-debug` but without `-daemon`, logging to stdout + is now the default behavior. Setting `-printtoconsole=1` no longer implicitly + disables logging to debug.log. Instead, logging to file can be explicitly disabled + by setting `-debuglogfile=0`. + +Transaction index changes +------------------------- + +The transaction index is now built separately from the main node procedure, +meaning the `-txindex` flag can be toggled without a full reindex. If bitcoind +is run with `-txindex` on a node that is already partially or fully synced +without one, the transaction index will be built in the background and become +available once caught up. When switching from running `-txindex` to running +without the flag, the transaction index database will *not* be deleted +automatically, meaning it could be turned back on at a later time without a full +resync. + +Miner block size removed +------------------------ + +The `-blockmaxsize` option for miners to limit their blocks' sizes was +deprecated in V0.15.1, and has now been removed. Miners should use the +`-blockmaxweight` option if they want to limit the weight of their blocks. + +Python Support +-------------- + +Support for Python 2 has been discontinued for all test files and tools. + +0.17.0 change log +================= + +### Consensus +- #12204 `3fa24bb` Fix overly eager BIP30 bypass (morcos) + +### Policy +- #12568 `ed6ae80` Allow dustrelayfee to be set to zero (luke-jr) +- #13120 `ca2a233` Treat segwit as always active (MarcoFalke) +- #13096 `062738c` Fix `MAX_STANDARD_TX_WEIGHT` check (jl2012) + +### Mining +- #12693 `df529dc` Remove unused variable in SortForBlock (drewx2) +- #12448 `84efa9a` Interrupt block generation on shutdown request (promag) + +### Block and transaction handling +- #12225 `67447ba` Mempool cleanups (sdaftuar) +- #12356 `fd65937` Fix 'mempool min fee not met' debug output (Empact) +- #12287 `bf3353d` Optimise lock behaviour for GuessVerificationProgress() (jonasschnelli) +- #11889 `47a7666` Drop extra script variable in ProduceSignature (ryanofsky) +- #11880 `d59b8d6` Stop special-casing phashBlock handling in validation for TBV (TheBlueMatt) +- #12431 `947c25e` Only call NotifyBlockTip when chainActive changes (jamesob) +- #12653 `534b8fa` Allow to optional specify the directory for the blocks storage (jonasschnelli) +- #12172 `3b62a91` Bugfix: RPC: savemempool: Don't save until LoadMempool() is finished (jtimon) +- #12167 `88430cb` Make segwit failure due to `CLEANSTACK` violation return a `SCRIPT_ERR_CLEANSTACK` error code (maaku) +- #12561 `24133b1` Check for block corruption in ConnectBlock() (sdaftuar) +- #11617 `1b5723e` Avoid lock: Call FlushStateToDisk(…) regardless of fCheckForPruning (practicalswift) +- #11739 `0a8b7b4` Enforce `SCRIPT_VERIFY_P2SH` and `SCRIPT_VERIFY_WITNESS` from genesis (sdaftuar) +- #12885 `a49381d` Reduce implementation code inside CScript (sipa) +- #13032 `34dd1a6` Output values for "min relay fee not met" error (kristapsk) +- #13033 `a07e8ca` Build txindex in parallel with validation (jimpo) +- #13080 `66cc47b` Add compile time checking for ::mempool.cs runtime locking assertions (practicalswift) +- #13185 `08c1caf` Bugfix: the end of a reorged chain is invalid when connect fails (sipa) +- #11689 `0264836` Fix missing locking in CTxMemPool::check(…) and CTxMemPool::setSanityCheck(…) (practicalswift) +- #13011 `3c2a41a` Cache witness hash in CTransaction (MarcoFalke) +- #13191 `0de7cc8` Specialized double-SHA256 with 64 byte inputs with SSE4.1 and AVX2 (sipa) +- #13243 `ea263e1` Make reusable base class for auxiliary indices (jimpo) +- #13393 `a607d23` Enable double-SHA256-for-64-byte code on 32-bit x86 (sipa) +- #13428 `caabdea` validation: check the specified number of blocks (off-by-one) (kallewoof) +- #13438 `450055b` Improve coverage of SHA256 SelfTest code (sipa) +- #13431 `954f4a9` validation: count blocks correctly for check level < 3 (kallewoof) +- #13386 `3a3eabe` SHA256 implementations based on Intel SHA Extensions (sipa) +- #11658 `9a1ad2c` During IBD, when doing pruning, prune 10% extra to avoid pruning again soon after (luke-jr) +- #13794 `8ce55df` chainparams: Update with data from assumed valid chain (MarcoFalke) +- #13527 `e7ea858` Remove promiscuousmempoolflags (MarcoFalke) + +### P2P protocol and network code +- #12342 `eaeaa2d` Extend #11583 ("Do not make it trivial for inbound peers to generate log entries") to include "version handshake timeout" message (clemtaylor) +- #12218 `9a32114` Move misbehaving logging to net logging category (laanwj) +- #10387 `5c2aff8` Eventually connect to `NODE_NETWORK_LIMITED` peers (jonasschnelli) +- #9037 `a36834f` Add test-before-evict discipline to addrman (EthanHeilman) +- #12622 `e1d6e2a` Correct addrman logging (laanwj) +- #11962 `0a01843` add seed.bitcoin.sprovoost.nl to DNS seeds (Sjors) +- #12569 `23e7fe8` Increase signal-to-noise ratio in debug.log by adjusting log level when logging failed non-manual connect():s (practicalswift) +- #12855 `c199869` Minor accumulated cleanups (tjps) +- #13153 `ef46c99` Add missing newlines to debug logging (laanwj) +- #13162 `a174702` Don't incorrectly log that REJECT messages are unknown (jnewbery) +- #13151 `7f4db9a` Serve blocks directly from disk when possible (laanwj) +- #13134 `70d3541` Add option `-enablebip61` to configure sending of BIP61 notifications (laanwj) +- #13532 `7209fec` Log warning when deprecated network name 'tor' is used (wodry) +- #13615 `172f984` Remove unused interrupt from SendMessages (fanquake) +- #13417 `1e90862` Tighten scope in `net_processing` (skeees) +- #13298 `f8d470e` Bucketing INV delays (1 bucket) for incoming connections to hide tx time (naumenkogs) +- #13672 `0d8d6be` Modified `in_addr6` cast in CConman class to work with msvc (sipsorcery) +- #11637 `c575260` Remove dead service bits code (MarcoFalke) +- #13212 `a6f00ce` Fixed a race condition when disabling the network (lmanners) +- #13656 `1211b15` Remove the boost/algorithm/string/predicate.hpp dependency (251Labs) +- #13423 `f58674a` Thread safety annotations in `net_processing` (skeees) +- #13776 `7d36237` Add missing verification of IPv6 address in CNetAddr::GetIn6Addr(…) (practicalswift) +- #13907 `48bf8ff` Introduce a maximum size for locators (gmaxwell) +- #13951 `8a9ffec` Hardcoded seeds update pre-0.17 branch (laanwj) + +### Wallet +- #12330 `2a30e67` Reduce scope of `cs_main` and `cs_wallet` locks in listtransactions (promag) +- #12298 `a1ffddb` Refactor HaveKeys to early return on false result (promag) +- #12282 `663911e` Disallow abandon of conflicted txes (MarcoFalke) +- #12333 `d405bee` Make CWallet::ListCoins atomic (promag) +- #12296 `8e6f9f4` Only fee-bump non-conflicted/non-confirmed txes (MarcoFalke) +- #11866 `6bb9c13` Do not un-mark fInMempool on wallet txn if ATMP fails (TheBlueMatt) +- #11882 `987a809` Disable default fallbackfee on mainnet (jonasschnelli) +- #9991 `4ca7c1e` listreceivedbyaddress Filter Address (NicolasDorier) +- #11687 `98bc27f` External wallet files (ryanofsky) +- #12658 `af88094` Sanitize some wallet serialization (sipa) +- #9680 `6acd870` Unify CWalletTx construction (ryanofsky) +- #10637 `e057589` Coin Selection with Murch's algorithm (achow101, Xekyo) +- #12408 `c39dd2e` Change output type globals to members (MarcoFalke) +- #12694 `9552dfb` Actually disable BnB when there are preset inputs (achow101) +- #11536 `cead84b` Rename account to label where appropriate (ryanofsky) +- #12709 `02b7e83` shuffle sendmany recipients ordering (instagibbs) +- #12699 `c948dc8` Shuffle transaction inputs before signing (instagibbs) +- #10762 `6d53663` Remove Wallet dependencies from init.cpp (jnewbery) +- #12857 `821980c` Avoid travis lint-include-guards error (ken2812221) +- #12702 `dab0d68` importprivkey: hint about importmulti (kallewoof) +- #12836 `9abdb7c` Make WalletInitInterface and DummyWalletInit private, fix nullptr deref (promag) +- #12785 `215158a` Initialize `m_last_block_processed` to nullptr (practicalswift) +- #12932 `8d651ae` Remove redundant lambda function arg in handleTransactionChanged (laanwj) +- #12749 `a84b056` feebumper: discard change outputs below discard rate (instagibbs) +- #12892 `9b3370d` introduce 'label' API for wallet (jnewbery) +- #12925 `6d3de17` Logprint the start of a rescan (jonasschnelli) +- #12888 `39439e5` debug log number of unknown wallet records on load (instagibbs) +- #12977 `434150a` Refactor `g_wallet_init_interface` to const reference (promag) +- #13017 `65d7083` Add wallets management functions (promag) +- #12953 `d1d54ae` Deprecate accounts (jnewbery) +- #12909 `476cb35` Make fee settings to be non-static members (MarcoFalke) +- #13002 `487dcbe` Do not treat bare multisig outputs as IsMine unless watched (sipa) +- #13028 `783bb64` Make vpwallets usage thread safe (promag) +- #12507 `2afdc29` Interrupt rescan on shutdown request (promag) +- #12729 `979150b` Get rid of ambiguous OutputType::NONE value (ryanofsky) +- #13079 `5778d44` Fix rescanblockchain rpc to properly report progress (Empact) +- #12560 `e03c0db` Upgrade path for non-HD wallets to HD (achow101) +- #13161 `7cc1bd3` Reset BerkeleyDB handle after connection fails (real-or-random) +- #13081 `0dec5b5` Add compile time checking for `cs_wallet` runtime locking assertions (practicalswift) +- #13127 `19a3a9e` Add Clang thread safety annotations for variables guarded by `cs_db` (practicalswift) +- #10740 `4cfe17c` `loadwallet` RPC - load wallet at runtime (jnewbery) +- #12924 `6738813` Fix hdmaster-key / seed-key confusion (scripted diff) (jnewbery) +- #13297 `d82c5d1` Fix incorrect comment for DeriveNewSeed (jnewbery) +- #13063 `6378eef` Use shared pointer to retain wallet instance (promag) +- #13142 `56fe3dc` Separate IsMine from solvability (sipa) +- #13194 `fd96d54` Remove template matching and pseudo opcodes (sipa) +- #13252 `c4cc8d9` Refactor ReserveKeyFromKeyPool for safety (Empact) +- #13058 `343d4e4` `createwallet` RPC - create new wallet at runtime (jnewbery) +- #13351 `2140f6c` Prevent segfault when sending to unspendable witness (MarcoFalke) +- #13060 `3f0f394` Remove getlabeladdress RPC (jnewbery) +- #13111 `000abbb` Add unloadwallet RPC (promag) +- #13160 `868cf43` Unlock spent outputs (promag) +- #13498 `f54f373` Fixups from account API deprecation (jnewbery) +- #13491 `61a044a` Improve handling of INVALID in IsMine (sipa) +- #13425 `028b0d9` Moving final scriptSig construction from CombineSignatures to ProduceSignature (PSBT signer logic) (achow101) +- #13564 `88a15eb` loadwallet shouldn't create new wallets (jnewbery) +- #12944 `619cd29` ScanforWalletTransactions should mark input txns as dirty (instagibbs) +- #13630 `d6b2235` Drop unused pindexRet arg to CMerkleTx::GetDepthInMainChain (Empact) +- #13566 `ad552a5` Fix get balance (jnewbery) +- #13500 `4a3e8c5` Decouple wallet version from client version (achow101) +- #13712 `aba2e66` Fix non-determinism in ParseHDKeypath(…). Avoid using an uninitialized variable in path calculation (practicalswift) +- #9662 `6b6e854` Add createwallet "disableprivatekeys" option: a sane mode for watchonly-wallets (jonasschnelli) +- #13683 `e8c7434` Introduce assertion to document the assumption that cache and cache_used are always set in tandem (practicalswift) +- #12257 `5f7575e` Use destination groups instead of coins in coin select (kallewoof) +- #13773 `89a116d` Fix accidental use of the comma operator (practicalswift) +- #13805 `c88529a` Correctly limit output group size (sdaftuar) +- #12992 `26f59f5` Add wallet name to log messages (PierreRochard) +- #13667 `b81a8a5` Fix backupwallet for multiwallets (domob1812) +- #13657 `51c693d` assert to ensure accuracy of CMerkleTx::GetBlocksToMaturity (Empact) +- #13812 `9d86aad` sum ancestors rather than taking max in output groups (kallewoof) +- #13876 `8eb9870` Catch `filesystem_error` and raise `InitError` (MarcoFalke) +- #13808 `13d51a2` shuffle coins before grouping, where warranted (kallewoof) +- #13666 `2115cba` Always create signatures with Low R values (achow101) +- #13917 `0333914` Additional safety checks in PSBT signer (sipa) +- #13968 `65e7a8b` couple of walletcreatefundedpsbt fixes (instagibbs) +- #14055 `2307a6e` fix walletcreatefundedpsbt deriv paths, add test (instagibbs) + +### RPC and other APIs +- #12336 `3843780` Remove deprecated rpc options (jnewbery) +- #12193 `5dc00f6` Consistently use UniValue.pushKV instead of push_back(Pair()) (karel-3d) (MarcoFalke) +- #12409 `0cc45ed` Reject deprecated reserveChangeKey in fundrawtransaction (MarcoFalke) +- #10583 `8a98dfe` Split part of validateaddress into getaddressinfo (achow101) +- #10579 `ffc6e48` Split signrawtransaction into wallet and non-wallet RPC command (achow101) +- #12494 `e4ffcac` Declare CMutableTransaction a struct in rawtransaction.h (Empact) +- #12503 `0e26591` createmultisig no longer takes addresses (instagibbs) +- #12083 `228b086` Improve getchaintxstats test coverage (promag) +- #12479 `cd5e438` Add child transactions to getrawmempool verbose output (conscott) +- #11872 `702e8b7` createrawtransaction: Accept sorted outputs (MarcoFalke) +- #12700 `ebdf84c` Document RPC method aliasing (ryanofsky) +- #12727 `8ee5c7b` Remove unreachable help conditions in rpcwallet.cpp (lutangar) +- #12778 `b648974` Add username and ip logging for RPC method requests (GabrielDav) +- #12717 `ac898b6` rest: Handle utxo retrieval when ignoring the mempool (romanz) +- #12787 `cd99e5b` Adjust ifdef to avoid unreachable code (practicalswift) +- #11742 `18815b4` Add testmempoolaccept (MarcoFalke) +- #12942 `fefb817` Drop redundant testing of signrawtransaction prevtxs args (Empact) +- #11200 `5f2a399` Allow for aborting rescans in the GUI (achow101) +- #12791 `3a8a4dc` Expose a transaction's weight via RPC (TheBlueMatt) +- #12436 `6e67754` Adds a functional test to validate the transaction version number in the RPC output (251Labs) +- #12240 `6f8b345` Introduced a new `fees` structure that aggregates all sub-field fee types denominated in BTC (mryandao) +- #12321 `eac067a` p2wsh and p2sh-p2wsh address in decodescript (fivepiece) +- #13090 `17266a1` Remove Safe mode (achow101, laanwj) +- #12639 `7eb7076` Reduce `cs_main` lock in listunspent (promag) +- #10267 `7b966d9` New -includeconf argument for including external configuration files (kallewoof) +- #10757 `b9551d3` Introduce getblockstats to plot things (jtimon) +- #13288 `a589f53` Remove the need to include rpc/blockchain.cpp in order to put `GetDifficulty` under test (Empact) +- #13394 `e1f8dce` cli: Ignore libevent warnings (theuni) +- #13439 `3f398d7` Avoid "duplicate" return value for invalid submitblock (TheBlueMatt) +- #13570 `a247594` Add new "getzmqnotifications" method (domob1812) +- #13072 `b25a4c2` Update createmultisig RPC to support segwit (ajtowns) +- #12196 `8fceae0` Add scantxoutset RPC method (jonasschnelli) +- #13557 `b654723` BIP 174 PSBT Serializations and RPCs (achow101) +- #13697 `f030410` Support output descriptors in scantxoutset (sipa) +- #13927 `bced8ea` Use pushKV in some new PSBT RPCs (domob1812) +- #13918 `a9c56b6` Replace median fee rate with feerate percentiles in getblockstats (marcinja) +- #13721 `9f23c16` Bugfixes for BIP 174 combining and deserialization (achow101) +- #13960 `517010e` Fix PSBT deserialization of 0-input transactions (achow101) + +### GUI +- #12416 `c997f88` Fix Windows build errors introduced in #10498 (practicalswift) +- #11733 `e782099` Remove redundant locks (practicalswift) +- #12426 `bfa3911` Initialize members in WalletModel (MarcoFalke) +- #12489 `e117cfe` Bugfix: respect user defined configuration file (-conf) in QT settings (jonasschnelli) +- #12421 `be263fa` navigate to transaction history page after send (Sjors) +- #12580 `ce56fdd` Show a transaction's virtual size in its details dialog (dooglus) +- #12501 `c8ea91a` Improved "custom fee" explanation in tooltip (randolf) +- #12616 `cff95a6` Set modal overlay hide button as default (promag) +- #12620 `8a43bdc` Remove TransactionTableModel::TxIDRole (promag) +- #12080 `56cc022` Add support to search the address book (promag) +- #12621 `2bac3e4` Avoid querying unnecessary model data when filtering transactions (promag) +- #12721 `e476826` remove "new" button during receive-mode in addressbook (jonasschnelli) +- #12723 `310dc61` Qt5: Warning users about invalid-BIP21 URI bitcoin:// (krab) +- #12610 `25cf18f` Multiwallet for the GUI (jonasschnelli) +- #12779 `f4353da` Remove unused method setupAmountWidget(…) (practicalswift) +- #12795 `68484d6` do not truncate .dat extension for wallets in gui (instagibbs) +- #12870 `1d54004` make clean removes `src/qt/moc_` files (Sjors) +- #13055 `bdda14d` Don't log to console by default (laanwj) +- #13141 `57c57df` fixes broken link on readme (marcoagner) +- #12928 `ef006d9` Initialize non-static class members that were previously neither initialized where defined nor in constructor (practicalswift) +- #13158 `81c533c` Improve sendcoinsdialog readability (marcoagner) +- #11491 `40c34a0` Add proxy icon in statusbar (mess110) +- #13264 `2a7c53b` Satoshi unit (GreatSock) +- #13097 `e545503` Support wallets loaded dynamically (promag) +- #13284 `f8be434` fix visual "overflow" of amount input (brandonrninefive) +- #13275 `a315b79` use `[default wallet]` as name for wallet with no name (jonasschnelli) +- #13273 `3fd0c23` Qt/Bugfix: fix handling default wallet with no name (jonasschnelli) +- #13341 `25d2df2` Stop translating command line options (laanwj) +- #13043 `6e249e4` OptionsDialog: add prune setting (Sjors) +- #13506 `6579d80` load wallet in UI after possible init aborts (jonasschnelli) +- #13458 `dc53f7f` Drop qt4 support (laanwj) +- #13528 `b877c39` Move BitcoinGUI initializers to class, fix initializer order warning (laanwj) +- #13536 `baf3a3a` coincontrol: Remove unused qt4 workaround (MarcoFalke) +- #13537 `10ffca7` Peer table: Visualize inbound/outbound state for every row (wodry) +- #13791 `2c14c1f` Reject dialogs if key escape is pressed (promag) + +### Build system +- #12371 `c9ca4f6` Add gitian PGP key: akx20000 (ghost) +- #11966 `f4f4f51` clientversion: Use full commit hash for commit-based version descriptions (luke-jr) +- #12417 `ae0fbf0` Upgrade `mac_alias` to 2.0.7 (droark) +- #12444 `1f055ef` gitian: Bump descriptors for (0.)17 (theuni) +- #12402 `59e032b` expat 2.2.5, ccache 3.4.1, miniupnpc 2.0.20180203 (fanquake) +- #12029 `daa84b3` Add a makefile target for Doxygen documentation (Ov3rlo4d) +- #12466 `6645eaf` Only use `D_DARWIN_C_SOURCE` when building miniupnpc on darwin (fanquake) +- #11986 `765a3eb` zeromq 4.2.3 (fanquake) +- #12373 `f13d756` Add build support for profiling (murrayn) +- #12631 `a312e20` gitian: Alphabetize signing keys & add kallewoof key (kallewoof) +- #12607 `29fad97` Remove ccache (fanquake) +- #12625 `c4219ff` biplist 1.0.3 (fanquake) +- #12666 `05042d3` configure: UniValue 1.0.4 is required for pushKV(, bool) (luke-jr) +- #12678 `6324c68` Fix a few compilation issues with Clang 7 and -Werror (vasild) +- #12692 `de6bdfd` Add configure options for various -fsanitize flags (eklitzke) +- #12901 `7e23972` Show enabled sanitizers in configure output (practicalswift) +- #12899 `3076993` macOS: Prevent Xcode 9.3 build warnings (AkioNak) +- #12715 `8fd6243` Add 'make clean' rule (hkjn) +- #13133 `a024a18` Remove python2 from configure.ac (ken2812221) +- #13005 `cb088b1` Make --enable-debug to pick better options (practicalswift) +- #13254 `092b366` Remove improper `qt/moc_*` cleaning glob from the general Makefile (Empact) +- #13306 `f5a7733` split warnings out of CXXFLAGS (theuni) +- #13385 `7c7508c` Guard against accidental introduction of new Boost dependencies (practicalswift) +- #13041 `5779dc4` Add linter checking for accidental introduction of locale dependence (practicalswift) +- #13408 `70a03c6` crypto: cleanup sha256 build (theuni) +- #13435 `cf7ca60` When build fails due to lib missing, indicate which one (Empact) +- #13445 `8eb76f3` Reset default -g -O2 flags when enable debug (ken2812221) +- #13465 `81069a7` Avoid concurrency issue when make multiple target (ken2812221) +- #13454 `45c00f8` Make sure `LC_ALL=C` is set in all shell scripts (practicalswift) +- #13480 `31145a3` Avoid copies in range-for loops and add a warning to detect them (theuni) +- #13486 `66e1a08` Move rpc/util.cpp from libbitcoin-util to libbitcoin-server (ken2812221) +- #13580 `40334c7` Detect if char equals `int8_t` (ken2812221) +- #12788 `287e4ed` Tune wildcards for LIBSECP256K1 target (kallewoof) +- #13611 `b55f0c3` bugfix: Use `__cpuid_count` for gnu C to avoid gitian build fail (ken2812221) +- #12971 `a6d14b1` Upgrade Qt to 5.9.6 (TheCharlatan) +- #13543 `6c6a300` Add RISC-V support (laanwj) +- #13177 `dcb154e` GCC-7 and glibc-2.27 back compat code (ken2812221) +- #13659 `90b1c7e` add missing leveldb defines (theuni) +- #13368 `c0f1569` Update gitian-build.sh for docker (achow101) +- #13171 `19d8ca5` Change gitian-descriptors to use bionic instead (ken2812221) +- #13604 `75bea05` Add depends 32-bit arm support for bitcoin-qt (TheCharlatan) +- #13623 `9cdb19f` Migrate gitian-build.sh to python (ken2812221) +- #13689 `8c36432` disable Werror when building zmq (greenaddress) +- #13617 `cf7f9ae` release: Require macos 10.10+ (fanquake) +- #13750 `c883653` use MacOS friendly sed syntax in qt.mk (Sjors) +- #13095 `415f2bf` update `ax_boost_chrono`/`unit_test_framework` (fanquake) +- #13732 `e8ffec6` Fix Qt's rcc determinism (Fuzzbawls) +- #13782 `8284f1d` Fix osslsigncode compile issue in gitian-build (ken2812221) +- #13696 `2ab7208` Add aarch64 qt depends support for cross compiling bitcoin-qt (TheCharlatan) +- #13705 `b413ba0` Add format string linter (practicalswift) +- #14000 `48c8459` fix qt determinism (theuni) +- #14018 `3e4829a` Bugfix: NSIS: Exclude `Makefile*` from docs (luke-jr) +- #12906 `048ac83` Avoid `interface` keyword to fix windows gitian build (ryanofsky) +- #13314 `a9b6957` Fix FreeBSD build by including utilstrencodings.h (laanwj) + +### Tests and QA +- #12252 `8d57319` Require all tests to follow naming convention (ajtowns) +- #12295 `935eb8d` Enable flake8 warnings for all currently non-violated rules (practicalswift) +- #11858 `b4d8549` Prepare tests for Windows (MarcoFalke) +- #11771 `2dbc4a4` Change invalidtxrequest to use BitcoinTestFramework (jnewbery) +- #12200 `d09968f` Bind functional test nodes to 127.0.0.1 (Sjors) +- #12425 `26dc2da` Add some script tests (richardkiss) +- #12455 `23481fa` Fix bip68 sequence test to reflect updated rpc error message (Empact) +- #12477 `acd1e61` Plug memory leaks and stack-use-after-scope (MarcoFalke) +- #12443 `07090c5` Move common args to bitcoin.conf (MarcoFalke) +- #12570 `39dcac2` Add test cases for HexStr (`std::reverse_iterator` and corner cases) (kostaz) +- #12582 `6012f1c` Fix ListCoins test failure due to unset `g_wallet_allow_fallback_fee` (ryanofsky) +- #12516 `7f99964` Avoid unintentional unsigned integer wraparounds in tests (practicalswift) +- #12512 `955fd23` Don't test against the mempool min fee information in mempool_limit.py (Empact) +- #12600 `29088b1` Add a test for large tx output scripts with segwit input (richardkiss) +- #12627 `791c3ea` Fix some tests to work on native windows (MarcoFalke) +- #12405 `0f58d7f` travis: Full clone for git subtree check (MarcoFalke) +- #11772 `0630974` Change invalidblockrequest to use BitcoinTestFramework (jnewbery) +- #12681 `1846296` Fix ComputeTimeSmart test failure with `-DDEBUG_LOCKORDER` (ryanofsky) +- #12682 `9f04c8e` travis: Clone depth 1 unless `$check_doc` (MarcoFalke) +- #12710 `00d1680` Append scripts to new `test_list` array to fix bad assignment (jeffrade) +- #12720 `872c921` Avoiding 'file' function name from python2 (jeffrade) +- #12728 `4ba3d4f` rename TestNode to TestP2PConn in tests (jnewbery) +- #12746 `2405ce1` Remove unused argument `max_invalid` from `check_estimates(…)` (practicalswift) +- #12718 `185d484` Require exact match in `assert_start_raises_init_eror` (jnewbery, MarcoFalke) +- #12076 `6d36f59` Use node.datadir instead of tmpdir in test framework (MarcoFalke) +- #12772 `b43aba8` ci: Bump travis timeout for make check to 50m (jnewbery) +- #12806 `18606eb` Fix function names in `feature_blocksdir` (MarcoFalke) +- #12811 `0d8fc8d` Make summary row bold-red if any test failed and show failed tests at end of table (laanwj) +- #12790 `490644d` Use blockmaxweight where tests previously had blockmaxsize (conscott) +- #11773 `f0f9732` Change `feature_block.py` to use BitcoinTestFramework (jnewbery) +- #12839 `40f4baf` Remove travis checkout depth (laanwj) +- #11817 `2a09a78` Change `feature_csv_activation.py` to use BitcoinTestFramework (jnewbery) +- #12284 `fa5825d` Remove assigned but never used local variables. Enable Travis checking for unused local variables (practicalswift) +- #12719 `9beded5` Add note about test suite naming convention in developer-notes.md (practicalswift) +- #12861 `c564424` Stop `feature_block.py` from blowing up memory (jnewbery) +- #12851 `648252e` travis: Run verify-commits only on cron jobs (MarcoFalke) +- #12853 `2106c4c` Match full plain text by default (MarcoFalke) +- #11818 `9a2db3b` I accidentally (deliberately) killed it (the ComparisonTestFramework) (jnewbery) +- #12766 `69310a3` Tidy up REST interface functional tests (romanz) +- #12849 `83c7533` Add logging in loops in `p2p_sendhears.py` (ccdle12) +- #12895 `d6f10b2` Add note about test suite name uniqueness requirement to developer notes (practicalswift) +- #12856 `27278df` Add Metaclass for BitcoinTestFramework (WillAyd) +- #12918 `6fc5a05` Assert on correct variable (kallewoof) +- #11878 `a04440f` Add Travis check for duplicate includes (practicalswift) +- #12917 `cf8073f` Windows fixups for functional tests (MarcoFalke) +- #12926 `dd1ca9e` Run unit tests in parallel (sipa) +- #12920 `b1fdfc1` Fix sign for expected values (kallewoof) +- #12947 `979f598` Wallet hd functional test speedup and clarification (instagibbs) +- #12993 `0d69921` Remove compatibility code not needed now when we're on Python 3 (practicalswift) +- #12996 `6a278e0` Remove redundant bytes(…) calls (practicalswift) +- #12949 `6b46288` Avoid copies of CTransaction (MarcoFalke) +- #13007 `0d12570` Fix dangling wallet pointer in vpwallets (promag) +- #13048 `cac6d11` Fix `feature_block` flakiness (jnewbery) +- #12510 `d5b2e98` Add `rpc_bind` test to default-run tests (laanwj) +- #13022 `896a9d0` Attach node index to `test_node` AssertionError and print messages (jamesob) +- #13024 `018c7e5` Add rpcauth pair that generated by rpcauth.py (ken2812221) +- #13013 `a0079d4` bench: Amend `mempool_eviction` test for witness txs (MarcoFalke) +- #13051 `e074097` Normalize executable location (MarcoFalke) +- #13056 `106d929` Make rpcauth.py testable and add unit tests (nixbox) +- #13073 `a785bc3` add rpcauth-test to `AC_CONFIG_LINKS` to fix out-of-tree make check (laanwj) +- #12830 `25ad2f7` Clarify address book error messages, add tests (jamesob) +- #13082 `24106a8` don't test against min relay fee information in `mining_prioritisetransaction.py` (kristapsk) +- #13003 `8d045a0` Add test for orphan handling (MarcoFalke) +- #13105 `9e9b48d` Add --failfast option to functional test runner (jamesob) +- #13130 `3186ad4` Fix race in `rpc_deprecated.py` (jnewbery) +- #13136 `baf6b4e` Fix flake8 warnings in several wallet functional tests (jnewbery) +- #13094 `bf9b03d` Add test for 64-bit Windows PE, modify 32-bit test results (ken2812221) +- #13183 `9458b05` travis: New travis job for `check_docs` steps (glaksmono) +- #12265 `1834d4d` fundrawtransaction: lock watch-only shared address (kallewoof) +- #13188 `4a50ec0` Remove unused option --srcdir (MarcoFalke) +- #12755 `612ba35` Better stderr testing (jnewbery) +- #13198 `196c5a9` Avoid printing to console during cache creation (sdaftuar) +- #13075 `cb9bbf7` Remove 'account' API from wallet functional tests (jnewbery) +- #13221 `ffa86af` travis: Rename the build stage `check_doc` to `lint` (practicalswift) +- #13205 `3cbd25f` Remove spurious error log in `p2p_segwit.py` (jnewbery) +- #13291 `536120e` Don't include torcontrol.cpp into the test file (Empact) +- #13281 `2ac6315` Move linters to test/lint, add readme (MarcoFalke) +- #13215 `f8a29ca` travis: Build tests on ubuntu 18.04 with docker (ken2812221) +- #13349 `24f7011` bench: Don't return a bool from main (laanwj) +- #13347 `87a9d03` travis: Skip cache for lint stage (MarcoFalke) +- #13355 `0b1c0c4` Fix "gmake check" under OpenBSD 6.3 (probably `*BSD`): Avoid using GNU grep specific regexp handling (practicalswift) +- #13353 `d4f6dac` Fixup setting of PATH env var (MarcoFalke) +- #13352 `e24bf1c` Avoid checking reject code for now (MarcoFalke) +- #13383 `2722a1f` bench: Use non-throwing parsedouble(…) instead of throwing boost::lexical_cast<double>(…) (practicalswift) +- #13367 `264efdc` Increase includeconf test coverage (MarcoFalke) +- #13404 `3d3d8ae` speed up of `tx_validationcache_tests` by reusing of CTransaction (lucash-dev) +- #13421 `531a033` Remove `portseed_offset` from test runner (MarcoFalke) +- #13440 `5315660` Log as utf-8 (MarcoFalke) +- #13066 `fa4b906` Migrate verify-commits script to python, run in travis (ken2812221) +- #13447 `4b1edd3` travis: Increase `travis_wait` time while verifying commits (ken2812221) +- #13350 `f532d52` Add logging to provide anchor points when debugging p2p_sendheaders (lmanners) +- #13406 `4382f19` travis: Change mac goal to all deploy (ken2812221) +- #13457 `b222138` Drop variadic macro (MarcoFalke) +- #13512 `3a45493` mininode: Expose connection state through `is_connected` (MarcoFalke) +- #13496 `9ab4c2a` Harden lint-filenames.sh (wodry) +- #13219 `08516e0` bench: Add block assemble benchmark (MarcoFalke) +- #13530 `b1dc39d` bench: Add missing pow.h header (laanwj) +- #12686 `2643fa5` Add -ftrapv to CFLAGS and CXXFLAGS when --enable-debug is used. Enable -ftrapv in Travis (practicalswift) +- #12882 `d96bdd7` Make `test_bitcoin` pass under ThreadSanitzer (clang). Fix lock-order-inversion (potential deadlock) (practicalswift) +- #13535 `2328039` `wallet_basic`: Specify minimum required amount for listunspent (MarcoFalke) +- #13551 `c93c360` Fix incorrect documentation for test case `cuckoocache_hit_rate_ok` (practicalswift) +- #13563 `b330f3f` bench: Simplify coinselection (promag) +- #13517 `a6ed99a` Remove need to handle the network thread in tests (MarcoFalke) +- #13522 `686e97a` Fix `p2p_sendheaders` race (jnewbery) +- #13467 `3dc2dcf` Make `p2p_segwit` easier to debug (jnewbery) +- #13598 `0212187` bench: Fix incorrect behaviour in prevector.cpp (AkioNak) +- #13565 `b05ded1` Fix AreInputsStandard test to reference the proper scriptPubKey (Empact) +- #13145 `d3dae3d` Use common getPath method to create temp directory in tests (winder) +- #13645 `2ea7eb6` skip `rpc_zmq` functional test as necessary (jamesob) +- #13626 `8f1106d` Fix some TODOs in `p2p_segwit` (MarcoFalke) +- #13138 `8803c91` Remove accounts from `wallet_importprunedfunds.py` (jnewbery) +- #13663 `cbc9b50` Avoid read/write to default datadir (MarcoFalke) +- #13682 `f8a32a3` bench: Remove unused variable (practicalswift) +- #13638 `6fcdb5e` Use `MAX_SCRIPT_ELEMENT_SIZE` from script.py (domob1812) +- #13687 `9d26b69` travis: Check that ~/.bitcoin is never created (MarcoFalke) +- #13715 `e1260a7` fixes mininode's P2PConnection sending messages on closing transport (marcoagner) +- #13729 `aa9429a` travis: Avoid unnecessarily setting env variables on the lint build (Empact) +- #13747 `ab28b5b` Skip P2PConnection's `is_closing()` check when not available (domob1812) +- #13650 `7a9bca6` travis: Don't store debug info if --enable-debug is set (ken2812221) +- #13711 `f98d1e0` bench: Add benchmark for unserialize prevector (AkioNak) +- #13771 `365384f` travis: Retry to fetch docker image (MarcoFalke) +- #13806 `4d550ff` Fix `bench/block_assemble` assert failure (jamesob) +- #13779 `d25079a` travis: Improve readability of travis.yml and log outputs (scravy) +- #13822 `0fb9c87` bench: Make coinselection output groups pass eligibility filter (achow101) +- #13247 `e83d82a` Add tests to SingleThreadedSchedulerClient() and document the memory model (skeees) +- #13811 `660abc1` travis: Run `bench_bitcoin` once (MarcoFalke) +- #13837 `990e182` Extract `rpc_timewait` as test param (MarcoFalke) +- #13851 `9c4324d` fix locale for lint-shell (scravy) +- #13823 `489b51b` quote path in authproxy for external multiwallets (MarcoFalke) +- #13849 `2b67354` travis: Use only travis jobs: instead of mix of jobs+matrix (scravy) +- #13859 `2384323` Add emojis to `test_runner` path and wallet filename (MarcoFalke) +- #13916 `8ac7125` `wait_for_verack` by default (MarcoFalke) +- #13669 `f66e1c7` Cleanup `create_transaction` implementations (conscott) +- #13924 `09ada21` Simplify comparison in `rpc_blockchain.py` (domob1812) +- #13913 `a08533c` Remove redundant checkmempool/checkblockindex `extra_args` (MarcoFalke) +- #13915 `a04888a` Add test for max number of entries in locator (MarcoFalke) +- #13867 `1b04b55` Make extended tests pass on native Windows (MarcoFalke) +- #13944 `0df7a6c` Port usage of deprecated optparse module to argparse module (Kvaciral) +- #13928 `b8eb0df` blocktools enforce named args for amount (MarcoFalke) +- #13054 `bffb35f` Enable automatic detection of undefined names in Python tests scripts. Remove wildcard imports (practicalswift) +- #14069 `cf3d7f9` Use assert not `BOOST_CHECK_*` from multithreaded tests (skeees) +- #14071 `fab0fbe` Stop txindex thread before calling destructor (MarcoFalke) + +### Miscellaneous +- #11909 `8897135` contrib: Replace developer keys with list of pgp fingerprints (MarcoFalke) +- #12394 `fe53d5f` gitian-builder.sh: fix --setup doc, since lxc is default (Sjors) +- #12468 `294a766` Add missing newline in init.cpp log message (Aesti) +- #12308 `dcfe218` contrib: Add support for out-of-tree builds in gen-manpages.sh (laanwj) +- #12451 `aae64a2` Bump leveldb subtree (MarcoFalke) +- #12527 `d77b4a7` gitian-build.sh: fix signProg being recognized as two parameters (ken2812221) +- #12588 `d74b01d` utils: Remove deprecated pyzmq call from python zmq example (kosciej) +- #10271 `bc67982` Use `std::thread::hardware_concurrency`, instead of Boost, to determine available cores (fanquake) +- #12097 `14475e2` scripts: Lint-whitespace: use perl instead of grep -p (Sjors) +- #12098 `17c44b2` scripts: Lint-whitespace: add param to check last n commits (Sjors) +- #11900 `842f61a` script: Simplify checkminimalpush checks, add safety assert (instagibbs) +- #12567 `bb98aec` util: Print timestamp strings in logs using iso 8601 formatting (practicalswift) +- #12572 `d8d9162` script: Lint-whitespace: find errors more easily (AkioNak) +- #10694 `ae5bcc7` Remove redundant code in MutateTxSign(CMutableTransaction&, const std::string&) (practicalswift) +- #12659 `3d16f58` Improve Fatal LevelDB Log Messages (eklitzke) +- #12643 `0f0229d` util: Remove unused `sync_chain` (MarcoFalke) +- #12102 `7fb8fb4` Apply hardening measures in bitcoind systemd service file (Flowdalic) +- #12652 `55f490a` bitcoin-cli: Provide a better error message when bitcoind is not running (practicalswift) +- #12630 `c290508` Provide useful error message if datadir is not writable (murrayn) +- #11881 `624bee9` Remove Python2 support (jnewbery) +- #12821 `082e26c` contrib: Remove unused import string (MarcoFalke) +- #12829 `252c1b0` Python3 fixup (jnewbery) +- #12822 `ff48f62` Revert 7deba93bdc76616011a9f493cbc203d60084416f and fix expired-key-sigs properly (TheBlueMatt) +- #12820 `5e53b80` contrib: Fix check-doc script regexes (MarcoFalke) +- #12713 `4490871` Track negated options in the option parser (eklitzke) +- #12708 `b2e5fe8` Make verify-commits.sh test that merges are clean (sipa) +- #12891 `3190785` logging: Add lint-logs.sh to check for newline termination (jnewbery) +- #12923 `a7cbe38` util: Pass `pthread_self()` to `pthread_setschedparam` instead of 0 (laanwj) +- #12871 `fb17fae` Add shell script linting: Check for shellcheck warnings in shell scripts (practicalswift) +- #12970 `5df84de` logging: Bypass timestamp formatting when not logging (theuni) +- #12987 `fe8fa22` tests/tools: Enable additional Python flake8 rules for automatic linting via Travis (practicalswift) +- #12972 `0782508` Add python3 script shebang lint (ken2812221) +- #13004 `58bbc55` Print to console by default when not run with -daemon (practicalswift) +- #13039 `8b4081a` Add logging and error handling for file syncing (laanwj) +- #13020 `4741ca5` Consistently log CValidationState on call failure (Empact) +- #13031 `826acc9` Fix for utiltime to compile with msvc (sipsorcery) +- #13119 `81743b5` Remove script to clean up datadirs (MarcoFalke) +- #12954 `5a66642` util: Refactor logging code into a global object (jimpo) +- #12769 `35eb9d6` Add systemd service to bitcoind in debian package (ghost) +- #13146 `0bc980b` rpcauth: Make it possible to provide a custom password (laanwj) +- #13148 `b62b437` logging: Fix potential use-after-free in logprintstr(…) (practicalswift) +- #13214 `0612d96` Enable Travis checking for two Python linting rules we are currently not violating (practicalswift) +- #13197 `6826989` util: Warn about ignored recursive -includeconf calls (kallewoof) +- #13176 `d9ebb63` Improve CRollingBloomFilter performance: replace modulus with FastMod (martinus) +- #13228 `d792e47` Add script to detect circular dependencies between source modules (sipa) +- #13320 `e08c130` Ensure gitian-build.sh uses bash (jhfrontz) +- #13301 `e4082d5` lint: Add linter to error on `#include <*.cpp>` (Empact) +- #13374 `56f6936` utils and libraries: checking for bitcoin address in translations (kaplanmaxe) +- #13230 `7c32b41` Simplify include analysis by enforcing the developer guide's include syntax (practicalswift) +- #13450 `32bf4c6` Add linter: Enforce the source code file naming convention described in the developer notes (practicalswift) +- #13479 `fa2ea37` contrib: Fix cve-2018-12356 by hardening the regex (loganaden) +- #13448 `a90ca40` Add linter: Make sure we explicitly open all text files using UTF-8 encoding in Python (practicalswift) +- #13494 `d67eff8` Follow-up to #13454: Fix broken build by exporting `LC_ALL=C` (practicalswift) +- #13510 `03f3925` Scripts and tools: Obsolete #!/bin/bash shebang (DesWurstes) +- #13577 `c9eb8d1` logging: Avoid nstart may be used uninitialized in appinitmain warning (mruddy) +- #13603 `453ae5e` bitcoin-tx: Stricter check for valid integers (domob1812) +- #13118 `c05c93c` RPCAuth Detection in Logs (Linrono) +- #13647 `4027ec1` Scripts and tools: Fix `BIND_NOW` check in security-check.py (conradoplg) +- #13692 `f5d166a` contrib: Clone core repo in gitian-build (MarcoFalke) +- #13699 `4c6d1b9` contrib: Correct version check (kallewoof) +- #13695 `dcc0cff` lint: Add linter for circular dependencies (Empact) +- #13733 `0d1ebf4` utils: Refactor argsmanager a little (AtsukiTak) +- #13714 `29b4ee6` contrib: Add lxc network setup for bionic host (ken2812221) +- #13764 `f8685f4` contrib: Fix test-security-check fail in ubuntu 18.04 (ken2812221) +- #13809 `77168f7` contrib: Remove debian and rpm subfolder (MarcoFalke) +- #13799 `230652c` Ignore unknown config file options; warn instead of error (sipa) +- #13894 `df9f712` shutdown: Stop threads before resetting ptrs (MarcoFalke) +- #13925 `71dec5c` Merge leveldb subtree (MarcoFalke) +- #13939 `ef86f26` lint: Make format string linter understand basic template parameter syntax (practicalswift) +- #14105 `eb202ea` util: Report parse errors in configuration file (laanwj) +- #12604 `9903537` Add DynamicMemoryUsage() to CDBWrapper to estimate LevelDB memory use (eklitzke) +- #12495 `047865e` Increase LevelDB `max_open_files` (eklitzke) +- #12784 `e80716d` Fix bug in memory usage calculation (unintended integer division) (practicalswift) +- #12618 `becd8dd` Set `SCHED_BATCH` priority on the loadblk thread (eklitzke) +- #12854 `5ca1509` Add P2P, Network, and Qt categories to the desktop icon (luke-jr) +- #11862 `4366f61` Network specific conf sections (ajtowns) +- #13441 `4a7e64f` Prevent shared conf files from failing with different available options in different binaries (achow101) +- #13471 `5eca4e8` For AVX2 code, also check for AVX, XSAVE, and OS support (sipa) +- #13503 `c655b2c` Document FreeBSD quirk. Fix FreeBSD build: Use std::min<int>(…) to allow for compilation under certain FreeBSD versions (practicalswift) +- #13725 `07ce278` Fix bitcoin-cli --version (Empact) + +### Documentation +- #12306 `216f9a4` Improvements to UNIX documentation (axvr) +- #12309 `895fbd7` Explain how to update chainTxData in release process (laanwj) +- #12317 `85123be` Document method for reviewers to verify chainTxData (jnewbery) +- #12331 `d32528e` Properly alphabetize output of CLI --help option (murrayn) +- #12322 `c345148` Remove step making cloned repository world-writable for Windows build (murrayn) +- #12354 `b264528` add gpg key for fivepiece (fivepiece) +- #11761 `89005dd` initial QT documentation (Sjors) +- #12232 `fdc2188` Improve "Turn Windows Features On or Off" step (MCFX2) +- #12487 `4528f74` init: Remove translation for `-blockmaxsize` option help (laanwj) +- #12546 `a4a5fc7` Minor improvements to Compatibility Notes (randolf) +- #12434 `21e2670` dev-notes: Members should be initialized (MarcoFalke) +- #12452 `71f56da` clarified systemd installation instructions in init.md for Ubuntu users (DaveFromBinary) +- #12615 `1f93491` allow for SIGNER containing spaces (ken2812221) +- #12603 `85424d7` PeerLogicValidation interface (jamesob) +- #12581 `12ac2f0` Mention configure without wallet in FreeBSD instructions (dbolser) +- #12619 `8a709fb` Give hint about gitian not able to download (kallewoof) +- #12668 `de2fcaa` do update before fetching packages in WSL build guide (nvercamm) +- #12586 `e7721e6` Update osx brew install instruction (fanquake) +- #12760 `7466a26` Improve documentation on standard communication channels (jimpo) +- #12797 `0415b1e` init: Fix help message for checkblockindex (MarcoFalke) +- #12800 `2d97611` Add note about our preference for scoped enumerations ("enum class") (practicalswift) +- #12798 `174d016` Refer to witness reserved value as spec. in the BIP (MarcoFalke) +- #12759 `d3908e2` Improve formatting of developer notes (eklitzke) +- #12877 `2b54155` Use bitcoind in Tor documentation (knoxcard) +- #12896 `b15485e` Fix conflicting statements about initialization in developer notes (practicalswift) +- #12850 `319991d` add qrencode to brew install instructions (buddilla) +- #12007 `cd8e45b` Clarify the meaning of fee delta not being a fee rate in prioritisetransaction RPC (honzik666) +- #12927 `06ead15` fixed link, replaced QT with Qt (trulex) +- #12852 `ebd786b` devtools: Setup ots git integration (MarcoFalke) +- #12933 `3cf76c2` Refine header include policy (MarcoFalke) +- #12951 `6df0c6c` Fix comment in FindForkInGlobalIndex (jamesob) +- #12982 `a63b4e3` Fix inconsistent namespace formatting guidelines (ryanofsky) +- #13026 `9b3a67e` Fix include comment in src/interfaces/wallet.h (promag) +- #13012 `d1e3c5e` Add comments for chainparams.h, validation.cpp (jamesob) +- #13064 `569e381` List support for BIP173 in bips.md (sipa) +- #12997 `646b7f6` build-windows: Switch to Artful, since Zesty is EOL (MarcoFalke) +- #12384 `c5f7efe` Add version footnote to tor.md (Willtech) +- #13165 `627c376` Mention good first issue list in CONTRIBUTING.md (fanquake) +- #13295 `fb77310` Update OpenBSD build instructions for OpenBSD 6.3 (practicalswift) +- #13340 `3a8e3f4` remove leftover check-doc documentation (fanquake) +- #13346 `60f0358` update bitcoin-dot-org links in release-process.md (fanquake) +- #13372 `f014933` split FreeBSD build instructions out of build-unix.md (steverusso) +- #13366 `861de3b` Rename “OS X” to the newer “macOS” convention (giulio92) +- #13369 `f8bcef3` update transifex doc link (mess110) +- #13312 `b22115d` Add a note about the source code filename naming convention (practicalswift) +- #13460 `1939536` Remove note to install all boost dev packages (MarcoFalke) +- #13476 `9501938` Fix incorrect shell quoting in FreeBSD build instructions (murrayn) +- #13402 `43fa355` Document validationinterace callback blocking deadlock potential (TheBlueMatt) +- #13488 `d6cf4bd` Improve readability of "Squashing commits" (wodry) +- #13531 `ee02deb` Clarify that mempool txiter is `const_iterator` (MarcoFalke) +- #13418 `01f9098` More precise explanation of parameter onlynet (wodry) +- #13592 `1756cb4` Modify policy to not translate command-line help (ken2812221) +- #13588 `b77c38e` Improve doc of options addnode, connect, seednode (wodry) +- #13614 `17e9106` Update command line help for -printtoconsole and -debuglogfile (satwo, fanquake) +- #13605 `8cc048e` corrected text to reflect new(er) process of specifying fingerprints (jhfrontz) +- #13481 `b641f60` Rewrite some validation docs as lock annotations (MarcoFalke) +- #13680 `30640f8` Remove outdated comment about miner ignoring CPFP (jamesob) +- #13625 `7146672` Add release notes for -printtoconsole and -debuglogfile changes (satwo) +- #13718 `f7f574d` Specify preferred Python string formatting technique (masonicboom) +- #12764 `10b9a81` Remove field in getblocktemplate help that has never been used (conscott) +- #13742 `d2186b3` Adjust bitcoincore.org links (MarcoFalke) +- #13706 `94dd89e` Minor improvements to release-process.md (MitchellCash) +- #13775 `ef4fac0` Remove newlines from error message (practicalswift) +- #13803 `feb7dd9` add note to contributor docs about warranted PR's (kallewoof) +- #13814 `67af7ef` Add BIP174 to list of implemented BIPs (sipa) +- #13835 `c1cba35` Fix memory consistency model in comment (skeees) +- #13824 `aa30e4b` Remove outdated net comment (MarcoFalke) +- #13853 `317477a` correct versions in dependencies.md (fanquake) +- #13872 `37ab117` Reformat -help output for help2man (real-or-random) +- #13717 `8c3c402` Link to python style guidelines from developer notes (masonicboom) +- #13895 `1cd5f2c` fix GetWarnings docs to reflect behavior (Empact) +- #13911 `3e3a50a` Revert translated string change, clarify wallet log messages (PierreRochard) +- #13908 `d6faea4` upgrade rescan time warning from minutes to >1 hour (masonicboom) +- #13905 `73a09b4` fixed bitcoin-cli -help output for help2man (hebasto) +- #14100 `2936dbc` Change documentation for =0 for non-boolean options (laanwj) +- #14096 `465a583` Add reference documentation for descriptors language (sipa) +- #12757 `0c5f67b` Clarify include guard naming convention (practicalswift) +- #13844 `d3325b0` Correct the help output for `-prune` (hebasto) + +Credits +======= + +Thanks to everyone who directly contributed to this release: + +- 251 +- 532479301 +- Aaron Clauson +- Akio Nakamura +- Akira Takizawa +- Alex Morcos +- Alex Vear +- Alexey Ivanov +- Alin Rus +- Andrea Comand +- Andrew Chow +- Anthony Towns +- AtsukiTak +- Ben Woosley +- Bernhard M. Wiedemann +- Brandon Ruggles +- buddilla +- ccdle12 +- Chris Moore +- Chun Kuan Lee +- Clem Taylor +- Conor Scott +- Conrado Gouvea +- Cory Fields +- Cristian Mircea Messel +- ctp-tsteenholdt +- Damian Williamson +- Dan Bolser +- Daniel Kraft +- Darko Janković +- DaveFromBinary +- David A. Harding +- DesWurstes +- Dimitris Apostolou +- donaloconnor +- Douglas Roark +- DrahtBot +- Drew Rasmussen +- e0 +- Ernest Hemingway +- Ethan Heilman +- Evan Klitzke +- fanquake +- Felix Wolfsteller +- fivepiece +- Florian Schmaus +- Fuzzbawls +- Gabriel Davidian +- Giulio Lombardo +- Gleb +- Grady Laksmono +- GreatSock +- Gregory Maxwell +- Gregory Sanders +- Hennadii Stepanov +- Henrik Jonsson +- Indospace.io +- James O'Beirne +- Jan Čapek +- Jeff Frontz +- Jeff Rade +- Jeremy Rubin +- JeremyRand +- Jesse Cohen +- Jim Posen +- joemphilips +- John Bampton +- John Newbery +- johnlow95 +- Johnson Lau +- Jonas Nick +- Jonas Schnelli +- João Barbosa +- Jorge Timón +- Josh Hartshorn +- Julian Fleischer +- kallewoof +- Karel Bilek +- Karl-Johan Alm +- Ken Lee +- Kevin Pan +- Kosta Zertsekel +- Kristaps Kaupe +- Kvaciral +- Lawrence Nahum +- Linrono +- lmanners +- Loganaden Velvindron +- Lowell Manners +- lucash.dev@gmail.com +- Luke Dashjr +- lutangar +- Marcin Jachymiak +- marcoagner +- MarcoFalke +- Mark Erhardt +- Mark Friedenbach +- Martin Ankerl +- Mason Simon +- Matt Corallo +- Matteo Sumberaz +- Max Kaplan +- MeshCollider +- Michał Zabielski +- Mitchell Cash +- mruddy +- mryandao +- murrayn +- Nick Vercammen +- Nicolas Dorier +- Nikolay Mitev +- okayplanet +- Pierre Rochard +- Pieter Wuille +- practicalswift +- Qasim Javed +- Randolf Richardson +- Richard Kiss +- Roman Zeyde +- Russell Yanofsky +- Samuel B. Atwood +- Sebastian Kung +- Sjors Provoost +- Steve Lee +- steverusso +- Suhas Daftuar +- Tamas Blummer +- TheCharlatan +- Thomas Kerin +- Thomas Snider +- Tim Ruffing +- Varunram +- Vasil Dimov +- Will Ayd +- William Robinson +- winder +- Wladimir J. van der Laan +- wodry + +And to those that reported security issues: + +- awemany (for CVE-2018-17144, previously credited as "anonymous reporter") + +As well as everyone that helped translating on [Transifex](https://www.transifex.com/projects/p/bitcoin/). diff --git a/doc/release-notes/release-notes-0.17.1.md b/doc/release-notes/release-notes-0.17.1.md new file mode 100644 index 0000000000..b1e50e0391 --- /dev/null +++ b/doc/release-notes/release-notes-0.17.1.md @@ -0,0 +1,168 @@ +Bitcoin Core version 0.17.1 is now available from: + + <https://bitcoincore.org/bin/bitcoin-core-0.17.1/> + +or through BitTorrent: + + magnet:?xt=urn:btih:c56c87ccfaa8e6fbccc90d549121e61efd97cb6f&dn=bitcoin-core-0.17.1&tr=udp%3A%2F%2Ftracker.openbittorrent.com%3A80&tr=udp%3A%2F%2Ftracker.opentrackr.org%3A1337&tr=udp%3A%2F%2Ftracker.coppersurfer.tk%3A6969&tr=udp%3A%2F%2Ftracker.leechers-paradise.org%3A6969&tr=udp%3A%2F%2Fzer0day.ch%3A1337&tr=udp%3A%2F%2Fexplodie.org%3A6969 + +This is a new minor version release, with various bugfixes +and performance improvements, as well as updated translations. + +Please report bugs using the issue tracker at GitHub: + + <https://github.com/bitcoin/bitcoin/issues> + +To receive security and update notifications, please subscribe to: + + <https://bitcoincore.org/en/list/announcements/join/> + +How to Upgrade +============== + +If you are running an older version, shut it down. Wait until it has completely +shut down (which might take a few minutes for older versions), then run the +installer (on Windows) or just copy over `/Applications/Bitcoin-Qt` (on Mac) +or `bitcoind`/`bitcoin-qt` (on Linux). + +If your node has a txindex, the txindex db will be migrated the first time you run 0.17.0 or newer, which may take up to a few hours. Your node will not be functional until this migration completes. + +The first time you run version 0.15.0 or newer, your chainstate database will be converted to a +new format, which will take anywhere from a few minutes to half an hour, +depending on the speed of your machine. + +Note that the block database format also changed in version 0.8.0 and there is no +automatic upgrade code from before version 0.8 to version 0.15.0. Upgrading +directly from 0.7.x and earlier without redownloading the blockchain is not supported. +However, as usual, old wallet versions are still supported. + +Downgrading warning +------------------- + +The chainstate database for this release is not compatible with previous +releases, so if you run 0.15 and then decide to switch back to any +older version, you will need to run the old release with the `-reindex-chainstate` +option to rebuild the chainstate data structures in the old format. + +If your node has pruning enabled, this will entail re-downloading and +processing the entire blockchain. + +Compatibility +============== + +Bitcoin Core is extensively tested on multiple operating systems using +the Linux kernel, macOS 10.10+, and Windows 7 and newer (Windows XP is not supported). + +Bitcoin Core should also work on most other Unix-like systems but is not +frequently tested on them. + +From 0.17.0 onwards macOS <10.10 is no longer supported. 0.17.0 is built using Qt 5.9.x, which doesn't +support versions of macOS older than 10.10. + +Notable changes +=============== + +`listtransactions` label support +-------------------------------- + +The `listtransactions` RPC `account` parameter which was deprecated in 0.17.0 +and renamed to `dummy` has been un-deprecated and renamed again to `label`. + +When bitcoin is configured with the `-deprecatedrpc=accounts` setting, specifying +a label/account/dummy argument will return both outgoing and incoming +transactions. Without the `-deprecatedrpc=accounts` setting, it will only return +incoming transactions (because it used to be possible to create transactions +spending from specific accounts, but this is no longer possible with labels). + +When `-deprecatedrpc=accounts` is set, it's possible to pass the empty string "" +to list transactions that don't have any label. Without +`-deprecatedrpc=accounts`, passing the empty string is an error because returning +only non-labeled transactions is not generally useful behavior and can cause +confusion. + +0.17.1 change log +================= + +### P2P protocol and network code +- #14685 `9406502` Fix a deserialization overflow edge case (kazcw) +- #14728 `b901578` Fix uninitialized read when stringifying an addrLocal (kazcw) + +### Wallet +- #14441 `5150acc` Restore ability to list incoming transactions by label (jnewbery) +- #13546 `91fa15a` Fix use of uninitialized value `bnb_used` in CWallet::CreateTransaction(…) (practicalswift) +- #14310 `bb90695` Ensure wallet is unlocked before signing (gustavonalle) +- #14690 `5782fdc` Throw error if CPubKey is invalid during PSBT keypath serialization (instagibbs) +- #14852 `2528443` backport: [tests] Add `wallet_balance.py` (MarcoFalke) +- #14196 `3362a95` psbt: always drop the unnecessary utxo and convert non-witness utxo to witness when necessary (achow101) +- #14588 `70ee1f8` Refactor PSBT signing logic to enforce invariant and fix signing bug (gwillen) +- #14424 `89a9a9d` Stop requiring imported pubkey to sign non-PKH schemes (sipa, MeshCollider) + +### RPC and other APIs +- #14417 `fb9ad04` Fix listreceivedbyaddress not taking address as a string (etscrivner) +- #14596 `de5e48a` Bugfix: RPC: Add `address_type` named param for createmultisig (luke-jr) +- #14618 `9666dba` Make HTTP RPC debug logging more informative (practicalswift) +- #14197 `7bee414` [psbt] Convert non-witness UTXOs to witness if witness sig created (achow101) +- #14377 `a3fe125` Check that a separator is found for psbt inputs, outputs, and global map (achow101) +- #14356 `7a590d8` Fix converttopsbt permitsigdata arg, add basic test (instagibbs) +- #14453 `75b5d8c` Fix wallet unload during walletpassphrase timeout (promag) + +### GUI +- #14403 `0242b5a` Revert "Force TLS1.0+ for SSL connections" (real-or-random) +- #14593 `df5131b` Explicitly disable "Dark Mode" appearance on macOS (fanquake) + +### Build system +- #14647 `7edebed` Remove illegal spacing in darwin.mk (ch4ot1c) +- #14698 `ec71f06` Add bitcoin-tx.exe into Windows installer (ken2812221) + +### Tests and QA +- #13965 `29899ec` Fix extended functional tests fail (ken2812221) +- #14011 `9461f98` Disable wallet and address book Qt tests on macOS minimal platform (ryanofsky) +- #14180 `86fadee` Run all tests even if wallet is not compiled (MarcoFalke) +- #14122 `8bc1bad` Test `rpc_help.py` failed: Check whether ZMQ is enabled or not (Kvaciral) +- #14101 `96dc936` Use named args in validation acceptance tests (MarcoFalke) +- #14020 `24d796a` Add tests for RPC help (promag) +- #14052 `7ff32a6` Add some actual witness in `rpc_rawtransaction` (MarcoFalke) +- #14215 `b72fbab` Use correct python index slices in example test (sdaftuar) +- #14024 `06544fa` Add `TestNode::assert_debug_log` (MarcoFalke) +- #14658 `60f7a97` Add test to ensure node can generate all rpc help texts at runtime (MarcoFalke) +- #14632 `96f15e8` Fix a comment (fridokus) +- #14700 `f9db08e` Avoid race in `p2p_invalid_block` by waiting for the block request (MarcoFalke) +- #14845 `67225e2` Add `wallet_balance.py` (jnewbery) + +### Documentation +- #14161 `5f51fd6` doc/descriptors.md tweaks (ryanofsky) +- #14276 `85aacc4` Add autogen.sh in ARM Cross-compilation (walterwhite81) + +Credits +======= + +Thanks to everyone who directly contributed to this release: + +- Andrew Chow +- Chun Kuan Lee +- David A. Harding +- Eric Scrivner +- fanquake +- fridokus +- Glenn Willen +- Gregory Sanders +- gustavonalle +- John Newbery +- Jon Layton +- Jonas Schnelli +- João Barbosa +- Kaz Wesley +- Kvaciral +- Luke Dashjr +- MarcoFalke +- MeshCollider +- Pieter Wuille +- practicalswift +- Russell Yanofsky +- Sjors Provoost +- Suhas Daftuar +- Tim Ruffing +- Walter +- Wladimir J. van der Laan + +As well as everyone that helped translating on [Transifex](https://www.transifex.com/projects/p/bitcoin/). diff --git a/doc/release-process.md b/doc/release-process.md index 3ba622ee6d..9fcd5e2298 100644 --- a/doc/release-process.md +++ b/doc/release-process.md @@ -6,11 +6,12 @@ Before every release candidate: * Update translations (ping wumpus on IRC) see [translation_process.md](https://github.com/bitcoin/bitcoin/blob/master/doc/translation_process.md#synchronising-translations). * Update manpages, see [gen-manpages.sh](https://github.com/bitcoin/bitcoin/blob/master/contrib/devtools/README.md#gen-manpagessh). +* Update release candidate version in `configure.ac` (`CLIENT_VERSION_RC`) Before every minor and major release: * Update [bips.md](bips.md) to account for changes since the last release. -* Update version in `configure.ac` (don't forget to set `CLIENT_VERSION_IS_RELEASE` to `true`) +* Update version in `configure.ac` (don't forget to set `CLIENT_VERSION_IS_RELEASE` to `true`) (don't forget to set `CLIENT_VERSION_RC` to `0`) * Write release notes (see below) * Update `src/chainparams.cpp` nMinimumChainWork with information from the getblockchaininfo rpc. * Update `src/chainparams.cpp` defaultAssumeValid with information from the getblockhash rpc. @@ -22,7 +23,7 @@ Before every minor and major release: Before every major release: * Update hardcoded [seeds](/contrib/seeds/README.md), see [this pull request](https://github.com/bitcoin/bitcoin/pull/7415) for an example. -* Update [`BLOCK_CHAIN_SIZE`](/src/qt/intro.cpp) to the current size plus some overhead. +* Update [`src/chainparams.cpp`](/src/chainparams.cpp) m_assumed_blockchain_size and m_assumed_chain_state_size with the current size plus some overhead. * Update `src/chainparams.cpp` chainTxData with statistics about the transaction count and rate. Use the output of the RPC `getchaintxstats`, see [this pull request](https://github.com/bitcoin/bitcoin/pull/12270) for an example. Reviewers can verify the results by running `getchaintxstats <window_block_count> <window_last_block_hash>` with the `window_block_count` and `window_last_block_hash` from your output. * Update version of `contrib/gitian-descriptors/*.yml`: usually one'd want to do this on master after branching off the release - but be sure to at least do it before a new major release @@ -86,7 +87,9 @@ Ensure gitian-builder is up-to-date: pushd ./gitian-builder mkdir -p inputs wget -P inputs https://bitcoincore.org/cfields/osslsigncode-Backports-to-1.7.1.patch - wget -P inputs http://downloads.sourceforge.net/project/osslsigncode/osslsigncode/osslsigncode-1.7.1.tar.gz + echo 'a8c4e9cafba922f89de0df1f2152e7be286aba73f78505169bc351a7938dd911 inputs/osslsigncode-Backports-to-1.7.1.patch' | sha256sum -c + wget -P inputs https://downloads.sourceforge.net/project/osslsigncode/osslsigncode/osslsigncode-1.7.1.tar.gz + echo 'f9a8cdb38b9c309326764ebc937cba1523a3a751a7ab05df3ecc99d18ae466c9 inputs/osslsigncode-1.7.1.tar.gz' | sha256sum -c popd Create the macOS SDK tarball, see the [macOS readme](README_osx.md) for details, and copy it into the inputs directory. @@ -296,6 +299,8 @@ bitcoin.org (see below for bitcoin.org update instructions). - bitcoincore.org blog post + - bitcoincore.org RPC documentation update + - Update title of #bitcoin on Freenode IRC - Optionally twitter, reddit /r/Bitcoin, ... but this will usually sort out itself diff --git a/doc/tor.md b/doc/tor.md index dc0b88618a..c46b7e9f60 100644 --- a/doc/tor.md +++ b/doc/tor.md @@ -109,9 +109,13 @@ preconfigured and the creation of a hidden service is automatic. If permission p are seen with `-debug=tor` they can be resolved by adding both the user running Tor and the user running bitcoind to the same group and setting permissions appropriately. On Debian-based systems the user running bitcoind can be added to the debian-tor group, -which has the appropriate permissions. An alternative authentication method is the use -of the `-torpassword` flag and a `hash-password` which can be enabled and specified in -Tor configuration. +which has the appropriate permissions. + +An alternative authentication method is the use +of the `-torpassword=password` option. The `password` is the clear text form that +was used when generating the hashed password for the `HashedControlPassword` option +in the tor configuration file. The hashed password can be obtained with the command +`tor --hash-password password` (read the tor manual for more details). ## 4. Privacy recommendations diff --git a/doc/zmq.md b/doc/zmq.md index a1a506f2e7..7ffc5623b6 100644 --- a/doc/zmq.md +++ b/doc/zmq.md @@ -66,10 +66,21 @@ Currently, the following notifications are supported: The socket type is PUB and the address must be a valid ZeroMQ socket address. The same address can be used in more than one notification. +The option to set the PUB socket's outbound message high water mark +(SNDHWM) may be set individually for each notification: + + -zmqpubhashtxhwm=n + -zmqpubhashblockhwm=n + -zmqpubrawblockhwm=n + -zmqpubrawtxhwm=n + +The high water mark value must be an integer greater than or equal to 0. + For instance: $ bitcoind -zmqpubhashtx=tcp://127.0.0.1:28332 \ - -zmqpubrawtx=ipc:///tmp/bitcoind.tx.raw + -zmqpubrawtx=ipc:///tmp/bitcoind.tx.raw \ + -zmqpubhashtxhwm=10000 Each PUB notification has a topic and body, where the header corresponds to the notification type. For instance, for the diff --git a/share/examples/bitcoin.conf b/share/examples/bitcoin.conf index 4dd73162a2..6062d49c40 100644 --- a/share/examples/bitcoin.conf +++ b/share/examples/bitcoin.conf @@ -71,12 +71,9 @@ # is .cookie and found in the `-datadir` being used for bitcoind. This option is typically used # when the server and client are run as the same user. # -# If not, you must set rpcuser and rpcpassword to secure the JSON-RPC api. The first -# method(DEPRECATED) is to set this pair for the server and client: -#rpcuser=Ulysseys -#rpcpassword=YourSuperGreatPasswordNumber_DO_NOT_USE_THIS_OR_YOU_WILL_GET_ROBBED_385593 +# If not, you must set rpcuser and rpcpassword to secure the JSON-RPC API. # -# The second method `rpcauth` can be added to server startup argument. It is set at initialization time +# The config option `rpcauth` can be added to server startup argument. It is set at initialization time # using the output from the script in share/rpcauth/rpcauth.py after providing a username: # # ./share/rpcauth/rpcauth.py alice @@ -116,21 +113,21 @@ # running on another host using this option: #rpcconnect=127.0.0.1 +# Wallet options + # Create transactions that have enough fees so they are likely to begin confirmation within n blocks (default: 6). # This setting is over-ridden by the -paytxfee option. #txconfirmtarget=n +# Pay a transaction fee every time you send bitcoins. +#paytxfee=0.000x + # Miscellaneous options # Pre-generate this many public/private key pairs, so wallet backups will be valid for # both prior transactions and several dozen future transactions. #keypool=100 -# Pay an optional transaction fee every time you send bitcoins. Transactions with fees -# are more likely than free transactions to be included in generated blocks, so may -# be validated sooner. -#paytxfee=0.00 - # Enable pruning to reduce storage requirements by deleting old blocks. # This mode is incompatible with -txindex and -rescan. # 0 = default (no pruning). diff --git a/share/qt/Info.plist.in b/share/qt/Info.plist.in index 17b4ee47de..3cd130ce48 100644 --- a/share/qt/Info.plist.in +++ b/share/qt/Info.plist.in @@ -17,7 +17,7 @@ <string>APPL</string> <key>CFBundleGetInfoString</key> - <string>@CLIENT_VERSION_MAJOR@.@CLIENT_VERSION_MINOR@.@CLIENT_VERSION_REVISION@, Copyright © 2009-@COPYRIGHT_YEAR@ @COPYRIGHT_HOLDERS_FINAL@</string> + <string>@CLIENT_VERSION_MAJOR@.@CLIENT_VERSION_MINOR@.@CLIENT_VERSION_REVISION@.@CLIENT_VERSION_BUILD@, Copyright © 2009-@COPYRIGHT_YEAR@ @COPYRIGHT_HOLDERS_FINAL@</string> <key>CFBundleShortVersionString</key> <string>@CLIENT_VERSION_MAJOR@.@CLIENT_VERSION_MINOR@.@CLIENT_VERSION_REVISION@</string> @@ -97,7 +97,7 @@ <key>NSHighResolutionCapable</key> <string>True</string> - <key>LSAppNapIsDisabled</key> + <key>NSRequiresAquaSystemAppearance</key> <string>True</string> <key>LSApplicationCategoryType</key> diff --git a/share/rpcauth/README.md b/share/rpcauth/README.md index 20d16f0a97..6f627b867b 100644 --- a/share/rpcauth/README.md +++ b/share/rpcauth/README.md @@ -3,12 +3,16 @@ RPC Tools ### [RPCAuth](/share/rpcauth) ### -Create login credentials for a JSON-RPC user. +``` +usage: rpcauth.py [-h] username [password] -Usage: +Create login credentials for a JSON-RPC user - ./rpcauth.py <username> +positional arguments: + username the username for authentication + password leave empty to generate a random password or specify "-" to + prompt for password -in which case the script will generate a password. To specify a custom password do: - - ./rpcauth.py <username> <password> +optional arguments: + -h, --help show this help message and exit + ``` diff --git a/share/rpcauth/rpcauth.py b/share/rpcauth/rpcauth.py index 13bef3d37a..b14c80171e 100755 --- a/share/rpcauth/rpcauth.py +++ b/share/rpcauth/rpcauth.py @@ -3,45 +3,44 @@ # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. -import sys -import os -from random import SystemRandom -import base64 -import hmac +from argparse import ArgumentParser +from base64 import urlsafe_b64encode +from binascii import hexlify +from getpass import getpass +from os import urandom -def generate_salt(): - # This uses os.urandom() underneath - cryptogen = SystemRandom() +import hmac - # Create 16 byte hex salt - salt_sequence = [cryptogen.randrange(256) for _ in range(16)] - return ''.join([format(r, 'x') for r in salt_sequence]) +def generate_salt(size): + """Create size byte hex salt""" + return hexlify(urandom(size)).decode() def generate_password(): """Create 32 byte b64 password""" - return base64.urlsafe_b64encode(os.urandom(32)).decode('utf-8') + return urlsafe_b64encode(urandom(32)).decode('utf-8') def password_to_hmac(salt, password): m = hmac.new(bytearray(salt, 'utf-8'), bytearray(password, 'utf-8'), 'SHA256') return m.hexdigest() def main(): - if len(sys.argv) < 2: - sys.stderr.write('Please include username (and an optional password, will generate one if not provided) as an argument.\n') - sys.exit(0) + parser = ArgumentParser(description='Create login credentials for a JSON-RPC user') + parser.add_argument('username', help='the username for authentication') + parser.add_argument('password', help='leave empty to generate a random password or specify "-" to prompt for password', nargs='?') + args = parser.parse_args() - username = sys.argv[1] + if not args.password: + args.password = generate_password() + elif args.password == '-': + args.password = getpass() - salt = generate_salt() - if len(sys.argv) > 2: - password = sys.argv[2] - else: - password = generate_password() - password_hmac = password_to_hmac(salt, password) + # Create 16 byte hex salt + salt = generate_salt(16) + password_hmac = password_to_hmac(salt, args.password) print('String to be appended to bitcoin.conf:') - print('rpcauth={0}:{1}${2}'.format(username, salt, password_hmac)) - print('Your password:\n{0}'.format(password)) + print('rpcauth={0}:{1}${2}'.format(args.username, salt, password_hmac)) + print('Your password:\n{0}'.format(args.password)) if __name__ == '__main__': main() diff --git a/share/setup.nsi.in b/share/setup.nsi.in index b58a84e02d..6542370f97 100644 --- a/share/setup.nsi.in +++ b/share/setup.nsi.in @@ -80,6 +80,7 @@ Section -Main SEC0000 SetOutPath $INSTDIR\daemon File @abs_top_srcdir@/release/@BITCOIN_DAEMON_NAME@@EXEEXT@ File @abs_top_srcdir@/release/@BITCOIN_CLI_NAME@@EXEEXT@ + File @abs_top_srcdir@/release/@BITCOIN_TX_NAME@@EXEEXT@ SetOutPath $INSTDIR\doc File /r /x Makefile* @abs_top_srcdir@/doc\*.* SetOutPath $INSTDIR diff --git a/src/Makefile.am b/src/Makefile.am index 7a2e9fa5e8..4b07f06c95 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -95,6 +95,8 @@ endif BITCOIN_CORE_H = \ addrdb.h \ addrman.h \ + attributes.h \ + banman.h \ base58.h \ bech32.h \ bloom.h \ @@ -125,6 +127,7 @@ BITCOIN_CORE_H = \ index/txindex.h \ indirectmap.h \ init.h \ + interfaces/chain.h \ interfaces/handler.h \ interfaces/node.h \ interfaces/wallet.h \ @@ -183,10 +186,11 @@ BITCOIN_CORE_H = \ txmempool.h \ ui_interface.h \ undo.h \ - util.h \ - utilmemory.h \ - utilmoneystr.h \ - utiltime.h \ + util/bytevectorhash.h \ + util/system.h \ + util/memory.h \ + util/moneystr.h \ + util/time.h \ validation.h \ validationinterface.h \ versionbits.h \ @@ -222,6 +226,7 @@ libbitcoin_server_a_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) libbitcoin_server_a_SOURCES = \ addrdb.cpp \ addrman.cpp \ + banman.cpp \ bloom.cpp \ blockencodings.cpp \ blockfilter.cpp \ @@ -232,6 +237,7 @@ libbitcoin_server_a_SOURCES = \ httpserver.cpp \ index/base.cpp \ index/txindex.cpp \ + interfaces/chain.cpp \ interfaces/handler.cpp \ interfaces/node.cpp \ init.cpp \ @@ -321,7 +327,9 @@ crypto_libbitcoin_crypto_base_a_SOURCES = \ crypto/sha256.cpp \ crypto/sha256.h \ crypto/sha512.cpp \ - crypto/sha512.h + crypto/sha512.h \ + crypto/siphash.cpp \ + crypto/siphash.h if USE_ASM crypto_libbitcoin_crypto_base_a_SOURCES += crypto/sha256_sse4.cpp @@ -377,8 +385,8 @@ libbitcoin_consensus_a_SOURCES = \ tinyformat.h \ uint256.cpp \ uint256.h \ - utilstrencodings.cpp \ - utilstrencodings.h \ + util/strencodings.cpp \ + util/strencodings.h \ version.h # common: shared between bitcoind, and bitcoin-qt and non-server tools @@ -427,10 +435,11 @@ libbitcoin_util_a_SOURCES = \ support/cleanse.cpp \ sync.cpp \ threadinterrupt.cpp \ - util.cpp \ - utilmoneystr.cpp \ - utilstrencodings.cpp \ - utiltime.cpp \ + util/bytevectorhash.cpp \ + util/system.cpp \ + util/moneystr.cpp \ + util/strencodings.cpp \ + util/time.cpp \ $(BITCOIN_CORE_H) if GLIBC_BACK_COMPAT @@ -461,6 +470,7 @@ endif bitcoind_LDADD = \ $(LIBBITCOIN_SERVER) \ $(LIBBITCOIN_WALLET) \ + $(LIBBITCOIN_SERVER) \ $(LIBBITCOIN_COMMON) \ $(LIBUNIVALUE) \ $(LIBBITCOIN_UTIL) \ @@ -587,9 +597,11 @@ if HARDEN $(AM_V_at) READELF=$(READELF) OBJDUMP=$(OBJDUMP) $(top_srcdir)/contrib/devtools/security-check.py < $(bin_PROGRAMS) endif +if ENABLE_BIP70 %.pb.cc %.pb.h: %.proto @test -f $(PROTOC) $(AM_V_GEN) $(PROTOC) --cpp_out=$(@D) --proto_path=$(<D) $< +endif if EMBEDDED_LEVELDB include Makefile.leveldb.include diff --git a/src/Makefile.bench.include b/src/Makefile.bench.include index 544092520f..5e787ca222 100644 --- a/src/Makefile.bench.include +++ b/src/Makefile.bench.include @@ -18,6 +18,7 @@ bench_bench_bitcoin_SOURCES = \ bench/block_assemble.cpp \ bench/checkblock.cpp \ bench/checkqueue.cpp \ + bench/duplicate_inputs.cpp \ bench/examples.cpp \ bench/rollingbloom.cpp \ bench/crypto_hash.cpp \ diff --git a/src/Makefile.qt.include b/src/Makefile.qt.include index c7a1963135..ba6523d7c2 100644 --- a/src/Makefile.qt.include +++ b/src/Makefile.qt.include @@ -120,9 +120,9 @@ QT_MOC_CPP = \ qt/moc_bantablemodel.cpp \ qt/moc_bitcoinaddressvalidator.cpp \ qt/moc_bitcoinamountfield.cpp \ + qt/moc_bitcoin.cpp \ qt/moc_bitcoingui.cpp \ qt/moc_bitcoinunits.cpp \ - qt/moc_callback.cpp \ qt/moc_clientmodel.cpp \ qt/moc_coincontroldialog.cpp \ qt/moc_coincontroltreewidget.cpp \ @@ -157,18 +157,18 @@ QT_MOC_CPP = \ qt/moc_transactiontablemodel.cpp \ qt/moc_transactionview.cpp \ qt/moc_utilitydialog.cpp \ + qt/moc_walletcontroller.cpp \ qt/moc_walletframe.cpp \ qt/moc_walletmodel.cpp \ qt/moc_walletview.cpp BITCOIN_MM = \ qt/macdockiconhandler.mm \ - qt/macnotificationhandler.mm + qt/macnotificationhandler.mm \ + qt/macos_appnap.mm QT_MOC = \ - qt/bitcoin.moc \ qt/bitcoinamountfield.moc \ - qt/callback.moc \ qt/intro.moc \ qt/overviewpage.moc \ qt/rpcconsole.moc @@ -178,9 +178,15 @@ QT_QRC = qt/bitcoin.qrc QT_QRC_LOCALE_CPP = qt/qrc_bitcoin_locale.cpp QT_QRC_LOCALE = qt/bitcoin_locale.qrc +if ENABLE_BIP70 PROTOBUF_CC = qt/paymentrequest.pb.cc PROTOBUF_H = qt/paymentrequest.pb.h PROTOBUF_PROTO = qt/paymentrequest.proto +else +PROTOBUF_CC = +PROTOBUF_H = +PROTOBUF_PROTO = +endif BITCOIN_QT_H = \ qt/addressbookpage.h \ @@ -189,9 +195,9 @@ BITCOIN_QT_H = \ qt/bantablemodel.h \ qt/bitcoinaddressvalidator.h \ qt/bitcoinamountfield.h \ + qt/bitcoin.h \ qt/bitcoingui.h \ qt/bitcoinunits.h \ - qt/callback.h \ qt/clientmodel.h \ qt/coincontroldialog.h \ qt/coincontroltreewidget.h \ @@ -202,6 +208,7 @@ BITCOIN_QT_H = \ qt/intro.h \ qt/macdockiconhandler.h \ qt/macnotificationhandler.h \ + qt/macos_appnap.h \ qt/modaloverlay.h \ qt/networkstyle.h \ qt/notificator.h \ @@ -231,6 +238,7 @@ BITCOIN_QT_H = \ qt/transactiontablemodel.h \ qt/transactionview.h \ qt/utilitydialog.h \ + qt/walletcontroller.h \ qt/walletframe.h \ qt/walletmodel.h \ qt/walletmodeltransaction.h \ @@ -297,6 +305,7 @@ RES_ICONS = \ BITCOIN_QT_BASE_CPP = \ qt/bantablemodel.cpp \ + qt/bitcoin.cpp \ qt/bitcoinaddressvalidator.cpp \ qt/bitcoinamountfield.cpp \ qt/bitcoingui.cpp \ @@ -330,7 +339,6 @@ BITCOIN_QT_WALLET_CPP = \ qt/editaddressdialog.cpp \ qt/openuridialog.cpp \ qt/overviewpage.cpp \ - qt/paymentrequestplus.cpp \ qt/paymentserver.cpp \ qt/receivecoinsdialog.cpp \ qt/receiverequestdialog.cpp \ @@ -344,18 +352,25 @@ BITCOIN_QT_WALLET_CPP = \ qt/transactionrecord.cpp \ qt/transactiontablemodel.cpp \ qt/transactionview.cpp \ + qt/walletcontroller.cpp \ qt/walletframe.cpp \ qt/walletmodel.cpp \ qt/walletmodeltransaction.cpp \ qt/walletview.cpp +BITCOIN_QT_WALLET_BIP70_CPP = \ + qt/paymentrequestplus.cpp + BITCOIN_QT_CPP = $(BITCOIN_QT_BASE_CPP) if TARGET_WINDOWS BITCOIN_QT_CPP += $(BITCOIN_QT_WINDOWS_CPP) endif if ENABLE_WALLET BITCOIN_QT_CPP += $(BITCOIN_QT_WALLET_CPP) -endif +if ENABLE_BIP70 +BITCOIN_QT_CPP += $(BITCOIN_QT_WALLET_BIP70_CPP) +endif # ENABLE_BIP70 +endif # ENABLE_WALLET RES_IMAGES = @@ -372,6 +387,9 @@ qt_libbitcoinqt_a_OBJCXXFLAGS = $(AM_OBJCXXFLAGS) $(QT_PIE_FLAGS) qt_libbitcoinqt_a_SOURCES = $(BITCOIN_QT_CPP) $(BITCOIN_QT_H) $(QT_FORMS_UI) \ $(QT_QRC) $(QT_QRC_LOCALE) $(QT_TS) $(PROTOBUF_PROTO) $(RES_ICONS) $(RES_IMAGES) $(RES_MOVIES) +if TARGET_DARWIN + qt_libbitcoinqt_a_SOURCES += $(BITCOIN_MM) +endif nodist_qt_libbitcoinqt_a_SOURCES = $(QT_MOC_CPP) $(QT_MOC) $(PROTOBUF_CC) \ $(PROTOBUF_H) $(QT_QRC_CPP) $(QT_QRC_LOCALE_CPP) @@ -394,10 +412,7 @@ qt_bitcoin_qt_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) $(BITCOIN_QT_INCLUDE $(QT_INCLUDES) $(PROTOBUF_CFLAGS) $(QR_CFLAGS) qt_bitcoin_qt_CXXFLAGS = $(AM_CXXFLAGS) $(QT_PIE_FLAGS) -qt_bitcoin_qt_SOURCES = qt/bitcoin.cpp -if TARGET_DARWIN - qt_bitcoin_qt_SOURCES += $(BITCOIN_MM) -endif +qt_bitcoin_qt_SOURCES = qt/main.cpp if TARGET_WINDOWS qt_bitcoin_qt_SOURCES += $(BITCOIN_RC) endif @@ -409,8 +424,16 @@ if ENABLE_ZMQ qt_bitcoin_qt_LDADD += $(LIBBITCOIN_ZMQ) $(ZMQ_LIBS) endif qt_bitcoin_qt_LDADD += $(LIBBITCOIN_CLI) $(LIBBITCOIN_COMMON) $(LIBBITCOIN_UTIL) $(LIBBITCOIN_CONSENSUS) $(LIBBITCOIN_CRYPTO) $(LIBUNIVALUE) $(LIBLEVELDB) $(LIBLEVELDB_SSE42) $(LIBMEMENV) \ - $(BOOST_LIBS) $(QT_LIBS) $(QT_DBUS_LIBS) $(QR_LIBS) $(PROTOBUF_LIBS) $(BDB_LIBS) $(SSL_LIBS) $(CRYPTO_LIBS) $(MINIUPNPC_LIBS) $(LIBSECP256K1) \ + $(BOOST_LIBS) $(QT_LIBS) $(QT_DBUS_LIBS) $(QR_LIBS) $(PROTOBUF_LIBS) $(BDB_LIBS) $(MINIUPNPC_LIBS) $(LIBSECP256K1) \ $(EVENT_PTHREADS_LIBS) $(EVENT_LIBS) +if ENABLE_BIP70 +qt_bitcoin_qt_LDADD += $(SSL_LIBS) +else +if TARGET_WINDOWS +qt_bitcoin_qt_LDADD += $(SSL_LIBS) +endif +endif +qt_bitcoin_qt_LDADD += $(CRYPTO_LIBS) qt_bitcoin_qt_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(QT_LDFLAGS) $(LIBTOOL_APP_LDFLAGS) qt_bitcoin_qt_LIBTOOLFLAGS = $(AM_LIBTOOLFLAGS) --tag CXX diff --git a/src/Makefile.qttest.include b/src/Makefile.qttest.include index 4b14212b2e..61977b50cd 100644 --- a/src/Makefile.qttest.include +++ b/src/Makefile.qttest.include @@ -6,6 +6,7 @@ bin_PROGRAMS += qt/test/test_bitcoin-qt TESTS += qt/test/test_bitcoin-qt TEST_QT_MOC_CPP = \ + qt/test/moc_apptests.cpp \ qt/test/moc_compattests.cpp \ qt/test/moc_rpcnestedtests.cpp \ qt/test/moc_uritests.cpp @@ -13,12 +14,16 @@ TEST_QT_MOC_CPP = \ if ENABLE_WALLET TEST_QT_MOC_CPP += \ qt/test/moc_addressbooktests.cpp \ - qt/test/moc_paymentservertests.cpp \ qt/test/moc_wallettests.cpp -endif +if ENABLE_BIP70 +TEST_QT_MOC_CPP += \ + qt/test/moc_paymentservertests.cpp +endif # ENABLE_BIP70 +endif # ENABLE_WALLET TEST_QT_H = \ qt/test/addressbooktests.h \ + qt/test/apptests.h \ qt/test/compattests.h \ qt/test/rpcnestedtests.h \ qt/test/uritests.h \ @@ -37,6 +42,7 @@ qt_test_test_bitcoin_qt_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) $(BITCOIN_ $(QT_INCLUDES) $(QT_TEST_INCLUDES) $(PROTOBUF_CFLAGS) qt_test_test_bitcoin_qt_SOURCES = \ + qt/test/apptests.cpp \ qt/test/compattests.cpp \ qt/test/rpcnestedtests.cpp \ qt/test/test_main.cpp \ @@ -48,10 +54,13 @@ qt_test_test_bitcoin_qt_SOURCES = \ if ENABLE_WALLET qt_test_test_bitcoin_qt_SOURCES += \ qt/test/addressbooktests.cpp \ - qt/test/paymentservertests.cpp \ qt/test/wallettests.cpp \ wallet/test/wallet_test_fixture.cpp -endif +if ENABLE_BIP70 +qt_test_test_bitcoin_qt_SOURCES += \ + qt/test/paymentservertests.cpp +endif # ENABLE_BIP70 +endif # ENABLE_WALLET nodist_qt_test_test_bitcoin_qt_SOURCES = $(TEST_QT_MOC_CPP) diff --git a/src/Makefile.test.include b/src/Makefile.test.include index 8a537ed4f6..8ce7562434 100644 --- a/src/Makefile.test.include +++ b/src/Makefile.test.include @@ -51,6 +51,7 @@ BITCOIN_TESTS =\ test/cuckoocache_tests.cpp \ test/denialofservice_tests.cpp \ test/descriptor_tests.cpp \ + test/fs_tests.cpp \ test/getarg_tests.cpp \ test/hash_tests.cpp \ test/key_io_tests.cpp \ @@ -110,11 +111,14 @@ BITCOIN_TESTS += \ wallet/test/psbt_wallet_tests.cpp \ wallet/test/wallet_tests.cpp \ wallet/test/wallet_crypto_tests.cpp \ - wallet/test/coinselector_tests.cpp + wallet/test/coinselector_tests.cpp \ + wallet/test/init_tests.cpp BITCOIN_TEST_SUITE += \ wallet/test/wallet_test_fixture.cpp \ - wallet/test/wallet_test_fixture.h + wallet/test/wallet_test_fixture.h \ + wallet/test/init_test_fixture.cpp \ + wallet/test/init_test_fixture.h endif test_test_bitcoin_SOURCES = $(BITCOIN_TEST_SUITE) $(BITCOIN_TESTS) $(JSON_TEST_FILES) $(RAW_TEST_FILES) @@ -128,7 +132,7 @@ test_test_bitcoin_LDADD += $(LIBBITCOIN_SERVER) $(LIBBITCOIN_CLI) $(LIBBITCOIN_C $(LIBLEVELDB) $(LIBLEVELDB_SSE42) $(LIBMEMENV) $(BOOST_LIBS) $(BOOST_UNIT_TEST_FRAMEWORK_LIB) $(LIBSECP256K1) $(EVENT_LIBS) $(EVENT_PTHREADS_LIBS) test_test_bitcoin_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) -test_test_bitcoin_LDADD += $(LIBBITCOIN_CONSENSUS) $(BDB_LIBS) $(CRYPTO_LIBS) $(MINIUPNPC_LIBS) $(RAPIDCHECK_LIBS) +test_test_bitcoin_LDADD += $(BDB_LIBS) $(CRYPTO_LIBS) $(MINIUPNPC_LIBS) $(RAPIDCHECK_LIBS) test_test_bitcoin_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS) -static if ENABLE_ZMQ @@ -161,11 +165,19 @@ nodist_test_test_bitcoin_SOURCES = $(GENERATED_TEST_FILES) $(BITCOIN_TESTS): $(GENERATED_TEST_FILES) -CLEAN_BITCOIN_TEST = test/*.gcda test/*.gcno $(GENERATED_TEST_FILES) +CLEAN_BITCOIN_TEST = test/*.gcda test/*.gcno $(GENERATED_TEST_FILES) $(BITCOIN_TESTS:=.log) CLEANFILES += $(CLEAN_BITCOIN_TEST) +if TARGET_WINDOWS bitcoin_test: $(TEST_BINARY) +else +if ENABLE_BENCH +bitcoin_test: $(TEST_BINARY) $(BENCH_BINARY) +else +bitcoin_test: $(TEST_BINARY) +endif +endif bitcoin_test_check: $(TEST_BINARY) FORCE $(MAKE) check-TESTS TESTS=$^ @@ -180,6 +192,13 @@ if BUILD_BITCOIN_TX endif @echo "Running test/util/rpcauth-test.py..." $(PYTHON) $(top_builddir)/test/util/rpcauth-test.py +if TARGET_WINDOWS +else +if ENABLE_BENCH + @echo "Running bench/bench_bitcoin -evals=1 -scaling=0..." + $(BENCH_BINARY) -evals=1 -scaling=0 > /dev/null +endif +endif $(AM_V_at)$(MAKE) $(AM_MAKEFLAGS) -C secp256k1 check if EMBEDDED_UNIVALUE $(AM_V_at)$(MAKE) $(AM_MAKEFLAGS) -C univalue check diff --git a/src/addrdb.cpp b/src/addrdb.cpp index 3eae2b5127..c6083f5554 100644 --- a/src/addrdb.cpp +++ b/src/addrdb.cpp @@ -12,7 +12,7 @@ #include <random.h> #include <streams.h> #include <tinyformat.h> -#include <util.h> +#include <util/system.h> namespace { @@ -105,19 +105,18 @@ bool DeserializeFileDB(const fs::path& path, Data& data) } -CBanDB::CBanDB() +CBanDB::CBanDB(fs::path ban_list_path) : m_ban_list_path(std::move(ban_list_path)) { - pathBanlist = GetDataDir() / "banlist.dat"; } bool CBanDB::Write(const banmap_t& banSet) { - return SerializeFileDB("banlist", pathBanlist, banSet); + return SerializeFileDB("banlist", m_ban_list_path, banSet); } bool CBanDB::Read(banmap_t& banSet) { - return DeserializeFileDB(pathBanlist, banSet); + return DeserializeFileDB(m_ban_list_path, banSet); } CAddrDB::CAddrDB() diff --git a/src/addrdb.h b/src/addrdb.h index 90eca44bdb..290b63dd12 100644 --- a/src/addrdb.h +++ b/src/addrdb.h @@ -43,6 +43,11 @@ public: nCreateTime = nCreateTimeIn; } + explicit CBanEntry(int64_t n_create_time_in, BanReason ban_reason_in) : CBanEntry(n_create_time_in) + { + banReason = ban_reason_in; + } + ADD_SERIALIZE_METHODS; template <typename Stream, typename Operation> @@ -92,9 +97,9 @@ public: class CBanDB { private: - fs::path pathBanlist; + const fs::path m_ban_list_path; public: - CBanDB(); + explicit CBanDB(fs::path ban_list_path); bool Write(const banmap_t& banSet); bool Read(banmap_t& banSet); }; diff --git a/src/addrman.cpp b/src/addrman.cpp index 093b263ab3..06c342ba73 100644 --- a/src/addrman.cpp +++ b/src/addrman.cpp @@ -11,22 +11,22 @@ int CAddrInfo::GetTriedBucket(const uint256& nKey) const { - uint64_t hash1 = (CHashWriter(SER_GETHASH, 0) << nKey << GetKey()).GetHash().GetCheapHash(); - uint64_t hash2 = (CHashWriter(SER_GETHASH, 0) << nKey << GetGroup() << (hash1 % ADDRMAN_TRIED_BUCKETS_PER_GROUP)).GetHash().GetCheapHash(); + uint64_t hash1 = (CHashWriter(SER_GETHASH, 0) << nKey << GetKey()).GetCheapHash(); + uint64_t hash2 = (CHashWriter(SER_GETHASH, 0) << nKey << GetGroup() << (hash1 % ADDRMAN_TRIED_BUCKETS_PER_GROUP)).GetCheapHash(); return hash2 % ADDRMAN_TRIED_BUCKET_COUNT; } int CAddrInfo::GetNewBucket(const uint256& nKey, const CNetAddr& src) const { std::vector<unsigned char> vchSourceGroupKey = src.GetGroup(); - uint64_t hash1 = (CHashWriter(SER_GETHASH, 0) << nKey << GetGroup() << vchSourceGroupKey).GetHash().GetCheapHash(); - uint64_t hash2 = (CHashWriter(SER_GETHASH, 0) << nKey << vchSourceGroupKey << (hash1 % ADDRMAN_NEW_BUCKETS_PER_SOURCE_GROUP)).GetHash().GetCheapHash(); + uint64_t hash1 = (CHashWriter(SER_GETHASH, 0) << nKey << GetGroup() << vchSourceGroupKey).GetCheapHash(); + uint64_t hash2 = (CHashWriter(SER_GETHASH, 0) << nKey << vchSourceGroupKey << (hash1 % ADDRMAN_NEW_BUCKETS_PER_SOURCE_GROUP)).GetCheapHash(); return hash2 % ADDRMAN_NEW_BUCKET_COUNT; } int CAddrInfo::GetBucketPosition(const uint256 &nKey, bool fNew, int nBucket) const { - uint64_t hash1 = (CHashWriter(SER_GETHASH, 0) << nKey << (fNew ? 'N' : 'K') << nBucket << GetKey()).GetHash().GetCheapHash(); + uint64_t hash1 = (CHashWriter(SER_GETHASH, 0) << nKey << (fNew ? 'N' : 'K') << nBucket << GetKey()).GetCheapHash(); return hash1 % ADDRMAN_BUCKET_SIZE; } @@ -217,7 +217,7 @@ void CAddrMan::Good_(const CService& addr, bool test_before_evict, int64_t nTime return; // find a bucket it is in now - int nRnd = RandomInt(ADDRMAN_NEW_BUCKET_COUNT); + int nRnd = insecure_rand.randrange(ADDRMAN_NEW_BUCKET_COUNT); int nUBucket = -1; for (unsigned int n = 0; n < ADDRMAN_NEW_BUCKET_COUNT; n++) { int nB = (n + nRnd) % ADDRMAN_NEW_BUCKET_COUNT; @@ -291,7 +291,7 @@ bool CAddrMan::Add_(const CAddress& addr, const CNetAddr& source, int64_t nTimeP int nFactor = 1; for (int n = 0; n < pinfo->nRefCount; n++) nFactor *= 2; - if (nFactor > 1 && (RandomInt(nFactor) != 0)) + if (nFactor > 1 && (insecure_rand.randrange(nFactor) != 0)) return false; } else { pinfo = Create(addr, source, &nId); @@ -356,12 +356,12 @@ CAddrInfo CAddrMan::Select_(bool newOnly) // Use a 50% chance for choosing between tried and new table entries. if (!newOnly && - (nTried > 0 && (nNew == 0 || RandomInt(2) == 0))) { + (nTried > 0 && (nNew == 0 || insecure_rand.randbool() == 0))) { // use a tried node double fChanceFactor = 1.0; while (1) { - int nKBucket = RandomInt(ADDRMAN_TRIED_BUCKET_COUNT); - int nKBucketPos = RandomInt(ADDRMAN_BUCKET_SIZE); + int nKBucket = insecure_rand.randrange(ADDRMAN_TRIED_BUCKET_COUNT); + int nKBucketPos = insecure_rand.randrange(ADDRMAN_BUCKET_SIZE); while (vvTried[nKBucket][nKBucketPos] == -1) { nKBucket = (nKBucket + insecure_rand.randbits(ADDRMAN_TRIED_BUCKET_COUNT_LOG2)) % ADDRMAN_TRIED_BUCKET_COUNT; nKBucketPos = (nKBucketPos + insecure_rand.randbits(ADDRMAN_BUCKET_SIZE_LOG2)) % ADDRMAN_BUCKET_SIZE; @@ -369,7 +369,7 @@ CAddrInfo CAddrMan::Select_(bool newOnly) int nId = vvTried[nKBucket][nKBucketPos]; assert(mapInfo.count(nId) == 1); CAddrInfo& info = mapInfo[nId]; - if (RandomInt(1 << 30) < fChanceFactor * info.GetChance() * (1 << 30)) + if (insecure_rand.randbits(30) < fChanceFactor * info.GetChance() * (1 << 30)) return info; fChanceFactor *= 1.2; } @@ -377,8 +377,8 @@ CAddrInfo CAddrMan::Select_(bool newOnly) // use a new node double fChanceFactor = 1.0; while (1) { - int nUBucket = RandomInt(ADDRMAN_NEW_BUCKET_COUNT); - int nUBucketPos = RandomInt(ADDRMAN_BUCKET_SIZE); + int nUBucket = insecure_rand.randrange(ADDRMAN_NEW_BUCKET_COUNT); + int nUBucketPos = insecure_rand.randrange(ADDRMAN_BUCKET_SIZE); while (vvNew[nUBucket][nUBucketPos] == -1) { nUBucket = (nUBucket + insecure_rand.randbits(ADDRMAN_NEW_BUCKET_COUNT_LOG2)) % ADDRMAN_NEW_BUCKET_COUNT; nUBucketPos = (nUBucketPos + insecure_rand.randbits(ADDRMAN_BUCKET_SIZE_LOG2)) % ADDRMAN_BUCKET_SIZE; @@ -386,7 +386,7 @@ CAddrInfo CAddrMan::Select_(bool newOnly) int nId = vvNew[nUBucket][nUBucketPos]; assert(mapInfo.count(nId) == 1); CAddrInfo& info = mapInfo[nId]; - if (RandomInt(1 << 30) < fChanceFactor * info.GetChance() * (1 << 30)) + if (insecure_rand.randbits(30) < fChanceFactor * info.GetChance() * (1 << 30)) return info; fChanceFactor *= 1.2; } @@ -482,7 +482,7 @@ void CAddrMan::GetAddr_(std::vector<CAddress>& vAddr) if (vAddr.size() >= nNodes) break; - int nRndPos = RandomInt(vRandom.size() - n) + n; + int nRndPos = insecure_rand.randrange(vRandom.size() - n) + n; SwapRandom(n, nRndPos); assert(mapInfo.count(vRandom[n]) == 1); @@ -530,10 +530,6 @@ void CAddrMan::SetServices_(const CService& addr, ServiceFlags nServices) info.nServices = nServices; } -int CAddrMan::RandomInt(int nMax){ - return GetRandInt(nMax); -} - void CAddrMan::ResolveCollisions_() { for (std::set<int>::iterator it = m_tried_collisions.begin(); it != m_tried_collisions.end();) { @@ -593,7 +589,7 @@ CAddrInfo CAddrMan::SelectTriedCollision_() std::set<int>::iterator it = m_tried_collisions.begin(); // Selects a random element from m_tried_collisions - std::advance(it, GetRandInt(m_tried_collisions.size())); + std::advance(it, insecure_rand.randrange(m_tried_collisions.size())); int id_new = *it; // If id_new not found in mapInfo remove it from m_tried_collisions diff --git a/src/addrman.h b/src/addrman.h index cf1949c28c..003bd059f8 100644 --- a/src/addrman.h +++ b/src/addrman.h @@ -11,7 +11,7 @@ #include <random.h> #include <sync.h> #include <timedata.h> -#include <util.h> +#include <util/system.h> #include <map> #include <set> @@ -23,33 +23,31 @@ */ class CAddrInfo : public CAddress { - - public: //! last try whatsoever by us (memory only) - int64_t nLastTry; + int64_t nLastTry{0}; //! last counted attempt (memory only) - int64_t nLastCountAttempt; + int64_t nLastCountAttempt{0}; private: //! where knowledge about this address first came from CNetAddr source; //! last successful connection by us - int64_t nLastSuccess; + int64_t nLastSuccess{0}; //! connection attempts since last successful attempt - int nAttempts; + int nAttempts{0}; //! reference count in new sets (memory only) - int nRefCount; + int nRefCount{0}; //! in tried set? (memory only) - bool fInTried; + bool fInTried{false}; //! position in vRandom - int nRandomPos; + int nRandomPos{-1}; friend class CAddrMan; @@ -65,25 +63,12 @@ public: READWRITE(nAttempts); } - void Init() - { - nLastSuccess = 0; - nLastTry = 0; - nLastCountAttempt = 0; - nAttempts = 0; - nRefCount = 0; - fInTried = false; - nRandomPos = -1; - } - CAddrInfo(const CAddress &addrIn, const CNetAddr &addrSource) : CAddress(addrIn), source(addrSource) { - Init(); } CAddrInfo() : CAddress(), source() { - Init(); } //! Calculate in which "tried" bucket this entry belongs @@ -106,7 +91,6 @@ public: //! Calculate the relative chance this entry should be given when selecting nodes to connect to double GetChance(int64_t nNow = GetAdjustedTime()) const; - }; /** Stochastic address manager @@ -187,36 +171,37 @@ public: */ class CAddrMan { -private: +protected: //! critical section to protect the inner data structures mutable CCriticalSection cs; +private: //! last used nId - int nIdCount; + int nIdCount GUARDED_BY(cs); //! table with information about all nIds - std::map<int, CAddrInfo> mapInfo; + std::map<int, CAddrInfo> mapInfo GUARDED_BY(cs); //! find an nId based on its network address - std::map<CNetAddr, int> mapAddr; + std::map<CNetAddr, int> mapAddr GUARDED_BY(cs); //! randomly-ordered vector of all nIds - std::vector<int> vRandom; + std::vector<int> vRandom GUARDED_BY(cs); // number of "tried" entries - int nTried; + int nTried GUARDED_BY(cs); //! list of "tried" buckets - int vvTried[ADDRMAN_TRIED_BUCKET_COUNT][ADDRMAN_BUCKET_SIZE]; + int vvTried[ADDRMAN_TRIED_BUCKET_COUNT][ADDRMAN_BUCKET_SIZE] GUARDED_BY(cs); //! number of (unique) "new" entries - int nNew; + int nNew GUARDED_BY(cs); //! list of "new" buckets - int vvNew[ADDRMAN_NEW_BUCKET_COUNT][ADDRMAN_BUCKET_SIZE]; + int vvNew[ADDRMAN_NEW_BUCKET_COUNT][ADDRMAN_BUCKET_SIZE] GUARDED_BY(cs); //! last time Good was called (memory only) - int64_t nLastGood; + int64_t nLastGood GUARDED_BY(cs); //! Holds addrs inserted into tried table that collide with existing entries. Test-before-evict discipline used to resolve these collisions. std::set<int> m_tried_collisions; @@ -229,58 +214,55 @@ protected: FastRandomContext insecure_rand; //! Find an entry. - CAddrInfo* Find(const CNetAddr& addr, int *pnId = nullptr); + CAddrInfo* Find(const CNetAddr& addr, int *pnId = nullptr) EXCLUSIVE_LOCKS_REQUIRED(cs); //! find an entry, creating it if necessary. //! nTime and nServices of the found node are updated, if necessary. - CAddrInfo* Create(const CAddress &addr, const CNetAddr &addrSource, int *pnId = nullptr); + CAddrInfo* Create(const CAddress &addr, const CNetAddr &addrSource, int *pnId = nullptr) EXCLUSIVE_LOCKS_REQUIRED(cs); //! Swap two elements in vRandom. - void SwapRandom(unsigned int nRandomPos1, unsigned int nRandomPos2); + void SwapRandom(unsigned int nRandomPos1, unsigned int nRandomPos2) EXCLUSIVE_LOCKS_REQUIRED(cs); //! Move an entry from the "new" table(s) to the "tried" table - void MakeTried(CAddrInfo& info, int nId); + void MakeTried(CAddrInfo& info, int nId) EXCLUSIVE_LOCKS_REQUIRED(cs); //! Delete an entry. It must not be in tried, and have refcount 0. - void Delete(int nId); + void Delete(int nId) EXCLUSIVE_LOCKS_REQUIRED(cs); //! Clear a position in a "new" table. This is the only place where entries are actually deleted. - void ClearNew(int nUBucket, int nUBucketPos); + void ClearNew(int nUBucket, int nUBucketPos) EXCLUSIVE_LOCKS_REQUIRED(cs); //! Mark an entry "good", possibly moving it from "new" to "tried". - void Good_(const CService &addr, bool test_before_evict, int64_t time); + void Good_(const CService &addr, bool test_before_evict, int64_t time) EXCLUSIVE_LOCKS_REQUIRED(cs); //! Add an entry to the "new" table. - bool Add_(const CAddress &addr, const CNetAddr& source, int64_t nTimePenalty); + bool Add_(const CAddress &addr, const CNetAddr& source, int64_t nTimePenalty) EXCLUSIVE_LOCKS_REQUIRED(cs); //! Mark an entry as attempted to connect. - void Attempt_(const CService &addr, bool fCountFailure, int64_t nTime); + void Attempt_(const CService &addr, bool fCountFailure, int64_t nTime) EXCLUSIVE_LOCKS_REQUIRED(cs); //! Select an address to connect to, if newOnly is set to true, only the new table is selected from. - CAddrInfo Select_(bool newOnly); + CAddrInfo Select_(bool newOnly) EXCLUSIVE_LOCKS_REQUIRED(cs); //! See if any to-be-evicted tried table entries have been tested and if so resolve the collisions. - void ResolveCollisions_(); + void ResolveCollisions_() EXCLUSIVE_LOCKS_REQUIRED(cs); //! Return a random to-be-evicted tried table address. - CAddrInfo SelectTriedCollision_(); - - //! Wraps GetRandInt to allow tests to override RandomInt and make it determinismistic. - virtual int RandomInt(int nMax); + CAddrInfo SelectTriedCollision_() EXCLUSIVE_LOCKS_REQUIRED(cs); #ifdef DEBUG_ADDRMAN //! Perform consistency check. Returns an error code or zero. - int Check_(); + int Check_() EXCLUSIVE_LOCKS_REQUIRED(cs); #endif //! Select several addresses at once. - void GetAddr_(std::vector<CAddress> &vAddr); + void GetAddr_(std::vector<CAddress> &vAddr) EXCLUSIVE_LOCKS_REQUIRED(cs); //! Mark an entry as currently-connected-to. - void Connected_(const CService &addr, int64_t nTime); + void Connected_(const CService &addr, int64_t nTime) EXCLUSIVE_LOCKS_REQUIRED(cs); //! Update an entry's service bits. - void SetServices_(const CService &addr, ServiceFlags nServices); + void SetServices_(const CService &addr, ServiceFlags nServices) EXCLUSIVE_LOCKS_REQUIRED(cs); public: /** @@ -472,7 +454,7 @@ public: { LOCK(cs); std::vector<int>().swap(vRandom); - nKey = GetRandHash(); + nKey = insecure_rand.rand256(); for (size_t bucket = 0; bucket < ADDRMAN_NEW_BUCKET_COUNT; bucket++) { for (size_t entry = 0; entry < ADDRMAN_BUCKET_SIZE; entry++) { vvNew[bucket][entry] = -1; diff --git a/src/amount.h b/src/amount.h index 449fd1b15f..47968e80b1 100644 --- a/src/amount.h +++ b/src/amount.h @@ -1,5 +1,5 @@ // Copyright (c) 2009-2010 Satoshi Nakamoto -// Copyright (c) 2009-2016 The Bitcoin Core developers +// 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. diff --git a/src/arith_uint256.cpp b/src/arith_uint256.cpp index 13fa176f8b..aa66d13102 100644 --- a/src/arith_uint256.cpp +++ b/src/arith_uint256.cpp @@ -6,7 +6,7 @@ #include <arith_uint256.h> #include <uint256.h> -#include <utilstrencodings.h> +#include <util/strencodings.h> #include <crypto/common.h> #include <stdio.h> @@ -176,7 +176,7 @@ unsigned int base_uint<BITS>::bits() const for (int pos = WIDTH - 1; pos >= 0; pos--) { if (pn[pos]) { for (int nbits = 31; nbits > 0; nbits--) { - if (pn[pos] & 1 << nbits) + if (pn[pos] & 1U << nbits) return 32 * pos + nbits + 1; } return 32 * pos + 1; diff --git a/src/arith_uint256.h b/src/arith_uint256.h index 5cc273be27..bd0360087d 100644 --- a/src/arith_uint256.h +++ b/src/arith_uint256.h @@ -8,6 +8,7 @@ #include <assert.h> #include <cstring> +#include <limits> #include <stdexcept> #include <stdint.h> #include <string> @@ -189,7 +190,7 @@ public: { // prefix operator int i = 0; - while (i < WIDTH && --pn[i] == (uint32_t)-1) + while (i < WIDTH && --pn[i] == std::numeric_limits<uint32_t>::max()) i++; return *this; } diff --git a/src/attributes.h b/src/attributes.h new file mode 100644 index 0000000000..45099bd8b8 --- /dev/null +++ b/src/attributes.h @@ -0,0 +1,22 @@ +// 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_ATTRIBUTES_H +#define BITCOIN_ATTRIBUTES_H + +#if defined(__has_cpp_attribute) +# if __has_cpp_attribute(nodiscard) +# define NODISCARD [[nodiscard]] +# endif +#endif +#ifndef NODISCARD +# if defined(_MSC_VER) && _MSC_VER >= 1700 +# define NODISCARD _Check_return_ +# else +# define NODISCARD __attribute__((warn_unused_result)) +# endif +#endif + +#endif // BITCOIN_ATTRIBUTES_H diff --git a/src/banman.cpp b/src/banman.cpp new file mode 100644 index 0000000000..9933c829c5 --- /dev/null +++ b/src/banman.cpp @@ -0,0 +1,197 @@ +// Copyright (c) 2009-2010 Satoshi Nakamoto +// Copyright (c) 2009-2017 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 <banman.h> + +#include <netaddress.h> +#include <ui_interface.h> +#include <util/system.h> +#include <util/time.h> + + +BanMan::BanMan(fs::path ban_file, CClientUIInterface* client_interface, int64_t default_ban_time) + : m_client_interface(client_interface), m_ban_db(std::move(ban_file)), m_default_ban_time(default_ban_time) +{ + if (m_client_interface) m_client_interface->InitMessage(_("Loading banlist...")); + + int64_t n_start = GetTimeMillis(); + m_is_dirty = false; + banmap_t banmap; + if (m_ban_db.Read(banmap)) { + SetBanned(banmap); // thread save setter + SetBannedSetDirty(false); // no need to write down, just read data + SweepBanned(); // sweep out unused entries + + LogPrint(BCLog::NET, "Loaded %d banned node ips/subnets from banlist.dat %dms\n", + banmap.size(), GetTimeMillis() - n_start); + } else { + LogPrintf("Invalid or missing banlist.dat; recreating\n"); + SetBannedSetDirty(true); // force write + DumpBanlist(); + } +} + +BanMan::~BanMan() +{ + DumpBanlist(); +} + +void BanMan::DumpBanlist() +{ + SweepBanned(); // clean unused entries (if bantime has expired) + + if (!BannedSetIsDirty()) return; + + int64_t n_start = GetTimeMillis(); + + banmap_t banmap; + GetBanned(banmap); + if (m_ban_db.Write(banmap)) { + SetBannedSetDirty(false); + } + + LogPrint(BCLog::NET, "Flushed %d banned node ips/subnets to banlist.dat %dms\n", + banmap.size(), GetTimeMillis() - n_start); +} + +void BanMan::ClearBanned() +{ + { + LOCK(m_cs_banned); + m_banned.clear(); + m_is_dirty = true; + } + DumpBanlist(); //store banlist to disk + if (m_client_interface) m_client_interface->BannedListChanged(); +} + +bool BanMan::IsBanned(CNetAddr net_addr) +{ + LOCK(m_cs_banned); + for (const auto& it : m_banned) { + CSubNet sub_net = it.first; + CBanEntry ban_entry = it.second; + + if (sub_net.Match(net_addr) && GetTime() < ban_entry.nBanUntil) { + return true; + } + } + return false; +} + +bool BanMan::IsBanned(CSubNet sub_net) +{ + LOCK(m_cs_banned); + banmap_t::iterator i = m_banned.find(sub_net); + if (i != m_banned.end()) { + CBanEntry ban_entry = (*i).second; + if (GetTime() < ban_entry.nBanUntil) { + return true; + } + } + return false; +} + +void BanMan::Ban(const CNetAddr& net_addr, const BanReason& ban_reason, int64_t ban_time_offset, bool since_unix_epoch) +{ + CSubNet sub_net(net_addr); + Ban(sub_net, ban_reason, ban_time_offset, since_unix_epoch); +} + +void BanMan::Ban(const CSubNet& sub_net, const BanReason& ban_reason, int64_t ban_time_offset, bool since_unix_epoch) +{ + CBanEntry ban_entry(GetTime(), ban_reason); + + int64_t normalized_ban_time_offset = ban_time_offset; + bool normalized_since_unix_epoch = since_unix_epoch; + if (ban_time_offset <= 0) { + normalized_ban_time_offset = m_default_ban_time; + normalized_since_unix_epoch = false; + } + ban_entry.nBanUntil = (normalized_since_unix_epoch ? 0 : GetTime()) + normalized_ban_time_offset; + + { + LOCK(m_cs_banned); + if (m_banned[sub_net].nBanUntil < ban_entry.nBanUntil) { + m_banned[sub_net] = ban_entry; + m_is_dirty = true; + } else + return; + } + if (m_client_interface) m_client_interface->BannedListChanged(); + + //store banlist to disk immediately if user requested ban + if (ban_reason == BanReasonManuallyAdded) DumpBanlist(); +} + +bool BanMan::Unban(const CNetAddr& net_addr) +{ + CSubNet sub_net(net_addr); + return Unban(sub_net); +} + +bool BanMan::Unban(const CSubNet& sub_net) +{ + { + LOCK(m_cs_banned); + if (m_banned.erase(sub_net) == 0) return false; + m_is_dirty = true; + } + if (m_client_interface) m_client_interface->BannedListChanged(); + DumpBanlist(); //store banlist to disk immediately + return true; +} + +void BanMan::GetBanned(banmap_t& banmap) +{ + LOCK(m_cs_banned); + // Sweep the banlist so expired bans are not returned + SweepBanned(); + banmap = m_banned; //create a thread safe copy +} + +void BanMan::SetBanned(const banmap_t& banmap) +{ + LOCK(m_cs_banned); + m_banned = banmap; + m_is_dirty = true; +} + +void BanMan::SweepBanned() +{ + int64_t now = GetTime(); + bool notify_ui = false; + { + LOCK(m_cs_banned); + banmap_t::iterator it = m_banned.begin(); + while (it != m_banned.end()) { + CSubNet sub_net = (*it).first; + CBanEntry ban_entry = (*it).second; + if (now > ban_entry.nBanUntil) { + m_banned.erase(it++); + m_is_dirty = true; + notify_ui = true; + LogPrint(BCLog::NET, "%s: Removed banned node ip/subnet from banlist.dat: %s\n", __func__, sub_net.ToString()); + } else + ++it; + } + } + // update UI + if (notify_ui && m_client_interface) { + m_client_interface->BannedListChanged(); + } +} + +bool BanMan::BannedSetIsDirty() +{ + LOCK(m_cs_banned); + return m_is_dirty; +} + +void BanMan::SetBannedSetDirty(bool dirty) +{ + LOCK(m_cs_banned); //reuse m_banned lock for the m_is_dirty flag + m_is_dirty = dirty; +} diff --git a/src/banman.h b/src/banman.h new file mode 100644 index 0000000000..69f62be368 --- /dev/null +++ b/src/banman.h @@ -0,0 +1,69 @@ +// Copyright (c) 2009-2010 Satoshi Nakamoto +// Copyright (c) 2009-2017 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_BANMAN_H +#define BITCOIN_BANMAN_H + +#include <cstdint> +#include <memory> + +#include <addrdb.h> +#include <fs.h> +#include <sync.h> + +// NOTE: When adjusting this, update rpcnet:setban's help ("24h") +static constexpr unsigned int DEFAULT_MISBEHAVING_BANTIME = 60 * 60 * 24; // Default 24-hour ban + +class CClientUIInterface; +class CNetAddr; +class CSubNet; + +// Denial-of-service detection/prevention +// The idea is to detect peers that are behaving +// badly and disconnect/ban them, but do it in a +// one-coding-mistake-won't-shatter-the-entire-network +// way. +// IMPORTANT: There should be nothing I can give a +// node that it will forward on that will make that +// node's peers drop it. If there is, an attacker +// can isolate a node and/or try to split the network. +// Dropping a node for sending stuff that is invalid +// now but might be valid in a later version is also +// dangerous, because it can cause a network split +// between nodes running old code and nodes running +// new code. + +class BanMan +{ +public: + ~BanMan(); + BanMan(fs::path ban_file, CClientUIInterface* client_interface, int64_t default_ban_time); + void Ban(const CNetAddr& net_addr, const BanReason& ban_reason, int64_t ban_time_offset = 0, bool since_unix_epoch = false); + void Ban(const CSubNet& sub_net, const BanReason& ban_reason, int64_t ban_time_offset = 0, bool since_unix_epoch = false); + void ClearBanned(); + bool IsBanned(CNetAddr net_addr); + bool IsBanned(CSubNet sub_net); + bool Unban(const CNetAddr& net_addr); + bool Unban(const CSubNet& sub_net); + void GetBanned(banmap_t& banmap); + void DumpBanlist(); + +private: + void SetBanned(const banmap_t& banmap); + bool BannedSetIsDirty(); + //!set the "dirty" flag for the banlist + void SetBannedSetDirty(bool dirty = true); + //!clean unused entries (if bantime has expired) + void SweepBanned(); + + CCriticalSection m_cs_banned; + banmap_t m_banned GUARDED_BY(m_cs_banned); + bool m_is_dirty GUARDED_BY(m_cs_banned); + CClientUIInterface* m_client_interface = nullptr; + CBanDB m_ban_db; + const int64_t m_default_ban_time; +}; + +extern std::unique_ptr<BanMan> g_banman; +#endif diff --git a/src/base58.cpp b/src/base58.cpp index 7020c24055..e3d2853399 100644 --- a/src/base58.cpp +++ b/src/base58.cpp @@ -6,6 +6,7 @@ #include <hash.h> #include <uint256.h> +#include <util/strencodings.h> #include <assert.h> #include <string.h> @@ -34,7 +35,7 @@ static const int8_t mapBase58[256] = { bool DecodeBase58(const char* psz, std::vector<unsigned char>& vch) { // Skip leading spaces. - while (*psz && isspace(*psz)) + while (*psz && IsSpace(*psz)) psz++; // Skip and count leading '1's. int zeroes = 0; @@ -48,7 +49,7 @@ bool DecodeBase58(const char* psz, std::vector<unsigned char>& vch) std::vector<unsigned char> b256(size); // Process the characters. static_assert(sizeof(mapBase58)/sizeof(mapBase58[0]) == 256, "mapBase58.size() should be 256"); // guarantee not out of range - while (*psz && !isspace(*psz)) { + while (*psz && !IsSpace(*psz)) { // Decode base58 character int carry = mapBase58[(uint8_t)*psz]; if (carry == -1) // Invalid b58 character @@ -64,7 +65,7 @@ bool DecodeBase58(const char* psz, std::vector<unsigned char>& vch) psz++; } // Skip trailing spaces. - while (isspace(*psz)) + while (IsSpace(*psz)) psz++; if (*psz != 0) return false; diff --git a/src/base58.h b/src/base58.h index 9d3f90652e..d6e0299a1e 100644 --- a/src/base58.h +++ b/src/base58.h @@ -14,6 +14,8 @@ #ifndef BITCOIN_BASE58_H #define BITCOIN_BASE58_H +#include <attributes.h> + #include <string> #include <vector> @@ -33,13 +35,13 @@ std::string EncodeBase58(const std::vector<unsigned char>& vch); * return true if decoding is successful. * psz cannot be nullptr. */ -bool DecodeBase58(const char* psz, std::vector<unsigned char>& vchRet); +NODISCARD bool DecodeBase58(const char* psz, std::vector<unsigned char>& vchRet); /** * Decode a base58-encoded string (str) into a byte vector (vchRet). * return true if decoding is successful. */ -bool DecodeBase58(const std::string& str, std::vector<unsigned char>& vchRet); +NODISCARD bool DecodeBase58(const std::string& str, std::vector<unsigned char>& vchRet); /** * Encode a byte vector into a base58-encoded string, including checksum @@ -50,12 +52,12 @@ std::string EncodeBase58Check(const std::vector<unsigned char>& vchIn); * Decode a base58-encoded string (psz) that includes a checksum into a byte * vector (vchRet), return true if decoding is successful */ -bool DecodeBase58Check(const char* psz, std::vector<unsigned char>& vchRet); +NODISCARD bool DecodeBase58Check(const char* psz, std::vector<unsigned char>& vchRet); /** * Decode a base58-encoded string (str) that includes a checksum into a byte * vector (vchRet), return true if decoding is successful */ -bool DecodeBase58Check(const std::string& str, std::vector<unsigned char>& vchRet); +NODISCARD bool DecodeBase58Check(const std::string& str, std::vector<unsigned char>& vchRet); #endif // BITCOIN_BASE58_H diff --git a/src/bench/base58.cpp b/src/bench/base58.cpp index a555376e40..e7702ec461 100644 --- a/src/bench/base58.cpp +++ b/src/bench/base58.cpp @@ -49,7 +49,7 @@ static void Base58Decode(benchmark::State& state) const char* addr = "17VZNX1SN5NtKa8UQFxwQbFeFc3iqRYhem"; std::vector<unsigned char> vch; while (state.KeepRunning()) { - DecodeBase58(addr, vch); + (void) DecodeBase58(addr, vch); } } diff --git a/src/bench/bech32.cpp b/src/bench/bech32.cpp index 8b80e17391..3c4b453a23 100644 --- a/src/bench/bech32.cpp +++ b/src/bench/bech32.cpp @@ -6,7 +6,7 @@ #include <validation.h> #include <bech32.h> -#include <utilstrencodings.h> +#include <util/strencodings.h> #include <vector> #include <string> diff --git a/src/bench/bench_bitcoin.cpp b/src/bench/bench_bitcoin.cpp index 4fa516cb81..b804a84478 100644 --- a/src/bench/bench_bitcoin.cpp +++ b/src/bench/bench_bitcoin.cpp @@ -6,9 +6,8 @@ #include <crypto/sha256.h> #include <key.h> -#include <random.h> -#include <util.h> -#include <utilstrencodings.h> +#include <util/system.h> +#include <util/strencodings.h> #include <validation.h> #include <memory> @@ -67,7 +66,6 @@ int main(int argc, char** argv) const fs::path bench_datadir{SetDataDir()}; SHA256AutoDetect(); - RandomInit(); ECC_Start(); SetupEnvironment(); diff --git a/src/bench/block_assemble.cpp b/src/bench/block_assemble.cpp index bc99b8cdcd..8133fd9d65 100644 --- a/src/bench/block_assemble.cpp +++ b/src/bench/block_assemble.cpp @@ -7,13 +7,14 @@ #include <coins.h> #include <consensus/merkle.h> #include <consensus/validation.h> +#include <crypto/sha256.h> #include <miner.h> #include <policy/policy.h> #include <pow.h> #include <scheduler.h> #include <txdb.h> #include <txmempool.h> -#include <utiltime.h> +#include <util/time.h> #include <validation.h> #include <validationinterface.h> @@ -26,7 +27,7 @@ static std::shared_ptr<CBlock> PrepareBlock(const CScript& coinbase_scriptPubKey { auto block = std::make_shared<CBlock>( BlockAssembler{Params()} - .CreateNewBlock(coinbase_scriptPubKey, /* fMineWitnessTx */ true) + .CreateNewBlock(coinbase_scriptPubKey) ->block); block->nTime = ::chainActive.Tip()->GetMedianTimePast() + 1; @@ -77,7 +78,7 @@ static void AssembleBlock(benchmark::State& state) ::pcoinsTip.reset(new CCoinsViewCache(pcoinsdbview.get())); const CChainParams& chainparams = Params(); - thread_group.create_thread(boost::bind(&CScheduler::serviceQueue, &scheduler)); + thread_group.create_thread(std::bind(&CScheduler::serviceQueue, &scheduler)); GetMainSignals().RegisterBackgroundSignalScheduler(scheduler); LoadGenesisBlock(chainparams); CValidationState state; diff --git a/src/bench/ccoins_caching.cpp b/src/bench/ccoins_caching.cpp index b8d82c0a89..9cfd5d23ef 100644 --- a/src/bench/ccoins_caching.cpp +++ b/src/bench/ccoins_caching.cpp @@ -35,14 +35,14 @@ SetupDummyInputs(CBasicKeyStore& keystoreRet, CCoinsViewCache& coinsRet) dummyTransactions[0].vout[0].scriptPubKey << ToByteVector(key[0].GetPubKey()) << OP_CHECKSIG; dummyTransactions[0].vout[1].nValue = 50 * COIN; dummyTransactions[0].vout[1].scriptPubKey << ToByteVector(key[1].GetPubKey()) << OP_CHECKSIG; - AddCoins(coinsRet, dummyTransactions[0], 0); + AddCoins(coinsRet, CTransaction(dummyTransactions[0]), 0); dummyTransactions[1].vout.resize(2); dummyTransactions[1].vout[0].nValue = 21 * COIN; dummyTransactions[1].vout[0].scriptPubKey = GetScriptForDestination(key[2].GetPubKey().GetID()); dummyTransactions[1].vout[1].nValue = 22 * COIN; dummyTransactions[1].vout[1].scriptPubKey = GetScriptForDestination(key[3].GetPubKey().GetID()); - AddCoins(coinsRet, dummyTransactions[1], 0); + AddCoins(coinsRet, CTransaction(dummyTransactions[1]), 0); return dummyTransactions; } @@ -76,10 +76,11 @@ static void CCoinsCaching(benchmark::State& state) t1.vout[0].scriptPubKey << OP_1; // Benchmark. + const CTransaction tx_1(t1); while (state.KeepRunning()) { - bool success = AreInputsStandard(t1, coins); + bool success = AreInputsStandard(tx_1, coins); assert(success); - CAmount value = coins.GetValueIn(t1); + CAmount value = coins.GetValueIn(tx_1); assert(value == (50 + 21 + 22) * COIN); } } diff --git a/src/bench/checkblock.cpp b/src/bench/checkblock.cpp index 6f03581c4b..e325333c01 100644 --- a/src/bench/checkblock.cpp +++ b/src/bench/checkblock.cpp @@ -20,7 +20,7 @@ namespace block_bench { static void DeserializeBlockTest(benchmark::State& state) { CDataStream stream((const char*)block_bench::block413567, - (const char*)&block_bench::block413567[sizeof(block_bench::block413567)], + (const char*)block_bench::block413567 + sizeof(block_bench::block413567), SER_NETWORK, PROTOCOL_VERSION); char a = '\0'; stream.write(&a, 1); // Prevent compaction @@ -36,7 +36,7 @@ static void DeserializeBlockTest(benchmark::State& state) static void DeserializeAndCheckBlockTest(benchmark::State& state) { CDataStream stream((const char*)block_bench::block413567, - (const char*)&block_bench::block413567[sizeof(block_bench::block413567)], + (const char*)block_bench::block413567 + sizeof(block_bench::block413567), SER_NETWORK, PROTOCOL_VERSION); char a = '\0'; stream.write(&a, 1); // Prevent compaction diff --git a/src/bench/checkqueue.cpp b/src/bench/checkqueue.cpp index 79689f6e0b..6ab542067a 100644 --- a/src/bench/checkqueue.cpp +++ b/src/bench/checkqueue.cpp @@ -3,7 +3,7 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include <bench/bench.h> -#include <util.h> +#include <util/system.h> #include <validation.h> #include <checkqueue.h> #include <prevector.h> diff --git a/src/bench/coin_selection.cpp b/src/bench/coin_selection.cpp index 27c23d6834..74641191a1 100644 --- a/src/bench/coin_selection.cpp +++ b/src/bench/coin_selection.cpp @@ -3,25 +3,20 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include <bench/bench.h> -#include <wallet/wallet.h> +#include <interfaces/chain.h> #include <wallet/coinselection.h> +#include <wallet/wallet.h> #include <set> -static void addCoin(const CAmount& nValue, const CWallet& wallet, std::vector<OutputGroup>& groups) +static void addCoin(const CAmount& nValue, const CWallet& wallet, std::vector<std::unique_ptr<CWalletTx>>& wtxs) { - int nInput = 0; - static int nextLockTime = 0; CMutableTransaction tx; tx.nLockTime = nextLockTime++; // so all transactions get different hashes - tx.vout.resize(nInput + 1); - tx.vout[nInput].nValue = nValue; - CWalletTx* wtx = new CWalletTx(&wallet, MakeTransactionRef(std::move(tx))); - - int nAge = 6 * 24; - COutput output(wtx, nInput, nAge, true /* spendable */, true /* solvable */, true /* safe */); - groups.emplace_back(output.GetInputCoin(), 6, false, 0, 0); + tx.vout.resize(1); + tx.vout[0].nValue = nValue; + wtxs.push_back(MakeUnique<CWalletTx>(&wallet, MakeTransactionRef(std::move(tx)))); } // Simple benchmark for wallet coin selection. Note that it maybe be necessary @@ -33,15 +28,23 @@ static void addCoin(const CAmount& nValue, const CWallet& wallet, std::vector<Ou // (https://github.com/bitcoin/bitcoin/issues/7883#issuecomment-224807484) static void CoinSelection(benchmark::State& state) { - const CWallet wallet("dummy", WalletDatabase::CreateDummy()); + auto chain = interfaces::MakeChain(); + const CWallet wallet(*chain, WalletLocation(), WalletDatabase::CreateDummy()); + std::vector<std::unique_ptr<CWalletTx>> wtxs; LOCK(wallet.cs_wallet); // Add coins. - std::vector<OutputGroup> groups; for (int i = 0; i < 1000; ++i) { - addCoin(1000 * COIN, wallet, groups); + addCoin(1000 * COIN, wallet, wtxs); + } + addCoin(3 * COIN, wallet, wtxs); + + // Create groups + std::vector<OutputGroup> groups; + for (const auto& wtx : wtxs) { + COutput output(wtx.get(), 0 /* iIn */, 6 * 24 /* nDepthIn */, true /* spendable */, true /* solvable */, true /* safe */); + groups.emplace_back(output.GetInputCoin(), 6, false, 0, 0); } - addCoin(3 * COIN, wallet, groups); const CoinEligibilityFilter filter_standard(1, 6, 0); const CoinSelectionParams coin_selection_params(true, 34, 148, CFeeRate(0), 0); @@ -57,7 +60,8 @@ static void CoinSelection(benchmark::State& state) } typedef std::set<CInputCoin> CoinSet; -static const CWallet testWallet("dummy", WalletDatabase::CreateDummy()); +static auto testChain = interfaces::MakeChain(); +static const CWallet testWallet(*testChain, WalletLocation(), WalletDatabase::CreateDummy()); std::vector<std::unique_ptr<CWalletTx>> wtxn; // Copied from src/wallet/test/coinselector_tests.cpp diff --git a/src/bench/crypto_hash.cpp b/src/bench/crypto_hash.cpp index 5b0cf27e04..dc0b054420 100644 --- a/src/bench/crypto_hash.cpp +++ b/src/bench/crypto_hash.cpp @@ -9,11 +9,12 @@ #include <hash.h> #include <random.h> #include <uint256.h> -#include <utiltime.h> +#include <util/time.h> #include <crypto/ripemd160.h> #include <crypto/sha1.h> #include <crypto/sha256.h> #include <crypto/sha512.h> +#include <crypto/siphash.h> /* Number of bytes to hash per iteration */ static const uint64_t BUFFER_SIZE = 1000*1000; diff --git a/src/bench/duplicate_inputs.cpp b/src/bench/duplicate_inputs.cpp new file mode 100644 index 0000000000..e0854e2c62 --- /dev/null +++ b/src/bench/duplicate_inputs.cpp @@ -0,0 +1,100 @@ +// Copyright (c) 2011-2018 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include <bench/bench.h> +#include <chainparams.h> +#include <coins.h> +#include <consensus/merkle.h> +#include <consensus/validation.h> +#include <miner.h> +#include <policy/policy.h> +#include <pow.h> +#include <scheduler.h> +#include <txdb.h> +#include <txmempool.h> +#include <util/time.h> +#include <validation.h> +#include <validationinterface.h> + +#include <boost/thread.hpp> + +#include <list> +#include <vector> + + +static void DuplicateInputs(benchmark::State& state) +{ + const CScript SCRIPT_PUB{CScript(OP_TRUE)}; + + // Switch to regtest so we can mine faster + // Also segwit is active, so we can include witness transactions + SelectParams(CBaseChainParams::REGTEST); + + InitScriptExecutionCache(); + + boost::thread_group thread_group; + CScheduler scheduler; + const CChainParams& chainparams = Params(); + { + ::pblocktree.reset(new CBlockTreeDB(1 << 20, true)); + ::pcoinsdbview.reset(new CCoinsViewDB(1 << 23, true)); + ::pcoinsTip.reset(new CCoinsViewCache(pcoinsdbview.get())); + + thread_group.create_thread(std::bind(&CScheduler::serviceQueue, &scheduler)); + GetMainSignals().RegisterBackgroundSignalScheduler(scheduler); + LoadGenesisBlock(chainparams); + CValidationState cvstate; + ActivateBestChain(cvstate, chainparams); + assert(::chainActive.Tip() != nullptr); + const bool witness_enabled{IsWitnessEnabled(::chainActive.Tip(), chainparams.GetConsensus())}; + assert(witness_enabled); + } + + CBlock block{}; + CMutableTransaction coinbaseTx{}; + CMutableTransaction naughtyTx{}; + + CBlockIndex* pindexPrev = ::chainActive.Tip(); + assert(pindexPrev != nullptr); + block.nBits = GetNextWorkRequired(pindexPrev, &block, chainparams.GetConsensus()); + block.nNonce = 0; + auto nHeight = pindexPrev->nHeight + 1; + + // Make a coinbase TX + coinbaseTx.vin.resize(1); + coinbaseTx.vin[0].prevout.SetNull(); + coinbaseTx.vout.resize(1); + coinbaseTx.vout[0].scriptPubKey = SCRIPT_PUB; + coinbaseTx.vout[0].nValue = GetBlockSubsidy(nHeight, chainparams.GetConsensus()); + coinbaseTx.vin[0].scriptSig = CScript() << nHeight << OP_0; + + + naughtyTx.vout.resize(1); + naughtyTx.vout[0].nValue = 0; + naughtyTx.vout[0].scriptPubKey = SCRIPT_PUB; + + uint64_t n_inputs = (((MAX_BLOCK_SERIALIZED_SIZE / WITNESS_SCALE_FACTOR) - (CTransaction(coinbaseTx).GetTotalSize() + CTransaction(naughtyTx).GetTotalSize())) / 41) - 100; + for (uint64_t x = 0; x < (n_inputs - 1); ++x) { + naughtyTx.vin.emplace_back(GetRandHash(), 0, CScript(), 0); + } + naughtyTx.vin.emplace_back(naughtyTx.vin.back()); + + block.vtx.push_back(MakeTransactionRef(std::move(coinbaseTx))); + block.vtx.push_back(MakeTransactionRef(std::move(naughtyTx))); + + block.hashMerkleRoot = BlockMerkleRoot(block); + + while (state.KeepRunning()) { + CValidationState cvstate{}; + assert(!CheckBlock(block, cvstate, chainparams.GetConsensus(), false, false)); + assert(cvstate.GetRejectReason() == "bad-txns-inputs-duplicate"); + } + + thread_group.interrupt_all(); + thread_group.join_all(); + GetMainSignals().FlushBackgroundCallbacks(); + GetMainSignals().UnregisterBackgroundSignalScheduler(); +} + +BENCHMARK(DuplicateInputs, 10); diff --git a/src/bench/examples.cpp b/src/bench/examples.cpp index 6d95e05ef6..e7ddd5a938 100644 --- a/src/bench/examples.cpp +++ b/src/bench/examples.cpp @@ -4,7 +4,7 @@ #include <bench/bench.h> #include <validation.h> -#include <utiltime.h> +#include <util/time.h> // Sanity test: this should loop ten times, and // min/max/average should be close to 100ms. diff --git a/src/bench/gcs_filter.cpp b/src/bench/gcs_filter.cpp index 6f4e384e3b..535ad35571 100644 --- a/src/bench/gcs_filter.cpp +++ b/src/bench/gcs_filter.cpp @@ -17,7 +17,7 @@ static void ConstructGCSFilter(benchmark::State& state) uint64_t siphash_k0 = 0; while (state.KeepRunning()) { - GCSFilter filter(siphash_k0, 0, 20, 1 << 20, elements); + GCSFilter filter({siphash_k0, 0, 20, 1 << 20}, elements); siphash_k0++; } @@ -32,7 +32,7 @@ static void MatchGCSFilter(benchmark::State& state) element[1] = static_cast<unsigned char>(i >> 8); elements.insert(std::move(element)); } - GCSFilter filter(0, 0, 20, 1 << 20, elements); + GCSFilter filter({0, 0, 20, 1 << 20}, elements); while (state.KeepRunning()) { filter.Match(GCSFilter::Element()); diff --git a/src/bench/mempool_eviction.cpp b/src/bench/mempool_eviction.cpp index 3908a7d231..ac8a182358 100644 --- a/src/bench/mempool_eviction.cpp +++ b/src/bench/mempool_eviction.cpp @@ -9,7 +9,7 @@ #include <list> #include <vector> -static void AddTx(const CTransactionRef& tx, const CAmount& nFee, CTxMemPool& pool) EXCLUSIVE_LOCKS_REQUIRED(pool.cs) +static void AddTx(const CTransactionRef& tx, const CAmount& nFee, CTxMemPool& pool) EXCLUSIVE_LOCKS_REQUIRED(cs_main, pool.cs) { int64_t nTime = 0; unsigned int nHeight = 1; @@ -108,7 +108,7 @@ static void MempoolEviction(benchmark::State& state) tx7.vout[1].nValue = 10 * COIN; CTxMemPool pool; - LOCK(pool.cs); + LOCK2(cs_main, pool.cs); // Create transaction references outside the "hot loop" const CTransactionRef tx1_r{MakeTransactionRef(tx1)}; const CTransactionRef tx2_r{MakeTransactionRef(tx2)}; @@ -127,7 +127,7 @@ static void MempoolEviction(benchmark::State& state) AddTx(tx6_r, 1100LL, pool); AddTx(tx7_r, 9000LL, pool); pool.TrimToSize(pool.DynamicMemoryUsage() * 3 / 4); - pool.TrimToSize(GetVirtualTransactionSize(tx1)); + pool.TrimToSize(GetVirtualTransactionSize(*tx1_r)); } } diff --git a/src/bench/prevector.cpp b/src/bench/prevector.cpp index 8cc404b9e2..00e5d7e7a0 100644 --- a/src/bench/prevector.cpp +++ b/src/bench/prevector.cpp @@ -2,13 +2,21 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#include <compat.h> #include <prevector.h> #include <serialize.h> #include <streams.h> +#include <type_traits> #include <bench/bench.h> +// GCC 4.8 is missing some C++11 type_traits, +// https://www.gnu.org/software/gcc/gcc-5/changes.html +#if defined(__GNUC__) && !defined(__clang__) && __GNUC__ < 5 +#define IS_TRIVIALLY_CONSTRUCTIBLE std::has_trivial_default_constructor +#else +#define IS_TRIVIALLY_CONSTRUCTIBLE std::is_trivially_default_constructible +#endif + struct nontrivial_t { int x; nontrivial_t() :x(-1) {} diff --git a/src/bitcoin-cli.cpp b/src/bitcoin-cli.cpp index 09507fd249..7cdf61ad35 100644 --- a/src/bitcoin-cli.cpp +++ b/src/bitcoin-cli.cpp @@ -12,11 +12,12 @@ #include <fs.h> #include <rpc/client.h> #include <rpc/protocol.h> -#include <util.h> -#include <utilstrencodings.h> +#include <util/system.h> +#include <util/strencodings.h> #include <memory> #include <stdio.h> +#include <tuple> #include <event2/buffer.h> #include <event2/keyvalq_struct.h> @@ -51,7 +52,7 @@ static void SetupCliArgs() gArgs.AddArg("-rpcport=<port>", strprintf("Connect to JSON-RPC on <port> (default: %u, testnet: %u, regtest: %u)", defaultBaseParams->RPCPort(), testnetBaseParams->RPCPort(), regtestBaseParams->RPCPort()), false, OptionsCategory::OPTIONS); gArgs.AddArg("-rpcuser=<user>", "Username for JSON-RPC connections", false, OptionsCategory::OPTIONS); gArgs.AddArg("-rpcwait", "Wait for RPC server to start", false, OptionsCategory::OPTIONS); - gArgs.AddArg("-rpcwallet=<walletname>", "Send RPC for non-default wallet on RPC server (needs to exactly match corresponding -wallet option passed to bitcoind)", false, OptionsCategory::OPTIONS); + gArgs.AddArg("-rpcwallet=<walletname>", "Send RPC for non-default wallet on RPC server (needs to exactly match corresponding -wallet option passed to bitcoind). This changes the RPC endpoint used, e.g. http://127.0.0.1:8332/wallet/<walletname>", false, OptionsCategory::OPTIONS); gArgs.AddArg("-stdin", "Read extra arguments from standard input, one per line until EOF/Ctrl-D (recommended for sensitive information such as passphrases). When combined with -stdinrpcpass, the first line from standard input is used for the RPC password.", false, OptionsCategory::OPTIONS); gArgs.AddArg("-stdinrpcpass", "Read RPC password from standard input as a single line. When combined with -stdin, the first line from standard input is used for the RPC password.", false, OptionsCategory::OPTIONS); @@ -491,9 +492,6 @@ static int CommandLineRPC(int argc, char *argv[]) } } while (fWait); } - catch (const boost::thread_interrupted&) { - throw; - } catch (const std::exception& e) { strPrint = std::string("error: ") + e.what(); nRet = EXIT_FAILURE; @@ -511,6 +509,10 @@ static int CommandLineRPC(int argc, char *argv[]) int main(int argc, char* argv[]) { +#ifdef WIN32 + util::WinCmdLineArgs winArgs; + std::tie(argc, argv) = winArgs.get(); +#endif SetupEnvironment(); if (!SetupNetworking()) { fprintf(stderr, "Error: Initializing networking failed\n"); diff --git a/src/bitcoin-tx.cpp b/src/bitcoin-tx.cpp index a3fcb87675..7c0c674a00 100644 --- a/src/bitcoin-tx.cpp +++ b/src/bitcoin-tx.cpp @@ -18,9 +18,9 @@ #include <script/script.h> #include <script/sign.h> #include <univalue.h> -#include <util.h> -#include <utilmoneystr.h> -#include <utilstrencodings.h> +#include <util/system.h> +#include <util/moneystr.h> +#include <util/strencodings.h> #include <memory> #include <stdio.h> @@ -255,7 +255,7 @@ static void MutateTxAddInput(CMutableTransaction& tx, const std::string& strInpu throw std::runtime_error("invalid TX input vout '" + strVout + "'"); // extract the optional sequence number - uint32_t nSequenceIn=std::numeric_limits<unsigned int>::max(); + uint32_t nSequenceIn = CTxIn::SEQUENCE_FINAL; if (vStrInputParts.size() > 2) nSequenceIn = std::stoul(vStrInputParts[2]); @@ -356,7 +356,7 @@ static void MutateTxAddOutMultiSig(CMutableTransaction& tx, const std::string& s if (vStrInputParts.size() < numkeys + 3) throw std::runtime_error("incorrect number of multisig pubkeys"); - if (required < 1 || required > 20 || numkeys < 1 || numkeys > 20 || numkeys < required) + if (required < 1 || required > MAX_PUBKEYS_PER_MULTISIG || numkeys < 1 || numkeys > MAX_PUBKEYS_PER_MULTISIG || numkeys < required) throw std::runtime_error("multisig parameter mismatch. Required " \ + std::to_string(required) + " of " + std::to_string(numkeys) + "signatures."); @@ -818,11 +818,7 @@ static int CommandLineRawTx(int argc, char* argv[]) MutateTx(tx, key, value); } - OutputTx(tx); - } - - catch (const boost::thread_interrupted&) { - throw; + OutputTx(CTransaction(tx)); } catch (const std::exception& e) { strPrint = std::string("error: ") + e.what(); diff --git a/src/bitcoind.cpp b/src/bitcoind.cpp index bf04d95b50..dde75c1b12 100644 --- a/src/bitcoind.cpp +++ b/src/bitcoind.cpp @@ -11,14 +11,15 @@ #include <clientversion.h> #include <compat.h> #include <fs.h> +#include <interfaces/chain.h> #include <rpc/server.h> #include <init.h> #include <noui.h> #include <shutdown.h> -#include <util.h> +#include <util/system.h> #include <httpserver.h> #include <httprpc.h> -#include <utilstrencodings.h> +#include <util/strencodings.h> #include <walletinitinterface.h> #include <stdio.h> @@ -58,6 +59,9 @@ static void WaitForShutdown() // static bool AppInit(int argc, char* argv[]) { + InitInterfaces interfaces; + interfaces.chain = interfaces::MakeChain(); + bool fRet = false; // @@ -77,7 +81,7 @@ static bool AppInit(int argc, char* argv[]) if (gArgs.IsArgSet("-version")) { - strUsage += FormatParagraph(LicenseInfo()); + strUsage += FormatParagraph(LicenseInfo()) + "\n"; } else { @@ -164,7 +168,7 @@ static bool AppInit(int argc, char* argv[]) // If locking the data directory failed, exit immediately return false; } - fRet = AppInitMain(); + fRet = AppInitMain(interfaces); } catch (const std::exception& e) { PrintExceptionContinue(&e, "AppInit()"); @@ -178,13 +182,17 @@ static bool AppInit(int argc, char* argv[]) } else { WaitForShutdown(); } - Shutdown(); + Shutdown(interfaces); return fRet; } int main(int argc, char* argv[]) { +#ifdef WIN32 + util::WinCmdLineArgs winArgs; + std::tie(argc, argv) = winArgs.get(); +#endif SetupEnvironment(); // Connect bitcoind signal handlers diff --git a/src/blockencodings.cpp b/src/blockencodings.cpp index 4c57965bec..10f51931f0 100644 --- a/src/blockencodings.cpp +++ b/src/blockencodings.cpp @@ -6,12 +6,13 @@ #include <consensus/consensus.h> #include <consensus/validation.h> #include <chainparams.h> -#include <hash.h> +#include <crypto/sha256.h> +#include <crypto/siphash.h> #include <random.h> #include <streams.h> #include <txmempool.h> #include <validation.h> -#include <util.h> +#include <util/system.h> #include <unordered_map> diff --git a/src/blockencodings.h b/src/blockencodings.h index fad1f56f54..0c2b83ebcf 100644 --- a/src/blockencodings.h +++ b/src/blockencodings.h @@ -52,12 +52,12 @@ public: } } - uint16_t offset = 0; + int32_t offset = 0; for (size_t j = 0; j < indexes.size(); j++) { - if (uint64_t(indexes[j]) + uint64_t(offset) > std::numeric_limits<uint16_t>::max()) + if (int32_t(indexes[j]) + offset > std::numeric_limits<uint16_t>::max()) throw std::ios_base::failure("indexes overflowed 16 bits"); indexes[j] = indexes[j] + offset; - offset = indexes[j] + 1; + offset = int32_t(indexes[j]) + 1; } } else { for (size_t i = 0; i < indexes.size(); i++) { @@ -186,6 +186,9 @@ public: READWRITE(prefilledtxn); + if (BlockTxCount() > std::numeric_limits<uint16_t>::max()) + throw std::ios_base::failure("indexes overflowed 16 bits"); + if (ser_action.ForRead()) FillShortTxIDSelector(); } diff --git a/src/blockfilter.cpp b/src/blockfilter.cpp index 91623fe70a..bcf24047ff 100644 --- a/src/blockfilter.cpp +++ b/src/blockfilter.cpp @@ -3,6 +3,7 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include <blockfilter.h> +#include <crypto/siphash.h> #include <hash.h> #include <primitives/transaction.h> #include <script/script.h> @@ -78,7 +79,7 @@ static uint64_t MapIntoRange(uint64_t x, uint64_t n) uint64_t GCSFilter::HashToRange(const Element& element) const { - uint64_t hash = CSipHasher(m_siphash_k0, m_siphash_k1) + uint64_t hash = CSipHasher(m_params.m_siphash_k0, m_params.m_siphash_k1) .Write(element.data(), element.size()) .Finalize(); return MapIntoRange(hash, m_F); @@ -95,16 +96,13 @@ std::vector<uint64_t> GCSFilter::BuildHashedSet(const ElementSet& elements) cons return hashed_elements; } -GCSFilter::GCSFilter(uint64_t siphash_k0, uint64_t siphash_k1, uint8_t P, uint32_t M) - : m_siphash_k0(siphash_k0), m_siphash_k1(siphash_k1), m_P(P), m_M(M), m_N(0), m_F(0) +GCSFilter::GCSFilter(const Params& params) + : m_params(params), m_N(0), m_F(0), m_encoded{0} {} -GCSFilter::GCSFilter(uint64_t siphash_k0, uint64_t siphash_k1, uint8_t P, uint32_t M, - std::vector<unsigned char> encoded_filter) - : GCSFilter(siphash_k0, siphash_k1, P, M) +GCSFilter::GCSFilter(const Params& params, std::vector<unsigned char> encoded_filter) + : m_params(params), m_encoded(std::move(encoded_filter)) { - m_encoded = std::move(encoded_filter); - VectorReader stream(GCS_SER_TYPE, GCS_SER_VERSION, m_encoded, 0); uint64_t N = ReadCompactSize(stream); @@ -112,29 +110,28 @@ GCSFilter::GCSFilter(uint64_t siphash_k0, uint64_t siphash_k1, uint8_t P, uint32 if (m_N != N) { throw std::ios_base::failure("N must be <2^32"); } - m_F = static_cast<uint64_t>(m_N) * static_cast<uint64_t>(m_M); + m_F = static_cast<uint64_t>(m_N) * static_cast<uint64_t>(m_params.m_M); // Verify that the encoded filter contains exactly N elements. If it has too much or too little // data, a std::ios_base::failure exception will be raised. BitStreamReader<VectorReader> bitreader(stream); for (uint64_t i = 0; i < m_N; ++i) { - GolombRiceDecode(bitreader, m_P); + GolombRiceDecode(bitreader, m_params.m_P); } if (!stream.empty()) { throw std::ios_base::failure("encoded_filter contains excess data"); } } -GCSFilter::GCSFilter(uint64_t siphash_k0, uint64_t siphash_k1, uint8_t P, uint32_t M, - const ElementSet& elements) - : GCSFilter(siphash_k0, siphash_k1, P, M) +GCSFilter::GCSFilter(const Params& params, const ElementSet& elements) + : m_params(params) { size_t N = elements.size(); m_N = static_cast<uint32_t>(N); if (m_N != N) { throw std::invalid_argument("N must be <2^32"); } - m_F = static_cast<uint64_t>(m_N) * static_cast<uint64_t>(m_M); + m_F = static_cast<uint64_t>(m_N) * static_cast<uint64_t>(m_params.m_M); CVectorWriter stream(GCS_SER_TYPE, GCS_SER_VERSION, m_encoded, 0); @@ -149,7 +146,7 @@ GCSFilter::GCSFilter(uint64_t siphash_k0, uint64_t siphash_k1, uint8_t P, uint32 uint64_t last_value = 0; for (uint64_t value : BuildHashedSet(elements)) { uint64_t delta = value - last_value; - GolombRiceEncode(bitwriter, m_P, delta); + GolombRiceEncode(bitwriter, m_params.m_P, delta); last_value = value; } @@ -169,7 +166,7 @@ bool GCSFilter::MatchInternal(const uint64_t* element_hashes, size_t size) const uint64_t value = 0; size_t hashes_index = 0; for (uint32_t i = 0; i < m_N; ++i) { - uint64_t delta = GolombRiceDecode(bitreader, m_P); + uint64_t delta = GolombRiceDecode(bitreader, m_params.m_P); value += delta; while (true) { @@ -224,19 +221,39 @@ static GCSFilter::ElementSet BasicFilterElements(const CBlock& block, return elements; } +BlockFilter::BlockFilter(BlockFilterType filter_type, const uint256& block_hash, + std::vector<unsigned char> filter) + : m_filter_type(filter_type), m_block_hash(block_hash) +{ + GCSFilter::Params params; + if (!BuildParams(params)) { + throw std::invalid_argument("unknown filter_type"); + } + m_filter = GCSFilter(params, std::move(filter)); +} + BlockFilter::BlockFilter(BlockFilterType filter_type, const CBlock& block, const CBlockUndo& block_undo) : m_filter_type(filter_type), m_block_hash(block.GetHash()) { + GCSFilter::Params params; + if (!BuildParams(params)) { + throw std::invalid_argument("unknown filter_type"); + } + m_filter = GCSFilter(params, BasicFilterElements(block, block_undo)); +} + +bool BlockFilter::BuildParams(GCSFilter::Params& params) const +{ switch (m_filter_type) { case BlockFilterType::BASIC: - m_filter = GCSFilter(m_block_hash.GetUint64(0), m_block_hash.GetUint64(1), - BASIC_FILTER_P, BASIC_FILTER_M, - BasicFilterElements(block, block_undo)); - break; - - default: - throw std::invalid_argument("unknown filter_type"); + params.m_siphash_k0 = m_block_hash.GetUint64(0); + params.m_siphash_k1 = m_block_hash.GetUint64(1); + params.m_P = BASIC_FILTER_P; + params.m_M = BASIC_FILTER_M; + return true; } + + return false; } uint256 BlockFilter::GetHash() const diff --git a/src/blockfilter.h b/src/blockfilter.h index 46833ac0be..4d1f51dd60 100644 --- a/src/blockfilter.h +++ b/src/blockfilter.h @@ -5,14 +5,15 @@ #ifndef BITCOIN_BLOCKFILTER_H #define BITCOIN_BLOCKFILTER_H -#include <set> #include <stdint.h> +#include <unordered_set> #include <vector> #include <primitives/block.h> #include <serialize.h> #include <uint256.h> #include <undo.h> +#include <util/bytevectorhash.h> /** * This implements a Golomb-coded set as defined in BIP 158. It is a @@ -22,13 +23,22 @@ class GCSFilter { public: typedef std::vector<unsigned char> Element; - typedef std::set<Element> ElementSet; + typedef std::unordered_set<Element, ByteVectorHash> ElementSet; + + struct Params + { + uint64_t m_siphash_k0; + uint64_t m_siphash_k1; + uint8_t m_P; //!< Golomb-Rice coding parameter + uint32_t m_M; //!< Inverse false positive rate + + Params(uint64_t siphash_k0 = 0, uint64_t siphash_k1 = 0, uint8_t P = 0, uint32_t M = 1) + : m_siphash_k0(siphash_k0), m_siphash_k1(siphash_k1), m_P(P), m_M(M) + {} + }; private: - uint64_t m_siphash_k0; - uint64_t m_siphash_k1; - uint8_t m_P; //!< Golomb-Rice coding parameter - uint32_t m_M; //!< Inverse false positive rate + Params m_params; uint32_t m_N; //!< Number of elements in the filter uint64_t m_F; //!< Range of element hashes, F = N * M std::vector<unsigned char> m_encoded; @@ -44,19 +54,16 @@ private: public: /** Constructs an empty filter. */ - GCSFilter(uint64_t siphash_k0 = 0, uint64_t siphash_k1 = 0, uint8_t P = 0, uint32_t M = 0); + explicit GCSFilter(const Params& params = Params()); /** Reconstructs an already-created filter from an encoding. */ - GCSFilter(uint64_t siphash_k0, uint64_t siphash_k1, uint8_t P, uint32_t M, - std::vector<unsigned char> encoded_filter); + GCSFilter(const Params& params, std::vector<unsigned char> encoded_filter); /** Builds a new filter from the params and set of elements. */ - GCSFilter(uint64_t siphash_k0, uint64_t siphash_k1, uint8_t P, uint32_t M, - const ElementSet& elements); + GCSFilter(const Params& params, const ElementSet& elements); - uint8_t GetP() const { return m_P; } uint32_t GetN() const { return m_N; } - uint32_t GetM() const { return m_M; } + const Params& GetParams() const { return m_params; } const std::vector<unsigned char>& GetEncoded() const { return m_encoded; } /** @@ -92,13 +99,21 @@ private: uint256 m_block_hash; GCSFilter m_filter; + bool BuildParams(GCSFilter::Params& params) const; + public: - // Construct a new BlockFilter of the specified type from a block. + BlockFilter() = default; + + //! Reconstruct a BlockFilter from parts. + BlockFilter(BlockFilterType filter_type, const uint256& block_hash, + std::vector<unsigned char> filter); + + //! Construct a new BlockFilter of the specified type from a block. BlockFilter(BlockFilterType filter_type, const CBlock& block, const CBlockUndo& block_undo); BlockFilterType GetFilterType() const { return m_filter_type; } - + const uint256& GetBlockHash() const { return m_block_hash; } const GCSFilter& GetFilter() const { return m_filter; } const std::vector<unsigned char>& GetEncodedFilter() const @@ -106,10 +121,10 @@ public: return m_filter.GetEncoded(); } - // Compute the filter hash. + //! Compute the filter hash. uint256 GetHash() const; - // Compute the filter header given the previous one. + //! Compute the filter header given the previous one. uint256 ComputeHeader(const uint256& prev_header) const; template <typename Stream> @@ -130,15 +145,11 @@ public: m_filter_type = static_cast<BlockFilterType>(filter_type); - switch (m_filter_type) { - case BlockFilterType::BASIC: - m_filter = GCSFilter(m_block_hash.GetUint64(0), m_block_hash.GetUint64(1), - BASIC_FILTER_P, BASIC_FILTER_M, std::move(encoded_filter)); - break; - - default: + GCSFilter::Params params; + if (!BuildParams(params)) { throw std::ios_base::failure("unknown filter_type"); } + m_filter = GCSFilter(params, std::move(encoded_filter)); } }; diff --git a/src/chain.h b/src/chain.h index 1c0c5a7e69..5a6f10b84f 100644 --- a/src/chain.h +++ b/src/chain.h @@ -18,7 +18,7 @@ * Maximum amount of time that a block timestamp is allowed to exceed the * current network-adjusted time before the block will be accepted. */ -static const int64_t MAX_FUTURE_BLOCK_TIME = 2 * 60 * 60; +static constexpr int64_t MAX_FUTURE_BLOCK_TIME = 2 * 60 * 60; /** * Timestamp window used as a grace period by code that compares external @@ -26,7 +26,15 @@ static const int64_t MAX_FUTURE_BLOCK_TIME = 2 * 60 * 60; * to block timestamps. This should be set at least as high as * MAX_FUTURE_BLOCK_TIME. */ -static const int64_t TIMESTAMP_WINDOW = MAX_FUTURE_BLOCK_TIME; +static constexpr int64_t TIMESTAMP_WINDOW = MAX_FUTURE_BLOCK_TIME; + +/** + * Maximum gap between node time and block time used + * for the "Catching up..." mode in GUI. + * + * Ref: https://github.com/bitcoin/bitcoin/pull/1026 + */ +static constexpr int64_t MAX_BLOCK_TIME_GAP = 90 * 60; class CBlockFileInfo { @@ -294,6 +302,15 @@ public: return *phashBlock; } + /** + * Check whether this block's and all previous blocks' transactions have been + * downloaded (and stored to disk) at some point. + * + * Does not imply the transactions are consensus-valid (ConnectTip might fail) + * Does not imply the transactions are still stored on disk. (IsBlockPruned might return true) + */ + bool HaveTxsDownloaded() const { return nChainTx != 0; } + int64_t GetBlockTime() const { return (int64_t)nTime; diff --git a/src/chainparams.cpp b/src/chainparams.cpp index 0574e2395e..d3972fdb89 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -8,8 +8,8 @@ #include <chainparamsseeds.h> #include <consensus/merkle.h> #include <tinyformat.h> -#include <util.h> -#include <utilstrencodings.h> +#include <util/system.h> +#include <util/strencodings.h> #include <versionbitsinfo.h> #include <assert.h> @@ -107,6 +107,8 @@ public: pchMessageStart[3] = 0xd9; nDefaultPort = 8333; nPruneAfterHeight = 100000; + m_assumed_blockchain_size = 200; + m_assumed_chain_state_size = 3; genesis = CreateGenesisBlock(1231006505, 2083236893, 0x1d00ffff, 1, 50 * COIN); consensus.hashGenesisBlock = genesis.GetHash(); @@ -125,6 +127,7 @@ public: vSeeds.emplace_back("seed.bitcoin.jonasschnelli.ch"); // Jonas Schnelli, only supports x1, x5, x9, and xd vSeeds.emplace_back("seed.btc.petertodd.org"); // Peter Todd, only supports x1, x5, x9, and xd vSeeds.emplace_back("seed.bitcoin.sprovoost.nl"); // Sjors Provoost + vSeeds.emplace_back("dnsseed.emzy.de"); // Stephan Oeste base58Prefixes[PUBKEY_ADDRESS] = std::vector<unsigned char>(1,0); base58Prefixes[SCRIPT_ADDRESS] = std::vector<unsigned char>(1,5); @@ -216,6 +219,8 @@ public: pchMessageStart[3] = 0x07; nDefaultPort = 18333; nPruneAfterHeight = 1000; + m_assumed_blockchain_size = 20; + m_assumed_chain_state_size = 2; genesis = CreateGenesisBlock(1296688602, 414098458, 0x1d00ffff, 1, 50 * COIN); consensus.hashGenesisBlock = genesis.GetHash(); @@ -272,10 +277,10 @@ public: strNetworkID = "regtest"; consensus.nSubsidyHalvingInterval = 150; consensus.BIP16Exception = uint256(); - consensus.BIP34Height = 100000000; // BIP34 has not activated on regtest (far in the future so block v1 are not rejected in tests) + consensus.BIP34Height = 500; // BIP34 activated on regtest (Used in functional tests) consensus.BIP34Hash = uint256(); - consensus.BIP65Height = 1351; // BIP65 activated on regtest (Used in rpc activation tests) - consensus.BIP66Height = 1251; // BIP66 activated on regtest (Used in rpc activation tests) + consensus.BIP65Height = 1351; // BIP65 activated on regtest (Used in functional tests) + consensus.BIP66Height = 1251; // BIP66 activated on regtest (Used in functional tests) consensus.powLimit = uint256S("7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); consensus.nPowTargetTimespan = 14 * 24 * 60 * 60; // two weeks consensus.nPowTargetSpacing = 10 * 60; @@ -305,6 +310,8 @@ public: pchMessageStart[3] = 0xda; nDefaultPort = 18444; nPruneAfterHeight = 1000; + m_assumed_blockchain_size = 0; + m_assumed_chain_state_size = 0; UpdateVersionBitsParametersFromArgs(args); diff --git a/src/chainparams.h b/src/chainparams.h index 19818b40af..6ff3dbb7e5 100644 --- a/src/chainparams.h +++ b/src/chainparams.h @@ -67,6 +67,10 @@ public: /** Policy: Filter transactions that do not match well-defined patterns */ bool RequireStandard() const { return fRequireStandard; } 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; } /** Return the BIP70 network string (main, test or regtest) */ @@ -87,6 +91,8 @@ protected: CMessageHeader::MessageStartChars pchMessageStart; int nDefaultPort; uint64_t nPruneAfterHeight; + uint64_t m_assumed_blockchain_size; + uint64_t m_assumed_chain_state_size; std::vector<std::string> vSeeds; std::vector<unsigned char> base58Prefixes[MAX_BASE58_TYPES]; std::string bech32_hrp; diff --git a/src/chainparamsbase.cpp b/src/chainparamsbase.cpp index 870640e77d..f0559a319a 100644 --- a/src/chainparamsbase.cpp +++ b/src/chainparamsbase.cpp @@ -6,8 +6,8 @@ #include <chainparamsbase.h> #include <tinyformat.h> -#include <util.h> -#include <utilmemory.h> +#include <util/system.h> +#include <util/memory.h> #include <assert.h> diff --git a/src/coins.cpp b/src/coins.cpp index f125b483bb..3ef9e0463c 100644 --- a/src/coins.cpp +++ b/src/coins.cpp @@ -6,6 +6,7 @@ #include <consensus/consensus.h> #include <random.h> +#include <version.h> bool CCoinsView::GetCoin(const COutPoint &outpoint, Coin &coin) const { return false; } uint256 CCoinsView::GetBestBlock() const { return uint256(); } diff --git a/src/coins.h b/src/coins.h index 3867a37b39..94493453f0 100644 --- a/src/coins.h +++ b/src/coins.h @@ -9,7 +9,7 @@ #include <primitives/transaction.h> #include <compressor.h> #include <core_memusage.h> -#include <hash.h> +#include <crypto/siphash.h> #include <memusage.h> #include <serialize.h> #include <uint256.h> diff --git a/src/compat.h b/src/compat.h index d228611160..7b164d5630 100644 --- a/src/compat.h +++ b/src/compat.h @@ -10,16 +10,6 @@ #include <config/bitcoin-config.h> #endif -#include <type_traits> - -// GCC 4.8 is missing some C++11 type_traits, -// https://www.gnu.org/software/gcc/gcc-5/changes.html -#if defined(__GNUC__) && !defined(__clang__) && __GNUC__ < 5 -#define IS_TRIVIALLY_CONSTRUCTIBLE std::has_trivial_default_constructor -#else -#define IS_TRIVIALLY_CONSTRUCTIBLE std::is_trivially_default_constructible -#endif - #ifdef WIN32 #ifdef _WIN32_WINNT #undef _WIN32_WINNT @@ -102,8 +92,15 @@ typedef void* sockopt_arg_type; typedef char* sockopt_arg_type; #endif +// Note these both should work with the current usage of poll, but best to be safe +// WIN32 poll is broken https://daniel.haxx.se/blog/2012/10/10/wsapoll-is-broken/ +// __APPLE__ poll is broke https://github.com/bitcoin/bitcoin/pull/14336#issuecomment-437384408 +#if defined(__linux__) +#define USE_POLL +#endif + bool static inline IsSelectableSocket(const SOCKET& s) { -#ifdef WIN32 +#if defined(USE_POLL) || defined(WIN32) return true; #else return (s < FD_SETSIZE); diff --git a/src/consensus/merkle.cpp b/src/consensus/merkle.cpp index 2a87a936b1..b47d9774ca 100644 --- a/src/consensus/merkle.cpp +++ b/src/consensus/merkle.cpp @@ -4,7 +4,7 @@ #include <consensus/merkle.h> #include <hash.h> -#include <utilstrencodings.h> +#include <util/strencodings.h> /* WARNING! If you're reading this because you're learning about crypto and/or designing a new system that will use merkle trees, keep in mind diff --git a/src/consensus/tx_verify.cpp b/src/consensus/tx_verify.cpp index 0628ec1d47..0a7eacfb91 100644 --- a/src/consensus/tx_verify.cpp +++ b/src/consensus/tx_verify.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2017 The Bitcoin Core developers +// Copyright (c) 2017-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. @@ -12,7 +12,7 @@ // TODO remove the following dependencies #include <chain.h> #include <coins.h> -#include <utilmoneystr.h> +#include <util/moneystr.h> bool IsFinalTx(const CTransaction &tx, int nBlockHeight, int64_t nBlockTime) { diff --git a/src/core_io.h b/src/core_io.h index 2c3b64d81e..6f87161f46 100644 --- a/src/core_io.h +++ b/src/core_io.h @@ -6,6 +6,7 @@ #define BITCOIN_CORE_IO_H #include <amount.h> +#include <attributes.h> #include <string> #include <vector> @@ -22,8 +23,8 @@ class UniValue; // core_read.cpp CScript ParseScript(const std::string& s); std::string ScriptToAsmStr(const CScript& script, const bool fAttemptSighashDecode = false); -bool DecodeHexTx(CMutableTransaction& tx, const std::string& hex_tx, bool try_no_witness = false, bool try_witness = true); -bool DecodeHexBlk(CBlock&, const std::string& strHexBlk); +NODISCARD bool DecodeHexTx(CMutableTransaction& tx, const std::string& hex_tx, bool try_no_witness = false, bool try_witness = true); +NODISCARD bool DecodeHexBlk(CBlock&, const std::string& strHexBlk); bool DecodeHexBlockHeader(CBlockHeader&, const std::string& hex_header); /** @@ -36,7 +37,7 @@ bool DecodeHexBlockHeader(CBlockHeader&, const std::string& hex_header); */ bool ParseHashStr(const std::string& strHex, uint256& result); std::vector<unsigned char> ParseHexUV(const UniValue& v, const std::string& strName); -bool DecodePSBT(PartiallySignedTransaction& psbt, const std::string& base64_tx, std::string& error); +NODISCARD bool DecodePSBT(PartiallySignedTransaction& psbt, const std::string& base64_tx, std::string& error); int ParseSighashString(const UniValue& sighash); // core_write.cpp diff --git a/src/core_read.cpp b/src/core_read.cpp index 301f99bc1c..3b82b2853c 100644 --- a/src/core_read.cpp +++ b/src/core_read.cpp @@ -11,8 +11,8 @@ #include <serialize.h> #include <streams.h> #include <univalue.h> -#include <util.h> -#include <utilstrencodings.h> +#include <util/system.h> +#include <util/strencodings.h> #include <version.h> #include <boost/algorithm/string/classification.hpp> diff --git a/src/core_write.cpp b/src/core_write.cpp index b86490716f..765a170307 100644 --- a/src/core_write.cpp +++ b/src/core_write.cpp @@ -12,9 +12,9 @@ #include <serialize.h> #include <streams.h> #include <univalue.h> -#include <util.h> -#include <utilmoneystr.h> -#include <utilstrencodings.h> +#include <util/system.h> +#include <util/moneystr.h> +#include <util/strencodings.h> UniValue ValueFromAmount(const CAmount& amount) { diff --git a/src/crypto/sha512.h b/src/crypto/sha512.h index cd1023bc85..4118ac1b18 100644 --- a/src/crypto/sha512.h +++ b/src/crypto/sha512.h @@ -17,7 +17,7 @@ private: uint64_t bytes; public: - static const size_t OUTPUT_SIZE = 64; + static constexpr size_t OUTPUT_SIZE = 64; CSHA512(); CSHA512& Write(const unsigned char* data, size_t len); diff --git a/src/crypto/siphash.cpp b/src/crypto/siphash.cpp new file mode 100644 index 0000000000..e81957111a --- /dev/null +++ b/src/crypto/siphash.cpp @@ -0,0 +1,173 @@ +// Copyright (c) 2016-2018 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include <crypto/siphash.h> + +#define ROTL(x, b) (uint64_t)(((x) << (b)) | ((x) >> (64 - (b)))) + +#define SIPROUND do { \ + v0 += v1; v1 = ROTL(v1, 13); v1 ^= v0; \ + v0 = ROTL(v0, 32); \ + v2 += v3; v3 = ROTL(v3, 16); v3 ^= v2; \ + v0 += v3; v3 = ROTL(v3, 21); v3 ^= v0; \ + v2 += v1; v1 = ROTL(v1, 17); v1 ^= v2; \ + v2 = ROTL(v2, 32); \ +} while (0) + +CSipHasher::CSipHasher(uint64_t k0, uint64_t k1) +{ + v[0] = 0x736f6d6570736575ULL ^ k0; + v[1] = 0x646f72616e646f6dULL ^ k1; + v[2] = 0x6c7967656e657261ULL ^ k0; + v[3] = 0x7465646279746573ULL ^ k1; + count = 0; + tmp = 0; +} + +CSipHasher& CSipHasher::Write(uint64_t data) +{ + uint64_t v0 = v[0], v1 = v[1], v2 = v[2], v3 = v[3]; + + assert(count % 8 == 0); + + v3 ^= data; + SIPROUND; + SIPROUND; + v0 ^= data; + + v[0] = v0; + v[1] = v1; + v[2] = v2; + v[3] = v3; + + count += 8; + return *this; +} + +CSipHasher& CSipHasher::Write(const unsigned char* data, size_t size) +{ + uint64_t v0 = v[0], v1 = v[1], v2 = v[2], v3 = v[3]; + uint64_t t = tmp; + int c = count; + + while (size--) { + t |= ((uint64_t)(*(data++))) << (8 * (c % 8)); + c++; + if ((c & 7) == 0) { + v3 ^= t; + SIPROUND; + SIPROUND; + v0 ^= t; + t = 0; + } + } + + v[0] = v0; + v[1] = v1; + v[2] = v2; + v[3] = v3; + count = c; + tmp = t; + + return *this; +} + +uint64_t CSipHasher::Finalize() const +{ + uint64_t v0 = v[0], v1 = v[1], v2 = v[2], v3 = v[3]; + + uint64_t t = tmp | (((uint64_t)count) << 56); + + v3 ^= t; + SIPROUND; + SIPROUND; + v0 ^= t; + v2 ^= 0xFF; + SIPROUND; + SIPROUND; + SIPROUND; + SIPROUND; + return v0 ^ v1 ^ v2 ^ v3; +} + +uint64_t SipHashUint256(uint64_t k0, uint64_t k1, const uint256& val) +{ + /* Specialized implementation for efficiency */ + uint64_t d = val.GetUint64(0); + + uint64_t v0 = 0x736f6d6570736575ULL ^ k0; + uint64_t v1 = 0x646f72616e646f6dULL ^ k1; + uint64_t v2 = 0x6c7967656e657261ULL ^ k0; + uint64_t v3 = 0x7465646279746573ULL ^ k1 ^ d; + + SIPROUND; + SIPROUND; + v0 ^= d; + d = val.GetUint64(1); + v3 ^= d; + SIPROUND; + SIPROUND; + v0 ^= d; + d = val.GetUint64(2); + v3 ^= d; + SIPROUND; + SIPROUND; + v0 ^= d; + d = val.GetUint64(3); + v3 ^= d; + SIPROUND; + SIPROUND; + v0 ^= d; + v3 ^= ((uint64_t)4) << 59; + SIPROUND; + SIPROUND; + v0 ^= ((uint64_t)4) << 59; + v2 ^= 0xFF; + SIPROUND; + SIPROUND; + SIPROUND; + SIPROUND; + return v0 ^ v1 ^ v2 ^ v3; +} + +uint64_t SipHashUint256Extra(uint64_t k0, uint64_t k1, const uint256& val, uint32_t extra) +{ + /* Specialized implementation for efficiency */ + uint64_t d = val.GetUint64(0); + + uint64_t v0 = 0x736f6d6570736575ULL ^ k0; + uint64_t v1 = 0x646f72616e646f6dULL ^ k1; + uint64_t v2 = 0x6c7967656e657261ULL ^ k0; + uint64_t v3 = 0x7465646279746573ULL ^ k1 ^ d; + + SIPROUND; + SIPROUND; + v0 ^= d; + d = val.GetUint64(1); + v3 ^= d; + SIPROUND; + SIPROUND; + v0 ^= d; + d = val.GetUint64(2); + v3 ^= d; + SIPROUND; + SIPROUND; + v0 ^= d; + d = val.GetUint64(3); + v3 ^= d; + SIPROUND; + SIPROUND; + v0 ^= d; + d = (((uint64_t)36) << 56) | extra; + v3 ^= d; + SIPROUND; + SIPROUND; + v0 ^= d; + v2 ^= 0xFF; + SIPROUND; + SIPROUND; + SIPROUND; + SIPROUND; + return v0 ^ v1 ^ v2 ^ v3; +} diff --git a/src/crypto/siphash.h b/src/crypto/siphash.h new file mode 100644 index 0000000000..b312f913f9 --- /dev/null +++ b/src/crypto/siphash.h @@ -0,0 +1,47 @@ +// Copyright (c) 2016-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_CRYPTO_SIPHASH_H +#define BITCOIN_CRYPTO_SIPHASH_H + +#include <stdint.h> + +#include <uint256.h> + +/** SipHash-2-4 */ +class CSipHasher +{ +private: + uint64_t v[4]; + uint64_t tmp; + int count; + +public: + /** Construct a SipHash calculator initialized with 128-bit key (k0, k1) */ + CSipHasher(uint64_t k0, uint64_t k1); + /** Hash a 64-bit integer worth of data + * It is treated as if this was the little-endian interpretation of 8 bytes. + * This function can only be used when a multiple of 8 bytes have been written so far. + */ + CSipHasher& Write(uint64_t data); + /** Hash arbitrary bytes. */ + CSipHasher& Write(const unsigned char* data, size_t size); + /** Compute the 64-bit SipHash-2-4 of the data written so far. The object remains untouched. */ + uint64_t Finalize() const; +}; + +/** Optimized SipHash-2-4 implementation for uint256. + * + * It is identical to: + * SipHasher(k0, k1) + * .Write(val.GetUint64(0)) + * .Write(val.GetUint64(1)) + * .Write(val.GetUint64(2)) + * .Write(val.GetUint64(3)) + * .Finalize() + */ +uint64_t SipHashUint256(uint64_t k0, uint64_t k1, const uint256& val); +uint64_t SipHashUint256Extra(uint64_t k0, uint64_t k1, const uint256& val, uint32_t extra); + +#endif // BITCOIN_CRYPTO_SIPHASH_H diff --git a/src/dbwrapper.h b/src/dbwrapper.h index 52f9efe17c..416f5e8399 100644 --- a/src/dbwrapper.h +++ b/src/dbwrapper.h @@ -9,8 +9,8 @@ #include <fs.h> #include <serialize.h> #include <streams.h> -#include <util.h> -#include <utilstrencodings.h> +#include <util/system.h> +#include <util/strencodings.h> #include <version.h> #include <leveldb/db.h> diff --git a/src/dummywallet.cpp b/src/dummywallet.cpp index 3714187a96..9211a7596b 100644 --- a/src/dummywallet.cpp +++ b/src/dummywallet.cpp @@ -3,7 +3,7 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include <stdio.h> -#include <util.h> +#include <util/system.h> #include <walletinitinterface.h> class CWallet; @@ -14,13 +14,7 @@ public: bool HasWalletSupport() const override {return false;} void AddWalletOptions() const override; bool ParameterInteraction() const override {return true;} - void RegisterRPC(CRPCTable &) const override {} - bool Verify() const override {return true;} - bool Open() const override {LogPrintf("No wallet support compiled in!\n"); return true;} - void Start(CScheduler& scheduler) const override {} - void Flush() const override {} - void Stop() const override {} - void Close() const override {} + void Construct(InitInterfaces& interfaces) const override {LogPrintf("No wallet support compiled in!\n");} }; void DummyWalletInit::AddWalletOptions() const @@ -34,6 +28,16 @@ void DummyWalletInit::AddWalletOptions() const const WalletInitInterface& g_wallet_init_interface = DummyWalletInit(); +fs::path GetWalletDir() +{ + throw std::logic_error("Wallet function called in non-wallet build."); +} + +std::vector<fs::path> ListWalletDir() +{ + throw std::logic_error("Wallet function called in non-wallet build."); +} + std::vector<std::shared_ptr<CWallet>> GetWallets() { throw std::logic_error("Wallet function called in non-wallet build."); diff --git a/src/fs.cpp b/src/fs.cpp index df79b5e3df..3c8f4c0247 100644 --- a/src/fs.cpp +++ b/src/fs.cpp @@ -3,6 +3,7 @@ #ifndef WIN32 #include <fcntl.h> #else +#define NOMINMAX #include <codecvt> #include <windows.h> #endif @@ -89,7 +90,7 @@ bool FileLock::TryLock() return false; } _OVERLAPPED overlapped = {0}; - if (!LockFileEx(hFile, LOCKFILE_EXCLUSIVE_LOCK | LOCKFILE_FAIL_IMMEDIATELY, 0, 0, 0, &overlapped)) { + if (!LockFileEx(hFile, LOCKFILE_EXCLUSIVE_LOCK | LOCKFILE_FAIL_IMMEDIATELY, 0, std::numeric_limits<DWORD>::max(), std::numeric_limits<DWORD>::max(), &overlapped)) { reason = GetErrorReason(); return false; } @@ -113,4 +114,106 @@ std::string get_filesystem_error_message(const fs::filesystem_error& e) #endif } +#ifdef WIN32 +#ifdef __GLIBCXX__ + +// reference: https://github.com/gcc-mirror/gcc/blob/gcc-7_3_0-release/libstdc%2B%2B-v3/include/std/fstream#L270 + +static std::string openmodeToStr(std::ios_base::openmode mode) +{ + switch (mode & ~std::ios_base::ate) { + case std::ios_base::out: + case std::ios_base::out | std::ios_base::trunc: + return "w"; + case std::ios_base::out | std::ios_base::app: + case std::ios_base::app: + return "a"; + case std::ios_base::in: + return "r"; + case std::ios_base::in | std::ios_base::out: + return "r+"; + case std::ios_base::in | std::ios_base::out | std::ios_base::trunc: + return "w+"; + case std::ios_base::in | std::ios_base::out | std::ios_base::app: + case std::ios_base::in | std::ios_base::app: + return "a+"; + case std::ios_base::out | std::ios_base::binary: + case std::ios_base::out | std::ios_base::trunc | std::ios_base::binary: + return "wb"; + case std::ios_base::out | std::ios_base::app | std::ios_base::binary: + case std::ios_base::app | std::ios_base::binary: + return "ab"; + case std::ios_base::in | std::ios_base::binary: + return "rb"; + case std::ios_base::in | std::ios_base::out | std::ios_base::binary: + return "r+b"; + case std::ios_base::in | std::ios_base::out | std::ios_base::trunc | std::ios_base::binary: + return "w+b"; + case std::ios_base::in | std::ios_base::out | std::ios_base::app | std::ios_base::binary: + case std::ios_base::in | std::ios_base::app | std::ios_base::binary: + return "a+b"; + default: + return std::string(); + } +} + +void ifstream::open(const fs::path& p, std::ios_base::openmode mode) +{ + close(); + m_file = fsbridge::fopen(p, openmodeToStr(mode).c_str()); + if (m_file == nullptr) { + return; + } + m_filebuf = __gnu_cxx::stdio_filebuf<char>(m_file, mode); + rdbuf(&m_filebuf); + if (mode & std::ios_base::ate) { + seekg(0, std::ios_base::end); + } +} + +void ifstream::close() +{ + if (m_file != nullptr) { + m_filebuf.close(); + fclose(m_file); + } + m_file = nullptr; +} + +void ofstream::open(const fs::path& p, std::ios_base::openmode mode) +{ + close(); + m_file = fsbridge::fopen(p, openmodeToStr(mode).c_str()); + if (m_file == nullptr) { + return; + } + m_filebuf = __gnu_cxx::stdio_filebuf<char>(m_file, mode); + rdbuf(&m_filebuf); + if (mode & std::ios_base::ate) { + seekp(0, std::ios_base::end); + } +} + +void ofstream::close() +{ + if (m_file != nullptr) { + m_filebuf.close(); + fclose(m_file); + } + m_file = nullptr; +} +#else // __GLIBCXX__ + +static_assert(sizeof(*fs::path().BOOST_FILESYSTEM_C_STR) == sizeof(wchar_t), + "Warning: This build is using boost::filesystem ofstream and ifstream " + "implementations which will fail to open paths containing multibyte " + "characters. You should delete this static_assert to ignore this warning, " + "or switch to a different C++ standard library like the Microsoft C++ " + "Standard Library (where boost uses non-standard extensions to construct " + "stream objects with wide filenames), or the GNU libstdc++ library (where " + "a more complicated workaround has been implemented above)."); + +#endif // __GLIBCXX__ +#endif // WIN32 + } // fsbridge @@ -1,4 +1,4 @@ -// Copyright (c) 2017 The Bitcoin Core developers +// Copyright (c) 2017-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. @@ -7,6 +7,9 @@ #include <stdio.h> #include <string> +#if defined WIN32 && defined __GLIBCXX__ +#include <ext/stdio_filebuf.h> +#endif #include <boost/filesystem.hpp> #include <boost/filesystem/fstream.hpp> @@ -39,6 +42,54 @@ namespace fsbridge { }; std::string get_filesystem_error_message(const fs::filesystem_error& e); + + // GNU libstdc++ specific workaround for opening UTF-8 paths on Windows. + // + // On Windows, it is only possible to reliably access multibyte file paths through + // `wchar_t` APIs, not `char` APIs. But because the C++ standard doesn't + // require ifstream/ofstream `wchar_t` constructors, and the GNU library doesn't + // provide them (in contrast to the Microsoft C++ library, see + // https://stackoverflow.com/questions/821873/how-to-open-an-stdfstream-ofstream-or-ifstream-with-a-unicode-filename/822032#822032), + // Boost is forced to fall back to `char` constructors which may not work properly. + // + // Work around this issue by creating stream objects with `_wfopen` in + // combination with `__gnu_cxx::stdio_filebuf`. This workaround can be removed + // with an upgrade to C++17, where streams can be constructed directly from + // `std::filesystem::path` objects. + +#if defined WIN32 && defined __GLIBCXX__ + class ifstream : public std::istream + { + public: + ifstream() = default; + explicit ifstream(const fs::path& p, std::ios_base::openmode mode = std::ios_base::in) { open(p, mode); } + ~ifstream() { close(); } + void open(const fs::path& p, std::ios_base::openmode mode = std::ios_base::in); + bool is_open() { return m_filebuf.is_open(); } + void close(); + + private: + __gnu_cxx::stdio_filebuf<char> m_filebuf; + FILE* m_file = nullptr; + }; + class ofstream : public std::ostream + { + public: + ofstream() = default; + explicit ofstream(const fs::path& p, std::ios_base::openmode mode = std::ios_base::out) { open(p, mode); } + ~ofstream() { close(); } + void open(const fs::path& p, std::ios_base::openmode mode = std::ios_base::out); + bool is_open() { return m_filebuf.is_open(); } + void close(); + + private: + __gnu_cxx::stdio_filebuf<char> m_filebuf; + FILE* m_file = nullptr; + }; +#else // !(WIN32 && __GLIBCXX__) + typedef fs::ifstream ifstream; + typedef fs::ofstream ofstream; +#endif // WIN32 && __GLIBCXX__ }; #endif // BITCOIN_FS_H diff --git a/src/hash.cpp b/src/hash.cpp index c049eea716..26150e5ca8 100644 --- a/src/hash.cpp +++ b/src/hash.cpp @@ -77,171 +77,3 @@ void BIP32Hash(const ChainCode &chainCode, unsigned int nChild, unsigned char he num[3] = (nChild >> 0) & 0xFF; CHMAC_SHA512(chainCode.begin(), chainCode.size()).Write(&header, 1).Write(data, 32).Write(num, 4).Finalize(output); } - -#define ROTL(x, b) (uint64_t)(((x) << (b)) | ((x) >> (64 - (b)))) - -#define SIPROUND do { \ - v0 += v1; v1 = ROTL(v1, 13); v1 ^= v0; \ - v0 = ROTL(v0, 32); \ - v2 += v3; v3 = ROTL(v3, 16); v3 ^= v2; \ - v0 += v3; v3 = ROTL(v3, 21); v3 ^= v0; \ - v2 += v1; v1 = ROTL(v1, 17); v1 ^= v2; \ - v2 = ROTL(v2, 32); \ -} while (0) - -CSipHasher::CSipHasher(uint64_t k0, uint64_t k1) -{ - v[0] = 0x736f6d6570736575ULL ^ k0; - v[1] = 0x646f72616e646f6dULL ^ k1; - v[2] = 0x6c7967656e657261ULL ^ k0; - v[3] = 0x7465646279746573ULL ^ k1; - count = 0; - tmp = 0; -} - -CSipHasher& CSipHasher::Write(uint64_t data) -{ - uint64_t v0 = v[0], v1 = v[1], v2 = v[2], v3 = v[3]; - - assert(count % 8 == 0); - - v3 ^= data; - SIPROUND; - SIPROUND; - v0 ^= data; - - v[0] = v0; - v[1] = v1; - v[2] = v2; - v[3] = v3; - - count += 8; - return *this; -} - -CSipHasher& CSipHasher::Write(const unsigned char* data, size_t size) -{ - uint64_t v0 = v[0], v1 = v[1], v2 = v[2], v3 = v[3]; - uint64_t t = tmp; - int c = count; - - while (size--) { - t |= ((uint64_t)(*(data++))) << (8 * (c % 8)); - c++; - if ((c & 7) == 0) { - v3 ^= t; - SIPROUND; - SIPROUND; - v0 ^= t; - t = 0; - } - } - - v[0] = v0; - v[1] = v1; - v[2] = v2; - v[3] = v3; - count = c; - tmp = t; - - return *this; -} - -uint64_t CSipHasher::Finalize() const -{ - uint64_t v0 = v[0], v1 = v[1], v2 = v[2], v3 = v[3]; - - uint64_t t = tmp | (((uint64_t)count) << 56); - - v3 ^= t; - SIPROUND; - SIPROUND; - v0 ^= t; - v2 ^= 0xFF; - SIPROUND; - SIPROUND; - SIPROUND; - SIPROUND; - return v0 ^ v1 ^ v2 ^ v3; -} - -uint64_t SipHashUint256(uint64_t k0, uint64_t k1, const uint256& val) -{ - /* Specialized implementation for efficiency */ - uint64_t d = val.GetUint64(0); - - uint64_t v0 = 0x736f6d6570736575ULL ^ k0; - uint64_t v1 = 0x646f72616e646f6dULL ^ k1; - uint64_t v2 = 0x6c7967656e657261ULL ^ k0; - uint64_t v3 = 0x7465646279746573ULL ^ k1 ^ d; - - SIPROUND; - SIPROUND; - v0 ^= d; - d = val.GetUint64(1); - v3 ^= d; - SIPROUND; - SIPROUND; - v0 ^= d; - d = val.GetUint64(2); - v3 ^= d; - SIPROUND; - SIPROUND; - v0 ^= d; - d = val.GetUint64(3); - v3 ^= d; - SIPROUND; - SIPROUND; - v0 ^= d; - v3 ^= ((uint64_t)4) << 59; - SIPROUND; - SIPROUND; - v0 ^= ((uint64_t)4) << 59; - v2 ^= 0xFF; - SIPROUND; - SIPROUND; - SIPROUND; - SIPROUND; - return v0 ^ v1 ^ v2 ^ v3; -} - -uint64_t SipHashUint256Extra(uint64_t k0, uint64_t k1, const uint256& val, uint32_t extra) -{ - /* Specialized implementation for efficiency */ - uint64_t d = val.GetUint64(0); - - uint64_t v0 = 0x736f6d6570736575ULL ^ k0; - uint64_t v1 = 0x646f72616e646f6dULL ^ k1; - uint64_t v2 = 0x6c7967656e657261ULL ^ k0; - uint64_t v3 = 0x7465646279746573ULL ^ k1 ^ d; - - SIPROUND; - SIPROUND; - v0 ^= d; - d = val.GetUint64(1); - v3 ^= d; - SIPROUND; - SIPROUND; - v0 ^= d; - d = val.GetUint64(2); - v3 ^= d; - SIPROUND; - SIPROUND; - v0 ^= d; - d = val.GetUint64(3); - v3 ^= d; - SIPROUND; - SIPROUND; - v0 ^= d; - d = (((uint64_t)36) << 56) | extra; - v3 ^= d; - SIPROUND; - SIPROUND; - v0 ^= d; - v2 ^= 0xFF; - SIPROUND; - SIPROUND; - SIPROUND; - SIPROUND; - return v0 ^ v1 ^ v2 ^ v3; -} diff --git a/src/hash.h b/src/hash.h index 3534a400b3..c295568a3e 100644 --- a/src/hash.h +++ b/src/hash.h @@ -6,6 +6,7 @@ #ifndef BITCOIN_HASH_H #define BITCOIN_HASH_H +#include <crypto/common.h> #include <crypto/ripemd160.h> #include <crypto/sha256.h> #include <prevector.h> @@ -138,6 +139,15 @@ public: return result; } + /** + * Returns the first 64 bits from the resulting hash. + */ + inline uint64_t GetCheapHash() { + unsigned char result[CHash256::OUTPUT_SIZE]; + ctx.Finalize(result); + return ReadLE64(result); + } + template<typename T> CHashWriter& operator<<(const T& obj) { // Serialize to this stream @@ -194,39 +204,4 @@ unsigned int MurmurHash3(unsigned int nHashSeed, const std::vector<unsigned char void BIP32Hash(const ChainCode &chainCode, unsigned int nChild, unsigned char header, const unsigned char data[32], unsigned char output[64]); -/** SipHash-2-4 */ -class CSipHasher -{ -private: - uint64_t v[4]; - uint64_t tmp; - int count; - -public: - /** Construct a SipHash calculator initialized with 128-bit key (k0, k1) */ - CSipHasher(uint64_t k0, uint64_t k1); - /** Hash a 64-bit integer worth of data - * It is treated as if this was the little-endian interpretation of 8 bytes. - * This function can only be used when a multiple of 8 bytes have been written so far. - */ - CSipHasher& Write(uint64_t data); - /** Hash arbitrary bytes. */ - CSipHasher& Write(const unsigned char* data, size_t size); - /** Compute the 64-bit SipHash-2-4 of the data written so far. The object remains untouched. */ - uint64_t Finalize() const; -}; - -/** Optimized SipHash-2-4 implementation for uint256. - * - * It is identical to: - * SipHasher(k0, k1) - * .Write(val.GetUint64(0)) - * .Write(val.GetUint64(1)) - * .Write(val.GetUint64(2)) - * .Write(val.GetUint64(3)) - * .Finalize() - */ -uint64_t SipHashUint256(uint64_t k0, uint64_t k1, const uint256& val); -uint64_t SipHashUint256Extra(uint64_t k0, uint64_t k1, const uint256& val, uint32_t extra); - #endif // BITCOIN_HASH_H diff --git a/src/httprpc.cpp b/src/httprpc.cpp index 4064251c71..fcf760a4c6 100644 --- a/src/httprpc.cpp +++ b/src/httprpc.cpp @@ -11,8 +11,8 @@ #include <rpc/server.h> #include <random.h> #include <sync.h> -#include <util.h> -#include <utilstrencodings.h> +#include <util/system.h> +#include <util/strencodings.h> #include <ui_interface.h> #include <walletinitinterface.h> #include <crypto/hmac_sha256.h> diff --git a/src/httpserver.cpp b/src/httpserver.cpp index c29f7a4375..ca60ea43a7 100644 --- a/src/httpserver.cpp +++ b/src/httpserver.cpp @@ -6,10 +6,11 @@ #include <chainparamsbase.h> #include <compat.h> -#include <util.h> -#include <utilstrencodings.h> +#include <util/system.h> +#include <util/strencodings.h> #include <netbase.h> #include <rpc/protocol.h> // For HTTP status codes +#include <shutdown.h> #include <sync.h> #include <ui_interface.h> @@ -21,7 +22,6 @@ #include <sys/types.h> #include <sys/stat.h> #include <signal.h> -#include <future> #include <event2/thread.h> #include <event2/buffer.h> @@ -124,7 +124,6 @@ public: struct HTTPPathHandler { - HTTPPathHandler() {} HTTPPathHandler(std::string _prefix, bool _exactMatch, HTTPRequestHandler _handler): prefix(_prefix), exactMatch(_exactMatch), handler(_handler) { @@ -224,21 +223,25 @@ static void http_request_cb(struct evhttp_request* req, void* arg) } std::unique_ptr<HTTPRequest> hreq(new HTTPRequest(req)); - LogPrint(BCLog::HTTP, "Received a %s request for %s from %s\n", - RequestMethodString(hreq->GetRequestMethod()), hreq->GetURI(), hreq->GetPeer().ToString()); - // Early address-based allow check if (!ClientAllowed(hreq->GetPeer())) { + LogPrint(BCLog::HTTP, "HTTP request from %s rejected: Client network is not allowed RPC access\n", + hreq->GetPeer().ToString()); hreq->WriteReply(HTTP_FORBIDDEN); return; } // Early reject unknown HTTP methods if (hreq->GetRequestMethod() == HTTPRequest::UNKNOWN) { + LogPrint(BCLog::HTTP, "HTTP request from %s rejected: Unknown HTTP request method\n", + hreq->GetPeer().ToString()); hreq->WriteReply(HTTP_BADMETHOD); return; } + LogPrint(BCLog::HTTP, "Received a %s request for %s from %s\n", + RequestMethodString(hreq->GetRequestMethod()), SanitizeString(hreq->GetURI(), SAFE_CHARS_URI).substr(0, 100), hreq->GetPeer().ToString()); + // Find registered handler for prefix std::string strURI = hreq->GetURI(); std::string path; @@ -292,26 +295,26 @@ static bool ThreadHTTP(struct event_base* base) /** Bind HTTP server to specified addresses */ static bool HTTPBindAddresses(struct evhttp* http) { - int defaultPort = gArgs.GetArg("-rpcport", BaseParams().RPCPort()); + int http_port = gArgs.GetArg("-rpcport", BaseParams().RPCPort()); std::vector<std::pair<std::string, uint16_t> > endpoints; // Determine what addresses to bind to - if (!gArgs.IsArgSet("-rpcallowip")) { // Default to loopback if not allowing external IPs - endpoints.push_back(std::make_pair("::1", defaultPort)); - endpoints.push_back(std::make_pair("127.0.0.1", defaultPort)); + if (!(gArgs.IsArgSet("-rpcallowip") && gArgs.IsArgSet("-rpcbind"))) { // Default to loopback if not allowing external IPs + endpoints.push_back(std::make_pair("::1", http_port)); + endpoints.push_back(std::make_pair("127.0.0.1", http_port)); + if (gArgs.IsArgSet("-rpcallowip")) { + LogPrintf("WARNING: option -rpcallowip was specified without -rpcbind; this doesn't usually make sense\n"); + } if (gArgs.IsArgSet("-rpcbind")) { LogPrintf("WARNING: option -rpcbind was ignored because -rpcallowip was not specified, refusing to allow everyone to connect\n"); } } else if (gArgs.IsArgSet("-rpcbind")) { // Specific bind address for (const std::string& strRPCBind : gArgs.GetArgs("-rpcbind")) { - int port = defaultPort; + int port = http_port; std::string host; SplitHostPort(strRPCBind, port, host); endpoints.push_back(std::make_pair(host, port)); } - } else { // No specific bind address specified, bind to any - endpoints.push_back(std::make_pair("::", defaultPort)); - endpoints.push_back(std::make_pair("0.0.0.0", defaultPort)); } // Bind addresses @@ -319,6 +322,10 @@ static bool HTTPBindAddresses(struct evhttp* http) LogPrint(BCLog::HTTP, "Binding RPC on address %s port %i\n", i->first, i->second); evhttp_bound_socket *bind_handle = evhttp_bind_socket_with_handle(http, i->first.empty() ? nullptr : i->first.c_str(), i->second); if (bind_handle) { + CNetAddr addr; + if (i->first.empty() || (LookupHost(i->first.c_str(), addr, false) && addr.IsBindAny())) { + LogPrintf("WARNING: the RPC server is not safe to expose to untrusted networks such as the public internet\n"); + } boundSockets.push_back(bind_handle); } else { LogPrintf("Binding RPC on address %s port %i failed.\n", i->first, i->second); @@ -413,7 +420,6 @@ bool UpdateHTTPServerLogging(bool enable) { } std::thread threadHTTP; -std::future<bool> threadResult; static std::vector<std::thread> g_thread_http_workers; void StartHTTPServer() @@ -421,9 +427,7 @@ void StartHTTPServer() LogPrint(BCLog::HTTP, "Starting HTTP server\n"); int rpcThreads = std::max((long)gArgs.GetArg("-rpcthreads", DEFAULT_HTTP_THREADS), 1L); LogPrintf("HTTP: starting %d worker threads\n", rpcThreads); - std::packaged_task<bool(event_base*)> task(ThreadHTTP); - threadResult = task.get_future(); - threadHTTP = std::thread(std::move(task), eventBase); + threadHTTP = std::thread(ThreadHTTP, eventBase); for (int i = 0; i < rpcThreads; i++) { g_thread_http_workers.emplace_back(HTTPWorkQueueRun, workQueue); @@ -434,10 +438,6 @@ void InterruptHTTPServer() { LogPrint(BCLog::HTTP, "Interrupting HTTP server\n"); if (eventHTTP) { - // Unlisten sockets - for (evhttp_bound_socket *socket : boundSockets) { - evhttp_del_accept_socket(eventHTTP, socket); - } // Reject requests on current connections evhttp_set_gencb(eventHTTP, http_reject_request_cb, nullptr); } @@ -457,20 +457,14 @@ void StopHTTPServer() delete workQueue; workQueue = nullptr; } + // Unlisten sockets, these are what make the event loop running, which means + // that after this and all connections are closed the event loop will quit. + for (evhttp_bound_socket *socket : boundSockets) { + evhttp_del_accept_socket(eventHTTP, socket); + } + boundSockets.clear(); if (eventBase) { LogPrint(BCLog::HTTP, "Waiting for HTTP event thread to exit\n"); - // Exit the event loop as soon as there are no active events. - event_base_loopexit(eventBase, nullptr); - // Give event loop a few seconds to exit (to send back last RPC responses), then break it - // Before this was solved with event_base_loopexit, but that didn't work as expected in - // at least libevent 2.0.21 and always introduced a delay. In libevent - // master that appears to be solved, so in the future that solution - // could be used again (if desirable). - // (see discussion in https://github.com/bitcoin/bitcoin/pull/6990) - if (threadResult.valid() && threadResult.wait_for(std::chrono::milliseconds(2000)) == std::future_status::timeout) { - LogPrintf("HTTP event loop did not exit within allotted time, sending loopbreak\n"); - event_base_loopbreak(eventBase); - } threadHTTP.join(); } if (eventHTTP) { @@ -575,6 +569,9 @@ void HTTPRequest::WriteHeader(const std::string& hdr, const std::string& value) void HTTPRequest::WriteReply(int nStatus, const std::string& strReply) { assert(!replySent && req); + if (ShutdownRequested()) { + WriteHeader("Connection", "close"); + } // Send event to main http thread to send reply message struct evbuffer* evb = evhttp_request_get_output_buffer(req); assert(evb); diff --git a/src/index/base.cpp b/src/index/base.cpp index 788f7adccd..f6f59572ce 100644 --- a/src/index/base.cpp +++ b/src/index/base.cpp @@ -7,7 +7,7 @@ #include <shutdown.h> #include <tinyformat.h> #include <ui_interface.h> -#include <util.h> +#include <util/system.h> #include <validation.h> #include <warnings.h> @@ -60,12 +60,16 @@ bool BaseIndex::Init() } LOCK(cs_main); - m_best_block_index = FindForkInGlobalIndex(chainActive, locator); + if (locator.IsNull()) { + m_best_block_index = nullptr; + } else { + m_best_block_index = FindForkInGlobalIndex(chainActive, locator); + } m_synced = m_best_block_index.load() == chainActive.Tip(); return true; } -static const CBlockIndex* NextSyncBlock(const CBlockIndex* pindex_prev) +static const CBlockIndex* NextSyncBlock(const CBlockIndex* pindex_prev) EXCLUSIVE_LOCKS_REQUIRED(cs_main) { AssertLockHeld(cs_main); diff --git a/src/index/txindex.cpp b/src/index/txindex.cpp index f606c8993c..10bc8419dd 100644 --- a/src/index/txindex.cpp +++ b/src/index/txindex.cpp @@ -5,7 +5,7 @@ #include <index/txindex.h> #include <shutdown.h> #include <ui_interface.h> -#include <util.h> +#include <util/system.h> #include <validation.h> #include <boost/thread.hpp> @@ -245,6 +245,9 @@ bool TxIndex::Init() bool TxIndex::WriteBlock(const CBlock& block, const CBlockIndex* pindex) { + // Exclude genesis block transaction because outputs are not spendable. + if (pindex->nHeight == 0) return true; + CDiskTxPos pos(pindex->GetBlockPos(), GetSizeOfCompactSize(block.vtx.size())); std::vector<std::pair<uint256, CDiskTxPos>> vPos; vPos.reserve(block.vtx.size()); diff --git a/src/init.cpp b/src/init.cpp index f9efaf7dc1..77d0505610 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -11,6 +11,7 @@ #include <addrman.h> #include <amount.h> +#include <banman.h> #include <chain.h> #include <chainparams.h> #include <checkpoints.h> @@ -19,6 +20,7 @@ #include <fs.h> #include <httpserver.h> #include <httprpc.h> +#include <interfaces/chain.h> #include <index/txindex.h> #include <key.h> #include <validation.h> @@ -32,6 +34,7 @@ #include <rpc/server.h> #include <rpc/register.h> #include <rpc/blockchain.h> +#include <rpc/util.h> #include <script/standard.h> #include <script/sigcache.h> #include <scheduler.h> @@ -41,8 +44,8 @@ #include <txmempool.h> #include <torcontrol.h> #include <ui_interface.h> -#include <util.h> -#include <utilmoneystr.h> +#include <util/system.h> +#include <util/moneystr.h> #include <validationinterface.h> #include <warnings.h> #include <walletinitinterface.h> @@ -57,11 +60,11 @@ #include <boost/algorithm/string/classification.hpp> #include <boost/algorithm/string/replace.hpp> #include <boost/algorithm/string/split.hpp> -#include <boost/bind.hpp> #include <boost/thread.hpp> #include <openssl/crypto.h> #if ENABLE_ZMQ +#include <zmq/zmqabstractnotifier.h> #include <zmq/zmqnotificationinterface.h> #include <zmq/zmqrpc.h> #endif @@ -71,8 +74,12 @@ static const bool DEFAULT_PROXYRANDOMIZE = true; static const bool DEFAULT_REST_ENABLE = false; static const bool DEFAULT_STOPAFTERBLOCKIMPORT = false; +// Dump addresses to banlist.dat every 15 minutes (900s) +static constexpr int DUMP_BANS_INTERVAL = 60 * 15; + std::unique_ptr<CConnman> g_connman; std::unique_ptr<PeerLogicValidation> peerLogic; +std::unique_ptr<BanMan> g_banman; #ifdef WIN32 // Win32 LevelDB doesn't use filedescriptors, and the ones used for @@ -156,7 +163,7 @@ void Interrupt() } } -void Shutdown() +void Shutdown(InitInterfaces& interfaces) { LogPrintf("%s: In progress...\n", __func__); static CCriticalSection cs_Shutdown; @@ -175,7 +182,9 @@ void Shutdown() StopREST(); StopRPC(); StopHTTPServer(); - g_wallet_init_interface.Flush(); + for (const auto& client : interfaces.chain_clients) { + client->flush(); + } StopMapPort(); // Because these depend on each-other, we make sure that neither can be @@ -195,6 +204,7 @@ void Shutdown() // destruct and reset all to nullptr. peerLogic.reset(); g_connman.reset(); + g_banman.reset(); g_txindex.reset(); if (g_is_mempool_loaded && gArgs.GetArg("-persistmempool", DEFAULT_PERSIST_MEMPOOL)) { @@ -238,7 +248,9 @@ void Shutdown() pcoinsdbview.reset(); pblocktree.reset(); } - g_wallet_init_interface.Stop(); + for (const auto& client : interfaces.chain_clients) { + client->stop(); + } #if ENABLE_ZMQ if (g_zmq_notification_interface) { @@ -255,10 +267,10 @@ void Shutdown() LogPrintf("%s: Unable to remove pidfile: %s\n", __func__, e.what()); } #endif + interfaces.chain_clients.clear(); UnregisterAllValidationInterfaces(); GetMainSignals().UnregisterBackgroundSignalScheduler(); GetMainSignals().UnregisterWithMempoolSignals(mempool); - g_wallet_init_interface.Close(); globalVerifyHandle.reset(); ECC_Stop(); LogPrintf("%s: done\n", __func__); @@ -396,6 +408,7 @@ void SetupServerArgs() gArgs.AddArg("-proxyrandomize", strprintf("Randomize credentials for every proxy connection. This enables Tor stream isolation (default: %u)", DEFAULT_PROXYRANDOMIZE), false, OptionsCategory::CONNECTION); gArgs.AddArg("-seednode=<ip>", "Connect to a node to retrieve peer addresses, and disconnect. This option can be specified multiple times to connect to multiple nodes.", false, OptionsCategory::CONNECTION); gArgs.AddArg("-timeout=<n>", strprintf("Specify connection timeout in milliseconds (minimum: 1, default: %d)", DEFAULT_CONNECT_TIMEOUT), false, OptionsCategory::CONNECTION); + gArgs.AddArg("-peertimeout=<n>", strprintf("Specify p2p connection timeout in seconds. This option determines the amount of time a peer may be inactive before the connection to it is dropped. (minimum: 1, default: %d)", DEFAULT_PEER_CONNECT_TIMEOUT), true, OptionsCategory::CONNECTION); gArgs.AddArg("-torcontrol=<ip>:<port>", strprintf("Tor control port to use if onion listening enabled (default: %s)", DEFAULT_TOR_CONTROL), false, OptionsCategory::CONNECTION); gArgs.AddArg("-torpassword=<pass>", "Tor control port password (default: empty)", false, OptionsCategory::CONNECTION); #ifdef USE_UPNP @@ -418,11 +431,19 @@ void SetupServerArgs() gArgs.AddArg("-zmqpubhashtx=<address>", "Enable publish hash transaction in <address>", false, OptionsCategory::ZMQ); gArgs.AddArg("-zmqpubrawblock=<address>", "Enable publish raw block in <address>", false, OptionsCategory::ZMQ); gArgs.AddArg("-zmqpubrawtx=<address>", "Enable publish raw transaction in <address>", false, OptionsCategory::ZMQ); + gArgs.AddArg("-zmqpubhashblockhwm=<n>", strprintf("Set publish hash block outbound message high water mark (default: %d)", CZMQAbstractNotifier::DEFAULT_ZMQ_SNDHWM), false, OptionsCategory::ZMQ); + gArgs.AddArg("-zmqpubhashtxhwm=<n>", strprintf("Set publish hash transaction outbound message high water mark (default: %d)", CZMQAbstractNotifier::DEFAULT_ZMQ_SNDHWM), false, OptionsCategory::ZMQ); + gArgs.AddArg("-zmqpubrawblockhwm=<n>", strprintf("Set publish raw block outbound message high water mark (default: %d)", CZMQAbstractNotifier::DEFAULT_ZMQ_SNDHWM), false, OptionsCategory::ZMQ); + gArgs.AddArg("-zmqpubrawtxhwm=<n>", strprintf("Set publish raw transaction outbound message high water mark (default: %d)", CZMQAbstractNotifier::DEFAULT_ZMQ_SNDHWM), false, OptionsCategory::ZMQ); #else hidden_args.emplace_back("-zmqpubhashblock=<address>"); hidden_args.emplace_back("-zmqpubhashtx=<address>"); hidden_args.emplace_back("-zmqpubrawblock=<address>"); hidden_args.emplace_back("-zmqpubrawtx=<address>"); + hidden_args.emplace_back("-zmqpubhashblockhwm=<n>"); + hidden_args.emplace_back("-zmqpubhashtxhwm=<n>"); + hidden_args.emplace_back("-zmqpubrawblockhwm=<n>"); + hidden_args.emplace_back("-zmqpubrawtxhwm=<n>"); #endif gArgs.AddArg("-checkblocks=<n>", strprintf("How many blocks to check at startup (default: %u, 0 = all)", DEFAULT_CHECKBLOCKS), true, OptionsCategory::DEBUG_TEST); @@ -484,8 +505,8 @@ void SetupServerArgs() gArgs.AddArg("-rest", strprintf("Accept public REST requests (default: %u)", DEFAULT_REST_ENABLE), false, OptionsCategory::RPC); gArgs.AddArg("-rpcallowip=<ip>", "Allow JSON-RPC connections from specified source. Valid for <ip> are a single IP (e.g. 1.2.3.4), a network/netmask (e.g. 1.2.3.4/255.255.255.0) or a network/CIDR (e.g. 1.2.3.4/24). This option can be specified multiple times", false, OptionsCategory::RPC); - gArgs.AddArg("-rpcauth=<userpw>", "Username and hashed password for JSON-RPC connections. The field <userpw> comes in the format: <USERNAME>:<SALT>$<HASH>. A canonical python script is included in share/rpcauth. The client then connects normally using the rpcuser=<USERNAME>/rpcpassword=<PASSWORD> pair of arguments. This option can be specified multiple times", false, OptionsCategory::RPC); - gArgs.AddArg("-rpcbind=<addr>[:port]", "Bind to given address to listen for JSON-RPC connections. This option is ignored unless -rpcallowip is also passed. Port is optional and overrides -rpcport. Use [host]:port notation for IPv6. This option can be specified multiple times (default: 127.0.0.1 and ::1 i.e., localhost, or if -rpcallowip has been specified, 0.0.0.0 and :: i.e., all addresses)", false, OptionsCategory::RPC); + gArgs.AddArg("-rpcauth=<userpw>", "Username and HMAC-SHA-256 hashed password for JSON-RPC connections. The field <userpw> comes in the format: <USERNAME>:<SALT>$<HASH>. A canonical python script is included in share/rpcauth. The client then connects normally using the rpcuser=<USERNAME>/rpcpassword=<PASSWORD> pair of arguments. This option can be specified multiple times", false, OptionsCategory::RPC); + gArgs.AddArg("-rpcbind=<addr>[:port]", "Bind to given address to listen for JSON-RPC connections. Do not expose the RPC server to untrusted networks such as the public internet! This option is ignored unless -rpcallowip is also passed. Port is optional and overrides -rpcport. Use [host]:port notation for IPv6. This option can be specified multiple times (default: 127.0.0.1 and ::1 i.e., localhost)", false, OptionsCategory::RPC); gArgs.AddArg("-rpccookiefile=<loc>", "Location of the auth cookie. Relative paths will be prefixed by a net-specific datadir location. (default: data dir)", false, OptionsCategory::RPC); gArgs.AddArg("-rpcpassword=<pw>", "Password for JSON-RPC connections", false, OptionsCategory::RPC); gArgs.AddArg("-rpcport=<port>", strprintf("Listen for JSON-RPC connections on <port> (default: %u, testnet: %u, regtest: %u)", defaultBaseParams->RPCPort(), testnetBaseParams->RPCPort(), regtestBaseParams->RPCPort()), false, OptionsCategory::RPC); @@ -788,7 +809,15 @@ void InitParameterInteraction() // Warn if network-specific options (-addnode, -connect, etc) are // specified in default section of config file, but not overridden // on the command line or in this network's section of the config file. - gArgs.WarnForSectionOnlyArgs(); + std::string network = gArgs.GetChainName(); + for (const auto& arg : gArgs.GetUnsuitableSectionOnlyArgs()) { + InitWarning(strprintf(_("Config setting for %s only applied on %s network when in [%s] section."), arg, network, network)); + } + + // Warn if unrecognized section name are present in the config file. + for (const auto& section : gArgs.GetUnrecognizedSections()) { + InitWarning(strprintf(_("Section [%s] is not recognized."), section)); + } } static std::string ResolveErrMsg(const char * const optname, const std::string& strBind) @@ -833,6 +862,7 @@ int nMaxConnections; int nUserMaxConnections; int nFD; ServiceFlags nLocalServices = ServiceFlags(NODE_NETWORK | NODE_NETWORK_LIMITED); +int64_t peer_connect_timeout; } // namespace @@ -906,7 +936,7 @@ bool AppInitParameterInteraction() // also see: InitParameterInteraction() - if (!fs::is_directory(GetBlocksDir(false))) { + if (!fs::is_directory(GetBlocksDir())) { return InitError(strprintf(_("Specified blocks directory \"%s\" does not exist."), gArgs.GetArg("-blocksdir", "").c_str())); } @@ -929,8 +959,13 @@ bool AppInitParameterInteraction() // Trim requested connection counts, to fit into system limitations // <int> in std::min<int>(...) to work around FreeBSD compilation issue described in #2695 - nMaxConnections = std::max(std::min<int>(nMaxConnections, FD_SETSIZE - nBind - MIN_CORE_FILEDESCRIPTORS - MAX_ADDNODE_CONNECTIONS), 0); nFD = RaiseFileDescriptorLimit(nMaxConnections + MIN_CORE_FILEDESCRIPTORS + MAX_ADDNODE_CONNECTIONS); +#ifdef USE_POLL + int fd_max = nFD; +#else + int fd_max = FD_SETSIZE; +#endif + nMaxConnections = std::max(std::min<int>(nMaxConnections, fd_max - nBind - MIN_CORE_FILEDESCRIPTORS - MAX_ADDNODE_CONNECTIONS), 0); if (nFD < MIN_CORE_FILEDESCRIPTORS) return InitError(_("Not enough file descriptors available.")); nMaxConnections = std::min(nFD - MIN_CORE_FILEDESCRIPTORS - MAX_ADDNODE_CONNECTIONS, nMaxConnections); @@ -1031,8 +1066,14 @@ bool AppInitParameterInteraction() } nConnectTimeout = gArgs.GetArg("-timeout", DEFAULT_CONNECT_TIMEOUT); - if (nConnectTimeout <= 0) + if (nConnectTimeout <= 0) { nConnectTimeout = DEFAULT_CONNECT_TIMEOUT; + } + + peer_connect_timeout = gArgs.GetArg("-peertimeout", DEFAULT_PEER_CONNECT_TIMEOUT); + if (peer_connect_timeout <= 0) { + return InitError("peertimeout cannot be configured with a negative value."); + } if (gArgs.IsArgSet("-minrelaytxfee")) { CAmount n = 0; @@ -1149,7 +1190,7 @@ bool AppInitLockDataDirectory() return true; } -bool AppInitMain() +bool AppInitMain(InitInterfaces& interfaces) { const CChainParams& chainparams = Params(); // ********************************************************* Step 4a: application initialization @@ -1206,17 +1247,26 @@ bool AppInitMain() } // Start the lightweight task scheduler thread - CScheduler::Function serviceLoop = boost::bind(&CScheduler::serviceQueue, &scheduler); - threadGroup.create_thread(boost::bind(&TraceThread<CScheduler::Function>, "scheduler", serviceLoop)); + CScheduler::Function serviceLoop = std::bind(&CScheduler::serviceQueue, &scheduler); + threadGroup.create_thread(std::bind(&TraceThread<CScheduler::Function>, "scheduler", serviceLoop)); GetMainSignals().RegisterBackgroundSignalScheduler(scheduler); GetMainSignals().RegisterWithMempoolSignals(mempool); + // Create client interfaces for wallets that are supposed to be loaded + // according to -wallet and -disablewallet options. This only constructs + // the interfaces, it doesn't load wallet data. Wallets actually get loaded + // when load() and start() interface methods are called below. + g_wallet_init_interface.Construct(interfaces); + /* Register RPC commands regardless of -server setting so they will be * available in the GUI RPC console even if external calls are disabled. */ RegisterAllCoreRPCCommands(tableRPC); - g_wallet_init_interface.RegisterRPC(tableRPC); + for (const auto& client : interfaces.chain_clients) { + client->registerRpcs(); + } + g_rpc_interfaces = &interfaces; #if ENABLE_ZMQ RegisterZMQRPCCommands(tableRPC); #endif @@ -1234,7 +1284,11 @@ bool AppInitMain() } // ********************************************************* Step 5: verify wallet database integrity - if (!g_wallet_init_interface.Verify()) return false; + for (const auto& client : interfaces.chain_clients) { + if (!client->verify()) { + return false; + } + } // ********************************************************* Step 6: network initialization // Note that we absolutely cannot open any actual connections @@ -1242,11 +1296,12 @@ bool AppInitMain() // is not yet setup and may end up being set up twice if we // need to reindex later. + assert(!g_banman); + g_banman = MakeUnique<BanMan>(GetDataDir() / "banlist.dat", &uiInterface, gArgs.GetArg("-bantime", DEFAULT_MISBEHAVING_BANTIME)); assert(!g_connman); g_connman = std::unique_ptr<CConnman>(new CConnman(GetRand(std::numeric_limits<uint64_t>::max()), GetRand(std::numeric_limits<uint64_t>::max()))); - CConnman& connman = *g_connman; - peerLogic.reset(new PeerLogicValidation(&connman, scheduler, gArgs.GetBoolArg("-enablebip61", DEFAULT_ENABLE_BIP61))); + peerLogic.reset(new PeerLogicValidation(g_connman.get(), g_banman.get(), scheduler, gArgs.GetBoolArg("-enablebip61", DEFAULT_ENABLE_BIP61))); RegisterValidationInterface(peerLogic.get()); // sanitize comments per BIP-0014, format user agent and check total size @@ -1273,7 +1328,7 @@ bool AppInitMain() for (int n = 0; n < NET_MAX; n++) { enum Network net = (enum Network)n; if (!nets.count(net)) - SetLimited(net); + SetReachable(net, false); } } @@ -1284,7 +1339,7 @@ bool AppInitMain() // -proxy sets a proxy for all outgoing network traffic // -noproxy (or -proxy=0) as well as the empty string can be used to not set a proxy, this is the default std::string proxyArg = gArgs.GetArg("-proxy", ""); - SetLimited(NET_ONION); + SetReachable(NET_ONION, false); if (proxyArg != "" && proxyArg != "0") { CService proxyAddr; if (!Lookup(proxyArg.c_str(), proxyAddr, 9050, fNameLookup)) { @@ -1299,7 +1354,7 @@ bool AppInitMain() SetProxy(NET_IPV6, addrProxy); SetProxy(NET_ONION, addrProxy); SetNameProxy(addrProxy); - SetLimited(NET_ONION, false); // by default, -proxy sets onion as reachable, unless -noonion later + SetReachable(NET_ONION, true); // by default, -proxy sets onion as reachable, unless -noonion later } // -onion can be used to set only a proxy for .onion, or override normal proxy for .onion addresses @@ -1308,7 +1363,7 @@ bool AppInitMain() std::string onionArg = gArgs.GetArg("-onion", ""); if (onionArg != "") { if (onionArg == "0") { // Handle -noonion/-onion=0 - SetLimited(NET_ONION); // set onions as unreachable + SetReachable(NET_ONION, false); } else { CService onionProxy; if (!Lookup(onionArg.c_str(), onionProxy, 9050, fNameLookup)) { @@ -1318,7 +1373,7 @@ bool AppInitMain() if (!addrOnion.IsValid()) return InitError(strprintf(_("Invalid -onion address or hostname: '%s'"), onionArg)); SetProxy(NET_ONION, addrOnion); - SetLimited(NET_ONION, false); + SetReachable(NET_ONION, true); } } @@ -1553,7 +1608,11 @@ bool AppInitMain() } // ********************************************************* Step 9: load wallet - if (!g_wallet_init_interface.Open()) return false; + for (const auto& client : interfaces.chain_clients) { + if (!client->load()) { + return false; + } + } // ********************************************************* Step 10: data directory maintenance @@ -1579,8 +1638,14 @@ bool AppInitMain() // ********************************************************* Step 11: import blocks - if (!CheckDiskSpace() && !CheckDiskSpace(0, true)) + if (!CheckDiskSpace(/* additional_bytes */ 0, /* blocks_dir */ false)) { + InitError(strprintf(_("Error: Disk space is low for %s"), GetDataDir())); return false; + } + if (!CheckDiskSpace(/* additional_bytes */ 0, /* blocks_dir */ true)) { + InitError(strprintf(_("Error: Disk space is low for %s"), GetBlocksDir())); + return false; + } // Either install a handler to notify us when genesis activates, or set fHaveGenesis directly. // No locking, as this happens before any background thread is started. @@ -1598,7 +1663,7 @@ bool AppInitMain() vImportFiles.push_back(strFile); } - threadGroup.create_thread(boost::bind(&ThreadImport, vImportFiles)); + threadGroup.create_thread(std::bind(&ThreadImport, vImportFiles)); // Wait for genesis block to be processed { @@ -1646,6 +1711,7 @@ bool AppInitMain() connOptions.nMaxFeeler = 1; connOptions.nBestHeight = chain_active_height; connOptions.uiInterface = &uiInterface; + connOptions.m_banman = g_banman.get(); connOptions.m_msgproc = peerLogic.get(); connOptions.nSendBufferMaxSize = 1000*gArgs.GetArg("-maxsendbuffer", DEFAULT_MAXSENDBUFFER); connOptions.nReceiveFloodSize = 1000*gArgs.GetArg("-maxreceivebuffer", DEFAULT_MAXRECEIVEBUFFER); @@ -1653,6 +1719,7 @@ bool AppInitMain() connOptions.nMaxOutboundTimeframe = nMaxOutboundTimeframe; connOptions.nMaxOutboundLimit = nMaxOutboundLimit; + connOptions.m_peer_connect_timeout = peer_connect_timeout; for (const std::string& strBind : gArgs.GetArgs("-bind")) { CService addrBind; @@ -1690,7 +1757,7 @@ bool AppInitMain() connOptions.m_specified_outgoing = connect; } } - if (!connman.Start(scheduler, connOptions)) { + if (!g_connman->Start(scheduler, connOptions)) { return false; } @@ -1699,7 +1766,13 @@ bool AppInitMain() SetRPCWarmupFinished(); uiInterface.InitMessage(_("Done loading")); - g_wallet_init_interface.Start(scheduler); + for (const auto& client : interfaces.chain_clients) { + client->start(scheduler); + } + + scheduler.scheduleEvery([]{ + g_banman->DumpBanlist(); + }, DUMP_BANS_INTERVAL * 1000); return true; } diff --git a/src/init.h b/src/init.h index c58ba5cfd3..1c59ca069e 100644 --- a/src/init.h +++ b/src/init.h @@ -8,10 +8,19 @@ #include <memory> #include <string> -#include <util.h> +#include <util/system.h> -class CScheduler; -class CWallet; +namespace interfaces { +class Chain; +class ChainClient; +} // namespace interfaces + +//! Pointers to interfaces used during init and destroyed on shutdown. +struct InitInterfaces +{ + std::unique_ptr<interfaces::Chain> chain; + std::vector<std::unique_ptr<interfaces::ChainClient>> chain_clients; +}; namespace boost { @@ -20,7 +29,7 @@ class thread_group; /** Interrupt threads */ void Interrupt(); -void Shutdown(); +void Shutdown(InitInterfaces& interfaces); //!Initialize the logging infrastructure void InitLogging(); //!Parameter interaction: change current parameters depending on various rules @@ -54,7 +63,7 @@ bool AppInitLockDataDirectory(); * @note This should only be done after daemonization. Call Shutdown() if this function fails. * @pre Parameters should be parsed and config file should be read, AppInitLockDataDirectory should have been called. */ -bool AppInitMain(); +bool AppInitMain(InitInterfaces& interfaces); /** * Setup the arguments for gArgs diff --git a/src/interfaces/README.md b/src/interfaces/README.md index e93b91d23c..57d41df746 100644 --- a/src/interfaces/README.md +++ b/src/interfaces/README.md @@ -4,7 +4,7 @@ The following interfaces are defined here: * [`Chain`](chain.h) — used by wallet to access blockchain and mempool state. Added in [#10973](https://github.com/bitcoin/bitcoin/pull/10973). -* [`Chain::Client`](chain.h) — used by node to start & stop `Chain` clients. Added in [#10973](https://github.com/bitcoin/bitcoin/pull/10973). +* [`ChainClient`](chain.h) — used by node to start & stop `Chain` clients. Added in [#10973](https://github.com/bitcoin/bitcoin/pull/10973). * [`Node`](node.h) — used by GUI to start & stop bitcoin node. Added in [#10244](https://github.com/bitcoin/bitcoin/pull/10244). diff --git a/src/interfaces/chain.cpp b/src/interfaces/chain.cpp new file mode 100644 index 0000000000..2571a91031 --- /dev/null +++ b/src/interfaces/chain.cpp @@ -0,0 +1,44 @@ +// Copyright (c) 2018 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include <interfaces/chain.h> + +#include <sync.h> +#include <util/system.h> +#include <validation.h> + +#include <memory> +#include <utility> + +namespace interfaces { +namespace { + +class LockImpl : public Chain::Lock +{ +}; + +class LockingStateImpl : public LockImpl, public UniqueLock<CCriticalSection> +{ + using UniqueLock::UniqueLock; +}; + +class ChainImpl : public Chain +{ +public: + std::unique_ptr<Chain::Lock> lock(bool try_lock) override + { + auto result = MakeUnique<LockingStateImpl>(::cs_main, "cs_main", __FILE__, __LINE__, try_lock); + if (try_lock && result && !*result) return {}; + // std::move necessary on some compilers due to conversion from + // LockingStateImpl to Lock pointer + return std::move(result); + } + std::unique_ptr<Chain::Lock> assumeLocked() override { return MakeUnique<LockImpl>(); } +}; + +} // namespace + +std::unique_ptr<Chain> MakeChain() { return MakeUnique<ChainImpl>(); } + +} // namespace interfaces diff --git a/src/interfaces/chain.h b/src/interfaces/chain.h new file mode 100644 index 0000000000..fe5658de4b --- /dev/null +++ b/src/interfaces/chain.h @@ -0,0 +1,84 @@ +// Copyright (c) 2018 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_INTERFACES_CHAIN_H +#define BITCOIN_INTERFACES_CHAIN_H + +#include <memory> +#include <string> +#include <vector> + +class CScheduler; + +namespace interfaces { + +//! Interface for giving wallet processes access to blockchain state. +class Chain +{ +public: + virtual ~Chain() {} + + //! Interface for querying locked chain state, used by legacy code that + //! assumes state won't change between calls. New code should avoid using + //! the Lock interface and instead call higher-level Chain methods + //! that return more information so the chain doesn't need to stay locked + //! between calls. + class Lock + { + public: + virtual ~Lock() {} + }; + + //! Return Lock interface. Chain is locked when this is called, and + //! unlocked when the returned interface is freed. + virtual std::unique_ptr<Lock> lock(bool try_lock = false) = 0; + + //! Return Lock interface assuming chain is already locked. This + //! method is temporary and is only used in a few places to avoid changing + //! behavior while code is transitioned to use the Chain::Lock interface. + virtual std::unique_ptr<Lock> assumeLocked() = 0; +}; + +//! Interface to let node manage chain clients (wallets, or maybe tools for +//! monitoring and analysis in the future). +class ChainClient +{ +public: + virtual ~ChainClient() {} + + //! Register rpcs. + virtual void registerRpcs() = 0; + + //! Check for errors before loading. + virtual bool verify() = 0; + + //! Load saved state. + virtual bool load() = 0; + + //! Start client execution and provide a scheduler. + virtual void start(CScheduler& scheduler) = 0; + + //! Save state to disk. + virtual void flush() = 0; + + //! Shut down client. + virtual void stop() = 0; +}; + +//! Return implementation of Chain interface. +std::unique_ptr<Chain> MakeChain(); + +//! Return implementation of ChainClient interface for a wallet client. This +//! function will be undefined in builds where ENABLE_WALLET is false. +//! +//! Currently, wallets are the only chain clients. But in the future, other +//! types of chain clients could be added, such as tools for monitoring, +//! analysis, or fee estimation. These clients need to expose their own +//! MakeXXXClient functions returning their implementations of the ChainClient +//! interface. +std::unique_ptr<ChainClient> MakeWalletClient(Chain& chain, std::vector<std::string> wallet_filenames); + +} // namespace interfaces + +#endif // BITCOIN_INTERFACES_CHAIN_H diff --git a/src/interfaces/handler.cpp b/src/interfaces/handler.cpp index ddf9b342d8..92601fc4e9 100644 --- a/src/interfaces/handler.cpp +++ b/src/interfaces/handler.cpp @@ -4,7 +4,7 @@ #include <interfaces/handler.h> -#include <utilmemory.h> +#include <util/memory.h> #include <boost/signals2/connection.hpp> #include <utility> diff --git a/src/interfaces/node.cpp b/src/interfaces/node.cpp index d95b41657c..c574f960e6 100644 --- a/src/interfaces/node.cpp +++ b/src/interfaces/node.cpp @@ -6,9 +6,11 @@ #include <addrdb.h> #include <amount.h> +#include <banman.h> #include <chain.h> #include <chainparams.h> #include <init.h> +#include <interfaces/chain.h> #include <interfaces/handler.h> #include <interfaces/wallet.h> #include <net.h> @@ -25,7 +27,7 @@ #include <sync.h> #include <txmempool.h> #include <ui_interface.h> -#include <util.h> +#include <util/system.h> #include <validation.h> #include <warnings.h> @@ -34,10 +36,11 @@ #endif #include <atomic> -#include <boost/thread/thread.hpp> #include <univalue.h> class CWallet; +fs::path GetWalletDir(); +std::vector<fs::path> ListWalletDir(); std::vector<std::shared_ptr<CWallet>> GetWallets(); namespace interfaces { @@ -48,6 +51,8 @@ namespace { class NodeImpl : public Node { +public: + NodeImpl() { m_interfaces.chain = MakeChain(); } bool parseParameters(int argc, const char* const argv[], std::string& error) override { return gArgs.ParseParameters(argc, argv, error); @@ -56,6 +61,8 @@ class NodeImpl : public Node bool softSetArg(const std::string& arg, const std::string& value) override { return gArgs.SoftSetArg(arg, value); } bool softSetBoolArg(const std::string& arg, bool value) override { return gArgs.SoftSetBoolArg(arg, value); } void selectParams(const std::string& network) override { SelectParams(network); } + uint64_t getAssumedBlockchainSize() override { return Params().AssumedBlockchainSize(); } + uint64_t getAssumedChainStateSize() override { return Params().AssumedChainStateSize(); } std::string getNetwork() override { return Params().NetworkIDString(); } void initLogging() override { InitLogging(); } void initParameterInteraction() override { InitParameterInteraction(); } @@ -66,11 +73,11 @@ class NodeImpl : public Node return AppInitBasicSetup() && AppInitParameterInteraction() && AppInitSanityChecks() && AppInitLockDataDirectory(); } - bool appInitMain() override { return AppInitMain(); } + bool appInitMain() override { return AppInitMain(m_interfaces); } void appShutdown() override { Interrupt(); - Shutdown(); + Shutdown(m_interfaces); } void startShutdown() override { StartShutdown(); } bool shutdownRequested() override { return ShutdownRequested(); } @@ -116,28 +123,35 @@ class NodeImpl : public Node } bool getBanned(banmap_t& banmap) override { - if (g_connman) { - g_connman->GetBanned(banmap); + if (g_banman) { + g_banman->GetBanned(banmap); return true; } return false; } bool ban(const CNetAddr& net_addr, BanReason reason, int64_t ban_time_offset) override { - if (g_connman) { - g_connman->Ban(net_addr, reason, ban_time_offset); + if (g_banman) { + g_banman->Ban(net_addr, reason, ban_time_offset); return true; } return false; } bool unban(const CSubNet& ip) override { - if (g_connman) { - g_connman->Unban(ip); + if (g_banman) { + g_banman->Unban(ip); return true; } return false; } + bool disconnect(const CNetAddr& net_addr) override + { + if (g_connman) { + return g_connman->DisconnectNode(net_addr); + } + return false; + } bool disconnect(NodeId id) override { if (g_connman) { @@ -218,6 +232,18 @@ class NodeImpl : public Node LOCK(::cs_main); return ::pcoinsTip->GetCoin(output, coin); } + std::string getWalletDir() override + { + return GetWalletDir().string(); + } + std::vector<std::string> listWalletDir() override + { + std::vector<std::string> paths; + for (auto& path : ListWalletDir()) { + paths.push_back(path.string()); + } + return paths; + } std::vector<std::unique_ptr<Wallet>> getWallets() override { std::vector<std::unique_ptr<Wallet>> wallets; @@ -277,6 +303,7 @@ class NodeImpl : public Node GuessVerificationProgress(Params().TxData(), block)); })); } + InitInterfaces m_interfaces; }; } // namespace diff --git a/src/interfaces/node.h b/src/interfaces/node.h index 8185c015a9..54c2d78338 100644 --- a/src/interfaces/node.h +++ b/src/interfaces/node.h @@ -18,6 +18,7 @@ #include <tuple> #include <vector> +class BanMan; class CCoinControl; class CFeeRate; class CNodeStats; @@ -52,6 +53,12 @@ public: //! Choose network parameters. virtual void selectParams(const std::string& network) = 0; + //! Get the (assumed) blockchain size. + virtual uint64_t getAssumedBlockchainSize() = 0; + + //! Get the (assumed) chain state size. + virtual uint64_t getAssumedChainStateSize() = 0; + //! Get network name. virtual std::string getNetwork() = 0; @@ -107,7 +114,10 @@ public: //! Unban node. virtual bool unban(const CSubNet& ip) = 0; - //! Disconnect node. + //! Disconnect node by address. + virtual bool disconnect(const CNetAddr& net_addr) = 0; + + //! Disconnect node by id. virtual bool disconnect(NodeId id) = 0; //! Get total bytes recv. @@ -173,6 +183,12 @@ public: //! Get unspent outputs associated with a transaction. virtual bool getUnspentOutput(const COutPoint& output, Coin& coin) = 0; + //! Return default wallet directory. + virtual std::string getWalletDir() = 0; + + //! Return available wallets in wallet directory. + virtual std::vector<std::string> listWalletDir() = 0; + //! Return interfaces for accessing wallets (if any). virtual std::vector<std::unique_ptr<Wallet>> getWallets() = 0; diff --git a/src/interfaces/wallet.cpp b/src/interfaces/wallet.cpp index 566ae37509..8db34ed759 100644 --- a/src/interfaces/wallet.cpp +++ b/src/interfaces/wallet.cpp @@ -7,12 +7,16 @@ #include <amount.h> #include <chain.h> #include <consensus/validation.h> +#include <init.h> +#include <interfaces/chain.h> #include <interfaces/handler.h> #include <net.h> #include <policy/feerate.h> #include <policy/fees.h> #include <policy/policy.h> #include <primitives/transaction.h> +#include <rpc/server.h> +#include <scheduler.h> #include <script/ismine.h> #include <script/standard.h> #include <support/allocators/secure.h> @@ -20,10 +24,18 @@ #include <timedata.h> #include <ui_interface.h> #include <uint256.h> +#include <util/system.h> #include <validation.h> #include <wallet/feebumper.h> #include <wallet/fees.h> +#include <wallet/rpcwallet.h> #include <wallet/wallet.h> +#include <wallet/walletutil.h> + +#include <memory> +#include <string> +#include <utility> +#include <vector> namespace interfaces { namespace { @@ -41,7 +53,8 @@ public: WalletOrderForm order_form, std::string& reject_reason) override { - LOCK2(cs_main, m_wallet.cs_wallet); + 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, g_connman.get(), state)) { reject_reason = state.GetRejectReason(); @@ -56,7 +69,7 @@ public: }; //! Construct wallet tx struct. -static WalletTx MakeWalletTx(CWallet& wallet, const CWalletTx& wtx) EXCLUSIVE_LOCKS_REQUIRED(cs_main) +WalletTx MakeWalletTx(interfaces::Chain::Lock& locked_chain, CWallet& wallet, const CWalletTx& wtx) { WalletTx result; result.tx = wtx.tx; @@ -74,7 +87,7 @@ static WalletTx MakeWalletTx(CWallet& wallet, const CWalletTx& wtx) EXCLUSIVE_LO IsMine(wallet, result.txout_address.back()) : ISMINE_NO); } - result.credit = wtx.GetCredit(ISMINE_ALL); + result.credit = wtx.GetCredit(locked_chain, ISMINE_ALL); result.debit = wtx.GetDebit(ISMINE_ALL); result.change = wtx.GetChange(); result.time = wtx.GetTxTime(); @@ -84,32 +97,38 @@ static WalletTx MakeWalletTx(CWallet& wallet, const CWalletTx& wtx) EXCLUSIVE_LO } //! Construct wallet tx status struct. -static WalletTxStatus MakeWalletTxStatus(const CWalletTx& wtx) EXCLUSIVE_LOCKS_REQUIRED(cs_main) +WalletTxStatus MakeWalletTxStatus(interfaces::Chain::Lock& locked_chain, const CWalletTx& wtx) { + LockAnnotation lock(::cs_main); // Temporary, for CheckFinalTx below. Removed in upcoming commit. + WalletTxStatus result; auto mi = ::mapBlockIndex.find(wtx.hashBlock); CBlockIndex* block = mi != ::mapBlockIndex.end() ? mi->second : nullptr; result.block_height = (block ? block->nHeight : std::numeric_limits<int>::max()); - result.blocks_to_maturity = wtx.GetBlocksToMaturity(); - result.depth_in_main_chain = wtx.GetDepthInMainChain(); + result.blocks_to_maturity = wtx.GetBlocksToMaturity(locked_chain); + result.depth_in_main_chain = wtx.GetDepthInMainChain(locked_chain); result.time_received = wtx.nTimeReceived; result.lock_time = wtx.tx->nLockTime; result.is_final = CheckFinalTx(*wtx.tx); - result.is_trusted = wtx.IsTrusted(); + result.is_trusted = wtx.IsTrusted(locked_chain); result.is_abandoned = wtx.isAbandoned(); result.is_coinbase = wtx.IsCoinBase(); - result.is_in_main_chain = wtx.IsInMainChain(); + result.is_in_main_chain = wtx.IsInMainChain(locked_chain); return result; } //! Construct wallet TxOut struct. -static WalletTxOut MakeWalletTxOut(CWallet& wallet, const CWalletTx& wtx, int n, int depth) EXCLUSIVE_LOCKS_REQUIRED(cs_main) +WalletTxOut MakeWalletTxOut(interfaces::Chain::Lock& locked_chain, + CWallet& wallet, + const CWalletTx& wtx, + int n, + int depth) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet) { WalletTxOut result; result.txout = wtx.tx->vout[n]; result.time = wtx.GetTxTime(); result.depth_in_main_chain = depth; - result.is_spent = wallet.IsSpent(wtx.GetHash(), n); + result.is_spent = wallet.IsSpent(locked_chain, wtx.GetHash(), n); return result; } @@ -197,22 +216,26 @@ public: } void lockCoin(const COutPoint& output) override { - LOCK2(cs_main, m_wallet.cs_wallet); + auto locked_chain = m_wallet.chain().lock(); + LOCK(m_wallet.cs_wallet); return m_wallet.LockCoin(output); } void unlockCoin(const COutPoint& output) override { - LOCK2(cs_main, m_wallet.cs_wallet); + auto locked_chain = m_wallet.chain().lock(); + LOCK(m_wallet.cs_wallet); return m_wallet.UnlockCoin(output); } bool isLockedCoin(const COutPoint& output) override { - LOCK2(cs_main, m_wallet.cs_wallet); + auto locked_chain = m_wallet.chain().lock(); + LOCK(m_wallet.cs_wallet); return m_wallet.IsLockedCoin(output.hash, output.n); } void listLockedCoins(std::vector<COutPoint>& outputs) override { - LOCK2(cs_main, m_wallet.cs_wallet); + auto locked_chain = m_wallet.chain().lock(); + LOCK(m_wallet.cs_wallet); return m_wallet.ListLockedCoins(outputs); } std::unique_ptr<PendingWalletTx> createTransaction(const std::vector<CRecipient>& recipients, @@ -222,9 +245,10 @@ public: CAmount& fee, std::string& fail_reason) override { - LOCK2(cs_main, m_wallet.cs_wallet); + auto locked_chain = m_wallet.chain().lock(); + LOCK(m_wallet.cs_wallet); auto pending = MakeUnique<PendingWalletTxImpl>(m_wallet); - if (!m_wallet.CreateTransaction(recipients, pending->m_tx, pending->m_key, fee, change_pos, + if (!m_wallet.CreateTransaction(*locked_chain, recipients, pending->m_tx, pending->m_key, fee, change_pos, fail_reason, coin_control, sign)) { return {}; } @@ -233,8 +257,9 @@ public: bool transactionCanBeAbandoned(const uint256& txid) override { return m_wallet.TransactionCanBeAbandoned(txid); } bool abandonTransaction(const uint256& txid) override { - LOCK2(cs_main, m_wallet.cs_wallet); - return m_wallet.AbandonTransaction(txid); + auto locked_chain = m_wallet.chain().lock(); + LOCK(m_wallet.cs_wallet); + return m_wallet.AbandonTransaction(*locked_chain, txid); } bool transactionCanBeBumped(const uint256& txid) override { @@ -262,7 +287,8 @@ public: } CTransactionRef getTx(const uint256& txid) override { - LOCK2(::cs_main, m_wallet.cs_wallet); + auto locked_chain = m_wallet.chain().lock(); + LOCK(m_wallet.cs_wallet); auto mi = m_wallet.mapWallet.find(txid); if (mi != m_wallet.mapWallet.end()) { return mi->second.tx; @@ -271,29 +297,31 @@ public: } WalletTx getWalletTx(const uint256& txid) override { - LOCK2(::cs_main, m_wallet.cs_wallet); + auto locked_chain = m_wallet.chain().lock(); + LOCK(m_wallet.cs_wallet); auto mi = m_wallet.mapWallet.find(txid); if (mi != m_wallet.mapWallet.end()) { - return MakeWalletTx(m_wallet, mi->second); + return MakeWalletTx(*locked_chain, m_wallet, mi->second); } return {}; } std::vector<WalletTx> getWalletTxs() override { - LOCK2(::cs_main, m_wallet.cs_wallet); + auto locked_chain = m_wallet.chain().lock(); + LOCK(m_wallet.cs_wallet); std::vector<WalletTx> result; result.reserve(m_wallet.mapWallet.size()); for (const auto& entry : m_wallet.mapWallet) { - result.emplace_back(MakeWalletTx(m_wallet, entry.second)); + result.emplace_back(MakeWalletTx(*locked_chain, m_wallet, entry.second)); } return result; } bool tryGetTxStatus(const uint256& txid, interfaces::WalletTxStatus& tx_status, int& num_blocks, - int64_t& adjusted_time) override + int64_t& block_time) override { - TRY_LOCK(::cs_main, locked_chain); + auto locked_chain = m_wallet.chain().lock(true /* try_lock */); if (!locked_chain) { return false; } @@ -306,26 +334,25 @@ public: return false; } num_blocks = ::chainActive.Height(); - adjusted_time = GetAdjustedTime(); - tx_status = MakeWalletTxStatus(mi->second); + block_time = ::chainActive.Tip()->GetBlockTime(); + tx_status = MakeWalletTxStatus(*locked_chain, mi->second); return true; } WalletTx getWalletTxDetails(const uint256& txid, WalletTxStatus& tx_status, WalletOrderForm& order_form, bool& in_mempool, - int& num_blocks, - int64_t& adjusted_time) override + int& num_blocks) override { - LOCK2(::cs_main, m_wallet.cs_wallet); + auto locked_chain = m_wallet.chain().lock(); + LOCK(m_wallet.cs_wallet); auto mi = m_wallet.mapWallet.find(txid); if (mi != m_wallet.mapWallet.end()) { num_blocks = ::chainActive.Height(); - adjusted_time = GetAdjustedTime(); in_mempool = mi->second.InMempool(); order_form = mi->second.vOrderForm; - tx_status = MakeWalletTxStatus(mi->second); - return MakeWalletTx(m_wallet, mi->second); + tx_status = MakeWalletTxStatus(*locked_chain, mi->second); + return MakeWalletTx(*locked_chain, m_wallet, mi->second); } return {}; } @@ -345,7 +372,7 @@ public: } bool tryGetBalances(WalletBalances& balances, int& num_blocks) override { - TRY_LOCK(cs_main, locked_chain); + auto locked_chain = m_wallet.chain().lock(true /* try_lock */); if (!locked_chain) return false; TRY_LOCK(m_wallet.cs_wallet, locked_wallet); if (!locked_wallet) { @@ -362,49 +389,55 @@ public: } isminetype txinIsMine(const CTxIn& txin) override { - LOCK2(::cs_main, m_wallet.cs_wallet); + auto locked_chain = m_wallet.chain().lock(); + LOCK(m_wallet.cs_wallet); return m_wallet.IsMine(txin); } isminetype txoutIsMine(const CTxOut& txout) override { - LOCK2(::cs_main, m_wallet.cs_wallet); + auto locked_chain = m_wallet.chain().lock(); + LOCK(m_wallet.cs_wallet); return m_wallet.IsMine(txout); } CAmount getDebit(const CTxIn& txin, isminefilter filter) override { - LOCK2(::cs_main, m_wallet.cs_wallet); + auto locked_chain = m_wallet.chain().lock(); + LOCK(m_wallet.cs_wallet); return m_wallet.GetDebit(txin, filter); } CAmount getCredit(const CTxOut& txout, isminefilter filter) override { - LOCK2(::cs_main, m_wallet.cs_wallet); + auto locked_chain = m_wallet.chain().lock(); + LOCK(m_wallet.cs_wallet); return m_wallet.GetCredit(txout, filter); } CoinsList listCoins() override { - LOCK2(::cs_main, m_wallet.cs_wallet); + auto locked_chain = m_wallet.chain().lock(); + LOCK(m_wallet.cs_wallet); CoinsList result; - for (const auto& entry : m_wallet.ListCoins()) { + for (const auto& entry : m_wallet.ListCoins(*locked_chain)) { auto& group = result[entry.first]; for (const auto& coin : entry.second) { - group.emplace_back( - COutPoint(coin.tx->GetHash(), coin.i), MakeWalletTxOut(m_wallet, *coin.tx, coin.i, coin.nDepth)); + group.emplace_back(COutPoint(coin.tx->GetHash(), coin.i), + MakeWalletTxOut(*locked_chain, m_wallet, *coin.tx, coin.i, coin.nDepth)); } } return result; } std::vector<WalletTxOut> getCoins(const std::vector<COutPoint>& outputs) override { - LOCK2(::cs_main, m_wallet.cs_wallet); + auto locked_chain = m_wallet.chain().lock(); + LOCK(m_wallet.cs_wallet); std::vector<WalletTxOut> result; result.reserve(outputs.size()); for (const auto& output : outputs) { result.emplace_back(); auto it = m_wallet.mapWallet.find(output.hash); if (it != m_wallet.mapWallet.end()) { - int depth = it->second.GetDepthInMainChain(); + int depth = it->second.GetDepthInMainChain(*locked_chain); if (depth >= 0) { - result.back() = MakeWalletTxOut(m_wallet, it->second, output.n, depth); + result.back() = MakeWalletTxOut(*locked_chain, m_wallet, it->second, output.n, depth); } } } @@ -460,8 +493,32 @@ public: CWallet& m_wallet; }; +class WalletClientImpl : public ChainClient +{ +public: + WalletClientImpl(Chain& chain, std::vector<std::string> wallet_filenames) + : m_chain(chain), m_wallet_filenames(std::move(wallet_filenames)) + { + } + void registerRpcs() override { return RegisterWalletRPCCommands(::tableRPC); } + bool verify() override { return VerifyWallets(m_chain, m_wallet_filenames); } + bool load() override { return LoadWallets(m_chain, m_wallet_filenames); } + void start(CScheduler& scheduler) override { return StartWallets(scheduler); } + void flush() override { return FlushWallets(); } + void stop() override { return StopWallets(); } + ~WalletClientImpl() override { UnloadWallets(); } + + Chain& m_chain; + std::vector<std::string> m_wallet_filenames; +}; + } // namespace std::unique_ptr<Wallet> MakeWallet(const std::shared_ptr<CWallet>& wallet) { return MakeUnique<WalletImpl>(wallet); } +std::unique_ptr<ChainClient> MakeWalletClient(Chain& chain, std::vector<std::string> wallet_filenames) +{ + return MakeUnique<WalletClientImpl>(chain, std::move(wallet_filenames)); +} + } // namespace interfaces diff --git a/src/interfaces/wallet.h b/src/interfaces/wallet.h index 7aa91f37e1..da60684a4f 100644 --- a/src/interfaces/wallet.h +++ b/src/interfaces/wallet.h @@ -179,15 +179,14 @@ public: virtual bool tryGetTxStatus(const uint256& txid, WalletTxStatus& tx_status, int& num_blocks, - int64_t& adjusted_time) = 0; + int64_t& block_time) = 0; //! Get transaction details. virtual WalletTx getWalletTxDetails(const uint256& txid, WalletTxStatus& tx_status, WalletOrderForm& order_form, bool& in_mempool, - int& num_blocks, - int64_t& adjusted_time) = 0; + int& num_blocks) = 0; //! Get balances. virtual WalletBalances getBalances() = 0; diff --git a/src/key_io.cpp b/src/key_io.cpp index c6a541cdb1..d998089535 100644 --- a/src/key_io.cpp +++ b/src/key_io.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2017 The Bitcoin Core developers +// Copyright (c) 2014-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. @@ -7,7 +7,7 @@ #include <base58.h> #include <bech32.h> #include <script/script.h> -#include <utilstrencodings.h> +#include <util/strencodings.h> #include <boost/variant/apply_visitor.hpp> #include <boost/variant/static_visitor.hpp> diff --git a/src/keystore.cpp b/src/keystore.cpp index b2012a04bb..148979cf35 100644 --- a/src/keystore.cpp +++ b/src/keystore.cpp @@ -5,7 +5,7 @@ #include <keystore.h> -#include <util.h> +#include <util/system.h> void CBasicKeyStore::ImplicitlyLearnRelatedKeyScripts(const CPubKey& pubkey) { diff --git a/src/logging.cpp b/src/logging.cpp index 0ae4f8121e..77dc2d0939 100644 --- a/src/logging.cpp +++ b/src/logging.cpp @@ -4,7 +4,7 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include <logging.h> -#include <utiltime.h> +#include <util/time.h> const char * const DEFAULT_DEBUGLOGFILE = "debug.log"; diff --git a/src/merkleblock.cpp b/src/merkleblock.cpp index 7bbc45d38a..a54268d655 100644 --- a/src/merkleblock.cpp +++ b/src/merkleblock.cpp @@ -7,7 +7,7 @@ #include <hash.h> #include <consensus/consensus.h> -#include <utilstrencodings.h> +#include <util/strencodings.h> CMerkleBlock::CMerkleBlock(const CBlock& block, CBloomFilter* filter, const std::set<uint256>* txids) @@ -53,7 +53,7 @@ uint256 CPartialMerkleTree::CalcHash(int height, unsigned int pos, const std::ve else right = left; // combine subhashes - return Hash(BEGIN(left), END(left), BEGIN(right), END(right)); + return Hash(left.begin(), left.end(), right.begin(), right.end()); } } @@ -109,7 +109,7 @@ uint256 CPartialMerkleTree::TraverseAndExtract(int height, unsigned int pos, uns right = left; } // and combine them before returning - return Hash(BEGIN(left), END(left), BEGIN(right), END(right)); + return Hash(left.begin(), left.end(), right.begin(), right.end()); } } diff --git a/src/miner.cpp b/src/miner.cpp index 6d35f9ac37..ef48a86e32 100644 --- a/src/miner.cpp +++ b/src/miner.cpp @@ -21,8 +21,8 @@ #include <primitives/transaction.h> #include <script/standard.h> #include <timedata.h> -#include <util.h> -#include <utilmoneystr.h> +#include <util/system.h> +#include <util/moneystr.h> #include <validationinterface.h> #include <algorithm> @@ -70,9 +70,8 @@ static BlockAssembler::Options DefaultOptions() // If -blockmaxweight is not given, limit to DEFAULT_BLOCK_MAX_WEIGHT BlockAssembler::Options options; options.nBlockMaxWeight = gArgs.GetArg("-blockmaxweight", DEFAULT_BLOCK_MAX_WEIGHT); - if (gArgs.IsArgSet("-blockmintxfee")) { - CAmount n = 0; - ParseMoney(gArgs.GetArg("-blockmintxfee", ""), n); + CAmount n = 0; + if (gArgs.IsArgSet("-blockmintxfee") && ParseMoney(gArgs.GetArg("-blockmintxfee", ""), n)) { options.blockMinFeeRate = CFeeRate(n); } else { options.blockMinFeeRate = CFeeRate(DEFAULT_BLOCK_MIN_TX_FEE); @@ -96,7 +95,7 @@ void BlockAssembler::resetBlock() nFees = 0; } -std::unique_ptr<CBlockTemplate> BlockAssembler::CreateNewBlock(const CScript& scriptPubKeyIn, bool fMineWitnessTx) +std::unique_ptr<CBlockTemplate> BlockAssembler::CreateNewBlock(const CScript& scriptPubKeyIn) { int64_t nTimeStart = GetTimeMicros(); @@ -140,7 +139,7 @@ std::unique_ptr<CBlockTemplate> BlockAssembler::CreateNewBlock(const CScript& sc // not activated. // TODO: replace this with a call to main to assess validity of a mempool // transaction (which in most cases can be a no-op). - fIncludeWitness = IsWitnessEnabled(pindexPrev, chainparams.GetConsensus()) && fMineWitnessTx; + fIncludeWitness = IsWitnessEnabled(pindexPrev, chainparams.GetConsensus()); int nPackagesSelected = 0; int nDescendantsUpdated = 0; diff --git a/src/miner.h b/src/miner.h index 8cdcf7133b..44c50b01ad 100644 --- a/src/miner.h +++ b/src/miner.h @@ -157,7 +157,7 @@ public: BlockAssembler(const CChainParams& params, const Options& options); /** Construct a new block template with coinbase to scriptPubKeyIn */ - std::unique_ptr<CBlockTemplate> CreateNewBlock(const CScript& scriptPubKeyIn, bool fMineWitnessTx=true); + std::unique_ptr<CBlockTemplate> CreateNewBlock(const CScript& scriptPubKeyIn); private: // utility functions diff --git a/src/net.cpp b/src/net.cpp index f83f39a67d..0490ccd6db 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -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. @@ -9,6 +9,7 @@ #include <net.h> +#include <banman.h> #include <chainparams.h> #include <clientversion.h> #include <consensus/consensus.h> @@ -18,7 +19,7 @@ #include <netbase.h> #include <scheduler.h> #include <ui_interface.h> -#include <utilstrencodings.h> +#include <util/strencodings.h> #ifdef WIN32 #include <string.h> @@ -26,6 +27,10 @@ #include <fcntl.h> #endif +#ifdef USE_POLL +#include <poll.h> +#endif + #ifdef USE_UPNP #include <miniupnpc/miniupnpc.h> #include <miniupnpc/miniwget.h> @@ -33,11 +38,12 @@ #include <miniupnpc/upnperrors.h> #endif +#include <unordered_map> #include <math.h> -// Dump addresses to peers.dat and banlist.dat every 15 minutes (900s) -#define DUMP_ADDRESSES_INTERVAL 900 +// Dump addresses to peers.dat every 15 minutes (900s) +static constexpr int DUMP_PEERS_INTERVAL = 15 * 60; // We add a random period time (0 to 1 seconds) to feeler connections to prevent synchronization. #define FEELER_SLEEP_WINDOW 1 @@ -71,7 +77,11 @@ enum BindFlags { BF_WHITELIST = (1U << 2), }; -const static std::string NET_MESSAGE_COMMAND_OTHER = "*other*"; +// The set of sockets cannot be modified while waiting +// The sleep time needs to be small to avoid new sockets stalling +static const uint64_t SELECT_TIMEOUT_MILLISECONDS = 50; + +const std::string NET_MESSAGE_COMMAND_OTHER = "*other*"; static const uint64_t RANDOMIZER_ID_NETGROUP = 0x6c0edd8036ef4036ULL; // SHA256("netgroup")[0:8] static const uint64_t RANDOMIZER_ID_LOCALHOSTNONCE = 0xd93e69e2bbfa5735ULL; // SHA256("localhostnonce")[0:8] @@ -82,8 +92,8 @@ bool fDiscover = true; bool fListen = true; bool fRelayTxes = true; CCriticalSection cs_mapLocalHost; -std::map<CNetAddr, LocalServiceInfo> mapLocalHost; -static bool vfLimited[NET_MAX] = {}; +std::map<CNetAddr, LocalServiceInfo> mapLocalHost GUARDED_BY(cs_mapLocalHost); +static bool vfLimited[NET_MAX] GUARDED_BY(cs_mapLocalHost) = {}; std::string strSubVersion; limitedmap<uint256, int64_t> mapAlreadyAskedFor(MAX_INV_SZ); @@ -134,11 +144,12 @@ static std::vector<CAddress> convertSeed6(const std::vector<SeedSpec6> &vSeedsIn const int64_t nOneWeek = 7*24*60*60; std::vector<CAddress> vSeedsOut; vSeedsOut.reserve(vSeedsIn.size()); + FastRandomContext rng; for (const auto& seed_in : vSeedsIn) { struct in6_addr ip; memcpy(&ip, seed_in.addr, sizeof(ip)); CAddress addr(CService(ip, seed_in.port), GetDesirableServiceFlags(NODE_NONE)); - addr.nTime = GetTime() - GetRand(nOneWeek) - nOneWeek; + addr.nTime = GetTime() - rng.randrange(nOneWeek) - nOneWeek; vSeedsOut.push_back(addr); } return vSeedsOut; @@ -173,7 +184,7 @@ bool IsPeerAddrLocalGood(CNode *pnode) { CService addrLocal = pnode->GetAddrLocal(); return fDiscover && pnode->addr.IsRoutable() && addrLocal.IsRoutable() && - !IsLimited(addrLocal.GetNetwork()); + IsReachable(addrLocal.GetNetwork()); } // pushes our own address to a peer @@ -189,16 +200,16 @@ void AdvertiseLocal(CNode *pnode) // If discovery is enabled, sometimes give our peer the address it // tells us that it sees us as in case it has a better idea of our // address than we do. + FastRandomContext rng; if (IsPeerAddrLocalGood(pnode) && (!addrLocal.IsRoutable() || - GetRand((GetnScore(addrLocal) > LOCAL_MANUAL) ? 8:2) == 0)) + rng.randbits((GetnScore(addrLocal) > LOCAL_MANUAL) ? 3 : 1) == 0)) { addrLocal.SetIP(pnode->GetAddrLocal()); } if (addrLocal.IsRoutable() || gArgs.GetBoolArg("-addrmantest", false)) { LogPrint(BCLog::NET, "AdvertiseLocal: advertising address %s\n", addrLocal.ToString()); - FastRandomContext insecure_rand; - pnode->PushAddress(addrLocal, insecure_rand); + pnode->PushAddress(addrLocal, rng); } } } @@ -212,7 +223,7 @@ bool AddLocal(const CService& addr, int nScore) if (!fDiscover && nScore < LOCAL_MANUAL) return false; - if (IsLimited(addr)) + if (!IsReachable(addr)) return false; LogPrintf("AddLocal(%s,%i)\n", addr.ToString(), nScore); @@ -242,24 +253,23 @@ void RemoveLocal(const CService& addr) mapLocalHost.erase(addr); } -/** Make a particular network entirely off-limits (no automatic connects to it) */ -void SetLimited(enum Network net, bool fLimited) +void SetReachable(enum Network net, bool reachable) { if (net == NET_UNROUTABLE || net == NET_INTERNAL) return; LOCK(cs_mapLocalHost); - vfLimited[net] = fLimited; + vfLimited[net] = !reachable; } -bool IsLimited(enum Network net) +bool IsReachable(enum Network net) { LOCK(cs_mapLocalHost); - return vfLimited[net]; + return !vfLimited[net]; } -bool IsLimited(const CNetAddr &addr) +bool IsReachable(const CNetAddr &addr) { - return IsLimited(addr.GetNetwork()); + return IsReachable(addr.GetNetwork()); } /** vote for a local address */ @@ -282,21 +292,6 @@ bool IsLocal(const CService& addr) return mapLocalHost.count(addr) > 0; } -/** check whether a given network is one we can probably connect to */ -bool IsReachable(enum Network net) -{ - LOCK(cs_mapLocalHost); - return !vfLimited[net]; -} - -/** check whether a given address is in a network we can probably connect to */ -bool IsReachable(const CNetAddr& addr) -{ - enum Network net = addr.GetNetwork(); - return IsReachable(net); -} - - CNode* CConnman::FindNode(const CNetAddr& ip) { LOCK(cs_vNodes); @@ -463,26 +458,6 @@ CNode* CConnman::ConnectNode(CAddress addrConnect, const char *pszDest, bool fCo return pnode; } -void CConnman::DumpBanlist() -{ - SweepBanned(); // clean unused entries (if bantime has expired) - - if (!BannedSetIsDirty()) - return; - - int64_t nStart = GetTimeMillis(); - - CBanDB bandb; - banmap_t banmap; - GetBanned(banmap); - if (bandb.Write(banmap)) { - SetBannedSetDirty(false); - } - - LogPrint(BCLog::NET, "Flushed %d banned node ips/subnets to banlist.dat %dms\n", - banmap.size(), GetTimeMillis() - nStart); -} - void CNode::CloseSocketDisconnect() { fDisconnect = true; @@ -494,157 +469,6 @@ void CNode::CloseSocketDisconnect() } } -void CConnman::ClearBanned() -{ - { - LOCK(cs_setBanned); - setBanned.clear(); - setBannedIsDirty = true; - } - DumpBanlist(); //store banlist to disk - if(clientInterface) - clientInterface->BannedListChanged(); -} - -bool CConnman::IsBanned(CNetAddr ip) -{ - LOCK(cs_setBanned); - for (const auto& it : setBanned) { - CSubNet subNet = it.first; - CBanEntry banEntry = it.second; - - if (subNet.Match(ip) && GetTime() < banEntry.nBanUntil) { - return true; - } - } - return false; -} - -bool CConnman::IsBanned(CSubNet subnet) -{ - LOCK(cs_setBanned); - banmap_t::iterator i = setBanned.find(subnet); - if (i != setBanned.end()) - { - CBanEntry banEntry = (*i).second; - if (GetTime() < banEntry.nBanUntil) { - return true; - } - } - return false; -} - -void CConnman::Ban(const CNetAddr& addr, const BanReason &banReason, int64_t bantimeoffset, bool sinceUnixEpoch) { - CSubNet subNet(addr); - Ban(subNet, banReason, bantimeoffset, sinceUnixEpoch); -} - -void CConnman::Ban(const CSubNet& subNet, const BanReason &banReason, int64_t bantimeoffset, bool sinceUnixEpoch) { - CBanEntry banEntry(GetTime()); - banEntry.banReason = banReason; - if (bantimeoffset <= 0) - { - bantimeoffset = gArgs.GetArg("-bantime", DEFAULT_MISBEHAVING_BANTIME); - sinceUnixEpoch = false; - } - banEntry.nBanUntil = (sinceUnixEpoch ? 0 : GetTime() )+bantimeoffset; - - { - LOCK(cs_setBanned); - if (setBanned[subNet].nBanUntil < banEntry.nBanUntil) { - setBanned[subNet] = banEntry; - setBannedIsDirty = true; - } - else - return; - } - if(clientInterface) - clientInterface->BannedListChanged(); - { - LOCK(cs_vNodes); - for (CNode* pnode : vNodes) { - if (subNet.Match(static_cast<CNetAddr>(pnode->addr))) - pnode->fDisconnect = true; - } - } - if(banReason == BanReasonManuallyAdded) - DumpBanlist(); //store banlist to disk immediately if user requested ban -} - -bool CConnman::Unban(const CNetAddr &addr) { - CSubNet subNet(addr); - return Unban(subNet); -} - -bool CConnman::Unban(const CSubNet &subNet) { - { - LOCK(cs_setBanned); - if (!setBanned.erase(subNet)) - return false; - setBannedIsDirty = true; - } - if(clientInterface) - clientInterface->BannedListChanged(); - DumpBanlist(); //store banlist to disk immediately - return true; -} - -void CConnman::GetBanned(banmap_t &banMap) -{ - LOCK(cs_setBanned); - // Sweep the banlist so expired bans are not returned - SweepBanned(); - banMap = setBanned; //create a thread safe copy -} - -void CConnman::SetBanned(const banmap_t &banMap) -{ - LOCK(cs_setBanned); - setBanned = banMap; - setBannedIsDirty = true; -} - -void CConnman::SweepBanned() -{ - int64_t now = GetTime(); - bool notifyUI = false; - { - LOCK(cs_setBanned); - banmap_t::iterator it = setBanned.begin(); - while(it != setBanned.end()) - { - CSubNet subNet = (*it).first; - CBanEntry banEntry = (*it).second; - if(now > banEntry.nBanUntil) - { - setBanned.erase(it++); - setBannedIsDirty = true; - notifyUI = true; - LogPrint(BCLog::NET, "%s: Removed banned node ip/subnet from banlist.dat: %s\n", __func__, subNet.ToString()); - } - else - ++it; - } - } - // update UI - if(notifyUI && clientInterface) { - clientInterface->BannedListChanged(); - } -} - -bool CConnman::BannedSetIsDirty() -{ - LOCK(cs_setBanned); - return setBannedIsDirty; -} - -void CConnman::SetBannedSetDirty(bool dirty) -{ - LOCK(cs_setBanned); //reuse setBanned lock for the isDirty flag - setBannedIsDirty = dirty; -} - - bool CConnman::IsWhitelistedRange(const CNetAddr &addr) { for (const CSubNet& subnet : vWhitelistedRange) { if (subnet.Match(addr)) @@ -715,7 +539,10 @@ void CNode::copyStats(CNodeStats &stats) X(nRecvBytes); } X(fWhitelisted); - X(minFeeFilter); + { + LOCK(cs_feeFilter); + X(minFeeFilter); + } // It is common for nodes with good ping times to suddenly become lagged, // due to a new block arriving or other large transfer. @@ -774,7 +601,6 @@ bool CNode::ReceiveMsgBytes(const char *pch, unsigned int nBytes, bool& complete nBytes -= handled; if (msg.complete()) { - //store received bytes per message command //to prevent a memory DOS, only allow valid commands mapMsgCmdSize::iterator i = mapRecvBytesPerMsgCmd.find(msg.hdr.pchCommand); @@ -874,16 +700,7 @@ const uint256& CNetMessage::GetMessageHash() const return data_hash; } - - - - - - - - -// requires LOCK(cs_vSend) -size_t CConnman::SocketSendData(CNode *pnode) const +size_t CConnman::SocketSendData(CNode *pnode) const EXCLUSIVE_LOCKS_REQUIRED(pnode->cs_vSend) { auto it = pnode->vSendMsg.begin(); size_t nSentSize = 0; @@ -1011,6 +828,7 @@ bool CConnman::AttemptToEvictConnection() continue; if (node->fDisconnect) continue; + LOCK(node->cs_filter); NodeEvictionCandidate candidate = {node->GetId(), node->nTimeConnected, node->nMinPingUsecTime, node->nLastBlockTime, node->nLastTXTime, HasAllDesirableServiceFlags(node->nServices), @@ -1119,7 +937,7 @@ void CConnman::AcceptConnection(const ListenSocket& hListenSocket) { // on all platforms. Set it again here just to be sure. SetSocketNoDelay(hSocket); - if (IsBanned(addr) && !whitelisted) + if (m_banman && m_banman->IsBanned(addr) && !whitelisted) { LogPrint(BCLog::NET, "connection from %s dropped (banned)\n", addr.ToString()); CloseSocket(hSocket); @@ -1153,310 +971,409 @@ void CConnman::AcceptConnection(const ListenSocket& hListenSocket) { } } -void CConnman::ThreadSocketHandler() +void CConnman::DisconnectNodes() { - unsigned int nPrevNodeCount = 0; - while (!interruptNet) { - // - // Disconnect nodes - // - { - LOCK(cs_vNodes); + LOCK(cs_vNodes); - if (!fNetworkActive) { - // Disconnect any connected nodes - for (CNode* pnode : vNodes) { - if (!pnode->fDisconnect) { - LogPrint(BCLog::NET, "Network not active, dropping peer=%d\n", pnode->GetId()); - pnode->fDisconnect = true; - } + if (!fNetworkActive) { + // Disconnect any connected nodes + for (CNode* pnode : vNodes) { + if (!pnode->fDisconnect) { + LogPrint(BCLog::NET, "Network not active, dropping peer=%d\n", pnode->GetId()); + pnode->fDisconnect = true; } } + } - // Disconnect unused nodes - std::vector<CNode*> vNodesCopy = vNodes; - for (CNode* pnode : vNodesCopy) + // Disconnect unused nodes + std::vector<CNode*> vNodesCopy = vNodes; + for (CNode* pnode : vNodesCopy) + { + if (pnode->fDisconnect) { - if (pnode->fDisconnect) - { - // remove from vNodes - vNodes.erase(remove(vNodes.begin(), vNodes.end(), pnode), vNodes.end()); + // remove from vNodes + vNodes.erase(remove(vNodes.begin(), vNodes.end(), pnode), vNodes.end()); - // release outbound grant (if any) - pnode->grantOutbound.Release(); + // release outbound grant (if any) + pnode->grantOutbound.Release(); - // close socket and cleanup - pnode->CloseSocketDisconnect(); + // close socket and cleanup + pnode->CloseSocketDisconnect(); - // hold in disconnected pool until all refs are released - pnode->Release(); - vNodesDisconnected.push_back(pnode); - } + // hold in disconnected pool until all refs are released + pnode->Release(); + vNodesDisconnected.push_back(pnode); } } + } + { + // Delete disconnected nodes + std::list<CNode*> vNodesDisconnectedCopy = vNodesDisconnected; + for (CNode* pnode : vNodesDisconnectedCopy) { - // Delete disconnected nodes - std::list<CNode*> vNodesDisconnectedCopy = vNodesDisconnected; - for (CNode* pnode : vNodesDisconnectedCopy) - { - // wait until threads are done using it - if (pnode->GetRefCount() <= 0) { - bool fDelete = false; - { - TRY_LOCK(pnode->cs_inventory, lockInv); - if (lockInv) { - TRY_LOCK(pnode->cs_vSend, lockSend); - if (lockSend) { - fDelete = true; - } + // wait until threads are done using it + if (pnode->GetRefCount() <= 0) { + bool fDelete = false; + { + TRY_LOCK(pnode->cs_inventory, lockInv); + if (lockInv) { + TRY_LOCK(pnode->cs_vSend, lockSend); + if (lockSend) { + fDelete = true; } } - if (fDelete) { - vNodesDisconnected.remove(pnode); - DeleteNode(pnode); - } + } + if (fDelete) { + vNodesDisconnected.remove(pnode); + DeleteNode(pnode); } } } - size_t vNodesSize; + } +} + +void CConnman::NotifyNumConnectionsChanged() +{ + size_t vNodesSize; + { + LOCK(cs_vNodes); + vNodesSize = vNodes.size(); + } + if(vNodesSize != nPrevNodeCount) { + nPrevNodeCount = vNodesSize; + if(clientInterface) + clientInterface->NotifyNumConnectionsChanged(vNodesSize); + } +} + +void CConnman::InactivityCheck(CNode *pnode) +{ + int64_t nTime = GetSystemTimeInSeconds(); + if (nTime - pnode->nTimeConnected > m_peer_connect_timeout) + { + if (pnode->nLastRecv == 0 || pnode->nLastSend == 0) { - LOCK(cs_vNodes); - vNodesSize = vNodes.size(); + LogPrint(BCLog::NET, "socket no message in first %i seconds, %d %d from %d\n", m_peer_connect_timeout, pnode->nLastRecv != 0, pnode->nLastSend != 0, pnode->GetId()); + pnode->fDisconnect = true; } - if(vNodesSize != nPrevNodeCount) { - nPrevNodeCount = vNodesSize; - if(clientInterface) - clientInterface->NotifyNumConnectionsChanged(vNodesSize); + else if (nTime - pnode->nLastSend > TIMEOUT_INTERVAL) + { + LogPrintf("socket sending timeout: %is\n", nTime - pnode->nLastSend); + pnode->fDisconnect = true; } - - // - // Find which sockets have data to receive - // - struct timeval timeout; - timeout.tv_sec = 0; - timeout.tv_usec = 50000; // frequency to poll pnode->vSend - - fd_set fdsetRecv; - fd_set fdsetSend; - fd_set fdsetError; - FD_ZERO(&fdsetRecv); - FD_ZERO(&fdsetSend); - FD_ZERO(&fdsetError); - SOCKET hSocketMax = 0; - bool have_fds = false; - - for (const ListenSocket& hListenSocket : vhListenSocket) { - FD_SET(hListenSocket.socket, &fdsetRecv); - hSocketMax = std::max(hSocketMax, hListenSocket.socket); - have_fds = true; + else if (nTime - pnode->nLastRecv > (pnode->nVersion > BIP0031_VERSION ? TIMEOUT_INTERVAL : 90*60)) + { + LogPrintf("socket receive timeout: %is\n", nTime - pnode->nLastRecv); + pnode->fDisconnect = true; + } + else if (pnode->nPingNonceSent && pnode->nPingUsecStart + TIMEOUT_INTERVAL * 1000000 < GetTimeMicros()) + { + LogPrintf("ping timeout: %fs\n", 0.000001 * (GetTimeMicros() - pnode->nPingUsecStart)); + pnode->fDisconnect = true; } + else if (!pnode->fSuccessfullyConnected) + { + LogPrint(BCLog::NET, "version handshake timeout from %d\n", pnode->GetId()); + pnode->fDisconnect = true; + } + } +} +bool CConnman::GenerateSelectSet(std::set<SOCKET> &recv_set, std::set<SOCKET> &send_set, std::set<SOCKET> &error_set) +{ + for (const ListenSocket& hListenSocket : vhListenSocket) { + recv_set.insert(hListenSocket.socket); + } + + { + LOCK(cs_vNodes); + for (CNode* pnode : vNodes) { - LOCK(cs_vNodes); - for (CNode* pnode : vNodes) + // Implement the following logic: + // * If there is data to send, select() for sending data. As this only + // happens when optimistic write failed, we choose to first drain the + // write buffer in this case before receiving more. This avoids + // needlessly queueing received data, if the remote peer is not themselves + // receiving data. This means properly utilizing TCP flow control signalling. + // * Otherwise, if there is space left in the receive buffer, select() for + // receiving data. + // * Hand off all complete messages to the processor, to be handled without + // blocking here. + + bool select_recv = !pnode->fPauseRecv; + bool select_send; { - // Implement the following logic: - // * If there is data to send, select() for sending data. As this only - // happens when optimistic write failed, we choose to first drain the - // write buffer in this case before receiving more. This avoids - // needlessly queueing received data, if the remote peer is not themselves - // receiving data. This means properly utilizing TCP flow control signalling. - // * Otherwise, if there is space left in the receive buffer, select() for - // receiving data. - // * Hand off all complete messages to the processor, to be handled without - // blocking here. - - bool select_recv = !pnode->fPauseRecv; - bool select_send; - { - LOCK(pnode->cs_vSend); - select_send = !pnode->vSendMsg.empty(); - } - - LOCK(pnode->cs_hSocket); - if (pnode->hSocket == INVALID_SOCKET) - continue; + LOCK(pnode->cs_vSend); + select_send = !pnode->vSendMsg.empty(); + } - FD_SET(pnode->hSocket, &fdsetError); - hSocketMax = std::max(hSocketMax, pnode->hSocket); - have_fds = true; + LOCK(pnode->cs_hSocket); + if (pnode->hSocket == INVALID_SOCKET) + continue; - if (select_send) { - FD_SET(pnode->hSocket, &fdsetSend); - continue; - } - if (select_recv) { - FD_SET(pnode->hSocket, &fdsetRecv); - } + error_set.insert(pnode->hSocket); + if (select_send) { + send_set.insert(pnode->hSocket); + continue; + } + if (select_recv) { + recv_set.insert(pnode->hSocket); } } + } - int nSelect = select(have_fds ? hSocketMax + 1 : 0, - &fdsetRecv, &fdsetSend, &fdsetError, &timeout); - if (interruptNet) + return !recv_set.empty() || !send_set.empty() || !error_set.empty(); +} + +#ifdef USE_POLL +void CConnman::SocketEvents(std::set<SOCKET> &recv_set, std::set<SOCKET> &send_set, std::set<SOCKET> &error_set) +{ + std::set<SOCKET> recv_select_set, send_select_set, error_select_set; + if (!GenerateSelectSet(recv_select_set, send_select_set, error_select_set)) { + interruptNet.sleep_for(std::chrono::milliseconds(SELECT_TIMEOUT_MILLISECONDS)); + return; + } + + std::unordered_map<SOCKET, struct pollfd> pollfds; + for (SOCKET socket_id : recv_select_set) { + pollfds[socket_id].fd = socket_id; + pollfds[socket_id].events |= POLLIN; + } + + for (SOCKET socket_id : send_select_set) { + pollfds[socket_id].fd = socket_id; + pollfds[socket_id].events |= POLLOUT; + } + + for (SOCKET socket_id : error_select_set) { + pollfds[socket_id].fd = socket_id; + // These flags are ignored, but we set them for clarity + pollfds[socket_id].events |= POLLERR|POLLHUP; + } + + std::vector<struct pollfd> vpollfds; + vpollfds.reserve(pollfds.size()); + for (auto it : pollfds) { + vpollfds.push_back(std::move(it.second)); + } + + if (poll(vpollfds.data(), vpollfds.size(), SELECT_TIMEOUT_MILLISECONDS) < 0) return; + + if (interruptNet) return; + + for (struct pollfd pollfd_entry : vpollfds) { + if (pollfd_entry.revents & POLLIN) recv_set.insert(pollfd_entry.fd); + if (pollfd_entry.revents & POLLOUT) send_set.insert(pollfd_entry.fd); + if (pollfd_entry.revents & (POLLERR|POLLHUP)) error_set.insert(pollfd_entry.fd); + } +} +#else +void CConnman::SocketEvents(std::set<SOCKET> &recv_set, std::set<SOCKET> &send_set, std::set<SOCKET> &error_set) +{ + std::set<SOCKET> recv_select_set, send_select_set, error_select_set; + if (!GenerateSelectSet(recv_select_set, send_select_set, error_select_set)) { + interruptNet.sleep_for(std::chrono::milliseconds(SELECT_TIMEOUT_MILLISECONDS)); + return; + } + + // + // Find which sockets have data to receive + // + struct timeval timeout; + timeout.tv_sec = 0; + timeout.tv_usec = SELECT_TIMEOUT_MILLISECONDS * 1000; // frequency to poll pnode->vSend + + fd_set fdsetRecv; + fd_set fdsetSend; + fd_set fdsetError; + FD_ZERO(&fdsetRecv); + FD_ZERO(&fdsetSend); + FD_ZERO(&fdsetError); + SOCKET hSocketMax = 0; + + for (SOCKET hSocket : recv_select_set) { + FD_SET(hSocket, &fdsetRecv); + hSocketMax = std::max(hSocketMax, hSocket); + } + + for (SOCKET hSocket : send_select_set) { + FD_SET(hSocket, &fdsetSend); + hSocketMax = std::max(hSocketMax, hSocket); + } + + for (SOCKET hSocket : error_select_set) { + FD_SET(hSocket, &fdsetError); + hSocketMax = std::max(hSocketMax, hSocket); + } + + int nSelect = select(hSocketMax + 1, &fdsetRecv, &fdsetSend, &fdsetError, &timeout); + + if (interruptNet) + return; + + if (nSelect == SOCKET_ERROR) + { + int nErr = WSAGetLastError(); + LogPrintf("socket select error %s\n", NetworkErrorString(nErr)); + for (unsigned int i = 0; i <= hSocketMax; i++) + FD_SET(i, &fdsetRecv); + FD_ZERO(&fdsetSend); + FD_ZERO(&fdsetError); + if (!interruptNet.sleep_for(std::chrono::milliseconds(SELECT_TIMEOUT_MILLISECONDS))) return; + } - if (nSelect == SOCKET_ERROR) - { - if (have_fds) - { - int nErr = WSAGetLastError(); - LogPrintf("socket select error %s\n", NetworkErrorString(nErr)); - for (unsigned int i = 0; i <= hSocketMax; i++) - FD_SET(i, &fdsetRecv); - } - FD_ZERO(&fdsetSend); - FD_ZERO(&fdsetError); - if (!interruptNet.sleep_for(std::chrono::milliseconds(timeout.tv_usec/1000))) - return; + for (SOCKET hSocket : recv_select_set) { + if (FD_ISSET(hSocket, &fdsetRecv)) { + recv_set.insert(hSocket); } + } - // - // Accept new connections - // - for (const ListenSocket& hListenSocket : vhListenSocket) + for (SOCKET hSocket : send_select_set) { + if (FD_ISSET(hSocket, &fdsetSend)) { + send_set.insert(hSocket); + } + } + + for (SOCKET hSocket : error_select_set) { + if (FD_ISSET(hSocket, &fdsetError)) { + error_set.insert(hSocket); + } + } +} +#endif + +void CConnman::SocketHandler() +{ + std::set<SOCKET> recv_set, send_set, error_set; + SocketEvents(recv_set, send_set, error_set); + + if (interruptNet) return; + + // + // Accept new connections + // + for (const ListenSocket& hListenSocket : vhListenSocket) + { + if (hListenSocket.socket != INVALID_SOCKET && recv_set.count(hListenSocket.socket) > 0) { - if (hListenSocket.socket != INVALID_SOCKET && FD_ISSET(hListenSocket.socket, &fdsetRecv)) - { - AcceptConnection(hListenSocket); - } + AcceptConnection(hListenSocket); } + } + + // + // Service each socket + // + std::vector<CNode*> vNodesCopy; + { + LOCK(cs_vNodes); + vNodesCopy = vNodes; + for (CNode* pnode : vNodesCopy) + pnode->AddRef(); + } + for (CNode* pnode : vNodesCopy) + { + if (interruptNet) + return; // - // Service each socket + // Receive // - std::vector<CNode*> vNodesCopy; + bool recvSet = false; + bool sendSet = false; + bool errorSet = false; { - LOCK(cs_vNodes); - vNodesCopy = vNodes; - for (CNode* pnode : vNodesCopy) - pnode->AddRef(); + LOCK(pnode->cs_hSocket); + if (pnode->hSocket == INVALID_SOCKET) + continue; + recvSet = recv_set.count(pnode->hSocket) > 0; + sendSet = send_set.count(pnode->hSocket) > 0; + errorSet = error_set.count(pnode->hSocket) > 0; } - for (CNode* pnode : vNodesCopy) + if (recvSet || errorSet) { - if (interruptNet) - return; - - // - // Receive - // - bool recvSet = false; - bool sendSet = false; - bool errorSet = false; + // typical socket buffer is 8K-64K + char pchBuf[0x10000]; + int nBytes = 0; { LOCK(pnode->cs_hSocket); if (pnode->hSocket == INVALID_SOCKET) continue; - recvSet = FD_ISSET(pnode->hSocket, &fdsetRecv); - sendSet = FD_ISSET(pnode->hSocket, &fdsetSend); - errorSet = FD_ISSET(pnode->hSocket, &fdsetError); + nBytes = recv(pnode->hSocket, pchBuf, sizeof(pchBuf), MSG_DONTWAIT); } - if (recvSet || errorSet) + if (nBytes > 0) { - // typical socket buffer is 8K-64K - char pchBuf[0x10000]; - int nBytes = 0; - { - LOCK(pnode->cs_hSocket); - if (pnode->hSocket == INVALID_SOCKET) - continue; - nBytes = recv(pnode->hSocket, pchBuf, sizeof(pchBuf), MSG_DONTWAIT); - } - if (nBytes > 0) - { - bool notify = false; - if (!pnode->ReceiveMsgBytes(pchBuf, nBytes, notify)) - pnode->CloseSocketDisconnect(); - RecordBytesRecv(nBytes); - if (notify) { - size_t nSizeAdded = 0; - auto it(pnode->vRecvMsg.begin()); - for (; it != pnode->vRecvMsg.end(); ++it) { - if (!it->complete()) - break; - nSizeAdded += it->vRecv.size() + CMessageHeader::HEADER_SIZE; - } - { - LOCK(pnode->cs_vProcessMsg); - pnode->vProcessMsg.splice(pnode->vProcessMsg.end(), pnode->vRecvMsg, pnode->vRecvMsg.begin(), it); - pnode->nProcessQueueSize += nSizeAdded; - pnode->fPauseRecv = pnode->nProcessQueueSize > nReceiveFloodSize; - } - WakeMessageHandler(); - } - } - else if (nBytes == 0) - { - // socket closed gracefully - if (!pnode->fDisconnect) { - LogPrint(BCLog::NET, "socket closed\n"); - } + bool notify = false; + if (!pnode->ReceiveMsgBytes(pchBuf, nBytes, notify)) pnode->CloseSocketDisconnect(); - } - else if (nBytes < 0) - { - // error - int nErr = WSAGetLastError(); - if (nErr != WSAEWOULDBLOCK && nErr != WSAEMSGSIZE && nErr != WSAEINTR && nErr != WSAEINPROGRESS) + RecordBytesRecv(nBytes); + if (notify) { + size_t nSizeAdded = 0; + auto it(pnode->vRecvMsg.begin()); + for (; it != pnode->vRecvMsg.end(); ++it) { + if (!it->complete()) + break; + nSizeAdded += it->vRecv.size() + CMessageHeader::HEADER_SIZE; + } { - if (!pnode->fDisconnect) - LogPrintf("socket recv error %s\n", NetworkErrorString(nErr)); - pnode->CloseSocketDisconnect(); + LOCK(pnode->cs_vProcessMsg); + pnode->vProcessMsg.splice(pnode->vProcessMsg.end(), pnode->vRecvMsg, pnode->vRecvMsg.begin(), it); + pnode->nProcessQueueSize += nSizeAdded; + pnode->fPauseRecv = pnode->nProcessQueueSize > nReceiveFloodSize; } + WakeMessageHandler(); } } - - // - // Send - // - if (sendSet) + else if (nBytes == 0) { - LOCK(pnode->cs_vSend); - size_t nBytes = SocketSendData(pnode); - if (nBytes) { - RecordBytesSent(nBytes); + // socket closed gracefully + if (!pnode->fDisconnect) { + LogPrint(BCLog::NET, "socket closed\n"); } + pnode->CloseSocketDisconnect(); } - - // - // Inactivity checking - // - int64_t nTime = GetSystemTimeInSeconds(); - if (nTime - pnode->nTimeConnected > 60) + else if (nBytes < 0) { - if (pnode->nLastRecv == 0 || pnode->nLastSend == 0) - { - LogPrint(BCLog::NET, "socket no message in first 60 seconds, %d %d from %d\n", pnode->nLastRecv != 0, pnode->nLastSend != 0, pnode->GetId()); - pnode->fDisconnect = true; - } - else if (nTime - pnode->nLastSend > TIMEOUT_INTERVAL) - { - LogPrintf("socket sending timeout: %is\n", nTime - pnode->nLastSend); - pnode->fDisconnect = true; - } - else if (nTime - pnode->nLastRecv > (pnode->nVersion > BIP0031_VERSION ? TIMEOUT_INTERVAL : 90*60)) - { - LogPrintf("socket receive timeout: %is\n", nTime - pnode->nLastRecv); - pnode->fDisconnect = true; - } - else if (pnode->nPingNonceSent && pnode->nPingUsecStart + TIMEOUT_INTERVAL * 1000000 < GetTimeMicros()) - { - LogPrintf("ping timeout: %fs\n", 0.000001 * (GetTimeMicros() - pnode->nPingUsecStart)); - pnode->fDisconnect = true; - } - else if (!pnode->fSuccessfullyConnected) + // error + int nErr = WSAGetLastError(); + if (nErr != WSAEWOULDBLOCK && nErr != WSAEMSGSIZE && nErr != WSAEINTR && nErr != WSAEINPROGRESS) { - LogPrint(BCLog::NET, "version handshake timeout from %d\n", pnode->GetId()); - pnode->fDisconnect = true; + if (!pnode->fDisconnect) + LogPrintf("socket recv error %s\n", NetworkErrorString(nErr)); + pnode->CloseSocketDisconnect(); } } } + + // + // Send + // + if (sendSet) { - LOCK(cs_vNodes); - for (CNode* pnode : vNodesCopy) - pnode->Release(); + LOCK(pnode->cs_vSend); + size_t nBytes = SocketSendData(pnode); + if (nBytes) { + RecordBytesSent(nBytes); + } } + + InactivityCheck(pnode); + } + { + LOCK(cs_vNodes); + for (CNode* pnode : vNodesCopy) + pnode->Release(); + } +} + +void CConnman::ThreadSocketHandler() +{ + while (!interruptNet) + { + DisconnectNodes(); + NotifyNumConnectionsChanged(); + SocketHandler(); } } @@ -1688,12 +1605,6 @@ void CConnman::DumpAddresses() addrman.size(), GetTimeMillis() - nStart); } -void CConnman::DumpData() -{ - DumpAddresses(); - DumpBanlist(); -} - void CConnman::ProcessOneShot() { std::string strDest; @@ -1864,7 +1775,7 @@ void CConnman::ThreadOpenConnections(const std::vector<std::string> connect) if (nTries > 100) break; - if (IsLimited(addr)) + if (!IsReachable(addr)) continue; // only consider very recently tried nodes after 30 failed attempts @@ -1998,7 +1909,7 @@ void CConnman::OpenNetworkConnection(const CAddress& addrConnect, bool fCountFai } if (!pszDest) { if (IsLocal(addrConnect) || - FindNode(static_cast<CNetAddr>(addrConnect)) || IsBanned(addrConnect) || + FindNode(static_cast<CNetAddr>(addrConnect)) || (m_banman && m_banman->IsBanned(addrConnect)) || FindNode(addrConnect.ToStringIPPort())) return; } else if (FindNode(std::string(pszDest))) @@ -2213,13 +2124,6 @@ void CConnman::SetNetworkActive(bool active) CConnman::CConnman(uint64_t nSeed0In, uint64_t nSeed1In) : nSeed0(nSeed0In), nSeed1(nSeed1In) { - fNetworkActive = true; - setBannedIsDirty = false; - fAddressesInitialized = false; - nLastNodeId = 0; - nSendBufferMaxSize = 0; - nReceiveFloodSize = 0; - flagInterruptMsgProc = false; SetTryNewOutboundPeer(false); Options connOptions; @@ -2233,7 +2137,7 @@ NodeId CConnman::GetNewNodeId() bool CConnman::Bind(const CService &addr, unsigned int flags) { - if (!(flags & BF_EXPLICIT) && IsLimited(addr)) + if (!(flags & BF_EXPLICIT) && !IsReachable(addr)) return false; std::string strError; if (!BindListenPort(addr, strError, (flags & BF_WHITELIST) != 0)) { @@ -2306,24 +2210,6 @@ bool CConnman::Start(CScheduler& scheduler, const Options& connOptions) DumpAddresses(); } } - if (clientInterface) - clientInterface->InitMessage(_("Loading banlist...")); - // Load addresses from banlist.dat - nStart = GetTimeMillis(); - CBanDB bandb; - banmap_t banmap; - if (bandb.Read(banmap)) { - SetBanned(banmap); // thread save setter - SetBannedSetDirty(false); // no need to write down, just read data - SweepBanned(); // sweep out unused entries - - LogPrint(BCLog::NET, "Loaded %d banned node ips/subnets from banlist.dat %dms\n", - banmap.size(), GetTimeMillis() - nStart); - } else { - LogPrintf("Invalid or missing banlist.dat; recreating\n"); - SetBannedSetDirty(true); // force write - DumpBanlist(); - } uiInterface.InitMessage(_("Starting network threads...")); @@ -2377,7 +2263,7 @@ bool CConnman::Start(CScheduler& scheduler, const Options& connOptions) threadMessageHandler = std::thread(&TraceThread<std::function<void()> >, "msghand", std::function<void()>(std::bind(&CConnman::ThreadMessageHandler, this))); // Dump network addresses - scheduler.scheduleEvery(std::bind(&CConnman::DumpData, this), DUMP_ADDRESSES_INTERVAL * 1000); + scheduler.scheduleEvery(std::bind(&CConnman::DumpAddresses, this), DUMP_PEERS_INTERVAL * 1000); return true; } @@ -2436,7 +2322,7 @@ void CConnman::Stop() if (fAddressesInitialized) { - DumpData(); + DumpAddresses(); fAddressesInitialized = false; } @@ -2563,6 +2449,25 @@ bool CConnman::DisconnectNode(const std::string& strNode) } return false; } + +bool CConnman::DisconnectNode(const CSubNet& subnet) +{ + bool disconnected = false; + LOCK(cs_vNodes); + for (CNode* pnode : vNodes) { + if (subnet.Match(pnode->addr)) { + pnode->fDisconnect = true; + disconnected = true; + } + } + return disconnected; +} + +bool CConnman::DisconnectNode(const CNetAddr& addr) +{ + return DisconnectNode(CSubNet(addr)); +} + bool CConnman::DisconnectNode(NodeId id) { LOCK(cs_vNodes); @@ -2700,8 +2605,8 @@ int CConnman::GetBestHeight() const unsigned int CConnman::GetReceiveFloodSize() const { return nReceiveFloodSize; } -CNode::CNode(NodeId idIn, ServiceFlags nLocalServicesIn, int nMyStartingHeightIn, SOCKET hSocketIn, const CAddress& addrIn, uint64_t nKeyedNetGroupIn, uint64_t nLocalHostNonceIn, const CAddress &addrBindIn, const std::string& addrNameIn, bool fInboundIn) : - nTimeConnected(GetSystemTimeInSeconds()), +CNode::CNode(NodeId idIn, ServiceFlags nLocalServicesIn, int nMyStartingHeightIn, SOCKET hSocketIn, const CAddress& addrIn, uint64_t nKeyedNetGroupIn, uint64_t nLocalHostNonceIn, const CAddress& addrBindIn, const std::string& addrNameIn, bool fInboundIn) + : nTimeConnected(GetSystemTimeInSeconds()), addr(addrIn), addrBind(addrBindIn), fInbound(fInboundIn), @@ -2711,56 +2616,14 @@ CNode::CNode(NodeId idIn, ServiceFlags nLocalServicesIn, int nMyStartingHeightIn id(idIn), nLocalHostNonce(nLocalHostNonceIn), nLocalServices(nLocalServicesIn), - nMyStartingHeight(nMyStartingHeightIn), - nSendVersion(0) + nMyStartingHeight(nMyStartingHeightIn) { - nServices = NODE_NONE; hSocket = hSocketIn; - nRecvVersion = INIT_PROTO_VERSION; - nLastSend = 0; - nLastRecv = 0; - nSendBytes = 0; - nRecvBytes = 0; - nTimeOffset = 0; addrName = addrNameIn == "" ? addr.ToStringIPPort() : addrNameIn; - nVersion = 0; strSubVer = ""; - fWhitelisted = false; - fOneShot = false; - m_manual_connection = false; - fClient = false; // set by version message - m_limited_node = false; // set by version message - fFeeler = false; - fSuccessfullyConnected = false; - fDisconnect = false; - nRefCount = 0; - nSendSize = 0; - nSendOffset = 0; hashContinue = uint256(); - nStartingHeight = -1; filterInventoryKnown.reset(); - fSendMempool = false; - fGetAddr = false; - nNextLocalAddrSend = 0; - nNextAddrSend = 0; - nNextInvSend = 0; - fRelayTxes = false; - fSentAddr = false; pfilter = MakeUnique<CBloomFilter>(); - timeLastMempoolReq = 0; - nLastBlockTime = 0; - nLastTXTime = 0; - nPingNonceSent = 0; - nPingUsecStart = 0; - nPingUsecTime = 0; - fPingQueued = false; - nMinPingUsecTime = std::numeric_limits<int64_t>::max(); - minFeeFilter = 0; - lastSentFeeFilter = 0; - nextSendTimeFeeFilter = 0; - fPauseRecv = false; - fPauseSend = false; - nProcessQueueSize = 0; for (const std::string &msg : getAllNetMessageTypes()) mapRecvBytesPerMsgCmd[msg] = 0; @@ -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. @@ -11,6 +11,7 @@ #include <amount.h> #include <bloom.h> #include <compat.h> +#include <crypto/siphash.h> #include <hash.h> #include <limitedmap.h> #include <netaddress.h> @@ -36,6 +37,7 @@ class CScheduler; class CNode; +class BanMan; /** Time between pings automatically sent out for latency probing and keepalive (in seconds). */ static const int PING_INTERVAL = 2 * 60; @@ -77,14 +79,13 @@ static const uint64_t DEFAULT_MAX_UPLOAD_TARGET = 0; static const uint64_t MAX_UPLOAD_TIMEFRAME = 60 * 60 * 24; /** Default for blocks only*/ static const bool DEFAULT_BLOCKSONLY = false; +/** -peertimeout default */ +static const int64_t DEFAULT_PEER_CONNECT_TIMEOUT = 60; static const bool DEFAULT_FORCEDNSSEED = false; static const size_t DEFAULT_MAXRECEIVEBUFFER = 5 * 1000; static const size_t DEFAULT_MAXSENDBUFFER = 1 * 1000; -// NOTE: When adjusting this, update rpcnet:setban's help ("24h") -static const unsigned int DEFAULT_MISBEHAVING_BANTIME = 60 * 60 * 24; // Default 24-hour ban - typedef int64_t NodeId; struct AddedNodeInfo @@ -111,6 +112,7 @@ struct CSerializedNetMsg std::string command; }; + class NetEventsInterface; class CConnman { @@ -133,10 +135,12 @@ public: int nBestHeight = 0; CClientUIInterface* uiInterface = nullptr; NetEventsInterface* m_msgproc = nullptr; + BanMan* m_banman = nullptr; unsigned int nSendBufferMaxSize = 0; unsigned int nReceiveFloodSize = 0; uint64_t nMaxOutboundTimeframe = 0; uint64_t nMaxOutboundLimit = 0; + int64_t m_peer_connect_timeout = DEFAULT_PEER_CONNECT_TIMEOUT; std::vector<std::string> vSeedNodes; std::vector<CSubNet> vWhitelistedRange; std::vector<CService> vBinds, vWhiteBinds; @@ -154,9 +158,11 @@ public: nMaxFeeler = connOptions.nMaxFeeler; nBestHeight = connOptions.nBestHeight; clientInterface = connOptions.uiInterface; + m_banman = connOptions.m_banman; m_msgproc = connOptions.m_msgproc; nSendBufferMaxSize = connOptions.nSendBufferMaxSize; nReceiveFloodSize = connOptions.nReceiveFloodSize; + m_peer_connect_timeout = connOptions.m_peer_connect_timeout; { LOCK(cs_totalBytesSent); nMaxOutboundTimeframe = connOptions.nMaxOutboundTimeframe; @@ -233,30 +239,6 @@ public: void AddNewAddresses(const std::vector<CAddress>& vAddr, const CAddress& addrFrom, int64_t nTimePenalty = 0); std::vector<CAddress> GetAddresses(); - // Denial-of-service detection/prevention - // The idea is to detect peers that are behaving - // badly and disconnect/ban them, but do it in a - // one-coding-mistake-won't-shatter-the-entire-network - // way. - // IMPORTANT: There should be nothing I can give a - // node that it will forward on that will make that - // node's peers drop it. If there is, an attacker - // can isolate a node and/or try to split the network. - // Dropping a node for sending stuff that is invalid - // now but might be valid in a later version is also - // dangerous, because it can cause a network split - // between nodes running old code and nodes running - // new code. - void Ban(const CNetAddr& netAddr, const BanReason& reason, int64_t bantimeoffset = 0, bool sinceUnixEpoch = false); - void Ban(const CSubNet& subNet, const BanReason& reason, int64_t bantimeoffset = 0, bool sinceUnixEpoch = false); - void ClearBanned(); // needed for unit testing - bool IsBanned(CNetAddr ip); - bool IsBanned(CSubNet subnet); - bool Unban(const CNetAddr &ip); - bool Unban(const CSubNet &ip); - void GetBanned(banmap_t &banmap); - void SetBanned(const banmap_t &banmap); - // This allows temporarily exceeding nMaxOutbound, with the goal of finding // a peer that is better than all our current peers. void SetTryNewOutboundPeer(bool flag); @@ -277,6 +259,8 @@ public: size_t GetNodeCount(NumConnections num); void GetNodeStats(std::vector<CNodeStats>& vstats); bool DisconnectNode(const std::string& node); + bool DisconnectNode(const CSubNet& subnet); + bool DisconnectNode(const CNetAddr& addr); bool DisconnectNode(NodeId id); ServiceFlags GetLocalServices() const; @@ -338,6 +322,12 @@ private: void ThreadOpenConnections(std::vector<std::string> connect); void ThreadMessageHandler(); void AcceptConnection(const ListenSocket& hListenSocket); + void DisconnectNodes(); + void NotifyNumConnectionsChanged(); + void InactivityCheck(CNode *pnode); + bool GenerateSelectSet(std::set<SOCKET> &recv_set, std::set<SOCKET> &send_set, std::set<SOCKET> &error_set); + void SocketEvents(std::set<SOCKET> &recv_set, std::set<SOCKET> &send_set, std::set<SOCKET> &error_set); + void SocketHandler(); void ThreadSocketHandler(); void ThreadDNSAddressSeed(); @@ -357,15 +347,7 @@ private: NodeId GetNewNodeId(); size_t SocketSendData(CNode *pnode) const; - //!check is the banlist has unwritten changes - bool BannedSetIsDirty(); - //!set the "dirty" flag for the banlist - void SetBannedSetDirty(bool dirty=true); - //!clean unused entries (if bantime has expired) - void SweepBanned(); void DumpAddresses(); - void DumpData(); - void DumpBanlist(); // Network stats void RecordBytesRecv(uint64_t bytes); @@ -386,28 +368,29 @@ private: uint64_t nMaxOutboundLimit GUARDED_BY(cs_totalBytesSent); uint64_t nMaxOutboundTimeframe GUARDED_BY(cs_totalBytesSent); + // P2P timeout in seconds + int64_t m_peer_connect_timeout; + // Whitelisted ranges. Any node connecting from these is automatically // whitelisted (as well as those connecting to whitelisted binds). std::vector<CSubNet> vWhitelistedRange; - unsigned int nSendBufferMaxSize; - unsigned int nReceiveFloodSize; + unsigned int nSendBufferMaxSize{0}; + unsigned int nReceiveFloodSize{0}; std::vector<ListenSocket> vhListenSocket; - std::atomic<bool> fNetworkActive; - banmap_t setBanned; - CCriticalSection cs_setBanned; - bool setBannedIsDirty; - bool fAddressesInitialized; + std::atomic<bool> fNetworkActive{true}; + bool fAddressesInitialized{false}; CAddrMan addrman; - std::deque<std::string> vOneShots; + std::deque<std::string> vOneShots GUARDED_BY(cs_vOneShots); CCriticalSection cs_vOneShots; std::vector<std::string> vAddedNodes GUARDED_BY(cs_vAddedNodes); CCriticalSection cs_vAddedNodes; std::vector<CNode*> vNodes; std::list<CNode*> vNodesDisconnected; mutable CCriticalSection cs_vNodes; - std::atomic<NodeId> nLastNodeId; + std::atomic<NodeId> nLastNodeId{0}; + unsigned int nPrevNodeCount{0}; /** Services this instance offers */ ServiceFlags nLocalServices; @@ -422,6 +405,7 @@ private: std::atomic<int> nBestHeight; CClientUIInterface* clientInterface; NetEventsInterface* m_msgproc; + BanMan* m_banman; /** SipHasher seeds for deterministic randomness */ const uint64_t nSeed0, nSeed1; @@ -431,7 +415,7 @@ private: std::condition_variable condMsgProc; Mutex mutexMsgProc; - std::atomic<bool> flagInterruptMsgProc; + std::atomic<bool> flagInterruptMsgProc{false}; CThreadInterrupt interruptNet; @@ -451,6 +435,7 @@ private: friend struct CConnmanTest; }; extern std::unique_ptr<CConnman> g_connman; +extern std::unique_ptr<BanMan> g_banman; void Discover(); void StartMapPort(); void InterruptMapPort(); @@ -505,17 +490,23 @@ enum bool IsPeerAddrLocalGood(CNode *pnode); void AdvertiseLocal(CNode *pnode); -void SetLimited(enum Network net, bool fLimited = true); -bool IsLimited(enum Network net); -bool IsLimited(const CNetAddr& addr); + +/** + * Mark a network as reachable or unreachable (no automatic connects to it) + * @note Networks are reachable by default + */ +void SetReachable(enum Network net, bool reachable); +/** @returns true if the network is reachable, false otherwise */ +bool IsReachable(enum Network net); +/** @returns true if the address is in a reachable network, false otherwise */ +bool IsReachable(const CNetAddr& addr); + bool AddLocal(const CService& addr, int nScore = LOCAL_NONE); bool AddLocal(const CNetAddr& addr, int nScore = LOCAL_NONE); void RemoveLocal(const CService& addr); bool SeenLocal(const CService& addr); bool IsLocal(const CService& addr); bool GetLocal(CService &addr, const CNetAddr *paddrPeer = nullptr); -bool IsReachable(enum Network net); -bool IsReachable(const CNetAddr &addr); CAddress GetLocalAddress(const CNetAddr *paddrPeer, ServiceFlags nLocalServices); @@ -534,7 +525,9 @@ struct LocalServiceInfo { }; extern CCriticalSection cs_mapLocalHost; -extern std::map<CNetAddr, LocalServiceInfo> mapLocalHost; +extern std::map<CNetAddr, LocalServiceInfo> mapLocalHost GUARDED_BY(cs_mapLocalHost); + +extern const std::string NET_MESSAGE_COMMAND_OTHER; typedef std::map<std::string, uint64_t> mapMsgCmdSize; //command, total bytes class CNodeStats @@ -623,123 +616,124 @@ class CNode friend class CConnman; public: // socket - std::atomic<ServiceFlags> nServices; - SOCKET hSocket; - size_t nSendSize; // total size of all vSendMsg entries - size_t nSendOffset; // offset inside the first vSendMsg already sent - uint64_t nSendBytes; - std::deque<std::vector<unsigned char>> vSendMsg; + std::atomic<ServiceFlags> nServices{NODE_NONE}; + SOCKET hSocket GUARDED_BY(cs_hSocket); + size_t nSendSize{0}; // total size of all vSendMsg entries + size_t nSendOffset{0}; // offset inside the first vSendMsg already sent + uint64_t nSendBytes GUARDED_BY(cs_vSend){0}; + std::deque<std::vector<unsigned char>> vSendMsg GUARDED_BY(cs_vSend); CCriticalSection cs_vSend; CCriticalSection cs_hSocket; CCriticalSection cs_vRecv; CCriticalSection cs_vProcessMsg; - std::list<CNetMessage> vProcessMsg; - size_t nProcessQueueSize; + std::list<CNetMessage> vProcessMsg GUARDED_BY(cs_vProcessMsg); + size_t nProcessQueueSize{0}; CCriticalSection cs_sendProcessing; std::deque<CInv> vRecvGetData; - uint64_t nRecvBytes; - std::atomic<int> nRecvVersion; + uint64_t nRecvBytes GUARDED_BY(cs_vRecv){0}; + std::atomic<int> nRecvVersion{INIT_PROTO_VERSION}; - std::atomic<int64_t> nLastSend; - std::atomic<int64_t> nLastRecv; + std::atomic<int64_t> nLastSend{0}; + std::atomic<int64_t> nLastRecv{0}; const int64_t nTimeConnected; - std::atomic<int64_t> nTimeOffset; + std::atomic<int64_t> nTimeOffset{0}; // Address of this peer const CAddress addr; // Bind address of our side of the connection const CAddress addrBind; - std::atomic<int> nVersion; + std::atomic<int> nVersion{0}; // strSubVer is whatever byte array we read from the wire. However, this field is intended // to be printed out, displayed to humans in various forms and so on. So we sanitize it and // store the sanitized version in cleanSubVer. The original should be used when dealing with // the network or wire types and the cleaned string used when displayed or logged. - std::string strSubVer, cleanSubVer; + std::string strSubVer GUARDED_BY(cs_SubVer), cleanSubVer GUARDED_BY(cs_SubVer); CCriticalSection cs_SubVer; // used for both cleanSubVer and strSubVer - bool fWhitelisted; // This peer can bypass DoS banning. - bool fFeeler; // If true this node is being used as a short lived feeler. - bool fOneShot; - bool m_manual_connection; - bool fClient; - bool m_limited_node; //after BIP159 + bool fWhitelisted{false}; // This peer can bypass DoS banning. + bool fFeeler{false}; // If true this node is being used as a short lived feeler. + bool fOneShot{false}; + bool m_manual_connection{false}; + bool fClient{false}; // set by version message + bool m_limited_node{false}; //after BIP159, set by version message const bool fInbound; - std::atomic_bool fSuccessfullyConnected; - std::atomic_bool fDisconnect; + std::atomic_bool fSuccessfullyConnected{false}; + // Setting fDisconnect to true will cause the node to be disconnected the + // next time DisconnectNodes() runs + std::atomic_bool fDisconnect{false}; // We use fRelayTxes for two purposes - // a) it allows us to not relay tx invs before receiving the peer's version message // b) the peer may tell us in its version message that we should not relay tx invs // unless it loads a bloom filter. - bool fRelayTxes; //protected by cs_filter - bool fSentAddr; + bool fRelayTxes GUARDED_BY(cs_filter){false}; + bool fSentAddr{false}; CSemaphoreGrant grantOutbound; - CCriticalSection cs_filter; - std::unique_ptr<CBloomFilter> pfilter; - std::atomic<int> nRefCount; + mutable CCriticalSection cs_filter; + std::unique_ptr<CBloomFilter> pfilter PT_GUARDED_BY(cs_filter); + std::atomic<int> nRefCount{0}; const uint64_t nKeyedNetGroup; - std::atomic_bool fPauseRecv; - std::atomic_bool fPauseSend; -protected: + std::atomic_bool fPauseRecv{false}; + std::atomic_bool fPauseSend{false}; +protected: mapMsgCmdSize mapSendBytesPerMsgCmd; - mapMsgCmdSize mapRecvBytesPerMsgCmd; + mapMsgCmdSize mapRecvBytesPerMsgCmd GUARDED_BY(cs_vRecv); public: uint256 hashContinue; - std::atomic<int> nStartingHeight; + std::atomic<int> nStartingHeight{-1}; // flood relay std::vector<CAddress> vAddrToSend; CRollingBloomFilter addrKnown; - bool fGetAddr; + bool fGetAddr{false}; std::set<uint256> setKnown; - int64_t nNextAddrSend; - int64_t nNextLocalAddrSend; + int64_t nNextAddrSend GUARDED_BY(cs_sendProcessing){0}; + int64_t nNextLocalAddrSend GUARDED_BY(cs_sendProcessing){0}; // inventory based relay - CRollingBloomFilter filterInventoryKnown; + CRollingBloomFilter filterInventoryKnown GUARDED_BY(cs_inventory); // Set of transaction ids we still have to announce. // They are sorted by the mempool before relay, so the order is not important. std::set<uint256> setInventoryTxToSend; // List of block ids we still have announce. // There is no final sorting before sending, as they are always sent immediately // and in the order requested. - std::vector<uint256> vInventoryBlockToSend; + std::vector<uint256> vInventoryBlockToSend GUARDED_BY(cs_inventory); CCriticalSection cs_inventory; std::set<uint256> setAskFor; std::multimap<int64_t, CInv> mapAskFor; - int64_t nNextInvSend; + int64_t nNextInvSend{0}; // Used for headers announcements - unfiltered blocks to relay - // Also protected by cs_inventory - std::vector<uint256> vBlockHashesToAnnounce; - // Used for BIP35 mempool sending, also protected by cs_inventory - bool fSendMempool; + std::vector<uint256> vBlockHashesToAnnounce GUARDED_BY(cs_inventory); + // Used for BIP35 mempool sending + bool fSendMempool GUARDED_BY(cs_inventory){false}; // Last time a "MEMPOOL" request was serviced. - std::atomic<int64_t> timeLastMempoolReq; + std::atomic<int64_t> timeLastMempoolReq{0}; // Block and TXN accept times - std::atomic<int64_t> nLastBlockTime; - std::atomic<int64_t> nLastTXTime; + std::atomic<int64_t> nLastBlockTime{0}; + std::atomic<int64_t> nLastTXTime{0}; // Ping time measurement: // The pong reply we're expecting, or 0 if no pong expected. - std::atomic<uint64_t> nPingNonceSent; + std::atomic<uint64_t> nPingNonceSent{0}; // Time (in usec) the last ping was sent, or 0 if no ping was ever sent. - std::atomic<int64_t> nPingUsecStart; + std::atomic<int64_t> nPingUsecStart{0}; // Last measured round-trip time. - std::atomic<int64_t> nPingUsecTime; + std::atomic<int64_t> nPingUsecTime{0}; // Best measured round-trip time. - std::atomic<int64_t> nMinPingUsecTime; + std::atomic<int64_t> nMinPingUsecTime{std::numeric_limits<int64_t>::max()}; // Whether a ping is requested. - std::atomic<bool> fPingQueued; + std::atomic<bool> fPingQueued{false}; // Minimum fee rate with which to filter inv's to this node - CAmount minFeeFilter; + CAmount minFeeFilter GUARDED_BY(cs_feeFilter){0}; CCriticalSection cs_feeFilter; - CAmount lastSentFeeFilter; - int64_t nextSendTimeFeeFilter; + CAmount lastSentFeeFilter{0}; + int64_t nextSendTimeFeeFilter{0}; CNode(NodeId id, ServiceFlags nLocalServicesIn, int nMyStartingHeightIn, SOCKET hSocketIn, const CAddress &addrIn, uint64_t nKeyedNetGroupIn, uint64_t nLocalHostNonceIn, const CAddress &addrBindIn, const std::string &addrNameIn = "", bool fInboundIn = false); ~CNode(); @@ -752,14 +746,14 @@ private: // Services offered to this peer const ServiceFlags nLocalServices; const int nMyStartingHeight; - int nSendVersion; + int nSendVersion{0}; std::list<CNetMessage> vRecvMsg; // Used only by SocketHandler thread mutable CCriticalSection cs_addrName; - std::string addrName; + std::string addrName GUARDED_BY(cs_addrName); // Our address, as reported by the peer - CService addrLocal; + CService addrLocal GUARDED_BY(cs_addrLocal); mutable CCriticalSection cs_addrLocal; public: diff --git a/src/net_processing.cpp b/src/net_processing.cpp index a1b6e021ae..62b7d4e966 100644 --- a/src/net_processing.cpp +++ b/src/net_processing.cpp @@ -6,6 +6,7 @@ #include <net_processing.h> #include <addrman.h> +#include <banman.h> #include <arith_uint256.h> #include <blockencodings.h> #include <chainparams.h> @@ -25,9 +26,9 @@ #include <tinyformat.h> #include <txmempool.h> #include <ui_interface.h> -#include <util.h> -#include <utilmoneystr.h> -#include <utilstrencodings.h> +#include <util/system.h> +#include <util/moneystr.h> +#include <util/strencodings.h> #include <memory> @@ -566,7 +567,7 @@ static void FindNextBlocksToDownload(NodeId nodeid, unsigned int count, std::vec return; } if (pindex->nStatus & BLOCK_HAVE_DATA || chainActive.Contains(pindex)) { - if (pindex->nChainTx) + if (pindex->HaveTxsDownloaded()) state->pindexLastCommonBlock = pindex; } else if (mapBlocksInFlight.count(pindex->GetBlockHash()) == 0) { // The block is not already downloaded, and not yet in flight. @@ -779,10 +780,11 @@ unsigned int LimitOrphanTxSize(unsigned int nMaxOrphans) nNextSweep = nMinExpTime + ORPHAN_TX_EXPIRE_INTERVAL; if (nErased > 0) LogPrint(BCLog::MEMPOOL, "Erased %d orphan tx due to expiration\n", nErased); } + FastRandomContext rng; while (mapOrphanTransactions.size() > nMaxOrphans) { // Evict a random orphan: - uint256 randomhash = GetRandHash(); + uint256 randomhash = rng.rand256(); std::map<uint256, COrphanTx>::iterator it = mapOrphanTransactions.lower_bound(randomhash); if (it == mapOrphanTransactions.end()) it = mapOrphanTransactions.begin(); @@ -840,9 +842,8 @@ static bool BlockRequestAllowed(const CBlockIndex* pindex, const Consensus::Para (GetBlockProofEquivalentTime(*pindexBestHeader, *pindex, *pindexBestHeader, consensusParams) < STALE_RELAY_AGE_LIMIT); } -PeerLogicValidation::PeerLogicValidation(CConnman* connmanIn, CScheduler &scheduler, bool enable_bip61) - : connman(connmanIn), m_stale_tip_check_time(0), m_enable_bip61(enable_bip61) { - +PeerLogicValidation::PeerLogicValidation(CConnman* connmanIn, BanMan* banman, CScheduler &scheduler, bool enable_bip61) + : connman(connmanIn), m_banman(banman), m_stale_tip_check_time(0), m_enable_bip61(enable_bip61) { // Initialize global variables that cannot be constructed at startup. recentRejects.reset(new CRollingBloomFilter(120000, 0.000001)); @@ -1124,7 +1125,7 @@ void static ProcessGetBlockData(CNode* pfrom, const CChainParams& chainparams, c LOCK(cs_main); const CBlockIndex* pindex = LookupBlockIndex(inv.hash); if (pindex) { - if (pindex->nChainTx && !pindex->IsValid(BLOCK_VALID_SCRIPTS) && + if (pindex->HaveTxsDownloaded() && !pindex->IsValid(BLOCK_VALID_SCRIPTS) && pindex->IsValid(BLOCK_VALID_TREE)) { // If we have the block and all of its parents, but have not yet validated it, // we might be in the middle of connecting it (ie in the unlock of cs_main @@ -2357,6 +2358,23 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr for (const CTransactionRef& removedTx : lRemovedTxn) AddToCompactExtraTransactions(removedTx); + // If a tx has been detected by recentRejects, we will have reached + // this point and the tx will have been ignored. Because we haven't run + // the tx through AcceptToMemoryPool, we won't have computed a DoS + // score for it or determined exactly why we consider it invalid. + // + // This means we won't penalize any peer subsequently relaying a DoSy + // tx (even if we penalized the first peer who gave it to us) because + // we have to account for recentRejects showing false positives. In + // other words, we shouldn't penalize a peer if we aren't *sure* they + // submitted a DoSy tx. + // + // Note that recentRejects doesn't just record DoSy or invalid + // transactions, but any tx not accepted by the mempool, which may be + // due to node policy (vs. consensus). So we can't blanket penalize a + // peer simply for relaying a tx that our recentRejects has caught, + // regardless of false positives. + int nDoS = 0; if (state.IsInvalid(nDoS)) { @@ -2925,7 +2943,7 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr return true; } -static bool SendRejectsAndCheckIfBanned(CNode* pnode, CConnman* connman, bool enable_bip61) EXCLUSIVE_LOCKS_REQUIRED(cs_main) +bool PeerLogicValidation::SendRejectsAndCheckIfBanned(CNode* pnode, bool enable_bip61) EXCLUSIVE_LOCKS_REQUIRED(cs_main) { AssertLockHeld(cs_main); CNodeState &state = *State(pnode->GetId()); @@ -2943,14 +2961,16 @@ static bool SendRejectsAndCheckIfBanned(CNode* pnode, CConnman* connman, bool en LogPrintf("Warning: not punishing whitelisted peer %s!\n", pnode->addr.ToString()); else if (pnode->m_manual_connection) LogPrintf("Warning: not punishing manually-connected peer %s!\n", pnode->addr.ToString()); - else { + else if (pnode->addr.IsLocal()) { + // Disconnect but don't ban _this_ local node + LogPrintf("Warning: disconnecting but not banning local peer %s!\n", pnode->addr.ToString()); pnode->fDisconnect = true; - if (pnode->addr.IsLocal()) - LogPrintf("Warning: not banning local peer %s!\n", pnode->addr.ToString()); - else - { - connman->Ban(pnode->addr, BanReasonNodeMisbehaving); + } else { + // Disconnect and ban all nodes sharing the address + if (m_banman) { + m_banman->Ban(pnode->addr, BanReasonNodeMisbehaving); } + connman->DisconnectNode(pnode->addr); } return true; } @@ -3074,7 +3094,7 @@ bool PeerLogicValidation::ProcessMessages(CNode* pfrom, std::atomic<bool>& inter } LOCK(cs_main); - SendRejectsAndCheckIfBanned(pfrom, connman, m_enable_bip61); + SendRejectsAndCheckIfBanned(pfrom, m_enable_bip61); return fMoreWork; } @@ -3275,8 +3295,7 @@ bool PeerLogicValidation::SendMessages(CNode* pto) if (!lockMain) return true; - if (SendRejectsAndCheckIfBanned(pto, connman, m_enable_bip61)) - return true; + if (SendRejectsAndCheckIfBanned(pto, m_enable_bip61)) return true; CNodeState &state = *State(pto->GetId()); // Address refresh broadcast diff --git a/src/net_processing.h b/src/net_processing.h index 88dbdf556d..39c22d7118 100644 --- a/src/net_processing.h +++ b/src/net_processing.h @@ -23,9 +23,11 @@ static constexpr bool DEFAULT_ENABLE_BIP61{false}; class PeerLogicValidation final : public CValidationInterface, public NetEventsInterface { private: CConnman* const connman; + BanMan* const m_banman; + bool SendRejectsAndCheckIfBanned(CNode* pnode, bool enable_bip61) EXCLUSIVE_LOCKS_REQUIRED(cs_main); public: - explicit PeerLogicValidation(CConnman* connman, CScheduler &scheduler, bool enable_bip61); + PeerLogicValidation(CConnman* connman, BanMan* banman, CScheduler &scheduler, bool enable_bip61); /** * Overridden from CValidationInterface. @@ -71,7 +73,7 @@ public: void EvictExtraOutboundPeers(int64_t time_in_seconds) EXCLUSIVE_LOCKS_REQUIRED(cs_main); private: - int64_t m_stale_tip_check_time; //! Next time to check for stale tip + int64_t m_stale_tip_check_time; //!< Next time to check for stale tip /** Enable BIP61 (sending reject messages) */ const bool m_enable_bip61; diff --git a/src/netaddress.cpp b/src/netaddress.cpp index 778c2700f9..a0c7f8e3c2 100644 --- a/src/netaddress.cpp +++ b/src/netaddress.cpp @@ -5,7 +5,7 @@ #include <netaddress.h> #include <hash.h> -#include <utilstrencodings.h> +#include <util/strencodings.h> #include <tinyformat.h> static const unsigned char pchIPv4[12] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff }; @@ -17,7 +17,6 @@ static const unsigned char g_internal_prefix[] = { 0xFD, 0x6B, 0x88, 0xC0, 0x87, CNetAddr::CNetAddr() { memset(ip, 0, sizeof(ip)); - scopeId = 0; } void CNetAddr::SetIP(const CNetAddr& ipIn) @@ -83,6 +82,16 @@ unsigned int CNetAddr::GetByte(int n) const return ip[15-n]; } +bool CNetAddr::IsBindAny() const +{ + const int cmplen = IsIPv4() ? 4 : 16; + for (int i = 0; i < cmplen; ++i) { + if (GetByte(i)) return false; + } + + return true; +} + bool CNetAddr::IsIPv4() const { return (memcmp(ip, pchIPv4, sizeof(pchIPv4)) == 0); diff --git a/src/netaddress.h b/src/netaddress.h index cc0e4d4f12..ca435d17dc 100644 --- a/src/netaddress.h +++ b/src/netaddress.h @@ -33,7 +33,7 @@ class CNetAddr { protected: unsigned char ip[16]; // in network byte order - uint32_t scopeId; // for scoped/link-local ipv6 addresses + uint32_t scopeId{0}; // for scoped/link-local ipv6 addresses public: CNetAddr(); @@ -55,6 +55,7 @@ class CNetAddr bool SetInternal(const std::string& name); bool SetSpecial(const std::string &strName); // for Tor addresses + bool IsBindAny() const; // INADDR_ANY equivalent bool IsIPv4() const; // IPv4 mapped address (::FFFF:0:0/96, 0.0.0.0/0) bool IsIPv6() const; // IPv6 address (not mapped IPv4, not Tor) bool IsRFC1918() const; // IPv4 private networks (10.0.0.0/8, 192.168.0.0/16, 172.16.0.0/12) diff --git a/src/netbase.cpp b/src/netbase.cpp index 093fd0bdb7..355e21d4e6 100644 --- a/src/netbase.cpp +++ b/src/netbase.cpp @@ -10,8 +10,8 @@ #include <uint256.h> #include <random.h> #include <tinyformat.h> -#include <util.h> -#include <utilstrencodings.h> +#include <util/system.h> +#include <util/strencodings.h> #include <atomic> @@ -21,14 +21,18 @@ #include <codecvt> #endif +#ifdef USE_POLL +#include <poll.h> +#endif + #if !defined(MSG_NOSIGNAL) #define MSG_NOSIGNAL 0 #endif // Settings -static proxyType proxyInfo[NET_MAX]; -static proxyType nameProxy; static CCriticalSection cs_proxyInfos; +static proxyType proxyInfo[NET_MAX] GUARDED_BY(cs_proxyInfos); +static proxyType nameProxy GUARDED_BY(cs_proxyInfos); int nConnectTimeout = DEFAULT_CONNECT_TIMEOUT; bool fNameLookup = DEFAULT_NAME_LOOKUP; @@ -191,10 +195,10 @@ enum SOCKSVersion: uint8_t { /** Values defined for METHOD in RFC1928 */ enum SOCKS5Method: uint8_t { - NOAUTH = 0x00, //! No authentication required - GSSAPI = 0x01, //! GSSAPI - USER_PASS = 0x02, //! Username/password - NO_ACCEPTABLE = 0xff, //! No acceptable methods + NOAUTH = 0x00, //!< No authentication required + GSSAPI = 0x01, //!< GSSAPI + USER_PASS = 0x02, //!< Username/password + NO_ACCEPTABLE = 0xff, //!< No acceptable methods }; /** Values defined for CMD in RFC1928 */ @@ -206,15 +210,15 @@ enum SOCKS5Command: uint8_t { /** Values defined for REP in RFC1928 */ enum SOCKS5Reply: uint8_t { - SUCCEEDED = 0x00, //! Succeeded - GENFAILURE = 0x01, //! General failure - NOTALLOWED = 0x02, //! Connection not allowed by ruleset - NETUNREACHABLE = 0x03, //! Network unreachable - HOSTUNREACHABLE = 0x04, //! Network unreachable - CONNREFUSED = 0x05, //! Connection refused - TTLEXPIRED = 0x06, //! TTL expired - CMDUNSUPPORTED = 0x07, //! Command not supported - ATYPEUNSUPPORTED = 0x08, //! Address type not supported + SUCCEEDED = 0x00, //!< Succeeded + GENFAILURE = 0x01, //!< General failure + NOTALLOWED = 0x02, //!< Connection not allowed by ruleset + NETUNREACHABLE = 0x03, //!< Network unreachable + HOSTUNREACHABLE = 0x04, //!< Network unreachable + CONNREFUSED = 0x05, //!< Connection refused + TTLEXPIRED = 0x06, //!< TTL expired + CMDUNSUPPORTED = 0x07, //!< Command not supported + ATYPEUNSUPPORTED = 0x08, //!< Address type not supported }; /** Values defined for ATYPE in RFC1928 */ @@ -264,11 +268,19 @@ static IntrRecvError InterruptibleRecv(uint8_t* data, size_t len, int timeout, c if (!IsSelectableSocket(hSocket)) { return IntrRecvError::NetworkError; } - struct timeval tval = MillisToTimeval(std::min(endTime - curTime, maxWait)); + int timeout_ms = std::min(endTime - curTime, maxWait); +#ifdef USE_POLL + struct pollfd pollfd = {}; + pollfd.fd = hSocket; + pollfd.events = POLLIN | POLLOUT; + int nRet = poll(&pollfd, 1, timeout_ms); +#else + struct timeval tval = MillisToTimeval(timeout_ms); fd_set fdset; FD_ZERO(&fdset); FD_SET(hSocket, &fdset); int nRet = select(hSocket + 1, &fdset, nullptr, nullptr, &tval); +#endif if (nRet == SOCKET_ERROR) { return IntrRecvError::NetworkError; } @@ -499,11 +511,18 @@ bool ConnectSocketDirectly(const CService &addrConnect, const SOCKET& hSocket, i // WSAEINVAL is here because some legacy version of winsock uses it if (nErr == WSAEINPROGRESS || nErr == WSAEWOULDBLOCK || nErr == WSAEINVAL) { +#ifdef USE_POLL + struct pollfd pollfd = {}; + pollfd.fd = hSocket; + pollfd.events = POLLIN | POLLOUT; + int nRet = poll(&pollfd, 1, nTimeout); +#else struct timeval timeout = MillisToTimeval(nTimeout); fd_set fdset; FD_ZERO(&fdset); FD_SET(hSocket, &fdset); int nRet = select(hSocket + 1, nullptr, &fdset, nullptr, &timeout); +#endif if (nRet == 0) { LogPrint(BCLog::NET, "connection to %s timeout\n", addrConnect.ToString()); diff --git a/src/noui.cpp b/src/noui.cpp index df4bfabb66..c7d8fee0ba 100644 --- a/src/noui.cpp +++ b/src/noui.cpp @@ -6,7 +6,7 @@ #include <noui.h> #include <ui_interface.h> -#include <util.h> +#include <util/system.h> #include <cstdio> #include <stdint.h> diff --git a/src/noui.h b/src/noui.h index 169c2bbd7f..79a79a9af2 100644 --- a/src/noui.h +++ b/src/noui.h @@ -1,4 +1,4 @@ -// Copyright (c) 2013-2014 The Bitcoin Core developers +// Copyright (c) 2013-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. diff --git a/src/outputtype.h b/src/outputtype.h index 4c4d93bc8b..6c30fd1950 100644 --- a/src/outputtype.h +++ b/src/outputtype.h @@ -6,6 +6,7 @@ #ifndef BITCOIN_OUTPUTTYPE_H #define BITCOIN_OUTPUTTYPE_H +#include <attributes.h> #include <keystore.h> #include <script/standard.h> @@ -26,7 +27,7 @@ enum class OutputType { CHANGE_AUTO, }; -bool ParseOutputType(const std::string& str, OutputType& output_type); +NODISCARD bool ParseOutputType(const std::string& str, OutputType& output_type); const std::string& FormatOutputType(OutputType type); /** diff --git a/src/policy/fees.cpp b/src/policy/fees.cpp index aee6fbee1a..c49b9fa36b 100644 --- a/src/policy/fees.cpp +++ b/src/policy/fees.cpp @@ -10,7 +10,7 @@ #include <primitives/transaction.h> #include <streams.h> #include <txmempool.h> -#include <util.h> +#include <util/system.h> static constexpr double INF_FEERATE = 1e99; @@ -511,7 +511,7 @@ void TxConfirmStats::removeTx(unsigned int entryHeight, unsigned int nBestSeenHe // of no harm to try to remove them again. bool CBlockPolicyEstimator::removeTx(uint256 hash, bool inBlock) { - LOCK(cs_feeEstimator); + LOCK(m_cs_fee_estimator); std::map<uint256, TxStatsInfo>::iterator pos = mapMemPoolTxs.find(hash); if (pos != mapMemPoolTxs.end()) { feeStats->removeTx(pos->second.blockHeight, nBestSeenHeight, pos->second.bucketIndex, inBlock); @@ -548,7 +548,7 @@ CBlockPolicyEstimator::~CBlockPolicyEstimator() void CBlockPolicyEstimator::processTransaction(const CTxMemPoolEntry& entry, bool validFeeEstimate) { - LOCK(cs_feeEstimator); + LOCK(m_cs_fee_estimator); unsigned int txHeight = entry.GetHeight(); uint256 hash = entry.GetTx().GetHash(); if (mapMemPoolTxs.count(hash)) { @@ -615,7 +615,7 @@ bool CBlockPolicyEstimator::processBlockTx(unsigned int nBlockHeight, const CTxM void CBlockPolicyEstimator::processBlock(unsigned int nBlockHeight, std::vector<const CTxMemPoolEntry*>& entries) { - LOCK(cs_feeEstimator); + LOCK(m_cs_fee_estimator); if (nBlockHeight <= nBestSeenHeight) { // Ignore side chains and re-orgs; assuming they are random // they don't affect the estimate. @@ -693,7 +693,7 @@ CFeeRate CBlockPolicyEstimator::estimateRawFee(int confTarget, double successThr } } - LOCK(cs_feeEstimator); + LOCK(m_cs_fee_estimator); // Return failure if trying to analyze a target we're not tracking if (confTarget <= 0 || (unsigned int)confTarget > stats->GetMaxConfirms()) return CFeeRate(0); @@ -710,6 +710,7 @@ CFeeRate CBlockPolicyEstimator::estimateRawFee(int confTarget, double successThr unsigned int CBlockPolicyEstimator::HighestTargetTracked(FeeEstimateHorizon horizon) const { + LOCK(m_cs_fee_estimator); switch (horizon) { case FeeEstimateHorizon::SHORT_HALFLIFE: { return shortStats->GetMaxConfirms(); @@ -819,7 +820,7 @@ double CBlockPolicyEstimator::estimateConservativeFee(unsigned int doubleTarget, */ CFeeRate CBlockPolicyEstimator::estimateSmartFee(int confTarget, FeeCalculation *feeCalc, bool conservative) const { - LOCK(cs_feeEstimator); + LOCK(m_cs_fee_estimator); if (feeCalc) { feeCalc->desiredTarget = confTarget; @@ -899,7 +900,7 @@ CFeeRate CBlockPolicyEstimator::estimateSmartFee(int confTarget, FeeCalculation bool CBlockPolicyEstimator::Write(CAutoFile& fileout) const { try { - LOCK(cs_feeEstimator); + LOCK(m_cs_fee_estimator); fileout << 149900; // version required to read: 0.14.99 or later fileout << CLIENT_VERSION; // version that wrote the file fileout << nBestSeenHeight; @@ -924,7 +925,7 @@ bool CBlockPolicyEstimator::Write(CAutoFile& fileout) const bool CBlockPolicyEstimator::Read(CAutoFile& filein) { try { - LOCK(cs_feeEstimator); + LOCK(m_cs_fee_estimator); int nVersionRequired, nVersionThatWrote; filein >> nVersionRequired >> nVersionThatWrote; if (nVersionRequired > CLIENT_VERSION) @@ -983,7 +984,7 @@ bool CBlockPolicyEstimator::Read(CAutoFile& filein) void CBlockPolicyEstimator::FlushUnconfirmed() { int64_t startclear = GetTimeMicros(); - LOCK(cs_feeEstimator); + LOCK(m_cs_fee_estimator); size_t num_entries = mapMemPoolTxs.size(); // Remove every entry in mapMemPoolTxs while (!mapMemPoolTxs.empty()) { diff --git a/src/policy/fees.h b/src/policy/fees.h index 2733c5a7de..c8472a12f5 100644 --- a/src/policy/fees.h +++ b/src/policy/fees.h @@ -50,9 +50,9 @@ std::string StringForFeeReason(FeeReason reason); /* Used to determine type of fee estimation requested */ enum class FeeEstimateMode { - UNSET, //! Use default settings based on other criteria - ECONOMICAL, //! Force estimateSmartFee to use non-conservative estimates - CONSERVATIVE, //! Force estimateSmartFee to use conservative estimates + UNSET, //!< Use default settings based on other criteria + ECONOMICAL, //!< Force estimateSmartFee to use non-conservative estimates + CONSERVATIVE, //!< Force estimateSmartFee to use conservative estimates }; bool FeeModeFromString(const std::string& mode_string, FeeEstimateMode& fee_estimate_mode); @@ -228,10 +228,12 @@ public: unsigned int HighestTargetTracked(FeeEstimateHorizon horizon) const; private: - unsigned int nBestSeenHeight; - unsigned int firstRecordedHeight; - unsigned int historicalFirst; - unsigned int historicalBest; + mutable CCriticalSection m_cs_fee_estimator; + + unsigned int nBestSeenHeight GUARDED_BY(m_cs_fee_estimator); + unsigned int firstRecordedHeight GUARDED_BY(m_cs_fee_estimator); + unsigned int historicalFirst GUARDED_BY(m_cs_fee_estimator); + unsigned int historicalBest GUARDED_BY(m_cs_fee_estimator); struct TxStatsInfo { @@ -241,34 +243,32 @@ private: }; // map of txids to information about that transaction - std::map<uint256, TxStatsInfo> mapMemPoolTxs; + std::map<uint256, TxStatsInfo> mapMemPoolTxs GUARDED_BY(m_cs_fee_estimator); /** Classes to track historical data on transaction confirmations */ - std::unique_ptr<TxConfirmStats> feeStats; - std::unique_ptr<TxConfirmStats> shortStats; - std::unique_ptr<TxConfirmStats> longStats; - - unsigned int trackedTxs; - unsigned int untrackedTxs; + std::unique_ptr<TxConfirmStats> feeStats PT_GUARDED_BY(m_cs_fee_estimator); + std::unique_ptr<TxConfirmStats> shortStats PT_GUARDED_BY(m_cs_fee_estimator); + std::unique_ptr<TxConfirmStats> longStats PT_GUARDED_BY(m_cs_fee_estimator); - std::vector<double> buckets; // The upper-bound of the range for the bucket (inclusive) - std::map<double, unsigned int> bucketMap; // Map of bucket upper-bound to index into all vectors by bucket + unsigned int trackedTxs GUARDED_BY(m_cs_fee_estimator); + unsigned int untrackedTxs GUARDED_BY(m_cs_fee_estimator); - mutable CCriticalSection cs_feeEstimator; + std::vector<double> buckets GUARDED_BY(m_cs_fee_estimator); // The upper-bound of the range for the bucket (inclusive) + std::map<double, unsigned int> bucketMap GUARDED_BY(m_cs_fee_estimator); // Map of bucket upper-bound to index into all vectors by bucket /** Process a transaction confirmed in a block*/ - bool processBlockTx(unsigned int nBlockHeight, const CTxMemPoolEntry* entry); + bool processBlockTx(unsigned int nBlockHeight, const CTxMemPoolEntry* entry) EXCLUSIVE_LOCKS_REQUIRED(m_cs_fee_estimator); /** Helper for estimateSmartFee */ - double estimateCombinedFee(unsigned int confTarget, double successThreshold, bool checkShorterHorizon, EstimationResult *result) const; + double estimateCombinedFee(unsigned int confTarget, double successThreshold, bool checkShorterHorizon, EstimationResult *result) const EXCLUSIVE_LOCKS_REQUIRED(m_cs_fee_estimator); /** Helper for estimateSmartFee */ - double estimateConservativeFee(unsigned int doubleTarget, EstimationResult *result) const; + double estimateConservativeFee(unsigned int doubleTarget, EstimationResult *result) const EXCLUSIVE_LOCKS_REQUIRED(m_cs_fee_estimator); /** Number of blocks of data recorded while fee estimates have been running */ - unsigned int BlockSpan() const; + unsigned int BlockSpan() const EXCLUSIVE_LOCKS_REQUIRED(m_cs_fee_estimator); /** Number of blocks of recorded fee estimate data represented in saved data file */ - unsigned int HistoricalBlockSpan() const; + unsigned int HistoricalBlockSpan() const EXCLUSIVE_LOCKS_REQUIRED(m_cs_fee_estimator); /** Calculation of highest target that reasonable estimate can be provided for */ - unsigned int MaxUsableEstimate() const; + unsigned int MaxUsableEstimate() const EXCLUSIVE_LOCKS_REQUIRED(m_cs_fee_estimator); }; class FeeFilterRounder diff --git a/src/policy/policy.cpp b/src/policy/policy.cpp index ac1b75edb4..d4cc538492 100644 --- a/src/policy/policy.cpp +++ b/src/policy/policy.cpp @@ -11,8 +11,8 @@ #include <validation.h> #include <coins.h> #include <tinyformat.h> -#include <util.h> -#include <utilstrencodings.h> +#include <util/system.h> +#include <util/strencodings.h> CAmount GetDustThreshold(const CTxOut& txout, const CFeeRate& dustRelayFeeIn) diff --git a/src/policy/rbf.cpp b/src/policy/rbf.cpp index 18f9c0c2a8..0dc130d104 100644 --- a/src/policy/rbf.cpp +++ b/src/policy/rbf.cpp @@ -7,7 +7,7 @@ bool SignalsOptInRBF(const CTransaction &tx) { for (const CTxIn &txin : tx.vin) { - if (txin.nSequence < std::numeric_limits<unsigned int>::max()-1) { + if (txin.nSequence <= MAX_BIP125_RBF_SEQUENCE) { return true; } } diff --git a/src/prevector.h b/src/prevector.h index 6ddb6f321f..99e5751634 100644 --- a/src/prevector.h +++ b/src/prevector.h @@ -10,12 +10,11 @@ #include <stdint.h> #include <string.h> +#include <algorithm> #include <cstddef> #include <iterator> #include <type_traits> -#include <compat.h> - #pragma pack(push, 1) /** Implements a drop-in replacement for std::vector<T> which stores up to N * elements directly (without heap allocation). The types Size and Diff are @@ -197,23 +196,8 @@ private: T* item_ptr(difference_type pos) { return is_direct() ? direct_ptr(pos) : indirect_ptr(pos); } const T* item_ptr(difference_type pos) const { return is_direct() ? direct_ptr(pos) : indirect_ptr(pos); } - void fill(T* dst, ptrdiff_t count) { - if (IS_TRIVIALLY_CONSTRUCTIBLE<T>::value) { - // The most common use of prevector is where T=unsigned char. For - // trivially constructible types, we can use memset() to avoid - // looping. - ::memset(dst, 0, count * sizeof(T)); - } else { - for (auto i = 0; i < count; ++i) { - new(static_cast<void*>(dst + i)) T(); - } - } - } - - void fill(T* dst, ptrdiff_t count, const T& value) { - for (auto i = 0; i < count; ++i) { - new(static_cast<void*>(dst + i)) T(value); - } + void fill(T* dst, ptrdiff_t count, const T& value = T{}) { + std::fill_n(dst, count, value); } template<typename InputIterator> diff --git a/src/primitives/block.cpp b/src/primitives/block.cpp index fb95a66bde..a0c2e3f125 100644 --- a/src/primitives/block.cpp +++ b/src/primitives/block.cpp @@ -7,7 +7,7 @@ #include <hash.h> #include <tinyformat.h> -#include <utilstrencodings.h> +#include <util/strencodings.h> #include <crypto/common.h> uint256 CBlockHeader::GetHash() const diff --git a/src/primitives/transaction.cpp b/src/primitives/transaction.cpp index bdb470470e..28c145f71d 100644 --- a/src/primitives/transaction.cpp +++ b/src/primitives/transaction.cpp @@ -7,7 +7,7 @@ #include <hash.h> #include <tinyformat.h> -#include <utilstrencodings.h> +#include <util/strencodings.h> std::string COutPoint::ToString() const { diff --git a/src/primitives/transaction.h b/src/primitives/transaction.h index fb9db508d2..f6f8e31363 100644 --- a/src/primitives/transaction.h +++ b/src/primitives/transaction.h @@ -21,7 +21,9 @@ public: uint256 hash; uint32_t n; - COutPoint(): n((uint32_t) -1) { } + static constexpr uint32_t NULL_INDEX = std::numeric_limits<uint32_t>::max(); + + COutPoint(): n(NULL_INDEX) { } COutPoint(const uint256& hashIn, uint32_t nIn): hash(hashIn), n(nIn) { } ADD_SERIALIZE_METHODS; @@ -32,8 +34,8 @@ public: READWRITE(n); } - void SetNull() { hash.SetNull(); n = (uint32_t) -1; } - bool IsNull() const { return (hash.IsNull() && n == (uint32_t) -1); } + void SetNull() { hash.SetNull(); n = NULL_INDEX; } + bool IsNull() const { return (hash.IsNull() && n == NULL_INDEX); } friend bool operator<(const COutPoint& a, const COutPoint& b) { @@ -64,7 +66,7 @@ public: COutPoint prevout; CScript scriptSig; uint32_t nSequence; - CScriptWitness scriptWitness; //! Only serialized through CTransaction + CScriptWitness scriptWitness; //!< Only serialized through CTransaction /* Setting nSequence to this value for every input in a transaction * disables nLockTime. */ @@ -73,7 +75,7 @@ public: /* Below flags apply in the context of BIP 68*/ /* If this flag set, CTxIn::nSequence is NOT interpreted as a * relative lock-time. */ - static const uint32_t SEQUENCE_LOCKTIME_DISABLE_FLAG = (1 << 31); + static const uint32_t SEQUENCE_LOCKTIME_DISABLE_FLAG = (1U << 31); /* If CTxIn::nSequence encodes a relative lock-time and this flag * is set, the relative lock-time has units of 512 seconds, @@ -296,7 +298,7 @@ public: CTransaction(); /** Convert a CMutableTransaction into a CTransaction. */ - CTransaction(const CMutableTransaction &tx); + explicit CTransaction(const CMutableTransaction &tx); CTransaction(CMutableTransaction &&tx); template <typename Stream> diff --git a/src/protocol.cpp b/src/protocol.cpp index b4fc9def1f..bdf236c2c7 100644 --- a/src/protocol.cpp +++ b/src/protocol.cpp @@ -5,8 +5,8 @@ #include <protocol.h> -#include <util.h> -#include <utilstrencodings.h> +#include <util/system.h> +#include <util/strencodings.h> #ifndef WIN32 # include <arpa/inet.h> diff --git a/src/protocol.h b/src/protocol.h index 50d197415b..a790a06906 100644 --- a/src/protocol.h +++ b/src/protocol.h @@ -402,7 +402,6 @@ public: std::string GetCommand() const; std::string ToString() const; - // TODO: make private (improves encapsulation) public: int type; uint256 hash; diff --git a/src/qt/README.md b/src/qt/README.md index 3ec538b4f4..0eb18f7cd5 100644 --- a/src/qt/README.md +++ b/src/qt/README.md @@ -64,8 +64,8 @@ Represents the view to a single wallet. * `callback.h` * `guiconstants.h`: UI colors, app name, etc * `guiutil.h`: several helper functions -* `macdockiconhandler.(h/cpp)` -* `macdockiconhandler.(h/cpp)`: display notifications in macOS +* `macdockiconhandler.(h/mm)`: macOS dock icon handler +* `macnotificationhandler.(h/mm)`: display notifications in macOS ## Contribute diff --git a/src/qt/addressbookpage.cpp b/src/qt/addressbookpage.cpp index 26bdf60d4a..726dafccb0 100644 --- a/src/qt/addressbookpage.cpp +++ b/src/qt/addressbookpage.cpp @@ -59,7 +59,7 @@ protected: AddressBookPage::AddressBookPage(const PlatformStyle *platformStyle, Mode _mode, Tabs _tab, QWidget *parent) : QDialog(parent), ui(new Ui::AddressBookPage), - model(0), + model(nullptr), mode(_mode), tab(_tab) { diff --git a/src/qt/addressbookpage.h b/src/qt/addressbookpage.h index 32ab2dfeb5..6394d26801 100644 --- a/src/qt/addressbookpage.h +++ b/src/qt/addressbookpage.h @@ -38,7 +38,7 @@ public: ForEditing /**< Open address book for editing */ }; - explicit AddressBookPage(const PlatformStyle *platformStyle, Mode mode, Tabs tab, QWidget *parent = 0); + explicit AddressBookPage(const PlatformStyle *platformStyle, Mode mode, Tabs tab, QWidget *parent = nullptr); ~AddressBookPage(); void setModel(AddressTableModel *model); diff --git a/src/qt/addresstablemodel.cpp b/src/qt/addresstablemodel.cpp index 2f88e15d21..c3143e948a 100644 --- a/src/qt/addresstablemodel.cpp +++ b/src/qt/addresstablemodel.cpp @@ -153,7 +153,7 @@ public: } else { - return 0; + return nullptr; } } }; @@ -300,8 +300,8 @@ QVariant AddressTableModel::headerData(int section, Qt::Orientation orientation, Qt::ItemFlags AddressTableModel::flags(const QModelIndex &index) const { - if(!index.isValid()) - return 0; + if (!index.isValid()) return Qt::NoItemFlags; + AddressTableEntry *rec = static_cast<AddressTableEntry*>(index.internalPointer()); Qt::ItemFlags retval = Qt::ItemIsSelectable | Qt::ItemIsEnabled; diff --git a/src/qt/addresstablemodel.h b/src/qt/addresstablemodel.h index 41bed4e290..035f3e0571 100644 --- a/src/qt/addresstablemodel.h +++ b/src/qt/addresstablemodel.h @@ -25,7 +25,7 @@ class AddressTableModel : public QAbstractTableModel Q_OBJECT public: - explicit AddressTableModel(WalletModel *parent = 0); + explicit AddressTableModel(WalletModel *parent = nullptr); ~AddressTableModel(); enum ColumnIndex { diff --git a/src/qt/askpassphrasedialog.cpp b/src/qt/askpassphrasedialog.cpp index a0270daf99..a89a15bc9d 100644 --- a/src/qt/askpassphrasedialog.cpp +++ b/src/qt/askpassphrasedialog.cpp @@ -22,7 +22,7 @@ AskPassphraseDialog::AskPassphraseDialog(Mode _mode, QWidget *parent) : QDialog(parent), ui(new Ui::AskPassphraseDialog), mode(_mode), - model(0), + model(nullptr), fCapsLock(false) { ui->setupUi(this); @@ -152,14 +152,15 @@ void AskPassphraseDialog::accept() } } break; case Unlock: - if(!model->setWalletLocked(false, oldpass)) - { - QMessageBox::critical(this, tr("Wallet unlock failed"), - tr("The passphrase entered for the wallet decryption was incorrect.")); - } - else - { - QDialog::accept(); // Success + try { + if (!model->setWalletLocked(false, oldpass)) { + QMessageBox::critical(this, tr("Wallet unlock failed"), + tr("The passphrase entered for the wallet decryption was incorrect.")); + } else { + QDialog::accept(); // Success + } + } catch (const std::runtime_error& e) { + QMessageBox::critical(this, tr("Wallet unlock failed"), e.what()); } break; case Decrypt: diff --git a/src/qt/bantablemodel.cpp b/src/qt/bantablemodel.cpp index 97348aad2b..00c446cc63 100644 --- a/src/qt/bantablemodel.cpp +++ b/src/qt/bantablemodel.cpp @@ -10,7 +10,7 @@ #include <interfaces/node.h> #include <sync.h> -#include <utiltime.h> +#include <util/time.h> #include <QDebug> #include <QList> @@ -40,8 +40,8 @@ class BanTablePriv public: /** Local cache of peer information */ QList<CCombinedBan> cachedBanlist; - /** Column to sort nodes by */ - int sortColumn; + /** Column to sort nodes by (default to unsorted) */ + int sortColumn{-1}; /** Order (ascending or descending) to sort nodes by */ Qt::SortOrder sortOrder; @@ -76,7 +76,7 @@ public: if (idx >= 0 && idx < cachedBanlist.size()) return &cachedBanlist[idx]; - return 0; + return nullptr; } }; @@ -87,8 +87,6 @@ BanTableModel::BanTableModel(interfaces::Node& node, ClientModel *parent) : { columns << tr("IP/Netmask") << tr("Banned Until"); priv.reset(new BanTablePriv()); - // default to unsorted - priv->sortColumn = -1; // load initial data refresh(); @@ -147,8 +145,7 @@ QVariant BanTableModel::headerData(int section, Qt::Orientation orientation, int Qt::ItemFlags BanTableModel::flags(const QModelIndex &index) const { - if(!index.isValid()) - return 0; + if (!index.isValid()) return Qt::NoItemFlags; Qt::ItemFlags retval = Qt::ItemIsSelectable | Qt::ItemIsEnabled; return retval; diff --git a/src/qt/bantablemodel.h b/src/qt/bantablemodel.h index b8505b599f..9dec5fa6a9 100644 --- a/src/qt/bantablemodel.h +++ b/src/qt/bantablemodel.h @@ -45,7 +45,7 @@ class BanTableModel : public QAbstractTableModel Q_OBJECT public: - explicit BanTableModel(interfaces::Node& node, ClientModel *parent = 0); + explicit BanTableModel(interfaces::Node& node, ClientModel *parent = nullptr); ~BanTableModel(); void startAutoRefresh(); void stopAutoRefresh(); diff --git a/src/qt/bitcoin.cpp b/src/qt/bitcoin.cpp index 1e950e2686..ca26131b95 100644 --- a/src/qt/bitcoin.cpp +++ b/src/qt/bitcoin.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. @@ -6,6 +6,7 @@ #include <config/bitcoin-config.h> #endif +#include <qt/bitcoin.h> #include <qt/bitcoingui.h> #include <chainparams.h> @@ -23,7 +24,7 @@ #ifdef ENABLE_WALLET #include <qt/paymentserver.h> -#include <qt/walletmodel.h> +#include <qt/walletcontroller.h> #endif #include <interfaces/handler.h> @@ -32,7 +33,7 @@ #include <rpc/server.h> #include <ui_interface.h> #include <uint256.h> -#include <util.h> +#include <util/system.h> #include <warnings.h> #include <walletinitinterface.h> @@ -51,7 +52,6 @@ #include <QThread> #include <QTimer> #include <QTranslator> -#include <QSslConfiguration> #if defined(QT_STATICPLUGIN) #include <QtPlugin> @@ -72,16 +72,6 @@ Q_DECLARE_METATYPE(bool*) Q_DECLARE_METATYPE(CAmount) Q_DECLARE_METATYPE(uint256) -static void InitMessage(const std::string& message) -{ - noui_InitMessage(message); -} - -/** Translate string to current locale using Qt. */ -const std::function<std::string(const char*)> G_TRANSLATION_FUN = [](const char* psz) { - return QCoreApplication::translate("bitcoin-core", psz).toStdString(); -}; - static QString GetLangTerritory() { QSettings settings; @@ -146,101 +136,6 @@ void DebugMessageHandler(QtMsgType type, const QMessageLogContext& context, cons } } -/** Class encapsulating Bitcoin Core startup and shutdown. - * Allows running startup and shutdown in a different thread from the UI thread. - */ -class BitcoinCore: public QObject -{ - Q_OBJECT -public: - explicit BitcoinCore(interfaces::Node& node); - -public Q_SLOTS: - void initialize(); - void shutdown(); - -Q_SIGNALS: - void initializeResult(bool success); - void shutdownResult(); - void runawayException(const QString &message); - -private: - /// Pass fatal exception message to UI thread - void handleRunawayException(const std::exception *e); - - interfaces::Node& m_node; -}; - -/** Main Bitcoin application object */ -class BitcoinApplication: public QApplication -{ - Q_OBJECT -public: - explicit BitcoinApplication(interfaces::Node& node, int &argc, char **argv); - ~BitcoinApplication(); - -#ifdef ENABLE_WALLET - /// Create payment server - void createPaymentServer(); -#endif - /// parameter interaction/setup based on rules - void parameterSetup(); - /// Create options model - void createOptionsModel(bool resetSettings); - /// Create main window - void createWindow(const NetworkStyle *networkStyle); - /// Create splash screen - void createSplashScreen(const NetworkStyle *networkStyle); - - /// Request core initialization - void requestInitialize(); - /// Request core shutdown - void requestShutdown(); - - /// Get process return value - int getReturnValue() const { return returnValue; } - - /// Get window identifier of QMainWindow (BitcoinGUI) - WId getMainWinId() const; - - /// Setup platform style - void setupPlatformStyle(); - -public Q_SLOTS: - void initializeResult(bool success); - void shutdownResult(); - /// Handle runaway exceptions. Shows a message box with the problem and quits the program. - void handleRunawayException(const QString &message); - void addWallet(WalletModel* walletModel); - void removeWallet(); - -Q_SIGNALS: - void requestedInitialize(); - void requestedShutdown(); - void stopThread(); - void splashFinished(QWidget *window); - -private: - QThread *coreThread; - interfaces::Node& m_node; - OptionsModel *optionsModel; - ClientModel *clientModel; - BitcoinGUI *window; - QTimer *pollShutdownTimer; -#ifdef ENABLE_WALLET - PaymentServer* paymentServer; - std::vector<WalletModel*> m_wallet_models; - std::unique_ptr<interfaces::Handler> m_handler_load_wallet; -#endif - int returnValue; - const PlatformStyle *platformStyle; - std::unique_ptr<QWidget> shutdownWindow; - - void startThread(); -}; - -#include <qt/bitcoin.moc> - BitcoinCore::BitcoinCore(interfaces::Node& node) : QObject(), m_node(node) { @@ -283,18 +178,14 @@ void BitcoinCore::shutdown() BitcoinApplication::BitcoinApplication(interfaces::Node& node, int &argc, char **argv): QApplication(argc, argv), - coreThread(0), + coreThread(nullptr), m_node(node), - optionsModel(0), - clientModel(0), - window(0), - pollShutdownTimer(0), -#ifdef ENABLE_WALLET - paymentServer(0), - m_wallet_models(), -#endif + optionsModel(nullptr), + clientModel(nullptr), + window(nullptr), + pollShutdownTimer(nullptr), returnValue(0), - platformStyle(0) + platformStyle(nullptr) { setQuitOnLastWindowClosed(false); } @@ -317,21 +208,21 @@ BitcoinApplication::~BitcoinApplication() if(coreThread) { qDebug() << __func__ << ": Stopping thread"; - Q_EMIT stopThread(); + coreThread->quit(); coreThread->wait(); qDebug() << __func__ << ": Stopped thread"; } delete window; - window = 0; + window = nullptr; #ifdef ENABLE_WALLET delete paymentServer; - paymentServer = 0; + paymentServer = nullptr; #endif delete optionsModel; - optionsModel = 0; + optionsModel = nullptr; delete platformStyle; - platformStyle = 0; + platformStyle = nullptr; } #ifdef ENABLE_WALLET @@ -348,7 +239,7 @@ void BitcoinApplication::createOptionsModel(bool resetSettings) void BitcoinApplication::createWindow(const NetworkStyle *networkStyle) { - window = new BitcoinGUI(m_node, platformStyle, networkStyle, 0); + window = new BitcoinGUI(m_node, platformStyle, networkStyle, nullptr); pollShutdownTimer = new QTimer(window); connect(pollShutdownTimer, &QTimer::timeout, window, &BitcoinGUI::detectShutdown); @@ -356,14 +247,19 @@ void BitcoinApplication::createWindow(const NetworkStyle *networkStyle) void BitcoinApplication::createSplashScreen(const NetworkStyle *networkStyle) { - SplashScreen *splash = new SplashScreen(m_node, 0, networkStyle); + SplashScreen *splash = new SplashScreen(m_node, nullptr, networkStyle); // We don't hold a direct pointer to the splash screen after creation, but the splash - // screen will take care of deleting itself when slotFinish happens. + // screen will take care of deleting itself when finish() happens. splash->show(); - connect(this, &BitcoinApplication::splashFinished, splash, &SplashScreen::slotFinish); + connect(this, &BitcoinApplication::splashFinished, splash, &SplashScreen::finish); connect(this, &BitcoinApplication::requestedShutdown, splash, &QWidget::close); } +bool BitcoinApplication::baseInitialize() +{ + return m_node.baseInitialize(); +} + void BitcoinApplication::startThread() { if(coreThread) @@ -379,8 +275,7 @@ void BitcoinApplication::startThread() connect(this, &BitcoinApplication::requestedInitialize, executor, &BitcoinCore::initialize); connect(this, &BitcoinApplication::requestedShutdown, executor, &BitcoinCore::shutdown); /* make sure executor object is deleted in its own thread */ - connect(this, &BitcoinApplication::stopThread, executor, &QObject::deleteLater); - connect(this, &BitcoinApplication::stopThread, coreThread, &QThread::quit); + connect(coreThread, &QThread::finished, executor, &QObject::deleteLater); coreThread->start(); } @@ -412,18 +307,15 @@ void BitcoinApplication::requestShutdown() qDebug() << __func__ << ": Requesting shutdown"; startThread(); window->hide(); - window->setClientModel(0); + window->setClientModel(nullptr); pollShutdownTimer->stop(); #ifdef ENABLE_WALLET - window->removeAllWallets(); - for (const WalletModel* walletModel : m_wallet_models) { - delete walletModel; - } - m_wallet_models.clear(); + delete m_wallet_controller; + m_wallet_controller = nullptr; #endif delete clientModel; - clientModel = 0; + clientModel = nullptr; m_node.startShutdown(); @@ -431,33 +323,6 @@ void BitcoinApplication::requestShutdown() Q_EMIT requestedShutdown(); } -void BitcoinApplication::addWallet(WalletModel* walletModel) -{ -#ifdef ENABLE_WALLET - window->addWallet(walletModel); - - if (m_wallet_models.empty()) { - window->setCurrentWallet(walletModel->getWalletName()); - } - - connect(walletModel, &WalletModel::coinsSent, - paymentServer, &PaymentServer::fetchPaymentACK); - connect(walletModel, &WalletModel::unload, this, &BitcoinApplication::removeWallet); - - m_wallet_models.push_back(walletModel); -#endif -} - -void BitcoinApplication::removeWallet() -{ -#ifdef ENABLE_WALLET - WalletModel* walletModel = static_cast<WalletModel*>(sender()); - m_wallet_models.erase(std::find(m_wallet_models.begin(), m_wallet_models.end(), walletModel)); - window->removeWallet(walletModel); - walletModel->deleteLater(); -#endif -} - void BitcoinApplication::initializeResult(bool success) { qDebug() << __func__ << ": Initialization result: " << success; @@ -468,50 +333,50 @@ void BitcoinApplication::initializeResult(bool success) // Log this only after AppInitMain finishes, as then logging setup is guaranteed complete qWarning() << "Platform customization:" << platformStyle->getName(); #ifdef ENABLE_WALLET + m_wallet_controller = new WalletController(m_node, platformStyle, optionsModel, this); +#ifdef ENABLE_BIP70 PaymentServer::LoadRootCAs(); - paymentServer->setOptionsModel(optionsModel); +#endif + if (paymentServer) { + paymentServer->setOptionsModel(optionsModel); +#ifdef ENABLE_BIP70 + connect(m_wallet_controller, &WalletController::coinsSent, paymentServer, &PaymentServer::fetchPaymentACK); +#endif + } #endif clientModel = new ClientModel(m_node, optionsModel); window->setClientModel(clientModel); - #ifdef ENABLE_WALLET - m_handler_load_wallet = m_node.handleLoadWallet([this](std::unique_ptr<interfaces::Wallet> wallet) { - WalletModel* wallet_model = new WalletModel(std::move(wallet), m_node, platformStyle, optionsModel, nullptr); - // Fix wallet model thread affinity. - wallet_model->moveToThread(thread()); - QMetaObject::invokeMethod(this, "addWallet", Qt::QueuedConnection, Q_ARG(WalletModel*, wallet_model)); - }); - - for (auto& wallet : m_node.getWallets()) { - addWallet(new WalletModel(std::move(wallet), m_node, platformStyle, optionsModel)); - } + window->setWalletController(m_wallet_controller); #endif - // If -min option passed, start window minimized. - if(gArgs.GetBoolArg("-min", false)) - { - window->showMinimized(); - } - else - { + // If -min option passed, start window minimized (iconified) or minimized to tray + if (!gArgs.GetBoolArg("-min", false)) { window->show(); + } else if (clientModel->getOptionsModel()->getMinimizeToTray() && window->hasTrayIcon()) { + // do nothing as the window is managed by the tray icon + } else { + window->showMinimized(); } - Q_EMIT splashFinished(window); + Q_EMIT splashFinished(); + Q_EMIT windowShown(window); #ifdef ENABLE_WALLET // Now that initialization/startup is done, process any command-line // bitcoin: URIs or payment requests: - connect(paymentServer, &PaymentServer::receivedPaymentRequest, window, &BitcoinGUI::handlePaymentRequest); - connect(window, &BitcoinGUI::receivedURI, paymentServer, &PaymentServer::handleURIOrFile); - connect(paymentServer, &PaymentServer::message, [this](const QString& title, const QString& message, unsigned int style) { - window->message(title, message, style); - }); - QTimer::singleShot(100, paymentServer, &PaymentServer::uiReady); + if (paymentServer) { + connect(paymentServer, &PaymentServer::receivedPaymentRequest, window, &BitcoinGUI::handlePaymentRequest); + connect(window, &BitcoinGUI::receivedURI, paymentServer, &PaymentServer::handleURIOrFile); + connect(paymentServer, &PaymentServer::message, [this](const QString& title, const QString& message, unsigned int style) { + window->message(title, message, style); + }); + QTimer::singleShot(100, paymentServer, &PaymentServer::uiReady); + } #endif pollShutdownTimer->start(200); } else { - Q_EMIT splashFinished(window); // Make sure splash screen doesn't stick around during shutdown + Q_EMIT splashFinished(); // Make sure splash screen doesn't stick around during shutdown quit(); // Exit first main loop invocation } } @@ -523,7 +388,7 @@ void BitcoinApplication::shutdownResult() void BitcoinApplication::handleRunawayException(const QString &message) { - QMessageBox::critical(0, "Runaway exception", BitcoinGUI::tr("A fatal error occurred. Bitcoin can no longer continue safely and will quit.") + QString("\n\n") + message); + QMessageBox::critical(nullptr, "Runaway exception", BitcoinGUI::tr("A fatal error occurred. Bitcoin can no longer continue safely and will quit.") + QString("\n\n") + message); ::exit(EXIT_FAILURE); } @@ -537,7 +402,7 @@ WId BitcoinApplication::getMainWinId() const static void SetupUIArgs() { -#ifdef ENABLE_WALLET +#if defined(ENABLE_WALLET) && defined(ENABLE_BIP70) gArgs.AddArg("-allowselfsignedrootcertificates", strprintf("Allow self signed root certificates (default: %u)", DEFAULT_SELFSIGNED_ROOTCERTS), true, OptionsCategory::GUI); #endif gArgs.AddArg("-choosedatadir", strprintf("Choose data directory on startup (default: %u)", DEFAULT_CHOOSE_DATADIR), false, OptionsCategory::GUI); @@ -550,12 +415,21 @@ static void SetupUIArgs() } #ifndef BITCOIN_QT_TEST -int main(int argc, char *argv[]) +int GuiMain(int argc, char* argv[]) { +#ifdef WIN32 + util::WinCmdLineArgs winArgs; + std::tie(argc, argv) = winArgs.get(); +#endif SetupEnvironment(); std::unique_ptr<interfaces::Node> node = interfaces::MakeNode(); + // Subscribe to global signals from core + std::unique_ptr<interfaces::Handler> handler_message_box = node->handleMessageBox(noui_ThreadSafeMessageBox); + std::unique_ptr<interfaces::Handler> handler_question = node->handleQuestion(noui_ThreadSafeQuestion); + std::unique_ptr<interfaces::Handler> handler_init_message = node->handleInitMessage(noui_InitMessage); + // Do not refer to data directory yet, this can be overridden by Intro::pickDataDirectory /// 1. Basic Qt initialization (not dependent on parameters or configuration) @@ -563,23 +437,14 @@ int main(int argc, char *argv[]) Q_INIT_RESOURCE(bitcoin_locale); BitcoinApplication app(*node, argc, argv); -#if QT_VERSION > 0x050100 // Generate high-dpi pixmaps QApplication::setAttribute(Qt::AA_UseHighDpiPixmaps); -#endif #if QT_VERSION >= 0x050600 QGuiApplication::setAttribute(Qt::AA_EnableHighDpiScaling); #endif #ifdef Q_OS_MAC QApplication::setAttribute(Qt::AA_DontShowIconsInMenus); #endif -#if QT_VERSION >= 0x050500 - // Because of the POODLE attack it is recommended to disable SSLv3 (https://disablessl3.com/), - // so set SSL protocols to TLS1.0+. - QSslConfiguration sslconf = QSslConfiguration::defaultConfiguration(); - sslconf.setProtocol(QSsl::TlsV1_0OrLater); - QSslConfiguration::setDefaultConfiguration(sslconf); -#endif // Register meta types used for QMetaObject::invokeMethod qRegisterMetaType< bool* >(); @@ -587,9 +452,6 @@ int main(int argc, char *argv[]) // IMPORTANT if it is no longer a typedef use the normal variant above qRegisterMetaType< CAmount >("CAmount"); qRegisterMetaType< std::function<void()> >("std::function<void()>"); -#ifdef ENABLE_WALLET - qRegisterMetaType<WalletModel*>("WalletModel*"); -#endif /// 2. Parse command-line options. We do this after qt in order to show an error if there are problems parsing these // Command-line options take precedence: @@ -597,7 +459,7 @@ int main(int argc, char *argv[]) SetupUIArgs(); std::string error; if (!node->parseParameters(argc, argv, error)) { - QMessageBox::critical(0, QObject::tr(PACKAGE_NAME), + QMessageBox::critical(nullptr, QObject::tr(PACKAGE_NAME), QObject::tr("Error parsing command line arguments: %1.").arg(QString::fromStdString(error))); return EXIT_FAILURE; } @@ -634,12 +496,12 @@ int main(int argc, char *argv[]) /// - Do not call GetDataDir(true) before this step finishes if (!fs::is_directory(GetDataDir(false))) { - QMessageBox::critical(0, QObject::tr(PACKAGE_NAME), - QObject::tr("Error: Specified data directory \"%1\" does not exist.").arg(QString::fromStdString(gArgs.GetArg("-datadir", "")))); + QMessageBox::critical(nullptr, QObject::tr(PACKAGE_NAME), + QObject::tr("Error: Specified data directory \"%1\" does not exist.").arg(QString::fromStdString(gArgs.GetArg("-datadir", "")))); return EXIT_FAILURE; } if (!node->readConfigFiles(error)) { - QMessageBox::critical(0, QObject::tr(PACKAGE_NAME), + QMessageBox::critical(nullptr, QObject::tr(PACKAGE_NAME), QObject::tr("Error: Cannot parse configuration file: %1.").arg(QString::fromStdString(error))); return EXIT_FAILURE; } @@ -654,7 +516,7 @@ int main(int argc, char *argv[]) try { node->selectParams(gArgs.GetChainName()); } catch(std::exception &e) { - QMessageBox::critical(0, QObject::tr(PACKAGE_NAME), QObject::tr("Error: %1").arg(e.what())); + QMessageBox::critical(nullptr, QObject::tr(PACKAGE_NAME), QObject::tr("Error: %1").arg(e.what())); return EXIT_FAILURE; } #ifdef ENABLE_WALLET @@ -698,9 +560,6 @@ int main(int argc, char *argv[]) // Load GUI settings from QSettings app.createOptionsModel(gArgs.GetBoolArg("-resetguisettings", false)); - // Subscribe to global signals from core - std::unique_ptr<interfaces::Handler> handler = node->handleInitMessage(InitMessage); - if (gArgs.GetBoolArg("-splash", DEFAULT_SPLASHSCREEN) && !gArgs.GetBoolArg("-min", false)) app.createSplashScreen(networkStyle.data()); @@ -711,7 +570,7 @@ int main(int argc, char *argv[]) // Perform base initialization before spinning up initialization/shutdown thread // This is acceptable because this function only contains steps that are quick to execute, // so the GUI thread won't be held up. - if (node->baseInitialize()) { + if (app.baseInitialize()) { app.requestInitialize(); #if defined(Q_OS_WIN) WinShutdownMonitor::registerShutdownBlockReason(QObject::tr("%1 didn't yet exit safely...").arg(QObject::tr(PACKAGE_NAME)), (HWND)app.getMainWinId()); diff --git a/src/qt/bitcoin.h b/src/qt/bitcoin.h new file mode 100644 index 0000000000..370712d953 --- /dev/null +++ b/src/qt/bitcoin.h @@ -0,0 +1,124 @@ +// Copyright (c) 2011-2016 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_QT_BITCOIN_H +#define BITCOIN_QT_BITCOIN_H + +#if defined(HAVE_CONFIG_H) +#include <config/bitcoin-config.h> +#endif + +#include <QApplication> +#include <memory> +#include <vector> + +class BitcoinGUI; +class ClientModel; +class NetworkStyle; +class OptionsModel; +class PaymentServer; +class PlatformStyle; +class WalletController; +class WalletModel; + +namespace interfaces { +class Handler; +class Node; +} // namespace interfaces + +/** Class encapsulating Bitcoin Core startup and shutdown. + * Allows running startup and shutdown in a different thread from the UI thread. + */ +class BitcoinCore: public QObject +{ + Q_OBJECT +public: + explicit BitcoinCore(interfaces::Node& node); + +public Q_SLOTS: + void initialize(); + void shutdown(); + +Q_SIGNALS: + void initializeResult(bool success); + void shutdownResult(); + void runawayException(const QString &message); + +private: + /// Pass fatal exception message to UI thread + void handleRunawayException(const std::exception *e); + + interfaces::Node& m_node; +}; + +/** Main Bitcoin application object */ +class BitcoinApplication: public QApplication +{ + Q_OBJECT +public: + explicit BitcoinApplication(interfaces::Node& node, int &argc, char **argv); + ~BitcoinApplication(); + +#ifdef ENABLE_WALLET + /// Create payment server + void createPaymentServer(); +#endif + /// parameter interaction/setup based on rules + void parameterSetup(); + /// Create options model + void createOptionsModel(bool resetSettings); + /// Create main window + void createWindow(const NetworkStyle *networkStyle); + /// Create splash screen + void createSplashScreen(const NetworkStyle *networkStyle); + /// Basic initialization, before starting initialization/shutdown thread. Return true on success. + bool baseInitialize(); + + /// Request core initialization + void requestInitialize(); + /// Request core shutdown + void requestShutdown(); + + /// Get process return value + int getReturnValue() const { return returnValue; } + + /// Get window identifier of QMainWindow (BitcoinGUI) + WId getMainWinId() const; + + /// Setup platform style + void setupPlatformStyle(); + +public Q_SLOTS: + void initializeResult(bool success); + void shutdownResult(); + /// Handle runaway exceptions. Shows a message box with the problem and quits the program. + void handleRunawayException(const QString &message); + +Q_SIGNALS: + void requestedInitialize(); + void requestedShutdown(); + void splashFinished(); + void windowShown(BitcoinGUI* window); + +private: + QThread *coreThread; + interfaces::Node& m_node; + OptionsModel *optionsModel; + ClientModel *clientModel; + BitcoinGUI *window; + QTimer *pollShutdownTimer; +#ifdef ENABLE_WALLET + PaymentServer* paymentServer{nullptr}; + WalletController* m_wallet_controller{nullptr}; +#endif + int returnValue; + const PlatformStyle *platformStyle; + std::unique_ptr<QWidget> shutdownWindow; + + void startThread(); +}; + +int GuiMain(int argc, char* argv[]); + +#endif // BITCOIN_QT_BITCOIN_H diff --git a/src/qt/bitcoinamountfield.cpp b/src/qt/bitcoinamountfield.cpp index b68f3a439b..5854ade655 100644 --- a/src/qt/bitcoinamountfield.cpp +++ b/src/qt/bitcoinamountfield.cpp @@ -23,9 +23,7 @@ class AmountSpinBox: public QAbstractSpinBox public: explicit AmountSpinBox(QWidget *parent): - QAbstractSpinBox(parent), - currentUnit(BitcoinUnits::BTC), - singleStep(100000) // satoshis + QAbstractSpinBox(parent) { setAlignment(Qt::AlignRight); @@ -44,16 +42,25 @@ public: void fixup(QString &input) const { - bool valid = false; - CAmount val = parse(input, &valid); - if(valid) - { + bool valid; + CAmount val; + + if (input.isEmpty() && !m_allow_empty) { + valid = true; + val = m_min_amount; + } else { + valid = false; + val = parse(input, &valid); + } + + if (valid) { + val = qBound(m_min_amount, val, m_max_amount); input = BitcoinUnits::format(currentUnit, val, false, BitcoinUnits::separatorAlways); lineEdit()->setText(input); } } - CAmount value(bool *valid_out=0) const + CAmount value(bool *valid_out=nullptr) const { return parse(text(), valid_out); } @@ -64,12 +71,27 @@ public: Q_EMIT valueChanged(); } + void SetAllowEmpty(bool allow) + { + m_allow_empty = allow; + } + + void SetMinValue(const CAmount& value) + { + m_min_amount = value; + } + + void SetMaxValue(const CAmount& value) + { + m_max_amount = value; + } + void stepBy(int steps) { bool valid = false; CAmount val = value(&valid); val = val + steps * singleStep; - val = qMin(qMax(val, CAmount(0)), BitcoinUnits::maxMoney()); + val = qBound(m_min_amount, val, m_max_amount); setValue(val); } @@ -125,16 +147,19 @@ public: } private: - int currentUnit; - CAmount singleStep; + int currentUnit{BitcoinUnits::BTC}; + CAmount singleStep{CAmount(100000)}; // satoshis mutable QSize cachedMinimumSizeHint; + bool m_allow_empty{true}; + CAmount m_min_amount{CAmount(0)}; + CAmount m_max_amount{BitcoinUnits::maxMoney()}; /** * Parse a string into a number of base monetary units and * return validity. * @note Must return 0 if !valid. */ - CAmount parse(const QString &text, bool *valid_out=0) const + CAmount parse(const QString &text, bool *valid_out=nullptr) const { CAmount val = 0; bool valid = BitcoinUnits::parse(currentUnit, text, &val); @@ -171,14 +196,13 @@ protected: if (text().isEmpty()) // Allow step-up with empty field return StepUpEnabled; - StepEnabled rv = 0; + StepEnabled rv = StepNone; bool valid = false; CAmount val = value(&valid); - if(valid) - { - if(val > 0) + if (valid) { + if (val > m_min_amount) rv |= StepDownEnabled; - if(val < BitcoinUnits::maxMoney()) + if (val < m_max_amount) rv |= StepUpEnabled; } return rv; @@ -192,7 +216,7 @@ Q_SIGNALS: BitcoinAmountField::BitcoinAmountField(QWidget *parent) : QWidget(parent), - amount(0) + amount(nullptr) { amount = new AmountSpinBox(this); amount->setLocale(QLocale::c()); @@ -275,6 +299,21 @@ void BitcoinAmountField::setValue(const CAmount& value) amount->setValue(value); } +void BitcoinAmountField::SetAllowEmpty(bool allow) +{ + amount->SetAllowEmpty(allow); +} + +void BitcoinAmountField::SetMinValue(const CAmount& value) +{ + amount->SetMinValue(value); +} + +void BitcoinAmountField::SetMaxValue(const CAmount& value) +{ + amount->SetMaxValue(value); +} + void BitcoinAmountField::setReadOnly(bool fReadOnly) { amount->setReadOnly(fReadOnly); diff --git a/src/qt/bitcoinamountfield.h b/src/qt/bitcoinamountfield.h index f93579c492..2db6b65f2c 100644 --- a/src/qt/bitcoinamountfield.h +++ b/src/qt/bitcoinamountfield.h @@ -26,11 +26,20 @@ class BitcoinAmountField: public QWidget Q_PROPERTY(qint64 value READ value WRITE setValue NOTIFY valueChanged USER true) public: - explicit BitcoinAmountField(QWidget *parent = 0); + explicit BitcoinAmountField(QWidget *parent = nullptr); - CAmount value(bool *value=0) const; + CAmount value(bool *value=nullptr) const; void setValue(const CAmount& value); + /** If allow empty is set to false the field will be set to the minimum allowed value if left empty. **/ + void SetAllowEmpty(bool allow); + + /** Set the minimum value in satoshis **/ + void SetMinValue(const CAmount& value); + + /** Set the maximum value in satoshis **/ + void SetMaxValue(const CAmount& value); + /** Set single step in satoshis **/ void setSingleStep(const CAmount& step); diff --git a/src/qt/bitcoingui.cpp b/src/qt/bitcoingui.cpp index 51aff08c42..ba7e8c7daf 100644 --- a/src/qt/bitcoingui.cpp +++ b/src/qt/bitcoingui.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. @@ -19,6 +19,7 @@ #include <qt/utilitydialog.h> #ifdef ENABLE_WALLET +#include <qt/walletcontroller.h> #include <qt/walletframe.h> #include <qt/walletmodel.h> #include <qt/walletview.h> @@ -28,14 +29,15 @@ #include <qt/macdockiconhandler.h> #endif +#include <chain.h> #include <chainparams.h> #include <interfaces/handler.h> #include <interfaces/node.h> -#include <noui.h> #include <ui_interface.h> -#include <util.h> +#include <util/system.h> #include <iostream> +#include <memory> #include <QAction> #include <QApplication> @@ -44,6 +46,7 @@ #include <QDesktopWidget> #include <QDragEnterEvent> #include <QListWidget> +#include <QMenu> #include <QMenuBar> #include <QMessageBox> #include <QMimeData> @@ -53,12 +56,13 @@ #include <QStackedWidget> #include <QStatusBar> #include <QStyle> +#include <QSystemTrayIcon> #include <QTimer> #include <QToolBar> #include <QUrlQuery> #include <QVBoxLayout> +#include <QWindow> -#include <boost/bind.hpp> const std::string BitcoinGUI::DEFAULT_UIPLATFORM = #if defined(Q_OS_MAC) @@ -73,7 +77,9 @@ const std::string BitcoinGUI::DEFAULT_UIPLATFORM = BitcoinGUI::BitcoinGUI(interfaces::Node& node, const PlatformStyle *_platformStyle, const NetworkStyle *networkStyle, QWidget *parent) : QMainWindow(parent), m_node(node), - platformStyle(_platformStyle) + trayIconMenu{new QMenu()}, + platformStyle(_platformStyle), + m_network_style(networkStyle) { QSettings settings; if (!restoreGeometry(settings.value("MainWindowGeometry").toByteArray())) { @@ -81,26 +87,14 @@ BitcoinGUI::BitcoinGUI(interfaces::Node& node, const PlatformStyle *_platformSty move(QApplication::desktop()->availableGeometry().center() - frameGeometry().center()); } - QString windowTitle = tr(PACKAGE_NAME) + " - "; #ifdef ENABLE_WALLET enableWallet = WalletModel::isWalletEnabled(); #endif // ENABLE_WALLET - if(enableWallet) - { - windowTitle += tr("Wallet"); - } else { - windowTitle += tr("Node"); - } - windowTitle += " " + networkStyle->getTitleAddText(); -#ifndef Q_OS_MAC - QApplication::setWindowIcon(networkStyle->getTrayAndWindowIcon()); - setWindowIcon(networkStyle->getTrayAndWindowIcon()); -#else - MacDockIconHandler::instance()->setIcon(networkStyle->getAppIcon()); -#endif - setWindowTitle(windowTitle); + QApplication::setWindowIcon(m_network_style->getTrayAndWindowIcon()); + setWindowIcon(m_network_style->getTrayAndWindowIcon()); + updateWindowTitle(); - rpcConsole = new RPCConsole(node, _platformStyle, 0); + rpcConsole = new RPCConsole(node, _platformStyle, nullptr); helpMessageDialog = new HelpMessageDialog(node, this, false); #ifdef ENABLE_WALLET if(enableWallet) @@ -115,6 +109,7 @@ BitcoinGUI::BitcoinGUI(interfaces::Node& node, const PlatformStyle *_platformSty * the central widget is the rpc console. */ setCentralWidget(rpcConsole); + Q_EMIT consoleShown(rpcConsole); } // Accept D&D of URIs @@ -131,7 +126,10 @@ BitcoinGUI::BitcoinGUI(interfaces::Node& node, const PlatformStyle *_platformSty createToolBars(); // Create system tray icon and notification - createTrayIcon(networkStyle); + if (QSystemTrayIcon::isSystemTrayAvailable()) { + createTrayIcon(); + } + notificator = new Notificator(QApplication::applicationName(), trayIcon, this); // Create status bar statusBar(); @@ -211,6 +209,10 @@ BitcoinGUI::BitcoinGUI(interfaces::Node& node, const PlatformStyle *_platformSty connect(progressBar, &GUIUtil::ClickableProgressBar::clicked, this, &BitcoinGUI::showModalOverlay); } #endif + +#ifdef Q_OS_MAC + m_app_nap_inhibitor = new CAppNapInhibitor; +#endif } BitcoinGUI::~BitcoinGUI() @@ -223,6 +225,7 @@ BitcoinGUI::~BitcoinGUI() if(trayIcon) // Hide tray icon, as deleting will let it linger until quit (on Ubuntu) trayIcon->hide(); #ifdef Q_OS_MAC + delete m_app_nap_inhibitor; delete appMenuBar; MacDockIconHandler::cleanup(); #endif @@ -273,17 +276,17 @@ void BitcoinGUI::createActions() #ifdef ENABLE_WALLET // These showNormalIfMinimized are needed because Send Coins and Receive Coins // can be triggered from the tray menu, and need to show the GUI to be useful. - connect(overviewAction, &QAction::triggered, this, static_cast<void (BitcoinGUI::*)()>(&BitcoinGUI::showNormalIfMinimized)); + connect(overviewAction, &QAction::triggered, [this]{ showNormalIfMinimized(); }); connect(overviewAction, &QAction::triggered, this, &BitcoinGUI::gotoOverviewPage); - connect(sendCoinsAction, &QAction::triggered, this, static_cast<void (BitcoinGUI::*)()>(&BitcoinGUI::showNormalIfMinimized)); + connect(sendCoinsAction, &QAction::triggered, [this]{ showNormalIfMinimized(); }); connect(sendCoinsAction, &QAction::triggered, [this]{ gotoSendCoinsPage(); }); - connect(sendCoinsMenuAction, &QAction::triggered, this, static_cast<void (BitcoinGUI::*)()>(&BitcoinGUI::showNormalIfMinimized)); + connect(sendCoinsMenuAction, &QAction::triggered, [this]{ showNormalIfMinimized(); }); connect(sendCoinsMenuAction, &QAction::triggered, [this]{ gotoSendCoinsPage(); }); - connect(receiveCoinsAction, &QAction::triggered, this, static_cast<void (BitcoinGUI::*)()>(&BitcoinGUI::showNormalIfMinimized)); + connect(receiveCoinsAction, &QAction::triggered, [this]{ showNormalIfMinimized(); }); connect(receiveCoinsAction, &QAction::triggered, this, &BitcoinGUI::gotoReceiveCoinsPage); - connect(receiveCoinsMenuAction, &QAction::triggered, this, static_cast<void (BitcoinGUI::*)()>(&BitcoinGUI::showNormalIfMinimized)); + connect(receiveCoinsMenuAction, &QAction::triggered, [this]{ showNormalIfMinimized(); }); connect(receiveCoinsMenuAction, &QAction::triggered, this, &BitcoinGUI::gotoReceiveCoinsPage); - connect(historyAction, &QAction::triggered, this, static_cast<void (BitcoinGUI::*)()>(&BitcoinGUI::showNormalIfMinimized)); + connect(historyAction, &QAction::triggered, [this]{ showNormalIfMinimized(); }); connect(historyAction, &QAction::triggered, this, &BitcoinGUI::gotoHistoryPage); #endif // ENABLE_WALLET @@ -321,10 +324,11 @@ void BitcoinGUI::createActions() openRPCConsoleAction->setStatusTip(tr("Open debugging and diagnostic console")); // initially disable the debug window menu item openRPCConsoleAction->setEnabled(false); + openRPCConsoleAction->setObjectName("openRPCConsoleAction"); - usedSendingAddressesAction = new QAction(platformStyle->TextColorIcon(":/icons/address-book"), tr("&Sending addresses..."), this); + usedSendingAddressesAction = new QAction(platformStyle->TextColorIcon(":/icons/address-book"), tr("&Sending addresses"), this); usedSendingAddressesAction->setStatusTip(tr("Show the list of used sending addresses and labels")); - usedReceivingAddressesAction = new QAction(platformStyle->TextColorIcon(":/icons/address-book"), tr("&Receiving addresses..."), this); + usedReceivingAddressesAction = new QAction(platformStyle->TextColorIcon(":/icons/address-book"), tr("&Receiving addresses"), this); usedReceivingAddressesAction->setStatusTip(tr("Show the list of used receiving addresses and labels")); openAction = new QAction(platformStyle->TextColorIcon(":/icons/open"), tr("Open &URI..."), this); @@ -350,7 +354,9 @@ void BitcoinGUI::createActions() connect(encryptWalletAction, &QAction::triggered, walletFrame, &WalletFrame::encryptWallet); connect(backupWalletAction, &QAction::triggered, walletFrame, &WalletFrame::backupWallet); connect(changePassphraseAction, &QAction::triggered, walletFrame, &WalletFrame::changePassphrase); + connect(signMessageAction, &QAction::triggered, [this]{ showNormalIfMinimized(); }); connect(signMessageAction, &QAction::triggered, [this]{ gotoSignMessageTab(); }); + connect(verifyMessageAction, &QAction::triggered, [this]{ showNormalIfMinimized(); }); connect(verifyMessageAction, &QAction::triggered, [this]{ gotoVerifyMessageTab(); }); connect(usedSendingAddressesAction, &QAction::triggered, walletFrame, &WalletFrame::usedSendingAddresses); connect(usedReceivingAddressesAction, &QAction::triggered, walletFrame, &WalletFrame::usedReceivingAddresses); @@ -381,9 +387,6 @@ void BitcoinGUI::createMenuBar() file->addAction(signMessageAction); file->addAction(verifyMessageAction); file->addSeparator(); - file->addAction(usedSendingAddressesAction); - file->addAction(usedReceivingAddressesAction); - file->addSeparator(); } file->addAction(quitAction); @@ -396,11 +399,64 @@ void BitcoinGUI::createMenuBar() } settings->addAction(optionsAction); - QMenu *help = appMenuBar->addMenu(tr("&Help")); - if(walletFrame) - { - help->addAction(openRPCConsoleAction); + QMenu* window_menu = appMenuBar->addMenu(tr("&Window")); + + QAction* minimize_action = window_menu->addAction(tr("Minimize")); + minimize_action->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_M)); + connect(minimize_action, &QAction::triggered, [] { + qApp->focusWindow()->showMinimized(); + }); + connect(qApp, &QApplication::focusWindowChanged, [minimize_action] (QWindow* window) { + minimize_action->setEnabled(window != nullptr && (window->flags() & Qt::Dialog) != Qt::Dialog && window->windowState() != Qt::WindowMinimized); + }); + +#ifdef Q_OS_MAC + QAction* zoom_action = window_menu->addAction(tr("Zoom")); + connect(zoom_action, &QAction::triggered, [] { + QWindow* window = qApp->focusWindow(); + if (window->windowState() != Qt::WindowMaximized) { + window->showMaximized(); + } else { + window->showNormal(); + } + }); + + connect(qApp, &QApplication::focusWindowChanged, [zoom_action] (QWindow* window) { + zoom_action->setEnabled(window != nullptr); + }); +#else + QAction* restore_action = window_menu->addAction(tr("Restore")); + connect(restore_action, &QAction::triggered, [] { + qApp->focusWindow()->showNormal(); + }); + + connect(qApp, &QApplication::focusWindowChanged, [restore_action] (QWindow* window) { + restore_action->setEnabled(window != nullptr); + }); +#endif + + if (walletFrame) { + window_menu->addSeparator(); + QAction* main_window_action = window_menu->addAction(tr("Main Window")); + connect(main_window_action, &QAction::triggered, [this] { + GUIUtil::bringToFront(this); + }); + + window_menu->addSeparator(); + window_menu->addAction(usedSendingAddressesAction); + window_menu->addAction(usedReceivingAddressesAction); } + + window_menu->addSeparator(); + for (RPCConsole::TabTypes tab_type : rpcConsole->tabs()) { + QAction* tab_action = window_menu->addAction(rpcConsole->tabTitle(tab_type)); + connect(tab_action, &QAction::triggered, [this, tab_type] { + rpcConsole->setTabFocus(tab_type); + showDebugWindow(); + }); + } + + QMenu *help = appMenuBar->addMenu(tr("&Help")); help->addAction(showHelpMessageAction); help->addSeparator(); help->addAction(aboutAction); @@ -428,6 +484,7 @@ void BitcoinGUI::createToolBars() toolbar->addWidget(spacer); m_wallet_selector = new QComboBox(); + m_wallet_selector->setSizeAdjustPolicy(QComboBox::AdjustToContents); connect(m_wallet_selector, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, &BitcoinGUI::setCurrentWalletBySelectorIndex); m_wallet_selector_label = new QLabel(); @@ -482,8 +539,7 @@ void BitcoinGUI::setClientModel(ClientModel *_clientModel) unitDisplayControl->setOptionsModel(_clientModel->getOptionsModel()); OptionsModel* optionsModel = _clientModel->getOptionsModel(); - if(optionsModel) - { + if (optionsModel && trayIcon) { // be aware of the tray icon disable state change reported by the OptionsModel object. connect(optionsModel, &OptionsModel::hideTrayIconChanged, this, &BitcoinGUI::setTrayIconVisible); @@ -511,27 +567,39 @@ void BitcoinGUI::setClientModel(ClientModel *_clientModel) } #ifdef ENABLE_WALLET -bool BitcoinGUI::addWallet(WalletModel *walletModel) +void BitcoinGUI::setWalletController(WalletController* wallet_controller) { - if(!walletFrame) - return false; - const QString name = walletModel->getWalletName(); - QString display_name = name.isEmpty() ? "["+tr("default wallet")+"]" : name; + assert(!m_wallet_controller); + assert(wallet_controller); + + m_wallet_controller = wallet_controller; + + connect(wallet_controller, &WalletController::walletAdded, this, &BitcoinGUI::addWallet); + connect(wallet_controller, &WalletController::walletRemoved, this, &BitcoinGUI::removeWallet); + + for (WalletModel* wallet_model : m_wallet_controller->getWallets()) { + addWallet(wallet_model); + } +} + +void BitcoinGUI::addWallet(WalletModel* walletModel) +{ + if (!walletFrame) return; + const QString display_name = walletModel->getDisplayName(); setWalletActionsEnabled(true); - m_wallet_selector->addItem(display_name, name); + rpcConsole->addWallet(walletModel); + walletFrame->addWallet(walletModel); + m_wallet_selector->addItem(display_name, QVariant::fromValue(walletModel)); if (m_wallet_selector->count() == 2) { m_wallet_selector_label_action->setVisible(true); m_wallet_selector_action->setVisible(true); } - rpcConsole->addWallet(walletModel); - return walletFrame->addWallet(walletModel); } -bool BitcoinGUI::removeWallet(WalletModel* walletModel) +void BitcoinGUI::removeWallet(WalletModel* walletModel) { - if (!walletFrame) return false; - QString name = walletModel->getWalletName(); - int index = m_wallet_selector->findData(name); + if (!walletFrame) return; + int index = m_wallet_selector->findData(QVariant::fromValue(walletModel)); m_wallet_selector->removeItem(index); if (m_wallet_selector->count() == 0) { setWalletActionsEnabled(false); @@ -540,20 +608,27 @@ bool BitcoinGUI::removeWallet(WalletModel* walletModel) m_wallet_selector_action->setVisible(false); } rpcConsole->removeWallet(walletModel); - return walletFrame->removeWallet(name); + walletFrame->removeWallet(walletModel); + updateWindowTitle(); } -bool BitcoinGUI::setCurrentWallet(const QString& name) +void BitcoinGUI::setCurrentWallet(WalletModel* wallet_model) { - if(!walletFrame) - return false; - return walletFrame->setCurrentWallet(name); + if (!walletFrame) return; + walletFrame->setCurrentWallet(wallet_model); + for (int index = 0; index < m_wallet_selector->count(); ++index) { + if (m_wallet_selector->itemData(index).value<WalletModel*>() == wallet_model) { + m_wallet_selector->setCurrentIndex(index); + break; + } + } + updateWindowTitle(); } -bool BitcoinGUI::setCurrentWalletBySelectorIndex(int index) +void BitcoinGUI::setCurrentWalletBySelectorIndex(int index) { - QString internal_name = m_wallet_selector->itemData(index).toString(); - return setCurrentWallet(internal_name); + WalletModel* wallet_model = m_wallet_selector->itemData(index).value<WalletModel*>(); + if (wallet_model) setCurrentWallet(wallet_model); } void BitcoinGUI::removeAllWallets() @@ -583,52 +658,52 @@ void BitcoinGUI::setWalletActionsEnabled(bool enabled) openAction->setEnabled(enabled); } -void BitcoinGUI::createTrayIcon(const NetworkStyle *networkStyle) +void BitcoinGUI::createTrayIcon() { + assert(QSystemTrayIcon::isSystemTrayAvailable()); + #ifndef Q_OS_MAC - trayIcon = new QSystemTrayIcon(this); - QString toolTip = tr("%1 client").arg(tr(PACKAGE_NAME)) + " " + networkStyle->getTitleAddText(); - trayIcon->setToolTip(toolTip); - trayIcon->setIcon(networkStyle->getTrayAndWindowIcon()); - trayIcon->hide(); + if (QSystemTrayIcon::isSystemTrayAvailable()) { + trayIcon = new QSystemTrayIcon(m_network_style->getTrayAndWindowIcon(), this); + QString toolTip = tr("%1 client").arg(tr(PACKAGE_NAME)) + " " + m_network_style->getTitleAddText(); + trayIcon->setToolTip(toolTip); + } #endif - - notificator = new Notificator(QApplication::applicationName(), trayIcon, this); } void BitcoinGUI::createTrayIconMenu() { #ifndef Q_OS_MAC - // return if trayIcon is unset (only on non-Mac OSes) + // return if trayIcon is unset (only on non-macOSes) if (!trayIcon) return; - trayIconMenu = new QMenu(this); - trayIcon->setContextMenu(trayIconMenu); - + trayIcon->setContextMenu(trayIconMenu.get()); connect(trayIcon, &QSystemTrayIcon::activated, this, &BitcoinGUI::trayIconActivated); #else - // Note: On Mac, the dock icon is used to provide the tray's functionality. + // Note: On macOS, the Dock icon is used to provide the tray's functionality. MacDockIconHandler *dockIconHandler = MacDockIconHandler::instance(); - dockIconHandler->setMainWindow(static_cast<QMainWindow*>(this)); - trayIconMenu = dockIconHandler->dockMenu(); + connect(dockIconHandler, &MacDockIconHandler::dockIconClicked, this, &BitcoinGUI::macosDockIconActivated); + trayIconMenu->setAsDockMenu(); #endif - // Configuration of the tray icon (or dock icon) icon menu + // Configuration of the tray icon (or Dock icon) menu #ifndef Q_OS_MAC - // Note: On Mac, the dock icon's menu already has show / hide action. + // Note: On macOS, the Dock icon's menu already has Show / Hide action. trayIconMenu->addAction(toggleHideAction); trayIconMenu->addSeparator(); #endif - trayIconMenu->addAction(sendCoinsMenuAction); - trayIconMenu->addAction(receiveCoinsMenuAction); - trayIconMenu->addSeparator(); - trayIconMenu->addAction(signMessageAction); - trayIconMenu->addAction(verifyMessageAction); - trayIconMenu->addSeparator(); + if (enableWallet) { + trayIconMenu->addAction(sendCoinsMenuAction); + trayIconMenu->addAction(receiveCoinsMenuAction); + trayIconMenu->addSeparator(); + trayIconMenu->addAction(signMessageAction); + trayIconMenu->addAction(verifyMessageAction); + trayIconMenu->addSeparator(); + trayIconMenu->addAction(openRPCConsoleAction); + } trayIconMenu->addAction(optionsAction); - trayIconMenu->addAction(openRPCConsoleAction); -#ifndef Q_OS_MAC // This is built-in on Mac +#ifndef Q_OS_MAC // This is built-in on macOS trayIconMenu->addSeparator(); trayIconMenu->addAction(quitAction); #endif @@ -643,6 +718,12 @@ void BitcoinGUI::trayIconActivated(QSystemTrayIcon::ActivationReason reason) toggleHidden(); } } +#else +void BitcoinGUI::macosDockIconActivated() +{ + show(); + activateWindow(); +} #endif void BitcoinGUI::optionsClicked() @@ -661,10 +742,8 @@ void BitcoinGUI::aboutClicked() void BitcoinGUI::showDebugWindow() { - rpcConsole->showNormal(); - rpcConsole->show(); - rpcConsole->raise(); - rpcConsole->activateWindow(); + GUIUtil::bringToFront(rpcConsole); + Q_EMIT consoleShown(rpcConsole); } void BitcoinGUI::showDebugWindowActivateConsole() @@ -784,6 +863,11 @@ void BitcoinGUI::openOptionsDialogWithTab(OptionsDialog::Tab tab) void BitcoinGUI::setNumBlocks(int count, const QDateTime& blockDate, double nVerificationProgress, bool header) { +// Disabling macOS App Nap on initial sync, disk and reindex operations. +#ifdef Q_OS_MAC + (m_node.isInitialBlockDownload() || m_node.getReindex() || m_node.getImporting()) ? m_app_nap_inhibitor->disableAppNap() : m_app_nap_inhibitor->enableAppNap(); +#endif + if (modalOverlay) { if (header) @@ -834,8 +918,7 @@ void BitcoinGUI::setNumBlocks(int count, const QDateTime& blockDate, double nVer tooltip = tr("Processed %n block(s) of transaction history.", "", count); // Set icon state: spinning if catching up, tick otherwise - if(secs < 90*60) - { + if (secs < MAX_BLOCK_TIME_GAP) { tooltip = tr("Up to date") + QString(".<br>") + tooltip; labelBlocksIcon->setPixmap(platformStyle->SingleColorIcon(":/icons/synced").pixmap(STATUSBAR_ICONSIZE, STATUSBAR_ICONSIZE)); @@ -1070,10 +1153,10 @@ bool BitcoinGUI::handlePaymentRequest(const SendCoinsRecipient& recipient) return false; } -void BitcoinGUI::setHDStatus(int hdEnabled) +void BitcoinGUI::setHDStatus(bool privkeyDisabled, int hdEnabled) { - labelWalletHDStatusIcon->setPixmap(platformStyle->SingleColorIcon(hdEnabled ? ":/icons/hd_enabled" : ":/icons/hd_disabled").pixmap(STATUSBAR_ICONSIZE,STATUSBAR_ICONSIZE)); - labelWalletHDStatusIcon->setToolTip(hdEnabled ? tr("HD key generation is <b>enabled</b>") : tr("HD key generation is <b>disabled</b>")); + labelWalletHDStatusIcon->setPixmap(platformStyle->SingleColorIcon(privkeyDisabled ? ":/icons/eye" : hdEnabled ? ":/icons/hd_enabled" : ":/icons/hd_disabled").pixmap(STATUSBAR_ICONSIZE,STATUSBAR_ICONSIZE)); + labelWalletHDStatusIcon->setToolTip(privkeyDisabled ? tr("Private key <b>disabled</b>") : hdEnabled ? tr("HD key generation is <b>enabled</b>") : tr("HD key generation is <b>disabled</b>")); // eventually disable the QLabel to set its opacity to 50% labelWalletHDStatusIcon->setEnabled(hdEnabled); @@ -1119,7 +1202,7 @@ void BitcoinGUI::updateWalletStatus() } WalletModel * const walletModel = walletView->getWalletModel(); setEncryptionStatus(walletModel->getEncryptionStatus()); - setHDStatus(walletModel->wallet().hdEnabled()); + setHDStatus(walletModel->privateKeysDisabled(), walletModel->wallet().hdEnabled()); } #endif // ENABLE_WALLET @@ -1129,7 +1212,7 @@ void BitcoinGUI::updateProxyIcon() bool proxy_enabled = clientModel->getProxyInfo(ip_port); if (proxy_enabled) { - if (labelProxyIcon->pixmap() == 0) { + if (labelProxyIcon->pixmap() == nullptr) { QString ip_port_q = QString::fromStdString(ip_port); labelProxyIcon->setPixmap(platformStyle->SingleColorIcon(":/icons/proxy").pixmap(STATUSBAR_ICONSIZE, STATUSBAR_ICONSIZE)); labelProxyIcon->setToolTip(tr("Proxy is <b>enabled</b>: %1").arg(ip_port_q)); @@ -1141,29 +1224,33 @@ void BitcoinGUI::updateProxyIcon() } } +void BitcoinGUI::updateWindowTitle() +{ + QString window_title = tr(PACKAGE_NAME); +#ifdef ENABLE_WALLET + if (walletFrame) { + WalletModel* const wallet_model = walletFrame->currentWalletModel(); + if (wallet_model && !wallet_model->getWalletName().isEmpty()) { + window_title += " - " + wallet_model->getDisplayName(); + } + } +#endif + if (!m_network_style->getTitleAddText().isEmpty()) { + window_title += " - " + m_network_style->getTitleAddText(); + } + setWindowTitle(window_title); +} + void BitcoinGUI::showNormalIfMinimized(bool fToggleHidden) { if(!clientModel) return; - // activateWindow() (sometimes) helps with keyboard focus on Windows - if (isHidden()) - { - show(); - activateWindow(); - } - else if (isMinimized()) - { - showNormal(); - activateWindow(); - } - else if (GUIUtil::isObscured(this)) - { - raise(); - activateWindow(); - } - else if(fToggleHidden) + if (!isHidden() && !isMinimized() && !GUIUtil::isObscured(this) && fToggleHidden) { hide(); + } else { + GUIUtil::bringToFront(this); + } } void BitcoinGUI::toggleHidden() @@ -1183,25 +1270,21 @@ void BitcoinGUI::detectShutdown() void BitcoinGUI::showProgress(const QString &title, int nProgress) { - if (nProgress == 0) - { - progressDialog = new QProgressDialog(title, "", 0, 100); + if (nProgress == 0) { + progressDialog = new QProgressDialog(title, QString(), 0, 100); + GUIUtil::PolishProgressDialog(progressDialog); progressDialog->setWindowModality(Qt::ApplicationModal); progressDialog->setMinimumDuration(0); - progressDialog->setCancelButton(0); progressDialog->setAutoClose(false); progressDialog->setValue(0); - } - else if (nProgress == 100) - { - if (progressDialog) - { + } else if (nProgress == 100) { + if (progressDialog) { progressDialog->close(); progressDialog->deleteLater(); } - } - else if (progressDialog) + } else if (progressDialog) { progressDialog->setValue(nProgress); + } } void BitcoinGUI::setTrayIconVisible(bool fHideTrayIcon) @@ -1220,9 +1303,6 @@ void BitcoinGUI::showModalOverlay() static bool ThreadSafeMessageBox(BitcoinGUI* gui, const std::string& message, const std::string& caption, unsigned int style) { - // Redundantly log and print message in non-gui fashion - noui_ThreadSafeMessageBox(message, caption, style); - bool modal = (style & CClientUIInterface::MODAL); // The SECURE flag has no effect in the Qt GUI. // bool secure = (style & CClientUIInterface::SECURE); @@ -1241,8 +1321,8 @@ static bool ThreadSafeMessageBox(BitcoinGUI* gui, const std::string& message, co void BitcoinGUI::subscribeToCoreSignals() { // Connect signals to client - m_handler_message_box = m_node.handleMessageBox(boost::bind(ThreadSafeMessageBox, this, _1, _2, _3)); - m_handler_question = m_node.handleQuestion(boost::bind(ThreadSafeMessageBox, this, _1, _3, _4)); + m_handler_message_box = m_node.handleMessageBox(std::bind(ThreadSafeMessageBox, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)); + m_handler_question = m_node.handleQuestion(std::bind(ThreadSafeMessageBox, this, std::placeholders::_1, std::placeholders::_3, std::placeholders::_4)); } void BitcoinGUI::unsubscribeFromCoreSignals() @@ -1253,8 +1333,8 @@ void BitcoinGUI::unsubscribeFromCoreSignals() } UnitDisplayStatusBarControl::UnitDisplayStatusBarControl(const PlatformStyle *platformStyle) : - optionsModel(0), - menu(0) + optionsModel(nullptr), + menu(nullptr) { createContextMenu(); setToolTip(tr("Unit to show amounts in. Click to select another unit.")); diff --git a/src/qt/bitcoingui.h b/src/qt/bitcoingui.h index dcaca10557..f1b76a6b64 100644 --- a/src/qt/bitcoingui.h +++ b/src/qt/bitcoingui.h @@ -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. @@ -16,10 +16,13 @@ #include <QLabel> #include <QMainWindow> #include <QMap> -#include <QMenu> #include <QPoint> #include <QSystemTrayIcon> +#ifdef Q_OS_MAC +#include <qt/macos_appnap.h> +#endif + #include <memory> class ClientModel; @@ -30,6 +33,7 @@ class PlatformStyle; class RPCConsole; class SendCoinsRecipient; class UnitDisplayStatusBarControl; +class WalletController; class WalletFrame; class WalletModel; class HelpMessageDialog; @@ -43,6 +47,7 @@ class Node; QT_BEGIN_NAMESPACE class QAction; class QComboBox; +class QMenu; class QProgressBar; class QProgressDialog; QT_END_NAMESPACE @@ -63,25 +68,33 @@ class BitcoinGUI : public QMainWindow public: static const std::string DEFAULT_UIPLATFORM; - explicit BitcoinGUI(interfaces::Node& node, const PlatformStyle *platformStyle, const NetworkStyle *networkStyle, QWidget *parent = 0); + explicit BitcoinGUI(interfaces::Node& node, const PlatformStyle *platformStyle, const NetworkStyle *networkStyle, QWidget *parent = nullptr); ~BitcoinGUI(); /** Set the client model. The client model represents the part of the core that communicates with the P2P network, and is wallet-agnostic. */ void setClientModel(ClientModel *clientModel); +#ifdef ENABLE_WALLET + void setWalletController(WalletController* wallet_controller); +#endif #ifdef ENABLE_WALLET /** Set the wallet model. The wallet model represents a bitcoin wallet, and offers access to the list of transactions, address book and sending functionality. */ - bool addWallet(WalletModel *walletModel); - bool removeWallet(WalletModel* walletModel); + void addWallet(WalletModel* walletModel); + void removeWallet(WalletModel* walletModel); void removeAllWallets(); #endif // ENABLE_WALLET bool enableWallet = false; + /** Get the tray icon status. + Some systems have not "system tray" or "notification area" available. + */ + bool hasTrayIcon() const { return trayIcon; } + protected: void changeEvent(QEvent *e); void closeEvent(QCloseEvent *event); @@ -92,6 +105,7 @@ protected: private: interfaces::Node& m_node; + WalletController* m_wallet_controller{nullptr}; std::unique_ptr<interfaces::Handler> m_handler_message_box; std::unique_ptr<interfaces::Handler> m_handler_question; ClientModel* clientModel = nullptr; @@ -137,17 +151,22 @@ private: QComboBox* m_wallet_selector = nullptr; QSystemTrayIcon* trayIcon = nullptr; - QMenu* trayIconMenu = nullptr; + const std::unique_ptr<QMenu> trayIconMenu; Notificator* notificator = nullptr; RPCConsole* rpcConsole = nullptr; HelpMessageDialog* helpMessageDialog = nullptr; ModalOverlay* modalOverlay = nullptr; +#ifdef Q_OS_MAC + CAppNapInhibitor* m_app_nap_inhibitor = nullptr; +#endif + /** Keep track of previous number of blocks, to detect progress */ int prevBlocks = 0; int spinnerFrame = 0; const PlatformStyle *platformStyle; + const NetworkStyle* const m_network_style; /** Create the main UI actions. */ void createActions(); @@ -156,7 +175,7 @@ private: /** Create the toolbars */ void createToolBars(); /** Create system tray icon and notification */ - void createTrayIcon(const NetworkStyle *networkStyle); + void createTrayIcon(); /** Create system tray menu (or setup the dock menu) */ void createTrayIconMenu(); @@ -179,6 +198,8 @@ private: Q_SIGNALS: /** Signal raised when a URI was entered or dragged to the GUI */ void receivedURI(const QString &uri); + /** Signal raised when RPC console shown */ + void consoleShown(RPCConsole* console); public Q_SLOTS: /** Set number of connections shown in the UI */ @@ -198,8 +219,8 @@ public Q_SLOTS: void message(const QString &title, const QString &message, unsigned int style, bool *ret = nullptr); #ifdef ENABLE_WALLET - bool setCurrentWallet(const QString& name); - bool setCurrentWalletBySelectorIndex(int index); + void setCurrentWallet(WalletModel* wallet_model); + void setCurrentWalletBySelectorIndex(int index); /** Set the UI status indicators based on the currently selected wallet. */ void updateWalletStatus(); @@ -215,7 +236,7 @@ private: @param[in] hdEnabled current hd enabled status @see WalletModel::EncryptionStatus */ - void setHDStatus(int hdEnabled); + void setHDStatus(bool privkeyDisabled, int hdEnabled); public Q_SLOTS: bool handlePaymentRequest(const SendCoinsRecipient& recipient); @@ -227,6 +248,7 @@ public Q_SLOTS: private: /** Set the proxy-enabled icon as shown in the UI. */ void updateProxyIcon(); + void updateWindowTitle(); public Q_SLOTS: #ifdef ENABLE_WALLET @@ -260,6 +282,9 @@ public Q_SLOTS: #ifndef Q_OS_MAC /** Handle tray icon clicked */ void trayIconActivated(QSystemTrayIcon::ActivationReason reason); +#else + /** Handle macOS Dock icon clicked */ + void macosDockIconActivated(); #endif /** Show window if hidden, unminimize when minimized, rise when obscured or show if hidden and fToggleHidden is true */ diff --git a/src/qt/callback.h b/src/qt/callback.h deleted file mode 100644 index da6b0c4c2e..0000000000 --- a/src/qt/callback.h +++ /dev/null @@ -1,30 +0,0 @@ -#ifndef BITCOIN_QT_CALLBACK_H -#define BITCOIN_QT_CALLBACK_H - -#include <QObject> - -class Callback : public QObject -{ - Q_OBJECT -public Q_SLOTS: - virtual void call() = 0; -}; - -template <typename F> -class FunctionCallback : public Callback -{ - F f; - -public: - explicit FunctionCallback(F f_) : f(std::move(f_)) {} - ~FunctionCallback() override {} - void call() override { f(this); } -}; - -template <typename F> -FunctionCallback<F>* makeCallback(F f) -{ - return new FunctionCallback<F>(std::move(f)); -} - -#endif // BITCOIN_QT_CALLBACK_H diff --git a/src/qt/clientmodel.cpp b/src/qt/clientmodel.cpp index b2cf4b6399..30d9e977b8 100644 --- a/src/qt/clientmodel.cpp +++ b/src/qt/clientmodel.cpp @@ -20,7 +20,7 @@ #include <netbase.h> #include <txmempool.h> #include <ui_interface.h> -#include <util.h> +#include <util/system.h> #include <warnings.h> #include <stdint.h> @@ -35,9 +35,9 @@ ClientModel::ClientModel(interfaces::Node& node, OptionsModel *_optionsModel, QO QObject(parent), m_node(node), optionsModel(_optionsModel), - peerTableModel(0), - banTableModel(0), - pollTimer(0) + peerTableModel(nullptr), + banTableModel(nullptr), + pollTimer(nullptr) { cachedBestHeaderHeight = -1; cachedBestHeaderTime = -1; @@ -177,6 +177,11 @@ QString ClientModel::dataDir() const return GUIUtil::boostPathToQString(GetDataDir()); } +QString ClientModel::blocksDir() const +{ + return GUIUtil::boostPathToQString(GetBlocksDir()); +} + void ClientModel::updateBanlist() { banTableModel->refresh(); @@ -247,13 +252,13 @@ static void BlockTipChanged(ClientModel *clientmodel, bool initialSync, int heig void ClientModel::subscribeToCoreSignals() { // Connect signals to client - m_handler_show_progress = m_node.handleShowProgress(boost::bind(ShowProgress, this, _1, _2)); - m_handler_notify_num_connections_changed = m_node.handleNotifyNumConnectionsChanged(boost::bind(NotifyNumConnectionsChanged, this, _1)); - m_handler_notify_network_active_changed = m_node.handleNotifyNetworkActiveChanged(boost::bind(NotifyNetworkActiveChanged, this, _1)); - m_handler_notify_alert_changed = m_node.handleNotifyAlertChanged(boost::bind(NotifyAlertChanged, this)); - m_handler_banned_list_changed = m_node.handleBannedListChanged(boost::bind(BannedListChanged, this)); - m_handler_notify_block_tip = m_node.handleNotifyBlockTip(boost::bind(BlockTipChanged, this, _1, _2, _3, _4, false)); - m_handler_notify_header_tip = m_node.handleNotifyHeaderTip(boost::bind(BlockTipChanged, this, _1, _2, _3, _4, true)); + m_handler_show_progress = m_node.handleShowProgress(std::bind(ShowProgress, this, std::placeholders::_1, std::placeholders::_2)); + m_handler_notify_num_connections_changed = m_node.handleNotifyNumConnectionsChanged(std::bind(NotifyNumConnectionsChanged, this, std::placeholders::_1)); + m_handler_notify_network_active_changed = m_node.handleNotifyNetworkActiveChanged(std::bind(NotifyNetworkActiveChanged, this, std::placeholders::_1)); + m_handler_notify_alert_changed = m_node.handleNotifyAlertChanged(std::bind(NotifyAlertChanged, this)); + m_handler_banned_list_changed = m_node.handleBannedListChanged(std::bind(BannedListChanged, this)); + m_handler_notify_block_tip = m_node.handleNotifyBlockTip(std::bind(BlockTipChanged, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, false)); + m_handler_notify_header_tip = m_node.handleNotifyHeaderTip(std::bind(BlockTipChanged, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, true)); } void ClientModel::unsubscribeFromCoreSignals() diff --git a/src/qt/clientmodel.h b/src/qt/clientmodel.h index ed7ecbf73b..95f4521f06 100644 --- a/src/qt/clientmodel.h +++ b/src/qt/clientmodel.h @@ -46,7 +46,7 @@ class ClientModel : public QObject Q_OBJECT public: - explicit ClientModel(interfaces::Node& node, OptionsModel *optionsModel, QObject *parent = 0); + explicit ClientModel(interfaces::Node& node, OptionsModel *optionsModel, QObject *parent = nullptr); ~ClientModel(); interfaces::Node& node() const { return m_node; } @@ -69,6 +69,7 @@ public: bool isReleaseVersion() const; QString formatClientStartupTime() const; QString dataDir() const; + QString blocksDir() const; bool getProxyInfo(std::string& ip_port) const; diff --git a/src/qt/coincontroldialog.cpp b/src/qt/coincontroldialog.cpp index 68330c51fa..0d9f1adcd2 100644 --- a/src/qt/coincontroldialog.cpp +++ b/src/qt/coincontroldialog.cpp @@ -2,10 +2,15 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. +#if defined(HAVE_CONFIG_H) +#include <config/bitcoin-config.h> +#endif + #include <qt/coincontroldialog.h> #include <qt/forms/ui_coincontroldialog.h> #include <qt/addresstablemodel.h> +#include <base58.h> #include <qt/bitcoinunits.h> #include <qt/guiutil.h> #include <qt/optionsmodel.h> @@ -44,7 +49,7 @@ bool CCoinControlWidgetItem::operator<(const QTreeWidgetItem &other) const { CoinControlDialog::CoinControlDialog(const PlatformStyle *_platformStyle, QWidget *parent) : QDialog(parent), ui(new Ui::CoinControlDialog), - model(0), + model(nullptr), platformStyle(_platformStyle) { ui->setupUi(this); @@ -124,8 +129,6 @@ CoinControlDialog::CoinControlDialog(const PlatformStyle *_platformStyle, QWidge ui->treeWidget->setColumnWidth(COLUMN_ADDRESS, 320); ui->treeWidget->setColumnWidth(COLUMN_DATE, 130); ui->treeWidget->setColumnWidth(COLUMN_CONFIRMATIONS, 110); - ui->treeWidget->setColumnHidden(COLUMN_TXHASH, true); // store transaction hash in this column, but don't show it - ui->treeWidget->setColumnHidden(COLUMN_VOUT_INDEX, true); // store vout index in this column, but don't show it // default view is sorted by amount desc sortView(COLUMN_AMOUNT, Qt::DescendingOrder); @@ -198,10 +201,10 @@ void CoinControlDialog::showMenu(const QPoint &point) contextMenuItem = item; // disable some items (like Copy Transaction ID, lock, unlock) for tree roots in context menu - if (item->text(COLUMN_TXHASH).length() == 64) // transaction hash is 64 characters (this means it is a child node, so it is not a parent node in tree mode) + if (item->data(COLUMN_ADDRESS, TxHashRole).toString().length() == 64) // transaction hash is 64 characters (this means it is a child node, so it is not a parent node in tree mode) { copyTransactionHashAction->setEnabled(true); - if (model->wallet().isLockedCoin(COutPoint(uint256S(item->text(COLUMN_TXHASH).toStdString()), item->text(COLUMN_VOUT_INDEX).toUInt()))) + if (model->wallet().isLockedCoin(COutPoint(uint256S(item->data(COLUMN_ADDRESS, TxHashRole).toString().toStdString()), item->data(COLUMN_ADDRESS, VOutRole).toUInt()))) { lockAction->setEnabled(false); unlockAction->setEnabled(true); @@ -251,7 +254,7 @@ void CoinControlDialog::copyAddress() // context menu action: copy transaction id void CoinControlDialog::copyTransactionHash() { - GUIUtil::setClipboard(contextMenuItem->text(COLUMN_TXHASH)); + GUIUtil::setClipboard(contextMenuItem->data(COLUMN_ADDRESS, TxHashRole).toString()); } // context menu action: lock coin @@ -260,7 +263,7 @@ void CoinControlDialog::lockCoin() if (contextMenuItem->checkState(COLUMN_CHECKBOX) == Qt::Checked) contextMenuItem->setCheckState(COLUMN_CHECKBOX, Qt::Unchecked); - COutPoint outpt(uint256S(contextMenuItem->text(COLUMN_TXHASH).toStdString()), contextMenuItem->text(COLUMN_VOUT_INDEX).toUInt()); + COutPoint outpt(uint256S(contextMenuItem->data(COLUMN_ADDRESS, TxHashRole).toString().toStdString()), contextMenuItem->data(COLUMN_ADDRESS, VOutRole).toUInt()); model->wallet().lockCoin(outpt); contextMenuItem->setDisabled(true); contextMenuItem->setIcon(COLUMN_CHECKBOX, platformStyle->SingleColorIcon(":/icons/lock_closed")); @@ -270,7 +273,7 @@ void CoinControlDialog::lockCoin() // context menu action: unlock coin void CoinControlDialog::unlockCoin() { - COutPoint outpt(uint256S(contextMenuItem->text(COLUMN_TXHASH).toStdString()), contextMenuItem->text(COLUMN_VOUT_INDEX).toUInt()); + COutPoint outpt(uint256S(contextMenuItem->data(COLUMN_ADDRESS, TxHashRole).toString().toStdString()), contextMenuItem->data(COLUMN_ADDRESS, VOutRole).toUInt()); model->wallet().unlockCoin(outpt); contextMenuItem->setDisabled(false); contextMenuItem->setIcon(COLUMN_CHECKBOX, QIcon()); @@ -366,9 +369,9 @@ void CoinControlDialog::radioListMode(bool checked) // checkbox clicked by user void CoinControlDialog::viewItemChanged(QTreeWidgetItem* item, int column) { - if (column == COLUMN_CHECKBOX && item->text(COLUMN_TXHASH).length() == 64) // transaction hash is 64 characters (this means it is a child node, so it is not a parent node in tree mode) + if (column == COLUMN_CHECKBOX && item->data(COLUMN_ADDRESS, TxHashRole).toString().length() == 64) // transaction hash is 64 characters (this means it is a child node, so it is not a parent node in tree mode) { - COutPoint outpt(uint256S(item->text(COLUMN_TXHASH).toStdString()), item->text(COLUMN_VOUT_INDEX).toUInt()); + COutPoint outpt(uint256S(item->data(COLUMN_ADDRESS, TxHashRole).toString().toStdString()), item->data(COLUMN_ADDRESS, VOutRole).toUInt()); if (item->checkState(COLUMN_CHECKBOX) == Qt::Unchecked) coinControl()->UnSelect(outpt); @@ -688,10 +691,10 @@ void CoinControlDialog::updateView() itemOutput->setData(COLUMN_CONFIRMATIONS, Qt::UserRole, QVariant((qlonglong)out.depth_in_main_chain)); // transaction hash - itemOutput->setText(COLUMN_TXHASH, QString::fromStdString(output.hash.GetHex())); + itemOutput->setData(COLUMN_ADDRESS, TxHashRole, QString::fromStdString(output.hash.GetHex())); // vout index - itemOutput->setText(COLUMN_VOUT_INDEX, QString::number(output.n)); + itemOutput->setData(COLUMN_ADDRESS, VOutRole, output.n); // disable locked coins if (model->wallet().isLockedCoin(output)) diff --git a/src/qt/coincontroldialog.h b/src/qt/coincontroldialog.h index 9c3f6a46a2..efc06a7656 100644 --- a/src/qt/coincontroldialog.h +++ b/src/qt/coincontroldialog.h @@ -43,7 +43,7 @@ class CoinControlDialog : public QDialog Q_OBJECT public: - explicit CoinControlDialog(const PlatformStyle *platformStyle, QWidget *parent = 0); + explicit CoinControlDialog(const PlatformStyle *platformStyle, QWidget *parent = nullptr); ~CoinControlDialog(); void setModel(WalletModel *model); @@ -80,9 +80,14 @@ private: COLUMN_ADDRESS, COLUMN_DATE, COLUMN_CONFIRMATIONS, - COLUMN_TXHASH, - COLUMN_VOUT_INDEX, }; + + enum + { + TxHashRole = Qt::UserRole, + VOutRole + }; + friend class CCoinControlWidgetItem; private Q_SLOTS: diff --git a/src/qt/coincontroltreewidget.h b/src/qt/coincontroltreewidget.h index 62645fcdb0..88fc8b704f 100644 --- a/src/qt/coincontroltreewidget.h +++ b/src/qt/coincontroltreewidget.h @@ -13,7 +13,7 @@ class CoinControlTreeWidget : public QTreeWidget Q_OBJECT public: - explicit CoinControlTreeWidget(QWidget *parent = 0); + explicit CoinControlTreeWidget(QWidget *parent = nullptr); protected: virtual void keyPressEvent(QKeyEvent *event); diff --git a/src/qt/csvmodelwriter.cpp b/src/qt/csvmodelwriter.cpp index fe63fed52c..656afb6e87 100644 --- a/src/qt/csvmodelwriter.cpp +++ b/src/qt/csvmodelwriter.cpp @@ -10,7 +10,7 @@ CSVModelWriter::CSVModelWriter(const QString &_filename, QObject *parent) : QObject(parent), - filename(_filename), model(0) + filename(_filename), model(nullptr) { } diff --git a/src/qt/csvmodelwriter.h b/src/qt/csvmodelwriter.h index edea369ad1..e8611bea35 100644 --- a/src/qt/csvmodelwriter.h +++ b/src/qt/csvmodelwriter.h @@ -20,7 +20,7 @@ class CSVModelWriter : public QObject Q_OBJECT public: - explicit CSVModelWriter(const QString &filename, QObject *parent = 0); + explicit CSVModelWriter(const QString &filename, QObject *parent = nullptr); void setModel(const QAbstractItemModel *model); void addColumn(const QString &title, int column, int role=Qt::EditRole); diff --git a/src/qt/editaddressdialog.cpp b/src/qt/editaddressdialog.cpp index 0b0b96b30c..4711412ac4 100644 --- a/src/qt/editaddressdialog.cpp +++ b/src/qt/editaddressdialog.cpp @@ -15,9 +15,9 @@ EditAddressDialog::EditAddressDialog(Mode _mode, QWidget *parent) : QDialog(parent), ui(new Ui::EditAddressDialog), - mapper(0), + mapper(nullptr), mode(_mode), - model(0) + model(nullptr) { ui->setupUi(this); diff --git a/src/qt/editaddressdialog.h b/src/qt/editaddressdialog.h index fd88b45793..4d0e0709be 100644 --- a/src/qt/editaddressdialog.h +++ b/src/qt/editaddressdialog.h @@ -30,7 +30,7 @@ public: EditSendingAddress }; - explicit EditAddressDialog(Mode mode, QWidget *parent = 0); + explicit EditAddressDialog(Mode mode, QWidget *parent = nullptr); ~EditAddressDialog(); void setModel(AddressTableModel *model); diff --git a/src/qt/forms/coincontroldialog.ui b/src/qt/forms/coincontroldialog.ui index d1237ad283..bd7f3c5f56 100644 --- a/src/qt/forms/coincontroldialog.ui +++ b/src/qt/forms/coincontroldialog.ui @@ -402,7 +402,7 @@ <bool>false</bool> </property> <property name="columnCount"> - <number>10</number> + <number>6</number> </property> <attribute name="headerShowSortIndicator" stdset="0"> <bool>true</bool> diff --git a/src/qt/forms/debugwindow.ui b/src/qt/forms/debugwindow.ui index 695ed61228..f0b976001e 100644 --- a/src/qt/forms/debugwindow.ui +++ b/src/qt/forms/debugwindow.ui @@ -127,6 +127,9 @@ <property name="cursor"> <cursorShape>IBeamCursor</cursorShape> </property> + <property name="toolTip"> + <string>To specify a non-default location of the data directory use the '%1' option.</string> + </property> <property name="text"> <string>N/A</string> </property> @@ -142,13 +145,42 @@ </widget> </item> <item row="5" column="0"> + <widget class="QLabel" name="label_11"> + <property name="text"> + <string>Blocksdir</string> + </property> + </widget> + </item> + <item row="5" column="1" colspan="2"> + <widget class="QLabel" name="blocksDir"> + <property name="cursor"> + <cursorShape>IBeamCursor</cursorShape> + </property> + <property name="toolTip"> + <string>To specify a non-default location of the blocks directory use the '%1' option.</string> + </property> + <property name="text"> + <string>N/A</string> + </property> + <property name="textFormat"> + <enum>Qt::PlainText</enum> + </property> + <property name="wordWrap"> + <bool>true</bool> + </property> + <property name="textInteractionFlags"> + <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set> + </property> + </widget> + </item> + <item row="6" column="0"> <widget class="QLabel" name="label_13"> <property name="text"> <string>Startup time</string> </property> </widget> </item> - <item row="5" column="1" colspan="2"> + <item row="6" column="1" colspan="2"> <widget class="QLabel" name="startupTime"> <property name="cursor"> <cursorShape>IBeamCursor</cursorShape> @@ -164,7 +196,7 @@ </property> </widget> </item> - <item row="6" column="0"> + <item row="7" column="0"> <widget class="QLabel" name="labelNetwork"> <property name="font"> <font> @@ -177,14 +209,14 @@ </property> </widget> </item> - <item row="7" column="0"> + <item row="8" column="0"> <widget class="QLabel" name="label_8"> <property name="text"> <string>Name</string> </property> </widget> </item> - <item row="7" column="1" colspan="2"> + <item row="8" column="1" colspan="2"> <widget class="QLabel" name="networkName"> <property name="cursor"> <cursorShape>IBeamCursor</cursorShape> @@ -200,14 +232,14 @@ </property> </widget> </item> - <item row="8" column="0"> + <item row="9" column="0"> <widget class="QLabel" name="label_7"> <property name="text"> <string>Number of connections</string> </property> </widget> </item> - <item row="8" column="1" colspan="2"> + <item row="9" column="1" colspan="2"> <widget class="QLabel" name="numberOfConnections"> <property name="cursor"> <cursorShape>IBeamCursor</cursorShape> @@ -223,7 +255,7 @@ </property> </widget> </item> - <item row="9" column="0"> + <item row="10" column="0"> <widget class="QLabel" name="label_10"> <property name="font"> <font> @@ -236,14 +268,14 @@ </property> </widget> </item> - <item row="10" column="0"> + <item row="11" column="0"> <widget class="QLabel" name="label_3"> <property name="text"> <string>Current number of blocks</string> </property> </widget> </item> - <item row="10" column="1" colspan="2"> + <item row="11" column="1" colspan="2"> <widget class="QLabel" name="numberOfBlocks"> <property name="cursor"> <cursorShape>IBeamCursor</cursorShape> @@ -259,14 +291,14 @@ </property> </widget> </item> - <item row="11" column="0"> + <item row="12" column="0"> <widget class="QLabel" name="labelLastBlockTime"> <property name="text"> <string>Last block time</string> </property> </widget> </item> - <item row="11" column="1" colspan="2"> + <item row="12" column="1" colspan="2"> <widget class="QLabel" name="lastBlockTime"> <property name="cursor"> <cursorShape>IBeamCursor</cursorShape> @@ -282,7 +314,7 @@ </property> </widget> </item> - <item row="12" column="0"> + <item row="13" column="0"> <widget class="QLabel" name="labelMempoolTitle"> <property name="font"> <font> @@ -295,14 +327,14 @@ </property> </widget> </item> - <item row="13" column="0"> + <item row="14" column="0"> <widget class="QLabel" name="labelNumberOfTransactions"> <property name="text"> <string>Current number of transactions</string> </property> </widget> </item> - <item row="13" column="1"> + <item row="14" column="1"> <widget class="QLabel" name="mempoolNumberTxs"> <property name="cursor"> <cursorShape>IBeamCursor</cursorShape> @@ -318,14 +350,14 @@ </property> </widget> </item> - <item row="14" column="0"> + <item row="15" column="0"> <widget class="QLabel" name="labelMemoryUsage"> <property name="text"> <string>Memory usage</string> </property> </widget> </item> - <item row="14" column="1"> + <item row="15" column="1"> <widget class="QLabel" name="mempoolSize"> <property name="cursor"> <cursorShape>IBeamCursor</cursorShape> @@ -341,7 +373,7 @@ </property> </widget> </item> - <item row="12" column="2" rowspan="3"> + <item row="13" column="2" rowspan="3"> <layout class="QVBoxLayout" name="verticalLayoutDebugButton"> <property name="spacing"> <number>3</number> @@ -381,7 +413,7 @@ </item> </layout> </item> - <item row="15" column="0"> + <item row="16" column="0"> <spacer name="verticalSpacer"> <property name="orientation"> <enum>Qt::Vertical</enum> @@ -421,6 +453,9 @@ </item> <item> <widget class="QComboBox" name="WalletSelector"> + <property name="sizeAdjustPolicy"> + <enum>QComboBox::AdjustToContents</enum> + </property> <item> <property name="text"> <string>(none)</string> @@ -864,570 +899,593 @@ <attribute name="title"> <string>&Peers</string> </attribute> - <layout class="QGridLayout" name="gridLayout_2"> - <item row="0" column="0" rowspan="2"> - <layout class="QVBoxLayout" name="verticalLayout_101"> - <property name="spacing"> - <number>0</number> - </property> - <item> - <widget class="QTableView" name="peerWidget"> - <property name="horizontalScrollBarPolicy"> - <enum>Qt::ScrollBarAsNeeded</enum> - </property> - <property name="tabKeyNavigation"> - <bool>false</bool> - </property> - <property name="sortingEnabled"> - <bool>true</bool> - </property> - <attribute name="horizontalHeaderHighlightSections"> - <bool>false</bool> - </attribute> - </widget> - </item> - <item> - <widget class="QLabel" name="banHeading"> - <property name="sizePolicy"> - <sizepolicy hsizetype="Preferred" vsizetype="Minimum"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> - </property> - <property name="minimumSize"> - <size> - <width>300</width> - <height>32</height> - </size> - </property> - <property name="maximumSize"> - <size> - <width>16777215</width> - <height>32</height> - </size> - </property> - <property name="font"> - <font> - <pointsize>12</pointsize> - </font> - </property> - <property name="cursor"> - <cursorShape>IBeamCursor</cursorShape> - </property> - <property name="text"> - <string>Banned peers</string> - </property> - <property name="alignment"> - <set>Qt::AlignBottom|Qt::AlignLeading|Qt::AlignLeft</set> - </property> - <property name="wordWrap"> - <bool>true</bool> - </property> - <property name="textInteractionFlags"> - <set>Qt::NoTextInteraction</set> - </property> - </widget> - </item> - <item> - <widget class="QTableView" name="banlistWidget"> - <property name="sizePolicy"> - <sizepolicy hsizetype="Expanding" vsizetype="Expanding"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> - </property> - <property name="horizontalScrollBarPolicy"> - <enum>Qt::ScrollBarAsNeeded</enum> - </property> - <property name="tabKeyNavigation"> - <bool>false</bool> - </property> - <property name="sortingEnabled"> - <bool>true</bool> - </property> - <attribute name="horizontalHeaderHighlightSections"> - <bool>false</bool> - </attribute> - </widget> - </item> - </layout> - </item> - <item row="0" column="1"> - <widget class="QLabel" name="peerHeading"> - <property name="sizePolicy"> - <sizepolicy hsizetype="Preferred" vsizetype="Minimum"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> - </property> - <property name="minimumSize"> - <size> - <width>300</width> - <height>32</height> - </size> - </property> - <property name="font"> - <font> - <pointsize>10</pointsize> - </font> - </property> - <property name="cursor"> - <cursorShape>IBeamCursor</cursorShape> - </property> - <property name="text"> - <string>Select a peer to view detailed information.</string> - </property> - <property name="alignment"> - <set>Qt::AlignHCenter|Qt::AlignTop</set> - </property> - <property name="wordWrap"> - <bool>true</bool> - </property> - <property name="textInteractionFlags"> - <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set> + <layout class="QVBoxLayout" name="verticalLayout_6"> + <item> + <widget class="QSplitter" name="splitter"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> </property> - </widget> - </item> - <item row="1" column="1"> - <widget class="QWidget" name="detailWidget" native="true"> - <property name="minimumSize"> - <size> - <width>300</width> - <height>0</height> - </size> + <property name="childrenCollapsible"> + <bool>false</bool> </property> - <layout class="QGridLayout" name="gridLayout_3"> - <item row="0" column="0"> - <widget class="QLabel" name="label_30"> - <property name="text"> - <string>Whitelisted</string> - </property> - </widget> - </item> - <item row="0" column="2"> - <widget class="QLabel" name="peerWhitelisted"> - <property name="cursor"> - <cursorShape>IBeamCursor</cursorShape> - </property> - <property name="text"> - <string>N/A</string> - </property> - <property name="textFormat"> - <enum>Qt::PlainText</enum> - </property> - <property name="textInteractionFlags"> - <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set> - </property> - </widget> - </item> - <item row="1" column="0"> - <widget class="QLabel" name="label_23"> - <property name="text"> - <string>Direction</string> - </property> - </widget> - </item> - <item row="1" column="2"> - <widget class="QLabel" name="peerDirection"> - <property name="cursor"> - <cursorShape>IBeamCursor</cursorShape> - </property> - <property name="text"> - <string>N/A</string> - </property> - <property name="textFormat"> - <enum>Qt::PlainText</enum> - </property> - <property name="textInteractionFlags"> - <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set> - </property> - </widget> - </item> - <item row="2" column="0"> - <widget class="QLabel" name="label_21"> - <property name="text"> - <string>Version</string> - </property> - </widget> - </item> - <item row="2" column="2"> - <widget class="QLabel" name="peerVersion"> - <property name="cursor"> - <cursorShape>IBeamCursor</cursorShape> - </property> - <property name="text"> - <string>N/A</string> - </property> - <property name="textFormat"> - <enum>Qt::PlainText</enum> - </property> - <property name="textInteractionFlags"> - <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set> - </property> - </widget> - </item> - <item row="3" column="0"> - <widget class="QLabel" name="label_28"> - <property name="text"> - <string>User Agent</string> - </property> - </widget> - </item> - <item row="3" column="2"> - <widget class="QLabel" name="peerSubversion"> - <property name="cursor"> - <cursorShape>IBeamCursor</cursorShape> - </property> - <property name="text"> - <string>N/A</string> - </property> - <property name="textFormat"> - <enum>Qt::PlainText</enum> - </property> - <property name="textInteractionFlags"> - <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set> - </property> - </widget> - </item> - <item row="4" column="0"> - <widget class="QLabel" name="label_4"> - <property name="text"> - <string>Services</string> - </property> - </widget> - </item> - <item row="4" column="2"> - <widget class="QLabel" name="peerServices"> - <property name="cursor"> - <cursorShape>IBeamCursor</cursorShape> - </property> - <property name="text"> - <string>N/A</string> - </property> - <property name="textFormat"> - <enum>Qt::PlainText</enum> - </property> - <property name="textInteractionFlags"> - <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set> - </property> - </widget> - </item> - <item row="5" column="0"> - <widget class="QLabel" name="label_29"> - <property name="text"> - <string>Starting Block</string> - </property> - </widget> - </item> - <item row="5" column="2"> - <widget class="QLabel" name="peerHeight"> - <property name="cursor"> - <cursorShape>IBeamCursor</cursorShape> - </property> - <property name="text"> - <string>N/A</string> - </property> - <property name="textFormat"> - <enum>Qt::PlainText</enum> - </property> - <property name="textInteractionFlags"> - <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set> - </property> - </widget> - </item> - <item row="6" column="0"> - <widget class="QLabel" name="label_27"> - <property name="text"> - <string>Synced Headers</string> - </property> - </widget> - </item> - <item row="6" column="2"> - <widget class="QLabel" name="peerSyncHeight"> - <property name="cursor"> - <cursorShape>IBeamCursor</cursorShape> - </property> - <property name="text"> - <string>N/A</string> - </property> - <property name="textFormat"> - <enum>Qt::PlainText</enum> - </property> - <property name="textInteractionFlags"> - <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set> - </property> - </widget> - </item> - <item row="7" column="0"> - <widget class="QLabel" name="label_25"> - <property name="text"> - <string>Synced Blocks</string> - </property> - </widget> - </item> - <item row="7" column="2"> - <widget class="QLabel" name="peerCommonHeight"> - <property name="cursor"> - <cursorShape>IBeamCursor</cursorShape> - </property> - <property name="text"> - <string>N/A</string> - </property> - <property name="textFormat"> - <enum>Qt::PlainText</enum> - </property> - <property name="textInteractionFlags"> - <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set> - </property> - </widget> - </item> - <item row="8" column="0"> - <widget class="QLabel" name="label_24"> - <property name="text"> - <string>Ban Score</string> - </property> - </widget> - </item> - <item row="8" column="2"> - <widget class="QLabel" name="peerBanScore"> - <property name="cursor"> - <cursorShape>IBeamCursor</cursorShape> - </property> - <property name="text"> - <string>N/A</string> - </property> - <property name="textFormat"> - <enum>Qt::PlainText</enum> - </property> - <property name="textInteractionFlags"> - <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set> - </property> - </widget> - </item> - <item row="9" column="0"> - <widget class="QLabel" name="label_22"> - <property name="text"> - <string>Connection Time</string> - </property> - </widget> - </item> - <item row="9" column="2"> - <widget class="QLabel" name="peerConnTime"> - <property name="cursor"> - <cursorShape>IBeamCursor</cursorShape> - </property> - <property name="text"> - <string>N/A</string> - </property> - <property name="textFormat"> - <enum>Qt::PlainText</enum> - </property> - <property name="textInteractionFlags"> - <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set> - </property> - </widget> - </item> - <item row="10" column="0"> - <widget class="QLabel" name="label_15"> - <property name="text"> - <string>Last Send</string> - </property> - </widget> - </item> - <item row="10" column="2"> - <widget class="QLabel" name="peerLastSend"> - <property name="cursor"> - <cursorShape>IBeamCursor</cursorShape> - </property> - <property name="text"> - <string>N/A</string> - </property> - <property name="textFormat"> - <enum>Qt::PlainText</enum> - </property> - <property name="textInteractionFlags"> - <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set> - </property> - </widget> - </item> - <item row="11" column="0"> - <widget class="QLabel" name="label_19"> - <property name="text"> - <string>Last Receive</string> - </property> - </widget> - </item> - <item row="11" column="2"> - <widget class="QLabel" name="peerLastRecv"> - <property name="cursor"> - <cursorShape>IBeamCursor</cursorShape> - </property> - <property name="text"> - <string>N/A</string> - </property> - <property name="textFormat"> - <enum>Qt::PlainText</enum> - </property> - <property name="textInteractionFlags"> - <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set> - </property> - </widget> - </item> - <item row="12" column="0"> - <widget class="QLabel" name="label_18"> - <property name="text"> - <string>Sent</string> - </property> - </widget> - </item> - <item row="12" column="2"> - <widget class="QLabel" name="peerBytesSent"> - <property name="cursor"> - <cursorShape>IBeamCursor</cursorShape> - </property> - <property name="text"> - <string>N/A</string> - </property> - <property name="textFormat"> - <enum>Qt::PlainText</enum> - </property> - <property name="textInteractionFlags"> - <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set> - </property> - </widget> - </item> - <item row="13" column="0"> - <widget class="QLabel" name="label_20"> - <property name="text"> - <string>Received</string> - </property> - </widget> - </item> - <item row="13" column="2"> - <widget class="QLabel" name="peerBytesRecv"> - <property name="cursor"> - <cursorShape>IBeamCursor</cursorShape> - </property> - <property name="text"> - <string>N/A</string> - </property> - <property name="textFormat"> - <enum>Qt::PlainText</enum> - </property> - <property name="textInteractionFlags"> - <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set> - </property> - </widget> - </item> - <item row="14" column="0"> - <widget class="QLabel" name="label_26"> - <property name="text"> - <string>Ping Time</string> - </property> - </widget> - </item> - <item row="14" column="2"> - <widget class="QLabel" name="peerPingTime"> - <property name="cursor"> - <cursorShape>IBeamCursor</cursorShape> - </property> - <property name="text"> - <string>N/A</string> - </property> - <property name="textFormat"> - <enum>Qt::PlainText</enum> - </property> - <property name="textInteractionFlags"> - <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set> - </property> - </widget> - </item> - <item row="15" column="0"> - <widget class="QLabel" name="peerPingWaitLabel"> - <property name="toolTip"> - <string>The duration of a currently outstanding ping.</string> - </property> - <property name="text"> - <string>Ping Wait</string> - </property> - </widget> - </item> - <item row="15" column="2"> - <widget class="QLabel" name="peerPingWait"> - <property name="cursor"> - <cursorShape>IBeamCursor</cursorShape> - </property> - <property name="text"> - <string>N/A</string> - </property> - <property name="textFormat"> - <enum>Qt::PlainText</enum> - </property> - <property name="textInteractionFlags"> - <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set> - </property> - </widget> - </item> - <item row="16" column="0"> - <widget class="QLabel" name="peerMinPingLabel"> - <property name="text"> - <string>Min Ping</string> - </property> - </widget> - </item> - <item row="16" column="2"> - <widget class="QLabel" name="peerMinPing"> - <property name="cursor"> - <cursorShape>IBeamCursor</cursorShape> - </property> - <property name="text"> - <string>N/A</string> - </property> - <property name="textFormat"> - <enum>Qt::PlainText</enum> - </property> - <property name="textInteractionFlags"> - <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set> - </property> - </widget> - </item> - <item row="17" column="0"> - <widget class="QLabel" name="label_timeoffset"> - <property name="text"> - <string>Time Offset</string> - </property> - </widget> - </item> - <item row="17" column="2"> - <widget class="QLabel" name="timeoffset"> - <property name="cursor"> - <cursorShape>IBeamCursor</cursorShape> - </property> - <property name="text"> - <string>N/A</string> - </property> - <property name="textFormat"> - <enum>Qt::PlainText</enum> - </property> - <property name="textInteractionFlags"> - <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set> - </property> - </widget> - </item> - <item row="18" column="1"> - <spacer name="verticalSpacer_3"> - <property name="orientation"> - <enum>Qt::Vertical</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>20</width> - <height>40</height> - </size> - </property> - </spacer> - </item> - </layout> + <widget class="QWidget" name="widget_1" native="true"> + <property name="sizePolicy"> + <sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred"> + <horstretch>1</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>400</width> + <height>0</height> + </size> + </property> + <layout class="QVBoxLayout" name="verticalLayout_7"> + <item> + <widget class="QTableView" name="peerWidget"> + <property name="tabKeyNavigation"> + <bool>false</bool> + </property> + <property name="sortingEnabled"> + <bool>true</bool> + </property> + <attribute name="horizontalHeaderHighlightSections"> + <bool>false</bool> + </attribute> + </widget> + </item> + <item> + <widget class="QLabel" name="banHeading"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Minimum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>0</width> + <height>32</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>16777215</width> + <height>32</height> + </size> + </property> + <property name="font"> + <font> + <pointsize>12</pointsize> + </font> + </property> + <property name="cursor"> + <cursorShape>IBeamCursor</cursorShape> + </property> + <property name="text"> + <string>Banned peers</string> + </property> + <property name="alignment"> + <set>Qt::AlignBottom|Qt::AlignLeading|Qt::AlignLeft</set> + </property> + <property name="wordWrap"> + <bool>true</bool> + </property> + <property name="textInteractionFlags"> + <set>Qt::NoTextInteraction</set> + </property> + </widget> + </item> + <item> + <widget class="QTableView" name="banlistWidget"> + <property name="tabKeyNavigation"> + <bool>false</bool> + </property> + <property name="sortingEnabled"> + <bool>true</bool> + </property> + <attribute name="horizontalHeaderHighlightSections"> + <bool>false</bool> + </attribute> + </widget> + </item> + </layout> + </widget> + <widget class="QWidget" name="widget_2" native="true"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Minimum" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>300</width> + <height>0</height> + </size> + </property> + <layout class="QVBoxLayout" name="verticalLayout_8"> + <item> + <widget class="QLabel" name="peerHeading"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Minimum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>0</width> + <height>32</height> + </size> + </property> + <property name="font"> + <font> + <pointsize>10</pointsize> + </font> + </property> + <property name="cursor"> + <cursorShape>IBeamCursor</cursorShape> + </property> + <property name="text"> + <string>Select a peer to view detailed information.</string> + </property> + <property name="alignment"> + <set>Qt::AlignHCenter|Qt::AlignTop</set> + </property> + <property name="wordWrap"> + <bool>true</bool> + </property> + <property name="textInteractionFlags"> + <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set> + </property> + </widget> + </item> + <item> + <widget class="QScrollArea" name="scrollArea"> + <property name="widgetResizable"> + <bool>true</bool> + </property> + <widget class="QWidget" name="detailWidget"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>300</width> + <height>426</height> + </rect> + </property> + <property name="minimumSize"> + <size> + <width>300</width> + <height>0</height> + </size> + </property> + <layout class="QGridLayout" name="gridLayout_2" columnstretch="0,1"> + <item row="0" column="0"> + <widget class="QLabel" name="label_30"> + <property name="text"> + <string>Whitelisted</string> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QLabel" name="peerWhitelisted"> + <property name="cursor"> + <cursorShape>IBeamCursor</cursorShape> + </property> + <property name="text"> + <string>N/A</string> + </property> + <property name="textFormat"> + <enum>Qt::PlainText</enum> + </property> + <property name="textInteractionFlags"> + <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="label_23"> + <property name="text"> + <string>Direction</string> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QLabel" name="peerDirection"> + <property name="cursor"> + <cursorShape>IBeamCursor</cursorShape> + </property> + <property name="text"> + <string>N/A</string> + </property> + <property name="textFormat"> + <enum>Qt::PlainText</enum> + </property> + <property name="textInteractionFlags"> + <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set> + </property> + </widget> + </item> + <item row="2" column="0"> + <widget class="QLabel" name="label_21"> + <property name="text"> + <string>Version</string> + </property> + </widget> + </item> + <item row="2" column="1"> + <widget class="QLabel" name="peerVersion"> + <property name="cursor"> + <cursorShape>IBeamCursor</cursorShape> + </property> + <property name="text"> + <string>N/A</string> + </property> + <property name="textFormat"> + <enum>Qt::PlainText</enum> + </property> + <property name="textInteractionFlags"> + <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set> + </property> + </widget> + </item> + <item row="3" column="0"> + <widget class="QLabel" name="label_28"> + <property name="text"> + <string>User Agent</string> + </property> + </widget> + </item> + <item row="3" column="1"> + <widget class="QLabel" name="peerSubversion"> + <property name="cursor"> + <cursorShape>IBeamCursor</cursorShape> + </property> + <property name="text"> + <string>N/A</string> + </property> + <property name="textFormat"> + <enum>Qt::PlainText</enum> + </property> + <property name="textInteractionFlags"> + <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set> + </property> + </widget> + </item> + <item row="4" column="0"> + <widget class="QLabel" name="label_4"> + <property name="text"> + <string>Services</string> + </property> + </widget> + </item> + <item row="4" column="1"> + <widget class="QLabel" name="peerServices"> + <property name="cursor"> + <cursorShape>IBeamCursor</cursorShape> + </property> + <property name="text"> + <string>N/A</string> + </property> + <property name="textFormat"> + <enum>Qt::PlainText</enum> + </property> + <property name="textInteractionFlags"> + <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set> + </property> + </widget> + </item> + <item row="5" column="0"> + <widget class="QLabel" name="label_29"> + <property name="text"> + <string>Starting Block</string> + </property> + </widget> + </item> + <item row="5" column="1"> + <widget class="QLabel" name="peerHeight"> + <property name="cursor"> + <cursorShape>IBeamCursor</cursorShape> + </property> + <property name="text"> + <string>N/A</string> + </property> + <property name="textFormat"> + <enum>Qt::PlainText</enum> + </property> + <property name="textInteractionFlags"> + <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set> + </property> + </widget> + </item> + <item row="6" column="0"> + <widget class="QLabel" name="label_27"> + <property name="text"> + <string>Synced Headers</string> + </property> + </widget> + </item> + <item row="6" column="1"> + <widget class="QLabel" name="peerSyncHeight"> + <property name="cursor"> + <cursorShape>IBeamCursor</cursorShape> + </property> + <property name="text"> + <string>N/A</string> + </property> + <property name="textFormat"> + <enum>Qt::PlainText</enum> + </property> + <property name="textInteractionFlags"> + <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set> + </property> + </widget> + </item> + <item row="7" column="0"> + <widget class="QLabel" name="label_25"> + <property name="text"> + <string>Synced Blocks</string> + </property> + </widget> + </item> + <item row="7" column="1"> + <widget class="QLabel" name="peerCommonHeight"> + <property name="cursor"> + <cursorShape>IBeamCursor</cursorShape> + </property> + <property name="text"> + <string>N/A</string> + </property> + <property name="textFormat"> + <enum>Qt::PlainText</enum> + </property> + <property name="textInteractionFlags"> + <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set> + </property> + </widget> + </item> + <item row="8" column="0"> + <widget class="QLabel" name="label_24"> + <property name="text"> + <string>Ban Score</string> + </property> + </widget> + </item> + <item row="8" column="1"> + <widget class="QLabel" name="peerBanScore"> + <property name="cursor"> + <cursorShape>IBeamCursor</cursorShape> + </property> + <property name="text"> + <string>N/A</string> + </property> + <property name="textFormat"> + <enum>Qt::PlainText</enum> + </property> + <property name="textInteractionFlags"> + <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set> + </property> + </widget> + </item> + <item row="9" column="0"> + <widget class="QLabel" name="label_22"> + <property name="text"> + <string>Connection Time</string> + </property> + </widget> + </item> + <item row="9" column="1"> + <widget class="QLabel" name="peerConnTime"> + <property name="cursor"> + <cursorShape>IBeamCursor</cursorShape> + </property> + <property name="text"> + <string>N/A</string> + </property> + <property name="textFormat"> + <enum>Qt::PlainText</enum> + </property> + <property name="textInteractionFlags"> + <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set> + </property> + </widget> + </item> + <item row="10" column="0"> + <widget class="QLabel" name="label_15"> + <property name="text"> + <string>Last Send</string> + </property> + </widget> + </item> + <item row="10" column="1"> + <widget class="QLabel" name="peerLastSend"> + <property name="cursor"> + <cursorShape>IBeamCursor</cursorShape> + </property> + <property name="text"> + <string>N/A</string> + </property> + <property name="textFormat"> + <enum>Qt::PlainText</enum> + </property> + <property name="textInteractionFlags"> + <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set> + </property> + </widget> + </item> + <item row="11" column="0"> + <widget class="QLabel" name="label_19"> + <property name="text"> + <string>Last Receive</string> + </property> + </widget> + </item> + <item row="11" column="1"> + <widget class="QLabel" name="peerLastRecv"> + <property name="cursor"> + <cursorShape>IBeamCursor</cursorShape> + </property> + <property name="text"> + <string>N/A</string> + </property> + <property name="textFormat"> + <enum>Qt::PlainText</enum> + </property> + <property name="textInteractionFlags"> + <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set> + </property> + </widget> + </item> + <item row="12" column="0"> + <widget class="QLabel" name="label_18"> + <property name="text"> + <string>Sent</string> + </property> + </widget> + </item> + <item row="12" column="1"> + <widget class="QLabel" name="peerBytesSent"> + <property name="cursor"> + <cursorShape>IBeamCursor</cursorShape> + </property> + <property name="text"> + <string>N/A</string> + </property> + <property name="textFormat"> + <enum>Qt::PlainText</enum> + </property> + <property name="textInteractionFlags"> + <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set> + </property> + </widget> + </item> + <item row="13" column="0"> + <widget class="QLabel" name="label_20"> + <property name="text"> + <string>Received</string> + </property> + </widget> + </item> + <item row="13" column="1"> + <widget class="QLabel" name="peerBytesRecv"> + <property name="cursor"> + <cursorShape>IBeamCursor</cursorShape> + </property> + <property name="text"> + <string>N/A</string> + </property> + <property name="textFormat"> + <enum>Qt::PlainText</enum> + </property> + <property name="textInteractionFlags"> + <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set> + </property> + </widget> + </item> + <item row="14" column="0"> + <widget class="QLabel" name="label_26"> + <property name="text"> + <string>Ping Time</string> + </property> + </widget> + </item> + <item row="14" column="1"> + <widget class="QLabel" name="peerPingTime"> + <property name="cursor"> + <cursorShape>IBeamCursor</cursorShape> + </property> + <property name="text"> + <string>N/A</string> + </property> + <property name="textFormat"> + <enum>Qt::PlainText</enum> + </property> + <property name="textInteractionFlags"> + <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set> + </property> + </widget> + </item> + <item row="15" column="0"> + <widget class="QLabel" name="peerPingWaitLabel"> + <property name="toolTip"> + <string>The duration of a currently outstanding ping.</string> + </property> + <property name="text"> + <string>Ping Wait</string> + </property> + </widget> + </item> + <item row="15" column="1"> + <widget class="QLabel" name="peerPingWait"> + <property name="cursor"> + <cursorShape>IBeamCursor</cursorShape> + </property> + <property name="text"> + <string>N/A</string> + </property> + <property name="textFormat"> + <enum>Qt::PlainText</enum> + </property> + <property name="textInteractionFlags"> + <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set> + </property> + </widget> + </item> + <item row="16" column="0"> + <widget class="QLabel" name="peerMinPingLabel"> + <property name="text"> + <string>Min Ping</string> + </property> + </widget> + </item> + <item row="16" column="1"> + <widget class="QLabel" name="peerMinPing"> + <property name="cursor"> + <cursorShape>IBeamCursor</cursorShape> + </property> + <property name="text"> + <string>N/A</string> + </property> + <property name="textFormat"> + <enum>Qt::PlainText</enum> + </property> + <property name="textInteractionFlags"> + <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set> + </property> + </widget> + </item> + <item row="17" column="0"> + <widget class="QLabel" name="label_timeoffset"> + <property name="text"> + <string>Time Offset</string> + </property> + </widget> + </item> + <item row="17" column="1"> + <widget class="QLabel" name="timeoffset"> + <property name="cursor"> + <cursorShape>IBeamCursor</cursorShape> + </property> + <property name="text"> + <string>N/A</string> + </property> + <property name="textFormat"> + <enum>Qt::PlainText</enum> + </property> + <property name="textInteractionFlags"> + <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set> + </property> + </widget> + </item> + </layout> + </widget> + </widget> + </item> + </layout> + </widget> </widget> </item> </layout> diff --git a/src/qt/forms/optionsdialog.ui b/src/qt/forms/optionsdialog.ui index 8f34e6bc82..507d195b72 100644 --- a/src/qt/forms/optionsdialog.ui +++ b/src/qt/forms/optionsdialog.ui @@ -714,7 +714,7 @@ <item> <widget class="QLabel" name="overriddenByCommandLineInfoLabel"> <property name="text"> - <string>Active command-line options that override above options:</string> + <string>Options set in this dialog are overridden by the command line or in the configuration file:</string> </property> <property name="textFormat"> <enum>Qt::PlainText</enum> diff --git a/src/qt/forms/sendcoinsdialog.ui b/src/qt/forms/sendcoinsdialog.ui index 6b31ddea90..386d559281 100644 --- a/src/qt/forms/sendcoinsdialog.ui +++ b/src/qt/forms/sendcoinsdialog.ui @@ -878,28 +878,15 @@ Note: Since the fee is calculated on a per-byte basis, a fee of "100 satoshis p <item> <layout class="QHBoxLayout" name="horizontalLayoutFee8"> <item> - <widget class="QCheckBox" name="checkBoxMinimumFee"> - <property name="toolTip"> - <string>Paying only the minimum fee is just fine as long as there is less transaction volume than space in the blocks. But be aware that this can end up in a never confirming transaction once there is more demand for bitcoin transactions than the network can process.</string> - </property> - <property name="text"> - <string/> - </property> - </widget> - </item> - <item> - <widget class="QLabel" name="labelMinFeeWarning"> + <widget class="QLabel" name="labelCustomFeeWarning"> <property name="enabled"> <bool>true</bool> </property> <property name="toolTip"> - <string>Paying only the minimum fee is just fine as long as there is less transaction volume than space in the blocks. But be aware that this can end up in a never confirming transaction once there is more demand for bitcoin transactions than the network can process.</string> + <string>When there is less transaction volume than space in the blocks, miners as well as relaying nodes may enforce a minimum fee. Paying only this minimum fee is just fine, but be aware that this can result in a never confirming transaction once there is more demand for bitcoin transactions than the network can process.</string> </property> <property name="text"> - <string>(read the tooltip)</string> - </property> - <property name="margin"> - <number>5</number> + <string>A too low fee might result in a never confirming transaction (read the tooltip)</string> </property> </widget> </item> @@ -992,9 +979,6 @@ Note: Since the fee is calculated on a per-byte basis, a fee of "100 satoshis p <property name="text"> <string/> </property> - <property name="margin"> - <number>2</number> - </property> </widget> </item> <item> @@ -1009,9 +993,6 @@ Note: Since the fee is calculated on a per-byte basis, a fee of "100 satoshis p <property name="text"> <string>(Smart fee not initialized yet. This usually takes a few blocks...)</string> </property> - <property name="margin"> - <number>2</number> - </property> </widget> </item> <item> @@ -1038,24 +1019,8 @@ Note: Since the fee is calculated on a per-byte basis, a fee of "100 satoshis p <property name="text"> <string>Confirmation time target:</string> </property> - <property name="margin"> - <number>2</number> - </property> </widget> </item> - <item> - <spacer name="verticalSpacer_3"> - <property name="orientation"> - <enum>Qt::Vertical</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>1</width> - <height>1</height> - </size> - </property> - </spacer> - </item> </layout> </item> <item> diff --git a/src/qt/guiconstants.h b/src/qt/guiconstants.h index 04b03a5435..4d6006c582 100644 --- a/src/qt/guiconstants.h +++ b/src/qt/guiconstants.h @@ -50,5 +50,6 @@ static const int MAX_URI_LENGTH = 255; #define QAPP_ORG_DOMAIN "bitcoin.org" #define QAPP_APP_NAME_DEFAULT "Bitcoin-Qt" #define QAPP_APP_NAME_TESTNET "Bitcoin-Qt-testnet" +#define QAPP_APP_NAME_REGTEST "Bitcoin-Qt-regtest" #endif // BITCOIN_QT_GUICONSTANTS_H diff --git a/src/qt/guiutil.cpp b/src/qt/guiutil.cpp index b894fc8166..71e987c8f4 100644 --- a/src/qt/guiutil.cpp +++ b/src/qt/guiutil.cpp @@ -18,7 +18,7 @@ #include <protocol.h> #include <script/script.h> #include <script/standard.h> -#include <util.h> +#include <util/system.h> #ifdef WIN32 #ifdef _WIN32_WINNT @@ -47,17 +47,23 @@ #include <QDoubleValidator> #include <QFileDialog> #include <QFont> +#include <QFontDatabase> +#include <QFontMetrics> #include <QKeyEvent> #include <QLineEdit> +#include <QMouseEvent> +#include <QProgressDialog> #include <QSettings> #include <QTextDocument> // for Qt::mightBeRichText #include <QThread> #include <QUrlQuery> -#include <QMouseEvent> +#if defined(Q_OS_MAC) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" -#if QT_VERSION >= 0x50200 -#include <QFontDatabase> +#include <objc/objc-runtime.h> +#include <CoreServices/CoreServices.h> #endif namespace GUIUtil { @@ -74,13 +80,7 @@ QString dateTimeStr(qint64 nTime) QFont fixedPitchFont() { -#if QT_VERSION >= 0x50200 return QFontDatabase::systemFont(QFontDatabase::FixedFont); -#else - QFont font("Monospace"); - font.setStyleHint(QFont::Monospace); - return font; -#endif } // Just some dummy data to generate a convincing random-looking (but consistent) address @@ -341,7 +341,7 @@ bool checkPoint(const QPoint &p, const QWidget *w) { QWidget *atW = QApplication::widgetAt(w->mapToGlobal(p)); if (!atW) return false; - return atW->topLevelWidget() == w; + return atW->window() == w; } bool isObscured(QWidget *w) @@ -353,6 +353,27 @@ bool isObscured(QWidget *w) && checkPoint(QPoint(w->width() / 2, w->height() / 2), w)); } +void bringToFront(QWidget* w) +{ +#ifdef Q_OS_MAC + // Force application activation on macOS. With Qt 5.4 this is required when + // an action in the dock menu is triggered. + id app = objc_msgSend((id) objc_getClass("NSApplication"), sel_registerName("sharedApplication")); + objc_msgSend(app, sel_registerName("activateIgnoringOtherApps:"), YES); +#endif + + if (w) { + // activateWindow() (sometimes) helps with keyboard focus on Windows + if (w->isMinimized()) { + w->showNormal(); + } else { + w->show(); + } + w->activateWindow(); + w->raise(); + } +} + void openDebugLogfile() { fs::path pathDebug = GetDataDir() / "debug.log"; @@ -367,7 +388,7 @@ bool openBitcoinConf() fs::path pathConfig = GetConfigFile(gArgs.GetArg("-conf", BITCOIN_CONF_FILENAME)); /* Create the file */ - fs::ofstream configFile(pathConfig, std::ios_base::app); + fsbridge::ofstream configFile(pathConfig, std::ios_base::app); if (!configFile.good()) return false; @@ -611,7 +632,7 @@ fs::path static GetAutostartFilePath() bool GetStartOnSystemStartup() { - fs::ifstream optionFile(GetAutostartFilePath()); + fsbridge::ifstream optionFile(GetAutostartFilePath()); if (!optionFile.good()) return false; // Scan through file for "Hidden=true": @@ -642,7 +663,7 @@ bool SetStartOnSystemStartup(bool fAutoStart) fs::create_directories(GetAutostartDir()); - fs::ofstream optionFile(GetAutostartFilePath(), std::ios_base::out|std::ios_base::trunc); + fsbridge::ofstream optionFile(GetAutostartFilePath(), std::ios_base::out | std::ios_base::trunc); if (!optionFile.good()) return false; std::string chain = gArgs.GetChainName(); @@ -662,18 +683,11 @@ bool SetStartOnSystemStartup(bool fAutoStart) } -#elif defined(Q_OS_MAC) -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#elif defined(Q_OS_MAC) && defined(MAC_OS_X_VERSION_MIN_REQUIRED) && MAC_OS_X_VERSION_MIN_REQUIRED <= 101100 // based on: https://github.com/Mozketo/LaunchAtLoginController/blob/master/LaunchAtLoginController.m -#include <CoreFoundation/CoreFoundation.h> -#include <CoreServices/CoreServices.h> - -LSSharedFileListItemRef findStartupItemInList(LSSharedFileListRef list, CFURLRef findUrl); -LSSharedFileListItemRef findStartupItemInList(LSSharedFileListRef list, CFURLRef findUrl) +LSSharedFileListItemRef findStartupItemInList(CFArrayRef listSnapshot, LSSharedFileListRef list, CFURLRef findUrl) { - CFArrayRef listSnapshot = LSSharedFileListCopySnapshot(list, nullptr); if (listSnapshot == nullptr) { return nullptr; } @@ -698,15 +712,12 @@ LSSharedFileListItemRef findStartupItemInList(LSSharedFileListRef list, CFURLRef if(currentItemURL) { if (CFEqual(currentItemURL, findUrl)) { // found - CFRelease(listSnapshot); CFRelease(currentItemURL); return item; } CFRelease(currentItemURL); } } - - CFRelease(listSnapshot); return nullptr; } @@ -718,10 +729,12 @@ bool GetStartOnSystemStartup() } LSSharedFileListRef loginItems = LSSharedFileListCreate(nullptr, kLSSharedFileListSessionLoginItems, nullptr); - LSSharedFileListItemRef foundItem = findStartupItemInList(loginItems, bitcoinAppUrl); - + CFArrayRef listSnapshot = LSSharedFileListCopySnapshot(loginItems, nullptr); + bool res = (findStartupItemInList(listSnapshot, loginItems, bitcoinAppUrl) != nullptr); CFRelease(bitcoinAppUrl); - return !!foundItem; // return boolified object + CFRelease(loginItems); + CFRelease(listSnapshot); + return res; } bool SetStartOnSystemStartup(bool fAutoStart) @@ -732,7 +745,8 @@ bool SetStartOnSystemStartup(bool fAutoStart) } LSSharedFileListRef loginItems = LSSharedFileListCreate(nullptr, kLSSharedFileListSessionLoginItems, nullptr); - LSSharedFileListItemRef foundItem = findStartupItemInList(loginItems, bitcoinAppUrl); + CFArrayRef listSnapshot = LSSharedFileListCopySnapshot(loginItems, nullptr); + LSSharedFileListItemRef foundItem = findStartupItemInList(listSnapshot, loginItems, bitcoinAppUrl); if(fAutoStart && !foundItem) { // add bitcoin app to startup item list @@ -744,6 +758,8 @@ bool SetStartOnSystemStartup(bool fAutoStart) } CFRelease(bitcoinAppUrl); + CFRelease(loginItems); + CFRelease(listSnapshot); return true; } #pragma GCC diagnostic pop @@ -919,4 +935,16 @@ bool ItemDelegate::eventFilter(QObject *object, QEvent *event) return QItemDelegate::eventFilter(object, event); } +void PolishProgressDialog(QProgressDialog* dialog) +{ +#ifdef Q_OS_MAC + // Workaround for macOS-only Qt bug; see: QTBUG-65750, QTBUG-70357. + const int margin = dialog->fontMetrics().width("X"); + dialog->resize(dialog->width() + 2 * margin, dialog->height()); + dialog->show(); +#else + Q_UNUSED(dialog); +#endif +} + } // namespace GUIUtil diff --git a/src/qt/guiutil.h b/src/qt/guiutil.h index 011827e134..cbec73a882 100644 --- a/src/qt/guiutil.h +++ b/src/qt/guiutil.h @@ -31,6 +31,7 @@ class QAbstractItemView; class QDateTime; class QFont; class QLineEdit; +class QProgressDialog; class QUrl; class QWidget; QT_END_NAMESPACE @@ -115,6 +116,9 @@ namespace GUIUtil // Determine whether a widget is hidden behind other windows bool isObscured(QWidget *w); + // Activate, show and raise the widget + void bringToFront(QWidget* w); + // Open debug.log void openDebugLogfile(); @@ -130,7 +134,7 @@ namespace GUIUtil Q_OBJECT public: - explicit ToolTipToRichTextFilter(int size_threshold, QObject *parent = 0); + explicit ToolTipToRichTextFilter(int size_threshold, QObject *parent = nullptr); protected: bool eventFilter(QObject *obj, QEvent *evt); @@ -245,6 +249,9 @@ namespace GUIUtil private: bool eventFilter(QObject *object, QEvent *event); }; + + // Fix known bugs in QProgressDialog class. + void PolishProgressDialog(QProgressDialog* dialog); } // namespace GUIUtil #endif // BITCOIN_QT_GUIUTIL_H diff --git a/src/qt/intro.cpp b/src/qt/intro.cpp index b19cc17a4d..69972fce3b 100644 --- a/src/qt/intro.cpp +++ b/src/qt/intro.cpp @@ -13,7 +13,7 @@ #include <qt/guiutil.h> #include <interfaces/node.h> -#include <util.h> +#include <util/system.h> #include <QFileDialog> #include <QSettings> @@ -22,10 +22,6 @@ #include <cmath> static const uint64_t GB_BYTES = 1000000000LL; -/* Minimum free space (in GB) needed for data directory */ -constexpr uint64_t BLOCK_CHAIN_SIZE = 220; -/* Minimum free space (in GB) needed for data directory when pruned; Does not include prune target */ -static const uint64_t CHAIN_STATE_SIZE = 3; /* Total required space (in GB) depending on user choice (prune, not prune) */ static uint64_t requiredSpace; @@ -114,11 +110,13 @@ void FreespaceChecker::check() } -Intro::Intro(QWidget *parent) : +Intro::Intro(QWidget *parent, uint64_t blockchain_size, uint64_t chain_state_size) : QDialog(parent), ui(new Ui::Intro), - thread(0), - signalled(false) + thread(nullptr), + signalled(false), + m_blockchain_size(blockchain_size), + m_chain_state_size(chain_state_size) { ui->setupUi(this); ui->welcomeLabel->setText(ui->welcomeLabel->text().arg(tr(PACKAGE_NAME))); @@ -126,14 +124,14 @@ Intro::Intro(QWidget *parent) : ui->lblExplanation1->setText(ui->lblExplanation1->text() .arg(tr(PACKAGE_NAME)) - .arg(BLOCK_CHAIN_SIZE) + .arg(m_blockchain_size) .arg(2009) .arg(tr("Bitcoin")) ); ui->lblExplanation2->setText(ui->lblExplanation2->text().arg(tr(PACKAGE_NAME))); uint64_t pruneTarget = std::max<int64_t>(0, gArgs.GetArg("-prune", 0)); - requiredSpace = BLOCK_CHAIN_SIZE; + requiredSpace = m_blockchain_size; QString storageRequiresMsg = tr("At least %1 GB of data will be stored in this directory, and it will grow over time."); if (pruneTarget) { uint64_t prunedGBs = std::ceil(pruneTarget * 1024 * 1024.0 / GB_BYTES); @@ -145,7 +143,7 @@ Intro::Intro(QWidget *parent) : } else { ui->lblExplanation3->setVisible(false); } - requiredSpace += CHAIN_STATE_SIZE; + requiredSpace += m_chain_state_size; ui->sizeWarningLabel->setText( tr("%1 will download and store a copy of the Bitcoin block chain.").arg(tr(PACKAGE_NAME)) + " " + storageRequiresMsg.arg(requiredSpace) + " " + @@ -158,7 +156,7 @@ Intro::~Intro() { delete ui; /* Ensure thread is finished before it is deleted */ - Q_EMIT stopThread(); + thread->quit(); thread->wait(); } @@ -201,8 +199,15 @@ bool Intro::pickDataDirectory(interfaces::Node& node) if(!fs::exists(GUIUtil::qstringToBoostPath(dataDir)) || gArgs.GetBoolArg("-choosedatadir", DEFAULT_CHOOSE_DATADIR) || settings.value("fReset", false).toBool() || gArgs.GetBoolArg("-resetguisettings", false)) { + /* Use selectParams here to guarantee Params() can be used by node interface */ + try { + node.selectParams(gArgs.GetChainName()); + } catch (const std::exception&) { + return false; + } + /* If current default data directory does not exist, let the user choose one */ - Intro intro; + Intro intro(0, node.getAssumedBlockchainSize(), node.getAssumedChainStateSize()); intro.setDataDirectory(dataDir); intro.setWindowIcon(QIcon(":icons/bitcoin")); @@ -221,7 +226,7 @@ bool Intro::pickDataDirectory(interfaces::Node& node) } break; } catch (const fs::filesystem_error&) { - QMessageBox::critical(0, tr(PACKAGE_NAME), + QMessageBox::critical(nullptr, tr(PACKAGE_NAME), tr("Error: Specified data directory \"%1\" cannot be created.").arg(dataDir)); /* fall through, back to choosing screen */ } @@ -281,7 +286,7 @@ void Intro::on_dataDirectory_textChanged(const QString &dataDirStr) void Intro::on_ellipsisButton_clicked() { - QString dir = QDir::toNativeSeparators(QFileDialog::getExistingDirectory(0, "Choose data directory", ui->dataDirectory->text())); + QString dir = QDir::toNativeSeparators(QFileDialog::getExistingDirectory(nullptr, "Choose data directory", ui->dataDirectory->text())); if(!dir.isEmpty()) ui->dataDirectory->setText(dir); } @@ -306,8 +311,7 @@ void Intro::startThread() connect(executor, &FreespaceChecker::reply, this, &Intro::setStatus); connect(this, &Intro::requestCheck, executor, &FreespaceChecker::check); /* make sure executor object is deleted in its own thread */ - connect(this, &Intro::stopThread, executor, &QObject::deleteLater); - connect(this, &Intro::stopThread, thread, &QThread::quit); + connect(thread, &QThread::finished, executor, &QObject::deleteLater); thread->start(); } diff --git a/src/qt/intro.h b/src/qt/intro.h index 2b3da963e2..b537c94f7d 100644 --- a/src/qt/intro.h +++ b/src/qt/intro.h @@ -30,7 +30,8 @@ class Intro : public QDialog Q_OBJECT public: - explicit Intro(QWidget *parent = 0); + explicit Intro(QWidget *parent = nullptr, + uint64_t blockchain_size = 0, uint64_t chain_state_size = 0); ~Intro(); QString getDataDirectory(); @@ -54,7 +55,6 @@ public: Q_SIGNALS: void requestCheck(); - void stopThread(); public Q_SLOTS: void setStatus(int status, const QString &message, quint64 bytesAvailable); @@ -71,6 +71,8 @@ private: QMutex mutex; bool signalled; QString pathToCheck; + uint64_t m_blockchain_size; + uint64_t m_chain_state_size; void startThread(); void checkPath(const QString &dataDir); diff --git a/src/qt/macdockiconhandler.h b/src/qt/macdockiconhandler.h index 1c28593d4a..ff867e21a7 100644 --- a/src/qt/macdockiconhandler.h +++ b/src/qt/macdockiconhandler.h @@ -1,44 +1,27 @@ -// Copyright (c) 2011-2015 The Bitcoin Core developers +// Copyright (c) 2011-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_QT_MACDOCKICONHANDLER_H #define BITCOIN_QT_MACDOCKICONHANDLER_H -#include <QMainWindow> #include <QObject> -QT_BEGIN_NAMESPACE -class QIcon; -class QMenu; -class QWidget; -QT_END_NAMESPACE - -/** Macintosh-specific dock icon handler. +/** macOS-specific Dock icon handler. */ class MacDockIconHandler : public QObject { Q_OBJECT public: - ~MacDockIconHandler(); - - QMenu *dockMenu(); - void setIcon(const QIcon &icon); - void setMainWindow(QMainWindow *window); static MacDockIconHandler *instance(); static void cleanup(); - void handleDockIconClickEvent(); Q_SIGNALS: void dockIconClicked(); private: MacDockIconHandler(); - - QWidget *m_dummyWidget; - QMenu *m_dockMenu; - QMainWindow *mainWindow; }; #endif // BITCOIN_QT_MACDOCKICONHANDLER_H diff --git a/src/qt/macdockiconhandler.mm b/src/qt/macdockiconhandler.mm index b9ad191da7..102adce6c5 100644 --- a/src/qt/macdockiconhandler.mm +++ b/src/qt/macdockiconhandler.mm @@ -1,107 +1,36 @@ -// Copyright (c) 2011-2013 The Bitcoin Core developers +// Copyright (c) 2011-2018 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include "macdockiconhandler.h" -#include <QImageWriter> -#include <QMenu> -#include <QBuffer> -#include <QWidget> - #undef slots -#include <Cocoa/Cocoa.h> #include <objc/objc.h> #include <objc/message.h> static MacDockIconHandler *s_instance = nullptr; -bool dockClickHandler(id self,SEL _cmd,...) { +bool dockClickHandler(id self, SEL _cmd, ...) { Q_UNUSED(self) Q_UNUSED(_cmd) - s_instance->handleDockIconClickEvent(); + Q_EMIT s_instance->dockIconClicked(); - // Return NO (false) to suppress the default OS X actions + // Return NO (false) to suppress the default macOS actions return false; } void setupDockClickHandler() { - Class cls = objc_getClass("NSApplication"); - id appInst = objc_msgSend((id)cls, sel_registerName("sharedApplication")); - - if (appInst != nullptr) { - id delegate = objc_msgSend(appInst, sel_registerName("delegate")); - Class delClass = (Class)objc_msgSend(delegate, sel_registerName("class")); - SEL shouldHandle = sel_registerName("applicationShouldHandleReopen:hasVisibleWindows:"); - if (class_getInstanceMethod(delClass, shouldHandle)) - class_replaceMethod(delClass, shouldHandle, (IMP)dockClickHandler, "B@:"); - else - class_addMethod(delClass, shouldHandle, (IMP)dockClickHandler,"B@:"); - } + id app = objc_msgSend((id)objc_getClass("NSApplication"), sel_registerName("sharedApplication")); + id delegate = objc_msgSend(app, sel_registerName("delegate")); + Class delClass = (Class)objc_msgSend(delegate, sel_registerName("class")); + SEL shouldHandle = sel_registerName("applicationShouldHandleReopen:hasVisibleWindows:"); + class_replaceMethod(delClass, shouldHandle, (IMP)dockClickHandler, "B@:"); } - MacDockIconHandler::MacDockIconHandler() : QObject() { - NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; - setupDockClickHandler(); - this->m_dummyWidget = new QWidget(); - this->m_dockMenu = new QMenu(this->m_dummyWidget); - this->setMainWindow(nullptr); -#if QT_VERSION >= 0x050200 - this->m_dockMenu->setAsDockMenu(); -#endif - [pool release]; -} - -void MacDockIconHandler::setMainWindow(QMainWindow *window) { - this->mainWindow = window; -} - -MacDockIconHandler::~MacDockIconHandler() -{ - delete this->m_dummyWidget; - this->setMainWindow(nullptr); -} - -QMenu *MacDockIconHandler::dockMenu() -{ - return this->m_dockMenu; -} - -void MacDockIconHandler::setIcon(const QIcon &icon) -{ - NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; - NSImage *image = nil; - if (icon.isNull()) - image = [[NSImage imageNamed:@"NSApplicationIcon"] retain]; - else { - // generate NSImage from QIcon and use this as dock icon. - QSize size = icon.actualSize(QSize(128, 128)); - QPixmap pixmap = icon.pixmap(size); - - // Write image into a R/W buffer from raw pixmap, then save the image. - QBuffer notificationBuffer; - if (!pixmap.isNull() && notificationBuffer.open(QIODevice::ReadWrite)) { - QImageWriter writer(¬ificationBuffer, "PNG"); - if (writer.write(pixmap.toImage())) { - NSData* macImgData = [NSData dataWithBytes:notificationBuffer.buffer().data() - length:notificationBuffer.buffer().size()]; - image = [[NSImage alloc] initWithData:macImgData]; - } - } - - if(!image) { - // if testnet image could not be created, load std. app icon - image = [[NSImage imageNamed:@"NSApplicationIcon"] retain]; - } - } - - [NSApp setApplicationIconImage:image]; - [image release]; - [pool release]; } MacDockIconHandler *MacDockIconHandler::instance() @@ -115,14 +44,3 @@ void MacDockIconHandler::cleanup() { delete s_instance; } - -void MacDockIconHandler::handleDockIconClickEvent() -{ - if (this->mainWindow) - { - this->mainWindow->activateWindow(); - this->mainWindow->show(); - } - - Q_EMIT this->dockIconClicked(); -} diff --git a/src/qt/macnotificationhandler.mm b/src/qt/macnotificationhandler.mm index 0e04d50baa..a07079eece 100644 --- a/src/qt/macnotificationhandler.mm +++ b/src/qt/macnotificationhandler.mm @@ -24,25 +24,10 @@ void MacNotificationHandler::showNotification(const QString &title, const QStrin { // check if users OS has support for NSUserNotification if(this->hasUserNotificationCenterSupport()) { - // okay, seems like 10.8+ - QByteArray utf8 = title.toUtf8(); - char* cString = (char *)utf8.constData(); - NSString *titleMac = [[NSString alloc] initWithUTF8String:cString]; - - utf8 = text.toUtf8(); - cString = (char *)utf8.constData(); - NSString *textMac = [[NSString alloc] initWithUTF8String:cString]; - - // do everything weak linked (because we will keep <10.8 compatibility) - id userNotification = [[NSClassFromString(@"NSUserNotification") alloc] init]; - [userNotification performSelector:@selector(setTitle:) withObject:titleMac]; - [userNotification performSelector:@selector(setInformativeText:) withObject:textMac]; - - id notificationCenterInstance = [NSClassFromString(@"NSUserNotificationCenter") performSelector:@selector(defaultUserNotificationCenter)]; - [notificationCenterInstance performSelector:@selector(deliverNotification:) withObject:userNotification]; - - [titleMac release]; - [textMac release]; + NSUserNotification* userNotification = [[NSUserNotification alloc] init]; + userNotification.title = title.toNSString(); + userNotification.informativeText = text.toNSString(); + [[NSUserNotificationCenter defaultUserNotificationCenter] deliverNotification: userNotification]; [userNotification release]; } } diff --git a/src/qt/macos_appnap.h b/src/qt/macos_appnap.h new file mode 100644 index 0000000000..8c2cd840b0 --- /dev/null +++ b/src/qt/macos_appnap.h @@ -0,0 +1,24 @@ +// Copyright (c) 2011-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_QT_MACOS_APPNAP_H +#define BITCOIN_QT_MACOS_APPNAP_H + +#include <memory> + +class CAppNapInhibitor final +{ +public: + explicit CAppNapInhibitor(); + ~CAppNapInhibitor(); + + void disableAppNap(); + void enableAppNap(); + +private: + class CAppNapImpl; + std::unique_ptr<CAppNapImpl> impl; +}; + +#endif // BITCOIN_QT_MACOS_APPNAP_H diff --git a/src/qt/macos_appnap.mm b/src/qt/macos_appnap.mm new file mode 100644 index 0000000000..22a88782ab --- /dev/null +++ b/src/qt/macos_appnap.mm @@ -0,0 +1,71 @@ +// Copyright (c) 2011-2018 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "macos_appnap.h" + +#include <AvailabilityMacros.h> +#include <Foundation/NSProcessInfo.h> +#include <Foundation/Foundation.h> + +class CAppNapInhibitor::CAppNapImpl +{ +public: + ~CAppNapImpl() + { + if(activityId) + enableAppNap(); + } + + void disableAppNap() + { + if (!activityId) + { + @autoreleasepool { + const NSActivityOptions activityOptions = + NSActivityUserInitiatedAllowingIdleSystemSleep & + ~(NSActivitySuddenTerminationDisabled | + NSActivityAutomaticTerminationDisabled); + + id processInfo = [NSProcessInfo processInfo]; + if ([processInfo respondsToSelector:@selector(beginActivityWithOptions:reason:)]) + { + activityId = [processInfo beginActivityWithOptions: activityOptions reason:@"Temporarily disable App Nap for bitcoin-qt."]; + [activityId retain]; + } + } + } + } + + void enableAppNap() + { + if(activityId) + { + @autoreleasepool { + id processInfo = [NSProcessInfo processInfo]; + if ([processInfo respondsToSelector:@selector(endActivity:)]) + [processInfo endActivity:activityId]; + + [activityId release]; + activityId = nil; + } + } + } + +private: + NSObject* activityId; +}; + +CAppNapInhibitor::CAppNapInhibitor() : impl(new CAppNapImpl()) {} + +CAppNapInhibitor::~CAppNapInhibitor() = default; + +void CAppNapInhibitor::disableAppNap() +{ + impl->disableAppNap(); +} + +void CAppNapInhibitor::enableAppNap() +{ + impl->enableAppNap(); +} diff --git a/src/qt/main.cpp b/src/qt/main.cpp new file mode 100644 index 0000000000..6a3c2249d1 --- /dev/null +++ b/src/qt/main.cpp @@ -0,0 +1,17 @@ +// Copyright (c) 2018 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include <qt/bitcoin.h> + +#include <QCoreApplication> + +#include <functional> +#include <string> + +/** Translate string to current locale using Qt. */ +extern const std::function<std::string(const char*)> G_TRANSLATION_FUN = [](const char* psz) { + return QCoreApplication::translate("bitcoin-core", psz).toStdString(); +}; + +int main(int argc, char* argv[]) { return GuiMain(argc, argv); } diff --git a/src/qt/networkstyle.cpp b/src/qt/networkstyle.cpp index b134a139b0..f0c860e669 100644 --- a/src/qt/networkstyle.cpp +++ b/src/qt/networkstyle.cpp @@ -17,7 +17,7 @@ static const struct { } network_styles[] = { {"main", QAPP_APP_NAME_DEFAULT, 0, 0, ""}, {"test", QAPP_APP_NAME_TESTNET, 70, 30, QT_TRANSLATE_NOOP("SplashScreen", "[testnet]")}, - {"regtest", QAPP_APP_NAME_TESTNET, 160, 30, "[regtest]"} + {"regtest", QAPP_APP_NAME_REGTEST, 160, 30, "[regtest]"} }; static const unsigned network_styles_count = sizeof(network_styles)/sizeof(*network_styles); @@ -88,5 +88,5 @@ const NetworkStyle *NetworkStyle::instantiate(const QString &networkId) network_styles[x].titleAddText); } } - return 0; + return nullptr; } diff --git a/src/qt/notificator.cpp b/src/qt/notificator.cpp index 94505043dc..4b91c19761 100644 --- a/src/qt/notificator.cpp +++ b/src/qt/notificator.cpp @@ -39,7 +39,7 @@ Notificator::Notificator(const QString &_programName, QSystemTrayIcon *_trayIcon mode(None), trayIcon(_trayIcon) #ifdef USE_DBUS - ,interface(0) + ,interface(nullptr) #endif { if(_trayIcon && _trayIcon->supportsMessages()) @@ -154,14 +154,14 @@ QVariant FreedesktopImage::toVariant(const QImage &img) void Notificator::notifyDBus(Class cls, const QString &title, const QString &text, const QIcon &icon, int millisTimeout) { - Q_UNUSED(cls); - // Arguments for DBus call: + // https://developer.gnome.org/notification-spec/ + // Arguments for DBus "Notify" call: QList<QVariant> args; // Program Name: args.append(programName); - // Unique ID of this notification type: + // Replaces ID; A value of 0 means that this notification won't replace any existing notifications: args.append(0U); // Application Icon, empty string @@ -209,9 +209,8 @@ void Notificator::notifyDBus(Class cls, const QString &title, const QString &tex } #endif -void Notificator::notifySystray(Class cls, const QString &title, const QString &text, const QIcon &icon, int millisTimeout) +void Notificator::notifySystray(Class cls, const QString &title, const QString &text, int millisTimeout) { - Q_UNUSED(icon); QSystemTrayIcon::MessageIcon sicon = QSystemTrayIcon::NoIcon; switch(cls) // Set icon based on class { @@ -222,13 +221,12 @@ void Notificator::notifySystray(Class cls, const QString &title, const QString & trayIcon->showMessage(title, text, sicon, millisTimeout); } -// Based on Qt's tray icon implementation #ifdef Q_OS_MAC -void Notificator::notifyMacUserNotificationCenter(Class cls, const QString &title, const QString &text, const QIcon &icon) { +void Notificator::notifyMacUserNotificationCenter(const QString &title, const QString &text) +{ // icon is not supported by the user notification center yet. OSX will use the app icon. MacNotificationHandler::instance()->showNotification(title, text); } - #endif void Notificator::notify(Class cls, const QString &title, const QString &text, const QIcon &icon, int millisTimeout) @@ -241,11 +239,11 @@ void Notificator::notify(Class cls, const QString &title, const QString &text, c break; #endif case QSystemTray: - notifySystray(cls, title, text, icon, millisTimeout); + notifySystray(cls, title, text, millisTimeout); break; #ifdef Q_OS_MAC case UserNotificationCenter: - notifyMacUserNotificationCenter(cls, title, text, icon); + notifyMacUserNotificationCenter(title, text); break; #endif default: diff --git a/src/qt/notificator.h b/src/qt/notificator.h index 10b7b6c50a..7d80b43e43 100644 --- a/src/qt/notificator.h +++ b/src/qt/notificator.h @@ -57,7 +57,7 @@ private: enum Mode { None, /**< Ignore informational notifications, and show a modal pop-up dialog for Critical notifications. */ Freedesktop, /**< Use DBus org.freedesktop.Notifications */ - QSystemTray, /**< Use QSystemTray::showMessage */ + QSystemTray, /**< Use QSystemTrayIcon::showMessage() */ UserNotificationCenter /**< Use the 10.8+ User Notification Center (Mac only) */ }; QString programName; @@ -68,9 +68,9 @@ private: void notifyDBus(Class cls, const QString &title, const QString &text, const QIcon &icon, int millisTimeout); #endif - void notifySystray(Class cls, const QString &title, const QString &text, const QIcon &icon, int millisTimeout); + void notifySystray(Class cls, const QString &title, const QString &text, int millisTimeout); #ifdef Q_OS_MAC - void notifyMacUserNotificationCenter(Class cls, const QString &title, const QString &text, const QIcon &icon); + void notifyMacUserNotificationCenter(const QString &title, const QString &text); #endif }; diff --git a/src/qt/optionsdialog.cpp b/src/qt/optionsdialog.cpp index b51322394f..849bc2e477 100644 --- a/src/qt/optionsdialog.cpp +++ b/src/qt/optionsdialog.cpp @@ -23,13 +23,14 @@ #include <QIntValidator> #include <QLocale> #include <QMessageBox> +#include <QSystemTrayIcon> #include <QTimer> OptionsDialog::OptionsDialog(QWidget *parent, bool enableWallet) : QDialog(parent), ui(new Ui::OptionsDialog), - model(0), - mapper(0) + model(nullptr), + mapper(nullptr) { ui->setupUi(this); @@ -73,6 +74,12 @@ OptionsDialog::OptionsDialog(QWidget *parent, bool enableWallet) : #ifdef Q_OS_MAC /* remove Window tab on Mac */ ui->tabWidget->removeTab(ui->tabWidget->indexOf(ui->tabWindow)); +#if defined(MAC_OS_X_VERSION_MIN_REQUIRED) && MAC_OS_X_VERSION_MIN_REQUIRED > 101100 + /* hide launch at startup option if compiled against macOS > 10.11 (removed API) */ + ui->bitcoinAtStartup->setVisible(false); + ui->verticalLayout_Main->removeWidget(ui->bitcoinAtStartup); + ui->verticalLayout_Main->removeItem(ui->horizontalSpacer_0_Main); +#endif #endif /* remove Wallet tab in case of -disablewallet */ @@ -126,6 +133,13 @@ OptionsDialog::OptionsDialog(QWidget *parent, bool enableWallet) : connect(ui->proxyIpTor, &QValidatedLineEdit::validationDidChange, this, &OptionsDialog::updateProxyValidationState); connect(ui->proxyPort, &QLineEdit::textChanged, this, &OptionsDialog::updateProxyValidationState); connect(ui->proxyPortTor, &QLineEdit::textChanged, this, &OptionsDialog::updateProxyValidationState); + + if (!QSystemTrayIcon::isSystemTrayAvailable()) { + ui->hideTrayIcon->setChecked(true); + ui->hideTrayIcon->setEnabled(false); + ui->minimizeToTray->setChecked(false); + ui->minimizeToTray->setEnabled(false); + } } OptionsDialog::~OptionsDialog() @@ -211,8 +225,10 @@ void OptionsDialog::setMapper() /* Window */ #ifndef Q_OS_MAC - mapper->addMapping(ui->hideTrayIcon, OptionsModel::HideTrayIcon); - mapper->addMapping(ui->minimizeToTray, OptionsModel::MinimizeToTray); + if (QSystemTrayIcon::isSystemTrayAvailable()) { + mapper->addMapping(ui->hideTrayIcon, OptionsModel::HideTrayIcon); + mapper->addMapping(ui->minimizeToTray, OptionsModel::MinimizeToTray); + } mapper->addMapping(ui->minimizeOnClose, OptionsModel::MinimizeOnClose); #endif diff --git a/src/qt/optionsmodel.h b/src/qt/optionsmodel.h index 2421734527..1af3a72b92 100644 --- a/src/qt/optionsmodel.h +++ b/src/qt/optionsmodel.h @@ -31,7 +31,7 @@ class OptionsModel : public QAbstractListModel Q_OBJECT public: - explicit OptionsModel(interfaces::Node& node, QObject *parent = 0, bool resetSettings = false); + explicit OptionsModel(interfaces::Node& node, QObject *parent = nullptr, bool resetSettings = false); enum OptionID { StartAtStartup, // bool diff --git a/src/qt/overviewpage.cpp b/src/qt/overviewpage.cpp index 1db9609979..d8e48f350a 100644 --- a/src/qt/overviewpage.cpp +++ b/src/qt/overviewpage.cpp @@ -113,8 +113,8 @@ public: OverviewPage::OverviewPage(const PlatformStyle *platformStyle, QWidget *parent) : QWidget(parent), ui(new Ui::OverviewPage), - clientModel(0), - walletModel(0), + clientModel(nullptr), + walletModel(nullptr), txdelegate(new TxViewDelegate(platformStyle, this)) { ui->setupUi(this); @@ -161,15 +161,21 @@ void OverviewPage::setBalance(const interfaces::WalletBalances& balances) { int unit = walletModel->getOptionsModel()->getDisplayUnit(); m_balances = balances; - ui->labelBalance->setText(BitcoinUnits::formatWithUnit(unit, balances.balance, false, BitcoinUnits::separatorAlways)); - ui->labelUnconfirmed->setText(BitcoinUnits::formatWithUnit(unit, balances.unconfirmed_balance, false, BitcoinUnits::separatorAlways)); - ui->labelImmature->setText(BitcoinUnits::formatWithUnit(unit, balances.immature_balance, false, BitcoinUnits::separatorAlways)); - ui->labelTotal->setText(BitcoinUnits::formatWithUnit(unit, balances.balance + balances.unconfirmed_balance + balances.immature_balance, false, BitcoinUnits::separatorAlways)); - ui->labelWatchAvailable->setText(BitcoinUnits::formatWithUnit(unit, balances.watch_only_balance, false, BitcoinUnits::separatorAlways)); - ui->labelWatchPending->setText(BitcoinUnits::formatWithUnit(unit, balances.unconfirmed_watch_only_balance, false, BitcoinUnits::separatorAlways)); - ui->labelWatchImmature->setText(BitcoinUnits::formatWithUnit(unit, balances.immature_watch_only_balance, false, BitcoinUnits::separatorAlways)); - ui->labelWatchTotal->setText(BitcoinUnits::formatWithUnit(unit, balances.watch_only_balance + balances.unconfirmed_watch_only_balance + balances.immature_watch_only_balance, false, BitcoinUnits::separatorAlways)); - + if (walletModel->privateKeysDisabled()) { + ui->labelBalance->setText(BitcoinUnits::formatWithUnit(unit, balances.watch_only_balance, false, BitcoinUnits::separatorAlways)); + ui->labelUnconfirmed->setText(BitcoinUnits::formatWithUnit(unit, balances.unconfirmed_watch_only_balance, false, BitcoinUnits::separatorAlways)); + ui->labelImmature->setText(BitcoinUnits::formatWithUnit(unit, balances.immature_watch_only_balance, false, BitcoinUnits::separatorAlways)); + ui->labelTotal->setText(BitcoinUnits::formatWithUnit(unit, balances.watch_only_balance + balances.unconfirmed_watch_only_balance + balances.immature_watch_only_balance, false, BitcoinUnits::separatorAlways)); + } else { + ui->labelBalance->setText(BitcoinUnits::formatWithUnit(unit, balances.balance, false, BitcoinUnits::separatorAlways)); + ui->labelUnconfirmed->setText(BitcoinUnits::formatWithUnit(unit, balances.unconfirmed_balance, false, BitcoinUnits::separatorAlways)); + ui->labelImmature->setText(BitcoinUnits::formatWithUnit(unit, balances.immature_balance, false, BitcoinUnits::separatorAlways)); + ui->labelTotal->setText(BitcoinUnits::formatWithUnit(unit, balances.balance + balances.unconfirmed_balance + balances.immature_balance, false, BitcoinUnits::separatorAlways)); + ui->labelWatchAvailable->setText(BitcoinUnits::formatWithUnit(unit, balances.watch_only_balance, false, BitcoinUnits::separatorAlways)); + ui->labelWatchPending->setText(BitcoinUnits::formatWithUnit(unit, balances.unconfirmed_watch_only_balance, false, BitcoinUnits::separatorAlways)); + ui->labelWatchImmature->setText(BitcoinUnits::formatWithUnit(unit, balances.immature_watch_only_balance, false, BitcoinUnits::separatorAlways)); + ui->labelWatchTotal->setText(BitcoinUnits::formatWithUnit(unit, balances.watch_only_balance + balances.unconfirmed_watch_only_balance + balances.immature_watch_only_balance, false, BitcoinUnits::separatorAlways)); + } // only show immature (newly mined) balance if it's non-zero, so as not to complicate things // for the non-mining users bool showImmature = balances.immature_balance != 0; @@ -178,7 +184,7 @@ void OverviewPage::setBalance(const interfaces::WalletBalances& balances) // for symmetry reasons also show immature label when the watch-only one is shown ui->labelImmature->setVisible(showImmature || showWatchOnlyImmature); ui->labelImmatureText->setVisible(showImmature || showWatchOnlyImmature); - ui->labelWatchImmature->setVisible(showWatchOnlyImmature); // show watch-only immature balance + ui->labelWatchImmature->setVisible(!walletModel->privateKeysDisabled() && showWatchOnlyImmature); // show watch-only immature balance } // show/hide watch-only labels @@ -231,8 +237,10 @@ void OverviewPage::setWalletModel(WalletModel *model) connect(model->getOptionsModel(), &OptionsModel::displayUnitChanged, this, &OverviewPage::updateDisplayUnit); - updateWatchOnlyLabels(wallet.haveWatchOnly()); - connect(model, &WalletModel::notifyWatchonlyChanged, this, &OverviewPage::updateWatchOnlyLabels); + updateWatchOnlyLabels(wallet.haveWatchOnly() && !model->privateKeysDisabled()); + connect(model, &WalletModel::notifyWatchonlyChanged, [this](bool showWatchOnly) { + updateWatchOnlyLabels(showWatchOnly && !walletModel->privateKeysDisabled()); + }); } // update the display unit, to not use the default ("BTC") diff --git a/src/qt/overviewpage.h b/src/qt/overviewpage.h index 75100ae6f7..00ba7ad4ce 100644 --- a/src/qt/overviewpage.h +++ b/src/qt/overviewpage.h @@ -30,7 +30,7 @@ class OverviewPage : public QWidget Q_OBJECT public: - explicit OverviewPage(const PlatformStyle *platformStyle, QWidget *parent = 0); + explicit OverviewPage(const PlatformStyle *platformStyle, QWidget *parent = nullptr); ~OverviewPage(); void setClientModel(ClientModel *clientModel); diff --git a/src/qt/paymentrequestplus.cpp b/src/qt/paymentrequestplus.cpp index a989988c45..b962ab1ef2 100644 --- a/src/qt/paymentrequestplus.cpp +++ b/src/qt/paymentrequestplus.cpp @@ -9,7 +9,7 @@ #include <qt/paymentrequestplus.h> -#include <util.h> +#include <util/system.h> #include <stdexcept> diff --git a/src/qt/paymentserver.cpp b/src/qt/paymentserver.cpp index bcafc8f859..b1ec90ad36 100644 --- a/src/qt/paymentserver.cpp +++ b/src/qt/paymentserver.cpp @@ -2,6 +2,10 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. +#if defined(HAVE_CONFIG_H) +#include <config/bitcoin-config.h> +#endif + #include <qt/paymentserver.h> #include <qt/bitcoinunits.h> @@ -13,7 +17,7 @@ #include <policy/policy.h> #include <key_io.h> #include <ui_interface.h> -#include <util.h> +#include <util/system.h> #include <wallet/wallet.h> #include <cstdlib> @@ -45,6 +49,7 @@ const int BITCOIN_IPC_CONNECT_TIMEOUT = 1000; // milliseconds const QString BITCOIN_IPC_PREFIX("bitcoin:"); +#ifdef ENABLE_BIP70 // BIP70 payment protocol messages const char* BIP70_MESSAGE_PAYMENTACK = "PaymentACK"; const char* BIP70_MESSAGE_PAYMENTREQUEST = "PaymentRequest"; @@ -52,21 +57,7 @@ const char* BIP70_MESSAGE_PAYMENTREQUEST = "PaymentRequest"; const char* BIP71_MIMETYPE_PAYMENT = "application/bitcoin-payment"; const char* BIP71_MIMETYPE_PAYMENTACK = "application/bitcoin-paymentack"; const char* BIP71_MIMETYPE_PAYMENTREQUEST = "application/bitcoin-paymentrequest"; - -struct X509StoreDeleter { - void operator()(X509_STORE* b) { - X509_STORE_free(b); - } -}; - -struct X509Deleter { - void operator()(X509* b) { X509_free(b); } -}; - -namespace // Anon namespace -{ - std::unique_ptr<X509_STORE, X509StoreDeleter> certStore; -} +#endif // // Create a name that is unique for: @@ -93,94 +84,6 @@ static QString ipcServerName() static QList<QString> savedPaymentRequests; -static void ReportInvalidCertificate(const QSslCertificate& cert) -{ - qDebug() << QString("%1: Payment server found an invalid certificate: ").arg(__func__) << cert.serialNumber() << cert.subjectInfo(QSslCertificate::CommonName) << cert.subjectInfo(QSslCertificate::DistinguishedNameQualifier) << cert.subjectInfo(QSslCertificate::OrganizationalUnitName); -} - -// -// Load OpenSSL's list of root certificate authorities -// -void PaymentServer::LoadRootCAs(X509_STORE* _store) -{ - // Unit tests mostly use this, to pass in fake root CAs: - if (_store) - { - certStore.reset(_store); - return; - } - - // Normal execution, use either -rootcertificates or system certs: - certStore.reset(X509_STORE_new()); - - // Note: use "-system-" default here so that users can pass -rootcertificates="" - // and get 'I don't like X.509 certificates, don't trust anybody' behavior: - QString certFile = QString::fromStdString(gArgs.GetArg("-rootcertificates", "-system-")); - - // Empty store - if (certFile.isEmpty()) { - qDebug() << QString("PaymentServer::%1: Payment request authentication via X.509 certificates disabled.").arg(__func__); - return; - } - - QList<QSslCertificate> certList; - - if (certFile != "-system-") { - qDebug() << QString("PaymentServer::%1: Using \"%2\" as trusted root certificate.").arg(__func__).arg(certFile); - - certList = QSslCertificate::fromPath(certFile); - // Use those certificates when fetching payment requests, too: - QSslSocket::setDefaultCaCertificates(certList); - } else - certList = QSslSocket::systemCaCertificates(); - - int nRootCerts = 0; - const QDateTime currentTime = QDateTime::currentDateTime(); - - for (const QSslCertificate& cert : certList) { - // Don't log NULL certificates - if (cert.isNull()) - continue; - - // Not yet active/valid, or expired certificate - if (currentTime < cert.effectiveDate() || currentTime > cert.expiryDate()) { - ReportInvalidCertificate(cert); - continue; - } - - // Blacklisted certificate - if (cert.isBlacklisted()) { - ReportInvalidCertificate(cert); - continue; - } - QByteArray certData = cert.toDer(); - const unsigned char *data = (const unsigned char *)certData.data(); - - std::unique_ptr<X509, X509Deleter> x509(d2i_X509(0, &data, certData.size())); - if (x509 && X509_STORE_add_cert(certStore.get(), x509.get())) - { - // Note: X509_STORE increases the reference count to the X509 object, - // we still have to release our reference to it. - ++nRootCerts; - } - else - { - ReportInvalidCertificate(cert); - continue; - } - } - qWarning() << "PaymentServer::LoadRootCAs: Loaded " << nRootCerts << " root certificates"; - - // Project for another day: - // Fetch certificate revocation lists, and add them to certStore. - // Issues to consider: - // performance (start a thread to fetch in background?) - // privacy (fetch through tor/proxy so IP address isn't revealed) - // would it be easier to just use a compiled-in blacklist? - // or use Qt's blacklist? - // "certificate stapling" with server-side caching is more efficient -} - // // Sending to the server is done synchronously, at startup. // If the server isn't already running, startup continues, @@ -221,6 +124,7 @@ void PaymentServer::ipcParseCommandLine(interfaces::Node& node, int argc, char* } } } +#ifdef ENABLE_BIP70 else if (QFile::exists(arg)) // Filename { savedPaymentRequests.append(arg); @@ -244,6 +148,7 @@ void PaymentServer::ipcParseCommandLine(interfaces::Node& node, int argc, char* // GUI hasn't started yet so we can't pop up a message box. qWarning() << "PaymentServer::ipcSendCommandLine: Payment request file does not exist: " << arg; } +#endif } } @@ -289,13 +194,17 @@ bool PaymentServer::ipcSendCommandLine() PaymentServer::PaymentServer(QObject* parent, bool startLocalServer) : QObject(parent), saveURIs(true), - uriServer(0), - netManager(0), - optionsModel(0) + uriServer(nullptr), + optionsModel(nullptr) +#ifdef ENABLE_BIP70 + ,netManager(nullptr) +#endif { +#ifdef ENABLE_BIP70 // Verify that the version of the library that we linked against is // compatible with the version of the headers we compiled against. GOOGLE_PROTOBUF_VERIFY_VERSION; +#endif // Install global event filter to catch QFileOpenEvents // on Mac: sent when you click bitcoin: links @@ -314,19 +223,23 @@ PaymentServer::PaymentServer(QObject* parent, bool startLocalServer) : if (!uriServer->listen(name)) { // constructor is called early in init, so don't use "Q_EMIT message()" here - QMessageBox::critical(0, tr("Payment request error"), + QMessageBox::critical(nullptr, tr("Payment request error"), tr("Cannot start bitcoin: click-to-pay handler")); } else { connect(uriServer, &QLocalServer::newConnection, this, &PaymentServer::handleURIConnection); +#ifdef ENABLE_BIP70 connect(this, &PaymentServer::receivedPaymentACK, this, &PaymentServer::handlePaymentACK); +#endif } } } PaymentServer::~PaymentServer() { +#ifdef ENABLE_BIP70 google::protobuf::ShutdownProtobufLibrary(); +#endif } // @@ -349,33 +262,11 @@ bool PaymentServer::eventFilter(QObject *object, QEvent *event) return QObject::eventFilter(object, event); } -void PaymentServer::initNetManager() -{ - if (!optionsModel) - return; - delete netManager; - - // netManager is used to fetch paymentrequests given in bitcoin: URIs - netManager = new QNetworkAccessManager(this); - - QNetworkProxy proxy; - - // Query active SOCKS5 proxy - if (optionsModel->getProxySettings(proxy)) { - netManager->setProxy(proxy); - - qDebug() << "PaymentServer::initNetManager: Using SOCKS5 proxy" << proxy.hostName() << ":" << proxy.port(); - } - else - qDebug() << "PaymentServer::initNetManager: No active proxy server found."; - - connect(netManager, &QNetworkAccessManager::finished, this, &PaymentServer::netRequestFinished); - connect(netManager, &QNetworkAccessManager::sslErrors, this, &PaymentServer::reportSslErrors); -} - void PaymentServer::uiReady() { +#ifdef ENABLE_BIP70 initNetManager(); +#endif saveURIs = false; for (const QString& s : savedPaymentRequests) @@ -403,6 +294,10 @@ void PaymentServer::handleURIOrFile(const QString& s) QUrlQuery uri((QUrl(s))); if (uri.hasQueryItem("r")) // payment request URI { +#ifdef ENABLE_BIP70 + Q_EMIT message(tr("URI handling"), + tr("You are using a BIP70 URL which will be unsupported in the future."), + CClientUIInterface::ICON_WARNING); QByteArray temp; temp.append(uri.queryItemValue("r")); QString decoded = QUrl::fromPercentEncoding(temp); @@ -420,7 +315,11 @@ void PaymentServer::handleURIOrFile(const QString& s) tr("Payment request fetch URL is invalid: %1").arg(fetchUrl.toString()), CClientUIInterface::ICON_WARNING); } - +#else + Q_EMIT message(tr("URI handling"), + tr("Cannot process payment request because BIP70 support was not compiled in."), + CClientUIInterface::ICON_WARNING); +#endif return; } else // normal URI @@ -444,6 +343,7 @@ void PaymentServer::handleURIOrFile(const QString& s) } } +#ifdef ENABLE_BIP70 if (QFile::exists(s)) // payment request file { PaymentRequestPlus request; @@ -459,6 +359,7 @@ void PaymentServer::handleURIOrFile(const QString& s) return; } +#endif } void PaymentServer::handleURIConnection() @@ -481,6 +382,140 @@ void PaymentServer::handleURIConnection() handleURIOrFile(msg); } +void PaymentServer::setOptionsModel(OptionsModel *_optionsModel) +{ + this->optionsModel = _optionsModel; +} + +#ifdef ENABLE_BIP70 +struct X509StoreDeleter { + void operator()(X509_STORE* b) { + X509_STORE_free(b); + } +}; + +struct X509Deleter { + void operator()(X509* b) { X509_free(b); } +}; + +namespace // Anon namespace +{ + std::unique_ptr<X509_STORE, X509StoreDeleter> certStore; +} + +static void ReportInvalidCertificate(const QSslCertificate& cert) +{ + qDebug() << QString("%1: Payment server found an invalid certificate: ").arg(__func__) << cert.serialNumber() << cert.subjectInfo(QSslCertificate::CommonName) << cert.subjectInfo(QSslCertificate::DistinguishedNameQualifier) << cert.subjectInfo(QSslCertificate::OrganizationalUnitName); +} + +// +// Load OpenSSL's list of root certificate authorities +// +void PaymentServer::LoadRootCAs(X509_STORE* _store) +{ + // Unit tests mostly use this, to pass in fake root CAs: + if (_store) + { + certStore.reset(_store); + return; + } + + // Normal execution, use either -rootcertificates or system certs: + certStore.reset(X509_STORE_new()); + + // Note: use "-system-" default here so that users can pass -rootcertificates="" + // and get 'I don't like X.509 certificates, don't trust anybody' behavior: + QString certFile = QString::fromStdString(gArgs.GetArg("-rootcertificates", "-system-")); + + // Empty store + if (certFile.isEmpty()) { + qDebug() << QString("PaymentServer::%1: Payment request authentication via X.509 certificates disabled.").arg(__func__); + return; + } + + QList<QSslCertificate> certList; + + if (certFile != "-system-") { + qDebug() << QString("PaymentServer::%1: Using \"%2\" as trusted root certificate.").arg(__func__).arg(certFile); + + certList = QSslCertificate::fromPath(certFile); + // Use those certificates when fetching payment requests, too: + QSslSocket::setDefaultCaCertificates(certList); + } else + certList = QSslSocket::systemCaCertificates(); + + int nRootCerts = 0; + const QDateTime currentTime = QDateTime::currentDateTime(); + + for (const QSslCertificate& cert : certList) { + // Don't log NULL certificates + if (cert.isNull()) + continue; + + // Not yet active/valid, or expired certificate + if (currentTime < cert.effectiveDate() || currentTime > cert.expiryDate()) { + ReportInvalidCertificate(cert); + continue; + } + + // Blacklisted certificate + if (cert.isBlacklisted()) { + ReportInvalidCertificate(cert); + continue; + } + + QByteArray certData = cert.toDer(); + const unsigned char *data = (const unsigned char *)certData.data(); + + std::unique_ptr<X509, X509Deleter> x509(d2i_X509(0, &data, certData.size())); + if (x509 && X509_STORE_add_cert(certStore.get(), x509.get())) + { + // Note: X509_STORE increases the reference count to the X509 object, + // we still have to release our reference to it. + ++nRootCerts; + } + else + { + ReportInvalidCertificate(cert); + continue; + } + } + qWarning() << "PaymentServer::LoadRootCAs: Loaded " << nRootCerts << " root certificates"; + + // Project for another day: + // Fetch certificate revocation lists, and add them to certStore. + // Issues to consider: + // performance (start a thread to fetch in background?) + // privacy (fetch through tor/proxy so IP address isn't revealed) + // would it be easier to just use a compiled-in blacklist? + // or use Qt's blacklist? + // "certificate stapling" with server-side caching is more efficient +} + +void PaymentServer::initNetManager() +{ + if (!optionsModel) + return; + delete netManager; + + // netManager is used to fetch paymentrequests given in bitcoin: URIs + netManager = new QNetworkAccessManager(this); + + QNetworkProxy proxy; + + // Query active SOCKS5 proxy + if (optionsModel->getProxySettings(proxy)) { + netManager->setProxy(proxy); + + qDebug() << "PaymentServer::initNetManager: Using SOCKS5 proxy" << proxy.hostName() << ":" << proxy.port(); + } + else + qDebug() << "PaymentServer::initNetManager: No active proxy server found."; + + connect(netManager, &QNetworkAccessManager::finished, this, &PaymentServer::netRequestFinished); + connect(netManager, &QNetworkAccessManager::sslErrors, this, &PaymentServer::reportSslErrors); +} + // // Warning: readPaymentRequestFromFile() is used in ipcSendCommandLine() // so don't use "Q_EMIT message()", but "QMessageBox::"! @@ -731,11 +766,6 @@ void PaymentServer::reportSslErrors(QNetworkReply* reply, const QList<QSslError> Q_EMIT message(tr("Network request error"), errString, CClientUIInterface::MSG_ERROR); } -void PaymentServer::setOptionsModel(OptionsModel *_optionsModel) -{ - this->optionsModel = _optionsModel; -} - void PaymentServer::handlePaymentACK(const QString& paymentACKMsg) { // currently we don't further process or store the paymentACK message @@ -794,3 +824,4 @@ X509_STORE* PaymentServer::getCertStore() { return certStore.get(); } +#endif diff --git a/src/qt/paymentserver.h b/src/qt/paymentserver.h index d335db9c85..30b5bc3b6d 100644 --- a/src/qt/paymentserver.h +++ b/src/qt/paymentserver.h @@ -32,7 +32,13 @@ // sends them to the server. // +#if defined(HAVE_CONFIG_H) +#include <config/bitcoin-config.h> +#endif + +#ifdef ENABLE_BIP70 #include <qt/paymentrequestplus.h> +#endif #include <qt/walletmodel.h> #include <QObject> @@ -73,6 +79,10 @@ public: explicit PaymentServer(QObject* parent, bool startLocalServer = true); ~PaymentServer(); + // OptionsModel is used for getting proxy settings and display unit + void setOptionsModel(OptionsModel *optionsModel); + +#ifdef ENABLE_BIP70 // Load root certificate authorities. Pass nullptr (default) // to read from the file specified in the -rootcertificates setting, // or, if that's not set, to use the system default root certificates. @@ -83,9 +93,6 @@ public: // Return certificate store static X509_STORE* getCertStore(); - // OptionsModel is used for getting proxy settings and display unit - void setOptionsModel(OptionsModel *optionsModel); - // Verify that the payment request network matches the client network static bool verifyNetwork(interfaces::Node& node, const payments::PaymentDetails& requestDetails); // Verify if the payment request is expired @@ -94,33 +101,40 @@ public: static bool verifySize(qint64 requestSize); // Verify the payment request amount is valid static bool verifyAmount(const CAmount& requestAmount); +#endif Q_SIGNALS: // Fired when a valid payment request is received void receivedPaymentRequest(SendCoinsRecipient); - // Fired when a valid PaymentACK is received - void receivedPaymentACK(const QString &paymentACKMsg); - // Fired when a message should be reported to the user void message(const QString &title, const QString &message, unsigned int style); +#ifdef ENABLE_BIP70 + // Fired when a valid PaymentACK is received + void receivedPaymentACK(const QString &paymentACKMsg); +#endif + public Q_SLOTS: // Signal this when the main window's UI is ready // to display payment requests to the user void uiReady(); - // Submit Payment message to a merchant, get back PaymentACK: - void fetchPaymentACK(WalletModel* walletModel, const SendCoinsRecipient& recipient, QByteArray transaction); - // Handle an incoming URI, URI with local file scheme or file void handleURIOrFile(const QString& s); +#ifdef ENABLE_BIP70 + // Submit Payment message to a merchant, get back PaymentACK: + void fetchPaymentACK(WalletModel* walletModel, const SendCoinsRecipient& recipient, QByteArray transaction); +#endif + private Q_SLOTS: void handleURIConnection(); +#ifdef ENABLE_BIP70 void netRequestFinished(QNetworkReply*); void reportSslErrors(QNetworkReply*, const QList<QSslError> &); void handlePaymentACK(const QString& paymentACKMsg); +#endif protected: // Constructor registers this on the parent QApplication to @@ -128,19 +142,19 @@ protected: bool eventFilter(QObject *object, QEvent *event); private: + bool saveURIs; // true during startup + QLocalServer* uriServer; + OptionsModel *optionsModel; + +#ifdef ENABLE_BIP70 static bool readPaymentRequestFromFile(const QString& filename, PaymentRequestPlus& request); bool processPaymentRequest(const PaymentRequestPlus& request, SendCoinsRecipient& recipient); void fetchRequest(const QUrl& url); // Setup networking void initNetManager(); - - bool saveURIs; // true during startup - QLocalServer* uriServer; - QNetworkAccessManager* netManager; // Used to fetch payment requests - - OptionsModel *optionsModel; +#endif }; #endif // BITCOIN_QT_PAYMENTSERVER_H diff --git a/src/qt/peertablemodel.cpp b/src/qt/peertablemodel.cpp index 8cfedca57f..496aeebf7d 100644 --- a/src/qt/peertablemodel.cpp +++ b/src/qt/peertablemodel.cpp @@ -49,8 +49,8 @@ class PeerTablePriv public: /** Local cache of peer information */ QList<CNodeCombinedStats> cachedNodeStats; - /** Column to sort nodes by */ - int sortColumn; + /** Column to sort nodes by (default to unsorted) */ + int sortColumn{-1}; /** Order (ascending or descending) to sort nodes by */ Qt::SortOrder sortOrder; /** Index of rows by node ID */ @@ -96,7 +96,7 @@ public: if (idx >= 0 && idx < cachedNodeStats.size()) return &cachedNodeStats[idx]; - return 0; + return nullptr; } }; @@ -104,12 +104,10 @@ PeerTableModel::PeerTableModel(interfaces::Node& node, ClientModel *parent) : QAbstractTableModel(parent), m_node(node), clientModel(parent), - timer(0) + timer(nullptr) { columns << tr("NodeId") << tr("Node/Service") << tr("Ping") << tr("Sent") << tr("Received") << tr("User Agent"); priv.reset(new PeerTablePriv()); - // default to unsorted - priv->sortColumn = -1; // set up timer for auto refresh timer = new QTimer(this); @@ -199,8 +197,7 @@ QVariant PeerTableModel::headerData(int section, Qt::Orientation orientation, in Qt::ItemFlags PeerTableModel::flags(const QModelIndex &index) const { - if(!index.isValid()) - return 0; + if (!index.isValid()) return Qt::NoItemFlags; Qt::ItemFlags retval = Qt::ItemIsSelectable | Qt::ItemIsEnabled; return retval; diff --git a/src/qt/peertablemodel.h b/src/qt/peertablemodel.h index 738a06496f..b3f5dd7dbe 100644 --- a/src/qt/peertablemodel.h +++ b/src/qt/peertablemodel.h @@ -51,7 +51,7 @@ class PeerTableModel : public QAbstractTableModel Q_OBJECT public: - explicit PeerTableModel(interfaces::Node& node, ClientModel *parent = 0); + explicit PeerTableModel(interfaces::Node& node, ClientModel *parent = nullptr); ~PeerTableModel(); const CNodeCombinedStats *getNodeStats(int idx); int getRowByNodeId(NodeId nodeid); diff --git a/src/qt/platformstyle.cpp b/src/qt/platformstyle.cpp index 56e0830cdc..d537d759de 100644 --- a/src/qt/platformstyle.cpp +++ b/src/qt/platformstyle.cpp @@ -139,6 +139,6 @@ const PlatformStyle *PlatformStyle::instantiate(const QString &platformId) platform_styles[x].useExtraSpacing); } } - return 0; + return nullptr; } diff --git a/src/qt/qvalidatedlineedit.cpp b/src/qt/qvalidatedlineedit.cpp index 85c5a58a84..aa936d6b7c 100644 --- a/src/qt/qvalidatedlineedit.cpp +++ b/src/qt/qvalidatedlineedit.cpp @@ -10,7 +10,7 @@ QValidatedLineEdit::QValidatedLineEdit(QWidget *parent) : QLineEdit(parent), valid(true), - checkValidator(0) + checkValidator(nullptr) { connect(this, &QValidatedLineEdit::textChanged, this, &QValidatedLineEdit::markValid); } diff --git a/src/qt/qvaluecombobox.h b/src/qt/qvaluecombobox.h index f266302310..8892071fba 100644 --- a/src/qt/qvaluecombobox.h +++ b/src/qt/qvaluecombobox.h @@ -16,7 +16,7 @@ class QValueComboBox : public QComboBox Q_PROPERTY(QVariant value READ value WRITE setValue NOTIFY valueChanged USER true) public: - explicit QValueComboBox(QWidget *parent = 0); + explicit QValueComboBox(QWidget *parent = nullptr); QVariant value() const; void setValue(const QVariant &value); diff --git a/src/qt/receivecoinsdialog.cpp b/src/qt/receivecoinsdialog.cpp index a7cc5da19e..bc96b5a6f7 100644 --- a/src/qt/receivecoinsdialog.cpp +++ b/src/qt/receivecoinsdialog.cpp @@ -25,8 +25,8 @@ ReceiveCoinsDialog::ReceiveCoinsDialog(const PlatformStyle *_platformStyle, QWidget *parent) : QDialog(parent), ui(new Ui::ReceiveCoinsDialog), - columnResizingFixer(0), - model(0), + columnResizingFixer(nullptr), + model(nullptr), platformStyle(_platformStyle) { ui->setupUi(this); diff --git a/src/qt/receivecoinsdialog.h b/src/qt/receivecoinsdialog.h index bc0f6248f0..5bca2f46e2 100644 --- a/src/qt/receivecoinsdialog.h +++ b/src/qt/receivecoinsdialog.h @@ -39,7 +39,7 @@ public: MINIMUM_COLUMN_WIDTH = 130 }; - explicit ReceiveCoinsDialog(const PlatformStyle *platformStyle, QWidget *parent = 0); + explicit ReceiveCoinsDialog(const PlatformStyle *platformStyle, QWidget *parent = nullptr); ~ReceiveCoinsDialog(); void setModel(WalletModel *model); diff --git a/src/qt/receiverequestdialog.cpp b/src/qt/receiverequestdialog.cpp index c561d948be..f5b30cf6d2 100644 --- a/src/qt/receiverequestdialog.cpp +++ b/src/qt/receiverequestdialog.cpp @@ -26,7 +26,7 @@ #endif QRImageWidget::QRImageWidget(QWidget *parent): - QLabel(parent), contextMenu(0) + QLabel(parent), contextMenu(nullptr) { contextMenu = new QMenu(this); QAction *saveImageAction = new QAction(tr("&Save Image..."), this); @@ -88,7 +88,7 @@ void QRImageWidget::contextMenuEvent(QContextMenuEvent *event) ReceiveRequestDialog::ReceiveRequestDialog(QWidget *parent) : QDialog(parent), ui(new Ui::ReceiveRequestDialog), - model(0) + model(nullptr) { ui->setupUi(this); diff --git a/src/qt/receiverequestdialog.h b/src/qt/receiverequestdialog.h index 5662af18b7..dd28fd73c8 100644 --- a/src/qt/receiverequestdialog.h +++ b/src/qt/receiverequestdialog.h @@ -28,7 +28,7 @@ class QRImageWidget : public QLabel Q_OBJECT public: - explicit QRImageWidget(QWidget *parent = 0); + explicit QRImageWidget(QWidget *parent = nullptr); QImage exportImage(); public Q_SLOTS: @@ -48,7 +48,7 @@ class ReceiveRequestDialog : public QDialog Q_OBJECT public: - explicit ReceiveRequestDialog(QWidget *parent = 0); + explicit ReceiveRequestDialog(QWidget *parent = nullptr); ~ReceiveRequestDialog(); void setModel(WalletModel *model); diff --git a/src/qt/recentrequeststablemodel.cpp b/src/qt/recentrequeststablemodel.cpp index 82ab48ac20..aa746017f3 100644 --- a/src/qt/recentrequeststablemodel.cpp +++ b/src/qt/recentrequeststablemodel.cpp @@ -15,8 +15,6 @@ RecentRequestsTableModel::RecentRequestsTableModel(WalletModel *parent) : QAbstractTableModel(parent), walletModel(parent) { - nReceiveRequestsMaxId = 0; - // Load entries from wallet std::vector<std::string> vReceiveRequests; parent->loadReceiveRequests(vReceiveRequests); diff --git a/src/qt/recentrequeststablemodel.h b/src/qt/recentrequeststablemodel.h index ff2c0c8098..8a1140e952 100644 --- a/src/qt/recentrequeststablemodel.h +++ b/src/qt/recentrequeststablemodel.h @@ -94,7 +94,7 @@ private: WalletModel *walletModel; QStringList columns; QList<RecentRequestEntry> list; - int64_t nReceiveRequestsMaxId; + int64_t nReceiveRequestsMaxId{0}; /** Updates the column title to "Amount (DisplayUnit)" and emits headerDataChanged() signal for table headers to react. */ void updateAmountColumnTitle(); diff --git a/src/qt/res/movies/makespinner.sh b/src/qt/res/movies/makespinner.sh index 76e36e4f31..f47c66e02c 100755 --- a/src/qt/res/movies/makespinner.sh +++ b/src/qt/res/movies/makespinner.sh @@ -1,3 +1,5 @@ +#!/usr/bin/env bash +# # Copyright (c) 2014-2015 The Bitcoin Core developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. diff --git a/src/qt/rpcconsole.cpp b/src/qt/rpcconsole.cpp index 3857befdf2..fc1e14b031 100644 --- a/src/qt/rpcconsole.cpp +++ b/src/qt/rpcconsole.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. @@ -18,7 +18,8 @@ #include <netbase.h> #include <rpc/server.h> #include <rpc/client.h> -#include <util.h> +#include <util/strencodings.h> +#include <util/system.h> #include <openssl/crypto.h> @@ -85,7 +86,7 @@ public: explicit RPCExecutor(interfaces::Node& node) : m_node(node) {} public Q_SLOTS: - void request(const QString &command, const QString &walletID); + void request(const QString &command, const WalletModel* wallet_model); Q_SIGNALS: void reply(int category, const QString &command); @@ -148,7 +149,7 @@ public: * @param[out] pstrFilteredOut Command line, filtered to remove any sensitive data */ -bool RPCConsole::RPCParseCommandLine(interfaces::Node* node, std::string &strResult, const std::string &strCommand, const bool fExecute, std::string * const pstrFilteredOut, const std::string *walletID) +bool RPCConsole::RPCParseCommandLine(interfaces::Node* node, std::string &strResult, const std::string &strCommand, const bool fExecute, std::string * const pstrFilteredOut, const WalletModel* wallet_model) { std::vector< std::vector<std::string> > stack; stack.push_back(std::vector<std::string>()); @@ -226,7 +227,7 @@ bool RPCConsole::RPCParseCommandLine(interfaces::Node* node, std::string &strRes if (lastResult.isArray()) { for(char argch: curarg) - if (!std::isdigit(argch)) + if (!IsDigit(argch)) throw std::runtime_error("Invalid result query"); subelement = lastResult[atoi(curarg.c_str())]; } @@ -306,8 +307,8 @@ bool RPCConsole::RPCParseCommandLine(interfaces::Node* node, std::string &strRes std::string method = stack.back()[0]; std::string uri; #ifdef ENABLE_WALLET - if (walletID) { - QByteArray encodedName = QUrl::toPercentEncoding(QString::fromStdString(*walletID)); + if (wallet_model) { + QByteArray encodedName = QUrl::toPercentEncoding(wallet_model->getWalletName()); uri = "/wallet/"+std::string(encodedName.constData(), encodedName.length()); } #endif @@ -387,7 +388,7 @@ bool RPCConsole::RPCParseCommandLine(interfaces::Node* node, std::string &strRes } } -void RPCExecutor::request(const QString &command, const QString &walletID) +void RPCExecutor::request(const QString &command, const WalletModel* wallet_model) { try { @@ -395,13 +396,12 @@ void RPCExecutor::request(const QString &command, const QString &walletID) std::string executableCommand = command.toStdString() + "\n"; // Catch the console-only-help command before RPC call is executed and reply with help text as-if a RPC reply. - if(executableCommand == "help-console\n") - { + if(executableCommand == "help-console\n") { Q_EMIT reply(RPCConsole::CMD_REPLY, QString(("\n" "This console accepts RPC commands using the standard syntax.\n" " example: getblockhash 0\n\n" - "This console can also accept RPC commands using parenthesized syntax.\n" + "This console can also accept RPC commands using the parenthesized syntax.\n" " example: getblockhash(0)\n\n" "Commands may be nested when specified with the parenthesized syntax.\n" @@ -411,16 +411,14 @@ void RPCExecutor::request(const QString &command, const QString &walletID) " example: getblockhash 0\n" " getblockhash,0\n\n" - "Named results can be queried with a non-quoted key string in brackets.\n" - " example: getblock(getblockhash(0) true)[tx]\n\n" + "Named results can be queried with a non-quoted key string in brackets using the parenthesized syntax.\n" + " example: getblock(getblockhash(0) 1)[tx]\n\n" - "Results without keys can be queried using an integer in brackets.\n" - " example: getblock(getblockhash(0),true)[tx][0]\n\n"))); + "Results without keys can be queried with an integer in brackets using the parenthesized syntax.\n" + " example: getblock(getblockhash(0),1)[tx][0]\n\n"))); return; } - std::string wallet_id = walletID.toStdString(); - if (!RPCConsole::RPCExecuteCommandLine(m_node, result, executableCommand, nullptr, walletID.isNull() ? nullptr : &wallet_id)) - { + if (!RPCConsole::RPCExecuteCommandLine(m_node, result, executableCommand, nullptr, wallet_model)) { Q_EMIT reply(RPCConsole::CMD_ERROR, QString("Parse error: unbalanced ' or \"")); return; } @@ -459,6 +457,9 @@ RPCConsole::RPCConsole(interfaces::Node& node, const PlatformStyle *_platformSty move(QApplication::desktop()->availableGeometry().center() - frameGeometry().center()); } + QChar nonbreaking_hyphen(8209); + ui->dataDir->setToolTip(ui->dataDir->toolTip().arg(QString(nonbreaking_hyphen) + "datadir")); + ui->blocksDir->setToolTip(ui->blocksDir->toolTip().arg(QString(nonbreaking_hyphen) + "blocksdir")); ui->openDebugLogfileButton->setToolTip(ui->openDebugLogfileButton->toolTip().arg(tr(PACKAGE_NAME))); if (platformStyle->getImagesOnButtons()) { @@ -483,7 +484,7 @@ RPCConsole::RPCConsole(interfaces::Node& node, const PlatformStyle *_platformSty // set library version labels #ifdef ENABLE_WALLET - ui->berkeleyDBVersion->setText(DbEnv::version(0, 0, 0)); + ui->berkeleyDBVersion->setText(DbEnv::version(nullptr, nullptr, nullptr)); #else ui->label_berkeleyDBVersion->hide(); ui->berkeleyDBVersion->hide(); @@ -536,6 +537,7 @@ bool RPCConsole::eventFilter(QObject* obj, QEvent *event) // forward these events to lineEdit if(obj == autoCompleter->popup()) { QApplication::postEvent(ui->lineEdit, new QKeyEvent(*keyevt)); + autoCompleter->popup()->hide(); return true; } break; @@ -661,6 +663,7 @@ void RPCConsole::setClientModel(ClientModel *model) ui->clientVersion->setText(model->formatFullVersion()); ui->clientUserAgent->setText(model->formatSubVersion()); ui->dataDir->setText(model->dataDir()); + ui->blocksDir->setText(model->blocksDir()); ui->startupTime->setText(model->formatClientStartupTime()); ui->networkName->setText(QString::fromStdString(Params().NetworkIDString())); @@ -684,8 +687,7 @@ void RPCConsole::setClientModel(ClientModel *model) } if (!model) { // Client model is being set to 0, this means shutdown() is about to be called. - // Make sure we clean up the executor thread - Q_EMIT stopExecutor(); + thread.quit(); thread.wait(); } } @@ -693,10 +695,8 @@ void RPCConsole::setClientModel(ClientModel *model) #ifdef ENABLE_WALLET void RPCConsole::addWallet(WalletModel * const walletModel) { - const QString name = walletModel->getWalletName(); - // use name for text and internal data object (to allow to move to a wallet id later) - QString display_name = name.isEmpty() ? "["+tr("default wallet")+"]" : name; - ui->WalletSelector->addItem(display_name, name); + // use name for text and wallet model for internal data object (to allow to move to a wallet id later) + ui->WalletSelector->addItem(walletModel->getDisplayName(), QVariant::fromValue(walletModel)); if (ui->WalletSelector->count() == 2 && !isVisible()) { // First wallet added, set to default so long as the window isn't presently visible (and potentially in use) ui->WalletSelector->setCurrentIndex(1); @@ -709,8 +709,7 @@ void RPCConsole::addWallet(WalletModel * const walletModel) void RPCConsole::removeWallet(WalletModel * const walletModel) { - const QString name = walletModel->getWalletName(); - ui->WalletSelector->removeItem(ui->WalletSelector->findData(name)); + ui->WalletSelector->removeItem(ui->WalletSelector->findData(QVariant::fromValue(walletModel))); if (ui->WalletSelector->count() == 2) { ui->WalletSelector->setVisible(false); ui->WalletSelectorLabel->setVisible(false); @@ -905,25 +904,25 @@ void RPCConsole::on_lineEdit_returnPressed() cmdBeforeBrowsing = QString(); - QString walletID; + WalletModel* wallet_model{nullptr}; #ifdef ENABLE_WALLET const int wallet_index = ui->WalletSelector->currentIndex(); if (wallet_index > 0) { - walletID = (QString)ui->WalletSelector->itemData(wallet_index).value<QString>(); + wallet_model = ui->WalletSelector->itemData(wallet_index).value<WalletModel*>(); } - if (m_last_wallet_id != walletID) { - if (walletID.isNull()) { - message(CMD_REQUEST, tr("Executing command without any wallet")); + if (m_last_wallet_model != wallet_model) { + if (wallet_model) { + message(CMD_REQUEST, tr("Executing command using \"%1\" wallet").arg(wallet_model->getWalletName())); } else { - message(CMD_REQUEST, tr("Executing command using \"%1\" wallet").arg(walletID)); + message(CMD_REQUEST, tr("Executing command without any wallet")); } - m_last_wallet_id = walletID; + m_last_wallet_model = wallet_model; } #endif message(CMD_REQUEST, QString::fromStdString(strFilteredCmd)); - Q_EMIT cmdRequest(cmd, walletID); + Q_EMIT cmdRequest(cmd, m_last_wallet_model); cmd = QString::fromStdString(strFilteredCmd); @@ -974,11 +973,8 @@ void RPCConsole::startExecutor() // Requests from this object must go to executor connect(this, &RPCConsole::cmdRequest, executor, &RPCExecutor::request); - // On stopExecutor signal - // - quit the Qt event loop in the execution thread - connect(this, &RPCConsole::stopExecutor, &thread, &QThread::quit); - // - queue executor for deletion (in execution thread) - connect(&thread, &QThread::finished, executor, &RPCExecutor::deleteLater, Qt::DirectConnection); + // Make sure executor object is deleted in its own thread + connect(&thread, &QThread::finished, executor, &RPCExecutor::deleteLater); // Default implementation of QThread::run() simply spins up an event loop in the thread, // which is what we want. @@ -987,10 +983,9 @@ void RPCConsole::startExecutor() void RPCConsole::on_tabWidget_currentChanged(int index) { - if (ui->tabWidget->widget(index) == ui->tab_console) + if (ui->tabWidget->widget(index) == ui->tab_console) { ui->lineEdit->setFocus(); - else if (ui->tabWidget->widget(index) != ui->tab_peers) - clearSelectedNode(); + } } void RPCConsole::on_openDebugLogfileButton_clicked() @@ -1216,16 +1211,16 @@ void RPCConsole::banSelectedNode(int bantime) // Get currently selected peer address NodeId id = nodes.at(i).data().toLongLong(); - // Get currently selected peer address - int detailNodeRow = clientModel->getPeerTableModel()->getRowByNodeId(id); - if(detailNodeRow < 0) - return; + // Get currently selected peer address + int detailNodeRow = clientModel->getPeerTableModel()->getRowByNodeId(id); + if (detailNodeRow < 0) return; - // Find possible nodes, ban it and clear the selected node - const CNodeCombinedStats *stats = clientModel->getPeerTableModel()->getNodeStats(detailNodeRow); - if(stats) { + // Find possible nodes, ban it and clear the selected node + const CNodeCombinedStats *stats = clientModel->getPeerTableModel()->getNodeStats(detailNodeRow); + if (stats) { m_node.ban(stats->nodeStats.addr, BanReasonManuallyAdded, bantime); - } + m_node.disconnect(stats->nodeStats.addr); + } } clearSelectedNode(); clientModel->getBanTableModel()->refresh(); @@ -1270,7 +1265,17 @@ void RPCConsole::showOrHideBanTableIfRequired() ui->banHeading->setVisible(visible); } +RPCConsole::TabTypes RPCConsole::tabFocus() const +{ + return (TabTypes) ui->tabWidget->currentIndex(); +} + void RPCConsole::setTabFocus(enum TabTypes tabType) { ui->tabWidget->setCurrentIndex(tabType); } + +QString RPCConsole::tabTitle(TabTypes tab_type) const +{ + return ui->tabWidget->tabText(tab_type); +} diff --git a/src/qt/rpcconsole.h b/src/qt/rpcconsole.h index db77043951..79b0f3b19c 100644 --- a/src/qt/rpcconsole.h +++ b/src/qt/rpcconsole.h @@ -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. @@ -41,9 +41,9 @@ public: explicit RPCConsole(interfaces::Node& node, const PlatformStyle *platformStyle, QWidget *parent); ~RPCConsole(); - static bool RPCParseCommandLine(interfaces::Node* node, std::string &strResult, const std::string &strCommand, bool fExecute, std::string * const pstrFilteredOut = nullptr, const std::string *walletID = nullptr); - static bool RPCExecuteCommandLine(interfaces::Node& node, std::string &strResult, const std::string &strCommand, std::string * const pstrFilteredOut = nullptr, const std::string *walletID = nullptr) { - return RPCParseCommandLine(&node, strResult, strCommand, true, pstrFilteredOut, walletID); + static bool RPCParseCommandLine(interfaces::Node* node, std::string &strResult, const std::string &strCommand, bool fExecute, std::string * const pstrFilteredOut = nullptr, const WalletModel* wallet_model = nullptr); + static bool RPCExecuteCommandLine(interfaces::Node& node, std::string &strResult, const std::string &strCommand, std::string * const pstrFilteredOut = nullptr, const WalletModel* wallet_model = nullptr) { + return RPCParseCommandLine(&node, strResult, strCommand, true, pstrFilteredOut, wallet_model); } void setClientModel(ClientModel *model); @@ -65,6 +65,11 @@ public: TAB_PEERS = 3 }; + std::vector<TabTypes> tabs() const { return {TAB_INFO, TAB_CONSOLE, TAB_GRAPH, TAB_PEERS}; } + + TabTypes tabFocus() const; + QString tabTitle(TabTypes tab_type) const; + protected: virtual bool eventFilter(QObject* obj, QEvent *event); void keyPressEvent(QKeyEvent *); @@ -127,8 +132,7 @@ public Q_SLOTS: Q_SIGNALS: // For RPC command executor - void stopExecutor(); - void cmdRequest(const QString &command, const QString &walletID); + void cmdRequest(const QString &command, const WalletModel* wallet_model); private: void startExecutor(); @@ -160,7 +164,7 @@ private: int consoleFontSize = 0; QCompleter *autoCompleter = nullptr; QThread thread; - QString m_last_wallet_id; + WalletModel* m_last_wallet_model{nullptr}; /** Update UI with latest network info from model. */ void updateNetworkState(); diff --git a/src/qt/sendcoinsdialog.cpp b/src/qt/sendcoinsdialog.cpp index 6f66bc19e1..6e00ab755c 100644 --- a/src/qt/sendcoinsdialog.cpp +++ b/src/qt/sendcoinsdialog.cpp @@ -2,6 +2,10 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. +#if defined(HAVE_CONFIG_H) +#include <config/bitcoin-config.h> +#endif + #include <qt/sendcoinsdialog.h> #include <qt/forms/ui_sendcoinsdialog.h> @@ -50,8 +54,8 @@ int getIndexForConfTarget(int target) { SendCoinsDialog::SendCoinsDialog(const PlatformStyle *_platformStyle, QWidget *parent) : QDialog(parent), ui(new Ui::SendCoinsDialog), - clientModel(0), - model(0), + clientModel(nullptr), + model(nullptr), fNewRecipientAllowed(true), fFeeMinimized(true), platformStyle(_platformStyle) @@ -115,13 +119,11 @@ SendCoinsDialog::SendCoinsDialog(const PlatformStyle *_platformStyle, QWidget *p settings.setValue("nSmartFeeSliderPosition", 0); if (!settings.contains("nTransactionFee")) settings.setValue("nTransactionFee", (qint64)DEFAULT_PAY_TX_FEE); - if (!settings.contains("fPayOnlyMinFee")) - settings.setValue("fPayOnlyMinFee", false); ui->groupFee->setId(ui->radioSmartFee, 0); ui->groupFee->setId(ui->radioCustomFee, 1); ui->groupFee->button((int)std::max(0, std::min(1, settings.value("nFeeRadio").toInt())))->setChecked(true); + ui->customFee->SetAllowEmpty(false); ui->customFee->setValue(settings.value("nTransactionFee").toLongLong()); - ui->checkBoxMinimumFee->setChecked(settings.value("fPayOnlyMinFee").toBool()); minimizeFeeSection(settings.value("fFeeSectionMinimized").toBool()); } @@ -170,14 +172,15 @@ void SendCoinsDialog::setModel(WalletModel *_model) connect(ui->groupFee, static_cast<void (QButtonGroup::*)(int)>(&QButtonGroup::buttonClicked), this, &SendCoinsDialog::updateFeeSectionControls); connect(ui->groupFee, static_cast<void (QButtonGroup::*)(int)>(&QButtonGroup::buttonClicked), this, &SendCoinsDialog::coinControlUpdateLabels); connect(ui->customFee, &BitcoinAmountField::valueChanged, this, &SendCoinsDialog::coinControlUpdateLabels); - connect(ui->checkBoxMinimumFee, &QCheckBox::stateChanged, this, &SendCoinsDialog::setMinimumFee); - connect(ui->checkBoxMinimumFee, &QCheckBox::stateChanged, this, &SendCoinsDialog::updateFeeSectionControls); - connect(ui->checkBoxMinimumFee, &QCheckBox::stateChanged, this, &SendCoinsDialog::coinControlUpdateLabels); connect(ui->optInRBF, &QCheckBox::stateChanged, this, &SendCoinsDialog::updateSmartFeeLabel); connect(ui->optInRBF, &QCheckBox::stateChanged, this, &SendCoinsDialog::coinControlUpdateLabels); - ui->customFee->setSingleStep(model->wallet().getRequiredFee(1000)); + CAmount requiredFee = model->wallet().getRequiredFee(1000); + ui->customFee->SetMinValue(requiredFee); + if (ui->customFee->value() < requiredFee) { + ui->customFee->setValue(requiredFee); + } + ui->customFee->setSingleStep(requiredFee); updateFeeSectionControls(); - updateMinFeeLabel(); updateSmartFeeLabel(); // set default rbf checkbox state @@ -206,7 +209,6 @@ SendCoinsDialog::~SendCoinsDialog() settings.setValue("nFeeRadio", ui->groupFee->checkedId()); settings.setValue("nConfTarget", getConfTargetForIndex(ui->confTargetSelector->currentIndex())); settings.setValue("nTransactionFee", (qint64)ui->customFee->value()); - settings.setValue("fPayOnlyMinFee", ui->checkBoxMinimumFee->isChecked()); delete ui; } @@ -290,7 +292,9 @@ void SendCoinsDialog::on_sendButton_clicked() QString recipientElement; recipientElement = "<br />"; +#ifdef ENABLE_BIP70 if (!rcp.paymentRequest.IsInitialized()) // normal payment +#endif { if(rcp.label.length() > 0) // label with address { @@ -302,6 +306,7 @@ void SendCoinsDialog::on_sendButton_clicked() recipientElement.append(tr("%1 to %2").arg(amount, address)); } } +#ifdef ENABLE_BIP70 else if(!rcp.authenticatedMerchant.isEmpty()) // authenticated payment request { recipientElement.append(tr("%1 to %2").arg(amount, GUIUtil::HtmlEscape(rcp.authenticatedMerchant))); @@ -310,6 +315,7 @@ void SendCoinsDialog::on_sendButton_clicked() { recipientElement.append(tr("%1 to %2").arg(amount, address)); } +#endif formatted.append(recipientElement); } @@ -437,7 +443,7 @@ SendCoinsEntry *SendCoinsDialog::addEntry() void SendCoinsDialog::updateTabsAndLabels() { - setupTabChain(0); + setupTabChain(nullptr); coinControlUpdateLabels(); } @@ -472,7 +478,7 @@ QWidget *SendCoinsDialog::setupTabChain(QWidget *prev) void SendCoinsDialog::setAddress(const QString &address) { - SendCoinsEntry *entry = 0; + SendCoinsEntry *entry = nullptr; // Replace the first entry if it is still unused if(ui->entries->count() == 1) { @@ -495,7 +501,7 @@ void SendCoinsDialog::pasteEntry(const SendCoinsRecipient &rv) if(!fNewRecipientAllowed) return; - SendCoinsEntry *entry = 0; + SendCoinsEntry *entry = nullptr; // Replace the first entry if it is still unused if(ui->entries->count() == 1) { @@ -534,7 +540,6 @@ void SendCoinsDialog::updateDisplayUnit() { setBalance(model->wallet().getBalances()); ui->customFee->setDisplayUnit(model->getOptionsModel()->getDisplayUnit()); - updateMinFeeLabel(); updateSmartFeeLabel(); } @@ -634,11 +639,6 @@ void SendCoinsDialog::useAvailableBalance(SendCoinsEntry* entry) } } -void SendCoinsDialog::setMinimumFee() -{ - ui->customFee->setValue(model->wallet().getRequiredFee(1000)); -} - void SendCoinsDialog::updateFeeSectionControls() { ui->confTargetSelector ->setEnabled(ui->radioSmartFee->isChecked()); @@ -646,10 +646,9 @@ void SendCoinsDialog::updateFeeSectionControls() ui->labelSmartFee2 ->setEnabled(ui->radioSmartFee->isChecked()); ui->labelSmartFee3 ->setEnabled(ui->radioSmartFee->isChecked()); ui->labelFeeEstimation ->setEnabled(ui->radioSmartFee->isChecked()); - ui->checkBoxMinimumFee ->setEnabled(ui->radioCustomFee->isChecked()); - ui->labelMinFeeWarning ->setEnabled(ui->radioCustomFee->isChecked()); - ui->labelCustomPerKilobyte ->setEnabled(ui->radioCustomFee->isChecked() && !ui->checkBoxMinimumFee->isChecked()); - ui->customFee ->setEnabled(ui->radioCustomFee->isChecked() && !ui->checkBoxMinimumFee->isChecked()); + ui->labelCustomFeeWarning ->setEnabled(ui->radioCustomFee->isChecked()); + ui->labelCustomPerKilobyte ->setEnabled(ui->radioCustomFee->isChecked()); + ui->customFee ->setEnabled(ui->radioCustomFee->isChecked()); } void SendCoinsDialog::updateFeeMinimizedLabel() @@ -664,14 +663,6 @@ void SendCoinsDialog::updateFeeMinimizedLabel() } } -void SendCoinsDialog::updateMinFeeLabel() -{ - if (model && model->getOptionsModel()) - ui->checkBoxMinimumFee->setText(tr("Pay only the required fee of %1").arg( - BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), model->wallet().getRequiredFee(1000)) + "/kB") - ); -} - void SendCoinsDialog::updateCoinControlState(CCoinControl& ctrl) { if (ui->radioCustomFee->isChecked()) { diff --git a/src/qt/sendcoinsdialog.h b/src/qt/sendcoinsdialog.h index 7009855f17..337a72b878 100644 --- a/src/qt/sendcoinsdialog.h +++ b/src/qt/sendcoinsdialog.h @@ -31,7 +31,7 @@ class SendCoinsDialog : public QDialog Q_OBJECT public: - explicit SendCoinsDialog(const PlatformStyle *platformStyle, QWidget *parent = 0); + explicit SendCoinsDialog(const PlatformStyle *platformStyle, QWidget *parent = nullptr); ~SendCoinsDialog(); void setClientModel(ClientModel *clientModel); @@ -92,9 +92,7 @@ private Q_SLOTS: void coinControlClipboardBytes(); void coinControlClipboardLowOutput(); void coinControlClipboardChange(); - void setMinimumFee(); void updateFeeSectionControls(); - void updateMinFeeLabel(); void updateSmartFeeLabel(); Q_SIGNALS: @@ -110,7 +108,7 @@ class SendConfirmationDialog : public QMessageBox Q_OBJECT public: - SendConfirmationDialog(const QString &title, const QString &text, int secDelay = SEND_CONFIRM_DELAY, QWidget *parent = 0); + SendConfirmationDialog(const QString &title, const QString &text, int secDelay = SEND_CONFIRM_DELAY, QWidget *parent = nullptr); int exec(); private Q_SLOTS: diff --git a/src/qt/sendcoinsentry.cpp b/src/qt/sendcoinsentry.cpp index b394ff3150..7324d759fb 100644 --- a/src/qt/sendcoinsentry.cpp +++ b/src/qt/sendcoinsentry.cpp @@ -2,6 +2,10 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. +#if defined(HAVE_CONFIG_H) +#include <config/bitcoin-config.h> +#endif + #include <qt/sendcoinsentry.h> #include <qt/forms/ui_sendcoinsentry.h> @@ -17,7 +21,7 @@ SendCoinsEntry::SendCoinsEntry(const PlatformStyle *_platformStyle, QWidget *parent) : QStackedWidget(parent), ui(new Ui::SendCoinsEntry), - model(0), + model(nullptr), platformStyle(_platformStyle) { ui->setupUi(this); @@ -133,9 +137,11 @@ bool SendCoinsEntry::validate(interfaces::Node& node) // Check input validity bool retval = true; +#ifdef ENABLE_BIP70 // Skip checks for payment request if (recipient.paymentRequest.IsInitialized()) return retval; +#endif if (!model->validateAddress(ui->payTo->text())) { @@ -149,7 +155,7 @@ bool SendCoinsEntry::validate(interfaces::Node& node) } // Sending a zero amount is invalid - if (ui->payAmount->value(0) <= 0) + if (ui->payAmount->value(nullptr) <= 0) { ui->payAmount->setValid(false); retval = false; @@ -166,9 +172,11 @@ bool SendCoinsEntry::validate(interfaces::Node& node) SendCoinsRecipient SendCoinsEntry::getValue() { +#ifdef ENABLE_BIP70 // Payment request if (recipient.paymentRequest.IsInitialized()) return recipient; +#endif // Normal payment recipient.address = ui->payTo->text(); @@ -196,6 +204,7 @@ void SendCoinsEntry::setValue(const SendCoinsRecipient &value) { recipient = value; +#ifdef ENABLE_BIP70 if (recipient.paymentRequest.IsInitialized()) // payment request { if (recipient.authenticatedMerchant.isEmpty()) // unauthenticated @@ -216,6 +225,7 @@ void SendCoinsEntry::setValue(const SendCoinsRecipient &value) } } else // normal payment +#endif { // message ui->messageTextLabel->setText(recipient.message); diff --git a/src/qt/sendcoinsentry.h b/src/qt/sendcoinsentry.h index 48ecd598d6..42e2217130 100644 --- a/src/qt/sendcoinsentry.h +++ b/src/qt/sendcoinsentry.h @@ -26,7 +26,7 @@ class SendCoinsEntry : public QStackedWidget Q_OBJECT public: - explicit SendCoinsEntry(const PlatformStyle *platformStyle, QWidget *parent = 0); + explicit SendCoinsEntry(const PlatformStyle *platformStyle, QWidget *parent = nullptr); ~SendCoinsEntry(); void setModel(WalletModel *model); diff --git a/src/qt/signverifymessagedialog.cpp b/src/qt/signverifymessagedialog.cpp index 487fc9ee1e..d37a78fa8c 100644 --- a/src/qt/signverifymessagedialog.cpp +++ b/src/qt/signverifymessagedialog.cpp @@ -22,7 +22,7 @@ SignVerifyMessageDialog::SignVerifyMessageDialog(const PlatformStyle *_platformStyle, QWidget *parent) : QDialog(parent), ui(new Ui::SignVerifyMessageDialog), - model(0), + model(nullptr), platformStyle(_platformStyle) { ui->setupUi(this); diff --git a/src/qt/splashscreen.cpp b/src/qt/splashscreen.cpp index 1eff4f6b65..0126a2920e 100644 --- a/src/qt/splashscreen.cpp +++ b/src/qt/splashscreen.cpp @@ -15,7 +15,7 @@ #include <interfaces/node.h> #include <interfaces/wallet.h> #include <ui_interface.h> -#include <util.h> +#include <util/system.h> #include <version.h> #include <QApplication> @@ -24,10 +24,9 @@ #include <QPainter> #include <QRadialGradient> -#include <boost/bind.hpp> SplashScreen::SplashScreen(interfaces::Node& node, Qt::WindowFlags f, const NetworkStyle *networkStyle) : - QWidget(0, f), curAlignment(0), m_node(node) + QWidget(nullptr, f), curAlignment(0), m_node(node) { // set reference point, paddings int paddingRight = 50; @@ -37,9 +36,7 @@ SplashScreen::SplashScreen(interfaces::Node& node, Qt::WindowFlags f, const Netw float fontFactor = 1.0; float devicePixelRatio = 1.0; -#if QT_VERSION > 0x050100 devicePixelRatio = static_cast<QGuiApplication*>(QCoreApplication::instance())->devicePixelRatio(); -#endif // define text to place QString titleText = tr(PACKAGE_NAME); @@ -53,10 +50,8 @@ SplashScreen::SplashScreen(interfaces::Node& node, Qt::WindowFlags f, const Netw QSize splashSize(480*devicePixelRatio,320*devicePixelRatio); pixmap = QPixmap(splashSize); -#if QT_VERSION > 0x050100 // change to HiDPI if it makes sense pixmap.setDevicePixelRatio(devicePixelRatio); -#endif QPainter pixPaint(&pixmap); pixPaint.setPen(QColor(100,100,100)); @@ -149,10 +144,8 @@ bool SplashScreen::eventFilter(QObject * obj, QEvent * ev) { return QObject::eventFilter(obj, ev); } -void SplashScreen::slotFinish(QWidget *mainWin) +void SplashScreen::finish() { - Q_UNUSED(mainWin); - /* If the window is minimized, hide() will be ignored. */ /* Make sure we de-minimize the splashscreen window before hiding */ if (isMinimized()) @@ -180,7 +173,7 @@ static void ShowProgress(SplashScreen *splash, const std::string &title, int nPr #ifdef ENABLE_WALLET void SplashScreen::ConnectWallet(std::unique_ptr<interfaces::Wallet> wallet) { - m_connected_wallet_handlers.emplace_back(wallet->handleShowProgress(boost::bind(ShowProgress, this, _1, _2, false))); + m_connected_wallet_handlers.emplace_back(wallet->handleShowProgress(std::bind(ShowProgress, this, std::placeholders::_1, std::placeholders::_2, false))); m_connected_wallets.emplace_back(std::move(wallet)); } #endif @@ -188,8 +181,8 @@ void SplashScreen::ConnectWallet(std::unique_ptr<interfaces::Wallet> wallet) void SplashScreen::subscribeToCoreSignals() { // Connect signals to client - m_handler_init_message = m_node.handleInitMessage(boost::bind(InitMessage, this, _1)); - m_handler_show_progress = m_node.handleShowProgress(boost::bind(ShowProgress, this, _1, _2, _3)); + m_handler_init_message = m_node.handleInitMessage(std::bind(InitMessage, this, std::placeholders::_1)); + m_handler_show_progress = m_node.handleShowProgress(std::bind(ShowProgress, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)); #ifdef ENABLE_WALLET m_handler_load_wallet = m_node.handleLoadWallet([this](std::unique_ptr<interfaces::Wallet> wallet) { ConnectWallet(std::move(wallet)); }); #endif diff --git a/src/qt/splashscreen.h b/src/qt/splashscreen.h index c28b6e5660..f99dd0c701 100644 --- a/src/qt/splashscreen.h +++ b/src/qt/splashscreen.h @@ -5,8 +5,7 @@ #ifndef BITCOIN_QT_SPLASHSCREEN_H #define BITCOIN_QT_SPLASHSCREEN_H -#include <functional> -#include <QSplashScreen> +#include <QWidget> #include <memory> @@ -37,8 +36,8 @@ protected: void closeEvent(QCloseEvent *event); public Q_SLOTS: - /** Slot to call finish() method as it's not defined as slot */ - void slotFinish(QWidget *mainWin); + /** Hide the splash screen window and schedule the splash screen object for deletion */ + void finish(); /** Show message and progress */ void showMessage(const QString &message, int alignment, const QColor &color); diff --git a/src/qt/test/addressbooktests.cpp b/src/qt/test/addressbooktests.cpp index c3d33c76d4..3e414df1f0 100644 --- a/src/qt/test/addressbooktests.cpp +++ b/src/qt/test/addressbooktests.cpp @@ -2,21 +2,22 @@ #include <qt/test/util.h> #include <test/test_bitcoin.h> +#include <interfaces/chain.h> #include <interfaces/node.h> #include <qt/addressbookpage.h> #include <qt/addresstablemodel.h> #include <qt/editaddressdialog.h> -#include <qt/callback.h> #include <qt/optionsmodel.h> #include <qt/platformstyle.h> #include <qt/qvalidatedlineedit.h> #include <qt/walletmodel.h> #include <key.h> -#include <pubkey.h> #include <key_io.h> +#include <pubkey.h> #include <wallet/wallet.h> +#include <QApplication> #include <QTimer> #include <QMessageBox> @@ -56,7 +57,8 @@ void EditAddressAndSubmit( void TestAddAddressesToSendBook() { TestChain100Setup test; - std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>("mock", WalletDatabase::CreateMock()); + auto chain = interfaces::MakeChain(); + std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(*chain, WalletLocation(), WalletDatabase::CreateMock()); bool firstRun; wallet->LoadWallet(firstRun); @@ -139,5 +141,16 @@ void TestAddAddressesToSendBook() void AddressBookTests::addressBookTests() { +#ifdef Q_OS_MAC + if (QApplication::platformName() == "minimal") { + // Disable for mac on "minimal" platform to avoid crashes inside the Qt + // framework when it tries to look up unimplemented cocoa functions, + // and fails to handle returned nulls + // (https://bugreports.qt.io/browse/QTBUG-49686). + QWARN("Skipping AddressBookTests on mac build with 'minimal' platform set due to Qt bugs. To run AppTests, invoke " + "with 'test_bitcoin-qt -platform cocoa' on mac, or else use a linux or windows build."); + return; + } +#endif TestAddAddressesToSendBook(); } diff --git a/src/qt/test/apptests.cpp b/src/qt/test/apptests.cpp new file mode 100644 index 0000000000..2c477a2e98 --- /dev/null +++ b/src/qt/test/apptests.cpp @@ -0,0 +1,117 @@ +// Copyright (c) 2018 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include <qt/test/apptests.h> + +#include <chainparams.h> +#include <init.h> +#include <qt/bitcoin.h> +#include <qt/bitcoingui.h> +#include <qt/networkstyle.h> +#include <qt/rpcconsole.h> +#include <shutdown.h> +#include <validation.h> + +#if defined(HAVE_CONFIG_H) +#include <config/bitcoin-config.h> +#endif +#ifdef ENABLE_WALLET +#include <wallet/db.h> +#endif + +#include <QAction> +#include <QEventLoop> +#include <QLineEdit> +#include <QScopedPointer> +#include <QTest> +#include <QTextEdit> +#include <QtGlobal> +#if QT_VERSION >= 0x050000 +#include <QtTest/QtTestWidgets> +#endif +#include <QtTest/QtTestGui> +#include <new> +#include <string> +#include <univalue.h> + +namespace { +//! Call getblockchaininfo RPC and check first field of JSON output. +void TestRpcCommand(RPCConsole* console) +{ + QEventLoop loop; + QTextEdit* messagesWidget = console->findChild<QTextEdit*>("messagesWidget"); + QObject::connect(messagesWidget, &QTextEdit::textChanged, &loop, &QEventLoop::quit); + QLineEdit* lineEdit = console->findChild<QLineEdit*>("lineEdit"); + QTest::keyClicks(lineEdit, "getblockchaininfo"); + QTest::keyClick(lineEdit, Qt::Key_Return); + loop.exec(); + QString output = messagesWidget->toPlainText(); + UniValue value; + value.read(output.right(output.size() - output.lastIndexOf(QChar::ObjectReplacementCharacter) - 1).toStdString()); + QCOMPARE(value["chain"].get_str(), std::string("regtest")); +} +} // namespace + +//! Entry point for BitcoinApplication tests. +void AppTests::appTests() +{ +#ifdef Q_OS_MAC + if (QApplication::platformName() == "minimal") { + // Disable for mac on "minimal" platform to avoid crashes inside the Qt + // framework when it tries to look up unimplemented cocoa functions, + // and fails to handle returned nulls + // (https://bugreports.qt.io/browse/QTBUG-49686). + QWARN("Skipping AppTests on mac build with 'minimal' platform set due to Qt bugs. To run AppTests, invoke " + "with 'test_bitcoin-qt -platform cocoa' on mac, or else use a linux or windows build."); + return; + } +#endif + + m_app.parameterSetup(); + m_app.createOptionsModel(true /* reset settings */); + QScopedPointer<const NetworkStyle> style( + NetworkStyle::instantiate(QString::fromStdString(Params().NetworkIDString()))); + m_app.setupPlatformStyle(); + m_app.createWindow(style.data()); + connect(&m_app, &BitcoinApplication::windowShown, this, &AppTests::guiTests); + expectCallback("guiTests"); + m_app.baseInitialize(); + m_app.requestInitialize(); + m_app.exec(); + m_app.requestShutdown(); + m_app.exec(); + + // Reset global state to avoid interfering with later tests. + AbortShutdown(); + UnloadBlockIndex(); +} + +//! Entry point for BitcoinGUI tests. +void AppTests::guiTests(BitcoinGUI* window) +{ + HandleCallback callback{"guiTests", *this}; + connect(window, &BitcoinGUI::consoleShown, this, &AppTests::consoleTests); + expectCallback("consoleTests"); + QAction* action = window->findChild<QAction*>("openRPCConsoleAction"); + action->activate(QAction::Trigger); +} + +//! Entry point for RPCConsole tests. +void AppTests::consoleTests(RPCConsole* console) +{ + HandleCallback callback{"consoleTests", *this}; + TestRpcCommand(console); +} + +//! Destructor to shut down after the last expected callback completes. +AppTests::HandleCallback::~HandleCallback() +{ + auto& callbacks = m_app_tests.m_callbacks; + auto it = callbacks.find(m_callback); + assert(it != callbacks.end()); + callbacks.erase(it); + if (callbacks.empty()) { + m_app_tests.m_app.quit(); + } +} diff --git a/src/qt/test/apptests.h b/src/qt/test/apptests.h new file mode 100644 index 0000000000..83bf56f1e4 --- /dev/null +++ b/src/qt/test/apptests.h @@ -0,0 +1,50 @@ +// Copyright (c) 2018 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_QT_TEST_APPTESTS_H +#define BITCOIN_QT_TEST_APPTESTS_H + +#include <QObject> +#include <set> +#include <string> +#include <utility> + +class BitcoinApplication; +class BitcoinGUI; +class RPCConsole; + +class AppTests : public QObject +{ + Q_OBJECT +public: + explicit AppTests(BitcoinApplication& app) : m_app(app) {} + +private Q_SLOTS: + void appTests(); + void guiTests(BitcoinGUI* window); + void consoleTests(RPCConsole* console); + +private: + //! Add expected callback name to list of pending callbacks. + void expectCallback(std::string callback) { m_callbacks.emplace(std::move(callback)); } + + //! RAII helper to remove no-longer-pending callback. + struct HandleCallback + { + std::string m_callback; + AppTests& m_app_tests; + ~HandleCallback(); + }; + + //! Bitcoin application. + BitcoinApplication& m_app; + + //! Set of pending callback names. Used to track expected callbacks and shut + //! down the app after the last callback has been handled and all tests have + //! either run or thrown exceptions. This could be a simple int counter + //! instead of a set of names, but the names might be useful for debugging. + std::multiset<std::string> m_callbacks; +}; + +#endif // BITCOIN_QT_TEST_APPTESTS_H diff --git a/src/qt/test/compattests.cpp b/src/qt/test/compattests.cpp index af5c69ea9a..6750c543da 100644 --- a/src/qt/test/compattests.cpp +++ b/src/qt/test/compattests.cpp @@ -2,7 +2,13 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. +#if defined(HAVE_CONFIG_H) +#include <config/bitcoin-config.h> +#endif + +#if defined(ENABLE_WALLET) && defined(ENABLE_BIP70) #include <qt/paymentrequestplus.h> // this includes protobuf's port.h which defines its own bswap macos +#endif #include <qt/test/compattests.h> diff --git a/src/qt/test/paymentservertests.cpp b/src/qt/test/paymentservertests.cpp index 5384b9e8b0..f0eca899fc 100644 --- a/src/qt/test/paymentservertests.cpp +++ b/src/qt/test/paymentservertests.cpp @@ -13,8 +13,8 @@ #include <random.h> #include <script/script.h> #include <script/standard.h> -#include <util.h> -#include <utilstrencodings.h> +#include <util/system.h> +#include <util/strencodings.h> #include <openssl/x509.h> #include <openssl/x509_vfy.h> @@ -181,12 +181,12 @@ void PaymentServerTests::paymentServerTests() QCOMPARE(PaymentServer::verifyExpired(r.paymentRequest.getDetails()), true); // Test BIP70 DoS protection: - unsigned char randData[BIP70_MAX_PAYMENTREQUEST_SIZE + 1]; - GetRandBytes(randData, sizeof(randData)); + auto randdata = FastRandomContext().randbytes(BIP70_MAX_PAYMENTREQUEST_SIZE + 1); + // Write data to a temp file: QTemporaryFile tempFile; tempFile.open(); - tempFile.write((const char*)randData, sizeof(randData)); + tempFile.write((const char*)randdata.data(), randdata.size()); tempFile.close(); // compares 50001 <= BIP70_MAX_PAYMENTREQUEST_SIZE == false QCOMPARE(PaymentServer::verifySize(tempFile.size()), false); diff --git a/src/qt/test/rpcnestedtests.cpp b/src/qt/test/rpcnestedtests.cpp index 2e321c1ba1..173c814f1e 100644 --- a/src/qt/test/rpcnestedtests.cpp +++ b/src/qt/test/rpcnestedtests.cpp @@ -14,7 +14,7 @@ #include <qt/rpcconsole.h> #include <test/test_bitcoin.h> #include <univalue.h> -#include <util.h> +#include <util/system.h> #include <QDir> #include <QtGlobal> @@ -41,7 +41,7 @@ void RPCNestedTests::rpcNestedTests() TestingSetup test; - SetRPCWarmupFinished(); + if (RPCIsInWarmup(nullptr)) SetRPCWarmupFinished(); std::string result; std::string result2; diff --git a/src/qt/test/test_main.cpp b/src/qt/test/test_main.cpp index df65a85fb5..a2bf53973b 100644 --- a/src/qt/test/test_main.cpp +++ b/src/qt/test/test_main.cpp @@ -7,16 +7,21 @@ #endif #include <chainparams.h> +#include <interfaces/node.h> +#include <qt/bitcoin.h> +#include <qt/test/apptests.h> #include <qt/test/rpcnestedtests.h> -#include <util.h> +#include <util/system.h> #include <qt/test/uritests.h> #include <qt/test/compattests.h> #ifdef ENABLE_WALLET #include <qt/test/addressbooktests.h> +#ifdef ENABLE_BIP70 #include <qt/test/paymentservertests.h> +#endif // ENABLE_BIP70 #include <qt/test/wallettests.h> -#endif +#endif // ENABLE_WALLET #include <QApplication> #include <QObject> @@ -45,12 +50,13 @@ int main(int argc, char *argv[]) { SetupEnvironment(); SetupNetworking(); - SelectParams(CBaseChainParams::MAIN); + SelectParams(CBaseChainParams::REGTEST); noui_connect(); ClearDatadirCache(); fs::path pathTemp = fs::temp_directory_path() / strprintf("test_bitcoin-qt_%lu_%i", (unsigned long)GetTime(), (int)GetRand(100000)); fs::create_directories(pathTemp); gArgs.ForceSetArg("-datadir", pathTemp.string()); + auto node = interfaces::MakeNode(); bool fInvalid = false; @@ -65,16 +71,20 @@ int main(int argc, char *argv[]) // Don't remove this, it's needed to access // QApplication:: and QCoreApplication:: in the tests - QApplication app(argc, argv); + BitcoinApplication app(*node, argc, argv); app.setApplicationName("Bitcoin-Qt-test"); SSL_library_init(); + AppTests app_tests(app); + if (QTest::qExec(&app_tests) != 0) { + fInvalid = true; + } URITests test1; if (QTest::qExec(&test1) != 0) { fInvalid = true; } -#ifdef ENABLE_WALLET +#if defined(ENABLE_WALLET) && defined(ENABLE_BIP70) PaymentServerTests test2; if (QTest::qExec(&test2) != 0) { fInvalid = true; diff --git a/src/qt/test/util.cpp b/src/qt/test/util.cpp index 0bd719326e..ae2fb93bf7 100644 --- a/src/qt/test/util.cpp +++ b/src/qt/test/util.cpp @@ -1,15 +1,13 @@ -#include <qt/callback.h> - #include <QApplication> #include <QMessageBox> -#include <QTimer> -#include <QString> #include <QPushButton> +#include <QString> +#include <QTimer> #include <QWidget> void ConfirmMessage(QString* text, int msec) { - QTimer::singleShot(msec, makeCallback([text](Callback* callback) { + QTimer::singleShot(msec, [text]() { for (QWidget* widget : QApplication::topLevelWidgets()) { if (widget->inherits("QMessageBox")) { QMessageBox* messageBox = qobject_cast<QMessageBox*>(widget); @@ -17,6 +15,5 @@ void ConfirmMessage(QString* text, int msec) messageBox->defaultButton()->click(); } } - delete callback; - }), &Callback::call); + }); } diff --git a/src/qt/test/util.h b/src/qt/test/util.h index 5363c94547..377f07dcba 100644 --- a/src/qt/test/util.h +++ b/src/qt/test/util.h @@ -1,6 +1,8 @@ #ifndef BITCOIN_QT_TEST_UTIL_H #define BITCOIN_QT_TEST_UTIL_H +#include <QString> + /** * Press "Ok" button in message box dialog. * diff --git a/src/qt/test/wallettests.cpp b/src/qt/test/wallettests.cpp index a0cfe8ae87..610d83acb6 100644 --- a/src/qt/test/wallettests.cpp +++ b/src/qt/test/wallettests.cpp @@ -1,9 +1,10 @@ #include <qt/test/wallettests.h> #include <qt/test/util.h> +#include <interfaces/chain.h> #include <interfaces/node.h> +#include <base58.h> #include <qt/bitcoinamountfield.h> -#include <qt/callback.h> #include <qt/optionsmodel.h> #include <qt/platformstyle.h> #include <qt/qvalidatedlineedit.h> @@ -39,7 +40,7 @@ namespace //! Press "Yes" or "Cancel" buttons in modal send confirmation dialog. void ConfirmSend(QString* text = nullptr, bool cancel = false) { - QTimer::singleShot(0, makeCallback([text, cancel](Callback* callback) { + QTimer::singleShot(0, [text, cancel]() { for (QWidget* widget : QApplication::topLevelWidgets()) { if (widget->inherits("SendConfirmationDialog")) { SendConfirmationDialog* dialog = qobject_cast<SendConfirmationDialog*>(widget); @@ -49,8 +50,7 @@ void ConfirmSend(QString* text = nullptr, bool cancel = false) button->click(); } } - delete callback; - }), &Callback::call); + }); } //! Send coins to address and return txid. @@ -133,7 +133,8 @@ void TestGUI() for (int i = 0; i < 5; ++i) { test.CreateAndProcessBlock({}, GetScriptForRawPubKey(test.coinbaseKey.GetPubKey())); } - std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>("mock", WalletDatabase::CreateMock()); + auto chain = interfaces::MakeChain(); + std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(*chain, WalletLocation(), WalletDatabase::CreateMock()); bool firstRun; wallet->LoadWallet(firstRun); { @@ -142,10 +143,16 @@ void TestGUI() wallet->AddKeyPubKey(test.coinbaseKey, test.coinbaseKey.GetPubKey()); } { - LOCK(cs_main); + auto locked_chain = wallet->chain().lock(); WalletRescanReserver reserver(wallet.get()); reserver.reserve(); - wallet->ScanForWalletTransactions(chainActive.Genesis(), nullptr, reserver, true); + const CBlockIndex* const null_block = nullptr; + const CBlockIndex *stop_block, *failed_block; + QCOMPARE( + wallet->ScanForWalletTransactions(chainActive.Genesis(), nullptr, reserver, failed_block, stop_block, true /* fUpdate */), + CWallet::ScanResult::SUCCESS); + QCOMPARE(stop_block, chainActive.Tip()); + QCOMPARE(failed_block, null_block); } wallet->SetBroadcastTransactions(true); @@ -243,5 +250,16 @@ void TestGUI() void WalletTests::walletTests() { +#ifdef Q_OS_MAC + if (QApplication::platformName() == "minimal") { + // Disable for mac on "minimal" platform to avoid crashes inside the Qt + // framework when it tries to look up unimplemented cocoa functions, + // and fails to handle returned nulls + // (https://bugreports.qt.io/browse/QTBUG-49686). + QWARN("Skipping WalletTests on mac build with 'minimal' platform set due to Qt bugs. To run AppTests, invoke " + "with 'test_bitcoin-qt -platform cocoa' on mac, or else use a linux or windows build."); + return; + } +#endif TestGUI(); } diff --git a/src/qt/trafficgraphwidget.cpp b/src/qt/trafficgraphwidget.cpp index 3aed3f2b97..1588be8da3 100644 --- a/src/qt/trafficgraphwidget.cpp +++ b/src/qt/trafficgraphwidget.cpp @@ -19,14 +19,14 @@ TrafficGraphWidget::TrafficGraphWidget(QWidget *parent) : QWidget(parent), - timer(0), + timer(nullptr), fMax(0.0f), nMins(0), vSamplesIn(), vSamplesOut(), nLastBytesIn(0), nLastBytesOut(0), - clientModel(0) + clientModel(nullptr) { timer = new QTimer(this); connect(timer, &QTimer::timeout, this, &TrafficGraphWidget::updateRates); diff --git a/src/qt/trafficgraphwidget.h b/src/qt/trafficgraphwidget.h index 00660574af..48bd246b34 100644 --- a/src/qt/trafficgraphwidget.h +++ b/src/qt/trafficgraphwidget.h @@ -20,7 +20,7 @@ class TrafficGraphWidget : public QWidget Q_OBJECT public: - explicit TrafficGraphWidget(QWidget *parent = 0); + explicit TrafficGraphWidget(QWidget *parent = nullptr); void setClientModel(ClientModel *model); int getGraphRangeMins() const; diff --git a/src/qt/transactiondesc.cpp b/src/qt/transactiondesc.cpp index 59332be754..0d070d9e87 100644 --- a/src/qt/transactiondesc.cpp +++ b/src/qt/transactiondesc.cpp @@ -2,6 +2,10 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. +#ifdef HAVE_CONFIG_H +#include <config/bitcoin-config.h> +#endif + #include <qt/transactiondesc.h> #include <qt/bitcoinunits.h> @@ -15,7 +19,7 @@ #include <validation.h> #include <script/script.h> #include <timedata.h> -#include <util.h> +#include <util/system.h> #include <wallet/db.h> #include <wallet/wallet.h> #include <policy/policy.h> @@ -23,7 +27,7 @@ #include <stdint.h> #include <string> -QString TransactionDesc::FormatTxStatus(const interfaces::WalletTx& wtx, const interfaces::WalletTxStatus& status, bool inMempool, int numBlocks, int64_t adjustedTime) +QString TransactionDesc::FormatTxStatus(const interfaces::WalletTx& wtx, const interfaces::WalletTxStatus& status, bool inMempool, int numBlocks) { if (!status.is_final) { @@ -49,11 +53,10 @@ QString TransactionDesc::FormatTxStatus(const interfaces::WalletTx& wtx, const i QString TransactionDesc::toHTML(interfaces::Node& node, interfaces::Wallet& wallet, TransactionRecord *rec, int unit) { int numBlocks; - int64_t adjustedTime; interfaces::WalletTxStatus status; interfaces::WalletOrderForm orderForm; bool inMempool; - interfaces::WalletTx wtx = wallet.getWalletTxDetails(rec->hash, status, orderForm, inMempool, numBlocks, adjustedTime); + interfaces::WalletTx wtx = wallet.getWalletTxDetails(rec->hash, status, orderForm, inMempool, numBlocks); QString strHTML; @@ -65,7 +68,7 @@ QString TransactionDesc::toHTML(interfaces::Node& node, interfaces::Wallet& wall CAmount nDebit = wtx.debit; CAmount nNet = nCredit - nDebit; - strHTML += "<b>" + tr("Status") + ":</b> " + FormatTxStatus(wtx, status, inMempool, numBlocks, adjustedTime); + strHTML += "<b>" + tr("Status") + ":</b> " + FormatTxStatus(wtx, status, inMempool, numBlocks); strHTML += "<br>"; strHTML += "<b>" + tr("Date") + ":</b> " + (nTime ? GUIUtil::dateTimeStr(nTime) : "") + "<br>"; @@ -257,6 +260,7 @@ QString TransactionDesc::toHTML(interfaces::Node& node, interfaces::Wallet& wall if (r.first == "Message") strHTML += "<br><b>" + tr("Message") + ":</b><br>" + GUIUtil::HtmlEscape(r.second, true) + "<br>"; +#ifdef ENABLE_BIP70 // // PaymentRequest info: // @@ -271,6 +275,7 @@ QString TransactionDesc::toHTML(interfaces::Node& node, interfaces::Wallet& wall strHTML += "<b>" + tr("Merchant") + ":</b> " + GUIUtil::HtmlEscape(merchant) + "<br>"; } } +#endif if (wtx.is_coinbase) { diff --git a/src/qt/transactiondesc.h b/src/qt/transactiondesc.h index 80eca7b97c..cf955a433c 100644 --- a/src/qt/transactiondesc.h +++ b/src/qt/transactiondesc.h @@ -29,7 +29,7 @@ public: private: TransactionDesc() {} - static QString FormatTxStatus(const interfaces::WalletTx& wtx, const interfaces::WalletTxStatus& status, bool inMempool, int numBlocks, int64_t adjustedTime); + static QString FormatTxStatus(const interfaces::WalletTx& wtx, const interfaces::WalletTxStatus& status, bool inMempool, int numBlocks); }; #endif // BITCOIN_QT_TRANSACTIONDESC_H diff --git a/src/qt/transactiondescdialog.h b/src/qt/transactiondescdialog.h index f1371b3856..8fd3f3166a 100644 --- a/src/qt/transactiondescdialog.h +++ b/src/qt/transactiondescdialog.h @@ -21,7 +21,7 @@ class TransactionDescDialog : public QDialog Q_OBJECT public: - explicit TransactionDescDialog(const QModelIndex &idx, QWidget *parent = 0); + explicit TransactionDescDialog(const QModelIndex &idx, QWidget *parent = nullptr); ~TransactionDescDialog(); private: diff --git a/src/qt/transactionfilterproxy.h b/src/qt/transactionfilterproxy.h index 9febd59820..685f8d3a26 100644 --- a/src/qt/transactionfilterproxy.h +++ b/src/qt/transactionfilterproxy.h @@ -16,7 +16,7 @@ class TransactionFilterProxy : public QSortFilterProxyModel Q_OBJECT public: - explicit TransactionFilterProxy(QObject *parent = 0); + explicit TransactionFilterProxy(QObject *parent = nullptr); /** Earliest date that can be represented (far in the past) */ static const QDateTime MIN_DATE; diff --git a/src/qt/transactionrecord.cpp b/src/qt/transactionrecord.cpp index d1a7527ac7..aa785553c8 100644 --- a/src/qt/transactionrecord.cpp +++ b/src/qt/transactionrecord.cpp @@ -4,6 +4,7 @@ #include <qt/transactionrecord.h> +#include <chain.h> #include <consensus/consensus.h> #include <interfaces/wallet.h> #include <key_io.h> @@ -12,6 +13,7 @@ #include <stdint.h> +#include <QDateTime> /* Return positive answer if transaction should be shown in list. */ @@ -158,7 +160,7 @@ QList<TransactionRecord> TransactionRecord::decomposeTransaction(const interface return parts; } -void TransactionRecord::updateStatus(const interfaces::WalletTxStatus& wtx, int numBlocks, int64_t adjustedTime) +void TransactionRecord::updateStatus(const interfaces::WalletTxStatus& wtx, int numBlocks, int64_t block_time) { // Determine transaction status @@ -172,10 +174,9 @@ void TransactionRecord::updateStatus(const interfaces::WalletTxStatus& wtx, int status.depth = wtx.depth_in_main_chain; status.cur_num_blocks = numBlocks; - if (!wtx.is_final) - { - if (wtx.lock_time < LOCKTIME_THRESHOLD) - { + const bool up_to_date = ((int64_t)QDateTime::currentMSecsSinceEpoch() / 1000 - block_time < MAX_BLOCK_TIME_GAP); + if (up_to_date && !wtx.is_final) { + if (wtx.lock_time < LOCKTIME_THRESHOLD) { status.status = TransactionStatus::OpenUntilBlock; status.open_for = wtx.lock_time - numBlocks; } diff --git a/src/qt/transactionrecord.h b/src/qt/transactionrecord.h index e187309d3f..3f64cefd09 100644 --- a/src/qt/transactionrecord.h +++ b/src/qt/transactionrecord.h @@ -138,7 +138,7 @@ public: /** Update status from core wallet tx. */ - void updateStatus(const interfaces::WalletTxStatus& wtx, int numBlocks, int64_t adjustedTime); + void updateStatus(const interfaces::WalletTxStatus& wtx, int numBlocks, int64_t block_time); /** Return whether a status update is needed. */ diff --git a/src/qt/transactiontablemodel.cpp b/src/qt/transactiontablemodel.cpp index b4be068904..631a9b891d 100644 --- a/src/qt/transactiontablemodel.cpp +++ b/src/qt/transactiontablemodel.cpp @@ -18,7 +18,7 @@ #include <interfaces/node.h> #include <sync.h> #include <uint256.h> -#include <util.h> +#include <util/system.h> #include <validation.h> #include <QColor> @@ -27,7 +27,6 @@ #include <QIcon> #include <QList> -#include <boost/bind.hpp> // Amount column is right-aligned it contains numbers static int column_alignments[] = { @@ -193,13 +192,13 @@ public: // simply re-use the cached status. interfaces::WalletTxStatus wtx; int numBlocks; - int64_t adjustedTime; - if (wallet.tryGetTxStatus(rec->hash, wtx, numBlocks, adjustedTime) && rec->statusUpdateNeeded(numBlocks)) { - rec->updateStatus(wtx, numBlocks, adjustedTime); + int64_t block_time; + if (wallet.tryGetTxStatus(rec->hash, wtx, numBlocks, block_time) && rec->statusUpdateNeeded(numBlocks)) { + rec->updateStatus(wtx, numBlocks, block_time); } return rec; } - return 0; + return nullptr; } QString describe(interfaces::Node& node, interfaces::Wallet& wallet, TransactionRecord *rec, int unit) @@ -746,8 +745,8 @@ static void ShowProgress(TransactionTableModel *ttm, const std::string &title, i void TransactionTableModel::subscribeToCoreSignals() { // Connect signals to wallet - m_handler_transaction_changed = walletModel->wallet().handleTransactionChanged(boost::bind(NotifyTransactionChanged, this, _1, _2)); - m_handler_show_progress = walletModel->wallet().handleShowProgress(boost::bind(ShowProgress, this, _1, _2)); + m_handler_transaction_changed = walletModel->wallet().handleTransactionChanged(std::bind(NotifyTransactionChanged, this, std::placeholders::_1, std::placeholders::_2)); + m_handler_show_progress = walletModel->wallet().handleShowProgress(std::bind(ShowProgress, this, std::placeholders::_1, std::placeholders::_2)); } void TransactionTableModel::unsubscribeFromCoreSignals() diff --git a/src/qt/transactiontablemodel.h b/src/qt/transactiontablemodel.h index 8be3a7ab2e..7a7d98962b 100644 --- a/src/qt/transactiontablemodel.h +++ b/src/qt/transactiontablemodel.h @@ -28,7 +28,7 @@ class TransactionTableModel : public QAbstractTableModel Q_OBJECT public: - explicit TransactionTableModel(const PlatformStyle *platformStyle, WalletModel *parent = 0); + explicit TransactionTableModel(const PlatformStyle *platformStyle, WalletModel *parent = nullptr); ~TransactionTableModel(); enum ColumnIndex { diff --git a/src/qt/transactionview.cpp b/src/qt/transactionview.cpp index 6d08a3b0fb..eb6437eb31 100644 --- a/src/qt/transactionview.cpp +++ b/src/qt/transactionview.cpp @@ -38,8 +38,8 @@ #include <QVBoxLayout> TransactionView::TransactionView(const PlatformStyle *platformStyle, QWidget *parent) : - QWidget(parent), model(0), transactionProxyModel(0), - transactionView(0), abandonAction(0), bumpFeeAction(0), columnResizingFixer(0) + QWidget(parent), model(nullptr), transactionProxyModel(nullptr), + transactionView(nullptr), abandonAction(nullptr), bumpFeeAction(nullptr), columnResizingFixer(nullptr) { // Build filter row setContentsMargins(0,0,0,0); @@ -106,7 +106,11 @@ TransactionView::TransactionView(const PlatformStyle *platformStyle, QWidget *pa } else { amountWidget->setFixedWidth(100); } - amountWidget->setValidator(new QDoubleValidator(0, 1e20, 8, this)); + QDoubleValidator *amountValidator = new QDoubleValidator(0, 1e20, 8, this); + QLocale amountLocale(QLocale::C); + amountLocale.setNumberOptions(QLocale::RejectGroupSeparator); + amountValidator->setLocale(amountLocale); + amountWidget->setValidator(amountValidator); hlayout->addWidget(amountWidget); // Delay before filtering transactions in ms diff --git a/src/qt/transactionview.h b/src/qt/transactionview.h index fdd8b069c7..e07181d1c8 100644 --- a/src/qt/transactionview.h +++ b/src/qt/transactionview.h @@ -35,7 +35,7 @@ class TransactionView : public QWidget Q_OBJECT public: - explicit TransactionView(const PlatformStyle *platformStyle, QWidget *parent = 0); + explicit TransactionView(const PlatformStyle *platformStyle, QWidget *parent = nullptr); void setModel(WalletModel *model); diff --git a/src/qt/utilitydialog.cpp b/src/qt/utilitydialog.cpp index 7d903b3b1c..b051dd159b 100644 --- a/src/qt/utilitydialog.cpp +++ b/src/qt/utilitydialog.cpp @@ -14,13 +14,16 @@ #include <qt/clientmodel.h> #include <qt/guiconstants.h> #include <qt/intro.h> +#ifdef ENABLE_BIP70 #include <qt/paymentrequestplus.h> +#endif #include <qt/guiutil.h> #include <clientversion.h> #include <init.h> #include <interfaces/node.h> -#include <util.h> +#include <util/system.h> +#include <util/strencodings.h> #include <stdio.h> @@ -52,9 +55,9 @@ HelpMessageDialog::HelpMessageDialog(interfaces::Node& node, QWidget *parent, bo { setWindowTitle(tr("About %1").arg(tr(PACKAGE_NAME))); + std::string licenseInfo = LicenseInfo(); /// HTML-format the license message from the core - QString licenseInfo = QString::fromStdString(LicenseInfo()); - QString licenseInfoHTML = licenseInfo; + QString licenseInfoHTML = QString::fromStdString(LicenseInfo()); // Make URLs clickable QRegExp uri("<(.*)>", Qt::CaseSensitive, QRegExp::RegExp2); uri.setMinimal(true); // use non-greedy matching @@ -64,7 +67,7 @@ HelpMessageDialog::HelpMessageDialog(interfaces::Node& node, QWidget *parent, bo ui->aboutMessage->setTextFormat(Qt::RichText); ui->scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); - text = version + "\n" + licenseInfo; + text = version + "\n" + QString::fromStdString(FormatParagraph(licenseInfo)); ui->aboutMessage->setText(version + "<br><br>" + licenseInfoHTML); ui->aboutMessage->setWordWrap(true); ui->helpMessage->setVisible(false); diff --git a/src/qt/utilitydialog.h b/src/qt/utilitydialog.h index ad1fda4573..f1cedff282 100644 --- a/src/qt/utilitydialog.h +++ b/src/qt/utilitydialog.h @@ -45,7 +45,7 @@ class ShutdownWindow : public QWidget Q_OBJECT public: - explicit ShutdownWindow(QWidget *parent=0, Qt::WindowFlags f=0); + explicit ShutdownWindow(QWidget *parent=nullptr, Qt::WindowFlags f=Qt::Widget); static QWidget *showShutdownWindow(BitcoinGUI *window); protected: diff --git a/src/qt/walletcontroller.cpp b/src/qt/walletcontroller.cpp new file mode 100644 index 0000000000..df2b7a3f9b --- /dev/null +++ b/src/qt/walletcontroller.cpp @@ -0,0 +1,95 @@ +// 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 <qt/walletcontroller.h> + +#include <interfaces/handler.h> +#include <interfaces/node.h> + +#include <algorithm> + +#include <QMutexLocker> +#include <QThread> + +WalletController::WalletController(interfaces::Node& node, const PlatformStyle* platform_style, OptionsModel* options_model, QObject* parent) + : QObject(parent) + , m_node(node) + , m_platform_style(platform_style) + , m_options_model(options_model) +{ + m_handler_load_wallet = m_node.handleLoadWallet([this](std::unique_ptr<interfaces::Wallet> wallet) { + getOrCreateWallet(std::move(wallet)); + }); + + for (std::unique_ptr<interfaces::Wallet>& wallet : m_node.getWallets()) { + getOrCreateWallet(std::move(wallet)); + } +} + +// Not using the default destructor because not all member types definitions are +// available in the header, just forward declared. +WalletController::~WalletController() {} + +std::vector<WalletModel*> WalletController::getWallets() const +{ + QMutexLocker locker(&m_mutex); + return m_wallets; +} + +WalletModel* WalletController::getOrCreateWallet(std::unique_ptr<interfaces::Wallet> wallet) +{ + QMutexLocker locker(&m_mutex); + + // Return model instance if exists. + if (!m_wallets.empty()) { + std::string name = wallet->getWalletName(); + for (WalletModel* wallet_model : m_wallets) { + if (wallet_model->wallet().getWalletName() == name) { + return wallet_model; + } + } + } + + // Instantiate model and register it. + WalletModel* wallet_model = new WalletModel(std::move(wallet), m_node, m_platform_style, m_options_model, nullptr); + m_wallets.push_back(wallet_model); + + connect(wallet_model, &WalletModel::unload, [this, wallet_model] { + removeAndDeleteWallet(wallet_model); + }); + + // Re-emit coinsSent signal from wallet model. + connect(wallet_model, &WalletModel::coinsSent, this, &WalletController::coinsSent); + + // Notify walletAdded signal on the GUI thread. + if (QThread::currentThread() == thread()) { + addWallet(wallet_model); + } 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)); + } + + return wallet_model; +} + +void WalletController::addWallet(WalletModel* wallet_model) +{ + // Take ownership of the wallet model and register it. + wallet_model->setParent(this); + Q_EMIT walletAdded(wallet_model); +} + +void WalletController::removeAndDeleteWallet(WalletModel* wallet_model) +{ + // Unregister wallet model. + { + QMutexLocker locker(&m_mutex); + m_wallets.erase(std::remove(m_wallets.begin(), m_wallets.end(), wallet_model)); + } + Q_EMIT walletRemoved(wallet_model); + // Currently this can trigger the unload since the model can hold the last + // CWallet shared pointer. + delete wallet_model; +} diff --git a/src/qt/walletcontroller.h b/src/qt/walletcontroller.h new file mode 100644 index 0000000000..22b71b07ff --- /dev/null +++ b/src/qt/walletcontroller.h @@ -0,0 +1,59 @@ +// Copyright (c) 2019 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_QT_WALLETCONTROLLER_H +#define BITCOIN_QT_WALLETCONTROLLER_H + +#include <qt/walletmodel.h> +#include <sync.h> + +#include <list> +#include <memory> +#include <vector> + +#include <QMutex> + +class OptionsModel; +class PlatformStyle; + +namespace interfaces { +class Handler; +class Node; +} // namespace interfaces + +/** + * Controller between interfaces::Node, WalletModel instances and the GUI. + */ +class WalletController : public QObject +{ + Q_OBJECT + + WalletModel* getOrCreateWallet(std::unique_ptr<interfaces::Wallet> wallet); + void removeAndDeleteWallet(WalletModel* wallet_model); + +public: + WalletController(interfaces::Node& node, const PlatformStyle* platform_style, OptionsModel* options_model, QObject* parent); + ~WalletController(); + + std::vector<WalletModel*> getWallets() const; + +private Q_SLOTS: + void addWallet(WalletModel* wallet_model); + +Q_SIGNALS: + void walletAdded(WalletModel* wallet_model); + void walletRemoved(WalletModel* wallet_model); + + void coinsSent(WalletModel* wallet_model, SendCoinsRecipient recipient, QByteArray transaction); + +private: + interfaces::Node& m_node; + const PlatformStyle* const m_platform_style; + OptionsModel* const m_options_model; + mutable QMutex m_mutex; + std::vector<WalletModel*> m_wallets; + std::unique_ptr<interfaces::Handler> m_handler_load_wallet; +}; + +#endif // BITCOIN_QT_WALLETCONTROLLER_H diff --git a/src/qt/walletframe.cpp b/src/qt/walletframe.cpp index d15bd95b8e..466f2278eb 100644 --- a/src/qt/walletframe.cpp +++ b/src/qt/walletframe.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. @@ -46,8 +46,7 @@ bool WalletFrame::addWallet(WalletModel *walletModel) return false; } - const QString name = walletModel->getWalletName(); - if (mapWalletViews.count(name) > 0) { + if (mapWalletViews.count(walletModel) > 0) { return false; } @@ -65,7 +64,7 @@ bool WalletFrame::addWallet(WalletModel *walletModel) } walletStack->addWidget(walletView); - mapWalletViews[name] = walletView; + mapWalletViews[walletModel] = walletView; // Ensure a walletView is able to show the main window connect(walletView, &WalletView::showNormalIfMinimized, [this]{ @@ -77,24 +76,24 @@ bool WalletFrame::addWallet(WalletModel *walletModel) return true; } -bool WalletFrame::setCurrentWallet(const QString& name) +bool WalletFrame::setCurrentWallet(WalletModel* wallet_model) { - if (mapWalletViews.count(name) == 0) + if (mapWalletViews.count(wallet_model) == 0) return false; - WalletView *walletView = mapWalletViews.value(name); + WalletView *walletView = mapWalletViews.value(wallet_model); walletStack->setCurrentWidget(walletView); assert(walletView); walletView->updateEncryptionStatus(); return true; } -bool WalletFrame::removeWallet(const QString &name) +bool WalletFrame::removeWallet(WalletModel* wallet_model) { - if (mapWalletViews.count(name) == 0) + if (mapWalletViews.count(wallet_model) == 0) return false; - WalletView *walletView = mapWalletViews.take(name); + WalletView *walletView = mapWalletViews.take(wallet_model); walletStack->removeWidget(walletView); delete walletView; return true; @@ -102,7 +101,7 @@ bool WalletFrame::removeWallet(const QString &name) void WalletFrame::removeAllWallets() { - QMap<QString, WalletView*>::const_iterator i; + QMap<WalletModel*, WalletView*>::const_iterator i; for (i = mapWalletViews.constBegin(); i != mapWalletViews.constEnd(); ++i) walletStack->removeWidget(i.value()); mapWalletViews.clear(); @@ -120,35 +119,35 @@ bool WalletFrame::handlePaymentRequest(const SendCoinsRecipient &recipient) void WalletFrame::showOutOfSyncWarning(bool fShow) { bOutOfSync = fShow; - QMap<QString, WalletView*>::const_iterator i; + QMap<WalletModel*, WalletView*>::const_iterator i; for (i = mapWalletViews.constBegin(); i != mapWalletViews.constEnd(); ++i) i.value()->showOutOfSyncWarning(fShow); } void WalletFrame::gotoOverviewPage() { - QMap<QString, WalletView*>::const_iterator i; + QMap<WalletModel*, WalletView*>::const_iterator i; for (i = mapWalletViews.constBegin(); i != mapWalletViews.constEnd(); ++i) i.value()->gotoOverviewPage(); } void WalletFrame::gotoHistoryPage() { - QMap<QString, WalletView*>::const_iterator i; + QMap<WalletModel*, WalletView*>::const_iterator i; for (i = mapWalletViews.constBegin(); i != mapWalletViews.constEnd(); ++i) i.value()->gotoHistoryPage(); } void WalletFrame::gotoReceiveCoinsPage() { - QMap<QString, WalletView*>::const_iterator i; + QMap<WalletModel*, WalletView*>::const_iterator i; for (i = mapWalletViews.constBegin(); i != mapWalletViews.constEnd(); ++i) i.value()->gotoReceiveCoinsPage(); } void WalletFrame::gotoSendCoinsPage(QString addr) { - QMap<QString, WalletView*>::const_iterator i; + QMap<WalletModel*, WalletView*>::const_iterator i; for (i = mapWalletViews.constBegin(); i != mapWalletViews.constEnd(); ++i) i.value()->gotoSendCoinsPage(addr); } @@ -209,11 +208,17 @@ void WalletFrame::usedReceivingAddresses() walletView->usedReceivingAddresses(); } -WalletView *WalletFrame::currentWalletView() +WalletView* WalletFrame::currentWalletView() const { return qobject_cast<WalletView*>(walletStack->currentWidget()); } +WalletModel* WalletFrame::currentWalletModel() const +{ + WalletView* wallet_view = currentWalletView(); + return wallet_view ? wallet_view->getWalletModel() : nullptr; +} + void WalletFrame::outOfSyncWarningClicked() { Q_EMIT requestedSyncWarningInfo(); diff --git a/src/qt/walletframe.h b/src/qt/walletframe.h index 05ca569d7a..6a74fde9fd 100644 --- a/src/qt/walletframe.h +++ b/src/qt/walletframe.h @@ -1,4 +1,4 @@ -// Copyright (c) 2011-2017 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. @@ -31,14 +31,14 @@ class WalletFrame : public QFrame Q_OBJECT public: - explicit WalletFrame(const PlatformStyle *platformStyle, BitcoinGUI *_gui = 0); + explicit WalletFrame(const PlatformStyle *platformStyle, BitcoinGUI *_gui = nullptr); ~WalletFrame(); void setClientModel(ClientModel *clientModel); bool addWallet(WalletModel *walletModel); - bool setCurrentWallet(const QString& name); - bool removeWallet(const QString &name); + bool setCurrentWallet(WalletModel* wallet_model); + bool removeWallet(WalletModel* wallet_model); void removeAllWallets(); bool handlePaymentRequest(const SendCoinsRecipient& recipient); @@ -53,14 +53,15 @@ private: QStackedWidget *walletStack; BitcoinGUI *gui; ClientModel *clientModel; - QMap<QString, WalletView*> mapWalletViews; + QMap<WalletModel*, WalletView*> mapWalletViews; bool bOutOfSync; const PlatformStyle *platformStyle; public: - WalletView *currentWalletView(); + WalletView* currentWalletView() const; + WalletModel* currentWalletModel() const; public Q_SLOTS: /** Switch to overview (home) page */ diff --git a/src/qt/walletmodel.cpp b/src/qt/walletmodel.cpp index f7cc94ae32..f139152042 100644 --- a/src/qt/walletmodel.cpp +++ b/src/qt/walletmodel.cpp @@ -1,7 +1,11 @@ -// 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. +#if defined(HAVE_CONFIG_H) +#include <config/bitcoin-config.h> +#endif + #include <qt/walletmodel.h> #include <qt/addresstablemodel.h> @@ -16,7 +20,7 @@ #include <interfaces/node.h> #include <key_io.h> #include <ui_interface.h> -#include <util.h> // for GetBoolArg +#include <util/system.h> // for GetBoolArg #include <wallet/coincontrol.h> #include <wallet/wallet.h> @@ -29,15 +33,13 @@ WalletModel::WalletModel(std::unique_ptr<interfaces::Wallet> wallet, interfaces::Node& node, const PlatformStyle *platformStyle, OptionsModel *_optionsModel, QObject *parent) : - QObject(parent), m_wallet(std::move(wallet)), m_node(node), optionsModel(_optionsModel), addressTableModel(0), - transactionTableModel(0), - recentRequestsTableModel(0), + QObject(parent), m_wallet(std::move(wallet)), m_node(node), optionsModel(_optionsModel), addressTableModel(nullptr), + transactionTableModel(nullptr), + recentRequestsTableModel(nullptr), cachedEncryptionStatus(Unencrypted), cachedNumBlocks(0) { fHaveWatchOnly = m_wallet->haveWatchOnly(); - fForceCheckBalanceChanged = false; - addressTableModel = new AddressTableModel(this); transactionTableModel = new TransactionTableModel(platformStyle, this); recentRequestsTableModel = new RecentRequestsTableModel(this); @@ -142,6 +144,7 @@ WalletModel::SendCoinsReturn WalletModel::prepareTransaction(WalletModelTransact if (rcp.fSubtractFeeFromAmount) fSubtractFeeFromAmount = true; +#ifdef ENABLE_BIP70 if (rcp.paymentRequest.IsInitialized()) { // PaymentRequest... CAmount subtotal = 0; @@ -164,6 +167,7 @@ WalletModel::SendCoinsReturn WalletModel::prepareTransaction(WalletModelTransact total += subtotal; } else +#endif { // User-entered bitcoin address / amount: if(!validateAddress(rcp.address)) { @@ -235,6 +239,7 @@ WalletModel::SendCoinsReturn WalletModel::sendCoins(WalletModelTransaction &tran std::vector<std::pair<std::string, std::string>> vOrderForm; for (const SendCoinsRecipient &rcp : transaction.getRecipients()) { +#ifdef ENABLE_BIP70 if (rcp.paymentRequest.IsInitialized()) { // Make sure any payment requests involved are still valid. @@ -247,7 +252,9 @@ WalletModel::SendCoinsReturn WalletModel::sendCoins(WalletModelTransaction &tran rcp.paymentRequest.SerializeToString(&value); vOrderForm.emplace_back("PaymentRequest", std::move(value)); } - else if (!rcp.message.isEmpty()) // Message from normal bitcoin:URI (bitcoin:123...?message=example) + else +#endif + if (!rcp.message.isEmpty()) // Message from normal bitcoin:URI (bitcoin:123...?message=example) vOrderForm.emplace_back("Message", rcp.message.toStdString()); } @@ -266,7 +273,9 @@ WalletModel::SendCoinsReturn WalletModel::sendCoins(WalletModelTransaction &tran for (const SendCoinsRecipient &rcp : transaction.getRecipients()) { // Don't touch the address book when we have a payment request +#ifdef ENABLE_BIP70 if (!rcp.paymentRequest.IsInitialized()) +#endif { std::string strAddress = rcp.address.toStdString(); CTxDestination dest = DecodeDestination(strAddress); @@ -367,7 +376,7 @@ bool WalletModel::changePassphrase(const SecureString &oldPass, const SecureStri static void NotifyUnload(WalletModel* walletModel) { qDebug() << "NotifyUnload"; - QMetaObject::invokeMethod(walletModel, "unload", Qt::QueuedConnection); + QMetaObject::invokeMethod(walletModel, "unload"); } static void NotifyKeyStoreStatusChanged(WalletModel *walletmodel) @@ -417,12 +426,12 @@ static void NotifyWatchonlyChanged(WalletModel *walletmodel, bool fHaveWatchonly void WalletModel::subscribeToCoreSignals() { // Connect signals to wallet - m_handler_unload = m_wallet->handleUnload(boost::bind(&NotifyUnload, this)); - m_handler_status_changed = m_wallet->handleStatusChanged(boost::bind(&NotifyKeyStoreStatusChanged, this)); - m_handler_address_book_changed = m_wallet->handleAddressBookChanged(boost::bind(NotifyAddressBookChanged, this, _1, _2, _3, _4, _5)); - m_handler_transaction_changed = m_wallet->handleTransactionChanged(boost::bind(NotifyTransactionChanged, this, _1, _2)); - m_handler_show_progress = m_wallet->handleShowProgress(boost::bind(ShowProgress, this, _1, _2)); - m_handler_watch_only_changed = m_wallet->handleWatchOnlyChanged(boost::bind(NotifyWatchonlyChanged, this, _1)); + m_handler_unload = m_wallet->handleUnload(std::bind(&NotifyUnload, this)); + m_handler_status_changed = m_wallet->handleStatusChanged(std::bind(&NotifyKeyStoreStatusChanged, this)); + m_handler_address_book_changed = m_wallet->handleAddressBookChanged(std::bind(NotifyAddressBookChanged, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5)); + m_handler_transaction_changed = m_wallet->handleTransactionChanged(std::bind(NotifyTransactionChanged, this, std::placeholders::_1, std::placeholders::_2)); + m_handler_show_progress = m_wallet->handleShowProgress(std::bind(ShowProgress, this, std::placeholders::_1, std::placeholders::_2)); + m_handler_watch_only_changed = m_wallet->handleWatchOnlyChanged(std::bind(NotifyWatchonlyChanged, this, std::placeholders::_1)); } void WalletModel::unsubscribeFromCoreSignals() @@ -501,7 +510,7 @@ bool WalletModel::bumpFee(uint256 hash, uint256& new_hash) CAmount new_fee; CMutableTransaction mtx; if (!m_wallet->createBumpTransaction(hash, coin_control, 0 /* totalFee */, errors, old_fee, new_fee, mtx)) { - QMessageBox::critical(0, tr("Fee bump error"), tr("Increasing transaction fee failed") + "<br />(" + + QMessageBox::critical(nullptr, tr("Fee bump error"), tr("Increasing transaction fee failed") + "<br />(" + (errors.size() ? QString::fromStdString(errors[0]) : "") +")"); return false; } @@ -540,12 +549,12 @@ bool WalletModel::bumpFee(uint256 hash, uint256& new_hash) // sign bumped transaction if (!m_wallet->signBumpTransaction(mtx)) { - QMessageBox::critical(0, tr("Fee bump error"), tr("Can't sign transaction.")); + QMessageBox::critical(nullptr, tr("Fee bump error"), tr("Can't sign transaction.")); return false; } // commit the bumped transaction if(!m_wallet->commitBumpTransaction(hash, std::move(mtx), errors, new_hash)) { - QMessageBox::critical(0, tr("Fee bump error"), tr("Could not commit transaction") + "<br />(" + + QMessageBox::critical(nullptr, tr("Fee bump error"), tr("Could not commit transaction") + "<br />(" + QString::fromStdString(errors[0])+")"); return false; } @@ -567,6 +576,12 @@ QString WalletModel::getWalletName() const return QString::fromStdString(m_wallet->getWalletName()); } +QString WalletModel::getDisplayName() const +{ + const QString name = getWalletName(); + return name.isEmpty() ? "["+tr("default wallet")+"]" : name; +} + bool WalletModel::isMultiwallet() { return m_node.getWallets().size() > 1; diff --git a/src/qt/walletmodel.h b/src/qt/walletmodel.h index b22728c69b..c3c8f36909 100644 --- a/src/qt/walletmodel.h +++ b/src/qt/walletmodel.h @@ -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. @@ -10,7 +10,13 @@ #include <serialize.h> #include <script/standard.h> +#if defined(HAVE_CONFIG_H) +#include <config/bitcoin-config.h> +#endif + +#ifdef ENABLE_BIP70 #include <qt/paymentrequestplus.h> +#endif #include <qt/walletmodeltransaction.h> #include <interfaces/wallet.h> @@ -63,8 +69,14 @@ public: // If from a payment request, this is used for storing the memo QString message; +#ifdef ENABLE_BIP70 // If from a payment request, paymentRequest.IsInitialized() will be true PaymentRequestPlus paymentRequest; +#else + // If building with BIP70 is disabled, keep the payment request around as + // serialized string to ensure load/store is lossless + std::string sPaymentRequest; +#endif // Empty if no authentication or invalid signature/cert/etc. QString authenticatedMerchant; @@ -80,9 +92,11 @@ public: std::string sAddress = address.toStdString(); std::string sLabel = label.toStdString(); std::string sMessage = message.toStdString(); +#ifdef ENABLE_BIP70 std::string sPaymentRequest; if (!ser_action.ForRead() && paymentRequest.IsInitialized()) paymentRequest.SerializeToString(&sPaymentRequest); +#endif std::string sAuthenticatedMerchant = authenticatedMerchant.toStdString(); READWRITE(this->nVersion); @@ -98,8 +112,10 @@ public: address = QString::fromStdString(sAddress); label = QString::fromStdString(sLabel); message = QString::fromStdString(sMessage); +#ifdef ENABLE_BIP70 if (!sPaymentRequest.empty()) paymentRequest.parse(QByteArray::fromRawData(sPaymentRequest.data(), sPaymentRequest.size())); +#endif authenticatedMerchant = QString::fromStdString(sAuthenticatedMerchant); } } @@ -111,7 +127,7 @@ class WalletModel : public QObject Q_OBJECT public: - explicit WalletModel(std::unique_ptr<interfaces::Wallet> wallet, interfaces::Node& node, const PlatformStyle *platformStyle, OptionsModel *optionsModel, QObject *parent = 0); + explicit WalletModel(std::unique_ptr<interfaces::Wallet> wallet, interfaces::Node& node, const PlatformStyle *platformStyle, OptionsModel *optionsModel, QObject *parent = nullptr); ~WalletModel(); enum StatusCode // Returned by sendCoins @@ -203,6 +219,7 @@ public: interfaces::Wallet& wallet() const { return *m_wallet; } QString getWalletName() const; + QString getDisplayName() const; bool isMultiwallet(); @@ -218,7 +235,7 @@ private: interfaces::Node& m_node; bool fHaveWatchOnly; - bool fForceCheckBalanceChanged; + bool fForceCheckBalanceChanged{false}; // Wallet has an options model for wallet-specific options // (transaction fee, for example) diff --git a/src/qt/walletmodeltransaction.cpp b/src/qt/walletmodeltransaction.cpp index de50616499..eb3b0baf08 100644 --- a/src/qt/walletmodeltransaction.cpp +++ b/src/qt/walletmodeltransaction.cpp @@ -2,6 +2,10 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. +#ifdef HAVE_CONFIG_H +#include <config/bitcoin-config.h> +#endif + #include <qt/walletmodeltransaction.h> #include <interfaces/node.h> @@ -46,6 +50,7 @@ void WalletModelTransaction::reassignAmounts(int nChangePosRet) { SendCoinsRecipient& rcp = (*it); +#ifdef ENABLE_BIP70 if (rcp.paymentRequest.IsInitialized()) { CAmount subtotal = 0; @@ -62,6 +67,7 @@ void WalletModelTransaction::reassignAmounts(int nChangePosRet) rcp.amount = subtotal; } else // normal recipient (no payment request) +#endif { if (i == nChangePosRet) i++; diff --git a/src/qt/walletmodeltransaction.h b/src/qt/walletmodeltransaction.h index 75ede2e2a1..289aee847b 100644 --- a/src/qt/walletmodeltransaction.h +++ b/src/qt/walletmodeltransaction.h @@ -8,6 +8,7 @@ #include <qt/walletmodel.h> #include <memory> +#include <amount.h> #include <QObject> diff --git a/src/qt/walletview.cpp b/src/qt/walletview.cpp index 053e951921..5f6f93d948 100644 --- a/src/qt/walletview.cpp +++ b/src/qt/walletview.cpp @@ -32,8 +32,8 @@ WalletView::WalletView(const PlatformStyle *_platformStyle, QWidget *parent): QStackedWidget(parent), - clientModel(0), - walletModel(0), + clientModel(nullptr), + walletModel(nullptr), platformStyle(_platformStyle) { // Create tabs @@ -292,9 +292,7 @@ void WalletView::usedSendingAddresses() if(!walletModel) return; - usedSendingAddressesPage->show(); - usedSendingAddressesPage->raise(); - usedSendingAddressesPage->activateWindow(); + GUIUtil::bringToFront(usedSendingAddressesPage); } void WalletView::usedReceivingAddresses() @@ -302,31 +300,24 @@ void WalletView::usedReceivingAddresses() if(!walletModel) return; - usedReceivingAddressesPage->show(); - usedReceivingAddressesPage->raise(); - usedReceivingAddressesPage->activateWindow(); + GUIUtil::bringToFront(usedReceivingAddressesPage); } void WalletView::showProgress(const QString &title, int nProgress) { - if (nProgress == 0) - { - progressDialog = new QProgressDialog(title, "", 0, 100); + if (nProgress == 0) { + progressDialog = new QProgressDialog(title, tr("Cancel"), 0, 100); + GUIUtil::PolishProgressDialog(progressDialog); progressDialog->setWindowModality(Qt::ApplicationModal); progressDialog->setMinimumDuration(0); progressDialog->setAutoClose(false); progressDialog->setValue(0); - progressDialog->setCancelButtonText(tr("Cancel")); - } - else if (nProgress == 100) - { - if (progressDialog) - { + } else if (nProgress == 100) { + if (progressDialog) { progressDialog->close(); progressDialog->deleteLater(); } - } - else if (progressDialog) { + } else if (progressDialog) { if (progressDialog->wasCanceled()) { getWalletModel()->wallet().abortRescan(); } else { diff --git a/src/qt/winshutdownmonitor.cpp b/src/qt/winshutdownmonitor.cpp index d732326b33..08cae76add 100644 --- a/src/qt/winshutdownmonitor.cpp +++ b/src/qt/winshutdownmonitor.cpp @@ -6,7 +6,7 @@ #if defined(Q_OS_WIN) #include <shutdown.h> -#include <util.h> +#include <util/system.h> #include <windows.h> diff --git a/src/random.cpp b/src/random.cpp index 503d5b3636..3b7f7910b0 100644 --- a/src/random.cpp +++ b/src/random.cpp @@ -13,12 +13,14 @@ #endif #include <logging.h> // for LogPrint() #include <sync.h> // for WAIT_LOCK -#include <utiltime.h> // for GetTime() +#include <util/time.h> // for GetTime() #include <stdlib.h> #include <chrono> #include <thread> +#include <support/allocators/secure.h> + #ifndef WIN32 #include <fcntl.h> #include <sys/time.h> @@ -35,7 +37,7 @@ #include <sys/random.h> #endif #ifdef HAVE_SYSCTL_ARND -#include <utilstrencodings.h> // for ARRAYLEN +#include <util/strencodings.h> // for ARRAYLEN #include <sys/sysctl.h> #endif @@ -47,6 +49,7 @@ #include <openssl/err.h> #include <openssl/rand.h> +#include <openssl/conf.h> [[noreturn]] static void RandFailure() { @@ -54,7 +57,7 @@ std::abort(); } -static inline int64_t GetPerformanceCounter() +static inline int64_t GetPerformanceCounter() noexcept { // Read the hardware time stamp counter when available. // See https://en.wikipedia.org/wiki/Time_Stamp_Counter for more information. @@ -74,27 +77,38 @@ static inline int64_t GetPerformanceCounter() #endif } - #if defined(__x86_64__) || defined(__amd64__) || defined(__i386__) -static std::atomic<bool> hwrand_initialized{false}; static bool rdrand_supported = false; static constexpr uint32_t CPUID_F1_ECX_RDRAND = 0x40000000; -static void RDRandInit() +static void InitHardwareRand() { uint32_t eax, ebx, ecx, edx; if (__get_cpuid(1, &eax, &ebx, &ecx, &edx) && (ecx & CPUID_F1_ECX_RDRAND)) { - LogPrintf("Using RdRand as an additional entropy source\n"); rdrand_supported = true; } - hwrand_initialized.store(true); } + +static void ReportHardwareRand() +{ + if (rdrand_supported) { + // This must be done in a separate function, as HWRandInit() may be indirectly called + // from global constructors, before logging is initialized. + LogPrintf("Using RdRand as an additional entropy source\n"); + } +} + #else -static void RDRandInit() {} +/* Access to other hardware random number generators could be added here later, + * assuming it is sufficiently fast (in the order of a few hundred CPU cycles). + * Slower sources should probably be invoked separately, and/or only from + * RandAddSeedSleep (which is called during idle background operation). + */ +static void InitHardwareRand() {} +static void ReportHardwareRand() {} #endif -static bool GetHWRand(unsigned char* ent32) { +static bool GetHardwareRand(unsigned char* ent32) noexcept { #if defined(__x86_64__) || defined(__amd64__) || defined(__i386__) - assert(hwrand_initialized.load(std::memory_order_relaxed)); if (rdrand_supported) { uint8_t ok; // Not all assemblers support the rdrand instruction, write it in hex. @@ -129,18 +143,8 @@ static bool GetHWRand(unsigned char* ent32) { return false; } -void RandAddSeed() +static void RandAddSeedPerfmon(CSHA512& hasher) { - // Seed with CPU performance counter - int64_t nCounter = GetPerformanceCounter(); - RAND_add(&nCounter, sizeof(nCounter), 1.5); - memory_cleanse((void*)&nCounter, sizeof(nCounter)); -} - -static void RandAddSeedPerfmon() -{ - RandAddSeed(); - #ifdef WIN32 // Don't need this on Linux, OpenSSL automatically uses /dev/urandom // Seed with the entire set of perfmon data @@ -164,15 +168,15 @@ static void RandAddSeedPerfmon() } RegCloseKey(HKEY_PERFORMANCE_DATA); if (ret == ERROR_SUCCESS) { - RAND_add(vData.data(), nSize, nSize / 100.0); + hasher.Write(vData.data(), nSize); memory_cleanse(vData.data(), nSize); - LogPrint(BCLog::RAND, "%s: %lu bytes\n", __func__, nSize); } else { - static bool warned = false; // Warn only once - if (!warned) { - LogPrintf("%s: Warning: RegQueryValueExA(HKEY_PERFORMANCE_DATA) failed with code %i\n", __func__, ret); - warned = true; - } + // Performance data is only a best-effort attempt at improving the + // situation when the OS randomness (and other sources) aren't + // adequate. As a result, failure to read it is isn't considered critical, + // so we don't call RandFailure(). + // TODO: Add logging when the logger is made functional before global + // constructors have been invoked. } #endif } @@ -272,106 +276,255 @@ void GetOSRand(unsigned char *ent32) #endif } -void GetRandBytes(unsigned char* buf, int num) +void LockingCallbackOpenSSL(int mode, int i, const char* file, int line); + +namespace { + +class RNGState { + Mutex m_mutex; + /* The RNG state consists of 256 bits of entropy, taken from the output of + * one operation's SHA512 output, and fed as input to the next one. + * Carrying 256 bits of entropy should be sufficient to guarantee + * unpredictability as long as any entropy source was ever unpredictable + * to an attacker. To protect against situations where an attacker might + * observe the RNG's state, fresh entropy is always mixed when + * GetStrongRandBytes is called. + */ + unsigned char m_state[32] GUARDED_BY(m_mutex) = {0}; + uint64_t m_counter GUARDED_BY(m_mutex) = 0; + bool m_strongly_seeded GUARDED_BY(m_mutex) = false; + std::unique_ptr<Mutex[]> m_mutex_openssl; + +public: + RNGState() noexcept + { + InitHardwareRand(); + + // Init OpenSSL library multithreading support + m_mutex_openssl.reset(new Mutex[CRYPTO_num_locks()]); + CRYPTO_set_locking_callback(LockingCallbackOpenSSL); + + // OpenSSL can optionally load a config file which lists optional loadable modules and engines. + // We don't use them so we don't require the config. However some of our libs may call functions + // which attempt to load the config file, possibly resulting in an exit() or crash if it is missing + // or corrupt. Explicitly tell OpenSSL not to try to load the file. The result for our libs will be + // that the config appears to have been loaded and there are no modules/engines available. + OPENSSL_no_config(); + } + + ~RNGState() + { + // Securely erase the memory used by the OpenSSL PRNG + RAND_cleanup(); + // Shutdown OpenSSL library multithreading support + CRYPTO_set_locking_callback(nullptr); + } + + /** Extract up to 32 bytes of entropy from the RNG state, mixing in new entropy from hasher. + * + * If this function has never been called with strong_seed = true, false is returned. + */ + bool MixExtract(unsigned char* out, size_t num, CSHA512&& hasher, bool strong_seed) noexcept + { + assert(num <= 32); + unsigned char buf[64]; + static_assert(sizeof(buf) == CSHA512::OUTPUT_SIZE, "Buffer needs to have hasher's output size"); + bool ret; + { + LOCK(m_mutex); + ret = (m_strongly_seeded |= strong_seed); + // Write the current state of the RNG into the hasher + hasher.Write(m_state, 32); + // Write a new counter number into the state + hasher.Write((const unsigned char*)&m_counter, sizeof(m_counter)); + ++m_counter; + // Finalize the hasher + hasher.Finalize(buf); + // Store the last 32 bytes of the hash output as new RNG state. + memcpy(m_state, buf + 32, 32); + } + // If desired, copy (up to) the first 32 bytes of the hash output as output. + if (num) { + assert(out != nullptr); + memcpy(out, buf, num); + } + // Best effort cleanup of internal state + hasher.Reset(); + memory_cleanse(buf, 64); + return ret; + } + + Mutex& GetOpenSSLMutex(int i) { return m_mutex_openssl[i]; } +}; + +RNGState& GetRNGState() noexcept +{ + // This C++11 idiom relies on the guarantee that static variable are initialized + // on first call, even when multiple parallel calls are permitted. + static std::vector<RNGState, secure_allocator<RNGState>> g_rng(1); + return g_rng[0]; +} +} + +void LockingCallbackOpenSSL(int mode, int i, const char* file, int line) NO_THREAD_SAFETY_ANALYSIS { - if (RAND_bytes(buf, num) != 1) { - RandFailure(); + RNGState& rng = GetRNGState(); + + if (mode & CRYPTO_LOCK) { + rng.GetOpenSSLMutex(i).lock(); + } else { + rng.GetOpenSSLMutex(i).unlock(); } } -static void AddDataToRng(void* data, size_t len); +/* A note on the use of noexcept in the seeding functions below: + * + * None of the RNG code should ever throw any exception, with the sole exception + * of MilliSleep in SeedSleep, which can (and does) support interruptions which + * cause a boost::thread_interrupted to be thrown. + * + * This means that SeedSleep, and all functions that invoke it are throwing. + * However, we know that GetRandBytes() and GetStrongRandBytes() never trigger + * this sleeping logic, so they are noexcept. The same is true for all the + * GetRand*() functions that use GetRandBytes() indirectly. + * + * TODO: After moving away from interruptible boost-based thread management, + * everything can become noexcept here. + */ -void RandAddSeedSleep() +static void SeedTimestamp(CSHA512& hasher) noexcept { - int64_t nPerfCounter1 = GetPerformanceCounter(); - std::this_thread::sleep_for(std::chrono::milliseconds(1)); - int64_t nPerfCounter2 = GetPerformanceCounter(); + int64_t perfcounter = GetPerformanceCounter(); + hasher.Write((const unsigned char*)&perfcounter, sizeof(perfcounter)); +} - // Combine with and update state - AddDataToRng(&nPerfCounter1, sizeof(nPerfCounter1)); - AddDataToRng(&nPerfCounter2, sizeof(nPerfCounter2)); +static void SeedFast(CSHA512& hasher) noexcept +{ + unsigned char buffer[32]; - memory_cleanse(&nPerfCounter1, sizeof(nPerfCounter1)); - memory_cleanse(&nPerfCounter2, sizeof(nPerfCounter2)); + // Stack pointer to indirectly commit to thread/callstack + const unsigned char* ptr = buffer; + hasher.Write((const unsigned char*)&ptr, sizeof(ptr)); + + // Hardware randomness is very fast when available; use it always. + bool have_hw_rand = GetHardwareRand(buffer); + if (have_hw_rand) hasher.Write(buffer, sizeof(buffer)); + + // High-precision timestamp + SeedTimestamp(hasher); } +static void SeedSlow(CSHA512& hasher) noexcept +{ + unsigned char buffer[32]; -static Mutex cs_rng_state; -static unsigned char rng_state[32] = {0}; -static uint64_t rng_counter = 0; + // Everything that the 'fast' seeder includes + SeedFast(hasher); -static void AddDataToRng(void* data, size_t len) { - CSHA512 hasher; - hasher.Write((const unsigned char*)&len, sizeof(len)); - hasher.Write((const unsigned char*)data, len); - unsigned char buf[64]; - { - WAIT_LOCK(cs_rng_state, lock); - hasher.Write(rng_state, sizeof(rng_state)); - hasher.Write((const unsigned char*)&rng_counter, sizeof(rng_counter)); - ++rng_counter; - hasher.Finalize(buf); - memcpy(rng_state, buf + 32, 32); - } - memory_cleanse(buf, 64); + // OS randomness + GetOSRand(buffer); + hasher.Write(buffer, sizeof(buffer)); + + // OpenSSL RNG (for now) + RAND_bytes(buffer, sizeof(buffer)); + hasher.Write(buffer, sizeof(buffer)); + + // High-precision timestamp. + // + // Note that we also commit to a timestamp in the Fast seeder, so we indirectly commit to a + // benchmark of all the entropy gathering sources in this function). + SeedTimestamp(hasher); } -void GetStrongRandBytes(unsigned char* out, int num) +static void SeedSleep(CSHA512& hasher) { - assert(num <= 32); - CSHA512 hasher; - unsigned char buf[64]; + // Everything that the 'fast' seeder includes + SeedFast(hasher); + + // High-precision timestamp + SeedTimestamp(hasher); + + // Sleep for 1ms + MilliSleep(1); + + // High-precision timestamp after sleeping (as we commit to both the time before and after, this measures the delay) + SeedTimestamp(hasher); + + // Windows performance monitor data (once every 10 minutes) + RandAddSeedPerfmon(hasher); +} - // First source: OpenSSL's RNG - RandAddSeedPerfmon(); - GetRandBytes(buf, 32); - hasher.Write(buf, 32); +static void SeedStartup(CSHA512& hasher) noexcept +{ +#ifdef WIN32 + RAND_screen(); +#endif + + // Everything that the 'slow' seeder includes. + SeedSlow(hasher); + + // Windows performance monitor data. + RandAddSeedPerfmon(hasher); +} + +enum class RNGLevel { + FAST, //!< Automatically called by GetRandBytes + SLOW, //!< Automatically called by GetStrongRandBytes + SLEEP, //!< Called by RandAddSeedSleep() +}; - // Second source: OS RNG - GetOSRand(buf); - hasher.Write(buf, 32); +static void ProcRand(unsigned char* out, int num, RNGLevel level) +{ + // Make sure the RNG is initialized first (as all Seed* function possibly need hwrand to be available). + RNGState& rng = GetRNGState(); + + assert(num <= 32); - // Third source: HW RNG, if available. - if (GetHWRand(buf)) { - hasher.Write(buf, 32); + CSHA512 hasher; + switch (level) { + case RNGLevel::FAST: + SeedFast(hasher); + break; + case RNGLevel::SLOW: + SeedSlow(hasher); + break; + case RNGLevel::SLEEP: + SeedSleep(hasher); + break; } // Combine with and update state - { - WAIT_LOCK(cs_rng_state, lock); - hasher.Write(rng_state, sizeof(rng_state)); - hasher.Write((const unsigned char*)&rng_counter, sizeof(rng_counter)); - ++rng_counter; - hasher.Finalize(buf); - memcpy(rng_state, buf + 32, 32); + if (!rng.MixExtract(out, num, std::move(hasher), false)) { + // On the first invocation, also seed with SeedStartup(). + CSHA512 startup_hasher; + SeedStartup(startup_hasher); + rng.MixExtract(out, num, std::move(startup_hasher), true); } - // Produce output - memcpy(out, buf, num); - memory_cleanse(buf, 64); + // For anything but the 'fast' level, feed the resulting RNG output (after an additional hashing step) back into OpenSSL. + if (level != RNGLevel::FAST) { + unsigned char buf[64]; + CSHA512().Write(out, num).Finalize(buf); + RAND_add(buf, sizeof(buf), num); + memory_cleanse(buf, 64); + } } -uint64_t GetRand(uint64_t nMax) -{ - if (nMax == 0) - return 0; +void GetRandBytes(unsigned char* buf, int num) noexcept { ProcRand(buf, num, RNGLevel::FAST); } +void GetStrongRandBytes(unsigned char* buf, int num) noexcept { ProcRand(buf, num, RNGLevel::SLOW); } +void RandAddSeedSleep() { ProcRand(nullptr, 0, RNGLevel::SLEEP); } - // The range of the random source must be a multiple of the modulus - // to give every possible output value an equal possibility - uint64_t nRange = (std::numeric_limits<uint64_t>::max() / nMax) * nMax; - uint64_t nRand = 0; - do { - GetRandBytes((unsigned char*)&nRand, sizeof(nRand)); - } while (nRand >= nRange); - return (nRand % nMax); +uint64_t GetRand(uint64_t nMax) noexcept +{ + return FastRandomContext().randrange(nMax); } -int GetRandInt(int nMax) +int GetRandInt(int nMax) noexcept { return GetRand(nMax); } -uint256 GetRandHash() +uint256 GetRandHash() noexcept { uint256 hash; GetRandBytes((unsigned char*)&hash, sizeof(hash)); @@ -385,7 +538,7 @@ void FastRandomContext::RandomSeed() requires_seed = false; } -uint256 FastRandomContext::rand256() +uint256 FastRandomContext::rand256() noexcept { if (bytebuf_size < 32) { FillByteBuffer(); @@ -398,6 +551,7 @@ uint256 FastRandomContext::rand256() std::vector<unsigned char> FastRandomContext::randbytes(size_t len) { + if (requires_seed) RandomSeed(); std::vector<unsigned char> ret(len); if (len > 0) { rng.Output(&ret[0], len); @@ -405,7 +559,7 @@ std::vector<unsigned char> FastRandomContext::randbytes(size_t len) return ret; } -FastRandomContext::FastRandomContext(const uint256& seed) : requires_seed(false), bytebuf_size(0), bitbuf_size(0) +FastRandomContext::FastRandomContext(const uint256& seed) noexcept : requires_seed(false), bytebuf_size(0), bitbuf_size(0) { rng.SetKey(seed.begin(), 32); } @@ -448,13 +602,15 @@ bool Random_SanityCheck() if (stop == start) return false; // We called GetPerformanceCounter. Use it as entropy. - RAND_add((const unsigned char*)&start, sizeof(start), 1); - RAND_add((const unsigned char*)&stop, sizeof(stop), 1); + CSHA512 to_add; + to_add.Write((const unsigned char*)&start, sizeof(start)); + to_add.Write((const unsigned char*)&stop, sizeof(stop)); + GetRNGState().MixExtract(nullptr, 0, std::move(to_add), false); return true; } -FastRandomContext::FastRandomContext(bool fDeterministic) : requires_seed(!fDeterministic), bytebuf_size(0), bitbuf_size(0) +FastRandomContext::FastRandomContext(bool fDeterministic) noexcept : requires_seed(!fDeterministic), bytebuf_size(0), bitbuf_size(0) { if (!fDeterministic) { return; @@ -463,7 +619,24 @@ FastRandomContext::FastRandomContext(bool fDeterministic) : requires_seed(!fDete rng.SetKey(seed.begin(), 32); } +FastRandomContext& FastRandomContext::operator=(FastRandomContext&& from) noexcept +{ + requires_seed = from.requires_seed; + rng = from.rng; + std::copy(std::begin(from.bytebuf), std::end(from.bytebuf), std::begin(bytebuf)); + bytebuf_size = from.bytebuf_size; + bitbuf = from.bitbuf; + bitbuf_size = from.bitbuf_size; + from.requires_seed = true; + from.bytebuf_size = 0; + from.bitbuf_size = 0; + return *this; +} + void RandomInit() { - RDRandInit(); + // Invoke RNG code to trigger initialization (if not already performed) + ProcRand(nullptr, 0, RNGLevel::FAST); + + ReportHardwareRand(); } diff --git a/src/random.h b/src/random.h index 3d5421eb3e..4c73f3822a 100644 --- a/src/random.h +++ b/src/random.h @@ -13,33 +13,83 @@ #include <stdint.h> #include <limits> -/* Seed OpenSSL PRNG with additional entropy data */ -void RandAddSeed(); +/** + * Overall design of the RNG and entropy sources. + * + * We maintain a single global 256-bit RNG state for all high-quality randomness. + * The following (classes of) functions interact with that state by mixing in new + * entropy, and optionally extracting random output from it: + * + * - The GetRand*() class of functions, as well as construction of FastRandomContext objects, + * perform 'fast' seeding, consisting of mixing in: + * - A stack pointer (indirectly committing to calling thread and call stack) + * - A high-precision timestamp (rdtsc when available, c++ high_resolution_clock otherwise) + * - Hardware RNG (rdrand) when available. + * These entropy sources are very fast, and only designed to protect against situations + * where a VM state restore/copy results in multiple systems with the same randomness. + * FastRandomContext on the other hand does not protect against this once created, but + * is even faster (and acceptable to use inside tight loops). + * + * - The GetStrongRand*() class of function perform 'slow' seeding, including everything + * that fast seeding includes, but additionally: + * - OS entropy (/dev/urandom, getrandom(), ...). The application will terminate if + * this entropy source fails. + * - Bytes from OpenSSL's RNG (which itself may be seeded from various sources) + * - Another high-precision timestamp (indirectly committing to a benchmark of all the + * previous sources). + * These entropy sources are slower, but designed to make sure the RNG state contains + * fresh data that is unpredictable to attackers. + * + * - RandAddSeedSleep() seeds everything that fast seeding includes, but additionally: + * - A high-precision timestamp before and after sleeping 1ms. + * - (On Windows) Once every 10 minutes, performance monitoring data from the OS. + * These just exploit the fact the system is idle to improve the quality of the RNG + * slightly. + * + * On first use of the RNG (regardless of what function is called first), all entropy + * sources used in the 'slow' seeder are included, but also: + * - (On Windows) Performance monitoring data from the OS. + * - (On Windows) Through OpenSSL, the screen contents. + * + * When mixing in new entropy, H = SHA512(entropy || old_rng_state) is computed, and + * (up to) the first 32 bytes of H are produced as output, while the last 32 bytes + * become the new RNG state. +*/ /** - * Functions to gather random data via the OpenSSL PRNG + * Generate random data via the internal PRNG. + * + * These functions are designed to be fast (sub microsecond), but do not necessarily + * meaningfully add entropy to the PRNG state. + * + * Thread-safe. */ -void GetRandBytes(unsigned char* buf, int num); -uint64_t GetRand(uint64_t nMax); -int GetRandInt(int nMax); -uint256 GetRandHash(); +void GetRandBytes(unsigned char* buf, int num) noexcept; +uint64_t GetRand(uint64_t nMax) noexcept; +int GetRandInt(int nMax) noexcept; +uint256 GetRandHash() noexcept; /** - * Add a little bit of randomness to the output of GetStrongRangBytes. - * This sleeps for a millisecond, so should only be called when there is - * no other work to be done. + * Gather entropy from various sources, feed it into the internal PRNG, and + * generate random data using it. + * + * This function will cause failure whenever the OS RNG fails. + * + * Thread-safe. */ -void RandAddSeedSleep(); +void GetStrongRandBytes(unsigned char* buf, int num) noexcept; /** - * Function to gather random data from multiple sources, failing whenever any - * of those sources fail to provide a result. + * Sleep for 1ms, gather entropy from various sources, and feed them to the PRNG state. + * + * Thread-safe. */ -void GetStrongRandBytes(unsigned char* buf, int num); +void RandAddSeedSleep(); /** * Fast randomness source. This is seeded once with secure random data, but - * is completely deterministic and insecure after that. + * is completely deterministic and does not gather more entropy after that. + * * This class is not thread-safe. */ class FastRandomContext { @@ -71,13 +121,21 @@ private: } public: - explicit FastRandomContext(bool fDeterministic = false); + explicit FastRandomContext(bool fDeterministic = false) noexcept; /** Initialize with explicit seed (only for testing) */ - explicit FastRandomContext(const uint256& seed); + explicit FastRandomContext(const uint256& seed) noexcept; + + // Do not permit copying a FastRandomContext (move it, or create a new one to get reseeded). + FastRandomContext(const FastRandomContext&) = delete; + FastRandomContext(FastRandomContext&&) = delete; + FastRandomContext& operator=(const FastRandomContext&) = delete; + + /** Move a FastRandomContext. If the original one is used again, it will be reseeded. */ + FastRandomContext& operator=(FastRandomContext&& from) noexcept; /** Generate a random 64-bit integer. */ - uint64_t rand64() + uint64_t rand64() noexcept { if (bytebuf_size < 8) FillByteBuffer(); uint64_t ret = ReadLE64(bytebuf + 64 - bytebuf_size); @@ -86,7 +144,7 @@ public: } /** Generate a random (bits)-bit integer. */ - uint64_t randbits(int bits) { + uint64_t randbits(int bits) noexcept { if (bits == 0) { return 0; } else if (bits > 32) { @@ -101,7 +159,7 @@ public: } /** Generate a random integer in the range [0..range). */ - uint64_t randrange(uint64_t range) + uint64_t randrange(uint64_t range) noexcept { --range; int bits = CountBits(range); @@ -115,21 +173,44 @@ public: std::vector<unsigned char> randbytes(size_t len); /** Generate a random 32-bit integer. */ - uint32_t rand32() { return randbits(32); } + uint32_t rand32() noexcept { return randbits(32); } /** generate a random uint256. */ - uint256 rand256(); + uint256 rand256() noexcept; /** Generate a random boolean. */ - bool randbool() { return randbits(1); } + bool randbool() noexcept { return randbits(1); } // Compatibility with the C++11 UniformRandomBitGenerator concept typedef uint64_t result_type; static constexpr uint64_t min() { return 0; } static constexpr uint64_t max() { return std::numeric_limits<uint64_t>::max(); } - inline uint64_t operator()() { return rand64(); } + inline uint64_t operator()() noexcept { return rand64(); } }; +/** More efficient than using std::shuffle on a FastRandomContext. + * + * This is more efficient as std::shuffle will consume entropy in groups of + * 64 bits at the time and throw away most. + * + * This also works around a bug in libstdc++ std::shuffle that may cause + * type::operator=(type&&) to be invoked on itself, which the library's + * debug mode detects and panics on. This is a known issue, see + * https://stackoverflow.com/questions/22915325/avoiding-self-assignment-in-stdshuffle + */ +template<typename I, typename R> +void Shuffle(I first, I last, R&& rng) +{ + while (first != last) { + size_t j = rng.randrange(last - first); + if (j) { + using std::swap; + swap(*first, *(first + j)); + } + ++first; + } +} + /* Number of random bytes returned by GetOSRand. * When changing this constant make sure to change all call sites, and make * sure that the underlying OS APIs for all platforms support the number. @@ -147,7 +228,12 @@ void GetOSRand(unsigned char *ent32); */ bool Random_SanityCheck(); -/** Initialize the RNG. */ +/** + * Initialize global RNG state and log any CPU features that are used. + * + * Calling this function is optional. RNG state will be initialized when first + * needed if it is not called. + */ void RandomInit(); #endif // BITCOIN_RANDOM_H diff --git a/src/rest.cpp b/src/rest.cpp index 3c12151c7d..c7a627d14e 100644 --- a/src/rest.cpp +++ b/src/rest.cpp @@ -3,20 +3,21 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. +#include <attributes.h> #include <chain.h> #include <chainparams.h> #include <core_io.h> +#include <httpserver.h> #include <index/txindex.h> #include <primitives/block.h> #include <primitives/transaction.h> -#include <validation.h> -#include <httpserver.h> #include <rpc/blockchain.h> #include <rpc/server.h> #include <streams.h> #include <sync.h> #include <txmempool.h> -#include <utilstrencodings.h> +#include <util/strencodings.h> +#include <validation.h> #include <version.h> #include <boost/algorithm/string.hpp> @@ -135,10 +136,12 @@ static bool rest_headers(HTTPRequest* req, if (!ParseHashStr(hashStr, hash)) return RESTERR(req, HTTP_BAD_REQUEST, "Invalid hash: " + hashStr); + const CBlockIndex* tip = nullptr; std::vector<const CBlockIndex *> headers; headers.reserve(count); { LOCK(cs_main); + tip = chainActive.Tip(); const CBlockIndex* pindex = LookupBlockIndex(hash); while (pindex != nullptr && chainActive.Contains(pindex)) { headers.push_back(pindex); @@ -174,11 +177,8 @@ static bool rest_headers(HTTPRequest* req, } case RetFormat::JSON: { UniValue jsonHeaders(UniValue::VARR); - { - LOCK(cs_main); - for (const CBlockIndex *pindex : headers) { - jsonHeaders.push_back(blockheaderToJSON(pindex)); - } + for (const CBlockIndex *pindex : headers) { + jsonHeaders.push_back(blockheaderToJSON(tip, pindex)); } std::string strJSON = jsonHeaders.write() + "\n"; req->WriteHeader("Content-Type", "application/json"); @@ -206,8 +206,10 @@ static bool rest_block(HTTPRequest* req, CBlock block; CBlockIndex* pblockindex = nullptr; + CBlockIndex* tip = nullptr; { LOCK(cs_main); + tip = chainActive.Tip(); pblockindex = LookupBlockIndex(hash); if (!pblockindex) { return RESTERR(req, HTTP_NOT_FOUND, hashStr + " not found"); @@ -240,11 +242,7 @@ static bool rest_block(HTTPRequest* req, } case RetFormat::JSON: { - UniValue objBlock; - { - LOCK(cs_main); - objBlock = blockToJSON(block, pblockindex, showTxDetails); - } + UniValue objBlock = blockToJSON(block, tip, pblockindex, showTxDetails); std::string strJSON = objBlock.write() + "\n"; req->WriteHeader("Content-Type", "application/json"); req->WriteReply(HTTP_OK, strJSON); diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp index 1eca0277b0..315f69d46b 100644 --- a/src/rpc/blockchain.cpp +++ b/src/rpc/blockchain.cpp @@ -12,8 +12,8 @@ #include <checkpoints.h> #include <coins.h> #include <consensus/validation.h> -#include <validation.h> #include <core_io.h> +#include <hash.h> #include <index/txindex.h> #include <key_io.h> #include <policy/feerate.h> @@ -21,14 +21,15 @@ #include <policy/rbf.h> #include <primitives/transaction.h> #include <rpc/server.h> +#include <rpc/util.h> #include <script/descriptor.h> #include <streams.h> #include <sync.h> #include <txdb.h> #include <txmempool.h> -#include <util.h> -#include <utilstrencodings.h> -#include <hash.h> +#include <util/strencodings.h> +#include <util/system.h> +#include <validation.h> #include <validationinterface.h> #include <versionbitsinfo.h> #include <warnings.h> @@ -38,7 +39,6 @@ #include <univalue.h> -#include <boost/algorithm/string.hpp> #include <boost/thread/thread.hpp> // boost::thread::interrupt #include <memory> @@ -59,10 +59,7 @@ static CUpdatedBlock latestblock; */ double GetDifficulty(const CBlockIndex* blockindex) { - if (blockindex == nullptr) - { - return 1.0; - } + assert(blockindex); int nShift = (blockindex->nBits >> 24) & 0xff; double dDiff = @@ -82,15 +79,22 @@ double GetDifficulty(const CBlockIndex* blockindex) return dDiff; } -UniValue blockheaderToJSON(const CBlockIndex* blockindex) +static int ComputeNextBlockAndDepth(const CBlockIndex* tip, const CBlockIndex* blockindex, const CBlockIndex*& next) +{ + next = tip->GetAncestor(blockindex->nHeight + 1); + if (next && next->pprev == blockindex) { + return tip->nHeight - blockindex->nHeight + 1; + } + next = nullptr; + return blockindex == tip ? 1 : -1; +} + +UniValue blockheaderToJSON(const CBlockIndex* tip, const CBlockIndex* blockindex) { - AssertLockHeld(cs_main); UniValue result(UniValue::VOBJ); result.pushKV("hash", blockindex->GetBlockHash().GetHex()); - int confirmations = -1; - // Only report confirmations if the block is on the main chain - if (chainActive.Contains(blockindex)) - confirmations = chainActive.Height() - blockindex->nHeight + 1; + const CBlockIndex* pnext; + int confirmations = ComputeNextBlockAndDepth(tip, blockindex, pnext); result.pushKV("confirmations", confirmations); result.pushKV("height", blockindex->nHeight); result.pushKV("version", blockindex->nVersion); @@ -106,21 +110,17 @@ UniValue blockheaderToJSON(const CBlockIndex* blockindex) if (blockindex->pprev) result.pushKV("previousblockhash", blockindex->pprev->GetBlockHash().GetHex()); - CBlockIndex *pnext = chainActive.Next(blockindex); if (pnext) result.pushKV("nextblockhash", pnext->GetBlockHash().GetHex()); return result; } -UniValue blockToJSON(const CBlock& block, const CBlockIndex* blockindex, bool txDetails) +UniValue blockToJSON(const CBlock& block, const CBlockIndex* tip, const CBlockIndex* blockindex, bool txDetails) { - AssertLockHeld(cs_main); UniValue result(UniValue::VOBJ); result.pushKV("hash", blockindex->GetBlockHash().GetHex()); - int confirmations = -1; - // Only report confirmations if the block is on the main chain - if (chainActive.Contains(blockindex)) - confirmations = chainActive.Height() - blockindex->nHeight + 1; + const CBlockIndex* pnext; + int confirmations = ComputeNextBlockAndDepth(tip, blockindex, pnext); result.pushKV("confirmations", confirmations); result.pushKV("strippedsize", (int)::GetSerializeSize(block, PROTOCOL_VERSION | SERIALIZE_TRANSACTION_NO_WITNESS)); result.pushKV("size", (int)::GetSerializeSize(block, PROTOCOL_VERSION)); @@ -152,7 +152,6 @@ UniValue blockToJSON(const CBlock& block, const CBlockIndex* blockindex, bool tx if (blockindex->pprev) result.pushKV("previousblockhash", blockindex->pprev->GetBlockHash().GetHex()); - CBlockIndex *pnext = chainActive.Next(blockindex); if (pnext) result.pushKV("nextblockhash", pnext->GetBlockHash().GetHex()); return result; @@ -162,8 +161,9 @@ static UniValue getblockcount(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() != 0) throw std::runtime_error( - "getblockcount\n" - "\nReturns the number of blocks in the longest blockchain.\n" + RPCHelpMan{"getblockcount", + "\nReturns the number of blocks in the longest blockchain.\n", {}} + .ToString() + "\nResult:\n" "n (numeric) The current block count\n" "\nExamples:\n" @@ -179,10 +179,11 @@ static UniValue getbestblockhash(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() != 0) throw std::runtime_error( - "getbestblockhash\n" - "\nReturns the hash of the best (tip) block in the longest blockchain.\n" + RPCHelpMan{"getbestblockhash", + "\nReturns the hash of the best (tip) block in the longest blockchain.\n", {}} + .ToString() + "\nResult:\n" - "\"hex\" (string) the block hash hex encoded\n" + "\"hex\" (string) the block hash, hex-encoded\n" "\nExamples:\n" + HelpExampleCli("getbestblockhash", "") + HelpExampleRpc("getbestblockhash", "") @@ -206,11 +207,13 @@ static UniValue waitfornewblock(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() > 1) throw std::runtime_error( - "waitfornewblock (timeout)\n" - "\nWaits for a specific new block and returns useful info about it.\n" - "\nReturns the current block on timeout or exit.\n" - "\nArguments:\n" - "1. timeout (int, optional, default=0) Time in milliseconds to wait for a response. 0 indicates no timeout.\n" + RPCHelpMan{"waitfornewblock", + "\nWaits for a specific new block and returns useful info about it.\n" + "\nReturns the current block on timeout or exit.\n", + { + {"timeout", RPCArg::Type::NUM, /* opt */ true, /* default_val */ "0", "Time in milliseconds to wait for a response. 0 indicates no timeout."}, + }} + .ToString() + "\nResult:\n" "{ (json object)\n" " \"hash\" : { (string) The blockhash\n" @@ -244,12 +247,14 @@ static UniValue waitforblock(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) throw std::runtime_error( - "waitforblock <blockhash> (timeout)\n" - "\nWaits for a specific new block and returns useful info about it.\n" - "\nReturns the current block on timeout or exit.\n" - "\nArguments:\n" - "1. \"blockhash\" (required, string) Block hash to wait for.\n" - "2. timeout (int, optional, default=0) Time in milliseconds to wait for a response. 0 indicates no timeout.\n" + RPCHelpMan{"waitforblock", + "\nWaits for a specific new block and returns useful info about it.\n" + "\nReturns the current block on timeout or exit.\n", + { + {"blockhash", RPCArg::Type::STR_HEX, /* opt */ false, /* default_val */ "", "Block hash to wait for."}, + {"timeout", RPCArg::Type::NUM, /* opt */ true, /* default_val */ "0", "Time in milliseconds to wait for a response. 0 indicates no timeout."}, + }} + .ToString() + "\nResult:\n" "{ (json object)\n" " \"hash\" : { (string) The blockhash\n" @@ -286,13 +291,15 @@ static UniValue waitforblockheight(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) throw std::runtime_error( - "waitforblockheight <height> (timeout)\n" - "\nWaits for (at least) block height and returns the height and hash\n" - "of the current tip.\n" - "\nReturns the current block on timeout or exit.\n" - "\nArguments:\n" - "1. height (required, int) Block height to wait for (int)\n" - "2. timeout (int, optional, default=0) Time in milliseconds to wait for a response. 0 indicates no timeout.\n" + RPCHelpMan{"waitforblockheight", + "\nWaits for (at least) block height and returns the height and hash\n" + "of the current tip.\n" + "\nReturns the current block on timeout or exit.\n", + { + {"height", RPCArg::Type::NUM, /* opt */ false, /* default_val */ "", "Block height to wait for."}, + {"timeout", RPCArg::Type::NUM, /* opt */ true, /* default_val */ "0", "Time in milliseconds to wait for a response. 0 indicates no timeout."}, + }} + .ToString() + "\nResult:\n" "{ (json object)\n" " \"hash\" : { (string) The blockhash\n" @@ -328,8 +335,9 @@ static UniValue syncwithvalidationinterfacequeue(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() > 0) { throw std::runtime_error( - "syncwithvalidationinterfacequeue\n" - "\nWaits for the validation interface queue to catch up on everything that was there when we entered this function.\n" + RPCHelpMan{"syncwithvalidationinterfacequeue", + "\nWaits for the validation interface queue to catch up on everything that was there when we entered this function.\n", {}} + .ToString() + "\nExamples:\n" + HelpExampleCli("syncwithvalidationinterfacequeue","") + HelpExampleRpc("syncwithvalidationinterfacequeue","") @@ -343,8 +351,9 @@ static UniValue getdifficulty(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() != 0) throw std::runtime_error( - "getdifficulty\n" - "\nReturns the proof-of-work difficulty as a multiple of the minimum difficulty.\n" + RPCHelpMan{"getdifficulty", + "\nReturns the proof-of-work difficulty as a multiple of the minimum difficulty.\n", {}} + .ToString() + "\nResult:\n" "n.nnn (numeric) the proof-of-work difficulty as a multiple of the minimum difficulty.\n" "\nExamples:\n" @@ -477,11 +486,13 @@ static UniValue getrawmempool(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() > 1) throw std::runtime_error( - "getrawmempool ( verbose )\n" - "\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" - "\nArguments:\n" - "1. verbose (boolean, optional, default=false) True for a json object, false for array of transaction ids\n" + RPCHelpMan{"getrawmempool", + "\nReturns all transaction ids in memory pool as a json array of string transaction ids.\n" + "\nHint: use getmempoolentry to fetch a specific transaction from the mempool.\n", + { + {"verbose", RPCArg::Type::BOOL, /* opt */ true, /* default_val */ "false", "True for a json object, false for array of transaction ids"}, + }} + .ToString() + "\nResult: (for verbose = false):\n" "[ (json array of string)\n" " \"transactionid\" (string) The transaction id\n" @@ -509,17 +520,19 @@ static UniValue getmempoolancestors(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) { throw std::runtime_error( - "getmempoolancestors txid (verbose)\n" - "\nIf txid is in the mempool, returns all in-mempool ancestors.\n" - "\nArguments:\n" - "1. \"txid\" (string, required) The transaction id (must be in mempool)\n" - "2. verbose (boolean, optional, default=false) True for a json object, false for array of transaction ids\n" - "\nResult (for verbose=false):\n" + RPCHelpMan{"getmempoolancestors", + "\nIf txid is in the mempool, returns all in-mempool ancestors.\n", + { + {"txid", RPCArg::Type::STR_HEX, /* opt */ false, /* default_val */ "", "The transaction id (must be in mempool)"}, + {"verbose", RPCArg::Type::BOOL, /* opt */ true, /* default_val */ "false", "True for a json object, false for array of transaction ids"}, + }} + .ToString() + + "\nResult (for verbose = false):\n" "[ (json array of strings)\n" " \"transactionid\" (string) The transaction id of an in-mempool ancestor transaction\n" " ,...\n" "]\n" - "\nResult (for verbose=true):\n" + "\nResult (for verbose = true):\n" "{ (json object)\n" " \"transactionid\" : { (json object)\n" + EntryDescriptionString() @@ -573,17 +586,19 @@ static UniValue getmempooldescendants(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) { throw std::runtime_error( - "getmempooldescendants txid (verbose)\n" - "\nIf txid is in the mempool, returns all in-mempool descendants.\n" - "\nArguments:\n" - "1. \"txid\" (string, required) The transaction id (must be in mempool)\n" - "2. verbose (boolean, optional, default=false) True for a json object, false for array of transaction ids\n" - "\nResult (for verbose=false):\n" + RPCHelpMan{"getmempooldescendants", + "\nIf txid is in the mempool, returns all in-mempool descendants.\n", + { + {"txid", RPCArg::Type::STR_HEX, /* opt */ false, /* default_val */ "", "The transaction id (must be in mempool)"}, + {"verbose", RPCArg::Type::BOOL, /* opt */ true, /* default_val */ "false", "True for a json object, false for array of transaction ids"}, + }} + .ToString() + + "\nResult (for verbose = false):\n" "[ (json array of strings)\n" " \"transactionid\" (string) The transaction id of an in-mempool descendant transaction\n" " ,...\n" "]\n" - "\nResult (for verbose=true):\n" + "\nResult (for verbose = true):\n" "{ (json object)\n" " \"transactionid\" : { (json object)\n" + EntryDescriptionString() @@ -637,10 +652,12 @@ static UniValue getmempoolentry(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() != 1) { throw std::runtime_error( - "getmempoolentry txid\n" - "\nReturns mempool data for given transaction\n" - "\nArguments:\n" - "1. \"txid\" (string, required) The transaction id (must be in mempool)\n" + RPCHelpMan{"getmempoolentry", + "\nReturns mempool data for given transaction\n", + { + {"txid", RPCArg::Type::STR_HEX, /* opt */ false, /* default_val */ "", "The transaction id (must be in mempool)"}, + }} + .ToString() + "\nResult:\n" "{ (json object)\n" + EntryDescriptionString() @@ -670,10 +687,12 @@ static UniValue getblockhash(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() != 1) throw std::runtime_error( - "getblockhash height\n" - "\nReturns hash of block in best-block-chain at height provided.\n" - "\nArguments:\n" - "1. height (numeric, required) The height index\n" + RPCHelpMan{"getblockhash", + "\nReturns hash of block in best-block-chain at height provided.\n", + { + {"height", RPCArg::Type::NUM, /* opt */ false, /* default_val */ "", "The height index"}, + }} + .ToString() + "\nResult:\n" "\"hash\" (string) The block hash\n" "\nExamples:\n" @@ -695,12 +714,14 @@ static UniValue getblockheader(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) throw std::runtime_error( - "getblockheader \"hash\" ( verbose )\n" - "\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" - "\nArguments:\n" - "1. \"hash\" (string, required) The block hash\n" - "2. verbose (boolean, optional, default=true) true for a json object, false for the hex encoded data\n" + 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", + { + {"blockhash", RPCArg::Type::STR_HEX, /* opt */ false, /* default_val */ "", "The block hash"}, + {"verbose", RPCArg::Type::BOOL, /* opt */ true, /* default_val */ "true", "true for a json object, false for the hex-encoded data"}, + }} + .ToString() + "\nResult (for verbose = true):\n" "{\n" " \"hash\" : \"hash\", (string) the block hash (same as provided)\n" @@ -726,15 +747,20 @@ static UniValue getblockheader(const JSONRPCRequest& request) + HelpExampleRpc("getblockheader", "\"00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09\"") ); - LOCK(cs_main); - uint256 hash(ParseHashV(request.params[0], "hash")); bool fVerbose = true; if (!request.params[1].isNull()) fVerbose = request.params[1].get_bool(); - const CBlockIndex* pblockindex = LookupBlockIndex(hash); + const CBlockIndex* pblockindex; + const CBlockIndex* tip; + { + LOCK(cs_main); + pblockindex = LookupBlockIndex(hash); + tip = chainActive.Tip(); + } + if (!pblockindex) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found"); } @@ -747,7 +773,7 @@ static UniValue getblockheader(const JSONRPCRequest& request) return strHex; } - return blockheaderToJSON(pblockindex); + return blockheaderToJSON(tip, pblockindex); } static CBlock GetBlockChecked(const CBlockIndex* pblockindex) @@ -773,13 +799,15 @@ static UniValue getblock(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) throw std::runtime_error( - "getblock \"blockhash\" ( verbosity ) \n" - "\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" - "\nArguments:\n" - "1. \"blockhash\" (string, required) The block hash\n" - "2. verbosity (numeric, optional, default=1) 0 for hex encoded data, 1 for a json object, and 2 for json object with transaction data\n" + 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", + { + {"blockhash", RPCArg::Type::STR_HEX, /* opt */ false, /* default_val */ "", "The block hash"}, + {"verbosity", RPCArg::Type::NUM, /* opt */ true, /* default_val */ "1", "0 for hex-encoded data, 1 for a json object, and 2 for json object with transaction data"}, + }} + .ToString() + "\nResult (for verbosity = 0):\n" "\"data\" (string) A string that is serialized, hex-encoded data for block 'hash'.\n" "\nResult (for verbosity = 1):\n" @@ -847,7 +875,7 @@ static UniValue getblock(const JSONRPCRequest& request) return strHex; } - return blockToJSON(block, pblockindex, verbosity >= 2); + return blockToJSON(block, chainActive.Tip(), pblockindex, verbosity >= 2); } struct CCoinsStats @@ -925,10 +953,12 @@ static UniValue pruneblockchain(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() != 1) throw std::runtime_error( - "pruneblockchain\n" - "\nArguments:\n" - "1. \"height\" (numeric, required) The block height to prune up to. May be set to a discrete height, or a unix timestamp\n" - " to prune blocks whose block time is at least 2 hours older than the provided timestamp.\n" + RPCHelpMan{"pruneblockchain", "", + { + {"height", RPCArg::Type::NUM, /* opt */ false, /* default_val */ "", "The block height to prune up to. May be set to a discrete height, or a unix timestamp\n" + " to prune blocks whose block time is at least 2 hours older than the provided timestamp."}, + }} + .ToString() + "\nResult:\n" "n (numeric) Height of the last block pruned.\n" "\nExamples:\n" @@ -974,9 +1004,11 @@ static UniValue gettxoutsetinfo(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() != 0) throw std::runtime_error( - "gettxoutsetinfo\n" - "\nReturns statistics about the unspent transaction output set.\n" - "Note this call may take some time.\n" + RPCHelpMan{"gettxoutsetinfo", + "\nReturns statistics about the unspent transaction output set.\n" + "Note this call may take some time.\n", + {}} + .ToString() + "\nResult:\n" "{\n" " \"height\":n, (numeric) The current block height (index)\n" @@ -1016,13 +1048,14 @@ UniValue gettxout(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() < 2 || request.params.size() > 3) throw std::runtime_error( - "gettxout \"txid\" n ( include_mempool )\n" - "\nReturns details about an unspent transaction output.\n" - "\nArguments:\n" - "1. \"txid\" (string, required) The transaction id\n" - "2. \"n\" (numeric, required) vout number\n" - "3. \"include_mempool\" (boolean, optional) Whether to include the mempool. Default: true." - " Note that an unspent output that is spent in the mempool won't appear.\n" + RPCHelpMan{"gettxout", + "\nReturns details about an unspent transaction output.\n", + { + {"txid", RPCArg::Type::STR, /* opt */ false, /* default_val */ "", "The transaction id"}, + {"n", RPCArg::Type::NUM, /* opt */ false, /* default_val */ "", "vout number"}, + {"include_mempool", RPCArg::Type::BOOL, /* opt */ true, /* default_val */ "true", "Whether to include the mempool. Note that an unspent output that is spent in the mempool won't appear."}, + }} + .ToString() + "\nResult:\n" "{\n" " \"bestblock\": \"hash\", (string) The hash of the block at the tip of the chain\n" @@ -1046,7 +1079,7 @@ UniValue gettxout(const JSONRPCRequest& request) + HelpExampleCli("listunspent", "") + "\nView the details\n" + HelpExampleCli("gettxout", "\"txid\" 1") + - "\nAs a json rpc call\n" + "\nAs a JSON-RPC call\n" + HelpExampleRpc("gettxout", "\"txid\", 1") ); @@ -1096,11 +1129,13 @@ static UniValue verifychain(const JSONRPCRequest& request) int nCheckDepth = gArgs.GetArg("-checkblocks", DEFAULT_CHECKBLOCKS); if (request.fHelp || request.params.size() > 2) throw std::runtime_error( - "verifychain ( checklevel nblocks )\n" - "\nVerifies blockchain database.\n" - "\nArguments:\n" - "1. checklevel (numeric, optional, 0-4, default=" + strprintf("%d", nCheckLevel) + ") How thorough the block verification is.\n" - "2. nblocks (numeric, optional, default=" + strprintf("%d", nCheckDepth) + ", 0=all) The number of blocks to check.\n" + RPCHelpMan{"verifychain", + "\nVerifies blockchain database.\n", + { + {"checklevel", RPCArg::Type::NUM, /* opt */ true, /* default_val */ strprintf("%d, range=0-4", nCheckLevel), "How thorough the block verification is."}, + {"nblocks", RPCArg::Type::NUM, /* opt */ true, /* default_val */ strprintf("%d, 0=all", nCheckDepth), "The number of blocks to check."}, + }} + .ToString() + "\nResult:\n" "true|false (boolean) Verified or not\n" "\nExamples:\n" @@ -1119,7 +1154,7 @@ static UniValue verifychain(const JSONRPCRequest& request) } /** Implementation of IsSuperMajority with better feedback */ -static UniValue SoftForkMajorityDesc(int version, CBlockIndex* pindex, const Consensus::Params& consensusParams) +static UniValue SoftForkMajorityDesc(int version, const CBlockIndex* pindex, const Consensus::Params& consensusParams) { UniValue rv(UniValue::VOBJ); bool activated = false; @@ -1139,7 +1174,7 @@ static UniValue SoftForkMajorityDesc(int version, CBlockIndex* pindex, const Con return rv; } -static UniValue SoftForkDesc(const std::string &name, int version, CBlockIndex* pindex, const Consensus::Params& consensusParams) +static UniValue SoftForkDesc(const std::string &name, int version, const CBlockIndex* pindex, const Consensus::Params& consensusParams) { UniValue rv(UniValue::VOBJ); rv.pushKV("id", name); @@ -1193,8 +1228,9 @@ UniValue getblockchaininfo(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() != 0) throw std::runtime_error( - "getblockchaininfo\n" - "Returns an object containing various state info regarding blockchain processing.\n" + RPCHelpMan{"getblockchaininfo", + "Returns an object containing various state info regarding blockchain processing.\n", {}} + .ToString() + "\nResult:\n" "{\n" " \"chain\": \"xxxx\", (string) current network name as defined in BIP70 (main, test, regtest)\n" @@ -1245,20 +1281,21 @@ UniValue getblockchaininfo(const JSONRPCRequest& request) LOCK(cs_main); + const CBlockIndex* tip = chainActive.Tip(); UniValue obj(UniValue::VOBJ); obj.pushKV("chain", Params().NetworkIDString()); obj.pushKV("blocks", (int)chainActive.Height()); obj.pushKV("headers", pindexBestHeader ? pindexBestHeader->nHeight : -1); - obj.pushKV("bestblockhash", chainActive.Tip()->GetBlockHash().GetHex()); - obj.pushKV("difficulty", (double)GetDifficulty(chainActive.Tip())); - obj.pushKV("mediantime", (int64_t)chainActive.Tip()->GetMedianTimePast()); - obj.pushKV("verificationprogress", GuessVerificationProgress(Params().TxData(), chainActive.Tip())); + obj.pushKV("bestblockhash", tip->GetBlockHash().GetHex()); + obj.pushKV("difficulty", (double)GetDifficulty(tip)); + obj.pushKV("mediantime", (int64_t)tip->GetMedianTimePast()); + obj.pushKV("verificationprogress", GuessVerificationProgress(Params().TxData(), tip)); obj.pushKV("initialblockdownload", IsInitialBlockDownload()); - obj.pushKV("chainwork", chainActive.Tip()->nChainWork.GetHex()); + obj.pushKV("chainwork", tip->nChainWork.GetHex()); obj.pushKV("size_on_disk", CalculateCurrentUsage()); obj.pushKV("pruned", fPruneMode); if (fPruneMode) { - CBlockIndex* block = chainActive.Tip(); + const CBlockIndex* block = tip; assert(block); while (block->pprev && (block->pprev->nStatus & BLOCK_HAVE_DATA)) { block = block->pprev; @@ -1275,7 +1312,6 @@ UniValue getblockchaininfo(const JSONRPCRequest& request) } const Consensus::Params& consensusParams = Params().GetConsensus(); - CBlockIndex* tip = chainActive.Tip(); UniValue softforks(UniValue::VARR); UniValue bip9_softforks(UniValue::VOBJ); softforks.push_back(SoftForkDesc("bip34", 2, tip, consensusParams)); @@ -1310,9 +1346,11 @@ static UniValue getchaintips(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() != 0) throw std::runtime_error( - "getchaintips\n" - "Return information about all known tips in the block tree," - " including the main chain as well as orphaned branches.\n" + RPCHelpMan{"getchaintips", + "Return information about all known tips in the block tree," + " including the main chain as well as orphaned branches.\n", + {}} + .ToString() + "\nResult:\n" "[\n" " {\n" @@ -1388,7 +1426,7 @@ static UniValue getchaintips(const JSONRPCRequest& request) } else if (block->nStatus & BLOCK_FAILED_MASK) { // This block or one of its ancestors is invalid. status = "invalid"; - } else if (block->nChainTx == 0) { + } else if (!block->HaveTxsDownloaded()) { // This block cannot be connected because full block data for it or one of its parents is missing. status = "headers-only"; } else if (block->IsValid(BLOCK_VALID_SCRIPTS)) { @@ -1427,8 +1465,9 @@ static UniValue getmempoolinfo(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() != 0) throw std::runtime_error( - "getmempoolinfo\n" - "\nReturns details on the active state of the TX memory pool.\n" + RPCHelpMan{"getmempoolinfo", + "\nReturns details on the active state of the TX memory pool.\n", {}} + .ToString() + "\nResult:\n" "{\n" " \"size\": xxxxx, (numeric) Current tx count\n" @@ -1450,12 +1489,14 @@ static UniValue preciousblock(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() != 1) throw std::runtime_error( - "preciousblock \"blockhash\"\n" - "\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" - "\nThe effects of preciousblock are not retained across restarts.\n" - "\nArguments:\n" - "1. \"blockhash\" (string, required) the hash of the block to mark as precious\n" + 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" + "\nThe effects of preciousblock are not retained across restarts.\n", + { + {"blockhash", RPCArg::Type::STR_HEX, /* opt */ false, /* default_val */ "", "the hash of the block to mark as precious"}, + }} + .ToString() + "\nResult:\n" "\nExamples:\n" + HelpExampleCli("preciousblock", "\"blockhash\"") @@ -1487,10 +1528,12 @@ static UniValue invalidateblock(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() != 1) throw std::runtime_error( - "invalidateblock \"blockhash\"\n" - "\nPermanently marks a block as invalid, as if it violated a consensus rule.\n" - "\nArguments:\n" - "1. \"blockhash\" (string, required) the hash of the block to mark as invalid\n" + RPCHelpMan{"invalidateblock", + "\nPermanently marks a block as invalid, as if it violated a consensus rule.\n", + { + {"blockhash", RPCArg::Type::STR_HEX, /* opt */ false, /* default_val */ "", "the hash of the block to mark as invalid"}, + }} + .ToString() + "\nResult:\n" "\nExamples:\n" + HelpExampleCli("invalidateblock", "\"blockhash\"") @@ -1525,11 +1568,13 @@ static UniValue reconsiderblock(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() != 1) throw std::runtime_error( - "reconsiderblock \"blockhash\"\n" - "\nRemoves invalidity status of a block and its descendants, reconsider them for activation.\n" - "This can be used to undo the effects of invalidateblock.\n" - "\nArguments:\n" - "1. \"blockhash\" (string, required) the hash of the block to reconsider\n" + 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", + { + {"blockhash", RPCArg::Type::STR_HEX, /* opt */ false, /* default_val */ "", "the hash of the block to reconsider"}, + }} + .ToString() + "\nResult:\n" "\nExamples:\n" + HelpExampleCli("reconsiderblock", "\"blockhash\"") @@ -1562,11 +1607,13 @@ static UniValue getchaintxstats(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() > 2) throw std::runtime_error( - "getchaintxstats ( nblocks blockhash )\n" - "\nCompute statistics about the total number and rate of transactions in the chain.\n" - "\nArguments:\n" - "1. nblocks (numeric, optional) Size of the window in number of blocks (default: one month).\n" - "2. \"blockhash\" (string, optional) The hash of the block that ends the window.\n" + RPCHelpMan{"getchaintxstats", + "\nCompute statistics about the total number and rate of transactions in the chain.\n", + { + {"nblocks", RPCArg::Type::NUM, /* opt */ true, /* default_val */ "one month", "Size of the window in number of blocks"}, + {"blockhash", RPCArg::Type::STR_HEX, /* opt */ true, /* default_val */ "chain tip", "The hash of the block that ends the window."}, + }} + .ToString() + "\nResult:\n" "{\n" " \"time\": xxxxx, (numeric) The timestamp for the final block in the window in UNIX format.\n" @@ -1692,18 +1739,20 @@ static UniValue getblockstats(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() < 1 || request.params.size() > 4) { throw std::runtime_error( - "getblockstats hash_or_height ( stats )\n" - "\nCompute per block statistics for a given window. All amounts are in satoshis.\n" - "It won't work for some heights with pruning.\n" - "It won't work without -txindex for utxo_size_inc, *fee or *feerate stats.\n" - "\nArguments:\n" - "1. \"hash_or_height\" (string or numeric, required) The block hash or height of the target block\n" - "2. \"stats\" (array, optional) Values to plot, by default all values (see result below)\n" - " [\n" - " \"height\", (string, optional) Selected statistic\n" - " \"time\", (string, optional) Selected statistic\n" - " ,...\n" - " ]\n" + 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" + "It won't work without -txindex for utxo_size_inc, *fee or *feerate stats.\n", + { + {"hash_or_height", RPCArg::Type::NUM, /* opt */ false, /* default_val */ "", "The block hash or height of the target block", "", {"", "string or numeric"}}, + {"stats", RPCArg::Type::ARR, /* opt */ true, /* default_val */ "all values", "Values to plot (see result below)", + { + {"height", RPCArg::Type::STR, /* opt */ true, /* default_val */ "", "Selected statistic"}, + {"time", RPCArg::Type::STR, /* opt */ true, /* default_val */ "", "Selected statistic"}, + }, + "stats"}, + }} + .ToString() + "\nResult:\n" "{ (json object)\n" " \"avgfee\": xxxxx, (numeric) Average fee in the block\n" @@ -1798,6 +1847,10 @@ static UniValue getblockstats(const JSONRPCRequest& request) const bool do_calculate_weight = do_all || SetHasKeys(stats, "total_weight", "avgfeerate", "swtotal_weight", "avgfeerate", "feerate_percentiles", "minfeerate", "maxfeerate"); const bool do_calculate_sw = do_all || SetHasKeys(stats, "swtxs", "swtotal_size", "swtotal_weight"); + if (loop_inputs && !g_txindex) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "One or more of the selected stats requires -txindex enabled"); + } + CAmount maxfee = 0; CAmount maxfeerate = 0; CAmount minfee = MAX_MONEY; @@ -1861,10 +1914,6 @@ static UniValue getblockstats(const JSONRPCRequest& request) } if (loop_inputs) { - - if (!g_txindex) { - throw JSONRPCError(RPC_INVALID_PARAMETER, "One or more of the selected stats requires -txindex enabled"); - } CAmount tx_total_in = 0; for (const CTxIn& in : tx->vin) { CTransactionRef tx_in; @@ -1956,8 +2005,9 @@ static UniValue savemempool(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() != 0) { throw std::runtime_error( - "savemempool\n" - "\nDumps the mempool to disk. It will fail until the previous dump is fully loaded.\n" + RPCHelpMan{"savemempool", + "\nDumps the mempool to disk. It will fail until the previous dump is fully loaded.\n", {}} + .ToString() + "\nExamples:\n" + HelpExampleCli("savemempool", "") + HelpExampleRpc("savemempool", "") @@ -2039,34 +2089,39 @@ UniValue scantxoutset(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) throw std::runtime_error( - "scantxoutset <action> ( <scanobjects> )\n" - "\nEXPERIMENTAL warning: this call may be removed or changed in future releases.\n" - "\nScans the unspent transaction output set for entries that match certain output descriptors.\n" - "Examples of output descriptors are:\n" - " addr(<address>) Outputs whose scriptPubKey corresponds to the specified address (does not include P2PK)\n" - " raw(<hex script>) Outputs whose scriptPubKey equals the specified hex scripts\n" - " combo(<pubkey>) P2PK, P2PKH, P2WPKH, and P2SH-P2WPKH outputs for the given pubkey\n" - " pkh(<pubkey>) P2PKH outputs for the given pubkey\n" - " sh(multi(<n>,<pubkey>,<pubkey>,...)) P2SH-multisig outputs for the given threshold and pubkeys\n" - "\nIn the above, <pubkey> either refers to a fixed public key in hexadecimal notation, or to an xpub/xprv optionally followed by one\n" - "or more path elements separated by \"/\", and optionally ending in \"/*\" (unhardened), or \"/*'\" or \"/*h\" (hardened) to specify all\n" - "unhardened or hardened child keys.\n" - "In the latter case, a range needs to be specified by below if different from 1000.\n" - "For more information on output descriptors, see the documentation in the doc/descriptors.md file.\n" - "\nArguments:\n" - "1. \"action\" (string, required) The action to execute\n" + RPCHelpMan{"scantxoutset", + "\nEXPERIMENTAL warning: this call may be removed or changed in future releases.\n" + "\nScans the unspent transaction output set for entries that match certain output descriptors.\n" + "Examples of output descriptors are:\n" + " addr(<address>) Outputs whose scriptPubKey corresponds to the specified address (does not include P2PK)\n" + " raw(<hex script>) Outputs whose scriptPubKey equals the specified hex scripts\n" + " combo(<pubkey>) P2PK, P2PKH, P2WPKH, and P2SH-P2WPKH outputs for the given pubkey\n" + " pkh(<pubkey>) P2PKH outputs for the given pubkey\n" + " sh(multi(<n>,<pubkey>,<pubkey>,...)) P2SH-multisig outputs for the given threshold and pubkeys\n" + "\nIn the above, <pubkey> either refers to a fixed public key in hexadecimal notation, or to an xpub/xprv optionally followed by one\n" + "or more path elements separated by \"/\", and optionally ending in \"/*\" (unhardened), or \"/*'\" or \"/*h\" (hardened) to specify all\n" + "unhardened or hardened child keys.\n" + "In the latter case, a range needs to be specified by below if different from 1000.\n" + "For more information on output descriptors, see the documentation in the doc/descriptors.md file.\n", + { + {"action", RPCArg::Type::STR, /* opt */ false, /* default_val */ "", "The action to execute\n" " \"start\" for starting a scan\n" " \"abort\" for aborting the current scan (returns true when abort was successful)\n" - " \"status\" for progress report (in %) of the current scan\n" - "2. \"scanobjects\" (array, required) Array of scan objects\n" - " [ Every scan object is either a string descriptor or an object:\n" - " \"descriptor\", (string, optional) An output descriptor\n" - " { (object, optional) An object with output descriptor and metadata\n" - " \"desc\": \"descriptor\", (string, required) An output descriptor\n" - " \"range\": n, (numeric, optional) Up to what child index HD chains should be explored (default: 1000)\n" - " },\n" - " ...\n" - " ]\n" + " \"status\" for progress report (in %) of the current scan"}, + {"scanobjects", RPCArg::Type::ARR, /* opt */ false, /* default_val */ "", "Array of scan objects\n" + " Every scan object is either a string descriptor or an object:", + { + {"descriptor", RPCArg::Type::STR, /* opt */ true, /* default_val */ "", "An output descriptor"}, + {"", RPCArg::Type::OBJ, /* opt */ true, /* default_val */ "", "An object with output descriptor and metadata", + { + {"desc", RPCArg::Type::STR, /* opt */ false, /* default_val */ "", "An output descriptor"}, + {"range", RPCArg::Type::NUM, /* opt */ true, /* default_val */ "1000", "Up to what child index HD chains should be explored"}, + }, + }, + }, + "[scanobjects,...]"}, + }} + .ToString() + "\nResult:\n" "{\n" " \"unspents\": [\n" @@ -2074,6 +2129,7 @@ UniValue scantxoutset(const JSONRPCRequest& request) " \"txid\" : \"transactionid\", (string) The transaction id\n" " \"vout\": n, (numeric) the vout value\n" " \"scriptPubKey\" : \"script\", (string) the script key\n" + " \"desc\" : \"descriptor\", (string) A specialized descriptor for the matched scriptPubKey\n" " \"amount\" : x.xxx, (numeric) The total amount in " + CURRENCY_UNIT + " of the unspent output\n" " \"height\" : n, (numeric) Height of the unspent transaction output\n" " }\n" @@ -2108,6 +2164,7 @@ UniValue scantxoutset(const JSONRPCRequest& request) throw JSONRPCError(RPC_INVALID_PARAMETER, "Scan already in progress, use action \"abort\" or \"status\""); } std::set<CScript> needles; + std::map<CScript, std::string> descriptors; CAmount total_in = 0; // loop through the scan objects @@ -2140,7 +2197,11 @@ UniValue scantxoutset(const JSONRPCRequest& request) if (!desc->Expand(i, provider, scripts, provider)) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Cannot derive script without private keys: '%s'", desc_str)); } - needles.insert(scripts.begin(), scripts.end()); + for (const auto& script : scripts) { + std::string inferred = InferDescriptor(script, provider)->ToString(); + needles.emplace(script); + descriptors.emplace(std::move(script), std::move(inferred)); + } } } @@ -2173,6 +2234,7 @@ UniValue scantxoutset(const JSONRPCRequest& request) unspent.pushKV("txid", outpoint.hash.GetHex()); unspent.pushKV("vout", (int32_t)outpoint.n); unspent.pushKV("scriptPubKey", HexStr(txo.scriptPubKey.begin(), txo.scriptPubKey.end())); + unspent.pushKV("desc", descriptors[txo.scriptPubKey]); unspent.pushKV("amount", ValueFromAmount(txo.nValue)); unspent.pushKV("height", (int32_t)coin.nHeight); diff --git a/src/rpc/blockchain.h b/src/rpc/blockchain.h index add335eb8a..529132d033 100644 --- a/src/rpc/blockchain.h +++ b/src/rpc/blockchain.h @@ -27,7 +27,7 @@ double GetDifficulty(const CBlockIndex* blockindex); void RPCNotifyBlockChange(bool ibd, const CBlockIndex *); /** Block description to JSON */ -UniValue blockToJSON(const CBlock& block, const CBlockIndex* blockindex, bool txDetails = false); +UniValue blockToJSON(const CBlock& block, const CBlockIndex* tip, const CBlockIndex* blockindex, bool txDetails = false); /** Mempool information to JSON */ UniValue mempoolInfoToJSON(); @@ -36,7 +36,7 @@ UniValue mempoolInfoToJSON(); UniValue mempoolToJSON(bool fVerbose = false); /** Block header to JSON */ -UniValue blockheaderToJSON(const CBlockIndex* blockindex); +UniValue blockheaderToJSON(const CBlockIndex* tip, const CBlockIndex* blockindex); /** Used by getblockstats to get feerates at different percentiles by weight */ void CalculatePercentilesByWeight(CAmount result[NUM_GETBLOCKSTATS_PERCENTILES], std::vector<std::pair<CAmount, int64_t>>& scores, int64_t total_weight); diff --git a/src/rpc/client.cpp b/src/rpc/client.cpp index 649e222c39..6f1bfb03d1 100644 --- a/src/rpc/client.cpp +++ b/src/rpc/client.cpp @@ -5,7 +5,7 @@ #include <rpc/client.h> #include <rpc/protocol.h> -#include <util.h> +#include <util/system.h> #include <set> #include <stdint.h> @@ -45,7 +45,6 @@ static const CRPCConvertParam vRPCConvertParams[] = { "listreceivedbyaddress", 0, "minconf" }, { "listreceivedbyaddress", 1, "include_empty" }, { "listreceivedbyaddress", 2, "include_watchonly" }, - { "listreceivedbyaddress", 3, "address_filter" }, { "listreceivedbylabel", 0, "minconf" }, { "listreceivedbylabel", 1, "include_empty" }, { "listreceivedbylabel", 2, "include_watchonly" }, @@ -148,7 +147,6 @@ static const CRPCConvertParam vRPCConvertParams[] = { "logging", 0, "include" }, { "logging", 1, "exclude" }, { "disconnectnode", 1, "nodeid" }, - { "addwitnessaddress", 1, "p2sh" }, // Echo with conversion (For testing only) { "echojson", 0, "arg0" }, { "echojson", 1, "arg1" }, @@ -164,6 +162,7 @@ static const CRPCConvertParam vRPCConvertParams[] = { "rescanblockchain", 1, "stop_height"}, { "createwallet", 1, "disable_private_keys"}, { "getnodeaddresses", 0, "count"}, + { "stop", 0, "wait" }, }; // clang-format on diff --git a/src/rpc/mining.cpp b/src/rpc/mining.cpp index 1b2fc2c156..2fd6f99be5 100644 --- a/src/rpc/mining.cpp +++ b/src/rpc/mining.cpp @@ -18,10 +18,11 @@ #include <rpc/blockchain.h> #include <rpc/mining.h> #include <rpc/server.h> +#include <rpc/util.h> #include <shutdown.h> #include <txmempool.h> -#include <util.h> -#include <utilstrencodings.h> +#include <util/strencodings.h> +#include <util/system.h> #include <validation.h> #include <validationinterface.h> #include <versionbitsinfo.h> @@ -86,13 +87,15 @@ static UniValue getnetworkhashps(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() > 2) throw std::runtime_error( - "getnetworkhashps ( nblocks height )\n" - "\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" - "Pass in [height] to estimate the network speed at the time when a certain block was found.\n" - "\nArguments:\n" - "1. nblocks (numeric, optional, default=120) The number of blocks, or -1 for blocks since last difficulty change.\n" - "2. height (numeric, optional, default=-1) To estimate at the time of the given height.\n" + 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" + "Pass in [height] to estimate the network speed at the time when a certain block was found.\n", + { + {"nblocks", RPCArg::Type::NUM, /* opt */ true, /* default_val */ "120", "The number of blocks, or -1 for blocks since last difficulty change."}, + {"height", RPCArg::Type::NUM, /* opt */ true, /* default_val */ "-1", "To estimate at the time of the given height."}, + }} + .ToString() + "\nResult:\n" "x (numeric) Hashes per second estimated\n" "\nExamples:\n" @@ -156,17 +159,21 @@ static UniValue generatetoaddress(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() < 2 || request.params.size() > 3) throw std::runtime_error( - "generatetoaddress nblocks address (maxtries)\n" - "\nMine blocks immediately to a specified address (before the RPC call returns)\n" - "\nArguments:\n" - "1. nblocks (numeric, required) How many blocks are generated immediately.\n" - "2. address (string, required) The address to send the newly generated bitcoin to.\n" - "3. maxtries (numeric, optional) How many iterations to try (default = 1000000).\n" + RPCHelpMan{"generatetoaddress", + "\nMine blocks immediately to a specified address (before the RPC call returns)\n", + { + {"nblocks", RPCArg::Type::NUM, /* opt */ false, /* default_val */ "", "How many blocks are generated immediately."}, + {"address", RPCArg::Type::STR, /* opt */ false, /* default_val */ "", "The address to send the newly generated bitcoin to."}, + {"maxtries", RPCArg::Type::NUM, /* opt */ true, /* default_val */ "1000000", "How many iterations to try."}, + }} + .ToString() + "\nResult:\n" "[ blockhashes ] (array) hashes of blocks generated\n" "\nExamples:\n" "\nGenerate 11 blocks to myaddress\n" + HelpExampleCli("generatetoaddress", "11 \"myaddress\"") + + "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", "") ); int nGenerate = request.params[0].get_int(); @@ -190,8 +197,9 @@ static UniValue getmininginfo(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() != 0) throw std::runtime_error( - "getmininginfo\n" - "\nReturns a json object containing mining-related information." + RPCHelpMan{"getmininginfo", + "\nReturns a json object containing mining-related information.", {}} + .ToString() + "\nResult:\n" "{\n" " \"blocks\": nnn, (numeric) The current block\n" @@ -229,16 +237,18 @@ static UniValue prioritisetransaction(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() != 3) throw std::runtime_error( - "prioritisetransaction <txid> <dummy value> <fee delta>\n" - "Accepts the transaction into mined blocks at a higher (or lower) priority\n" - "\nArguments:\n" - "1. \"txid\" (string, required) The transaction id.\n" - "2. dummy (numeric, optional) API-Compatibility for previous API. Must be zero or null.\n" - " DEPRECATED. For forward compatibility use named arguments and omit this parameter.\n" - "3. fee_delta (numeric, required) The fee value (in satoshis) to add (or subtract, if negative).\n" + RPCHelpMan{"prioritisetransaction", + "Accepts the transaction into mined blocks at a higher (or lower) priority\n", + { + {"txid", RPCArg::Type::STR_HEX, /* opt */ false, /* default_val */ "", "The transaction id."}, + {"dummy", RPCArg::Type::NUM, /* opt */ true, /* default_val */ "null", "API-Compatibility for previous API. Must be zero or null.\n" + " DEPRECATED. For forward compatibility use named arguments and omit this parameter."}, + {"fee_delta", RPCArg::Type::NUM, /* opt */ false, /* default_val */ "", "The fee value (in satoshis) to add (or subtract, if negative).\n" " Note, that this value is not a fee rate. It is a value to modify absolute fee of the TX.\n" " The fee is not actually paid, only the algorithm for selecting transactions into a block\n" - " considers the transaction as it would have paid a higher (or lower) fee.\n" + " considers the transaction as it would have paid a higher (or lower) fee."}, + }} + .ToString() + "\nResult:\n" "true (boolean) Returns true\n" "\nExamples:\n" @@ -292,30 +302,32 @@ static UniValue getblocktemplate(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() > 1) throw std::runtime_error( - "getblocktemplate ( TemplateRequest )\n" - "\nIf the request parameters include a 'mode' key, that is used to explicitly select between the default 'template' request or a 'proposal'.\n" - "It returns data needed to construct a block to work on.\n" - "For full specification, see BIPs 22, 23, 9, and 145:\n" - " https://github.com/bitcoin/bips/blob/master/bip-0022.mediawiki\n" - " https://github.com/bitcoin/bips/blob/master/bip-0023.mediawiki\n" - " https://github.com/bitcoin/bips/blob/master/bip-0009.mediawiki#getblocktemplate_changes\n" - " https://github.com/bitcoin/bips/blob/master/bip-0145.mediawiki\n" - - "\nArguments:\n" - "1. template_request (json object, optional) A json object in the following spec\n" - " {\n" - " \"mode\":\"template\" (string, optional) This must be set to \"template\", \"proposal\" (see BIP 23), or omitted\n" - " \"capabilities\":[ (array, optional) A list of strings\n" - " \"support\" (string) client side supported feature, 'longpoll', 'coinbasetxn', 'coinbasevalue', 'proposal', 'serverlist', 'workid'\n" - " ,...\n" - " ],\n" - " \"rules\":[ (array, optional) A list of strings\n" - " \"support\" (string) client side supported softfork deployment\n" - " ,...\n" - " ]\n" - " }\n" - "\n" - + RPCHelpMan{"getblocktemplate", + "\nIf the request parameters include a 'mode' key, that is used to explicitly select between the default 'template' request or a 'proposal'.\n" + "It returns data needed to construct a block to work on.\n" + "For full specification, see BIPs 22, 23, 9, and 145:\n" + " https://github.com/bitcoin/bips/blob/master/bip-0022.mediawiki\n" + " https://github.com/bitcoin/bips/blob/master/bip-0023.mediawiki\n" + " https://github.com/bitcoin/bips/blob/master/bip-0009.mediawiki#getblocktemplate_changes\n" + " https://github.com/bitcoin/bips/blob/master/bip-0145.mediawiki\n", + { + {"template_request", RPCArg::Type::OBJ, /* opt */ false, /* default_val */ "", "A json object in the following spec", + { + {"mode", RPCArg::Type::STR, /* opt */ true, /* default_val */ "", "This must be set to \"template\", \"proposal\" (see BIP 23), or omitted"}, + {"capabilities", RPCArg::Type::ARR, /* opt */ true, /* default_val */ "", "A list of strings", + { + {"support", RPCArg::Type::STR, /* opt */ true, /* default_val */ "", "client side supported feature, 'longpoll', 'coinbasetxn', 'coinbasevalue', 'proposal', 'serverlist', 'workid'"}, + }, + }, + {"rules", RPCArg::Type::ARR, /* opt */ false, /* default_val */ "", "A list of strings", + { + {"support", RPCArg::Type::STR, /* opt */ true, /* default_val */ "", "client side supported softfork deployment"}, + }, + }, + }, + "\"template_request\""}, + }} + .ToString() + "\nResult:\n" "{\n" " \"version\" : n, (numeric) The preferred block version\n" @@ -362,8 +374,8 @@ static UniValue getblocktemplate(const JSONRPCRequest& request) "}\n" "\nExamples:\n" - + HelpExampleCli("getblocktemplate", "") - + HelpExampleRpc("getblocktemplate", "") + + HelpExampleCli("getblocktemplate", "{\"rules\": [\"segwit\"]}") + + HelpExampleRpc("getblocktemplate", "{\"rules\": [\"segwit\"]}") ); LOCK(cs_main); @@ -491,21 +503,17 @@ static UniValue getblocktemplate(const JSONRPCRequest& request) } const struct VBDeploymentInfo& segwit_info = VersionBitsDeploymentInfo[Consensus::DEPLOYMENT_SEGWIT]; - // If the caller is indicating segwit support, then allow CreateNewBlock() - // to select witness transactions, after segwit activates (otherwise - // don't). - bool fSupportsSegwit = setClientRules.find(segwit_info.name) != setClientRules.end(); + // GBT must be called with 'segwit' set in the rules + if (setClientRules.count(segwit_info.name) != 1) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "getblocktemplate must be called with the segwit rule set (call with {\"rules\": [\"segwit\"]})"); + } // Update block static CBlockIndex* pindexPrev; static int64_t nStart; static std::unique_ptr<CBlockTemplate> pblocktemplate; - // Cache whether the last invocation was with segwit support, to avoid returning - // a segwit-block to a non-segwit caller. - static bool fLastTemplateSupportsSegwit = true; if (pindexPrev != chainActive.Tip() || - (mempool.GetTransactionsUpdated() != nTransactionsUpdatedLast && GetTime() - nStart > 5) || - fLastTemplateSupportsSegwit != fSupportsSegwit) + (mempool.GetTransactionsUpdated() != nTransactionsUpdatedLast && GetTime() - nStart > 5)) { // Clear pindexPrev so future calls make a new block, despite any failures from here on pindexPrev = nullptr; @@ -514,11 +522,10 @@ static UniValue getblocktemplate(const JSONRPCRequest& request) nTransactionsUpdatedLast = mempool.GetTransactionsUpdated(); CBlockIndex* pindexPrevNew = chainActive.Tip(); nStart = GetTime(); - fLastTemplateSupportsSegwit = fSupportsSegwit; // Create new block CScript scriptDummy = CScript() << OP_TRUE; - pblocktemplate = BlockAssembler(Params()).CreateNewBlock(scriptDummy, fSupportsSegwit); + pblocktemplate = BlockAssembler(Params()).CreateNewBlock(scriptDummy); if (!pblocktemplate) throw JSONRPCError(RPC_OUT_OF_MEMORY, "Out of memory"); @@ -670,7 +677,7 @@ static UniValue getblocktemplate(const JSONRPCRequest& request) result.pushKV("bits", strprintf("%08x", pblock->nBits)); result.pushKV("height", (int64_t)(pindexPrev->nHeight+1)); - if (!pblocktemplate->vchCoinbaseCommitment.empty() && fSupportsSegwit) { + if (!pblocktemplate->vchCoinbaseCommitment.empty()) { result.pushKV("default_witness_commitment", HexStr(pblocktemplate->vchCoinbaseCommitment.begin(), pblocktemplate->vchCoinbaseCommitment.end())); } @@ -700,13 +707,14 @@ 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( - "submitblock \"hexdata\" ( \"dummy\" )\n" - "\nAttempts to submit new block to network.\n" - "See https://en.bitcoin.it/wiki/BIP_0022 for full specification.\n" - - "\nArguments\n" - "1. \"hexdata\" (string, required) the hex-encoded block data to submit\n" - "2. \"dummy\" (optional) dummy value, for compatibility with BIP22. This value is ignored.\n" + RPCHelpMan{"submitblock", + "\nAttempts to submit new block to network.\n" + "See https://en.bitcoin.it/wiki/BIP_0022 for full specification.\n", + { + {"hexdata", RPCArg::Type::STR_HEX, /* opt */ false, /* default_val */ "", "the hex-encoded block data to submit"}, + {"dummy", RPCArg::Type::STR, /* opt */ true, /* default_val */ "ignored", "dummy value, for compatibility with BIP22. This value is ignored."}, + }} + .ToString() + "\nResult:\n" "\nExamples:\n" + HelpExampleCli("submitblock", "\"mydata\"") @@ -764,11 +772,13 @@ static UniValue submitheader(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() != 1) { throw std::runtime_error( - "submitheader \"hexdata\"\n" - "\nDecode the given hexdata as a header and submit it as a candidate chain tip if valid." - "\nThrows when the header is invalid.\n" - "\nArguments\n" - "1. \"hexdata\" (string, required) the hex-encoded block header data\n" + 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", + { + {"hexdata", RPCArg::Type::STR_HEX, /* opt */ false, /* default_val */ "", "the hex-encoded block header data"}, + }} + .ToString() + "\nResult:\n" "None" "\nExamples:\n" + @@ -800,22 +810,24 @@ static UniValue estimatesmartfee(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) throw std::runtime_error( - "estimatesmartfee conf_target (\"estimate_mode\")\n" - "\nEstimates the approximate fee per kilobyte needed for a transaction to begin\n" - "confirmation within conf_target blocks if possible and return the number of blocks\n" - "for which the estimate is valid. Uses virtual transaction size as defined\n" - "in BIP 141 (witness data is discounted).\n" - "\nArguments:\n" - "1. conf_target (numeric) Confirmation target in blocks (1 - 1008)\n" - "2. \"estimate_mode\" (string, optional, default=CONSERVATIVE) The fee estimate mode.\n" + RPCHelpMan{"estimatesmartfee", + "\nEstimates the approximate fee per kilobyte needed for a transaction to begin\n" + "confirmation within conf_target blocks if possible and return the number of blocks\n" + "for which the estimate is valid. Uses virtual transaction size as defined\n" + "in BIP 141 (witness data is discounted).\n", + { + {"conf_target", RPCArg::Type::NUM, /* opt */ false, /* default_val */ "", "Confirmation target in blocks (1 - 1008)"}, + {"estimate_mode", RPCArg::Type::STR, /* opt */ true, /* default_val */ "CONSERVATIVE", "The fee estimate mode.\n" " Whether to return a more conservative estimate which also satisfies\n" " a longer history. A conservative estimate potentially returns a\n" " higher feerate and is more likely to be sufficient for the desired\n" " target, but is not as responsive to short term drops in the\n" " prevailing fee market. Must be one of:\n" - " \"UNSET\" (defaults to CONSERVATIVE)\n" + " \"UNSET\"\n" " \"ECONOMICAL\"\n" - " \"CONSERVATIVE\"\n" + " \"CONSERVATIVE\""}, + }} + .ToString() + "\nResult:\n" "{\n" " \"feerate\" : x.x, (numeric, optional) estimate fee rate in " + CURRENCY_UNIT + "/kB\n" @@ -861,19 +873,21 @@ static UniValue estimaterawfee(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) throw std::runtime_error( - "estimaterawfee conf_target (threshold)\n" - "\nWARNING: This interface is unstable and may disappear or change!\n" - "\nWARNING: This is an advanced API call that is tightly coupled to the specific\n" - " implementation of fee estimation. The parameters it can be called with\n" - " and the results it returns will change if the internal implementation changes.\n" - "\nEstimates the approximate fee per kilobyte needed for a transaction to begin\n" - "confirmation within conf_target blocks if possible. Uses virtual transaction size as\n" - "defined in BIP 141 (witness data is discounted).\n" - "\nArguments:\n" - "1. conf_target (numeric) Confirmation target in blocks (1 - 1008)\n" - "2. threshold (numeric, optional) The proportion of transactions in a given feerate range that must have been\n" + RPCHelpMan{"estimaterawfee", + "\nWARNING: This interface is unstable and may disappear or change!\n" + "\nWARNING: This is an advanced API call that is tightly coupled to the specific\n" + " implementation of fee estimation. The parameters it can be called with\n" + " and the results it returns will change if the internal implementation changes.\n" + "\nEstimates the approximate fee per kilobyte needed for a transaction to begin\n" + "confirmation within conf_target blocks if possible. Uses virtual transaction size as\n" + "defined in BIP 141 (witness data is discounted).\n", + { + {"conf_target", RPCArg::Type::NUM, /* opt */ false, /* default_val */ "", "Confirmation target in blocks (1 - 1008)"}, + {"threshold", RPCArg::Type::NUM, /* opt */ true, /* default_val */ "0.95", "The proportion of transactions in a given feerate range that must have been\n" " confirmed within conf_target in order to consider those feerates as high enough and proceed to check\n" - " lower buckets. Default: 0.95\n" + " lower buckets."}, + }} + .ToString() + "\nResult:\n" "{\n" " \"short\" : { (json object, optional) estimate for short time horizon\n" diff --git a/src/rpc/misc.cpp b/src/rpc/misc.cpp index 0f3b601414..18c867bff3 100644 --- a/src/rpc/misc.cpp +++ b/src/rpc/misc.cpp @@ -17,8 +17,8 @@ #include <rpc/server.h> #include <rpc/util.h> #include <timedata.h> -#include <util.h> -#include <utilstrencodings.h> +#include <util/system.h> +#include <util/strencodings.h> #include <warnings.h> #include <stdint.h> @@ -32,19 +32,21 @@ static UniValue validateaddress(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() != 1) throw std::runtime_error( - "validateaddress \"address\"\n" - "\nReturn information about the given bitcoin address.\n" - "DEPRECATION WARNING: Parts of this command have been deprecated and moved to getaddressinfo. Clients must\n" - "transition to using getaddressinfo to access this information before upgrading to v0.18. The following deprecated\n" - "fields have moved to getaddressinfo and will only be shown here with -deprecatedrpc=validateaddress: ismine, iswatchonly,\n" - "script, hex, pubkeys, sigsrequired, pubkey, addresses, embedded, iscompressed, account, timestamp, hdkeypath, kdmasterkeyid.\n" - "\nArguments:\n" - "1. \"address\" (string, required) The bitcoin address to validate\n" + RPCHelpMan{"validateaddress", + "\nReturn information about the given bitcoin address.\n" + "DEPRECATION WARNING: Parts of this command have been deprecated and moved to getaddressinfo. Clients must\n" + "transition to using getaddressinfo to access this information before upgrading to v0.18. The following deprecated\n" + "fields have moved to getaddressinfo and will only be shown here with -deprecatedrpc=validateaddress: ismine, iswatchonly,\n" + "script, hex, pubkeys, sigsrequired, pubkey, addresses, embedded, iscompressed, account, timestamp, hdkeypath, kdmasterkeyid.\n", + { + {"address", RPCArg::Type::STR, /* opt */ false, /* default_val */ "", "The bitcoin address to validate"}, + }} + .ToString() + "\nResult:\n" "{\n" " \"isvalid\" : true|false, (boolean) If the address is valid or not. If not, this is the only property returned.\n" " \"address\" : \"address\", (string) The bitcoin address validated\n" - " \"scriptPubKey\" : \"hex\", (string) The hex encoded scriptPubKey generated by the address\n" + " \"scriptPubKey\" : \"hex\", (string) The hex-encoded scriptPubKey generated by the address\n" " \"isscript\" : true|false, (boolean) If the key is a script\n" " \"iswitness\" : true|false, (boolean) If the address is a witness address\n" " \"witness_version\" : version (numeric, optional) The version number of the witness program\n" @@ -78,18 +80,19 @@ static UniValue createmultisig(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() < 2 || request.params.size() > 3) { - std::string msg = "createmultisig nrequired [\"key\",...] ( \"address_type\" )\n" - "\nCreates a multi-signature address with n signature of m keys required.\n" - "It returns a json object with the address and redeemScript.\n" - "\nArguments:\n" - "1. nrequired (numeric, required) The number of required signatures out of the n keys.\n" - "2. \"keys\" (string, required) A json array of hex-encoded public keys\n" - " [\n" - " \"key\" (string) The hex-encoded public key\n" - " ,...\n" - " ]\n" - "3. \"address_type\" (string, optional) The address type to use. Options are \"legacy\", \"p2sh-segwit\", and \"bech32\". Default is legacy.\n" - + 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", + { + {"nrequired", RPCArg::Type::NUM, /* opt */ false, /* default_val */ "", "The number of required signatures out of the n keys."}, + {"keys", RPCArg::Type::ARR, /* opt */ false, /* default_val */ "", "A json array of hex-encoded public keys.", + { + {"key", RPCArg::Type::STR_HEX, /* opt */ false, /* default_val */ "", "The hex-encoded public key"}, + }}, + {"address_type", RPCArg::Type::STR, /* opt */ true, /* default_val */ "legacy", "The address type to use. Options are \"legacy\", \"p2sh-segwit\", and \"bech32\"."}, + }} + .ToString() + "\nResult:\n" "{\n" " \"address\":\"multisigaddress\", (string) The value of the new multisig address.\n" @@ -99,7 +102,7 @@ static UniValue createmultisig(const JSONRPCRequest& request) "\nExamples:\n" "\nCreate a multisig address from 2 public keys\n" + HelpExampleCli("createmultisig", "2 \"[\\\"03789ed0bb717d88f7d321a368d905e7430207ebbd82bd342cf11ae157a7ace5fd\\\",\\\"03dbc6764b8884a92e871274b87583e6d5c2a58819473e17e107ef3f6aa5a61626\\\"]\"") + - "\nAs a json rpc call\n" + "\nAs a JSON-RPC call\n" + HelpExampleRpc("createmultisig", "2, \"[\\\"03789ed0bb717d88f7d321a368d905e7430207ebbd82bd342cf11ae157a7ace5fd\\\",\\\"03dbc6764b8884a92e871274b87583e6d5c2a58819473e17e107ef3f6aa5a61626\\\"]\"") ; throw std::runtime_error(msg); @@ -142,12 +145,14 @@ static UniValue verifymessage(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() != 3) throw std::runtime_error( - "verifymessage \"address\" \"signature\" \"message\"\n" - "\nVerify a signed message\n" - "\nArguments:\n" - "1. \"address\" (string, required) The bitcoin address to use for the signature.\n" - "2. \"signature\" (string, required) The signature provided by the signer in base 64 encoding (see signmessage).\n" - "3. \"message\" (string, required) The message that was signed.\n" + RPCHelpMan{"verifymessage", + "\nVerify a signed message\n", + { + {"address", RPCArg::Type::STR, /* opt */ false, /* default_val */ "", "The bitcoin address to use for the signature."}, + {"signature", RPCArg::Type::STR, /* opt */ false, /* default_val */ "", "The signature provided by the signer in base 64 encoding (see signmessage)."}, + {"message", RPCArg::Type::STR, /* opt */ false, /* default_val */ "", "The message that was signed."}, + }} + .ToString() + "\nResult:\n" "true|false (boolean) If the signature is verified or not.\n" "\nExamples:\n" @@ -157,7 +162,7 @@ static UniValue verifymessage(const JSONRPCRequest& request) + HelpExampleCli("signmessage", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\" \"my message\"") + "\nVerify the signature\n" + HelpExampleCli("verifymessage", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\" \"signature\" \"my message\"") + - "\nAs json rpc\n" + "\nAs a JSON-RPC call\n" + HelpExampleRpc("verifymessage", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\", \"signature\", \"my message\"") ); @@ -198,11 +203,13 @@ static UniValue signmessagewithprivkey(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() != 2) throw std::runtime_error( - "signmessagewithprivkey \"privkey\" \"message\"\n" - "\nSign a message with the private key of an address\n" - "\nArguments:\n" - "1. \"privkey\" (string, required) The private key to sign the message with.\n" - "2. \"message\" (string, required) The message to create a signature of.\n" + RPCHelpMan{"signmessagewithprivkey", + "\nSign a message with the private key of an address\n", + { + {"privkey", RPCArg::Type::STR, /* opt */ false, /* default_val */ "", "The private key to sign the message with."}, + {"message", RPCArg::Type::STR, /* opt */ false, /* default_val */ "", "The message to create a signature of."}, + }} + .ToString() + "\nResult:\n" "\"signature\" (string) The signature of the message encoded in base 64\n" "\nExamples:\n" @@ -210,7 +217,7 @@ static UniValue signmessagewithprivkey(const JSONRPCRequest& request) + HelpExampleCli("signmessagewithprivkey", "\"privkey\" \"my message\"") + "\nVerify the signature\n" + HelpExampleCli("verifymessage", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\" \"signature\" \"my message\"") + - "\nAs json rpc\n" + "\nAs a JSON-RPC call\n" + HelpExampleRpc("signmessagewithprivkey", "\"privkey\", \"my message\"") ); @@ -237,11 +244,13 @@ static UniValue setmocktime(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() != 1) throw std::runtime_error( - "setmocktime timestamp\n" - "\nSet the local time to given timestamp (-regtest only)\n" - "\nArguments:\n" - "1. timestamp (integer, required) Unix seconds-since-epoch timestamp\n" - " Pass 0 to go back to using the system time." + RPCHelpMan{"setmocktime", + "\nSet the local time to given timestamp (-regtest only)\n", + { + {"timestamp", RPCArg::Type::NUM, /* opt */ false, /* default_val */ "", "Unix seconds-since-epoch timestamp\n" + " Pass 0 to go back to using the system time."}, + }} + .ToString() ); if (!Params().MineBlocksOnDemand()) @@ -299,12 +308,14 @@ static UniValue getmemoryinfo(const JSONRPCRequest& request) */ if (request.fHelp || request.params.size() > 1) throw std::runtime_error( - "getmemoryinfo (\"mode\")\n" - "Returns an object containing information about memory usage.\n" - "Arguments:\n" - "1. \"mode\" determines what kind of information is returned. This argument is optional, the default mode is \"stats\".\n" + RPCHelpMan{"getmemoryinfo", + "Returns an object containing information about memory usage.\n", + { + {"mode", RPCArg::Type::STR, /* opt */ true, /* default_val */ "\"stats\"", "determines what kind of information is returned.\n" " - \"stats\" returns general statistics about memory usage in the daemon.\n" - " - \"mallocinfo\" returns an XML string describing low-level heap state (only available if compiled with glibc 2.10+).\n" + " - \"mallocinfo\" returns an XML string describing low-level heap state (only available if compiled with glibc 2.10+)."}, + }} + .ToString() + "\nResult (mode \"stats\"):\n" "{\n" " \"locked\": { (json object) Information about locked memory manager\n" @@ -361,7 +372,7 @@ UniValue logging(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() > 2) { throw std::runtime_error( - "logging ( <include> <exclude> )\n" + RPCHelpMan{"logging", "Gets and sets the logging configuration.\n" "When called without an argument, returns the list of categories with status that are currently being debug logged or not.\n" "When called with arguments, adds or removes categories from debug logging and return the lists above.\n" @@ -371,17 +382,18 @@ UniValue logging(const JSONRPCRequest& request) "In addition, the following are available as category names with special meanings:\n" " - \"all\", \"1\" : represent all logging categories.\n" " - \"none\", \"0\" : even if other logging categories are specified, ignore all of them.\n" - "\nArguments:\n" - "1. \"include\" (array of strings, optional) A json array of categories to add debug logging\n" - " [\n" - " \"category\" (string) the valid logging category\n" - " ,...\n" - " ]\n" - "2. \"exclude\" (array of strings, optional) A json array of categories to remove debug logging\n" - " [\n" - " \"category\" (string) the valid logging category\n" - " ,...\n" - " ]\n" + , + { + {"include", RPCArg::Type::ARR, /* opt */ true, /* default_val */ "null", "A json array of categories to add debug logging", + { + {"include_category", RPCArg::Type::STR, /* opt */ false, /* default_val */ "", "the valid logging category"}, + }}, + {"exclude", RPCArg::Type::ARR, /* opt */ true, /* default_val */ "null", "A json array of categories to remove debug logging", + { + {"exclude_category", RPCArg::Type::STR, /* opt */ false, /* default_val */ "", "the valid logging category"}, + }}, + }} + .ToString() + "\nResult:\n" "{ (json object where keys are the logging categories, and values indicates its status\n" " \"category\": 0|1, (numeric) if being debug logged or not. 0:inactive, 1:active\n" @@ -430,11 +442,13 @@ static UniValue echo(const JSONRPCRequest& request) { if (request.fHelp) throw std::runtime_error( - "echo|echojson \"message\" ...\n" - "\nSimply echo back the input arguments. This command is for testing.\n" - "\nThe difference between echo and echojson is that echojson has argument conversion enabled in the client-side table in" - "bitcoin-cli and the GUI. There is no server-side difference." - ); + RPCHelpMan{"echo|echojson ...", + "\nSimply echo back the input arguments. This command is for testing.\n" + "\nThe difference between echo and echojson is that echojson has argument conversion enabled in the client-side table in " + "bitcoin-cli and the GUI. There is no server-side difference.", + {}} + .ToString() + + ""); return request.params; } @@ -446,7 +460,7 @@ static const CRPCCommand commands[] = { "control", "getmemoryinfo", &getmemoryinfo, {"mode"} }, { "control", "logging", &logging, {"include", "exclude"}}, { "util", "validateaddress", &validateaddress, {"address"} }, - { "util", "createmultisig", &createmultisig, {"nrequired","keys"} }, + { "util", "createmultisig", &createmultisig, {"nrequired","keys","address_type"} }, { "util", "verifymessage", &verifymessage, {"address","signature","message"} }, { "util", "signmessagewithprivkey", &signmessagewithprivkey, {"privkey","message"} }, diff --git a/src/rpc/net.cpp b/src/rpc/net.cpp index 846d90cd0a..7994d3b125 100644 --- a/src/rpc/net.cpp +++ b/src/rpc/net.cpp @@ -1,23 +1,25 @@ -// 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/server.h> +#include <banman.h> #include <chainparams.h> #include <clientversion.h> #include <core_io.h> -#include <validation.h> #include <net.h> #include <net_processing.h> #include <netbase.h> #include <policy/policy.h> #include <rpc/protocol.h> +#include <rpc/util.h> #include <sync.h> #include <timedata.h> #include <ui_interface.h> -#include <util.h> -#include <utilstrencodings.h> +#include <util/strencodings.h> +#include <util/system.h> +#include <validation.h> #include <version.h> #include <warnings.h> @@ -27,8 +29,9 @@ static UniValue getconnectioncount(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() != 0) throw std::runtime_error( - "getconnectioncount\n" - "\nReturns the number of connections to other nodes.\n" + RPCHelpMan{"getconnectioncount", + "\nReturns the number of connections to other nodes.\n", {}} + .ToString() + "\nResult:\n" "n (numeric) The connection count\n" "\nExamples:\n" @@ -46,10 +49,12 @@ static UniValue ping(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() != 0) throw std::runtime_error( - "ping\n" - "\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" - "Ping command is handled in queue with all other commands, so it measures processing backlog, not just network ping.\n" + 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" + "Ping command is handled in queue with all other commands, so it measures processing backlog, not just network ping.\n", + {}} + .ToString() + "\nExamples:\n" + HelpExampleCli("ping", "") + HelpExampleRpc("ping", "") @@ -69,8 +74,9 @@ static UniValue getpeerinfo(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() != 0) throw std::runtime_error( - "getpeerinfo\n" - "\nReturns data about each connected network node as a json array of objects.\n" + RPCHelpMan{"getpeerinfo", + "\nReturns data about each connected network node as a json array of objects.\n", {}} + .ToString() + "\nResult:\n" "[\n" " {\n" @@ -104,11 +110,15 @@ static UniValue getpeerinfo(const JSONRPCRequest& request) " \"whitelisted\": true|false, (boolean) Whether the peer is whitelisted\n" " \"minfeefilter\": n, (numeric) The minimum fee rate for transactions this peer accepts\n" " \"bytessent_per_msg\": {\n" - " \"addr\": n, (numeric) The total bytes sent aggregated by message type\n" + " \"msg\": n, (numeric) The total bytes sent aggregated by message type\n" + " When a message type is not listed in this json object, the bytes sent are 0.\n" + " Only known message types can appear as keys in the object.\n" " ...\n" " },\n" " \"bytesrecv_per_msg\": {\n" - " \"addr\": n, (numeric) The total bytes received aggregated by message type\n" + " \"msg\": n, (numeric) The total bytes received aggregated by message type\n" + " When a message type is not listed in this json object, the bytes received are 0.\n" + " Only known message types can appear as keys in the object and all bytes received of unknown message types are listed under '"+NET_MESSAGE_COMMAND_OTHER+"'.\n" " ...\n" " }\n" " }\n" @@ -173,14 +183,14 @@ static UniValue getpeerinfo(const JSONRPCRequest& request) obj.pushKV("minfeefilter", ValueFromAmount(stats.minFeeFilter)); UniValue sendPerMsgCmd(UniValue::VOBJ); - for (const mapMsgCmdSize::value_type &i : stats.mapSendBytesPerMsgCmd) { + for (const auto& i : stats.mapSendBytesPerMsgCmd) { if (i.second > 0) sendPerMsgCmd.pushKV(i.first, i.second); } obj.pushKV("bytessent_per_msg", sendPerMsgCmd); UniValue recvPerMsgCmd(UniValue::VOBJ); - for (const mapMsgCmdSize::value_type &i : stats.mapRecvBytesPerMsgCmd) { + for (const auto& i : stats.mapRecvBytesPerMsgCmd) { if (i.second > 0) recvPerMsgCmd.pushKV(i.first, i.second); } @@ -200,14 +210,16 @@ static UniValue addnode(const JSONRPCRequest& request) if (request.fHelp || request.params.size() != 2 || (strCommand != "onetry" && strCommand != "add" && strCommand != "remove")) throw std::runtime_error( - "addnode \"node\" \"add|remove|onetry\"\n" - "\nAttempts to add or remove a node from the addnode list.\n" - "Or try a connection to a node once.\n" - "Nodes added using addnode (or -connect) are protected from DoS disconnection and are not required to be\n" - "full nodes/support SegWit as other outbound peers are (though such peers will not be synced from).\n" - "\nArguments:\n" - "1. \"node\" (string, required) The node (see getpeerinfo for nodes)\n" - "2. \"command\" (string, required) 'add' to add a node to the list, 'remove' to remove a node from the list, 'onetry' to try a connection to the node once\n" + RPCHelpMan{"addnode", + "\nAttempts to add or remove a node from the addnode list.\n" + "Or try a connection to a node once.\n" + "Nodes added using addnode (or -connect) are protected from DoS disconnection and are not required to be\n" + "full nodes/support SegWit as other outbound peers are (though such peers will not be synced from).\n", + { + {"node", RPCArg::Type::STR, /* opt */ false, /* default_val */ "", "The node (see getpeerinfo for nodes)"}, + {"command", RPCArg::Type::STR, /* opt */ false, /* default_val */ "", "'add' to add a node to the list, 'remove' to remove a node from the list, 'onetry' to try a connection to the node once"}, + }} + .ToString() + "\nExamples:\n" + HelpExampleCli("addnode", "\"192.168.0.6:8333\" \"onetry\"") + HelpExampleRpc("addnode", "\"192.168.0.6:8333\", \"onetry\"") @@ -243,13 +255,15 @@ static UniValue disconnectnode(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() == 0 || request.params.size() >= 3) throw std::runtime_error( - "disconnectnode \"[address]\" [nodeid]\n" - "\nImmediately disconnects from the specified peer node.\n" - "\nStrictly one out of 'address' and 'nodeid' can be provided to identify the node.\n" - "\nTo disconnect by nodeid, either set 'address' to the empty string, or call using the named 'nodeid' argument only.\n" - "\nArguments:\n" - "1. \"address\" (string, optional) The IP address/port of the node\n" - "2. \"nodeid\" (number, optional) The node ID (see getpeerinfo for node IDs)\n" + 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" + "\nTo disconnect by nodeid, either set 'address' to the empty string, or call using the named 'nodeid' argument only.\n", + { + {"address", RPCArg::Type::STR, /* opt */ true, /* default_val */ "fallback to nodeid", "The IP address/port of the node"}, + {"nodeid", RPCArg::Type::NUM, /* opt */ true, /* default_val */ "fallback to address", "The node ID (see getpeerinfo for node IDs)"}, + }} + .ToString() + "\nExamples:\n" + HelpExampleCli("disconnectnode", "\"192.168.0.6:8333\"") + HelpExampleCli("disconnectnode", "\"\" 1") @@ -286,11 +300,13 @@ static UniValue getaddednodeinfo(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() > 1) throw std::runtime_error( - "getaddednodeinfo ( \"node\" )\n" - "\nReturns information about the given added node, or all added nodes\n" - "(note that onetry addnodes are not listed here)\n" - "\nArguments:\n" - "1. \"node\" (string, optional) If provided, return information about this specific node, otherwise all nodes are returned.\n" + RPCHelpMan{"getaddednodeinfo", + "\nReturns information about the given added node, or all added nodes\n" + "(note that onetry addnodes are not listed here)\n", + { + {"node", RPCArg::Type::STR, /* opt */ true, /* default_val */ "all nodes", "If provided, return information about this specific node, otherwise all nodes are returned."}, + }} + .ToString() + "\nResult:\n" "[\n" " {\n" @@ -353,9 +369,11 @@ static UniValue getnettotals(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() > 0) throw std::runtime_error( - "getnettotals\n" - "\nReturns information about network traffic, including bytes in, bytes out,\n" - "and current time.\n" + RPCHelpMan{"getnettotals", + "\nReturns information about network traffic, including bytes in, bytes out,\n" + "and current time.\n", + {}} + .ToString() + "\nResult:\n" "{\n" " \"totalbytesrecv\": n, (numeric) Total bytes received\n" @@ -406,7 +424,7 @@ static UniValue GetNetworksInfo() UniValue obj(UniValue::VOBJ); GetProxy(network, proxy); obj.pushKV("name", GetNetworkName(network)); - obj.pushKV("limited", IsLimited(network)); + obj.pushKV("limited", !IsReachable(network)); obj.pushKV("reachable", IsReachable(network)); obj.pushKV("proxy", proxy.IsValid() ? proxy.proxy.ToStringIPPort() : std::string()); obj.pushKV("proxy_randomize_credentials", proxy.randomize_credentials); @@ -419,8 +437,9 @@ static UniValue getnetworkinfo(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() != 0) throw std::runtime_error( - "getnetworkinfo\n" - "Returns an object containing various state info regarding P2P networking.\n" + RPCHelpMan{"getnetworkinfo", + "Returns an object containing various state info regarding P2P networking.\n", {}} + .ToString() + "\nResult:\n" "{\n" " \"version\": xxxxx, (numeric) the server version\n" @@ -499,20 +518,23 @@ static UniValue setban(const JSONRPCRequest& request) if (request.fHelp || request.params.size() < 2 || (strCommand != "add" && strCommand != "remove")) throw std::runtime_error( - "setban \"subnet\" \"add|remove\" (bantime) (absolute)\n" - "\nAttempts to add or remove an IP/Subnet from the banned list.\n" - "\nArguments:\n" - "1. \"subnet\" (string, required) The IP/Subnet (see getpeerinfo for nodes IP) with an optional netmask (default is /32 = single IP)\n" - "2. \"command\" (string, required) 'add' to add an IP/Subnet to the list, 'remove' to remove an IP/Subnet from the list\n" - "3. \"bantime\" (numeric, optional) time in seconds how long (or until when if [absolute] is set) the IP is banned (0 or empty means using the default time of 24h which can also be overwritten by the -bantime startup argument)\n" - "4. \"absolute\" (boolean, optional) If set, the bantime must be an absolute timestamp in seconds since epoch (Jan 1 1970 GMT)\n" + RPCHelpMan{"setban", + "\nAttempts to add or remove an IP/Subnet from the banned list.\n", + { + {"subnet", RPCArg::Type::STR, /* opt */ false, /* default_val */ "", "The IP/Subnet (see getpeerinfo for nodes IP) with an optional netmask (default is /32 = single IP)"}, + {"command", RPCArg::Type::STR, /* opt */ false, /* default_val */ "", "'add' to add an IP/Subnet to the list, 'remove' to remove an IP/Subnet from the list"}, + {"bantime", RPCArg::Type::NUM, /* opt */ true, /* default_val */ "0", "time in seconds how long (or until when if [absolute] is set) the IP is banned (0 or empty means using the default time of 24h which can also be overwritten by the -bantime startup argument)"}, + {"absolute", RPCArg::Type::BOOL, /* opt */ true, /* default_val */ "false", "If set, the bantime must be an absolute timestamp in seconds since epoch (Jan 1 1970 GMT)"}, + }} + .ToString() + "\nExamples:\n" + HelpExampleCli("setban", "\"192.168.0.6\" \"add\" 86400") + HelpExampleCli("setban", "\"192.168.0.0/24\" \"add\"") + HelpExampleRpc("setban", "\"192.168.0.6\", \"add\", 86400") ); - if(!g_connman) - throw JSONRPCError(RPC_CLIENT_P2P_DISABLED, "Error: Peer-to-peer functionality missing or disabled"); + if (!g_banman) { + throw JSONRPCError(RPC_DATABASE_ERROR, "Error: Ban database not loaded"); + } CSubNet subNet; CNetAddr netAddr; @@ -534,8 +556,9 @@ static UniValue setban(const JSONRPCRequest& request) if (strCommand == "add") { - if (isSubnet ? g_connman->IsBanned(subNet) : g_connman->IsBanned(netAddr)) + if (isSubnet ? g_banman->IsBanned(subNet) : g_banman->IsBanned(netAddr)) { throw JSONRPCError(RPC_CLIENT_NODE_ALREADY_ADDED, "Error: IP/Subnet already banned"); + } int64_t banTime = 0; //use standard bantime if not specified if (!request.params[2].isNull()) @@ -545,12 +568,23 @@ static UniValue setban(const JSONRPCRequest& request) if (request.params[3].isTrue()) absolute = true; - isSubnet ? g_connman->Ban(subNet, BanReasonManuallyAdded, banTime, absolute) : g_connman->Ban(netAddr, BanReasonManuallyAdded, banTime, absolute); + if (isSubnet) { + g_banman->Ban(subNet, BanReasonManuallyAdded, banTime, absolute); + if (g_connman) { + g_connman->DisconnectNode(subNet); + } + } else { + g_banman->Ban(netAddr, BanReasonManuallyAdded, banTime, absolute); + if (g_connman) { + g_connman->DisconnectNode(netAddr); + } + } } else if(strCommand == "remove") { - if (!( isSubnet ? g_connman->Unban(subNet) : g_connman->Unban(netAddr) )) + if (!( isSubnet ? g_banman->Unban(subNet) : g_banman->Unban(netAddr) )) { throw JSONRPCError(RPC_CLIENT_INVALID_IP_OR_SUBNET, "Error: Unban failed. Requested address/subnet was not previously banned."); + } } return NullUniValue; } @@ -559,18 +593,20 @@ static UniValue listbanned(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() != 0) throw std::runtime_error( - "listbanned\n" - "\nList all banned IPs/Subnets.\n" + RPCHelpMan{"listbanned", + "\nList all banned IPs/Subnets.\n", {}} + .ToString() + "\nExamples:\n" + HelpExampleCli("listbanned", "") + HelpExampleRpc("listbanned", "") ); - if(!g_connman) - throw JSONRPCError(RPC_CLIENT_P2P_DISABLED, "Error: Peer-to-peer functionality missing or disabled"); + if(!g_banman) { + throw JSONRPCError(RPC_DATABASE_ERROR, "Error: Ban database not loaded"); + } banmap_t banMap; - g_connman->GetBanned(banMap); + g_banman->GetBanned(banMap); UniValue bannedAddresses(UniValue::VARR); for (const auto& entry : banMap) @@ -592,16 +628,18 @@ static UniValue clearbanned(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() != 0) throw std::runtime_error( - "clearbanned\n" - "\nClear all banned IPs.\n" + RPCHelpMan{"clearbanned", + "\nClear all banned IPs.\n", {}} + .ToString() + "\nExamples:\n" + HelpExampleCli("clearbanned", "") + HelpExampleRpc("clearbanned", "") ); - if(!g_connman) - throw JSONRPCError(RPC_CLIENT_P2P_DISABLED, "Error: Peer-to-peer functionality missing or disabled"); + if (!g_banman) { + throw JSONRPCError(RPC_DATABASE_ERROR, "Error: Ban database not loaded"); + } - g_connman->ClearBanned(); + g_banman->ClearBanned(); return NullUniValue; } @@ -610,10 +648,12 @@ static UniValue setnetworkactive(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() != 1) { throw std::runtime_error( - "setnetworkactive true|false\n" - "\nDisable/enable all p2p network activity.\n" - "\nArguments:\n" - "1. \"state\" (boolean, required) true to enable networking, false to disable\n" + RPCHelpMan{"setnetworkactive", + "\nDisable/enable all p2p network activity.\n", + { + {"state", RPCArg::Type::BOOL, /* opt */ false, /* default_val */ "", "true to enable networking, false to disable"}, + }} + .ToString() ); } @@ -630,11 +670,12 @@ static UniValue getnodeaddresses(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() > 1) { throw std::runtime_error( - "getnodeaddresses ( count )\n" - "\nReturn known addresses which can potentially be used to find new nodes in the network\n" - "\nArguments:\n" - "1. \"count\" (numeric, optional) How many addresses to return. Limited to the smaller of " + std::to_string(ADDRMAN_GETADDR_MAX) + - " or " + std::to_string(ADDRMAN_GETADDR_MAX_PCT) + "% of all known addresses. (default = 1)\n" + RPCHelpMan{"getnodeaddresses", + "\nReturn known addresses which can potentially be used to find new nodes in the network\n", + { + {"count", RPCArg::Type::NUM, /* opt */ true, /* default_val */ "1", "How many addresses to return. Limited to the smaller of " + std::to_string(ADDRMAN_GETADDR_MAX) + " or " + std::to_string(ADDRMAN_GETADDR_MAX_PCT) + "% of all known addresses."}, + }} + .ToString() + "\nResult:\n" "[\n" " {\n" diff --git a/src/rpc/protocol.cpp b/src/rpc/protocol.cpp index 55bebb5662..23999b305a 100644 --- a/src/rpc/protocol.cpp +++ b/src/rpc/protocol.cpp @@ -7,13 +7,11 @@ #include <random.h> #include <tinyformat.h> -#include <util.h> -#include <utilstrencodings.h> -#include <utiltime.h> +#include <util/system.h> +#include <util/strencodings.h> +#include <util/time.h> #include <version.h> -#include <fstream> - /** * JSON-RPC protocol. Bitcoin speaks version 1.0 for maximum compatibility, * but uses JSON-RPC 1.1/2.0 standards for parts of the 1.0 standard that were @@ -85,9 +83,9 @@ bool GenerateAuthCookie(std::string *cookie_out) /** the umask determines what permissions are used to create this file - * these are set to 077 in init.cpp unless overridden with -sysperms. */ - std::ofstream file; + fsbridge::ofstream file; fs::path filepath_tmp = GetAuthCookieFile(true); - file.open(filepath_tmp.string().c_str()); + file.open(filepath_tmp); if (!file.is_open()) { LogPrintf("Unable to open cookie authentication file %s for writing\n", filepath_tmp.string()); return false; @@ -109,10 +107,10 @@ bool GenerateAuthCookie(std::string *cookie_out) bool GetAuthCookie(std::string *cookie_out) { - std::ifstream file; + fsbridge::ifstream file; std::string cookie; fs::path filepath = GetAuthCookieFile(); - file.open(filepath.string().c_str()); + file.open(filepath); if (!file.is_open()) return false; std::getline(file, cookie); diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp index 7397216506..91de72b70e 100644 --- a/src/rpc/rawtransaction.cpp +++ b/src/rpc/rawtransaction.cpp @@ -9,10 +9,9 @@ #include <consensus/validation.h> #include <core_io.h> #include <index/txindex.h> -#include <keystore.h> -#include <validation.h> -#include <validationinterface.h> +#include <init.h> #include <key_io.h> +#include <keystore.h> #include <merkleblock.h> #include <net.h> #include <policy/policy.h> @@ -20,13 +19,16 @@ #include <primitives/transaction.h> #include <rpc/rawtransaction.h> #include <rpc/server.h> +#include <rpc/util.h> #include <script/script.h> #include <script/script_error.h> #include <script/sign.h> #include <script/standard.h> #include <txmempool.h> #include <uint256.h> -#include <utilstrencodings.h> +#include <util/strencodings.h> +#include <validation.h> +#include <validationinterface.h> #include <future> #include <stdint.h> @@ -64,24 +66,24 @@ static UniValue getrawtransaction(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() < 1 || request.params.size() > 3) throw std::runtime_error( - "getrawtransaction \"txid\" ( verbose \"blockhash\" )\n" - - "\nNOTE: By default this function only works for mempool transactions. If the -txindex option is\n" - "enabled, it also works for blockchain transactions. If the block which contains the transaction\n" - "is known, its hash can be provided even for nodes without -txindex. Note that if a blockhash is\n" - "provided, only that block will be searched and if the transaction is in the mempool or other\n" - "blocks, or if this node does not have the given block available, the transaction will not be found.\n" + RPCHelpMan{"getrawtransaction", + "\nNOTE: By default this function only works for mempool transactions. If the -txindex option is\n" + "enabled, it also works for blockchain transactions. If the block which contains the transaction\n" + "is known, its hash can be provided even for nodes without -txindex. Note that if a blockhash is\n" + "provided, only that block will be searched and if the transaction is in the mempool or other\n" + "blocks, or if this node does not have the given block available, the transaction will not be found.\n" "DEPRECATED: for now, it also works for transactions with unspent outputs.\n" "\nReturn the raw transaction data.\n" "\nIf verbose is 'true', returns an Object with information about 'txid'.\n" "If verbose is 'false' or omitted, returns a string that is serialized, hex-encoded data for 'txid'.\n" - - "\nArguments:\n" - "1. \"txid\" (string, required) The transaction id\n" - "2. verbose (bool, optional, default=false) If false, return a string, otherwise return a json object\n" - "3. \"blockhash\" (string, optional) The block in which to look for the transaction\n" - + , + { + {"txid", RPCArg::Type::STR_HEX, /* opt */ false, /* default_val */ "", "The transaction id"}, + {"verbose", RPCArg::Type::BOOL, /* opt */ true, /* default_val */ "false", "If false, return a string, otherwise return a json object"}, + {"blockhash", RPCArg::Type::STR_HEX, /* opt */ true, /* default_val */ "null", "The block in which to look for the transaction"}, + }} + .ToString() + "\nResult (if verbose is not set or set to false):\n" "\"data\" (string) The serialized, hex-encoded data for 'txid'\n" @@ -128,8 +130,8 @@ static UniValue getrawtransaction(const JSONRPCRequest& request) " ],\n" " \"blockhash\" : \"hash\", (string) the block hash\n" " \"confirmations\" : n, (numeric) The confirmations\n" - " \"time\" : ttt, (numeric) The transaction time in seconds since epoch (Jan 1 1970 GMT)\n" " \"blocktime\" : ttt (numeric) The block time in seconds since epoch (Jan 1 1970 GMT)\n" + " \"time\" : ttt, (numeric) Same as \"blocktime\"\n" "}\n" "\nExamples:\n" @@ -204,19 +206,21 @@ static UniValue gettxoutproof(const JSONRPCRequest& request) { if (request.fHelp || (request.params.size() != 1 && request.params.size() != 2)) throw std::runtime_error( - "gettxoutproof [\"txid\",...] ( blockhash )\n" - "\nReturns a hex-encoded proof that \"txid\" was included in a block.\n" - "\nNOTE: By default this function only works sometimes. This is when there is an\n" - "unspent output in the utxo for this transaction. To make it always work,\n" - "you need to maintain a transaction index, using the -txindex command line option or\n" - "specify the block in which the transaction is included manually (by blockhash).\n" - "\nArguments:\n" - "1. \"txids\" (string) A json array of txids to filter\n" - " [\n" - " \"txid\" (string) A transaction hash\n" - " ,...\n" - " ]\n" - "2. \"blockhash\" (string, optional) If specified, looks for txid in the block with this hash\n" + RPCHelpMan{"gettxoutproof", + "\nReturns a hex-encoded proof that \"txid\" was included in a block.\n" + "\nNOTE: By default this function only works sometimes. This is when there is an\n" + "unspent output in the utxo for this transaction. To make it always work,\n" + "you need to maintain a transaction index, using the -txindex command line option or\n" + "specify the block in which the transaction is included manually (by blockhash).\n", + { + {"txids", RPCArg::Type::ARR, /* opt */ false, /* default_val */ "", "A json array of txids to filter", + { + {"txid", RPCArg::Type::STR_HEX, /* opt */ false, /* default_val */ "", "A transaction hash"}, + }, + }, + {"blockhash", RPCArg::Type::STR_HEX, /* opt */ true, /* default_val */ "null", "If specified, looks for txid in the block with this hash"}, + }} + .ToString() + "\nResult:\n" "\"data\" (string) A string that is a serialized, hex-encoded data for the proof.\n" ); @@ -296,11 +300,13 @@ static UniValue verifytxoutproof(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() != 1) throw std::runtime_error( - "verifytxoutproof \"proof\"\n" - "\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" - "\nArguments:\n" - "1. \"proof\" (string, required) The hex-encoded proof generated by gettxoutproof\n" + RPCHelpMan{"verifytxoutproof", + "\nVerifies that a proof points to a transaction in a block, returning the transaction it commits to\n" + "and throwing an RPC error if the block is not in our best chain\n", + { + {"proof", RPCArg::Type::STR_HEX, /* opt */ false, /* default_val */ "", "The hex-encoded proof generated by gettxoutproof"}, + }} + .ToString() + "\nResult:\n" "[\"txid\"] (array, strings) The txid(s) which the proof commits to, or empty array if the proof can not be validated.\n" ); @@ -346,7 +352,7 @@ CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniVal if (!locktime.isNull()) { int64_t nLockTime = locktime.get_int64(); - if (nLockTime < 0 || nLockTime > std::numeric_limits<uint32_t>::max()) + if (nLockTime < 0 || nLockTime > LOCKTIME_MAX) throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, locktime out of range"); rawTx.nLockTime = nLockTime; } @@ -368,18 +374,18 @@ CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniVal uint32_t nSequence; if (rbfOptIn) { - nSequence = MAX_BIP125_RBF_SEQUENCE; + nSequence = MAX_BIP125_RBF_SEQUENCE; /* CTxIn::SEQUENCE_FINAL - 2 */ } else if (rawTx.nLockTime) { - nSequence = std::numeric_limits<uint32_t>::max() - 1; + nSequence = CTxIn::SEQUENCE_FINAL - 1; } else { - nSequence = std::numeric_limits<uint32_t>::max(); + nSequence = CTxIn::SEQUENCE_FINAL; } // set the sequence number if passed in the parameters object const UniValue& sequenceObj = find_value(o, "sequence"); if (sequenceObj.isNum()) { int64_t seqNr64 = sequenceObj.get_int64(); - if (seqNr64 < 0 || seqNr64 > std::numeric_limits<uint32_t>::max()) { + if (seqNr64 < 0 || seqNr64 > CTxIn::SEQUENCE_FINAL) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, sequence number is out of range"); } else { nSequence = (uint32_t)seqNr64; @@ -391,7 +397,6 @@ CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniVal rawTx.vin.push_back(in); } - std::set<CTxDestination> destinations; if (!outputs_is_obj) { // Translate array of key-value pairs into dict UniValue outputs_dict = UniValue(UniValue::VOBJ); @@ -407,8 +412,17 @@ CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniVal } outputs = std::move(outputs_dict); } + + // Duplicate checking + std::set<CTxDestination> destinations; + bool has_data{false}; + for (const std::string& name_ : outputs.getKeys()) { if (name_ == "data") { + if (has_data) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, duplicate key: data"); + } + has_data = true; std::vector<unsigned char> data = ParseHexV(outputs[name_].getValStr(), "Data"); CTxOut out(0, CScript() << OP_RETURN << data); @@ -431,7 +445,7 @@ CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniVal } } - if (!rbf.isNull() && rawTx.vin.size() > 0 && rbfOptIn != SignalsOptInRBF(rawTx)) { + if (!rbf.isNull() && rawTx.vin.size() > 0 && rbfOptIn != SignalsOptInRBF(CTransaction(rawTx))) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter combination: Sequence number(s) contradict replaceable option"); } @@ -442,38 +456,46 @@ static UniValue createrawtransaction(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() < 2 || request.params.size() > 4) { throw std::runtime_error( - // clang-format off - "createrawtransaction [{\"txid\":\"id\",\"vout\":n},...] [{\"address\":amount},{\"data\":\"hex\"},...] ( locktime ) ( replaceable )\n" - "\nCreate a transaction spending the given inputs and creating new outputs.\n" - "Outputs can be addresses or data.\n" - "Returns hex-encoded raw transaction.\n" - "Note that the transaction's inputs are not signed, and\n" - "it is not stored in the wallet or transmitted to the network.\n" - - "\nArguments:\n" - "1. \"inputs\" (array, required) A json array of json objects\n" - " [\n" - " {\n" - " \"txid\":\"id\", (string, required) The transaction id\n" - " \"vout\":n, (numeric, required) The output number\n" - " \"sequence\":n (numeric, optional) The sequence number\n" - " } \n" - " ,...\n" - " ]\n" - "2. \"outputs\" (array, required) a json array with outputs (key-value pairs)\n" - " [\n" - " {\n" - " \"address\": x.xxx, (obj, optional) A key-value pair. The key (string) is the bitcoin address, the value (float or string) is the amount in " + CURRENCY_UNIT + "\n" - " },\n" - " {\n" - " \"data\": \"hex\" (obj, optional) A key-value pair. The key must be \"data\", the value is hex encoded data\n" - " }\n" - " ,... More key-value pairs of the above form. For compatibility reasons, a dictionary, which holds the key-value pairs directly, is also\n" - " accepted as second parameter.\n" - " ]\n" - "3. locktime (numeric, optional, default=0) Raw locktime. Non-0 value also locktime-activates inputs\n" - "4. replaceable (boolean, optional, default=false) Marks this transaction as BIP125 replaceable.\n" - " Allows this transaction to be replaced by a transaction with higher fees. If provided, it is an error if explicit sequence numbers are incompatible.\n" + RPCHelpMan{"createrawtransaction", + "\nCreate a transaction spending the given inputs and creating new outputs.\n" + "Outputs can be addresses or data.\n" + "Returns hex-encoded raw transaction.\n" + "Note that the transaction's inputs are not signed, and\n" + "it is not stored in the wallet or transmitted to the network.\n", + { + {"inputs", RPCArg::Type::ARR, /* opt */ false, /* default_val */ "", "A json array of json objects", + { + {"", RPCArg::Type::OBJ, /* opt */ true, /* default_val */ "", "", + { + {"txid", RPCArg::Type::STR_HEX, /* opt */ false, /* default_val */ "", "The transaction id"}, + {"vout", RPCArg::Type::NUM, /* opt */ false, /* default_val */ "", "The output number"}, + {"sequence", RPCArg::Type::NUM, /* opt */ true, /* default_val */ "depends on the value of the 'replaceable' and 'locktime' arguments", "The sequence number"}, + }, + }, + }, + }, + {"outputs", RPCArg::Type::ARR, /* opt */ false, /* default_val */ "", "a json array with outputs (key-value pairs), where none of the keys are duplicated.\n" + "That is, each address can only appear once and there can only be one 'data' object.\n" + "For compatibility reasons, a dictionary, which holds the key-value pairs directly, is also\n" + " accepted as second parameter.", + { + {"", RPCArg::Type::OBJ, /* opt */ true, /* default_val */ "", "", + { + {"address", RPCArg::Type::AMOUNT, /* opt */ false, /* default_val */ "", "A key-value pair. The key (string) is the bitcoin address, the value (float or string) is the amount in " + CURRENCY_UNIT}, + }, + }, + {"", RPCArg::Type::OBJ, /* opt */ true, /* default_val */ "", "", + { + {"data", RPCArg::Type::STR_HEX, /* opt */ false, /* default_val */ "", "A key-value pair. The key must be \"data\", the value is hex-encoded data"}, + }, + }, + }, + }, + {"locktime", RPCArg::Type::NUM, /* opt */ true, /* default_val */ "0", "Raw locktime. Non-0 value also locktime-activates inputs"}, + {"replaceable", RPCArg::Type::BOOL, /* opt */ true, /* default_val */ "false", "Marks this transaction as BIP125-replaceable.\n" + " Allows this transaction to be replaced by a transaction with higher fees. If provided, it is an error if explicit sequence numbers are incompatible."}, + }} + .ToString() + "\nResult:\n" "\"transaction\" (string) hex string of the transaction\n" @@ -482,7 +504,6 @@ static UniValue createrawtransaction(const JSONRPCRequest& request) + HelpExampleCli("createrawtransaction", "\"[{\\\"txid\\\":\\\"myid\\\",\\\"vout\\\":0}]\" \"[{\\\"data\\\":\\\"00010203\\\"}]\"") + HelpExampleRpc("createrawtransaction", "\"[{\\\"txid\\\":\\\"myid\\\",\\\"vout\\\":0}]\", \"[{\\\"address\\\":0.01}]\"") + HelpExampleRpc("createrawtransaction", "\"[{\\\"txid\\\":\\\"myid\\\",\\\"vout\\\":0}]\", \"[{\\\"data\\\":\\\"00010203\\\"}]\"") - // clang-format on ); } @@ -496,21 +517,21 @@ static UniValue createrawtransaction(const JSONRPCRequest& request) CMutableTransaction rawTx = ConstructTransaction(request.params[0], request.params[1], request.params[2], request.params[3]); - return EncodeHexTx(rawTx); + return EncodeHexTx(CTransaction(rawTx)); } static UniValue decoderawtransaction(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) throw std::runtime_error( - "decoderawtransaction \"hexstring\" ( iswitness )\n" - "\nReturn a JSON object representing the serialized, hex-encoded transaction.\n" - - "\nArguments:\n" - "1. \"hexstring\" (string, required) The transaction hex string\n" - "2. iswitness (boolean, optional) Whether the transaction hex is a serialized witness transaction\n" - " If iswitness is not present, heuristic tests will be used in decoding\n" - + RPCHelpMan{"decoderawtransaction", + "\nReturn a JSON object representing the serialized, hex-encoded transaction.\n", + { + {"hexstring", RPCArg::Type::STR_HEX, /* opt */ false, /* default_val */ "", "The transaction hex string"}, + {"iswitness", RPCArg::Type::BOOL, /* opt */ true, /* default_val */ "depends on heuristic tests", "Whether the transaction hex is a serialized witness transaction\n" + " If iswitness is not present, heuristic tests will be used in decoding"}, + }} + .ToString() + "\nResult:\n" "{\n" " \"txid\" : \"id\", (string) The transaction id\n" @@ -578,14 +599,16 @@ static UniValue decodescript(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() != 1) throw std::runtime_error( - "decodescript \"hexstring\"\n" - "\nDecode a hex-encoded script.\n" - "\nArguments:\n" - "1. \"hexstring\" (string) the hex encoded script\n" + RPCHelpMan{"decodescript", + "\nDecode a hex-encoded script.\n", + { + {"hexstring", RPCArg::Type::STR_HEX, /* opt */ false, /* default_val */ "", "the hex-encoded script"}, + }} + .ToString() + "\nResult:\n" "{\n" " \"asm\":\"asm\", (string) Script public key\n" - " \"hex\":\"hex\", (string) hex encoded public key\n" + " \"hex\":\"hex\", (string) hex-encoded public key\n" " \"type\":\"type\", (string) The output type\n" " \"reqSigs\": n, (numeric) The required signatures\n" " \"addresses\": [ (json array of string)\n" @@ -671,21 +694,20 @@ static void TxInErrorToJSON(const CTxIn& txin, UniValue& vErrorsRet, const std:: static UniValue combinerawtransaction(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() != 1) throw std::runtime_error( - "combinerawtransaction [\"hexstring\",...]\n" - "\nCombine multiple partially signed transactions into one transaction.\n" - "The combined transaction may be another partially signed transaction or a \n" - "fully signed transaction." - - "\nArguments:\n" - "1. \"txs\" (string) A json array of hex strings of partially signed transactions\n" - " [\n" - " \"hexstring\" (string) A transaction hash\n" - " ,...\n" - " ]\n" - + RPCHelpMan{"combinerawtransaction", + "\nCombine multiple partially signed transactions into one transaction.\n" + "The combined transaction may be another partially signed transaction or a \n" + "fully signed transaction.", + { + {"txs", RPCArg::Type::ARR, /* opt */ false, /* default_val */ "", "A json array of hex strings of partially signed transactions", + { + {"hexstring", RPCArg::Type::STR_HEX, /* opt */ false, /* default_val */ "", "A transaction hash"}, + }, + }, + }} + .ToString() + "\nResult:\n" "\"hex\" (string) The hex-encoded raw transaction with signature(s)\n" @@ -751,10 +773,10 @@ static UniValue combinerawtransaction(const JSONRPCRequest& request) UpdateInput(txin, sigdata); } - return EncodeHexTx(mergedTx); + return EncodeHexTx(CTransaction(mergedTx)); } -UniValue SignTransaction(CMutableTransaction& mtx, const UniValue& prevTxsUnival, CBasicKeyStore *keystore, bool is_temp_keystore, const UniValue& hashType) +UniValue SignTransaction(interfaces::Chain& chain, CMutableTransaction& mtx, const UniValue& prevTxsUnival, CBasicKeyStore *keystore, bool is_temp_keystore, const UniValue& hashType) { // Fetch previous transactions (inputs): CCoinsView viewDummy; @@ -884,7 +906,7 @@ UniValue SignTransaction(CMutableTransaction& mtx, const UniValue& prevTxsUnival bool fComplete = vErrors.empty(); UniValue result(UniValue::VOBJ); - result.pushKV("hex", EncodeHexTx(mtx)); + result.pushKV("hex", EncodeHexTx(CTransaction(mtx))); result.pushKV("complete", fComplete); if (!vErrors.empty()) { result.pushKV("errors", vErrors); @@ -897,39 +919,42 @@ static UniValue signrawtransactionwithkey(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() < 2 || request.params.size() > 4) throw std::runtime_error( - "signrawtransactionwithkey \"hexstring\" [\"privatekey1\",...] ( [{\"txid\":\"id\",\"vout\":n,\"scriptPubKey\":\"hex\",\"redeemScript\":\"hex\"},...] sighashtype )\n" - "\nSign inputs for raw transaction (serialized, hex-encoded).\n" - "The second argument is an array of base58-encoded private\n" - "keys that will be the only keys used to sign the transaction.\n" - "The third optional argument (may be null) is an array of previous transaction outputs that\n" - "this transaction depends on but may not yet be in the block chain.\n" - - "\nArguments:\n" - "1. \"hexstring\" (string, required) The transaction hex string\n" - "2. \"privkeys\" (string, required) A json array of base58-encoded private keys for signing\n" - " [ (json array of strings)\n" - " \"privatekey\" (string) private key in base58-encoding\n" - " ,...\n" - " ]\n" - "3. \"prevtxs\" (string, optional) An json array of previous dependent transaction outputs\n" - " [ (json array of json objects, or 'null' if none provided)\n" - " {\n" - " \"txid\":\"id\", (string, required) The transaction id\n" - " \"vout\":n, (numeric, required) The output number\n" - " \"scriptPubKey\": \"hex\", (string, required) script key\n" - " \"redeemScript\": \"hex\", (string, required for P2SH or P2WSH) redeem script\n" - " \"amount\": value (numeric, required) The amount spent\n" - " }\n" - " ,...\n" - " ]\n" - "4. \"sighashtype\" (string, optional, default=ALL) The signature hash type. Must be one of\n" + RPCHelpMan{"signrawtransactionwithkey", + "\nSign inputs for raw transaction (serialized, hex-encoded).\n" + "The second argument is an array of base58-encoded private\n" + "keys that will be the only keys used to sign the transaction.\n" + "The third optional argument (may be null) is an array of previous transaction outputs that\n" + "this transaction depends on but may not yet be in the block chain.\n", + { + {"hexstring", RPCArg::Type::STR, /* opt */ false, /* default_val */ "", "The transaction hex string"}, + {"privkeys", RPCArg::Type::ARR, /* opt */ false, /* default_val */ "", "A json array of base58-encoded private keys for signing", + { + {"privatekey", RPCArg::Type::STR_HEX, /* opt */ false, /* default_val */ "", "private key in base58-encoding"}, + }, + }, + {"prevtxs", RPCArg::Type::ARR, /* opt */ true, /* default_val */ "null", "A json array of previous dependent transaction outputs", + { + {"", RPCArg::Type::OBJ, /* opt */ true, /* default_val */ "", "", + { + {"txid", RPCArg::Type::STR_HEX, /* opt */ false, /* default_val */ "", "The transaction id"}, + {"vout", RPCArg::Type::NUM, /* opt */ false, /* default_val */ "", "The output number"}, + {"scriptPubKey", RPCArg::Type::STR_HEX, /* opt */ false, /* default_val */ "", "script key"}, + {"redeemScript", RPCArg::Type::STR_HEX, /* opt */ true, /* default_val */ "omitted", "(required for P2SH or P2WSH) redeem script"}, + {"amount", RPCArg::Type::AMOUNT, /* opt */ false, /* default_val */ "", "The amount spent"}, + }, + }, + }, + }, + {"sighashtype", RPCArg::Type::STR, /* opt */ true, /* default_val */ "ALL", "The signature hash type. Must be one of:\n" " \"ALL\"\n" " \"NONE\"\n" " \"SINGLE\"\n" " \"ALL|ANYONECANPAY\"\n" " \"NONE|ANYONECANPAY\"\n" " \"SINGLE|ANYONECANPAY\"\n" - + }, + }} + .ToString() + "\nResult:\n" "{\n" " \"hex\" : \"value\", (string) The hex-encoded raw transaction with signature(s)\n" @@ -969,7 +994,7 @@ static UniValue signrawtransactionwithkey(const JSONRPCRequest& request) keystore.AddKey(key); } - return SignTransaction(mtx, request.params[2], &keystore, true, request.params[3]); + return SignTransaction(*g_rpc_interfaces->chain, mtx, request.params[2], &keystore, true, request.params[3]); } UniValue signrawtransaction(const JSONRPCRequest& request) @@ -984,12 +1009,14 @@ static UniValue sendrawtransaction(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) throw std::runtime_error( - "sendrawtransaction \"hexstring\" ( allowhighfees )\n" - "\nSubmits raw transaction (serialized, hex-encoded) to local node and network.\n" - "\nAlso see createrawtransaction and signrawtransactionwithkey calls.\n" - "\nArguments:\n" - "1. \"hexstring\" (string, required) The hex string of the raw transaction)\n" - "2. allowhighfees (boolean, optional, default=false) Allow high fees\n" + RPCHelpMan{"sendrawtransaction", + "\nSubmits raw transaction (serialized, hex-encoded) to local node and network.\n" + "\nAlso see createrawtransaction and signrawtransactionwithkey calls.\n", + { + {"hexstring", RPCArg::Type::STR_HEX, /* opt */ false, /* default_val */ "", "The hex string of the raw transaction"}, + {"allowhighfees", RPCArg::Type::BOOL, /* opt */ true, /* default_val */ "false", "Allow high fees"}, + }} + .ToString() + "\nResult:\n" "\"hex\" (string) The transaction hash in hex\n" "\nExamples:\n" @@ -999,7 +1026,7 @@ static UniValue sendrawtransaction(const JSONRPCRequest& request) + HelpExampleCli("signrawtransactionwithwallet", "\"myhex\"") + "\nSend the transaction (signed hex)\n" + HelpExampleCli("sendrawtransaction", "\"signedhex\"") + - "\nAs a json rpc call\n" + "\nAs a JSON-RPC call\n" + HelpExampleRpc("sendrawtransaction", "\"signedhex\"") ); @@ -1079,15 +1106,20 @@ static UniValue testmempoolaccept(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) { throw std::runtime_error( - // clang-format off - "testmempoolaccept [\"rawtxs\"] ( allowhighfees )\n" - "\nReturns 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" - "\nArguments:\n" - "1. [\"rawtxs\"] (array, required) An array of hex strings of raw transactions.\n" - " Length must be one for now.\n" - "2. allowhighfees (boolean, optional, default=false) Allow high fees\n" + 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", + { + {"rawtxs", RPCArg::Type::ARR, /* opt */ false, /* default_val */ "", "An array of hex strings of raw transactions.\n" + " Length must be one for now.", + { + {"rawtx", RPCArg::Type::STR_HEX, /* opt */ false, /* default_val */ "", ""}, + }, + }, + {"allowhighfees", RPCArg::Type::BOOL, /* opt */ true, /* default_val */ "false", "Allow high fees"}, + }} + .ToString() + "\nResult:\n" "[ (array) The result of the mempool acceptance test for each raw transaction in the input array.\n" " Length is exactly one for now.\n" @@ -1103,10 +1135,9 @@ static UniValue testmempoolaccept(const JSONRPCRequest& request) "Sign the transaction, and get back the hex\n" + HelpExampleCli("signrawtransactionwithwallet", "\"myhex\"") + "\nTest acceptance of the transaction (signed hex)\n" - + HelpExampleCli("testmempoolaccept", "\"signedhex\"") + - "\nAs a json rpc call\n" + + HelpExampleCli("testmempoolaccept", "[\"signedhex\"]") + + "\nAs a JSON-RPC call\n" + HelpExampleRpc("testmempoolaccept", "[\"signedhex\"]") - // clang-format on ); } @@ -1177,12 +1208,12 @@ UniValue decodepsbt(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() != 1) throw std::runtime_error( - "decodepsbt \"psbt\"\n" - "\nReturn a JSON object representing the serialized, base64-encoded partially signed Bitcoin transaction.\n" - - "\nArguments:\n" - "1. \"psbt\" (string, required) The PSBT base64 string\n" - + RPCHelpMan{"decodepsbt", + "\nReturn a JSON object representing the serialized, base64-encoded partially signed Bitcoin transaction.\n", + { + {"psbt", RPCArg::Type::STR, /* opt */ false, /* default_val */ "", "The PSBT base64 string"}, + }} + .ToString() + "\nResult:\n" "{\n" " \"tx\" : { (json object) The decoded network-serialized unsigned transaction.\n" @@ -1452,16 +1483,17 @@ UniValue combinepsbt(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() != 1) throw std::runtime_error( - "combinepsbt [\"psbt\",...]\n" - "\nCombine multiple partially signed Bitcoin transactions into one transaction.\n" - "Implements the Combiner role.\n" - "\nArguments:\n" - "1. \"txs\" (string) A json array of base64 strings of partially signed transactions\n" - " [\n" - " \"psbt\" (string) A base64 string of a PSBT\n" - " ,...\n" - " ]\n" - + RPCHelpMan{"combinepsbt", + "\nCombine multiple partially signed Bitcoin transactions into one transaction.\n" + "Implements the Combiner role.\n", + { + {"txs", RPCArg::Type::ARR, /* opt */ false, /* default_val */ "", "A json array of base64 strings of partially signed transactions", + { + {"psbt", RPCArg::Type::STR, /* opt */ false, /* default_val */ "", "A base64 string of a PSBT"}, + }, + }, + }} + .ToString() + "\nResult:\n" " \"psbt\" (string) The base64-encoded partially signed transaction\n" "\nExamples:\n" @@ -1505,16 +1537,17 @@ UniValue finalizepsbt(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) throw std::runtime_error( - "finalizepsbt \"psbt\" ( extract )\n" - "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" - "created which has the final_scriptSig and final_scriptWitness fields filled for inputs that are complete.\n" - "Implements the Finalizer and Extractor roles.\n" - "\nArguments:\n" - "1. \"psbt\" (string) A base64 string of a PSBT\n" - "2. \"extract\" (boolean, optional, default=true) If true and the transaction is complete, \n" - " extract and return the complete transaction in normal network serialization instead of the PSBT.\n" - + 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" + "created which has the final_scriptSig and final_scriptWitness fields filled for inputs that are complete.\n" + "Implements the Finalizer and Extractor roles.\n", + { + {"psbt", RPCArg::Type::STR, /* opt */ false, /* default_val */ "", "A base64 string of a PSBT"}, + {"extract", RPCArg::Type::BOOL, /* opt */ true, /* default_val */ "true", "If true and the transaction is complete,\n" + " extract and return the complete transaction in normal network serialization instead of the PSBT."}, + }} + .ToString() + "\nResult:\n" "{\n" " \"psbt\" : \"value\", (string) The base64-encoded partially signed transaction if not extracted\n" @@ -1536,12 +1569,13 @@ UniValue finalizepsbt(const JSONRPCRequest& request) throw JSONRPCError(RPC_DESERIALIZATION_ERROR, strprintf("TX decode failed %s", error)); } - // Get all of the previous transactions + // Finalize input signatures -- in case we have partial signatures that add up to a complete + // signature, but have not combined them yet (e.g. because the combiner that created this + // PartiallySignedTransaction did not understand them), this will combine them into a final + // script. bool complete = true; for (unsigned int i = 0; i < psbtx.tx->vin.size(); ++i) { - PSBTInput& input = psbtx.inputs.at(i); - - complete &= SignPSBTInput(DUMMY_SIGNING_PROVIDER, *psbtx.tx, input, i, 1); + complete &= SignPSBTInput(DUMMY_SIGNING_PROVIDER, psbtx, i, SIGHASH_ALL); } UniValue result(UniValue::VOBJ); @@ -1554,10 +1588,10 @@ UniValue finalizepsbt(const JSONRPCRequest& request) mtx.vin[i].scriptWitness = psbtx.inputs[i].final_script_witness; } ssTx << mtx; - result.pushKV("hex", HexStr(ssTx.begin(), ssTx.end())); + result.pushKV("hex", HexStr(ssTx.str())); } else { ssTx << psbtx; - result.pushKV("psbt", EncodeBase64((unsigned char*)ssTx.data(), ssTx.size())); + result.pushKV("psbt", EncodeBase64(ssTx.str())); } result.pushKV("complete", complete); @@ -1568,33 +1602,43 @@ UniValue createpsbt(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() < 2 || request.params.size() > 4) throw std::runtime_error( - "createpsbt [{\"txid\":\"id\",\"vout\":n},...] [{\"address\":amount},{\"data\":\"hex\"},...] ( locktime ) ( replaceable )\n" - "\nCreates a transaction in the Partially Signed Transaction format.\n" - "Implements the Creator role.\n" - "\nArguments:\n" - "1. \"inputs\" (array, required) A json array of json objects\n" - " [\n" - " {\n" - " \"txid\":\"id\", (string, required) The transaction id\n" - " \"vout\":n, (numeric, required) The output number\n" - " \"sequence\":n (numeric, optional) The sequence number\n" - " } \n" - " ,...\n" - " ]\n" - "2. \"outputs\" (array, required) a json array with outputs (key-value pairs)\n" - " [\n" - " {\n" - " \"address\": x.xxx, (obj, optional) A key-value pair. The key (string) is the bitcoin address, the value (float or string) is the amount in " + CURRENCY_UNIT + "\n" - " },\n" - " {\n" - " \"data\": \"hex\" (obj, optional) A key-value pair. The key must be \"data\", the value is hex encoded data\n" - " }\n" - " ,... More key-value pairs of the above form. For compatibility reasons, a dictionary, which holds the key-value pairs directly, is also\n" - " accepted as second parameter.\n" - " ]\n" - "3. locktime (numeric, optional, default=0) Raw locktime. Non-0 value also locktime-activates inputs\n" - "4. replaceable (boolean, optional, default=false) Marks this transaction as BIP125 replaceable.\n" - " Allows this transaction to be replaced by a transaction with higher fees. If provided, it is an error if explicit sequence numbers are incompatible.\n" + RPCHelpMan{"createpsbt", + "\nCreates a transaction in the Partially Signed Transaction format.\n" + "Implements the Creator role.\n", + { + {"inputs", RPCArg::Type::ARR, /* opt */ false, /* default_val */ "", "A json array of json objects", + { + {"", RPCArg::Type::OBJ, /* opt */ false, /* default_val */ "", "", + { + {"txid", RPCArg::Type::STR_HEX, /* opt */ false, /* default_val */ "", "The transaction id"}, + {"vout", RPCArg::Type::NUM, /* opt */ false, /* default_val */ "", "The output number"}, + {"sequence", RPCArg::Type::NUM, /* opt */ true, /* default_val */ "depends on the value of the 'replaceable' and 'locktime' arguments", "The sequence number"}, + }, + }, + }, + }, + {"outputs", RPCArg::Type::ARR, /* opt */ false, /* default_val */ "", "a json array with outputs (key-value pairs), where none of the keys are duplicated.\n" + "That is, each address can only appear once and there can only be one 'data' object.\n" + "For compatibility reasons, a dictionary, which holds the key-value pairs directly, is also\n" + " accepted as second parameter.", + { + {"", RPCArg::Type::OBJ, /* opt */ true, /* default_val */ "", "", + { + {"address", RPCArg::Type::AMOUNT, /* opt */ false, /* default_val */ "", "A key-value pair. The key (string) is the bitcoin address, the value (float or string) is the amount in " + CURRENCY_UNIT}, + }, + }, + {"", RPCArg::Type::OBJ, /* opt */ true, /* default_val */ "", "", + { + {"data", RPCArg::Type::STR_HEX, /* opt */ false, /* default_val */ "", "A key-value pair. The key must be \"data\", the value is hex-encoded data"}, + }, + }, + }, + }, + {"locktime", RPCArg::Type::NUM, /* opt */ true, /* default_val */ "0", "Raw locktime. Non-0 value also locktime-activates inputs"}, + {"replaceable", RPCArg::Type::BOOL, /* opt */ true, /* default_val */ "false", "Marks this transaction as BIP125 replaceable.\n" + " Allows this transaction to be replaced by a transaction with higher fees. If provided, it is an error if explicit sequence numbers are incompatible."}, + }} + .ToString() + "\nResult:\n" " \"psbt\" (string) The resulting raw transaction (base64-encoded string)\n" "\nExamples:\n" @@ -1633,17 +1677,19 @@ UniValue converttopsbt(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() < 1 || request.params.size() > 3) throw std::runtime_error( - "converttopsbt \"hexstring\" ( permitsigdata iswitness )\n" - "\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" - "\nArguments:\n" - "1. \"hexstring\" (string, required) The hex string of a raw transaction\n" - "2. permitsigdata (boolean, optional, default=false) If true, any signatures in the input will be discarded and conversion.\n" - " will continue. If false, RPC will fail if any signatures are present.\n" - "3. iswitness (boolean, optional) Whether the transaction hex is a serialized witness transaction.\n" + 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", + { + {"hexstring", RPCArg::Type::STR_HEX, /* opt */ false, /* default_val */ "", "The hex string of a raw transaction"}, + {"permitsigdata", RPCArg::Type::BOOL, /* opt */ true, /* default_val */ "false", "If true, any signatures in the input will be discarded and conversion.\n" + " will continue. If false, RPC will fail if any signatures are present."}, + {"iswitness", RPCArg::Type::BOOL, /* opt */ true, /* default_val */ "depends on heuristic tests", "Whether the transaction hex is a serialized witness transaction.\n" " If iswitness is not present, heuristic tests will be used in decoding. If true, only witness deserializaion\n" " will be tried. If false, only non-witness deserialization will be tried. Only has an effect if\n" - " permitsigdata is true.\n" + " permitsigdata is true."}, + }} + .ToString() + "\nResult:\n" " \"psbt\" (string) The resulting raw transaction (base64-encoded string)\n" "\nExamples:\n" @@ -1669,7 +1715,7 @@ UniValue converttopsbt(const JSONRPCRequest& request) // Remove all scriptSigs and scriptWitnesses from inputs for (CTxIn& input : tx.vin) { - if ((!input.scriptSig.empty() || !input.scriptWitness.IsNull()) && (request.params[1].isNull() || (!request.params[1].isNull() && request.params[1].get_bool()))) { + if ((!input.scriptSig.empty() || !input.scriptWitness.IsNull()) && !permitsigdata) { throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "Inputs must not have scriptSigs and scriptWitnesses"); } input.scriptSig.clear(); diff --git a/src/rpc/rawtransaction.h b/src/rpc/rawtransaction.h index 924611ed5a..52d701d1c3 100644 --- a/src/rpc/rawtransaction.h +++ b/src/rpc/rawtransaction.h @@ -9,8 +9,12 @@ class CBasicKeyStore; struct CMutableTransaction; class UniValue; +namespace interfaces { +class Chain; +} // namespace interfaces + /** Sign a transaction with the given keystore and previous transactions */ -UniValue SignTransaction(CMutableTransaction& mtx, const UniValue& prevTxs, CBasicKeyStore *keystore, bool tempKeystore, const UniValue& hashType); +UniValue SignTransaction(interfaces::Chain& chain, CMutableTransaction& mtx, const UniValue& prevTxs, CBasicKeyStore *keystore, 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/server.cpp b/src/rpc/server.cpp index 60bf3c28c0..edaf64f3e1 100644 --- a/src/rpc/server.cpp +++ b/src/rpc/server.cpp @@ -8,13 +8,13 @@ #include <fs.h> #include <key_io.h> #include <random.h> +#include <rpc/util.h> #include <shutdown.h> #include <sync.h> #include <ui_interface.h> -#include <util.h> -#include <utilstrencodings.h> +#include <util/strencodings.h> +#include <util/system.h> -#include <boost/bind.hpp> #include <boost/signals2/signal.hpp> #include <boost/algorithm/string/classification.hpp> #include <boost/algorithm/string/split.hpp> @@ -23,7 +23,7 @@ #include <unordered_map> static CCriticalSection cs_rpcWarmup; -static bool fRPCRunning = false; +static std::atomic<bool> g_rpc_running{false}; static bool fRPCInWarmup GUARDED_BY(cs_rpcWarmup) = true; static std::string rpcWarmupStatus GUARDED_BY(cs_rpcWarmup) = "RPC server started"; /* Timer-creating functions */ @@ -31,11 +31,39 @@ static RPCTimerInterface* timerInterface = nullptr; /* Map of name to timer. */ static std::map<std::string, std::unique_ptr<RPCTimerBase> > deadlineTimers; +struct RPCCommandExecutionInfo +{ + std::string method; + int64_t start; +}; + +struct RPCServerInfo +{ + Mutex mutex; + std::list<RPCCommandExecutionInfo> active_commands GUARDED_BY(mutex); +}; + +static RPCServerInfo g_rpc_server_info; + +struct RPCCommandExecution +{ + std::list<RPCCommandExecutionInfo>::iterator it; + explicit RPCCommandExecution(const std::string& method) + { + LOCK(g_rpc_server_info.mutex); + it = g_rpc_server_info.active_commands.insert(g_rpc_server_info.active_commands.cend(), {method, GetTimeMicros()}); + } + ~RPCCommandExecution() + { + LOCK(g_rpc_server_info.mutex); + g_rpc_server_info.active_commands.erase(it); + } +}; + static struct CRPCSignals { boost::signals2::signal<void ()> Started; boost::signals2::signal<void ()> Stopped; - boost::signals2::signal<void (const CRPCCommand&)> PreCommand; } g_rpcSignals; void RPCServer::OnStarted(std::function<void ()> slot) @@ -141,10 +169,6 @@ std::vector<unsigned char> ParseHexO(const UniValue& o, std::string strKey) return ParseHexV(find_value(o, strKey), strKey); } -/** - * Note: This interface may still be subject to change. - */ - std::string CRPCTable::help(const std::string& strCommand, const JSONRPCRequest& helpreq) const { std::string strRet; @@ -203,10 +227,12 @@ UniValue help(const JSONRPCRequest& jsonRequest) { if (jsonRequest.fHelp || jsonRequest.params.size() > 1) throw std::runtime_error( - "help ( \"command\" )\n" - "\nList all commands, or get help for a specified command.\n" - "\nArguments:\n" - "1. \"command\" (string, optional) The command to get help on\n" + RPCHelpMan{"help", + "\nList all commands, or get help for a specified command.\n", + { + {"command", RPCArg::Type::STR, /* opt */ true, /* default_val */ "all commands", "The command to get help on"}, + }} + .ToString() + "\nResult:\n" "\"text\" (string) The help text\n" ); @@ -222,22 +248,30 @@ UniValue help(const JSONRPCRequest& jsonRequest) UniValue stop(const JSONRPCRequest& jsonRequest) { // Accept the deprecated and ignored 'detach' boolean argument + // Also accept the hidden 'wait' integer argument (milliseconds) + // For instance, 'stop 1000' makes the call wait 1 second before returning + // to the client (intended for testing) if (jsonRequest.fHelp || jsonRequest.params.size() > 1) throw std::runtime_error( - "stop\n" - "\nStop Bitcoin server."); + RPCHelpMan{"stop", + "\nStop Bitcoin server.", {}} + .ToString()); // Event loop will exit after current HTTP requests have been handled, so // this reply will get back to the client. StartShutdown(); + if (jsonRequest.params[0].isNum()) { + MilliSleep(jsonRequest.params[0].get_int()); + } return "Bitcoin server stopping"; } static UniValue uptime(const JSONRPCRequest& jsonRequest) { - if (jsonRequest.fHelp || jsonRequest.params.size() > 1) + if (jsonRequest.fHelp || jsonRequest.params.size() > 0) throw std::runtime_error( - "uptime\n" - "\nReturns the total uptime of the server.\n" + RPCHelpMan{"uptime", + "\nReturns the total uptime of the server.\n", {}} + .ToString() + "\nResult:\n" "ttt (numeric) The number of seconds that the server has been running\n" "\nExamples:\n" @@ -248,16 +282,39 @@ static UniValue uptime(const JSONRPCRequest& 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", {}} + .ToString() + ); + } + + LOCK(g_rpc_server_info.mutex); + UniValue active_commands(UniValue::VARR); + for (const RPCCommandExecutionInfo& info : g_rpc_server_info.active_commands) { + UniValue entry(UniValue::VOBJ); + entry.pushKV("method", info.method); + entry.pushKV("duration", GetTimeMicros() - info.start); + active_commands.push_back(entry); + } + + UniValue result(UniValue::VOBJ); + result.pushKV("active_commands", active_commands); + + return result; +} + // clang-format off -/** - * Call Table - */ static const CRPCCommand vRPCCommands[] = { // category name actor (function) argNames // --------------------- ------------------------ ----------------------- ---------- /* Overall control/query calls */ + { "control", "getrpcinfo", &getrpcinfo, {} }, { "control", "help", &help, {"command"} }, - { "control", "stop", &stop, {} }, + { "control", "stop", &stop, {"wait"} }, { "control", "uptime", &uptime, {} }, }; // clang-format on @@ -299,7 +356,7 @@ bool CRPCTable::appendCommand(const std::string& name, const CRPCCommand* pcmd) void StartRPC() { LogPrint(BCLog::RPC, "Starting RPC\n"); - fRPCRunning = true; + g_rpc_running = true; g_rpcSignals.Started(); } @@ -307,7 +364,7 @@ void InterruptRPC() { LogPrint(BCLog::RPC, "Interrupting RPC\n"); // Interrupt e.g. running longpolls - fRPCRunning = false; + g_rpc_running = false; } void StopRPC() @@ -320,7 +377,7 @@ void StopRPC() bool IsRPCRunning() { - return fRPCRunning; + return g_rpc_running; } void SetRPCWarmupStatus(const std::string& newStatus) @@ -480,10 +537,9 @@ UniValue CRPCTable::execute(const JSONRPCRequest &request) const if (!pcmd) throw JSONRPCError(RPC_METHOD_NOT_FOUND, "Method not found"); - g_rpcSignals.PreCommand(*pcmd); - try { + RPCCommandExecution execution(request.strMethod); // Execute, convert arguments to array if necessary if (request.params.isObject()) { return pcmd->actor(transformNamedArguments(request, pcmd->argNames)); @@ -500,11 +556,7 @@ UniValue CRPCTable::execute(const JSONRPCRequest &request) const std::vector<std::string> CRPCTable::listCommands() const { std::vector<std::string> commandList; - typedef std::map<std::string, const CRPCCommand*> commandMap; - - std::transform( mapCommands.begin(), mapCommands.end(), - std::back_inserter(commandList), - boost::bind(&commandMap::value_type::first,_1) ); + for (const auto& i : mapCommands) commandList.emplace_back(i.first); return commandList; } diff --git a/src/rpc/util.cpp b/src/rpc/util.cpp index ba414bf3f5..b91baee4ac 100644 --- a/src/rpc/util.cpp +++ b/src/rpc/util.cpp @@ -7,7 +7,9 @@ #include <rpc/protocol.h> #include <rpc/util.h> #include <tinyformat.h> -#include <utilstrencodings.h> +#include <util/strencodings.h> + +InitInterfaces* g_rpc_interfaces = nullptr; // Converts a hex string to a public key if possible CPubKey HexToPubKey(const std::string& hex_in) @@ -126,3 +128,304 @@ UniValue DescribeAddress(const CTxDestination& dest) { return boost::apply_visitor(DescribeAddressVisitor(), dest); } + +struct Section { + Section(const std::string& left, const std::string& right) + : m_left{left}, m_right{right} {} + const std::string m_left; + const std::string m_right; +}; + +struct Sections { + std::vector<Section> m_sections; + size_t m_max_pad{0}; + + void PushSection(const Section& s) + { + m_max_pad = std::max(m_max_pad, s.m_left.size()); + m_sections.push_back(s); + } + + enum class OuterType { + ARR, + OBJ, + NAMED_ARG, // Only set on first recursion + }; + + void Push(const RPCArg& arg, const size_t current_indent = 5, const OuterType outer_type = OuterType::NAMED_ARG) + { + const auto indent = std::string(current_indent, ' '); + const auto indent_next = std::string(current_indent + 2, ' '); + switch (arg.m_type) { + case RPCArg::Type::STR_HEX: + case RPCArg::Type::STR: + case RPCArg::Type::NUM: + case RPCArg::Type::AMOUNT: + case RPCArg::Type::BOOL: { + if (outer_type == OuterType::NAMED_ARG) return; // Nothing more to do for non-recursive types on first recursion + auto left = indent; + if (arg.m_type_str.size() != 0 && outer_type == OuterType::OBJ) { + left += "\"" + arg.m_name + "\": " + arg.m_type_str.at(0); + } else { + left += outer_type == OuterType::OBJ ? arg.ToStringObj(/* oneline */ false) : arg.ToString(/* oneline */ false); + } + left += ","; + PushSection({left, arg.ToDescriptionString(/* implicitly_required */ outer_type == OuterType::ARR)}); + break; + } + case RPCArg::Type::OBJ: + case RPCArg::Type::OBJ_USER_KEYS: { + const auto right = outer_type == OuterType::NAMED_ARG ? "" : arg.ToDescriptionString(/* implicitly_required */ outer_type == OuterType::ARR); + PushSection({indent + "{", right}); + for (const auto& arg_inner : arg.m_inner) { + Push(arg_inner, current_indent + 2, OuterType::OBJ); + } + if (arg.m_type != RPCArg::Type::OBJ) { + PushSection({indent_next + "...", ""}); + } + PushSection({indent + "}" + (outer_type != OuterType::NAMED_ARG ? "," : ""), ""}); + break; + } + case RPCArg::Type::ARR: { + auto left = indent; + left += outer_type == OuterType::OBJ ? "\"" + arg.m_name + "\": " : ""; + left += "["; + const auto right = outer_type == OuterType::NAMED_ARG ? "" : arg.ToDescriptionString(/* implicitly_required */ outer_type == OuterType::ARR); + PushSection({left, right}); + for (const auto& arg_inner : arg.m_inner) { + Push(arg_inner, current_indent + 2, OuterType::ARR); + } + PushSection({indent_next + "...", ""}); + PushSection({indent + "]" + (outer_type != OuterType::NAMED_ARG ? "," : ""), ""}); + break; + } + + // no default case, so the compiler can warn about missing cases + } + } + + std::string ToString() const + { + std::string ret; + const size_t pad = m_max_pad + 4; + for (const auto& s : m_sections) { + if (s.m_right.empty()) { + ret += s.m_left; + ret += "\n"; + continue; + } + + std::string left = s.m_left; + left.resize(pad, ' '); + ret += left; + + // Properly pad after newlines + std::string right; + size_t begin = 0; + size_t new_line_pos = s.m_right.find_first_of('\n'); + while (true) { + right += s.m_right.substr(begin, new_line_pos - begin); + if (new_line_pos == std::string::npos) { + break; //No new line + } + right += "\n" + std::string(pad, ' '); + begin = s.m_right.find_first_not_of(' ', new_line_pos + 1); + if (begin == std::string::npos) { + break; // Empty line + } + new_line_pos = s.m_right.find_first_of('\n', begin + 1); + } + ret += right; + ret += "\n"; + } + return ret; + } +}; + +RPCHelpMan::RPCHelpMan(const std::string& name, const std::string& description, const std::vector<RPCArg>& args) + : m_name{name}, m_description{description}, m_args{args} +{ + std::set<std::string> named_args; + for (const auto& arg : m_args) { + // Should have unique named arguments + assert(named_args.insert(arg.m_name).second); + } +} + +std::string RPCHelpMan::ToString() const +{ + std::string ret; + + // Oneline summary + ret += m_name; + bool was_optional{false}; + for (const auto& arg : m_args) { + ret += " "; + if (arg.m_optional) { + if (!was_optional) ret += "( "; + was_optional = true; + } else { + if (was_optional) ret += ") "; + was_optional = false; + } + ret += arg.ToString(/* oneline */ true); + } + if (was_optional) ret += " )"; + ret += "\n"; + + // Description + ret += m_description; + + // Arguments + Sections sections; + for (size_t i{0}; i < m_args.size(); ++i) { + const auto& arg = m_args.at(i); + + if (i == 0) ret += "\nArguments:\n"; + + // Push named argument name and description + sections.m_sections.emplace_back(std::to_string(i + 1) + ". " + arg.m_name, arg.ToDescriptionString()); + sections.m_max_pad = std::max(sections.m_max_pad, sections.m_sections.back().m_left.size()); + + // Recursively push nested args + sections.Push(arg); + } + ret += sections.ToString(); + + return ret; +} + +std::string RPCArg::ToDescriptionString(const bool implicitly_required) const +{ + std::string ret; + ret += "("; + if (m_type_str.size() != 0) { + ret += m_type_str.at(1); + } else { + switch (m_type) { + case Type::STR_HEX: + case Type::STR: { + ret += "string"; + break; + } + case Type::NUM: { + ret += "numeric"; + break; + } + case Type::AMOUNT: { + ret += "numeric or string"; + break; + } + case Type::BOOL: { + ret += "boolean"; + break; + } + case Type::OBJ: + case Type::OBJ_USER_KEYS: { + ret += "json object"; + break; + } + case Type::ARR: { + ret += "json array"; + break; + } + + // no default case, so the compiler can warn about missing cases + } + } + if (!implicitly_required) { + ret += ", "; + if (m_optional) { + ret += "optional"; + if (!m_default_value.empty()) { + ret += ", default=" + m_default_value; + } else { + // TODO enable this assert, when all optional parameters have their default value documented + //assert(false); + } + } else { + ret += "required"; + assert(m_default_value.empty()); // Default value is ignored, and must not be present + } + } + ret += ")"; + ret += m_description.empty() ? "" : " " + m_description; + return ret; +} + +std::string RPCArg::ToStringObj(const bool oneline) const +{ + std::string res; + res += "\""; + res += m_name; + if (oneline) { + res += "\":"; + } else { + res += "\": "; + } + switch (m_type) { + case Type::STR: + return res + "\"str\""; + case Type::STR_HEX: + return res + "\"hex\""; + case Type::NUM: + return res + "n"; + case Type::AMOUNT: + return res + "amount"; + case Type::BOOL: + return res + "bool"; + case Type::ARR: + res += "["; + for (const auto& i : m_inner) { + res += i.ToString(oneline) + ","; + } + return res + "...]"; + case Type::OBJ: + case Type::OBJ_USER_KEYS: + // Currently unused, so avoid writing dead code + assert(false); + + // no default case, so the compiler can warn about missing cases + } + assert(false); +} + +std::string RPCArg::ToString(const bool oneline) const +{ + if (oneline && !m_oneline_description.empty()) return m_oneline_description; + + switch (m_type) { + case Type::STR_HEX: + case Type::STR: { + return "\"" + m_name + "\""; + } + case Type::NUM: + case Type::AMOUNT: + case Type::BOOL: { + return m_name; + } + case Type::OBJ: + case Type::OBJ_USER_KEYS: { + std::string res; + for (size_t i = 0; i < m_inner.size();) { + res += m_inner[i].ToStringObj(oneline); + if (++i < m_inner.size()) res += ","; + } + if (m_type == Type::OBJ) { + return "{" + res + "}"; + } else { + return "{" + res + ",...}"; + } + } + case Type::ARR: { + std::string res; + for (const auto& i : m_inner) { + res += i.ToString(oneline) + ","; + } + return "[" + res + "...]"; + } + + // no default case, so the compiler can warn about missing cases + } + assert(false); +} diff --git a/src/rpc/util.h b/src/rpc/util.h index 0a3a156e45..9e97b3ae0b 100644 --- a/src/rpc/util.h +++ b/src/rpc/util.h @@ -9,14 +9,18 @@ #include <script/standard.h> #include <univalue.h> -#include <boost/variant/static_visitor.hpp> - #include <string> #include <vector> class CKeyStore; class CPubKey; class CScript; +struct InitInterfaces; + +//! Pointers to interfaces that need to be accessible from RPC methods. Due to +//! limitations of the RPC framework, there's currently no direct way to pass in +//! state to RPC method implementations. +extern InitInterfaces* g_rpc_interfaces; CPubKey HexToPubKey(const std::string& hex_in); CPubKey AddrToPubKey(CKeyStore* const keystore, const std::string& addr_in); @@ -24,4 +28,95 @@ CScript CreateMultisigRedeemscript(const int required, const std::vector<CPubKey UniValue DescribeAddress(const CTxDestination& dest); +struct RPCArg { + enum class Type { + OBJ, + ARR, + STR, + NUM, + BOOL, + OBJ_USER_KEYS, //!< Special type where the user must set the keys e.g. to define multiple addresses; as opposed to e.g. an options object where the keys are predefined + AMOUNT, //!< Special type representing a floating point amount (can be either NUM or STR) + STR_HEX, //!< Special type that is a STR with only hex chars + }; + const std::string m_name; //!< The name of the arg (can be empty for inner args) + const Type m_type; + const std::vector<RPCArg> m_inner; //!< Only used for arrays or dicts + const bool m_optional; + const std::string m_default_value; //!< Only used for optional args + const std::string m_description; + const std::string m_oneline_description; //!< Should be empty unless it is supposed to override the auto-generated summary line + const std::vector<std::string> m_type_str; //!< Should be empty unless it is supposed to override the auto-generated type strings. Vector length is either 0 or 2, m_type_str.at(0) will override the type of the value in a key-value pair, m_type_str.at(1) will override the type in the argument description. + + RPCArg( + const std::string& name, + const Type& type, + const bool opt, + const std::string& default_val, + const std::string& description, + const std::string& oneline_description = "", + const std::vector<std::string>& type_str = {}) + : m_name{name}, + m_type{type}, + m_optional{opt}, + m_default_value{default_val}, + m_description{description}, + m_oneline_description{oneline_description}, + m_type_str{type_str} + { + assert(type != Type::ARR && type != Type::OBJ); + } + + RPCArg( + const std::string& name, + const Type& type, + const bool opt, + const std::string& default_val, + const std::string& description, + const std::vector<RPCArg>& inner, + const std::string& oneline_description = "", + const std::vector<std::string>& type_str = {}) + : m_name{name}, + m_type{type}, + m_inner{inner}, + m_optional{opt}, + m_default_value{default_val}, + m_description{description}, + m_oneline_description{oneline_description}, + m_type_str{type_str} + { + assert(type == Type::ARR || type == Type::OBJ); + } + + /** + * Return the type string of the argument. + * Set oneline to allow it to be overridden by a custom oneline type string (m_oneline_description). + */ + std::string ToString(bool oneline) const; + /** + * Return the type string of the argument when it is in an object (dict). + * Set oneline to get the oneline representation (less whitespace) + */ + std::string ToStringObj(bool oneline) const; + /** + * Return the description string, including the argument type and whether + * the argument is required. + * implicitly_required is set for arguments in an array, which are neither optional nor required. + */ + std::string ToDescriptionString(bool implicitly_required = false) const; +}; + +class RPCHelpMan +{ +public: + RPCHelpMan(const std::string& name, const std::string& description, const std::vector<RPCArg>& args); + + std::string ToString() const; + +private: + const std::string m_name; + const std::string m_description; + const std::vector<RPCArg> m_args; +}; + #endif // BITCOIN_RPC_UTIL_H diff --git a/src/scheduler.cpp b/src/scheduler.cpp index 552391d7d0..fdc859b3a0 100644 --- a/src/scheduler.cpp +++ b/src/scheduler.cpp @@ -8,7 +8,6 @@ #include <reverselock.h> #include <assert.h> -#include <boost/bind.hpp> #include <utility> CScheduler::CScheduler() : nThreadsServicingQueue(0), stopRequested(false), stopWhenEmpty(false) @@ -42,7 +41,7 @@ void CScheduler::serviceQueue() try { if (!shouldStop() && taskQueue.empty()) { reverse_lock<boost::unique_lock<boost::mutex> > rlock(lock); - // Use this chance to get a tiny bit more entropy + // Use this chance to get more entropy RandAddSeedSleep(); } while (!shouldStop() && taskQueue.empty()) { @@ -120,12 +119,12 @@ void CScheduler::scheduleFromNow(CScheduler::Function f, int64_t deltaMilliSecon static void Repeat(CScheduler* s, CScheduler::Function f, int64_t deltaMilliSeconds) { f(); - s->scheduleFromNow(boost::bind(&Repeat, s, f, deltaMilliSeconds), deltaMilliSeconds); + s->scheduleFromNow(std::bind(&Repeat, s, f, deltaMilliSeconds), deltaMilliSeconds); } void CScheduler::scheduleEvery(CScheduler::Function f, int64_t deltaMilliSeconds) { - scheduleFromNow(boost::bind(&Repeat, this, f, deltaMilliSeconds), deltaMilliSeconds); + scheduleFromNow(std::bind(&Repeat, this, f, deltaMilliSeconds), deltaMilliSeconds); } size_t CScheduler::getQueueInfo(boost::chrono::system_clock::time_point &first, diff --git a/src/scheduler.h b/src/scheduler.h index 6c45f508ec..6d7f42cf9f 100644 --- a/src/scheduler.h +++ b/src/scheduler.h @@ -25,7 +25,7 @@ // CScheduler* s = new CScheduler(); // s->scheduleFromNow(doSomething, 11); // Assuming a: void doSomething() { } // s->scheduleFromNow(std::bind(Class::func, this, argument), 3); -// boost::thread* t = new boost::thread(boost::bind(CScheduler::serviceQueue, s)); +// boost::thread* t = new boost::thread(std::bind(CScheduler::serviceQueue, s)); // // ... then at program shutdown, clean up the thread running serviceQueue: // t->interrupt(); diff --git a/src/script/descriptor.cpp b/src/script/descriptor.cpp index 45b097dde6..a702be5b78 100644 --- a/src/script/descriptor.cpp +++ b/src/script/descriptor.cpp @@ -10,8 +10,8 @@ #include <script/standard.h> #include <span.h> -#include <util.h> -#include <utilstrencodings.h> +#include <util/system.h> +#include <util/strencodings.h> #include <memory> #include <string> @@ -40,8 +40,8 @@ struct PubkeyProvider { virtual ~PubkeyProvider() = default; - /** Derive a public key. */ - virtual bool GetPubKey(int pos, const SigningProvider& arg, CPubKey& out) const = 0; + /** Derive a public key. If key==nullptr, only info is desired. */ + virtual bool GetPubKey(int pos, const SigningProvider& arg, CPubKey* key, KeyOriginInfo& info) const = 0; /** Whether this represent multiple public keys at different positions. */ virtual bool IsRange() const = 0; @@ -56,6 +56,37 @@ struct PubkeyProvider virtual bool ToPrivateString(const SigningProvider& arg, std::string& out) const = 0; }; +class OriginPubkeyProvider final : public PubkeyProvider +{ + KeyOriginInfo m_origin; + std::unique_ptr<PubkeyProvider> m_provider; + + std::string OriginString() const + { + return HexStr(std::begin(m_origin.fingerprint), std::end(m_origin.fingerprint)) + FormatKeyPath(m_origin.path); + } + +public: + OriginPubkeyProvider(KeyOriginInfo info, std::unique_ptr<PubkeyProvider> provider) : m_origin(std::move(info)), m_provider(std::move(provider)) {} + bool GetPubKey(int pos, const SigningProvider& arg, CPubKey* key, KeyOriginInfo& info) const override + { + if (!m_provider->GetPubKey(pos, arg, key, info)) return false; + std::copy(std::begin(m_origin.fingerprint), std::end(m_origin.fingerprint), info.fingerprint); + info.path.insert(info.path.begin(), m_origin.path.begin(), m_origin.path.end()); + return true; + } + bool IsRange() const override { return m_provider->IsRange(); } + size_t GetSize() const override { return m_provider->GetSize(); } + std::string ToString() const override { return "[" + OriginString() + "]" + m_provider->ToString(); } + bool ToPrivateString(const SigningProvider& arg, std::string& ret) const override + { + std::string sub; + if (!m_provider->ToPrivateString(arg, sub)) return false; + ret = "[" + OriginString() + "]" + std::move(sub); + return true; + } +}; + /** An object representing a parsed constant public key in a descriptor. */ class ConstPubkeyProvider final : public PubkeyProvider { @@ -63,9 +94,12 @@ class ConstPubkeyProvider final : public PubkeyProvider public: ConstPubkeyProvider(const CPubKey& pubkey) : m_pubkey(pubkey) {} - bool GetPubKey(int pos, const SigningProvider& arg, CPubKey& out) const override + bool GetPubKey(int pos, const SigningProvider& arg, CPubKey* key, KeyOriginInfo& info) const override { - out = m_pubkey; + if (key) *key = m_pubkey; + info.path.clear(); + CKeyID keyid = m_pubkey.GetID(); + std::copy(keyid.begin(), keyid.begin() + sizeof(info.fingerprint), info.fingerprint); return true; } bool IsRange() const override { return false; } @@ -98,7 +132,7 @@ class BIP32PubkeyProvider final : public PubkeyProvider CKey key; if (!arg.GetKey(m_extkey.pubkey.GetID(), key)) return false; ret.nDepth = m_extkey.nDepth; - std::copy(m_extkey.vchFingerprint, m_extkey.vchFingerprint + 4, ret.vchFingerprint); + std::copy(m_extkey.vchFingerprint, m_extkey.vchFingerprint + sizeof(ret.vchFingerprint), ret.vchFingerprint); ret.nChild = m_extkey.nChild; ret.chaincode = m_extkey.chaincode; ret.key = key; @@ -118,27 +152,34 @@ public: BIP32PubkeyProvider(const CExtPubKey& extkey, KeyPath path, DeriveType derive) : m_extkey(extkey), m_path(std::move(path)), m_derive(derive) {} bool IsRange() const override { return m_derive != DeriveType::NO; } size_t GetSize() const override { return 33; } - bool GetPubKey(int pos, const SigningProvider& arg, CPubKey& out) const override + bool GetPubKey(int pos, const SigningProvider& arg, CPubKey* key, KeyOriginInfo& info) const override { - if (IsHardened()) { - CExtKey key; - if (!GetExtKey(arg, key)) return false; - for (auto entry : m_path) { - key.Derive(key, entry); + if (key) { + if (IsHardened()) { + CExtKey extkey; + if (!GetExtKey(arg, extkey)) return false; + for (auto entry : m_path) { + extkey.Derive(extkey, entry); + } + if (m_derive == DeriveType::UNHARDENED) extkey.Derive(extkey, pos); + if (m_derive == DeriveType::HARDENED) extkey.Derive(extkey, pos | 0x80000000UL); + *key = extkey.Neuter().pubkey; + } else { + // TODO: optimize by caching + CExtPubKey extkey = m_extkey; + for (auto entry : m_path) { + extkey.Derive(extkey, entry); + } + if (m_derive == DeriveType::UNHARDENED) extkey.Derive(extkey, pos); + assert(m_derive != DeriveType::HARDENED); + *key = extkey.pubkey; } - if (m_derive == DeriveType::UNHARDENED) key.Derive(key, pos); - if (m_derive == DeriveType::HARDENED) key.Derive(key, pos | 0x80000000UL); - out = key.Neuter().pubkey; - } else { - // TODO: optimize by caching - CExtPubKey key = m_extkey; - for (auto entry : m_path) { - key.Derive(key, entry); - } - if (m_derive == DeriveType::UNHARDENED) key.Derive(key, pos); - assert(m_derive != DeriveType::HARDENED); - out = key.pubkey; } + CKeyID keyid = m_extkey.pubkey.GetID(); + std::copy(keyid.begin(), keyid.begin() + sizeof(info.fingerprint), info.fingerprint); + info.path = m_path; + if (m_derive == DeriveType::UNHARDENED) info.path.push_back((uint32_t)pos); + if (m_derive == DeriveType::HARDENED) info.path.push_back(((uint32_t)pos) | 0x80000000L); return true; } std::string ToString() const override @@ -163,204 +204,261 @@ public: } }; -/** A parsed addr(A) descriptor. */ -class AddressDescriptor final : public Descriptor +/** Base class for all Descriptor implementations. */ +class DescriptorImpl : public Descriptor { - CTxDestination m_destination; + //! Public key arguments for this descriptor (size 1 for PK, PKH, WPKH; any size of Multisig). + const std::vector<std::unique_ptr<PubkeyProvider>> m_pubkey_args; + //! The sub-descriptor argument (nullptr for everything but SH and WSH). + const std::unique_ptr<DescriptorImpl> m_script_arg; + //! The string name of the descriptor function. + const std::string m_name; + +protected: + //! Return a serialization of anything except pubkey and script arguments, to be prepended to those. + virtual std::string ToStringExtra() const { return ""; } + + /** A helper function to construct the scripts for this descriptor. + * + * This function is invoked once for every CScript produced by evaluating + * m_script_arg, or just once in case m_script_arg is nullptr. + + * @param pubkeys The evaluations of the m_pubkey_args field. + * @param script The evaluation of m_script_arg (or nullptr when m_script_arg is nullptr). + * @param out A FlatSigningProvider to put scripts or public keys in that are necessary to the solver. + * The script and pubkeys argument to this function are automatically added. + * @return A vector with scriptPubKeys for this descriptor. + */ + virtual std::vector<CScript> MakeScripts(const std::vector<CPubKey>& pubkeys, const CScript* script, FlatSigningProvider& out) const = 0; public: - AddressDescriptor(CTxDestination destination) : m_destination(std::move(destination)) {} + DescriptorImpl(std::vector<std::unique_ptr<PubkeyProvider>> pubkeys, std::unique_ptr<DescriptorImpl> script, const std::string& name) : m_pubkey_args(std::move(pubkeys)), m_script_arg(std::move(script)), m_name(name) {} - bool IsRange() const override { return false; } - std::string ToString() const override { return "addr(" + EncodeDestination(m_destination) + ")"; } - bool ToPrivateString(const SigningProvider& arg, std::string& out) const override { out = ToString(); return true; } - bool Expand(int pos, const SigningProvider& arg, std::vector<CScript>& output_scripts, FlatSigningProvider& out) const override + bool IsSolvable() const override { - output_scripts = std::vector<CScript>{GetScriptForDestination(m_destination)}; + if (m_script_arg) { + if (!m_script_arg->IsSolvable()) return false; + } return true; } -}; - -/** A parsed raw(H) descriptor. */ -class RawDescriptor final : public Descriptor -{ - CScript m_script; - -public: - RawDescriptor(CScript script) : m_script(std::move(script)) {} - bool IsRange() const override { return false; } - std::string ToString() const override { return "raw(" + HexStr(m_script.begin(), m_script.end()) + ")"; } - bool ToPrivateString(const SigningProvider& arg, std::string& out) const override { out = ToString(); return true; } - bool Expand(int pos, const SigningProvider& arg, std::vector<CScript>& output_scripts, FlatSigningProvider& out) const override + bool IsRange() const final { - output_scripts = std::vector<CScript>{m_script}; - return true; + for (const auto& pubkey : m_pubkey_args) { + if (pubkey->IsRange()) return true; + } + if (m_script_arg) { + if (m_script_arg->IsRange()) return true; + } + return false; } -}; - -/** A parsed pk(P), pkh(P), or wpkh(P) descriptor. */ -class SingleKeyDescriptor final : public Descriptor -{ - const std::function<CScript(const CPubKey&)> m_script_fn; - const std::string m_fn_name; - std::unique_ptr<PubkeyProvider> m_provider; -public: - SingleKeyDescriptor(std::unique_ptr<PubkeyProvider> prov, const std::function<CScript(const CPubKey&)>& fn, const std::string& name) : m_script_fn(fn), m_fn_name(name), m_provider(std::move(prov)) {} - - bool IsRange() const override { return m_provider->IsRange(); } - std::string ToString() const override { return m_fn_name + "(" + m_provider->ToString() + ")"; } - bool ToPrivateString(const SigningProvider& arg, std::string& out) const override + bool ToStringHelper(const SigningProvider* arg, std::string& out, bool priv) const { - std::string ret; - if (!m_provider->ToPrivateString(arg, ret)) return false; - out = m_fn_name + "(" + std::move(ret) + ")"; + std::string extra = ToStringExtra(); + size_t pos = extra.size() > 0 ? 1 : 0; + std::string ret = m_name + "(" + extra; + for (const auto& pubkey : m_pubkey_args) { + if (pos++) ret += ","; + std::string tmp; + if (priv) { + if (!pubkey->ToPrivateString(*arg, tmp)) return false; + } else { + tmp = pubkey->ToString(); + } + ret += std::move(tmp); + } + if (m_script_arg) { + if (pos++) ret += ","; + std::string tmp; + if (!m_script_arg->ToStringHelper(arg, tmp, priv)) return false; + ret += std::move(tmp); + } + out = std::move(ret) + ")"; return true; } - bool Expand(int pos, const SigningProvider& arg, std::vector<CScript>& output_scripts, FlatSigningProvider& out) const override + + std::string ToString() const final { - CPubKey key; - if (!m_provider->GetPubKey(pos, arg, key)) return false; - output_scripts = std::vector<CScript>{m_script_fn(key)}; - out.pubkeys.emplace(key.GetID(), std::move(key)); - return true; + std::string ret; + ToStringHelper(nullptr, ret, false); + return ret; } -}; - -CScript P2PKHGetScript(const CPubKey& pubkey) { return GetScriptForDestination(pubkey.GetID()); } -CScript P2PKGetScript(const CPubKey& pubkey) { return GetScriptForRawPubKey(pubkey); } -CScript P2WPKHGetScript(const CPubKey& pubkey) { return GetScriptForDestination(WitnessV0KeyHash(pubkey.GetID())); } - -/** A parsed multi(...) descriptor. */ -class MultisigDescriptor : public Descriptor -{ - int m_threshold; - std::vector<std::unique_ptr<PubkeyProvider>> m_providers; -public: - MultisigDescriptor(int threshold, std::vector<std::unique_ptr<PubkeyProvider>> providers) : m_threshold(threshold), m_providers(std::move(providers)) {} + bool ToPrivateString(const SigningProvider& arg, std::string& out) const override final { return ToStringHelper(&arg, out, true); } - bool IsRange() const override + bool ExpandHelper(int pos, const SigningProvider& arg, Span<const unsigned char>* cache_read, std::vector<CScript>& output_scripts, FlatSigningProvider& out, std::vector<unsigned char>* cache_write) const { - for (const auto& p : m_providers) { - if (p->IsRange()) return true; + std::vector<std::pair<CPubKey, KeyOriginInfo>> entries; + entries.reserve(m_pubkey_args.size()); + + // Construct temporary data in `entries` and `subscripts`, to avoid producing output in case of failure. + for (const auto& p : m_pubkey_args) { + entries.emplace_back(); + if (!p->GetPubKey(pos, arg, cache_read ? nullptr : &entries.back().first, entries.back().second)) return false; + if (cache_read) { + // Cached expanded public key exists, use it. + if (cache_read->size() == 0) return false; + bool compressed = ((*cache_read)[0] == 0x02 || (*cache_read)[0] == 0x03) && cache_read->size() >= 33; + bool uncompressed = ((*cache_read)[0] == 0x04) && cache_read->size() >= 65; + if (!(compressed || uncompressed)) return false; + CPubKey pubkey(cache_read->begin(), cache_read->begin() + (compressed ? 33 : 65)); + entries.back().first = pubkey; + *cache_read = cache_read->subspan(compressed ? 33 : 65); + } + if (cache_write) { + cache_write->insert(cache_write->end(), entries.back().first.begin(), entries.back().first.end()); + } + } + std::vector<CScript> subscripts; + if (m_script_arg) { + FlatSigningProvider subprovider; + if (!m_script_arg->ExpandHelper(pos, arg, cache_read, subscripts, subprovider, cache_write)) return false; + out = Merge(out, subprovider); } - return false; - } - std::string ToString() const override - { - std::string ret = strprintf("multi(%i", m_threshold); - for (const auto& p : m_providers) { - ret += "," + p->ToString(); + std::vector<CPubKey> pubkeys; + pubkeys.reserve(entries.size()); + for (auto& entry : entries) { + pubkeys.push_back(entry.first); + out.origins.emplace(entry.first.GetID(), std::move(entry.second)); + out.pubkeys.emplace(entry.first.GetID(), entry.first); + } + if (m_script_arg) { + for (const auto& subscript : subscripts) { + out.scripts.emplace(CScriptID(subscript), subscript); + std::vector<CScript> addscripts = MakeScripts(pubkeys, &subscript, out); + for (auto& addscript : addscripts) { + output_scripts.push_back(std::move(addscript)); + } + } + } else { + output_scripts = MakeScripts(pubkeys, nullptr, out); } - return std::move(ret) + ")"; + return true; } - bool ToPrivateString(const SigningProvider& arg, std::string& out) const override + bool Expand(int pos, const SigningProvider& provider, std::vector<CScript>& output_scripts, FlatSigningProvider& out, std::vector<unsigned char>* cache = nullptr) const final { - std::string ret = strprintf("multi(%i", m_threshold); - for (const auto& p : m_providers) { - std::string sub; - if (!p->ToPrivateString(arg, sub)) return false; - ret += "," + std::move(sub); - } - out = std::move(ret) + ")"; - return true; + return ExpandHelper(pos, provider, nullptr, output_scripts, out, cache); } - bool Expand(int pos, const SigningProvider& arg, std::vector<CScript>& output_scripts, FlatSigningProvider& out) const override + bool ExpandFromCache(int pos, const std::vector<unsigned char>& cache, std::vector<CScript>& output_scripts, FlatSigningProvider& out) const final { - std::vector<CPubKey> pubkeys; - pubkeys.reserve(m_providers.size()); - for (const auto& p : m_providers) { - CPubKey key; - if (!p->GetPubKey(pos, arg, key)) return false; - pubkeys.push_back(key); - } - for (const CPubKey& key : pubkeys) { - out.pubkeys.emplace(key.GetID(), std::move(key)); - } - output_scripts = std::vector<CScript>{GetScriptForMultisig(m_threshold, pubkeys)}; - return true; + Span<const unsigned char> span = MakeSpan(cache); + return ExpandHelper(pos, DUMMY_SIGNING_PROVIDER, &span, output_scripts, out, nullptr) && span.size() == 0; } }; -/** A parsed sh(S) or wsh(S) descriptor. */ -class ConvertorDescriptor : public Descriptor +/** Construct a vector with one element, which is moved into it. */ +template<typename T> +std::vector<T> Singleton(T elem) { - const std::function<CScript(const CScript&)> m_convert_fn; - const std::string m_fn_name; - std::unique_ptr<Descriptor> m_descriptor; + std::vector<T> ret; + ret.emplace_back(std::move(elem)); + return ret; +} +/** A parsed addr(A) descriptor. */ +class AddressDescriptor final : public DescriptorImpl +{ + const CTxDestination m_destination; +protected: + std::string ToStringExtra() const override { return EncodeDestination(m_destination); } + std::vector<CScript> MakeScripts(const std::vector<CPubKey>&, const CScript*, FlatSigningProvider&) const override { return Singleton(GetScriptForDestination(m_destination)); } public: - ConvertorDescriptor(std::unique_ptr<Descriptor> descriptor, const std::function<CScript(const CScript&)>& fn, const std::string& name) : m_convert_fn(fn), m_fn_name(name), m_descriptor(std::move(descriptor)) {} + AddressDescriptor(CTxDestination destination) : DescriptorImpl({}, {}, "addr"), m_destination(std::move(destination)) {} + bool IsSolvable() const final { return false; } +}; - bool IsRange() const override { return m_descriptor->IsRange(); } - std::string ToString() const override { return m_fn_name + "(" + m_descriptor->ToString() + ")"; } - bool ToPrivateString(const SigningProvider& arg, std::string& out) const override - { - std::string ret; - if (!m_descriptor->ToPrivateString(arg, ret)) return false; - out = m_fn_name + "(" + std::move(ret) + ")"; - return true; - } - bool Expand(int pos, const SigningProvider& arg, std::vector<CScript>& output_scripts, FlatSigningProvider& out) const override - { - std::vector<CScript> sub; - if (!m_descriptor->Expand(pos, arg, sub, out)) return false; - output_scripts.clear(); - for (const auto& script : sub) { - CScriptID id(script); - out.scripts.emplace(CScriptID(script), script); - output_scripts.push_back(m_convert_fn(script)); - } - return true; - } +/** A parsed raw(H) descriptor. */ +class RawDescriptor final : public DescriptorImpl +{ + const CScript m_script; +protected: + std::string ToStringExtra() const override { return HexStr(m_script.begin(), m_script.end()); } + std::vector<CScript> MakeScripts(const std::vector<CPubKey>&, const CScript*, FlatSigningProvider&) const override { return Singleton(m_script); } +public: + RawDescriptor(CScript script) : DescriptorImpl({}, {}, "raw"), m_script(std::move(script)) {} + bool IsSolvable() const final { return false; } }; -CScript ConvertP2SH(const CScript& script) { return GetScriptForDestination(CScriptID(script)); } -CScript ConvertP2WSH(const CScript& script) { return GetScriptForDestination(WitnessV0ScriptHash(script)); } +/** A parsed pk(P) descriptor. */ +class PKDescriptor final : public DescriptorImpl +{ +protected: + std::vector<CScript> MakeScripts(const std::vector<CPubKey>& keys, const CScript*, FlatSigningProvider&) const override { return Singleton(GetScriptForRawPubKey(keys[0])); } +public: + PKDescriptor(std::unique_ptr<PubkeyProvider> prov) : DescriptorImpl(Singleton(std::move(prov)), {}, "pk") {} +}; -/** A parsed combo(P) descriptor. */ -class ComboDescriptor final : public Descriptor +/** A parsed pkh(P) descriptor. */ +class PKHDescriptor final : public DescriptorImpl { - std::unique_ptr<PubkeyProvider> m_provider; +protected: + std::vector<CScript> MakeScripts(const std::vector<CPubKey>& keys, const CScript*, FlatSigningProvider&) const override { return Singleton(GetScriptForDestination(keys[0].GetID())); } +public: + PKHDescriptor(std::unique_ptr<PubkeyProvider> prov) : DescriptorImpl(Singleton(std::move(prov)), {}, "pkh") {} +}; +/** A parsed wpkh(P) descriptor. */ +class WPKHDescriptor final : public DescriptorImpl +{ +protected: + std::vector<CScript> MakeScripts(const std::vector<CPubKey>& keys, const CScript*, FlatSigningProvider&) const override { return Singleton(GetScriptForDestination(WitnessV0KeyHash(keys[0].GetID()))); } public: - ComboDescriptor(std::unique_ptr<PubkeyProvider> provider) : m_provider(std::move(provider)) {} + WPKHDescriptor(std::unique_ptr<PubkeyProvider> prov) : DescriptorImpl(Singleton(std::move(prov)), {}, "wpkh") {} +}; - bool IsRange() const override { return m_provider->IsRange(); } - std::string ToString() const override { return "combo(" + m_provider->ToString() + ")"; } - bool ToPrivateString(const SigningProvider& arg, std::string& out) const override - { - std::string ret; - if (!m_provider->ToPrivateString(arg, ret)) return false; - out = "combo(" + std::move(ret) + ")"; - return true; - } - bool Expand(int pos, const SigningProvider& arg, std::vector<CScript>& output_scripts, FlatSigningProvider& out) const override +/** A parsed combo(P) descriptor. */ +class ComboDescriptor final : public DescriptorImpl +{ +protected: + std::vector<CScript> MakeScripts(const std::vector<CPubKey>& keys, const CScript*, FlatSigningProvider& out) const override { - CPubKey key; - if (!m_provider->GetPubKey(pos, arg, key)) return false; - CKeyID keyid = key.GetID(); - { - CScript p2pk = GetScriptForRawPubKey(key); - CScript p2pkh = GetScriptForDestination(keyid); - output_scripts = std::vector<CScript>{std::move(p2pk), std::move(p2pkh)}; - out.pubkeys.emplace(keyid, key); + std::vector<CScript> ret; + CKeyID id = keys[0].GetID(); + ret.emplace_back(GetScriptForRawPubKey(keys[0])); // P2PK + ret.emplace_back(GetScriptForDestination(id)); // P2PKH + if (keys[0].IsCompressed()) { + CScript p2wpkh = GetScriptForDestination(WitnessV0KeyHash(id)); + out.scripts.emplace(CScriptID(p2wpkh), p2wpkh); + ret.emplace_back(p2wpkh); + ret.emplace_back(GetScriptForDestination(CScriptID(p2wpkh))); // P2SH-P2WPKH } - if (key.IsCompressed()) { - CScript p2wpkh = GetScriptForDestination(WitnessV0KeyHash(keyid)); - CScriptID p2wpkh_id(p2wpkh); - CScript p2sh_p2wpkh = GetScriptForDestination(p2wpkh_id); - out.scripts.emplace(p2wpkh_id, p2wpkh); - output_scripts.push_back(std::move(p2wpkh)); - output_scripts.push_back(std::move(p2sh_p2wpkh)); - } - return true; + return ret; } +public: + ComboDescriptor(std::unique_ptr<PubkeyProvider> prov) : DescriptorImpl(Singleton(std::move(prov)), {}, "combo") {} +}; + +/** A parsed multi(...) descriptor. */ +class MultisigDescriptor final : public DescriptorImpl +{ + const int m_threshold; +protected: + std::string ToStringExtra() const override { return strprintf("%i", m_threshold); } + std::vector<CScript> MakeScripts(const std::vector<CPubKey>& keys, const CScript*, FlatSigningProvider&) const override { return Singleton(GetScriptForMultisig(m_threshold, keys)); } +public: + MultisigDescriptor(int threshold, std::vector<std::unique_ptr<PubkeyProvider>> providers) : DescriptorImpl(std::move(providers), {}, "multi"), m_threshold(threshold) {} +}; + +/** A parsed sh(...) descriptor. */ +class SHDescriptor final : public DescriptorImpl +{ +protected: + std::vector<CScript> MakeScripts(const std::vector<CPubKey>&, const CScript* script, FlatSigningProvider&) const override { return Singleton(GetScriptForDestination(CScriptID(*script))); } +public: + SHDescriptor(std::unique_ptr<DescriptorImpl> desc) : DescriptorImpl({}, std::move(desc), "sh") {} +}; + +/** A parsed wsh(...) descriptor. */ +class WSHDescriptor final : public DescriptorImpl +{ +protected: + std::vector<CScript> MakeScripts(const std::vector<CPubKey>&, const CScript* script, FlatSigningProvider&) const override { return Singleton(GetScriptForDestination(WitnessV0ScriptHash(*script))); } +public: + WSHDescriptor(std::unique_ptr<DescriptorImpl> desc) : DescriptorImpl({}, std::move(desc), "wsh") {} }; //////////////////////////////////////////////////////////////////////////// @@ -431,7 +529,7 @@ std::vector<Span<const char>> Split(const Span<const char>& sp, char sep) } /** Parse a key path, being passed a split list of elements (the first element is ignored). */ -bool ParseKeyPath(const std::vector<Span<const char>>& split, KeyPath& out) +NODISCARD bool ParseKeyPath(const std::vector<Span<const char>>& split, KeyPath& out) { for (size_t i = 1; i < split.size(); ++i) { Span<const char> elem = split[i]; @@ -447,7 +545,8 @@ bool ParseKeyPath(const std::vector<Span<const char>>& split, KeyPath& out) return true; } -std::unique_ptr<PubkeyProvider> ParsePubkey(const Span<const char>& sp, bool permit_uncompressed, FlatSigningProvider& out) +/** Parse a public key that excludes origin information. */ +std::unique_ptr<PubkeyProvider> ParsePubkeyInner(const Span<const char>& sp, bool permit_uncompressed, FlatSigningProvider& out) { auto split = Split(sp, '/'); std::string str(split[0].begin(), split[0].end()); @@ -484,19 +583,41 @@ std::unique_ptr<PubkeyProvider> ParsePubkey(const Span<const char>& sp, bool per return MakeUnique<BIP32PubkeyProvider>(extpubkey, std::move(path), type); } +/** Parse a public key including origin information (if enabled). */ +std::unique_ptr<PubkeyProvider> ParsePubkey(const Span<const char>& sp, bool permit_uncompressed, FlatSigningProvider& out) +{ + auto origin_split = Split(sp, ']'); + if (origin_split.size() > 2) return nullptr; + if (origin_split.size() == 1) return ParsePubkeyInner(origin_split[0], permit_uncompressed, out); + if (origin_split[0].size() < 1 || origin_split[0][0] != '[') return nullptr; + auto slash_split = Split(origin_split[0].subspan(1), '/'); + if (slash_split[0].size() != 8) return nullptr; + std::string fpr_hex = std::string(slash_split[0].begin(), slash_split[0].end()); + if (!IsHex(fpr_hex)) return nullptr; + auto fpr_bytes = ParseHex(fpr_hex); + KeyOriginInfo info; + static_assert(sizeof(info.fingerprint) == 4, "Fingerprint must be 4 bytes"); + assert(fpr_bytes.size() == 4); + std::copy(fpr_bytes.begin(), fpr_bytes.end(), info.fingerprint); + if (!ParseKeyPath(slash_split, info.path)) return nullptr; + auto provider = ParsePubkeyInner(origin_split[1], permit_uncompressed, out); + if (!provider) return nullptr; + return MakeUnique<OriginPubkeyProvider>(std::move(info), std::move(provider)); +} + /** Parse a script in a particular context. */ -std::unique_ptr<Descriptor> ParseScript(Span<const char>& sp, ParseScriptContext ctx, FlatSigningProvider& out) +std::unique_ptr<DescriptorImpl> ParseScript(Span<const char>& sp, ParseScriptContext ctx, FlatSigningProvider& out) { auto expr = Expr(sp); if (Func("pk", expr)) { auto pubkey = ParsePubkey(expr, ctx != ParseScriptContext::P2WSH, out); if (!pubkey) return nullptr; - return MakeUnique<SingleKeyDescriptor>(std::move(pubkey), P2PKGetScript, "pk"); + return MakeUnique<PKDescriptor>(std::move(pubkey)); } if (Func("pkh", expr)) { auto pubkey = ParsePubkey(expr, ctx != ParseScriptContext::P2WSH, out); if (!pubkey) return nullptr; - return MakeUnique<SingleKeyDescriptor>(std::move(pubkey), P2PKHGetScript, "pkh"); + return MakeUnique<PKHDescriptor>(std::move(pubkey)); } if (ctx == ParseScriptContext::TOP && Func("combo", expr)) { auto pubkey = ParsePubkey(expr, true, out); @@ -529,17 +650,17 @@ std::unique_ptr<Descriptor> ParseScript(Span<const char>& sp, ParseScriptContext if (ctx != ParseScriptContext::P2WSH && Func("wpkh", expr)) { auto pubkey = ParsePubkey(expr, false, out); if (!pubkey) return nullptr; - return MakeUnique<SingleKeyDescriptor>(std::move(pubkey), P2WPKHGetScript, "wpkh"); + return MakeUnique<WPKHDescriptor>(std::move(pubkey)); } if (ctx == ParseScriptContext::TOP && Func("sh", expr)) { auto desc = ParseScript(expr, ParseScriptContext::P2SH, out); if (!desc || expr.size()) return nullptr; - return MakeUnique<ConvertorDescriptor>(std::move(desc), ConvertP2SH, "sh"); + return MakeUnique<SHDescriptor>(std::move(desc)); } if (ctx != ParseScriptContext::P2WSH && Func("wsh", expr)) { auto desc = ParseScript(expr, ParseScriptContext::P2WSH, out); if (!desc || expr.size()) return nullptr; - return MakeUnique<ConvertorDescriptor>(std::move(desc), ConvertP2WSH, "wsh"); + return MakeUnique<WSHDescriptor>(std::move(desc)); } if (ctx == ParseScriptContext::TOP && Func("addr", expr)) { CTxDestination dest = DecodeDestination(std::string(expr.begin(), expr.end())); @@ -555,12 +676,91 @@ std::unique_ptr<Descriptor> ParseScript(Span<const char>& sp, ParseScriptContext return nullptr; } +std::unique_ptr<PubkeyProvider> InferPubkey(const CPubKey& pubkey, ParseScriptContext, const SigningProvider& provider) +{ + std::unique_ptr<PubkeyProvider> key_provider = MakeUnique<ConstPubkeyProvider>(pubkey); + KeyOriginInfo info; + if (provider.GetKeyOrigin(pubkey.GetID(), info)) { + return MakeUnique<OriginPubkeyProvider>(std::move(info), std::move(key_provider)); + } + return key_provider; +} + +std::unique_ptr<DescriptorImpl> InferScript(const CScript& script, ParseScriptContext ctx, const SigningProvider& provider) +{ + std::vector<std::vector<unsigned char>> data; + txnouttype txntype = Solver(script, data); + + if (txntype == TX_PUBKEY) { + CPubKey pubkey(data[0].begin(), data[0].end()); + if (pubkey.IsValid()) { + return MakeUnique<PKDescriptor>(InferPubkey(pubkey, ctx, provider)); + } + } + if (txntype == TX_PUBKEYHASH) { + uint160 hash(data[0]); + CKeyID keyid(hash); + CPubKey pubkey; + if (provider.GetPubKey(keyid, pubkey)) { + return MakeUnique<PKHDescriptor>(InferPubkey(pubkey, ctx, provider)); + } + } + if (txntype == TX_WITNESS_V0_KEYHASH && ctx != ParseScriptContext::P2WSH) { + uint160 hash(data[0]); + CKeyID keyid(hash); + CPubKey pubkey; + if (provider.GetPubKey(keyid, pubkey)) { + return MakeUnique<WPKHDescriptor>(InferPubkey(pubkey, ctx, provider)); + } + } + if (txntype == TX_MULTISIG) { + std::vector<std::unique_ptr<PubkeyProvider>> providers; + for (size_t i = 1; i + 1 < data.size(); ++i) { + CPubKey pubkey(data[i].begin(), data[i].end()); + providers.push_back(InferPubkey(pubkey, ctx, provider)); + } + return MakeUnique<MultisigDescriptor>((int)data[0][0], std::move(providers)); + } + if (txntype == TX_SCRIPTHASH && ctx == ParseScriptContext::TOP) { + uint160 hash(data[0]); + CScriptID scriptid(hash); + CScript subscript; + if (provider.GetCScript(scriptid, subscript)) { + auto sub = InferScript(subscript, ParseScriptContext::P2SH, provider); + if (sub) return MakeUnique<SHDescriptor>(std::move(sub)); + } + } + if (txntype == TX_WITNESS_V0_SCRIPTHASH && ctx != ParseScriptContext::P2WSH) { + CScriptID scriptid; + CRIPEMD160().Write(data[0].data(), data[0].size()).Finalize(scriptid.begin()); + CScript subscript; + if (provider.GetCScript(scriptid, subscript)) { + auto sub = InferScript(subscript, ParseScriptContext::P2WSH, provider); + if (sub) return MakeUnique<WSHDescriptor>(std::move(sub)); + } + } + + CTxDestination dest; + if (ExtractDestination(script, dest)) { + if (GetScriptForDestination(dest) == script) { + return MakeUnique<AddressDescriptor>(std::move(dest)); + } + } + + return MakeUnique<RawDescriptor>(script); +} + } // namespace std::unique_ptr<Descriptor> Parse(const std::string& descriptor, FlatSigningProvider& out) { Span<const char> sp(descriptor.data(), descriptor.size()); auto ret = ParseScript(sp, ParseScriptContext::TOP, out); - if (sp.size() == 0 && ret) return ret; + if (sp.size() == 0 && ret) return std::unique_ptr<Descriptor>(std::move(ret)); return nullptr; } + +std::unique_ptr<Descriptor> InferDescriptor(const CScript& script, const SigningProvider& provider) +{ + return InferScript(script, ParseScriptContext::TOP, provider); +} diff --git a/src/script/descriptor.h b/src/script/descriptor.h index 87e07369c7..44f0efca03 100644 --- a/src/script/descriptor.h +++ b/src/script/descriptor.h @@ -32,6 +32,10 @@ struct Descriptor { /** Whether the expansion of this descriptor depends on the position. */ virtual bool IsRange() const = 0; + /** Whether this descriptor has all information about signing ignoring lack of private keys. + * This is true for all descriptors except ones that use `raw` or `addr` constructions. */ + virtual bool IsSolvable() const = 0; + /** Convert the descriptor back to a string, undoing parsing. */ virtual std::string ToString() const = 0; @@ -44,12 +48,37 @@ struct Descriptor { * provider: the provider to query for private keys in case of hardened derivation. * output_script: the expanded scriptPubKeys will be put here. * out: scripts and public keys necessary for solving the expanded scriptPubKeys will be put here (may be equal to provider). + * cache: vector which will be overwritten with cache data necessary to-evaluate the descriptor at this point without access to private keys. */ - virtual bool Expand(int pos, const SigningProvider& provider, std::vector<CScript>& output_scripts, FlatSigningProvider& out) const = 0; + virtual bool Expand(int pos, const SigningProvider& provider, std::vector<CScript>& output_scripts, FlatSigningProvider& out, std::vector<unsigned char>* cache = nullptr) const = 0; + + /** Expand a descriptor at a specified position using cached expansion data. + * + * pos: the position at which to expand the descriptor. If IsRange() is false, this is ignored. + * cache: vector from which cached expansion data will be read. + * output_script: the expanded scriptPubKeys will be put here. + * out: scripts and public keys necessary for solving the expanded scriptPubKeys will be put here (may be equal to provider). + */ + virtual bool ExpandFromCache(int pos, const std::vector<unsigned char>& cache, std::vector<CScript>& output_scripts, FlatSigningProvider& out) const = 0; }; /** Parse a descriptor string. Included private keys are put in out. Returns nullptr if parsing fails. */ std::unique_ptr<Descriptor> Parse(const std::string& descriptor, FlatSigningProvider& out); -#endif // BITCOIN_SCRIPT_DESCRIPTOR_H +/** Find a descriptor for the specified script, using information from provider where possible. + * + * A non-ranged descriptor which only generates the specified script will be returned in all + * circumstances. + * + * For public keys with key origin information, this information will be preserved in the returned + * descriptor. + * + * - If all information for solving `script` is present in `provider`, a descriptor will be returned + * which is `IsSolvable()` and encapsulates said information. + * - Failing that, if `script` corresponds to a known address type, an "addr()" descriptor will be + * returned (which is not `IsSolvable()`). + * - Failing that, a "raw()" descriptor is returned. + */ +std::unique_ptr<Descriptor> InferDescriptor(const CScript& script, const SigningProvider& provider); +#endif // BITCOIN_SCRIPT_DESCRIPTOR_H diff --git a/src/script/ismine.cpp b/src/script/ismine.cpp index 1433ebf42f..51bd2d6e9f 100644 --- a/src/script/ismine.cpp +++ b/src/script/ismine.cpp @@ -23,9 +23,9 @@ namespace { */ enum class IsMineSigVersion { - TOP = 0, //! scriptPubKey execution - P2SH = 1, //! P2SH redeemScript - WITNESS_V0 = 2 //! P2WSH witness script execution + TOP = 0, //!< scriptPubKey execution + P2SH = 1, //!< P2SH redeemScript + WITNESS_V0 = 2, //!< P2WSH witness script execution }; /** @@ -35,10 +35,10 @@ enum class IsMineSigVersion */ enum class IsMineResult { - NO = 0, //! Not ours - WATCH_ONLY = 1, //! Included in watch-only balance - SPENDABLE = 2, //! Included in all balances - INVALID = 3, //! Not spendable by anyone (uncompressed pubkey in segwit, P2SH inside P2SH or witness, witness inside witness) + NO = 0, //!< Not ours + WATCH_ONLY = 1, //!< Included in watch-only balance + SPENDABLE = 2, //!< Included in all balances + INVALID = 3, //!< Not spendable by anyone (uncompressed pubkey in segwit, P2SH inside P2SH or witness, witness inside witness) }; bool PermitsUncompressed(IsMineSigVersion sigversion) diff --git a/src/script/script.cpp b/src/script/script.cpp index 9bdf3ed808..982aa241e7 100644 --- a/src/script/script.cpp +++ b/src/script/script.cpp @@ -6,7 +6,7 @@ #include <script/script.h> #include <tinyformat.h> -#include <utilstrencodings.h> +#include <util/strencodings.h> const char* GetOpName(opcodetype opcode) { diff --git a/src/script/script.h b/src/script/script.h index 00065a24be..1d8ddba2f2 100644 --- a/src/script/script.h +++ b/src/script/script.h @@ -38,6 +38,12 @@ static const int MAX_STACK_SIZE = 1000; // otherwise as UNIX timestamp. static const unsigned int LOCKTIME_THRESHOLD = 500000000; // Tue Nov 5 00:53:20 1985 UTC +// Maximum nLockTime. Since a lock time indicates the last invalid timestamp, a +// transaction with this lock time will never be valid unless lock time +// checking is disabled (by setting all input sequence numbers to +// SEQUENCE_FINAL). +static const uint32_t LOCKTIME_MAX = 0xFFFFFFFFU; + template <typename T> std::vector<unsigned char> ToByteVector(const T& in) { diff --git a/src/script/sigcache.cpp b/src/script/sigcache.cpp index 68f0542294..94005cf6f3 100644 --- a/src/script/sigcache.cpp +++ b/src/script/sigcache.cpp @@ -9,7 +9,7 @@ #include <pubkey.h> #include <random.h> #include <uint256.h> -#include <util.h> +#include <util/system.h> #include <cuckoocache.h> #include <boost/thread.hpp> diff --git a/src/script/sign.cpp b/src/script/sign.cpp index d779910425..792fb2997f 100644 --- a/src/script/sign.cpp +++ b/src/script/sign.cpp @@ -48,7 +48,7 @@ static bool GetCScript(const SigningProvider& provider, const SignatureData& sig return false; } -static bool GetPubKey(const SigningProvider& provider, SignatureData& sigdata, const CKeyID& address, CPubKey& pubkey) +static bool GetPubKey(const SigningProvider& provider, const SignatureData& sigdata, const CKeyID& address, CPubKey& pubkey) { // Look for pubkey in all partial sigs const auto it = sigdata.signatures.find(address); @@ -63,25 +63,21 @@ static bool GetPubKey(const SigningProvider& provider, SignatureData& sigdata, c return true; } // Query the underlying provider - if (provider.GetPubKey(address, pubkey)) { - KeyOriginInfo info; - if (provider.GetKeyOrigin(address, info)) { - sigdata.misc_pubkeys.emplace(address, std::make_pair(pubkey, std::move(info))); - } - return true; - } - return false; + return provider.GetPubKey(address, pubkey); } -static bool CreateSig(const BaseSignatureCreator& creator, SignatureData& sigdata, const SigningProvider& provider, std::vector<unsigned char>& sig_out, const CKeyID& keyid, const CScript& scriptcode, SigVersion sigversion) +static bool CreateSig(const BaseSignatureCreator& creator, SignatureData& sigdata, const SigningProvider& provider, std::vector<unsigned char>& sig_out, const CPubKey& pubkey, const CScript& scriptcode, SigVersion sigversion) { + CKeyID keyid = pubkey.GetID(); const auto it = sigdata.signatures.find(keyid); if (it != sigdata.signatures.end()) { sig_out = it->second.second; return true; } - CPubKey pubkey; - GetPubKey(provider, sigdata, keyid, pubkey); + KeyOriginInfo info; + if (provider.GetKeyOrigin(keyid, info)) { + sigdata.misc_pubkeys.emplace(keyid, std::make_pair(pubkey, std::move(info))); + } if (creator.CreateSig(provider, sig_out, keyid, scriptcode, sigversion)) { auto i = sigdata.signatures.emplace(keyid, SigPair(pubkey, sig_out)); assert(i.second); @@ -114,15 +110,15 @@ static bool SignStep(const SigningProvider& provider, const BaseSignatureCreator case TX_WITNESS_UNKNOWN: return false; case TX_PUBKEY: - if (!CreateSig(creator, sigdata, provider, sig, CPubKey(vSolutions[0]).GetID(), scriptPubKey, sigversion)) return false; + if (!CreateSig(creator, sigdata, provider, sig, CPubKey(vSolutions[0]), scriptPubKey, sigversion)) return false; ret.push_back(std::move(sig)); return true; case TX_PUBKEYHASH: { CKeyID keyID = CKeyID(uint160(vSolutions[0])); - if (!CreateSig(creator, sigdata, provider, sig, keyID, scriptPubKey, sigversion)) return false; - ret.push_back(std::move(sig)); CPubKey pubkey; - GetPubKey(provider, sigdata, keyID, pubkey); + if (!GetPubKey(provider, sigdata, keyID, pubkey)) return false; + if (!CreateSig(creator, sigdata, provider, sig, pubkey, scriptPubKey, sigversion)) return false; + ret.push_back(std::move(sig)); ret.push_back(ToByteVector(pubkey)); return true; } @@ -138,7 +134,7 @@ static bool SignStep(const SigningProvider& provider, const BaseSignatureCreator ret.push_back(valtype()); // workaround CHECKMULTISIG bug for (size_t i = 1; i < vSolutions.size() - 1; ++i) { CPubKey pubkey = CPubKey(vSolutions[i]); - if (ret.size() < required + 1 && CreateSig(creator, sigdata, provider, sig, pubkey.GetID(), scriptPubKey, sigversion)) { + if (ret.size() < required + 1 && CreateSig(creator, sigdata, provider, sig, pubkey, scriptPubKey, sigversion)) { ret.push_back(std::move(sig)); } } @@ -236,10 +232,17 @@ bool ProduceSignature(const SigningProvider& provider, const BaseSignatureCreato return sigdata.complete; } -bool SignPSBTInput(const SigningProvider& provider, const CMutableTransaction& tx, PSBTInput& input, int index, int sighash) +bool PSBTInputSigned(PSBTInput& input) { - // if this input has a final scriptsig or scriptwitness, don't do anything with it - if (!input.final_script_sig.empty() || !input.final_script_witness.IsNull()) { + return !input.final_script_sig.empty() || !input.final_script_witness.IsNull(); +} + +bool SignPSBTInput(const SigningProvider& provider, PartiallySignedTransaction& psbt, int index, int sighash) +{ + PSBTInput& input = psbt.inputs.at(index); + const CMutableTransaction& tx = *psbt.tx; + + if (PSBTInputSigned(input)) { return true; } @@ -250,15 +253,19 @@ bool SignPSBTInput(const SigningProvider& provider, const CMutableTransaction& t // Get UTXO bool require_witness_sig = false; CTxOut utxo; + + // Verify input sanity, which checks that at most one of witness or non-witness utxos is provided. + if (!input.IsSane()) { + return false; + } + if (input.non_witness_utxo) { // If we're taking our information from a non-witness UTXO, verify that it matches the prevout. - if (input.non_witness_utxo->GetHash() != tx.vin[index].prevout.hash) return false; - // If both witness and non-witness UTXO are provided, verify that they match. This check shouldn't - // matter, as the PSBT deserializer enforces only one of both is provided, and the only way both - // can be present is when they're added simultaneously by FillPSBT (in which case they always match). - // Still, check in order to not rely on callers to enforce this. - if (!input.witness_utxo.IsNull() && input.non_witness_utxo->vout[tx.vin[index].prevout.n] != input.witness_utxo) return false; - utxo = input.non_witness_utxo->vout[tx.vin[index].prevout.n]; + COutPoint prevout = tx.vin[index].prevout; + if (input.non_witness_utxo->GetHash() != prevout.hash) { + return false; + } + utxo = input.non_witness_utxo->vout[prevout.n]; } else if (!input.witness_utxo.IsNull()) { utxo = input.witness_utxo; // When we're taking our information from a witness UTXO, we can't verify it is actually data from @@ -277,13 +284,10 @@ bool SignPSBTInput(const SigningProvider& provider, const CMutableTransaction& t if (require_witness_sig && !sigdata.witness) return false; input.FromSignatureData(sigdata); - // If both UTXO types are present, drop the unnecessary one. - if (input.non_witness_utxo && !input.witness_utxo.IsNull()) { - if (sigdata.witness) { - input.non_witness_utxo = nullptr; - } else { - input.witness_utxo.SetNull(); - } + // If we have a witness signature, use the smaller witness UTXO. + if (sigdata.witness) { + input.witness_utxo = utxo; + input.non_witness_utxo = nullptr; } return sig_complete; @@ -505,6 +509,12 @@ bool IsSolvable(const SigningProvider& provider, const CScript& script) return false; } +PartiallySignedTransaction::PartiallySignedTransaction(const CMutableTransaction& tx) : tx(tx) +{ + inputs.resize(tx.vin.size()); + outputs.resize(tx.vout.size()); +} + bool PartiallySignedTransaction::IsNull() const { return !tx && inputs.empty() && outputs.empty() && unknown.empty(); @@ -683,6 +693,7 @@ bool HidingSigningProvider::GetKeyOrigin(const CKeyID& keyid, KeyOriginInfo& inf 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 { return LookupHelper(origins, keyid, info); } bool FlatSigningProvider::GetKey(const CKeyID& keyid, CKey& key) const { return LookupHelper(keys, keyid, key); } FlatSigningProvider Merge(const FlatSigningProvider& a, const FlatSigningProvider& b) @@ -694,5 +705,7 @@ FlatSigningProvider Merge(const FlatSigningProvider& a, const FlatSigningProvide 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; } diff --git a/src/script/sign.h b/src/script/sign.h index 2fc4575e59..e884f4c480 100644 --- a/src/script/sign.h +++ b/src/script/sign.h @@ -24,6 +24,11 @@ struct KeyOriginInfo { unsigned char fingerprint[4]; 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; + } }; /** An interface to be implemented by keystores that support signing. */ @@ -34,7 +39,7 @@ public: 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& id, KeyOriginInfo& info) const { return false; } + virtual bool GetKeyOrigin(const CKeyID& keyid, KeyOriginInfo& info) const { return false; } }; extern const SigningProvider& DUMMY_SIGNING_PROVIDER; @@ -58,10 +63,12 @@ struct FlatSigningProvider final : public SigningProvider { std::map<CScriptID, CScript> scripts; std::map<CKeyID, CPubKey> pubkeys; + std::map<CKeyID, 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; }; @@ -204,6 +211,9 @@ template<typename Stream> void SerializeHDKeypaths(Stream& s, const std::map<CPubKey, KeyOriginInfo>& hd_keypaths, uint8_t type) { for (auto keypath_pair : hd_keypaths) { + if (!keypath_pair.first.IsValid()) { + throw std::ios_base::failure("Invalid CPubKey being serialized"); + } SerializeToVector(s, type, MakeSpan(keypath_pair.first)); WriteCompactSize(s, (keypath_pair.second.path.size() + 1) * sizeof(uint32_t)); s << keypath_pair.second.fingerprint; @@ -300,6 +310,7 @@ struct PSBTInput template <typename Stream> inline void Unserialize(Stream& s) { // Read loop + bool found_sep = false; while(!s.empty()) { // Read std::vector<unsigned char> key; @@ -307,7 +318,10 @@ struct PSBTInput // the key is empty if that was actually a separator byte // This is a special case for key lengths 0 as those are not allowed (except for separator) - if (key.empty()) return; + if (key.empty()) { + found_sep = true; + break; + } // First byte of key is the type unsigned char type = key[0]; @@ -422,6 +436,10 @@ struct PSBTInput break; } } + + if (!found_sep) { + throw std::ios_base::failure("Separator is missing at the end of an input map"); + } } template <typename Stream> @@ -475,6 +493,7 @@ struct PSBTOutput template <typename Stream> inline void Unserialize(Stream& s) { // Read loop + bool found_sep = false; while(!s.empty()) { // Read std::vector<unsigned char> key; @@ -482,7 +501,10 @@ struct PSBTOutput // the key is empty if that was actually a separator byte // This is a special case for key lengths 0 as those are not allowed (except for separator) - if (key.empty()) return; + if (key.empty()) { + found_sep = true; + break; + } // First byte of key is the type unsigned char type = key[0]; @@ -527,6 +549,10 @@ struct PSBTOutput } } } + + if (!found_sep) { + throw std::ios_base::failure("Separator is missing at the end of an output map"); + } } template <typename Stream> @@ -548,6 +574,7 @@ struct PartiallySignedTransaction bool IsSane() const; PartiallySignedTransaction() {} PartiallySignedTransaction(const PartiallySignedTransaction& psbt_in) : tx(psbt_in.tx), inputs(psbt_in.inputs), outputs(psbt_in.outputs), unknown(psbt_in.unknown) {} + explicit PartiallySignedTransaction(const CMutableTransaction& tx); // Only checks if they refer to the same transaction friend bool operator==(const PartiallySignedTransaction& a, const PartiallySignedTransaction &b) @@ -602,6 +629,7 @@ struct PartiallySignedTransaction } // Read global data + bool found_sep = false; while(!s.empty()) { // Read std::vector<unsigned char> key; @@ -609,7 +637,10 @@ struct PartiallySignedTransaction // the key is empty if that was actually a separator byte // This is a special case for key lengths 0 as those are not allowed (except for separator) - if (key.empty()) break; + if (key.empty()) { + found_sep = true; + break; + } // First byte of key is the type unsigned char type = key[0]; @@ -649,6 +680,10 @@ struct PartiallySignedTransaction } } + if (!found_sep) { + throw std::ios_base::failure("Separator is missing at the end of the global map"); + } + // Make sure that we got an unsigned tx if (!tx) { throw std::ios_base::failure("No unsigned transcation was provided"); @@ -703,8 +738,11 @@ bool ProduceSignature(const SigningProvider& provider, const BaseSignatureCreato bool SignSignature(const SigningProvider &provider, const CScript& fromPubKey, CMutableTransaction& txTo, unsigned int nIn, const CAmount& amount, int nHashType); bool SignSignature(const SigningProvider &provider, const CTransaction& txFrom, CMutableTransaction& txTo, unsigned int nIn, int nHashType); +/** Checks whether a PSBTInput is already signed. */ +bool PSBTInputSigned(PSBTInput& input); + /** Signs a PSBTInput, verifying that all provided data matches what is being signed. */ -bool SignPSBTInput(const SigningProvider& provider, const CMutableTransaction& tx, PSBTInput& input, int index, int sighash = SIGHASH_ALL); +bool SignPSBTInput(const SigningProvider& provider, PartiallySignedTransaction& psbt, int index, int sighash = SIGHASH_ALL); /** Extract signature data from a transaction input, and insert it. */ SignatureData DataFromTransaction(const CMutableTransaction& tx, unsigned int nIn, const CTxOut& txout); diff --git a/src/script/standard.cpp b/src/script/standard.cpp index 08ba1b1e0f..31bfd04b0f 100644 --- a/src/script/standard.cpp +++ b/src/script/standard.cpp @@ -8,8 +8,8 @@ #include <crypto/sha256.h> #include <pubkey.h> #include <script/script.h> -#include <util.h> -#include <utilstrencodings.h> +#include <util/system.h> +#include <util/strencodings.h> typedef std::vector<unsigned char> valtype; diff --git a/src/streams.h b/src/streams.h index dc20f7a9da..0809c96be1 100644 --- a/src/streams.h +++ b/src/streams.h @@ -64,12 +64,6 @@ public: size_t size() const { return stream->size(); } }; -template<typename S> -OverrideStream<S> WithOrVersion(S* s, int nVersionFlag) -{ - return OverrideStream<S>(s, s->GetType(), s->GetVersion() | nVersionFlag); -} - /* Minimal stream for overwriting and/or appending to an existing byte vector * * The referenced vector will grow as necessary @@ -126,12 +120,6 @@ class CVectorWriter { return nType; } - void seek(size_t nSize) - { - nPos += nSize; - if(nPos > vchData.size()) - vchData.resize(nPos); - } private: const int nType; const int nVersion; @@ -158,9 +146,11 @@ public: * @param[in] pos Starting position. Vector index where reads should start. */ VectorReader(int type, int version, const std::vector<unsigned char>& data, size_t pos) - : m_type(type), m_version(version), m_data(data) + : m_type(type), m_version(version), m_data(data), m_pos(pos) { - seek(pos); + if (m_pos > m_data.size()) { + throw std::ios_base::failure("VectorReader(...): end of data (m_pos > m_data.size())"); + } } /* @@ -203,14 +193,6 @@ public: memcpy(dst, m_data.data() + m_pos, n); m_pos = pos_next; } - - void seek(size_t n) - { - m_pos += n; - if (m_pos > m_data.size()) { - throw std::ios_base::failure("VectorReader::seek(): end of data"); - } - } }; /** Double ended buffer combining vector and stream-like interfaces. @@ -761,7 +743,7 @@ protected: public: CBufferedFile(FILE *fileIn, uint64_t nBufSize, uint64_t nRewindIn, int nTypeIn, int nVersionIn) : - nType(nTypeIn), nVersion(nVersionIn), nSrcPos(0), nReadPos(0), nReadLimit((uint64_t)(-1)), nRewind(nRewindIn), vchBuf(nBufSize, 0) + nType(nTypeIn), nVersion(nVersionIn), nSrcPos(0), nReadPos(0), nReadLimit(std::numeric_limits<uint64_t>::max()), nRewind(nRewindIn), vchBuf(nBufSize, 0) { src = fileIn; } @@ -846,7 +828,7 @@ public: // prevent reading beyond a certain position // no argument removes the limit - bool SetLimit(uint64_t nPos = (uint64_t)(-1)) { + bool SetLimit(uint64_t nPos = std::numeric_limits<uint64_t>::max()) { if (nPos < nReadPos) return false; nReadLimit = nPos; diff --git a/src/support/allocators/secure.h b/src/support/allocators/secure.h index 7cd0df135d..57f5b1f733 100644 --- a/src/support/allocators/secure.h +++ b/src/support/allocators/secure.h @@ -40,7 +40,11 @@ struct secure_allocator : public std::allocator<T> { T* allocate(std::size_t n, const void* hint = 0) { - return static_cast<T*>(LockedPoolManager::Instance().alloc(sizeof(T) * n)); + T* allocation = static_cast<T*>(LockedPoolManager::Instance().alloc(sizeof(T) * n)); + if (!allocation) { + throw std::bad_alloc(); + } + return allocation; } void deallocate(T* p, std::size_t n) diff --git a/src/support/lockedpool.cpp b/src/support/lockedpool.cpp index 8d577cf521..627018083e 100644 --- a/src/support/lockedpool.cpp +++ b/src/support/lockedpool.cpp @@ -248,6 +248,9 @@ void *PosixLockedPageAllocator::AllocateLocked(size_t len, bool *lockingSuccess) void *addr; len = align_up(len, page_size); addr = mmap(nullptr, len, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0); + if (addr == MAP_FAILED) { + return nullptr; + } if (addr) { *lockingSuccess = mlock(addr, len) == 0; } diff --git a/src/support/lockedpool.h b/src/support/lockedpool.h index 48ffd7b307..b420c909fc 100644 --- a/src/support/lockedpool.h +++ b/src/support/lockedpool.h @@ -22,7 +22,7 @@ public: virtual ~LockedPageAllocator() {} /** Allocate and lock memory pages. * If len is not a multiple of the system page size, it is rounded up. - * Returns 0 in case of allocation failure. + * Returns nullptr in case of allocation failure. * * If locking the memory pages could not be accomplished it will still * return the memory, however the lockingSuccess flag will be false. diff --git a/src/sync.cpp b/src/sync.cpp index c9aa98dcd6..30811f5f89 100644 --- a/src/sync.cpp +++ b/src/sync.cpp @@ -5,7 +5,7 @@ #include <sync.h> #include <logging.h> -#include <utilstrencodings.h> +#include <util/strencodings.h> #include <stdio.h> diff --git a/src/sync.h b/src/sync.h index 40709bdd7f..3857eda56b 100644 --- a/src/sync.h +++ b/src/sync.h @@ -20,7 +20,7 @@ //////////////////////////////////////////////// /* -CCriticalSection mutex; +RecursiveMutex mutex; std::recursive_mutex mutex; LOCK(mutex); @@ -104,6 +104,7 @@ public: * Wrapped mutex: supports recursive locking, but no waiting * TODO: We should move away from using the recursive lock by default. */ +using RecursiveMutex = AnnotatedMixin<std::recursive_mutex>; typedef AnnotatedMixin<std::recursive_mutex> CCriticalSection; /** Wrapped mutex: supports waiting but not recursive locking */ diff --git a/src/test/addrman_tests.cpp b/src/test/addrman_tests.cpp index f57d0c6d79..22347fbc57 100644 --- a/src/test/addrman_tests.cpp +++ b/src/test/addrman_tests.cpp @@ -12,13 +12,9 @@ class CAddrManTest : public CAddrMan { - uint64_t state; - public: explicit CAddrManTest(bool makeDeterministic = true) { - state = 1; - if (makeDeterministic) { // Set addrman addr placement to be deterministic. MakeDeterministic(); @@ -32,30 +28,28 @@ public: insecure_rand = FastRandomContext(true); } - int RandomInt(int nMax) override - { - state = (CHashWriter(SER_GETHASH, 0) << state).GetHash().GetCheapHash(); - return (unsigned int)(state % nMax); - } - CAddrInfo* Find(const CNetAddr& addr, int* pnId = nullptr) { + LOCK(cs); return CAddrMan::Find(addr, pnId); } CAddrInfo* Create(const CAddress& addr, const CNetAddr& addrSource, int* pnId = nullptr) { + LOCK(cs); return CAddrMan::Create(addr, addrSource, pnId); } void Delete(int nId) { + LOCK(cs); CAddrMan::Delete(nId); } // Simulates connection failure so that we can test eviction of offline nodes void SimConnFail(CService& addr) { + LOCK(cs); int64_t nLastSuccess = 1; Good_(addr, true, nLastSuccess); // Set last good connection in the deep past. @@ -150,11 +144,11 @@ BOOST_AUTO_TEST_CASE(addrman_ports) // Test 7; Addr with same IP but diff port does not replace existing addr. CService addr1 = ResolveService("250.1.1.1", 8333); - addrman.Add(CAddress(addr1, NODE_NONE), source); + BOOST_CHECK(addrman.Add(CAddress(addr1, NODE_NONE), source)); BOOST_CHECK_EQUAL(addrman.size(), 1U); CService addr1_port = ResolveService("250.1.1.1", 8334); - addrman.Add(CAddress(addr1_port, NODE_NONE), source); + BOOST_CHECK(!addrman.Add(CAddress(addr1_port, NODE_NONE), source)); BOOST_CHECK_EQUAL(addrman.size(), 1U); CAddrInfo addr_ret2 = addrman.Select(); BOOST_CHECK_EQUAL(addr_ret2.ToString(), "250.1.1.1:8333"); @@ -177,7 +171,7 @@ BOOST_AUTO_TEST_CASE(addrman_select) // Test: Select from new with 1 addr in new. CService addr1 = ResolveService("250.1.1.1", 8333); - addrman.Add(CAddress(addr1, NODE_NONE), source); + BOOST_CHECK(addrman.Add(CAddress(addr1, NODE_NONE), source)); BOOST_CHECK_EQUAL(addrman.size(), 1U); bool newOnly = true; @@ -201,20 +195,20 @@ BOOST_AUTO_TEST_CASE(addrman_select) CService addr3 = ResolveService("250.3.2.2", 9999); CService addr4 = ResolveService("250.3.3.3", 9999); - addrman.Add(CAddress(addr2, NODE_NONE), ResolveService("250.3.1.1", 8333)); - addrman.Add(CAddress(addr3, NODE_NONE), ResolveService("250.3.1.1", 8333)); - addrman.Add(CAddress(addr4, NODE_NONE), ResolveService("250.4.1.1", 8333)); + BOOST_CHECK(addrman.Add(CAddress(addr2, NODE_NONE), ResolveService("250.3.1.1", 8333))); + BOOST_CHECK(addrman.Add(CAddress(addr3, NODE_NONE), ResolveService("250.3.1.1", 8333))); + BOOST_CHECK(addrman.Add(CAddress(addr4, NODE_NONE), ResolveService("250.4.1.1", 8333))); // Add three addresses to tried table. CService addr5 = ResolveService("250.4.4.4", 8333); CService addr6 = ResolveService("250.4.5.5", 7777); CService addr7 = ResolveService("250.4.6.6", 8333); - addrman.Add(CAddress(addr5, NODE_NONE), ResolveService("250.3.1.1", 8333)); + BOOST_CHECK(addrman.Add(CAddress(addr5, NODE_NONE), ResolveService("250.3.1.1", 8333))); addrman.Good(CAddress(addr5, NODE_NONE)); - addrman.Add(CAddress(addr6, NODE_NONE), ResolveService("250.3.1.1", 8333)); + BOOST_CHECK(addrman.Add(CAddress(addr6, NODE_NONE), ResolveService("250.3.1.1", 8333))); addrman.Good(CAddress(addr6, NODE_NONE)); - addrman.Add(CAddress(addr7, NODE_NONE), ResolveService("250.1.1.3", 8333)); + BOOST_CHECK(addrman.Add(CAddress(addr7, NODE_NONE), ResolveService("250.1.1.3", 8333))); addrman.Good(CAddress(addr7, NODE_NONE)); // Test: 6 addrs + 1 addr from last test = 7. @@ -238,7 +232,7 @@ BOOST_AUTO_TEST_CASE(addrman_new_collisions) for (unsigned int i = 1; i < 18; i++) { CService addr = ResolveService("250.1.1." + std::to_string(i)); - addrman.Add(CAddress(addr, NODE_NONE), source); + BOOST_CHECK(addrman.Add(CAddress(addr, NODE_NONE), source)); //Test: No collision in new table yet. BOOST_CHECK_EQUAL(addrman.size(), i); @@ -246,11 +240,11 @@ BOOST_AUTO_TEST_CASE(addrman_new_collisions) //Test: new table collision! CService addr1 = ResolveService("250.1.1.18"); - addrman.Add(CAddress(addr1, NODE_NONE), source); + BOOST_CHECK(addrman.Add(CAddress(addr1, NODE_NONE), source)); BOOST_CHECK_EQUAL(addrman.size(), 17U); CService addr2 = ResolveService("250.1.1.19"); - addrman.Add(CAddress(addr2, NODE_NONE), source); + BOOST_CHECK(addrman.Add(CAddress(addr2, NODE_NONE), source)); BOOST_CHECK_EQUAL(addrman.size(), 18U); } @@ -264,7 +258,7 @@ BOOST_AUTO_TEST_CASE(addrman_tried_collisions) for (unsigned int i = 1; i < 80; i++) { CService addr = ResolveService("250.1.1." + std::to_string(i)); - addrman.Add(CAddress(addr, NODE_NONE), source); + BOOST_CHECK(addrman.Add(CAddress(addr, NODE_NONE), source)); addrman.Good(CAddress(addr, NODE_NONE)); //Test: No collision in tried table yet. @@ -273,11 +267,11 @@ BOOST_AUTO_TEST_CASE(addrman_tried_collisions) //Test: tried table collision! CService addr1 = ResolveService("250.1.1.80"); - addrman.Add(CAddress(addr1, NODE_NONE), source); + BOOST_CHECK(addrman.Add(CAddress(addr1, NODE_NONE), source)); BOOST_CHECK_EQUAL(addrman.size(), 79U); CService addr2 = ResolveService("250.1.1.81"); - addrman.Add(CAddress(addr2, NODE_NONE), source); + BOOST_CHECK(addrman.Add(CAddress(addr2, NODE_NONE), source)); BOOST_CHECK_EQUAL(addrman.size(), 80U); } @@ -294,9 +288,9 @@ BOOST_AUTO_TEST_CASE(addrman_find) CNetAddr source1 = ResolveIP("250.1.2.1"); CNetAddr source2 = ResolveIP("250.1.2.2"); - addrman.Add(addr1, source1); - addrman.Add(addr2, source2); - addrman.Add(addr3, source1); + BOOST_CHECK(addrman.Add(addr1, source1)); + BOOST_CHECK(!addrman.Add(addr2, source2)); + BOOST_CHECK(addrman.Add(addr3, source1)); // Test: ensure Find returns an IP matching what we searched on. CAddrInfo* info1 = addrman.Find(addr1); @@ -378,11 +372,11 @@ BOOST_AUTO_TEST_CASE(addrman_getaddr) CNetAddr source2 = ResolveIP("250.2.3.3"); // Test: Ensure GetAddr works with new addresses. - addrman.Add(addr1, source1); - addrman.Add(addr2, source2); - addrman.Add(addr3, source1); - addrman.Add(addr4, source2); - addrman.Add(addr5, source1); + BOOST_CHECK(addrman.Add(addr1, source1)); + BOOST_CHECK(addrman.Add(addr2, source2)); + BOOST_CHECK(addrman.Add(addr3, source1)); + BOOST_CHECK(addrman.Add(addr4, source2)); + BOOST_CHECK(addrman.Add(addr5, source1)); // GetAddr returns 23% of addresses, 23% of 5 is 1 rounded down. BOOST_CHECK_EQUAL(addrman.GetAddr().size(), 1U); @@ -551,7 +545,7 @@ BOOST_AUTO_TEST_CASE(addrman_selecttriedcollision) CNetAddr source = ResolveIP("252.2.2.2"); for (unsigned int i = 1; i < 23; i++) { CService addr = ResolveService("250.1.1."+std::to_string(i)); - addrman.Add(CAddress(addr, NODE_NONE), source); + BOOST_CHECK(addrman.Add(CAddress(addr, NODE_NONE), source)); addrman.Good(addr); // No collisions yet. @@ -581,7 +575,7 @@ BOOST_AUTO_TEST_CASE(addrman_noevict) CNetAddr source = ResolveIP("252.2.2.2"); for (unsigned int i = 1; i < 23; i++) { CService addr = ResolveService("250.1.1."+std::to_string(i)); - addrman.Add(CAddress(addr, NODE_NONE), source); + BOOST_CHECK(addrman.Add(CAddress(addr, NODE_NONE), source)); addrman.Good(addr); // No collision yet. @@ -591,7 +585,7 @@ BOOST_AUTO_TEST_CASE(addrman_noevict) // Collision between 23 and 19. CService addr23 = ResolveService("250.1.1.23"); - addrman.Add(CAddress(addr23, NODE_NONE), source); + BOOST_CHECK(addrman.Add(CAddress(addr23, NODE_NONE), source)); addrman.Good(addr23); BOOST_CHECK(addrman.size() == 23); @@ -604,7 +598,7 @@ BOOST_AUTO_TEST_CASE(addrman_noevict) // Lets create two collisions. for (unsigned int i = 24; i < 33; i++) { CService addr = ResolveService("250.1.1."+std::to_string(i)); - addrman.Add(CAddress(addr, NODE_NONE), source); + BOOST_CHECK(addrman.Add(CAddress(addr, NODE_NONE), source)); addrman.Good(addr); BOOST_CHECK(addrman.size() == i); @@ -613,14 +607,14 @@ BOOST_AUTO_TEST_CASE(addrman_noevict) // Cause a collision. CService addr33 = ResolveService("250.1.1.33"); - addrman.Add(CAddress(addr33, NODE_NONE), source); + BOOST_CHECK(addrman.Add(CAddress(addr33, NODE_NONE), source)); addrman.Good(addr33); BOOST_CHECK(addrman.size() == 33); BOOST_CHECK(addrman.SelectTriedCollision().ToString() == "250.1.1.27:0"); // Cause a second collision. - addrman.Add(CAddress(addr23, NODE_NONE), source); + BOOST_CHECK(!addrman.Add(CAddress(addr23, NODE_NONE), source)); addrman.Good(addr23); BOOST_CHECK(addrman.size() == 33); @@ -645,7 +639,7 @@ BOOST_AUTO_TEST_CASE(addrman_evictionworks) CNetAddr source = ResolveIP("252.2.2.2"); for (unsigned int i = 1; i < 23; i++) { CService addr = ResolveService("250.1.1."+std::to_string(i)); - addrman.Add(CAddress(addr, NODE_NONE), source); + BOOST_CHECK(addrman.Add(CAddress(addr, NODE_NONE), source)); addrman.Good(addr); // No collision yet. @@ -655,7 +649,7 @@ BOOST_AUTO_TEST_CASE(addrman_evictionworks) // Collision between 23 and 19. CService addr = ResolveService("250.1.1.23"); - addrman.Add(CAddress(addr, NODE_NONE), source); + BOOST_CHECK(addrman.Add(CAddress(addr, NODE_NONE), source)); addrman.Good(addr); BOOST_CHECK(addrman.size() == 23); @@ -670,14 +664,14 @@ BOOST_AUTO_TEST_CASE(addrman_evictionworks) BOOST_CHECK(addrman.SelectTriedCollision().ToString() == "[::]:0"); // If 23 was swapped for 19, then this should cause no collisions. - addrman.Add(CAddress(addr, NODE_NONE), source); + BOOST_CHECK(!addrman.Add(CAddress(addr, NODE_NONE), source)); addrman.Good(addr); BOOST_CHECK(addrman.SelectTriedCollision().ToString() == "[::]:0"); // If we insert 19 is should collide with 23. CService addr19 = ResolveService("250.1.1.19"); - addrman.Add(CAddress(addr19, NODE_NONE), source); + BOOST_CHECK(!addrman.Add(CAddress(addr19, NODE_NONE), source)); addrman.Good(addr19); BOOST_CHECK(addrman.SelectTriedCollision().ToString() == "250.1.1.23:0"); diff --git a/src/test/allocator_tests.cpp b/src/test/allocator_tests.cpp index c72c062b81..9eded4f5b2 100644 --- a/src/test/allocator_tests.cpp +++ b/src/test/allocator_tests.cpp @@ -2,7 +2,7 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#include <util.h> +#include <util/system.h> #include <support/allocators/secure.h> #include <test/test_bitcoin.h> @@ -105,13 +105,13 @@ BOOST_AUTO_TEST_CASE(arena_tests) // Go entirely wild: free and alloc interleaved, // generate targets and sizes using pseudo-randomness. for (int x=0; x<2048; ++x) - addr.push_back(0); + addr.push_back(nullptr); uint32_t s = 0x12345678; for (int x=0; x<5000; ++x) { int idx = s & (addr.size()-1); if (s & 0x80000000) { b.free(addr[idx]); - addr[idx] = 0; + addr[idx] = nullptr; } else if(!addr[idx]) { addr[idx] = b.alloc((s >> 16) & 2047); } @@ -144,9 +144,9 @@ public: *lockingSuccess = true; } - return reinterpret_cast<void*>(0x08000000 + (count<<24)); // Fake address, do not actually use this memory + return reinterpret_cast<void*>(uint64_t{static_cast<uint64_t>(0x08000000) + (count << 24)}); // Fake address, do not actually use this memory } - return 0; + return nullptr; } void FreeLocked(void* addr, size_t len) override { diff --git a/src/test/amount_tests.cpp b/src/test/amount_tests.cpp index adffcfeef5..1ff040b077 100644 --- a/src/test/amount_tests.cpp +++ b/src/test/amount_tests.cpp @@ -13,8 +13,10 @@ BOOST_FIXTURE_TEST_SUITE(amount_tests, BasicTestingSetup) BOOST_AUTO_TEST_CASE(MoneyRangeTest) { BOOST_CHECK_EQUAL(MoneyRange(CAmount(-1)), false); - BOOST_CHECK_EQUAL(MoneyRange(MAX_MONEY + CAmount(1)), false); + BOOST_CHECK_EQUAL(MoneyRange(CAmount(0)), true); BOOST_CHECK_EQUAL(MoneyRange(CAmount(1)), true); + BOOST_CHECK_EQUAL(MoneyRange(MAX_MONEY), true); + BOOST_CHECK_EQUAL(MoneyRange(MAX_MONEY + CAmount(1)), false); } BOOST_AUTO_TEST_CASE(GetFeeTest) @@ -23,43 +25,43 @@ BOOST_AUTO_TEST_CASE(GetFeeTest) feeRate = CFeeRate(0); // Must always return 0 - BOOST_CHECK_EQUAL(feeRate.GetFee(0), 0); - BOOST_CHECK_EQUAL(feeRate.GetFee(1e5), 0); + BOOST_CHECK_EQUAL(feeRate.GetFee(0), CAmount(0)); + BOOST_CHECK_EQUAL(feeRate.GetFee(1e5), CAmount(0)); feeRate = CFeeRate(1000); // Must always just return the arg - BOOST_CHECK_EQUAL(feeRate.GetFee(0), 0); - BOOST_CHECK_EQUAL(feeRate.GetFee(1), 1); - BOOST_CHECK_EQUAL(feeRate.GetFee(121), 121); - BOOST_CHECK_EQUAL(feeRate.GetFee(999), 999); - BOOST_CHECK_EQUAL(feeRate.GetFee(1e3), 1e3); - BOOST_CHECK_EQUAL(feeRate.GetFee(9e3), 9e3); + BOOST_CHECK_EQUAL(feeRate.GetFee(0), CAmount(0)); + BOOST_CHECK_EQUAL(feeRate.GetFee(1), CAmount(1)); + BOOST_CHECK_EQUAL(feeRate.GetFee(121), CAmount(121)); + BOOST_CHECK_EQUAL(feeRate.GetFee(999), CAmount(999)); + BOOST_CHECK_EQUAL(feeRate.GetFee(1e3), CAmount(1e3)); + BOOST_CHECK_EQUAL(feeRate.GetFee(9e3), CAmount(9e3)); feeRate = CFeeRate(-1000); // Must always just return -1 * arg - BOOST_CHECK_EQUAL(feeRate.GetFee(0), 0); - BOOST_CHECK_EQUAL(feeRate.GetFee(1), -1); - BOOST_CHECK_EQUAL(feeRate.GetFee(121), -121); - BOOST_CHECK_EQUAL(feeRate.GetFee(999), -999); - BOOST_CHECK_EQUAL(feeRate.GetFee(1e3), -1e3); - BOOST_CHECK_EQUAL(feeRate.GetFee(9e3), -9e3); + BOOST_CHECK_EQUAL(feeRate.GetFee(0), CAmount(0)); + BOOST_CHECK_EQUAL(feeRate.GetFee(1), CAmount(-1)); + BOOST_CHECK_EQUAL(feeRate.GetFee(121), CAmount(-121)); + BOOST_CHECK_EQUAL(feeRate.GetFee(999), CAmount(-999)); + BOOST_CHECK_EQUAL(feeRate.GetFee(1e3), CAmount(-1e3)); + BOOST_CHECK_EQUAL(feeRate.GetFee(9e3), CAmount(-9e3)); feeRate = CFeeRate(123); // Truncates the result, if not integer - BOOST_CHECK_EQUAL(feeRate.GetFee(0), 0); - BOOST_CHECK_EQUAL(feeRate.GetFee(8), 1); // Special case: returns 1 instead of 0 - BOOST_CHECK_EQUAL(feeRate.GetFee(9), 1); - BOOST_CHECK_EQUAL(feeRate.GetFee(121), 14); - BOOST_CHECK_EQUAL(feeRate.GetFee(122), 15); - BOOST_CHECK_EQUAL(feeRate.GetFee(999), 122); - BOOST_CHECK_EQUAL(feeRate.GetFee(1e3), 123); - BOOST_CHECK_EQUAL(feeRate.GetFee(9e3), 1107); + BOOST_CHECK_EQUAL(feeRate.GetFee(0), CAmount(0)); + BOOST_CHECK_EQUAL(feeRate.GetFee(8), CAmount(1)); // Special case: returns 1 instead of 0 + BOOST_CHECK_EQUAL(feeRate.GetFee(9), CAmount(1)); + BOOST_CHECK_EQUAL(feeRate.GetFee(121), CAmount(14)); + BOOST_CHECK_EQUAL(feeRate.GetFee(122), CAmount(15)); + BOOST_CHECK_EQUAL(feeRate.GetFee(999), CAmount(122)); + BOOST_CHECK_EQUAL(feeRate.GetFee(1e3), CAmount(123)); + BOOST_CHECK_EQUAL(feeRate.GetFee(9e3), CAmount(1107)); feeRate = CFeeRate(-123); // Truncates the result, if not integer - BOOST_CHECK_EQUAL(feeRate.GetFee(0), 0); - BOOST_CHECK_EQUAL(feeRate.GetFee(8), -1); // Special case: returns -1 instead of 0 - BOOST_CHECK_EQUAL(feeRate.GetFee(9), -1); + BOOST_CHECK_EQUAL(feeRate.GetFee(0), CAmount(0)); + BOOST_CHECK_EQUAL(feeRate.GetFee(8), CAmount(-1)); // Special case: returns -1 instead of 0 + BOOST_CHECK_EQUAL(feeRate.GetFee(9), CAmount(-1)); // check alternate constructor feeRate = CFeeRate(1000); @@ -67,6 +69,9 @@ BOOST_AUTO_TEST_CASE(GetFeeTest) BOOST_CHECK_EQUAL(feeRate.GetFee(100), altFeeRate.GetFee(100)); // Check full constructor + BOOST_CHECK(CFeeRate(CAmount(-1), 0) == CFeeRate(0)); + BOOST_CHECK(CFeeRate(CAmount(0), 0) == CFeeRate(0)); + BOOST_CHECK(CFeeRate(CAmount(1), 0) == CFeeRate(0)); // default value BOOST_CHECK(CFeeRate(CAmount(-1), 1000) == CFeeRate(-1)); BOOST_CHECK(CFeeRate(CAmount(0), 1000) == CFeeRate(0)); diff --git a/src/test/base32_tests.cpp b/src/test/base32_tests.cpp index 6b06d2e1b6..32af843bf6 100644 --- a/src/test/base32_tests.cpp +++ b/src/test/base32_tests.cpp @@ -2,7 +2,7 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#include <utilstrencodings.h> +#include <util/strencodings.h> #include <test/test_bitcoin.h> #include <boost/test/unit_test.hpp> diff --git a/src/test/base58_tests.cpp b/src/test/base58_tests.cpp index 5fc4abaf3d..f8f9b3c1a7 100644 --- a/src/test/base58_tests.cpp +++ b/src/test/base58_tests.cpp @@ -6,7 +6,7 @@ #include <base58.h> #include <test/test_bitcoin.h> -#include <utilstrencodings.h> +#include <util/strencodings.h> #include <univalue.h> diff --git a/src/test/base64_tests.cpp b/src/test/base64_tests.cpp index daceea262c..0abbb682a7 100644 --- a/src/test/base64_tests.cpp +++ b/src/test/base64_tests.cpp @@ -2,7 +2,7 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#include <utilstrencodings.h> +#include <util/strencodings.h> #include <test/test_bitcoin.h> #include <boost/test/unit_test.hpp> diff --git a/src/test/bip32_tests.cpp b/src/test/bip32_tests.cpp index 2cdbaca7ba..c9951f4b7e 100644 --- a/src/test/bip32_tests.cpp +++ b/src/test/bip32_tests.cpp @@ -7,8 +7,8 @@ #include <key.h> #include <key_io.h> #include <uint256.h> -#include <util.h> -#include <utilstrencodings.h> +#include <util/system.h> +#include <util/strencodings.h> #include <test/test_bitcoin.h> #include <string> diff --git a/src/test/blockchain_tests.cpp b/src/test/blockchain_tests.cpp index 7d8ae46fb8..b61152985f 100644 --- a/src/test/blockchain_tests.cpp +++ b/src/test/blockchain_tests.cpp @@ -68,11 +68,4 @@ BOOST_AUTO_TEST_CASE(get_difficulty_for_very_high_target) TestDifficulty(0x12345678, 5913134931067755359633408.0); } -// Verify that difficulty is 1.0 for an empty chain. -BOOST_AUTO_TEST_CASE(get_difficulty_for_null_tip) -{ - double difficulty = GetDifficulty(nullptr); - RejectDifficultyMismatch(difficulty, 1.0); -} - BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/blockencodings_tests.cpp b/src/test/blockencodings_tests.cpp index 5131fe8235..607af8a32a 100644 --- a/src/test/blockencodings_tests.cpp +++ b/src/test/blockencodings_tests.cpp @@ -62,7 +62,7 @@ BOOST_AUTO_TEST_CASE(SimpleRoundTripTest) TestMemPoolEntryHelper entry; CBlock block(BuildBlockTestCase()); - LOCK(pool.cs); + LOCK2(cs_main, pool.cs); pool.addUnchecked(entry.FromTx(block.vtx[2])); BOOST_CHECK_EQUAL(pool.mapTx.find(block.vtx[2]->GetHash())->GetSharedTx().use_count(), SHARED_TX_OFFSET + 0); @@ -162,7 +162,7 @@ BOOST_AUTO_TEST_CASE(NonCoinbasePreforwardRTTest) TestMemPoolEntryHelper entry; CBlock block(BuildBlockTestCase()); - LOCK(pool.cs); + LOCK2(cs_main, pool.cs); pool.addUnchecked(entry.FromTx(block.vtx[2])); BOOST_CHECK_EQUAL(pool.mapTx.find(block.vtx[2]->GetHash())->GetSharedTx().use_count(), SHARED_TX_OFFSET + 0); @@ -232,7 +232,7 @@ BOOST_AUTO_TEST_CASE(SufficientPreforwardRTTest) TestMemPoolEntryHelper entry; CBlock block(BuildBlockTestCase()); - LOCK(pool.cs); + LOCK2(cs_main, pool.cs); pool.addUnchecked(entry.FromTx(block.vtx[1])); BOOST_CHECK_EQUAL(pool.mapTx.find(block.vtx[1]->GetHash())->GetSharedTx().use_count(), SHARED_TX_OFFSET + 0); @@ -344,4 +344,49 @@ BOOST_AUTO_TEST_CASE(TransactionsRequestSerializationTest) { BOOST_CHECK_EQUAL(req1.indexes[3], req2.indexes[3]); } +BOOST_AUTO_TEST_CASE(TransactionsRequestDeserializationMaxTest) { + // Check that the highest legal index is decoded correctly + BlockTransactionsRequest req0; + req0.blockhash = InsecureRand256(); + req0.indexes.resize(1); + req0.indexes[0] = 0xffff; + CDataStream stream(SER_NETWORK, PROTOCOL_VERSION); + stream << req0; + + BlockTransactionsRequest req1; + stream >> req1; + BOOST_CHECK_EQUAL(req0.indexes.size(), req1.indexes.size()); + BOOST_CHECK_EQUAL(req0.indexes[0], req1.indexes[0]); +} + +BOOST_AUTO_TEST_CASE(TransactionsRequestDeserializationOverflowTest) { + // Any set of index deltas that starts with N values that sum to (0x10000 - N) + // causes the edge-case overflow that was originally not checked for. Such + // a request cannot be created by serializing a real BlockTransactionsRequest + // due to the overflow, so here we'll serialize from raw deltas. + BlockTransactionsRequest req0; + req0.blockhash = InsecureRand256(); + req0.indexes.resize(3); + req0.indexes[0] = 0x7000; + req0.indexes[1] = 0x10000 - 0x7000 - 2; + req0.indexes[2] = 0; + CDataStream stream(SER_NETWORK, PROTOCOL_VERSION); + stream << req0.blockhash; + WriteCompactSize(stream, req0.indexes.size()); + WriteCompactSize(stream, req0.indexes[0]); + WriteCompactSize(stream, req0.indexes[1]); + WriteCompactSize(stream, req0.indexes[2]); + + BlockTransactionsRequest req1; + try { + stream >> req1; + // before patch: deserialize above succeeds and this check fails, demonstrating the overflow + BOOST_CHECK(req1.indexes[1] < req1.indexes[2]); + // this shouldn't be reachable before or after patch + BOOST_CHECK(0); + } catch(std::ios_base::failure &) { + // deserialize should fail + } +} + BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/blockfilter_tests.cpp b/src/test/blockfilter_tests.cpp index 4941ebd483..625362f446 100644 --- a/src/test/blockfilter_tests.cpp +++ b/src/test/blockfilter_tests.cpp @@ -10,7 +10,7 @@ #include <serialize.h> #include <streams.h> #include <univalue.h> -#include <utilstrencodings.h> +#include <util/strencodings.h> #include <boost/test/unit_test.hpp> @@ -29,7 +29,7 @@ BOOST_AUTO_TEST_CASE(gcsfilter_test) excluded_elements.insert(std::move(element2)); } - GCSFilter filter(0, 0, 10, 1 << 10, included_elements); + GCSFilter filter({0, 0, 10, 1 << 10}, included_elements); for (const auto& element : included_elements) { BOOST_CHECK(filter.Match(element)); @@ -39,6 +39,19 @@ BOOST_AUTO_TEST_CASE(gcsfilter_test) } } +BOOST_AUTO_TEST_CASE(gcsfilter_default_constructor) +{ + GCSFilter filter; + BOOST_CHECK_EQUAL(filter.GetN(), 0); + BOOST_CHECK_EQUAL(filter.GetEncoded().size(), 1); + + const GCSFilter::Params& params = filter.GetParams(); + BOOST_CHECK_EQUAL(params.m_siphash_k0, 0); + BOOST_CHECK_EQUAL(params.m_siphash_k1, 0); + BOOST_CHECK_EQUAL(params.m_P, 0); + BOOST_CHECK_EQUAL(params.m_M, 1); +} + BOOST_AUTO_TEST_CASE(blockfilter_basic_test) { CScript included_scripts[5], excluded_scripts[3]; @@ -88,6 +101,17 @@ BOOST_AUTO_TEST_CASE(blockfilter_basic_test) for (const CScript& script : excluded_scripts) { BOOST_CHECK(!filter.Match(GCSFilter::Element(script.begin(), script.end()))); } + + // Test serialization/unserialization. + BlockFilter block_filter2; + + CDataStream stream(SER_NETWORK, PROTOCOL_VERSION); + stream << block_filter; + stream >> block_filter2; + + BOOST_CHECK_EQUAL(block_filter.GetFilterType(), block_filter2.GetFilterType()); + BOOST_CHECK_EQUAL(block_filter.GetBlockHash(), block_filter2.GetBlockHash()); + BOOST_CHECK(block_filter.GetEncodedFilter() == block_filter2.GetEncodedFilter()); } BOOST_AUTO_TEST_CASE(blockfilters_json_test) diff --git a/src/test/bloom_tests.cpp b/src/test/bloom_tests.cpp index 8a8e8bfdc3..c900bee199 100644 --- a/src/test/bloom_tests.cpp +++ b/src/test/bloom_tests.cpp @@ -13,8 +13,8 @@ #include <serialize.h> #include <streams.h> #include <uint256.h> -#include <util.h> -#include <utilstrencodings.h> +#include <util/system.h> +#include <util/strencodings.h> #include <test/test_bitcoin.h> #include <vector> diff --git a/src/test/checkqueue_tests.cpp b/src/test/checkqueue_tests.cpp index f4b416c4ca..3469c6dfba 100644 --- a/src/test/checkqueue_tests.cpp +++ b/src/test/checkqueue_tests.cpp @@ -2,8 +2,8 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#include <util.h> -#include <utiltime.h> +#include <util/system.h> +#include <util/time.h> #include <validation.h> #include <test/test_bitcoin.h> @@ -355,7 +355,8 @@ BOOST_AUTO_TEST_CASE(test_CheckQueue_FrozenCleanup) // would get called twice). vChecks[0].should_freeze = true; control.Add(vChecks); - control.Wait(); // Hangs here + bool waitResult = control.Wait(); // Hangs here + assert(waitResult); }); { std::unique_lock<std::mutex> l(FrozenCleanupCheck::m); diff --git a/src/test/coins_tests.cpp b/src/test/coins_tests.cpp index 6f4b5ecd26..aa2e88477d 100644 --- a/src/test/coins_tests.cpp +++ b/src/test/coins_tests.cpp @@ -2,17 +2,18 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. +#include <attributes.h> #include <coins.h> +#include <consensus/validation.h> #include <script/standard.h> +#include <test/test_bitcoin.h> #include <uint256.h> #include <undo.h> -#include <utilstrencodings.h> -#include <test/test_bitcoin.h> +#include <util/strencodings.h> #include <validation.h> -#include <consensus/validation.h> -#include <vector> #include <map> +#include <vector> #include <boost/test/unit_test.hpp> @@ -36,7 +37,7 @@ class CCoinsViewTest : public CCoinsView std::map<COutPoint, Coin> map_; public: - bool GetCoin(const COutPoint& outpoint, Coin& coin) const override + NODISCARD bool GetCoin(const COutPoint& outpoint, Coin& coin) const override { std::map<COutPoint, Coin>::const_iterator it = map_.find(outpoint); if (it == map_.end()) { @@ -175,7 +176,7 @@ BOOST_AUTO_TEST_CASE(coins_cache_simulation_test) } else { removed_an_entry = true; coin.Clear(); - stack.back()->SpendCoin(COutPoint(txid, 0)); + BOOST_CHECK(stack.back()->SpendCoin(COutPoint(txid, 0))); } } @@ -210,14 +211,14 @@ BOOST_AUTO_TEST_CASE(coins_cache_simulation_test) // Every 100 iterations, flush an intermediate cache if (stack.size() > 1 && InsecureRandBool() == 0) { unsigned int flushIndex = InsecureRandRange(stack.size() - 1); - stack[flushIndex]->Flush(); + BOOST_CHECK(stack[flushIndex]->Flush()); } } if (InsecureRandRange(100) == 0) { // Every 100 iterations, change the cache stack. if (stack.size() > 0 && InsecureRandBool() == 0) { //Remove the top cache - stack.back()->Flush(); + BOOST_CHECK(stack.back()->Flush()); delete stack.back(); stack.pop_back(); } @@ -376,7 +377,7 @@ BOOST_AUTO_TEST_CASE(updatecoins_simulation_test) // Call UpdateCoins on the top cache CTxUndo undo; - UpdateCoins(tx, *(stack.back()), undo, height); + UpdateCoins(CTransaction(tx), *(stack.back()), undo, height); // Update the utxo set for future spends utxoset.insert(outpoint); @@ -402,7 +403,7 @@ BOOST_AUTO_TEST_CASE(updatecoins_simulation_test) // Disconnect the tx from the current UTXO // See code in DisconnectBlock // remove outputs - stack.back()->SpendCoin(utxod->first); + BOOST_CHECK(stack.back()->SpendCoin(utxod->first)); // restore inputs if (!tx.IsCoinBase()) { const COutPoint &out = tx.vin[0].prevout; @@ -443,13 +444,13 @@ BOOST_AUTO_TEST_CASE(updatecoins_simulation_test) // Every 100 iterations, flush an intermediate cache if (stack.size() > 1 && InsecureRandBool() == 0) { unsigned int flushIndex = InsecureRandRange(stack.size() - 1); - stack[flushIndex]->Flush(); + BOOST_CHECK(stack[flushIndex]->Flush()); } } if (InsecureRandRange(100) == 0) { // Every 100 iterations, change the cache stack. if (stack.size() > 0 && InsecureRandBool() == 0) { - stack.back()->Flush(); + BOOST_CHECK(stack.back()->Flush()); delete stack.back(); stack.pop_back(); } @@ -588,7 +589,7 @@ void WriteCoinsViewEntry(CCoinsView& view, CAmount value, char flags) { CCoinsMap map; InsertCoinsMapEntry(map, value, flags); - view.BatchWrite(map, {}); + BOOST_CHECK(view.BatchWrite(map, {})); } class SingleEntryCacheTest diff --git a/src/test/compress_tests.cpp b/src/test/compress_tests.cpp index a4e99d438f..e686c05165 100644 --- a/src/test/compress_tests.cpp +++ b/src/test/compress_tests.cpp @@ -3,7 +3,7 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include <compressor.h> -#include <util.h> +#include <util/system.h> #include <test/test_bitcoin.h> #include <stdint.h> diff --git a/src/test/crypto_tests.cpp b/src/test/crypto_tests.cpp index 713e3e2ded..86cb00a78f 100644 --- a/src/test/crypto_tests.cpp +++ b/src/test/crypto_tests.cpp @@ -11,7 +11,7 @@ #include <crypto/hmac_sha256.h> #include <crypto/hmac_sha512.h> #include <random.h> -#include <utilstrencodings.h> +#include <util/strencodings.h> #include <test/test_bitcoin.h> #include <vector> @@ -532,7 +532,7 @@ BOOST_AUTO_TEST_CASE(countbits_tests) // Check handling of zero. BOOST_CHECK_EQUAL(CountBits(0), 0U); } else if (i < 10) { - for (uint64_t j = 1 << (i - 1); (j >> i) == 0; ++j) { + for (uint64_t j = (uint64_t)1 << (i - 1); (j >> i) == 0; ++j) { // Exhaustively test up to 10 bits BOOST_CHECK_EQUAL(CountBits(j), i); } diff --git a/src/test/cuckoocache_tests.cpp b/src/test/cuckoocache_tests.cpp index dbceb9d2e0..d8286520ec 100644 --- a/src/test/cuckoocache_tests.cpp +++ b/src/test/cuckoocache_tests.cpp @@ -21,40 +21,23 @@ * using BOOST_CHECK_CLOSE to fail. * */ -FastRandomContext local_rand_ctx(true); - BOOST_AUTO_TEST_SUITE(cuckoocache_tests); - -/** insecure_GetRandHash fills in a uint256 from local_rand_ctx - */ -static void insecure_GetRandHash(uint256& t) -{ - uint32_t* ptr = (uint32_t*)t.begin(); - for (uint8_t j = 0; j < 8; ++j) - *(ptr++) = local_rand_ctx.rand32(); -} - - - /* Test that no values not inserted into the cache are read out of it. * * There are no repeats in the first 200000 insecure_GetRandHash calls */ BOOST_AUTO_TEST_CASE(test_cuckoocache_no_fakes) { - local_rand_ctx = FastRandomContext(true); + SeedInsecureRand(true); CuckooCache::cache<uint256, SignatureCacheHasher> cc{}; size_t megabytes = 4; cc.setup_bytes(megabytes << 20); - uint256 v; for (int x = 0; x < 100000; ++x) { - insecure_GetRandHash(v); - cc.insert(v); + cc.insert(InsecureRand256()); } for (int x = 0; x < 100000; ++x) { - insecure_GetRandHash(v); - BOOST_CHECK(!cc.contains(v, false)); + BOOST_CHECK(!cc.contains(InsecureRand256(), false)); } }; @@ -64,7 +47,7 @@ BOOST_AUTO_TEST_CASE(test_cuckoocache_no_fakes) template <typename Cache> static double test_cache(size_t megabytes, double load) { - local_rand_ctx = FastRandomContext(true); + SeedInsecureRand(true); std::vector<uint256> hashes; Cache set{}; size_t bytes = megabytes * (1 << 20); @@ -74,7 +57,7 @@ static double test_cache(size_t megabytes, double load) for (uint32_t i = 0; i < n_insert; ++i) { uint32_t* ptr = (uint32_t*)hashes[i].begin(); for (uint8_t j = 0; j < 8; ++j) - *(ptr++) = local_rand_ctx.rand32(); + *(ptr++) = InsecureRand32(); } /** We make a copy of the hashes because future optimizations of the * cuckoocache may overwrite the inserted element, so the test is @@ -135,7 +118,7 @@ template <typename Cache> static void test_cache_erase(size_t megabytes) { double load = 1; - local_rand_ctx = FastRandomContext(true); + SeedInsecureRand(true); std::vector<uint256> hashes; Cache set{}; size_t bytes = megabytes * (1 << 20); @@ -145,7 +128,7 @@ static void test_cache_erase(size_t megabytes) for (uint32_t i = 0; i < n_insert; ++i) { uint32_t* ptr = (uint32_t*)hashes[i].begin(); for (uint8_t j = 0; j < 8; ++j) - *(ptr++) = local_rand_ctx.rand32(); + *(ptr++) = InsecureRand32(); } /** We make a copy of the hashes because future optimizations of the * cuckoocache may overwrite the inserted element, so the test is @@ -158,7 +141,7 @@ static void test_cache_erase(size_t megabytes) set.insert(hashes_insert_copy[i]); /** Erase the first quarter */ for (uint32_t i = 0; i < (n_insert / 4); ++i) - set.contains(hashes[i], true); + BOOST_CHECK(set.contains(hashes[i], true)); /** Insert the second half */ for (uint32_t i = (n_insert / 2); i < n_insert; ++i) set.insert(hashes_insert_copy[i]); @@ -198,7 +181,7 @@ template <typename Cache> static void test_cache_erase_parallel(size_t megabytes) { double load = 1; - local_rand_ctx = FastRandomContext(true); + SeedInsecureRand(true); std::vector<uint256> hashes; Cache set{}; size_t bytes = megabytes * (1 << 20); @@ -208,7 +191,7 @@ static void test_cache_erase_parallel(size_t megabytes) for (uint32_t i = 0; i < n_insert; ++i) { uint32_t* ptr = (uint32_t*)hashes[i].begin(); for (uint8_t j = 0; j < 8; ++j) - *(ptr++) = local_rand_ctx.rand32(); + *(ptr++) = InsecureRand32(); } /** We make a copy of the hashes because future optimizations of the * cuckoocache may overwrite the inserted element, so the test is @@ -237,8 +220,10 @@ static void test_cache_erase_parallel(size_t megabytes) size_t ntodo = (n_insert/4)/3; size_t start = ntodo*x; size_t end = ntodo*(x+1); - for (uint32_t i = start; i < end; ++i) - set.contains(hashes[i], true); + for (uint32_t i = start; i < end; ++i) { + bool contains = set.contains(hashes[i], true); + assert(contains); + } }); /** Wait for all threads to finish @@ -300,7 +285,7 @@ static void test_cache_generations() // iterations with non-deterministic values, so it isn't "overfit" to the // specific entropy in FastRandomContext(true) and implementation of the // cache. - local_rand_ctx = FastRandomContext(true); + SeedInsecureRand(true); // block_activity models a chunk of network activity. n_insert elements are // added to the cache. The first and last n/4 are stored for removal later @@ -317,7 +302,7 @@ static void test_cache_generations() for (uint32_t i = 0; i < n_insert; ++i) { uint32_t* ptr = (uint32_t*)inserts[i].begin(); for (uint8_t j = 0; j < 8; ++j) - *(ptr++) = local_rand_ctx.rand32(); + *(ptr++) = InsecureRand32(); } for (uint32_t i = 0; i < n_insert / 4; ++i) reads.push_back(inserts[i]); diff --git a/src/test/dbwrapper_tests.cpp b/src/test/dbwrapper_tests.cpp index 9957ac074b..94e8c95345 100644 --- a/src/test/dbwrapper_tests.cpp +++ b/src/test/dbwrapper_tests.cpp @@ -67,7 +67,7 @@ BOOST_AUTO_TEST_CASE(dbwrapper_batch) // Remove key3 before it's even been written batch.Erase(key3); - dbw.WriteBatch(batch); + BOOST_CHECK(dbw.WriteBatch(batch)); BOOST_CHECK(dbw.Read(key, res)); BOOST_CHECK_EQUAL(res.ToString(), in.ToString()); @@ -102,15 +102,15 @@ BOOST_AUTO_TEST_CASE(dbwrapper_iterator) char key_res; uint256 val_res; - it->GetKey(key_res); - it->GetValue(val_res); + BOOST_REQUIRE(it->GetKey(key_res)); + BOOST_REQUIRE(it->GetValue(val_res)); BOOST_CHECK_EQUAL(key_res, key); BOOST_CHECK_EQUAL(val_res.ToString(), in.ToString()); it->Next(); - it->GetKey(key_res); - it->GetValue(val_res); + BOOST_REQUIRE(it->GetKey(key_res)); + BOOST_REQUIRE(it->GetValue(val_res)); BOOST_CHECK_EQUAL(key_res, key2); BOOST_CHECK_EQUAL(val_res.ToString(), in2.ToString()); diff --git a/src/test/denialofservice_tests.cpp b/src/test/denialofservice_tests.cpp index 52bbe96b96..e5d62a3ab2 100644 --- a/src/test/denialofservice_tests.cpp +++ b/src/test/denialofservice_tests.cpp @@ -4,6 +4,7 @@ // Unit tests for denial-of-service detection/prevention code +#include <banman.h> #include <chainparams.h> #include <keystore.h> #include <net.h> @@ -11,7 +12,7 @@ #include <pow.h> #include <script/sign.h> #include <serialize.h> -#include <util.h> +#include <util/system.h> #include <validation.h> #include <test/test_bitcoin.h> @@ -20,6 +21,23 @@ #include <boost/test/unit_test.hpp> +struct CConnmanTest : public CConnman { + using CConnman::CConnman; + void AddNode(CNode& node) + { + LOCK(cs_vNodes); + vNodes.push_back(&node); + } + void ClearNodes() + { + LOCK(cs_vNodes); + for (CNode* node : vNodes) { + delete node; + } + vNodes.clear(); + } +}; + // Tests these internal-to-net_processing.cpp methods: extern bool AddOrphanTx(const CTransactionRef& tx, NodeId peer); extern void EraseOrphansFor(NodeId peer); @@ -57,6 +75,8 @@ BOOST_FIXTURE_TEST_SUITE(denialofservice_tests, TestingSetup) // work. BOOST_AUTO_TEST_CASE(outbound_slow_chain_eviction) { + auto connman = MakeUnique<CConnman>(0x1337, 0x1337); + auto peerLogic = MakeUnique<PeerLogicValidation>(connman.get(), nullptr, scheduler, false); // Mock an outbound peer CAddress addr1(ip(0xa0b0c001), NODE_NONE); @@ -77,7 +97,7 @@ BOOST_AUTO_TEST_CASE(outbound_slow_chain_eviction) // Test starts here { LOCK2(cs_main, dummyNode1.cs_sendProcessing); - peerLogic->SendMessages(&dummyNode1); // should result in getheaders + BOOST_CHECK(peerLogic->SendMessages(&dummyNode1)); // should result in getheaders } { LOCK2(cs_main, dummyNode1.cs_vSend); @@ -90,7 +110,7 @@ BOOST_AUTO_TEST_CASE(outbound_slow_chain_eviction) SetMockTime(nStartTime+21*60); { LOCK2(cs_main, dummyNode1.cs_sendProcessing); - peerLogic->SendMessages(&dummyNode1); // should result in getheaders + BOOST_CHECK(peerLogic->SendMessages(&dummyNode1)); // should result in getheaders } { LOCK2(cs_main, dummyNode1.cs_vSend); @@ -100,7 +120,7 @@ BOOST_AUTO_TEST_CASE(outbound_slow_chain_eviction) SetMockTime(nStartTime+24*60); { LOCK2(cs_main, dummyNode1.cs_sendProcessing); - peerLogic->SendMessages(&dummyNode1); // should result in disconnect + BOOST_CHECK(peerLogic->SendMessages(&dummyNode1)); // should result in disconnect } BOOST_CHECK(dummyNode1.fDisconnect == true); SetMockTime(0); @@ -109,9 +129,9 @@ BOOST_AUTO_TEST_CASE(outbound_slow_chain_eviction) peerLogic->FinalizeNode(dummyNode1.GetId(), dummy); } -static void AddRandomOutboundPeer(std::vector<CNode *> &vNodes, PeerLogicValidation &peerLogic) +static void AddRandomOutboundPeer(std::vector<CNode *> &vNodes, PeerLogicValidation &peerLogic, CConnmanTest* connman) { - CAddress addr(ip(GetRandInt(0xffffffff)), NODE_NONE); + CAddress addr(ip(g_insecure_rand_ctx.randbits(32)), NODE_NONE); vNodes.emplace_back(new CNode(id++, ServiceFlags(NODE_NETWORK|NODE_WITNESS), 0, INVALID_SOCKET, addr, 0, 0, CAddress(), "", /*fInboundIn=*/ false)); CNode &node = *vNodes.back(); node.SetSendVersion(PROTOCOL_VERSION); @@ -120,11 +140,14 @@ static void AddRandomOutboundPeer(std::vector<CNode *> &vNodes, PeerLogicValidat node.nVersion = 1; node.fSuccessfullyConnected = true; - CConnmanTest::AddNode(node); + connman->AddNode(node); } BOOST_AUTO_TEST_CASE(stale_tip_peer_management) { + auto connman = MakeUnique<CConnmanTest>(0x1337, 0x1337); + auto peerLogic = MakeUnique<PeerLogicValidation>(connman.get(), nullptr, scheduler, false); + const Consensus::Params& consensusParams = Params().GetConsensus(); constexpr int nMaxOutbound = 8; CConnman::Options options; @@ -137,7 +160,7 @@ BOOST_AUTO_TEST_CASE(stale_tip_peer_management) // Mock some outbound peers for (int i=0; i<nMaxOutbound; ++i) { - AddRandomOutboundPeer(vNodes, *peerLogic); + AddRandomOutboundPeer(vNodes, *peerLogic, connman.get()); } peerLogic->CheckForStaleTipAndEvictPeers(consensusParams); @@ -162,7 +185,7 @@ BOOST_AUTO_TEST_CASE(stale_tip_peer_management) // If we add one more peer, something should get marked for eviction // on the next check (since we're mocking the time to be in the future, the // required time connected check should be satisfied). - AddRandomOutboundPeer(vNodes, *peerLogic); + AddRandomOutboundPeer(vNodes, *peerLogic, connman.get()); peerLogic->CheckForStaleTipAndEvictPeers(consensusParams); for (int i=0; i<nMaxOutbound; ++i) { @@ -189,13 +212,16 @@ BOOST_AUTO_TEST_CASE(stale_tip_peer_management) peerLogic->FinalizeNode(node->GetId(), dummy); } - CConnmanTest::ClearNodes(); + connman->ClearNodes(); } BOOST_AUTO_TEST_CASE(DoS_banning) { + auto banman = MakeUnique<BanMan>(GetDataDir() / "banlist.dat", nullptr, DEFAULT_MISBEHAVING_BANTIME); + auto connman = MakeUnique<CConnman>(0x1337, 0x1337); + auto peerLogic = MakeUnique<PeerLogicValidation>(connman.get(), banman.get(), scheduler, false); - connman->ClearBanned(); + banman->ClearBanned(); CAddress addr1(ip(0xa0b0c001), NODE_NONE); CNode dummyNode1(id++, NODE_NETWORK, 0, INVALID_SOCKET, addr1, 0, 0, CAddress(), "", true); dummyNode1.SetSendVersion(PROTOCOL_VERSION); @@ -208,10 +234,10 @@ BOOST_AUTO_TEST_CASE(DoS_banning) } { LOCK2(cs_main, dummyNode1.cs_sendProcessing); - peerLogic->SendMessages(&dummyNode1); + BOOST_CHECK(peerLogic->SendMessages(&dummyNode1)); } - BOOST_CHECK(connman->IsBanned(addr1)); - BOOST_CHECK(!connman->IsBanned(ip(0xa0b0c001|0x0000ff00))); // Different IP, not banned + BOOST_CHECK(banman->IsBanned(addr1)); + BOOST_CHECK(!banman->IsBanned(ip(0xa0b0c001|0x0000ff00))); // Different IP, not banned CAddress addr2(ip(0xa0b0c002), NODE_NONE); CNode dummyNode2(id++, NODE_NETWORK, 0, INVALID_SOCKET, addr2, 1, 1, CAddress(), "", true); @@ -225,19 +251,19 @@ BOOST_AUTO_TEST_CASE(DoS_banning) } { LOCK2(cs_main, dummyNode2.cs_sendProcessing); - peerLogic->SendMessages(&dummyNode2); + BOOST_CHECK(peerLogic->SendMessages(&dummyNode2)); } - BOOST_CHECK(!connman->IsBanned(addr2)); // 2 not banned yet... - BOOST_CHECK(connman->IsBanned(addr1)); // ... but 1 still should be + BOOST_CHECK(!banman->IsBanned(addr2)); // 2 not banned yet... + BOOST_CHECK(banman->IsBanned(addr1)); // ... but 1 still should be { LOCK(cs_main); Misbehaving(dummyNode2.GetId(), 50); } { LOCK2(cs_main, dummyNode2.cs_sendProcessing); - peerLogic->SendMessages(&dummyNode2); + BOOST_CHECK(peerLogic->SendMessages(&dummyNode2)); } - BOOST_CHECK(connman->IsBanned(addr2)); + BOOST_CHECK(banman->IsBanned(addr2)); bool dummy; peerLogic->FinalizeNode(dummyNode1.GetId(), dummy); @@ -246,8 +272,11 @@ BOOST_AUTO_TEST_CASE(DoS_banning) BOOST_AUTO_TEST_CASE(DoS_banscore) { + auto banman = MakeUnique<BanMan>(GetDataDir() / "banlist.dat", nullptr, DEFAULT_MISBEHAVING_BANTIME); + auto connman = MakeUnique<CConnman>(0x1337, 0x1337); + auto peerLogic = MakeUnique<PeerLogicValidation>(connman.get(), banman.get(), scheduler, false); - connman->ClearBanned(); + banman->ClearBanned(); gArgs.ForceSetArg("-banscore", "111"); // because 11 is my favorite number CAddress addr1(ip(0xa0b0c001), NODE_NONE); CNode dummyNode1(id++, NODE_NETWORK, 0, INVALID_SOCKET, addr1, 3, 1, CAddress(), "", true); @@ -261,27 +290,27 @@ BOOST_AUTO_TEST_CASE(DoS_banscore) } { LOCK2(cs_main, dummyNode1.cs_sendProcessing); - peerLogic->SendMessages(&dummyNode1); + BOOST_CHECK(peerLogic->SendMessages(&dummyNode1)); } - BOOST_CHECK(!connman->IsBanned(addr1)); + BOOST_CHECK(!banman->IsBanned(addr1)); { LOCK(cs_main); Misbehaving(dummyNode1.GetId(), 10); } { LOCK2(cs_main, dummyNode1.cs_sendProcessing); - peerLogic->SendMessages(&dummyNode1); + BOOST_CHECK(peerLogic->SendMessages(&dummyNode1)); } - BOOST_CHECK(!connman->IsBanned(addr1)); + BOOST_CHECK(!banman->IsBanned(addr1)); { LOCK(cs_main); Misbehaving(dummyNode1.GetId(), 1); } { LOCK2(cs_main, dummyNode1.cs_sendProcessing); - peerLogic->SendMessages(&dummyNode1); + BOOST_CHECK(peerLogic->SendMessages(&dummyNode1)); } - BOOST_CHECK(connman->IsBanned(addr1)); + BOOST_CHECK(banman->IsBanned(addr1)); gArgs.ForceSetArg("-banscore", std::to_string(DEFAULT_BANSCORE_THRESHOLD)); bool dummy; @@ -290,8 +319,11 @@ BOOST_AUTO_TEST_CASE(DoS_banscore) BOOST_AUTO_TEST_CASE(DoS_bantime) { + auto banman = MakeUnique<BanMan>(GetDataDir() / "banlist.dat", nullptr, DEFAULT_MISBEHAVING_BANTIME); + auto connman = MakeUnique<CConnman>(0x1337, 0x1337); + auto peerLogic = MakeUnique<PeerLogicValidation>(connman.get(), banman.get(), scheduler, false); - connman->ClearBanned(); + banman->ClearBanned(); int64_t nStartTime = GetTime(); SetMockTime(nStartTime); // Overrides future calls to GetTime() @@ -308,15 +340,15 @@ BOOST_AUTO_TEST_CASE(DoS_bantime) } { LOCK2(cs_main, dummyNode.cs_sendProcessing); - peerLogic->SendMessages(&dummyNode); + BOOST_CHECK(peerLogic->SendMessages(&dummyNode)); } - BOOST_CHECK(connman->IsBanned(addr)); + BOOST_CHECK(banman->IsBanned(addr)); SetMockTime(nStartTime+60*60); - BOOST_CHECK(connman->IsBanned(addr)); + BOOST_CHECK(banman->IsBanned(addr)); SetMockTime(nStartTime+60*60*24+1); - BOOST_CHECK(!connman->IsBanned(addr)); + BOOST_CHECK(!banman->IsBanned(addr)); bool dummy; peerLogic->FinalizeNode(dummyNode.GetId(), dummy); @@ -337,7 +369,7 @@ BOOST_AUTO_TEST_CASE(DoS_mapOrphans) CKey key; key.MakeNewKey(true); CBasicKeyStore keystore; - keystore.AddKey(key); + BOOST_CHECK(keystore.AddKey(key)); // 50 orphan transactions: for (int i = 0; i < 50; i++) @@ -366,7 +398,7 @@ BOOST_AUTO_TEST_CASE(DoS_mapOrphans) tx.vout.resize(1); tx.vout[0].nValue = 1*CENT; tx.vout[0].scriptPubKey = GetScriptForDestination(key.GetPubKey().GetID()); - SignSignature(keystore, *txPrev, tx, 0, SIGHASH_ALL); + BOOST_CHECK(SignSignature(keystore, *txPrev, tx, 0, SIGHASH_ALL)); AddOrphanTx(MakeTransactionRef(tx), i); } @@ -386,7 +418,7 @@ BOOST_AUTO_TEST_CASE(DoS_mapOrphans) tx.vin[j].prevout.n = j; tx.vin[j].prevout.hash = txPrev->GetHash(); } - SignSignature(keystore, *txPrev, tx, 0, SIGHASH_ALL); + BOOST_CHECK(SignSignature(keystore, *txPrev, tx, 0, SIGHASH_ALL)); // Re-use same signature for other inputs // (they don't have to be valid for this test) for (unsigned int j = 1; j < tx.vin.size(); j++) diff --git a/src/test/descriptor_tests.cpp b/src/test/descriptor_tests.cpp index e739b84a48..8da8cfc00c 100644 --- a/src/test/descriptor_tests.cpp +++ b/src/test/descriptor_tests.cpp @@ -9,7 +9,7 @@ #include <test/test_bitcoin.h> #include <boost/test/unit_test.hpp> #include <script/descriptor.h> -#include <utilstrencodings.h> +#include <util/strencodings.h> namespace { @@ -43,9 +43,12 @@ std::string MaybeUseHInsteadOfApostrophy(std::string ret) return ret; } -void Check(const std::string& prv, const std::string& pub, int flags, const std::vector<std::vector<std::string>>& scripts) +const std::set<std::vector<uint32_t>> ONLY_EMPTY{{}}; + +void Check(const std::string& prv, const std::string& pub, int flags, const std::vector<std::vector<std::string>>& scripts, const std::set<std::vector<uint32_t>>& paths = ONLY_EMPTY) { FlatSigningProvider keys_priv, keys_pub; + std::set<std::vector<uint32_t>> left_paths = paths; // Check that parsing succeeds. auto parse_priv = Parse(MaybeUseHInsteadOfApostrophy(prv), keys_priv); @@ -59,7 +62,7 @@ void Check(const std::string& prv, const std::string& pub, int flags, const std: // Check that both versions serialize back to the public version. std::string pub1 = parse_priv->ToString(); - std::string pub2 = parse_priv->ToString(); + std::string pub2 = parse_pub->ToString(); BOOST_CHECK_EQUAL(pub, pub1); BOOST_CHECK_EQUAL(pub, pub2); @@ -76,19 +79,42 @@ void Check(const std::string& prv, const std::string& pub, int flags, const std: BOOST_CHECK_EQUAL(parse_pub->IsRange(), (flags & RANGE) != 0); BOOST_CHECK_EQUAL(parse_priv->IsRange(), (flags & RANGE) != 0); - - // Is not ranged descriptor, only a single result is expected. + // * For ranged descriptors, the `scripts` parameter is a list of expected result outputs, for subsequent + // positions to evaluate the descriptors on (so the first element of `scripts` is for evaluating the + // descriptor at 0; the second at 1; and so on). To verify this, we evaluate the descriptors once for + // each element in `scripts`. + // * For non-ranged descriptors, we evaluate the descriptors at positions 0, 1, and 2, but expect the + // same result in each case, namely the first element of `scripts`. Because of that, the size of + // `scripts` must be one in that case. if (!(flags & RANGE)) assert(scripts.size() == 1); - size_t max = (flags & RANGE) ? scripts.size() : 3; + + // Iterate over the position we'll evaluate the descriptors in. for (size_t i = 0; i < max; ++i) { + // Call the expected result scripts `ref`. const auto& ref = scripts[(flags & RANGE) ? i : 0]; + // When t=0, evaluate the `prv` descriptor; when t=1, evaluate the `pub` descriptor. for (int t = 0; t < 2; ++t) { - FlatSigningProvider key_provider = (flags & HARDENED) ? keys_priv : keys_pub; - FlatSigningProvider script_provider; - std::vector<CScript> spks; - BOOST_CHECK((t ? parse_priv : parse_pub)->Expand(i, key_provider, spks, script_provider)); + // When the descriptor is hardened, evaluate with access to the private keys inside. + const FlatSigningProvider& key_provider = (flags & HARDENED) ? keys_priv : keys_pub; + + // Evaluate the descriptor selected by `t` in poisition `i`. + FlatSigningProvider script_provider, script_provider_cached; + std::vector<CScript> spks, spks_cached; + std::vector<unsigned char> cache; + BOOST_CHECK((t ? parse_priv : parse_pub)->Expand(i, key_provider, spks, script_provider, &cache)); + + // Compare the output with the expected result. BOOST_CHECK_EQUAL(spks.size(), ref.size()); + + // Try to expand again using cached data, and compare. + BOOST_CHECK(parse_pub->ExpandFromCache(i, cache, spks_cached, script_provider_cached)); + BOOST_CHECK(spks == spks_cached); + BOOST_CHECK(script_provider.pubkeys == script_provider_cached.pubkeys); + BOOST_CHECK(script_provider.scripts == script_provider_cached.scripts); + BOOST_CHECK(script_provider.origins == script_provider_cached.origins); + + // For each of the produced scripts, verify solvability, and when possible, try to sign a transaction spending it. for (size_t n = 0; n < spks.size(); ++n) { BOOST_CHECK_EQUAL(ref[n], HexStr(spks[n].begin(), spks[n].end())); BOOST_CHECK_EQUAL(IsSolvable(Merge(key_provider, script_provider), spks[n]), (flags & UNSOLVABLE) == 0); @@ -99,10 +125,30 @@ void Check(const std::string& prv, const std::string& pub, int flags, const std: spend.vout.resize(1); BOOST_CHECK_MESSAGE(SignSignature(Merge(keys_priv, script_provider), spks[n], spend, 0, 1, SIGHASH_ALL), prv); } + + /* Infer a descriptor from the generated script, and verify its solvability and that it roundtrips. */ + auto inferred = InferDescriptor(spks[n], script_provider); + BOOST_CHECK_EQUAL(inferred->IsSolvable(), !(flags & UNSOLVABLE)); + std::vector<CScript> spks_inferred; + FlatSigningProvider provider_inferred; + BOOST_CHECK(inferred->Expand(0, provider_inferred, spks_inferred, provider_inferred)); + BOOST_CHECK_EQUAL(spks_inferred.size(), 1); + BOOST_CHECK(spks_inferred[0] == spks[n]); + BOOST_CHECK_EQUAL(IsSolvable(provider_inferred, spks_inferred[0]), !(flags & UNSOLVABLE)); + BOOST_CHECK(provider_inferred.origins == script_provider.origins); } + // Test whether the observed key path is present in the 'paths' variable (which contains expected, unobserved paths), + // and then remove it from that set. + for (const auto& origin : script_provider.origins) { + BOOST_CHECK_MESSAGE(paths.count(origin.second.path), "Unexpected key path: " + prv); + left_paths.erase(origin.second.path); + } } } + + // Verify no expected paths remain that were not observed. + BOOST_CHECK_MESSAGE(left_paths.empty(), "Not all expected key paths found: " + prv); } } @@ -114,7 +160,7 @@ BOOST_AUTO_TEST_CASE(descriptor_test) // Basic single-key compressed Check("combo(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)", "combo(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", SIGNABLE, {{"2103a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bdac","76a9149a1c78a507689f6f54b847ad1cef1e614ee23f1e88ac","00149a1c78a507689f6f54b847ad1cef1e614ee23f1e","a91484ab21b1b2fd065d4504ff693d832434b6108d7b87"}}); Check("pk(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)", "pk(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", SIGNABLE, {{"2103a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bdac"}}); - Check("pkh(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)", "pkh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", SIGNABLE, {{"76a9149a1c78a507689f6f54b847ad1cef1e614ee23f1e88ac"}}); + Check("pkh([deadbeef/1/2'/3/4']L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)", "pkh([deadbeef/1/2'/3/4']03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", SIGNABLE, {{"76a9149a1c78a507689f6f54b847ad1cef1e614ee23f1e88ac"}}, {{1,0x80000002UL,3,0x80000004UL}}); Check("wpkh(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)", "wpkh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", SIGNABLE, {{"00149a1c78a507689f6f54b847ad1cef1e614ee23f1e"}}); Check("sh(wpkh(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1))", "sh(wpkh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd))", SIGNABLE, {{"a91484ab21b1b2fd065d4504ff693d832434b6108d7b87"}}); @@ -135,20 +181,26 @@ BOOST_AUTO_TEST_CASE(descriptor_test) Check("sh(wsh(pkh(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)))", "sh(wsh(pkh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)))", SIGNABLE, {{"a914b61b92e2ca21bac1e72a3ab859a742982bea960a87"}}); // Versions with BIP32 derivations - Check("combo(xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc)", "combo(xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL)", SIGNABLE, {{"2102d2b36900396c9282fa14628566582f206a5dd0bcc8d5e892611806cafb0301f0ac","76a91431a507b815593dfc51ffc7245ae7e5aee304246e88ac","001431a507b815593dfc51ffc7245ae7e5aee304246e","a9142aafb926eb247cb18240a7f4c07983ad1f37922687"}}); - Check("pk(xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0)", "pk(xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0)", DEFAULT, {{"210379e45b3cf75f9c5f9befd8e9506fb962f6a9d185ac87001ec44a8d3df8d4a9e3ac"}}); - Check("pkh(xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U/2147483647'/0)", "pkh(xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/2147483647'/0)", HARDENED, {{"76a914ebdc90806a9c4356c1c88e42216611e1cb4c1c1788ac"}}); - Check("wpkh(xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt/1/2/*)", "wpkh(xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/1/2/*)", RANGE, {{"0014326b2249e3a25d5dc60935f044ee835d090ba859"},{"0014af0bd98abc2f2cae66e36896a39ffe2d32984fb7"},{"00141fa798efd1cbf95cebf912c031b8a4a6e9fb9f27"}}); - Check("sh(wpkh(xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi/10/20/30/40/*'))", "sh(wpkh(xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8/10/20/30/40/*'))", RANGE | HARDENED, {{"a9149a4d9901d6af519b2a23d4a2f51650fcba87ce7b87"},{"a914bed59fc0024fae941d6e20a3b44a109ae740129287"},{"a9148483aa1116eb9c05c482a72bada4b1db24af654387"}}); - Check("combo(xprvA2JDeKCSNNZky6uBCviVfJSKyQ1mDYahRjijr5idH2WwLsEd4Hsb2Tyh8RfQMuPh7f7RtyzTtdrbdqqsunu5Mm3wDvUAKRHSC34sJ7in334/*)", "combo(xpub6FHa3pjLCk84BayeJxFW2SP4XRrFd1JYnxeLeU8EqN3vDfZmbqBqaGJAyiLjTAwm6ZLRQUMv1ZACTj37sR62cfN7fe5JnJ7dh8zL4fiyLHV/*)", RANGE, {{"2102df12b7035bdac8e3bab862a3a83d06ea6b17b6753d52edecba9be46f5d09e076ac","76a914f90e3178ca25f2c808dc76624032d352fdbdfaf288ac","0014f90e3178ca25f2c808dc76624032d352fdbdfaf2","a91408f3ea8c68d4a7585bf9e8bda226723f70e445f087"},{"21032869a233c9adff9a994e4966e5b821fd5bac066da6c3112488dc52383b4a98ecac","76a914a8409d1b6dfb1ed2a3e8aa5e0ef2ff26b15b75b788ac","0014a8409d1b6dfb1ed2a3e8aa5e0ef2ff26b15b75b7","a91473e39884cb71ae4e5ac9739e9225026c99763e6687"}}); + Check("combo([01234567]xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc)", "combo([01234567]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL)", SIGNABLE, {{"2102d2b36900396c9282fa14628566582f206a5dd0bcc8d5e892611806cafb0301f0ac","76a91431a507b815593dfc51ffc7245ae7e5aee304246e88ac","001431a507b815593dfc51ffc7245ae7e5aee304246e","a9142aafb926eb247cb18240a7f4c07983ad1f37922687"}}); + Check("pk(xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0)", "pk(xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0)", DEFAULT, {{"210379e45b3cf75f9c5f9befd8e9506fb962f6a9d185ac87001ec44a8d3df8d4a9e3ac"}}, {{0}}); + Check("pkh(xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U/2147483647'/0)", "pkh(xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/2147483647'/0)", HARDENED, {{"76a914ebdc90806a9c4356c1c88e42216611e1cb4c1c1788ac"}}, {{0xFFFFFFFFUL,0}}); + Check("wpkh([ffffffff/13']xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt/1/2/*)", "wpkh([ffffffff/13']xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/1/2/*)", RANGE, {{"0014326b2249e3a25d5dc60935f044ee835d090ba859"},{"0014af0bd98abc2f2cae66e36896a39ffe2d32984fb7"},{"00141fa798efd1cbf95cebf912c031b8a4a6e9fb9f27"}}, {{0x8000000DUL, 1, 2, 0}, {0x8000000DUL, 1, 2, 1}, {0x8000000DUL, 1, 2, 2}}); + Check("sh(wpkh(xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi/10/20/30/40/*'))", "sh(wpkh(xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8/10/20/30/40/*'))", RANGE | HARDENED, {{"a9149a4d9901d6af519b2a23d4a2f51650fcba87ce7b87"},{"a914bed59fc0024fae941d6e20a3b44a109ae740129287"},{"a9148483aa1116eb9c05c482a72bada4b1db24af654387"}}, {{10, 20, 30, 40, 0x80000000UL}, {10, 20, 30, 40, 0x80000001UL}, {10, 20, 30, 40, 0x80000002UL}}); + Check("combo(xprvA2JDeKCSNNZky6uBCviVfJSKyQ1mDYahRjijr5idH2WwLsEd4Hsb2Tyh8RfQMuPh7f7RtyzTtdrbdqqsunu5Mm3wDvUAKRHSC34sJ7in334/*)", "combo(xpub6FHa3pjLCk84BayeJxFW2SP4XRrFd1JYnxeLeU8EqN3vDfZmbqBqaGJAyiLjTAwm6ZLRQUMv1ZACTj37sR62cfN7fe5JnJ7dh8zL4fiyLHV/*)", RANGE, {{"2102df12b7035bdac8e3bab862a3a83d06ea6b17b6753d52edecba9be46f5d09e076ac","76a914f90e3178ca25f2c808dc76624032d352fdbdfaf288ac","0014f90e3178ca25f2c808dc76624032d352fdbdfaf2","a91408f3ea8c68d4a7585bf9e8bda226723f70e445f087"},{"21032869a233c9adff9a994e4966e5b821fd5bac066da6c3112488dc52383b4a98ecac","76a914a8409d1b6dfb1ed2a3e8aa5e0ef2ff26b15b75b788ac","0014a8409d1b6dfb1ed2a3e8aa5e0ef2ff26b15b75b7","a91473e39884cb71ae4e5ac9739e9225026c99763e6687"}}, {{0}, {1}}); + CheckUnparsable("combo([012345678]xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc)", "combo([012345678]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL)"); // Too long key fingerprint CheckUnparsable("pkh(xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U/2147483648)", "pkh(xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/2147483648)"); // BIP 32 path element overflow // Multisig constructions Check("multi(1,L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1,5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss)", "multi(1,03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd,04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235)", SIGNABLE, {{"512103a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd4104a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea23552ae"}}); - Check("sh(multi(2,xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0))", "sh(multi(2,xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0))", DEFAULT, {{"a91445a9a622a8b0a1269944be477640eedc447bbd8487"}}); - Check("wsh(multi(2,xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U/2147483647'/0,xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt/1/2/*,xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi/10/20/30/40/*'))", "wsh(multi(2,xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/2147483647'/0,xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/1/2/*,xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8/10/20/30/40/*'))", HARDENED | RANGE, {{"0020b92623201f3bb7c3771d45b2ad1d0351ea8fbf8cfe0a0e570264e1075fa1948f"},{"002036a08bbe4923af41cf4316817c93b8d37e2f635dd25cfff06bd50df6ae7ea203"},{"0020a96e7ab4607ca6b261bfe3245ffda9c746b28d3f59e83d34820ec0e2b36c139c"}}); + Check("sh(multi(2,[00000000/111'/222]xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0))", "sh(multi(2,[00000000/111'/222]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0))", DEFAULT, {{"a91445a9a622a8b0a1269944be477640eedc447bbd8487"}}, {{0x8000006FUL,222},{0}}); + Check("wsh(multi(2,xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U/2147483647'/0,xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt/1/2/*,xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi/10/20/30/40/*'))", "wsh(multi(2,xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/2147483647'/0,xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/1/2/*,xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8/10/20/30/40/*'))", HARDENED | RANGE, {{"0020b92623201f3bb7c3771d45b2ad1d0351ea8fbf8cfe0a0e570264e1075fa1948f"},{"002036a08bbe4923af41cf4316817c93b8d37e2f635dd25cfff06bd50df6ae7ea203"},{"0020a96e7ab4607ca6b261bfe3245ffda9c746b28d3f59e83d34820ec0e2b36c139c"}}, {{0xFFFFFFFFUL,0}, {1,2,0}, {1,2,1}, {1,2,2}, {10, 20, 30, 40, 0x80000000UL}, {10, 20, 30, 40, 0x80000001UL}, {10, 20, 30, 40, 0x80000002UL}}); Check("sh(wsh(multi(16,KzoAz5CanayRKex3fSLQ2BwJpN7U52gZvxMyk78nDMHuqrUxuSJy,KwGNz6YCCQtYvFzMtrC6D3tKTKdBBboMrLTsjr2NYVBwapCkn7Mr,KxogYhiNfwxuswvXV66eFyKcCpm7dZ7TqHVqujHAVUjJxyivxQ9X,L2BUNduTSyZwZjwNHynQTF14mv2uz2NRq5n5sYWTb4FkkmqgEE9f,L1okJGHGn1kFjdXHKxXjwVVtmCMR2JA5QsbKCSpSb7ReQjezKeoD,KxDCNSST75HFPaW5QKpzHtAyaCQC7p9Vo3FYfi2u4dXD1vgMiboK,L5edQjFtnkcf5UWURn6UuuoFrabgDQUHdheKCziwN42aLwS3KizU,KzF8UWFcEC7BYTq8Go1xVimMkDmyNYVmXV5PV7RuDicvAocoPB8i,L3nHUboKG2w4VSJ5jYZ5CBM97oeK6YuKvfZxrefdShECcjEYKMWZ,KyjHo36dWkYhimKmVVmQTq3gERv3pnqA4xFCpvUgbGDJad7eS8WE,KwsfyHKRUTZPQtysN7M3tZ4GXTnuov5XRgjdF2XCG8faAPmFruRF,KzCUbGhN9LJhdeFfL9zQgTJMjqxdBKEekRGZX24hXdgCNCijkkap,KzgpMBwwsDLwkaC5UrmBgCYaBD2WgZ7PBoGYXR8KT7gCA9UTN5a3,KyBXTPy4T7YG4q9tcAM3LkvfRpD1ybHMvcJ2ehaWXaSqeGUxEdkP,KzJDe9iwJRPtKP2F2AoN6zBgzS7uiuAwhWCfGdNeYJ3PC1HNJ8M8,L1xbHrxynrqLKkoYc4qtoQPx6uy5qYXR5ZDYVYBSRmCV5piU3JG9)))","sh(wsh(multi(16,03669b8afcec803a0d323e9a17f3ea8e68e8abe5a278020a929adbec52421adbd0,0260b2003c386519fc9eadf2b5cf124dd8eea4c4e68d5e154050a9346ea98ce600,0362a74e399c39ed5593852a30147f2959b56bb827dfa3e60e464b02ccf87dc5e8,0261345b53de74a4d721ef877c255429961b7e43714171ac06168d7e08c542a8b8,02da72e8b46901a65d4374fe6315538d8f368557dda3a1dcf9ea903f3afe7314c8,0318c82dd0b53fd3a932d16e0ba9e278fcc937c582d5781be626ff16e201f72286,0297ccef1ef99f9d73dec9ad37476ddb232f1238aff877af19e72ba04493361009,02e502cfd5c3f972fe9a3e2a18827820638f96b6f347e54d63deb839011fd5765d,03e687710f0e3ebe81c1037074da939d409c0025f17eb86adb9427d28f0f7ae0e9,02c04d3a5274952acdbc76987f3184b346a483d43be40874624b29e3692c1df5af,02ed06e0f418b5b43a7ec01d1d7d27290fa15f75771cb69b642a51471c29c84acd,036d46073cbb9ffee90473f3da429abc8de7f8751199da44485682a989a4bebb24,02f5d1ff7c9029a80a4e36b9a5497027ef7f3e73384a4a94fbfe7c4e9164eec8bc,02e41deffd1b7cce11cde209a781adcffdabd1b91c0ba0375857a2bfd9302419f3,02d76625f7956a7fc505ab02556c23ee72d832f1bac391bcd2d3abce5710a13d06,0399eb0a5487515802dc14544cf10b3666623762fbed2ec38a3975716e2c29c232)))", SIGNABLE, {{"a9147fc63e13dc25e8a95a3cee3d9a714ac3afd96f1e87"}}); CheckUnparsable("sh(multi(16,KzoAz5CanayRKex3fSLQ2BwJpN7U52gZvxMyk78nDMHuqrUxuSJy,KwGNz6YCCQtYvFzMtrC6D3tKTKdBBboMrLTsjr2NYVBwapCkn7Mr,KxogYhiNfwxuswvXV66eFyKcCpm7dZ7TqHVqujHAVUjJxyivxQ9X,L2BUNduTSyZwZjwNHynQTF14mv2uz2NRq5n5sYWTb4FkkmqgEE9f,L1okJGHGn1kFjdXHKxXjwVVtmCMR2JA5QsbKCSpSb7ReQjezKeoD,KxDCNSST75HFPaW5QKpzHtAyaCQC7p9Vo3FYfi2u4dXD1vgMiboK,L5edQjFtnkcf5UWURn6UuuoFrabgDQUHdheKCziwN42aLwS3KizU,KzF8UWFcEC7BYTq8Go1xVimMkDmyNYVmXV5PV7RuDicvAocoPB8i,L3nHUboKG2w4VSJ5jYZ5CBM97oeK6YuKvfZxrefdShECcjEYKMWZ,KyjHo36dWkYhimKmVVmQTq3gERv3pnqA4xFCpvUgbGDJad7eS8WE,KwsfyHKRUTZPQtysN7M3tZ4GXTnuov5XRgjdF2XCG8faAPmFruRF,KzCUbGhN9LJhdeFfL9zQgTJMjqxdBKEekRGZX24hXdgCNCijkkap,KzgpMBwwsDLwkaC5UrmBgCYaBD2WgZ7PBoGYXR8KT7gCA9UTN5a3,KyBXTPy4T7YG4q9tcAM3LkvfRpD1ybHMvcJ2ehaWXaSqeGUxEdkP,KzJDe9iwJRPtKP2F2AoN6zBgzS7uiuAwhWCfGdNeYJ3PC1HNJ8M8,L1xbHrxynrqLKkoYc4qtoQPx6uy5qYXR5ZDYVYBSRmCV5piU3JG9))","sh(multi(16,03669b8afcec803a0d323e9a17f3ea8e68e8abe5a278020a929adbec52421adbd0,0260b2003c386519fc9eadf2b5cf124dd8eea4c4e68d5e154050a9346ea98ce600,0362a74e399c39ed5593852a30147f2959b56bb827dfa3e60e464b02ccf87dc5e8,0261345b53de74a4d721ef877c255429961b7e43714171ac06168d7e08c542a8b8,02da72e8b46901a65d4374fe6315538d8f368557dda3a1dcf9ea903f3afe7314c8,0318c82dd0b53fd3a932d16e0ba9e278fcc937c582d5781be626ff16e201f72286,0297ccef1ef99f9d73dec9ad37476ddb232f1238aff877af19e72ba04493361009,02e502cfd5c3f972fe9a3e2a18827820638f96b6f347e54d63deb839011fd5765d,03e687710f0e3ebe81c1037074da939d409c0025f17eb86adb9427d28f0f7ae0e9,02c04d3a5274952acdbc76987f3184b346a483d43be40874624b29e3692c1df5af,02ed06e0f418b5b43a7ec01d1d7d27290fa15f75771cb69b642a51471c29c84acd,036d46073cbb9ffee90473f3da429abc8de7f8751199da44485682a989a4bebb24,02f5d1ff7c9029a80a4e36b9a5497027ef7f3e73384a4a94fbfe7c4e9164eec8bc,02e41deffd1b7cce11cde209a781adcffdabd1b91c0ba0375857a2bfd9302419f3,02d76625f7956a7fc505ab02556c23ee72d832f1bac391bcd2d3abce5710a13d06,0399eb0a5487515802dc14544cf10b3666623762fbed2ec38a3975716e2c29c232))"); // P2SH does not fit 16 compressed pubkeys in a redeemscript + CheckUnparsable("wsh(multi(2,[aaaaaaaa][aaaaaaaa]xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U/2147483647'/0,xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt/1/2/*,xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi/10/20/30/40/*'))", "wsh(multi(2,[aaaaaaaa][aaaaaaaa]xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/2147483647'/0,xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/1/2/*,xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8/10/20/30/40/*'))"); // Double key origin descriptor + CheckUnparsable("wsh(multi(2,[aaaagaaa]xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U/2147483647'/0,xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt/1/2/*,xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi/10/20/30/40/*'))", "wsh(multi(2,[aaagaaaa]xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/2147483647'/0,xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/1/2/*,xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8/10/20/30/40/*'))"); // Non hex fingerprint + CheckUnparsable("wsh(multi(2,[aaaaaaaa],xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt/1/2/*,xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi/10/20/30/40/*'))", "wsh(multi(2,[aaaaaaaa],xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/1/2/*,xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8/10/20/30/40/*'))"); // No public key with origin + CheckUnparsable("wsh(multi(2,[aaaaaaa]xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U/2147483647'/0,xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt/1/2/*,xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi/10/20/30/40/*'))", "wsh(multi(2,[aaaaaaa]xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/2147483647'/0,xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/1/2/*,xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8/10/20/30/40/*'))"); // Too short fingerprint + CheckUnparsable("wsh(multi(2,[aaaaaaaaa]xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U/2147483647'/0,xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt/1/2/*,xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi/10/20/30/40/*'))", "wsh(multi(2,[aaaaaaaaa]xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/2147483647'/0,xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/1/2/*,xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8/10/20/30/40/*'))"); // Too long fingerprint // Check for invalid nesting of structures CheckUnparsable("sh(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)", "sh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)"); // P2SH needs a script, not a key diff --git a/src/test/fs_tests.cpp b/src/test/fs_tests.cpp new file mode 100644 index 0000000000..93aee10bb7 --- /dev/null +++ b/src/test/fs_tests.cpp @@ -0,0 +1,56 @@ +// Copyright (c) 2011-2018 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. +// +#include <fs.h> +#include <test/test_bitcoin.h> + +#include <boost/test/unit_test.hpp> + +BOOST_FIXTURE_TEST_SUITE(fs_tests, BasicTestingSetup) + +BOOST_AUTO_TEST_CASE(fsbridge_fstream) +{ + fs::path tmpfolder = SetDataDir("fsbridge_fstream"); + // tmpfile1 should be the same as tmpfile2 + fs::path tmpfile1 = tmpfolder / "fs_tests_₿_🏃"; + fs::path tmpfile2 = tmpfolder / L"fs_tests_₿_🏃"; + { + fsbridge::ofstream file(tmpfile1); + file << "bitcoin"; + } + { + fsbridge::ifstream file(tmpfile2); + std::string input_buffer; + file >> input_buffer; + BOOST_CHECK_EQUAL(input_buffer, "bitcoin"); + } + { + fsbridge::ifstream file(tmpfile1, std::ios_base::in | std::ios_base::ate); + std::string input_buffer; + file >> input_buffer; + BOOST_CHECK_EQUAL(input_buffer, ""); + } + { + fsbridge::ofstream file(tmpfile2, std::ios_base::out | std::ios_base::app); + file << "tests"; + } + { + fsbridge::ifstream file(tmpfile1); + std::string input_buffer; + file >> input_buffer; + BOOST_CHECK_EQUAL(input_buffer, "bitcointests"); + } + { + fsbridge::ofstream file(tmpfile2, std::ios_base::out | std::ios_base::trunc); + file << "bitcoin"; + } + { + fsbridge::ifstream file(tmpfile1); + std::string input_buffer; + file >> input_buffer; + BOOST_CHECK_EQUAL(input_buffer, "bitcoin"); + } +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/getarg_tests.cpp b/src/test/getarg_tests.cpp index 7592330b10..8048238028 100644 --- a/src/test/getarg_tests.cpp +++ b/src/test/getarg_tests.cpp @@ -2,7 +2,8 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#include <util.h> +#include <util/strencodings.h> +#include <util/system.h> #include <test/test_bitcoin.h> #include <string> @@ -17,7 +18,7 @@ static void ResetArgs(const std::string& strArg) { std::vector<std::string> vecArg; if (strArg.size()) - boost::split(vecArg, strArg, boost::is_space(), boost::token_compress_on); + boost::split(vecArg, strArg, IsSpace, boost::token_compress_on); // Insert dummy executable name: vecArg.insert(vecArg.begin(), "testbitcoin"); @@ -28,7 +29,7 @@ static void ResetArgs(const std::string& strArg) vecChar.push_back(s.c_str()); std::string error; - gArgs.ParseParameters(vecChar.size(), vecChar.data(), error); + BOOST_CHECK(gArgs.ParseParameters(vecChar.size(), vecChar.data(), error)); } static void SetupArgs(const std::vector<std::string>& args) diff --git a/src/test/hash_tests.cpp b/src/test/hash_tests.cpp index 6ed0209895..e8e5040855 100644 --- a/src/test/hash_tests.cpp +++ b/src/test/hash_tests.cpp @@ -2,8 +2,9 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. +#include <crypto/siphash.h> #include <hash.h> -#include <utilstrencodings.h> +#include <util/strencodings.h> #include <test/test_bitcoin.h> #include <vector> diff --git a/src/test/key_io_tests.cpp b/src/test/key_io_tests.cpp index a0c10d8ddd..bf295042de 100644 --- a/src/test/key_io_tests.cpp +++ b/src/test/key_io_tests.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2011-2017 The Bitcoin Core developers +// Copyright (c) 2011-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. @@ -8,7 +8,7 @@ #include <key.h> #include <key_io.h> #include <script/script.h> -#include <utilstrencodings.h> +#include <util/strencodings.h> #include <test/test_bitcoin.h> #include <boost/test/unit_test.hpp> @@ -107,7 +107,7 @@ BOOST_AUTO_TEST_CASE(key_io_valid_gen) } else { CTxDestination dest; CScript exp_script(exp_payload.begin(), exp_payload.end()); - ExtractDestination(exp_script, dest); + BOOST_CHECK(ExtractDestination(exp_script, dest)); std::string address = EncodeDestination(dest); BOOST_CHECK_EQUAL(address, exp_base58string); diff --git a/src/test/key_properties.cpp b/src/test/key_properties.cpp index 14e3c85359..c564b4eab8 100644 --- a/src/test/key_properties.cpp +++ b/src/test/key_properties.cpp @@ -6,8 +6,8 @@ #include <base58.h> #include <script/script.h> #include <uint256.h> -#include <util.h> -#include <utilstrencodings.h> +#include <util/system.h> +#include <util/strencodings.h> #include <test/test_bitcoin.h> #include <string> #include <vector> diff --git a/src/test/key_tests.cpp b/src/test/key_tests.cpp index 61db70decb..a768b4bcbd 100644 --- a/src/test/key_tests.cpp +++ b/src/test/key_tests.cpp @@ -7,8 +7,8 @@ #include <key_io.h> #include <script/script.h> #include <uint256.h> -#include <util.h> -#include <utilstrencodings.h> +#include <util/system.h> +#include <util/strencodings.h> #include <test/test_bitcoin.h> #include <string> @@ -163,7 +163,7 @@ BOOST_AUTO_TEST_CASE(key_signature_tests) for (int i = 1; i <=20; ++i) { sig.clear(); - key.Sign(msg_hash, sig, false, i); + BOOST_CHECK(key.Sign(msg_hash, sig, false, i)); found = sig[3] == 0x21 && sig[4] == 0x00; if (found) { break; @@ -179,7 +179,7 @@ BOOST_AUTO_TEST_CASE(key_signature_tests) sig.clear(); std::string msg = "A message to be signed" + std::to_string(i); msg_hash = Hash(msg.begin(), msg.end()); - key.Sign(msg_hash, sig); + BOOST_CHECK(key.Sign(msg_hash, sig)); found = sig[3] == 0x20; BOOST_CHECK(sig.size() <= 70); found_small |= sig.size() < 70; diff --git a/src/test/mempool_tests.cpp b/src/test/mempool_tests.cpp index 0e15464fd9..23ca9d89ae 100644 --- a/src/test/mempool_tests.cpp +++ b/src/test/mempool_tests.cpp @@ -4,7 +4,7 @@ #include <policy/policy.h> #include <txmempool.h> -#include <util.h> +#include <util/system.h> #include <test/test_bitcoin.h> @@ -55,17 +55,17 @@ BOOST_AUTO_TEST_CASE(MempoolRemoveTest) CTxMemPool testPool; - LOCK(testPool.cs); + LOCK2(cs_main, testPool.cs); // Nothing in pool, remove should do nothing: unsigned int poolSize = testPool.size(); - testPool.removeRecursive(txParent); + testPool.removeRecursive(CTransaction(txParent)); BOOST_CHECK_EQUAL(testPool.size(), poolSize); // Just the parent: testPool.addUnchecked(entry.FromTx(txParent)); poolSize = testPool.size(); - testPool.removeRecursive(txParent); + testPool.removeRecursive(CTransaction(txParent)); BOOST_CHECK_EQUAL(testPool.size(), poolSize - 1); // Parent, children, grandchildren: @@ -77,18 +77,18 @@ BOOST_AUTO_TEST_CASE(MempoolRemoveTest) } // Remove Child[0], GrandChild[0] should be removed: poolSize = testPool.size(); - testPool.removeRecursive(txChild[0]); + testPool.removeRecursive(CTransaction(txChild[0])); BOOST_CHECK_EQUAL(testPool.size(), poolSize - 2); // ... make sure grandchild and child are gone: poolSize = testPool.size(); - testPool.removeRecursive(txGrandChild[0]); + testPool.removeRecursive(CTransaction(txGrandChild[0])); BOOST_CHECK_EQUAL(testPool.size(), poolSize); poolSize = testPool.size(); - testPool.removeRecursive(txChild[0]); + testPool.removeRecursive(CTransaction(txChild[0])); BOOST_CHECK_EQUAL(testPool.size(), poolSize); // Remove parent, all children/grandchildren should go: poolSize = testPool.size(); - testPool.removeRecursive(txParent); + testPool.removeRecursive(CTransaction(txParent)); BOOST_CHECK_EQUAL(testPool.size(), poolSize - 5); BOOST_CHECK_EQUAL(testPool.size(), 0U); @@ -101,7 +101,7 @@ BOOST_AUTO_TEST_CASE(MempoolRemoveTest) // Now remove the parent, as might happen if a block-re-org occurs but the parent cannot be // put into the mempool (maybe because it is non-standard): poolSize = testPool.size(); - testPool.removeRecursive(txParent); + testPool.removeRecursive(CTransaction(txParent)); BOOST_CHECK_EQUAL(testPool.size(), poolSize - 6); BOOST_CHECK_EQUAL(testPool.size(), 0U); } @@ -120,7 +120,7 @@ static void CheckSort(CTxMemPool &pool, std::vector<std::string> &sortedOrder) E BOOST_AUTO_TEST_CASE(MempoolIndexingTest) { CTxMemPool pool; - LOCK(pool.cs); + LOCK2(cs_main, pool.cs); TestMemPoolEntryHelper entry; /* 3rd highest fee */ @@ -293,7 +293,7 @@ BOOST_AUTO_TEST_CASE(MempoolIndexingTest) BOOST_AUTO_TEST_CASE(MempoolAncestorIndexingTest) { CTxMemPool pool; - LOCK(pool.cs); + LOCK2(cs_main, pool.cs); TestMemPoolEntryHelper entry; /* 3rd highest fee */ @@ -309,7 +309,7 @@ BOOST_AUTO_TEST_CASE(MempoolAncestorIndexingTest) tx2.vout[0].scriptPubKey = CScript() << OP_11 << OP_EQUAL; tx2.vout[0].nValue = 2 * COIN; pool.addUnchecked(entry.Fee(20000LL).FromTx(tx2)); - uint64_t tx2Size = GetVirtualTransactionSize(tx2); + uint64_t tx2Size = GetVirtualTransactionSize(CTransaction(tx2)); /* lowest fee */ CMutableTransaction tx3 = CMutableTransaction(); @@ -357,7 +357,7 @@ BOOST_AUTO_TEST_CASE(MempoolAncestorIndexingTest) tx6.vout.resize(1); tx6.vout[0].scriptPubKey = CScript() << OP_11 << OP_EQUAL; tx6.vout[0].nValue = 20 * COIN; - uint64_t tx6Size = GetVirtualTransactionSize(tx6); + uint64_t tx6Size = GetVirtualTransactionSize(CTransaction(tx6)); pool.addUnchecked(entry.Fee(0LL).FromTx(tx6)); BOOST_CHECK_EQUAL(pool.size(), 6U); @@ -376,7 +376,7 @@ BOOST_AUTO_TEST_CASE(MempoolAncestorIndexingTest) tx7.vout.resize(1); tx7.vout[0].scriptPubKey = CScript() << OP_11 << OP_EQUAL; tx7.vout[0].nValue = 10 * COIN; - uint64_t tx7Size = GetVirtualTransactionSize(tx7); + uint64_t tx7Size = GetVirtualTransactionSize(CTransaction(tx7)); /* set the fee to just below tx2's feerate when including ancestor */ CAmount fee = (20000/tx2Size)*(tx7Size + tx6Size) - 1; @@ -422,7 +422,7 @@ BOOST_AUTO_TEST_CASE(MempoolAncestorIndexingTest) BOOST_AUTO_TEST_CASE(MempoolSizeLimitTest) { CTxMemPool pool; - LOCK(pool.cs); + LOCK2(cs_main, pool.cs); TestMemPoolEntryHelper entry; CMutableTransaction tx1 = CMutableTransaction(); @@ -464,12 +464,12 @@ BOOST_AUTO_TEST_CASE(MempoolSizeLimitTest) BOOST_CHECK(pool.exists(tx2.GetHash())); BOOST_CHECK(pool.exists(tx3.GetHash())); - pool.TrimToSize(GetVirtualTransactionSize(tx1)); // mempool is limited to tx1's size in memory usage, so nothing fits + pool.TrimToSize(GetVirtualTransactionSize(CTransaction(tx1))); // mempool is limited to tx1's size in memory usage, so nothing fits BOOST_CHECK(!pool.exists(tx1.GetHash())); BOOST_CHECK(!pool.exists(tx2.GetHash())); BOOST_CHECK(!pool.exists(tx3.GetHash())); - CFeeRate maxFeeRateRemoved(25000, GetVirtualTransactionSize(tx3) + GetVirtualTransactionSize(tx2)); + CFeeRate maxFeeRateRemoved(25000, GetVirtualTransactionSize(CTransaction(tx3)) + GetVirtualTransactionSize(CTransaction(tx2))); BOOST_CHECK_EQUAL(pool.GetMinFee(1).GetFeePerK(), maxFeeRateRemoved.GetFeePerK() + 1000); CMutableTransaction tx4 = CMutableTransaction(); @@ -595,7 +595,7 @@ BOOST_AUTO_TEST_CASE(MempoolAncestryTests) size_t ancestors, descendants; CTxMemPool pool; - LOCK(pool.cs); + LOCK2(cs_main, pool.cs); TestMemPoolEntryHelper entry; /* Base transaction */ diff --git a/src/test/merkle_tests.cpp b/src/test/merkle_tests.cpp index 5e55ad6622..4cdf0f003e 100644 --- a/src/test/merkle_tests.cpp +++ b/src/test/merkle_tests.cpp @@ -13,9 +13,9 @@ static uint256 ComputeMerkleRootFromBranch(const uint256& leaf, const std::vecto uint256 hash = leaf; for (std::vector<uint256>::const_iterator it = vMerkleBranch.begin(); it != vMerkleBranch.end(); ++it) { if (nIndex & 1) { - hash = Hash(BEGIN(*it), END(*it), BEGIN(hash), END(hash)); + hash = Hash(it->begin(), it->end(), hash.begin(), hash.end()); } else { - hash = Hash(BEGIN(hash), END(hash), BEGIN(*it), END(*it)); + hash = Hash(hash.begin(), hash.end(), it->begin(), it->end()); } nIndex >>= 1; } diff --git a/src/test/miner_tests.cpp b/src/test/miner_tests.cpp index 3eb8aa14fd..5ba1df2ec2 100644 --- a/src/test/miner_tests.cpp +++ b/src/test/miner_tests.cpp @@ -15,8 +15,8 @@ #include <script/standard.h> #include <txmempool.h> #include <uint256.h> -#include <util.h> -#include <utilstrencodings.h> +#include <util/system.h> +#include <util/strencodings.h> #include <test/test_bitcoin.h> @@ -92,14 +92,14 @@ static CBlockIndex CreateBlockIndex(int nHeight) static bool TestSequenceLocks(const CTransaction &tx, int flags) EXCLUSIVE_LOCKS_REQUIRED(cs_main) { - LOCK(mempool.cs); - return CheckSequenceLocks(tx, flags); + LOCK(::mempool.cs); + return CheckSequenceLocks(::mempool, tx, flags); } // Test suite for ancestor feerate transaction selection. // Implemented as an additional function, rather than a separate test case, // to allow reusing the blockchain created in CreateNewBlock_validity. -static void TestPackageSelection(const CChainParams& chainparams, const CScript& scriptPubKey, const std::vector<CTransactionRef>& txFirst) EXCLUSIVE_LOCKS_REQUIRED(::mempool.cs) +static void TestPackageSelection(const CChainParams& chainparams, const CScript& scriptPubKey, const std::vector<CTransactionRef>& txFirst) EXCLUSIVE_LOCKS_REQUIRED(cs_main, ::mempool.cs) { // Test the ancestor feerate transaction selection. TestMemPoolEntryHelper entry; @@ -159,7 +159,7 @@ static void TestPackageSelection(const CChainParams& chainparams, const CScript& // Test that packages above the min relay fee do get included, even if one // of the transactions is below the min relay fee // Remove the low fee transaction and replace with a higher fee transaction - mempool.removeRecursive(tx); + mempool.removeRecursive(CTransaction(tx)); tx.vout[0].nValue -= 2; // Now we should be just over the min relay fee hashLowFeeTx = tx.GetHash(); mempool.addUnchecked(entry.Fee(feeToUse+2).FromTx(tx)); @@ -441,9 +441,9 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity) tx.nLockTime = 0; hash = tx.GetHash(); mempool.addUnchecked(entry.Fee(HIGHFEE).Time(GetTime()).SpendsCoinbase(true).FromTx(tx)); - BOOST_CHECK(CheckFinalTx(tx, flags)); // Locktime passes - BOOST_CHECK(!TestSequenceLocks(tx, flags)); // Sequence locks fail - BOOST_CHECK(SequenceLocks(tx, flags, &prevheights, CreateBlockIndex(chainActive.Tip()->nHeight + 2))); // Sequence locks pass on 2nd block + BOOST_CHECK(CheckFinalTx(CTransaction(tx), flags)); // Locktime passes + BOOST_CHECK(!TestSequenceLocks(CTransaction(tx), flags)); // Sequence locks fail + BOOST_CHECK(SequenceLocks(CTransaction(tx), flags, &prevheights, CreateBlockIndex(chainActive.Tip()->nHeight + 2))); // Sequence locks pass on 2nd block // relative time locked tx.vin[0].prevout.hash = txFirst[1]->GetHash(); @@ -451,12 +451,12 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity) prevheights[0] = baseheight + 2; hash = tx.GetHash(); mempool.addUnchecked(entry.Time(GetTime()).FromTx(tx)); - BOOST_CHECK(CheckFinalTx(tx, flags)); // Locktime passes - BOOST_CHECK(!TestSequenceLocks(tx, flags)); // Sequence locks fail + BOOST_CHECK(CheckFinalTx(CTransaction(tx), flags)); // Locktime passes + BOOST_CHECK(!TestSequenceLocks(CTransaction(tx), flags)); // Sequence locks fail for (int i = 0; i < CBlockIndex::nMedianTimeSpan; i++) chainActive.Tip()->GetAncestor(chainActive.Tip()->nHeight - i)->nTime += 512; //Trick the MedianTimePast - BOOST_CHECK(SequenceLocks(tx, flags, &prevheights, CreateBlockIndex(chainActive.Tip()->nHeight + 1))); // Sequence locks pass 512 seconds later + BOOST_CHECK(SequenceLocks(CTransaction(tx), flags, &prevheights, CreateBlockIndex(chainActive.Tip()->nHeight + 1))); // Sequence locks pass 512 seconds later for (int i = 0; i < CBlockIndex::nMedianTimeSpan; i++) chainActive.Tip()->GetAncestor(chainActive.Tip()->nHeight - i)->nTime -= 512; //undo tricked MTP @@ -467,9 +467,9 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity) tx.nLockTime = chainActive.Tip()->nHeight + 1; hash = tx.GetHash(); mempool.addUnchecked(entry.Time(GetTime()).FromTx(tx)); - BOOST_CHECK(!CheckFinalTx(tx, flags)); // Locktime fails - BOOST_CHECK(TestSequenceLocks(tx, flags)); // Sequence locks pass - BOOST_CHECK(IsFinalTx(tx, chainActive.Tip()->nHeight + 2, chainActive.Tip()->GetMedianTimePast())); // Locktime passes on 2nd block + BOOST_CHECK(!CheckFinalTx(CTransaction(tx), flags)); // Locktime fails + BOOST_CHECK(TestSequenceLocks(CTransaction(tx), flags)); // Sequence locks pass + BOOST_CHECK(IsFinalTx(CTransaction(tx), chainActive.Tip()->nHeight + 2, chainActive.Tip()->GetMedianTimePast())); // Locktime passes on 2nd block // absolute time locked tx.vin[0].prevout.hash = txFirst[3]->GetHash(); @@ -478,23 +478,23 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity) prevheights[0] = baseheight + 4; hash = tx.GetHash(); mempool.addUnchecked(entry.Time(GetTime()).FromTx(tx)); - BOOST_CHECK(!CheckFinalTx(tx, flags)); // Locktime fails - BOOST_CHECK(TestSequenceLocks(tx, flags)); // Sequence locks pass - BOOST_CHECK(IsFinalTx(tx, chainActive.Tip()->nHeight + 2, chainActive.Tip()->GetMedianTimePast() + 1)); // Locktime passes 1 second later + BOOST_CHECK(!CheckFinalTx(CTransaction(tx), flags)); // Locktime fails + BOOST_CHECK(TestSequenceLocks(CTransaction(tx), flags)); // Sequence locks pass + BOOST_CHECK(IsFinalTx(CTransaction(tx), chainActive.Tip()->nHeight + 2, chainActive.Tip()->GetMedianTimePast() + 1)); // Locktime passes 1 second later // mempool-dependent transactions (not added) tx.vin[0].prevout.hash = hash; prevheights[0] = chainActive.Tip()->nHeight + 1; tx.nLockTime = 0; tx.vin[0].nSequence = 0; - BOOST_CHECK(CheckFinalTx(tx, flags)); // Locktime passes - BOOST_CHECK(TestSequenceLocks(tx, flags)); // Sequence locks pass + BOOST_CHECK(CheckFinalTx(CTransaction(tx), flags)); // Locktime passes + BOOST_CHECK(TestSequenceLocks(CTransaction(tx), flags)); // Sequence locks pass tx.vin[0].nSequence = 1; - BOOST_CHECK(!TestSequenceLocks(tx, flags)); // Sequence locks fail + BOOST_CHECK(!TestSequenceLocks(CTransaction(tx), flags)); // Sequence locks fail tx.vin[0].nSequence = CTxIn::SEQUENCE_LOCKTIME_TYPE_FLAG; - BOOST_CHECK(TestSequenceLocks(tx, flags)); // Sequence locks pass + BOOST_CHECK(TestSequenceLocks(CTransaction(tx), flags)); // Sequence locks pass tx.vin[0].nSequence = CTxIn::SEQUENCE_LOCKTIME_TYPE_FLAG | 1; - BOOST_CHECK(!TestSequenceLocks(tx, flags)); // Sequence locks fail + BOOST_CHECK(!TestSequenceLocks(CTransaction(tx), flags)); // Sequence locks fail BOOST_CHECK(pblocktemplate = AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey)); diff --git a/src/test/multisig_tests.cpp b/src/test/multisig_tests.cpp index 61e579ed38..8afe4b8a59 100644 --- a/src/test/multisig_tests.cpp +++ b/src/test/multisig_tests.cpp @@ -76,20 +76,20 @@ BOOST_AUTO_TEST_CASE(multisig_verify) // Test a AND b: keys.assign(1,key[0]); keys.push_back(key[1]); - s = sign_multisig(a_and_b, keys, txTo[0], 0); + s = sign_multisig(a_and_b, keys, CTransaction(txTo[0]), 0); BOOST_CHECK(VerifyScript(s, a_and_b, nullptr, flags, MutableTransactionSignatureChecker(&txTo[0], 0, amount), &err)); BOOST_CHECK_MESSAGE(err == SCRIPT_ERR_OK, ScriptErrorString(err)); for (int i = 0; i < 4; i++) { keys.assign(1,key[i]); - s = sign_multisig(a_and_b, keys, txTo[0], 0); + s = sign_multisig(a_and_b, keys, CTransaction(txTo[0]), 0); BOOST_CHECK_MESSAGE(!VerifyScript(s, a_and_b, nullptr, flags, MutableTransactionSignatureChecker(&txTo[0], 0, amount), &err), strprintf("a&b 1: %d", i)); BOOST_CHECK_MESSAGE(err == SCRIPT_ERR_INVALID_STACK_OPERATION, ScriptErrorString(err)); keys.assign(1,key[1]); keys.push_back(key[i]); - s = sign_multisig(a_and_b, keys, txTo[0], 0); + s = sign_multisig(a_and_b, keys, CTransaction(txTo[0]), 0); BOOST_CHECK_MESSAGE(!VerifyScript(s, a_and_b, nullptr, flags, MutableTransactionSignatureChecker(&txTo[0], 0, amount), &err), strprintf("a&b 2: %d", i)); BOOST_CHECK_MESSAGE(err == SCRIPT_ERR_EVAL_FALSE, ScriptErrorString(err)); } @@ -98,7 +98,7 @@ BOOST_AUTO_TEST_CASE(multisig_verify) for (int i = 0; i < 4; i++) { keys.assign(1,key[i]); - s = sign_multisig(a_or_b, keys, txTo[1], 0); + s = sign_multisig(a_or_b, keys, CTransaction(txTo[1]), 0); if (i == 0 || i == 1) { BOOST_CHECK_MESSAGE(VerifyScript(s, a_or_b, nullptr, flags, MutableTransactionSignatureChecker(&txTo[1], 0, amount), &err), strprintf("a|b: %d", i)); @@ -121,7 +121,7 @@ BOOST_AUTO_TEST_CASE(multisig_verify) { keys.assign(1,key[i]); keys.push_back(key[j]); - s = sign_multisig(escrow, keys, txTo[2], 0); + s = sign_multisig(escrow, keys, CTransaction(txTo[2]), 0); if (i < j && i < 3 && j < 3) { BOOST_CHECK_MESSAGE(VerifyScript(s, escrow, nullptr, flags, MutableTransactionSignatureChecker(&txTo[2], 0, amount), &err), strprintf("escrow 1: %d %d", i, j)); @@ -179,7 +179,7 @@ BOOST_AUTO_TEST_CASE(multisig_Sign) for (int i = 0; i < 4; i++) { key[i].MakeNewKey(true); - keystore.AddKey(key[i]); + BOOST_CHECK(keystore.AddKey(key[i])); } CScript a_and_b; @@ -209,7 +209,7 @@ BOOST_AUTO_TEST_CASE(multisig_Sign) for (int i = 0; i < 3; i++) { - BOOST_CHECK_MESSAGE(SignSignature(keystore, txFrom, txTo[i], 0, SIGHASH_ALL), strprintf("SignSignature %d", i)); + BOOST_CHECK_MESSAGE(SignSignature(keystore, CTransaction(txFrom), txTo[i], 0, SIGHASH_ALL), strprintf("SignSignature %d", i)); } } diff --git a/src/test/net_tests.cpp b/src/test/net_tests.cpp index 35a143957e..b4ae8e9765 100644 --- a/src/test/net_tests.cpp +++ b/src/test/net_tests.cpp @@ -11,7 +11,7 @@ #include <net.h> #include <netbase.h> #include <chainparams.h> -#include <util.h> +#include <util/system.h> #include <memory> @@ -54,10 +54,10 @@ public: s << nUBuckets; CService serv; - Lookup("252.1.1.1", serv, 7777, false); + BOOST_CHECK(Lookup("252.1.1.1", serv, 7777, false)); CAddress addr = CAddress(serv, NODE_NONE); CNetAddr resolved; - LookupHost("252.2.2.2", resolved, false); + BOOST_CHECK(LookupHost("252.2.2.2", resolved, false)); CAddrInfo info = CAddrInfo(addr, resolved); s << info; } @@ -82,7 +82,7 @@ BOOST_AUTO_TEST_CASE(cnode_listen_port) BOOST_CHECK(port == Params().GetDefaultPort()); // test set port unsigned short altPort = 12345; - gArgs.SoftSetArg("-port", std::to_string(altPort)); + BOOST_CHECK(gArgs.SoftSetArg("-port", std::to_string(altPort))); port = GetListenPort(); BOOST_CHECK(port == altPort); } @@ -94,16 +94,16 @@ BOOST_AUTO_TEST_CASE(caddrdb_read) addrmanUncorrupted.MakeDeterministic(); CService addr1, addr2, addr3; - Lookup("250.7.1.1", addr1, 8333, false); - Lookup("250.7.2.2", addr2, 9999, false); - Lookup("250.7.3.3", addr3, 9999, false); + BOOST_CHECK(Lookup("250.7.1.1", addr1, 8333, false)); + BOOST_CHECK(Lookup("250.7.2.2", addr2, 9999, false)); + BOOST_CHECK(Lookup("250.7.3.3", addr3, 9999, false)); // Add three addresses to new table. CService source; - Lookup("252.5.1.1", source, 8333, false); - addrmanUncorrupted.Add(CAddress(addr1, NODE_NONE), source); - addrmanUncorrupted.Add(CAddress(addr2, NODE_NONE), source); - addrmanUncorrupted.Add(CAddress(addr3, NODE_NONE), source); + BOOST_CHECK(Lookup("252.5.1.1", source, 8333, false)); + BOOST_CHECK(addrmanUncorrupted.Add(CAddress(addr1, NODE_NONE), source)); + BOOST_CHECK(addrmanUncorrupted.Add(CAddress(addr2, NODE_NONE), source)); + BOOST_CHECK(addrmanUncorrupted.Add(CAddress(addr3, NODE_NONE), source)); // Test that the de-serialization does not throw an exception. CDataStream ssPeers1 = AddrmanToStream(addrmanUncorrupted); @@ -128,7 +128,7 @@ BOOST_AUTO_TEST_CASE(caddrdb_read) CAddrMan addrman2; CAddrDB adb; BOOST_CHECK(addrman2.size() == 0); - adb.Read(addrman2, ssPeers2); + BOOST_CHECK(adb.Read(addrman2, ssPeers2)); BOOST_CHECK(addrman2.size() == 3); } @@ -161,7 +161,7 @@ BOOST_AUTO_TEST_CASE(caddrdb_read_corrupted) CAddrMan addrman2; CAddrDB adb; BOOST_CHECK(addrman2.size() == 0); - adb.Read(addrman2, ssPeers2); + BOOST_CHECK(!adb.Read(addrman2, ssPeers2)); BOOST_CHECK(addrman2.size() == 0); } @@ -189,4 +189,118 @@ BOOST_AUTO_TEST_CASE(cnode_simple_test) BOOST_CHECK(pnode2->fFeeler == false); } +// prior to PR #14728, this test triggers an undefined behavior +BOOST_AUTO_TEST_CASE(ipv4_peer_with_ipv6_addrMe_test) +{ + // set up local addresses; all that's necessary to reproduce the bug is + // that a normal IPv4 address is among the entries, but if this address is + // !IsRoutable the undefined behavior is easier to trigger deterministically + { + LOCK(cs_mapLocalHost); + in_addr ipv4AddrLocal; + ipv4AddrLocal.s_addr = 0x0100007f; + CNetAddr addr = CNetAddr(ipv4AddrLocal); + LocalServiceInfo lsi; + lsi.nScore = 23; + lsi.nPort = 42; + mapLocalHost[addr] = lsi; + } + + // create a peer with an IPv4 address + in_addr ipv4AddrPeer; + ipv4AddrPeer.s_addr = 0xa0b0c001; + CAddress addr = CAddress(CService(ipv4AddrPeer, 7777), NODE_NETWORK); + std::unique_ptr<CNode> pnode = MakeUnique<CNode>(0, NODE_NETWORK, 0, INVALID_SOCKET, addr, 0, 0, CAddress{}, std::string{}, false); + pnode->fSuccessfullyConnected.store(true); + + // the peer claims to be reaching us via IPv6 + in6_addr ipv6AddrLocal; + memset(ipv6AddrLocal.s6_addr, 0, 16); + ipv6AddrLocal.s6_addr[0] = 0xcc; + CAddress addrLocal = CAddress(CService(ipv6AddrLocal, 7777), NODE_NETWORK); + pnode->SetAddrLocal(addrLocal); + + // before patch, this causes undefined behavior detectable with clang's -fsanitize=memory + AdvertiseLocal(&*pnode); + + // suppress no-checks-run warning; if this test fails, it's by triggering a sanitizer + BOOST_CHECK(1); +} + + +BOOST_AUTO_TEST_CASE(LimitedAndReachable_Network) +{ + BOOST_CHECK_EQUAL(IsReachable(NET_IPV4), true); + BOOST_CHECK_EQUAL(IsReachable(NET_IPV6), true); + BOOST_CHECK_EQUAL(IsReachable(NET_ONION), true); + + SetReachable(NET_IPV4, false); + SetReachable(NET_IPV6, false); + SetReachable(NET_ONION, false); + + BOOST_CHECK_EQUAL(IsReachable(NET_IPV4), false); + BOOST_CHECK_EQUAL(IsReachable(NET_IPV6), false); + BOOST_CHECK_EQUAL(IsReachable(NET_ONION), false); + + SetReachable(NET_IPV4, true); + SetReachable(NET_IPV6, true); + SetReachable(NET_ONION, true); + + BOOST_CHECK_EQUAL(IsReachable(NET_IPV4), true); + BOOST_CHECK_EQUAL(IsReachable(NET_IPV6), true); + BOOST_CHECK_EQUAL(IsReachable(NET_ONION), true); +} + +BOOST_AUTO_TEST_CASE(LimitedAndReachable_NetworkCaseUnroutableAndInternal) +{ + BOOST_CHECK_EQUAL(IsReachable(NET_UNROUTABLE), true); + BOOST_CHECK_EQUAL(IsReachable(NET_INTERNAL), true); + + SetReachable(NET_UNROUTABLE, false); + SetReachable(NET_INTERNAL, false); + + BOOST_CHECK_EQUAL(IsReachable(NET_UNROUTABLE), true); // Ignored for both networks + BOOST_CHECK_EQUAL(IsReachable(NET_INTERNAL), true); +} + +CNetAddr UtilBuildAddress(unsigned char p1, unsigned char p2, unsigned char p3, unsigned char p4) +{ + unsigned char ip[] = {p1, p2, p3, p4}; + + struct sockaddr_in sa; + memset(&sa, 0, sizeof(sockaddr_in)); // initialize the memory block + memcpy(&(sa.sin_addr), &ip, sizeof(ip)); + return CNetAddr(sa.sin_addr); +} + + +BOOST_AUTO_TEST_CASE(LimitedAndReachable_CNetAddr) +{ + CNetAddr addr = UtilBuildAddress(0x001, 0x001, 0x001, 0x001); // 1.1.1.1 + + SetReachable(NET_IPV4, true); + BOOST_CHECK_EQUAL(IsReachable(addr), true); + + SetReachable(NET_IPV4, false); + BOOST_CHECK_EQUAL(IsReachable(addr), false); + + SetReachable(NET_IPV4, true); // have to reset this, because this is stateful. +} + + +BOOST_AUTO_TEST_CASE(LocalAddress_BasicLifecycle) +{ + CService addr = CService(UtilBuildAddress(0x002, 0x001, 0x001, 0x001), 1000); // 2.1.1.1:1000 + + SetReachable(NET_IPV4, true); + + BOOST_CHECK_EQUAL(IsLocal(addr), false); + BOOST_CHECK_EQUAL(AddLocal(addr, 1000), true); + BOOST_CHECK_EQUAL(IsLocal(addr), true); + + RemoveLocal(addr); + BOOST_CHECK_EQUAL(IsLocal(addr), false); +} + + BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/netbase_tests.cpp b/src/test/netbase_tests.cpp index 8072eb922d..0d557cff13 100644 --- a/src/test/netbase_tests.cpp +++ b/src/test/netbase_tests.cpp @@ -4,7 +4,7 @@ #include <netbase.h> #include <test/test_bitcoin.h> -#include <utilstrencodings.h> +#include <util/strencodings.h> #include <string> diff --git a/src/test/policyestimator_tests.cpp b/src/test/policyestimator_tests.cpp index 2022ed6659..7b274a1658 100644 --- a/src/test/policyestimator_tests.cpp +++ b/src/test/policyestimator_tests.cpp @@ -6,7 +6,7 @@ #include <policy/fees.h> #include <txmempool.h> #include <uint256.h> -#include <util.h> +#include <util/system.h> #include <test/test_bitcoin.h> @@ -18,7 +18,7 @@ BOOST_AUTO_TEST_CASE(BlockPolicyEstimates) { CBlockPolicyEstimator feeEst; CTxMemPool mpool(&feeEst); - LOCK(mpool.cs); + LOCK2(cs_main, mpool.cs); TestMemPoolEntryHelper entry; CAmount basefee(2000); CAmount deltaFee(100); @@ -44,7 +44,7 @@ BOOST_AUTO_TEST_CASE(BlockPolicyEstimates) tx.vin[0].scriptSig = garbage; tx.vout.resize(1); tx.vout[0].nValue=0LL; - CFeeRate baseRate(basefee, GetVirtualTransactionSize(tx)); + CFeeRate baseRate(basefee, GetVirtualTransactionSize(CTransaction(tx))); // Create a fake block std::vector<CTransactionRef> block; diff --git a/src/test/pow_tests.cpp b/src/test/pow_tests.cpp index 1ac9adc740..26cdc9bc5c 100644 --- a/src/test/pow_tests.cpp +++ b/src/test/pow_tests.cpp @@ -1,12 +1,12 @@ // Copyright (c) 2015-2018 The Bitcoin Core developers -// Distributed under the MIT/X11 software license, see the accompanying +// Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include <chain.h> #include <chainparams.h> #include <pow.h> #include <random.h> -#include <util.h> +#include <util/system.h> #include <test/test_bitcoin.h> #include <boost/test/unit_test.hpp> diff --git a/src/test/prevector_tests.cpp b/src/test/prevector_tests.cpp index c488d3edcf..7341389208 100644 --- a/src/test/prevector_tests.cpp +++ b/src/test/prevector_tests.cpp @@ -189,8 +189,8 @@ public: prevector_tester() { SeedInsecureRand(); - rand_seed = insecure_rand_seed; - rand_cache = insecure_rand_ctx; + rand_seed = InsecureRand256(); + rand_cache = FastRandomContext(rand_seed); } }; diff --git a/src/test/random_tests.cpp b/src/test/random_tests.cpp index 679e857ce6..1057d09471 100644 --- a/src/test/random_tests.cpp +++ b/src/test/random_tests.cpp @@ -38,11 +38,18 @@ BOOST_AUTO_TEST_CASE(fastrandom_tests) BOOST_CHECK(ctx1.randbytes(50) == ctx2.randbytes(50)); // Check that a nondeterministic ones are not - FastRandomContext ctx3; - FastRandomContext ctx4; - BOOST_CHECK(ctx3.rand64() != ctx4.rand64()); // extremely unlikely to be equal - BOOST_CHECK(ctx3.rand256() != ctx4.rand256()); - BOOST_CHECK(ctx3.randbytes(7) != ctx4.randbytes(7)); + { + FastRandomContext ctx3, ctx4; + BOOST_CHECK(ctx3.rand64() != ctx4.rand64()); // extremely unlikely to be equal + } + { + FastRandomContext ctx3, ctx4; + BOOST_CHECK(ctx3.rand256() != ctx4.rand256()); + } + { + FastRandomContext ctx3, ctx4; + BOOST_CHECK(ctx3.randbytes(7) != ctx4.randbytes(7)); + } } BOOST_AUTO_TEST_CASE(fastrandom_randbits) @@ -75,8 +82,42 @@ BOOST_AUTO_TEST_CASE(stdrandom_test) for (int j = 1; j <= 10; ++j) { BOOST_CHECK(std::find(test.begin(), test.end(), j) != test.end()); } + Shuffle(test.begin(), test.end(), ctx); + for (int j = 1; j <= 10; ++j) { + BOOST_CHECK(std::find(test.begin(), test.end(), j) != test.end()); + } } } +/** Test that Shuffle reaches every permutation with equal probability. */ +BOOST_AUTO_TEST_CASE(shuffle_stat_test) +{ + FastRandomContext ctx(true); + uint32_t counts[5 * 5 * 5 * 5 * 5] = {0}; + for (int i = 0; i < 12000; ++i) { + int data[5] = {0, 1, 2, 3, 4}; + Shuffle(std::begin(data), std::end(data), ctx); + int pos = data[0] + data[1] * 5 + data[2] * 25 + data[3] * 125 + data[4] * 625; + ++counts[pos]; + } + unsigned int sum = 0; + double chi_score = 0.0; + for (int i = 0; i < 5 * 5 * 5 * 5 * 5; ++i) { + int i1 = i % 5, i2 = (i / 5) % 5, i3 = (i / 25) % 5, i4 = (i / 125) % 5, i5 = i / 625; + uint32_t count = counts[i]; + if (i1 == i2 || i1 == i3 || i1 == i4 || i1 == i5 || i2 == i3 || i2 == i4 || i2 == i5 || i3 == i4 || i3 == i5 || i4 == i5) { + BOOST_CHECK(count == 0); + } else { + chi_score += ((count - 100.0) * (count - 100.0)) / 100.0; + BOOST_CHECK(count > 50); + BOOST_CHECK(count < 150); + sum += count; + } + } + BOOST_CHECK(chi_score > 58.1411); // 99.9999% confidence interval + BOOST_CHECK(chi_score < 210.275); + BOOST_CHECK_EQUAL(sum, 12000); +} + BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/rpc_tests.cpp b/src/test/rpc_tests.cpp index a49796d6f4..ff48398925 100644 --- a/src/test/rpc_tests.cpp +++ b/src/test/rpc_tests.cpp @@ -4,8 +4,11 @@ #include <rpc/server.h> #include <rpc/client.h> +#include <rpc/util.h> #include <core_io.h> +#include <init.h> +#include <interfaces/chain.h> #include <key_io.h> #include <netbase.h> @@ -112,19 +115,20 @@ BOOST_AUTO_TEST_CASE(rpc_rawsign) std::string notsigned = r.get_str(); std::string privkey1 = "\"KzsXybp9jX64P5ekX1KUxRQ79Jht9uzW7LorgwE65i5rWACL6LQe\""; std::string privkey2 = "\"Kyhdf5LuKTRx4ge69ybABsiUAWjVRK4XGxAKk2FQLp2HjGMy87Z4\""; + InitInterfaces interfaces; + interfaces.chain = interfaces::MakeChain(); + g_rpc_interfaces = &interfaces; r = CallRPC(std::string("signrawtransactionwithkey ")+notsigned+" [] "+prevout); BOOST_CHECK(find_value(r.get_obj(), "complete").get_bool() == false); r = CallRPC(std::string("signrawtransactionwithkey ")+notsigned+" ["+privkey1+","+privkey2+"] "+prevout); BOOST_CHECK(find_value(r.get_obj(), "complete").get_bool() == true); + g_rpc_interfaces = nullptr; } BOOST_AUTO_TEST_CASE(rpc_createraw_op_return) { BOOST_CHECK_NO_THROW(CallRPC("createrawtransaction [{\"txid\":\"a3b807410df0b60fcb9736768df5823938b2f838694939ba45f3c0a1bff150ed\",\"vout\":0}] {\"data\":\"68656c6c6f776f726c64\"}")); - // Allow more than one data transaction output - BOOST_CHECK_NO_THROW(CallRPC("createrawtransaction [{\"txid\":\"a3b807410df0b60fcb9736768df5823938b2f838694939ba45f3c0a1bff150ed\",\"vout\":0}] {\"data\":\"68656c6c6f776f726c64\",\"data\":\"68656c6c6f776f726c64\"}")); - // Key not "data" (bad address) BOOST_CHECK_THROW(CallRPC("createrawtransaction [{\"txid\":\"a3b807410df0b60fcb9736768df5823938b2f838694939ba45f3c0a1bff150ed\",\"vout\":0}] {\"somedata\":\"68656c6c6f776f726c64\"}"), std::runtime_error); diff --git a/src/test/scheduler_tests.cpp b/src/test/scheduler_tests.cpp index 12ec00ce02..100d65b779 100644 --- a/src/test/scheduler_tests.cpp +++ b/src/test/scheduler_tests.cpp @@ -7,7 +7,6 @@ #include <test/test_bitcoin.h> -#include <boost/bind.hpp> #include <boost/thread.hpp> #include <boost/test/unit_test.hpp> @@ -21,7 +20,7 @@ static void microTask(CScheduler& s, boost::mutex& mutex, int& counter, int delt } boost::chrono::system_clock::time_point noTime = boost::chrono::system_clock::time_point::min(); if (rescheduleTime != noTime) { - CScheduler::Function f = boost::bind(µTask, boost::ref(s), boost::ref(mutex), boost::ref(counter), -delta + 1, noTime); + CScheduler::Function f = std::bind(µTask, std::ref(s), std::ref(mutex), std::ref(counter), -delta + 1, noTime); s.schedule(f, rescheduleTime); } } @@ -69,8 +68,8 @@ BOOST_AUTO_TEST_CASE(manythreads) boost::chrono::system_clock::time_point t = now + boost::chrono::microseconds(randomMsec(rng)); boost::chrono::system_clock::time_point tReschedule = now + boost::chrono::microseconds(500 + randomMsec(rng)); int whichCounter = zeroToNine(rng); - CScheduler::Function f = boost::bind(µTask, boost::ref(microTasks), - boost::ref(counterMutex[whichCounter]), boost::ref(counter[whichCounter]), + CScheduler::Function f = std::bind(µTask, std::ref(microTasks), + std::ref(counterMutex[whichCounter]), std::ref(counter[whichCounter]), randomDelta(rng), tReschedule); microTasks.schedule(f, t); } @@ -82,20 +81,20 @@ BOOST_AUTO_TEST_CASE(manythreads) // As soon as these are created they will start running and servicing the queue boost::thread_group microThreads; for (int i = 0; i < 5; i++) - microThreads.create_thread(boost::bind(&CScheduler::serviceQueue, µTasks)); + microThreads.create_thread(std::bind(&CScheduler::serviceQueue, µTasks)); MicroSleep(600); now = boost::chrono::system_clock::now(); // More threads and more tasks: for (int i = 0; i < 5; i++) - microThreads.create_thread(boost::bind(&CScheduler::serviceQueue, µTasks)); + microThreads.create_thread(std::bind(&CScheduler::serviceQueue, µTasks)); for (int i = 0; i < 100; i++) { boost::chrono::system_clock::time_point t = now + boost::chrono::microseconds(randomMsec(rng)); boost::chrono::system_clock::time_point tReschedule = now + boost::chrono::microseconds(500 + randomMsec(rng)); int whichCounter = zeroToNine(rng); - CScheduler::Function f = boost::bind(µTask, boost::ref(microTasks), - boost::ref(counterMutex[whichCounter]), boost::ref(counter[whichCounter]), + CScheduler::Function f = std::bind(µTask, std::ref(microTasks), + std::ref(counterMutex[whichCounter]), std::ref(counter[whichCounter]), randomDelta(rng), tReschedule); microTasks.schedule(f, t); } @@ -126,7 +125,7 @@ BOOST_AUTO_TEST_CASE(singlethreadedscheduler_ordered) // if they don't we'll get out of order behaviour boost::thread_group threads; for (int i = 0; i < 5; ++i) { - threads.create_thread(boost::bind(&CScheduler::serviceQueue, &scheduler)); + threads.create_thread(std::bind(&CScheduler::serviceQueue, &scheduler)); } // these are not atomic, if SinglethreadedSchedulerClient prevents diff --git a/src/test/script_p2sh_tests.cpp b/src/test/script_p2sh_tests.cpp index 5b10935302..3a2a11ef98 100644 --- a/src/test/script_p2sh_tests.cpp +++ b/src/test/script_p2sh_tests.cpp @@ -61,7 +61,7 @@ BOOST_AUTO_TEST_CASE(sign) for (int i = 0; i < 4; i++) { key[i].MakeNewKey(true); - keystore.AddKey(key[i]); + BOOST_CHECK(keystore.AddKey(key[i])); } // 8 Scripts: checking all combinations of @@ -74,7 +74,7 @@ BOOST_AUTO_TEST_CASE(sign) CScript evalScripts[4]; for (int i = 0; i < 4; i++) { - keystore.AddCScript(standardScripts[i]); + BOOST_CHECK(keystore.AddCScript(standardScripts[i])); evalScripts[i] = GetScriptForDestination(CScriptID(standardScripts[i])); } @@ -88,7 +88,7 @@ BOOST_AUTO_TEST_CASE(sign) txFrom.vout[i+4].scriptPubKey = standardScripts[i]; txFrom.vout[i+4].nValue = COIN; } - BOOST_CHECK(IsStandardTx(txFrom, reason)); + BOOST_CHECK(IsStandardTx(CTransaction(txFrom), reason)); CMutableTransaction txTo[8]; // Spending transactions for (int i = 0; i < 8; i++) @@ -102,7 +102,7 @@ BOOST_AUTO_TEST_CASE(sign) } for (int i = 0; i < 8; i++) { - BOOST_CHECK_MESSAGE(SignSignature(keystore, txFrom, txTo[i], 0, SIGHASH_ALL), strprintf("SignSignature %d", i)); + BOOST_CHECK_MESSAGE(SignSignature(keystore, CTransaction(txFrom), txTo[i], 0, SIGHASH_ALL), strprintf("SignSignature %d", i)); } // All of the above should be OK, and the txTos have valid signatures // Check to make sure signature verification fails if we use the wrong ScriptSig: @@ -112,7 +112,7 @@ BOOST_AUTO_TEST_CASE(sign) { CScript sigSave = txTo[i].vin[0].scriptSig; txTo[i].vin[0].scriptSig = txTo[j].vin[0].scriptSig; - bool sigOK = CScriptCheck(txFrom.vout[txTo[i].vin[0].prevout.n], txTo[i], 0, SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_STRICTENC, false, &txdata)(); + bool sigOK = CScriptCheck(txFrom.vout[txTo[i].vin[0].prevout.n], CTransaction(txTo[i]), 0, SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_STRICTENC, false, &txdata)(); if (i == j) BOOST_CHECK_MESSAGE(sigOK, strprintf("VerifySignature %d %d", i, j)); else @@ -159,7 +159,7 @@ BOOST_AUTO_TEST_CASE(set) for (int i = 0; i < 4; i++) { key[i].MakeNewKey(true); - keystore.AddKey(key[i]); + BOOST_CHECK(keystore.AddKey(key[i])); keys.push_back(key[i].GetPubKey()); } @@ -173,7 +173,7 @@ BOOST_AUTO_TEST_CASE(set) for (int i = 0; i < 4; i++) { outer[i] = GetScriptForDestination(CScriptID(inner[i])); - keystore.AddCScript(inner[i]); + BOOST_CHECK(keystore.AddCScript(inner[i])); } CMutableTransaction txFrom; // Funding transaction: @@ -184,7 +184,7 @@ BOOST_AUTO_TEST_CASE(set) txFrom.vout[i].scriptPubKey = outer[i]; txFrom.vout[i].nValue = CENT; } - BOOST_CHECK(IsStandardTx(txFrom, reason)); + BOOST_CHECK(IsStandardTx(CTransaction(txFrom), reason)); CMutableTransaction txTo[4]; // Spending transactions for (int i = 0; i < 4; i++) @@ -199,8 +199,8 @@ BOOST_AUTO_TEST_CASE(set) } for (int i = 0; i < 4; i++) { - BOOST_CHECK_MESSAGE(SignSignature(keystore, txFrom, txTo[i], 0, SIGHASH_ALL), strprintf("SignSignature %d", i)); - BOOST_CHECK_MESSAGE(IsStandardTx(txTo[i], reason), strprintf("txTo[%d].IsStandard", i)); + BOOST_CHECK_MESSAGE(SignSignature(keystore, CTransaction(txFrom), txTo[i], 0, SIGHASH_ALL), strprintf("SignSignature %d", i)); + BOOST_CHECK_MESSAGE(IsStandardTx(CTransaction(txTo[i]), reason), strprintf("txTo[%d].IsStandard", i)); } } @@ -213,14 +213,22 @@ BOOST_AUTO_TEST_CASE(is) BOOST_CHECK(p2sh.IsPayToScriptHash()); // Not considered pay-to-script-hash if using one of the OP_PUSHDATA opcodes: - static const unsigned char direct[] = { OP_HASH160, 20, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, OP_EQUAL }; - BOOST_CHECK(CScript(direct, direct+sizeof(direct)).IsPayToScriptHash()); - static const unsigned char pushdata1[] = { OP_HASH160, OP_PUSHDATA1, 20, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, OP_EQUAL }; - BOOST_CHECK(!CScript(pushdata1, pushdata1+sizeof(pushdata1)).IsPayToScriptHash()); - static const unsigned char pushdata2[] = { OP_HASH160, OP_PUSHDATA2, 20,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, OP_EQUAL }; - BOOST_CHECK(!CScript(pushdata2, pushdata2+sizeof(pushdata2)).IsPayToScriptHash()); - static const unsigned char pushdata4[] = { OP_HASH160, OP_PUSHDATA4, 20,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, OP_EQUAL }; - BOOST_CHECK(!CScript(pushdata4, pushdata4+sizeof(pushdata4)).IsPayToScriptHash()); + std::vector<unsigned char> direct = {OP_HASH160, 20}; + direct.insert(direct.end(), 20, 0); + direct.push_back(OP_EQUAL); + BOOST_CHECK(CScript(direct.begin(), direct.end()).IsPayToScriptHash()); + std::vector<unsigned char> pushdata1 = {OP_HASH160, OP_PUSHDATA1, 20}; + pushdata1.insert(pushdata1.end(), 20, 0); + pushdata1.push_back(OP_EQUAL); + BOOST_CHECK(!CScript(pushdata1.begin(), pushdata1.end()).IsPayToScriptHash()); + std::vector<unsigned char> pushdata2 = {OP_HASH160, 20, 0}; + pushdata2.insert(pushdata2.end(), 20, 0); + pushdata2.push_back(OP_EQUAL); + BOOST_CHECK(!CScript(pushdata2.begin(), pushdata2.end()).IsPayToScriptHash()); + std::vector<unsigned char> pushdata4 = {OP_HASH160, 20, 0, 0, 0}; + pushdata4.insert(pushdata4.end(), 20, 0); + pushdata4.push_back(OP_EQUAL); + BOOST_CHECK(!CScript(pushdata4.begin(), pushdata4.end()).IsPayToScriptHash()); CScript not_p2sh; BOOST_CHECK(!not_p2sh.IsPayToScriptHash()); @@ -266,7 +274,7 @@ BOOST_AUTO_TEST_CASE(AreInputsStandard) for (int i = 0; i < 6; i++) { key[i].MakeNewKey(true); - keystore.AddKey(key[i]); + BOOST_CHECK(keystore.AddKey(key[i])); } for (int i = 0; i < 3; i++) keys.push_back(key[i].GetPubKey()); @@ -276,7 +284,7 @@ BOOST_AUTO_TEST_CASE(AreInputsStandard) // First three are standard: CScript pay1 = GetScriptForDestination(key[0].GetPubKey().GetID()); - keystore.AddCScript(pay1); + BOOST_CHECK(keystore.AddCScript(pay1)); CScript pay1of3 = GetScriptForMultisig(1, keys); txFrom.vout[0].scriptPubKey = GetScriptForDestination(CScriptID(pay1)); // P2SH (OP_CHECKSIG) @@ -293,7 +301,7 @@ BOOST_AUTO_TEST_CASE(AreInputsStandard) oneAndTwo << OP_3 << OP_CHECKMULTISIGVERIFY; oneAndTwo << OP_2 << ToByteVector(key[3].GetPubKey()) << ToByteVector(key[4].GetPubKey()) << ToByteVector(key[5].GetPubKey()); oneAndTwo << OP_3 << OP_CHECKMULTISIG; - keystore.AddCScript(oneAndTwo); + BOOST_CHECK(keystore.AddCScript(oneAndTwo)); txFrom.vout[3].scriptPubKey = GetScriptForDestination(CScriptID(oneAndTwo)); txFrom.vout[3].nValue = 4000; @@ -302,21 +310,21 @@ BOOST_AUTO_TEST_CASE(AreInputsStandard) for (unsigned i = 0; i < MAX_P2SH_SIGOPS; i++) fifteenSigops << ToByteVector(key[i%3].GetPubKey()); fifteenSigops << OP_15 << OP_CHECKMULTISIG; - keystore.AddCScript(fifteenSigops); + BOOST_CHECK(keystore.AddCScript(fifteenSigops)); txFrom.vout[4].scriptPubKey = GetScriptForDestination(CScriptID(fifteenSigops)); txFrom.vout[4].nValue = 5000; // vout[5/6] are non-standard because they exceed MAX_P2SH_SIGOPS CScript sixteenSigops; sixteenSigops << OP_16 << OP_CHECKMULTISIG; - keystore.AddCScript(sixteenSigops); + BOOST_CHECK(keystore.AddCScript(sixteenSigops)); txFrom.vout[5].scriptPubKey = GetScriptForDestination(CScriptID(sixteenSigops)); txFrom.vout[5].nValue = 5000; CScript twentySigops; twentySigops << OP_CHECKMULTISIG; - keystore.AddCScript(twentySigops); + BOOST_CHECK(keystore.AddCScript(twentySigops)); txFrom.vout[6].scriptPubKey = GetScriptForDestination(CScriptID(twentySigops)); txFrom.vout[6].nValue = 6000; - AddCoins(coins, txFrom, 0); + AddCoins(coins, CTransaction(txFrom), 0); CMutableTransaction txTo; txTo.vout.resize(1); @@ -328,18 +336,18 @@ BOOST_AUTO_TEST_CASE(AreInputsStandard) txTo.vin[i].prevout.n = i; txTo.vin[i].prevout.hash = txFrom.GetHash(); } - BOOST_CHECK(SignSignature(keystore, txFrom, txTo, 0, SIGHASH_ALL)); - BOOST_CHECK(SignSignature(keystore, txFrom, txTo, 1, SIGHASH_ALL)); - BOOST_CHECK(SignSignature(keystore, txFrom, txTo, 2, SIGHASH_ALL)); + BOOST_CHECK(SignSignature(keystore, CTransaction(txFrom), txTo, 0, SIGHASH_ALL)); + BOOST_CHECK(SignSignature(keystore, CTransaction(txFrom), txTo, 1, SIGHASH_ALL)); + BOOST_CHECK(SignSignature(keystore, CTransaction(txFrom), txTo, 2, SIGHASH_ALL)); // SignSignature doesn't know how to sign these. We're // not testing validating signatures, so just create // dummy signatures that DO include the correct P2SH scripts: txTo.vin[3].scriptSig << OP_11 << OP_11 << std::vector<unsigned char>(oneAndTwo.begin(), oneAndTwo.end()); txTo.vin[4].scriptSig << std::vector<unsigned char>(fifteenSigops.begin(), fifteenSigops.end()); - BOOST_CHECK(::AreInputsStandard(txTo, coins)); + BOOST_CHECK(::AreInputsStandard(CTransaction(txTo), coins)); // 22 P2SH sigops for all inputs (1 for vin[0], 6 for vin[3], 15 for vin[4] - BOOST_CHECK_EQUAL(GetP2SHSigOpCount(txTo, coins), 22U); + BOOST_CHECK_EQUAL(GetP2SHSigOpCount(CTransaction(txTo), coins), 22U); CMutableTransaction txToNonStd1; txToNonStd1.vout.resize(1); @@ -350,8 +358,8 @@ BOOST_AUTO_TEST_CASE(AreInputsStandard) txToNonStd1.vin[0].prevout.hash = txFrom.GetHash(); txToNonStd1.vin[0].scriptSig << std::vector<unsigned char>(sixteenSigops.begin(), sixteenSigops.end()); - BOOST_CHECK(!::AreInputsStandard(txToNonStd1, coins)); - BOOST_CHECK_EQUAL(GetP2SHSigOpCount(txToNonStd1, coins), 16U); + BOOST_CHECK(!::AreInputsStandard(CTransaction(txToNonStd1), coins)); + BOOST_CHECK_EQUAL(GetP2SHSigOpCount(CTransaction(txToNonStd1), coins), 16U); CMutableTransaction txToNonStd2; txToNonStd2.vout.resize(1); @@ -362,8 +370,8 @@ BOOST_AUTO_TEST_CASE(AreInputsStandard) txToNonStd2.vin[0].prevout.hash = txFrom.GetHash(); txToNonStd2.vin[0].scriptSig << std::vector<unsigned char>(twentySigops.begin(), twentySigops.end()); - BOOST_CHECK(!::AreInputsStandard(txToNonStd2, coins)); - BOOST_CHECK_EQUAL(GetP2SHSigOpCount(txToNonStd2, coins), 20U); + BOOST_CHECK(!::AreInputsStandard(CTransaction(txToNonStd2), coins)); + BOOST_CHECK_EQUAL(GetP2SHSigOpCount(CTransaction(txToNonStd2), coins), 20U); } BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/script_standard_tests.cpp b/src/test/script_standard_tests.cpp index b3e4b12918..bde82018c7 100644 --- a/src/test/script_standard_tests.cpp +++ b/src/test/script_standard_tests.cpp @@ -398,7 +398,7 @@ BOOST_AUTO_TEST_CASE(script_standard_IsMine) BOOST_CHECK_EQUAL(result, ISMINE_NO); // Keystore has key - keystore.AddKey(keys[0]); + BOOST_CHECK(keystore.AddKey(keys[0])); result = IsMine(keystore, scriptPubKey); BOOST_CHECK_EQUAL(result, ISMINE_SPENDABLE); } @@ -413,7 +413,7 @@ BOOST_AUTO_TEST_CASE(script_standard_IsMine) BOOST_CHECK_EQUAL(result, ISMINE_NO); // Keystore has key - keystore.AddKey(uncompressedKey); + BOOST_CHECK(keystore.AddKey(uncompressedKey)); result = IsMine(keystore, scriptPubKey); BOOST_CHECK_EQUAL(result, ISMINE_SPENDABLE); } @@ -428,7 +428,7 @@ BOOST_AUTO_TEST_CASE(script_standard_IsMine) BOOST_CHECK_EQUAL(result, ISMINE_NO); // Keystore has key - keystore.AddKey(keys[0]); + BOOST_CHECK(keystore.AddKey(keys[0])); result = IsMine(keystore, scriptPubKey); BOOST_CHECK_EQUAL(result, ISMINE_SPENDABLE); } @@ -443,7 +443,7 @@ BOOST_AUTO_TEST_CASE(script_standard_IsMine) BOOST_CHECK_EQUAL(result, ISMINE_NO); // Keystore has key - keystore.AddKey(uncompressedKey); + BOOST_CHECK(keystore.AddKey(uncompressedKey)); result = IsMine(keystore, scriptPubKey); BOOST_CHECK_EQUAL(result, ISMINE_SPENDABLE); } @@ -460,12 +460,12 @@ BOOST_AUTO_TEST_CASE(script_standard_IsMine) BOOST_CHECK_EQUAL(result, ISMINE_NO); // Keystore has redeemScript but no key - keystore.AddCScript(redeemScript); + BOOST_CHECK(keystore.AddCScript(redeemScript)); result = IsMine(keystore, scriptPubKey); BOOST_CHECK_EQUAL(result, ISMINE_NO); // Keystore has redeemScript and key - keystore.AddKey(keys[0]); + BOOST_CHECK(keystore.AddKey(keys[0])); result = IsMine(keystore, scriptPubKey); BOOST_CHECK_EQUAL(result, ISMINE_SPENDABLE); } @@ -478,10 +478,10 @@ BOOST_AUTO_TEST_CASE(script_standard_IsMine) CScript redeemscript = GetScriptForDestination(CScriptID(redeemscript_inner)); scriptPubKey = GetScriptForDestination(CScriptID(redeemscript)); - keystore.AddCScript(redeemscript); - keystore.AddCScript(redeemscript_inner); - keystore.AddCScript(scriptPubKey); - keystore.AddKey(keys[0]); + BOOST_CHECK(keystore.AddCScript(redeemscript)); + BOOST_CHECK(keystore.AddCScript(redeemscript_inner)); + BOOST_CHECK(keystore.AddCScript(scriptPubKey)); + BOOST_CHECK(keystore.AddKey(keys[0])); result = IsMine(keystore, scriptPubKey); BOOST_CHECK_EQUAL(result, ISMINE_NO); } @@ -494,10 +494,10 @@ BOOST_AUTO_TEST_CASE(script_standard_IsMine) CScript witnessscript = GetScriptForDestination(CScriptID(redeemscript)); scriptPubKey = GetScriptForDestination(WitnessV0ScriptHash(witnessscript)); - keystore.AddCScript(witnessscript); - keystore.AddCScript(redeemscript); - keystore.AddCScript(scriptPubKey); - keystore.AddKey(keys[0]); + BOOST_CHECK(keystore.AddCScript(witnessscript)); + BOOST_CHECK(keystore.AddCScript(redeemscript)); + BOOST_CHECK(keystore.AddCScript(scriptPubKey)); + BOOST_CHECK(keystore.AddKey(keys[0])); result = IsMine(keystore, scriptPubKey); BOOST_CHECK_EQUAL(result, ISMINE_NO); } @@ -509,9 +509,9 @@ BOOST_AUTO_TEST_CASE(script_standard_IsMine) CScript witnessscript = GetScriptForDestination(WitnessV0KeyHash(pubkeys[0].GetID())); scriptPubKey = GetScriptForDestination(WitnessV0ScriptHash(witnessscript)); - keystore.AddCScript(witnessscript); - keystore.AddCScript(scriptPubKey); - keystore.AddKey(keys[0]); + BOOST_CHECK(keystore.AddCScript(witnessscript)); + BOOST_CHECK(keystore.AddCScript(scriptPubKey)); + BOOST_CHECK(keystore.AddKey(keys[0])); result = IsMine(keystore, scriptPubKey); BOOST_CHECK_EQUAL(result, ISMINE_NO); } @@ -524,10 +524,10 @@ BOOST_AUTO_TEST_CASE(script_standard_IsMine) CScript witnessscript = GetScriptForDestination(WitnessV0ScriptHash(witnessscript_inner)); scriptPubKey = GetScriptForDestination(WitnessV0ScriptHash(witnessscript)); - keystore.AddCScript(witnessscript_inner); - keystore.AddCScript(witnessscript); - keystore.AddCScript(scriptPubKey); - keystore.AddKey(keys[0]); + BOOST_CHECK(keystore.AddCScript(witnessscript_inner)); + BOOST_CHECK(keystore.AddCScript(witnessscript)); + BOOST_CHECK(keystore.AddCScript(scriptPubKey)); + BOOST_CHECK(keystore.AddKey(keys[0])); result = IsMine(keystore, scriptPubKey); BOOST_CHECK_EQUAL(result, ISMINE_NO); } @@ -535,12 +535,12 @@ BOOST_AUTO_TEST_CASE(script_standard_IsMine) // P2WPKH compressed { CBasicKeyStore keystore; - keystore.AddKey(keys[0]); + BOOST_CHECK(keystore.AddKey(keys[0])); scriptPubKey = GetScriptForDestination(WitnessV0KeyHash(pubkeys[0].GetID())); // Keystore implicitly has key and P2SH redeemScript - keystore.AddCScript(scriptPubKey); + BOOST_CHECK(keystore.AddCScript(scriptPubKey)); result = IsMine(keystore, scriptPubKey); BOOST_CHECK_EQUAL(result, ISMINE_SPENDABLE); } @@ -548,7 +548,7 @@ BOOST_AUTO_TEST_CASE(script_standard_IsMine) // P2WPKH uncompressed { CBasicKeyStore keystore; - keystore.AddKey(uncompressedKey); + BOOST_CHECK(keystore.AddKey(uncompressedKey)); scriptPubKey = GetScriptForDestination(WitnessV0KeyHash(uncompressedPubkey.GetID())); @@ -557,7 +557,7 @@ BOOST_AUTO_TEST_CASE(script_standard_IsMine) BOOST_CHECK_EQUAL(result, ISMINE_NO); // Keystore has key and P2SH redeemScript - keystore.AddCScript(scriptPubKey); + BOOST_CHECK(keystore.AddCScript(scriptPubKey)); result = IsMine(keystore, scriptPubKey); BOOST_CHECK_EQUAL(result, ISMINE_NO); } @@ -573,19 +573,19 @@ BOOST_AUTO_TEST_CASE(script_standard_IsMine) BOOST_CHECK_EQUAL(result, ISMINE_NO); // Keystore has 1/2 keys - keystore.AddKey(uncompressedKey); + BOOST_CHECK(keystore.AddKey(uncompressedKey)); result = IsMine(keystore, scriptPubKey); BOOST_CHECK_EQUAL(result, ISMINE_NO); // Keystore has 2/2 keys - keystore.AddKey(keys[1]); + BOOST_CHECK(keystore.AddKey(keys[1])); result = IsMine(keystore, scriptPubKey); BOOST_CHECK_EQUAL(result, ISMINE_NO); // Keystore has 2/2 keys and the script - keystore.AddCScript(scriptPubKey); + BOOST_CHECK(keystore.AddCScript(scriptPubKey)); result = IsMine(keystore, scriptPubKey); BOOST_CHECK_EQUAL(result, ISMINE_NO); @@ -594,8 +594,8 @@ BOOST_AUTO_TEST_CASE(script_standard_IsMine) // P2SH multisig { CBasicKeyStore keystore; - keystore.AddKey(uncompressedKey); - keystore.AddKey(keys[1]); + BOOST_CHECK(keystore.AddKey(uncompressedKey)); + BOOST_CHECK(keystore.AddKey(keys[1])); CScript redeemScript = GetScriptForMultisig(2, {uncompressedPubkey, pubkeys[1]}); scriptPubKey = GetScriptForDestination(CScriptID(redeemScript)); @@ -605,7 +605,7 @@ BOOST_AUTO_TEST_CASE(script_standard_IsMine) BOOST_CHECK_EQUAL(result, ISMINE_NO); // Keystore has redeemScript - keystore.AddCScript(redeemScript); + BOOST_CHECK(keystore.AddCScript(redeemScript)); result = IsMine(keystore, scriptPubKey); BOOST_CHECK_EQUAL(result, ISMINE_SPENDABLE); } @@ -613,8 +613,8 @@ BOOST_AUTO_TEST_CASE(script_standard_IsMine) // P2WSH multisig with compressed keys { CBasicKeyStore keystore; - keystore.AddKey(keys[0]); - keystore.AddKey(keys[1]); + BOOST_CHECK(keystore.AddKey(keys[0])); + BOOST_CHECK(keystore.AddKey(keys[1])); CScript witnessScript = GetScriptForMultisig(2, {pubkeys[0], pubkeys[1]}); scriptPubKey = GetScriptForDestination(WitnessV0ScriptHash(witnessScript)); @@ -624,12 +624,12 @@ BOOST_AUTO_TEST_CASE(script_standard_IsMine) BOOST_CHECK_EQUAL(result, ISMINE_NO); // Keystore has keys and witnessScript, but no P2SH redeemScript - keystore.AddCScript(witnessScript); + BOOST_CHECK(keystore.AddCScript(witnessScript)); result = IsMine(keystore, scriptPubKey); BOOST_CHECK_EQUAL(result, ISMINE_NO); // Keystore has keys, witnessScript, P2SH redeemScript - keystore.AddCScript(scriptPubKey); + BOOST_CHECK(keystore.AddCScript(scriptPubKey)); result = IsMine(keystore, scriptPubKey); BOOST_CHECK_EQUAL(result, ISMINE_SPENDABLE); } @@ -637,8 +637,8 @@ BOOST_AUTO_TEST_CASE(script_standard_IsMine) // P2WSH multisig with uncompressed key { CBasicKeyStore keystore; - keystore.AddKey(uncompressedKey); - keystore.AddKey(keys[1]); + BOOST_CHECK(keystore.AddKey(uncompressedKey)); + BOOST_CHECK(keystore.AddKey(keys[1])); CScript witnessScript = GetScriptForMultisig(2, {uncompressedPubkey, pubkeys[1]}); scriptPubKey = GetScriptForDestination(WitnessV0ScriptHash(witnessScript)); @@ -648,12 +648,12 @@ BOOST_AUTO_TEST_CASE(script_standard_IsMine) BOOST_CHECK_EQUAL(result, ISMINE_NO); // Keystore has keys and witnessScript, but no P2SH redeemScript - keystore.AddCScript(witnessScript); + BOOST_CHECK(keystore.AddCScript(witnessScript)); result = IsMine(keystore, scriptPubKey); BOOST_CHECK_EQUAL(result, ISMINE_NO); // Keystore has keys, witnessScript, P2SH redeemScript - keystore.AddCScript(scriptPubKey); + BOOST_CHECK(keystore.AddCScript(scriptPubKey)); result = IsMine(keystore, scriptPubKey); BOOST_CHECK_EQUAL(result, ISMINE_NO); } @@ -671,14 +671,14 @@ BOOST_AUTO_TEST_CASE(script_standard_IsMine) BOOST_CHECK_EQUAL(result, ISMINE_NO); // Keystore has witnessScript and P2SH redeemScript, but no keys - keystore.AddCScript(redeemScript); - keystore.AddCScript(witnessScript); + BOOST_CHECK(keystore.AddCScript(redeemScript)); + BOOST_CHECK(keystore.AddCScript(witnessScript)); result = IsMine(keystore, scriptPubKey); BOOST_CHECK_EQUAL(result, ISMINE_NO); // Keystore has keys, witnessScript, P2SH redeemScript - keystore.AddKey(keys[0]); - keystore.AddKey(keys[1]); + BOOST_CHECK(keystore.AddKey(keys[0])); + BOOST_CHECK(keystore.AddKey(keys[1])); result = IsMine(keystore, scriptPubKey); BOOST_CHECK_EQUAL(result, ISMINE_SPENDABLE); } @@ -686,7 +686,7 @@ BOOST_AUTO_TEST_CASE(script_standard_IsMine) // OP_RETURN { CBasicKeyStore keystore; - keystore.AddKey(keys[0]); + BOOST_CHECK(keystore.AddKey(keys[0])); scriptPubKey.clear(); scriptPubKey << OP_RETURN << ToByteVector(pubkeys[0]); @@ -698,7 +698,7 @@ BOOST_AUTO_TEST_CASE(script_standard_IsMine) // witness unspendable { CBasicKeyStore keystore; - keystore.AddKey(keys[0]); + BOOST_CHECK(keystore.AddKey(keys[0])); scriptPubKey.clear(); scriptPubKey << OP_0 << ToByteVector(ParseHex("aabb")); @@ -710,7 +710,7 @@ BOOST_AUTO_TEST_CASE(script_standard_IsMine) // witness unknown { CBasicKeyStore keystore; - keystore.AddKey(keys[0]); + BOOST_CHECK(keystore.AddKey(keys[0])); scriptPubKey.clear(); scriptPubKey << OP_16 << ToByteVector(ParseHex("aabb")); @@ -722,7 +722,7 @@ BOOST_AUTO_TEST_CASE(script_standard_IsMine) // Nonstandard { CBasicKeyStore keystore; - keystore.AddKey(keys[0]); + BOOST_CHECK(keystore.AddKey(keys[0])); scriptPubKey.clear(); scriptPubKey << OP_9 << OP_ADD << OP_11 << OP_EQUAL; diff --git a/src/test/script_tests.cpp b/src/test/script_tests.cpp index 67c377778f..87c3e74df0 100644 --- a/src/test/script_tests.cpp +++ b/src/test/script_tests.cpp @@ -10,8 +10,8 @@ #include <script/script.h> #include <script/script_error.h> #include <script/sign.h> -#include <util.h> -#include <utilstrencodings.h> +#include <util/system.h> +#include <util/strencodings.h> #include <test/test_bitcoin.h> #include <rpc/server.h> @@ -184,11 +184,12 @@ void DoTest(const CScript& scriptPubKey, const CScript& scriptSig, const CScript stream << tx2; int libconsensus_flags = flags & bitcoinconsensus_SCRIPT_FLAGS_VERIFY_ALL; if (libconsensus_flags == flags) { + int expectedSuccessCode = expect ? 1 : 0; if (flags & bitcoinconsensus_SCRIPT_FLAGS_VERIFY_WITNESS) { - BOOST_CHECK_MESSAGE(bitcoinconsensus_verify_script_with_amount(scriptPubKey.data(), scriptPubKey.size(), txCredit.vout[0].nValue, (const unsigned char*)&stream[0], stream.size(), 0, libconsensus_flags, nullptr) == expect, message); + BOOST_CHECK_MESSAGE(bitcoinconsensus_verify_script_with_amount(scriptPubKey.data(), scriptPubKey.size(), txCredit.vout[0].nValue, (const unsigned char*)&stream[0], stream.size(), 0, libconsensus_flags, nullptr) == expectedSuccessCode, message); } else { - BOOST_CHECK_MESSAGE(bitcoinconsensus_verify_script_with_amount(scriptPubKey.data(), scriptPubKey.size(), 0, (const unsigned char*)&stream[0], stream.size(), 0, libconsensus_flags, nullptr) == expect, message); - BOOST_CHECK_MESSAGE(bitcoinconsensus_verify_script(scriptPubKey.data(), scriptPubKey.size(), (const unsigned char*)&stream[0], stream.size(), 0, libconsensus_flags, nullptr) == expect,message); + BOOST_CHECK_MESSAGE(bitcoinconsensus_verify_script_with_amount(scriptPubKey.data(), scriptPubKey.size(), 0, (const unsigned char*)&stream[0], stream.size(), 0, libconsensus_flags, nullptr) == expectedSuccessCode, message); + BOOST_CHECK_MESSAGE(bitcoinconsensus_verify_script(scriptPubKey.data(), scriptPubKey.size(), (const unsigned char*)&stream[0], stream.size(), 0, libconsensus_flags, nullptr) == expectedSuccessCode, message); } } #endif @@ -1013,23 +1014,45 @@ BOOST_AUTO_TEST_CASE(script_PushData) ScriptError err; std::vector<std::vector<unsigned char> > directStack; - BOOST_CHECK(EvalScript(directStack, CScript(&direct[0], &direct[sizeof(direct)]), SCRIPT_VERIFY_P2SH, BaseSignatureChecker(), SigVersion::BASE, &err)); + BOOST_CHECK(EvalScript(directStack, CScript(direct, direct + sizeof(direct)), SCRIPT_VERIFY_P2SH, BaseSignatureChecker(), SigVersion::BASE, &err)); BOOST_CHECK_MESSAGE(err == SCRIPT_ERR_OK, ScriptErrorString(err)); std::vector<std::vector<unsigned char> > pushdata1Stack; - BOOST_CHECK(EvalScript(pushdata1Stack, CScript(&pushdata1[0], &pushdata1[sizeof(pushdata1)]), SCRIPT_VERIFY_P2SH, BaseSignatureChecker(), SigVersion::BASE, &err)); + BOOST_CHECK(EvalScript(pushdata1Stack, CScript(pushdata1, pushdata1 + sizeof(pushdata1)), SCRIPT_VERIFY_P2SH, BaseSignatureChecker(), SigVersion::BASE, &err)); BOOST_CHECK(pushdata1Stack == directStack); BOOST_CHECK_MESSAGE(err == SCRIPT_ERR_OK, ScriptErrorString(err)); std::vector<std::vector<unsigned char> > pushdata2Stack; - BOOST_CHECK(EvalScript(pushdata2Stack, CScript(&pushdata2[0], &pushdata2[sizeof(pushdata2)]), SCRIPT_VERIFY_P2SH, BaseSignatureChecker(), SigVersion::BASE, &err)); + BOOST_CHECK(EvalScript(pushdata2Stack, CScript(pushdata2, pushdata2 + sizeof(pushdata2)), SCRIPT_VERIFY_P2SH, BaseSignatureChecker(), SigVersion::BASE, &err)); BOOST_CHECK(pushdata2Stack == directStack); BOOST_CHECK_MESSAGE(err == SCRIPT_ERR_OK, ScriptErrorString(err)); std::vector<std::vector<unsigned char> > pushdata4Stack; - BOOST_CHECK(EvalScript(pushdata4Stack, CScript(&pushdata4[0], &pushdata4[sizeof(pushdata4)]), SCRIPT_VERIFY_P2SH, BaseSignatureChecker(), SigVersion::BASE, &err)); + BOOST_CHECK(EvalScript(pushdata4Stack, CScript(pushdata4, pushdata4 + sizeof(pushdata4)), SCRIPT_VERIFY_P2SH, BaseSignatureChecker(), SigVersion::BASE, &err)); BOOST_CHECK(pushdata4Stack == directStack); BOOST_CHECK_MESSAGE(err == SCRIPT_ERR_OK, ScriptErrorString(err)); + + const std::vector<unsigned char> pushdata1_trunc{OP_PUSHDATA1, 1}; + const std::vector<unsigned char> pushdata2_trunc{OP_PUSHDATA2, 1, 0}; + const std::vector<unsigned char> pushdata4_trunc{OP_PUSHDATA4, 1, 0, 0, 0}; + + std::vector<std::vector<unsigned char>> stack_ignore; + BOOST_CHECK(!EvalScript(stack_ignore, CScript(pushdata1_trunc.begin(), pushdata1_trunc.end()), SCRIPT_VERIFY_P2SH, BaseSignatureChecker(), SigVersion::BASE, &err)); + BOOST_CHECK_EQUAL(err, SCRIPT_ERR_BAD_OPCODE); + BOOST_CHECK(!EvalScript(stack_ignore, CScript(pushdata2_trunc.begin(), pushdata2_trunc.end()), SCRIPT_VERIFY_P2SH, BaseSignatureChecker(), SigVersion::BASE, &err)); + BOOST_CHECK_EQUAL(err, SCRIPT_ERR_BAD_OPCODE); + BOOST_CHECK(!EvalScript(stack_ignore, CScript(pushdata4_trunc.begin(), pushdata4_trunc.end()), SCRIPT_VERIFY_P2SH, BaseSignatureChecker(), SigVersion::BASE, &err)); + BOOST_CHECK_EQUAL(err, SCRIPT_ERR_BAD_OPCODE); +} + +BOOST_AUTO_TEST_CASE(script_cltv_truncated) +{ + const auto script_cltv_trunc = CScript() << OP_CHECKLOCKTIMEVERIFY; + + std::vector<std::vector<unsigned char>> stack_ignore; + ScriptError err; + BOOST_CHECK(!EvalScript(stack_ignore, script_cltv_trunc, SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY, BaseSignatureChecker(), SigVersion::BASE, &err)); + BOOST_CHECK_EQUAL(err, SCRIPT_ERR_INVALID_STACK_OPERATION); } static CScript @@ -1078,18 +1101,18 @@ BOOST_AUTO_TEST_CASE(script_CHECKMULTISIG12) const CTransaction txFrom12{BuildCreditingTransaction(scriptPubKey12)}; CMutableTransaction txTo12 = BuildSpendingTransaction(CScript(), CScriptWitness(), txFrom12); - CScript goodsig1 = sign_multisig(scriptPubKey12, key1, txTo12); + CScript goodsig1 = sign_multisig(scriptPubKey12, key1, CTransaction(txTo12)); BOOST_CHECK(VerifyScript(goodsig1, scriptPubKey12, nullptr, gFlags, MutableTransactionSignatureChecker(&txTo12, 0, txFrom12.vout[0].nValue), &err)); BOOST_CHECK_MESSAGE(err == SCRIPT_ERR_OK, ScriptErrorString(err)); txTo12.vout[0].nValue = 2; BOOST_CHECK(!VerifyScript(goodsig1, scriptPubKey12, nullptr, gFlags, MutableTransactionSignatureChecker(&txTo12, 0, txFrom12.vout[0].nValue), &err)); BOOST_CHECK_MESSAGE(err == SCRIPT_ERR_EVAL_FALSE, ScriptErrorString(err)); - CScript goodsig2 = sign_multisig(scriptPubKey12, key2, txTo12); + CScript goodsig2 = sign_multisig(scriptPubKey12, key2, CTransaction(txTo12)); BOOST_CHECK(VerifyScript(goodsig2, scriptPubKey12, nullptr, gFlags, MutableTransactionSignatureChecker(&txTo12, 0, txFrom12.vout[0].nValue), &err)); BOOST_CHECK_MESSAGE(err == SCRIPT_ERR_OK, ScriptErrorString(err)); - CScript badsig1 = sign_multisig(scriptPubKey12, key3, txTo12); + CScript badsig1 = sign_multisig(scriptPubKey12, key3, CTransaction(txTo12)); BOOST_CHECK(!VerifyScript(badsig1, scriptPubKey12, nullptr, gFlags, MutableTransactionSignatureChecker(&txTo12, 0, txFrom12.vout[0].nValue), &err)); BOOST_CHECK_MESSAGE(err == SCRIPT_ERR_EVAL_FALSE, ScriptErrorString(err)); } @@ -1111,54 +1134,54 @@ BOOST_AUTO_TEST_CASE(script_CHECKMULTISIG23) std::vector<CKey> keys; keys.push_back(key1); keys.push_back(key2); - CScript goodsig1 = sign_multisig(scriptPubKey23, keys, txTo23); + CScript goodsig1 = sign_multisig(scriptPubKey23, keys, CTransaction(txTo23)); BOOST_CHECK(VerifyScript(goodsig1, scriptPubKey23, nullptr, gFlags, MutableTransactionSignatureChecker(&txTo23, 0, txFrom23.vout[0].nValue), &err)); BOOST_CHECK_MESSAGE(err == SCRIPT_ERR_OK, ScriptErrorString(err)); keys.clear(); keys.push_back(key1); keys.push_back(key3); - CScript goodsig2 = sign_multisig(scriptPubKey23, keys, txTo23); + CScript goodsig2 = sign_multisig(scriptPubKey23, keys, CTransaction(txTo23)); BOOST_CHECK(VerifyScript(goodsig2, scriptPubKey23, nullptr, gFlags, MutableTransactionSignatureChecker(&txTo23, 0, txFrom23.vout[0].nValue), &err)); BOOST_CHECK_MESSAGE(err == SCRIPT_ERR_OK, ScriptErrorString(err)); keys.clear(); keys.push_back(key2); keys.push_back(key3); - CScript goodsig3 = sign_multisig(scriptPubKey23, keys, txTo23); + CScript goodsig3 = sign_multisig(scriptPubKey23, keys, CTransaction(txTo23)); BOOST_CHECK(VerifyScript(goodsig3, scriptPubKey23, nullptr, gFlags, MutableTransactionSignatureChecker(&txTo23, 0, txFrom23.vout[0].nValue), &err)); BOOST_CHECK_MESSAGE(err == SCRIPT_ERR_OK, ScriptErrorString(err)); keys.clear(); keys.push_back(key2); keys.push_back(key2); // Can't re-use sig - CScript badsig1 = sign_multisig(scriptPubKey23, keys, txTo23); + CScript badsig1 = sign_multisig(scriptPubKey23, keys, CTransaction(txTo23)); BOOST_CHECK(!VerifyScript(badsig1, scriptPubKey23, nullptr, gFlags, MutableTransactionSignatureChecker(&txTo23, 0, txFrom23.vout[0].nValue), &err)); BOOST_CHECK_MESSAGE(err == SCRIPT_ERR_EVAL_FALSE, ScriptErrorString(err)); keys.clear(); keys.push_back(key2); keys.push_back(key1); // sigs must be in correct order - CScript badsig2 = sign_multisig(scriptPubKey23, keys, txTo23); + CScript badsig2 = sign_multisig(scriptPubKey23, keys, CTransaction(txTo23)); BOOST_CHECK(!VerifyScript(badsig2, scriptPubKey23, nullptr, gFlags, MutableTransactionSignatureChecker(&txTo23, 0, txFrom23.vout[0].nValue), &err)); BOOST_CHECK_MESSAGE(err == SCRIPT_ERR_EVAL_FALSE, ScriptErrorString(err)); keys.clear(); keys.push_back(key3); keys.push_back(key2); // sigs must be in correct order - CScript badsig3 = sign_multisig(scriptPubKey23, keys, txTo23); + CScript badsig3 = sign_multisig(scriptPubKey23, keys, CTransaction(txTo23)); BOOST_CHECK(!VerifyScript(badsig3, scriptPubKey23, nullptr, gFlags, MutableTransactionSignatureChecker(&txTo23, 0, txFrom23.vout[0].nValue), &err)); BOOST_CHECK_MESSAGE(err == SCRIPT_ERR_EVAL_FALSE, ScriptErrorString(err)); keys.clear(); keys.push_back(key4); keys.push_back(key2); // sigs must match pubkeys - CScript badsig4 = sign_multisig(scriptPubKey23, keys, txTo23); + CScript badsig4 = sign_multisig(scriptPubKey23, keys, CTransaction(txTo23)); BOOST_CHECK(!VerifyScript(badsig4, scriptPubKey23, nullptr, gFlags, MutableTransactionSignatureChecker(&txTo23, 0, txFrom23.vout[0].nValue), &err)); BOOST_CHECK_MESSAGE(err == SCRIPT_ERR_EVAL_FALSE, ScriptErrorString(err)); keys.clear(); keys.push_back(key1); keys.push_back(key4); // sigs must match pubkeys - CScript badsig5 = sign_multisig(scriptPubKey23, keys, txTo23); + CScript badsig5 = sign_multisig(scriptPubKey23, keys, CTransaction(txTo23)); BOOST_CHECK(!VerifyScript(badsig5, scriptPubKey23, nullptr, gFlags, MutableTransactionSignatureChecker(&txTo23, 0, txFrom23.vout[0].nValue), &err)); BOOST_CHECK_MESSAGE(err == SCRIPT_ERR_EVAL_FALSE, ScriptErrorString(err)); keys.clear(); // Must have signatures - CScript badsig6 = sign_multisig(scriptPubKey23, keys, txTo23); + CScript badsig6 = sign_multisig(scriptPubKey23, keys, CTransaction(txTo23)); BOOST_CHECK(!VerifyScript(badsig6, scriptPubKey23, nullptr, gFlags, MutableTransactionSignatureChecker(&txTo23, 0, txFrom23.vout[0].nValue), &err)); BOOST_CHECK_MESSAGE(err == SCRIPT_ERR_INVALID_STACK_OPERATION, ScriptErrorString(err)); } @@ -1185,11 +1208,11 @@ BOOST_AUTO_TEST_CASE(script_combineSigs) key.MakeNewKey(i%2 == 1); keys.push_back(key); pubkeys.push_back(key.GetPubKey()); - keystore.AddKey(key); + BOOST_CHECK(keystore.AddKey(key)); } CMutableTransaction txFrom = BuildCreditingTransaction(GetScriptForDestination(keys[0].GetPubKey().GetID())); - CMutableTransaction txTo = BuildSpendingTransaction(CScript(), CScriptWitness(), txFrom); + CMutableTransaction txTo = BuildSpendingTransaction(CScript(), CScriptWitness(), CTransaction(txFrom)); CScript& scriptPubKey = txFrom.vout[0].scriptPubKey; SignatureData scriptSig; @@ -1198,7 +1221,7 @@ BOOST_AUTO_TEST_CASE(script_combineSigs) BOOST_CHECK(combined.scriptSig.empty()); // Single signature case: - SignSignature(keystore, txFrom, txTo, 0, SIGHASH_ALL); // changes scriptSig + BOOST_CHECK(SignSignature(keystore, CTransaction(txFrom), txTo, 0, SIGHASH_ALL)); // changes scriptSig scriptSig = DataFromTransaction(txTo, 0, txFrom.vout[0]); combined = CombineSignatures(txFrom.vout[0], txTo, scriptSig, empty); BOOST_CHECK(combined.scriptSig == scriptSig.scriptSig); @@ -1206,31 +1229,31 @@ BOOST_AUTO_TEST_CASE(script_combineSigs) BOOST_CHECK(combined.scriptSig == scriptSig.scriptSig); SignatureData scriptSigCopy = scriptSig; // Signing again will give a different, valid signature: - SignSignature(keystore, txFrom, txTo, 0, SIGHASH_ALL); + BOOST_CHECK(SignSignature(keystore, CTransaction(txFrom), txTo, 0, SIGHASH_ALL)); scriptSig = DataFromTransaction(txTo, 0, txFrom.vout[0]); combined = CombineSignatures(txFrom.vout[0], txTo, scriptSigCopy, scriptSig); BOOST_CHECK(combined.scriptSig == scriptSigCopy.scriptSig || combined.scriptSig == scriptSig.scriptSig); // P2SH, single-signature case: CScript pkSingle; pkSingle << ToByteVector(keys[0].GetPubKey()) << OP_CHECKSIG; - keystore.AddCScript(pkSingle); + BOOST_CHECK(keystore.AddCScript(pkSingle)); scriptPubKey = GetScriptForDestination(CScriptID(pkSingle)); - SignSignature(keystore, txFrom, txTo, 0, SIGHASH_ALL); + BOOST_CHECK(SignSignature(keystore, CTransaction(txFrom), txTo, 0, SIGHASH_ALL)); scriptSig = DataFromTransaction(txTo, 0, txFrom.vout[0]); combined = CombineSignatures(txFrom.vout[0], txTo, scriptSig, empty); BOOST_CHECK(combined.scriptSig == scriptSig.scriptSig); combined = CombineSignatures(txFrom.vout[0], txTo, empty, scriptSig); BOOST_CHECK(combined.scriptSig == scriptSig.scriptSig); scriptSigCopy = scriptSig; - SignSignature(keystore, txFrom, txTo, 0, SIGHASH_ALL); + BOOST_CHECK(SignSignature(keystore, CTransaction(txFrom), txTo, 0, SIGHASH_ALL)); scriptSig = DataFromTransaction(txTo, 0, txFrom.vout[0]); combined = CombineSignatures(txFrom.vout[0], txTo, scriptSigCopy, scriptSig); BOOST_CHECK(combined.scriptSig == scriptSigCopy.scriptSig || combined.scriptSig == scriptSig.scriptSig); // Hardest case: Multisig 2-of-3 scriptPubKey = GetScriptForMultisig(2, pubkeys); - keystore.AddCScript(scriptPubKey); - SignSignature(keystore, txFrom, txTo, 0, SIGHASH_ALL); + BOOST_CHECK(keystore.AddCScript(scriptPubKey)); + BOOST_CHECK(SignSignature(keystore, CTransaction(txFrom), txTo, 0, SIGHASH_ALL)); scriptSig = DataFromTransaction(txTo, 0, txFrom.vout[0]); combined = CombineSignatures(txFrom.vout[0], txTo, scriptSig, empty); BOOST_CHECK(combined.scriptSig == scriptSig.scriptSig); diff --git a/src/test/serialize_tests.cpp b/src/test/serialize_tests.cpp index e754996d2f..002f61f6a2 100644 --- a/src/test/serialize_tests.cpp +++ b/src/test/serialize_tests.cpp @@ -200,7 +200,7 @@ BOOST_AUTO_TEST_CASE(varints) } for (uint64_t i = 0; i < 100000000000ULL; i += 999999937) { - uint64_t j = -1; + uint64_t j = std::numeric_limits<uint64_t>::max(); ss >> VARINT(j); BOOST_CHECK_MESSAGE(i == j, "decoded:" << j << " expected:" << i); } diff --git a/src/test/sighash_tests.cpp b/src/test/sighash_tests.cpp index e6905457bb..04d5462acb 100644 --- a/src/test/sighash_tests.cpp +++ b/src/test/sighash_tests.cpp @@ -11,8 +11,8 @@ #include <serialize.h> #include <streams.h> #include <test/test_bitcoin.h> -#include <util.h> -#include <utilstrencodings.h> +#include <util/system.h> +#include <util/strencodings.h> #include <version.h> #include <iostream> @@ -105,7 +105,7 @@ void static RandomTransaction(CMutableTransaction &tx, bool fSingle) { txin.prevout.hash = InsecureRand256(); txin.prevout.n = InsecureRandBits(2); RandomScript(txin.scriptSig); - txin.nSequence = (InsecureRandBool()) ? InsecureRand32() : (unsigned int)-1; + txin.nSequence = (InsecureRandBool()) ? InsecureRand32() : std::numeric_limits<uint32_t>::max(); } for (int out = 0; out < outs; out++) { tx.vout.push_back(CTxOut()); @@ -137,7 +137,7 @@ BOOST_AUTO_TEST_CASE(sighash_test) int nIn = InsecureRandRange(txTo.vin.size()); uint256 sh, sho; - sho = SignatureHashOld(scriptCode, txTo, nIn, nHashType); + sho = SignatureHashOld(scriptCode, CTransaction(txTo), nIn, nHashType); sh = SignatureHash(scriptCode, txTo, nIn, nHashType, 0, SigVersion::BASE); #if defined(PRINT_SIGHASH_JSON) CDataStream ss(SER_NETWORK, PROTOCOL_VERSION); diff --git a/src/test/sigopcount_tests.cpp b/src/test/sigopcount_tests.cpp index 5462fea777..6a0349cd4e 100644 --- a/src/test/sigopcount_tests.cpp +++ b/src/test/sigopcount_tests.cpp @@ -102,7 +102,7 @@ static void BuildTxs(CMutableTransaction& spendingTx, CCoinsViewCache& coins, CM spendingTx.vout[0].nValue = 1; spendingTx.vout[0].scriptPubKey = CScript(); - AddCoins(coins, creationTx, 0); + AddCoins(coins, CTransaction(creationTx), 0); } BOOST_AUTO_TEST_CASE(GetTxSigOpCost) @@ -138,7 +138,7 @@ BOOST_AUTO_TEST_CASE(GetTxSigOpCost) // is not accurate. assert(GetTransactionSigOpCost(CTransaction(creationTx), coins, flags) == MAX_PUBKEYS_PER_MULTISIG * WITNESS_SCALE_FACTOR); // Sanity check: script verification fails because of an invalid signature. - assert(VerifyWithFlag(creationTx, spendingTx, flags) == SCRIPT_ERR_CHECKMULTISIGVERIFY); + assert(VerifyWithFlag(CTransaction(creationTx), spendingTx, flags) == SCRIPT_ERR_CHECKMULTISIGVERIFY); } // Multisig nested in P2SH @@ -149,7 +149,7 @@ BOOST_AUTO_TEST_CASE(GetTxSigOpCost) BuildTxs(spendingTx, coins, creationTx, scriptPubKey, scriptSig, CScriptWitness()); assert(GetTransactionSigOpCost(CTransaction(spendingTx), coins, flags) == 2 * WITNESS_SCALE_FACTOR); - assert(VerifyWithFlag(creationTx, spendingTx, flags) == SCRIPT_ERR_CHECKMULTISIGVERIFY); + assert(VerifyWithFlag(CTransaction(creationTx), spendingTx, flags) == SCRIPT_ERR_CHECKMULTISIGVERIFY); } // P2WPKH witness program @@ -166,7 +166,7 @@ BOOST_AUTO_TEST_CASE(GetTxSigOpCost) assert(GetTransactionSigOpCost(CTransaction(spendingTx), coins, flags) == 1); // No signature operations if we don't verify the witness. assert(GetTransactionSigOpCost(CTransaction(spendingTx), coins, flags & ~SCRIPT_VERIFY_WITNESS) == 0); - assert(VerifyWithFlag(creationTx, spendingTx, flags) == SCRIPT_ERR_EQUALVERIFY); + assert(VerifyWithFlag(CTransaction(creationTx), spendingTx, flags) == SCRIPT_ERR_EQUALVERIFY); // The sig op cost for witness version != 0 is zero. assert(scriptPubKey[0] == 0x00); @@ -193,7 +193,7 @@ BOOST_AUTO_TEST_CASE(GetTxSigOpCost) BuildTxs(spendingTx, coins, creationTx, scriptPubKey, scriptSig, scriptWitness); assert(GetTransactionSigOpCost(CTransaction(spendingTx), coins, flags) == 1); - assert(VerifyWithFlag(creationTx, spendingTx, flags) == SCRIPT_ERR_EQUALVERIFY); + assert(VerifyWithFlag(CTransaction(creationTx), spendingTx, flags) == SCRIPT_ERR_EQUALVERIFY); } // P2WSH witness program @@ -209,7 +209,7 @@ BOOST_AUTO_TEST_CASE(GetTxSigOpCost) BuildTxs(spendingTx, coins, creationTx, scriptPubKey, scriptSig, scriptWitness); assert(GetTransactionSigOpCost(CTransaction(spendingTx), coins, flags) == 2); assert(GetTransactionSigOpCost(CTransaction(spendingTx), coins, flags & ~SCRIPT_VERIFY_WITNESS) == 0); - assert(VerifyWithFlag(creationTx, spendingTx, flags) == SCRIPT_ERR_CHECKMULTISIGVERIFY); + assert(VerifyWithFlag(CTransaction(creationTx), spendingTx, flags) == SCRIPT_ERR_CHECKMULTISIGVERIFY); } // P2WSH nested in P2SH @@ -225,7 +225,7 @@ BOOST_AUTO_TEST_CASE(GetTxSigOpCost) BuildTxs(spendingTx, coins, creationTx, scriptPubKey, scriptSig, scriptWitness); assert(GetTransactionSigOpCost(CTransaction(spendingTx), coins, flags) == 2); - assert(VerifyWithFlag(creationTx, spendingTx, flags) == SCRIPT_ERR_CHECKMULTISIGVERIFY); + assert(VerifyWithFlag(CTransaction(creationTx), spendingTx, flags) == SCRIPT_ERR_CHECKMULTISIGVERIFY); } } diff --git a/src/test/skiplist_tests.cpp b/src/test/skiplist_tests.cpp index c0754618fb..5c46976ace 100644 --- a/src/test/skiplist_tests.cpp +++ b/src/test/skiplist_tests.cpp @@ -3,7 +3,7 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include <chain.h> -#include <util.h> +#include <util/system.h> #include <test/test_bitcoin.h> #include <vector> @@ -170,7 +170,6 @@ BOOST_AUTO_TEST_CASE(findearliestatleast_edge_test) BOOST_CHECK_EQUAL(chain.FindEarliestAtLeast(-1)->nHeight, 0); BOOST_CHECK_EQUAL(chain.FindEarliestAtLeast(std::numeric_limits<int64_t>::min())->nHeight, 0); - BOOST_CHECK_EQUAL(chain.FindEarliestAtLeast(std::numeric_limits<unsigned int>::min())->nHeight, 0); BOOST_CHECK_EQUAL(chain.FindEarliestAtLeast(-int64_t(std::numeric_limits<unsigned int>::max()) - 1)->nHeight, 0); BOOST_CHECK(!chain.FindEarliestAtLeast(std::numeric_limits<int64_t>::max())); BOOST_CHECK(!chain.FindEarliestAtLeast(std::numeric_limits<unsigned int>::max())); diff --git a/src/test/streams_tests.cpp b/src/test/streams_tests.cpp index 26cf74830d..a1940eb80e 100644 --- a/src/test/streams_tests.cpp +++ b/src/test/streams_tests.cpp @@ -102,15 +102,15 @@ BOOST_AUTO_TEST_CASE(streams_vector_reader) BOOST_CHECK_THROW(reader >> d, std::ios_base::failure); // Read a 4 bytes as a signed int from the beginning of the buffer. - reader.seek(-6); - reader >> d; + VectorReader new_reader(SER_NETWORK, INIT_PROTO_VERSION, vch, 0); + new_reader >> d; BOOST_CHECK_EQUAL(d, 67370753); // 1,255,3,4 in little-endian base-256 - BOOST_CHECK_EQUAL(reader.size(), 2); - BOOST_CHECK(!reader.empty()); + BOOST_CHECK_EQUAL(new_reader.size(), 2); + BOOST_CHECK(!new_reader.empty()); // Reading after end of byte vector throws an error even if the reader is // not totally empty. - BOOST_CHECK_THROW(reader >> d, std::ios_base::failure); + BOOST_CHECK_THROW(new_reader >> d, std::ios_base::failure); } BOOST_AUTO_TEST_CASE(bitstream_reader_writer) diff --git a/src/test/test_bitcoin.cpp b/src/test/test_bitcoin.cpp index f7874e6882..0c3fb7c398 100644 --- a/src/test/test_bitcoin.cpp +++ b/src/test/test_bitcoin.cpp @@ -4,6 +4,7 @@ #include <test/test_bitcoin.h> +#include <banman.h> #include <chainparams.h> #include <consensus/consensus.h> #include <consensus/params.h> @@ -11,6 +12,7 @@ #include <crypto/sha256.h> #include <miner.h> #include <net_processing.h> +#include <noui.h> #include <pow.h> #include <rpc/register.h> #include <rpc/server.h> @@ -21,26 +23,7 @@ const std::function<std::string(const char*)> G_TRANSLATION_FUN = nullptr; -void CConnmanTest::AddNode(CNode& node) -{ - LOCK(g_connman->cs_vNodes); - g_connman->vNodes.push_back(&node); -} - -void CConnmanTest::ClearNodes() -{ - LOCK(g_connman->cs_vNodes); - for (const CNode* node : g_connman->vNodes) { - delete node; - } - g_connman->vNodes.clear(); -} - -uint256 insecure_rand_seed = GetRandHash(); -FastRandomContext insecure_rand_ctx(insecure_rand_seed); - -extern bool fPrintToConsole; -extern void noui_connect(); +FastRandomContext g_insecure_rand_ctx; std::ostream& operator<<(std::ostream& os, const uint256& num) { @@ -52,7 +35,6 @@ BasicTestingSetup::BasicTestingSetup(const std::string& chainName) : m_path_root(fs::temp_directory_path() / "test_bitcoin" / strprintf("%lu_%i", (unsigned long)GetTime(), (int)(InsecureRandRange(1 << 30)))) { SHA256AutoDetect(); - RandomInit(); ECC_Start(); SetupEnvironment(); SetupNetworking(); @@ -92,7 +74,7 @@ TestingSetup::TestingSetup(const std::string& chainName) : BasicTestingSetup(cha // We have to run a scheduler thread to prevent ActivateBestChain // from blocking due to queue overrun. - threadGroup.create_thread(boost::bind(&CScheduler::serviceQueue, &scheduler)); + threadGroup.create_thread(std::bind(&CScheduler::serviceQueue, &scheduler)); GetMainSignals().RegisterBackgroundSignalScheduler(scheduler); mempool.setSanityCheck(1.0); @@ -111,23 +93,23 @@ TestingSetup::TestingSetup(const std::string& chainName) : BasicTestingSetup(cha nScriptCheckThreads = 3; for (int i=0; i < nScriptCheckThreads-1; i++) threadGroup.create_thread(&ThreadScriptCheck); + + g_banman = MakeUnique<BanMan>(GetDataDir() / "banlist.dat", nullptr, DEFAULT_MISBEHAVING_BANTIME); g_connman = MakeUnique<CConnman>(0x1337, 0x1337); // Deterministic randomness for tests. - connman = g_connman.get(); - peerLogic.reset(new PeerLogicValidation(connman, scheduler, /*enable_bip61=*/true)); } TestingSetup::~TestingSetup() { - threadGroup.interrupt_all(); - threadGroup.join_all(); - GetMainSignals().FlushBackgroundCallbacks(); - GetMainSignals().UnregisterBackgroundSignalScheduler(); - g_connman.reset(); - peerLogic.reset(); - UnloadBlockIndex(); - pcoinsTip.reset(); - pcoinsdbview.reset(); - pblocktree.reset(); + threadGroup.interrupt_all(); + threadGroup.join_all(); + GetMainSignals().FlushBackgroundCallbacks(); + GetMainSignals().UnregisterBackgroundSignalScheduler(); + g_connman.reset(); + g_banman.reset(); + UnloadBlockIndex(); + pcoinsTip.reset(); + pcoinsdbview.reset(); + pblocktree.reset(); } TestChain100Setup::TestChain100Setup() : TestingSetup(CBaseChainParams::REGTEST) diff --git a/src/test/test_bitcoin.h b/src/test/test_bitcoin.h index 3872767133..71520232ac 100644 --- a/src/test/test_bitcoin.h +++ b/src/test/test_bitcoin.h @@ -15,27 +15,36 @@ #include <txmempool.h> #include <memory> +#include <type_traits> #include <boost/thread.hpp> -extern uint256 insecure_rand_seed; -extern FastRandomContext insecure_rand_ctx; +// Enable BOOST_CHECK_EQUAL for enum class types +template <typename T> +std::ostream& operator<<(typename std::enable_if<std::is_enum<T>::value, std::ostream>::type& stream, const T& e) +{ + return stream << static_cast<typename std::underlying_type<T>::type>(e); +} -static inline void SeedInsecureRand(bool fDeterministic = false) +/** + * This global and the helpers that use it are not thread-safe. + * + * If thread-safety is needed, the global could be made thread_local (given + * that thread_local is supported on all architectures we support) or a + * per-thread instance could be used in the multi-threaded test. + */ +extern FastRandomContext g_insecure_rand_ctx; + +static inline void SeedInsecureRand(bool deterministic = false) { - if (fDeterministic) { - insecure_rand_seed = uint256(); - } else { - insecure_rand_seed = GetRandHash(); - } - insecure_rand_ctx = FastRandomContext(insecure_rand_seed); + g_insecure_rand_ctx = FastRandomContext(deterministic); } -static inline uint32_t InsecureRand32() { return insecure_rand_ctx.rand32(); } -static inline uint256 InsecureRand256() { return insecure_rand_ctx.rand256(); } -static inline uint64_t InsecureRandBits(int bits) { return insecure_rand_ctx.randbits(bits); } -static inline uint64_t InsecureRandRange(uint64_t range) { return insecure_rand_ctx.randrange(range); } -static inline bool InsecureRandBool() { return insecure_rand_ctx.randbool(); } +static inline uint32_t InsecureRand32() { return g_insecure_rand_ctx.rand32(); } +static inline uint256 InsecureRand256() { return g_insecure_rand_ctx.rand256(); } +static inline uint64_t InsecureRandBits(int bits) { return g_insecure_rand_ctx.randbits(bits); } +static inline uint64_t InsecureRandRange(uint64_t range) { return g_insecure_rand_ctx.randrange(range); } +static inline bool InsecureRandBool() { return g_insecure_rand_ctx.randbool(); } static constexpr CAmount CENT{1000000}; @@ -59,17 +68,11 @@ private: */ class CConnman; class CNode; -struct CConnmanTest { - static void AddNode(CNode& node); - static void ClearNodes(); -}; class PeerLogicValidation; -struct TestingSetup: public BasicTestingSetup { +struct TestingSetup : public BasicTestingSetup { boost::thread_group threadGroup; - CConnman* connman; CScheduler scheduler; - std::unique_ptr<PeerLogicValidation> peerLogic; explicit TestingSetup(const std::string& chainName = CBaseChainParams::MAIN); ~TestingSetup(); diff --git a/src/test/test_bitcoin_main.cpp b/src/test/test_bitcoin_main.cpp index 6c066d3fea..46b63b93b4 100644 --- a/src/test/test_bitcoin_main.cpp +++ b/src/test/test_bitcoin_main.cpp @@ -4,6 +4,7 @@ #define BOOST_TEST_MODULE Bitcoin Test Suite +#include <banman.h> #include <net.h> #include <memory> @@ -11,6 +12,7 @@ #include <boost/test/unit_test.hpp> std::unique_ptr<CConnman> g_connman; +std::unique_ptr<BanMan> g_banman; [[noreturn]] void Shutdown(void* parg) { diff --git a/src/test/transaction_tests.cpp b/src/test/transaction_tests.cpp index c527ad448c..39cff3f463 100644 --- a/src/test/transaction_tests.cpp +++ b/src/test/transaction_tests.cpp @@ -19,7 +19,7 @@ #include <script/sign.h> #include <script/script_error.h> #include <script/standard.h> -#include <utilstrencodings.h> +#include <util/strencodings.h> #include <map> #include <string> @@ -273,11 +273,11 @@ BOOST_AUTO_TEST_CASE(basic_transaction_tests) CMutableTransaction tx; stream >> tx; CValidationState state; - BOOST_CHECK_MESSAGE(CheckTransaction(tx, state) && state.IsValid(), "Simple deserialized transaction should be valid."); + BOOST_CHECK_MESSAGE(CheckTransaction(CTransaction(tx), state) && state.IsValid(), "Simple deserialized transaction should be valid."); // Check that duplicate txins fail tx.vin.push_back(tx.vin[0]); - BOOST_CHECK_MESSAGE(!CheckTransaction(tx, state) || !state.IsValid(), "Transaction with duplicate txins should be invalid."); + BOOST_CHECK_MESSAGE(!CheckTransaction(CTransaction(tx), state) || !state.IsValid(), "Transaction with duplicate txins should be invalid."); } // @@ -306,14 +306,14 @@ SetupDummyInputs(CBasicKeyStore& keystoreRet, CCoinsViewCache& coinsRet) dummyTransactions[0].vout[0].scriptPubKey << ToByteVector(key[0].GetPubKey()) << OP_CHECKSIG; dummyTransactions[0].vout[1].nValue = 50*CENT; dummyTransactions[0].vout[1].scriptPubKey << ToByteVector(key[1].GetPubKey()) << OP_CHECKSIG; - AddCoins(coinsRet, dummyTransactions[0], 0); + AddCoins(coinsRet, CTransaction(dummyTransactions[0]), 0); dummyTransactions[1].vout.resize(2); dummyTransactions[1].vout[0].nValue = 21*CENT; dummyTransactions[1].vout[0].scriptPubKey = GetScriptForDestination(key[2].GetPubKey().GetID()); dummyTransactions[1].vout[1].nValue = 22*CENT; dummyTransactions[1].vout[1].scriptPubKey = GetScriptForDestination(key[3].GetPubKey().GetID()); - AddCoins(coinsRet, dummyTransactions[1], 0); + AddCoins(coinsRet, CTransaction(dummyTransactions[1]), 0); return dummyTransactions; } @@ -340,8 +340,8 @@ BOOST_AUTO_TEST_CASE(test_Get) t1.vout[0].nValue = 90*CENT; t1.vout[0].scriptPubKey << OP_1; - BOOST_CHECK(AreInputsStandard(t1, coins)); - BOOST_CHECK_EQUAL(coins.GetValueIn(t1), (50+21+22)*CENT); + BOOST_CHECK(AreInputsStandard(CTransaction(t1), coins)); + 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) @@ -414,14 +414,15 @@ static void ReplaceRedeemScript(CScript& script, const CScript& redeemScript) script = PushAll(stack); } -BOOST_AUTO_TEST_CASE(test_big_witness_transaction) { +BOOST_AUTO_TEST_CASE(test_big_witness_transaction) +{ CMutableTransaction mtx; mtx.nVersion = 1; CKey key; key.MakeNewKey(true); // Need to use compressed keys in segwit or the signing will fail CBasicKeyStore keystore; - keystore.AddKeyPubKey(key, key.GetPubKey()); + 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()); @@ -456,9 +457,8 @@ BOOST_AUTO_TEST_CASE(test_big_witness_transaction) { } CDataStream ssout(SER_NETWORK, PROTOCOL_VERSION); - auto vstream = WithOrVersion(&ssout, 0); - vstream << mtx; - CTransaction tx(deserialize, vstream); + ssout << mtx; + CTransaction tx(deserialize, ssout); // check all inputs concurrently, with the cache PrecomputedTransactionData txdata(tx); @@ -467,7 +467,7 @@ BOOST_AUTO_TEST_CASE(test_big_witness_transaction) { CCheckQueueControl<CScriptCheck> control(&scriptcheckqueue); for (int i=0; i<20; i++) - threadGroup.create_thread(boost::bind(&CCheckQueue<CScriptCheck>::Thread, boost::ref(scriptcheckqueue))); + threadGroup.create_thread(std::bind(&CCheckQueue<CScriptCheck>::Thread, std::ref(scriptcheckqueue))); std::vector<Coin> coins; for(uint32_t i = 0; i < mtx.vin.size(); i++) { @@ -518,10 +518,10 @@ BOOST_AUTO_TEST_CASE(test_witness) pubkey3 = key3.GetPubKey(); pubkey1L = key1L.GetPubKey(); pubkey2L = key2L.GetPubKey(); - keystore.AddKeyPubKey(key1, pubkey1); - keystore.AddKeyPubKey(key2, pubkey2); - keystore.AddKeyPubKey(key1L, pubkey1L); - keystore.AddKeyPubKey(key2L, pubkey2L); + BOOST_CHECK(keystore.AddKeyPubKey(key1, pubkey1)); + BOOST_CHECK(keystore.AddKeyPubKey(key2, pubkey2)); + BOOST_CHECK(keystore.AddKeyPubKey(key1L, pubkey1L)); + BOOST_CHECK(keystore.AddKeyPubKey(key2L, pubkey2L)); CScript scriptPubkey1, scriptPubkey2, scriptPubkey1L, scriptPubkey2L, scriptMulti; scriptPubkey1 << ToByteVector(pubkey1) << OP_CHECKSIG; scriptPubkey2 << ToByteVector(pubkey2) << OP_CHECKSIG; @@ -531,19 +531,19 @@ BOOST_AUTO_TEST_CASE(test_witness) oneandthree.push_back(pubkey1); oneandthree.push_back(pubkey3); scriptMulti = GetScriptForMultisig(2, oneandthree); - keystore.AddCScript(scriptPubkey1); - keystore.AddCScript(scriptPubkey2); - keystore.AddCScript(scriptPubkey1L); - keystore.AddCScript(scriptPubkey2L); - keystore.AddCScript(scriptMulti); - keystore.AddCScript(GetScriptForWitness(scriptPubkey1)); - keystore.AddCScript(GetScriptForWitness(scriptPubkey2)); - keystore.AddCScript(GetScriptForWitness(scriptPubkey1L)); - keystore.AddCScript(GetScriptForWitness(scriptPubkey2L)); - keystore.AddCScript(GetScriptForWitness(scriptMulti)); - keystore2.AddCScript(scriptMulti); - keystore2.AddCScript(GetScriptForWitness(scriptMulti)); - keystore2.AddKeyPubKey(key3, pubkey3); + BOOST_CHECK(keystore.AddCScript(scriptPubkey1)); + BOOST_CHECK(keystore.AddCScript(scriptPubkey2)); + BOOST_CHECK(keystore.AddCScript(scriptPubkey1L)); + BOOST_CHECK(keystore.AddCScript(scriptPubkey2L)); + BOOST_CHECK(keystore.AddCScript(scriptMulti)); + BOOST_CHECK(keystore.AddCScript(GetScriptForWitness(scriptPubkey1))); + BOOST_CHECK(keystore.AddCScript(GetScriptForWitness(scriptPubkey2))); + BOOST_CHECK(keystore.AddCScript(GetScriptForWitness(scriptPubkey1L))); + BOOST_CHECK(keystore.AddCScript(GetScriptForWitness(scriptPubkey2L))); + BOOST_CHECK(keystore.AddCScript(GetScriptForWitness(scriptMulti))); + BOOST_CHECK(keystore2.AddCScript(scriptMulti)); + BOOST_CHECK(keystore2.AddCScript(GetScriptForWitness(scriptMulti))); + BOOST_CHECK(keystore2.AddKeyPubKey(key3, pubkey3)); CTransactionRef output1, output2; CMutableTransaction input1, input2; @@ -697,75 +697,75 @@ BOOST_AUTO_TEST_CASE(test_IsStandard) t.vout[0].scriptPubKey = GetScriptForDestination(key.GetPubKey().GetID()); std::string reason; - BOOST_CHECK(IsStandardTx(t, reason)); + BOOST_CHECK(IsStandardTx(CTransaction(t), reason)); // Check dust with default relay fee: CAmount nDustThreshold = 182 * dustRelayFee.GetFeePerK()/1000; BOOST_CHECK_EQUAL(nDustThreshold, 546); // dust: t.vout[0].nValue = nDustThreshold - 1; - BOOST_CHECK(!IsStandardTx(t, reason)); + BOOST_CHECK(!IsStandardTx(CTransaction(t), reason)); // not dust: t.vout[0].nValue = nDustThreshold; - BOOST_CHECK(IsStandardTx(t, reason)); + BOOST_CHECK(IsStandardTx(CTransaction(t), reason)); // Check dust with odd relay fee to verify rounding: // nDustThreshold = 182 * 3702 / 1000 dustRelayFee = CFeeRate(3702); // dust: t.vout[0].nValue = 673 - 1; - BOOST_CHECK(!IsStandardTx(t, reason)); + BOOST_CHECK(!IsStandardTx(CTransaction(t), reason)); // not dust: t.vout[0].nValue = 673; - BOOST_CHECK(IsStandardTx(t, reason)); + BOOST_CHECK(IsStandardTx(CTransaction(t), reason)); dustRelayFee = CFeeRate(DUST_RELAY_TX_FEE); t.vout[0].scriptPubKey = CScript() << OP_1; - BOOST_CHECK(!IsStandardTx(t, reason)); + BOOST_CHECK(!IsStandardTx(CTransaction(t), reason)); // MAX_OP_RETURN_RELAY-byte TX_NULL_DATA (standard) t.vout[0].scriptPubKey = CScript() << OP_RETURN << ParseHex("04678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef3804678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38"); BOOST_CHECK_EQUAL(MAX_OP_RETURN_RELAY, t.vout[0].scriptPubKey.size()); - BOOST_CHECK(IsStandardTx(t, reason)); + BOOST_CHECK(IsStandardTx(CTransaction(t), reason)); // MAX_OP_RETURN_RELAY+1-byte TX_NULL_DATA (non-standard) t.vout[0].scriptPubKey = CScript() << OP_RETURN << ParseHex("04678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef3804678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef3800"); BOOST_CHECK_EQUAL(MAX_OP_RETURN_RELAY + 1, t.vout[0].scriptPubKey.size()); - BOOST_CHECK(!IsStandardTx(t, reason)); + BOOST_CHECK(!IsStandardTx(CTransaction(t), reason)); // Data payload can be encoded in any way... t.vout[0].scriptPubKey = CScript() << OP_RETURN << ParseHex(""); - BOOST_CHECK(IsStandardTx(t, reason)); + BOOST_CHECK(IsStandardTx(CTransaction(t), reason)); t.vout[0].scriptPubKey = CScript() << OP_RETURN << ParseHex("00") << ParseHex("01"); - BOOST_CHECK(IsStandardTx(t, reason)); + BOOST_CHECK(IsStandardTx(CTransaction(t), reason)); // OP_RESERVED *is* considered to be a PUSHDATA type opcode by IsPushOnly()! t.vout[0].scriptPubKey = CScript() << OP_RETURN << OP_RESERVED << -1 << 0 << ParseHex("01") << 2 << 3 << 4 << 5 << 6 << 7 << 8 << 9 << 10 << 11 << 12 << 13 << 14 << 15 << 16; - BOOST_CHECK(IsStandardTx(t, reason)); + BOOST_CHECK(IsStandardTx(CTransaction(t), reason)); t.vout[0].scriptPubKey = CScript() << OP_RETURN << 0 << ParseHex("01") << 2 << ParseHex("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); - BOOST_CHECK(IsStandardTx(t, reason)); + BOOST_CHECK(IsStandardTx(CTransaction(t), reason)); // ...so long as it only contains PUSHDATA's t.vout[0].scriptPubKey = CScript() << OP_RETURN << OP_RETURN; - BOOST_CHECK(!IsStandardTx(t, reason)); + BOOST_CHECK(!IsStandardTx(CTransaction(t), reason)); // TX_NULL_DATA w/o PUSHDATA t.vout.resize(1); t.vout[0].scriptPubKey = CScript() << OP_RETURN; - BOOST_CHECK(IsStandardTx(t, reason)); + BOOST_CHECK(IsStandardTx(CTransaction(t), reason)); // Only one TX_NULL_DATA permitted in all cases t.vout.resize(2); t.vout[0].scriptPubKey = CScript() << OP_RETURN << ParseHex("04678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38"); t.vout[1].scriptPubKey = CScript() << OP_RETURN << ParseHex("04678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38"); - BOOST_CHECK(!IsStandardTx(t, reason)); + BOOST_CHECK(!IsStandardTx(CTransaction(t), reason)); t.vout[0].scriptPubKey = CScript() << OP_RETURN << ParseHex("04678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38"); t.vout[1].scriptPubKey = CScript() << OP_RETURN; - BOOST_CHECK(!IsStandardTx(t, reason)); + BOOST_CHECK(!IsStandardTx(CTransaction(t), reason)); t.vout[0].scriptPubKey = CScript() << OP_RETURN; t.vout[1].scriptPubKey = CScript() << OP_RETURN; - BOOST_CHECK(!IsStandardTx(t, reason)); + BOOST_CHECK(!IsStandardTx(CTransaction(t), reason)); } BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/txindex_tests.cpp b/src/test/txindex_tests.cpp index 2a160b9988..0301901bf0 100644 --- a/src/test/txindex_tests.cpp +++ b/src/test/txindex_tests.cpp @@ -2,11 +2,12 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. +#include <chainparams.h> #include <index/txindex.h> #include <script/standard.h> #include <test/test_bitcoin.h> -#include <util.h> -#include <utiltime.h> +#include <util/system.h> +#include <util/time.h> #include <validation.h> #include <boost/test/unit_test.hpp> @@ -38,6 +39,12 @@ BOOST_FIXTURE_TEST_CASE(txindex_initial_sync, TestChain100Setup) MilliSleep(100); } + // Check that txindex excludes genesis block transactions. + const CBlock& genesis_block = Params().GenesisBlock(); + for (const auto& txn : genesis_block.vtx) { + BOOST_CHECK(!txindex.FindTx(txn->GetHash(), block_hash, tx_disk)); + } + // Check that txindex has all txs that were in the chain before it started. for (const auto& txn : m_coinbase_txns) { if (!txindex.FindTx(txn->GetHash(), block_hash, tx_disk)) { diff --git a/src/test/txvalidation_tests.cpp b/src/test/txvalidation_tests.cpp index 473ec5addf..c2777cd6d1 100644 --- a/src/test/txvalidation_tests.cpp +++ b/src/test/txvalidation_tests.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017 The Bitcoin Core developers +// Copyright (c) 2017-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. diff --git a/src/test/txvalidationcache_tests.cpp b/src/test/txvalidationcache_tests.cpp index 2bf835a756..c467f27836 100644 --- a/src/test/txvalidationcache_tests.cpp +++ b/src/test/txvalidationcache_tests.cpp @@ -12,7 +12,7 @@ #include <script/standard.h> #include <script/sign.h> #include <test/test_bitcoin.h> -#include <utiltime.h> +#include <util/time.h> #include <core_io.h> #include <keystore.h> #include <policy/policy.h> @@ -156,8 +156,8 @@ BOOST_FIXTURE_TEST_CASE(checkinputs_test, TestChain100Setup) CScript p2wpkh_scriptPubKey = GetScriptForWitness(p2pkh_scriptPubKey); CBasicKeyStore keystore; - keystore.AddKey(coinbaseKey); - keystore.AddCScript(p2pk_scriptPubKey); + BOOST_CHECK(keystore.AddKey(coinbaseKey)); + BOOST_CHECK(keystore.AddCScript(p2pk_scriptPubKey)); // flags to test: SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY, SCRIPT_VERIFY_CHECKSEQUENCE_VERIFY, SCRIPT_VERIFY_NULLDUMMY, uncompressed pubkey thing @@ -198,20 +198,20 @@ BOOST_FIXTURE_TEST_CASE(checkinputs_test, TestChain100Setup) CValidationState state; PrecomputedTransactionData ptd_spend_tx(spend_tx); - BOOST_CHECK(!CheckInputs(spend_tx, state, pcoinsTip.get(), true, SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_DERSIG, true, true, ptd_spend_tx, nullptr)); + BOOST_CHECK(!CheckInputs(CTransaction(spend_tx), state, pcoinsTip.get(), true, SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_DERSIG, true, true, ptd_spend_tx, nullptr)); // If we call again asking for scriptchecks (as happens in // ConnectBlock), we should add a script check object for this -- we're // not caching invalidity (if that changes, delete this test case). std::vector<CScriptCheck> scriptchecks; - BOOST_CHECK(CheckInputs(spend_tx, state, pcoinsTip.get(), true, SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_DERSIG, true, true, ptd_spend_tx, &scriptchecks)); + BOOST_CHECK(CheckInputs(CTransaction(spend_tx), state, pcoinsTip.get(), true, SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_DERSIG, true, true, ptd_spend_tx, &scriptchecks)); BOOST_CHECK_EQUAL(scriptchecks.size(), 1U); // Test that CheckInputs returns true iff DERSIG-enforcing flags are // not present. Don't add these checks to the cache, so that we can // test later that block validation works fine in the absence of cached // successes. - ValidateCheckInputsForAllFlags(spend_tx, SCRIPT_VERIFY_DERSIG | SCRIPT_VERIFY_LOW_S | SCRIPT_VERIFY_STRICTENC, false); + ValidateCheckInputsForAllFlags(CTransaction(spend_tx), SCRIPT_VERIFY_DERSIG | SCRIPT_VERIFY_LOW_S | SCRIPT_VERIFY_STRICTENC, false); } // And if we produce a block with this tx, it should be valid (DERSIG not @@ -238,7 +238,7 @@ BOOST_FIXTURE_TEST_CASE(checkinputs_test, TestChain100Setup) std::vector<unsigned char> vchSig2(p2pk_scriptPubKey.begin(), p2pk_scriptPubKey.end()); invalid_under_p2sh_tx.vin[0].scriptSig << vchSig2; - ValidateCheckInputsForAllFlags(invalid_under_p2sh_tx, SCRIPT_VERIFY_P2SH, true); + ValidateCheckInputsForAllFlags(CTransaction(invalid_under_p2sh_tx), SCRIPT_VERIFY_P2SH, true); } // Test CHECKLOCKTIMEVERIFY @@ -261,13 +261,13 @@ BOOST_FIXTURE_TEST_CASE(checkinputs_test, TestChain100Setup) vchSig.push_back((unsigned char)SIGHASH_ALL); invalid_with_cltv_tx.vin[0].scriptSig = CScript() << vchSig << 101; - ValidateCheckInputsForAllFlags(invalid_with_cltv_tx, SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY, true); + ValidateCheckInputsForAllFlags(CTransaction(invalid_with_cltv_tx), SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY, true); // Make it valid, and check again invalid_with_cltv_tx.vin[0].scriptSig = CScript() << vchSig << 100; CValidationState state; PrecomputedTransactionData txdata(invalid_with_cltv_tx); - BOOST_CHECK(CheckInputs(invalid_with_cltv_tx, state, pcoinsTip.get(), true, SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY, true, true, txdata, nullptr)); + BOOST_CHECK(CheckInputs(CTransaction(invalid_with_cltv_tx), state, pcoinsTip.get(), true, SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY, true, true, txdata, nullptr)); } // TEST CHECKSEQUENCEVERIFY @@ -289,13 +289,13 @@ BOOST_FIXTURE_TEST_CASE(checkinputs_test, TestChain100Setup) vchSig.push_back((unsigned char)SIGHASH_ALL); invalid_with_csv_tx.vin[0].scriptSig = CScript() << vchSig << 101; - ValidateCheckInputsForAllFlags(invalid_with_csv_tx, SCRIPT_VERIFY_CHECKSEQUENCEVERIFY, true); + ValidateCheckInputsForAllFlags(CTransaction(invalid_with_csv_tx), SCRIPT_VERIFY_CHECKSEQUENCEVERIFY, true); // Make it valid, and check again invalid_with_csv_tx.vin[0].scriptSig = CScript() << vchSig << 100; CValidationState state; PrecomputedTransactionData txdata(invalid_with_csv_tx); - BOOST_CHECK(CheckInputs(invalid_with_csv_tx, state, pcoinsTip.get(), true, SCRIPT_VERIFY_CHECKSEQUENCEVERIFY, true, true, txdata, nullptr)); + BOOST_CHECK(CheckInputs(CTransaction(invalid_with_csv_tx), state, pcoinsTip.get(), true, SCRIPT_VERIFY_CHECKSEQUENCEVERIFY, true, true, txdata, nullptr)); } // TODO: add tests for remaining script flags @@ -314,15 +314,15 @@ BOOST_FIXTURE_TEST_CASE(checkinputs_test, TestChain100Setup) // Sign SignatureData sigdata; - ProduceSignature(keystore, MutableTransactionSignatureCreator(&valid_with_witness_tx, 0, 11*CENT, SIGHASH_ALL), spend_tx.vout[1].scriptPubKey, sigdata); + BOOST_CHECK(ProduceSignature(keystore, MutableTransactionSignatureCreator(&valid_with_witness_tx, 0, 11*CENT, SIGHASH_ALL), spend_tx.vout[1].scriptPubKey, sigdata)); UpdateInput(valid_with_witness_tx.vin[0], sigdata); // This should be valid under all script flags. - ValidateCheckInputsForAllFlags(valid_with_witness_tx, 0, true); + ValidateCheckInputsForAllFlags(CTransaction(valid_with_witness_tx), 0, true); // Remove the witness, and check that it is now invalid. valid_with_witness_tx.vin[0].scriptWitness.SetNull(); - ValidateCheckInputsForAllFlags(valid_with_witness_tx, SCRIPT_VERIFY_WITNESS, true); + ValidateCheckInputsForAllFlags(CTransaction(valid_with_witness_tx), SCRIPT_VERIFY_WITNESS, true); } { @@ -342,12 +342,12 @@ BOOST_FIXTURE_TEST_CASE(checkinputs_test, TestChain100Setup) // Sign for (int i=0; i<2; ++i) { SignatureData sigdata; - ProduceSignature(keystore, MutableTransactionSignatureCreator(&tx, i, 11*CENT, SIGHASH_ALL), spend_tx.vout[i].scriptPubKey, sigdata); + BOOST_CHECK(ProduceSignature(keystore, MutableTransactionSignatureCreator(&tx, i, 11*CENT, SIGHASH_ALL), spend_tx.vout[i].scriptPubKey, sigdata)); UpdateInput(tx.vin[i], sigdata); } // This should be valid under all script flags - ValidateCheckInputsForAllFlags(tx, 0, true); + ValidateCheckInputsForAllFlags(CTransaction(tx), 0, true); // Check that if the second input is invalid, but the first input is // valid, the transaction is not cached. @@ -357,12 +357,12 @@ BOOST_FIXTURE_TEST_CASE(checkinputs_test, TestChain100Setup) CValidationState state; PrecomputedTransactionData txdata(tx); // This transaction is now invalid under segwit, because of the second input. - BOOST_CHECK(!CheckInputs(tx, state, pcoinsTip.get(), true, SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_WITNESS, true, true, txdata, nullptr)); + BOOST_CHECK(!CheckInputs(CTransaction(tx), state, pcoinsTip.get(), true, SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_WITNESS, true, true, txdata, nullptr)); std::vector<CScriptCheck> scriptchecks; // Make sure this transaction was not cached (ie because the first // input was valid) - BOOST_CHECK(CheckInputs(tx, state, pcoinsTip.get(), true, SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_WITNESS, true, true, txdata, &scriptchecks)); + BOOST_CHECK(CheckInputs(CTransaction(tx), state, pcoinsTip.get(), true, SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_WITNESS, true, true, txdata, &scriptchecks)); // Should get 2 script checks back -- caching is on a whole-transaction basis. BOOST_CHECK_EQUAL(scriptchecks.size(), 2U); } diff --git a/src/test/util_tests.cpp b/src/test/util_tests.cpp index c74eb7531a..71b6ec7425 100644 --- a/src/test/util_tests.cpp +++ b/src/test/util_tests.cpp @@ -2,13 +2,13 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#include <util.h> +#include <util/system.h> #include <clientversion.h> #include <primitives/transaction.h> #include <sync.h> -#include <utilstrencodings.h> -#include <utilmoneystr.h> +#include <util/strencodings.h> +#include <util/moneystr.h> #include <test/test_bitcoin.h> #include <stdint.h> @@ -187,7 +187,7 @@ struct TestArgsManager : public ArgsManager m_config_args.clear(); } std::string error; - ReadConfigStream(streamConfig, error); + BOOST_REQUIRE(ReadConfigStream(streamConfig, error)); } void SetNetworkOnlyArg(const std::string arg) { @@ -210,13 +210,13 @@ BOOST_AUTO_TEST_CASE(util_ParseParameters) std::string error; testArgs.SetupArgs(4, avail_args); - testArgs.ParseParameters(0, (char**)argv_test, error); + BOOST_CHECK(testArgs.ParseParameters(0, (char**)argv_test, error)); BOOST_CHECK(testArgs.GetOverrideArgs().empty() && testArgs.GetConfigArgs().empty()); - testArgs.ParseParameters(1, (char**)argv_test, error); + BOOST_CHECK(testArgs.ParseParameters(1, (char**)argv_test, error)); BOOST_CHECK(testArgs.GetOverrideArgs().empty() && testArgs.GetConfigArgs().empty()); - testArgs.ParseParameters(7, (char**)argv_test, error); + BOOST_CHECK(testArgs.ParseParameters(7, (char**)argv_test, error)); // expectation: -ignored is ignored (program name argument), // -a, -b and -ccc end up in map, -d ignored because it is after // a non-option argument (non-GNU option parsing) @@ -242,7 +242,7 @@ BOOST_AUTO_TEST_CASE(util_GetBoolArg) "ignored", "-a", "-nob", "-c=0", "-d=1", "-e=false", "-f=true"}; std::string error; testArgs.SetupArgs(6, avail_args); - testArgs.ParseParameters(7, (char**)argv_test, error); + BOOST_CHECK(testArgs.ParseParameters(7, (char**)argv_test, error)); // Each letter should be set. for (const char opt : "abcdef") @@ -278,7 +278,7 @@ BOOST_AUTO_TEST_CASE(util_GetBoolArgEdgeCases) const char *argv_test[] = {"ignored", "-nofoo", "-foo", "-nobar=0"}; testArgs.SetupArgs(2, avail_args); std::string error; - testArgs.ParseParameters(4, (char**)argv_test, error); + BOOST_CHECK(testArgs.ParseParameters(4, (char**)argv_test, error)); // This was passed twice, second one overrides the negative setting. BOOST_CHECK(!testArgs.IsArgNegated("-foo")); @@ -290,7 +290,7 @@ BOOST_AUTO_TEST_CASE(util_GetBoolArgEdgeCases) // Config test const char *conf_test = "nofoo=1\nfoo=1\nnobar=0\n"; - testArgs.ParseParameters(1, (char**)argv_test, error); + BOOST_CHECK(testArgs.ParseParameters(1, (char**)argv_test, error)); testArgs.ReadConfigString(conf_test); // This was passed twice, second one overrides the negative setting, @@ -305,7 +305,7 @@ BOOST_AUTO_TEST_CASE(util_GetBoolArgEdgeCases) // Combined test const char *combo_test_args[] = {"ignored", "-nofoo", "-bar"}; const char *combo_test_conf = "foo=1\nnobar=1\n"; - testArgs.ParseParameters(3, (char**)combo_test_args, error); + BOOST_CHECK(testArgs.ParseParameters(3, (char**)combo_test_args, error)); testArgs.ReadConfigString(combo_test_conf); // Command line overrides, but doesn't erase old setting @@ -557,38 +557,38 @@ BOOST_AUTO_TEST_CASE(util_GetChainName) const char* testnetconf = "testnet=1\nregtest=0\n[test]\nregtest=1"; std::string error; - test_args.ParseParameters(0, (char**)argv_testnet, error); + BOOST_CHECK(test_args.ParseParameters(0, (char**)argv_testnet, error)); BOOST_CHECK_EQUAL(test_args.GetChainName(), "main"); - test_args.ParseParameters(2, (char**)argv_testnet, error); + BOOST_CHECK(test_args.ParseParameters(2, (char**)argv_testnet, error)); BOOST_CHECK_EQUAL(test_args.GetChainName(), "test"); - test_args.ParseParameters(2, (char**)argv_regtest, error); + BOOST_CHECK(test_args.ParseParameters(2, (char**)argv_regtest, error)); BOOST_CHECK_EQUAL(test_args.GetChainName(), "regtest"); - test_args.ParseParameters(3, (char**)argv_test_no_reg, error); + BOOST_CHECK(test_args.ParseParameters(3, (char**)argv_test_no_reg, error)); BOOST_CHECK_EQUAL(test_args.GetChainName(), "test"); - test_args.ParseParameters(3, (char**)argv_both, error); + BOOST_CHECK(test_args.ParseParameters(3, (char**)argv_both, error)); BOOST_CHECK_THROW(test_args.GetChainName(), std::runtime_error); - test_args.ParseParameters(0, (char**)argv_testnet, error); + BOOST_CHECK(test_args.ParseParameters(0, (char**)argv_testnet, error)); test_args.ReadConfigString(testnetconf); BOOST_CHECK_EQUAL(test_args.GetChainName(), "test"); - test_args.ParseParameters(2, (char**)argv_testnet, error); + BOOST_CHECK(test_args.ParseParameters(2, (char**)argv_testnet, error)); test_args.ReadConfigString(testnetconf); BOOST_CHECK_EQUAL(test_args.GetChainName(), "test"); - test_args.ParseParameters(2, (char**)argv_regtest, error); + BOOST_CHECK(test_args.ParseParameters(2, (char**)argv_regtest, error)); test_args.ReadConfigString(testnetconf); BOOST_CHECK_THROW(test_args.GetChainName(), std::runtime_error); - test_args.ParseParameters(3, (char**)argv_test_no_reg, error); + BOOST_CHECK(test_args.ParseParameters(3, (char**)argv_test_no_reg, error)); test_args.ReadConfigString(testnetconf); BOOST_CHECK_EQUAL(test_args.GetChainName(), "test"); - test_args.ParseParameters(3, (char**)argv_both, error); + BOOST_CHECK(test_args.ParseParameters(3, (char**)argv_both, error)); test_args.ReadConfigString(testnetconf); BOOST_CHECK_THROW(test_args.GetChainName(), std::runtime_error); @@ -596,23 +596,23 @@ BOOST_AUTO_TEST_CASE(util_GetChainName) // [test] regtest=1 potentially relevant) doesn't break things test_args.SelectConfigNetwork("test"); - test_args.ParseParameters(0, (char**)argv_testnet, error); + BOOST_CHECK(test_args.ParseParameters(0, (char**)argv_testnet, error)); test_args.ReadConfigString(testnetconf); BOOST_CHECK_EQUAL(test_args.GetChainName(), "test"); - test_args.ParseParameters(2, (char**)argv_testnet, error); + BOOST_CHECK(test_args.ParseParameters(2, (char**)argv_testnet, error)); test_args.ReadConfigString(testnetconf); BOOST_CHECK_EQUAL(test_args.GetChainName(), "test"); - test_args.ParseParameters(2, (char**)argv_regtest, error); + BOOST_CHECK(test_args.ParseParameters(2, (char**)argv_regtest, error)); test_args.ReadConfigString(testnetconf); BOOST_CHECK_THROW(test_args.GetChainName(), std::runtime_error); - test_args.ParseParameters(2, (char**)argv_test_no_reg, error); + BOOST_CHECK(test_args.ParseParameters(2, (char**)argv_test_no_reg, error)); test_args.ReadConfigString(testnetconf); BOOST_CHECK_EQUAL(test_args.GetChainName(), "test"); - test_args.ParseParameters(3, (char**)argv_both, error); + BOOST_CHECK(test_args.ParseParameters(3, (char**)argv_both, error)); test_args.ReadConfigString(testnetconf); BOOST_CHECK_THROW(test_args.GetChainName(), std::runtime_error); } @@ -1224,7 +1224,7 @@ BOOST_AUTO_TEST_CASE(test_ToLower) BOOST_CHECK_EQUAL(ToLower('Z'), 'z'); BOOST_CHECK_EQUAL(ToLower('['), '['); BOOST_CHECK_EQUAL(ToLower(0), 0); - BOOST_CHECK_EQUAL(ToLower(255), 255); + BOOST_CHECK_EQUAL(ToLower('\xff'), '\xff'); std::string testVector; Downcase(testVector); @@ -1246,7 +1246,7 @@ BOOST_AUTO_TEST_CASE(test_ToUpper) BOOST_CHECK_EQUAL(ToUpper('z'), 'Z'); BOOST_CHECK_EQUAL(ToUpper('{'), '{'); BOOST_CHECK_EQUAL(ToUpper(0), 0); - BOOST_CHECK_EQUAL(ToUpper(255), 255); + BOOST_CHECK_EQUAL(ToUpper('\xff'), '\xff'); } BOOST_AUTO_TEST_CASE(test_Capitalize) diff --git a/src/test/validation_block_tests.cpp b/src/test/validation_block_tests.cpp index 4316f37999..44432cd0a1 100644 --- a/src/test/validation_block_tests.cpp +++ b/src/test/validation_block_tests.cpp @@ -54,7 +54,7 @@ std::shared_ptr<CBlock> Block(const uint256& prev_hash) CScript pubKey; pubKey << i++ << OP_TRUE; - auto ptemplate = BlockAssembler(Params()).CreateNewBlock(pubKey, false); + auto ptemplate = BlockAssembler(Params()).CreateNewBlock(pubKey); auto pblock = std::make_shared<CBlock>(ptemplate->block); pblock->hashPrevBlock = prev_hash; pblock->nTime = ++time; @@ -104,8 +104,8 @@ void BuildChain(const uint256& root, int height, const unsigned int invalid_rate { if (height <= 0 || blocks.size() >= max_size) return; - bool gen_invalid = GetRand(100) < invalid_rate; - bool gen_fork = GetRand(100) < branch_rate; + bool gen_invalid = InsecureRandRange(100) < invalid_rate; + bool gen_fork = InsecureRandRange(100) < branch_rate; const std::shared_ptr<const CBlock> pblock = gen_invalid ? BadBlock(root) : GoodBlock(root); blocks.push_back(pblock); @@ -137,7 +137,7 @@ BOOST_AUTO_TEST_CASE(processnewblock_signals_ordering) BOOST_CHECK(ProcessNewBlockHeaders(headers, state, Params())); // Connect the genesis block and drain any outstanding events - ProcessNewBlock(Params(), std::make_shared<CBlock>(Params().GenesisBlock()), true, &ignored); + BOOST_CHECK(ProcessNewBlock(Params(), std::make_shared<CBlock>(Params().GenesisBlock()), true, &ignored)); SyncWithValidationInterfaceQueue(); // subscribe to events (this subscriber will validate event ordering) @@ -152,12 +152,13 @@ BOOST_AUTO_TEST_CASE(processnewblock_signals_ordering) // create a bunch of threads that repeatedly process a block generated above at random // this will create parallelism and randomness inside validation - the ValidationInterface // will subscribe to events generated during block validation and assert on ordering invariance - boost::thread_group threads; + std::vector<std::thread> threads; for (int i = 0; i < 10; i++) { - threads.create_thread([&blocks]() { + threads.emplace_back([&blocks]() { bool ignored; + FastRandomContext insecure; for (int i = 0; i < 1000; i++) { - auto block = blocks[GetRand(blocks.size() - 1)]; + auto block = blocks[insecure.randrange(blocks.size() - 1)]; ProcessNewBlock(Params(), block, true, &ignored); } @@ -171,7 +172,9 @@ BOOST_AUTO_TEST_CASE(processnewblock_signals_ordering) }); } - threads.join_all(); + for (auto& t : threads) { + t.join(); + } while (GetMainSignals().CallbacksPending() > 0) { MilliSleep(100); } diff --git a/src/threadinterrupt.h b/src/threadinterrupt.h index 9c6fccfcde..2743571379 100644 --- a/src/threadinterrupt.h +++ b/src/threadinterrupt.h @@ -1,4 +1,4 @@ -// Copyright (c) 2016-2017 The Bitcoin Core developers +// Copyright (c) 2016-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. diff --git a/src/threadsafety.h b/src/threadsafety.h index 47e6b2ea38..33acddc65c 100644 --- a/src/threadsafety.h +++ b/src/threadsafety.h @@ -54,4 +54,15 @@ #define ASSERT_EXCLUSIVE_LOCK(...) #endif // __GNUC__ +// Utility class for indicating to compiler thread analysis that a mutex is +// locked (when it couldn't be determined otherwise). +struct SCOPED_LOCKABLE LockAnnotation +{ + template <typename Mutex> + explicit LockAnnotation(Mutex& mutex) EXCLUSIVE_LOCK_FUNCTION(mutex) + { + } + ~LockAnnotation() UNLOCK_FUNCTION() {} +}; + #endif // BITCOIN_THREADSAFETY_H diff --git a/src/timedata.cpp b/src/timedata.cpp index 291111feb2..9c022c9ad1 100644 --- a/src/timedata.cpp +++ b/src/timedata.cpp @@ -11,8 +11,8 @@ #include <netaddress.h> #include <sync.h> #include <ui_interface.h> -#include <util.h> -#include <utilstrencodings.h> +#include <util/system.h> +#include <util/strencodings.h> #include <warnings.h> diff --git a/src/torcontrol.cpp b/src/torcontrol.cpp index c88f61f1ec..550e23b222 100644 --- a/src/torcontrol.cpp +++ b/src/torcontrol.cpp @@ -4,10 +4,10 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include <torcontrol.h> -#include <utilstrencodings.h> +#include <util/strencodings.h> #include <netbase.h> #include <net.h> -#include <util.h> +#include <util/system.h> #include <crypto/hmac_sha256.h> #include <vector> @@ -15,7 +15,6 @@ #include <set> #include <stdlib.h> -#include <boost/bind.hpp> #include <boost/signals2/signal.hpp> #include <boost/algorithm/string/split.hpp> #include <boost/algorithm/string/classification.hpp> @@ -459,8 +458,8 @@ TorController::TorController(struct event_base* _base, const std::string& _targe if (!reconnect_ev) LogPrintf("tor: Failed to create event for reconnection: out of memory?\n"); // Start connection attempts immediately - if (!conn.Connect(_target, boost::bind(&TorController::connected_cb, this, _1), - boost::bind(&TorController::disconnected_cb, this, _1) )) { + if (!conn.Connect(_target, std::bind(&TorController::connected_cb, this, std::placeholders::_1), + std::bind(&TorController::disconnected_cb, this, std::placeholders::_1) )) { LogPrintf("tor: Initiating connection to Tor control port %s failed\n", _target); } // Read service private key if cached @@ -528,7 +527,7 @@ void TorController::auth_cb(TorControlConnection& _conn, const TorControlReply& CService resolved(LookupNumeric("127.0.0.1", 9050)); proxyType addrOnion = proxyType(resolved, true); SetProxy(NET_ONION, addrOnion); - SetLimited(NET_ONION, false); + SetReachable(NET_ONION, true); } // Finally - now create the service @@ -538,7 +537,7 @@ void TorController::auth_cb(TorControlConnection& _conn, const TorControlReply& // Note that the 'virtual' port doesn't have to be the same as our internal port, but this is just a convenient // choice. TODO; refactor the shutdown sequence some day. _conn.Command(strprintf("ADD_ONION %s Port=%i,127.0.0.1:%i", private_key, GetListenPort(), GetListenPort()), - boost::bind(&TorController::add_onion_cb, this, _1, _2)); + std::bind(&TorController::add_onion_cb, this, std::placeholders::_1, std::placeholders::_2)); } else { LogPrintf("tor: Authentication failed\n"); } @@ -597,7 +596,7 @@ void TorController::authchallenge_cb(TorControlConnection& _conn, const TorContr } std::vector<uint8_t> computedClientHash = ComputeResponse(TOR_SAFE_CLIENTKEY, cookie, clientNonce, serverNonce); - _conn.Command("AUTHENTICATE " + HexStr(computedClientHash), boost::bind(&TorController::auth_cb, this, _1, _2)); + _conn.Command("AUTHENTICATE " + HexStr(computedClientHash), std::bind(&TorController::auth_cb, this, std::placeholders::_1, std::placeholders::_2)); } else { LogPrintf("tor: Invalid reply to AUTHCHALLENGE\n"); } @@ -646,23 +645,23 @@ void TorController::protocolinfo_cb(TorControlConnection& _conn, const TorContro if (methods.count("HASHEDPASSWORD")) { LogPrint(BCLog::TOR, "tor: Using HASHEDPASSWORD authentication\n"); boost::replace_all(torpassword, "\"", "\\\""); - _conn.Command("AUTHENTICATE \"" + torpassword + "\"", boost::bind(&TorController::auth_cb, this, _1, _2)); + _conn.Command("AUTHENTICATE \"" + torpassword + "\"", std::bind(&TorController::auth_cb, this, std::placeholders::_1, std::placeholders::_2)); } else { LogPrintf("tor: Password provided with -torpassword, but HASHEDPASSWORD authentication is not available\n"); } } else if (methods.count("NULL")) { LogPrint(BCLog::TOR, "tor: Using NULL authentication\n"); - _conn.Command("AUTHENTICATE", boost::bind(&TorController::auth_cb, this, _1, _2)); + _conn.Command("AUTHENTICATE", std::bind(&TorController::auth_cb, this, std::placeholders::_1, std::placeholders::_2)); } else if (methods.count("SAFECOOKIE")) { // Cookie: hexdump -e '32/1 "%02x""\n"' ~/.tor/control_auth_cookie LogPrint(BCLog::TOR, "tor: Using SAFECOOKIE authentication, reading cookie authentication from %s\n", cookiefile); std::pair<bool,std::string> status_cookie = ReadBinaryFile(cookiefile, TOR_COOKIE_SIZE); if (status_cookie.first && status_cookie.second.size() == TOR_COOKIE_SIZE) { - // _conn.Command("AUTHENTICATE " + HexStr(status_cookie.second), boost::bind(&TorController::auth_cb, this, _1, _2)); + // _conn.Command("AUTHENTICATE " + HexStr(status_cookie.second), std::bind(&TorController::auth_cb, this, std::placeholders::_1, std::placeholders::_2)); cookie = std::vector<uint8_t>(status_cookie.second.begin(), status_cookie.second.end()); clientNonce = std::vector<uint8_t>(TOR_NONCE_SIZE, 0); GetRandBytes(clientNonce.data(), TOR_NONCE_SIZE); - _conn.Command("AUTHCHALLENGE SAFECOOKIE " + HexStr(clientNonce), boost::bind(&TorController::authchallenge_cb, this, _1, _2)); + _conn.Command("AUTHCHALLENGE SAFECOOKIE " + HexStr(clientNonce), std::bind(&TorController::authchallenge_cb, this, std::placeholders::_1, std::placeholders::_2)); } else { if (status_cookie.first) { LogPrintf("tor: Authentication cookie %s is not exactly %i bytes, as is required by the spec\n", cookiefile, TOR_COOKIE_SIZE); @@ -684,7 +683,7 @@ void TorController::connected_cb(TorControlConnection& _conn) { reconnect_timeout = RECONNECT_TIMEOUT_START; // First send a PROTOCOLINFO command to figure out what authentication is expected - if (!_conn.Command("PROTOCOLINFO 1", boost::bind(&TorController::protocolinfo_cb, this, _1, _2))) + if (!_conn.Command("PROTOCOLINFO 1", std::bind(&TorController::protocolinfo_cb, this, std::placeholders::_1, std::placeholders::_2))) LogPrintf("tor: Error sending initial protocolinfo command\n"); } @@ -711,8 +710,8 @@ void TorController::Reconnect() /* Try to reconnect and reestablish if we get booted - for example, Tor * may be restarting. */ - if (!conn.Connect(target, boost::bind(&TorController::connected_cb, this, _1), - boost::bind(&TorController::disconnected_cb, this, _1) )) { + if (!conn.Connect(target, std::bind(&TorController::connected_cb, this, std::placeholders::_1), + std::bind(&TorController::disconnected_cb, this, std::placeholders::_1) )) { LogPrintf("tor: Re-initiating connection to Tor control port %s failed\n", target); } } diff --git a/src/txdb.cpp b/src/txdb.cpp index cbea550739..8447352c54 100644 --- a/src/txdb.cpp +++ b/src/txdb.cpp @@ -11,7 +11,7 @@ #include <pow.h> #include <shutdown.h> #include <uint256.h> -#include <util.h> +#include <util/system.h> #include <ui_interface.h> #include <stdint.h> diff --git a/src/txmempool.cpp b/src/txmempool.cpp index 3ad93342c4..68f47d5cce 100644 --- a/src/txmempool.cpp +++ b/src/txmempool.cpp @@ -14,9 +14,9 @@ #include <reverse_iterator.h> #include <streams.h> #include <timedata.h> -#include <util.h> -#include <utilmoneystr.h> -#include <utiltime.h> +#include <util/system.h> +#include <util/moneystr.h> +#include <util/time.h> CTxMemPoolEntry::CTxMemPoolEntry(const CTransactionRef& _tx, const CAmount& _nFee, int64_t _nTime, unsigned int _entryHeight, @@ -498,7 +498,7 @@ void CTxMemPool::removeForReorg(const CCoinsViewCache *pcoins, unsigned int nMem const CTransaction& tx = it->GetTx(); LockPoints lp = it->GetLockPoints(); bool validLP = TestLockPointValidity(&lp); - if (!CheckFinalTx(tx, flags) || !CheckSequenceLocks(tx, flags, &lp, validLP)) { + if (!CheckFinalTx(tx, flags) || !CheckSequenceLocks(*this, tx, flags, &lp, validLP)) { // Note if CheckSequenceLocks fails the LockPoints may still be invalid // So it's critical that we remove the tx and not depend on the LockPoints. txToRemove.insert(it); diff --git a/src/txmempool.h b/src/txmempool.h index 913501fd66..b10c9f099f 100644 --- a/src/txmempool.h +++ b/src/txmempool.h @@ -15,6 +15,7 @@ #include <amount.h> #include <coins.h> +#include <crypto/siphash.h> #include <indirectmap.h> #include <policy/feerate.h> #include <primitives/transaction.h> @@ -343,13 +344,13 @@ struct TxMempoolInfo * this is passed to the notification signal. */ enum class MemPoolRemovalReason { - UNKNOWN = 0, //! Manually removed or unknown reason - EXPIRY, //! Expired from mempool - SIZELIMIT, //! Removed in size limiting - REORG, //! Removed for reorganization - BLOCK, //! Removed for block - CONFLICT, //! Removed for conflict with in-block transaction - REPLACED //! Removed for replacement + UNKNOWN = 0, //!< Manually removed or unknown reason + EXPIRY, //!< Expired from mempool + SIZELIMIT, //!< Removed in size limiting + REORG, //!< Removed for reorganization + BLOCK, //!< Removed for block + CONFLICT, //!< Removed for conflict with in-block transaction + REPLACED, //!< Removed for replacement }; class SaltedTxidHasher @@ -484,7 +485,43 @@ public: > > indexed_transaction_set; - mutable CCriticalSection cs; + /** + * This mutex needs to be locked when accessing `mapTx` or other members + * that are guarded by it. + * + * @par Consistency guarantees + * + * By design, it is guaranteed that: + * + * 1. Locking both `cs_main` and `mempool.cs` will give a view of mempool + * that is consistent with current chain tip (`chainActive` and + * `pcoinsTip`) and is fully populated. Fully populated means that if the + * current active chain is missing transactions that were present in a + * previously active chain, all the missing transactions will have been + * re-added to the mempool and should be present if they meet size and + * consistency constraints. + * + * 2. Locking `mempool.cs` without `cs_main` will give a view of a mempool + * consistent with some chain that was active since `cs_main` was last + * locked, and that is fully populated as described above. It is ok for + * code that only needs to query or remove transactions from the mempool + * to lock just `mempool.cs` without `cs_main`. + * + * To provide these guarantees, it is necessary to lock both `cs_main` and + * `mempool.cs` whenever adding transactions to the mempool and whenever + * changing the chain tip. It's necessary to keep both mutexes locked until + * the mempool is consistent with the new chain tip and fully populated. + * + * @par Consistency bug + * + * The second guarantee above is not currently enforced, but + * https://github.com/bitcoin/bitcoin/pull/14193 will fix it. No known code + * in bitcoin currently depends on second guarantee, but it is important to + * fix for third party code that needs be able to frequently poll the + * mempool without locking `cs_main` and without encountering missing + * transactions during reorgs. + */ + mutable RecursiveMutex cs; indexed_transaction_set mapTx GUARDED_BY(cs); using txiter = indexed_transaction_set::nth_index<0>::type::const_iterator; @@ -540,8 +577,8 @@ public: // Note that addUnchecked is ONLY called from ATMP outside of tests // and any other callers may break wallet's in-mempool tracking (due to // lack of CValidationInterface::TransactionAddedToMempool callbacks). - void addUnchecked(const CTxMemPoolEntry& entry, bool validFeeEstimate = true) EXCLUSIVE_LOCKS_REQUIRED(cs); - void addUnchecked(const CTxMemPoolEntry& entry, setEntries& setAncestors, bool validFeeEstimate = true) EXCLUSIVE_LOCKS_REQUIRED(cs); + void addUnchecked(const CTxMemPoolEntry& entry, bool validFeeEstimate = true) EXCLUSIVE_LOCKS_REQUIRED(cs, cs_main); + void addUnchecked(const CTxMemPoolEntry& entry, setEntries& setAncestors, bool validFeeEstimate = true) EXCLUSIVE_LOCKS_REQUIRED(cs, cs_main); void removeRecursive(const CTransaction &tx, MemPoolRemovalReason reason = MemPoolRemovalReason::UNKNOWN); void removeForReorg(const CCoinsViewCache *pcoins, unsigned int nMemPoolHeight, int flags) EXCLUSIVE_LOCKS_REQUIRED(cs_main); @@ -593,7 +630,7 @@ public: * for). Note: vHashesToUpdate should be the set of transactions from the * disconnected block that have been accepted back into the mempool. */ - void UpdateTransactionsFromBlock(const std::vector<uint256> &vHashesToUpdate); + void UpdateTransactionsFromBlock(const std::vector<uint256>& vHashesToUpdate) EXCLUSIVE_LOCKS_REQUIRED(cs_main); /** Try to calculate all in-mempool ancestors of entry. * (these are all calculated including the tx itself) @@ -635,7 +672,7 @@ public: */ void GetTransactionAncestry(const uint256& txid, size_t& ancestors, size_t& descendants) const; - unsigned long size() + unsigned long size() const { LOCK(cs); return mapTx.size(); diff --git a/src/ui_interface.cpp b/src/ui_interface.cpp index 22b4768059..947d7e2308 100644 --- a/src/ui_interface.cpp +++ b/src/ui_interface.cpp @@ -3,7 +3,7 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include <ui_interface.h> -#include <util.h> +#include <util/system.h> #include <boost/signals2/last_value.hpp> #include <boost/signals2/signal.hpp> diff --git a/src/uint256.cpp b/src/uint256.cpp index e513dc1de7..e3bc9712e8 100644 --- a/src/uint256.cpp +++ b/src/uint256.cpp @@ -5,7 +5,7 @@ #include <uint256.h> -#include <utilstrencodings.h> +#include <util/strencodings.h> #include <stdio.h> #include <string.h> @@ -29,11 +29,11 @@ void base_blob<BITS>::SetHex(const char* psz) memset(data, 0, sizeof(data)); // skip leading spaces - while (isspace(*psz)) + while (IsSpace(*psz)) psz++; // skip 0x - if (psz[0] == '0' && tolower(psz[1]) == 'x') + if (psz[0] == '0' && ToLower(psz[1]) == 'x') psz += 2; // hex string to uint diff --git a/src/uint256.h b/src/uint256.h index 26a3331d92..97e0cfa015 100644 --- a/src/uint256.h +++ b/src/uint256.h @@ -12,7 +12,6 @@ #include <stdint.h> #include <string> #include <vector> -#include <crypto/common.h> /** Template base class for fixed-sized opaque blobs. */ template<unsigned int BITS> @@ -123,16 +122,6 @@ class uint256 : public base_blob<256> { public: uint256() {} explicit uint256(const std::vector<unsigned char>& vch) : base_blob<256>(vch) {} - - /** A cheap hash function that just returns 64 bits from the result, it can be - * used when the contents are considered uniformly random. It is not appropriate - * when the value can easily be influenced from outside as e.g. a network adversary could - * provide values to trigger worst-case behavior. - */ - uint64_t GetCheapHash() const - { - return ReadLE64(data); - } }; /* uint256 from const char *. diff --git a/src/undo.h b/src/undo.h index 4ed3dc4ca0..3f50f4caad 100644 --- a/src/undo.h +++ b/src/undo.h @@ -11,6 +11,7 @@ #include <consensus/consensus.h> #include <primitives/transaction.h> #include <serialize.h> +#include <version.h> /** Undo information for a CTxIn * diff --git a/src/util/bytevectorhash.cpp b/src/util/bytevectorhash.cpp new file mode 100644 index 0000000000..f87d0e04b3 --- /dev/null +++ b/src/util/bytevectorhash.cpp @@ -0,0 +1,18 @@ +// Copyright (c) 2018 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include <crypto/siphash.h> +#include <random.h> +#include <util/bytevectorhash.h> + +ByteVectorHash::ByteVectorHash() +{ + GetRandBytes(reinterpret_cast<unsigned char*>(&m_k0), sizeof(m_k0)); + GetRandBytes(reinterpret_cast<unsigned char*>(&m_k1), sizeof(m_k1)); +} + +size_t ByteVectorHash::operator()(const std::vector<unsigned char>& input) const +{ + return CSipHasher(m_k0, m_k1).Write(input.data(), input.size()).Finalize(); +} diff --git a/src/util/bytevectorhash.h b/src/util/bytevectorhash.h new file mode 100644 index 0000000000..b88c17460b --- /dev/null +++ b/src/util/bytevectorhash.h @@ -0,0 +1,26 @@ +// Copyright (c) 2018 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_UTIL_BYTEVECTORHASH_H +#define BITCOIN_UTIL_BYTEVECTORHASH_H + +#include <stdint.h> +#include <vector> + +/** + * Implementation of Hash named requirement for types that internally store a byte array. This may + * be used as the hash function in std::unordered_set or std::unordered_map over such types. + * Internally, this uses a random instance of SipHash-2-4. + */ +class ByteVectorHash final +{ +private: + uint64_t m_k0, m_k1; + +public: + ByteVectorHash(); + size_t operator()(const std::vector<unsigned char>& input) const; +}; + +#endif // BITCOIN_UTIL_BYTEVECTORHASH_H diff --git a/src/utilmemory.h b/src/util/memory.h index e71fe92284..15ecf8f80d 100644 --- a/src/utilmemory.h +++ b/src/util/memory.h @@ -3,8 +3,8 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#ifndef BITCOIN_UTILMEMORY_H -#define BITCOIN_UTILMEMORY_H +#ifndef BITCOIN_UTIL_MEMORY_H +#define BITCOIN_UTIL_MEMORY_H #include <memory> #include <utility> diff --git a/src/utilmoneystr.cpp b/src/util/moneystr.cpp index 326ef9b27a..f4e41eea4f 100644 --- a/src/utilmoneystr.cpp +++ b/src/util/moneystr.cpp @@ -3,11 +3,11 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#include <utilmoneystr.h> +#include <util/moneystr.h> #include <primitives/transaction.h> #include <tinyformat.h> -#include <utilstrencodings.h> +#include <util/strencodings.h> std::string FormatMoney(const CAmount& n) { @@ -20,7 +20,7 @@ std::string FormatMoney(const CAmount& n) // Right-trim excess zeros before the decimal point: int nTrim = 0; - for (int i = str.size()-1; (str[i] == '0' && isdigit(str[i-2])); --i) + for (int i = str.size()-1; (str[i] == '0' && IsDigit(str[i-2])); --i) ++nTrim; if (nTrim) str.erase(str.size()-nTrim, nTrim); @@ -41,7 +41,7 @@ bool ParseMoney(const char* pszIn, CAmount& nRet) std::string strWhole; int64_t nUnits = 0; const char* p = pszIn; - while (isspace(*p)) + while (IsSpace(*p)) p++; for (; *p; p++) { @@ -49,21 +49,21 @@ bool ParseMoney(const char* pszIn, CAmount& nRet) { p++; int64_t nMult = COIN / 10; - while (isdigit(*p) && (nMult > 0)) + while (IsDigit(*p) && (nMult > 0)) { nUnits += nMult * (*p++ - '0'); nMult /= 10; } break; } - if (isspace(*p)) + if (IsSpace(*p)) break; - if (!isdigit(*p)) + if (!IsDigit(*p)) return false; strWhole.insert(strWhole.end(), *p); } for (; *p; p++) - if (!isspace(*p)) + if (!IsSpace(*p)) return false; if (strWhole.size() > 10) // guard against 63 bit overflow return false; diff --git a/src/utilmoneystr.h b/src/util/moneystr.h index 67579adb77..b8e2812a96 100644 --- a/src/utilmoneystr.h +++ b/src/util/moneystr.h @@ -6,19 +6,20 @@ /** * Money parsing/formatting utilities. */ -#ifndef BITCOIN_UTILMONEYSTR_H -#define BITCOIN_UTILMONEYSTR_H - -#include <stdint.h> -#include <string> +#ifndef BITCOIN_UTIL_MONEYSTR_H +#define BITCOIN_UTIL_MONEYSTR_H #include <amount.h> +#include <attributes.h> + +#include <cstdint> +#include <string> /* Do not use these functions to represent or parse monetary amounts to or from * JSON but use AmountFromValue and ValueFromAmount for that. */ std::string FormatMoney(const CAmount& n); -bool ParseMoney(const std::string& str, CAmount& nRet); -bool ParseMoney(const char* pszIn, CAmount& nRet); +NODISCARD bool ParseMoney(const std::string& str, CAmount& nRet); +NODISCARD bool ParseMoney(const char* pszIn, CAmount& nRet); -#endif // BITCOIN_UTILMONEYSTR_H +#endif // BITCOIN_UTIL_MONEYSTR_H diff --git a/src/utilstrencodings.cpp b/src/util/strencodings.cpp index 4940267bae..fedeeac39b 100644 --- a/src/utilstrencodings.cpp +++ b/src/util/strencodings.cpp @@ -3,7 +3,7 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#include <utilstrencodings.h> +#include <util/strencodings.h> #include <tinyformat.h> @@ -20,6 +20,7 @@ static const std::string SAFE_CHARS[] = CHARS_ALPHA_NUM + " .,;-_/:?@()", // SAFE_CHARS_DEFAULT CHARS_ALPHA_NUM + " .,;-_?@", // SAFE_CHARS_UA_COMMENT CHARS_ALPHA_NUM + ".-_", // SAFE_CHARS_FILENAME + CHARS_ALPHA_NUM + "!*'();:@&=+$,/?#[]-_.~%", // SAFE_CHARS_URI }; std::string SanitizeString(const std::string& str, int rule) @@ -85,7 +86,7 @@ std::vector<unsigned char> ParseHex(const char* psz) std::vector<unsigned char> vch; while (true) { - while (isspace(*psz)) + while (IsSpace(*psz)) psz++; signed char c = HexDigit(*psz++); if (c == (signed char)-1) @@ -262,11 +263,11 @@ std::string DecodeBase32(const std::string& str) return std::string((const char*)vchRet.data(), vchRet.size()); } -static bool ParsePrechecks(const std::string& str) +NODISCARD static bool ParsePrechecks(const std::string& str) { if (str.empty()) // No empty string allowed return false; - if (str.size() >= 1 && (isspace(str[0]) || isspace(str[str.size()-1]))) // No padding allowed + if (str.size() >= 1 && (IsSpace(str[0]) || IsSpace(str[str.size()-1]))) // No padding allowed return false; if (str.size() != strlen(str.c_str())) // No embedded NUL characters allowed return false; @@ -588,7 +589,7 @@ bool ParseHDKeypath(const std::string& keypath_str, std::vector<uint32_t>& keypa void Downcase(std::string& str) { - std::transform(str.begin(), str.end(), str.begin(), [](unsigned char c){return ToLower(c);}); + std::transform(str.begin(), str.end(), str.begin(), [](char c){return ToLower(c);}); } std::string Capitalize(std::string str) diff --git a/src/utilstrencodings.h b/src/util/strencodings.h index 846a3917a2..e392055f27 100644 --- a/src/utilstrencodings.h +++ b/src/util/strencodings.h @@ -6,17 +6,15 @@ /** * Utilities for converting data from/to strings. */ -#ifndef BITCOIN_UTILSTRENCODINGS_H -#define BITCOIN_UTILSTRENCODINGS_H +#ifndef BITCOIN_UTIL_STRENCODINGS_H +#define BITCOIN_UTIL_STRENCODINGS_H -#include <stdint.h> +#include <attributes.h> + +#include <cstdint> #include <string> #include <vector> -#define BEGIN(a) ((char*)&(a)) -#define END(a) ((char*)&((&(a))[1])) -#define UBEGIN(a) ((unsigned char*)&(a)) -#define UEND(a) ((unsigned char*)&((&(a))[1])) #define ARRAYLEN(array) (sizeof(array)/sizeof((array)[0])) /** Used by SanitizeString() */ @@ -25,6 +23,7 @@ enum SafeChars SAFE_CHARS_DEFAULT, //!< The full set of allowed chars SAFE_CHARS_UA_COMMENT, //!< BIP-0014 subset SAFE_CHARS_FILENAME, //!< Chars allowed in filenames + SAFE_CHARS_URI, //!< Chars allowed in URIs (RFC 3986) }; /** @@ -72,39 +71,54 @@ constexpr bool IsDigit(char c) } /** + * Tests if the given character is a whitespace character. The whitespace characters + * are: space, form-feed ('\f'), newline ('\n'), carriage return ('\r'), horizontal + * tab ('\t'), and vertical tab ('\v'). + * + * This function is locale independent. Under the C locale this function gives the + * same result as std::isspace. + * + * @param[in] c character to test + * @return true if the argument is a whitespace character; otherwise false + */ +constexpr inline bool IsSpace(char c) noexcept { + return c == ' ' || c == '\f' || c == '\n' || c == '\r' || c == '\t' || c == '\v'; +} + +/** * Convert string to signed 32-bit integer with strict parse error feedback. * @returns true if the entire string could be parsed as valid integer, * false if not the entire string could be parsed or when overflow or underflow occurred. */ -bool ParseInt32(const std::string& str, int32_t *out); +NODISCARD bool ParseInt32(const std::string& str, int32_t *out); /** * Convert string to signed 64-bit integer with strict parse error feedback. * @returns true if the entire string could be parsed as valid integer, * false if not the entire string could be parsed or when overflow or underflow occurred. */ -bool ParseInt64(const std::string& str, int64_t *out); +NODISCARD bool ParseInt64(const std::string& str, int64_t *out); /** * Convert decimal string to unsigned 32-bit integer with strict parse error feedback. * @returns true if the entire string could be parsed as valid integer, * false if not the entire string could be parsed or when overflow or underflow occurred. */ -bool ParseUInt32(const std::string& str, uint32_t *out); +NODISCARD bool ParseUInt32(const std::string& str, uint32_t *out); /** * Convert decimal string to unsigned 64-bit integer with strict parse error feedback. * @returns true if the entire string could be parsed as valid integer, * false if not the entire string could be parsed or when overflow or underflow occurred. */ -bool ParseUInt64(const std::string& str, uint64_t *out); +NODISCARD bool ParseUInt64(const std::string& str, uint64_t *out); /** * Convert string to double with strict parse error feedback. * @returns true if the entire string could be parsed as valid double, * false if not the entire string could be parsed or when overflow or underflow occurred. */ -bool ParseDouble(const std::string& str, double *out); +NODISCARD bool ParseDouble(const std::string& str, double *out); template<typename T> std::string HexStr(const T itbegin, const T itend, bool fSpaces=false) @@ -157,7 +171,7 @@ bool TimingResistantEqual(const T& a, const T& b) * @returns true on success, false on error. * @note The result must be in the range (-10^18,10^18), otherwise an overflow error will trigger. */ -bool ParseFixedPoint(const std::string &val, int decimals, int64_t *amount_out); +NODISCARD bool ParseFixedPoint(const std::string &val, int decimals, int64_t *amount_out); /** Convert from one power-of-2 number base to another. */ template<int frombits, int tobits, bool pad, typename O, typename I> @@ -184,7 +198,7 @@ bool ConvertBits(const O& outfn, I it, I end) { } /** Parse an HD keypaths like "m/7/0'/2000". */ -bool ParseHDKeypath(const std::string& keypath_str, std::vector<uint32_t>& keypath); +NODISCARD bool ParseHDKeypath(const std::string& keypath_str, std::vector<uint32_t>& keypath); /** * Converts the given character to its lowercase equivalent. @@ -194,7 +208,7 @@ bool ParseHDKeypath(const std::string& keypath_str, std::vector<uint32_t>& keypa * @return the lowercase equivalent of c; or the argument * if no conversion is possible. */ -constexpr unsigned char ToLower(unsigned char c) +constexpr char ToLower(char c) { return (c >= 'A' && c <= 'Z' ? (c - 'A') + 'a' : c); } @@ -215,7 +229,7 @@ void Downcase(std::string& str); * @return the uppercase equivalent of c; or the argument * if no conversion is possible. */ -constexpr unsigned char ToUpper(unsigned char c) +constexpr char ToUpper(char c) { return (c >= 'a' && c <= 'z' ? (c - 'a') + 'A' : c); } @@ -230,4 +244,4 @@ constexpr unsigned char ToUpper(unsigned char c) */ std::string Capitalize(std::string str); -#endif // BITCOIN_UTILSTRENCODINGS_H +#endif // BITCOIN_UTIL_STRENCODINGS_H diff --git a/src/util.cpp b/src/util/system.cpp index fa624aee90..06317a3a90 100644 --- a/src/util.cpp +++ b/src/util/system.cpp @@ -3,12 +3,12 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#include <util.h> +#include <util/system.h> #include <chainparamsbase.h> #include <random.h> #include <serialize.h> -#include <utilstrencodings.h> +#include <util/strencodings.h> #include <stdarg.h> @@ -61,6 +61,7 @@ #include <codecvt> #include <io.h> /* for _commit */ +#include <shellapi.h> #include <shlobj.h> #endif @@ -72,10 +73,6 @@ #include <malloc.h> #endif -#include <boost/thread.hpp> -#include <openssl/crypto.h> -#include <openssl/rand.h> -#include <openssl/conf.h> #include <thread> // Application startup time (used for uptime calculation) @@ -86,54 +83,6 @@ const char * const BITCOIN_PID_FILENAME = "bitcoind.pid"; ArgsManager gArgs; -/** Init OpenSSL library multithreading support */ -static std::unique_ptr<CCriticalSection[]> ppmutexOpenSSL; -void locking_callback(int mode, int i, const char* file, int line) NO_THREAD_SAFETY_ANALYSIS -{ - if (mode & CRYPTO_LOCK) { - ENTER_CRITICAL_SECTION(ppmutexOpenSSL[i]); - } else { - LEAVE_CRITICAL_SECTION(ppmutexOpenSSL[i]); - } -} - -// Singleton for wrapping OpenSSL setup/teardown. -class CInit -{ -public: - CInit() - { - // Init OpenSSL library multithreading support - ppmutexOpenSSL.reset(new CCriticalSection[CRYPTO_num_locks()]); - CRYPTO_set_locking_callback(locking_callback); - - // OpenSSL can optionally load a config file which lists optional loadable modules and engines. - // We don't use them so we don't require the config. However some of our libs may call functions - // which attempt to load the config file, possibly resulting in an exit() or crash if it is missing - // or corrupt. Explicitly tell OpenSSL not to try to load the file. The result for our libs will be - // that the config appears to have been loaded and there are no modules/engines available. - OPENSSL_no_config(); - -#ifdef WIN32 - // Seed OpenSSL PRNG with current contents of the screen - RAND_screen(); -#endif - - // Seed OpenSSL PRNG with performance counter - RandAddSeed(); - } - ~CInit() - { - // Securely erase the memory used by the PRNG - RAND_cleanup(); - // Shutdown OpenSSL library multithreading support - CRYPTO_set_locking_callback(nullptr); - // Clear the set of locks now to maintain symmetry with the constructor. - ppmutexOpenSSL.reset(); - } -} -instance_of_cinit; - /** A map that contains all the currently held directory locks. After * successful locking, these will be held here until the global destructor * cleans them up and thus automatically unlocks them, or ReleaseDirectoryLocks @@ -371,15 +320,17 @@ ArgsManager::ArgsManager() : // nothing to do } -void ArgsManager::WarnForSectionOnlyArgs() +const std::set<std::string> ArgsManager::GetUnsuitableSectionOnlyArgs() const { + std::set<std::string> unsuitables; + LOCK(cs_args); // if there's no section selected, don't worry - if (m_network.empty()) return; + if (m_network.empty()) return std::set<std::string> {}; // if it's okay to use the default section for this network, don't worry - if (m_network == CBaseChainParams::MAIN) return; + if (m_network == CBaseChainParams::MAIN) return std::set<std::string> {}; for (const auto& arg : m_network_only_args) { std::pair<bool, std::string> found_result; @@ -397,8 +348,28 @@ void ArgsManager::WarnForSectionOnlyArgs() if (!found_result.first) continue; // otherwise, issue a warning - LogPrintf("Warning: Config setting for %s only applied on %s network when in [%s] section.\n", arg, m_network, m_network); + unsuitables.insert(arg); } + return unsuitables; +} + + +const std::set<std::string> ArgsManager::GetUnrecognizedSections() const +{ + // Section names to be recognized in the config file. + static const std::set<std::string> available_sections{ + CBaseChainParams::REGTEST, + CBaseChainParams::TESTNET, + CBaseChainParams::MAIN + }; + std::set<std::string> diff; + + LOCK(cs_args); + std::set_difference( + m_config_sections.begin(), m_config_sections.end(), + available_sections.begin(), available_sections.end(), + std::inserter(diff, diff.end())); + return diff; } void ArgsManager::SelectConfigNetwork(const std::string& network) @@ -421,7 +392,7 @@ bool ArgsManager::ParseParameters(int argc, const char* const argv[], std::strin key.erase(is_index); } #ifdef WIN32 - std::transform(key.begin(), key.end(), key.begin(), ::tolower); + std::transform(key.begin(), key.end(), key.begin(), ToLower); if (key[0] == '/') key[0] = '-'; #endif @@ -727,18 +698,17 @@ fs::path GetDefaultDataDir() #endif } -static fs::path g_blocks_path_cached; static fs::path g_blocks_path_cache_net_specific; static fs::path pathCached; static fs::path pathCachedNetSpecific; static CCriticalSection csPathCached; -const fs::path &GetBlocksDir(bool fNetSpecific) +const fs::path &GetBlocksDir() { LOCK(csPathCached); - fs::path &path = fNetSpecific ? g_blocks_path_cache_net_specific : g_blocks_path_cached; + fs::path &path = g_blocks_path_cache_net_specific; // This can be called during exceptions by LogPrintf(), so we cache the // value so we don't have to do memory allocations after that. @@ -754,9 +724,8 @@ const fs::path &GetBlocksDir(bool fNetSpecific) } else { path = GetDataDir(false); } - if (fNetSpecific) - path /= BaseParams().DataDir(); + path /= BaseParams().DataDir(); path /= "blocks"; fs::create_directories(path); return path; @@ -800,7 +769,6 @@ void ClearDatadirCache() pathCached = fs::path(); pathCachedNetSpecific = fs::path(); - g_blocks_path_cached = fs::path(); g_blocks_path_cache_net_specific = fs::path(); } @@ -819,27 +787,38 @@ static std::string TrimString(const std::string& str, const std::string& pattern return str.substr(front, end - front + 1); } -static bool GetConfigOptions(std::istream& stream, std::string& error, std::vector<std::pair<std::string, std::string>> &options) +static bool GetConfigOptions(std::istream& stream, std::string& error, std::vector<std::pair<std::string, std::string>>& options, std::set<std::string>& sections) { std::string str, prefix; std::string::size_type pos; int linenr = 1; while (std::getline(stream, str)) { + bool used_hash = false; if ((pos = str.find('#')) != std::string::npos) { str = str.substr(0, pos); + used_hash = true; } const static std::string pattern = " \t\r\n"; str = TrimString(str, pattern); if (!str.empty()) { if (*str.begin() == '[' && *str.rbegin() == ']') { - prefix = str.substr(1, str.size() - 2) + '.'; + const std::string section = str.substr(1, str.size() - 2); + sections.insert(section); + prefix = section + '.'; } else if (*str.begin() == '-') { error = strprintf("parse error on line %i: %s, options in configuration file must be specified without leading -", linenr, str); return false; } else if ((pos = str.find('=')) != std::string::npos) { std::string name = prefix + TrimString(str.substr(0, pos), pattern); std::string value = TrimString(str.substr(pos + 1), pattern); + if (used_hash && name.find("rpcpassword") != std::string::npos) { + error = strprintf("parse error on line %i, using # in rpcpassword can be ambiguous and should be avoided", linenr); + return false; + } options.emplace_back(name, value); + if ((pos = name.rfind('.')) != std::string::npos) { + sections.insert(name.substr(0, pos)); + } } else { error = strprintf("parse error on line %i: %s", linenr, str); if (str.size() >= 2 && str.substr(0, 2) == "no") { @@ -857,7 +836,8 @@ bool ArgsManager::ReadConfigStream(std::istream& stream, std::string& error, boo { LOCK(cs_args); std::vector<std::pair<std::string, std::string>> options; - if (!GetConfigOptions(stream, error, options)) { + m_config_sections.clear(); + if (!GetConfigOptions(stream, error, options, m_config_sections)) { return false; } for (const std::pair<std::string, std::string>& option : options) { @@ -891,7 +871,7 @@ bool ArgsManager::ReadConfigFiles(std::string& error, bool ignore_invalid_keys) } const std::string confPath = GetArg("-conf", BITCOIN_CONF_FILENAME); - fs::ifstream stream(GetConfigFile(confPath)); + fsbridge::ifstream stream(GetConfigFile(confPath)); // ok to not have a config file if (stream.good()) { @@ -924,7 +904,7 @@ bool ArgsManager::ReadConfigFiles(std::string& error, bool ignore_invalid_keys) } for (const std::string& to_include : includeconf) { - fs::ifstream include_config(GetConfigFile(to_include)); + fsbridge::ifstream include_config(GetConfigFile(to_include)); if (include_config.good()) { if (!ReadConfigStream(include_config, error, ignore_invalid_keys)) { return false; @@ -1200,6 +1180,10 @@ void SetupEnvironment() } catch (const std::runtime_error&) { setenv("LC_ALL", "C", 1); } +#elif defined(WIN32) + // Set the default input/output charset is utf-8 + SetConsoleCP(CP_UTF8); + SetConsoleOutputCP(CP_UTF8); #endif // The path locale is lazy initialized and to avoid deinitialization errors // in multithreading environments, it is set explicitly by the main thread. @@ -1255,7 +1239,7 @@ fs::path AbsPathForConfigVal(const fs::path& path, bool net_specific) int ScheduleBatchPriority() { #ifdef SCHED_BATCH - const static sched_param param{0}; + const static sched_param param{}; if (int ret = pthread_setschedparam(pthread_self(), SCHED_BATCH, ¶m)) { LogPrintf("Failed to pthread_setschedparam: %s\n", strerror(errno)); return ret; @@ -1265,3 +1249,30 @@ int ScheduleBatchPriority() return 1; #endif } + +namespace util { +#ifdef WIN32 +WinCmdLineArgs::WinCmdLineArgs() +{ + wchar_t** wargv = CommandLineToArgvW(GetCommandLineW(), &argc); + std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>, wchar_t> utf8_cvt; + argv = new char*[argc]; + args.resize(argc); + for (int i = 0; i < argc; i++) { + args[i] = utf8_cvt.to_bytes(wargv[i]); + argv[i] = &*args[i].begin(); + } + LocalFree(wargv); +} + +WinCmdLineArgs::~WinCmdLineArgs() +{ + delete[] argv; +} + +std::pair<int, char**> WinCmdLineArgs::get() +{ + return std::make_pair(argc, argv); +} +#endif +} // namespace util diff --git a/src/util.h b/src/util/system.h index f119385e48..5932e55793 100644 --- a/src/util.h +++ b/src/util/system.h @@ -7,20 +7,21 @@ * Server/client environment: argument handling, config file parsing, * thread wrappers, startup time */ -#ifndef BITCOIN_UTIL_H -#define BITCOIN_UTIL_H +#ifndef BITCOIN_UTIL_SYSTEM_H +#define BITCOIN_UTIL_SYSTEM_H #if defined(HAVE_CONFIG_H) #include <config/bitcoin-config.h> #endif +#include <attributes.h> #include <compat.h> #include <fs.h> #include <logging.h> #include <sync.h> #include <tinyformat.h> -#include <utilmemory.h> -#include <utiltime.h> +#include <util/memory.h> +#include <util/time.h> #include <atomic> #include <exception> @@ -29,6 +30,7 @@ #include <stdint.h> #include <string> #include <unordered_set> +#include <utility> #include <vector> #include <boost/thread/condition_variable.hpp> // for boost::thread_interrupted @@ -77,7 +79,8 @@ void ReleaseDirectoryLocks(); bool TryCreateDirectories(const fs::path& p); fs::path GetDefaultDataDir(); -const fs::path &GetBlocksDir(bool fNetSpecific = true); +// The blocks directory is always net specific. +const fs::path &GetBlocksDir(); const fs::path &GetDataDir(bool fNetSpecific = true); void ClearDatadirCache(); fs::path GetConfigFile(const std::string& confPath); @@ -147,8 +150,9 @@ protected: std::string m_network GUARDED_BY(cs_args); std::set<std::string> m_network_only_args GUARDED_BY(cs_args); std::map<OptionsCategory, std::map<std::string, Arg>> m_available_args GUARDED_BY(cs_args); + std::set<std::string> m_config_sections GUARDED_BY(cs_args); - bool ReadConfigStream(std::istream& stream, std::string& error, bool ignore_invalid_keys = false); + NODISCARD bool ReadConfigStream(std::istream& stream, std::string& error, bool ignore_invalid_keys = false); public: ArgsManager(); @@ -158,8 +162,8 @@ public: */ void SelectConfigNetwork(const std::string& network); - bool ParseParameters(int argc, const char* const argv[], std::string& error); - bool ReadConfigFiles(std::string& error, bool ignore_invalid_keys = false); + NODISCARD bool ParseParameters(int argc, const char* const argv[], std::string& error); + NODISCARD bool ReadConfigFiles(std::string& error, bool ignore_invalid_keys = false); /** * Log warnings for options in m_section_only_args when @@ -167,7 +171,12 @@ public: * on the command line or in a network-specific section in the * config file. */ - void WarnForSectionOnlyArgs(); + const std::set<std::string> GetUnsuitableSectionOnlyArgs() const; + + /** + * Log warnings for unrecognized section names in the config file. + */ + const std::set<std::string> GetUnrecognizedSections() const; /** * Return a vector of strings of the given argument @@ -361,6 +370,21 @@ inline void insert(std::set<TsetT>& dst, const Tsrc& src) { dst.insert(src.begin(), src.end()); } +#ifdef WIN32 +class WinCmdLineArgs +{ +public: + WinCmdLineArgs(); + ~WinCmdLineArgs(); + std::pair<int, char**> get(); + +private: + int argc; + char** argv; + std::vector<std::string> args; +}; +#endif + } // namespace util -#endif // BITCOIN_UTIL_H +#endif // BITCOIN_UTIL_SYSTEM_H diff --git a/src/utiltime.cpp b/src/util/time.cpp index 908791da48..83a7937d8f 100644 --- a/src/utiltime.cpp +++ b/src/util/time.cpp @@ -7,7 +7,7 @@ #include <config/bitcoin-config.h> #endif -#include <utiltime.h> +#include <util/time.h> #include <atomic> #include <boost/date_time/posix_time/posix_time.hpp> diff --git a/src/utiltime.h b/src/util/time.h index c3e3da3014..f2e2747434 100644 --- a/src/utiltime.h +++ b/src/util/time.h @@ -3,8 +3,8 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#ifndef BITCOIN_UTILTIME_H -#define BITCOIN_UTILTIME_H +#ifndef BITCOIN_UTIL_TIME_H +#define BITCOIN_UTIL_TIME_H #include <stdint.h> #include <string> @@ -35,4 +35,4 @@ std::string FormatISO8601DateTime(int64_t nTime); std::string FormatISO8601Date(int64_t nTime); std::string FormatISO8601Time(int64_t nTime); -#endif // BITCOIN_UTILTIME_H +#endif // BITCOIN_UTIL_TIME_H diff --git a/src/validation.cpp b/src/validation.cpp index 458458d85d..6a26bf9baa 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -35,9 +35,9 @@ #include <txmempool.h> #include <ui_interface.h> #include <undo.h> -#include <util.h> -#include <utilmoneystr.h> -#include <utilstrencodings.h> +#include <util/system.h> +#include <util/moneystr.h> +#include <util/strencodings.h> #include <validationinterface.h> #include <warnings.h> @@ -173,7 +173,7 @@ public: CCoinsViewCache& view, const CChainParams& chainparams, bool fJustCheck = false) EXCLUSIVE_LOCKS_REQUIRED(cs_main); // Block disconnection on our pcoinsTip: - bool DisconnectTip(CValidationState& state, const CChainParams& chainparams, DisconnectedBlockTransactions *disconnectpool); + bool DisconnectTip(CValidationState& state, const CChainParams& chainparams, DisconnectedBlockTransactions* disconnectpool) EXCLUSIVE_LOCKS_REQUIRED(cs_main); // Manual block validity manipulation: bool PreciousBlock(CValidationState& state, const CChainParams& params, CBlockIndex* pindex) LOCKS_EXCLUDED(cs_main); @@ -210,9 +210,17 @@ private: bool RollforwardBlock(const CBlockIndex* pindex, CCoinsViewCache& inputs, const CChainParams& params) EXCLUSIVE_LOCKS_REQUIRED(cs_main); } g_chainstate; - - -CCriticalSection cs_main; +/** + * Mutex to guard access to validation specific variables, such as reading + * or changing the chainstate. + * + * This may also need to be locked when updating the transaction pool, e.g. on + * AcceptToMemoryPool. See CTxMemPool::cs comment for details. + * + * The transaction pool has a separate lock to allow reading from it and the + * chainstate at the same time. + */ +RecursiveMutex cs_main; BlockMap& mapBlockIndex = g_chainstate.mapBlockIndex; CChain& chainActive = g_chainstate.chainActive; @@ -361,10 +369,10 @@ bool TestLockPointValidity(const LockPoints* lp) return true; } -bool CheckSequenceLocks(const CTransaction &tx, int flags, LockPoints* lp, bool useExistingLockPoints) +bool CheckSequenceLocks(const CTxMemPool& pool, const CTransaction& tx, int flags, LockPoints* lp, bool useExistingLockPoints) { AssertLockHeld(cs_main); - AssertLockHeld(mempool.cs); + AssertLockHeld(pool.cs); CBlockIndex* tip = chainActive.Tip(); assert(tip != nullptr); @@ -387,7 +395,7 @@ bool CheckSequenceLocks(const CTransaction &tx, int flags, LockPoints* lp, bool } else { // pcoinsTip contains the UTXO set for chainActive.Tip() - CCoinsViewMemPool viewMemPool(pcoinsTip.get(), mempool); + CCoinsViewMemPool viewMemPool(pcoinsTip.get(), pool); std::vector<int> prevheights; prevheights.resize(tx.vin.size()); for (size_t txinIndex = 0; txinIndex < tx.vin.size(); txinIndex++) { @@ -679,7 +687,7 @@ static bool AcceptToMemoryPoolWorker(const CChainParams& chainparams, CTxMemPool // be mined yet. // Must keep pool.cs for this unless we change CheckSequenceLocks to take a // CoinsViewCache instead of create its own - if (!CheckSequenceLocks(tx, STANDARD_LOCKTIME_VERIFY_FLAGS, &lp)) + if (!CheckSequenceLocks(pool, tx, STANDARD_LOCKTIME_VERIFY_FLAGS, &lp)) return state.DoS(0, false, REJECT_NONSTANDARD, "non-BIP68-final"); CAmount nFees = 0; @@ -918,7 +926,7 @@ static bool AcceptToMemoryPoolWorker(const CChainParams& chainparams, CTxMemPool // There is a similar check in CreateNewBlock() to prevent creating // invalid blocks (using TestBlockValidity), however allowing such // transactions into the mempool can be exploited as a DoS attack. - unsigned int currentBlockScriptVerifyFlags = GetBlockScriptFlags(chainActive.Tip(), Params().GetConsensus()); + unsigned int currentBlockScriptVerifyFlags = GetBlockScriptFlags(chainActive.Tip(), chainparams.GetConsensus()); if (!CheckInputsFromMempoolAndCache(tx, state, view, pool, currentBlockScriptVerifyFlags, true, txdata)) { return error("%s: BUG! PLEASE REPORT THIS! CheckInputs failed against latest-block but not STANDARD flags %s, %s", __func__, hash.ToString(), FormatStateMessage(state)); @@ -1680,8 +1688,7 @@ void ThreadScriptCheck() { scriptcheckqueue.Thread(); } -// Protected by cs_main -VersionBitsCache versionbitscache; +VersionBitsCache versionbitscache GUARDED_BY(cs_main); int32_t ComputeBlockVersion(const CBlockIndex* pindexPrev, const Consensus::Params& params) { @@ -1722,8 +1729,7 @@ public: } }; -// Protected by cs_main -static ThresholdConditionCache warningcache[VERSIONBITS_NUM_BITS]; +static ThresholdConditionCache warningcache[VERSIONBITS_NUM_BITS] GUARDED_BY(cs_main); // 0.13.0 was shipped with a segwit deployment defined for testnet, but not for // mainnet. We no longer need to support disabling the segwit deployment @@ -2368,14 +2374,11 @@ class ConnectTrace { private: std::vector<PerBlockConnectTrace> blocksConnected; CTxMemPool &pool; + boost::signals2::scoped_connection m_connNotifyEntryRemoved; public: explicit ConnectTrace(CTxMemPool &_pool) : blocksConnected(1), pool(_pool) { - pool.NotifyEntryRemoved.connect(boost::bind(&ConnectTrace::NotifyEntryRemoved, this, _1, _2)); - } - - ~ConnectTrace() { - pool.NotifyEntryRemoved.disconnect(boost::bind(&ConnectTrace::NotifyEntryRemoved, this, _1, _2)); + m_connNotifyEntryRemoved = pool.NotifyEntryRemoved.connect(std::bind(&ConnectTrace::NotifyEntryRemoved, this, std::placeholders::_1, std::placeholders::_2)); } void BlockConnected(CBlockIndex* pindex, std::shared_ptr<const CBlock> pblock) { @@ -2489,7 +2492,7 @@ CBlockIndex* CChainState::FindMostWorkChain() { CBlockIndex *pindexTest = pindexNew; bool fInvalidAncestor = false; while (pindexTest && !chainActive.Contains(pindexTest)) { - assert(pindexTest->nChainTx || pindexTest->nHeight == 0); + assert(pindexTest->HaveTxsDownloaded() || pindexTest->nHeight == 0); // Pruned nodes may have entries in setBlockIndexCandidates for // which block files have been deleted. Remove those as candidates @@ -2779,7 +2782,7 @@ bool CChainState::PreciousBlock(CValidationState& state, const CChainParams& par // call preciousblock 2**31-1 times on the same set of tips... nBlockReverseSequenceId--; } - if (pindex->IsValid(BLOCK_VALID_TRANSACTIONS) && pindex->nChainTx) { + if (pindex->IsValid(BLOCK_VALID_TRANSACTIONS) && pindex->HaveTxsDownloaded()) { setBlockIndexCandidates.insert(pindex); PruneBlockIndexCandidates(); } @@ -2840,7 +2843,7 @@ bool CChainState::InvalidateBlock(CValidationState& state, const CChainParams& c // add it again. BlockMap::iterator it = mapBlockIndex.begin(); while (it != mapBlockIndex.end()) { - if (it->second->IsValid(BLOCK_VALID_TRANSACTIONS) && it->second->nChainTx && !setBlockIndexCandidates.value_comp()(it->second, chainActive.Tip())) { + if (it->second->IsValid(BLOCK_VALID_TRANSACTIONS) && it->second->HaveTxsDownloaded() && !setBlockIndexCandidates.value_comp()(it->second, chainActive.Tip())) { setBlockIndexCandidates.insert(it->second); } it++; @@ -2870,7 +2873,7 @@ void CChainState::ResetBlockFailureFlags(CBlockIndex *pindex) { if (!it->second->IsValid() && it->second->GetAncestor(nHeight) == pindex) { it->second->nStatus &= ~BLOCK_FAILED_MASK; setDirtyBlockIndex.insert(it->second); - if (it->second->IsValid(BLOCK_VALID_TRANSACTIONS) && it->second->nChainTx && setBlockIndexCandidates.value_comp()(chainActive.Tip(), it->second)) { + if (it->second->IsValid(BLOCK_VALID_TRANSACTIONS) && it->second->HaveTxsDownloaded() && setBlockIndexCandidates.value_comp()(chainActive.Tip(), it->second)) { setBlockIndexCandidates.insert(it->second); } if (it->second == pindexBestInvalid) { @@ -2948,7 +2951,7 @@ void CChainState::ReceivedBlockTransactions(const CBlock& block, CBlockIndex* pi pindexNew->RaiseValidity(BLOCK_VALID_TRANSACTIONS); setDirtyBlockIndex.insert(pindexNew); - if (pindexNew->pprev == nullptr || pindexNew->pprev->nChainTx) { + if (pindexNew->pprev == nullptr || pindexNew->pprev->HaveTxsDownloaded()) { // If pindexNew is the genesis block or all parents are BLOCK_VALID_TRANSACTIONS. std::deque<CBlockIndex*> queue; queue.push_back(pindexNew); @@ -3269,6 +3272,7 @@ static bool ContextualCheckBlock(const CBlock& block, CValidationState& state, c // Start enforcing BIP113 (Median Time Past) using versionbits logic. int nLockTimeFlags = 0; if (VersionBitsState(pindexPrev, consensusParams, Consensus::DEPLOYMENT_CSV, versionbitscache) == ThresholdState::ACTIVE) { + assert(pindexPrev != nullptr); nLockTimeFlags |= LOCKTIME_MEDIAN_TIME_PAST; } @@ -3375,10 +3379,30 @@ bool CChainState::AcceptBlockHeader(const CBlockHeader& block, CValidationState& if (!ContextualCheckBlockHeader(block, state, chainparams, pindexPrev, GetAdjustedTime())) return error("%s: Consensus::ContextualCheckBlockHeader: %s, %s", __func__, hash.ToString(), FormatStateMessage(state)); - // If the previous block index isn't valid, determine if it descends from any block which - // has been found invalid (m_failed_blocks), then mark pindexPrev and any blocks - // between them as failed. + /* Determine if this block descends from any block which has been found + * invalid (m_failed_blocks), then mark pindexPrev and any blocks between + * them as failed. For example: + * + * D3 + * / + * B2 - C2 + * / \ + * A D2 - E2 - F2 + * \ + * B1 - C1 - D1 - E1 + * + * In the case that we attempted to reorg from E1 to F2, only to find + * C2 to be invalid, we would mark D2, E2, and F2 as BLOCK_FAILED_CHILD + * but NOT D3 (it was not in any of our candidate sets at the time). + * + * In any case D3 will also be marked as BLOCK_FAILED_CHILD at restart + * in LoadBlockIndex. + */ if (!pindexPrev->IsValid(BLOCK_VALID_SCRIPTS)) { + // The above does not mean "invalid": it checks if the previous block + // hasn't been validated up to BLOCK_VALID_SCRIPTS. This is a performance + // optimization, in the common case of adding a new block to the tip, + // we don't need to iterate over the failed blocks list. for (const CBlockIndex* failedit : m_failed_blocks) { if (pindexPrev->GetAncestor(failedit->nHeight) == failedit) { assert(failedit->nStatus & BLOCK_FAILED_VALID); @@ -3532,12 +3556,14 @@ bool ProcessNewBlock(const CChainParams& chainparams, const std::shared_ptr<cons CBlockIndex *pindex = nullptr; if (fNewBlock) *fNewBlock = false; CValidationState state; - // Ensure that CheckBlock() passes before calling AcceptBlock, as - // belt-and-suspenders. - bool ret = CheckBlock(*pblock, state, chainparams.GetConsensus()); + // CheckBlock() does not support multi-threaded block validation because CBlock::fChecked can cause data race. + // Therefore, the following critical section must include the CheckBlock() call as well. LOCK(cs_main); + // Ensure that CheckBlock() passes before calling AcceptBlock, as + // belt-and-suspenders. + bool ret = CheckBlock(*pblock, state, chainparams.GetConsensus()); if (ret) { // Store to disk ret = g_chainstate.AcceptBlock(pblock, state, chainparams, &pindex, fForceProcessing, nullptr, fNewBlock); @@ -3819,8 +3845,6 @@ bool CChainState::LoadBlockIndex(const Consensus::Params& consensus_params, CBlo if (!blocktree.LoadBlockIndexGuts(consensus_params, [this](const uint256& hash) EXCLUSIVE_LOCKS_REQUIRED(cs_main) { return this->InsertBlockIndex(hash); })) return false; - boost::this_thread::interruption_point(); - // Calculate nChainWork std::vector<std::pair<int, CBlockIndex*> > vSortedByHeight; vSortedByHeight.reserve(mapBlockIndex.size()); @@ -3839,7 +3863,7 @@ bool CChainState::LoadBlockIndex(const Consensus::Params& consensus_params, CBlo // Pruned nodes may have deleted the block. if (pindex->nTx > 0) { if (pindex->pprev) { - if (pindex->pprev->nChainTx) { + if (pindex->pprev->HaveTxsDownloaded()) { pindex->nChainTx = pindex->pprev->nChainTx + pindex->nTx; } else { pindex->nChainTx = 0; @@ -3853,7 +3877,7 @@ bool CChainState::LoadBlockIndex(const Consensus::Params& consensus_params, CBlo pindex->nStatus |= BLOCK_FAILED_CHILD; setDirtyBlockIndex.insert(pindex); } - if (pindex->IsValid(BLOCK_VALID_TRANSACTIONS) && (pindex->nChainTx || pindex->pprev == nullptr)) + if (pindex->IsValid(BLOCK_VALID_TRANSACTIONS) && (pindex->HaveTxsDownloaded() || pindex->pprev == nullptr)) setBlockIndexCandidates.insert(pindex); if (pindex->nStatus & BLOCK_FAILED_MASK && (!pindexBestInvalid || pindex->nChainWork > pindexBestInvalid->nChainWork)) pindexBestInvalid = pindex; @@ -3982,7 +4006,7 @@ bool CVerifyDB::VerifyDB(const CChainParams& chainparams, CCoinsView *coinsview, LogPrintf("[0%%]..."); /* Continued */ for (pindex = chainActive.Tip(); pindex && pindex->pprev; pindex = pindex->pprev) { boost::this_thread::interruption_point(); - int percentageDone = std::max(1, std::min(99, (int)(((double)(chainActive.Height() - pindex->nHeight)) / (double)nCheckDepth * (nCheckLevel >= 4 ? 50 : 100)))); + const int percentageDone = std::max(1, std::min(99, (int)(((double)(chainActive.Height() - pindex->nHeight)) / (double)nCheckDepth * (nCheckLevel >= 4 ? 50 : 100)))); if (reportDone < percentageDone/10) { // report every 10% step LogPrintf("[%d%%]...", percentageDone); /* Continued */ @@ -4040,7 +4064,13 @@ bool CVerifyDB::VerifyDB(const CChainParams& chainparams, CCoinsView *coinsview, if (nCheckLevel >= 4) { while (pindex != chainActive.Tip()) { boost::this_thread::interruption_point(); - uiInterface.ShowProgress(_("Verifying blocks..."), std::max(1, std::min(99, 100 - (int)(((double)(chainActive.Height() - pindex->nHeight)) / (double)nCheckDepth * 50))), false); + const int percentageDone = std::max(1, std::min(99, 100 - (int)(((double)(chainActive.Height() - pindex->nHeight)) / (double)nCheckDepth * 50))); + if (reportDone < percentageDone/10) { + // report every 10% step + LogPrintf("[%d%%]...", percentageDone); /* Continued */ + reportDone = percentageDone/10; + } + uiInterface.ShowProgress(_("Verifying blocks..."), percentageDone, false); pindex = chainActive.Next(pindex); CBlock block; if (!ReadBlockFromDisk(block, pindex, chainparams.GetConsensus())) @@ -4222,7 +4252,7 @@ bool CChainState::RewindBlockIndex(const CChainParams& params) ++ret.first; } } - } else if (pindexIter->IsValid(BLOCK_VALID_TRANSACTIONS) && pindexIter->nChainTx) { + } else if (pindexIter->IsValid(BLOCK_VALID_TRANSACTIONS) && pindexIter->HaveTxsDownloaded()) { setBlockIndexCandidates.insert(pindexIter); } } @@ -4523,7 +4553,7 @@ void CChainState::CheckBlockIndex(const Consensus::Params& consensusParams) assert(pindex->GetBlockHash() == consensusParams.hashGenesisBlock); // Genesis block's hash must match. assert(pindex == chainActive.Genesis()); // The current active chain's genesis block must be this block. } - if (pindex->nChainTx == 0) assert(pindex->nSequenceId <= 0); // nSequenceId can't be set positive for blocks that aren't linked (negative is used for preciousblock) + if (!pindex->HaveTxsDownloaded()) assert(pindex->nSequenceId <= 0); // nSequenceId can't be set positive for blocks that aren't linked (negative is used for preciousblock) // VALID_TRANSACTIONS is equivalent to nTx > 0 for all nodes (whether or not pruning has occurred). // HAVE_DATA is only equivalent to nTx > 0 (or VALID_TRANSACTIONS) if no pruning has occurred. if (!fHavePruned) { @@ -4536,9 +4566,9 @@ void CChainState::CheckBlockIndex(const Consensus::Params& consensusParams) } if (pindex->nStatus & BLOCK_HAVE_UNDO) assert(pindex->nStatus & BLOCK_HAVE_DATA); assert(((pindex->nStatus & BLOCK_VALID_MASK) >= BLOCK_VALID_TRANSACTIONS) == (pindex->nTx > 0)); // This is pruning-independent. - // All parents having had data (at some point) is equivalent to all parents being VALID_TRANSACTIONS, which is equivalent to nChainTx being set. - assert((pindexFirstNeverProcessed != nullptr) == (pindex->nChainTx == 0)); // nChainTx != 0 is used to signal that all parent blocks have been processed (but may have been pruned). - assert((pindexFirstNotTransactionsValid != nullptr) == (pindex->nChainTx == 0)); + // All parents having had data (at some point) is equivalent to all parents being VALID_TRANSACTIONS, which is equivalent to HaveTxsDownloaded(). + assert((pindexFirstNeverProcessed == nullptr) == pindex->HaveTxsDownloaded()); + assert((pindexFirstNotTransactionsValid == nullptr) == pindex->HaveTxsDownloaded()); 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. @@ -4766,6 +4796,9 @@ bool DumpMempool() std::map<uint256, CAmount> mapDeltas; std::vector<TxMempoolInfo> vinfo; + static Mutex dump_mutex; + LOCK(dump_mutex); + { LOCK(mempool.cs); for (const auto &i : mempool.mapDeltas) { diff --git a/src/validation.h b/src/validation.h index 1034ba4665..b5548a9293 100644 --- a/src/validation.h +++ b/src/validation.h @@ -12,6 +12,7 @@ #include <amount.h> #include <coins.h> +#include <crypto/common.h> // for ReadLE64 #include <fs.h> #include <protocol.h> // For CMessageHeader::MessageStartChars #include <policy/feerate.h> @@ -138,7 +139,10 @@ static const int DEFAULT_STOPATHEIGHT = 0; struct BlockHasher { - size_t operator()(const uint256& hash) const { return hash.GetCheapHash(); } + // this used to call `GetCheapHash()` in uint256, which was later moved; the + // cheap hash function simply calls ReadLE64() however, so the end result is + // identical + size_t operator()(const uint256& hash) const { return ReadLE64(hash.begin()); } }; extern CScript COINBASE_FLAGS; @@ -257,7 +261,7 @@ bool LoadGenesisBlock(const CChainParams& chainparams); * initializing state if we're running with -reindex. */ bool LoadBlockIndex(const CChainParams& chainparams) EXCLUSIVE_LOCKS_REQUIRED(cs_main); /** Update the chain tip based on database information. */ -bool LoadChainTip(const CChainParams& chainparams); +bool LoadChainTip(const CChainParams& chainparams) EXCLUSIVE_LOCKS_REQUIRED(cs_main); /** Unload database information */ void UnloadBlockIndex(); /** Run an instance of the script checking thread */ @@ -347,7 +351,7 @@ bool TestLockPointValidity(const LockPoints* lp) EXCLUSIVE_LOCKS_REQUIRED(cs_mai * * See consensus/consensus.h for flag definitions. */ -bool CheckSequenceLocks(const CTransaction &tx, int flags, LockPoints* lp = nullptr, bool useExistingLockPoints = false) EXCLUSIVE_LOCKS_REQUIRED(cs_main); +bool CheckSequenceLocks(const CTxMemPool& pool, const CTransaction& tx, int flags, LockPoints* lp = nullptr, bool useExistingLockPoints = false) EXCLUSIVE_LOCKS_REQUIRED(cs_main); /** * Closure representing one script verification @@ -436,7 +440,7 @@ inline CBlockIndex* LookupBlockIndex(const uint256& hash) } /** Find the last common block between the parameter chain and a locator. */ -CBlockIndex* FindForkInGlobalIndex(const CChain& chain, const CBlockLocator& locator); +CBlockIndex* FindForkInGlobalIndex(const CChain& chain, const CBlockLocator& locator) EXCLUSIVE_LOCKS_REQUIRED(cs_main); /** Mark a block as precious and reorganize. * diff --git a/src/validationinterface.cpp b/src/validationinterface.cpp index e25eca2368..533d412888 100644 --- a/src/validationinterface.cpp +++ b/src/validationinterface.cpp @@ -8,7 +8,7 @@ #include <primitives/block.h> #include <scheduler.h> #include <txmempool.h> -#include <util.h> +#include <util/system.h> #include <validation.h> #include <list> @@ -17,6 +17,18 @@ #include <boost/signals2/signal.hpp> +struct ValidationInterfaceConnections { + boost::signals2::scoped_connection UpdatedBlockTip; + boost::signals2::scoped_connection TransactionAddedToMempool; + boost::signals2::scoped_connection BlockConnected; + boost::signals2::scoped_connection BlockDisconnected; + boost::signals2::scoped_connection TransactionRemovedFromMempool; + boost::signals2::scoped_connection ChainStateFlushed; + boost::signals2::scoped_connection Broadcast; + boost::signals2::scoped_connection BlockChecked; + boost::signals2::scoped_connection NewPoWValidBlock; +}; + struct MainSignalsInstance { boost::signals2::signal<void (const CBlockIndex *, const CBlockIndex *, bool fInitialDownload)> UpdatedBlockTip; boost::signals2::signal<void (const CTransactionRef &)> TransactionAddedToMempool; @@ -32,12 +44,18 @@ struct MainSignalsInstance { // but must ensure all callbacks happen in-order, so we end up creating // our own queue here :( SingleThreadedSchedulerClient m_schedulerClient; + std::unordered_map<CValidationInterface*, ValidationInterfaceConnections> m_connMainSignals; explicit MainSignalsInstance(CScheduler *pscheduler) : m_schedulerClient(pscheduler) {} }; static CMainSignals g_signals; +// This map has to a separate global instead of a member of MainSignalsInstance, +// because RegisterWithMempoolSignals is currently called before RegisterBackgroundSignalScheduler, +// so MainSignalsInstance hasn't been created yet. +static std::unordered_map<CTxMemPool*, boost::signals2::scoped_connection> g_connNotifyEntryRemoved; + void CMainSignals::RegisterBackgroundSignalScheduler(CScheduler& scheduler) { assert(!m_internals); m_internals.reset(new MainSignalsInstance(&scheduler)); @@ -59,11 +77,11 @@ size_t CMainSignals::CallbacksPending() { } void CMainSignals::RegisterWithMempoolSignals(CTxMemPool& pool) { - pool.NotifyEntryRemoved.connect(boost::bind(&CMainSignals::MempoolEntryRemoved, this, _1, _2)); + g_connNotifyEntryRemoved.emplace(&pool, pool.NotifyEntryRemoved.connect(std::bind(&CMainSignals::MempoolEntryRemoved, this, std::placeholders::_1, std::placeholders::_2))); } void CMainSignals::UnregisterWithMempoolSignals(CTxMemPool& pool) { - pool.NotifyEntryRemoved.disconnect(boost::bind(&CMainSignals::MempoolEntryRemoved, this, _1, _2)); + g_connNotifyEntryRemoved.erase(&pool); } CMainSignals& GetMainSignals() @@ -72,42 +90,27 @@ CMainSignals& GetMainSignals() } void RegisterValidationInterface(CValidationInterface* pwalletIn) { - g_signals.m_internals->UpdatedBlockTip.connect(boost::bind(&CValidationInterface::UpdatedBlockTip, pwalletIn, _1, _2, _3)); - g_signals.m_internals->TransactionAddedToMempool.connect(boost::bind(&CValidationInterface::TransactionAddedToMempool, pwalletIn, _1)); - g_signals.m_internals->BlockConnected.connect(boost::bind(&CValidationInterface::BlockConnected, pwalletIn, _1, _2, _3)); - g_signals.m_internals->BlockDisconnected.connect(boost::bind(&CValidationInterface::BlockDisconnected, pwalletIn, _1)); - g_signals.m_internals->TransactionRemovedFromMempool.connect(boost::bind(&CValidationInterface::TransactionRemovedFromMempool, pwalletIn, _1)); - g_signals.m_internals->ChainStateFlushed.connect(boost::bind(&CValidationInterface::ChainStateFlushed, pwalletIn, _1)); - g_signals.m_internals->Broadcast.connect(boost::bind(&CValidationInterface::ResendWalletTransactions, pwalletIn, _1, _2)); - g_signals.m_internals->BlockChecked.connect(boost::bind(&CValidationInterface::BlockChecked, pwalletIn, _1, _2)); - g_signals.m_internals->NewPoWValidBlock.connect(boost::bind(&CValidationInterface::NewPoWValidBlock, pwalletIn, _1, _2)); + ValidationInterfaceConnections& conns = g_signals.m_internals->m_connMainSignals[pwalletIn]; + conns.UpdatedBlockTip = g_signals.m_internals->UpdatedBlockTip.connect(std::bind(&CValidationInterface::UpdatedBlockTip, pwalletIn, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)); + conns.TransactionAddedToMempool = g_signals.m_internals->TransactionAddedToMempool.connect(std::bind(&CValidationInterface::TransactionAddedToMempool, pwalletIn, std::placeholders::_1)); + conns.BlockConnected = g_signals.m_internals->BlockConnected.connect(std::bind(&CValidationInterface::BlockConnected, pwalletIn, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)); + conns.BlockDisconnected = g_signals.m_internals->BlockDisconnected.connect(std::bind(&CValidationInterface::BlockDisconnected, pwalletIn, std::placeholders::_1)); + conns.TransactionRemovedFromMempool = g_signals.m_internals->TransactionRemovedFromMempool.connect(std::bind(&CValidationInterface::TransactionRemovedFromMempool, pwalletIn, std::placeholders::_1)); + conns.ChainStateFlushed = g_signals.m_internals->ChainStateFlushed.connect(std::bind(&CValidationInterface::ChainStateFlushed, pwalletIn, std::placeholders::_1)); + conns.Broadcast = g_signals.m_internals->Broadcast.connect(std::bind(&CValidationInterface::ResendWalletTransactions, pwalletIn, std::placeholders::_1, std::placeholders::_2)); + conns.BlockChecked = g_signals.m_internals->BlockChecked.connect(std::bind(&CValidationInterface::BlockChecked, pwalletIn, std::placeholders::_1, std::placeholders::_2)); + conns.NewPoWValidBlock = g_signals.m_internals->NewPoWValidBlock.connect(std::bind(&CValidationInterface::NewPoWValidBlock, pwalletIn, std::placeholders::_1, std::placeholders::_2)); } void UnregisterValidationInterface(CValidationInterface* pwalletIn) { - g_signals.m_internals->BlockChecked.disconnect(boost::bind(&CValidationInterface::BlockChecked, pwalletIn, _1, _2)); - g_signals.m_internals->Broadcast.disconnect(boost::bind(&CValidationInterface::ResendWalletTransactions, pwalletIn, _1, _2)); - g_signals.m_internals->ChainStateFlushed.disconnect(boost::bind(&CValidationInterface::ChainStateFlushed, pwalletIn, _1)); - g_signals.m_internals->TransactionAddedToMempool.disconnect(boost::bind(&CValidationInterface::TransactionAddedToMempool, pwalletIn, _1)); - g_signals.m_internals->BlockConnected.disconnect(boost::bind(&CValidationInterface::BlockConnected, pwalletIn, _1, _2, _3)); - g_signals.m_internals->BlockDisconnected.disconnect(boost::bind(&CValidationInterface::BlockDisconnected, pwalletIn, _1)); - g_signals.m_internals->TransactionRemovedFromMempool.disconnect(boost::bind(&CValidationInterface::TransactionRemovedFromMempool, pwalletIn, _1)); - g_signals.m_internals->UpdatedBlockTip.disconnect(boost::bind(&CValidationInterface::UpdatedBlockTip, pwalletIn, _1, _2, _3)); - g_signals.m_internals->NewPoWValidBlock.disconnect(boost::bind(&CValidationInterface::NewPoWValidBlock, pwalletIn, _1, _2)); + g_signals.m_internals->m_connMainSignals.erase(pwalletIn); } void UnregisterAllValidationInterfaces() { if (!g_signals.m_internals) { return; } - g_signals.m_internals->BlockChecked.disconnect_all_slots(); - g_signals.m_internals->Broadcast.disconnect_all_slots(); - g_signals.m_internals->ChainStateFlushed.disconnect_all_slots(); - g_signals.m_internals->TransactionAddedToMempool.disconnect_all_slots(); - g_signals.m_internals->BlockConnected.disconnect_all_slots(); - g_signals.m_internals->BlockDisconnected.disconnect_all_slots(); - g_signals.m_internals->TransactionRemovedFromMempool.disconnect_all_slots(); - g_signals.m_internals->UpdatedBlockTip.disconnect_all_slots(); - g_signals.m_internals->NewPoWValidBlock.disconnect_all_slots(); + g_signals.m_internals->m_connMainSignals.clear(); } void CallFunctionInValidationInterfaceQueue(std::function<void ()> func) { diff --git a/src/wallet/coincontrol.cpp b/src/wallet/coincontrol.cpp index 645981faa4..87d2c4f06e 100644 --- a/src/wallet/coincontrol.cpp +++ b/src/wallet/coincontrol.cpp @@ -4,7 +4,7 @@ #include <wallet/coincontrol.h> -#include <util.h> +#include <util/system.h> void CCoinControl::SetNull() { diff --git a/src/wallet/coinselection.cpp b/src/wallet/coinselection.cpp index fdeb89553b..8a37f374a1 100644 --- a/src/wallet/coinselection.cpp +++ b/src/wallet/coinselection.cpp @@ -4,8 +4,8 @@ #include <wallet/coinselection.h> -#include <util.h> -#include <utilmoneystr.h> +#include <util/system.h> +#include <util/moneystr.h> #include <boost/optional.hpp> @@ -223,7 +223,7 @@ bool KnapsackSolver(const CAmount& nTargetValue, std::vector<OutputGroup>& group std::vector<OutputGroup> applicable_groups; CAmount nTotalLower = 0; - random_shuffle(groups.begin(), groups.end(), GetRandInt); + Shuffle(groups.begin(), groups.end(), FastRandomContext()); for (const OutputGroup& group : groups) { if (group.m_value == nTargetValue) { diff --git a/src/wallet/crypter.cpp b/src/wallet/crypter.cpp index 729e4e39b0..f5ac6e98b2 100644 --- a/src/wallet/crypter.cpp +++ b/src/wallet/crypter.cpp @@ -8,7 +8,7 @@ #include <crypto/sha512.h> #include <script/script.h> #include <script/standard.h> -#include <util.h> +#include <util/system.h> #include <string> #include <vector> @@ -202,7 +202,7 @@ bool CCryptoKeyStore::Unlock(const CKeyingMaterial& vMasterKeyIn) if (keyPass && keyFail) { LogPrintf("The wallet is probably corrupted: Some keys decrypt but not all.\n"); - assert(false); + throw std::runtime_error("Error unlocking wallet: some keys decrypt but not all. Your wallet file may be corrupt."); } if (keyFail || !keyPass) return false; diff --git a/src/wallet/db.cpp b/src/wallet/db.cpp index a7bf89c572..a2e795056a 100644 --- a/src/wallet/db.cpp +++ b/src/wallet/db.cpp @@ -8,7 +8,7 @@ #include <addrman.h> #include <hash.h> #include <protocol.h> -#include <utilstrencodings.h> +#include <util/strencodings.h> #include <wallet/walletutil.h> #include <stdint.h> @@ -20,6 +20,7 @@ #include <boost/thread.hpp> namespace { + //! Make sure database has a unique fileid within the environment. If it //! doesn't, throw an error. BDB caches do not work properly when more than one //! open database has the same fileid (values written to one database may show @@ -29,25 +30,19 @@ namespace { //! (https://docs.oracle.com/cd/E17275_01/html/programmer_reference/program_copy.html), //! so bitcoin should never create different databases with the same fileid, but //! this error can be triggered if users manually copy database files. -void CheckUniqueFileid(const BerkeleyEnvironment& env, const std::string& filename, Db& db) +void CheckUniqueFileid(const BerkeleyEnvironment& env, const std::string& filename, Db& db, WalletDatabaseFileId& fileid) { if (env.IsMock()) return; - u_int8_t fileid[DB_FILE_ID_LEN]; - int ret = db.get_mpf()->get_fileid(fileid); + int ret = db.get_mpf()->get_fileid(fileid.value); if (ret != 0) { throw std::runtime_error(strprintf("BerkeleyBatch: Can't open database %s (get_fileid failed with %d)", filename, ret)); } - for (const auto& item : env.mapDb) { - u_int8_t item_fileid[DB_FILE_ID_LEN]; - if (item.second && item.second->get_mpf()->get_fileid(item_fileid) == 0 && - memcmp(fileid, item_fileid, sizeof(fileid)) == 0) { - const char* item_filename = nullptr; - item.second->get_dbname(&item_filename, nullptr); + for (const auto& item : env.m_fileids) { + if (fileid == item.second && &fileid != &item.second) { throw std::runtime_error(strprintf("BerkeleyBatch: Can't open database %s (duplicates fileid %s from %s)", filename, - HexStr(std::begin(item_fileid), std::end(item_fileid)), - item_filename ? item_filename : "(unknown database)")); + HexStr(std::begin(item.second.value), std::end(item.second.value)), item.first)); } } } @@ -56,9 +51,13 @@ CCriticalSection cs_db; std::map<std::string, BerkeleyEnvironment> g_dbenvs GUARDED_BY(cs_db); //!< Map from directory name to open db environment. } // namespace -BerkeleyEnvironment* GetWalletEnv(const fs::path& wallet_path, std::string& database_filename) +bool WalletDatabaseFileId::operator==(const WalletDatabaseFileId& rhs) const +{ + return memcmp(value, &rhs.value, sizeof(value)) == 0; +} + +static void SplitWalletPath(const fs::path& wallet_path, fs::path& env_directory, std::string& database_filename) { - fs::path env_directory; if (fs::is_regular_file(wallet_path)) { // Special case for backwards compatibility: if wallet path points to an // existing file, treat it as the path to a BDB data file in a parent @@ -71,6 +70,23 @@ BerkeleyEnvironment* GetWalletEnv(const fs::path& wallet_path, std::string& data env_directory = wallet_path; database_filename = "wallet.dat"; } +} + +bool IsWalletLoaded(const fs::path& wallet_path) +{ + fs::path env_directory; + std::string database_filename; + SplitWalletPath(wallet_path, env_directory, database_filename); + LOCK(cs_db); + auto env = g_dbenvs.find(env_directory.string()); + if (env == g_dbenvs.end()) return false; + return env->second.IsDatabaseLoaded(database_filename); +} + +BerkeleyEnvironment* GetWalletEnv(const fs::path& wallet_path, std::string& database_filename) +{ + fs::path env_directory; + SplitWalletPath(wallet_path, env_directory, database_filename); LOCK(cs_db); // Note: An unused temporary BerkeleyEnvironment object may be created inside the // emplace function if the key already exists. This is a little inefficient, @@ -90,13 +106,13 @@ void BerkeleyEnvironment::Close() fDbEnvInit = false; - for (auto& db : mapDb) { + for (auto& db : m_databases) { auto count = mapFileUseCount.find(db.first); assert(count == mapFileUseCount.end() || count->second == 0); - if (db.second) { - db.second->close(0); - delete db.second; - db.second = nullptr; + BerkeleyDatabase& database = db.second.get(); + if (database.m_db) { + database.m_db->close(0); + database.m_db.reset(); } } @@ -247,6 +263,45 @@ BerkeleyEnvironment::VerifyResult BerkeleyEnvironment::Verify(const std::string& return (fRecovered ? VerifyResult::RECOVER_OK : VerifyResult::RECOVER_FAIL); } +BerkeleyBatch::SafeDbt::SafeDbt() +{ + m_dbt.set_flags(DB_DBT_MALLOC); +} + +BerkeleyBatch::SafeDbt::SafeDbt(void* data, size_t size) + : m_dbt(data, size) +{ +} + +BerkeleyBatch::SafeDbt::~SafeDbt() +{ + if (m_dbt.get_data() != nullptr) { + // Clear memory, e.g. in case it was a private key + memory_cleanse(m_dbt.get_data(), m_dbt.get_size()); + // under DB_DBT_MALLOC, data is malloced by the Dbt, but must be + // freed by the caller. + // https://docs.oracle.com/cd/E17275_01/html/api_reference/C/dbt.html + if (m_dbt.get_flags() & DB_DBT_MALLOC) { + free(m_dbt.get_data()); + } + } +} + +const void* BerkeleyBatch::SafeDbt::get_data() const +{ + return m_dbt.get_data(); +} + +u_int32_t BerkeleyBatch::SafeDbt::get_size() const +{ + return m_dbt.get_size(); +} + +BerkeleyBatch::SafeDbt::operator Dbt*() +{ + return &m_dbt; +} + bool BerkeleyBatch::Recover(const fs::path& file_path, void *callbackDataIn, bool (*recoverKVcallback)(void* callbackData, CDataStream ssKey, CDataStream ssValue), std::string& newFilename) { std::string filename; @@ -322,7 +377,7 @@ bool BerkeleyBatch::VerifyEnvironment(const fs::path& file_path, std::string& er BerkeleyEnvironment* env = GetWalletEnv(file_path, walletFile); fs::path walletDir = env->Directory(); - LogPrintf("Using BerkeleyDB version %s\n", DbEnv::version(0, 0, 0)); + LogPrintf("Using BerkeleyDB version %s\n", DbEnv::version(nullptr, nullptr, nullptr)); LogPrintf("Using wallet %s\n", walletFile); // Wallet file must be a plain filename without a directory @@ -463,7 +518,7 @@ BerkeleyBatch::BerkeleyBatch(BerkeleyDatabase& database, const char* pszMode, bo if (!env->Open(false /* retry */)) throw std::runtime_error("BerkeleyBatch: Failed to open database environment."); - pdb = env->mapDb[strFilename]; + pdb = database.m_db.get(); if (pdb == nullptr) { int ret; std::unique_ptr<Db> pdb_temp = MakeUnique<Db>(env->dbenv.get(), 0); @@ -504,11 +559,11 @@ BerkeleyBatch::BerkeleyBatch(BerkeleyDatabase& database, const char* pszMode, bo // versions of BDB have an set_lk_exclusive method for this // purpose, but the older version we use does not.) for (const auto& env : g_dbenvs) { - CheckUniqueFileid(env.second, strFilename, *pdb_temp); + CheckUniqueFileid(env.second, strFilename, *pdb_temp, this->env->m_fileids[strFilename]); } pdb = pdb_temp.release(); - env->mapDb[strFilename] = pdb; + database.m_db.reset(pdb); if (fCreate && !Exists(std::string("version"))) { bool fTmp = fReadOnly; @@ -563,12 +618,13 @@ void BerkeleyEnvironment::CloseDb(const std::string& strFile) { { LOCK(cs_db); - if (mapDb[strFile] != nullptr) { + auto it = m_databases.find(strFile); + assert(it != m_databases.end()); + BerkeleyDatabase& database = it->second.get(); + if (database.m_db) { // Close the database handle - Db* pdb = mapDb[strFile]; - pdb->close(0); - delete pdb; - mapDb[strFile] = nullptr; + database.m_db->close(0); + database.m_db.reset(); } } } @@ -586,7 +642,7 @@ void BerkeleyEnvironment::ReloadDbEnv() }); std::vector<std::string> filenames; - for (auto it : mapDb) { + for (auto it : m_databases) { filenames.push_back(it.first); } // Close the individual Db's @@ -692,7 +748,7 @@ void BerkeleyEnvironment::Flush(bool fShutdown) { int64_t nStart = GetTimeMillis(); // Flush log data to the actual data file on all files that are not in use - LogPrint(BCLog::DB, "BerkeleyEnvironment::Flush: Flush(%s)%s\n", fShutdown ? "true" : "false", fDbEnvInit ? "" : " database not started"); + LogPrint(BCLog::DB, "BerkeleyEnvironment::Flush: [%s] Flush(%s)%s\n", strPath, fShutdown ? "true" : "false", fDbEnvInit ? "" : " database not started"); if (!fDbEnvInit) return; { @@ -826,6 +882,13 @@ void BerkeleyDatabase::Flush(bool shutdown) LOCK(cs_db); g_dbenvs.erase(env->Directory().string()); env = nullptr; + } else { + // TODO: To avoid g_dbenvs.erase erasing the environment prematurely after the + // first database shutdown when multiple databases are open in the same + // environment, should replace raw database `env` pointers with shared or weak + // pointers, or else separate the database and environment shutdowns so + // environments can be shut down after databases. + env->m_fileids.erase(strFile); } } } diff --git a/src/wallet/db.h b/src/wallet/db.h index 467ed13b45..9dc373c89b 100644 --- a/src/wallet/db.h +++ b/src/wallet/db.h @@ -11,13 +11,14 @@ #include <serialize.h> #include <streams.h> #include <sync.h> -#include <util.h> +#include <util/system.h> #include <version.h> #include <atomic> #include <map> #include <memory> #include <string> +#include <unordered_map> #include <vector> #include <db_cxx.h> @@ -25,6 +26,13 @@ static const unsigned int DEFAULT_WALLET_DBLOGSIZE = 100; static const bool DEFAULT_WALLET_PRIVDB = true; +struct WalletDatabaseFileId { + u_int8_t value[DB_FILE_ID_LEN]; + bool operator==(const WalletDatabaseFileId& rhs) const; +}; + +class BerkeleyDatabase; + class BerkeleyEnvironment { private: @@ -37,7 +45,8 @@ private: public: std::unique_ptr<DbEnv> dbenv; std::map<std::string, int> mapFileUseCount; - std::map<std::string, Db*> mapDb; + std::map<std::string, std::reference_wrapper<BerkeleyDatabase>> m_databases; + std::unordered_map<std::string, WalletDatabaseFileId> m_fileids; std::condition_variable_any m_db_in_use; BerkeleyEnvironment(const fs::path& env_directory); @@ -47,6 +56,7 @@ public: void MakeMock(); bool IsMock() const { return fMockDb; } bool IsInitialized() const { return fDbEnvInit; } + bool IsDatabaseLoaded(const std::string& db_filename) const { return m_databases.find(db_filename) != m_databases.end(); } fs::path Directory() const { return strPath; } /** @@ -88,6 +98,9 @@ public: } }; +/** Return whether a wallet database is currently loaded. */ +bool IsWalletLoaded(const fs::path& wallet_path); + /** Get BerkeleyEnvironment and database filename given a wallet path. */ BerkeleyEnvironment* GetWalletEnv(const fs::path& wallet_path, std::string& database_filename); @@ -108,6 +121,8 @@ public: nUpdateCounter(0), nLastSeen(0), nLastFlushed(0), nLastWalletUpdate(0) { env = GetWalletEnv(wallet_path, strFile); + auto inserted = env->m_databases.emplace(strFile, std::ref(*this)); + assert(inserted.second); if (mock) { env->Close(); env->Reset(); @@ -115,6 +130,13 @@ public: } } + ~BerkeleyDatabase() { + if (env) { + size_t erased = env->m_databases.erase(strFile); + assert(erased == 1); + } + } + /** Return object for accessing database at specified path. */ static std::unique_ptr<BerkeleyDatabase> Create(const fs::path& path) { @@ -154,6 +176,9 @@ public: unsigned int nLastFlushed; int64_t nLastWalletUpdate; + /** Database pointer. This is initialized lazily and reset during flushes, so it can be null. */ + std::unique_ptr<Db> m_db; + private: /** BerkeleyDB specific */ BerkeleyEnvironment *env; @@ -166,10 +191,29 @@ private: bool IsDummy() { return env == nullptr; } }; - /** RAII class that provides access to a Berkeley database */ class BerkeleyBatch { + /** RAII class that automatically cleanses its data on destruction */ + class SafeDbt final + { + Dbt m_dbt; + + public: + // construct Dbt with internally-managed data + SafeDbt(); + // construct Dbt with provided data + SafeDbt(void* data, size_t size); + ~SafeDbt(); + + // delegate to Dbt + const void* get_data() const; + u_int32_t get_size() const; + + // conversion operator to access the underlying Dbt + operator Dbt*(); + }; + protected: Db* pdb; std::string strFile; @@ -197,7 +241,6 @@ public: /* verifies the database file */ static bool VerifyDatabaseFile(const fs::path& file_path, std::string& warningStr, std::string& errorStr, BerkeleyEnvironment::recoverFunc_type recoverFunc); -public: template <typename K, typename T> bool Read(const K& key, T& value) { @@ -208,13 +251,11 @@ public: CDataStream ssKey(SER_DISK, CLIENT_VERSION); ssKey.reserve(1000); ssKey << key; - Dbt datKey(ssKey.data(), ssKey.size()); + SafeDbt datKey(ssKey.data(), ssKey.size()); // Read - Dbt datValue; - datValue.set_flags(DB_DBT_MALLOC); - int ret = pdb->get(activeTxn, &datKey, &datValue, 0); - memory_cleanse(datKey.get_data(), datKey.get_size()); + SafeDbt datValue; + int ret = pdb->get(activeTxn, datKey, datValue, 0); bool success = false; if (datValue.get_data() != nullptr) { // Unserialize value @@ -225,10 +266,6 @@ public: } catch (const std::exception&) { // In this case success remains 'false' } - - // Clear and free memory - memory_cleanse(datValue.get_data(), datValue.get_size()); - free(datValue.get_data()); } return ret == 0 && success; } @@ -245,20 +282,16 @@ public: CDataStream ssKey(SER_DISK, CLIENT_VERSION); ssKey.reserve(1000); ssKey << key; - Dbt datKey(ssKey.data(), ssKey.size()); + SafeDbt datKey(ssKey.data(), ssKey.size()); // Value CDataStream ssValue(SER_DISK, CLIENT_VERSION); ssValue.reserve(10000); ssValue << value; - Dbt datValue(ssValue.data(), ssValue.size()); + SafeDbt datValue(ssValue.data(), ssValue.size()); // Write - int ret = pdb->put(activeTxn, &datKey, &datValue, (fOverwrite ? 0 : DB_NOOVERWRITE)); - - // Clear memory in case it was a private key - memory_cleanse(datKey.get_data(), datKey.get_size()); - memory_cleanse(datValue.get_data(), datValue.get_size()); + int ret = pdb->put(activeTxn, datKey, datValue, (fOverwrite ? 0 : DB_NOOVERWRITE)); return (ret == 0); } @@ -274,13 +307,10 @@ public: CDataStream ssKey(SER_DISK, CLIENT_VERSION); ssKey.reserve(1000); ssKey << key; - Dbt datKey(ssKey.data(), ssKey.size()); + SafeDbt datKey(ssKey.data(), ssKey.size()); // Erase - int ret = pdb->del(activeTxn, &datKey, 0); - - // Clear memory - memory_cleanse(datKey.get_data(), datKey.get_size()); + int ret = pdb->del(activeTxn, datKey, 0); return (ret == 0 || ret == DB_NOTFOUND); } @@ -294,13 +324,10 @@ public: CDataStream ssKey(SER_DISK, CLIENT_VERSION); ssKey.reserve(1000); ssKey << key; - Dbt datKey(ssKey.data(), ssKey.size()); + SafeDbt datKey(ssKey.data(), ssKey.size()); // Exists - int ret = pdb->exists(activeTxn, &datKey, 0); - - // Clear memory - memory_cleanse(datKey.get_data(), datKey.get_size()); + int ret = pdb->exists(activeTxn, datKey, 0); return (ret == 0); } @@ -315,20 +342,12 @@ public: return pcursor; } - int ReadAtCursor(Dbc* pcursor, CDataStream& ssKey, CDataStream& ssValue, bool setRange = false) + int ReadAtCursor(Dbc* pcursor, CDataStream& ssKey, CDataStream& ssValue) { // Read at cursor - Dbt datKey; - unsigned int fFlags = DB_NEXT; - if (setRange) { - datKey.set_data(ssKey.data()); - datKey.set_size(ssKey.size()); - fFlags = DB_SET_RANGE; - } - Dbt datValue; - datKey.set_flags(DB_DBT_MALLOC); - datValue.set_flags(DB_DBT_MALLOC); - int ret = pcursor->get(&datKey, &datValue, fFlags); + SafeDbt datKey; + SafeDbt datValue; + int ret = pcursor->get(datKey, datValue, DB_NEXT); if (ret != 0) return ret; else if (datKey.get_data() == nullptr || datValue.get_data() == nullptr) @@ -341,16 +360,9 @@ public: ssValue.SetType(SER_DISK); ssValue.clear(); ssValue.write((char*)datValue.get_data(), datValue.get_size()); - - // Clear and free memory - memory_cleanse(datKey.get_data(), datKey.get_size()); - memory_cleanse(datValue.get_data(), datValue.get_size()); - free(datKey.get_data()); - free(datValue.get_data()); return 0; } -public: bool TxnBegin() { if (!pdb || activeTxn) diff --git a/src/wallet/feebumper.cpp b/src/wallet/feebumper.cpp index 677bf74b5d..7a71aea715 100644 --- a/src/wallet/feebumper.cpp +++ b/src/wallet/feebumper.cpp @@ -3,6 +3,7 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include <consensus/validation.h> +#include <interfaces/chain.h> #include <wallet/coincontrol.h> #include <wallet/feebumper.h> #include <wallet/fees.h> @@ -12,13 +13,13 @@ #include <policy/rbf.h> #include <validation.h> //for mempool access #include <txmempool.h> -#include <utilmoneystr.h> -#include <util.h> +#include <util/moneystr.h> +#include <util/system.h> #include <net.h> //! Check whether transaction has descendant in wallet or mempool, or has been //! mined, or conflicts with a mined transaction. Return a feebumper::Result. -static feebumper::Result PreconditionChecks(const CWallet* wallet, const CWalletTx& wtx, std::vector<std::string>& errors) EXCLUSIVE_LOCKS_REQUIRED(cs_main, wallet->cs_wallet) +static feebumper::Result PreconditionChecks(interfaces::Chain::Lock& locked_chain, const CWallet* wallet, const CWalletTx& wtx, std::vector<std::string>& errors) EXCLUSIVE_LOCKS_REQUIRED(wallet->cs_wallet) { if (wallet->HasWalletSpend(wtx.GetHash())) { errors.push_back("Transaction has descendants in the wallet"); @@ -34,7 +35,7 @@ static feebumper::Result PreconditionChecks(const CWallet* wallet, const CWallet } } - if (wtx.GetDepthInMainChain() != 0) { + if (wtx.GetDepthInMainChain(locked_chain) != 0) { errors.push_back("Transaction has been mined, or is conflicted with a mined transaction"); return feebumper::Result::WALLET_ERROR; } @@ -64,19 +65,21 @@ namespace feebumper { bool TransactionCanBeBumped(const CWallet* wallet, const uint256& txid) { - LOCK2(cs_main, wallet->cs_wallet); + auto locked_chain = wallet->chain().lock(); + LOCK(wallet->cs_wallet); const CWalletTx* wtx = wallet->GetWalletTx(txid); if (wtx == nullptr) return false; std::vector<std::string> errors_dummy; - feebumper::Result res = PreconditionChecks(wallet, *wtx, errors_dummy); + feebumper::Result res = PreconditionChecks(*locked_chain, wallet, *wtx, errors_dummy); return res == feebumper::Result::OK; } Result CreateTransaction(const CWallet* wallet, const uint256& txid, const CCoinControl& coin_control, CAmount total_fee, std::vector<std::string>& errors, CAmount& old_fee, CAmount& new_fee, CMutableTransaction& mtx) { - LOCK2(cs_main, wallet->cs_wallet); + auto locked_chain = wallet->chain().lock(); + LOCK(wallet->cs_wallet); errors.clear(); auto it = wallet->mapWallet.find(txid); if (it == wallet->mapWallet.end()) { @@ -85,7 +88,7 @@ Result CreateTransaction(const CWallet* wallet, const uint256& txid, const CCoin } const CWalletTx& wtx = it->second; - Result result = PreconditionChecks(wallet, wtx, errors); + Result result = PreconditionChecks(*locked_chain, wallet, wtx, errors); if (result != Result::OK) { return result; } @@ -212,13 +215,15 @@ Result CreateTransaction(const CWallet* wallet, const uint256& txid, const CCoin } bool SignTransaction(CWallet* wallet, CMutableTransaction& mtx) { - LOCK2(cs_main, wallet->cs_wallet); + auto locked_chain = wallet->chain().lock(); + LOCK(wallet->cs_wallet); return wallet->SignTransaction(mtx); } Result CommitTransaction(CWallet* wallet, const uint256& txid, CMutableTransaction&& mtx, std::vector<std::string>& errors, uint256& bumped_txid) { - LOCK2(cs_main, wallet->cs_wallet); + auto locked_chain = wallet->chain().lock(); + LOCK(wallet->cs_wallet); if (!errors.empty()) { return Result::MISC_ERROR; } @@ -230,7 +235,7 @@ Result CommitTransaction(CWallet* wallet, const uint256& txid, CMutableTransacti CWalletTx& oldWtx = it->second; // make sure the transaction still has no descendants and hasn't been mined in the meantime - Result result = PreconditionChecks(wallet, oldWtx, errors); + Result result = PreconditionChecks(*locked_chain, wallet, oldWtx, errors); if (result != Result::OK) { return result; } diff --git a/src/wallet/fees.cpp b/src/wallet/fees.cpp index d620e25f2b..9e2984ff05 100644 --- a/src/wallet/fees.cpp +++ b/src/wallet/fees.cpp @@ -7,7 +7,7 @@ #include <policy/policy.h> #include <txmempool.h> -#include <util.h> +#include <util/system.h> #include <validation.h> #include <wallet/coincontrol.h> #include <wallet/wallet.h> diff --git a/src/wallet/init.cpp b/src/wallet/init.cpp index a299a4ee44..87cd264c3d 100644 --- a/src/wallet/init.cpp +++ b/src/wallet/init.cpp @@ -5,11 +5,12 @@ #include <chainparams.h> #include <init.h> +#include <interfaces/chain.h> #include <net.h> #include <scheduler.h> #include <outputtype.h> -#include <util.h> -#include <utilmoneystr.h> +#include <util/system.h> +#include <util/moneystr.h> #include <validation.h> #include <walletinitinterface.h> #include <wallet/rpcwallet.h> @@ -28,28 +29,8 @@ public: //! Wallets parameter interaction bool ParameterInteraction() const override; - //! Register wallet RPCs. - void RegisterRPC(CRPCTable &tableRPC) const override; - - //! Responsible for reading and validating the -wallet arguments and verifying the wallet database. - // This function will perform salvage on the wallet if requested, as long as only one wallet is - // being loaded (WalletParameterInteraction forbids -salvagewallet, -zapwallettxes or -upgradewallet with multiwallet). - bool Verify() const override; - - //! Load wallet databases. - bool Open() const override; - - //! Complete startup of wallets. - void Start(CScheduler& scheduler) const override; - - //! Flush all wallets in preparation for shutdown. - void Flush() const override; - - //! Stop all wallets. Wallets will be flushed first. - void Stop() const override; - - //! Close all wallets. - void Close() const override; + //! Add wallets that should be opened to list of init interfaces. + void Construct(InitInterfaces& interfaces) const override; }; const WalletInitInterface& g_wallet_init_interface = WalletInit(); @@ -57,7 +38,7 @@ const WalletInitInterface& g_wallet_init_interface = WalletInit(); void WalletInit::AddWalletOptions() const { gArgs.AddArg("-addresstype", strprintf("What type of addresses to use (\"legacy\", \"p2sh-segwit\", or \"bech32\", default: \"%s\")", FormatOutputType(DEFAULT_ADDRESS_TYPE)), false, OptionsCategory::WALLET); - gArgs.AddArg("-avoidpartialspends", strprintf(_("Group outputs by address, selecting all or none, instead of selecting on a per-output basis. Privacy is improved as an address is only used once (unless someone sends to it after spending from it), but may result in slightly higher fees as suboptimal coin selection may result due to the added limitation (default: %u)"), DEFAULT_AVOIDPARTIALSPENDS), false, OptionsCategory::WALLET); + gArgs.AddArg("-avoidpartialspends", strprintf("Group outputs by address, selecting all or none, instead of selecting on a per-output basis. Privacy is improved as an address is only used once (unless someone sends to it after spending from it), but may result in slightly higher fees as suboptimal coin selection may result due to the added limitation (default: %u)", DEFAULT_AVOIDPARTIALSPENDS), false, OptionsCategory::WALLET); gArgs.AddArg("-changetype", "What type of change to use (\"legacy\", \"p2sh-segwit\", or \"bech32\"). Default is same as -addresstype, except when -addresstype=p2sh-segwit a native segwit output is used when sending to a native segwit address)", false, OptionsCategory::WALLET); gArgs.AddArg("-disablewallet", "Do not load the wallet and disable wallet RPC calls", false, OptionsCategory::WALLET); gArgs.AddArg("-discardfee=<amt>", strprintf("The fee rate (in %s/kB) that indicates your tolerance for discarding change by adding it to the fee (default: %s). " @@ -99,7 +80,6 @@ bool WalletInit::ParameterInteraction() const return true; } - gArgs.SoftSetArg("-wallet", ""); const bool is_multiwallet = gArgs.GetArgs("-wallet").size() > 1; if (gArgs.GetBoolArg("-blocksonly", DEFAULT_BLOCKSONLY) && gArgs.SoftSetBoolArg("-walletbroadcast", false)) { @@ -165,38 +145,28 @@ bool WalletInit::ParameterInteraction() const return true; } -void WalletInit::RegisterRPC(CRPCTable &t) const +bool VerifyWallets(interfaces::Chain& chain, const std::vector<std::string>& wallet_files) { - if (gArgs.GetBoolArg("-disablewallet", DEFAULT_DISABLE_WALLET)) { - return; - } - - RegisterWalletRPCCommands(t); -} - -bool WalletInit::Verify() const -{ - if (gArgs.GetBoolArg("-disablewallet", DEFAULT_DISABLE_WALLET)) { - return true; - } - if (gArgs.IsArgSet("-walletdir")) { fs::path wallet_dir = gArgs.GetArg("-walletdir", ""); - if (!fs::exists(wallet_dir)) { + boost::system::error_code error; + // The canonical path cleans the path, preventing >1 Berkeley environment instances for the same directory + fs::path canonical_wallet_dir = fs::canonical(wallet_dir, error); + if (error || !fs::exists(wallet_dir)) { return InitError(strprintf(_("Specified -walletdir \"%s\" does not exist"), wallet_dir.string())); } else if (!fs::is_directory(wallet_dir)) { return InitError(strprintf(_("Specified -walletdir \"%s\" is not a directory"), wallet_dir.string())); + // The canonical path transforms relative paths into absolute ones, so we check the non-canonical version } else if (!wallet_dir.is_absolute()) { return InitError(strprintf(_("Specified -walletdir \"%s\" is a relative path"), wallet_dir.string())); } + gArgs.ForceSetArg("-walletdir", canonical_wallet_dir.string()); } LogPrintf("Using wallet directory %s\n", GetWalletDir().string()); uiInterface.InitMessage(_("Verifying wallet(s)...")); - std::vector<std::string> wallet_files = gArgs.GetArgs("-wallet"); - // Parameter interaction code should have thrown an error if -salvagewallet // was enabled with more than wallet file, so the wallet_files size check // here should have no effect. @@ -206,15 +176,15 @@ bool WalletInit::Verify() const std::set<fs::path> wallet_paths; for (const auto& wallet_file : wallet_files) { - fs::path wallet_path = fs::absolute(wallet_file, GetWalletDir()); + WalletLocation location(wallet_file); - if (!wallet_paths.insert(wallet_path).second) { + if (!wallet_paths.insert(location.GetPath()).second) { return InitError(strprintf(_("Error loading wallet %s. Duplicate -wallet filename specified."), wallet_file)); } std::string error_string; std::string warning_string; - bool verify_success = CWallet::Verify(wallet_file, salvage_wallet, error_string, warning_string); + bool verify_success = CWallet::Verify(chain, location, salvage_wallet, error_string, warning_string); if (!error_string.empty()) InitError(error_string); if (!warning_string.empty()) InitWarning(warning_string); if (!verify_success) return false; @@ -223,15 +193,20 @@ bool WalletInit::Verify() const return true; } -bool WalletInit::Open() const +void WalletInit::Construct(InitInterfaces& interfaces) const { if (gArgs.GetBoolArg("-disablewallet", DEFAULT_DISABLE_WALLET)) { LogPrintf("Wallet disabled!\n"); - return true; + return; } + gArgs.SoftSetArg("-wallet", ""); + interfaces.chain_clients.emplace_back(interfaces::MakeWalletClient(*interfaces.chain, gArgs.GetArgs("-wallet"))); +} - for (const std::string& walletFile : gArgs.GetArgs("-wallet")) { - std::shared_ptr<CWallet> pwallet = CWallet::CreateWalletFromFile(walletFile, fs::absolute(walletFile, GetWalletDir())); +bool LoadWallets(interfaces::Chain& chain, const std::vector<std::string>& wallet_files) +{ + for (const std::string& walletFile : wallet_files) { + std::shared_ptr<CWallet> pwallet = CWallet::CreateWalletFromFile(chain, WalletLocation(walletFile)); if (!pwallet) { return false; } @@ -241,7 +216,7 @@ bool WalletInit::Open() const return true; } -void WalletInit::Start(CScheduler& scheduler) const +void StartWallets(CScheduler& scheduler) { for (const std::shared_ptr<CWallet>& pwallet : GetWallets()) { pwallet->postInitProcess(); @@ -251,23 +226,27 @@ void WalletInit::Start(CScheduler& scheduler) const scheduler.scheduleEvery(MaybeCompactWalletDB, 500); } -void WalletInit::Flush() const +void FlushWallets() { for (const std::shared_ptr<CWallet>& pwallet : GetWallets()) { pwallet->Flush(false); } } -void WalletInit::Stop() const +void StopWallets() { for (const std::shared_ptr<CWallet>& pwallet : GetWallets()) { pwallet->Flush(true); } } -void WalletInit::Close() const +void UnloadWallets() { - for (const std::shared_ptr<CWallet>& pwallet : GetWallets()) { - RemoveWallet(pwallet); + auto wallets = GetWallets(); + while (!wallets.empty()) { + auto wallet = wallets.back(); + wallets.pop_back(); + RemoveWallet(wallet); + UnloadWallet(std::move(wallet)); } } diff --git a/src/wallet/rpcdump.cpp b/src/wallet/rpcdump.cpp index c97bc38e6f..295dfcc63f 100644 --- a/src/wallet/rpcdump.cpp +++ b/src/wallet/rpcdump.cpp @@ -3,21 +3,22 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include <chain.h> +#include <core_io.h> +#include <interfaces/chain.h> #include <key_io.h> +#include <merkleblock.h> #include <rpc/server.h> -#include <validation.h> +#include <rpc/util.h> #include <script/script.h> #include <script/standard.h> #include <sync.h> -#include <util.h> -#include <utiltime.h> +#include <util/system.h> +#include <util/time.h> +#include <validation.h> #include <wallet/wallet.h> -#include <merkleblock.h> -#include <core_io.h> #include <wallet/rpcwallet.h> -#include <fstream> #include <stdint.h> #include <boost/algorithm/string.hpp> @@ -108,13 +109,15 @@ UniValue importprivkey(const JSONRPCRequest& request) if (request.fHelp || request.params.size() < 1 || request.params.size() > 3) throw std::runtime_error( - "importprivkey \"privkey\" ( \"label\" ) ( rescan )\n" - "\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" - "\nArguments:\n" - "1. \"privkey\" (string, required) The private key (see dumpprivkey)\n" - "2. \"label\" (string, optional, default=\"\") An optional label\n" - "3. rescan (boolean, optional, default=true) Rescan the wallet for transactions\n" + 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", + { + {"privkey", RPCArg::Type::STR, /* opt */ false, /* default_val */ "", "The private key (see dumpprivkey)"}, + {"label", RPCArg::Type::STR, /* opt */ true, /* default_val */ "current label if address exists, otherwise \"\"", "An optional label"}, + {"rescan", RPCArg::Type::BOOL, /* opt */ true, /* default_val */ "true", "Rescan the wallet for transactions"}, + }} + .ToString() + "\nNote: This call can take over an hour to complete if rescan is true, during that time, other rpc calls\n" "may report that the imported key exists but related transactions are still missing, leading to temporarily incorrect/bogus balances and unspent outputs until rescan completes.\n" "\nExamples:\n" @@ -134,7 +137,8 @@ UniValue importprivkey(const JSONRPCRequest& request) WalletRescanReserver reserver(pwallet); bool fRescan = true; { - LOCK2(cs_main, pwallet->cs_wallet); + auto locked_chain = pwallet->chain().lock(); + LOCK(pwallet->cs_wallet); EnsureWalletIsUnlocked(pwallet); @@ -162,9 +166,14 @@ UniValue importprivkey(const JSONRPCRequest& request) CKeyID vchAddress = pubkey.GetID(); { pwallet->MarkDirty(); - // We don't know which corresponding address will be used; label them all + + // We don't know which corresponding address will be used; + // label all new addresses, and label existing addresses if a + // label was passed. for (const auto& dest : GetAllDestinationsForKey(pubkey)) { - pwallet->SetAddressBook(dest, strLabel, "receive"); + if (!request.params[1].isNull() || pwallet->mapAddressBook.count(dest) == 0) { + pwallet->SetAddressBook(dest, strLabel, "receive"); + } } // Don't throw error in case a key is already there @@ -199,8 +208,9 @@ UniValue abortrescan(const JSONRPCRequest& request) if (request.fHelp || request.params.size() > 0) throw std::runtime_error( - "abortrescan\n" - "\nStops current wallet rescan triggered by an RPC call, e.g. by an importprivkey call.\n" + RPCHelpMan{"abortrescan", + "\nStops current wallet rescan triggered by an RPC call, e.g. by an importprivkey call.\n", {}} + .ToString() + "\nExamples:\n" "\nImport a private key\n" + HelpExampleCli("importprivkey", "\"mykey\"") + @@ -261,13 +271,15 @@ UniValue importaddress(const JSONRPCRequest& request) if (request.fHelp || request.params.size() < 1 || request.params.size() > 4) throw std::runtime_error( - "importaddress \"address\" ( \"label\" rescan p2sh )\n" - "\nAdds an address or script (in hex) that can be watched as if it were in your wallet but cannot be used to spend. Requires a new wallet backup.\n" - "\nArguments:\n" - "1. \"address\" (string, required) The Bitcoin address (or hex-encoded script)\n" - "2. \"label\" (string, optional, default=\"\") An optional label\n" - "3. rescan (boolean, optional, default=true) Rescan the wallet for transactions\n" - "4. p2sh (boolean, optional, default=false) Add the P2SH version of the script as well\n" + 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", + { + {"address", RPCArg::Type::STR, /* opt */ false, /* default_val */ "", "The Bitcoin address (or hex-encoded script)"}, + {"label", RPCArg::Type::STR, /* opt */ true, /* default_val */ "\"\"", "An optional label"}, + {"rescan", RPCArg::Type::BOOL, /* opt */ true, /* default_val */ "true", "Rescan the wallet for transactions"}, + {"p2sh", RPCArg::Type::BOOL, /* opt */ true, /* default_val */ "false", "Add the P2SH version of the script as well"}, + }} + .ToString() + "\nNote: This call can take over an hour to complete if rescan is true, during that time, other rpc calls\n" "may report that the imported address exists but related transactions are still missing, leading to temporarily incorrect/bogus balances and unspent outputs until rescan completes.\n" "If you have the full public key, you should call importpubkey instead of this.\n" @@ -306,7 +318,8 @@ UniValue importaddress(const JSONRPCRequest& request) fP2SH = request.params[3].get_bool(); { - LOCK2(cs_main, pwallet->cs_wallet); + auto locked_chain = pwallet->chain().lock(); + LOCK(pwallet->cs_wallet); CTxDestination dest = DecodeDestination(request.params[0].get_str()); if (IsValidDestination(dest)) { @@ -340,11 +353,13 @@ UniValue importprunedfunds(const JSONRPCRequest& request) if (request.fHelp || request.params.size() != 2) throw std::runtime_error( - "importprunedfunds\n" - "\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" - "\nArguments:\n" - "1. \"rawtransaction\" (string, required) A raw transaction in hex funding an already-existing address in wallet\n" - "2. \"txoutproof\" (string, required) The hex output from gettxoutproof that contains the transaction\n" + 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", + { + {"rawtransaction", RPCArg::Type::STR_HEX, /* opt */ false, /* default_val */ "", "A raw transaction in hex funding an already-existing address in wallet"}, + {"txoutproof", RPCArg::Type::STR_HEX, /* opt */ false, /* default_val */ "", "The hex output from gettxoutproof that contains the transaction"}, + }} + .ToString() ); CMutableTransaction tx; @@ -363,7 +378,7 @@ UniValue importprunedfunds(const JSONRPCRequest& request) unsigned int txnIndex = 0; if (merkleBlock.txn.ExtractMatches(vMatch, vIndex) == merkleBlock.header.hashMerkleRoot) { - LOCK(cs_main); + auto locked_chain = pwallet->chain().lock(); const CBlockIndex* pindex = LookupBlockIndex(merkleBlock.header.GetHash()); if (!pindex || !chainActive.Contains(pindex)) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found in chain"); @@ -383,7 +398,8 @@ UniValue importprunedfunds(const JSONRPCRequest& request) wtx.nIndex = txnIndex; wtx.hashBlock = merkleBlock.header.GetHash(); - LOCK2(cs_main, pwallet->cs_wallet); + auto locked_chain = pwallet->chain().lock(); + LOCK(pwallet->cs_wallet); if (pwallet->IsMine(*wtx.tx)) { pwallet->AddToWallet(wtx, false); @@ -403,17 +419,20 @@ UniValue removeprunedfunds(const JSONRPCRequest& request) if (request.fHelp || request.params.size() != 1) throw std::runtime_error( - "removeprunedfunds \"txid\"\n" - "\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" - "\nArguments:\n" - "1. \"txid\" (string, required) The hex-encoded id of the transaction you are deleting\n" + 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", + { + {"txid", RPCArg::Type::STR_HEX, /* opt */ false, /* default_val */ "", "The hex-encoded id of the transaction you are deleting"}, + }} + .ToString() + "\nExamples:\n" + HelpExampleCli("removeprunedfunds", "\"a8d0c0184dde994a09ec054286f1ce581bebf46446a512166eae7628734ea0a5\"") + "\nAs a JSON-RPC call\n" + HelpExampleRpc("removeprunedfunds", "\"a8d0c0184dde994a09ec054286f1ce581bebf46446a512166eae7628734ea0a5\"") ); - LOCK2(cs_main, pwallet->cs_wallet); + auto locked_chain = pwallet->chain().lock(); + LOCK(pwallet->cs_wallet); uint256 hash(ParseHashV(request.params[0], "txid")); std::vector<uint256> vHash; @@ -441,12 +460,14 @@ UniValue importpubkey(const JSONRPCRequest& request) if (request.fHelp || request.params.size() < 1 || request.params.size() > 3) throw std::runtime_error( - "importpubkey \"pubkey\" ( \"label\" rescan )\n" - "\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" - "\nArguments:\n" - "1. \"pubkey\" (string, required) The hex-encoded public key\n" - "2. \"label\" (string, optional, default=\"\") An optional label\n" - "3. rescan (boolean, optional, default=true) Rescan the wallet for transactions\n" + 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", + { + {"pubkey", RPCArg::Type::STR, /* opt */ false, /* default_val */ "", "The hex-encoded public key"}, + {"label", RPCArg::Type::STR, /* opt */ true, /* default_val */ "\"\"", "An optional label"}, + {"rescan", RPCArg::Type::BOOL, /* opt */ true, /* default_val */ "true", "Rescan the wallet for transactions"}, + }} + .ToString() + "\nNote: This call can take over an hour to complete if rescan is true, during that time, other rpc calls\n" "may report that the imported pubkey exists but related transactions are still missing, leading to temporarily incorrect/bogus balances and unspent outputs until rescan completes.\n" "\nExamples:\n" @@ -484,7 +505,8 @@ UniValue importpubkey(const JSONRPCRequest& request) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Pubkey is not a valid public key"); { - LOCK2(cs_main, pwallet->cs_wallet); + auto locked_chain = pwallet->chain().lock(); + LOCK(pwallet->cs_wallet); for (const auto& dest : GetAllDestinationsForKey(pubKey)) { ImportAddress(pwallet, dest, strLabel); @@ -512,10 +534,12 @@ UniValue importwallet(const JSONRPCRequest& request) if (request.fHelp || request.params.size() != 1) throw std::runtime_error( - "importwallet \"filename\"\n" - "\nImports keys from a wallet dump file (see dumpwallet). Requires a new wallet backup to include imported keys.\n" - "\nArguments:\n" - "1. \"filename\" (string, required) The wallet file\n" + RPCHelpMan{"importwallet", + "\nImports keys from a wallet dump file (see dumpwallet). Requires a new wallet backup to include imported keys.\n", + { + {"filename", RPCArg::Type::STR, /* opt */ false, /* default_val */ "", "The wallet file"}, + }} + .ToString() + "\nExamples:\n" "\nDump the wallet\n" + HelpExampleCli("dumpwallet", "\"test\"") + @@ -536,12 +560,13 @@ UniValue importwallet(const JSONRPCRequest& request) int64_t nTimeBegin = 0; bool fGood = true; { - LOCK2(cs_main, pwallet->cs_wallet); + auto locked_chain = pwallet->chain().lock(); + LOCK(pwallet->cs_wallet); EnsureWalletIsUnlocked(pwallet); - std::ifstream file; - file.open(request.params[0].get_str().c_str(), std::ios::in | std::ios::ate); + fsbridge::ifstream file; + file.open(request.params[0].get_str(), std::ios::in | std::ios::ate); if (!file.is_open()) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot open wallet dump file"); } @@ -641,11 +666,13 @@ UniValue dumpprivkey(const JSONRPCRequest& request) if (request.fHelp || request.params.size() != 1) throw std::runtime_error( - "dumpprivkey \"address\"\n" - "\nReveals the private key corresponding to 'address'.\n" - "Then the importprivkey can be used with this output\n" - "\nArguments:\n" - "1. \"address\" (string, required) The bitcoin address for the private key\n" + RPCHelpMan{"dumpprivkey", + "\nReveals the private key corresponding to 'address'.\n" + "Then the importprivkey can be used with this output\n", + { + {"address", RPCArg::Type::STR, /* opt */ false, /* default_val */ "", "The bitcoin address for the private key"}, + }} + .ToString() + "\nResult:\n" "\"key\" (string) The private key\n" "\nExamples:\n" @@ -654,7 +681,8 @@ UniValue dumpprivkey(const JSONRPCRequest& request) + HelpExampleRpc("dumpprivkey", "\"myaddress\"") ); - LOCK2(cs_main, pwallet->cs_wallet); + auto locked_chain = pwallet->chain().lock(); + LOCK(pwallet->cs_wallet); EnsureWalletIsUnlocked(pwallet); @@ -685,13 +713,15 @@ UniValue dumpwallet(const JSONRPCRequest& request) if (request.fHelp || request.params.size() != 1) throw std::runtime_error( - "dumpwallet \"filename\"\n" - "\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" - "Note that if your wallet contains keys which are not derived from your HD seed (e.g. imported keys), these are not covered by\n" - "only backing up the seed itself, and must be backed up too (e.g. ensure you back up the whole dumpfile).\n" - "\nArguments:\n" - "1. \"filename\" (string, required) The filename with path (either absolute or relative to bitcoind)\n" + 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" + "Note that if your wallet contains keys which are not derived from your HD seed (e.g. imported keys), these are not covered by\n" + "only backing up the seed itself, and must be backed up too (e.g. ensure you back up the whole dumpfile).\n", + { + {"filename", RPCArg::Type::STR, /* opt */ false, /* default_val */ "", "The filename with path (either absolute or relative to bitcoind)"}, + }} + .ToString() + "\nResult:\n" "{ (json object)\n" " \"filename\" : { (string) The filename with full absolute path\n" @@ -701,7 +731,8 @@ UniValue dumpwallet(const JSONRPCRequest& request) + HelpExampleRpc("dumpwallet", "\"test\"") ); - LOCK2(cs_main, pwallet->cs_wallet); + auto locked_chain = pwallet->chain().lock(); + LOCK(pwallet->cs_wallet); EnsureWalletIsUnlocked(pwallet); @@ -717,14 +748,14 @@ UniValue dumpwallet(const JSONRPCRequest& request) throw JSONRPCError(RPC_INVALID_PARAMETER, filepath.string() + " already exists. If you are sure this is what you want, move it out of the way first"); } - std::ofstream file; - file.open(filepath.string().c_str()); + fsbridge::ofstream file; + file.open(filepath); if (!file.is_open()) throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot open wallet dump file"); std::map<CTxDestination, int64_t> mapKeyBirth; const std::map<CKeyID, int64_t>& mapKeyPool = pwallet->GetAllReserveKeys(); - pwallet->GetKeyBirthTimes(mapKeyBirth); + pwallet->GetKeyBirthTimes(*locked_chain, mapKeyBirth); std::set<CScriptID> scripts = pwallet->GetCScripts(); // TODO: include scripts in GetKeyBirthTimes() output instead of separate @@ -805,47 +836,129 @@ UniValue dumpwallet(const JSONRPCRequest& request) return reply; } +struct ImportData +{ + // Input data + std::unique_ptr<CScript> redeemscript; //!< Provided redeemScript; will be moved to `import_scripts` if relevant. + std::unique_ptr<CScript> witnessscript; //!< Provided witnessScript; will be moved to `import_scripts` if relevant. + + // Output data + std::set<CScript> import_scripts; + std::map<CKeyID, bool> used_keys; //!< Import these private keys if available (the value indicates whether if the key is required for solvability) +}; + +enum class ScriptContext +{ + TOP, //! Top-level scriptPubKey + P2SH, //! P2SH redeemScript + WITNESS_V0, //! P2WSH witnessScript +}; + +// Analyse the provided scriptPubKey, determining which keys and which redeem scripts from the ImportData struct are needed to spend it, and mark them as used. +// Returns an error string, or the empty string for success. +static std::string RecurseImportData(const CScript& script, ImportData& import_data, const ScriptContext script_ctx) +{ + // Use Solver to obtain script type and parsed pubkeys or hashes: + std::vector<std::vector<unsigned char>> solverdata; + txnouttype script_type = Solver(script, solverdata); + + switch (script_type) { + case TX_PUBKEY: { + CPubKey pubkey(solverdata[0].begin(), solverdata[0].end()); + import_data.used_keys.emplace(pubkey.GetID(), false); + return ""; + } + case TX_PUBKEYHASH: { + CKeyID id = CKeyID(uint160(solverdata[0])); + import_data.used_keys[id] = true; + return ""; + } + case TX_SCRIPTHASH: { + if (script_ctx == ScriptContext::P2SH) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Trying to nest P2SH inside another P2SH"); + if (script_ctx == ScriptContext::WITNESS_V0) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Trying to nest P2SH inside a P2WSH"); + assert(script_ctx == ScriptContext::TOP); + CScriptID id = CScriptID(uint160(solverdata[0])); + auto subscript = std::move(import_data.redeemscript); // Remove redeemscript from import_data to check for superfluous script later. + if (!subscript) return "missing redeemscript"; + if (CScriptID(*subscript) != id) return "redeemScript does not match the scriptPubKey"; + import_data.import_scripts.emplace(*subscript); + return RecurseImportData(*subscript, import_data, ScriptContext::P2SH); + } + case TX_MULTISIG: { + for (size_t i = 1; i + 1< solverdata.size(); ++i) { + CPubKey pubkey(solverdata[i].begin(), solverdata[i].end()); + import_data.used_keys.emplace(pubkey.GetID(), false); + } + return ""; + } + case TX_WITNESS_V0_SCRIPTHASH: { + if (script_ctx == ScriptContext::WITNESS_V0) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Trying to nest P2WSH inside another P2WSH"); + uint256 fullid(solverdata[0]); + CScriptID id; + CRIPEMD160().Write(fullid.begin(), fullid.size()).Finalize(id.begin()); + auto subscript = std::move(import_data.witnessscript); // Remove redeemscript from import_data to check for superfluous script later. + if (!subscript) return "missing witnessscript"; + if (CScriptID(*subscript) != id) return "witnessScript does not match the scriptPubKey or redeemScript"; + if (script_ctx == ScriptContext::TOP) { + import_data.import_scripts.emplace(script); // Special rule for IsMine: native P2WSH requires the TOP script imported (see script/ismine.cpp) + } + import_data.import_scripts.emplace(*subscript); + return RecurseImportData(*subscript, import_data, ScriptContext::WITNESS_V0); + } + case TX_WITNESS_V0_KEYHASH: { + if (script_ctx == ScriptContext::WITNESS_V0) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Trying to nest P2WPKH inside P2WSH"); + CKeyID id = CKeyID(uint160(solverdata[0])); + import_data.used_keys[id] = true; + if (script_ctx == ScriptContext::TOP) { + import_data.import_scripts.emplace(script); // Special rule for IsMine: native P2WPKH requires the TOP script imported (see script/ismine.cpp) + } + return ""; + } + case TX_NULL_DATA: + return "unspendable script"; + case TX_NONSTANDARD: + case TX_WITNESS_UNKNOWN: + default: + return "unrecognized script"; + } +} static UniValue ProcessImport(CWallet * const pwallet, const UniValue& data, const int64_t timestamp) EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet) { - try { - bool success = false; + UniValue warnings(UniValue::VARR); + UniValue result(UniValue::VOBJ); - // Required fields. + try { + // First ensure scriptPubKey has either a script or JSON with "address" string const UniValue& scriptPubKey = data["scriptPubKey"]; - - // Should have script or JSON with "address". - if (!(scriptPubKey.getType() == UniValue::VOBJ && scriptPubKey.exists("address")) && !(scriptPubKey.getType() == UniValue::VSTR)) { - throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid scriptPubKey"); + bool isScript = scriptPubKey.getType() == UniValue::VSTR; + if (!isScript && !(scriptPubKey.getType() == UniValue::VOBJ && scriptPubKey.exists("address"))) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "scriptPubKey must be string with script or JSON with address string"); } + const std::string& output = isScript ? scriptPubKey.get_str() : scriptPubKey["address"].get_str(); // Optional fields. const std::string& strRedeemScript = data.exists("redeemscript") ? data["redeemscript"].get_str() : ""; + const std::string& witness_script_hex = data.exists("witnessscript") ? data["witnessscript"].get_str() : ""; const UniValue& pubKeys = data.exists("pubkeys") ? data["pubkeys"].get_array() : UniValue(); const UniValue& keys = data.exists("keys") ? data["keys"].get_array() : UniValue(); const bool internal = data.exists("internal") ? data["internal"].get_bool() : false; const bool watchOnly = data.exists("watchonly") ? data["watchonly"].get_bool() : false; - const std::string& label = data.exists("label") && !internal ? data["label"].get_str() : ""; + const std::string& label = data.exists("label") ? data["label"].get_str() : ""; - bool isScript = scriptPubKey.getType() == UniValue::VSTR; - bool isP2SH = strRedeemScript.length() > 0; - const std::string& output = isScript ? scriptPubKey.get_str() : scriptPubKey["address"].get_str(); - - // Parse the output. + // Generate the script and destination for the scriptPubKey provided CScript script; CTxDestination dest; - if (!isScript) { dest = DecodeDestination(output); if (!IsValidDestination(dest)) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid address"); + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid address \"" + output + "\""); } script = GetScriptForDestination(dest); } else { if (!IsHex(output)) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid scriptPubKey"); + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid scriptPubKey \"" + output + "\""); } - std::vector<unsigned char> vData(ParseHex(output)); script = CScript(vData.begin(), vData.end()); if (!ExtractDestination(script, dest) && !internal) { @@ -853,231 +966,161 @@ static UniValue ProcessImport(CWallet * const pwallet, const UniValue& data, con } } - // Watchonly and private keys - if (watchOnly && keys.size()) { - throw JSONRPCError(RPC_INVALID_PARAMETER, "Incompatibility found between watchonly and keys"); - } - - // Internal + Label - if (internal && data.exists("label")) { - throw JSONRPCError(RPC_INVALID_PARAMETER, "Incompatibility found between internal and label"); - } - - // Keys / PubKeys size check. - if (!isP2SH && (keys.size() > 1 || pubKeys.size() > 1)) { // Address / scriptPubKey - throw JSONRPCError(RPC_INVALID_PARAMETER, "More than private key given for one address"); + // Parse all arguments + ImportData import_data; + if (strRedeemScript.size()) { + if (!IsHex(strRedeemScript)) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid redeem script \"" + strRedeemScript + "\": must be hex string"); + } + auto parsed_redeemscript = ParseHex(strRedeemScript); + import_data.redeemscript = MakeUnique<CScript>(parsed_redeemscript.begin(), parsed_redeemscript.end()); } - - // Invalid P2SH redeemScript - if (isP2SH && !IsHex(strRedeemScript)) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid redeem script"); + if (witness_script_hex.size()) { + if (!IsHex(witness_script_hex)) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid witness script \"" + witness_script_hex + "\": must be hex string"); + } + auto parsed_witnessscript = ParseHex(witness_script_hex); + import_data.witnessscript = MakeUnique<CScript>(parsed_witnessscript.begin(), parsed_witnessscript.end()); } - - // Process. // - - // P2SH - if (isP2SH) { - // Import redeem script. - std::vector<unsigned char> vData(ParseHex(strRedeemScript)); - CScript redeemScript = CScript(vData.begin(), vData.end()); - - // Invalid P2SH address - if (!script.IsPayToScriptHash()) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid P2SH address / script"); + std::map<CKeyID, CPubKey> pubkey_map; + for (size_t i = 0; i < pubKeys.size(); ++i) { + const auto& str = pubKeys[i].get_str(); + if (!IsHex(str)) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Pubkey \"" + str + "\" must be a hex string"); } - - pwallet->MarkDirty(); - - if (!pwallet->AddWatchOnly(redeemScript, timestamp)) { - throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet"); + auto parsed_pubkey = ParseHex(str); + CPubKey pubkey(parsed_pubkey.begin(), parsed_pubkey.end()); + if (!pubkey.IsFullyValid()) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Pubkey \"" + str + "\" is not a valid public key"); } - - CScriptID redeem_id(redeemScript); - if (!pwallet->HaveCScript(redeem_id) && !pwallet->AddCScript(redeemScript)) { - throw JSONRPCError(RPC_WALLET_ERROR, "Error adding p2sh redeemScript to wallet"); + pubkey_map.emplace(pubkey.GetID(), pubkey); + } + std::map<CKeyID, CKey> privkey_map; + for (size_t i = 0; i < keys.size(); ++i) { + const auto& str = keys[i].get_str(); + CKey key = DecodeSecret(str); + if (!key.IsValid()) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid private key encoding"); } - - CScript redeemDestination = GetScriptForDestination(redeem_id); - - if (::IsMine(*pwallet, redeemDestination) == ISMINE_SPENDABLE) { - throw JSONRPCError(RPC_WALLET_ERROR, "The wallet already contains the private key for this address or script"); + CPubKey pubkey = key.GetPubKey(); + CKeyID id = pubkey.GetID(); + if (pubkey_map.count(id)) { + pubkey_map.erase(id); } + privkey_map.emplace(id, key); + } - pwallet->MarkDirty(); + // Internal addresses should not have a label + if (internal && data.exists("label")) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Internal addresses should not have a label"); + } - if (!pwallet->AddWatchOnly(redeemDestination, timestamp)) { - throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet"); - } + // Verify and process input data + bool have_solving_data = import_data.redeemscript || import_data.witnessscript || pubkey_map.size() || privkey_map.size(); + if (have_solving_data) { + // Match up data in import_data with the scriptPubKey in script. + auto error = RecurseImportData(script, import_data, ScriptContext::TOP); - // add to address book or update label - if (IsValidDestination(dest)) { - pwallet->SetAddressBook(dest, label, "receive"); + // Verify whether the watchonly option corresponds to the availability of private keys. + bool spendable = std::all_of(import_data.used_keys.begin(), import_data.used_keys.end(), [&](const std::pair<CKeyID, bool>& used_key){ return privkey_map.count(used_key.first) > 0; }); + if (!watchOnly && !spendable) { + warnings.push_back("Some private keys are missing, outputs will be considered watchonly. If this is intentional, specify the watchonly flag."); + } + if (watchOnly && spendable) { + warnings.push_back("All private keys are provided, outputs will be considered spendable. If this is intentional, do not specify the watchonly flag."); } - // Import private keys. - if (keys.size()) { - for (size_t i = 0; i < keys.size(); i++) { - const std::string& privkey = keys[i].get_str(); - - CKey key = DecodeSecret(privkey); - - if (!key.IsValid()) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid private key encoding"); - } - - CPubKey pubkey = key.GetPubKey(); - assert(key.VerifyPubKey(pubkey)); - - CKeyID vchAddress = pubkey.GetID(); - pwallet->MarkDirty(); - pwallet->SetAddressBook(vchAddress, label, "receive"); - - if (pwallet->HaveKey(vchAddress)) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Already have this key"); - } - - pwallet->mapKeyMetadata[vchAddress].nCreateTime = timestamp; - - if (!pwallet->AddKeyPubKey(key, pubkey)) { - throw JSONRPCError(RPC_WALLET_ERROR, "Error adding key to wallet"); + // Check that all required keys for solvability are provided. + if (error.empty()) { + for (const auto& require_key : import_data.used_keys) { + if (!require_key.second) continue; // Not a required key + if (pubkey_map.count(require_key.first) == 0 && privkey_map.count(require_key.first) == 0) { + error = "some required keys are missing"; } - - pwallet->UpdateTimeFirstKey(timestamp); } } - success = true; - } else { - // Import public keys. - if (pubKeys.size() && keys.size() == 0) { - const std::string& strPubKey = pubKeys[0].get_str(); - - if (!IsHex(strPubKey)) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Pubkey must be a hex string"); - } - - std::vector<unsigned char> vData(ParseHex(strPubKey)); - CPubKey pubKey(vData.begin(), vData.end()); - - if (!pubKey.IsFullyValid()) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Pubkey is not a valid public key"); - } - - CTxDestination pubkey_dest = pubKey.GetID(); - - // Consistency check. - if (!(pubkey_dest == dest)) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Consistency check failed"); - } - - CScript pubKeyScript = GetScriptForDestination(pubkey_dest); - - if (::IsMine(*pwallet, pubKeyScript) == ISMINE_SPENDABLE) { - throw JSONRPCError(RPC_WALLET_ERROR, "The wallet already contains the private key for this address or script"); - } - - pwallet->MarkDirty(); - - if (!pwallet->AddWatchOnly(pubKeyScript, timestamp)) { - throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet"); - } - - // add to address book or update label - if (IsValidDestination(pubkey_dest)) { - pwallet->SetAddressBook(pubkey_dest, label, "receive"); - } - - // TODO Is this necessary? - CScript scriptRawPubKey = GetScriptForRawPubKey(pubKey); - - if (::IsMine(*pwallet, scriptRawPubKey) == ISMINE_SPENDABLE) { - throw JSONRPCError(RPC_WALLET_ERROR, "The wallet already contains the private key for this address or script"); + if (!error.empty()) { + warnings.push_back("Importing as non-solvable: " + error + ". If this is intentional, don't provide any keys, pubkeys, witnessscript, or redeemscript."); + import_data = ImportData(); + pubkey_map.clear(); + privkey_map.clear(); + have_solving_data = false; + } else { + // RecurseImportData() removes any relevant redeemscript/witnessscript from import_data, so we can use that to discover if a superfluous one was provided. + if (import_data.redeemscript) warnings.push_back("Ignoring redeemscript as this is not a P2SH script."); + if (import_data.witnessscript) warnings.push_back("Ignoring witnessscript as this is not a (P2SH-)P2WSH script."); + for (auto it = privkey_map.begin(); it != privkey_map.end(); ) { + auto oldit = it++; + if (import_data.used_keys.count(oldit->first) == 0) { + warnings.push_back("Ignoring irrelevant private key."); + privkey_map.erase(oldit); + } } - - pwallet->MarkDirty(); - - if (!pwallet->AddWatchOnly(scriptRawPubKey, timestamp)) { - throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet"); + for (auto it = pubkey_map.begin(); it != pubkey_map.end(); ) { + auto oldit = it++; + auto key_data_it = import_data.used_keys.find(oldit->first); + if (key_data_it == import_data.used_keys.end() || !key_data_it->second) { + warnings.push_back("Ignoring public key \"" + HexStr(oldit->first) + "\" as it doesn't appear inside P2PKH or P2WPKH."); + pubkey_map.erase(oldit); + } } - - success = true; } + } - // Import private keys. - if (keys.size()) { - const std::string& strPrivkey = keys[0].get_str(); - - // Checks. - CKey key = DecodeSecret(strPrivkey); - - if (!key.IsValid()) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid private key encoding"); - } - - CPubKey pubKey = key.GetPubKey(); - assert(key.VerifyPubKey(pubKey)); - - CTxDestination pubkey_dest = pubKey.GetID(); - - // Consistency check. - if (!(pubkey_dest == dest)) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Consistency check failed"); - } - - CKeyID vchAddress = pubKey.GetID(); - pwallet->MarkDirty(); - pwallet->SetAddressBook(vchAddress, label, "receive"); - - if (pwallet->HaveKey(vchAddress)) { - throw JSONRPCError(RPC_WALLET_ERROR, "The wallet already contains the private key for this address or script"); - } - - pwallet->mapKeyMetadata[vchAddress].nCreateTime = timestamp; - - if (!pwallet->AddKeyPubKey(key, pubKey)) { - throw JSONRPCError(RPC_WALLET_ERROR, "Error adding key to wallet"); - } - - pwallet->UpdateTimeFirstKey(timestamp); + // Check whether we have any work to do + if (::IsMine(*pwallet, script) & ISMINE_SPENDABLE) { + throw JSONRPCError(RPC_WALLET_ERROR, "The wallet already contains the private key for this address or script"); + } - success = true; + // All good, time to import + pwallet->MarkDirty(); + for (const auto& entry : import_data.import_scripts) { + if (!pwallet->HaveCScript(CScriptID(entry)) && !pwallet->AddCScript(entry)) { + throw JSONRPCError(RPC_WALLET_ERROR, "Error adding script to wallet"); } - - // Import scriptPubKey only. - if (pubKeys.size() == 0 && keys.size() == 0) { - if (::IsMine(*pwallet, script) == ISMINE_SPENDABLE) { - throw JSONRPCError(RPC_WALLET_ERROR, "The wallet already contains the private key for this address or script"); - } - - pwallet->MarkDirty(); - - if (!pwallet->AddWatchOnly(script, timestamp)) { - throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet"); - } - - // add to address book or update label - if (IsValidDestination(dest)) { - pwallet->SetAddressBook(dest, label, "receive"); - } - - success = true; + } + for (const auto& entry : privkey_map) { + const CKey& key = entry.second; + CPubKey pubkey = key.GetPubKey(); + const CKeyID& id = entry.first; + assert(key.VerifyPubKey(pubkey)); + pwallet->mapKeyMetadata[id].nCreateTime = timestamp; + // If the private key is not present in the wallet, insert it. + if (!pwallet->HaveKey(id) && !pwallet->AddKeyPubKey(key, pubkey)) { + throw JSONRPCError(RPC_WALLET_ERROR, "Error adding key to wallet"); } + pwallet->UpdateTimeFirstKey(timestamp); + } + for (const auto& entry : pubkey_map) { + const CPubKey& pubkey = entry.second; + const CKeyID& id = entry.first; + CPubKey temp; + if (!pwallet->GetPubKey(id, temp) && !pwallet->AddWatchOnly(GetScriptForRawPubKey(pubkey), timestamp)) { + throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet"); + } + } + if (!have_solving_data || !::IsMine(*pwallet, script)) { // Always call AddWatchOnly for non-solvable watch-only, so that watch timestamp gets updated + if (!pwallet->AddWatchOnly(script, timestamp)) { + throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet"); + } + } + if (!internal) { + assert(IsValidDestination(dest)); + pwallet->SetAddressBook(dest, label, "receive"); } - UniValue result = UniValue(UniValue::VOBJ); - result.pushKV("success", UniValue(success)); - return result; + result.pushKV("success", UniValue(true)); } catch (const UniValue& e) { - UniValue result = UniValue(UniValue::VOBJ); result.pushKV("success", UniValue(false)); result.pushKV("error", e); - return result; } catch (...) { - UniValue result = UniValue(UniValue::VOBJ); result.pushKV("success", UniValue(false)); + result.pushKV("error", JSONRPCError(RPC_MISC_ERROR, "Missing required fields")); - return result; } + if (warnings.size()) result.pushKV("warnings", warnings); + return result; } static int64_t GetImportTimestamp(const UniValue& data, int64_t now) @@ -1102,35 +1145,54 @@ UniValue importmulti(const JSONRPCRequest& mainRequest) return NullUniValue; } - // clang-format off if (mainRequest.fHelp || mainRequest.params.size() < 1 || mainRequest.params.size() > 2) throw std::runtime_error( - "importmulti \"requests\" ( \"options\" )\n\n" - "Import addresses/scripts (with private or public keys, redeem script (P2SH)), rescanning all addresses in one-shot-only (rescan can be disabled via options). Requires a new wallet backup.\n\n" - "Arguments:\n" - "1. requests (array, required) Data to be imported\n" - " [ (array of json objects)\n" - " {\n" - " \"scriptPubKey\": \"<script>\" | { \"address\":\"<address>\" }, (string / json, required) Type of scriptPubKey (string for script, json for address)\n" - " \"timestamp\": timestamp | \"now\" , (integer / string, required) Creation time of the key in seconds since epoch (Jan 1 1970 GMT),\n" - " or the string \"now\" to substitute the current synced blockchain time. The timestamp of the oldest\n" - " key will determine how far back blockchain rescans need to begin for missing wallet transactions.\n" - " \"now\" can be specified to bypass scanning, for keys which are known to never have been used, and\n" - " 0 can be specified to scan the entire blockchain. Blocks up to 2 hours before the earliest key\n" - " creation time of all keys being imported by the importmulti call will be scanned.\n" - " \"redeemscript\": \"<script>\" , (string, optional) Allowed only if the scriptPubKey is a P2SH address or a P2SH scriptPubKey\n" - " \"pubkeys\": [\"<pubKey>\", ... ] , (array, optional) Array of strings giving pubkeys that must occur in the output or redeemscript\n" - " \"keys\": [\"<key>\", ... ] , (array, optional) Array of strings giving private keys whose corresponding public keys must occur in the output or redeemscript\n" - " \"internal\": <true> , (boolean, optional, default: false) Stating whether matching outputs should be treated as not incoming payments\n" - " \"watchonly\": <true> , (boolean, optional, default: false) Stating whether matching outputs should be considered watched even when they're not spendable, only allowed if keys are empty\n" - " \"label\": <label> , (string, optional, default: '') Label to assign to the address, only allowed with internal=false\n" - " }\n" - " ,...\n" - " ]\n" - "2. options (json, optional)\n" - " {\n" - " \"rescan\": <false>, (boolean, optional, default: true) Stating if should rescan the blockchain after all imports\n" - " }\n" + 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" + "Conversely, if all the private keys are provided and the address/script is spendable, the watchonly option must be set to false, or a warning will be returned.\n", + { + {"requests", RPCArg::Type::ARR, /* opt */ false, /* default_val */ "", "Data to be imported", + { + {"", RPCArg::Type::OBJ, /* opt */ false, /* default_val */ "", "", + { + {"scriptPubKey", RPCArg::Type::STR, /* opt */ false, /* default_val */ "", "Type of scriptPubKey (string for script, json for address)", + /* oneline_description */ "", {"\"<script>\" | { \"address\":\"<address>\" }", "string / json"} + }, + {"timestamp", RPCArg::Type::NUM, /* opt */ false, /* default_val */ "", "Creation time of the key in seconds since epoch (Jan 1 1970 GMT),\n" + " or the string \"now\" to substitute the current synced blockchain time. The timestamp of the oldest\n" + " key will determine how far back blockchain rescans need to begin for missing wallet transactions.\n" + " \"now\" can be specified to bypass scanning, for keys which are known to never have been used, and\n" + " 0 can be specified to scan the entire blockchain. Blocks up to 2 hours before the earliest key\n" + " creation time of all keys being imported by the importmulti call will be scanned.", + /* oneline_description */ "", {"timestamp | \"now\"", "integer / string"} + }, + {"redeemscript", RPCArg::Type::STR, /* opt */ true, /* default_val */ "omitted", "Allowed only if the scriptPubKey is a P2SH or P2SH-P2WSH address/scriptPubKey"}, + {"witnessscript", RPCArg::Type::STR, /* opt */ true, /* default_val */ "omitted", "Allowed only if the scriptPubKey is a P2SH-P2WSH or P2WSH address/scriptPubKey"}, + {"pubkeys", RPCArg::Type::ARR, /* opt */ true, /* default_val */ "empty array", "Array of strings giving pubkeys to import. They must occur in P2PKH or P2WPKH scripts. They are not required when the private key is also provided (see the \"keys\" argument).", + { + {"pubKey", RPCArg::Type::STR, /* opt */ false, /* default_val */ "", ""}, + } + }, + {"keys", RPCArg::Type::ARR, /* opt */ true, /* default_val */ "empty array", "Array of strings giving private keys to import. The corresponding public keys must occur in the output or redeemscript.", + { + {"key", RPCArg::Type::STR, /* opt */ false, /* default_val */ "", ""}, + } + }, + {"internal", RPCArg::Type::BOOL, /* opt */ true, /* default_val */ "false", "Stating whether matching outputs should be treated as not incoming payments (also known as change)"}, + {"watchonly", RPCArg::Type::BOOL, /* opt */ true, /* default_val */ "false", "Stating whether matching outputs should be considered watchonly."}, + {"label", RPCArg::Type::STR, /* opt */ true, /* default_val */ "''", "Label to assign to the address, only allowed with internal=false"}, + }, + }, + }, + "\"requests\""}, + {"options", RPCArg::Type::OBJ, /* opt */ true, /* default_val */ "null", "", + { + {"rescan", RPCArg::Type::BOOL, /* opt */ true, /* default_val */ "true", "Stating if should rescan the blockchain after all imports"}, + }, + "\"options\""}, + }} + .ToString() + "\nNote: This call can take over an hour to complete if rescan is true, during that time, other rpc calls\n" "may report that the imported keys, addresses or scripts exists but related transactions are still missing.\n" "\nExamples:\n" + @@ -1139,9 +1201,8 @@ UniValue importmulti(const JSONRPCRequest& mainRequest) HelpExampleCli("importmulti", "'[{ \"scriptPubKey\": { \"address\": \"<my address>\" }, \"timestamp\":1455191478 }]' '{ \"rescan\": false}'") + "\nResponse is an array with the same size as the input that has the execution result :\n" - " [{ \"success\": true } , { \"success\": false, \"error\": { \"code\": -1, \"message\": \"Internal Server Error\"} }, ... ]\n"); + " [{\"success\": true}, {\"success\": true, \"warnings\": [\"Ignoring irrelevant private key\"]}, {\"success\": false, \"error\": {\"code\": -1, \"message\": \"Internal Server Error\"}}, ...]\n"); - // clang-format on RPCTypeCheck(mainRequest.params, {UniValue::VARR, UniValue::VOBJ}); @@ -1168,7 +1229,8 @@ UniValue importmulti(const JSONRPCRequest& mainRequest) int64_t nLowestTimestamp = 0; UniValue response(UniValue::VARR); { - LOCK2(cs_main, pwallet->cs_wallet); + auto locked_chain = pwallet->chain().lock(); + LOCK(pwallet->cs_wallet); EnsureWalletIsUnlocked(pwallet); // Verify all timestamps are present before importing any keys. diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 7d0219201e..5e036eb5df 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -8,6 +8,8 @@ #include <consensus/validation.h> #include <core_io.h> #include <httpserver.h> +#include <init.h> +#include <interfaces/chain.h> #include <validation.h> #include <key_io.h> #include <net.h> @@ -20,11 +22,12 @@ #include <rpc/rawtransaction.h> #include <rpc/server.h> #include <rpc/util.h> +#include <script/descriptor.h> #include <script/sign.h> #include <shutdown.h> #include <timedata.h> -#include <util.h> -#include <utilmoneystr.h> +#include <util/system.h> +#include <util/moneystr.h> #include <wallet/coincontrol.h> #include <wallet/feebumper.h> #include <wallet/rpcwallet.h> @@ -89,9 +92,9 @@ void EnsureWalletIsUnlocked(CWallet * const pwallet) } } -static void WalletTxToJSON(const CWalletTx& wtx, UniValue& entry) EXCLUSIVE_LOCKS_REQUIRED(cs_main) +static void WalletTxToJSON(interfaces::Chain& chain, interfaces::Chain::Lock& locked_chain, const CWalletTx& wtx, UniValue& entry) { - int confirms = wtx.GetDepthInMainChain(); + int confirms = wtx.GetDepthInMainChain(locked_chain); entry.pushKV("confirmations", confirms); if (wtx.IsCoinBase()) entry.pushKV("generated", true); @@ -101,7 +104,7 @@ static void WalletTxToJSON(const CWalletTx& wtx, UniValue& entry) EXCLUSIVE_LOCK entry.pushKV("blockindex", wtx.nIndex); entry.pushKV("blocktime", LookupBlockIndex(wtx.hashBlock)->GetBlockTime()); } else { - entry.pushKV("trusted", wtx.IsTrusted()); + entry.pushKV("trusted", wtx.IsTrusted(locked_chain)); } uint256 hash = wtx.GetHash(); entry.pushKV("txid", hash.GetHex()); @@ -147,13 +150,15 @@ static UniValue getnewaddress(const JSONRPCRequest& request) if (request.fHelp || request.params.size() > 2) throw std::runtime_error( - "getnewaddress ( \"label\" \"address_type\" )\n" - "\nReturns a new Bitcoin address for receiving payments.\n" - "If 'label' is specified, it is added to the address book \n" - "so payments received with the address will be associated with 'label'.\n" - "\nArguments:\n" - "1. \"label\" (string, optional) The label name for the address to be linked to. If not provided, the default label \"\" is used. It can also be set to the empty string \"\" to represent the default label. The label does not need to exist, it will be created if there is no label by the given name.\n" - "2. \"address_type\" (string, optional) The address type to use. Options are \"legacy\", \"p2sh-segwit\", and \"bech32\". Default is set by -addresstype.\n" + RPCHelpMan{"getnewaddress", + "\nReturns a new Bitcoin address for receiving payments.\n" + "If 'label' is specified, it is added to the address book \n" + "so payments received with the address will be associated with 'label'.\n", + { + {"label", RPCArg::Type::STR, /* opt */ true, /* default_val */ "null", "The label name for the address to be linked to. If not provided, the default label \"\" is used. It can also be set to the empty string \"\" to represent the default label. The label does not need to exist, it will be created if there is no label by the given name."}, + {"address_type", RPCArg::Type::STR, /* opt */ true, /* default_val */ "set by -addresstype", "The address type to use. Options are \"legacy\", \"p2sh-segwit\", and \"bech32\"."}, + }} + .ToString() + "\nResult:\n" "\"address\" (string) The new bitcoin address\n" "\nExamples:\n" @@ -207,11 +212,13 @@ static UniValue getrawchangeaddress(const JSONRPCRequest& request) if (request.fHelp || request.params.size() > 1) throw std::runtime_error( - "getrawchangeaddress ( \"address_type\" )\n" - "\nReturns a new Bitcoin address, for receiving change.\n" - "This is for use with raw transactions, NOT normal use.\n" - "\nArguments:\n" - "1. \"address_type\" (string, optional) The address type to use. Options are \"legacy\", \"p2sh-segwit\", and \"bech32\". Default is set by -changetype.\n" + RPCHelpMan{"getrawchangeaddress", + "\nReturns a new Bitcoin address, for receiving change.\n" + "This is for use with raw transactions, NOT normal use.\n", + { + {"address_type", RPCArg::Type::STR, /* opt */ true, /* default_val */ "set by -changetype", "The address type to use. Options are \"legacy\", \"p2sh-segwit\", and \"bech32\"."}, + }} + .ToString() + "\nResult:\n" "\"address\" (string) The address\n" "\nExamples:\n" @@ -261,11 +268,13 @@ static UniValue setlabel(const JSONRPCRequest& request) if (request.fHelp || request.params.size() != 2) throw std::runtime_error( - "setlabel \"address\" \"label\"\n" - "\nSets the label associated with the given address.\n" - "\nArguments:\n" - "1. \"address\" (string, required) The bitcoin address to be associated with a label.\n" - "2. \"label\" (string, required) The label to assign to the address.\n" + RPCHelpMan{"setlabel", + "\nSets the label associated with the given address.\n", + { + {"address", RPCArg::Type::STR, /* opt */ false, /* default_val */ "", "The bitcoin address to be associated with a label."}, + {"label", RPCArg::Type::STR, /* opt */ false, /* default_val */ "", "The label to assign to the address."}, + }} + .ToString() + "\nExamples:\n" + HelpExampleCli("setlabel", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\" \"tabby\"") + HelpExampleRpc("setlabel", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\", \"tabby\"") @@ -290,7 +299,7 @@ static UniValue setlabel(const JSONRPCRequest& request) } -static CTransactionRef SendMoney(CWallet * const pwallet, const CTxDestination &address, CAmount nValue, bool fSubtractFeeFromAmount, const CCoinControl& coin_control, mapValue_t mapValue) +static CTransactionRef SendMoney(interfaces::Chain::Lock& locked_chain, CWallet * const pwallet, const CTxDestination &address, CAmount nValue, bool fSubtractFeeFromAmount, const CCoinControl& coin_control, mapValue_t mapValue) { CAmount curBalance = pwallet->GetBalance(); @@ -317,7 +326,7 @@ static CTransactionRef SendMoney(CWallet * const pwallet, const CTxDestination & CRecipient recipient = {scriptPubKey, nValue, fSubtractFeeFromAmount}; vecSend.push_back(recipient); CTransactionRef tx; - if (!pwallet->CreateTransaction(vecSend, tx, reservekey, nFeeRequired, nChangePosRet, strError, coin_control)) { + if (!pwallet->CreateTransaction(locked_chain, vecSend, tx, reservekey, 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); @@ -341,25 +350,27 @@ static UniValue sendtoaddress(const JSONRPCRequest& request) if (request.fHelp || request.params.size() < 2 || request.params.size() > 8) throw std::runtime_error( - "sendtoaddress \"address\" amount ( \"comment\" \"comment_to\" subtractfeefromamount replaceable conf_target \"estimate_mode\")\n" - "\nSend an amount to a given address.\n" - + HelpRequiringPassphrase(pwallet) + - "\nArguments:\n" - "1. \"address\" (string, required) The bitcoin address to send to.\n" - "2. \"amount\" (numeric or string, required) The amount in " + CURRENCY_UNIT + " to send. eg 0.1\n" - "3. \"comment\" (string, optional) A comment used to store what the transaction is for. \n" - " This is not part of the transaction, just kept in your wallet.\n" - "4. \"comment_to\" (string, optional) A comment to store the name of the person or organization \n" + RPCHelpMan{"sendtoaddress", + "\nSend an amount to a given address." + + HelpRequiringPassphrase(pwallet) + "\n", + { + {"address", RPCArg::Type::STR, /* opt */ false, /* default_val */ "", "The bitcoin address to send to."}, + {"amount", RPCArg::Type::AMOUNT, /* opt */ false, /* default_val */ "", "The amount in " + CURRENCY_UNIT + " to send. eg 0.1"}, + {"comment", RPCArg::Type::STR, /* opt */ true, /* default_val */ "null", "A comment used to store what the transaction is for.\n" + " This is not part of the transaction, just kept in your wallet."}, + {"comment_to", RPCArg::Type::STR, /* opt */ true, /* default_val */ "null", "A comment to store the name of the person or organization\n" " to which you're sending the transaction. This is not part of the \n" - " transaction, just kept in your wallet.\n" - "5. subtractfeefromamount (boolean, optional, default=false) The fee will be deducted from the amount being sent.\n" - " The recipient will receive less bitcoins than you enter in the amount field.\n" - "6. replaceable (boolean, optional) Allow this transaction to be replaced by a transaction with higher fees via BIP 125\n" - "7. conf_target (numeric, optional) Confirmation target (in blocks)\n" - "8. \"estimate_mode\" (string, optional, default=UNSET) The fee estimate mode, must be one of:\n" + " transaction, just kept in your wallet."}, + {"subtractfeefromamount", RPCArg::Type::BOOL, /* opt */ true, /* default_val */ "false", "The fee will be deducted from the amount being sent.\n" + " The recipient will receive less bitcoins than you enter in the amount field."}, + {"replaceable", RPCArg::Type::BOOL, /* opt */ true, /* default_val */ "fallback to wallet's default", "Allow this transaction to be replaced by a transaction with higher fees via BIP 125"}, + {"conf_target", RPCArg::Type::NUM, /* opt */ true, /* default_val */ "fallback to wallet's default", "Confirmation target (in blocks)"}, + {"estimate_mode", RPCArg::Type::STR, /* opt */ true, /* default_val */ "UNSET", "The fee estimate mode, must be one of:\n" " \"UNSET\"\n" " \"ECONOMICAL\"\n" - " \"CONSERVATIVE\"\n" + " \"CONSERVATIVE\""}, + }} + .ToString() + "\nResult:\n" "\"txid\" (string) The transaction id.\n" "\nExamples:\n" @@ -373,7 +384,8 @@ static UniValue sendtoaddress(const JSONRPCRequest& request) // the user could have gotten from another RPC command prior to now pwallet->BlockUntilSyncedToCurrentChain(); - LOCK2(cs_main, pwallet->cs_wallet); + auto locked_chain = pwallet->chain().lock(); + LOCK(pwallet->cs_wallet); CTxDestination dest = DecodeDestination(request.params[0].get_str()); if (!IsValidDestination(dest)) { @@ -415,7 +427,7 @@ static UniValue sendtoaddress(const JSONRPCRequest& request) EnsureWalletIsUnlocked(pwallet); - CTransactionRef tx = SendMoney(pwallet, dest, nAmount, fSubtractFeeFromAmount, coin_control, std::move(mapValue)); + CTransactionRef tx = SendMoney(*locked_chain, pwallet, dest, nAmount, fSubtractFeeFromAmount, coin_control, std::move(mapValue)); return tx->GetHash().GetHex(); } @@ -430,10 +442,12 @@ static UniValue listaddressgroupings(const JSONRPCRequest& request) if (request.fHelp || request.params.size() != 0) throw std::runtime_error( - "listaddressgroupings\n" - "\nLists groups of addresses which have had their common ownership\n" - "made public by common use as inputs or as the resulting change\n" - "in past transactions\n" + 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" + "in past transactions\n", + {}} + .ToString() + "\nResult:\n" "[\n" " [\n" @@ -455,10 +469,11 @@ static UniValue listaddressgroupings(const JSONRPCRequest& request) // the user could have gotten from another RPC command prior to now pwallet->BlockUntilSyncedToCurrentChain(); - LOCK2(cs_main, pwallet->cs_wallet); + auto locked_chain = pwallet->chain().lock(); + LOCK(pwallet->cs_wallet); UniValue jsonGroupings(UniValue::VARR); - std::map<CTxDestination, CAmount> balances = pwallet->GetAddressBalances(); + std::map<CTxDestination, CAmount> balances = pwallet->GetAddressBalances(*locked_chain); for (const std::set<CTxDestination>& grouping : pwallet->GetAddressGroupings()) { UniValue jsonGrouping(UniValue::VARR); for (const CTxDestination& address : grouping) @@ -489,12 +504,14 @@ static UniValue signmessage(const JSONRPCRequest& request) if (request.fHelp || request.params.size() != 2) throw std::runtime_error( - "signmessage \"address\" \"message\"\n" - "\nSign a message with the private key of an address" - + HelpRequiringPassphrase(pwallet) + "\n" - "\nArguments:\n" - "1. \"address\" (string, required) The bitcoin address to use for the private key.\n" - "2. \"message\" (string, required) The message to create a signature of.\n" + RPCHelpMan{"signmessage", + "\nSign a message with the private key of an address" + + HelpRequiringPassphrase(pwallet) + "\n", + { + {"address", RPCArg::Type::STR, /* opt */ false, /* default_val */ "", "The bitcoin address to use for the private key."}, + {"message", RPCArg::Type::STR, /* opt */ false, /* default_val */ "", "The message to create a signature of."}, + }} + .ToString() + "\nResult:\n" "\"signature\" (string) The signature of the message encoded in base 64\n" "\nExamples:\n" @@ -504,11 +521,12 @@ static UniValue signmessage(const JSONRPCRequest& request) + HelpExampleCli("signmessage", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\" \"my message\"") + "\nVerify the signature\n" + HelpExampleCli("verifymessage", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\" \"signature\" \"my message\"") + - "\nAs json rpc\n" + "\nAs a JSON-RPC call\n" + HelpExampleRpc("signmessage", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\", \"my message\"") ); - LOCK2(cs_main, pwallet->cs_wallet); + auto locked_chain = pwallet->chain().lock(); + LOCK(pwallet->cs_wallet); EnsureWalletIsUnlocked(pwallet); @@ -552,11 +570,13 @@ static UniValue getreceivedbyaddress(const JSONRPCRequest& request) if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) throw std::runtime_error( - "getreceivedbyaddress \"address\" ( minconf )\n" - "\nReturns the total amount received by the given address in transactions with at least minconf confirmations.\n" - "\nArguments:\n" - "1. \"address\" (string, required) The bitcoin address for transactions.\n" - "2. minconf (numeric, optional, default=1) Only include transactions confirmed at least this many times.\n" + RPCHelpMan{"getreceivedbyaddress", + "\nReturns the total amount received by the given address in transactions with at least minconf confirmations.\n", + { + {"address", RPCArg::Type::STR, /* opt */ false, /* default_val */ "", "The bitcoin address for transactions."}, + {"minconf", RPCArg::Type::NUM, /* opt */ true, /* default_val */ "1", "Only include transactions confirmed at least this many times."}, + }} + .ToString() + "\nResult:\n" "amount (numeric) The total amount in " + CURRENCY_UNIT + " received at this address.\n" "\nExamples:\n" @@ -566,7 +586,7 @@ static UniValue getreceivedbyaddress(const JSONRPCRequest& request) + HelpExampleCli("getreceivedbyaddress", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\" 0") + "\nThe amount with at least 6 confirmations\n" + HelpExampleCli("getreceivedbyaddress", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\" 6") + - "\nAs a json rpc call\n" + "\nAs a JSON-RPC call\n" + HelpExampleRpc("getreceivedbyaddress", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\", 6") ); @@ -574,7 +594,9 @@ static UniValue getreceivedbyaddress(const JSONRPCRequest& request) // the user could have gotten from another RPC command prior to now pwallet->BlockUntilSyncedToCurrentChain(); - LOCK2(cs_main, pwallet->cs_wallet); + LockAnnotation lock(::cs_main); // Temporary, for CheckFinalTx below. Removed in upcoming commit. + auto locked_chain = pwallet->chain().lock(); + LOCK(pwallet->cs_wallet); // Bitcoin address CTxDestination dest = DecodeDestination(request.params[0].get_str()); @@ -600,7 +622,7 @@ static UniValue getreceivedbyaddress(const JSONRPCRequest& request) for (const CTxOut& txout : wtx.tx->vout) if (txout.scriptPubKey == scriptPubKey) - if (wtx.GetDepthInMainChain() >= nMinDepth) + if (wtx.GetDepthInMainChain(*locked_chain) >= nMinDepth) nAmount += txout.nValue; } @@ -619,11 +641,13 @@ static UniValue getreceivedbylabel(const JSONRPCRequest& request) if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) throw std::runtime_error( - "getreceivedbylabel \"label\" ( minconf )\n" - "\nReturns the total amount received by addresses with <label> in transactions with at least [minconf] confirmations.\n" - "\nArguments:\n" - "1. \"label\" (string, required) The selected label, may be the default label using \"\".\n" - "2. minconf (numeric, optional, default=1) Only include transactions confirmed at least this many times.\n" + RPCHelpMan{"getreceivedbylabel", + "\nReturns the total amount received by addresses with <label> in transactions with at least [minconf] confirmations.\n", + { + {"label", RPCArg::Type::STR, /* opt */ false, /* default_val */ "", "The selected label, may be the default label using \"\"."}, + {"minconf", RPCArg::Type::NUM, /* opt */ true, /* default_val */ "1", "Only include transactions confirmed at least this many times."}, + }} + .ToString() + "\nResult:\n" "amount (numeric) The total amount in " + CURRENCY_UNIT + " received for this label.\n" "\nExamples:\n" @@ -633,7 +657,7 @@ static UniValue getreceivedbylabel(const JSONRPCRequest& request) + HelpExampleCli("getreceivedbylabel", "\"tabby\" 0") + "\nThe amount with at least 6 confirmations\n" + HelpExampleCli("getreceivedbylabel", "\"tabby\" 6") + - "\nAs a json rpc call\n" + "\nAs a JSON-RPC call\n" + HelpExampleRpc("getreceivedbylabel", "\"tabby\", 6") ); @@ -641,7 +665,9 @@ static UniValue getreceivedbylabel(const JSONRPCRequest& request) // the user could have gotten from another RPC command prior to now pwallet->BlockUntilSyncedToCurrentChain(); - LOCK2(cs_main, pwallet->cs_wallet); + LockAnnotation lock(::cs_main); // Temporary, for CheckFinalTx below. Removed in upcoming commit. + auto locked_chain = pwallet->chain().lock(); + LOCK(pwallet->cs_wallet); // Minimum confirmations int nMinDepth = 1; @@ -663,7 +689,7 @@ static UniValue getreceivedbylabel(const JSONRPCRequest& request) { CTxDestination address; if (ExtractDestination(txout.scriptPubKey, address) && IsMine(*pwallet, address) && setAddress.count(address)) { - if (wtx.GetDepthInMainChain() >= nMinDepth) + if (wtx.GetDepthInMainChain(*locked_chain) >= nMinDepth) nAmount += txout.nValue; } } @@ -684,14 +710,16 @@ static UniValue getbalance(const JSONRPCRequest& request) if (request.fHelp || (request.params.size() > 3 )) throw std::runtime_error( - "getbalance ( \"(dummy)\" minconf include_watchonly )\n" - "\nReturns the total available balance.\n" - "The available balance is what the wallet considers currently spendable, and is\n" - "thus affected by options which limit spendability such as -spendzeroconfchange.\n" - "\nArguments:\n" - "1. (dummy) (string, optional) Remains for backward compatibility. Must be excluded or set to \"*\".\n" - "2. minconf (numeric, optional, default=0) Only include transactions confirmed at least this many times.\n" - "3. include_watchonly (bool, optional, default=false) Also include balance in watch-only addresses (see 'importaddress')\n" + RPCHelpMan{"getbalance", + "\nReturns the total available balance.\n" + "The available balance is what the wallet considers currently spendable, and is\n" + "thus affected by options which limit spendability such as -spendzeroconfchange.\n", + { + {"dummy", RPCArg::Type::STR, /* opt */ true, /* default_val */ "null", "Remains for backward compatibility. Must be excluded or set to \"*\"."}, + {"minconf", RPCArg::Type::NUM, /* opt */ true, /* default_val */ "0", "Only include transactions confirmed at least this many times."}, + {"include_watchonly", RPCArg::Type::BOOL, /* opt */ true, /* default_val */ "false", "Also include balance in watch-only addresses (see 'importaddress')"}, + }} + .ToString() + "\nResult:\n" "amount (numeric) The total amount in " + CURRENCY_UNIT + " received for this wallet.\n" "\nExamples:\n" @@ -699,7 +727,7 @@ static UniValue getbalance(const JSONRPCRequest& request) + HelpExampleCli("getbalance", "") + "\nThe total amount in the wallet at least 6 blocks confirmed\n" + HelpExampleCli("getbalance", "\"*\" 6") + - "\nAs a json rpc call\n" + "\nAs a JSON-RPC call\n" + HelpExampleRpc("getbalance", "\"*\", 6") ); @@ -707,7 +735,8 @@ static UniValue getbalance(const JSONRPCRequest& request) // the user could have gotten from another RPC command prior to now pwallet->BlockUntilSyncedToCurrentChain(); - LOCK2(cs_main, pwallet->cs_wallet); + auto locked_chain = pwallet->chain().lock(); + LOCK(pwallet->cs_wallet); const UniValue& dummy_value = request.params[0]; if (!dummy_value.isNull() && dummy_value.get_str() != "*") { @@ -738,14 +767,16 @@ static UniValue getunconfirmedbalance(const JSONRPCRequest &request) if (request.fHelp || request.params.size() > 0) throw std::runtime_error( - "getunconfirmedbalance\n" - "Returns the server's total unconfirmed balance\n"); + RPCHelpMan{"getunconfirmedbalance", + "Returns the server's total unconfirmed balance\n", {}} + .ToString()); // Make sure the results are valid at least up to the most recent block // the user could have gotten from another RPC command prior to now pwallet->BlockUntilSyncedToCurrentChain(); - LOCK2(cs_main, pwallet->cs_wallet); + auto locked_chain = pwallet->chain().lock(); + LOCK(pwallet->cs_wallet); return ValueFromAmount(pwallet->GetUnconfirmedBalance()); } @@ -762,32 +793,34 @@ static UniValue sendmany(const JSONRPCRequest& request) if (request.fHelp || request.params.size() < 2 || request.params.size() > 8) throw std::runtime_error( - "sendmany \"\" {\"address\":amount,...} ( minconf \"comment\" [\"address\",...] replaceable conf_target \"estimate_mode\")\n" - "\nSend multiple times. Amounts are double-precision floating point numbers.\n" - + HelpRequiringPassphrase(pwallet) + "\n" - "\nArguments:\n" - "1. \"dummy\" (string, required) Must be set to \"\" for backwards compatibility.\n" - "2. \"amounts\" (string, required) A json object with addresses and amounts\n" - " {\n" - " \"address\":amount (numeric or string) The bitcoin address is the key, the numeric amount (can be string) in " + CURRENCY_UNIT + " is the value\n" - " ,...\n" - " }\n" - "3. minconf (numeric, optional, default=1) Only use the balance confirmed at least this many times.\n" - "4. \"comment\" (string, optional) A comment\n" - "5. subtractfeefrom (array, optional) A json array with addresses.\n" + RPCHelpMan{"sendmany", + "\nSend multiple times. Amounts are double-precision floating point numbers." + + HelpRequiringPassphrase(pwallet) + "\n", + { + {"dummy", RPCArg::Type::STR, /* opt */ false, /* default_val */ "", "Must be set to \"\" for backwards compatibility.", "\"\""}, + {"amounts", RPCArg::Type::OBJ, /* opt */ false, /* default_val */ "", "A json object with addresses and amounts", + { + {"address", RPCArg::Type::AMOUNT, /* opt */ false, /* default_val */ "", "The bitcoin address is the key, the numeric amount (can be string) in " + CURRENCY_UNIT + " is the value"}, + }, + }, + {"minconf", RPCArg::Type::NUM, /* opt */ true, /* default_val */ "1", "Only use the balance confirmed at least this many times."}, + {"comment", RPCArg::Type::STR, /* opt */ true, /* default_val */ "null", "A comment"}, + {"subtractfeefrom", RPCArg::Type::ARR, /* opt */ true, /* default_val */ "null", "A json array with addresses.\n" " The fee will be equally deducted from the amount of each selected address.\n" " Those recipients will receive less bitcoins than you enter in their corresponding amount field.\n" - " If no addresses are specified here, the sender pays the fee.\n" - " [\n" - " \"address\" (string) Subtract fee from this address\n" - " ,...\n" - " ]\n" - "6. replaceable (boolean, optional) Allow this transaction to be replaced by a transaction with higher fees via BIP 125\n" - "7. conf_target (numeric, optional) Confirmation target (in blocks)\n" - "8. \"estimate_mode\" (string, optional, default=UNSET) The fee estimate mode, must be one of:\n" + " If no addresses are specified here, the sender pays the fee.", + { + {"address", RPCArg::Type::STR, /* opt */ true, /* default_val */ "", "Subtract fee from this address"}, + }, + }, + {"replaceable", RPCArg::Type::BOOL, /* opt */ true, /* default_val */ "fallback to wallet's default", "Allow this transaction to be replaced by a transaction with higher fees via BIP 125"}, + {"conf_target", RPCArg::Type::NUM, /* opt */ true, /* default_val */ "fallback to wallet's default", "Confirmation target (in blocks)"}, + {"estimate_mode", RPCArg::Type::STR, /* opt */ true, /* default_val */ "UNSET", "The fee estimate mode, must be one of:\n" " \"UNSET\"\n" " \"ECONOMICAL\"\n" - " \"CONSERVATIVE\"\n" + " \"CONSERVATIVE\""}, + }} + .ToString() + "\nResult:\n" "\"txid\" (string) The transaction id for the send. Only 1 transaction is created regardless of \n" " the number of addresses.\n" @@ -798,7 +831,7 @@ static UniValue sendmany(const JSONRPCRequest& request) + HelpExampleCli("sendmany", "\"\" \"{\\\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\\\":0.01,\\\"1353tsE8YMTA4EuV7dgUXGjNFf9KpVvKHz\\\":0.02}\" 6 \"testing\"") + "\nSend two amounts to two different addresses, subtract fee from amount:\n" + HelpExampleCli("sendmany", "\"\" \"{\\\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\\\":0.01,\\\"1353tsE8YMTA4EuV7dgUXGjNFf9KpVvKHz\\\":0.02}\" 1 \"\" \"[\\\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\\\",\\\"1353tsE8YMTA4EuV7dgUXGjNFf9KpVvKHz\\\"]\"") + - "\nAs a json rpc call\n" + "\nAs a JSON-RPC call\n" + HelpExampleRpc("sendmany", "\"\", {\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\":0.01,\"1353tsE8YMTA4EuV7dgUXGjNFf9KpVvKHz\":0.02}, 6, \"testing\"") ); @@ -806,7 +839,8 @@ static UniValue sendmany(const JSONRPCRequest& request) // the user could have gotten from another RPC command prior to now pwallet->BlockUntilSyncedToCurrentChain(); - LOCK2(cs_main, pwallet->cs_wallet); + auto locked_chain = pwallet->chain().lock(); + LOCK(pwallet->cs_wallet); if (pwallet->GetBroadcastTransactions() && !g_connman) { throw JSONRPCError(RPC_CLIENT_P2P_DISABLED, "Error: Peer-to-peer functionality missing or disabled"); @@ -892,7 +926,7 @@ static UniValue sendmany(const JSONRPCRequest& request) int nChangePosRet = -1; std::string strFailReason; CTransactionRef tx; - bool fCreated = pwallet->CreateTransaction(vecSend, tx, keyChange, nFeeRequired, nChangePosRet, strFailReason, coin_control); + bool fCreated = pwallet->CreateTransaction(*locked_chain, vecSend, tx, keyChange, nFeeRequired, nChangePosRet, strFailReason, coin_control); if (!fCreated) throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, strFailReason); CValidationState state; @@ -914,23 +948,24 @@ static UniValue addmultisigaddress(const JSONRPCRequest& request) } if (request.fHelp || request.params.size() < 2 || request.params.size() > 4) { - std::string msg = "addmultisigaddress nrequired [\"key\",...] ( \"label\" \"address_type\" )\n" - "\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" - "This functionality is only intended for use with non-watchonly addresses.\n" - "See `importaddress` for watchonly p2sh address support.\n" - "If 'label' is specified, assign address to that label.\n" - - "\nArguments:\n" - "1. nrequired (numeric, required) The number of required signatures out of the n keys or addresses.\n" - "2. \"keys\" (string, required) A json array of bitcoin addresses or hex-encoded public keys\n" - " [\n" - " \"address\" (string) bitcoin address or hex-encoded public key\n" - " ...,\n" - " ]\n" - "3. \"label\" (string, optional) A label to assign the addresses to.\n" - "4. \"address_type\" (string, optional) The address type to use. Options are \"legacy\", \"p2sh-segwit\", and \"bech32\". Default is set by -addresstype.\n" - + 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" + "This functionality is only intended for use with non-watchonly addresses.\n" + "See `importaddress` for watchonly p2sh address support.\n" + "If 'label' is specified, assign address to that label.\n", + { + {"nrequired", RPCArg::Type::NUM, /* opt */ false, /* default_val */ "", "The number of required signatures out of the n keys or addresses."}, + {"keys", RPCArg::Type::ARR, /* opt */ false, /* default_val */ "", "A json array of bitcoin addresses or hex-encoded public keys", + { + {"key", RPCArg::Type::STR, /* opt */ false, /* default_val */ "", "bitcoin address or hex-encoded public key"}, + }, + }, + {"label", RPCArg::Type::STR, /* opt */ true, /* default_val */ "null", "A label to assign the addresses to."}, + {"address_type", RPCArg::Type::STR, /* opt */ true, /* default_val */ "set by -addresstype", "The address type to use. Options are \"legacy\", \"p2sh-segwit\", and \"bech32\"."}, + }} + .ToString() + "\nResult:\n" "{\n" " \"address\":\"multisigaddress\", (string) The value of the new multisig address.\n" @@ -939,13 +974,14 @@ static UniValue addmultisigaddress(const JSONRPCRequest& request) "\nExamples:\n" "\nAdd a multisig address from 2 addresses\n" + HelpExampleCli("addmultisigaddress", "2 \"[\\\"16sSauSf5pF2UkUwvKGq4qjNRzBZYqgEL5\\\",\\\"171sgjn4YtPu27adkKGrdDwzRTxnRkBfKV\\\"]\"") + - "\nAs json rpc call\n" + "\nAs a JSON-RPC call\n" + HelpExampleRpc("addmultisigaddress", "2, \"[\\\"16sSauSf5pF2UkUwvKGq4qjNRzBZYqgEL5\\\",\\\"171sgjn4YtPu27adkKGrdDwzRTxnRkBfKV\\\"]\"") ; throw std::runtime_error(msg); } - LOCK2(cs_main, pwallet->cs_wallet); + auto locked_chain = pwallet->chain().lock(); + LOCK(pwallet->cs_wallet); std::string label; if (!request.params[2].isNull()) @@ -982,147 +1018,21 @@ static UniValue addmultisigaddress(const JSONRPCRequest& request) return result; } -class Witnessifier : public boost::static_visitor<bool> -{ -public: - CWallet * const pwallet; - CTxDestination result; - bool already_witness; - - explicit Witnessifier(CWallet *_pwallet) : pwallet(_pwallet), already_witness(false) {} - - bool operator()(const CKeyID &keyID) { - if (pwallet) { - CScript basescript = GetScriptForDestination(keyID); - CScript witscript = GetScriptForWitness(basescript); - if (!IsSolvable(*pwallet, witscript)) { - return false; - } - return ExtractDestination(witscript, result); - } - return false; - } - - bool operator()(const CScriptID &scriptID) { - CScript subscript; - if (pwallet && pwallet->GetCScript(scriptID, subscript)) { - int witnessversion; - std::vector<unsigned char> witprog; - if (subscript.IsWitnessProgram(witnessversion, witprog)) { - ExtractDestination(subscript, result); - already_witness = true; - return true; - } - CScript witscript = GetScriptForWitness(subscript); - if (!IsSolvable(*pwallet, witscript)) { - return false; - } - return ExtractDestination(witscript, result); - } - return false; - } - - bool operator()(const WitnessV0KeyHash& id) - { - already_witness = true; - result = id; - return true; - } - - bool operator()(const WitnessV0ScriptHash& id) - { - already_witness = true; - result = id; - return true; - } - - template<typename T> - bool operator()(const T& dest) { return false; } -}; - -static UniValue addwitnessaddress(const JSONRPCRequest& request) -{ - std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); - CWallet* const pwallet = wallet.get(); - - if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { - return NullUniValue; - } - - if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) - { - std::string msg = "addwitnessaddress \"address\" ( p2sh )\n" - "\nDEPRECATED: set the address_type argument of getnewaddress, or option -addresstype=[bech32|p2sh-segwit] instead.\n" - "Add a witness address for a script (with pubkey or redeemscript known). Requires a new wallet backup.\n" - "It returns the witness script.\n" - - "\nArguments:\n" - "1. \"address\" (string, required) An address known to the wallet\n" - "2. p2sh (bool, optional, default=true) Embed inside P2SH\n" - - "\nResult:\n" - "\"witnessaddress\", (string) The value of the new address (P2SH or BIP173).\n" - "}\n" - ; - throw std::runtime_error(msg); - } - - if (!IsDeprecatedRPCEnabled("addwitnessaddress")) { - throw JSONRPCError(RPC_METHOD_DEPRECATED, "addwitnessaddress is deprecated and will be fully removed in v0.17. " - "To use addwitnessaddress in v0.16, restart bitcoind with -deprecatedrpc=addwitnessaddress.\n" - "Projects should transition to using the address_type argument of getnewaddress, or option -addresstype=[bech32|p2sh-segwit] instead.\n"); - } - - CTxDestination dest = DecodeDestination(request.params[0].get_str()); - if (!IsValidDestination(dest)) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Bitcoin address"); - } - - bool p2sh = true; - if (!request.params[1].isNull()) { - p2sh = request.params[1].get_bool(); - } - - Witnessifier w(pwallet); - bool ret = boost::apply_visitor(w, dest); - if (!ret) { - throw JSONRPCError(RPC_WALLET_ERROR, "Public key or redeemscript not known to wallet, or the key is uncompressed"); - } - - CScript witprogram = GetScriptForDestination(w.result); - - if (p2sh) { - w.result = CScriptID(witprogram); - } - - if (w.already_witness) { - if (!(dest == w.result)) { - throw JSONRPCError(RPC_WALLET_ERROR, "Cannot convert between witness address types"); - } - } else { - pwallet->AddCScript(witprogram); // Implicit for single-key now, but necessary for multisig and for compatibility with older software - pwallet->SetAddressBook(w.result, "", "receive"); - } - - return EncodeDestination(w.result); -} - struct tallyitem { - CAmount nAmount; - int nConf; + CAmount nAmount{0}; + int nConf{std::numeric_limits<int>::max()}; std::vector<uint256> txids; - bool fIsWatchonly; + bool fIsWatchonly{false}; tallyitem() { - nAmount = 0; - nConf = std::numeric_limits<int>::max(); - fIsWatchonly = false; } }; -static UniValue ListReceived(CWallet * const pwallet, const UniValue& params, bool by_label) EXCLUSIVE_LOCKS_REQUIRED(cs_main) +static UniValue ListReceived(interfaces::Chain::Lock& locked_chain, CWallet * const pwallet, const UniValue& params, bool by_label) EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet) { + LockAnnotation lock(::cs_main); // Temporary, for CheckFinalTx below. Removed in upcoming commit. + // Minimum confirmations int nMinDepth = 1; if (!params[0].isNull()) @@ -1156,7 +1066,7 @@ static UniValue ListReceived(CWallet * const pwallet, const UniValue& params, bo if (wtx.IsCoinBase() || !CheckFinalTx(*wtx.tx)) continue; - int nDepth = wtx.GetDepthInMainChain(); + int nDepth = wtx.GetDepthInMainChain(locked_chain); if (nDepth < nMinDepth) continue; @@ -1276,13 +1186,15 @@ static UniValue listreceivedbyaddress(const JSONRPCRequest& request) if (request.fHelp || request.params.size() > 4) throw std::runtime_error( - "listreceivedbyaddress ( minconf include_empty include_watchonly address_filter )\n" - "\nList balances by receiving address.\n" - "\nArguments:\n" - "1. minconf (numeric, optional, default=1) The minimum number of confirmations before payments are included.\n" - "2. include_empty (bool, optional, default=false) Whether to include addresses that haven't received any payments.\n" - "3. include_watchonly (bool, optional, default=false) Whether to include watch-only addresses (see 'importaddress').\n" - "4. address_filter (string, optional) If present, only return information on this address.\n" + RPCHelpMan{"listreceivedbyaddress", + "\nList balances by receiving address.\n", + { + {"minconf", RPCArg::Type::NUM, /* opt */ true, /* default_val */ "1", "The minimum number of confirmations before payments are included."}, + {"include_empty", RPCArg::Type::BOOL, /* opt */ true, /* default_val */ "false", "Whether to include addresses that haven't received any payments."}, + {"include_watchonly", RPCArg::Type::BOOL, /* opt */ true, /* default_val */ "false", "Whether to include watch-only addresses (see 'importaddress')."}, + {"address_filter", RPCArg::Type::STR, /* opt */ true, /* default_val */ "null", "If present, only return information on this address."}, + }} + .ToString() + "\nResult:\n" "[\n" " {\n" @@ -1310,9 +1222,10 @@ static UniValue listreceivedbyaddress(const JSONRPCRequest& request) // the user could have gotten from another RPC command prior to now pwallet->BlockUntilSyncedToCurrentChain(); - LOCK2(cs_main, pwallet->cs_wallet); + auto locked_chain = pwallet->chain().lock(); + LOCK(pwallet->cs_wallet); - return ListReceived(pwallet, request.params, false); + return ListReceived(*locked_chain, pwallet, request.params, false); } static UniValue listreceivedbylabel(const JSONRPCRequest& request) @@ -1326,13 +1239,14 @@ static UniValue listreceivedbylabel(const JSONRPCRequest& request) if (request.fHelp || request.params.size() > 3) throw std::runtime_error( - "listreceivedbylabel ( minconf include_empty include_watchonly)\n" - "\nList received transactions by label.\n" - "\nArguments:\n" - "1. minconf (numeric, optional, default=1) The minimum number of confirmations before payments are included.\n" - "2. include_empty (bool, optional, default=false) Whether to include labels that haven't received any payments.\n" - "3. include_watchonly (bool, optional, default=false) Whether to include watch-only addresses (see 'importaddress').\n" - + RPCHelpMan{"listreceivedbylabel", + "\nList received transactions by label.\n", + { + {"minconf", RPCArg::Type::NUM, /* opt */ true, /* default_val */ "1", "The minimum number of confirmations before payments are included."}, + {"include_empty", RPCArg::Type::BOOL, /* opt */ true, /* default_val */ "false", "Whether to include labels that haven't received any payments."}, + {"include_watchonly", RPCArg::Type::BOOL, /* opt */ true, /* default_val */ "false", "Whether to include watch-only addresses (see 'importaddress')."}, + }} + .ToString() + "\nResult:\n" "[\n" " {\n" @@ -1354,9 +1268,10 @@ static UniValue listreceivedbylabel(const JSONRPCRequest& request) // the user could have gotten from another RPC command prior to now pwallet->BlockUntilSyncedToCurrentChain(); - LOCK2(cs_main, pwallet->cs_wallet); + auto locked_chain = pwallet->chain().lock(); + LOCK(pwallet->cs_wallet); - return ListReceived(pwallet, request.params, true); + return ListReceived(*locked_chain, pwallet, request.params, true); } static void MaybePushAddress(UniValue & entry, const CTxDestination &dest) @@ -1369,25 +1284,26 @@ static void MaybePushAddress(UniValue & entry, const CTxDestination &dest) /** * List transactions based on the given criteria. * - * @param pwallet The wallet. - * @param wtx The wallet transaction. - * @param nMinDepth The minimum confirmation depth. - * @param fLong Whether to include the JSON version of the transaction. - * @param ret The UniValue into which the result is stored. - * @param filter The "is mine" filter bool. + * @param pwallet The wallet. + * @param wtx The wallet transaction. + * @param nMinDepth The minimum confirmation depth. + * @param fLong Whether to include the JSON version of the transaction. + * @param ret The UniValue into which the result is stored. + * @param filter_ismine The "is mine" filter flags. + * @param filter_label Optional label string to filter incoming transactions. */ -static void ListTransactions(CWallet* const pwallet, const CWalletTx& wtx, int nMinDepth, bool fLong, UniValue& ret, const isminefilter& filter) EXCLUSIVE_LOCKS_REQUIRED(cs_main) +static void ListTransactions(interfaces::Chain::Lock& locked_chain, CWallet* const pwallet, const CWalletTx& wtx, int nMinDepth, bool fLong, UniValue& ret, const isminefilter& filter_ismine, const std::string* filter_label) { CAmount nFee; std::list<COutputEntry> listReceived; std::list<COutputEntry> listSent; - wtx.GetAmounts(listReceived, listSent, nFee, filter); + wtx.GetAmounts(listReceived, listSent, nFee, filter_ismine); bool involvesWatchonly = wtx.IsFromMe(ISMINE_WATCH_ONLY); // Sent - if ((!listSent.empty() || nFee != 0)) + if (!filter_label) { for (const COutputEntry& s : listSent) { @@ -1404,14 +1320,14 @@ static void ListTransactions(CWallet* const pwallet, const CWalletTx& wtx, int n entry.pushKV("vout", s.vout); entry.pushKV("fee", ValueFromAmount(-nFee)); if (fLong) - WalletTxToJSON(wtx, entry); + WalletTxToJSON(pwallet->chain(), locked_chain, wtx, entry); entry.pushKV("abandoned", wtx.isAbandoned()); ret.push_back(entry); } } // Received - if (listReceived.size() > 0 && wtx.GetDepthInMainChain() >= nMinDepth) + if (listReceived.size() > 0 && wtx.GetDepthInMainChain(locked_chain) >= nMinDepth) { for (const COutputEntry& r : listReceived) { @@ -1419,6 +1335,9 @@ static void ListTransactions(CWallet* const pwallet, const CWalletTx& wtx, int n if (pwallet->mapAddressBook.count(r.destination)) { label = pwallet->mapAddressBook[r.destination].name; } + if (filter_label && label != *filter_label) { + continue; + } UniValue entry(UniValue::VOBJ); if (involvesWatchonly || (::IsMine(*pwallet, r.destination) & ISMINE_WATCH_ONLY)) { entry.pushKV("involvesWatchonly", true); @@ -1426,9 +1345,9 @@ static void ListTransactions(CWallet* const pwallet, const CWalletTx& wtx, int n MaybePushAddress(entry, r.destination); if (wtx.IsCoinBase()) { - if (wtx.GetDepthInMainChain() < 1) + if (wtx.GetDepthInMainChain(locked_chain) < 1) entry.pushKV("category", "orphan"); - else if (wtx.IsImmatureCoinBase()) + else if (wtx.IsImmatureCoinBase(locked_chain)) entry.pushKV("category", "immature"); else entry.pushKV("category", "generate"); @@ -1443,7 +1362,7 @@ static void ListTransactions(CWallet* const pwallet, const CWalletTx& wtx, int n } entry.pushKV("vout", r.vout); if (fLong) - WalletTxToJSON(wtx, entry); + WalletTxToJSON(pwallet->chain(), locked_chain, wtx, entry); ret.push_back(entry); } } @@ -1460,20 +1379,29 @@ UniValue listtransactions(const JSONRPCRequest& request) if (request.fHelp || request.params.size() > 4) throw std::runtime_error( - "listtransactions (dummy count skip include_watchonly)\n" - "\nReturns up to 'count' most recent transactions skipping the first 'from' transactions.\n" - "\nArguments:\n" - "1. \"dummy\" (string, optional) If set, should be \"*\" for backwards compatibility.\n" - "2. count (numeric, optional, default=10) The number of transactions to return\n" - "3. skip (numeric, optional, default=0) The number of transactions to skip\n" - "4. include_watchonly (bool, optional, default=false) Include transactions to watch-only addresses (see 'importaddress')\n" + 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", + { + {"label", RPCArg::Type::STR, /* opt */ true, /* default_val */ "null", "If set, should be a valid label name to return only incoming transactions\n" + " with the specified label, or \"*\" to disable filtering and return all transactions."}, + {"count", RPCArg::Type::NUM, /* opt */ true, /* default_val */ "10", "The number of transactions to return"}, + {"skip", RPCArg::Type::NUM, /* opt */ true, /* default_val */ "0", "The number of transactions to skip"}, + {"include_watchonly", RPCArg::Type::BOOL, /* opt */ true, /* default_val */ "false", "Include transactions to watch-only addresses (see 'importaddress')"}, + }} + .ToString() + "\nResult:\n" "[\n" " {\n" " \"address\":\"address\", (string) The bitcoin address of the transaction.\n" - " \"category\":\"send|receive\", (string) The transaction category.\n" + " \"category\": (string) The transaction category.\n" + " \"send\" Transactions sent.\n" + " \"receive\" Non-coinbase transactions received.\n" + " \"generate\" Coinbase transactions received with more than 100 confirmations.\n" + " \"immature\" Coinbase transactions received with 100 or fewer confirmations.\n" + " \"orphan\" Orphaned coinbase transactions received.\n" " \"amount\": x.xxx, (numeric) The amount in " + CURRENCY_UNIT + ". This is negative for the 'send' category, and is positive\n" - " for the 'receive' category,\n" + " for all other categories\n" " \"label\": \"label\", (string) A comment for the address/transaction, if any\n" " \"vout\": n, (numeric) the vout value\n" " \"fee\": x.xxx, (numeric) The amount of the fee in " + CURRENCY_UNIT + ". This is negative and only available for the \n" @@ -1500,7 +1428,7 @@ UniValue listtransactions(const JSONRPCRequest& request) + HelpExampleCli("listtransactions", "") + "\nList transactions 100 to 120\n" + HelpExampleCli("listtransactions", "\"*\" 20 100") + - "\nAs a json rpc call\n" + "\nAs a JSON-RPC call\n" + HelpExampleRpc("listtransactions", "\"*\", 20, 100") ); @@ -1508,8 +1436,12 @@ UniValue listtransactions(const JSONRPCRequest& request) // the user could have gotten from another RPC command prior to now pwallet->BlockUntilSyncedToCurrentChain(); + const std::string* filter_label = nullptr; if (!request.params[0].isNull() && request.params[0].get_str() != "*") { - throw JSONRPCError(RPC_INVALID_PARAMETER, "Dummy value must be set to \"*\""); + filter_label = &request.params[0].get_str(); + if (filter_label->empty()) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Label argument must be a valid label name or \"*\"."); + } } int nCount = 10; if (!request.params[1].isNull()) @@ -1530,7 +1462,8 @@ UniValue listtransactions(const JSONRPCRequest& request) UniValue ret(UniValue::VARR); { - LOCK2(cs_main, pwallet->cs_wallet); + auto locked_chain = pwallet->chain().lock(); + LOCK(pwallet->cs_wallet); const CWallet::TxItems & txOrdered = pwallet->wtxOrdered; @@ -1538,7 +1471,7 @@ UniValue listtransactions(const JSONRPCRequest& request) for (CWallet::TxItems::const_reverse_iterator it = txOrdered.rbegin(); it != txOrdered.rend(); ++it) { CWalletTx *const pwtx = (*it).second; - ListTransactions(pwallet, *pwtx, 0, true, ret, filter); + ListTransactions(*locked_chain, pwallet, *pwtx, 0, true, ret, filter, filter_label); if ((int)ret.size() >= (nCount+nFrom)) break; } } @@ -1580,33 +1513,40 @@ static UniValue listsinceblock(const JSONRPCRequest& request) if (request.fHelp || request.params.size() > 4) throw std::runtime_error( - "listsinceblock ( \"blockhash\" target_confirmations include_watchonly include_removed )\n" - "\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" - "Additionally, if include_removed is set, transactions affecting the wallet which were removed are returned in the \"removed\" array.\n" - "\nArguments:\n" - "1. \"blockhash\" (string, optional) The block hash to list transactions since\n" - "2. target_confirmations: (numeric, optional, default=1) Return the nth block hash from the main chain. e.g. 1 would mean the best block hash. Note: this is not used as a filter, but only affects [lastblock] in the return value\n" - "3. include_watchonly: (bool, optional, default=false) Include transactions to watch-only addresses (see 'importaddress')\n" - "4. include_removed: (bool, optional, default=true) Show transactions that were removed due to a reorg in the \"removed\" array\n" - " (not guaranteed to work on pruned nodes)\n" + 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" + "Additionally, if include_removed is set, transactions affecting the wallet which were removed are returned in the \"removed\" array.\n", + { + {"blockhash", RPCArg::Type::STR, /* opt */ true, /* default_val */ "null", "If set, the block hash to list transactions since, otherwise list all transactions."}, + {"target_confirmations", RPCArg::Type::NUM, /* opt */ true, /* default_val */ "1", "Return the nth block hash from the main chain. e.g. 1 would mean the best block hash. Note: this is not used as a filter, but only affects [lastblock] in the return value"}, + {"include_watchonly", RPCArg::Type::BOOL, /* opt */ true, /* default_val */ "false", "Include transactions to watch-only addresses (see 'importaddress')"}, + {"include_removed", RPCArg::Type::BOOL, /* opt */ true, /* default_val */ "true", "Show transactions that were removed due to a reorg in the \"removed\" array\n" + " (not guaranteed to work on pruned nodes)"}, + }} + .ToString() + "\nResult:\n" "{\n" " \"transactions\": [\n" - " \"address\":\"address\", (string) The bitcoin address of the transaction. Not present for move transactions (category = move).\n" - " \"category\":\"send|receive\", (string) The transaction category. 'send' has negative amounts, 'receive' has positive amounts.\n" - " \"amount\": x.xxx, (numeric) The amount in " + CURRENCY_UNIT + ". This is negative for the 'send' category, and for the 'move' category for moves \n" - " outbound. It is positive for the 'receive' category, and for the 'move' category for inbound funds.\n" + " \"address\":\"address\", (string) The bitcoin address of the transaction.\n" + " \"category\": (string) The transaction category.\n" + " \"send\" Transactions sent.\n" + " \"receive\" Non-coinbase transactions received.\n" + " \"generate\" Coinbase transactions received with more than 100 confirmations.\n" + " \"immature\" Coinbase transactions received with 100 or fewer confirmations.\n" + " \"orphan\" Orphaned coinbase transactions received.\n" + " \"amount\": x.xxx, (numeric) The amount in " + CURRENCY_UNIT + ". This is negative for the 'send' category, and is positive\n" + " for all other categories\n" " \"vout\" : n, (numeric) the vout value\n" " \"fee\": x.xxx, (numeric) The amount of the fee in " + CURRENCY_UNIT + ". This is negative and only available for the 'send' category of transactions.\n" - " \"confirmations\": n, (numeric) The number of confirmations for the transaction. Available for 'send' and 'receive' category of transactions.\n" + " \"confirmations\": n, (numeric) The number of confirmations for the transaction.\n" " When it's < 0, it means the transaction conflicted that many blocks ago.\n" - " \"blockhash\": \"hashvalue\", (string) The block hash containing the transaction. Available for 'send' and 'receive' category of transactions.\n" - " \"blockindex\": n, (numeric) The index of the transaction in the block that includes it. Available for 'send' and 'receive' category of transactions.\n" + " \"blockhash\": \"hashvalue\", (string) The block hash containing the transaction.\n" + " \"blockindex\": n, (numeric) The index of the transaction in the block that includes it.\n" " \"blocktime\": xxx, (numeric) The block time in seconds since epoch (1 Jan 1970 GMT).\n" - " \"txid\": \"transactionid\", (string) The transaction id. Available for 'send' and 'receive' category of transactions.\n" + " \"txid\": \"transactionid\", (string) The transaction id.\n" " \"time\": xxx, (numeric) The transaction time in seconds since epoch (Jan 1 1970 GMT).\n" - " \"timereceived\": xxx, (numeric) The time received in seconds since epoch (Jan 1 1970 GMT). Available for 'send' and 'receive' category of transactions.\n" + " \"timereceived\": xxx, (numeric) The time received in seconds since epoch (Jan 1 1970 GMT).\n" " \"bip125-replaceable\": \"yes|no|unknown\", (string) Whether this transaction could be replaced due to BIP125 (replace-by-fee);\n" " may be unknown for unconfirmed transactions not in the mempool\n" " \"abandoned\": xxx, (bool) 'true' if the transaction has been abandoned (inputs are respendable). Only available for the 'send' category of transactions.\n" @@ -1630,7 +1570,8 @@ static UniValue listsinceblock(const JSONRPCRequest& request) // the user could have gotten from another RPC command prior to now pwallet->BlockUntilSyncedToCurrentChain(); - LOCK2(cs_main, pwallet->cs_wallet); + auto locked_chain = pwallet->chain().lock(); + LOCK(pwallet->cs_wallet); const CBlockIndex* pindex = nullptr; // Block index of the specified block or the common ancestor, if the block provided was in a deactivated chain. const CBlockIndex* paltindex = nullptr; // Block index of the specified block, even if it's in a deactivated chain. @@ -1673,8 +1614,8 @@ static UniValue listsinceblock(const JSONRPCRequest& request) for (const std::pair<const uint256, CWalletTx>& pairWtx : pwallet->mapWallet) { CWalletTx tx = pairWtx.second; - if (depth == -1 || tx.GetDepthInMainChain() < depth) { - ListTransactions(pwallet, tx, 0, true, transactions, filter); + if (depth == -1 || tx.GetDepthInMainChain(*locked_chain) < depth) { + ListTransactions(*locked_chain, pwallet, tx, 0, true, transactions, filter, nullptr /* filter_label */); } } @@ -1691,7 +1632,7 @@ static UniValue listsinceblock(const JSONRPCRequest& request) if (it != pwallet->mapWallet.end()) { // We want all transactions regardless of confirmation count to appear here, // even negative confirmation ones, hence the big negative. - ListTransactions(pwallet, it->second, -100000000, true, removed, filter); + ListTransactions(*locked_chain, pwallet, it->second, -100000000, true, removed, filter, nullptr /* filter_label */); } } paltindex = paltindex->pprev; @@ -1719,11 +1660,13 @@ static UniValue gettransaction(const JSONRPCRequest& request) if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) throw std::runtime_error( - "gettransaction \"txid\" ( include_watchonly )\n" - "\nGet detailed information about in-wallet transaction <txid>\n" - "\nArguments:\n" - "1. \"txid\" (string, required) The transaction id\n" - "2. \"include_watchonly\" (bool, optional, default=false) Whether to include watch-only addresses in balance calculation and details[]\n" + RPCHelpMan{"gettransaction", + "\nGet detailed information about in-wallet transaction <txid>\n", + { + {"txid", RPCArg::Type::STR, /* opt */ false, /* default_val */ "", "The transaction id"}, + {"include_watchonly", RPCArg::Type::BOOL, /* opt */ true, /* default_val */ "false", "Whether to include watch-only addresses in balance calculation and details[]"}, + }} + .ToString() + "\nResult:\n" "{\n" " \"amount\" : x.xxx, (numeric) The transaction amount in " + CURRENCY_UNIT + "\n" @@ -1741,7 +1684,12 @@ static UniValue gettransaction(const JSONRPCRequest& request) " \"details\" : [\n" " {\n" " \"address\" : \"address\", (string) The bitcoin address involved in the transaction\n" - " \"category\" : \"send|receive\", (string) The category, either 'send' or 'receive'\n" + " \"category\" : (string) The transaction category.\n" + " \"send\" Transactions sent.\n" + " \"receive\" Non-coinbase transactions received.\n" + " \"generate\" Coinbase transactions received with more than 100 confirmations.\n" + " \"immature\" Coinbase transactions received with 100 or fewer confirmations.\n" + " \"orphan\" Orphaned coinbase transactions received.\n" " \"amount\" : x.xxx, (numeric) The amount in " + CURRENCY_UNIT + "\n" " \"label\" : \"label\", (string) A comment for the address/transaction, if any\n" " \"vout\" : n, (numeric) the vout value\n" @@ -1765,7 +1713,8 @@ static UniValue gettransaction(const JSONRPCRequest& request) // the user could have gotten from another RPC command prior to now pwallet->BlockUntilSyncedToCurrentChain(); - LOCK2(cs_main, pwallet->cs_wallet); + auto locked_chain = pwallet->chain().lock(); + LOCK(pwallet->cs_wallet); uint256 hash(ParseHashV(request.params[0], "txid")); @@ -1781,7 +1730,7 @@ static UniValue gettransaction(const JSONRPCRequest& request) } const CWalletTx& wtx = it->second; - CAmount nCredit = wtx.GetCredit(filter); + CAmount nCredit = wtx.GetCredit(*locked_chain, filter); CAmount nDebit = wtx.GetDebit(filter); CAmount nNet = nCredit - nDebit; CAmount nFee = (wtx.IsFromMe(filter) ? wtx.tx->GetValueOut() - nDebit : 0); @@ -1790,10 +1739,10 @@ static UniValue gettransaction(const JSONRPCRequest& request) if (wtx.IsFromMe(filter)) entry.pushKV("fee", ValueFromAmount(nFee)); - WalletTxToJSON(wtx, entry); + WalletTxToJSON(pwallet->chain(), *locked_chain, wtx, entry); UniValue details(UniValue::VARR); - ListTransactions(pwallet, wtx, 0, false, details, filter); + ListTransactions(*locked_chain, pwallet, wtx, 0, false, details, filter, nullptr /* filter_label */); entry.pushKV("details", details); std::string strHex = EncodeHexTx(*wtx.tx, RPCSerializationFlags()); @@ -1813,14 +1762,16 @@ static UniValue abandontransaction(const JSONRPCRequest& request) if (request.fHelp || request.params.size() != 1) { throw std::runtime_error( - "abandontransaction \"txid\"\n" - "\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" - "for their inputs to be respent. It can be used to replace \"stuck\" or evicted transactions.\n" - "It only works on transactions which are not included in a block and are not currently in the mempool.\n" - "It has no effect on transactions which are already abandoned.\n" - "\nArguments:\n" - "1. \"txid\" (string, required) The transaction id\n" + 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" + "for their inputs to be respent. It can be used to replace \"stuck\" or evicted transactions.\n" + "It only works on transactions which are not included in a block and are not currently in the mempool.\n" + "It has no effect on transactions which are already abandoned.\n", + { + {"txid", RPCArg::Type::STR_HEX, /* opt */ false, /* default_val */ "", "The transaction id"}, + }} + .ToString() + "\nResult:\n" "\nExamples:\n" + HelpExampleCli("abandontransaction", "\"1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d\"") @@ -1832,14 +1783,15 @@ static UniValue abandontransaction(const JSONRPCRequest& request) // the user could have gotten from another RPC command prior to now pwallet->BlockUntilSyncedToCurrentChain(); - LOCK2(cs_main, pwallet->cs_wallet); + auto locked_chain = pwallet->chain().lock(); + LOCK(pwallet->cs_wallet); uint256 hash(ParseHashV(request.params[0], "txid")); if (!pwallet->mapWallet.count(hash)) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid or non-wallet transaction id"); } - if (!pwallet->AbandonTransaction(hash)) { + if (!pwallet->AbandonTransaction(*locked_chain, hash)) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Transaction not eligible for abandonment"); } @@ -1858,10 +1810,12 @@ static UniValue backupwallet(const JSONRPCRequest& request) if (request.fHelp || request.params.size() != 1) throw std::runtime_error( - "backupwallet \"destination\"\n" - "\nSafely copies current wallet file to destination, which can be a directory or a path with filename.\n" - "\nArguments:\n" - "1. \"destination\" (string) The destination directory or file\n" + RPCHelpMan{"backupwallet", + "\nSafely copies current wallet file to destination, which can be a directory or a path with filename.\n", + { + {"destination", RPCArg::Type::STR, /* opt */ false, /* default_val */ "", "The destination directory or file"}, + }} + .ToString() + "\nExamples:\n" + HelpExampleCli("backupwallet", "\"backup.dat\"") + HelpExampleRpc("backupwallet", "\"backup.dat\"") @@ -1871,7 +1825,8 @@ static UniValue backupwallet(const JSONRPCRequest& request) // the user could have gotten from another RPC command prior to now pwallet->BlockUntilSyncedToCurrentChain(); - LOCK2(cs_main, pwallet->cs_wallet); + auto locked_chain = pwallet->chain().lock(); + LOCK(pwallet->cs_wallet); std::string strDest = request.params[0].get_str(); if (!pwallet->BackupWallet(strDest)) { @@ -1893,11 +1848,13 @@ static UniValue keypoolrefill(const JSONRPCRequest& request) if (request.fHelp || request.params.size() > 1) throw std::runtime_error( - "keypoolrefill ( newsize )\n" - "\nFills the keypool." - + HelpRequiringPassphrase(pwallet) + "\n" - "\nArguments\n" - "1. newsize (numeric, optional, default=100) The new keypool size\n" + RPCHelpMan{"keypoolrefill", + "\nFills the keypool."+ + HelpRequiringPassphrase(pwallet) + "\n", + { + {"newsize", RPCArg::Type::NUM, /* opt */ true, /* default_val */ "100", "The new keypool size"}, + }} + .ToString() + "\nExamples:\n" + HelpExampleCli("keypoolrefill", "") + HelpExampleRpc("keypoolrefill", "") @@ -1907,7 +1864,8 @@ static UniValue keypoolrefill(const JSONRPCRequest& request) throw JSONRPCError(RPC_WALLET_ERROR, "Error: Private keys are disabled for this wallet"); } - LOCK2(cs_main, pwallet->cs_wallet); + auto locked_chain = pwallet->chain().lock(); + LOCK(pwallet->cs_wallet); // 0 is interpreted by TopUpKeyPool() as the default keypool size given by -keypool unsigned int kpSize = 0; @@ -1928,13 +1886,6 @@ static UniValue keypoolrefill(const JSONRPCRequest& request) } -static void LockWallet(CWallet* pWallet) -{ - LOCK(pWallet->cs_wallet); - pWallet->nRelockTime = 0; - pWallet->Lock(); -} - static UniValue walletpassphrase(const JSONRPCRequest& request) { std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); @@ -1946,12 +1897,14 @@ static UniValue walletpassphrase(const JSONRPCRequest& request) if (request.fHelp || request.params.size() != 2) { throw std::runtime_error( - "walletpassphrase \"passphrase\" timeout\n" - "\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" - "\nArguments:\n" - "1. \"passphrase\" (string, required) The wallet passphrase\n" - "2. timeout (numeric, required) The time to keep the decryption key in seconds; capped at 100000000 (~3 years).\n" + 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", + { + {"passphrase", RPCArg::Type::STR, /* opt */ false, /* default_val */ "", "The wallet passphrase"}, + {"timeout", RPCArg::Type::NUM, /* opt */ false, /* default_val */ "", "The time to keep the decryption key in seconds; capped at 100000000 (~3 years)."}, + }} + .ToString() + "\nNote:\n" "Issuing the walletpassphrase command while the wallet is already unlocked will set a new unlock\n" "time that overrides the old one.\n" @@ -1960,12 +1913,13 @@ static UniValue walletpassphrase(const JSONRPCRequest& request) + HelpExampleCli("walletpassphrase", "\"my pass phrase\" 60") + "\nLock the wallet again (before 60 seconds)\n" + HelpExampleCli("walletlock", "") + - "\nAs json rpc call\n" + "\nAs a JSON-RPC call\n" + HelpExampleRpc("walletpassphrase", "\"my pass phrase\", 60") ); } - LOCK2(cs_main, pwallet->cs_wallet); + auto locked_chain = pwallet->chain().lock(); + LOCK(pwallet->cs_wallet); if (!pwallet->IsCrypted()) { throw JSONRPCError(RPC_WALLET_WRONG_ENC_STATE, "Error: running with an unencrypted wallet, but walletpassphrase was called."); @@ -1990,21 +1944,29 @@ static UniValue walletpassphrase(const JSONRPCRequest& request) nSleepTime = MAX_SLEEP_TIME; } - if (strWalletPass.length() > 0) - { - if (!pwallet->Unlock(strWalletPass)) { - throw JSONRPCError(RPC_WALLET_PASSPHRASE_INCORRECT, "Error: The wallet passphrase entered was incorrect."); - } + if (strWalletPass.empty()) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "passphrase can not be empty"); + } + + if (!pwallet->Unlock(strWalletPass)) { + throw JSONRPCError(RPC_WALLET_PASSPHRASE_INCORRECT, "Error: The wallet passphrase entered was incorrect."); } - else - throw std::runtime_error( - "walletpassphrase <passphrase> <timeout>\n" - "Stores the wallet decryption key in memory for <timeout> seconds."); pwallet->TopUpKeyPool(); pwallet->nRelockTime = GetTime() + nSleepTime; - RPCRunLater(strprintf("lockwallet(%s)", pwallet->GetName()), std::bind(LockWallet, pwallet), nSleepTime); + + // Keep a weak pointer to the wallet so that it is possible to unload the + // wallet before the following callback is called. If a valid shared pointer + // is acquired in the callback then the wallet is still loaded. + std::weak_ptr<CWallet> weak_wallet = wallet; + RPCRunLater(strprintf("lockwallet(%s)", pwallet->GetName()), [weak_wallet] { + if (auto shared_wallet = weak_wallet.lock()) { + LOCK(shared_wallet->cs_wallet); + shared_wallet->Lock(); + shared_wallet->nRelockTime = 0; + } + }, nSleepTime); return NullUniValue; } @@ -2021,18 +1983,21 @@ static UniValue walletpassphrasechange(const JSONRPCRequest& request) if (request.fHelp || request.params.size() != 2) { throw std::runtime_error( - "walletpassphrasechange \"oldpassphrase\" \"newpassphrase\"\n" - "\nChanges the wallet passphrase from 'oldpassphrase' to 'newpassphrase'.\n" - "\nArguments:\n" - "1. \"oldpassphrase\" (string) The current passphrase\n" - "2. \"newpassphrase\" (string) The new passphrase\n" + RPCHelpMan{"walletpassphrasechange", + "\nChanges the wallet passphrase from 'oldpassphrase' to 'newpassphrase'.\n", + { + {"oldpassphrase", RPCArg::Type::STR, /* opt */ false, /* default_val */ "", "The current passphrase"}, + {"newpassphrase", RPCArg::Type::STR, /* opt */ false, /* default_val */ "", "The new passphrase"}, + }} + .ToString() + "\nExamples:\n" + HelpExampleCli("walletpassphrasechange", "\"old one\" \"new one\"") + HelpExampleRpc("walletpassphrasechange", "\"old one\", \"new one\"") ); } - LOCK2(cs_main, pwallet->cs_wallet); + auto locked_chain = pwallet->chain().lock(); + LOCK(pwallet->cs_wallet); if (!pwallet->IsCrypted()) { throw JSONRPCError(RPC_WALLET_WRONG_ENC_STATE, "Error: running with an unencrypted wallet, but walletpassphrasechange was called."); @@ -2048,10 +2013,9 @@ static UniValue walletpassphrasechange(const JSONRPCRequest& request) strNewWalletPass.reserve(100); strNewWalletPass = request.params[1].get_str().c_str(); - if (strOldWalletPass.length() < 1 || strNewWalletPass.length() < 1) - throw std::runtime_error( - "walletpassphrasechange <oldpassphrase> <newpassphrase>\n" - "Changes the wallet passphrase from <oldpassphrase> to <newpassphrase>."); + if (strOldWalletPass.empty() || strNewWalletPass.empty()) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "passphrase can not be empty"); + } if (!pwallet->ChangeWalletPassphrase(strOldWalletPass, strNewWalletPass)) { throw JSONRPCError(RPC_WALLET_PASSPHRASE_INCORRECT, "Error: The wallet passphrase entered was incorrect."); @@ -2072,10 +2036,12 @@ static UniValue walletlock(const JSONRPCRequest& request) if (request.fHelp || request.params.size() != 0) { throw std::runtime_error( - "walletlock\n" - "\nRemoves the wallet encryption key from memory, locking the wallet.\n" - "After calling this method, you will need to call walletpassphrase again\n" - "before being able to call any methods which require the wallet to be unlocked.\n" + 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" + "before being able to call any methods which require the wallet to be unlocked.\n", + {}} + .ToString() + "\nExamples:\n" "\nSet the passphrase for 2 minutes to perform a transaction\n" + HelpExampleCli("walletpassphrase", "\"my pass phrase\" 120") + @@ -2083,12 +2049,13 @@ static UniValue walletlock(const JSONRPCRequest& request) + HelpExampleCli("sendtoaddress", "\"1M72Sfpbz1BPpXFHz9m3CdqATR44Jvaydd\" 1.0") + "\nClear the passphrase since we are done before 2 minutes is up\n" + HelpExampleCli("walletlock", "") + - "\nAs json rpc call\n" + "\nAs a JSON-RPC call\n" + HelpExampleRpc("walletlock", "") ); } - LOCK2(cs_main, pwallet->cs_wallet); + auto locked_chain = pwallet->chain().lock(); + LOCK(pwallet->cs_wallet); if (!pwallet->IsCrypted()) { throw JSONRPCError(RPC_WALLET_WRONG_ENC_STATE, "Error: running with an unencrypted wallet, but walletlock was called."); @@ -2112,14 +2079,16 @@ static UniValue encryptwallet(const JSONRPCRequest& request) if (request.fHelp || request.params.size() != 1) { throw std::runtime_error( - "encryptwallet \"passphrase\"\n" - "\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" - "will require the passphrase to be set prior the making these calls.\n" - "Use the walletpassphrase call for this, and then walletlock call.\n" - "If the wallet is already encrypted, use the walletpassphrasechange call.\n" - "\nArguments:\n" - "1. \"passphrase\" (string) The pass phrase to encrypt the wallet with. It must be at least 1 character, but should be long.\n" + 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" + "will require the passphrase to be set prior the making these calls.\n" + "Use the walletpassphrase call for this, and then walletlock call.\n" + "If the wallet is already encrypted, use the walletpassphrasechange call.\n", + { + {"passphrase", RPCArg::Type::STR, /* opt */ false, /* default_val */ "", "The pass phrase to encrypt the wallet with. It must be at least 1 character, but should be long."}, + }} + .ToString() + "\nExamples:\n" "\nEncrypt your wallet\n" + HelpExampleCli("encryptwallet", "\"my pass phrase\"") + @@ -2129,12 +2098,13 @@ static UniValue encryptwallet(const JSONRPCRequest& request) + HelpExampleCli("signmessage", "\"address\" \"test message\"") + "\nNow lock the wallet again by removing the passphrase\n" + HelpExampleCli("walletlock", "") + - "\nAs a json rpc call\n" + "\nAs a JSON-RPC call\n" + HelpExampleRpc("encryptwallet", "\"my pass phrase\"") ); } - LOCK2(cs_main, pwallet->cs_wallet); + auto locked_chain = pwallet->chain().lock(); + LOCK(pwallet->cs_wallet); if (pwallet->IsCrypted()) { throw JSONRPCError(RPC_WALLET_WRONG_ENC_STATE, "Error: running with an encrypted wallet, but encryptwallet was called."); @@ -2146,10 +2116,9 @@ static UniValue encryptwallet(const JSONRPCRequest& request) strWalletPass.reserve(100); strWalletPass = request.params[0].get_str().c_str(); - if (strWalletPass.length() < 1) - throw std::runtime_error( - "encryptwallet <passphrase>\n" - "Encrypts the wallet with <passphrase>."); + if (strWalletPass.empty()) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "passphrase can not be empty"); + } if (!pwallet->EncryptWallet(strWalletPass)) { throw JSONRPCError(RPC_WALLET_ENCRYPTION_FAILED, "Error: Failed to encrypt the wallet."); @@ -2169,25 +2138,28 @@ static UniValue lockunspent(const JSONRPCRequest& request) if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) throw std::runtime_error( - "lockunspent unlock ([{\"txid\":\"txid\",\"vout\":n},...])\n" - "\nUpdates list of temporarily unspendable outputs.\n" - "Temporarily lock (unlock=false) or unlock (unlock=true) specified transaction outputs.\n" - "If no transaction outputs are specified when unlocking then all current locked transaction outputs are unlocked.\n" - "A locked transaction output will not be chosen by automatic coin selection, when spending bitcoins.\n" - "Locks are stored in memory only. Nodes start with zero locked outputs, and the locked output list\n" - "is always cleared (by virtue of process exit) when a node stops or fails.\n" - "Also see the listunspent call\n" - "\nArguments:\n" - "1. unlock (boolean, required) Whether to unlock (true) or lock (false) the specified transactions\n" - "2. \"transactions\" (string, optional) A json array of objects. Each object the txid (string) vout (numeric)\n" - " [ (json array of json objects)\n" - " {\n" - " \"txid\":\"id\", (string) The transaction id\n" - " \"vout\": n (numeric) The output number\n" - " }\n" - " ,...\n" - " ]\n" - + RPCHelpMan{"lockunspent", + "\nUpdates list of temporarily unspendable outputs.\n" + "Temporarily lock (unlock=false) or unlock (unlock=true) specified transaction outputs.\n" + "If no transaction outputs are specified when unlocking then all current locked transaction outputs are unlocked.\n" + "A locked transaction output will not be chosen by automatic coin selection, when spending bitcoins.\n" + "Locks are stored in memory only. Nodes start with zero locked outputs, and the locked output list\n" + "is always cleared (by virtue of process exit) when a node stops or fails.\n" + "Also see the listunspent call\n", + { + {"unlock", RPCArg::Type::BOOL, /* opt */ false, /* default_val */ "", "Whether to unlock (true) or lock (false) the specified transactions"}, + {"transactions", RPCArg::Type::ARR, /* opt */ true, /* default_val */ "empty array", "A json array of objects. Each object the txid (string) vout (numeric).", + { + {"", RPCArg::Type::OBJ, /* opt */ true, /* default_val */ "", "", + { + {"txid", RPCArg::Type::STR_HEX, /* opt */ false, /* default_val */ "", "The transaction id"}, + {"vout", RPCArg::Type::NUM, /* opt */ false, /* default_val */ "", "The output number"}, + }, + }, + }, + }, + }} + .ToString() + "\nResult:\n" "true|false (boolean) Whether the command was successful or not\n" @@ -2200,7 +2172,7 @@ static UniValue lockunspent(const JSONRPCRequest& request) + HelpExampleCli("listlockunspent", "") + "\nUnlock the transaction again\n" + HelpExampleCli("lockunspent", "true \"[{\\\"txid\\\":\\\"a08e6907dbbd3d809776dbfc5d82e371b764ed838b5655e72f463568df1aadf0\\\",\\\"vout\\\":1}]\"") + - "\nAs a json rpc call\n" + "\nAs a JSON-RPC call\n" + HelpExampleRpc("lockunspent", "false, \"[{\\\"txid\\\":\\\"a08e6907dbbd3d809776dbfc5d82e371b764ed838b5655e72f463568df1aadf0\\\",\\\"vout\\\":1}]\"") ); @@ -2208,7 +2180,8 @@ static UniValue lockunspent(const JSONRPCRequest& request) // the user could have gotten from another RPC command prior to now pwallet->BlockUntilSyncedToCurrentChain(); - LOCK2(cs_main, pwallet->cs_wallet); + auto locked_chain = pwallet->chain().lock(); + LOCK(pwallet->cs_wallet); RPCTypeCheckArgument(request.params[0], UniValue::VBOOL); @@ -2257,7 +2230,7 @@ static UniValue lockunspent(const JSONRPCRequest& request) throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, vout index out of bounds"); } - if (pwallet->IsSpent(outpt.hash, outpt.n)) { + if (pwallet->IsSpent(*locked_chain, outpt.hash, outpt.n)) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, expected unspent output"); } @@ -2294,9 +2267,11 @@ static UniValue listlockunspent(const JSONRPCRequest& request) if (request.fHelp || request.params.size() > 0) throw std::runtime_error( - "listlockunspent\n" - "\nReturns list of temporarily unspendable outputs.\n" - "See the lockunspent call to lock and unlock transactions for spending.\n" + RPCHelpMan{"listlockunspent", + "\nReturns list of temporarily unspendable outputs.\n" + "See the lockunspent call to lock and unlock transactions for spending.\n", + {}} + .ToString() + "\nResult:\n" "[\n" " {\n" @@ -2314,11 +2289,12 @@ static UniValue listlockunspent(const JSONRPCRequest& request) + HelpExampleCli("listlockunspent", "") + "\nUnlock the transaction again\n" + HelpExampleCli("lockunspent", "true \"[{\\\"txid\\\":\\\"a08e6907dbbd3d809776dbfc5d82e371b764ed838b5655e72f463568df1aadf0\\\",\\\"vout\\\":1}]\"") + - "\nAs a json rpc call\n" + "\nAs a JSON-RPC call\n" + HelpExampleRpc("listlockunspent", "") ); - LOCK2(cs_main, pwallet->cs_wallet); + auto locked_chain = pwallet->chain().lock(); + LOCK(pwallet->cs_wallet); std::vector<COutPoint> vOutpts; pwallet->ListLockedCoins(vOutpts); @@ -2347,10 +2323,12 @@ static UniValue settxfee(const JSONRPCRequest& request) if (request.fHelp || request.params.size() < 1 || request.params.size() > 1) { throw std::runtime_error( - "settxfee amount\n" - "\nSet the transaction fee per kB for this wallet. Overrides the global -paytxfee command line parameter.\n" - "\nArguments:\n" - "1. amount (numeric or string, required) The transaction fee in " + CURRENCY_UNIT + "/kB\n" + RPCHelpMan{"settxfee", + "\nSet the transaction fee per kB for this wallet. Overrides the global -paytxfee command line parameter.\n", + { + {"amount", RPCArg::Type::AMOUNT, /* opt */ false, /* default_val */ "", "The transaction fee in " + CURRENCY_UNIT + "/kB"}, + }} + .ToString() + "\nResult\n" "true|false (boolean) Returns true if successful\n" "\nExamples:\n" @@ -2359,7 +2337,8 @@ static UniValue settxfee(const JSONRPCRequest& request) ); } - LOCK2(cs_main, pwallet->cs_wallet); + auto locked_chain = pwallet->chain().lock(); + LOCK(pwallet->cs_wallet); CAmount nAmount = AmountFromValue(request.params[0]); CFeeRate tx_fee_rate(nAmount, 1000); @@ -2386,8 +2365,9 @@ static UniValue getwalletinfo(const JSONRPCRequest& request) if (request.fHelp || request.params.size() != 0) throw std::runtime_error( - "getwalletinfo\n" - "Returns an object containing various wallet state info.\n" + RPCHelpMan{"getwalletinfo", + "Returns an object containing various wallet state info.\n", {}} + .ToString() + "\nResult:\n" "{\n" " \"walletname\": xxxxx, (string) the wallet name\n" @@ -2414,7 +2394,8 @@ static UniValue getwalletinfo(const JSONRPCRequest& request) // the user could have gotten from another RPC command prior to now pwallet->BlockUntilSyncedToCurrentChain(); - LOCK2(cs_main, pwallet->cs_wallet); + auto locked_chain = pwallet->chain().lock(); + LOCK(pwallet->cs_wallet); UniValue obj(UniValue::VOBJ); @@ -2443,13 +2424,48 @@ static UniValue getwalletinfo(const JSONRPCRequest& request) return obj; } +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", {}} + .ToString() + + "{\n" + " \"wallets\" : [ (json array of objects)\n" + " {\n" + " \"name\" : \"name\" (string) The wallet name\n" + " }\n" + " ,...\n" + " ]\n" + "}\n" + "\nExamples:\n" + + HelpExampleCli("listwalletdir", "") + + HelpExampleRpc("listwalletdir", "") + ); + } + + UniValue wallets(UniValue::VARR); + for (const auto& path : ListWalletDir()) { + UniValue wallet(UniValue::VOBJ); + wallet.pushKV("name", path.string()); + wallets.push_back(wallet); + } + + UniValue result(UniValue::VOBJ); + result.pushKV("wallets", wallets); + return result; +} + static UniValue listwallets(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() != 0) throw std::runtime_error( - "listwallets\n" - "Returns a list of currently loaded wallets.\n" - "For full information on the wallet, use \"getwalletinfo\"\n" + RPCHelpMan{"listwallets", + "Returns a list of currently loaded wallets.\n" + "For full information on the wallet, use \"getwalletinfo\"\n", + {}} + .ToString() + "\nResult:\n" "[ (json array of strings)\n" " \"walletname\" (string) the wallet name\n" @@ -2479,12 +2495,14 @@ static UniValue loadwallet(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() != 1) throw std::runtime_error( - "loadwallet \"filename\"\n" - "\nLoads a wallet from a wallet file or directory." - "\nNote that all wallet command-line options used when starting bitcoind will be" - "\napplied to the new wallet (eg -zapwallettxes, upgradewallet, rescan, etc).\n" - "\nArguments:\n" - "1. \"filename\" (string, required) The wallet directory or .dat file.\n" + RPCHelpMan{"loadwallet", + "\nLoads a wallet from a wallet file or directory." + "\nNote that all wallet command-line options used when starting bitcoind will be" + "\napplied to the new wallet (eg -zapwallettxes, upgradewallet, rescan, etc).\n", + { + {"filename", RPCArg::Type::STR, /* opt */ false, /* default_val */ "", "The wallet directory or .dat file."}, + }} + .ToString() + "\nResult:\n" "{\n" " \"name\" : <wallet_name>, (string) The wallet name if loaded successfully.\n" @@ -2494,26 +2512,26 @@ static UniValue loadwallet(const JSONRPCRequest& request) + HelpExampleCli("loadwallet", "\"test.dat\"") + HelpExampleRpc("loadwallet", "\"test.dat\"") ); - std::string wallet_file = request.params[0].get_str(); + + WalletLocation location(request.params[0].get_str()); std::string error; - fs::path wallet_path = fs::absolute(wallet_file, GetWalletDir()); - if (fs::symlink_status(wallet_path).type() == fs::file_not_found) { - throw JSONRPCError(RPC_WALLET_NOT_FOUND, "Wallet " + wallet_file + " not found."); - } else if (fs::is_directory(wallet_path)) { + if (!location.Exists()) { + throw JSONRPCError(RPC_WALLET_NOT_FOUND, "Wallet " + location.GetName() + " not found."); + } else if (fs::is_directory(location.GetPath())) { // The given filename is a directory. Check that there's a wallet.dat file. - fs::path wallet_dat_file = wallet_path / "wallet.dat"; + fs::path wallet_dat_file = location.GetPath() / "wallet.dat"; if (fs::symlink_status(wallet_dat_file).type() == fs::file_not_found) { - throw JSONRPCError(RPC_WALLET_NOT_FOUND, "Directory " + wallet_file + " does not contain a wallet.dat file."); + throw JSONRPCError(RPC_WALLET_NOT_FOUND, "Directory " + location.GetName() + " does not contain a wallet.dat file."); } } std::string warning; - if (!CWallet::Verify(wallet_file, false, error, warning)) { + 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(wallet_file, fs::absolute(wallet_file, GetWalletDir())); + std::shared_ptr<CWallet> const wallet = CWallet::CreateWalletFromFile(*g_rpc_interfaces->chain, location); if (!wallet) { throw JSONRPCError(RPC_WALLET_ERROR, "Wallet loading failed."); } @@ -2532,11 +2550,13 @@ static UniValue createwallet(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) { throw std::runtime_error( - "createwallet \"wallet_name\" ( disable_private_keys )\n" - "\nCreates and loads a new wallet.\n" - "\nArguments:\n" - "1. \"wallet_name\" (string, required) The name for the new wallet. If this is a path, the wallet will be created at the path location.\n" - "2. disable_private_keys (boolean, optional, default: false) Disable the possibility of private keys (only watchonlys are possible in this mode).\n" + RPCHelpMan{"createwallet", + "\nCreates and loads a new wallet.\n", + { + {"wallet_name", RPCArg::Type::STR, /* opt */ false, /* default_val */ "", "The name for the new wallet. If this is a path, the wallet will be created at the path location."}, + {"disable_private_keys", RPCArg::Type::BOOL, /* opt */ true, /* default_val */ "false", "Disable the possibility of private keys (only watchonlys are possible in this mode)."}, + }} + .ToString() + "\nResult:\n" "{\n" " \"name\" : <wallet_name>, (string) The wallet name if created successfully. If the wallet was created using a full path, the wallet_name will be the full path.\n" @@ -2547,7 +2567,6 @@ static UniValue createwallet(const JSONRPCRequest& request) + HelpExampleRpc("createwallet", "\"testwallet\"") ); } - std::string wallet_name = request.params[0].get_str(); std::string error; std::string warning; @@ -2556,17 +2575,17 @@ static UniValue createwallet(const JSONRPCRequest& request) disable_privatekeys = request.params[1].get_bool(); } - fs::path wallet_path = fs::absolute(wallet_name, GetWalletDir()); - if (fs::symlink_status(wallet_path).type() != fs::file_not_found) { - throw JSONRPCError(RPC_WALLET_ERROR, "Wallet " + wallet_name + " already exists."); + 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(wallet_name, false, error, warning)) { + 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(wallet_name, fs::absolute(wallet_name, GetWalletDir()), (disable_privatekeys ? (uint64_t)WALLET_FLAG_DISABLE_PRIVATE_KEYS : 0)); + std::shared_ptr<CWallet> const wallet = CWallet::CreateWalletFromFile(*g_rpc_interfaces->chain, location, (disable_privatekeys ? (uint64_t)WALLET_FLAG_DISABLE_PRIVATE_KEYS : 0)); if (!wallet) { throw JSONRPCError(RPC_WALLET_ERROR, "Wallet creation failed."); } @@ -2585,11 +2604,13 @@ static UniValue unloadwallet(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() > 1) { throw std::runtime_error( - "unloadwallet ( \"wallet_name\" )\n" - "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." - "\nArguments:\n" - "1. \"wallet_name\" (string, optional) The name of the wallet to unload.\n" + 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.", + { + {"wallet_name", RPCArg::Type::STR, /* opt */ true, /* default_val */ "the wallet name from the RPC request", "The name of the wallet to unload."}, + }} + .ToString() + "\nExamples:\n" + HelpExampleCli("unloadwallet", "wallet_name") + HelpExampleRpc("unloadwallet", "wallet_name") @@ -2616,16 +2637,8 @@ static UniValue unloadwallet(const JSONRPCRequest& request) if (!RemoveWallet(wallet)) { throw JSONRPCError(RPC_MISC_ERROR, "Requested wallet already unloaded"); } - UnregisterValidationInterface(wallet.get()); - - // The wallet can be in use so it's not possible to explicitly unload here. - // Just notify the unload intent so that all shared pointers are released. - // The wallet will be destroyed once the last shared pointer is released. - wallet->NotifyUnload(); - // There's no point in waiting for the wallet to unload. - // At this point this method should never fail. The unloading could only - // fail due to an unexpected error which would cause a process termination. + UnloadWallet(std::move(wallet)); return NullUniValue; } @@ -2641,9 +2654,11 @@ static UniValue resendwallettransactions(const JSONRPCRequest& request) if (request.fHelp || request.params.size() != 0) throw std::runtime_error( - "resendwallettransactions\n" - "Immediately re-broadcast unconfirmed wallet transactions to all peers.\n" - "Intended only for testing; the wallet code periodically re-broadcasts\n" + RPCHelpMan{"resendwallettransactions", + "Immediately re-broadcast unconfirmed wallet transactions to all peers.\n" + "Intended only for testing; the wallet code periodically re-broadcasts\n", + {}} + .ToString() + "automatically.\n" "Returns an RPC error if -walletbroadcast is set to false.\n" "Returns array of transaction ids that were re-broadcast.\n" @@ -2652,13 +2667,14 @@ static UniValue resendwallettransactions(const JSONRPCRequest& request) if (!g_connman) throw JSONRPCError(RPC_CLIENT_P2P_DISABLED, "Error: Peer-to-peer functionality missing or disabled"); - LOCK2(cs_main, pwallet->cs_wallet); + auto locked_chain = pwallet->chain().lock(); + LOCK(pwallet->cs_wallet); if (!pwallet->GetBroadcastTransactions()) { throw JSONRPCError(RPC_WALLET_ERROR, "Error: Wallet transaction broadcasting is disabled with -walletbroadcast"); } - std::vector<uint256> txids = pwallet->ResendWalletTransactionsBefore(GetTime(), g_connman.get()); + std::vector<uint256> txids = pwallet->ResendWalletTransactionsBefore(*locked_chain, GetTime(), g_connman.get()); UniValue result(UniValue::VARR); for (const uint256& txid : txids) { @@ -2678,27 +2694,30 @@ static UniValue listunspent(const JSONRPCRequest& request) if (request.fHelp || request.params.size() > 5) throw std::runtime_error( - "listunspent ( minconf maxconf [\"addresses\",...] [include_unsafe] [query_options])\n" - "\nReturns array of unspent transaction outputs\n" - "with between minconf and maxconf (inclusive) confirmations.\n" - "Optionally filter to only include txouts paid to specified addresses.\n" - "\nArguments:\n" - "1. minconf (numeric, optional, default=1) The minimum confirmations to filter\n" - "2. maxconf (numeric, optional, default=9999999) The maximum confirmations to filter\n" - "3. \"addresses\" (string) A json array of bitcoin addresses to filter\n" - " [\n" - " \"address\" (string) bitcoin address\n" - " ,...\n" - " ]\n" - "4. include_unsafe (bool, optional, default=true) Include outputs that are not safe to spend\n" - " See description of \"safe\" attribute below.\n" - "5. query_options (json, optional) JSON with query options\n" - " {\n" - " \"minimumAmount\" (numeric or string, default=0) Minimum value of each UTXO in " + CURRENCY_UNIT + "\n" - " \"maximumAmount\" (numeric or string, default=unlimited) Maximum value of each UTXO in " + CURRENCY_UNIT + "\n" - " \"maximumCount\" (numeric or string, default=unlimited) Maximum number of UTXOs\n" - " \"minimumSumAmount\" (numeric or string, default=unlimited) Minimum sum value of all UTXOs in " + CURRENCY_UNIT + "\n" - " }\n" + RPCHelpMan{"listunspent", + "\nReturns array of unspent transaction outputs\n" + "with between minconf and maxconf (inclusive) confirmations.\n" + "Optionally filter to only include txouts paid to specified addresses.\n", + { + {"minconf", RPCArg::Type::NUM, /* opt */ true, /* default_val */ "1", "The minimum confirmations to filter"}, + {"maxconf", RPCArg::Type::NUM, /* opt */ true, /* default_val */ "9999999", "The maximum confirmations to filter"}, + {"addresses", RPCArg::Type::ARR, /* opt */ true, /* default_val */ "empty array", "A json array of bitcoin addresses to filter", + { + {"address", RPCArg::Type::STR, /* opt */ true, /* default_val */ "", "bitcoin address"}, + }, + }, + {"include_unsafe", RPCArg::Type::BOOL, /* opt */ true, /* default_val */ "true", "Include outputs that are not safe to spend\n" + " See description of \"safe\" attribute below."}, + {"query_options", RPCArg::Type::OBJ, /* opt */ true, /* default_val */ "null", "JSON with query options", + { + {"minimumAmount", RPCArg::Type::AMOUNT, /* opt */ true, /* default_val */ "0", "Minimum value of each UTXO in " + CURRENCY_UNIT + ""}, + {"maximumAmount", RPCArg::Type::AMOUNT, /* opt */ true, /* default_val */ "unlimited", "Maximum value of each UTXO in " + CURRENCY_UNIT + ""}, + {"maximumCount", RPCArg::Type::NUM, /* opt */ true, /* default_val */ "unlimited", "Maximum number of UTXOs"}, + {"minimumSumAmount", RPCArg::Type::AMOUNT, /* opt */ true, /* default_val */ "unlimited", "Minimum sum value of all UTXOs in " + CURRENCY_UNIT + ""}, + }, + "query_options"}, + }} + .ToString() + "\nResult\n" "[ (array of json object)\n" " {\n" @@ -2712,6 +2731,7 @@ static UniValue listunspent(const JSONRPCRequest& request) " \"redeemScript\" : n (string) The redeemScript if scriptPubKey is P2SH\n" " \"spendable\" : xxx, (bool) Whether we have the private keys to spend this output\n" " \"solvable\" : xxx, (bool) Whether we know how to spend this output, ignoring the lack of keys\n" + " \"desc\" : xxx, (string, only when solvable) A descriptor for spending this output\n" " \"safe\" : xxx (bool) Whether this output is considered safe to spend. Unconfirmed transactions\n" " from outside keys and unconfirmed replacement transactions are considered unsafe\n" " and are not eligible for spending by fundrawtransaction and sendtoaddress.\n" @@ -2789,8 +2809,9 @@ static UniValue listunspent(const JSONRPCRequest& request) UniValue results(UniValue::VARR); std::vector<COutput> vecOutputs; { - LOCK2(cs_main, pwallet->cs_wallet); - pwallet->AvailableCoins(vecOutputs, !include_unsafe, nullptr, nMinimumAmount, nMaximumAmount, nMinimumSumAmount, nMaximumCount, nMinDepth, nMaxDepth); + auto locked_chain = pwallet->chain().lock(); + LOCK(pwallet->cs_wallet); + pwallet->AvailableCoins(*locked_chain, vecOutputs, !include_unsafe, nullptr, nMinimumAmount, nMaximumAmount, nMinimumSumAmount, nMaximumCount, nMinDepth, nMaxDepth); } LOCK(pwallet->cs_wallet); @@ -2829,6 +2850,10 @@ static UniValue listunspent(const JSONRPCRequest& request) entry.pushKV("confirmations", out.nDepth); entry.pushKV("spendable", out.fSpendable); entry.pushKV("solvable", out.fSolvable); + if (out.fSolvable) { + auto descriptor = InferDescriptor(scriptPubKey, *pwallet); + entry.pushKV("desc", descriptor->ToString()); + } entry.pushKV("safe", out.fSafe); results.push_back(entry); } @@ -2963,45 +2988,48 @@ static UniValue fundrawtransaction(const JSONRPCRequest& request) if (request.fHelp || request.params.size() < 1 || request.params.size() > 3) throw std::runtime_error( - "fundrawtransaction \"hexstring\" ( options iswitness )\n" - "\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" - "Note that inputs which were signed may need to be resigned after completion since in/outputs have been added.\n" - "The inputs added will not be signed, use signrawtransaction for that.\n" - "Note that all existing inputs must have their previous output transaction be in the wallet.\n" - "Note that all inputs selected must be of standard form and P2SH scripts must be\n" - "in the wallet using importaddress or addmultisigaddress (to calculate fees).\n" - "You can see whether this is the case by checking the \"solvable\" field in the listunspent output.\n" - "Only pay-to-pubkey, multisig, and P2SH versions thereof are currently supported for watch-only\n" - "\nArguments:\n" - "1. \"hexstring\" (string, required) The hex string of the raw transaction\n" - "2. options (object, optional)\n" - " {\n" - " \"changeAddress\" (string, optional, default pool address) The bitcoin address to receive the change\n" - " \"changePosition\" (numeric, optional, default random) The index of the change output\n" - " \"change_type\" (string, optional) The output type to use. Only valid if changeAddress is not specified. Options are \"legacy\", \"p2sh-segwit\", and \"bech32\". Default is set by -changetype.\n" - " \"includeWatching\" (boolean, optional, default false) Also select inputs which are watch only\n" - " \"lockUnspents\" (boolean, optional, default false) Lock selected unspent outputs\n" - " \"feeRate\" (numeric, optional, default not set: makes wallet determine the fee) Set a specific fee rate in " + CURRENCY_UNIT + "/kB\n" - " \"subtractFeeFromOutputs\" (array, optional) A json array of integers.\n" + 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" + "Note that inputs which were signed may need to be resigned after completion since in/outputs have been added.\n" + "The inputs added will not be signed, use signrawtransaction for that.\n" + "Note that all existing inputs must have their previous output transaction be in the wallet.\n" + "Note that all inputs selected must be of standard form and P2SH scripts must be\n" + "in the wallet using importaddress or addmultisigaddress (to calculate fees).\n" + "You can see whether this is the case by checking the \"solvable\" field in the listunspent output.\n" + "Only pay-to-pubkey, multisig, and P2SH versions thereof are currently supported for watch-only\n", + { + {"hexstring", RPCArg::Type::STR_HEX, /* opt */ false, /* default_val */ "", "The hex string of the raw transaction"}, + {"options", RPCArg::Type::OBJ, /* opt */ true, /* default_val */ "null", "for backward compatibility: passing in a true instead of an object will result in {\"includeWatching\":true}", + { + {"changeAddress", RPCArg::Type::STR, /* opt */ true, /* default_val */ "pool address", "The bitcoin address to receive the change"}, + {"changePosition", RPCArg::Type::NUM, /* opt */ true, /* default_val */ "random", "The index of the change output"}, + {"change_type", RPCArg::Type::STR, /* opt */ true, /* default_val */ "set by -changetype", "The output type to use. Only valid if changeAddress is not specified. Options are \"legacy\", \"p2sh-segwit\", and \"bech32\"."}, + {"includeWatching", RPCArg::Type::BOOL, /* opt */ true, /* default_val */ "false", "Also select inputs which are watch only"}, + {"lockUnspents", RPCArg::Type::BOOL, /* opt */ true, /* default_val */ "false", "Lock selected unspent outputs"}, + {"feeRate", RPCArg::Type::AMOUNT, /* opt */ true, /* default_val */ "not set: makes wallet determine the fee", "Set a specific fee rate in " + CURRENCY_UNIT + "/kB"}, + {"subtractFeeFromOutputs", RPCArg::Type::ARR, /* opt */ true, /* default_val */ "empty array", "A json array of integers.\n" " The fee will be equally deducted from the amount of each specified output.\n" - " The outputs are specified by their zero-based index, before any change output is added.\n" " Those recipients will receive less bitcoins than you enter in their corresponding amount field.\n" - " If no outputs are specified here, the sender pays the fee.\n" - " [vout_index,...]\n" - " \"replaceable\" (boolean, optional) Marks this transaction as BIP125 replaceable.\n" - " Allows this transaction to be replaced by a transaction with higher fees\n" - " \"conf_target\" (numeric, optional) Confirmation target (in blocks)\n" - " \"estimate_mode\" (string, optional, default=UNSET) The fee estimate mode, must be one of:\n" + " If no outputs are specified here, the sender pays the fee.", + { + {"vout_index", RPCArg::Type::NUM, /* opt */ true, /* default_val */ "", "The zero-based output index, before a change output is added."}, + }, + }, + {"replaceable", RPCArg::Type::BOOL, /* opt */ true, /* default_val */ "fallback to wallet's default", "Marks this transaction as BIP125 replaceable.\n" + " Allows this transaction to be replaced by a transaction with higher fees"}, + {"conf_target", RPCArg::Type::NUM, /* opt */ true, /* default_val */ "fallback to wallet's default", "Confirmation target (in blocks)"}, + {"estimate_mode", RPCArg::Type::STR, /* opt */ true, /* default_val */ "UNSET", "The fee estimate mode, must be one of:\n" " \"UNSET\"\n" " \"ECONOMICAL\"\n" - " \"CONSERVATIVE\"\n" - " }\n" - " for backward compatibility: passing in a true instead of an object will result in {\"includeWatching\":true}\n" - "3. iswitness (boolean, optional) Whether the transaction hex is a serialized witness transaction \n" - " If iswitness is not present, heuristic tests will be used in decoding\n" - + " \"CONSERVATIVE\""}, + }, + "options"}, + {"iswitness", RPCArg::Type::BOOL, /* opt */ true, /* default_val */ "depends on heuristic tests", "Whether the transaction hex is a serialized witness transaction \n" + " If iswitness is not present, heuristic tests will be used in decoding"}, + }} + .ToString() + "\nResult:\n" "{\n" " \"hex\": \"value\", (string) The resulting raw transaction (hex-encoded string)\n" @@ -3034,7 +3062,7 @@ static UniValue fundrawtransaction(const JSONRPCRequest& request) FundTransaction(pwallet, tx, fee, change_position, request.params[1]); UniValue result(UniValue::VOBJ); - result.pushKV("hex", EncodeHexTx(tx)); + result.pushKV("hex", EncodeHexTx(CTransaction(tx))); result.pushKV("fee", ValueFromAmount(fee)); result.pushKV("changepos", change_position); @@ -3052,33 +3080,35 @@ UniValue signrawtransactionwithwallet(const JSONRPCRequest& request) if (request.fHelp || request.params.size() < 1 || request.params.size() > 3) throw std::runtime_error( - "signrawtransactionwithwallet \"hexstring\" ( [{\"txid\":\"id\",\"vout\":n,\"scriptPubKey\":\"hex\",\"redeemScript\":\"hex\"},...] sighashtype )\n" - "\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" - "this transaction depends on but may not yet be in the block chain.\n" - + HelpRequiringPassphrase(pwallet) + "\n" - - "\nArguments:\n" - "1. \"hexstring\" (string, required) The transaction hex string\n" - "2. \"prevtxs\" (string, optional) An json array of previous dependent transaction outputs\n" - " [ (json array of json objects, or 'null' if none provided)\n" - " {\n" - " \"txid\":\"id\", (string, required) The transaction id\n" - " \"vout\":n, (numeric, required) The output number\n" - " \"scriptPubKey\": \"hex\", (string, required) script key\n" - " \"redeemScript\": \"hex\", (string, required for P2SH or P2WSH) redeem script\n" - " \"amount\": value (numeric, required) The amount spent\n" - " }\n" - " ,...\n" - " ]\n" - "3. \"sighashtype\" (string, optional, default=ALL) The signature hash type. Must be one of\n" + 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" + "this transaction depends on but may not yet be in the block chain." + + HelpRequiringPassphrase(pwallet) + "\n", + { + {"hexstring", RPCArg::Type::STR, /* opt */ false, /* default_val */ "", "The transaction hex string"}, + {"prevtxs", RPCArg::Type::ARR, /* opt */ true, /* default_val */ "null", "A json array of previous dependent transaction outputs", + { + {"", RPCArg::Type::OBJ, /* opt */ false, /* default_val */ "", "", + { + {"txid", RPCArg::Type::STR_HEX, /* opt */ false, /* default_val */ "", "The transaction id"}, + {"vout", RPCArg::Type::NUM, /* opt */ false, /* default_val */ "", "The output number"}, + {"scriptPubKey", RPCArg::Type::STR_HEX, /* opt */ false, /* default_val */ "", "script key"}, + {"redeemScript", RPCArg::Type::STR_HEX, /* opt */ true, /* default_val */ "omitted", "(required for P2SH or P2WSH)"}, + {"amount", RPCArg::Type::AMOUNT, /* opt */ false, /* default_val */ "", "The amount spent"}, + }, + }, + }, + }, + {"sighashtype", RPCArg::Type::STR, /* opt */ true, /* default_val */ "ALL", "The signature hash type. Must be one of\n" " \"ALL\"\n" " \"NONE\"\n" " \"SINGLE\"\n" " \"ALL|ANYONECANPAY\"\n" " \"NONE|ANYONECANPAY\"\n" - " \"SINGLE|ANYONECANPAY\"\n" - + " \"SINGLE|ANYONECANPAY\""}, + }} + .ToString() + "\nResult:\n" "{\n" " \"hex\" : \"value\", (string) The hex-encoded raw transaction with signature(s)\n" @@ -3108,10 +3138,11 @@ UniValue signrawtransactionwithwallet(const JSONRPCRequest& request) } // Sign the transaction - LOCK2(cs_main, pwallet->cs_wallet); + auto locked_chain = pwallet->chain().lock(); + LOCK(pwallet->cs_wallet); EnsureWalletIsUnlocked(pwallet); - return SignTransaction(mtx, request.params[1], pwallet, false, request.params[2]); + return SignTransaction(pwallet->chain(), mtx, request.params[1], pwallet, false, request.params[2]); } static UniValue bumpfee(const JSONRPCRequest& request) @@ -3125,39 +3156,42 @@ static UniValue bumpfee(const JSONRPCRequest& request) if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) { throw std::runtime_error( - "bumpfee \"txid\" ( options ) \n" - "\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" - "The command will pay the additional fee by decreasing (or perhaps removing) its change output.\n" - "If the change output is not big enough to cover the increased fee, the command will currently fail\n" - "instead of adding new inputs to compensate. (A future implementation could improve this.)\n" - "The command will fail if the wallet or mempool contains a transaction that spends one of T's outputs.\n" - "By default, the new fee will be calculated automatically using estimatesmartfee.\n" - "The user can specify a confirmation target for estimatesmartfee.\n" - "Alternatively, the user can specify totalFee, or use RPC settxfee to set a higher fee rate.\n" - "At a minimum, the new fee rate must be high enough to pay an additional new relay fee (incrementalfee\n" - "returned by getnetworkinfo) to enter the node's mempool.\n" - "\nArguments:\n" - "1. txid (string, required) The txid to be bumped\n" - "2. options (object, optional)\n" - " {\n" - " \"confTarget\" (numeric, optional) Confirmation target (in blocks)\n" - " \"totalFee\" (numeric, optional) Total fee (NOT feerate) to pay, in satoshis.\n" + 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" + "The command will pay the additional fee by decreasing (or perhaps removing) its change output.\n" + "If the change output is not big enough to cover the increased fee, the command will currently fail\n" + "instead of adding new inputs to compensate. (A future implementation could improve this.)\n" + "The command will fail if the wallet or mempool contains a transaction that spends one of T's outputs.\n" + "By default, the new fee will be calculated automatically using estimatesmartfee.\n" + "The user can specify a confirmation target for estimatesmartfee.\n" + "Alternatively, the user can specify totalFee, or use RPC settxfee to set a higher fee rate.\n" + "At a minimum, the new fee rate must be high enough to pay an additional new relay fee (incrementalfee\n" + "returned by getnetworkinfo) to enter the node's mempool.\n", + { + {"txid", RPCArg::Type::STR_HEX, /* opt */ false, /* default_val */ "", "The txid to be bumped"}, + {"options", RPCArg::Type::OBJ, /* opt */ true, /* default_val */ "null", "", + { + {"confTarget", RPCArg::Type::NUM, /* opt */ true, /* default_val */ "fallback to wallet's default", "Confirmation target (in blocks)"}, + {"totalFee", RPCArg::Type::NUM, /* opt */ true, /* default_val */ "fallback to 'confTarget'", "Total fee (NOT feerate) to pay, in satoshis.\n" " In rare cases, the actual fee paid might be slightly higher than the specified\n" " totalFee if the tx change output has to be removed because it is too close to\n" - " the dust threshold.\n" - " \"replaceable\" (boolean, optional, default true) Whether the new transaction should still be\n" + " the dust threshold."}, + {"replaceable", RPCArg::Type::BOOL, /* opt */ true, /* default_val */ "true", "Whether the new transaction should still be\n" " marked bip-125 replaceable. If true, the sequence numbers in the transaction will\n" " be left unchanged from the original. If false, any input sequence numbers in the\n" " original transaction that were less than 0xfffffffe will be increased to 0xfffffffe\n" " so the new transaction will not be explicitly bip-125 replaceable (though it may\n" " still be replaceable in practice, for example if it has unconfirmed ancestors which\n" - " are replaceable).\n" - " \"estimate_mode\" (string, optional, default=UNSET) The fee estimate mode, must be one of:\n" + " are replaceable)."}, + {"estimate_mode", RPCArg::Type::STR, /* opt */ true, /* default_val */ "UNSET", "The fee estimate mode, must be one of:\n" " \"UNSET\"\n" " \"ECONOMICAL\"\n" - " \"CONSERVATIVE\"\n" - " }\n" + " \"CONSERVATIVE\""}, + }, + "options"}, + }} + .ToString() + "\nResult:\n" "{\n" " \"txid\": \"value\", (string) The id of the new transaction\n" @@ -3213,7 +3247,8 @@ static UniValue bumpfee(const JSONRPCRequest& request) // the user could have gotten from another RPC command prior to now pwallet->BlockUntilSyncedToCurrentChain(); - LOCK2(cs_main, pwallet->cs_wallet); + auto locked_chain = pwallet->chain().lock(); + LOCK(pwallet->cs_wallet); EnsureWalletIsUnlocked(pwallet); @@ -3276,11 +3311,13 @@ UniValue generate(const JSONRPCRequest& request) if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) { throw std::runtime_error( - "generate nblocks ( maxtries )\n" - "\nMine up to nblocks blocks immediately (before the RPC call returns) to an address in the wallet.\n" - "\nArguments:\n" - "1. nblocks (numeric, required) How many blocks are generated immediately.\n" - "2. maxtries (numeric, optional) How many iterations to try (default = 1000000).\n" + RPCHelpMan{"generate", + "\nMine up to nblocks blocks immediately (before the RPC call returns) to an address in the wallet.\n", + { + {"nblocks", RPCArg::Type::NUM, /* opt */ false, /* default_val */ "", "How many blocks are generated immediately."}, + {"maxtries", RPCArg::Type::NUM, /* opt */ true, /* default_val */ "1000000", "How many iterations to try."}, + }} + .ToString() + "\nResult:\n" "[ blockhashes ] (array) hashes of blocks generated\n" "\nExamples:\n" @@ -3289,6 +3326,12 @@ UniValue generate(const JSONRPCRequest& request) ); } + if (!IsDeprecatedRPCEnabled("generate")) { + throw JSONRPCError(RPC_METHOD_DEPRECATED, "The wallet generate rpc method is deprecated and will be fully removed in v0.19. " + "To use generate in v0.18, restart bitcoind with -deprecatedrpc=generate.\n" + "Clients should transition to using the node rpc method generatetoaddress\n"); + } + int num_generate = request.params[0].get_int(); uint64_t max_tries = 1000000; if (!request.params[1].isNull()) { @@ -3322,11 +3365,13 @@ UniValue rescanblockchain(const JSONRPCRequest& request) if (request.fHelp || request.params.size() > 2) { throw std::runtime_error( - "rescanblockchain (\"start_height\") (\"stop_height\")\n" - "\nRescan the local blockchain for wallet related transactions.\n" - "\nArguments:\n" - "1. \"start_height\" (numeric, optional) block height where the rescan should start\n" - "2. \"stop_height\" (numeric, optional) the last block height that should be scanned\n" + RPCHelpMan{"rescanblockchain", + "\nRescan the local blockchain for wallet related transactions.\n", + { + {"start_height", RPCArg::Type::NUM, /* opt */ true, /* default_val */ "0", "block height where the rescan should start"}, + {"stop_height", RPCArg::Type::NUM, /* opt */ true, /* default_val */ "tip height", "the last block height that should be scanned"}, + }} + .ToString() + "\nResult:\n" "{\n" " \"start_height\" (numeric) The block height where the rescan has started. If omitted, rescan started from the genesis block.\n" @@ -3347,7 +3392,7 @@ UniValue rescanblockchain(const JSONRPCRequest& request) CBlockIndex *pindexStop = nullptr; CBlockIndex *pChainTip = nullptr; { - LOCK(cs_main); + auto locked_chain = pwallet->chain().lock(); pindexStart = chainActive.Genesis(); pChainTip = chainActive.Tip(); @@ -3371,7 +3416,7 @@ UniValue rescanblockchain(const JSONRPCRequest& request) // We can't rescan beyond non-pruned blocks, stop and throw an error if (fPruneMode) { - LOCK(cs_main); + auto locked_chain = pwallet->chain().lock(); CBlockIndex *block = pindexStop ? pindexStop : pChainTip; while (block && block->nHeight >= pindexStart->nHeight) { if (!(block->nStatus & BLOCK_HAVE_DATA)) { @@ -3381,16 +3426,17 @@ UniValue rescanblockchain(const JSONRPCRequest& request) } } - CBlockIndex *stopBlock = pwallet->ScanForWalletTransactions(pindexStart, pindexStop, reserver, true); - if (!stopBlock) { - if (pwallet->IsAbortingRescan()) { - throw JSONRPCError(RPC_MISC_ERROR, "Rescan aborted."); - } - // if we got a nullptr returned, ScanForWalletTransactions did rescan up to the requested stopindex - stopBlock = pindexStop ? pindexStop : pChainTip; - } - else { + const CBlockIndex *failed_block, *stopBlock; + CWallet::ScanResult result = + pwallet->ScanForWalletTransactions(pindexStart, pindexStop, reserver, failed_block, stopBlock, true); + switch (result) { + case CWallet::ScanResult::SUCCESS: + break; // stopBlock set by ScanForWalletTransactions + case CWallet::ScanResult::FAILURE: throw JSONRPCError(RPC_MISC_ERROR, "Rescan failed. Potentially corrupted data files."); + case CWallet::ScanResult::USER_ABORT: + throw JSONRPCError(RPC_MISC_ERROR, "Rescan aborted."); + // no default case, so the compiler can warn about missing cases } UniValue response(UniValue::VOBJ); response.pushKV("start_height", pindexStart->nHeight); @@ -3528,18 +3574,23 @@ UniValue getaddressinfo(const JSONRPCRequest& request) if (request.fHelp || request.params.size() != 1) { throw std::runtime_error( - "getaddressinfo \"address\"\n" - "\nReturn information about the given bitcoin address. Some information requires the address\n" - "to be in the wallet.\n" - "\nArguments:\n" - "1. \"address\" (string, required) The bitcoin address to get the information of.\n" + RPCHelpMan{"getaddressinfo", + "\nReturn information about the given bitcoin address. Some information requires the address\n" + "to be in the wallet.\n", + { + {"address", RPCArg::Type::STR, /* opt */ false, /* default_val */ "", "The bitcoin address to get the information of."}, + }} + .ToString() + "\nResult:\n" "{\n" " \"address\" : \"address\", (string) The bitcoin address validated\n" - " \"scriptPubKey\" : \"hex\", (string) The hex encoded scriptPubKey generated by the address\n" + " \"scriptPubKey\" : \"hex\", (string) The hex-encoded scriptPubKey generated by the address\n" " \"ismine\" : true|false, (boolean) If the address is yours or not\n" " \"iswatchonly\" : true|false, (boolean) If the address is watchonly\n" + " \"solvable\" : true|false, (boolean) Whether we know how to spend coins sent to this address, ignoring the possible lack of private keys\n" + " \"desc\" : \"desc\", (string, optional) A descriptor for spending coins sent to this address (only when solvable)\n" " \"isscript\" : true|false, (boolean) If the key is a script\n" + " \"ischange\" : true|false, (boolean) If the address was used for change output\n" " \"iswitness\" : true|false, (boolean) If the address is a witness address\n" " \"witness_version\" : version (numeric, optional) The version number of the witness program\n" " \"witness_program\" : \"hex\" (string, optional) The hex value of the witness program\n" @@ -3553,7 +3604,7 @@ UniValue getaddressinfo(const JSONRPCRequest& request) " \"sigsrequired\" : xxxxx (numeric, optional) Number of signatures required to spend multisig output (only if \"script\" is \"multisig\")\n" " \"pubkey\" : \"publickeyhex\", (string, optional) The hex value of the raw public key, for single-key addresses (possibly embedded in P2SH or P2WSH)\n" " \"embedded\" : {...}, (object, optional) Information about the address embedded in P2SH or P2WSH, if relevant and known. It includes all getaddressinfo output fields for the embedded address, excluding metadata (\"timestamp\", \"hdkeypath\", \"hdseedid\") and relation to the wallet (\"ismine\", \"iswatchonly\").\n" - " \"iscompressed\" : true|false, (boolean) If the address is compressed\n" + " \"iscompressed\" : true|false, (boolean, optional) If the pubkey is compressed\n" " \"label\" : \"label\" (string) The label associated with the address, \"\" is the default label\n" " \"timestamp\" : timestamp, (number, optional) The creation time of the key if available in seconds since epoch (Jan 1 1970 GMT)\n" " \"hdkeypath\" : \"keypath\" (string, optional) The HD keypath if the key is HD and available\n" @@ -3591,12 +3642,18 @@ UniValue getaddressinfo(const JSONRPCRequest& request) isminetype mine = IsMine(*pwallet, dest); ret.pushKV("ismine", bool(mine & ISMINE_SPENDABLE)); + bool solvable = IsSolvable(*pwallet, scriptPubKey); + ret.pushKV("solvable", solvable); + if (solvable) { + ret.pushKV("desc", InferDescriptor(scriptPubKey, *pwallet)->ToString()); + } ret.pushKV("iswatchonly", bool(mine & ISMINE_WATCH_ONLY)); UniValue detail = DescribeWalletAddress(pwallet, dest); ret.pushKVs(detail); if (pwallet->mapAddressBook.count(dest)) { ret.pushKV("label", pwallet->mapAddressBook[dest].name); } + ret.pushKV("ischange", pwallet->IsChange(scriptPubKey)); const CKeyMetadata* meta = nullptr; CKeyID key_id = GetKeyForDestination(*pwallet, dest); if (!key_id.IsNull()) { @@ -3644,10 +3701,12 @@ static UniValue getaddressesbylabel(const JSONRPCRequest& request) if (request.fHelp || request.params.size() != 1) throw std::runtime_error( - "getaddressesbylabel \"label\"\n" - "\nReturns the list of addresses assigned the specified label.\n" - "\nArguments:\n" - "1. \"label\" (string, required) The label.\n" + RPCHelpMan{"getaddressesbylabel", + "\nReturns the list of addresses assigned the specified label.\n", + { + {"label", RPCArg::Type::STR, /* opt */ false, /* default_val */ "", "The label."}, + }} + .ToString() + "\nResult:\n" "{ (json object with addresses as keys)\n" " \"address\": { (json object with information about address)\n" @@ -3689,10 +3748,12 @@ static UniValue listlabels(const JSONRPCRequest& request) if (request.fHelp || request.params.size() > 1) throw std::runtime_error( - "listlabels ( \"purpose\" )\n" - "\nReturns the list of all labels, or labels that are assigned to addresses with a specific purpose.\n" - "\nArguments:\n" - "1. \"purpose\" (string, optional) Address purpose to list labels for ('send','receive'). An empty string is the same as not providing this argument.\n" + RPCHelpMan{"listlabels", + "\nReturns the list of all labels, or labels that are assigned to addresses with a specific purpose.\n", + { + {"purpose", RPCArg::Type::STR, /* opt */ true, /* default_val */ "null", "Address purpose to list labels for ('send','receive'). An empty string is the same as not providing this argument."}, + }} + .ToString() + "\nResult:\n" "[ (json array of string)\n" " \"label\", (string) Label name\n" @@ -3705,7 +3766,7 @@ static UniValue listlabels(const JSONRPCRequest& request) + HelpExampleCli("listlabels", "receive") + "\nList labels that have sending addresses\n" + HelpExampleCli("listlabels", "send") + - "\nAs json rpc call\n" + "\nAs a JSON-RPC call\n" + HelpExampleRpc("listlabels", "receive") ); @@ -3743,18 +3804,20 @@ UniValue sethdseed(const JSONRPCRequest& request) if (request.fHelp || request.params.size() > 2) { throw std::runtime_error( - "sethdseed ( \"newkeypool\" \"seed\" )\n" - "\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" - "\nNote that you will need to MAKE A NEW BACKUP of your wallet after setting the HD wallet seed.\n" - + HelpRequiringPassphrase(pwallet) + - "\nArguments:\n" - "1. \"newkeypool\" (boolean, optional, default=true) Whether to flush old unused addresses, including change addresses, from the keypool and regenerate it.\n" + 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" + "\nNote that you will need to MAKE A NEW BACKUP of your wallet after setting the HD wallet seed." + + HelpRequiringPassphrase(pwallet) + "\n", + { + {"newkeypool", RPCArg::Type::BOOL, /* opt */ true, /* default_val */ "true", "Whether to flush old unused addresses, including change addresses, from the keypool and regenerate it.\n" " If true, the next address from getnewaddress and change address from getrawchangeaddress will be from this new seed.\n" " If false, addresses (including change addresses if the wallet already had HD Chain Split enabled) from the existing\n" - " keypool will be used until it has been depleted.\n" - "2. \"seed\" (string, optional) The WIF private key to use as the new HD seed; if not provided a random seed will be used.\n" - " The seed value can be retrieved using the dumpwallet command. It is the private key marked hdseed=1\n" + " keypool will be used until it has been depleted."}, + {"seed", RPCArg::Type::STR, /* opt */ true, /* default_val */ "random seed", "The WIF private key to use as the new HD seed.\n" + " The seed value can be retrieved using the dumpwallet command. It is the private key marked hdseed=1"}, + }} + .ToString() + "\nExamples:\n" + HelpExampleCli("sethdseed", "") + HelpExampleCli("sethdseed", "false") @@ -3767,7 +3830,8 @@ UniValue sethdseed(const JSONRPCRequest& request) throw JSONRPCError(RPC_CLIENT_IN_INITIAL_DOWNLOAD, "Cannot set a new HD seed while still in Initial Block Download"); } - LOCK2(cs_main, pwallet->cs_wallet); + auto locked_chain = pwallet->chain().lock(); + LOCK(pwallet->cs_wallet); // Do not do anything to non-HD wallets if (!pwallet->IsHDEnabled()) { @@ -3816,24 +3880,34 @@ void AddKeypathToMap(const CWallet* pwallet, const CKeyID& keyID, std::map<CPubK hd_keypaths.emplace(vchPubKey, std::move(info)); } -bool FillPSBT(const CWallet* pwallet, PartiallySignedTransaction& psbtx, const CTransaction* txConst, int sighash_type, bool sign, bool bip32derivs) +bool FillPSBT(const CWallet* pwallet, PartiallySignedTransaction& psbtx, int sighash_type, bool sign, bool bip32derivs) { LOCK(pwallet->cs_wallet); // Get all of the previous transactions bool complete = true; - for (unsigned int i = 0; i < txConst->vin.size(); ++i) { - const CTxIn& txin = txConst->vin[i]; + for (unsigned int i = 0; i < psbtx.tx->vin.size(); ++i) { + const CTxIn& txin = psbtx.tx->vin[i]; PSBTInput& input = psbtx.inputs.at(i); - // If we don't know about this input, skip it and let someone else deal with it - const uint256& txhash = txin.prevout.hash; - const auto it = pwallet->mapWallet.find(txhash); - if (it != pwallet->mapWallet.end()) { - const CWalletTx& wtx = it->second; - CTxOut utxo = wtx.tx->vout[txin.prevout.n]; - // Update both UTXOs from the wallet. - input.non_witness_utxo = wtx.tx; - input.witness_utxo = utxo; + if (PSBTInputSigned(input)) { + continue; + } + + // Verify input looks sane. This will check that we have at most one uxto, witness or non-witness. + if (!input.IsSane()) { + throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "PSBT input is not sane."); + } + + // If we have no utxo, grab it from the wallet. + if (!input.non_witness_utxo && input.witness_utxo.IsNull()) { + const uint256& txhash = txin.prevout.hash; + const auto it = pwallet->mapWallet.find(txhash); + if (it != pwallet->mapWallet.end()) { + const CWalletTx& wtx = it->second; + // We only need the non_witness_utxo, which is a superset of the witness_utxo. + // The signing code will switch to the smaller witness_utxo if this is ok. + input.non_witness_utxo = wtx.tx; + } } // Get the Sighash type @@ -3841,12 +3915,12 @@ bool FillPSBT(const CWallet* pwallet, PartiallySignedTransaction& psbtx, const C throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "Specified Sighash and sighash in PSBT do not match."); } - complete &= SignPSBTInput(HidingSigningProvider(pwallet, !sign, !bip32derivs), *psbtx.tx, input, i, sighash_type); + complete &= SignPSBTInput(HidingSigningProvider(pwallet, !sign, !bip32derivs), psbtx, i, sighash_type); } // Fill in the bip32 keypaths and redeemscripts for the outputs so that hardware wallets can identify change - for (unsigned int i = 0; i < txConst->vout.size(); ++i) { - const CTxOut& out = txConst->vout.at(i); + for (unsigned int i = 0; i < psbtx.tx->vout.size(); ++i) { + const CTxOut& out = psbtx.tx->vout.at(i); PSBTOutput& psbt_out = psbtx.outputs.at(i); // Fill a SignatureData with output info @@ -3871,23 +3945,23 @@ UniValue walletprocesspsbt(const JSONRPCRequest& request) if (request.fHelp || request.params.size() < 1 || request.params.size() > 4) throw std::runtime_error( - "walletprocesspsbt \"psbt\" ( sign \"sighashtype\" bip32derivs )\n" - "\nUpdate a PSBT with input information from our wallet and then sign inputs\n" - "that we can sign for.\n" - + HelpRequiringPassphrase(pwallet) + "\n" - - "\nArguments:\n" - "1. \"psbt\" (string, required) The transaction base64 string\n" - "2. sign (boolean, optional, default=true) Also sign the transaction when updating\n" - "3. \"sighashtype\" (string, optional, default=ALL) The signature hash type to sign with if not specified by the PSBT. Must be one of\n" + RPCHelpMan{"walletprocesspsbt", + "\nUpdate a PSBT with input information from our wallet and then sign inputs\n" + "that we can sign for." + + HelpRequiringPassphrase(pwallet) + "\n", + { + {"psbt", RPCArg::Type::STR, /* opt */ false, /* default_val */ "", "The transaction base64 string"}, + {"sign", RPCArg::Type::BOOL, /* opt */ true, /* default_val */ "true", "Also sign the transaction when updating"}, + {"sighashtype", RPCArg::Type::STR, /* opt */ true, /* default_val */ "ALL", "The signature hash type to sign with if not specified by the PSBT. Must be one of\n" " \"ALL\"\n" " \"NONE\"\n" " \"SINGLE\"\n" " \"ALL|ANYONECANPAY\"\n" " \"NONE|ANYONECANPAY\"\n" - " \"SINGLE|ANYONECANPAY\"\n" - "4. bip32derivs (boolean, optional, default=false) If true, includes the BIP 32 derivation paths for public keys if we know them\n" - + " \"SINGLE|ANYONECANPAY\""}, + {"bip32derivs", RPCArg::Type::BOOL, /* opt */ true, /* default_val */ "false", "If true, includes the BIP 32 derivation paths for public keys if we know them"}, + }} + .ToString() + "\nResult:\n" "{\n" " \"psbt\" : \"value\", (string) The base64-encoded partially signed transaction\n" @@ -3911,19 +3985,15 @@ UniValue walletprocesspsbt(const JSONRPCRequest& request) // Get the sighash type int nHashType = ParseSighashString(request.params[2]); - // Use CTransaction for the constant parts of the - // transaction to avoid rehashing. - const CTransaction txConst(*psbtx.tx); - // Fill transaction with our data and also sign bool sign = request.params[1].isNull() ? true : request.params[1].get_bool(); bool bip32derivs = request.params[3].isNull() ? false : request.params[3].get_bool(); - bool complete = FillPSBT(pwallet, psbtx, &txConst, nHashType, sign, bip32derivs); + bool complete = FillPSBT(pwallet, psbtx, nHashType, sign, bip32derivs); UniValue result(UniValue::VOBJ); CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION); ssTx << psbtx; - result.pushKV("psbt", EncodeBase64((unsigned char*)ssTx.data(), ssTx.size())); + result.pushKV("psbt", EncodeBase64(ssTx.str())); result.pushKV("complete", complete); return result; @@ -3940,55 +4010,67 @@ UniValue walletcreatefundedpsbt(const JSONRPCRequest& request) if (request.fHelp || request.params.size() < 2 || request.params.size() > 5) throw std::runtime_error( - "walletcreatefundedpsbt [{\"txid\":\"id\",\"vout\":n},...] [{\"address\":amount},{\"data\":\"hex\"},...] ( locktime ) ( replaceable ) ( options bip32derivs )\n" - "\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" - "\nArguments:\n" - "1. \"inputs\" (array, required) A json array of json objects\n" - " [\n" - " {\n" - " \"txid\":\"id\", (string, required) The transaction id\n" - " \"vout\":n, (numeric, required) The output number\n" - " \"sequence\":n (numeric, optional) The sequence number\n" - " } \n" - " ,...\n" - " ]\n" - "2. \"outputs\" (array, required) a json array with outputs (key-value pairs)\n" - " [\n" - " {\n" - " \"address\": x.xxx, (obj, optional) A key-value pair. The key (string) is the bitcoin address, the value (float or string) is the amount in " + CURRENCY_UNIT + "\n" - " },\n" - " {\n" - " \"data\": \"hex\" (obj, optional) A key-value pair. The key must be \"data\", the value is hex encoded data\n" - " }\n" - " ,... More key-value pairs of the above form. For compatibility reasons, a dictionary, which holds the key-value pairs directly, is also\n" - " accepted as second parameter.\n" - " ]\n" - "3. locktime (numeric, optional, default=0) Raw locktime. Non-0 value also locktime-activates inputs\n" - " Allows this transaction to be replaced by a transaction with higher fees. If provided, it is an error if explicit sequence numbers are incompatible.\n" - "4. options (object, optional)\n" - " {\n" - " \"changeAddress\" (string, optional, default pool address) The bitcoin address to receive the change\n" - " \"changePosition\" (numeric, optional, default random) The index of the change output\n" - " \"change_type\" (string, optional) The output type to use. Only valid if changeAddress is not specified. Options are \"legacy\", \"p2sh-segwit\", and \"bech32\". Default is set by -changetype.\n" - " \"includeWatching\" (boolean, optional, default false) Also select inputs which are watch only\n" - " \"lockUnspents\" (boolean, optional, default false) Lock selected unspent outputs\n" - " \"feeRate\" (numeric, optional, default not set: makes wallet determine the fee) Set a specific fee rate in " + CURRENCY_UNIT + "/kB\n" - " \"subtractFeeFromOutputs\" (array, optional) A json array of integers.\n" + 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", + { + {"inputs", RPCArg::Type::ARR, /* opt */ false, /* default_val */ "", "A json array of json objects", + { + {"", RPCArg::Type::OBJ, /* opt */ false, /* default_val */ "", "", + { + {"txid", RPCArg::Type::STR_HEX, /* opt */ false, /* default_val */ "", "The transaction id"}, + {"vout", RPCArg::Type::NUM, /* opt */ false, /* default_val */ "", "The output number"}, + {"sequence", RPCArg::Type::NUM, /* opt */ false, /* default_val */ "", "The sequence number"}, + }, + }, + }, + }, + {"outputs", RPCArg::Type::ARR, /* opt */ false, /* default_val */ "", "a json array with outputs (key-value pairs), where none of the keys are duplicated.\n" + "That is, each address can only appear once and there can only be one 'data' object.\n" + "For compatibility reasons, a dictionary, which holds the key-value pairs directly, is also\n" + " accepted as second parameter.", + { + {"", RPCArg::Type::OBJ, /* opt */ true, /* default_val */ "", "", + { + {"address", RPCArg::Type::AMOUNT, /* opt */ false, /* default_val */ "", "A key-value pair. The key (string) is the bitcoin address, the value (float or string) is the amount in " + CURRENCY_UNIT + ""}, + }, + }, + {"", RPCArg::Type::OBJ, /* opt */ true, /* default_val */ "", "", + { + {"data", RPCArg::Type::STR_HEX, /* opt */ false, /* default_val */ "", "A key-value pair. The key must be \"data\", the value is hex-encoded data"}, + }, + }, + }, + }, + {"locktime", RPCArg::Type::NUM, /* opt */ true, /* default_val */ "0", "Raw locktime. Non-0 value also locktime-activates inputs"}, + {"options", RPCArg::Type::OBJ, /* opt */ true, /* default_val */ "null", "", + { + {"changeAddress", RPCArg::Type::STR_HEX, /* opt */ true, /* default_val */ "pool address", "The bitcoin address to receive the change"}, + {"changePosition", RPCArg::Type::NUM, /* opt */ true, /* default_val */ "random", "The index of the change output"}, + {"change_type", RPCArg::Type::STR, /* opt */ true, /* default_val */ "set by -changetype", "The output type to use. Only valid if changeAddress is not specified. Options are \"legacy\", \"p2sh-segwit\", and \"bech32\"."}, + {"includeWatching", RPCArg::Type::BOOL, /* opt */ true, /* default_val */ "false", "Also select inputs which are watch only"}, + {"lockUnspents", RPCArg::Type::BOOL, /* opt */ true, /* default_val */ "false", "Lock selected unspent outputs"}, + {"feeRate", RPCArg::Type::AMOUNT, /* opt */ true, /* default_val */ "not set: makes wallet determine the fee", "Set a specific fee rate in " + CURRENCY_UNIT + "/kB"}, + {"subtractFeeFromOutputs", RPCArg::Type::ARR, /* opt */ true, /* default_val */ "empty array", "A json array of integers.\n" " The fee will be equally deducted from the amount of each specified output.\n" - " The outputs are specified by their zero-based index, before any change output is added.\n" " Those recipients will receive less bitcoins than you enter in their corresponding amount field.\n" - " If no outputs are specified here, the sender pays the fee.\n" - " [vout_index,...]\n" - " \"replaceable\" (boolean, optional) Marks this transaction as BIP125 replaceable.\n" - " Allows this transaction to be replaced by a transaction with higher fees\n" - " \"conf_target\" (numeric, optional) Confirmation target (in blocks)\n" - " \"estimate_mode\" (string, optional, default=UNSET) The fee estimate mode, must be one of:\n" + " If no outputs are specified here, the sender pays the fee.", + { + {"vout_index", RPCArg::Type::NUM, /* opt */ true, /* default_val */ "", "The zero-based output index, before a change output is added."}, + }, + }, + {"replaceable", RPCArg::Type::BOOL, /* opt */ true, /* default_val */ "false", "Marks this transaction as BIP125 replaceable.\n" + " Allows this transaction to be replaced by a transaction with higher fees"}, + {"conf_target", RPCArg::Type::NUM, /* opt */ true, /* default_val */ "Fallback to wallet's confirmation target", "Confirmation target (in blocks)"}, + {"estimate_mode", RPCArg::Type::STR, /* opt */ true, /* default_val */ "UNSET", "The fee estimate mode, must be one of:\n" " \"UNSET\"\n" " \"ECONOMICAL\"\n" - " \"CONSERVATIVE\"\n" - " }\n" - "5. bip32derivs (boolean, optional, default=false) If true, includes the BIP 32 derivation paths for public keys if we know them\n" + " \"CONSERVATIVE\""}, + }, + "options"}, + {"bip32derivs", RPCArg::Type::BOOL, /* opt */ true, /* default_val */ "false", "If true, includes the BIP 32 derivation paths for public keys if we know them"}, + }} + .ToString() + "\nResult:\n" "{\n" " \"psbt\": \"value\", (string) The resulting raw transaction (base64-encoded string)\n" @@ -4015,29 +4097,18 @@ UniValue walletcreatefundedpsbt(const JSONRPCRequest& request) FundTransaction(pwallet, rawTx, fee, change_position, request.params[3]); // Make a blank psbt - PartiallySignedTransaction psbtx; - psbtx.tx = rawTx; - for (unsigned int i = 0; i < rawTx.vin.size(); ++i) { - psbtx.inputs.push_back(PSBTInput()); - } - for (unsigned int i = 0; i < rawTx.vout.size(); ++i) { - psbtx.outputs.push_back(PSBTOutput()); - } - - // Use CTransaction for the constant parts of the - // transaction to avoid rehashing. - const CTransaction txConst(*psbtx.tx); + PartiallySignedTransaction psbtx(rawTx); // Fill transaction with out data but don't sign bool bip32derivs = request.params[4].isNull() ? false : request.params[4].get_bool(); - FillPSBT(pwallet, psbtx, &txConst, 1, false, bip32derivs); + FillPSBT(pwallet, psbtx, 1, false, bip32derivs); // Serialize the PSBT CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION); ssTx << psbtx; UniValue result(UniValue::VOBJ); - result.pushKV("psbt", EncodeBase64((unsigned char*)ssTx.data(), ssTx.size())); + result.pushKV("psbt", EncodeBase64(ssTx.str())); result.pushKV("fee", ValueFromAmount(fee)); result.pushKV("changepos", change_position); return result; @@ -4059,7 +4130,6 @@ static const CRPCCommand commands[] = { // category name actor (function) argNames // --------------------- ------------------------ ----------------------- ---------- { "generating", "generate", &generate, {"nblocks","maxtries"} }, - { "hidden", "addwitnessaddress", &addwitnessaddress, {"address","p2sh"} }, { "hidden", "resendwallettransactions", &resendwallettransactions, {} }, { "rawtransactions", "fundrawtransaction", &fundrawtransaction, {"hexstring","options","iswitness"} }, { "wallet", "abandontransaction", &abandontransaction, {"txid"} }, @@ -4094,8 +4164,9 @@ static const CRPCCommand commands[] = { "wallet", "listreceivedbyaddress", &listreceivedbyaddress, {"minconf","include_empty","include_watchonly","address_filter"} }, { "wallet", "listreceivedbylabel", &listreceivedbylabel, {"minconf","include_empty","include_watchonly"} }, { "wallet", "listsinceblock", &listsinceblock, {"blockhash","target_confirmations","include_watchonly","include_removed"} }, - { "wallet", "listtransactions", &listtransactions, {"dummy","count","skip","include_watchonly"} }, + { "wallet", "listtransactions", &listtransactions, {"label|dummy","count","skip","include_watchonly"} }, { "wallet", "listunspent", &listunspent, {"minconf","maxconf","addresses","include_unsafe","query_options"} }, + { "wallet", "listwalletdir", &listwalletdir, {} }, { "wallet", "listwallets", &listwallets, {} }, { "wallet", "loadwallet", &loadwallet, {"filename"} }, { "wallet", "lockunspent", &lockunspent, {"unlock","transactions"} }, diff --git a/src/wallet/rpcwallet.h b/src/wallet/rpcwallet.h index 9b9a159b86..abd7750874 100644 --- a/src/wallet/rpcwallet.h +++ b/src/wallet/rpcwallet.h @@ -30,5 +30,5 @@ bool EnsureWalletIsAvailable(CWallet *, bool avoidException); UniValue getaddressinfo(const JSONRPCRequest& request); UniValue signrawtransactionwithwallet(const JSONRPCRequest& request); -bool FillPSBT(const CWallet* pwallet, PartiallySignedTransaction& psbtx, const CTransaction* txConst, int sighash_type = 1, bool sign = true, bool bip32derivs = false); +bool FillPSBT(const CWallet* pwallet, PartiallySignedTransaction& psbtx, int sighash_type = 1 /* SIGHASH_ALL */, bool sign = true, bool bip32derivs = false); #endif //BITCOIN_WALLET_RPCWALLET_H diff --git a/src/wallet/test/coinselector_tests.cpp b/src/wallet/test/coinselector_tests.cpp index 21857df081..5c65acf601 100644 --- a/src/wallet/test/coinselector_tests.cpp +++ b/src/wallet/test/coinselector_tests.cpp @@ -28,7 +28,8 @@ std::vector<std::unique_ptr<CWalletTx>> wtxn; typedef std::set<CInputCoin> CoinSet; static std::vector<COutput> vCoins; -static CWallet testWallet("dummy", WalletDatabase::CreateDummy()); +static auto testChain = interfaces::MakeChain(); +static CWallet testWallet(*testChain, WalletLocation(), WalletDatabase::CreateDummy()); static CAmount balance = 0; CoinEligibilityFilter filter_standard(1, 6, 0); diff --git a/src/wallet/test/init_test_fixture.cpp b/src/wallet/test/init_test_fixture.cpp new file mode 100644 index 0000000000..3b828d57f9 --- /dev/null +++ b/src/wallet/test/init_test_fixture.cpp @@ -0,0 +1,44 @@ +// Copyright (c) 2018 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include <fs.h> + +#include <wallet/test/init_test_fixture.h> + +InitWalletDirTestingSetup::InitWalletDirTestingSetup(const std::string& chainName): BasicTestingSetup(chainName) +{ + m_chain_client = MakeWalletClient(*m_chain, {}); + + std::string sep; + sep += fs::path::preferred_separator; + + m_datadir = SetDataDir("tempdir"); + m_cwd = fs::current_path(); + + m_walletdir_path_cases["default"] = m_datadir / "wallets"; + m_walletdir_path_cases["custom"] = m_datadir / "my_wallets"; + m_walletdir_path_cases["nonexistent"] = m_datadir / "path_does_not_exist"; + m_walletdir_path_cases["file"] = m_datadir / "not_a_directory.dat"; + m_walletdir_path_cases["trailing"] = m_datadir / "wallets" / sep; + m_walletdir_path_cases["trailing2"] = m_datadir / "wallets" / sep / sep; + + fs::current_path(m_datadir); + m_walletdir_path_cases["relative"] = "wallets"; + + fs::create_directories(m_walletdir_path_cases["default"]); + fs::create_directories(m_walletdir_path_cases["custom"]); + fs::create_directories(m_walletdir_path_cases["relative"]); + std::ofstream f(m_walletdir_path_cases["file"].BOOST_FILESYSTEM_C_STR); + f.close(); +} + +InitWalletDirTestingSetup::~InitWalletDirTestingSetup() +{ + fs::current_path(m_cwd); +} + +void InitWalletDirTestingSetup::SetWalletDir(const fs::path& walletdir_path) +{ + gArgs.ForceSetArg("-walletdir", walletdir_path.string()); +}
\ No newline at end of file diff --git a/src/wallet/test/init_test_fixture.h b/src/wallet/test/init_test_fixture.h new file mode 100644 index 0000000000..cd47b31da1 --- /dev/null +++ b/src/wallet/test/init_test_fixture.h @@ -0,0 +1,24 @@ +// Copyright (c) 2018 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_WALLET_TEST_INIT_TEST_FIXTURE_H +#define BITCOIN_WALLET_TEST_INIT_TEST_FIXTURE_H + +#include <interfaces/chain.h> +#include <test/test_bitcoin.h> + + +struct InitWalletDirTestingSetup: public BasicTestingSetup { + explicit InitWalletDirTestingSetup(const std::string& chainName = CBaseChainParams::MAIN); + ~InitWalletDirTestingSetup(); + void SetWalletDir(const fs::path& walletdir_path); + + fs::path m_datadir; + fs::path m_cwd; + std::map<std::string, fs::path> m_walletdir_path_cases; + std::unique_ptr<interfaces::Chain> m_chain = interfaces::MakeChain(); + std::unique_ptr<interfaces::ChainClient> m_chain_client; +}; + +#endif // BITCOIN_WALLET_TEST_INIT_TEST_FIXTURE_H diff --git a/src/wallet/test/init_tests.cpp b/src/wallet/test/init_tests.cpp new file mode 100644 index 0000000000..5852d3ef84 --- /dev/null +++ b/src/wallet/test/init_tests.cpp @@ -0,0 +1,78 @@ +// Copyright (c) 2018 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include <boost/test/unit_test.hpp> + +#include <test/test_bitcoin.h> +#include <wallet/test/init_test_fixture.h> + +#include <init.h> +#include <walletinitinterface.h> +#include <wallet/wallet.h> + + +BOOST_FIXTURE_TEST_SUITE(init_tests, InitWalletDirTestingSetup) + +BOOST_AUTO_TEST_CASE(walletinit_verify_walletdir_default) +{ + SetWalletDir(m_walletdir_path_cases["default"]); + bool result = m_chain_client->verify(); + BOOST_CHECK(result == true); + fs::path walletdir = gArgs.GetArg("-walletdir", ""); + fs::path expected_path = fs::canonical(m_walletdir_path_cases["default"]); + BOOST_CHECK(walletdir == expected_path); +} + +BOOST_AUTO_TEST_CASE(walletinit_verify_walletdir_custom) +{ + SetWalletDir(m_walletdir_path_cases["custom"]); + bool result = m_chain_client->verify(); + BOOST_CHECK(result == true); + fs::path walletdir = gArgs.GetArg("-walletdir", ""); + fs::path expected_path = fs::canonical(m_walletdir_path_cases["custom"]); + BOOST_CHECK(walletdir == expected_path); +} + +BOOST_AUTO_TEST_CASE(walletinit_verify_walletdir_does_not_exist) +{ + SetWalletDir(m_walletdir_path_cases["nonexistent"]); + bool result = m_chain_client->verify(); + BOOST_CHECK(result == false); +} + +BOOST_AUTO_TEST_CASE(walletinit_verify_walletdir_is_not_directory) +{ + SetWalletDir(m_walletdir_path_cases["file"]); + bool result = m_chain_client->verify(); + BOOST_CHECK(result == false); +} + +BOOST_AUTO_TEST_CASE(walletinit_verify_walletdir_is_not_relative) +{ + SetWalletDir(m_walletdir_path_cases["relative"]); + bool result = m_chain_client->verify(); + BOOST_CHECK(result == false); +} + +BOOST_AUTO_TEST_CASE(walletinit_verify_walletdir_no_trailing) +{ + SetWalletDir(m_walletdir_path_cases["trailing"]); + bool result = m_chain_client->verify(); + BOOST_CHECK(result == true); + fs::path walletdir = gArgs.GetArg("-walletdir", ""); + fs::path expected_path = fs::canonical(m_walletdir_path_cases["default"]); + BOOST_CHECK(walletdir == expected_path); +} + +BOOST_AUTO_TEST_CASE(walletinit_verify_walletdir_no_trailing2) +{ + SetWalletDir(m_walletdir_path_cases["trailing2"]); + bool result = m_chain_client->verify(); + BOOST_CHECK(result == true); + fs::path walletdir = gArgs.GetArg("-walletdir", ""); + fs::path expected_path = fs::canonical(m_walletdir_path_cases["default"]); + BOOST_CHECK(walletdir == expected_path); +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/src/wallet/test/psbt_wallet_tests.cpp b/src/wallet/test/psbt_wallet_tests.cpp index 526f2d983f..9918eeb89f 100644 --- a/src/wallet/test/psbt_wallet_tests.cpp +++ b/src/wallet/test/psbt_wallet_tests.cpp @@ -4,7 +4,7 @@ #include <key_io.h> #include <script/sign.h> -#include <utilstrencodings.h> +#include <util/strencodings.h> #include <wallet/rpcwallet.h> #include <wallet/wallet.h> #include <univalue.h> @@ -17,6 +17,8 @@ BOOST_FIXTURE_TEST_SUITE(psbt_wallet_tests, WalletTestingSetup) BOOST_AUTO_TEST_CASE(psbt_updater_test) { + LOCK(m_wallet.cs_wallet); + // Create prevtxs and add to wallet CDataStream s_prev_tx1(ParseHex("0200000000010158e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd7501000000171600145f275f436b09a8cc9a2eb2a2f528485c68a56323feffffff02d8231f1b0100000017a914aed962d6654f9a2b36608eb9d64d2b260db4f1118700c2eb0b0000000017a914b7f5faf40e3d40a5a459b1db3535f2b72fa921e88702483045022100a22edcc6e5bc511af4cc4ae0de0fcd75c7e04d8c1c3a8aa9d820ed4b967384ec02200642963597b9b1bc22c75e9f3e117284a962188bf5e8a74c895089046a20ad770121035509a48eb623e10aace8bfd0212fdb8a8e5af3c94b0b133b95e114cab89e4f7965000000"), SER_NETWORK, PROTOCOL_VERSION); CTransactionRef prev_tx1; @@ -57,12 +59,8 @@ BOOST_AUTO_TEST_CASE(psbt_updater_test) CDataStream ssData(ParseHex("70736274ff01009a020000000258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd750000000000ffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d0100000000ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f000000000000000000"), SER_NETWORK, PROTOCOL_VERSION); ssData >> psbtx; - // Use CTransaction for the constant parts of the - // transaction to avoid rehashing. - const CTransaction txConst(*psbtx.tx); - // Fill transaction with our data - FillPSBT(&m_wallet, psbtx, &txConst, 1, false, true); + FillPSBT(&m_wallet, psbtx, SIGHASH_ALL, false, true); // Get the final tx CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION); diff --git a/src/wallet/test/wallet_crypto_tests.cpp b/src/wallet/test/wallet_crypto_tests.cpp index f193d5c41d..ae7092fa89 100644 --- a/src/wallet/test/wallet_crypto_tests.cpp +++ b/src/wallet/test/wallet_crypto_tests.cpp @@ -3,7 +3,7 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include <test/test_bitcoin.h> -#include <utilstrencodings.h> +#include <util/strencodings.h> #include <wallet/crypter.h> #include <vector> diff --git a/src/wallet/test/wallet_test_fixture.cpp b/src/wallet/test/wallet_test_fixture.cpp index de59b60349..a5fb1db86c 100644 --- a/src/wallet/test/wallet_test_fixture.cpp +++ b/src/wallet/test/wallet_test_fixture.cpp @@ -6,9 +6,10 @@ #include <rpc/server.h> #include <wallet/db.h> +#include <wallet/rpcwallet.h> WalletTestingSetup::WalletTestingSetup(const std::string& chainName): - TestingSetup(chainName), m_wallet("mock", WalletDatabase::CreateMock()) + TestingSetup(chainName), m_wallet(*m_chain, WalletLocation(), WalletDatabase::CreateMock()) { bool fFirstRun; m_wallet.LoadWallet(fFirstRun); diff --git a/src/wallet/test/wallet_test_fixture.h b/src/wallet/test/wallet_test_fixture.h index ff8ec32b03..e6fe8c9473 100644 --- a/src/wallet/test/wallet_test_fixture.h +++ b/src/wallet/test/wallet_test_fixture.h @@ -7,6 +7,8 @@ #include <test/test_bitcoin.h> +#include <interfaces/chain.h> +#include <interfaces/wallet.h> #include <wallet/wallet.h> #include <memory> @@ -17,6 +19,7 @@ struct WalletTestingSetup: public TestingSetup { explicit WalletTestingSetup(const std::string& chainName = CBaseChainParams::MAIN); ~WalletTestingSetup(); + std::unique_ptr<interfaces::Chain> m_chain = interfaces::MakeChain(); CWallet m_wallet; }; diff --git a/src/wallet/test/wallet_tests.cpp b/src/wallet/test/wallet_tests.cpp index 3a8e6f751a..0db22cf6fe 100644 --- a/src/wallet/test/wallet_tests.cpp +++ b/src/wallet/test/wallet_tests.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2012-2018 The Bitcoin Core developers +// Copyright (c) 2012-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,11 +11,13 @@ #include <vector> #include <consensus/validation.h> +#include <interfaces/chain.h> #include <rpc/server.h> #include <test/test_bitcoin.h> #include <validation.h> #include <wallet/coincontrol.h> #include <wallet/test/wallet_test_fixture.h> +#include <policy/policy.h> #include <boost/test/unit_test.hpp> #include <univalue.h> @@ -32,25 +34,43 @@ static void AddKey(CWallet& wallet, const CKey& key) wallet.AddKeyPubKey(key, key.GetPubKey()); } -BOOST_FIXTURE_TEST_CASE(rescan, TestChain100Setup) +BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup) { + auto chain = interfaces::MakeChain(); + // Cap last block file size, and mine new block in a new block file. - CBlockIndex* const nullBlock = nullptr; + const CBlockIndex* const null_block = nullptr; CBlockIndex* oldTip = chainActive.Tip(); GetBlockFileInfo(oldTip->GetBlockPos().nFile)->nSize = MAX_BLOCKFILE_SIZE; CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey())); CBlockIndex* newTip = chainActive.Tip(); - LOCK(cs_main); + auto locked_chain = chain->lock(); + + // Verify ScanForWalletTransactions accommodates a null start block. + { + CWallet wallet(*chain, WalletLocation(), WalletDatabase::CreateDummy()); + AddKey(wallet, coinbaseKey); + WalletRescanReserver reserver(&wallet); + reserver.reserve(); + const CBlockIndex *stop_block = null_block + 1, *failed_block = null_block + 1; + BOOST_CHECK_EQUAL(wallet.ScanForWalletTransactions(nullptr, nullptr, reserver, failed_block, stop_block), CWallet::ScanResult::SUCCESS); + BOOST_CHECK_EQUAL(failed_block, null_block); + BOOST_CHECK_EQUAL(stop_block, null_block); + BOOST_CHECK_EQUAL(wallet.GetImmatureBalance(), 0); + } // Verify ScanForWalletTransactions picks up transactions in both the old // and new block files. { - CWallet wallet("dummy", WalletDatabase::CreateDummy()); + CWallet wallet(*chain, WalletLocation(), WalletDatabase::CreateDummy()); AddKey(wallet, coinbaseKey); WalletRescanReserver reserver(&wallet); reserver.reserve(); - BOOST_CHECK_EQUAL(nullBlock, wallet.ScanForWalletTransactions(oldTip, nullptr, reserver)); + const CBlockIndex *stop_block = null_block + 1, *failed_block = null_block + 1; + BOOST_CHECK_EQUAL(wallet.ScanForWalletTransactions(oldTip, nullptr, reserver, failed_block, stop_block), CWallet::ScanResult::SUCCESS); + BOOST_CHECK_EQUAL(failed_block, null_block); + BOOST_CHECK_EQUAL(stop_block, newTip); BOOST_CHECK_EQUAL(wallet.GetImmatureBalance(), 100 * COIN); } @@ -61,19 +81,56 @@ BOOST_FIXTURE_TEST_CASE(rescan, TestChain100Setup) // Verify ScanForWalletTransactions only picks transactions in the new block // file. { - CWallet wallet("dummy", WalletDatabase::CreateDummy()); + CWallet wallet(*chain, WalletLocation(), WalletDatabase::CreateDummy()); AddKey(wallet, coinbaseKey); WalletRescanReserver reserver(&wallet); reserver.reserve(); - BOOST_CHECK_EQUAL(oldTip, wallet.ScanForWalletTransactions(oldTip, nullptr, reserver)); + const CBlockIndex *stop_block = null_block + 1, *failed_block = null_block + 1; + BOOST_CHECK_EQUAL(wallet.ScanForWalletTransactions(oldTip, nullptr, reserver, failed_block, stop_block), CWallet::ScanResult::FAILURE); + BOOST_CHECK_EQUAL(failed_block, oldTip); + BOOST_CHECK_EQUAL(stop_block, newTip); BOOST_CHECK_EQUAL(wallet.GetImmatureBalance(), 50 * COIN); } + // Prune the remaining block file. + PruneOneBlockFile(newTip->GetBlockPos().nFile); + UnlinkPrunedFiles({newTip->GetBlockPos().nFile}); + + // Verify ScanForWalletTransactions scans no blocks. + { + CWallet wallet(*chain, WalletLocation(), WalletDatabase::CreateDummy()); + AddKey(wallet, coinbaseKey); + WalletRescanReserver reserver(&wallet); + reserver.reserve(); + const CBlockIndex *stop_block = null_block + 1, *failed_block = null_block + 1; + BOOST_CHECK_EQUAL(wallet.ScanForWalletTransactions(oldTip, nullptr, reserver, failed_block, stop_block), CWallet::ScanResult::FAILURE); + BOOST_CHECK_EQUAL(failed_block, newTip); + BOOST_CHECK_EQUAL(stop_block, null_block); + BOOST_CHECK_EQUAL(wallet.GetImmatureBalance(), 0); + } +} + +BOOST_FIXTURE_TEST_CASE(importmulti_rescan, TestChain100Setup) +{ + auto chain = interfaces::MakeChain(); + + // Cap last block file size, and mine new block in a new block file. + CBlockIndex* oldTip = chainActive.Tip(); + GetBlockFileInfo(oldTip->GetBlockPos().nFile)->nSize = MAX_BLOCKFILE_SIZE; + CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey())); + CBlockIndex* newTip = chainActive.Tip(); + + auto locked_chain = chain->lock(); + + // Prune the older block file. + PruneOneBlockFile(oldTip->GetBlockPos().nFile); + UnlinkPrunedFiles({oldTip->GetBlockPos().nFile}); + // Verify importmulti RPC returns failure for a key whose creation time is // before the missing block, and success for a key whose creation time is // after. { - std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>("dummy", WalletDatabase::CreateDummy()); + std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(*chain, WalletLocation(), WalletDatabase::CreateDummy()); AddWallet(wallet); UniValue keys; keys.setArray(); @@ -115,6 +172,8 @@ BOOST_FIXTURE_TEST_CASE(rescan, TestChain100Setup) // than or equal to key birthday. BOOST_FIXTURE_TEST_CASE(importwallet_rescan, TestChain100Setup) { + auto chain = interfaces::MakeChain(); + // Create two blocks with same timestamp to verify that importwallet rescan // will pick up both blocks, not just the first. const int64_t BLOCK_TIME = chainActive.Tip()->GetBlockTimeMax() + 5; @@ -128,13 +187,13 @@ BOOST_FIXTURE_TEST_CASE(importwallet_rescan, TestChain100Setup) SetMockTime(KEY_TIME); m_coinbase_txns.emplace_back(CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey())).vtx[0]); - LOCK(cs_main); + auto locked_chain = chain->lock(); std::string backup_file = (SetDataDir("importwallet_rescan") / "wallet.backup").string(); // Import key into wallet and call dumpwallet to create backup file. { - std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>("dummy", WalletDatabase::CreateDummy()); + std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(*chain, WalletLocation(), WalletDatabase::CreateDummy()); LOCK(wallet->cs_wallet); wallet->mapKeyMetadata[coinbaseKey.GetPubKey().GetID()].nCreateTime = KEY_TIME; wallet->AddKeyPubKey(coinbaseKey, coinbaseKey.GetPubKey()); @@ -150,7 +209,7 @@ BOOST_FIXTURE_TEST_CASE(importwallet_rescan, TestChain100Setup) // Call importwallet RPC and verify all blocks with timestamps >= BLOCK_TIME // were scanned, and no prior blocks were scanned. { - std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>("dummy", WalletDatabase::CreateDummy()); + std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(*chain, WalletLocation(), WalletDatabase::CreateDummy()); JSONRPCRequest request; request.params.setArray(); @@ -180,21 +239,23 @@ BOOST_FIXTURE_TEST_CASE(importwallet_rescan, TestChain100Setup) // debit functions. BOOST_FIXTURE_TEST_CASE(coin_mark_dirty_immature_credit, TestChain100Setup) { - CWallet wallet("dummy", WalletDatabase::CreateDummy()); + auto chain = interfaces::MakeChain(); + CWallet wallet(*chain, WalletLocation(), WalletDatabase::CreateDummy()); CWalletTx wtx(&wallet, m_coinbase_txns.back()); - LOCK2(cs_main, wallet.cs_wallet); + auto locked_chain = chain->lock(); + LOCK(wallet.cs_wallet); wtx.hashBlock = chainActive.Tip()->GetBlockHash(); wtx.nIndex = 0; // Call GetImmatureCredit() once before adding the key to the wallet to // cache the current immature credit amount, which is 0. - BOOST_CHECK_EQUAL(wtx.GetImmatureCredit(), 0); + BOOST_CHECK_EQUAL(wtx.GetImmatureCredit(*locked_chain), 0); // Invalidate the cached value, add the key, and make sure a new immature // credit amount is calculated. wtx.MarkDirty(); wallet.AddKeyPubKey(coinbaseKey, coinbaseKey.GetPubKey()); - BOOST_CHECK_EQUAL(wtx.GetImmatureCredit(), 50*COIN); + BOOST_CHECK_EQUAL(wtx.GetImmatureCredit(*locked_chain), 50*COIN); } static int64_t AddTx(CWallet& wallet, uint32_t lockTime, int64_t mockTime, int64_t blockTime) @@ -204,7 +265,7 @@ static int64_t AddTx(CWallet& wallet, uint32_t lockTime, int64_t mockTime, int64 SetMockTime(mockTime); CBlockIndex* block = nullptr; if (blockTime > 0) { - LOCK(cs_main); + auto locked_chain = wallet.chain().lock(); auto inserted = mapBlockIndex.emplace(GetRandHash(), new CBlockIndex); assert(inserted.second); const uint256& hash = inserted.first->first; @@ -273,13 +334,17 @@ public: ListCoinsTestingSetup() { CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey())); - wallet = MakeUnique<CWallet>("mock", WalletDatabase::CreateMock()); + wallet = MakeUnique<CWallet>(*m_chain, WalletLocation(), WalletDatabase::CreateMock()); bool firstRun; wallet->LoadWallet(firstRun); AddKey(*wallet, coinbaseKey); WalletRescanReserver reserver(wallet.get()); reserver.reserve(); - wallet->ScanForWalletTransactions(chainActive.Genesis(), nullptr, reserver); + const CBlockIndex* const null_block = nullptr; + const CBlockIndex *stop_block = null_block + 1, *failed_block = null_block + 1; + BOOST_CHECK_EQUAL(wallet->ScanForWalletTransactions(chainActive.Genesis(), nullptr, reserver, failed_block, stop_block), CWallet::ScanResult::SUCCESS); + BOOST_CHECK_EQUAL(stop_block, chainActive.Tip()); + BOOST_CHECK_EQUAL(failed_block, null_block); } ~ListCoinsTestingSetup() @@ -295,7 +360,7 @@ public: int changePos = -1; std::string error; CCoinControl dummy; - BOOST_CHECK(wallet->CreateTransaction({recipient}, tx, reservekey, fee, changePos, error, dummy)); + BOOST_CHECK(wallet->CreateTransaction(*m_locked_chain, {recipient}, tx, reservekey, fee, changePos, error, dummy)); CValidationState state; BOOST_CHECK(wallet->CommitTransaction(tx, {}, {}, reservekey, nullptr, state)); CMutableTransaction blocktx; @@ -311,6 +376,8 @@ public: return it->second; } + std::unique_ptr<interfaces::Chain> m_chain = interfaces::MakeChain(); + std::unique_ptr<interfaces::Chain::Lock> m_locked_chain = m_chain->assumeLocked(); // Temporary. Removed in upcoming lock cleanup std::unique_ptr<CWallet> wallet; }; @@ -323,7 +390,7 @@ BOOST_FIXTURE_TEST_CASE(ListCoins, ListCoinsTestingSetup) std::map<CTxDestination, std::vector<COutput>> list; { LOCK2(cs_main, wallet->cs_wallet); - list = wallet->ListCoins(); + list = wallet->ListCoins(*m_locked_chain); } BOOST_CHECK_EQUAL(list.size(), 1U); BOOST_CHECK_EQUAL(boost::get<CKeyID>(list.begin()->first).ToString(), coinbaseAddress); @@ -339,7 +406,7 @@ BOOST_FIXTURE_TEST_CASE(ListCoins, ListCoinsTestingSetup) AddTx(CRecipient{GetScriptForRawPubKey({}), 1 * COIN, false /* subtract fee */}); { LOCK2(cs_main, wallet->cs_wallet); - list = wallet->ListCoins(); + list = wallet->ListCoins(*m_locked_chain); } BOOST_CHECK_EQUAL(list.size(), 1U); BOOST_CHECK_EQUAL(boost::get<CKeyID>(list.begin()->first).ToString(), coinbaseAddress); @@ -349,7 +416,7 @@ BOOST_FIXTURE_TEST_CASE(ListCoins, ListCoinsTestingSetup) { LOCK2(cs_main, wallet->cs_wallet); std::vector<COutput> available; - wallet->AvailableCoins(available); + wallet->AvailableCoins(*m_locked_chain, available); BOOST_CHECK_EQUAL(available.size(), 2U); } for (const auto& group : list) { @@ -361,14 +428,14 @@ BOOST_FIXTURE_TEST_CASE(ListCoins, ListCoinsTestingSetup) { LOCK2(cs_main, wallet->cs_wallet); std::vector<COutput> available; - wallet->AvailableCoins(available); + wallet->AvailableCoins(*m_locked_chain, available); BOOST_CHECK_EQUAL(available.size(), 0U); } // Confirm ListCoins still returns same result as before, despite coins // being locked. { LOCK2(cs_main, wallet->cs_wallet); - list = wallet->ListCoins(); + list = wallet->ListCoins(*m_locked_chain); } BOOST_CHECK_EQUAL(list.size(), 1U); BOOST_CHECK_EQUAL(boost::get<CKeyID>(list.begin()->first).ToString(), coinbaseAddress); @@ -377,11 +444,55 @@ BOOST_FIXTURE_TEST_CASE(ListCoins, ListCoinsTestingSetup) BOOST_FIXTURE_TEST_CASE(wallet_disableprivkeys, TestChain100Setup) { - std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>("dummy", WalletDatabase::CreateDummy()); + auto chain = interfaces::MakeChain(); + std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(*chain, WalletLocation(), WalletDatabase::CreateDummy()); wallet->SetWalletFlag(WALLET_FLAG_DISABLE_PRIVATE_KEYS); BOOST_CHECK(!wallet->TopUpKeyPool(1000)); CPubKey pubkey; BOOST_CHECK(!wallet->GetKeyFromPool(pubkey, false)); } +// Explicit calculation which is used to test the wallet constant +// We get the same virtual size due to rounding(weight/4) for both use_max_sig values +static size_t CalculateNestedKeyhashInputSize(bool use_max_sig) +{ + // Generate ephemeral valid pubkey + CKey key; + key.MakeNewKey(true); + CPubKey pubkey = key.GetPubKey(); + + // Generate pubkey hash + uint160 key_hash(Hash160(pubkey.begin(), pubkey.end())); + + // Create inner-script to enter into keystore. Key hash can't be 0... + CScript inner_script = CScript() << OP_0 << std::vector<unsigned char>(key_hash.begin(), key_hash.end()); + + // Create outer P2SH script for the output + uint160 script_id(Hash160(inner_script.begin(), inner_script.end())); + 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; + keystore.AddCScript(inner_script); + keystore.AddKeyPubKey(key, pubkey); + + // Fill in dummy signatures for fee calculation. + SignatureData sig_data; + + if (!ProduceSignature(keystore, use_max_sig ? DUMMY_MAXIMUM_SIGNATURE_CREATOR : DUMMY_SIGNATURE_CREATOR, script_pubkey, sig_data)) { + // We're hand-feeding it correct arguments; shouldn't happen + assert(false); + } + + CTxIn tx_in; + UpdateInput(tx_in, sig_data); + return (size_t)GetVirtualTransactionInputSize(tx_in); +} + +BOOST_FIXTURE_TEST_CASE(dummy_input_size_test, TestChain100Setup) +{ + BOOST_CHECK_EQUAL(CalculateNestedKeyhashInputSize(false), DUMMY_NESTED_P2WPKH_INPUT_SIZE); + BOOST_CHECK_EQUAL(CalculateNestedKeyhashInputSize(true), DUMMY_NESTED_P2WPKH_INPUT_SIZE); +} + BOOST_AUTO_TEST_SUITE_END() diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index afe47d986e..74deb2dddc 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -11,6 +11,7 @@ #include <consensus/consensus.h> #include <consensus/validation.h> #include <fs.h> +#include <interfaces/chain.h> #include <key.h> #include <key_io.h> #include <keystore.h> @@ -21,13 +22,13 @@ #include <policy/rbf.h> #include <primitives/block.h> #include <primitives/transaction.h> +#include <script/descriptor.h> #include <script/script.h> #include <shutdown.h> #include <timedata.h> #include <txmempool.h> -#include <utilmoneystr.h> +#include <util/moneystr.h> #include <wallet/fees.h> -#include <wallet/walletutil.h> #include <algorithm> #include <assert.h> @@ -81,13 +82,52 @@ std::shared_ptr<CWallet> GetWallet(const std::string& name) return nullptr; } +static Mutex g_wallet_release_mutex; +static std::condition_variable g_wallet_release_cv; +static std::set<CWallet*> g_unloading_wallet_set; + // Custom deleter for shared_ptr<CWallet>. static void ReleaseWallet(CWallet* wallet) { + // Unregister and delete the wallet right after BlockUntilSyncedToCurrentChain + // so that it's in sync with the current chainstate. wallet->WalletLogPrintf("Releasing wallet\n"); wallet->BlockUntilSyncedToCurrentChain(); wallet->Flush(); + UnregisterValidationInterface(wallet); delete wallet; + // Wallet is now released, notify UnloadWallet, if any. + { + LOCK(g_wallet_release_mutex); + if (g_unloading_wallet_set.erase(wallet) == 0) { + // UnloadWallet was not called for this wallet, all done. + return; + } + } + g_wallet_release_cv.notify_all(); +} + +void UnloadWallet(std::shared_ptr<CWallet>&& wallet) +{ + // Mark wallet for unloading. + CWallet* pwallet = wallet.get(); + { + LOCK(g_wallet_release_mutex); + auto it = g_unloading_wallet_set.insert(pwallet); + assert(it.second); + } + // The wallet can be in use so it's not possible to explicitly unload here. + // Notify the unload intent so that all remaining shared pointers are + // released. + pwallet->NotifyUnload(); + // Time to ditch our shared_ptr and wait for ReleaseWallet call. + wallet.reset(); + { + WAIT_LOCK(g_wallet_release_mutex, lock); + while (g_unloading_wallet_set.count(pwallet) == 1) { + g_wallet_release_cv.wait(lock); + } + } } const uint32_t BIP32_HARDENED_KEY_LIMIT = 0x80000000; @@ -104,67 +144,17 @@ std::string COutput::ToString() const return strprintf("COutput(%s, %d, %d) [%s]", tx->GetHash().ToString(), i, nDepth, FormatMoney(tx->tx->vout[i].nValue)); } -/** A class to identify which pubkeys a script and a keystore have in common. */ -class CAffectedKeysVisitor : public boost::static_visitor<void> { -private: - const CKeyStore &keystore; - std::vector<CKeyID> &vKeys; - -public: - /** - * @param[in] keystoreIn The CKeyStore that is queried for the presence of a pubkey. - * @param[out] vKeysIn A vector to which a script's pubkey identifiers are appended if they are in the keystore. - */ - CAffectedKeysVisitor(const CKeyStore &keystoreIn, std::vector<CKeyID> &vKeysIn) : keystore(keystoreIn), vKeys(vKeysIn) {} - - /** - * Apply the visitor to each destination in a script, recursively to the redeemscript - * in the case of p2sh destinations. - * @param[in] script The CScript from which destinations are extracted. - * @post Any CKeyIDs that script and keystore have in common are appended to the visitor's vKeys. - */ - void Process(const CScript &script) { - txnouttype type; - std::vector<CTxDestination> vDest; - int nRequired; - if (ExtractDestinations(script, type, vDest, nRequired)) { - for (const CTxDestination &dest : vDest) - boost::apply_visitor(*this, dest); - } - } - - void operator()(const CKeyID &keyId) { - if (keystore.HaveKey(keyId)) - vKeys.push_back(keyId); - } - - void operator()(const CScriptID &scriptId) { - CScript script; - if (keystore.GetCScript(scriptId, script)) - Process(script); - } - - void operator()(const WitnessV0ScriptHash& scriptID) - { - CScriptID id; - CRIPEMD160().Write(scriptID.begin(), 32).Finalize(id.begin()); - CScript script; - if (keystore.GetCScript(id, script)) { - Process(script); - } - } - - void operator()(const WitnessV0KeyHash& keyid) - { - CKeyID id(keyid); - if (keystore.HaveKey(id)) { - vKeys.push_back(id); - } +std::vector<CKeyID> GetAffectedKeys(const CScript& spk, const SigningProvider& provider) +{ + std::vector<CScript> dummy; + FlatSigningProvider out; + InferDescriptor(spk, provider)->Expand(0, DUMMY_SIGNING_PROVIDER, dummy, out); + std::vector<CKeyID> ret; + for (const auto& entry : out.pubkeys) { + ret.push_back(entry.first); } - - template<typename X> - void operator()(const X &none) {} -}; + return ret; +} const CWalletTx* CWallet::GetWalletTx(const uint256& hash) const { @@ -594,7 +584,7 @@ void CWallet::SyncMetaData(std::pair<TxSpends::iterator, TxSpends::iterator> ran * Outpoint is spent if any non-conflicted transaction * spends it: */ -bool CWallet::IsSpent(const uint256& hash, unsigned int n) const +bool CWallet::IsSpent(interfaces::Chain::Lock& locked_chain, const uint256& hash, unsigned int n) const { const COutPoint outpoint(hash, n); std::pair<TxSpends::const_iterator, TxSpends::const_iterator> range; @@ -605,7 +595,7 @@ bool CWallet::IsSpent(const uint256& hash, unsigned int n) const const uint256& wtxid = it->second; std::map<uint256, CWalletTx>::const_iterator mit = mapWallet.find(wtxid); if (mit != mapWallet.end()) { - int depth = mit->second.GetDepthInMainChain(); + int depth = mit->second.GetDepthInMainChain(locked_chain); if (depth > 0 || (depth == 0 && !mit->second.isAbandoned())) return true; // Spent } @@ -977,9 +967,7 @@ bool CWallet::AddToWalletIfInvolvingMe(const CTransactionRef& ptx, const CBlockI // loop though all outputs for (const CTxOut& txout: tx.vout) { // extract addresses and check if they match with an unused keypool key - std::vector<CKeyID> vAffected; - CAffectedKeysVisitor(*this, vAffected).Process(txout.scriptPubKey); - for (const CKeyID &keyid : vAffected) { + for (const auto& keyid : GetAffectedKeys(txout.scriptPubKey, *this)) { std::map<CKeyID, int64_t>::const_iterator mi = m_pool_key_to_index.find(keyid); if (mi != m_pool_key_to_index.end()) { WalletLogPrintf("%s: Detected a used keypool key, mark all keypool key up to this key as used\n", __func__); @@ -1006,9 +994,10 @@ bool CWallet::AddToWalletIfInvolvingMe(const CTransactionRef& ptx, const CBlockI bool CWallet::TransactionCanBeAbandoned(const uint256& hashTx) const { - LOCK2(cs_main, cs_wallet); + auto locked_chain = chain().lock(); + LOCK(cs_wallet); const CWalletTx* wtx = GetWalletTx(hashTx); - return wtx && !wtx->isAbandoned() && wtx->GetDepthInMainChain() == 0 && !wtx->InMempool(); + return wtx && !wtx->isAbandoned() && wtx->GetDepthInMainChain(*locked_chain) == 0 && !wtx->InMempool(); } void CWallet::MarkInputsDirty(const CTransactionRef& tx) @@ -1021,9 +1010,10 @@ void CWallet::MarkInputsDirty(const CTransactionRef& tx) } } -bool CWallet::AbandonTransaction(const uint256& hashTx) +bool CWallet::AbandonTransaction(interfaces::Chain::Lock& locked_chain, const uint256& hashTx) { - LOCK2(cs_main, cs_wallet); + auto locked_chain_recursive = chain().lock(); // Temporary. Removed in upcoming lock cleanup + LOCK(cs_wallet); WalletBatch batch(*database, "r+"); @@ -1034,7 +1024,7 @@ bool CWallet::AbandonTransaction(const uint256& hashTx) auto it = mapWallet.find(hashTx); assert(it != mapWallet.end()); CWalletTx& origtx = it->second; - if (origtx.GetDepthInMainChain() != 0 || origtx.InMempool()) { + if (origtx.GetDepthInMainChain(locked_chain) != 0 || origtx.InMempool()) { return false; } @@ -1047,7 +1037,7 @@ bool CWallet::AbandonTransaction(const uint256& hashTx) auto it = mapWallet.find(now); assert(it != mapWallet.end()); CWalletTx& wtx = it->second; - int currentconfirm = wtx.GetDepthInMainChain(); + int currentconfirm = wtx.GetDepthInMainChain(locked_chain); // If the orig tx was not in block, none of its spends can be assert(currentconfirm <= 0); // if (currentconfirm < 0) {Tx and spends are already conflicted, no need to abandon} @@ -1078,7 +1068,8 @@ bool CWallet::AbandonTransaction(const uint256& hashTx) void CWallet::MarkConflicted(const uint256& hashBlock, const uint256& hashTx) { - LOCK2(cs_main, cs_wallet); + auto locked_chain = chain().lock(); + LOCK(cs_wallet); int conflictconfirms = 0; CBlockIndex* pindex = LookupBlockIndex(hashBlock); @@ -1107,7 +1098,7 @@ void CWallet::MarkConflicted(const uint256& hashBlock, const uint256& hashTx) auto it = mapWallet.find(now); assert(it != mapWallet.end()); CWalletTx& wtx = it->second; - int currentconfirm = wtx.GetDepthInMainChain(); + int currentconfirm = wtx.GetDepthInMainChain(*locked_chain); if (conflictconfirms < currentconfirm) { // Block is 'more conflicted' than current confirm; update. // Mark transaction as conflicted with this block. @@ -1141,7 +1132,8 @@ void CWallet::SyncTransaction(const CTransactionRef& ptx, const CBlockIndex *pin } void CWallet::TransactionAddedToMempool(const CTransactionRef& ptx) { - LOCK2(cs_main, cs_wallet); + auto locked_chain = chain().lock(); + LOCK(cs_wallet); SyncTransaction(ptx); auto it = mapWallet.find(ptx->GetHash()); @@ -1159,7 +1151,8 @@ void CWallet::TransactionRemovedFromMempool(const CTransactionRef &ptx) { } void CWallet::BlockConnected(const std::shared_ptr<const CBlock>& pblock, const CBlockIndex *pindex, const std::vector<CTransactionRef>& vtxConflicted) { - LOCK2(cs_main, cs_wallet); + auto locked_chain = chain().lock(); + LOCK(cs_wallet); // TODO: Temporarily ensure that mempool removals are notified before // connected transactions. This shouldn't matter, but the abandoned // state of transactions in our wallet is currently cleared when we @@ -1181,7 +1174,8 @@ void CWallet::BlockConnected(const std::shared_ptr<const CBlock>& pblock, const } void CWallet::BlockDisconnected(const std::shared_ptr<const CBlock>& pblock) { - LOCK2(cs_main, cs_wallet); + auto locked_chain = chain().lock(); + LOCK(cs_wallet); for (const CTransactionRef& ptx : pblock->vtx) { SyncTransaction(ptx); @@ -1200,7 +1194,7 @@ void CWallet::BlockUntilSyncedToCurrentChain() { // We could also take cs_wallet here, and call m_last_block_processed // protected by cs_wallet instead of cs_main, but as long as we need // cs_main here anyway, it's easier to just call it cs_main-protected. - LOCK(cs_main); + auto locked_chain = chain().lock(); const CBlockIndex* initialChainTip = chainActive.Tip(); if (m_last_block_processed && m_last_block_processed->GetAncestor(initialChainTip->nHeight) == initialChainTip) { @@ -1262,6 +1256,11 @@ CAmount CWallet::GetCredit(const CTxOut& txout, const isminefilter& filter) cons bool CWallet::IsChange(const CTxOut& txout) const { + return IsChange(txout.scriptPubKey); +} + +bool CWallet::IsChange(const CScript& script) const +{ // TODO: fix handling of 'change' outputs. The assumption is that any // payment to a script that is ours, but is not in the address book // is change. That assumption is likely to break when we implement multisignature @@ -1269,10 +1268,10 @@ bool CWallet::IsChange(const CTxOut& txout) const // a better way of identifying which outputs are 'the send' and which are // 'the change' will need to be implemented (maybe extend CWalletTx to remember // which output, if any, was change). - if (::IsMine(*this, txout.scriptPubKey)) + if (::IsMine(*this, script)) { CTxDestination address; - if (!ExtractDestination(txout.scriptPubKey, address)) + if (!ExtractDestination(script, address)) return true; LOCK(cs_wallet); @@ -1511,7 +1510,7 @@ int64_t CalculateMaximumSignedTxSize(const CTransaction &tx, const CWallet *wall // implies that we can sign for every input. return -1; } - return GetVirtualTransactionSize(txNew); + return GetVirtualTransactionSize(CTransaction(txNew)); } int CalculateMaximumSignedInputSize(const CTxOut& txout, const CWallet* wallet, bool use_max_sig) @@ -1519,8 +1518,6 @@ int CalculateMaximumSignedInputSize(const CTxOut& txout, const CWallet* wallet, CMutableTransaction txn; txn.vin.push_back(CTxIn(COutPoint())); if (!wallet->DummySignInput(txn.vin[0], txout, use_max_sig)) { - // This should never happen, because IsAllFromMe(ISMINE_SPENDABLE) - // implies that we can sign for every input. return -1; } return GetVirtualTransactionInputSize(txn.vin[0]); @@ -1596,14 +1593,15 @@ int64_t CWallet::RescanFromTime(int64_t startTime, const WalletRescanReserver& r // to be scanned. CBlockIndex* startBlock = nullptr; { - LOCK(cs_main); + auto locked_chain = chain().lock(); startBlock = chainActive.FindEarliestAtLeast(startTime - TIMESTAMP_WINDOW); WalletLogPrintf("%s: Rescanning last %i blocks\n", __func__, startBlock ? chainActive.Height() - startBlock->nHeight + 1 : 0); } if (startBlock) { - const CBlockIndex* const failedBlock = ScanForWalletTransactions(startBlock, nullptr, reserver, update); - if (failedBlock) { + const CBlockIndex *failedBlock, *stop_block; + // TODO: this should take into account failure by ScanResult::USER_ABORT + if (ScanResult::FAILURE == ScanForWalletTransactions(startBlock, nullptr, reserver, failedBlock, stop_block, update)) { return failedBlock->GetBlockTimeMax() + TIMESTAMP_WINDOW + 1; } } @@ -1615,18 +1613,22 @@ int64_t CWallet::RescanFromTime(int64_t startTime, const WalletRescanReserver& r * from or to us. If fUpdate is true, found transactions that already * exist in the wallet will be updated. * - * Returns null if scan was successful. Otherwise, if a complete rescan was not - * possible (due to pruning or corruption), returns pointer to the most recent - * block that could not be scanned. + * @param[in] pindexStop if not a nullptr, the scan will stop at this block-index + * @param[out] failed_block if FAILURE is returned, the most recent block + * that could not be scanned, otherwise nullptr + * @param[out] stop_block the most recent block that could be scanned, + * otherwise nullptr if no block could be scanned * - * If pindexStop is not a nullptr, the scan will stop at the block-index - * defined by pindexStop + * @return ScanResult indicating success or failure of the scan. SUCCESS if + * scan was successful. FAILURE if a complete rescan was not possible (due to + * pruning or corruption). USER_ABORT if the rescan was aborted before it + * could complete. * - * Caller needs to make sure pindexStop (and the optional pindexStart) are on + * @pre Caller needs to make sure pindexStop (and the optional pindexStart) are on * the main chain after to the addition of any new keys you want to detect * transactions for. */ -CBlockIndex* CWallet::ScanForWalletTransactions(CBlockIndex* pindexStart, CBlockIndex* pindexStop, const WalletRescanReserver &reserver, bool fUpdate) +CWallet::ScanResult CWallet::ScanForWalletTransactions(const CBlockIndex* const pindexStart, const CBlockIndex* const pindexStop, const WalletRescanReserver& reserver, const CBlockIndex*& failed_block, const CBlockIndex*& stop_block, bool fUpdate) { int64_t nNow = GetTime(); const CChainParams& chainParams = Params(); @@ -1636,8 +1638,9 @@ CBlockIndex* CWallet::ScanForWalletTransactions(CBlockIndex* pindexStart, CBlock assert(pindexStop->nHeight >= pindexStart->nHeight); } - CBlockIndex* pindex = pindexStart; - CBlockIndex* ret = nullptr; + const CBlockIndex* pindex = pindexStart; + failed_block = nullptr; + stop_block = nullptr; if (pindex) WalletLogPrintf("Rescan started from block %d...\n", pindex->nHeight); @@ -1648,7 +1651,7 @@ CBlockIndex* CWallet::ScanForWalletTransactions(CBlockIndex* pindexStart, CBlock double progress_begin; double progress_end; { - LOCK(cs_main); + auto locked_chain = chain().lock(); progress_begin = GuessVerificationProgress(chainParams.TxData(), pindex); if (pindexStop == nullptr) { tip = chainActive.Tip(); @@ -1658,8 +1661,7 @@ CBlockIndex* CWallet::ScanForWalletTransactions(CBlockIndex* pindexStart, CBlock } } double progress_current = progress_begin; - while (pindex && !fAbortRescan && !ShutdownRequested()) - { + while (pindex && !fAbortRescan && !ShutdownRequested()) { if (pindex->nHeight % 100 == 0 && progress_end - progress_begin > 0.0) { ShowProgress(strprintf("%s " + _("Rescanning..."), GetDisplayName()), std::max(1, std::min(99, (int)((progress_current - progress_begin) / (progress_end - progress_begin) * 100)))); } @@ -1670,24 +1672,28 @@ CBlockIndex* CWallet::ScanForWalletTransactions(CBlockIndex* pindexStart, CBlock CBlock block; if (ReadBlockFromDisk(block, pindex, Params().GetConsensus())) { - LOCK2(cs_main, cs_wallet); + auto locked_chain = chain().lock(); + LOCK(cs_wallet); if (pindex && !chainActive.Contains(pindex)) { // Abort scan if current block is no longer active, to prevent // marking transactions as coming from the wrong block. - ret = pindex; + failed_block = pindex; break; } for (size_t posInBlock = 0; posInBlock < block.vtx.size(); ++posInBlock) { SyncTransaction(block.vtx[posInBlock], pindex, posInBlock, fUpdate); } + // scan succeeded, record block as most recent successfully scanned + stop_block = pindex; } else { - ret = pindex; + // could not scan block, keep scanning but record this block as the most recent failure + failed_block = pindex; } if (pindex == pindexStop) { break; } { - LOCK(cs_main); + auto locked_chain = chain().lock(); pindex = chainActive.Next(pindex); progress_current = GuessVerificationProgress(chainParams.TxData(), pindex); if (pindexStop == nullptr && tip != chainActive.Tip()) { @@ -1697,14 +1703,20 @@ CBlockIndex* CWallet::ScanForWalletTransactions(CBlockIndex* pindexStart, CBlock } } } + ShowProgress(strprintf("%s " + _("Rescanning..."), GetDisplayName()), 100); // hide progress dialog in GUI if (pindex && fAbortRescan) { WalletLogPrintf("Rescan aborted at block %d. Progress=%f\n", pindex->nHeight, progress_current); + return ScanResult::USER_ABORT; } else if (pindex && ShutdownRequested()) { WalletLogPrintf("Rescan interrupted by shutdown request at block %d. Progress=%f\n", pindex->nHeight, progress_current); + return ScanResult::USER_ABORT; } - ShowProgress(strprintf("%s " + _("Rescanning..."), GetDisplayName()), 100); // hide progress dialog in GUI } - return ret; + if (failed_block) { + return ScanResult::FAILURE; + } else { + return ScanResult::SUCCESS; + } } void CWallet::ReacceptWalletTransactions() @@ -1712,7 +1724,8 @@ void CWallet::ReacceptWalletTransactions() // If transactions aren't being broadcasted, don't let them into local mempool either if (!fBroadcastTransactions) return; - LOCK2(cs_main, cs_wallet); + auto locked_chain = chain().lock(); + LOCK(cs_wallet); std::map<int64_t, CWalletTx*> mapSorted; // Sort pending wallet transactions based on their initial wallet insertion order @@ -1722,7 +1735,7 @@ void CWallet::ReacceptWalletTransactions() CWalletTx& wtx = item.second; assert(wtx.GetHash() == wtxid); - int nDepth = wtx.GetDepthInMainChain(); + int nDepth = wtx.GetDepthInMainChain(*locked_chain); if (!wtx.IsCoinBase() && (nDepth == 0 && !wtx.isAbandoned())) { mapSorted.insert(std::make_pair(wtx.nOrderPos, &wtx)); @@ -1733,18 +1746,18 @@ void CWallet::ReacceptWalletTransactions() for (const std::pair<const int64_t, CWalletTx*>& item : mapSorted) { CWalletTx& wtx = *(item.second); CValidationState state; - wtx.AcceptToMemoryPool(maxTxFee, state); + wtx.AcceptToMemoryPool(*locked_chain, maxTxFee, state); } } -bool CWalletTx::RelayWalletTransaction(CConnman* connman) +bool CWalletTx::RelayWalletTransaction(interfaces::Chain::Lock& locked_chain, CConnman* connman) { assert(pwallet->GetBroadcastTransactions()); - if (!IsCoinBase() && !isAbandoned() && GetDepthInMainChain() == 0) + if (!IsCoinBase() && !isAbandoned() && GetDepthInMainChain(locked_chain) == 0) { CValidationState state; /* GetDepthInMainChain already catches known conflicts. */ - if (InMempool() || AcceptToMemoryPool(maxTxFee, state)) { + if (InMempool() || AcceptToMemoryPool(locked_chain, maxTxFee, state)) { pwallet->WalletLogPrintf("Relaying wtx %s\n", GetHash().ToString()); if (connman) { CInv inv(MSG_TX, GetHash()); @@ -1802,10 +1815,10 @@ CAmount CWalletTx::GetDebit(const isminefilter& filter) const return debit; } -CAmount CWalletTx::GetCredit(const isminefilter& filter) const +CAmount CWalletTx::GetCredit(interfaces::Chain::Lock& locked_chain, const isminefilter& filter) const { // Must wait until coinbase is safely deep enough in the chain before valuing it - if (IsImmatureCoinBase()) + if (IsImmatureCoinBase(locked_chain)) return 0; CAmount credit = 0; @@ -1835,9 +1848,9 @@ CAmount CWalletTx::GetCredit(const isminefilter& filter) const return credit; } -CAmount CWalletTx::GetImmatureCredit(bool fUseCache) const +CAmount CWalletTx::GetImmatureCredit(interfaces::Chain::Lock& locked_chain, bool fUseCache) const { - if (IsImmatureCoinBase() && IsInMainChain()) { + if (IsImmatureCoinBase(locked_chain) && IsInMainChain(locked_chain)) { if (fUseCache && fImmatureCreditCached) return nImmatureCreditCached; nImmatureCreditCached = pwallet->GetCredit(*tx, ISMINE_SPENDABLE); @@ -1848,13 +1861,13 @@ CAmount CWalletTx::GetImmatureCredit(bool fUseCache) const return 0; } -CAmount CWalletTx::GetAvailableCredit(bool fUseCache, const isminefilter& filter) const +CAmount CWalletTx::GetAvailableCredit(interfaces::Chain::Lock& locked_chain, bool fUseCache, const isminefilter& filter) const { if (pwallet == nullptr) return 0; // Must wait until coinbase is safely deep enough in the chain before valuing it - if (IsImmatureCoinBase()) + if (IsImmatureCoinBase(locked_chain)) return 0; CAmount* cache = nullptr; @@ -1876,7 +1889,7 @@ CAmount CWalletTx::GetAvailableCredit(bool fUseCache, const isminefilter& filter uint256 hashTx = GetHash(); for (unsigned int i = 0; i < tx->vout.size(); i++) { - if (!pwallet->IsSpent(hashTx, i)) + if (!pwallet->IsSpent(locked_chain, hashTx, i)) { const CTxOut &txout = tx->vout[i]; nCredit += pwallet->GetCredit(txout, filter); @@ -1893,9 +1906,9 @@ CAmount CWalletTx::GetAvailableCredit(bool fUseCache, const isminefilter& filter return nCredit; } -CAmount CWalletTx::GetImmatureWatchOnlyCredit(const bool fUseCache) const +CAmount CWalletTx::GetImmatureWatchOnlyCredit(interfaces::Chain::Lock& locked_chain, const bool fUseCache) const { - if (IsImmatureCoinBase() && IsInMainChain()) { + if (IsImmatureCoinBase(locked_chain) && IsInMainChain(locked_chain)) { if (fUseCache && fImmatureWatchCreditCached) return nImmatureWatchCreditCached; nImmatureWatchCreditCached = pwallet->GetCredit(*tx, ISMINE_WATCH_ONLY); @@ -1920,12 +1933,14 @@ bool CWalletTx::InMempool() const return fInMempool; } -bool CWalletTx::IsTrusted() const +bool CWalletTx::IsTrusted(interfaces::Chain::Lock& locked_chain) const { + LockAnnotation lock(::cs_main); // Temporary, for CheckFinalTx below. Removed in upcoming commit. + // Quick answer in most cases if (!CheckFinalTx(*tx)) return false; - int nDepth = GetDepthInMainChain(); + int nDepth = GetDepthInMainChain(locked_chain); if (nDepth >= 1) return true; if (nDepth < 0) @@ -1960,7 +1975,7 @@ bool CWalletTx::IsEquivalentTo(const CWalletTx& _tx) const return CTransaction(tx1) == CTransaction(tx2); } -std::vector<uint256> CWallet::ResendWalletTransactionsBefore(int64_t nTime, CConnman* connman) +std::vector<uint256> CWallet::ResendWalletTransactionsBefore(interfaces::Chain::Lock& locked_chain, int64_t nTime, CConnman* connman) { std::vector<uint256> result; @@ -1979,7 +1994,7 @@ std::vector<uint256> CWallet::ResendWalletTransactionsBefore(int64_t nTime, CCon for (const std::pair<const unsigned int, CWalletTx*>& item : mapSorted) { CWalletTx& wtx = *item.second; - if (wtx.RelayWalletTransaction(connman)) + if (wtx.RelayWalletTransaction(locked_chain, connman)) result.push_back(wtx.GetHash()); } return result; @@ -2003,7 +2018,8 @@ void CWallet::ResendWalletTransactions(int64_t nBestBlockTime, CConnman* connman // Rebroadcast unconfirmed txes older than 5 minutes before the last // block was found: - std::vector<uint256> relayed = ResendWalletTransactionsBefore(nBestBlockTime-5*60, connman); + auto locked_chain = chain().assumeLocked(); // Temporary. Removed in upcoming lock cleanup + std::vector<uint256> relayed = ResendWalletTransactionsBefore(*locked_chain, nBestBlockTime-5*60, connman); if (!relayed.empty()) WalletLogPrintf("%s: rebroadcast %u unconfirmed transactions\n", __func__, relayed.size()); } @@ -2023,12 +2039,13 @@ CAmount CWallet::GetBalance(const isminefilter& filter, const int min_depth) con { CAmount nTotal = 0; { - LOCK2(cs_main, cs_wallet); + auto locked_chain = chain().lock(); + LOCK(cs_wallet); for (const auto& entry : mapWallet) { const CWalletTx* pcoin = &entry.second; - if (pcoin->IsTrusted() && pcoin->GetDepthInMainChain() >= min_depth) { - nTotal += pcoin->GetAvailableCredit(true, filter); + if (pcoin->IsTrusted(*locked_chain) && pcoin->GetDepthInMainChain(*locked_chain) >= min_depth) { + nTotal += pcoin->GetAvailableCredit(*locked_chain, true, filter); } } } @@ -2040,12 +2057,13 @@ CAmount CWallet::GetUnconfirmedBalance() const { CAmount nTotal = 0; { - LOCK2(cs_main, cs_wallet); + auto locked_chain = chain().lock(); + LOCK(cs_wallet); for (const auto& entry : mapWallet) { const CWalletTx* pcoin = &entry.second; - if (!pcoin->IsTrusted() && pcoin->GetDepthInMainChain() == 0 && pcoin->InMempool()) - nTotal += pcoin->GetAvailableCredit(); + if (!pcoin->IsTrusted(*locked_chain) && pcoin->GetDepthInMainChain(*locked_chain) == 0 && pcoin->InMempool()) + nTotal += pcoin->GetAvailableCredit(*locked_chain); } } return nTotal; @@ -2055,11 +2073,12 @@ CAmount CWallet::GetImmatureBalance() const { CAmount nTotal = 0; { - LOCK2(cs_main, cs_wallet); + auto locked_chain = chain().lock(); + LOCK(cs_wallet); for (const auto& entry : mapWallet) { const CWalletTx* pcoin = &entry.second; - nTotal += pcoin->GetImmatureCredit(); + nTotal += pcoin->GetImmatureCredit(*locked_chain); } } return nTotal; @@ -2069,12 +2088,13 @@ CAmount CWallet::GetUnconfirmedWatchOnlyBalance() const { CAmount nTotal = 0; { - LOCK2(cs_main, cs_wallet); + auto locked_chain = chain().lock(); + LOCK(cs_wallet); for (const auto& entry : mapWallet) { const CWalletTx* pcoin = &entry.second; - if (!pcoin->IsTrusted() && pcoin->GetDepthInMainChain() == 0 && pcoin->InMempool()) - nTotal += pcoin->GetAvailableCredit(true, ISMINE_WATCH_ONLY); + if (!pcoin->IsTrusted(*locked_chain) && pcoin->GetDepthInMainChain(*locked_chain) == 0 && pcoin->InMempool()) + nTotal += pcoin->GetAvailableCredit(*locked_chain, true, ISMINE_WATCH_ONLY); } } return nTotal; @@ -2084,11 +2104,12 @@ CAmount CWallet::GetImmatureWatchOnlyBalance() const { CAmount nTotal = 0; { - LOCK2(cs_main, cs_wallet); + auto locked_chain = chain().lock(); + LOCK(cs_wallet); for (const auto& entry : mapWallet) { const CWalletTx* pcoin = &entry.second; - nTotal += pcoin->GetImmatureWatchOnlyCredit(); + nTotal += pcoin->GetImmatureWatchOnlyCredit(*locked_chain); } } return nTotal; @@ -2102,13 +2123,15 @@ CAmount CWallet::GetImmatureWatchOnlyBalance() const // trusted. CAmount CWallet::GetLegacyBalance(const isminefilter& filter, int minDepth) const { - LOCK2(cs_main, cs_wallet); + LockAnnotation lock(::cs_main); // Temporary, for CheckFinalTx below. Removed in upcoming commit. + auto locked_chain = chain().lock(); + LOCK(cs_wallet); CAmount balance = 0; for (const auto& entry : mapWallet) { const CWalletTx& wtx = entry.second; - const int depth = wtx.GetDepthInMainChain(); - if (depth < 0 || !CheckFinalTx(*wtx.tx) || wtx.IsImmatureCoinBase()) { + const int depth = wtx.GetDepthInMainChain(*locked_chain); + if (depth < 0 || !CheckFinalTx(*wtx.tx) || wtx.IsImmatureCoinBase(*locked_chain)) { continue; } @@ -2135,11 +2158,12 @@ CAmount CWallet::GetLegacyBalance(const isminefilter& filter, int minDepth) cons CAmount CWallet::GetAvailableBalance(const CCoinControl* coinControl) const { - LOCK2(cs_main, cs_wallet); + auto locked_chain = chain().lock(); + LOCK(cs_wallet); CAmount balance = 0; std::vector<COutput> vCoins; - AvailableCoins(vCoins, true, coinControl); + AvailableCoins(*locked_chain, vCoins, true, coinControl); for (const COutput& out : vCoins) { if (out.fSpendable) { balance += out.tx->tx->vout[out.i].nValue; @@ -2148,7 +2172,7 @@ CAmount CWallet::GetAvailableBalance(const CCoinControl* coinControl) const return balance; } -void CWallet::AvailableCoins(std::vector<COutput> &vCoins, bool fOnlySafe, const CCoinControl *coinControl, const CAmount &nMinimumAmount, const CAmount &nMaximumAmount, const CAmount &nMinimumSumAmount, const uint64_t nMaximumCount, const int nMinDepth, const int nMaxDepth) const +void CWallet::AvailableCoins(interfaces::Chain::Lock& locked_chain, std::vector<COutput> &vCoins, bool fOnlySafe, const CCoinControl *coinControl, const CAmount &nMinimumAmount, const CAmount &nMaximumAmount, const CAmount &nMinimumSumAmount, const uint64_t nMaximumCount, const int nMinDepth, const int nMaxDepth) const { AssertLockHeld(cs_main); AssertLockHeld(cs_wallet); @@ -2164,10 +2188,10 @@ void CWallet::AvailableCoins(std::vector<COutput> &vCoins, bool fOnlySafe, const if (!CheckFinalTx(*pcoin->tx)) continue; - if (pcoin->IsImmatureCoinBase()) + if (pcoin->IsImmatureCoinBase(locked_chain)) continue; - int nDepth = pcoin->GetDepthInMainChain(); + int nDepth = pcoin->GetDepthInMainChain(locked_chain); if (nDepth < 0) continue; @@ -2176,7 +2200,7 @@ void CWallet::AvailableCoins(std::vector<COutput> &vCoins, bool fOnlySafe, const if (nDepth == 0 && !pcoin->InMempool()) continue; - bool safeTx = pcoin->IsTrusted(); + bool safeTx = pcoin->IsTrusted(locked_chain); // We should not consider coins from transactions that are replacing // other transactions. @@ -2226,7 +2250,7 @@ void CWallet::AvailableCoins(std::vector<COutput> &vCoins, bool fOnlySafe, const if (IsLockedCoin(entry.first, i)) continue; - if (IsSpent(wtxid, i)) + if (IsSpent(locked_chain, wtxid, i)) continue; isminetype mine = IsMine(pcoin->tx->vout[i]); @@ -2257,7 +2281,7 @@ void CWallet::AvailableCoins(std::vector<COutput> &vCoins, bool fOnlySafe, const } } -std::map<CTxDestination, std::vector<COutput>> CWallet::ListCoins() const +std::map<CTxDestination, std::vector<COutput>> CWallet::ListCoins(interfaces::Chain::Lock& locked_chain) const { AssertLockHeld(cs_main); AssertLockHeld(cs_wallet); @@ -2265,7 +2289,7 @@ std::map<CTxDestination, std::vector<COutput>> CWallet::ListCoins() const std::map<CTxDestination, std::vector<COutput>> result; std::vector<COutput> availableCoins; - AvailableCoins(availableCoins); + AvailableCoins(locked_chain, availableCoins); for (const COutput& coin : availableCoins) { CTxDestination address; @@ -2280,7 +2304,7 @@ std::map<CTxDestination, std::vector<COutput>> CWallet::ListCoins() const for (const COutPoint& output : lockedCoins) { auto it = mapWallet.find(output.hash); if (it != mapWallet.end()) { - int depth = it->second.GetDepthInMainChain(); + int depth = it->second.GetDepthInMainChain(locked_chain); if (depth >= 0 && output.n < it->second.tx->vout.size() && IsMine(it->second.tx->vout[output.n]) == ISMINE_SPENDABLE) { CTxDestination address; @@ -2427,7 +2451,7 @@ bool CWallet::SelectCoins(const std::vector<COutput>& vAvailableCoins, const CAm // Cases where we have 11+ outputs all pointing to the same destination may result in // privacy leaks as they will potentially be deterministically sorted. We solve that by // explicitly shuffling the outputs before processing - std::shuffle(vCoins.begin(), vCoins.end(), FastRandomContext()); + Shuffle(vCoins.begin(), vCoins.end(), FastRandomContext()); } std::vector<OutputGroup> groups = GroupOutputs(vCoins, !coin_control.m_avoid_partial_spends); @@ -2495,11 +2519,12 @@ bool CWallet::FundTransaction(CMutableTransaction& tx, CAmount& nFeeRet, int& nC // Acquire the locks to prevent races to the new locked unspents between the // CreateTransaction call and LockCoin calls (when lockUnspents is true). - LOCK2(cs_main, cs_wallet); + auto locked_chain = chain().lock(); + LOCK(cs_wallet); CReserveKey reservekey(this); CTransactionRef tx_new; - if (!CreateTransaction(vecSend, tx_new, reservekey, nFeeRet, nChangePosInOut, strFailReason, coinControl, false)) { + if (!CreateTransaction(*locked_chain, vecSend, tx_new, reservekey, nFeeRet, nChangePosInOut, strFailReason, coinControl, false)) { return false; } @@ -2530,6 +2555,65 @@ bool CWallet::FundTransaction(CMutableTransaction& tx, CAmount& nFeeRet, int& nC return true; } +static bool IsCurrentForAntiFeeSniping(interfaces::Chain::Lock& locked_chain) +{ + if (IsInitialBlockDownload()) { + return false; + } + constexpr int64_t MAX_ANTI_FEE_SNIPING_TIP_AGE = 8 * 60 * 60; // in seconds + if (chainActive.Tip()->GetBlockTime() < (GetTime() - MAX_ANTI_FEE_SNIPING_TIP_AGE)) { + return false; + } + return true; +} + +/** + * Return a height-based locktime for new transactions (uses the height of the + * current chain tip unless we are not synced with the current chain + */ +static uint32_t GetLocktimeForNewTransaction(interfaces::Chain::Lock& locked_chain) +{ + uint32_t locktime; + // Discourage fee sniping. + // + // For a large miner the value of the transactions in the best block and + // the mempool can exceed the cost of deliberately attempting to mine two + // blocks to orphan the current best block. By setting nLockTime such that + // only the next block can include the transaction, we discourage this + // practice as the height restricted and limited blocksize gives miners + // considering fee sniping fewer options for pulling off this attack. + // + // A simple way to think about this is from the wallet's point of view we + // always want the blockchain to move forward. By setting nLockTime this + // way we're basically making the statement that we only want this + // transaction to appear in the next block; we don't want to potentially + // encourage reorgs by allowing transactions to appear at lower heights + // than the next block in forks of the best chain. + // + // Of course, the subsidy is high enough, and transaction volume low + // enough, that fee sniping isn't a problem yet, but by implementing a fix + // now we ensure code won't be written that makes assumptions about + // nLockTime that preclude a fix later. + if (IsCurrentForAntiFeeSniping(locked_chain)) { + locktime = chainActive.Height(); + + // Secondly occasionally randomly pick a nLockTime even further back, so + // that transactions that are delayed after signing for whatever reason, + // e.g. high-latency mix networks and some CoinJoin implementations, have + // better privacy. + if (GetRandInt(10) == 0) + locktime = std::max(0, (int)locktime - GetRandInt(100)); + } else { + // If our chain is lagging behind, we can't discourage fee sniping nor help + // the privacy of high-latency transactions. To avoid leaking a potentially + // unique "nLockTime fingerprint", set nLockTime to a constant. + locktime = 0; + } + assert(locktime <= (unsigned int)chainActive.Height()); + assert(locktime < LOCKTIME_THRESHOLD); + return locktime; +} + OutputType CWallet::TransactionChangeType(OutputType change_type, const std::vector<CRecipient>& vecSend) { // If -changetype is specified, always use that change type. @@ -2558,7 +2642,7 @@ OutputType CWallet::TransactionChangeType(OutputType change_type, const std::vec return m_default_address_type; } -bool CWallet::CreateTransaction(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, CReserveKey& reservekey, CAmount& nFeeRet, int& nChangePosInOut, std::string& strFailReason, const CCoinControl& coin_control, bool sign) { CAmount nValue = 0; @@ -2584,46 +2668,18 @@ bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CTransac CMutableTransaction txNew; - // Discourage fee sniping. - // - // For a large miner the value of the transactions in the best block and - // the mempool can exceed the cost of deliberately attempting to mine two - // blocks to orphan the current best block. By setting nLockTime such that - // only the next block can include the transaction, we discourage this - // practice as the height restricted and limited blocksize gives miners - // considering fee sniping fewer options for pulling off this attack. - // - // A simple way to think about this is from the wallet's point of view we - // always want the blockchain to move forward. By setting nLockTime this - // way we're basically making the statement that we only want this - // transaction to appear in the next block; we don't want to potentially - // encourage reorgs by allowing transactions to appear at lower heights - // than the next block in forks of the best chain. - // - // Of course, the subsidy is high enough, and transaction volume low - // enough, that fee sniping isn't a problem yet, but by implementing a fix - // now we ensure code won't be written that makes assumptions about - // nLockTime that preclude a fix later. - txNew.nLockTime = chainActive.Height(); - - // Secondly occasionally randomly pick a nLockTime even further back, so - // that transactions that are delayed after signing for whatever reason, - // e.g. high-latency mix networks and some CoinJoin implementations, have - // better privacy. - if (GetRandInt(10) == 0) - txNew.nLockTime = std::max(0, (int)txNew.nLockTime - GetRandInt(100)); + txNew.nLockTime = GetLocktimeForNewTransaction(locked_chain); - assert(txNew.nLockTime <= (unsigned int)chainActive.Height()); - assert(txNew.nLockTime < LOCKTIME_THRESHOLD); FeeCalculation feeCalc; CAmount nFeeNeeded; int nBytes; { std::set<CInputCoin> setCoins; - LOCK2(cs_main, cs_wallet); + auto locked_chain = chain().lock(); + LOCK(cs_wallet); { std::vector<COutput> vAvailableCoins; - AvailableCoins(vAvailableCoins, true, &coin_control); + AvailableCoins(*locked_chain, vAvailableCoins, true, &coin_control); CoinSelectionParams coin_selection_params; // Parameters for coin selection, init with dummy // Create change script that will be used if we need change @@ -2729,7 +2785,14 @@ bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CTransac if (pick_new_inputs) { nValueIn = 0; setCoins.clear(); - coin_selection_params.change_spend_size = CalculateMaximumSignedInputSize(change_prototype_txout, this); + int change_spend_size = CalculateMaximumSignedInputSize(change_prototype_txout, this); + // If the wallet doesn't know how to sign change output, assume p2sh-p2wpkh + // as lower-bound to allow BnB to do it's thing + if (change_spend_size == -1) { + coin_selection_params.change_spend_size = DUMMY_NESTED_P2WPKH_INPUT_SIZE; + } else { + coin_selection_params.change_spend_size = (size_t)change_spend_size; + } coin_selection_params.effective_fee = nFeeRateNeeded; if (!SelectCoins(vAvailableCoins, nValueToSelect, setCoins, nValueIn, coin_control, coin_selection_params, bnb_used)) { @@ -2787,7 +2850,7 @@ bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CTransac txNew.vin.push_back(CTxIn(coin.outpoint,CScript())); } - nBytes = CalculateMaximumSignedTxSize(txNew, this, coin_control.fAllowWatchOnly); + nBytes = CalculateMaximumSignedTxSize(CTransaction(txNew), this, coin_control.fAllowWatchOnly); if (nBytes < 0) { strFailReason = _("Signing transaction failed"); return false; @@ -2878,7 +2941,7 @@ bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CTransac // Shuffle selected coins and fill in final vin txNew.vin.clear(); std::vector<CInputCoin> selected_coins(setCoins.begin(), setCoins.end()); - std::shuffle(selected_coins.begin(), selected_coins.end(), FastRandomContext()); + Shuffle(selected_coins.begin(), selected_coins.end(), FastRandomContext()); // Note how the sequence number is set to non-maxint so that // the nLockTime set above actually works. @@ -2958,7 +3021,8 @@ bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CTransac bool CWallet::CommitTransaction(CTransactionRef tx, mapValue_t mapValue, std::vector<std::pair<std::string, std::string>> orderForm, CReserveKey& reservekey, CConnman* connman, CValidationState& state) { { - LOCK2(cs_main, cs_wallet); + auto locked_chain = chain().lock(); + LOCK(cs_wallet); CWalletTx wtxNew(this, std::move(tx)); wtxNew.mapValue = std::move(mapValue); @@ -2991,11 +3055,11 @@ bool CWallet::CommitTransaction(CTransactionRef tx, mapValue_t mapValue, std::ve if (fBroadcastTransactions) { // Broadcast - if (!wtx.AcceptToMemoryPool(maxTxFee, state)) { + if (!wtx.AcceptToMemoryPool(*locked_chain, maxTxFee, state)) { WalletLogPrintf("CommitTransaction(): Transaction cannot be broadcast immediately, %s\n", FormatStateMessage(state)); // TODO: if we expect the failure to be long term or permanent, instead delete wtx from the wallet and return failure. } else { - wtx.RelayWalletTransaction(connman); + wtx.RelayWalletTransaction(*locked_chain, connman); } } } @@ -3004,7 +3068,8 @@ bool CWallet::CommitTransaction(CTransactionRef tx, mapValue_t mapValue, std::ve DBErrors CWallet::LoadWallet(bool& fFirstRunRet) { - LOCK2(cs_main, cs_wallet); + auto locked_chain = chain().lock(); + LOCK(cs_wallet); fFirstRunRet = false; DBErrors nLoadWalletRet = WalletBatch(*database,"cr+").LoadWallet(this); @@ -3388,7 +3453,7 @@ int64_t CWallet::GetOldestKeyPoolTime() return oldestKey; } -std::map<CTxDestination, CAmount> CWallet::GetAddressBalances() +std::map<CTxDestination, CAmount> CWallet::GetAddressBalances(interfaces::Chain::Lock& locked_chain) { std::map<CTxDestination, CAmount> balances; @@ -3398,13 +3463,13 @@ std::map<CTxDestination, CAmount> CWallet::GetAddressBalances() { const CWalletTx *pcoin = &walletEntry.second; - if (!pcoin->IsTrusted()) + if (!pcoin->IsTrusted(locked_chain)) continue; - if (pcoin->IsImmatureCoinBase()) + if (pcoin->IsImmatureCoinBase(locked_chain)) continue; - int nDepth = pcoin->GetDepthInMainChain(); + int nDepth = pcoin->GetDepthInMainChain(locked_chain); if (nDepth < (pcoin->IsFromMe(ISMINE_ALL) ? 0 : 1)) continue; @@ -3416,7 +3481,7 @@ std::map<CTxDestination, CAmount> CWallet::GetAddressBalances() if(!ExtractDestination(pcoin->tx->vout[i].scriptPubKey, addr)) continue; - CAmount n = IsSpent(walletEntry.first, i) ? 0 : pcoin->tx->vout[i].nValue; + CAmount n = IsSpent(locked_chain, walletEntry.first, i) ? 0 : pcoin->tx->vout[i].nValue; if (!balances.count(addr)) balances[addr] = 0; @@ -3641,7 +3706,7 @@ void CWallet::ListLockedCoins(std::vector<COutPoint>& vOutpts) const /** @} */ // end of Actions -void CWallet::GetKeyBirthTimes(std::map<CTxDestination, int64_t> &mapKeyBirth) const { +void CWallet::GetKeyBirthTimes(interfaces::Chain::Lock& locked_chain, std::map<CTxDestination, int64_t> &mapKeyBirth) const { AssertLockHeld(cs_wallet); // mapKeyMetadata mapKeyBirth.clear(); @@ -3665,7 +3730,6 @@ void CWallet::GetKeyBirthTimes(std::map<CTxDestination, int64_t> &mapKeyBirth) c return; // find first block that affects those keys, if there are any left - std::vector<CKeyID> vAffected; for (const auto& entry : mapWallet) { // iterate over all wallet transactions... const CWalletTx &wtx = entry.second; @@ -3675,14 +3739,12 @@ void CWallet::GetKeyBirthTimes(std::map<CTxDestination, int64_t> &mapKeyBirth) c int nHeight = pindex->nHeight; for (const CTxOut &txout : wtx.tx->vout) { // iterate over all their outputs - CAffectedKeysVisitor(*this, vAffected).Process(txout.scriptPubKey); - for (const CKeyID &keyid : vAffected) { + for (const auto &keyid : GetAffectedKeys(txout.scriptPubKey, *this)) { // ... and all their affected keys std::map<CKeyID, CBlockIndex*>::iterator rit = mapKeyFirstBlock.find(keyid); if (rit != mapKeyFirstBlock.end() && nHeight < rit->second->nHeight) rit->second = pindex; } - vAffected.clear(); } } } @@ -3821,7 +3883,7 @@ void CWallet::MarkPreSplitKeys() } } -bool CWallet::Verify(std::string wallet_file, bool salvage_wallet, std::string& error_string, std::string& warning_string) +bool CWallet::Verify(interfaces::Chain& chain, const WalletLocation& location, bool salvage_wallet, std::string& error_string, std::string& warning_string) { // Do some checking on wallet path. It should be either a: // @@ -3830,25 +3892,23 @@ bool CWallet::Verify(std::string wallet_file, bool salvage_wallet, std::string& // 3. Path to a symlink to a directory. // 4. For backwards compatibility, the name of a data file in -walletdir. LOCK(cs_wallets); - fs::path wallet_path = fs::absolute(wallet_file, GetWalletDir()); + const fs::path& wallet_path = location.GetPath(); fs::file_type path_type = fs::symlink_status(wallet_path).type(); if (!(path_type == fs::file_not_found || path_type == fs::directory_file || (path_type == fs::symlink_file && fs::is_directory(wallet_path)) || - (path_type == fs::regular_file && fs::path(wallet_file).filename() == wallet_file))) { + (path_type == fs::regular_file && fs::path(location.GetName()).filename() == location.GetName()))) { error_string = strprintf( "Invalid -wallet path '%s'. -wallet path should point to a directory where wallet.dat and " "database/log.?????????? files can be stored, a location where such a directory could be created, " "or (for backwards compatibility) the name of an existing data file in -walletdir (%s)", - wallet_file, GetWalletDir()); + location.GetName(), GetWalletDir()); return false; } // Make sure that the wallet path doesn't clash with an existing wallet path - for (auto wallet : GetWallets()) { - if (fs::absolute(wallet->GetName(), GetWalletDir()) == wallet_path) { - error_string = strprintf("Error loading wallet %s. Duplicate -wallet filename specified.", wallet_file); - return false; - } + if (IsWalletLoaded(wallet_path)) { + error_string = strprintf("Error loading wallet %s. Duplicate -wallet filename specified.", location.GetName()); + return false; } try { @@ -3856,13 +3916,13 @@ bool CWallet::Verify(std::string wallet_file, bool salvage_wallet, std::string& return false; } } catch (const fs::filesystem_error& e) { - error_string = strprintf("Error loading wallet %s. %s", wallet_file, fsbridge::get_filesystem_error_message(e)); + error_string = strprintf("Error loading wallet %s. %s", location.GetName(), fsbridge::get_filesystem_error_message(e)); return false; } if (salvage_wallet) { // Recover readable keypairs: - CWallet dummyWallet("dummy", WalletDatabase::CreateDummy()); + CWallet dummyWallet(chain, WalletLocation(), WalletDatabase::CreateDummy()); std::string backup_filename; if (!WalletBatch::Recover(wallet_path, (void *)&dummyWallet, WalletBatch::RecoverKeysOnlyFilter, backup_filename)) { return false; @@ -3872,9 +3932,9 @@ bool CWallet::Verify(std::string wallet_file, bool salvage_wallet, std::string& return WalletBatch::VerifyDatabaseFile(wallet_path, warning_string, error_string); } -std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(const std::string& name, const fs::path& path, uint64_t wallet_creation_flags) +std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(interfaces::Chain& chain, const WalletLocation& location, uint64_t wallet_creation_flags) { - const std::string& walletFile = name; + const std::string& walletFile = location.GetName(); // needed to restore wallet transaction meta data after -zapwallettxes std::vector<CWalletTx> vWtx; @@ -3882,7 +3942,7 @@ std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(const std::string& name, if (gArgs.GetBoolArg("-zapwallettxes", false)) { uiInterface.InitMessage(_("Zapping all transactions from wallet...")); - std::unique_ptr<CWallet> tempWallet = MakeUnique<CWallet>(name, WalletDatabase::Create(path)); + std::unique_ptr<CWallet> tempWallet = MakeUnique<CWallet>(chain, location, WalletDatabase::Create(location.GetPath())); DBErrors nZapWalletRet = tempWallet->ZapWalletTx(vWtx); if (nZapWalletRet != DBErrors::LOAD_OK) { InitError(strprintf(_("Error loading %s: Wallet corrupted"), walletFile)); @@ -3896,7 +3956,7 @@ std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(const std::string& name, bool fFirstRun = true; // TODO: Can't use std::make_shared because we need a custom deleter but // should be possible to use std::allocate_shared. - std::shared_ptr<CWallet> walletInstance(new CWallet(name, WalletDatabase::Create(path)), ReleaseWallet); + std::shared_ptr<CWallet> walletInstance(new CWallet(chain, location, WalletDatabase::Create(location.GetPath())), ReleaseWallet); DBErrors nLoadWalletRet = walletInstance->LoadWallet(fFirstRun); if (nLoadWalletRet != DBErrors::LOAD_OK) { @@ -4006,6 +4066,7 @@ std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(const std::string& name, return nullptr; } + auto locked_chain = chain.assumeLocked(); // Temporary. Removed in upcoming lock cleanup walletInstance->ChainStateFlushed(chainActive.GetLocator()); } else if (wallet_creation_flags & WALLET_FLAG_DISABLE_PRIVATE_KEYS) { // Make it impossible to disable private keys after creation @@ -4093,7 +4154,9 @@ std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(const std::string& name, // Try to top up keypool. No-op if the wallet is locked. walletInstance->TopUpKeyPool(); - LOCK(cs_main); + LockAnnotation lock(::cs_main); // Temporary, for FindForkInGlobalIndex below. Removed in upcoming commit. + auto locked_chain = chain.lock(); + LOCK(walletInstance->cs_wallet); CBlockIndex *pindexRescan = chainActive.Genesis(); if (!gArgs.GetBoolArg("-rescan", false)) @@ -4135,11 +4198,11 @@ std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(const std::string& name, nStart = GetTimeMillis(); { WalletRescanReserver reserver(walletInstance.get()); - if (!reserver.reserve()) { + const CBlockIndex *stop_block, *failed_block; + if (!reserver.reserve() || (ScanResult::SUCCESS != walletInstance->ScanForWalletTransactions(pindexRescan, nullptr, reserver, failed_block, stop_block, true))) { InitError(_("Failed to rescan the wallet during initialization")); return nullptr; } - walletInstance->ScanForWalletTransactions(pindexRescan, nullptr, reserver, true); } walletInstance->WalletLogPrintf("Rescan completed in %15dms\n", GetTimeMillis() - nStart); walletInstance->ChainStateFlushed(chainActive.GetLocator()); @@ -4178,7 +4241,6 @@ std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(const std::string& name, walletInstance->SetBroadcastTransactions(gArgs.GetBoolArg("-walletbroadcast", DEFAULT_WALLETBROADCAST)); { - LOCK(walletInstance->cs_wallet); walletInstance->WalletLogPrintf("setKeyPool.size() = %u\n", walletInstance->GetKeyPoolSize()); walletInstance->WalletLogPrintf("mapWallet.size() = %u\n", walletInstance->mapWallet.size()); walletInstance->WalletLogPrintf("mapAddressBook.size() = %u\n", walletInstance->mapAddressBook.size()); @@ -4229,7 +4291,7 @@ void CMerkleTx::SetMerkleBranch(const CBlockIndex* pindex, int posInBlock) nIndex = posInBlock; } -int CMerkleTx::GetDepthInMainChain() const +int CMerkleTx::GetDepthInMainChain(interfaces::Chain::Lock& locked_chain) const { if (hashUnset()) return 0; @@ -4244,23 +4306,25 @@ int CMerkleTx::GetDepthInMainChain() const return ((nIndex == -1) ? (-1) : 1) * (chainActive.Height() - pindex->nHeight + 1); } -int CMerkleTx::GetBlocksToMaturity() const +int CMerkleTx::GetBlocksToMaturity(interfaces::Chain::Lock& locked_chain) const { if (!IsCoinBase()) return 0; - int chain_depth = GetDepthInMainChain(); + int chain_depth = GetDepthInMainChain(locked_chain); assert(chain_depth >= 0); // coinbase tx should not be conflicted return std::max(0, (COINBASE_MATURITY+1) - chain_depth); } -bool CMerkleTx::IsImmatureCoinBase() const +bool CMerkleTx::IsImmatureCoinBase(interfaces::Chain::Lock& locked_chain) const { // note GetBlocksToMaturity is 0 for non-coinbase tx - return GetBlocksToMaturity() > 0; + return GetBlocksToMaturity(locked_chain) > 0; } -bool CWalletTx::AcceptToMemoryPool(const CAmount& nAbsurdFee, CValidationState& state) +bool CWalletTx::AcceptToMemoryPool(interfaces::Chain::Lock& locked_chain, const CAmount& nAbsurdFee, CValidationState& state) { + LockAnnotation lock(::cs_main); // Temporary, for AcceptToMemoryPool below. Removed in upcoming commit. + // We must set fInMempool here - while it will be re-set to true by the // entered-mempool callback, if we did not there would be a race where a // user could call sendmoney in a loop and hit spurious out of funds errors diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index da326517c0..6872fbad2d 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -7,20 +7,21 @@ #define BITCOIN_WALLET_WALLET_H #include <amount.h> +#include <interfaces/chain.h> #include <outputtype.h> #include <policy/feerate.h> #include <streams.h> #include <tinyformat.h> #include <ui_interface.h> -#include <utilstrencodings.h> +#include <util/strencodings.h> #include <validationinterface.h> #include <script/ismine.h> #include <script/sign.h> -#include <util.h> +#include <util/system.h> #include <wallet/crypter.h> #include <wallet/coinselection.h> #include <wallet/walletdb.h> -#include <wallet/rpcwallet.h> +#include <wallet/walletutil.h> #include <algorithm> #include <atomic> @@ -33,6 +34,33 @@ #include <utility> #include <vector> +//! Responsible for reading and validating the -wallet arguments and verifying the wallet database. +// This function will perform salvage on the wallet if requested, as long as only one wallet is +// being loaded (WalletParameterInteraction forbids -salvagewallet, -zapwallettxes or -upgradewallet with multiwallet). +bool VerifyWallets(interfaces::Chain& chain, const std::vector<std::string>& wallet_files); + +//! Load wallet databases. +bool LoadWallets(interfaces::Chain& chain, const std::vector<std::string>& wallet_files); + +//! Complete startup of wallets. +void StartWallets(CScheduler& scheduler); + +//! Flush all wallets in preparation for shutdown. +void FlushWallets(); + +//! Stop all wallets. Wallets will be flushed first. +void StopWallets(); + +//! Close all wallets. +void UnloadWallets(); + +//! Explicitly unload and delete the wallet. +// Blocks the current thread after signaling the unload intent so that all +// wallet clients release the wallet. +// Note that, when blocking is not required, the wallet is implicitly unloaded +// by the shared pointer deleter. +void UnloadWallet(std::shared_ptr<CWallet>&& wallet); + bool AddWallet(const std::shared_ptr<CWallet>& wallet); bool RemoveWallet(const std::shared_ptr<CWallet>& wallet); bool HasWallets(); @@ -64,6 +92,9 @@ static const bool DEFAULT_WALLET_RBF = false; static const bool DEFAULT_WALLETBROADCAST = true; static const bool DEFAULT_DISABLE_WALLET = false; +//! Pre-calculated constants for input size estimation in *virtual size* +static constexpr size_t DUMMY_NESTED_P2WPKH_INPUT_SIZE = 91; + class CBlockIndex; class CCoinControl; class COutput; @@ -264,22 +295,22 @@ public: * 0 : in memory pool, waiting to be included in a block * >=1 : this many blocks deep in the main chain */ - int GetDepthInMainChain() const EXCLUSIVE_LOCKS_REQUIRED(cs_main); - bool IsInMainChain() const EXCLUSIVE_LOCKS_REQUIRED(cs_main) { return GetDepthInMainChain() > 0; } + int GetDepthInMainChain(interfaces::Chain::Lock& locked_chain) const; + bool IsInMainChain(interfaces::Chain::Lock& locked_chain) const { return GetDepthInMainChain(locked_chain) > 0; } /** * @return number of blocks to maturity for this transaction: * 0 : is not a coinbase transaction, or is a mature coinbase transaction * >0 : is a coinbase transaction which matures in this many blocks */ - int GetBlocksToMaturity() const EXCLUSIVE_LOCKS_REQUIRED(cs_main); + int GetBlocksToMaturity(interfaces::Chain::Lock& locked_chain) const; bool hashUnset() const { return (hashBlock.IsNull() || hashBlock == ABANDON_HASH); } bool isAbandoned() const { return (hashBlock == ABANDON_HASH); } void setAbandoned() { hashBlock = ABANDON_HASH; } const uint256& GetHash() const { return tx->GetHash(); } bool IsCoinBase() const { return tx->IsCoinBase(); } - bool IsImmatureCoinBase() const EXCLUSIVE_LOCKS_REQUIRED(cs_main); + bool IsImmatureCoinBase(interfaces::Chain::Lock& locked_chain) const; }; //Get the marginal bytes of spending the specified output @@ -458,10 +489,14 @@ public: //! filter decides which addresses will count towards the debit CAmount GetDebit(const isminefilter& filter) const; - CAmount GetCredit(const isminefilter& filter) const EXCLUSIVE_LOCKS_REQUIRED(cs_main); - CAmount GetImmatureCredit(bool fUseCache=true) const EXCLUSIVE_LOCKS_REQUIRED(cs_main); - CAmount GetAvailableCredit(bool fUseCache=true, const isminefilter& filter=ISMINE_SPENDABLE) const EXCLUSIVE_LOCKS_REQUIRED(cs_main); - CAmount GetImmatureWatchOnlyCredit(const bool fUseCache=true) const EXCLUSIVE_LOCKS_REQUIRED(cs_main); + CAmount GetCredit(interfaces::Chain::Lock& locked_chain, const isminefilter& filter) const; + CAmount GetImmatureCredit(interfaces::Chain::Lock& locked_chain, bool fUseCache=true) const; + // TODO: Remove "NO_THREAD_SAFETY_ANALYSIS" and replace it with the correct + // annotation "EXCLUSIVE_LOCKS_REQUIRED(cs_main, pwallet->cs_wallet)". The + // annotation "NO_THREAD_SAFETY_ANALYSIS" was temporarily added to avoid + // having to resolve the issue of member access into incomplete type CWallet. + CAmount GetAvailableCredit(interfaces::Chain::Lock& locked_chain, bool fUseCache=true, const isminefilter& filter=ISMINE_SPENDABLE) const NO_THREAD_SAFETY_ANALYSIS; + CAmount GetImmatureWatchOnlyCredit(interfaces::Chain::Lock& locked_chain, const bool fUseCache=true) const; CAmount GetChange() const; // Get the marginal bytes if spending the specified output from this transaction @@ -482,17 +517,23 @@ public: bool IsEquivalentTo(const CWalletTx& tx) const; bool InMempool() const; - bool IsTrusted() const EXCLUSIVE_LOCKS_REQUIRED(cs_main); + bool IsTrusted(interfaces::Chain::Lock& locked_chain) const; int64_t GetTxTime() const; // RelayWalletTransaction may only be called if fBroadcastTransactions! - bool RelayWalletTransaction(CConnman* connman) EXCLUSIVE_LOCKS_REQUIRED(cs_main); + bool RelayWalletTransaction(interfaces::Chain::Lock& locked_chain, CConnman* connman); /** Pass this transaction to the mempool. Fails if absolute fee exceeds absurd fee. */ - bool AcceptToMemoryPool(const CAmount& nAbsurdFee, CValidationState& state) EXCLUSIVE_LOCKS_REQUIRED(cs_main); - - std::set<uint256> GetConflicts() const; + bool AcceptToMemoryPool(interfaces::Chain::Lock& locked_chain, const CAmount& nAbsurdFee, CValidationState& state); + + // TODO: Remove "NO_THREAD_SAFETY_ANALYSIS" and replace it with the correct + // annotation "EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet)". The annotation + // "NO_THREAD_SAFETY_ANALYSIS" was temporarily added to avoid having to + // resolve the issue of member access into incomplete type CWallet. Note + // that we still have the runtime check "AssertLockHeld(pwallet->cs_wallet)" + // in place. + std::set<uint256> GetConflicts() const NO_THREAD_SAFETY_ANALYSIS; }; class COutput @@ -591,13 +632,13 @@ private: std::mutex mutexScanning; friend class WalletRescanReserver; - WalletBatch *encrypted_batch = nullptr; + WalletBatch *encrypted_batch GUARDED_BY(cs_wallet) = nullptr; //! the current wallet version: clients below this version are not able to load the wallet int nWalletVersion = FEATURE_BASE; //! the maximum wallet format version: memory-only variable that specifies to what version this wallet may be upgraded - int nWalletMaxVersion = FEATURE_BASE; + int nWalletMaxVersion GUARDED_BY(cs_wallet) = FEATURE_BASE; int64_t nNextResend = 0; int64_t nLastResend = 0; @@ -609,9 +650,9 @@ private: * mutated transactions where the mutant gets mined). */ typedef std::multimap<COutPoint, uint256> TxSpends; - TxSpends mapTxSpends; - void AddToSpends(const COutPoint& outpoint, const uint256& wtxid); - void AddToSpends(const uint256& wtxid); + TxSpends mapTxSpends GUARDED_BY(cs_wallet); + void AddToSpends(const COutPoint& outpoint, const uint256& wtxid) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + void AddToSpends(const uint256& wtxid) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); /** * Add a transaction to the wallet, or update it. pIndex and posInBlock should @@ -632,9 +673,9 @@ private: void MarkConflicted(const uint256& hashBlock, const uint256& hashTx); /* Mark a transaction's inputs dirty, thus forcing the outputs to be recomputed */ - void MarkInputsDirty(const CTransactionRef& tx); + void MarkInputsDirty(const CTransactionRef& tx) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); - void SyncMetaData(std::pair<TxSpends::iterator, TxSpends::iterator>); + void SyncMetaData(std::pair<TxSpends::iterator, TxSpends::iterator>) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); /* Used by TransactionAddedToMemorypool/BlockConnected/Disconnected/ScanForWalletTransactions. * Should be called with pindexBlock and posInBlock if this is for a transaction that is included in a block. */ @@ -647,13 +688,13 @@ private: void DeriveNewChildKey(WalletBatch &batch, CKeyMetadata& metadata, CKey& secret, bool internal = false) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); std::set<int64_t> setInternalKeyPool; - std::set<int64_t> setExternalKeyPool; + std::set<int64_t> setExternalKeyPool GUARDED_BY(cs_wallet); std::set<int64_t> set_pre_split_keypool; - int64_t m_max_keypool_index = 0; + int64_t m_max_keypool_index GUARDED_BY(cs_wallet) = 0; std::map<CKeyID, int64_t> m_pool_key_to_index; std::atomic<uint64_t> m_wallet_flags{0}; - int64_t nTimeFirstKey = 0; + int64_t nTimeFirstKey GUARDED_BY(cs_wallet) = 0; /** * Private version of AddWatchOnly method which does not accept a @@ -666,12 +707,11 @@ private: */ bool AddWatchOnly(const CScript& dest) override EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); - /** - * Wallet filename from wallet=<path> command line or config option. - * Used in debug logs and to send RPCs to the right wallet instance when - * more than one wallet is loaded. - */ - std::string m_name; + /** Interface for accessing chain state. */ + interfaces::Chain& m_chain; + + /** Wallet location which includes wallet name (see WalletLocation). */ + WalletLocation m_location; /** Internal database handle. */ std::unique_ptr<WalletDatabase> database; @@ -709,47 +749,54 @@ public: * if they are not ours */ bool SelectCoins(const std::vector<COutput>& vAvailableCoins, const CAmount& nTargetValue, std::set<CInputCoin>& setCoinsRet, CAmount& nValueRet, - const CCoinControl& coin_control, CoinSelectionParams& coin_selection_params, bool& bnb_used) const; + const CCoinControl& coin_control, CoinSelectionParams& coin_selection_params, bool& bnb_used) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + + const WalletLocation& GetLocation() const { return m_location; } /** Get a name for this wallet for logging/debugging purposes. */ - const std::string& GetName() const { return m_name; } + const std::string& GetName() const { return m_location.GetName(); } void LoadKeyPool(int64_t nIndex, const CKeyPool &keypool) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); - void MarkPreSplitKeys(); + void MarkPreSplitKeys() EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); // Map from Key ID to key metadata. - std::map<CKeyID, CKeyMetadata> mapKeyMetadata; + std::map<CKeyID, CKeyMetadata> mapKeyMetadata GUARDED_BY(cs_wallet); // Map from Script ID to key metadata (for watch-only keys). - std::map<CScriptID, CKeyMetadata> m_script_metadata; + std::map<CScriptID, CKeyMetadata> m_script_metadata GUARDED_BY(cs_wallet); typedef std::map<unsigned int, CMasterKey> MasterKeyMap; MasterKeyMap mapMasterKeys; unsigned int nMasterKeyMaxID = 0; /** Construct wallet with specified name and database implementation. */ - CWallet(std::string name, std::unique_ptr<WalletDatabase> database) : m_name(std::move(name)), database(std::move(database)) + CWallet(interfaces::Chain& chain, const WalletLocation& location, std::unique_ptr<WalletDatabase> database) : m_chain(chain), m_location(location), database(std::move(database)) { } ~CWallet() { + // Should not have slots connected at this point. + assert(NotifyUnload.empty()); delete encrypted_batch; encrypted_batch = nullptr; } - std::map<uint256, CWalletTx> mapWallet; + std::map<uint256, CWalletTx> mapWallet GUARDED_BY(cs_wallet); typedef std::multimap<int64_t, CWalletTx*> TxItems; TxItems wtxOrdered; - int64_t nOrderPosNext = 0; + int64_t nOrderPosNext GUARDED_BY(cs_wallet) = 0; uint64_t nAccountingEntryNumber = 0; std::map<CTxDestination, CAddressBookData> mapAddressBook; - std::set<COutPoint> setLockedCoins; + std::set<COutPoint> setLockedCoins GUARDED_BY(cs_wallet); + + /** Interface for accessing chain state. */ + interfaces::Chain& chain() const { return m_chain; } const CWalletTx* GetWalletTx(const uint256& hash) const; @@ -759,17 +806,17 @@ public: /** * populate vCoins with vector of available COutputs. */ - void AvailableCoins(std::vector<COutput>& vCoins, bool fOnlySafe=true, const CCoinControl *coinControl = nullptr, const CAmount& nMinimumAmount = 1, const CAmount& nMaximumAmount = MAX_MONEY, const CAmount& nMinimumSumAmount = MAX_MONEY, const uint64_t nMaximumCount = 0, const int nMinDepth = 0, const int nMaxDepth = 9999999) const EXCLUSIVE_LOCKS_REQUIRED(cs_main, cs_wallet); + void AvailableCoins(interfaces::Chain::Lock& locked_chain, std::vector<COutput>& vCoins, bool fOnlySafe=true, const CCoinControl *coinControl = nullptr, const CAmount& nMinimumAmount = 1, const CAmount& nMaximumAmount = MAX_MONEY, const CAmount& nMinimumSumAmount = MAX_MONEY, const uint64_t nMaximumCount = 0, const int nMinDepth = 0, const int nMaxDepth = 9999999) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); /** * Return list of available coins and locked coins grouped by non-change output address. */ - std::map<CTxDestination, std::vector<COutput>> ListCoins() const EXCLUSIVE_LOCKS_REQUIRED(cs_main, cs_wallet); + std::map<CTxDestination, std::vector<COutput>> ListCoins(interfaces::Chain::Lock& locked_chain) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); /** * Find non-change parent output. */ - const CTxOut& FindNonChangeParentOutput(const CTransaction& tx, int output) const; + const CTxOut& FindNonChangeParentOutput(const CTransaction& tx, int output) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); /** * Shuffle and select coins until nTargetValue is reached while avoiding @@ -780,7 +827,7 @@ public: bool SelectCoinsMinConf(const CAmount& nTargetValue, const CoinEligibilityFilter& eligibility_filter, std::vector<OutputGroup> groups, std::set<CInputCoin>& setCoinsRet, CAmount& nValueRet, const CoinSelectionParams& coin_selection_params, bool& bnb_used) const; - bool IsSpent(const uint256& hash, unsigned int n) const EXCLUSIVE_LOCKS_REQUIRED(cs_main); + bool IsSpent(interfaces::Chain::Lock& locked_chain, const uint256& hash, unsigned int n) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); std::vector<OutputGroup> GroupOutputs(const std::vector<COutput>& outputs, bool single_coin) const; bool IsLockedCoin(uint256 hash, unsigned int n) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); @@ -844,7 +891,7 @@ public: bool ChangeWalletPassphrase(const SecureString& strOldWalletPassphrase, const SecureString& strNewWalletPassphrase); bool EncryptWallet(const SecureString& strWalletPassphrase); - void GetKeyBirthTimes(std::map<CTxDestination, int64_t> &mapKeyBirth) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + void GetKeyBirthTimes(interfaces::Chain::Lock& locked_chain, std::map<CTxDestination, int64_t> &mapKeyBirth) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); unsigned int ComputeTimeSmart(const CWalletTx& wtx) const; /** @@ -856,17 +903,23 @@ public: void MarkDirty(); bool AddToWallet(const CWalletTx& wtxIn, bool fFlushOnClose=true); - void LoadToWallet(const CWalletTx& wtxIn); + void LoadToWallet(const CWalletTx& wtxIn) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); void TransactionAddedToMempool(const CTransactionRef& tx) override; void BlockConnected(const std::shared_ptr<const CBlock>& pblock, const CBlockIndex *pindex, const std::vector<CTransactionRef>& vtxConflicted) override; void BlockDisconnected(const std::shared_ptr<const CBlock>& pblock) override; int64_t RescanFromTime(int64_t startTime, const WalletRescanReserver& reserver, bool update); - CBlockIndex* ScanForWalletTransactions(CBlockIndex* pindexStart, CBlockIndex* pindexStop, const WalletRescanReserver& reserver, bool fUpdate = false); + + enum class ScanResult { + SUCCESS, + FAILURE, + USER_ABORT + }; + ScanResult ScanForWalletTransactions(const CBlockIndex* const pindexStart, const CBlockIndex* const pindexStop, const WalletRescanReserver& reserver, const CBlockIndex*& failed_block, const CBlockIndex*& stop_block, bool fUpdate = false); void TransactionRemovedFromMempool(const CTransactionRef &ptx) override; void ReacceptWalletTransactions(); void ResendWalletTransactions(int64_t nBestBlockTime, CConnman* connman) override EXCLUSIVE_LOCKS_REQUIRED(cs_main); // ResendWalletTransactionsBefore may only be called if fBroadcastTransactions! - std::vector<uint256> ResendWalletTransactionsBefore(int64_t nTime, CConnman* connman) EXCLUSIVE_LOCKS_REQUIRED(cs_main); + std::vector<uint256> ResendWalletTransactionsBefore(interfaces::Chain::Lock& locked_chain, int64_t nTime, CConnman* connman); CAmount GetBalance(const isminefilter& filter=ISMINE_SPENDABLE, const int min_depth=0) const; CAmount GetUnconfirmedBalance() const; CAmount GetImmatureBalance() const; @@ -889,7 +942,7 @@ 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(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, CReserveKey& reservekey, 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, CConnman* connman, CValidationState& state); @@ -948,7 +1001,7 @@ public: const std::map<CKeyID, int64_t>& GetAllReserveKeys() const { return m_pool_key_to_index; } std::set<std::set<CTxDestination>> GetAddressGroupings() EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); - std::map<CTxDestination, CAmount> GetAddressBalances() EXCLUSIVE_LOCKS_REQUIRED(cs_main); + std::map<CTxDestination, CAmount> GetAddressBalances(interfaces::Chain::Lock& locked_chain); std::set<CTxDestination> GetLabelAddresses(const std::string& label) const; @@ -961,6 +1014,7 @@ public: isminetype IsMine(const CTxOut& txout) const; CAmount GetCredit(const CTxOut& txout, const isminefilter& filter) const; bool IsChange(const CTxOut& txout) const; + bool IsChange(const CScript& script) const; CAmount GetChange(const CTxOut& txout) const; bool IsMine(const CTransaction& tx) const; /** should probably be renamed to IsRelevantToMe */ @@ -1000,7 +1054,7 @@ public: int GetVersion() { LOCK(cs_wallet); return nWalletVersion; } //! Get wallet transactions that conflict with given transaction (spend same outputs) - std::set<uint256> GetConflicts(const uint256& txid) const; + std::set<uint256> GetConflicts(const uint256& txid) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); //! Check if a given transaction has any of its outputs spent by another transaction in the wallet bool HasWalletSpend(const uint256& txid) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); @@ -1042,16 +1096,16 @@ public: bool TransactionCanBeAbandoned(const uint256& hashTx) const; /* Mark a transaction (and it in-wallet descendants) as abandoned so its inputs may be respent. */ - bool AbandonTransaction(const uint256& hashTx); + bool AbandonTransaction(interfaces::Chain::Lock& locked_chain, const uint256& hashTx); /** Mark a transaction as replaced by another transaction (e.g., BIP 125). */ bool MarkReplaced(const uint256& originalHash, const uint256& newHash); //! Verify wallet naming and perform salvage on the wallet if required - static bool Verify(std::string wallet_file, bool salvage_wallet, std::string& error_string, std::string& warning_string); + static bool Verify(interfaces::Chain& chain, const WalletLocation& location, bool salvage_wallet, std::string& error_string, std::string& warning_string); /* Initializes the wallet, returns a new CWallet instance or a null pointer in case of an error */ - static std::shared_ptr<CWallet> CreateWalletFromFile(const std::string& name, const fs::path& path, uint64_t wallet_creation_flags = 0); + static std::shared_ptr<CWallet> CreateWalletFromFile(interfaces::Chain& chain, const WalletLocation& location, uint64_t wallet_creation_flags = 0); /** * Wallet post-init setup @@ -1133,18 +1187,16 @@ class CReserveKey final : public CReserveScript { protected: CWallet* pwallet; - int64_t nIndex; + int64_t nIndex{-1}; CPubKey vchPubKey; - bool fInternal; + bool fInternal{false}; + public: explicit CReserveKey(CWallet* pwalletIn) { - nIndex = -1; pwallet = pwalletIn; - fInternal = false; } - CReserveKey() = default; CReserveKey(const CReserveKey&) = delete; CReserveKey& operator=(const CReserveKey&) = delete; @@ -1198,6 +1250,6 @@ public: // Use DummySignatureCreator, which inserts 71 byte signatures everywhere. // NOTE: this requires that all inputs must be in mapWallet (eg the tx should // be IsAllFromMe). -int64_t CalculateMaximumSignedTxSize(const CTransaction &tx, const CWallet *wallet, bool use_max_sig = false); +int64_t CalculateMaximumSignedTxSize(const CTransaction &tx, const CWallet *wallet, bool use_max_sig = false) EXCLUSIVE_LOCKS_REQUIRED(wallet->cs_wallet); int64_t CalculateMaximumSignedTxSize(const CTransaction &tx, const CWallet *wallet, const std::vector<CTxOut>& txouts, bool use_max_sig = false); #endif // BITCOIN_WALLET_WALLET_H diff --git a/src/wallet/walletdb.cpp b/src/wallet/walletdb.cpp index 5e85151358..6e037808e3 100644 --- a/src/wallet/walletdb.cpp +++ b/src/wallet/walletdb.cpp @@ -12,8 +12,8 @@ #include <protocol.h> #include <serialize.h> #include <sync.h> -#include <util.h> -#include <utiltime.h> +#include <util/system.h> +#include <util/time.h> #include <wallet/wallet.h> #include <atomic> @@ -153,21 +153,17 @@ bool WalletBatch::WriteMinVersion(int nVersion) class CWalletScanState { public: - unsigned int nKeys; - unsigned int nCKeys; - unsigned int nWatchKeys; - unsigned int nKeyMeta; - unsigned int m_unknown_records; - bool fIsEncrypted; - bool fAnyUnordered; - int nFileVersion; + unsigned int nKeys{0}; + unsigned int nCKeys{0}; + unsigned int nWatchKeys{0}; + unsigned int nKeyMeta{0}; + unsigned int m_unknown_records{0}; + bool fIsEncrypted{false}; + bool fAnyUnordered{false}; + int nFileVersion{0}; std::vector<uint256> vWalletUpgrade; CWalletScanState() { - nKeys = nCKeys = nWatchKeys = nKeyMeta = m_unknown_records = 0; - fIsEncrypted = false; - fAnyUnordered = false; - nFileVersion = 0; } }; diff --git a/src/wallet/walletutil.cpp b/src/wallet/walletutil.cpp index 34c76ec4c4..6db4c63acb 100644 --- a/src/wallet/walletutil.cpp +++ b/src/wallet/walletutil.cpp @@ -4,6 +4,8 @@ #include <wallet/walletutil.h> +#include <util/system.h> + fs::path GetWalletDir() { fs::path path; @@ -25,3 +27,67 @@ fs::path GetWalletDir() return path; } + +static bool IsBerkeleyBtree(const fs::path& path) +{ + // A Berkeley DB Btree file has at least 4K. + // This check also prevents opening lock files. + boost::system::error_code ec; + if (fs::file_size(path, ec) < 4096) return false; + + fs::ifstream file(path.string(), std::ios::binary); + if (!file.is_open()) return false; + + file.seekg(12, std::ios::beg); // Magic bytes start at offset 12 + uint32_t data = 0; + file.read((char*) &data, sizeof(data)); // Read 4 bytes of file to compare against magic + + // Berkeley DB Btree magic bytes, from: + // https://github.com/file/file/blob/5824af38469ec1ca9ac3ffd251e7afe9dc11e227/magic/Magdir/database#L74-L75 + // - big endian systems - 00 05 31 62 + // - little endian systems - 62 31 05 00 + return data == 0x00053162 || data == 0x62310500; +} + +std::vector<fs::path> ListWalletDir() +{ + const fs::path wallet_dir = GetWalletDir(); + const size_t offset = wallet_dir.string().size() + 1; + std::vector<fs::path> paths; + + for (auto it = fs::recursive_directory_iterator(wallet_dir); it != fs::recursive_directory_iterator(); ++it) { + // Get wallet path relative to walletdir by removing walletdir from the wallet path. + // This can be replaced by boost::filesystem::lexically_relative once boost is bumped to 1.60. + const fs::path path = it->path().string().substr(offset); + + if (it->status().type() == fs::directory_file && IsBerkeleyBtree(it->path() / "wallet.dat")) { + // Found a directory which contains wallet.dat btree file, add it as a wallet. + paths.emplace_back(path); + } else if (it.level() == 0 && it->symlink_status().type() == fs::regular_file && IsBerkeleyBtree(it->path())) { + if (it->path().filename() == "wallet.dat") { + // Found top-level wallet.dat btree file, add top level directory "" + // as a wallet. + paths.emplace_back(); + } else { + // Found top-level btree file not called wallet.dat. Current bitcoin + // software will never create these files but will allow them to be + // opened in a shared database environment for backwards compatibility. + // Add it to the list of available wallets. + paths.emplace_back(path); + } + } + } + + return paths; +} + +WalletLocation::WalletLocation(const std::string& name) + : m_name(name) + , m_path(fs::absolute(name, GetWalletDir())) +{ +} + +bool WalletLocation::Exists() const +{ + return fs::symlink_status(m_path).type() != fs::file_not_found; +} diff --git a/src/wallet/walletutil.h b/src/wallet/walletutil.h index 7c42954616..ba2f913841 100644 --- a/src/wallet/walletutil.h +++ b/src/wallet/walletutil.h @@ -5,10 +5,34 @@ #ifndef BITCOIN_WALLET_WALLETUTIL_H #define BITCOIN_WALLET_WALLETUTIL_H -#include <chainparamsbase.h> -#include <util.h> +#include <fs.h> + +#include <vector> //! Get the path of the wallet directory. fs::path GetWalletDir(); +//! Get wallets in wallet directory. +std::vector<fs::path> ListWalletDir(); + +//! The WalletLocation class provides wallet information. +class WalletLocation final +{ + std::string m_name; + fs::path m_path; + +public: + explicit WalletLocation() {} + explicit WalletLocation(const std::string& name); + + //! Get wallet name. + const std::string& GetName() const { return m_name; } + + //! Get wallet absolute path. + const fs::path& GetPath() const { return m_path; } + + //! Return whether the wallet exists. + bool Exists() const; +}; + #endif // BITCOIN_WALLET_WALLETUTIL_H diff --git a/src/walletinitinterface.h b/src/walletinitinterface.h index 6f12551273..22aca65990 100644 --- a/src/walletinitinterface.h +++ b/src/walletinitinterface.h @@ -9,6 +9,7 @@ class CScheduler; class CRPCTable; +struct InitInterfaces; class WalletInitInterface { public: @@ -18,20 +19,8 @@ public: virtual void AddWalletOptions() const = 0; /** Check wallet parameter interaction */ virtual bool ParameterInteraction() const = 0; - /** Register wallet RPC*/ - virtual void RegisterRPC(CRPCTable &) const = 0; - /** Verify wallets */ - virtual bool Verify() const = 0; - /** Open wallets*/ - virtual bool Open() const = 0; - /** Start wallets*/ - virtual void Start(CScheduler& scheduler) const = 0; - /** Flush Wallets*/ - virtual void Flush() const = 0; - /** Stop Wallets*/ - virtual void Stop() const = 0; - /** Close wallets */ - virtual void Close() const = 0; + /** Add wallets that should be opened to list of init interfaces. */ + virtual void Construct(InitInterfaces& interfaces) const = 0; virtual ~WalletInitInterface() {} }; diff --git a/src/warnings.cpp b/src/warnings.cpp index 9f10c48eef..1c6ba13f60 100644 --- a/src/warnings.cpp +++ b/src/warnings.cpp @@ -5,7 +5,7 @@ #include <sync.h> #include <clientversion.h> -#include <util.h> +#include <util/system.h> #include <warnings.h> CCriticalSection cs_warnings; diff --git a/src/zmq/zmqabstractnotifier.cpp b/src/zmq/zmqabstractnotifier.cpp index 39cc8968d2..6a9661e3e8 100644 --- a/src/zmq/zmqabstractnotifier.cpp +++ b/src/zmq/zmqabstractnotifier.cpp @@ -3,8 +3,9 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include <zmq/zmqabstractnotifier.h> -#include <util.h> +#include <util/system.h> +const int CZMQAbstractNotifier::DEFAULT_ZMQ_SNDHWM; CZMQAbstractNotifier::~CZMQAbstractNotifier() { diff --git a/src/zmq/zmqabstractnotifier.h b/src/zmq/zmqabstractnotifier.h index 5081c6cd02..887dde7b27 100644 --- a/src/zmq/zmqabstractnotifier.h +++ b/src/zmq/zmqabstractnotifier.h @@ -15,7 +15,9 @@ typedef CZMQAbstractNotifier* (*CZMQNotifierFactory)(); class CZMQAbstractNotifier { public: - CZMQAbstractNotifier() : psocket(nullptr) { } + static const int DEFAULT_ZMQ_SNDHWM {1000}; + + CZMQAbstractNotifier() : psocket(nullptr), outbound_message_high_water_mark(DEFAULT_ZMQ_SNDHWM) { } virtual ~CZMQAbstractNotifier(); template <typename T> @@ -28,6 +30,12 @@ public: void SetType(const std::string &t) { type = t; } std::string GetAddress() const { return address; } void SetAddress(const std::string &a) { address = a; } + int GetOutboundMessageHighWaterMark() const { return outbound_message_high_water_mark; } + void SetOutboundMessageHighWaterMark(const int sndhwm) { + if (sndhwm >= 0) { + outbound_message_high_water_mark = sndhwm; + } + } virtual bool Initialize(void *pcontext) = 0; virtual void Shutdown() = 0; @@ -39,6 +47,7 @@ protected: void *psocket; std::string type; std::string address; + int outbound_message_high_water_mark; // aka SNDHWM }; #endif // BITCOIN_ZMQ_ZMQABSTRACTNOTIFIER_H diff --git a/src/zmq/zmqnotificationinterface.cpp b/src/zmq/zmqnotificationinterface.cpp index 1d9f86d450..6826cf62d6 100644 --- a/src/zmq/zmqnotificationinterface.cpp +++ b/src/zmq/zmqnotificationinterface.cpp @@ -8,7 +8,7 @@ #include <version.h> #include <validation.h> #include <streams.h> -#include <util.h> +#include <util/system.h> void zmqError(const char *str) { @@ -59,6 +59,7 @@ CZMQNotificationInterface* CZMQNotificationInterface::Create() CZMQAbstractNotifier *notifier = factory(); notifier->SetType(entry.first); notifier->SetAddress(address); + notifier->SetOutboundMessageHighWaterMark(static_cast<int>(gArgs.GetArg(arg + "hwm", CZMQAbstractNotifier::DEFAULT_ZMQ_SNDHWM))); notifiers.push_back(notifier); } } @@ -102,11 +103,11 @@ bool CZMQNotificationInterface::Initialize() CZMQAbstractNotifier *notifier = *i; if (notifier->Initialize(pcontext)) { - LogPrint(BCLog::ZMQ, " Notifier %s ready (address = %s)\n", notifier->GetType(), notifier->GetAddress()); + LogPrint(BCLog::ZMQ, "zmq: Notifier %s ready (address = %s)\n", notifier->GetType(), notifier->GetAddress()); } else { - LogPrint(BCLog::ZMQ, " Notifier %s failed (address = %s)\n", notifier->GetType(), notifier->GetAddress()); + LogPrint(BCLog::ZMQ, "zmq: Notifier %s failed (address = %s)\n", notifier->GetType(), notifier->GetAddress()); break; } } @@ -128,7 +129,7 @@ void CZMQNotificationInterface::Shutdown() for (std::list<CZMQAbstractNotifier*>::iterator i=notifiers.begin(); i!=notifiers.end(); ++i) { CZMQAbstractNotifier *notifier = *i; - LogPrint(BCLog::ZMQ, " Shutdown notifier %s at %s\n", notifier->GetType(), notifier->GetAddress()); + LogPrint(BCLog::ZMQ, "zmq: Shutdown notifier %s at %s\n", notifier->GetType(), notifier->GetAddress()); notifier->Shutdown(); } zmq_ctx_term(pcontext); diff --git a/src/zmq/zmqpublishnotifier.cpp b/src/zmq/zmqpublishnotifier.cpp index 36a6458f67..ba89d1401d 100644 --- a/src/zmq/zmqpublishnotifier.cpp +++ b/src/zmq/zmqpublishnotifier.cpp @@ -7,7 +7,7 @@ #include <streams.h> #include <zmq/zmqpublishnotifier.h> #include <validation.h> -#include <util.h> +#include <util/system.h> #include <rpc/server.h> static std::multimap<std::string, CZMQAbstractPublishNotifier*> mapPublishNotifiers; @@ -76,8 +76,18 @@ bool CZMQAbstractPublishNotifier::Initialize(void *pcontext) return false; } - int rc = zmq_bind(psocket, address.c_str()); - if (rc!=0) + LogPrint(BCLog::ZMQ, "zmq: Outbound message high water mark for %s at %s is %d\n", type, address, outbound_message_high_water_mark); + + int rc = zmq_setsockopt(psocket, ZMQ_SNDHWM, &outbound_message_high_water_mark, sizeof(outbound_message_high_water_mark)); + if (rc != 0) + { + zmqError("Failed to set outbound message high water mark"); + zmq_close(psocket); + return false; + } + + rc = zmq_bind(psocket, address.c_str()); + if (rc != 0) { zmqError("Failed to bind address"); zmq_close(psocket); @@ -91,6 +101,7 @@ bool CZMQAbstractPublishNotifier::Initialize(void *pcontext) else { LogPrint(BCLog::ZMQ, "zmq: Reusing socket for address %s\n", address); + LogPrint(BCLog::ZMQ, "zmq: Outbound message high water mark for %s at %s is %d\n", type, address, outbound_message_high_water_mark); psocket = i->second->psocket; mapPublishNotifiers.insert(std::make_pair(address, this)); @@ -120,7 +131,7 @@ void CZMQAbstractPublishNotifier::Shutdown() if (count == 1) { - LogPrint(BCLog::ZMQ, "Close socket at address %s\n", address); + LogPrint(BCLog::ZMQ, "zmq: Close socket at address %s\n", address); int linger = 0; zmq_setsockopt(psocket, ZMQ_LINGER, &linger, sizeof(linger)); zmq_close(psocket); diff --git a/src/zmq/zmqpublishnotifier.h b/src/zmq/zmqpublishnotifier.h index 0f5b43d726..278fdb94d2 100644 --- a/src/zmq/zmqpublishnotifier.h +++ b/src/zmq/zmqpublishnotifier.h @@ -12,7 +12,7 @@ class CBlockIndex; class CZMQAbstractPublishNotifier : public CZMQAbstractNotifier { private: - uint32_t nSequence; //!< upcounting per message sequence number + uint32_t nSequence {0U}; //!< upcounting per message sequence number public: diff --git a/src/zmq/zmqrpc.cpp b/src/zmq/zmqrpc.cpp index 4f88bf4eb9..d3eab46e5f 100644 --- a/src/zmq/zmqrpc.cpp +++ b/src/zmq/zmqrpc.cpp @@ -5,6 +5,7 @@ #include <zmq/zmqrpc.h> #include <rpc/server.h> +#include <rpc/util.h> #include <zmq/zmqabstractnotifier.h> #include <zmq/zmqnotificationinterface.h> @@ -16,13 +17,15 @@ UniValue getzmqnotifications(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() != 0) { throw std::runtime_error( - "getzmqnotifications\n" - "\nReturns information about the active ZeroMQ notifications.\n" + RPCHelpMan{"getzmqnotifications", + "\nReturns information about the active ZeroMQ notifications.\n", {}} + .ToString() + "\nResult:\n" "[\n" " { (json object)\n" " \"type\": \"pubhashtx\", (string) Type of notification\n" - " \"address\": \"...\" (string) Address of the publisher\n" + " \"address\": \"...\", (string) Address of the publisher\n" + " \"hwm\": n (numeric) Outbound message high water mark\n" " },\n" " ...\n" "]\n" @@ -38,6 +41,7 @@ UniValue getzmqnotifications(const JSONRPCRequest& request) UniValue obj(UniValue::VOBJ); obj.pushKV("type", n->GetType()); obj.pushKV("address", n->GetAddress()); + obj.pushKV("hwm", n->GetOutboundMessageHighWaterMark()); result.push_back(obj); } } diff --git a/test/README.md b/test/README.md index a5ba732bc0..b5cbe1aff3 100644 --- a/test/README.md +++ b/test/README.md @@ -3,21 +3,23 @@ utilities in their entirety. It does not contain unit tests, which can be found in [/src/test](/src/test), [/src/wallet/test](/src/wallet/test), etc. -There are currently two sets of tests in this directory: +This directory contains the following sets of tests: - [functional](/test/functional) which test the functionality of bitcoind and bitcoin-qt by interacting with them through the RPC and P2P interfaces. - [util](/test/util) which tests the bitcoin utilities, currently only bitcoin-tx. +- [lint](/test/lint/) which perform various static analysis checks. The util tests are run as part of `make check` target. The functional -tests are run by the travis continuous build process whenever a pull -request is opened. Both sets of tests can also be run locally. +tests and lint scripts are run by the travis continuous build process whenever a pull +request is opened. All sets of tests can also be run locally. # Running tests locally -Build for your system first. Be sure to enable wallet, utils and daemon when you configure. Tests will not run otherwise. +Before tests can be run locally, Bitcoin Core must be built. See the [building instructions](/doc#building) for help. + ### Functional tests @@ -30,7 +32,7 @@ The ZMQ functional test requires a python ZMQ library. To install it: #### Running the tests -Individual tests can be run by directly calling the test script, eg: +Individual tests can be run by directly calling the test script, e.g.: ``` test/functional/feature_rbf.py @@ -180,6 +182,26 @@ Note: gdb attach step may require `sudo` Util tests can be run locally by running `test/util/bitcoin-util-test.py`. Use the `-v` option for verbose output. +### Lint tests + +#### Dependencies + +The lint tests require codespell and flake8. To install: `pip3 install codespell flake8`. + +#### Running the tests + +Individual tests can be run by directly calling the test script, e.g.: + +``` +test/lint/lint-filenames.sh +``` + +You can run all the shell-based lint tests by running: + +``` +test/lint/lint-all.sh +``` + # Writing functional tests You are encouraged to write functional tests for new or existing features. diff --git a/test/functional/README.md b/test/functional/README.md index d40052ac93..bce0d5db2e 100644 --- a/test/functional/README.md +++ b/test/functional/README.md @@ -20,6 +20,10 @@ don't have test cases for. - Where possible, try to adhere to [PEP-8 guidelines](https://www.python.org/dev/peps/pep-0008/) - Use a python linter like flake8 before submitting PRs to catch common style nits (eg trailing whitespace, unused imports, etc) +- The oldest supported Python version is specified in [doc/dependencies.md](/doc/dependencies.md). + Consider using [pyenv](https://github.com/pyenv/pyenv), which checks [.python-version](/.python-version), + to prevent accidentally introducing modern syntax from an unsupported Python version. + The Travis linter also checks this, but [possibly not in all cases](https://github.com/bitcoin/bitcoin/pull/14884#discussion_r239585126). - See [the python lint script](/test/lint/lint-python.sh) that checks for violations that could lead to bugs and issues in the test code. - Avoid wildcard imports where possible diff --git a/test/functional/combine_logs.py b/test/functional/combine_logs.py index 3230d5cb6b..5bb3b5c094 100755 --- a/test/functional/combine_logs.py +++ b/test/functional/combine_logs.py @@ -2,7 +2,9 @@ """Combine logs from multiple bitcoin nodes as well as the test_framework log. This streams the combined log output to stdout. Use combine_logs.py > outputfile -to write to an outputfile.""" +to write to an outputfile. + +If no argument is provided, the most recent test directory will be used.""" import argparse from collections import defaultdict, namedtuple @@ -11,6 +13,13 @@ import itertools import os import re import sys +import tempfile + +# N.B.: don't import any local modules here - this script must remain executable +# without the parent module installed. + +# Should match same symbol in `test_framework.test_framework`. +TMPDIR_PREFIX = "bitcoin_func_test_" # Matches on the date format at the start of the log event TIMESTAMP_PATTERN = re.compile(r"^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{6})?Z") @@ -19,22 +28,30 @@ LogEvent = namedtuple('LogEvent', ['timestamp', 'source', 'event']) def main(): """Main function. Parses args, reads the log files and renders them as text or html.""" - - parser = argparse.ArgumentParser(usage='%(prog)s [options] <test temporary directory>', description=__doc__) + parser = argparse.ArgumentParser( + description=__doc__, formatter_class=argparse.RawTextHelpFormatter) + parser.add_argument( + 'testdir', nargs='?', default='', + help=('temporary test directory to combine logs from. ' + 'Defaults to the most recent')) parser.add_argument('-c', '--color', dest='color', action='store_true', help='outputs the combined log with events colored by source (requires posix terminal colors. Use less -r for viewing)') parser.add_argument('--html', dest='html', action='store_true', help='outputs the combined log as html. Requires jinja2. pip install jinja2') - args, unknown_args = parser.parse_known_args() + args = parser.parse_args() if args.html and args.color: print("Only one out of --color or --html should be specified") sys.exit(1) - # There should only be one unknown argument - the path of the temporary test directory - if len(unknown_args) != 1: - print("Unexpected arguments" + str(unknown_args)) + testdir = args.testdir or find_latest_test_dir() + + if not testdir: + print("No test directories found") sys.exit(1) - log_events = read_logs(unknown_args[0]) + if not args.testdir: + print("Opening latest test directory: {}".format(testdir), file=sys.stderr) + + log_events = read_logs(testdir) print_logs(log_events, color=args.color, html=args.html) @@ -53,6 +70,29 @@ def read_logs(tmp_dir): return heapq.merge(*[get_log_events(source, f) for source, f in files]) + +def find_latest_test_dir(): + """Returns the latest tmpfile test directory prefix.""" + tmpdir = tempfile.gettempdir() + + def join_tmp(basename): + return os.path.join(tmpdir, basename) + + def is_valid_test_tmpdir(basename): + fullpath = join_tmp(basename) + return ( + os.path.isdir(fullpath) + and basename.startswith(TMPDIR_PREFIX) + and os.access(fullpath, os.R_OK) + ) + + testdir_paths = [ + join_tmp(name) for name in os.listdir(tmpdir) if is_valid_test_tmpdir(name) + ] + + return max(testdir_paths, key=os.path.getmtime) if testdir_paths else None + + def get_log_events(source, logfile): """Generator function that returns individual log events. diff --git a/test/functional/data/invalid_txs.py b/test/functional/data/invalid_txs.py new file mode 100644 index 0000000000..02deae92f3 --- /dev/null +++ b/test/functional/data/invalid_txs.py @@ -0,0 +1,180 @@ +#!/usr/bin/env python3 +# Copyright (c) 2015-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. +""" +Templates for constructing various sorts of invalid transactions. + +These templates (or an iterator over all of them) can be reused in different +contexts to test using a number of invalid transaction types. + +Hopefully this makes it easier to get coverage of a full variety of tx +validation checks through different interfaces (AcceptBlock, AcceptToMemPool, +etc.) without repeating ourselves. + +Invalid tx cases not covered here can be found by running: + + $ diff \ + <(grep -IREho "bad-txns[a-zA-Z-]+" src | sort -u) \ + <(grep -IEho "bad-txns[a-zA-Z-]+" test/functional/data/invalid_txs.py | sort -u) + +""" +import abc + +from test_framework.messages import CTransaction, CTxIn, CTxOut, COutPoint +from test_framework import script as sc +from test_framework.blocktools import create_tx_with_script, MAX_BLOCK_SIGOPS + +basic_p2sh = sc.CScript([sc.OP_HASH160, sc.hash160(sc.CScript([sc.OP_0])), sc.OP_EQUAL]) + + +class BadTxTemplate: + """Allows simple construction of a certain kind of invalid tx. Base class to be subclassed.""" + __metaclass__ = abc.ABCMeta + + # The expected error code given by bitcoind upon submission of the tx. + reject_reason = "" + + # Only specified if it differs from mempool acceptance error. + block_reject_reason = "" + + # Do we expect to be disconnected after submitting this tx? + expect_disconnect = False + + # Is this tx considered valid when included in a block, but not for acceptance into + # the mempool (i.e. does it violate policy but not consensus)? + valid_in_block = False + + def __init__(self, *, spend_tx=None, spend_block=None): + self.spend_tx = spend_block.vtx[0] if spend_block else spend_tx + self.spend_avail = sum(o.nValue for o in self.spend_tx.vout) + self.valid_txin = CTxIn(COutPoint(self.spend_tx.sha256, 0), b"", 0xffffffff) + + @abc.abstractmethod + def get_tx(self, *args, **kwargs): + """Return a CTransaction that is invalid per the subclass.""" + pass + + +class OutputMissing(BadTxTemplate): + reject_reason = "bad-txns-vout-empty" + expect_disconnect = False + + def get_tx(self): + tx = CTransaction() + tx.vin.append(self.valid_txin) + tx.calc_sha256() + return tx + + +class InputMissing(BadTxTemplate): + reject_reason = "bad-txns-vin-empty" + expect_disconnect = False + + def get_tx(self): + tx = CTransaction() + tx.vout.append(CTxOut(0, sc.CScript([sc.OP_TRUE] * 100))) + tx.calc_sha256() + return tx + + +class SizeTooSmall(BadTxTemplate): + reject_reason = "tx-size-small" + expect_disconnect = False + valid_in_block = True + + def get_tx(self): + tx = CTransaction() + tx.vin.append(self.valid_txin) + tx.vout.append(CTxOut(0, sc.CScript([sc.OP_TRUE]))) + tx.calc_sha256() + return tx + + +class BadInputOutpointIndex(BadTxTemplate): + # Won't be rejected - nonexistent outpoint index is treated as an orphan since the coins + # database can't distinguish between spent outpoints and outpoints which never existed. + reject_reason = None + expect_disconnect = False + + def get_tx(self): + num_indices = len(self.spend_tx.vin) + bad_idx = num_indices + 100 + + tx = CTransaction() + tx.vin.append(CTxIn(COutPoint(self.spend_tx.sha256, bad_idx), b"", 0xffffffff)) + tx.vout.append(CTxOut(0, basic_p2sh)) + tx.calc_sha256() + return tx + + +class DuplicateInput(BadTxTemplate): + reject_reason = 'bad-txns-inputs-duplicate' + expect_disconnect = True + + def get_tx(self): + tx = CTransaction() + tx.vin.append(self.valid_txin) + tx.vin.append(self.valid_txin) + tx.vout.append(CTxOut(1, basic_p2sh)) + tx.calc_sha256() + return tx + + +class NonexistentInput(BadTxTemplate): + reject_reason = None # Added as an orphan tx. + expect_disconnect = False + + def get_tx(self): + tx = CTransaction() + tx.vin.append(CTxIn(COutPoint(self.spend_tx.sha256 + 1, 0), b"", 0xffffffff)) + tx.vin.append(self.valid_txin) + tx.vout.append(CTxOut(1, basic_p2sh)) + tx.calc_sha256() + return tx + + +class SpendTooMuch(BadTxTemplate): + reject_reason = 'bad-txns-in-belowout' + expect_disconnect = True + + def get_tx(self): + return create_tx_with_script( + self.spend_tx, 0, script_pub_key=basic_p2sh, amount=(self.spend_avail + 1)) + + +class SpendNegative(BadTxTemplate): + reject_reason = 'bad-txns-vout-negative' + expect_disconnect = True + + def get_tx(self): + return create_tx_with_script(self.spend_tx, 0, amount=-1) + + +class InvalidOPIFConstruction(BadTxTemplate): + reject_reason = "mandatory-script-verify-flag-failed (Invalid OP_IF construction)" + expect_disconnect = True + valid_in_block = True + + def get_tx(self): + return create_tx_with_script( + self.spend_tx, 0, script_sig=b'\x64' * 35, + amount=(self.spend_avail // 2)) + + +class TooManySigops(BadTxTemplate): + reject_reason = "bad-txns-too-many-sigops" + block_reject_reason = "bad-blk-sigops, out-of-bounds SigOpCount" + expect_disconnect = False + + def get_tx(self): + lotsa_checksigs = sc.CScript([sc.OP_CHECKSIG] * (MAX_BLOCK_SIGOPS)) + return create_tx_with_script( + self.spend_tx, 0, + script_pub_key=lotsa_checksigs, + amount=1) + + +def iter_all_templates(): + """Iterate through all bad transaction template types.""" + return BadTxTemplate.__subclasses__() diff --git a/test/functional/data/rpc_psbt.json b/test/functional/data/rpc_psbt.json index dd40a096de..57f2608ee9 100644 --- a/test/functional/data/rpc_psbt.json +++ b/test/functional/data/rpc_psbt.json @@ -17,7 +17,8 @@ "cHNidP8BAJoCAAAAAljoeiG1ba8MI76OcHBFbDNvfLqlyHV5JPVFiHuyq911AAAAAAD/////g40EJ9DsZQpoqka7CwmK6kQiwHGyyng1Kgd5WdB86h0BAAAAAP////8CcKrwCAAAAAAWABTYXCtx0AYLCcmIauuBXlCZHdoSTQDh9QUAAAAAFgAUAK6pouXw+HaliN9VRuh0LR2HAI8AAAAAAAEAuwIAAAABqtc5MQGL0l+ErkALaISL4J23BurCrBgpi6vucatlb4sAAAAASEcwRAIgWPb8fGoz4bMVSNSByCbAFb0wE1qtQs1neQ2rZtKtJDsCIEoc7SYExnNbY5PltBaR3XiwDwxZQvufdRhW+qk4FX26Af7///8CgPD6AgAAAAAXqRQPuUY0IWlrgsgzryQceMF9295JNIfQ8gonAQAAABepFCnKdPigj4GZlCgYXJe12FLkBj9hh2UAAAABB9oARzBEAiB0AYrUGACXuHMyPAAVcgs2hMyBI4kQSOfbzZtVrWecmQIgc9Npt0Dj61Pc76M4I8gHBRTKVafdlUTxV8FnkTJhEYwBSDBFAiEA9hA4swjcHahlo0hSdG8BV3KTQgjG0kRUOTzZm98iF3cCIAVuZ1pnWm0KArhbFOXikHTYolqbV2C+ooFvZhkQoAbqAUdSIQKVg785rgpgl0etGZrd1jT6YQhVnWxc05tMIYPxq5bgfyEC2rYf9JoU22p9ArDNH7t4/EsYMStbTlTa5Nui+/71NtdSrgABASAAwusLAAAAABepFLf1+vQOPUClpFmx2zU18rcvqSHohwEHIyIAIIwjUxc3Q7WV37Sge3K6jkLjeX2nTof+fZ10l+OyAokDAQjaBABHMEQCIGLrelVhB6fHP0WsSrWh3d9vcHX7EnWWmn84Pv/3hLyyAiAMBdu3Rw2/LwhVfdNWxzJcHtMJE+mWzThAlF2xIijaXwFHMEQCIGX0W6WZi1mif/4ae+0BavHx+Q1Us6qPdFCqX1aiUQO9AiB/ckcDrR7blmgLKEtW1P/LiPf7dZ6rvgiqMPKbhROD0gFHUiEDCJ3BDHrG21T5EymvYXMz2ziM6tDCMfcjN50bmQMLAtwhAjrdkE89bc9Z3bkGsN7iNSm3/7ntUOXoYVGSaGAiHw5zUq4AIQIDqaTDf1mW06ol26xrVwrwZQOUSSlCRgs1R1PtnuylhxDZDGpPAAAAgAAAAIAEAACAACICAn9jmXV9Lv9VoTatAsaEsYOLZVbl8bazQoKpS2tQBRCWENkMak8AAACAAAAAgAUAAIAA", "cHNidP8BAHMCAAAAATAa6YblFqHsisW0vGVz0y+DtGXiOtdhZ9aLOOcwtNvbAAAAAAD/////AnR7AQAAAAAAF6kUA6oXrogrXQ1Usl1jEE5P/s57nqKHYEOZOwAAAAAXqRS5IbG6b3IuS/qDtlV6MTmYakLsg4cAAAAAAAEBHwDKmjsAAAAAFgAU0tlLZK4IWH7vyO6xh8YB6Tn5A3wCAwABAAAAAAEAFgAUYunpgv/zTdgjlhAxawkM0qO3R8sAAQAiACCHa62DLx0WgBXtQSMqnqZaGBXZ7xPA74dZ9ktbKyeKZQEBJVEhA7fOI6AcW0vwCmQlN836uzFbZoMyhnR471EwnSvVf4qHUa4A", "cHNidP8BAHMCAAAAATAa6YblFqHsisW0vGVz0y+DtGXiOtdhZ9aLOOcwtNvbAAAAAAD/////AnR7AQAAAAAAF6kUA6oXrogrXQ1Usl1jEE5P/s57nqKHYEOZOwAAAAAXqRS5IbG6b3IuS/qDtlV6MTmYakLsg4cAAAAAAAEBHwDKmjsAAAAAFgAU0tlLZK4IWH7vyO6xh8YB6Tn5A3wAAgAAFgAUYunpgv/zTdgjlhAxawkM0qO3R8sAAQAiACCHa62DLx0WgBXtQSMqnqZaGBXZ7xPA74dZ9ktbKyeKZQEBJVEhA7fOI6AcW0vwCmQlN836uzFbZoMyhnR471EwnSvVf4qHUa4A", - "cHNidP8BAHMCAAAAATAa6YblFqHsisW0vGVz0y+DtGXiOtdhZ9aLOOcwtNvbAAAAAAD/////AnR7AQAAAAAAF6kUA6oXrogrXQ1Usl1jEE5P/s57nqKHYEOZOwAAAAAXqRS5IbG6b3IuS/qDtlV6MTmYakLsg4cAAAAAAAEBHwDKmjsAAAAAFgAU0tlLZK4IWH7vyO6xh8YB6Tn5A3wAAQAWABRi6emC//NN2COWEDFrCQzSo7dHywABACIAIIdrrYMvHRaAFe1BIyqeploYFdnvE8Dvh1n2S1srJ4plIQEAJVEhA7fOI6AcW0vwCmQlN836uzFbZoMyhnR471EwnSvVf4qHUa4A" + "cHNidP8BAHMCAAAAATAa6YblFqHsisW0vGVz0y+DtGXiOtdhZ9aLOOcwtNvbAAAAAAD/////AnR7AQAAAAAAF6kUA6oXrogrXQ1Usl1jEE5P/s57nqKHYEOZOwAAAAAXqRS5IbG6b3IuS/qDtlV6MTmYakLsg4cAAAAAAAEBHwDKmjsAAAAAFgAU0tlLZK4IWH7vyO6xh8YB6Tn5A3wAAQAWABRi6emC//NN2COWEDFrCQzSo7dHywABACIAIIdrrYMvHRaAFe1BIyqeploYFdnvE8Dvh1n2S1srJ4plIQEAJVEhA7fOI6AcW0vwCmQlN836uzFbZoMyhnR471EwnSvVf4qHUa4A", + "cHNidP8BAHMCAAAAAbiWoY6pOQepFsEGhUPXaulX9rvye2NH+NrdlAHg+WgpAQAAAAD/////AkBLTAAAAAAAF6kUqWwXCcLM5BN2zoNqMNT5qMlIi7+HQEtMAAAAAAAXqRSVF/in2XNxAlN1OSxkyp0z+Wtg2YcAAAAAAAEBIBNssgAAAAAAF6kUamsvautR8hRlMRY6OKNTx03DK96HAQcXFgAUo8u1LWpHprjt/uENAwBpGZD0UH0BCGsCRzBEAiAONfH3DYiw67ZbylrsxCF/XXpVwyWBRgofyRbPslzvwgIgIKCsWw5sHSIPh1icNvcVLZLHWj6NA7Dk+4Os2pOnMbQBIQPGStfYHPtyhpV7zIWtn0Q4GXv5gK1zy/tnJ+cBXu4iiwABABYAFMwmJQEz+HDpBEEabxJ5PogPsqZRAAEAFgAUyCrGc3h3FYCmiIspbv2pSTKZ5jU" ], "valid" : [ "cHNidP8BAHUCAAAAASaBcTce3/KF6Tet7qSze3gADAVmy7OtZGQXE8pCFxv2AAAAAAD+////AtPf9QUAAAAAGXapFNDFmQPFusKGh2DpD9UhpGZap2UgiKwA4fUFAAAAABepFDVF5uM7gyxHBQ8k0+65PJwDlIvHh7MuEwAAAQD9pQEBAAAAAAECiaPHHqtNIOA3G7ukzGmPopXJRjr6Ljl/hTPMti+VZ+UBAAAAFxYAFL4Y0VKpsBIDna89p95PUzSe7LmF/////4b4qkOnHf8USIk6UwpyN+9rRgi7st0tAXHmOuxqSJC0AQAAABcWABT+Pp7xp0XpdNkCxDVZQ6vLNL1TU/////8CAMLrCwAAAAAZdqkUhc/xCX/Z4Ai7NK9wnGIZeziXikiIrHL++E4sAAAAF6kUM5cluiHv1irHU6m80GfWx6ajnQWHAkcwRAIgJxK+IuAnDzlPVoMR3HyppolwuAJf3TskAinwf4pfOiQCIAGLONfc0xTnNMkna9b7QPZzMlvEuqFEyADS8vAtsnZcASED0uFWdJQbrUqZY3LLh+GFbTZSYG2YVi/jnF6efkE/IQUCSDBFAiEA0SuFLYXc2WHS9fSrZgZU327tzHlMDDPOXMMJ/7X85Y0CIGczio4OFyXBl/saiK9Z9R5E5CVbIBZ8hoQDHAXR8lkqASECI7cr7vCWXRC+B3jv7NYfysb3mk6haTkzgHNEZPhPKrMAAAAAAAAA", diff --git a/test/functional/example_test.py b/test/functional/example_test.py index 3f15367a75..be3544ee74 100755 --- a/test/functional/example_test.py +++ b/test/functional/example_test.py @@ -164,13 +164,13 @@ class ExampleTest(BitcoinTestFramework): self.tip = int(self.nodes[0].getbestblockhash(), 16) self.block_time = self.nodes[0].getblock(self.nodes[0].getbestblockhash())['time'] + 1 - height = 1 + height = self.nodes[0].getblockcount() for i in range(10): # Use the mininode and blocktools functionality to manually build a block # Calling the generate() rpc is easier, but this allows us to exactly # control the blocks and transactions. - block = create_block(self.tip, create_coinbase(height), self.block_time) + block = create_block(self.tip, create_coinbase(height+1), self.block_time) block.solve() block_message = msg_block(block) # Send message is used to send a P2P message to the node over our P2PInterface diff --git a/test/functional/feature_assumevalid.py b/test/functional/feature_assumevalid.py index 3d0467038d..12a4ce9aff 100755 --- a/test/functional/feature_assumevalid.py +++ b/test/functional/feature_assumevalid.py @@ -180,7 +180,7 @@ class AssumeValidTest(BitcoinTestFramework): for i in range(2202): p2p1.send_message(msg_block(self.blocks[i])) # Syncing 2200 blocks can take a while on slow systems. Give it plenty of time to sync. - p2p1.sync_with_ping(120) + p2p1.sync_with_ping(150) assert_equal(self.nodes[1].getblock(self.nodes[1].getbestblockhash())['height'], 2202) # Send blocks to node2. Block 102 will be rejected. diff --git a/test/functional/feature_block.py b/test/functional/feature_block.py index 628cefb76d..5253ff7aaa 100755 --- a/test/functional/feature_block.py +++ b/test/functional/feature_block.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -# Copyright (c) 2015-2018 The Bitcoin Core developers +# Copyright (c) 2015-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 block processing.""" @@ -7,7 +7,13 @@ import copy import struct import time -from test_framework.blocktools import create_block, create_coinbase, create_tx_with_script, get_legacy_sigopcount_block +from test_framework.blocktools import ( + create_block, + create_coinbase, + create_tx_with_script, + get_legacy_sigopcount_block, + MAX_BLOCK_SIGOPS, +) from test_framework.key import CECKey from test_framework.messages import ( CBlock, @@ -45,8 +51,7 @@ from test_framework.script import ( ) from test_framework.test_framework import BitcoinTestFramework from test_framework.util import assert_equal - -MAX_BLOCK_SIGOPS = 20000 +from data import invalid_txs # Use this class for tests that require behavior other than normal "mininode" behavior. # For now, it is used to serialize a bloated varint (b64). @@ -95,16 +100,21 @@ class FullBlockTest(BitcoinTestFramework): self.save_spendable_output() self.sync_blocks([b0]) + # These constants chosen specifically to trigger an immature coinbase spend + # at a certain time below. + NUM_BUFFER_BLOCKS_TO_GENERATE = 99 + NUM_OUTPUTS_TO_COLLECT = 33 + # Allow the block to mature blocks = [] - for i in range(99): - blocks.append(self.next_block(5000 + i)) + for i in range(NUM_BUFFER_BLOCKS_TO_GENERATE): + blocks.append(self.next_block("maturitybuffer.{}".format(i))) self.save_spendable_output() self.sync_blocks(blocks) # collect spendable outputs now to avoid cluttering the code later on out = [] - for i in range(33): + for i in range(NUM_OUTPUTS_TO_COLLECT): out.append(self.get_spendable_output()) # Start by building a couple of blocks on top (which output is spent is @@ -116,7 +126,48 @@ class FullBlockTest(BitcoinTestFramework): b2 = self.next_block(2, spend=out[1]) self.save_spendable_output() - self.sync_blocks([b1, b2]) + self.sync_blocks([b1, b2], timeout=4) + + # Select a txn with an output eligible for spending. This won't actually be spent, + # since we're testing submission of a series of blocks with invalid txns. + attempt_spend_tx = out[2] + + # Submit blocks for rejection, each of which contains a single transaction + # (aside from coinbase) which should be considered invalid. + for TxTemplate in invalid_txs.iter_all_templates(): + template = TxTemplate(spend_tx=attempt_spend_tx) + + if template.valid_in_block: + continue + + self.log.info("Reject block with invalid tx: %s", TxTemplate.__name__) + blockname = "for_invalid.%s" % TxTemplate.__name__ + badblock = self.next_block(blockname) + badtx = template.get_tx() + if TxTemplate != invalid_txs.InputMissing: + self.sign_tx(badtx, attempt_spend_tx) + else: + # Segwit is active in regtest at this point, so to deserialize a + # transaction without any inputs correctly, we set the outputs + # to an empty list. This is a hack, as the serialization of an + # empty list of outputs is deserialized as flags==0 and thus + # deserialization of the outputs is skipped. + # A policy check requires "loose" txs to be of a minimum size, + # so vtx is not set to be empty in the TxTemplate class and we + # only apply the workaround where txs are not "loose", i.e. in + # blocks. + # + # The workaround has the purpose that both sides calculate + # the same tx hash in the merkle tree + badtx.vout = [] + badtx.rehash() + badblock = self.update_block(blockname, [badtx]) + self.sync_blocks( + [badblock], success=False, + reject_reason=(template.block_reject_reason or template.reject_reason), + reconnect=True, timeout=2) + + self.move_tip(2) # Fork like this: # @@ -579,14 +630,14 @@ class FullBlockTest(BitcoinTestFramework): while b47.sha256 < target: b47.nNonce += 1 b47.rehash() - self.sync_blocks([b47], False, request_block=False) + self.sync_blocks([b47], False, force_send=True, reject_reason='high-hash') self.log.info("Reject a block with a timestamp >2 hours in the future") self.move_tip(44) b48 = self.next_block(48, solve=False) b48.nTime = int(time.time()) + 60 * 60 * 3 b48.solve() - self.sync_blocks([b48], False, request_block=False) + self.sync_blocks([b48], False, force_send=True, reject_reason='time-too-new') self.log.info("Reject a block with invalid merkle hash") self.move_tip(44) @@ -600,7 +651,7 @@ class FullBlockTest(BitcoinTestFramework): b50 = self.next_block(50) b50.nBits = b50.nBits - 1 b50.solve() - self.sync_blocks([b50], False, request_block=False, reconnect=True) + self.sync_blocks([b50], False, force_send=True, reject_reason='bad-diffbits', reconnect=True) self.log.info("Reject a block with two coinbase transactions") self.move_tip(44) @@ -630,7 +681,7 @@ class FullBlockTest(BitcoinTestFramework): b54 = self.next_block(54, spend=out[15]) b54.nTime = b35.nTime - 1 b54.solve() - self.sync_blocks([b54], False, request_block=False) + self.sync_blocks([b54], False, force_send=True, reject_reason='time-too-old') # valid timestamp self.move_tip(53) @@ -824,7 +875,7 @@ class FullBlockTest(BitcoinTestFramework): tx.vin.append(CTxIn(COutPoint(b64a.vtx[1].sha256, 0))) b64a = self.update_block("64a", [tx]) assert_equal(len(b64a.serialize()), MAX_BLOCK_BASE_SIZE + 8) - self.sync_blocks([b64a], success=False, reject_reason='non-canonical ReadCompactSize():') + self.sync_blocks([b64a], success=False, reject_reason='non-canonical ReadCompactSize()') # bitcoind doesn't disconnect us for sending a bloated block, but if we subsequently # resend the header message, it won't send us the getdata message again. Just @@ -874,7 +925,7 @@ class FullBlockTest(BitcoinTestFramework): # \-> b67 (20) # # - self.log.info("Reject a block with a transaction double spending a transaction creted in the same block") + self.log.info("Reject a block with a transaction double spending a transaction created in the same block") self.move_tip(65) b67 = self.next_block(67) tx1 = self.create_and_sign_transaction(out[20], out[20].vout[0].nValue) @@ -1078,11 +1129,11 @@ class FullBlockTest(BitcoinTestFramework): self.move_tip(77) b80 = self.next_block(80, spend=out[25]) - self.sync_blocks([b80], False, request_block=False) + self.sync_blocks([b80], False, force_send=True) self.save_spendable_output() b81 = self.next_block(81, spend=out[26]) - self.sync_blocks([b81], False, request_block=False) # other chain is same length + self.sync_blocks([b81], False, force_send=True) # other chain is same length self.save_spendable_output() b82 = self.next_block(82, spend=out[27]) @@ -1169,7 +1220,7 @@ class FullBlockTest(BitcoinTestFramework): blocks = [] spend = out[32] for i in range(89, LARGE_REORG_SIZE + 89): - b = self.next_block(i, spend) + b = self.next_block(i, spend, version=4) tx = CTransaction() script_length = MAX_BLOCK_BASE_SIZE - len(b.serialize()) - 69 script_output = CScript([b'\x00' * script_length]) @@ -1181,26 +1232,38 @@ class FullBlockTest(BitcoinTestFramework): self.save_spendable_output() spend = self.get_spendable_output() - self.sync_blocks(blocks, True, timeout=180) + self.sync_blocks(blocks, True, timeout=480) chain1_tip = i # now create alt chain of same length self.move_tip(88) blocks2 = [] for i in range(89, LARGE_REORG_SIZE + 89): - blocks2.append(self.next_block("alt" + str(i))) - self.sync_blocks(blocks2, False, request_block=False) + blocks2.append(self.next_block("alt" + str(i), version=4)) + self.sync_blocks(blocks2, False, force_send=True) # extend alt chain to trigger re-org - block = self.next_block("alt" + str(chain1_tip + 1)) - self.sync_blocks([block], True, timeout=180) + block = self.next_block("alt" + str(chain1_tip + 1), version=4) + self.sync_blocks([block], True, timeout=480) # ... and re-org back to the first chain self.move_tip(chain1_tip) - block = self.next_block(chain1_tip + 1) - self.sync_blocks([block], False, request_block=False) - block = self.next_block(chain1_tip + 2) - self.sync_blocks([block], True, timeout=180) + block = self.next_block(chain1_tip + 1, version=4) + self.sync_blocks([block], False, force_send=True) + block = self.next_block(chain1_tip + 2, version=4) + self.sync_blocks([block], True, timeout=480) + + self.log.info("Reject a block with an invalid block header version") + b_v1 = self.next_block('b_v1', version=1) + self.sync_blocks([b_v1], success=False, force_send=True, reject_reason='bad-version(0x00000001)') + + self.move_tip(chain1_tip + 2) + b_cb34 = self.next_block('b_cb34', version=4) + b_cb34.vtx[0].vin[0].scriptSig = b_cb34.vtx[0].vin[0].scriptSig[:-1] + b_cb34.vtx[0].rehash() + b_cb34.hashMerkleRoot = b_cb34.calc_merkle_root() + b_cb34.solve() + self.sync_blocks([b_cb34], success=False, reject_reason='bad-cb-height', reconnect=True) # Helper methods ################ @@ -1229,7 +1292,7 @@ class FullBlockTest(BitcoinTestFramework): tx.rehash() return tx - def next_block(self, number, spend=None, additional_coinbase_value=0, script=CScript([OP_TRUE]), solve=True): + def next_block(self, number, spend=None, additional_coinbase_value=0, script=CScript([OP_TRUE]), solve=True, *, version=1): if self.tip is None: base_block_hash = self.genesis_hash block_time = int(time.time()) + 1 @@ -1242,11 +1305,11 @@ class FullBlockTest(BitcoinTestFramework): coinbase.vout[0].nValue += additional_coinbase_value coinbase.rehash() if spend is None: - block = create_block(base_block_hash, coinbase, block_time) + block = create_block(base_block_hash, coinbase, block_time, version=version) else: coinbase.vout[0].nValue += spend.vout[0].nValue - 1 # all but one satoshi to fees coinbase.rehash() - block = create_block(base_block_hash, coinbase, block_time) + block = create_block(base_block_hash, coinbase, block_time, version=version) tx = self.create_tx(spend, 0, 1, script) # spend 1 satoshi self.sign_tx(tx, spend) self.add_transactions_to_block(block, [tx]) @@ -1288,7 +1351,7 @@ class FullBlockTest(BitcoinTestFramework): self.blocks[block_number] = block return block - def bootstrap_p2p(self): + def bootstrap_p2p(self, timeout=10): """Add a P2P connection to the node. Helper to connect and wait for version handshake.""" @@ -1299,24 +1362,25 @@ class FullBlockTest(BitcoinTestFramework): # an INV for the next block and receive two getheaders - one for the # IBD and one for the INV. We'd respond to both and could get # unexpectedly disconnected if the DoS score for that error is 50. - self.nodes[0].p2p.wait_for_getheaders(timeout=5) + self.nodes[0].p2p.wait_for_getheaders(timeout=timeout) - def reconnect_p2p(self): + def reconnect_p2p(self, timeout=60): """Tear down and bootstrap the P2P connection to the node. The node gets disconnected several times in this test. This helper method reconnects the p2p and restarts the network thread.""" self.nodes[0].disconnect_p2ps() - self.bootstrap_p2p() + self.bootstrap_p2p(timeout=timeout) - def sync_blocks(self, blocks, success=True, reject_reason=None, request_block=True, reconnect=False, timeout=60): + def sync_blocks(self, blocks, success=True, reject_reason=None, force_send=False, reconnect=False, timeout=60): """Sends blocks to test node. Syncs and verifies that tip has advanced to most recent block. Call with success = False if the tip shouldn't advance to the most recent block.""" - self.nodes[0].p2p.send_blocks_and_test(blocks, self.nodes[0], success=success, reject_reason=reject_reason, request_block=request_block, timeout=timeout, expect_disconnect=reconnect) + self.nodes[0].p2p.send_blocks_and_test(blocks, self.nodes[0], success=success, reject_reason=reject_reason, force_send=force_send, timeout=timeout, expect_disconnect=reconnect) if reconnect: - self.reconnect_p2p() + self.reconnect_p2p(timeout=timeout) + if __name__ == '__main__': FullBlockTest().main() diff --git a/test/functional/feature_blocksdir.py b/test/functional/feature_blocksdir.py index c170f510c8..3a4889bbe9 100755 --- a/test/functional/feature_blocksdir.py +++ b/test/functional/feature_blocksdir.py @@ -18,6 +18,8 @@ class BlocksdirTest(BitcoinTestFramework): def run_test(self): self.stop_node(0) + assert os.path.isdir(os.path.join(self.nodes[0].datadir, "regtest", "blocks")) + assert not os.path.isdir(os.path.join(self.nodes[0].datadir, "blocks")) shutil.rmtree(self.nodes[0].datadir) initialize_datadir(self.options.tmpdir, 0) self.log.info("Starting with nonexistent blocksdir ...") diff --git a/test/functional/feature_cltv.py b/test/functional/feature_cltv.py index f84b08a199..302a5ec1cb 100755 --- a/test/functional/feature_cltv.py +++ b/test/functional/feature_cltv.py @@ -25,7 +25,6 @@ CLTV_HEIGHT = 1351 # Reject codes that we might receive in this test REJECT_INVALID = 16 -REJECT_OBSOLETE = 17 REJECT_NONSTANDARD = 64 def cltv_invalidate(tx): diff --git a/test/functional/feature_config_args.py b/test/functional/feature_config_args.py index 1124119e2b..4b3f6603a2 100755 --- a/test/functional/feature_config_args.py +++ b/test/functional/feature_config_args.py @@ -14,9 +14,6 @@ class ConfArgsTest(BitcoinTestFramework): self.setup_clean_chain = True self.num_nodes = 1 - def skip_test_if_missing_module(self): - self.skip_if_no_wallet() - def test_config_file_parser(self): # Assume node is stopped @@ -33,6 +30,23 @@ class ConfArgsTest(BitcoinTestFramework): self.nodes[0].assert_start_raises_init_error(expected_msg='Error reading configuration file: parse error on line 1: nono, if you intended to specify a negated option, use nono=1 instead') with open(inc_conf_file_path, 'w', encoding='utf-8') as conf: + conf.write('server=1\nrpcuser=someuser\nrpcpassword=some#pass') + self.nodes[0].assert_start_raises_init_error(expected_msg='Error reading configuration file: parse error on line 3, using # in rpcpassword can be ambiguous and should be avoided') + + with open(inc_conf_file_path, 'w', encoding='utf-8') as conf: + conf.write('server=1\nrpcuser=someuser\nmain.rpcpassword=some#pass') + self.nodes[0].assert_start_raises_init_error(expected_msg='Error reading configuration file: parse error on line 3, using # in rpcpassword can be ambiguous and should be avoided') + + with open(inc_conf_file_path, 'w', encoding='utf-8') as conf: + conf.write('server=1\nrpcuser=someuser\n[main]\nrpcpassword=some#pass') + self.nodes[0].assert_start_raises_init_error(expected_msg='Error reading configuration file: parse error on line 4, using # in rpcpassword can be ambiguous and should be avoided') + + with open(inc_conf_file_path, 'w', encoding='utf-8') as conf: + conf.write('testnot.datadir=1\n[testnet]\n') + self.restart_node(0) + self.nodes[0].stop_node(expected_stderr='Warning: Section [testnet] is not recognized.' + os.linesep + 'Warning: Section [testnot] is not recognized.') + + with open(inc_conf_file_path, 'w', encoding='utf-8') as conf: conf.write('') # clear def run_test(self): @@ -68,13 +82,18 @@ class ConfArgsTest(BitcoinTestFramework): # Temporarily disabled, because this test would access the user's home dir (~/.bitcoin) #self.start_node(0, ['-conf='+conf_file, '-wallet=w1']) #self.stop_node(0) + #assert os.path.exists(os.path.join(new_data_dir, 'regtest', 'blocks')) + #if self.is_wallet_compiled(): #assert os.path.exists(os.path.join(new_data_dir, 'regtest', 'wallets', 'w1')) # Ensure command line argument overrides datadir in conf os.mkdir(new_data_dir_2) self.nodes[0].datadir = new_data_dir_2 self.start_node(0, ['-datadir='+new_data_dir_2, '-conf='+conf_file, '-wallet=w2']) - assert os.path.exists(os.path.join(new_data_dir_2, 'regtest', 'wallets', 'w2')) + assert os.path.exists(os.path.join(new_data_dir_2, 'regtest', 'blocks')) + if self.is_wallet_compiled(): + assert os.path.exists(os.path.join(new_data_dir_2, 'regtest', 'wallets', 'w2')) + if __name__ == '__main__': ConfArgsTest().main() diff --git a/test/functional/feature_dbcrash.py b/test/functional/feature_dbcrash.py index ae1eacf2d7..8b06cc7372 100755 --- a/test/functional/feature_dbcrash.py +++ b/test/functional/feature_dbcrash.py @@ -46,7 +46,7 @@ class ChainstateWriteCrashTest(BitcoinTestFramework): self.num_nodes = 4 self.setup_clean_chain = False # Need a bit of extra time for the nodes to start up for this test - self.rpc_timewait = 90 + self.rpc_timeout = 90 # Set -maxmempool=0 to turn off mempool memory sharing with dbcache # Set -rpcservertimeout=900 to reduce socket disconnects in this @@ -69,6 +69,7 @@ class ChainstateWriteCrashTest(BitcoinTestFramework): def setup_network(self): self.add_nodes(self.num_nodes, extra_args=self.extra_args) self.start_nodes() + self.import_deterministic_coinbase_privkeys() # Leave them unconnected, we'll use submitblock directly in this test def restart_node(self, node_index, expected_tip): diff --git a/test/functional/feature_dersig.py b/test/functional/feature_dersig.py index 16272877e7..9cbc1b39bd 100755 --- a/test/functional/feature_dersig.py +++ b/test/functional/feature_dersig.py @@ -22,7 +22,6 @@ DERSIG_HEIGHT = 1251 # Reject codes that we might receive in this test REJECT_INVALID = 16 -REJECT_OBSOLETE = 17 REJECT_NONSTANDARD = 64 # A canonical signature consists of: diff --git a/test/functional/feature_fee_estimation.py b/test/functional/feature_fee_estimation.py index aaab4279b5..b68e46adbc 100755 --- a/test/functional/feature_fee_estimation.py +++ b/test/functional/feature_fee_estimation.py @@ -144,6 +144,9 @@ class EstimateFeeTest(BitcoinTestFramework): # (68k weight is room enough for 120 or so transactions) # Node2 is a stingy miner, that # produces too small blocks (room for only 55 or so transactions) + self.start_nodes() + self.import_deterministic_coinbase_privkeys() + self.stop_nodes() def transact_and_mine(self, numblocks, mining_node): min_fee = Decimal("0.00001") @@ -171,11 +174,6 @@ class EstimateFeeTest(BitcoinTestFramework): newmem.append(utx) self.memutxo = newmem - def import_deterministic_coinbase_privkeys(self): - self.start_nodes() - super().import_deterministic_coinbase_privkeys() - self.stop_nodes() - def run_test(self): self.log.info("This test is time consuming, please be patient") self.log.info("Splitting inputs so we can generate tx's") diff --git a/test/functional/feature_filelock.py b/test/functional/feature_filelock.py new file mode 100755 index 0000000000..9fb0d35a68 --- /dev/null +++ b/test/functional/feature_filelock.py @@ -0,0 +1,36 @@ +#!/usr/bin/env python3 +# Copyright (c) 2018 The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. +"""Check that it's not possible to start a second bitcoind instance using the same datadir or wallet.""" +import os + +from test_framework.test_framework import BitcoinTestFramework +from test_framework.test_node import ErrorMatch + +class FilelockTest(BitcoinTestFramework): + def set_test_params(self): + self.setup_clean_chain = True + self.num_nodes = 2 + + def setup_network(self): + self.add_nodes(self.num_nodes, extra_args=None) + self.nodes[0].start([]) + self.nodes[0].wait_for_rpc_connection() + + def run_test(self): + datadir = os.path.join(self.nodes[0].datadir, 'regtest') + self.log.info("Using datadir {}".format(datadir)) + + self.log.info("Check that we can't start a second bitcoind instance using the same datadir") + expected_msg = "Error: Cannot obtain a lock on data directory {}. Bitcoin Core is probably already running.".format(datadir) + self.nodes[1].assert_start_raises_init_error(extra_args=['-datadir={}'.format(self.nodes[0].datadir), '-noserver'], expected_msg=expected_msg) + + if self.is_wallet_compiled(): + wallet_dir = os.path.join(datadir, 'wallets') + self.log.info("Check that we can't start a second bitcoind instance using the same wallet") + expected_msg = "Error: Error initializing wallet database environment" + self.nodes[1].assert_start_raises_init_error(extra_args=['-walletdir={}'.format(wallet_dir), '-noserver'], expected_msg=expected_msg, match=ErrorMatch.PARTIAL_REGEX) + +if __name__ == '__main__': + FilelockTest().main() diff --git a/test/functional/feature_notifications.py b/test/functional/feature_notifications.py index a93443f2db..d8083b2840 100755 --- a/test/functional/feature_notifications.py +++ b/test/functional/feature_notifications.py @@ -5,17 +5,16 @@ """Test the -alertnotify, -blocknotify and -walletnotify options.""" import os +from test_framework.address import ADDRESS_BCRT1_UNSPENDABLE from test_framework.test_framework import BitcoinTestFramework from test_framework.util import assert_equal, wait_until, connect_nodes_bi + class NotificationsTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 2 self.setup_clean_chain = True - def skip_test_if_missing_module(self): - self.skip_if_no_wallet() - def setup_network(self): self.alertnotify_dir = os.path.join(self.options.tmpdir, "alertnotify") self.blocknotify_dir = os.path.join(self.options.tmpdir, "blocknotify") @@ -25,7 +24,7 @@ class NotificationsTest(BitcoinTestFramework): os.mkdir(self.walletnotify_dir) # -alertnotify and -blocknotify on node0, walletnotify on node1 - self.extra_args = [["-blockversion=2", + self.extra_args = [[ "-alertnotify=echo > {}".format(os.path.join(self.alertnotify_dir, '%s')), "-blocknotify=echo > {}".format(os.path.join(self.blocknotify_dir, '%s'))], ["-blockversion=211", @@ -36,7 +35,7 @@ class NotificationsTest(BitcoinTestFramework): def run_test(self): self.log.info("test -blocknotify") block_count = 10 - blocks = self.nodes[1].generate(block_count) + blocks = self.nodes[1].generatetoaddress(block_count, self.nodes[1].getnewaddress() if self.is_wallet_compiled() else ADDRESS_BCRT1_UNSPENDABLE) # wait at most 10 seconds for expected number of files before reading the content wait_until(lambda: len(os.listdir(self.blocknotify_dir)) == block_count, timeout=10) @@ -44,30 +43,32 @@ class NotificationsTest(BitcoinTestFramework): # directory content should equal the generated blocks hashes assert_equal(sorted(blocks), sorted(os.listdir(self.blocknotify_dir))) - self.log.info("test -walletnotify") - # wait at most 10 seconds for expected number of files before reading the content - wait_until(lambda: len(os.listdir(self.walletnotify_dir)) == block_count, timeout=10) + if self.is_wallet_compiled(): + self.log.info("test -walletnotify") + # wait at most 10 seconds for expected number of files before reading the content + wait_until(lambda: len(os.listdir(self.walletnotify_dir)) == block_count, timeout=10) - # directory content should equal the generated transaction hashes - txids_rpc = list(map(lambda t: t['txid'], self.nodes[1].listtransactions("*", block_count))) - assert_equal(sorted(txids_rpc), sorted(os.listdir(self.walletnotify_dir))) - for tx_file in os.listdir(self.walletnotify_dir): - os.remove(os.path.join(self.walletnotify_dir, tx_file)) + # directory content should equal the generated transaction hashes + txids_rpc = list(map(lambda t: t['txid'], self.nodes[1].listtransactions("*", block_count))) + assert_equal(sorted(txids_rpc), sorted(os.listdir(self.walletnotify_dir))) + self.stop_node(1) + for tx_file in os.listdir(self.walletnotify_dir): + os.remove(os.path.join(self.walletnotify_dir, tx_file)) - self.log.info("test -walletnotify after rescan") - # restart node to rescan to force wallet notifications - self.restart_node(1) - connect_nodes_bi(self.nodes, 0, 1) + self.log.info("test -walletnotify after rescan") + # restart node to rescan to force wallet notifications + self.start_node(1) + connect_nodes_bi(self.nodes, 0, 1) - wait_until(lambda: len(os.listdir(self.walletnotify_dir)) == block_count, timeout=10) + wait_until(lambda: len(os.listdir(self.walletnotify_dir)) == block_count, timeout=10) - # directory content should equal the generated transaction hashes - txids_rpc = list(map(lambda t: t['txid'], self.nodes[1].listtransactions("*", block_count))) - assert_equal(sorted(txids_rpc), sorted(os.listdir(self.walletnotify_dir))) + # directory content should equal the generated transaction hashes + txids_rpc = list(map(lambda t: t['txid'], self.nodes[1].listtransactions("*", block_count))) + assert_equal(sorted(txids_rpc), sorted(os.listdir(self.walletnotify_dir))) # Mine another 41 up-version blocks. -alertnotify should trigger on the 51st. self.log.info("test -alertnotify") - self.nodes[1].generate(41) + self.nodes[1].generatetoaddress(41, ADDRESS_BCRT1_UNSPENDABLE) self.sync_all() # Give bitcoind 10 seconds to write the alert notification @@ -77,7 +78,7 @@ class NotificationsTest(BitcoinTestFramework): os.remove(os.path.join(self.alertnotify_dir, notify_file)) # Mine more up-version blocks, should not get more alerts: - self.nodes[1].generate(2) + self.nodes[1].generatetoaddress(2, ADDRESS_BCRT1_UNSPENDABLE) self.sync_all() self.log.info("-alertnotify should not continue notifying for more unknown version blocks") diff --git a/test/functional/feature_nulldummy.py b/test/functional/feature_nulldummy.py index a79cc3d34b..eb76089d9c 100755 --- a/test/functional/feature_nulldummy.py +++ b/test/functional/feature_nulldummy.py @@ -12,6 +12,7 @@ Generate 427 more blocks. [Consensus] Check that the new NULLDUMMY rules are not enforced on the 431st block. [Policy/Consensus] Check that the new NULLDUMMY rules are enforced on the 432nd block. """ +import time from test_framework.blocktools import create_coinbase, create_block, create_transaction, add_witness_commitment from test_framework.messages import CTransaction @@ -19,8 +20,6 @@ from test_framework.script import CScript from test_framework.test_framework import BitcoinTestFramework from test_framework.util import assert_equal, assert_raises_rpc_error, bytes_to_hex_str -import time - NULLDUMMY_ERROR = "non-mandatory-script-verify-flag (Dummy CHECKMULTISIG argument must be zero) (code 64)" def trueDummy(tx): @@ -42,7 +41,7 @@ class NULLDUMMYTest(BitcoinTestFramework): self.setup_clean_chain = True # This script tests NULLDUMMY activation, which is part of the 'segwit' deployment, so we go through # normal segwit activation here (and don't use the default always-on behaviour). - self.extra_args = [['-whitelist=127.0.0.1', '-vbparams=segwit:0:999999999999', '-addresstype=legacy', "-deprecatedrpc=addwitnessaddress"]] + self.extra_args = [['-whitelist=127.0.0.1', '-vbparams=segwit:0:999999999999', '-addresstype=legacy']] def skip_test_if_missing_module(self): self.skip_if_no_wallet() @@ -50,14 +49,14 @@ class NULLDUMMYTest(BitcoinTestFramework): def run_test(self): self.address = self.nodes[0].getnewaddress() self.ms_address = self.nodes[0].addmultisigaddress(1, [self.address])['address'] - self.wit_address = self.nodes[0].addwitnessaddress(self.address) + self.wit_address = self.nodes[0].getnewaddress(address_type='p2sh-segwit') self.wit_ms_address = self.nodes[0].addmultisigaddress(1, [self.address], '', 'p2sh-segwit')['address'] - self.coinbase_blocks = self.nodes[0].generate(2) # Block 2 + self.coinbase_blocks = self.nodes[0].generate(2) # Block 2 coinbase_txid = [] for i in self.coinbase_blocks: coinbase_txid.append(self.nodes[0].getblock(i)['tx'][0]) - self.nodes[0].generate(427) # Block 429 + self.nodes[0].generate(427) # Block 429 self.lastblockhash = self.nodes[0].getbestblockhash() self.tip = int("0x" + self.lastblockhash, 0) self.lastblockheight = 429 @@ -82,7 +81,7 @@ class NULLDUMMYTest(BitcoinTestFramework): self.log.info("Test 4: Non-NULLDUMMY base multisig transaction is invalid after activation") test4tx = create_transaction(self.nodes[0], test2tx.hash, self.address, amount=46) - test6txs=[CTransaction(test4tx)] + test6txs = [CTransaction(test4tx)] trueDummy(test4tx) assert_raises_rpc_error(-26, NULLDUMMY_ERROR, self.nodes[0].sendrawtransaction, bytes_to_hex_str(test4tx.serialize_with_witness()), True) self.block_submit(self.nodes[0], [test4tx]) @@ -99,8 +98,7 @@ class NULLDUMMYTest(BitcoinTestFramework): self.nodes[0].sendrawtransaction(bytes_to_hex_str(i.serialize_with_witness()), True) self.block_submit(self.nodes[0], test6txs, True, True) - - def block_submit(self, node, txs, witness = False, accept = False): + def block_submit(self, node, txs, witness=False, accept=False): block = create_block(self.tip, create_coinbase(self.lastblockheight + 1), self.lastblocktime + 1) block.nVersion = 4 for tx in txs: diff --git a/test/functional/feature_pruning.py b/test/functional/feature_pruning.py index 772151dc4b..9a3f4fae45 100755 --- a/test/functional/feature_pruning.py +++ b/test/functional/feature_pruning.py @@ -29,7 +29,7 @@ class PruneTest(BitcoinTestFramework): def set_test_params(self): self.setup_clean_chain = True self.num_nodes = 6 - self.rpc_timewait = 900 + self.rpc_timeout = 900 # Create nodes 0 and 1 to mine. # Create node 2 to test pruning. @@ -63,6 +63,8 @@ class PruneTest(BitcoinTestFramework): def setup_nodes(self): self.add_nodes(self.num_nodes, self.extra_args) self.start_nodes() + for n in self.nodes: + n.importprivkey(privkey=n.get_deterministic_priv_key().key, label='coinbase', rescan=False) def create_big_chain(self): # Start by creating some coinbases we can spend later @@ -189,6 +191,8 @@ class PruneTest(BitcoinTestFramework): def reorg_back(self): # Verify that a block on the old main chain fork has been pruned away assert_raises_rpc_error(-1, "Block not available (pruned data)", self.nodes[2].getblock, self.forkhash) + with self.nodes[2].assert_debug_log(expected_msgs=['block verification stopping at height', '(pruning, no data)']): + self.nodes[2].verifychain(checklevel=4, nblocks=0) self.log.info("Will need to redownload block %d" % self.forkheight) # Verify that we have enough history to reorg back to the fork point @@ -247,7 +251,7 @@ class PruneTest(BitcoinTestFramework): return index def prune(index, expected_ret=None): - ret = node.pruneblockchain(height(index)) + ret = node.pruneblockchain(height=height(index)) # Check the return value. When use_timestamp is True, just check # that the return value is less than or equal to the expected # value, because when more than one block is generated per second, diff --git a/test/functional/feature_segwit.py b/test/functional/feature_segwit.py index 2cbfc26e89..4bcdf9af55 100755 --- a/test/functional/feature_segwit.py +++ b/test/functional/feature_segwit.py @@ -5,11 +5,10 @@ """Test the SegWit changeover logic.""" from decimal import Decimal +from io import BytesIO from test_framework.address import ( key_to_p2pkh, - key_to_p2sh_p2wpkh, - key_to_p2wpkh, program_to_witness, script_to_p2sh, script_to_p2sh_p2wsh, @@ -21,8 +20,6 @@ from test_framework.script import CScript, OP_HASH160, OP_CHECKSIG, OP_0, hash16 from test_framework.test_framework import BitcoinTestFramework from test_framework.util import assert_equal, assert_raises_rpc_error, bytes_to_hex_str, connect_nodes, hex_str_to_bytes, sync_blocks, try_rpc -from io import BytesIO - NODE_0 = 0 NODE_2 = 2 WIT_V0 = 0 @@ -51,20 +48,17 @@ class SegWitTest(BitcoinTestFramework): "-rpcserialversion=0", "-vbparams=segwit:0:999999999999", "-addresstype=legacy", - "-deprecatedrpc=addwitnessaddress", ], [ "-blockversion=4", "-rpcserialversion=1", "-vbparams=segwit:0:999999999999", "-addresstype=legacy", - "-deprecatedrpc=addwitnessaddress", ], [ "-blockversion=536870915", "-vbparams=segwit:0:999999999999", "-addresstype=legacy", - "-deprecatedrpc=addwitnessaddress", ], ] @@ -91,40 +85,35 @@ class SegWitTest(BitcoinTestFramework): def fail_accept(self, node, error_msg, txid, sign, redeem_script=""): assert_raises_rpc_error(-26, error_msg, send_to_witness, use_p2wsh=1, node=node, utxo=getutxo(txid), pubkey=self.pubkey[0], encode_p2sh=False, amount=Decimal("49.998"), sign=sign, insert_redeem_script=redeem_script) - def run_test(self): - self.nodes[0].generate(161) #block 161 + self.nodes[0].generate(161) # block 161 self.log.info("Verify sigops are counted in GBT with pre-BIP141 rules before the fork") txid = self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), 1) - tmpl = self.nodes[0].getblocktemplate({}) + tmpl = self.nodes[0].getblocktemplate({'rules': ['segwit']}) assert(tmpl['sizelimit'] == 1000000) assert('weightlimit' not in tmpl) assert(tmpl['sigoplimit'] == 20000) assert(tmpl['transactions'][0]['hash'] == txid) assert(tmpl['transactions'][0]['sigops'] == 2) - tmpl = self.nodes[0].getblocktemplate({'rules':['segwit']}) + tmpl = self.nodes[0].getblocktemplate({'rules': ['segwit']}) assert(tmpl['sizelimit'] == 1000000) assert('weightlimit' not in tmpl) assert(tmpl['sigoplimit'] == 20000) assert(tmpl['transactions'][0]['hash'] == txid) assert(tmpl['transactions'][0]['sigops'] == 2) - self.nodes[0].generate(1) #block 162 + self.nodes[0].generate(1) # block 162 balance_presetup = self.nodes[0].getbalance() self.pubkey = [] - p2sh_ids = [] # p2sh_ids[NODE][VER] is an array of txids that spend to a witness version VER pkscript to an address for NODE embedded in p2sh - wit_ids = [] # wit_ids[NODE][VER] is an array of txids that spend to a witness version VER pkscript to an address for NODE via bare witness + p2sh_ids = [] # p2sh_ids[NODE][VER] is an array of txids that spend to a witness version VER pkscript to an address for NODE embedded in p2sh + wit_ids = [] # wit_ids[NODE][VER] is an array of txids that spend to a witness version VER pkscript to an address for NODE via bare witness for i in range(3): newaddress = self.nodes[i].getnewaddress() self.pubkey.append(self.nodes[i].getaddressinfo(newaddress)["pubkey"]) multiscript = CScript([OP_1, hex_str_to_bytes(self.pubkey[-1]), OP_1, OP_CHECKMULTISIG]) - p2sh_addr = self.nodes[i].addwitnessaddress(newaddress) - bip173_addr = self.nodes[i].addwitnessaddress(newaddress, False) p2sh_ms_addr = self.nodes[i].addmultisigaddress(1, [self.pubkey[-1]], '', 'p2sh-segwit')['address'] bip173_ms_addr = self.nodes[i].addmultisigaddress(1, [self.pubkey[-1]], '', 'bech32')['address'] - assert_equal(p2sh_addr, key_to_p2sh_p2wpkh(self.pubkey[-1])) - assert_equal(bip173_addr, key_to_p2wpkh(self.pubkey[-1])) assert_equal(p2sh_ms_addr, script_to_p2sh_p2wsh(multiscript)) assert_equal(bip173_ms_addr, script_to_p2wsh(multiscript)) p2sh_ids.append([]) @@ -139,32 +128,32 @@ class SegWitTest(BitcoinTestFramework): wit_ids[n][v].append(send_to_witness(v, self.nodes[0], find_spendable_utxo(self.nodes[0], 50), self.pubkey[n], False, Decimal("49.999"))) p2sh_ids[n][v].append(send_to_witness(v, self.nodes[0], find_spendable_utxo(self.nodes[0], 50), self.pubkey[n], True, Decimal("49.999"))) - self.nodes[0].generate(1) #block 163 + self.nodes[0].generate(1) # block 163 sync_blocks(self.nodes) # Make sure all nodes recognize the transactions as theirs - assert_equal(self.nodes[0].getbalance(), balance_presetup - 60*50 + 20*Decimal("49.999") + 50) - assert_equal(self.nodes[1].getbalance(), 20*Decimal("49.999")) - assert_equal(self.nodes[2].getbalance(), 20*Decimal("49.999")) + assert_equal(self.nodes[0].getbalance(), balance_presetup - 60 * 50 + 20 * Decimal("49.999") + 50) + assert_equal(self.nodes[1].getbalance(), 20 * Decimal("49.999")) + assert_equal(self.nodes[2].getbalance(), 20 * Decimal("49.999")) - self.nodes[0].generate(260) #block 423 + self.nodes[0].generate(260) # block 423 sync_blocks(self.nodes) self.log.info("Verify witness txs are skipped for mining before the fork") - self.skip_mine(self.nodes[2], wit_ids[NODE_2][WIT_V0][0], True) #block 424 - self.skip_mine(self.nodes[2], wit_ids[NODE_2][WIT_V1][0], True) #block 425 - self.skip_mine(self.nodes[2], p2sh_ids[NODE_2][WIT_V0][0], True) #block 426 - self.skip_mine(self.nodes[2], p2sh_ids[NODE_2][WIT_V1][0], True) #block 427 + self.skip_mine(self.nodes[2], wit_ids[NODE_2][WIT_V0][0], True) # block 424 + self.skip_mine(self.nodes[2], wit_ids[NODE_2][WIT_V1][0], True) # block 425 + self.skip_mine(self.nodes[2], p2sh_ids[NODE_2][WIT_V0][0], True) # block 426 + self.skip_mine(self.nodes[2], p2sh_ids[NODE_2][WIT_V1][0], True) # block 427 self.log.info("Verify unsigned p2sh witness txs without a redeem script are invalid") self.fail_accept(self.nodes[2], "mandatory-script-verify-flag", p2sh_ids[NODE_2][WIT_V0][1], False) self.fail_accept(self.nodes[2], "mandatory-script-verify-flag", p2sh_ids[NODE_2][WIT_V1][1], False) - self.nodes[2].generate(4) # blocks 428-431 + self.nodes[2].generate(4) # blocks 428-431 self.log.info("Verify previous witness txs skipped for mining can now be mined") assert_equal(len(self.nodes[2].getrawmempool()), 4) - block = self.nodes[2].generate(1) #block 432 (first block with new rules; 432 = 144 * 3) + block = self.nodes[2].generate(1) # block 432 (first block with new rules; 432 = 144 * 3) sync_blocks(self.nodes) assert_equal(len(self.nodes[2].getrawmempool()), 0) segwit_tx_list = self.nodes[2].getblock(block[0])["tx"] @@ -181,8 +170,8 @@ class SegWitTest(BitcoinTestFramework): self.fail_accept(self.nodes[0], "mandatory-script-verify-flag", p2sh_ids[NODE_0][WIT_V1][0], False, witness_script(True, self.pubkey[0])) self.log.info("Verify block and transaction serialization rpcs return differing serializations depending on rpc serialization flag") - assert(self.nodes[2].getblock(block[0], False) != self.nodes[0].getblock(block[0], False)) - assert(self.nodes[1].getblock(block[0], False) == self.nodes[2].getblock(block[0], False)) + assert(self.nodes[2].getblock(block[0], False) != self.nodes[0].getblock(block[0], False)) + assert(self.nodes[1].getblock(block[0], False) == self.nodes[2].getblock(block[0], False)) for i in range(len(segwit_tx_list)): tx = FromHex(CTransaction(), self.nodes[2].gettransaction(segwit_tx_list[i])["hex"]) assert(self.nodes[2].getrawtransaction(segwit_tx_list[i]) != self.nodes[0].getrawtransaction(segwit_tx_list[i])) @@ -198,21 +187,21 @@ class SegWitTest(BitcoinTestFramework): self.fail_accept(self.nodes[2], 'non-mandatory-script-verify-flag (Witness program was passed an empty witness) (code 64)', p2sh_ids[NODE_2][WIT_V1][2], sign=False, redeem_script=witness_script(True, self.pubkey[2])) self.log.info("Verify default node can now use witness txs") - self.success_mine(self.nodes[0], wit_ids[NODE_0][WIT_V0][0], True) #block 432 - self.success_mine(self.nodes[0], wit_ids[NODE_0][WIT_V1][0], True) #block 433 - self.success_mine(self.nodes[0], p2sh_ids[NODE_0][WIT_V0][0], True) #block 434 - self.success_mine(self.nodes[0], p2sh_ids[NODE_0][WIT_V1][0], True) #block 435 + self.success_mine(self.nodes[0], wit_ids[NODE_0][WIT_V0][0], True) # block 432 + self.success_mine(self.nodes[0], wit_ids[NODE_0][WIT_V1][0], True) # block 433 + self.success_mine(self.nodes[0], p2sh_ids[NODE_0][WIT_V0][0], True) # block 434 + self.success_mine(self.nodes[0], p2sh_ids[NODE_0][WIT_V1][0], True) # block 435 self.log.info("Verify sigops are counted in GBT with BIP141 rules after the fork") txid = self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), 1) - tmpl = self.nodes[0].getblocktemplate({'rules':['segwit']}) + tmpl = self.nodes[0].getblocktemplate({'rules': ['segwit']}) assert(tmpl['sizelimit'] >= 3999577) # actual maximum size is lower due to minimum mandatory non-witness data assert(tmpl['weightlimit'] == 4000000) assert(tmpl['sigoplimit'] == 80000) assert(tmpl['transactions'][0]['txid'] == txid) assert(tmpl['transactions'][0]['sigops'] == 8) - self.nodes[0].generate(1) # Mine a block to clear the gbt cache + self.nodes[0].generate(1) # Mine a block to clear the gbt cache self.log.info("Non-segwit miners are able to use GBT response after activation.") # Create a 3-tx chain: tx1 (non-segwit input, paying to a segwit output) -> @@ -222,7 +211,7 @@ class SegWitTest(BitcoinTestFramework): txid1 = send_to_witness(1, self.nodes[0], find_spendable_utxo(self.nodes[0], 50), self.pubkey[0], False, Decimal("49.996")) hex_tx = self.nodes[0].gettransaction(txid)['hex'] tx = FromHex(CTransaction(), hex_tx) - assert(tx.wit.is_null()) # This should not be a segwit input + assert(tx.wit.is_null()) # This should not be a segwit input assert(txid1 in self.nodes[0].getrawmempool()) # Now create tx2, which will spend from txid1. @@ -243,17 +232,9 @@ class SegWitTest(BitcoinTestFramework): assert(tx.wit.is_null()) assert(txid3 in self.nodes[0].getrawmempool()) - # Now try calling getblocktemplate() without segwit support. - template = self.nodes[0].getblocktemplate() - - # Check that tx1 is the only transaction of the 3 in the template. - template_txids = [ t['txid'] for t in template['transactions'] ] - assert(txid2 not in template_txids and txid3 not in template_txids) - assert(txid1 in template_txids) - - # Check that running with segwit support results in all 3 being included. + # Check that getblocktemplate includes all transactions. template = self.nodes[0].getblocktemplate({"rules": ["segwit"]}) - template_txids = [ t['txid'] for t in template['transactions'] ] + template_txids = [t['txid'] for t in template['transactions']] assert(txid1 in template_txids) assert(txid2 in template_txids) assert(txid3 in template_txids) @@ -264,17 +245,17 @@ class SegWitTest(BitcoinTestFramework): # Mine a block to clear the gbt cache again. self.nodes[0].generate(1) - self.log.info("Verify behaviour of importaddress, addwitnessaddress and listunspent") + self.log.info("Verify behaviour of importaddress and listunspent") # Some public keys to be used later pubkeys = [ - "0363D44AABD0F1699138239DF2F042C3282C0671CC7A76826A55C8203D90E39242", # cPiM8Ub4heR9NBYmgVzJQiUH1if44GSBGiqaeJySuL2BKxubvgwb - "02D3E626B3E616FC8662B489C123349FECBFC611E778E5BE739B257EAE4721E5BF", # cPpAdHaD6VoYbW78kveN2bsvb45Q7G5PhaPApVUGwvF8VQ9brD97 - "04A47F2CBCEFFA7B9BCDA184E7D5668D3DA6F9079AD41E422FA5FD7B2D458F2538A62F5BD8EC85C2477F39650BD391EA6250207065B2A81DA8B009FC891E898F0E", # 91zqCU5B9sdWxzMt1ca3VzbtVm2YM6Hi5Rxn4UDtxEaN9C9nzXV - "02A47F2CBCEFFA7B9BCDA184E7D5668D3DA6F9079AD41E422FA5FD7B2D458F2538", # cPQFjcVRpAUBG8BA9hzr2yEzHwKoMgLkJZBBtK9vJnvGJgMjzTbd - "036722F784214129FEB9E8129D626324F3F6716555B603FFE8300BBCB882151228", # cQGtcm34xiLjB1v7bkRa4V3aAc9tS2UTuBZ1UnZGeSeNy627fN66 - "0266A8396EE936BF6D99D17920DB21C6C7B1AB14C639D5CD72B300297E416FD2EC", # cTW5mR5M45vHxXkeChZdtSPozrFwFgmEvTNnanCW6wrqwaCZ1X7K - "0450A38BD7F0AC212FEBA77354A9B036A32E0F7C81FC4E0C5ADCA7C549C4505D2522458C2D9AE3CEFD684E039194B72C8A10F9CB9D4764AB26FCC2718D421D3B84", # 92h2XPssjBpsJN5CqSP7v9a7cf2kgDunBC6PDFwJHMACM1rrVBJ + "0363D44AABD0F1699138239DF2F042C3282C0671CC7A76826A55C8203D90E39242", # cPiM8Ub4heR9NBYmgVzJQiUH1if44GSBGiqaeJySuL2BKxubvgwb + "02D3E626B3E616FC8662B489C123349FECBFC611E778E5BE739B257EAE4721E5BF", # cPpAdHaD6VoYbW78kveN2bsvb45Q7G5PhaPApVUGwvF8VQ9brD97 + "04A47F2CBCEFFA7B9BCDA184E7D5668D3DA6F9079AD41E422FA5FD7B2D458F2538A62F5BD8EC85C2477F39650BD391EA6250207065B2A81DA8B009FC891E898F0E", # 91zqCU5B9sdWxzMt1ca3VzbtVm2YM6Hi5Rxn4UDtxEaN9C9nzXV + "02A47F2CBCEFFA7B9BCDA184E7D5668D3DA6F9079AD41E422FA5FD7B2D458F2538", # cPQFjcVRpAUBG8BA9hzr2yEzHwKoMgLkJZBBtK9vJnvGJgMjzTbd + "036722F784214129FEB9E8129D626324F3F6716555B603FFE8300BBCB882151228", # cQGtcm34xiLjB1v7bkRa4V3aAc9tS2UTuBZ1UnZGeSeNy627fN66 + "0266A8396EE936BF6D99D17920DB21C6C7B1AB14C639D5CD72B300297E416FD2EC", # cTW5mR5M45vHxXkeChZdtSPozrFwFgmEvTNnanCW6wrqwaCZ1X7K + "0450A38BD7F0AC212FEBA77354A9B036A32E0F7C81FC4E0C5ADCA7C549C4505D2522458C2D9AE3CEFD684E039194B72C8A10F9CB9D4764AB26FCC2718D421D3B84", # 92h2XPssjBpsJN5CqSP7v9a7cf2kgDunBC6PDFwJHMACM1rrVBJ ] # Import a compressed key and an uncompressed key, generate some multisig addresses @@ -282,8 +263,8 @@ class SegWitTest(BitcoinTestFramework): uncompressed_spendable_address = ["mvozP4UwyGD2mGZU4D2eMvMLPB9WkMmMQu"] self.nodes[0].importprivkey("cNC8eQ5dg3mFAVePDX4ddmPYpPbw41r9bm2jd1nLJT77e6RrzTRR") compressed_spendable_address = ["mmWQubrDomqpgSYekvsU7HWEVjLFHAakLe"] - assert ((self.nodes[0].getaddressinfo(uncompressed_spendable_address[0])['iscompressed'] == False)) - assert ((self.nodes[0].getaddressinfo(compressed_spendable_address[0])['iscompressed'] == True)) + assert not self.nodes[0].getaddressinfo(uncompressed_spendable_address[0])['iscompressed'] + assert self.nodes[0].getaddressinfo(compressed_spendable_address[0])['iscompressed'] self.nodes[0].importpubkey(pubkeys[0]) compressed_solvable_address = [key_to_p2pkh(pubkeys[0])] @@ -305,7 +286,6 @@ class SegWitTest(BitcoinTestFramework): uncompressed_solvable_address.append(self.nodes[0].addmultisigaddress(2, [compressed_spendable_address[0], uncompressed_solvable_address[0]])['address']) compressed_solvable_address.append(self.nodes[0].addmultisigaddress(2, [compressed_spendable_address[0], compressed_solvable_address[0]])['address']) compressed_solvable_address.append(self.nodes[0].addmultisigaddress(2, [compressed_solvable_address[0], compressed_solvable_address[1]])['address']) - unknown_address = ["mtKKyoHabkk6e4ppT7NaM7THqPUt7AzPrT", "2NDP3jLWAFT8NDAiUa9qiE6oBt2awmMq7Dx"] # Test multisig_without_privkey # We have 2 public keys without private keys, use addmultisigaddress to add to wallet. @@ -386,7 +366,6 @@ class SegWitTest(BitcoinTestFramework): op1 = CScript([OP_1]) op0 = CScript([OP_0]) # 2N7MGY19ti4KDMSzRfPAssP6Pxyuxoi6jLe is the P2SH(P2PKH) version of mjoE3sSrb8ByYEvgnC3Aox86u1CHnfJA4V - unsolvable_address = ["mjoE3sSrb8ByYEvgnC3Aox86u1CHnfJA4V", "2N7MGY19ti4KDMSzRfPAssP6Pxyuxoi6jLe", script_to_p2sh(op1), script_to_p2sh(op0)] unsolvable_address_key = hex_str_to_bytes("02341AEC7587A51CDE5279E0630A531AEA2615A9F80B17E8D9376327BAEAA59E3D") unsolvablep2pkh = CScript([OP_DUP, OP_HASH160, hash160(unsolvable_address_key), OP_EQUALVERIFY, OP_CHECKSIG]) unsolvablep2wshp2pkh = CScript([OP_0, sha256(unsolvablep2pkh)]) @@ -394,9 +373,9 @@ class SegWitTest(BitcoinTestFramework): p2wshop1 = CScript([OP_0, sha256(op1)]) unsolvable_after_importaddress.append(unsolvablep2pkh) unsolvable_after_importaddress.append(unsolvablep2wshp2pkh) - unsolvable_after_importaddress.append(op1) # OP_1 will be imported as script + unsolvable_after_importaddress.append(op1) # OP_1 will be imported as script unsolvable_after_importaddress.append(p2wshop1) - unseen_anytime.append(op0) # OP_0 will be imported as P2SH address with no script provided + unseen_anytime.append(op0) # OP_0 will be imported as P2SH address with no script provided unsolvable_after_importaddress.append(p2shop0) spendable_txid = [] @@ -432,27 +411,14 @@ class SegWitTest(BitcoinTestFramework): # exceptions and continue. try_rpc(-4, "The wallet already contains the private key for this address or script", self.nodes[0].importaddress, i, "", False, True) - self.nodes[0].importaddress(script_to_p2sh(op0)) # import OP_0 as address only - self.nodes[0].importaddress(multisig_without_privkey_address) # Test multisig_without_privkey + self.nodes[0].importaddress(script_to_p2sh(op0)) # import OP_0 as address only + self.nodes[0].importaddress(multisig_without_privkey_address) # Test multisig_without_privkey spendable_txid.append(self.mine_and_test_listunspent(spendable_anytime + spendable_after_importaddress, 2)) solvable_txid.append(self.mine_and_test_listunspent(solvable_anytime + solvable_after_importaddress, 1)) self.mine_and_test_listunspent(unsolvable_after_importaddress, 1) self.mine_and_test_listunspent(unseen_anytime, 0) - # addwitnessaddress should refuse to return a witness address if an uncompressed key is used - # note that no witness address should be returned by unsolvable addresses - for i in uncompressed_spendable_address + uncompressed_solvable_address + unknown_address + unsolvable_address: - assert_raises_rpc_error(-4, "Public key or redeemscript not known to wallet, or the key is uncompressed", self.nodes[0].addwitnessaddress, i) - - # addwitnessaddress should return a witness addresses even if keys are not in the wallet - self.nodes[0].addwitnessaddress(multisig_without_privkey_address) - - for i in compressed_spendable_address + compressed_solvable_address: - witaddress = self.nodes[0].addwitnessaddress(i) - # addwitnessaddress should return the same address if it is a known P2SH-witness address - assert_equal(witaddress, self.nodes[0].addwitnessaddress(witaddress)) - spendable_txid.append(self.mine_and_test_listunspent(spendable_anytime + spendable_after_importaddress, 2)) solvable_txid.append(self.mine_and_test_listunspent(solvable_anytime + solvable_after_importaddress, 1)) self.mine_and_test_listunspent(unsolvable_after_importaddress, 1) @@ -470,8 +436,6 @@ class SegWitTest(BitcoinTestFramework): self.nodes[0].importpubkey(pubkeys[6]) uncompressed_solvable_address = [key_to_p2pkh(pubkeys[6])] - spendable_after_addwitnessaddress = [] # These outputs should be seen after importaddress - solvable_after_addwitnessaddress=[] # These outputs should be seen after importaddress but not spendable unseen_anytime = [] # These outputs should never be seen solvable_anytime = [] # These outputs should be solvable after importpubkey unseen_anytime = [] # These outputs should never be seen @@ -488,8 +452,6 @@ class SegWitTest(BitcoinTestFramework): v = self.nodes[0].getaddressinfo(i) if (v['isscript']): [bare, p2sh, p2wsh, p2sh_p2wsh] = self.p2sh_address_to_script(v) - # P2WSH and P2SH(P2WSH) multisig with compressed keys are spendable after addwitnessaddress - spendable_after_addwitnessaddress.extend([p2wsh, p2sh_p2wsh]) premature_witaddress.append(script_to_p2sh(p2wsh)) else: [p2wpkh, p2sh_p2wpkh, p2pk, p2pkh, p2sh_p2pk, p2sh_p2pkh, p2wsh_p2pk, p2wsh_p2pkh, p2sh_p2wsh_p2pk, p2sh_p2wsh_p2pkh] = self.p2pkh_address_to_script(v) @@ -510,9 +472,7 @@ class SegWitTest(BitcoinTestFramework): for i in compressed_solvable_address: v = self.nodes[0].getaddressinfo(i) if (v['isscript']): - # P2WSH multisig without private key are seen after addwitnessaddress [bare, p2sh, p2wsh, p2sh_p2wsh] = self.p2sh_address_to_script(v) - solvable_after_addwitnessaddress.extend([p2wsh, p2sh_p2wsh]) premature_witaddress.append(script_to_p2sh(p2wsh)) else: [p2wpkh, p2sh_p2wpkh, p2pk, p2pkh, p2sh_p2pk, p2sh_p2pkh, p2wsh_p2pk, p2wsh_p2pkh, p2sh_p2wsh_p2pk, p2sh_p2wsh_p2pkh] = self.p2pkh_address_to_script(v) @@ -521,29 +481,11 @@ class SegWitTest(BitcoinTestFramework): self.mine_and_test_listunspent(spendable_anytime, 2) self.mine_and_test_listunspent(solvable_anytime, 1) - self.mine_and_test_listunspent(spendable_after_addwitnessaddress + solvable_after_addwitnessaddress + unseen_anytime, 0) - - # addwitnessaddress should refuse to return a witness address if an uncompressed key is used - # note that a multisig address returned by addmultisigaddress is not solvable until it is added with importaddress - # premature_witaddress are not accepted until the script is added with addwitnessaddress first - for i in uncompressed_spendable_address + uncompressed_solvable_address + premature_witaddress: - # This will raise an exception - assert_raises_rpc_error(-4, "Public key or redeemscript not known to wallet, or the key is uncompressed", self.nodes[0].addwitnessaddress, i) - - # after importaddress it should pass addwitnessaddress - v = self.nodes[0].getaddressinfo(compressed_solvable_address[1]) - self.nodes[0].importaddress(v['hex'],"",False,True) - for i in compressed_spendable_address + compressed_solvable_address + premature_witaddress: - witaddress = self.nodes[0].addwitnessaddress(i) - assert_equal(witaddress, self.nodes[0].addwitnessaddress(witaddress)) - - spendable_txid.append(self.mine_and_test_listunspent(spendable_after_addwitnessaddress + spendable_anytime, 2)) - solvable_txid.append(self.mine_and_test_listunspent(solvable_after_addwitnessaddress + solvable_anytime, 1)) self.mine_and_test_listunspent(unseen_anytime, 0) # Check that createrawtransaction/decoderawtransaction with non-v0 Bech32 works - v1_addr = program_to_witness(1, [3,5]) - v1_tx = self.nodes[0].createrawtransaction([getutxo(spendable_txid[0])],{v1_addr: 1}) + v1_addr = program_to_witness(1, [3, 5]) + v1_tx = self.nodes[0].createrawtransaction([getutxo(spendable_txid[0])], {v1_addr: 1}) v1_decoded = self.nodes[1].decoderawtransaction(v1_tx) assert_equal(v1_decoded['vout'][0]['scriptPubKey']['addresses'][0], v1_addr) assert_equal(v1_decoded['vout'][0]['scriptPubKey']['hex'], "51020305") @@ -586,7 +528,7 @@ class SegWitTest(BitcoinTestFramework): def mine_and_test_listunspent(self, script_list, ismine): utxo = find_spendable_utxo(self.nodes[0], 50) tx = CTransaction() - tx.vin.append(CTxIn(COutPoint(int('0x'+utxo['txid'],0), utxo['vout']))) + tx.vin.append(CTxIn(COutPoint(int('0x' + utxo['txid'], 0), utxo['vout']))) for i in script_list: tx.vout.append(CTxOut(10000000, i)) tx.rehash() @@ -599,7 +541,7 @@ class SegWitTest(BitcoinTestFramework): for i in self.nodes[0].listunspent(): if (i['txid'] == txid): watchcount += 1 - if (i['spendable'] == True): + if i['spendable']: spendcount += 1 if (ismine == 2): assert_equal(spendcount, len(script_list)) @@ -610,14 +552,14 @@ class SegWitTest(BitcoinTestFramework): assert_equal(watchcount, 0) return txid - def p2sh_address_to_script(self,v): + def p2sh_address_to_script(self, v): bare = CScript(hex_str_to_bytes(v['hex'])) p2sh = CScript(hex_str_to_bytes(v['scriptPubKey'])) p2wsh = CScript([OP_0, sha256(bare)]) p2sh_p2wsh = CScript([OP_HASH160, hash160(p2wsh), OP_EQUAL]) return([bare, p2sh, p2wsh, p2sh_p2wsh]) - def p2pkh_address_to_script(self,v): + def p2pkh_address_to_script(self, v): pubkey = hex_str_to_bytes(v['pubkey']) p2wpkh = CScript([OP_0, hash160(pubkey)]) p2sh_p2wpkh = CScript([OP_HASH160, hash160(p2wpkh), OP_EQUAL]) @@ -631,7 +573,7 @@ class SegWitTest(BitcoinTestFramework): p2sh_p2wsh_p2pkh = CScript([OP_HASH160, hash160(p2wsh_p2pkh), OP_EQUAL]) return [p2wpkh, p2sh_p2wpkh, p2pk, p2pkh, p2sh_p2pk, p2sh_p2pkh, p2wsh_p2pk, p2wsh_p2pkh, p2sh_p2wsh_p2pk, p2sh_p2wsh_p2pkh] - def create_and_mine_tx_from_txids(self, txids, success = True): + def create_and_mine_tx_from_txids(self, txids, success=True): tx = CTransaction() for i in txids: txtmp = CTransaction() @@ -639,7 +581,7 @@ class SegWitTest(BitcoinTestFramework): f = BytesIO(hex_str_to_bytes(txraw)) txtmp.deserialize(f) for j in range(len(txtmp.vout)): - tx.vin.append(CTxIn(COutPoint(int('0x'+i,0), j))) + tx.vin.append(CTxIn(COutPoint(int('0x' + i, 0), j))) tx.vout.append(CTxOut(0, CScript())) tx.rehash() signresults = self.nodes[0].signrawtransactionwithwallet(bytes_to_hex_str(tx.serialize_without_witness()))['hex'] diff --git a/test/functional/feature_shutdown.py b/test/functional/feature_shutdown.py new file mode 100755 index 0000000000..5084cb1322 --- /dev/null +++ b/test/functional/feature_shutdown.py @@ -0,0 +1,34 @@ +#!/usr/bin/env python3 +# Copyright (c) 2018 The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. +"""Test bitcoind shutdown.""" + +from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import assert_equal, get_rpc_proxy, wait_until +from threading import Thread + +def test_long_call(node): + block = node.waitfornewblock() + assert_equal(block['height'], 0) + +class ShutdownTest(BitcoinTestFramework): + + def set_test_params(self): + self.setup_clean_chain = True + self.num_nodes = 1 + + def run_test(self): + node = get_rpc_proxy(self.nodes[0].url, 1, timeout=600, coveragedir=self.nodes[0].coverage_dir) + # Force connection establishment by executing a dummy command. + node.getblockcount() + Thread(target=test_long_call, args=(node,)).start() + # Wait until the server is executing the above `waitfornewblock`. + wait_until(lambda: len(self.nodes[0].getrpcinfo()['active_commands']) == 2) + # Wait 1 second after requesting shutdown but not before the `stop` call + # finishes. This is to ensure event loop waits for current connections + # to close. + self.stop_node(0, wait=1000) + +if __name__ == '__main__': + ShutdownTest().main() diff --git a/test/functional/feature_uacomment.py b/test/functional/feature_uacomment.py index 691a39b825..fb4ad21359 100755 --- a/test/functional/feature_uacomment.py +++ b/test/functional/feature_uacomment.py @@ -31,7 +31,7 @@ class UacommentTest(BitcoinTestFramework): self.nodes[0].assert_start_raises_init_error(["-uacomment=" + 'a' * 256], expected, match=ErrorMatch.FULL_REGEX) self.log.info("test -uacomment unsafe characters") - for unsafe_char in ['/', ':', '(', ')']: + for unsafe_char in ['/', ':', '(', ')', '₿', '🏃']: expected = "Error: User Agent comment \(" + re.escape(unsafe_char) + "\) contains unsafe characters." self.nodes[0].assert_start_raises_init_error(["-uacomment=" + unsafe_char], expected, match=ErrorMatch.FULL_REGEX) diff --git a/test/functional/interface_bitcoin_cli.py b/test/functional/interface_bitcoin_cli.py index f311858bee..aed439339f 100755 --- a/test/functional/interface_bitcoin_cli.py +++ b/test/functional/interface_bitcoin_cli.py @@ -12,19 +12,17 @@ class TestBitcoinCli(BitcoinTestFramework): self.setup_clean_chain = True self.num_nodes = 1 - def skip_test_if_missing_module(self): - self.skip_if_no_wallet() - def run_test(self): """Main test logic""" cli_response = self.nodes[0].cli("-version").send_cli() assert("Bitcoin Core RPC client version" in cli_response) - self.log.info("Compare responses from gewalletinfo RPC and `bitcoin-cli getwalletinfo`") - cli_response = self.nodes[0].cli.getwalletinfo() - rpc_response = self.nodes[0].getwalletinfo() - assert_equal(cli_response, rpc_response) + self.log.info("Compare responses from getwalletinfo RPC and `bitcoin-cli getwalletinfo`") + if self.is_wallet_compiled(): + cli_response = self.nodes[0].cli.getwalletinfo() + rpc_response = self.nodes[0].getwalletinfo() + assert_equal(cli_response, rpc_response) self.log.info("Compare responses from getblockchaininfo RPC and `bitcoin-cli getblockchaininfo`") cli_response = self.nodes[0].cli.getblockchaininfo() @@ -52,26 +50,30 @@ class TestBitcoinCli(BitcoinTestFramework): self.log.info("Compare responses from `bitcoin-cli -getinfo` and the RPCs data is retrieved from.") cli_get_info = self.nodes[0].cli('-getinfo').send_cli() - wallet_info = self.nodes[0].getwalletinfo() + if self.is_wallet_compiled(): + wallet_info = self.nodes[0].getwalletinfo() network_info = self.nodes[0].getnetworkinfo() blockchain_info = self.nodes[0].getblockchaininfo() assert_equal(cli_get_info['version'], network_info['version']) assert_equal(cli_get_info['protocolversion'], network_info['protocolversion']) - assert_equal(cli_get_info['walletversion'], wallet_info['walletversion']) - assert_equal(cli_get_info['balance'], wallet_info['balance']) + if self.is_wallet_compiled(): + assert_equal(cli_get_info['walletversion'], wallet_info['walletversion']) + assert_equal(cli_get_info['balance'], wallet_info['balance']) assert_equal(cli_get_info['blocks'], blockchain_info['blocks']) assert_equal(cli_get_info['timeoffset'], network_info['timeoffset']) assert_equal(cli_get_info['connections'], network_info['connections']) assert_equal(cli_get_info['proxy'], network_info['networks'][0]['proxy']) assert_equal(cli_get_info['difficulty'], blockchain_info['difficulty']) assert_equal(cli_get_info['testnet'], blockchain_info['chain'] == "test") - assert_equal(cli_get_info['balance'], wallet_info['balance']) - assert_equal(cli_get_info['keypoololdest'], wallet_info['keypoololdest']) - assert_equal(cli_get_info['keypoolsize'], wallet_info['keypoolsize']) - assert_equal(cli_get_info['paytxfee'], wallet_info['paytxfee']) - assert_equal(cli_get_info['relayfee'], network_info['relayfee']) - # unlocked_until is not tested because the wallet is not encrypted + if self.is_wallet_compiled(): + assert_equal(cli_get_info['balance'], wallet_info['balance']) + assert_equal(cli_get_info['keypoololdest'], wallet_info['keypoololdest']) + assert_equal(cli_get_info['keypoolsize'], wallet_info['keypoolsize']) + assert_equal(cli_get_info['paytxfee'], wallet_info['paytxfee']) + assert_equal(cli_get_info['relayfee'], network_info['relayfee']) + # unlocked_until is not tested because the wallet is not encrypted + if __name__ == '__main__': TestBitcoinCli().main() diff --git a/test/functional/interface_http.py b/test/functional/interface_http.py index e4b86f9e1e..20889366e5 100755 --- a/test/functional/interface_http.py +++ b/test/functional/interface_http.py @@ -31,13 +31,13 @@ class HTTPBasicsTest (BitcoinTestFramework): conn.request('POST', '/', '{"method": "getbestblockhash"}', headers) out1 = conn.getresponse().read() assert(b'"error":null' in out1) - assert(conn.sock!=None) #according to http/1.1 connection must still be open! + assert(conn.sock is not None) #according to http/1.1 connection must still be open! #send 2nd request without closing connection conn.request('POST', '/', '{"method": "getchaintips"}', headers) out1 = conn.getresponse().read() assert(b'"error":null' in out1) #must also response with a correct json-rpc message - assert(conn.sock!=None) #according to http/1.1 connection must still be open! + assert(conn.sock is not None) #according to http/1.1 connection must still be open! conn.close() #same should be if we add keep-alive because this should be the std. behaviour @@ -48,13 +48,13 @@ class HTTPBasicsTest (BitcoinTestFramework): conn.request('POST', '/', '{"method": "getbestblockhash"}', headers) out1 = conn.getresponse().read() assert(b'"error":null' in out1) - assert(conn.sock!=None) #according to http/1.1 connection must still be open! + assert(conn.sock is not None) #according to http/1.1 connection must still be open! #send 2nd request without closing connection conn.request('POST', '/', '{"method": "getchaintips"}', headers) out1 = conn.getresponse().read() assert(b'"error":null' in out1) #must also response with a correct json-rpc message - assert(conn.sock!=None) #according to http/1.1 connection must still be open! + assert(conn.sock is not None) #according to http/1.1 connection must still be open! conn.close() #now do the same with "Connection: close" @@ -65,7 +65,7 @@ class HTTPBasicsTest (BitcoinTestFramework): conn.request('POST', '/', '{"method": "getbestblockhash"}', headers) out1 = conn.getresponse().read() assert(b'"error":null' in out1) - assert(conn.sock==None) #now the connection must be closed after the response + assert(conn.sock is None) #now the connection must be closed after the response #node1 (2nd node) is running with disabled keep-alive option urlNode1 = urllib.parse.urlparse(self.nodes[1].url) @@ -88,7 +88,7 @@ class HTTPBasicsTest (BitcoinTestFramework): conn.request('POST', '/', '{"method": "getbestblockhash"}', headers) out1 = conn.getresponse().read() assert(b'"error":null' in out1) - assert(conn.sock!=None) #connection must be closed because bitcoind should use keep-alive by default + assert(conn.sock is not None) #connection must be closed because bitcoind should use keep-alive by default # Check excessive request size conn = http.client.HTTPConnection(urlNode2.hostname, urlNode2.port) diff --git a/test/functional/interface_rest.py b/test/functional/interface_rest.py index 525e3e3cf0..d5a1b53408 100755 --- a/test/functional/interface_rest.py +++ b/test/functional/interface_rest.py @@ -201,6 +201,16 @@ class RESTTest (BitcoinTestFramework): self.log.info("Test the /block, /blockhashbyheight and /headers URIs") bb_hash = self.nodes[0].getbestblockhash() + # Check result if block does not exists + assert_equal(self.test_rest_request('/headers/1/0000000000000000000000000000000000000000000000000000000000000000'), []) + self.test_rest_request('/block/0000000000000000000000000000000000000000000000000000000000000000', status=404, ret_type=RetType.OBJ) + + # Check result if block is not in the active chain + self.nodes[0].invalidateblock(bb_hash) + assert_equal(self.test_rest_request('/headers/1/{}'.format(bb_hash)), []) + self.test_rest_request('/block/{}'.format(bb_hash)) + self.nodes[0].reconsiderblock(bb_hash) + # Check binary format response = self.test_rest_request("/block/{}".format(bb_hash), req_type=ReqType.BIN, ret_type=RetType.OBJ) assert_greater_than(int(response.getheader('content-length')), 80) diff --git a/test/functional/interface_rpc.py b/test/functional/interface_rpc.py new file mode 100755 index 0000000000..b6955d4492 --- /dev/null +++ b/test/functional/interface_rpc.py @@ -0,0 +1,57 @@ +#!/usr/bin/env python3 +# Copyright (c) 2018 The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. +"""Tests some generic aspects of the RPC interface.""" + +from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import assert_equal, assert_greater_than_or_equal + +class RPCInterfaceTest(BitcoinTestFramework): + def set_test_params(self): + self.num_nodes = 1 + self.setup_clean_chain = True + + def test_getrpcinfo(self): + self.log.info("Testing getrpcinfo...") + + info = self.nodes[0].getrpcinfo() + assert_equal(len(info['active_commands']), 1) + + command = info['active_commands'][0] + assert_equal(command['method'], 'getrpcinfo') + assert_greater_than_or_equal(command['duration'], 0) + + def test_batch_request(self): + self.log.info("Testing basic JSON-RPC batch request...") + + results = self.nodes[0].batch([ + # A basic request that will work fine. + {"method": "getblockcount", "id": 1}, + # Request that will fail. The whole batch request should still + # work fine. + {"method": "invalidmethod", "id": 2}, + # Another call that should succeed. + {"method": "getbestblockhash", "id": 3}, + ]) + + result_by_id = {} + for res in results: + result_by_id[res["id"]] = res + + assert_equal(result_by_id[1]['error'], None) + assert_equal(result_by_id[1]['result'], 0) + + assert_equal(result_by_id[2]['error']['code'], -32601) + assert_equal(result_by_id[2]['result'], None) + + assert_equal(result_by_id[3]['error'], None) + assert result_by_id[3]['result'] is not None + + def run_test(self): + self.test_getrpcinfo() + self.test_batch_request() + + +if __name__ == '__main__': + RPCInterfaceTest().main() diff --git a/test/functional/interface_zmq.py b/test/functional/interface_zmq.py index 1c518eab75..94fea37090 100755 --- a/test/functional/interface_zmq.py +++ b/test/functional/interface_zmq.py @@ -5,6 +5,7 @@ """Test the ZMQ notification interface.""" import struct +from test_framework.address import ADDRESS_BCRT1_UNSPENDABLE from test_framework.test_framework import BitcoinTestFramework from test_framework.messages import CTransaction from test_framework.util import ( @@ -14,6 +15,7 @@ from test_framework.util import ( ) from io import BytesIO +ADDRESS = "tcp://127.0.0.1:28332" class ZMQSubscriber: def __init__(self, socket, topic): @@ -41,7 +43,6 @@ class ZMQTest (BitcoinTestFramework): def skip_test_if_missing_module(self): self.skip_if_no_py3_zmq() self.skip_if_no_bitcoind_zmq() - self.skip_if_no_wallet() def setup_nodes(self): import zmq @@ -51,11 +52,10 @@ class ZMQTest (BitcoinTestFramework): # that this test fails if the publishing order changes. # Note that the publishing order is not defined in the documentation and # is subject to change. - address = "tcp://127.0.0.1:28332" self.zmq_context = zmq.Context() socket = self.zmq_context.socket(zmq.SUB) socket.set(zmq.RCVTIMEO, 60000) - socket.connect(address) + socket.connect(ADDRESS) # Subscribe to all available topics. self.hashblock = ZMQSubscriber(socket, b"hashblock") @@ -64,11 +64,12 @@ class ZMQTest (BitcoinTestFramework): self.rawtx = ZMQSubscriber(socket, b"rawtx") self.extra_args = [ - ["-zmqpub%s=%s" % (sub.topic.decode(), address) for sub in [self.hashblock, self.hashtx, self.rawblock, self.rawtx]], + ["-zmqpub%s=%s" % (sub.topic.decode(), ADDRESS) for sub in [self.hashblock, self.hashtx, self.rawblock, self.rawtx]], [], ] self.add_nodes(self.num_nodes, self.extra_args) self.start_nodes() + self.import_deterministic_coinbase_privkeys() def run_test(self): try: @@ -81,7 +82,7 @@ class ZMQTest (BitcoinTestFramework): def _zmq_test(self): num_blocks = 5 self.log.info("Generate %(n)d blocks (and %(n)d coinbase txes)" % {"n": num_blocks}) - genhashes = self.nodes[0].generate(num_blocks) + genhashes = self.nodes[0].generatetoaddress(num_blocks, ADDRESS_BCRT1_UNSPENDABLE) self.sync_all() for x in range(num_blocks): @@ -105,17 +106,29 @@ class ZMQTest (BitcoinTestFramework): block = self.rawblock.receive() assert_equal(genhashes[x], bytes_to_hex_str(hash256(block[:80]))) - self.log.info("Wait for tx from second node") - payment_txid = self.nodes[1].sendtoaddress(self.nodes[0].getnewaddress(), 1.0) - self.sync_all() + if self.is_wallet_compiled(): + self.log.info("Wait for tx from second node") + payment_txid = self.nodes[1].sendtoaddress(self.nodes[0].getnewaddress(), 1.0) + self.sync_all() + + # Should receive the broadcasted txid. + txid = self.hashtx.receive() + assert_equal(payment_txid, bytes_to_hex_str(txid)) + + # Should receive the broadcasted raw transaction. + hex = self.rawtx.receive() + assert_equal(payment_txid, bytes_to_hex_str(hash256(hex))) + - # Should receive the broadcasted txid. - txid = self.hashtx.receive() - assert_equal(payment_txid, bytes_to_hex_str(txid)) + self.log.info("Test the getzmqnotifications RPC") + assert_equal(self.nodes[0].getzmqnotifications(), [ + {"type": "pubhashblock", "address": ADDRESS, "hwm": 1000}, + {"type": "pubhashtx", "address": ADDRESS, "hwm": 1000}, + {"type": "pubrawblock", "address": ADDRESS, "hwm": 1000}, + {"type": "pubrawtx", "address": ADDRESS, "hwm": 1000}, + ]) - # Should receive the broadcasted raw transaction. - hex = self.rawtx.receive() - assert_equal(payment_txid, bytes_to_hex_str(hash256(hex))) + assert_equal(self.nodes[1].getzmqnotifications(), []) if __name__ == '__main__': ZMQTest().main() diff --git a/test/functional/mempool_accept.py b/test/functional/mempool_accept.py index 8847777ba7..e2a219b85a 100755 --- a/test/functional/mempool_accept.py +++ b/test/functional/mempool_accept.py @@ -1,10 +1,12 @@ #!/usr/bin/env python3 -# Copyright (c) 2017 The Bitcoin Core developers +# Copyright (c) 2017-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. """Test mempool acceptance of raw transactions.""" from io import BytesIO +import math + from test_framework.test_framework import BitcoinTestFramework from test_framework.messages import ( BIP125_SEQUENCE_NUMBER, @@ -56,6 +58,7 @@ class MempoolAcceptanceTest(BitcoinTestFramework): self.mempool_size = 0 wait_until(lambda: node.getblockcount() == 200) assert_equal(node.getmempoolinfo()['size'], self.mempool_size) + coins = node.listunspent() self.log.info('Should not accept garbage to testmempoolaccept') assert_raises_rpc_error(-3, 'Expected type array, got string', lambda: node.testmempoolaccept(rawtxs='ff00baar')) @@ -63,13 +66,14 @@ class MempoolAcceptanceTest(BitcoinTestFramework): assert_raises_rpc_error(-22, 'TX decode failed', lambda: node.testmempoolaccept(rawtxs=['ff00baar'])) self.log.info('A transaction already in the blockchain') - coin = node.listunspent()[0] # Pick a random coin(base) to spend + coin = coins.pop() # Pick a random coin(base) to spend raw_tx_in_block = node.signrawtransactionwithwallet(node.createrawtransaction( inputs=[{'txid': coin['txid'], 'vout': coin['vout']}], outputs=[{node.getnewaddress(): 0.3}, {node.getnewaddress(): 49}], ))['hex'] txid_in_block = node.sendrawtransaction(hexstring=raw_tx_in_block, allowhighfees=True) node.generate(1) + self.mempool_size = 0 self.check_mempool_result( result_expected=[{'txid': txid_in_block, 'allowed': False, 'reject-reason': '18: txn-already-known'}], rawtxs=[raw_tx_in_block], @@ -89,9 +93,25 @@ class MempoolAcceptanceTest(BitcoinTestFramework): rawtxs=[raw_tx_0], ) + self.log.info('A final transaction not in the mempool') + coin = coins.pop() # Pick a random coin(base) to spend + raw_tx_final = node.signrawtransactionwithwallet(node.createrawtransaction( + inputs=[{'txid': coin['txid'], 'vout': coin['vout'], "sequence": 0xffffffff}], # SEQUENCE_FINAL + outputs=[{node.getnewaddress(): 0.025}], + locktime=node.getblockcount() + 2000, # Can be anything + ))['hex'] + tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_final))) + self.check_mempool_result( + result_expected=[{'txid': tx.rehash(), 'allowed': True}], + rawtxs=[bytes_to_hex_str(tx.serialize())], + allowhighfees=True, + ) + node.sendrawtransaction(hexstring=raw_tx_final, allowhighfees=True) + self.mempool_size += 1 + self.log.info('A transaction in the mempool') node.sendrawtransaction(hexstring=raw_tx_0) - self.mempool_size = 1 + self.mempool_size += 1 self.check_mempool_result( result_expected=[{'txid': txid_0, 'allowed': False, 'reject-reason': '18: txn-already-in-mempool'}], rawtxs=[raw_tx_0], @@ -181,7 +201,7 @@ class MempoolAcceptanceTest(BitcoinTestFramework): self.log.info('A really large transaction') tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_reference))) - tx.vin = [tx.vin[0]] * (MAX_BLOCK_BASE_SIZE // len(tx.vin[0].serialize())) + tx.vin = [tx.vin[0]] * math.ceil(MAX_BLOCK_BASE_SIZE / len(tx.vin[0].serialize())) self.check_mempool_result( result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': '16: bad-txns-oversize'}], rawtxs=[bytes_to_hex_str(tx.serialize())], diff --git a/test/functional/mempool_persist.py b/test/functional/mempool_persist.py index b4e9d967fd..d74d4eaaf1 100755 --- a/test/functional/mempool_persist.py +++ b/test/functional/mempool_persist.py @@ -42,6 +42,7 @@ import time from test_framework.test_framework import BitcoinTestFramework from test_framework.util import assert_equal, assert_raises_rpc_error, wait_until + class MempoolPersistTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 3 @@ -60,7 +61,7 @@ class MempoolPersistTest(BitcoinTestFramework): self.log.debug("Send 5 transactions from node2 (to its own address)") for i in range(5): - self.nodes[2].sendtoaddress(self.nodes[2].getnewaddress(), Decimal("10")) + last_txid = self.nodes[2].sendtoaddress(self.nodes[2].getnewaddress(), Decimal("10")) node2_balance = self.nodes[2].getbalance() self.sync_all() @@ -68,6 +69,13 @@ class MempoolPersistTest(BitcoinTestFramework): assert_equal(len(self.nodes[0].getrawmempool()), 5) assert_equal(len(self.nodes[1].getrawmempool()), 5) + self.log.debug("Prioritize a transaction on node0") + fees = self.nodes[0].getmempoolentry(txid=last_txid)['fees'] + assert_equal(fees['base'], fees['modified']) + self.nodes[0].prioritisetransaction(txid=last_txid, fee_delta=1000) + fees = self.nodes[0].getmempoolentry(txid=last_txid)['fees'] + assert_equal(fees['base'] + Decimal('0.00001000'), fees['modified']) + self.log.debug("Stop-start the nodes. Verify that node0 has the transactions in its mempool and node1 does not. Verify that node2 calculates its balance correctly after loading wallet transactions.") self.stop_nodes() # Give this node a head-start, so we can be "extra-sure" that it didn't load anything later @@ -81,6 +89,10 @@ class MempoolPersistTest(BitcoinTestFramework): # The others have loaded their mempool. If node_1 loaded anything, we'd probably notice by now: assert_equal(len(self.nodes[1].getrawmempool()), 0) + self.log.debug('Verify prioritization is loaded correctly') + fees = self.nodes[0].getmempoolentry(txid=last_txid)['fees'] + assert_equal(fees['base'] + Decimal('0.00001000'), fees['modified']) + # Verify accounting of mempool transactions after restart is correct self.nodes[2].syncwithvalidationinterfacequeue() # Flush mempool to wallet assert_equal(node2_balance, self.nodes[2].getbalance()) diff --git a/test/functional/mempool_resurrect.py b/test/functional/mempool_resurrect.py index d035ca907a..845beb551e 100755 --- a/test/functional/mempool_resurrect.py +++ b/test/functional/mempool_resurrect.py @@ -47,12 +47,11 @@ class MempoolCoinbaseTest(BitcoinTestFramework): tx = self.nodes[0].gettransaction(txid) assert(tx["confirmations"] > 0) - # Use invalidateblock to re-org back; all transactions should - # end up unconfirmed and back in the mempool + # Use invalidateblock to re-org back for node in self.nodes: node.invalidateblock(blocks[0]) - # mempool should be empty, all txns confirmed + # All txns should be back in mempool with 0 confirmations assert_equal(set(self.nodes[0].getrawmempool()), set(spends1_id+spends2_id)) for txid in spends1_id+spends2_id: tx = self.nodes[0].gettransaction(txid) diff --git a/test/functional/mining_basic.py b/test/functional/mining_basic.py index ff55ea5528..661d9f4c97 100755 --- a/test/functional/mining_basic.py +++ b/test/functional/mining_basic.py @@ -25,14 +25,15 @@ from test_framework.util import ( assert_raises_rpc_error, bytes_to_hex_str as b2x, ) - +from test_framework.script import CScriptNum def assert_template(node, block, expect, rehash=True): if rehash: block.hashMerkleRoot = block.calc_merkle_root() - rsp = node.getblocktemplate({'data': b2x(block.serialize()), 'mode': 'proposal'}) + rsp = node.getblocktemplate(template_request={'data': b2x(block.serialize()), 'mode': 'proposal', 'rules': ['segwit']}) assert_equal(rsp, expect) + class MiningTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 2 @@ -59,16 +60,24 @@ class MiningTest(BitcoinTestFramework): # Mine a block to leave initial block download node.generatetoaddress(1, node.get_deterministic_priv_key().address) - tmpl = node.getblocktemplate() + tmpl = node.getblocktemplate({'rules': ['segwit']}) self.log.info("getblocktemplate: Test capability advertised") assert 'proposal' in tmpl['capabilities'] assert 'coinbasetxn' not in tmpl - coinbase_tx = create_coinbase(height=int(tmpl["height"]) + 1) + next_height = int(tmpl["height"]) + coinbase_tx = create_coinbase(height=next_height) # sequence numbers must not be max for nLockTime to have effect coinbase_tx.vin[0].nSequence = 2 ** 32 - 2 coinbase_tx.rehash() + # round-trip the encoded bip34 block height commitment + assert_equal(CScriptNum.decode(coinbase_tx.vin[0].scriptSig), next_height) + # round-trip negative and multi-byte CScriptNums to catch python regression + assert_equal(CScriptNum.decode(CScriptNum.encode(CScriptNum(1500))), 1500) + assert_equal(CScriptNum.decode(CScriptNum.encode(CScriptNum(-1500))), -1500) + assert_equal(CScriptNum.decode(CScriptNum.encode(CScriptNum(-1))), -1) + block = CBlock() block.nVersion = tmpl["version"] block.hashPrevBlock = int(tmpl["previousblockhash"], 16) @@ -77,6 +86,9 @@ class MiningTest(BitcoinTestFramework): block.nNonce = 0 block.vtx = [coinbase_tx] + self.log.info("getblocktemplate: segwit rule must be set") + assert_raises_rpc_error(-8, "getblocktemplate must be called with the segwit rule set", node.getblocktemplate) + self.log.info("getblocktemplate: Test valid block") assert_template(node, block, None) @@ -93,7 +105,7 @@ class MiningTest(BitcoinTestFramework): assert_raises_rpc_error(-22, "Block does not start with a coinbase", node.submitblock, b2x(bad_block.serialize())) self.log.info("getblocktemplate: Test truncated final transaction") - assert_raises_rpc_error(-22, "Block decode failed", node.getblocktemplate, {'data': b2x(block.serialize()[:-1]), 'mode': 'proposal'}) + assert_raises_rpc_error(-22, "Block decode failed", node.getblocktemplate, {'data': b2x(block.serialize()[:-1]), 'mode': 'proposal', 'rules': ['segwit']}) self.log.info("getblocktemplate: Test duplicate transaction") bad_block = copy.deepcopy(block) @@ -123,7 +135,7 @@ class MiningTest(BitcoinTestFramework): bad_block_sn = bytearray(block.serialize()) assert_equal(bad_block_sn[TX_COUNT_OFFSET], 1) bad_block_sn[TX_COUNT_OFFSET] += 1 - assert_raises_rpc_error(-22, "Block decode failed", node.getblocktemplate, {'data': b2x(bad_block_sn), 'mode': 'proposal'}) + assert_raises_rpc_error(-22, "Block decode failed", node.getblocktemplate, {'data': b2x(bad_block_sn), 'mode': 'proposal', 'rules': ['segwit']}) self.log.info("getblocktemplate: Test bad bits") bad_block = copy.deepcopy(block) diff --git a/test/functional/mining_getblocktemplate_longpoll.py b/test/functional/mining_getblocktemplate_longpoll.py index 9a3c15a4a7..72cde8e811 100755 --- a/test/functional/mining_getblocktemplate_longpoll.py +++ b/test/functional/mining_getblocktemplate_longpoll.py @@ -15,14 +15,14 @@ class LongpollThread(threading.Thread): def __init__(self, node): threading.Thread.__init__(self) # query current longpollid - template = node.getblocktemplate() + template = node.getblocktemplate({'rules': ['segwit']}) self.longpollid = template['longpollid'] # create a new connection to the node, we can't use the same # connection from two threads self.node = get_rpc_proxy(node.url, 1, timeout=600, coveragedir=node.coverage_dir) def run(self): - self.node.getblocktemplate({'longpollid':self.longpollid}) + self.node.getblocktemplate({'longpollid': self.longpollid, 'rules': ['segwit']}) class GetBlockTemplateLPTest(BitcoinTestFramework): def set_test_params(self): @@ -34,10 +34,10 @@ class GetBlockTemplateLPTest(BitcoinTestFramework): def run_test(self): self.log.info("Warning: this test will take about 70 seconds in the best case. Be patient.") self.nodes[0].generate(10) - template = self.nodes[0].getblocktemplate() + template = self.nodes[0].getblocktemplate({'rules': ['segwit']}) longpollid = template['longpollid'] # longpollid should not change between successive invocations if nothing else happens - template2 = self.nodes[0].getblocktemplate() + template2 = self.nodes[0].getblocktemplate({'rules': ['segwit']}) assert(template2['longpollid'] == longpollid) # Test 1: test that the longpolling wait if we do nothing diff --git a/test/functional/mining_prioritisetransaction.py b/test/functional/mining_prioritisetransaction.py index c5ddee56f1..ca4b621a78 100755 --- a/test/functional/mining_prioritisetransaction.py +++ b/test/functional/mining_prioritisetransaction.py @@ -84,7 +84,7 @@ class PrioritiseTransactionTest(BitcoinTestFramework): high_fee_tx = x # Something high-fee should have been mined! - assert(high_fee_tx != None) + assert(high_fee_tx is not None) # Add a prioritisation before a tx is in the mempool (de-prioritising a # high-fee transaction so that it's now low fee). @@ -142,10 +142,10 @@ class PrioritiseTransactionTest(BitcoinTestFramework): # getblocktemplate to (eventually) return a new block. mock_time = int(time.time()) self.nodes[0].setmocktime(mock_time) - template = self.nodes[0].getblocktemplate() + template = self.nodes[0].getblocktemplate({'rules': ['segwit']}) self.nodes[0].prioritisetransaction(txid=tx_id, fee_delta=-int(self.relayfee*COIN)) self.nodes[0].setmocktime(mock_time+10) - new_template = self.nodes[0].getblocktemplate() + new_template = self.nodes[0].getblocktemplate({'rules': ['segwit']}) assert(template != new_template) diff --git a/test/functional/p2p_compactblocks.py b/test/functional/p2p_compactblocks.py index fafc27d16a..f0dee59b5c 100755 --- a/test/functional/p2p_compactblocks.py +++ b/test/functional/p2p_compactblocks.py @@ -7,7 +7,6 @@ Version 1 compact blocks are pre-segwit (txids) Version 2 compact blocks are post-segwit (wtxids) """ - from decimal import Decimal import random @@ -99,7 +98,7 @@ class CompactBlocksTest(BitcoinTestFramework): self.num_nodes = 2 # This test was written assuming SegWit is activated using BIP9 at height 432 (3x confirmation window). # TODO: Rewrite this test to support SegWit being always active. - self.extra_args = [["-vbparams=segwit:0:0"], ["-vbparams=segwit:0:999999999999", "-txindex", "-deprecatedrpc=addwitnessaddress"]] + self.extra_args = [["-vbparams=segwit:0:0"], ["-vbparams=segwit:0:999999999999", "-txindex"]] self.utxos = [] def skip_test_if_missing_module(self): @@ -189,7 +188,7 @@ class CompactBlocksTest(BitcoinTestFramework): # Now try a SENDCMPCT message with too-high version sendcmpct = msg_sendcmpct() - sendcmpct.version = preferred_version+1 + sendcmpct.version = preferred_version + 1 sendcmpct.announce = True test_node.send_and_ping(sendcmpct) check_announcement_of_new_block(node, test_node, lambda p: "cmpctblock" not in p.last_message) @@ -220,7 +219,7 @@ class CompactBlocksTest(BitcoinTestFramework): check_announcement_of_new_block(node, test_node, lambda p: "cmpctblock" in p.last_message) # Try one more time, after sending a version-1, announce=false message. - sendcmpct.version = preferred_version-1 + sendcmpct.version = preferred_version - 1 sendcmpct.announce = False test_node.send_and_ping(sendcmpct) check_announcement_of_new_block(node, test_node, lambda p: "cmpctblock" in p.last_message) @@ -234,7 +233,7 @@ class CompactBlocksTest(BitcoinTestFramework): if old_node is not None: # Verify that a peer using an older protocol version can receive # announcements from this node. - sendcmpct.version = preferred_version-1 + sendcmpct.version = preferred_version - 1 sendcmpct.announce = True old_node.send_and_ping(sendcmpct) # Header sync @@ -265,9 +264,9 @@ class CompactBlocksTest(BitcoinTestFramework): if use_witness_address: # Want at least one segwit spend, so move all funds to # a witness address. - address = node.addwitnessaddress(address) + address = node.getnewaddress(address_type='bech32') value_to_send = node.getbalance() - node.sendtoaddress(address, satoshi_round(value_to_send-Decimal(0.1))) + node.sendtoaddress(address, satoshi_round(value_to_send - Decimal(0.1))) node.generate(1) segwit_tx_generated = False @@ -279,7 +278,7 @@ class CompactBlocksTest(BitcoinTestFramework): segwit_tx_generated = True if use_witness_address: - assert(segwit_tx_generated) # check that our test is not broken + assert segwit_tx_generated # check that our test is not broken # Wait until we've seen the block announcement for the resulting tip tip = int(node.getbestblockhash(), 16) @@ -403,8 +402,7 @@ class CompactBlocksTest(BitcoinTestFramework): coinbase_hash = block.vtx[0].sha256 if version == 2: coinbase_hash = block.vtx[0].calc_sha256(True) - comp_block.shortids = [ - calculate_shortid(k0, k1, coinbase_hash) ] + comp_block.shortids = [calculate_shortid(k0, k1, coinbase_hash)] test_node.send_and_ping(msg_cmpctblock(comp_block.to_p2p())) assert_equal(int(node.getbestblockhash(), 16), block.hashPrevBlock) # Expect a getblocktxn message. @@ -443,7 +441,7 @@ class CompactBlocksTest(BitcoinTestFramework): # node needs, and that responding to them causes the block to be # reconstructed. def test_getblocktxn_requests(self, node, test_node, version): - with_witness = (version==2) + with_witness = (version == 2) def test_getblocktxn_response(compact_block, peer, expected_result): msg = msg_cmpctblock(compact_block.to_p2p()) @@ -470,7 +468,7 @@ class CompactBlocksTest(BitcoinTestFramework): msg_bt = msg_blocktxn() if with_witness: - msg_bt = msg_witness_blocktxn() # serialize with witnesses + msg_bt = msg_witness_blocktxn() # serialize with witnesses msg_bt.block_transactions = BlockTransactions(block.sha256, block.vtx[1:]) test_tip_after_message(node, test_node, msg_bt, block.sha256) @@ -560,7 +558,7 @@ class CompactBlocksTest(BitcoinTestFramework): # verifying that the block isn't marked bad permanently. This is good # enough for now. msg = msg_blocktxn() - if version==2: + if version == 2: msg = msg_witness_blocktxn() msg.block_transactions = BlockTransactions(block.sha256, [block.vtx[5]] + block.vtx[7:]) test_node.send_and_ping(msg) @@ -571,11 +569,11 @@ class CompactBlocksTest(BitcoinTestFramework): # We should receive a getdata request wait_until(lambda: "getdata" in test_node.last_message, timeout=10, lock=mininode_lock) assert_equal(len(test_node.last_message["getdata"].inv), 1) - assert(test_node.last_message["getdata"].inv[0].type == 2 or test_node.last_message["getdata"].inv[0].type == 2|MSG_WITNESS_FLAG) + assert(test_node.last_message["getdata"].inv[0].type == 2 or test_node.last_message["getdata"].inv[0].type == 2 | MSG_WITNESS_FLAG) assert_equal(test_node.last_message["getdata"].inv[0].hash, block.sha256) # Deliver the block - if version==2: + if version == 2: test_node.send_and_ping(msg_witness_block(block)) else: test_node.send_and_ping(msg_block(block)) @@ -655,7 +653,7 @@ class CompactBlocksTest(BitcoinTestFramework): # Generate an old compactblock, and verify that it's not accepted. cur_height = node.getblockcount() - hashPrevBlock = int(node.getblockhash(cur_height-5), 16) + hashPrevBlock = int(node.getblockhash(cur_height - 5), 16) block = self.build_block_on_tip(node) block.hashPrevBlock = hashPrevBlock block.solve() @@ -684,7 +682,7 @@ class CompactBlocksTest(BitcoinTestFramework): assert "blocktxn" not in test_node.last_message def activate_segwit(self, node): - node.generate(144*3) + node.generate(144 * 3) assert_equal(get_bip9_status(node, "segwit")["status"], 'active') def test_end_to_end_block_relay(self, node, listeners): @@ -780,7 +778,7 @@ class CompactBlocksTest(BitcoinTestFramework): delivery_peer.send_message(msg_tx(tx)) delivery_peer.sync_with_ping() - cmpct_block.prefilled_txn[0].tx.wit.vtxinwit = [ CTxInWitness() ] + cmpct_block.prefilled_txn[0].tx.wit.vtxinwit = [CTxInWitness()] cmpct_block.prefilled_txn[0].tx.wit.vtxinwit[0].scriptWitness.stack = [ser_uint256(0)] cmpct_block.use_witness = True @@ -886,7 +884,7 @@ class CompactBlocksTest(BitcoinTestFramework): self.log.info("Syncing nodes...") assert(self.nodes[0].getbestblockhash() != self.nodes[1].getbestblockhash()) while (self.nodes[0].getblockcount() > self.nodes[1].getblockcount()): - block_hash = self.nodes[0].getblockhash(self.nodes[1].getblockcount()+1) + block_hash = self.nodes[0].getblockhash(self.nodes[1].getblockcount() + 1) self.nodes[1].submitblock(self.nodes[0].getblock(block_hash, False)) assert_equal(self.nodes[0].getbestblockhash(), self.nodes[1].getbestblockhash()) diff --git a/test/functional/p2p_disconnect_ban.py b/test/functional/p2p_disconnect_ban.py index 67f24d6bff..1b11a2a294 100755 --- a/test/functional/p2p_disconnect_ban.py +++ b/test/functional/p2p_disconnect_ban.py @@ -22,7 +22,7 @@ class DisconnectBanTest(BitcoinTestFramework): self.log.info("setban: successfully ban single IP address") assert_equal(len(self.nodes[1].getpeerinfo()), 2) # node1 should have 2 connections to node0 at this point - self.nodes[1].setban("127.0.0.1", "add") + self.nodes[1].setban(subnet="127.0.0.1", command="add") wait_until(lambda: len(self.nodes[1].getpeerinfo()) == 0, timeout=10) assert_equal(len(self.nodes[1].getpeerinfo()), 0) # all nodes must be disconnected at this point assert_equal(len(self.nodes[1].listbanned()), 1) diff --git a/test/functional/p2p_invalid_block.py b/test/functional/p2p_invalid_block.py index 0678b1a651..1e0b876593 100755 --- a/test/functional/p2p_invalid_block.py +++ b/test/functional/p2p_invalid_block.py @@ -77,9 +77,9 @@ class InvalidBlockRequestTest(BitcoinTestFramework): block2.vtx.append(tx2) assert_equal(block2.hashMerkleRoot, block2.calc_merkle_root()) assert_equal(orig_hash, block2.rehash()) - assert(block2_orig.vtx != block2.vtx) + assert block2_orig.vtx != block2.vtx - node.p2p.send_blocks_and_test([block2], node, success=False, request_block=False, reject_reason='bad-txns-duplicate') + node.p2p.send_blocks_and_test([block2], node, success=False, reject_reason='bad-txns-duplicate') # Check transactions for duplicate inputs self.log.info("Test duplicate input block.") @@ -89,7 +89,7 @@ class InvalidBlockRequestTest(BitcoinTestFramework): block2_orig.hashMerkleRoot = block2_orig.calc_merkle_root() block2_orig.rehash() block2_orig.solve() - node.p2p.send_blocks_and_test([block2_orig], node, success=False, request_block=False, reject_reason='bad-txns-inputs-duplicate') + node.p2p.send_blocks_and_test([block2_orig], node, success=False, reject_reason='bad-txns-inputs-duplicate') self.log.info("Test very broken block.") @@ -102,7 +102,8 @@ class InvalidBlockRequestTest(BitcoinTestFramework): block3.rehash() block3.solve() - node.p2p.send_blocks_and_test([block3], node, success=False, request_block=False, reject_reason='bad-cb-amount') + node.p2p.send_blocks_and_test([block3], node, success=False, reject_reason='bad-cb-amount') + if __name__ == '__main__': InvalidBlockRequestTest().main() diff --git a/test/functional/p2p_invalid_locator.py b/test/functional/p2p_invalid_locator.py index c8c752d1f7..33b7060060 100755 --- a/test/functional/p2p_invalid_locator.py +++ b/test/functional/p2p_invalid_locator.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -# Copyright (c) 2015-2017 The Bitcoin Core developers +# Copyright (c) 2015-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. """Test node responses to invalid locators. diff --git a/test/functional/p2p_invalid_messages.py b/test/functional/p2p_invalid_messages.py new file mode 100755 index 0000000000..dbc5c5fff6 --- /dev/null +++ b/test/functional/p2p_invalid_messages.py @@ -0,0 +1,179 @@ +#!/usr/bin/env python3 +# Copyright (c) 2015-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. +"""Test node responses to invalid network messages.""" +import os +import struct + +from test_framework import messages +from test_framework.mininode import P2PDataStore +from test_framework.test_framework import BitcoinTestFramework + + +class msg_unrecognized: + """Nonsensical message. Modeled after similar types in test_framework.messages.""" + + command = b'badmsg' + + def __init__(self, str_data): + self.str_data = str_data.encode() if not isinstance(str_data, bytes) else str_data + + def serialize(self): + return messages.ser_string(self.str_data) + + def __repr__(self): + return "{}(data={})".format(self.command, self.str_data) + + +class msg_nametoolong(msg_unrecognized): + + command = b'thisnameiswayyyyyyyyytoolong' + + +class InvalidMessagesTest(BitcoinTestFramework): + + def set_test_params(self): + self.num_nodes = 1 + self.setup_clean_chain = True + + def run_test(self): + """ + 0. Send a bunch of large (4MB) messages of an unrecognized type. Check to see + that it isn't an effective DoS against the node. + + 1. Send an oversized (4MB+) message and check that we're disconnected. + + 2. Send a few messages with an incorrect data size in the header, ensure the + messages are ignored. + + 3. Send an unrecognized message with a command name longer than 12 characters. + + """ + node = self.nodes[0] + self.node = node + node.add_p2p_connection(P2PDataStore()) + conn2 = node.add_p2p_connection(P2PDataStore()) + + msg_limit = 4 * 1000 * 1000 # 4MB, per MAX_PROTOCOL_MESSAGE_LENGTH + valid_data_limit = msg_limit - 5 # Account for the 4-byte length prefix + + # + # 0. + # + # Send as large a message as is valid, ensure we aren't disconnected but + # also can't exhaust resources. + # + msg_at_size = msg_unrecognized("b" * valid_data_limit) + assert len(msg_at_size.serialize()) == msg_limit + + increase_allowed = 0.5 + if [s for s in os.environ.get("BITCOIN_CONFIG", "").split(" ") if "--with-sanitizers" in s and "address" in s]: + increase_allowed = 3.5 + with node.assert_memory_usage_stable(increase_allowed=increase_allowed): + self.log.info( + "Sending a bunch of large, junk messages to test " + "memory exhaustion. May take a bit...") + + # Run a bunch of times to test for memory exhaustion. + for _ in range(80): + node.p2p.send_message(msg_at_size) + + # Check that, even though the node is being hammered by nonsense from one + # connection, it can still service other peers in a timely way. + for _ in range(20): + conn2.sync_with_ping(timeout=2) + + # Peer 1, despite serving up a bunch of nonsense, should still be connected. + self.log.info("Waiting for node to drop junk messages.") + node.p2p.sync_with_ping(timeout=120) + assert node.p2p.is_connected + + # + # 1. + # + # Send an oversized message, ensure we're disconnected. + # + msg_over_size = msg_unrecognized("b" * (valid_data_limit + 1)) + assert len(msg_over_size.serialize()) == (msg_limit + 1) + + with node.assert_debug_log(["Oversized message from peer=0, disconnecting"]): + # An unknown message type (or *any* message type) over + # MAX_PROTOCOL_MESSAGE_LENGTH should result in a disconnect. + node.p2p.send_message(msg_over_size) + node.p2p.wait_for_disconnect(timeout=4) + + node.disconnect_p2ps() + conn = node.add_p2p_connection(P2PDataStore()) + conn.wait_for_verack() + + # + # 2. + # + # Send messages with an incorrect data size in the header. + # + actual_size = 100 + msg = msg_unrecognized("b" * actual_size) + + # TODO: handle larger-than cases. I haven't been able to pin down what behavior to expect. + for wrong_size in (2, 77, 78, 79): + self.log.info("Sending a message with incorrect size of {}".format(wrong_size)) + + # Unmodified message should submit okay. + node.p2p.send_and_ping(msg) + + # A message lying about its data size results in a disconnect when the incorrect + # data size is less than the actual size. + # + # TODO: why does behavior change at 78 bytes? + # + node.p2p.send_raw_message(self._tweak_msg_data_size(msg, wrong_size)) + + # For some reason unknown to me, we sometimes have to push additional data to the + # peer in order for it to realize a disconnect. + try: + node.p2p.send_message(messages.msg_ping(nonce=123123)) + except IOError: + pass + + node.p2p.wait_for_disconnect(timeout=10) + node.disconnect_p2ps() + node.add_p2p_connection(P2PDataStore()) + + # + # 3. + # + # Send a message with a too-long command name. + # + node.p2p.send_message(msg_nametoolong("foobar")) + node.p2p.wait_for_disconnect(timeout=4) + + # Node is still up. + conn = node.add_p2p_connection(P2PDataStore()) + conn.sync_with_ping() + + + def _tweak_msg_data_size(self, message, wrong_size): + """ + Return a raw message based on another message but with an incorrect data size in + the message header. + """ + raw_msg = self.node.p2p.build_message(message) + + bad_size_bytes = struct.pack("<I", wrong_size) + num_header_bytes_before_size = 4 + 12 + + # Replace the correct data size in the message with an incorrect one. + raw_msg_with_wrong_size = ( + raw_msg[:num_header_bytes_before_size] + + bad_size_bytes + + raw_msg[(num_header_bytes_before_size + len(bad_size_bytes)):] + ) + assert len(raw_msg) == len(raw_msg_with_wrong_size) + + return raw_msg_with_wrong_size + + + +if __name__ == '__main__': + InvalidMessagesTest().main() diff --git a/test/functional/p2p_invalid_tx.py b/test/functional/p2p_invalid_tx.py index 58e129b57d..1b18dd3e58 100755 --- a/test/functional/p2p_invalid_tx.py +++ b/test/functional/p2p_invalid_tx.py @@ -5,7 +5,7 @@ """Test node responses to invalid transactions. In this test we connect to one node over p2p, and test tx requests.""" -from test_framework.blocktools import create_block, create_coinbase, create_tx_with_script +from test_framework.blocktools import create_block, create_coinbase from test_framework.messages import ( COIN, COutPoint, @@ -19,6 +19,7 @@ from test_framework.util import ( assert_equal, wait_until, ) +from data import invalid_txs class InvalidTxRequestTest(BitcoinTestFramework): @@ -63,12 +64,21 @@ class InvalidTxRequestTest(BitcoinTestFramework): self.log.info("Mature the block.") self.nodes[0].generatetoaddress(100, self.nodes[0].get_deterministic_priv_key().address) - # b'\x64' is OP_NOTIF - # Transaction will be rejected with code 16 (REJECT_INVALID) - # and we get disconnected immediately - self.log.info('Test a transaction that is rejected') - tx1 = create_tx_with_script(block1.vtx[0], 0, script_sig=b'\x64' * 35, amount=50 * COIN - 12000) - node.p2p.send_txs_and_test([tx1], node, success=False, expect_disconnect=True) + # Iterate through a list of known invalid transaction types, ensuring each is + # rejected. Some are consensus invalid and some just violate policy. + for BadTxTemplate in invalid_txs.iter_all_templates(): + self.log.info("Testing invalid transaction: %s", BadTxTemplate.__name__) + template = BadTxTemplate(spend_block=block1) + tx = template.get_tx() + node.p2p.send_txs_and_test( + [tx], node, success=False, + expect_disconnect=template.expect_disconnect, + reject_reason=template.reject_reason, + ) + + if template.expect_disconnect: + self.log.info("Reconnecting to peer") + self.reconnect_p2p() # Make two p2p connections to provide the node with orphans # * p2ps[0] will send valid orphan txs (one with low fee) diff --git a/test/functional/p2p_leak_tx.py b/test/functional/p2p_leak_tx.py new file mode 100755 index 0000000000..dc4d475b2d --- /dev/null +++ b/test/functional/p2p_leak_tx.py @@ -0,0 +1,57 @@ +#!/usr/bin/env python3 +# Copyright (c) 2017-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. +"""Test that we don't leak txs to inbound peers that we haven't yet announced to""" + +from test_framework.messages import msg_getdata, CInv +from test_framework.mininode import P2PDataStore +from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import ( + assert_equal, +) + + +class P2PNode(P2PDataStore): + def on_inv(self, msg): + pass + + +class P2PLeakTxTest(BitcoinTestFramework): + def set_test_params(self): + self.num_nodes = 1 + + def skip_test_if_missing_module(self): + self.skip_if_no_wallet() + + def run_test(self): + gen_node = self.nodes[0] # The block and tx generating node + gen_node.generate(1) + + inbound_peer = self.nodes[0].add_p2p_connection(P2PNode()) # An "attacking" inbound peer + + MAX_REPEATS = 100 + self.log.info("Running test up to {} times.".format(MAX_REPEATS)) + for i in range(MAX_REPEATS): + self.log.info('Run repeat {}'.format(i + 1)) + txid = gen_node.sendtoaddress(gen_node.getnewaddress(), 0.01) + + want_tx = msg_getdata() + want_tx.inv.append(CInv(t=1, h=int(txid, 16))) + inbound_peer.last_message.pop('notfound', None) + inbound_peer.send_message(want_tx) + inbound_peer.sync_with_ping() + + if inbound_peer.last_message.get('notfound'): + self.log.debug('tx {} was not yet announced to us.'.format(txid)) + self.log.debug("node has responded with a notfound message. End test.") + assert_equal(inbound_peer.last_message['notfound'].vec[0].hash, int(txid, 16)) + inbound_peer.last_message.pop('notfound') + break + else: + self.log.debug('tx {} was already announced to us. Try test again.'.format(txid)) + assert int(txid, 16) in [inv.hash for inv in inbound_peer.last_message['inv'].inv] + + +if __name__ == '__main__': + P2PLeakTxTest().main() diff --git a/test/functional/p2p_node_network_limited.py b/test/functional/p2p_node_network_limited.py index ec3d336dc1..359880506e 100755 --- a/test/functional/p2p_node_network_limited.py +++ b/test/functional/p2p_node_network_limited.py @@ -43,8 +43,8 @@ class NodeNetworkLimitedTest(BitcoinTestFramework): disconnect_nodes(self.nodes[1], 2) def setup_network(self): - super(NodeNetworkLimitedTest, self).setup_network() - self.disconnect_all() + self.add_nodes(self.num_nodes, self.extra_args) + self.start_nodes() def run_test(self): node = self.nodes[0].add_p2p_connection(P2PIgnoreInv()) diff --git a/test/functional/p2p_segwit.py b/test/functional/p2p_segwit.py index afbbfa8992..d95da227e5 100755 --- a/test/functional/p2p_segwit.py +++ b/test/functional/p2p_segwit.py @@ -545,31 +545,13 @@ class SegWitTest(BitcoinTestFramework): @subtest def test_getblocktemplate_before_lockin(self): - # Node0 is segwit aware, node2 is not. - for node in [self.nodes[0], self.nodes[2]]: - gbt_results = node.getblocktemplate() - block_version = gbt_results['version'] - # If we're not indicating segwit support, we will still be - # signalling for segwit activation. - assert_equal((block_version & (1 << VB_WITNESS_BIT) != 0), node == self.nodes[0]) - # If we don't specify the segwit rule, then we won't get a default - # commitment. - assert('default_witness_commitment' not in gbt_results) - - # Workaround: - # Can either change the tip, or change the mempool and wait 5 seconds - # to trigger a recomputation of getblocktemplate. txid = int(self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), 1), 16) - # Using mocktime lets us avoid sleep() - sync_mempools(self.nodes) - self.nodes[0].setmocktime(int(time.time()) + 10) - self.nodes[2].setmocktime(int(time.time()) + 10) for node in [self.nodes[0], self.nodes[2]]: gbt_results = node.getblocktemplate({"rules": ["segwit"]}) block_version = gbt_results['version'] if node == self.nodes[2]: - # If this is a non-segwit node, we should still not get a witness + # If this is a non-segwit node, we should not get a witness # commitment, nor a version bit signalling segwit. assert_equal(block_version & (1 << VB_WITNESS_BIT), 0) assert('default_witness_commitment' not in gbt_results) @@ -586,10 +568,6 @@ class SegWitTest(BitcoinTestFramework): script = get_witness_script(witness_root, 0) assert_equal(witness_commitment, bytes_to_hex_str(script)) - # undo mocktime - self.nodes[0].setmocktime(0) - self.nodes[2].setmocktime(0) - @subtest def advance_to_segwit_lockin(self): """Mine enough blocks to lock in segwit, but don't activate.""" diff --git a/test/functional/p2p_timeouts.py b/test/functional/p2p_timeouts.py index 2459a9f243..02ceec3dc1 100755 --- a/test/functional/p2p_timeouts.py +++ b/test/functional/p2p_timeouts.py @@ -14,11 +14,11 @@ - Wait 1 second - Assert that we're connected - Send a ping to no_verack_node and no_version_node -- Wait 30 seconds +- Wait 1 second - Assert that we're still connected - Send a ping to no_verack_node and no_version_node -- Wait 31 seconds -- Assert that we're no longer connected (timeout to receive version/verack is 60 seconds) +- Wait 2 seconds +- Assert that we're no longer connected (timeout to receive version/verack is 3 seconds) """ from time import sleep @@ -36,6 +36,8 @@ class TimeoutsTest(BitcoinTestFramework): def set_test_params(self): self.setup_clean_chain = True self.num_nodes = 1 + # set timeout to receive version/verack to 3 seconds + self.extra_args = [["-peertimeout=3"]] def run_test(self): # Setup the p2p connections @@ -52,7 +54,7 @@ class TimeoutsTest(BitcoinTestFramework): no_verack_node.send_message(msg_ping()) no_version_node.send_message(msg_ping()) - sleep(30) + sleep(1) assert "version" in no_verack_node.last_message @@ -63,11 +65,21 @@ class TimeoutsTest(BitcoinTestFramework): no_verack_node.send_message(msg_ping()) no_version_node.send_message(msg_ping()) - sleep(31) - - assert not no_verack_node.is_connected - assert not no_version_node.is_connected - assert not no_send_node.is_connected + expected_timeout_logs = [ + "version handshake timeout from 0", + "socket no message in first 3 seconds, 1 0 from 1", + "socket no message in first 3 seconds, 0 0 from 2", + ] + + with self.nodes[0].assert_debug_log(expected_msgs=expected_timeout_logs): + sleep(3) + # By now, we waited a total of 5 seconds. Off-by-two for two + # reasons: + # * The internal precision is one second + # * Account for network delay + assert not no_verack_node.is_connected + assert not no_version_node.is_connected + assert not no_send_node.is_connected if __name__ == '__main__': TimeoutsTest().main() diff --git a/test/functional/rpc_bind.py b/test/functional/rpc_bind.py index 53916d5290..3938ca98dd 100755 --- a/test/functional/rpc_bind.py +++ b/test/functional/rpc_bind.py @@ -48,9 +48,12 @@ class RPCBindTest(BitcoinTestFramework): at a non-localhost IP. ''' self.log.info("Allow IP test for %s:%d" % (rpchost, rpcport)) - base_args = ['-disablewallet', '-nolisten'] + ['-rpcallowip='+x for x in allow_ips] + node_args = \ + ['-disablewallet', '-nolisten'] + \ + ['-rpcallowip='+x for x in allow_ips] + \ + ['-rpcbind='+addr for addr in ['127.0.0.1', "%s:%d" % (rpchost, rpcport)]] # Bind to localhost as well so start_nodes doesn't hang self.nodes[0].rpchost = None - self.start_nodes([base_args]) + self.start_nodes([node_args]) # connect to node through non-loopback interface node = get_rpc_proxy(rpc_url(self.nodes[0].datadir, 0, "%s:%d" % (rpchost, rpcport)), 0, coveragedir=self.options.coveragedir) node.getnetworkinfo() @@ -67,7 +70,7 @@ class RPCBindTest(BitcoinTestFramework): self.log.info("Check for ipv6") have_ipv6 = test_ipv6_local() - if not have_ipv6 and not self.options.run_ipv4: + if not have_ipv6 and not (self.options.run_ipv4 or self.options.run_nonloopback): raise SkipTest("This test requires ipv6 support.") self.log.info("Check for non-loopback interface") @@ -101,9 +104,9 @@ class RPCBindTest(BitcoinTestFramework): # check default without rpcallowip (IPv4 and IPv6 localhost) self.run_bind_test(None, '127.0.0.1', [], [('127.0.0.1', self.defaultport), ('::1', self.defaultport)]) - # check default with rpcallowip (IPv6 any) + # check default with rpcallowip (IPv4 and IPv6 localhost) self.run_bind_test(['127.0.0.1'], '127.0.0.1', [], - [('::0', self.defaultport)]) + [('127.0.0.1', self.defaultport), ('::1', self.defaultport)]) # check only IPv6 localhost (explicit) self.run_bind_test(['[::1]'], '[::1]', ['[::1]'], [('::1', self.defaultport)]) diff --git a/test/functional/rpc_blockchain.py b/test/functional/rpc_blockchain.py index 92b690176d..31e60f1cea 100755 --- a/test/functional/rpc_blockchain.py +++ b/test/functional/rpc_blockchain.py @@ -133,7 +133,7 @@ class BlockchainTest(BitcoinTestFramework): assert_raises_rpc_error(-8, "Block is not in main chain", self.nodes[0].getchaintxstats, blockhash=blockhash) self.nodes[0].reconsiderblock(blockhash) - chaintxstats = self.nodes[0].getchaintxstats(1) + chaintxstats = self.nodes[0].getchaintxstats(nblocks=1) # 200 txs plus genesis tx assert_equal(chaintxstats['txcount'], 201) # tx rate should be 1 per 10 minutes, or 1/600 @@ -211,7 +211,7 @@ class BlockchainTest(BitcoinTestFramework): besthash = node.getbestblockhash() secondbesthash = node.getblockhash(199) - header = node.getblockheader(besthash) + header = node.getblockheader(blockhash=besthash) assert_equal(header['hash'], besthash) assert_equal(header['height'], 200) @@ -287,7 +287,7 @@ class BlockchainTest(BitcoinTestFramework): def assert_waitforheight(height, timeout=2): assert_equal( - node.waitforblockheight(height, timeout)['height'], + node.waitforblockheight(height=height, timeout=timeout)['height'], current_height) assert_waitforheight(0) diff --git a/test/functional/rpc_deprecated.py b/test/functional/rpc_deprecated.py index 58074803cc..588bfbe083 100755 --- a/test/functional/rpc_deprecated.py +++ b/test/functional/rpc_deprecated.py @@ -4,12 +4,17 @@ # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test deprecation of RPC calls.""" from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import assert_raises_rpc_error class DeprecatedRpcTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 2 self.setup_clean_chain = True - self.extra_args = [[], ["-deprecatedrpc=validateaddress"]] + self.extra_args = [[], ["-deprecatedrpc=generate"]] + + def skip_test_if_missing_module(self): + # The generate RPC method requires the wallet to be compiled + self.skip_if_no_wallet() def run_test(self): # This test should be used to verify correct behaviour of deprecated @@ -18,7 +23,10 @@ class DeprecatedRpcTest(BitcoinTestFramework): # self.log.info("Make sure that -deprecatedrpc=createmultisig allows it to take addresses") # assert_raises_rpc_error(-5, "Invalid public key", self.nodes[0].createmultisig, 1, [self.nodes[0].getnewaddress()]) # self.nodes[1].createmultisig(1, [self.nodes[1].getnewaddress()]) - pass + + self.log.info("Test generate RPC") + assert_raises_rpc_error(-32, 'The wallet generate rpc method is deprecated', self.nodes[0].rpc.generate, 1) + self.nodes[1].generate(1) if __name__ == '__main__': DeprecatedRpcTest().main() diff --git a/test/functional/rpc_fundrawtransaction.py b/test/functional/rpc_fundrawtransaction.py index 0c61e9ab62..4f350953b2 100755 --- a/test/functional/rpc_fundrawtransaction.py +++ b/test/functional/rpc_fundrawtransaction.py @@ -32,7 +32,7 @@ class RawTransactionsTest(BitcoinTestFramework): def skip_test_if_missing_module(self): self.skip_if_no_wallet() - def setup_network(self, split=False): + def setup_network(self): self.setup_nodes() connect_nodes_bi(self.nodes, 0, 1) diff --git a/test/functional/rpc_getblockstats.py b/test/functional/rpc_getblockstats.py index b24bed6adf..ca9e24367a 100755 --- a/test/functional/rpc_getblockstats.py +++ b/test/functional/rpc_getblockstats.py @@ -170,6 +170,8 @@ class GetblockstatsTest(BitcoinTestFramework): self.nodes[0].getblockstats, hash_or_height=1, stats=['minfee' , 'aaa%s' % inv_sel_stat]) assert_raises_rpc_error(-8, 'One or more of the selected stats requires -txindex enabled', + self.nodes[1].getblockstats, hash_or_height=1) + assert_raises_rpc_error(-8, 'One or more of the selected stats requires -txindex enabled', self.nodes[1].getblockstats, hash_or_height=self.start_height + self.max_stat_pos) # Mainchain's genesis block shouldn't be found on regtest diff --git a/test/functional/rpc_help.py b/test/functional/rpc_help.py index be096af892..78d6e78aed 100755 --- a/test/functional/rpc_help.py +++ b/test/functional/rpc_help.py @@ -7,12 +7,18 @@ from test_framework.test_framework import BitcoinTestFramework from test_framework.util import assert_equal, assert_raises_rpc_error +import os + class HelpRpcTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 1 def run_test(self): + self.test_categories() + self.dump_help() + + def test_categories(self): node = self.nodes[0] # wrong argument count @@ -37,6 +43,15 @@ class HelpRpcTest(BitcoinTestFramework): assert_equal(titles, components) + def dump_help(self): + dump_dir = os.path.join(self.options.tmpdir, 'rpc_help_dump') + os.mkdir(dump_dir) + calls = [line.split(' ', 1)[0] for line in self.nodes[0].help().splitlines() if line and not line.startswith('==')] + for call in calls: + with open(os.path.join(dump_dir, call), 'w', encoding='utf-8') as f: + # Make sure the node can generate the help at runtime without crashing + f.write(self.nodes[0].help(call)) + if __name__ == '__main__': HelpRpcTest().main() diff --git a/test/functional/rpc_invalidateblock.py b/test/functional/rpc_invalidateblock.py index d8ecdd573a..d8a1deb2a3 100755 --- a/test/functional/rpc_invalidateblock.py +++ b/test/functional/rpc_invalidateblock.py @@ -4,10 +4,15 @@ # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test the invalidateblock RPC.""" -import time - from test_framework.test_framework import BitcoinTestFramework -from test_framework.util import assert_equal, connect_nodes_bi, sync_blocks +from test_framework.address import ADDRESS_BCRT1_UNSPENDABLE +from test_framework.util import ( + assert_equal, + connect_nodes_bi, + sync_blocks, + wait_until, +) + class InvalidateTest(BitcoinTestFramework): def set_test_params(self): @@ -21,46 +26,66 @@ class InvalidateTest(BitcoinTestFramework): self.log.info("Make sure we repopulate setBlockIndexCandidates after InvalidateBlock:") self.log.info("Mine 4 blocks on Node 0") self.nodes[0].generatetoaddress(4, self.nodes[0].get_deterministic_priv_key().address) - assert(self.nodes[0].getblockcount() == 4) - besthash = self.nodes[0].getbestblockhash() + assert_equal(self.nodes[0].getblockcount(), 4) + besthash_n0 = self.nodes[0].getbestblockhash() self.log.info("Mine competing 6 blocks on Node 1") self.nodes[1].generatetoaddress(6, self.nodes[1].get_deterministic_priv_key().address) - assert(self.nodes[1].getblockcount() == 6) + assert_equal(self.nodes[1].getblockcount(), 6) self.log.info("Connect nodes to force a reorg") - connect_nodes_bi(self.nodes,0,1) + connect_nodes_bi(self.nodes, 0, 1) sync_blocks(self.nodes[0:2]) - assert(self.nodes[0].getblockcount() == 6) + assert_equal(self.nodes[0].getblockcount(), 6) badhash = self.nodes[1].getblockhash(2) self.log.info("Invalidate block 2 on node 0 and verify we reorg to node 0's original chain") self.nodes[0].invalidateblock(badhash) - newheight = self.nodes[0].getblockcount() - newhash = self.nodes[0].getbestblockhash() - if (newheight != 4 or newhash != besthash): - raise AssertionError("Wrong tip for node0, hash %s, height %d"%(newhash,newheight)) + assert_equal(self.nodes[0].getblockcount(), 4) + assert_equal(self.nodes[0].getbestblockhash(), besthash_n0) self.log.info("Make sure we won't reorg to a lower work chain:") - connect_nodes_bi(self.nodes,1,2) + connect_nodes_bi(self.nodes, 1, 2) self.log.info("Sync node 2 to node 1 so both have 6 blocks") sync_blocks(self.nodes[1:3]) - assert(self.nodes[2].getblockcount() == 6) + assert_equal(self.nodes[2].getblockcount(), 6) self.log.info("Invalidate block 5 on node 1 so its tip is now at 4") self.nodes[1].invalidateblock(self.nodes[1].getblockhash(5)) - assert(self.nodes[1].getblockcount() == 4) + assert_equal(self.nodes[1].getblockcount(), 4) self.log.info("Invalidate block 3 on node 2, so its tip is now 2") self.nodes[2].invalidateblock(self.nodes[2].getblockhash(3)) - assert(self.nodes[2].getblockcount() == 2) + assert_equal(self.nodes[2].getblockcount(), 2) self.log.info("..and then mine a block") self.nodes[2].generatetoaddress(1, self.nodes[2].get_deterministic_priv_key().address) self.log.info("Verify all nodes are at the right height") - time.sleep(5) - assert_equal(self.nodes[2].getblockcount(), 3) - assert_equal(self.nodes[0].getblockcount(), 4) - node1height = self.nodes[1].getblockcount() - if node1height < 4: - raise AssertionError("Node 1 reorged to a lower height: %d"%node1height) + wait_until(lambda: self.nodes[2].getblockcount() == 3, timeout=5) + wait_until(lambda: self.nodes[0].getblockcount() == 4, timeout=5) + wait_until(lambda: self.nodes[1].getblockcount() == 4, timeout=5) + + self.log.info("Verify that we reconsider all ancestors as well") + blocks = self.nodes[1].generatetoaddress(10, ADDRESS_BCRT1_UNSPENDABLE) + assert_equal(self.nodes[1].getbestblockhash(), blocks[-1]) + # Invalidate the two blocks at the tip + self.nodes[1].invalidateblock(blocks[-1]) + self.nodes[1].invalidateblock(blocks[-2]) + assert_equal(self.nodes[1].getbestblockhash(), blocks[-3]) + # Reconsider only the previous tip + self.nodes[1].reconsiderblock(blocks[-1]) + # Should be back at the tip by now + assert_equal(self.nodes[1].getbestblockhash(), blocks[-1]) + + self.log.info("Verify that we reconsider all descendants") + blocks = self.nodes[1].generatetoaddress(10, ADDRESS_BCRT1_UNSPENDABLE) + assert_equal(self.nodes[1].getbestblockhash(), blocks[-1]) + # Invalidate the two blocks at the tip + self.nodes[1].invalidateblock(blocks[-2]) + self.nodes[1].invalidateblock(blocks[-4]) + assert_equal(self.nodes[1].getbestblockhash(), blocks[-5]) + # Reconsider only the previous tip + self.nodes[1].reconsiderblock(blocks[-4]) + # Should be back at the tip by now + assert_equal(self.nodes[1].getbestblockhash(), blocks[-1]) + if __name__ == '__main__': InvalidateTest().main() diff --git a/test/functional/rpc_net.py b/test/functional/rpc_net.py index 1e525214fa..b12eb1d9ec 100755 --- a/test/functional/rpc_net.py +++ b/test/functional/rpc_net.py @@ -67,19 +67,19 @@ class NetTest(BitcoinTestFramework): peer_info_after_ping = self.nodes[0].getpeerinfo() for before, after in zip(peer_info, peer_info_after_ping): - assert_greater_than_or_equal(after['bytesrecv_per_msg']['pong'], before['bytesrecv_per_msg']['pong'] + 32) - assert_greater_than_or_equal(after['bytessent_per_msg']['ping'], before['bytessent_per_msg']['ping'] + 32) + assert_greater_than_or_equal(after['bytesrecv_per_msg'].get('pong', 0), before['bytesrecv_per_msg'].get('pong', 0) + 32) + assert_greater_than_or_equal(after['bytessent_per_msg'].get('ping', 0), before['bytessent_per_msg'].get('ping', 0) + 32) def _test_getnetworkinginfo(self): assert_equal(self.nodes[0].getnetworkinfo()['networkactive'], True) assert_equal(self.nodes[0].getnetworkinfo()['connections'], 2) - self.nodes[0].setnetworkactive(False) + self.nodes[0].setnetworkactive(state=False) assert_equal(self.nodes[0].getnetworkinfo()['networkactive'], False) # Wait a bit for all sockets to close wait_until(lambda: self.nodes[0].getnetworkinfo()['connections'] == 0, timeout=3) - self.nodes[0].setnetworkactive(True) + self.nodes[0].setnetworkactive(state=True) connect_nodes_bi(self.nodes, 0, 1) assert_equal(self.nodes[0].getnetworkinfo()['networkactive'], True) assert_equal(self.nodes[0].getnetworkinfo()['connections'], 2) @@ -88,7 +88,7 @@ class NetTest(BitcoinTestFramework): assert_equal(self.nodes[0].getaddednodeinfo(), []) # add a node (node2) to node0 ip_port = "127.0.0.1:{}".format(p2p_port(2)) - self.nodes[0].addnode(ip_port, 'add') + self.nodes[0].addnode(node=ip_port, command='add') # check that the node has indeed been added added_nodes = self.nodes[0].getaddednodeinfo(ip_port) assert_equal(len(added_nodes), 1) diff --git a/test/functional/rpc_psbt.py b/test/functional/rpc_psbt.py index 54dc871448..272ebe65cb 100755 --- a/test/functional/rpc_psbt.py +++ b/test/functional/rpc_psbt.py @@ -6,7 +6,7 @@ """ from test_framework.test_framework import BitcoinTestFramework -from test_framework.util import assert_equal, assert_raises_rpc_error, find_output +from test_framework.util import assert_equal, assert_raises_rpc_error, find_output, disconnect_nodes, connect_nodes_bi, sync_blocks import json import os @@ -23,6 +23,45 @@ class PSBTTest(BitcoinTestFramework): def skip_test_if_missing_module(self): self.skip_if_no_wallet() + def test_utxo_conversion(self): + mining_node = self.nodes[2] + offline_node = self.nodes[0] + online_node = self.nodes[1] + + # Disconnect offline node from others + disconnect_nodes(offline_node, 1) + disconnect_nodes(online_node, 0) + disconnect_nodes(offline_node, 2) + disconnect_nodes(mining_node, 0) + + # Mine a transaction that credits the offline address + offline_addr = offline_node.getnewaddress(address_type="p2sh-segwit") + online_addr = online_node.getnewaddress(address_type="p2sh-segwit") + online_node.importaddress(offline_addr, "", False) + mining_node.sendtoaddress(address=offline_addr, amount=1.0) + mining_node.generate(nblocks=1) + sync_blocks([mining_node, online_node]) + + # Construct an unsigned PSBT on the online node (who doesn't know the output is Segwit, so will include a non-witness UTXO) + utxos = online_node.listunspent(addresses=[offline_addr]) + raw = online_node.createrawtransaction([{"txid":utxos[0]["txid"], "vout":utxos[0]["vout"]}],[{online_addr:0.9999}]) + psbt = online_node.walletprocesspsbt(online_node.converttopsbt(raw))["psbt"] + assert("non_witness_utxo" in mining_node.decodepsbt(psbt)["inputs"][0]) + + # Have the offline node sign the PSBT (which will update the UTXO to segwit) + signed_psbt = offline_node.walletprocesspsbt(psbt)["psbt"] + assert("witness_utxo" in mining_node.decodepsbt(signed_psbt)["inputs"][0]) + + # Make sure we can mine the resulting transaction + txid = mining_node.sendrawtransaction(mining_node.finalizepsbt(signed_psbt)["hex"]) + mining_node.generate(1) + sync_blocks([mining_node, online_node]) + assert_equal(online_node.gettxout(txid,0)["confirmations"], 1) + + # Reconnect + connect_nodes_bi(self.nodes, 0, 1) + connect_nodes_bi(self.nodes, 0, 2) + def run_test(self): # Create and fund a raw tx for sending 10 BTC psbtx1 = self.nodes[0].walletcreatefundedpsbt([], {self.nodes[2].getnewaddress():10})['psbt'] @@ -107,6 +146,9 @@ class PSBTTest(BitcoinTestFramework): # Make sure that a psbt with signatures cannot be converted signedtx = self.nodes[0].signrawtransactionwithwallet(rawtx['hex']) assert_raises_rpc_error(-22, "TX decode failed", self.nodes[0].converttopsbt, signedtx['hex']) + assert_raises_rpc_error(-22, "TX decode failed", self.nodes[0].converttopsbt, signedtx['hex'], False) + # Unless we allow it to convert and strip signatures + self.nodes[0].converttopsbt(signedtx['hex'], True) # Explicitly allow converting non-empty txs new_psbt = self.nodes[0].converttopsbt(rawtx['hex']) @@ -168,6 +210,17 @@ class PSBTTest(BitcoinTestFramework): assert tx_in["sequence"] > MAX_BIP125_RBF_SEQUENCE assert_equal(decoded_psbt["tx"]["locktime"], 0) + # Make sure change address wallet does not have P2SH innerscript access to results in success + # when attempting BnB coin selection + self.nodes[0].walletcreatefundedpsbt([], [{self.nodes[2].getnewaddress():unspent["amount"]+1}], block_height+2, {"changeAddress":self.nodes[1].getnewaddress()}, False) + + # Regression test for 14473 (mishandling of already-signed witness transaction): + psbtx_info = self.nodes[0].walletcreatefundedpsbt([{"txid":unspent["txid"], "vout":unspent["vout"]}], [{self.nodes[2].getnewaddress():unspent["amount"]+1}]) + complete_psbt = self.nodes[0].walletprocesspsbt(psbtx_info["psbt"]) + double_processed_psbt = self.nodes[0].walletprocesspsbt(complete_psbt["psbt"]) + assert_equal(complete_psbt, double_processed_psbt) + # We don't care about the decode result, but decoding must succeed. + self.nodes[0].decodepsbt(double_processed_psbt["psbt"]) # BIP 174 Test Vectors @@ -224,6 +277,16 @@ class PSBTTest(BitcoinTestFramework): extracted = self.nodes[2].finalizepsbt(extractor['extract'], True)['hex'] assert_equal(extracted, extractor['result']) + # Unload extra wallets + for i, signer in enumerate(signers): + self.nodes[2].unloadwallet("wallet{}".format(i)) + + self.test_utxo_conversion() + + # Test that psbts with p2pkh outputs are created properly + p2pkh = self.nodes[0].getnewaddress(address_type='legacy') + psbt = self.nodes[1].walletcreatefundedpsbt([], [{p2pkh : 1}], 0, {"includeWatching" : True}, True) + self.nodes[0].decodepsbt(psbt['psbt']) if __name__ == '__main__': PSBTTest().main() diff --git a/test/functional/rpc_rawtransaction.py b/test/functional/rpc_rawtransaction.py index 8ed490f552..5b9dbef68d 100755 --- a/test/functional/rpc_rawtransaction.py +++ b/test/functional/rpc_rawtransaction.py @@ -47,7 +47,7 @@ class RawTransactionsTest(BitcoinTestFramework): def skip_test_if_missing_module(self): self.skip_if_no_wallet() - def setup_network(self, split=False): + def setup_network(self): super().setup_network() connect_nodes_bi(self.nodes, 0, 2) @@ -100,6 +100,8 @@ class RawTransactionsTest(BitcoinTestFramework): assert_raises_rpc_error(-3, "Amount out of range", self.nodes[0].createrawtransaction, [], {address: -1}) assert_raises_rpc_error(-8, "Invalid parameter, duplicated address: %s" % address, self.nodes[0].createrawtransaction, [], multidict([(address, 1), (address, 1)])) assert_raises_rpc_error(-8, "Invalid parameter, duplicated address: %s" % address, self.nodes[0].createrawtransaction, [], [{address: 1}, {address: 1}]) + assert_raises_rpc_error(-8, "Invalid parameter, duplicate key: data", self.nodes[0].createrawtransaction, [], [{"data": 'aa'}, {"data": "bb"}]) + assert_raises_rpc_error(-8, "Invalid parameter, duplicate key: data", self.nodes[0].createrawtransaction, [], multidict([("data", 'aa'), ("data", "bb")])) assert_raises_rpc_error(-8, "Invalid parameter, key-value pair must contain exactly one key", self.nodes[0].createrawtransaction, [], [{'a': 1, 'b': 2}]) assert_raises_rpc_error(-8, "Invalid parameter, key-value pair not an object as expected", self.nodes[0].createrawtransaction, [], [['key-value pair1'], ['2']]) @@ -127,19 +129,12 @@ class RawTransactionsTest(BitcoinTestFramework): bytes_to_hex_str(tx.serialize()), self.nodes[2].createrawtransaction(inputs=[{'txid': txid, 'vout': 9}], outputs=[{address: 99}, {address2: 99}]), ) - # Two data outputs - tx.deserialize(BytesIO(hex_str_to_bytes(self.nodes[2].createrawtransaction(inputs=[{'txid': txid, 'vout': 9}], outputs=multidict([('data', '99'), ('data', '99')]))))) - assert_equal(len(tx.vout), 2) - assert_equal( - bytes_to_hex_str(tx.serialize()), - self.nodes[2].createrawtransaction(inputs=[{'txid': txid, 'vout': 9}], outputs=[{'data': '99'}, {'data': '99'}]), - ) # Multiple mixed outputs - tx.deserialize(BytesIO(hex_str_to_bytes(self.nodes[2].createrawtransaction(inputs=[{'txid': txid, 'vout': 9}], outputs=multidict([(address, 99), ('data', '99'), ('data', '99')]))))) + tx.deserialize(BytesIO(hex_str_to_bytes(self.nodes[2].createrawtransaction(inputs=[{'txid': txid, 'vout': 9}], outputs=multidict([(address, 99), (address2, 99), ('data', '99')]))))) assert_equal(len(tx.vout), 3) assert_equal( bytes_to_hex_str(tx.serialize()), - self.nodes[2].createrawtransaction(inputs=[{'txid': txid, 'vout': 9}], outputs=[{address: 99}, {'data': '99'}, {'data': '99'}]), + self.nodes[2].createrawtransaction(inputs=[{'txid': txid, 'vout': 9}], outputs=[{address: 99}, {address2: 99}, {'data': '99'}]), ) for type in ["bech32", "p2sh-segwit", "legacy"]: diff --git a/test/functional/rpc_scantxoutset.py b/test/functional/rpc_scantxoutset.py index 96f9ccdbdb..11b4db6ec5 100755 --- a/test/functional/rpc_scantxoutset.py +++ b/test/functional/rpc_scantxoutset.py @@ -10,6 +10,9 @@ from decimal import Decimal import shutil import os +def descriptors(out): + return sorted(u['desc'] for u in out['unspents']) + class ScantxoutsetTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 1 @@ -65,6 +68,8 @@ class ScantxoutsetTest(BitcoinTestFramework): assert_equal(self.nodes[0].scantxoutset("start", [ "addr(" + addr_P2SH_SEGWIT + ")", "addr(" + addr_LEGACY + ")", "combo(" + pubk3 + ")"])['total_amount'], Decimal("0.007")) self.log.info("Test extended key derivation.") + # Run various scans, and verify that the sum of the amounts of the matches corresponds to the expected subset. + # Note that all amounts in the UTXO set are powers of 2 multiplied by 0.001 BTC, so each amounts uniquely identifies a subset. assert_equal(self.nodes[0].scantxoutset("start", [ "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/0'/0h/0h)"])['total_amount'], Decimal("0.008")) assert_equal(self.nodes[0].scantxoutset("start", [ "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/0'/0'/1h)"])['total_amount'], Decimal("0.016")) assert_equal(self.nodes[0].scantxoutset("start", [ "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/0h/0'/1500')"])['total_amount'], Decimal("0.032")) @@ -82,7 +87,7 @@ class ScantxoutsetTest(BitcoinTestFramework): assert_equal(self.nodes[0].scantxoutset("start", [ "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/1)"])['total_amount'], Decimal("8.192")) assert_equal(self.nodes[0].scantxoutset("start", [ "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/1500)"])['total_amount'], Decimal("16.384")) assert_equal(self.nodes[0].scantxoutset("start", [ "combo(tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/1/1/0)"])['total_amount'], Decimal("4.096")) - assert_equal(self.nodes[0].scantxoutset("start", [ "combo(tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/1/1/1)"])['total_amount'], Decimal("8.192")) + assert_equal(self.nodes[0].scantxoutset("start", [ "combo([abcdef88/1/2'/3/4h]tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/1/1/1)"])['total_amount'], Decimal("8.192")) assert_equal(self.nodes[0].scantxoutset("start", [ "combo(tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/1/1/1500)"])['total_amount'], Decimal("16.384")) assert_equal(self.nodes[0].scantxoutset("start", [ {"desc": "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/*')", "range": 1499}])['total_amount'], Decimal("1.536")) assert_equal(self.nodes[0].scantxoutset("start", [ {"desc": "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/*')", "range": 1500}])['total_amount'], Decimal("3.584")) @@ -91,5 +96,10 @@ class ScantxoutsetTest(BitcoinTestFramework): assert_equal(self.nodes[0].scantxoutset("start", [ {"desc": "combo(tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/1/1/*)", "range": 1499}])['total_amount'], Decimal("12.288")) assert_equal(self.nodes[0].scantxoutset("start", [ {"desc": "combo(tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/1/1/*)", "range": 1500}])['total_amount'], Decimal("28.672")) + # Test the reported descriptors for a few matches + assert_equal(descriptors(self.nodes[0].scantxoutset("start", [ {"desc": "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/0h/0'/*)", "range": 1499}])), ["pkh([0c5f9a1e/0'/0'/0]026dbd8b2315f296d36e6b6920b1579ca75569464875c7ebe869b536a7d9503c8c)", "pkh([0c5f9a1e/0'/0'/1]033e6f25d76c00bedb3a8993c7d5739ee806397f0529b1b31dda31ef890f19a60c)"]) + assert_equal(descriptors(self.nodes[0].scantxoutset("start", [ "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/0)"])), ["pkh([0c5f9a1e/1/1/0]03e1c5b6e650966971d7e71ef2674f80222752740fc1dfd63bbbd220d2da9bd0fb)"]) + assert_equal(descriptors(self.nodes[0].scantxoutset("start", [ {"desc": "combo(tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/1/1/*)", "range": 1500}])), ['pkh([0c5f9a1e/1/1/0]03e1c5b6e650966971d7e71ef2674f80222752740fc1dfd63bbbd220d2da9bd0fb)', 'pkh([0c5f9a1e/1/1/1500]03832901c250025da2aebae2bfb38d5c703a57ab66ad477f9c578bfbcd78abca6f)', 'pkh([0c5f9a1e/1/1/1]030d820fc9e8211c4169be8530efbc632775d8286167afd178caaf1089b77daba7)']) + if __name__ == '__main__': ScantxoutsetTest().main() diff --git a/test/functional/rpc_zmq.py b/test/functional/rpc_zmq.py deleted file mode 100755 index bfa6b06f67..0000000000 --- a/test/functional/rpc_zmq.py +++ /dev/null @@ -1,37 +0,0 @@ -#!/usr/bin/env python3 -# Copyright (c) 2018 The Bitcoin Core developers -# Distributed under the MIT software license, see the accompanying -# file COPYING or http://www.opensource.org/licenses/mit-license.php. -"""Test for the ZMQ RPC methods.""" - -from test_framework.test_framework import BitcoinTestFramework -from test_framework.util import assert_equal - - -class RPCZMQTest(BitcoinTestFramework): - - address = "tcp://127.0.0.1:28332" - - def set_test_params(self): - self.num_nodes = 1 - self.setup_clean_chain = True - - def skip_test_if_missing_module(self): - self.skip_if_no_py3_zmq() - self.skip_if_no_bitcoind_zmq() - - def run_test(self): - self._test_getzmqnotifications() - - def _test_getzmqnotifications(self): - self.restart_node(0, extra_args=[]) - assert_equal(self.nodes[0].getzmqnotifications(), []) - - self.restart_node(0, extra_args=["-zmqpubhashtx=%s" % self.address]) - assert_equal(self.nodes[0].getzmqnotifications(), [ - {"type": "pubhashtx", "address": self.address}, - ]) - - -if __name__ == '__main__': - RPCZMQTest().main() diff --git a/test/functional/test_framework/address.py b/test/functional/test_framework/address.py index d1fb97b024..456d43aa2e 100644 --- a/test/functional/test_framework/address.py +++ b/test/functional/test_framework/address.py @@ -9,8 +9,11 @@ from .util import bytes_to_hex_str, hex_str_to_bytes from . import segwit_addr +ADDRESS_BCRT1_UNSPENDABLE = 'bcrt1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq3xueyj' + chars = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz' + def byte_to_base58(b, version): result = '' str = bytes_to_hex_str(b) diff --git a/test/functional/test_framework/blocktools.py b/test/functional/test_framework/blocktools.py index 35004fb588..6b47cae4c3 100644 --- a/test/functional/test_framework/blocktools.py +++ b/test/functional/test_framework/blocktools.py @@ -41,12 +41,15 @@ from .script import ( from .util import assert_equal from io import BytesIO +MAX_BLOCK_SIGOPS = 20000 + # From BIP141 WITNESS_COMMITMENT_HEADER = b"\xaa\x21\xa9\xed" -def create_block(hashprev, coinbase, ntime=None): +def create_block(hashprev, coinbase, ntime=None, *, version=1): """Create a block (with regtest difficulty).""" block = CBlock() + block.nVersion = version if ntime is None: import time block.nTime = int(time.time() + 600) @@ -169,7 +172,7 @@ def get_legacy_sigopcount_tx(tx, accurate=True): return count def witness_script(use_p2wsh, pubkey): - """Create a scriptPubKey for a pay-to-wtiness TxOut. + """Create a scriptPubKey for a pay-to-witness TxOut. This is either a P2WPKH output for the given pubkey, or a P2WSH output of a 1-of-1 multisig for the given pubkey. Returns the hex encoding of the diff --git a/test/functional/test_framework/messages.py b/test/functional/test_framework/messages.py index 8e9372767d..356a45d6d0 100755 --- a/test/functional/test_framework/messages.py +++ b/test/functional/test_framework/messages.py @@ -35,7 +35,6 @@ MY_VERSION = 70014 # past bip-31 for ping/pong MY_SUBVERSION = b"/python-mininode-tester:0.0.3/" MY_RELAY = 1 # from version 70001 onwards, fRelay should be appended to version messages (BIP37) -MAX_INV_SZ = 50000 MAX_LOCATOR_SZ = 101 MAX_BLOCK_BASE_SIZE = 1000000 @@ -58,9 +57,6 @@ MSG_TYPE_MASK = 0xffffffff >> 2 def sha256(s): return hashlib.new('sha256', s).digest() -def ripemd160(s): - return hashlib.new('ripemd160', s).digest() - def hash256(s): return sha256(sha256(s)) @@ -454,6 +450,8 @@ class CTransaction: if flags != 0: self.wit.vtxinwit = [CTxInWitness() for i in range(len(self.vin))] self.wit.deserialize(f) + else: + self.wit = CTxWitness() self.nLockTime = struct.unpack("<I", f.read(4))[0] self.sha256 = None self.hash = None @@ -768,7 +766,7 @@ class HeaderAndShortIDs: self.prefilled_txn = [] self.use_witness = False - if p2pheaders_and_shortids != None: + if p2pheaders_and_shortids is not None: self.header = p2pheaders_and_shortids.header self.nonce = p2pheaders_and_shortids.nonce self.shortids = p2pheaders_and_shortids.shortids @@ -826,7 +824,7 @@ class BlockTransactionsRequest: def __init__(self, blockhash=0, indexes = None): self.blockhash = blockhash - self.indexes = indexes if indexes != None else [] + self.indexes = indexes if indexes is not None else [] def deserialize(self, f): self.blockhash = deser_uint256(f) @@ -867,7 +865,7 @@ class BlockTransactions: def __init__(self, blockhash=0, transactions = None): self.blockhash = blockhash - self.transactions = transactions if transactions != None else [] + self.transactions = transactions if transactions is not None else [] def deserialize(self, f): self.blockhash = deser_uint256(f) @@ -887,13 +885,12 @@ class BlockTransactions: class CPartialMerkleTree: - __slots__ = ("fBad", "nTransactions", "vBits", "vHash") + __slots__ = ("nTransactions", "vBits", "vHash") def __init__(self): self.nTransactions = 0 self.vHash = [] self.vBits = [] - self.fBad = False def deserialize(self, f): self.nTransactions = struct.unpack("<i", f.read(4))[0] @@ -1057,7 +1054,7 @@ class msg_getdata: command = b"getdata" def __init__(self, inv=None): - self.inv = inv if inv != None else [] + self.inv = inv if inv is not None else [] def deserialize(self, f): self.inv = deser_vector(f, CInv) @@ -1232,6 +1229,23 @@ class msg_mempool: return "msg_mempool()" +class msg_notfound: + __slots__ = ("vec", ) + command = b"notfound" + + def __init__(self, vec=None): + self.vec = vec or [] + + def deserialize(self, f): + self.vec = deser_vector(f, CInv) + + def serialize(self): + return ser_vector(self.vec) + + def __repr__(self): + return "msg_notfound(vec=%s)" % (repr(self.vec)) + + class msg_sendheaders: __slots__ = () command = b"sendheaders" diff --git a/test/functional/test_framework/mininode.py b/test/functional/test_framework/mininode.py index f8caa57250..ca5734d67d 100755 --- a/test/functional/test_framework/mininode.py +++ b/test/functional/test_framework/mininode.py @@ -21,7 +21,38 @@ import struct import sys import threading -from test_framework.messages import CBlockHeader, MIN_VERSION_SUPPORTED, msg_addr, msg_block, MSG_BLOCK, msg_blocktxn, msg_cmpctblock, msg_feefilter, msg_getaddr, msg_getblocks, msg_getblocktxn, msg_getdata, msg_getheaders, msg_headers, msg_inv, msg_mempool, msg_ping, msg_pong, msg_reject, msg_sendcmpct, msg_sendheaders, msg_tx, MSG_TX, MSG_TYPE_MASK, msg_verack, msg_version, NODE_NETWORK, NODE_WITNESS, sha256 +from test_framework.messages import ( + CBlockHeader, + MIN_VERSION_SUPPORTED, + msg_addr, + msg_block, + MSG_BLOCK, + msg_blocktxn, + msg_cmpctblock, + msg_feefilter, + msg_getaddr, + msg_getblocks, + msg_getblocktxn, + msg_getdata, + msg_getheaders, + msg_headers, + msg_inv, + msg_mempool, + msg_notfound, + msg_ping, + msg_pong, + msg_reject, + msg_sendcmpct, + msg_sendheaders, + msg_tx, + MSG_TX, + MSG_TYPE_MASK, + msg_verack, + msg_version, + NODE_NETWORK, + NODE_WITNESS, + sha256, +) from test_framework.util import wait_until logger = logging.getLogger("TestFramework.mininode") @@ -40,6 +71,7 @@ MESSAGEMAP = { b"headers": msg_headers, b"inv": msg_inv, b"mempool": msg_mempool, + b"notfound": msg_notfound, b"ping": msg_ping, b"pong": msg_pong, b"reject": msg_reject, @@ -175,10 +207,13 @@ class P2PConnection(asyncio.Protocol): This method takes a P2P payload, builds the P2P header and adds the message to the send buffer to be sent over the socket.""" + tmsg = self.build_message(message) + self._log_message("send", message) + return self.send_raw_message(tmsg) + + def send_raw_message(self, raw_message_bytes): if not self.is_connected: raise IOError('Not connected') - self._log_message("send", message) - tmsg = self._build_message(message) def maybe_write(): if not self._transport: @@ -188,12 +223,12 @@ class P2PConnection(asyncio.Protocol): # Python 3.4 versions. if hasattr(self._transport, 'is_closing') and self._transport.is_closing(): return - self._transport.write(tmsg) + self._transport.write(raw_message_bytes) NetworkThread.network_event_loop.call_soon_threadsafe(maybe_write) # Class utility methods - def _build_message(self, message): + def build_message(self, message): """Build a serialized P2P message""" command = message.command data = message.serialize() @@ -295,6 +330,7 @@ class P2PInterface(P2PConnection): def on_getheaders(self, message): pass def on_headers(self, message): pass def on_mempool(self, message): pass + def on_notfound(self, message): pass def on_pong(self, message): pass def on_reject(self, message): pass def on_sendcmpct(self, message): pass @@ -313,7 +349,7 @@ class P2PInterface(P2PConnection): self.send_message(msg_pong(message.nonce)) def on_verack(self, message): - self.verack_received = True + pass def on_version(self, message): assert message.nVersion >= MIN_VERSION_SUPPORTED, "Version {} received. Test framework only supports versions greater than {}".format(message.nVersion, MIN_VERSION_SUPPORTED) @@ -376,9 +412,9 @@ class P2PInterface(P2PConnection): # Message sending helper functions - def send_and_ping(self, message): + def send_and_ping(self, message, timeout=60): self.send_message(message) - self.sync_with_ping() + self.sync_with_ping(timeout=timeout) # Sync up with the node def sync_with_ping(self, timeout=60): @@ -475,14 +511,14 @@ class P2PDataStore(P2PInterface): if response is not None: self.send_message(response) - def send_blocks_and_test(self, blocks, node, *, success=True, request_block=True, reject_reason=None, expect_disconnect=False, timeout=60): + def send_blocks_and_test(self, blocks, node, *, success=True, force_send=False, reject_reason=None, expect_disconnect=False, timeout=60): """Send blocks to test node and test whether the tip advances. - add all blocks to our block_store - send a headers message for the final block - the on_getheaders handler will ensure that any getheaders are responded to - - if request_block is True: wait for getdata for each of the blocks. The on_getdata handler will - ensure that any getdata messages are responded to + - if force_send is False: wait for getdata for each of the blocks. The on_getdata handler will + ensure that any getdata messages are responded to. Otherwise send the full block unsolicited. - if success is True: assert that the node's tip advances to the most recent block - if success is False: assert that the node's tip doesn't advance - if reject_reason is set: assert that the correct reject message is logged""" @@ -494,15 +530,17 @@ class P2PDataStore(P2PInterface): reject_reason = [reject_reason] if reject_reason else [] with node.assert_debug_log(expected_msgs=reject_reason): - self.send_message(msg_headers([CBlockHeader(blocks[-1])])) - - if request_block: + if force_send: + for b in blocks: + self.send_message(msg_block(block=b)) + else: + self.send_message(msg_headers([CBlockHeader(blocks[-1])])) wait_until(lambda: blocks[-1].sha256 in self.getdata_requests, timeout=timeout, lock=mininode_lock) if expect_disconnect: - self.wait_for_disconnect() + self.wait_for_disconnect(timeout=timeout) else: - self.sync_with_ping() + self.sync_with_ping(timeout=timeout) if success: wait_until(lambda: node.getbestblockhash() == blocks[-1].hash, timeout=timeout) diff --git a/test/functional/test_framework/script.py b/test/functional/test_framework/script.py index 2fe44010ba..012c80a1be 100644 --- a/test/functional/test_framework/script.py +++ b/test/functional/test_framework/script.py @@ -385,6 +385,22 @@ class CScriptNum: r[-1] |= 0x80 return bytes([len(r)]) + r + @staticmethod + def decode(vch): + result = 0 + # We assume valid push_size and minimal encoding + value = vch[1:] + if len(value) == 0: + return result + for i, byte in enumerate(value): + result |= int(byte) << 8*i + if value[-1] >= 0x80: + # Mask for all but the highest result bit + num_mask = (2**(len(value)*8) - 1) >> 1 + result &= num_mask + result *= -1 + return result + class CScript(bytes): """Serialized script @@ -434,6 +450,10 @@ class CScript(bytes): # join makes no sense for a CScript() raise NotImplementedError + # Python 3.4 compatibility + def hex(self): + return hexlify(self).decode('ascii') + def __new__(cls, value=b''): if isinstance(value, bytes) or isinstance(value, bytearray): return super(CScript, cls).__new__(cls, value) diff --git a/test/functional/test_framework/socks5.py b/test/functional/test_framework/socks5.py index dd0f209268..a21c864e75 100644 --- a/test/functional/test_framework/socks5.py +++ b/test/functional/test_framework/socks5.py @@ -54,10 +54,9 @@ class Socks5Command(): return 'Socks5Command(%s,%s,%s,%s,%s,%s)' % (self.cmd, self.atyp, self.addr, self.port, self.username, self.password) class Socks5Connection(): - def __init__(self, serv, conn, peer): + def __init__(self, serv, conn): self.serv = serv self.conn = conn - self.peer = peer def handle(self): """Handle socks5 request according to RFC192.""" @@ -137,9 +136,9 @@ class Socks5Server(): def run(self): while self.running: - (sockconn, peer) = self.s.accept() + (sockconn, _) = self.s.accept() if self.running: - conn = Socks5Connection(self, sockconn, peer) + conn = Socks5Connection(self, sockconn) thread = threading.Thread(None, conn.handle) thread.daemon = True thread.start() diff --git a/test/functional/test_framework/test_framework.py b/test/functional/test_framework/test_framework.py index 97710bd6cd..352fa32b5b 100755 --- a/test/functional/test_framework/test_framework.py +++ b/test/functional/test_framework/test_framework.py @@ -43,6 +43,8 @@ TEST_EXIT_PASSED = 0 TEST_EXIT_FAILED = 1 TEST_EXIT_SKIPPED = 77 +TMPDIR_PREFIX = "bitcoin_func_test_" + class SkipTest(Exception): """This exception is raised to skip a test""" @@ -93,7 +95,7 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass): self.nodes = [] self.network_thread = None self.mocktime = 0 - self.rpc_timewait = 60 # Wait for up to 60 seconds for the RPC server to respond + self.rpc_timeout = 60 # Wait for up to 60 seconds for the RPC server to respond self.supports_cli = False self.bind_to_localhost_only = True self.set_test_params() @@ -151,7 +153,7 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass): self.options.tmpdir = os.path.abspath(self.options.tmpdir) os.makedirs(self.options.tmpdir, exist_ok=False) else: - self.options.tmpdir = tempfile.mkdtemp(prefix="test") + self.options.tmpdir = tempfile.mkdtemp(prefix=TMPDIR_PREFIX) self._start_logging() self.log.debug('Setting up network thread') @@ -161,12 +163,13 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass): success = TestStatus.FAILED try: - if self.options.usecli and not self.supports_cli: - raise SkipTest("--usecli specified but test does not support using CLI") + if self.options.usecli: + if not self.supports_cli: + raise SkipTest("--usecli specified but test does not support using CLI") + self.skip_if_no_cli() self.skip_test_if_missing_module() self.setup_chain() self.setup_network() - self.import_deterministic_coinbase_privkeys() self.run_test() success = TestStatus.PASSED except JSONRPCException as e: @@ -259,11 +262,9 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass): extra_args = self.extra_args self.add_nodes(self.num_nodes, extra_args) self.start_nodes() + self.import_deterministic_coinbase_privkeys() def import_deterministic_coinbase_privkeys(self): - if self.setup_clean_chain: - return - for n in self.nodes: try: n.getwalletinfo() @@ -271,7 +272,7 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass): assert str(e).startswith('Method not found') continue - n.importprivkey(n.get_deterministic_priv_key().key) + n.importprivkey(privkey=n.get_deterministic_priv_key().key, label='coinbase') def run_test(self): """Tests must override this method to define test logic""" @@ -280,7 +281,10 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass): # Public helper methods. These can be accessed by the subclass test scripts. def add_nodes(self, num_nodes, extra_args=None, *, rpchost=None, binary=None): - """Instantiate TestNode objects""" + """Instantiate TestNode objects. + + Should only be called once after the nodes have been specified in + set_test_params().""" if self.bind_to_localhost_only: extra_confs = [["bind=127.0.0.1"]] * num_nodes else: @@ -293,7 +297,19 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass): assert_equal(len(extra_args), num_nodes) assert_equal(len(binary), num_nodes) for i in range(num_nodes): - self.nodes.append(TestNode(i, get_datadir_path(self.options.tmpdir, i), rpchost=rpchost, timewait=self.rpc_timewait, bitcoind=binary[i], bitcoin_cli=self.options.bitcoincli, mocktime=self.mocktime, coverage_dir=self.options.coveragedir, extra_conf=extra_confs[i], extra_args=extra_args[i], use_cli=self.options.usecli)) + self.nodes.append(TestNode( + i, + get_datadir_path(self.options.tmpdir, i), + rpchost=rpchost, + timewait=self.rpc_timeout, + bitcoind=binary[i], + bitcoin_cli=self.options.bitcoincli, + mocktime=self.mocktime, + coverage_dir=self.options.coveragedir, + extra_conf=extra_confs[i], + extra_args=extra_args[i], + use_cli=self.options.usecli, + )) def start_node(self, i, *args, **kwargs): """Start a bitcoind""" @@ -326,16 +342,16 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass): for node in self.nodes: coverage.write_all_rpc_commands(self.options.coveragedir, node.rpc) - def stop_node(self, i, expected_stderr=''): + def stop_node(self, i, expected_stderr='', wait=0): """Stop a bitcoind test node""" - self.nodes[i].stop_node(expected_stderr) + self.nodes[i].stop_node(expected_stderr, wait=wait) self.nodes[i].wait_until_stopped() - def stop_nodes(self): + def stop_nodes(self, wait=0): """Stop multiple bitcoind test nodes""" for node in self.nodes: # Issue RPC to stop nodes - node.stop_node() + node.stop_node(wait=wait) for node in self.nodes: # Wait for nodes to stop @@ -372,21 +388,6 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass): sync_blocks(group) sync_mempools(group) - def enable_mocktime(self): - """Enable mocktime for the script. - - mocktime may be needed for scripts that use the cached version of the - blockchain. If the cached version of the blockchain is used without - mocktime then the mempools will not sync due to IBD. - - For backward compatibility of the python scripts with previous - versions of the cache, this helper function sets mocktime to Jan 1, - 2014 + (201 * 10 * 60)""" - self.mocktime = 1388534400 + (201 * 10 * 60) - - def disable_mocktime(self): - self.mocktime = 0 - # Private helper methods. These should not be accessed by the subclass test scripts. def _start_logging(self): @@ -444,7 +445,18 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass): args = [self.options.bitcoind, "-datadir=" + datadir, '-disablewallet'] if i > 0: args.append("-connect=127.0.0.1:" + str(p2p_port(0))) - self.nodes.append(TestNode(i, get_datadir_path(self.options.cachedir, i), extra_conf=["bind=127.0.0.1"], extra_args=[], rpchost=None, timewait=self.rpc_timewait, bitcoind=self.options.bitcoind, bitcoin_cli=self.options.bitcoincli, mocktime=self.mocktime, coverage_dir=None)) + self.nodes.append(TestNode( + i, + get_datadir_path(self.options.cachedir, i), + extra_conf=["bind=127.0.0.1"], + extra_args=[], + rpchost=None, + timewait=self.rpc_timeout, + bitcoind=self.options.bitcoind, + bitcoin_cli=self.options.bitcoincli, + mocktime=self.mocktime, + coverage_dir=None, + )) self.nodes[i].args = args self.start_node(i) @@ -452,6 +464,11 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass): for node in self.nodes: node.wait_for_rpc_connection() + # For backward compatibility of the python scripts with previous + # versions of the cache, set mocktime to Jan 1, + # 2014 + (201 * 10 * 60)""" + self.mocktime = 1388534400 + (201 * 10 * 60) + # Create a 200-block-long chain; each of the 4 first nodes # gets 25 mature blocks and 25 immature. # Note: To preserve compatibility with older versions of @@ -459,7 +476,6 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass): # # blocks are created with timestamps 10 minutes apart # starting from 2010 minutes in the past - self.enable_mocktime() block_time = self.mocktime - (201 * 10 * 60) for i in range(2): for peer in range(4): @@ -473,7 +489,7 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass): # Shut them down, and clean up cache directories: self.stop_nodes() self.nodes = [] - self.disable_mocktime() + self.mocktime = 0 def cache_path(n, *paths): return os.path.join(get_datadir_path(self.options.cachedir, n), "regtest", *paths) diff --git a/test/functional/test_framework/test_node.py b/test/functional/test_framework/test_node.py index 7ab7fcfcb4..031a8824b1 100755 --- a/test/functional/test_framework/test_node.py +++ b/test/functional/test_framework/test_node.py @@ -68,7 +68,7 @@ class TestNode(): self.rpc_timeout = timewait self.binary = bitcoind self.coverage_dir = coverage_dir - if extra_conf != None: + if extra_conf is not None: append_config(datadir, extra_conf) # Most callers will just need to add extra args to the standard list below. # For those callers that need more flexibility, they can just set the args property directly. @@ -115,6 +115,25 @@ class TestNode(): ] return PRIV_KEYS[self.index] + def get_mem_rss_kilobytes(self): + """Get the memory usage (RSS) per `ps`. + + Returns None if `ps` is unavailable. + """ + assert self.running + + try: + return int(subprocess.check_output( + ["ps", "h", "-o", "rss", "{}".format(self.process.pid)], + stderr=subprocess.DEVNULL).split()[-1]) + + # Avoid failing on platforms where ps isn't installed. + # + # We could later use something like `psutils` to work across platforms. + except (FileNotFoundError, subprocess.SubprocessError): + self.log.exception("Unable to get memory usage") + return None + def _node_msg(self, msg: str) -> str: """Return a modified msg that identifies this node by its index as a debugging aid.""" return "[node %d] %s" % (self.index, msg) @@ -187,7 +206,9 @@ class TestNode(): if e.errno != errno.ECONNREFUSED: # Port not yet open? raise # unknown IO error except JSONRPCException as e: # Initialization phase - if e.error['code'] != -28: # RPC in warmup? + # -28 RPC in warmup + # -342 Service unavailable, RPC server started but is shutting down due to error + if e.error['code'] != -28 and e.error['code'] != -342: raise # unknown JSON RPC exception except ValueError as e: # cookie file not found and no rpcuser or rpcassword. bitcoind still starting if "No RPC credentials" not in str(e): @@ -195,6 +216,10 @@ class TestNode(): time.sleep(1.0 / poll_per_s) self._raise_assertion_error("Unable to connect to bitcoind") + def generate(self, nblocks, maxtries=1000000): + self.log.debug("TestNode.generate() dispatches `generate` call to `generatetoaddress`") + return self.generatetoaddress(nblocks=nblocks, address=self.get_deterministic_priv_key().address, maxtries=maxtries) + def get_wallet_rpc(self, wallet_name): if self.use_cli: return self.cli("-rpcwallet={}".format(wallet_name)) @@ -203,13 +228,13 @@ class TestNode(): wallet_path = "wallet/{}".format(urllib.parse.quote(wallet_name)) return self.rpc / wallet_path - def stop_node(self, expected_stderr=''): + def stop_node(self, expected_stderr='', wait=0): """Stop the node.""" if not self.running: return self.log.debug("Stopping node") try: - self.stop() + self.stop(wait=wait) except http.client.CannotSendRequest: self.log.exception("Unable to stop node.") @@ -265,6 +290,33 @@ class TestNode(): if re.search(re.escape(expected_msg), log, flags=re.MULTILINE) is None: self._raise_assertion_error('Expected message "{}" does not partially match log:\n\n{}\n\n'.format(expected_msg, print_log)) + @contextlib.contextmanager + def assert_memory_usage_stable(self, *, increase_allowed=0.03): + """Context manager that allows the user to assert that a node's memory usage (RSS) + hasn't increased beyond some threshold percentage. + + Args: + increase_allowed (float): the fractional increase in memory allowed until failure; + e.g. `0.12` for up to 12% increase allowed. + """ + before_memory_usage = self.get_mem_rss_kilobytes() + + yield + + after_memory_usage = self.get_mem_rss_kilobytes() + + if not (before_memory_usage and after_memory_usage): + self.log.warning("Unable to detect memory usage (RSS) - skipping memory check.") + return + + perc_increase_memory_usage = (after_memory_usage / before_memory_usage) - 1 + + if perc_increase_memory_usage > increase_allowed: + self._raise_assertion_error( + "Memory usage increased over threshold of {:.3f}% from {} to {} ({:.3f}%)".format( + increase_allowed * 100, before_memory_usage, after_memory_usage, + perc_increase_memory_usage * 100)) + def assert_start_raises_init_error(self, extra_args=None, expected_msg=None, match=ErrorMatch.FULL_TEXT, *args, **kwargs): """Attempt to start the node and expect it to raise an error. diff --git a/test/functional/test_framework/util.py b/test/functional/test_framework/util.py index b355816d8b..d0a78d8dfd 100644 --- a/test/functional/test_framework/util.py +++ b/test/functional/test_framework/util.py @@ -326,7 +326,7 @@ def get_auth_cookie(datadir): if line.startswith("rpcpassword="): assert password is None # Ensure that there is only one rpcpassword line password = line.split("=")[1].strip("\n") - if os.path.isfile(os.path.join(datadir, "regtest", ".cookie")): + if os.path.isfile(os.path.join(datadir, "regtest", ".cookie")) and os.access(os.path.join(datadir, "regtest", ".cookie"), os.R_OK): with open(os.path.join(datadir, "regtest", ".cookie"), 'r', encoding="ascii") as f: userpass = f.read() split_userpass = userpass.split(':') diff --git a/test/functional/test_framework/wallet_util.py b/test/functional/test_framework/wallet_util.py new file mode 100755 index 0000000000..c0dfa4c3f0 --- /dev/null +++ b/test/functional/test_framework/wallet_util.py @@ -0,0 +1,99 @@ +#!/usr/bin/env python3 +# Copyright (c) 2018 The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. +"""Useful util functions for testing the wallet""" +from collections import namedtuple + +from test_framework.address import ( + key_to_p2pkh, + key_to_p2sh_p2wpkh, + key_to_p2wpkh, + script_to_p2sh, + script_to_p2sh_p2wsh, + script_to_p2wsh, +) +from test_framework.script import ( + CScript, + OP_0, + OP_2, + OP_3, + OP_CHECKMULTISIG, + OP_CHECKSIG, + OP_DUP, + OP_EQUAL, + OP_EQUALVERIFY, + OP_HASH160, + hash160, + sha256, +) +from test_framework.util import hex_str_to_bytes + +Key = namedtuple('Key', ['privkey', + 'pubkey', + 'p2pkh_script', + 'p2pkh_addr', + 'p2wpkh_script', + 'p2wpkh_addr', + 'p2sh_p2wpkh_script', + 'p2sh_p2wpkh_redeem_script', + 'p2sh_p2wpkh_addr']) + +Multisig = namedtuple('Multisig', ['privkeys', + 'pubkeys', + 'p2sh_script', + 'p2sh_addr', + 'redeem_script', + 'p2wsh_script', + 'p2wsh_addr', + 'p2sh_p2wsh_script', + 'p2sh_p2wsh_addr']) + +def get_key(node): + """Generate a fresh key on node + + Returns a named tuple of privkey, pubkey and all address and scripts.""" + addr = node.getnewaddress() + pubkey = node.getaddressinfo(addr)['pubkey'] + pkh = hash160(hex_str_to_bytes(pubkey)) + return Key(privkey=node.dumpprivkey(addr), + pubkey=pubkey, + p2pkh_script=CScript([OP_DUP, OP_HASH160, pkh, OP_EQUALVERIFY, OP_CHECKSIG]).hex(), + p2pkh_addr=key_to_p2pkh(pubkey), + p2wpkh_script=CScript([OP_0, pkh]).hex(), + p2wpkh_addr=key_to_p2wpkh(pubkey), + p2sh_p2wpkh_script=CScript([OP_HASH160, hash160(CScript([OP_0, pkh])), OP_EQUAL]).hex(), + p2sh_p2wpkh_redeem_script=CScript([OP_0, pkh]).hex(), + p2sh_p2wpkh_addr=key_to_p2sh_p2wpkh(pubkey)) + +def get_multisig(node): + """Generate a fresh 2-of-3 multisig on node + + Returns a named tuple of privkeys, pubkeys and all address and scripts.""" + addrs = [] + pubkeys = [] + for _ in range(3): + addr = node.getaddressinfo(node.getnewaddress()) + addrs.append(addr['address']) + pubkeys.append(addr['pubkey']) + script_code = CScript([OP_2] + [hex_str_to_bytes(pubkey) for pubkey in pubkeys] + [OP_3, OP_CHECKMULTISIG]) + witness_script = CScript([OP_0, sha256(script_code)]) + return Multisig(privkeys=[node.dumpprivkey(addr) for addr in addrs], + pubkeys=pubkeys, + p2sh_script=CScript([OP_HASH160, hash160(script_code), OP_EQUAL]).hex(), + p2sh_addr=script_to_p2sh(script_code), + redeem_script=script_code.hex(), + p2wsh_script=witness_script.hex(), + p2wsh_addr=script_to_p2wsh(script_code), + p2sh_p2wsh_script=CScript([OP_HASH160, witness_script, OP_EQUAL]).hex(), + p2sh_p2wsh_addr=script_to_p2sh_p2wsh(script_code)) + +def test_address(node, address, **kwargs): + """Get address info for `address` and test whether the returned values are as expected.""" + addr_info = node.getaddressinfo(address) + for key, value in kwargs.items(): + if value is None: + if key in addr_info.keys(): + raise AssertionError("key {} unexpectedly returned in getaddressinfo.".format(key)) + elif addr_info[key] != value: + raise AssertionError("key {} value {} did not match expected value {}".format(key, addr_info[key], value)) diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py index d9960460d9..8c6f6706e7 100755 --- a/test/functional/test_runner.py +++ b/test/functional/test_runner.py @@ -68,9 +68,6 @@ if os.name != 'nt' or sys.getwindowsversion() >= (10, 0, 14393): TEST_EXIT_PASSED = 0 TEST_EXIT_SKIPPED = 77 -# 20 minutes represented in seconds -TRAVIS_TIMEOUT_DURATION = 20 * 60 - BASE_SCRIPTS = [ # Scripts that are run by the travis build process. # Longest test should go first, to favor running tests in parallel @@ -123,6 +120,7 @@ BASE_SCRIPTS = [ 'wallet_disableprivatekeys.py', 'wallet_disableprivatekeys.py --usecli', 'interface_http.py', + 'interface_rpc.py', 'rpc_psbt.py', 'rpc_users.py', 'feature_proxy.py', @@ -139,6 +137,7 @@ BASE_SCRIPTS = [ 'mining_prioritisetransaction.py', 'p2p_invalid_locator.py', 'p2p_invalid_block.py', + 'p2p_invalid_messages.py', 'p2p_invalid_tx.py', 'feature_assumevalid.py', 'example_test.py', @@ -152,11 +151,13 @@ BASE_SCRIPTS = [ 'feature_versionbits_warning.py', 'rpc_preciousblock.py', 'wallet_importprunedfunds.py', - 'rpc_zmq.py', + 'p2p_leak_tx.py', 'rpc_signmessage.py', + 'wallet_balance.py', 'feature_nulldummy.py', 'mempool_accept.py', 'wallet_import_rescan.py', + 'wallet_import_with_label.py', 'rpc_bind.py --ipv4', 'rpc_bind.py --ipv6', 'rpc_bind.py --nonloopback', @@ -173,8 +174,11 @@ BASE_SCRIPTS = [ 'wallet_fallbackfee.py', 'feature_minchainwork.py', 'rpc_getblockstats.py', + 'wallet_create_tx.py', 'p2p_fingerprint.py', 'feature_uacomment.py', + 'wallet_coinbase_category.py', + 'feature_filelock.py', 'p2p_unrequested_blocks.py', 'feature_includeconf.py', 'rpc_scantxoutset.py', @@ -184,6 +188,7 @@ BASE_SCRIPTS = [ 'feature_config_args.py', 'rpc_help.py', 'feature_help.py', + 'feature_shutdown.py', # Don't append tests at the end to avoid merge conflicts # Put them in a random line within the section that fits their approximate run-time ] @@ -213,15 +218,16 @@ def main(): epilog=''' Help text and arguments for individual test script:''', formatter_class=argparse.RawTextHelpFormatter) - parser.add_argument('--combinedlogslen', '-c', type=int, default=0, help='print a combined log (of length n lines) from all test nodes and test framework to the console on failure.') + parser.add_argument('--combinedlogslen', '-c', type=int, default=0, metavar='n', help='On failure, print a log (of length n lines) to the console, combined from the test framework and all test nodes.') parser.add_argument('--coverage', action='store_true', help='generate a basic coverage report for the RPC interface') + parser.add_argument('--ci', action='store_true', help='Run checks and code that are usually only enabled in a continuous integration environment') parser.add_argument('--exclude', '-x', help='specify a comma-separated-list of scripts to exclude.') parser.add_argument('--extended', action='store_true', help='run the extended test suite in addition to the basic tests') parser.add_argument('--force', '-f', action='store_true', help='run tests even on platforms where they are disabled by default (e.g. windows).') parser.add_argument('--help', '-h', '-?', action='store_true', help='print help text and exit') parser.add_argument('--jobs', '-j', type=int, default=4, help='how many test scripts to run in parallel. Default=4.') parser.add_argument('--keepcache', '-k', action='store_true', help='the default behavior is to flush the cache directory on startup. --keepcache retains the cache from the previous testrun.') - parser.add_argument('--quiet', '-q', action='store_true', help='only print results summary and failure logs') + 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') args, unknown_args = parser.parse_known_args() @@ -270,7 +276,7 @@ def main(): 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 = [re.sub("\.py$", "", test) + ".py" for test in tests] + tests = [test + ".py" if ".py" not in test else test for test in tests] for test in tests: if test in ALL_SCRIPTS: test_list.append(test) @@ -305,25 +311,26 @@ def main(): subprocess.check_call([sys.executable, os.path.join(config["environment"]["SRCDIR"], 'test', 'functional', test_list[0].split()[0]), '-h']) sys.exit(0) - check_script_list(config["environment"]["SRCDIR"]) + check_script_list(src_dir=config["environment"]["SRCDIR"], fail_on_warn=args.ci) check_script_prefixes() if not args.keepcache: shutil.rmtree("%s/test/cache" % config["environment"]["BUILDDIR"], ignore_errors=True) run_tests( - test_list, - config["environment"]["SRCDIR"], - config["environment"]["BUILDDIR"], - tmpdir, + test_list=test_list, + src_dir=config["environment"]["SRCDIR"], + build_dir=config["environment"]["BUILDDIR"], + tmpdir=tmpdir, jobs=args.jobs, enable_coverage=args.coverage, args=passon_args, combined_logs_len=args.combinedlogslen, failfast=args.failfast, + runs_ci=args.ci, ) -def run_tests(test_list, src_dir, build_dir, tmpdir, jobs=1, enable_coverage=False, args=None, combined_logs_len=0, failfast=False): +def run_tests(*, test_list, src_dir, build_dir, tmpdir, jobs=1, enable_coverage=False, args=None, combined_logs_len=0, failfast=False, runs_ci): args = args or [] # Warn if bitcoind is already running (unix only) @@ -358,22 +365,29 @@ def run_tests(test_list, src_dir, build_dir, tmpdir, jobs=1, enable_coverage=Fal raise #Run Tests - job_queue = TestHandler(jobs, tests_dir, tmpdir, test_list, flags) + job_queue = TestHandler( + num_tests_parallel=jobs, + tests_dir=tests_dir, + tmpdir=tmpdir, + test_list=test_list, + flags=flags, + timeout_duration=40 * 60 if runs_ci else float('inf'), # in seconds + ) start_time = time.time() test_results = [] max_len_name = len(max(test_list, key=len)) - - for _ in range(len(test_list)): + test_count = len(test_list) + for i in range(test_count): test_result, testdir, stdout, stderr = job_queue.get_next() test_results.append(test_result) - + done_str = "{}/{} - {}{}{}".format(i + 1, test_count, BOLD[1], test_result.name, BOLD[0]) if test_result.status == "Passed": - logging.debug("\n%s%s%s passed, Duration: %s s" % (BOLD[1], test_result.name, BOLD[0], test_result.time)) + logging.debug("%s passed, Duration: %s s" % (done_str, test_result.time)) elif test_result.status == "Skipped": - logging.debug("\n%s%s%s skipped" % (BOLD[1], test_result.name, BOLD[0])) + logging.debug("%s skipped" % (done_str)) else: - print("\n%s%s%s failed, Duration: %s s\n" % (BOLD[1], test_result.name, BOLD[0], test_result.time)) + print("%s failed, Duration: %s s\n" % (done_str, test_result.time)) print(BOLD[1] + 'stdout:\n' + BOLD[0] + stdout + '\n') print(BOLD[1] + 'stderr:\n' + BOLD[0] + stderr + '\n') if combined_logs_len and os.path.isdir(testdir): @@ -439,11 +453,12 @@ class TestHandler: Trigger the test scripts passed in via the list. """ - def __init__(self, num_tests_parallel, tests_dir, tmpdir, test_list=None, flags=None): - assert(num_tests_parallel >= 1) + def __init__(self, *, num_tests_parallel, tests_dir, tmpdir, test_list, flags, timeout_duration): + assert num_tests_parallel >= 1 self.num_jobs = num_tests_parallel self.tests_dir = tests_dir self.tmpdir = tmpdir + self.timeout_duration = timeout_duration self.test_list = test_list self.flags = flags self.num_running = 0 @@ -472,12 +487,13 @@ class TestHandler: log_stderr)) if not self.jobs: raise IndexError('pop from empty list') + dot_count = 0 while True: # Return first proc that finishes time.sleep(.5) for job in self.jobs: (name, start_time, proc, testdir, log_out, log_err) = job - if os.getenv('TRAVIS') == 'true' and int(time.time() - start_time) > TRAVIS_TIMEOUT_DURATION: + if int(time.time() - start_time) > self.timeout_duration: # In travis, timeout individual tests (to stop tests hanging and not providing useful output). proc.send_signal(signal.SIGINT) if proc.poll() is not None: @@ -492,9 +508,12 @@ class TestHandler: status = "Failed" self.num_running -= 1 self.jobs.remove(job) - + clearline = '\r' + (' ' * dot_count) + '\r' + print(clearline, end='', flush=True) + dot_count = 0 return TestResult(name, status, int(time.time() - start_time)), testdir, stdout, stderr print('.', end='', flush=True) + dot_count += 1 def kill_and_join(self): """Send SIGKILL to all jobs and block until all have ended.""" @@ -552,7 +571,7 @@ def check_script_prefixes(): raise AssertionError("Some tests are not following naming convention!") -def check_script_list(src_dir): +def check_script_list(*, src_dir, fail_on_warn): """Check scripts directory. Check that there are no scripts in the functional tests directory which are @@ -562,10 +581,11 @@ def check_script_list(src_dir): missed_tests = list(python_files - set(map(lambda x: x.split()[0], ALL_SCRIPTS + NON_SCRIPTS))) if len(missed_tests) != 0: print("%sWARNING!%s The following scripts are not being run: %s. Check the test lists in test_runner.py." % (BOLD[1], BOLD[0], str(missed_tests))) - if os.getenv('TRAVIS') == 'true': + if fail_on_warn: # On travis this warning is an error to prevent merging incomplete commits into master sys.exit(1) + class RPCCoverage(): """ Coverage reporting utilities for test_runner. @@ -621,7 +641,7 @@ class RPCCoverage(): with open(coverage_ref_filename, 'r', encoding="utf8") as coverage_ref_file: all_cmds.update([line.strip() for line in coverage_ref_file.readlines()]) - for root, dirs, files in os.walk(self.dir): + for root, _, files in os.walk(self.dir): for filename in files: if filename.startswith(coverage_file_prefix): coverage_filenames.add(os.path.join(root, filename)) diff --git a/test/functional/wallet_address_types.py b/test/functional/wallet_address_types.py index 0f75045c9d..bafa556aad 100755 --- a/test/functional/wallet_address_types.py +++ b/test/functional/wallet_address_types.py @@ -99,6 +99,8 @@ class AddressTypeTest(BitcoinTestFramework): """Run sanity checks on an address.""" info = self.nodes[node].getaddressinfo(address) assert(self.nodes[node].validateaddress(address)['isvalid']) + assert_equal(info.get('solvable'), True) + if not multisig and typ == 'legacy': # P2PKH assert(not info['isscript']) @@ -146,6 +148,47 @@ class AddressTypeTest(BitcoinTestFramework): # Unknown type assert(False) + def test_desc(self, node, address, multisig, typ, utxo): + """Run sanity checks on a descriptor reported by getaddressinfo.""" + info = self.nodes[node].getaddressinfo(address) + assert('desc' in info) + assert_equal(info['desc'], utxo['desc']) + assert(self.nodes[node].validateaddress(address)['isvalid']) + + # Use a ridiculously roundabout way to find the key origin info through + # the PSBT logic. However, this does test consistency between the PSBT reported + # fingerprints/paths and the descriptor logic. + psbt = self.nodes[node].createpsbt([{'txid':utxo['txid'], 'vout':utxo['vout']}],[{address:0.00010000}]) + psbt = self.nodes[node].walletprocesspsbt(psbt, False, "ALL", True) + decode = self.nodes[node].decodepsbt(psbt['psbt']) + key_descs = {} + for deriv in decode['inputs'][0]['bip32_derivs']: + assert_equal(len(deriv['master_fingerprint']), 8) + assert_equal(deriv['path'][0], 'm') + key_descs[deriv['pubkey']] = '[' + deriv['master_fingerprint'] + deriv['path'][1:] + ']' + deriv['pubkey'] + + if not multisig and typ == 'legacy': + # P2PKH + assert_equal(info['desc'], "pkh(%s)" % key_descs[info['pubkey']]) + elif not multisig and typ == 'p2sh-segwit': + # P2SH-P2WPKH + assert_equal(info['desc'], "sh(wpkh(%s))" % key_descs[info['pubkey']]) + elif not multisig and typ == 'bech32': + # P2WPKH + assert_equal(info['desc'], "wpkh(%s)" % key_descs[info['pubkey']]) + elif typ == 'legacy': + # P2SH-multisig + assert_equal(info['desc'], "sh(multi(2,%s,%s))" % (key_descs[info['pubkeys'][0]], key_descs[info['pubkeys'][1]])) + elif typ == 'p2sh-segwit': + # P2SH-P2WSH-multisig + assert_equal(info['desc'], "sh(wsh(multi(2,%s,%s)))" % (key_descs[info['embedded']['pubkeys'][0]], key_descs[info['embedded']['pubkeys'][1]])) + elif typ == 'bech32': + # P2WSH-multisig + assert_equal(info['desc'], "wsh(multi(2,%s,%s))" % (key_descs[info['pubkeys'][0]], key_descs[info['pubkeys'][1]])) + else: + # Unknown type + assert(False) + def test_change_output_type(self, node_sender, destinations, expected_type): txid = self.nodes[node_sender].sendmany(dummy="", amounts=dict.fromkeys(destinations, 0.001)) raw_tx = self.nodes[node_sender].getrawtransaction(txid) @@ -198,6 +241,7 @@ class AddressTypeTest(BitcoinTestFramework): self.log.debug("Old balances are {}".format(old_balances)) to_send = (old_balances[from_node] / 101).quantize(Decimal("0.00000001")) sends = {} + addresses = {} self.log.debug("Prepare sends") for n, to_node in enumerate(range(from_node, from_node + 4)): @@ -228,6 +272,7 @@ class AddressTypeTest(BitcoinTestFramework): # Output entry sends[address] = to_send * 10 * (1 + n) + addresses[to_node] = (address, typ) self.log.debug("Sending: {}".format(sends)) self.nodes[from_node].sendmany("", sends) @@ -244,6 +289,17 @@ class AddressTypeTest(BitcoinTestFramework): self.nodes[5].generate(1) sync_blocks(self.nodes) + # Verify that the receiving wallet contains a UTXO with the expected address, and expected descriptor + for n, to_node in enumerate(range(from_node, from_node + 4)): + to_node %= 4 + found = False + for utxo in self.nodes[to_node].listunspent(): + if utxo['address'] == addresses[to_node][0]: + found = True + self.test_desc(to_node, addresses[to_node][0], multisig, addresses[to_node][1], utxo) + break + assert found + new_balances = self.get_balances() self.log.debug("Check new balances: {}".format(new_balances)) # We don't know what fee was set, so we can only check bounds on the balance of the sending node diff --git a/test/functional/wallet_backup.py b/test/functional/wallet_backup.py index 32ec385fa1..dd3750203a 100755 --- a/test/functional/wallet_backup.py +++ b/test/functional/wallet_backup.py @@ -48,7 +48,7 @@ class WalletBackupTest(BitcoinTestFramework): def skip_test_if_missing_module(self): self.skip_if_no_wallet() - def setup_network(self, split=False): + def setup_network(self): self.setup_nodes() connect_nodes(self.nodes[0], 3) connect_nodes(self.nodes[1], 3) diff --git a/test/functional/wallet_balance.py b/test/functional/wallet_balance.py new file mode 100755 index 0000000000..05c97e0340 --- /dev/null +++ b/test/functional/wallet_balance.py @@ -0,0 +1,133 @@ +#!/usr/bin/env python3 +# Copyright (c) 2018 The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. +"""Test the wallet balance RPC methods.""" +from decimal import Decimal + +from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import ( + assert_equal, + assert_raises_rpc_error, +) + +RANDOM_COINBASE_ADDRESS = 'mneYUmWYsuk7kySiURxCi3AGxrAqZxLgPZ' + +def create_transactions(node, address, amt, fees): + # Create and sign raw transactions from node to address for amt. + # Creates a transaction for each fee and returns an array + # of the raw transactions. + utxos = node.listunspent(0) + + # Create transactions + inputs = [] + ins_total = 0 + for utxo in utxos: + inputs.append({"txid": utxo["txid"], "vout": utxo["vout"]}) + ins_total += utxo['amount'] + if ins_total > amt: + break + + txs = [] + for fee in fees: + outputs = {address: amt, node.getrawchangeaddress(): ins_total - amt - fee} + raw_tx = node.createrawtransaction(inputs, outputs, 0, True) + raw_tx = node.signrawtransactionwithwallet(raw_tx) + txs.append(raw_tx) + + return txs + +class WalletTest(BitcoinTestFramework): + def set_test_params(self): + self.num_nodes = 2 + self.setup_clean_chain = True + + def skip_test_if_missing_module(self): + self.skip_if_no_wallet() + + def run_test(self): + # Check that nodes don't own any UTXOs + assert_equal(len(self.nodes[0].listunspent()), 0) + assert_equal(len(self.nodes[1].listunspent()), 0) + + self.log.info("Mining one block for each node") + + self.nodes[0].generate(1) + self.sync_all() + self.nodes[1].generate(1) + self.nodes[1].generatetoaddress(100, RANDOM_COINBASE_ADDRESS) + self.sync_all() + + assert_equal(self.nodes[0].getbalance(), 50) + assert_equal(self.nodes[1].getbalance(), 50) + + self.log.info("Test getbalance with different arguments") + assert_equal(self.nodes[0].getbalance("*"), 50) + assert_equal(self.nodes[0].getbalance("*", 1), 50) + assert_equal(self.nodes[0].getbalance("*", 1, True), 50) + assert_equal(self.nodes[0].getbalance(minconf=1), 50) + + # Send 40 BTC from 0 to 1 and 60 BTC from 1 to 0. + txs = create_transactions(self.nodes[0], self.nodes[1].getnewaddress(), 40, [Decimal('0.01')]) + self.nodes[0].sendrawtransaction(txs[0]['hex']) + self.nodes[1].sendrawtransaction(txs[0]['hex']) # sending on both nodes is faster than waiting for propagation + + self.sync_all() + txs = create_transactions(self.nodes[1], self.nodes[0].getnewaddress(), 60, [Decimal('0.01'), Decimal('0.02')]) + self.nodes[1].sendrawtransaction(txs[0]['hex']) + self.nodes[0].sendrawtransaction(txs[0]['hex']) # sending on both nodes is faster than waiting for propagation + self.sync_all() + + # First argument of getbalance must be set to "*" + assert_raises_rpc_error(-32, "dummy first argument must be excluded or set to \"*\"", self.nodes[1].getbalance, "") + + self.log.info("Test getbalance and getunconfirmedbalance with unconfirmed inputs") + + # getbalance without any arguments includes unconfirmed transactions, but not untrusted transactions + assert_equal(self.nodes[0].getbalance(), Decimal('9.99')) # change from node 0's send + assert_equal(self.nodes[1].getbalance(), Decimal('29.99')) # change from node 1's send + # Same with minconf=0 + assert_equal(self.nodes[0].getbalance(minconf=0), Decimal('9.99')) + assert_equal(self.nodes[1].getbalance(minconf=0), Decimal('29.99')) + # getbalance with a minconf incorrectly excludes coins that have been spent more recently than the minconf blocks ago + # TODO: fix getbalance tracking of coin spentness depth + assert_equal(self.nodes[0].getbalance(minconf=1), Decimal('0')) + assert_equal(self.nodes[1].getbalance(minconf=1), Decimal('0')) + # getunconfirmedbalance + assert_equal(self.nodes[0].getunconfirmedbalance(), Decimal('60')) # output of node 1's spend + assert_equal(self.nodes[1].getunconfirmedbalance(), Decimal('0')) # Doesn't include output of node 0's send since it was spent + + # Node 1 bumps the transaction fee and resends + self.nodes[1].sendrawtransaction(txs[1]['hex']) + self.sync_all() + + self.log.info("Test getbalance and getunconfirmedbalance with conflicted unconfirmed inputs") + + assert_equal(self.nodes[0].getwalletinfo()["unconfirmed_balance"], Decimal('60')) # output of node 1's send + assert_equal(self.nodes[0].getunconfirmedbalance(), Decimal('60')) + assert_equal(self.nodes[1].getwalletinfo()["unconfirmed_balance"], Decimal('0')) # Doesn't include output of node 0's send since it was spent + assert_equal(self.nodes[1].getunconfirmedbalance(), Decimal('0')) + + self.nodes[1].generatetoaddress(1, RANDOM_COINBASE_ADDRESS) + self.sync_all() + + # balances are correct after the transactions are confirmed + assert_equal(self.nodes[0].getbalance(), Decimal('69.99')) # node 1's send plus change from node 0's send + assert_equal(self.nodes[1].getbalance(), Decimal('29.98')) # change from node 0's send + + # Send total balance away from node 1 + txs = create_transactions(self.nodes[1], self.nodes[0].getnewaddress(), Decimal('29.97'), [Decimal('0.01')]) + self.nodes[1].sendrawtransaction(txs[0]['hex']) + self.nodes[1].generatetoaddress(2, RANDOM_COINBASE_ADDRESS) + self.sync_all() + + # getbalance with a minconf incorrectly excludes coins that have been spent more recently than the minconf blocks ago + # TODO: fix getbalance tracking of coin spentness depth + # getbalance with minconf=3 should still show the old balance + assert_equal(self.nodes[1].getbalance(minconf=3), Decimal('0')) + + # getbalance with minconf=2 will show the new balance. + assert_equal(self.nodes[1].getbalance(minconf=2), Decimal('0')) + +if __name__ == '__main__': + WalletTest().main() diff --git a/test/functional/wallet_basic.py b/test/functional/wallet_basic.py index 8bbff7f7ef..7184bb8cb6 100755 --- a/test/functional/wallet_basic.py +++ b/test/functional/wallet_basic.py @@ -11,6 +11,7 @@ from test_framework.util import ( assert_array_result, assert_equal, assert_fee_amount, + assert_greater_than, assert_raises_rpc_error, connect_nodes_bi, sync_blocks, @@ -27,10 +28,9 @@ class WalletTest(BitcoinTestFramework): self.skip_if_no_wallet() def setup_network(self): - self.add_nodes(4) - self.start_node(0) - self.start_node(1) - self.start_node(2) + self.setup_nodes() + # Only need nodes 0-2 running at start of test + self.stop_node(3) connect_nodes_bi(self.nodes, 0, 1) connect_nodes_bi(self.nodes, 1, 2) connect_nodes_bi(self.nodes, 0, 2) @@ -67,15 +67,6 @@ class WalletTest(BitcoinTestFramework): assert_equal(self.nodes[1].getbalance(), 50) assert_equal(self.nodes[2].getbalance(), 0) - # Check getbalance with different arguments - assert_equal(self.nodes[0].getbalance("*"), 50) - assert_equal(self.nodes[0].getbalance("*", 1), 50) - assert_equal(self.nodes[0].getbalance("*", 1, True), 50) - assert_equal(self.nodes[0].getbalance(minconf=1), 50) - - # first argument of getbalance must be excluded or set to "*" - assert_raises_rpc_error(-32, "dummy first argument must be excluded or set to \"*\"", self.nodes[0].getbalance, "") - # Check that only first and second nodes have UTXOs utxos = self.nodes[0].listunspent() assert_equal(len(utxos), 1) @@ -92,13 +83,13 @@ class WalletTest(BitcoinTestFramework): assert_equal(txout['value'], 50) # Send 21 BTC from 0 to 2 using sendtoaddress call. - # Locked memory should use at least 32 bytes to sign each transaction + # Locked memory should increase to sign transactions self.log.info("test getmemoryinfo") memory_before = self.nodes[0].getmemoryinfo() self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), 11) mempool_txid = self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), 10) memory_after = self.nodes[0].getmemoryinfo() - assert(memory_before['locked']['used'] + 64 <= memory_after['locked']['used']) + assert_greater_than(memory_after['locked']['used'], memory_before['locked']['used']) self.log.info("test gettxout (second part)") # utxo spent in mempool should be visible if you exclude mempool @@ -248,10 +239,6 @@ class WalletTest(BitcoinTestFramework): assert(txid1 in self.nodes[3].getrawmempool()) - # Exercise balance rpcs - assert_equal(self.nodes[0].getwalletinfo()["unconfirmed_balance"], 1) - assert_equal(self.nodes[0].getunconfirmedbalance(), 1) - # check if we can list zero value tx as available coins # 1. create raw_tx # 2. hex-changed one output to 0.0 @@ -479,7 +466,7 @@ class WalletTest(BitcoinTestFramework): # Verify nothing new in wallet assert_equal(total_txs, len(self.nodes[0].listtransactions("*", 99999))) - # Test getaddressinfo. Note that these addresses are taken from disablewallet.py + # Test getaddressinfo on external address. Note that these addresses are taken from disablewallet.py assert_raises_rpc_error(-5, "Invalid address", self.nodes[0].getaddressinfo, "3J98t1WpEZ73CNmQviecrnyiWrnqRhWNLy") address_info = self.nodes[0].getaddressinfo("mneYUmWYsuk7kySiURxCi3AGxrAqZxLgPZ") assert_equal(address_info['address'], "mneYUmWYsuk7kySiURxCi3AGxrAqZxLgPZ") @@ -487,6 +474,22 @@ class WalletTest(BitcoinTestFramework): assert not address_info["ismine"] assert not address_info["iswatchonly"] assert not address_info["isscript"] + assert not address_info["ischange"] + + # Test getaddressinfo 'ischange' field on change address. + self.nodes[0].generate(1) + destination = self.nodes[1].getnewaddress() + txid = self.nodes[0].sendtoaddress(destination, 0.123) + tx = self.nodes[0].decoderawtransaction(self.nodes[0].getrawtransaction(txid)) + output_addresses = [vout['scriptPubKey']['addresses'][0] for vout in tx["vout"]] + assert len(output_addresses) > 1 + for address in output_addresses: + ischange = self.nodes[0].getaddressinfo(address)['ischange'] + assert_equal(ischange, address != destination) + if ischange: + change = address + self.nodes[0].setlabel(change, 'foobar') + assert_equal(self.nodes[0].getaddressinfo(change)['ischange'], False) if __name__ == '__main__': WalletTest().main() diff --git a/test/functional/wallet_bumpfee.py b/test/functional/wallet_bumpfee.py index 67ee00871d..7d3d9b61e2 100755 --- a/test/functional/wallet_bumpfee.py +++ b/test/functional/wallet_bumpfee.py @@ -13,26 +13,22 @@ can be disabled or reordered if needed for debugging. If new test cases are added in the future, they should try to follow the same convention and not make assumptions about execution order. """ - from decimal import Decimal +import io from test_framework.blocktools import add_witness_commitment, create_block, create_coinbase, send_to_witness from test_framework.messages import BIP125_SEQUENCE_NUMBER, CTransaction from test_framework.test_framework import BitcoinTestFramework from test_framework.util import assert_equal, assert_greater_than, assert_raises_rpc_error, bytes_to_hex_str, connect_nodes_bi, hex_str_to_bytes, sync_mempools -import io - WALLET_PASSPHRASE = "test" WALLET_PASSPHRASE_TIMEOUT = 3600 - class BumpFeeTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 2 self.setup_clean_chain = True self.extra_args = [[ - "-deprecatedrpc=addwitnessaddress", "-walletrbf={}".format(i), "-mintxfee=0.00002", ] for i in range(self.num_nodes)] @@ -107,8 +103,7 @@ def test_segwit_bumpfee_succeeds(rbf_node, dest_address): # which spends it, and make sure bumpfee can be called on it. segwit_in = next(u for u in rbf_node.listunspent() if u["amount"] == Decimal("0.001")) - segwit_out = rbf_node.getaddressinfo(rbf_node.getnewaddress()) - rbf_node.addwitnessaddress(segwit_out["address"]) + segwit_out = rbf_node.getaddressinfo(rbf_node.getnewaddress(address_type='p2sh-segwit')) segwitid = send_to_witness( use_p2wsh=False, node=rbf_node, @@ -157,7 +152,7 @@ def test_notmine_bumpfee_fails(rbf_node, peer_node, dest_address): signedtx = peer_node.signrawtransactionwithwallet(signedtx["hex"]) rbfid = rbf_node.sendrawtransaction(signedtx["hex"]) assert_raises_rpc_error(-4, "Transaction contains inputs that don't belong to this wallet", - rbf_node.bumpfee, rbfid) + rbf_node.bumpfee, rbfid) def test_bumpfee_with_descendant_fails(rbf_node, rbf_node_address, dest_address): @@ -187,11 +182,11 @@ def test_dust_to_fee(rbf_node, dest_address): # (32-byte p2sh-pwpkh output size + 148 p2pkh spend estimate) * 10k(discard_rate) / 1000 = 1800 # P2SH outputs are slightly "over-discarding" due to the IsDust calculation assuming it will # be spent as a P2PKH. - bumped_tx = rbf_node.bumpfee(rbfid, {"totalFee": 50000-1800}) + bumped_tx = rbf_node.bumpfee(rbfid, {"totalFee": 50000 - 1800}) full_bumped_tx = rbf_node.getrawtransaction(bumped_tx["txid"], 1) assert_equal(bumped_tx["fee"], Decimal("0.00050000")) assert_equal(len(fulltx["vout"]), 2) - assert_equal(len(full_bumped_tx["vout"]), 1) #change output is eliminated + assert_equal(len(full_bumped_tx["vout"]), 1) # change output is eliminated def test_settxfee(rbf_node, dest_address): @@ -222,7 +217,7 @@ def test_rebumping_not_replaceable(rbf_node, dest_address): rbfid = spend_one_input(rbf_node, dest_address) bumped = rbf_node.bumpfee(rbfid, {"totalFee": 10000, "replaceable": False}) assert_raises_rpc_error(-4, "Transaction is not BIP 125 replaceable", rbf_node.bumpfee, bumped["txid"], - {"totalFee": 20000}) + {"totalFee": 20000}) def test_unconfirmed_not_spendable(rbf_node, rbf_node_address): @@ -276,7 +271,7 @@ def test_locked_wallet_fails(rbf_node, dest_address): rbfid = spend_one_input(rbf_node, dest_address) rbf_node.walletlock() assert_raises_rpc_error(-13, "Please enter the wallet passphrase with walletpassphrase first.", - rbf_node.bumpfee, rbfid) + rbf_node.bumpfee, rbfid) def spend_one_input(node, dest_address): diff --git a/test/functional/wallet_coinbase_category.py b/test/functional/wallet_coinbase_category.py new file mode 100755 index 0000000000..7aa8b44ebd --- /dev/null +++ b/test/functional/wallet_coinbase_category.py @@ -0,0 +1,59 @@ +#!/usr/bin/env python3 +# Copyright (c) 2014-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. +"""Test coinbase transactions return the correct categories. + +Tests listtransactions, listsinceblock, and gettransaction. +""" + +from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import ( + assert_array_result +) + +class CoinbaseCategoryTest(BitcoinTestFramework): + def set_test_params(self): + self.num_nodes = 1 + + def skip_test_if_missing_module(self): + self.skip_if_no_wallet() + + def assert_category(self, category, address, txid, skip): + assert_array_result(self.nodes[0].listtransactions(skip=skip), + {"address": address}, + {"category": category}) + assert_array_result(self.nodes[0].listsinceblock()["transactions"], + {"address": address}, + {"category": category}) + assert_array_result(self.nodes[0].gettransaction(txid)["details"], + {"address": address}, + {"category": category}) + + def run_test(self): + # Generate one block to an address + address = self.nodes[0].getnewaddress() + self.nodes[0].generatetoaddress(1, address) + hash = self.nodes[0].getbestblockhash() + txid = self.nodes[0].getblock(hash)["tx"][0] + + # Coinbase transaction is immature after 1 confirmation + self.assert_category("immature", address, txid, 0) + + # Mine another 99 blocks on top + self.nodes[0].generate(99) + # Coinbase transaction is still immature after 100 confirmations + self.assert_category("immature", address, txid, 99) + + # Mine one more block + self.nodes[0].generate(1) + # Coinbase transaction is now matured, so category is "generate" + self.assert_category("generate", address, txid, 100) + + # Orphan block that paid to address + self.nodes[0].invalidateblock(hash) + # Coinbase transaction is now orphaned + self.assert_category("orphan", address, txid, 100) + +if __name__ == '__main__': + CoinbaseCategoryTest().main() diff --git a/test/functional/wallet_create_tx.py b/test/functional/wallet_create_tx.py new file mode 100755 index 0000000000..27dc0fb279 --- /dev/null +++ b/test/functional/wallet_create_tx.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python3 +# Copyright (c) 2018 The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. + +from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import ( + assert_equal, +) + + +class CreateTxWalletTest(BitcoinTestFramework): + def set_test_params(self): + self.setup_clean_chain = False + self.num_nodes = 1 + + def skip_test_if_missing_module(self): + self.skip_if_no_wallet() + + def run_test(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) + tx = self.nodes[0].decoderawtransaction(self.nodes[0].gettransaction(txid)['hex']) + assert_equal(tx['locktime'], 0) + + self.log.info('Check that anti-fee-sniping is enabled when we mine a recent block') + self.nodes[0].generate(1) + txid = self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), 1) + tx = self.nodes[0].decoderawtransaction(self.nodes[0].gettransaction(txid)['hex']) + assert 0 < tx['locktime'] <= 201 + + +if __name__ == '__main__': + CreateTxWalletTest().main() diff --git a/test/functional/wallet_dump.py b/test/functional/wallet_dump.py index b1db1e4ab9..3f39654bb8 100755 --- a/test/functional/wallet_dump.py +++ b/test/functional/wallet_dump.py @@ -3,7 +3,6 @@ # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test the dumpwallet RPC.""" - import os from test_framework.test_framework import BitcoinTestFramework @@ -19,11 +18,12 @@ def read_dump(file_name, addrs, script_addrs, hd_master_addr_old): Also check that the old hd_master is inactive """ with open(file_name, encoding='utf8') as inputfile: - found_addr = 0 + found_legacy_addr = 0 + found_p2sh_segwit_addr = 0 + found_bech32_addr = 0 found_script_addr = 0 found_addr_chg = 0 found_addr_rsv = 0 - witness_addr_ret = None hd_master_addr_ret = None for line in inputfile: # only read non comment lines @@ -60,14 +60,14 @@ def read_dump(file_name, addrs, script_addrs, hd_master_addr_old): # count key types for addrObj in addrs: if addrObj['address'] == addr.split(",")[0] and addrObj['hdkeypath'] == keypath and keytype == "label=": - # a labeled entry in the wallet should contain both a native address - # and the p2sh-p2wpkh address that was added at wallet setup - if len(addr.split(",")) == 2: - addr_list = addr.split(",") - # the entry should be of the first key in the wallet - assert_equal(addrs[0]['address'], addr_list[0]) - witness_addr_ret = addr_list[1] - found_addr += 1 + if addr.startswith('m') or addr.startswith('n'): + # P2PKH address + found_legacy_addr += 1 + elif addr.startswith('2'): + # P2SH-segwit address + found_p2sh_segwit_addr += 1 + elif addr.startswith('bcrt1'): + found_bech32_addr += 1 break elif keytype == "change=1": found_addr_chg += 1 @@ -82,19 +82,19 @@ def read_dump(file_name, addrs, script_addrs, hd_master_addr_old): found_script_addr += 1 break - return found_addr, found_script_addr, found_addr_chg, found_addr_rsv, hd_master_addr_ret, witness_addr_ret + return found_legacy_addr, found_p2sh_segwit_addr, found_bech32_addr, found_script_addr, found_addr_chg, found_addr_rsv, hd_master_addr_ret class WalletDumpTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 1 - self.extra_args = [["-keypool=90", "-addresstype=legacy", "-deprecatedrpc=addwitnessaddress"]] + self.extra_args = [["-keypool=90", "-addresstype=legacy"]] self.rpc_timeout = 120 def skip_test_if_missing_module(self): self.skip_if_no_wallet() - def setup_network(self, split=False): + def setup_network(self): self.add_nodes(self.num_nodes, extra_args=self.extra_args) self.start_nodes() @@ -102,49 +102,54 @@ class WalletDumpTest(BitcoinTestFramework): wallet_unenc_dump = os.path.join(self.nodes[0].datadir, "wallet.unencrypted.dump") wallet_enc_dump = os.path.join(self.nodes[0].datadir, "wallet.encrypted.dump") - # generate 20 addresses to compare against the dump - # but since we add a p2sh-p2wpkh address for the first pubkey in the - # wallet, we will expect 21 addresses in the dump - test_addr_count = 20 + # generate 30 addresses to compare against the dump + # - 10 legacy P2PKH + # - 10 P2SH-segwit + # - 10 bech32 + test_addr_count = 10 addrs = [] - for i in range(0,test_addr_count): - addr = self.nodes[0].getnewaddress() - vaddr= self.nodes[0].getaddressinfo(addr) #required to get hd keypath - addrs.append(vaddr) - # Should be a no-op: - self.nodes[0].keypoolrefill() + for address_type in ['legacy', 'p2sh-segwit', 'bech32']: + for i in range(0, test_addr_count): + addr = self.nodes[0].getnewaddress(address_type=address_type) + vaddr = self.nodes[0].getaddressinfo(addr) # required to get hd keypath + addrs.append(vaddr) - # Test scripts dump by adding a P2SH witness and a 1-of-1 multisig address - witness_addr = self.nodes[0].addwitnessaddress(addrs[0]["address"], True) + # Test scripts dump by adding a 1-of-1 multisig address multisig_addr = self.nodes[0].addmultisigaddress(1, [addrs[1]["address"]])["address"] - script_addrs = [witness_addr, multisig_addr] + + # Refill the keypool. getnewaddress() refills the keypool *before* taking a key from + # the keypool, so the final call to getnewaddress leaves the keypool with one key below + # its capacity + self.nodes[0].keypoolrefill() # dump unencrypted wallet result = self.nodes[0].dumpwallet(wallet_unenc_dump) assert_equal(result['filename'], wallet_unenc_dump) - found_addr, found_script_addr, found_addr_chg, found_addr_rsv, hd_master_addr_unenc, witness_addr_ret = \ - read_dump(wallet_unenc_dump, addrs, script_addrs, None) - assert_equal(found_addr, test_addr_count) # all keys must be in the dump - assert_equal(found_script_addr, 2) # all scripts must be in the dump + found_legacy_addr, found_p2sh_segwit_addr, found_bech32_addr, found_script_addr, found_addr_chg, found_addr_rsv, hd_master_addr_unenc = \ + read_dump(wallet_unenc_dump, addrs, [multisig_addr], None) + assert_equal(found_legacy_addr, test_addr_count) # all keys must be in the dump + assert_equal(found_p2sh_segwit_addr, test_addr_count) # all keys must be in the dump + assert_equal(found_bech32_addr, test_addr_count) # all keys must be in the dump + assert_equal(found_script_addr, 1) # all scripts must be in the dump assert_equal(found_addr_chg, 0) # 0 blocks where mined assert_equal(found_addr_rsv, 90 * 2) # 90 keys plus 100% internal keys - assert_equal(witness_addr_ret, witness_addr) # p2sh-p2wsh address added to the first key - #encrypt wallet, restart, unlock and dump + # encrypt wallet, restart, unlock and dump self.nodes[0].encryptwallet('test') self.nodes[0].walletpassphrase('test', 10) # Should be a no-op: self.nodes[0].keypoolrefill() self.nodes[0].dumpwallet(wallet_enc_dump) - found_addr, found_script_addr, found_addr_chg, found_addr_rsv, _, witness_addr_ret = \ - read_dump(wallet_enc_dump, addrs, script_addrs, hd_master_addr_unenc) - assert_equal(found_addr, test_addr_count) - assert_equal(found_script_addr, 2) + found_legacy_addr, found_p2sh_segwit_addr, found_bech32_addr, found_script_addr, found_addr_chg, found_addr_rsv, _ = \ + read_dump(wallet_enc_dump, addrs, [multisig_addr], hd_master_addr_unenc) + assert_equal(found_legacy_addr, test_addr_count) # all keys must be in the dump + assert_equal(found_p2sh_segwit_addr, test_addr_count) # all keys must be in the dump + assert_equal(found_bech32_addr, test_addr_count) # all keys must be in the dump + assert_equal(found_script_addr, 1) assert_equal(found_addr_chg, 90 * 2) # old reserve keys are marked as change now assert_equal(found_addr_rsv, 90 * 2) - assert_equal(witness_addr_ret, witness_addr) # Overwriting should fail assert_raises_rpc_error(-8, "already exists", lambda: self.nodes[0].dumpwallet(wallet_enc_dump)) @@ -155,13 +160,13 @@ class WalletDumpTest(BitcoinTestFramework): # Make sure the address is not IsMine before import result = self.nodes[0].getaddressinfo(multisig_addr) - assert(result['ismine'] == False) + assert not result['ismine'] self.nodes[0].importwallet(wallet_unenc_dump) # Now check IsMine is true result = self.nodes[0].getaddressinfo(multisig_addr) - assert(result['ismine'] == True) + assert result['ismine'] if __name__ == '__main__': WalletDumpTest().main() diff --git a/test/functional/wallet_encryption.py b/test/functional/wallet_encryption.py index ab9ebed8d4..c514b7e0b4 100755 --- a/test/functional/wallet_encryption.py +++ b/test/functional/wallet_encryption.py @@ -31,12 +31,18 @@ class WalletEncryptionTest(BitcoinTestFramework): privkey = self.nodes[0].dumpprivkey(address) assert_equal(privkey[:1], "c") assert_equal(len(privkey), 52) + assert_raises_rpc_error(-15, "Error: running with an unencrypted wallet, but walletpassphrase was called", self.nodes[0].walletpassphrase, 'ff', 1) + assert_raises_rpc_error(-15, "Error: running with an unencrypted wallet, but walletpassphrasechange was called.", self.nodes[0].walletpassphrasechange, 'ff', 'ff') # Encrypt the wallet + assert_raises_rpc_error(-8, "passphrase can not be empty", self.nodes[0].encryptwallet, '') self.nodes[0].encryptwallet(passphrase) # Test that the wallet is encrypted assert_raises_rpc_error(-13, "Please enter the wallet passphrase with walletpassphrase first", self.nodes[0].dumpprivkey, address) + assert_raises_rpc_error(-15, "Error: running with an encrypted wallet, but encryptwallet was called.", self.nodes[0].encryptwallet, 'ff') + assert_raises_rpc_error(-8, "passphrase can not be empty", self.nodes[0].walletpassphrase, '', 1) + assert_raises_rpc_error(-8, "passphrase can not be empty", self.nodes[0].walletpassphrasechange, '', 'ff') # Check that walletpassphrase works self.nodes[0].walletpassphrase(passphrase, 2) diff --git a/test/functional/wallet_groups.py b/test/functional/wallet_groups.py index 9d61483868..5452433acf 100755 --- a/test/functional/wallet_groups.py +++ b/test/functional/wallet_groups.py @@ -21,7 +21,7 @@ class WalletGroupTest(BitcoinTestFramework): self.setup_clean_chain = True self.num_nodes = 3 self.extra_args = [[], [], ['-avoidpartialspends']] - self.rpc_timewait = 120 + self.rpc_timeout = 120 def skip_test_if_missing_module(self): self.skip_if_no_wallet() diff --git a/test/functional/wallet_import_rescan.py b/test/functional/wallet_import_rescan.py index f043a544fc..46462a16f3 100755 --- a/test/functional/wallet_import_rescan.py +++ b/test/functional/wallet_import_rescan.py @@ -46,11 +46,11 @@ class Variant(collections.namedtuple("Variant", "call data rescan prune")): if self.call == Call.single: if self.data == Data.address: - response = self.try_rpc(self.node.importaddress, address=self.address["address"], rescan=rescan) + response = self.try_rpc(self.node.importaddress, address=self.address["address"], label=self.label, rescan=rescan) elif self.data == Data.pub: - response = self.try_rpc(self.node.importpubkey, pubkey=self.address["pubkey"], rescan=rescan) + response = self.try_rpc(self.node.importpubkey, pubkey=self.address["pubkey"], label=self.label, rescan=rescan) elif self.data == Data.priv: - response = self.try_rpc(self.node.importprivkey, privkey=self.key, rescan=rescan) + response = self.try_rpc(self.node.importprivkey, privkey=self.key, label=self.label, rescan=rescan) assert_equal(response, None) elif self.call in (Call.multiaddress, Call.multiscript): @@ -61,18 +61,32 @@ class Variant(collections.namedtuple("Variant", "call data rescan prune")): "timestamp": timestamp + TIMESTAMP_WINDOW + (1 if self.rescan == Rescan.late_timestamp else 0), "pubkeys": [self.address["pubkey"]] if self.data == Data.pub else [], "keys": [self.key] if self.data == Data.priv else [], + "label": self.label, "watchonly": self.data != Data.priv }], {"rescan": self.rescan in (Rescan.yes, Rescan.late_timestamp)}) assert_equal(response, [{"success": True}]) def check(self, txid=None, amount=None, confirmations=None): - """Verify that listreceivedbyaddress returns expected values.""" + """Verify that listtransactions/listreceivedbyaddress return expected values.""" + + txs = self.node.listtransactions(label=self.label, count=10000, include_watchonly=True) + assert_equal(len(txs), self.expected_txs) addresses = self.node.listreceivedbyaddress(minconf=0, include_watchonly=True, address_filter=self.address['address']) if self.expected_txs: assert_equal(len(addresses[0]["txids"]), self.expected_txs) if txid is not None: + tx, = [tx for tx in txs if tx["txid"] == txid] + assert_equal(tx["label"], self.label) + assert_equal(tx["address"], self.address["address"]) + assert_equal(tx["amount"], amount) + assert_equal(tx["category"], "receive") + assert_equal(tx["label"], self.label) + assert_equal(tx["txid"], txid) + assert_equal(tx["confirmations"], confirmations) + assert_equal("trusted" not in tx, True) + address, = [ad for ad in addresses if txid in ad["txids"]] assert_equal(address["address"], self.address["address"]) assert_equal(address["amount"], self.expected_balance) @@ -122,21 +136,20 @@ class ImportRescanTest(BitcoinTestFramework): # Import keys with pruning disabled self.start_nodes(extra_args=[[]] * self.num_nodes) - super().import_deterministic_coinbase_privkeys() + for n in self.nodes: + n.importprivkey(privkey=n.get_deterministic_priv_key().key, label='coinbase') self.stop_nodes() self.start_nodes() for i in range(1, self.num_nodes): connect_nodes(self.nodes[i], 0) - def import_deterministic_coinbase_privkeys(self): - pass - def run_test(self): # Create one transaction on node 0 with a unique amount for # each possible type of wallet import RPC. for i, variant in enumerate(IMPORT_VARIANTS): - variant.address = self.nodes[1].getaddressinfo(self.nodes[1].getnewaddress()) + variant.label = "label {} {}".format(i, variant) + variant.address = self.nodes[1].getaddressinfo(self.nodes[1].getnewaddress(variant.label)) variant.key = self.nodes[1].dumpprivkey(variant.address["address"]) variant.initial_amount = 1 - (i + 1) / 64 variant.initial_txid = self.nodes[0].sendtoaddress(variant.address["address"], variant.initial_amount) diff --git a/test/functional/wallet_import_with_label.py b/test/functional/wallet_import_with_label.py new file mode 100755 index 0000000000..a623b75606 --- /dev/null +++ b/test/functional/wallet_import_with_label.py @@ -0,0 +1,135 @@ +#!/usr/bin/env python3 +# Copyright (c) 2018 The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. +"""Test the behavior of RPC importprivkey on set and unset labels of +addresses. + +It tests different cases in which an address is imported with importaddress +with or without a label and then its private key is imported with importprivkey +with and without a label. +""" + +from test_framework.test_framework import BitcoinTestFramework +from test_framework.wallet_util import test_address + + +class ImportWithLabel(BitcoinTestFramework): + def set_test_params(self): + self.num_nodes = 2 + self.setup_clean_chain = True + + def skip_test_if_missing_module(self): + self.skip_if_no_wallet() + + def run_test(self): + """Main test logic""" + + self.log.info( + "Test importaddress with label and importprivkey without label." + ) + self.log.info("Import a watch-only address with a label.") + address = self.nodes[0].getnewaddress() + label = "Test Label" + self.nodes[1].importaddress(address, label) + test_address(self.nodes[1], + address, + iswatchonly=True, + ismine=False, + label=label) + + self.log.info( + "Import the watch-only address's private key without a " + "label and the address should keep its label." + ) + priv_key = self.nodes[0].dumpprivkey(address) + self.nodes[1].importprivkey(priv_key) + + test_address(self.nodes[1], + address, + label=label) + + self.log.info( + "Test importaddress without label and importprivkey with label." + ) + self.log.info("Import a watch-only address without a label.") + address2 = self.nodes[0].getnewaddress() + self.nodes[1].importaddress(address2) + test_address(self.nodes[1], + address2, + iswatchonly=True, + ismine=False, + label="") + + self.log.info( + "Import the watch-only address's private key with a " + "label and the address should have its label updated." + ) + priv_key2 = self.nodes[0].dumpprivkey(address2) + label2 = "Test Label 2" + self.nodes[1].importprivkey(priv_key2, label2) + + test_address(self.nodes[1], + address2, + label=label2) + + self.log.info("Test importaddress with label and importprivkey with label.") + self.log.info("Import a watch-only address with a label.") + address3 = self.nodes[0].getnewaddress() + label3_addr = "Test Label 3 for importaddress" + self.nodes[1].importaddress(address3, label3_addr) + test_address(self.nodes[1], + address3, + iswatchonly=True, + ismine=False, + label=label3_addr) + + self.log.info( + "Import the watch-only address's private key with a " + "label and the address should have its label updated." + ) + priv_key3 = self.nodes[0].dumpprivkey(address3) + label3_priv = "Test Label 3 for importprivkey" + self.nodes[1].importprivkey(priv_key3, label3_priv) + + test_address(self.nodes[1], + address3, + label=label3_priv) + + self.log.info( + "Test importprivkey won't label new dests with the same " + "label as others labeled dests for the same key." + ) + self.log.info("Import a watch-only legacy address with a label.") + address4 = self.nodes[0].getnewaddress() + label4_addr = "Test Label 4 for importaddress" + self.nodes[1].importaddress(address4, label4_addr) + test_address(self.nodes[1], + address4, + iswatchonly=True, + ismine=False, + label=label4_addr, + embedded=None) + + self.log.info( + "Import the watch-only address's private key without a " + "label and new destinations for the key should have an " + "empty label while the 'old' destination should keep " + "its label." + ) + priv_key4 = self.nodes[0].dumpprivkey(address4) + self.nodes[1].importprivkey(priv_key4) + embedded_addr = self.nodes[1].getaddressinfo(address4)['embedded']['address'] + + test_address(self.nodes[1], + embedded_addr, + label="") + test_address(self.nodes[1], + address4, + label=label4_addr) + + self.stop_nodes() + + +if __name__ == "__main__": + ImportWithLabel().main() diff --git a/test/functional/wallet_importmulti.py b/test/functional/wallet_importmulti.py index f78ff19791..f122f19e3a 100755 --- a/test/functional/wallet_importmulti.py +++ b/test/functional/wallet_importmulti.py @@ -2,9 +2,23 @@ # Copyright (c) 2014-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. -"""Test the importmulti RPC.""" - -from test_framework import script +"""Test the importmulti RPC. + +Test importmulti by generating keys on node0, importing the scriptPubKeys and +addresses on node1 and then testing the address info for the different address +variants. + +- `get_key()` and `get_multisig()` are called to generate keys on node0 and + return the privkeys, pubkeys and all variants of scriptPubKey and address. +- `test_importmulti()` is called to send an importmulti call to node1, test + success, and (if unsuccessful) test the error code and error message returned. +- `test_address()` is called to call getaddressinfo for an address on node1 + and test the values returned.""" + +from test_framework.script import ( + CScript, + OP_NOP, +) from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_equal, @@ -12,7 +26,11 @@ from test_framework.util import ( assert_raises_rpc_error, bytes_to_hex_str, ) - +from test_framework.wallet_util import ( + get_key, + get_multisig, + test_address, +) class ImportMultiTest(BitcoinTestFramework): def set_test_params(self): @@ -26,7 +44,19 @@ class ImportMultiTest(BitcoinTestFramework): def setup_network(self): self.setup_nodes() - def run_test (self): + def test_importmulti(self, req, success, error_code=None, error_message=None, warnings=[]): + """Run importmulti and assert success""" + result = self.nodes[1].importmulti([req]) + observed_warnings = [] + if 'warnings' in result[0]: + observed_warnings = result[0]['warnings'] + assert_equal("\n".join(sorted(warnings)), "\n".join(sorted(observed_warnings))) + assert_equal(result[0]['success'], success) + if error_code is not None: + assert_equal(result[0]['error']['code'], error_code) + assert_equal(result[0]['error']['message'], error_message) + + def run_test(self): self.log.info("Mining blocks...") self.nodes[0].generate(1) self.nodes[1].generate(1) @@ -34,430 +64,484 @@ class ImportMultiTest(BitcoinTestFramework): node0_address1 = self.nodes[0].getaddressinfo(self.nodes[0].getnewaddress()) - #Check only one address + # Check only one address assert_equal(node0_address1['ismine'], True) - #Node 1 sync test - assert_equal(self.nodes[1].getblockcount(),1) + # Node 1 sync test + assert_equal(self.nodes[1].getblockcount(), 1) - #Address Test - before import + # Address Test - before import address_info = self.nodes[1].getaddressinfo(node0_address1['address']) assert_equal(address_info['iswatchonly'], False) assert_equal(address_info['ismine'], False) - # RPC importmulti ----------------------------------------------- - # Bitcoin Address + # Bitcoin Address (implicit non-internal) self.log.info("Should import an address") - address = self.nodes[0].getaddressinfo(self.nodes[0].getnewaddress()) - result = self.nodes[1].importmulti([{ - "scriptPubKey": { - "address": address['address'] - }, - "timestamp": "now", - }]) - assert_equal(result[0]['success'], True) - address_assert = self.nodes[1].getaddressinfo(address['address']) - assert_equal(address_assert['iswatchonly'], True) - assert_equal(address_assert['ismine'], False) - assert_equal(address_assert['timestamp'], timestamp) - watchonly_address = address['address'] + key = get_key(self.nodes[0]) + self.test_importmulti({"scriptPubKey": {"address": key.p2pkh_addr}, + "timestamp": "now"}, + success=True) + test_address(self.nodes[1], + key.p2pkh_addr, + iswatchonly=True, + ismine=False, + timestamp=timestamp, + ischange=False) + watchonly_address = key.p2pkh_addr watchonly_timestamp = timestamp self.log.info("Should not import an invalid address") - result = self.nodes[1].importmulti([{ - "scriptPubKey": { - "address": "not valid address", - }, - "timestamp": "now", - }]) - assert_equal(result[0]['success'], False) - assert_equal(result[0]['error']['code'], -5) - assert_equal(result[0]['error']['message'], 'Invalid address') + self.test_importmulti({"scriptPubKey": {"address": "not valid address"}, + "timestamp": "now"}, + success=False, + error_code=-5, + error_message='Invalid address \"not valid address\"') # ScriptPubKey + internal self.log.info("Should import a scriptPubKey with internal flag") - address = self.nodes[0].getaddressinfo(self.nodes[0].getnewaddress()) - result = self.nodes[1].importmulti([{ - "scriptPubKey": address['scriptPubKey'], - "timestamp": "now", - "internal": True - }]) - assert_equal(result[0]['success'], True) - address_assert = self.nodes[1].getaddressinfo(address['address']) - assert_equal(address_assert['iswatchonly'], True) - assert_equal(address_assert['ismine'], False) - assert_equal(address_assert['timestamp'], timestamp) + key = get_key(self.nodes[0]) + self.test_importmulti({"scriptPubKey": key.p2pkh_script, + "timestamp": "now", + "internal": True}, + success=True) + test_address(self.nodes[1], + key.p2pkh_addr, + iswatchonly=True, + ismine=False, + timestamp=timestamp, + ischange=True) + + # ScriptPubKey + internal + label + self.log.info("Should not allow a label to be specified when internal is true") + key = get_key(self.nodes[0]) + self.test_importmulti({"scriptPubKey": key.p2pkh_script, + "timestamp": "now", + "internal": True, + "label": "Example label"}, + success=False, + error_code=-8, + error_message='Internal addresses should not have a label') # Nonstandard scriptPubKey + !internal self.log.info("Should not import a nonstandard scriptPubKey without internal flag") - nonstandardScriptPubKey = address['scriptPubKey'] + bytes_to_hex_str(script.CScript([script.OP_NOP])) - address = self.nodes[0].getaddressinfo(self.nodes[0].getnewaddress()) - result = self.nodes[1].importmulti([{ - "scriptPubKey": nonstandardScriptPubKey, - "timestamp": "now", - }]) - assert_equal(result[0]['success'], False) - assert_equal(result[0]['error']['code'], -8) - assert_equal(result[0]['error']['message'], 'Internal must be set to true for nonstandard scriptPubKey imports.') - address_assert = self.nodes[1].getaddressinfo(address['address']) - assert_equal(address_assert['iswatchonly'], False) - assert_equal(address_assert['ismine'], False) - assert_equal('timestamp' in address_assert, False) - - - # Address + Public key + !Internal + nonstandardScriptPubKey = key.p2pkh_script + bytes_to_hex_str(CScript([OP_NOP])) + key = get_key(self.nodes[0]) + self.test_importmulti({"scriptPubKey": nonstandardScriptPubKey, + "timestamp": "now"}, + success=False, + error_code=-8, + error_message='Internal must be set to true for nonstandard scriptPubKey imports.') + test_address(self.nodes[1], + key.p2pkh_addr, + iswatchonly=False, + ismine=False, + timestamp=None) + + # Address + Public key + !Internal(explicit) self.log.info("Should import an address with public key") - address = self.nodes[0].getaddressinfo(self.nodes[0].getnewaddress()) - result = self.nodes[1].importmulti([{ - "scriptPubKey": { - "address": address['address'] - }, - "timestamp": "now", - "pubkeys": [ address['pubkey'] ] - }]) - assert_equal(result[0]['success'], True) - address_assert = self.nodes[1].getaddressinfo(address['address']) - assert_equal(address_assert['iswatchonly'], True) - assert_equal(address_assert['ismine'], False) - assert_equal(address_assert['timestamp'], timestamp) - + key = get_key(self.nodes[0]) + self.test_importmulti({"scriptPubKey": {"address": key.p2pkh_addr}, + "timestamp": "now", + "pubkeys": [key.pubkey], + "internal": False}, + success=True, + warnings=["Some private keys are missing, outputs will be considered watchonly. If this is intentional, specify the watchonly flag."]) + test_address(self.nodes[1], + key.p2pkh_addr, + iswatchonly=True, + ismine=False, + timestamp=timestamp) # ScriptPubKey + Public key + internal self.log.info("Should import a scriptPubKey with internal and with public key") - address = self.nodes[0].getaddressinfo(self.nodes[0].getnewaddress()) - request = [{ - "scriptPubKey": address['scriptPubKey'], - "timestamp": "now", - "pubkeys": [ address['pubkey'] ], - "internal": True - }] - result = self.nodes[1].importmulti(request) - assert_equal(result[0]['success'], True) - address_assert = self.nodes[1].getaddressinfo(address['address']) - assert_equal(address_assert['iswatchonly'], True) - assert_equal(address_assert['ismine'], False) - assert_equal(address_assert['timestamp'], timestamp) + key = get_key(self.nodes[0]) + self.test_importmulti({"scriptPubKey": key.p2pkh_script, + "timestamp": "now", + "pubkeys": [key.pubkey], + "internal": True}, + success=True, + warnings=["Some private keys are missing, outputs will be considered watchonly. If this is intentional, specify the watchonly flag."]) + test_address(self.nodes[1], + key.p2pkh_addr, + iswatchonly=True, + ismine=False, + timestamp=timestamp) # Nonstandard scriptPubKey + Public key + !internal self.log.info("Should not import a nonstandard scriptPubKey without internal and with public key") - address = self.nodes[0].getaddressinfo(self.nodes[0].getnewaddress()) - request = [{ - "scriptPubKey": nonstandardScriptPubKey, - "timestamp": "now", - "pubkeys": [ address['pubkey'] ] - }] - result = self.nodes[1].importmulti(request) - assert_equal(result[0]['success'], False) - assert_equal(result[0]['error']['code'], -8) - assert_equal(result[0]['error']['message'], 'Internal must be set to true for nonstandard scriptPubKey imports.') - address_assert = self.nodes[1].getaddressinfo(address['address']) - assert_equal(address_assert['iswatchonly'], False) - assert_equal(address_assert['ismine'], False) - assert_equal('timestamp' in address_assert, False) + key = get_key(self.nodes[0]) + self.test_importmulti({"scriptPubKey": nonstandardScriptPubKey, + "timestamp": "now", + "pubkeys": [key.pubkey]}, + success=False, + error_code=-8, + error_message='Internal must be set to true for nonstandard scriptPubKey imports.') + test_address(self.nodes[1], + key.p2pkh_addr, + iswatchonly=False, + ismine=False, + timestamp=None) # Address + Private key + !watchonly self.log.info("Should import an address with private key") - address = self.nodes[0].getaddressinfo(self.nodes[0].getnewaddress()) - result = self.nodes[1].importmulti([{ - "scriptPubKey": { - "address": address['address'] - }, - "timestamp": "now", - "keys": [ self.nodes[0].dumpprivkey(address['address']) ] - }]) - assert_equal(result[0]['success'], True) - address_assert = self.nodes[1].getaddressinfo(address['address']) - assert_equal(address_assert['iswatchonly'], False) - assert_equal(address_assert['ismine'], True) - assert_equal(address_assert['timestamp'], timestamp) + key = get_key(self.nodes[0]) + self.test_importmulti({"scriptPubKey": {"address": key.p2pkh_addr}, + "timestamp": "now", + "keys": [key.privkey]}, + success=True) + test_address(self.nodes[1], + key.p2pkh_addr, + iswatchonly=False, + ismine=True, + timestamp=timestamp) self.log.info("Should not import an address with private key if is already imported") - result = self.nodes[1].importmulti([{ - "scriptPubKey": { - "address": address['address'] - }, - "timestamp": "now", - "keys": [ self.nodes[0].dumpprivkey(address['address']) ] - }]) - assert_equal(result[0]['success'], False) - assert_equal(result[0]['error']['code'], -4) - assert_equal(result[0]['error']['message'], 'The wallet already contains the private key for this address or script') + self.test_importmulti({"scriptPubKey": {"address": key.p2pkh_addr}, + "timestamp": "now", + "keys": [key.privkey]}, + success=False, + error_code=-4, + error_message='The wallet already contains the private key for this address or script') # Address + Private key + watchonly - self.log.info("Should not import an address with private key and with watchonly") - address = self.nodes[0].getaddressinfo(self.nodes[0].getnewaddress()) - result = self.nodes[1].importmulti([{ - "scriptPubKey": { - "address": address['address'] - }, - "timestamp": "now", - "keys": [ self.nodes[0].dumpprivkey(address['address']) ], - "watchonly": True - }]) - assert_equal(result[0]['success'], False) - assert_equal(result[0]['error']['code'], -8) - assert_equal(result[0]['error']['message'], 'Incompatibility found between watchonly and keys') - address_assert = self.nodes[1].getaddressinfo(address['address']) - assert_equal(address_assert['iswatchonly'], False) - assert_equal(address_assert['ismine'], False) - assert_equal('timestamp' in address_assert, False) + self.log.info("Should import an address with private key and with watchonly") + key = get_key(self.nodes[0]) + self.test_importmulti({"scriptPubKey": {"address": key.p2pkh_addr}, + "timestamp": "now", + "keys": [key.privkey], + "watchonly": True}, + success=True, + warnings=["All private keys are provided, outputs will be considered spendable. If this is intentional, do not specify the watchonly flag."]) + test_address(self.nodes[1], + key.p2pkh_addr, + iswatchonly=False, + ismine=True, + timestamp=timestamp) # ScriptPubKey + Private key + internal self.log.info("Should import a scriptPubKey with internal and with private key") - address = self.nodes[0].getaddressinfo(self.nodes[0].getnewaddress()) - result = self.nodes[1].importmulti([{ - "scriptPubKey": address['scriptPubKey'], - "timestamp": "now", - "keys": [ self.nodes[0].dumpprivkey(address['address']) ], - "internal": True - }]) - assert_equal(result[0]['success'], True) - address_assert = self.nodes[1].getaddressinfo(address['address']) - assert_equal(address_assert['iswatchonly'], False) - assert_equal(address_assert['ismine'], True) - assert_equal(address_assert['timestamp'], timestamp) + key = get_key(self.nodes[0]) + self.test_importmulti({"scriptPubKey": key.p2pkh_script, + "timestamp": "now", + "keys": [key.privkey], + "internal": True}, + success=True) + test_address(self.nodes[1], + key.p2pkh_addr, + iswatchonly=False, + ismine=True, + timestamp=timestamp) # Nonstandard scriptPubKey + Private key + !internal self.log.info("Should not import a nonstandard scriptPubKey without internal and with private key") - address = self.nodes[0].getaddressinfo(self.nodes[0].getnewaddress()) - result = self.nodes[1].importmulti([{ - "scriptPubKey": nonstandardScriptPubKey, - "timestamp": "now", - "keys": [ self.nodes[0].dumpprivkey(address['address']) ] - }]) - assert_equal(result[0]['success'], False) - assert_equal(result[0]['error']['code'], -8) - assert_equal(result[0]['error']['message'], 'Internal must be set to true for nonstandard scriptPubKey imports.') - address_assert = self.nodes[1].getaddressinfo(address['address']) - assert_equal(address_assert['iswatchonly'], False) - assert_equal(address_assert['ismine'], False) - assert_equal('timestamp' in address_assert, False) - + key = get_key(self.nodes[0]) + self.test_importmulti({"scriptPubKey": nonstandardScriptPubKey, + "timestamp": "now", + "keys": [key.privkey]}, + success=False, + error_code=-8, + error_message='Internal must be set to true for nonstandard scriptPubKey imports.') + test_address(self.nodes[1], + key.p2pkh_addr, + iswatchonly=False, + ismine=False, + timestamp=None) # P2SH address - sig_address_1 = self.nodes[0].getaddressinfo(self.nodes[0].getnewaddress()) - sig_address_2 = self.nodes[0].getaddressinfo(self.nodes[0].getnewaddress()) - sig_address_3 = self.nodes[0].getaddressinfo(self.nodes[0].getnewaddress()) - multi_sig_script = self.nodes[0].createmultisig(2, [sig_address_1['pubkey'], sig_address_2['pubkey'], sig_address_3['pubkey']]) + multisig = get_multisig(self.nodes[0]) self.nodes[1].generate(100) - self.nodes[1].sendtoaddress(multi_sig_script['address'], 10.00) + self.nodes[1].sendtoaddress(multisig.p2sh_addr, 10.00) self.nodes[1].generate(1) timestamp = self.nodes[1].getblock(self.nodes[1].getbestblockhash())['mediantime'] self.log.info("Should import a p2sh") - result = self.nodes[1].importmulti([{ - "scriptPubKey": { - "address": multi_sig_script['address'] - }, - "timestamp": "now", - }]) - assert_equal(result[0]['success'], True) - address_assert = self.nodes[1].getaddressinfo(multi_sig_script['address']) - assert_equal(address_assert['isscript'], True) - assert_equal(address_assert['iswatchonly'], True) - assert_equal(address_assert['timestamp'], timestamp) - p2shunspent = self.nodes[1].listunspent(0,999999, [multi_sig_script['address']])[0] + self.test_importmulti({"scriptPubKey": {"address": multisig.p2sh_addr}, + "timestamp": "now"}, + success=True) + test_address(self.nodes[1], + multisig.p2sh_addr, + isscript=True, + iswatchonly=True, + timestamp=timestamp) + p2shunspent = self.nodes[1].listunspent(0, 999999, [multisig.p2sh_addr])[0] assert_equal(p2shunspent['spendable'], False) assert_equal(p2shunspent['solvable'], False) - # P2SH + Redeem script - sig_address_1 = self.nodes[0].getaddressinfo(self.nodes[0].getnewaddress()) - sig_address_2 = self.nodes[0].getaddressinfo(self.nodes[0].getnewaddress()) - sig_address_3 = self.nodes[0].getaddressinfo(self.nodes[0].getnewaddress()) - multi_sig_script = self.nodes[0].createmultisig(2, [sig_address_1['pubkey'], sig_address_2['pubkey'], sig_address_3['pubkey']]) + multisig = get_multisig(self.nodes[0]) self.nodes[1].generate(100) - self.nodes[1].sendtoaddress(multi_sig_script['address'], 10.00) + self.nodes[1].sendtoaddress(multisig.p2sh_addr, 10.00) self.nodes[1].generate(1) timestamp = self.nodes[1].getblock(self.nodes[1].getbestblockhash())['mediantime'] self.log.info("Should import a p2sh with respective redeem script") - result = self.nodes[1].importmulti([{ - "scriptPubKey": { - "address": multi_sig_script['address'] - }, - "timestamp": "now", - "redeemscript": multi_sig_script['redeemScript'] - }]) - assert_equal(result[0]['success'], True) - address_assert = self.nodes[1].getaddressinfo(multi_sig_script['address']) - assert_equal(address_assert['timestamp'], timestamp) - - p2shunspent = self.nodes[1].listunspent(0,999999, [multi_sig_script['address']])[0] + self.test_importmulti({"scriptPubKey": {"address": multisig.p2sh_addr}, + "timestamp": "now", + "redeemscript": multisig.redeem_script}, + success=True, + warnings=["Some private keys are missing, outputs will be considered watchonly. If this is intentional, specify the watchonly flag."]) + test_address(self.nodes[1], + multisig.p2sh_addr, timestamp=timestamp, iswatchonly=True, ismine=False, solvable=True) + + p2shunspent = self.nodes[1].listunspent(0, 999999, [multisig.p2sh_addr])[0] assert_equal(p2shunspent['spendable'], False) assert_equal(p2shunspent['solvable'], True) - # P2SH + Redeem script + Private Keys + !Watchonly - sig_address_1 = self.nodes[0].getaddressinfo(self.nodes[0].getnewaddress()) - sig_address_2 = self.nodes[0].getaddressinfo(self.nodes[0].getnewaddress()) - sig_address_3 = self.nodes[0].getaddressinfo(self.nodes[0].getnewaddress()) - multi_sig_script = self.nodes[0].createmultisig(2, [sig_address_1['pubkey'], sig_address_2['pubkey'], sig_address_3['pubkey']]) + multisig = get_multisig(self.nodes[0]) self.nodes[1].generate(100) - self.nodes[1].sendtoaddress(multi_sig_script['address'], 10.00) + self.nodes[1].sendtoaddress(multisig.p2sh_addr, 10.00) self.nodes[1].generate(1) timestamp = self.nodes[1].getblock(self.nodes[1].getbestblockhash())['mediantime'] self.log.info("Should import a p2sh with respective redeem script and private keys") - result = self.nodes[1].importmulti([{ - "scriptPubKey": { - "address": multi_sig_script['address'] - }, - "timestamp": "now", - "redeemscript": multi_sig_script['redeemScript'], - "keys": [ self.nodes[0].dumpprivkey(sig_address_1['address']), self.nodes[0].dumpprivkey(sig_address_2['address'])] - }]) - assert_equal(result[0]['success'], True) - address_assert = self.nodes[1].getaddressinfo(multi_sig_script['address']) - assert_equal(address_assert['timestamp'], timestamp) - - p2shunspent = self.nodes[1].listunspent(0,999999, [multi_sig_script['address']])[0] + self.test_importmulti({"scriptPubKey": {"address": multisig.p2sh_addr}, + "timestamp": "now", + "redeemscript": multisig.redeem_script, + "keys": multisig.privkeys[0:2]}, + success=True, + warnings=["Some private keys are missing, outputs will be considered watchonly. If this is intentional, specify the watchonly flag."]) + test_address(self.nodes[1], + multisig.p2sh_addr, + timestamp=timestamp, + ismine=False, + iswatchonly=True, + solvable=True) + + p2shunspent = self.nodes[1].listunspent(0, 999999, [multisig.p2sh_addr])[0] assert_equal(p2shunspent['spendable'], False) assert_equal(p2shunspent['solvable'], True) # P2SH + Redeem script + Private Keys + Watchonly - sig_address_1 = self.nodes[0].getaddressinfo(self.nodes[0].getnewaddress()) - sig_address_2 = self.nodes[0].getaddressinfo(self.nodes[0].getnewaddress()) - sig_address_3 = self.nodes[0].getaddressinfo(self.nodes[0].getnewaddress()) - multi_sig_script = self.nodes[0].createmultisig(2, [sig_address_1['pubkey'], sig_address_2['pubkey'], sig_address_3['pubkey']]) + multisig = get_multisig(self.nodes[0]) self.nodes[1].generate(100) - self.nodes[1].sendtoaddress(multi_sig_script['address'], 10.00) + self.nodes[1].sendtoaddress(multisig.p2sh_addr, 10.00) self.nodes[1].generate(1) timestamp = self.nodes[1].getblock(self.nodes[1].getbestblockhash())['mediantime'] self.log.info("Should import a p2sh with respective redeem script and private keys") - result = self.nodes[1].importmulti([{ - "scriptPubKey": { - "address": multi_sig_script['address'] - }, - "timestamp": "now", - "redeemscript": multi_sig_script['redeemScript'], - "keys": [ self.nodes[0].dumpprivkey(sig_address_1['address']), self.nodes[0].dumpprivkey(sig_address_2['address'])], - "watchonly": True - }]) - assert_equal(result[0]['success'], False) - assert_equal(result[0]['error']['code'], -8) - assert_equal(result[0]['error']['message'], 'Incompatibility found between watchonly and keys') - + self.test_importmulti({"scriptPubKey": {"address": multisig.p2sh_addr}, + "timestamp": "now", + "redeemscript": multisig.redeem_script, + "keys": multisig.privkeys[0:2], + "watchonly": True}, + success=True) + test_address(self.nodes[1], + multisig.p2sh_addr, + iswatchonly=True, + ismine=False, + solvable=True, + timestamp=timestamp) # Address + Public key + !Internal + Wrong pubkey - self.log.info("Should not import an address with a wrong public key") - address = self.nodes[0].getaddressinfo(self.nodes[0].getnewaddress()) - address2 = self.nodes[0].getaddressinfo(self.nodes[0].getnewaddress()) - result = self.nodes[1].importmulti([{ - "scriptPubKey": { - "address": address['address'] - }, - "timestamp": "now", - "pubkeys": [ address2['pubkey'] ] - }]) - assert_equal(result[0]['success'], False) - assert_equal(result[0]['error']['code'], -5) - assert_equal(result[0]['error']['message'], 'Consistency check failed') - address_assert = self.nodes[1].getaddressinfo(address['address']) - assert_equal(address_assert['iswatchonly'], False) - assert_equal(address_assert['ismine'], False) - assert_equal('timestamp' in address_assert, False) - + self.log.info("Should not import an address with the wrong public key as non-solvable") + key = get_key(self.nodes[0]) + wrong_key = get_key(self.nodes[0]).pubkey + self.test_importmulti({"scriptPubKey": {"address": key.p2pkh_addr}, + "timestamp": "now", + "pubkeys": [wrong_key]}, + success=True, + warnings=["Importing as non-solvable: some required keys are missing. If this is intentional, don't provide any keys, pubkeys, witnessscript, or redeemscript.", "Some private keys are missing, outputs will be considered watchonly. If this is intentional, specify the watchonly flag."]) + test_address(self.nodes[1], + key.p2pkh_addr, + iswatchonly=True, + ismine=False, + solvable=False, + timestamp=timestamp) # ScriptPubKey + Public key + internal + Wrong pubkey - self.log.info("Should not import a scriptPubKey with internal and with a wrong public key") - address = self.nodes[0].getaddressinfo(self.nodes[0].getnewaddress()) - address2 = self.nodes[0].getaddressinfo(self.nodes[0].getnewaddress()) - request = [{ - "scriptPubKey": address['scriptPubKey'], - "timestamp": "now", - "pubkeys": [ address2['pubkey'] ], - "internal": True - }] - result = self.nodes[1].importmulti(request) - assert_equal(result[0]['success'], False) - assert_equal(result[0]['error']['code'], -5) - assert_equal(result[0]['error']['message'], 'Consistency check failed') - address_assert = self.nodes[1].getaddressinfo(address['address']) - assert_equal(address_assert['iswatchonly'], False) - assert_equal(address_assert['ismine'], False) - assert_equal('timestamp' in address_assert, False) - + self.log.info("Should import a scriptPubKey with internal and with a wrong public key as non-solvable") + key = get_key(self.nodes[0]) + wrong_key = get_key(self.nodes[0]).pubkey + self.test_importmulti({"scriptPubKey": key.p2pkh_script, + "timestamp": "now", + "pubkeys": [wrong_key], + "internal": True}, + success=True, + warnings=["Importing as non-solvable: some required keys are missing. If this is intentional, don't provide any keys, pubkeys, witnessscript, or redeemscript.", "Some private keys are missing, outputs will be considered watchonly. If this is intentional, specify the watchonly flag."]) + test_address(self.nodes[1], + key.p2pkh_addr, + iswatchonly=True, + ismine=False, + solvable=False, + timestamp=timestamp) # Address + Private key + !watchonly + Wrong private key - self.log.info("Should not import an address with a wrong private key") - address = self.nodes[0].getaddressinfo(self.nodes[0].getnewaddress()) - address2 = self.nodes[0].getaddressinfo(self.nodes[0].getnewaddress()) - result = self.nodes[1].importmulti([{ - "scriptPubKey": { - "address": address['address'] - }, - "timestamp": "now", - "keys": [ self.nodes[0].dumpprivkey(address2['address']) ] - }]) - assert_equal(result[0]['success'], False) - assert_equal(result[0]['error']['code'], -5) - assert_equal(result[0]['error']['message'], 'Consistency check failed') - address_assert = self.nodes[1].getaddressinfo(address['address']) - assert_equal(address_assert['iswatchonly'], False) - assert_equal(address_assert['ismine'], False) - assert_equal('timestamp' in address_assert, False) - + self.log.info("Should import an address with a wrong private key as non-solvable") + key = get_key(self.nodes[0]) + wrong_privkey = get_key(self.nodes[0]).privkey + self.test_importmulti({"scriptPubKey": {"address": key.p2pkh_addr}, + "timestamp": "now", + "keys": [wrong_privkey]}, + success=True, + warnings=["Importing as non-solvable: some required keys are missing. If this is intentional, don't provide any keys, pubkeys, witnessscript, or redeemscript.", "Some private keys are missing, outputs will be considered watchonly. If this is intentional, specify the watchonly flag."]) + test_address(self.nodes[1], + key.p2pkh_addr, + iswatchonly=True, + ismine=False, + solvable=False, + timestamp=timestamp) # ScriptPubKey + Private key + internal + Wrong private key - self.log.info("Should not import a scriptPubKey with internal and with a wrong private key") - address = self.nodes[0].getaddressinfo(self.nodes[0].getnewaddress()) - address2 = self.nodes[0].getaddressinfo(self.nodes[0].getnewaddress()) - result = self.nodes[1].importmulti([{ - "scriptPubKey": address['scriptPubKey'], - "timestamp": "now", - "keys": [ self.nodes[0].dumpprivkey(address2['address']) ], - "internal": True - }]) - assert_equal(result[0]['success'], False) - assert_equal(result[0]['error']['code'], -5) - assert_equal(result[0]['error']['message'], 'Consistency check failed') - address_assert = self.nodes[1].getaddressinfo(address['address']) - assert_equal(address_assert['iswatchonly'], False) - assert_equal(address_assert['ismine'], False) - assert_equal('timestamp' in address_assert, False) - + self.log.info("Should import a scriptPubKey with internal and with a wrong private key as non-solvable") + key = get_key(self.nodes[0]) + wrong_privkey = get_key(self.nodes[0]).privkey + self.test_importmulti({"scriptPubKey": key.p2pkh_script, + "timestamp": "now", + "keys": [wrong_privkey], + "internal": True}, + success=True, + warnings=["Importing as non-solvable: some required keys are missing. If this is intentional, don't provide any keys, pubkeys, witnessscript, or redeemscript.", "Some private keys are missing, outputs will be considered watchonly. If this is intentional, specify the watchonly flag."]) + test_address(self.nodes[1], + key.p2pkh_addr, + iswatchonly=True, + ismine=False, + solvable=False, + timestamp=timestamp) # Importing existing watch only address with new timestamp should replace saved timestamp. assert_greater_than(timestamp, watchonly_timestamp) self.log.info("Should replace previously saved watch only timestamp.") - result = self.nodes[1].importmulti([{ - "scriptPubKey": { - "address": watchonly_address, - }, - "timestamp": "now", - }]) - assert_equal(result[0]['success'], True) - address_assert = self.nodes[1].getaddressinfo(watchonly_address) - assert_equal(address_assert['iswatchonly'], True) - assert_equal(address_assert['ismine'], False) - assert_equal(address_assert['timestamp'], timestamp) + self.test_importmulti({"scriptPubKey": {"address": watchonly_address}, + "timestamp": "now"}, + success=True) + test_address(self.nodes[1], + watchonly_address, + iswatchonly=True, + ismine=False, + timestamp=timestamp) watchonly_timestamp = timestamp - # restart nodes to check for proper serialization/deserialization of watch only address self.stop_nodes() self.start_nodes() - address_assert = self.nodes[1].getaddressinfo(watchonly_address) - assert_equal(address_assert['iswatchonly'], True) - assert_equal(address_assert['ismine'], False) - assert_equal(address_assert['timestamp'], watchonly_timestamp) + test_address(self.nodes[1], + watchonly_address, + iswatchonly=True, + ismine=False, + timestamp=watchonly_timestamp) # Bad or missing timestamps self.log.info("Should throw on invalid or missing timestamp values") assert_raises_rpc_error(-3, 'Missing required timestamp field for key', - self.nodes[1].importmulti, [{ - "scriptPubKey": address['scriptPubKey'], - }]) + self.nodes[1].importmulti, [{"scriptPubKey": key.p2pkh_script}]) assert_raises_rpc_error(-3, 'Expected number or "now" timestamp value for key. got type string', - self.nodes[1].importmulti, [{ - "scriptPubKey": address['scriptPubKey'], - "timestamp": "", - }]) - + self.nodes[1].importmulti, [{ + "scriptPubKey": key.p2pkh_script, + "timestamp": "" + }]) + + # Import P2WPKH address as watch only + self.log.info("Should import a P2WPKH address as watch only") + key = get_key(self.nodes[0]) + self.test_importmulti({"scriptPubKey": {"address": key.p2wpkh_addr}, + "timestamp": "now"}, + success=True) + test_address(self.nodes[1], + key.p2wpkh_addr, + iswatchonly=True, + solvable=False) + + # Import P2WPKH address with public key but no private key + self.log.info("Should import a P2WPKH address and public key as solvable but not spendable") + key = get_key(self.nodes[0]) + self.test_importmulti({"scriptPubKey": {"address": key.p2wpkh_addr}, + "timestamp": "now", + "pubkeys": [key.pubkey]}, + success=True, + warnings=["Some private keys are missing, outputs will be considered watchonly. If this is intentional, specify the watchonly flag."]) + test_address(self.nodes[1], + key.p2wpkh_addr, + ismine=False, + solvable=True) + + # Import P2WPKH address with key and check it is spendable + self.log.info("Should import a P2WPKH address with key") + key = get_key(self.nodes[0]) + self.test_importmulti({"scriptPubKey": {"address": key.p2wpkh_addr}, + "timestamp": "now", + "keys": [key.privkey]}, + success=True) + test_address(self.nodes[1], + key.p2wpkh_addr, + iswatchonly=False, + ismine=True) + + # P2WSH multisig address without scripts or keys + multisig = get_multisig(self.nodes[0]) + self.log.info("Should import a p2wsh multisig as watch only without respective redeem script and private keys") + self.test_importmulti({"scriptPubKey": {"address": multisig.p2wsh_addr}, + "timestamp": "now"}, + success=True) + test_address(self.nodes[1], + multisig.p2sh_addr, + solvable=False) + + # Same P2WSH multisig address as above, but now with witnessscript + private keys + self.log.info("Should import a p2wsh with respective witness script and private keys") + self.test_importmulti({"scriptPubKey": {"address": multisig.p2wsh_addr}, + "timestamp": "now", + "witnessscript": multisig.redeem_script, + "keys": multisig.privkeys}, + success=True) + test_address(self.nodes[1], + multisig.p2sh_addr, + solvable=True, + ismine=True, + sigsrequired=2) + + # P2SH-P2WPKH address with no redeemscript or public or private key + key = get_key(self.nodes[0]) + self.log.info("Should import a p2sh-p2wpkh without redeem script or keys") + self.test_importmulti({"scriptPubKey": {"address": key.p2sh_p2wpkh_addr}, + "timestamp": "now"}, + success=True) + test_address(self.nodes[1], + key.p2sh_p2wpkh_addr, + solvable=False, + ismine=False) + + # P2SH-P2WPKH address + redeemscript + public key with no private key + self.log.info("Should import a p2sh-p2wpkh with respective redeem script and pubkey as solvable") + self.test_importmulti({"scriptPubKey": {"address": key.p2sh_p2wpkh_addr}, + "timestamp": "now", + "redeemscript": key.p2sh_p2wpkh_redeem_script, + "pubkeys": [key.pubkey]}, + success=True, + warnings=["Some private keys are missing, outputs will be considered watchonly. If this is intentional, specify the watchonly flag."]) + test_address(self.nodes[1], + key.p2sh_p2wpkh_addr, + solvable=True, + ismine=False) + + # P2SH-P2WPKH address + redeemscript + private key + key = get_key(self.nodes[0]) + self.log.info("Should import a p2sh-p2wpkh with respective redeem script and private keys") + self.test_importmulti({"scriptPubKey": {"address": key.p2sh_p2wpkh_addr}, + "timestamp": "now", + "redeemscript": key.p2sh_p2wpkh_redeem_script, + "keys": [key.privkey]}, + success=True) + test_address(self.nodes[1], + key.p2sh_p2wpkh_addr, + solvable=True, + ismine=True) + + # P2SH-P2WSH multisig + redeemscript with no private key + multisig = get_multisig(self.nodes[0]) + self.log.info("Should import a p2sh-p2wsh with respective redeem script but no private key") + self.test_importmulti({"scriptPubKey": {"address": multisig.p2sh_p2wsh_addr}, + "timestamp": "now", + "redeemscript": multisig.p2wsh_script, + "witnessscript": multisig.redeem_script}, + success=True, + warnings=["Some private keys are missing, outputs will be considered watchonly. If this is intentional, specify the watchonly flag."]) + test_address(self.nodes[1], + multisig.p2sh_p2wsh_addr, + solvable=True, + ismine=False) if __name__ == '__main__': - ImportMultiTest ().main () + ImportMultiTest().main() diff --git a/test/functional/wallet_importprunedfunds.py b/test/functional/wallet_importprunedfunds.py index 26b181db33..78426018ef 100755 --- a/test/functional/wallet_importprunedfunds.py +++ b/test/functional/wallet_importprunedfunds.py @@ -81,7 +81,7 @@ class ImportPrunedFundsTest(BitcoinTestFramework): # Import with affiliated address with no rescan self.nodes[1].importaddress(address=address2, rescan=False) - self.nodes[1].importprunedfunds(rawtxn2, proof2) + self.nodes[1].importprunedfunds(rawtransaction=rawtxn2, txoutproof=proof2) assert [tx for tx in self.nodes[1].listtransactions(include_watchonly=True) if tx['txid'] == txnid2] # Import with private key with no rescan diff --git a/test/functional/wallet_keypool.py b/test/functional/wallet_keypool.py index 51afa0cb1a..ceb9709712 100755 --- a/test/functional/wallet_keypool.py +++ b/test/functional/wallet_keypool.py @@ -73,11 +73,10 @@ class KeyPoolTest(BitcoinTestFramework): time.sleep(1.1) assert_equal(nodes[0].getwalletinfo()["unlocked_until"], 0) - # drain them by mining - nodes[0].generate(1) - nodes[0].generate(1) - nodes[0].generate(1) - assert_raises_rpc_error(-12, "Keypool ran out", nodes[0].generate, 1) + # drain the keypool + for _ in range(3): + nodes[0].getnewaddress() + assert_raises_rpc_error(-12, "Keypool ran out", nodes[0].getnewaddress) nodes[0].walletpassphrase('test', 100) nodes[0].keypoolrefill(100) diff --git a/test/functional/wallet_keypool_topup.py b/test/functional/wallet_keypool_topup.py index f1a441c399..b7c8d3098d 100755 --- a/test/functional/wallet_keypool_topup.py +++ b/test/functional/wallet_keypool_topup.py @@ -24,8 +24,8 @@ from test_framework.util import ( class KeypoolRestoreTest(BitcoinTestFramework): def set_test_params(self): self.setup_clean_chain = True - self.num_nodes = 2 - self.extra_args = [[], ['-keypool=100']] + self.num_nodes = 4 + self.extra_args = [[], ['-keypool=100'], ['-keypool=100'], ['-keypool=100']] def skip_test_if_missing_module(self): self.skip_if_no_wallet() @@ -40,32 +40,47 @@ class KeypoolRestoreTest(BitcoinTestFramework): shutil.copyfile(wallet_path, wallet_backup_path) self.start_node(1, self.extra_args[1]) connect_nodes_bi(self.nodes, 0, 1) + connect_nodes_bi(self.nodes, 0, 2) + connect_nodes_bi(self.nodes, 0, 3) - self.log.info("Generate keys for wallet") - for _ in range(90): - addr_oldpool = self.nodes[1].getnewaddress() - for _ in range(20): - addr_extpool = self.nodes[1].getnewaddress() + for i, output_type in enumerate(["legacy", "p2sh-segwit", "bech32"]): - self.log.info("Send funds to wallet") - self.nodes[0].sendtoaddress(addr_oldpool, 10) - self.nodes[0].generate(1) - self.nodes[0].sendtoaddress(addr_extpool, 5) - self.nodes[0].generate(1) - sync_blocks(self.nodes) + self.log.info("Generate keys for wallet with address type: {}".format(output_type)) + idx = i+1 + for _ in range(90): + addr_oldpool = self.nodes[idx].getnewaddress(address_type=output_type) + for _ in range(20): + addr_extpool = self.nodes[idx].getnewaddress(address_type=output_type) - self.log.info("Restart node with wallet backup") - self.stop_node(1) - shutil.copyfile(wallet_backup_path, wallet_path) - self.start_node(1, self.extra_args[1]) - connect_nodes_bi(self.nodes, 0, 1) - self.sync_all() + # Make sure we're creating the outputs we expect + address_details = self.nodes[idx].validateaddress(addr_extpool) + if i == 0: + assert(not address_details["isscript"] and not address_details["iswitness"]) + elif i == 1: + assert(address_details["isscript"] and not address_details["iswitness"]) + else: + assert(not address_details["isscript"] and address_details["iswitness"]) + + + self.log.info("Send funds to wallet") + self.nodes[0].sendtoaddress(addr_oldpool, 10) + self.nodes[0].generate(1) + self.nodes[0].sendtoaddress(addr_extpool, 5) + self.nodes[0].generate(1) + sync_blocks(self.nodes) + + self.log.info("Restart node with wallet backup") + self.stop_node(idx) + shutil.copyfile(wallet_backup_path, wallet_path) + self.start_node(idx, self.extra_args[idx]) + connect_nodes_bi(self.nodes, 0, idx) + self.sync_all() - self.log.info("Verify keypool is restored and balance is correct") - assert_equal(self.nodes[1].getbalance(), 15) - assert_equal(self.nodes[1].listtransactions()[0]['category'], "receive") - # Check that we have marked all keys up to the used keypool key as used - assert_equal(self.nodes[1].getaddressinfo(self.nodes[1].getnewaddress())['hdkeypath'], "m/0'/0'/110'") + self.log.info("Verify keypool is restored and balance is correct") + assert_equal(self.nodes[idx].getbalance(), 15) + assert_equal(self.nodes[idx].listtransactions()[0]['category'], "receive") + # Check that we have marked all keys up to the used keypool key as used + assert_equal(self.nodes[idx].getaddressinfo(self.nodes[idx].getnewaddress())['hdkeypath'], "m/0'/0'/110'") if __name__ == '__main__': diff --git a/test/functional/wallet_labels.py b/test/functional/wallet_labels.py index 8d7c77bb96..b71dae9f40 100755 --- a/test/functional/wallet_labels.py +++ b/test/functional/wallet_labels.py @@ -29,8 +29,8 @@ class WalletLabelsTest(BitcoinTestFramework): # Note each time we call generate, all generated coins go into # the same address, so we call twice to get two addresses w/50 each - node.generate(1) - node.generate(101) + node.generatetoaddress(nblocks=1, address=node.getnewaddress(label='coinbase')) + node.generatetoaddress(nblocks=101, address=node.getnewaddress(label='coinbase')) assert_equal(node.getbalance(), 100) # there should be 2 address groups @@ -42,8 +42,9 @@ class WalletLabelsTest(BitcoinTestFramework): linked_addresses = set() for address_group in address_groups: assert_equal(len(address_group), 1) - assert_equal(len(address_group[0]), 2) + assert_equal(len(address_group[0]), 3) assert_equal(address_group[0][1], 50) + assert_equal(address_group[0][2], 'coinbase') linked_addresses.add(address_group[0][0]) # send 50 from each address to a third address not in this wallet @@ -77,7 +78,7 @@ class WalletLabelsTest(BitcoinTestFramework): label.verify(node) # Check all labels are returned by listlabels. - assert_equal(node.listlabels(), [label.name for label in labels]) + assert_equal(node.listlabels(), sorted(['coinbase'] + [label.name for label in labels])) # Send a transaction to each label. for label in labels: diff --git a/test/functional/wallet_listreceivedby.py b/test/functional/wallet_listreceivedby.py index 3485c4470f..011975e371 100755 --- a/test/functional/wallet_listreceivedby.py +++ b/test/functional/wallet_listreceivedby.py @@ -18,11 +18,6 @@ class ReceivedByTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 2 - def import_deterministic_coinbase_privkeys(self): - assert_equal(0, len(self.nodes[1].listreceivedbyaddress(minconf=0, include_empty=True, include_watchonly=True))) - super().import_deterministic_coinbase_privkeys() - self.num_cb_reward_addresses = len(self.nodes[1].listreceivedbyaddress(minconf=0, include_empty=True, include_watchonly=True)) - def skip_test_if_missing_module(self): self.skip_if_no_wallet() @@ -31,6 +26,9 @@ class ReceivedByTest(BitcoinTestFramework): self.nodes[0].generate(1) sync_blocks(self.nodes) + # save the number of coinbase reward addresses so far + num_cb_reward_addresses = len(self.nodes[1].listreceivedbyaddress(minconf=0, include_empty=True, include_watchonly=True)) + self.log.info("listreceivedbyaddress Test") # Send from node 0 to 1 @@ -68,11 +66,15 @@ class ReceivedByTest(BitcoinTestFramework): res = self.nodes[1].listreceivedbyaddress(minconf=0, include_empty=True, include_watchonly=True, address_filter=addr) assert_array_result(res, {"address": addr}, expected) assert_equal(len(res), 1) + # Test for regression on CLI calls with address string (#14173) + cli_res = self.nodes[1].cli.listreceivedbyaddress(0, True, True, addr) + assert_array_result(cli_res, {"address": addr}, expected) + assert_equal(len(cli_res), 1) # Error on invalid address assert_raises_rpc_error(-4, "address_filter parameter was invalid", self.nodes[1].listreceivedbyaddress, minconf=0, include_empty=True, include_watchonly=True, address_filter="bamboozling") # Another address receive money res = self.nodes[1].listreceivedbyaddress(0, True, True) - assert_equal(len(res), 2 + self.num_cb_reward_addresses) # Right now 2 entries + assert_equal(len(res), 2 + num_cb_reward_addresses) # Right now 2 entries other_addr = self.nodes[1].getnewaddress() txid2 = self.nodes[0].sendtoaddress(other_addr, 0.1) self.nodes[0].generate(1) @@ -89,7 +91,7 @@ class ReceivedByTest(BitcoinTestFramework): assert_equal(len(res), 1) # Should be two entries though without filter res = self.nodes[1].listreceivedbyaddress(0, True, True) - assert_equal(len(res), 3 + self.num_cb_reward_addresses) # Became 3 entries + assert_equal(len(res), 3 + num_cb_reward_addresses) # Became 3 entries # Not on random addr other_addr = self.nodes[0].getnewaddress() # note on node[0]! just a random addr diff --git a/test/functional/wallet_listtransactions.py b/test/functional/wallet_listtransactions.py index 5a17395abd..17f044bf65 100755 --- a/test/functional/wallet_listtransactions.py +++ b/test/functional/wallet_listtransactions.py @@ -25,12 +25,13 @@ def tx_from_hex(hexstring): class ListTransactionsTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 2 - self.enable_mocktime() def skip_test_if_missing_module(self): self.skip_if_no_wallet() def run_test(self): + self.nodes[0].generate(1) # Get out of IBD + self.sync_all() # Simple send, 0 to 1: txid = self.nodes[0].sendtoaddress(self.nodes[1].getnewaddress(), 0.1) self.sync_all() @@ -97,9 +98,10 @@ class ListTransactionsTest(BitcoinTestFramework): txid = self.nodes[1].sendtoaddress(multisig["address"], 0.1) self.nodes[1].generate(1) self.sync_all() - assert not [tx for tx in self.nodes[0].listtransactions(dummy="*", count=100, skip=0, include_watchonly=False) if "label" in tx and tx["label"] == "watchonly"] - txs = [tx for tx in self.nodes[0].listtransactions(dummy="*", count=100, skip=0, include_watchonly=True) if "label" in tx and tx['label'] == 'watchonly'] - assert_array_result(txs, {"category": "receive", "amount": Decimal("0.1")}, {"txid": txid}) + assert len(self.nodes[0].listtransactions(label="watchonly", count=100, include_watchonly=False)) == 0 + assert_array_result(self.nodes[0].listtransactions(label="watchonly", count=100, include_watchonly=True), + {"category": "receive", "amount": Decimal("0.1")}, + {"txid": txid, "label": "watchonly"}) self.run_rbf_opt_in_test() diff --git a/test/functional/wallet_multiwallet.py b/test/functional/wallet_multiwallet.py index 189bc2d50e..8ab569a3c3 100755 --- a/test/functional/wallet_multiwallet.py +++ b/test/functional/wallet_multiwallet.py @@ -8,6 +8,7 @@ Verify that a bitcoind node can load multiple wallet files """ import os import shutil +import time from test_framework.test_framework import BitcoinTestFramework from test_framework.test_node import ErrorMatch @@ -38,15 +39,16 @@ class MultiWalletTest(BitcoinTestFramework): return wallet_dir(name, "wallet.dat") return wallet_dir(name) + assert_equal(self.nodes[0].listwalletdir(), { 'wallets': [{ 'name': '' }] }) + # check wallet.dat is created self.stop_nodes() assert_equal(os.path.isfile(wallet_dir('wallet.dat')), True) # create symlink to verify wallet directory path can be referenced # through symlink - if os.name != 'nt': - os.mkdir(wallet_dir('w7')) - os.symlink('w7', wallet_dir('w7_symlink')) + os.mkdir(wallet_dir('w7')) + os.symlink('w7', wallet_dir('w7_symlink')) # rename wallet.dat to make sure plain wallet file paths (as opposed to # directory paths) can be loaded @@ -67,10 +69,10 @@ class MultiWalletTest(BitcoinTestFramework): # w8 - to verify existing wallet file is loaded correctly # '' - to verify default wallet file is created correctly wallet_names = ['w1', 'w2', 'w3', 'w', 'sub/w5', os.path.join(self.options.tmpdir, 'extern/w6'), 'w7_symlink', 'w8', ''] - if os.name == 'nt': - wallet_names.remove('w7_symlink') extra_args = ['-wallet={}'.format(n) for n in wallet_names] self.start_node(0, extra_args) + assert_equal(sorted(map(lambda w: w['name'], self.nodes[0].listwalletdir()['wallets'])), ['', os.path.join('sub', 'w5'), 'w', 'w1', 'w2', 'w3', 'w7', 'w7_symlink', 'w8']) + assert_equal(set(node.listwallets()), set(wallet_names)) # check that all requested wallets were created @@ -95,9 +97,8 @@ class MultiWalletTest(BitcoinTestFramework): self.nodes[0].assert_start_raises_init_error(['-wallet=w8', '-wallet=w8_copy'], exp_stderr, match=ErrorMatch.PARTIAL_REGEX) # should not initialize if wallet file is a symlink - if os.name != 'nt': - os.symlink('w8', wallet_dir('w8_symlink')) - self.nodes[0].assert_start_raises_init_error(['-wallet=w8_symlink'], 'Error: Invalid -wallet path \'w8_symlink\'\. .*', match=ErrorMatch.FULL_REGEX) + os.symlink('w8', wallet_dir('w8_symlink')) + self.nodes[0].assert_start_raises_init_error(['-wallet=w8_symlink'], 'Error: Invalid -wallet path \'w8_symlink\'\. .*', match=ErrorMatch.FULL_REGEX) # should not initialize if the specified walletdir does not exist self.nodes[0].assert_start_raises_init_error(['-walletdir=bad'], 'Error: Specified -walletdir "bad" does not exist') @@ -125,7 +126,7 @@ class MultiWalletTest(BitcoinTestFramework): self.start_node(0, ['-wallet=w4', '-wallet=w5']) assert_equal(set(node.listwallets()), {"w4", "w5"}) w5 = wallet("w5") - w5.generate(1) + node.generatetoaddress(nblocks=1, address=w5.getnewaddress()) # now if wallets/ exists again, but the rootdir is specified as the walletdir, w4 and w5 should still be loaded os.rename(wallet_dir2, wallet_dir()) @@ -143,11 +144,13 @@ class MultiWalletTest(BitcoinTestFramework): self.restart_node(0, extra_args) + assert_equal(sorted(map(lambda w: w['name'], self.nodes[0].listwalletdir()['wallets'])), ['', os.path.join('sub', 'w5'), 'w', 'w1', 'w2', 'w3', 'w7', 'w7_symlink', 'w8', 'w8_copy']) + wallets = [wallet(w) for w in wallet_names] wallet_bad = wallet("bad") # check wallet names and balances - wallets[0].generate(1) + node.generatetoaddress(nblocks=1, address=wallets[0].getnewaddress()) for wallet_name, wallet in zip(wallet_names, wallets): info = wallet.getwalletinfo() assert_equal(info['immature_balance'], 50 if wallet is wallets[0] else 0) @@ -160,7 +163,7 @@ class MultiWalletTest(BitcoinTestFramework): assert_raises_rpc_error(-19, "Wallet file not specified", node.getwalletinfo) w1, w2, w3, w4, *_ = wallets - w1.generate(101) + node.generatetoaddress(nblocks=101, address=w1.getnewaddress()) assert_equal(w1.getbalance(), 100) assert_equal(w2.getbalance(), 0) assert_equal(w3.getbalance(), 0) @@ -169,7 +172,7 @@ class MultiWalletTest(BitcoinTestFramework): w1.sendtoaddress(w2.getnewaddress(), 1) w1.sendtoaddress(w3.getnewaddress(), 2) w1.sendtoaddress(w4.getnewaddress(), 3) - w1.generate(1) + node.generatetoaddress(nblocks=1, address=w1.getnewaddress()) assert_equal(w2.getbalance(), 1) assert_equal(w3.getbalance(), 2) assert_equal(w4.getbalance(), 3) @@ -220,12 +223,18 @@ class MultiWalletTest(BitcoinTestFramework): # Fail to load duplicate wallets assert_raises_rpc_error(-4, 'Wallet file verification failed: Error loading wallet w1. Duplicate -wallet filename specified.', self.nodes[0].loadwallet, wallet_names[0]) + # Fail to load duplicate wallets by different ways (directory and filepath) + assert_raises_rpc_error(-4, "Wallet file verification failed: Error loading wallet wallet.dat. Duplicate -wallet filename specified.", self.nodes[0].loadwallet, 'wallet.dat') + # Fail to load if one wallet is a copy of another assert_raises_rpc_error(-1, "BerkeleyBatch: Can't open database w8_copy (duplicates fileid", self.nodes[0].loadwallet, 'w8_copy') + # Fail to load if one wallet is a copy of another, test this twice to make sure that we don't re-introduce #14304 + assert_raises_rpc_error(-1, "BerkeleyBatch: Can't open database w8_copy (duplicates fileid", self.nodes[0].loadwallet, 'w8_copy') + + # Fail to load if wallet file is a symlink - if os.name != 'nt': - assert_raises_rpc_error(-4, "Wallet file verification failed: Invalid -wallet path 'w8_symlink'", self.nodes[0].loadwallet, 'w8_symlink') + assert_raises_rpc_error(-4, "Wallet file verification failed: Invalid -wallet path 'w8_symlink'", self.nodes[0].loadwallet, 'w8_symlink') # Fail to load if a directory is specified that doesn't contain a wallet os.mkdir(wallet_dir('empty_wallet_dir')) @@ -267,7 +276,11 @@ class MultiWalletTest(BitcoinTestFramework): assert 'w1' not in self.nodes[0].listwallets() # Successfully unload the wallet referenced by the request endpoint + # Also ensure unload works during walletpassphrase timeout + w2.encryptwallet('test') + w2.walletpassphrase('test', 1) w2.unloadwallet() + time.sleep(1.1) assert 'w2' not in self.nodes[0].listwallets() # Successfully unload all wallets @@ -281,6 +294,8 @@ class MultiWalletTest(BitcoinTestFramework): assert_equal(self.nodes[0].listwallets(), ['w1']) assert_equal(w1.getwalletinfo()['walletname'], 'w1') + assert_equal(sorted(map(lambda w: w['name'], self.nodes[0].listwalletdir()['wallets'])), ['', os.path.join('sub', 'w5'), 'w', 'w1', 'w2', 'w3', 'w7', 'w7_symlink', 'w8', 'w8_copy', 'w9']) + # Test backing up and restoring wallets self.log.info("Test wallet backup") self.restart_node(0, ['-nowallet']) diff --git a/test/functional/wallet_txn_clone.py b/test/functional/wallet_txn_clone.py index d78c105c17..1c2e0a9cb7 100755 --- a/test/functional/wallet_txn_clone.py +++ b/test/functional/wallet_txn_clone.py @@ -65,7 +65,7 @@ class TxnMallTest(BitcoinTestFramework): # Construct a clone of tx1, to be malleated rawtx1 = self.nodes[0].getrawtransaction(txid1, 1) - clone_inputs = [{"txid": rawtx1["vin"][0]["txid"], "vout": rawtx1["vin"][0]["vout"]}] + clone_inputs = [{"txid": rawtx1["vin"][0]["txid"], "vout": rawtx1["vin"][0]["vout"], "sequence": rawtx1["vin"][0]["sequence"]}] clone_outputs = {rawtx1["vout"][0]["scriptPubKey"]["addresses"][0]: rawtx1["vout"][0]["value"], rawtx1["vout"][1]["scriptPubKey"]["addresses"][0]: rawtx1["vout"][1]["value"]} clone_locktime = rawtx1["locktime"] diff --git a/test/lint/check-doc.py b/test/lint/check-doc.py index b0d9f87958..4facd6c334 100755 --- a/test/lint/check-doc.py +++ b/test/lint/check-doc.py @@ -26,8 +26,12 @@ SET_DOC_OPTIONAL = set(['-h', '-help', '-dbcrashratio', '-forcecompactdb']) def main(): - used = check_output(CMD_GREP_ARGS, shell=True, universal_newlines=True, encoding='utf8') - docd = check_output(CMD_GREP_DOCS, shell=True, universal_newlines=True, encoding='utf8') + if sys.version_info >= (3, 6): + used = check_output(CMD_GREP_ARGS, shell=True, universal_newlines=True, encoding='utf8') + docd = check_output(CMD_GREP_DOCS, shell=True, universal_newlines=True, encoding='utf8') + else: + used = check_output(CMD_GREP_ARGS, shell=True, universal_newlines=True) # encoding='utf8' + docd = check_output(CMD_GREP_DOCS, shell=True, universal_newlines=True) # encoding='utf8' args_used = set(re.findall(re.compile(REGEX_ARG), used)) args_docd = set(re.findall(re.compile(REGEX_DOC), docd)).union(SET_DOC_OPTIONAL) diff --git a/test/lint/commit-script-check.sh b/test/lint/commit-script-check.sh index f1327469f3..4267f9fa0d 100755 --- a/test/lint/commit-script-check.sh +++ b/test/lint/commit-script-check.sh @@ -20,23 +20,23 @@ fi RET=0 PREV_BRANCH=`git name-rev --name-only HEAD` PREV_HEAD=`git rev-parse HEAD` -for i in `git rev-list --reverse $1`; do - if git rev-list -n 1 --pretty="%s" $i | grep -q "^scripted-diff:"; then - git checkout --quiet $i^ || exit - SCRIPT="`git rev-list --format=%b -n1 $i | sed '/^-BEGIN VERIFY SCRIPT-$/,/^-END VERIFY SCRIPT-$/{//!b};d'`" +for commit in `git rev-list --reverse $1`; do + if git rev-list -n 1 --pretty="%s" $commit | grep -q "^scripted-diff:"; then + git checkout --quiet $commit^ || exit + SCRIPT="`git rev-list --format=%b -n1 $commit | sed '/^-BEGIN VERIFY SCRIPT-$/,/^-END VERIFY SCRIPT-$/{//!b};d'`" if test "x$SCRIPT" = "x"; then - echo "Error: missing script for: $i" + echo "Error: missing script for: $commit" echo "Failed" RET=1 else - echo "Running script for: $i" + echo "Running script for: $commit" echo "$SCRIPT" - eval "$SCRIPT" - git --no-pager diff --exit-code $i && echo "OK" || (echo "Failed"; false) || RET=1 + (eval "$SCRIPT") + git --no-pager diff --exit-code $commit && echo "OK" || (echo "Failed"; false) || RET=1 fi git reset --quiet --hard HEAD else - if git rev-list "--format=%b" -n1 $i | grep -q '^-\(BEGIN\|END\)[ a-zA-Z]*-$'; then + if git rev-list "--format=%b" -n1 $commit | grep -q '^-\(BEGIN\|END\)[ a-zA-Z]*-$'; then echo "Error: script block marker but no scripted-diff in title" echo "Failed" RET=1 diff --git a/test/lint/lint-circular-dependencies.sh b/test/lint/lint-circular-dependencies.sh index 3972baed1d..be67cbed31 100755 --- a/test/lint/lint-circular-dependencies.sh +++ b/test/lint/lint-circular-dependencies.sh @@ -9,7 +9,7 @@ export LC_ALL=C EXPECTED_CIRCULAR_DEPENDENCIES=( - "chainparamsbase -> util -> chainparamsbase" + "chainparamsbase -> util/system -> chainparamsbase" "checkpoints -> validation -> checkpoints" "index/txindex -> validation -> index/txindex" "policy/fees -> txmempool -> policy/fees" @@ -29,7 +29,6 @@ EXPECTED_CIRCULAR_DEPENDENCIES=( "validation -> validationinterface -> validation" "wallet/coincontrol -> wallet/wallet -> wallet/coincontrol" "wallet/fees -> wallet/wallet -> wallet/fees" - "wallet/rpcwallet -> wallet/wallet -> wallet/rpcwallet" "wallet/wallet -> wallet/walletdb -> wallet/wallet" "policy/fees -> policy/policy -> validation -> policy/fees" "policy/rbf -> txmempool -> validation -> policy/rbf" diff --git a/test/lint/lint-format-strings.py b/test/lint/lint-format-strings.py index 2fb35fd8ca..f5d4780b6d 100755 --- a/test/lint/lint-format-strings.py +++ b/test/lint/lint-format-strings.py @@ -16,8 +16,8 @@ FALSE_POSITIVES = [ ("src/dbwrapper.cpp", "vsnprintf(p, limit - p, format, backup_ap)"), ("src/index/base.cpp", "FatalError(const char* fmt, const Args&... args)"), ("src/netbase.cpp", "LogConnectFailure(bool manual_connection, const char* fmt, const Args&... args)"), - ("src/util.cpp", "strprintf(_(COPYRIGHT_HOLDERS), _(COPYRIGHT_HOLDERS_SUBSTITUTION))"), - ("src/util.cpp", "strprintf(COPYRIGHT_HOLDERS, COPYRIGHT_HOLDERS_SUBSTITUTION)"), + ("src/util/system.cpp", "strprintf(_(COPYRIGHT_HOLDERS), _(COPYRIGHT_HOLDERS_SUBSTITUTION))"), + ("src/util/system.cpp", "strprintf(COPYRIGHT_HOLDERS, COPYRIGHT_HOLDERS_SUBSTITUTION)"), ("src/wallet/wallet.h", "WalletLogPrintf(std::string fmt, Params... parameters)"), ("src/wallet/wallet.h", "LogPrintf((\"%s \" + fmt).c_str(), GetDisplayName(), parameters...)"), ("src/logging.h", "LogPrintf(const char* fmt, const Args&... args)"), @@ -241,12 +241,11 @@ def count_format_specifiers(format_string): 4 """ assert(type(format_string) is str) + format_string = format_string.replace('%%', 'X') n = 0 in_specifier = False for i, char in enumerate(format_string): - if format_string[i - 1:i + 1] == "%%" or format_string[i:i + 2] == "%%": - pass - elif char == "%": + if char == "%": in_specifier = True n += 1 elif char in "aAcdeEfFgGinopsuxX": diff --git a/test/lint/lint-format-strings.sh b/test/lint/lint-format-strings.sh index 2c443abf6b..c994ae3f4d 100755 --- a/test/lint/lint-format-strings.sh +++ b/test/lint/lint-format-strings.sh @@ -11,20 +11,20 @@ export LC_ALL=C FUNCTION_NAMES_AND_NUMBER_OF_LEADING_ARGUMENTS=( - FatalError,0 - fprintf,1 - LogConnectFailure,1 - LogPrint,1 - LogPrintf,0 - printf,0 - snprintf,2 - sprintf,1 - strprintf,0 - vfprintf,1 - vprintf,1 - vsnprintf,1 - vsprintf,1 - WalletLogPrintf,0 + "FatalError,0" + "fprintf,1" + "LogConnectFailure,1" + "LogPrint,1" + "LogPrintf,0" + "printf,0" + "snprintf,2" + "sprintf,1" + "strprintf,0" + "vfprintf,1" + "vprintf,1" + "vsnprintf,1" + "vsprintf,1" + "WalletLogPrintf,0" ) EXIT_CODE=0 diff --git a/test/lint/lint-includes.sh b/test/lint/lint-includes.sh index f6d0fd382b..4b9e2615b6 100755 --- a/test/lint/lint-includes.sh +++ b/test/lint/lint-includes.sh @@ -50,7 +50,6 @@ EXPECTED_BOOST_INCLUDES=( boost/algorithm/string/classification.hpp boost/algorithm/string/replace.hpp boost/algorithm/string/split.hpp - boost/bind.hpp boost/chrono/chrono.hpp boost/date_time/posix_time/posix_time.hpp boost/filesystem.hpp diff --git a/test/lint/lint-locale-dependence.sh b/test/lint/lint-locale-dependence.sh index cbee437c91..1534d5ef68 100755 --- a/test/lint/lint-locale-dependence.sh +++ b/test/lint/lint-locale-dependence.sh @@ -2,36 +2,23 @@ export LC_ALL=C KNOWN_VIOLATIONS=( - "src/base58.cpp:.*isspace" "src/bitcoin-tx.cpp.*stoul" "src/bitcoin-tx.cpp.*trim_right" - "src/bitcoin-tx.cpp:.*atoi" - "src/core_read.cpp.*is_digit" "src/dbwrapper.cpp.*stoul" "src/dbwrapper.cpp:.*vsnprintf" "src/httprpc.cpp.*trim" "src/init.cpp:.*atoi" "src/qt/rpcconsole.cpp:.*atoi" - "src/qt/rpcconsole.cpp:.*isdigit" "src/rest.cpp:.*strtol" "src/test/dbwrapper_tests.cpp:.*snprintf" - "src/test/getarg_tests.cpp.*split" "src/torcontrol.cpp:.*atoi" "src/torcontrol.cpp:.*strtol" - "src/uint256.cpp:.*isspace" - "src/uint256.cpp:.*tolower" - "src/util.cpp:.*atoi" - "src/util.cpp:.*fprintf" - "src/util.cpp:.*tolower" - "src/utilmoneystr.cpp:.*isdigit" - "src/utilmoneystr.cpp:.*isspace" - "src/utilstrencodings.cpp:.*atoi" - "src/utilstrencodings.cpp:.*isspace" - "src/utilstrencodings.cpp:.*strtol" - "src/utilstrencodings.cpp:.*strtoll" - "src/utilstrencodings.cpp:.*strtoul" - "src/utilstrencodings.cpp:.*strtoull" - "src/utilstrencodings.h:.*atoi" + "src/util/strencodings.cpp:.*atoi" + "src/util/strencodings.cpp:.*strtol" + "src/util/strencodings.cpp:.*strtoul" + "src/util/strencodings.h:.*atoi" + "src/util/system.cpp:.*atoi" + "src/util/system.cpp:.*fprintf" ) REGEXP_IGNORE_EXTERNAL_DEPENDENCIES="^src/(crypto/ctaes/|leveldb/|secp256k1/|tinyformat.h|univalue/)" diff --git a/test/lint/lint-python-dead-code.sh b/test/lint/lint-python-dead-code.sh new file mode 100755 index 0000000000..1b897cd131 --- /dev/null +++ b/test/lint/lint-python-dead-code.sh @@ -0,0 +1,19 @@ +#!/usr/bin/env bash +# +# Copyright (c) 2018 The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. +# +# Find dead Python code. + +export LC_ALL=C + +if ! command -v vulture > /dev/null; then + echo "Skipping Python dead code linting since vulture is not installed. Install by running \"pip3 install vulture\"" + exit 0 +fi + +vulture \ + --min-confidence 60 \ + --ignore-names "argtypes,connection_lost,connection_made,converter,data_received,daemon,errcheck,get_ecdh_key,get_privkey,is_compressed,is_fullyvalid,msg_generic,on_*,optionxform,restype,set_privkey" \ + $(git ls-files -- "*.py" ":(exclude)contrib/" ":(exclude)test/functional/data/invalid_txs.py") diff --git a/test/lint/lint-python.sh b/test/lint/lint-python.sh index 4f4542ff0c..f5b851aeab 100755 --- a/test/lint/lint-python.sh +++ b/test/lint/lint-python.sh @@ -1,4 +1,4 @@ -#!/bin/sh +#!/usr/bin/env bash # # Copyright (c) 2017 The Bitcoin Core developers # Distributed under the MIT software license, see the accompanying @@ -8,76 +8,79 @@ export LC_ALL=C -# E101 indentation contains mixed spaces and tabs -# E112 expected an indented block -# E113 unexpected indentation -# E115 expected an indented block (comment) -# E116 unexpected indentation (comment) -# E125 continuation line with same indent as next logical line -# E129 visually indented line with same indent as next logical line -# E131 continuation line unaligned for hanging indent -# E133 closing bracket is missing indentation -# E223 tab before operator -# E224 tab after operator -# E242 tab after ',' -# E266 too many leading '#' for block comment -# E271 multiple spaces after keyword -# E272 multiple spaces before keyword -# E273 tab after keyword -# E274 tab before keyword -# E275 missing whitespace after keyword -# E304 blank lines found after function decorator -# E306 expected 1 blank line before a nested definition -# E401 multiple imports on one line -# E402 module level import not at top of file -# F403 'from foo_module import *' used; unable to detect undefined names -# F405 foo_function may be undefined, or defined from star imports: bar_module -# E502 the backslash is redundant between brackets -# E701 multiple statements on one line (colon) -# E702 multiple statements on one line (semicolon) -# E703 statement ends with a semicolon -# E714 test for object identity should be "is not" -# E721 do not compare types, use "isinstance()" -# E741 do not use variables named "l", "O", or "I" -# E742 do not define classes named "l", "O", or "I" -# E743 do not define functions named "l", "O", or "I" -# E901 SyntaxError: invalid syntax -# E902 TokenError: EOF in multi-line string -# F401 module imported but unused -# F402 import module from line N shadowed by loop variable -# F404 future import(s) name after other statements -# F406 "from module import *" only allowed at module level -# F407 an undefined __future__ feature name was imported -# F601 dictionary key name repeated with different values -# F602 dictionary key variable name repeated with different values -# F621 too many expressions in an assignment with star-unpacking -# F622 two or more starred expressions in an assignment (a, *b, *c = d) -# F631 assertion test is a tuple, which are always True -# F701 a break statement outside of a while or for loop -# F702 a continue statement outside of a while or for loop -# F703 a continue statement in a finally block in a loop -# F704 a yield or yield from statement outside of a function -# F705 a return statement with arguments inside a generator -# F706 a return statement outside of a function/method -# F707 an except: block as not the last exception handler -# F811 redefinition of unused name from line N -# F812 list comprehension redefines 'foo' from line N -# F821 undefined name 'Foo' -# F822 undefined name name in __all__ -# F823 local variable name … referenced before assignment -# F831 duplicate argument name in function definition -# F841 local variable 'foo' is assigned to but never used -# W191 indentation contains tabs -# W291 trailing whitespace -# W292 no newline at end of file -# W293 blank line contains whitespace -# W504 line break after binary operator -# W601 .has_key() is deprecated, use "in" -# W602 deprecated form of raising exception -# W603 "<>" is deprecated, use "!=" -# W604 backticks are deprecated, use "repr()" -# W605 invalid escape sequence "x" -# W606 'async' and 'await' are reserved keywords starting with Python 3.7 +enabled=( + E101 # indentation contains mixed spaces and tabs + E112 # expected an indented block + E113 # unexpected indentation + E115 # expected an indented block (comment) + E116 # unexpected indentation (comment) + E125 # continuation line with same indent as next logical line + E129 # visually indented line with same indent as next logical line + E131 # continuation line unaligned for hanging indent + E133 # closing bracket is missing indentation + E223 # tab before operator + E224 # tab after operator + E242 # tab after ',' + E266 # too many leading '#' for block comment + E271 # multiple spaces after keyword + E272 # multiple spaces before keyword + E273 # tab after keyword + E274 # tab before keyword + E275 # missing whitespace after keyword + E304 # blank lines found after function decorator + E306 # expected 1 blank line before a nested definition + E401 # multiple imports on one line + E402 # module level import not at top of file + E502 # the backslash is redundant between brackets + E701 # multiple statements on one line (colon) + E702 # multiple statements on one line (semicolon) + E703 # statement ends with a semicolon + E711 # comparison to None should be 'if cond is None:' + E714 # test for object identity should be "is not" + E721 # do not compare types, use "isinstance()" + E741 # do not use variables named "l", "O", or "I" + E742 # do not define classes named "l", "O", or "I" + E743 # do not define functions named "l", "O", or "I" + E901 # SyntaxError: invalid syntax + E902 # TokenError: EOF in multi-line string + F401 # module imported but unused + F402 # import module from line N shadowed by loop variable + F403 # 'from foo_module import *' used; unable to detect undefined names + F404 # future import(s) name after other statements + F405 # foo_function may be undefined, or defined from star imports: bar_module + F406 # "from module import *" only allowed at module level + F407 # an undefined __future__ feature name was imported + F601 # dictionary key name repeated with different values + F602 # dictionary key variable name repeated with different values + F621 # too many expressions in an assignment with star-unpacking + F622 # two or more starred expressions in an assignment (a, *b, *c = d) + F631 # assertion test is a tuple, which are always True + F701 # a break statement outside of a while or for loop + F702 # a continue statement outside of a while or for loop + F703 # a continue statement in a finally block in a loop + F704 # a yield or yield from statement outside of a function + F705 # a return statement with arguments inside a generator + F706 # a return statement outside of a function/method + F707 # an except: block as not the last exception handler + F811 # redefinition of unused name from line N + F812 # list comprehension redefines 'foo' from line N + F821 # undefined name 'Foo' + F822 # undefined name name in __all__ + F823 # local variable name … referenced before assignment + F831 # duplicate argument name in function definition + F841 # local variable 'foo' is assigned to but never used + W191 # indentation contains tabs + W291 # trailing whitespace + W292 # no newline at end of file + W293 # blank line contains whitespace + W504 # line break after binary operator + W601 # .has_key() is deprecated, use "in" + W602 # deprecated form of raising exception + W603 # "<>" is deprecated, use "!=" + W604 # backticks are deprecated, use "repr()" + W605 # invalid escape sequence "x" + W606 # 'async' and 'await' are reserved keywords starting with Python 3.7 +) if ! command -v flake8 > /dev/null; then echo "Skipping Python linting since flake8 is not installed. Install by running \"pip3 install flake8\"" @@ -87,4 +90,4 @@ elif PYTHONWARNINGS="ignore" flake8 --version | grep -q "Python 2"; then exit 0 fi -PYTHONWARNINGS="ignore" flake8 --ignore=B,C,E,F,I,N,W --select=E101,E112,E113,E115,E116,E125,E129,E131,E133,E223,E224,E242,E266,E271,E272,E273,E274,E275,E304,E306,E401,E402,E502,E701,E702,E703,E714,E721,E741,E742,E743,E901,E902,F401,F402,F403,F404,F405,F406,F407,F601,F602,F621,F622,F631,F701,F702,F703,F704,F705,F706,F707,F811,F812,F821,F822,F823,F831,F841,W191,W291,W292,W293,W504,W601,W602,W603,W604,W605,W606 . +PYTHONWARNINGS="ignore" flake8 --ignore=B,C,E,F,I,N,W --select=$(IFS=","; echo "${enabled[*]}") "${@:-.}" diff --git a/test/lint/lint-rpc-help.sh b/test/lint/lint-rpc-help.sh new file mode 100755 index 0000000000..faac5d43e2 --- /dev/null +++ b/test/lint/lint-rpc-help.sh @@ -0,0 +1,24 @@ +#!/usr/bin/env bash +# +# Copyright (c) 2018 The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. +# +# Check that all RPC help texts are generated by RPCHelpMan. + +export LC_ALL=C + +EXIT_CODE=0 + +# Assume that all multiline strings passed into a runtime_error are help texts. +# This is potentially fragile, but the linter is only temporary and can safely +# be removed early 2019. + +non_autogenerated_help=$(grep --perl-regexp --null-data --only-matching 'runtime_error\(\n\s*".*\\n"\n' $(git ls-files -- "*.cpp")) +if [[ ${non_autogenerated_help} != "" ]]; then + echo "Must use RPCHelpMan to generate the help for the following RPC methods:" + echo "${non_autogenerated_help}" + echo + EXIT_CODE=1 +fi +exit ${EXIT_CODE} diff --git a/test/lint/lint-python-shebang.sh b/test/lint/lint-shebang.sh index 4ff87f0bf7..fda22592d3 100755 --- a/test/lint/lint-python-shebang.sh +++ b/test/lint/lint-shebang.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -# Shebang must use python3 (not python or python2) +# Assert expected shebang lines export LC_ALL=C EXIT_CODE=0 @@ -10,4 +10,11 @@ for PYTHON_FILE in $(git ls-files -- "*.py"); do EXIT_CODE=1 fi done +for SHELL_FILE in $(git ls-files -- "*.sh"); do + if [[ $(head -n 1 "${SHELL_FILE}") != "#!/usr/bin/env bash" && + $(head -n 1 "${SHELL_FILE}") != "#!/bin/sh" ]]; then + echo "Missing expected shebang \"#!/usr/bin/env bash\" or \"#!/bin/sh\" in ${SHELL_FILE}" + EXIT_CODE=1 + fi +done exit ${EXIT_CODE} diff --git a/test/lint/lint-shell.sh b/test/lint/lint-shell.sh index 9af3c10ed6..6f5e6546c5 100755 --- a/test/lint/lint-shell.sh +++ b/test/lint/lint-shell.sh @@ -13,7 +13,7 @@ export LC_ALL=C # respectively. So export LC_ALL=C is set as required by lint-shell-locale.sh # but unset here in case of running in Travis. if [ "$TRAVIS" = "true" ]; then - unset LC_ALL + unset LC_ALL fi if ! command -v shellcheck > /dev/null; then @@ -22,26 +22,26 @@ if ! command -v shellcheck > /dev/null; then fi # Disabled warnings: -# SC1087: Use braces when expanding arrays, e.g. ${array[idx]} (or ${var}[.. to quiet). -# SC1117: Backslash is literal in "\.". Prefer explicit escaping: "\\.". -# SC2001: See if you can use ${variable//search/replace} instead. -# SC2004: $/${} is unnecessary on arithmetic variables. -# SC2005: Useless echo? Instead of 'echo $(cmd)', just use 'cmd'. -# SC2006: Use $(..) instead of legacy `..`. -# SC2016: Expressions don't expand in single quotes, use double quotes for that. -# SC2028: echo won't expand escape sequences. Consider printf. -# SC2046: Quote this to prevent word splitting. -# SC2048: Use "$@" (with quotes) to prevent whitespace problems. -# SC2066: Since you double quoted this, it will not word split, and the loop will only run once. -# SC2086: Double quote to prevent globbing and word splitting. -# SC2116: Useless echo? Instead of 'cmd $(echo foo)', just use 'cmd foo'. -# SC2148: Tips depend on target shell and yours is unknown. Add a shebang. -# SC2162: read without -r will mangle backslashes. -# SC2166: Prefer [ p ] && [ q ] as [ p -a q ] is not well defined. -# SC2166: Prefer [ p ] || [ q ] as [ p -o q ] is not well defined. -# SC2181: Check exit code directly with e.g. 'if mycmd;', not indirectly with $?. -# SC2206: Quote to prevent word splitting, or split robustly with mapfile or read -a. -# SC2207: Prefer mapfile or read -a to split command output (or quote to avoid splitting). -# SC2230: which is non-standard. Use builtin 'command -v' instead. -shellcheck -e SC1087,SC1117,SC2001,SC2004,SC2005,SC2006,SC2016,SC2028,SC2046,SC2048,SC2066,SC2086,SC2116,SC2148,SC2162,SC2166,SC2181,SC2206,SC2207,SC2230 \ +disabled=( + SC1087 # Use braces when expanding arrays, e.g. ${array[idx]} (or ${var}[.. to quiet). + SC2001 # See if you can use ${variable//search/replace} instead. + SC2004 # $/${} is unnecessary on arithmetic variables. + SC2005 # Useless echo? Instead of 'echo $(cmd)', just use 'cmd'. + SC2006 # Use $(..) instead of legacy `..`. + SC2016 # Expressions don't expand in single quotes, use double quotes for that. + SC2028 # echo won't expand escape sequences. Consider printf. + SC2046 # Quote this to prevent word splitting. + SC2048 # Use "$@" (with quotes) to prevent whitespace problems. + SC2066 # Since you double quoted this, it will not word split, and the loop will only run once. + SC2086 # Double quote to prevent globbing and word splitting. + SC2116 # Useless echo? Instead of 'cmd $(echo foo)', just use 'cmd foo'. + SC2162 # read without -r will mangle backslashes. + SC2166 # Prefer [ p ] {&&,||} [ q ] as [ p -{a,o} q ] is not well defined. + SC2181 # Check exit code directly with e.g. 'if mycmd;', not indirectly with $?. + SC2206 # Quote to prevent word splitting, or split robustly with mapfile or read -a. + SC2207 # Prefer mapfile or read -a to split command output (or quote to avoid splitting). + SC2230 # which is non-standard. Use builtin 'command -v' instead. + SC2236 # Don't force -n instead of ! -z. +) +shellcheck -e "$(IFS=","; echo "${disabled[*]}")" \ $(git ls-files -- "*.sh" | grep -vE 'src/(secp256k1|univalue)/') diff --git a/test/lint/lint-spelling.ignore-words.txt b/test/lint/lint-spelling.ignore-words.txt index 9a49f32271..f0415443db 100644 --- a/test/lint/lint-spelling.ignore-words.txt +++ b/test/lint/lint-spelling.ignore-words.txt @@ -1,6 +1,7 @@ cas hights mor +mut objext unselect useable diff --git a/test/lint/lint-spelling.sh b/test/lint/lint-spelling.sh index ebeafd7d58..5d672698a7 100755 --- a/test/lint/lint-spelling.sh +++ b/test/lint/lint-spelling.sh @@ -9,10 +9,7 @@ export LC_ALL=C -EXIT_CODE=0 IGNORE_WORDS_FILE=test/lint/lint-spelling.ignore-words.txt if ! codespell --check-filenames --disable-colors --quiet-level=7 --ignore-words=${IGNORE_WORDS_FILE} $(git ls-files -- ":(exclude)build-aux/m4/" ":(exclude)contrib/seeds/*.txt" ":(exclude)depends/" ":(exclude)doc/release-notes/" ":(exclude)src/leveldb/" ":(exclude)src/qt/locale/" ":(exclude)src/secp256k1/" ":(exclude)src/univalue/"); then echo "^ Warning: codespell identified likely spelling errors. Any false positives? Add them to the list of ignored words in ${IGNORE_WORDS_FILE}" - EXIT_CODE=1 fi -exit ${EXIT_CODE} diff --git a/test/lint/lint-whitespace.sh b/test/lint/lint-whitespace.sh index beb7ec42f4..f318e19071 100755 --- a/test/lint/lint-whitespace.sh +++ b/test/lint/lint-whitespace.sh @@ -23,7 +23,7 @@ while getopts "?" opt; do done if [ -z "${TRAVIS_COMMIT_RANGE}" ]; then - if [ "$1" ]; then + if [ -n "$1" ]; then TRAVIS_COMMIT_RANGE="HEAD~$1...HEAD" else TRAVIS_COMMIT_RANGE="HEAD" diff --git a/test/sanitizer_suppressions/lsan b/test/sanitizer_suppressions/lsan new file mode 100644 index 0000000000..6f15c0f1d4 --- /dev/null +++ b/test/sanitizer_suppressions/lsan @@ -0,0 +1,6 @@ +# Suppress warnings triggered in dependencies +leak:libcrypto +leak:libqminimal +leak:libQt5Core +leak:libQt5Gui +leak:libQt5Widgets diff --git a/test/sanitizer_suppressions/tsan b/test/sanitizer_suppressions/tsan new file mode 100644 index 0000000000..70eea34363 --- /dev/null +++ b/test/sanitizer_suppressions/tsan @@ -0,0 +1,16 @@ +# ThreadSanitizer suppressions +# ============================ + +# WalletBatch (unidentified deadlock) +deadlock:WalletBatch + +# Intentional deadlock in tests +deadlock:TestPotentialDeadLockDetected + +# Wildcard for all gui tests, should be replaced with non-wildcard suppressions +race:src/qt/test/* +deadlock:src/qt/test/* + +# External libraries +deadlock:libdb +race:libzmq diff --git a/test/sanitizer_suppressions/ubsan b/test/sanitizer_suppressions/ubsan new file mode 100644 index 0000000000..f0107f1361 --- /dev/null +++ b/test/sanitizer_suppressions/ubsan @@ -0,0 +1,35 @@ +alignment:move.h +alignment:prevector.h +bool:wallet/wallet.cpp +float-divide-by-zero:policy/fees.cpp +float-divide-by-zero:validation.cpp +float-divide-by-zero:wallet/wallet.cpp +nonnull-attribute:support/cleanse.cpp +unsigned-integer-overflow:arith_uint256.h +unsigned-integer-overflow:basic_string.h +unsigned-integer-overflow:bench/bench.h +unsigned-integer-overflow:bitcoin-tx.cpp +unsigned-integer-overflow:bloom.cpp +unsigned-integer-overflow:chain.cpp +unsigned-integer-overflow:chain.h +unsigned-integer-overflow:coded_stream.h +unsigned-integer-overflow:core_write.cpp +unsigned-integer-overflow:crypto/chacha20.cpp +unsigned-integer-overflow:crypto/ctaes/ctaes.c +unsigned-integer-overflow:crypto/ripemd160.cpp +unsigned-integer-overflow:crypto/sha1.cpp +unsigned-integer-overflow:crypto/sha256.cpp +unsigned-integer-overflow:crypto/sha512.cpp +unsigned-integer-overflow:hash.cpp +unsigned-integer-overflow:leveldb/db/log_reader.cc +unsigned-integer-overflow:leveldb/util/bloom.cc +unsigned-integer-overflow:leveldb/util/crc32c.h +unsigned-integer-overflow:leveldb/util/hash.cc +unsigned-integer-overflow:policy/fees.cpp +unsigned-integer-overflow:prevector.h +unsigned-integer-overflow:script/interpreter.cpp +unsigned-integer-overflow:stl_bvector.h +unsigned-integer-overflow:txmempool.cpp +unsigned-integer-overflow:util/strencodings.cpp +unsigned-integer-overflow:validation.cpp +vptr:fs.cpp diff --git a/test/util/bitcoin-util-test.py b/test/util/bitcoin-util-test.py index 92fef30e13..7b1cc2b031 100755 --- a/test/util/bitcoin-util-test.py +++ b/test/util/bitcoin-util-test.py @@ -9,14 +9,9 @@ Runs automatically during `make check`. Can also be run manually.""" -from __future__ import division,print_function,unicode_literals - import argparse import binascii -try: - import configparser -except ImportError: - import ConfigParser as configparser +import configparser import difflib import json import logging diff --git a/test/util/rpcauth-test.py b/test/util/rpcauth-test.py index 46e9fbc739..53058dc394 100755 --- a/test/util/rpcauth-test.py +++ b/test/util/rpcauth-test.py @@ -24,8 +24,8 @@ class TestRPCAuth(unittest.TestCase): self.rpcauth = importlib.import_module('rpcauth') def test_generate_salt(self): - self.assertLessEqual(len(self.rpcauth.generate_salt()), 32) - self.assertGreaterEqual(len(self.rpcauth.generate_salt()), 16) + for i in range(16, 32 + 1): + self.assertEqual(len(self.rpcauth.generate_salt(i)), i * 2) def test_generate_password(self): password = self.rpcauth.generate_password() @@ -34,7 +34,7 @@ class TestRPCAuth(unittest.TestCase): self.assertEqual(expected_password, password) def test_check_password_hmac(self): - salt = self.rpcauth.generate_salt() + salt = self.rpcauth.generate_salt(16) password = self.rpcauth.generate_password() password_hmac = self.rpcauth.password_to_hmac(salt, password) |